[rstudio] 01/02: Imported Upstream version 0.98.636+dfsg
Andreas Tille
tille at debian.org
Fri Feb 7 19:22:58 UTC 2014
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository rstudio.
commit ca24d6865ffc1d83c699403e45747b2e3048ebcf
Author: Andreas Tille <tille at debian.org>
Date: Fri Feb 7 20:15:43 2014 +0100
Imported Upstream version 0.98.636+dfsg
---
.gitignore | 23 +
.gitmodules | 0
CMakeCompiler.txt | 20 +
CMakeGlobals.txt | 143 +
CMakeInstallDocs.txt | 22 +
CMakeLists.txt | 64 +
COPYING | 672 +
INSTALL | 166 +
NOTICE | 3260 ++
README.md | 45 +
SOURCE.in | 14 +
VERSION.in | 1 +
cmake/modules/CPackRPM.cmake | 625 +
cmake/modules/FindLibR.cmake | 183 +
cmake/modules/FindPAM.cmake | 77 +
package/.gitignore | 3 +
package/CMakeLists.txt | 23 +
package/linux/.gitignore | 2 +
package/linux/CMakeLists.txt | 115 +
package/linux/debian-control/postinst-desktop.in | 10 +
package/linux/debian-control/postinst.in | 56 +
package/linux/debian-control/postrm-desktop.in | 10 +
package/linux/debian-control/postrm.in | 14 +
package/linux/install-dependencies | 16 +
package/linux/make-desktop-package | 45 +
package/linux/make-package | 67 +
package/linux/make-server-package | 7 +
package/linux/rpm-script/postinst-desktop.sh.in | 13 +
package/linux/rpm-script/postinst.sh.in | 56 +
package/linux/rpm-script/postrm-desktop.sh.in | 10 +
package/linux/rpm-script/postrm.sh.in | 15 +
package/osx/CMakeLists.txt | 28 +
package/osx/make-package | 32 +
package/win32/.gitignore | 1 +
package/win32/CMakeLists.txt | 91 +
.../win32/cert/After_10-10-10_MSCV-VSClass3.cer | 32 +
package/win32/cert/Symantec.cer | 28 +
package/win32/clean-build.bat | 11 +
package/win32/cmake/modules/NSIS.template.in | 1016 +
package/win32/codesign.bat | 13 +
package/win32/make-install-win64.bat | 26 +
package/win32/make-package.bat | 43 +
package/win32/src/ssh-console.bat | 4 +
src/.gitignore | 3 +
src/CMakeLists.txt | 41 +
src/cpp/.gitignore | 1 +
src/cpp/CMakeLists.txt | 262 +
src/cpp/conf/rdesktop-dev.conf | 57 +
src/cpp/conf/rserver-dev.conf | 44 +
src/cpp/conf/rsession-dev.conf | 40 +
src/cpp/core/Assert.cpp | 79 +
src/cpp/core/Base64.cpp | 77 +
src/cpp/core/BoostErrors.cpp | 213 +
src/cpp/core/CMakeLists.txt | 270 +
src/cpp/core/ConfigUtils.cpp | 79 +
src/cpp/core/DateTime.cpp | 95 +
src/cpp/core/Error.cpp | 303 +
src/cpp/core/Exec.cpp | 48 +
src/cpp/core/FileInfo.cpp | 72 +
src/cpp/core/FileLock.cpp | 175 +
src/cpp/core/FileLogWriter.cpp | 93 +
src/cpp/core/FilePath.cpp | 1131 +
src/cpp/core/FileSerializer.cpp | 260 +
src/cpp/core/FileUtils.cpp | 45 +
src/cpp/core/GitGraph.cpp | 163 +
src/cpp/core/Hash.cpp | 51 +
src/cpp/core/HtmlUtils.cpp | 125 +
src/cpp/core/Log.cpp | 172 +
src/cpp/core/LogWriter.cpp | 48 +
src/cpp/core/PerformanceTimer.cpp | 133 +
src/cpp/core/PosixStringUtils.cpp | 88 +
src/cpp/core/ProgramOptions.cpp | 193 +
src/cpp/core/RegexUtils.cpp | 117 +
src/cpp/core/SafeConvert.cpp | 27 +
src/cpp/core/Settings.cpp | 146 +
src/cpp/core/StderrLogWriter.cpp | 61 +
src/cpp/core/StringUtils.cpp | 353 +
src/cpp/core/SyslogLogWriter.cpp | 109 +
src/cpp/core/Thread.cpp | 63 +
src/cpp/core/Trace.cpp | 45 +
src/cpp/core/WaitUtils.cpp | 65 +
src/cpp/core/Win32StringUtils.cpp | 82 +
src/cpp/core/config.h.in | 23 +
src/cpp/core/dev/CMakeLists.txt | 44 +
src/cpp/core/dev/Main.cpp | 47 +
src/cpp/core/dev/coredev-profile.in | 36 +
src/cpp/core/gwt/GwtFileHandler.cpp | 217 +
src/cpp/core/gwt/GwtLogHandler.cpp | 293 +
src/cpp/core/gwt/GwtSymbolMaps.cpp | 365 +
src/cpp/core/http/Cookie.cpp | 105 +
src/cpp/core/http/Header.cpp | 98 +
src/cpp/core/http/Message.cpp | 245 +
src/cpp/core/http/MultipartRelated.cpp | 52 +
src/cpp/core/http/NamedPipeProtocol.cpp | 48 +
src/cpp/core/http/Request.cpp | 254 +
src/cpp/core/http/RequestParser.cpp | 359 +
src/cpp/core/http/Response.cpp | 453 +
src/cpp/core/http/SocketProxy.cpp | 147 +
src/cpp/core/http/URL.cpp | 308 +
src/cpp/core/http/UriHandler.cpp | 98 +
src/cpp/core/http/Util.cpp | 360 +
src/cpp/core/include/core/Algorithm.hpp | 62 +
src/cpp/core/include/core/Base64.hpp | 37 +
src/cpp/core/include/core/BoostErrors.hpp | 47 +
src/cpp/core/include/core/BoostLamda.hpp | 31 +
src/cpp/core/include/core/BoostThread.hpp | 30 +
src/cpp/core/include/core/ConfigUtils.hpp | 39 +
src/cpp/core/include/core/DateTime.hpp | 45 +
src/cpp/core/include/core/Error.hpp | 188 +
src/cpp/core/include/core/Exec.hpp | 72 +
src/cpp/core/include/core/FileInfo.hpp | 149 +
src/cpp/core/include/core/FileLock.hpp | 51 +
src/cpp/core/include/core/FileLogWriter.hpp | 50 +
src/cpp/core/include/core/FilePath.hpp | 236 +
src/cpp/core/include/core/FileSerializer.hpp | 348 +
src/cpp/core/include/core/FileUtils.hpp | 36 +
src/cpp/core/include/core/GitGraph.hpp | 87 +
src/cpp/core/include/core/Hash.hpp | 33 +
src/cpp/core/include/core/HtmlUtils.hpp | 63 +
src/cpp/core/include/core/IncrementalCommand.hpp | 70 +
src/cpp/core/include/core/Log.hpp | 66 +
src/cpp/core/include/core/LogWriter.hpp | 49 +
src/cpp/core/include/core/PerformanceTimer.hpp | 64 +
src/cpp/core/include/core/PeriodicCommand.hpp | 70 +
src/cpp/core/include/core/Predicate.hpp | 43 +
src/cpp/core/include/core/ProgramOptions.hpp | 76 +
src/cpp/core/include/core/ProgramStatus.hpp | 58 +
src/cpp/core/include/core/Promise.hpp | 63 +
src/cpp/core/include/core/Random.hpp | 49 +
src/cpp/core/include/core/RegexUtils.hpp | 58 +
src/cpp/core/include/core/SafeConvert.hpp | 93 +
src/cpp/core/include/core/ScheduledCommand.hpp | 63 +
src/cpp/core/include/core/Scope.hpp | 73 +
src/cpp/core/include/core/Settings.hpp | 70 +
src/cpp/core/include/core/StderrLogWriter.hpp | 43 +
src/cpp/core/include/core/StringUtils.hpp | 179 +
src/cpp/core/include/core/SyslogLogWriter.hpp | 41 +
src/cpp/core/include/core/Thread.hpp | 292 +
src/cpp/core/include/core/Trace.hpp | 36 +
src/cpp/core/include/core/WaitUtils.hpp | 50 +
src/cpp/core/include/core/collection/Tree.hpp | 2806 ++
src/cpp/core/include/core/gwt/GwtFileHandler.hpp | 35 +
src/cpp/core/include/core/gwt/GwtLogHandler.hpp | 44 +
src/cpp/core/include/core/gwt/GwtSymbolMaps.hpp | 64 +
src/cpp/core/include/core/http/AsyncClient.hpp | 482 +
src/cpp/core/include/core/http/AsyncConnection.hpp | 58 +
.../core/include/core/http/AsyncConnectionImpl.hpp | 264 +
src/cpp/core/include/core/http/AsyncServer.hpp | 75 +
src/cpp/core/include/core/http/AsyncServerImpl.hpp | 470 +
src/cpp/core/include/core/http/AsyncUriHandler.hpp | 112 +
src/cpp/core/include/core/http/BlockingClient.hpp | 73 +
src/cpp/core/include/core/http/BoostAsioSsl.hpp | 32 +
.../include/core/http/ConnectionRetryProfile.hpp | 55 +
src/cpp/core/include/core/http/Cookie.hpp | 74 +
src/cpp/core/include/core/http/Header.hpp | 70 +
.../include/core/http/LocalStreamAsyncClient.hpp | 107 +
.../include/core/http/LocalStreamAsyncServer.hpp | 127 +
.../core/http/LocalStreamBlockingClient.hpp | 46 +
.../include/core/http/LocalStreamSocketUtils.hpp | 88 +
src/cpp/core/include/core/http/Message.hpp | 154 +
.../core/include/core/http/MultipartRelated.hpp | 50 +
.../include/core/http/NamedPipeAsyncClient.hpp | 134 +
.../include/core/http/NamedPipeBlockingClient.hpp | 47 +
.../core/include/core/http/NamedPipeProtocol.hpp | 45 +
src/cpp/core/include/core/http/Request.hpp | 160 +
src/cpp/core/include/core/http/RequestParser.hpp | 131 +
src/cpp/core/include/core/http/Response.hpp | 360 +
src/cpp/core/include/core/http/ResponseParser.hpp | 152 +
src/cpp/core/include/core/http/Socket.hpp | 49 +
.../include/core/http/SocketAcceptorService.hpp | 106 +
src/cpp/core/include/core/http/SocketProxy.hpp | 77 +
src/cpp/core/include/core/http/SocketUtils.hpp | 89 +
.../core/include/core/http/TcpIpAsyncClient.hpp | 89 +
.../core/include/core/http/TcpIpAsyncClientSsl.hpp | 156 +
.../core/include/core/http/TcpIpAsyncConnector.hpp | 171 +
.../core/include/core/http/TcpIpAsyncServer.hpp | 48 +
.../core/include/core/http/TcpIpBlockingClient.hpp | 48 +
.../include/core/http/TcpIpBlockingClientSsl.hpp | 48 +
.../core/include/core/http/TcpIpSocketUtils.hpp | 114 +
src/cpp/core/include/core/http/URL.hpp | 136 +
src/cpp/core/include/core/http/UriHandler.hpp | 93 +
src/cpp/core/include/core/http/Util.hpp | 158 +
src/cpp/core/include/core/json/Json.hpp | 91 +
src/cpp/core/include/core/json/JsonRpc.hpp | 1037 +
.../include/core/json/spirit/json_spirit_value.h | 532 +
src/cpp/core/include/core/markdown/Markdown.hpp | 108 +
src/cpp/core/include/core/r_util/REnvironment.hpp | 45 +
src/cpp/core/include/core/r_util/RPackageInfo.hpp | 71 +
src/cpp/core/include/core/r_util/RProjectFile.hpp | 121 +
.../include/core/r_util/RSessionLaunchProfile.hpp | 45 +
src/cpp/core/include/core/r_util/RSourceIndex.hpp | 270 +
src/cpp/core/include/core/r_util/RTokenizer.hpp | 221 +
src/cpp/core/include/core/r_util/RToolsInfo.hpp | 80 +
src/cpp/core/include/core/rapidxml/rapidxml.hpp | 2596 ++
.../core/spelling/HunspellCustomDictionaries.hpp | 59 +
.../core/spelling/HunspellDictionaryManager.hpp | 108 +
.../core/spelling/HunspellSpellingEngine.hpp | 67 +
.../core/include/core/spelling/SpellingEngine.hpp | 51 +
src/cpp/core/include/core/system/Crypto.hpp | 60 +
src/cpp/core/include/core/system/Environment.hpp | 87 +
.../core/include/core/system/FileChangeEvent.hpp | 179 +
src/cpp/core/include/core/system/FileMode.hpp | 103 +
src/cpp/core/include/core/system/FileMonitor.hpp | 160 +
src/cpp/core/include/core/system/FileScanner.hpp | 66 +
src/cpp/core/include/core/system/LibraryLoader.hpp | 34 +
src/cpp/core/include/core/system/OutputCapture.hpp | 37 +
src/cpp/core/include/core/system/Pam.hpp | 58 +
.../include/core/system/ParentProcessMonitor.hpp | 39 +
.../core/system/PosixChildProcessTracker.hpp | 59 +
src/cpp/core/include/core/system/PosixSystem.hpp | 127 +
src/cpp/core/include/core/system/PosixUser.hpp | 65 +
src/cpp/core/include/core/system/Process.hpp | 328 +
src/cpp/core/include/core/system/ProcessArgs.hpp | 96 +
src/cpp/core/include/core/system/RecycleBin.hpp | 35 +
src/cpp/core/include/core/system/RegistryKey.hpp | 57 +
src/cpp/core/include/core/system/ShellUtils.hpp | 122 +
src/cpp/core/include/core/system/System.hpp | 271 +
src/cpp/core/include/core/system/Types.hpp | 31 +
src/cpp/core/include/core/tex/TexLogParser.hpp | 86 +
src/cpp/core/include/core/tex/TexMagicComment.hpp | 61 +
src/cpp/core/include/core/tex/TexSynctex.hpp | 143 +
src/cpp/core/include/core/text/CsvParser.hpp | 132 +
src/cpp/core/include/core/text/DcfParser.hpp | 54 +
src/cpp/core/include/core/text/TemplateFilter.hpp | 96 +
src/cpp/core/json/Json.cpp | 79 +
src/cpp/core/json/JsonRpc.cpp | 380 +
src/cpp/core/json/spirit/LICENSE.txt | 24 +
src/cpp/core/json/spirit/json_spirit.h | 18 +
.../core/json/spirit/json_spirit_error_position.h | 54 +
src/cpp/core/json/spirit/json_spirit_reader.cpp | 137 +
src/cpp/core/json/spirit/json_spirit_reader.h | 62 +
.../core/json/spirit/json_spirit_reader_template.h | 612 +
.../core/json/spirit/json_spirit_stream_reader.h | 70 +
src/cpp/core/json/spirit/json_spirit_strings.h | 20 +
src/cpp/core/json/spirit/json_spirit_utils.h | 61 +
src/cpp/core/json/spirit/json_spirit_value.cpp | 8 +
src/cpp/core/json/spirit/json_spirit_value.h | 15 +
src/cpp/core/json/spirit/json_spirit_writer.cpp | 95 +
src/cpp/core/json/spirit/json_spirit_writer.h | 50 +
.../core/json/spirit/json_spirit_writer_template.h | 255 +
src/cpp/core/markdown/Markdown.cpp | 461 +
src/cpp/core/markdown/MathJax.cpp | 272 +
src/cpp/core/markdown/MathJax.hpp | 104 +
src/cpp/core/markdown/sundown/autolink.c | 264 +
src/cpp/core/markdown/sundown/autolink.h | 44 +
src/cpp/core/markdown/sundown/buffer.c | 225 +
src/cpp/core/markdown/sundown/buffer.h | 96 +
src/cpp/core/markdown/sundown/houdini.h | 37 +
src/cpp/core/markdown/sundown/houdini_href_e.c | 108 +
src/cpp/core/markdown/sundown/houdini_html_e.c | 84 +
src/cpp/core/markdown/sundown/html.c | 635 +
src/cpp/core/markdown/sundown/html.h | 77 +
src/cpp/core/markdown/sundown/html_blocks.h | 206 +
src/cpp/core/markdown/sundown/html_smartypants.c | 389 +
src/cpp/core/markdown/sundown/markdown.c | 2551 ++
src/cpp/core/markdown/sundown/markdown.h | 138 +
src/cpp/core/markdown/sundown/stack.c | 81 +
src/cpp/core/markdown/sundown/stack.h | 29 +
src/cpp/core/markdown/sundown/sundown_version.h | 1 +
src/cpp/core/r_util/REnvironmentPosix.cpp | 709 +
src/cpp/core/r_util/RPackageInfo.cpp | 123 +
src/cpp/core/r_util/RProjectFile.cpp | 780 +
src/cpp/core/r_util/RSessionLaunchProfile.cpp | 107 +
src/cpp/core/r_util/RSourceIndex.cpp | 377 +
src/cpp/core/r_util/RTokenizer.cpp | 373 +
src/cpp/core/r_util/RTokenizerTests.cpp | 244 +
src/cpp/core/r_util/RToolsInfo.cpp | 174 +
.../core/spelling/HunspellCustomDictionaries.cpp | 85 +
.../core/spelling/HunspellDictionaryManager.cpp | 205 +
src/cpp/core/spelling/HunspellSpellingEngine.cpp | 489 +
src/cpp/core/spelling/hunspell/CMakeLists.txt | 60 +
src/cpp/core/spelling/hunspell/README | 21 +
src/cpp/core/spelling/hunspell/affentry.cxx | 962 +
src/cpp/core/spelling/hunspell/affentry.hxx | 136 +
src/cpp/core/spelling/hunspell/affixmgr.cxx | 4525 +++
src/cpp/core/spelling/hunspell/affixmgr.hxx | 250 +
src/cpp/core/spelling/hunspell/atypes.hxx | 107 +
src/cpp/core/spelling/hunspell/baseaffix.hxx | 28 +
src/cpp/core/spelling/hunspell/config.h.in | 15 +
src/cpp/core/spelling/hunspell/csutil.cxx | 5834 ++++
src/cpp/core/spelling/hunspell/csutil.hxx | 220 +
src/cpp/core/spelling/hunspell/dictmgr.cxx | 180 +
src/cpp/core/spelling/hunspell/dictmgr.hxx | 36 +
src/cpp/core/spelling/hunspell/filemgr.cxx | 49 +
src/cpp/core/spelling/hunspell/filemgr.hxx | 25 +
src/cpp/core/spelling/hunspell/hashmgr.cxx | 928 +
src/cpp/core/spelling/hunspell/hashmgr.hxx | 69 +
src/cpp/core/spelling/hunspell/htypes.hxx | 32 +
src/cpp/core/spelling/hunspell/hunspell.cxx | 2006 ++
src/cpp/core/spelling/hunspell/hunspell.h | 95 +
src/cpp/core/spelling/hunspell/hunspell.hxx | 172 +
src/cpp/core/spelling/hunspell/hunvisapi.h | 18 +
src/cpp/core/spelling/hunspell/hunzip.cxx | 193 +
src/cpp/core/spelling/hunspell/hunzip.hxx | 45 +
src/cpp/core/spelling/hunspell/langnum.hxx | 38 +
src/cpp/core/spelling/hunspell/license.hunspell | 59 +
src/cpp/core/spelling/hunspell/license.myspell | 61 +
src/cpp/core/spelling/hunspell/phonet.cxx | 292 +
src/cpp/core/spelling/hunspell/phonet.hxx | 52 +
src/cpp/core/spelling/hunspell/replist.cxx | 87 +
src/cpp/core/spelling/hunspell/replist.hxx | 27 +
src/cpp/core/spelling/hunspell/suggestmgr.cxx | 2004 ++
src/cpp/core/spelling/hunspell/suggestmgr.hxx | 111 +
src/cpp/core/spelling/hunspell/utf_info.cxx | 19676 +++++++++++
src/cpp/core/spelling/hunspell/w_char.hxx | 21 +
src/cpp/core/system/ChildProcess.hpp | 236 +
src/cpp/core/system/CriticalSection.hpp | 78 +
src/cpp/core/system/Environment.cpp | 138 +
src/cpp/core/system/Pam.cpp | 210 +
src/cpp/core/system/PosixChildProcess.cpp | 823 +
src/cpp/core/system/PosixChildProcessTracker.cpp | 140 +
src/cpp/core/system/PosixCrypto.cpp | 320 +
src/cpp/core/system/PosixEnvironment.cpp | 68 +
src/cpp/core/system/PosixFileScanner.cpp | 186 +
src/cpp/core/system/PosixLibraryLoader.cpp | 90 +
src/cpp/core/system/PosixOutputCapture.cpp | 179 +
src/cpp/core/system/PosixParentProcessMonitor.cpp | 108 +
src/cpp/core/system/PosixShellUtils.cpp | 82 +
src/cpp/core/system/PosixSystem.cpp | 1432 +
src/cpp/core/system/PosixUser.cpp | 149 +
src/cpp/core/system/Process.cpp | 336 +
src/cpp/core/system/RegistryKey.cpp | 202 +
src/cpp/core/system/ShellUtils.cpp | 127 +
src/cpp/core/system/System.cpp | 72 +
src/cpp/core/system/Win32ChildProcess.cpp | 647 +
src/cpp/core/system/Win32Environment.cpp | 115 +
src/cpp/core/system/Win32FileScanner.cpp | 146 +
src/cpp/core/system/Win32LibraryLoader.cpp | 67 +
src/cpp/core/system/Win32OutputCapture.cpp | 147 +
src/cpp/core/system/Win32ParentProcessMonitor.cpp | 40 +
src/cpp/core/system/Win32ShellUtils.cpp | 83 +
src/cpp/core/system/Win32System.cpp | 830 +
src/cpp/core/system/file_monitor/FileMonitor.cpp | 687 +
.../core/system/file_monitor/FileMonitorImpl.hpp | 124 +
.../core/system/file_monitor/LinuxFileMonitor.cpp | 647 +
.../core/system/file_monitor/MacFileMonitor.cpp | 337 +
.../core/system/file_monitor/Win32FileMonitor.cpp | 624 +
.../core/system/recycle_bin/LinuxRecycleBin.cpp | 31 +
src/cpp/core/system/recycle_bin/MacRecycleBin.cpp | 73 +
.../core/system/recycle_bin/Win32RecycleBin.cpp | 66 +
src/cpp/core/tex/TexLogParser.cpp | 545 +
src/cpp/core/tex/TexMagicComment.cpp | 69 +
src/cpp/core/tex/TexSynctex.cpp | 242 +
src/cpp/core/tex/synctex/CMakeLists.txt | 44 +
src/cpp/core/tex/synctex/README.txt | 176 +
src/cpp/core/tex/synctex/synctex_parser.c | 4429 +++
src/cpp/core/tex/synctex/synctex_parser.h | 360 +
src/cpp/core/tex/synctex/synctex_parser_readme.txt | 169 +
src/cpp/core/tex/synctex/synctex_parser_utils.c | 497 +
src/cpp/core/tex/synctex/synctex_parser_utils.h | 155 +
src/cpp/core/text/DcfParser.cpp | 177 +
src/cpp/core/text/TemplateFilter.cpp | 60 +
src/cpp/core/zlib/.gitignore | 1 +
src/cpp/core/zlib/CMakeLists.txt | 104 +
src/cpp/core/zlib/adler32.c | 179 +
src/cpp/core/zlib/compress.c | 80 +
src/cpp/core/zlib/crc32.c | 447 +
src/cpp/core/zlib/crc32.h | 441 +
src/cpp/core/zlib/deflate.c | 1965 ++
src/cpp/core/zlib/deflate.h | 346 +
src/cpp/core/zlib/gzclose.c | 25 +
src/cpp/core/zlib/gzguts.h | 190 +
src/cpp/core/zlib/gzlib.c | 564 +
src/cpp/core/zlib/gzread.c | 584 +
src/cpp/core/zlib/gzwrite.c | 593 +
src/cpp/core/zlib/infback.c | 640 +
src/cpp/core/zlib/inffast.c | 340 +
src/cpp/core/zlib/inffast.h | 11 +
src/cpp/core/zlib/inffixed.h | 94 +
src/cpp/core/zlib/inflate.c | 1501 +
src/cpp/core/zlib/inflate.h | 122 +
src/cpp/core/zlib/inftrees.c | 306 +
src/cpp/core/zlib/inftrees.h | 62 +
src/cpp/core/zlib/trees.c | 1224 +
src/cpp/core/zlib/trees.h | 128 +
src/cpp/core/zlib/uncompr.c | 59 +
src/cpp/core/zlib/zconf.h | 468 +
src/cpp/core/zlib/zconf.h.cmakein | 468 +
src/cpp/core/zlib/zlib.h | 1732 +
src/cpp/core/zlib/zutil.c | 301 +
src/cpp/core/zlib/zutil.h | 248 +
src/cpp/desktop-mac/AppDelegate.h | 11 +
src/cpp/desktop-mac/AppDelegate.mm | 433 +
src/cpp/desktop-mac/CMakeLists.txt | 106 +
src/cpp/desktop-mac/DockTileView.h | 27 +
src/cpp/desktop-mac/DockTileView.mm | 258 +
src/cpp/desktop-mac/FileDownloader.h | 23 +
src/cpp/desktop-mac/FileDownloader.mm | 125 +
src/cpp/desktop-mac/GwtCallbacks.h | 30 +
src/cpp/desktop-mac/GwtCallbacks.mm | 818 +
src/cpp/desktop-mac/Info.plist.in | 245 +
src/cpp/desktop-mac/Main.mm | 50 +
src/cpp/desktop-mac/MainFrameController.h | 58 +
src/cpp/desktop-mac/MainFrameController.mm | 370 +
src/cpp/desktop-mac/MainFrameMenu.h | 36 +
src/cpp/desktop-mac/MainFrameMenu.mm | 484 +
src/cpp/desktop-mac/Options.hpp | 81 +
src/cpp/desktop-mac/Options.mm | 186 +
src/cpp/desktop-mac/RProject.ico | Bin 0 -> 61882 bytes
src/cpp/desktop-mac/RStudio.ico | Bin 0 -> 68935 bytes
src/cpp/desktop-mac/SatelliteController.h | 23 +
src/cpp/desktop-mac/SatelliteController.mm | 53 +
src/cpp/desktop-mac/SecondaryWindowController.h | 22 +
src/cpp/desktop-mac/SecondaryWindowController.mm | 113 +
src/cpp/desktop-mac/SessionLauncher.hpp | 98 +
src/cpp/desktop-mac/SessionLauncher.mm | 394 +
src/cpp/desktop-mac/Utils.hpp | 53 +
src/cpp/desktop-mac/Utils.mm | 234 +
src/cpp/desktop-mac/WebViewController.h | 48 +
src/cpp/desktop-mac/WebViewController.mm | 577 +
src/cpp/desktop-mac/WebViewWithKeyEquiv.h | 25 +
src/cpp/desktop-mac/WebViewWithKeyEquiv.mm | 32 +
src/cpp/desktop-mac/desktop-config.h.in | 21 +
src/cpp/desktop-mac/mac-terminal.in | 9 +
src/cpp/desktop-mac/resources/icns/CSS.icns | Bin 0 -> 118491 bytes
src/cpp/desktop-mac/resources/icns/HTML.icns | Bin 0 -> 125943 bytes
src/cpp/desktop-mac/resources/icns/JS.icns | Bin 0 -> 100490 bytes
src/cpp/desktop-mac/resources/icns/Markdown.icns | Bin 0 -> 85205 bytes
src/cpp/desktop-mac/resources/icns/RData.icns | Bin 0 -> 140314 bytes
src/cpp/desktop-mac/resources/icns/RDoc.icns | Bin 0 -> 104538 bytes
src/cpp/desktop-mac/resources/icns/RHTML.icns | Bin 0 -> 131340 bytes
src/cpp/desktop-mac/resources/icns/RMarkdown.icns | Bin 0 -> 105550 bytes
.../desktop-mac/resources/icns/RPresentation.icns | Bin 0 -> 138883 bytes
src/cpp/desktop-mac/resources/icns/RProject.icns | Bin 0 -> 228866 bytes
src/cpp/desktop-mac/resources/icns/RSource.icns | Bin 0 -> 92249 bytes
src/cpp/desktop-mac/resources/icns/RStudio.icns | Bin 0 -> 251289 bytes
src/cpp/desktop-mac/resources/icns/RSweave.icns | Bin 0 -> 105962 bytes
src/cpp/desktop-mac/resources/icns/RTex.icns | Bin 0 -> 85693 bytes
src/cpp/desktop-mac/resources/png/back_mac.png | Bin 0 -> 772 bytes
src/cpp/desktop-mac/resources/png/back_mac at 2x.png | Bin 0 -> 1455 bytes
src/cpp/desktop-mac/resources/png/dialog_error.png | Bin 0 -> 2943 bytes
.../desktop-mac/resources/png/dialog_error at 2x.png | Bin 0 -> 6535 bytes
src/cpp/desktop-mac/resources/png/dialog_info.png | Bin 0 -> 3563 bytes
.../desktop-mac/resources/png/dialog_info at 2x.png | Bin 0 -> 8788 bytes
.../resources/png/dialog_popup_blocked.png | Bin 0 -> 2333 bytes
.../resources/png/dialog_popup_blocked at 2x.png | Bin 0 -> 4812 bytes
.../desktop-mac/resources/png/dialog_question.png | Bin 0 -> 3895 bytes
.../resources/png/dialog_question at 2x.png | Bin 0 -> 9421 bytes
.../desktop-mac/resources/png/dialog_warning.png | Bin 0 -> 3227 bytes
.../resources/png/dialog_warning at 2x.png | Bin 0 -> 7730 bytes
src/cpp/desktop-mac/resources/png/forward_mac.png | Bin 0 -> 753 bytes
.../desktop-mac/resources/png/forward_mac at 2x.png | Bin 0 -> 1539 bytes
src/cpp/desktop-mac/resources/png/print_mac.png | Bin 0 -> 925 bytes
src/cpp/desktop-mac/resources/png/print_mac at 2x.png | Bin 0 -> 1428 bytes
src/cpp/desktop-mac/resources/png/reload_mac.png | Bin 0 -> 1283 bytes
.../desktop-mac/resources/png/reload_mac at 2x.png | Bin 0 -> 2723 bytes
src/cpp/desktop/.gitignore | 2 +
.../3rdparty/qtsingleapplication/QtLockedFile | 1 +
.../qtsingleapplication/QtSingleApplication | 1 +
.../3rdparty/qtsingleapplication/qtlocalpeer.cpp | 200 +
.../3rdparty/qtsingleapplication/qtlocalpeer.h | 76 +
.../3rdparty/qtsingleapplication/qtlockedfile.cpp | 192 +
.../3rdparty/qtsingleapplication/qtlockedfile.h | 96 +
.../qtsingleapplication/qtlockedfile_unix.cpp | 114 +
.../qtsingleapplication/qtlockedfile_win.cpp | 209 +
.../qtsingleapplication/qtsingleapplication.cpp | 344 +
.../qtsingleapplication/qtsingleapplication.h | 102 +
src/cpp/desktop/CMakeLists.txt | 481 +
src/cpp/desktop/DesktopAboutDialog.cpp | 64 +
src/cpp/desktop/DesktopAboutDialog.hpp | 37 +
src/cpp/desktop/DesktopAboutDialog.ui | 195 +
src/cpp/desktop/DesktopApplicationLaunch.hpp | 62 +
src/cpp/desktop/DesktopBrowserWindow.cpp | 133 +
src/cpp/desktop/DesktopBrowserWindow.hpp | 75 +
src/cpp/desktop/DesktopChooseRHome.cpp | 303 +
src/cpp/desktop/DesktopChooseRHome.hpp | 56 +
src/cpp/desktop/DesktopChooseRHome.ui | 225 +
src/cpp/desktop/DesktopCommandInvoker.cpp | 30 +
src/cpp/desktop/DesktopCommandInvoker.hpp | 41 +
src/cpp/desktop/DesktopDataUriNetworkReply.cpp | 91 +
src/cpp/desktop/DesktopDataUriNetworkReply.hpp | 67 +
src/cpp/desktop/DesktopDetectRHome.hpp | 31 +
src/cpp/desktop/DesktopDownloadHelper.cpp | 90 +
src/cpp/desktop/DesktopDownloadHelper.hpp | 46 +
src/cpp/desktop/DesktopGwtCallback.cpp | 977 +
src/cpp/desktop/DesktopGwtCallback.hpp | 175 +
src/cpp/desktop/DesktopGwtCallbackOwner.hpp | 40 +
src/cpp/desktop/DesktopGwtWindow.cpp | 39 +
src/cpp/desktop/DesktopGwtWindow.hpp | 44 +
src/cpp/desktop/DesktopInputDialog.cpp | 104 +
src/cpp/desktop/DesktopInputDialog.hpp | 52 +
src/cpp/desktop/DesktopInputDialog.ui | 106 +
src/cpp/desktop/DesktopMain.cpp | 374 +
src/cpp/desktop/DesktopMainWindow.cpp | 372 +
src/cpp/desktop/DesktopMainWindow.hpp | 103 +
src/cpp/desktop/DesktopMenuCallback.cpp | 311 +
src/cpp/desktop/DesktopMenuCallback.hpp | 121 +
src/cpp/desktop/DesktopNetworkAccessManager.cpp | 68 +
src/cpp/desktop/DesktopNetworkAccessManager.hpp | 41 +
src/cpp/desktop/DesktopNetworkIOService.cpp | 40 +
src/cpp/desktop/DesktopNetworkIOService.hpp | 27 +
src/cpp/desktop/DesktopNetworkProxyFactory.cpp | 37 +
src/cpp/desktop/DesktopNetworkProxyFactory.hpp | 30 +
src/cpp/desktop/DesktopNetworkReply.cpp | 315 +
src/cpp/desktop/DesktopNetworkReply.hpp | 76 +
src/cpp/desktop/DesktopOptions.cpp | 380 +
src/cpp/desktop/DesktopOptions.hpp | 111 +
src/cpp/desktop/DesktopPosixApplication.cpp | 77 +
src/cpp/desktop/DesktopPosixApplication.hpp | 54 +
src/cpp/desktop/DesktopPosixApplicationLaunch.cpp | 90 +
src/cpp/desktop/DesktopPosixDetectRHome.cpp | 91 +
src/cpp/desktop/DesktopRVersion.cpp | 585 +
src/cpp/desktop/DesktopRVersion.hpp | 91 +
src/cpp/desktop/DesktopSatelliteWindow.cpp | 72 +
src/cpp/desktop/DesktopSatelliteWindow.hpp | 63 +
src/cpp/desktop/DesktopSecondaryWindow.cpp | 97 +
src/cpp/desktop/DesktopSecondaryWindow.hpp | 50 +
src/cpp/desktop/DesktopSessionLauncher.cpp | 398 +
src/cpp/desktop/DesktopSessionLauncher.hpp | 84 +
src/cpp/desktop/DesktopSlotBinders.cpp | 32 +
src/cpp/desktop/DesktopSlotBinders.hpp | 67 +
src/cpp/desktop/DesktopSubMenu.cpp | 79 +
src/cpp/desktop/DesktopSubMenu.hpp | 42 +
src/cpp/desktop/DesktopSynctex.cpp | 175 +
src/cpp/desktop/DesktopSynctex.hpp | 91 +
src/cpp/desktop/DesktopURLDownloader.cpp | 92 +
src/cpp/desktop/DesktopURLDownloader.hpp | 60 +
src/cpp/desktop/DesktopUtils.cpp | 283 +
src/cpp/desktop/DesktopUtils.hpp | 76 +
src/cpp/desktop/DesktopUtilsMac.mm | 174 +
src/cpp/desktop/DesktopWebPage.cpp | 301 +
src/cpp/desktop/DesktopWebPage.hpp | 89 +
src/cpp/desktop/DesktopWebView.cpp | 278 +
src/cpp/desktop/DesktopWebView.hpp | 68 +
src/cpp/desktop/DesktopWin32ApplicationLaunch.cpp | 204 +
src/cpp/desktop/DesktopWin32DetectRHome.cpp | 73 +
src/cpp/desktop/DesktopWindowTracker.cpp | 53 +
src/cpp/desktop/DesktopWindowTracker.hpp | 45 +
src/cpp/desktop/FixBundle.cmake.in | 8 +
src/cpp/desktop/Info.plist.in | 236 +
src/cpp/desktop/RProject.ico | Bin 0 -> 61882 bytes
src/cpp/desktop/RStudio.ico | Bin 0 -> 68935 bytes
src/cpp/desktop/assets.qrc | 5 +
src/cpp/desktop/back.png | Bin 0 -> 781 bytes
src/cpp/desktop/back_mac.png | Bin 0 -> 772 bytes
src/cpp/desktop/desktop-config.h.in | 23 +
src/cpp/desktop/desktop.qrc | 14 +
src/cpp/desktop/fancybrowser.pro | 27 +
src/cpp/desktop/forward.png | Bin 0 -> 793 bytes
src/cpp/desktop/forward_mac.png | Bin 0 -> 753 bytes
src/cpp/desktop/mac-terminal.in | 9 +
src/cpp/desktop/print.png | Bin 0 -> 655 bytes
src/cpp/desktop/print_mac.png | Bin 0 -> 925 bytes
.../qt-patch/mac-colorspace/qpaintengine_mac.cpp | 1755 +
.../qt-patch/mac-colorspace/qpaintengine_mac_p.h | 256 +
src/cpp/desktop/qt.conf | 0
src/cpp/desktop/reload.png | Bin 0 -> 1119 bytes
src/cpp/desktop/reload_mac.png | Bin 0 -> 1283 bytes
.../icons/128x128/application-x-r-data.png | Bin 0 -> 5934 bytes
.../icons/128x128/application-x-r-project.png | Bin 0 -> 14087 bytes
.../freedesktop/icons/128x128/rstudio.png | Bin 0 -> 13243 bytes
.../freedesktop/icons/128x128/text-css.png | Bin 0 -> 6889 bytes
.../freedesktop/icons/128x128/text-html.png | Bin 0 -> 8259 bytes
.../freedesktop/icons/128x128/text-javascript.png | Bin 0 -> 7043 bytes
.../freedesktop/icons/128x128/text-x-markdown.png | Bin 0 -> 3727 bytes
.../freedesktop/icons/128x128/text-x-r-doc.png | Bin 0 -> 7091 bytes
.../freedesktop/icons/128x128/text-x-r-html.png | Bin 0 -> 7005 bytes
.../icons/128x128/text-x-r-markdown.png | Bin 0 -> 6981 bytes
.../icons/128x128/text-x-r-presentation.png | Bin 0 -> 8036 bytes
.../freedesktop/icons/128x128/text-x-r-source.png | Bin 0 -> 6121 bytes
.../freedesktop/icons/128x128/text-x-r-sweave.png | Bin 0 -> 7068 bytes
.../freedesktop/icons/128x128/text-x-tex.png | Bin 0 -> 3930 bytes
.../icons/16x16/application-x-r-data.png | Bin 0 -> 688 bytes
.../icons/16x16/application-x-r-project.png | Bin 0 -> 905 bytes
.../resources/freedesktop/icons/16x16/rstudio.png | Bin 0 -> 849 bytes
.../resources/freedesktop/icons/16x16/text-css.png | Bin 0 -> 821 bytes
.../freedesktop/icons/16x16/text-html.png | Bin 0 -> 835 bytes
.../freedesktop/icons/16x16/text-javascript.png | Bin 0 -> 816 bytes
.../freedesktop/icons/16x16/text-x-markdown.png | Bin 0 -> 649 bytes
.../freedesktop/icons/16x16/text-x-r-doc.png | Bin 0 -> 789 bytes
.../freedesktop/icons/16x16/text-x-r-html.png | Bin 0 -> 844 bytes
.../freedesktop/icons/16x16/text-x-r-markdown.png | Bin 0 -> 799 bytes
.../icons/16x16/text-x-r-presentation.png | Bin 0 -> 574 bytes
.../freedesktop/icons/16x16/text-x-r-source.png | Bin 0 -> 686 bytes
.../freedesktop/icons/16x16/text-x-r-sweave.png | Bin 0 -> 808 bytes
.../freedesktop/icons/16x16/text-x-tex.png | Bin 0 -> 608 bytes
.../icons/24x24/application-x-r-data.png | Bin 0 -> 1240 bytes
.../icons/24x24/application-x-r-project.png | Bin 0 -> 1388 bytes
.../resources/freedesktop/icons/24x24/rstudio.png | Bin 0 -> 1583 bytes
.../resources/freedesktop/icons/24x24/text-css.png | Bin 0 -> 1077 bytes
.../freedesktop/icons/24x24/text-html.png | Bin 0 -> 1072 bytes
.../freedesktop/icons/24x24/text-javascript.png | Bin 0 -> 1078 bytes
.../freedesktop/icons/24x24/text-x-markdown.png | Bin 0 -> 768 bytes
.../freedesktop/icons/24x24/text-x-r-doc.png | Bin 0 -> 1069 bytes
.../freedesktop/icons/24x24/text-x-r-html.png | Bin 0 -> 1099 bytes
.../freedesktop/icons/24x24/text-x-r-markdown.png | Bin 0 -> 1062 bytes
.../icons/24x24/text-x-r-presentation.png | Bin 0 -> 1263 bytes
.../freedesktop/icons/24x24/text-x-r-source.png | Bin 0 -> 1023 bytes
.../freedesktop/icons/24x24/text-x-r-sweave.png | Bin 0 -> 1082 bytes
.../freedesktop/icons/24x24/text-x-tex.png | Bin 0 -> 835 bytes
.../icons/256x256/application-x-r-data.png | Bin 0 -> 12739 bytes
.../icons/256x256/application-x-r-project.png | Bin 0 -> 31847 bytes
.../freedesktop/icons/256x256/rstudio.png | Bin 0 -> 33166 bytes
.../freedesktop/icons/256x256/text-css.png | Bin 0 -> 14240 bytes
.../freedesktop/icons/256x256/text-html.png | Bin 0 -> 17845 bytes
.../freedesktop/icons/256x256/text-javascript.png | Bin 0 -> 14188 bytes
.../freedesktop/icons/256x256/text-x-markdown.png | Bin 0 -> 7451 bytes
.../freedesktop/icons/256x256/text-x-r-doc.png | Bin 0 -> 13993 bytes
.../freedesktop/icons/256x256/text-x-r-html.png | Bin 0 -> 19116 bytes
.../icons/256x256/text-x-r-markdown.png | Bin 0 -> 13960 bytes
.../icons/256x256/text-x-r-presentation.png | Bin 0 -> 16046 bytes
.../freedesktop/icons/256x256/text-x-r-source.png | Bin 0 -> 12207 bytes
.../freedesktop/icons/256x256/text-x-r-sweave.png | Bin 0 -> 14206 bytes
.../freedesktop/icons/256x256/text-x-tex.png | Bin 0 -> 7584 bytes
.../icons/32x32/application-x-r-data.png | Bin 0 -> 1616 bytes
.../icons/32x32/application-x-r-project.png | Bin 0 -> 2203 bytes
.../resources/freedesktop/icons/32x32/rstudio.png | Bin 0 -> 2255 bytes
.../resources/freedesktop/icons/32x32/text-css.png | Bin 0 -> 1442 bytes
.../freedesktop/icons/32x32/text-html.png | Bin 0 -> 1486 bytes
.../freedesktop/icons/32x32/text-javascript.png | Bin 0 -> 1487 bytes
.../freedesktop/icons/32x32/text-x-markdown.png | Bin 0 -> 1033 bytes
.../freedesktop/icons/32x32/text-x-r-doc.png | Bin 0 -> 1501 bytes
.../freedesktop/icons/32x32/text-x-r-html.png | Bin 0 -> 1538 bytes
.../freedesktop/icons/32x32/text-x-r-markdown.png | Bin 0 -> 1457 bytes
.../icons/32x32/text-x-r-presentation.png | Bin 0 -> 1715 bytes
.../freedesktop/icons/32x32/text-x-r-source.png | Bin 0 -> 1383 bytes
.../freedesktop/icons/32x32/text-x-r-sweave.png | Bin 0 -> 1423 bytes
.../freedesktop/icons/32x32/text-x-tex.png | Bin 0 -> 1089 bytes
.../icons/48x48/application-x-r-data.png | Bin 0 -> 2368 bytes
.../icons/48x48/application-x-r-project.png | Bin 0 -> 4043 bytes
.../resources/freedesktop/icons/48x48/rstudio.png | Bin 0 -> 3661 bytes
.../resources/freedesktop/icons/48x48/text-css.png | Bin 0 -> 2340 bytes
.../freedesktop/icons/48x48/text-html.png | Bin 0 -> 2204 bytes
.../freedesktop/icons/48x48/text-javascript.png | Bin 0 -> 2327 bytes
.../freedesktop/icons/48x48/text-x-markdown.png | Bin 0 -> 1425 bytes
.../freedesktop/icons/48x48/text-x-r-doc.png | Bin 0 -> 2375 bytes
.../freedesktop/icons/48x48/text-x-r-html.png | Bin 0 -> 2463 bytes
.../freedesktop/icons/48x48/text-x-r-markdown.png | Bin 0 -> 2159 bytes
.../freedesktop/icons/48x48/text-x-r-source.png | Bin 0 -> 2122 bytes
.../freedesktop/icons/48x48/text-x-r-sweave.png | Bin 0 -> 2231 bytes
.../freedesktop/icons/48x48/text-x-tex.png | Bin 0 -> 1506 bytes
.../icons/512x512/application-x-r-data.png | Bin 0 -> 27007 bytes
.../icons/512x512/application-x-r-project.png | Bin 0 -> 69435 bytes
.../freedesktop/icons/512x512/rstudio.png | Bin 0 -> 80589 bytes
.../freedesktop/icons/512x512/text-css.png | Bin 0 -> 30613 bytes
.../freedesktop/icons/512x512/text-html.png | Bin 0 -> 43077 bytes
.../freedesktop/icons/512x512/text-javascript.png | Bin 0 -> 29950 bytes
.../freedesktop/icons/512x512/text-x-markdown.png | Bin 0 -> 16590 bytes
.../freedesktop/icons/512x512/text-x-r-doc.png | Bin 0 -> 29808 bytes
.../freedesktop/icons/512x512/text-x-r-html.png | Bin 0 -> 45790 bytes
.../icons/512x512/text-x-r-markdown.png | Bin 0 -> 30202 bytes
.../icons/512x512/text-x-r-presentation.png | Bin 0 -> 36523 bytes
.../freedesktop/icons/512x512/text-x-r-source.png | Bin 0 -> 26617 bytes
.../freedesktop/icons/512x512/text-x-r-sweave.png | Bin 0 -> 30690 bytes
.../freedesktop/icons/512x512/text-x-tex.png | Bin 0 -> 16791 bytes
.../icons/64x64/application-x-r-data.png | Bin 0 -> 3202 bytes
.../icons/64x64/application-x-r-project.png | Bin 0 -> 5827 bytes
.../resources/freedesktop/icons/64x64/rstudio.png | Bin 0 -> 5430 bytes
.../resources/freedesktop/icons/64x64/text-css.png | Bin 0 -> 3505 bytes
.../freedesktop/icons/64x64/text-html.png | Bin 0 -> 3591 bytes
.../freedesktop/icons/64x64/text-javascript.png | Bin 0 -> 3443 bytes
.../freedesktop/icons/64x64/text-x-markdown.png | Bin 0 -> 1954 bytes
.../freedesktop/icons/64x64/text-x-r-doc.png | Bin 0 -> 3465 bytes
.../freedesktop/icons/64x64/text-x-r-html.png | Bin 0 -> 3894 bytes
.../freedesktop/icons/64x64/text-x-r-markdown.png | Bin 0 -> 3302 bytes
.../icons/64x64/text-x-r-presentation.png | Bin 0 -> 3774 bytes
.../freedesktop/icons/64x64/text-x-r-source.png | Bin 0 -> 3048 bytes
.../freedesktop/icons/64x64/text-x-r-sweave.png | Bin 0 -> 3304 bytes
.../freedesktop/icons/64x64/text-x-tex.png | Bin 0 -> 2051 bytes
.../resources/freedesktop/rstudio.desktop.in | 8 +
src/cpp/desktop/resources/freedesktop/rstudio.xml | 76 +
src/cpp/desktop/resources/icns/CSS.icns | Bin 0 -> 118491 bytes
src/cpp/desktop/resources/icns/HTML.icns | Bin 0 -> 125943 bytes
src/cpp/desktop/resources/icns/JS.icns | Bin 0 -> 100490 bytes
src/cpp/desktop/resources/icns/Markdown.icns | Bin 0 -> 85205 bytes
src/cpp/desktop/resources/icns/RData.icns | Bin 0 -> 140314 bytes
src/cpp/desktop/resources/icns/RDoc.icns | Bin 0 -> 104538 bytes
src/cpp/desktop/resources/icns/RHTML.icns | Bin 0 -> 131340 bytes
src/cpp/desktop/resources/icns/RMarkdown.icns | Bin 0 -> 105550 bytes
src/cpp/desktop/resources/icns/RPresentation.icns | Bin 0 -> 138883 bytes
src/cpp/desktop/resources/icns/RProject.icns | Bin 0 -> 228866 bytes
src/cpp/desktop/resources/icns/RSource.icns | Bin 0 -> 92249 bytes
src/cpp/desktop/resources/icns/RStudio.icns | Bin 0 -> 251289 bytes
src/cpp/desktop/resources/icns/RSweave.icns | Bin 0 -> 105962 bytes
src/cpp/desktop/resources/icns/RTex.icns | Bin 0 -> 85693 bytes
src/cpp/desktop/rstudio-backtrace.sh.in | 81 +
src/cpp/desktop/rstudio.exe.manifest | 23 +
src/cpp/desktop/rstudio.rc.in | 32 +
src/cpp/desktop/synctex/evince/EvinceDaemon.cpp | 36 +
src/cpp/desktop/synctex/evince/EvinceDaemon.hpp | 65 +
src/cpp/desktop/synctex/evince/EvinceSynctex.cpp | 206 +
src/cpp/desktop/synctex/evince/EvinceSynctex.hpp | 101 +
src/cpp/desktop/synctex/evince/EvinceWindow.cpp | 39 +
src/cpp/desktop/synctex/evince/EvinceWindow.hpp | 90 +
src/cpp/desktop/synctex/evince/README | 110 +
src/cpp/desktop/synctex/rsinverse/CMakeLists.txt | 65 +
.../desktop/synctex/rsinverse/RsInverseMain.cpp | 145 +
.../synctex/rsinverse/rsinverse.exe.manifest | 23 +
src/cpp/desktop/synctex/rsinverse/rsinverse.rc.in | 29 +
src/cpp/desktop/synctex/sumatra/SumatraSynctex.cpp | 114 +
src/cpp/desktop/synctex/sumatra/SumatraSynctex.hpp | 52 +
src/cpp/desktop/urlopener/CMakeLists.txt | 63 +
src/cpp/desktop/urlopener/UrlOpenerMain.cpp | 82 +
src/cpp/desktop/urlopener/urlopener.exe.manifest | 23 +
src/cpp/desktop/urlopener/urlopener.rc.in | 29 +
src/cpp/diagnostics/CMakeLists.txt | 69 +
src/cpp/diagnostics/DiagnosticsMain.cpp | 91 +
src/cpp/diagnostics/config.h.in | 16 +
src/cpp/diagnostics/diagnostics.exe.manifest | 23 +
src/cpp/diagnostics/diagnostics.rc.in | 29 +
src/cpp/monitor/CMakeLists.txt | 44 +
src/cpp/monitor/MonitorClient.cpp | 85 +
src/cpp/monitor/MonitorClientImpl.hpp | 73 +
src/cpp/monitor/MonitorClientOverlay.cpp | 62 +
src/cpp/monitor/events/Event.cpp | 82 +
src/cpp/monitor/include/monitor/MonitorClient.hpp | 81 +
.../monitor/include/monitor/MonitorConstants.hpp | 24 +
src/cpp/monitor/include/monitor/events/Event.hpp | 91 +
src/cpp/monitor/include/monitor/metrics/Metric.hpp | 162 +
src/cpp/monitor/metrics/Metric.cpp | 177 +
src/cpp/r/CMakeLists.txt | 99 +
src/cpp/r/R/Diagnostics.R | 69 +
src/cpp/r/R/Options.R | 77 +
src/cpp/r/R/ServerOptions.R | 30 +
src/cpp/r/R/Tools.R | 577 +
src/cpp/r/R/packages/CMakeLists.txt | 70 +
src/cpp/r/R/packages/manipulate/.gitignore | 3 +
src/cpp/r/R/packages/manipulate/DESCRIPTION.in | 13 +
src/cpp/r/R/packages/manipulate/NAMESPACE | 11 +
.../R/packages/manipulate/R/manipulate-internal.R | 150 +
src/cpp/r/R/packages/manipulate/R/manipulate.R | 253 +
src/cpp/r/R/packages/manipulate/man/button.Rd | 44 +
src/cpp/r/R/packages/manipulate/man/checkbox.Rd | 46 +
.../packages/manipulate/man/manipulate-package.Rd | 71 +
src/cpp/r/R/packages/manipulate/man/manipulate.Rd | 74 +
src/cpp/r/R/packages/manipulate/man/mouseclick.Rd | 45 +
src/cpp/r/R/packages/manipulate/man/picker.Rd | 64 +
src/cpp/r/R/packages/manipulate/man/slider.Rd | 70 +
src/cpp/r/R/packages/manipulate/man/state.Rd | 45 +
src/cpp/r/R/packages/rstudio/.Rbuildignore | 2 +
src/cpp/r/R/packages/rstudio/.gitignore | 1 +
src/cpp/r/R/packages/rstudio/DESCRIPTION.in | 13 +
src/cpp/r/R/packages/rstudio/NAMESPACE | 6 +
src/cpp/r/R/packages/rstudio/R/rstudio.R | 40 +
src/cpp/r/R/packages/rstudio/inst/CITATION | 19 +
.../r/R/packages/rstudio/man/diagnosticsReport.Rd | 17 +
src/cpp/r/R/packages/rstudio/man/previewRd.Rd | 21 +
.../r/R/packages/rstudio/man/rstudio-package.Rd | 52 +
src/cpp/r/R/packages/rstudio/man/versionInfo.Rd | 40 +
src/cpp/r/R/packages/rstudio/man/viewer.Rd | 69 +
src/cpp/r/RErrorCategory.cpp | 117 +
src/cpp/r/RExec.cpp | 505 +
src/cpp/r/RFunctionHook.cpp | 50 +
src/cpp/r/RJson.cpp | 417 +
src/cpp/r/RJsonRpc.cpp | 155 +
src/cpp/r/ROptions.cpp | 87 +
src/cpp/r/RRoutines.cpp | 75 +
src/cpp/r/RSexp.cpp | 649 +
src/cpp/r/RSourceManager.cpp | 154 +
src/cpp/r/RUtil.cpp | 165 +
src/cpp/r/VERSION | 1 +
src/cpp/r/config.h.in | 19 +
src/cpp/r/include/r/RErrorCategory.hpp | 85 +
src/cpp/r/include/r/RExec.hpp | 284 +
src/cpp/r/include/r/RFunctionHook.hpp | 41 +
src/cpp/r/include/r/RInterface.hpp | 112 +
src/cpp/r/include/r/RInternal.hpp | 247 +
src/cpp/r/include/r/RJson.hpp | 42 +
src/cpp/r/include/r/RJsonRpc.hpp | 38 +
src/cpp/r/include/r/ROptions.hpp | 108 +
src/cpp/r/include/r/RRoutines.hpp | 36 +
src/cpp/r/include/r/RSexp.hpp | 287 +
src/cpp/r/include/r/RSourceManager.hpp | 89 +
src/cpp/r/include/r/RUtil.hpp | 50 +
src/cpp/r/include/r/session/RClientState.hpp | 96 +
src/cpp/r/include/r/session/RConsoleActions.hpp | 82 +
src/cpp/r/include/r/session/RConsoleHistory.hpp | 105 +
src/cpp/r/include/r/session/RDiscovery.hpp | 40 +
src/cpp/r/include/r/session/REventLoop.hpp | 67 +
src/cpp/r/include/r/session/RGraphics.hpp | 180 +
src/cpp/r/include/r/session/RSession.hpp | 188 +
src/cpp/r/include/r/session/RSessionUtils.hpp | 59 +
src/cpp/r/session/RClientMetrics.cpp | 115 +
src/cpp/r/session/RClientMetrics.hpp | 40 +
src/cpp/r/session/RClientState.cpp | 323 +
src/cpp/r/session/RConsoleActions.cpp | 233 +
src/cpp/r/session/RConsoleHistory.cpp | 191 +
src/cpp/r/session/RDiscovery.cpp | 84 +
src/cpp/r/session/REmbedded.hpp | 79 +
src/cpp/r/session/REmbeddedPosix.cpp | 374 +
src/cpp/r/session/REmbeddedWin32.cpp | 269 +
src/cpp/r/session/RRestartContext.cpp | 110 +
src/cpp/r/session/RRestartContext.hpp | 62 +
src/cpp/r/session/RSearchPath.cpp | 411 +
src/cpp/r/session/RSearchPath.hpp | 37 +
src/cpp/r/session/RSession.cpp | 1666 +
src/cpp/r/session/RSessionState.cpp | 535 +
src/cpp/r/session/RSessionState.hpp | 56 +
src/cpp/r/session/graphics/RGraphicsDevDesc.cpp | 675 +
src/cpp/r/session/graphics/RGraphicsDevDesc.hpp | 176 +
src/cpp/r/session/graphics/RGraphicsDevice.cpp | 777 +
src/cpp/r/session/graphics/RGraphicsDevice.hpp | 55 +
.../r/session/graphics/RGraphicsErrorCategory.cpp | 85 +
src/cpp/r/session/graphics/RGraphicsHandler.cpp | 121 +
src/cpp/r/session/graphics/RGraphicsHandler.hpp | 159 +
src/cpp/r/session/graphics/RGraphicsPlot.cpp | 300 +
src/cpp/r/session/graphics/RGraphicsPlot.hpp | 104 +
.../r/session/graphics/RGraphicsPlotManager.cpp | 872 +
.../r/session/graphics/RGraphicsPlotManager.hpp | 216 +
.../session/graphics/RGraphicsPlotManipulator.cpp | 271 +
.../session/graphics/RGraphicsPlotManipulator.hpp | 69 +
.../graphics/RGraphicsPlotManipulatorManager.cpp | 353 +
.../graphics/RGraphicsPlotManipulatorManager.hpp | 97 +
src/cpp/r/session/graphics/RGraphicsTypes.hpp | 82 +
src/cpp/r/session/graphics/RGraphicsUtils.cpp | 224 +
src/cpp/r/session/graphics/RGraphicsUtils.hpp | 58 +
.../session/graphics/RShadowPngGraphicsHandler.cpp | 565 +
src/cpp/rdesktop-dev.in | 22 +
src/cpp/rserver-dev.in | 35 +
src/cpp/rserver-test.in | 19 +
src/cpp/server/CMakeLists.txt | 200 +
src/cpp/server/Main.cpp | 27 +
src/cpp/server/ServerAddins.cpp.in | 48 +
src/cpp/server/ServerAddins.hpp | 32 +
src/cpp/server/ServerAppArmor.cpp | 104 +
src/cpp/server/ServerAppArmor.hpp | 34 +
src/cpp/server/ServerBrowser.cpp | 73 +
src/cpp/server/ServerBrowser.hpp | 43 +
src/cpp/server/ServerEval.cpp | 65 +
src/cpp/server/ServerEval.hpp | 36 +
src/cpp/server/ServerInit.cpp | 41 +
src/cpp/server/ServerInit.hpp | 37 +
src/cpp/server/ServerMain.cpp | 519 +
src/cpp/server/ServerMainOverlay.cpp | 38 +
src/cpp/server/ServerMeta.cpp | 69 +
src/cpp/server/ServerMeta.hpp | 39 +
src/cpp/server/ServerOffline.cpp | 85 +
src/cpp/server/ServerOffline.hpp | 32 +
src/cpp/server/ServerOptions.cpp | 328 +
src/cpp/server/ServerOptionsOverlay.cpp | 41 +
src/cpp/server/ServerPAMAuth.cpp | 369 +
src/cpp/server/ServerPAMAuth.hpp | 32 +
src/cpp/server/ServerREnvironment.cpp | 79 +
src/cpp/server/ServerREnvironment.hpp | 37 +
src/cpp/server/ServerSessionManager.cpp | 252 +
src/cpp/server/ServerSessionManager.hpp | 103 +
src/cpp/server/ServerSessionProxy.cpp | 501 +
src/cpp/server/ServerSessionProxy.hpp | 54 +
src/cpp/server/auth/ServerAuthHandler.cpp | 144 +
src/cpp/server/auth/ServerSecureCookie.cpp | 300 +
src/cpp/server/auth/ServerSecureUriHandler.cpp | 195 +
src/cpp/server/auth/ServerValidateUser.cpp | 93 +
src/cpp/server/extras/admin/rstudio-server.in | 101 +
.../server/extras/apparmor/apparmor-profile-load | 44 +
src/cpp/server/extras/apparmor/rstudio-server.in | 55 +
.../server/extras/init.d/debian/rstudio-server.in | 106 +
.../server/extras/init.d/redhat/rstudio-server.in | 68 +
.../server/extras/init.d/suse/rstudio-server.in | 75 +
src/cpp/server/extras/pam/rstudio | 5 +
.../server/extras/upstart/rstudio-server.conf.in | 27 +
.../extras/upstart/rstudio-server.redhat.conf.in | 21 +
src/cpp/server/include/server/ServerOptions.hpp | 247 +
src/cpp/server/include/server/ServerScheduler.hpp | 36 +
.../server/include/server/ServerUriHandlers.hpp | 45 +
.../include/server/auth/ServerAuthHandler.hpp | 99 +
.../include/server/auth/ServerSecureCookie.hpp | 66 +
.../include/server/auth/ServerSecureUriHandler.hpp | 69 +
.../include/server/auth/ServerValidateUser.hpp | 43 +
src/cpp/server/pam/CMakeLists.txt | 51 +
src/cpp/server/pam/PamMain.cpp | 107 +
src/cpp/server/server-config.h.in | 21 +
src/cpp/session/CMakeLists.txt | 377 +
src/cpp/session/SessionAddins.cpp.in | 47 +
src/cpp/session/SessionAddins.hpp | 32 +
src/cpp/session/SessionClientEvent.cpp | 328 +
src/cpp/session/SessionClientEventQueue.cpp | 177 +
src/cpp/session/SessionClientEventQueue.hpp | 88 +
src/cpp/session/SessionClientEventService.cpp | 353 +
src/cpp/session/SessionClientEventService.hpp | 75 +
src/cpp/session/SessionContentUrls.cpp | 220 +
src/cpp/session/SessionMain.cpp | 2936 ++
src/cpp/session/SessionModuleContext.cpp | 1449 +
src/cpp/session/SessionModuleContext.mm | 70 +
src/cpp/session/SessionModuleContextInternal.hpp | 55 +
src/cpp/session/SessionOptions.cpp | 507 +
src/cpp/session/SessionOptionsOverlay.cpp | 36 +
src/cpp/session/SessionPersistentState.cpp | 117 +
src/cpp/session/SessionPostback.cpp | 116 +
src/cpp/session/SessionSSH.cpp | 93 +
src/cpp/session/SessionSourceDatabase.cpp | 673 +
.../session/SessionSourceDatabaseSupervisor.cpp | 407 +
.../session/SessionSourceDatabaseSupervisor.hpp | 37 +
src/cpp/session/SessionUserSettings.cpp | 636 +
src/cpp/session/SessionWorkerContext.cpp | 44 +
src/cpp/session/consoleio/CMakeLists.txt | 62 +
src/cpp/session/consoleio/ConsoleIOMain.cpp | 473 +
src/cpp/session/consoleio/consoleio.exe.manifest | 23 +
src/cpp/session/consoleio/consoleio.rc.in | 29 +
src/cpp/session/http/.gitignore | 0
src/cpp/session/http/SessionHttpConnectionImpl.hpp | 227 +
.../http/SessionHttpConnectionListenerImpl.hpp | 319 +
.../session/http/SessionHttpConnectionQueue.cpp | 139 +
.../session/http/SessionHttpConnectionUtils.cpp | 236 +
.../session/http/SessionHttpConnectionUtils.hpp | 58 +
.../SessionLocalStreamHttpConnectionListener.hpp | 133 +
.../SessionNamedPipeHttpConnectionListener.hpp | 487 +
.../http/SessionPosixHttpConnectionListener.cpp | 84 +
.../http/SessionTcpIpHttpConnectionListener.hpp | 74 +
.../http/SessionWin32HttpConnectionListener.cpp | 54 +
.../session/include/session/SessionClientEvent.hpp | 17 +
.../session/include/session/SessionConstants.hpp | 51 +
.../session/include/session/SessionContentUrls.hpp | 40 +
.../include/session/SessionHttpConnection.hpp | 80 +
.../session/SessionHttpConnectionListener.hpp | 104 +
.../include/session/SessionHttpConnectionQueue.hpp | 76 +
.../include/session/SessionLocalStreams.hpp | 54 +
src/cpp/session/include/session/SessionMain.hpp | 25 +
.../include/session/SessionModuleContext.hpp | 450 +
src/cpp/session/include/session/SessionOptions.hpp | 469 +
.../include/session/SessionPersistentState.hpp | 66 +
src/cpp/session/include/session/SessionSSH.hpp | 72 +
.../include/session/SessionSourceDatabase.hpp | 147 +
.../include/session/SessionUserSettings.hpp | 209 +
.../include/session/projects/SessionProjects.hpp | 220 +
.../worker_safe/session/SessionClientEvent.hpp | 174 +
.../worker_safe/session/SessionWorkerContext.hpp | 41 +
src/cpp/session/modules/ModuleTools.R | 97 +
src/cpp/session/modules/SessionAbout.cpp | 61 +
src/cpp/session/modules/SessionAbout.hpp | 33 +
src/cpp/session/modules/SessionAgreement.cpp | 191 +
src/cpp/session/modules/SessionAgreement.hpp | 40 +
src/cpp/session/modules/SessionAskPass.R | 20 +
src/cpp/session/modules/SessionAskPass.cpp | 175 +
src/cpp/session/modules/SessionAskPass.hpp | 51 +
src/cpp/session/modules/SessionAuthoring.R | 72 +
src/cpp/session/modules/SessionAuthoring.cpp | 279 +
src/cpp/session/modules/SessionAuthoring.hpp | 46 +
src/cpp/session/modules/SessionBreakpoints.R | 505 +
src/cpp/session/modules/SessionBreakpoints.cpp | 627 +
src/cpp/session/modules/SessionBreakpoints.hpp | 37 +
src/cpp/session/modules/SessionBuild.R | 26 +
src/cpp/session/modules/SessionCodeSearch.cpp | 1444 +
src/cpp/session/modules/SessionCodeSearch.hpp | 34 +
src/cpp/session/modules/SessionCodeTools.R | 247 +
src/cpp/session/modules/SessionCompileAttributes.R | 8 +
src/cpp/session/modules/SessionConsole.R | 16 +
src/cpp/session/modules/SessionConsole.cpp | 181 +
src/cpp/session/modules/SessionConsole.hpp | 33 +
src/cpp/session/modules/SessionConsoleProcess.cpp | 765 +
src/cpp/session/modules/SessionConsoleProcess.hpp | 256 +
src/cpp/session/modules/SessionCrypto.cpp | 78 +
src/cpp/session/modules/SessionCrypto.hpp | 37 +
src/cpp/session/modules/SessionDataImport.R | 125 +
src/cpp/session/modules/SessionDataViewer.R | 57 +
src/cpp/session/modules/SessionDirty.cpp | 132 +
src/cpp/session/modules/SessionDirty.hpp | 33 +
src/cpp/session/modules/SessionEnvironment.R | 578 +
src/cpp/session/modules/SessionErrors.R | 189 +
src/cpp/session/modules/SessionErrors.cpp | 223 +
src/cpp/session/modules/SessionErrors.hpp | 44 +
src/cpp/session/modules/SessionFiles.R | 39 +
src/cpp/session/modules/SessionFiles.cpp | 919 +
src/cpp/session/modules/SessionFiles.hpp | 36 +
.../session/modules/SessionFilesListingMonitor.cpp | 208 +
.../session/modules/SessionFilesListingMonitor.hpp | 93 +
src/cpp/session/modules/SessionFilesQuotas.cpp | 244 +
src/cpp/session/modules/SessionFilesQuotas.hpp | 37 +
src/cpp/session/modules/SessionFind.cpp | 587 +
src/cpp/session/modules/SessionFind.hpp | 34 +
src/cpp/session/modules/SessionGit.cpp | 2857 ++
src/cpp/session/modules/SessionGit.hpp | 86 +
src/cpp/session/modules/SessionHTMLPreview.R | 40 +
src/cpp/session/modules/SessionHTMLPreview.cpp | 1048 +
src/cpp/session/modules/SessionHTMLPreview.hpp | 37 +
src/cpp/session/modules/SessionHelp.R | 163 +
src/cpp/session/modules/SessionHelp.cpp | 917 +
src/cpp/session/modules/SessionHelp.hpp | 33 +
src/cpp/session/modules/SessionHistory.cpp | 409 +
src/cpp/session/modules/SessionHistory.hpp | 33 +
src/cpp/session/modules/SessionHistoryArchive.cpp | 217 +
src/cpp/session/modules/SessionHistoryArchive.hpp | 70 +
src/cpp/session/modules/SessionLimits.cpp | 73 +
src/cpp/session/modules/SessionLimits.hpp | 33 +
src/cpp/session/modules/SessionLists.cpp | 354 +
src/cpp/session/modules/SessionLists.hpp | 37 +
src/cpp/session/modules/SessionOverlay.R | 14 +
src/cpp/session/modules/SessionPackages.R | 439 +
src/cpp/session/modules/SessionPackages.cpp | 251 +
src/cpp/session/modules/SessionPackages.hpp | 33 +
src/cpp/session/modules/SessionPath.cpp | 133 +
src/cpp/session/modules/SessionPath.hpp | 35 +
src/cpp/session/modules/SessionPlots.cpp | 811 +
src/cpp/session/modules/SessionPlots.hpp | 35 +
src/cpp/session/modules/SessionPresentation.R | 35 +
src/cpp/session/modules/SessionProfiler.R | 16 +
src/cpp/session/modules/SessionProfiler.cpp | 51 +
src/cpp/session/modules/SessionProfiler.hpp | 33 +
src/cpp/session/modules/SessionRMarkdown.cpp | 57 +
src/cpp/session/modules/SessionRMarkdown.hpp | 33 +
src/cpp/session/modules/SessionRPubs.R | 422 +
src/cpp/session/modules/SessionRPubs.cpp | 432 +
src/cpp/session/modules/SessionRPubs.hpp | 37 +
src/cpp/session/modules/SessionSVN.cpp | 1849 ++
src/cpp/session/modules/SessionSVN.hpp | 76 +
src/cpp/session/modules/SessionShinyApps.R | 53 +
src/cpp/session/modules/SessionShinyApps.cpp | 51 +
src/cpp/session/modules/SessionShinyApps.hpp | 33 +
src/cpp/session/modules/SessionShinyViewer.R | 49 +
src/cpp/session/modules/SessionShinyViewer.cpp | 228 +
src/cpp/session/modules/SessionShinyViewer.hpp | 39 +
src/cpp/session/modules/SessionSource.R | 300 +
src/cpp/session/modules/SessionSource.cpp | 1072 +
src/cpp/session/modules/SessionSource.hpp | 46 +
src/cpp/session/modules/SessionSpelling.R | 64 +
src/cpp/session/modules/SessionSpelling.cpp | 329 +
src/cpp/session/modules/SessionSpelling.hpp | 37 +
src/cpp/session/modules/SessionUpdates.R | 22 +
src/cpp/session/modules/SessionUpdates.cpp | 155 +
src/cpp/session/modules/SessionUpdates.hpp | 33 +
src/cpp/session/modules/SessionVCS.cpp | 311 +
src/cpp/session/modules/SessionVCS.hpp | 64 +
src/cpp/session/modules/SessionViewer.cpp | 178 +
src/cpp/session/modules/SessionViewer.hpp | 33 +
src/cpp/session/modules/SessionWorkbench.cpp | 832 +
src/cpp/session/modules/SessionWorkbench.hpp | 37 +
src/cpp/session/modules/build/SessionBuild.cpp | 1665 +
src/cpp/session/modules/build/SessionBuild.hpp | 37 +
.../modules/build/SessionBuildEnvironment.cpp | 237 +
.../modules/build/SessionBuildEnvironment.hpp | 38 +
.../session/modules/build/SessionBuildErrors.cpp | 254 +
.../session/modules/build/SessionBuildErrors.hpp | 104 +
.../session/modules/build/SessionBuildUtils.cpp | 34 +
.../session/modules/build/SessionBuildUtils.hpp | 51 +
src/cpp/session/modules/build/SessionSourceCpp.cpp | 288 +
src/cpp/session/modules/build/SessionSourceCpp.hpp | 36 +
src/cpp/session/modules/data/DataViewer.cpp | 276 +
src/cpp/session/modules/data/DataViewer.hpp | 35 +
src/cpp/session/modules/data/SessionData.cpp | 45 +
src/cpp/session/modules/data/SessionData.hpp | 35 +
.../modules/environment/EnvironmentMonitor.cpp | 236 +
.../modules/environment/EnvironmentMonitor.hpp | 47 +
.../modules/environment/EnvironmentUtils.cpp | 223 +
.../modules/environment/EnvironmentUtils.hpp | 30 +
.../modules/environment/SessionEnvironment.cpp | 1170 +
.../modules/environment/SessionEnvironment.hpp | 37 +
src/cpp/session/modules/overlay/SessionOverlay.cpp | 33 +
src/cpp/session/modules/overlay/SessionOverlay.hpp | 34 +
.../modules/presentation/PresentationLog.cpp | 369 +
.../modules/presentation/PresentationLog.hpp | 100 +
.../modules/presentation/PresentationOverlay.cpp | 44 +
.../modules/presentation/PresentationState.cpp | 232 +
.../modules/presentation/PresentationState.hpp | 65 +
.../modules/presentation/SessionPresentation.cpp | 511 +
.../modules/presentation/SessionPresentation.hpp | 44 +
.../modules/presentation/SlideMediaRenderer.cpp | 40 +
.../modules/presentation/SlideMediaRenderer.hpp | 48 +
.../modules/presentation/SlideNavigationList.cpp | 145 +
.../modules/presentation/SlideNavigationList.hpp | 57 +
.../session/modules/presentation/SlideParser.cpp | 504 +
.../session/modules/presentation/SlideParser.hpp | 189 +
.../modules/presentation/SlideQuizRenderer.cpp | 159 +
.../modules/presentation/SlideQuizRenderer.hpp | 31 +
.../session/modules/presentation/SlideRenderer.cpp | 555 +
.../session/modules/presentation/SlideRenderer.hpp | 44 +
.../modules/presentation/SlideRequestHandler.cpp | 1255 +
.../modules/presentation/SlideRequestHandler.hpp | 67 +
src/cpp/session/modules/shiny/SessionShiny.cpp | 140 +
src/cpp/session/modules/shiny/SessionShiny.hpp | 35 +
src/cpp/session/modules/tex/SessionCompilePdf.cpp | 883 +
src/cpp/session/modules/tex/SessionCompilePdf.hpp | 53 +
.../modules/tex/SessionCompilePdfSupervisor.cpp | 157 +
.../modules/tex/SessionCompilePdfSupervisor.hpp | 58 +
src/cpp/session/modules/tex/SessionPdfLatex.cpp | 449 +
src/cpp/session/modules/tex/SessionPdfLatex.hpp | 89 +
.../session/modules/tex/SessionRnwConcordance.cpp | 413 +
.../session/modules/tex/SessionRnwConcordance.hpp | 182 +
src/cpp/session/modules/tex/SessionRnwWeave.cpp | 580 +
src/cpp/session/modules/tex/SessionRnwWeave.hpp | 93 +
src/cpp/session/modules/tex/SessionSynctex.cpp | 374 +
src/cpp/session/modules/tex/SessionSynctex.hpp | 44 +
src/cpp/session/modules/tex/SessionTexUtils.cpp | 186 +
src/cpp/session/modules/tex/SessionTexUtils.hpp | 65 +
src/cpp/session/modules/tex/SessionViewPdf.cpp | 75 +
src/cpp/session/modules/tex/SessionViewPdf.hpp | 40 +
src/cpp/session/modules/vcs/SessionVCSCore.cpp | 37 +
src/cpp/session/modules/vcs/SessionVCSCore.hpp | 95 +
src/cpp/session/modules/vcs/SessionVCSUtils.cpp | 206 +
src/cpp/session/modules/vcs/SessionVCSUtils.hpp | 76 +
src/cpp/session/postback/CMakeLists.txt | 60 +
src/cpp/session/postback/PostbackMain.cpp | 124 +
src/cpp/session/postback/PostbackOptions.cpp | 62 +
src/cpp/session/postback/PostbackOptions.hpp | 64 +
src/cpp/session/postback/askpass-passthrough | 18 +
src/cpp/session/postback/rpostback-askpass | 20 +
src/cpp/session/postback/rpostback-editfile | 20 +
src/cpp/session/postback/rpostback-gitssh | 22 +
src/cpp/session/postback/rpostback-pdfviewer | 20 +
src/cpp/session/projects/SessionProjectContext.cpp | 702 +
.../session/projects/SessionProjectFirstRun.cpp | 86 +
.../session/projects/SessionProjectFirstRun.hpp | 35 +
src/cpp/session/projects/SessionProjects.cpp | 680 +
.../session/projects/SessionProjectsInternal.hpp | 39 +
src/cpp/session/r-ldpath.in | 21 +
src/cpp/session/resources/.gitignore | 1 +
src/cpp/session/resources/R.css | 97 +
src/cpp/session/resources/markdown.css | 124 +
src/cpp/session/resources/markdown.html | 25 +
src/cpp/session/resources/markdown_help.html | 243 +
src/cpp/session/resources/mathjax.html | 4 +
src/cpp/session/resources/presentation/helpdoc.css | 16 +
.../session/resources/presentation/helpdoc.html | 28 +
.../session/resources/presentation/mathjax.html | 10 +
.../resources/presentation/revealjs/LICENSE | 19 +
.../presentation/revealjs/css/print/paper.css | 176 +
.../presentation/revealjs/css/print/pdf.css | 164 +
.../resources/presentation/revealjs/css/reveal.css | 1370 +
.../presentation/revealjs/css/reveal.min.css | 7 +
.../presentation/revealjs/css/theme/simple.css | 130 +
.../presentation/revealjs/fonts/Lato-Bold.ttf | Bin 0 -> 121788 bytes
.../revealjs/fonts/Lato-BoldItalic.ttf | Bin 0 -> 120312 bytes
.../presentation/revealjs/fonts/Lato-Italic.ttf | Bin 0 -> 118352 bytes
.../presentation/revealjs/fonts/Lato-Regular.ttf | Bin 0 -> 120196 bytes
.../resources/presentation/revealjs/fonts/Lato.css | 24 +
.../presentation/revealjs/fonts/NewsCycle-Bold.ttf | Bin 0 -> 65096 bytes
.../revealjs/fonts/NewsCycle-Regular.ttf | Bin 0 -> 70872 bytes
.../presentation/revealjs/fonts/NewsCycle.css | 12 +
.../resources/presentation/revealjs/js/reveal.js | 2229 ++
.../presentation/revealjs/js/reveal.min.js | 8 +
.../presentation/revealjs/lib/js/classList.js | 2 +
.../presentation/revealjs/lib/js/head.min.js | 8 +
.../presentation/revealjs/lib/js/html5shiv.js | 7 +
.../presentation/slides-images/correct.png | Bin 0 -> 1748 bytes
.../presentation/slides-images/incorrect.png | Bin 0 -> 2459 bytes
src/cpp/session/resources/presentation/slides.css | 346 +
src/cpp/session/resources/presentation/slides.html | 152 +
src/cpp/session/resources/presentation/slides.js | 99 +
src/cpp/session/resources/r_highlight.html | 37 +
src/cpp/session/resources/sumatrapdfrestrict.ini | 60 +
.../session/resources/templates/r_documentation.Rd | 64 +
.../resources/templates/r_documentation_empty.Rd | 5 +
src/cpp/session/resources/templates/r_html.Rhtml | 22 +
src/cpp/session/resources/templates/r_markdown.Rmd | 17 +
.../resources/templates/r_presentation.Rpres | 28 +
src/cpp/session/resources/templates/rcpp.cpp | 13 +
src/cpp/session/resources/templates/shiny/server.R | 21 +
src/cpp/session/resources/templates/shiny/ui.R | 28 +
src/cpp/session/resources/templates/sweave.Rnw | 8 +
src/cpp/session/rsession.exe.manifest | 23 +
src/cpp/session/rsession.rc.in | 29 +
src/cpp/session/session-config.h.in | 19 +
src/cpp/session/workers/CMakeLists.txt | 43 +
.../session/workers/SessionWebRequestWorker.cpp | 50 +
.../session/workers/SessionWebRequestWorker.hpp | 33 +
src/cpp/tools/.gitignore | 3 +
src/cpp/tools/extract-rstudio_boost | 102 +
src/cpp/tools/highlight-version | 1 +
src/cpp/tools/revealjs-version | 1 +
src/cpp/tools/sync-highlight | 67 +
src/cpp/tools/sync-revealjs | 38 +
src/cpp/tools/sync-sundown | 29 +
src/gwt/.classpath | 20 +
src/gwt/.gitignore | 7 +
src/gwt/.idea/.gitignore | 1 +
src/gwt/.idea/ant.xml | 15 +
src/gwt/.idea/artifacts/Client_war_exploded.xml | 13 +
src/gwt/.idea/compiler.xml | 28 +
src/gwt/.idea/encodings.xml | 5 +
.../.idea/inspectionProfiles/Project_Default.xml | 40 +
.../.idea/inspectionProfiles/profiles_settings.xml | 7 +
src/gwt/.idea/libraries/gin.xml | 15 +
src/gwt/.idea/libraries/gwt.xml | 17 +
src/gwt/.idea/misc.xml | 66 +
src/gwt/.idea/modules.xml | 9 +
src/gwt/.idea/projectCodeStyle.xml | 132 +
src/gwt/.idea/runConfigurations/Run_Client.xml | 21 +
src/gwt/.idea/vcs.xml | 7 +
src/gwt/.project | 23 +
.../.settings/com.google.gdt.eclipse.core.prefs | 3 +
.../.settings/com.google.gwt.eclipse.core.prefs | 4 +
src/gwt/CMakeLists.txt | 29 +
src/gwt/GWT.iml | 45 +
src/gwt/RStudio.launch | 23 +
src/gwt/acesupport/acemode/auto_brace_insert.js | 199 +
src/gwt/acesupport/acemode/c_cpp.js | 197 +
src/gwt/acesupport/acemode/c_cpp_fold_mode.js | 85 +
.../acesupport/acemode/c_cpp_highlight_rules.js | 226 +
src/gwt/acesupport/acemode/dcf.js | 35 +
src/gwt/acesupport/acemode/dcf_highlight_rules.js | 39 +
.../acemode/doc_comment_highlight_rules.js | 80 +
src/gwt/acesupport/acemode/markdown.js | 44 +
src/gwt/acesupport/acemode/markdown_folding.js | 112 +
.../acesupport/acemode/markdown_highlight_rules.js | 218 +
src/gwt/acesupport/acemode/r.js | 111 +
.../acesupport/acemode/r_background_highlighter.js | 130 +
src/gwt/acesupport/acemode/r_code_model.js | 1214 +
src/gwt/acesupport/acemode/r_highlight_rules.js | 188 +
.../acesupport/acemode/r_matching_brace_outdent.js | 82 +
src/gwt/acesupport/acemode/r_scope_tree.js | 395 +
src/gwt/acesupport/acemode/rdoc.js | 40 +
src/gwt/acesupport/acemode/rdoc_highlight_rules.js | 99 +
src/gwt/acesupport/acemode/rhtml.js | 64 +
.../acesupport/acemode/rhtml_highlight_rules.js | 50 +
src/gwt/acesupport/acemode/rmarkdown.js | 180 +
.../acemode/rmarkdown_highlight_rules.js | 65 +
src/gwt/acesupport/acemode/sweave.js | 97 +
.../acemode/sweave_background_highlighter.js | 211 +
.../acesupport/acemode/sweave_highlight_rules.js | 55 +
src/gwt/acesupport/acemode/tex.js | 49 +
src/gwt/acesupport/acemode/tex_highlight_rules.js | 108 +
src/gwt/acesupport/acetheme/default.js | 24 +
src/gwt/acesupport/extern.js | 16 +
src/gwt/acesupport/loader.js | 187 +
src/gwt/build.xml | 207 +
.../src/com/google/gwt/user/client/ui/MenuBar.java | 1397 +
.../google/gwt/user/client/ui/MenuBar.java.diff | 53 +
.../gwt/user/client/ui/SplitLayoutPanel.java | 429 +
.../com/google/gwt/user/client/ui/SplitPanel.java | 479 +
.../user/client/ui/SplitterBeforeResizeEvent.java | 24 +
.../client/ui/SplitterBeforeResizeHandler.java | 8 +
.../gwt/user/client/ui/SplitterResizedEvent.java | 24 +
.../gwt/user/client/ui/SplitterResizedHandler.java | 8 +
src/gwt/src/com/google/gwt/user/revertchanges.sh | 12 +
.../com/google/gwt/widgetideas/SliderBar.gwt.xml | 6 +
.../gwt/widgetideas/client/ResizableWidget.java | 46 +
.../client/ResizableWidgetCollection.java | 327 +
.../google/gwt/widgetideas/client/SliderBar.java | 1002 +
.../com/google/gwt/widgetideas/client/slider.png | Bin 0 -> 608 bytes
.../gwt/widgetideas/client/sliderDisabled.png | Bin 0 -> 590 bytes
.../gwt/widgetideas/client/sliderSliding.png | Bin 0 -> 498 bytes
src/gwt/src/org/rstudio/core/Core.gwt.xml | 59 +
src/gwt/src/org/rstudio/core/client/AsyncShim.java | 111 +
src/gwt/src/org/rstudio/core/client/Barrier.java | 81 +
src/gwt/src/org/rstudio/core/client/BrowseCap.java | 208 +
.../org/rstudio/core/client/BrowseCapFirefox.java | 24 +
.../src/org/rstudio/core/client/BrowseCapIE8.java | 31 +
.../org/rstudio/core/client/BrowseCapSafari.java | 24 +
.../rstudio/core/client/CodeNavigationTarget.java | 42 +
.../src/org/rstudio/core/client/CommandUtil.java | 40 +
.../org/rstudio/core/client/CommandWithArg.java | 20 +
src/gwt/src/org/rstudio/core/client/CsvReader.java | 109 +
src/gwt/src/org/rstudio/core/client/CsvWriter.java | 56 +
src/gwt/src/org/rstudio/core/client/Debug.java | 119 +
.../org/rstudio/core/client/DebugFilePosition.java | 73 +
.../org/rstudio/core/client/DuplicateHelper.java | 275 +
.../src/org/rstudio/core/client/ElementIds.java | 28 +
.../core/client/ExternalJavaScriptLoader.java | 132 +
.../src/org/rstudio/core/client/FilePosition.java | 46 +
.../rstudio/core/client/HandlerRegistrations.java | 42 +
.../org/rstudio/core/client/IntervalTracker.java | 47 +
.../src/org/rstudio/core/client/Invalidation.java | 45 +
.../org/rstudio/core/client/MessageDisplay.java | 334 +
src/gwt/src/org/rstudio/core/client/Pair.java | 27 +
src/gwt/src/org/rstudio/core/client/Point.java | 71 +
src/gwt/src/org/rstudio/core/client/Rectangle.java | 306 +
.../org/rstudio/core/client/ResultCallback.java | 26 +
.../src/org/rstudio/core/client/SafeHtmlUtil.java | 107 +
.../org/rstudio/core/client/SafeUriStringImpl.java | 55 +
.../org/rstudio/core/client/SeparatorManager.java | 75 +
.../org/rstudio/core/client/SerializedCommand.java | 22 +
.../core/client/SerializedCommandQueue.java | 107 +
src/gwt/src/org/rstudio/core/client/Size.java | 70 +
src/gwt/src/org/rstudio/core/client/Stopwatch.java | 36 +
.../src/org/rstudio/core/client/StringUtil.java | 481 +
.../rstudio/core/client/TimeBufferedCommand.java | 152 +
src/gwt/src/org/rstudio/core/client/Triad.java | 29 +
.../org/rstudio/core/client/UnicodeLetters.java | 86 +
src/gwt/src/org/rstudio/core/client/ValueSink.java | 20 +
.../org/rstudio/core/client/VirtualConsole.java | 134 +
.../core/client/WidgetHandlerRegistration.java | 64 +
src/gwt/src/org/rstudio/core/client/WordWrap.java | 210 +
.../cellview/AutoHidingSplitLayoutPanel.java | 45 +
.../core/client/cellview/ColumnSortInfo.java | 77 +
.../core/client/cellview/ImageButtonColumn.java | 71 +
.../rstudio/core/client/cellview/LinkColumn.css | 12 +
.../rstudio/core/client/cellview/LinkColumn.java | 112 +
.../core/client/cellview/ScrollingDataGrid.java | 37 +
.../core/client/cellview/TriStateCheckboxCell.java | 145 +
.../core/client/cellview/checkboxIndeterminate.png | Bin 0 -> 442 bytes
.../rstudio/core/client/cellview/checkboxOff.png | Bin 0 -> 436 bytes
.../rstudio/core/client/cellview/checkboxOn.png | Bin 0 -> 490 bytes
.../rstudio/core/client/command/AppCommand.java | 395 +
.../rstudio/core/client/command/AppMenuBar.java | 137 +
.../rstudio/core/client/command/AppMenuItem.java | 83 +
.../rstudio/core/client/command/BaseMenuBar.java | 194 +
.../rstudio/core/client/command/CommandBinder.java | 61 +
.../rstudio/core/client/command/CommandBundle.java | 39 +
.../core/client/command/CommandContext.java | 19 +
.../rstudio/core/client/command/CommandEvent.java | 46 +
.../core/client/command/CommandHandler.java | 22 +
.../core/client/command/DisabledMenuItem.java | 40 +
.../core/client/command/EnabledChangedEvent.java | 46 +
.../core/client/command/EnabledChangedHandler.java | 22 +
.../org/rstudio/core/client/command/Handler.java | 24 +
.../core/client/command/ImageResourceProvider.java | 25 +
.../core/client/command/KeyboardShortcut.java | 174 +
.../rstudio/core/client/command/MenuCallback.java | 29 +
.../rstudio/core/client/command/ShortcutInfo.java | 75 +
.../core/client/command/ShortcutManager.java | 198 +
.../core/client/command/ShortcutViewer.java | 117 +
.../command/SimpleImageResourceProvider.java | 40 +
.../client/command/SubMenuVisibleChangedEvent.java | 45 +
.../command/SubMenuVisibleChangedHandler.java | 22 +
.../core/client/command/VisibleChangedEvent.java | 46 +
.../core/client/command/VisibleChangedHandler.java | 22 +
.../client/command/impl/DesktopMenuCallback.java | 59 +
.../core/client/command/impl/WebMenuCallback.java | 72 +
.../org/rstudio/core/client/dom/DomMetrics.java | 102 +
.../src/org/rstudio/core/client/dom/DomUtils.java | 732 +
.../src/org/rstudio/core/client/dom/ElementEx.java | 72 +
.../rstudio/core/client/dom/IFrameElementEx.java | 28 +
.../org/rstudio/core/client/dom/NativeScreen.java | 35 +
.../org/rstudio/core/client/dom/NativeWindow.java | 67 +
.../src/org/rstudio/core/client/dom/TextEx.java | 51 +
.../src/org/rstudio/core/client/dom/WindowEx.java | 175 +
.../core/client/dom/impl/DomUtilsIE8Impl.java | 172 +
.../rstudio/core/client/dom/impl/DomUtilsImpl.java | 46 +
.../core/client/dom/impl/DomUtilsStandardImpl.java | 198 +
.../core/client/dom/impl/NodeRelativePosition.java | 155 +
.../org/rstudio/core/client/dom/impl/Range.java | 127 +
.../rstudio/core/client/dom/impl/Selection.java | 73 +
.../core/client/events/BarrierReleasedEvent.java | 34 +
.../core/client/events/BarrierReleasedHandler.java | 22 +
.../core/client/events/BeforeShowEvent.java | 38 +
.../core/client/events/BeforeShowHandler.java | 22 +
.../core/client/events/EnsureHeightEvent.java | 49 +
.../core/client/events/EnsureHeightHandler.java | 22 +
.../core/client/events/EnsureHiddenEvent.java | 38 +
.../core/client/events/EnsureHiddenHandler.java | 22 +
.../core/client/events/EnsureVisibleEvent.java | 52 +
.../core/client/events/EnsureVisibleHandler.java | 22 +
.../core/client/events/HasContextMenuHandlers.java | 23 +
.../client/events/HasEnsureHeightHandlers.java | 22 +
.../client/events/HasEnsureHiddenHandlers.java | 23 +
.../client/events/HasEnsureVisibleHandlers.java | 22 +
.../core/client/events/HasNativeKeyHandlers.java | 23 +
.../client/events/HasSelectionCommitHandlers.java | 24 +
.../core/client/events/HasTabCloseHandlers.java | 22 +
.../core/client/events/HasTabClosedHandlers.java | 22 +
.../core/client/events/HasTabClosingHandlers.java | 22 +
.../events/HasWindowStateChangeHandlers.java | 23 +
.../core/client/events/NativeKeyDownEvent.java | 72 +
.../core/client/events/NativeKeyDownHandler.java | 22 +
.../core/client/events/NativeKeyPressEvent.java | 81 +
.../core/client/events/NativeKeyPressHandler.java | 22 +
.../core/client/events/SelectionCommitEvent.java | 69 +
.../core/client/events/SelectionCommitHandler.java | 22 +
.../rstudio/core/client/events/TabCloseEvent.java | 51 +
.../core/client/events/TabCloseHandler.java | 22 +
.../rstudio/core/client/events/TabClosedEvent.java | 46 +
.../core/client/events/TabClosedHandler.java | 22 +
.../core/client/events/TabClosingEvent.java | 58 +
.../core/client/events/TabClosingHandler.java | 22 +
.../core/client/events/WindowStateChangeEvent.java | 48 +
.../client/events/WindowStateChangeHandler.java | 22 +
.../core/client/files/FileSystemContext.java | 95 +
.../rstudio/core/client/files/FileSystemItem.java | 420 +
.../core/client/files/PosixFileSystemContext.java | 154 +
.../files/filedialog/ChooseFolderDialog.java | 161 +
.../files/filedialog/ChooseFolderDialog2.java | 83 +
.../files/filedialog/DirectoryContentsWidget.java | 403 +
.../core/client/files/filedialog/FileDialog.java | 223 +
.../files/filedialog/FileDialogResources.java | 42 +
.../client/files/filedialog/FileDialogStyles.css | 133 +
.../client/files/filedialog/FileDialogStyles.java | 41 +
.../client/files/filedialog/FileSystemDialog.java | 425 +
.../client/files/filedialog/OpenFileDialog.java | 30 +
.../files/filedialog/PathBreadcrumbWidget.java | 235 +
.../client/files/filedialog/SaveFileDialog.java | 88 +
.../core/client/files/filedialog/browse.png | Bin 0 -> 405 bytes
.../core/client/files/filedialog/dirseparator.png | Bin 0 -> 2894 bytes
.../rstudio/core/client/files/filedialog/fade.png | Bin 0 -> 2862 bytes
.../rstudio/core/client/files/filedialog/home.png | Bin 0 -> 543 bytes
.../org/rstudio/core/client/js/BaseExpression.java | 20 +
.../core/client/js/JavaScriptPassthrough.java | 19 +
.../src/org/rstudio/core/client/js/JsObject.java | 139 +
.../rstudio/core/client/js/JsObjectInjector.java | 20 +
src/gwt/src/org/rstudio/core/client/js/JsUtil.java | 134 +
.../rstudio/core/client/jsonrpc/RequestLog.java | 52 +
.../core/client/jsonrpc/RequestLogEntry.java | 149 +
.../org/rstudio/core/client/jsonrpc/RpcError.java | 92 +
.../rstudio/core/client/jsonrpc/RpcObjectList.java | 50 +
.../rstudio/core/client/jsonrpc/RpcRequest.java | 215 +
.../core/client/jsonrpc/RpcRequestCallback.java | 22 +
.../rstudio/core/client/jsonrpc/RpcResponse.java | 89 +
.../core/client/jsonrpc/RpcResponseHandler.java | 20 +
.../core/client/jsonrpc/RpcUnderlyingError.java | 38 +
.../core/client/layout/AnimationHelper.java | 214 +
.../core/client/layout/BinarySplitLayoutPanel.java | 337 +
.../core/client/layout/DelayFadeInHelper.java | 73 +
.../core/client/layout/DualWindowLayoutPanel.java | 629 +
.../core/client/layout/FadeInAnimation.java | 72 +
.../core/client/layout/FadeOutAnimation.java | 54 +
.../rstudio/core/client/layout/LogicalWindow.java | 146 +
.../client/layout/RequiresVisibilityChanged.java | 20 +
.../rstudio/core/client/layout/ScreenUtils.java | 66 +
.../rstudio/core/client/layout/WindowState.java | 24 +
.../rstudio/core/client/patch/DiffMatchPatch.java | 58 +
.../rstudio/core/client/patch/SubstringDiff.java | 111 +
.../rstudio/core/client/patch/diff_match_patch.js | 46 +
.../core/client/prefs/PreferencesDialogBase.css | 79 +
.../core/client/prefs/PreferencesDialogBase.java | 217 +
.../prefs/PreferencesDialogBaseResources.java | 53 +
.../client/prefs/PreferencesDialogPaneBase.java | 140 +
.../rstudio/core/client/prefs/SectionChooser.java | 105 +
.../rstudio/core/client/prefs/iconCodeEditing.png | Bin 0 -> 1366 bytes
.../rstudio/core/client/prefs/iconCompilePdf.png | Bin 0 -> 2626 bytes
.../src/org/rstudio/core/client/prefs/iconR.png | Bin 0 -> 1877 bytes
.../core/client/prefs/iconSourceControl.png | Bin 0 -> 2203 bytes
.../org/rstudio/core/client/prefs/iconSpelling.png | Bin 0 -> 1524 bytes
.../src/org/rstudio/core/client/regex/Match.java | 47 +
.../src/org/rstudio/core/client/regex/Pattern.java | 102 +
.../core/client/resources/CoreResources.java | 39 +
.../rstudio/core/client/resources/CoreStyles.java | 22 +
.../core/client/resources/StaticDataResource.java | 24 +
.../org/rstudio/core/client/resources/clear.gif | Bin 0 -> 43 bytes
.../org/rstudio/core/client/resources/progress.gif | Bin 0 -> 3410 bytes
.../core/client/resources/progress_gray.gif | Bin 0 -> 2882 bytes
.../core/client/resources/progress_large.gif | Bin 0 -> 11645 bytes
.../core/client/resources/progress_large_gray.gif | Bin 0 -> 12034 bytes
.../org/rstudio/core/client/resources/styles.css | 102 +
.../rstudio/core/client/tex/TexMagicComment.java | 82 +
.../core/client/theme/DocTabLayoutPanel.java | 373 +
.../theme/MinimizedModuleTabLayoutPanel.java | 76 +
.../core/client/theme/MinimizedWindowFrame.java | 153 +
.../core/client/theme/ModuleTabLayoutPanel.java | 174 +
.../core/client/theme/PrimaryWindowFrame.java | 99 +
.../client/theme/RStudioCellTableResources.java | 24 +
.../core/client/theme/RStudioCellTableStyle.css | 143 +
.../core/client/theme/RStudioCellTableStyle.java | 22 +
.../client/theme/RStudioDataGridResources.java | 25 +
.../core/client/theme/RStudioDataGridStyle.css | 164 +
.../core/client/theme/RStudioDataGridStyle.java | 22 +
.../rstudio/core/client/theme/ShadowBorder.java | 105 +
.../org/rstudio/core/client/theme/ThemeFonts.java | 74 +
.../org/rstudio/core/client/theme/WindowFrame.java | 354 +
.../core/client/theme/res/ThemeResources.java | 194 +
.../rstudio/core/client/theme/res/ThemeStyles.java | 157 +
.../core/client/theme/res/activeBreakpoint.png | Bin 0 -> 207 bytes
.../core/client/theme/res/activeDocTabLeft.png | Bin 0 -> 370 bytes
.../core/client/theme/res/activeDocTabRight.png | Bin 0 -> 919 bytes
.../core/client/theme/res/activeDocTabTile.png | Bin 0 -> 324 bytes
.../core/client/theme/res/backgroundGradient.png | Bin 0 -> 1410 bytes
.../org/rstudio/core/client/theme/res/chevron.png | Bin 0 -> 298 bytes
.../org/rstudio/core/client/theme/res/clear.gif | Bin 0 -> 49 bytes
.../rstudio/core/client/theme/res/clearSearch.png | Bin 0 -> 306 bytes
.../rstudio/core/client/theme/res/closeChevron.png | Bin 0 -> 162 bytes
.../org/rstudio/core/client/theme/res/closeTab.png | Bin 0 -> 262 bytes
.../core/client/theme/res/closeTabSelected.png | Bin 0 -> 179 bytes
.../core/client/theme/res/codeTransform.png | Bin 0 -> 566 bytes
.../theme/res/desktopGlobalToolbarBackground.png | Bin 0 -> 355 bytes
.../rstudio/core/client/theme/res/dialogBottom.png | Bin 0 -> 2892 bytes
.../core/client/theme/res/dialogBottomLeft.png | Bin 0 -> 509 bytes
.../core/client/theme/res/dialogBottomRight.png | Bin 0 -> 476 bytes
.../rstudio/core/client/theme/res/dialogLeft.png | Bin 0 -> 268 bytes
.../rstudio/core/client/theme/res/dialogRight.png | Bin 0 -> 187 bytes
.../rstudio/core/client/theme/res/dialogTop.png | Bin 0 -> 810 bytes
.../core/client/theme/res/dialogTopLeft.png | Bin 0 -> 777 bytes
.../core/client/theme/res/dialogTopRight.png | Bin 0 -> 857 bytes
.../rstudio/core/client/theme/res/docTabLeft.png | Bin 0 -> 358 bytes
.../rstudio/core/client/theme/res/docTabRight.png | Bin 0 -> 992 bytes
.../rstudio/core/client/theme/res/docTabTile.png | Bin 0 -> 306 bytes
.../core/client/theme/res/dropDownArrow.png | Bin 0 -> 2857 bytes
.../rstudio/core/client/theme/res/errorSmall.png | Bin 0 -> 630 bytes
.../core/client/theme/res/executingLine.png | Bin 0 -> 217 bytes
.../src/org/rstudio/core/client/theme/res/help.png | Bin 0 -> 676 bytes
.../core/client/theme/res/horizontalHandle.png | Bin 0 -> 2827 bytes
.../core/client/theme/res/inactiveBreakpoint.png | Bin 0 -> 269 bytes
.../rstudio/core/client/theme/res/infoSmall.png | Bin 0 -> 600 bytes
.../core/client/theme/res/inlineDeleteIcon.png | Bin 0 -> 703 bytes
.../core/client/theme/res/inlineEditIcon.png | Bin 0 -> 614 bytes
.../core/client/theme/res/linkDownArrow.png | Bin 0 -> 136 bytes
.../org/rstudio/core/client/theme/res/maximize.png | Bin 0 -> 249 bytes
.../core/client/theme/res/maximizeSelected.png | Bin 0 -> 267 bytes
.../core/client/theme/res/mediumDropDownArrow.png | Bin 0 -> 143 bytes
.../rstudio/core/client/theme/res/menuBevel.png | Bin 0 -> 152 bytes
.../rstudio/core/client/theme/res/menuCheck.png | Bin 0 -> 374 bytes
.../core/client/theme/res/menuDownArrow.png | Bin 0 -> 2828 bytes
.../org/rstudio/core/client/theme/res/minimize.png | Bin 0 -> 219 bytes
.../core/client/theme/res/minimizeSelected.png | Bin 0 -> 216 bytes
.../client/theme/res/multiPodActiveTabLeft.png | Bin 0 -> 323 bytes
.../client/theme/res/multiPodActiveTabRight.png | Bin 0 -> 346 bytes
.../client/theme/res/multiPodActiveTabTile.png | Bin 0 -> 275 bytes
.../core/client/theme/res/multiPodTabLeft.png | Bin 0 -> 2810 bytes
.../core/client/theme/res/multiPodTabRight.png | Bin 0 -> 2811 bytes
.../rstudio/core/client/theme/res/multiPodTop.png | Bin 0 -> 2811 bytes
.../core/client/theme/res/multiPodTopFade.png | Bin 0 -> 130 bytes
.../core/client/theme/res/multiPodTopLeft.png | Bin 0 -> 266 bytes
.../core/client/theme/res/multiPodTopRight.png | Bin 0 -> 286 bytes
.../rstudio/core/client/theme/res/newsButton.png | Bin 0 -> 728 bytes
.../core/client/theme/res/pendingBreakpoint.png | Bin 0 -> 225 bytes
.../rstudio/core/client/theme/res/podBottom.png | Bin 0 -> 145 bytes
.../core/client/theme/res/podBottomLeft.png | Bin 0 -> 208 bytes
.../core/client/theme/res/podBottomRight.png | Bin 0 -> 209 bytes
.../org/rstudio/core/client/theme/res/podLeft.png | Bin 0 -> 134 bytes
.../core/client/theme/res/podMinimizedLeft.png | Bin 0 -> 3067 bytes
.../core/client/theme/res/podMinimizedRight.png | Bin 0 -> 3092 bytes
.../core/client/theme/res/podMinimizedTile.png | Bin 0 -> 420 bytes
.../org/rstudio/core/client/theme/res/podRight.png | Bin 0 -> 133 bytes
.../org/rstudio/core/client/theme/res/podTop.png | Bin 0 -> 431 bytes
.../rstudio/core/client/theme/res/podTopLeft.png | Bin 0 -> 436 bytes
.../rstudio/core/client/theme/res/podTopRight.png | Bin 0 -> 455 bytes
.../core/client/theme/res/removePackage.png | Bin 0 -> 361 bytes
.../org/rstudio/core/client/theme/res/restore.png | Bin 0 -> 195 bytes
.../core/client/theme/res/restoreSelected.png | Bin 0 -> 262 bytes
.../org/rstudio/core/client/theme/res/rstudio.png | Bin 0 -> 4367 bytes
.../core/client/theme/res/rstudio_small.png | Bin 0 -> 2116 bytes
.../core/client/theme/res/searchFieldLeft.png | Bin 0 -> 288 bytes
.../core/client/theme/res/searchFieldRight.png | Bin 0 -> 277 bytes
.../core/client/theme/res/searchFieldTile.png | Bin 0 -> 135 bytes
.../core/client/theme/res/smallMagGlassIcon.png | Bin 0 -> 328 bytes
.../core/client/theme/res/tabBackground.png | Bin 0 -> 330 bytes
.../rstudio/core/client/theme/res/themeStyles.css | 1123 +
.../core/client/theme/res/toolbarBackground.png | Bin 0 -> 288 bytes
.../core/client/theme/res/toolbarBackground2.png | Bin 0 -> 2956 bytes
.../core/client/theme/res/toolbarSeparator.png | Bin 0 -> 206 bytes
.../core/client/theme/res/verticalHandle.png | Bin 0 -> 2828 bytes
.../rstudio/core/client/theme/res/warningSmall.png | Bin 0 -> 745 bytes
.../core/client/theme/res/webGlobalToolbarLeft.png | Bin 0 -> 269 bytes
.../client/theme/res/webGlobalToolbarRight.png | Bin 0 -> 264 bytes
.../core/client/theme/res/webGlobalToolbarTile.png | Bin 0 -> 168 bytes
.../theme/res/workspaceSectionHeaderTile.png | Bin 0 -> 272 bytes
.../rstudio/core/client/theme/res/zoomDataset.png | Bin 0 -> 1701 bytes
.../core/client/widget/AnchorableFrame.java | 108 +
.../core/client/widget/BeforeShowCallback.java | 20 +
.../core/client/widget/BottomScrollPanel.java | 105 +
.../org/rstudio/core/client/widget/CanFocus.java | 20 +
.../rstudio/core/client/widget/CaptionWithHelp.css | 9 +
.../core/client/widget/CaptionWithHelp.java | 125 +
.../rstudio/core/client/widget/CenterPanel.java | 30 +
.../rstudio/core/client/widget/CheckboxLabel.java | 56 +
.../org/rstudio/core/client/widget/ClickImage.java | 58 +
.../rstudio/core/client/widget/DialogBuilder.java | 28 +
.../client/widget/DirectoryChooserTextBox.java | 87 +
.../core/client/widget/DoubleClickState.java | 71 +
.../rstudio/core/client/widget/DynamicIFrame.java | 64 +
.../core/client/widget/FastSelectTable.java | 660 +
.../core/client/widget/FileChooserTextBox.java | 71 +
.../rstudio/core/client/widget/FindTextBox.java | 118 +
.../rstudio/core/client/widget/FindTextBox.ui.xml | 25 +
.../rstudio/core/client/widget/FocusContext.java | 68 +
.../rstudio/core/client/widget/FocusHelper.java | 42 +
.../rstudio/core/client/widget/FontDetector.java | 90 +
.../org/rstudio/core/client/widget/FontSizer.css | 26 +
.../org/rstudio/core/client/widget/FontSizer.java | 91 +
.../core/client/widget/FullscreenPopupPanel.java | 150 +
.../rstudio/core/client/widget/GlassAttacher.java | 69 +
.../org/rstudio/core/client/widget/GlassPanel.java | 52 +
.../core/client/widget/HasButtonMethods.java | 22 +
.../core/client/widget/HasCustomizableToolbar.java | 27 +
.../core/client/widget/HeaderBreaksItemCodec.java | 182 +
.../org/rstudio/core/client/widget/HelpButton.java | 65 +
.../core/client/widget/HorizontalCenterPanel.java | 40 +
.../core/client/widget/HtmlFormModalDialog.java | 138 +
.../rstudio/core/client/widget/HyperlinkLabel.java | 103 +
.../org/rstudio/core/client/widget/ImageFrame.java | 125 +
.../org/rstudio/core/client/widget/InfoBar.java | 91 +
.../org/rstudio/core/client/widget/InfoBar.ui.xml | 46 +
.../core/client/widget/InlineToolbarButton.java | 233 +
.../core/client/widget/InlineToolbarButton.ui.xml | 30 +
.../core/client/widget/InlineToolbarSeparator.java | 31 +
.../core/client/widget/IsWidgetAdapter.java | 33 +
.../core/client/widget/IsWidgetWithHeight.java | 22 +
.../core/client/widget/LeftCenterRightBorder.java | 101 +
.../core/client/widget/LeftRightToggleButton.css | 54 +
.../core/client/widget/LeftRightToggleButton.java | 98 +
.../client/widget/LeftRightToggleButton.ui.xml | 16 +
.../org/rstudio/core/client/widget/MenuLabel.java | 22 +
.../rstudio/core/client/widget/MessageDialog.java | 152 +
.../core/client/widget/MessageDialogLabel.java | 43 +
.../core/client/widget/MiniDialogPopupPanel.java | 119 +
.../rstudio/core/client/widget/ModalDialog.java | 123 +
.../core/client/widget/ModalDialogBase.java | 519 +
.../core/client/widget/ModalDialogTracker.java | 42 +
.../core/client/widget/ModalPopupPanel.java | 93 +
.../rstudio/core/client/widget/MultiLineLabel.java | 46 +
.../core/client/widget/MultiSelectCellTable.java | 337 +
.../client/widget/MultipleItemSuggestTextBox.java | 106 +
.../rstudio/core/client/widget/NineUpBorder.css | 71 +
.../rstudio/core/client/widget/NineUpBorder.java | 122 +
.../core/client/widget/NullProgressIndicator.java | 45 +
.../rstudio/core/client/widget/NumericTextBox.java | 87 +
.../core/client/widget/NumericValueWidget.java | 116 +
.../org/rstudio/core/client/widget/Operation.java | 20 +
.../core/client/widget/OperationWithInput.java | 20 +
.../org/rstudio/core/client/widget/PreWidget.java | 91 +
.../rstudio/core/client/widget/ProgressDialog.css | 37 +
.../rstudio/core/client/widget/ProgressDialog.java | 185 +
.../core/client/widget/ProgressDialog.ui.xml | 20 +
.../rstudio/core/client/widget/ProgressImage.java | 55 +
.../core/client/widget/ProgressIndicator.java | 23 +
.../core/client/widget/ProgressOperation.java | 20 +
.../client/widget/ProgressOperationWithInput.java | 20 +
.../rstudio/core/client/widget/ProgressPanel.java | 73 +
.../rstudio/core/client/widget/RStudioFrame.java | 47 +
.../rstudio/core/client/widget/ResizeGripper.css | 4 +
.../rstudio/core/client/widget/ResizeGripper.java | 167 +
.../core/client/widget/ScrollPanelWithClick.java | 45 +
.../client/widget/ScrollableToolbarPopupMenu.java | 94 +
.../rstudio/core/client/widget/SearchDisplay.java | 44 +
.../rstudio/core/client/widget/SearchWidget.java | 330 +
.../rstudio/core/client/widget/SearchWidget.ui.xml | 26 +
.../core/client/widget/SecondaryToolbar.java | 37 +
.../rstudio/core/client/widget/SelectWidget.java | 179 +
.../core/client/widget/ShortcutInfoPanel.java | 105 +
.../core/client/widget/ShortcutInfoPanel.ui.xml | 77 +
.../core/client/widget/ShowContentDialog.java | 100 +
.../core/client/widget/SimpleMenuLabel.java | 37 +
.../client/widget/SimplePanelWithProgress.java | 59 +
.../org/rstudio/core/client/widget/SlideLabel.css | 48 +
.../org/rstudio/core/client/widget/SlideLabel.java | 271 +
.../rstudio/core/client/widget/SlideLabel.ui.xml | 32 +
.../org/rstudio/core/client/widget/SmallButton.css | 29 +
.../rstudio/core/client/widget/SmallButton.java | 121 +
.../rstudio/core/client/widget/SmallButton.ui.xml | 14 +
.../org/rstudio/core/client/widget/SpanLabel.java | 33 +
.../core/client/widget/TextBoxWithButton.java | 174 +
.../rstudio/core/client/widget/TextBoxWithCue.java | 133 +
.../core/client/widget/TextEntryModalDialog.java | 148 +
.../rstudio/core/client/widget/ThemedButton.css | 136 +
.../rstudio/core/client/widget/ThemedButton.java | 192 +
.../rstudio/core/client/widget/ThemedButton.ui.xml | 14 +
.../core/client/widget/ThemedPopupPanel.css | 63 +
.../core/client/widget/ThemedPopupPanel.java | 85 +
.../org/rstudio/core/client/widget/Toolbar.java | 314 +
.../rstudio/core/client/widget/ToolbarButton.java | 417 +
.../core/client/widget/ToolbarButton.ui.xml | 14 +
.../core/client/widget/ToolbarFileLabel.java | 69 +
.../rstudio/core/client/widget/ToolbarLabel.java | 38 +
.../core/client/widget/ToolbarPopupMenu.java | 210 +
.../core/client/widget/ToolbarPopupMenuButton.java | 69 +
.../rstudio/core/client/widget/WindowLauncher.java | 24 +
.../src/org/rstudio/core/client/widget/Wizard.css | 64 +
.../src/org/rstudio/core/client/widget/Wizard.java | 412 +
.../core/client/widget/WizardNavigationPage.java | 87 +
.../org/rstudio/core/client/widget/WizardPage.java | 129 +
.../rstudio/core/client/widget/WizardPageInfo.java | 26 +
.../core/client/widget/WizardPageSelector.java | 141 +
.../core/client/widget/WizardResources.java | 63 +
.../core/client/widget/ZeroHeightPanel.java | 53 +
.../core/client/widget/ZeroHeightPanel.ui.xml | 23 +
.../core/client/widget/buttonLeftDisabled.png | Bin 0 -> 437 bytes
.../core/client/widget/buttonLeftEnabled.png | Bin 0 -> 451 bytes
.../core/client/widget/buttonLeftFocusEnabled.png | Bin 0 -> 531 bytes
.../core/client/widget/buttonLeftFocusPressed.png | Bin 0 -> 561 bytes
.../core/client/widget/buttonLeftFocusSelected.png | Bin 0 -> 538 bytes
.../core/client/widget/buttonLeftPressed.png | Bin 0 -> 513 bytes
.../core/client/widget/buttonLeftSelected.png | Bin 0 -> 496 bytes
.../core/client/widget/buttonRightDisabled.png | Bin 0 -> 480 bytes
.../core/client/widget/buttonRightEnabled.png | Bin 0 -> 479 bytes
.../core/client/widget/buttonRightFocusEnabled.png | Bin 0 -> 573 bytes
.../core/client/widget/buttonRightFocusPressed.png | Bin 0 -> 613 bytes
.../client/widget/buttonRightFocusSelected.png | Bin 0 -> 593 bytes
.../core/client/widget/buttonRightPressed.png | Bin 0 -> 581 bytes
.../core/client/widget/buttonRightSelected.png | Bin 0 -> 534 bytes
.../core/client/widget/buttonTileDisabled.png | Bin 0 -> 292 bytes
.../core/client/widget/buttonTileEnabled.png | Bin 0 -> 294 bytes
.../core/client/widget/buttonTileFocusEnabled.png | Bin 0 -> 330 bytes
.../core/client/widget/buttonTileFocusPressed.png | Bin 0 -> 345 bytes
.../core/client/widget/buttonTileFocusSelected.png | Bin 0 -> 352 bytes
.../core/client/widget/buttonTilePressed.png | Bin 0 -> 312 bytes
.../core/client/widget/buttonTileSelected.png | Bin 0 -> 369 bytes
.../rstudio/core/client/widget/dynamicFrame.html | 12 +
.../client/widget/events/GlassVisibilityEvent.java | 47 +
.../widget/events/GlassVisibilityHandler.java | 22 +
.../widget/events/SelectionChangedEvent.java | 35 +
.../widget/events/SelectionChangedHandler.java | 22 +
.../core/client/widget/fullscreenPopupBottom.png | Bin 0 -> 187 bytes
.../client/widget/fullscreenPopupBottomLeft.png | Bin 0 -> 485 bytes
.../client/widget/fullscreenPopupBottomRight.png | Bin 0 -> 464 bytes
.../core/client/widget/fullscreenPopupClose.png | Bin 0 -> 200 bytes
.../core/client/widget/fullscreenPopupLeft.png | Bin 0 -> 183 bytes
.../core/client/widget/fullscreenPopupRight.png | Bin 0 -> 187 bytes
.../core/client/widget/fullscreenPopupTop.png | Bin 0 -> 824 bytes
.../core/client/widget/fullscreenPopupTopLeft.png | Bin 0 -> 768 bytes
.../core/client/widget/fullscreenPopupTopRight.png | Bin 0 -> 829 bytes
.../client/widget/images/LeftToggleLeftOff.png | Bin 0 -> 344 bytes
.../core/client/widget/images/LeftToggleLeftOn.png | Bin 0 -> 386 bytes
.../client/widget/images/LeftToggleRightOff.png | Bin 0 -> 180 bytes
.../client/widget/images/LeftToggleRightOn.png | Bin 0 -> 220 bytes
.../client/widget/images/MessageDialogImages.java | 30 +
.../core/client/widget/images/ProgressImages.java | 41 +
.../client/widget/images/RightToggleLeftOff.png | Bin 0 -> 160 bytes
.../client/widget/images/RightToggleLeftOn.png | Bin 0 -> 193 bytes
.../client/widget/images/RightToggleRightOff.png | Bin 0 -> 375 bytes
.../client/widget/images/RightToggleRightOn.png | Bin 0 -> 410 bytes
.../core/client/widget/images/dialog_error.png | Bin 0 -> 3001 bytes
.../core/client/widget/images/dialog_info.png | Bin 0 -> 2720 bytes
.../client/widget/images/dialog_popup_blocked.png | Bin 0 -> 1993 bytes
.../core/client/widget/images/dialog_question.png | Bin 0 -> 2833 bytes
.../core/client/widget/images/dialog_warning.png | Bin 0 -> 2342 bytes
.../org/rstudio/core/client/widget/images/test.htm | 3 +
.../core/client/widget/popupBottomCenter.png | Bin 0 -> 157 bytes
.../rstudio/core/client/widget/popupBottomLeft.png | Bin 0 -> 2872 bytes
.../core/client/widget/popupBottomRight.png | Bin 0 -> 236 bytes
.../rstudio/core/client/widget/popupMiddleLeft.png | Bin 0 -> 2809 bytes
.../core/client/widget/popupMiddleRight.png | Bin 0 -> 135 bytes
.../rstudio/core/client/widget/popupTopCenter.png | Bin 0 -> 2819 bytes
.../rstudio/core/client/widget/popupTopLeft.png | Bin 0 -> 2844 bytes
.../rstudio/core/client/widget/popupTopRight.png | Bin 0 -> 2885 bytes
.../org/rstudio/core/client/widget/progress.gif | Bin 0 -> 40531 bytes
.../rstudio/core/client/widget/resizeGripper.png | Bin 0 -> 364 bytes
.../core/client/widget/slideLabelBottom.png | Bin 0 -> 162 bytes
.../core/client/widget/slideLabelBottomLeft.png | Bin 0 -> 423 bytes
.../core/client/widget/slideLabelBottomRight.png | Bin 0 -> 430 bytes
.../rstudio/core/client/widget/slideLabelFill.png | Bin 0 -> 124 bytes
.../rstudio/core/client/widget/slideLabelLeft.png | Bin 0 -> 175 bytes
.../rstudio/core/client/widget/slideLabelRight.png | Bin 0 -> 165 bytes
.../rstudio/core/client/widget/smallButtonLeft.png | Bin 0 -> 322 bytes
.../core/client/widget/smallButtonRight.png | Bin 0 -> 347 bytes
.../rstudio/core/client/widget/smallButtonTile.png | Bin 0 -> 183 bytes
.../core/client/widget/wizardBackButton.png | Bin 0 -> 1187 bytes
.../core/client/widget/wizardDisclosureArrow.png | Bin 0 -> 267 bytes
.../core/client/widget/wizardListInnerShadow.png | Bin 0 -> 136 bytes
.../core/client/widget/wizardPageBackground.png | Bin 0 -> 1391 bytes
.../client/widget/wizardPageSelectorBackground.png | Bin 0 -> 132 bytes
.../widget/wizardPageSelectorBackgroundFirst.png | Bin 0 -> 152 bytes
.../widget/wizardPageSelectorBackgroundLast.png | Bin 0 -> 149 bytes
.../rstudio/core/rebind/AsyncShimGenerator.java | 262 +
.../rebind/JavaScriptPassthroughGenerator.java | 155 +
.../core/rebind/StaticDataResourceGenerator.java | 83 +
.../rebind/command/CommandBinderGenerator.java | 187 +
.../rebind/command/CommandBundleGenerator.java | 515 +
.../rebind/command/JsObjectInjectorGenerator.java | 164 +
.../rstudio/core/rebind/command/MenuEmitter.java | 153 +
.../core/rebind/command/ShortcutsEmitter.java | 236 +
src/gwt/src/org/rstudio/studio/.gitignore | 2 +
src/gwt/src/org/rstudio/studio/RStudio.gwt.xml | 129 +
.../src/org/rstudio/studio/RStudioDraft.gwt.xml | 4 +
.../org/rstudio/studio/RStudioSuperDevMode.gwt.xml | 7 +
src/gwt/src/org/rstudio/studio/client/RStudio.java | 249 +
.../rstudio/studio/client/RStudioGinModule.java | 338 +
.../studio/client/RStudioGinModuleOverlay.java | 26 +
.../rstudio/studio/client/RStudioGinjector.java | 112 +
.../studio/client/application/Application.java | 723 +
.../client/application/ApplicationClientInit.java | 169 +
.../client/application/ApplicationInterrupt.java | 209 +
.../studio/client/application/ApplicationQuit.java | 659 +
.../ApplicationUncaughtExceptionHandler.java | 83 +
.../studio/client/application/ApplicationView.java | 60 +
.../rstudio/studio/client/application/Desktop.java | 31 +
.../studio/client/application/DesktopFrame.java | 133 +
.../studio/client/application/DesktopHooks.java | 188 +
.../studio/client/application/IgnoredUpdates.java | 76 +
.../events/ApplicationEventHandlers.java | 29 +
.../application/events/ChangeFontSizeEvent.java | 46 +
.../application/events/ChangeFontSizeHandler.java | 22 +
.../events/ClientDisconnectedEvent.java | 35 +
.../events/ClientDisconnectedHandler.java | 22 +
.../events/DeferredInitCompletedEvent.java | 44 +
.../studio/client/application/events/EventBus.java | 55 +
.../events/HandleUnsavedChangesEvent.java | 41 +
.../events/HandleUnsavedChangesHandler.java | 22 +
.../events/InvalidClientVersionEvent.java | 35 +
.../events/InvalidClientVersionHandler.java | 22 +
.../application/events/LogoutRequestedEvent.java | 35 +
.../application/events/LogoutRequestedHandler.java | 23 +
.../client/application/events/QuitEvent.java | 47 +
.../client/application/events/QuitHandler.java | 22 +
.../client/application/events/ReloadEvent.java | 44 +
.../application/events/RestartStatusEvent.java | 55 +
.../application/events/SaveActionChangedEvent.java | 50 +
.../events/SaveActionChangedHandler.java | 22 +
.../application/events/ServerOfflineEvent.java | 41 +
.../application/events/ServerOfflineHandler.java | 22 +
.../application/events/ServerUnavailableEvent.java | 41 +
.../events/ServerUnavailableHandler.java | 22 +
.../events/SessionAbendWarningEvent.java | 36 +
.../events/SessionAbendWarningHandler.java | 22 +
.../events/SessionSerializationEvent.java | 49 +
.../events/SessionSerializationHandler.java | 22 +
.../client/application/events/SuicideEvent.java | 48 +
.../client/application/events/SuicideHandler.java | 22 +
.../application/events/SuspendAndRestartEvent.java | 62 +
.../events/SuspendAndRestartHandler.java | 22 +
.../application/events/UnauthorizedEvent.java | 35 +
.../application/events/UnauthorizedHandler.java | 22 +
.../model/ApplicationServerOperations.java | 72 +
.../client/application/model/HttpLogEntry.java | 72 +
.../client/application/model/ProductInfo.java | 30 +
.../client/application/model/SaveAction.java | 38 +
.../model/SessionSerializationAction.java | 39 +
.../client/application/model/SuspendOptions.java | 73 +
.../application/model/UpdateCheckResult.java | 38 +
.../studio/client/application/ui/AboutDialog.java | 50 +
.../client/application/ui/AboutDialogContents.java | 59 +
.../application/ui/AboutDialogContents.ui.xml | 76 +
.../application/ui/ApplicationAgreementDialog.java | 67 +
.../client/application/ui/ApplicationHeader.java | 27 +
.../client/application/ui/ApplicationWindow.java | 290 +
.../client/application/ui/GlobalToolbar.java | 200 +
.../client/application/ui/ProjectPopupMenu.java | 105 +
.../client/application/ui/RequestLogDetail.java | 47 +
.../application/ui/RequestLogVisualization.java | 352 +
.../studio/client/application/ui/WarningBar.css | 39 +
.../studio/client/application/ui/WarningBar.java | 120 +
.../studio/client/application/ui/WarningBar.ui.xml | 28 +
.../ui/appended/ApplicationEndedPopupPanel.css | 71 +
.../ui/appended/ApplicationEndedPopupPanel.java | 271 +
.../ui/appended/ApplicationEndedPopupPanel.ui.xml | 28 +
.../client/application/ui/appended/FancyButton.css | 31 +
.../application/ui/appended/FancyButton.java | 83 +
.../application/ui/appended/FancyButton.ui.xml | 14 +
.../ui/appended/applicationDisconnected.png | Bin 0 -> 2437 bytes
.../application/ui/appended/applicationOffline.png | Bin 0 -> 4870 bytes
.../application/ui/appended/applicationQuit.png | Bin 0 -> 4039 bytes
.../application/ui/appended/applicationSuicide.png | Bin 0 -> 2685 bytes
.../client/application/ui/appended/buttonLeft.png | Bin 0 -> 639 bytes
.../client/application/ui/appended/buttonRight.png | Bin 0 -> 3341 bytes
.../client/application/ui/appended/buttonTile.png | Bin 0 -> 189 bytes
.../client/application/ui/appended/panelBottom.png | Bin 0 -> 165 bytes
.../application/ui/appended/panelBottomLeft.png | Bin 0 -> 393 bytes
.../application/ui/appended/panelBottomRight.png | Bin 0 -> 393 bytes
.../client/application/ui/appended/panelLeft.png | Bin 0 -> 161 bytes
.../client/application/ui/appended/panelRight.png | Bin 0 -> 158 bytes
.../client/application/ui/appended/panelTop.png | Bin 0 -> 182 bytes
.../application/ui/appended/panelTopLeft.png | Bin 0 -> 359 bytes
.../application/ui/appended/panelTopRight.png | Bin 0 -> 404 bytes
.../ui/impl/DesktopApplicationHeader.java | 367 +
.../application/ui/impl/WebApplicationHeader.java | 381 +
.../application/ui/impl/header/HeaderPanel.css | 18 +
.../application/ui/impl/header/HeaderPanel.java | 68 +
.../application/ui/impl/header/HeaderPanel.ui.xml | 19 +
.../application/ui/impl/header/MenubarPanel.css | 18 +
.../application/ui/impl/header/MenubarPanel.java | 64 +
.../application/ui/impl/header/MenubarPanel.ui.xml | 19 +
.../application/ui/impl/header/headerPanelLeft.png | Bin 0 -> 420 bytes
.../ui/impl/header/headerPanelRight.png | Bin 0 -> 416 bytes
.../application/ui/impl/header/headerPanelTile.png | Bin 0 -> 203 bytes
.../application/ui/impl/header/menubarLeft.png | Bin 0 -> 348 bytes
.../application/ui/impl/header/menubarRight.png | Bin 0 -> 365 bytes
.../application/ui/impl/header/menubarTile.png | Bin 0 -> 170 bytes
.../studio/client/application/ui/projectMenu.png | Bin 0 -> 914 bytes
.../studio/client/application/ui/rstudio_sm.png | Bin 0 -> 830 bytes
.../ApplicationSerializationProgress.css | 37 +
.../ApplicationSerializationProgress.java | 120 +
.../ApplicationSerializationProgressLabel.java | 43 +
.../ApplicationSerializationProgressLabel.ui.xml | 21 +
.../ui/serializationprogress/spinnerManilla.gif | Bin 0 -> 3937 bytes
.../ui/serializationprogress/statusPopupLeft.png | Bin 0 -> 815 bytes
.../ui/serializationprogress/statusPopupRight.png | Bin 0 -> 809 bytes
.../ui/serializationprogress/statusPopupTile.png | Bin 0 -> 247 bytes
.../application/ui/support/SupportPopupMenu.css | 9 +
.../application/ui/support/SupportPopupMenu.java | 103 +
.../client/application/ui/warningBarLeft.png | Bin 0 -> 2866 bytes
.../client/application/ui/warningBarRight.png | Bin 0 -> 2909 bytes
.../client/application/ui/warningBarTile.png | Bin 0 -> 2826 bytes
.../client/application/ui/warningIconSmall.png | Bin 0 -> 971 bytes
.../studio/client/common/AutoGlassAttacher.java | 40 +
.../studio/client/common/AutoGlassPanel.java | 45 +
.../studio/client/common/CommandLineHistory.java | 97 +
.../studio/client/common/ConsoleDispatcher.java | 190 +
.../studio/client/common/DefaultGlobalDisplay.java | 354 +
.../common/DelayedProgressRequestCallback.java | 50 +
.../rstudio/studio/client/common/FileDialogs.java | 45 +
.../studio/client/common/FilePathUtils.java | 64 +
.../studio/client/common/GlobalDisplay.java | 109 +
.../client/common/GlobalProgressDelayer.java | 84 +
.../org/rstudio/studio/client/common/HelpLink.java | 57 +
.../studio/client/common/ImageMenuItem.java | 23 +
.../rstudio/studio/client/common/JSONUtils.java | 46 +
.../client/common/NotifyingSplitLayoutPanel.java | 49 +
.../studio/client/common/PackagesHelpLink.java | 26 +
.../studio/client/common/ReadOnlyValue.java | 22 +
.../client/common/SimpleRequestCallback.java | 44 +
.../studio/client/common/StudioResources.java | 26 +
.../rstudio/studio/client/common/StudioStyles.java | 21 +
.../rstudio/studio/client/common/StyleUtils.java | 36 +
.../rstudio/studio/client/common/SuperDevMode.java | 40 +
.../rstudio/studio/client/common/TextInput.java | 47 +
.../org/rstudio/studio/client/common/Value.java | 78 +
.../rstudio/studio/client/common/WindowOpener.java | 36 +
.../codetools/CodeToolsServerOperations.java | 30 +
.../client/common/codetools/Completions.java | 79 +
.../studio/client/common/compile/CompileError.java | 107 +
.../client/common/compile/CompileOutput.java | 37 +
.../client/common/compile/CompileOutputBuffer.java | 88 +
.../compile/CompileOutputBufferWithHighlight.java | 102 +
.../common/compile/CompileOutputDisplay.java | 32 +
.../studio/client/common/compile/CompilePanel.java | 202 +
.../compile/errorlist/CompileErrorItemCodec.java | 199 +
.../common/compile/errorlist/CompileErrorList.css | 67 +
.../common/compile/errorlist/CompileErrorList.java | 206 +
.../errorlist/CompileErrorListResources.java | 57 +
.../common/compile/errorlist/images/badbox.png | Bin 0 -> 3121 bytes
.../common/compile/errorlist/images/error.png | Bin 0 -> 653 bytes
.../compile/errorlist/images/logContextButton.png | Bin 0 -> 728 bytes
.../dialog/CompilePdfProgressDialog.java | 153 +
.../events/CompilePdfCompletedEvent.java | 54 +
.../compilepdf/events/CompilePdfErrorsEvent.java | 55 +
.../compilepdf/events/CompilePdfOutputEvent.java | 52 +
.../compilepdf/events/CompilePdfStartedEvent.java | 73 +
.../common/compilepdf/model/CompilePdfResult.java | 51 +
.../model/CompilePdfServerOperations.java | 46 +
.../common/compilepdf/model/CompilePdfState.java | 48 +
.../client/common/console/ConsoleOutputEvent.java | 66 +
.../client/common/console/ConsoleProcess.java | 346 +
.../common/console/ConsoleProcessCreatedEvent.java | 75 +
.../client/common/console/ConsoleProcessInfo.java | 61 +
.../client/common/console/ConsolePromptEvent.java | 59 +
.../client/common/console/ProcessExitEvent.java | 59 +
.../common/console/ServerConsoleOutputEvent.java | 81 +
.../common/console/ServerConsolePromptEvent.java | 71 +
.../common/console/ServerProcessExitEvent.java | 69 +
.../common/crypto/CryptoServerOperations.java | 22 +
.../studio/client/common/crypto/PublicKeyInfo.java | 30 +
.../studio/client/common/crypto/RSAEncrypt.java | 104 +
.../org/rstudio/studio/client/common/cstyles.css | 1 +
.../client/common/debugging/BreakpointManager.java | 898 +
.../client/common/debugging/DebugCommander.java | 256 +
.../debugging/DebuggingServerOperations.java | 62 +
.../client/common/debugging/ErrorManager.java | 216 +
.../debugging/events/BreakpointsSavedEvent.java | 65 +
.../debugging/events/ErrorHandlerChangedEvent.java | 55 +
.../debugging/events/PackageLoadedEvent.java | 53 +
.../debugging/events/PackageUnloadedEvent.java | 53 +
.../debugging/events/UnhandledErrorEvent.java | 55 +
.../client/common/debugging/model/Breakpoint.java | 188 +
.../common/debugging/model/BreakpointState.java | 36 +
.../client/common/debugging/model/ErrorFrame.java | 31 +
.../common/debugging/model/ErrorHandlerType.java | 34 +
.../common/debugging/model/ErrorManagerState.java | 31 +
.../common/debugging/model/FunctionState.java | 35 +
.../common/debugging/model/FunctionSteps.java | 35 +
.../common/debugging/model/TopLevelLineData.java | 28 +
.../common/debugging/model/UnhandledError.java | 32 +
.../client/common/debugging/ui/ConsoleError.java | 112 +
.../client/common/debugging/ui/ConsoleError.ui.xml | 71 +
.../common/debugging/ui/ConsoleErrorFrame.java | 81 +
.../common/debugging/ui/ConsoleErrorFrame.ui.xml | 40 +
.../studio/client/common/debugging/ui/rerun.png | Bin 0 -> 262 bytes
.../client/common/debugging/ui/traceback.png | Bin 0 -> 189 bytes
.../common/dialog/DesktopDialogBuilderFactory.java | 127 +
.../client/common/dialog/DialogBuilderBase.java | 75 +
.../client/common/dialog/DialogBuilderFactory.java | 22 +
.../common/dialog/WebDialogBuilderFactory.java | 77 +
.../client/common/fileexport/FileExport.java | 142 +
.../client/common/filetypes/BrowserType.java | 33 +
.../client/common/filetypes/CodeBrowserType.java | 33 +
.../client/common/filetypes/CppFileType.java | 77 +
.../client/common/filetypes/DataFrameType.java | 33 +
.../client/common/filetypes/EditableFileType.java | 40 +
.../client/common/filetypes/FileIconResources.java | 54 +
.../studio/client/common/filetypes/FileType.java | 50 +
.../client/common/filetypes/FileTypeCommands.java | 116 +
.../client/common/filetypes/FileTypeRegistry.java | 468 +
.../client/common/filetypes/NewFileMenu.java | 48 +
.../client/common/filetypes/PlainTextFileType.java | 47 +
.../client/common/filetypes/ProfilerType.java | 33 +
.../studio/client/common/filetypes/RDataType.java | 33 +
.../studio/client/common/filetypes/RFileType.java | 67 +
.../common/filetypes/RPresentationFileType.java | 44 +
.../client/common/filetypes/RProjectType.java | 33 +
.../common/filetypes/RWebContentFileType.java | 101 +
.../client/common/filetypes/SweaveFileType.java | 82 +
.../client/common/filetypes/TexFileType.java | 59 +
.../client/common/filetypes/TextFileType.java | 373 +
.../client/common/filetypes/UrlContentType.java | 33 +
.../common/filetypes/WebContentFileType.java | 65 +
.../common/filetypes/events/OpenDataFileEvent.java | 48 +
.../filetypes/events/OpenDataFileHandler.java | 22 +
.../filetypes/events/OpenFileInBrowserEvent.java | 47 +
.../filetypes/events/OpenFileInBrowserHandler.java | 22 +
.../events/OpenPresentationSourceFileEvent.java | 91 +
.../events/OpenPresentationSourceFileHandler.java | 22 +
.../filetypes/events/OpenSourceFileEvent.java | 96 +
.../filetypes/events/OpenSourceFileHandler.java | 22 +
.../studio/client/common/filetypes/iconC.png | Bin 0 -> 633 bytes
.../studio/client/common/filetypes/iconCpp.png | Bin 0 -> 634 bytes
.../studio/client/common/filetypes/iconCss.png | Bin 0 -> 821 bytes
.../studio/client/common/filetypes/iconCsv.png | Bin 0 -> 445 bytes
.../studio/client/common/filetypes/iconFolder.png | Bin 0 -> 517 bytes
.../studio/client/common/filetypes/iconH.png | Bin 0 -> 602 bytes
.../studio/client/common/filetypes/iconHTML.png | Bin 0 -> 835 bytes
.../studio/client/common/filetypes/iconHpp.png | Bin 0 -> 566 bytes
.../client/common/filetypes/iconJavascript.png | Bin 0 -> 816 bytes
.../client/common/filetypes/iconMarkdown.png | Bin 0 -> 649 bytes
.../studio/client/common/filetypes/iconPdf.png | Bin 0 -> 690 bytes
.../studio/client/common/filetypes/iconPng.png | Bin 0 -> 688 bytes
.../client/common/filetypes/iconProfiler.png | Bin 0 -> 780 bytes
.../client/common/filetypes/iconPublicFolder.png | Bin 0 -> 601 bytes
.../studio/client/common/filetypes/iconRd.png | Bin 0 -> 789 bytes
.../studio/client/common/filetypes/iconRdata.png | Bin 0 -> 737 bytes
.../studio/client/common/filetypes/iconRdoc.png | Bin 0 -> 686 bytes
.../client/common/filetypes/iconRhistory.png | Bin 0 -> 785 bytes
.../studio/client/common/filetypes/iconRhtml.png | Bin 0 -> 844 bytes
.../client/common/filetypes/iconRmarkdown.png | Bin 0 -> 799 bytes
.../client/common/filetypes/iconRpresentation.png | Bin 0 -> 574 bytes
.../client/common/filetypes/iconRprofile.png | Bin 0 -> 758 bytes
.../client/common/filetypes/iconRproject.png | Bin 0 -> 905 bytes
.../studio/client/common/filetypes/iconRsweave.png | Bin 0 -> 808 bytes
.../client/common/filetypes/iconSourceViewer.png | Bin 0 -> 863 bytes
.../studio/client/common/filetypes/iconTex.png | Bin 0 -> 608 bytes
.../studio/client/common/filetypes/iconText.png | Bin 0 -> 508 bytes
.../client/common/filetypes/iconUpFolder.png | Bin 0 -> 397 bytes
.../studio/client/common/icons/StandardIcons.java | 38 +
.../studio/client/common/icons/chunk_menu.png | Bin 0 -> 465 bytes
.../studio/client/common/icons/click_feedback.png | Bin 0 -> 1162 bytes
.../studio/client/common/icons/empty_command.png | Bin 0 -> 115 bytes
.../studio/client/common/icons/function.png | Bin 0 -> 304 bytes
.../org/rstudio/studio/client/common/icons/git.png | Bin 0 -> 390 bytes
.../rstudio/studio/client/common/icons/go_up.png | Bin 0 -> 375 bytes
.../rstudio/studio/client/common/icons/help.png | Bin 0 -> 273 bytes
.../studio/client/common/icons/import_dataset.png | Bin 0 -> 642 bytes
.../studio/client/common/icons/more_actions.png | Bin 0 -> 1122 bytes
.../studio/client/common/icons/right_arrow.png | Bin 0 -> 147 bytes
.../studio/client/common/icons/show_log.png | Bin 0 -> 639 bytes
.../studio/client/common/icons/stock_new.png | Bin 0 -> 3403 bytes
.../org/rstudio/studio/client/common/icons/svn.png | Bin 0 -> 848 bytes
.../studio/client/common/icons/viewer_window.png | Bin 0 -> 442 bytes
.../client/common/impl/DesktopFileDialogs.java | 201 +
.../client/common/impl/DesktopTextInput.java | 103 +
.../client/common/impl/DesktopWindowOpener.java | 86 +
.../studio/client/common/impl/WebFileDialogs.java | 108 +
.../studio/client/common/impl/WebTextInput.java | 128 +
.../studio/client/common/impl/WebWindowOpener.java | 192 +
.../client/common/latex/LatexProgramRegistry.java | 98 +
.../common/latex/LatexProgramSelectWidget.java | 33 +
.../client/common/mirrors/ChooseMirrorDialog.css | 6 +
.../client/common/mirrors/ChooseMirrorDialog.java | 182 +
.../client/common/mirrors/DefaultCRANMirror.java | 105 +
.../common/mirrors/model/BioconductorMirror.java | 40 +
.../client/common/mirrors/model/CRANMirror.java | 60 +
.../mirrors/model/MirrorsServerOperations.java | 29 +
.../studio/client/common/r/RStringToken.java | 36 +
.../org/rstudio/studio/client/common/r/RToken.java | 105 +
.../rstudio/studio/client/common/r/RTokenizer.java | 305 +
.../client/common/reditor/EditorLanguage.java | 88 +
.../rstudio/studio/client/common/rnw/RnwWeave.java | 44 +
.../client/common/rnw/RnwWeaveDirective.java | 54 +
.../studio/client/common/rnw/RnwWeaveRegistry.java | 93 +
.../client/common/rnw/RnwWeaveSelectWidget.java | 125 +
.../studio/client/common/rpubs/RPubsPresenter.java | 66 +
.../rpubs/events/RPubsUploadStatusEvent.java | 77 +
.../common/rpubs/model/RPubsServerOperations.java | 33 +
.../client/common/rpubs/ui/RPubsUploadDialog.css | 25 +
.../client/common/rpubs/ui/RPubsUploadDialog.java | 362 +
.../studio/client/common/rpubs/ui/publishLarge.png | Bin 0 -> 2140 bytes
.../studio/client/common/satellite/Satellite.java | 229 +
.../common/satellite/SatelliteApplication.java | 103 +
.../common/satellite/SatelliteApplicationView.java | 26 +
.../client/common/satellite/SatelliteManager.java | 446 +
.../client/common/satellite/SatelliteUtils.java | 23 +
.../client/common/satellite/SatelliteWindow.java | 103 +
.../studio/client/common/shell/ShellDisplay.java | 52 +
.../studio/client/common/shell/ShellInput.java | 55 +
.../common/shell/ShellInteractionManager.java | 336 +
.../client/common/shell/ShellOutputWriter.java | 27 +
.../studio/client/common/shell/ShellWidget.java | 805 +
.../shiny/model/ShinyAppsServerOperations.java | 41 +
.../common/shiny/model/ShinyCapabilities.java | 39 +
.../common/shiny/model/ShinyServerOperations.java | 37 +
.../client/common/spelling/SpellChecker.java | 237 +
.../client/common/spelling/SpellingService.java | 233 +
.../common/spelling/model/SpellCheckerResult.java | 41 +
.../common/spelling/model/SpellingLanguage.java | 32 +
.../spelling/model/SpellingServerOperations.java | 43 +
.../ui/SpellingCustomDictionariesWidget.css | 14 +
.../ui/SpellingCustomDictionariesWidget.java | 221 +
.../spelling/ui/SpellingLanguageSelectWidget.java | 114 +
.../org/rstudio/studio/client/common/styles.css | 88 +
.../studio/client/common/synctex/Synctex.java | 462 +
.../studio/client/common/synctex/SynctexUtils.java | 53 +
.../synctex/events/SynctexEditFileEvent.java | 55 +
.../synctex/events/SynctexStatusChangedEvent.java | 64 +
.../common/synctex/events/SynctexViewPdfEvent.java | 55 +
.../client/common/synctex/model/PdfLocation.java | 85 +
.../common/synctex/model/SourceLocation.java | 55 +
.../synctex/model/SynctexServerOperations.java | 37 +
.../studio/client/common/vcs/AllStatus.java | 35 +
.../studio/client/common/vcs/AskPassManager.java | 161 +
.../studio/client/common/vcs/BranchesInfo.java | 35 +
.../studio/client/common/vcs/CreateKeyDialog.css | 32 +
.../studio/client/common/vcs/CreateKeyDialog.java | 297 +
.../studio/client/common/vcs/CreateKeyOptions.java | 52 +
.../studio/client/common/vcs/CreateKeyResult.java | 38 +
.../studio/client/common/vcs/DiffResult.java | 39 +
.../client/common/vcs/GitServerOperations.java | 145 +
.../studio/client/common/vcs/ProcessResult.java | 30 +
.../studio/client/common/vcs/RemoteBranchInfo.java | 33 +
.../client/common/vcs/SVNServerOperations.java | 107 +
.../client/common/vcs/ShowPublicKeyDialog.css | 14 +
.../client/common/vcs/ShowPublicKeyDialog.java | 110 +
.../studio/client/common/vcs/SshKeyWidget.css | 20 +
.../studio/client/common/vcs/SshKeyWidget.java | 214 +
.../studio/client/common/vcs/StatusAndPath.java | 138 +
.../client/common/vcs/StatusAndPathInfo.java | 61 +
.../studio/client/common/vcs/VCSConstants.java | 23 +
.../client/common/vcs/VCSServerOperations.java | 33 +
.../studio/client/common/vcs/VcsCloneOptions.java | 60 +
.../studio/client/common/vcs/VcsHelpLink.java | 26 +
.../studio/client/common/vcs/ignore/Ignore.java | 321 +
.../client/common/vcs/ignore/IgnoreDialog.css | 14 +
.../client/common/vcs/ignore/IgnoreDialog.java | 199 +
.../client/common/vcs/ignore/IgnoreList.java | 32 +
.../studio/client/htmlpreview/HTMLPreview.java | 47 +
.../client/htmlpreview/HTMLPreviewApplication.java | 40 +
.../client/htmlpreview/HTMLPreviewPresenter.java | 437 +
.../events/HTMLPreviewCompletedEvent.java | 54 +
.../htmlpreview/events/HTMLPreviewOutputEvent.java | 52 +
.../events/HTMLPreviewStartedEvent.java | 64 +
.../htmlpreview/events/ShowHTMLPreviewEvent.java | 49 +
.../htmlpreview/events/ShowHTMLPreviewHandler.java | 22 +
.../htmlpreview/model/HTMLPreviewParams.java | 58 +
.../htmlpreview/model/HTMLPreviewResult.java | 53 +
.../model/HTMLPreviewServerOperations.java | 40 +
.../htmlpreview/ui/HTMLPreviewApplicationView.java | 22 +
.../ui/HTMLPreviewApplicationWindow.java | 83 +
.../client/htmlpreview/ui/HTMLPreviewPanel.java | 278 +
.../htmlpreview/ui/HTMLPreviewProgressDialog.java | 90 +
.../rstudio/studio/client/impl/BrowserFence.java | 22 +
.../studio/client/impl/BrowserFenceSupported.java | 25 +
.../client/impl/BrowserFenceUnsupported.java | 28 +
.../client/notebook/CompileNotebookOptions.java | 74 +
.../notebook/CompileNotebookOptionsDialog.java | 190 +
.../notebook/CompileNotebookOptionsDialog.ui.xml | 44 +
.../client/notebook/CompileNotebookPrefs.java | 58 +
.../client/notebook/CompileNotebookResult.java | 31 +
.../rstudio/studio/client/pdfviewer/PDFViewer.java | 50 +
.../client/pdfviewer/PDFViewerApplication.java | 55 +
.../client/pdfviewer/PDFViewerPresenter.java | 522 +
.../client/pdfviewer/events/InitCompleteEvent.java | 40 +
.../client/pdfviewer/events/PageClickEvent.java | 59 +
.../pdfviewer/events/ShowPDFViewerEvent.java | 39 +
.../pdfviewer/events/ShowPDFViewerHandler.java | 22 +
.../client/pdfviewer/model/PDFViewerParams.java | 29 +
.../client/pdfviewer/model/SyncTexCoordinates.java | 44 +
.../org/rstudio/studio/client/pdfviewer/pdf.min.js | 0
.../studio/client/pdfviewer/pdfjs/PDFView.java | 159 +
.../studio/client/pdfviewer/pdfjs/PdfJs.java | 84 +
.../client/pdfviewer/pdfjs/PdfJsResources.java | 39 +
.../studio/client/pdfviewer/pdfjs/compatibility.js | 254 +
.../client/pdfviewer/pdfjs/compatibility.min.js | 9 +
.../studio/client/pdfviewer/pdfjs/debugger.js | 475 +
.../studio/client/pdfviewer/pdfjs/debugger.min.js | 16 +
.../pdfviewer/pdfjs/events/PDFLoadEvent.java | 40 +
.../pdfviewer/pdfjs/events/PageChangeEvent.java | 44 +
.../pdfviewer/pdfjs/events/ScaleChangeEvent.java | 44 +
.../rstudio/studio/client/pdfviewer/pdfjs/pdf.js | 32598 +++++++++++++++++++
.../studio/client/pdfviewer/pdfjs/pdf.min.js | 1458 +
.../studio/client/pdfviewer/pdfjs/viewer.css | 486 +
.../studio/client/pdfviewer/pdfjs/viewer.js | 1616 +
.../studio/client/pdfviewer/pdfjs/viewer.min.js | 50 +
.../pdfviewer/ui/PDFViewerApplicationView.java | 24 +
.../pdfviewer/ui/PDFViewerApplicationWindow.java | 119 +
.../studio/client/pdfviewer/ui/PDFViewerPanel.java | 361 +
.../client/pdfviewer/ui/PDFViewerPanel.ui.xml | 175 +
.../client/pdfviewer/ui/PDFViewerToolbar.java | 184 +
.../client/pdfviewer/ui/PDFViewerToolbar.ui.xml | 164 +
.../pdfviewer/ui/PDFViewerToolbarDisplay.java | 35 +
.../studio/client/pdfviewer/ui/PDFWidget.java | 76 +
.../client/pdfviewer/ui/PageNumberListBox.java | 84 +
.../pdfviewer/ui/images/JumpToSourceIcon.png | Bin 0 -> 912 bytes
.../client/pdfviewer/ui/images/NextPageIcon.png | Bin 0 -> 533 bytes
.../pdfviewer/ui/images/OpenPdfExternalIcon.png | Bin 0 -> 494 bytes
.../pdfviewer/ui/images/PreviousPageIcon.png | Bin 0 -> 533 bytes
.../client/pdfviewer/ui/images/Resources.java | 60 +
.../client/pdfviewer/ui/images/SizeButton.png | Bin 0 -> 1109 bytes
.../pdfviewer/ui/images/SizeButtonPressed.png | Bin 0 -> 1524 bytes
.../client/pdfviewer/ui/images/StatusBarTile.png | Bin 0 -> 183 bytes
.../client/pdfviewer/ui/images/ThumbnailsIcon.png | Bin 0 -> 491 bytes
.../client/pdfviewer/ui/images/ZoomButtonLeft.png | Bin 0 -> 3848 bytes
.../pdfviewer/ui/images/ZoomButtonLeftPressed.png | Bin 0 -> 3952 bytes
.../client/pdfviewer/ui/images/ZoomButtonRight.png | Bin 0 -> 3819 bytes
.../pdfviewer/ui/images/ZoomButtonRightPressed.png | Bin 0 -> 3945 bytes
.../client/pdfviewer/ui/images/ZoomInIcon.png | Bin 0 -> 783 bytes
.../client/pdfviewer/ui/images/ZoomOutIcon.png | Bin 0 -> 779 bytes
.../studio/client/pdfviewer/ui/images/toolbar.html | 152 +
.../pdfviewer/ui/images/toolbarSeparator.png | Bin 0 -> 206 bytes
.../studio/client/projects/ProjectMRUList.java | 68 +
.../rstudio/studio/client/projects/Projects.java | 664 +
.../projects/events/OpenProjectErrorEvent.java | 54 +
.../projects/events/OpenProjectErrorHandler.java | 22 +
.../projects/events/OpenProjectFileEvent.java | 48 +
.../projects/events/OpenProjectFileHandler.java | 22 +
.../projects/events/SwitchToProjectEvent.java | 47 +
.../projects/events/SwitchToProjectHandler.java | 22 +
.../client/projects/model/NewPackageOptions.java | 42 +
.../client/projects/model/NewProjectContext.java | 28 +
.../client/projects/model/NewProjectInput.java | 40 +
.../client/projects/model/NewProjectResult.java | 84 +
.../client/projects/model/NewShinyAppOptions.java | 30 +
.../client/projects/model/OpenProjectError.java | 38 +
.../projects/model/ProjectsServerOperations.java | 39 +
.../model/RProjectAutoRoxygenizeOptions.java | 59 +
.../projects/model/RProjectBuildContext.java | 32 +
.../projects/model/RProjectBuildOptions.java | 46 +
.../client/projects/model/RProjectConfig.java | 285 +
.../client/projects/model/RProjectOptions.java | 63 +
.../client/projects/model/RProjectVcsContext.java | 41 +
.../client/projects/model/RProjectVcsOptions.java | 37 +
.../projects/ui/newproject/CodeFilesList.java | 142 +
.../ui/newproject/ExistingDirectoryPage.java | 91 +
.../client/projects/ui/newproject/GitPage.java | 66 +
.../ui/newproject/NewDirectoryNavigationPage.java | 53 +
.../projects/ui/newproject/NewDirectoryPage.java | 190 +
.../projects/ui/newproject/NewPackagePage.java | 103 +
.../ui/newproject/NewProjectResources.java | 64 +
.../projects/ui/newproject/NewProjectWizard.css | 55 +
.../projects/ui/newproject/NewProjectWizard.java | 85 +
.../ui/newproject/NewProjectWizardPage.java | 96 +
.../projects/ui/newproject/NewShinyAppPage.java | 58 +
.../client/projects/ui/newproject/SvnPage.java | 64 +
.../newproject/VersionControlNavigationPage.java | 53 +
.../projects/ui/newproject/VersionControlPage.java | 291 +
.../ui/newproject/existingDirectoryIcon.png | Bin 0 -> 7826 bytes
.../ui/newproject/existingDirectoryIconLarge.png | Bin 0 -> 17589 bytes
.../client/projects/ui/newproject/gitIcon.png | Bin 0 -> 2717 bytes
.../client/projects/ui/newproject/gitIconLarge.png | Bin 0 -> 4606 bytes
.../ui/newproject/newProjectDirectoryIcon.png | Bin 0 -> 5845 bytes
.../ui/newproject/newProjectDirectoryIconLarge.png | Bin 0 -> 12824 bytes
.../client/projects/ui/newproject/packageIcon.png | Bin 0 -> 3658 bytes
.../projects/ui/newproject/packageIconLarge.png | Bin 0 -> 5687 bytes
.../ui/newproject/projectFromRepositoryIcon.png | Bin 0 -> 5067 bytes
.../newproject/projectFromRepositoryIconLarge.png | Bin 0 -> 9900 bytes
.../client/projects/ui/newproject/shinyAppIcon.png | Bin 0 -> 6519 bytes
.../projects/ui/newproject/shinyAppIconLarge.png | Bin 0 -> 11084 bytes
.../client/projects/ui/newproject/svnIcon.png | Bin 0 -> 3151 bytes
.../client/projects/ui/newproject/svnIconLarge.png | Bin 0 -> 5278 bytes
.../ui/prefs/ProjectCompilePdfPreferencesPane.java | 182 +
.../ui/prefs/ProjectEditingPreferencesPane.java | 173 +
.../ui/prefs/ProjectGeneralPreferencesPane.java | 127 +
.../projects/ui/prefs/ProjectPreferencesDialog.css | 93 +
.../ui/prefs/ProjectPreferencesDialog.java | 131 +
.../prefs/ProjectPreferencesDialogResources.java | 50 +
.../projects/ui/prefs/ProjectPreferencesPane.java | 79 +
.../prefs/ProjectSourceControlPreferencesPane.java | 325 +
.../ui/prefs/buildtools/BuildToolsCustomPanel.java | 62 +
.../prefs/buildtools/BuildToolsMakefilePanel.java | 62 +
.../prefs/buildtools/BuildToolsPackagePanel.java | 213 +
.../ui/prefs/buildtools/BuildToolsPanel.java | 227 +
.../prefs/buildtools/BuildToolsRoxygenOptions.java | 84 +
.../buildtools/BuildToolsRoxygenOptionsDialog.java | 95 +
.../BuildToolsRoxygenOptionsDialog.ui.xml | 33 +
.../ProjectBuildToolsPreferencesPane.java | 184 +
.../studio/client/projects/ui/prefs/iconBuild.png | Bin 0 -> 2790 bytes
.../src/org/rstudio/studio/client/server/Bool.java | 29 +
.../studio/client/server/ClientException.java | 105 +
.../rstudio/studio/client/server/LogEntryType.java | 22 +
.../org/rstudio/studio/client/server/Server.java | 45 +
.../studio/client/server/ServerDataSource.java | 20 +
.../rstudio/studio/client/server/ServerError.java | 57 +
.../studio/client/server/ServerErrorCause.java | 39 +
.../client/server/ServerRequestCallback.java | 31 +
.../src/org/rstudio/studio/client/server/Void.java | 32 +
.../client/server/VoidServerRequestCallback.java | 61 +
.../client/server/remote/AsyncCompletion.java | 33 +
.../studio/client/server/remote/ClientEvent.java | 118 +
.../server/remote/ClientEventDispatcher.java | 555 +
.../client/server/remote/ClientEventHandler.java | 22 +
.../studio/client/server/remote/RemoteServer.java | 3486 ++
.../client/server/remote/RemoteServerAuth.java | 258 +
.../client/server/remote/RemoteServerError.java | 124 +
.../server/remote/RemoteServerEventListener.java | 508 +
.../studio/client/server/remote/RetryHandler.java | 28 +
.../studio/client/shiny/ShinyApplication.java | 260 +
.../client/shiny/ShinyApplicationPresenter.java | 157 +
.../client/shiny/ShinyApplicationSatellite.java | 40 +
.../org/rstudio/studio/client/shiny/ShinyApps.java | 295 +
.../shiny/events/ShinyApplicationStatusEvent.java | 55 +
.../client/shiny/events/ShinyAppsActionEvent.java | 63 +
.../events/ShinyAppsDeployInitiatedEvent.java | 62 +
.../client/shiny/model/ShinyApplicationParams.java | 58 +
.../shiny/model/ShinyAppsApplicationInfo.java | 44 +
.../shiny/model/ShinyAppsDeploymentRecord.java | 47 +
.../shiny/model/ShinyAppsDirectoryState.java | 40 +
.../studio/client/shiny/model/ShinyRunCmd.java | 26 +
.../studio/client/shiny/model/ShinyViewerType.java | 32 +
.../rstudio/studio/client/shiny/ui/DeployArrow.png | Bin 0 -> 1206 bytes
.../client/shiny/ui/ShinyApplicationPanel.java | 143 +
.../client/shiny/ui/ShinyApplicationView.java | 22 +
.../client/shiny/ui/ShinyApplicationWindow.java | 73 +
.../client/shiny/ui/ShinyAppsAccountManager.java | 78 +
.../client/shiny/ui/ShinyAppsAccountManager.ui.xml | 24 +
.../shiny/ui/ShinyAppsAccountManagerDialog.java | 183 +
.../client/shiny/ui/ShinyAppsConnectAccount.java | 79 +
.../client/shiny/ui/ShinyAppsConnectAccount.ui.xml | 44 +
.../shiny/ui/ShinyAppsConnectAccountDialog.java | 102 +
.../studio/client/shiny/ui/ShinyAppsDeploy.java | 223 +
.../studio/client/shiny/ui/ShinyAppsDeploy.ui.xml | 117 +
.../client/shiny/ui/ShinyAppsDeployDialog.java | 363 +
.../studio/client/shiny/ui/ShinyAppsDialog.java | 45 +
.../client/shiny/ui/ShinyViewerTypePopupMenu.java | 76 +
.../rstudio/studio/client/vcs/VCSApplication.java | 39 +
.../studio/client/vcs/VCSApplicationParams.java | 79 +
.../studio/client/vcs/VCSApplicationView.java | 21 +
.../studio/client/vcs/ui/VCSApplicationWindow.java | 120 +
.../client/workbench/ClientStateUpdater.java | 134 +
.../studio/client/workbench/FileMRUList.java | 75 +
.../rstudio/studio/client/workbench/MRUList.java | 161 +
.../rstudio/studio/client/workbench/Workbench.java | 406 +
.../studio/client/workbench/WorkbenchContext.java | 187 +
.../studio/client/workbench/WorkbenchList.java | 39 +
.../client/workbench/WorkbenchListManager.java | 181 +
.../studio/client/workbench/WorkbenchMainView.java | 22 +
.../studio/client/workbench/WorkbenchView.java | 27 +
.../client/workbench/codesearch/CodeSearch.java | 222 +
.../workbench/codesearch/CodeSearchOracle.java | 301 +
.../workbench/codesearch/CodeSearchSuggestion.java | 172 +
.../codesearch/model/CodeSearchResults.java | 40 +
.../model/CodeSearchServerOperations.java | 94 +
.../codesearch/model/FunctionDefinition.java | 57 +
.../workbench/codesearch/model/RFileItem.java | 36 +
.../workbench/codesearch/model/RS4MethodParam.java | 36 +
.../workbench/codesearch/model/RSourceItem.java | 65 +
.../model/SearchPathFunctionDefinition.java | 61 +
.../client/workbench/codesearch/ui/CodeSearch.css | 38 +
.../workbench/codesearch/ui/CodeSearchDialog.java | 117 +
.../codesearch/ui/CodeSearchResources.java | 46 +
.../workbench/codesearch/ui/CodeSearchWidget.java | 71 +
.../studio/client/workbench/codesearch/ui/cls.png | Bin 0 -> 259 bytes
.../workbench/codesearch/ui/gotoFunction.png | Bin 0 -> 252 bytes
.../client/workbench/codesearch/ui/method.png | Bin 0 -> 229 bytes
.../client/workbench/commands/Commands.cmd.xml | 1418 +
.../studio/client/workbench/commands/Commands.java | 357 +
.../studio/client/workbench/commands/buildAll.png | Bin 0 -> 724 bytes
.../workbench/commands/buildToolsProjectSetup.png | Bin 0 -> 755 bytes
.../client/workbench/commands/checkPackage.png | Bin 0 -> 496 bytes
.../client/workbench/commands/checkSpelling.png | Bin 0 -> 397 bytes
.../client/workbench/commands/clearHistory.png | Bin 0 -> 729 bytes
.../client/workbench/commands/clearPlots.png | Bin 0 -> 729 bytes
.../workbench/commands/clearPresentationCache.png | Bin 0 -> 729 bytes
.../client/workbench/commands/clearWorkspace.png | Bin 0 -> 729 bytes
.../client/workbench/commands/compileNotebook.png | Bin 0 -> 476 bytes
.../client/workbench/commands/compilePDF.png | Bin 0 -> 755 bytes
.../client/workbench/commands/debugBreakpoint.png | Bin 0 -> 240 bytes
.../workbench/commands/debugClearBreakpoints.png | Bin 0 -> 729 bytes
.../client/workbench/commands/debugContinue.png | Bin 0 -> 279 bytes
.../client/workbench/commands/debugFinish.png | Bin 0 -> 343 bytes
.../studio/client/workbench/commands/debugHelp.png | Bin 0 -> 273 bytes
.../studio/client/workbench/commands/debugStep.png | Bin 0 -> 305 bytes
.../client/workbench/commands/debugStepInto.png | Bin 0 -> 450 bytes
.../studio/client/workbench/commands/debugStop.png | Bin 0 -> 216 bytes
.../client/workbench/commands/deleteFiles.png | Bin 0 -> 806 bytes
.../client/workbench/commands/devtoolsLoadAll.png | Bin 0 -> 761 bytes
.../client/workbench/commands/editVariable.png | Bin 0 -> 556 bytes
.../client/workbench/commands/errorsBreak.png | Bin 0 -> 577 bytes
.../client/workbench/commands/errorsMessage.png | Bin 0 -> 319 bytes
.../client/workbench/commands/errorsTraceback.png | Bin 0 -> 264 bytes
.../client/workbench/commands/executeCode.png | Bin 0 -> 441 bytes
.../client/workbench/commands/executeLastCode.png | Bin 0 -> 560 bytes
.../studio/client/workbench/commands/find.png | Bin 0 -> 723 bytes
.../client/workbench/commands/findReplace.png | Bin 0 -> 723 bytes
.../client/workbench/commands/goToWorkingDir.png | Bin 0 -> 484 bytes
.../studio/client/workbench/commands/helpBack.png | Bin 0 -> 636 bytes
.../client/workbench/commands/helpForward.png | Bin 0 -> 613 bytes
.../studio/client/workbench/commands/helpHome.png | Bin 0 -> 523 bytes
.../client/workbench/commands/helpPopout.png | Bin 0 -> 494 bytes
.../workbench/commands/historyDismissContext.png | Bin 0 -> 575 bytes
.../workbench/commands/historyDismissResults.png | Bin 0 -> 575 bytes
.../workbench/commands/historyRemoveEntries.png | Bin 0 -> 806 bytes
.../workbench/commands/historySendToConsole.png | Bin 0 -> 557 bytes
.../workbench/commands/historySendToSource.png | Bin 0 -> 628 bytes
.../workbench/commands/historyShowContext.png | Bin 0 -> 347 bytes
.../client/workbench/commands/insertChunk.png | Bin 0 -> 696 bytes
.../client/workbench/commands/insertSection.png | Bin 0 -> 732 bytes
.../client/workbench/commands/installPackage.png | Bin 0 -> 675 bytes
.../client/workbench/commands/interruptR.png | Bin 0 -> 794 bytes
.../client/workbench/commands/knitDocument.png | Bin 0 -> 914 bytes
.../client/workbench/commands/loadHistory.png | Bin 0 -> 772 bytes
.../client/workbench/commands/loadWorkspace.png | Bin 0 -> 772 bytes
.../studio/client/workbench/commands/newCppDoc.png | Bin 0 -> 634 bytes
.../studio/client/workbench/commands/newFolder.png | Bin 0 -> 821 bytes
.../client/workbench/commands/newProject.png | Bin 0 -> 1079 bytes
.../workbench/commands/newRDocumentationDoc.png | Bin 0 -> 789 bytes
.../client/workbench/commands/newRHTMLDoc.png | Bin 0 -> 844 bytes
.../client/workbench/commands/newRMarkdownDoc.png | Bin 0 -> 799 bytes
.../workbench/commands/newRPresentationDoc.png | Bin 0 -> 574 bytes
.../client/workbench/commands/newSourceDoc.png | Bin 0 -> 682 bytes
.../client/workbench/commands/newSweaveDoc.png | Bin 0 -> 808 bytes
.../client/workbench/commands/newTextDoc.png | Bin 0 -> 508 bytes
.../studio/client/workbench/commands/nextPlot.png | Bin 0 -> 613 bytes
.../client/workbench/commands/openHtmlExternal.png | Bin 0 -> 494 bytes
.../client/workbench/commands/openProject.png | Bin 0 -> 732 bytes
.../client/workbench/commands/openSourceDoc.png | Bin 0 -> 772 bytes
.../studio/client/workbench/commands/popoutDoc.png | Bin 0 -> 494 bytes
.../client/workbench/commands/presentationEdit.png | Bin 0 -> 556 bytes
.../workbench/commands/presentationFullscreen.png | Bin 0 -> 848 bytes
.../client/workbench/commands/presentationHome.png | Bin 0 -> 523 bytes
.../client/workbench/commands/presentationNext.png | Bin 0 -> 613 bytes
.../client/workbench/commands/presentationPrev.png | Bin 0 -> 636 bytes
.../commands/presentationPublishToRpubs.png | Bin 0 -> 1051 bytes
.../commands/presentationSaveAsStandalone.png | Bin 0 -> 390 bytes
.../commands/presentationViewInBrowser.png | Bin 0 -> 494 bytes
.../client/workbench/commands/previewHTML.png | Bin 0 -> 992 bytes
.../client/workbench/commands/previousPlot.png | Bin 0 -> 636 bytes
.../studio/client/workbench/commands/printHelp.png | Bin 0 -> 701 bytes
.../client/workbench/commands/printHtmlPreview.png | Bin 0 -> 701 bytes
.../client/workbench/commands/printSourceDoc.png | Bin 0 -> 701 bytes
.../client/workbench/commands/publishHTML.png | Bin 0 -> 1051 bytes
.../client/workbench/commands/quitSession.png | Bin 0 -> 666 bytes
.../studio/client/workbench/commands/rcppHelp.png | Bin 0 -> 273 bytes
.../workbench/commands/refreshEnvironment.png | Bin 0 -> 840 bytes
.../client/workbench/commands/refreshFiles.png | Bin 0 -> 840 bytes
.../client/workbench/commands/refreshHelp.png | Bin 0 -> 840 bytes
.../workbench/commands/refreshHtmlPreview.png | Bin 0 -> 840 bytes
.../client/workbench/commands/refreshPackages.png | Bin 0 -> 840 bytes
.../client/workbench/commands/refreshPlot.png | Bin 0 -> 840 bytes
.../workbench/commands/refreshPresentation.png | Bin 0 -> 840 bytes
.../client/workbench/commands/refreshWorkspace.png | Bin 0 -> 840 bytes
.../client/workbench/commands/reloadShinyApp.png | Bin 0 -> 445 bytes
.../client/workbench/commands/removePlot.png | Bin 0 -> 806 bytes
.../client/workbench/commands/renameFile.png | Bin 0 -> 533 bytes
.../studio/client/workbench/commands/restartR.png | Bin 0 -> 3312 bytes
.../workbench/commands/saveAllSourceDocs.png | Bin 0 -> 519 bytes
.../client/workbench/commands/saveHistory.png | Bin 0 -> 390 bytes
.../workbench/commands/saveHtmlPreviewAs.png | Bin 0 -> 390 bytes
.../commands/saveHtmlPreviewAsLocalFile.png | Bin 0 -> 536 bytes
.../client/workbench/commands/savePlotAsImage.png | Bin 0 -> 705 bytes
.../client/workbench/commands/savePlotAsPdf.png | Bin 0 -> 755 bytes
.../client/workbench/commands/saveSourceDoc.png | Bin 0 -> 390 bytes
.../client/workbench/commands/saveWorkspace.png | Bin 0 -> 390 bytes
.../client/workbench/commands/searchHistory.png | Bin 0 -> 723 bytes
.../client/workbench/commands/shinyAppsDeploy.png | Bin 0 -> 479 bytes
.../workbench/commands/showHtmlPreviewLog.png | Bin 0 -> 639 bytes
.../client/workbench/commands/showPdfExternal.png | Bin 0 -> 494 bytes
.../workbench/commands/sourceActiveDocument.png | Bin 0 -> 430 bytes
.../workbench/commands/sourceNavigateBack.png | Bin 0 -> 441 bytes
.../workbench/commands/sourceNavigateForward.png | Bin 0 -> 419 bytes
.../client/workbench/commands/startProfiler.png | Bin 0 -> 279 bytes
.../studio/client/workbench/commands/stopBuild.png | Bin 0 -> 794 bytes
.../client/workbench/commands/stopProfiler.png | Bin 0 -> 216 bytes
.../client/workbench/commands/synctexSearch.png | Bin 0 -> 672 bytes
.../client/workbench/commands/tutorialFeedback.png | Bin 0 -> 862 bytes
.../client/workbench/commands/updatePackages.png | Bin 0 -> 727 bytes
.../client/workbench/commands/uploadFile.png | Bin 0 -> 713 bytes
.../client/workbench/commands/vcsAddFiles.png | Bin 0 -> 447 bytes
.../studio/client/workbench/commands/vcsCommit.png | Bin 0 -> 509 bytes
.../studio/client/workbench/commands/vcsDiff.png | Bin 0 -> 365 bytes
.../client/workbench/commands/vcsFileDiff.png | Bin 0 -> 365 bytes
.../studio/client/workbench/commands/vcsIgnore.png | Bin 0 -> 711 bytes
.../studio/client/workbench/commands/vcsPull.png | Bin 0 -> 536 bytes
.../studio/client/workbench/commands/vcsPush.png | Bin 0 -> 508 bytes
.../client/workbench/commands/vcsRefresh.png | Bin 0 -> 840 bytes
.../client/workbench/commands/vcsRemoveFiles.png | Bin 0 -> 468 bytes
.../client/workbench/commands/vcsResolve.png | Bin 0 -> 472 bytes
.../studio/client/workbench/commands/vcsRevert.png | Bin 0 -> 461 bytes
.../client/workbench/commands/vcsShowHistory.png | Bin 0 -> 692 bytes
.../client/workbench/commands/vcsUnstage.png | Bin 0 -> 613 bytes
.../client/workbench/commands/vcsViewOnGitHub.png | Bin 0 -> 414 bytes
.../commands/versionControlProjectSetup.png | Bin 0 -> 904 bytes
.../client/workbench/commands/viewerClear.png | Bin 0 -> 729 bytes
.../client/workbench/commands/viewerPopout.png | Bin 0 -> 494 bytes
.../client/workbench/commands/viewerRefresh.png | Bin 0 -> 840 bytes
.../client/workbench/commands/viewerStop.png | Bin 0 -> 794 bytes
.../studio/client/workbench/commands/zoomPlot.png | Bin 0 -> 895 bytes
.../client/workbench/events/ActivatePaneEvent.java | 49 +
.../workbench/events/ActivatePaneHandler.java | 22 +
.../client/workbench/events/BrowseUrlEvent.java | 48 +
.../client/workbench/events/BrowseUrlHandler.java | 22 +
.../studio/client/workbench/events/BusyEvent.java | 47 +
.../client/workbench/events/BusyHandler.java | 22 +
.../workbench/events/LastChanceSaveEvent.java | 64 +
.../workbench/events/LastChanceSaveHandler.java | 22 +
.../client/workbench/events/ListChangedEvent.java | 69 +
.../workbench/events/ListChangedHandler.java | 22 +
.../workbench/events/PushClientStateEvent.java | 39 +
.../workbench/events/PushClientStateHandler.java | 22 +
.../client/workbench/events/QuotaStatusEvent.java | 48 +
.../workbench/events/QuotaStatusHandler.java | 22 +
.../workbench/events/SaveClientStateEvent.java | 53 +
.../workbench/events/SaveClientStateHandler.java | 22 +
.../client/workbench/events/SessionInitEvent.java | 39 +
.../workbench/events/SessionInitHandler.java | 22 +
.../workbench/events/ShowErrorMessageEvent.java | 48 +
.../workbench/events/ShowErrorMessageHandler.java | 22 +
.../workbench/events/ShowWarningBarEvent.java | 50 +
.../workbench/events/ShowWarningBarHandler.java | 22 +
.../workbench/events/WorkbenchLoadedEvent.java | 35 +
.../workbench/events/WorkbenchLoadedHandler.java | 22 +
.../events/WorkbenchMetricsChangedEvent.java | 49 +
.../events/WorkbenchMetricsChangedHandler.java | 22 +
.../studio/client/workbench/model/Agreement.java | 41 +
.../client/workbench/model/BrowseUrlInfo.java | 33 +
.../client/workbench/model/ChangeTracker.java | 28 +
.../client/workbench/model/ClientInitState.java | 35 +
.../studio/client/workbench/model/ClientState.java | 105 +
.../client/workbench/model/ConsoleAction.java | 35 +
.../client/workbench/model/ErrorMessage.java | 33 +
.../workbench/model/EventBasedChangeTracker.java | 54 +
.../client/workbench/model/HTMLCapabilities.java | 30 +
.../workbench/model/MetaServerOperations.java | 22 +
.../studio/client/workbench/model/QuotaStatus.java | 78 +
.../workbench/model/RemoteFileSystemContext.java | 131 +
.../studio/client/workbench/model/Session.java | 46 +
.../studio/client/workbench/model/SessionInfo.java | 362 +
.../client/workbench/model/TerminalOptions.java | 34 +
.../client/workbench/model/TexCapabilities.java | 40 +
.../workbench/model/UnsavedChangesTarget.java | 26 +
.../client/workbench/model/ValueChangeTracker.java | 46 +
.../client/workbench/model/WarningBarMessage.java | 33 +
.../client/workbench/model/WorkbenchLists.java | 48 +
.../model/WorkbenchListsServerOperations.java | 49 +
.../client/workbench/model/WorkbenchMetrics.java | 77 +
.../workbench/model/WorkbenchServerOperations.java | 103 +
.../workbench/model/helper/BoolStateValue.java | 49 +
.../workbench/model/helper/ClientStateValue.java | 118 +
.../workbench/model/helper/IntStateValue.java | 54 +
.../workbench/model/helper/JSObjectStateValue.java | 52 +
.../workbench/model/helper/StringStateValue.java | 49 +
.../prefs/events/PreferenceChangedEvent.java | 46 +
.../prefs/events/PreferenceChangedHandler.java | 22 +
.../prefs/events/UiPrefsChangedEvent.java | 72 +
.../prefs/events/UiPrefsChangedHandler.java | 22 +
.../workbench/prefs/model/CompilePdfPrefs.java | 38 +
.../client/workbench/prefs/model/GeneralPrefs.java | 51 +
.../client/workbench/prefs/model/HistoryPrefs.java | 42 +
.../workbench/prefs/model/PackagesPrefs.java | 66 +
.../studio/client/workbench/prefs/model/Prefs.java | 336 +
.../prefs/model/PrefsServerOperations.java | 30 +
.../workbench/prefs/model/ProjectsPrefs.java | 33 +
.../client/workbench/prefs/model/RPrefs.java | 86 +
.../workbench/prefs/model/SourceControlPrefs.java | 68 +
.../prefs/model/SpellingPrefsContext.java | 39 +
.../client/workbench/prefs/model/UIPrefs.java | 320 +
.../workbench/prefs/model/UIPrefsAccessor.java | 367 +
.../workbench/prefs/views/AceEditorPreview.java | 184 +
.../prefs/views/AppearancePreferencesPane.java | 276 +
.../workbench/prefs/views/CheckBoxPrefView.java | 21 +
.../prefs/views/CompilePdfPreferencesPane.java | 210 +
.../prefs/views/EditingPreferencesPane.java | 85 +
.../prefs/views/GeneralPreferencesPane.java | 344 +
.../prefs/views/PackagesPreferencesPane.java | 236 +
.../prefs/views/PaneLayoutPreferencesPane.java | 377 +
.../workbench/prefs/views/PreferencesDialog.css | 46 +
.../workbench/prefs/views/PreferencesDialog.java | 120 +
.../prefs/views/PreferencesDialogResources.java | 39 +
.../workbench/prefs/views/PreferencesPane.java | 79 +
.../prefs/views/SourceControlPreferencesPane.java | 259 +
.../prefs/views/SpellingPreferencesPane.java | 164 +
.../workbench/prefs/views/iconAppearance.png | Bin 0 -> 2287 bytes
.../client/workbench/prefs/views/iconPackages.png | Bin 0 -> 939 bytes
.../client/workbench/prefs/views/iconPanes.png | Bin 0 -> 1317 bytes
.../client/workbench/ui/ConsoleTabPanel.java | 192 +
.../client/workbench/ui/DelayLoadTabShim.java | 89 +
.../client/workbench/ui/DelayLoadWorkbenchTab.java | 200 +
.../client/workbench/ui/FontSizeManager.java | 58 +
.../studio/client/workbench/ui/MainSplitPanel.java | 267 +
.../studio/client/workbench/ui/OptionsLoader.java | 180 +
.../studio/client/workbench/ui/PaneConfig.java | 298 +
.../studio/client/workbench/ui/PaneManager.java | 508 +
.../studio/client/workbench/ui/ToolbarPane.java | 188 +
.../studio/client/workbench/ui/WorkbenchPane.java | 69 +
.../client/workbench/ui/WorkbenchScreen.java | 354 +
.../studio/client/workbench/ui/WorkbenchTab.java | 37 +
.../client/workbench/ui/WorkbenchTabPanel.java | 268 +
.../unsaved/UnsavedChangesCellTableResources.java | 32 +
.../ui/unsaved/UnsavedChangesCellTableStyle.css | 84 +
.../workbench/ui/unsaved/UnsavedChangesDialog.css | 33 +
.../workbench/ui/unsaved/UnsavedChangesDialog.java | 309 +
.../client/workbench/views/BasePresenter.java | 49 +
.../workbench/views/buildtools/BuildPane.java | 163 +
.../workbench/views/buildtools/BuildPresenter.java | 376 +
.../workbench/views/buildtools/BuildTab.java | 161 +
.../buildtools/events/BuildCompletedEvent.java | 75 +
.../views/buildtools/events/BuildErrorsEvent.java | 77 +
.../views/buildtools/events/BuildOutputEvent.java | 54 +
.../views/buildtools/events/BuildStartedEvent.java | 44 +
.../buildtools/model/BuildRestartContext.java | 33 +
.../buildtools/model/BuildServerOperations.java | 39 +
.../views/buildtools/model/BuildState.java | 45 +
.../workbench/views/buildtools/ui/BuildPane.css | 0
.../views/buildtools/ui/BuildPaneResources.java | 39 +
.../workbench/views/buildtools/ui/iconBuild.png | Bin 0 -> 622 bytes
.../workbench/views/choosefile/ChooseFile.java | 87 +
.../views/choosefile/events/ChooseFileEvent.java | 47 +
.../views/choosefile/events/ChooseFileHandler.java | 22 +
.../model/ChooseFileServerOperations.java | 25 +
.../client/workbench/views/console/Console.java | 111 +
.../views/console/ConsoleInterruptButton.java | 103 +
.../workbench/views/console/ConsolePane.java | 172 +
.../workbench/views/console/ConsoleResources.java | 48 +
.../workbench/views/console/consoleStyles.css | 117 +
.../views/console/events/ConsoleBusyEvent.java | 54 +
.../events/ConsoleExecutePendingInputEvent.java | 44 +
.../views/console/events/ConsoleInputEvent.java | 47 +
.../views/console/events/ConsoleInputHandler.java | 22 +
.../views/console/events/ConsolePromptEvent.java | 49 +
.../views/console/events/ConsolePromptHandler.java | 22 +
.../console/events/ConsoleResetHistoryEvent.java | 55 +
.../console/events/ConsoleResetHistoryHandler.java | 22 +
.../events/ConsoleRestartRCompletedEvent.java | 44 +
.../console/events/ConsoleWriteErrorEvent.java | 47 +
.../console/events/ConsoleWriteErrorHandler.java | 22 +
.../console/events/ConsoleWriteInputEvent.java | 46 +
.../console/events/ConsoleWriteInputHandler.java | 22 +
.../console/events/ConsoleWriteOutputEvent.java | 48 +
.../console/events/ConsoleWriteOutputHandler.java | 22 +
.../console/events/ConsoleWritePromptEvent.java | 46 +
.../console/events/ConsoleWritePromptHandler.java | 22 +
.../console/events/RunCommandWithDebugEvent.java | 54 +
.../views/console/events/SendToConsoleEvent.java | 81 +
.../views/console/events/SendToConsoleHandler.java | 22 +
.../console/events/WorkingDirChangedEvent.java | 47 +
.../console/events/WorkingDirChangedHandler.java | 22 +
.../views/console/model/ConsolePrompt.java | 33 +
.../views/console/model/ConsoleResetHistory.java | 34 +
.../console/model/ConsoleServerOperations.java | 47 +
.../views/console/shell/KeyDownPreviewHandler.java | 22 +
.../console/shell/KeyPressPreviewHandler.java | 20 +
.../workbench/views/console/shell/Shell.java | 647 +
.../views/console/shell/ShellInputAnimator.java | 130 +
.../workbench/views/console/shell/ShellPane.java | 88 +
.../views/console/shell/assist/CompletionList.java | 304 +
.../shell/assist/CompletionListPopupPanel.java | 90 +
.../console/shell/assist/CompletionManager.java | 36 +
.../shell/assist/CompletionPopupDisplay.java | 63 +
.../console/shell/assist/CompletionPopupPanel.java | 226 +
.../console/shell/assist/CompletionRequester.java | 272 +
.../console/shell/assist/CompletionUtils.java | 21 +
.../views/console/shell/assist/HelpInfoPane.java | 116 +
.../views/console/shell/assist/HelpStrategy.java | 213 +
.../shell/assist/HistoryCompletionManager.java | 198 +
.../shell/assist/NullCompletionManager.java | 46 +
.../console/shell/assist/PopupPositioner.java | 49 +
.../console/shell/assist/RCompletionManager.java | 720 +
.../console/shell/editor/InputEditorDisplay.java | 58 +
.../editor/InputEditorLineWithCursorPosition.java | 37 +
.../console/shell/editor/InputEditorPosition.java | 92 +
.../console/shell/editor/InputEditorSelection.java | 99 +
.../console/shell/editor/InputEditorUtil.java | 90 +
.../console/shell/impl/PlainTextEditorImpl.java | 49 +
.../shell/impl/PlainTextEditorImplFirefox.java | 120 +
.../console/shell/impl/PlainTextEditorImplIE8.java | 20 +
.../studio/client/workbench/views/data/Data.java | 45 +
.../client/workbench/views/data/DataPane.java | 49 +
.../client/workbench/views/data/DataTab.java | 30 +
.../workbench/views/data/events/ViewDataEvent.java | 49 +
.../views/data/events/ViewDataHandler.java | 22 +
.../views/data/model/DataServerOperations.java | 20 +
.../workbench/views/data/model/DataView.java | 66 +
.../studio/client/workbench/views/edit/Edit.java | 81 +
.../views/edit/events/ShowEditorEvent.java | 63 +
.../views/edit/events/ShowEditorHandler.java | 22 +
.../views/edit/model/EditServerOperations.java | 24 +
.../workbench/views/edit/model/ShowEditorData.java | 36 +
.../client/workbench/views/edit/ui/EditDialog.java | 129 +
.../client/workbench/views/edit/ui/EditView.java | 37 +
.../views/environment/ClearAllDialog.java | 128 +
.../views/environment/EnvironmentPane.css | 5 +
.../views/environment/EnvironmentPane.java | 592 +
.../environment/EnvironmentPaneResources.java | 29 +
.../views/environment/EnvironmentPaneStyle.java | 23 +
.../views/environment/EnvironmentPresenter.java | 855 +
.../views/environment/EnvironmentTab.java | 79 +
.../environment/dataimport/ImportFileSettings.java | 102 +
.../dataimport/ImportFileSettingsDialog.css | 46 +
.../dataimport/ImportFileSettingsDialog.java | 375 +
.../dataimport/ImportFileSettingsDialog.ui.xml | 53 +
.../dataimport/ImportFileSettingsDialogResult.java | 39 +
.../dataimport/ImportGoogleSpreadsheetDialog.css | 64 +
.../views/environment/dataimport/spreadsheet.png | Bin 0 -> 440 bytes
.../events/BrowserLineChangedEvent.java | 79 +
.../events/ContextDepthChangedEvent.java | 103 +
.../environment/events/DebugModeChangedEvent.java | 52 +
.../events/DebugSourceCompletedEvent.java | 62 +
.../events/EnvironmentObjectAssignedEvent.java | 57 +
.../events/EnvironmentObjectRemovedEvent.java | 54 +
.../events/EnvironmentRefreshEvent.java | 44 +
.../views/environment/events/LineData.java | 40 +
.../views/environment/model/CallFrame.java | 113 +
.../views/environment/model/DataPreviewResult.java | 59 +
.../views/environment/model/DebugSourceResult.java | 30 +
.../views/environment/model/DownloadInfo.java | 32 +
.../environment/model/EnvironmentContextData.java | 55 +
.../views/environment/model/EnvironmentFrame.java | 35 +
.../model/EnvironmentServerOperations.java | 69 +
.../views/environment/model/ObjectContents.java | 27 +
.../workbench/views/environment/model/RObject.java | 68 +
.../views/environment/view/AttachedEnvironment.png | Bin 0 -> 431 bytes
.../views/environment/view/CallFrameItem.css | 30 +
.../views/environment/view/CallFrameItem.java | 171 +
.../views/environment/view/CallFrameItem.ui.xml | 6 +
.../views/environment/view/CallFramePanel.css | 36 +
.../views/environment/view/CallFramePanel.java | 209 +
.../views/environment/view/CallFramePanel.ui.xml | 18 +
.../environment/view/CallFramePanelStyle.java | 24 +
.../views/environment/view/CollapseIcon.png | Bin 0 -> 606 bytes
.../environment/view/EnvironmentClientState.java | 55 +
.../environment/view/EnvironmentObjectDisplay.java | 127 +
.../environment/view/EnvironmentObjectGrid.css | 53 +
.../environment/view/EnvironmentObjectGrid.java | 376 +
.../environment/view/EnvironmentObjectList.css | 107 +
.../environment/view/EnvironmentObjectList.java | 434 +
.../views/environment/view/EnvironmentObjects.css | 143 +
.../views/environment/view/EnvironmentObjects.java | 696 +
.../environment/view/EnvironmentObjects.ui.xml | 6 +
.../view/EnvironmentObjectsObserver.java | 31 +
.../environment/view/EnvironmentResources.java | 57 +
.../views/environment/view/EnvironmentStyle.java | 42 +
.../views/environment/view/ExecutionArrow.png | Bin 0 -> 217 bytes
.../views/environment/view/ExpandIcon.png | Bin 0 -> 615 bytes
.../views/environment/view/FunctionEnvironment.png | Bin 0 -> 325 bytes
.../views/environment/view/GlobalEnvironment.png | Bin 0 -> 219 bytes
.../views/environment/view/ObjectGridColumn.java | 96 +
.../views/environment/view/ObjectGridView.png | Bin 0 -> 215 bytes
.../views/environment/view/ObjectListView.png | Bin 0 -> 178 bytes
.../views/environment/view/PackageEnvironment.png | Bin 0 -> 448 bytes
.../views/environment/view/RObjectEntry.java | 108 +
.../views/environment/view/RObjectEntrySort.java | 123 +
.../views/environment/view/TracedFunction.png | Bin 0 -> 254 bytes
.../studio/client/workbench/views/files/Files.java | 653 +
.../client/workbench/views/files/FilesCopy.java | 111 +
.../client/workbench/views/files/FilesPane.java | 260 +
.../client/workbench/views/files/FilesTab.java | 56 +
.../client/workbench/views/files/FilesUpload.java | 139 +
.../views/files/events/DirectoryNavigateEvent.java | 83 +
.../files/events/DirectoryNavigateHandler.java | 23 +
.../views/files/events/FileChangeEvent.java | 48 +
.../views/files/events/FileChangeHandler.java | 22 +
.../views/files/events/ShowFolderEvent.java | 48 +
.../views/files/events/ShowFolderHandler.java | 22 +
.../workbench/views/files/model/FileChange.java | 61 +
.../views/files/model/FileSystemItemAction.java | 22 +
.../views/files/model/FileUploadToken.java | 24 +
.../views/files/model/FilesServerOperations.java | 81 +
.../views/files/model/PendingFileUpload.java | 34 +
.../views/files/ui/FileCommandToolbar.java | 61 +
.../workbench/views/files/ui/FilePathToolbar.java | 146 +
.../workbench/views/files/ui/FileUploadDialog.java | 240 +
.../client/workbench/views/files/ui/FilesList.java | 579 +
.../files/ui/FilesListCellTableResources.java | 46 +
.../views/files/ui/FilesListCellTableStyle.css | 24 +
.../workbench/views/files/ui/ascendingArrow.png | Bin 0 -> 153 bytes
.../workbench/views/files/ui/descendingArrow.png | Bin 0 -> 152 bytes
.../studio/client/workbench/views/help/Help.java | 215 +
.../client/workbench/views/help/HelpPane.java | 647 +
.../client/workbench/views/help/HelpTab.java | 51 +
.../workbench/views/help/ToolbarLinkMenu.java | 179 +
.../views/help/events/ActivateHelpEvent.java | 39 +
.../views/help/events/ActivateHelpHandler.java | 22 +
.../views/help/events/HasHelpNavigateHandlers.java | 23 +
.../views/help/events/HelpNavigateEvent.java | 54 +
.../views/help/events/HelpNavigateHandler.java | 22 +
.../workbench/views/help/events/ShowHelpEvent.java | 47 +
.../views/help/events/ShowHelpHandler.java | 22 +
.../workbench/views/help/model/HelpInfo.java | 205 +
.../views/help/model/HelpServerOperations.java | 36 +
.../client/workbench/views/help/model/Link.java | 106 +
.../workbench/views/help/model/VirtualHistory.java | 72 +
.../workbench/views/help/search/HelpSearch.java | 86 +
.../views/help/search/HelpSearchOracle.java | 82 +
.../views/help/search/HelpSearchWidget.java | 38 +
.../client/workbench/views/history/HasHistory.java | 29 +
.../client/workbench/views/history/History.java | 683 +
.../client/workbench/views/history/HistoryTab.java | 48 +
.../views/history/events/FetchCommandsEvent.java | 35 +
.../views/history/events/FetchCommandsHandler.java | 22 +
.../history/events/HistoryEntriesAddedEvent.java | 49 +
.../history/events/HistoryEntriesAddedHandler.java | 22 +
.../views/history/model/HistoryEntry.java | 64 +
.../history/model/HistoryServerOperations.java | 85 +
.../views/history/view/HistoryEntryItemCodec.java | 187 +
.../workbench/views/history/view/HistoryPane.css | 87 +
.../workbench/views/history/view/HistoryPane.java | 640 +
.../workbench/views/history/view/HistoryTable.java | 128 +
.../history/view/HistoryTableWithToolbar.java | 146 +
.../client/workbench/views/history/view/Shelf.css | 27 +
.../client/workbench/views/history/view/Shelf.java | 125 +
.../workbench/views/history/view/Shelf.ui.xml | 10 +
.../history/view/searchResultsContextButton.png | Bin 0 -> 696 bytes
.../history/view/searchResultsContextButton2.png | Bin 0 -> 185 bytes
.../workbench/views/history/view/shelfbg.png | Bin 0 -> 118 bytes
.../workbench/views/history/view/shelfbgLarge.png | Bin 0 -> 2811 bytes
.../output/compilepdf/CompilePdfOutputPane.java | 165 +
.../compilepdf/CompilePdfOutputPresenter.java | 310 +
.../output/compilepdf/CompilePdfOutputTab.java | 90 +
.../output/compilepdf/events/CompilePdfEvent.java | 87 +
.../views/output/find/FindInFilesDialog.java | 245 +
.../views/output/find/FindInFilesDialog.ui.xml | 56 +
.../workbench/views/output/find/FindOutput.css | 56 +
.../views/output/find/FindOutputCodec.java | 123 +
.../views/output/find/FindOutputPane.java | 274 +
.../views/output/find/FindOutputPresenter.java | 292 +
.../views/output/find/FindOutputResources.java | 34 +
.../workbench/views/output/find/FindOutputTab.java | 80 +
.../views/output/find/FindResultContext.java | 174 +
.../views/output/find/events/FindInFilesEvent.java | 52 +
.../find/events/FindOperationEndedEvent.java | 52 +
.../views/output/find/events/FindResultEvent.java | 79 +
.../find/model/FindInFilesServerOperations.java | 35 +
.../views/output/find/model/FindInFilesState.java | 56 +
.../views/output/find/model/FindResult.java | 125 +
.../output/sourcecpp/SourceCppOutputPane.java | 102 +
.../output/sourcecpp/SourceCppOutputPresenter.java | 123 +
.../views/output/sourcecpp/SourceCppOutputTab.java | 48 +
.../sourcecpp/events/SourceCppCompletedEvent.java | 53 +
.../sourcecpp/events/SourceCppStartedEvent.java | 45 +
.../output/sourcecpp/model/SourceCppState.java | 41 +
.../client/workbench/views/packages/Packages.java | 833 +
.../views/packages/PackagesDisplayObserver.java | 27 +
.../workbench/views/packages/PackagesPane.java | 307 +
.../workbench/views/packages/PackagesTab.java | 67 +
.../events/InstalledPackagesChangedEvent.java | 41 +
.../events/InstalledPackagesChangedHandler.java | 22 +
.../packages/events/LoadedPackageUpdatesEvent.java | 52 +
.../packages/events/PackageStatusChangedEvent.java | 49 +
.../events/PackageStatusChangedHandler.java | 22 +
.../views/packages/model/PackageInfo.java | 71 +
.../packages/model/PackageInstallContext.java | 57 +
.../packages/model/PackageInstallOptions.java | 65 +
.../packages/model/PackageInstallRequest.java | 65 +
.../views/packages/model/PackageStatus.java | 68 +
.../views/packages/model/PackageUpdate.java | 46 +
.../packages/model/PackagesServerOperations.java | 53 +
.../views/packages/ui/CheckForUpdatesDialog.css | 8 +
.../views/packages/ui/CheckForUpdatesDialog.java | 307 +
.../views/packages/ui/InstallPackageDialog.css | 46 +
.../views/packages/ui/InstallPackageDialog.java | 412 +
.../packages/ui/PackagesCellTableResources.java | 33 +
.../views/packages/ui/PackagesCellTableStyle.css | 22 +
.../client/workbench/views/plots/Locator.java | 103 +
.../client/workbench/views/plots/LocatorPanel.java | 237 +
.../studio/client/workbench/views/plots/Plots.java | 647 +
.../client/workbench/views/plots/PlotsPane.java | 192 +
.../client/workbench/views/plots/PlotsTab.java | 192 +
.../workbench/views/plots/events/LocatorEvent.java | 40 +
.../views/plots/events/LocatorHandler.java | 22 +
.../views/plots/events/PlotsChangedEvent.java | 48 +
.../views/plots/events/PlotsChangedHandler.java | 22 +
.../plots/events/PlotsZoomSizeChangedEvent.java | 74 +
.../views/plots/model/ExportPlotOptions.java | 104 +
.../workbench/views/plots/model/Manipulator.java | 145 +
.../views/plots/model/PlotsServerOperations.java | 80 +
.../workbench/views/plots/model/PlotsState.java | 56 +
.../client/workbench/views/plots/model/Point.java | 42 +
.../views/plots/model/SavePlotAsImageContext.java | 39 +
.../views/plots/model/SavePlotAsImageFormat.java | 32 +
.../views/plots/model/SavePlotAsPdfOptions.java | 95 +
.../workbench/views/plots/ui/PlotsToolbar.java | 77 +
.../workbench/views/plots/ui/export/ExportPlot.css | 150 +
.../views/plots/ui/export/ExportPlot.java | 120 +
.../views/plots/ui/export/ExportPlotDialog.java | 117 +
.../views/plots/ui/export/ExportPlotResources.java | 71 +
.../plots/ui/export/ExportPlotSizeEditor.java | 475 +
.../views/plots/ui/export/SavePlotAsHandler.java | 224 +
.../plots/ui/export/SavePlotAsImageDialog.java | 164 +
.../views/plots/ui/export/SavePlotAsPdfDialog.java | 547 +
.../plots/ui/export/SavePlotAsTargetEditor.java | 182 +
.../impl/CopyPlotToClipboardDesktopDialog.java | 104 +
.../CopyPlotToClipboardDesktopMetafileDialog.java | 115 +
.../export/impl/CopyPlotToClipboardWebDialog.java | 72 +
.../plots/ui/export/impl/ExportPlotDesktop.java | 51 +
.../views/plots/ui/export/impl/ExportPlotWeb.java | 33 +
.../workbench/views/plots/ui/export/rightMouse.png | Bin 0 -> 1407 bytes
.../ui/manipulator/ManipulatorChangedHandler.java | 22 +
.../plots/ui/manipulator/ManipulatorControl.java | 61 +
.../ui/manipulator/ManipulatorControlButton.java | 59 +
.../ui/manipulator/ManipulatorControlCheckBox.java | 78 +
.../ui/manipulator/ManipulatorControlPicker.java | 89 +
.../ui/manipulator/ManipulatorControlSlider.java | 137 +
.../plots/ui/manipulator/ManipulatorManager.java | 190 +
.../ui/manipulator/ManipulatorPopupPanel.java | 185 +
.../plots/ui/manipulator/ManipulatorResources.java | 34 +
.../plots/ui/manipulator/ManipulatorStyles.css | 109 +
.../plots/ui/manipulator/ManipulatorStyles.java | 43 +
.../plots/ui/manipulator/manipulateButton.png | Bin 0 -> 499 bytes
.../plots/ui/manipulator/manipulateProgress.gif | Bin 0 -> 1040 bytes
.../plots/ui/manipulator/manipulateSliderBar.png | Bin 0 -> 98 bytes
.../workbench/views/presentation/Presentation.java | 757 +
.../views/presentation/PresentationDispatcher.java | 141 +
.../views/presentation/PresentationFrame.java | 131 +
.../views/presentation/PresentationPane.java | 355 +
.../views/presentation/PresentationTab.java | 102 +
.../PresentationPaneRequestCompletedEvent.java | 45 +
.../events/ShowPresentationPaneEvent.java | 54 +
.../events/SourceFileSaveCompletedEvent.java | 71 +
.../presentation/model/PresentationCommand.java | 40 +
.../model/PresentationRPubsSource.java | 32 +
.../model/PresentationServerOperations.java | 71 +
.../presentation/model/PresentationState.java | 48 +
.../views/presentation/model/SlideNavigation.java | 34 +
.../presentation/model/SlideNavigationItem.java | 40 +
.../client/workbench/views/source/DocsMenu.java | 116 +
.../workbench/views/source/PanelWithToolbars.java | 75 +
.../client/workbench/views/source/Source.java | 2461 ++
.../workbench/views/source/SourceBuildHelper.java | 90 +
.../client/workbench/views/source/SourcePane.java | 304 +
.../client/workbench/views/source/SourceShim.java | 306 +
.../views/source/TabOverflowPopupPanel.java | 162 +
.../views/source/editors/EditingTarget.java | 131 +
.../source/editors/EditingTargetCodeExecution.java | 192 +
.../views/source/editors/EditingTargetSource.java | 96 +
.../views/source/editors/EditingTargetToolbar.java | 34 +
.../codebrowser/CodeBrowserContextWidget.java | 157 +
.../codebrowser/CodeBrowserEditingTarget.java | 713 +
.../codebrowser/CodeBrowserEditingTargetWidget.css | 28 +
.../CodeBrowserEditingTargetWidget.java | 485 +
.../source/editors/data/DataEditingTarget.java | 153 +
.../editors/data/DataEditingTargetWidget.css | 22 +
.../editors/data/DataEditingTargetWidget.java | 135 +
.../editors/profiler/ProfilerEditingTarget.java | 324 +
.../profiler/ProfilerEditingTargetWidget.java | 82 +
.../source/editors/profiler/ProfilerPresenter.java | 184 +
.../editors/profiler/model/ProfilerContents.java | 72 +
.../profiler/model/ProfilerServerOperations.java | 27 +
.../views/source/editors/text/AceEditor.java | 1902 ++
.../views/source/editors/text/AceEditorWidget.java | 622 +
.../source/editors/text/AceKeyboardPreviewer.java | 112 +
.../source/editors/text/AceVimCommandHandler.java | 97 +
.../views/source/editors/text/DocDisplay.java | 224 +
.../workbench/views/source/editors/text/Fold.java | 208 +
.../views/source/editors/text/IconvListResult.java | 33 +
.../source/editors/text/NavigableSourceEditor.java | 42 +
.../workbench/views/source/editors/text/Scope.java | 96 +
.../views/source/editors/text/ScopeList.java | 148 +
.../views/source/editors/text/TextDisplay.java | 26 +
.../source/editors/text/TextEditingTarget.java | 3883 +++
.../text/TextEditingTargetCompilePdfHelper.java | 428 +
.../editors/text/TextEditingTargetCppHelper.java | 86 +
.../editors/text/TextEditingTargetFindReplace.java | 169 +
.../text/TextEditingTargetLatexFormatMenu.java | 163 +
.../text/TextEditingTargetPresentationHelper.java | 212 +
.../text/TextEditingTargetPreviewHtmlHelper.java | 83 +
.../editors/text/TextEditingTargetScopeHelper.java | 163 +
.../editors/text/TextEditingTargetSpelling.java | 140 +
.../editors/text/TextEditingTargetWidget.java | 568 +
.../source/editors/text/WarningBarDisplay.java | 23 +
.../source/editors/text/WordWrapCursorTracker.java | 80 +
.../source/editors/text/ace/AceClickEvent.java | 68 +
.../text/ace/AceDocumentChangeEventNative.java | 30 +
.../source/editors/text/ace/AceEditorNative.java | 257 +
.../views/source/editors/text/ace/AceFold.java | 36 +
.../editors/text/ace/AceInputEditorPosition.java | 163 +
.../editors/text/ace/AceMouseEventNative.java | 47 +
.../source/editors/text/ace/AceResources.java | 30 +
.../views/source/editors/text/ace/Anchor.java | 36 +
.../views/source/editors/text/ace/CodeModel.java | 83 +
.../views/source/editors/text/ace/Document.java | 53 +
.../views/source/editors/text/ace/EditSession.java | 171 +
.../source/editors/text/ace/FoldingRules.java | 29 +
.../source/editors/text/ace/KeyboardHandler.java | 27 +
.../views/source/editors/text/ace/Mode.java | 59 +
.../views/source/editors/text/ace/Position.java | 74 +
.../views/source/editors/text/ace/Range.java | 43 +
.../views/source/editors/text/ace/Renderer.java | 149 +
.../views/source/editors/text/ace/Search.java | 47 +
.../views/source/editors/text/ace/Selection.java | 77 +
.../views/source/editors/text/ace/Token.java | 30 +
.../source/editors/text/ace/TokenIterator.java | 49 +
.../views/source/editors/text/ace/UndoManager.java | 27 +
.../source/editors/text/ace/ace-uncompressed.js | 19980 ++++++++++++
.../workbench/views/source/editors/text/ace/ace.js | 11 +
.../editors/text/ace/spelling/CharClassifier.java | 27 +
.../editors/text/ace/spelling/TokenPredicate.java | 22 +
.../editors/text/ace/spelling/WordIterable.java | 231 +
.../editors/text/cpp/CppCompletionManager.java | 373 +
.../editors/text/events/BreakpointMoveEvent.java | 53 +
.../editors/text/events/BreakpointSetEvent.java | 67 +
.../editors/text/events/CommandClickEvent.java | 41 +
.../editors/text/events/CursorChangedEvent.java | 47 +
.../editors/text/events/CursorChangedHandler.java | 22 +
.../editors/text/events/EditorLoadedEvent.java | 34 +
.../editors/text/events/EditorLoadedHandler.java | 22 +
.../editors/text/events/FileTypeChangedEvent.java | 39 +
.../text/events/FileTypeChangedHandler.java | 22 +
.../editors/text/events/FindRequestedEvent.java | 52 +
.../editors/text/events/FoldChangeEvent.java | 44 +
.../editors/text/events/HasFoldChangeHandlers.java | 23 +
.../source/editors/text/events/PasteEvent.java | 53 +
.../text/events/SourceOnSaveChangedEvent.java | 34 +
.../text/events/SourceOnSaveChangedHandler.java | 22 +
.../source/editors/text/events/UndoRedoEvent.java | 45 +
.../editors/text/events/UndoRedoHandler.java | 22 +
.../editors/text/findreplace/FindReplace.java | 555 +
.../editors/text/findreplace/FindReplaceBar.css | 68 +
.../editors/text/findreplace/FindReplaceBar.java | 338 +
.../editors/text/findreplace/findReplace.png | Bin 0 -> 723 bytes
.../text/findreplace/findReplaceLatched.png | Bin 0 -> 3621 bytes
.../editors/text/spelling/CheckSpelling.java | 413 +
.../text/spelling/InitialProgressDialog.java | 82 +
.../editors/text/spelling/SpellingDialog.java | 254 +
.../editors/text/spelling/SpellingDialog.ui.xml | 79 +
.../source/editors/text/status/StatusBar.java | 29 +
.../editors/text/status/StatusBarElement.java | 32 +
.../text/status/StatusBarElementWidget.java | 163 +
.../editors/text/status/StatusBarPopupMenu.java | 46 +
.../editors/text/status/StatusBarPopupRequest.java | 40 +
.../editors/text/status/StatusBarWidget.java | 109 +
.../editors/text/status/StatusBarWidget.ui.xml | 67 +
.../views/source/editors/text/status/chunk.png | Bin 0 -> 245 bytes
.../views/source/editors/text/status/section.png | Bin 0 -> 257 bytes
.../views/source/editors/text/status/slide.png | Bin 0 -> 222 bytes
.../editors/text/status/statusBarSeparator.png | Bin 0 -> 130 bytes
.../source/editors/text/status/statusBarTile.png | Bin 0 -> 183 bytes
.../source/editors/text/status/upDownArrow.png | Bin 0 -> 195 bytes
.../editors/text/themes/AceThemeResources.java | 57 +
.../source/editors/text/themes/AceThemes.java | 119 +
.../views/source/editors/text/themes/chrome.css | 170 +
.../views/source/editors/text/themes/clouds.css | 128 +
.../source/editors/text/themes/clouds_midnight.css | 129 +
.../views/source/editors/text/themes/cobalt.css | 155 +
.../source/editors/text/themes/crimson_editor.css | 161 +
.../views/source/editors/text/themes/dawn.css | 159 +
.../views/source/editors/text/themes/demo.html | 2 +
.../source/editors/text/themes/dreamweaver.css | 192 +
.../views/source/editors/text/themes/eclipse.css | 116 +
.../source/editors/text/themes/idle_fingers.css | 147 +
.../views/source/editors/text/themes/kr_theme.css | 150 +
.../views/source/editors/text/themes/merbivore.css | 150 +
.../source/editors/text/themes/merbivore_soft.css | 156 +
.../source/editors/text/themes/mono_industrial.css | 158 +
.../views/source/editors/text/themes/monokai.css | 155 +
.../source/editors/text/themes/pastel_on_dark.css | 160 +
.../source/editors/text/themes/solarized_dark.css | 141 +
.../source/editors/text/themes/solarized_light.css | 140 +
.../views/source/editors/text/themes/textmate.css | 176 +
.../views/source/editors/text/themes/tomorrow.css | 174 +
.../source/editors/text/themes/tomorrow_night.css | 174 +
.../editors/text/themes/tomorrow_night_blue.css | 174 +
.../editors/text/themes/tomorrow_night_bright.css | 174 +
.../text/themes/tomorrow_night_eighties.css | 170 +
.../views/source/editors/text/themes/twilight.css | 172 +
.../source/editors/text/themes/vibrant_ink.css | 151 +
.../editors/text/ui/ChooseEncodingDialog.java | 203 +
.../views/source/editors/text/ui/NewRdDialog.java | 103 +
.../source/editors/text/ui/NewRdDialog.ui.xml | 32 +
.../urlcontent/UrlContentEditingTarget.java | 393 +
.../urlcontent/UrlContentEditingTargetWidget.java | 63 +
.../source/events/CodeBrowserFinishedEvent.java | 41 +
.../source/events/CodeBrowserFinishedHandler.java | 22 +
.../source/events/CodeBrowserHighlightEvent.java | 57 +
.../source/events/CodeBrowserNavigationEvent.java | 72 +
.../events/CodeBrowserNavigationHandler.java | 22 +
.../views/source/events/DocTabsChangedEvent.java | 71 +
.../views/source/events/DocTabsChangedHandler.java | 22 +
.../source/events/EditPresentationSourceEvent.java | 62 +
.../views/source/events/FileEditEvent.java | 49 +
.../views/source/events/FileEditHandler.java | 22 +
.../views/source/events/InsertSourceEvent.java | 55 +
.../views/source/events/InsertSourceHandler.java | 22 +
.../source/events/LastSourceDocClosedEvent.java | 35 +
.../source/events/LastSourceDocClosedHandler.java | 22 +
.../events/RecordNavigationPositionEvent.java | 48 +
.../events/RecordNavigationPositionHandler.java | 22 +
.../views/source/events/SaveFileEvent.java | 36 +
.../views/source/events/SaveFileHandler.java | 22 +
.../views/source/events/ShowContentEvent.java | 49 +
.../views/source/events/ShowContentHandler.java | 22 +
.../views/source/events/ShowDataEvent.java | 49 +
.../views/source/events/ShowDataHandler.java | 22 +
.../events/SourceExtendedTypeDetectedEvent.java | 73 +
.../views/source/events/SourceFileSavedEvent.java | 50 +
.../source/events/SourceFileSavedHandler.java | 22 +
.../views/source/events/SourceNavigationEvent.java | 48 +
.../source/events/SourceNavigationHandler.java | 22 +
.../views/source/events/SwitchToDocEvent.java | 46 +
.../views/source/events/SwitchToDocHandler.java | 22 +
.../source/model/CheckForExternalEditResult.java | 40 +
.../views/source/model/CodeBrowserContents.java | 55 +
.../views/source/model/CompletionOptions.java | 44 +
.../workbench/views/source/model/ContentItem.java | 41 +
.../views/source/model/CppCapabilities.java | 44 +
.../workbench/views/source/model/DataItem.java | 93 +
.../workbench/views/source/model/DirtyState.java | 111 +
.../views/source/model/DocUpdateSentinel.java | 556 +
.../views/source/model/RdShellResult.java | 35 +
.../views/source/model/RnwChunkOptions.java | 257 +
.../views/source/model/RnwCompletionContext.java | 34 +
.../views/source/model/SourceDocument.java | 125 +
.../views/source/model/SourceNavigation.java | 74 +
.../source/model/SourceNavigationHistory.java | 154 +
.../views/source/model/SourcePosition.java | 79 +
.../views/source/model/SourceServerOperations.java | 185 +
.../views/source/model/TexServerOperations.java | 27 +
.../workbench/views/vcs/BaseVcsPresenter.java | 49 +
.../workbench/views/vcs/BranchToolbarButton.java | 121 +
.../views/vcs/CheckoutBranchToolbarButton.java | 68 +
.../views/vcs/HistoryBranchToolbarButton.java | 56 +
.../client/workbench/views/vcs/VCSPresenter.java | 142 +
.../studio/client/workbench/views/vcs/VCSTab.java | 79 +
.../workbench/views/vcs/common/ChangelistTable.css | 4 +
.../views/vcs/common/ChangelistTable.java | 437 +
.../vcs/common/ChangelistTableCellTableStyle.css | 20 +
.../views/vcs/common/ConsoleProgressDialog.java | 320 +
.../views/vcs/common/ConsoleProgressWidget.java | 29 +
.../client/workbench/views/vcs/common/Pager.java | 118 +
.../views/vcs/common/ProcessCallback.java | 113 +
.../views/vcs/common/SimplePagerStyle.css | 8 +
.../workbench/views/vcs/common/VCSFileOpener.java | 117 +
.../workbench/views/vcs/common/ascendingArrow.png | Bin 0 -> 153 bytes
.../workbench/views/vcs/common/descendingArrow.png | Bin 0 -> 152 bytes
.../views/vcs/common/diff/ChunkHeaderParser.java | 143 +
.../views/vcs/common/diff/ChunkOrLine.java | 55 +
.../workbench/views/vcs/common/diff/DiffChunk.java | 81 +
.../views/vcs/common/diff/DiffFileHeader.java | 46 +
.../views/vcs/common/diff/DiffFormatException.java | 25 +
.../views/vcs/common/diff/DiffParser.java | 22 +
.../workbench/views/vcs/common/diff/Line.java | 167 +
.../views/vcs/common/diff/LineActionButton.css | 27 +
.../vcs/common/diff/LineActionButtonRenderer.java | 139 +
.../views/vcs/common/diff/LineTablePresenter.java | 41 +
.../views/vcs/common/diff/LineTableView.java | 558 +
.../common/diff/LineTableViewCellTableStyle.css | 138 +
.../workbench/views/vcs/common/diff/Range.java | 27 +
.../views/vcs/common/diff/UnifiedEmitter.java | 390 +
.../views/vcs/common/diff/UnifiedParser.java | 321 +
.../vcs/common/diff/images/SmallBlueButtonLeft.png | Bin 0 -> 300 bytes
.../common/diff/images/SmallBlueButtonRight.png | Bin 0 -> 310 bytes
.../vcs/common/diff/images/SmallBlueButtonTile.png | Bin 0 -> 166 bytes
.../vcs/common/diff/images/SmallGrayButtonLeft.png | Bin 0 -> 290 bytes
.../common/diff/images/SmallGrayButtonRight.png | Bin 0 -> 289 bytes
.../vcs/common/diff/images/SmallGrayButtonTile.png | Bin 0 -> 164 bytes
.../views/vcs/common/events/AskPassEvent.java | 84 +
.../vcs/common/events/DiffChunkActionEvent.java | 61 +
.../vcs/common/events/DiffChunkActionHandler.java | 22 +
.../vcs/common/events/DiffLinesActionEvent.java | 47 +
.../vcs/common/events/DiffLinesActionHandler.java | 22 +
.../views/vcs/common/events/ShowVcsDiffEvent.java | 54 +
.../vcs/common/events/ShowVcsHistoryEvent.java | 54 +
.../views/vcs/common/events/StageUnstageEvent.java | 56 +
.../vcs/common/events/StageUnstageHandler.java | 22 +
.../views/vcs/common/events/SwitchViewEvent.java | 44 +
.../views/vcs/common/events/VcsRefreshEvent.java | 60 +
.../views/vcs/common/events/VcsRefreshHandler.java | 22 +
.../vcs/common/events/VcsRevertFileEvent.java | 54 +
.../vcs/common/events/VcsViewOnGitHubEvent.java | 54 +
.../vcs/common/events/ViewFileRevisionEvent.java | 53 +
.../vcs/common/events/ViewFileRevisionHandler.java | 22 +
.../views/vcs/common/images/PageBackwardButton.png | Bin 0 -> 835 bytes
.../common/images/PageBackwardButtonDisabled.png | Bin 0 -> 829 bytes
.../views/vcs/common/images/PageFirstButton.png | Bin 0 -> 853 bytes
.../vcs/common/images/PageFirstButtonDisabled.png | Bin 0 -> 850 bytes
.../views/vcs/common/images/PageForwardButton.png | Bin 0 -> 844 bytes
.../common/images/PageForwardButtonDisabled.png | Bin 0 -> 832 bytes
.../views/vcs/common/images/PageLastButton.png | Bin 0 -> 905 bytes
.../vcs/common/images/PageLastButtonDisabled.png | Bin 0 -> 884 bytes
.../views/vcs/common/images/PageNextButton.png | Bin 0 -> 860 bytes
.../vcs/common/images/PageNextButtonDisabled.png | Bin 0 -> 853 bytes
.../views/vcs/common/images/PagePreviousButton.png | Bin 0 -> 873 bytes
.../common/images/PagePreviousButtonDisabled.png | Bin 0 -> 856 bytes
.../views/vcs/common/model/GitHubViewRequest.java | 68 +
.../workbench/views/vcs/common/model/VcsState.java | 194 +
.../workbench/views/vcs/dialog/CommitCount.java | 28 +
.../workbench/views/vcs/dialog/CommitDetail.java | 275 +
.../workbench/views/vcs/dialog/CommitDetail.ui.xml | 84 +
.../vcs/dialog/CommitFilterToolbarButton.java | 171 +
.../workbench/views/vcs/dialog/CommitInfo.java | 66 +
.../views/vcs/dialog/CommitListTable.java | 327 +
.../vcs/dialog/CommitListTableCellTableStyle.css | 15 +
.../workbench/views/vcs/dialog/CommitTocRow.java | 60 +
.../workbench/views/vcs/dialog/CommitTocRow.ui.xml | 26 +
.../workbench/views/vcs/dialog/DiffFrame.css | 35 +
.../workbench/views/vcs/dialog/DiffFrame.java | 116 +
.../workbench/views/vcs/dialog/DiffFrame.ui.xml | 34 +
.../views/vcs/dialog/HistoryAsyncDataProvider.java | 148 +
.../workbench/views/vcs/dialog/HistoryPanel.css | 40 +
.../workbench/views/vcs/dialog/HistoryPanel.java | 261 +
.../workbench/views/vcs/dialog/HistoryPanel.ui.xml | 37 +
.../views/vcs/dialog/HistoryPresenter.java | 412 +
.../views/vcs/dialog/HistoryStrategy.java | 63 +
.../views/vcs/dialog/ReviewPresenter.java | 31 +
.../views/vcs/dialog/ReviewPresenterImpl.java | 72 +
.../workbench/views/vcs/dialog/SharedStyles.java | 28 +
.../views/vcs/dialog/SizeWarningWidget.java | 60 +
.../views/vcs/dialog/SizeWarningWidget.ui.xml | 44 +
.../workbench/views/vcs/dialog/ViewFilePanel.java | 359 +
.../views/vcs/dialog/graph/GraphColumn.java | 47 +
.../views/vcs/dialog/graph/GraphLine.java | 158 +
.../views/vcs/dialog/graph/GraphTheme.java | 79 +
.../views/vcs/dialog/images/blankFileIcon.png | Bin 0 -> 2813 bytes
.../views/vcs/dialog/images/diffHeaderTile.png | Bin 0 -> 2890 bytes
.../workbench/views/vcs/dialog/images/discard.png | Bin 0 -> 461 bytes
.../workbench/views/vcs/dialog/images/ignore.png | Bin 0 -> 711 bytes
.../views/vcs/dialog/images/splitterTileH.png | Bin 0 -> 123 bytes
.../views/vcs/dialog/images/splitterTileV.png | Bin 0 -> 128 bytes
.../workbench/views/vcs/dialog/images/stage.png | Bin 0 -> 485 bytes
.../views/vcs/dialog/images/stageAllFiles.png | Bin 0 -> 570 bytes
.../views/vcs/dialog/images/toolbarTile.png | Bin 0 -> 170 bytes
.../client/workbench/views/vcs/frame/VCSPopup.java | 112 +
.../views/vcs/git/GitChangelistTable.java | 103 +
.../views/vcs/git/GitChangelistTablePresenter.java | 102 +
.../client/workbench/views/vcs/git/GitPane.java | 208 +
.../workbench/views/vcs/git/GitPresenter.java | 407 +
.../workbench/views/vcs/git/GitPresenterCore.java | 178 +
.../workbench/views/vcs/git/GitStatusRenderer.java | 154 +
.../git/dialog/GitHistoryAsyncDataProvider.java | 63 +
.../views/vcs/git/dialog/GitHistoryStrategy.java | 163 +
.../views/vcs/git/dialog/GitReviewPanel.css | 84 +
.../views/vcs/git/dialog/GitReviewPanel.java | 669 +
.../views/vcs/git/dialog/GitReviewPanel.ui.xml | 78 +
.../views/vcs/git/dialog/GitReviewPresenter.java | 814 +
.../workbench/views/vcs/git/images/statusAdded.png | Bin 0 -> 339 bytes
.../views/vcs/git/images/statusCopied.png | Bin 0 -> 310 bytes
.../views/vcs/git/images/statusDeleted.png | Bin 0 -> 315 bytes
.../views/vcs/git/images/statusModified.png | Bin 0 -> 337 bytes
.../workbench/views/vcs/git/images/statusNone.png | Bin 0 -> 2805 bytes
.../views/vcs/git/images/statusRenamed.png | Bin 0 -> 301 bytes
.../views/vcs/git/images/statusUnmerged.png | Bin 0 -> 275 bytes
.../views/vcs/git/images/statusUntracked.png | Bin 0 -> 328 bytes
.../workbench/views/vcs/git/model/GitState.java | 113 +
.../views/vcs/svn/SVNChangelistTable.java | 75 +
.../views/vcs/svn/SVNChangelistTablePresenter.java | 81 +
.../workbench/views/vcs/svn/SVNCommandHandler.java | 363 +
.../workbench/views/vcs/svn/SVNDiffParser.java | 209 +
.../client/workbench/views/vcs/svn/SVNPane.java | 158 +
.../workbench/views/vcs/svn/SVNPresenter.java | 235 +
.../views/vcs/svn/SVNPresenterDisplay.java | 26 +
.../workbench/views/vcs/svn/SVNResolveDialog.java | 107 +
.../views/vcs/svn/SVNResolveDialog.ui.xml | 112 +
.../views/vcs/svn/SVNSelectChangelistTable.java | 148 +
.../vcs/svn/SVNSelectChangelistTablePresenter.java | 134 +
.../workbench/views/vcs/svn/SVNStatusRenderer.java | 163 +
.../views/vcs/svn/commit/SVNCommitDialog.java | 266 +
.../views/vcs/svn/commit/SVNCommitDialog.ui.xml | 52 +
.../svn/dialog/SVNHistoryAsyncDataProvider.java | 64 +
.../views/vcs/svn/dialog/SVNHistoryStrategy.java | 181 +
.../views/vcs/svn/dialog/SVNReviewPanel.css | 52 +
.../views/vcs/svn/dialog/SVNReviewPanel.java | 480 +
.../views/vcs/svn/dialog/SVNReviewPanel.ui.xml | 48 +
.../views/vcs/svn/dialog/SVNReviewPresenter.java | 525 +
.../workbench/views/vcs/svn/images/statusAdded.png | Bin 0 -> 339 bytes
.../views/vcs/svn/images/statusConflicted.png | Bin 0 -> 316 bytes
.../views/vcs/svn/images/statusDeleted.png | Bin 0 -> 315 bytes
.../views/vcs/svn/images/statusExternal.png | Bin 0 -> 351 bytes
.../views/vcs/svn/images/statusIgnored.png | Bin 0 -> 271 bytes
.../views/vcs/svn/images/statusLocked.png | Bin 0 -> 238 bytes
.../vcs/svn/images/statusLockedInRepository.png | Bin 0 -> 288 bytes
.../views/vcs/svn/images/statusMissing.png | Bin 0 -> 246 bytes
.../views/vcs/svn/images/statusModified.png | Bin 0 -> 337 bytes
.../workbench/views/vcs/svn/images/statusNone.png | Bin 0 -> 2805 bytes
.../views/vcs/svn/images/statusObstructed.png | Bin 0 -> 273 bytes
.../views/vcs/svn/images/statusUnversioned.png | Bin 0 -> 328 bytes
.../workbench/views/vcs/svn/model/SVNState.java | 89 +
.../client/workbench/views/viewer/ViewerPane.java | 123 +
.../workbench/views/viewer/ViewerPresenter.java | 201 +
.../client/workbench/views/viewer/ViewerTab.java | 40 +
.../views/viewer/events/ViewerNavigateEvent.java | 75 +
.../views/viewer/model/ViewerServerOperations.java | 23 +
src/gwt/test/autoindent_test.html | 325 +
.../studio/client/common/r/RTokenizerTests.java | 182 +
.../views/vcs/common/diff/UnifiedParserTest.java | 102 +
.../workbench/views/vcs/common/diff/diff1.out.txt | 23 +
.../workbench/views/vcs/common/diff/diff1.txt | 27 +
.../workbench/views/vcs/common/diff/diff2.out.txt | 12 +
.../workbench/views/vcs/common/diff/diff2.txt | 12 +
.../org/rstudio/studio/selenium/BootRStudio.java | 36 +
.../rstudio/studio/selenium/ConsoleTestUtils.java | 61 +
.../rstudio/studio/selenium/DataImportTests.java | 94 +
.../rstudio/studio/selenium/DialogTestUtils.java | 79 +
.../org/rstudio/studio/selenium/MenuNavigator.java | 83 +
.../studio/selenium/RConsoleInteraction.java | 145 +
.../rstudio/studio/selenium/RStudioTestSuite.java | 31 +
.../studio/selenium/RStudioWebAppDriver.java | 38 +
.../rstudio/studio/selenium/SourceInteraction.java | 149 +
.../rstudio/studio/selenium/WorkbenchTests.java | 115 +
.../rstudio/studio/selenium/resources/banklist.csv | 516 +
src/gwt/test/outline_harness.html | 53 +
src/gwt/test/rmode_harness.html | 23 +
src/gwt/test/sizetofit_harness.html | 49 +
src/gwt/tools/compile-themes | 92 +
src/gwt/tools/compiler/COPYING | 202 +
src/gwt/tools/compiler/README | 292 +
src/gwt/tools/compiler/compiler.jar | Bin 0 -> 5352128 bytes
src/gwt/tools/compiler/installed_version | 1 +
src/gwt/tools/encrypt-bootstrap.js | 20 +
src/gwt/tools/rsa.js | 861 +
src/gwt/tools/sync-ace-commits | 107 +
src/gwt/tools/sync-pdfjs | 36 +
src/gwt/tools/unicode-chars-util.rb | 49 +
src/gwt/tools/update-rsa-js | 33 +
src/gwt/www/.gitignore | 3 +
src/gwt/www/css/data.css | 45 +
src/gwt/www/docs/keyboard.htm | 687 +
src/gwt/www/expired.htm | 88 +
src/gwt/www/favicon.ico | Bin 0 -> 68935 bytes
src/gwt/www/images/buttonLeft.png | Bin 0 -> 3158 bytes
src/gwt/www/images/buttonRight.png | Bin 0 -> 3237 bytes
src/gwt/www/images/buttonTile.png | Bin 0 -> 2868 bytes
src/gwt/www/images/expired.png | Bin 0 -> 6795 bytes
src/gwt/www/images/favicon.ico | Bin 0 -> 1150 bytes
src/gwt/www/images/offline.png | Bin 0 -> 4870 bytes
src/gwt/www/images/progress_large.gif | Bin 0 -> 11645 bytes
src/gwt/www/images/rstudio.png | Bin 0 -> 859 bytes
src/gwt/www/images/warning.png | Bin 0 -> 745 bytes
src/gwt/www/index.htm | 33 +
src/gwt/www/js/diff.js | 1 +
src/gwt/www/js/encrypt.min.js | 23 +
src/gwt/www/offline.htm | 88 +
src/gwt/www/progress.htm | 33 +
src/gwt/www/rstudio.css | 69 +
src/gwt/www/templates/encrypted-sign-in.htm | 221 +
src/gwt/www/unsupported_browser.htm | 42 +
src/gwt/www/webkit.nocache.html | 87 +
3273 files changed, 459289 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..efd0901
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+/build/
+build_*/
+build-*/
+*-build/
+qtcreator-build
+.DS_Store
+src/gwt/www/js/acesupport.js
+src/gwt/www/js/ace.js
+src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/acesupport.js
+src/gwt/src/org/rstudio/core/client/jsonrpc/json2.min.js
+src/gwt/www/js/json2.min.js
+src/gwt/tools/ace/
+src/gwt/tools/pdfjs/
+/src/gwt/.gwt/.gwt-log
+rstudio*.Rproj
+.Rproj.user
+.Rhistory
+.RData
+.Rprofile
+/src/gwt/bin.test/
+/src/gwt/gwt-unitCache/
+NEWS.html
+CMakeLists.txt.user
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..e69de29
diff --git a/CMakeCompiler.txt b/CMakeCompiler.txt
new file mode 100644
index 0000000..9e22a63
--- /dev/null
+++ b/CMakeCompiler.txt
@@ -0,0 +1,20 @@
+#
+# CMakeCompiler.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# use clang on osx
+if(APPLE)
+ set(CMAKE_C_COMPILER /usr/bin/cc)
+ set(CMAKE_CXX_COMPILER /usr/bin/c++)
+endif()
diff --git a/CMakeGlobals.txt b/CMakeGlobals.txt
new file mode 100644
index 0000000..cc10191
--- /dev/null
+++ b/CMakeGlobals.txt
@@ -0,0 +1,143 @@
+#
+# CMakeGlobals.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# version info
+if ("$ENV{RSTUDIO_VERSION_MAJOR}" STREQUAL "")
+ set(CPACK_PACKAGE_VERSION_MAJOR "99")
+ set(RSTUDIO_UNVERSIONED_BUILD TRUE)
+else()
+ set(CPACK_PACKAGE_VERSION_MAJOR $ENV{RSTUDIO_VERSION_MAJOR})
+endif()
+if ("$ENV{RSTUDIO_VERSION_MINOR}" STREQUAL "")
+ set(CPACK_PACKAGE_VERSION_MINOR "9")
+else()
+ set(CPACK_PACKAGE_VERSION_MINOR $ENV{RSTUDIO_VERSION_MINOR})
+endif()
+if ("$ENV{RSTUDIO_VERSION_PATCH}" STREQUAL "")
+ set(CPACK_PACKAGE_VERSION_PATCH "9")
+else()
+ set(CPACK_PACKAGE_VERSION_PATCH $ENV{RSTUDIO_VERSION_PATCH})
+endif()
+set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
+
+# default to debug builds
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug")
+endif()
+
+# platform specific default for targets
+if(NOT RSTUDIO_TARGET)
+ set(RSTUDIO_TARGET "Development")
+ set(RSTUDIO_DEVELOPMENT TRUE)
+endif()
+
+# set desktop and server build flags
+if( NOT WIN32 AND ( (${RSTUDIO_TARGET} STREQUAL "Development") OR
+ (${RSTUDIO_TARGET} STREQUAL "Server")) )
+ set(RSTUDIO_SERVER TRUE)
+endif()
+if( ${RSTUDIO_TARGET} STREQUAL "Development" OR ${RSTUDIO_TARGET} STREQUAL "Desktop" )
+ set(RSTUDIO_DESKTOP TRUE)
+endif()
+
+# set session64 if specified
+if (${RSTUDIO_TARGET} STREQUAL "SessionWin64")
+ set(RSTUDIO_SESSION_WIN64 TRUE)
+endif()
+
+# required R version
+set(RSTUDIO_R_MAJOR_VERSION_REQUIRED 2)
+set(RSTUDIO_R_MINOR_VERSION_REQUIRED 11)
+set(RSTUDIO_R_PATCH_VERSION_REQUIRED 1)
+
+# allow opting out of version checking (for building on older distros)
+if(NOT DEFINED RSTUDIO_VERIFY_R_VERSION)
+ if(RSTUDIO_PACKAGE_BUILD)
+ set(RSTUDIO_VERIFY_R_VERSION FALSE)
+ else()
+ set(RSTUDIO_VERIFY_R_VERSION TRUE)
+ endif()
+endif()
+
+# install freedesktop integration files if we are installing into /usr
+if(NOT DEFINED RSTUDIO_INSTALL_FREEDESKTOP)
+ if(${CMAKE_INSTALL_PREFIX} MATCHES "/usr/.*")
+ set(RSTUDIO_INSTALL_WITH_PRIV TRUE)
+ else()
+ set(RSTUDIO_INSTALL_WITH_PRIV FALSE)
+ endif()
+ if(RSTUDIO_INSTALL_WITH_PRIV AND UNIX AND NOT APPLE)
+ set(RSTUDIO_INSTALL_FREEDESKTOP TRUE)
+ else()
+ set(RSTUDIO_INSTALL_FREEDESKTOP FALSE)
+ endif()
+endif()
+
+# cmake modules (compute path relative to this file)
+get_filename_component(ROOT_SRC_DIR ${CMAKE_CURRENT_LIST_FILE} PATH)
+set(CMAKE_MODULE_PATH "${ROOT_SRC_DIR}/cmake/modules/")
+
+# dependencies
+set(RSTUDIO_DEPENDENCIES_DIR "${ROOT_SRC_DIR}/dependencies")
+if(WIN32)
+ set(RSTUDIO_WINDOWS_DEPENDENCIES_DIR "${RSTUDIO_DEPENDENCIES_DIR}/windows")
+endif()
+
+# special install directories for apple desktop
+if (APPLE AND RSTUDIO_DESKTOP)
+ set(RSTUDIO_INSTALL_BIN RStudio.app/Contents/MacOS)
+ set(RSTUDIO_INSTALL_SUPPORTING RStudio.app/Contents/Resources)
+else()
+ if (RSTUDIO_SESSION_WIN64)
+ set(RSTUDIO_INSTALL_BIN x64)
+ else()
+ set(RSTUDIO_INSTALL_BIN bin)
+ endif()
+ set(RSTUDIO_INSTALL_SUPPORTING .)
+endif()
+
+# if the install prefix is /usr/local then tweak as appropriate
+if(UNIX)
+ if(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr/local")
+ if(APPLE AND RSTUDIO_DESKTOP)
+ set(CMAKE_INSTALL_PREFIX "/Applications")
+ else()
+ if(RSTUDIO_DESKTOP)
+ set(CMAKE_INSTALL_PREFIX "/usr/local/lib/rstudio")
+ else()
+ set(CMAKE_INSTALL_PREFIX "/usr/local/lib/rstudio-server")
+ endif()
+ endif()
+ endif()
+endif()
+
+# detect lsb release
+if (UNIX AND NOT APPLE)
+ if(NOT RSTUDIO_LSB_RELEASE)
+ execute_process(COMMAND /usr/bin/lsb_release "--id" "--short"
+ OUTPUT_VARIABLE RSTUDIO_LSB_RELEASE)
+ if (RSTUDIO_LSB_RELEASE)
+ string(STRIP ${RSTUDIO_LSB_RELEASE} RSTUDIO_LSB_RELEASE)
+ string(TOLOWER ${RSTUDIO_LSB_RELEASE} RSTUDIO_LSB_RELEASE)
+ set(RSTUDIO_LSB_RELEASE ${RSTUDIO_LSB_RELEASE} CACHE STRING "LSB release")
+ message(STATUS "LSB release: ${RSTUDIO_LSB_RELEASE}")
+ endif()
+ endif()
+endif()
+
+# make sure the CMAKE_INSTALL_PREFIX uses a cmake style path
+file(TO_CMAKE_PATH "${CMAKE_INSTALL_PREFIX}" CMAKE_INSTALL_PREFIX)
+
+
diff --git a/CMakeInstallDocs.txt b/CMakeInstallDocs.txt
new file mode 100644
index 0000000..419c7fb
--- /dev/null
+++ b/CMakeInstallDocs.txt
@@ -0,0 +1,22 @@
+#
+# CMakeInstallDocs.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+install(FILES README.md
+ INSTALL
+ COPYING
+ NOTICE
+ ${CMAKE_CURRENT_BINARY_DIR}/SOURCE
+ ${CMAKE_CURRENT_BINARY_DIR}/VERSION
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING})
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..1d9e6ee
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,64 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# set compiler
+include(CMakeCompiler.txt)
+
+cmake_minimum_required(VERSION 2.6)
+project (RStudio)
+
+# project globals
+include(CMakeGlobals.txt)
+
+# remove previous installation if requested
+if(RSTUDIO_UNINSTALL_PREVIOUS)
+ install(CODE "execute_process(COMMAND rm -rf \${CMAKE_INSTALL_PREFIX})")
+endif()
+
+# install root docs
+if (NOT RSTUDIO_SESSION_WIN64)
+ # dynamically configure SOURCE with the git revision hash
+ INSTALL(CODE "
+ exec_program(git ARGS rev-parse HEAD
+ OUTPUT_VARIABLE RSTUDIO_GIT_REVISION_HASH)
+ configure_file (\"${CMAKE_CURRENT_SOURCE_DIR}/SOURCE.in\"
+ \"${CMAKE_CURRENT_BINARY_DIR}/SOURCE\")
+ ")
+
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/VERSION.in
+ ${CMAKE_CURRENT_BINARY_DIR}/VERSION)
+
+ # install root docs
+ include(CMakeInstallDocs.txt)
+endif()
+
+# overlay
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+endif()
+
+# main src
+add_subdirectory(src)
+
+# packaging
+add_subdirectory(package)
+
+
+
+
+
+
+
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..e04b1f8
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,672 @@
+
+Unless you have received this program directly from RStudio pursuant to
+the terms of a commercial license agreement with RStudio, then RStudio is
+licensed to you under the AGPLv3, the terms of which are included below.
+Details on obtaining the source code for this distribution are included
+in the file SOURCE.
+
+RStudio includes other open source software whose license terms can be found
+in the file NOTICE. RStudio for Windows is bundled with binary copies of
+several utilities, whose license terms can be found in the file SOURCE.
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
\ No newline at end of file
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..26a9b16
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,166 @@
+
+Installing RStudio from Source
+=============================================================================
+
+This document describes how to build and install RStudio from the source
+distribution. Information on obtaining the RStudio source code can be found
+in the file SOURCE. Note that precompiled binaries are also available for
+Windows, OSX, as well as recent versions of various Linux distributions.
+
+1) Installing Dependencies
+----------------------------------------------------------------------------
+
+Building RStudio requires a number of dependencies (including R itself).
+There are platform-specific instructions for satsifying these dependencies
+within the the following directories
+
+ dependencies
+ linux
+ osx
+ windows
+
+Please see the README file contained within the root of each platform's
+directory for specific instructions.
+
+
+2) Configuring the Build Environment
+----------------------------------------------------------------------------
+
+a) From the root of the RStudio tree create a build directory and then
+ change to it:
+
+ mkdir build
+ cd build
+
+
+b) Configure the build using cmake as appopriate, e.g.
+
+ cmake .. -DRSTUDIO_TARGET=Server -DCMAKE_BUILD_TYPE=Release
+
+ Variables that control configuration include:
+
+ RSTUDIO_TARGET Desktop or Server
+
+ CMAKE_BUILD_TYPE Debug, Release, RelMinSize, or RelWithDebInfo
+
+ CMAKE_INSTALL_PREFIX Defaults:
+ Linux (Desktop): /usr/local/lib/rstudio
+ Linux (Server): /usr/local/lib/rstudio-server
+ OSX: /Applications/RStudio
+ Windows: C:\Program Files\RStudio
+
+
+c) There are a couple of additional considerations on Windows. First,
+ RStudio Server is not supported on Windows so the configuration
+ always defaults to Desktop. Second, you need to add an extra -G
+ paramater to specify MinGW as the build toolchain, for example:
+
+ cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
+
+
+3) Building and Installing
+----------------------------------------------------------------------------
+
+a) Acquire administrative rights (if necessary). If you have configured
+ RStudio to be installed in a protected directory (the default on all
+ platforms) then you need to run the build/install command as an
+ administrator (e.g. "su -", "sudo sh", or running a console as an
+ Administrator on Windows)
+
+
+b) Change to the build directory where you configured RStudio
+
+
+c) Run the "make install" command:
+
+ Linux & OSX: sudo make install OR
+ Windows: mingw32-make install
+
+ NOTE: For RStudio Desktop on Linux, make install automatically creates
+ an entry in the Applications -> Programming menu for RStudio.
+
+
+d) If you are installing RStudio Server some additional configuration
+ steps are required to complete the installation. These steps are
+ detailed in the section below.
+
+
+4) RStudio Server Configuration
+----------------------------------------------------------------------------
+
+If you have installed RStudio Server from source there are a number of other
+steps (some required, some optional) you should take to complete your
+installation. Note that these steps are taken automatically by the DEB
+and RPM pre-built binary distributions of RStudio Server.
+
+a) Create an rstudio-server system user account (RStudio will automatically
+ run under this account if it is present). You can do this with:
+
+ sudo useradd -r rstudio-server
+
+
+b) RStudio Server uses PAM to authenticate users. Some Unix systems (such
+ as Debian and Ubuntu) use default PAM settings for applications which
+ aren't explicitly registered with PAM, so don't require additional PAM
+ configuration. If however your system requires explicit registration
+ (i.e. Redhat, Fedora, openSUSE) then you need to add an
+ /etc/pam.d/rstudio file to your configuration. You can find a default
+ version of this file at:
+
+ extras
+ /pam
+ rstudio
+
+
+c) Register RStudio as a daemon using an init.d (for most systems) or
+ upstart (for Ubuntu) script appropriate to your system.
+ The rstudio/server/extras directory contains the following scripts:
+
+ extras
+ /init.d
+ /debian
+ rstudio-server
+ /redhat
+ rstudio-server
+ /suse
+ rstudio-server
+ /upstart
+ rstudio-server.conf
+
+ NOTE: installation of init.d scripts require both copying them
+ into /etc/init.d, making them executable (chmod +x), as well as
+ executing a system dependent command to ensure that the service
+ is registered with the appropriate runlevels. For example:
+
+ Debian: sudo update-rc.d rstudio-server defaults
+ Redhat/SUSE: sudo /sbin/chkconfig --add rstudio-server
+
+
+d) Create a soft link in /usr/sbin to the server administrative script
+
+ sudo ln -f -s /usr/local/lib/rstudio-server/bin/rstudio-server /usr/sbin/rstudio-server
+
+ Assuming you have previously installed an init.d or upstart script (as
+ described above) then you should now be able start the server with the
+ following command:
+
+ sudo rstudio-server start
+
+ Additional commands include stop, restart, offline, online, and others
+
+e) Create /var directories required for RStudio to run. This can be done with:
+
+ mkdir -p /var/run/rstudio-server
+ mkdir -p /var/lock/rstudio-server
+ mkdir -p /var/log/rstudio-server
+ mkdir -p /var/lib/rstudio-server
+
+f) If your system supports AppArmor you may wish to add an AppArmor profile
+ for RStudio Server. You can find one which is compatible with the
+ Ubuntu implementation of AppArmor here:
+
+ extras
+ /apparmor
+ rstudio-server
+
+
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..5d1d151
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,3260 @@
+RStudio includes other open source software components. The following
+is a list of these components (full copies of the license agreements
+used by these components are included below):
+
+- Qt (LGPL v2.1)
+- QtSingleApplication
+- Ace (LGPL v2.1)
+- Boost
+- RapidXml
+- JSON Spirit
+- Google Web Toolkit
+- Guice
+- GIN
+- AOP Alliance
+- RSA-JS
+- tree.hh
+- Hunspell (MPL)
+- Chromium Hunspell Dictionaries (MPL)
+- pdf.js
+- SyncTeX
+- ZLib
+- Sundown
+- highlight.js
+- MathJax
+- reveal.js
+- node-webkit
+- JSCustomBadge
+- jQuery
+- Bootstrap
+- Bootswatch
+
+RStudio also includes a binary copy of pandoc. This component is licensed
+to you under the GPLv2, the terms of which are included below. You can
+obtain the source code for pandoc at:
+
+ https://github.com/jgm/pandoc
+
+In addition, RStudio for Windows is bundled with binary copies of GNU
+DiffUtils, GNU Grep, SumatraPDF, and several components of MSYS.
+These components are licensed to you under the GPLv3, the terms of which
+are included below. You can obtain source code for these components at:
+
+ http://ftp.gnu.org/gnu/diffutils/
+ http://ftp.gnu.org/gnu/grep/
+ http://sourceforge.net/projects/mingw/files/MSYS/
+ http://code.google.com/p/sumatrapdf/
+
+In the alternate, you may request a copy of the source code for
+any of these components by e-mail to info at rstudio.com.
+
+
+GNU LGPL v2.1
+----------------------------------------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
+QtSingleApplication License
+----------------------------------------------------------------------
+
+Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+All rights reserved.
+
+Contact: Nokia Corporation (qt-info at nokia.com)
+
+This file is part of a Qt Solutions component.
+
+You may use this file under the terms of the BSD license as follows:
+
+"Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+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
+OWNER 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."
+
+
+Cygwin License
+----------------------------------------------------------------------
+
+--------------------------------------------------------------------------
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License (GPL) as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+--------------------------------------------------------------------------
+
+ *** NOTE ***
+
+In accordance with section 10 of the GPL, Cygnus permits programs whose
+sources are distributed under a license that complies with the Open
+Source definition to be linked with libcygwin.a without libcygwin.a
+itself causing the resulting program to be covered by the GNU GPL.
+
+This means that you can port an Open Source(tm) application to cygwin,
+and distribute that executable as if it didn't include a copy of
+libcygwin.a linked into it. Note that this does not apply to the cygwin
+DLL itself. If you distribute a (possibly modified) version of the DLL
+you must adhere to the terms of the GPL, i.e., you must provide sources
+for the cygwin DLL.
+
+See http://www.opensource.org/osd.html for the precise Open Source
+Definition referenced above.
+
+If you have questions about any of the above or would like to arrange
+for other licensing terms, please contact Cygnus using the information
+given below:
+
+ Cygnus Solutions
+ 1325 Chesapeake Terrace
+ Sunnyvale, CA 94089
+ USA
+
+ +1 408 542 9600
+ hotline: +1 408 542 9601
+ email: info at cygnus.com
+ fax: +1 408 542 9699
+
+
+OpenSSL License
+----------------------------------------------------------------------
+
+ LICENSE ISSUES
+ ==============
+
+ The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+ the OpenSSL License and the original SSLeay license apply to the toolkit.
+ See below for the actual license texts. Actually both licenses are BSD-style
+ Open Source licenses. In case of any license issues related to OpenSSL
+ please contact openssl-core at openssl.org.
+
+ OpenSSL License
+ ---------------
+
+/* ====================================================================
+ * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 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.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * openssl-core at openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ * nor may "OpenSSL" appear in their names without prior written
+ * permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED 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 OpenSSL PROJECT OR
+ * ITS 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.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay at cryptsoft.com). This product includes software written by Tim
+ * Hudson (tjh at cryptsoft.com).
+ *
+ */
+
+ Original SSLeay License
+ -----------------------
+
+/* Copyright (C) 1995-1998 Eric Young (eay at cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay at cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to. The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code. The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh at cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * "This product includes cryptographic software written by
+ * Eric Young (eay at cryptsoft.com)"
+ * The word 'cryptographic' can be left out if the rouines from the library
+ * being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ * the apps directory (application code) you must include an acknowledgement:
+ * "This product includes software written by Tim Hudson (tjh at cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed. i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
+
+ZLib License
+----------------------------------------------------------------------
+
+ Copyright notice:
+
+ (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup at gzip.org madler at alumni.caltech.edu
+
+If you use the zlib library in a product, we would appreciate *not* receiving
+lengthy legal documents to sign. The sources are provided for free but without
+warranty of any kind. The library has been entirely written by Jean-loup
+Gailly and Mark Adler; it does not include third-party code.
+
+If you redistribute modified sources, we would appreciate that you include in
+the file ChangeLog history information documenting your changes. Please read
+the FAQ for more information on the distribution of modified source versions.
+
+
+OpenSSH License
+----------------------------------------------------------------------
+
+This file is part of the OpenSSH software.
+
+The licences which components of this software fall under are as
+follows. First, we will summarize and say that all components
+are under a BSD licence, or a licence more free than that.
+
+OpenSSH contains no GPL code.
+
+1)
+ * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+
+ [Tatu continues]
+ * However, I am not implying to give any licenses to any patents or
+ * copyrights held by third parties, and the software includes parts that
+ * are not under my direct control. As far as I know, all included
+ * source code is used in accordance with the relevant license agreements
+ * and can be used freely for any purpose (the GNU license being the most
+ * restrictive); see below for details.
+
+ [However, none of that term is relevant at this point in time. All of
+ these restrictively licenced software components which he talks about
+ have been removed from OpenSSH, i.e.,
+
+ - RSA is no longer included, found in the OpenSSL library
+ - IDEA is no longer included, its use is deprecated
+ - DES is now external, in the OpenSSL library
+ - GMP is no longer used, and instead we call BN code from OpenSSL
+ - Zlib is now external, in a library
+ - The make-ssh-known-hosts script is no longer included
+ - TSS has been removed
+ - MD5 is now external, in the OpenSSL library
+ - RC4 support has been replaced with ARC4 support from OpenSSL
+ - Blowfish is now external, in the OpenSSL library
+
+ [The licence continues]
+
+ Note that any information and cryptographic algorithms used in this
+ software are publicly available on the Internet and at any major
+ bookstore, scientific library, and patent office worldwide. More
+ information can be found e.g. at "http://www.cs.hut.fi/crypto".
+
+ The legal status of this program is some combination of all these
+ permissions and restrictions. Use only at your own responsibility.
+ You will be responsible for any legal consequences yourself; I am not
+ making any claims whether possessing or using this is legal or not in
+ your country, and I am not taking any responsibility on your behalf.
+
+
+ NO WARRANTY
+
+ BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+ REPAIR OR CORRECTION.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGES.
+
+2)
+ The 32-bit CRC compensation attack detector in deattack.c was
+ contributed by CORE SDI S.A. under a BSD-style license.
+
+ * Cryptographic attack detector for ssh - source code
+ *
+ * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina.
+ *
+ * All rights reserved. Redistribution and use in source and binary
+ * forms, with or without modification, are permitted provided that
+ * this copyright notice is retained.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR
+ * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS
+ * SOFTWARE.
+ *
+ * Ariel Futoransky <futo at core-sdi.com>
+ * <http://www.core-sdi.com>
+
+3)
+ ssh-keyscan was contributed by David Mazieres under a BSD-style
+ license.
+
+ * Copyright 1995, 1996 by David Mazieres <dm at lcs.mit.edu>.
+ *
+ * Modification and redistribution in source and binary forms is
+ * permitted provided that due credit is given to the author and the
+ * OpenBSD project by leaving this copyright notice intact.
+
+4)
+ The Rijndael implementation by Vincent Rijmen, Antoon Bosselaers
+ and Paulo Barreto is in the public domain and distributed
+ with the following license:
+
+ * @version 3.0 (December 2000)
+ *
+ * Optimised ANSI C code for the Rijndael cipher (now AES)
+ *
+ * @author Vincent Rijmen <vincent.rijmen at esat.kuleuven.ac.be>
+ * @author Antoon Bosselaers <antoon.bosselaers at esat.kuleuven.ac.be>
+ * @author Paulo Barreto <paulo.barreto at terra.com.br>
+ *
+ * This code is hereby placed in the public domain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''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 AUTHORS 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.
+
+5)
+ One component of the ssh source code is under a 3-clause BSD license,
+ held by the University of California, since we pulled these parts from
+ original Berkeley code.
+
+ * Copyright (c) 1983, 1990, 1992, 1993, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+
+6)
+ Remaining components of the software are provided under a standard
+ 2-term BSD licence with the following names as copyright holders:
+
+ Markus Friedl
+ Theo de Raadt
+ Niels Provos
+ Dug Song
+ Aaron Campbell
+ Damien Miller
+ Kevin Steves
+ Daniel Kouril
+ Wesley Griffin
+ Per Allansson
+ Nils Nordman
+ Simon Wilkinson
+
+ Portable OpenSSH additionally includes code from the following copyright
+ holders, also under the 2-term BSD license:
+
+ Ben Lindstrom
+ Tim Rice
+ Andre Lucas
+ Chris Adams
+ Corinna Vinschen
+ Cray Inc.
+ Denis Parker
+ Gert Doering
+ Jakob Schlyter
+ Jason Downs
+ Juha Yrjölä
+ Michael Stone
+ Networks Associates Technology, Inc.
+ Solar Designer
+ Todd C. Miller
+ Wayne Schroeder
+ William Jones
+ Darren Tucker
+ Sun Microsystems
+ The SCO Group
+ Daniel Walsh
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+
+8) Portable OpenSSH contains the following additional licenses:
+
+ a) md5crypt.c, md5crypt.h
+
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk at login.dknet.dk> wrote this file. As long as you retain this
+ * notice you can do whatever you want with this stuff. If we meet
+ * some day, and you think this stuff is worth it, you can buy me a
+ * beer in return. Poul-Henning Kamp
+
+ b) snprintf replacement
+
+ * Copyright Patrick Powell 1995
+ * This code is based on code written by Patrick Powell
+ * (papowell at astart.com) It may be used for any purpose as long as this
+ * notice remains intact on all source code distributions
+
+ c) Compatibility code (openbsd-compat)
+
+ Apart from the previously mentioned licenses, various pieces of code
+ in the openbsd-compat/ subdirectory are licensed as follows:
+
+ Some code is licensed under a 3-term BSD license, to the following
+ copyright holders:
+
+ Todd C. Miller
+ Theo de Raadt
+ Damien Miller
+ Eric P. Allman
+ The Regents of the University of California
+ Constantin S. Svintsoff
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+
+ Some code is licensed under an ISC-style license, to the following
+ copyright holders:
+
+ Internet Software Consortium.
+ Todd C. Miller
+ Reyk Floeter
+ Chad Mynhier
+
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
+ * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ Some code is licensed under a MIT-style license to the following
+ copyright holders:
+
+ Free Software Foundation, Inc.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a *
+ * copy of this software and associated documentation files (the *
+ * "Software"), to deal in the Software without restriction, including *
+ * without limitation the rights to use, copy, modify, merge, publish, *
+ * distribute, distribute with modifications, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included *
+ * in all copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
+ * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
+ * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
+ * *
+ * Except as contained in this notice, the name(s) of the above copyright *
+ * holders shall not be used in advertising or otherwise to promote the *
+ * sale, use or other dealings in this Software without prior written *
+ * authorization. *
+ ****************************************************************************/
+
+
+Boost Software License
+----------------------------------------------------------------------
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+RapidXml License
+----------------------------------------------------------------------
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Copyright (c) 2006, 2007 Marcin Kalicinski
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+JSON Spirit License
+----------------------------------------------------------------------
+
+The MIT License
+
+Copyright (c) 2007 - 2009 John W. Wilkinson
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+Apache License v2
+This product includes software developed by The Apache Software
+Foundation (http://www.apache.org/).
+----------------------------------------------------------------------
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+RSA-JS License
+----------------------------------------------------------------------
+
+Copyright (c) 2003-2005 Tom Wu
+All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+In addition, the following condition applies:
+
+All redistributions must retain an intact copy of this copyright notice
+and disclaimer.
+
+Address all questions regarding this license to:
+
+Tom Wu
+tjw at cs.Stanford.EDU
+
+
+GNU GPL v2
+----------------------------------------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
+
+GNU GPL v3.0
+----------------------------------------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
+
+CPackRPM.cmake License
+----------------------------------------------------------------------
+
+CMake - Cross Platform Makefile Generator
+Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* 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.
+
+* Neither the names of Kitware, Inc., the Insight Software Consortium,
+ nor the names of their contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+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.
+
+
+MOZILLA PUBLIC LICENSE - Version 1.1
+----------------------------------------------------------------------
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in
+ compliance with the License. You may obtain a copy of the License at
+ http://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
+
+
+pdf.js License
+----------------------------------------------------------------------
+
+Copyright (c) 2011 Mozilla Foundation
+
+Contributors: Andreas Gal <gal at mozilla.com>
+ Chris G Jones <cjones at mozilla.com>
+ Shaon Barman <shaon.barman at gmail.com>
+ Vivien Nicolas <21 at vingtetun.org>
+ Justin D'Arcangelo <justindarc at gmail.com>
+ Yury Delendik
+ Kalervo Kujala
+ Adil Allawi <@ironymark>
+ Jakob Miland <saebekassebil at gmail.com>
+ Artur Adib <aadib at mozilla.com>
+ Brendan Dahl <bdahl at mozilla.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+SyncTeX License
+----------------------------------------------------------------------
+
+Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr
+
+This file is part of the SyncTeX package.
+
+License:
+--------
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE
+
+Except as contained in this notice, the name of the copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization from the copyright holder.
+
+
+ZLib License
+----------------------------------------------------------------------
+
+(C) 1995-2012 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup at gzip.org madler at alumni.caltech.edu
+
+
+Sundown License
+----------------------------------------------------------------------
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+highlight.js
+----------------------------------------------------------------------
+
+Copyright (c) 2006, Ivan Sagalaev
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of highlight.js nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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.
+
+
+reveal.js
+----------------------------------------------------------------------
+
+Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+node-webkit
+----------------------------------------------------------------------
+
+Copyright (c) 2012 Intel Corp
+Copyright (c) 2012 The Chromium Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in th
+e Software without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished to do so, subj
+ect to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all c
+opies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
+ED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
+A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYR
+IGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WIT
+H THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+JSCustomBadge
+----------------------------------------------------------------------
+
+Original work by Sascha Marc Paulus
+Copyright (c) 2011
+https://github.com/ckteebe/CustomBadge
+http://www.spaulus.com
+
+MIT License
+Copyright (c) 2013 Jesse Squires
+
+http://www.hexedbits.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+jQuery
+----------------------------------------------------------------------
+
+Copyright (c) 2012 jQuery Foundation and other contributors,
+http://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..39cb7b7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+RStudio
+=============================================================================
+
+RStudio is an integrated development environment (IDE) for the
+[R programming language](http://www.r-project.org). Some of its
+features include:
+
+- Customizable workbench with all of the tools required to work with R in one
+place (console, source, plots, workspace, help, history, etc.).
+- Syntax highlighting editor with code completion.
+- Execute code directly from the source editor (line, selection, or file).
+- Full support for authoring Sweave and TeX documents.
+- Runs on all major platforms (Windows, Mac, and Linux) and can also be
+run as a server, enabling multiple users to access the RStudio IDE using
+a web browser.
+
+For more information on RStudio please visit the
+[project website](http://www.rstudio.com/).
+
+Getting the Code
+-----------------------------------------------------------------------------
+
+RStudio is licensed under the AGPLv3, the terms of which are included in
+the file COPYING. You can find our source code repository on GitHub at [https://github.com/rstudio/rstudio](https://github.com/rstudio/rstudio).
+
+Documentation
+-----------------------------------------------------------------------------
+
+For information on how to use RStudio check out our
+[online documentation](http://www.rstudio.com/ide/docs/).
+
+For documentation on running your own RStudio Server see the
+[server getting started](http://www.rstudio.com/ide/docs/server/getting_started)
+guide.
+
+See also the following files included with the distribution:
+
+- COPYING - RStudio license (AGPLv3)
+- NOTICE - Additional open source software included with RStudio
+- SOURCE - How to obtain the source code for RStudio
+- INSTALL - How to build and install RStudio from source
+
+If you have problems or want to share feedback with us please visit our
+[support website](http://support.rstudio.org/). For other inquiries you can
+also email us at [info at rstudio.com](mailto:info at rstudio.com).
diff --git a/SOURCE.in b/SOURCE.in
new file mode 100644
index 0000000..0b36217
--- /dev/null
+++ b/SOURCE.in
@@ -0,0 +1,14 @@
+
+Unless you have received this program directly from RStudio pursuant to
+the terms of a commercial license agreement with RStudio, then RStudio is
+licensed to you under the AGPLv3, the terms of which are included in
+the file COPYING.
+
+You can obtain the source code for this version of RStudio at:
+
+ https://github.com/rstudio/rstudio/tree/${RSTUDIO_GIT_REVISION_HASH}
+
+You can obtain the source code for the latest version of RStudio at:
+
+ https://github.com/rstudio/rstudio/
+
diff --git a/VERSION.in b/VERSION.in
new file mode 100644
index 0000000..22648cb
--- /dev/null
+++ b/VERSION.in
@@ -0,0 +1 @@
+${CPACK_PACKAGE_VERSION}
diff --git a/cmake/modules/CPackRPM.cmake b/cmake/modules/CPackRPM.cmake
new file mode 100644
index 0000000..e2d7880
--- /dev/null
+++ b/cmake/modules/CPackRPM.cmake
@@ -0,0 +1,625 @@
+# - The builtin (binary) CPack RPM generator (Unix only)
+# CPackRPM may be used to create RPM package using CPack.
+# CPackRPM is a CPack generator thus it uses the CPACK_XXX variables
+# used by CPack : http://www.cmake.org/Wiki/CMake:CPackConfiguration
+#
+# However CPackRPM has specific features which are controlled by
+# the specifics CPACK_RPM_XXX variables. You'll find a detailed usage on
+# the wiki:
+# http://www.cmake.org/Wiki/CMake:CPackPackageGenerators#RPM_.28Unix_Only.29
+# However as a handy reminder here comes the list of specific variables:
+#
+# CPACK_RPM_PACKAGE_SUMMARY
+# Mandatory : YES
+# Default : CPACK_PACKAGE_DESCRIPTION_SUMMARY
+# The RPM package summary
+# CPACK_RPM_PACKAGE_NAME
+# Mandatory : YES
+# Default : CPACK_PACKAGE_NAME
+# The RPM package name
+# CPACK_RPM_PACKAGE_VERSION
+# Mandatory : YES
+# Default : CPACK_PACKAGE_VERSION
+# The RPM package version
+# CPACK_RPM_PACKAGE_ARCHITECTURE
+# Mandatory : NO
+# Default : -
+# The RPM package architecture. This may be set to "noarch" if you
+# know you are building a noarch package.
+# CPACK_RPM_PACKAGE_RELEASE
+# Mandatory : YES
+# Default : 1
+# The RPM package release. This is the numbering of the RPM package
+# itself, i.e. the version of the packaging and not the version of the
+# content (see CPACK_RPM_PACKAGE_VERSION). One may change the default
+# value if the previous packaging was buggy and/or you want to put here
+# a fancy Linux distro specific numbering.
+# CPACK_RPM_PACKAGE_LICENSE
+# Mandatory : YES
+# Default : "unknown"
+# The RPM package license policy.
+# CPACK_RPM_PACKAGE_GROUP
+# Mandatory : YES
+# Default : "unknown"
+# The RPM package group.
+# CPACK_RPM_PACKAGE_VENDOR
+# Mandatory : YES
+# Default : CPACK_PACKAGE_VENDOR if set or "unknown"
+# The RPM package vendor.
+# CPACK_RPM_PACKAGE_URL
+# Mandatory : NO
+# Default : -
+# The projects URL.
+# CPACK_RPM_PACKAGE_DESCRIPTION
+# Mandatory : YES
+# Default : CPACK_PACKAGE_DESCRIPTION_FILE if set or "no package description available"
+# CPACK_RPM_COMPRESSION_TYPE
+# Mandatory : NO
+# Default : -
+# May be used to override RPM compression type to be used
+# to build the RPM. For example some Linux distribution now default
+# to lzma or xz compression whereas older cannot use such RPM.
+# Using this one can enforce compression type to be used.
+# Possible value are: lzma, xz, bzip2 and gzip.
+# CPACK_RPM_PACKAGE_REQUIRES
+# Mandatory : NO
+# Default : -
+# May be used to set RPM dependencies (requires).
+# Note that you must enclose the complete requires string between quotes,
+# for example:
+# set(CPACK_RPM_PACKAGE_REQUIRES "python >= 2.5.0, cmake >= 2.8")
+# CPACK_RPM_PACKAGE_SUGGESTS
+# Mandatory : NO
+# Default : -
+# May be used to set weak RPM dependencies (suggests).
+# Note that you must enclose the complete requires string between quotes.
+# CPACK_RPM_PACKAGE_PROVIDES
+# Mandatory : NO
+# Default : -
+# May be used to set RPM dependencies (provides).
+# CPACK_RPM_PACKAGE_OBSOLETES
+# Mandatory : NO
+# Default : -
+# May be used to set RPM packages that are obsoleted by this one.
+# CPACK_RPM_PACKAGE_RELOCATABLE
+# Mandatory : NO
+# Default : CPACK_PACKAGE_RELOCATABLE
+# If this variable is set to TRUE or ON CPackRPM will try
+# to build a relocatable RPM package. A relocatable RPM may
+# be installed using rpm --prefix or --relocate in order to
+# install it at an alternate place see rpm(8).
+# Note that currently this may fail if CPACK_SET_DESTDIR is set to ON.
+# If CPACK_SET_DESTDIR is set then you will get a warning message
+# but if there is file installed with absolute path you'll get
+# unexpected behavior.
+# CPACK_RPM_SPEC_INSTALL_POST
+# Mandatory : NO
+# Default : -
+# May be used to set an RPM post-install command inside the spec file.
+# For example setting it to "/bin/true" may be used to prevent
+# rpmbuild to strip binaries.
+# CPACK_RPM_SPEC_MORE_DEFINE
+# Mandatory : NO
+# Default : -
+# May be used to add any %define lines to the generated spec file.
+# CPACK_RPM_PACKAGE_DEBUG
+# Mandatory : NO
+# Default : -
+# May be set when invoking cpack in order to trace debug information
+# during CPack RPM run. For example you may launch CPack like this
+# cpack -D CPACK_RPM_PACKAGE_DEBUG=1 -G RPM
+# CPACK_RPM_USER_BINARY_SPECFILE
+# Mandatory : NO
+# Default : -
+# May be set by the user in order to specify a USER binary spec file
+# to be used by CPackRPM instead of generating the file.
+# The specified file will be processed by CONFIGURE_FILE( @ONLY).
+# CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE
+# Mandatory : NO
+# Default : -
+# If set CPack will generate a template for USER specified binary
+# spec file and stop with an error. For example launch CPack like this
+# cpack -D CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE=1 -G RPM
+# The user may then use this file in order to hand-craft is own
+# binary spec file which may be used with CPACK_RPM_USER_BINARY_SPECFILE.
+# CPACK_RPM_PRE_INSTALL_SCRIPT_FILE
+# CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE
+# Mandatory : NO
+# Default : -
+# May be used to embbed a pre (un)installation script in the spec file.
+# The refered script file(s) will be read and directly
+# put after the %pre or %preun section
+# One may verify which scriptlet has been included with
+# rpm -qp --scripts package.rpm
+# CPACK_RPM_POST_INSTALL_SCRIPT_FILE
+# CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE
+# Mandatory : NO
+# Default : -
+# May be used to embbed a post (un)installation script in the spec file.
+# The refered script file(s) will be read and directly
+# put after the %post or %postun section
+# One may verify which scriptlet has been included with
+# rpm -qp --scripts package.rpm
+# CPACK_RPM_CHANGELOG_FILE
+# Mandatory : NO
+# Default : -
+# May be used to embbed a changelog in the spec file.
+# The refered file will be read and directly put after the %changelog
+# section.
+
+#=============================================================================
+# Copyright 2007-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+# Author: Eric Noulard with the help of Alexander Neundorf.
+
+IF(CMAKE_BINARY_DIR)
+ MESSAGE(FATAL_ERROR "CPackRPM.cmake may only be used by CPack internally.")
+ENDIF(CMAKE_BINARY_DIR)
+
+IF(NOT UNIX)
+ MESSAGE(FATAL_ERROR "CPackRPM.cmake may only be used under UNIX.")
+ENDIF(NOT UNIX)
+
+# rpmbuild is the basic command for building RPM package
+# it may be a simple (symbolic) link to rpm command.
+FIND_PROGRAM(RPMBUILD_EXECUTABLE rpmbuild)
+
+# Check version of the rpmbuild tool this would be easier to
+# track bugs with users and CPackRPM debug mode.
+# We may use RPM version in order to check for available version dependent features
+IF(RPMBUILD_EXECUTABLE)
+ execute_process(COMMAND ${RPMBUILD_EXECUTABLE} --version
+ OUTPUT_VARIABLE _TMP_VERSION
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ string(REGEX REPLACE "^.*\ " ""
+ RPMBUILD_EXECUTABLE_VERSION
+ ${_TMP_VERSION})
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: rpmbuild version is <${RPMBUILD_EXECUTABLE_VERSION}>")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ENDIF(RPMBUILD_EXECUTABLE)
+
+IF(NOT RPMBUILD_EXECUTABLE)
+ MESSAGE(FATAL_ERROR "RPM package requires rpmbuild executable")
+ENDIF(NOT RPMBUILD_EXECUTABLE)
+
+# We may use RPM version in the future in order
+# to shut down warning about space in buildtree
+# some recent RPM version should support space in different places.
+# not checked [yet].
+IF(CPACK_TOPLEVEL_DIRECTORY MATCHES ".* .*")
+ MESSAGE(FATAL_ERROR "${RPMBUILD_EXECUTABLE} can't handle paths with spaces, use a build directory without spaces for building RPMs.")
+ENDIF(CPACK_TOPLEVEL_DIRECTORY MATCHES ".* .*")
+
+# If rpmbuild is found
+# we try to discover alien since we may be on non RPM distro like Debian.
+# In this case we may try to to use more advanced features
+# like generating RPM directly from DEB using alien.
+# FIXME feature not finished (yet)
+FIND_PROGRAM(ALIEN_EXECUTABLE alien)
+IF(ALIEN_EXECUTABLE)
+ MESSAGE(STATUS "alien found, we may be on a Debian based distro.")
+ENDIF(ALIEN_EXECUTABLE)
+
+#
+# Use user-defined RPM specific variables value
+# or generate reasonable default value from
+# CPACK_xxx generic values.
+# The variables comes from the needed (mandatory or not)
+# values found in the RPM specification file aka ".spec" file.
+# The variables which may/should be defined are:
+#
+
+# CPACK_RPM_PACKAGE_SUMMARY (mandatory)
+IF(NOT CPACK_RPM_PACKAGE_SUMMARY)
+ # if neither var is defined lets use the name as summary
+ IF(NOT CPACK_PACKAGE_DESCRIPTION_SUMMARY)
+ STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_RPM_PACKAGE_SUMMARY)
+ ELSE(NOT CPACK_PACKAGE_DESCRIPTION_SUMMARY)
+ SET(CPACK_RPM_PACKAGE_SUMMARY ${CPACK_PACKAGE_DESCRIPTION_SUMMARY})
+ ENDIF(NOT CPACK_PACKAGE_DESCRIPTION_SUMMARY)
+ENDIF(NOT CPACK_RPM_PACKAGE_SUMMARY)
+
+# CPACK_RPM_PACKAGE_NAME (mandatory)
+IF(NOT CPACK_RPM_PACKAGE_NAME)
+ STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_RPM_PACKAGE_NAME)
+ENDIF(NOT CPACK_RPM_PACKAGE_NAME)
+
+# CPACK_RPM_PACKAGE_VERSION (mandatory)
+IF(NOT CPACK_RPM_PACKAGE_VERSION)
+ IF(NOT CPACK_PACKAGE_VERSION)
+ MESSAGE(FATAL_ERROR "RPM package requires a package version")
+ ENDIF(NOT CPACK_PACKAGE_VERSION)
+ SET(CPACK_RPM_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION})
+ENDIF(NOT CPACK_RPM_PACKAGE_VERSION)
+# Replace '-' in version with '_'
+# '-' character is an Illegal RPM version character
+# it is illegal because it is used to separate
+# RPM "Version" from RPM "Release"
+STRING(REPLACE "-" "_" CPACK_RPM_PACKAGE_VERSION ${CPACK_RPM_PACKAGE_VERSION})
+
+# CPACK_RPM_PACKAGE_ARCHITECTURE (optional)
+IF(CPACK_RPM_PACKAGE_ARCHITECTURE)
+ SET(TMP_RPM_BUILDARCH "Buildarch: ${CPACK_RPM_PACKAGE_ARCHITECTURE}")
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: using user-specified build arch = ${CPACK_RPM_PACKAGE_ARCHITECTURE}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ELSE(CPACK_RPM_PACKAGE_ARCHITECTURE)
+ SET(TMP_RPM_BUILDARCH "")
+ENDIF(CPACK_RPM_PACKAGE_ARCHITECTURE)
+
+# CPACK_RPM_PACKAGE_RELEASE
+# The RPM release is the numbering of the RPM package ITSELF
+# this is the version of the PACKAGING and NOT the version
+# of the CONTENT of the package.
+# You may well need to generate a new RPM package release
+# without changing the version of the packaged software.
+# This is the case when the packaging is buggy (not) the software :=)
+# If not set, 1 is a good candidate
+IF(NOT CPACK_RPM_PACKAGE_RELEASE)
+ SET(CPACK_RPM_PACKAGE_RELEASE 1)
+ENDIF(NOT CPACK_RPM_PACKAGE_RELEASE)
+
+# CPACK_RPM_PACKAGE_LICENSE
+IF(NOT CPACK_RPM_PACKAGE_LICENSE)
+ SET(CPACK_RPM_PACKAGE_LICENSE "unknown")
+ENDIF(NOT CPACK_RPM_PACKAGE_LICENSE)
+
+# CPACK_RPM_PACKAGE_GROUP
+IF(NOT CPACK_RPM_PACKAGE_GROUP)
+ SET(CPACK_RPM_PACKAGE_GROUP "unknown")
+ENDIF(NOT CPACK_RPM_PACKAGE_GROUP)
+
+# CPACK_RPM_PACKAGE_VENDOR
+IF(NOT CPACK_RPM_PACKAGE_VENDOR)
+ IF(CPACK_PACKAGE_VENDOR)
+ SET(CPACK_RPM_PACKAGE_VENDOR "${CPACK_PACKAGE_VENDOR}")
+ ELSE(CPACK_PACKAGE_VENDOR)
+ SET(CPACK_RPM_PACKAGE_VENDOR "unknown")
+ ENDIF(CPACK_PACKAGE_VENDOR)
+ENDIF(NOT CPACK_RPM_PACKAGE_VENDOR)
+
+# CPACK_RPM_PACKAGE_SOURCE
+# The name of the source tarball in case we generate a source RPM
+
+# CPACK_RPM_PACKAGE_DESCRIPTION
+# The variable content may be either
+# - explicitly given by the user or
+# - filled with the content of CPACK_PACKAGE_DESCRIPTION_FILE
+# if it is defined
+# - set to a default value
+#
+IF (NOT CPACK_RPM_PACKAGE_DESCRIPTION)
+ IF (CPACK_PACKAGE_DESCRIPTION_FILE)
+ FILE(READ ${CPACK_PACKAGE_DESCRIPTION_FILE} CPACK_RPM_PACKAGE_DESCRIPTION)
+ ELSE (CPACK_PACKAGE_DESCRIPTION_FILE)
+ SET(CPACK_RPM_PACKAGE_DESCRIPTION "no package description available")
+ ENDIF (CPACK_PACKAGE_DESCRIPTION_FILE)
+ENDIF (NOT CPACK_RPM_PACKAGE_DESCRIPTION)
+
+# CPACK_RPM_COMPRESSION_TYPE
+#
+IF (CPACK_RPM_COMPRESSION_TYPE)
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: User Specified RPM compression type: ${CPACK_RPM_COMPRESSION_TYPE}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ IF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "lzma")
+ SET(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.lzdio")
+ ENDIF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "lzma")
+ IF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "xz")
+ SET(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w7.xzdio")
+ ENDIF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "xz")
+ IF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "bzip2")
+ SET(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.bzdio")
+ ENDIF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "bzip2")
+ IF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "gzip")
+ SET(CPACK_RPM_COMPRESSION_TYPE_TMP "%define _binary_payload w9.gzdio")
+ ENDIF(CPACK_RPM_COMPRESSION_TYPE STREQUAL "gzip")
+ELSE(CPACK_RPM_COMPRESSION_TYPE)
+ SET(CPACK_RPM_COMPRESSION_TYPE_TMP "")
+ENDIF(CPACK_RPM_COMPRESSION_TYPE)
+
+if(CPACK_PACKAGE_RELOCATABLE)
+ set(CPACK_RPM_PACKAGE_RELOCATABLE TRUE)
+endif(CPACK_PACKAGE_RELOCATABLE)
+if(CPACK_RPM_PACKAGE_RELOCATABLE)
+ if(CPACK_RPM_PACKAGE_DEBUG)
+ message("CPackRPM:Debug: Trying to build a relocatable package")
+ endif(CPACK_RPM_PACKAGE_DEBUG)
+ if(CPACK_SET_DESTDIR AND (NOT CPACK_SET_DESTDIR STREQUAL "I_ON"))
+ message("CPackRPM:Warning: CPACK_SET_DESTDIR is set (=${CPACK_SET_DESTDIR}) while requesting a relocatable package (CPACK_RPM_PACKAGE_RELOCATABLE is set): this is not supported, the package won't be relocatable.")
+ else(CPACK_SET_DESTDIR AND (NOT CPACK_SET_DESTDIR STREQUAL "I_ON"))
+ set(CPACK_RPM_PACKAGE_PREFIX ${CPACK_PACKAGING_INSTALL_PREFIX})
+ endif(CPACK_SET_DESTDIR AND (NOT CPACK_SET_DESTDIR STREQUAL "I_ON"))
+endif(CPACK_RPM_PACKAGE_RELOCATABLE)
+
+# check if additional fields for RPM spec header are given
+FOREACH(_RPM_SPEC_HEADER URL REQUIRES SUGGESTS PROVIDES OBSOLETES PREFIX)
+ IF(CPACK_RPM_PACKAGE_${_RPM_SPEC_HEADER})
+ STRING(LENGTH ${_RPM_SPEC_HEADER} _PACKAGE_HEADER_STRLENGTH)
+ MATH(EXPR _PACKAGE_HEADER_STRLENGTH "${_PACKAGE_HEADER_STRLENGTH} - 1")
+ STRING(SUBSTRING ${_RPM_SPEC_HEADER} 1 ${_PACKAGE_HEADER_STRLENGTH} _PACKAGE_HEADER_TAIL)
+ STRING(TOLOWER "${_PACKAGE_HEADER_TAIL}" _PACKAGE_HEADER_TAIL)
+ STRING(SUBSTRING ${_RPM_SPEC_HEADER} 0 1 _PACKAGE_HEADER_NAME)
+ SET(_PACKAGE_HEADER_NAME "${_PACKAGE_HEADER_NAME}${_PACKAGE_HEADER_TAIL}")
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: User defined ${_PACKAGE_HEADER_NAME}:\n ${CPACK_RPM_PACKAGE_${_RPM_SPEC_HEADER}}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ SET(TMP_RPM_${_RPM_SPEC_HEADER} "${_PACKAGE_HEADER_NAME}: ${CPACK_RPM_PACKAGE_${_RPM_SPEC_HEADER}}")
+ ENDIF(CPACK_RPM_PACKAGE_${_RPM_SPEC_HEADER})
+ENDFOREACH(_RPM_SPEC_HEADER)
+
+# CPACK_RPM_SPEC_INSTALL_POST
+# May be used to define a RPM post intallation script
+# for example setting it to "/bin/true" may prevent
+# rpmbuild from stripping binaries.
+IF(CPACK_RPM_SPEC_INSTALL_POST)
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: User defined CPACK_RPM_SPEC_INSTALL_POST = ${CPACK_RPM_SPEC_INSTALL_POST}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ SET(TMP_RPM_SPEC_INSTALL_POST "%define __spec_install_post ${CPACK_RPM_SPEC_INSTALL_POST}")
+ENDIF(CPACK_RPM_SPEC_INSTALL_POST)
+
+# CPACK_RPM_POST_INSTALL_SCRIPT_FILE
+# CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE
+# May be used to embbed a post (un)installation script in the spec file.
+# The refered script file(s) will be read and directly
+# put after the %post or %postun section
+if(CPACK_RPM_POST_INSTALL_SCRIPT_FILE)
+ if(EXISTS ${CPACK_RPM_POST_INSTALL_SCRIPT_FILE})
+ file(READ ${CPACK_RPM_POST_INSTALL_SCRIPT_FILE} CPACK_RPM_SPEC_POSTINSTALL)
+ else(EXISTS ${CPACK_RPM_POST_INSTALL_SCRIPT_FILE})
+ message("CPackRPM:Warning: CPACK_RPM_POST_INSTALL_SCRIPT_FILE <${CPACK_RPM_POST_INSTALL_SCRIPT_FILE}> does not exists - ignoring")
+ endif(EXISTS ${CPACK_RPM_POST_INSTALL_SCRIPT_FILE})
+endif(CPACK_RPM_POST_INSTALL_SCRIPT_FILE)
+
+if(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE)
+ if(EXISTS ${CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE})
+ file(READ ${CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE} CPACK_RPM_SPEC_POSTUNINSTALL)
+ else(EXISTS ${CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE})
+ message("CPackRPM:Warning: CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE <${CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE}> does not exists - ignoring")
+ endif(EXISTS ${CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE})
+endif(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE)
+
+# CPACK_RPM_PRE_INSTALL_SCRIPT_FILE
+# CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE
+# May be used to embed a pre (un)installation script in the spec file.
+# The refered script file(s) will be read and directly
+# put after the %pre or %preun section
+if(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE)
+ if(EXISTS ${CPACK_RPM_PRE_INSTALL_SCRIPT_FILE})
+ file(READ ${CPACK_RPM_PRE_INSTALL_SCRIPT_FILE} CPACK_RPM_SPEC_PREINSTALL)
+ else(EXISTS ${CPACK_RPM_PRE_INSTALL_SCRIPT_FILE})
+ message("CPackRPM:Warning: CPACK_RPM_PRE_INSTALL_SCRIPT_FILE <${CPACK_RPM_PRE_INSTALL_SCRIPT_FILE}> does not exists - ignoring")
+ endif(EXISTS ${CPACK_RPM_PRE_INSTALL_SCRIPT_FILE})
+endif(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE)
+
+if(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE)
+ if(EXISTS ${CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE})
+ file(READ ${CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE} CPACK_RPM_SPEC_PREUNINSTALL)
+ else(EXISTS ${CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE})
+ message("CPackRPM:Warning: CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE <${CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE}> does not exists - ignoring")
+ endif(EXISTS ${CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE})
+endif(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE)
+
+# CPACK_RPM_CHANGELOG_FILE
+# May be used to embed a changelog in the spec file.
+# The refered file will be read and directly put after the %changelog section
+if(CPACK_RPM_CHANGELOG_FILE)
+ if(EXISTS ${CPACK_RPM_CHANGELOG_FILE})
+ file(READ ${CPACK_RPM_CHANGELOG_FILE} CPACK_RPM_SPEC_CHANGELOG)
+ else(EXISTS ${CPACK_RPM_CHANGELOG_FILE})
+ message(SEND_ERROR "CPackRPM:Warning: CPACK_RPM_CHANGELOG_FILE <${CPACK_RPM_CHANGELOG_FILE}> does not exists - ignoring")
+ endif(EXISTS ${CPACK_RPM_CHANGELOG_FILE})
+else(CPACK_RPM_CHANGELOG_FILE)
+ set(CPACK_RPM_SPEC_CHANGELOG "* Sun Jul 4 2010 Erk <eric.noulard at gmail.com>\n Generated by CPack RPM (no Changelog file were provided)")
+endif(CPACK_RPM_CHANGELOG_FILE)
+
+# CPACK_RPM_SPEC_MORE_DEFINE
+# This is a generated spec rpm file spaceholder
+IF(CPACK_RPM_SPEC_MORE_DEFINE)
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: User defined more define spec line specified:\n ${CPACK_RPM_SPEC_MORE_DEFINE}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ENDIF(CPACK_RPM_SPEC_MORE_DEFINE)
+
+# Now we may create the RPM build tree structure
+SET(CPACK_RPM_ROOTDIR "${CPACK_TOPLEVEL_DIRECTORY}")
+MESSAGE(STATUS "CPackRPM:Debug: Using CPACK_RPM_ROOTDIR=${CPACK_RPM_ROOTDIR}")
+# Prepare RPM build tree
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR})
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/tmp)
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/BUILD)
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/RPMS)
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/SOURCES)
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/SPECS)
+FILE(MAKE_DIRECTORY ${CPACK_RPM_ROOTDIR}/SRPMS)
+
+#SET(CPACK_RPM_FILE_NAME "${CPACK_RPM_PACKAGE_NAME}-${CPACK_RPM_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}-${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm")
+SET(CPACK_RPM_FILE_NAME "${CPACK_OUTPUT_FILE_NAME}")
+# it seems rpmbuild can't handle spaces in the path
+# neither escaping (as below) nor putting quotes around the path seem to help
+#STRING(REGEX REPLACE " " "\\\\ " CPACK_RPM_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}")
+SET(CPACK_RPM_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}")
+
+# Use files tree to construct files command (spec file)
+# We should not forget to include symlinks (thus -o -type l)
+# We must remove the './' due to the local search and escape the
+# file name by enclosing it between double quotes (thus the sed)
+# Then we must authorize any man pages extension (adding * at the end)
+# because rpmbuild may automatically compress those files
+EXECUTE_PROCESS(COMMAND find -type f -o -type l
+ COMMAND sed {s:.*/man.*/.*:&*:}
+ COMMAND sed {s/\\.\\\(.*\\\)/\"\\1\"/}
+ WORKING_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}"
+ OUTPUT_VARIABLE CPACK_RPM_INSTALL_FILES)
+
+if (CPACK_ABSOLUTE_DESTINATION_FILES)
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ message("CPackRPM:Debug: Handling Absolute Destination Files: ${CPACK_ABSOLUTE_DESTINATION_FILES}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ # Remove trailing space
+ string(STRIP "${CPACK_RPM_INSTALL_FILES}" CPACK_RPM_INSTALL_FILES_LIST)
+ # Transform endline separated - string into CMake List
+ string(REPLACE "\n" ";" CPACK_RPM_INSTALL_FILES_LIST "${CPACK_RPM_INSTALL_FILES_LIST}")
+ # Remove unecessary quotes
+ string(REPLACE "\"" "" CPACK_RPM_INSTALL_FILES_LIST "${CPACK_RPM_INSTALL_FILES_LIST}")
+ # Remove ABSOLUTE install file from INSTALL FILE LIST
+ list(REMOVE_ITEM CPACK_RPM_INSTALL_FILES_LIST ${CPACK_ABSOLUTE_DESTINATION_FILES})
+ # Rebuild INSTALL_FILES
+ set(CPACK_RPM_INSTALL_FILES "")
+ foreach(F IN LISTS CPACK_RPM_INSTALL_FILES_LIST)
+ set(CPACK_RPM_INSTALL_FILES "${CPACK_RPM_INSTALL_FILES}\"${F}\"\n")
+ endforeach(F)
+ # Build ABSOLUTE_INSTALL_FILES
+ set(CPACK_RPM_ABSOLUTE_INSTALL_FILES "")
+ foreach(F IN LISTS CPACK_ABSOLUTE_DESTINATION_FILES)
+ set(CPACK_RPM_ABSOLUTE_INSTALL_FILES "${CPACK_RPM_ABSOLUTE_INSTALL_FILES}%config \"${F}\"\n")
+ endforeach(F)
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ message("CPackRPM:Debug: CPACK_RPM_ABSOLUTE_INSTALL_FILES=${CPACK_RPM_ABSOLUTE_INSTALL_FILES}")
+ message("CPackRPM:Debug: CPACK_RPM_INSTALL_FILES=${CPACK_RPM_INSTALL_FILES}")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+endif(CPACK_ABSOLUTE_DESTINATION_FILES)
+
+# The name of the final spec file to be used by rpmbuild
+SET(CPACK_RPM_BINARY_SPECFILE "${CPACK_RPM_ROOTDIR}/SPECS/${CPACK_RPM_PACKAGE_NAME}.spec")
+
+# Print out some debug information if we were asked for that
+IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: CPACK_TOPLEVEL_DIRECTORY = ${CPACK_TOPLEVEL_DIRECTORY}")
+ MESSAGE("CPackRPM:Debug: CPACK_TOPLEVEL_TAG = ${CPACK_TOPLEVEL_TAG}")
+ MESSAGE("CPackRPM:Debug: CPACK_TEMPORARY_DIRECTORY = ${CPACK_TEMPORARY_DIRECTORY}")
+ MESSAGE("CPackRPM:Debug: CPACK_OUTPUT_FILE_NAME = ${CPACK_OUTPUT_FILE_NAME}")
+ MESSAGE("CPackRPM:Debug: CPACK_OUTPUT_FILE_PATH = ${CPACK_OUTPUT_FILE_PATH}")
+ MESSAGE("CPackRPM:Debug: CPACK_PACKAGE_FILE_NAME = ${CPACK_PACKAGE_FILE_NAME}")
+ MESSAGE("CPackRPM:Debug: CPACK_RPM_BINARY_SPECFILE = ${CPACK_RPM_BINARY_SPECFILE}")
+ MESSAGE("CPackRPM:Debug: CPACK_PACKAGE_INSTALL_DIRECTORY = ${CPACK_PACKAGE_INSTALL_DIRECTORY}")
+ MESSAGE("CPackRPM:Debug: CPACK_TEMPORARY_PACKAGE_FILE_NAME = ${CPACK_TEMPORARY_PACKAGE_FILE_NAME}")
+ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+
+# USER generated spec file handling.
+# We should generate a spec file template:
+# - either because the user asked for it : CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE
+# - or the user did not provide one : NOT CPACK_RPM_USER_BINARY_SPECFILE
+#
+IF(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE OR NOT CPACK_RPM_USER_BINARY_SPECFILE)
+ FILE(WRITE ${CPACK_RPM_BINARY_SPECFILE}.in
+ "# -*- rpm-spec -*-
+BuildRoot: \@CPACK_RPM_DIRECTORY\@/\@CPACK_PACKAGE_FILE_NAME\@
+Summary: \@CPACK_RPM_PACKAGE_SUMMARY\@
+Name: \@CPACK_RPM_PACKAGE_NAME\@
+Version: \@CPACK_RPM_PACKAGE_VERSION\@
+Release: \@CPACK_RPM_PACKAGE_RELEASE\@
+License: \@CPACK_RPM_PACKAGE_LICENSE\@
+Group: \@CPACK_RPM_PACKAGE_GROUP\@
+Vendor: \@CPACK_RPM_PACKAGE_VENDOR\@
+\@TMP_RPM_URL\@
+\@TMP_RPM_REQUIRES\@
+\@TMP_RPM_PROVIDES\@
+\@TMP_RPM_OBSOLETES\@
+\@TMP_RPM_BUILDARCH\@
+\@TMP_RPM_PREFIX\@
+
+%define _rpmdir \@CPACK_RPM_DIRECTORY\@
+%define _rpmfilename \@CPACK_RPM_FILE_NAME\@
+%define _unpackaged_files_terminate_build 0
+%define _topdir \@CPACK_RPM_DIRECTORY\@
+\@TMP_RPM_SPEC_INSTALL_POST\@
+\@CPACK_RPM_SPEC_MORE_DEFINE\@
+\@CPACK_RPM_COMPRESSION_TYPE_TMP\@
+
+%description
+\@CPACK_RPM_PACKAGE_DESCRIPTION\@
+
+# This is a shortcutted spec file generated by CMake RPM generator
+# we skip _install step because CPack does that for us.
+# We do only save CPack installed tree in _prepr
+# and then restore it in build.
+%prep
+mv $RPM_BUILD_ROOT \"\@CPACK_TOPLEVEL_DIRECTORY\@/tmpBBroot\"
+
+#p build
+
+%install
+if [ -e $RPM_BUILD_ROOT ];
+then
+ mv \"\@CPACK_TOPLEVEL_DIRECTORY\@/tmpBBroot/*\" $RPM_BUILD_ROOT
+else
+ mv \"\@CPACK_TOPLEVEL_DIRECTORY\@/tmpBBroot\" $RPM_BUILD_ROOT
+fi
+
+%clean
+
+%post
+\@CPACK_RPM_SPEC_POSTINSTALL\@
+
+%postun
+\@CPACK_RPM_SPEC_POSTUNINSTALL\@
+
+%pre
+\@CPACK_RPM_SPEC_PREINSTALL\@
+
+%preun
+\@CPACK_RPM_SPEC_PREUNINSTALL\@
+
+%files
+%defattr(-,root,root,-)
+${CPACK_RPM_INSTALL_FILES}
+${CPACK_RPM_ABSOLUTE_INSTALL_FILES}
+
+%changelog
+\@CPACK_RPM_SPEC_CHANGELOG\@
+")
+ # Stop here if we were asked to only generate a template USER spec file
+ # The generated file may then be used as a template by user who wants
+ # to customize their own spec file.
+ IF(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE)
+ MESSAGE(FATAL_ERROR "CPackRPM: STOP here Generated USER binary spec file templare is: ${CPACK_RPM_BINARY_SPECFILE}.in")
+ ENDIF(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE)
+ENDIF(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE OR NOT CPACK_RPM_USER_BINARY_SPECFILE)
+
+# After that we may either use a user provided spec file
+# or generate one using appropriate variables value.
+IF(CPACK_RPM_USER_BINARY_SPECFILE)
+ # User may have specified SPECFILE just use it
+ MESSAGE("CPackRPM: Will use USER specified spec file: ${CPACK_RPM_USER_BINARY_SPECFILE}")
+ # The user provided file is processed for @var replacement
+ CONFIGURE_FILE(${CPACK_RPM_USER_BINARY_SPECFILE} ${CPACK_RPM_BINARY_SPECFILE} @ONLY)
+ELSE(CPACK_RPM_USER_BINARY_SPECFILE)
+ # No User specified spec file, will use the generated spec file
+ MESSAGE("CPackRPM: Will use GENERATED spec file: ${CPACK_RPM_BINARY_SPECFILE}")
+ # Note the just created file is processed for @var replacement
+ CONFIGURE_FILE(${CPACK_RPM_BINARY_SPECFILE}.in ${CPACK_RPM_BINARY_SPECFILE} @ONLY)
+ENDIF(CPACK_RPM_USER_BINARY_SPECFILE)
+
+IF(RPMBUILD_EXECUTABLE)
+ # Now call rpmbuild using the SPECFILE
+ EXECUTE_PROCESS(
+ COMMAND "${RPMBUILD_EXECUTABLE}" -bb
+ --buildroot "${CPACK_RPM_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}"
+ "${CPACK_RPM_BINARY_SPECFILE}"
+ WORKING_DIRECTORY "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}"
+ ERROR_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild.err"
+ OUTPUT_FILE "${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild.out")
+ IF(CPACK_RPM_PACKAGE_DEBUG)
+ MESSAGE("CPackRPM:Debug: You may consult rpmbuild logs in: ")
+ MESSAGE("CPackRPM:Debug: - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild.err")
+ MESSAGE("CPackRPM:Debug: - ${CPACK_TOPLEVEL_DIRECTORY}/rpmbuild.out")
+ ENDIF(CPACK_RPM_PACKAGE_DEBUG)
+ELSE(RPMBUILD_EXECUTABLE)
+ IF(ALIEN_EXECUTABLE)
+ MESSAGE(FATAL_ERROR "RPM packaging through alien not done (yet)")
+ ENDIF(ALIEN_EXECUTABLE)
+ENDIF(RPMBUILD_EXECUTABLE)
diff --git a/cmake/modules/FindLibR.cmake b/cmake/modules/FindLibR.cmake
new file mode 100644
index 0000000..5255f56
--- /dev/null
+++ b/cmake/modules/FindLibR.cmake
@@ -0,0 +1,183 @@
+#
+# FindLibR.cmake
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# LIBR_FOUND
+# LIBR_HOME
+# LIBR_INCLUDE_DIRS
+# LIBR_DOC_DIR
+# LIBR_LIBRARIES
+
+# detection for OSX (look for R framework)
+if(APPLE)
+
+ find_library(LIBR_LIBRARIES R)
+ if(LIBR_LIBRARIES)
+ set(LIBR_HOME "${LIBR_LIBRARIES}/Resources" CACHE PATH "R home directory")
+ set(LIBR_INCLUDE_DIRS "${LIBR_HOME}/include" CACHE PATH "R include directory")
+ set(LIBR_DOC_DIR "${LIBR_HOME}/doc" CACHE PATH "R doc directory")
+ set(LIBR_EXECUTABLE "${LIBR_HOME}/R" CACHE PATH "R executable")
+ endif()
+
+# detection for UNIX & Win32
+else()
+
+ # Find R executable and paths (UNIX)
+ if(UNIX)
+
+ # find executable
+ find_program(LIBR_EXECUTABLE R)
+ if(LIBR_EXECUTABLE-NOTFOUND)
+ message(STATUS "Unable to locate R executable")
+ endif()
+
+ # ask R for the home path
+ if(NOT LIBR_HOME)
+ execute_process(
+ COMMAND ${LIBR_EXECUTABLE} "--slave" "--vanilla" "-e" "cat(R.home())"
+ OUTPUT_VARIABLE LIBR_HOME
+ )
+ if(LIBR_HOME)
+ set(LIBR_HOME ${LIBR_HOME} CACHE PATH "R home directory")
+ endif()
+ endif()
+
+ # ask R for the include dir
+ if(NOT LIBR_INCLUDE_DIRS)
+ execute_process(
+ COMMAND ${LIBR_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home('include'))"
+ OUTPUT_VARIABLE LIBR_INCLUDE_DIRS
+ )
+ if(LIBR_INCLUDE_DIRS)
+ set(LIBR_INCLUDE_DIRS ${LIBR_INCLUDE_DIRS} CACHE PATH "R include directory")
+ endif()
+ endif()
+
+ # ask R for the doc dir
+ if(NOT LIBR_DOC_DIR)
+ execute_process(
+ COMMAND ${LIBR_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home('doc'))"
+ OUTPUT_VARIABLE LIBR_DOC_DIR
+ )
+ if(LIBR_DOC_DIR)
+ set(LIBR_DOC_DIR ${LIBR_DOC_DIR} CACHE PATH "R doc directory")
+ endif()
+ endif()
+
+ # ask R for the lib dir
+ if(NOT LIBR_LIB_DIR)
+ execute_process(
+ COMMAND ${LIBR_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home('lib'))"
+ OUTPUT_VARIABLE LIBR_LIB_DIR
+ )
+ endif()
+
+ # Find R executable and paths (Win32)
+ else()
+
+ # find the home path
+ if(NOT LIBR_HOME)
+
+ # read home from the registry
+ get_filename_component(LIBR_HOME
+ "[HKEY_LOCAL_MACHINE\\SOFTWARE\\R-core\\R;InstallPath]"
+ ABSOLUTE CACHE)
+
+ # print message if not found
+ if(NOT LIBR_HOME)
+ message(STATUS "Unable to locate R home (not written to registry)")
+ endif()
+
+ endif()
+
+ # set other R paths based on home path
+ set(LIBR_INCLUDE_DIRS "${LIBR_HOME}/include" CACHE PATH "R include directory")
+ set(LIBR_DOC_DIR "${LIBR_HOME}/doc" CACHE PATH "R doc directory")
+
+ # set library hint path based on whether we are doing a special session 64 build
+ if(LIBR_FIND_WINDOWS_64BIT)
+ set(LIBRARY_ARCH_HINT_PATH "${LIBR_HOME}/bin/x64")
+ else()
+ set(LIBRARY_ARCH_HINT_PATH "${LIBR_HOME}/bin/i386")
+ endif()
+
+ endif()
+
+ # look for the R executable
+ find_program(LIBR_EXECUTABLE R
+ HINTS ${LIBRARY_ARCH_HINT_PATH} ${LIBR_HOME}/bin)
+ if(LIBR_EXECUTABLE-NOTFOUND)
+ message(STATUS "Unable to locate R executable")
+ endif()
+
+ # look for the core R library
+ find_library(LIBR_CORE_LIBRARY NAMES R
+ HINTS ${LIBR_LIB_DIR} ${LIBRARY_ARCH_HINT_PATH} ${LIBR_HOME}/bin)
+ if(LIBR_CORE_LIBRARY)
+ set(LIBR_LIBRARIES ${LIBR_CORE_LIBRARY})
+ else()
+ message(STATUS "Could not find libR shared library.")
+ endif()
+
+ # look for lapack
+ find_library(LIBR_LAPACK_LIBRARY NAMES Rlapack
+ HINTS ${LIBR_LIB_DIR} ${LIBRARY_ARCH_HINT_PATH} ${LIBR_HOME}/bin)
+ if(LIBR_LAPACK_LIBRARY)
+ set(LIBR_LIBRARIES ${LIBR_LIBRARIES} ${LIBR_LAPACK_LIBRARY})
+ if(UNIX)
+ set(LIBR_LIBRARIES ${LIBR_LIBRARIES} gfortran)
+ endif()
+ endif()
+
+ # look for blas
+ find_library(LIBR_BLAS_LIBRARY NAMES Rblas
+ HINTS ${LIBR_LIB_DIR} ${LIBRARY_ARCH_HINT_PATH} ${LIBR_HOME}/bin)
+ if(LIBR_BLAS_LIBRARY)
+ set(LIBR_LIBRARIES ${LIBR_LIBRARIES} ${LIBR_BLAS_LIBRARY})
+ endif()
+
+ # look for rgraphapp on win32
+ if(WIN32)
+ find_library(LIBR_GRAPHAPP_LIBRARY NAMES Rgraphapp
+ HINTS ${LIBR_LIB_DIR} ${LIBRARY_ARCH_HINT_PATH} ${LIBR_HOME}/bin)
+ if(LIBR_GRAPHAPP_LIBRARY)
+ set(LIBR_LIBRARIES ${LIBR_LIBRARIES} ${LIBR_GRAPHAPP_LIBRARY})
+ endif()
+ endif()
+
+ # cache LIBR_LIBRARIES
+ if(LIBR_LIBRARIES)
+ set(LIBR_LIBRARIES ${LIBR_LIBRARIES} CACHE PATH "R runtime libraries")
+ endif()
+
+endif()
+
+# define find requirements
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(LibR DEFAULT_MSG
+ LIBR_HOME
+ LIBR_EXECUTABLE
+ LIBR_INCLUDE_DIRS
+ LIBR_LIBRARIES
+ LIBR_DOC_DIR
+)
+
+if(LIBR_FOUND)
+ message(STATUS "Found R: ${LIBR_HOME}")
+endif()
+
+# mark low-level variables from FIND_* calls as advanced
+mark_as_advanced(
+ LIBR_CORE_LIBRARY
+ LIBR_LAPACK_LIBRARY
+ LIBR_BLAS_LIBRARY
+)
diff --git a/cmake/modules/FindPAM.cmake b/cmake/modules/FindPAM.cmake
new file mode 100644
index 0000000..8088027
--- /dev/null
+++ b/cmake/modules/FindPAM.cmake
@@ -0,0 +1,77 @@
+# From http://code.google.com/p/pam-face-authentication/source/browse/branches/pam_face_authentication/cmake/modules/FindPAM.cmake?r=336
+
+# - Try to find the PAM libraries
+# Once done this will define
+#
+# PAM_FOUND - system has pam
+# PAM_INCLUDE_DIR - the pam include directory
+# PAM_LIBRARIES - libpam library
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ # Already in cache, be silent
+ set(PAM_FIND_QUIETLY TRUE)
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h pam/pam_appl.h)
+find_library(PAM_LIBRARY pam)
+find_library(DL_LIBRARY dl)
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ set(PAM_FOUND TRUE)
+ if (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY} ${DL_LIBRARY})
+ else (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY})
+ endif (DL_LIBRARY)
+
+ if (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+ # darwin claims to be something special
+ set(HAVE_PAM_PAM_APPL_H 1)
+ endif (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+
+ if (NOT DEFINED PAM_MESSAGE_CONST)
+ include(CheckCXXSourceCompiles)
+ # XXX does this work with plain c?
+ check_cxx_source_compiles("
+#if ${HAVE_PAM_PAM_APPL_H}+0
+# include <pam/pam_appl.h>
+#else
+# include <security/pam_appl.h>
+#endif
+
+static int PAM_conv(
+ int num_msg,
+ const struct pam_message **msg, /* this is the culprit */
+ struct pam_response **resp,
+ void *ctx)
+{
+ return 0;
+}
+
+int main(void)
+{
+ struct pam_conv PAM_conversation = {
+ &PAM_conv, /* this bombs out if the above does not match */
+ 0
+ };
+
+ return 0;
+}
+" PAM_MESSAGE_CONST)
+ endif (NOT DEFINED PAM_MESSAGE_CONST)
+ set(PAM_MESSAGE_CONST ${PAM_MESSAGE_CONST} CACHE BOOL "PAM expects a conversation function with const pam_message")
+
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+if (PAM_FOUND)
+ if (NOT PAM_FIND_QUIETLY)
+ message(STATUS "Found PAM: ${PAM_LIBRARIES}")
+ endif (NOT PAM_FIND_QUIETLY)
+else (PAM_FOUND)
+ if (PAM_FIND_REQUIRED)
+ message(FATAL_ERROR "PAM was not found")
+ endif(PAM_FIND_REQUIRED)
+endif (PAM_FOUND)
+
+mark_as_advanced(PAM_INCLUDE_DIR PAM_LIBRARY DL_LIBRARY PAM_MESSAGE_CONST)
+
diff --git a/package/.gitignore b/package/.gitignore
new file mode 100644
index 0000000..f433c3b
--- /dev/null
+++ b/package/.gitignore
@@ -0,0 +1,3 @@
+build
+
+
diff --git a/package/CMakeLists.txt b/package/CMakeLists.txt
new file mode 100644
index 0000000..bcd7e30
--- /dev/null
+++ b/package/CMakeLists.txt
@@ -0,0 +1,23 @@
+
+
+# shared cpack variables
+set(CPACK_PACKAGE_NAME "RStudio")
+set(CPACK_PACKAGE_DESCRIPTION "RStudio")
+set(CPACK_PACKAGE_VENDOR "RStudio")
+set(CPACK_PACKAGE_CONTACT "RStudio <info at rstudio.com>")
+set(CPACK_PACKAGE_INSTALL_DIRECTORY "RStudio")
+
+if(WIN32)
+
+ add_subdirectory(win32)
+
+elseif(APPLE)
+
+ add_subdirectory(osx)
+
+elseif(UNIX)
+
+ add_subdirectory(linux)
+
+endif()
+
diff --git a/package/linux/.gitignore b/package/linux/.gitignore
new file mode 100644
index 0000000..364932e
--- /dev/null
+++ b/package/linux/.gitignore
@@ -0,0 +1,2 @@
+iterate*
+
diff --git a/package/linux/CMakeLists.txt b/package/linux/CMakeLists.txt
new file mode 100644
index 0000000..cf85f87
--- /dev/null
+++ b/package/linux/CMakeLists.txt
@@ -0,0 +1,115 @@
+
+# configure cpack install location
+set(CPACK_SET_DESTDIR "ON")
+set(CPACK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+
+# detect architecture (packaging platform specific)
+find_program(DPKG_EXECUTABLE dpkg)
+find_program(RPM_EXECUTABLE rpm)
+if (NOT PACKAGE_ARCHITECTURE)
+ if(DPKG_EXECUTABLE)
+ exec_program(dpkg ARGS --print-architecture
+ OUTPUT_VARIABLE PACKAGE_ARCHITECTURE)
+ elseif(RPM_EXECUTABLE)
+ exec_program(arch OUTPUT_VARIABLE PACKAGE_ARCHITECTURE)
+ endif()
+endif()
+
+# configuration specific
+if(RSTUDIO_SERVER)
+
+ # package name and description
+ set(CPACK_PACKAGE_NAME "rstudio-server")
+ set(CPACK_PACKAGE_DESCRIPTION "RStudio Server")
+
+ # debian control files
+ set(DEBIAN_POSTINST postinst.in)
+ set(DEBIAN_POSTRM postrm.in)
+
+ # rpm scripts
+ set(RPM_POSTINST postinst.sh.in)
+ set(RPM_POSTRM postrm.sh.in)
+
+ # deiban dependencies -- to install the .deb from the command line with
+ # automatic dependency resolution use e.g.
+ # sudo apt-get install gdebi-core
+ # sudo gdebi rstudio-server-0.97.151-amd64.deb
+ set(RSTUDIO_DEBIAN_DEPENDS "psmisc, libssl0.9.8, ")
+
+ # rpm dependencies
+ set(RSTUDIO_RPM_DEPENDS "psmisc, libffi, ")
+
+elseif(RSTUDIO_DESKTOP)
+
+ # debian control files
+ set(DEBIAN_POSTINST postinst-desktop.in)
+ set(DEBIAN_POSTRM postrm-desktop.in)
+
+ # rpm scripts
+ set(RPM_POSTINST postinst-desktop.sh.in)
+ set(RPM_POSTRM postrm-desktop.sh.in)
+
+ # depend on libjpeg62 (for Qt 4.8 jpeg plugin)
+ set(RSTUDIO_DEBIAN_DEPENDS "libjpeg62, ")
+
+endif()
+
+# define package suffix
+set(RSTUDIO_PACKAGE_SUFFIX "-")
+
+# include overlay if it exists
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+endif()
+
+# dynamically configured debian control scripts
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/debian-control/${DEBIAN_POSTINST}
+ ${CMAKE_CURRENT_BINARY_DIR}/debian-control/postinst)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/debian-control/${DEBIAN_POSTRM}
+ ${CMAKE_CURRENT_BINARY_DIR}/debian-control/postrm)
+
+set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_BINARY_DIR}/debian-control/postinst;${CMAKE_CURRENT_BINARY_DIR}/debian-control/postrm")
+
+# dynamically configured rpm scripts (only works with cmake 2.8.1 or higher).
+# alternatively you can get CPackRPM.cmake from the cmake tip and copy it into
+# your local cmake modules directory -- this is what we currently do
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rpm-script/${RPM_POSTINST}
+ ${CMAKE_CURRENT_BINARY_DIR}/rpm-script/postinst.sh)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rpm-script/${RPM_POSTRM}
+ ${CMAKE_CURRENT_BINARY_DIR}/rpm-script/postrm.sh)
+
+set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/rpm-script/postinst.sh")
+set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/rpm-script/postrm.sh")
+
+
+# package file name
+set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}${RSTUDIO_PACKAGE_SUFFIX}${CPACK_PACKAGE_VERSION}-${PACKAGE_ARCHITECTURE}")
+if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Release")
+ set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${CMAKE_BUILD_TYPE}")
+endif()
+string(TOLOWER "${CPACK_PACKAGE_FILE_NAME}" CPACK_PACKAGE_FILE_NAME)
+
+# variables to be re-used in package description fields
+set(PACKAGE_LONG_DESCRIPTION "RStudio is a set of integrated tools designed to help you be more productive with R. It includes a console, syntax-highlighting editor that supports direct code execution, as well as tools for plotting, history, and workspace management.")
+
+# debian-specific
+set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}\n ${PACKAGE_LONG_DESCRIPTION}")
+set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${PACKAGE_ARCHITECTURE}")
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "${RSTUDIO_DEBIAN_DEPENDS} libc6 (>= 2.7)")
+set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "r-base (>= 2.11.1)")
+
+# rpm-specific
+set(CPACK_RPM_PACKAGE_SUMMARY "${CPACK_PACKAGE_NAME}")
+set(CPACK_RPM_PACKAGE_DESCRIPTION "${PACKAGE_LONG_DESCRIPTION}")
+set(CPACK_RPM_PACKAGE_LICENSE "AGPL v.3.0")
+set(CPACK_RPM_PACKAGE_GROUP "Development/Tools")
+set(CPACK_RPM_PACKAGE_ARCHITECTURE "${PACKAGE_ARCHITECTURE}")
+set(CPACK_RPM_PACKAGE_REQUIRES "${RSTUDIO_RPM_DEPENDS}")
+
+# build package
+include(CPack)
+
+
+
+
+
diff --git a/package/linux/debian-control/postinst-desktop.in b/package/linux/debian-control/postinst-desktop.in
new file mode 100755
index 0000000..47d3405
--- /dev/null
+++ b/package/linux/debian-control/postinst-desktop.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# create softlink to rstudio /usr/bin
+sudo ln -f -s ${CMAKE_INSTALL_PREFIX}/bin/rstudio /usr/bin/rstudio
+
+# clear error termination state
+set -e
diff --git a/package/linux/debian-control/postinst.in b/package/linux/debian-control/postinst.in
new file mode 100755
index 0000000..2f41394
--- /dev/null
+++ b/package/linux/debian-control/postinst.in
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# add rserver user account
+sudo useradd -r rstudio-server
+sudo groupadd -r rstudio-server
+
+# create softlink to admin script in /usr/sbin
+sudo ln -f -s ${CMAKE_INSTALL_PREFIX}/bin/rstudio-server /usr/sbin/rstudio-server
+
+# create config directory
+sudo mkdir -p /etc/rstudio
+
+# create var directories
+sudo mkdir -p /var/run/rstudio-server
+sudo mkdir -p /var/lock/rstudio-server
+sudo mkdir -p /var/log/rstudio-server
+sudo mkdir -p /var/lib/rstudio-server
+sudo mkdir -p /var/lib/rstudio-server/conf
+sudo mkdir -p /var/lib/rstudio-server/body
+sudo mkdir -p /var/lib/rstudio-server/proxy
+
+# write installed indicator then force suspend active sessions
+# (so that the new client is never paired against the old server)
+sudo sh -c "echo `date +%s` > /var/lib/rstudio-server/installed"
+sudo rstudio-server force-suspend-all
+
+# check lsb release
+LSB_RELEASE=`lsb_release --id --short`
+
+# add apparmor profile
+if test $LSB_RELEASE = "Ubuntu" && test -d /etc/apparmor.d/
+then
+ sudo cp ${CMAKE_INSTALL_PREFIX}/extras/apparmor/rstudio-server /etc/apparmor.d/
+ sudo apparmor_parser -r /etc/apparmor.d/rstudio-server 2>/dev/null
+fi
+
+# add upstart profile or init.d script and start the server
+if test $LSB_RELEASE = "Ubuntu" && test -d /etc/init/
+then
+ sudo cp ${CMAKE_INSTALL_PREFIX}/extras/upstart/rstudio-server.conf /etc/init/
+ sudo initctl reload-configuration
+ sudo initctl stop rstudio-server 2>/dev/null
+ sudo initctl start rstudio-server
+else
+ sudo cp ${CMAKE_INSTALL_PREFIX}/extras/init.d/debian/rstudio-server /etc/init.d/
+ sudo chmod +x /etc/init.d/rstudio-server
+ sudo update-rc.d rstudio-server defaults
+ sudo /etc/init.d/rstudio-server stop 2>/dev/null
+ sudo /etc/init.d/rstudio-server start
+fi
+
+# clear error termination state
+set -e
diff --git a/package/linux/debian-control/postrm-desktop.in b/package/linux/debian-control/postrm-desktop.in
new file mode 100755
index 0000000..900f6ec
--- /dev/null
+++ b/package/linux/debian-control/postrm-desktop.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# remove softlink to rstudio
+sudo rm -f /usr/bin/rstudio
+
+# clear error termination state
+set -e
diff --git a/package/linux/debian-control/postrm.in b/package/linux/debian-control/postrm.in
new file mode 100755
index 0000000..61d8772
--- /dev/null
+++ b/package/linux/debian-control/postrm.in
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# remove softlink to admin script
+sudo rm -f /usr/sbin/rstudio-server
+
+# remove temporary streams
+sudo rm -rf /tmp/rstudio-rsession
+sudo rm -rf /tmp/rstudio-rserver
+
+# clear error termination state
+set -e
diff --git a/package/linux/install-dependencies b/package/linux/install-dependencies
new file mode 100755
index 0000000..ca719f9
--- /dev/null
+++ b/package/linux/install-dependencies
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+
+CMAKE_VERSION=cmake-2.8.11.2
+CMAKE_TARBALL=$CMAKE_VERSION.tar.gz
+wget http://www.cmake.org/files/v2.8/$CMAKE_TARBALL
+tar xf $CMAKE_TARBALL
+cd $CMAKE_VERSION
+./bootstrap
+make
+sudo make install
+cd ..
+rm -rf $CMAKE_VERSION
+rm -f $CMAKE_TARBALL
+
diff --git a/package/linux/make-desktop-package b/package/linux/make-desktop-package
new file mode 100755
index 0000000..296e95b
--- /dev/null
+++ b/package/linux/make-desktop-package
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+
+# make standard package
+./make-package Desktop $1 $2
+
+# make tar.gz
+BUILD_DIR=build-Desktop-$1
+if ! test -z "$CMAKE_BUILD_TYPE"
+then
+ BUILD_DIR=$BUILD_DIR-$CMAKE_BUILD_TYPE
+fi
+
+# switch to build dir and determine name of package root
+CURRENT_DIR=`pwd`
+PACKAGE_DIR=$CURRENT_DIR/$BUILD_DIR/_CPack_Packages/Linux/$1
+cd $PACKAGE_DIR
+PACKAGE_ROOT=`ls -d rstudio-*/`
+PACKAGE_ROOT=${PACKAGE_ROOT%/}
+
+# tarball name
+if [ "$1" == "DEB" ]
+then
+ PLATFORM=debian
+else
+ PLATFORM=fedora
+fi
+PACKAGE_TAR=$PACKAGE_DIR/$PACKAGE_ROOT-$PLATFORM.tar.gz
+
+# copy rstudio dir to local (versioned) dir and make tarball
+VERSIONED_RSTUDIO=rstudio-${RSTUDIO_VERSION_MAJOR}.${RSTUDIO_VERSION_MINOR}.${RSTUDIO_VERSION_PATCH}
+rm -rf ${VERSIONED_RSTUDIO}
+cp -R ${PACKAGE_ROOT}/usr/lib/rstudio ${VERSIONED_RSTUDIO}
+
+# create tarball
+tar -c -z -f $PACKAGE_TAR ${VERSIONED_RSTUDIO}
+
+# back to current dir
+cd $CURRENT_DIR
+
+
+
+
+
diff --git a/package/linux/make-package b/package/linux/make-package
new file mode 100755
index 0000000..1e94e87
--- /dev/null
+++ b/package/linux/make-package
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+set -e
+
+PACKAGE_DIR=`pwd`
+
+if [ "$1" != "Desktop" ] && [ "$1" != "Server" ]
+then
+ echo "error: must specify Desktop or Server as configuration"
+ exit 1
+fi
+
+if [ "$2" != "DEB" ] && [ "$2" != "RPM" ]
+then
+ echo "error: must specify DEB or RPM as package target"
+ exit 1
+fi
+
+# set build type( if necessary) and build dir
+if test -z "$CMAKE_BUILD_TYPE"
+then
+ CMAKE_BUILD_TYPE=Release
+ BUILD_DIR=build-$1-$2
+else
+ BUILD_DIR=build-$1-$2-$CMAKE_BUILD_TYPE
+fi
+
+# clean if requested
+if [ "$3" == "clean" ]
+then
+ # remove existing build dir
+ rm -rf $BUILD_DIR
+
+ # clean out ant build
+ cd ../../src/gwt
+ ant clean
+ cd $PACKAGE_DIR
+fi
+
+
+if [ "$1" == "Desktop" ]
+then
+ INSTALL_DIR=rstudio
+else
+ INSTALL_DIR=rstudio-server
+fi
+
+mkdir -p $BUILD_DIR
+cd $BUILD_DIR
+rm -f CMakeCache.txt
+rm -rf $BUILD_DIR/_CPack_Packages
+cmake -DRSTUDIO_TARGET=$1 \
+ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
+ -DRSTUDIO_PACKAGE_BUILD=1 \
+ -DCMAKE_INSTALL_PREFIX=/usr/lib/$INSTALL_DIR \
+ ../../..
+
+make
+
+if [ "$2" != "DEB" ]
+then
+ fakeroot cpack -G $2
+else
+ cpack -G $2
+fi
+
+cd ..
diff --git a/package/linux/make-server-package b/package/linux/make-server-package
new file mode 100755
index 0000000..060a6cb
--- /dev/null
+++ b/package/linux/make-server-package
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+./make-package Server $1 $2
+
+
diff --git a/package/linux/rpm-script/postinst-desktop.sh.in b/package/linux/rpm-script/postinst-desktop.sh.in
new file mode 100755
index 0000000..dc3c7fe
--- /dev/null
+++ b/package/linux/rpm-script/postinst-desktop.sh.in
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# create softlink to rstudio in /usr/bin
+ln -f -s ${CMAKE_INSTALL_PREFIX}/bin/rstudio /usr/bin/rstudio
+
+# update mime database
+update-mime-database /usr/share/mime
+
+# clear error termination state
+set -e
diff --git a/package/linux/rpm-script/postinst.sh.in b/package/linux/rpm-script/postinst.sh.in
new file mode 100755
index 0000000..3fe5cc1
--- /dev/null
+++ b/package/linux/rpm-script/postinst.sh.in
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# add rserver user account
+useradd -r rstudio-server
+groupadd -r rstudio-server
+
+# create softlink to admin script in /usr/sbin
+ln -f -s ${CMAKE_INSTALL_PREFIX}/bin/rstudio-server /usr/sbin/rstudio-server
+
+# create config directory
+mkdir -p /etc/rstudio
+
+# create var directories
+mkdir -p /var/run/rstudio-server
+mkdir -p /var/lock/rstudio-server
+mkdir -p /var/log/rstudio-server
+mkdir -p /var/lib/rstudio-server
+mkdir -p /var/lib/rstudio-server/conf
+mkdir -p /var/lib/rstudio-server/body
+mkdir -p /var/lib/rstudio-server/proxy
+
+# write installed indicator then force suspend active sessions
+# (so that the new client is never paired against the old server)
+sh -c "echo `date +%s` > /var/lib/rstudio-server/installed"
+rstudio-server force-suspend-all
+
+# add upstart profile or init.d script and start the server
+if test -d /etc/init/
+then
+ # remove any previously existing init.d based scheme
+ service rstudio-server stop 2>/dev/null
+ rm -f /etc/init.d/rstudio-server
+
+ cp ${CMAKE_INSTALL_PREFIX}/extras/upstart/rstudio-server.redhat.conf /etc/init/rstudio-server.conf
+ initctl reload-configuration
+ initctl stop rstudio-server 2>/dev/null
+ initctl start rstudio-server
+else
+ cp ${CMAKE_INSTALL_PREFIX}/extras/init.d/redhat/rstudio-server /etc/init.d/
+ chmod +x /etc/init.d/rstudio-server
+ chkconfig --add rstudio-server
+ service rstudio-server stop 2>/dev/null
+ service rstudio-server start
+fi
+
+# add pam profile
+if [ ! -e /etc/pam.d/rstudio ]
+then
+ cp ${CMAKE_INSTALL_PREFIX}/extras/pam/rstudio /etc/pam.d/
+fi
+
+# clear error termination state
+set -e
diff --git a/package/linux/rpm-script/postrm-desktop.sh.in b/package/linux/rpm-script/postrm-desktop.sh.in
new file mode 100755
index 0000000..4c66058
--- /dev/null
+++ b/package/linux/rpm-script/postrm-desktop.sh.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# remove softlink to rstudio
+rm -f /usr/bin/rstudio
+
+# clear error termination state
+set -e
diff --git a/package/linux/rpm-script/postrm.sh.in b/package/linux/rpm-script/postrm.sh.in
new file mode 100755
index 0000000..45dc204
--- /dev/null
+++ b/package/linux/rpm-script/postrm.sh.in
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+# remove softlink to admin script
+rm -f /usr/sbin/rstudio-server
+
+# remove temporary streams
+# remove temporary streams
+sudo rm -rf /tmp/rstudio-rsession
+sudo rm -rf /tmp/rstudio-rserver
+
+# clear error termination state
+set -e
diff --git a/package/osx/CMakeLists.txt b/package/osx/CMakeLists.txt
new file mode 100644
index 0000000..a8a5193
--- /dev/null
+++ b/package/osx/CMakeLists.txt
@@ -0,0 +1,28 @@
+
+# developer-id code signing
+if (RSTUDIO_PACKAGE_BUILD)
+ INSTALL(CODE "
+ list (APPEND CODESIGN_TARGETS \"\${CMAKE_INSTALL_PREFIX}/RStudio.app\")
+
+ file(GLOB_RECURSE CODESIGN_PLUGINS \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/plugins\")
+ list (APPEND CODESIGN_TARGETS \${CODESIGN_PLUGINS})
+
+ file(GLOB_RECURSE CODESIGN_FRAMEWORKS \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/Frameworks\")
+ list (APPEND CODESIGN_TARGETS \${CODESIGN_FRAMEWORKS})
+
+ file(GLOB_RECURSE CODESIGN_MACOS \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/MacOS\")
+ list (APPEND CODESIGN_TARGETS \${CODESIGN_MACOS})
+
+ foreach(CODESIGN_TARGET \${CODESIGN_TARGETS})
+ execute_process(COMMAND codesign \"-s\" \"Developer ID Application: RStudio Inc.\" \"-i\" \"org.rstudio.RStudio\" \"\${CODESIGN_TARGET}\")
+ endforeach()
+ ")
+endif()
+
+# package attributes
+set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
+
+# create drag and drop installer
+set(CPACK_BINARY_DRAGNDROP ON)
+
+include(CPack)
diff --git a/package/osx/make-package b/package/osx/make-package
new file mode 100755
index 0000000..8337633
--- /dev/null
+++ b/package/osx/make-package
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+set -e
+
+PACKAGE_DIR=`pwd`
+
+if [ "$1" == "clean" ]
+then
+ # remove existing build dir
+ rm -rf build
+
+ # clean out ant build
+ cd ../../src/gwt
+ ant clean
+ cd $PACKAGE_DIR
+fi
+
+mkdir -p build
+cd build
+rm -f CMakeCache.txt
+rm -rf build/_CPack_Packages
+
+cmake -DRSTUDIO_TARGET=Desktop \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DRSTUDIO_PACKAGE_BUILD=1 \
+ ../../..
+
+make
+
+cpack -G DragNDrop
+
+cd ..
diff --git a/package/win32/.gitignore b/package/win32/.gitignore
new file mode 100644
index 0000000..dd8f24a
--- /dev/null
+++ b/package/win32/.gitignore
@@ -0,0 +1 @@
+build64*
diff --git a/package/win32/CMakeLists.txt b/package/win32/CMakeLists.txt
new file mode 100644
index 0000000..2656da8
--- /dev/null
+++ b/package/win32/CMakeLists.txt
@@ -0,0 +1,91 @@
+
+# append our module directory to path
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
+
+# install ssh-console batch file into msys_ssh directory (called by shortcut)
+if(NOT RSTUDIO_SESSION_WIN64)
+ install(PROGRAMS src/ssh-console.bat
+ DESTINATION "${RSTUDIO_INSTALL_BIN}/msys_ssh")
+endif()
+
+# cpack variables
+set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
+if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Release")
+ set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${CMAKE_BUILD_TYPE}")
+endif()
+set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\rstudio.exe")
+set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "RStudio")
+set(CPACK_PACKAGE_EXECUTABLES "rstudio" "RStudio")
+set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
+
+# registry keys
+set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "
+ WriteRegStr HKLM 'Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\rstudio.exe' '' '$INSTDIR\\\\bin\\\\rstudio.exe'
+
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\shell\\\\open\\\\command' '' '$INSTDIR\\\\bin\\\\rstudio.exe \\\"%1\\\"'
+
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.R' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.RData' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.rda' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rd' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rnw' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rmd' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rmarkdown' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rhtml' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rpres' ''
+ WriteRegStr HKCR 'Applications\\\\rstudio.exe\\\\SupportedTypes' '.Rproj' ''
+
+ WriteRegStr HKCR '.R\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.RData\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.rda\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rd\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rnw\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rmd\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rmarkdown\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rhtml\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rpres\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.Rproj\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.tex\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.md\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.mdtxt\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.markdown\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.htm\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.html\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.css\\\\OpenWithList\\\\rstudio.exe' '' ''
+ WriteRegStr HKCR '.js\\\\OpenWithList\\\\rstudio.exe' '' ''
+
+ WriteRegStr HKCR '.Rproj' '' 'RStudio.Rproj'
+ WriteRegStr HKCR 'RStudio.Rproj' '' 'R Project'
+ WriteRegStr HKCR 'RStudio.Rproj\\\\DefaultIcon' '' '$INSTDIR\\\\bin\\\\rstudio.exe,-2'
+ WriteRegStr HKCR 'RStudio.Rproj\\\\\\\\shell\\\\open\\\\command' '' '$INSTDIR\\\\bin\\\\rstudio.exe \\\"%1\\\"'
+
+ System::Call 'Shell32::SHChangeNotify(i 0x8000000, i 0, i 0, i 0)'
+")
+
+set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "
+ DeleteRegKey HKLM 'Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\rstudio.exe'
+ DeleteRegKey HKCR 'Applications\\\\rstudio.exe'
+ DeleteRegKey HKCR '.R\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.RData\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.rda\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rd\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rnw\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rmd\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rmarkdown\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rhtml\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rpres\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.Rproj\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.tex\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.md\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.mdtxt\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.markdown\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.htm\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.html\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.css\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR '.js\\\\OpenWithList\\\\rstudio.exe'
+ DeleteRegKey HKCR 'RStudio.Rproj'
+#")
+
+# build installer
+include(CPack)
+
diff --git a/package/win32/cert/After_10-10-10_MSCV-VSClass3.cer b/package/win32/cert/After_10-10-10_MSCV-VSClass3.cer
new file mode 100644
index 0000000..75f6e36
--- /dev/null
+++ b/package/win32/cert/After_10-10-10_MSCV-VSClass3.cer
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFmjCCA4KgAwIBAgIKYRmT5AAAAAAAHDANBgkqhkiG9w0BAQUFADB/MQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
+MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNyb3Nv
+ZnQgQ29kZSBWZXJpZmljYXRpb24gUm9vdDAeFw0xMTAyMjIxOTI1MTdaFw0yMTAy
+MjIxOTM1MTdaMIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIElu
+Yy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShj
+KSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkx
+RTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkgLSBHNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAK8kCAgpejWeYAyq50s7Ttx8vDxFHLsr4P4pAvlXCKNkhRUn9fGtyDGJ
+XSLoKqqmQrOP+LlVt7G3S7P+j34HV+zvQ9tmYhVhz2ANpNje+ODDYgg9VBPrScpZ
+VIUm5SuPG5/r9aGRwjNJ2ENjalJL0o/ocFFN0Ylpe8dw9rPcEnTbe11LVtOWvxV3
+obD0oiXyrxySZxjl9AYE75C55ADk3Tq1Gf8CuvQ87uCL6zeL7PTXrPL28D2v3XWR
+MxkdHEDLdCQZIZPZFP6sKlLHj9UESeSNY0eIPGmDy/5HvSt+T8WVrg6d1NFDwGdz
+4xQIfuU/n3O4MwrPXT80h5aK7lPoJRUCAwEAAaOByzCByDARBgNVHSAECjAIMAYG
+BFUdIAAwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAYYwHQYDVR0OBBYEFH/T
+ZafC3ey78DAJ80M5+gKvMzEzMB8GA1UdIwQYMBaAFGL7CiFbf0NuEdoJVFBr9dKW
+cfGeMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
+a2kvY3JsL3Byb2R1Y3RzL01pY3Jvc29mdENvZGVWZXJpZlJvb3QuY3JsMA0GCSqG
+SIb3DQEBBQUAA4ICAQCBKoIWjDRnK+UD6zR7jKKjUIr0VYbxHoyOrn3uAxnOcpUY
+SK1iEf0g/T9HBgFa4uBvjBUsTjxqUGwLNqPPeg2cQrxc+BnVYONp5uIjQWeMaIN2
+K4+Toyq1f75Z+6nJsiaPyqLzghuYPpGVJ5eGYe5bXQdrzYao4mWAqOIV4rK+IwVq
+ugzzR5NNrKSMB3k5wGESOgUNiaPsn1eJhPvsynxHZhSR2LYPGV3muEqsvEfIcUOW
+5jIgpdx3hv0844tx23ubA/y3HTJk6xZSoEOj+i6tWZJOfMfyM0JIOFE6fDjHGyQi
+KEAeGkYfF9sY9/AnNWy4Y9nNuWRdK6Ve78YptPLH+CHMBLpX/QG2q8Zn+efTmX/0
+9SL6cvX9/zocQjqh+YAYpe6NHNRmnkUB/qru//sXjzD38c0pxZ3stdVJAD2FuMu7
+kzonaknAMK5myfcjKDJ2+aSDVshIzlqWqqDMDMR/tI6Xr23jVCfDn4bA1uRzCJcF
+29BUYl4DSMLVn3+nZozQnbBP1NOYX0t6yX+yKVLQEoDHD1S2HmfNxqBsEQOE00h1
+5yr+sDtuCjqma3aZBaPxd2hhMxRHBvxTf1K9khRcSiRqZ4yvjZCq0PZ5IRuTJnzD
+zh69iDiSrkXGGWpJULMF+K5ZN4pqJQOUsVmBUOi6g4C3IzX0drlnHVkYrSCNlA==
+-----END CERTIFICATE-----
diff --git a/package/win32/cert/Symantec.cer b/package/win32/cert/Symantec.cer
new file mode 100644
index 0000000..450a287
--- /dev/null
+++ b/package/win32/cert/Symantec.cer
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
+KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
+j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
+L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
+b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC
+BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA
+A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K
+lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ
+tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/
+-----END CERTIFICATE-----
diff --git a/package/win32/clean-build.bat b/package/win32/clean-build.bat
new file mode 100644
index 0000000..f25f7cd
--- /dev/null
+++ b/package/win32/clean-build.bat
@@ -0,0 +1,11 @@
+
+rmdir /s /q build
+cd ..\..\src\gwt
+ant clean
+
+
+
+
+
+
+
diff --git a/package/win32/cmake/modules/NSIS.template.in b/package/win32/cmake/modules/NSIS.template.in
new file mode 100644
index 0000000..3004913
--- /dev/null
+++ b/package/win32/cmake/modules/NSIS.template.in
@@ -0,0 +1,1016 @@
+; CPack install script designed for a nmake build
+
+;--------------------------------
+; You must define these values
+
+ !define VERSION "@CPACK_PACKAGE_VERSION@"
+ !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@"
+ !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@"
+
+;--------------------------------
+;Variables
+
+ Var MUI_TEMP
+ Var STARTMENU_FOLDER
+ Var SV_ALLUSERS
+ Var START_MENU
+ Var DO_NOT_ADD_TO_PATH
+ Var ADD_TO_PATH_ALL_USERS
+ Var ADD_TO_PATH_CURRENT_USER
+ Var INSTALL_DESKTOP
+ Var IS_DEFAULT_INSTALLDIR
+;--------------------------------
+;Include Modern UI
+
+ !include "MUI.nsh"
+
+ ;
+ ; RSTUDIO CHANGE - Replace PROGRAMFILES with PROGRAMFILES64 (done in 3 locations)
+ ;
+
+ ;Default installation folder
+ InstallDir "$PROGRAMFILES64\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+;--------------------------------
+;General
+
+ ;Name and file
+ Name "@CPACK_NSIS_PACKAGE_NAME@"
+ OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@"
+
+ ;Set compression
+ SetCompressor @CPACK_NSIS_COMPRESSOR@
+
+ at CPACK_NSIS_DEFINES@
+
+ !include Sections.nsh
+
+;--- Component support macros: ---
+; The code for the add/remove functionality is from:
+; http://nsis.sourceforge.net/Add/Remove_Functionality
+; It has been modified slightly and extended to provide
+; inter-component dependencies.
+Var AR_SecFlags
+Var AR_RegFlags
+ at CPACK_NSIS_SECTION_SELECTED_VARS@
+
+; Loads the "selected" flag for the section named SecName into the
+; variable VarName.
+!macro LoadSectionSelectedIntoVar SecName VarName
+ SectionGetFlags ${${SecName}} $${VarName}
+ IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits
+!macroend
+
+; Loads the value of a variable... can we get around this?
+!macro LoadVar VarName
+ IntOp $R0 0 + $${VarName}
+!macroend
+
+; Sets the value of a variable
+!macro StoreVar VarName IntValue
+ IntOp $${VarName} 0 + ${IntValue}
+!macroend
+
+!macro InitSection SecName
+ ; This macro reads component installed flag from the registry and
+ ;changes checked state of the section on the components page.
+ ;Input: section index constant name specified in Section command.
+
+ ClearErrors
+ ;Reading component status from registry
+ ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@\Components\${SecName}" "Installed"
+ IfErrors "default_${SecName}"
+ ;Status will stay default if registry value not found
+ ;(component was never installed)
+ IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits
+ SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags
+ IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off
+ IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit
+
+ ; Note whether this component was installed before
+ !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags
+ IntOp $R0 $AR_RegFlags & $AR_RegFlags
+
+ ;Writing modified flags
+ SectionSetFlags ${${SecName}} $AR_SecFlags
+
+ "default_${SecName}:"
+ !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected
+!macroend
+
+!macro FinishSection SecName
+ ; This macro reads section flag set by user and removes the section
+ ;if it is not selected.
+ ;Then it writes component installed flag to registry
+ ;Input: section index constant name specified in Section command.
+
+ SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags
+ ;Checking lowest bit:
+ IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED}
+ IntCmp $AR_SecFlags 1 "leave_${SecName}"
+ ;Section is not selected:
+ ;Calling Section uninstall macro and writing zero installed flag
+ !insertmacro "Remove_${${SecName}}"
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@\Components\${SecName}" \
+ "Installed" 0
+ Goto "exit_${SecName}"
+
+ "leave_${SecName}:"
+ ;Section is selected:
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@\Components\${SecName}" \
+ "Installed" 1
+
+ "exit_${SecName}:"
+!macroend
+
+!macro RemoveSection SecName
+ ; This macro is used to call section's Remove_... macro
+ ;from the uninstaller.
+ ;Input: section index constant name specified in Section command.
+
+ !insertmacro "Remove_${${SecName}}"
+!macroend
+
+; Determine whether the selection of SecName changed
+!macro MaybeSelectionChanged SecName
+ !insertmacro LoadVar ${SecName}_selected
+ SectionGetFlags ${${SecName}} $R1
+ IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits
+
+ ; See if the status has changed:
+ IntCmp $R0 $R1 "${SecName}_unchanged"
+ !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected
+
+ IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected"
+ !insertmacro "Deselect_required_by_${SecName}"
+ goto "${SecName}_unchanged"
+
+ "${SecName}_was_selected:"
+ !insertmacro "Select_${SecName}_depends"
+
+ "${SecName}_unchanged:"
+!macroend
+;--- End of Add/Remove macros ---
+
+;--------------------------------
+;Interface Settings
+
+ !define MUI_HEADERIMAGE
+ !define MUI_ABORTWARNING
+
+;--------------------------------
+; path functions
+
+!verbose 3
+!include "WinMessages.NSH"
+!verbose 4
+
+;----------------------------------------
+; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02"
+;----------------------------------------
+!verbose 3
+!include "WinMessages.NSH"
+!verbose 4
+;====================================================
+; get_NT_environment
+; Returns: the selected environment
+; Output : head of the stack
+;====================================================
+!macro select_NT_profile UN
+Function ${UN}select_NT_profile
+ StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single
+ DetailPrint "Selected environment for all users"
+ Push "all"
+ Return
+ environment_single:
+ DetailPrint "Selected environment for current user only."
+ Push "current"
+ Return
+FunctionEnd
+!macroend
+!insertmacro select_NT_profile ""
+!insertmacro select_NT_profile "un."
+;----------------------------------------------------
+!define NT_current_env 'HKCU "Environment"'
+!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+
+!ifndef WriteEnvStr_RegKey
+ !ifdef ALL_USERS
+ !define WriteEnvStr_RegKey \
+ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+ !else
+ !define WriteEnvStr_RegKey 'HKCU "Environment"'
+ !endif
+!endif
+
+; AddToPath - Adds the given dir to the search path.
+; Input - head of the stack
+; Note - Win9x systems requires reboot
+
+Function AddToPath
+ Exch $0
+ Push $1
+ Push $2
+ Push $3
+
+ # don't add if the path doesn't exist
+ IfFileExists "$0\*.*" "" AddToPath_done
+
+ ReadEnvStr $1 PATH
+ ; if the path is too long for a NSIS variable NSIS will return a 0
+ ; length string. If we find that, then warn and skip any path
+ ; modification as it will trash the existing path.
+ StrLen $2 $1
+ IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done
+ CheckPathLength_ShowPathWarning:
+ Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!"
+ Goto AddToPath_done
+ CheckPathLength_Done:
+ Push "$1;"
+ Push "$0;"
+ Call StrStr
+ Pop $2
+ StrCmp $2 "" "" AddToPath_done
+ Push "$1;"
+ Push "$0\;"
+ Call StrStr
+ Pop $2
+ StrCmp $2 "" "" AddToPath_done
+ GetFullPathName /SHORT $3 $0
+ Push "$1;"
+ Push "$3;"
+ Call StrStr
+ Pop $2
+ StrCmp $2 "" "" AddToPath_done
+ Push "$1;"
+ Push "$3\;"
+ Call StrStr
+ Pop $2
+ StrCmp $2 "" "" AddToPath_done
+
+ Call IsNT
+ Pop $1
+ StrCmp $1 1 AddToPath_NT
+ ; Not on NT
+ StrCpy $1 $WINDIR 2
+ FileOpen $1 "$1\autoexec.bat" a
+ FileSeek $1 -1 END
+ FileReadByte $1 $2
+ IntCmp $2 26 0 +2 +2 # DOS EOF
+ FileSeek $1 -1 END # write over EOF
+ FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n"
+ FileClose $1
+ SetRebootFlag true
+ Goto AddToPath_done
+
+ AddToPath_NT:
+ StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey
+ ReadRegStr $1 ${NT_current_env} "PATH"
+ Goto DoTrim
+ ReadAllKey:
+ ReadRegStr $1 ${NT_all_env} "PATH"
+ DoTrim:
+ StrCmp $1 "" AddToPath_NTdoIt
+ Push $1
+ Call Trim
+ Pop $1
+ StrCpy $0 "$1;$0"
+ AddToPath_NTdoIt:
+ StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey
+ WriteRegExpandStr ${NT_current_env} "PATH" $0
+ Goto DoSend
+ WriteAllKey:
+ WriteRegExpandStr ${NT_all_env} "PATH" $0
+ DoSend:
+ SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+ AddToPath_done:
+ Pop $3
+ Pop $2
+ Pop $1
+ Pop $0
+FunctionEnd
+
+
+; RemoveFromPath - Remove a given dir from the path
+; Input: head of the stack
+
+Function un.RemoveFromPath
+ Exch $0
+ Push $1
+ Push $2
+ Push $3
+ Push $4
+ Push $5
+ Push $6
+
+ IntFmt $6 "%c" 26 # DOS EOF
+
+ Call un.IsNT
+ Pop $1
+ StrCmp $1 1 unRemoveFromPath_NT
+ ; Not on NT
+ StrCpy $1 $WINDIR 2
+ FileOpen $1 "$1\autoexec.bat" r
+ GetTempFileName $4
+ FileOpen $2 $4 w
+ GetFullPathName /SHORT $0 $0
+ StrCpy $0 "SET PATH=%PATH%;$0"
+ Goto unRemoveFromPath_dosLoop
+
+ unRemoveFromPath_dosLoop:
+ FileRead $1 $3
+ StrCpy $5 $3 1 -1 # read last char
+ StrCmp $5 $6 0 +2 # if DOS EOF
+ StrCpy $3 $3 -1 # remove DOS EOF so we can compare
+ StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine
+ StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine
+ StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine
+ StrCmp $3 "" unRemoveFromPath_dosLoopEnd
+ FileWrite $2 $3
+ Goto unRemoveFromPath_dosLoop
+ unRemoveFromPath_dosLoopRemoveLine:
+ SetRebootFlag true
+ Goto unRemoveFromPath_dosLoop
+
+ unRemoveFromPath_dosLoopEnd:
+ FileClose $2
+ FileClose $1
+ StrCpy $1 $WINDIR 2
+ Delete "$1\autoexec.bat"
+ CopyFiles /SILENT $4 "$1\autoexec.bat"
+ Delete $4
+ Goto unRemoveFromPath_done
+
+ unRemoveFromPath_NT:
+ StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey
+ ReadRegStr $1 ${NT_current_env} "PATH"
+ Goto unDoTrim
+ unReadAllKey:
+ ReadRegStr $1 ${NT_all_env} "PATH"
+ unDoTrim:
+ StrCpy $5 $1 1 -1 # copy last char
+ StrCmp $5 ";" +2 # if last char != ;
+ StrCpy $1 "$1;" # append ;
+ Push $1
+ Push "$0;"
+ Call un.StrStr ; Find `$0;` in $1
+ Pop $2 ; pos of our dir
+ StrCmp $2 "" unRemoveFromPath_done
+ ; else, it is in path
+ # $0 - path to add
+ # $1 - path var
+ StrLen $3 "$0;"
+ StrLen $4 $2
+ StrCpy $5 $1 -$4 # $5 is now the part before the path to remove
+ StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove
+ StrCpy $3 $5$6
+
+ StrCpy $5 $3 1 -1 # copy last char
+ StrCmp $5 ";" 0 +2 # if last char == ;
+ StrCpy $3 $3 -1 # remove last char
+
+ StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey
+ WriteRegExpandStr ${NT_current_env} "PATH" $3
+ Goto unDoSend
+ unWriteAllKey:
+ WriteRegExpandStr ${NT_all_env} "PATH" $3
+ unDoSend:
+ SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+ unRemoveFromPath_done:
+ Pop $6
+ Pop $5
+ Pop $4
+ Pop $3
+ Pop $2
+ Pop $1
+ Pop $0
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Uninstall sutff
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+###########################################
+# Utility Functions #
+###########################################
+
+;====================================================
+; IsNT - Returns 1 if the current system is NT, 0
+; otherwise.
+; Output: head of the stack
+;====================================================
+; IsNT
+; no input
+; output, top of the stack = 1 if NT or 0 if not
+;
+; Usage:
+; Call IsNT
+; Pop $R0
+; ($R0 at this point is 1 or 0)
+
+!macro IsNT un
+Function ${un}IsNT
+ Push $0
+ ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
+ StrCmp $0 "" 0 IsNT_yes
+ ; we are not NT.
+ Pop $0
+ Push 0
+ Return
+
+ IsNT_yes:
+ ; NT!!!
+ Pop $0
+ Push 1
+FunctionEnd
+!macroend
+!insertmacro IsNT ""
+!insertmacro IsNT "un."
+
+; StrStr
+; input, top of stack = string to search for
+; top of stack-1 = string to search in
+; output, top of stack (replaces with the portion of the string remaining)
+; modifies no other variables.
+;
+; Usage:
+; Push "this is a long ass string"
+; Push "ass"
+; Call StrStr
+; Pop $R0
+; ($R0 at this point is "ass string")
+
+!macro StrStr un
+Function ${un}StrStr
+Exch $R1 ; st=haystack,old$R1, $R1=needle
+ Exch ; st=old$R1,haystack
+ Exch $R2 ; st=old$R1,old$R2, $R2=haystack
+ Push $R3
+ Push $R4
+ Push $R5
+ StrLen $R3 $R1
+ StrCpy $R4 0
+ ; $R1=needle
+ ; $R2=haystack
+ ; $R3=len(needle)
+ ; $R4=cnt
+ ; $R5=tmp
+ loop:
+ StrCpy $R5 $R2 $R3 $R4
+ StrCmp $R5 $R1 done
+ StrCmp $R5 "" done
+ IntOp $R4 $R4 + 1
+ Goto loop
+done:
+ StrCpy $R1 $R2 "" $R4
+ Pop $R5
+ Pop $R4
+ Pop $R3
+ Pop $R2
+ Exch $R1
+FunctionEnd
+!macroend
+!insertmacro StrStr ""
+!insertmacro StrStr "un."
+
+Function Trim ; Added by Pelaca
+ Exch $R1
+ Push $R2
+Loop:
+ StrCpy $R2 "$R1" 1 -1
+ StrCmp "$R2" " " RTrim
+ StrCmp "$R2" "$\n" RTrim
+ StrCmp "$R2" "$\r" RTrim
+ StrCmp "$R2" ";" RTrim
+ GoTo Done
+RTrim:
+ StrCpy $R1 "$R1" -1
+ Goto Loop
+Done:
+ Pop $R2
+ Exch $R1
+FunctionEnd
+
+Function ConditionalAddToRegisty
+ Pop $0
+ Pop $1
+ StrCmp "$0" "" ConditionalAddToRegisty_EmptyString
+ WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" \
+ "$1" "$0"
+ ;MessageBox MB_OK "Set Registry: '$1' to '$0'"
+ DetailPrint "Set install registry entry: '$1' to '$0'"
+ ConditionalAddToRegisty_EmptyString:
+FunctionEnd
+
+;--------------------------------
+
+!ifdef CPACK_USES_DOWNLOAD
+Function DownloadFile
+ IfFileExists $INSTDIR\* +2
+ CreateDirectory $INSTDIR
+ Pop $0
+
+ ; Skip if already downloaded
+ IfFileExists $INSTDIR\$0 0 +2
+ Return
+
+ StrCpy $1 "@CPACK_DOWNLOAD_SITE@"
+
+ try_again:
+ NSISdl::download "$1/$0" "$INSTDIR\$0"
+
+ Pop $1
+ StrCmp $1 "success" success
+ StrCmp $1 "Cancelled" cancel
+ MessageBox MB_OK "Download failed: $1"
+ cancel:
+ Return
+ success:
+FunctionEnd
+!endif
+
+;--------------------------------
+; Installation types
+ at CPACK_NSIS_INSTALLATION_TYPES@
+
+;--------------------------------
+; Component sections
+ at CPACK_NSIS_COMPONENT_SECTIONS@
+
+;--------------------------------
+; Define some macro setting for the gui
+ at CPACK_NSIS_INSTALLER_MUI_ICON_CODE@
+ at CPACK_NSIS_INSTALLER_ICON_CODE@
+ at CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@
+
+;--------------------------------
+;Pages
+ !insertmacro MUI_PAGE_WELCOME
+
+ ;
+ ; RSTUDIO CHANGE - eliminate license screen
+ ;
+ ;!insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
+
+ Page custom InstallOptionsPage
+ !insertmacro MUI_PAGE_DIRECTORY
+
+ ;Start Menu Folder Page Configuration
+ !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX"
+ !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+ !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
+ !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
+
+ @CPACK_NSIS_PAGE_COMPONENTS@
+
+ !insertmacro MUI_PAGE_INSTFILES
+ !insertmacro MUI_PAGE_FINISH
+
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+ !insertmacro MUI_LANGUAGE "English" ;first language is the default language
+ !insertmacro MUI_LANGUAGE "Albanian"
+ !insertmacro MUI_LANGUAGE "Arabic"
+ !insertmacro MUI_LANGUAGE "Basque"
+ !insertmacro MUI_LANGUAGE "Belarusian"
+ !insertmacro MUI_LANGUAGE "Bosnian"
+ !insertmacro MUI_LANGUAGE "Breton"
+ !insertmacro MUI_LANGUAGE "Bulgarian"
+ !insertmacro MUI_LANGUAGE "Croatian"
+ !insertmacro MUI_LANGUAGE "Czech"
+ !insertmacro MUI_LANGUAGE "Danish"
+ !insertmacro MUI_LANGUAGE "Dutch"
+ !insertmacro MUI_LANGUAGE "Estonian"
+ !insertmacro MUI_LANGUAGE "Farsi"
+ !insertmacro MUI_LANGUAGE "Finnish"
+ !insertmacro MUI_LANGUAGE "French"
+ !insertmacro MUI_LANGUAGE "German"
+ !insertmacro MUI_LANGUAGE "Greek"
+ !insertmacro MUI_LANGUAGE "Hebrew"
+ !insertmacro MUI_LANGUAGE "Hungarian"
+ !insertmacro MUI_LANGUAGE "Icelandic"
+ !insertmacro MUI_LANGUAGE "Indonesian"
+ !insertmacro MUI_LANGUAGE "Irish"
+ !insertmacro MUI_LANGUAGE "Italian"
+ !insertmacro MUI_LANGUAGE "Japanese"
+ !insertmacro MUI_LANGUAGE "Korean"
+ !insertmacro MUI_LANGUAGE "Kurdish"
+ !insertmacro MUI_LANGUAGE "Latvian"
+ !insertmacro MUI_LANGUAGE "Lithuanian"
+ !insertmacro MUI_LANGUAGE "Luxembourgish"
+ !insertmacro MUI_LANGUAGE "Macedonian"
+ !insertmacro MUI_LANGUAGE "Malay"
+ !insertmacro MUI_LANGUAGE "Mongolian"
+ !insertmacro MUI_LANGUAGE "Norwegian"
+ !insertmacro MUI_LANGUAGE "Polish"
+ !insertmacro MUI_LANGUAGE "Portuguese"
+ !insertmacro MUI_LANGUAGE "PortugueseBR"
+ !insertmacro MUI_LANGUAGE "Romanian"
+ !insertmacro MUI_LANGUAGE "Russian"
+ !insertmacro MUI_LANGUAGE "Serbian"
+ !insertmacro MUI_LANGUAGE "SerbianLatin"
+ !insertmacro MUI_LANGUAGE "SimpChinese"
+ !insertmacro MUI_LANGUAGE "Slovak"
+ !insertmacro MUI_LANGUAGE "Slovenian"
+ !insertmacro MUI_LANGUAGE "Spanish"
+ !insertmacro MUI_LANGUAGE "Swedish"
+ !insertmacro MUI_LANGUAGE "Thai"
+ !insertmacro MUI_LANGUAGE "TradChinese"
+ !insertmacro MUI_LANGUAGE "Turkish"
+ !insertmacro MUI_LANGUAGE "Ukrainian"
+ !insertmacro MUI_LANGUAGE "Welsh"
+
+
+;--------------------------------
+;Reserve Files
+
+ ;These files should be inserted before other files in the data block
+ ;Keep these lines before any File command
+ ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA)
+
+ ReserveFile "NSIS.InstallOptions.ini"
+ !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
+
+;--------------------------------
+;Installer Sections
+
+;
+; RSTUDIO CHANGE - uninstall previous version
+;
+Section "-Remove previous installation"
+
+ ; check for uninstaller
+ IfFileExists "$INSTDIR\Uninstall.exe" 0 done
+
+ ;Run the uninstaller (in place, to allow ExecWait to work properly)
+ ClearErrors
+ DetailPrint "Removing previous installation..."
+ ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR'
+
+ ; don't remove the uninstaller if we failed
+ IfErrors done
+
+ ; remove the uninstaller if it exists
+ IfFileExists "$INSTDIR\Uninstall.exe" 0 done
+ Delete "$INSTDIR\Uninstall.exe"
+ RMDir $INSTDIR
+
+done:
+
+SectionEnd
+
+Section "-Core installation"
+ ;Use the entire tree produced by the INSTALL target. Keep the
+ ;list of directories here in sync with the RMDir commands below.
+ SetOutPath "$INSTDIR"
+ @CPACK_NSIS_FULL_INSTALL@
+
+ ;Store installation folder
+ WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR
+
+ ;Create uninstaller
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+ Push "DisplayName"
+ Push "@CPACK_NSIS_DISPLAY_NAME@"
+ Call ConditionalAddToRegisty
+ Push "DisplayVersion"
+ Push "@CPACK_PACKAGE_VERSION@"
+ Call ConditionalAddToRegisty
+ Push "Publisher"
+ Push "@CPACK_PACKAGE_VENDOR@"
+ Call ConditionalAddToRegisty
+ Push "UninstallString"
+ Push "$INSTDIR\Uninstall.exe"
+ Call ConditionalAddToRegisty
+ Push "NoRepair"
+ Push "1"
+ Call ConditionalAddToRegisty
+
+ !ifdef CPACK_NSIS_ADD_REMOVE
+ ;Create add/remove functionality
+ Push "ModifyPath"
+ Push "$INSTDIR\AddRemove.exe"
+ Call ConditionalAddToRegisty
+ !else
+ Push "NoModify"
+ Push "1"
+ Call ConditionalAddToRegisty
+ !endif
+
+ ; Optional registration
+ Push "DisplayIcon"
+ Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@"
+ Call ConditionalAddToRegisty
+ Push "HelpLink"
+ Push "@CPACK_NSIS_HELP_LINK@"
+ Call ConditionalAddToRegisty
+ Push "URLInfoAbout"
+ Push "@CPACK_NSIS_URL_INFO_ABOUT@"
+ Call ConditionalAddToRegisty
+ Push "Contact"
+ Push "@CPACK_NSIS_CONTACT@"
+ Call ConditionalAddToRegisty
+ !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State"
+ !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+
+ ;Create shortcuts
+ CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
+ at CPACK_NSIS_CREATE_ICONS@
+ at CPACK_NSIS_CREATE_ICONS_EXTRA@
+ CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
+
+ ;
+ ; RSTUDIO CHANGE - add shortcut to diagnostics
+ ;
+ CreateShortCut "$INSTDIR\bin\Run Diagnostics.lnk" \
+ "$WINDIR\system32\cmd.exe" \
+ "/K $\"$INSTDIR\bin\rstudio.exe$\" --run-diagnostics" \
+ "$WINDIR\system32\cmd.exe" 0 \
+ SW_SHOWNORMAL
+
+ ;
+ ; RSTUDIO CHANGE - add shortcut to SSH Command Prompt
+ ;
+ CreateShortCut "$INSTDIR\bin\msys_ssh\SSH Command Prompt.lnk" \
+ "$WINDIR\system32\cmd.exe" \
+ "/K $\"$INSTDIR\bin\msys_ssh\ssh-console.bat$\"" \
+ "$WINDIR\system32\cmd.exe" 0 \
+ SW_SHOWNORMAL
+
+ ;Read a value from an InstallOptions INI file
+ !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State"
+ !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State"
+ !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State"
+
+ ; Write special uninstall registry entries
+ Push "StartMenu"
+ Push "$STARTMENU_FOLDER"
+ Call ConditionalAddToRegisty
+ Push "DoNotAddToPath"
+ Push "$DO_NOT_ADD_TO_PATH"
+ Call ConditionalAddToRegisty
+ Push "AddToPathAllUsers"
+ Push "$ADD_TO_PATH_ALL_USERS"
+ Call ConditionalAddToRegisty
+ Push "AddToPathCurrentUser"
+ Push "$ADD_TO_PATH_CURRENT_USER"
+ Call ConditionalAddToRegisty
+ Push "InstallToDesktop"
+ Push "$INSTALL_DESKTOP"
+ Call ConditionalAddToRegisty
+
+ !insertmacro MUI_STARTMENU_WRITE_END
+
+ at CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
+
+SectionEnd
+
+Section "-Add to path"
+ Push $INSTDIR\bin
+ StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath
+ StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0
+ Call AddToPath
+ doNotAddToPath:
+SectionEnd
+
+;--------------------------------
+; Create custom pages
+Function InstallOptionsPage
+ !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini"
+
+FunctionEnd
+
+;--------------------------------
+; determine admin versus local install
+Function un.onInit
+
+ ClearErrors
+ UserInfo::GetName
+ IfErrors noLM
+ Pop $0
+ UserInfo::GetAccountType
+ Pop $1
+ StrCmp $1 "Admin" 0 +3
+ SetShellVarContext all
+ ;MessageBox MB_OK 'User "$0" is in the Admin group'
+ Goto done
+ StrCmp $1 "Power" 0 +3
+ SetShellVarContext all
+ ;MessageBox MB_OK 'User "$0" is in the Power Users group'
+ Goto done
+
+ noLM:
+ ;Get installation folder from registry if available
+
+ done:
+
+FunctionEnd
+
+;--- Add/Remove callback functions: ---
+!macro SectionList MacroName
+ ;This macro used to perform operation on multiple sections.
+ ;List all of your components in following manner here.
+ at CPACK_NSIS_COMPONENT_SECTION_LIST@
+!macroend
+
+Section -FinishComponents
+ ;Removes unselected components and writes component status to registry
+ !insertmacro SectionList "FinishSection"
+
+!ifdef CPACK_NSIS_ADD_REMOVE
+ ; Get the name of the installer executable
+ System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1'
+ StrCpy $R3 $R0
+
+ ; Strip off the last 13 characters, to see if we have AddRemove.exe
+ StrLen $R1 $R0
+ IntOp $R1 $R0 - 13
+ StrCpy $R2 $R0 13 $R1
+ StrCmp $R2 "AddRemove.exe" addremove_installed
+
+ ; We're not running AddRemove.exe, so install it
+ CopyFiles $R3 $INSTDIR\AddRemove.exe
+
+ addremove_installed:
+!endif
+SectionEnd
+;--- End of Add/Remove callback functions ---
+
+;--------------------------------
+; Component dependencies
+Function .onSelChange
+ !insertmacro SectionList MaybeSelectionChanged
+FunctionEnd
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+ ReadRegStr $START_MENU SHCTX \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" "StartMenu"
+ ;MessageBox MB_OK "Start menu is in: $START_MENU"
+ ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" "DoNotAddToPath"
+ ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" "AddToPathAllUsers"
+ ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" "AddToPathCurrentUser"
+ ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS"
+ ReadRegStr $INSTALL_DESKTOP SHCTX \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@" "InstallToDesktop"
+ ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP "
+
+ at CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@
+
+ ;Remove files we installed.
+ ;Keep the list of directories here in sync with the File commands above.
+ at CPACK_NSIS_DELETE_FILES@
+ at CPACK_NSIS_DELETE_DIRECTORIES@
+
+!ifdef CPACK_NSIS_ADD_REMOVE
+ ;Remove the add/remove program
+ Delete "$INSTDIR\AddRemove.exe"
+!endif
+
+ ;Remove the uninstaller itself.
+ Delete "$INSTDIR\Uninstall.exe"
+ DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_NAME@"
+
+ ;Remove shortcuts
+ Delete "$INSTDIR\bin\Run Diagnostics.lnk"
+ Delete "$INSTDIR\bin\msys_ssh\SSH Command Prompt.lnk"
+ RMDir /r "$INSTDIR\bin\"
+
+ ;Remove the installation directory if it is empty.
+ RMDir "$INSTDIR"
+
+ ; Remove the registry entries.
+ DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+
+ ; Removes all optional components
+ !insertmacro SectionList "RemoveSection"
+
+ !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
+
+ Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
+ at CPACK_NSIS_DELETE_ICONS@
+ at CPACK_NSIS_DELETE_ICONS_EXTRA@
+
+ ;Delete empty start menu parent diretories
+ StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
+
+ startMenuDeleteLoop:
+ ClearErrors
+ RMDir $MUI_TEMP
+ GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
+
+ IfErrors startMenuDeleteLoopDone
+
+ StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop
+ startMenuDeleteLoopDone:
+
+ ; If the user changed the shortcut, then untinstall may not work. This should
+ ; try to fix it.
+ StrCpy $MUI_TEMP "$START_MENU"
+ Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
+ at CPACK_NSIS_DELETE_ICONS_EXTRA@
+
+ ;Delete empty start menu parent diretories
+ StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
+
+ secondStartMenuDeleteLoop:
+ ClearErrors
+ RMDir $MUI_TEMP
+ GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
+
+ IfErrors secondStartMenuDeleteLoopDone
+
+ StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop
+ secondStartMenuDeleteLoopDone:
+
+ DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+
+ Push $INSTDIR\bin
+ StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0
+ Call un.RemoveFromPath
+ doNotRemoveFromPath:
+SectionEnd
+
+;--------------------------------
+; determine admin versus local install
+; Is install for "AllUsers" or "JustMe"?
+; Default to "JustMe" - set to "AllUsers" if admin or on Win9x
+; This function is used for the very first "custom page" of the installer.
+; This custom page does not show up visibly, but it executes prior to the
+; first visible page and sets up $INSTDIR properly...
+; Choose different default installation folder based on SV_ALLUSERS...
+; "Program Files" for AllUsers, "My Documents" for JustMe...
+
+Function .onInit
+
+ ;
+ ; RSTUDIO CHANGE - check for running version of app
+ ;
+
+ ; Detect whether the application is running
+ FindWindow $R0 "QWidget" "@CPACK_PACKAGE_NAME@"
+ StrCmp $R0 0 not_running
+ MessageBox MB_OK|MB_ICONEXCLAMATION "@CPACK_PACKAGE_NAME@ is currently running. Please close it before installing a new version." /SD IDOK
+ Abort
+ not_running:
+
+ ; Reads components status for registry
+ !insertmacro SectionList "InitSection"
+
+ ; check to see if /D has been used to change
+ ; the install directory by comparing it to the
+ ; install directory that is expected to be the
+ ; default
+ StrCpy $IS_DEFAULT_INSTALLDIR 0
+ StrCmp "$INSTDIR" "$PROGRAMFILES64\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2
+ StrCpy $IS_DEFAULT_INSTALLDIR 1
+
+ StrCpy $SV_ALLUSERS "JustMe"
+ ; if default install dir then change the default
+ ; if it is installed for JustMe
+ StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2
+ StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+ ClearErrors
+ UserInfo::GetName
+ IfErrors noLM
+ Pop $0
+ UserInfo::GetAccountType
+ Pop $1
+ StrCmp $1 "Admin" 0 +3
+ SetShellVarContext all
+ ;MessageBox MB_OK 'User "$0" is in the Admin group'
+ StrCpy $SV_ALLUSERS "AllUsers"
+ Goto done
+ StrCmp $1 "Power" 0 +3
+ SetShellVarContext all
+ ;MessageBox MB_OK 'User "$0" is in the Power Users group'
+ StrCpy $SV_ALLUSERS "AllUsers"
+ Goto done
+
+ noLM:
+ StrCpy $SV_ALLUSERS "AllUsers"
+ ;Get installation folder from registry if available
+
+ done:
+ StrCmp $SV_ALLUSERS "AllUsers" 0 +3
+ StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2
+ StrCpy $INSTDIR "$PROGRAMFILES64\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+ StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage
+ !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini"
+
+ noOptionsPage:
+FunctionEnd
diff --git a/package/win32/codesign.bat b/package/win32/codesign.bat
new file mode 100644
index 0000000..f7bd46b
--- /dev/null
+++ b/package/win32/codesign.bat
@@ -0,0 +1,13 @@
+
+set SIGN_TARGET=%1%
+
+"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\signtool" sign /v /ac "cert\After_10-10-10_MSCV-VSClass3.cer" /s MY /n "RStudio, Inc." /t http://timestamp.VeriSign.com/scripts/timstamp.dll %SIGN_TARGET%
+"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\signtool" verify /v /kp %SIGN_TARGET%
+
+
+
+
+
+
+
+
diff --git a/package/win32/make-install-win64.bat b/package/win32/make-install-win64.bat
new file mode 100644
index 0000000..2fd305e
--- /dev/null
+++ b/package/win32/make-install-win64.bat
@@ -0,0 +1,26 @@
+REM setup variables
+setlocal
+set WIN64_BUILD_PATH=build64
+IF "%CMAKE_BUILD_TYPE%" == "" set CMAKE_BUILD_TYPE=Release
+IF "%CMAKE_BUILD_TYPE%" == "Debug" set WIN64_BUILD_PATH=build64-debug
+set MINGW64_PATH=%CD%\..\..\dependencies\windows\mingw64\bin
+set INSTALL_PATH=%1%
+
+REM perform 64-bit build
+if "%2" == "clean" rmdir /s /q %WIN64_BUILD_PATH%
+setlocal
+set PATH=%MINGW64_PATH%;%PATH%
+mkdir %WIN64_BUILD_PATH%
+cd %WIN64_BUILD_PATH%
+del CMakeCache.txt
+cmake -G"MinGW Makefiles" ^
+ -DCMAKE_INSTALL_PREFIX:String=%INSTALL_PATH% ^
+ -DRSTUDIO_TARGET=SessionWin64 ^
+ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% ^
+ -DRSTUDIO_PACKAGE_BUILD=1 ^
+ ..\..\..
+mingw32-make install
+cd ..
+endlocal
+
+
diff --git a/package/win32/make-package.bat b/package/win32/make-package.bat
new file mode 100644
index 0000000..04215c4
--- /dev/null
+++ b/package/win32/make-package.bat
@@ -0,0 +1,43 @@
+
+set PACKAGE_DIR="%CD%"
+
+REM clean if requested
+if "%1" == "clean" call clean-build.bat
+
+REM Prepend Qt 4.8 SDK to path
+setlocal
+set PATH=C:\QtSDK\mingw\bin;%PATH%
+
+
+REM Establish build dir
+set BUILD_DIR=build
+IF "%CMAKE_BUILD_TYPE%" == "" set CMAKE_BUILD_TYPE=Release
+IF "%CMAKE_BUILD_TYPE%" == "Debug" set BUILD_DIR=build-debug
+
+REM perform 32-bit build
+cd "%PACKAGE_DIR%"
+mkdir "%BUILD_DIR%"
+cd "%BUILD_DIR%"
+del CMakeCache.txt
+rmdir /s /q "%BUILD_DIR%\_CPack_Packages"
+cmake -G"MinGW Makefiles" -DRSTUDIO_TARGET=Desktop -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% -DRSTUDIO_PACKAGE_BUILD=1 ..\..\..
+mingw32-make
+cd ..
+
+REM perform 64-bit build and install it into the 32-bit tree
+REM (but only do this if we are on win64)
+IF "%PROCESSOR_ARCHITECTURE%" == "AMD64" call make-install-win64.bat "%PACKAGE_DIR%\%BUILD_DIR%\src\cpp\session" %1
+
+REM create packages
+cd "%BUILD_DIR%"
+cpack -G NSIS
+IF "%CMAKE_BUILD_TYPE%" == "Release" cpack -G ZIP
+cd ..
+
+REM reset modified environment variables (PATH)
+endlocal
+
+
+
+
+
diff --git a/package/win32/src/ssh-console.bat b/package/win32/src/ssh-console.bat
new file mode 100644
index 0000000..e44ef55
--- /dev/null
+++ b/package/win32/src/ssh-console.bat
@@ -0,0 +1,4 @@
+ at ECHO OFF
+SET HOME=%USERPROFILE%
+SET Path=%Path%;%~dp0
+cd %HOMEDRIVE%%HOMEPATH%
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..832eb77
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,3 @@
+client.extras
+qtcreator-build*/
+CMakeLists.txt.user
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..8edd57c
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,41 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# set minimum version
+cmake_minimum_required(VERSION 2.6)
+
+# don't add gwt for special 64-bit binary builds on windows (since
+# we've already got it from the 32-bit build), for development mode
+# (since we'll want to build it incrementally using superdevmode),
+# or when building monitor only
+if ( (NOT RSTUDIO_SESSION_WIN64) AND
+ (NOT RSTUDIO_DEVELOPMENT) AND
+ (NOT RSTUDIO_CONFIG_MONITOR_ONLY) )
+ add_subdirectory(gwt)
+endif()
+
+add_subdirectory(cpp)
+
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+endif()
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cpp/.gitignore b/src/cpp/.gitignore
new file mode 100644
index 0000000..cf25ac6
--- /dev/null
+++ b/src/cpp/.gitignore
@@ -0,0 +1 @@
+CMakeLists.txt.user*
diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt
new file mode 100644
index 0000000..f82a2da
--- /dev/null
+++ b/src/cpp/CMakeLists.txt
@@ -0,0 +1,262 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# set compiler
+include("${CMAKE_CURRENT_SOURCE_DIR}/../../CMakeCompiler.txt")
+
+# set minimum version
+cmake_minimum_required(VERSION 2.6)
+
+project (RSTUDIO_CPP)
+
+# include globals. normally these are included at the root but we also
+# include them here to support configuring at the src level (i.e. for
+# cpp-development-configurations)
+include("${CMAKE_CURRENT_SOURCE_DIR}/../../CMakeGlobals.txt")
+
+# global directives
+add_definitions(-DBOOST_ENABLE_ASSERT_HANDLER)
+
+# UNIX specific global directivies
+if(UNIX)
+ # cmake modules
+ include(CheckFunctionExists REQUIRED)
+ include(CheckSymbolExists REQUIRED)
+
+ # compiler flags
+ add_definitions(-Wall -pthread)
+
+ # workaround boost bug (https://svn.boost.org/trac/boost/ticket/4568)
+ # by disabling kqueue support. note that this bug was fixed in boost 1.45
+ add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE)
+
+ if(APPLE)
+ # if present, set osx deployment target variables from environment vars
+ if(NOT $ENV{CMAKE_OSX_SYSROOT} STREQUAL "")
+ set(CMAKE_OSX_SYSROOT $ENV{CMAKE_OSX_SYSROOT})
+ message(STATUS "Set CMAKE_OSX_SYSROOT to ${CMAKE_OSX_SYSROOT}")
+ endif()
+ if(NOT $ENV{CMAKE_OSX_DEPLOYMENT_TARGET} STREQUAL "")
+ set(CMAKE_OSX_DEPLOYMENT_TARGET $ENV{CMAKE_OSX_DEPLOYMENT_TARGET})
+ message(STATUS "Set CMAKE_OSX_DEPLOYMENT_TARGET to ${CMAKE_OSX_DEPLOYMENT_TARGET}")
+ endif()
+ # Mavericks and later default to libc++, which is not compatible with the
+ # older libstdc++. Figure out if we're on Mavericks, and if so, add
+ # flags to enforce usage of the older library.
+
+ # Figure out what version of Mac OS X this is. Unfortunately
+ # CMAKE_SYSTEM_VERSION does not match uname -r, so get the Mac OS
+ # version number another way.
+ EXECUTE_PROCESS(COMMAND /usr/bin/sw_vers -productVersion OUTPUT_VARIABLE MACOSX_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+ message(STATUS "Mac OS X version: ${MACOSX_VERSION}")
+ if(NOT(${MACOSX_VERSION} VERSION_LESS "10.9"))
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++")
+ set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -stdlib=libstdc++")
+ endif()
+ endif()
+
+ # gcc hardending options (see: http://wiki.debian.org/Hardening)
+ if(NOT APPLE)
+ add_definitions(-Wformat -Wformat-security)
+ add_definitions(-D_FORTIFY_SOURCE=2)
+ add_definitions(-fstack-protector --param ssp-buffer-size=4)
+ add_definitions(-pie -fPIE)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
+ endif()
+
+ # static link to gcc runtime libraries on linux
+ if(NOT APPLE)
+ execute_process(COMMAND g++ -print-file-name=libstdc++.a
+ OUTPUT_VARIABLE RSTUDIO_STATIC_LIBSTDCPP
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ execute_process(COMMAND ln -fs "${RSTUDIO_STATIC_LIBSTDCPP}" "${CMAKE_CURRENT_BINARY_DIR}/libstdc++.a")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -L${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+
+# Win32 specific global directives
+else()
+ add_definitions(-DWINVER=0x501)
+
+ if(RSTUDIO_SESSION_WIN64)
+
+ # increase stack size to 20MB, avoid mingw auto-importing warning,
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack=0x01400000,--enable-auto-import")
+
+ add_definitions(-D_WIN64
+ -D_WIN64_WINNT=0x0501
+ -D_WIN64_IE=0x600
+ -DWIN64_LEAN_AND_MEAN
+ -DBOOST_USE_WINDOWS_H)
+ else()
+
+ # increase stack size to 20MB, avoid mingw auto-importing warning,
+ # and ensure that we are large address aware
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack=0x01400000,--enable-auto-import,--large-address-aware")
+
+ add_definitions(-D_WIN32_WINNT=0x0501
+ -D_WIN32_IE=0x600
+ -DWIN32_LEAN_AND_MEAN)
+ endif()
+
+endif()
+
+# determine whether we should statically link boost. we always do this
+# unless we are building a non-packaged build on linux (in which case
+# boost dynamic libraries are presumed to be installed on the system ldpath)
+if(APPLE OR WIN32 OR RSTUDIO_PACKAGE_BUILD)
+ set(Boost_USE_STATIC_LIBS ON)
+endif()
+
+# require boost 1.50
+set(BOOST_VERSION 1.50.0)
+
+list(APPEND BOOST_LIBS
+ date_time
+ filesystem
+ iostreams
+ program_options
+ regex
+ signals
+ system
+ thread
+ chrono
+)
+
+# UNIX BOOST
+if(UNIX)
+ # prefer static link to our custom built version
+ set(RSTUDIO_TOOLS_BOOST /opt/rstudio-tools/boost/boost_1_50_0)
+ if(EXISTS ${RSTUDIO_TOOLS_BOOST})
+ # find headers
+ set(Boost_USE_STATIC_LIBS ON)
+ set(BOOST_INCLUDEDIR ${RSTUDIO_TOOLS_BOOST}/include)
+ find_package(Boost ${BOOST_VERSION} REQUIRED)
+
+ # define library list manually (find_package doesn't always pick them up)
+ set(BOOST_LIB_DIR ${RSTUDIO_TOOLS_BOOST}/lib)
+ foreach(BOOST_LIB ${BOOST_LIBS})
+ list(APPEND Boost_LIBRARIES ${BOOST_LIB_DIR}/libboost_${BOOST_LIB}.a)
+ endforeach()
+ else()
+ find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS ${BOOST_LIBS})
+ endif()
+
+# WIN32 BOOST
+else()
+ # hard-code to our own prebuilt boost libs
+ if(RSTUDIO_SESSION_WIN64)
+ set(BOOST_ROOT "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/boost-1.50-win/boost64")
+ else()
+ set(BOOST_ROOT "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/boost-1.50-win/boost32")
+ endif()
+ set(BOOST_INCLUDEDIR "${BOOST_ROOT}/include/boost-1_50")
+ find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS ${BOOST_LIBS})
+endif()
+
+
+# add boost as system include directory
+include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
+
+
+# automatically build addins found in the addins subdirectory
+# (if another path wasn't already specified)
+if(NOT RSTUDIO_ADDINS_PATH)
+ set(RSTUDIO_DEFAULT_ADDINS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/addins")
+ if(EXISTS ${RSTUDIO_DEFAULT_ADDINS_PATH})
+ set(RSTUDIO_ADDINS_PATH ${RSTUDIO_DEFAULT_ADDINS_PATH})
+ endif()
+endif()
+
+# core library
+add_subdirectory(core)
+
+# are we in CORE_DEV mode? if so then just add the core/dev project
+# otherwise, add the rest of our projects
+if(RSTUDIO_CONFIG_CORE_DEV)
+
+ add_subdirectory(core/dev)
+
+else()
+
+ # monitor library
+ add_subdirectory(monitor)
+
+ # add overlay if it exists
+ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+ endif()
+
+ # proceed if we aren't building rstudio monitor-only
+ if(NOT RSTUDIO_CONFIG_MONITOR_ONLY)
+
+ # find LibR
+ if(RSTUDIO_SESSION_WIN64)
+ set(LIBR_FIND_WINDOWS_64BIT TRUE)
+ endif()
+ find_package(LibR REQUIRED)
+
+ # verify we got the required R version
+ if(LIBR_FOUND AND RSTUDIO_VERIFY_R_VERSION)
+ include(CheckCSourceRuns)
+ set(CMAKE_REQUIRED_INCLUDES ${LIBR_INCLUDE_DIRS})
+ check_c_source_runs("
+ #include <Rversion.h>
+ int main()
+ {
+ int meetsRequirement = R_VERSION >= R_Version(${RSTUDIO_R_MAJOR_VERSION_REQUIRED},${RSTUDIO_R_MINOR_VERSION_REQUIRED},${RSTUDIO_R_PATCH_VERSION_REQUIRED});
+ return !meetsRequirement;
+ }"
+ LIBR_MINIMUM_VERSION)
+ if(NOT LIBR_MINIMUM_VERSION)
+ message(FATAL_ERROR "Minimum R version (${RSTUDIO_R_MAJOR_VERSION_REQUIRED}.${RSTUDIO_R_MINOR_VERSION_REQUIRED}.${RSTUDIO_R_PATCH_VERSION_REQUIRED}) not found.")
+ endif()
+ endif()
+
+ # r library
+ add_subdirectory(r)
+
+ # initialize subdirectories
+ file(MAKE_DIRECTORY conf)
+
+ # add desktop subprojects if we aren't building in server only mode
+ if(RSTUDIO_DESKTOP)
+ add_subdirectory(diagnostics)
+ if(NOT APPLE)
+ add_subdirectory(desktop)
+ else()
+ add_subdirectory(desktop-mac)
+ endif()
+ configure_file(rdesktop-dev.in ${CMAKE_CURRENT_BINARY_DIR}/rdesktop-dev)
+ configure_file(conf/rdesktop-dev.conf ${CMAKE_CURRENT_BINARY_DIR}/conf/rdesktop-dev.conf)
+ endif()
+
+ # add this after desktop so it is not included in fixup_bundle
+ # processing which we do in desktop
+ add_subdirectory(session)
+
+ # add server subprojects if we aren't building in desktop only mode
+ if(RSTUDIO_SERVER)
+
+ add_subdirectory(server)
+ configure_file(rserver-dev.in ${CMAKE_CURRENT_BINARY_DIR}/rserver-dev)
+ configure_file(rserver-test.in ${CMAKE_CURRENT_BINARY_DIR}/rserver-test)
+ configure_file(conf/rserver-dev.conf ${CMAKE_CURRENT_BINARY_DIR}/conf/rserver-dev.conf)
+ configure_file(conf/rsession-dev.conf ${CMAKE_CURRENT_BINARY_DIR}/conf/rsession-dev.conf)
+
+ endif()
+ endif()
+
+endif()
+
diff --git a/src/cpp/conf/rdesktop-dev.conf b/src/cpp/conf/rdesktop-dev.conf
new file mode 100644
index 0000000..9064118
--- /dev/null
+++ b/src/cpp/conf/rdesktop-dev.conf
@@ -0,0 +1,57 @@
+#
+# rdesktop-dev.conf
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+
+# desktop mode
+program-mode=desktop
+
+# read web/gwt stuff from the source tree
+www-local-path=${CMAKE_CURRENT_SOURCE_DIR}/../gwt/www
+www-symbol-maps-path=${CMAKE_CURRENT_SOURCE_DIR}/../gwt/extras/rstudio/symbolMaps
+
+# reload changed R source files on the fly
+r-auto-reload-source=1
+
+# read R code and resource files from the src tree
+r-core-source=${CMAKE_CURRENT_SOURCE_DIR}/r/R
+r-modules-source=${CMAKE_CURRENT_BINARY_DIR}/session/modules/R
+r-resources-path=${CMAKE_CURRENT_SOURCE_DIR}/session/resources
+r-session-library=${CMAKE_CURRENT_BINARY_DIR}/r/R/packages/library
+r-session-packages=${CMAKE_CURRENT_SOURCE_DIR}/r/R/packages
+
+# override r home and doc dir (to ensure we always run against the version
+# we built against and so we can pick them up even when we are launched
+# standalone in the debugger)
+r-home-dir-override=${LIBR_HOME}
+r-doc-dir-override=${LIBR_DOC_DIR}
+
+external-rpostback-path=${CMAKE_CURRENT_BINARY_DIR}/session/postback/postback/rpostback
+external-consoleio-path=${CMAKE_CURRENT_BINARY_DIR}/session/consoleio/consoleio.exe
+external-gnudiff-path=${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/gnudiff
+external-gnugrep-path=${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/gnugrep
+external-msysssh-path=${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/msys_ssh
+external-sumatra-path=${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/sumatra/2.4
+external-hunspell-dictionaries-path=${RSTUDIO_DEPENDENCIES_DIR}/common/dictionaries
+external-mathjax-path=${RSTUDIO_DEPENDENCIES_DIR}/common/mathjax
+external-pandoc-path=${RSTUDIO_DEPENDENCIES_DIR}/common/pandoc/1.12.3
+
+
+
+
+
+
+
+
+
diff --git a/src/cpp/conf/rserver-dev.conf b/src/cpp/conf/rserver-dev.conf
new file mode 100644
index 0000000..7acf5c2
--- /dev/null
+++ b/src/cpp/conf/rserver-dev.conf
@@ -0,0 +1,44 @@
+#
+# rserver-dev.conf
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# set which r from config
+rsession-which-r=${LIBR_EXECUTABLE}
+
+# don't daemonize so we can see errors in the console and easily abort
+server-daemonize=0
+
+# app-armor is not enabled in debug mode
+server-app-armor-enabled=0
+
+# don't validate that web authenticated users exist on the system
+auth-validate-users=0
+
+# always authenticate users (defaults to no-auth if not running as root)
+auth-none=0
+
+# read gwt app directly from the source tree
+www-local-path=${CMAKE_CURRENT_SOURCE_DIR}/../gwt/www
+www-symbol-maps-path=${CMAKE_CURRENT_SOURCE_DIR}/../gwt/extras/rstudio/symbolMaps
+www-use-emulated-stack=1
+
+# execute pam helper, rsession, and rldpath script from within the build tree
+auth-pam-helper-path=${CMAKE_CURRENT_BINARY_DIR}/server/pam/rserver-pam
+rsession-path=${CMAKE_CURRENT_BINARY_DIR}/session/rsession
+rldpath-path=${CMAKE_CURRENT_BINARY_DIR}/session/r-ldpath
+
+# use dev config for rsession
+rsession-config-file=${CMAKE_CURRENT_BINARY_DIR}/conf/rsession-dev.conf
+
+
diff --git a/src/cpp/conf/rsession-dev.conf b/src/cpp/conf/rsession-dev.conf
new file mode 100644
index 0000000..d204459
--- /dev/null
+++ b/src/cpp/conf/rsession-dev.conf
@@ -0,0 +1,40 @@
+#
+# rsession-dev.conf
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+
+# timeout frequently for build iteration and to test suspend
+session-timeout-minutes=1
+
+# reload changed R source files on the fly
+r-auto-reload-source=1
+
+# read R code and resource files from the src tree
+r-core-source=${CMAKE_CURRENT_SOURCE_DIR}/r/R
+r-modules-source=${CMAKE_CURRENT_BINARY_DIR}/session/modules/R
+r-resources-path=${CMAKE_CURRENT_SOURCE_DIR}/session/resources
+r-session-library=${CMAKE_CURRENT_BINARY_DIR}/r/R/packages/library
+r-session-packages=${CMAKE_CURRENT_SOURCE_DIR}/r/R/packages
+
+# execute R postback from the build tree
+external-rpostback-path=session/postback/postback/rpostback
+
+# common dependencies
+external-hunspell-dictionaries-path=${RSTUDIO_DEPENDENCIES_DIR}/common/dictionaries
+external-mathjax-path=${RSTUDIO_DEPENDENCIES_DIR}/common/mathjax
+external-pandoc-path=${RSTUDIO_DEPENDENCIES_DIR}/common/pandoc/1.12.3
+
+
+
+
diff --git a/src/cpp/core/Assert.cpp b/src/cpp/core/Assert.cpp
new file mode 100644
index 0000000..e793ee9
--- /dev/null
+++ b/src/cpp/core/Assert.cpp
@@ -0,0 +1,79 @@
+/*
+ * Assert.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <signal.h>
+#endif
+
+using namespace core;
+
+namespace boost
+{
+void assertion_failed(char const * expr,
+ char const * function,
+ char const * file,
+ long line)
+{
+ // derive location
+ ErrorLocation location(function, file, line);
+
+ // always log the failure
+ std::string msg = "ASSERTION FAILED: " + std::string(expr);
+ core::log::logWarningMessage(msg, location);
+
+#ifndef NDEBUG
+#ifdef _WIN32
+ DebugBreak();
+#else
+ ::raise(SIGTRAP);
+#endif
+#endif
+
+}
+
+void assertion_failed_msg(char const * expr,
+ char const * msg,
+ char const * function,
+ char const * file,
+ long line)
+{
+ // derive location
+ ErrorLocation location(function, file, line);
+
+ // always log the failure
+ std::string message = "ASSERTION FAILED: " + std::string(expr) +
+ " - " + std::string(msg);
+ core::log::logWarningMessage(message, location);
+
+#ifndef NDEBUG
+#ifdef _WIN32
+ DebugBreak();
+#else
+ ::raise(SIGTRAP);
+#endif
+#endif
+}
+
+} // namespace boost
+
+
+
+
diff --git a/src/cpp/core/Base64.cpp b/src/cpp/core/Base64.cpp
new file mode 100644
index 0000000..53bf358
--- /dev/null
+++ b/src/cpp/core/Base64.cpp
@@ -0,0 +1,77 @@
+/*
+ * Base64.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <sstream>
+
+#include <algorithm>
+
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/archive/iterators/ostream_iterator.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FileSerializer.hpp>
+
+namespace core {
+namespace base64 {
+
+Error encode(const std::string& input, std::string* pOutput)
+{
+ using namespace boost::archive::iterators;
+
+ try
+ {
+ typedef base64_from_binary<transform_width<const char *,6,8> > b64_text;
+ std::stringstream os;
+ std::copy(b64_text(input.c_str()),
+ b64_text(input.c_str() + input.size()),
+ ostream_iterator<char>(os));
+
+ pOutput->clear();
+ pOutput->reserve(((input.size() * 4) / 3) + 3);
+ pOutput->append(os.str());
+
+ std::size_t mod = input.size() % 3;
+ if (mod == 1)
+ pOutput->append("==");
+ else if(mod == 2)
+ pOutput->append("=");
+
+ return Success();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // keep compiler happy
+ return Success();
+}
+
+Error encode(const FilePath& inputFile, std::string* pOutput)
+{
+ std::string contents;
+ Error error = core::readStringFromFile(inputFile, &contents);
+ if (error)
+ return error;
+
+ return encode(contents, pOutput);
+}
+
+
+} // namespace base64
+} // namespace core
+
+
+
+
diff --git a/src/cpp/core/BoostErrors.cpp b/src/cpp/core/BoostErrors.cpp
new file mode 100644
index 0000000..36ef129
--- /dev/null
+++ b/src/cpp/core/BoostErrors.cpp
@@ -0,0 +1,213 @@
+/*
+ * BoostErrors.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/BoostErrors.hpp>
+
+#include <core/BoostThread.hpp>
+
+// we define BOOST_USE_WINDOWS_H on mingw64 to work around some
+// incompatabilities. however, this prevents the interprocess headers
+// from compiling so we undef it in this localized context
+#if defined(__GNUC__) && defined(_WIN64)
+ #undef BOOST_USE_WINDOWS_H
+#endif
+
+#include <boost/interprocess/errors.hpp>
+#include <boost/interprocess/exceptions.hpp>
+
+using namespace boost::system ;
+
+namespace boost {
+namespace interprocess {
+
+class interprocess_error_category : public error_category
+{
+public:
+ const char* name() const ;
+ std::string message(int ev) const ;
+};
+
+const error_category& interprocess_category()
+{
+ static interprocess_error_category interprocessCategoryConst ;
+ return interprocessCategoryConst ;
+}
+
+const char* interprocess_error_category::name() const
+{
+ return "interprocess" ;
+}
+
+std::string interprocess_error_category::message(int ev) const
+{
+ std::string message ;
+ switch (ev)
+ {
+ case no_error:
+ message = "No error";
+ break;
+
+ case system_error:
+ message = "System error";
+ break;
+
+ case other_error:
+ message = "Library generated error";
+ break;
+
+ case security_error:
+ message = "Security error";
+ break;
+
+ case read_only_error:
+ message = "Read only error";
+ break;
+
+ case io_error:
+ message = "IO error";
+ break;
+
+ case path_error:
+ message = "Path error";
+ break;
+
+ case not_found_error:
+ message = "Not found error";
+ break;
+
+ case busy_error:
+ message = "Busy error" ;
+ break;
+
+ case already_exists_error:
+ message = "Already exists error";
+ break;
+
+ case not_empty_error:
+ message = "Not empty error";
+ break;
+
+ case is_directory_error:
+ message = "Is directory error";
+ break;
+
+ case out_of_space_error:
+ message = "Out of space error";
+ break;
+
+ case out_of_memory_error:
+ message = "Out of memory error";
+ break;
+
+ case out_of_resource_error:
+ message = "Out of resource error" ;
+ break;
+
+ case lock_error:
+ message = "Lock error" ;
+ break ;
+
+ case sem_error:
+ message = "Sem error" ;
+ break ;
+
+ case mode_error:
+ message = "Mode error" ;
+ break;
+
+ case size_error:
+ message = "Size error" ;
+ break;
+
+ case corrupted_error:
+ message = "Corrupted error" ;
+ break ;
+
+ default:
+ message = "Unknown error" ;
+ break;
+ }
+
+ return message ;
+}
+
+
+
+boost::system::error_code ec_from_exception(const interprocess_exception& e)
+{
+ if (e.get_error_code() == system_error)
+ return error_code(e.get_native_error(), get_system_category()) ;
+ else
+ return error_code(e.get_error_code(), interprocess_category()) ;
+}
+
+} // namespace interprocess
+
+
+// thread_error
+namespace thread_error {
+
+namespace errc {
+enum errc_t {
+ thread_resource_error = 1
+};
+} // namesapce thread_errc
+
+
+class thread_error_category : public error_category
+{
+public:
+ const char* name() const ;
+ std::string message(int ev) const ;
+};
+
+const error_category& thread_category()
+{
+ static thread_error_category threadCategoryConst ;
+ return threadCategoryConst ;
+}
+
+const char* thread_error_category::name() const
+{
+ return "thread" ;
+}
+
+std::string thread_error_category::message(int ev) const
+{
+ std::string message ;
+ switch (ev)
+ {
+ // only one error code for now
+ case errc::thread_resource_error:
+ default:
+ message = "Thread resource error" ;
+ break ;
+ }
+ return message ;
+}
+
+boost::system::error_code ec_from_exception(
+ const boost::thread_resource_error& e)
+{
+ return error_code(errc::thread_resource_error, thread_category()) ;
+}
+
+} // namespace thread_error
+
+} // namespace boost
+
+
+
+
diff --git a/src/cpp/core/CMakeLists.txt b/src/cpp/core/CMakeLists.txt
new file mode 100644
index 0000000..69ae703
--- /dev/null
+++ b/src/cpp/core/CMakeLists.txt
@@ -0,0 +1,270 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (CORE)
+
+add_subdirectory(spelling/hunspell)
+add_subdirectory(tex/synctex)
+
+# include files
+file(GLOB_RECURSE CORE_HEADER_FILES "*.h*")
+
+# source files
+set (CORE_SOURCE_FILES
+ Assert.cpp
+ Base64.cpp
+ BoostErrors.cpp
+ ConfigUtils.cpp
+ DateTime.cpp
+ Error.cpp
+ Exec.cpp
+ FileInfo.cpp
+ FileLock.cpp
+ FileLogWriter.cpp
+ FilePath.cpp
+ FileSerializer.cpp
+ FileUtils.cpp
+ GitGraph.cpp
+ Hash.cpp
+ HtmlUtils.cpp
+ Log.cpp
+ LogWriter.cpp
+ PerformanceTimer.cpp
+ ProgramOptions.cpp
+ RegexUtils.cpp
+ SafeConvert.cpp
+ Settings.cpp
+ StderrLogWriter.cpp
+ StringUtils.cpp
+ Thread.cpp
+ Trace.cpp
+ WaitUtils.cpp
+ gwt/GwtFileHandler.cpp
+ gwt/GwtLogHandler.cpp
+ gwt/GwtSymbolMaps.cpp
+ json/Json.cpp
+ json/JsonRpc.cpp
+ json/spirit/json_spirit_reader.cpp
+ json/spirit/json_spirit_value.cpp
+ json/spirit/json_spirit_writer.cpp
+ http/Cookie.cpp
+ http/Header.cpp
+ http/Message.cpp
+ http/MultipartRelated.cpp
+ http/Request.cpp
+ http/RequestParser.cpp
+ http/Response.cpp
+ http/SocketProxy.cpp
+ http/URL.cpp
+ http/UriHandler.cpp
+ http/Util.cpp
+ markdown/Markdown.cpp
+ markdown/MathJax.cpp
+ markdown/sundown/autolink.c
+ markdown/sundown/buffer.c
+ markdown/sundown/houdini_href_e.c
+ markdown/sundown/houdini_html_e.c
+ markdown/sundown/html.c
+ markdown/sundown/html_smartypants.c
+ markdown/sundown/markdown.c
+ markdown/sundown/stack.c
+ r_util/RPackageInfo.cpp
+ r_util/RProjectFile.cpp
+ r_util/RTokenizer.cpp
+ r_util/RSourceIndex.cpp
+ r_util/RTokenizerTests.cpp
+ spelling/HunspellCustomDictionaries.cpp
+ spelling/HunspellDictionaryManager.cpp
+ spelling/HunspellSpellingEngine.cpp
+ system/Environment.cpp
+ system/Process.cpp
+ system/ShellUtils.cpp
+ system/System.cpp
+ system/file_monitor/FileMonitor.cpp
+ tex/TexLogParser.cpp
+ tex/TexMagicComment.cpp
+ tex/TexSynctex.cpp
+ text/DcfParser.cpp
+ text/TemplateFilter.cpp
+)
+
+# UNIX specific
+if (UNIX)
+
+ include (CheckCXXSourceCompiles)
+ CHECK_CXX_SOURCE_COMPILES (
+ "# include <dirent.h>
+ int func (const char *d, dirent ***list, void *sort)
+ {
+ int n = scandir(d, list, 0, (int(*)(const dirent **, const dirent **))sort);
+ return n;
+ }
+
+ int main (int, char **)
+ {
+ return 0;
+ }
+ "
+ HAVE_SCANDIR_POSIX)
+
+ # platform introspection
+ check_symbol_exists(SA_NOCLDWAIT "signal.h" HAVE_SA_NOCLDWAIT)
+ check_symbol_exists(SO_PEERCRED "sys/socket.h" HAVE_SO_PEERCRED)
+ check_function_exists(inotify_init1 HAVE_INOTIFY_INIT1)
+ check_function_exists(getpeereid HAVE_GETPEEREID)
+ check_function_exists(setresuid HAVE_SETRESUID)
+ if(EXISTS "/proc/self")
+ set(HAVE_PROCSELF TRUE)
+ endif()
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+ # find packages and libraries
+ find_library(PTHREAD_LIBRARIES pthread)
+ if(NOT APPLE)
+ find_library(UTIL_LIBRARIES util)
+ find_library(UUID_LIBRARIES uuid)
+ find_library(RT_LIBRARIES rt)
+ endif()
+ find_package(ZLIB REQUIRED QUIET)
+
+ # find apple frameworks we depend on
+ if(APPLE)
+ find_library(CORE_SERVICES_LIBRARY NAMES CoreServices)
+ endif()
+
+ # include directories and libraries
+ set (CORE_SYSTEM_LIBRARIES
+ ${PTHREAD_LIBRARIES}
+ ${UTIL_LIBRARIES}
+ ${UUID_LIBRARIES}
+ ${RT_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ ${CORE_SERVICES_LIBRARY}
+ )
+
+ if(RSTUDIO_SERVER)
+ find_package(OpenSSL REQUIRED QUIET)
+ find_package(PAM REQUIRED)
+ set(CORE_SYSTEM_LIBRARIES
+ ${CORE_SYSTEM_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${PAM_LIBRARIES}
+ )
+ set(CORE_INCLUDE_DIRS
+ ${CORE_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ ${PAM_INCLUDE_DIRS}
+ )
+ endif()
+
+ # source files
+ set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
+ ${DIRECTORY_MONITOR_CPP}
+ PosixStringUtils.cpp
+ r_util/REnvironmentPosix.cpp
+ r_util/RSessionLaunchProfile.cpp
+ SyslogLogWriter.cpp
+ system/PosixChildProcessTracker.cpp
+ system/PosixEnvironment.cpp
+ system/PosixFileScanner.cpp
+ system/PosixLibraryLoader.cpp
+ system/PosixParentProcessMonitor.cpp
+ system/PosixOutputCapture.cpp
+ system/PosixShellUtils.cpp
+ system/PosixSystem.cpp
+ system/PosixUser.cpp
+ system/PosixChildProcess.cpp
+ )
+
+ if(RSTUDIO_SERVER)
+ set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
+ system/PosixCrypto.cpp
+ system/Pam.cpp
+ )
+ endif()
+
+ if(APPLE)
+ set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
+ system/file_monitor/MacFileMonitor.cpp
+ system/recycle_bin/MacRecycleBin.cpp
+ )
+ else()
+ set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
+ system/file_monitor/LinuxFileMonitor.cpp
+ system/recycle_bin/LinuxRecycleBin.cpp
+ )
+ endif()
+
+# Win32 specific
+else()
+
+ # embedded version of zlib
+ add_subdirectory(zlib)
+
+ # system libraries
+ set (CORE_SYSTEM_LIBRARIES -lws2_32 -lmswsock -lrpcrt4 -lShlwapi)
+
+ # source files
+ set(CORE_SOURCE_FILES ${CORE_SOURCE_FILES}
+ Win32StringUtils.cpp
+ http/NamedPipeProtocol.cpp
+ system/Win32FileScanner.cpp
+ system/RegistryKey.cpp
+ system/Win32Environment.cpp
+ system/Win32LibraryLoader.cpp
+ system/Win32ParentProcessMonitor.cpp
+ system/Win32OutputCapture.cpp
+ system/Win32ShellUtils.cpp
+ system/Win32System.cpp
+ system/Win32ChildProcess.cpp
+ system/file_monitor/Win32FileMonitor.cpp
+ system/recycle_bin/Win32RecycleBin.cpp
+ r_util/RToolsInfo.cpp
+ )
+
+endif()
+
+# define include dirs
+set(CORE_INCLUDE_DIRS ${CORE_INCLUDE_DIRS} include)
+
+# search for core addins
+if(RSTUDIO_ADDINS_PATH)
+ set(CORE_ADDIN_PATH ${RSTUDIO_ADDINS_PATH}/core)
+ if(EXISTS ${CORE_ADDIN_PATH})
+ file(GLOB_RECURSE ADDIN_HEADER_FILES "${CORE_ADDIN_PATH}/*.h*")
+ list(APPEND CORE_HEADER_FILES ${ADDIN_HEADER_FILES})
+ file(GLOB_RECURSE ADDIN_SOURCE_FILES "${CORE_ADDIN_PATH}/*.c*")
+ list(APPEND CORE_SOURCE_FILES ${ADDIN_SOURCE_FILES})
+ list(APPEND CORE_INCLUDE_DIRS ${CORE_ADDIN_PATH}/include)
+ endif()
+endif()
+
+
+# include directories
+include_directories(
+ ${CORE_INCLUDE_DIRS}
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# define shared library
+add_library(rstudio-core STATIC ${CORE_SOURCE_FILES} ${CORE_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(
+ rstudio-core
+ ${Boost_LIBRARIES}
+ ${CORE_SYSTEM_LIBRARIES}
+)
diff --git a/src/cpp/core/ConfigUtils.cpp b/src/cpp/core/ConfigUtils.cpp
new file mode 100644
index 0000000..6e6374d
--- /dev/null
+++ b/src/cpp/core/ConfigUtils.cpp
@@ -0,0 +1,79 @@
+/*
+ * ConfigUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/ConfigUtils.hpp>
+
+#include <algorithm>
+
+#include <boost/regex.hpp>
+#include <boost/bind.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+namespace core {
+namespace config_utils {
+
+namespace {
+
+void extractToMap(const std::string& keyAndValue,
+ std::map<std::string,std::string>* pMap)
+{
+ std::string::size_type pos = keyAndValue.find("=") ;
+ if ( pos != std::string::npos )
+ {
+ std::string key = keyAndValue.substr(0, pos) ;
+ boost::algorithm::trim(key);
+ std::string value = keyAndValue.substr(pos + 1) ;
+ boost::algorithm::trim(value) ;
+ boost::algorithm::replace_all(value, "\"", "");
+ pMap->operator[](key) = value;
+ }
+}
+
+}
+
+void extractVariables(const std::string& vars, Variables* pVariables)
+{
+ // scan for variables via regex iterator
+ boost::regex var("^([A-Za-z0-9_]+=[^\n]+)$");
+ boost::sregex_token_iterator it(vars.begin(), vars.end(), var, 0);
+ boost::sregex_token_iterator end;
+ std::for_each(it, end, boost::bind(extractToMap, _1, pVariables));
+}
+
+Error extractVariables(const FilePath& file, Variables* pVariables)
+{
+ // read in the file
+ std::string contents;
+ Error error = readStringFromFile(file,
+ &contents,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+ extractVariables(contents, pVariables);
+
+ return Success();
+}
+
+} // namespace config_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/DateTime.cpp b/src/cpp/core/DateTime.cpp
new file mode 100644
index 0000000..8bcc493
--- /dev/null
+++ b/src/cpp/core/DateTime.cpp
@@ -0,0 +1,95 @@
+/*
+ * DateTime.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/DateTime.hpp>
+
+namespace core {
+namespace date_time {
+
+double secondsSinceEpoch()
+{
+ return millisecondsSinceEpoch() / 1000;
+}
+
+double secondsSinceEpoch(const boost::posix_time::ptime& time)
+{
+ return millisecondsSinceEpoch(time) / 1000;
+}
+
+double secondsSinceEpoch(std::time_t time)
+{
+ return millisecondsSinceEpoch(time) / 1000;
+}
+
+double millisecondsSinceEpoch()
+{
+ return millisecondsSinceEpoch(
+ boost::posix_time::microsec_clock::universal_time());
+}
+
+double millisecondsSinceEpoch(const boost::posix_time::ptime& time)
+{
+ using namespace boost::gregorian;
+ using namespace boost::posix_time;
+
+ ptime time_t_epoch(date(1970,1,1));
+ time_duration diff = time - time_t_epoch;
+ return diff.total_milliseconds();
+}
+
+double millisecondsSinceEpoch(std::time_t time)
+{
+ return std::difftime(time, 0) * 1000;
+}
+
+boost::posix_time::ptime timeFromSecondsSinceEpoch(double sec)
+{
+ using namespace boost::gregorian;
+ using namespace boost::posix_time;
+
+ ptime time_t_epoch(date(1970,1,1));
+ return time_t_epoch + seconds(sec);
+}
+
+boost::posix_time::ptime timeFromMillisecondsSinceEpoch(int64_t ms)
+{
+ using namespace boost::gregorian;
+ using namespace boost::posix_time;
+
+ ptime time_t_epoch(date(1970,1,1));
+ return time_t_epoch + milliseconds(ms);
+}
+
+std::string format(const boost::posix_time::ptime& datetime,
+ const std::string& format)
+{
+ using namespace boost::posix_time;
+
+ // facet for http date (construct w/ a_ref == 1 so we manage memory)
+ time_facet httpDateFacet(1);
+ httpDateFacet.format(format.c_str());
+
+ // output and return the date
+ std::ostringstream dateStream;
+ dateStream.imbue(std::locale(dateStream.getloc(), &httpDateFacet));
+ dateStream << datetime;
+ return dateStream.str();
+}
+
+} // namespace date_time
+} // namespace core
+
+
+
diff --git a/src/cpp/core/Error.cpp b/src/cpp/core/Error.cpp
new file mode 100644
index 0000000..8176d6b
--- /dev/null
+++ b/src/cpp/core/Error.cpp
@@ -0,0 +1,303 @@
+/*
+ * Error.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <ostream>
+#include <sstream>
+
+#include <core/Error.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <boost/lexical_cast.hpp>
+
+#ifdef _WIN32
+#include <boost/system/windows_error.hpp>
+#endif
+
+namespace core {
+
+struct Error::Impl
+{
+ boost::system::error_code ec ;
+ ErrorProperties properties ;
+ Error cause ;
+ ErrorLocation location ;
+};
+
+Error::Error()
+ : pImpl_()
+{
+}
+
+Error::Error(const boost::system::error_code& ec,
+ const ErrorLocation& location)
+ : pImpl_(new Impl())
+{
+ pImpl_->ec = ec ;
+ pImpl_->location = location ;
+}
+
+Error::Error(const boost::system::error_code& ec,
+ const Error& cause,
+ const ErrorLocation& location)
+ : pImpl_(new Impl())
+{
+ pImpl_->ec = ec ;
+ pImpl_->cause = cause ;
+ pImpl_->location = location ;
+}
+
+
+Error::~Error()
+{
+}
+
+void Error::copyOnWrite()
+{
+ if ( (pImpl_.get() != NULL) && !pImpl_.unique())
+ {
+ Impl* pOldImpl = pImpl_.get() ;
+ pImpl_ = boost::shared_ptr<Impl>(new Impl(*pOldImpl)) ;
+ }
+}
+
+const boost::system::error_code& Error::code() const
+{
+ return impl().ec;
+}
+
+
+std::string Error::summary() const
+{
+ std::ostringstream ostr;
+ ostr << code().category().name() << " error "
+ << code().value() << " (" << code().message() << ")" ;
+ return ostr.str();
+}
+
+const Error& Error::cause() const
+{
+ return impl().cause ;
+}
+
+const ErrorLocation& Error::location() const
+{
+ return impl().location ;
+}
+
+void Error::addProperty(const std::string& name, const std::string& value)
+{
+ copyOnWrite() ;
+ impl().properties.push_back(std::make_pair(name, value)) ;
+}
+
+void Error::addProperty(const std::string& name, const FilePath& value)
+{
+ addProperty(name, value.absolutePath());
+}
+
+void Error::addProperty(const std::string& name, int value)
+{
+ addProperty(name, safe_convert::numberToString(value));
+}
+
+
+const std::vector<std::pair<std::string,std::string> >&
+ Error::properties() const
+{
+ return impl().properties ;
+}
+
+std::string Error::getProperty(const std::string& name) const
+{
+ for (ErrorProperties::const_iterator it = properties().begin();
+ it != properties().end(); ++it)
+ {
+ if (it->first == name)
+ return it->second;
+ }
+
+ return std::string();
+}
+
+bool Error::isError() const
+{
+ if ( pImpl_.get() != NULL )
+ return pImpl_->ec.value() != 0 ;
+ else
+ return false ;
+}
+
+Error::Impl& Error::impl() const
+{
+ if (pImpl_.get() == NULL)
+ pImpl_.reset(new Impl()) ;
+ return *pImpl_ ;
+}
+
+Error systemError(int value, const ErrorLocation& location)
+{
+ using namespace boost::system ;
+ return Error(error_code(value, get_system_category()), location);
+}
+
+Error systemError(int value,
+ const std::string& description,
+ const ErrorLocation& location)
+{
+ Error error = systemError(value, location);
+ error.addProperty("description", description);
+ return error;
+}
+
+Error fileExistsError(const ErrorLocation& location)
+{
+#ifdef _WIN32
+ return systemError(boost::system::windows_error::file_exists, location);
+#else
+ return systemError(boost::system::errc::file_exists, location);
+#endif
+}
+
+Error fileNotFoundError(const ErrorLocation& location)
+{
+#ifdef _WIN32
+ return systemError(boost::system::windows_error::file_not_found, location);
+#else
+ return systemError(boost::system::errc::no_such_file_or_directory, location);
+#endif
+}
+
+Error fileNotFoundError(const std::string& path,
+ const ErrorLocation& location)
+{
+ Error error = fileNotFoundError(location);
+ error.addProperty("path", path);
+ return error;
+}
+
+Error fileNotFoundError(const FilePath& filePath,
+ const ErrorLocation& location)
+{
+ Error error = fileNotFoundError(location);
+ error.addProperty("path", filePath);
+ return error;
+}
+
+bool isPathNotFoundError(const Error& error)
+{
+#ifdef _WIN32
+ return error.code() == boost::system::windows_error::path_not_found;
+#else
+ return error.code() == boost::system::errc::no_such_file_or_directory;
+#endif
+}
+
+Error pathNotFoundError(const ErrorLocation& location)
+{
+#ifdef _WIN32
+ return systemError(boost::system::windows_error::path_not_found, location);
+#else
+ return systemError(boost::system::errc::no_such_file_or_directory, location);
+#endif
+}
+
+Error pathNotFoundError(const std::string& path, const ErrorLocation& location)
+{
+ Error error = pathNotFoundError(location);
+ error.addProperty("path", path);
+ return error;
+}
+
+struct ErrorLocation::Impl
+{
+ Impl() : line(0)
+ {
+ }
+
+ Impl(const char* function, const char* file, long line)
+ : function(function), file(file), line(line)
+ {
+ }
+
+ std::string function ;
+ std::string file ;
+ long line ;
+};
+
+ErrorLocation::ErrorLocation()
+ : pImpl_(new Impl())
+{
+}
+
+ErrorLocation::ErrorLocation(const char* function, const char* file, long line)
+ : pImpl_(new Impl(function, file, line))
+{
+}
+
+ErrorLocation::~ErrorLocation()
+{
+}
+
+
+bool ErrorLocation::hasLocation() const
+{
+ return line() > 0 ;
+}
+
+const std::string& ErrorLocation::function() const
+{
+ return pImpl_->function ;
+}
+
+const std::string& ErrorLocation::file() const
+{
+ return pImpl_->file ;
+}
+
+long ErrorLocation::line() const
+{
+ return pImpl_->line ;
+}
+
+std::string ErrorLocation::asString() const
+{
+ std::ostringstream ostr ;
+ ostr << *this;
+ return ostr.str();
+}
+
+bool ErrorLocation::operator==(const ErrorLocation& location) const
+{
+ return function() == location.function() &&
+ file() == location.file() &&
+ line() == location.line();
+}
+
+std::ostream& operator<<(std::ostream& os, const ErrorLocation& location)
+{
+ os << location.function() << " "
+ << location.file() << ":"
+ << location.line() ;
+
+ return os;
+}
+
+
+
+} // namespace core
+
+
diff --git a/src/cpp/core/Exec.cpp b/src/cpp/core/Exec.cpp
new file mode 100644
index 0000000..c84a7b2
--- /dev/null
+++ b/src/cpp/core/Exec.cpp
@@ -0,0 +1,48 @@
+/*
+ * Exec.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Exec.hpp>
+
+#include <core/Error.hpp>
+
+namespace core {
+
+ExecBlock& ExecBlock::add(Function function)
+{
+ functions_.push_back(function);
+ return *this;
+}
+
+Error ExecBlock::execute() const
+{
+ for (std::vector<Function>::const_iterator
+ it = functions_.begin(); it != functions_.end(); ++it)
+ {
+ Error error = (*it)();
+ if (error)
+ return error ;
+ }
+ return Success();
+}
+
+Error ExecBlock::operator()() const
+{
+ return execute();
+}
+
+} // namespace core
+
+
+
diff --git a/src/cpp/core/FileInfo.cpp b/src/cpp/core/FileInfo.cpp
new file mode 100644
index 0000000..09db772
--- /dev/null
+++ b/src/cpp/core/FileInfo.cpp
@@ -0,0 +1,72 @@
+/*
+ * FileInfo.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FileInfo.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+FileInfo::FileInfo(const FilePath& filePath, bool isSymlink)
+ : absolutePath_(filePath.absolutePath()),
+ isDirectory_(filePath.isDirectory()),
+ size_(0),
+ lastWriteTime_(0),
+ isSymlink_(isSymlink)
+{
+ if (!isDirectory_ && filePath.exists())
+ {
+ size_ = filePath.size();
+ lastWriteTime_ = filePath.lastWriteTime();
+ }
+}
+
+FileInfo::FileInfo(const std::string& absolutePath,
+ bool isDirectory,
+ bool isSymlink)
+ : absolutePath_(absolutePath),
+ isDirectory_(isDirectory),
+ size_(0),
+ lastWriteTime_(0),
+ isSymlink_(isSymlink)
+{
+}
+
+FileInfo::FileInfo(const std::string& absolutePath,
+ bool isDirectory,
+ uintmax_t size,
+ std::time_t lastWriteTime,
+ bool isSymlink)
+ : absolutePath_(absolutePath),
+ isDirectory_(isDirectory),
+ size_(size),
+ lastWriteTime_(lastWriteTime),
+ isSymlink_(isSymlink)
+{
+}
+
+std::ostream& operator << (std::ostream& stream, const FileInfo& fileInfo)
+{
+ stream << fileInfo.absolutePath();
+ return stream ;
+}
+
+
+
+
+} // namespace core
+
+
+
diff --git a/src/cpp/core/FileLock.cpp b/src/cpp/core/FileLock.cpp
new file mode 100644
index 0000000..de40f3e
--- /dev/null
+++ b/src/cpp/core/FileLock.cpp
@@ -0,0 +1,175 @@
+/*
+ * FileLock.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FileLock.hpp>
+
+#include <boost/scope_exit.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/StringUtils.hpp>
+
+#include <core/BoostErrors.hpp>
+
+// we define BOOST_USE_WINDOWS_H on mingw64 to work around some
+// incompatabilities. however, this prevents the interprocess headers
+// from compiling so we undef it in this localized context
+#if defined(__GNUC__) && defined(_WIN64)
+ #undef BOOST_USE_WINDOWS_H
+#endif
+#include <boost/interprocess/sync/file_lock.hpp>
+
+namespace core {
+
+bool FileLock::isLocked(const FilePath& lockFilePath)
+{
+ using namespace boost::interprocess;
+
+ // if the lock file doesn't exist then it's not locked
+ if (!lockFilePath.exists())
+ return false;
+
+ // check if it is locked
+ try
+ {
+ file_lock lock(string_utils::utf8ToSystem(lockFilePath.absolutePath()).c_str());
+
+ if (lock.try_lock())
+ {
+ lock.unlock();
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ catch(boost::interprocess::interprocess_exception& e)
+ {
+ Error error(boost::interprocess::ec_from_exception(e), ERROR_LOCATION);
+ error.addProperty("lock-file", lockFilePath);
+ LOG_ERROR(error);
+ return false;
+ }
+}
+
+
+struct FileLock::Impl
+{
+ FilePath lockFilePath;
+ boost::interprocess::file_lock lock;
+};
+
+FileLock::FileLock()
+ : pImpl_(new Impl())
+{
+}
+
+FileLock::~FileLock()
+{
+}
+
+Error FileLock::acquire(const FilePath& lockFilePath)
+{
+ using namespace boost::interprocess;
+
+ // make sure the lock file exists
+ if (!lockFilePath.exists())
+ {
+ Error error = core::writeStringToFile(lockFilePath, "");
+ if (error)
+ return error;
+ }
+
+ // try to acquire the lock
+ try
+ {
+ file_lock lock(string_utils::utf8ToSystem(lockFilePath.absolutePath()).c_str());
+
+ if (lock.try_lock())
+ {
+ // set members
+ pImpl_->lockFilePath = lockFilePath;
+ pImpl_->lock.swap(lock);
+
+ return Success();
+ }
+ else
+ {
+ return systemError(boost::system::errc::no_lock_available,
+ ERROR_LOCATION);
+ }
+ }
+ catch(interprocess_exception& e)
+ {
+ Error error(ec_from_exception(e), ERROR_LOCATION);
+ error.addProperty("lock-file", lockFilePath);
+ return error;
+ }
+
+ return Success();
+}
+
+Error FileLock::release()
+{
+ using namespace boost::interprocess;
+
+ // make sure the lock file exists
+ if (!pImpl_->lockFilePath.exists())
+ {
+ return systemError(boost::system::errc::no_lock_available,
+ ERROR_LOCATION);
+ }
+
+ // always cleanup the lock file on exit
+ FilePath lockFilePath = pImpl_->lockFilePath;
+ BOOST_SCOPE_EXIT( (&lockFilePath) )
+ {
+ Error error = lockFilePath.remove();
+ if (error)
+ LOG_ERROR(error);
+ }
+ BOOST_SCOPE_EXIT_END
+
+ // try to unlock it
+ try
+ {
+ pImpl_->lock.unlock();
+ pImpl_->lock = file_lock();
+ pImpl_->lockFilePath = FilePath();
+ return Success();
+ }
+ catch(interprocess_exception& e)
+ {
+ Error error(ec_from_exception(e), ERROR_LOCATION);
+ error.addProperty("lock-file", pImpl_->lockFilePath);
+ return error;
+ }
+
+ return Success();
+}
+
+FilePath FileLock::lockFilePath() const
+{
+ return pImpl_->lockFilePath;
+}
+
+
+} // namespace core
+
+
+
diff --git a/src/cpp/core/FileLogWriter.cpp b/src/cpp/core/FileLogWriter.cpp
new file mode 100644
index 0000000..acad952
--- /dev/null
+++ b/src/cpp/core/FileLogWriter.cpp
@@ -0,0 +1,93 @@
+/*
+ * FileLogWriter.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FileLogWriter.hpp>
+
+
+#include <core/FileInfo.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+
+FileLogWriter::FileLogWriter(const std::string& programIdentity,
+ int logLevel,
+ const FilePath& logDir)
+ : programIdentity_(programIdentity),
+ logLevel_(logLevel)
+{
+ logDir.ensureDirectory();
+
+ logFile_ = logDir.childPath(programIdentity + ".log");
+ rotatedLogFile_ = logDir.childPath(programIdentity + ".rotated.log");
+
+ if (!logFile_.exists())
+ {
+ // swallow errors -- we can't log so it doesn't matter
+ core::appendToFile(logFile_, "");
+ }
+}
+
+FileLogWriter::~FileLogWriter()
+{
+ try
+ {
+ // we don't keep a file handle open so do nothing here
+ }
+ catch(...)
+ {
+ }
+}
+
+void FileLogWriter::log(core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ log(programIdentity_, logLevel, message);
+}
+
+void FileLogWriter::log(const std::string& programIdentity,
+ core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ if (logLevel > logLevel_)
+ return;
+
+ rotateLogFile();
+
+ // Swallow errors--we can't do anything anyway
+ core::appendToFile(logFile_, formatLogEntry(programIdentity, message));
+}
+
+
+
+#define LOGMAX (2048*1024) // rotate/remove every 2 megabytes
+bool FileLogWriter::rotateLogFile()
+{
+ if (logFile_.exists() && logFile_.size() > LOGMAX)
+ {
+ // first remove the rotated log file if it exists (ignore errors because
+ // there's nothing we can do with them at this level)
+ rotatedLogFile_.removeIfExists();
+
+ // now rotate the log file
+ logFile_.move(rotatedLogFile_);
+
+ return true;
+ }
+ return false;
+}
+
+
+} // namespace core
diff --git a/src/cpp/core/FilePath.cpp b/src/cpp/core/FilePath.cpp
new file mode 100644
index 0000000..7d589ab
--- /dev/null
+++ b/src/cpp/core/FilePath.cpp
@@ -0,0 +1,1131 @@
+/*
+ * FilePath.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FilePath.hpp>
+
+#include <algorithm>
+#include <fstream>
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#define BOOST_FILESYSTEM_NO_DEPRECATED
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <boost/filesystem.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <boost/iostreams/device/file_descriptor.hpp>
+
+#include <core/StringUtils.hpp>
+#include <core/system/System.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+
+typedef boost::filesystem::path path_t;
+
+namespace core {
+
+namespace {
+
+// We use boost::filesystem in one of three different ways:
+// - On Windows, we use Filesystem v3 with wide character paths. This is
+// because narrow character paths on Windows can't cover the entire
+// Unicode space since there is no ANSI code page for UTF-8.
+// - On non-Windows, if Filesystem v3 is available, we use it.
+// - Otherwise, we use Filesystem v2. This is necessary for older versions
+// of Boost that come preinstalled on some long-term-stable Linux distro
+// versions that we still want to support.
+#ifdef _WIN32
+
+#define BOOST_FS_STRING(path) toString((path).generic_wstring())
+#define BOOST_FS_PATH2STR(path) toString((path).generic_wstring())
+#define BOOST_FS_PATH2STRNATIVE(path) toString((path).wstring())
+#define BOOST_FS_COMPLETE(p, base) boost::filesystem::absolute(fromString(p), base)
+typedef boost::filesystem::directory_iterator dir_iterator;
+typedef boost::filesystem::recursive_directory_iterator recursive_dir_iterator;
+
+#elif defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION != 2
+
+#define BOOST_FS_STRING(path) ((path).generic_string())
+#define BOOST_FS_PATH2STR(path) ((path).generic_string())
+#define BOOST_FS_PATH2STRNATIVE(path) ((path).generic_string())
+#define BOOST_FS_COMPLETE(p, base) boost::filesystem::absolute(p, base)
+typedef boost::filesystem::directory_iterator dir_iterator;
+typedef boost::filesystem::recursive_directory_iterator recursive_dir_iterator;
+
+#else
+
+#define BOOST_FS_STRING(str) (str)
+#define BOOST_FS_PATH2STR(str) ((str).string())
+#define BOOST_FS_PATH2STRNATIVE(str) ((str).string())
+#define BOOST_FS_COMPLETE(p, base) boost::filesystem::complete(p, base)
+typedef boost::filesystem::basic_directory_iterator<path_t> dir_iterator;
+typedef boost::filesystem::basic_recursive_directory_iterator<path_t> recursive_dir_iterator;
+
+#endif
+
+
+
+#ifdef _WIN32
+
+// For Windows only, we need to use the wide character versions of the file
+// APIs in order to deal properly with characters that cannot be represented
+// in the default system encoding. (It would be preferable if UTF-8 were the
+// system encoding, but Windows doesn't support that.) However, we can't give
+// FilePath a wide character API because Mac needs to use narrow characters
+// (see note below). So we use wstring internally, and translate to/from UTF-8
+// narrow strings that are used in the API.
+
+typedef std::wstring internal_string;
+
+std::string toString(const internal_string& value)
+{
+ return string_utils::wideToUtf8(value);
+}
+
+internal_string fromString(const std::string& value)
+{
+ return string_utils::utf8ToWide(value);
+}
+
+#else
+
+// We only support running with UTF-8 codeset on Mac and Linux, so
+// strings are a passthrough.
+
+typedef std::string internal_string;
+
+internal_string fromString(const std::string& value)
+{
+ return value;
+}
+
+#endif
+
+void logError(path_t path,
+ const boost::filesystem::filesystem_error& e,
+ const ErrorLocation& errorLocation);
+void addErrorProperties(path_t path, Error* pError) ;
+}
+
+struct FilePath::Impl
+{
+ Impl()
+ {
+ }
+ Impl(path_t path)
+ : path(path)
+ {
+ }
+ path_t path ;
+};
+
+FilePath FilePath::safeCurrentPath(const FilePath& revertToPath)
+{
+ try
+ {
+#ifdef _WIN32
+ return FilePath(boost::filesystem::current_path().wstring()) ;
+#elif defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION != 2
+ return FilePath(boost::filesystem::current_path().string()) ;
+#else
+ return FilePath(boost::filesystem::current_path<path_t>().string()) ;
+#endif
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ if (e.code() != boost::system::errc::no_such_file_or_directory)
+ LOG_ERROR(Error(e.code(), ERROR_LOCATION));
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // revert to the specified path if it exists, otherwise
+ // take the user home path from the system
+ FilePath safePath = revertToPath;
+ if (!safePath.exists())
+ safePath = core::system::userHomePath();
+
+ Error error = safePath.makeCurrentPath();
+ if (error)
+ LOG_ERROR(error);
+
+ return safePath;
+}
+
+Error FilePath::makeCurrent(const std::string& path)
+{
+ return FilePath(path).makeCurrentPath();
+}
+
+#define kHomePathAlias "~/"
+#define kHomePathLeafAlias "~"
+
+std::string FilePath::createAliasedPath(const FilePath& path,
+ const FilePath& userHomePath)
+{
+ // Special case for "~"
+ if (path == userHomePath)
+ return kHomePathLeafAlias;
+
+ // if the path is contained within the home path then alias it
+ std::string homeRelativePath = path.relativePath(userHomePath);
+ if (!homeRelativePath.empty())
+ {
+ std::string aliasedPath = kHomePathAlias + homeRelativePath;
+ return aliasedPath;
+ }
+ else // no aliasing
+ {
+ return path.absolutePath();
+ }
+}
+
+FilePath FilePath::resolveAliasedPath(const std::string& aliasedPath,
+ const FilePath& userHomePath)
+{
+ // Special case for empty string or "~"
+ if (aliasedPath.empty() || (aliasedPath.compare(kHomePathLeafAlias) == 0))
+ return userHomePath;
+
+ // if the path starts with the home alias then substitute the home path
+ if (aliasedPath.find(kHomePathAlias) == 0)
+ {
+ std::string resolvedPath = userHomePath.absolutePath() +
+ aliasedPath.substr(1);
+ return FilePath(resolvedPath);
+ }
+ else // no aliasing, this is either an absolute path or path
+ // relative to the current directory
+ {
+ return FilePath::safeCurrentPath(userHomePath).complete(aliasedPath);
+ }
+}
+
+bool FilePath::exists(const std::string& path)
+{
+ if (path.empty())
+ return false;
+
+ path_t p(fromString(path));
+ try
+ {
+ return boost::filesystem::exists(p);
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(p, e, ERROR_LOCATION) ;
+ return false ;
+ }
+}
+
+bool FilePath::isRootPath(const std::string& path)
+{
+ if (path.empty())
+ return false;
+
+ path_t p(fromString(path));
+ try
+ {
+ return p.has_root_path();
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(p, e, ERROR_LOCATION) ;
+ return false ;
+ }
+}
+
+Error FilePath::tempFilePath(FilePath* pFilePath)
+{
+ using namespace boost::filesystem;
+ try
+ {
+ path_t path = absolute(unique_path(), temp_directory_path());
+ *pFilePath = FilePath(BOOST_FS_PATH2STR(path));
+ return Success();
+ }
+ catch(const filesystem_error& e)
+ {
+ return Error(e.code(), ERROR_LOCATION) ;
+ }
+
+ // keep compiler happy
+ return pathNotFoundError(ERROR_LOCATION);
+}
+
+FilePath::FilePath()
+ : pImpl_(new Impl())
+{
+}
+
+FilePath::FilePath(const std::string& absolutePath)
+ : pImpl_(new Impl(fromString(std::string(absolutePath.c_str())))) // thwart ref-count
+{
+}
+
+#if _WIN32
+FilePath::FilePath(const std::wstring& absolutePath)
+ : pImpl_(new Impl(absolutePath)) // thwart ref-count
+{
+}
+#endif
+
+FilePath::~FilePath()
+{
+}
+
+
+bool FilePath::empty() const
+{
+ return pImpl_->path.empty() ;
+}
+
+bool FilePath::exists() const
+{
+ try
+ {
+ return !empty() && boost::filesystem::exists(pImpl_->path) ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(pImpl_->path, e, ERROR_LOCATION) ;
+ return false ;
+ }
+}
+
+bool FilePath::isSymlink() const
+{
+ try
+ {
+ return exists() && boost::filesystem::is_symlink(pImpl_->path);
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(pImpl_->path, e, ERROR_LOCATION);
+ return false;
+ }
+}
+
+uintmax_t FilePath::size() const
+{
+ try
+ {
+ if (!exists())
+ return 0;
+ else
+ return boost::filesystem::file_size(pImpl_->path) ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+#ifdef _WIN32
+ if (e.code().value() == ERROR_NOT_SUPPORTED)
+ return 0;
+#endif
+ logError(pImpl_->path, e, ERROR_LOCATION) ;
+ return 0;
+ }
+}
+
+std::string FilePath::filename() const
+{
+ return BOOST_FS_STRING(pImpl_->path.filename()) ;
+}
+
+std::string FilePath::stem() const
+{
+ return BOOST_FS_STRING(pImpl_->path.stem());
+}
+
+std::string FilePath::extension() const
+{
+ return BOOST_FS_STRING(pImpl_->path.extension()) ;
+}
+
+std::string FilePath::extensionLowerCase() const
+{
+ return string_utils::toLower(extension());
+}
+
+bool FilePath::hasExtension(const std::string& ext) const
+{
+ return extension() == ext;
+}
+
+bool FilePath::hasExtensionLowerCase(const std::string& ext) const
+{
+ return extensionLowerCase() == ext;
+}
+
+namespace {
+
+struct MimeType
+{
+ const char* extension;
+ const char* contentType;
+} ;
+
+// NOTE: should be synced with mime type database in FileSystemItem.java
+MimeType s_mimeTypes[] =
+{
+ // most common web types
+ { "htm", "text/html" },
+ { "html", "text/html" },
+ { "css", "text/css" },
+ { "gif", "image/gif" },
+ { "jpg", "image/jpeg" },
+ { "jpeg", "image/jpeg" },
+ { "jpe", "image/jpeg" },
+ { "png", "image/png" },
+ { "js", "text/javascript" },
+ { "pdf", "application/pdf" },
+ { "svg", "image/svg+xml" },
+ { "swf", "application/x-shockwave-flash" },
+ { "ttf", "application/x-font-ttf" },
+ { "woff", "application/font-woff" },
+
+ // markdown types
+ { "md", "text/x-markdown" },
+ { "mdtxt", "text/x-markdown" },
+ { "markdown", "text/x-markdown" },
+
+ // programming language types
+ { "f", "text/x-fortran" },
+
+ // other types we are likely to serve
+ { "xml", "text/xml" },
+ { "csv", "text/csv" },
+ { "ico", "image/x-icon" },
+ { "zip", "application/zip" },
+ { "bz", "application/x-bzip" },
+ { "bz2", "application/x-bzip2" },
+ { "gz", "application/x-gzip" },
+ { "tar", "application/x-tar" },
+
+ // yet more types...
+
+ { "shtml", "text/html" },
+ { "tsv", "text/tab-separated-values" },
+ { "tab", "text/tab-separated-values" },
+ { "dcf", "text/debian-control-file" },
+ { "txt", "text/plain" },
+ { "mml", "text/mathml" },
+ { "log", "text/plain" },
+ { "out", "text/plain" },
+ { "R", "text/x-r-source"},
+ { "Rd", "text/x-r-doc"},
+ { "Rnw", "text/x-r-sweave"},
+ { "Rmd", "text/x-r-markdown"},
+ { "Rhtml", "text/x-r-html"},
+ { "Rpres", "text/x-r-presentation"},
+ { "Rout", "text/plain" },
+ { "po", "text/plain" },
+ { "pot", "text/plain"},
+ { "gitignore", "text/plain"},
+ { "Rbuildignore", "text/plain"},
+
+ { "tif", "image/tiff" },
+ { "tiff", "image/tiff" },
+ { "bmp", "image/bmp" },
+ { "ps", "application/postscript" },
+ { "eps", "application/postscript" },
+ { "dvi" "application/x-dvi" },
+
+ { "atom", "application/atom+xml" },
+ { "rss", "application/rss+xml" },
+
+ { "doc", "application/msword" },
+ { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
+ { "odt", "application/vnd.oasis.opendocument.text" },
+ { "rtf", "application/rtf" },
+ { "xls", "application/vnd.ms-excel" },
+ { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
+ { "ods", "application/x-vnd.oasis.opendocument.spreadsheet" },
+ { "ppt", "application/vnd.ms-powerpoint" },
+ { "pps", "application/vnd.ms-powerpoint" },
+ { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
+
+ { "sit", "application/x-stuffit" },
+ { "sxw", "application/vnd.sun.xml.writer" },
+
+ { "iso", "application/octet-stream" },
+ { "dmg", "application/octet-stream" },
+ { "exe", "application/octet-stream" },
+ { "dll", "application/octet-stream" },
+ { "deb", "application/octet-stream" },
+ { "otf", "application/octet-stream" },
+ { "xpi", "application/x-xpinstall" },
+
+ { "mp2", "audio/mpeg" },
+
+ { "mpg", "video/mpeg" },
+ { "mpeg", "video/mpeg" },
+ { "flv", "video/x-flv" },
+
+ { "mp4", "video/mp4" },
+ { "webm", "video/webm" },
+ { "ogv", "video/ogg" },
+
+ { "mp3", "audio/mp3" },
+ { "wav", "audio/wav" },
+ { "oga", "audio/ogg" },
+ { "ogg", "audio/ogg" },
+
+ { NULL, NULL }
+};
+
+}
+
+std::string FilePath::mimeContentType(const std::string& defaultType) const
+{
+ std::string ext = extensionLowerCase();
+ if (!ext.empty())
+ {
+ ext = ext.substr(1); // remove leading .
+ for (MimeType* mimeType = s_mimeTypes; mimeType->extension; ++mimeType)
+ {
+ if (boost::algorithm::iequals(mimeType->extension,ext))
+ return mimeType->contentType;
+ }
+
+ // none found
+ return defaultType;
+ }
+ else
+ {
+ // no extension
+ return defaultType;
+ }
+}
+
+bool FilePath::hasTextMimeType() const
+{
+ std::string mimeType = mimeContentType("application/octet-stream");
+ return boost::algorithm::starts_with(mimeType, "text/") ||
+ boost::algorithm::ends_with(mimeType, "+xml");
+}
+
+std::time_t FilePath::lastWriteTime() const
+{
+ try
+ {
+ if (!exists())
+ return 0;
+ else
+ return boost::filesystem::last_write_time(pImpl_->path) ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(pImpl_->path, e, ERROR_LOCATION) ;
+ return 0;
+ }
+}
+
+// NOTE: this does not properly handle .. and . path elements
+std::string FilePath::relativePath(const FilePath& parentPath) const
+{
+ // get iterators to this path and parent path
+ path_t::iterator thisBegin = pImpl_->path.begin() ;
+ path_t::iterator thisEnd = pImpl_->path.end() ;
+ path_t::iterator parentBegin = parentPath.pImpl_->path.begin();
+ path_t::iterator parentEnd = parentPath.pImpl_->path.end() ;
+
+ // if the child is fully prefixed by the parent
+ path_t::iterator it = std::search(thisBegin, thisEnd, parentBegin, parentEnd);
+ if ( it == thisBegin )
+ {
+ // search for mismatch location
+ std::pair<path_t::iterator,path_t::iterator> mmPair =
+ std::mismatch(thisBegin, thisEnd, parentBegin);
+
+ // build relative path from mismatch on
+ path_t relativePath ;
+ path_t::iterator mmit = mmPair.first ;
+ while (mmit != thisEnd)
+ {
+ relativePath /= *mmit ;
+ mmit++ ;
+ }
+ return BOOST_FS_PATH2STR(relativePath) ;
+ }
+ else
+ {
+ return std::string() ;
+ }
+}
+
+bool FilePath::isWithin(const FilePath& scopePath) const
+{
+ if (*this == scopePath)
+ return true ;
+
+ std::string relativePath = this->relativePath(scopePath);
+ return !relativePath.empty();
+}
+
+
+std::string FilePath::absolutePath() const
+{
+ if (empty())
+ return std::string();
+ else
+ return BOOST_FS_PATH2STR(pImpl_->path) ;
+}
+
+std::string FilePath::absolutePathNative() const
+{
+ if (empty())
+ return std::string();
+ else
+ return BOOST_FS_PATH2STRNATIVE(pImpl_->path) ;
+}
+
+#if _WIN32
+std::wstring FilePath::absolutePathW() const
+{
+ if (empty())
+ return std::wstring();
+ else
+ return pImpl_->path.wstring();
+}
+#endif
+
+Error FilePath::remove() const
+{
+ try
+ {
+ if (isDirectory())
+ boost::filesystem::remove_all(pImpl_->path);
+ else
+ boost::filesystem::remove(pImpl_->path) ;
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ return error ;
+ }
+}
+
+Error FilePath::removeIfExists() const
+{
+ if (exists())
+ return remove();
+ else
+ return Success();
+}
+
+Error FilePath::move(const FilePath& targetPath) const
+{
+ try
+ {
+ boost::filesystem::rename(pImpl_->path, targetPath.pImpl_->path) ;
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("target-path", targetPath.absolutePath()) ;
+ return error ;
+ }
+}
+
+
+Error FilePath::copy(const FilePath& targetPath) const
+{
+ try
+ {
+ boost::filesystem::copy_file(pImpl_->path, targetPath.pImpl_->path) ;
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("target-path", targetPath.absolutePath()) ;
+ return error ;
+ }
+}
+
+
+
+bool FilePath::isHidden() const
+{
+ return system::isHiddenFile(*this) ;
+}
+
+
+bool FilePath::isDirectory() const
+{
+ try
+ {
+ if (!exists())
+ return false;
+ else
+ return boost::filesystem::is_directory(pImpl_->path) ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ logError(pImpl_->path, e, ERROR_LOCATION) ;
+ return false;
+ }
+}
+
+
+Error FilePath::ensureDirectory() const
+{
+ if ( !exists() )
+ return createDirectory(std::string()) ;
+ else
+ return Success() ;
+}
+
+
+Error FilePath::createDirectory(const std::string& name) const
+{
+ try
+ {
+ path_t targetDirectory ;
+ if (name.empty())
+ targetDirectory = pImpl_->path ;
+ else
+ targetDirectory = BOOST_FS_COMPLETE(name, pImpl_->path) ;
+ boost::filesystem::create_directories(targetDirectory) ;
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("target-dir", name) ;
+ return error ;
+ }
+}
+
+Error FilePath::resetDirectory() const
+{
+ Error error = removeIfExists();
+ if (error)
+ return error;
+
+ return ensureDirectory();
+}
+
+
+FilePath FilePath::complete(const std::string& path) const
+{
+ // in-theory boost::filesystem::complete can throw but the conditions
+ // are very obscure and are in any case a programming error. therefore,
+ // we log silently if there is an error so that clients don't have to
+ // deal with any error states (if there an error then a copy of
+ // this path is returned)
+ try
+ {
+ // NOTE: The path gets round-tripped through toString/fromString, would
+ // be nice to have a direct constructor
+ return FilePath(BOOST_FS_PATH2STR(BOOST_FS_COMPLETE(path, pImpl_->path)));
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("path", path) ;
+ LOG_ERROR(error) ;
+ return *this ;
+ }
+}
+
+FilePath FilePath::parent() const
+{
+ try
+ {
+ // NOTE: The path gets round-tripped through toString/fromString, would
+ // be nice to have a direct constructor
+ return FilePath(BOOST_FS_PATH2STR(pImpl_->path.parent_path()));
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ LOG_ERROR(error);
+ return *this ;
+ }
+}
+
+// note: this differs from complete in the following ways:
+// - the passed path can be an empty string (returns self)
+// - the passed path must be relative
+FilePath FilePath::childPath(const std::string& path) const
+{
+ try
+ {
+ if (path.empty())
+ {
+ return *this ;
+ }
+ else
+ {
+ // confirm this is a relative path
+ path_t relativePath(fromString(path));
+ if (relativePath.has_root_path())
+ {
+ throw boost::filesystem::filesystem_error(
+ "absolute path not permitted",
+ boost::system::error_code(
+ boost::system::errc::no_such_file_or_directory,
+ boost::system::get_system_category()));
+ }
+
+ return complete(path);
+ }
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("path", path) ;
+ LOG_ERROR(error);
+ return *this ;
+ }
+
+}
+
+namespace {
+
+Error notFoundError(const FilePath& filePath,
+ const ErrorLocation& location)
+{
+ Error error = pathNotFoundError(location);
+ if (!filePath.empty())
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+}
+
+}
+
+Error FilePath::children(std::vector<FilePath>* pFilePaths) const
+{
+ if (!exists())
+ return notFoundError(*this, ERROR_LOCATION);
+
+ try
+ {
+ dir_iterator end ;
+ for (dir_iterator itr(pImpl_->path); itr != end; ++itr)
+ {
+ // NOTE: The path gets round-tripped through toString/fromString, would
+ // be nice to have a direct constructor
+ std::string itemPath = BOOST_FS_PATH2STR(itr->path());
+ pFilePaths->push_back(FilePath(itemPath)) ;
+ }
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ return error ;
+ }
+}
+
+
+Error FilePath::childrenRecursive(
+ RecursiveIterationFunction iterationFunction) const
+{
+ if (!exists())
+ return notFoundError(*this, ERROR_LOCATION);
+
+ try
+ {
+ recursive_dir_iterator end ;
+
+ for (recursive_dir_iterator itr(pImpl_->path); itr != end; ++itr)
+ {
+ // NOTE: The path gets round-tripped through toString/fromString, would
+ // be nice to have a direct constructor
+ iterationFunction(itr.level(),FilePath(BOOST_FS_PATH2STR(itr->path())));
+ }
+
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ return error ;
+ }
+}
+
+
+Error FilePath::makeCurrentPath(bool autoCreate) const
+{
+ if (autoCreate)
+ {
+ Error autoCreateError = ensureDirectory();
+ if (autoCreateError)
+ return autoCreateError ;
+ }
+
+ try
+ {
+ boost::filesystem::current_path(pImpl_->path) ;
+ return Success() ;
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ return error ;
+ }
+}
+
+Error FilePath::open_r(boost::shared_ptr<std::istream>* pStream) const
+{
+ try
+ {
+ std::istream* pResult = NULL;
+ #ifdef _WIN32
+ using namespace boost::iostreams;
+ HANDLE hFile = ::CreateFileW(pImpl_->path.wstring().c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+ boost::iostreams::file_descriptor_source fd;
+ fd.open(hFile, boost::iostreams::close_handle);
+ pResult = new boost::iostreams::stream<file_descriptor_source>(fd);
+ #else
+ pResult = new std::ifstream(absolutePath().c_str(),
+ std::ios_base::in | std::ios_base::binary);
+ #endif
+
+ // In case we were able to make the stream but it failed to open
+ if (!(*pResult))
+ {
+ delete pResult;
+
+ Error error = systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION);
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+ pStream->reset(pResult);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+
+
+ return Success();
+}
+
+Error FilePath::open_w(boost::shared_ptr<std::ostream>* pStream, bool truncate) const
+{
+ try
+ {
+ std::ostream* pResult = NULL;
+ #ifdef _WIN32
+ using namespace boost::iostreams;
+ HANDLE hFile = ::CreateFileW(pImpl_->path.wstring().c_str(),
+ GENERIC_WRITE,
+ 0, // exclusive access
+ NULL,
+ truncate ? CREATE_ALWAYS : OPEN_ALWAYS,
+ 0,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+ file_descriptor_sink fd;
+ fd.open(hFile, close_handle);
+ pResult = new boost::iostreams::stream<file_descriptor_sink>(fd);
+ #else
+ using std::ios_base;
+ ios_base::openmode flags = ios_base::out | ios_base::binary;
+ if (truncate)
+ flags |= ios_base::trunc;
+ else
+ flags |= ios_base::app;
+ pResult = new std::ofstream(absolutePath().c_str(), flags);
+ #endif
+
+ if (!(*pResult))
+ {
+ delete pResult;
+
+ Error error = systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION);
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+
+ pStream->reset(pResult);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", absolutePath());
+ return error;
+ }
+
+ return Success();
+}
+
+// check for equivalence (point to the same file-system entity)
+bool FilePath::isEquivalentTo(const FilePath& filePath) const
+{
+ if (!exists() || !filePath.exists())
+ return false;
+
+ try
+ {
+ return boost::filesystem::equivalent(pImpl_->path, filePath.pImpl_->path);
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION) ;
+ addErrorProperties(pImpl_->path, &error) ;
+ error.addProperty("equivilant-to", filePath);
+ return error ;
+ }
+
+ // keep compiler happy
+ return false;
+}
+
+bool FilePath::operator== (const FilePath& filePath) const
+{
+ return pImpl_->path == filePath.pImpl_->path ;
+}
+
+bool FilePath::operator!= (const FilePath& filePath) const
+{
+ return pImpl_->path != filePath.pImpl_->path ;
+}
+
+bool FilePath::operator < (const FilePath& other) const
+{
+ return pImpl_->path < other.pImpl_->path ;
+}
+
+std::ostream& operator << (std::ostream& stream, const FilePath& fp)
+{
+ stream << fp.absolutePath();
+ return stream ;
+}
+
+bool compareAbsolutePathNoCase(const FilePath& file1, const FilePath& file2)
+{
+ std::string file1Lower = string_utils::toLower(file1.absolutePath());
+ std::string file2Lower = string_utils::toLower(file2.absolutePath());
+ return file1Lower < file2Lower;
+}
+
+struct RecursiveDirectoryIterator::Impl
+{
+ explicit Impl(path_t path)
+ : itr_(path), end_()
+ {
+ }
+ recursive_dir_iterator itr_;
+ recursive_dir_iterator end_;
+ std::string lastPath_;
+};
+
+
+RecursiveDirectoryIterator::RecursiveDirectoryIterator(
+ const FilePath& filePath)
+ : pImpl_(new Impl(filePath.pImpl_->path))
+{
+}
+
+RecursiveDirectoryIterator::~RecursiveDirectoryIterator()
+{
+}
+
+Error RecursiveDirectoryIterator::next(FilePath* pFilePath)
+{
+ try
+ {
+ // calling next() when we are already finished is illegal
+ if (finished())
+ {
+ return systemError(boost::system::errc::operation_not_permitted,
+ ERROR_LOCATION);
+ }
+
+ // get the next file path (save it so we can use it in error messages)
+ pImpl_->lastPath_ = BOOST_FS_PATH2STR(pImpl_->itr_->path());
+ *pFilePath = FilePath(pImpl_->lastPath_);
+
+ // increment the iterator
+ ++(pImpl_->itr_);
+
+ // success
+ return Success();
+ }
+ catch(const boost::filesystem::filesystem_error& e)
+ {
+ Error error(e.code(), ERROR_LOCATION);
+ error.addProperty("last-path", pImpl_->lastPath_);
+ return error;
+ }
+}
+
+bool RecursiveDirectoryIterator::finished() const
+{
+ return pImpl_->itr_ == pImpl_->end_;
+}
+
+
+namespace {
+void logError(path_t path,
+ const boost::filesystem::filesystem_error& e,
+ const core::ErrorLocation& errorLocation)
+{
+ Error error(e.code(), errorLocation) ;
+ addErrorProperties(path, &error) ;
+ core::log::logError(error, errorLocation) ;
+}
+
+void addErrorProperties(path_t path, Error* pError)
+{
+ pError->addProperty("path", BOOST_FS_PATH2STR(path)) ;
+}
+}
+
+} // namespace core
+
diff --git a/src/cpp/core/FileSerializer.cpp b/src/cpp/core/FileSerializer.cpp
new file mode 100644
index 0000000..9660d45
--- /dev/null
+++ b/src/cpp/core/FileSerializer.cpp
@@ -0,0 +1,260 @@
+/*
+ * FileSerializer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FileSerializer.hpp>
+
+#include <utility>
+
+#include <iostream>
+#include <sstream>
+
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/iostreams/copy.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+namespace core {
+
+std::string stringifyStringPair(const std::pair<std::string,std::string>& pair)
+{
+ return pair.first + "=\"" + string_utils::jsonLiteralEscape(pair.second) + "\"" ;
+}
+
+Error writeStringMapToFile(const core::FilePath& filePath,
+ const std::map<std::string,std::string>& map)
+{
+ return writeCollectionToFile<std::map<std::string,std::string> >(
+ filePath,
+ map,
+ stringifyStringPair) ;
+}
+
+ReadCollectionAction parseStringPair(
+ const std::string& line,
+ std::pair<const std::string,std::string>* pPair)
+{
+ std::string::size_type pos = line.find("=") ;
+ if ( pos != std::string::npos )
+ {
+ std::string name = line.substr(0, pos) ;
+ boost::algorithm::trim(name);
+ std::string value = line.substr(pos + 1) ;
+ boost::algorithm::trim(value) ;
+ if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"')
+ {
+ value = string_utils::jsonLiteralUnescape(value);
+ }
+
+ // HACK: workaround the fact that std::map uses const for the Key
+ std::string* pFirst = const_cast<std::string*>(&(pPair->first)) ;
+ *pFirst = name ;
+
+ pPair->second = value ;
+
+ return ReadCollectionAddLine ;
+ }
+ else
+ {
+ return ReadCollectionIgnoreLine;
+ }
+}
+
+
+Error readStringMapFromFile(const core::FilePath& filePath,
+ std::map<std::string,std::string>* pMap)
+{
+ return readCollectionFromFile<std::map<std::string,std::string> >(
+ filePath,
+ pMap,
+ parseStringPair) ;
+}
+
+
+std::string stringifyString(const std::string& str)
+{
+ return str;
+}
+
+
+Error writeStringVectorToFile(const core::FilePath& filePath,
+ const std::vector<std::string>& vector)
+{
+ return writeCollectionToFile<std::vector<std::string> >(filePath,
+ vector,
+ stringifyString);
+
+}
+
+
+ReadCollectionAction parseString(const std::string& line, std::string* pStr)
+{
+ *pStr = line ;
+ return ReadCollectionAddLine ;
+}
+
+Error readStringVectorFromFile(const core::FilePath& filePath,
+ std::vector<std::string>* pVector,
+ bool trimAndIgnoreBlankLines)
+{
+ return readCollectionFromFile<std::vector<std::string> > (
+ filePath, pVector, parseString, trimAndIgnoreBlankLines);
+
+}
+
+Error writeStringToFile(const FilePath& filePath,
+ const std::string& str,
+ string_utils::LineEnding lineEnding)
+{
+ using namespace boost::system::errc ;
+
+ // open file
+ boost::shared_ptr<std::ostream> pOfs;
+ Error error = filePath.open_w(&pOfs);
+ if (error)
+ return error;
+
+ try
+ {
+ // set exception mask (required for proper reporting of errors)
+ pOfs->exceptions(std::ostream::failbit | std::ostream::badbit);
+
+ // copy string to file
+ std::string normalized = str;
+ string_utils::convertLineEndings(&normalized, lineEnding);
+ std::istringstream istr(normalized);
+ boost::iostreams::copy(istr, *pOfs);
+
+ // return success
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+}
+
+Error readStringFromFile(const FilePath& filePath,
+ std::string* pStr,
+ string_utils::LineEnding lineEnding,
+ int startLine,
+ int endLine,
+ int startCharacter,
+ int endCharacter)
+{
+ using namespace boost::system::errc ;
+
+ // open file
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = filePath.open_r(&pIfs);
+ if (error)
+ return error;
+
+ try
+ {
+ // if a line region was specified, read that region instead of the
+ // entire file.
+ if (endLine > startLine)
+ {
+ // set exception mask; note that we can't let failbit create an
+ // exception here because reading eof can trigger failbit in our case.
+ pIfs->exceptions(std::istream::badbit);
+
+ int currentLine = 0;
+ std::string content;
+ std::string line;
+ // loop over each line in the file. (consider: is there a more
+ // performant way to seek past the first N lines?)
+ while (++currentLine <= endLine
+ && !pIfs->eof())
+ {
+ std::getline(*pIfs, line);
+ if (currentLine >= startLine)
+ {
+ // compute the portion of the line to be read; if this is the
+ // start or end of the region to be read, use the character
+ // offsets supplied
+ int lineLength = line.length();
+ content += line.substr(
+ currentLine == startLine ?
+ std::min(
+ std::max(startCharacter - 1, 0),
+ lineLength) :
+ 0,
+ currentLine == endLine ?
+ std::min(endCharacter, lineLength) :
+ lineLength);
+ if (currentLine != endLine)
+ {
+ content += "\n";
+ }
+ }
+ }
+ *pStr = content;
+ }
+ // reading the entire file
+ else
+ {
+ // set exception mask (required for proper reporting of errors)
+ pIfs->exceptions(std::istream::failbit | std::istream::badbit);
+
+ // copy file to string stream
+ std::ostringstream ostr;
+ boost::iostreams::copy(*pIfs, ostr);
+ *pStr = ostr.str();
+ }
+
+ string_utils::convertLineEndings(pStr, lineEnding);
+
+ // return success
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+}
+
+bool stripBOM(std::string* pStr)
+{
+ if (boost::algorithm::starts_with(*pStr, "\xEF\xBB\xBF"))
+ {
+ pStr->erase(0, 3);
+ return true;
+ }
+ else if (boost::algorithm::starts_with(*pStr, "\xFF\xFE"))
+ {
+ pStr->erase(0, 2);
+ return true;
+ }
+ else if (boost::algorithm::starts_with(*pStr, "\xFE\xFF"))
+ {
+ pStr->erase(0, 2);
+ return true;
+ }
+ return false;
+}
+
+} // namespace core
+
diff --git a/src/cpp/core/FileUtils.cpp b/src/cpp/core/FileUtils.cpp
new file mode 100644
index 0000000..121b6e4
--- /dev/null
+++ b/src/cpp/core/FileUtils.cpp
@@ -0,0 +1,45 @@
+/*
+ * FileUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/FileUtils.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/system/System.hpp>
+
+namespace core {
+namespace file_utils {
+
+FilePath uniqueFilePath(const FilePath& parent, const std::string& prefix)
+{
+ // try up to 100 times then fallback to a uuid
+ for (int i=0; i<100; i++)
+ {
+ // get a shortened uuid
+ std::string shortentedUuid = core::system::generateShortenedUuid();
+
+ // form full path
+ FilePath uniqueDir = parent.childPath(prefix + shortentedUuid);
+
+ // return if it doesn't exist
+ if (!uniqueDir.exists())
+ return uniqueDir;
+ }
+
+ // if we didn't succeed then return prefix + uuid
+ return parent.childPath(prefix + core::system::generateUuid(false));
+}
+
+} // namespace file_utils
+} // namespace core
diff --git a/src/cpp/core/GitGraph.cpp b/src/cpp/core/GitGraph.cpp
new file mode 100644
index 0000000..a3764c9
--- /dev/null
+++ b/src/cpp/core/GitGraph.cpp
@@ -0,0 +1,163 @@
+/*
+ * GitGraph.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <limits>
+#include <algorithm>
+
+#include <core/GitGraph.hpp>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/SafeConvert.hpp>
+
+namespace core {
+namespace gitgraph {
+
+namespace {
+
+// If true, then this column terminates in this row's nexus.
+bool isColumnTerminating(const Column& column)
+{
+ return column.postCommit.empty();
+}
+
+// If true, then this column either starts in this row's nexus,
+// ends there, or passes through it. If false, then the column
+// has nothing going on in this row (well, it's possibly changing
+// positions as a result of other columns being added/removed).
+bool isColumnDynamic(const Column& column)
+{
+ return column.preCommit != column.postCommit;
+}
+
+} // namespace
+
+size_t Line::nexus() const
+{
+ Line::const_iterator it = std::find_if(begin(), end(), &isColumnDynamic);
+ if (it == end())
+ return std::numeric_limits<size_t>::max();
+ else
+ return it - begin();
+}
+
+std::string Line::string() const
+{
+ std::string output;
+ bool sawNexus = false;
+ for (size_t i = 0; i < size(); i++)
+ {
+ const Column& c = at(i);
+ if (isColumnDynamic(c))
+ {
+ if (!sawNexus)
+ {
+ sawNexus = true;
+ output.append("*");
+ }
+
+ if (c.preCommit.empty())
+ output.append("+");
+ if (c.postCommit.empty())
+ output.append("-");
+ }
+
+ output.append(safe_convert::numberToString(c.id));
+
+ if (i < size() - 1)
+ output.append(" ");
+ }
+ return output;
+}
+
+Line GitGraph::addCommit(const std::string& commit,
+ const std::vector<std::string>& parents)
+{
+ // If this commit is a merge (has multiple parents) then we'll want to
+ // insert new columns immediately to the right of the existing column.
+ // If this commit isn't the parent of a previously seen node, then we'll
+ // definitely be adding one or more columns to the right of all the
+ // existing columns.
+ Line::iterator insertNewColumnsAt = pendingLine_.end();
+
+ // Counts how many of the parents have been assigned to columns.
+ size_t parentsUsed = 0;
+
+ for (Line::iterator it = pendingLine_.begin();
+ it != pendingLine_.end();
+ it++)
+ {
+ if (it->preCommit == commit)
+ {
+ // This column was expecting the current commit. We can either
+ // terminate the column here, or, we can take the first parent
+ // and set that as the new commit (postCommit) for this column.
+ // We can only do the latter once, as we want all of the columns
+ // for this commit to converge on one point in the graph.
+
+ // This if clause is what ensures we'll only do this once.
+ if (insertNewColumnsAt == pendingLine_.end())
+ {
+ // If this is a merge, we'll insert the other branches just
+ // to the right of us.
+ insertNewColumnsAt = it + 1;
+
+ // Either assign the first parent to this, or if this is an
+ // unparented commit (e.g. initial commit in a repo) then just
+ // terminate here.
+ if (parentsUsed == parents.size())
+ it->postCommit = "";
+ else
+ it->postCommit = parents[parentsUsed++];
+ }
+ else
+ {
+ it->postCommit = "";
+ }
+ }
+ }
+
+ // Make new columns for any parents we haven't already used.
+ while (parentsUsed != parents.size())
+ {
+ insertNewColumnsAt = 1 + pendingLine_.insert(
+ insertNewColumnsAt,
+ Column(nextColumnId_++, "", parents[parentsUsed++]));
+ }
+
+ // This line is ready. Make a copy of it.
+ Line result = pendingLine_;
+
+ /*
+ * Now fix up pendingLine_ to get ready for the next call to addCommit.
+ */
+
+ // First remove all columns that have empty postCommit--these terminated.
+ Line::iterator newEnd = std::remove_if(
+ pendingLine_.begin(), pendingLine_.end(), &isColumnTerminating);
+ pendingLine_.erase(newEnd, pendingLine_.end());
+
+ // Now copy all of the postCommits to preCommit.
+ BOOST_FOREACH(Column& column, pendingLine_)
+ {
+ column.preCommit = column.postCommit;
+ }
+
+ return result;
+}
+
+} // namespace gitgraph
+} // namespace core
diff --git a/src/cpp/core/Hash.cpp b/src/cpp/core/Hash.cpp
new file mode 100644
index 0000000..bf97d26
--- /dev/null
+++ b/src/cpp/core/Hash.cpp
@@ -0,0 +1,51 @@
+/*
+ * Hash.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Hash.hpp>
+
+#include <sstream>
+
+#include <boost/crc.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <core/SafeConvert.hpp>
+
+namespace core {
+namespace hash {
+
+std::string crc32Hash(const std::string& content)
+{
+ boost::crc_32_type result;
+ result.process_bytes(content.data(), content.length());
+ return safe_convert::numberToString(result.checksum());
+}
+
+std::string crc32HexHash(const std::string& content)
+{
+ // compute checksum
+ boost::crc_32_type result;
+ result.process_bytes(content.data(), content.length());
+
+ // return hex representation
+ std::ostringstream output;
+ output << std::uppercase << std::hex << result.checksum();
+ return output.str();
+}
+
+} // namespace hash
+} // namespace core
+
+
+
diff --git a/src/cpp/core/HtmlUtils.cpp b/src/cpp/core/HtmlUtils.cpp
new file mode 100644
index 0000000..d00335f
--- /dev/null
+++ b/src/cpp/core/HtmlUtils.cpp
@@ -0,0 +1,125 @@
+/*
+ * HtmlUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/HtmlUtils.hpp>
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Base64.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/http/Util.hpp>
+
+namespace core {
+namespace html_utils {
+
+std::string defaultTitle(const std::string& htmlContent)
+{
+ boost::regex re("<[Hh]([1-6]).*?>(.*?)</[Hh]\\1>");
+ boost::smatch match;
+ if (boost::regex_search(htmlContent, match, re))
+ return match[2];
+ else
+ return "";
+}
+
+
+Base64ImageFilter::Base64ImageFilter(const FilePath& basePath)
+ : boost::iostreams::regex_filter(
+ boost::regex(
+ "(<\\s*[Ii][Mm][Gg] [^\\>]*[Ss][Rr][Cc]\\s*=\\s*)([\"'])(.*?)(\\2)"),
+ boost::bind(&Base64ImageFilter::toBase64Image, this, _1)),
+ basePath_(basePath)
+{
+}
+
+
+std::string Base64ImageFilter::toBase64Image(const boost::cmatch& match)
+{
+ // extract image reference
+ std::string imgRef = match[3];
+
+ // url decode it
+ imgRef = http::util::urlDecode(imgRef);
+
+ // see if this is an image within the base directory. if it is then
+ // base64 encode it
+ FilePath imagePath = basePath_.childPath(imgRef);
+ if (imagePath.exists() &&
+ boost::algorithm::starts_with(imagePath.mimeContentType(), "image/"))
+ {
+ std::string imageBase64;
+ Error error = core::base64::encode(imagePath, &imageBase64);
+ if (!error)
+ {
+ imgRef = "data:" + imagePath.mimeContentType() + ";base64,";
+ imgRef.append(imageBase64);
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+
+ // return the filtered result
+ return match[1] + match[2] + imgRef + match[4];
+}
+
+// convert fonts to base64
+
+CssUrlFilter::CssUrlFilter(const FilePath& basePath)
+ : boost::iostreams::regex_filter(
+ boost::regex("url\\('([^'']+)'\\)"),
+ boost::bind(&CssUrlFilter::toBase64Url, this, _1)),
+ basePath_(basePath)
+{
+}
+
+std::string CssUrlFilter::toBase64Url(const boost::cmatch& match)
+{
+ // is this a local file?
+ std::string urlRef = match[1];
+ FilePath urlPath = basePath_.childPath(urlRef);
+ std::string ext = urlPath.extensionLowerCase();
+ if (urlPath.exists() && (ext == ".ttf" || ext == ".otf"))
+ {
+ std::string fontBase64;
+ Error error = core::base64::encode(urlPath, &fontBase64);
+ if (!error)
+ {
+ // return base64 encoded font
+ std::string type = (ext == ".ttf") ? "truetype" : "opentype";
+ boost::format fmt("url(data:font/%1%;base64,%2%)");
+ return boost::str(fmt % type % fontBase64);
+ }
+ else
+ {
+ LOG_ERROR(error);
+ return match[0];
+ }
+ }
+ else
+ {
+ return match[0];
+ }
+}
+
+
+} // namespace html_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/Log.cpp b/src/cpp/core/Log.cpp
new file mode 100644
index 0000000..164508d
--- /dev/null
+++ b/src/cpp/core/Log.cpp
@@ -0,0 +1,172 @@
+/*
+ * Log.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Log.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+
+#include <core/Error.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+namespace log {
+
+namespace {
+
+const char * const OCCURRED_AT = "OCCURRED AT";
+const char * const LOGGED_FROM = "LOGGED FROM";
+const char * const CAUSED_BY = "CAUSED BY";
+
+void writeError(const Error& error, std::ostream& os)
+{
+ // build intermediate string so we can remove any embedded instances of
+ // DELIM in the output (since this is used as a delimiter for parsing)
+ std::ostringstream errorStream ;
+
+ // basics
+ const boost::system::error_code& ec = error.code();
+ errorStream << "ERROR " << ec.category().name() << " error "
+ << ec.value() << " (" << ec.message() << ")" ;
+
+ // properties
+ if ( !error.properties().empty() )
+ {
+ errorStream << " [" ;
+ std::vector<std::pair<std::string,std::string> >::const_iterator
+ it = error.properties().begin() ;
+ errorStream << it->first << "=" << it->second ;
+ ++it ;
+ for ( ; it != error.properties().end(); ++it)
+ errorStream << ", " << it->first << "=" << it->second ;
+ errorStream << "]" ;
+ }
+
+ // clean delims and output
+ os << cleanDelims(errorStream.str());
+
+ // location
+ os << DELIM << " " << OCCURRED_AT << ": "
+ << cleanDelims(error.location().asString());
+
+ // cause (recurse)
+ if (error.cause() )
+ {
+ os << DELIM << " " << CAUSED_BY << ": " ;
+ writeError(error.cause(), os);
+ }
+}
+
+void logMessageWithLocation(const std::string& prefix,
+ system::LogLevel logLevel,
+ const std::string& message,
+ const ErrorLocation& loggedFromLocation)
+{
+ try
+ {
+ std::ostringstream os ;
+
+ // error
+ os << prefix << " " << message ;
+
+ // log location
+ os << DELIM << " " << LOGGED_FROM << ": "
+ << cleanDelims(loggedFromLocation.asString());
+
+ system::log(logLevel, os.str()) ;
+ }
+ catch(...)
+ {
+ system::log(system::kLogLevelError,
+ "ERROR unexpected error while logging");
+ }
+}
+
+}
+
+const char DELIM = ';';
+
+std::string cleanDelims(const std::string& source)
+{
+ std::string cleanTarget(source);
+ std::replace(cleanTarget.begin(), cleanTarget.end(), DELIM, ' ');
+ return cleanTarget;
+}
+
+
+void logError(const Error& error, const ErrorLocation& loggedFromLocation)
+{
+ try
+ {
+ std::ostringstream os ;
+
+ // error
+ writeError(error, os) ;
+
+ // log location
+ os << DELIM << " " << LOGGED_FROM << ": "
+ << cleanDelims(loggedFromLocation.asString());
+
+ system::log( system::kLogLevelError, os.str()) ;
+ }
+ catch(...)
+ {
+ system::log(system::kLogLevelError,
+ "ERROR unexpected error while logging");
+ }
+}
+
+void logErrorMessage(const std::string& message,
+ const ErrorLocation& loggedFromLocation)
+{
+ logMessageWithLocation("ERROR",
+ system::kLogLevelError,
+ message,
+ loggedFromLocation);
+}
+
+void logWarningMessage(const std::string& message,
+ const ErrorLocation& loggedFromLocation)
+{
+ logMessageWithLocation("WARNING",
+ system::kLogLevelWarning,
+ message,
+ loggedFromLocation);
+}
+
+void logInfoMessage(const std::string& message)
+{
+ system::log(system::kLogLevelInfo, message.c_str());
+}
+
+void logDebugMessage(const std::string& message)
+{
+ system::log(system::kLogLevelDebug, message.c_str());
+}
+
+std::string errorAsLogEntry(const Error& error)
+{
+ std::ostringstream ostr;
+ writeError(error, ostr);
+ return ostr.str();
+}
+
+
+} // namespace log
+} // namespace core
+
+
+
diff --git a/src/cpp/core/LogWriter.cpp b/src/cpp/core/LogWriter.cpp
new file mode 100644
index 0000000..ad151ae
--- /dev/null
+++ b/src/cpp/core/LogWriter.cpp
@@ -0,0 +1,48 @@
+/*
+ * LogWriter.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/LogWriter.hpp>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/DateTime.hpp>
+
+namespace core {
+
+std::string LogWriter::formatLogEntry(const std::string& programIdentity,
+ const std::string& message,
+ bool escapeNewlines)
+{
+ // replace newlines with standard escape sequence if requested
+ std::string cleanedMessage(message);
+ if (escapeNewlines)
+ boost::algorithm::replace_all(cleanedMessage, "\n", "|||");
+
+ // generate time string
+ using namespace boost::posix_time;
+ ptime time = microsec_clock::universal_time();
+ std::string dateTime = date_time::format(time, "%d %b %Y %H:%M:%S");
+
+ // generate log entry
+ std::ostringstream ostr;
+ ostr << dateTime
+ << " [" << programIdentity << "] "
+ << cleanedMessage
+ << std::endl;
+ return ostr.str();
+}
+
+} // namespace core
diff --git a/src/cpp/core/PerformanceTimer.cpp b/src/cpp/core/PerformanceTimer.cpp
new file mode 100644
index 0000000..c3232b7
--- /dev/null
+++ b/src/cpp/core/PerformanceTimer.cpp
@@ -0,0 +1,133 @@
+/*
+ * PerformanceTimer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/PerformanceTimer.hpp>
+
+#include <iostream>
+#include <iomanip>
+
+using namespace boost::posix_time;
+
+namespace core {
+
+PerformanceTimer::PerformanceTimer()
+ : startTime_(ptime(not_a_date_time))
+{
+}
+
+PerformanceTimer::PerformanceTimer(const std::string& step)
+ : startTime_(ptime(not_a_date_time))
+{
+ start(step);
+}
+
+PerformanceTimer::~PerformanceTimer()
+{
+ try
+ {
+ if (running())
+ {
+ stop();
+ std::cerr << *this;
+ }
+ }
+ catch(...)
+ {
+ }
+}
+
+void PerformanceTimer::start(const std::string& step)
+{
+ BOOST_ASSERT(!running());
+
+ // reset state
+ startTime_ = now();
+ steps_.clear();
+
+ // advance to step
+ advance(step);
+}
+
+void PerformanceTimer::advance(const std::string& step)
+{
+ BOOST_ASSERT(running());
+
+ // if there is an existing step pending then record its duration
+ recordPendingStep();
+
+ // record the start time and add a step
+ startTime_ = now();
+ steps_.push_back(std::make_pair(step, time_duration()));
+}
+
+void PerformanceTimer::stop()
+{
+ BOOST_ASSERT(running());
+
+ // if there is an existing step pending then record its duration
+ recordPendingStep();
+
+ // reset start time
+ startTime_ = ptime(not_a_date_time);
+}
+
+bool PerformanceTimer::running() const
+{
+ return !startTime_.is_not_a_date_time();
+}
+
+void PerformanceTimer::recordPendingStep()
+{
+ if (!steps_.empty())
+ steps_.back().second = now() - startTime_;
+}
+
+boost::posix_time::ptime PerformanceTimer::now() const
+{
+ return microsec_clock::universal_time();
+}
+
+std::ostream& operator << (std::ostream& os, const PerformanceTimer& t)
+{
+ BOOST_ASSERT(!t.running());
+
+ // fixed width output
+ std::ios::fmtflags oldFlags = os.setf(std::ios::fixed);
+
+ os << "PERFORMANCE";
+
+ // start on a new line if we have more than one step
+ if (t.steps_.size() > 1)
+ os << std::endl;
+
+ for (PerformanceTimer::Steps::const_iterator
+ it = t.steps_.begin(); it != t.steps_.end(); ++it)
+ {
+ double ms = it->second.total_microseconds() * 0.001;
+ os << std::setprecision(ms < 10 ? 1 : 0);
+ os << " " << ms << " ms (" << it->first << ")" << std::endl;
+ }
+
+ // restore old output flags
+ os.setf(oldFlags);
+
+ return os;
+}
+
+
+} // namespace core
+
+
+
diff --git a/src/cpp/core/PosixStringUtils.cpp b/src/cpp/core/PosixStringUtils.cpp
new file mode 100644
index 0000000..e548052
--- /dev/null
+++ b/src/cpp/core/PosixStringUtils.cpp
@@ -0,0 +1,88 @@
+/*
+ * PosixStringUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StringUtils.hpp>
+
+#include <cstdlib>
+
+#include <vector>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Woverloaded-virtual"
+#endif
+
+#include <boost/program_options/detail/convert.hpp>
+#include <boost/program_options/detail/utf8_codecvt_facet.hpp>
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace core {
+namespace string_utils {
+
+std::string wideToUtf8(const std::wstring& value)
+{
+ try
+ {
+ boost::program_options::detail::utf8_codecvt_facet utf8_facet;
+ return boost::to_8_bit(value, utf8_facet);
+ }
+ catch(const std::exception& e)
+ {
+ // this should NEVER happen!
+ LOG_ERROR_MESSAGE(e.what());
+ return std::string();
+ }
+}
+
+std::wstring utf8ToWide(const std::string& value, const std::string& context)
+{
+ try
+ {
+ boost::program_options::detail::utf8_codecvt_facet utf8_facet;
+ return boost::from_8_bit(value, utf8_facet);
+ }
+ catch(const std::exception&)
+ {
+ // could happen if the inbound data isn't correctly utf8 encoded,
+ // in this case just use the system default encoding
+ static const std::size_t ERR = -1;
+ std::vector<wchar_t> wide(value.length() + 1);
+ std::size_t len = ::mbstowcs(&(wide[0]), value.c_str(), wide.size());
+ if (len != ERR)
+ {
+ return std::wstring(&(wide[0]), len);
+ }
+ else
+ {
+ std::string message = "Invalid multibyte character";
+ if (!context.empty())
+ message += " " + context;
+ LOG_ERROR_MESSAGE(message);
+ return std::wstring();
+ }
+ }
+}
+
+} // namespace string_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/ProgramOptions.cpp b/src/cpp/core/ProgramOptions.cpp
new file mode 100644
index 0000000..0c7577a
--- /dev/null
+++ b/src/cpp/core/ProgramOptions.cpp
@@ -0,0 +1,193 @@
+/*
+ * ProgramOptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/ProgramOptions.hpp>
+
+#include <string>
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/system/System.hpp>
+
+using namespace boost::program_options ;
+
+namespace core {
+
+namespace program_options {
+
+namespace {
+
+bool validateOptionsProvided(const variables_map& vm,
+ const options_description& optionsDescription,
+ const std::string& configFile = std::string())
+{
+ BOOST_FOREACH( const boost::shared_ptr<option_description>& pOptionsDesc,
+ optionsDescription.options() )
+ {
+ std::string optionName = pOptionsDesc->long_name();
+ if ( !(vm.count(optionName)) )
+ {
+ std::string msg = "Required option " + optionName + " not specified";
+ if (!configFile.empty())
+ msg += " in config file " + configFile;
+ reportError(msg, ERROR_LOCATION);
+ return false ;
+ }
+ }
+
+ // all options validated
+ return true;
+}
+
+}
+
+void reportError(const std::string& errorMessage, const ErrorLocation& location)
+{
+ if (core::system::stderrIsTerminal())
+ std::cerr << errorMessage << std::endl;
+ else
+ core::log::logErrorMessage(errorMessage, location);
+}
+
+void reportWarnings(const std::string& warningMessages,
+ const ErrorLocation& location)
+{
+ if (core::system::stderrIsTerminal())
+ std::cerr << "WARNINGS: " << warningMessages << std::endl;
+ else
+ core::log::logWarningMessage(warningMessages, location);
+}
+
+
+ProgramStatus read(const OptionsDescription& optionsDescription,
+ int argc,
+ char * const argv[],
+ bool* pHelp)
+{
+ *pHelp = false;
+ std::string configFile;
+ try
+ {
+ // general options
+ options_description general("general") ;
+ general.add_options()
+ ("help", "print help message")
+ ("test-config", "test to ensure the config file is valid")
+ ("config-file",
+ value<std::string>(&configFile)->default_value(
+ optionsDescription.defaultConfigFilePath),
+ std::string("configuration file").c_str());
+
+
+ // make copy of command line options so we can add general to them
+ options_description commandLineOptions(optionsDescription.commandLine);
+ commandLineOptions.add(general);
+
+ // parse the command line
+ variables_map vm ;
+ command_line_parser parser(argc, const_cast<char**>(argv));
+ store(parser.options(commandLineOptions).
+ positional(optionsDescription.positionalOptions).run(), vm);
+ notify(vm) ;
+
+ // "none" is a special sentinel value for the config-file which
+ // explicitly prevents us from reading the defautl config file above
+ // now that we are past that we can reset it to empty
+ if (configFile == "none")
+ configFile = "";
+
+ // open the config file
+ if (!configFile.empty())
+ {
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = FilePath(configFile).open_r(&pIfs);
+ if (error)
+ {
+ reportError("Unable to open config file: " + configFile,
+ ERROR_LOCATION);
+ return ProgramStatus::exitFailure() ;
+ }
+
+ try
+ {
+ // parse config file
+ store(parse_config_file(*pIfs, optionsDescription.configFile), vm) ;
+ notify(vm) ;
+ }
+ catch(const std::exception& e)
+ {
+ reportError(
+ "Error reading " + configFile + ": " + std::string(e.what()),
+ ERROR_LOCATION);
+
+ return ProgramStatus::exitFailure();
+ }
+ }
+
+ // show help if requested
+ if (vm.count("help"))
+ {
+ *pHelp = true;
+ std::cout << commandLineOptions ;
+ return ProgramStatus::exitSuccess() ;
+ }
+
+ // validate all options are provided
+ else
+ {
+ if (!validateOptionsProvided(vm, optionsDescription.commandLine))
+ return ProgramStatus::exitFailure();
+
+ if (!configFile.empty())
+ {
+ if (!validateOptionsProvided(vm,
+ optionsDescription.configFile,
+ configFile))
+ return ProgramStatus::exitFailure();
+ }
+ }
+
+ // if this was a config-test then return exitSuccess, otherwise run
+ if (vm.count("test-config"))
+ {
+ return ProgramStatus::exitSuccess();
+ }
+ else
+ {
+ return ProgramStatus::run() ;
+ }
+ }
+ catch(const boost::program_options::error& e)
+ {
+ std::string msg(e.what());
+ if (!configFile.empty())
+ msg += " in config file " + configFile;
+ reportError(msg, ERROR_LOCATION);
+ return ProgramStatus::exitFailure();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // keep compiler happy
+ return ProgramStatus::exitFailure();
+}
+
+
+} // namespace program_options
+} // namespace core
diff --git a/src/cpp/core/RegexUtils.cpp b/src/cpp/core/RegexUtils.cpp
new file mode 100644
index 0000000..e87d114
--- /dev/null
+++ b/src/cpp/core/RegexUtils.cpp
@@ -0,0 +1,117 @@
+/*
+ * RegexUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/RegexUtils.hpp>
+
+#include <vector>
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
+
+#include <core/StringUtils.hpp>
+
+namespace core {
+namespace regex_utils {
+
+boost::regex wildcardPatternToRegex(const std::string& pattern)
+{
+ // split into componenents
+ using namespace boost::algorithm;
+ std::vector<std::string> components;
+ split(components, pattern, is_any_of("*"), token_compress_on);
+
+ // build and return regex
+ std::string regex;
+ for (std::size_t i=0; i<components.size(); i++)
+ {
+ if (i > 0)
+ regex.append(".*");
+ regex.append("\\Q");
+ regex.append(components.at(i));
+ regex.append("\\E");
+ }
+ return boost::regex(regex);
+}
+
+bool textMatches(const std::string& text,
+ const boost::regex& regex,
+ bool prefixOnly,
+ bool caseSensitive)
+{
+ boost::smatch match;
+ boost::match_flag_type flags = boost::match_default;
+ if (prefixOnly)
+ flags |= boost::match_continuous;
+ return regex_search(caseSensitive ? text : string_utils::toLower(text),
+ match,
+ regex,
+ flags);
+}
+
+Error filterString(const std::string& input,
+ const std::vector<boost::iostreams::regex_filter>& filters,
+ std::string* pOutput)
+{
+ try
+ {
+ // create input stream
+ std::istringstream inputStream(input);
+ inputStream.exceptions(std::istream::failbit | std::istream::badbit);
+
+ // create filtered output stream
+ std::ostringstream outputStream;
+ outputStream.exceptions(std::istream::failbit | std::istream::badbit);
+ boost::iostreams::filtering_ostream filteredStream;
+ for (std::size_t i=0; i<filters.size(); i++)
+ filteredStream.push(filters[i]);
+ filteredStream.push(outputStream);
+
+ boost::iostreams::copy(inputStream, filteredStream, 128);
+
+ *pOutput = outputStream.str();
+
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error, ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("input", input.substr(0, 50));
+ return error;
+ }
+
+ // keep compiler happy
+ return Success();
+}
+
+Error filterString(const std::string& input,
+ const boost::iostreams::regex_filter& filter,
+ std::string* pOutput)
+{
+ std::vector<boost::iostreams::regex_filter> filters;
+ filters.push_back(filter);
+ return filterString(input, filters, pOutput);
+}
+
+
+} // namespace regex_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/SafeConvert.cpp b/src/cpp/core/SafeConvert.cpp
new file mode 100644
index 0000000..94e796e
--- /dev/null
+++ b/src/cpp/core/SafeConvert.cpp
@@ -0,0 +1,27 @@
+/*
+ * SafeConvert.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/SafeConvert.hpp>
+
+
+namespace core {
+namespace safe_convert {
+
+
+} // namespace safe_convert
+} // namespace core
+
+
+
diff --git a/src/cpp/core/Settings.cpp b/src/cpp/core/Settings.cpp
new file mode 100644
index 0000000..4bf9f8d
--- /dev/null
+++ b/src/cpp/core/Settings.cpp
@@ -0,0 +1,146 @@
+/*
+ * Settings.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Settings.hpp>
+
+#include <boost/lexical_cast.hpp>
+
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+namespace core {
+
+Settings::Settings()
+ : updatePending_(false),
+ isDirty_(false)
+{
+}
+
+Settings::~Settings()
+{
+}
+
+Error Settings::initialize(const FilePath& filePath)
+{
+ settingsFile_ = filePath ;
+ settingsMap_.clear() ;
+ Error error = core::readStringMapFromFile(settingsFile_, &settingsMap_) ;
+ if (error)
+ {
+ // we don't consider file-not-found and error because it is a
+ // common initialization case
+ if (error.code() != boost::system::errc::no_such_file_or_directory)
+ {
+ error.addProperty("settings-file", settingsFile_);
+ return error ;
+ }
+ }
+
+ return Success() ;
+}
+
+void Settings::set(const std::string& name, const std::string& value)
+{
+ if (value != settingsMap_[name])
+ {
+ settingsMap_[name] = value ;
+ isDirty_ = true;
+
+ if (!updatePending_)
+ writeSettings() ;
+ }
+}
+
+void Settings::set(const std::string& name, int value)
+{
+ set(name, safe_convert::numberToString(value));
+}
+
+void Settings::set(const std::string& name, bool value)
+{
+ set(name, safe_convert::numberToString(value));
+}
+
+bool Settings::contains(const std::string& name) const
+{
+ return settingsMap_.find(name) != settingsMap_.end();
+}
+
+std::string Settings::get(const std::string& name,
+ const std::string& defaultValue) const
+{
+ std::map<std::string,std::string>::const_iterator pos =
+ settingsMap_.find(name) ;
+ if (pos != settingsMap_.end())
+ return (*pos).second ;
+ else
+ return defaultValue ;
+}
+
+int Settings::getInt(const std::string& name, int defaultValue) const
+{
+ std::string value = get(name) ;
+ if (value.empty())
+ return defaultValue ;
+ else
+ return boost::lexical_cast<int>(value);
+}
+
+int Settings::getBool(const std::string& name, bool defaultValue) const
+{
+ std::string value = get(name) ;
+ if (value.empty())
+ return defaultValue ;
+ else
+ return boost::lexical_cast<bool>(value);
+}
+
+void Settings::forEach(const boost::function<void(const std::string&,
+ const std::string&)>& func)
+ const
+{
+ for (std::map<std::string,std::string>::const_iterator
+ it = settingsMap_.begin(); it != settingsMap_.end(); ++it)
+ {
+ func(it->first, it->second);
+ }
+}
+
+void Settings::beginUpdate()
+{
+ updatePending_ = true ;
+}
+
+void Settings::endUpdate()
+{
+ updatePending_ = false ;
+ if (isDirty_)
+ writeSettings();
+}
+
+void Settings::writeSettings()
+{
+ isDirty_ = false;
+ Error error = core::writeStringMapToFile(settingsFile_, settingsMap_) ;
+ if (error)
+ LOG_ERROR(error);
+}
+
+
+}
+
+
diff --git a/src/cpp/core/StderrLogWriter.cpp b/src/cpp/core/StderrLogWriter.cpp
new file mode 100644
index 0000000..5247c38
--- /dev/null
+++ b/src/cpp/core/StderrLogWriter.cpp
@@ -0,0 +1,61 @@
+/*
+ * StderrLogWriter.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StderrLogWriter.hpp>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/DateTime.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+
+StderrLogWriter::StderrLogWriter(const std::string& programIdentity,
+ int logLevel)
+ : programIdentity_(programIdentity), logLevel_(logLevel)
+{
+}
+
+StderrLogWriter::~StderrLogWriter()
+{
+ try
+ {
+ }
+ catch(...)
+ {
+ }
+}
+
+void StderrLogWriter::log(core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ log(programIdentity_, logLevel, message);
+}
+
+void StderrLogWriter::log(const std::string& programIdentity,
+ core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ if (logLevel > logLevel_)
+ return;
+
+ std::cerr << formatLogEntry(programIdentity, message, false);
+}
+
+
+} // namespace core
diff --git a/src/cpp/core/StringUtils.cpp b/src/cpp/core/StringUtils.cpp
new file mode 100644
index 0000000..b443cf3
--- /dev/null
+++ b/src/cpp/core/StringUtils.cpp
@@ -0,0 +1,353 @@
+/*
+ * StringUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StringUtils.hpp>
+
+#include <map>
+#include <ostream>
+
+#include <algorithm>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/regex.hpp>
+
+#include <core/Log.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/json/Json.hpp>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <winnls.h>
+#endif
+
+namespace core {
+namespace string_utils {
+
+void convertLineEndings(std::string* pStr, LineEnding type)
+{
+ std::string replacement;
+ switch (type)
+ {
+ case LineEndingWindows:
+ replacement = "\r\n";
+ break;
+ case LineEndingPosix:
+ replacement = "\n";
+ break;
+ case LineEndingNative:
+#if _WIN32
+ replacement = "\r\n";
+#else
+ replacement = "\n";
+#endif
+ break;
+ case LineEndingPassthrough:
+ default:
+ return;
+ }
+
+ *pStr = boost::regex_replace(*pStr, boost::regex("\\r?\\n|\\r|\\xE2\\x80[\\xA8\\xA9]"), replacement);
+}
+
+std::string utf8ToSystem(const std::string& str,
+ bool escapeInvalidChars)
+{
+ if (str.empty())
+ return std::string();
+
+#ifdef _WIN32
+ wchar_t wide[str.length() + 1];
+ int chars = ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, wide, sizeof(wide));
+ if (chars < 0)
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return str;
+ }
+
+ std::ostringstream output;
+ char mbbuf[10];
+ // Only go up to chars - 1 because last char is \0
+ for (int i = 0; i < chars - 1; i++)
+ {
+ int mbc = wctomb(mbbuf, wide[i]);
+ if (mbc == -1)
+ {
+ if (escapeInvalidChars)
+ output << "\\u{" << std::hex << wide[i] << "}";
+ else
+ output << "?"; // TODO: Use GetCPInfo()
+ }
+ else
+ output.write(mbbuf, mbc);
+ }
+ return output.str();
+#else
+ // Assumes that UTF8 is the locale on POSIX
+ return str;
+#endif
+}
+
+std::string systemToUtf8(const std::string& str)
+{
+ if (str.empty())
+ return std::string();
+
+#ifdef _WIN32
+ wchar_t wide[str.length() + 1];
+ int chars = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), wide, sizeof(wide));
+ if (chars < 0)
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return str;
+ }
+
+ int bytesRequired = ::WideCharToMultiByte(CP_UTF8, 0, wide, chars,
+ NULL, 0,
+ NULL, NULL);
+ if (bytesRequired == 0)
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return str;
+ }
+ std::vector<char> buf(bytesRequired, 0);
+ int bytesWritten = ::WideCharToMultiByte(CP_UTF8, 0, wide, chars,
+ &(buf[0]), buf.size(),
+ NULL, NULL);
+ return std::string(buf.begin(), buf.end());
+#else
+ return str;
+#endif
+}
+
+std::string toLower(const std::string& str)
+{
+ std::string lower = str;
+ std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
+ return lower;
+}
+
+std::string textToHtml(const std::string& str)
+{
+ std::string html = str;
+ boost::replace_all(html, "&", "&");
+ boost::replace_all(html, "<", "<");
+ return html;
+}
+
+namespace {
+std::string escape(std::string specialChars,
+ const std::map<char, std::string>& replacements,
+ std::string str)
+{
+ std::string result;
+ result.reserve(static_cast<size_t>(str.size() * 1.2));
+
+ size_t tail = 0;
+ for (size_t head = 0;
+ head < str.size()
+ && str.npos != (head = str.find_first_of(specialChars, head));
+ tail = ++head)
+ {
+ if (tail < head)
+ result.append(str, tail, head - tail);
+
+ result.append(replacements.find(str.at(head))->second);
+ }
+
+ if (tail < str.size())
+ result.append(str, tail, std::string::npos);
+
+ return result;
+
+}
+} // anonymous namespace
+
+std::string htmlEscape(const std::string& str, bool isAttributeValue)
+{
+ std::string escapes = isAttributeValue ?
+ "<>&'\"\r\n" :
+ "<>&" ;
+
+ std::map<char, std::string> subs;
+ subs['<'] = "<";
+ subs['>'] = ">";
+ subs['&'] = "&";
+ if (isAttributeValue)
+ {
+ subs['\''] = "'";
+ subs['"'] = """;
+ subs['\r'] = "
";
+ subs['\n'] = "
";
+ }
+
+ return escape(escapes, subs, str);
+}
+
+std::string jsLiteralEscape(const std::string& str)
+{
+ std::string escapes = "\\'\"\r\n<";
+
+ std::map<char, std::string> subs;
+ subs['\\'] = "\\\\";
+ subs['\''] = "\\'";
+ subs['"'] = "\\\"";
+ subs['\r'] = "\\r";
+ subs['\n'] = "\\n";
+ subs['<'] = "\074";
+
+ return escape(escapes, subs, str);
+}
+
+std::string jsonLiteralEscape(const std::string& str)
+{
+ std::string escapes = "\\\"\r\n";
+
+ std::map<char, std::string> subs;
+ subs['\\'] = "\\\\";
+ subs['"'] = "\\\"";
+ subs['\r'] = "\\r";
+ subs['\n'] = "\\n";
+
+ return escape(escapes, subs, str);
+}
+
+// The str that is passed in should INCLUDE the " " around the value!
+// (Sorry this is inconsistent with jsonLiteralEscape, but it's more efficient
+// than adding double-quotes in this function)
+std::string jsonLiteralUnescape(const std::string& str)
+{
+ json::Value value;
+ if (!json::parse(str, &value) || !json::isType<std::string>(value))
+ {
+ LOG_ERROR_MESSAGE("Failed to unescape JS literal");
+ return str;
+ }
+
+ return value.get_str();
+}
+
+std::string filterControlChars(const std::string& str)
+{
+ // Delete control chars, which can cause errors in JSON parsing (especially
+ // \0003)
+ return boost::regex_replace(str,
+ boost::regex("[\\0000-\\0010\\0016-\\0037]+"),
+ "");
+}
+
+namespace {
+
+std::vector<bool> initLookupTable(wchar_t ranges[][2], size_t rangeCount)
+{
+ std::vector<bool> results(0xFFFF, false);
+ for (size_t i = 0; i < rangeCount; i++)
+ {
+ for (wchar_t j = ranges[i][0]; j <= ranges[i][1]; j++)
+ results[j] = true;
+ }
+ return results;
+}
+
+// See https://gist.github.com/1110629 for range generating script
+
+std::vector<bool> initAlnumLookupTable()
+{
+ wchar_t ranges[][2] = {
+ {0x30, 0x39}, {0x41, 0x5A}, {0x61, 0x7A}, {0xAA, 0xAA}, {0xB5, 0xB5}, {0xBA, 0xBA}, {0xC0, 0xD6}, {0xD8, 0xF6}, {0xF8, 0x2C1}, {0x2C6, 0x2D1}, {0x2E0, 0x2E4}, {0x2EC, 0x2EC}, {0x2EE, 0x2EE}, {0x370, 0x374}, {0x376, 0x37D}, {0x386, 0x386}, {0x388, 0x3F5}, {0x3F7, 0x481}, {0x48A, 0x559}, {0x561, 0x587}, {0x5D0, 0x5F2}, {0x620, 0x64A}, {0x660, 0x669}, {0x66E, 0x66F}, {0x671, 0x6D3}, {0x6D5, 0x6D5}, {0x6E5, 0x6E6}, {0x6EE, 0x6FC}, {0x6FF, 0x6FF}, {0x710, 0x710}, {0x712, 0x72F}, {0x74D, [...]
+ };
+
+ return initLookupTable(ranges, sizeof(ranges) / sizeof(ranges[0]));
+}
+
+std::vector<bool> initAlphaLookupTable()
+{
+ wchar_t ranges[][2] = {
+ {0x41, 0x5A}, {0x61, 0x7A}, {0xAA, 0xAA}, {0xB5, 0xB5}, {0xBA, 0xBA}, {0xC0, 0xD6}, {0xD8, 0xF6}, {0xF8, 0x2C1}, {0x2C6, 0x2D1}, {0x2E0, 0x2E4}, {0x2EC, 0x2EC}, {0x2EE, 0x2EE}, {0x370, 0x374}, {0x376, 0x37D}, {0x386, 0x386}, {0x388, 0x3F5}, {0x3F7, 0x481}, {0x48A, 0x559}, {0x561, 0x587}, {0x5D0, 0x5F2}, {0x620, 0x64A}, {0x66E, 0x66F}, {0x671, 0x6D3}, {0x6D5, 0x6D5}, {0x6E5, 0x6E6}, {0x6EE, 0x6EF}, {0x6FA, 0x6FC}, {0x6FF, 0x6FF}, {0x710, 0x710}, {0x712, 0x72F}, {0x74D, 0x7A5}, {0x7B [...]
+ };
+
+ return initLookupTable(ranges, sizeof(ranges) / sizeof(ranges[0]));
+}
+
+} // anonymous namespace
+
+bool isalpha(wchar_t c)
+{
+ static std::vector<bool> lookup = initAlphaLookupTable();
+ if (c > 0xFFFF)
+ return false; // This function only supports BMP
+ return lookup.at(c);
+}
+
+bool isalnum(wchar_t c)
+{
+ static std::vector<bool> lookup;
+ if (lookup.empty())
+ lookup = initAlnumLookupTable();
+
+ if (c > 0xFFFF)
+ return false; // This function only supports BMP
+ return lookup.at(c);
+}
+
+bool parseVersion(const std::string& str, uint64_t* pVersion)
+{
+ uint64_t version = 0;
+
+ std::vector<std::string> chunks;
+ boost::algorithm::split(chunks, str, boost::algorithm::is_any_of("."));
+
+ if (chunks.empty())
+ return false;
+
+ for (size_t i = 0; i < chunks.size() && i < 4; i++)
+ {
+ uint16_t value = core::safe_convert::stringTo<uint16_t>(
+ chunks[i], std::numeric_limits<uint16_t>::max());
+ if (value == std::numeric_limits<uint16_t>::max())
+ return false;
+ version += static_cast<uint64_t>(value) << ((3-i) * 16);
+ }
+ if (pVersion)
+ *pVersion = version;
+ return true;
+}
+
+void trimLeadingLines(int maxLines, std::string* pLines)
+{
+ if (pLines->length() > static_cast<unsigned int>(maxLines*2))
+ {
+ int lineCount = 0;
+ std::string::const_iterator begin = pLines->begin();
+ std::string::iterator pos = pLines->end();
+ while (--pos >= begin)
+ {
+ if (*pos == '\n')
+ {
+ if (++lineCount > maxLines)
+ {
+ pLines->erase(pLines->begin(), pos);
+ break;
+ }
+ }
+ }
+ }
+}
+
+} // namespace string_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/SyslogLogWriter.cpp b/src/cpp/core/SyslogLogWriter.cpp
new file mode 100644
index 0000000..55e8693
--- /dev/null
+++ b/src/cpp/core/SyslogLogWriter.cpp
@@ -0,0 +1,109 @@
+/*
+ * SyslogLogWriter.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/SyslogLogWriter.hpp>
+
+#include <syslog.h>
+
+#include <iostream>
+
+#include <boost/algorithm/string.hpp>
+
+#include <core/system/System.hpp>
+
+namespace {
+
+int logPriority(int logLevel)
+{
+ using namespace core::system;
+
+ // map universal log entry type to posix constant
+ switch(logLevel)
+ {
+ case kLogLevelError:
+ return LOG_ERR;
+
+ case kLogLevelWarning:
+ return LOG_WARNING ;
+
+ case kLogLevelInfo:
+ return LOG_INFO ;
+
+ case kLogLevelDebug:
+ return LOG_DEBUG ;
+
+ default:
+ return LOG_WARNING;
+ }
+}
+
+} // anonymous namespace
+
+namespace core {
+
+SyslogLogWriter::~SyslogLogWriter()
+{
+ try
+ {
+ ::closelog();
+ }
+ catch(...)
+ {
+ }
+}
+
+SyslogLogWriter::SyslogLogWriter(const std::string& programIdentity,
+ int logLevel)
+ : programIdentity_(programIdentity),
+ logToStderr_(core::system::stderrIsTerminal())
+{
+ // copy program identity into new string whose buffer will stay
+ // around long enough to successfully register with openlog
+ // (passing the c_str of programIdentity wasn't working on OSX)
+ std::string* pProgramIdentity = new std::string(programIdentity);
+
+ // initialize log options
+ int logOptions = LOG_CONS | LOG_PID ;
+
+ // open log
+ ::openlog(pProgramIdentity->c_str(), logOptions, LOG_USER);
+ ::setlogmask(LOG_UPTO(logPriority(logLevel)));
+}
+
+void SyslogLogWriter::log(core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ if (logToStderr_)
+ std::cerr << formatLogEntry(programIdentity_, message, false);
+
+ // unix system log entries are delimited by newlines so we replace
+ // them with an alternate delimiter
+ std::string cleanedMessage(message);
+ boost::algorithm::replace_all(cleanedMessage, "\n", "|||");
+
+ // log to the sys-log, to display in real-time use e.g
+ // tail --follow --lines=0 /var/log/user.log
+ ::syslog(logPriority(logLevel), "%s", cleanedMessage.c_str()) ;
+}
+
+void SyslogLogWriter::log(const std::string&,
+ core::system::LogLevel logLevel,
+ const std::string& message)
+{
+ log(logLevel, message);
+}
+
+} // namespace core
+
diff --git a/src/cpp/core/Thread.cpp b/src/cpp/core/Thread.cpp
new file mode 100644
index 0000000..182eb60
--- /dev/null
+++ b/src/cpp/core/Thread.cpp
@@ -0,0 +1,63 @@
+/*
+ * Thread.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Thread.hpp>
+
+#include <core/system/System.hpp>
+
+namespace core {
+namespace thread {
+
+void safeLaunchThread(boost::function<void()> threadMain,
+ boost::thread* pThread)
+{
+ try
+ {
+ // block all signals for launch of background thread (will cause it
+ // to never receive signals)
+ core::system::SignalBlocker signalBlocker;
+ Error error = signalBlocker.blockAll();
+ if (error)
+ LOG_ERROR(error);
+
+ boost::thread t(threadMain);
+
+ if (pThread)
+ *pThread = t.move();
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ LOG_ERROR(Error(boost::thread_error::ec_from_exception(e),
+ ERROR_LOCATION));
+ }
+}
+
+} // namespace core
+} // namespace thread
+
+
+// dummy function to satisfy boost linking requirement (fixed in 1.45)
+// see: https://svn.boost.org/trac/boost/ticket/4258
+#if defined(__GNUC__) && defined(_WIN64)
+
+namespace boost {
+
+void tss_cleanup_implemented()
+{
+}
+
+} // namespace boost
+
+#endif
diff --git a/src/cpp/core/Trace.cpp b/src/cpp/core/Trace.cpp
new file mode 100644
index 0000000..28e45f5
--- /dev/null
+++ b/src/cpp/core/Trace.cpp
@@ -0,0 +1,45 @@
+/*
+ * Trace.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/Trace.hpp>
+
+#include <map>
+
+#include <boost/utility.hpp>
+
+#include <core/Thread.hpp>
+
+namespace core {
+namespace trace {
+
+namespace {
+
+boost::mutex s_traceMutex ;
+
+} // anonymous namespace
+
+
+void add(void* key, const std::string& functionName)
+{
+ LOCK_MUTEX(s_traceMutex)
+ {
+ std::cerr << key << " " << functionName << std::endl;
+ }
+ END_LOCK_MUTEX
+}
+
+} // namespace trace
+} // namespace core
diff --git a/src/cpp/core/WaitUtils.cpp b/src/cpp/core/WaitUtils.cpp
new file mode 100644
index 0000000..e633af3
--- /dev/null
+++ b/src/cpp/core/WaitUtils.cpp
@@ -0,0 +1,65 @@
+/*
+ * WaitUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/WaitUtils.hpp>
+#include <boost/system/error_code.hpp>
+
+#include <core/BoostThread.hpp>
+
+namespace core {
+
+Error waitWithTimeout(const boost::function<WaitResult()>& waitFunction,
+ int initialWaitMs,
+ int incrementWaitMs,
+ int maxWaitSec)
+{
+ // wait for the session to be available
+ using boost::system_time;
+ using namespace boost::posix_time;
+ using namespace boost::system;
+
+ // wait 30ms (this value is somewhat arbitrary -- ideally it would
+ // be as close as possible to the expected total launch time of
+ // rsession (observed to be ~35ms on a 3ghz MacPro w/ SSD disk)
+ boost::this_thread::sleep(milliseconds(initialWaitMs));
+
+ // try the connection again and if we don't get it try to reconnect
+ // every 10ms until a timeout occurs -- we use a low granularity
+ // here because expect our initial guess of 30ms to be pretty close
+ // to the total launch time
+ boost::system_time timeoutTime = boost::get_system_time() + seconds(maxWaitSec);
+ while(boost::get_system_time() < timeoutTime)
+ {
+ WaitResult result = waitFunction();
+ if (result.type == WaitSuccess)
+ return Success();
+
+ if (result.type == WaitContinue)
+ {
+ // try again after waiting a short while
+ boost::this_thread::sleep(milliseconds(incrementWaitMs));
+ continue;
+ }
+ else /* if (result.type == WaitError) */
+ {
+ // unrecoverable error (return connectError below)
+ return result.error;
+ }
+ }
+
+ return systemError(boost::system::errc::timed_out, ERROR_LOCATION);
+}
+
+} // namespace core
diff --git a/src/cpp/core/Win32StringUtils.cpp b/src/cpp/core/Win32StringUtils.cpp
new file mode 100644
index 0000000..ed1c01a
--- /dev/null
+++ b/src/cpp/core/Win32StringUtils.cpp
@@ -0,0 +1,82 @@
+/*
+ * Win32StringUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StringUtils.hpp>
+
+#include <windows.h>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+namespace core {
+namespace string_utils {
+
+std::string wideToUtf8(const std::wstring& value)
+{
+ if (value.size() == 0)
+ return std::string();
+
+ const wchar_t * cstr = value.c_str();
+ int chars = ::WideCharToMultiByte(CP_UTF8, 0,
+ cstr, -1,
+ NULL, 0, NULL, NULL);
+ if (chars == 0)
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return std::string();
+ }
+
+ std::vector<char> result(chars, 0);
+ chars = ::WideCharToMultiByte(CP_UTF8, 0,
+ cstr, -1,
+ &(result[0]), result.size(),
+ NULL, NULL);
+
+ return std::string(&(result[0]));
+}
+
+std::wstring utf8ToWide(const std::string& value,
+ const std::string& context)
+{
+ if (value.size() == 0)
+ return std::wstring();
+
+ const char * cstr = value.c_str();
+ int chars = ::MultiByteToWideChar(CP_UTF8, 0,
+ cstr, -1,
+ NULL, 0);
+ if (chars == 0)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ if (!context.empty())
+ error.addProperty("context", context);
+ LOG_ERROR(error);
+ return std::wstring();
+ }
+
+ std::vector<wchar_t> result(chars, 0);
+ chars = ::MultiByteToWideChar(CP_UTF8, 0,
+ cstr, -1,
+ &(result[0]), result.size());
+
+ return std::wstring(&(result[0]));
+}
+
+
+} // namespace string_utils
+} // namespace core
+
+
+
diff --git a/src/cpp/core/config.h.in b/src/cpp/core/config.h.in
new file mode 100644
index 0000000..6043506
--- /dev/null
+++ b/src/cpp/core/config.h.in
@@ -0,0 +1,23 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#cmakedefine HAVE_SA_NOCLDWAIT
+#cmakedefine HAVE_INOTIFY_INIT1
+#cmakedefine HAVE_SO_PEERCRED
+#cmakedefine HAVE_GETPEEREID
+#cmakedefine HAVE_PROCSELF
+#cmakedefine HAVE_SETRESUID
+#cmakedefine HAVE_SCANDIR_POSIX
+#cmakedefine RSTUDIO_SERVER
diff --git a/src/cpp/core/dev/CMakeLists.txt b/src/cpp/core/dev/CMakeLists.txt
new file mode 100644
index 0000000..4c9f1a1
--- /dev/null
+++ b/src/cpp/core/dev/CMakeLists.txt
@@ -0,0 +1,44 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (CORE_DEV)
+
+# include files
+file(GLOB_RECURSE CORE_DEV_HEADER_FILES "*.h*")
+
+# source files
+set(CORE_DEV_SOURCE_FILES
+ Main.cpp
+)
+
+# set include directories
+include_directories(
+ ${Boost_INCLUDE_DIRS}
+ ${CORE_SOURCE_DIR}/include
+)
+
+# define executable
+add_executable(coredev ${CORE_DEV_SOURCE_FILES} ${CORE_DEV_HEADER_FILES})
+
+# set link dependencies
+target_link_libraries(coredev
+ rstudio-core
+)
+
+# copy profiler script
+configure_file(coredev-profile.in ${CMAKE_CURRENT_BINARY_DIR}/coredev-profile)
+
+
+
diff --git a/src/cpp/core/dev/Main.cpp b/src/cpp/core/dev/Main.cpp
new file mode 100644
index 0000000..765b3f8
--- /dev/null
+++ b/src/cpp/core/dev/Main.cpp
@@ -0,0 +1,47 @@
+/*
+ * Main.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <iostream>
+
+#include <boost/test/minimal.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+
+using namespace core ;
+
+int test_main(int argc, char * argv[])
+{
+ try
+ {
+ // setup log
+ initializeStderrLog("coredev", core::system::kLogLevelWarning);
+
+ // ignore sigpipe
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+
+
+ return EXIT_SUCCESS;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
+
diff --git a/src/cpp/core/dev/coredev-profile.in b/src/cpp/core/dev/coredev-profile.in
new file mode 100755
index 0000000..7446523
--- /dev/null
+++ b/src/cpp/core/dev/coredev-profile.in
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+#
+# coredev-profile
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# setup profiling target/output
+env CPUPROFILE=${CMAKE_CURRENT_BINARY_DIR}/coredev.prof ${CMAKE_CURRENT_BINARY_DIR}/coredev
+
+# if the profile frequency isn't defined then default it to 1000 samples/sec
+# (default is 100 samples/sec which doesn't have enough granularity for
+# most of our measurement cases)
+if test -z "$CPUPROFILE_FREQUENCY"
+then
+ env CPUPROFILE_FREQUENCY=1000
+fi
+
+# run the executable
+${CMAKE_CURRENT_BINARY_DIR}/coredev
+
+# output the profiling data
+pprof --text "$@" ${CMAKE_CURRENT_BINARY_DIR}/coredev ${CMAKE_CURRENT_BINARY_DIR}/coredev.prof
+
+
+
diff --git a/src/cpp/core/gwt/GwtFileHandler.cpp b/src/cpp/core/gwt/GwtFileHandler.cpp
new file mode 100644
index 0000000..731bfc5
--- /dev/null
+++ b/src/cpp/core/gwt/GwtFileHandler.cpp
@@ -0,0 +1,217 @@
+/*
+ * GwtFileHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/gwt/GwtFileHandler.hpp>
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/text/TemplateFilter.hpp>
+#include <core/system/System.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+
+namespace core {
+namespace gwt {
+
+namespace {
+
+FilePath requestedFile(const std::string& wwwLocalPath,
+ const std::string& relativePath)
+{
+ // ensure that this path does not start with /
+ if (relativePath.find('/') == 0)
+ return FilePath();
+
+ // ensure that this path does not contain ..
+ if (relativePath.find("..") != std::string::npos)
+ return FilePath();
+
+#ifndef _WIN32
+
+ // calculate "real" wwwPath
+ FilePath wwwRealPath;
+ Error error = core::system::realPath(wwwLocalPath, &wwwRealPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+
+ // calculate "real" requested path
+ FilePath realRequestedPath;
+ FilePath requestedPath = wwwRealPath.complete(relativePath);
+ error = core::system::realPath(requestedPath.absolutePath(),
+ &realRequestedPath);
+ if (error)
+ {
+ // log if this isn't file not found
+ if (error.code() != boost::system::errc::no_such_file_or_directory)
+ {
+ error.addProperty("requested-path", relativePath);
+ LOG_ERROR(error);
+ }
+ return FilePath();
+ }
+
+ // validate that the requested path falls within the www path
+ if ( (realRequestedPath != wwwRealPath) &&
+ realRequestedPath.relativePath(wwwRealPath).empty() )
+ {
+ LOG_WARNING_MESSAGE("Non www-local-path URI requested: " +
+ relativePath);
+ return FilePath();
+ }
+
+ // return the path
+ return realRequestedPath;
+
+#else
+
+ // just complete the path straight away on Win32
+ return FilePath(wwwLocalPath).complete(relativePath);
+
+#endif
+
+}
+
+void handleFileRequest(const std::string& wwwLocalPath,
+ const std::string& baseUri,
+ core::http::UriFilterFunction mainPageFilter,
+ const std::string& initJs,
+ bool useEmulatedStack,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // get the uri and strip the query string
+ std::string uri = request.uri();
+ std::size_t pos = uri.find("?");
+ if (pos != std::string::npos)
+ uri.erase(pos);
+
+ // request for one-character short of root location redirects to root
+ if (uri == baseUri.substr(0, baseUri.size()-1))
+ {
+ pResponse->setMovedPermanently(request, baseUri);
+ return;
+ }
+
+ // request for a URI not within our location scope
+ if (uri.find(baseUri) != 0)
+ {
+ pResponse->setError(http::status::NotFound,
+ request.uri() + " not found");
+ return;
+ }
+
+ // auto-append index.htm to request for root location
+ const char * const kIndexFile = "index.htm";
+ if (uri == baseUri)
+ uri += kIndexFile;
+
+ // if this is main page and we have a filter then then give it a crack
+ // at the request
+ std::string mainPage = baseUri + kIndexFile;
+ if (uri == mainPage)
+ {
+ // run filter if we have one
+ if (mainPageFilter)
+ {
+ // if the filter returns false it means we should stop processing
+ if (!mainPageFilter(request, pResponse))
+ return ;
+ }
+
+ // set as chrome frame compatible
+ pResponse->setChromeFrameCompatible(request);
+ }
+
+ // get the requested file
+ std::string relativePath = uri.substr(baseUri.length());
+ FilePath filePath = requestedFile(wwwLocalPath, relativePath);
+ if (filePath.empty())
+ {
+ pResponse->setError(http::status::NotFound,
+ request.uri() + " not found");
+ return;
+ }
+
+ // case: files designated to be cached "forever"
+ if (regex_match(uri, boost::regex(".*\\.cache\\..*")))
+ {
+ pResponse->setCacheForeverHeaders();
+ pResponse->setFile(filePath, request);
+ }
+
+ // case: files designated to never be cached
+ else if (regex_match(uri, boost::regex(".*\\.nocache\\..*")))
+ {
+ pResponse->setNoCacheHeaders();
+ pResponse->setFile(filePath, request);
+ }
+ // case: main page -- don't cache and dynamically set compiler stack mode
+ else if (uri == mainPage)
+ {
+ // check for emulated stack
+ std::map<std::string,std::string> vars;
+ useEmulatedStack = useEmulatedStack ||
+ (request.queryParamValue("emulatedStack") == "1");
+ vars["compiler_stack_mode"] = useEmulatedStack ? "emulated" : "native";
+
+ // check for initJs
+ if (!initJs.empty())
+ vars["head_tags"] = "<script>" + initJs + "</script>";
+ else
+ vars["head_tags"] = std::string();
+
+ // return the page
+ pResponse->setNoCacheHeaders();
+ pResponse->setFile(filePath, request, text::TemplateFilter(vars));
+ }
+
+ // case: normal cacheable file
+ else
+ {
+ // since these are application components we force revalidation
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableFile(filePath, request);
+ }
+
+}
+
+} // anonymous namespace
+
+http::UriHandlerFunction fileHandlerFunction(
+ const std::string& wwwLocalPath,
+ const std::string& baseUri,
+ http::UriFilterFunction mainPageFilter,
+ const std::string& initJs,
+ bool useEmulatedStack)
+{
+ return boost::bind(handleFileRequest,
+ wwwLocalPath,
+ baseUri,
+ mainPageFilter,
+ initJs,
+ useEmulatedStack,
+ _1,
+ _2);
+}
+
+} // namespace gwt
+} // namespace core
+
diff --git a/src/cpp/core/gwt/GwtLogHandler.cpp b/src/cpp/core/gwt/GwtLogHandler.cpp
new file mode 100644
index 0000000..03bf5f3
--- /dev/null
+++ b/src/cpp/core/gwt/GwtLogHandler.cpp
@@ -0,0 +1,293 @@
+/*
+ * GwtLogHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/gwt/GwtLogHandler.hpp>
+
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Log.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/gwt/GwtSymbolMaps.hpp>
+
+namespace core {
+namespace gwt {
+
+namespace {
+
+// symbol maps
+SymbolMaps s_symbolMaps;
+
+// client exception
+struct ClientException
+{
+ std::string message;
+ std::string strongName;
+ std::vector<StackElement> stack;
+};
+
+Error parseClientException(const json::Object exJson, ClientException* pEx)
+{
+ json::Array stackJson;
+ Error error = json::readObject(exJson,
+ "message", &(pEx->message),
+ "strong_name", &(pEx->strongName),
+ "stack", &stackJson);
+ if (error)
+ return error;
+
+ BOOST_FOREACH(const json::Value& elementJson, stackJson)
+ {
+ if (!json::isType<json::Object>(elementJson))
+ return Error(json::errc::ParamTypeMismatch, ERROR_LOCATION);
+
+ StackElement element;
+ Error error = json::readObject(elementJson.get_obj(),
+ "file_name", &element.fileName,
+ "class_name", &element.className,
+ "method_name", &element.methodName,
+ "line_number", &element.lineNumber);
+ if (error)
+ return error;
+
+ pEx->stack.push_back(element);
+ }
+
+ return Success();
+}
+
+
+std::string formatMethod(const std::string& method)
+{
+ if (!method.empty() && method[0] == '$')
+ return method.substr(1);
+ else
+ return method;
+}
+
+bool isExceptionMechanismElement(const StackElement& element)
+{
+ return boost::algorithm::ends_with(element.methodName, "fillInStackTrace")
+
+ ||
+
+ boost::algorithm::ends_with(element.methodName, "createStackTrace")
+
+ ||
+
+ boost::algorithm::starts_with(element.fileName,
+ "com/google/gwt/emul/java/lang/Throwable")
+
+ ||
+
+ (boost::algorithm::starts_with(element.fileName,
+ "com/google/gwt/emul/java/lang/")
+ &&
+
+ boost::algorithm::ends_with(element.fileName,
+ "Exception.java"));
+}
+
+void handleLogExceptionRequest(const std::string& username,
+ const std::string& userAgent,
+ const json::JsonRpcRequest& jsonRpcRequest,
+ http::Response* pResponse)
+{
+ // read client exception json object
+ json::Object exJson;
+ Error error = json::readParam(jsonRpcRequest.params, 0, &exJson);
+ if (error)
+ {
+ LOG_ERROR(error);
+ json::setJsonRpcError(error, pResponse);
+ return;
+ }
+
+
+ // parse
+ ClientException ex;
+ error = parseClientException(exJson, &ex);
+ if (error)
+ {
+ LOG_ERROR(error);
+ json::setJsonRpcError(error, pResponse);
+ return;
+ }
+
+ // resymbolize the stack
+ std::vector<StackElement> stack = s_symbolMaps.resymbolize(ex.stack,
+ ex.strongName);
+
+ // build the log message
+ bool printFrame = false;
+ std::ostringstream ostr;
+ BOOST_FOREACH(const StackElement& element, stack)
+ {
+ // skip past java/lang/Exception entries
+ if (!printFrame)
+ {
+ if (!isExceptionMechanismElement(element))
+ printFrame = true;
+ }
+
+ if (printFrame)
+ {
+ ostr << element.fileName << "#" << element.lineNumber
+ << "::" << formatMethod(element.methodName)
+ << std::endl;
+ }
+ }
+
+ // form the log entry
+ boost::format fmt("CLIENT EXCEPTION (%1%): %2%%3%\n"
+ "%4%"
+ "Client-ID: %5%\n"
+ "User-Agent: %6%");
+ std::string logEntry = boost::str(
+ fmt % log::cleanDelims("rsession-" + username)
+ % log::cleanDelims(ex.message)
+ % log::DELIM
+ % log::cleanDelims(ostr.str())
+ % log::cleanDelims(jsonRpcRequest.clientId)
+ % log::cleanDelims(userAgent));
+
+ // log it
+ core::system::log(core::system::kLogLevelError, logEntry);
+
+
+ // set void result
+ json::setVoidJsonRpcResult(pResponse);
+}
+
+void handleLogMessageRequest(const std::string& username,
+ const std::string& userAgent,
+ const json::JsonRpcRequest& jsonRpcRequest,
+ http::Response* pResponse)
+{
+ // read params
+ int level = 0;
+ std::string message ;
+ Error error = json::readParams(jsonRpcRequest.params, &level, &message);
+ if (error)
+ {
+ LOG_ERROR(error);
+ json::setJsonRpcError(error, pResponse);
+ return;
+ }
+
+ // convert level to appropriate enum and str
+ using namespace core::system;
+ LogLevel logLevel;
+ std::string logLevelStr;
+ switch(level)
+ {
+ case 0:
+ logLevel = kLogLevelError;
+ logLevelStr = "ERROR";
+ break;
+ case 1:
+ logLevel = kLogLevelWarning;
+ logLevelStr = "WARNING";
+ break;
+ case 2:
+ logLevel = kLogLevelInfo;
+ logLevelStr = "INFO";
+ break;
+ default:
+ LOG_WARNING_MESSAGE("Unexpected log level: " +
+ safe_convert::numberToString(level));
+ logLevel = kLogLevelError;
+ logLevelStr = "ERROR";
+ break;
+ }
+
+ // form the log entry
+ boost::format fmt("CLIENT %1% (%2%-%3%): %4%; USER-AGENT: %5%");
+ std::string logEntry = boost::str(fmt % logLevelStr %
+ username %
+ jsonRpcRequest.clientId %
+ message %
+
+
+ userAgent);
+ // log it
+ core::system::log(logLevel, logEntry);
+
+ // set void result
+ json::setVoidJsonRpcResult(pResponse);
+}
+
+
+} // anonymous namespace
+
+void initializeSymbolMaps(const core::FilePath& symbolMapsPath)
+{
+ Error error = s_symbolMaps.initialize(symbolMapsPath);
+ if (error)
+ LOG_ERROR(error);
+}
+
+void handleLogRequest(const std::string& username,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // parse request
+ json::JsonRpcRequest jsonRpcRequest;
+ Error parseError = parseJsonRpcRequest(request.body(), &jsonRpcRequest) ;
+ if (parseError)
+ {
+ LOG_ERROR(parseError);
+ json::setJsonRpcError(parseError, pResponse);
+ return;
+ }
+
+ // check for supported methods
+ if (jsonRpcRequest.method == "log")
+ {
+ handleLogMessageRequest(username,
+ request.userAgent(),
+ jsonRpcRequest,
+ pResponse);
+ }
+ else if (jsonRpcRequest.method == "log_exception")
+ {
+ handleLogExceptionRequest(username,
+ request.userAgent(),
+ jsonRpcRequest,
+ pResponse);
+ }
+ else
+ {
+ Error methodError = Error(json::errc::MethodNotFound, ERROR_LOCATION);
+ methodError.addProperty("method", jsonRpcRequest.method);
+ LOG_ERROR(methodError);
+ json::setJsonRpcError(methodError, pResponse);
+ return;
+ }
+}
+
+
+} // namespace gwt
+} // namespace core
+
+
diff --git a/src/cpp/core/gwt/GwtSymbolMaps.cpp b/src/cpp/core/gwt/GwtSymbolMaps.cpp
new file mode 100644
index 0000000..1b99e37
--- /dev/null
+++ b/src/cpp/core/gwt/GwtSymbolMaps.cpp
@@ -0,0 +1,365 @@
+/*
+ * GwtSymbolMaps.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/gwt/GwtSymbolMaps.hpp>
+
+#include <string>
+#include <map>
+#include <set>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Thread.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+// NOTE: this is a port of the following GWT class:
+// (our rev was 11565, we should track to future changes)
+
+// https://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/gwt/core/server/impl/StackTraceDeobfuscator.java?r=11565
+
+namespace core {
+namespace gwt {
+
+namespace {
+
+const char * const SYMBOL_DATA_UNKNOWN = "";
+const int LINE_NUMBER_UNKNOWN = -1;
+
+ReadCollectionAction parseSymbolMapLine(
+ const std::string& line,
+ std::pair<const std::string,std::string>* pMapEntry,
+ std::set<std::string>* pSymbolsLeftToFind)
+{
+ // bail if we have no more symbols left to find
+ if (pSymbolsLeftToFind->empty())
+ return ReadCollectionTerminate;
+
+ // ignore comments and empty lines
+ if (line.empty() || (line[0] == '#'))
+ return ReadCollectionIgnoreLine;
+
+ size_t commaPos = line.find_first_of(',');
+ if (commaPos == std::string::npos)
+ return ReadCollectionIgnoreLine;
+
+ // HACK: workaround the fact that std::map uses const for the Key
+ std::string* pFirst = const_cast<std::string*>(&(pMapEntry->first)) ;
+ *pFirst = line.substr(0, commaPos);
+
+ pMapEntry->second = line.substr(commaPos+1);
+
+ pSymbolsLeftToFind->erase(*pFirst);
+
+ return ReadCollectionAddLine;
+}
+
+class SymbolCache : boost::noncopyable
+{
+public:
+ void putAll(const std::string& strongName,
+ const std::map<std::string,std::string>& symbolMap)
+ {
+ if (strongName.empty() || symbolMap.empty())
+ return;
+
+ LOCK_MUTEX(mutex_)
+ {
+ cache_[strongName].insert(symbolMap.begin(), symbolMap.end());
+ }
+ END_LOCK_MUTEX
+ }
+
+ std::map<std::string,std::string> getAll(
+ const std::string& strongName,
+ const std::set<std::string>& symbols)
+ {
+ std::map<std::string,std::string> toReturn;
+
+ LOCK_MUTEX(mutex_)
+ {
+ if (strongName.empty() ||
+ (cache_.find(strongName) == cache_.end()) ||
+ symbols.empty())
+ {
+ return toReturn;
+ }
+
+ std::map<std::string,std::string>& map = cache_[strongName];
+ BOOST_FOREACH(const std::string& symbol, symbols)
+ {
+ std::map<std::string,std::string>::const_iterator it =
+ map.find(symbol);
+ if (it != map.end())
+ toReturn.insert(*it);
+ }
+ }
+ END_LOCK_MUTEX
+
+ return toReturn;
+ }
+
+private:
+ boost::mutex mutex_;
+ std::map<std::string, std::map<std::string,std::string> > cache_;
+};
+
+} // anonymous namespace
+
+
+struct SymbolMaps::Impl
+{
+ FilePath symbolMapsPath;
+ SymbolCache symbolCache;
+
+ std::string loadOneSymbol(const std::string& strongName,
+ const std::string& symbol)
+ {
+ std::set<std::string> symbolSet;
+ symbolSet.insert(symbol);
+ std::map<std::string,std::string> map = loadSymbolMap(strongName,
+ symbolSet);
+
+ return map[symbol];
+ }
+
+ std::map<std::string,std::string> loadSymbolMap(
+ const std::string& strongName,
+ const std::set<std::string>& requiredSymbols)
+ {
+ // cache lookup first
+ std::map<std::string,std::string> toReturn = symbolCache.getAll(
+ strongName,
+ requiredSymbols);
+
+ // did that satisfy the request fully?
+ if (toReturn.size() == requiredSymbols.size())
+ return toReturn;
+
+ // lookup additional symbols by reading the file
+ std::set<std::string> symbolsLeftToFind = requiredSymbols;
+
+ // read it from disk if it exists
+ FilePath mapPath = symbolMapsPath.childPath(strongName + ".symbolMap");
+ if (mapPath.exists())
+ {
+ Error error = readCollectionFromFile
+ <std::map<std::string,std::string> >(
+ mapPath,
+ &toReturn,
+ boost::bind(parseSymbolMapLine, _1, _2, &symbolsLeftToFind));
+ if (error)
+ LOG_ERROR(error);
+
+ }
+
+ // mark all remaining symbols as having been looked for
+ BOOST_FOREACH(const std::string& symbol, symbolsLeftToFind)
+ {
+ toReturn[symbol] = SYMBOL_DATA_UNKNOWN;
+ }
+
+ // add the return results to the cache
+ symbolCache.putAll(strongName, toReturn);
+
+ // return the results
+ return toReturn;
+ }
+};
+
+
+SymbolMaps::SymbolMaps()
+ : pImpl_(new Impl())
+{
+}
+
+SymbolMaps::~SymbolMaps()
+{
+ try
+ {
+ }
+ catch(...)
+ {
+ }
+}
+
+Error SymbolMaps::initialize(const FilePath& symbolMapsPath)
+{
+ pImpl_->symbolMapsPath = symbolMapsPath;
+ return Success();
+}
+
+std::vector<StackElement> SymbolMaps::resymbolize(
+ const std::vector<StackElement>& stack,
+ const std::string& strongName)
+{
+ // warm the symbol cache
+ std::set<std::string> requiredSymbols;
+ BOOST_FOREACH(const StackElement& stackElement, stack)
+ {
+ requiredSymbols.insert(stackElement.methodName);
+ }
+ pImpl_->loadSymbolMap(strongName, requiredSymbols);
+
+ // perform the resymbolization
+ std::vector<StackElement> resymbolizedStack;
+ BOOST_FOREACH(const StackElement& se, stack)
+ {
+ resymbolizedStack.push_back(resymbolize(se, strongName));
+ }
+ return resymbolizedStack;
+}
+
+StackElement SymbolMaps::resymbolize(const StackElement& se,
+ const std::string& strongName)
+{
+ using namespace boost::algorithm;
+
+ // values we will fill in
+ std::string declaringClass, methodName, fileName;
+ int lineNumber = -1, fragmentId = -1;
+
+ std::string steFilename = se.fileName;
+ std::string symbolData = pImpl_->loadOneSymbol(strongName, se.methodName);
+
+ // detect whether we are source map capable and extract the column if we are
+ // (note: were not currently using source maps but we still ported this
+ // code from the GWT Java implementation in case we want to add the
+ // sourcemap support later)
+ bool sourceMapCapable = false;
+ int column = 1;
+ if (!steFilename.empty())
+ {
+ // column information is encoded after '@' for sourceMap capable browsers
+ size_t columnMarkerPos = steFilename.find_first_of('@');
+ if (columnMarkerPos != std::string::npos)
+ {
+ try
+ {
+ std::string colStr = steFilename.substr(columnMarkerPos+1);
+ column = boost::lexical_cast<int>(colStr);
+ sourceMapCapable = true;
+ }
+ catch(boost::bad_lexical_cast&)
+ {
+ }
+ steFilename = steFilename.substr(0, columnMarkerPos);
+ }
+ }
+
+ // extract the details
+ if (!symbolData.empty())
+ {
+ std::vector<std::string> parts;
+ split(parts, symbolData, is_any_of(","));
+ if (parts.size() == 6)
+ {
+ boost::regex re("@?([^:]+)::([^(]+)(\\((.*)\\))?");
+ boost::smatch match;
+ if (boost::regex_search(parts[0], match, re))
+ {
+ declaringClass = match[1];
+ methodName = match[2];
+ }
+ else
+ {
+ declaringClass = se.className;
+ methodName = se.methodName;
+ }
+
+ // parts[3] contains the source file URI or "Unknown"
+ fileName = "";
+ if (parts[3] != "Unknown")
+ {
+ fileName = parts[3];
+
+ // normalize filenames that are generated by custom linkers
+ size_t pos = fileName.find("file:");
+ if (pos == 0)
+ {
+ std::string genPrefix("src/gwt/gen/");
+ pos = fileName.find(genPrefix);
+ if (pos != std::string::npos)
+ fileName = fileName.substr(pos + genPrefix.length());
+ }
+ }
+
+ lineNumber = se.lineNumber;
+
+ // When lineNumber is LINE_NUMBER_UNKNOWN, either because
+ // compiler.stackMode is not emulated or
+ // compiler.emulatedStack.recordLineNumbers is false, use the method
+ // declaration line number from the symbol map.
+ if (lineNumber == LINE_NUMBER_UNKNOWN ||
+ (sourceMapCapable && column == -1))
+ {
+ // Safari will send line numbers, with col == -1, we need to
+ // use symbolMap in this case
+ lineNumber = safe_convert::stringTo<int>(parts[4], lineNumber);
+ }
+
+ fragmentId = safe_convert::stringTo<int>(parts[5], fragmentId);
+ }
+ }
+
+ // anonymous function, try to use <fragmentNum>.js:line to determine
+ // fragment id
+ if (fragmentId == -1 && !steFilename.empty())
+ {
+ // fragment identifier encoded in filename
+ boost::regex re(".*(\\d+)\\.js");
+ boost::smatch match;
+ if (boost::regex_search(steFilename, match, re))
+ {
+ fragmentId = safe_convert::stringTo<int>(match[1], fragmentId);
+ }
+ else if (boost::algorithm::contains(steFilename, strongName))
+ {
+ fragmentId = 0;
+ }
+ }
+
+ // try to refine location with sourceMap
+ // int jsLineNumber = se.lineNumber;
+
+ // NOTE: not yet implemented -- the upside of this appears to be that
+ // we could get the precise line location of the exception
+
+ if (!declaringClass.empty())
+ {
+ StackElement element;
+ element.className = declaringClass;
+ element.methodName = methodName;
+ element.fileName = fileName;
+ element.lineNumber = lineNumber;
+ return element;
+ }
+ else // if anything goes wrong just return the original
+ {
+ return se;
+ }
+
+ return se;
+}
+
+} // namespace gwt
+} // namespace core
+
+
diff --git a/src/cpp/core/http/Cookie.cpp b/src/cpp/core/http/Cookie.cpp
new file mode 100644
index 0000000..6137359
--- /dev/null
+++ b/src/cpp/core/http/Cookie.cpp
@@ -0,0 +1,105 @@
+/*
+ * Cookie.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/Cookie.hpp>
+
+#include <core/http/URL.hpp>
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+using namespace boost::gregorian ;
+
+namespace core {
+namespace http {
+
+Cookie::Cookie(const Request& request,
+ const std::string& name,
+ const std::string& value,
+ const std::string& path,
+ bool httpOnly)
+ : name_(name),
+ value_(value),
+ path_(path),
+ expires_(not_a_date_time),
+ httpOnly_(httpOnly)
+{
+ if (path.empty() && URL::complete(request.uri(), "") != "/")
+ {
+ // If we're here, it means we're using an implicit path that
+ // isn't the root. In other words, we're setting a cookie
+ // that will only apply under the current "path". Since this
+ // is not something we're likely to want, we warn--if this
+ // is actually the desired behavior then use an explicit path
+ // value.
+ LOG_WARNING_MESSAGE("Implicit path used with non-root URL (" +
+ request.uri() + ")");
+ }
+}
+
+Cookie::~Cookie()
+{
+}
+
+void Cookie::setExpires(const days& expiresDays)
+{
+ expires_ = date(day_clock::universal_day() + expiresDays) ;
+}
+
+void Cookie::setExpiresDelete()
+{
+ expires_ = date(day_clock::universal_day() - days(2)) ;
+}
+
+void Cookie::setHttpOnly()
+{
+ httpOnly_ = true;
+}
+
+std::string Cookie::cookieHeaderValue() const
+{
+ // basic name/value
+ std::ostringstream headerValue ;
+ headerValue << name() << "=" << value() ;
+
+ // expiries if specified
+ if ( !expires().is_not_a_date() )
+ {
+ date::ymd_type ymd = expires_.year_month_day() ;
+ greg_weekday wd = expires_.day_of_week() ;
+
+ headerValue << "; expires=" ;
+ headerValue << wd.as_short_string() << ", "
+ << ymd.day << "-" << ymd.month.as_short_string() << "-"
+ << ymd.year << " 23:59:59 GMT" ;
+ }
+
+ // path if specified
+ if ( !path().empty() )
+ headerValue << "; path=" << path();
+
+ // domain if specified
+ if ( !domain().empty() )
+ headerValue << "; domain=" << domain() ;
+
+ // http only if specified
+ if (httpOnly_)
+ headerValue << "; HttpOnly";
+
+ // return the header value
+ return headerValue.str() ;
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/Header.cpp b/src/cpp/core/http/Header.cpp
new file mode 100644
index 0000000..60d35b7
--- /dev/null
+++ b/src/cpp/core/http/Header.cpp
@@ -0,0 +1,98 @@
+/*
+ * Header.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/Header.hpp>
+
+#include <iostream>
+#include <algorithm>
+
+#include <boost/algorithm/string.hpp>
+
+namespace core {
+namespace http {
+
+bool HeaderNamePredicate::operator()(const Header& header) const
+{
+ return boost::iequals(name_, header.name);
+}
+
+bool containsHeader(const Headers& headers, const std::string& name)
+{
+ return findHeader(headers, name) != headers.end();
+}
+
+Headers::const_iterator findHeader(const Headers& headers,
+ const std::string& name)
+{
+ return std::find_if(headers.begin(),
+ headers.end(),
+ HeaderNamePredicate(name));
+}
+
+std::string headerValue(const Headers& headers, const std::string& name)
+{
+ Headers::const_iterator it = std::find_if(headers.begin(),
+ headers.end(),
+ HeaderNamePredicate(name)) ;
+
+ if ( it != headers.end() )
+ return (*it).value ;
+ else
+ return std::string() ;
+}
+
+
+bool parseHeader(const std::string& line, Header* pHeader)
+{
+ // parse the name and value out of the header
+ std::string::size_type pos = line.find(": ") ;
+ if ( pos != std::string::npos )
+ {
+ pHeader->name = line.substr(0, pos) ;
+ pHeader->value = line.substr(pos + 2) ;
+ boost::algorithm::trim(pHeader->name);
+ boost::algorithm::trim(pHeader->value) ;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void parseHeaders(std::istream& is, Headers* pHeaders)
+{
+ bool parsing = false;
+ std::string line;
+ while (std::getline(is, line))
+ {
+ // this is either leading whitespace or a termination condition
+ if (line == "\r")
+ {
+ if (parsing)
+ break;
+ else
+ continue;
+ }
+
+ Header header;
+ if (parseHeader(line, &header))
+ pHeaders->push_back(header);
+ parsing = true;
+ }
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/Message.cpp b/src/cpp/core/http/Message.cpp
new file mode 100644
index 0000000..27cbcbe
--- /dev/null
+++ b/src/cpp/core/http/Message.cpp
@@ -0,0 +1,245 @@
+/*
+ * Message.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/Message.hpp>
+
+#include <algorithm>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/function.hpp>
+#include <boost/asio/buffer.hpp>
+
+#include <core/SafeConvert.hpp>
+
+namespace core {
+namespace http {
+
+// encodings
+const char * const kGzipEncoding = "gzip";
+
+void Message::setHttpVersion(int httpVersionMajor, int httpVersionMinor)
+{
+ httpVersionMajor_ = httpVersionMajor ;
+ httpVersionMinor_ = httpVersionMinor ;
+}
+
+void Message::setContentType(const std::string& contentType)
+{
+ setHeader("Content-Type", contentType) ;
+}
+
+std::string Message::contentType() const
+{
+ return headerValue("Content-Type") ;
+}
+
+std::size_t Message::contentLength() const
+{
+ return safe_convert::stringTo<std::size_t>(headerValue("Content-Length"), 0);
+}
+
+void Message::setContentLength(int contentLength)
+{
+ setHeader("Content-Length", contentLength);
+}
+
+void Message::addHeader(const std::string& name, const std::string& value)
+{
+ Header header ;
+ header.name = name ;
+ header.value = value ;
+ addHeader(header);
+}
+
+void Message::addHeader(const Header& header)
+{
+ headers_.push_back(header);
+}
+
+void Message::addHeaders(const std::vector<Header>& headers)
+{
+ std::copy(headers.begin(), headers.end(), std::back_inserter(headers_));
+}
+
+std::string Message::headerValue(const std::string& name) const
+{
+ return http::headerValue(headers_, name);
+}
+
+bool Message::containsHeader(const std::string& name) const
+{
+ return http::containsHeader(headers_, name);
+}
+
+void Message::setHeaderLine(const std::string& line)
+{
+ Header header;
+ if (http::parseHeader(line, &header))
+ setHeader(header);
+}
+
+void Message::setHeader(const Header& header)
+{
+ setHeader(header.name, header.value);
+}
+
+void Message::setHeader(const std::string& name, const std::string& value)
+{
+ Headers::iterator it = std::find_if(headers_.begin(),
+ headers_.end(),
+ HeaderNamePredicate(name));
+ if ( it != headers_.end() )
+ {
+ Header hdr ;
+ hdr.name = name ;
+ hdr.value = value ;
+ *it = hdr ;
+ }
+ else
+ {
+ addHeader(name, value) ;
+ }
+}
+
+void Message::setHeader(const std::string& name, int value)
+{
+ setHeader(name, safe_convert::numberToString(value));
+}
+
+void Message::replaceHeader(const std::string& name, const std::string& value)
+{
+ Header hdr ;
+ hdr.name = name ;
+ hdr.value = value ;
+ std::replace_if(headers_.begin(),
+ headers_.end(),
+ HeaderNamePredicate(name),
+ hdr) ;
+}
+
+
+void Message::removeHeader(const std::string& name)
+{
+ headers_.erase(std::remove_if(headers_.begin(),
+ headers_.end(),
+ HeaderNamePredicate(name)),
+ headers_.end()) ;
+}
+
+
+void Message::reset()
+{
+ setHttpVersion(1,1) ;
+ httpVersion_.clear() ;
+ headers_.clear() ;
+ body_.clear() ;
+
+ // allow additional reseting by subclasses
+ resetMembers() ;
+}
+
+namespace {
+const char Space[] = { ' ' } ;
+const char HeaderSeparator[] = { ':', ' ' } ;
+const char CrLf[] = { '\r', '\n' } ;
+
+void appendHeader(const Header& header,
+ std::vector<boost::asio::const_buffer>* pBuffers)
+{
+ pBuffers->push_back(boost::asio::buffer(header.name)) ;
+ pBuffers->push_back(boost::asio::buffer(HeaderSeparator)) ;
+ pBuffers->push_back(boost::asio::buffer(header.value)) ;
+ pBuffers->push_back(boost::asio::buffer(CrLf)) ;
+}
+
+}
+
+std::vector<boost::asio::const_buffer> Message::toBuffers(
+ const Header& overrideHeader) const
+{
+ // buffers to return
+ std::vector<boost::asio::const_buffer> buffers ;
+
+ // call subclass to append first line
+ appendFirstLineBuffers(buffers) ;
+ buffers.push_back(boost::asio::buffer(CrLf)) ;
+
+ // copy override header (for stable storage)
+ overrideHeader_ = overrideHeader;
+
+ // headers
+ for (Headers::const_iterator
+ it = headers_.begin(); it != headers_.end(); ++it)
+ {
+ // add the header if it isn't being overriden
+ if (it->name != overrideHeader_.name)
+ appendHeader(*it, &buffers);
+ }
+
+ // add override header
+ if (!overrideHeader_.empty())
+ appendHeader(overrideHeader_, &buffers);
+
+ // empty line
+ buffers.push_back(boost::asio::buffer(CrLf)) ;
+
+ // body
+ buffers.push_back(boost::asio::buffer(body_)) ;
+
+ // return the buffers
+ return buffers ;
+}
+
+
+void Message::appendSpaceBuffer(
+ std::vector<boost::asio::const_buffer>& buffers) const
+{
+ buffers.push_back(boost::asio::buffer(Space)) ;
+}
+
+void Message::appendHttpVersionBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const
+{
+ std::ostringstream httpVersionStream;
+ httpVersionStream << "HTTP/" << httpVersionMajor_ << "." << httpVersionMinor_ ;
+ httpVersion_ = httpVersionStream.str() ;
+ buffers.push_back(boost::asio::buffer(httpVersion_)) ;
+}
+
+
+std::ostream& operator << (std::ostream& stream, const Message& m)
+{
+ // headers
+ for (Headers::const_iterator it =
+ m.headers().begin(); it != m.headers().end(); ++ it)
+ {
+ stream << it->name << ": " << it->value << std::endl ;
+ }
+
+ // empty line
+ stream << std::endl ;
+
+ // body
+ stream << m.body() << std::endl ;
+
+ return stream ;
+}
+
+
+} // namespace http
+} // namespace core
+
+
diff --git a/src/cpp/core/http/MultipartRelated.cpp b/src/cpp/core/http/MultipartRelated.cpp
new file mode 100644
index 0000000..015cf13
--- /dev/null
+++ b/src/cpp/core/http/MultipartRelated.cpp
@@ -0,0 +1,52 @@
+/*
+ * MultipartRelated.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/MultipartRelated.hpp>
+
+#include <iostream>
+
+#define kBoundary "END_OF_PART";
+#define kSectionBoundary "--END_OF_PART"
+#define kTerminatingBoundary "--END_OF_PART--"
+#define kContentType "multipart/related; boundary=END_OF_PART"
+
+namespace core {
+namespace http {
+
+void MultipartRelated::addPart(const std::string& contentType,
+ const std::string& body)
+{
+ bodyStream_ << kSectionBoundary << std::endl;
+ bodyStream_ << "Content-Type: " << contentType << std::endl << std::endl;
+ bodyStream_ << body << std::endl;
+}
+
+void MultipartRelated::terminate()
+{
+ bodyStream_ << kTerminatingBoundary;
+}
+
+std::string MultipartRelated::contentType() const
+{
+ return kContentType;
+}
+
+std::string MultipartRelated::body() const
+{
+ return bodyStream_.str();
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/NamedPipeProtocol.cpp b/src/cpp/core/http/NamedPipeProtocol.cpp
new file mode 100644
index 0000000..e04d0f5
--- /dev/null
+++ b/src/cpp/core/http/NamedPipeProtocol.cpp
@@ -0,0 +1,48 @@
+/*
+ * NamedPipeProtocol.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/NamedPipeProtocol.hpp>
+
+namespace core {
+namespace http {
+
+
+// specialization of closeSocket for stream handle lowest level
+template<> Error closeSocket(
+ boost::asio::windows::stream_handle::lowest_layer_type& socket)
+{
+ if (socket.is_open())
+ {
+ boost::system::error_code ec;
+ socket.close(ec);
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+ }
+
+ return Success();
+}
+
+// specialization of closeSocket for stream handles
+template<> Error closeSocket(boost::asio::windows::stream_handle& socket)
+{
+ // delegate to lowest_layer (it's the same object)
+ return closeSocket(socket.lowest_layer());
+}
+
+
+
+} // namespace http
+} // namespace core
+
diff --git a/src/cpp/core/http/Request.cpp b/src/cpp/core/http/Request.cpp
new file mode 100644
index 0000000..cfea658
--- /dev/null
+++ b/src/cpp/core/http/Request.cpp
@@ -0,0 +1,254 @@
+/*
+ * Request.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/Request.hpp>
+
+#include <boost/tokenizer.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Log.hpp>
+#include <core/Thread.hpp>
+
+namespace core {
+namespace http {
+
+Request::Request()
+ : remoteUid_(-1),
+ parsedCookies_(false),
+ parsedFormFields_(false),
+ parsedQueryParams_(false)
+{
+}
+
+Request::~Request()
+{
+}
+
+std::string Request::absoluteUri() const
+{
+ std::string scheme = "http";
+ std::string forwardedScheme = headerValue("X-Forwarded-Proto");
+ if (!forwardedScheme.empty())
+ scheme = forwardedScheme;
+
+ return scheme + "://" + host() + uri();
+}
+
+bool Request::acceptsContentType(const std::string& contentType) const
+{
+ return headerValue("Accept").find(contentType) != std::string::npos;
+}
+
+bool Request::acceptsEncoding(const std::string& encoding) const
+{
+ // read , separated fields
+ using namespace boost ;
+ char_separator<char> comma(",");
+ tokenizer<char_separator<char> > tokens(acceptEncoding(), comma);
+ return std::find(tokens.begin(), tokens.end(), encoding) != tokens.end();
+}
+
+boost::posix_time::ptime Request::ifModifiedSince() const
+{
+ using namespace boost::posix_time;
+
+ std::string modifiedSinceDate = headerValue("If-Modified-Since");
+ if (!modifiedSinceDate.empty())
+ {
+ return util::parseHttpDate(modifiedSinceDate);
+ }
+ else
+ {
+ return ptime(not_a_date_time);
+ }
+
+}
+
+std::string Request::queryString() const
+{
+ // find ? in uri()
+ std::string::size_type pos = uri().find('?');
+ if (pos != std::string::npos)
+ {
+ std::string::size_type qsPos = pos + 1;
+ if (uri().length() > qsPos)
+ return uri().substr(qsPos);
+ else
+ return std::string();
+ }
+ else
+ {
+ return std::string();
+ }
+}
+
+const Fields& Request::queryParams() const
+{
+ if (!parsedQueryParams_)
+ {
+ util::parseQueryString(queryString(), &queryParams_);
+ parsedQueryParams_ = true;
+ }
+
+ return queryParams_;
+}
+
+std::string Request::cookieValue(const std::string& name) const
+{
+ // parse cookies on demand
+ if ( !parsedCookies_ )
+ {
+ for (Headers::const_iterator it =
+ headers().begin(); it != headers().end(); ++it )
+ {
+ scanHeaderForCookie(it->name, it->value) ;
+ }
+ parsedCookies_ = true ;
+ }
+
+ // lookup the cookie
+ return util::fieldValue(cookies_, name);
+}
+
+std::string Request::formFieldValue(const std::string& name) const
+{
+ ensureFormFieldsParsed();
+
+ // lookup the form field
+ return util::fieldValue(formFields_, name);
+}
+
+const Fields& Request::formFields() const
+{
+ ensureFormFieldsParsed();
+
+ return formFields_ ;
+}
+
+const File& Request::uploadedFile(const std::string& name) const
+{
+ ensureFormFieldsParsed();
+
+ // lookup the file
+ for (Files::const_iterator it = files_.begin(); it != files_.end(); ++it)
+ {
+ if (it->first == name)
+ return it->second;
+ }
+
+ // not found
+ return emptyFile_;
+}
+
+std::string Request::queryParamValue(const std::string& name) const
+{
+ // lookup the query param
+ return util::fieldValue(queryParams(), name);
+}
+
+void Request::setBody(const std::string& body)
+{
+ body_ = body;
+ setContentLength(body_.length());
+}
+
+void Request::debugPrintUri(const std::string& caption) const
+{
+ static boost::mutex printMutex;
+ LOCK_MUTEX(printMutex)
+ {
+ std::cerr << caption << ": " << uri() << std::endl;
+ }
+ END_LOCK_MUTEX
+}
+
+void Request::resetMembers()
+{
+ method_.clear() ;
+ uri_.clear() ;
+ parsedCookies_ = false ;
+ cookies_.clear() ;
+ parsedFormFields_ = false ;
+ formFields_.clear() ;
+ parsedQueryParams_ = false;
+ queryParams_.clear();
+}
+
+void Request::appendFirstLineBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const
+{
+ using boost::asio::buffer ;
+
+ // request line
+ buffers.push_back(buffer(method_)) ;
+ appendSpaceBuffer(buffers) ;
+ buffers.push_back(buffer(uri_)) ;
+ appendSpaceBuffer(buffers) ;
+ appendHttpVersionBuffers(buffers) ;
+}
+
+void Request::ensureFormFieldsParsed() const
+{
+ // parase form fields on demand
+ if ( !parsedFormFields_ )
+ {
+ std::string contentType = headerValue("Content-Type");
+ if (contentType == "application/x-www-form-urlencoded")
+ {
+ util::parseFields(body(),
+ "&",
+ "=",
+ &formFields_,
+ util::FieldDecodeForm);
+ }
+ else if (contentType.find("multipart/form-data") == 0)
+ {
+ util::parseMultipartForm(contentType, body(), &formFields_, &files_);
+ }
+ else
+ {
+ // no form fields available
+ }
+
+ parsedFormFields_ = true ;
+ }
+}
+
+void Request::scanHeaderForCookie(const std::string& name,
+ const std::string& value) const
+{
+ if (boost::iequals(name, "cookie"))
+ util::parseFields(value, ";, ", "= ", &cookies_, util::FieldDecodeNone) ;
+}
+
+std::ostream& operator << (std::ostream& stream, const Request& r)
+{
+ // output request line
+ stream << r.method() << " "
+ << r.uri()
+ << " HTTP/" << r.httpVersionMajor() << "." << r.httpVersionMinor()
+ << std::endl ;
+
+ // output headers and body
+ const Message& m = r ;
+ stream << m ;
+
+ return stream ;
+}
+
+} // namespacce http
+} // namespace core
+
diff --git a/src/cpp/core/http/RequestParser.cpp b/src/cpp/core/http/RequestParser.cpp
new file mode 100644
index 0000000..fb701d2
--- /dev/null
+++ b/src/cpp/core/http/RequestParser.cpp
@@ -0,0 +1,359 @@
+/*
+ * RequestParser.cpp
+ *
+ * Copyright (C) 2009-11 by RStudio, Inc.
+ * Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/RequestParser.hpp>
+
+#include <boost/lexical_cast.hpp>
+
+namespace core {
+namespace http {
+
+RequestParser::RequestParser()
+ : state_(method_start),
+ content_length_(0),
+ parsing_content_length_(false),
+ parsing_body_(false)
+{
+}
+
+void RequestParser::reset()
+{
+ state_ = method_start;
+ content_length_ = 0 ;
+ parsing_content_length_ = false ;
+ parsing_body_ = false ;
+}
+
+RequestParser::status RequestParser::consume(Request& req, char input)
+{
+ switch (state_)
+ {
+ case method_start:
+ if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+ {
+ return error;
+ }
+ else
+ {
+ state_ = method;
+ req.method_.push_back(input);
+ return incomplete;
+ }
+ case method:
+ if (input == ' ')
+ {
+ state_ = uri;
+ return incomplete;
+ }
+ else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+ {
+ return error;
+ }
+ else
+ {
+ req.method_.push_back(input);
+ return incomplete;
+ }
+ case uri_start:
+ if (is_ctl(input))
+ {
+ return error;
+ }
+ else
+ {
+ state_ = uri;
+ req.uri_.push_back(input);
+ return incomplete;
+ }
+ case uri:
+ if (input == ' ')
+ {
+ state_ = http_version_h;
+ return incomplete;
+ }
+ else if (is_ctl(input))
+ {
+ return error;
+ }
+ else
+ {
+ req.uri_.push_back(input);
+ return incomplete;
+ }
+ case http_version_h:
+ if (input == 'H')
+ {
+ state_ = http_version_t_1;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_t_1:
+ if (input == 'T')
+ {
+ state_ = http_version_t_2;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_t_2:
+ if (input == 'T')
+ {
+ state_ = http_version_p;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_p:
+ if (input == 'P')
+ {
+ state_ = http_version_slash;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_slash:
+ if (input == '/')
+ {
+ req.httpVersionMajor_ = 0;
+ req.httpVersionMinor_ = 0;
+ state_ = http_version_major_start;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_major_start:
+ if (is_digit(input))
+ {
+ req.httpVersionMajor_ = req.httpVersionMajor_ * 10 + input - '0';
+ state_ = http_version_major;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_major:
+ if (input == '.')
+ {
+ state_ = http_version_minor_start;
+ return incomplete;
+ }
+ else if (is_digit(input))
+ {
+ req.httpVersionMajor_ = req.httpVersionMajor_ * 10 + input - '0';
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_minor_start:
+ if (is_digit(input))
+ {
+ req.httpVersionMinor_ = req.httpVersionMinor_ * 10 + input - '0';
+ state_ = http_version_minor;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case http_version_minor:
+ if (input == '\r')
+ {
+ state_ = expecting_newline_1;
+ return incomplete;
+ }
+ else if (is_digit(input))
+ {
+ req.httpVersionMinor_ = req.httpVersionMinor_ * 10 + input - '0';
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case expecting_newline_1:
+ if (input == '\n')
+ {
+ state_ = header_line_start;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case header_line_start:
+ if (input == '\r')
+ {
+ state_ = expecting_newline_3;
+ return incomplete;
+ }
+ else if (!req.headers_.empty() && (input == ' ' || input == '\t'))
+ {
+ state_ = header_lws;
+ return incomplete;
+ }
+ else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+ {
+ return error;
+ }
+ else
+ {
+ req.headers_.push_back(Header());
+ req.headers_.back().name.push_back(input);
+ state_ = header_name;
+ return incomplete;
+ }
+ case header_lws:
+ if (input == '\r')
+ {
+ state_ = expecting_newline_2;
+ return incomplete;
+ }
+ else if (input == ' ' || input == '\t')
+ {
+ return incomplete;
+ }
+ else if (is_ctl(input))
+ {
+ return error;
+ }
+ else
+ {
+ state_ = header_value;
+ req.headers_.back().value.push_back(input);
+ return incomplete;
+ }
+ case header_name:
+ if (input == ':')
+ {
+ state_ = space_before_header_value;
+
+ // look for special content-length state
+ if ( !req.headers_.back().name.compare("Content-Length") )
+ parsing_content_length_ = true ;
+
+ return incomplete;
+ }
+ else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
+ {
+ return error;
+ }
+ else
+ {
+ req.headers_.back().name.push_back(input);
+ return incomplete;
+ }
+ case space_before_header_value:
+ if (input == ' ')
+ {
+ state_ = header_value;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case header_value:
+ if (input == '\r')
+ {
+ state_ = expecting_newline_2;
+
+ // if this header was Content-Length then save it
+ if (parsing_content_length_)
+ {
+ content_length_ = boost::lexical_cast<int>(req.headers_.back().value);
+ parsing_content_length_ = false ;
+ }
+
+ return incomplete;
+ }
+ else if (is_ctl(input))
+ {
+ return error;
+ }
+ else
+ {
+ req.headers_.back().value.push_back(input);
+ return incomplete;
+ }
+ case expecting_newline_2:
+ if (input == '\n')
+ {
+ state_ = header_line_start;
+ return incomplete;
+ }
+ else
+ {
+ return error;
+ }
+ case expecting_newline_3:
+ if ( input == '\n' )
+ {
+ return complete ;
+ }
+ else
+ {
+ return error ;
+ }
+ default:
+ return error;
+ }
+}
+
+bool RequestParser::is_char(int c)
+{
+ return c >= 0 && c <= 127;
+}
+
+bool RequestParser::is_ctl(int c)
+{
+ return (c >= 0 && c <= 31) || c == 127;
+}
+
+bool RequestParser::is_tspecial(int c)
+{
+ switch (c)
+ {
+ case '(': case ')': case '<': case '>': case '@':
+ case ',': case ';': case ':': case '\\': case '"':
+ case '/': case '[': case ']': case '?': case '=':
+ case '{': case '}': case ' ': case '\t':
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool RequestParser::is_digit(int c)
+{
+ return c >= '0' && c <= '9';
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/Response.cpp b/src/cpp/core/http/Response.cpp
new file mode 100644
index 0000000..732ffac
--- /dev/null
+++ b/src/cpp/core/http/Response.cpp
@@ -0,0 +1,453 @@
+/*
+ * Response.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/Response.hpp>
+
+#include <algorithm>
+
+#include <boost/regex.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/asio/buffer.hpp>
+
+#include <core/http/URL.hpp>
+#include <core/http/Util.hpp>
+#include <core/http/Cookie.hpp>
+#include <core/Hash.hpp>
+
+#include <core/FileSerializer.hpp>
+
+namespace core {
+namespace http {
+
+Response::Response()
+ : Message(), statusCode_(status::Ok)
+{
+}
+
+const std::string& Response::statusMessage() const
+{
+ ensureStatusMessage();
+ return statusMessage_;
+}
+
+void Response::setStatusMessage(const std::string& statusMessage)
+{
+ statusMessage_ = statusMessage ;
+}
+
+std::string Response::contentEncoding() const
+{
+ return headerValue("Content-Encoding");
+}
+
+void Response::setContentEncoding(const std::string& encoding)
+{
+ setHeader("Content-Encoding", encoding);
+}
+
+void Response::setCacheWithRevalidationHeaders()
+{
+ setHeader("Expires", http::util::httpDate());
+ setHeader("Cache-Control", "public, max-age=0, must-revalidate");
+}
+
+void Response::setCacheForeverHeaders(bool publicAccessiblity)
+{
+ // set Expires header
+ using namespace boost::posix_time;
+ time_duration yearDuration = hours(365 * 24);
+ ptime expireTime = second_clock::universal_time() + yearDuration;
+ setHeader("Expires", http::util::httpDate(expireTime));
+
+ // set Cache-Control header
+ int durationSeconds = yearDuration.total_seconds();
+ std::string accessibility = publicAccessiblity ? "public" : "private";
+ std::string cacheControl(accessibility + ", max-age=" +
+ safe_convert::numberToString(durationSeconds));
+ setHeader("Cache-Control", cacheControl);
+}
+
+void Response::setCacheForeverHeaders()
+{
+ setCacheForeverHeaders(true);
+}
+
+void Response::setPrivateCacheForeverHeaders()
+{
+ // NOTE: the Google article referenced above indicates that for the
+ // private scenario you should set the Expires header in the past so
+ // that HTTP 1.0 proxies never cache it. Unfortuantely when running
+ // against localhost in Firefox we observed that this prevented Firefox
+ // from caching.
+ setCacheForeverHeaders(false);
+}
+
+// WARNING: This appears to break IE8 if Content-Disposition: attachment
+void Response::setNoCacheHeaders()
+{
+ setHeader("Expires", "Fri, 01 Jan 1990 00:00:00 GMT");
+ setHeader("Pragma", "no-cache");
+ setHeader("Cache-Control",
+ "no-cache, no-store, max-age=0, must-revalidate");
+}
+
+void Response::setChromeFrameCompatible(const Request& request)
+{
+ if (boost::algorithm::contains(request.userAgent(), "chromeframe"))
+ setHeader("X-UA-Compatible", "chrome=1");
+}
+
+void Response::addCookie(const Cookie& cookie)
+{
+ addHeader("Set-Cookie", cookie.cookieHeaderValue()) ;
+}
+
+
+Error Response::setBody(const std::string& content)
+{
+ std::istringstream is(content);
+ return setBody(is);
+}
+
+Error Response::setCacheableBody(const FilePath& filePath,
+ const Request& request)
+{
+ std::string content;
+ Error error = core::readStringFromFile(filePath, &content);
+ if (error)
+ return error;
+
+ return setCacheableBody(content, request);
+}
+
+void Response::setDynamicHtml(const std::string& html,
+ const Request& request)
+{
+ // dynamic html
+ setContentType("text/html");
+ setNoCacheHeaders();
+
+ // gzip if possible
+ if (request.acceptsEncoding(kGzipEncoding))
+ setContentEncoding(kGzipEncoding);
+
+ // set body
+ setBody(html);
+}
+
+void Response::setRangeableFile(const FilePath& filePath,
+ const Request& request)
+{
+ // read the file in from disk
+ std::string contents;
+ Error error = core::readStringFromFile(filePath, &contents);
+ if (error)
+ {
+ setError(error);
+ return;
+ }
+
+ setRangeableFile(contents, filePath.mimeContentType(), request);
+}
+
+void Response::setRangeableFile(const std::string& contents,
+ const std::string& mimeType,
+ const Request& request)
+{
+ // set content type
+ setContentType(mimeType);
+
+ // parse the range field
+ std::string range = request.headerValue("Range");
+ boost::regex re("bytes=(\\d*)\\-(\\d*)");
+ boost::smatch match;
+ if (boost::regex_match(range, match, re))
+ {
+ // specify partial content
+ setStatusCode(http::status::PartialContent);
+
+ // determine the byte range
+ const size_t kNone = -1;
+ size_t begin = safe_convert::stringTo<size_t>(match[1], kNone);
+ size_t end = safe_convert::stringTo<size_t>(match[2], kNone);
+ size_t total = contents.length();
+
+ if (end == kNone)
+ {
+ end = total-1;
+ }
+ if (begin == kNone)
+ {
+ begin = total - end;
+ end = total-1;
+ }
+
+ // set the byte range
+ addHeader("Accept-Ranges", "bytes");
+ boost::format fmt("bytes %1%-%2%/%3%");
+ std::string range = boost::str(fmt % begin % end % contents.length());
+ addHeader("Content-Range", range);
+
+ // always attempt gzip
+ if (request.acceptsEncoding(http::kGzipEncoding))
+ setContentEncoding(http::kGzipEncoding);
+
+ // set body
+ if (begin == 0 && end == (contents.length()-1))
+ setBody(contents);
+ else
+ setBody(contents.substr(begin, end-begin+1));
+ }
+ else
+ {
+ setStatusCode(http::status::RangeNotSatisfiable);
+ boost::format fmt("bytes */%1%");
+ std::string range = boost::str(fmt % contents.length());
+ addHeader("Content-Range", range);
+ }
+}
+
+void Response::setBodyUnencoded(const std::string& body)
+{
+ removeHeader("Content-Encoding");
+ body_ = body;
+ setContentLength(body_.length());
+}
+
+
+void Response::setError(int statusCode, const std::string& message)
+{
+ setStatusCode(statusCode);
+ removeCachingHeaders();
+ setContentType("text/plain");
+ setBodyUnencoded(message);
+}
+
+void Response::setError(const Error& error)
+{
+ setError(status::InternalServerError, error.code().message());
+}
+
+namespace {
+
+// only take up to the first newline to prevent http response split
+std::string safeLocation(const std::string& location)
+{
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines,
+ location,
+ boost::algorithm::is_any_of("\r\n"));
+ return lines.size() > 0 ? lines[0] : "";
+}
+
+} // anonymous namespace
+
+
+void Response::setMovedPermanently(const http::Request& request,
+ const std::string& location)
+{
+ std::string uri = URL::complete(request.absoluteUri(),
+ safeLocation(location));
+ setError(http::status::MovedPermanently, uri);
+ setHeader("Location", uri);
+}
+
+void Response::setMovedTemporarily(const http::Request& request,
+ const std::string& location)
+{
+ std::string uri = URL::complete(request.absoluteUri(),
+ safeLocation(location));
+ setError(http::status::MovedTemporarily, uri);
+ setHeader("Location", uri);
+}
+
+void Response::resetMembers()
+{
+ statusCode_ = status::Ok ;
+ statusCodeStr_.clear() ;
+ statusMessage_.clear() ;
+}
+
+void Response::removeCachingHeaders()
+{
+ removeHeader("Expires");
+ removeHeader("Pragma");
+ removeHeader("Cache-Control");
+ removeHeader("Last-Modified");
+ removeHeader("ETag");
+}
+
+std::string Response::eTagForContent(const std::string& content)
+{
+ return core::hash::crc32Hash(content);
+}
+
+void Response::appendFirstLineBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const
+{
+ // create status code string (needs to be a member so memory is still valid
+ // for use of buffers)
+ std::ostringstream statusCodeStream ;
+ statusCodeStream << statusCode_ ;
+ statusCodeStr_ = statusCodeStream.str() ;
+
+ // status line
+ appendHttpVersionBuffers(buffers) ;
+ appendSpaceBuffer(buffers) ;
+ buffers.push_back(boost::asio::buffer(statusCodeStr_)) ;
+ appendSpaceBuffer(buffers) ;
+ ensureStatusMessage() ;
+ buffers.push_back(boost::asio::buffer(statusMessage_)) ;
+}
+
+namespace status {
+namespace Message {
+ const char * const SwitchingProtocols = "SwitchingProtocols";
+ const char * const Ok = "OK" ;
+ const char * const Created = "Created";
+ const char * const PartialContent = "Partial Content";
+ const char * const MovedPermanently = "Moved Permanently" ;
+ const char * const MovedTemporarily = "Moved Temporarily" ;
+ const char * const TooManyRedirects = "Too Many Redirects";
+ const char * const SeeOther = "See Other" ;
+ const char * const NotModified = "Not Modified" ;
+ const char * const BadRequest = "Bad Request" ;
+ const char * const Unauthorized = "Unauthorized" ;
+ const char * const Forbidden = "Forbidden" ;
+ const char * const NotFound = "Not Found" ;
+ const char * const MethodNotAllowed = "Method Not Allowed" ;
+ const char * const RangeNotSatisfiable = "Range Not Satisfyable";
+ const char * const InternalServerError = "Internal Server Error" ;
+ const char * const NotImplemented = "Not Implemented" ;
+ const char * const BadGateway = "Bad Gateway" ;
+ const char * const ServiceUnavailable = "Service Unavailable" ;
+ const char * const GatewayTimeout = "Gateway Timeout" ;
+} // namespace Message
+} // namespace status
+
+
+void Response::ensureStatusMessage() const
+{
+ if ( statusMessage_.empty() )
+ {
+ using namespace status ;
+
+ switch(statusCode_)
+ {
+ case SwitchingProtocols:
+ statusMessage_ = status::Message::SwitchingProtocols;
+ break;
+
+ case Ok:
+ statusMessage_ = status::Message::Ok ;
+ break;
+
+ case Created:
+ statusMessage_ = status::Message::Created;
+ break;
+
+ case PartialContent:
+ statusMessage_ = status::Message::PartialContent;
+ break;
+
+ case MovedPermanently:
+ statusMessage_ = status::Message::MovedPermanently ;
+ break;
+
+ case MovedTemporarily:
+ statusMessage_ = status::Message::MovedTemporarily ;
+ break;
+
+ case TooManyRedirects:
+ statusMessage_ = status::Message::TooManyRedirects ;
+ break;
+
+ case SeeOther:
+ statusMessage_ = status::Message::SeeOther ;
+ break;
+
+ case NotModified:
+ statusMessage_ = status::Message::NotModified ;
+ break;
+
+ case BadRequest:
+ statusMessage_ = status::Message::BadRequest ;
+ break;
+
+ case Unauthorized:
+ statusMessage_ = status::Message::Unauthorized ;
+ break;
+
+ case Forbidden:
+ statusMessage_ = status::Message::Forbidden ;
+ break;
+
+ case NotFound:
+ statusMessage_ = status::Message::NotFound ;
+ break;
+
+ case MethodNotAllowed:
+ statusMessage_ = status::Message::MethodNotAllowed ;
+ break;
+
+ case RangeNotSatisfiable:
+ statusMessage_ = status::Message::RangeNotSatisfiable;
+ break;
+
+ case InternalServerError:
+ statusMessage_ = status::Message::InternalServerError ;
+ break;
+
+ case NotImplemented:
+ statusMessage_ = status::Message::NotImplemented ;
+ break;
+
+ case BadGateway:
+ statusMessage_ = status::Message::BadGateway ;
+ break;
+
+ case ServiceUnavailable:
+ statusMessage_ = status::Message::ServiceUnavailable ;
+ break;
+
+ case GatewayTimeout:
+ statusMessage_ = status::Message::GatewayTimeout ;
+ break;
+ }
+ }
+}
+
+
+std::ostream& operator << (std::ostream& stream, const Response& r)
+{
+ // output status line
+ stream << "HTTP/" << r.httpVersionMajor() << "." << r.httpVersionMinor()
+ << " " << r.statusCode() << " " << r.statusMessage()
+ << std::endl ;
+
+ // output headers and body
+ const Message& m = r ;
+ stream << m ;
+
+ return stream ;
+}
+
+
+} // namespacc http
+} // namespace core
+
diff --git a/src/cpp/core/http/SocketProxy.cpp b/src/cpp/core/http/SocketProxy.cpp
new file mode 100644
index 0000000..bf8d6e8
--- /dev/null
+++ b/src/cpp/core/http/SocketProxy.cpp
@@ -0,0 +1,147 @@
+/*
+ * SocketProxy.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// boost requires that winsock2.h must be included before windows.h
+#ifdef _WIN32
+#include <winsock2.h>
+#endif
+
+#include <core/http/SocketProxy.hpp>
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+
+#include <boost/asio/placeholders.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <core/http/SocketUtils.hpp>
+
+using namespace core;
+
+namespace core {
+namespace http {
+
+void SocketProxy::readClient()
+{
+ ptrClient_->asyncReadSome(
+ boost::asio::buffer(clientBuffer_),
+ boost::bind(
+ &SocketProxy::handleClientRead,
+ SocketProxy::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void SocketProxy::readServer()
+{
+ ptrServer_->asyncReadSome(
+ boost::asio::buffer(serverBuffer_),
+ boost::bind(
+ &SocketProxy::handleServerRead,
+ SocketProxy::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void SocketProxy::handleClientRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+{
+ if (!e)
+ {
+ std::vector<boost::asio::const_buffer> buffers;
+ buffers.push_back(boost::asio::buffer(clientBuffer_.data(),
+ bytesTransferred));
+ ptrServer_->asyncWrite(buffers,
+ boost::bind(
+ &SocketProxy::handleServerWrite,
+ SocketProxy::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+ else
+ {
+ handleError(e, ERROR_LOCATION);
+ }
+}
+
+void SocketProxy::handleServerRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+{
+ if (!e)
+ {
+ std::vector<boost::asio::const_buffer> buffers;
+ buffers.push_back(boost::asio::buffer(serverBuffer_.data(),
+ bytesTransferred));
+ ptrClient_->asyncWrite(buffers,
+ boost::bind(
+ &SocketProxy::handleClientWrite,
+ SocketProxy::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+ else
+ {
+ handleError(e, ERROR_LOCATION);
+ }
+}
+
+void SocketProxy::handleClientWrite(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+{
+ if (!e)
+ {
+ readServer();
+ }
+ else
+ {
+ handleError(e, ERROR_LOCATION);
+ }
+}
+
+void SocketProxy::handleServerWrite(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+{
+ if (!e)
+ {
+ readClient();
+ }
+ else
+ {
+ handleError(e, ERROR_LOCATION);
+ }
+}
+
+void SocketProxy::handleError(const boost::system::error_code& e,
+ const core::ErrorLocation& location)
+{
+ // log the error if it wasn't connection terminated
+ Error error(e, location);
+ if (!http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+
+ close();
+}
+
+void SocketProxy::close()
+{
+ ptrClient_->close();
+ ptrServer_->close();
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/URL.cpp b/src/cpp/core/http/URL.cpp
new file mode 100644
index 0000000..8ff0e92
--- /dev/null
+++ b/src/cpp/core/http/URL.cpp
@@ -0,0 +1,308 @@
+/*
+ * URL.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/URL.hpp>
+
+#include <iostream>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
+
+namespace core {
+namespace http {
+
+URL::URL(const std::string& absoluteURL)
+{
+ std::string protocol, host, path;
+ boost::regex re("(http|https)://([^/#?]+)(.*)", boost::regex::icase);
+ boost::cmatch matches ;
+ if (boost::regex_match(absoluteURL.c_str(), matches, re))
+ {
+ protocol = matches[1];
+ host = matches[2];
+ path = matches[3];
+ }
+
+ // thwart ref-counting
+ assign(absoluteURL, protocol, host, path);
+}
+
+void URL::split(std::string* pBaseURL, std::string* pQueryParams) const
+{
+ if (empty())
+ return;
+
+ // look for '?'
+ std::string::size_type pos = absoluteURL_.find('?');
+ if (pos != std::string::npos)
+ {
+ *pBaseURL = absoluteURL_.substr(0, pos);
+ if ((pos+1) < absoluteURL_.length())
+ *pQueryParams = absoluteURL_.substr(pos+1);
+ }
+ else
+ {
+ *pBaseURL = absoluteURL_;
+ }
+}
+
+std::ostream& operator << (std::ostream& stream, const URL& url)
+{
+ stream << url.absoluteURL();
+ return stream;
+}
+
+namespace {
+
+class Path
+{
+public:
+ Path(std::string path)
+ {
+ if (path.empty())
+ {
+ rooted_ = false;
+ return;
+ }
+
+ rooted_ = path.at(0) == '/';
+ if (rooted_)
+ path = path.substr(1);
+
+ size_t lastSlash = path.find_last_of('/');
+ if (lastSlash == std::string::npos)
+ {
+ file_ = path;
+ path.clear();
+ }
+ else if (lastSlash < path.length() - 1)
+ {
+ file_ = path.substr(lastSlash + 1);
+ path.erase(lastSlash);
+ }
+
+ boost::algorithm::split(dirs_,
+ path,
+ boost::is_any_of("/"),
+ boost::algorithm::token_compress_off);
+ }
+
+ std::string value() const
+ {
+ std::string output;
+ if (rooted_)
+ output.push_back('/');
+ for (size_t i = 0; i < dirs_.size(); i++)
+ {
+ output.append(dirs_.at(i));
+ output.push_back('/');
+ }
+ output.append(file_);
+ return output;
+ }
+
+ void cleanup()
+ {
+ std::vector<std::string> result;
+ for (size_t i = 0; i < dirs_.size(); i++)
+ {
+ if (dirs_.at(i).empty() || dirs_.at(i) == ".")
+ continue;
+
+ if (dirs_.at(i) == "..")
+ {
+ if (!result.empty() && result.back() != "..")
+ {
+ result.pop_back();
+ continue;
+ }
+ }
+
+ result.push_back(dirs_.at(i));
+ }
+ dirs_ = result;
+ }
+
+ bool rooted() const { return rooted_; }
+ void setRooted(bool rooted) { rooted_ = rooted; }
+
+ std::string file() const { return file_; }
+ void setFile(const std::string& file) { file_ = file; }
+
+ std::vector<std::string> dirs() { return dirs_; }
+ void setDirs(std::vector<std::string> dirs) { dirs_ = dirs; }
+
+private:
+ bool rooted_;
+ std::vector<std::string> dirs_;
+ std::string file_;
+};
+
+void splitParts(const std::string pathInfo, std::string* path, std::string* extraInfo)
+{
+ size_t end = pathInfo.find_first_of('?');
+ if (end == std::string::npos)
+ end = pathInfo.find_first_of('#');
+
+ if (end == std::string::npos)
+ {
+ if (path)
+ *path = pathInfo;
+ if (extraInfo)
+ *extraInfo = std::string();
+ }
+ else
+ {
+ if (path)
+ *path = pathInfo.substr(0, end);
+ if (extraInfo)
+ *extraInfo = pathInfo.substr(end);
+ }
+}
+
+// If a file path, strips the file. If a dir path (i.e. has trailing slash)
+// then returns the path unchanged.
+std::string getDir(std::string fileOrDirPath)
+{
+ splitParts(fileOrDirPath, &fileOrDirPath, NULL);
+ size_t lastSlash = fileOrDirPath.find_last_of('/');
+ if (lastSlash != std::string::npos)
+ fileOrDirPath = fileOrDirPath.substr(0, lastSlash + 1);
+ return fileOrDirPath;
+}
+
+std::string cleanupPath(std::string path)
+{
+ std::string suffix;
+ splitParts(path, &path, &suffix);
+
+ Path richPath(path);
+ richPath.cleanup();
+ return richPath.value() + suffix;
+}
+
+} // anonymous namespace
+
+std::string URL::complete(std::string absoluteUri, std::string targetUri)
+{
+ URL uri(targetUri);
+ if (uri.isValid())
+ return targetUri;
+
+ std::string prefix;
+ std::string path;
+ URL absUrl(absoluteUri);
+ if (absUrl.isValid())
+ {
+ prefix = absUrl.protocol() + "://" + absUrl.host();
+ path = getDir(absUrl.path());
+ // Deal with URLs with no path at all (e.g. "http://www.example.com")
+ if (path.size() == 0)
+ path = "/";
+ }
+ else
+ {
+ path = getDir(absoluteUri);
+ }
+
+ if (!targetUri.empty() && targetUri.at(0) == '/')
+ path = targetUri;
+ else
+ path = path + targetUri;
+
+ return prefix + cleanupPath(path);
+}
+
+std::string URL::uncomplete(std::string baseUri, std::string targetUri)
+{
+ splitParts(baseUri, &baseUri, NULL);
+ std::string targetPath, targetExtra;
+ splitParts(targetUri, &targetPath, &targetExtra);
+
+ Path from(baseUri);
+ Path to(targetPath);
+
+ if (!from.rooted())
+ return targetPath;
+ if (!to.rooted())
+ to = Path(complete(baseUri, targetPath));
+
+ from.cleanup();
+ to.cleanup();
+
+ std::vector<std::string> fromDirs = from.dirs();
+ std::vector<std::string> toDirs = to.dirs();
+
+ while (fromDirs.size() > 0 && toDirs.size() > 0)
+ {
+ if (fromDirs.front() == toDirs.front())
+ {
+ fromDirs.erase(fromDirs.begin());
+ toDirs.erase(toDirs.begin());
+ }
+ else
+ break;
+ }
+
+ for (size_t i = 0; i < fromDirs.size(); i++)
+ {
+ toDirs.insert(toDirs.begin(), "..");
+ }
+
+ to.setDirs(toDirs);
+ to.setRooted(false);
+
+ return to.value() + targetExtra;
+}
+
+void URL::test()
+{
+ BOOST_ASSERT(cleanupPath("") == "");
+ BOOST_ASSERT(cleanupPath("/") == "/");
+ BOOST_ASSERT(cleanupPath("./") == "");
+ BOOST_ASSERT(cleanupPath("/./") == "/");
+ BOOST_ASSERT(cleanupPath("/.") == "/.");
+ BOOST_ASSERT(cleanupPath("/foo/../") == "/");
+ BOOST_ASSERT(cleanupPath("foo/../") == "");
+ BOOST_ASSERT(cleanupPath("/foo/bar/../../") == "/");
+ BOOST_ASSERT(cleanupPath("foo/bar/../../") == "");
+ BOOST_ASSERT(cleanupPath("/foo/bar/../../") == "/");
+ BOOST_ASSERT(cleanupPath("/foo/bar/../..") == "/foo/..");
+ BOOST_ASSERT(cleanupPath("/foo/?/../") == "/foo/?/../");
+ BOOST_ASSERT(cleanupPath("/foo/#/../") == "/foo/#/../");
+ BOOST_ASSERT(cleanupPath("/foo/?/../#/../") == "/foo/?/../#/../");
+
+ BOOST_ASSERT(complete("http://www.example.com", "foo") == "http://www.example.com/foo");
+ BOOST_ASSERT(complete("http://www.example.com/foo", "bar") == "http://www.example.com/bar");
+ BOOST_ASSERT(complete("http://www.example.com/foo/", "bar") == "http://www.example.com/foo/bar");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/", "/bar") == "http://www.example.com:80/bar");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/bar", "baz/qux") == "http://www.example.com:80/foo/baz/qux");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/bar", "../baz/qux") == "http://www.example.com:80/baz/qux");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/bar/", "../baz/qux") == "http://www.example.com:80/foo/baz/qux");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/bar/", "baz/../qux") == "http://www.example.com:80/foo/bar/qux");
+ BOOST_ASSERT(complete("http://www.example.com:80/foo/bar", "http://baz") == "http://baz");
+
+ BOOST_ASSERT(complete("foo/bar/", "baz/qux") == "foo/bar/baz/qux");
+ BOOST_ASSERT(complete("foo/bar/", "../baz/qux") == "foo/baz/qux");
+ BOOST_ASSERT(complete("../foo/bar/", "../baz/qux") == "../foo/baz/qux");
+ BOOST_ASSERT(complete("../../foo/bar/", "../baz/qux") == "../../foo/baz/qux");
+
+ BOOST_ASSERT(uncomplete("/foo/bar/baz", "/foo/qux/quux") == "../qux/quux");
+ BOOST_ASSERT(uncomplete("/foo/bar/baz/", "/foo/qux/quux") == "../../qux/quux");
+ BOOST_ASSERT(uncomplete("/bar/baz", "/qux/quux") == "../qux/quux");
+}
+
+} // namespace http
+} // namespace core
diff --git a/src/cpp/core/http/UriHandler.cpp b/src/cpp/core/http/UriHandler.cpp
new file mode 100644
index 0000000..cf9c1a8
--- /dev/null
+++ b/src/cpp/core/http/UriHandler.cpp
@@ -0,0 +1,98 @@
+/*
+ * UriHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/http/UriHandler.hpp>
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/http/Request.hpp>
+
+namespace core {
+namespace http {
+
+namespace {
+
+void runSynchronousHandler(const UriHandlerFunction& function,
+ const Request& request,
+ const UriHandlerFunctionContinuation& cont)
+{
+ http::Response response;
+ function(request, &response);
+ cont(&response);
+}
+
+UriAsyncHandlerFunction adaptToAsync(const UriHandlerFunction& function)
+{
+ return boost::bind(runSynchronousHandler, function, _1, _2);
+}
+
+} // anonymous namespace
+
+UriHandler::UriHandler(const std::string& prefix,
+ const UriAsyncHandlerFunction& function)
+ : prefix_(prefix), function_(function)
+{
+}
+
+UriHandler::UriHandler(const std::string& prefix,
+ const UriHandlerFunction& function)
+ : prefix_(prefix), function_(adaptToAsync(function))
+{
+}
+
+bool UriHandler::matches(const std::string& uri) const
+{
+ return boost::algorithm::starts_with(uri, prefix_);
+}
+
+UriAsyncHandlerFunction UriHandler::function() const
+{
+ return function_;
+}
+
+// implement UriHandlerFunction concept
+void UriHandler::operator()(const Request& request,
+ const UriHandlerFunctionContinuation& cont) const
+{
+ function_(request, cont);
+}
+
+void UriHandlers::add(const UriHandler& handler)
+{
+ uriHandlers_.push_back(handler);
+}
+
+UriAsyncHandlerFunction UriHandlers::handlerFor(const std::string& uri) const
+{
+ std::vector<UriHandler>::const_iterator handler = std::find_if(
+ uriHandlers_.begin(),
+ uriHandlers_.end(),
+ boost::bind(&UriHandler::matches, _1, uri));
+ if ( handler != uriHandlers_.end() )
+ {
+ return handler->function();
+ }
+ else
+ {
+ return UriAsyncHandlerFunction();
+ }
+}
+
+} // namespace http
+} // namespace core
+
diff --git a/src/cpp/core/http/Util.cpp b/src/cpp/core/http/Util.cpp
new file mode 100644
index 0000000..a2d8343
--- /dev/null
+++ b/src/cpp/core/http/Util.cpp
@@ -0,0 +1,360 @@
+/*
+ * Util.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/http/Util.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/regex.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+
+#include <core/http/Header.hpp>
+#include <core/http/Request.hpp>
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+namespace core {
+namespace http {
+
+namespace util {
+
+
+Fields::const_iterator findField(const Fields& fields, const std::string& name)
+{
+ return std::find_if(fields.begin(), fields.end(), FieldPredicate(name));
+}
+
+std::string fieldValue(const Fields& fields, const std::string& name)
+{
+ Fields::const_iterator pos = findField(fields, name);
+ if (pos != fields.end())
+ return pos->second;
+ else
+ return std::string();
+}
+
+void parseFields(const std::string& fields,
+ const char* fieldDelim,
+ const char* valueDelim,
+ Fields* pFields,
+ FieldDecodeType fieldDecode)
+{
+ // enable straightforward references to tokenizer class & helpers
+ using namespace boost ;
+
+ // delimiters
+ char_separator<char> fieldSeparator(fieldDelim);
+ char_separator<char> valueSeparator(valueDelim) ;
+
+ // iterate over the fields
+ tokenizer<char_separator<char> > fieldTokens(fields, fieldSeparator) ;
+ for (tokenizer<char_separator<char> >::iterator
+ fieldIter = fieldTokens.begin();
+ fieldIter != fieldTokens.end();
+ ++fieldIter)
+ {
+ // split into name and value
+ std::string name ;
+ std::string value ;
+ tokenizer<char_separator<char> > valTokens(*fieldIter, valueSeparator);
+ tokenizer<char_separator<char> >::iterator valIter = valTokens.begin();
+
+ if ( valIter != valTokens.end() )
+ name = *valIter++ ;
+ if ( valIter != valTokens.end() )
+ value = *valIter ;
+
+ if ( fieldDecode != FieldDecodeNone )
+ {
+ bool queryString = (fieldDecode == FieldDecodeQueryString);
+ name = util::urlDecode(name, queryString);
+ value = util::urlDecode(value, queryString) ;
+ }
+
+ if ( !name.empty() )
+ pFields->push_back(std::make_pair(name,value));
+ }
+}
+
+void buildQueryString(const Fields& fields, std::string* pQueryString)
+{
+ pQueryString->clear();
+
+ for (Fields::const_iterator it = fields.begin();
+ it != fields.end();
+ ++it)
+ {
+ std::string encodedKey = urlEncode(it->first, true);
+ pQueryString->append(encodedKey);
+ pQueryString->append("=");
+ std::string encodedValue = urlEncode(it->second, true);
+ pQueryString->append(encodedValue);
+ pQueryString->append("&");
+ }
+
+ // remove trailing &
+ if (!pQueryString->empty())
+ pQueryString->erase(pQueryString->length()-1);
+}
+
+void parseForm(const std::string& body, Fields* pFields)
+{
+ return parseFields(body, "&", "=", pFields, FieldDecodeForm);
+}
+
+
+void parseQueryString(const std::string& queryString, Fields* pFields)
+{
+ return parseFields(queryString, "&", "=", pFields, FieldDecodeQueryString);
+}
+
+void parseMultipartForm(const std::string& contentType,
+ const std::string& body,
+ Fields* pFields,
+ Files* pFiles)
+{
+ // get the boundary token
+ std::string boundaryPrefix("boundary=");
+ std::string boundary;
+ size_t prefixLoc = contentType.find(boundaryPrefix);
+ if (prefixLoc != std::string::npos)
+ {
+ boundary = contentType.substr(prefixLoc+boundaryPrefix.size(),
+ std::string::npos);
+ boost::algorithm::trim(boundary);
+ }
+
+ // extract the fields
+ size_t beginBoundaryLoc = body.find(boundary);
+ size_t endBoundaryLoc = body.find(boundary,
+ beginBoundaryLoc+boundary.size());
+ while (endBoundaryLoc != std::string::npos)
+ {
+ // extract the part into a string stream
+ size_t beginPart = beginBoundaryLoc + boundary.size();
+ size_t partLength = (endBoundaryLoc - 1) - beginPart - 1;
+ std::istringstream partStream(body.substr(beginPart, partLength));
+ partStream.unsetf(std::ios::skipws);
+
+ // read the headers
+ Headers headers ;
+ http::parseHeaders(partStream, &headers);
+
+ // check for content-disposition
+ std::string cDisp = http::headerValue(headers,"Content-Disposition");
+ if (!cDisp.empty())
+ {
+ // parse values out of content disposition
+ std::string nameRegex("form-data; name=\"(.*)\"");
+ boost::smatch nameMatch;
+ if (regex_match(cDisp, nameMatch, boost::regex(nameRegex)))
+ {
+ // read the rest of the stream
+ std::ostringstream valueStream ;
+ std::copy(std::istream_iterator<char>(partStream),
+ std::istream_iterator<char>(),
+ std::ostream_iterator<char>(valueStream));
+
+ // check for filename
+ std::string filenameRegex(nameRegex + "; filename=\"(.*)\"");
+ boost::smatch fileMatch;
+ if (regex_match(cDisp, fileMatch, boost::regex(filenameRegex)))
+ {
+ std::string name(fileMatch[1]);
+
+ File uploadedFile;
+ uploadedFile.name = fileMatch[2];
+ uploadedFile.contentType = http::headerValue(headers,
+ "Content-Type");
+ if (uploadedFile.contentType.empty())
+ uploadedFile.contentType = "application/octet-stream";
+
+ uploadedFile.contents = valueStream.str();
+ pFiles->insert(std::make_pair(name, uploadedFile));
+ }
+ // else process regular form field
+ else
+ {
+ std::string name(nameMatch[1]);
+ std::string value = valueStream.str();
+ boost::algorithm::trim(value);
+ pFields->push_back(std::make_pair(name, value));
+ }
+ }
+
+ }
+
+ // next boundary
+ beginBoundaryLoc = endBoundaryLoc;
+ endBoundaryLoc = body.find(boundary, beginBoundaryLoc+boundary.size());
+ }
+}
+
+
+std::string urlEncode(const std::string& in, bool queryStringSpaces)
+{
+ std::string encodedURL ;
+
+ int inputLength = in.length();
+ for (int i=0; i<inputLength; i++)
+ {
+ char ch = in[i];
+
+ if ( ('0' <= ch && ch <= '9') ||
+ ('a' <= ch && ch <= 'z') ||
+ ('A' <= ch && ch <= 'Z') ||
+ (ch=='~' || ch=='!' || ch=='*' || ch=='(' || ch==')' || ch=='\'' ||
+ ch=='.' || ch=='-' || ch=='_') )
+ {
+ encodedURL += ch ;
+ }
+ else if ((ch == ' ') && queryStringSpaces)
+ {
+ encodedURL += '+';
+ }
+ else
+ {
+ std::ostringstream ostr ;
+ ostr << "%" ;
+ ostr << std::setw(2) << std::setfill('0') << std::hex << std::uppercase
+ << (int)(uint8_t)ch ;
+ std::string charAsHex = ostr.str();
+ encodedURL += charAsHex;
+ }
+ }
+
+ return encodedURL;
+}
+
+std::string urlDecode(const std::string& in, bool fromQueryString)
+{
+ std::string out;
+ out.reserve(in.size());
+ for (std::size_t i = 0; i < in.size(); ++i)
+ {
+ if (in[i] == '%')
+ {
+ if (i + 3 <= in.size())
+ {
+ int value;
+ std::istringstream is(in.substr(i + 1, 2));
+ if (is >> std::hex >> value)
+ {
+ out += static_cast<char>(value);
+ i += 2;
+ }
+ else
+ {
+ out = in; // no decode performed
+ return out;
+ }
+ }
+ else
+ {
+ out = in; // no decode performned
+ return out;
+ }
+ }
+ else if (fromQueryString && (in[i] == '+'))
+ {
+ out += ' ';
+ }
+ else
+ {
+ out += in[i];
+ }
+ }
+ return out;
+}
+
+namespace {
+
+const char * const kHttpDateFormat = "%a, %d %b %Y %H:%M:%S GMT";
+const char * const kAtomDateFormat = "%Y-%m-%dT%H:%M:%S%F%Q";
+
+boost::posix_time::ptime parseDate(const std::string& date, const char* format)
+{
+ using namespace boost::posix_time;
+
+ // facet for date (construct w/ a_ref == 1 so we manage memory)
+ time_input_facet dateFacet(1);
+ dateFacet.format(format);
+
+ // parse from string
+ std::stringstream dateStream;
+ dateStream.str(date);
+ dateStream.imbue(std::locale(dateStream.getloc(), &dateFacet));
+ ptime posixDate(not_a_date_time) ;
+ dateStream >> posixDate ;
+ return posixDate;
+}
+
+}
+
+boost::posix_time::ptime parseAtomDate(const std::string& date)
+{
+ return parseDate(date, kAtomDateFormat);
+}
+
+
+boost::posix_time::ptime parseHttpDate(const std::string& date)
+{
+ return parseDate(date, kHttpDateFormat);
+}
+
+std::string httpDate(const boost::posix_time::ptime& datetime)
+{
+ using namespace boost::posix_time;
+
+ // facet for http date (construct w/ a_ref == 1 so we manage memory)
+ time_facet httpDateFacet(1);
+ httpDateFacet.format("%a, %d %b %Y %H:%M:%S GMT");
+
+ // output and return the date
+ std::ostringstream dateStream;
+ dateStream.imbue(std::locale(dateStream.getloc(), &httpDateFacet));
+ dateStream << datetime;
+ return dateStream.str();
+}
+
+
+std::string pathAfterPrefix(const Request& request,
+ const std::string& pathPrefix)
+{
+ // get the raw uri & strip its location prefix
+ std::string uri = request.uri();
+ if (!pathPrefix.empty() && !uri.compare(0, pathPrefix.length(), pathPrefix))
+ uri = uri.substr(pathPrefix.length());
+
+ // strip query string
+ size_t pos = uri.find("?");
+ if (pos != std::string::npos)
+ uri.erase(pos);
+
+ // uri has now been reduced to path. url decode it (we noted that R
+ // was url encoding dashes in e.g. help for memory-limits)
+ return http::util::urlDecode(uri);
+}
+
+} // namespace util
+
+} // namespace http
+} // namespace core
+
diff --git a/src/cpp/core/include/core/Algorithm.hpp b/src/cpp/core/include/core/Algorithm.hpp
new file mode 100644
index 0000000..f3ebbd8
--- /dev/null
+++ b/src/cpp/core/include/core/Algorithm.hpp
@@ -0,0 +1,62 @@
+/*
+ * Algorithm.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_ALGORITHM_HPP
+#define CORE_ALGORITHM_HPP
+
+namespace core {
+namespace algorithm {
+
+template<typename InputIterator, typename OutputIterator, typename Predicate>
+OutputIterator copy_if(InputIterator begin,
+ InputIterator end,
+ OutputIterator destBegin,
+ Predicate p)
+{
+ while (begin != end)
+ {
+ if (p(*begin))
+ *destBegin++ = *begin;
+ ++begin;
+ }
+ return destBegin;
+}
+
+template<typename InputIterator,
+ typename OutputIterator,
+ typename Predicate,
+ typename UnaryOperator>
+OutputIterator copy_transformed_if(InputIterator begin,
+ InputIterator end,
+ OutputIterator destBegin,
+ Predicate p,
+ UnaryOperator op)
+{
+ while (begin != end)
+ {
+ if (p(*begin))
+ *destBegin++ = op(*begin);
+ ++begin;
+ }
+ return destBegin;
+}
+
+
+} // namespace algorithm
+} // namespace core
+
+
+#endif // CORE_ALGORITHM_HPP
diff --git a/src/cpp/core/include/core/Base64.hpp b/src/cpp/core/include/core/Base64.hpp
new file mode 100644
index 0000000..bba6176
--- /dev/null
+++ b/src/cpp/core/include/core/Base64.hpp
@@ -0,0 +1,37 @@
+/*
+ * Base64.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_BASE64_HPP
+#define CORE_SYSTEM_BASE64_HPP
+
+#include <string>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace base64 {
+
+
+Error encode(const std::string& input, std::string* pOutput);
+Error encode(const FilePath& inputFile, std::string* pOutput);
+
+
+} // namespace base64
+} // namespace core
+
+#endif // CORE_SYSTEM_BASE64_HPP
+
diff --git a/src/cpp/core/include/core/BoostErrors.hpp b/src/cpp/core/include/core/BoostErrors.hpp
new file mode 100644
index 0000000..2904179
--- /dev/null
+++ b/src/cpp/core/include/core/BoostErrors.hpp
@@ -0,0 +1,47 @@
+/*
+ * BoostErrors.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_BOOST_ERRORS_HPP
+#define CORE_BOOST_ERRORS_HPP
+
+#include <boost/system/error_code.hpp>
+
+// bridges for boost libraries without support for system::error_code
+namespace boost {
+
+namespace interprocess {
+
+const boost::system::error_category& interprocess_category() ;
+
+class interprocess_exception ;
+boost::system::error_code ec_from_exception(const interprocess_exception& e) ;
+
+} // namespace interprocess
+
+class thread_resource_error ;
+
+namespace thread_error {
+
+const boost::system::error_category& thread_category() ;
+
+boost::system::error_code ec_from_exception(
+ const boost::thread_resource_error& e) ;
+
+} // namespace thread_error
+
+} // namespace boost
+
+#endif // CORE_BOOST_ERRORS_HPP
+
diff --git a/src/cpp/core/include/core/BoostLamda.hpp b/src/cpp/core/include/core/BoostLamda.hpp
new file mode 100644
index 0000000..9f541e6
--- /dev/null
+++ b/src/cpp/core/include/core/BoostLamda.hpp
@@ -0,0 +1,31 @@
+/*
+ * BoostLamda.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_BOOST_LAMBDA_HPP
+#define CORE_BOOST_LAMBDA_HPP
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-variable"
+#endif
+
+#include <boost/lambda/lambda.hpp>
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif // CORE_BOOST_LAMBDA_HPP
+
diff --git a/src/cpp/core/include/core/BoostThread.hpp b/src/cpp/core/include/core/BoostThread.hpp
new file mode 100644
index 0000000..a9c753b
--- /dev/null
+++ b/src/cpp/core/include/core/BoostThread.hpp
@@ -0,0 +1,30 @@
+/*
+ * BoostThread.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_BOOST_THREAD_HPP
+#define CORE_BOOST_THREAD_HPP
+
+#if defined(__GNUC__) && defined(_WIN32)
+ // Boost attempts to use declspec(dllimport) in some inline
+ // functions within boost::thread. This causes compiler warnings
+ // on MinGW which we want to avoid.
+ #undef BOOST_HAS_DECLSPEC
+#endif
+
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
+
+#endif // CORE_BOOST_THREAD_HPP
+
diff --git a/src/cpp/core/include/core/ConfigUtils.hpp b/src/cpp/core/include/core/ConfigUtils.hpp
new file mode 100644
index 0000000..a37ebf0
--- /dev/null
+++ b/src/cpp/core/include/core/ConfigUtils.hpp
@@ -0,0 +1,39 @@
+/*
+ * ConfigUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_CONFIG_UTILS_HPP
+#define CORE_CONFIG_UTILS_HPP
+
+#include <string>
+#include <map>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace config_utils {
+
+typedef std::map<std::string,std::string> Variables;
+
+void extractVariables(const std::string& vars, Variables* pVariables);
+Error extractVariables(const FilePath& file, Variables* pVariables);
+
+} // namespace config_utils
+} // namespace core
+
+
+#endif // CORE_CONFIG_UTILS_HPP
+
diff --git a/src/cpp/core/include/core/DateTime.hpp b/src/cpp/core/include/core/DateTime.hpp
new file mode 100644
index 0000000..c4ab8d1
--- /dev/null
+++ b/src/cpp/core/include/core/DateTime.hpp
@@ -0,0 +1,45 @@
+/*
+ * DateTime.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_DATE_TIME_HPP
+#define CORE_DATE_TIME_HPP
+
+#include <ctime>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+namespace date_time {
+
+double secondsSinceEpoch();
+double secondsSinceEpoch(const boost::posix_time::ptime& time);
+double secondsSinceEpoch(std::time_t time);
+
+double millisecondsSinceEpoch();
+double millisecondsSinceEpoch(const boost::posix_time::ptime& time);
+double millisecondsSinceEpoch(std::time_t time);
+
+boost::posix_time::ptime timeFromSecondsSinceEpoch(double sec);
+boost::posix_time::ptime timeFromMillisecondsSinceEpoch(int64_t ms);
+
+std::string format(const boost::posix_time::ptime& datetime,
+ const std::string& format);
+
+} // namespace date_time
+} // namespace core
+
+
+#endif // CORE_DATE_TIME_HPP
+
diff --git a/src/cpp/core/include/core/Error.hpp b/src/cpp/core/include/core/Error.hpp
new file mode 100644
index 0000000..99a5582
--- /dev/null
+++ b/src/cpp/core/include/core/Error.hpp
@@ -0,0 +1,188 @@
+/*
+ * Error.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_ERROR_HPP
+#define CORE_ERROR_HPP
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <boost/system/error_code.hpp>
+
+#include <boost/current_function.hpp>
+
+namespace core {
+
+class FilePath;
+class ErrorLocation ;
+
+class Error ;
+class Success ;
+
+class Error_lock
+{
+ friend class Error ;
+ friend class Success ;
+private:
+ Error_lock() {}
+ Error_lock(const Error_lock&) {}
+};
+
+// typedef for error properties
+typedef std::vector<std::pair<std::string,std::string> > ErrorProperties ;
+
+// Concrete class for returning error codes from methods.
+// Since Error is copied during returns, it should not be derived
+// from to create "convenience" subclasses for various error domains.
+// Rater, a global function like systemError below should be created
+// on a per-domain basis as a helper.
+// derive from Error_lock to prevent further derivation (see comment above)
+class Error : public virtual Error_lock
+{
+public:
+ Error() ;
+
+ Error(const boost::system::error_code& ec,
+ const ErrorLocation& location);
+
+ Error(const boost::system::error_code& ec,
+ const Error& cause,
+ const ErrorLocation& location);
+
+ // non-virtual destructor because no subclasses are permitted and we
+ // want to keep Error as lightweight as possible
+ ~Error() ;
+
+ // COPYING: via shared_ptr, mutating functions must call copyOnWrite
+ // prior to executing
+
+ void addProperty(const std::string& name, const std::string& value);
+ void addProperty(const std::string& name, const FilePath& value);
+ void addProperty(const std::string& name, int value);
+
+ const boost::system::error_code& code() const;
+
+ std::string summary() const;
+
+ const Error& cause() const ;
+
+ const ErrorLocation& location() const ;
+
+ const ErrorProperties& properties() const;
+ std::string getProperty(const std::string& name) const;
+
+ // below based on boost::system::error_code
+ typedef void (*unspecified_bool_type)();
+ static void unspecified_bool_true() {}
+ operator unspecified_bool_type() const
+ {
+ return !isError() ? 0 : unspecified_bool_true;
+ }
+ bool operator!() const
+ {
+ return !isError();
+ }
+
+private:
+ bool isError() const ;
+ void copyOnWrite() ;
+
+private:
+ struct Impl ;
+ mutable boost::shared_ptr<Impl> pImpl_ ;
+ Impl& impl() const;
+};
+
+//
+// No error subclass created for syntactic conveneince:
+// return Success();
+//
+class Success : public Error
+{
+public:
+ Success() : Error() {}
+};
+
+
+Error systemError(int value, const ErrorLocation& location) ;
+Error systemError(int value,
+ const std::string& description,
+ const ErrorLocation& location) ;
+
+Error fileExistsError(const ErrorLocation& location);
+Error fileNotFoundError(const ErrorLocation& location);
+Error fileNotFoundError(const std::string& path,
+ const ErrorLocation& location);
+Error fileNotFoundError(const FilePath& filePath,
+ const ErrorLocation& location);
+
+bool isPathNotFoundError(const Error& error);
+Error pathNotFoundError(const ErrorLocation& location);
+Error pathNotFoundError(const std::string& path,
+ const ErrorLocation& location);
+
+
+class ErrorLocation
+{
+public:
+ ErrorLocation() ;
+ ErrorLocation(const char* function, const char* file, long line) ;
+ virtual ~ErrorLocation() ;
+
+ // immutable - copying and assignment via shared_ptr
+
+ bool hasLocation() const ;
+
+ const std::string& function() const ;
+ const std::string& file() const ;
+ long line() const ;
+
+ std::string asString() const;
+
+ bool operator==(const ErrorLocation& location) const;
+ bool operator!=(const ErrorLocation& location) const
+ {
+ return !(*this == location);
+ }
+
+private:
+ struct Impl ;
+ boost::shared_ptr<Impl> pImpl_ ;
+};
+
+std::ostream& operator<<(std::ostream& os, const ErrorLocation& location);
+
+} // namespace core
+
+#define ERROR_LOCATION core::ErrorLocation( \
+ BOOST_CURRENT_FUNCTION,__FILE__,__LINE__)
+
+#define CATCH_UNEXPECTED_EXCEPTION \
+ catch(const std::exception& e) \
+ { \
+ LOG_ERROR_MESSAGE(std::string("Unexpected exception: ") + \
+ e.what()) ; \
+ } \
+ catch(...) \
+ { \
+ LOG_ERROR_MESSAGE("Unknown exception"); \
+ }
+
+
+#endif // CORE_ERROR_HPP
+
diff --git a/src/cpp/core/include/core/Exec.hpp b/src/cpp/core/include/core/Exec.hpp
new file mode 100644
index 0000000..68771b3
--- /dev/null
+++ b/src/cpp/core/include/core/Exec.hpp
@@ -0,0 +1,72 @@
+/*
+ * Exec.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_EXEC_HPP
+#define CORE_EXEC_HPP
+
+#include <vector>
+
+#include <boost/function.hpp>
+
+namespace core {
+
+class Error ;
+
+class ExecBlock
+{
+public:
+ typedef boost::function<core::Error()> Function ;
+
+public:
+ ExecBlock() {}
+
+ // COPYING: via compiler (copyable members)
+
+ // add to the block
+ ExecBlock& add(Function function) ;
+
+ // easy init style (based on idiom in boost::program_options)
+ class EasyInit;
+ EasyInit addFunctions() { return EasyInit(this); }
+
+ // execute the block
+ core::Error execute() const;
+
+ // allow an ExecBlock to act as a boost::function<core::Error()>
+ core::Error operator()() const;
+
+public:
+ // easy init helper class
+ class EasyInit
+ {
+ public:
+ EasyInit(ExecBlock* pExecBlock) : pExecBlock_(pExecBlock) {}
+ EasyInit& operator()(Function function)
+ {
+ pExecBlock_->add(function);
+ return *this;
+ }
+ private:
+ ExecBlock* pExecBlock_ ;
+ };
+private:
+ std::vector<Function> functions_ ;
+};
+
+
+} // namespace core
+
+#endif // CORE_EXEC_HPP
+
diff --git a/src/cpp/core/include/core/FileInfo.hpp b/src/cpp/core/include/core/FileInfo.hpp
new file mode 100644
index 0000000..25acdc9
--- /dev/null
+++ b/src/cpp/core/include/core/FileInfo.hpp
@@ -0,0 +1,149 @@
+/*
+ * FileInfo.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_FILE_INFO_HPP
+#define CORE_FILE_INFO_HPP
+
+#include <stdint.h>
+#include <ctime>
+#include <string.h>
+
+#include <string>
+#include <iosfwd>
+
+#include <core/FilePath.hpp>
+
+// TODO: satisfy outselves that it is safe to query for symlink status
+// in all cases and eliminate its "optional" semantics
+
+namespace core {
+
+class FileInfo
+{
+public:
+ FileInfo()
+ : absolutePath_(),
+ isDirectory_(false),
+ size_(0),
+ lastWriteTime_(0)
+ {
+ }
+
+
+ // NOTE: this constructor will NOT read symlink info from the passed
+ // FilePath object. this is because we want to restrict reading of
+ // symlink to status to funcitons that are expressly symlink aware
+ // (this is because the behavior of reading symlink status is not
+ // fully known and we don't want to make a change underneath our
+ // entire codebase which does this universally (note that we've been
+ // burned by boost filesystem having nasty beahvior for seemingly
+ // innocuous operations before!)
+ explicit FileInfo(const FilePath& filePath,
+ bool isSymlink = false) ;
+
+ FileInfo(const std::string& absolutePath,
+ bool isDirectory,
+ bool isSymlink = false);
+
+ FileInfo(const std::string& absolutePath,
+ bool isDirectory,
+ uintmax_t size,
+ std::time_t lastWriteTime,
+ bool isSymlink = false);
+
+ virtual ~FileInfo()
+ {
+ }
+
+ // COPYING: via compliler (copyable members)
+
+public:
+ bool empty() const { return absolutePath_.empty(); }
+
+ // NOTE: because symlink status is optional, it is NOT taken
+ // into account for equality tests
+ bool operator==(const FileInfo& other) const
+ {
+ return absolutePath_ == other.absolutePath_ &&
+ isDirectory_ == other.isDirectory_ &&
+ size_ == other.size_ &&
+ lastWriteTime_ == other.lastWriteTime_;
+ }
+
+ bool operator!=(const FileInfo& other) const
+ {
+ return !(*this == other);
+ }
+
+public:
+ std::string absolutePath() const { return absolutePath_.c_str(); }
+ bool isDirectory() const { return isDirectory_; }
+ uintmax_t size() const { return size_; }
+ std::time_t lastWriteTime() const { return lastWriteTime_; }
+ bool isSymlink() const { return isSymlink_; }
+
+private:
+ std::string absolutePath_;
+ bool isDirectory_;
+ uintmax_t size_;
+ std::time_t lastWriteTime_;
+ bool isSymlink_;
+};
+
+inline int fileInfoPathCompare(const FileInfo& a, const FileInfo& b)
+{
+ // use stcoll because that is what alphasort (comp function passed to
+ // scandir) uses for its sorting)
+ int result = ::strcoll(a.absolutePath().c_str(), b.absolutePath().c_str());
+
+ if (result != 0)
+ return result;
+
+ if (a.isDirectory() == b.isDirectory())
+ return 0;
+
+ return a.isDirectory() ? -1 : 1;
+}
+
+inline bool fileInfoPathLessThan(const FileInfo& a, const FileInfo& b)
+{
+ return fileInfoPathCompare(a, b) < 0;
+}
+
+
+inline bool fileInfoHasPath(const FileInfo& fileInfo, const std::string& path)
+{
+ return fileInfo.absolutePath() == path;
+}
+
+inline FilePath toFilePath(const FileInfo& fileInfo)
+{
+ return FilePath(fileInfo.absolutePath());
+}
+
+inline FileInfo toFileInfo(const FilePath& filePath)
+{
+ return FileInfo(filePath);
+}
+
+
+std::ostream& operator << (std::ostream& stream, const FileInfo& fileInfo) ;
+
+
+} // namespace core
+
+
+#endif // CORE_FILE_INFO_HPP
+
diff --git a/src/cpp/core/include/core/FileLock.hpp b/src/cpp/core/include/core/FileLock.hpp
new file mode 100644
index 0000000..b0705df
--- /dev/null
+++ b/src/cpp/core/include/core/FileLock.hpp
@@ -0,0 +1,51 @@
+/*
+ * FileLock.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_FILE_LOCK_HPP
+#define CORE_FILE_LOCK_HPP
+
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+class FileLock : boost::noncopyable
+{
+public:
+ static bool isLocked(const FilePath& lockFilePath);
+
+public:
+ FileLock();
+ virtual ~FileLock();
+
+ // COPYING: noncopyable
+
+ Error acquire(const FilePath& lockFilePath);
+ Error release();
+
+ FilePath lockFilePath() const;
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+} // namespace core
+
+
+#endif // CORE_FILE_LOCK_HPP
diff --git a/src/cpp/core/include/core/FileLogWriter.hpp b/src/cpp/core/include/core/FileLogWriter.hpp
new file mode 100644
index 0000000..ab45cd0
--- /dev/null
+++ b/src/cpp/core/include/core/FileLogWriter.hpp
@@ -0,0 +1,50 @@
+/*
+ * FileLogWriter.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef FILE_LOG_WRITER_HPP
+#define FILE_LOG_WRITER_HPP
+
+#include <core/FilePath.hpp>
+#include <core/LogWriter.hpp>
+
+namespace core {
+
+class FileLogWriter : public LogWriter
+{
+public:
+ FileLogWriter(const std::string& programIdentity,
+ int logLevel,
+ const FilePath& logDir);
+ virtual ~FileLogWriter();
+
+ virtual void log(core::system::LogLevel level,
+ const std::string& message);
+ virtual void log(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message);
+
+
+private:
+ bool rotateLogFile();
+
+ std::string programIdentity_;
+ int logLevel_;
+ FilePath logFile_;
+ FilePath rotatedLogFile_;
+};
+
+} // namespace core
+
+#endif // FILE_LOG_WRITER_HPP
diff --git a/src/cpp/core/include/core/FilePath.hpp b/src/cpp/core/include/core/FilePath.hpp
new file mode 100644
index 0000000..1bf4853
--- /dev/null
+++ b/src/cpp/core/include/core/FilePath.hpp
@@ -0,0 +1,236 @@
+/*
+ * FilePath.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_FILE_PATH_HPP
+#define CORE_FILE_PATH_HPP
+
+#include <stdint.h>
+#include <ctime>
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/function.hpp>
+
+#include <boost/utility.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+namespace core {
+
+class Error ;
+
+class FilePath
+{
+public:
+ typedef boost::function<void(int, const FilePath&)>
+ RecursiveIterationFunction;
+
+public:
+ // special accessor which detects when the current path no longer exists
+ // and switches to the specified alternate path if it doesn't
+ static FilePath safeCurrentPath(const FilePath& revertToPath) ;
+
+ static Error makeCurrent(const std::string& path);
+
+ static std::string createAliasedPath(const core::FilePath& path,
+ const FilePath& userHomePath);
+ static FilePath resolveAliasedPath(const std::string& aliasedPath,
+ const FilePath& userHomePath) ;
+
+ static bool exists(const std::string& path);
+
+ static bool isRootPath(const std::string& path);
+
+ static Error tempFilePath(FilePath* pFilePath);
+
+public:
+ FilePath() ;
+ explicit FilePath(const std::string& absolutePath) ;
+#if _WIN32
+ explicit FilePath(const std::wstring& absolutePath) ;
+#endif
+ virtual ~FilePath() ;
+ // COPYING: via shared_ptr (immutable)
+
+public:
+ // does this instance contain a path?
+ bool empty() const;
+
+ // does this file exist?
+ bool exists() const;
+
+ // is the file a symlink?
+ bool isSymlink() const;
+
+ // size of file in bytes
+ uintmax_t size() const;
+
+ // filename only
+ std::string filename() const ;
+
+ // filename without extension
+ std::string stem() const ;
+
+ // file extensions
+ std::string extension() const ;
+ std::string extensionLowerCase() const;
+ bool hasExtension(const std::string& ext) const;
+ bool hasExtensionLowerCase(const std::string& ext) const;
+
+ // mime types
+ std::string mimeContentType(
+ const std::string& defaultType = "text/plain") const;
+
+ bool hasTextMimeType() const;
+
+ // last write time
+ std::time_t lastWriteTime() const;
+
+ // full filesystem absolute path
+ std::string absolutePath() const ;
+
+ // full filesystem absolute path in native format
+ std::string absolutePathNative() const ;
+
+#if _WIN32
+ std::wstring absolutePathW() const;
+#endif
+
+ // path relative to parent directory. returns empty string if this path
+ // is not a child of the passed parent path
+ std::string relativePath(const FilePath& parentPath) const ;
+
+ // is this path within the scope of the passed scopePath (returns true
+ // if the two paths are equal)
+ bool isWithin(const FilePath& scopePath) const;
+
+ // delete file
+ Error remove() const ;
+ Error removeIfExists() const;
+
+ // move to path
+ Error move(const FilePath& targetPath) const ;
+
+ // copy to path
+ Error copy(const FilePath& targetPath) const;
+
+ // is this a hidden file?
+ bool isHidden() const ;
+
+ // is this a directory?
+ bool isDirectory() const ;
+
+ // create this directory if it doesn't already exist
+ Error ensureDirectory() const ;
+
+ // create directory at relative path
+ Error createDirectory(const std::string& path) const ;
+
+ // remove the directory (if it exists) and create a new one in its place
+ Error resetDirectory() const;
+
+ // complete a path (if input path is relative, returns path relative
+ // to this one; if input path is absolute returns that path)
+ FilePath complete(const std::string& path) const;
+
+ // get child path relative to this one.
+ FilePath childPath(const std::string& path) const ;
+
+ // get this path's parent
+ FilePath parent() const;
+
+ // list child paths
+ Error children(std::vector<FilePath>* pFilePaths) const ;
+
+ // recursively iterate over child paths
+ Error childrenRecursive(RecursiveIterationFunction iterationFunction) const;
+
+ // make this path the system current directory
+ Error makeCurrentPath(bool autoCreate = false) const ;
+
+ Error open_r(boost::shared_ptr<std::istream>* pStream) const;
+ Error open_w(boost::shared_ptr<std::ostream>* pStream, bool truncate = true) const;
+
+ // check for equivalence (point to the same file-system entity)
+ bool isEquivalentTo(const FilePath& filePath) const;
+
+ // compare two instances (equal if absolutePath == absolutePath)
+ bool operator== (const FilePath& filePath) const ;
+ bool operator!= (const FilePath& filePath) const ;
+
+ // natural order is based on absolute path
+ bool operator < (const FilePath& other) const ;
+
+private:
+ friend class RecursiveDirectoryIterator;
+
+private:
+ struct Impl ;
+ boost::shared_ptr<const Impl> pImpl_ ;
+};
+
+std::ostream& operator << (std::ostream& stream, const FilePath& fp) ;
+
+bool compareAbsolutePathNoCase(const FilePath& file1, const FilePath& file2);
+
+class RestoreCurrentPathScope : boost::noncopyable
+{
+public:
+ RestoreCurrentPathScope(const FilePath& restorePath)
+ : restorePath_(restorePath)
+ {
+ }
+
+ virtual ~RestoreCurrentPathScope()
+ {
+ try
+ {
+ Error error = restorePath_.makeCurrentPath();
+ if (error)
+ LOG_ERROR(error);
+ }
+ catch(...)
+ {
+ }
+ }
+private:
+ FilePath restorePath_ ;
+};
+
+class RecursiveDirectoryIterator : boost::noncopyable
+{
+public:
+ explicit RecursiveDirectoryIterator(const FilePath& filePath);
+ virtual ~RecursiveDirectoryIterator();
+
+ Error next(FilePath* pFilePath);
+ bool finished() const;
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+}
+
+#endif // CORE_FILE_PATH_HPP
+
+
+
diff --git a/src/cpp/core/include/core/FileSerializer.hpp b/src/cpp/core/include/core/FileSerializer.hpp
new file mode 100644
index 0000000..9c16f15
--- /dev/null
+++ b/src/cpp/core/include/core/FileSerializer.hpp
@@ -0,0 +1,348 @@
+/*
+ * FileSerializer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_FILE_SERIALIZER_HPP
+#define CORE_FILE_SERIALIZER_HPP
+
+#include <string>
+#include <map>
+#include <iterator>
+#include <istream>
+#include <sstream>
+
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
+
+#include <boost/function.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+namespace core {
+
+template <typename CollectionType>
+Error writeCollectionToFile(
+ const core::FilePath& filePath,
+ const CollectionType& collection,
+ boost::function<std::string(
+ const typename CollectionType::value_type&)>
+ stringifyFunction)
+{
+ using namespace boost::system::errc ;
+
+ // open the file stream
+ boost::shared_ptr<std::ostream> pOfs;
+ Error error = filePath.open_w(&pOfs, true);
+ if (error)
+ return error;
+
+ try
+ {
+ // write each line
+ for (typename CollectionType::const_iterator
+ it = collection.begin();
+ it != collection.end();
+ ++it)
+ {
+ *pOfs << stringifyFunction(*it) << std::endl ;
+
+ if (pOfs->fail())
+ return systemError(io_error, ERROR_LOCATION);
+ }
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success() ;
+}
+
+enum ReadCollectionAction
+{
+ ReadCollectionAddLine,
+ ReadCollectionIgnoreLine,
+ ReadCollectionTerminate
+};
+
+template <typename CollectionType>
+Error readCollectionFromFile(
+ const core::FilePath& filePath,
+ CollectionType* pCollection,
+ boost::function<ReadCollectionAction(const std::string& line,
+ typename CollectionType::value_type* pValue)>
+ parseFunction,
+ bool trimAndIgnoreBlankLines=true)
+{
+ using namespace boost::system::errc ;
+
+ // open the file stream
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = filePath.open_r(&pIfs);
+ if (error)
+ return error;
+
+ // create insert iterator
+ std::insert_iterator<CollectionType> insertIterator(*pCollection,
+ pCollection->begin());
+
+ try
+ {
+ // read each line
+ std::string nextLine ;
+ while (true)
+ {
+ // read the next line
+ std::getline(*pIfs, nextLine) ;
+ if (pIfs->eof())
+ break;
+ else if (pIfs->fail())
+ return systemError(io_error, ERROR_LOCATION);
+
+ // trim whitespace then ignore it if it is a blank line
+ if (trimAndIgnoreBlankLines)
+ {
+ boost::algorithm::trim(nextLine) ;
+ if (nextLine.empty())
+ continue ;
+ }
+
+ // parse it and add it to the collection
+ typename CollectionType::value_type value ;
+ ReadCollectionAction action = parseFunction(nextLine, &value);
+ if (action == ReadCollectionAddLine)
+ {
+ *insertIterator++ = value ;
+ }
+ else if (action == ReadCollectionIgnoreLine)
+ {
+ // do nothing
+ }
+ else if (action == ReadCollectionTerminate)
+ {
+ break; // exit read loop
+ }
+ }
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success() ;
+}
+
+template <typename ContentType>
+Error appendToFile(const core::FilePath& filePath,
+ const ContentType& content)
+{
+ using namespace boost::system::errc ;
+
+ // open the file stream
+ boost::shared_ptr<std::ostream> pOfs;
+ Error error = filePath.open_w(&pOfs, false);
+ if (error)
+ return error;
+
+ try
+ {
+ pOfs->seekp(0, std::ios_base::end);
+
+ // append the content
+ *pOfs << content ;
+ if (pOfs->fail())
+ return systemError(io_error, ERROR_LOCATION);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success() ;
+}
+
+template <typename T>
+Error appendStructToFile(const core::FilePath& filePath,
+ const T& data)
+{
+ using namespace boost::system::errc ;
+
+ // open the file stream
+ boost::shared_ptr<std::ostream> pOfs;
+ Error error = filePath.open_w(&pOfs, false);
+ if (error)
+ return error;
+
+ try
+ {
+ pOfs->seekp(0, std::ios_base::end);
+
+ // append the content
+ pOfs->write((const char*)&data, sizeof(T));
+ if (pOfs->fail())
+ return systemError(io_error, ERROR_LOCATION);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success() ;
+}
+
+template <typename T>
+Error readStructVectorFromFile(const core::FilePath& filePath,
+ std::vector<T>* pVector)
+{
+ using namespace boost::system::errc ;
+
+ // open the file stream
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = filePath.open_r(&pIfs);
+ if (error)
+ return error;
+
+ try
+ {
+ while(true)
+ {
+ T data;
+ pIfs->read((char*)&data, sizeof(T));
+ if (pIfs->eof())
+ break;
+ else if (pIfs->fail())
+ return systemError(io_error, ERROR_LOCATION);
+ else
+ pVector->push_back(data);
+ }
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success() ;
+}
+
+
+
+// convenince methods for simple string collections
+ReadCollectionAction parseString(const std::string& line, std::string* pStr);
+std::string stringifyString(const std::string& str);
+
+
+Error writeStringMapToFile(const core::FilePath& filePath,
+ const std::map<std::string,std::string>& map) ;
+
+Error readStringMapFromFile(const core::FilePath& filePath,
+ std::map<std::string,std::string>* pMap) ;
+
+Error writeStringVectorToFile(const core::FilePath& filePath,
+ const std::vector<std::string>& vector);
+
+Error readStringVectorFromFile(const core::FilePath& filePath,
+ std::vector<std::string>* pVector,
+ bool trimAndIgnoreBlankLines=true);
+
+// lineEnding is the type of line ending you want to end up on disk
+Error writeStringToFile(const core::FilePath& filePath,
+ const std::string& str,
+ string_utils::LineEnding lineEnding=string_utils::LineEndingPassthrough);
+
+// lineEnding is the type of line ending you want the resulting string to have
+Error readStringFromFile(const core::FilePath& filePath,
+ std::string* pStr,
+ string_utils::LineEnding lineEnding=string_utils::LineEndingPassthrough,
+ int startLine = 0,
+ int endLine = 0,
+ int startCharacter = 0,
+ int endCharacter = 0);
+
+// read a string from a file with a filter
+template <typename Filter>
+Error readStringFromFile(
+ const core::FilePath& filePath,
+ const Filter& filter,
+ std::string* pContents,
+ string_utils::LineEnding lineEnding=string_utils::LineEndingPassthrough)
+{
+ try
+ {
+ // open the file stream (report errors with exceptions)
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = filePath.open_r(&pIfs);
+ if (error)
+ return error;
+ pIfs->exceptions(std::istream::failbit | std::istream::badbit);
+
+ // output string stream (report errors with exceptions)
+ std::stringstream ostr;
+ ostr.exceptions(std::ostream::failbit | std::ostream::badbit);
+
+ // do the copy
+ boost::iostreams::filtering_ostream filteringOStream ;
+ filteringOStream.push(filter);
+ filteringOStream.push(ostr);
+ boost::iostreams::copy(*pIfs, filteringOStream, 128);
+
+ // return contents with requested line endings
+ *pContents = ostr.str();
+ string_utils::convertLineEndings(pContents, lineEnding);
+
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+
+ return Success();
+}
+
+
+
+bool stripBOM(std::string* pStr);
+
+} // namespace core
+
+
+#endif // CORE_FILE_SERIALIZER_HPP
diff --git a/src/cpp/core/include/core/FileUtils.hpp b/src/cpp/core/include/core/FileUtils.hpp
new file mode 100644
index 0000000..239ee48
--- /dev/null
+++ b/src/cpp/core/include/core/FileUtils.hpp
@@ -0,0 +1,36 @@
+/*
+ * FileUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_FILEUTILS_HPP
+#define CORE_FILEUTILS_HPP
+
+#include <string>
+
+
+
+namespace core {
+
+class FilePath;
+
+namespace file_utils {
+
+FilePath uniqueFilePath(const core::FilePath& parent,
+ const std::string& prefix = "");
+
+
+} // namespace file_utils
+} // namespace core
+
+#endif // CORE_FILEUTILS_HPP
diff --git a/src/cpp/core/include/core/GitGraph.hpp b/src/cpp/core/include/core/GitGraph.hpp
new file mode 100644
index 0000000..8966ce8
--- /dev/null
+++ b/src/cpp/core/include/core/GitGraph.hpp
@@ -0,0 +1,87 @@
+/*
+ * GitGraph.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef CORE_GITGRAPH_HPP
+#define CORE_GITGRAPH_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+namespace core {
+namespace gitgraph {
+
+struct Column
+{
+ Column(int id, const std::string& preCommit, const std::string& postCommit)
+ : id(id), preCommit(preCommit), postCommit(postCommit)
+ {
+ }
+
+ // The ID of the column--useful for coloring.
+ int id;
+
+ // The commit that the column points to at the start. If the column
+ // starts here the preCommit will be empty
+ std::string preCommit;
+ // The commit that the column points to at the end. If the column
+ // terminates here the postCommit will be empty
+ std::string postCommit;
+};
+
+// Represents one line of the graph.
+class Line : public std::vector<Column>
+{
+public:
+ // The nexus is the column that represents this row's commit.
+ // There are potentially multiple columns that could be used
+ // to represent the commit; we pick the leftmost one as this
+ // results in the most orderly graphs.
+ size_t nexus() const;
+
+ // Prints out a machine-parsable string representation of this row
+ std::string string() const;
+};
+
+typedef std::vector<Line> Lines;
+
+// Encapsulates the state and logic used to build up a graph,
+// based on repeated calls with commit-and-parent info.
+// This class doesn't hold all of the result lines; the caller
+// must decide what to do with the lines as they are created
+// during calls to addCommit.
+class GitGraph : boost::noncopyable
+{
+public:
+ GitGraph() : nextColumnId_(0)
+ {}
+
+ // Call addCommit to yield the next line of the graph.
+ // Note that GitGraph is stateful; each call to addCommit
+ // builds on the state of previous calls to addCommit.
+ // So it's important to call in reverse chronological or
+ // topographic order.
+ Line addCommit(const std::string& commit,
+ const std::vector<std::string>& parents);
+
+private:
+ int nextColumnId_;
+ Line pendingLine_;
+};
+
+} // namespace gitgraph
+} // namespace core
+
+#endif // CORE_GITGRAPH_HPP
diff --git a/src/cpp/core/include/core/Hash.hpp b/src/cpp/core/include/core/Hash.hpp
new file mode 100644
index 0000000..6e6f29c
--- /dev/null
+++ b/src/cpp/core/include/core/Hash.hpp
@@ -0,0 +1,33 @@
+/*
+ * Hash.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HASH_HPP
+#define CORE_HASH_HPP
+
+#include <string>
+
+namespace core {
+namespace hash {
+
+std::string crc32Hash(const std::string& content);
+
+std::string crc32HexHash(const std::string& content);
+
+} // namespace hash
+} // namespace core
+
+
+#endif // CORE_HASH_HPP
+
diff --git a/src/cpp/core/include/core/HtmlUtils.hpp b/src/cpp/core/include/core/HtmlUtils.hpp
new file mode 100644
index 0000000..14a9485
--- /dev/null
+++ b/src/cpp/core/include/core/HtmlUtils.hpp
@@ -0,0 +1,63 @@
+/*
+ * HtmlUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTML_UTILS_HPP
+#define CORE_HTML_UTILS_HPP
+
+#include <string>
+
+#include <boost/regex.hpp>
+#include <boost/iostreams/filter/regex.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+namespace html_utils {
+
+std::string defaultTitle(const std::string& htmlContent);
+
+// convert images to base64
+class Base64ImageFilter : public boost::iostreams::regex_filter
+{
+public:
+ explicit Base64ImageFilter(const FilePath& basePath);
+
+private:
+ std::string toBase64Image(const boost::cmatch& match);
+
+private:
+ FilePath basePath_;
+};
+
+// convert fonts to base64
+class CssUrlFilter : public boost::iostreams::regex_filter
+{
+public:
+ explicit CssUrlFilter(const FilePath& basePath);
+
+private:
+ std::string toBase64Url(const boost::cmatch& match);
+
+private:
+ FilePath basePath_;
+};
+
+
+} // namespace regex_utils
+} // namespace core
+
+
+#endif // CORE_HTML_UTILS_HPP
+
diff --git a/src/cpp/core/include/core/IncrementalCommand.hpp b/src/cpp/core/include/core/IncrementalCommand.hpp
new file mode 100644
index 0000000..623b2cd
--- /dev/null
+++ b/src/cpp/core/include/core/IncrementalCommand.hpp
@@ -0,0 +1,70 @@
+/*
+ * IncrementalCommand.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_INCREMENTAL_COMMAND_HPP
+#define CORE_INCREMENTAL_COMMAND_HPP
+
+
+#include <core/ScheduledCommand.hpp>
+
+namespace core {
+
+class IncrementalCommand : public ScheduledCommand
+{
+public:
+ IncrementalCommand(
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute)
+ : ScheduledCommand(execute), incrementalDuration_(incrementalDuration)
+ {
+ }
+
+ IncrementalCommand(
+ const boost::posix_time::time_duration& initialDuration,
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute)
+ : ScheduledCommand(execute), incrementalDuration_(incrementalDuration)
+ {
+ executeUntil(now() + initialDuration);
+ }
+
+ virtual ~IncrementalCommand() {}
+
+ // COPYING: boost::noncopyable
+
+public:
+ virtual void execute()
+ {
+ executeUntil(now() + incrementalDuration_);
+ }
+
+private:
+ void executeUntil(const boost::posix_time::ptime& time)
+ {
+ while (!finished_ && (now() < time))
+ finished_ = !execute_();
+ }
+
+private:
+ const boost::posix_time::time_duration incrementalDuration_;
+};
+
+
+
+} // namespace core
+
+
+#endif // CORE_INCREMENTAL_COMMAND_HPP
diff --git a/src/cpp/core/include/core/Log.hpp b/src/cpp/core/include/core/Log.hpp
new file mode 100644
index 0000000..a27c7d7
--- /dev/null
+++ b/src/cpp/core/include/core/Log.hpp
@@ -0,0 +1,66 @@
+/*
+ * Log.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_LOG_HPP
+#define CORE_LOG_HPP
+
+#include <string>
+
+namespace core {
+
+class Error ;
+class ErrorLocation ;
+
+namespace log {
+
+extern const char DELIM;
+
+std::string cleanDelims(const std::string& source);
+
+void logError(const Error& error, const ErrorLocation& loggedFromLocation) ;
+
+void logErrorMessage(const std::string& message,
+ const ErrorLocation& loggedFromlocation);
+
+void logWarningMessage(const std::string& message,
+ const ErrorLocation& loggedFromLocation);
+
+void logInfoMessage(const std::string& message);
+
+void logDebugMessage(const std::string& message);
+
+std::string errorAsLogEntry(const Error& error);
+
+} // namespace log
+} // namespace core
+
+// Macros for automatic inclusion of ERROR_LOCATION and easy ability to
+// compile out logging calls
+
+#define LOG_ERROR(error) core::log::logError(error, ERROR_LOCATION)
+
+#define LOG_ERROR_MESSAGE(message) core::log::logErrorMessage(message, \
+ ERROR_LOCATION)
+
+#define LOG_WARNING_MESSAGE(message) core::log::logWarningMessage( \
+ message, \
+ ERROR_LOCATION)
+
+#define LOG_INFO_MESSAGE(message) core::log::logInfoMessage(message)
+
+#define LOG_DEBUG_MESSAGE(message) core::log::logDebugMessage(message)
+
+#endif // CORE_LOG_HPP
+
diff --git a/src/cpp/core/include/core/LogWriter.hpp b/src/cpp/core/include/core/LogWriter.hpp
new file mode 100644
index 0000000..8f98338
--- /dev/null
+++ b/src/cpp/core/include/core/LogWriter.hpp
@@ -0,0 +1,49 @@
+/*
+ * LogWriter.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef LOG_WRITER_HPP
+#define LOG_WRITER_HPP
+
+#include <core/system/System.hpp>
+
+namespace core {
+
+class LogWriter
+{
+public:
+ virtual ~LogWriter() {}
+
+ virtual void log(core::system::LogLevel level,
+ const std::string& message) = 0;
+
+ virtual void log(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message) = 0;
+
+protected:
+ std::string formatLogEntry(const std::string& programIdentify,
+ const std::string& message,
+ bool escapeNewlines = true);
+};
+
+namespace system {
+
+void addLogWriter(boost::shared_ptr<core::LogWriter> pLogWriter);
+
+} // namespace system
+
+} // namespace core
+
+#endif // LOG_WRITER_HPP
diff --git a/src/cpp/core/include/core/PerformanceTimer.hpp b/src/cpp/core/include/core/PerformanceTimer.hpp
new file mode 100644
index 0000000..cdf2276
--- /dev/null
+++ b/src/cpp/core/include/core/PerformanceTimer.hpp
@@ -0,0 +1,64 @@
+/*
+ * PerformanceTimer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_PERFORMANCE_TIMER_HPP
+#define CORE_PERFORMANCE_TIMER_HPP
+
+#include <iosfwd>
+#include <vector>
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+
+class PerformanceTimer : boost::noncopyable
+{
+public:
+ PerformanceTimer() ;
+ explicit PerformanceTimer(const std::string& step);
+ virtual ~PerformanceTimer() ;
+ // COPYING: boost::noncopyable
+
+public:
+ void start(const std::string& step);
+ void advance(const std::string& step);
+ void stop();
+ bool running() const;
+
+private:
+ boost::posix_time::ptime now() const;
+ void recordPendingStep();
+
+private:
+ typedef std::pair<std::string,boost::posix_time::time_duration> Step;
+ typedef std::vector<Step> Steps;
+
+ boost::posix_time::ptime startTime_;
+ Steps steps_ ;
+
+ friend std::ostream& operator << (std::ostream& stream,
+ const PerformanceTimer& t);
+};
+
+std::ostream& operator << (std::ostream& os, const PerformanceTimer& t) ;
+
+} // namespace core
+
+#define TIME_FUNCTION core::PerformanceTimer t(BOOST_CURRENT_FUNCTION);
+
+#endif // CORE_PERFORMANCE_TIMER_HPP
+
diff --git a/src/cpp/core/include/core/PeriodicCommand.hpp b/src/cpp/core/include/core/PeriodicCommand.hpp
new file mode 100644
index 0000000..d1200e0
--- /dev/null
+++ b/src/cpp/core/include/core/PeriodicCommand.hpp
@@ -0,0 +1,70 @@
+/*
+ * PeriodicCommand.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_PERIODIC_COMMAND_HPP
+#define CORE_PERIODIC_COMMAND_HPP
+
+#include <core/ScheduledCommand.hpp>
+
+namespace core {
+
+class PeriodicCommand : public ScheduledCommand
+{
+public:
+ PeriodicCommand(const boost::posix_time::time_duration& period,
+ const boost::function<bool()>& execute,
+ bool immediate = true)
+ : ScheduledCommand(execute),
+ period_(period)
+ {
+ if (immediate)
+ nextExecutionTime_ = now();
+ else
+ nextExecutionTime_ = now() + period_;
+
+ }
+
+ virtual ~PeriodicCommand() {}
+
+ // COPYING: boost::noncopyable
+
+public:
+ virtual void execute()
+ {
+ if (now() > nextExecutionTime_)
+ {
+ if (execute_())
+ {
+ nextExecutionTime_ = now() + period_;
+ }
+ else
+ {
+ finished_ = true;
+ }
+ }
+ }
+
+private:
+ const boost::posix_time::time_duration period_;
+ boost::posix_time::ptime nextExecutionTime_;
+};
+
+
+
+} // namespace core
+
+
+#endif // CORE_PERIODIC_COMMAND_HPP
diff --git a/src/cpp/core/include/core/Predicate.hpp b/src/cpp/core/include/core/Predicate.hpp
new file mode 100644
index 0000000..cbb4cf4
--- /dev/null
+++ b/src/cpp/core/include/core/Predicate.hpp
@@ -0,0 +1,43 @@
+/*
+ * Predicate.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_PREDICATE_HPP
+#define CORE_PREDICATE_HPP
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+namespace core {
+namespace predicate {
+
+template <typename T>
+bool isWithinRange(const T& value, const T& min, const T& max)
+{
+ return (value >= min) && (value <= max);
+}
+
+template <typename T>
+boost::function<bool(T)> range(const T& min, const T& max)
+{
+ return boost::bind(isWithinRange<T>, _1, min, max);
+}
+
+
+} // namespace predicate
+} // namespace core
+
+
+#endif // CORE_PREDICATE_HPP
+
diff --git a/src/cpp/core/include/core/ProgramOptions.hpp b/src/cpp/core/include/core/ProgramOptions.hpp
new file mode 100644
index 0000000..4f0f8d6
--- /dev/null
+++ b/src/cpp/core/include/core/ProgramOptions.hpp
@@ -0,0 +1,76 @@
+/*
+ * ProgramOptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_PROGRAM_OPTIONS_HPP
+#define CORE_PROGRAM_OPTIONS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/program_options.hpp>
+
+#include <core/ProgramStatus.hpp>
+
+namespace core {
+
+class Error;
+class ErrorLocation;
+class ProgramStatus;
+
+namespace program_options {
+
+struct OptionsDescription
+{
+ OptionsDescription(const std::string& programName,
+ const std::string& defaultConfigFilePath = std::string())
+ : programName(programName),
+ defaultConfigFilePath(defaultConfigFilePath),
+ commandLine("command-line options"),
+ configFile("config-file options")
+ {
+ }
+ std::string programName ;
+ std::string defaultConfigFilePath;
+ boost::program_options::options_description commandLine;
+ boost::program_options::positional_options_description positionalOptions;
+ boost::program_options::options_description configFile;
+};
+
+
+ProgramStatus read(const OptionsDescription& optionsDescription,
+ int argc,
+ char * const argv[],
+ bool* pHelp);
+
+inline ProgramStatus read(const OptionsDescription& optionsDescription,
+ int argc,
+ char * const argv[])
+{
+ bool help;
+ return read(optionsDescription, argc, argv, &help);
+}
+
+void reportError(const std::string& errorMessage,
+ const ErrorLocation& location);
+
+void reportWarnings(const std::string& warningMessages,
+ const ErrorLocation& location);
+
+} // namespace program_options
+} // namespace core
+
+#endif // CORE_PROGRAM_OPTIONS_HPP
+
diff --git a/src/cpp/core/include/core/ProgramStatus.hpp b/src/cpp/core/include/core/ProgramStatus.hpp
new file mode 100644
index 0000000..66928ae
--- /dev/null
+++ b/src/cpp/core/include/core/ProgramStatus.hpp
@@ -0,0 +1,58 @@
+/*
+ * ProgramStatus.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_PROGRAM_STATUS_HPP
+#define CORE_PROGRAM_STATUS_HPP
+
+#include <cstdlib>
+
+namespace core {
+
+class ProgramStatus
+{
+public:
+ static ProgramStatus run()
+ {
+ return ProgramStatus(false, EXIT_SUCCESS);
+ }
+
+ static ProgramStatus exitSuccess()
+ {
+ return ProgramStatus(true, EXIT_SUCCESS);
+ }
+
+ static ProgramStatus exitFailure()
+ {
+ return ProgramStatus(true, EXIT_FAILURE);
+ }
+
+public:
+ ProgramStatus(bool exit, int exitCode) : exit_(exit), exitCode_(exitCode) {}
+
+ // COPYING: via compiler (copyable members)
+
+ bool exit() const { return exit_; }
+ int exitCode() const { return exitCode_; }
+
+private:
+ bool exit_ ;
+ int exitCode_ ;
+};
+
+} // namespace core
+
+
+#endif // CORE_PROGRAM_STATUS_HPP
+
diff --git a/src/cpp/core/include/core/Promise.hpp b/src/cpp/core/include/core/Promise.hpp
new file mode 100644
index 0000000..fdb3da5
--- /dev/null
+++ b/src/cpp/core/include/core/Promise.hpp
@@ -0,0 +1,63 @@
+/*
+ * Promise.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef PROMISE_HPP
+#define PROMISE_HPP
+
+#include <boost/function.hpp>
+
+/*
+ This class is NOT threadsafe.
+
+ Convenient way to delay and memoize an expensive computation.
+ */
+template <typename T>
+class Promise
+{
+public:
+ Promise(boost::function<T ()> func) :
+ resolved_(false),
+ func_(func)
+ {
+ }
+
+ T& value()
+ {
+ if (!resolved_)
+ {
+ value_ = func_();
+ resolved_ = true;
+ }
+ return value_;
+ }
+
+ operator T&()
+ {
+ return value();
+ }
+
+ bool isResolved()
+ {
+ return resolved_;
+ }
+
+private:
+ bool resolved_;
+ T value_;
+ boost::function<T ()> func_;
+};
+
+#endif // PROMISE_HPP
diff --git a/src/cpp/core/include/core/Random.hpp b/src/cpp/core/include/core/Random.hpp
new file mode 100644
index 0000000..9481eca
--- /dev/null
+++ b/src/cpp/core/include/core/Random.hpp
@@ -0,0 +1,49 @@
+/*
+ * Random.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_RANDOM_HPP
+#define CORE_RANDOM_HPP
+
+#include <limits>
+#include <ctime>
+
+#include <boost/random.hpp>
+
+namespace core {
+namespace random {
+
+template <typename T>
+T uniformRandomInteger()
+{
+ // setup generator and distribution
+ typedef boost::mt19937 GeneratorType;
+ typedef boost::uniform_int<T> DistributionType;
+ GeneratorType generator(std::time(NULL));
+ DistributionType distribution(std::numeric_limits<T>::min(),
+ std::numeric_limits<T>::max());
+
+ // create variate generator
+ boost::variate_generator<GeneratorType, DistributionType> vg(generator,
+ distribution);
+
+ // return random number
+ return vg();
+}
+
+} // namespace random
+} // namespace core
+
+
+#endif // CORE_RANDOM_HPP
diff --git a/src/cpp/core/include/core/RegexUtils.hpp b/src/cpp/core/include/core/RegexUtils.hpp
new file mode 100644
index 0000000..507fa1d
--- /dev/null
+++ b/src/cpp/core/include/core/RegexUtils.hpp
@@ -0,0 +1,58 @@
+/*
+ * RegexUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_REGEX_UTILS_HPP
+#define CORE_REGEX_UTILS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/regex_fwd.hpp>
+#include <boost/iostreams/filter/regex.hpp>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace regex_utils {
+
+// convert a pattern which includes wildcard (i.e. '*') characters
+// into a regulard expression
+boost::regex wildcardPatternToRegex(const std::string& pattern);
+
+
+bool textMatches(const std::string& text,
+ const boost::regex& regex,
+ bool prefixOnly,
+ bool caseSensitive);
+
+core::Error filterString(
+ const std::string& input,
+ const std::vector<boost::iostreams::regex_filter>& filters,
+ std::string* pOutput);
+
+core::Error filterString(
+ const std::string& input,
+ const boost::iostreams::regex_filter& filter,
+ std::string* pOutput);
+
+
+} // namespace regex_utils
+} // namespace core
+
+
+#endif // CORE_REGEX_UTILS_HPP
+
diff --git a/src/cpp/core/include/core/SafeConvert.hpp b/src/cpp/core/include/core/SafeConvert.hpp
new file mode 100644
index 0000000..6d9aa57
--- /dev/null
+++ b/src/cpp/core/include/core/SafeConvert.hpp
@@ -0,0 +1,93 @@
+/*
+ * SafeConvert.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SAFE_CONVERT_HPP
+#define CORE_SAFE_CONVERT_HPP
+
+#include <string>
+#include <ios>
+#include <iostream>
+#include <locale>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+namespace core {
+namespace safe_convert {
+
+template <typename T>
+T stringTo(const std::string& str, T defaultValue)
+{
+ try
+ {
+ return boost::lexical_cast<T>(str);
+ }
+ catch(boost::bad_lexical_cast&)
+ {
+ return defaultValue;
+ }
+}
+
+template <typename T>
+T stringTo(const std::string& str,
+ T defaultValue,
+ std::ios_base& (*f)(std::ios_base&))
+{
+ std::istringstream iss(str);
+ T result;
+ if ((iss >> f >> result).fail())
+ return defaultValue;
+ return result;
+}
+
+template <typename T>
+std::string numberToString(T input, bool localeIndependent = true)
+{
+ try
+ {
+ std::ostringstream stream;
+ if (localeIndependent)
+ stream.imbue(std::locale::classic()); // force locale-independence
+ stream << input;
+ return stream.str();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // return empty string for unexpected error
+ return std::string();
+}
+
+template <typename TInput, typename TOutput>
+TOutput numberTo(TInput input, TOutput defaultValue)
+{
+ try
+ {
+ return boost::numeric_cast<TOutput>(input);
+ }
+ catch(...)
+ {
+ return defaultValue;
+ }
+}
+
+} // namespace safe_convert
+} // namespace core
+
+
+#endif // CORE_SAFE_CONVERT_HPP
+
diff --git a/src/cpp/core/include/core/ScheduledCommand.hpp b/src/cpp/core/include/core/ScheduledCommand.hpp
new file mode 100644
index 0000000..e89fd1e
--- /dev/null
+++ b/src/cpp/core/include/core/ScheduledCommand.hpp
@@ -0,0 +1,63 @@
+/*
+ * ScheduledCommand.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_SCHEDULED_COMMAND_HPP
+#define CORE_SCHEDULED_COMMAND_HPP
+
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+
+// NOTE: execute function should return true if it has more work to do
+// or false to indicate all work is completed
+
+class ScheduledCommand : boost::noncopyable
+{
+public:
+ explicit ScheduledCommand(const boost::function<bool()>& execute)
+ : execute_(execute), finished_(false)
+ {
+ }
+
+ virtual ~ScheduledCommand() {}
+
+ // COPYING: boost::noncopyable
+
+public:
+ virtual void execute() = 0;
+
+ bool finished() const { return finished_; }
+
+protected:
+ boost::function<bool()> execute_;
+ bool finished_;
+
+protected:
+ static boost::posix_time::ptime now()
+ {
+ return boost::posix_time::microsec_clock::universal_time();
+ }
+};
+
+
+
+} // namespace core
+
+
+#endif // CORE_SCHEDULED_COMMAND_HPP
diff --git a/src/cpp/core/include/core/Scope.hpp b/src/cpp/core/include/core/Scope.hpp
new file mode 100644
index 0000000..099939a
--- /dev/null
+++ b/src/cpp/core/include/core/Scope.hpp
@@ -0,0 +1,73 @@
+/*
+ * Scope.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SCOPE_HPP
+#define CORE_SCOPE_HPP
+
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace core {
+namespace scope {
+
+template <class T>
+class SetOnExit : boost::noncopyable
+{
+public:
+ SetOnExit(T* pLocation, const T& value)
+ {
+ pLocation_ = pLocation;
+ value_ = value;
+ }
+
+ virtual ~SetOnExit()
+ {
+ try
+ {
+ *pLocation_ = value_;
+ }
+ catch(...)
+ {
+ }
+ }
+
+ private:
+ T* pLocation_;
+ T value_;
+};
+
+class CallOnExit : boost::noncopyable
+{
+public:
+ CallOnExit(const boost::function<void()>& func)
+ {
+ func_ = func;
+ }
+
+ ~CallOnExit()
+ {
+ func_();
+ }
+
+private:
+ boost::function<void()> func_;
+};
+
+} // namespace scope
+} // namespace core
+
+
+#endif // CORE_SCOPE_HPP
+
diff --git a/src/cpp/core/include/core/Settings.hpp b/src/cpp/core/include/core/Settings.hpp
new file mode 100644
index 0000000..2c3b028
--- /dev/null
+++ b/src/cpp/core/include/core/Settings.hpp
@@ -0,0 +1,70 @@
+/*
+ * Settings.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SETTINGS_HPP
+#define CORE_SETTINGS_HPP
+
+#include <string>
+#include <map>
+
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error ;
+
+class Settings : boost::noncopyable
+{
+public:
+ Settings() ;
+ virtual ~Settings() ;
+ // COPYING: boost::noncopyable
+
+ Error initialize(const FilePath& filePath) ;
+
+public:
+ void set(const std::string& name, const std::string& value);
+ void set(const std::string& name, int value);
+ void set(const std::string& name, bool value);
+
+ bool contains(const std::string& name) const;
+ std::string get(const std::string& name,
+ const std::string& defaultValue = std::string()) const;
+ int getInt(const std::string& name, int defaultValue = 0) const;
+ int getBool(const std::string& name, bool defaultValue = false) const;
+
+ void forEach(const boost::function<void(const std::string&,
+ const std::string&)>& func) const;
+
+ void beginUpdate();
+ void endUpdate();
+
+private:
+ void writeSettings() ;
+
+private:
+ FilePath settingsFile_ ;
+ std::map<std::string, std::string> settingsMap_ ;
+ bool updatePending_ ;
+ bool isDirty_;
+};
+
+}
+
+#endif // CORE_SETTINGS_HPP
+
diff --git a/src/cpp/core/include/core/StderrLogWriter.hpp b/src/cpp/core/include/core/StderrLogWriter.hpp
new file mode 100644
index 0000000..462c184
--- /dev/null
+++ b/src/cpp/core/include/core/StderrLogWriter.hpp
@@ -0,0 +1,43 @@
+/*
+ * StderrLogWriter.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef STDERR_LOG_WRITER_HPP
+#define STDERR_LOG_WRITER_HPP
+
+#include <core/LogWriter.hpp>
+
+namespace core {
+
+class StderrLogWriter : public LogWriter
+{
+public:
+ StderrLogWriter(const std::string& programIdentity, int logLevel);
+ virtual ~StderrLogWriter();
+
+ virtual void log(core::system::LogLevel level,
+ const std::string& message);
+
+ virtual void log(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message);
+
+private:
+ std::string programIdentity_;
+ int logLevel_;
+};
+
+} // namespace core
+
+#endif // STDERR_LOG_WRITER_HPP
diff --git a/src/cpp/core/include/core/StringUtils.hpp b/src/cpp/core/include/core/StringUtils.hpp
new file mode 100644
index 0000000..e46e514
--- /dev/null
+++ b/src/cpp/core/include/core/StringUtils.hpp
@@ -0,0 +1,179 @@
+/*
+ * StringUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_STRING_UTILS_HPP
+#define CORE_STRING_UTILS_HPP
+
+#include <string>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+namespace core {
+namespace string_utils {
+
+enum LineEnding {
+ LineEndingWindows,
+ LineEndingPosix,
+ LineEndingNative,
+ LineEndingPassthrough
+};
+
+std::string utf8ToSystem(const std::string& str,
+ bool escapeInvalidChars=false);
+std::string systemToUtf8(const std::string& str);
+
+std::string toLower(const std::string& str);
+std::string textToHtml(const std::string& str);
+
+std::string htmlEscape(const std::string& str, bool isAttributeValue = false);
+std::string jsLiteralEscape(const std::string& str);
+std::string jsonLiteralEscape(const std::string& str);
+std::string jsonLiteralUnescape(const std::string& str);
+
+void convertLineEndings(std::string* str, LineEnding type);
+
+std::string filterControlChars(const std::string& str);
+
+bool parseVersion(const std::string& str, uint64_t* pVersion);
+
+template<typename T>
+T hashStable(const std::string& str)
+{
+ T hash = 5381;
+
+ std::string::const_iterator it;
+ for (it = str.begin(); it != str.end(); it++)
+ hash = ((hash << 5) + hash) + *it;
+
+ return hash;
+}
+
+// Moves the begin pointer the specified number of UTF8
+// characters.
+template <typename InputIterator>
+Error utf8Advance(InputIterator begin,
+ size_t chars,
+ InputIterator end,
+ InputIterator* pResult)
+{
+ using namespace boost::system;
+
+ for ( ; begin != end && chars > 0; --chars)
+ {
+ unsigned char byte = static_cast<unsigned char>(*(begin++));
+ if (byte > 0x7F)
+ {
+ // Outside the legal range of UTF-8 bytes
+ if (byte > 0xF4)
+ return systemError(errc::illegal_byte_sequence, ERROR_LOCATION);
+
+ // Don't count the first bit, which represents the
+ // initial character.
+ byte <<= 1;
+
+ // Found a continuation byte (10...) where none was expected!
+ if (byte < 0x80)
+ return systemError(errc::illegal_byte_sequence, ERROR_LOCATION);
+
+ // OK, now eat the appropriate number of continuation bytes,
+ // by counting the number of leading bits that are on.
+ for ( ; byte >= 0x80 && begin != end; byte <<= 1)
+ {
+ unsigned char contByte = static_cast<unsigned char>(*(begin++));
+
+ // Expected a continuation byte but didn't get one!
+ if ((contByte & 0xC0) != 0x80)
+ return systemError(errc::illegal_byte_sequence, ERROR_LOCATION);
+ }
+
+ // Premature EOF--malformed UTF-8
+ if (byte >= 0x80)
+ return systemError(errc::illegal_byte_sequence, ERROR_LOCATION);
+ }
+ }
+
+ // Premature EOF
+ if (chars != 0)
+ return systemError(errc::invalid_argument, ERROR_LOCATION);
+
+ *pResult = begin;
+ return Success();
+}
+std::string wideToUtf8(const std::wstring& value);
+std::wstring utf8ToWide(const std::string& value,
+ const std::string& context = std::string());
+
+template <typename Iterator, typename InputIterator>
+Error utf8Clean(Iterator begin,
+ InputIterator end,
+ unsigned char replacementChar)
+
+{
+ using namespace boost::system;
+
+ if (replacementChar > 0x7F)
+ return systemError(errc::invalid_argument,
+ "Invalid UTF-8 replacement character",
+ ERROR_LOCATION);
+
+ Error error;
+
+ while (begin != end)
+ {
+ error = utf8Advance(begin, 1, end, &begin);
+ if (error)
+ {
+ *begin = replacementChar;
+ }
+ }
+
+ return Success();
+}
+
+
+template <typename InputIterator>
+Error utf8Distance(InputIterator begin,
+ InputIterator end,
+ size_t* pResult)
+{
+ *pResult = 0;
+ Error error;
+ while (begin != end)
+ {
+ error = utf8Advance(begin, 1, end, &begin);
+ if (error)
+ return error;
+ (*pResult)++;
+ }
+
+ return Success();
+}
+
+
+bool isalpha(wchar_t c);
+bool isalnum(wchar_t c);
+
+inline bool stringNotEmpty(const std::string& str)
+{
+ return !str.empty();
+}
+
+void trimLeadingLines(int maxLines, std::string* pLines);
+
+} // namespace string_utils
+} // namespace core
+
+#endif // CORE_STRING_UTILS_HPP
+
diff --git a/src/cpp/core/include/core/SyslogLogWriter.hpp b/src/cpp/core/include/core/SyslogLogWriter.hpp
new file mode 100644
index 0000000..31fcca2
--- /dev/null
+++ b/src/cpp/core/include/core/SyslogLogWriter.hpp
@@ -0,0 +1,41 @@
+/*
+ * SyslogLogWriter.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SYSLOG_LOG_WRITER_HPP
+#define SYSLOG_LOG_WRITER_HPP
+
+#include <core/LogWriter.hpp>
+
+namespace core {
+
+class SyslogLogWriter : public LogWriter
+{
+public:
+ SyslogLogWriter(const std::string& programIdentity, int logLevel);
+ virtual ~SyslogLogWriter();
+ virtual void log(core::system::LogLevel level,
+ const std::string& message);
+ virtual void log(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message);
+
+private:
+ std::string programIdentity_;
+ bool logToStderr_;
+};
+
+} // namespace core
+
+#endif // SYSLOG_LOG_WRITER_HPP
diff --git a/src/cpp/core/include/core/Thread.hpp b/src/cpp/core/include/core/Thread.hpp
new file mode 100644
index 0000000..81cfcd6
--- /dev/null
+++ b/src/cpp/core/include/core/Thread.hpp
@@ -0,0 +1,292 @@
+/*
+ * Thread.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_THREAD_HPP
+#define CORE_THREAD_HPP
+
+#include <queue>
+
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+
+#include <core/BoostErrors.hpp>
+#include <core/BoostThread.hpp>
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+
+
+#define LOCK_MUTEX(m) try { \
+ boost::lock_guard<boost::mutex> lock(m);
+
+#define END_LOCK_MUTEX } \
+ catch(const boost::thread_resource_error& e) \
+ { \
+ Error threadError(boost::thread_error::ec_from_exception(e), \
+ ERROR_LOCATION) ; \
+ LOG_ERROR(threadError); \
+ }
+
+namespace core {
+namespace thread {
+
+template <typename T>
+class ThreadsafeValue : boost::noncopyable
+{
+public:
+ explicit ThreadsafeValue(const T& value = T()) : value_(value) {}
+ virtual ~ThreadsafeValue() {}
+
+ T get()
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ return value_;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return T();
+ }
+
+ void set(const T& value)
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ value_ = value;
+ }
+ END_LOCK_MUTEX
+ }
+
+private:
+ boost::mutex mutex_;
+ T value_;
+};
+
+template <typename K, typename V>
+class ThreadsafeMap : boost::noncopyable
+{
+public:
+ ThreadsafeMap() {}
+ virtual ~ThreadsafeMap() {}
+
+ bool contains(const K& key)
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ return map_.find(key) != map_.end();
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false;
+ }
+
+ V get(const K& key, const V& defaultValue = V())
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ typename std::map<K,V>::const_iterator it = map_.find(key);
+ if (it != map_.end())
+ return it->second;
+ else
+ return defaultValue;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return defaultValue;
+ }
+
+ V collect(const K& key)
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ typename std::map<K,V>::const_iterator it = map_.find(key);
+ if (it != map_.end())
+ {
+ std::string val = it->second;
+ map_.erase(key);
+ return val;
+ }
+ }
+ END_LOCK_MUTEX
+
+ return V();
+ }
+
+ void set(const K& key, const V& val)
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ map_[key] = val;
+ }
+ END_LOCK_MUTEX
+ }
+
+ void remove(const K& key)
+ {
+ LOCK_MUTEX(mutex_)
+ {
+ map_.erase(key);
+ }
+ END_LOCK_MUTEX
+ }
+
+private:
+ boost::mutex mutex_;
+ std::map<K,V> map_;
+};
+
+
+template <typename T>
+class ThreadsafeQueue : boost::noncopyable
+{
+public:
+ explicit ThreadsafeQueue(bool freeSyncObjects = false)
+ : pMutex_(new boost::mutex()),
+ pWaitCondition_(new boost::condition()),
+ freeSyncObjects_(freeSyncObjects)
+ {
+ }
+
+ virtual ~ThreadsafeQueue()
+ {
+ try
+ {
+ if (freeSyncObjects_)
+ {
+ delete pMutex_;
+ delete pWaitCondition_;
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+ // COPYING: boost::noncopyable
+
+public:
+
+ void enque(const T& val)
+ {
+ LOCK_MUTEX(*pMutex_)
+ {
+ // enque
+ queue_.push(val);
+ }
+ END_LOCK_MUTEX
+
+ pWaitCondition_->notify_all();
+ }
+
+ bool deque(T* pVal)
+ {
+ LOCK_MUTEX(*pMutex_)
+ {
+ if (!queue_.empty())
+ {
+ // remove it
+ *pVal = queue_.front();
+ queue_.pop();
+
+ // return true
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false;
+ }
+
+ bool isEmpty()
+ {
+ LOCK_MUTEX(*pMutex_)
+ {
+ return queue_.empty();
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return true;
+ }
+
+ bool deque(T* pVal, const boost::posix_time::time_duration& waitDuration)
+ {
+ // first see if we already have one
+ if (deque(pVal))
+ return true;
+
+ // now wait the specified interval for one to materialize
+ if (wait(waitDuration))
+ return deque(pVal);
+ else
+ return false;
+ }
+
+ bool wait(const boost::posix_time::time_duration& waitDuration =
+ boost::posix_time::time_duration(boost::posix_time::not_a_date_time))
+ {
+ using namespace boost;
+ try
+ {
+ unique_lock<mutex> lock(*pMutex_);
+ if (waitDuration.is_not_a_date_time())
+ {
+ pWaitCondition_->wait(lock);
+ return true;
+ }
+ else
+ {
+ system_time timeoutTime = get_system_time() + waitDuration;
+ return pWaitCondition_->timed_wait(lock, timeoutTime);
+ }
+ }
+ catch(const thread_resource_error& e)
+ {
+ Error waitError(boost::thread_error::ec_from_exception(e), ERROR_LOCATION) ;
+ LOG_ERROR(waitError);
+ return false ;
+ }
+ }
+
+
+private:
+ // synchronization objects. heap based so that we can control whether
+ // they are destroyed or not (boost has been known to crash if a mutex
+ // is being destroyed while it is being waited on so sometimes it is
+ // better to simply never delete these objects
+ boost::mutex* pMutex_ ;
+ boost::condition* pWaitCondition_ ;
+
+ // instance data
+ const bool freeSyncObjects_;
+ std::queue<T> queue_;
+};
+
+void safeLaunchThread(boost::function<void()> threadMain,
+ boost::thread* pThread = NULL);
+
+} // namespace thread
+} // namespace core
+
+#endif // CORE_THREAD_HPP
+
diff --git a/src/cpp/core/include/core/Trace.hpp b/src/cpp/core/include/core/Trace.hpp
new file mode 100644
index 0000000..ce2b7a9
--- /dev/null
+++ b/src/cpp/core/include/core/Trace.hpp
@@ -0,0 +1,36 @@
+/*
+ * Trace.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_TRACE_HPP
+#define CORE_TRACE_HPP
+
+#include <iosfwd>
+#include <string>
+
+#include <boost/current_function.hpp>
+
+namespace core {
+namespace trace {
+
+void add(void* key, const std::string& functionName);
+
+} // namespace trace
+} // namespace core
+
+#define TRACE_CURRENT_METHOD \
+ core::trace::add(this, BOOST_CURRENT_FUNCTION);
+
+#endif // CORE_PERFORMANCE_TIMER_HPP
+
diff --git a/src/cpp/core/include/core/WaitUtils.hpp b/src/cpp/core/include/core/WaitUtils.hpp
new file mode 100644
index 0000000..f20a86c
--- /dev/null
+++ b/src/cpp/core/include/core/WaitUtils.hpp
@@ -0,0 +1,50 @@
+/*
+ * WaitUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_WAITUTILS_HPP
+#define CORE_WAITUTILS_HPP
+
+#include <boost/function.hpp>
+
+#include <core/Error.hpp>
+
+namespace core {
+
+
+enum WaitResultType {
+ WaitSuccess,
+ WaitContinue,
+ WaitError
+};
+
+struct WaitResult
+{
+ WaitResult(WaitResultType type, Error error)
+ : type(type), error(error)
+ {
+ }
+
+ WaitResultType type;
+ Error error;
+};
+
+Error waitWithTimeout(const boost::function<WaitResult()>& connectFunction,
+ int initialWaitMs = 30,
+ int incrementWaitMs = 10,
+ int maxWaitSec = 10);
+
+} // namespace core
+
+#endif // CORE_WAITUTILS_HPP
diff --git a/src/cpp/core/include/core/collection/Tree.hpp b/src/cpp/core/include/core/collection/Tree.hpp
new file mode 100644
index 0000000..942701b
--- /dev/null
+++ b/src/cpp/core/include/core/collection/Tree.hpp
@@ -0,0 +1,2806 @@
+/*
+ * Tree.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ * Copyright (C) 2001-2011 Kasper Peeters <kasper at phi-sci.com>
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// STL-like templated tree class.
+//
+// Copyright (C) 2001-2011 Kasper Peeters <kasper at phi-sci.com>
+// Distributed under the GNU General Public License version 3.
+//
+// When used together with the htmlcxx library to create
+// HTML::Node template instances, the GNU Lesser General Public
+// version 2 applies. Special permission to use tree.hh under
+// the LGPL for other projects can be requested from the author.
+//
+// If you have received this program directly from RStudio pursuant
+// to the terms of a commercial license then tree.hh is covered
+// under this same commerical license.
+//
+
+/** \mainpage tree.hh
+ \author Kasper Peeters
+ \version 2.81
+ \date 23-Aug-2011
+ \see http://tree.phi-sci.com/
+ \see http://tree.phi-sci.com/ChangeLog
+
+ The tree.hh library for C++ provides an STL-like container class
+ for n-ary trees, templated over the data stored at the
+ nodes. Various types of iterators are provided (post-order,
+ pre-order, and others). Where possible the access methods are
+ compatible with the STL or alternative algorithms are
+ available.
+*/
+
+
+#ifndef tree_hh_
+#define tree_hh_
+
+#include <cassert>
+#include <memory>
+#include <stdexcept>
+#include <iterator>
+#include <set>
+#include <queue>
+#include <algorithm>
+#include <cstddef>
+
+
+/// A node in the tree, combining links to other nodes as well as the actual data.
+template<class T>
+class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8.
+ public:
+ tree_node_();
+ tree_node_(const T&);
+
+ tree_node_<T> *parent;
+ tree_node_<T> *first_child, *last_child;
+ tree_node_<T> *prev_sibling, *next_sibling;
+ T data;
+}; // __attribute__((packed));
+
+template<class T>
+tree_node_<T>::tree_node_()
+ : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0)
+ {
+ }
+
+template<class T>
+tree_node_<T>::tree_node_(const T& val)
+ : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val)
+ {
+ }
+
+template <class T, class tree_node_allocator = std::allocator<tree_node_<T> > >
+class tree {
+ protected:
+ typedef tree_node_<T> tree_node;
+ public:
+ /// Value of the data stored at a node.
+ typedef T value_type;
+
+ class iterator_base;
+ class pre_order_iterator;
+ class post_order_iterator;
+ class sibling_iterator;
+ class leaf_iterator;
+
+ tree();
+ tree(const T&);
+ tree(const iterator_base&);
+ tree(const tree<T, tree_node_allocator>&);
+ ~tree();
+ tree<T,tree_node_allocator>& operator=(const tree<T, tree_node_allocator>&);
+
+ /// Base class for iterators, only pointers stored, no traversal logic.
+#ifdef __SGI_STL_PORT
+ class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> {
+#else
+ class iterator_base {
+#endif
+ public:
+ typedef T value_type;
+ typedef T* pointer;
+ typedef T& reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+ typedef std::bidirectional_iterator_tag iterator_category;
+
+ iterator_base();
+ iterator_base(tree_node *);
+
+ T& operator*() const;
+ T* operator->() const;
+
+ /// When called, the next increment/decrement skips children of this node.
+ void skip_children();
+ void skip_children(bool skip);
+ /// Number of children of the node pointed to by the iterator.
+ unsigned int number_of_children() const;
+
+ sibling_iterator begin() const;
+ sibling_iterator end() const;
+
+ tree_node *node;
+ protected:
+ bool skip_current_children_;
+ };
+
+ /// Depth-first iterator, first accessing the node, then its children.
+ class pre_order_iterator : public iterator_base {
+ public:
+ pre_order_iterator();
+ pre_order_iterator(tree_node *);
+ pre_order_iterator(const iterator_base&);
+ pre_order_iterator(const sibling_iterator&);
+
+ bool operator==(const pre_order_iterator&) const;
+ bool operator!=(const pre_order_iterator&) const;
+ pre_order_iterator& operator++();
+ pre_order_iterator& operator--();
+ pre_order_iterator operator++(int);
+ pre_order_iterator operator--(int);
+ pre_order_iterator& operator+=(unsigned int);
+ pre_order_iterator& operator-=(unsigned int);
+ };
+
+ /// Depth-first iterator, first accessing the children, then the node itself.
+ class post_order_iterator : public iterator_base {
+ public:
+ post_order_iterator();
+ post_order_iterator(tree_node *);
+ post_order_iterator(const iterator_base&);
+ post_order_iterator(const sibling_iterator&);
+
+ bool operator==(const post_order_iterator&) const;
+ bool operator!=(const post_order_iterator&) const;
+ post_order_iterator& operator++();
+ post_order_iterator& operator--();
+ post_order_iterator operator++(int);
+ post_order_iterator operator--(int);
+ post_order_iterator& operator+=(unsigned int);
+ post_order_iterator& operator-=(unsigned int);
+
+ /// Set iterator to the first child as deep as possible down the tree.
+ void descend_all();
+ };
+
+ /// Breadth-first iterator, using a queue
+ class breadth_first_queued_iterator : public iterator_base {
+ public:
+ breadth_first_queued_iterator();
+ breadth_first_queued_iterator(tree_node *);
+ breadth_first_queued_iterator(const iterator_base&);
+
+ bool operator==(const breadth_first_queued_iterator&) const;
+ bool operator!=(const breadth_first_queued_iterator&) const;
+ breadth_first_queued_iterator& operator++();
+ breadth_first_queued_iterator operator++(int);
+ breadth_first_queued_iterator& operator+=(unsigned int);
+
+ private:
+ std::queue<tree_node *> traversal_queue;
+ };
+
+ /// The default iterator types throughout the tree class.
+ typedef pre_order_iterator iterator;
+ typedef breadth_first_queued_iterator breadth_first_iterator;
+
+ /// Iterator which traverses only the nodes at a given depth from the root.
+ class fixed_depth_iterator : public iterator_base {
+ public:
+ fixed_depth_iterator();
+ fixed_depth_iterator(tree_node *);
+ fixed_depth_iterator(const iterator_base&);
+ fixed_depth_iterator(const sibling_iterator&);
+ fixed_depth_iterator(const fixed_depth_iterator&);
+
+ bool operator==(const fixed_depth_iterator&) const;
+ bool operator!=(const fixed_depth_iterator&) const;
+ fixed_depth_iterator& operator++();
+ fixed_depth_iterator& operator--();
+ fixed_depth_iterator operator++(int);
+ fixed_depth_iterator operator--(int);
+ fixed_depth_iterator& operator+=(unsigned int);
+ fixed_depth_iterator& operator-=(unsigned int);
+
+ tree_node *top_node;
+ };
+
+ /// Iterator which traverses only the nodes which are siblings of each other.
+ class sibling_iterator : public iterator_base {
+ public:
+ sibling_iterator();
+ sibling_iterator(tree_node *);
+ sibling_iterator(const sibling_iterator&);
+ sibling_iterator(const iterator_base&);
+
+ bool operator==(const sibling_iterator&) const;
+ bool operator!=(const sibling_iterator&) const;
+ sibling_iterator& operator++();
+ sibling_iterator& operator--();
+ sibling_iterator operator++(int);
+ sibling_iterator operator--(int);
+ sibling_iterator& operator+=(unsigned int);
+ sibling_iterator& operator-=(unsigned int);
+
+ tree_node *range_first() const;
+ tree_node *range_last() const;
+ tree_node *parent_;
+ private:
+ void set_parent_();
+ };
+
+ /// Iterator which traverses only the leaves.
+ class leaf_iterator : public iterator_base {
+ public:
+ leaf_iterator();
+ leaf_iterator(tree_node *, tree_node *top=0);
+ leaf_iterator(const sibling_iterator&);
+ leaf_iterator(const iterator_base&);
+
+ bool operator==(const leaf_iterator&) const;
+ bool operator!=(const leaf_iterator&) const;
+ leaf_iterator& operator++();
+ leaf_iterator& operator--();
+ leaf_iterator operator++(int);
+ leaf_iterator operator--(int);
+ leaf_iterator& operator+=(unsigned int);
+ leaf_iterator& operator-=(unsigned int);
+ private:
+ tree_node *top_node;
+ };
+
+ /// Return iterator to the beginning of the tree.
+ inline pre_order_iterator begin() const;
+ /// Return iterator to the end of the tree.
+ inline pre_order_iterator end() const;
+ /// Return post-order iterator to the beginning of the tree.
+ post_order_iterator begin_post() const;
+ /// Return post-order end iterator of the tree.
+ post_order_iterator end_post() const;
+ /// Return fixed-depth iterator to the first node at a given depth from the given iterator.
+ fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int) const;
+ /// Return fixed-depth end iterator.
+ fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const;
+ /// Return breadth-first iterator to the first node at a given depth.
+ breadth_first_queued_iterator begin_breadth_first() const;
+ /// Return breadth-first end iterator.
+ breadth_first_queued_iterator end_breadth_first() const;
+ /// Return sibling iterator to the first child of given node.
+ sibling_iterator begin(const iterator_base&) const;
+ /// Return sibling end iterator for children of given node.
+ sibling_iterator end(const iterator_base&) const;
+ /// Return leaf iterator to the first leaf of the tree.
+ leaf_iterator begin_leaf() const;
+ /// Return leaf end iterator for entire tree.
+ leaf_iterator end_leaf() const;
+ /// Return leaf iterator to the first leaf of the subtree at the given node.
+ leaf_iterator begin_leaf(const iterator_base& top) const;
+ /// Return leaf end iterator for the subtree at the given node.
+ leaf_iterator end_leaf(const iterator_base& top) const;
+
+ /// Return iterator to the parent of a node.
+ template<typename iter> static iter parent(iter);
+ /// Return iterator to the previous sibling of a node.
+ template<typename iter> iter previous_sibling(iter) const;
+ /// Return iterator to the next sibling of a node.
+ template<typename iter> iter next_sibling(iter) const;
+ /// Return iterator to the next node at a given depth.
+ template<typename iter> iter next_at_same_depth(iter) const;
+
+ /// Erase all nodes of the tree.
+ void clear();
+ /// Erase element at position pointed to by iterator, return incremented iterator.
+ template<typename iter> iter erase(iter);
+ /// Erase all children of the node pointed to by iterator.
+ void erase_children(const iterator_base&);
+
+ /// Insert empty node as last/first child of node pointed to by position.
+ template<typename iter> iter append_child(iter position);
+ template<typename iter> iter prepend_child(iter position);
+ /// Insert node as last/first child of node pointed to by position.
+ template<typename iter> iter append_child(iter position, const T& x);
+ template<typename iter> iter prepend_child(iter position, const T& x);
+ /// Append the node (plus its children) at other_position as last/first child of position.
+ template<typename iter> iter append_child(iter position, iter other_position);
+ template<typename iter> iter prepend_child(iter position, iter other_position);
+ /// Append the nodes in the from-to range (plus their children) as last/first children of position.
+ template<typename iter> iter append_children(iter position, sibling_iterator from, sibling_iterator to);
+ template<typename iter> iter prepend_children(iter position, sibling_iterator from, sibling_iterator to);
+
+ /// Short-hand to insert topmost node in otherwise empty tree.
+ pre_order_iterator set_head(const T& x);
+ /// Insert node as previous sibling of node pointed to by position.
+ template<typename iter> iter insert(iter position, const T& x);
+ /// Specialisation of previous member.
+ sibling_iterator insert(sibling_iterator position, const T& x);
+ /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position.
+ template<typename iter> iter insert_subtree(iter position, const iterator_base& subtree);
+ /// Insert node as next sibling of node pointed to by position.
+ template<typename iter> iter insert_after(iter position, const T& x);
+ /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position.
+ template<typename iter> iter insert_subtree_after(iter position, const iterator_base& subtree);
+
+ /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid.
+ template<typename iter> iter replace(iter position, const T& x);
+ /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above.
+ template<typename iter> iter replace(iter position, const iterator_base& from);
+ /// Replace string of siblings (plus their children) with copy of a new string (with children); see above
+ sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end,
+ sibling_iterator new_begin, sibling_iterator new_end);
+
+ /// Move all children of node at 'position' to be siblings, returns position.
+ template<typename iter> iter flatten(iter position);
+ /// Move nodes in range to be children of 'position'.
+ template<typename iter> iter reparent(iter position, sibling_iterator begin, sibling_iterator end);
+ /// Move all child nodes of 'from' to be children of 'position'.
+ template<typename iter> iter reparent(iter position, iter from);
+
+ /// Replace node with a new node, making the old node a child of the new node.
+ template<typename iter> iter wrap(iter position, const T& x);
+
+ /// Move 'source' node (plus its children) to become the next sibling of 'target'.
+ template<typename iter> iter move_after(iter target, iter source);
+ /// Move 'source' node (plus its children) to become the previous sibling of 'target'.
+ template<typename iter> iter move_before(iter target, iter source);
+ sibling_iterator move_before(sibling_iterator target, sibling_iterator source);
+ /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target').
+ template<typename iter> iter move_ontop(iter target, iter source);
+
+ /// Merge with other tree, creating new branches and leaves only if they are not already present.
+ void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator,
+ bool duplicate_leaves=false);
+ /// Sort (std::sort only moves values of nodes, this one moves children as well).
+ void sort(sibling_iterator from, sibling_iterator to, bool deep=false);
+ template<class StrictWeakOrdering>
+ void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false);
+ /// Compare two ranges of nodes (compares nodes as well as tree structure).
+ template<typename iter>
+ bool equal(const iter& one, const iter& two, const iter& three) const;
+ template<typename iter, class BinaryPredicate>
+ bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const;
+ template<typename iter>
+ bool equal_subtree(const iter& one, const iter& two) const;
+ template<typename iter, class BinaryPredicate>
+ bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const;
+ /// Extract a new tree formed by the range of siblings plus all their children.
+ tree subtree(sibling_iterator from, sibling_iterator to) const;
+ void subtree(tree&, sibling_iterator from, sibling_iterator to) const;
+ /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present).
+ void swap(sibling_iterator it);
+ /// Exchange two nodes (plus subtrees)
+ void swap(iterator, iterator);
+
+ /// Count the total number of nodes.
+ size_t size() const;
+ /// Count the total number of nodes below the indicated node (plus one).
+ size_t size(const iterator_base&) const;
+ /// Check if tree is empty.
+ bool empty() const;
+ /// Compute the depth to the root or to a fixed other iterator.
+ static int depth(const iterator_base&);
+ static int depth(const iterator_base&, const iterator_base&);
+ /// Determine the maximal depth of the tree. An empty tree has max_depth=-1.
+ int max_depth() const;
+ /// Determine the maximal depth of the tree with top node at the given position.
+ int max_depth(const iterator_base&) const;
+ /// Count the number of children of node at position.
+ static unsigned int number_of_children(const iterator_base&);
+ /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1.
+ unsigned int number_of_siblings(const iterator_base&) const;
+ /// Determine whether node at position is in the subtrees with root in the range.
+ bool is_in_subtree(const iterator_base& position, const iterator_base& begin,
+ const iterator_base& end) const;
+ /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node.
+ bool is_valid(const iterator_base&) const;
+ /// Find the lowest common ancestor of two nodes, that is, the deepest node such that
+ /// both nodes are descendants of it.
+ iterator lowest_common_ancestor(const iterator_base&, const iterator_base &) const;
+
+ /// Determine the index of a node in the range of siblings to which it belongs.
+ unsigned int index(sibling_iterator it) const;
+ /// Inverse of 'index': return the n-th child of the node at position.
+ static sibling_iterator child(const iterator_base& position, unsigned int);
+ /// Return iterator to the sibling indicated by index
+ sibling_iterator sibling(const iterator_base& position, unsigned int);
+
+ /// For debugging only: verify internal consistency by inspecting all pointers in the tree
+ /// (which will also trigger a valgrind error in case something got corrupted).
+ void debug_verify_consistency() const;
+
+ /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?)
+ class iterator_base_less {
+ public:
+ bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one,
+ const typename tree<T, tree_node_allocator>::iterator_base& two) const
+ {
+ return one.node < two.node;
+ }
+ };
+ tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid
+ private:
+ tree_node_allocator alloc_;
+ void head_initialise_();
+ void copy_(const tree<T, tree_node_allocator>& other);
+
+ /// Comparator class for two nodes of a tree (used for sorting and searching).
+ template<class StrictWeakOrdering>
+ class compare_nodes {
+ public:
+ compare_nodes(StrictWeakOrdering comp) : comp_(comp) {};
+
+ bool operator()(const tree_node *a, const tree_node *b)
+ {
+ return comp_(a->data, b->data);
+ }
+ private:
+ StrictWeakOrdering comp_;
+ };
+};
+
+//template <class T, class tree_node_allocator>
+//class iterator_base_less {
+// public:
+// bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one,
+// const typename tree<T, tree_node_allocator>::iterator_base& two) const
+// {
+// txtout << "operatorclass<" << one.node < two.node << std::endl;
+// return one.node < two.node;
+// }
+//};
+
+// template <class T, class tree_node_allocator>
+// bool operator<(const typename tree<T, tree_node_allocator>::iterator& one,
+// const typename tree<T, tree_node_allocator>::iterator& two)
+// {
+// txtout << "operator< " << one.node < two.node << std::endl;
+// if(one.node < two.node) return true;
+// return false;
+// }
+//
+// template <class T, class tree_node_allocator>
+// bool operator==(const typename tree<T, tree_node_allocator>::iterator& one,
+// const typename tree<T, tree_node_allocator>::iterator& two)
+// {
+// txtout << "operator== " << one.node == two.node << std::endl;
+// if(one.node == two.node) return true;
+// return false;
+// }
+//
+// template <class T, class tree_node_allocator>
+// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& one,
+// const typename tree<T, tree_node_allocator>::iterator_base& two)
+// {
+// txtout << "operator> " << one.node < two.node << std::endl;
+// if(one.node > two.node) return true;
+// return false;
+// }
+
+
+
+// Tree
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::tree()
+ {
+ head_initialise_();
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::tree(const T& x)
+ {
+ head_initialise_();
+ set_head(x);
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::tree(const iterator_base& other)
+ {
+ head_initialise_();
+ set_head((*other));
+ replace(begin(), other);
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::~tree()
+ {
+ clear();
+ alloc_.destroy(head);
+ alloc_.destroy(feet);
+ alloc_.deallocate(head,1);
+ alloc_.deallocate(feet,1);
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::head_initialise_()
+ {
+ head = alloc_.allocate(1,0); // MSVC does not have default second argument
+ feet = alloc_.allocate(1,0);
+ alloc_.construct(head, tree_node_<T>());
+ alloc_.construct(feet, tree_node_<T>());
+
+ head->parent=0;
+ head->first_child=0;
+ head->last_child=0;
+ head->prev_sibling=0; //head;
+ head->next_sibling=feet; //head;
+
+ feet->parent=0;
+ feet->first_child=0;
+ feet->last_child=0;
+ feet->prev_sibling=head;
+ feet->next_sibling=0;
+ }
+
+template <class T, class tree_node_allocator>
+tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(const tree<T, tree_node_allocator>& other)
+ {
+ if(this != &other)
+ copy_(other);
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator>& other)
+ {
+ head_initialise_();
+ copy_(other);
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::copy_(const tree<T, tree_node_allocator>& other)
+ {
+ clear();
+ pre_order_iterator it=other.begin(), to=begin();
+ while(it!=other.end()) {
+ to=insert(to, (*it));
+ it.skip_children();
+ ++it;
+ }
+ to=begin();
+ it=other.begin();
+ while(it!=other.end()) {
+ to=replace(to, it);
+ to.skip_children();
+ it.skip_children();
+ ++to;
+ ++it;
+ }
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::clear()
+ {
+ if(head)
+ while(head->next_sibling!=feet)
+ erase(pre_order_iterator(head->next_sibling));
+ }
+
+template<class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::erase_children(const iterator_base& it)
+ {
+// std::cout << "erase_children " << it.node << std::endl;
+ if(it.node==0) return;
+
+ tree_node *cur=it.node->first_child;
+ tree_node *prev=0;
+
+ while(cur!=0) {
+ prev=cur;
+ cur=cur->next_sibling;
+ erase_children(pre_order_iterator(prev));
+// kp::destructor(&prev->data);
+ alloc_.destroy(prev);
+ alloc_.deallocate(prev,1);
+ }
+ it.node->first_child=0;
+ it.node->last_child=0;
+// std::cout << "exit" << std::endl;
+ }
+
+template<class T, class tree_node_allocator>
+template<class iter>
+iter tree<T, tree_node_allocator>::erase(iter it)
+ {
+ tree_node *cur=it.node;
+ assert(cur!=head);
+ iter ret=it;
+ ret.skip_children();
+ ++ret;
+ erase_children(it);
+ if(cur->prev_sibling==0) {
+ cur->parent->first_child=cur->next_sibling;
+ }
+ else {
+ cur->prev_sibling->next_sibling=cur->next_sibling;
+ }
+ if(cur->next_sibling==0) {
+ cur->parent->last_child=cur->prev_sibling;
+ }
+ else {
+ cur->next_sibling->prev_sibling=cur->prev_sibling;
+ }
+
+// kp::destructor(&cur->data);
+ alloc_.destroy(cur);
+ alloc_.deallocate(cur,1);
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::begin() const
+ {
+ return pre_order_iterator(head->next_sibling);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::end() const
+ {
+ return pre_order_iterator(feet);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::begin_breadth_first() const
+ {
+ return breadth_first_queued_iterator(head->next_sibling);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::end_breadth_first() const
+ {
+ return breadth_first_queued_iterator();
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::begin_post() const
+ {
+ tree_node *tmp=head->next_sibling;
+ if(tmp!=feet) {
+ while(tmp->first_child)
+ tmp=tmp->first_child;
+ }
+ return post_order_iterator(tmp);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::end_post() const
+ {
+ return post_order_iterator(feet);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::begin_fixed(const iterator_base& pos, unsigned int dp) const
+ {
+ typename tree<T, tree_node_allocator>::fixed_depth_iterator ret;
+ ret.top_node=pos.node;
+
+ tree_node *tmp=pos.node;
+ unsigned int curdepth=0;
+ while(curdepth<dp) { // go down one level
+ while(tmp->first_child==0) {
+ if(tmp->next_sibling==0) {
+ // try to walk up and then right again
+ do {
+ if(tmp==ret.top_node)
+ throw std::range_error("tree: begin_fixed out of range");
+ tmp=tmp->parent;
+ if(tmp==0)
+ throw std::range_error("tree: begin_fixed out of range");
+ --curdepth;
+ } while(tmp->next_sibling==0);
+ }
+ tmp=tmp->next_sibling;
+ }
+ tmp=tmp->first_child;
+ ++curdepth;
+ }
+
+ ret.node=tmp;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::end_fixed(const iterator_base& pos, unsigned int dp) const
+ {
+ assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround
+ tree_node *tmp=pos.node;
+ unsigned int curdepth=1;
+ while(curdepth<dp) { // go down one level
+ while(tmp->first_child==0) {
+ tmp=tmp->next_sibling;
+ if(tmp==0)
+ throw std::range_error("tree: end_fixed out of range");
+ }
+ tmp=tmp->first_child;
+ ++curdepth;
+ }
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::begin(const iterator_base& pos) const
+ {
+ assert(pos.node!=0);
+ if(pos.node->first_child==0) {
+ return end(pos);
+ }
+ return pos.node->first_child;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::end(const iterator_base& pos) const
+ {
+ sibling_iterator ret(0);
+ ret.parent_=pos.node;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf() const
+ {
+ tree_node *tmp=head->next_sibling;
+ if(tmp!=feet) {
+ while(tmp->first_child)
+ tmp=tmp->first_child;
+ }
+ return leaf_iterator(tmp);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf() const
+ {
+ return leaf_iterator(feet);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf(const iterator_base& top) const
+ {
+ tree_node *tmp=top.node;
+ while(tmp->first_child)
+ tmp=tmp->first_child;
+ return leaf_iterator(tmp, top.node);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf(const iterator_base& top) const
+ {
+ return leaf_iterator(top.node, top.node);
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::parent(iter position)
+ {
+ assert(position.node!=0);
+ return iter(position.node->parent);
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::previous_sibling(iter position) const
+ {
+ assert(position.node!=0);
+ iter ret(position);
+ ret.node=position.node->prev_sibling;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::next_sibling(iter position) const
+ {
+ assert(position.node!=0);
+ iter ret(position);
+ ret.node=position.node->next_sibling;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const
+ {
+ // We make use of a temporary fixed_depth iterator to implement this.
+
+ typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp(position.node);
+
+ ++tmp;
+ return iter(tmp);
+
+// assert(position.node!=0);
+// iter ret(position);
+//
+// if(position.node->next_sibling) {
+// ret.node=position.node->next_sibling;
+// }
+// else {
+// int relative_depth=0;
+// upper:
+// do {
+// ret.node=ret.node->parent;
+// if(ret.node==0) return ret;
+// --relative_depth;
+// } while(ret.node->next_sibling==0);
+// lower:
+// ret.node=ret.node->next_sibling;
+// while(ret.node->first_child==0) {
+// if(ret.node->next_sibling==0)
+// goto upper;
+// ret.node=ret.node->next_sibling;
+// if(ret.node==0) return ret;
+// }
+// while(relative_depth<0 && ret.node->first_child!=0) {
+// ret.node=ret.node->first_child;
+// ++relative_depth;
+// }
+// if(relative_depth<0) {
+// if(ret.node->next_sibling==0) goto upper;
+// else goto lower;
+// }
+// }
+// return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::append_child(iter position)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ tree_node *tmp=alloc_.allocate(1,0);
+ alloc_.construct(tmp, tree_node_<T>());
+// kp::constructor(&tmp->data);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node;
+ if(position.node->last_child!=0) {
+ position.node->last_child->next_sibling=tmp;
+ }
+ else {
+ position.node->first_child=tmp;
+ }
+ tmp->prev_sibling=position.node->last_child;
+ position.node->last_child=tmp;
+ tmp->next_sibling=0;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::prepend_child(iter position)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ tree_node *tmp=alloc_.allocate(1,0);
+ alloc_.construct(tmp, tree_node_<T>());
+// kp::constructor(&tmp->data);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node;
+ if(position.node->first_child!=0) {
+ position.node->first_child->prev_sibling=tmp;
+ }
+ else {
+ position.node->last_child=tmp;
+ }
+ tmp->next_sibling=position.node->first_child;
+ position.node->prev_child=tmp;
+ tmp->prev_sibling=0;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::append_child(iter position, const T& x)
+ {
+ // If your program fails here you probably used 'append_child' to add the top
+ // node to an empty tree. From version 1.45 the top element should be added
+ // using 'insert'. See the documentation for further information, and sorry about
+ // the API change.
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, x);
+// kp::constructor(&tmp->data, x);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node;
+ if(position.node->last_child!=0) {
+ position.node->last_child->next_sibling=tmp;
+ }
+ else {
+ position.node->first_child=tmp;
+ }
+ tmp->prev_sibling=position.node->last_child;
+ position.node->last_child=tmp;
+ tmp->next_sibling=0;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::prepend_child(iter position, const T& x)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, x);
+// kp::constructor(&tmp->data, x);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node;
+ if(position.node->first_child!=0) {
+ position.node->first_child->prev_sibling=tmp;
+ }
+ else {
+ position.node->last_child=tmp;
+ }
+ tmp->next_sibling=position.node->first_child;
+ position.node->first_child=tmp;
+ tmp->prev_sibling=0;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::append_child(iter position, iter other)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ sibling_iterator aargh=append_child(position, value_type());
+ return replace(aargh, other);
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ sibling_iterator aargh=prepend_child(position, value_type());
+ return replace(aargh, other);
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::append_children(iter position, sibling_iterator from, sibling_iterator to)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ iter ret=from;
+
+ while(from!=to) {
+ insert_subtree(position.end(), from);
+ ++from;
+ }
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::prepend_children(iter position, sibling_iterator from, sibling_iterator to)
+ {
+ assert(position.node!=head);
+ assert(position.node!=feet);
+ assert(position.node);
+
+ iter ret=from;
+
+ while(from!=to) {
+ insert_subtree(position.begin(), from);
+ ++from;
+ }
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(const T& x)
+ {
+ assert(head->next_sibling==feet);
+ return insert(iterator(feet), x);
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::insert(iter position, const T& x)
+ {
+ if(position.node==0) {
+ position.node=feet; // Backward compatibility: when calling insert on a null node,
+ // insert before the feet.
+ }
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, x);
+// kp::constructor(&tmp->data, x);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node->parent;
+ tmp->next_sibling=position.node;
+ tmp->prev_sibling=position.node->prev_sibling;
+ position.node->prev_sibling=tmp;
+
+ if(tmp->prev_sibling==0) {
+ if(tmp->parent) // when inserting nodes at the head, there is no parent
+ tmp->parent->first_child=tmp;
+ }
+ else
+ tmp->prev_sibling->next_sibling=tmp;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, const T& x)
+ {
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, x);
+// kp::constructor(&tmp->data, x);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->next_sibling=position.node;
+ if(position.node==0) { // iterator points to end of a subtree
+ tmp->parent=position.parent_;
+ tmp->prev_sibling=position.range_last();
+ tmp->parent->last_child=tmp;
+ }
+ else {
+ tmp->parent=position.node->parent;
+ tmp->prev_sibling=position.node->prev_sibling;
+ position.node->prev_sibling=tmp;
+ }
+
+ if(tmp->prev_sibling==0) {
+ if(tmp->parent) // when inserting nodes at the head, there is no parent
+ tmp->parent->first_child=tmp;
+ }
+ else
+ tmp->prev_sibling->next_sibling=tmp;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::insert_after(iter position, const T& x)
+ {
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, x);
+// kp::constructor(&tmp->data, x);
+ tmp->first_child=0;
+ tmp->last_child=0;
+
+ tmp->parent=position.node->parent;
+ tmp->prev_sibling=position.node;
+ tmp->next_sibling=position.node->next_sibling;
+ position.node->next_sibling=tmp;
+
+ if(tmp->next_sibling==0) {
+ if(tmp->parent) // when inserting nodes at the head, there is no parent
+ tmp->parent->last_child=tmp;
+ }
+ else {
+ tmp->next_sibling->prev_sibling=tmp;
+ }
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::insert_subtree(iter position, const iterator_base& subtree)
+ {
+ // insert dummy
+ iter it=insert(position, value_type());
+ // replace dummy with subtree
+ return replace(it, subtree);
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const iterator_base& subtree)
+ {
+ // insert dummy
+ iter it=insert_after(position, value_type());
+ // replace dummy with subtree
+ return replace(it, subtree);
+ }
+
+// template <class T, class tree_node_allocator>
+// template <class iter>
+// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, iter subtree)
+// {
+// // insert dummy
+// iter it(insert(position, value_type()));
+// // replace dummy with subtree
+// return replace(it, subtree);
+// }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::replace(iter position, const T& x)
+ {
+// kp::destructor(&position.node->data);
+// kp::constructor(&position.node->data, x);
+ position.node->data=x;
+// alloc_.destroy(position.node);
+// alloc_.construct(position.node, x);
+ return position;
+ }
+
+template <class T, class tree_node_allocator>
+template <class iter>
+iter tree<T, tree_node_allocator>::replace(iter position, const iterator_base& from)
+ {
+ assert(position.node!=head);
+ tree_node *current_from=from.node;
+ tree_node *start_from=from.node;
+ tree_node *current_to =position.node;
+
+ // replace the node at position with head of the replacement tree at from
+// std::cout << "warning!" << position.node << std::endl;
+ erase_children(position);
+// std::cout << "no warning!" << std::endl;
+ tree_node* tmp = alloc_.allocate(1,0);
+ alloc_.construct(tmp, (*from));
+// kp::constructor(&tmp->data, (*from));
+ tmp->first_child=0;
+ tmp->last_child=0;
+ if(current_to->prev_sibling==0) {
+ if(current_to->parent!=0)
+ current_to->parent->first_child=tmp;
+ }
+ else {
+ current_to->prev_sibling->next_sibling=tmp;
+ }
+ tmp->prev_sibling=current_to->prev_sibling;
+ if(current_to->next_sibling==0) {
+ if(current_to->parent!=0)
+ current_to->parent->last_child=tmp;
+ }
+ else {
+ current_to->next_sibling->prev_sibling=tmp;
+ }
+ tmp->next_sibling=current_to->next_sibling;
+ tmp->parent=current_to->parent;
+// kp::destructor(¤t_to->data);
+ alloc_.destroy(current_to);
+ alloc_.deallocate(current_to,1);
+ current_to=tmp;
+
+ // only at this stage can we fix 'last'
+ tree_node *last=from.node->next_sibling;
+
+ pre_order_iterator toit=tmp;
+ // copy all children
+ do {
+ assert(current_from!=0);
+ if(current_from->first_child != 0) {
+ current_from=current_from->first_child;
+ toit=append_child(toit, current_from->data);
+ }
+ else {
+ while(current_from->next_sibling==0 && current_from!=start_from) {
+ current_from=current_from->parent;
+ toit=parent(toit);
+ assert(current_from!=0);
+ }
+ current_from=current_from->next_sibling;
+ if(current_from!=last) {
+ toit=append_child(parent(toit), current_from->data);
+ }
+ }
+ } while(current_from!=last);
+
+ return current_to;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::replace(
+ sibling_iterator orig_begin,
+ sibling_iterator orig_end,
+ sibling_iterator new_begin,
+ sibling_iterator new_end)
+ {
+ tree_node *orig_first=orig_begin.node;
+ tree_node *new_first=new_begin.node;
+ tree_node *orig_last=orig_first;
+ while((++orig_begin)!=orig_end)
+ orig_last=orig_last->next_sibling;
+ tree_node *new_last=new_first;
+ while((++new_begin)!=new_end)
+ new_last=new_last->next_sibling;
+
+ // insert all siblings in new_first..new_last before orig_first
+ bool first=true;
+ pre_order_iterator ret;
+ while(1==1) {
+ pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first));
+ if(first) {
+ ret=tt;
+ first=false;
+ }
+ if(new_first==new_last)
+ break;
+ new_first=new_first->next_sibling;
+ }
+
+ // erase old range of siblings
+ bool last=false;
+ tree_node *next=orig_first;
+ while(1==1) {
+ if(next==orig_last)
+ last=true;
+ next=next->next_sibling;
+ erase((pre_order_iterator)orig_first);
+ if(last)
+ break;
+ orig_first=next;
+ }
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::flatten(iter position)
+ {
+ if(position.node->first_child==0)
+ return position;
+
+ tree_node *tmp=position.node->first_child;
+ while(tmp) {
+ tmp->parent=position.node->parent;
+ tmp=tmp->next_sibling;
+ }
+ if(position.node->next_sibling) {
+ position.node->last_child->next_sibling=position.node->next_sibling;
+ position.node->next_sibling->prev_sibling=position.node->last_child;
+ }
+ else {
+ position.node->parent->last_child=position.node->last_child;
+ }
+ position.node->next_sibling=position.node->first_child;
+ position.node->next_sibling->prev_sibling=position.node;
+ position.node->first_child=0;
+ position.node->last_child=0;
+
+ return position;
+ }
+
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+iter tree<T, tree_node_allocator>::reparent(iter position, sibling_iterator begin, sibling_iterator end)
+ {
+ tree_node *first=begin.node;
+ tree_node *last=first;
+
+ assert(first!=position.node);
+
+ if(begin==end) return begin;
+ // determine last node
+ while((++begin)!=end) {
+ last=last->next_sibling;
+ }
+ // move subtree
+ if(first->prev_sibling==0) {
+ first->parent->first_child=last->next_sibling;
+ }
+ else {
+ first->prev_sibling->next_sibling=last->next_sibling;
+ }
+ if(last->next_sibling==0) {
+ last->parent->last_child=first->prev_sibling;
+ }
+ else {
+ last->next_sibling->prev_sibling=first->prev_sibling;
+ }
+ if(position.node->first_child==0) {
+ position.node->first_child=first;
+ position.node->last_child=last;
+ first->prev_sibling=0;
+ }
+ else {
+ position.node->last_child->next_sibling=first;
+ first->prev_sibling=position.node->last_child;
+ position.node->last_child=last;
+ }
+ last->next_sibling=0;
+
+ tree_node *pos=first;
+ for(;;) {
+ pos->parent=position.node;
+ if(pos==last) break;
+ pos=pos->next_sibling;
+ }
+
+ return first;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter> iter tree<T, tree_node_allocator>::reparent(iter position, iter from)
+ {
+ if(from.node->first_child==0) return position;
+ return reparent(position, from.node->first_child, end(from));
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter position, const T& x)
+ {
+ assert(position.node!=0);
+ sibling_iterator fr=position, to=position;
+ ++to;
+ iter ret = insert(position, x);
+ reparent(ret, fr, to);
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter> iter tree<T, tree_node_allocator>::move_after(iter target, iter source)
+ {
+ tree_node *dst=target.node;
+ tree_node *src=source.node;
+ assert(dst);
+ assert(src);
+
+ if(dst==src) return source;
+ if(dst->next_sibling)
+ if(dst->next_sibling==src) // already in the right spot
+ return source;
+
+ // take src out of the tree
+ if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling;
+ else src->parent->first_child=src->next_sibling;
+ if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling;
+ else src->parent->last_child=src->prev_sibling;
+
+ // connect it to the new point
+ if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src;
+ else dst->parent->last_child=src;
+ src->next_sibling=dst->next_sibling;
+ dst->next_sibling=src;
+ src->prev_sibling=dst;
+ src->parent=dst->parent;
+ return src;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter> iter tree<T, tree_node_allocator>::move_before(iter target, iter source)
+ {
+ tree_node *dst=target.node;
+ tree_node *src=source.node;
+ assert(dst);
+ assert(src);
+
+ if(dst==src) return source;
+ if(dst->prev_sibling)
+ if(dst->prev_sibling==src) // already in the right spot
+ return source;
+
+ // take src out of the tree
+ if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling;
+ else src->parent->first_child=src->next_sibling;
+ if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling;
+ else src->parent->last_child=src->prev_sibling;
+
+ // connect it to the new point
+ if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src;
+ else dst->parent->first_child=src;
+ src->prev_sibling=dst->prev_sibling;
+ dst->prev_sibling=src;
+ src->next_sibling=dst;
+ src->parent=dst->parent;
+ return src;
+ }
+
+// specialisation for sibling_iterators
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::move_before(sibling_iterator target,
+ sibling_iterator source)
+ {
+ tree_node *dst=target.node;
+ tree_node *src=source.node;
+ tree_node *dst_prev_sibling;
+ if(dst==0) { // must then be an end iterator
+ dst_prev_sibling=target.parent_->last_child;
+ assert(dst_prev_sibling);
+ }
+ else dst_prev_sibling=dst->prev_sibling;
+ assert(src);
+
+ if(dst==src) return source;
+ if(dst_prev_sibling)
+ if(dst_prev_sibling==src) // already in the right spot
+ return source;
+
+ // take src out of the tree
+ if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling;
+ else src->parent->first_child=src->next_sibling;
+ if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling;
+ else src->parent->last_child=src->prev_sibling;
+
+ // connect it to the new point
+ if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src;
+ else target.parent_->first_child=src;
+ src->prev_sibling=dst_prev_sibling;
+ if(dst) {
+ dst->prev_sibling=src;
+ src->parent=dst->parent;
+ }
+ src->next_sibling=dst;
+ return src;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter> iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source)
+ {
+ tree_node *dst=target.node;
+ tree_node *src=source.node;
+ assert(dst);
+ assert(src);
+
+ if(dst==src) return source;
+
+// if(dst==src->prev_sibling) {
+//
+// }
+
+ // remember connection points
+ tree_node *b_prev_sibling=dst->prev_sibling;
+ tree_node *b_next_sibling=dst->next_sibling;
+ tree_node *b_parent=dst->parent;
+
+ // remove target
+ erase(target);
+
+ // take src out of the tree
+ if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling;
+ else src->parent->first_child=src->next_sibling;
+ if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling;
+ else src->parent->last_child=src->prev_sibling;
+
+ // connect it to the new point
+ if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src;
+ else b_parent->first_child=src;
+ if(b_next_sibling!=0) b_next_sibling->prev_sibling=src;
+ else b_parent->last_child=src;
+ src->prev_sibling=b_prev_sibling;
+ src->next_sibling=b_next_sibling;
+ src->parent=b_parent;
+ return src;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::merge(sibling_iterator to1, sibling_iterator to2,
+ sibling_iterator from1, sibling_iterator from2,
+ bool duplicate_leaves)
+ {
+ sibling_iterator fnd;
+ while(from1!=from2) {
+ if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found
+ if(from1.begin()==from1.end()) { // full depth reached
+ if(duplicate_leaves)
+ append_child(parent(to1), (*from1));
+ }
+ else { // descend further
+ merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves);
+ }
+ }
+ else { // element missing
+ insert_subtree(to2, from1);
+ }
+ ++from1;
+ }
+ }
+
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, bool deep)
+ {
+ std::less<T> comp;
+ sort(from, to, comp, deep);
+ }
+
+template <class T, class tree_node_allocator>
+template <class StrictWeakOrdering>
+void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to,
+ StrictWeakOrdering comp, bool deep)
+ {
+ if(from==to) return;
+ // make list of sorted nodes
+ // CHECK: if multiset stores equivalent nodes in the order in which they
+ // are inserted, then this routine should be called 'stable_sort'.
+ std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> > nodes(comp);
+ sibling_iterator it=from, it2=to;
+ while(it != to) {
+ nodes.insert(it.node);
+ ++it;
+ }
+ // reassemble
+ --it2;
+
+ // prev and next are the nodes before and after the sorted range
+ tree_node *prev=from.node->prev_sibling;
+ tree_node *next=it2.node->next_sibling;
+ typename std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> >::iterator nit=nodes.begin(), eit=nodes.end();
+ if(prev==0) {
+ if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent
+ (*nit)->parent->first_child=(*nit);
+ }
+ else prev->next_sibling=(*nit);
+
+ --eit;
+ while(nit!=eit) {
+ (*nit)->prev_sibling=prev;
+ if(prev)
+ prev->next_sibling=(*nit);
+ prev=(*nit);
+ ++nit;
+ }
+ // prev now points to the last-but-one node in the sorted range
+ if(prev)
+ prev->next_sibling=(*eit);
+
+ // eit points to the last node in the sorted range.
+ (*eit)->next_sibling=next;
+ (*eit)->prev_sibling=prev; // missed in the loop above
+ if(next==0) {
+ if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent
+ (*eit)->parent->last_child=(*eit);
+ }
+ else next->prev_sibling=(*eit);
+
+ if(deep) { // sort the children of each node too
+ sibling_iterator bcs(*nodes.begin());
+ sibling_iterator ecs(*eit);
+ ++ecs;
+ while(bcs!=ecs) {
+ sort(begin(bcs), end(bcs), comp, deep);
+ ++bcs;
+ }
+ }
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_) const
+ {
+ std::equal_to<T> comp;
+ return equal(one_, two, three_, comp);
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter>
+bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_) const
+ {
+ std::equal_to<T> comp;
+ return equal_subtree(one_, two_, comp);
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter, class BinaryPredicate>
+bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const
+ {
+ pre_order_iterator one(one_), three(three_);
+
+// if(one==two && is_valid(three) && three.number_of_children()!=0)
+// return false;
+ while(one!=two && is_valid(three)) {
+ if(!fun(*one,*three))
+ return false;
+ if(one.number_of_children()!=three.number_of_children())
+ return false;
+ ++one;
+ ++three;
+ }
+ return true;
+ }
+
+template <class T, class tree_node_allocator>
+template <typename iter, class BinaryPredicate>
+bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const
+ {
+ pre_order_iterator one(one_), two(two_);
+
+ if(!fun(*one,*two)) return false;
+ if(number_of_children(one)!=number_of_children(two)) return false;
+ return equal(begin(one),end(one),begin(two),fun);
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator> tree<T, tree_node_allocator>::subtree(sibling_iterator from, sibling_iterator to) const
+ {
+ tree tmp;
+ tmp.set_head(value_type());
+ tmp.replace(tmp.begin(), tmp.end(), from, to);
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const
+ {
+ tmp.set_head(value_type());
+ tmp.replace(tmp.begin(), tmp.end(), from, to);
+ }
+
+template <class T, class tree_node_allocator>
+size_t tree<T, tree_node_allocator>::size() const
+ {
+ size_t i=0;
+ pre_order_iterator it=begin(), eit=end();
+ while(it!=eit) {
+ ++i;
+ ++it;
+ }
+ return i;
+ }
+
+template <class T, class tree_node_allocator>
+size_t tree<T, tree_node_allocator>::size(const iterator_base& top) const
+ {
+ size_t i=0;
+ pre_order_iterator it=top, eit=top;
+ eit.skip_children();
+ ++eit;
+ while(it!=eit) {
+ ++i;
+ ++it;
+ }
+ return i;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::empty() const
+ {
+ pre_order_iterator it=begin(), eit=end();
+ return (it==eit);
+ }
+
+template <class T, class tree_node_allocator>
+int tree<T, tree_node_allocator>::depth(const iterator_base& it)
+ {
+ tree_node* pos=it.node;
+ assert(pos!=0);
+ int ret=0;
+ while(pos->parent!=0) {
+ pos=pos->parent;
+ ++ret;
+ }
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+int tree<T, tree_node_allocator>::depth(const iterator_base& it, const iterator_base& root)
+ {
+ tree_node* pos=it.node;
+ assert(pos!=0);
+ int ret=0;
+ while(pos->parent!=0 && pos!=root.node) {
+ pos=pos->parent;
+ ++ret;
+ }
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+int tree<T, tree_node_allocator>::max_depth() const
+ {
+ int maxd=-1;
+ for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling)
+ maxd=std::max(maxd, max_depth(it));
+
+ return maxd;
+ }
+
+
+template <class T, class tree_node_allocator>
+int tree<T, tree_node_allocator>::max_depth(const iterator_base& pos) const
+ {
+ tree_node *tmp=pos.node;
+
+ if(tmp==0 || tmp==head || tmp==feet) return -1;
+
+ int curdepth=0, maxdepth=0;
+ while(true) { // try to walk the bottom of the tree
+ while(tmp->first_child==0) {
+ if(tmp==pos.node) return maxdepth;
+ if(tmp->next_sibling==0) {
+ // try to walk up and then right again
+ do {
+ tmp=tmp->parent;
+ if(tmp==0) return maxdepth;
+ --curdepth;
+ } while(tmp->next_sibling==0);
+ }
+ if(tmp==pos.node) return maxdepth;
+ tmp=tmp->next_sibling;
+ }
+ tmp=tmp->first_child;
+ ++curdepth;
+ maxdepth=std::max(curdepth, maxdepth);
+ }
+ }
+
+template <class T, class tree_node_allocator>
+unsigned int tree<T, tree_node_allocator>::number_of_children(const iterator_base& it)
+ {
+ tree_node *pos=it.node->first_child;
+ if(pos==0) return 0;
+
+ unsigned int ret=1;
+// while(pos!=it.node->last_child) {
+// ++ret;
+// pos=pos->next_sibling;
+// }
+ while((pos=pos->next_sibling))
+ ++ret;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+unsigned int tree<T, tree_node_allocator>::number_of_siblings(const iterator_base& it) const
+ {
+ tree_node *pos=it.node;
+ unsigned int ret=0;
+ // count forward
+ while(pos->next_sibling &&
+ pos->next_sibling!=head &&
+ pos->next_sibling!=feet) {
+ ++ret;
+ pos=pos->next_sibling;
+ }
+ // count backward
+ pos=it.node;
+ while(pos->prev_sibling &&
+ pos->prev_sibling!=head &&
+ pos->prev_sibling!=feet) {
+ ++ret;
+ pos=pos->prev_sibling;
+ }
+
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::swap(sibling_iterator it)
+ {
+ tree_node *nxt=it.node->next_sibling;
+ if(nxt) {
+ if(it.node->prev_sibling)
+ it.node->prev_sibling->next_sibling=nxt;
+ else
+ it.node->parent->first_child=nxt;
+ nxt->prev_sibling=it.node->prev_sibling;
+ tree_node *nxtnxt=nxt->next_sibling;
+ if(nxtnxt)
+ nxtnxt->prev_sibling=it.node;
+ else
+ it.node->parent->last_child=it.node;
+ nxt->next_sibling=it.node;
+ it.node->prev_sibling=nxt;
+ it.node->next_sibling=nxtnxt;
+ }
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::swap(iterator one, iterator two)
+ {
+ // if one and two are adjacent siblings, use the sibling swap
+ if(one.node->next_sibling==two.node) swap(one);
+ else if(two.node->next_sibling==one.node) swap(two);
+ else {
+ tree_node *nxt1=one.node->next_sibling;
+ tree_node *nxt2=two.node->next_sibling;
+ tree_node *pre1=one.node->prev_sibling;
+ tree_node *pre2=two.node->prev_sibling;
+ tree_node *par1=one.node->parent;
+ tree_node *par2=two.node->parent;
+
+ // reconnect
+ one.node->parent=par2;
+ one.node->next_sibling=nxt2;
+ if(nxt2) nxt2->prev_sibling=one.node;
+ else par2->last_child=one.node;
+ one.node->prev_sibling=pre2;
+ if(pre2) pre2->next_sibling=one.node;
+ else par2->first_child=one.node;
+
+ two.node->parent=par1;
+ two.node->next_sibling=nxt1;
+ if(nxt1) nxt1->prev_sibling=two.node;
+ else par1->last_child=two.node;
+ two.node->prev_sibling=pre1;
+ if(pre1) pre1->next_sibling=two.node;
+ else par1->first_child=two.node;
+ }
+ }
+
+// template <class BinaryPredicate>
+// tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::find_subtree(
+// sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to,
+// BinaryPredicate fun) const
+// {
+// assert(1==0); // this routine is not finished yet.
+// while(from!=to) {
+// if(fun(*subfrom, *from)) {
+//
+// }
+// }
+// return to;
+// }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& begin,
+ const iterator_base& end) const
+ {
+ // FIXME: this should be optimised.
+ pre_order_iterator tmp=begin;
+ while(tmp!=end) {
+ if(tmp==it) return true;
+ ++tmp;
+ }
+ return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::is_valid(const iterator_base& it) const
+ {
+ if(it.node==0 || it.node==feet || it.node==head) return false;
+ else return true;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::lowest_common_ancestor(
+ const iterator_base& one, const iterator_base& two) const
+ {
+ std::set<iterator, iterator_base_less> parents;
+
+ // Walk up from 'one' storing all parents.
+ iterator walk=one;
+ do {
+ walk=parent(walk);
+ parents.insert(walk);
+ } while( is_valid(parent(walk)) );
+
+ // Walk up from 'two' until we encounter a node in parents.
+ walk=two;
+ do {
+ walk=parent(walk);
+ if(parents.find(walk) != parents.end()) break;
+ } while( is_valid(parent(walk)) );
+
+ return walk;
+ }
+
+template <class T, class tree_node_allocator>
+unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const
+ {
+ unsigned int ind=0;
+ if(it.node->parent==0) {
+ while(it.node->prev_sibling!=head) {
+ it.node=it.node->prev_sibling;
+ ++ind;
+ }
+ }
+ else {
+ while(it.node->prev_sibling!=0) {
+ it.node=it.node->prev_sibling;
+ ++ind;
+ }
+ }
+ return ind;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling(const iterator_base& it, unsigned int num)
+ {
+ tree_node *tmp;
+ if(it.node->parent==0) {
+ tmp=head->next_sibling;
+ while(num) {
+ tmp = tmp->next_sibling;
+ --num;
+ }
+ }
+ else {
+ tmp=it.node->parent->first_child;
+ while(num) {
+ assert(tmp!=0);
+ tmp = tmp->next_sibling;
+ --num;
+ }
+ }
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::debug_verify_consistency() const
+ {
+ iterator it=begin();
+ while(it!=end()) {
+ if(it.node->parent!=0) {
+ if(it.node->prev_sibling==0)
+ assert(it.node->parent->first_child==it.node);
+ else
+ assert(it.node->prev_sibling->next_sibling==it.node);
+ if(it.node->next_sibling==0)
+ assert(it.node->parent->last_child==it.node);
+ else
+ assert(it.node->next_sibling->prev_sibling==it.node);
+ }
+ ++it;
+ }
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::child(const iterator_base& it, unsigned int num)
+ {
+ tree_node *tmp=it.node->first_child;
+ while(num--) {
+ assert(tmp!=0);
+ tmp=tmp->next_sibling;
+ }
+ return tmp;
+ }
+
+
+
+
+// Iterator base
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::iterator_base::iterator_base()
+ : node(0), skip_current_children_(false)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::iterator_base::iterator_base(tree_node *tn)
+ : node(tn), skip_current_children_(false)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+T& tree<T, tree_node_allocator>::iterator_base::operator*() const
+ {
+ return node->data;
+ }
+
+template <class T, class tree_node_allocator>
+T* tree<T, tree_node_allocator>::iterator_base::operator->() const
+ {
+ return &(node->data);
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::post_order_iterator::operator!=(const post_order_iterator& other) const
+ {
+ if(other.node!=this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::post_order_iterator::operator==(const post_order_iterator& other) const
+ {
+ if(other.node==this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=(const pre_order_iterator& other) const
+ {
+ if(other.node!=this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::pre_order_iterator::operator==(const pre_order_iterator& other) const
+ {
+ if(other.node==this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::sibling_iterator::operator!=(const sibling_iterator& other) const
+ {
+ if(other.node!=this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::sibling_iterator::operator==(const sibling_iterator& other) const
+ {
+ if(other.node==this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::leaf_iterator::operator!=(const leaf_iterator& other) const
+ {
+ if(other.node!=this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::leaf_iterator::operator==(const leaf_iterator& other) const
+ {
+ if(other.node==this->node && other.top_node==this->top_node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::begin() const
+ {
+ if(node->first_child==0)
+ return end();
+
+ sibling_iterator ret(node->first_child);
+ ret.parent_=this->node;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::end() const
+ {
+ sibling_iterator ret(0);
+ ret.parent_=node;
+ return ret;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::iterator_base::skip_children()
+ {
+ skip_current_children_=true;
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip)
+ {
+ skip_current_children_=skip;
+ }
+
+template <class T, class tree_node_allocator>
+unsigned int tree<T, tree_node_allocator>::iterator_base::number_of_children() const
+ {
+ tree_node *pos=node->first_child;
+ if(pos==0) return 0;
+
+ unsigned int ret=1;
+ while(pos!=node->last_child) {
+ ++ret;
+ pos=pos->next_sibling;
+ }
+ return ret;
+ }
+
+
+
+// Pre-order iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator()
+ : iterator_base(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(tree_node *tn)
+ : iterator_base(tn)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const iterator_base &other)
+ : iterator_base(other.node)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const sibling_iterator& other)
+ : iterator_base(other.node)
+ {
+ if(this->node==0) {
+ if(other.range_last()!=0)
+ this->node=other.range_last();
+ else
+ this->node=other.parent_;
+ this->skip_children();
+ ++(*this);
+ }
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator++()
+ {
+ assert(this->node!=0);
+ if(!this->skip_current_children_ && this->node->first_child != 0) {
+ this->node=this->node->first_child;
+ }
+ else {
+ this->skip_current_children_=false;
+ while(this->node->next_sibling==0) {
+ this->node=this->node->parent;
+ if(this->node==0)
+ return *this;
+ }
+ this->node=this->node->next_sibling;
+ }
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator--()
+ {
+ assert(this->node!=0);
+ if(this->node->prev_sibling) {
+ this->node=this->node->prev_sibling;
+ while(this->node->last_child)
+ this->node=this->node->last_child;
+ }
+ else {
+ this->node=this->node->parent;
+ if(this->node==0)
+ return *this;
+ }
+ return *this;
+}
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator++(int)
+ {
+ pre_order_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator--(int)
+{
+ pre_order_iterator copy = *this;
+ --(*this);
+ return copy;
+}
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num)
+ {
+ while(num>0) {
+ --(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+
+
+// Post-order iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator()
+ : iterator_base(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(tree_node *tn)
+ : iterator_base(tn)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const iterator_base &other)
+ : iterator_base(other.node)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const sibling_iterator& other)
+ : iterator_base(other.node)
+ {
+ if(this->node==0) {
+ if(other.range_last()!=0)
+ this->node=other.range_last();
+ else
+ this->node=other.parent_;
+ this->skip_children();
+ ++(*this);
+ }
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator++()
+ {
+ assert(this->node!=0);
+ if(this->node->next_sibling==0) {
+ this->node=this->node->parent;
+ this->skip_current_children_=false;
+ }
+ else {
+ this->node=this->node->next_sibling;
+ if(this->skip_current_children_) {
+ this->skip_current_children_=false;
+ }
+ else {
+ while(this->node->first_child)
+ this->node=this->node->first_child;
+ }
+ }
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator--()
+ {
+ assert(this->node!=0);
+ if(this->skip_current_children_ || this->node->last_child==0) {
+ this->skip_current_children_=false;
+ while(this->node->prev_sibling==0)
+ this->node=this->node->parent;
+ this->node=this->node->prev_sibling;
+ }
+ else {
+ this->node=this->node->last_child;
+ }
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator++(int)
+ {
+ post_order_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator--(int)
+ {
+ post_order_iterator copy = *this;
+ --(*this);
+ return copy;
+ }
+
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator-=(unsigned int num)
+ {
+ while(num>0) {
+ --(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::post_order_iterator::descend_all()
+ {
+ assert(this->node!=0);
+ while(this->node->first_child)
+ this->node=this->node->first_child;
+ }
+
+
+// Breadth-first iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator()
+ : iterator_base()
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn)
+ : iterator_base(tn)
+ {
+ traversal_queue.push(tn);
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other)
+ : iterator_base(other.node)
+ {
+ traversal_queue.push(other.node);
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const
+ {
+ if(other.node!=this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const
+ {
+ if(other.node==this->node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++()
+ {
+ assert(this->node!=0);
+
+ // Add child nodes and pop current node
+ sibling_iterator sib=this->begin();
+ while(sib!=this->end()) {
+ traversal_queue.push(sib.node);
+ ++sib;
+ }
+ traversal_queue.pop();
+ if(traversal_queue.size()>0)
+ this->node=traversal_queue.front();
+ else
+ this->node=0;
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int)
+ {
+ breadth_first_queued_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+
+
+// Fixed depth iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator()
+ : iterator_base()
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn)
+ : iterator_base(tn), top_node(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other)
+ : iterator_base(other.node), top_node(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other)
+ : iterator_base(other.node), top_node(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other)
+ : iterator_base(other.node), top_node(other.top_node)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const
+ {
+ if(other.node==this->node && other.top_node==top_node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const
+ {
+ if(other.node!=this->node || other.top_node!=top_node) return true;
+ else return false;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator++()
+ {
+ assert(this->node!=0);
+
+ if(this->node->next_sibling) {
+ this->node=this->node->next_sibling;
+ }
+ else {
+ int relative_depth=0;
+ upper:
+ do {
+ if(this->node==this->top_node) {
+ this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented
+ return *this;
+ }
+ this->node=this->node->parent;
+ if(this->node==0) return *this;
+ --relative_depth;
+ } while(this->node->next_sibling==0);
+ lower:
+ this->node=this->node->next_sibling;
+ while(this->node->first_child==0) {
+ if(this->node->next_sibling==0)
+ goto upper;
+ this->node=this->node->next_sibling;
+ if(this->node==0) return *this;
+ }
+ while(relative_depth<0 && this->node->first_child!=0) {
+ this->node=this->node->first_child;
+ ++relative_depth;
+ }
+ if(relative_depth<0) {
+ if(this->node->next_sibling==0) goto upper;
+ else goto lower;
+ }
+ }
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator--()
+ {
+ assert(this->node!=0);
+
+ if(this->node->prev_sibling) {
+ this->node=this->node->prev_sibling;
+ }
+ else {
+ int relative_depth=0;
+ upper:
+ do {
+ if(this->node==this->top_node) {
+ this->node=0;
+ return *this;
+ }
+ this->node=this->node->parent;
+ if(this->node==0) return *this;
+ --relative_depth;
+ } while(this->node->prev_sibling==0);
+ lower:
+ this->node=this->node->prev_sibling;
+ while(this->node->last_child==0) {
+ if(this->node->prev_sibling==0)
+ goto upper;
+ this->node=this->node->prev_sibling;
+ if(this->node==0) return *this;
+ }
+ while(relative_depth<0 && this->node->last_child!=0) {
+ this->node=this->node->last_child;
+ ++relative_depth;
+ }
+ if(relative_depth<0) {
+ if(this->node->prev_sibling==0) goto upper;
+ else goto lower;
+ }
+ }
+ return *this;
+
+//
+//
+// assert(this->node!=0);
+// if(this->node->prev_sibling!=0) {
+// this->node=this->node->prev_sibling;
+// assert(this->node!=0);
+// if(this->node->parent==0 && this->node->prev_sibling==0) // head element
+// this->node=0;
+// }
+// else {
+// tree_node *par=this->node->parent;
+// do {
+// par=par->prev_sibling;
+// if(par==0) { // FIXME: need to keep track of this!
+// this->node=0;
+// return *this;
+// }
+// } while(par->last_child==0);
+// this->node=par->last_child;
+// }
+// return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int)
+ {
+ fixed_depth_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int)
+ {
+ fixed_depth_iterator copy = *this;
+ --(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=(unsigned int num)
+ {
+ while(num>0) {
+ --(*this);
+ --(num);
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --(num);
+ }
+ return *this;
+ }
+
+
+// Sibling iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator()
+ : iterator_base()
+ {
+ set_parent_();
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(tree_node *tn)
+ : iterator_base(tn)
+ {
+ set_parent_();
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const iterator_base& other)
+ : iterator_base(other.node)
+ {
+ set_parent_();
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const sibling_iterator& other)
+ : iterator_base(other), parent_(other.parent_)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+void tree<T, tree_node_allocator>::sibling_iterator::set_parent_()
+ {
+ parent_=0;
+ if(this->node==0) return;
+ if(this->node->parent!=0)
+ parent_=this->node->parent;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator++()
+ {
+ if(this->node)
+ this->node=this->node->next_sibling;
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator--()
+ {
+ if(this->node) this->node=this->node->prev_sibling;
+ else {
+ assert(parent_);
+ this->node=parent_->last_child;
+ }
+ return *this;
+}
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator++(int)
+ {
+ sibling_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator--(int)
+ {
+ sibling_iterator copy = *this;
+ --(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num)
+ {
+ while(num>0) {
+ --(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_first() const
+ {
+ tree_node *tmp=parent_->first_child;
+ return tmp;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_last() const
+ {
+ return parent_->last_child;
+ }
+
+// Leaf iterator
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator()
+ : iterator_base(0), top_node(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top)
+ : iterator_base(tn), top_node(top)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const iterator_base &other)
+ : iterator_base(other.node), top_node(0)
+ {
+ }
+
+template <class T, class tree_node_allocator>
+tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const sibling_iterator& other)
+ : iterator_base(other.node), top_node(0)
+ {
+ if(this->node==0) {
+ if(other.range_last()!=0)
+ this->node=other.range_last();
+ else
+ this->node=other.parent_;
+ ++(*this);
+ }
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator++()
+ {
+ assert(this->node!=0);
+ if(this->node->first_child!=0) { // current node is no longer leaf (children got added)
+ while(this->node->first_child)
+ this->node=this->node->first_child;
+ }
+ else {
+ while(this->node->next_sibling==0) {
+ if (this->node->parent==0) return *this;
+ this->node=this->node->parent;
+ if (top_node != 0 && this->node==top_node) return *this;
+ }
+ this->node=this->node->next_sibling;
+ while(this->node->first_child)
+ this->node=this->node->first_child;
+ }
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator--()
+ {
+ assert(this->node!=0);
+ while (this->node->prev_sibling==0) {
+ if (this->node->parent==0) return *this;
+ this->node=this->node->parent;
+ if (top_node !=0 && this->node==top_node) return *this;
+ }
+ this->node=this->node->prev_sibling;
+ while(this->node->last_child)
+ this->node=this->node->last_child;
+ return *this;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator++(int)
+ {
+ leaf_iterator copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator--(int)
+ {
+ leaf_iterator copy = *this;
+ --(*this);
+ return copy;
+ }
+
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num)
+ {
+ while(num>0) {
+ ++(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+template <class T, class tree_node_allocator>
+typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num)
+ {
+ while(num>0) {
+ --(*this);
+ --num;
+ }
+ return (*this);
+ }
+
+#endif
+
+// Local variables:
+// default-tab-width: 3
+// End:
diff --git a/src/cpp/core/include/core/gwt/GwtFileHandler.hpp b/src/cpp/core/include/core/gwt/GwtFileHandler.hpp
new file mode 100644
index 0000000..4801e15
--- /dev/null
+++ b/src/cpp/core/include/core/gwt/GwtFileHandler.hpp
@@ -0,0 +1,35 @@
+/*
+ * GwtFileHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_GWT_FILE_HANDLER_HPP
+#define CORE_GWT_FILE_HANDLER_HPP
+
+#include <core/http/UriHandler.hpp>
+
+namespace core {
+namespace gwt {
+
+http::UriHandlerFunction fileHandlerFunction(
+ const std::string& wwwLocalPath,
+ const std::string& baseUri = std::string(),
+ http::UriFilterFunction mainPageFilter = http::UriFilterFunction(),
+ const std::string& initJs = std::string(),
+ bool useEmulatedStack = false);
+
+} // namespace gwt
+} // namespace core
+
+#endif // CORE_GWT_FILE_HANDLER_HPP
+
diff --git a/src/cpp/core/include/core/gwt/GwtLogHandler.hpp b/src/cpp/core/include/core/gwt/GwtLogHandler.hpp
new file mode 100644
index 0000000..570f45a
--- /dev/null
+++ b/src/cpp/core/include/core/gwt/GwtLogHandler.hpp
@@ -0,0 +1,44 @@
+/*
+ * GwtLogHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_GWT_LOG_HANDLER_HPP
+#define CORE_GWT_LOG_HANDLER_HPP
+
+#include <string>
+
+namespace core {
+
+class FilePath;
+
+namespace http {
+ class Request;
+ class Response;
+}
+
+namespace gwt {
+
+
+void initializeSymbolMaps(const core::FilePath& symbolMapsPath);
+
+void handleLogRequest(const std::string& username,
+ const http::Request& request,
+ http::Response* pResponse);
+
+} // namespace gwt
+} // namespace core
+
+#endif // CORE_GWT_LOG_HANDLER_HPP
+
+
diff --git a/src/cpp/core/include/core/gwt/GwtSymbolMaps.hpp b/src/cpp/core/include/core/gwt/GwtSymbolMaps.hpp
new file mode 100644
index 0000000..fdf48f9
--- /dev/null
+++ b/src/cpp/core/include/core/gwt/GwtSymbolMaps.hpp
@@ -0,0 +1,64 @@
+/*
+ * GwtSymbolMaps.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_GWT_SYMBOL_MAPS_HPP
+#define CORE_GWT_SYMBOL_MAPS_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace gwt {
+
+struct StackElement
+{
+ StackElement() : lineNumber(0) {}
+ std::string fileName;
+ std::string className;
+ std::string methodName;
+ int lineNumber;
+};
+
+class SymbolMaps : boost::noncopyable
+{
+public:
+ SymbolMaps();
+ virtual ~SymbolMaps();
+
+ Error initialize(const FilePath& symbolMapsPath);
+
+ std::vector<StackElement> resymbolize(const std::vector<StackElement>& stack,
+ const std::string& strongName);
+
+ StackElement resymbolize(const StackElement& se,
+ const std::string& strongName);
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+} // namespace gwt
+} // namespace core
+
+#endif // CORE_GWT_SYMBOL_MAPS_HPP
diff --git a/src/cpp/core/include/core/http/AsyncClient.hpp b/src/cpp/core/include/core/http/AsyncClient.hpp
new file mode 100644
index 0000000..af1ccba
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncClient.hpp
@@ -0,0 +1,482 @@
+/*
+ * AsyncClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_CLIENT_HPP
+#define CORE_HTTP_ASYNC_CLIENT_HPP
+
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <boost/asio/write.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/placeholders.hpp>
+#include <boost/asio/streambuf.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/asio/read_until.hpp>
+#include <boost/asio/deadline_timer.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/ResponseParser.hpp>
+#include <core/http/Socket.hpp>
+#include <core/http/SocketUtils.hpp>
+#include <core/http/ConnectionRetryProfile.hpp>
+
+// special version of unexpected exception handler which makes
+// sure to call the user's ErrorHandler
+#define CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION \
+ catch(const std::exception& e) \
+ { \
+ handleUnexpectedError(std::string("Unexpected exception: ") + \
+ e.what(), ERROR_LOCATION) ; \
+ } \
+ catch(...) \
+ { \
+ handleUnexpectedError("Unknown exception", ERROR_LOCATION); \
+ }
+
+namespace core {
+namespace http {
+
+typedef boost::function<void(const http::Response&)> ResponseHandler;
+typedef boost::function<void(const core::Error&)> ErrorHandler;
+
+
+template <typename SocketService>
+class AsyncClient :
+ public boost::enable_shared_from_this<AsyncClient<SocketService> >,
+ public Socket,
+ boost::noncopyable
+{
+public:
+ AsyncClient(boost::asio::io_service& ioService,
+ bool logToStderr = false)
+ : ioService_(ioService),
+ connectionRetryContext_(ioService),
+ logToStderr_(logToStderr)
+ {
+ }
+
+ virtual ~AsyncClient()
+ {
+ }
+
+ // populate the request before calling execute
+ http::Request& request() { return request_; }
+
+ // set (optional) connection retry profile. must do this prior
+ // to calling execute
+ void setConnectionRetryProfile(
+ const http::ConnectionRetryProfile& connectionRetryProfile)
+ {
+ connectionRetryContext_.profile = connectionRetryProfile;
+ }
+
+ // execute the async client
+ void execute(const ResponseHandler& responseHandler,
+ const ErrorHandler& errorHandler)
+ {
+ // set handlers
+ responseHandler_ = responseHandler;
+ errorHandler_ = errorHandler;
+
+ // connect and write request (implmented in a protocol
+ // specific manner by subclassees)
+ connectAndWriteRequest();
+ }
+
+ // if an embedder of this class calls close() on AsyncClient in it's
+ // destructor (for more rigorous cleanup) then it's possible that the
+ // onError handler will still be called as a result of the socket close.
+ // the callback might then be interacting with a C++ object that has
+ // already been deleted. for this case (which does occur in the
+ // desktop::NetworkReply class) we provide a method that disables
+ // any pending handlers
+ void disableHandlers()
+ {
+ responseHandler_ = ResponseHandler();
+ errorHandler_ = ErrorHandler();
+ }
+
+ // satisfy lower-level http::Socket interface (used when the client
+ // is upgraded to a websocket connection and no longer conforms to
+ // the request/response protocol used by the class in the ordinary
+ // course of business)
+
+ virtual void asyncReadSome(boost::asio::mutable_buffers_1 buffer,
+ Handler handler)
+ {
+ socket().async_read_some(buffer, handler);
+ }
+
+ virtual void asyncWrite(
+ const std::vector<boost::asio::const_buffer>& buffers,
+ Handler handler)
+ {
+ boost::asio::async_write(socket(), buffers, handler);
+ }
+
+ void close()
+ {
+ Error error = closeSocket(socket().lowest_layer());
+ if (error && !core::http::isConnectionTerminatedError(error))
+ logError(error);
+ }
+
+protected:
+
+ boost::asio::io_service& ioService() { return ioService_; }
+
+ virtual SocketService& socket() = 0;
+
+ void handleConnectionError(const Error& connectionError)
+ {
+ // retry if necessary, otherwise just forward the error to
+ // customary error handling scheme
+
+ if (!retryConnectionIfRequired(connectionError))
+ handleError(connectionError);
+ }
+
+ // asynchronously write the request (called by subclasses after
+ // they finish connecting)
+ void writeRequest()
+ {
+ // write
+ boost::asio::async_write(
+ socket(),
+ request_.toBuffers(Header::connectionClose()),
+ boost::bind(
+ &AsyncClient<SocketService>::handleWrite,
+ AsyncClient<SocketService>::shared_from_this(),
+ boost::asio::placeholders::error)
+ );
+ }
+
+ void handleError(const Error& error)
+ {
+ // close the socket
+ close();
+
+ if (errorHandler_)
+ errorHandler_(error);
+ }
+
+ void handleErrorCode(const boost::system::error_code& ec,
+ const ErrorLocation& location)
+ {
+ handleError(Error(ec, location));
+ }
+
+ void handleUnexpectedError(const std::string& description,
+ const ErrorLocation& location)
+ {
+ Error error = systemError(boost::system::errc::state_not_recoverable,
+ description,
+ location);
+ handleError(error);
+ }
+
+private:
+
+ virtual void connectAndWriteRequest() = 0;
+
+
+ bool retryConnectionIfRequired(const Error& connectionError)
+ {
+ // retry if this is a connection unavailable error and the
+ // caller has provided a connection retry profile
+ if (http::isConnectionUnavailableError(connectionError) &&
+ !connectionRetryContext_.profile.empty())
+ {
+ // if this is our first retry then set our stop trying time
+ // and call the (optional) recovery function
+ if (connectionRetryContext_.stopTryingTime.is_not_a_date_time())
+ {
+ connectionRetryContext_.stopTryingTime =
+ boost::posix_time::microsec_clock::universal_time() +
+ connectionRetryContext_.profile.maxWait;
+
+ if (connectionRetryContext_.profile.recoveryFunction)
+ connectionRetryContext_.profile.recoveryFunction();
+ }
+
+ // if we aren't alrady past the maximum wait time then
+ // wait the appropriate interval and attempt connection again
+ if (boost::posix_time::microsec_clock::universal_time() <
+ connectionRetryContext_.stopTryingTime)
+ {
+ return scheduleRetry(); // continuation
+ }
+ else // otherwise we've waited long enough, bail and
+ // perform normal error handling
+ {
+ return false;
+ }
+ }
+ else // not an error subject to retrying or no retry profile provided
+ {
+ return false;
+ }
+ }
+
+
+ bool scheduleRetry()
+ {
+ // set expiration
+ boost::system::error_code ec;
+ connectionRetryContext_.retryTimer.expires_from_now(
+ connectionRetryContext_.profile.retryInterval,
+ ec);
+
+ // attempt to schedule retry timer (should always succeed but
+ // include error check to be paranoid/robust)
+ if (!ec)
+ {
+ connectionRetryContext_.retryTimer.async_wait(boost::bind(
+ &AsyncClient<SocketService>::handleConnectionRetryTimer,
+ AsyncClient<SocketService>::shared_from_this(),
+ boost::asio::placeholders::error));
+
+ return true;
+ }
+ else
+ {
+ logError(Error(ec, ERROR_LOCATION));
+ return false;
+ }
+ }
+
+ void handleConnectionRetryTimer(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ connectAndWriteRequest();
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ void handleWrite(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // initiate async read of the first line of the response
+ boost::asio::async_read_until(
+ socket(),
+ responseBuffer_,
+ "\r\n",
+ boost::bind(&AsyncClient<SocketService>::handleReadStatusLine,
+ AsyncClient<SocketService>::shared_from_this(),
+ boost::asio::placeholders::error));
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ void handleReadStatusLine(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // parase status line
+ Error error = ResponseParser::parseStatusLine(&responseBuffer_,
+ &response_);
+ if (error)
+ {
+ handleError(error);
+ }
+ else
+ {
+ // initiate async read of the headers
+ boost::asio::async_read_until(
+ socket(),
+ responseBuffer_,
+ "\r\n\r\n",
+ boost::bind(&AsyncClient<SocketService>::handleReadHeaders,
+ AsyncClient<SocketService>::shared_from_this(),
+ boost::asio::placeholders::error));
+ }
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ void readSomeContent()
+ {
+ // provide a hook for subclasses to force termination of
+ // content reads (this is needed for named pipes on windows,
+ // where the client disconnecting from the server is part
+ // of the normal pipe shutdown sequence). without this
+ // the subsequent call to handleReadContent will perform
+ // the close and respond when it gets a shutdown error (as
+ // a result of the server shutting down)
+ if (stopReadingAndRespond())
+ {
+ closeAndRespond();
+ return;
+ }
+
+ boost::asio::async_read(
+ socket(),
+ responseBuffer_,
+ boost::asio::transfer_at_least(1),
+ boost::bind(&AsyncClient<SocketService>::handleReadContent,
+ AsyncClient<SocketService>::shared_from_this(),
+ boost::asio::placeholders::error));
+ }
+
+ virtual bool stopReadingAndRespond()
+ {
+ return false;
+ }
+
+ virtual bool keepConnectionAlive()
+ {
+ return false;
+ }
+
+ void handleReadHeaders(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // parse headers
+ ResponseParser::parseHeaders(&responseBuffer_, &response_);
+
+ // append any lefover buffer contents to the body
+ if (responseBuffer_.size() > 0)
+ ResponseParser::appendToBody(&responseBuffer_, &response_);
+
+ // start reading content
+ readSomeContent();
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ void handleReadContent(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // copy content
+ ResponseParser::appendToBody(&responseBuffer_, &response_);
+
+ // continue reading content
+ readSomeContent();
+ }
+ else if (ec == boost::asio::error::eof ||
+ isShutdownError(ec))
+ {
+ closeAndRespond();
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ virtual bool isShutdownError(const boost::system::error_code& ec)
+ {
+ return false;
+ }
+
+ void closeAndRespond()
+ {
+ if (!keepConnectionAlive())
+ close();
+
+ if (responseHandler_)
+ responseHandler_(response_);
+ }
+
+ void logError(const Error& error) const
+ {
+ if (logToStderr_)
+ {
+ std::cerr << error << std::endl;
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+
+// struct and instance variable to track connection retry state
+private:
+ struct ConnectionRetryContext
+ {
+ ConnectionRetryContext(boost::asio::io_service& ioService)
+ : stopTryingTime(boost::posix_time::not_a_date_time),
+ retryTimer(ioService)
+ {
+ }
+
+ http::ConnectionRetryProfile profile;
+ boost::posix_time::ptime stopTryingTime;
+ boost::asio::deadline_timer retryTimer;
+ };
+
+protected:
+ http::Response response_;
+
+private:
+ boost::asio::io_service& ioService_;
+ ConnectionRetryContext connectionRetryContext_;
+ bool logToStderr_;
+ ResponseHandler responseHandler_;
+ ErrorHandler errorHandler_;
+ http::Request request_;
+ boost::asio::streambuf responseBuffer_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_ASYNC_CLIENT_HPP
+
+
diff --git a/src/cpp/core/include/core/http/AsyncConnection.hpp b/src/cpp/core/include/core/http/AsyncConnection.hpp
new file mode 100644
index 0000000..1d1fd68
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncConnection.hpp
@@ -0,0 +1,58 @@
+/*
+ * AsyncConnection.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_CONNECTION_HPP
+#define CORE_HTTP_ASYNC_CONNECTION_HPP
+
+#include <boost/shared_ptr.hpp>
+#include <boost/asio/io_service.hpp>
+
+#include <core/http/Socket.hpp>
+
+namespace core {
+
+class Error;
+
+namespace http {
+
+class Request;
+class Response;
+
+// abstract base (insulate clients from knowledge of protocol-specifics)
+class AsyncConnection : public Socket
+{
+public:
+ virtual ~AsyncConnection() {}
+
+ // io service for initiating dependent async network operations
+ virtual boost::asio::io_service& ioService() = 0;
+
+ // request
+ virtual const http::Request& request() const = 0;
+
+ // populate or set response then call writeResponse when done
+ virtual http::Response& response() = 0;
+ virtual void writeResponse(bool close = true) = 0;
+
+ // simple wrappers for writing an existing response or error
+ virtual void writeResponse(const http::Response& response,
+ bool close = true) = 0;
+ virtual void writeError(const Error& error) = 0;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_ASYNC_CONNECTION_HPP
diff --git a/src/cpp/core/include/core/http/AsyncConnectionImpl.hpp b/src/cpp/core/include/core/http/AsyncConnectionImpl.hpp
new file mode 100644
index 0000000..e12ca8b
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncConnectionImpl.hpp
@@ -0,0 +1,264 @@
+/*
+ * AsyncConnectionImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_CONNECTION_IMPL_HPP
+#define CORE_HTTP_ASYNC_CONNECTION_IMPL_HPP
+
+#include <boost/array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <boost/asio/write.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/placeholders.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/SocketUtils.hpp>
+#include <core/http/RequestParser.hpp>
+#include <core/http/AsyncConnection.hpp>
+
+namespace core {
+namespace http {
+
+template <typename ProtocolType>
+class AsyncConnectionImpl :
+ public AsyncConnection,
+ public boost::enable_shared_from_this<AsyncConnectionImpl<ProtocolType> >,
+ boost::noncopyable
+{
+public:
+ typedef boost::function<void(
+ boost::shared_ptr<AsyncConnectionImpl<ProtocolType> >,
+ http::Request*)> Handler;
+
+ typedef boost::function<void(http::Response*)> ResponseFilter;
+
+public:
+ AsyncConnectionImpl(boost::asio::io_service& ioService,
+ const Handler& handler,
+ const ResponseFilter& responseFilter =ResponseFilter())
+ : ioService_(ioService),
+ socket_(ioService),
+ handler_(handler),
+ responseFilter_(responseFilter)
+
+ {
+ }
+
+ typename ProtocolType::socket& socket()
+ {
+ return socket_;
+ }
+
+ void startReading()
+ {
+ readSome();
+ }
+
+ virtual boost::asio::io_service& ioService()
+ {
+ return ioService_;
+ }
+
+ virtual const http::Request& request() const
+ {
+ return request_;
+ }
+
+ virtual http::Response& response()
+ {
+ return response_;
+ }
+
+ virtual void writeResponse(bool close = true)
+ {
+ // add extra response headers
+ response_.setHeader("Date", util::httpDate());
+ if (close)
+ response_.setHeader("Connection", "close");
+
+ // call the response filter if we have one
+ if (responseFilter_)
+ responseFilter_(&response_);
+
+ // write
+ boost::asio::async_write(
+ socket_,
+ response_.toBuffers(),
+ boost::bind(
+ &AsyncConnectionImpl<ProtocolType>::handleWrite,
+ AsyncConnectionImpl<ProtocolType>::shared_from_this(),
+ boost::asio::placeholders::error,
+ close)
+ );
+ }
+
+ virtual void writeResponse(const http::Response& response, bool close = true)
+ {
+ response_.assign(response);
+ writeResponse(close);
+ }
+
+ virtual void writeError(const Error& error)
+ {
+ response_.setError(error);
+ writeResponse();
+ }
+
+ // satisfy lower-level http::Socket interface (used when the connection
+ // is upgraded to a websocket connection and no longer conforms to the
+ // request/response protocol used by the class in the ordinary course
+ // of business)
+
+ virtual void asyncReadSome(boost::asio::mutable_buffers_1 buffer,
+ Socket::Handler handler)
+ {
+ socket().async_read_some(buffer, handler);
+ }
+
+ virtual void asyncWrite(
+ const std::vector<boost::asio::const_buffer>& buffers,
+ Socket::Handler handler)
+ {
+ boost::asio::async_write(socket(), buffers, handler);
+ }
+
+ virtual void close()
+ {
+ Error error = closeSocket(socket_);
+ if (error && !core::http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+ }
+
+private:
+
+ void handleRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+ {
+ try
+ {
+ if (!e)
+ {
+ // parse next chunk
+ RequestParser::status status = requestParser_.parse(
+ request_,
+ buffer_.data(),
+ buffer_.data() + bytesTransferred);
+
+ // error - return bad request
+ if (status == RequestParser::error)
+ {
+ response_.setStatusCode(http::status::BadRequest);
+ writeResponse();
+ }
+
+ // incomplete -- keep reading
+ else if (status == RequestParser::incomplete)
+ {
+ readSome();
+ }
+
+ // got valid request -- handle it
+ else
+ {
+ handler_(AsyncConnectionImpl<ProtocolType>::shared_from_this(),
+ &request_);
+ }
+ }
+ else // error reading
+ {
+ // log the error if it wasn't connection terminated
+ Error error(e, ERROR_LOCATION);
+ if (!isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+
+ // close the socket
+ error = closeSocket(socket_);
+ if (error)
+ LOG_ERROR(error);
+
+ //
+ // no more async operations are initiated here so the shared_ptr to
+ // this connection no more references and is automatically destroyed
+ //
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+
+ void handleWrite(const boost::system::error_code& e, bool close)
+ {
+ try
+ {
+ if (e)
+ {
+ // log the error if it wasn't connection terminated
+ Error error(e, ERROR_LOCATION);
+ if (!http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+ }
+
+ // close the socket
+ if (close)
+ {
+ Error error = closeSocket(socket_);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ //
+ // no more async operations are initiated here so the shared_ptr to
+ // this connection no more references and is automatically destroyed
+ //
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ void readSome()
+ {
+ socket_.async_read_some(
+ boost::asio::buffer(buffer_),
+ boost::bind(
+ &AsyncConnectionImpl<ProtocolType>::handleRead,
+ AsyncConnectionImpl<ProtocolType>::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred)
+ );
+ }
+
+private:
+ boost::asio::io_service& ioService_;
+ typename ProtocolType::socket socket_;
+ Handler handler_;
+ ResponseFilter responseFilter_;
+ boost::array<char, 8192> buffer_ ;
+ RequestParser requestParser_ ;
+ http::Request request_;
+ http::Response response_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_ASYNC_CONNECTION_IMPL_HPP
+
+
diff --git a/src/cpp/core/include/core/http/AsyncServer.hpp b/src/cpp/core/include/core/http/AsyncServer.hpp
new file mode 100644
index 0000000..adb1564
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncServer.hpp
@@ -0,0 +1,75 @@
+/*
+ * AsyncServer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_SERVER_HPP
+#define CORE_HTTP_ASYNC_SERVER_HPP
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <boost/asio/io_service.hpp>
+
+#include <core/ScheduledCommand.hpp>
+
+#include <core/http/UriHandler.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+
+namespace core {
+namespace http {
+
+class AsyncServer
+{
+public:
+ virtual ~AsyncServer()
+ {
+ }
+
+ virtual boost::asio::io_service& ioService() = 0;
+
+ virtual void setAbortOnResourceError(bool abortOnResourceError) = 0;
+
+ virtual void addHandler(const std::string& prefix,
+ const AsyncUriHandlerFunction& handler) = 0;
+
+
+ virtual void addBlockingHandler(const std::string& prefix,
+ const UriHandlerFunction& handler) = 0;
+
+
+ virtual void setDefaultHandler(const AsyncUriHandlerFunction& handler) = 0;
+
+
+ virtual void setBlockingDefaultHandler(const UriHandlerFunction& handler) = 0;
+
+ virtual void setScheduledCommandInterval(
+ boost::posix_time::time_duration interval) = 0;
+ virtual void addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCmd) = 0;
+
+ virtual Error runSingleThreaded() = 0;
+
+ virtual Error run(std::size_t threadPoolSize = 1) = 0;
+
+ virtual void stop() = 0;
+
+ virtual void waitUntilStopped() = 0;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_ASYNC_SERVER_HPP
+
+
diff --git a/src/cpp/core/include/core/http/AsyncServerImpl.hpp b/src/cpp/core/include/core/http/AsyncServerImpl.hpp
new file mode 100644
index 0000000..1fd708c
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncServerImpl.hpp
@@ -0,0 +1,470 @@
+/*
+ * AsyncServerImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_SERVER_IMPL_HPP
+#define CORE_HTTP_ASYNC_SERVER_IMPL_HPP
+
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/placeholders.hpp>
+#include <boost/asio/deadline_timer.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/Error.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/Log.hpp>
+#include <core/ScheduledCommand.hpp>
+#include <core/system/System.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/AsyncServer.hpp>
+#include <core/http/AsyncConnectionImpl.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+#include <core/http/Util.hpp>
+#include <core/http/UriHandler.hpp>
+#include <core/http/SocketUtils.hpp>
+#include <core/http/SocketAcceptorService.hpp>
+
+
+namespace core {
+namespace http {
+
+template <typename ProtocolType>
+class AsyncServerImpl : public AsyncServer, boost::noncopyable
+{
+public:
+ AsyncServerImpl(const std::string& serverName,
+ const std::string& baseUri = std::string())
+ : abortOnResourceError_(false),
+ serverName_(serverName),
+ baseUri_(baseUri),
+ acceptorService_(),
+ scheduledCommandInterval_(boost::posix_time::seconds(3)),
+ scheduledCommandTimer_(acceptorService_.ioService()),
+ running_(false)
+ {
+ }
+
+ virtual ~AsyncServerImpl()
+ {
+ }
+
+ virtual boost::asio::io_service& ioService()
+ {
+ return acceptorService_.ioService();
+ }
+
+ virtual void setAbortOnResourceError(bool abortOnResourceError)
+ {
+ BOOST_ASSERT(!running_);
+ abortOnResourceError_ = abortOnResourceError;
+ }
+
+ virtual void addHandler(const std::string& prefix,
+ const AsyncUriHandlerFunction& handler)
+ {
+ BOOST_ASSERT(!running_);
+ uriHandlers_.add(AsyncUriHandler(baseUri_ + prefix, handler));
+ }
+
+ virtual void addBlockingHandler(const std::string& prefix,
+ const UriHandlerFunction& handler)
+ {
+ BOOST_ASSERT(!running_);
+ addHandler(prefix,
+ boost::bind(handleAsyncConnectionSynchronously, handler, _1));
+ }
+
+ virtual void setDefaultHandler(const AsyncUriHandlerFunction& handler)
+ {
+ BOOST_ASSERT(!running_);
+ defaultHandler_ = handler;
+ }
+
+ virtual void setBlockingDefaultHandler(const UriHandlerFunction& handler)
+ {
+ BOOST_ASSERT(!running_);
+ setDefaultHandler(boost::bind(handleAsyncConnectionSynchronously,
+ handler,
+ _1));
+ }
+
+ virtual void setScheduledCommandInterval(
+ boost::posix_time::time_duration interval)
+ {
+ BOOST_ASSERT(!running_);
+ scheduledCommandInterval_ = interval;
+ }
+
+ virtual void addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCmd)
+ {
+ BOOST_ASSERT(!running_);
+ scheduledCommands_.push_back(pCmd);
+ }
+
+ virtual Error runSingleThreaded()
+ {
+
+ // update state
+ running_ = true;
+
+ // get ready for next connection
+ acceptNextConnection();
+
+ // initialize scheduled command timer
+ waitForScheduledCommandTimer();
+
+
+ // run
+ runServiceThread();
+
+
+ return Success();
+ }
+
+ virtual Error run(std::size_t threadPoolSize = 1)
+ {
+ try
+ {
+ // update state
+ running_ = true;
+
+ // get ready for next connection
+ acceptNextConnection();
+
+ // initialize scheduled command timer
+ waitForScheduledCommandTimer();
+
+ // block all signals for the creation of the thread pool
+ // (prevents signals from occurring on any of the handler threads)
+ core::system::SignalBlocker signalBlocker;
+ Error error = signalBlocker.blockAll();
+ if (error)
+ return error ;
+
+ // create the threads
+ for (std::size_t i=0; i < threadPoolSize; ++i)
+ {
+ // run the thread
+ boost::shared_ptr<boost::thread> pThread(new boost::thread(
+ &AsyncServerImpl<ProtocolType>::runServiceThread,
+ this));
+
+ // add to list of threads
+ threads_.push_back(pThread);
+ }
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ return Error(boost::thread_error::ec_from_exception(e),
+ ERROR_LOCATION);
+ }
+
+ return Success();
+ }
+
+ virtual void stop()
+ {
+ // close acceptor so we free up the main port immediately
+ boost::system::error_code closeEc;
+ acceptorService_.closeAcceptor(closeEc);
+ if (closeEc)
+ LOG_ERROR(Error(closeEc, ERROR_LOCATION));
+
+ // stop the server
+ acceptorService_.ioService().stop();
+
+ // update state
+ running_ = false;
+ }
+
+ virtual void waitUntilStopped()
+ {
+ // wait until all of the threads in the pool exit
+ for (std::size_t i=0; i < threads_.size(); ++i)
+ threads_[i]->join();
+ }
+
+
+private:
+
+ void runServiceThread()
+ {
+ try
+ {
+ boost::system::error_code ec;
+ acceptorService_.ioService().run(ec);
+ if (ec)
+ LOG_ERROR(Error(ec, ERROR_LOCATION));
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ void acceptNextConnection()
+ {
+ // create a new connection
+ ptrNextConnection_.reset(new AsyncConnectionImpl<ProtocolType>(
+
+ // controlling io_service
+ acceptorService_.ioService(),
+
+ // connection handler
+ boost::bind(&AsyncServerImpl<ProtocolType>::handleConnection,
+ this, _1, _2),
+
+ // response filter
+ boost::bind(&AsyncServerImpl<ProtocolType>::connectionResponseFilter,
+ this, _1)
+ ));
+
+ // wait for next connection
+ acceptorService_.asyncAccept(
+ ptrNextConnection_->socket(),
+ boost::bind(&AsyncServerImpl<ProtocolType>::handleAccept,
+ this,
+ boost::asio::placeholders::error)
+ );
+ }
+
+ void handleAccept(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // start connection
+ ptrNextConnection_->startReading();
+ }
+ else
+ {
+ // for errors, log and continue (but don't log operation aborted
+ // or bad file descriptor since it happens in the ordinary course
+ // of shutting down the server)
+ if (ec != boost::asio::error::operation_aborted &&
+ ec != boost::asio::error::bad_descriptor)
+ {
+ // log the error
+ LOG_ERROR(Error(ec, ERROR_LOCATION)) ;
+
+ // check for resource exhaustion
+ checkForResourceExhaustion(ec, ERROR_LOCATION);
+ }
+ }
+ }
+ catch(const boost::system::system_error& e)
+ {
+ // always log
+ LOG_ERROR_MESSAGE(std::string("Unexpected exception: ") + e.what());
+
+ // check for resource exhaustion
+ checkForResourceExhaustion(e.code(), ERROR_LOCATION);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // ALWAYS accept next connection
+ try
+ {
+ acceptNextConnection() ;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ void handleConnection(
+ boost::shared_ptr<AsyncConnectionImpl<ProtocolType> > pConnection,
+ http::Request* pRequest)
+ {
+ try
+ {
+ // call filter
+ onRequest(&(pConnection->socket()), pRequest);
+
+ // convert to cannonical HttpConnection
+ boost::shared_ptr<AsyncConnection> pAsyncConnection =
+ boost::static_pointer_cast<AsyncConnection>(pConnection);
+
+ // call the appropriate handler to generate a response
+ std::string uri = pRequest->uri();
+ AsyncUriHandlerFunction handler = uriHandlers_.handlerFor(uri);
+ if (handler)
+ {
+ // call the handler
+ handler(pAsyncConnection) ;
+ }
+ else if (defaultHandler_)
+ {
+ // call the default handler
+ defaultHandler_(pAsyncConnection);
+ }
+ else
+ {
+ // log error
+ LOG_ERROR_MESSAGE("Handler not found for uri: " + pRequest->uri());
+
+ // return 404 not found
+ pConnection->response().setStatusCode(http::status::NotFound) ;
+ }
+ }
+ catch(const boost::system::system_error& e)
+ {
+ // always log
+ LOG_ERROR_MESSAGE(std::string("Unexpected exception: ") + e.what());
+
+ // check for resource exhaustion
+ checkForResourceExhaustion(e.code(), ERROR_LOCATION);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ void connectionResponseFilter(http::Response* pResponse)
+ {
+ // set server header (evade ref-counting to defend against
+ // non-threadsafe std::string implementations)
+ pResponse->setHeader("Server", std::string(serverName_.c_str()));
+ }
+
+ void waitForScheduledCommandTimer()
+ {
+ // set expiration time for 3 seconds from now
+ boost::system::error_code ec;
+ scheduledCommandTimer_.expires_from_now(scheduledCommandInterval_, ec);
+
+ // attempt to schedule timer (should always succeed but
+ // include error check to be paranoid/robust)
+ if (!ec)
+ {
+ scheduledCommandTimer_.async_wait(boost::bind(
+ &AsyncServerImpl<ProtocolType>::handleScheduledCommandTimer,
+ this,
+ boost::asio::placeholders::error));
+ }
+ else
+ {
+ // unexpected error setting timer. log it
+ LOG_ERROR(Error(ec, ERROR_LOCATION));
+ }
+ }
+
+ void handleScheduledCommandTimer(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // execute all commands
+ std::for_each(scheduledCommands_.begin(),
+ scheduledCommands_.end(),
+ boost::bind(&ScheduledCommand::execute, _1));
+
+ // remove any commands which are finished
+ scheduledCommands_.erase(
+ std::remove_if(scheduledCommands_.begin(),
+ scheduledCommands_.end(),
+ boost::bind(&ScheduledCommand::finished, _1)),
+ scheduledCommands_.end());
+
+ // wait for the timer again
+ waitForScheduledCommandTimer();
+
+ }
+ else
+ {
+ if (ec != boost::system::errc::operation_canceled)
+ LOG_ERROR(Error(ec, ERROR_LOCATION));
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+
+protected:
+ SocketAcceptorService<ProtocolType>& acceptorService()
+ {
+ return acceptorService_;
+ }
+
+private:
+
+ virtual void onRequest(typename ProtocolType::socket* pSocket,
+ http::Request* pRequest)
+ {
+
+ }
+
+ void maybeAbortServer(const std::string& message,
+ const core::ErrorLocation& location)
+ {
+ if (abortOnResourceError_)
+ {
+ core::log::logErrorMessage("(ABORTING SERVER): " + message, location);
+ ::abort();
+ }
+ else
+ {
+ core::log::logWarningMessage(
+ "Resource exhaustion error occurred (continuing to run)",
+ location);
+ }
+ }
+
+ void checkForResourceExhaustion(const boost::system::error_code& ec,
+ const core::ErrorLocation& location)
+ {
+ if ( ec.category() == boost::system::get_system_category() &&
+ (ec.value() == boost::system::errc::too_many_files_open ||
+ ec.value() == boost::system::errc::not_enough_memory) )
+ {
+ // our process has run out of memory or file handles. in this
+ // case the only way future requests can be serviced is if we
+ // abort and allow upstart to respawn us
+ maybeAbortServer("Resource exhaustion", location);
+ }
+ }
+
+ static void handleAsyncConnectionSynchronously(
+ const UriHandlerFunction& uriHandlerFunction,
+ boost::shared_ptr<AsyncConnection> pConnection)
+ {
+ uriHandlerFunction(pConnection->request(), &(pConnection->response()));
+ pConnection->writeResponse();
+ }
+
+private:
+ bool abortOnResourceError_;
+ std::string serverName_;
+ std::string baseUri_;
+ boost::shared_ptr<AsyncConnectionImpl<ProtocolType> > ptrNextConnection_;
+ AsyncUriHandlers uriHandlers_ ;
+ AsyncUriHandlerFunction defaultHandler_;
+ std::vector<boost::shared_ptr<boost::thread> > threads_;
+ SocketAcceptorService<ProtocolType> acceptorService_;
+ boost::posix_time::time_duration scheduledCommandInterval_;
+ boost::asio::deadline_timer scheduledCommandTimer_;
+ std::vector<boost::shared_ptr<ScheduledCommand> > scheduledCommands_;
+ bool running_;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_ASYNC_SERVER_IMPL_HPP
+
+
diff --git a/src/cpp/core/include/core/http/AsyncUriHandler.hpp b/src/cpp/core/include/core/http/AsyncUriHandler.hpp
new file mode 100644
index 0000000..9a64b5c
--- /dev/null
+++ b/src/cpp/core/include/core/http/AsyncUriHandler.hpp
@@ -0,0 +1,112 @@
+/*
+ * AsyncUriHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_ASYNC_URI_HANDLER_HPP
+#define CORE_HTTP_ASYNC_URI_HANDLER_HPP
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/http/UriHandler.hpp>
+#include <core/http/AsyncConnection.hpp>
+
+
+namespace core {
+namespace http {
+
+// AsyncUriHandlerFunction concept
+typedef boost::function<void(
+ boost::shared_ptr<AsyncConnection>)> AsyncUriHandlerFunction;
+
+class AsyncUriHandler
+{
+public:
+
+public:
+ AsyncUriHandler(const std::string& prefix,
+ AsyncUriHandlerFunction function)
+ : prefix_(prefix), function_(function)
+ {
+ }
+
+ // COPYING: via compiler
+
+ bool matches(const std::string& uri) const
+ {
+ return boost::algorithm::starts_with(uri, prefix_);
+ }
+
+ AsyncUriHandlerFunction function() const
+ {
+ return function_;
+ }
+
+
+ // implement AsyncUriHandlerFunction concept
+ void operator()(boost::shared_ptr<AsyncConnection> pConnection) const
+ {
+ function_(pConnection);
+ }
+
+private:
+ std::string prefix_;
+ AsyncUriHandlerFunction function_ ;
+
+};
+
+class AsyncUriHandlers
+{
+ // COPYING: via compiler
+
+public:
+ void add(AsyncUriHandler handler)
+ {
+ uriHandlers_.push_back(handler);
+ }
+
+ AsyncUriHandlerFunction handlerFor(const std::string& uri) const
+ {
+ std::vector<AsyncUriHandler>::const_iterator handler =
+ std::find_if(
+ uriHandlers_.begin(),
+ uriHandlers_.end(),
+ boost::bind(&AsyncUriHandler::matches, _1, uri));
+ if ( handler != uriHandlers_.end() )
+ {
+ return handler->function();
+ }
+ else
+ {
+ return AsyncUriHandlerFunction();
+ }
+ }
+
+private:
+ std::vector<AsyncUriHandler> uriHandlers_;
+};
+
+} // namespace http
+} // namespace core
+
+
+#endif // CORE_HTTP_ASYNC_URI_HANDLER_HPP
+
+
diff --git a/src/cpp/core/include/core/http/BlockingClient.hpp b/src/cpp/core/include/core/http/BlockingClient.hpp
new file mode 100644
index 0000000..e7a1c16
--- /dev/null
+++ b/src/cpp/core/include/core/http/BlockingClient.hpp
@@ -0,0 +1,73 @@
+/*
+ * BlockingClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_BLOCKING_CLIENT_HPP
+#define CORE_HTTP_BLOCKING_CLIENT_HPP
+
+#include <boost/function.hpp>
+
+
+#include <core/FilePath.hpp>
+
+#include <core/http/AsyncClient.hpp>
+
+namespace core {
+namespace http {
+
+namespace {
+
+void responseHandler(const http::Response& response,
+ http::Response* pTargetResponse)
+{
+ pTargetResponse->assign(response);
+}
+
+void errorHandler(const Error& error, Error* pTargetError)
+{
+ *pTargetError = error;
+}
+
+}
+
+template <typename SocketService>
+Error sendRequest(boost::asio::io_service& ioService,
+ boost::shared_ptr<AsyncClient<SocketService> > pClient,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // assign request
+ pClient->request().assign(request);
+
+ // start execution
+ Error error;
+ pClient->execute(boost::bind(responseHandler, _1, pResponse),
+ boost::bind(errorHandler, _1, &error));
+
+ // run the io service
+ boost::system::error_code ec;
+ ioService.run(ec);
+ if (ec)
+ return Error(ec, ERROR_LOCATION);
+
+ // return error status
+ return error;
+}
+
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_BLOCKING_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/BoostAsioSsl.hpp b/src/cpp/core/include/core/http/BoostAsioSsl.hpp
new file mode 100644
index 0000000..fe80795
--- /dev/null
+++ b/src/cpp/core/include/core/http/BoostAsioSsl.hpp
@@ -0,0 +1,32 @@
+/*
+ * BoostAsioSsl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_BOOST_ASIOSSL_HPP
+#define CORE_HTTP_BOOST_ASIOSSL_HPP
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+#include <boost/asio/ssl/context.hpp>
+#include <boost/asio/ssl/stream.hpp>
+#include <boost/asio/ssl/rfc2818_verification.hpp>
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif // CORE_HTTP_BOOST_ASIOSSL_HPP
diff --git a/src/cpp/core/include/core/http/ConnectionRetryProfile.hpp b/src/cpp/core/include/core/http/ConnectionRetryProfile.hpp
new file mode 100644
index 0000000..fa1a1b7
--- /dev/null
+++ b/src/cpp/core/include/core/http/ConnectionRetryProfile.hpp
@@ -0,0 +1,55 @@
+/*
+ * ConnectionRetryProfile.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_CONNECTION_RETRY_PROFILE_HPP
+#define CORE_HTTP_CONNECTION_RETRY_PROFILE_HPP
+
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+namespace http {
+
+struct ConnectionRetryProfile
+{
+ ConnectionRetryProfile()
+ : maxWait(boost::posix_time::not_a_date_time),
+ retryInterval(boost::posix_time::not_a_date_time)
+ {
+ }
+
+ ConnectionRetryProfile(
+ const boost::posix_time::time_duration& maxWait,
+ const boost::posix_time::time_duration& retryInterval,
+ const boost::function<void()>& recoveryFunction =
+ boost::function<void()>())
+ : maxWait(maxWait),
+ retryInterval(retryInterval),
+ recoveryFunction(recoveryFunction)
+ {
+ }
+
+ bool empty() const { return maxWait.is_not_a_date_time(); }
+
+ boost::posix_time::time_duration maxWait;
+ boost::posix_time::time_duration retryInterval;
+ boost::function<void()> recoveryFunction;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_CONNECTION_RETRY_PROFILE_HPP
diff --git a/src/cpp/core/include/core/http/Cookie.hpp b/src/cpp/core/include/core/http/Cookie.hpp
new file mode 100644
index 0000000..f4087f5
--- /dev/null
+++ b/src/cpp/core/include/core/http/Cookie.hpp
@@ -0,0 +1,74 @@
+/*
+ * Cookie.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_COOKIE_HPP
+#define CORE_HTTP_COOKIE_HPP
+
+#include <string>
+
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include "Request.hpp"
+
+namespace core {
+namespace http {
+
+class Cookie
+{
+public:
+ Cookie(const Request& request,
+ const std::string& name,
+ const std::string& value,
+ const std::string& path,
+ bool httpOnly = false) ;
+ virtual ~Cookie();
+
+ // COPYING: via compiler (copyable members)
+
+ void setName(const std::string& name) { name_ = name; }
+ const std::string& name() const { return name_; }
+
+ void setValue(const std::string& value) { value_ = value; }
+ const std::string& value() const { return value_; }
+
+ void setDomain(const std::string& domain) { domain_ = domain; }
+ const std::string& domain() const { return domain_; }
+
+ void setPath(const std::string& path) { path_ = path; }
+ const std::string& path() const { return path_; }
+
+ void setExpires(const boost::gregorian::date& expires) { expires_ = expires; }
+ void setExpires(const boost::gregorian::days& expiresDays) ;
+ void setExpiresDelete() ;
+ const boost::gregorian::date& expires() const { return expires_; }
+
+ void setHttpOnly();
+
+ std::string cookieHeaderValue() const ;
+
+private:
+ std::string name_ ;
+ std::string value_ ;
+ std::string domain_ ;
+ std::string path_ ;
+ boost::gregorian::date expires_ ;
+ bool httpOnly_;
+};
+
+
+} // namespace http
+} // namespace core
+
+
+#endif // CORE_HTTP_COOKIE_HTTP
diff --git a/src/cpp/core/include/core/http/Header.hpp b/src/cpp/core/include/core/http/Header.hpp
new file mode 100644
index 0000000..c680d7a
--- /dev/null
+++ b/src/cpp/core/include/core/http/Header.hpp
@@ -0,0 +1,70 @@
+/*
+ * Header.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_HEADER_HPP
+#define CORE_HTTP_HEADER_HPP
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+namespace core {
+namespace http {
+
+struct Header
+{
+ Header() {}
+ Header(const std::string& name, const std::string& value)
+ : name(name), value(value)
+ {
+ }
+ std::string name;
+ std::string value;
+ bool empty() const { return name.empty(); }
+
+ static Header connectionClose() { return Header("Connection", "close"); }
+};
+
+typedef std::vector<Header> Headers ;
+
+class HeaderNamePredicate
+{
+public:
+ HeaderNamePredicate(const std::string& name)
+ : name_(name)
+ {
+ }
+ bool operator()(const Header& header) const;
+private:
+ std::string name_ ;
+};
+
+bool containsHeader(const Headers& headers, const std::string& name);
+
+Headers::const_iterator findHeader(const Headers& headers,
+ const std::string& name);
+
+std::string headerValue(const Headers& headers, const std::string& name);
+
+bool parseHeader(const std::string& line, Header* pHeader);
+
+void parseHeaders(std::istream& is, Headers* pHeaders);
+
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_HEADER_HPP
diff --git a/src/cpp/core/include/core/http/LocalStreamAsyncClient.hpp b/src/cpp/core/include/core/http/LocalStreamAsyncClient.hpp
new file mode 100644
index 0000000..a81e386
--- /dev/null
+++ b/src/cpp/core/include/core/http/LocalStreamAsyncClient.hpp
@@ -0,0 +1,107 @@
+/*
+ * LocalStreamAsyncClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_LOCAL_STREAM_ASYNC_CLIENT_HPP
+#define CORE_HTTP_LOCAL_STREAM_ASYNC_CLIENT_HPP
+
+#include <boost/function.hpp>
+
+#include <boost/asio/local/stream_protocol.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/http/AsyncClient.hpp>
+#include <core/http/LocalStreamSocketUtils.hpp>
+
+namespace core {
+namespace http {
+
+class LocalStreamAsyncClient
+ : public AsyncClient<boost::asio::local::stream_protocol::socket>
+{
+public:
+ LocalStreamAsyncClient(boost::asio::io_service& ioService,
+ const FilePath localStreamPath,
+ bool logToStderr = false,
+ const http::ConnectionRetryProfile& retryProfile =
+ http::ConnectionRetryProfile())
+ : AsyncClient<boost::asio::local::stream_protocol::socket>(ioService,
+ logToStderr),
+ socket_(ioService),
+ localStreamPath_(localStreamPath)
+ {
+ setConnectionRetryProfile(retryProfile);
+ }
+
+protected:
+
+ virtual boost::asio::local::stream_protocol::socket& socket()
+ {
+ return socket_;
+ }
+
+private:
+
+ virtual void connectAndWriteRequest()
+ {
+ // establish endpoint
+ using boost::asio::local::stream_protocol;
+ stream_protocol::endpoint endpoint(localStreamPath_.absolutePath());
+
+ // connect
+ socket().async_connect(
+ endpoint,
+ boost::bind(&LocalStreamAsyncClient::handleConnect,
+ sharedFromThis(),
+ boost::asio::placeholders::error));
+ }
+
+ void handleConnect(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // the connection was successful call base to write the request
+ writeRequest();
+ }
+ else
+ {
+ handleConnectionError(Error(ec, ERROR_LOCATION));
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+
+ const boost::shared_ptr<LocalStreamAsyncClient> sharedFromThis()
+ {
+ boost::shared_ptr<AsyncClient<boost::asio::local::stream_protocol::socket> >
+ ptrShared = shared_from_this();
+
+ return boost::static_pointer_cast<LocalStreamAsyncClient>(ptrShared);
+ }
+
+private:
+ boost::asio::local::stream_protocol::socket socket_;
+ core::FilePath localStreamPath_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_ASYNC_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/LocalStreamAsyncServer.hpp b/src/cpp/core/include/core/http/LocalStreamAsyncServer.hpp
new file mode 100644
index 0000000..475a8bb
--- /dev/null
+++ b/src/cpp/core/include/core/http/LocalStreamAsyncServer.hpp
@@ -0,0 +1,127 @@
+/*
+ * LocalStreamAsyncServer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_LOCAL_STREAM_ASYNC_SERVER_HPP
+#define CORE_HTTP_LOCAL_STREAM_ASYNC_SERVER_HPP
+
+#include <boost/asio/io_service.hpp>
+
+#include <core/http/LocalStreamSocketUtils.hpp>
+#include <core/http/AsyncServerImpl.hpp>
+
+#include <core/system/PosixUser.hpp>
+
+namespace core {
+namespace http {
+
+class LocalStreamAsyncServer
+ : public AsyncServerImpl<boost::asio::local::stream_protocol>
+{
+public:
+ LocalStreamAsyncServer(const std::string& serverName,
+ const std::string& baseUri,
+ core::system::FileMode fileMode)
+ : AsyncServerImpl<boost::asio::local::stream_protocol>(serverName, baseUri),
+ fileMode_(fileMode)
+ {
+ }
+
+ virtual ~LocalStreamAsyncServer()
+ {
+ try
+ {
+ Error error = removeLocalStream();
+
+ // log error, but not for permission denied (because this could be
+ // a stream created by root and then torn down after yielding
+ // privilege to a different user)
+ if (error && (
+ error.code() != boost::system::errc::permission_denied &&
+ error.code() != boost::system::errc::operation_not_permitted
+ ))
+ LOG_ERROR(error);
+ }
+ catch(...)
+ {
+ }
+ }
+
+
+public:
+ Error init(const core::FilePath& localStreamPath)
+ {
+ // set stream path
+ localStreamPath_ = localStreamPath;
+
+ // remove any existing stream
+ Error error = removeLocalStream();
+ if (error)
+ return error ;
+
+ // initialize stream dir
+ error = initializeStreamDir(localStreamPath_.parent());
+ if (error)
+ return error;
+
+ // initialize acceptor
+ return initLocalStreamAcceptor(acceptorService(),
+ localStreamPath_,
+ fileMode_);
+ }
+
+private:
+ virtual void onRequest(boost::asio::local::stream_protocol::socket* pSocket,
+ http::Request* pRequest)
+ {
+ // get peer identity
+ core::system::user::UserIdentity peerIdentity;
+ Error error = core::system::user::socketPeerIdentity(pSocket->native(),
+ &peerIdentity);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // set it
+ pRequest->remoteUid_ = peerIdentity.userId;
+ }
+
+
+
+ Error removeLocalStream()
+ {
+ if (localStreamPath_.exists())
+ {
+ return localStreamPath_.remove();
+ }
+ else
+ {
+ return Success();
+ }
+ }
+
+private:
+ core::system::FileMode fileMode_;
+ core::FilePath localStreamPath_;
+
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_ASYNC_SERVER_HPP
+
+
diff --git a/src/cpp/core/include/core/http/LocalStreamBlockingClient.hpp b/src/cpp/core/include/core/http/LocalStreamBlockingClient.hpp
new file mode 100644
index 0000000..15b5aa5
--- /dev/null
+++ b/src/cpp/core/include/core/http/LocalStreamBlockingClient.hpp
@@ -0,0 +1,46 @@
+/*
+ * LocalStreamBlockingClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_LOCAL_STREAM_BLOCKING_CLIENT_HPP
+#define CORE_HTTP_LOCAL_STREAM_BLOCKING_CLIENT_HPP
+
+
+#include <core/http/BlockingClient.hpp>
+
+#include <core/http/LocalStreamAsyncClient.hpp>
+
+namespace core {
+namespace http {
+
+inline Error sendRequest(const FilePath& localStreamPath,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // create client
+ boost::asio::io_service ioService;
+ boost::shared_ptr<LocalStreamAsyncClient> pClient(
+ new LocalStreamAsyncClient(ioService, localStreamPath, true));
+
+ // execute blocking request
+ return sendRequest<boost::asio::local::stream_protocol::socket>(ioService,
+ pClient,
+ request,
+ pResponse);
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_BLOCKING_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/LocalStreamSocketUtils.hpp b/src/cpp/core/include/core/http/LocalStreamSocketUtils.hpp
new file mode 100644
index 0000000..5accb0b
--- /dev/null
+++ b/src/cpp/core/include/core/http/LocalStreamSocketUtils.hpp
@@ -0,0 +1,88 @@
+/*
+ * LocalStreamSocketUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_LOCAL_STREAM_SOCKET_UTILS_HPP
+#define CORE_HTTP_LOCAL_STREAM_SOCKET_UTILS_HPP
+
+#include <boost/asio/local/stream_protocol.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/system/FileMode.hpp>
+
+#include <core/http/SocketAcceptorService.hpp>
+
+namespace core {
+namespace http {
+
+inline Error initializeStreamDir(const FilePath& streamDir)
+{
+ if (!streamDir.exists())
+ {
+ Error error = streamDir.ensureDirectory();
+ if (error)
+ return error;
+
+ return changeFileMode(streamDir,
+ system::EveryoneReadWriteExecuteMode,
+ true);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+inline Error initLocalStreamAcceptor(
+ SocketAcceptorService<boost::asio::local::stream_protocol>& acceptorService,
+ const core::FilePath& localStreamPath,
+ core::system::FileMode fileMode)
+{
+ // initialize endpoint
+ using boost::asio::local::stream_protocol;
+ stream_protocol::endpoint endpoint(localStreamPath.absolutePath());
+
+ // get acceptor
+ stream_protocol::acceptor& acceptor = acceptorService.acceptor();
+
+ // open
+ boost::system::error_code ec;
+ acceptor.open(endpoint.protocol(), ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ // bind
+ acceptor.bind(endpoint, ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ // chmod on the stream file
+ Error error = changeFileMode(localStreamPath, fileMode);
+ if (error)
+ return error;
+
+ // listen
+ acceptor.listen(boost::asio::socket_base::max_connections, ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ return Success() ;
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_SOCKET_UTILS_HPP
diff --git a/src/cpp/core/include/core/http/Message.hpp b/src/cpp/core/include/core/http/Message.hpp
new file mode 100644
index 0000000..2ff110e
--- /dev/null
+++ b/src/cpp/core/include/core/http/Message.hpp
@@ -0,0 +1,154 @@
+/*
+ * Message.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_MESSAGE_HPP
+#define CORE_HTTP_MESSAGE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+namespace boost {
+namespace asio {
+ class const_buffer ;
+}
+}
+
+#include "Header.hpp"
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace http {
+
+// encodings
+extern const char * const kGzipEncoding;
+
+class Response;
+
+class Message : boost::noncopyable
+{
+public:
+ Message() : httpVersionMajor_(1), httpVersionMinor_(1) {}
+ virtual ~Message() {}
+ // COPYING: boost::noncopyable
+
+public:
+ int httpVersionMajor() const { return httpVersionMajor_; }
+ int httpVersionMinor() const { return httpVersionMinor_; }
+ void setHttpVersion(int httpVersionMajor, int httpVersionMinor) ;
+
+ bool isHttp10() const
+ {
+ return httpVersionMajor() == 1 && httpVersionMinor() == 0;
+ }
+
+ std::string contentType() const ;
+ void setContentType(const std::string& contentType) ;
+
+ std::size_t contentLength() const;
+ void setContentLength(int contentLength);
+
+ bool containsHeader(const std::string& name) const ;
+ std::string headerValue(const std::string& name) const ;
+
+ // add a header to the message
+ void addHeader(const Header& header);
+ void addHeader(const std::string& name, const std::string& value) ;
+ void addHeaders(const std::vector<Header>& headers);
+
+ // replace the existing value of a header (won't ever insert a new header)
+ void replaceHeader(const std::string& name, const std::string& value) ;
+
+ // set the value of a header (replace existing, or add new if necessary
+ void setHeaderLine(const std::string& line);
+ void setHeader(const Header& header);
+ void setHeader(const std::string& name, const std::string& value) ;
+ void setHeader(const std::string& name, int value);
+
+ void removeHeader(const std::string& name) ;
+
+ const Headers& headers() const { return headers_; }
+
+ const std::string& body() const { return body_; }
+
+ void reset();
+
+ std::vector<boost::asio::const_buffer> toBuffers(
+ const Header& overrideHeader = Header()) const ;
+
+protected:
+ // body_ is protected so that sub-classes set it directly (facilitating the
+ // RVO for potentially large buffers). note this means that you MUST always
+ // remember to call setContentLength after setting the body!
+ std::string body_;
+
+ void appendSpaceBuffer(
+ std::vector<boost::asio::const_buffer>& buffers) const ;
+
+ void appendHttpVersionBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const ;
+
+ void assign(const Message& message)
+ {
+ body_ = message.body_;
+ httpVersionMajor_ = message.httpVersionMajor_;
+ httpVersionMinor_ = message.httpVersionMinor_;
+ headers_ = message.headers_;
+ overrideHeader_ = message.overrideHeader_;
+ httpVersion_ = message.httpVersion_;
+ }
+
+private:
+
+ virtual void appendFirstLineBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const = 0;
+
+ virtual void resetMembers() = 0;
+
+private:
+
+ // IMPORTANT NOTE: when adding data members be sure to update
+ // the implementation of the assign method!!!!!
+
+
+ int httpVersionMajor_;
+ int httpVersionMinor_;
+ std::vector<Header> headers_;
+
+ // storage for override header (used by toBuffers to override a header
+ // when asking for the message bytes)
+ mutable Header overrideHeader_ ;
+
+ // string storage for integer members (need for to_buffers)
+ mutable std::string httpVersion_ ;
+
+ // grant friendship to subclasses and parsers so they can
+ // direclty manipulate fields
+ friend class Response; // done to ensure body_ can be assigned to directly
+ // with no intermediate std::string copies made
+ friend class RequestParser;
+ friend class ResponseParser;
+};
+
+std::ostream& operator << (std::ostream& stream, const Message& m) ;
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_MESSAGE_HPP
diff --git a/src/cpp/core/include/core/http/MultipartRelated.hpp b/src/cpp/core/include/core/http/MultipartRelated.hpp
new file mode 100644
index 0000000..1b3f5d5
--- /dev/null
+++ b/src/cpp/core/include/core/http/MultipartRelated.hpp
@@ -0,0 +1,50 @@
+/*
+ * MultipartRelated.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_MULTIPART_RELATED_HPP
+#define CORE_HTTP_MULTIPART_RELATED_HPP
+
+#include <string>
+#include <sstream>
+
+#include <boost/utility.hpp>
+
+namespace core {
+namespace http {
+
+class MultipartRelated : boost::noncopyable
+{
+public:
+ MultipartRelated() {}
+ // COPYING: boost::noncoypable
+
+public:
+ void addPart(const std::string& contentType,
+ const std::string& body);
+
+ void terminate();
+
+ std::string contentType() const ;
+ std::string body() const;
+
+private:
+ std::ostringstream bodyStream_;
+};
+
+} // namespace http
+} // namespace core
+
+
+#endif // CORE_HTTP_MULTIPART_RELATED_HPP
diff --git a/src/cpp/core/include/core/http/NamedPipeAsyncClient.hpp b/src/cpp/core/include/core/http/NamedPipeAsyncClient.hpp
new file mode 100644
index 0000000..047378a
--- /dev/null
+++ b/src/cpp/core/include/core/http/NamedPipeAsyncClient.hpp
@@ -0,0 +1,134 @@
+/*
+ * NamedPipeAsyncClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_NAMED_PIPE_ASYNC_CLIENT_HPP
+#define CORE_HTTP_NAMED_PIPE_ASYNC_CLIENT_HPP
+
+#include <boost/function.hpp>
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/windows/stream_handle.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/http/AsyncClient.hpp>
+
+#include <core/http/NamedPipeProtocol.hpp>
+
+namespace core {
+namespace http {
+
+class NamedPipeAsyncClient
+ : public AsyncClient<boost::asio::windows::stream_handle>
+{
+public:
+ // create a named pipe client -- note that the connectionRetryProfile is
+ // required because named pipes typically require a retry loop (due to
+ // servers either not having a pipe available or being between calls
+ // to ConnectNamedPipe). rather than create yet another timer-based
+ // retry mechanism for CreateFile on the named pipe client handle we
+ // require that clients use a connection retry profile
+ NamedPipeAsyncClient(boost::asio::io_service& ioService,
+ const std::string& pipeName,
+ const http::ConnectionRetryProfile& retryProfile)
+ : AsyncClient<boost::asio::windows::stream_handle>(ioService),
+ handle_(ioService),
+ pipeName_(pipeName)
+ {
+ setConnectionRetryProfile(retryProfile);
+ }
+
+protected:
+
+ virtual boost::asio::windows::stream_handle& socket()
+ {
+ return handle_;
+ }
+
+private:
+
+ virtual void connectAndWriteRequest()
+ {
+ try
+ {
+ // connect to named pipe
+ HANDLE hPipe = ::CreateFileA(
+ pipeName_.c_str(), // pipe name
+ GENERIC_READ | // allow reading
+ GENERIC_WRITE, // allow writing
+ 0, // no sharing
+ NULL, // default security attributes
+ OPEN_EXISTING, // opens existing
+ FILE_FLAG_OVERLAPPED | // allow overlapped io
+ SECURITY_SQOS_PRESENT | // custom security attribs
+ SECURITY_IDENTIFICATION,// impersonate identity only
+ NULL); // no template file
+
+ // handle connection error if necessary)
+ if (hPipe == INVALID_HANDLE_VALUE)
+ {
+ handleConnectionError(systemError(::GetLastError(),ERROR_LOCATION));
+ return;
+ }
+
+ // assign the pipe to our handle
+ handle_.assign(hPipe);
+
+ // write the request
+ writeRequest();
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ // detect when we've got the whole response and force a
+ // response + close of the socket
+ virtual bool stopReadingAndRespond()
+ {
+ return response_.body().length() >= response_.contentLength();
+ }
+
+ virtual bool isShutdownError(const boost::system::error_code& ec)
+ {
+ if (ec.category() == boost::system::get_system_category() &&
+ (ec.value() == ERROR_PIPE_NOT_CONNECTED) )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+
+ const boost::shared_ptr<NamedPipeAsyncClient> sharedFromThis()
+ {
+ boost::shared_ptr<AsyncClient<boost::asio::windows::stream_handle> >
+ ptrShared = shared_from_this();
+
+ return boost::static_pointer_cast<NamedPipeAsyncClient>(ptrShared);
+ }
+
+private:
+ std::string pipeName_;
+ boost::asio::windows::stream_handle handle_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_LOCAL_STREAM_ASYNC_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/NamedPipeBlockingClient.hpp b/src/cpp/core/include/core/http/NamedPipeBlockingClient.hpp
new file mode 100644
index 0000000..dd2e2c6
--- /dev/null
+++ b/src/cpp/core/include/core/http/NamedPipeBlockingClient.hpp
@@ -0,0 +1,47 @@
+/*
+ * NamedPipeBlockingClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_NAMED_PIPE_BLOCKING_CLIENT_HPP
+#define CORE_HTTP_NAMED_PIPE_BLOCKING_CLIENT_HPP
+
+
+#include <core/http/BlockingClient.hpp>
+
+#include <core/http/NamedPipeAsyncClient.hpp>
+
+namespace core {
+namespace http {
+
+inline Error sendRequest(const std::string& pipeName,
+ const http::Request& request,
+ const http::ConnectionRetryProfile& retryProfile,
+ http::Response* pResponse)
+{
+ // create client
+ boost::asio::io_service ioService;
+ boost::shared_ptr<NamedPipeAsyncClient> pClient(
+ new NamedPipeAsyncClient(ioService, pipeName, retryProfile));
+
+ // execute blocking request
+ return sendRequest<boost::asio::windows::stream_handle>(ioService,
+ pClient,
+ request,
+ pResponse);
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_NAMED_PIPE_BLOCKING_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/NamedPipeProtocol.hpp b/src/cpp/core/include/core/http/NamedPipeProtocol.hpp
new file mode 100644
index 0000000..68344cc
--- /dev/null
+++ b/src/cpp/core/include/core/http/NamedPipeProtocol.hpp
@@ -0,0 +1,45 @@
+/*
+ * NamedPipeProtocol.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_NAMED_PIPE_PROTOCOL_HPP
+#define CORE_HTTP_NAMED_PIPE_PROTOCOL_HPP
+
+#include <boost/asio/windows/stream_handle.hpp>
+
+#include <core/Error.hpp>
+
+#include <core/http/SocketUtils.hpp>
+#include <core/http/AsyncConnectionImpl.hpp>
+
+namespace core {
+namespace http {
+
+class NamedPipeProtocol
+{
+public:
+ typedef boost::asio::windows::stream_handle socket;
+};
+
+// specialization of closeSocket for stream handle lowest level
+template<> Error closeSocket(
+ boost::asio::windows::stream_handle::lowest_layer_type& socket);
+
+// specialization of closeSocket for stream handles
+template<> Error closeSocket(boost::asio::windows::stream_handle& socket);
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_NAMED_PIPE_PROTOCOL_HPP
diff --git a/src/cpp/core/include/core/http/Request.hpp b/src/cpp/core/include/core/http/Request.hpp
new file mode 100644
index 0000000..4eec8c4
--- /dev/null
+++ b/src/cpp/core/include/core/http/Request.hpp
@@ -0,0 +1,160 @@
+/*
+ * Request.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_REQUEST_HPP
+#define CORE_HTTP_REQUEST_HPP
+
+#include "Message.hpp"
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include "Util.hpp"
+
+namespace core {
+namespace http {
+
+class Request : public Message
+{
+public:
+ Request() ;
+ virtual ~Request() ;
+ // COPYING: boost::noncopyable
+
+ void assign(const Request& request)
+ {
+ Message::assign(request);
+ method_ = request.method_;
+ uri_ = request.uri_;
+ remoteUid_ = request.remoteUid_;
+ parsedCookies_ = request.parsedCookies_;
+ cookies_ = request.cookies_;
+ parsedFormFields_ = request.parsedFormFields_;
+ formFields_ = request.formFields_;
+ files_ = request.files_;
+ emptyFile_ = request.emptyFile_;
+ parsedQueryParams_ = request.parsedQueryParams_;
+ queryParams_ = request.queryParams_;
+ }
+
+public:
+ const std::string& method() const { return method_; }
+ void setMethod(const std::string& method) { method_ = method; }
+ const std::string& uri() const { return uri_; }
+ void setUri(const std::string& uri) { uri_ = uri; }
+
+ std::string absoluteUri() const;
+
+ bool acceptsContentType(const std::string& contentType) const;
+
+ std::string acceptEncoding() const { return headerValue("Accept-Encoding"); }
+ bool acceptsEncoding(const std::string& encoding) const;
+
+ std::string host() const { return headerValue("Host"); }
+ void setHost(const std::string& host) { setHeader("Host", host); }
+
+ std::string userAgent() const { return headerValue("User-Agent"); }
+
+ // only applies to local stream connections (returns -1 if unknown)
+ int remoteUid() const { return remoteUid_; }
+
+ boost::posix_time::ptime ifModifiedSince() const;
+
+ std::string queryString() const;
+ const Fields& queryParams() const;
+ std::string queryParamValue(const std::string& name) const;
+
+ template <typename T>
+ T queryParamValue(const std::string& name, const T& defaultVal) const
+ {
+ return http::util::fieldValue(queryParams(), name, defaultVal);
+ }
+
+ template <typename T, typename Predicate>
+ bool queryParamValue(const std::string& name,
+ const Predicate& validator,
+ T* pValue) const
+ {
+ return http::util::fieldValue(queryParams(), name, validator, pValue);
+ }
+
+
+ std::string cookieValue(const std::string& name) const;
+
+ const Fields& formFields() const;
+ std::string formFieldValue(const std::string& name) const;
+
+ template <typename T>
+ T formFieldValue(const std::string& name, const T& defaultVal) const
+ {
+ ensureFormFieldsParsed();
+ return http::util::fieldValue(formFields_, name, defaultVal);
+ }
+
+ template <typename T, typename Predicate>
+ bool formFieldValue(const std::string& name,
+ const Predicate& validator,
+ T* pValue) const
+ {
+ ensureFormFieldsParsed();
+ return http::util::fieldValue(formFields_, name, validator, pValue);
+ }
+
+ const File& uploadedFile(const std::string& name) const;
+
+ void setBody(const std::string& body);
+
+ void debugPrintUri(const std::string& caption) const;
+
+private:
+ virtual void appendFirstLineBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const ;
+
+ virtual void resetMembers();
+
+private:
+ void ensureFormFieldsParsed() const;
+ void scanHeaderForCookie(const std::string& name,
+ const std::string& value) const;
+
+private:
+
+ // IMPORTANT NOTE: when adding data members be sure to update
+ // the implementation of the assign method!!!!!
+
+
+ std::string method_;
+ std::string uri_;
+ int remoteUid_;
+
+ // cookies, form fields, and query string are parsed on demand
+ mutable bool parsedCookies_ ;
+ mutable Fields cookies_ ;
+ mutable bool parsedFormFields_ ;
+ mutable Fields formFields_;
+ mutable Files files_;
+ File emptyFile_;
+ mutable bool parsedQueryParams_;
+ mutable Fields queryParams_;
+
+ friend class RequestParser ;
+ friend class LocalStreamAsyncServer;
+};
+
+std::ostream& operator << (std::ostream& stream, const Request& r) ;
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_REQUEST_HPP
diff --git a/src/cpp/core/include/core/http/RequestParser.hpp b/src/cpp/core/include/core/http/RequestParser.hpp
new file mode 100644
index 0000000..0e17101
--- /dev/null
+++ b/src/cpp/core/include/core/http/RequestParser.hpp
@@ -0,0 +1,131 @@
+/*
+ * RequestParser.hpp
+ *
+ * Copyright (C) 2009-11 by RStudio, Inc.
+ * Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_REQUEST_PARSER_HPP
+#define CORE_HTTP_REQUEST_PARSER_HPP
+
+#include <core/http/Request.hpp>
+
+namespace core {
+namespace http {
+
+/// Parser for incoming requests.
+class RequestParser
+{
+public:
+ /// Construct ready to parse the request method.
+ RequestParser();
+
+ /// Reset to initial parser state.
+ void reset();
+
+ // enum for parse results
+ enum status
+ {
+ incomplete,
+ complete,
+ error
+ };
+
+ template <typename InputIterator>
+ status parse(Request& req, InputIterator begin, InputIterator end)
+ {
+ while (begin != end)
+ {
+ // header parsing
+ if (!parsing_body_)
+ {
+ status st = consume(req, *begin++);
+ if ( st == error )
+ {
+ return st ;
+ }
+ else if ( st == complete )
+ {
+ // if we have a body then continue parsing it
+ if (content_length_ > 0)
+ {
+ parsing_body_ = true ;
+ continue ;
+ }
+ else
+ {
+ return st ;
+ }
+ }
+ }
+ // body parsing
+ else
+ {
+ req.body_.push_back(*begin++) ;
+ if (req.body_.size() == content_length_)
+ return complete ;
+ }
+ }
+ return incomplete ;
+ }
+
+private:
+ /// Handle the next character of input.
+ status consume(Request& req, char input);
+
+ /// Check if a byte is an HTTP character.
+ static bool is_char(int c);
+
+ /// Check if a byte is an HTTP control character.
+ static bool is_ctl(int c);
+
+ /// Check if a byte is defined as an HTTP tspecial character.
+ static bool is_tspecial(int c);
+
+ /// Check if a byte is a digit.
+ static bool is_digit(int c);
+
+ /// The current state of the parser.
+ enum state
+ {
+ method_start,
+ method,
+ uri_start,
+ uri,
+ http_version_h,
+ http_version_t_1,
+ http_version_t_2,
+ http_version_p,
+ http_version_slash,
+ http_version_major_start,
+ http_version_major,
+ http_version_minor_start,
+ http_version_minor,
+ expecting_newline_1,
+ header_line_start,
+ header_lws,
+ header_name,
+ space_before_header_value,
+ header_value,
+ expecting_newline_2,
+ expecting_newline_3
+ } state_;
+
+ std::size_t content_length_ ;
+ bool parsing_content_length_ ;
+ bool parsing_body_ ;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_REQUEST_PARSER_HPP
diff --git a/src/cpp/core/include/core/http/Response.hpp b/src/cpp/core/include/core/http/Response.hpp
new file mode 100644
index 0000000..ee838d3
--- /dev/null
+++ b/src/cpp/core/include/core/http/Response.hpp
@@ -0,0 +1,360 @@
+/*
+ * Response.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_RESPONSE_HPP
+#define CORE_HTTP_RESPONSE_HPP
+
+#include <iostream>
+#include <sstream>
+#include <boost/type_traits/is_same.hpp>
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
+
+#ifndef _WIN32
+#include <boost/iostreams/filter/gzip.hpp>
+#endif
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include "Message.hpp"
+#include "Request.hpp"
+#include "Util.hpp"
+
+namespace core {
+
+class ErrorLocation;
+
+namespace http {
+
+class Cookie ;
+
+namespace status {
+enum Code {
+ SwitchingProtocols = 101,
+ Ok = 200,
+ Created = 201,
+ PartialContent = 206,
+ MovedPermanently = 301,
+ MovedTemporarily = 302,
+ SeeOther = 303,
+ NotModified = 304,
+ TooManyRedirects = 310,
+ BadRequest = 400,
+ Unauthorized = 401,
+ Forbidden = 403,
+ NotFound = 404,
+ MethodNotAllowed = 405,
+ RangeNotSatisfiable = 416,
+ InternalServerError = 500 ,
+ NotImplemented = 501,
+ BadGateway = 502,
+ ServiceUnavailable = 503,
+ GatewayTimeout = 504
+};
+}
+
+class NullOutputFilter : public boost::iostreams::multichar_output_filter
+{
+public:
+ template<typename Sink>
+ std::streamsize write(Sink& dest, const char* s, std::streamsize n)
+ {
+ // this class exists only as a "null" tag for the setBody Filter
+ // argument -- it should never actually be used as a filter!
+ BOOST_ASSERT(false);
+ return boost::iostreams::write(dest, s, n);
+ }
+};
+
+class Response : public Message
+{
+public:
+ Response();
+ virtual ~Response() {}
+
+ // COPYING: boost::noncopyable (but see explicit assign method below)
+
+ void assign(const Response& response)
+ {
+ Message::assign(response);
+ statusCode_ = response.statusCode_;
+ statusCodeStr_ = response.statusCodeStr_;
+ statusMessage_ = response.statusMessage_;
+ }
+
+public:
+ int statusCode() const { return statusCode_; }
+ void setStatusCode(int statusCode) { statusCode_ = statusCode; }
+
+ const std::string& statusMessage() const;
+ void setStatusMessage(const std::string& statusMessage) ;
+
+ std::string contentEncoding() const;
+ void setContentEncoding(const std::string& encoding);
+
+ void setCacheWithRevalidationHeaders();
+ void setCacheForeverHeaders();
+ void setPrivateCacheForeverHeaders();
+ void setNoCacheHeaders();
+
+ void setChromeFrameCompatible(const Request& request);
+
+ void addCookie(const Cookie& cookie) ;
+
+ Error setBody(const std::string& content);
+
+ Error setCacheableBody(const std::string& content,
+ const Request& request)
+ {
+ NullOutputFilter nullFilter;
+ return setCacheableBody(content, request, nullFilter);
+ }
+
+ template <typename Filter>
+ Error setCacheableBody(const std::string& content,
+ const Request& request,
+ const Filter& filter)
+ {
+ // compute and set the eTag
+ std::string eTag = eTagForContent(content);
+ setHeader("ETag", eTag);
+
+ if (eTag == request.headerValue("If-None-Match"))
+ {
+ removeHeader("Content-Type"); // upstream code may have set this
+ setStatusCode(status::NotModified);
+ return Success();
+ }
+ else
+ {
+ return setBody(content, filter);
+ }
+ }
+
+ Error setCacheableBody(const FilePath& filePath, const Request& request);
+
+ template <typename Filter>
+ Error setBody(const std::string& content,
+ const Filter& filter,
+ std::streamsize buffSize = 128)
+ {
+ std::istringstream is(content);
+ return setBody(is, filter, buffSize);
+ }
+
+ Error setBody(std::istream& is, std::streamsize buffSize = 128)
+ {
+ NullOutputFilter nullFilter;
+ return setBody(is, nullFilter, buffSize);
+ }
+
+ template <typename Filter>
+ Error setBody(std::istream& is,
+ const Filter& filter,
+ std::streamsize buffSize = 128)
+ {
+ try
+ {
+ // set exception mask (required for proper reporting of errors)
+ is.exceptions(std::istream::failbit | std::istream::badbit);
+
+ // setup filtering stream for writing body
+ boost::iostreams::filtering_ostream filteringStream ;
+
+ // don't bother adding the filter if it is the NullOutputFilter
+ if ( !boost::is_same<Filter, NullOutputFilter>::value )
+ filteringStream.push(filter, buffSize);
+
+ // handle gzip
+ if (contentEncoding() == kGzipEncoding)
+#ifdef _WIN32
+ // never gzip on win32
+ removeHeader("Content-Encoding");
+#else
+ // add gzip compressor on posix
+ filteringStream.push(boost::iostreams::gzip_compressor(), buffSize);
+#endif
+
+ // buffer to write to
+ std::ostringstream bodyStream;
+ filteringStream.push(bodyStream, buffSize);
+
+ // copy input stream
+ boost::iostreams::copy(is, filteringStream, buffSize);
+
+ // set body
+ body_ = bodyStream.str();
+ setContentLength(body_.length());
+
+ // return success
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ return error;
+ }
+ }
+
+ Error setBody(const FilePath& filePath, std::streamsize buffSize = 512)
+ {
+ NullOutputFilter nullFilter;
+ return setBody(filePath, nullFilter, buffSize);
+ }
+
+ template <typename Filter>
+ Error setBody(const FilePath& filePath,
+ const Filter& filter,
+ std::streamsize buffSize = 128)
+ {
+ // open the file
+ boost::shared_ptr<std::istream> pIfs;
+ Error error = filePath.open_r(&pIfs);
+ if (error)
+ return error;
+
+ // send the file from its stream
+ try
+ {
+ return setBody(*pIfs, filter, buffSize);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ error.addProperty("path", filePath.absolutePath());
+ return error;
+ }
+ }
+
+ void setDynamicHtml(const std::string& html, const Request& request);
+
+ void setFile(const FilePath& filePath, const Request& request)
+ {
+ NullOutputFilter nullFilter;
+ setFile(filePath, request, nullFilter);
+ }
+
+ template <typename Filter>
+ void setFile(const FilePath& filePath,
+ const Request& request,
+ const Filter& filter)
+ {
+ // ensure that the file exists
+ if (!filePath.exists())
+ {
+ setError(http::status::NotFound, request.uri() + " not found");
+ return;
+ }
+
+ // set content type
+ setContentType(filePath.mimeContentType());
+
+ // gzip if possible
+ if (request.acceptsEncoding(kGzipEncoding))
+ setContentEncoding(kGzipEncoding);
+
+ // set body from file
+ Error error = setBody(filePath, filter);
+ if (error)
+ setError(status::InternalServerError, error.code().message());
+ }
+
+ void setCacheableFile(const FilePath& filePath, const Request& request)
+ {
+ NullOutputFilter nullFilter;
+ setCacheableFile(filePath, request, nullFilter);
+ }
+
+ template <typename Filter>
+ void setCacheableFile(const FilePath& filePath,
+ const Request& request,
+ const Filter& filter)
+ {
+ // ensure that the file exists
+ if (!filePath.exists())
+ {
+ setError(http::status::NotFound, request.uri() + " not found");
+ return;
+ }
+
+ // set Last-Modified
+ using namespace boost::posix_time;
+ ptime lastModifiedDate = from_time_t(filePath.lastWriteTime());
+ setHeader("Last-Modified", util::httpDate(lastModifiedDate));
+
+ // compare file modified time to If-Modified-Since
+ if (lastModifiedDate == request.ifModifiedSince())
+ {
+ removeHeader("Content-Type"); // upstream code may have set this
+ setStatusCode(status::NotModified);
+ }
+ else
+ {
+ setFile(filePath, request, filter);
+ }
+ }
+
+ void setRangeableFile(const FilePath& filePath, const Request& request);
+
+ void setRangeableFile(const std::string& contents,
+ const std::string& mimeType,
+ const Request& request);
+
+ // these calls do no stream io or encoding so don't return errors
+ void setBodyUnencoded(const std::string& body);
+ void setError(int statusCode, const std::string& message);
+ void setError(const Error& error);
+
+ void setMovedPermanently(const http::Request& request, const std::string& location);
+ void setMovedTemporarily(const http::Request& request, const std::string& location);
+
+
+private:
+ virtual void appendFirstLineBuffers(
+ std::vector<boost::asio::const_buffer>& buffers) const ;
+
+ virtual void resetMembers();
+
+private:
+ void ensureStatusMessage() const ;
+ void removeCachingHeaders();
+ void setCacheForeverHeaders(bool publicAccessiblity);
+ std::string eTagForContent(const std::string& content);
+
+private:
+
+ // IMPORTANT NOTE: when adding data members be sure to update
+ // the implementation of the assign method!!!!!
+
+
+ int statusCode_ ;
+ mutable std::string statusMessage_ ;
+
+ // string storage for integer members (need for toBuffers)
+ mutable std::string statusCodeStr_ ;
+};
+
+std::ostream& operator << (std::ostream& stream, const Response& r) ;
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_RESPONSE_HPP
diff --git a/src/cpp/core/include/core/http/ResponseParser.hpp b/src/cpp/core/include/core/http/ResponseParser.hpp
new file mode 100644
index 0000000..f280c65
--- /dev/null
+++ b/src/cpp/core/include/core/http/ResponseParser.hpp
@@ -0,0 +1,152 @@
+/*
+ * ResponseParser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_RESPONSE_PARSER_HPP
+#define CORE_HTTP_RESPONSE_PARSER_HPP
+
+#include <iostream>
+#include <sstream>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/asio/streambuf.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/asio/read_until.hpp>
+
+#include <core/Error.hpp>
+#include <core/http/Response.hpp>
+
+namespace core {
+namespace http {
+
+// we use a class rather than a namespace so we can grant friendship
+// to template functions
+class ResponseParser
+{
+public:
+
+ static Error parseStatusLine(boost::asio::streambuf* pResponseBuffer,
+ Response* pResponse)
+ {
+ std::istream responseStream(pResponseBuffer);
+ std::string httpVersion;
+ responseStream >> httpVersion >> std::ws;
+
+ // status code
+ int statusCode ;
+ responseStream >> statusCode >> std::ws;
+ pResponse->setStatusCode(statusCode) ;
+
+ // status message
+ std::string statusMessage ;
+ std::getline(responseStream, statusMessage);
+ boost::algorithm::trim(statusMessage);
+ pResponse->setStatusMessage(statusMessage) ;
+
+ // validate that all elements required were in the response
+ if ( !responseStream )
+ {
+ return systemError(boost::system::errc::protocol_error,
+ ERROR_LOCATION) ;
+ }
+
+ // validate that the http version was specified correctly
+ if ( httpVersion.substr(0, 5) != "HTTP/" || httpVersion.size() != 8)
+ {
+ return systemError(boost::system::errc::protocol_error,
+ "Bad http version: " + httpVersion,
+ ERROR_LOCATION) ;
+
+ }
+
+ // parse out the major and minor version
+ pResponse->setHttpVersion(
+ boost::lexical_cast<int>(httpVersion.substr(5,1)),
+ boost::lexical_cast<int>(httpVersion.substr(7,1)));
+
+ return Success();
+ }
+
+
+ static void parseHeaders(boost::asio::streambuf* pResponseBuffer,
+ Response* pResponse)
+ {
+ std::istream responseStream(pResponseBuffer);
+
+ Headers headers ;
+ http::parseHeaders(responseStream, &headers);
+ std::for_each(headers.begin(),
+ headers.end(),
+ boost::bind(&Response::addHeader, pResponse, _1));
+ }
+
+ static void appendToBody(boost::asio::streambuf* pResponseBuffer,
+ Response* pResponse)
+ {
+ std::ostringstream bodyStream ;
+ if (pResponseBuffer->size() > 0)
+ bodyStream << pResponseBuffer;
+ pResponse->body_ += bodyStream.str();
+ }
+
+ template <typename SyncReadStream>
+ static Error parseFromStream(SyncReadStream& stream, Response* pResponse)
+ {
+ // declarations
+ boost::system::error_code ec ;
+ boost::asio::streambuf response;
+
+ // read the status line
+ boost::asio::read_until(stream, response, "\r\n", ec);
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ // parse the status line
+ Error error = ResponseParser::parseStatusLine(&response, pResponse);
+ if(error)
+ return error;
+
+ // read the headers
+ boost::asio::read_until(stream, response, "\r\n\r\n", ec);
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ // parse the headers
+ ResponseParser::parseHeaders(&response, pResponse);
+
+ // append any lefover buffer contents to the body
+ if (response.size() > 0)
+ ResponseParser::appendToBody(&response, pResponse);
+
+ // read the body
+ while (boost::asio::read(stream,
+ response, boost::asio::transfer_at_least(1),
+ ec))
+ {
+ ResponseParser::appendToBody(&response, pResponse);
+ }
+
+ if (ec != boost::asio::error::eof)
+ return Error(ec, ERROR_LOCATION) ;
+
+ return Success();
+ }
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_RESPONSE_PARSER_HPP
diff --git a/src/cpp/core/include/core/http/Socket.hpp b/src/cpp/core/include/core/http/Socket.hpp
new file mode 100644
index 0000000..82fd02f
--- /dev/null
+++ b/src/cpp/core/include/core/http/Socket.hpp
@@ -0,0 +1,49 @@
+/*
+ * Socket.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_SOCKET_HPP
+#define CORE_HTTP_SOCKET_HPP
+
+#include <vector>
+
+#include <boost/system/error_code.hpp>
+#include <boost/function.hpp>
+
+#include <boost/asio/buffer.hpp>
+
+namespace core {
+namespace http {
+
+class Socket
+{
+public:
+ typedef boost::function<void(const boost::system::error_code&, std::size_t)>
+ Handler;
+
+public:
+ virtual void asyncReadSome(boost::asio::mutable_buffers_1 buffers,
+ Handler handler) = 0;
+
+ virtual void asyncWrite(
+ const std::vector<boost::asio::const_buffer>& buffers,
+ Handler Handler) = 0;
+
+ virtual void close() = 0;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_SOCKET_HPP
diff --git a/src/cpp/core/include/core/http/SocketAcceptorService.hpp b/src/cpp/core/include/core/http/SocketAcceptorService.hpp
new file mode 100644
index 0000000..b679e0c
--- /dev/null
+++ b/src/cpp/core/include/core/http/SocketAcceptorService.hpp
@@ -0,0 +1,106 @@
+/*
+ * SocketAcceptorService.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_SOCKET_ACCEPTOR_SERVICE_HPP
+#define CORE_HTTP_SOCKET_ACCEPTOR_SERVICE_HPP
+
+#include <string>
+
+#include <boost/function.hpp>
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <boost/asio/io_service.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+
+namespace core {
+namespace http {
+
+typedef boost::function<void(const boost::system::error_code& ec)>
+ AcceptHandler ;
+
+template <typename ProtocolType>
+class SocketAcceptorService : boost::noncopyable
+{
+public:
+ SocketAcceptorService()
+ : pInternalIOService_(new boost::asio::io_service()),
+ ioService_(*pInternalIOService_),
+ acceptor_(ioService_)
+ {
+ }
+
+ explicit SocketAcceptorService(boost::asio::io_service& ioService)
+ : ioService_(ioService),
+ acceptor_(ioService_)
+ {
+ }
+
+ virtual ~SocketAcceptorService()
+ {
+ try
+ {
+ if (acceptor_.is_open())
+ {
+ boost::system::error_code ec ;
+ closeAcceptor(ec);
+ if (ec && (ec.value() != boost::system::errc::bad_file_descriptor))
+ LOG_ERROR(Error(ec, ERROR_LOCATION));
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+ // COPYING: boost::noncopyable
+
+public:
+
+ boost::asio::io_service& ioService()
+ {
+ return ioService_ ;
+ }
+
+ typename ProtocolType::acceptor& acceptor()
+ {
+ return acceptor_;
+ }
+
+ void asyncAccept(typename ProtocolType::socket& socket,
+ AcceptHandler acceptHandler)
+ {
+ acceptor_.async_accept(socket, acceptHandler);
+ }
+
+ void closeAcceptor(boost::system::error_code& ec)
+ {
+ acceptor_.close(ec);
+ }
+
+private:
+ boost::scoped_ptr<boost::asio::io_service> pInternalIOService_;
+ boost::asio::io_service& ioService_;
+ typename ProtocolType::acceptor acceptor_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_SOCKET_ACCEPTOR_SERVICE_HPP
diff --git a/src/cpp/core/include/core/http/SocketProxy.hpp b/src/cpp/core/include/core/http/SocketProxy.hpp
new file mode 100644
index 0000000..af7e83f
--- /dev/null
+++ b/src/cpp/core/include/core/http/SocketProxy.hpp
@@ -0,0 +1,77 @@
+/*
+ * SocketProxy.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_SOCKET_PROXY_HPP
+#define CORE_HTTP_SOCKET_PROXY_HPP
+
+#include <string>
+
+#include <boost/array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/Error.hpp>
+#include <core/http/Socket.hpp>
+
+namespace core {
+namespace http {
+
+class SocketProxy : public boost::enable_shared_from_this<SocketProxy>
+{
+public:
+ static void create(boost::shared_ptr<core::http::Socket> ptrClient,
+ boost::shared_ptr<core::http::Socket> ptrServer)
+ {
+ boost::shared_ptr<SocketProxy> pProxy(new SocketProxy(ptrClient,
+ ptrServer));
+ pProxy->readClient();
+ pProxy->readServer();
+ }
+
+private:
+ SocketProxy(boost::shared_ptr<core::http::Socket> ptrClient,
+ boost::shared_ptr<core::http::Socket> ptrServer)
+ : ptrClient_(ptrClient), ptrServer_(ptrServer)
+ {
+ }
+
+ void readClient();
+ void readServer();
+
+ void handleClientRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred);
+ void handleServerRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred);
+ void handleClientWrite(const boost::system::error_code& e,
+ std::size_t bytesTransferred);
+ void handleServerWrite(const boost::system::error_code& e,
+ std::size_t bytesTransferred);
+ void handleError(const boost::system::error_code& e,
+ const core::ErrorLocation& location);
+
+ void close();
+
+private:
+ boost::shared_ptr<core::http::Socket> ptrClient_;
+ boost::shared_ptr<core::http::Socket> ptrServer_;
+ boost::array<char, 8192> clientBuffer_;
+ boost::array<char, 8192> serverBuffer_;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_SOCKET_PROXY_HPP
+
diff --git a/src/cpp/core/include/core/http/SocketUtils.hpp b/src/cpp/core/include/core/http/SocketUtils.hpp
new file mode 100644
index 0000000..038d59e
--- /dev/null
+++ b/src/cpp/core/include/core/http/SocketUtils.hpp
@@ -0,0 +1,89 @@
+/*
+ * SocketUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_SOCKET_UTILS_HPP
+#define CORE_HTTP_SOCKET_UTILS_HPP
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/socket_base.hpp>
+
+#ifdef _WIN32
+#include <boost/system/windows_error.hpp>
+#endif
+
+#include <core/Error.hpp>
+
+namespace core {
+namespace http {
+
+template <typename SocketService>
+Error closeSocket(SocketService& socket)
+{
+ if (socket.is_open())
+ {
+ // shutdown, but don't allow shutdown errors to prevent us from closing
+ // (shutdown errors often occur b/c the other end of the socket has
+ // already been closed)
+ boost::system::error_code ec ;
+ socket.shutdown(boost::asio::socket_base::shutdown_both, ec) ;
+
+ socket.close(ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+ }
+
+ return Success() ;
+}
+
+inline bool isConnectionTerminatedError(const core::Error& error)
+{
+ // look for errors that indicate the client closing the connection
+ bool timedOut = error.code() == boost::asio::error::timed_out;
+ bool eof = error.code() == boost::asio::error::eof;
+ bool reset = error.code() == boost::asio::error::connection_reset;
+ bool badFile = error.code() == boost::asio::error::bad_descriptor;
+ bool brokenPipe = error.code() == boost::asio::error::broken_pipe;
+ bool noFile = boost::system::errc::no_such_file_or_directory;
+
+ return timedOut || eof || reset || badFile || brokenPipe || noFile;
+}
+
+inline bool isConnectionUnavailableError(const Error& error)
+{
+ // determine whether the error connecting was caused by the session process
+ // not currently existing (and thus remediable by launching a new one)
+
+ return (
+ // for unix domain sockets
+ error.code() == boost::system::errc::no_such_file_or_directory ||
+
+ // for tcp-ip and unix domain sockets
+ error.code() == boost::asio::error::connection_refused
+
+ // for windows named pipes
+ #ifdef _WIN32
+ || error.code() == boost::system::windows_error::file_not_found
+ || error.code() == boost::system::windows_error::broken_pipe
+ || error.code() == boost::system::error_code(
+ ERROR_PIPE_BUSY,
+ boost::system::get_system_category())
+ #endif
+ );
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_SOCKET_UTILS_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpAsyncClient.hpp b/src/cpp/core/include/core/http/TcpIpAsyncClient.hpp
new file mode 100644
index 0000000..76ee5e6
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpAsyncClient.hpp
@@ -0,0 +1,89 @@
+/*
+ * TcpIpAsyncClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_ASYNC_CLIENT_HPP
+#define CORE_HTTP_TCP_IP_ASYNC_CLIENT_HPP
+
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <boost/asio/ip/tcp.hpp>
+
+#include <core/Log.hpp>
+
+#include <core/http/AsyncClient.hpp>
+#include <core/http/TcpIpSocketUtils.hpp>
+#include <core/http/TcpIpAsyncConnector.hpp>
+
+namespace core {
+namespace http {
+
+class TcpIpAsyncClient :
+ public AsyncClient<boost::asio::ip::tcp::socket>
+{
+public:
+ TcpIpAsyncClient(boost::asio::io_service& ioService,
+ const std::string& address,
+ const std::string& port)
+ : AsyncClient<boost::asio::ip::tcp::socket>(ioService),
+ socket_(ioService),
+ address_(address),
+ port_(port)
+ {
+ }
+
+protected:
+
+ virtual boost::asio::ip::tcp::socket& socket()
+ {
+ return socket_;
+ }
+
+private:
+
+ virtual void connectAndWriteRequest()
+ {
+ boost::shared_ptr<TcpIpAsyncConnector> pAsyncConnector(
+ new TcpIpAsyncConnector(ioService(), &(socket())));
+
+ pAsyncConnector->connect(
+ address_,
+ port_,
+ boost::bind(&TcpIpAsyncClient::writeRequest,
+ TcpIpAsyncClient::sharedFromThis()),
+ boost::bind(&TcpIpAsyncClient::handleConnectionError,
+ TcpIpAsyncClient::sharedFromThis(),
+ _1));
+
+ }
+
+ const boost::shared_ptr<TcpIpAsyncClient> sharedFromThis()
+ {
+ boost::shared_ptr<AsyncClient<boost::asio::ip::tcp::socket> > ptrShared
+ = shared_from_this();
+
+ return boost::static_pointer_cast<TcpIpAsyncClient>(ptrShared);
+ }
+
+private:
+ boost::asio::ip::tcp::socket socket_;
+ std::string address_;
+ std::string port_;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_ASYNC_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpAsyncClientSsl.hpp b/src/cpp/core/include/core/http/TcpIpAsyncClientSsl.hpp
new file mode 100644
index 0000000..9188167
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpAsyncClientSsl.hpp
@@ -0,0 +1,156 @@
+/*
+ * TcpIpAsyncClientSsl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
+#define CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
+
+#ifdef _WIN32
+#error TcpIpAsyncClientSsl is not supported on Windows
+#endif
+
+#include <boost/scoped_ptr.hpp>
+
+#include <boost/asio/ip/tcp.hpp>
+
+#include "BoostAsioSsl.hpp"
+
+#include <core/http/AsyncClient.hpp>
+#include <core/http/TcpIpAsyncConnector.hpp>
+
+namespace core {
+namespace http {
+
+class TcpIpAsyncClientSsl
+ : public AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >
+{
+public:
+ TcpIpAsyncClientSsl(boost::asio::io_service& ioService,
+ const std::string& address,
+ const std::string& port,
+ bool verify)
+ : AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >(ioService),
+ sslContext_(ioService, boost::asio::ssl::context::sslv23_client),
+ address_(address),
+ port_(port),
+ verify_(verify)
+ {
+ if (verify_)
+ {
+ sslContext_.set_default_verify_paths();
+ sslContext_.set_verify_mode(boost::asio::ssl::context::verify_peer);
+ }
+ else
+ {
+ sslContext_.set_verify_mode(boost::asio::ssl::context::verify_none);
+ }
+
+ // use scoped ptr so we can call the constructor after we've configured
+ // the ssl::context (immediately above)
+ ptrSslStream_.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(ioService, sslContext_));
+ }
+
+
+protected:
+
+ virtual boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& socket()
+ {
+ return *(ptrSslStream_);
+ }
+
+ virtual void connectAndWriteRequest()
+ {
+ boost::shared_ptr<TcpIpAsyncConnector> pAsyncConnector(
+ new TcpIpAsyncConnector(ioService(),
+ &(ptrSslStream_->next_layer())));
+
+ pAsyncConnector->connect(
+ address_,
+ port_,
+ boost::bind(&TcpIpAsyncClientSsl::performHandshake,
+ TcpIpAsyncClientSsl::sharedFromThis()),
+ boost::bind(&TcpIpAsyncClientSsl::handleConnectionError,
+ TcpIpAsyncClientSsl::sharedFromThis(),
+ _1));
+ }
+
+
+private:
+
+ void performHandshake()
+ {
+ if (verify_)
+ {
+ ptrSslStream_->set_verify_callback(
+ boost::asio::ssl::rfc2818_verification(address_));
+ }
+ ptrSslStream_->async_handshake(
+ boost::asio::ssl::stream_base::client,
+ boost::bind(&TcpIpAsyncClientSsl::handleHandshake,
+ sharedFromThis(),
+ boost::asio::placeholders::error));
+ }
+
+ void handleHandshake(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // finished handshake, commence with request
+ writeRequest();
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CLIENT_EXCEPTION
+ }
+
+ const boost::shared_ptr<TcpIpAsyncClientSsl> sharedFromThis()
+ {
+ boost::shared_ptr<AsyncClient<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > > ptrShared
+ = shared_from_this();
+
+ return boost::static_pointer_cast<TcpIpAsyncClientSsl>(ptrShared);
+ }
+
+ virtual bool isShutdownError(const boost::system::error_code& ec)
+ {
+ // boost returns "short_read" when the peer calls SSL_shutdown()
+ if (ec.category() == boost::asio::error::get_ssl_category() &&
+ ec.value() == ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+private:
+ boost::asio::ssl::context sslContext_;
+ boost::scoped_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > ptrSslStream_;
+ std::string address_;
+ std::string port_;
+ bool verify_;
+};
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_ASYNC_CLIENT_SSL_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpAsyncConnector.hpp b/src/cpp/core/include/core/http/TcpIpAsyncConnector.hpp
new file mode 100644
index 0000000..31b0d69
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpAsyncConnector.hpp
@@ -0,0 +1,171 @@
+/*
+ * TcpIpAsyncConnector.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_ASYNC_CONNECTOR_HPP
+#define CORE_HTTP_TCP_IP_ASYNC_CONNECTOR_HPP
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/placeholders.hpp>
+
+
+#include <core/http/TcpIpSocketUtils.hpp>
+
+// special version of unexpected exception handler which makes
+// sure to call the user's ErrorHandler
+#define CATCH_UNEXPECTED_ASYNC_CONNECTOR_EXCEPTION \
+ catch(const std::exception& e) \
+ { \
+ handleUnexpectedError(std::string("Unexpected exception: ") + \
+ e.what(), ERROR_LOCATION) ; \
+ } \
+ catch(...) \
+ { \
+ handleUnexpectedError("Unknown exception", ERROR_LOCATION); \
+ }
+
+namespace core {
+namespace http {
+
+class TcpIpAsyncConnector :
+ public boost::enable_shared_from_this<TcpIpAsyncConnector>,
+ boost::noncopyable
+{
+public:
+ typedef boost::function<void()> ConnectedHandler;
+ typedef boost::function<void(const core::Error&)> ErrorHandler;
+
+public:
+ TcpIpAsyncConnector(boost::asio::io_service& ioService,
+ boost::asio::ip::tcp::socket* pSocket)
+ : pSocket_(pSocket),
+ resolver_(ioService)
+ {
+ }
+
+public:
+ void connect(const std::string& address,
+ const std::string& port,
+ const ConnectedHandler& connectedHandler,
+ const ErrorHandler& errorHandler)
+ {
+ // save handlers
+ connectedHandler_ = connectedHandler;
+ errorHandler_ = errorHandler;
+
+ // start an async resolve
+ boost::asio::ip::tcp::resolver::query query(address, port);
+ resolver_.async_resolve(
+ query,
+ boost::bind(&TcpIpAsyncConnector::handleResolve,
+ TcpIpAsyncConnector::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::iterator));
+ }
+
+private:
+
+ void handleResolve(
+ const boost::system::error_code& ec,
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // try endpoints until we successfully connect with one
+ boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
+ pSocket_->async_connect(
+ endpoint,
+ boost::bind(&TcpIpAsyncConnector::handleConnect,
+ TcpIpAsyncConnector::shared_from_this(),
+ boost::asio::placeholders::error,
+ ++endpoint_iterator));
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CONNECTOR_EXCEPTION
+ }
+
+ void handleConnect(
+ const boost::system::error_code& ec,
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
+ {
+ try
+ {
+ if (!ec)
+ {
+ if (connectedHandler_)
+ connectedHandler_();
+ }
+ else if (endpoint_iterator !=
+ boost::asio::ip::tcp::resolver::iterator())
+ {
+ // try next endpoint
+ pSocket_->close();
+ boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
+ pSocket_->async_connect(
+ endpoint,
+ boost::bind(&TcpIpAsyncConnector::handleConnect,
+ TcpIpAsyncConnector::shared_from_this(),
+ boost::asio::placeholders::error,
+ ++endpoint_iterator));
+ }
+ else
+ {
+ handleErrorCode(ec, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_ASYNC_CONNECTOR_EXCEPTION
+ }
+
+ void handleError(const Error& error)
+ {
+ if (errorHandler_)
+ errorHandler_(error);
+ }
+
+ void handleErrorCode(const boost::system::error_code& ec,
+ const ErrorLocation& location)
+ {
+ handleError(Error(ec, location));
+ }
+
+ void handleUnexpectedError(const std::string& description,
+ const ErrorLocation& location)
+ {
+ Error error = systemError(boost::system::errc::state_not_recoverable,
+ description,
+ location);
+ handleError(error);
+ }
+
+private:
+ boost::asio::ip::tcp::socket* pSocket_;
+ boost::asio::ip::tcp::resolver resolver_;
+ ConnectedHandler connectedHandler_;
+ ErrorHandler errorHandler_;
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_ASYNC_CONNECTOR_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpAsyncServer.hpp b/src/cpp/core/include/core/http/TcpIpAsyncServer.hpp
new file mode 100644
index 0000000..c0e06fd
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpAsyncServer.hpp
@@ -0,0 +1,48 @@
+/*
+ * TcpIpAsyncServer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_ASYNC_SERVER_HPP
+#define CORE_HTTP_TCP_IP_ASYNC_SERVER_HPP
+
+#include <boost/asio/ip/tcp.hpp>
+
+#include <core/http/AsyncServerImpl.hpp>
+#include <core/http/TcpIpSocketUtils.hpp>
+
+namespace core {
+namespace http {
+
+class TcpIpAsyncServer : public AsyncServerImpl<boost::asio::ip::tcp>
+{
+public:
+ TcpIpAsyncServer(const std::string& serverName,
+ const std::string& baseUri = std::string())
+ : AsyncServerImpl<boost::asio::ip::tcp>(serverName, baseUri)
+ {
+ }
+
+public:
+ Error init(const std::string& address, const std::string& port)
+ {
+ return initTcpIpAcceptor(acceptorService(), address, port);
+ }
+};
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_ASYNC_SERVER_HPP
+
+
diff --git a/src/cpp/core/include/core/http/TcpIpBlockingClient.hpp b/src/cpp/core/include/core/http/TcpIpBlockingClient.hpp
new file mode 100644
index 0000000..98490f7
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpBlockingClient.hpp
@@ -0,0 +1,48 @@
+/*
+ * TcpIpBlockingClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_BLOCKING_CLIENT_HPP
+#define CORE_HTTP_TCP_IP_BLOCKING_CLIENT_HPP
+
+
+#include <core/http/BlockingClient.hpp>
+
+#include <core/http/TcpIpAsyncClient.hpp>
+
+namespace core {
+namespace http {
+
+inline Error sendRequest(const std::string& address,
+ const std::string& port,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // create client
+ boost::asio::io_service ioService;
+ boost::shared_ptr<TcpIpAsyncClient> pClient(new TcpIpAsyncClient(ioService,
+ address,
+ port));
+
+ // execute blocking request
+ return sendRequest<boost::asio::ip::tcp::socket>(ioService,
+ pClient,
+ request,
+ pResponse);
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_BLOCKING_CLIENT_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpBlockingClientSsl.hpp b/src/cpp/core/include/core/http/TcpIpBlockingClientSsl.hpp
new file mode 100644
index 0000000..0263083
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpBlockingClientSsl.hpp
@@ -0,0 +1,48 @@
+/*
+ * TcpIpBlockingClientSsl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_BLOCKING_CLIENT_SSL_HPP
+#define CORE_HTTP_TCP_IP_BLOCKING_CLIENT_SSL_HPP
+
+#include <core/http/BlockingClient.hpp>
+
+#include <core/http/TcpIpAsyncClientSsl.hpp>
+
+namespace core {
+namespace http {
+
+inline Error sendSslRequest(const std::string& address,
+ const std::string& port,
+ bool verify,
+ const http::Request& request,
+ http::Response* pResponse,)
+{
+ // create client
+ boost::asio::io_service ioService;
+ boost::shared_ptr<TcpIpAsyncClientSsl> pClient(
+ new TcpIpAsyncClientSsl(ioService, address, port, verify));
+
+ // execute blocking request
+ return sendRequest<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> >
+ (ioService,
+ pClient,
+ request,
+ pResponse);
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_BLOCKING_CLIENT_SSL_HPP
diff --git a/src/cpp/core/include/core/http/TcpIpSocketUtils.hpp b/src/cpp/core/include/core/http/TcpIpSocketUtils.hpp
new file mode 100644
index 0000000..750cb5b
--- /dev/null
+++ b/src/cpp/core/include/core/http/TcpIpSocketUtils.hpp
@@ -0,0 +1,114 @@
+/*
+ * TcpIpSocketUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_TCP_IP_SOCKET_UTILS_HPP
+#define CORE_HTTP_TCP_IP_SOCKET_UTILS_HPP
+
+#include <boost/asio/ip/tcp.hpp>
+
+#include <core/Error.hpp>
+
+#include <core/http/SocketAcceptorService.hpp>
+
+namespace core {
+namespace http {
+
+template <typename SocketType>
+Error connect(boost::asio::io_service& ioService,
+ const std::string& address,
+ const std::string& port,
+ SocketType* pSocket)
+{
+ using boost::asio::ip::tcp ;
+
+ // resolve the address
+ tcp::resolver resolver(ioService);
+ tcp::resolver::query query(address, port);
+
+ boost::system::error_code ec;
+ tcp::resolver::iterator endpointIterator = resolver.resolve(query, ec);
+ if (ec)
+ return Error(ec, ERROR_LOCATION);
+
+ tcp::resolver::iterator end;
+ ec = boost::asio::error::host_not_found;
+ while (ec && endpointIterator != end)
+ {
+ // cleanup existing socket connection (if any). don't allow
+ // an error shutting down to prevent us from trying a
+ // subsequent connection
+ Error closeError = closeSocket(*pSocket);
+ if (closeError)
+ LOG_ERROR(closeError) ;
+
+ // attempt to connect
+ pSocket->connect(*endpointIterator++, ec);
+ }
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ // set tcp nodelay (propagate any errors)
+ pSocket->set_option(tcp::no_delay(true), ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+ else
+ return Success() ;
+}
+
+
+inline Error initTcpIpAcceptor(
+ SocketAcceptorService<boost::asio::ip::tcp>& acceptorService,
+ const std::string& address,
+ const std::string& port)
+{
+ using boost::asio::ip::tcp;
+
+ tcp::resolver resolver(acceptorService.ioService()) ;
+ tcp::resolver::query query(address, port) ;
+
+ boost::system::error_code ec ;
+ tcp::resolver::iterator entries = resolver.resolve(query,ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ tcp::acceptor& acceptor = acceptorService.acceptor();
+ const tcp::endpoint& endpoint = *entries ;
+ acceptor.open(endpoint.protocol(), ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ acceptor.set_option(tcp::acceptor::reuse_address(true), ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ acceptor.set_option(tcp::no_delay(true), ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ acceptor.bind(endpoint, ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ acceptor.listen(boost::asio::socket_base::max_connections, ec) ;
+ if (ec)
+ return Error(ec, ERROR_LOCATION) ;
+
+ return Success() ;
+}
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_TCP_IP_SOCKET_UTILS_HPP
diff --git a/src/cpp/core/include/core/http/URL.hpp b/src/cpp/core/include/core/http/URL.hpp
new file mode 100644
index 0000000..7a3d9a7
--- /dev/null
+++ b/src/cpp/core/include/core/http/URL.hpp
@@ -0,0 +1,136 @@
+/*
+ * URL.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_URL_HPP
+#define CORE_HTTP_URL_HPP
+
+#include <string>
+#include <iosfwd>
+
+#include <core/SafeConvert.hpp>
+
+namespace core {
+namespace http {
+
+// NOTE: The URL class is a part of shared endpoint and association caches
+// in our open-id implemetnation. we therefore need to make sure that
+// copying a URL or accessing its members never falls prey to ref-counted
+// strings which are (potentially) not threadsafe. To work around this we do
+// manual assignment of all strings during copy and assignment and have
+// all accessors return copies of new strings
+//
+
+class URL
+{
+public:
+ URL() {}
+ URL(const std::string& absoluteURL);
+
+ // implement copying and assignment to prevent any behind our back
+ // ref-counting of strings (which would cause thread-safety problems)
+
+ URL(const URL& rhs)
+ {
+ assign(rhs);
+ }
+
+ URL& operator=(const URL& rhs)
+ {
+ if (&rhs != this)
+ {
+ assign(rhs);
+ }
+ return *this;
+ }
+
+ std::string absoluteURL() const { return std::string(absoluteURL_.c_str()); }
+ bool isValid() const { return !protocol_.empty() && !host_.empty(); }
+ bool empty() const { return absoluteURL_.empty(); }
+
+ std::string protocol() const { return std::string(protocol_.c_str()); }
+ std::string host() const { return std::string(host_.c_str()); }
+ std::string path() const { return std::string(path_.c_str()); }
+ std::string hostname() const { return host_.substr(0, host_.find(':')); }
+ int port() const
+ {
+ size_t idx = host_.find(':');
+ if (idx != std::string::npos)
+ {
+ std::string port = host_.substr(idx + 1);
+ return safe_convert::stringTo(port, 80);
+ }
+ return 80;
+ }
+
+ void split(std::string* pBaseURL, std::string* pQueryParams) const;
+
+ bool operator < (const URL& other) const
+ {
+ return absoluteURL_ < other.absoluteURL_;
+ }
+
+ bool operator > (const URL& other) const
+ {
+ return absoluteURL_ > other.absoluteURL_;
+ }
+
+ bool operator == (const URL& other) const
+ {
+ return absoluteURL_ == other.absoluteURL_;
+ }
+
+ bool operator != (const URL& other) const
+ {
+ return absoluteURL_ != other.absoluteURL_;
+ }
+
+ static std::string complete(std::string absoluteUri, std::string targetUri);
+ static std::string uncomplete(std::string baseUri, std::string targetUri);
+
+ static void test();
+
+private:
+
+ void assign(const URL& rhs)
+ {
+ assign(rhs.absoluteURL_, rhs.protocol_, rhs.host_, rhs.path_);
+ }
+
+ void assign(const std::string& absoluteURL,
+ const std::string& protocol,
+ const std::string& host,
+ const std::string& path)
+ {
+ absoluteURL_.assign(absoluteURL.data(), absoluteURL.size());
+ protocol_.assign(protocol.data(), protocol.size());
+ host_.assign(host.data(), host.size());
+ path_.assign(path.data(), path.size());
+ }
+
+ std::string absoluteURL_;
+ std::string protocol_;
+ std::string host_;
+ std::string path_;
+};
+
+std::ostream& operator << (std::ostream& stream, const URL& url);
+
+
+
+} // namespace http
+} // namespace core
+
+
+#endif // CORE_HTTP_URL_HPP
diff --git a/src/cpp/core/include/core/http/UriHandler.hpp b/src/cpp/core/include/core/http/UriHandler.hpp
new file mode 100644
index 0000000..173bc13
--- /dev/null
+++ b/src/cpp/core/include/core/http/UriHandler.hpp
@@ -0,0 +1,93 @@
+/*
+ * UriHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_URI_HANDLER_HPP
+#define CORE_HTTP_URI_HANDLER_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+
+#include <core/http/Response.hpp>
+
+namespace core {
+namespace http {
+
+class Request;
+
+typedef boost::function<void(Response*)> UriHandlerFunctionContinuation ;
+
+// UriHandlerFunction concept
+typedef boost::function<void(const Request&,const UriHandlerFunctionContinuation&)>
+ UriAsyncHandlerFunction ;
+
+// UriHandlerFunction concept
+typedef boost::function<void(const Request&,Response*)> UriHandlerFunction ;
+
+// UriFilterFunction concept - return true if the filter handled the request
+typedef boost::function<bool(const http::Request&, http::Response*)>
+ UriFilterFunction;
+
+class UriHandler
+{
+public:
+ UriHandler(const std::string& prefix, const UriAsyncHandlerFunction& function);
+ UriHandler(const std::string& prefix, const UriHandlerFunction& function);
+
+ // COPYING: via compiler
+
+ bool matches(const std::string& uri) const;
+
+ UriAsyncHandlerFunction function() const;
+
+ // implement UriHandlerFunction concept
+ void operator()(const Request& request,
+ const UriHandlerFunctionContinuation& cont) const;
+
+private:
+ std::string prefix_;
+ UriAsyncHandlerFunction function_ ;
+};
+
+class UriHandlers
+{
+public:
+ UriHandlers() {}
+
+ // COPYING: via compiler
+
+ void add(const UriHandler& handler);
+
+ UriAsyncHandlerFunction handlerFor(const std::string& uri) const;
+
+private:
+ std::vector<UriHandler> uriHandlers_;
+};
+
+inline void notFoundHandler(const Request& request, Response* pResponse)
+{
+ pResponse->setStatusCode(http::status::NotFound);
+ pResponse->setContentType("text/plain");
+ pResponse->setBody(request.uri() + " not found");
+}
+
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_URI_HANDLER_HPP
+
+
diff --git a/src/cpp/core/include/core/http/Util.hpp b/src/cpp/core/include/core/http/Util.hpp
new file mode 100644
index 0000000..6bd779a
--- /dev/null
+++ b/src/cpp/core/include/core/http/Util.hpp
@@ -0,0 +1,158 @@
+/*
+ * Util.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_HTTP_UTIL_HPP
+#define CORE_HTTP_UTIL_HPP
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+
+class Error;
+
+namespace http {
+
+class Request;
+
+typedef std::pair<std::string,std::string> Field;
+typedef std::vector<Field> Fields;
+
+class FieldPredicate
+{
+public:
+ FieldPredicate(const std::string& name)
+ : name_(name)
+ {
+ }
+ bool operator()(const Field& field)
+ {
+ return name_.compare(field.first) == 0;
+ }
+private:
+ std::string name_ ;
+};
+
+struct File
+{
+ bool empty() const { return name.empty(); }
+ std::string name;
+ std::string contentType;
+ std::string contents;
+};
+
+typedef std::map<std::string,File> Files;
+
+namespace util {
+
+Fields::const_iterator findField(const Fields& fields, const std::string& name);
+std::string fieldValue(const Fields& fields, const std::string& name);
+
+template <typename T>
+T fieldValue(const Fields& fields, const std::string& name, const T& defaultVal)
+{
+ Fields::const_iterator pos = findField(fields, name);
+ if (pos != fields.end())
+ {
+ try
+ {
+ return boost::lexical_cast<T>(pos->second);
+ }
+ catch(boost::bad_lexical_cast&)
+ {
+ return defaultVal;
+ }
+ }
+ else // not found, return default
+ {
+ return defaultVal;
+ }
+}
+
+template <typename T, typename Predicate>
+bool fieldValue(const Fields& fields,
+ const std::string& name,
+ const Predicate& validator,
+ T* pValue)
+{
+ Fields::const_iterator pos = findField(fields, name);
+ if (pos != fields.end())
+ {
+ try
+ {
+ *pValue = boost::lexical_cast<T>(pos->second);
+ return validator(*pValue);
+ }
+ catch(boost::bad_lexical_cast&)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+
+enum FieldDecodeType
+{
+ FieldDecodeNone,
+ FieldDecodeForm,
+ FieldDecodeQueryString
+};
+
+void parseFields(const std::string& fields,
+ const char* fieldDelim,
+ const char* valueDelim,
+ Fields* pFields,
+ FieldDecodeType fieldDecode) ;
+
+void parseForm(const std::string& body, Fields* pFields);
+
+void parseMultipartForm(const std::string& contentType,
+ const std::string& body,
+ Fields* pFields,
+ Files* pFiles);
+
+void buildQueryString(const Fields& fields, std::string* pQueryString);
+void parseQueryString(const std::string& queryString, Fields* pFields);
+
+std::string urlEncode(const std::string& in, bool queryStringSpaces = false);
+std::string urlDecode(const std::string& in, bool fromQueryString = false);
+
+
+boost::posix_time::ptime parseHttpDate(const std::string& date);
+
+boost::posix_time::ptime parseAtomDate(const std::string& date);
+
+std::string httpDate(const boost::posix_time::ptime& datetime =
+ boost::posix_time::second_clock::universal_time());
+
+
+std::string pathAfterPrefix(const Request& request,
+ const std::string& pathPrefix);
+
+} // namespace util
+
+} // namespace http
+} // namespace core
+
+#endif // CORE_HTTP_UTIL_HPP
diff --git a/src/cpp/core/include/core/json/Json.hpp b/src/cpp/core/include/core/json/Json.hpp
new file mode 100644
index 0000000..e501418
--- /dev/null
+++ b/src/cpp/core/include/core/json/Json.hpp
@@ -0,0 +1,91 @@
+/*
+ * Json.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_JSON_HPP
+#define CORE_JSON_HPP
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+#include <boost/type_traits/is_same.hpp>
+
+#include <core/json/spirit/json_spirit_value.h>
+
+namespace core {
+namespace json {
+
+// alias json_spirit type constants
+extern json_spirit::Value_type ObjectType;
+extern json_spirit::Value_type ArrayType;
+extern json_spirit::Value_type StringType;
+extern json_spirit::Value_type BooleanType;
+extern json_spirit::Value_type IntegerType;
+extern json_spirit::Value_type RealType;
+extern json_spirit::Value_type NullType;
+
+// alias json_spirit value and collection types
+typedef json_spirit::Value_impl<json_spirit::mConfig> Value;
+typedef json_spirit::mConfig::Array_type Array;
+typedef json_spirit::mConfig::Object_type Object;
+typedef Object::value_type Member;
+
+template <typename T>
+bool isType(const Value& value)
+{
+ if (value.is_null())
+ return false;
+ else if (boost::is_same<T, Object>::value)
+ return value.type() == ObjectType;
+ else if (boost::is_same<T, Array>::value)
+ return value.type() == ArrayType;
+ else if (boost::is_same<T, std::string>::value)
+ return value.type() == StringType;
+ else if (boost::is_same<T, bool>::value)
+ return value.type() == BooleanType;
+ else if (boost::is_same<T, int>::value)
+ return value.type() == IntegerType;
+ else if (boost::is_same<T, double>::value)
+ return value.type() == RealType || value.type() == IntegerType;
+ else
+ return false;
+}
+
+template<typename T>
+json::Value toJsonValue(const T& val)
+{
+ return json::Value(val);
+}
+
+json::Value toJsonString(const std::string& val);
+
+template<typename T>
+json::Array toJsonArray(const std::vector<T>& val)
+{
+ json::Array results;
+ std::copy(val.begin(), val.end(), std::back_inserter(results));
+ return results;
+}
+
+bool parse(const std::string& input, Value* pValue);
+
+void write(const Value& value, std::ostream& os);
+void writeFormatted(const Value& value, std::ostream& os);
+
+} // namespace json
+} // namespace core
+
+#endif // CORE_JSON_HPP
+
diff --git a/src/cpp/core/include/core/json/JsonRpc.hpp b/src/cpp/core/include/core/json/JsonRpc.hpp
new file mode 100644
index 0000000..5e2bab4
--- /dev/null
+++ b/src/cpp/core/include/core/json/JsonRpc.hpp
@@ -0,0 +1,1037 @@
+/*
+ * JsonRpc.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_JSON_JSON_RPC_HPP
+#define CORE_JSON_JSON_RPC_HPP
+
+#include <boost/system/error_code.hpp>
+
+namespace core {
+namespace json {
+namespace errc {
+
+enum errc_t {
+ Success = 0, // request succeeded
+
+ //
+ // Invocation Errors -- All of these errors are guaranteed to have occurred
+ // prior to the execution of the method on the service
+ //
+ ConnectionError = 1, // unable to connect to the service
+ Unavailable = 2, // service is currently unavailable
+ Unauthorized = 3, // client does not have required credentials
+ InvalidClientId = 4, // provided client id is invalid
+ ParseError = 5, // invalid json or an unexpected error during parsing
+ InvalidRequest = 6, // invalid json-rpc request
+ MethodNotFound = 7, // specified method not found on the server
+ ParamMissing = 8, // parameter missing
+ ParamTypeMismatch = 9, // parameter type mismatch
+ ParamInvalid = 10, // parameter invalid
+ MethodUnexpected = 11, // method unexpected for current application state
+ InvalidClientVersion = 12, // client is running an invalid version
+ ServerOffline = 13, // server is offline
+
+ // Execution errors -- These errors occurred during execution of the method.
+ // Application state is therefore known based on the expected behavior
+ // of the error which occurred. More details are provided within the
+ // optional "error" field of the result
+ ExecutionError = 100,
+
+ // Transmission errors -- These errors leave the application in an unknown
+ // state (it is not known whether the method finished all, some, or none
+ // of its work).
+ TransmissionError = 200
+};
+
+} // namespace errc
+} // namespace json
+} // namespace core
+
+namespace boost {
+namespace system {
+template <>
+struct is_error_code_enum<core::json::errc::errc_t>
+ { static const bool value = true; };
+} // namespace system
+} // namespace boost
+
+
+
+#include <string>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/optional.hpp>
+#include <boost/unordered_map.hpp>
+
+#include <core/Error.hpp>
+#include <core/json/Json.hpp>
+
+namespace core {
+namespace http {
+ class Response ;
+}
+}
+
+namespace core {
+namespace json {
+
+// constants
+extern const char * const kRpcResult;
+extern const char * const kRpcError;
+extern const char * const kJsonContentType ;
+
+
+// jsonRpcCategory
+const boost::system::error_category& jsonRpcCategory() ;
+
+
+//
+// json error codes
+//
+namespace errc {
+
+inline boost::system::error_code make_error_code( errc_t e )
+{
+ return boost::system::error_code( e, jsonRpcCategory() ); }
+
+inline boost::system::error_condition make_error_condition( errc_t e )
+{
+ return boost::system::error_condition( e, jsonRpcCategory() );
+}
+
+} // namespace errc
+} // namespace json
+} // namespace core
+
+namespace core {
+namespace json {
+
+struct JsonRpcRequest
+{
+ JsonRpcRequest() : version(0), isBackgroundConnection(false) {}
+
+ std::string method ;
+ json::Array params ;
+ json::Object kwparams ;
+ std::string sourceWindow;
+ std::string clientId ;
+ double version;
+ bool isBackgroundConnection ;
+
+ bool empty() const { return method.empty(); }
+
+ void clear()
+ {
+ method.clear() ;
+ params.clear() ;
+ kwparams.clear() ;
+ }
+};
+
+//
+// json request parsing
+//
+Error parseJsonRpcRequest(const std::string& input, JsonRpcRequest* pRequest) ;
+
+bool parseJsonRpcRequestForMethod(const std::string& input,
+ const std::string& method,
+ JsonRpcRequest* pRequest,
+ http::Response* pResponse);
+
+
+//
+// json parameter reading helpers
+//
+
+
+inline core::Error readParam(const json::Array& params,
+ unsigned int index,
+ json::Value* pValue)
+{
+ if (index >= params.size())
+ return core::Error(errc::ParamMissing, ERROR_LOCATION);
+
+ *pValue = params[index] ;
+ return Success();
+}
+
+template <typename T>
+core::Error readParam(const json::Array& params, unsigned int index, T* pValue)
+{
+ if (index >= params.size())
+ return core::Error(errc::ParamMissing, ERROR_LOCATION);
+
+ if (!isType<T>(params[index]))
+ return core::Error(errc::ParamTypeMismatch, ERROR_LOCATION) ;
+
+ *pValue = params[index].get_value<T>();
+
+ return Success() ;
+}
+
+template <typename T1>
+core::Error readParams(const json::Array& params, T1* pValue1)
+{
+ return readParam(params, 0, pValue1) ;
+}
+
+template <typename T1, typename T2>
+core::Error readParams(const json::Array& params, T1* pValue1, T2* pValue2)
+{
+ core::Error error = readParam(params, 0, pValue1) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 1, pValue2) ;
+}
+
+template <typename T1, typename T2, typename T3>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3)
+{
+ core::Error error = readParams(params, pValue1, pValue2) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 2, pValue3) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4)
+{
+ core::Error error = readParams(params, pValue1, pValue2, pValue3) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 3, pValue4) ;
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5)
+{
+ core::Error error = readParams(params, pValue1, pValue2, pValue3, pValue4) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 4, pValue5) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+ typename T6>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 5, pValue6) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+typename T6, typename T7>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6,
+ T7* pValue7)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5,
+ pValue6) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 6, pValue7) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+typename T6, typename T7, typename T8>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6,
+ T7* pValue7,
+ T8* pValue8)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5,
+ pValue6,
+ pValue7) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 7, pValue8) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+typename T6, typename T7, typename T8, typename T9>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6,
+ T7* pValue7,
+ T8* pValue8,
+ T9* pValue9)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5,
+ pValue6,
+ pValue7,
+ pValue8) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 8, pValue9) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+typename T6, typename T7, typename T8, typename T9, typename T10>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6,
+ T7* pValue7,
+ T8* pValue8,
+ T9* pValue9,
+ T10* pValue10)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5,
+ pValue6,
+ pValue7,
+ pValue8,
+ pValue9) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 9, pValue10) ;
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5,
+typename T6, typename T7, typename T8, typename T9, typename T10, typename T11>
+core::Error readParams(const json::Array& params,
+ T1* pValue1,
+ T2* pValue2,
+ T3* pValue3,
+ T4* pValue4,
+ T5* pValue5,
+ T6* pValue6,
+ T7* pValue7,
+ T8* pValue8,
+ T9* pValue9,
+ T10* pValue10,
+ T11* pValue11)
+{
+ core::Error error = readParams(params,
+ pValue1,
+ pValue2,
+ pValue3,
+ pValue4,
+ pValue5,
+ pValue6,
+ pValue7,
+ pValue8,
+ pValue9,
+ pValue10) ;
+ if (error)
+ return error ;
+
+ return readParam(params, 10, pValue11) ;
+}
+
+template <typename T>
+core::Error readObject(const json::Object& object,
+ const std::string& name,
+ T* pValue)
+{
+ json::Object::const_iterator it = object.find(name) ;
+ if (it == object.end())
+ return Error(errc::ParamMissing, ERROR_LOCATION) ;
+
+ if (!isType<T>(it->second))
+ return Error(errc::ParamTypeMismatch, ERROR_LOCATION) ;
+
+ *pValue = it->second.get_value<T>() ;
+
+ return Success() ;
+}
+
+template <typename T>
+core::Error readObject(const json::Object& object,
+ const std::string& name,
+ const T& defaultValue,
+ T* pValue)
+{
+ json::Object::const_iterator it = object.find(name) ;
+ if (it == object.end())
+ {
+ *pValue = defaultValue;
+ return Success();
+ }
+
+ if (!isType<T>(it->second))
+ return Error(errc::ParamTypeMismatch, ERROR_LOCATION) ;
+
+ *pValue = it->second.get_value<T>() ;
+
+ return Success() ;
+}
+
+template <typename T>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name,
+ T* pValue)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object, name, pValue);
+}
+
+
+template <typename T1, typename T2>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2)
+{
+ Error error = readObject(object, name1, pValue1);
+ if (error)
+ return error;
+
+ return readObject(object, name2, pValue2);
+}
+
+template <typename T1, typename T2>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object, name1, pValue1, name2, pValue2);
+}
+
+template <typename T1, typename T2, typename T3>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3)
+{
+ Error error = readObject(object, name1, pValue1, name2, pValue2);
+ if (error)
+ return error;
+
+ return readObject(object, name3, pValue3);
+}
+
+template <typename T1, typename T2, typename T3>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object, name1, pValue1, name2, pValue2, name3, pValue3);
+}
+
+template <typename T1, typename T2, typename T3, typename T4>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3);
+ if (error)
+ return error;
+
+ return readObject(object, name4, pValue4);
+}
+
+template <typename T1, typename T2, typename T3, typename T4>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4);
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4);
+ if (error)
+ return error;
+
+ return readObject(object, name5, pValue5);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5);
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5);
+ if (error)
+ return error;
+
+ return readObject(object, name6, pValue6);
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6);
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6);
+ if (error)
+ return error;
+
+ return readObject(object, name7, pValue7);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7);
+ if (error)
+ return error;
+
+ return readObject(object, name8, pValue8);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8,
+ const std::string& name9, T9* pValue9)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8);
+ if (error)
+ return error;
+
+ return readObject(object, name9, pValue9);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8,
+ const std::string& name9, T9* pValue9)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8,
+ name9, pValue9);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8,
+ const std::string& name9, T9* pValue9,
+ const std::string& name10, T10* pValue10)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8,
+ name9, pValue9);
+ if (error)
+ return error;
+
+ return readObject(object, name10, pValue10);
+}
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10>
+core::Error readObjectParam(const json::Array& params,
+ unsigned int index,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8,
+ const std::string& name9, T9* pValue9,
+ const std::string& name10, T10* pValue10)
+{
+ json::Object object;
+ Error error = json::readParam(params, index, &object);
+ if (error)
+ return error;
+
+ return readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8,
+ name9, pValue9,
+ name10, pValue10);
+}
+
+
+template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10, typename T11>
+core::Error readObject(const json::Object& object,
+ const std::string& name1, T1* pValue1,
+ const std::string& name2, T2* pValue2,
+ const std::string& name3, T3* pValue3,
+ const std::string& name4, T4* pValue4,
+ const std::string& name5, T5* pValue5,
+ const std::string& name6, T6* pValue6,
+ const std::string& name7, T7* pValue7,
+ const std::string& name8, T8* pValue8,
+ const std::string& name9, T9* pValue9,
+ const std::string& name10, T10* pValue10,
+ const std::string& name11, T11* pValue11)
+{
+ Error error = readObject(object,
+ name1, pValue1,
+ name2, pValue2,
+ name3, pValue3,
+ name4, pValue4,
+ name5, pValue5,
+ name6, pValue6,
+ name7, pValue7,
+ name8, pValue8,
+ name9, pValue9,
+ name10, pValue10);
+ if (error)
+ return error;
+
+ return readObject(object, name11, pValue11);
+}
+
+// json rpc response
+
+class JsonRpcResponse
+{
+public:
+ JsonRpcResponse() : suppressDetectChanges_(false)
+ {
+ setResult(json::Value());
+ };
+
+ // COPYING: via compiler (copyable members)
+
+public:
+
+ template <typename T>
+ void setResult(const T& result)
+ {
+ setField(kRpcResult, result);
+ }
+
+ json::Value& result()
+ {
+ return response_[kRpcResult];
+ }
+
+ void setError(const core::Error& error);
+
+ void setError(const core::Error& error, const json::Value& clientInfo);
+
+ void setError(const boost::system::error_code& ec);
+
+ void setAsyncHandle(const std::string& handle);
+
+ void setField(const std::string& name, const json::Value& value)
+ {
+ response_[name] = value;
+ }
+
+ template <typename T>
+ void setField(const std::string& name, const T& value)
+ {
+ setField(name, json::Value(value));
+ }
+
+ // low level hook to set the full response
+ void setResponse(const json::Object& response)
+ {
+ response_ = response;
+ }
+
+ // specify a function to run after the response
+ void setAfterResponse(const boost::function<void()>& afterResponse);
+ bool hasAfterResponse() const;
+ void runAfterResponse();
+
+ bool suppressDetectChanges() { return suppressDetectChanges_; }
+ void setSuppressDetectChanges(bool suppress)
+ {
+ suppressDetectChanges_ = suppress;
+ }
+
+ json::Object getRawResponse();
+
+ void write(std::ostream& os) const;
+
+private:
+ json::Object response_;
+ boost::function<void()> afterResponse_ ;
+ bool suppressDetectChanges_;
+};
+
+
+// convenience functions for sending json-rpc responses
+
+void setJsonRpcResponse(const JsonRpcResponse& jsonRpcResponse,
+ http::Response* pResponse);
+
+
+inline void setVoidJsonRpcResult(http::Response* pResponse)
+{
+ JsonRpcResponse jsonRpcResponse;
+ setJsonRpcResponse(jsonRpcResponse, pResponse);
+}
+
+
+template <typename T>
+void setJsonRpcResult(const T& result, http::Response* pResponse)
+{
+ JsonRpcResponse jsonRpcResponse ;
+ jsonRpcResponse.setResult(result);
+ setJsonRpcResponse(jsonRpcResponse, pResponse);
+}
+
+template <typename T>
+void setJsonRpcError(const T& error, core::http::Response* pResponse)
+{
+ JsonRpcResponse jsonRpcResponse ;
+ jsonRpcResponse.setError(error);
+ setJsonRpcResponse(jsonRpcResponse, pResponse);
+}
+
+
+// convenience typedefs for managing a map of json rpc functions
+typedef boost::function<core::Error(const core::json::JsonRpcRequest&, core::json::JsonRpcResponse*)>
+ JsonRpcFunction ;
+typedef std::pair<std::string,core::json::JsonRpcFunction>
+ JsonRpcMethod ;
+typedef boost::unordered_map<std::string,JsonRpcFunction>
+ JsonRpcMethods;
+
+/*
+ Async method support -- JsonRpcAsyncFunction is intended for potentially
+ long running operations that need to keep the HTTP connection open until
+ their work is done. (See registerRpcAsyncCoupleMethod for a different
+ mechanism that provides similar functionality, but closes the HTTP
+ connection and uses an event to simulate returning a result to the client.)
+*/
+
+// JsonRpcFunctionContinuation is what a JsonRpcAsyncFunction needs to call
+// when its work is complete
+typedef boost::function<void(const core::Error&, core::json::JsonRpcResponse*)>
+ JsonRpcFunctionContinuation ;
+typedef boost::function<void(const core::json::JsonRpcRequest&, const JsonRpcFunctionContinuation&)>
+ JsonRpcAsyncFunction ;
+// The bool in the next two typedefs specifies whether the function wants the
+// HTTP connection to stay open until the method finishes executing (direct return),
+// or for the HTTP connection to immediate return with an "asyncHandle" value that
+// can be used to look in the event stream later when the method completes (indirect
+// return). Direct return provides lower latency for short operations, and indirect
+// return must be used for longer-running operations to prevent the browser from
+// being starved of available HTTP connections to the server.
+typedef std::pair<std::string,std::pair<bool, core::json::JsonRpcAsyncFunction> >
+ JsonRpcAsyncMethod ;
+typedef boost::unordered_map<std::string,std::pair<bool, JsonRpcAsyncFunction> >
+ JsonRpcAsyncMethods ;
+
+JsonRpcAsyncFunction adaptToAsync(JsonRpcFunction synchronousFunction);
+JsonRpcAsyncMethod adaptMethodToAsync(JsonRpcMethod synchronousMethod);
+
+} // namespace json
+} // namespace core
+
+
+
+#endif // CORE_JSON_JSON_RPC_HPP
+
diff --git a/src/cpp/core/include/core/json/spirit/json_spirit_value.h b/src/cpp/core/include/core/json/spirit/json_spirit_value.h
new file mode 100644
index 0000000..b18b513
--- /dev/null
+++ b/src/cpp/core/include/core/json/spirit/json_spirit_value.h
@@ -0,0 +1,532 @@
+#ifndef JSON_SPIRIT_VALUE
+#define JSON_SPIRIT_VALUE
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include <vector>
+#include <map>
+#include <string>
+#include <cassert>
+#include <sstream>
+#include <stdexcept>
+#include <boost/config.hpp>
+#include <boost/cstdint.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/variant.hpp>
+
+namespace json_spirit
+{
+ enum Value_type{ obj_type, array_type, str_type, bool_type, int_type, real_type, null_type };
+
+ template< class Config > // Config determines whether the value uses std::string or std::wstring and
+ // whether JSON Objects are represented as vectors or maps
+ class Value_impl
+ {
+ public:
+
+ typedef Config Config_type;
+ typedef typename Config::String_type String_type;
+ typedef typename Config::Object_type Object;
+ typedef typename Config::Array_type Array;
+ typedef typename String_type::const_pointer Const_str_ptr; // eg const char*
+
+ Value_impl(); // creates null value
+ Value_impl( Const_str_ptr value );
+ Value_impl( const String_type& value );
+ Value_impl( const Object& value );
+ Value_impl( const Array& value );
+ Value_impl( bool value );
+ Value_impl( int value );
+ Value_impl( boost::int64_t value );
+ Value_impl( boost::uint64_t value );
+ Value_impl( double value );
+
+ Value_impl( const Value_impl& other );
+
+ bool operator==( const Value_impl& lhs ) const;
+
+ Value_impl& operator=( const Value_impl& lhs );
+
+ Value_type type() const;
+
+ bool is_uint64() const;
+ bool is_null() const;
+
+ const String_type& get_str() const;
+ const Object& get_obj() const;
+ const Array& get_array() const;
+ bool get_bool() const;
+ int get_int() const;
+ boost::int64_t get_int64() const;
+ boost::uint64_t get_uint64() const;
+ double get_real() const;
+
+ Object& get_obj();
+ Array& get_array();
+
+ template< typename T > T get_value() const; // example usage: int i = value.get_value< int >();
+ // or double d = value.get_value< double >();
+
+ static const Value_impl null;
+
+ private:
+
+ void check_type( const Value_type vtype ) const;
+
+ typedef boost::variant< String_type,
+ boost::recursive_wrapper< Object >, boost::recursive_wrapper< Array >,
+ bool, boost::int64_t, double > Variant;
+
+ Value_type type_;
+ Variant v_;
+ bool is_uint64_;
+ };
+
+ // vector objects
+
+ template< class Config >
+ struct Pair_impl
+ {
+ typedef typename Config::String_type String_type;
+ typedef typename Config::Value_type Value_type;
+
+ Pair_impl( const String_type& name, const Value_type& value );
+
+ bool operator==( const Pair_impl& lhs ) const;
+
+ String_type name_;
+ Value_type value_;
+ };
+
+ template< class String >
+ struct Config_vector
+ {
+ typedef String String_type;
+ typedef Value_impl< Config_vector > Value_type;
+ typedef Pair_impl < Config_vector > Pair_type;
+ typedef std::vector< Value_type > Array_type;
+ typedef std::vector< Pair_type > Object_type;
+
+ static Value_type& add( Object_type& obj, const String_type& name, const Value_type& value )
+ {
+ obj.push_back( Pair_type( name , value ) );
+
+ return obj.back().value_;
+ }
+
+ static String_type get_name( const Pair_type& pair )
+ {
+ return pair.name_;
+ }
+
+ static Value_type get_value( const Pair_type& pair )
+ {
+ return pair.value_;
+ }
+ };
+
+ // typedefs for ASCII
+
+ typedef Config_vector< std::string > Config;
+
+ typedef Config::Value_type Value;
+ typedef Config::Pair_type Pair;
+ typedef Config::Object_type Object;
+ typedef Config::Array_type Array;
+
+ // typedefs for Unicode
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ typedef Config_vector< std::wstring > wConfig;
+
+ typedef wConfig::Value_type wValue;
+ typedef wConfig::Pair_type wPair;
+ typedef wConfig::Object_type wObject;
+ typedef wConfig::Array_type wArray;
+#endif
+
+ // map objects
+
+ template< class String >
+ struct Config_map
+ {
+ typedef String String_type;
+ typedef Value_impl< Config_map > Value_type;
+ typedef std::vector< Value_type > Array_type;
+ typedef std::map< String_type, Value_type > Object_type;
+ typedef typename Object_type::value_type Pair_type;
+
+ static Value_type& add( Object_type& obj, const String_type& name, const Value_type& value )
+ {
+ return obj[ name ] = value;
+ }
+
+ static String_type get_name( const Pair_type& pair )
+ {
+ return pair.first;
+ }
+
+ static Value_type get_value( const Pair_type& pair )
+ {
+ return pair.second;
+ }
+ };
+
+ // typedefs for ASCII
+
+ typedef Config_map< std::string > mConfig;
+
+ typedef mConfig::Value_type mValue;
+ typedef mConfig::Object_type mObject;
+ typedef mConfig::Array_type mArray;
+
+ // typedefs for Unicode
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ typedef Config_map< std::wstring > wmConfig;
+
+ typedef wmConfig::Value_type wmValue;
+ typedef wmConfig::Object_type wmObject;
+ typedef wmConfig::Array_type wmArray;
+
+#endif
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // implementation
+
+ template< class Config >
+ const Value_impl< Config > Value_impl< Config >::null;
+
+ template< class Config >
+ Value_impl< Config >::Value_impl()
+ : type_( null_type )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( const Const_str_ptr value )
+ : type_( str_type )
+ , v_( String_type( value ) )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( const String_type& value )
+ : type_( str_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( const Object& value )
+ : type_( obj_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( const Array& value )
+ : type_( array_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( bool value )
+ : type_( bool_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( int value )
+ : type_( int_type )
+ , v_( static_cast< boost::int64_t >( value ) )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( boost::int64_t value )
+ : type_( int_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( boost::uint64_t value )
+ : type_( int_type )
+ , v_( static_cast< boost::int64_t >( value ) )
+ , is_uint64_( true )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( double value )
+ : type_( real_type )
+ , v_( value )
+ , is_uint64_( false )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >::Value_impl( const Value_impl< Config >& other )
+ : type_( other.type() )
+ , v_( other.v_ )
+ , is_uint64_( other.is_uint64_ )
+ {
+ }
+
+ template< class Config >
+ Value_impl< Config >& Value_impl< Config >::operator=( const Value_impl& lhs )
+ {
+ Value_impl tmp( lhs );
+
+ std::swap( type_, tmp.type_ );
+ std::swap( v_, tmp.v_ );
+ std::swap( is_uint64_, tmp.is_uint64_ );
+
+ return *this;
+ }
+
+ template< class Config >
+ bool Value_impl< Config >::operator==( const Value_impl& lhs ) const
+ {
+ if( this == &lhs ) return true;
+
+ if( type() != lhs.type() ) return false;
+
+ return v_ == lhs.v_;
+ }
+
+ template< class Config >
+ Value_type Value_impl< Config >::type() const
+ {
+ return type_;
+ }
+
+ template< class Config >
+ bool Value_impl< Config >::is_uint64() const
+ {
+ return is_uint64_;
+ }
+
+ template< class Config >
+ bool Value_impl< Config >::is_null() const
+ {
+ return type() == null_type;
+ }
+
+ template< class Config >
+ void Value_impl< Config >::check_type( const Value_type vtype ) const
+ {
+ if( type() != vtype )
+ {
+ std::ostringstream os;
+
+ os << "value type is " << type() << " not " << vtype;
+
+ throw std::runtime_error( os.str() );
+ }
+ }
+
+ template< class Config >
+ const typename Config::String_type& Value_impl< Config >::get_str() const
+ {
+ check_type( str_type );
+
+ return *boost::get< String_type >( &v_ );
+ }
+
+ template< class Config >
+ const typename Value_impl< Config >::Object& Value_impl< Config >::get_obj() const
+ {
+ check_type( obj_type );
+
+ return *boost::get< Object >( &v_ );
+ }
+
+ template< class Config >
+ const typename Value_impl< Config >::Array& Value_impl< Config >::get_array() const
+ {
+ check_type( array_type );
+
+ return *boost::get< Array >( &v_ );
+ }
+
+ template< class Config >
+ bool Value_impl< Config >::get_bool() const
+ {
+ check_type( bool_type );
+
+ return boost::get< bool >( v_ );
+ }
+
+ template< class Config >
+ int Value_impl< Config >::get_int() const
+ {
+ check_type( int_type );
+
+ return static_cast< int >( get_int64() );
+ }
+
+ template< class Config >
+ boost::int64_t Value_impl< Config >::get_int64() const
+ {
+ check_type( int_type );
+
+ return boost::get< boost::int64_t >( v_ );
+ }
+
+ template< class Config >
+ boost::uint64_t Value_impl< Config >::get_uint64() const
+ {
+ check_type( int_type );
+
+ return static_cast< boost::uint64_t >( get_int64() );
+ }
+
+ template< class Config >
+ double Value_impl< Config >::get_real() const
+ {
+ if( type() == int_type )
+ {
+ return is_uint64() ? static_cast< double >( get_uint64() )
+ : static_cast< double >( get_int64() );
+ }
+
+ check_type( real_type );
+
+ return boost::get< double >( v_ );
+ }
+
+ template< class Config >
+ typename Value_impl< Config >::Object& Value_impl< Config >::get_obj()
+ {
+ check_type( obj_type );
+
+ return *boost::get< Object >( &v_ );
+ }
+
+ template< class Config >
+ typename Value_impl< Config >::Array& Value_impl< Config >::get_array()
+ {
+ check_type( array_type );
+
+ return *boost::get< Array >( &v_ );
+ }
+
+ template< class Config >
+ Pair_impl< Config >::Pair_impl( const String_type& name, const Value_type& value )
+ : name_( name )
+ , value_( value )
+ {
+ }
+
+ template< class Config >
+ bool Pair_impl< Config >::operator==( const Pair_impl< Config >& lhs ) const
+ {
+ if( this == &lhs ) return true;
+
+ return ( name_ == lhs.name_ ) && ( value_ == lhs.value_ );
+ }
+
+ // converts a C string, ie. 8 bit char array, to a string object
+ //
+ template < class String_type >
+ String_type to_str( const char* c_str )
+ {
+ String_type result;
+
+ for( const char* p = c_str; *p != 0; ++p )
+ {
+ result += *p;
+ }
+
+ return result;
+ }
+
+ //
+
+ namespace internal_
+ {
+ template< typename T >
+ struct Type_to_type
+ {
+ };
+
+ template< class Value >
+ int get_value( const Value& value, Type_to_type< int > )
+ {
+ return value.get_int();
+ }
+
+ template< class Value >
+ boost::int64_t get_value( const Value& value, Type_to_type< boost::int64_t > )
+ {
+ return value.get_int64();
+ }
+
+ template< class Value >
+ boost::uint64_t get_value( const Value& value, Type_to_type< boost::uint64_t > )
+ {
+ return value.get_uint64();
+ }
+
+ template< class Value >
+ double get_value( const Value& value, Type_to_type< double > )
+ {
+ return value.get_real();
+ }
+
+ template< class Value >
+ typename Value::String_type get_value( const Value& value, Type_to_type< typename Value::String_type > )
+ {
+ return value.get_str();
+ }
+
+ template< class Value >
+ typename Value::Array get_value( const Value& value, Type_to_type< typename Value::Array > )
+ {
+ return value.get_array();
+ }
+
+ template< class Value >
+ typename Value::Object get_value( const Value& value, Type_to_type< typename Value::Object > )
+ {
+ return value.get_obj();
+ }
+
+ template< class Value >
+ bool get_value( const Value& value, Type_to_type< bool > )
+ {
+ return value.get_bool();
+ }
+ }
+
+ template< class Config >
+ template< typename T >
+ T Value_impl< Config >::get_value() const
+ {
+ return internal_::get_value( *this, internal_::Type_to_type< T >() );
+ }
+}
+
+#endif
diff --git a/src/cpp/core/include/core/markdown/Markdown.hpp b/src/cpp/core/include/core/markdown/Markdown.hpp
new file mode 100644
index 0000000..83e9f97
--- /dev/null
+++ b/src/cpp/core/include/core/markdown/Markdown.hpp
@@ -0,0 +1,108 @@
+/*
+ * Markdown.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_MARKDOWN_MARKDOWN_HPP
+#define CORE_MARKDOWN_MARKDOWN_HPP
+
+#include <string>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace markdown {
+
+struct Extensions
+{
+ Extensions()
+ : noIntraEmphasis(true),
+ tables(true),
+ fencedCode(true),
+ autolink(true),
+ laxSpacing(true),
+ spaceHeaders(true),
+ strikethrough(true),
+ superscript(true),
+ ignoreMath(true),
+ stripMetadata(true)
+ {
+ }
+
+ bool noIntraEmphasis;
+ bool tables;
+ bool fencedCode;
+ bool autolink;
+ bool laxSpacing;
+ bool spaceHeaders;
+ bool strikethrough;
+ bool superscript;
+ bool ignoreMath;
+ bool stripMetadata;
+};
+
+struct HTMLOptions
+{
+ HTMLOptions()
+ : useXHTML(true),
+ hardWrap(false),
+ smartypants(true),
+ safelink(false),
+ toc(false),
+ skipHTML(false),
+ skipStyle(false),
+ skipImages(false),
+ skipLinks(false),
+ escape(false)
+ {
+ }
+ bool useXHTML;
+ bool hardWrap;
+ bool smartypants;
+ bool safelink;
+ bool toc;
+ bool skipHTML;
+ bool skipStyle;
+ bool skipImages;
+ bool skipLinks;
+ bool escape;
+};
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const FilePath& markdownFile,
+ const Extensions& extensions,
+ const HTMLOptions& htmlOptions,
+ const FilePath& htmlFile);
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const FilePath& markdownFile,
+ const Extensions& extensions,
+ const HTMLOptions& htmlOptions,
+ std::string* pHTMLOutput);
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const std::string& markdownInput,
+ const Extensions& extensions,
+ const HTMLOptions& htmlOptions,
+ std::string* pHTMLOutput);
+
+
+bool isMathJaxRequired(const std::string& htmlOutput);
+
+} // namespace markdown
+} // namespace core
+
+#endif // CORE_MARKDOWN_MARKDOWN_HPP
+
diff --git a/src/cpp/core/include/core/r_util/REnvironment.hpp b/src/cpp/core/include/core/r_util/REnvironment.hpp
new file mode 100644
index 0000000..ec8b0eb
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/REnvironment.hpp
@@ -0,0 +1,45 @@
+/*
+ * REnvironment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_ENVIRONMENT_HPP
+#define CORE_R_UTIL_R_ENVIRONMENT_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace r_util {
+
+typedef std::vector<std::pair<std::string,std::string> > EnvironmentVars;
+
+bool detectREnvironment(const FilePath& whichRScript,
+ const FilePath& ldPathsScript,
+ const std::string& ldLibraryPath,
+ std::string* pRScriptPath,
+ EnvironmentVars* pVars,
+ std::string* pErrMsg);
+
+void setREnvironmentVars(const EnvironmentVars& vars);
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_ENVIRONMENT_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RPackageInfo.hpp b/src/cpp/core/include/core/r_util/RPackageInfo.hpp
new file mode 100644
index 0000000..e3fdeeb
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RPackageInfo.hpp
@@ -0,0 +1,71 @@
+/*
+ * RPackageInfo.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_PACKAGE_INFO_HPP
+#define CORE_R_UTIL_R_PACKAGE_INFO_HPP
+
+#include <string>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace r_util {
+
+class RPackageInfo
+{
+public:
+ RPackageInfo()
+ {
+ }
+ ~RPackageInfo()
+ {
+ }
+
+ // COPYING: via compiler
+
+public:
+ Error read(const FilePath& packageDir);
+
+ bool empty() const { return name().empty(); }
+
+ const std::string& name() const { return name_; }
+ const std::string& version() const { return version_; }
+ const std::string& linkingTo() const { return linkingTo_; }
+ const std::string& type() const { return type_; }
+
+ std::string sourcePackageFilename() const;
+
+private:
+ std::string packageFilename(const std::string& extension) const;
+
+private:
+ std::string name_;
+ std::string version_;
+ std::string linkingTo_;
+ std::string type_;
+};
+
+bool isPackageDirectory(const FilePath& dir);
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_PACKAGE_INFO_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RProjectFile.hpp b/src/cpp/core/include/core/r_util/RProjectFile.hpp
new file mode 100644
index 0000000..e42f577
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RProjectFile.hpp
@@ -0,0 +1,121 @@
+/*
+ * RProjectFile.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_PROJECT_FILE_HPP
+#define CORE_R_UTIL_R_PROJECT_FILE_HPP
+
+#include <string>
+#include <iosfwd>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace r_util {
+
+enum YesNoAskValue
+{
+ DefaultValue = 0,
+ YesValue = 1,
+ NoValue = 2,
+ AskValue = 3
+};
+
+extern const char * const kBuildTypeNone;
+extern const char * const kBuildTypePackage;
+extern const char * const kBuildTypeMakefile;
+extern const char * const kBuildTypeCustom;
+
+std::ostream& operator << (std::ostream& stream, const YesNoAskValue& val);
+
+struct RProjectConfig
+{
+ RProjectConfig()
+ : version(1.0),
+ saveWorkspace(DefaultValue),
+ restoreWorkspace(DefaultValue),
+ alwaysSaveHistory(DefaultValue),
+ enableCodeIndexing(true),
+ useSpacesForTab(true),
+ numSpacesForTab(2),
+ autoAppendNewline(false),
+ stripTrailingWhitespace(false),
+ encoding(),
+ defaultSweaveEngine(),
+ defaultLatexProgram(),
+ rootDocument(),
+ buildType(),
+ packagePath(),
+ packageInstallArgs(),
+ packageBuildArgs(),
+ packageBuildBinaryArgs(),
+ packageCheckArgs(),
+ packageRoxygenize(),
+ packageUseDevtools(false),
+ makefilePath(),
+ customScriptPath(),
+ tutorialPath()
+ {
+ }
+
+ double version;
+ int saveWorkspace;
+ int restoreWorkspace;
+ int alwaysSaveHistory;
+ bool enableCodeIndexing;
+ bool useSpacesForTab;
+ int numSpacesForTab;
+ bool autoAppendNewline;
+ bool stripTrailingWhitespace;
+ std::string encoding;
+ std::string defaultSweaveEngine;
+ std::string defaultLatexProgram;
+ std::string rootDocument;
+ std::string buildType;
+ std::string packagePath;
+ std::string packageInstallArgs;
+ std::string packageBuildArgs;
+ std::string packageBuildBinaryArgs;
+ std::string packageCheckArgs;
+ std::string packageRoxygenize;
+ bool packageUseDevtools;
+ std::string makefilePath;
+ std::string customScriptPath;
+ std::string tutorialPath;
+};
+
+
+Error readProjectFile(const FilePath& projectFilePath,
+ const RProjectConfig& defaultConfig,
+ RProjectConfig* pConfig,
+ bool* pProvidedDefaults,
+ std::string* pUserErrMsg);
+
+Error writeProjectFile(const FilePath& projectFilePath,
+ const RProjectConfig& config);
+
+FilePath projectFromDirectory(const FilePath& directoryPath);
+
+// update the package install args default (only if it is set to
+// the previous default value)
+bool updateSetPackageInstallArgsDefault(RProjectConfig* pConfig);
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_PROJECT_FILE_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RSessionLaunchProfile.hpp b/src/cpp/core/include/core/r_util/RSessionLaunchProfile.hpp
new file mode 100644
index 0000000..600f7b3
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RSessionLaunchProfile.hpp
@@ -0,0 +1,45 @@
+/*
+ * RSessionLaunchProfile.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_SESSION_LAUNCH_PROFILE_HPP
+#define CORE_R_UTIL_R_SESSION_LAUNCH_PROFILE_HPP
+
+#include <string>
+
+#include <core/system/PosixSystem.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+namespace r_util {
+
+struct SessionLaunchProfile
+{
+ std::string username;
+ std::string executablePath;
+ core::system::ProcessConfig config;
+};
+
+json::Object sessionLaunchProfileToJson(const SessionLaunchProfile& profile);
+
+SessionLaunchProfile sessionLaunchProfileFromJson(
+ const json::Object& jsonProfile);
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_SESSION_LAUNCH_PROFILE_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RSourceIndex.hpp b/src/cpp/core/include/core/r_util/RSourceIndex.hpp
new file mode 100644
index 0000000..19537ee
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RSourceIndex.hpp
@@ -0,0 +1,270 @@
+/*
+ * RSourceIndex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_SOURCE_INDEX_HPP
+#define CORE_R_UTIL_R_SOURCE_INDEX_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/utility.hpp>
+#include <boost/regex.hpp>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Algorithm.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/StringUtils.hpp>
+#include <core/RegexUtils.hpp>
+
+#include <core/r_util/RTokenizer.hpp>
+
+namespace core {
+namespace r_util {
+
+class RS4MethodParam
+{
+public:
+ RS4MethodParam(const std::string& name, const std::string& type)
+ : name_(name), type_(type)
+ {
+ }
+
+ explicit RS4MethodParam(const std::string& type)
+ : name_(), type_(type)
+ {
+ }
+
+ // COPYING: via compiler / concrete-type
+
+ const std::string& name() const { return name_; }
+ const std::string& type() const { return type_; }
+
+private:
+ std::string name_;
+ std::string type_;
+};
+
+
+class RSourceItem
+{
+public:
+ enum Type
+ {
+ None = 0,
+ Function = 1,
+ Method = 2,
+ Class = 3
+ };
+
+public:
+ RSourceItem() : type_(None), braceLevel_(0), line_(0), column_(0)
+ {
+ }
+
+ RSourceItem(int type,
+ const std::string& name,
+ const std::vector<RS4MethodParam>& signature,
+ int braceLevel,
+ std::size_t line,
+ std::size_t column)
+ : type_(type),
+ name_(name),
+ signature_(signature),
+ braceLevel_(braceLevel),
+ line_(line),
+ column_(column)
+ {
+ }
+
+ virtual ~RSourceItem() {}
+
+ // COPYING: via compiler (copyable members)
+
+private:
+ RSourceItem(const std::string& context,
+ int type,
+ const std::string& name,
+ const std::vector<RS4MethodParam>& signature,
+ int braceLevel,
+ std::size_t line,
+ std::size_t column)
+ : context_(context),
+ type_(type),
+ name_(name),
+ signature_(signature),
+ braceLevel_(braceLevel),
+ line_(line),
+ column_(column)
+ {
+ }
+
+public:
+ // accessors
+ int type() const { return type_; }
+ const std::string& context() const { return context_; }
+ const std::string& name() const { return name_; }
+ const std::vector<RS4MethodParam>& signature() const { return signature_; }
+ const int braceLevel() const { return braceLevel_; }
+ int line() const { return core::safe_convert::numberTo<int>(line_,0); }
+ int column() const { return core::safe_convert::numberTo<int>(column_,0); }
+
+ // support for RSourceIndex::search
+
+ bool nameStartsWith(const std::string& term, bool caseSensitive) const
+ {
+ if (caseSensitive)
+ return boost::algorithm::starts_with(name_, term);
+ else
+ return boost::algorithm::istarts_with(name_, term);
+ }
+
+ bool nameContains(const std::string& term, bool caseSensitive) const
+ {
+ if (caseSensitive)
+ return boost::algorithm::contains(name_, term);
+ else
+ return boost::algorithm::icontains(name_, term);
+ }
+
+ bool nameMatches(const boost::regex& regex,
+ bool prefixOnly,
+ bool caseSensitive) const
+ {
+ return regex_utils::textMatches(name_, regex, prefixOnly, caseSensitive);
+ }
+
+ RSourceItem withContext(const std::string& context) const
+ {
+ return RSourceItem(context,
+ type_,
+ name_,
+ signature_,
+ braceLevel_,
+ line_,
+ column_);
+ }
+
+private:
+ std::string context_;
+ int type_;
+ std::string name_;
+ std::vector<RS4MethodParam> signature_;
+ int braceLevel_;
+ std::size_t line_;
+ std::size_t column_;
+};
+
+
+class RSourceIndex : boost::noncopyable
+{
+public:
+ // Index the provided R source code so that we can efficiently search
+ // for functions within it
+ //
+ // Requirements for code:
+ // - Must be UTF-8 encoded
+ // - Must use \n only for linebreaks
+ //
+ RSourceIndex(const std::string& context,
+ const std::string& code);
+
+ const std::string& context() const { return context_; }
+
+ template <typename OutputIterator>
+ OutputIterator search(
+ const std::string& newContext,
+ const boost::function<bool(const RSourceItem&)> predicate,
+ OutputIterator out) const
+ {
+ // perform the copy and transform to include context
+ core::algorithm::copy_transformed_if(
+ items_.begin(),
+ items_.end(),
+ out,
+ predicate,
+ boost::bind(&RSourceItem::withContext, _1, newContext));
+
+ // return the output iterator
+ return out;
+ }
+
+ template <typename OutputIterator>
+ OutputIterator search(
+ const boost::function<bool(const RSourceItem&)> predicate,
+ OutputIterator out) const
+ {
+ return search(context_, predicate, out);
+ }
+
+ template <typename OutputIterator>
+ OutputIterator search(const std::string& term,
+ const std::string& newContext,
+ bool prefixOnly,
+ bool caseSensitive,
+ OutputIterator out) const
+ {
+ // define the predicate
+ boost::function<bool(const RSourceItem&)> predicate;
+
+ // check for wildcard character
+ if (term.find('*') != std::string::npos)
+ {
+ boost::regex patternRegex = regex_utils::wildcardPatternToRegex(
+ caseSensitive ?
+ term :
+ string_utils::toLower(term));
+ predicate = boost::bind(&RSourceItem::nameMatches,
+ _1,
+ patternRegex,
+ prefixOnly,
+ caseSensitive);
+ }
+ else
+ {
+ if (prefixOnly)
+ predicate = boost::bind(&RSourceItem::nameStartsWith,
+ _1, term, caseSensitive);
+ else
+ predicate = boost::bind(&RSourceItem::nameContains,
+ _1, term, caseSensitive);
+ }
+
+ return search(newContext, predicate, out);
+ }
+
+ template <typename OutputIterator>
+ OutputIterator search(const std::string& term,
+ bool prefixOnly,
+ bool caseSensitive,
+ OutputIterator out) const
+ {
+ return search(term, context_, prefixOnly, caseSensitive, out);
+ }
+
+private:
+ std::string context_;
+ std::vector<RSourceItem> items_;
+};
+
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_SOURCE_INDEX_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RTokenizer.hpp b/src/cpp/core/include/core/r_util/RTokenizer.hpp
new file mode 100644
index 0000000..f35f94f
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RTokenizer.hpp
@@ -0,0 +1,221 @@
+/*
+ * RTokenizer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_TOKENIZER_HPP
+#define CORE_R_UTIL_R_TOKENIZER_HPP
+
+#include <string>
+#include <deque>
+#include <algorithm>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/regex_fwd.hpp>
+
+// On Linux confirm that wchar_t is Unicode
+#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__STDC_ISO_10646__)
+ #error "wchar_t is not Unicode"
+#endif
+
+namespace core {
+
+class Error;
+
+namespace r_util {
+
+// Make RToken non-subclassable (since it has copy/byval semantics any
+// subclass would be sliced
+class RToken_lock
+{
+ friend class RToken ;
+private:
+ RToken_lock() {}
+ RToken_lock(const RToken_lock&) {}
+};
+
+// RToken. Note that RToken instances are only valid as long as the class
+// which yielded them (RTokenizer or RTokens) is alive. This is because
+// they contain iterators into the original source data rather than their
+// own copy of their contents.
+class RToken : public virtual RToken_lock
+{
+public:
+
+ static const wchar_t LPAREN;
+ static const wchar_t RPAREN;
+ static const wchar_t LBRACKET;
+ static const wchar_t RBRACKET;
+ static const wchar_t LBRACE;
+ static const wchar_t RBRACE;
+ static const wchar_t COMMA;
+ static const wchar_t SEMI;
+ static const wchar_t WHITESPACE;
+ static const wchar_t STRING;
+ static const wchar_t NUMBER;
+ static const wchar_t ID;
+ static const wchar_t OPER;
+ static const wchar_t UOPER;
+ static const wchar_t ERR;
+ static const wchar_t LDBRACKET;
+ static const wchar_t RDBRACKET;
+ static const wchar_t COMMENT;
+
+public:
+ RToken()
+ : offset_(-1)
+ {
+ }
+
+ RToken(wchar_t type,
+ std::wstring::const_iterator begin,
+ std::wstring::const_iterator end,
+ std::size_t offset)
+ : type_(type), begin_(begin), end_(end), offset_(offset)
+ {
+ }
+
+ // COPYING: via compiler (copyable members)
+
+ // accessors
+ wchar_t type() const { return type_; }
+ std::wstring content() const { return std::wstring(begin_, end_); }
+ std::size_t offset() const { return offset_; }
+ std::size_t length() const { return end_ - begin_; }
+
+ // efficient comparison operations
+ bool contentEquals(const std::wstring& text) const
+ {
+ return std::equal(begin_, end_, text.begin());
+ }
+
+ bool contentStartsWith(const std::wstring& text) const
+ {
+ return std::search(begin_, end_, text.begin(), text.end()) == begin_;
+ }
+
+ bool isOperator(const std::wstring& op) const
+ {
+ return (type_ == RToken::OPER) &&
+ std::equal(begin_, end_, op.begin());
+ }
+
+ bool isType(wchar_t type) const
+ {
+ return type_ == type;
+ }
+
+ // allow direct use in conditional statements (nullability)
+ typedef void (*unspecified_bool_type)();
+ static void unspecified_bool_true() {}
+ operator unspecified_bool_type() const
+ {
+ return offset_ == static_cast<std::size_t>(-1) ?
+ 0 :
+ unspecified_bool_true;
+ }
+ bool operator!() const
+ {
+ return offset_ == static_cast<std::size_t>(-1);
+ }
+
+private:
+ wchar_t type_;
+ std::wstring::const_iterator begin_;
+ std::wstring::const_iterator end_;
+ std::size_t offset_;
+};
+
+// Tokenize R code. Note that the RToken instances which are returned are
+// valid only during the lifetime of the RTokenizer which yielded them
+// (because they store iterators into their content rather than making a copy
+// of the content)
+class RTokenizer : boost::noncopyable
+{
+public:
+ explicit RTokenizer(const std::wstring& data)
+ : data_(data), pos_(data_.begin())
+ {
+ }
+
+ virtual ~RTokenizer() {}
+
+ // COPYING: boost::noncopyable
+
+ RToken nextToken();
+
+private:
+ RToken matchWhitespace();
+ RToken matchStringLiteral();
+ RToken matchNumber();
+ RToken matchIdentifier();
+ RToken matchQuotedIdentifier();
+ RToken matchComment();
+ RToken matchUserOperator();
+ RToken matchOperator();
+ bool eol();
+ wchar_t peek();
+ wchar_t peek(std::size_t lookahead);
+ wchar_t eat();
+ std::wstring peek(const boost::wregex& regex);
+ void eatUntil(const boost::wregex& regex);
+ RToken consumeToken(wchar_t tokenType, std::size_t length);
+
+private:
+ std::wstring data_;
+ std::wstring::const_iterator pos_;
+};
+
+
+// Set of RTokens. Note that the RTokens returned from the set
+// are conceptually iterators so are only valid for the lifetime of
+// the RTokens object which yielded them.
+class RTokens : public std::deque<RToken>, boost::noncopyable
+{
+public:
+ enum Flags
+ {
+ None = 0,
+ StripWhitespace = 1,
+ StripComments = 2
+ };
+
+public:
+ explicit RTokens(const std::wstring& code, int flags = None)
+ : tokenizer_(code)
+ {
+ RToken token;
+ while ((token = tokenizer_.nextToken()))
+ {
+ if ((flags & StripWhitespace) && token.type() == RToken::WHITESPACE)
+ continue;
+
+ if ((flags & StripComments) && token.type() == RToken::COMMENT)
+ continue;
+
+ push_back(token);
+ }
+ }
+
+private:
+ RTokenizer tokenizer_;
+};
+
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_ENVIRONMENT_HPP
+
diff --git a/src/cpp/core/include/core/r_util/RToolsInfo.hpp b/src/cpp/core/include/core/r_util/RToolsInfo.hpp
new file mode 100644
index 0000000..8a553cb
--- /dev/null
+++ b/src/cpp/core/include/core/r_util/RToolsInfo.hpp
@@ -0,0 +1,80 @@
+/*
+ * RToolsInfo.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_R_UTIL_R_TOOLS_INFO_HPP
+#define CORE_R_UTIL_R_TOOLS_INFO_HPP
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+#include <boost/algorithm/string/replace.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/system/Environment.hpp>
+
+namespace core {
+namespace r_util {
+
+class RToolsInfo
+{
+public:
+ RToolsInfo() {}
+ RToolsInfo(const std::string& name,
+ const FilePath& installPath);
+
+ bool empty() const { return name_.empty(); }
+
+ bool isRecognized() const { return !versionPredicate().empty(); }
+
+ bool isStillInstalled() const { return installPath_.exists(); }
+
+ const std::string& name() const { return name_; }
+ const std::string& versionPredicate() const { return versionPredicate_; }
+ const FilePath& installPath() const { return installPath_; }
+ const std::vector<FilePath>& pathEntries() const { return pathEntries_; }
+
+private:
+ std::string name_;
+ FilePath installPath_;
+ std::string versionPredicate_;
+ std::vector<FilePath> pathEntries_;
+};
+
+std::ostream& operator<<(std::ostream& os, const RToolsInfo& info);
+
+Error scanRegistryForRTools(std::vector<RToolsInfo>* pRTools);
+
+template <typename T>
+void prependToSystemPath(const RToolsInfo& toolsInfo, T* pTarget)
+{
+ // prepend in reverse order
+ std::vector<FilePath>::const_reverse_iterator it
+ = toolsInfo.pathEntries().rbegin();
+ for ( ; it != toolsInfo.pathEntries().rend(); ++it)
+ {
+ std::string path = it->absolutePath();
+ boost::algorithm::replace_all(path, "/", "\\");
+ core::system::addToPath(pTarget, path, true);
+ }
+}
+
+
+} // namespace r_util
+} // namespace core
+
+
+#endif // CORE_R_UTIL_R_TOOLS_INFO_HPP
+
diff --git a/src/cpp/core/include/core/rapidxml/rapidxml.hpp b/src/cpp/core/include/core/rapidxml/rapidxml.hpp
new file mode 100755
index 0000000..541bec5
--- /dev/null
+++ b/src/cpp/core/include/core/rapidxml/rapidxml.hpp
@@ -0,0 +1,2596 @@
+#ifndef RAPIDXML_HPP_INCLUDED
+#define RAPIDXML_HPP_INCLUDED
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation
+
+// If standard library is disabled, user must provide implementations of required functions and typedefs
+#if !defined(RAPIDXML_NO_STDLIB)
+ #include <cstdlib> // For std::size_t
+ #include <cassert> // For assert
+ #include <new> // For placement new
+#endif
+
+// On MSVC, disable "conditional expression is constant" warning (level 4).
+// This warning is almost impossible to avoid with certain types of templated code
+#ifdef _MSC_VER
+ #pragma warning(push)
+ #pragma warning(disable:4127) // Conditional expression is constant
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// RAPIDXML_PARSE_ERROR
+
+#if defined(RAPIDXML_NO_EXCEPTIONS)
+
+#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); }
+
+namespace rapidxml
+{
+ //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS,
+ //! this function is called to notify user about the error.
+ //! It must be defined by the user.
+ //! <br><br>
+ //! This function cannot return. If it does, the results are undefined.
+ //! <br><br>
+ //! A very simple definition might look like that:
+ //! <pre>
+ //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+ //! {
+ //! std::cout << "Parse error: " << what << "\n";
+ //! std::abort();
+ //! }
+ //! </pre>
+ //! \param what Human readable description of the error.
+ //! \param where Pointer to character data where error was detected.
+ void parse_error_handler(const char *what, void *where);
+}
+
+#else
+
+#include <exception> // For std::exception
+
+#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where)
+
+namespace rapidxml
+{
+
+ //! Parse error exception.
+ //! This exception is thrown by the parser when an error occurs.
+ //! Use what() function to get human-readable error message.
+ //! Use where() function to get a pointer to position within source text where error was detected.
+ //! <br><br>
+ //! If throwing exceptions by the parser is undesirable,
+ //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included.
+ //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception.
+ //! This function must be defined by the user.
+ //! <br><br>
+ //! This class derives from <code>std::exception</code> class.
+ class parse_error: public std::exception
+ {
+
+ public:
+
+ //! Constructs parse error
+ parse_error(const char *what, void *where)
+ : m_what(what)
+ , m_where(where)
+ {
+ }
+
+ //! Gets human readable description of error.
+ //! \return Pointer to null terminated description of the error.
+ virtual const char *what() const throw()
+ {
+ return m_what;
+ }
+
+ //! Gets pointer to character data where error happened.
+ //! Ch should be the same as char type of xml_document that produced the error.
+ //! \return Pointer to location within the parsed string where error occured.
+ template<class Ch>
+ Ch *where() const
+ {
+ return reinterpret_cast<Ch *>(m_where);
+ }
+
+ private:
+
+ const char *m_what;
+ void *m_where;
+
+ };
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// Pool sizes
+
+#ifndef RAPIDXML_STATIC_POOL_SIZE
+ // Size of static memory block of memory_pool.
+ // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+ // No dynamic memory allocations are performed by memory_pool until static memory is exhausted.
+ #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_DYNAMIC_POOL_SIZE
+ // Size of dynamic memory block of memory_pool.
+ // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+ // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool.
+ #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_ALIGNMENT
+ // Memory allocation alignment.
+ // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer.
+ // All memory allocations for nodes, attributes and strings will be aligned to this value.
+ // This must be a power of 2 and at least 1, otherwise memory_pool will not work.
+ #define RAPIDXML_ALIGNMENT sizeof(void *)
+#endif
+
+namespace rapidxml
+{
+ // Forward declarations
+ template<class Ch> class xml_node;
+ template<class Ch> class xml_attribute;
+ template<class Ch> class xml_document;
+
+ //! Enumeration listing all node types produced by the parser.
+ //! Use xml_node::type() function to query node type.
+ enum node_type
+ {
+ node_document, //!< A document node. Name and value are empty.
+ node_element, //!< An element node. Name contains element name. Value contains text of first data node.
+ node_data, //!< A data node. Name is empty. Value contains data text.
+ node_cdata, //!< A CDATA node. Name is empty. Value contains data text.
+ node_comment, //!< A comment node. Name is empty. Value contains comment text.
+ node_declaration, //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
+ node_doctype, //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
+ node_pi //!< A PI node. Name contains target. Value contains instructions.
+ };
+
+ ///////////////////////////////////////////////////////////////////////
+ // Parsing flags
+
+ //! Parse flag instructing the parser to not create data nodes.
+ //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_data_nodes = 0x1;
+
+ //! Parse flag instructing the parser to not use text of first data node as a value of parent element.
+ //! Can be combined with other flags by use of | operator.
+ //! Note that child data nodes of element node take precendence over its value when printing.
+ //! That is, if element has one or more child data nodes <em>and</em> a value, the value will be ignored.
+ //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_element_values = 0x2;
+
+ //! Parse flag instructing the parser to not place zero terminators after strings in the source text.
+ //! By default zero terminators are placed, modifying source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_string_terminators = 0x4;
+
+ //! Parse flag instructing the parser to not translate entities in the source text.
+ //! By default entities are translated, modifying source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_entity_translation = 0x8;
+
+ //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters.
+ //! By default, UTF-8 handling is enabled.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_no_utf8 = 0x10;
+
+ //! Parse flag instructing the parser to create XML declaration node.
+ //! By default, declaration node is not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_declaration_node = 0x20;
+
+ //! Parse flag instructing the parser to create comments nodes.
+ //! By default, comment nodes are not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_comment_nodes = 0x40;
+
+ //! Parse flag instructing the parser to create DOCTYPE node.
+ //! By default, doctype node is not created.
+ //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_doctype_node = 0x80;
+
+ //! Parse flag instructing the parser to create PI nodes.
+ //! By default, PI nodes are not created.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_pi_nodes = 0x100;
+
+ //! Parse flag instructing the parser to validate closing tag names.
+ //! If not set, name inside closing tag is irrelevant to the parser.
+ //! By default, closing tags are not validated.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_validate_closing_tags = 0x200;
+
+ //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes.
+ //! By default, whitespace is not trimmed.
+ //! This flag does not cause the parser to modify source text.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_trim_whitespace = 0x400;
+
+ //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character.
+ //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag.
+ //! By default, whitespace is not normalized.
+ //! If this flag is specified, source text will be modified.
+ //! Can be combined with other flags by use of | operator.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_normalize_whitespace = 0x800;
+
+ // Compound flags
+
+ //! Parse flags which represent default behaviour of the parser.
+ //! This is always equal to 0, so that all other flags can be simply ored together.
+ //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values.
+ //! This also means that meaning of each flag is a <i>negation</i> of the default setting.
+ //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is <i>enabled</i> by default,
+ //! and using the flag will disable it.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_default = 0;
+
+ //! A combination of parse flags that forbids any modifications of the source text.
+ //! This also results in faster parsing. However, note that the following will occur:
+ //! <ul>
+ //! <li>names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends</li>
+ //! <li>entities will not be translated</li>
+ //! <li>whitespace will not be normalized</li>
+ //! </ul>
+ //! See xml_document::parse() function.
+ const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;
+
+ //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_fastest = parse_non_destructive | parse_no_data_nodes;
+
+ //! A combination of parse flags resulting in largest amount of data being extracted.
+ //! This usually results in slowest parsing.
+ //! <br><br>
+ //! See xml_document::parse() function.
+ const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internals
+
+ //! \cond internal
+ namespace internal
+ {
+
+ // Struct that contains lookup tables for the parser
+ // It must be a template to allow correct linking (because it has static data members, which are defined in a header file).
+ template<int Dummy>
+ struct lookup_tables
+ {
+ static const unsigned char lookup_whitespace[256]; // Whitespace table
+ static const unsigned char lookup_node_name[256]; // Node name table
+ static const unsigned char lookup_text[256]; // Text table
+ static const unsigned char lookup_text_pure_no_ws[256]; // Text table
+ static const unsigned char lookup_text_pure_with_ws[256]; // Text table
+ static const unsigned char lookup_attribute_name[256]; // Attribute name table
+ static const unsigned char lookup_attribute_data_1[256]; // Attribute data table with single quote
+ static const unsigned char lookup_attribute_data_1_pure[256]; // Attribute data table with single quote
+ static const unsigned char lookup_attribute_data_2[256]; // Attribute data table with double quotes
+ static const unsigned char lookup_attribute_data_2_pure[256]; // Attribute data table with double quotes
+ static const unsigned char lookup_digits[256]; // Digits
+ static const unsigned char lookup_upcase[256]; // To uppercase conversion table for ASCII characters
+ };
+
+ // Find length of the string
+ template<class Ch>
+ inline std::size_t measure(const Ch *p)
+ {
+ const Ch *tmp = p;
+ while (*tmp)
+ ++tmp;
+ return tmp - p;
+ }
+
+ // Compare strings for equality
+ template<class Ch>
+ inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive)
+ {
+ if (size1 != size2)
+ return false;
+ if (case_sensitive)
+ {
+ for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+ if (*p1 != *p2)
+ return false;
+ }
+ else
+ {
+ for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+ if (lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)])
+ return false;
+ }
+ return true;
+ }
+ }
+ //! \endcond
+
+ ///////////////////////////////////////////////////////////////////////
+ // Memory pool
+
+ //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation.
+ //! In most cases, you will not need to use this class directly.
+ //! However, if you need to create nodes manually or modify names/values of nodes,
+ //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory.
+ //! Not only is this faster than allocating them by using <code>new</code> operator,
+ //! but also their lifetime will be tied to the lifetime of document,
+ //! possibly simplyfing memory management.
+ //! <br><br>
+ //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool.
+ //! You can also call allocate_string() function to allocate strings.
+ //! Such strings can then be used as names or values of nodes without worrying about their lifetime.
+ //! Note that there is no <code>free()</code> function -- all allocations are freed at once when clear() function is called,
+ //! or when the pool is destroyed.
+ //! <br><br>
+ //! It is also possible to create a standalone memory_pool, and use it
+ //! to allocate nodes, whose lifetime will not be tied to any document.
+ //! <br><br>
+ //! Pool maintains <code>RAPIDXML_STATIC_POOL_SIZE</code> bytes of statically allocated memory.
+ //! Until static memory is exhausted, no dynamic memory allocations are done.
+ //! When static memory is exhausted, pool allocates additional blocks of memory of size <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> each,
+ //! by using global <code>new[]</code> and <code>delete[]</code> operators.
+ //! This behaviour can be changed by setting custom allocation routines.
+ //! Use set_allocator() function to set them.
+ //! <br><br>
+ //! Allocations for nodes, attributes and strings are aligned at <code>RAPIDXML_ALIGNMENT</code> bytes.
+ //! This value defaults to the size of pointer on target architecture.
+ //! <br><br>
+ //! To obtain absolutely top performance from the parser,
+ //! it is important that all nodes are allocated from a single, contiguous block of memory.
+ //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably.
+ //! If required, you can tweak <code>RAPIDXML_STATIC_POOL_SIZE</code>, <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> and <code>RAPIDXML_ALIGNMENT</code>
+ //! to obtain best wasted memory to performance compromise.
+ //! To do it, define their values before rapidxml.hpp file is included.
+ //! \param Ch Character type of created nodes.
+ template<class Ch = char>
+ class memory_pool
+ {
+
+ public:
+
+ //! \cond internal
+ typedef void *(alloc_func)(std::size_t); // Type of user-defined function used to allocate memory
+ typedef void (free_func)(void *); // Type of user-defined function used to free memory
+ //! \endcond
+
+ //! Constructs empty pool with default allocator functions.
+ memory_pool()
+ : m_alloc_func(0)
+ , m_free_func(0)
+ {
+ init();
+ }
+
+ //! Destroys pool and frees all the memory.
+ //! This causes memory occupied by nodes allocated by the pool to be freed.
+ //! Nodes allocated from the pool are no longer valid.
+ ~memory_pool()
+ {
+ clear();
+ }
+
+ //! Allocates a new node from the pool, and optionally assigns name and value to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param type Type of node to create.
+ //! \param name Name to assign to the node, or 0 to assign no name.
+ //! \param value Value to assign to the node, or 0 to assign no value.
+ //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+ //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+ //! \return Pointer to allocated node. This pointer will never be NULL.
+ xml_node<Ch> *allocate_node(node_type type,
+ const Ch *name = 0, const Ch *value = 0,
+ std::size_t name_size = 0, std::size_t value_size = 0)
+ {
+ void *memory = allocate_aligned(sizeof(xml_node<Ch>));
+ xml_node<Ch> *node = new(memory) xml_node<Ch>(type);
+ if (name)
+ {
+ if (name_size > 0)
+ node->name(name, name_size);
+ else
+ node->name(name);
+ }
+ if (value)
+ {
+ if (value_size > 0)
+ node->value(value, value_size);
+ else
+ node->value(value);
+ }
+ return node;
+ }
+
+ //! Allocates a new attribute from the pool, and optionally assigns name and value to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param name Name to assign to the attribute, or 0 to assign no name.
+ //! \param value Value to assign to the attribute, or 0 to assign no value.
+ //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+ //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+ //! \return Pointer to allocated attribute. This pointer will never be NULL.
+ xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0,
+ std::size_t name_size = 0, std::size_t value_size = 0)
+ {
+ void *memory = allocate_aligned(sizeof(xml_attribute<Ch>));
+ xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>;
+ if (name)
+ {
+ if (name_size > 0)
+ attribute->name(name, name_size);
+ else
+ attribute->name(name);
+ }
+ if (value)
+ {
+ if (value_size > 0)
+ attribute->value(value, value_size);
+ else
+ attribute->value(value);
+ }
+ return attribute;
+ }
+
+ //! Allocates a char array of given size from the pool, and optionally copies a given string to it.
+ //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+ //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+ //! will call rapidxml::parse_error_handler() function.
+ //! \param source String to initialize the allocated memory with, or 0 to not initialize it.
+ //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.
+ //! \return Pointer to allocated char array. This pointer will never be NULL.
+ Ch *allocate_string(const Ch *source = 0, std::size_t size = 0)
+ {
+ assert(source || size); // Either source or size (or both) must be specified
+ if (size == 0)
+ size = internal::measure(source) + 1;
+ Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch)));
+ if (source)
+ for (std::size_t i = 0; i < size; ++i)
+ result[i] = source[i];
+ return result;
+ }
+
+ //! Clones an xml_node and its hierarchy of child nodes and attributes.
+ //! Nodes and attributes are allocated from this memory pool.
+ //! Names and values are not cloned, they are shared between the clone and the source.
+ //! Result node can be optionally specified as a second parameter,
+ //! in which case its contents will be replaced with cloned source node.
+ //! This is useful when you want to clone entire document.
+ //! \param source Node to clone.
+ //! \param result Node to put results in, or 0 to automatically allocate result node
+ //! \return Pointer to cloned node. This pointer will never be NULL.
+ xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0)
+ {
+ // Prepare result node
+ if (result)
+ {
+ result->remove_all_attributes();
+ result->remove_all_nodes();
+ result->type(source->type());
+ }
+ else
+ result = allocate_node(source->type());
+
+ // Clone name and value
+ result->name(source->name(), source->name_size());
+ result->value(source->value(), source->value_size());
+
+ // Clone child nodes and attributes
+ for (xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling())
+ result->append_node(clone_node(child));
+ for (xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute())
+ result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size()));
+
+ return result;
+ }
+
+ //! Clears the pool.
+ //! This causes memory occupied by nodes allocated by the pool to be freed.
+ //! Any nodes or strings allocated from the pool will no longer be valid.
+ void clear()
+ {
+ while (m_begin != m_static_memory)
+ {
+ char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin;
+ if (m_free_func)
+ m_free_func(m_begin);
+ else
+ delete[] m_begin;
+ m_begin = previous_begin;
+ }
+ init();
+ }
+
+ //! Sets or resets the user-defined memory allocation functions for the pool.
+ //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined.
+ //! Allocation function must not return invalid pointer on failure. It should either throw,
+ //! stop the program, or use <code>longjmp()</code> function to pass control to other place of program.
+ //! If it returns invalid pointer, results are undefined.
+ //! <br><br>
+ //! User defined allocation functions must have the following forms:
+ //! <br><code>
+ //! <br>void *allocate(std::size_t size);
+ //! <br>void free(void *pointer);
+ //! </code><br>
+ //! \param af Allocation function, or 0 to restore default function
+ //! \param ff Free function, or 0 to restore default function
+ void set_allocator(alloc_func *af, free_func *ff)
+ {
+ assert(m_begin == m_static_memory && m_ptr == align(m_begin)); // Verify that no memory is allocated yet
+ m_alloc_func = af;
+ m_free_func = ff;
+ }
+
+ private:
+
+ struct header
+ {
+ char *previous_begin;
+ };
+
+ void init()
+ {
+ m_begin = m_static_memory;
+ m_ptr = align(m_begin);
+ m_end = m_static_memory + sizeof(m_static_memory);
+ }
+
+ char *align(char *ptr)
+ {
+ std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1));
+ return ptr + alignment;
+ }
+
+ char *allocate_raw(std::size_t size)
+ {
+ // Allocate
+ void *memory;
+ if (m_alloc_func) // Allocate memory using either user-specified allocation function or global operator new[]
+ {
+ memory = m_alloc_func(size);
+ assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp
+ }
+ else
+ {
+ memory = new char[size];
+#ifdef RAPIDXML_NO_EXCEPTIONS
+ if (!memory) // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc
+ RAPIDXML_PARSE_ERROR("out of memory", 0);
+#endif
+ }
+ return static_cast<char *>(memory);
+ }
+
+ void *allocate_aligned(std::size_t size)
+ {
+ // Calculate aligned pointer
+ char *result = align(m_ptr);
+
+ // If not enough memory left in current pool, allocate a new pool
+ if (result + size > m_end)
+ {
+ // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE)
+ std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE;
+ if (pool_size < size)
+ pool_size = size;
+
+ // Allocate
+ std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size; // 2 alignments required in worst case: one for header, one for actual allocation
+ char *raw_memory = allocate_raw(alloc_size);
+
+ // Setup new pool in allocated memory
+ char *pool = align(raw_memory);
+ header *new_header = reinterpret_cast<header *>(pool);
+ new_header->previous_begin = m_begin;
+ m_begin = raw_memory;
+ m_ptr = pool + sizeof(header);
+ m_end = raw_memory + alloc_size;
+
+ // Calculate aligned pointer again using new pool
+ result = align(m_ptr);
+ }
+
+ // Update pool and return aligned pointer
+ m_ptr = result + size;
+ return result;
+ }
+
+ char *m_begin; // Start of raw memory making up current pool
+ char *m_ptr; // First free byte in current pool
+ char *m_end; // One past last available byte in current pool
+ char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; // Static raw memory
+ alloc_func *m_alloc_func; // Allocator function, or 0 if default is to be used
+ free_func *m_free_func; // Free function, or 0 if default is to be used
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML base
+
+ //! Base class for xml_node and xml_attribute implementing common functions:
+ //! name(), name_size(), value(), value_size() and parent().
+ //! \param Ch Character type to use
+ template<class Ch = char>
+ class xml_base
+ {
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ // Construct a base with empty name, value and parent
+ xml_base()
+ : m_name(0)
+ , m_value(0)
+ , m_parent(0)
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node data access
+
+ //! Gets name of the node.
+ //! Interpretation of name depends on type of node.
+ //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+ //! <br><br>
+ //! Use name_size() function to determine length of the name.
+ //! \return Name of node, or empty string if node has no name.
+ Ch *name() const
+ {
+ return m_name ? m_name : nullstr();
+ }
+
+ //! Gets size of node name, not including terminator character.
+ //! This function works correctly irrespective of whether name is or is not zero terminated.
+ //! \return Size of node name, in characters.
+ std::size_t name_size() const
+ {
+ return m_name ? m_name_size : 0;
+ }
+
+ //! Gets value of node.
+ //! Interpretation of value depends on type of node.
+ //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+ //! <br><br>
+ //! Use value_size() function to determine length of the value.
+ //! \return Value of node, or empty string if node has no value.
+ Ch *value() const
+ {
+ return m_value ? m_value : nullstr();
+ }
+
+ //! Gets size of node value, not including terminator character.
+ //! This function works correctly irrespective of whether value is or is not zero terminated.
+ //! \return Size of node value, in characters.
+ std::size_t value_size() const
+ {
+ return m_value ? m_value_size : 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node modification
+
+ //! Sets name of node to a non zero-terminated string.
+ //! See \ref ownership_of_strings.
+ //! <br><br>
+ //! Note that node does not own its name or value, it only stores a pointer to it.
+ //! It will not delete or otherwise free the pointer on destruction.
+ //! It is reponsibility of the user to properly manage lifetime of the string.
+ //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+ //! on destruction of the document the string will be automatically freed.
+ //! <br><br>
+ //! Size of name must be specified separately, because name does not have to be zero terminated.
+ //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+ //! \param name Name of node to set. Does not have to be zero terminated.
+ //! \param size Size of name, in characters. This does not include zero terminator, if one is present.
+ void name(const Ch *name, std::size_t size)
+ {
+ m_name = const_cast<Ch *>(name);
+ m_name_size = size;
+ }
+
+ //! Sets name of node to a zero-terminated string.
+ //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t).
+ //! \param name Name of node to set. Must be zero terminated.
+ void name(const Ch *name)
+ {
+ this->name(name, internal::measure(name));
+ }
+
+ //! Sets value of node to a non zero-terminated string.
+ //! See \ref ownership_of_strings.
+ //! <br><br>
+ //! Note that node does not own its name or value, it only stores a pointer to it.
+ //! It will not delete or otherwise free the pointer on destruction.
+ //! It is reponsibility of the user to properly manage lifetime of the string.
+ //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+ //! on destruction of the document the string will be automatically freed.
+ //! <br><br>
+ //! Size of value must be specified separately, because it does not have to be zero terminated.
+ //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+ //! <br><br>
+ //! If an element has a child node of type node_data, it will take precedence over element value when printing.
+ //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.
+ //! \param value value of node to set. Does not have to be zero terminated.
+ //! \param size Size of value, in characters. This does not include zero terminator, if one is present.
+ void value(const Ch *value, std::size_t size)
+ {
+ m_value = const_cast<Ch *>(value);
+ m_value_size = size;
+ }
+
+ //! Sets value of node to a zero-terminated string.
+ //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t).
+ //! \param value Vame of node to set. Must be zero terminated.
+ void value(const Ch *value)
+ {
+ this->value(value, internal::measure(value));
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets node parent.
+ //! \return Pointer to parent node, or 0 if there is no parent.
+ xml_node<Ch> *parent() const
+ {
+ return m_parent;
+ }
+
+ protected:
+
+ // Return empty string
+ static Ch *nullstr()
+ {
+ static Ch zero = Ch('\0');
+ return &zero;
+ }
+
+ Ch *m_name; // Name of node, or 0 if no name
+ Ch *m_value; // Value of node, or 0 if no value
+ std::size_t m_name_size; // Length of node name, or undefined of no name
+ std::size_t m_value_size; // Length of node value, or undefined if no value
+ xml_node<Ch> *m_parent; // Pointer to parent node, or 0 if none
+
+ };
+
+ //! Class representing attribute node of XML document.
+ //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base).
+ //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing.
+ //! Thus, this text must persist in memory for the lifetime of attribute.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_attribute: public xml_base<Ch>
+ {
+
+ friend class xml_node<Ch>;
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ //! Constructs an empty attribute with the specified type.
+ //! Consider using memory_pool of appropriate xml_document if allocating attributes manually.
+ xml_attribute()
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets document of which attribute is a child.
+ //! \return Pointer to document that contains this attribute, or 0 if there is no parent document.
+ xml_document<Ch> *document() const
+ {
+ if (xml_node<Ch> *node = this->parent())
+ {
+ while (node->parent())
+ node = node->parent();
+ return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+ }
+ else
+ return 0;
+ }
+
+ //! Gets previous attribute, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return this->m_parent ? m_prev_attribute : 0;
+ }
+
+ //! Gets next attribute, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return this->m_parent ? m_next_attribute : 0;
+ }
+
+ private:
+
+ xml_attribute<Ch> *m_prev_attribute; // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero
+ xml_attribute<Ch> *m_next_attribute; // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero
+
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML node
+
+ //! Class representing a node of XML document.
+ //! Each node may have associated name and value strings, which are available through name() and value() functions.
+ //! Interpretation of name and value depends on type of the node.
+ //! Type of node can be determined by using type() function.
+ //! <br><br>
+ //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing.
+ //! Thus, this text must persist in the memory for the lifetime of node.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_node: public xml_base<Ch>
+ {
+
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction & destruction
+
+ //! Constructs an empty node with the specified type.
+ //! Consider using memory_pool of appropriate document to allocate nodes manually.
+ //! \param type Type of node to construct.
+ xml_node(node_type type)
+ : m_type(type)
+ , m_first_node(0)
+ , m_first_attribute(0)
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node data access
+
+ //! Gets type of node.
+ //! \return Type of node.
+ node_type type() const
+ {
+ return m_type;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Related nodes access
+
+ //! Gets document of which node is a child.
+ //! \return Pointer to document that contains this node, or 0 if there is no parent document.
+ xml_document<Ch> *document() const
+ {
+ xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this);
+ while (node->parent())
+ node = node->parent();
+ return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+ }
+
+ //! Gets first child node, optionally matching node name.
+ //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found child, or 0 if not found.
+ xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *child = m_first_node; child; child = child->next_sibling())
+ if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+ return child;
+ return 0;
+ }
+ else
+ return m_first_node;
+ }
+
+ //! Gets last child node, optionally matching node name.
+ //! Behaviour is undefined if node has no children.
+ //! Use first_node() to test if node has children.
+ //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found child, or 0 if not found.
+ xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(m_first_node); // Cannot query for last child if node has no children
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling())
+ if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+ return child;
+ return 0;
+ }
+ else
+ return m_last_node;
+ }
+
+ //! Gets previous sibling node, optionally matching node name.
+ //! Behaviour is undefined if node has no parent.
+ //! Use parent() to test if node has a parent.
+ //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found sibling, or 0 if not found.
+ xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(this->m_parent); // Cannot query for siblings if node has no parent
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling)
+ if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+ return sibling;
+ return 0;
+ }
+ else
+ return m_prev_sibling;
+ }
+
+ //! Gets next sibling node, optionally matching node name.
+ //! Behaviour is undefined if node has no parent.
+ //! Use parent() to test if node has a parent.
+ //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found sibling, or 0 if not found.
+ xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ assert(this->m_parent); // Cannot query for siblings if node has no parent
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)
+ if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+ return sibling;
+ return 0;
+ }
+ else
+ return m_next_sibling;
+ }
+
+ //! Gets first attribute of node, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return m_first_attribute;
+ }
+
+ //! Gets last attribute of node, optionally matching attribute name.
+ //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+ //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+ //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+ //! \return Pointer to found attribute, or 0 if not found.
+ xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+ {
+ if (name)
+ {
+ if (name_size == 0)
+ name_size = internal::measure(name);
+ for (xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute)
+ if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+ return attribute;
+ return 0;
+ }
+ else
+ return m_first_attribute ? m_last_attribute : 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node modification
+
+ //! Sets type of node.
+ //! \param type Type of node to set.
+ void type(node_type type)
+ {
+ m_type = type;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Node manipulation
+
+ //! Prepends a new child node.
+ //! The prepended child becomes the first child, and all existing children are moved one position back.
+ //! \param child Node to prepend.
+ void prepend_node(xml_node<Ch> *child)
+ {
+ assert(child && !child->parent() && child->type() != node_document);
+ if (first_node())
+ {
+ child->m_next_sibling = m_first_node;
+ m_first_node->m_prev_sibling = child;
+ }
+ else
+ {
+ child->m_next_sibling = 0;
+ m_last_node = child;
+ }
+ m_first_node = child;
+ child->m_parent = this;
+ child->m_prev_sibling = 0;
+ }
+
+ //! Appends a new child node.
+ //! The appended child becomes the last child.
+ //! \param child Node to append.
+ void append_node(xml_node<Ch> *child)
+ {
+ assert(child && !child->parent() && child->type() != node_document);
+ if (first_node())
+ {
+ child->m_prev_sibling = m_last_node;
+ m_last_node->m_next_sibling = child;
+ }
+ else
+ {
+ child->m_prev_sibling = 0;
+ m_first_node = child;
+ }
+ m_last_node = child;
+ child->m_parent = this;
+ child->m_next_sibling = 0;
+ }
+
+ //! Inserts a new child node at specified place inside the node.
+ //! All children after and including the specified node are moved one position back.
+ //! \param where Place where to insert the child, or 0 to insert at the back.
+ //! \param child Node to insert.
+ void insert_node(xml_node<Ch> *where, xml_node<Ch> *child)
+ {
+ assert(!where || where->parent() == this);
+ assert(child && !child->parent() && child->type() != node_document);
+ if (where == m_first_node)
+ prepend_node(child);
+ else if (where == 0)
+ append_node(child);
+ else
+ {
+ child->m_prev_sibling = where->m_prev_sibling;
+ child->m_next_sibling = where;
+ where->m_prev_sibling->m_next_sibling = child;
+ where->m_prev_sibling = child;
+ child->m_parent = this;
+ }
+ }
+
+ //! Removes first child node.
+ //! If node has no children, behaviour is undefined.
+ //! Use first_node() to test if node has children.
+ void remove_first_node()
+ {
+ assert(first_node());
+ xml_node<Ch> *child = m_first_node;
+ m_first_node = child->m_next_sibling;
+ if (child->m_next_sibling)
+ child->m_next_sibling->m_prev_sibling = 0;
+ else
+ m_last_node = 0;
+ child->m_parent = 0;
+ }
+
+ //! Removes last child of the node.
+ //! If node has no children, behaviour is undefined.
+ //! Use first_node() to test if node has children.
+ void remove_last_node()
+ {
+ assert(first_node());
+ xml_node<Ch> *child = m_last_node;
+ if (child->m_prev_sibling)
+ {
+ m_last_node = child->m_prev_sibling;
+ child->m_prev_sibling->m_next_sibling = 0;
+ }
+ else
+ m_first_node = 0;
+ child->m_parent = 0;
+ }
+
+ //! Removes specified child from the node
+ // \param where Pointer to child to be removed.
+ void remove_node(xml_node<Ch> *where)
+ {
+ assert(where && where->parent() == this);
+ assert(first_node());
+ if (where == m_first_node)
+ remove_first_node();
+ else if (where == m_last_node)
+ remove_last_node();
+ else
+ {
+ where->m_prev_sibling->m_next_sibling = where->m_next_sibling;
+ where->m_next_sibling->m_prev_sibling = where->m_prev_sibling;
+ where->m_parent = 0;
+ }
+ }
+
+ //! Removes all child nodes (but not attributes).
+ void remove_all_nodes()
+ {
+ for (xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling)
+ node->m_parent = 0;
+ m_first_node = 0;
+ }
+
+ //! Prepends a new attribute to the node.
+ //! \param attribute Attribute to prepend.
+ void prepend_attribute(xml_attribute<Ch> *attribute)
+ {
+ assert(attribute && !attribute->parent());
+ if (first_attribute())
+ {
+ attribute->m_next_attribute = m_first_attribute;
+ m_first_attribute->m_prev_attribute = attribute;
+ }
+ else
+ {
+ attribute->m_next_attribute = 0;
+ m_last_attribute = attribute;
+ }
+ m_first_attribute = attribute;
+ attribute->m_parent = this;
+ attribute->m_prev_attribute = 0;
+ }
+
+ //! Appends a new attribute to the node.
+ //! \param attribute Attribute to append.
+ void append_attribute(xml_attribute<Ch> *attribute)
+ {
+ assert(attribute && !attribute->parent());
+ if (first_attribute())
+ {
+ attribute->m_prev_attribute = m_last_attribute;
+ m_last_attribute->m_next_attribute = attribute;
+ }
+ else
+ {
+ attribute->m_prev_attribute = 0;
+ m_first_attribute = attribute;
+ }
+ m_last_attribute = attribute;
+ attribute->m_parent = this;
+ attribute->m_next_attribute = 0;
+ }
+
+ //! Inserts a new attribute at specified place inside the node.
+ //! All attributes after and including the specified attribute are moved one position back.
+ //! \param where Place where to insert the attribute, or 0 to insert at the back.
+ //! \param attribute Attribute to insert.
+ void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute)
+ {
+ assert(!where || where->parent() == this);
+ assert(attribute && !attribute->parent());
+ if (where == m_first_attribute)
+ prepend_attribute(attribute);
+ else if (where == 0)
+ append_attribute(attribute);
+ else
+ {
+ attribute->m_prev_attribute = where->m_prev_attribute;
+ attribute->m_next_attribute = where;
+ where->m_prev_attribute->m_next_attribute = attribute;
+ where->m_prev_attribute = attribute;
+ attribute->m_parent = this;
+ }
+ }
+
+ //! Removes first attribute of the node.
+ //! If node has no attributes, behaviour is undefined.
+ //! Use first_attribute() to test if node has attributes.
+ void remove_first_attribute()
+ {
+ assert(first_attribute());
+ xml_attribute<Ch> *attribute = m_first_attribute;
+ if (attribute->m_next_attribute)
+ {
+ attribute->m_next_attribute->m_prev_attribute = 0;
+ }
+ else
+ m_last_attribute = 0;
+ attribute->m_parent = 0;
+ m_first_attribute = attribute->m_next_attribute;
+ }
+
+ //! Removes last attribute of the node.
+ //! If node has no attributes, behaviour is undefined.
+ //! Use first_attribute() to test if node has attributes.
+ void remove_last_attribute()
+ {
+ assert(first_attribute());
+ xml_attribute<Ch> *attribute = m_last_attribute;
+ if (attribute->m_prev_attribute)
+ {
+ attribute->m_prev_attribute->m_next_attribute = 0;
+ m_last_attribute = attribute->m_prev_attribute;
+ }
+ else
+ m_first_attribute = 0;
+ attribute->m_parent = 0;
+ }
+
+ //! Removes specified attribute from node.
+ //! \param where Pointer to attribute to be removed.
+ void remove_attribute(xml_attribute<Ch> *where)
+ {
+ assert(first_attribute() && where->parent() == this);
+ if (where == m_first_attribute)
+ remove_first_attribute();
+ else if (where == m_last_attribute)
+ remove_last_attribute();
+ else
+ {
+ where->m_prev_attribute->m_next_attribute = where->m_next_attribute;
+ where->m_next_attribute->m_prev_attribute = where->m_prev_attribute;
+ where->m_parent = 0;
+ }
+ }
+
+ //! Removes all attributes of node.
+ void remove_all_attributes()
+ {
+ for (xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute)
+ attribute->m_parent = 0;
+ m_first_attribute = 0;
+ }
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Restrictions
+
+ // No copying
+ xml_node(const xml_node &);
+ void operator =(const xml_node &);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Data members
+
+ // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0.
+ // This is required for maximum performance, as it allows the parser to omit initialization of
+ // unneded/redundant values.
+ //
+ // The rules are as follows:
+ // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively
+ // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage
+ // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage
+
+ node_type m_type; // Type of node; always valid
+ xml_node<Ch> *m_first_node; // Pointer to first child node, or 0 if none; always valid
+ xml_node<Ch> *m_last_node; // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero
+ xml_attribute<Ch> *m_first_attribute; // Pointer to first attribute of node, or 0 if none; always valid
+ xml_attribute<Ch> *m_last_attribute; // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero
+ xml_node<Ch> *m_prev_sibling; // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+ xml_node<Ch> *m_next_sibling; // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XML document
+
+ //! This class represents root of the DOM hierarchy.
+ //! It is also an xml_node and a memory_pool through public inheritance.
+ //! Use parse() function to build a DOM tree from a zero-terminated XML text string.
+ //! parse() function allocates memory for nodes and attributes by using functions of xml_document,
+ //! which are inherited from memory_pool.
+ //! To access root node of the document, use the document itself, as if it was an xml_node.
+ //! \param Ch Character type to use.
+ template<class Ch = char>
+ class xml_document: public xml_node<Ch>, public memory_pool<Ch>
+ {
+
+ public:
+
+ //! Constructs empty XML document
+ xml_document()
+ : xml_node<Ch>(node_document)
+ {
+ }
+
+ //! Parses zero-terminated XML string according to given flags.
+ //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used.
+ //! The string must persist for the lifetime of the document.
+ //! In case of error, rapidxml::parse_error exception will be thrown.
+ //! <br><br>
+ //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning.
+ //! Make sure that data is zero-terminated.
+ //! <br><br>
+ //! Document can be parsed into multiple times.
+ //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.
+ //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.
+ template<int Flags>
+ void parse(Ch *text)
+ {
+ assert(text);
+
+ // Remove current contents
+ this->remove_all_nodes();
+ this->remove_all_attributes();
+
+ // Parse BOM, if any
+ parse_bom<Flags>(text);
+
+ // Parse children
+ while (1)
+ {
+ // Skip whitespace before node
+ skip<whitespace_pred, Flags>(text);
+ if (*text == 0)
+ break;
+
+ // Parse and append new child
+ if (*text == Ch('<'))
+ {
+ ++text; // Skip '<'
+ if (xml_node<Ch> *node = parse_node<Flags>(text))
+ this->append_node(node);
+ }
+ else
+ RAPIDXML_PARSE_ERROR("expected <", text);
+ }
+
+ }
+
+ //! Clears the document by deleting all nodes and clearing the memory pool.
+ //! All nodes owned by document pool are destroyed.
+ void clear()
+ {
+ this->remove_all_nodes();
+ this->remove_all_attributes();
+ memory_pool<Ch>::clear();
+ }
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internal character utility functions
+
+ // Detect whitespace character
+ struct whitespace_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect node name character
+ struct node_name_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect attribute name character
+ struct attribute_name_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA)
+ struct text_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA) that does not require processing
+ struct text_pure_no_ws_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect text character (PCDATA) that does not require processing
+ struct text_pure_with_ws_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)];
+ }
+ };
+
+ // Detect attribute value character
+ template<Ch Quote>
+ struct attribute_value_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ if (Quote == Ch('\''))
+ return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
+ if (Quote == Ch('\"'))
+ return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
+ return 0; // Should never be executed, to avoid warnings on Comeau
+ }
+ };
+
+ // Detect attribute value character
+ template<Ch Quote>
+ struct attribute_value_pure_pred
+ {
+ static unsigned char test(Ch ch)
+ {
+ if (Quote == Ch('\''))
+ return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)];
+ if (Quote == Ch('\"'))
+ return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)];
+ return 0; // Should never be executed, to avoid warnings on Comeau
+ }
+ };
+
+ // Insert coded character, using UTF8 or 8-bit ASCII
+ template<int Flags>
+ static void insert_coded_character(Ch *&text, unsigned long code)
+ {
+ if (Flags & parse_no_utf8)
+ {
+ // Insert 8-bit ASCII character
+ // possibly verify that code is less than 256 and use replacement char otherwise?
+ text[0] = static_cast<unsigned char>(code);
+ text += 1;
+ }
+ else
+ {
+ // Insert UTF8 sequence
+ if (code < 0x80) // 1 byte sequence
+ {
+ text[0] = static_cast<unsigned char>(code);
+ text += 1;
+ }
+ else if (code < 0x800) // 2 byte sequence
+ {
+ text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<unsigned char>(code | 0xC0);
+ text += 2;
+ }
+ else if (code < 0x10000) // 3 byte sequence
+ {
+ text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<unsigned char>(code | 0xE0);
+ text += 3;
+ }
+ else if (code < 0x110000) // 4 byte sequence
+ {
+ text[3] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+ text[0] = static_cast<unsigned char>(code | 0xF0);
+ text += 4;
+ }
+ else // Invalid, only codes up to 0x10FFFF are allowed in Unicode
+ {
+ RAPIDXML_PARSE_ERROR("invalid numeric character entity", text);
+ }
+ }
+ }
+
+ // Skip characters until predicate evaluates to true
+ template<class StopPred, int Flags>
+ static void skip(Ch *&text)
+ {
+ Ch *tmp = text;
+ while (StopPred::test(*tmp))
+ ++tmp;
+ text = tmp;
+ }
+
+ // Skip characters until predicate evaluates to true while doing the following:
+ // - replacing XML character entity references with proper characters (' & " < > &#...;)
+ // - condensing whitespace sequences to single space character
+ template<class StopPred, class StopPredPure, int Flags>
+ static Ch *skip_and_expand_character_refs(Ch *&text)
+ {
+ // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip
+ if (Flags & parse_no_entity_translation &&
+ !(Flags & parse_normalize_whitespace) &&
+ !(Flags & parse_trim_whitespace))
+ {
+ skip<StopPred, Flags>(text);
+ return text;
+ }
+
+ // Use simple skip until first modification is detected
+ skip<StopPredPure, Flags>(text);
+
+ // Use translation skip
+ Ch *src = text;
+ Ch *dest = src;
+ while (StopPred::test(*src))
+ {
+ // If entity translation is enabled
+ if (!(Flags & parse_no_entity_translation))
+ {
+ // Test if replacement is needed
+ if (src[0] == Ch('&'))
+ {
+ switch (src[1])
+ {
+
+ // & '
+ case Ch('a'):
+ if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';'))
+ {
+ *dest = Ch('&');
+ ++dest;
+ src += 5;
+ continue;
+ }
+ if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';'))
+ {
+ *dest = Ch('\'');
+ ++dest;
+ src += 6;
+ continue;
+ }
+ break;
+
+ // "
+ case Ch('q'):
+ if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';'))
+ {
+ *dest = Ch('"');
+ ++dest;
+ src += 6;
+ continue;
+ }
+ break;
+
+ // >
+ case Ch('g'):
+ if (src[2] == Ch('t') && src[3] == Ch(';'))
+ {
+ *dest = Ch('>');
+ ++dest;
+ src += 4;
+ continue;
+ }
+ break;
+
+ // <
+ case Ch('l'):
+ if (src[2] == Ch('t') && src[3] == Ch(';'))
+ {
+ *dest = Ch('<');
+ ++dest;
+ src += 4;
+ continue;
+ }
+ break;
+
+ // &#...; - assumes ASCII
+ case Ch('#'):
+ if (src[2] == Ch('x'))
+ {
+ unsigned long code = 0;
+ src += 3; // Skip &#x
+ while (1)
+ {
+ unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+ if (digit == 0xFF)
+ break;
+ code = code * 16 + digit;
+ ++src;
+ }
+ insert_coded_character<Flags>(dest, code); // Put character in output
+ }
+ else
+ {
+ unsigned long code = 0;
+ src += 2; // Skip &#
+ while (1)
+ {
+ unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+ if (digit == 0xFF)
+ break;
+ code = code * 10 + digit;
+ ++src;
+ }
+ insert_coded_character<Flags>(dest, code); // Put character in output
+ }
+ if (*src == Ch(';'))
+ ++src;
+ else
+ RAPIDXML_PARSE_ERROR("expected ;", src);
+ continue;
+
+ // Something else
+ default:
+ // Ignore, just copy '&' verbatim
+ break;
+
+ }
+ }
+ }
+
+ // If whitespace condensing is enabled
+ if (Flags & parse_normalize_whitespace)
+ {
+ // Test if condensing is needed
+ if (whitespace_pred::test(*src))
+ {
+ *dest = Ch(' '); ++dest; // Put single space in dest
+ ++src; // Skip first whitespace char
+ // Skip remaining whitespace chars
+ while (whitespace_pred::test(*src))
+ ++src;
+ continue;
+ }
+ }
+
+ // No replacement, only copy character
+ *dest++ = *src++;
+
+ }
+
+ // Return new end
+ text = src;
+ return dest;
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ // Internal parsing functions
+
+ // Parse BOM, if any
+ template<int Flags>
+ void parse_bom(Ch *&text)
+ {
+ // UTF-8?
+ if (static_cast<unsigned char>(text[0]) == 0xEF &&
+ static_cast<unsigned char>(text[1]) == 0xBB &&
+ static_cast<unsigned char>(text[2]) == 0xBF)
+ {
+ text += 3; // Skup utf-8 bom
+ }
+ }
+
+ // Parse XML declaration (<?xml...)
+ template<int Flags>
+ xml_node<Ch> *parse_xml_declaration(Ch *&text)
+ {
+ // If parsing of declaration is disabled
+ if (!(Flags & parse_declaration_node))
+ {
+ // Skip until end of declaration
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 2; // Skip '?>'
+ return 0;
+ }
+
+ // Create declaration
+ xml_node<Ch> *declaration = this->allocate_node(node_declaration);
+
+ // Skip whitespace before attributes or ?>
+ skip<whitespace_pred, Flags>(text);
+
+ // Parse declaration attributes
+ parse_node_attributes<Flags>(text, declaration);
+
+ // Skip ?>
+ if (text[0] != Ch('?') || text[1] != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected ?>", text);
+ text += 2;
+
+ return declaration;
+ }
+
+ // Parse XML comment (<!--...)
+ template<int Flags>
+ xml_node<Ch> *parse_comment(Ch *&text)
+ {
+ // If parsing of comments is disabled
+ if (!(Flags & parse_comment_nodes))
+ {
+ // Skip until end of comment
+ while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 3; // Skip '-->'
+ return 0; // Do not produce comment node
+ }
+
+ // Remember value start
+ Ch *value = text;
+
+ // Skip until end of comment
+ while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Create comment node
+ xml_node<Ch> *comment = this->allocate_node(node_comment);
+ comment->value(value, text - value);
+
+ // Place zero terminator after comment value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 3; // Skip '-->'
+ return comment;
+ }
+
+ // Parse DOCTYPE
+ template<int Flags>
+ xml_node<Ch> *parse_doctype(Ch *&text)
+ {
+ // Remember value start
+ Ch *value = text;
+
+ // Skip to >
+ while (*text != Ch('>'))
+ {
+ // Determine character type
+ switch (*text)
+ {
+
+ // If '[' encountered, scan for matching ending ']' using naive algorithm with depth
+ // This works for all W3C test files except for 2 most wicked
+ case Ch('['):
+ {
+ ++text; // Skip '['
+ int depth = 1;
+ while (depth > 0)
+ {
+ switch (*text)
+ {
+ case Ch('['): ++depth; break;
+ case Ch(']'): --depth; break;
+ case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ }
+ ++text;
+ }
+ break;
+ }
+
+ // Error on end of text
+ case Ch('\0'):
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+
+ // Other character, skip it
+ default:
+ ++text;
+
+ }
+ }
+
+ // If DOCTYPE nodes enabled
+ if (Flags & parse_doctype_node)
+ {
+ // Create a new doctype node
+ xml_node<Ch> *doctype = this->allocate_node(node_doctype);
+ doctype->value(value, text - value);
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 1; // skip '>'
+ return doctype;
+ }
+ else
+ {
+ text += 1; // skip '>'
+ return 0;
+ }
+
+ }
+
+ // Parse PI
+ template<int Flags>
+ xml_node<Ch> *parse_pi(Ch *&text)
+ {
+ // If creation of PI nodes is enabled
+ if (Flags & parse_pi_nodes)
+ {
+ // Create pi node
+ xml_node<Ch> *pi = this->allocate_node(node_pi);
+
+ // Extract PI target name
+ Ch *name = text;
+ skip<node_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected PI target", text);
+ pi->name(name, text - name);
+
+ // Skip whitespace between pi target and pi
+ skip<whitespace_pred, Flags>(text);
+
+ // Remember start of pi
+ Ch *value = text;
+
+ // Skip to '?>'
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (*text == Ch('\0'))
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Set pi value (verbatim, no entity expansion or whitespace normalization)
+ pi->value(value, text - value);
+
+ // Place zero terminator after name and value
+ if (!(Flags & parse_no_string_terminators))
+ {
+ pi->name()[pi->name_size()] = Ch('\0');
+ pi->value()[pi->value_size()] = Ch('\0');
+ }
+
+ text += 2; // Skip '?>'
+ return pi;
+ }
+ else
+ {
+ // Skip to '?>'
+ while (text[0] != Ch('?') || text[1] != Ch('>'))
+ {
+ if (*text == Ch('\0'))
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 2; // Skip '?>'
+ return 0;
+ }
+ }
+
+ // Parse and append data
+ // Return character that ends data.
+ // This is necessary because this character might have been overwritten by a terminating 0
+ template<int Flags>
+ Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start)
+ {
+ // Backup to contents start if whitespace trimming is disabled
+ if (!(Flags & parse_trim_whitespace))
+ text = contents_start;
+
+ // Skip until end of data
+ Ch *value = text, *end;
+ if (Flags & parse_normalize_whitespace)
+ end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text);
+ else
+ end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text);
+
+ // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after >
+ if (Flags & parse_trim_whitespace)
+ {
+ if (Flags & parse_normalize_whitespace)
+ {
+ // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end
+ if (*(end - 1) == Ch(' '))
+ --end;
+ }
+ else
+ {
+ // Backup until non-whitespace character is found
+ while (whitespace_pred::test(*(end - 1)))
+ --end;
+ }
+ }
+
+ // If characters are still left between end and value (this test is only necessary if normalization is enabled)
+ // Create new data node
+ if (!(Flags & parse_no_data_nodes))
+ {
+ xml_node<Ch> *data = this->allocate_node(node_data);
+ data->value(value, end - value);
+ node->append_node(data);
+ }
+
+ // Add data to parent node if no data exists yet
+ if (!(Flags & parse_no_element_values))
+ if (*node->value() == Ch('\0'))
+ node->value(value, end - value);
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ {
+ Ch ch = *text;
+ *end = Ch('\0');
+ return ch; // Return character that ends data; this is required because zero terminator overwritten it
+ }
+
+ // Return character that ends data
+ return *text;
+ }
+
+ // Parse CDATA
+ template<int Flags>
+ xml_node<Ch> *parse_cdata(Ch *&text)
+ {
+ // If CDATA is disabled
+ if (Flags & parse_no_data_nodes)
+ {
+ // Skip until end of cdata
+ while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ text += 3; // Skip ]]>
+ return 0; // Do not produce CDATA node
+ }
+
+ // Skip until end of cdata
+ Ch *value = text;
+ while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+ {
+ if (!text[0])
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+
+ // Create new cdata node
+ xml_node<Ch> *cdata = this->allocate_node(node_cdata);
+ cdata->value(value, text - value);
+
+ // Place zero terminator after value
+ if (!(Flags & parse_no_string_terminators))
+ *text = Ch('\0');
+
+ text += 3; // Skip ]]>
+ return cdata;
+ }
+
+ // Parse element node
+ template<int Flags>
+ xml_node<Ch> *parse_element(Ch *&text)
+ {
+ // Create element node
+ xml_node<Ch> *element = this->allocate_node(node_element);
+
+ // Extract element name
+ Ch *name = text;
+ skip<node_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected element name", text);
+ element->name(name, text - name);
+
+ // Skip whitespace between element name and attributes or >
+ skip<whitespace_pred, Flags>(text);
+
+ // Parse attributes, if any
+ parse_node_attributes<Flags>(text, element);
+
+ // Determine ending type
+ if (*text == Ch('>'))
+ {
+ ++text;
+ parse_node_contents<Flags>(text, element);
+ }
+ else if (*text == Ch('/'))
+ {
+ ++text;
+ if (*text != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected >", text);
+ ++text;
+ }
+ else
+ RAPIDXML_PARSE_ERROR("expected >", text);
+
+ // Place zero terminator after name
+ if (!(Flags & parse_no_string_terminators))
+ element->name()[element->name_size()] = Ch('\0');
+
+ // Return parsed element
+ return element;
+ }
+
+ // Determine node type, and parse it
+ template<int Flags>
+ xml_node<Ch> *parse_node(Ch *&text)
+ {
+ // Parse proper node type
+ switch (text[0])
+ {
+
+ // <...
+ default:
+ // Parse and append element node
+ return parse_element<Flags>(text);
+
+ // <?...
+ case Ch('?'):
+ ++text; // Skip ?
+ if ((text[0] == Ch('x') || text[0] == Ch('X')) &&
+ (text[1] == Ch('m') || text[1] == Ch('M')) &&
+ (text[2] == Ch('l') || text[2] == Ch('L')) &&
+ whitespace_pred::test(text[3]))
+ {
+ // '<?xml ' - xml declaration
+ text += 4; // Skip 'xml '
+ return parse_xml_declaration<Flags>(text);
+ }
+ else
+ {
+ // Parse PI
+ return parse_pi<Flags>(text);
+ }
+
+ // <!...
+ case Ch('!'):
+
+ // Parse proper subset of <! node
+ switch (text[1])
+ {
+
+ // <!-
+ case Ch('-'):
+ if (text[2] == Ch('-'))
+ {
+ // '<!--' - xml comment
+ text += 3; // Skip '!--'
+ return parse_comment<Flags>(text);
+ }
+ break;
+
+ // <![
+ case Ch('['):
+ if (text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') &&
+ text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('['))
+ {
+ // '<![CDATA[' - cdata
+ text += 8; // Skip '![CDATA['
+ return parse_cdata<Flags>(text);
+ }
+ break;
+
+ // <!D
+ case Ch('D'):
+ if (text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') &&
+ text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') &&
+ whitespace_pred::test(text[8]))
+ {
+ // '<!DOCTYPE ' - doctype
+ text += 9; // skip '!DOCTYPE '
+ return parse_doctype<Flags>(text);
+ }
+
+ } // switch
+
+ // Attempt to skip other, unrecognized node types starting with <!
+ ++text; // Skip !
+ while (*text != Ch('>'))
+ {
+ if (*text == 0)
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+ ++text;
+ }
+ ++text; // Skip '>'
+ return 0; // No node recognized
+
+ }
+ }
+
+ // Parse contents of the node - children, data etc.
+ template<int Flags>
+ void parse_node_contents(Ch *&text, xml_node<Ch> *node)
+ {
+ // For all children and text
+ while (1)
+ {
+ // Skip whitespace between > and node contents
+ Ch *contents_start = text; // Store start of node contents before whitespace is skipped
+ skip<whitespace_pred, Flags>(text);
+ Ch next_char = *text;
+
+ // After data nodes, instead of continuing the loop, control jumps here.
+ // This is because zero termination inside parse_and_append_data() function
+ // would wreak havoc with the above code.
+ // Also, skipping whitespace after data nodes is unnecessary.
+ after_data_node:
+
+ // Determine what comes next: node closing, child node, data node, or 0?
+ switch (next_char)
+ {
+
+ // Node closing or child node
+ case Ch('<'):
+ if (text[1] == Ch('/'))
+ {
+ // Node closing
+ text += 2; // Skip '</'
+ if (Flags & parse_validate_closing_tags)
+ {
+ // Skip and validate closing tag name
+ Ch *closing_name = text;
+ skip<node_name_pred, Flags>(text);
+ if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true))
+ RAPIDXML_PARSE_ERROR("invalid closing tag name", text);
+ }
+ else
+ {
+ // No validation, just skip name
+ skip<node_name_pred, Flags>(text);
+ }
+ // Skip remaining whitespace after node name
+ skip<whitespace_pred, Flags>(text);
+ if (*text != Ch('>'))
+ RAPIDXML_PARSE_ERROR("expected >", text);
+ ++text; // Skip '>'
+ return; // Node closed, finished parsing contents
+ }
+ else
+ {
+ // Child node
+ ++text; // Skip '<'
+ if (xml_node<Ch> *child = parse_node<Flags>(text))
+ node->append_node(child);
+ }
+ break;
+
+ // End of data - error
+ case Ch('\0'):
+ RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+
+ // Data node
+ default:
+ next_char = parse_and_append_data<Flags>(node, text, contents_start);
+ goto after_data_node; // Bypass regular processing after data nodes
+
+ }
+ }
+ }
+
+ // Parse XML attributes of the node
+ template<int Flags>
+ void parse_node_attributes(Ch *&text, xml_node<Ch> *node)
+ {
+ // For all attributes
+ while (attribute_name_pred::test(*text))
+ {
+ // Extract attribute name
+ Ch *name = text;
+ ++text; // Skip first character of attribute name
+ skip<attribute_name_pred, Flags>(text);
+ if (text == name)
+ RAPIDXML_PARSE_ERROR("expected attribute name", name);
+
+ // Create new attribute
+ xml_attribute<Ch> *attribute = this->allocate_attribute();
+ attribute->name(name, text - name);
+ node->append_attribute(attribute);
+
+ // Skip whitespace after attribute name
+ skip<whitespace_pred, Flags>(text);
+
+ // Skip =
+ if (*text != Ch('='))
+ RAPIDXML_PARSE_ERROR("expected =", text);
+ ++text;
+
+ // Add terminating zero after name
+ if (!(Flags & parse_no_string_terminators))
+ attribute->name()[attribute->name_size()] = 0;
+
+ // Skip whitespace after =
+ skip<whitespace_pred, Flags>(text);
+
+ // Skip quote and remember if it was ' or "
+ Ch quote = *text;
+ if (quote != Ch('\'') && quote != Ch('"'))
+ RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+ ++text;
+
+ // Extract attribute value and expand char refs in it
+ Ch *value = text, *end;
+ const int AttFlags = Flags & ~parse_normalize_whitespace; // No whitespace normalization in attributes
+ if (quote == Ch('\''))
+ end = skip_and_expand_character_refs<attribute_value_pred<Ch('\'')>, attribute_value_pure_pred<Ch('\'')>, AttFlags>(text);
+ else
+ end = skip_and_expand_character_refs<attribute_value_pred<Ch('"')>, attribute_value_pure_pred<Ch('"')>, AttFlags>(text);
+
+ // Set attribute value
+ attribute->value(value, end - value);
+
+ // Make sure that end quote is present
+ if (*text != quote)
+ RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+ ++text; // Skip quote
+
+ // Add terminating zero after value
+ if (!(Flags & parse_no_string_terminators))
+ attribute->value()[attribute->value_size()] = 0;
+
+ // Skip whitespace after attribute value
+ skip<whitespace_pred, Flags>(text);
+ }
+ }
+
+ };
+
+ //! \cond internal
+ namespace internal
+ {
+
+ // Whitespace (space \n \r \t)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
+ };
+
+ // Node name (anything but space \n \r \t / > ? \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_node_name[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) (anything but < \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled
+ // (anything but < \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled
+ // (anything but < \0 & space \n \r \t)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute name (anything but space \n \r \t / < > = ? ! \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with single quote (anything but ' \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with single quote that does not require processing (anything but ' \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with double quote (anything but " \0)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Attribute data with double quote that does not require processing (anything but " \0 &)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
+ 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F
+ };
+
+ // Digits (dec and hex, 255 denotes end of numeric character reference)
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_digits[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3
+ 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5
+ 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F
+ };
+
+ // Upper case conversion
+ template<int Dummy>
+ const unsigned char lookup_tables<Dummy>::lookup_upcase[256] =
+ {
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A B C D E F
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 1
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 2
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 3
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 4
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 5
+ 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 6
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127, // 7
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 8
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 9
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // A
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // B
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // C
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // D
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // E
+ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // F
+ };
+ }
+ //! \endcond
+
+}
+
+// Undefine internal macros
+#undef RAPIDXML_PARSE_ERROR
+
+// On MSVC, restore warnings state
+#ifdef _MSC_VER
+ #pragma warning(pop)
+#endif
+
+#endif
diff --git a/src/cpp/core/include/core/spelling/HunspellCustomDictionaries.hpp b/src/cpp/core/include/core/spelling/HunspellCustomDictionaries.hpp
new file mode 100644
index 0000000..7358262
--- /dev/null
+++ b/src/cpp/core/include/core/spelling/HunspellCustomDictionaries.hpp
@@ -0,0 +1,59 @@
+/*
+ * HunspellCustomDictionaries.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SPELLING_HUNSPELL_CUSTOM_DICTIONARIES_HPP
+#define CORE_SPELLING_HUNSPELL_CUSTOM_DICTIONARIES_HPP
+
+#include <vector>
+#include <string>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error;
+
+namespace spelling {
+
+class HunspellCustomDictionaries
+{
+public:
+ HunspellCustomDictionaries(const FilePath& customDictionariesDir)
+ : customDictionariesDir_(customDictionariesDir)
+ {
+ }
+
+ ~HunspellCustomDictionaries()
+ {
+ }
+
+ // COPYING: via compiler
+
+ std::vector<std::string> dictionaries() const;
+ FilePath dictionaryPath(const std::string& name) const;
+
+ Error add(const FilePath& dicPath) const;
+ Error remove(const std::string& name) const;
+
+private:
+ core::FilePath customDictionariesDir_;
+};
+
+} // namespace spelling
+} // namespace core
+
+
+#endif // CORE_SPELLING_HUNSPELL_CUSTOM_DICTIONARIES_HPP
+
diff --git a/src/cpp/core/include/core/spelling/HunspellDictionaryManager.hpp b/src/cpp/core/include/core/spelling/HunspellDictionaryManager.hpp
new file mode 100644
index 0000000..6e7a5b8
--- /dev/null
+++ b/src/cpp/core/include/core/spelling/HunspellDictionaryManager.hpp
@@ -0,0 +1,108 @@
+/*
+ * HunspellDictionaryManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SPELLING_HUNSPELL_DICTIONARY_MANAGER_HPP
+#define CORE_SPELLING_HUNSPELL_DICTIONARY_MANAGER_HPP
+
+#include <vector>
+#include <string>
+
+#include <core/FilePath.hpp>
+
+#include <core/spelling/HunspellCustomDictionaries.hpp>
+
+namespace core {
+
+class Error;
+
+namespace spelling {
+
+class HunspellDictionary
+{
+public:
+ HunspellDictionary()
+ {
+ }
+
+ explicit HunspellDictionary(const FilePath& affPath)
+ : affPath_(affPath)
+ {
+ }
+
+ ~HunspellDictionary()
+ {
+ }
+
+ // COPYING: via compiler
+
+ bool empty() const { return affPath_.empty(); }
+
+ bool operator==(const HunspellDictionary& other) const
+ {
+ return affPath_ == other.affPath_;
+ }
+
+ std::string id() const { return affPath_.stem(); }
+ std::string name() const;
+
+ FilePath dicPath() const;
+ FilePath affPath() const { return affPath_; }
+
+private:
+ FilePath affPath_;
+};
+
+class HunspellDictionaryManager
+{
+public:
+ HunspellDictionaryManager(const FilePath& coreLanguagesDir,
+ const FilePath& userDir)
+ : coreLanguagesDir_(coreLanguagesDir),
+ userDir_(userDir),
+ customDicts_(userDir_.childPath("custom"))
+ {
+ }
+
+ ~HunspellDictionaryManager()
+ {
+ }
+
+ // COPYING: via compiler
+
+ bool allLanguagesInstalled() const { return allLanguagesDir().exists(); }
+
+ core::Error availableLanguages(
+ std::vector<HunspellDictionary>* pDictionaries) const;
+
+ HunspellDictionary dictionaryForLanguageId(const std::string& langId) const;
+
+ const HunspellCustomDictionaries& custom() const;
+
+private:
+ core::FilePath allLanguagesDir() const;
+ core::FilePath userLanguagesDir() const;
+
+private:
+ core::FilePath coreLanguagesDir_;
+ core::FilePath userDir_;
+ HunspellCustomDictionaries customDicts_;
+};
+
+} // namespace spelling
+} // namespace core
+
+
+#endif // CORE_SPELLING_HUNSPELL_DICTIONARY_MANAGER_HPP
+
diff --git a/src/cpp/core/include/core/spelling/HunspellSpellingEngine.hpp b/src/cpp/core/include/core/spelling/HunspellSpellingEngine.hpp
new file mode 100644
index 0000000..8fc4412
--- /dev/null
+++ b/src/cpp/core/include/core/spelling/HunspellSpellingEngine.hpp
@@ -0,0 +1,67 @@
+/*
+ * HunspellSpellingEngine.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SPELLING_HUNSPELL_SPELLING_ENGINE_HPP
+#define CORE_SPELLING_HUNSPELL_SPELLING_ENGINE_HPP
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/function.hpp>
+
+#include <core/spelling/SpellingEngine.hpp>
+
+#include <core/spelling/HunspellDictionaryManager.hpp>
+
+namespace core {
+
+class FilePath;
+
+namespace spelling {
+
+typedef boost::function<core::Error(const std::string&,
+ const std::string&,
+ const std::string&,
+ bool,
+ std::string*)> IconvstrFunction;
+
+class HunspellSpellingEngine : public SpellingEngine
+{
+public:
+ HunspellSpellingEngine(const std::string& langId,
+ const HunspellDictionaryManager& dictionaryManager,
+ const IconvstrFunction& iconvstrFunction);
+
+public:
+
+ void useDictionary(const std::string& langId);
+
+ Error checkSpelling(const std::string& word,
+ bool *pCorrect);
+
+ Error suggestionList(const std::string& word,
+ std::vector<std::string>* pSugs);
+
+ Error wordChars(std::wstring* pChars);
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+} // namespace spelling
+} // namespace core
+
+
+#endif // CORE_SPELLING_HUNSPELL_SPELLING_ENGINE_HPP
+
diff --git a/src/cpp/core/include/core/spelling/SpellingEngine.hpp b/src/cpp/core/include/core/spelling/SpellingEngine.hpp
new file mode 100644
index 0000000..289b62f
--- /dev/null
+++ b/src/cpp/core/include/core/spelling/SpellingEngine.hpp
@@ -0,0 +1,51 @@
+/*
+ * SpellingEngine.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SPELLING_SPELLING_ENGINE_HPP
+#define CORE_SPELLING_SPELLING_ENGINE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+namespace core {
+
+class Error;
+
+namespace spelling {
+
+class SpellingEngine : boost::noncopyable
+{
+public:
+ virtual ~SpellingEngine() {}
+
+ virtual void useDictionary(const std::string& langId) = 0;
+
+ virtual Error checkSpelling(const std::string& word,
+ bool *pCorrect) = 0;
+
+ virtual Error suggestionList(const std::string& word,
+ std::vector<std::string>* pSugs) = 0;
+
+ virtual Error wordChars(std::wstring* pChars) = 0;
+};
+
+} // namespace spelling
+} // namespace core
+
+
+#endif // CORE_SPELLING_SPELLING_ENGINE_HPP
+
diff --git a/src/cpp/core/include/core/system/Crypto.hpp b/src/cpp/core/include/core/system/Crypto.hpp
new file mode 100644
index 0000000..09c00b5
--- /dev/null
+++ b/src/cpp/core/include/core/system/Crypto.hpp
@@ -0,0 +1,60 @@
+/*
+ * Crypto.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_CRYPTO_HPP
+#define CORE_SYSTEM_CRYPTO_HPP
+
+#include <string>
+#include <vector>
+
+#include <core/Error.hpp>
+
+namespace core {
+namespace system {
+namespace crypto {
+
+void initialize();
+
+core::Error HMAC_SHA1(const std::string& data,
+ const std::string& key,
+ std::vector<unsigned char>* pHMAC);
+
+core::Error HMAC_SHA1(const std::string& data,
+ const std::vector<unsigned char>& key,
+ std::vector<unsigned char>* pHMAC);
+
+core::Error base64Encode(const std::vector<unsigned char>& data,
+ std::string* pEncoded);
+
+core::Error base64Encode(const unsigned char* pData,
+ int len,
+ std::string* pEncoded);
+
+core::Error base64Decode(const std::string& data,
+ std::vector<unsigned char>* pDecoded);
+
+core::Error rsaInit();
+
+void rsaPublicKey(std::string* pExponent, std::string* pModulo);
+
+core::Error rsaPrivateDecrypt(const std::string& pCipherText, std::string* pPlainText);
+
+
+} // namespace crypto
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_CRYPTO_HPP
+
diff --git a/src/cpp/core/include/core/system/Environment.hpp b/src/cpp/core/include/core/system/Environment.hpp
new file mode 100644
index 0000000..bb5236d
--- /dev/null
+++ b/src/cpp/core/include/core/system/Environment.hpp
@@ -0,0 +1,87 @@
+/*
+ * Environment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_ENVIRONMENT_HPP
+#define CORE_SYSTEM_ENVIRONMENT_HPP
+
+#include <string>
+
+#include <core/system/Types.hpp>
+
+namespace core {
+namespace system {
+
+// NOTE: all environment variables are UTF8-encoded
+
+
+/****************************************************************
+ Direct manipulation of global environment
+*****************************************************************/
+
+std::string getenv(const std::string& name);
+void setenv(const std::string& name, const std::string& value);
+void unsetenv(const std::string& name);
+
+
+/****************************************************************
+ Read current environment into memory and access its variables
+*****************************************************************/
+
+// get a copy of all current environment variables
+void environment(Options* pEnvironment);
+
+// get an environment variable within an Options structure
+std::string getenv(const Options& environment, const std::string& name);
+
+
+/****************************************************************
+ Manipulating the memory environment variable structure. These
+ functions are typically used for preparing values to be passed
+ as ProcessOptions::environnent
+*****************************************************************/
+
+// set an environment variable within an Options structure (replaces
+// any existing value)
+void setenv(Options* pEnvironment,
+ const std::string& name,
+ const std::string& value);
+
+// remove an enviroment variable from an Options structure
+void unsetenv(Options* pEnvironment,
+ const std::string& name);
+
+void getModifiedEnv(const Options& extraVars, Options* pEnv);
+
+// add to the PATH within a string
+void addToPath(std::string* pPath,
+ const std::string& filePath,
+ bool prepend = false);
+
+// add to the PATH within an Options struture
+void addToPath(Options* pEnvironment,
+ const std::string& filePath,
+ bool prepend = false);
+
+
+/****************************************************************
+ Utility functions
+*****************************************************************/
+
+bool parseEnvVar(const std::string envVar, Option* pEnvVar);
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_ENVIRONMENT_HPP
diff --git a/src/cpp/core/include/core/system/FileChangeEvent.hpp b/src/cpp/core/include/core/system/FileChangeEvent.hpp
new file mode 100644
index 0000000..33edb95
--- /dev/null
+++ b/src/cpp/core/include/core/system/FileChangeEvent.hpp
@@ -0,0 +1,179 @@
+/*
+ * FileChangeEvent.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
+#define CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
+
+#include <string>
+#include <iostream>
+#include <vector>
+
+#include <boost/function.hpp>
+
+#include <core/FileInfo.hpp>
+
+namespace core {
+
+class Error;
+
+namespace system {
+
+// event struct
+class FileChangeEvent
+{
+public:
+ // NOTE: skip 2 for compatabilty with old clients (used to be FileRenamed)
+ enum Type
+ {
+ None = 0,
+ FileAdded = 1,
+ FileRemoved = 3,
+ FileModified = 4
+ };
+
+public:
+ FileChangeEvent(Type type, const core::FileInfo& fileInfo)
+ : type_(type), fileInfo_(fileInfo)
+ {
+ }
+
+ // COPYING: via compiler
+
+ FileChangeEvent& operator=(const FileChangeEvent& rhs)
+ {
+ if (&rhs != this)
+ {
+ type_ = rhs.type_;
+ fileInfo_ = rhs.fileInfo_;
+ }
+ return *this;
+ }
+
+public:
+ Type type() const { return type_; }
+ const FileInfo& fileInfo() const { return fileInfo_; }
+
+private:
+ Type type_;
+ core::FileInfo fileInfo_;
+};
+
+inline std::ostream& operator << (std::ostream& ostr,
+ const FileChangeEvent& event)
+{
+ if (event.type() == FileChangeEvent::FileAdded)
+ ostr << "FileAdded: " ;
+ else if (event.type() == FileChangeEvent::FileRemoved)
+ ostr << "FileRemoved: " ;
+ else if (event.type() == FileChangeEvent::FileModified)
+ ostr << "FileModified: ";
+
+ ostr << event.fileInfo();
+
+ if (event.fileInfo().isDirectory())
+ ostr << " (directory)";
+
+ return ostr;
+}
+
+template<typename PreviousIterator, typename CurrentIterator>
+void collectFileChangeEvents(PreviousIterator prevBegin,
+ PreviousIterator prevEnd,
+ CurrentIterator currBegin,
+ CurrentIterator currEnd,
+ const boost::function<bool(const FileInfo&)>& filter,
+ std::vector<FileChangeEvent>* pEvents)
+{
+ // sort the ranges
+ std::vector<FileInfo> prev;
+ std::copy(prevBegin, prevEnd, std::back_inserter(prev));
+ std::sort(prev.begin(), prev.end(), fileInfoPathLessThan);
+ std::vector<FileInfo> curr;
+ std::copy(currBegin, currEnd, std::back_inserter(curr));
+ std::sort(curr.begin(), curr.end(), fileInfoPathLessThan);
+
+ // initalize the iterators
+ std::vector<FileInfo>::iterator prevIt = prev.begin();
+ std::vector<FileInfo>::iterator currIt = curr.begin();
+
+ FileInfo noFile;
+ while (prevIt != prev.end() || currIt != curr.end())
+ {
+ const FileInfo& prevFile = prevIt != prev.end() ? *prevIt : noFile;
+ const FileInfo& currFile = currIt != curr.end() ? *currIt : noFile;
+
+ int comp;
+ if (prevFile.empty())
+ comp = 1;
+ else if (currFile.empty())
+ comp = -1;
+ else
+ comp = fileInfoPathCompare(prevFile, currFile);
+
+ if (comp == 0)
+ {
+ if (currFile.lastWriteTime() != prevFile.lastWriteTime())
+ {
+ if (!filter || filter(currFile))
+ {
+ pEvents->push_back(FileChangeEvent(FileChangeEvent::FileModified,
+ currFile));
+ }
+ }
+ prevIt++;
+ currIt++;
+ }
+ else if (comp < 0)
+ {
+ if (!filter || filter(prevFile))
+ {
+ pEvents->push_back(FileChangeEvent(FileChangeEvent::FileRemoved,
+ prevFile));
+ }
+ prevIt++;
+ }
+ else // comp > 1
+ {
+ if (!filter || filter(currFile))
+ {
+ pEvents->push_back(FileChangeEvent(FileChangeEvent::FileAdded,
+ currFile));
+ }
+ currIt++;
+ }
+ }
+}
+
+template<typename PreviousIterator, typename CurrentIterator>
+void collectFileChangeEvents(PreviousIterator prevBegin,
+ PreviousIterator prevEnd,
+ CurrentIterator currBegin,
+ CurrentIterator currEnd,
+ std::vector<FileChangeEvent>* pEvents)
+{
+ collectFileChangeEvents(prevBegin,
+ prevEnd,
+ currBegin,
+ currEnd,
+ boost::function<bool(const FileInfo&)>(),
+ pEvents);
+}
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_FILE_CHANGE_EVENT_HPP
+
+
diff --git a/src/cpp/core/include/core/system/FileMode.hpp b/src/cpp/core/include/core/system/FileMode.hpp
new file mode 100644
index 0000000..10e0f9a
--- /dev/null
+++ b/src/cpp/core/include/core/system/FileMode.hpp
@@ -0,0 +1,103 @@
+/*
+ * FileMode.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_SYSTEM_FILE_MODE_HPP
+#define CORE_SYSTEM_FILE_MODE_HPP
+
+#ifdef _WIN32
+#error FileMode.hpp is is not supported on Windows
+#endif
+
+#include <sys/stat.h>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+namespace core {
+namespace system {
+
+enum FileMode
+{
+ UserReadWriteMode,
+ UserReadWriteExecuteMode,
+ UserReadWriteGroupReadMode,
+ EveryoneReadMode,
+ EveryoneReadWriteMode,
+ EveryoneReadWriteExecuteMode
+};
+
+inline Error changeFileMode(const FilePath& filePath,
+ FileMode fileMode,
+ bool stickyBit)
+{
+ mode_t mode ;
+ switch(fileMode)
+ {
+ case UserReadWriteMode:
+ mode = S_IRUSR | S_IWUSR;
+ break;
+
+ case UserReadWriteExecuteMode:
+ mode = S_IRUSR | S_IWUSR | S_IXUSR;
+ break;
+
+ case UserReadWriteGroupReadMode:
+ mode = S_IRUSR | S_IWUSR | S_IRGRP ;
+ break;
+
+ case EveryoneReadMode:
+ mode = S_IRUSR | S_IRGRP | S_IROTH;
+ break;
+
+ case EveryoneReadWriteMode:
+ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ break;
+
+ case EveryoneReadWriteExecuteMode:
+ mode = S_IRWXU | S_IRWXG | S_IRWXO;
+ break;
+
+ default:
+ return systemError(ENOTSUP, ERROR_LOCATION);
+ }
+
+ // check for sticky bit
+ if (stickyBit)
+ mode |= S_ISVTX;
+
+ // change the mode
+ errno = 0;
+ if (::chmod(filePath.absolutePath().c_str(), mode) < 0)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", filePath);
+ return error;
+ }
+ else
+ return Success();
+}
+
+inline Error changeFileMode(const FilePath& filePath, FileMode fileMode)
+{
+ return changeFileMode(filePath, fileMode, false);
+}
+
+
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_FILE_MODE_HPP
diff --git a/src/cpp/core/include/core/system/FileMonitor.hpp b/src/cpp/core/include/core/system/FileMonitor.hpp
new file mode 100644
index 0000000..72f3215
--- /dev/null
+++ b/src/cpp/core/include/core/system/FileMonitor.hpp
@@ -0,0 +1,160 @@
+/*
+ * FileMonitor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_FILE_MONITOR_HPP
+#define CORE_SYSTEM_FILE_MONITOR_HPP
+
+#include <string>
+#include <set>
+#include <vector>
+
+#include <boost/function.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/collection/Tree.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/FileChangeEvent.hpp>
+
+// cross-platform recursive file monitoring service. note that the
+// implementation specifically avoids following soft-links. only directories
+// physically contained in the root will be monitored (this is to prevent
+// both extremely large trees and or self-referential (and thus infinitely
+// recursive) trees.
+
+namespace core {
+namespace system {
+namespace file_monitor {
+
+// initialize the file monitoring service (creates a background thread
+// which performs the monitoring)
+void initialize();
+
+// stop the file monitoring service (automatically unregisters all
+// active file monitoring handles)
+void stop();
+
+
+// opaque handle to a registration (used to unregister). the id field
+// is included so that handles have additional uniqueness beyond the
+// value of the pData pointer (which could be duplicated accross monitors
+// depending upon the order of allocations/deallocations)
+struct Handle
+{
+ Handle()
+ : pData(NULL)
+ {
+ }
+
+ explicit Handle(void* pData)
+ : id(core::system::generateUuid()),
+ pData(pData)
+ {
+ }
+
+ bool empty() const { return id.empty(); }
+
+ bool operator==(const Handle& other) const
+ {
+ return id == other.id &&
+ pData == other.pData;
+ }
+
+ bool operator < (const Handle& other) const
+ {
+ return id < other.id;
+ }
+
+ std::string id;
+ void* pData;
+};
+
+// file monitoring callbacks (all callbacks are optional)
+struct Callbacks
+{
+ // callback which occurs after a successful registration (includes an initial
+ // listing of all of the files in the directory)
+ boost::function<void(Handle, const tree<FileInfo>&)> onRegistered;
+
+ // callback which occurs if a registration error occurs
+ boost::function<void(const core::Error&)> onRegistrationError;
+
+ // callback which occurs if an error occurs during monitoring (the
+ // monitor is automatically unregistered if a monitoring error occurs)
+ boost::function<void(const core::Error&)> onMonitoringError;
+
+ // callback which occurs when files change
+ boost::function<void(const std::vector<FileChangeEvent>&)> onFilesChanged;
+
+ // callback which occurs when the monitor is fully unregistered. note that
+ // this callback can occur as a result of:
+ // - an explicit call to unregisterMonitor;
+ // - a monitoring error which caused an automatic unregistration; or
+ // - a call to the global file_monitor::stop function
+ boost::function<void(Handle)> onUnregistered;
+};
+
+// register a new file monitor. the result of this call will be an
+// aynchronous call to either onRegistered or onRegistrationError. onRegistered
+// will provide an opaque Handle which can used for a subsequent call
+// to unregisterMonitor. if you want to bind a c++ object to the lifetime
+// of this file monitor simply create a shared_ptr and bind its members
+// to the file monitor callbacks. note that if you also would like to
+// guarantee that the deletion of your shared_ptr object is invoked on the same
+// thread that called registerMonitor you should also bind a function to
+// onUnregistered (otherwise the delete will occur on the file monitoring thread)
+void registerMonitor(const core::FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks);
+
+// unregister a file monitor. note that file monitors can be automatically
+// unregistered in the case of errors or a call to global file_monitor::stop,
+// as a result multiple calls to unregisterMonitor are permitted (and no-op
+// if the handle has already been unregistered)
+void unregisterMonitor(Handle handle);
+
+
+// check for changes (will cause onRegistered, onRegistrationError,
+// onMonitoringError, onFilesChanged, and onUnregistered calls to occur
+// on the same thread that calls checkForChanges)
+void checkForChanges();
+
+
+
+// convenience functions for creating filters that are useful in
+// file monitoring scenarios
+
+// filter out any directory (and its children) with the specified name
+// (no matter where it is located within the tree). useful for directories
+// like .git, .svn, .RProj.user, etc.
+boost::function<bool(const FileInfo&)> excludeDirectoryFilter(
+ const std::string& name);
+
+// aggregate version of above
+boost::function<bool(const FileInfo&)> excludeDirectoriesFilter(
+ const std::vector<std::string>& names);
+
+// exclude hidden files
+boost::function<bool(const FileInfo&)> excludeHiddenFilter();
+
+
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_FILE_MONITOR_HPP
+
+
diff --git a/src/cpp/core/include/core/system/FileScanner.hpp b/src/cpp/core/include/core/system/FileScanner.hpp
new file mode 100644
index 0000000..501c78e
--- /dev/null
+++ b/src/cpp/core/include/core/system/FileScanner.hpp
@@ -0,0 +1,66 @@
+/*
+ * FileScanner.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_SYSTEM_FILE_SCANNER_HPP
+#define CORE_SYSTEM_FILE_SCANNER_HPP
+
+#include <boost/function.hpp>
+
+#include <core/Error.hpp>
+#include <core/FileInfo.hpp>
+
+#include <core/collection/Tree.hpp>
+
+
+namespace core {
+
+// recursively enumerate files from the specified root. these functions
+// are symlink aware -- this has two implications:
+//
+// (1) The FileInfo::isSymlink member returns accurate symlink status
+// (2) Symlink to directories are not traversed recursively
+
+namespace system {
+
+struct FileScannerOptions
+{
+ FileScannerOptions()
+ : recursive(false), yield(false)
+ {
+ }
+
+ bool recursive;
+ bool yield;
+ boost::function<bool(const FileInfo&)> filter;
+ boost::function<Error(const FileInfo&)> onBeforeScanDir;
+};
+
+Error scanFiles(const tree<FileInfo>::iterator_base& fromNode,
+ const FileScannerOptions& options,
+ tree<FileInfo>* pTree);
+
+inline Error scanFiles(const FileInfo& fromRoot,
+ const FileScannerOptions& options,
+ tree<FileInfo>* pTree)
+{
+ return scanFiles(pTree->set_head(fromRoot), options, pTree);
+}
+
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_FILE_SCANNER_HPP
diff --git a/src/cpp/core/include/core/system/LibraryLoader.hpp b/src/cpp/core/include/core/system/LibraryLoader.hpp
new file mode 100644
index 0000000..056cc8b
--- /dev/null
+++ b/src/cpp/core/include/core/system/LibraryLoader.hpp
@@ -0,0 +1,34 @@
+/*
+ * LibraryLoader.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_LIBRARY_LOADER_HPP
+#define CORE_SYSTEM_LIBRARY_LOADER_HPP
+
+#include <string>
+
+namespace core {
+
+class Error;
+
+namespace system {
+
+Error loadLibrary(const std::string& libPath, int options, void** ppLib);
+Error loadSymbol(void* pLib, const std::string& name, void** ppSymbol);
+Error closeLibrary(void* pLib);
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_LIBRARY_LOADER_HPP
diff --git a/src/cpp/core/include/core/system/OutputCapture.hpp b/src/cpp/core/include/core/system/OutputCapture.hpp
new file mode 100644
index 0000000..74c3c8a
--- /dev/null
+++ b/src/cpp/core/include/core/system/OutputCapture.hpp
@@ -0,0 +1,37 @@
+/*
+ * OutputCapture.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_OUTPUT_CAPTURE_HPP
+#define CORE_SYSTEM_OUTPUT_CAPTURE_HPP
+
+#include <string>
+
+#include <boost/function.hpp>
+
+namespace core {
+
+class Error;
+
+namespace system {
+
+Error captureStandardStreams(
+ const boost::function<void(const std::string&)>& stdoutHandler,
+ const boost::function<void(const std::string&)>& stderrHandler);
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_OUTPUT_CAPTURE_HPP
+
diff --git a/src/cpp/core/include/core/system/Pam.hpp b/src/cpp/core/include/core/system/Pam.hpp
new file mode 100644
index 0000000..676c465
--- /dev/null
+++ b/src/cpp/core/include/core/system/Pam.hpp
@@ -0,0 +1,58 @@
+/*
+ * Pam.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <security/pam_appl.h>
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+namespace core {
+namespace system {
+
+// NOTE: Mac OS X supports PAM but ships with it in a locked-down config
+// which will cause all passwords to be rejected. To make it work run:
+//
+// sudo cp /etc/pam.d/ftpd /etc/pam.d/rstudio
+//
+// That configures PAM to send rstudio through the same authentication
+// stack as ftpd uses, which is similar to us.
+
+
+// Low-level C++ wrapper around PAM API.
+class PAM : boost::noncopyable
+{
+public:
+ PAM(const std::string& service, bool silent);
+ virtual ~PAM();
+
+ std::pair<int, const std::string> lastError();
+
+ int status() const { return status_; }
+
+ virtual int login(const std::string& username,
+ const std::string& password);
+
+ virtual void close();
+
+protected:
+ std::string service_;
+ int defaultFlags_;
+ pam_handle_t* pamh_;
+ int status_;
+};
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/include/core/system/ParentProcessMonitor.hpp b/src/cpp/core/include/core/system/ParentProcessMonitor.hpp
new file mode 100644
index 0000000..2067d01
--- /dev/null
+++ b/src/cpp/core/include/core/system/ParentProcessMonitor.hpp
@@ -0,0 +1,39 @@
+/*
+ * ParentProcessMonitor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef PARENT_PROCESS_MONITOR_HPP
+#define PARENT_PROCESS_MONITOR_HPP
+
+#include <core/Error.hpp>
+#include <boost/function.hpp>
+
+namespace core {
+namespace parent_process_monitor {
+
+Error wrapFork(boost::function<void()> func);
+
+enum ParentTermination {
+ ParentTerminationNormal,
+ ParentTerminationAbnormal,
+ ParentTerminationNoParent,
+ ParentTerminationWaitFailure
+};
+
+ParentTermination waitForParentTermination();
+
+} // namespace parent_process_monitor
+} // namespace core
+
+#endif // PARENT_PROCESS_MONITOR_HPP
diff --git a/src/cpp/core/include/core/system/PosixChildProcessTracker.hpp b/src/cpp/core/include/core/system/PosixChildProcessTracker.hpp
new file mode 100644
index 0000000..93de356
--- /dev/null
+++ b/src/cpp/core/include/core/system/PosixChildProcessTracker.hpp
@@ -0,0 +1,59 @@
+/*
+ * PosixChildProcessTracker.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_CHILD_PROCESS_TRACKER_HPP
+#define CORE_SYSTEM_CHILD_PROCESS_TRACKER_HPP
+
+#include <map>
+
+#include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+
+#include <core/Thread.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace system {
+
+class ChildProcessTracker : boost::noncopyable
+{
+public:
+
+ typedef boost::function<void(PidType,int)> ExitHandler;
+
+ void addProcess(PidType pid, ExitHandler exitHandler = ExitHandler());
+
+ void notifySIGCHILD();
+
+private:
+ void attemptToReapProcess(const std::pair<PidType,ExitHandler>& process);
+ void removeProcess(PidType pid);
+ std::map<PidType,ExitHandler> activeProcesses();
+
+private:
+ boost::mutex mutex_;
+ std::map<PidType,ExitHandler> processes_;
+};
+
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_CHILD_PROCESS_TRACKER_HPP
+
diff --git a/src/cpp/core/include/core/system/PosixSystem.hpp b/src/cpp/core/include/core/system/PosixSystem.hpp
new file mode 100644
index 0000000..ca17b75
--- /dev/null
+++ b/src/cpp/core/include/core/system/PosixSystem.hpp
@@ -0,0 +1,127 @@
+/*
+ * PosixSystem.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_POSIX_SYSTEM_HPP
+#define CORE_SYSTEM_POSIX_SYSTEM_HPP
+
+#include <core/system/System.hpp>
+
+// typedefs (in case we need indirection on these for porting)
+#include <sys/resource.h>
+typedef pid_t PidType;
+typedef rlim_t RLimitType;
+
+
+namespace core {
+ class Error;
+}
+
+namespace core {
+namespace system {
+
+namespace user {
+ struct User;
+}
+
+// daemonize the process
+core::Error daemonize();
+
+// umask
+// file creation masks and file modes
+enum UMask
+{
+ OthersNoWriteMask, // S_IWGRP | S_IWOTH
+ OthersNoneMask // S_IWGRP | S_IRWXO
+};
+void setUMask(UMask mask);
+
+
+// resource limits
+enum ResourceLimit
+{
+ MemoryLimit,
+ FilesLimit,
+ UserProcessesLimit,
+ StackLimit
+};
+
+bool resourceIsUnlimited(RLimitType limitValue);
+
+core::Error getResourceLimit(ResourceLimit resourcelimit,
+ RLimitType* pSoft,
+ RLimitType* pHard);
+
+core::Error setResourceLimit(ResourceLimit resourceLimit, RLimitType limit);
+
+core::Error setResourceLimit(ResourceLimit resourceLimit,
+ RLimitType soft,
+ RLimitType hard);
+
+// launching child processes
+
+enum StdStreamBehavior
+{
+ StdStreamClose = 0,
+ StdStreamDevNull = 1,
+ StdStreamInherit = 2
+};
+
+struct ProcessConfig
+{
+ ProcessConfig()
+ : stdStreamBehavior(StdStreamInherit),
+ memoryLimitBytes(0),
+ stackLimitBytes(0),
+ userProcessesLimit(0)
+ {
+ }
+
+ core::system::Options args;
+ core::system::Options environment;
+ StdStreamBehavior stdStreamBehavior;
+ RLimitType memoryLimitBytes;
+ RLimitType stackLimitBytes;
+ RLimitType userProcessesLimit;
+};
+
+core::Error waitForProcessExit(PidType processId);
+
+core::Error launchChildProcess(std::string path,
+ std::string runAsUser,
+ ProcessConfig config,
+ PidType* pProcessId ) ;
+
+bool isUserNotFoundError(const core::Error& error);
+
+core::Error userBelongsToGroup(const user::User& user,
+ const std::string& groupName,
+ bool* pBelongs);
+
+// query priv state
+bool realUserIsRoot();
+bool effectiveUserIsRoot();
+
+// privillege management (not thread safe, call from main thread at app startup
+// or just after fork() prior to exec() for new processes)
+core::Error temporarilyDropPriv(const std::string& newUsername);
+core::Error permanentlyDropPriv(const std::string& newUsername);
+core::Error restorePriv();
+
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_POSIX_SYSTEM_HPP
+
diff --git a/src/cpp/core/include/core/system/PosixUser.hpp b/src/cpp/core/include/core/system/PosixUser.hpp
new file mode 100644
index 0000000..9467d94
--- /dev/null
+++ b/src/cpp/core/include/core/system/PosixUser.hpp
@@ -0,0 +1,65 @@
+/*
+ * PosixUser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_POSIX_USER_HPP
+#define CORE_SYSTEM_POSIX_USER_HPP
+
+#include <string>
+#include <unistd.h>
+
+// typdefs (in case we need indirection for porting)
+typedef uid_t UidType;
+typedef gid_t GidType;
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace core {
+namespace system {
+namespace user {
+
+struct UserIdentity
+{
+ UidType userId;
+ GidType groupId;
+};
+
+UserIdentity currentUserIdentity();
+
+core::Error socketPeerIdentity(int socket, UserIdentity* pIdentity);
+
+struct User
+{
+ UidType userId;
+ GidType groupId;
+ std::string username;
+ std::string homeDirectory;
+};
+
+core::Error currentUser(User* pUser);
+
+bool exists(const std::string& username);
+core::Error userFromUsername(const std::string& username, User* pUser);
+core::Error userFromId(UidType uid, User* pUser);
+
+
+} // namespace user
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_POSIX_USER_HPP
+
diff --git a/src/cpp/core/include/core/system/Process.hpp b/src/cpp/core/include/core/system/Process.hpp
new file mode 100644
index 0000000..299ae50
--- /dev/null
+++ b/src/cpp/core/include/core/system/Process.hpp
@@ -0,0 +1,328 @@
+/*
+ * Process.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#ifndef CORE_SYSTEM_PROCESS_HPP
+#define CORE_SYSTEM_PROCESS_HPP
+
+#include <vector>
+
+#include <boost/optional.hpp>
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <core/system/Types.hpp>
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error;
+
+namespace system {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Run child process synchronously
+//
+//
+
+// Struct for speicfying pseudoterminal options
+struct Pseudoterminal
+{
+ Pseudoterminal(int cols, int rows)
+ : cols(cols), rows(rows)
+ {
+ }
+
+ int cols;
+ int rows;
+};
+
+// Struct for specifying process options
+struct ProcessOptions
+{
+ ProcessOptions()
+#ifdef _WIN32
+ : terminateChildren(false),
+ detachProcess(false),
+ createNewConsole(false),
+ breakawayFromJob(false),
+ redirectStdErrToStdOut(false)
+#else
+ : terminateChildren(false),
+ detachSession(false),
+ redirectStdErrToStdOut(false)
+#endif
+ {
+ }
+
+ // environment variables to set for the child process
+ // if you want to simply merge in some additional environment
+ // variables you can use the helper functions in Environment.hpp
+ // to derive the desired environment
+ boost::optional<Options> environment;
+
+ // terminate should also terminate all children owned by the process
+ // NOTE: currently only supported on posix -- in the posix case this
+ // results in a call to ::setpgid(0,0) to create a new process group
+ // and the specification of -pid to kill so as to kill the child and
+ // all of its subprocesses
+ // NOTE: to support the same behavior on Win32 we'll need to use
+ // CreateJobObject/CREATE_BREAKAWAY_FROM_JOB to get the same effect
+ bool terminateChildren;
+
+#ifndef _WIN32
+ // Calls ::setsid after fork for POSIX (no effect on Windows)
+ bool detachSession;
+
+ // attach the child process to pseudoterminal pipes
+ boost::optional<Pseudoterminal> pseudoterminal;
+#endif
+
+#ifdef _WIN32
+ // Creates the process with DETACHED_PROCESS
+ bool detachProcess;
+
+ // Creates the process with CREATE_NEW_CONSOLE but with the console hidden
+ bool createNewConsole;
+
+ // create the process with CREATE_BREAKAWAY_FROM_JOB
+ bool breakawayFromJob;
+#endif
+
+ bool redirectStdErrToStdOut;
+
+ // If not empty, these two provide paths that stdout and stderr
+ // (respectively) should be redirected to. Note that this ONLY works
+ // if you use runCommand, not runProgram, as we use the shell to do
+ // the redirection.
+ core::FilePath stdOutFile;
+ core::FilePath stdErrFile;
+
+ // function to run within the child process immediately after the fork
+ // NOTE: only supported on posix as there is no fork on Win32
+ boost::function<void()> onAfterFork;
+
+ core::FilePath workingDir;
+};
+
+// Struct for returning output and exit status from a process
+struct ProcessResult
+{
+ ProcessResult() : exitStatus(-1) {}
+
+ // Standard output from process
+ std::string stdOut;
+
+ // Standard error from process
+ std::string stdErr;
+
+ // Process exit status. Potential values:
+ // 0 - successful execution
+ // 1 - application defined failure code (1, 2, 3, etc.)
+ // 15 - process killed by terminate()
+ // -1 - unable to determine exit status
+ int exitStatus;
+};
+
+
+// Run a program synchronously. Note that if executable is not an absolute
+// path then runProgram will duplicate the actions of the shell in searching
+// for an executable to run. Some platform specific notes:
+//
+// - Posix: The executable path is not executed by /bin/sh, rather it is
+// executed directly by ::execvp. This means that shell metacharacters
+// (e.g. stream redirection, piping, etc.) are not supported in the
+// command string.
+//
+// - Win32: The search for the executable path includes auto-appending .exe
+// and .cmd (in that order) for the path search and invoking cmd.exe if
+// the target is a batch (.cmd) file.
+//
+Error runProgram(const std::string& executable,
+ const std::vector<std::string>& args,
+ const std::string& input,
+ const ProcessOptions& options,
+ ProcessResult* pResult);
+
+// Run a command synchronously. The command will be passed to and executed
+// by a command shell (/bin/sh on posix, cmd.exe on windows).
+//
+Error runCommand(const std::string& command,
+ const ProcessOptions& options,
+ ProcessResult* pResult);
+
+Error runCommand(const std::string& command,
+ const std::string& input,
+ const ProcessOptions& options,
+ ProcessResult* pResult);
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessSupervisor -- run child processes asynchronously
+//
+// Any number of processes can be run by calling runProgram or runCommand and
+// their results will be delivered using the provided callbacks. Note that
+// the poll() method must be called periodically (e.g. during standard event
+// pumping / idle time) in order to check for output & status of children.
+//
+// If you want to pair a call to runProgam or runCommand with an object which
+// will live for the lifetime of the child process you should create a
+// shared_ptr to that object and then bind the applicable members to the
+// callback function(s) -- the bind will keep the shared_ptr alive (see the
+// implementation of the single-callback version of runProgram for an example)
+//
+//
+
+// Operations that can be performed from within ProcessCallbacks
+class ProcessOperations
+{
+public:
+ virtual ~ProcessOperations() {}
+
+ // Write (synchronously) to standard input
+ virtual Error writeToStdin(const std::string& input, bool eof) = 0;
+
+ // Operations which apply to Pseudoterminals (only available
+ // if ProcessOptions::pseudoterminal is specified)
+ virtual Error ptySetSize(int cols, int rows) = 0;
+ virtual Error ptyInterrupt() = 0;
+
+ // Terminate the process (SIGTERM)
+ virtual Error terminate() = 0;
+};
+
+// Callbacks for reporting various states and streaming output (note that
+// all callbacks are optional)
+struct ProcessCallbacks
+{
+ // Called after the process begins running (note: is called during
+ // the first call to poll and therefore after runAsync returns). Can
+ // be used for writing initial standard input to the child
+ boost::function<void(ProcessOperations&)> onStarted;
+
+ // Called periodically (at whatever interval poll is called) during the
+ // lifetime of the child process (will not be called until after the
+ // first call to onStarted). If it returns false then the child process
+ // is terminated.
+ boost::function<bool(ProcessOperations&)> onContinue;
+
+ // Streaming callback for standard output
+ boost::function<void(ProcessOperations&, const std::string&)> onStdout;
+
+ // Streaming callback for standard error
+ boost::function<void(ProcessOperations&, const std::string&)> onStderr;
+
+ boost::function<void(ProcessOperations&, const std::vector<char>&)>
+ onConsoleOutputSnapshot;
+
+ // Called if an IO error occurs while reading from standard streams. The
+ // default behavior if no callback is specified is to log and then terminate
+ // the child (which will result in onExit being called w/ exitStatus == 15)
+ boost::function<void(ProcessOperations&,const Error&)> onError;
+
+ // Called after the process has exited. Passes exitStatus (see ProcessResult
+ // comment above for potential values)
+ boost::function<void(int)> onExit;
+};
+
+ProcessCallbacks createProcessCallbacks(
+ const std::string& input,
+ const boost::function<void(const ProcessResult&)>& onCompleted,
+ const boost::function<void(const Error&)>& onError=
+ boost::function<void(const core::Error&)>());
+
+// Process supervisor
+class ProcessSupervisor : boost::noncopyable
+{
+public:
+ ProcessSupervisor();
+ virtual ~ProcessSupervisor();
+
+ // Run a child asynchronously, invoking callbacks as the process starts,
+ // produces output, and exits. Output callbacks are streamed/interleaved,
+ // but note that output is collected at a polling interval so it is
+ // possible that e.g. two writes to standard output which had an
+ // intervening write to standard input might still be concatenated. See
+ // comment on runProgram above for the semantics of the "executable"
+ // argument.
+ Error runProgram(const std::string& executable,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options,
+ const ProcessCallbacks& callbacks);
+
+ // Run a command asynchronously (same as above but uses a command shell
+ // rather than running the executable directly)
+ Error runCommand(const std::string& command,
+ const ProcessOptions& options,
+ const ProcessCallbacks& callbacks);
+
+ // Run a child asynchronously, invoking the completed callback when the
+ // process exits. Note that if input is provided then then the standard
+ // input stream is closed (so EOF is sent) after the input is written.
+ // Note also that the standard error handler (log and terminate) is also
+ // used. If you want more customized behavior then you can use the more
+ // granular runProgram call above. See comment on runProgram above for the
+ // semantics of the "command" argument.
+ Error runProgram(
+ const std::string& executable,
+ const std::vector<std::string>& args,
+ const std::string& input,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted);
+
+ // Run a command asynchronously (same as above but uses a command shell
+ // rather than running the executable directly)
+ Error runCommand(
+ const std::string& command,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted);
+
+ Error runCommand(
+ const std::string& command,
+ const std::string& input,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted);
+
+
+ // Check whether any children are currently active
+ bool hasRunningChildren();
+
+ // Poll for child (output and exit) events. returns true if there
+ // are still children being supervised after the poll
+ bool poll();
+
+ // Terminate all running children
+ void terminateAll();
+
+ // Wait for all children to exit. Returns false if the operaiton timed out
+ bool wait(
+ const boost::posix_time::time_duration& pollingInterval =
+ boost::posix_time::milliseconds(100),
+ const boost::posix_time::time_duration& maxWait =
+ boost::posix_time::time_duration(boost::posix_time::not_a_date_time));
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_PROCESS_HPP
diff --git a/src/cpp/core/include/core/system/ProcessArgs.hpp b/src/cpp/core/include/core/system/ProcessArgs.hpp
new file mode 100644
index 0000000..09cb3e9
--- /dev/null
+++ b/src/cpp/core/include/core/system/ProcessArgs.hpp
@@ -0,0 +1,96 @@
+/*
+ * ProcessArgs.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_PROCESS_ARGS_HPP
+#define CORE_SYSTEM_PROCESS_ARGS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+namespace core {
+namespace system {
+
+// helper class used to manage argument memory during child spawn
+class ProcessArgs : boost::noncopyable
+{
+public:
+ ProcessArgs() : argCount_(0), args_(NULL) {}
+
+ ProcessArgs(const std::vector<std::string>& args)
+ : argCount_(0), args_(NULL)
+ {
+ setArgs(args);
+ }
+
+ virtual ~ProcessArgs()
+ {
+ try
+ {
+ freeArgs();
+ }
+ catch(...)
+ {
+ }
+ }
+
+ bool empty() const { return argCount_ == 0; }
+ std::size_t argCount() const { return argCount_; }
+ char** args() const { return !empty() ? args_ : NULL; }
+
+private:
+ void setArgs(const std::vector<std::string>& args)
+ {
+ // free existing
+ freeArgs();
+
+ // allocate args
+ argCount_ = args.size() ;
+ args_ = new char*[argCount_+1] ;
+
+ // copy each arg to a buffer
+ for (unsigned int i=0; i<argCount_; i++)
+ {
+ const std::string& arg = args[i];
+ args_[i] = new char[arg.size() + 1];
+ arg.copy(args_[i], arg.size());
+ args_[i][arg.size()] = '\0';
+ }
+
+ // null terminate the list of args
+ args_[argCount_] = NULL;
+ }
+
+ void freeArgs()
+ {
+ if (argCount_ > 0)
+ {
+ for (std::size_t i = 0; i<argCount_; ++i)
+ delete [] args_[i] ;
+ delete [] args_ ;
+ }
+ }
+
+private:
+ std::size_t argCount_ ;
+ char** args_ ;
+};
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_PROCESS_ARGS_HPP
+
diff --git a/src/cpp/core/include/core/system/RecycleBin.hpp b/src/cpp/core/include/core/system/RecycleBin.hpp
new file mode 100644
index 0000000..6f473af
--- /dev/null
+++ b/src/cpp/core/include/core/system/RecycleBin.hpp
@@ -0,0 +1,35 @@
+/*
+ * RecycleBin.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_RECYCLE_BIN_HPP
+#define CORE_SYSTEM_RECYCLE_BIN_HPP
+
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace system {
+namespace recycle_bin {
+
+Error sendTo(const FilePath& filePath);
+
+} // namespace recycle_bin
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_RECYCLE_BIN_HPP
+
diff --git a/src/cpp/core/include/core/system/RegistryKey.hpp b/src/cpp/core/include/core/system/RegistryKey.hpp
new file mode 100644
index 0000000..178fa06
--- /dev/null
+++ b/src/cpp/core/include/core/system/RegistryKey.hpp
@@ -0,0 +1,57 @@
+/*
+ * RegistryKey.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef REGISTRYKEY_HPP
+#define REGISTRYKEY_HPP
+
+#ifndef _WIN32
+#error RegistryKey.hpp is Windows-specific
+#endif
+
+#include <string>
+
+#include <Windows.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <core/Error.hpp>
+
+namespace core {
+namespace system {
+
+class RegistryKey : boost::noncopyable
+{
+public:
+ RegistryKey();
+ virtual ~RegistryKey();
+
+ core::Error open(HKEY hKey, std::string subKey, REGSAM samDesired);
+ bool isOpen();
+
+ HKEY handle();
+
+ core::Error getStringValue(std::string name, std::string* pValue);
+ std::string getStringValue(std::string name, std::string defaultValue);
+
+ std::vector<std::string> keyNames();
+
+private:
+ HKEY hKey_;
+};
+
+} // namespace system
+} // namespace core
+
+#endif // REGISTRYKEY_HPP
diff --git a/src/cpp/core/include/core/system/ShellUtils.hpp b/src/cpp/core/include/core/system/ShellUtils.hpp
new file mode 100644
index 0000000..f72db49
--- /dev/null
+++ b/src/cpp/core/include/core/system/ShellUtils.hpp
@@ -0,0 +1,122 @@
+/*
+ * ShellUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SHELL_UTILS_HPP
+#define CORE_SHELL_UTILS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/regex.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+namespace core {
+
+namespace shell_utils {
+
+std::string escape(const std::string& arg);
+std::string escape(const FilePath& path);
+
+std::string join(const std::string& command1, const std::string& command2);
+std::string join_and(const std::string& command1, const std::string& command2);
+std::string join_or(const std::string& command1, const std::string& command2);
+
+std::string pipe(const std::string& command1, const std::string& command2);
+
+std::string sendStdErrToStdOut(const std::string& command);
+std::string sendAllOutputToNull(const std::string& command);
+std::string sendStdErrToNull(const std::string& command);
+std::string sendNullToStdIn(const std::string& command);
+
+const FilePath& devnull();
+
+enum EscapeMode
+{
+ EscapeAll,
+ EscapeFilesOnly
+};
+
+class ShellCommand
+{
+public:
+ explicit ShellCommand(const core::FilePath& filePath)
+ : escapeMode_(EscapeAll)
+ {
+ output_ = escape(string_utils::utf8ToSystem(filePath.absolutePath()));
+ }
+
+ explicit ShellCommand(const std::string& program)
+ : escapeMode_(EscapeAll)
+ {
+ boost::regex simpleCommand("^[a-zA-Z]+$");
+ if (boost::regex_match(program, simpleCommand))
+ output_ = program;
+ else
+ output_ = escape(program);
+ }
+
+ ShellCommand& operator<<(EscapeMode escapeMode);
+ ShellCommand& operator<<(const std::string& arg);
+ ShellCommand& operator<<(int arg);
+ ShellCommand& operator<<(const FilePath& path);
+ ShellCommand& operator<<(const std::vector<std::string> args);
+ ShellCommand& operator<<(const std::vector<FilePath> args);
+
+ operator std::string() const
+ {
+ return output_;
+ }
+
+ std::string string() const
+ {
+ return output_;
+ }
+
+private:
+ std::string maybeEscape(const std::string& value);
+
+ std::string output_;
+ EscapeMode escapeMode_;
+};
+
+class ShellArgs
+{
+public:
+ ShellArgs& operator<<(const std::string& arg);
+ ShellArgs& operator<<(int arg);
+ ShellArgs& operator<<(const FilePath& path);
+ ShellArgs& operator<<(const std::vector<std::string> args);
+ ShellArgs& operator<<(const std::vector<FilePath> args);
+
+ operator std::vector<std::string>() const
+ {
+ return args_;
+ }
+
+ std::vector<std::string> args() const
+ {
+ return args_;
+ }
+
+private:
+ std::vector<std::string> args_;
+};
+
+}
+}
+
+#endif // CORE_SHELL_UTILS_HPP
diff --git a/src/cpp/core/include/core/system/System.hpp b/src/cpp/core/include/core/system/System.hpp
new file mode 100644
index 0000000..eb201fb
--- /dev/null
+++ b/src/cpp/core/include/core/system/System.hpp
@@ -0,0 +1,271 @@
+/*
+ * System.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_SYSTEM_HPP
+#define CORE_SYSTEM_SYSTEM_HPP
+
+
+#if defined(_WIN32)
+#include <windef.h>
+typedef DWORD PidType;
+#else // UNIX
+#include <sys/types.h>
+typedef pid_t PidType;
+#endif
+
+#include <string>
+#include <vector>
+#include <map>
+#include <iosfwd>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+#include <core/system/Types.hpp>
+
+namespace core {
+
+class FileInfo;
+class FilePath;
+
+namespace system {
+
+enum LogLevel
+{
+ kLogLevelError = 0,
+ kLogLevelWarning = 1,
+ kLogLevelInfo = 2,
+ kLogLevelDebug = 3
+};
+
+// portable realPath
+Error realPath(const FilePath& filePath, FilePath* pRealPath);
+
+void addToSystemPath(const FilePath& path, bool prepend = false);
+
+#ifndef _WIN32
+Error closeAllFileDescriptors();
+Error closeNonStdFileDescriptors();
+void closeStdFileDescriptors();
+void attachStdFileDescriptorsToDevNull();
+void setStandardStreamsToDevNull();
+Error realPath(const std::string& path, FilePath* pRealPath);
+
+// Handles EINTR retrying. Only for use with functions that return -1 on
+// error and set errno.
+template <typename T>
+T posixCall(const boost::function<T()>& func)
+{
+ const T ERR = -1;
+
+ T result;
+ while (true)
+ {
+ result = func();
+
+ if (result == ERR && errno == EINTR)
+ continue;
+ else
+ break;
+ }
+
+ return result;
+}
+
+// Handles EINTR retrying and error construction (also optionally returns
+// the result as an out parameter). Only for use with functions that return
+// -1 on error and set errno.
+template <typename T>
+Error posixCall(const boost::function<T()>& func,
+ const ErrorLocation& location,
+ T *pResult = NULL)
+{
+ const T ERR = -1;
+
+ // make the call
+ T result = posixCall<T>(func);
+
+ // set out param (if requested)
+ if (pResult)
+ *pResult = result;
+
+ // return status
+ if (result == ERR)
+ return systemError(errno, location);
+ else
+ return Success();
+}
+
+// Handles EINTR retrying and error logging. Only for use with functions
+// that return -1 on error and set errno.
+template <typename T>
+void safePosixCall(const boost::function<T()>& func,
+ const ErrorLocation& location)
+{
+ Error error = posixCall<T>(func, location, NULL);
+ if (error)
+ LOG_ERROR(error);
+}
+
+#endif
+
+#ifdef _WIN32
+bool isWin64();
+bool isVistaOrLater();
+Error makeFileHidden(const FilePath& path);
+Error copyMetafileToClipboard(const FilePath& path);
+void ensureLongPath(FilePath* pFilePath);
+
+// close a handle then set it to NULL (so we can call this function
+// repeatedly without failure or other side effects)
+Error closeHandle(HANDLE* pHandle, const ErrorLocation& location);
+
+class CloseHandleOnExitScope : boost::noncopyable
+{
+public:
+ CloseHandleOnExitScope(HANDLE* pHandle, const ErrorLocation& location)
+ : pHandle_(pHandle), location_(location)
+ {
+ }
+
+ virtual ~CloseHandleOnExitScope();
+
+private:
+ HANDLE* pHandle_;
+ ErrorLocation location_;
+};
+
+
+
+#endif
+
+void initHook();
+// initialization (not thread safe, call from main thread at app startup)
+void initializeSystemLog(const std::string& programIdentity, int logLevel);
+void initializeStderrLog(const std::string& programIdentity, int logLevel);
+void initializeLog(const std::string& programIdentity,
+ int logLevel,
+ const FilePath& logDir);
+
+Error setExitFunction(void (*exitFunction) (void));
+
+// exit
+int exitFailure(const Error& error, const ErrorLocation& loggedFromLocation);
+int exitFailure(const std::string& errMsg,
+ const ErrorLocation& loggedFromLocation);
+
+// signals
+
+// ignore selected signals
+Error ignoreTerminalSignals();
+Error ignoreChildExits();
+
+// reap children (better way to handle child exits than ignoreChildExits
+// because it doesn't prevent system from determining exit codes)
+Error reapChildren();
+
+
+enum SignalType
+{
+ SigInt,
+ SigHup,
+ SigAbrt,
+ SigSegv,
+ SigIll,
+ SigUsr1,
+ SigUsr2,
+ SigPipe,
+ SigChld
+};
+
+
+
+// block all signals for a given scope
+class SignalBlocker : boost::noncopyable
+{
+public:
+ SignalBlocker();
+ virtual ~SignalBlocker();
+ // COPYING: boost::noncopyable
+
+ Error block(SignalType signal);
+ Error blockAll();
+
+private:
+ struct Impl;
+ boost::shared_ptr<Impl> pImpl_;
+};
+
+core::Error clearSignalMask();
+
+core::Error handleSignal(SignalType signal, void (*handler)(int));
+core::Error ignoreSignal(SignalType signal);
+core::Error useDefaultSignalHandler(SignalType signal);
+
+void sendSignalToSelf(SignalType signal);
+
+// user info
+std::string username();
+FilePath userHomePath(std::string envOverride = std::string());
+FilePath userSettingsPath(const FilePath& userHomeDirectory,
+ const std::string& appName);
+bool currentUserIsPrivilleged(unsigned int minimumUserId);
+
+// log
+void log(LogLevel level, const std::string& message) ;
+
+// filesystem
+bool isHiddenFile(const FilePath& filePath) ;
+bool isHiddenFile(const FileInfo& fileInfo) ;
+bool isReadOnly(const FilePath& filePath);
+
+// terminals
+bool stderrIsTerminal();
+bool stdoutIsTerminal();
+
+// uuid
+std::string generateUuid(bool includeDashes = true);
+std::string generateShortenedUuid();
+
+// process info
+
+PidType currentProcessId();
+
+Error executablePath(int argc, const char * argv[],
+ FilePath* pExecutablePath);
+
+Error executablePath(const char * argv0,
+ FilePath* pExecutablePath);
+
+
+Error installPath(const std::string& relativeToExecutable,
+ const char * argv0,
+ FilePath* pInstallationPath);
+
+void fixupExecutablePath(FilePath* pExePath);
+
+void abort();
+
+Error terminateProcess(PidType pid);
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_SYSTEM_HPP
+
diff --git a/src/cpp/core/include/core/system/Types.hpp b/src/cpp/core/include/core/system/Types.hpp
new file mode 100644
index 0000000..bd044fc
--- /dev/null
+++ b/src/cpp/core/include/core/system/Types.hpp
@@ -0,0 +1,31 @@
+/*
+ * Types.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_TYPES_HPP
+#define CORE_SYSTEM_TYPES_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+namespace system {
+
+typedef std::pair<std::string,std::string> Option;
+typedef std::vector<Option> Options;
+
+} // namespace system
+} // namespace stypes
+
+#endif // CORE_SYSTEM_TYPES_HPP
diff --git a/src/cpp/core/include/core/tex/TexLogParser.hpp b/src/cpp/core/include/core/tex/TexLogParser.hpp
new file mode 100644
index 0000000..2075349
--- /dev/null
+++ b/src/cpp/core/include/core/tex/TexLogParser.hpp
@@ -0,0 +1,86 @@
+/*
+ * TexLogParser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_TEX_TEX_LOG_PARSER_HPP
+#define CORE_TEX_TEX_LOG_PARSER_HPP
+
+#include <string>
+#include <vector>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+
+class Error;
+
+namespace tex {
+
+class LogEntry
+{
+public:
+ enum Type { Error = 0, Warning = 1, Box = 2};
+
+public:
+ LogEntry(const FilePath& logFilePath,
+ int logLine,
+ Type type,
+ const FilePath& filePath,
+ int line,
+ const std::string& message)
+ : type_(type), logFilePath_(logFilePath), logLine_(logLine),
+ filePath_(filePath), line_(line), message_(message)
+ {
+ }
+
+ // COPYING: via compiler
+
+public:
+ Type type() const { return type_; }
+
+ // The log file in which this entry can be found
+ const FilePath& logFilePath() const { return logFilePath_; }
+
+ // The line number at which this entry can be found in the log file
+ int logLine() const { return logLine_; }
+
+ // The source file that this error refers to
+ const FilePath& filePath() const { return filePath_; }
+
+ // The line number in the source file that this error refers to
+ int line() const { return line_; }
+
+ const std::string& message() const { return message_; }
+
+private:
+ Type type_;
+ FilePath logFilePath_;
+ int logLine_;
+ FilePath filePath_;
+ int line_;
+ std::string message_;
+};
+
+typedef std::vector<LogEntry> LogEntries;
+
+Error parseLatexLog(const FilePath& logFilePath, LogEntries* pLogEntries);
+
+Error parseBibtexLog(const FilePath& logFilePath, LogEntries* pLogEntries);
+
+} // namespace tex
+} // namespace core
+
+
+#endif // CORE_TEX_TEX_LOG_PARSER_HPP
+
diff --git a/src/cpp/core/include/core/tex/TexMagicComment.hpp b/src/cpp/core/include/core/tex/TexMagicComment.hpp
new file mode 100644
index 0000000..ce3ba69
--- /dev/null
+++ b/src/cpp/core/include/core/tex/TexMagicComment.hpp
@@ -0,0 +1,61 @@
+/*
+ * TexMagicComment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_TEX_TEX_MAGIC_COMMENT_HPP
+#define CORE_TEX_TEX_MAGIC_COMMENT_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace tex {
+
+class TexMagicComment
+{
+public:
+
+ TexMagicComment(const std::string& scope,
+ const std::string& variable,
+ const std::string& value)
+ : scope_(scope), variable_(variable), value_(value)
+ {
+ }
+
+ const std::string& scope() const { return scope_; }
+ const std::string& variable() const { return variable_; }
+ const std::string& value() const { return value_; }
+
+private:
+ std::string scope_;
+ std::string variable_;
+ std::string value_;
+};
+
+typedef std::vector<TexMagicComment> TexMagicComments;
+
+Error parseMagicComments(const FilePath& texFile, TexMagicComments* pComments);
+
+
+
+} // namespace tex
+} // namespace core
+
+
+#endif // CORE_TEX_TEX_MAGIC_COMMENT_HPP
+
diff --git a/src/cpp/core/include/core/tex/TexSynctex.hpp b/src/cpp/core/include/core/tex/TexSynctex.hpp
new file mode 100644
index 0000000..c7c26b1
--- /dev/null
+++ b/src/cpp/core/include/core/tex/TexSynctex.hpp
@@ -0,0 +1,143 @@
+/*
+ * TexSynctex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// This is a C++ wrapper for the SyncTeX utilities from the TexLive project.
+// We have an embedded copy of the synctex code at core/tex/synctex. The
+// original sources can be accessed/updated from:
+//
+// svn://www.tug.org/texlive/trunk/Build/source/texk/web2c/synctexdir/
+//
+
+#ifndef CORE_TEX_TEX_SYNCTEX_HPP
+#define CORE_TEX_TEX_SYNCTEX_HPP
+
+#include <iosfwd>
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+namespace tex {
+
+class SourceLocation
+{
+public:
+ SourceLocation()
+ : line_(0)
+ {
+ }
+
+ SourceLocation(const FilePath& file, int line, int column)
+ : file_(file), line_(line), column_(column)
+ {
+ }
+
+ // COPYING: via compiler
+
+ bool empty() const { return file().empty(); }
+
+ const FilePath& file() const { return file_; }
+
+ // 1-based line and column. Note that synctex returns 0 if there
+ // is no column information available
+ int line() const { return line_; }
+ int column() const { return column_; }
+
+private:
+ FilePath file_;
+ int line_;
+ int column_;
+};
+
+
+std::ostream& operator << (std::ostream& stream, const SourceLocation& loc);
+
+class PdfLocation
+{
+public:
+ PdfLocation()
+ : page_(0), x_(0), y_(0), width_(0), height_(0)
+ {
+ }
+
+ PdfLocation(int page, float x, float y)
+ : page_(page), x_(x), y_(y), width_(0), height_(0)
+ {
+ }
+
+ PdfLocation(int page, float x, float y, float width, float height)
+ : page_(page), x_(x), y_(y), width_(width), height_(height)
+ {
+ }
+
+ // COPYING: via compiler
+
+ bool empty() const { return page() < 1; }
+
+ // 1-based pages
+ int page() const { return page_; }
+
+ // coordinates in 72-dpi units (require transform for both dpi as
+ // as well as whatever magnfification level is currently active)
+ float x() const { return x_; }
+ float y() const { return y_; }
+ float width() const { return width_; }
+ float height() const { return height_; }
+
+private:
+ int page_;
+ float x_;
+ float y_;
+ float width_;
+ float height_;
+};
+
+std::ostream& operator << (std::ostream& stream, const PdfLocation& loc);
+
+class Synctex : boost::noncopyable
+{
+public:
+ Synctex();
+ virtual ~Synctex();
+ // COPYING: prohibited
+
+public:
+ bool parse(const FilePath& pdfPath);
+
+ PdfLocation forwardSearch(const SourceLocation& location);
+ SourceLocation inverseSearch(const PdfLocation& location);
+
+ PdfLocation topOfPageContent(int page);
+
+private:
+ std::string synctexNameForInputFile(const FilePath& inputFile);
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+};
+
+std::string normalizeSynctexName(const std::string& name);
+
+
+} // namespace tex
+} // namespace core
+
+
+#endif // CORE_TEX_TEX_SYNCTEX_HPP
+
diff --git a/src/cpp/core/include/core/text/CsvParser.hpp b/src/cpp/core/include/core/text/CsvParser.hpp
new file mode 100644
index 0000000..2dfb0e9
--- /dev/null
+++ b/src/cpp/core/include/core/text/CsvParser.hpp
@@ -0,0 +1,132 @@
+/*
+ * CsvParser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CSV_PARSER_HPP
+#define CSV_PARSER_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+namespace text {
+
+/*
+Parses up to one line of CSV data. Empty lines will be skipped.
+
+The value returned is a pair consisting of the line that was
+successfully parsed and an iterator value that indicates where
+parsing should begin next time.
+
+If less than one line of CSV data is available, an empty vector
+is returned.
+
+This implementation is RFC4180 compliant.
+
+Note that if parseCsvLine is called in a loop, the termination
+condition should be that the returned vector is empty, NOT that
+the returned iterator == end. (In the case of malformed or
+incomplete CSV data that do not end with a line break, the
+returned iterator will never move past the beginning of the
+last line.)
+*/
+template<typename InputIterator>
+std::pair<std::vector<std::string>, InputIterator> parseCsvLine(InputIterator begin,
+ InputIterator end)
+{
+ std::vector<std::string> line;
+
+ bool inQuote = false;
+
+ std::string element;
+
+ InputIterator pos = begin;
+ while (pos != end)
+ {
+ bool noIncrement = false;
+
+ if (inQuote)
+ {
+ if (*pos == '"')
+ {
+ if (++pos != end)
+ {
+ if (*pos == '"')
+ {
+ element.push_back('"');
+ ++pos;
+ continue;
+ }
+ }
+ noIncrement = true;
+ inQuote = false;
+ }
+ else
+ {
+ element.push_back(*pos);
+ }
+ }
+ else // not in quote
+ {
+ if (*pos == '"')
+ {
+ // starting a quote
+ element.clear();
+ inQuote = true;
+ }
+ else if (*pos == ',')
+ {
+ line.push_back(element);
+ element.clear();
+ }
+ else if (*pos == '\r')
+ {
+ // ignore--expect a \n next
+ }
+ else if (*pos == '\n')
+ {
+ if (!element.empty() || !line.empty())
+ {
+ line.push_back(element);
+ element.clear();
+ }
+
+ begin = ++pos;
+ noIncrement = true;
+
+ // don't return blank lines
+ if (!line.empty())
+ {
+ return std::pair<std::vector<std::string>, InputIterator>(
+ line, begin);
+ }
+ }
+ else
+ {
+ element.push_back(*pos);
+ }
+ }
+
+ if (!noIncrement)
+ ++pos;
+ }
+
+ return std::pair<std::vector<std::string>, InputIterator>(
+ std::vector<std::string>(), begin);
+}
+
+} // namespace text
+} // namespace core
+
+#endif // CSV_PARSER_HPP
diff --git a/src/cpp/core/include/core/text/DcfParser.hpp b/src/cpp/core/include/core/text/DcfParser.hpp
new file mode 100644
index 0000000..eea7797
--- /dev/null
+++ b/src/cpp/core/include/core/text/DcfParser.hpp
@@ -0,0 +1,54 @@
+/*
+ * DcfParser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DCF_PARSER_HPP
+#define DCF_PARSER_HPP
+
+#include <string>
+#include <map>
+
+#include <boost/function.hpp>
+
+
+namespace core {
+
+class Error;
+class FilePath;
+
+namespace text {
+
+extern const char * const kDcfFieldRegex;
+
+typedef boost::function<void(const std::pair<std::string,std::string>&)>
+ DcfFieldRecorder;
+
+Error parseDcfFile(const std::string& dcfFileContents,
+ bool preserveKeyCase,
+ DcfFieldRecorder recordField,
+ std::string* pUserErrMsg);
+
+Error parseDcfFile(const FilePath& dcfFilePath,
+ bool preserveKeyCase,
+ std::map<std::string,std::string>* pFields,
+ std::string* pUserErrMsg);
+
+
+std::string dcfMultilineAsFolded(const std::string& line);
+
+
+} // namespace text
+} // namespace core
+
+#endif // DCF_PARSER_HPP
diff --git a/src/cpp/core/include/core/text/TemplateFilter.hpp b/src/cpp/core/include/core/text/TemplateFilter.hpp
new file mode 100644
index 0000000..cc54d25
--- /dev/null
+++ b/src/cpp/core/include/core/text/TemplateFilter.hpp
@@ -0,0 +1,96 @@
+/*
+ * TemplateFilter.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_TEXT_TEMPLATE_FILTER_HPP
+#define CORE_TEXT_TEMPLATE_FILTER_HPP
+
+#include <iostream>
+
+#include <string>
+#include <map>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <boost/iostreams/filter/regex.hpp>
+
+#include <core/StringUtils.hpp>
+
+namespace core {
+
+class FilePath;
+namespace http {
+ class Request;
+ class Response;
+}
+
+namespace text {
+
+// Add variables to templates using #foo# syntax. All values will be
+// HTML-escaped automatically unless prepended with !, e.g. #!foo# will
+// use the raw value of foo. Alternatively, you can prepend with ' and JS
+// literal escaping will be used instead of HTML escaping (i.e. the value
+// will be prepared for inserting into a JavaScript string literal).
+class TemplateFilter : public boost::iostreams::regex_filter
+{
+public:
+ TemplateFilter(const std::map<std::string, std::string>& variables)
+ : boost::iostreams::regex_filter(
+ boost::regex("#([!\\']?)([A-Za-z0-9_-]+)#"),
+ boost::bind(&TemplateFilter::substitute, this, _1)),
+
+ variables_(variables)
+ {
+ }
+
+private:
+ std::string substitute(const boost::cmatch& match)
+ {
+ std::map<std::string, std::string>::const_iterator valPos =
+ variables_.find(match[2]);
+ if (valPos != variables_.end())
+ {
+ if (match[1] == "!")
+ return valPos->second;
+ else if (match[1] == "'")
+ return string_utils::jsLiteralEscape(valPos->second);
+ else
+ return string_utils::htmlEscape(valPos->second, true);
+ return valPos->second;
+ }
+ else
+ return "MISSING VALUE";
+ }
+
+private:
+ std::map<std::string, std::string> variables_;
+};
+
+
+
+void handleTemplateRequest(const FilePath& templatePath,
+ const http::Request& request,
+ http::Response* pResponse);
+
+void handleSecureTemplateRequest(const std::string& username,
+ const FilePath& progressPagePath,
+ const http::Request& request,
+ http::Response* pResponse);
+
+} // namespace text
+} // namespace core
+
+
+#endif // CORE_TEXT_TEMPLATE_FILTER_HPP
diff --git a/src/cpp/core/json/Json.cpp b/src/cpp/core/json/Json.cpp
new file mode 100644
index 0000000..16bc993
--- /dev/null
+++ b/src/cpp/core/json/Json.cpp
@@ -0,0 +1,79 @@
+/*
+ * Json.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/json/Json.hpp>
+
+#include <cstdlib>
+#include <sstream>
+
+#include <boost/format.hpp>
+#include <boost/scoped_array.hpp>
+
+#include <core/Log.hpp>
+#include <core/Thread.hpp>
+
+#include "spirit/json_spirit.h"
+
+namespace core {
+namespace json {
+
+json_spirit::Value_type ObjectType = json_spirit::obj_type;
+json_spirit::Value_type ArrayType = json_spirit::array_type;
+json_spirit::Value_type StringType = json_spirit::str_type;
+json_spirit::Value_type BooleanType = json_spirit::bool_type;
+json_spirit::Value_type IntegerType = json_spirit::int_type;
+json_spirit::Value_type RealType = json_spirit::real_type;
+json_spirit::Value_type NullType = json_spirit::null_type;
+
+json::Value toJsonString(const std::string& val)
+{
+ return json::Value(val);
+}
+
+bool parse(const std::string& input, Value* pValue)
+{
+ // two threads simultaneously using the json parser has been observed
+ // to crash the process. protect it globally with a mutex. note this was
+ // probably a result of not defining BOOST_SPIRIT_THREADSAFE (which we
+ // have subsequently defined) however since there isn't much documentation
+ // on the behavior of boost spirit w/ threads and the specific behavior
+ // of this constant we leave in mutex just to be sure
+
+ static boost::mutex s_spiritMutex ;
+ LOCK_MUTEX(s_spiritMutex)
+ {
+ return json_spirit::read(input, *pValue);
+ }
+ END_LOCK_MUTEX
+
+ // mutex related error
+ return false;
+}
+
+void write(const Value& value, std::ostream& os)
+{
+ json_spirit::write(value, os);
+}
+
+void writeFormatted(const Value& value, std::ostream& os)
+{
+ json_spirit::write_formatted(value, os);
+}
+
+} // namespace json
+} // namespace core
+
+
+
diff --git a/src/cpp/core/json/JsonRpc.cpp b/src/cpp/core/json/JsonRpc.cpp
new file mode 100644
index 0000000..1672d60
--- /dev/null
+++ b/src/cpp/core/json/JsonRpc.cpp
@@ -0,0 +1,380 @@
+/*
+ * JsonRpc.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/json/JsonRpc.hpp>
+
+#include <sstream>
+
+#include <core/Log.hpp>
+#include <core/http/Response.hpp>
+
+
+namespace core {
+namespace json {
+
+const char * const kRpcResult = "result";
+const char * const kRpcAsyncHandle = "asyncHandle";
+const char * const kRpcError = "error";
+const char * const kJsonContentType = "application/json" ;
+
+Error parseJsonRpcRequest(const std::string& input, JsonRpcRequest* pRequest)
+{
+ // json_spirit is not documented to throw an exceptions but surround
+ // the code with an exception handling block just to be defensive...
+ try
+ {
+ // parse data and verify it contains an object
+ json::Value var;
+ if ( !json::parse(input, &var) ||
+ (var.type() != json::ObjectType) )
+ {
+ return Error(errc::InvalidRequest, ERROR_LOCATION) ;
+ }
+
+ // extract the fields
+ json::Object& requestObject = var.get_obj();
+ for (json::Object::const_iterator it =
+ requestObject.begin(); it != requestObject.end(); ++it)
+ {
+ std::string fieldName = it->first ;
+ json::Value fieldValue = it->second ;
+
+ if ( fieldName == "method" )
+ {
+ if (fieldValue.type() != json::StringType)
+ return Error(errc::InvalidRequest, ERROR_LOCATION) ;
+
+ pRequest->method = fieldValue.get_str() ;
+ }
+ else if ( fieldName == "params" )
+ {
+ if (fieldValue.type() != json::ArrayType)
+ return Error(errc::ParamTypeMismatch, ERROR_LOCATION) ;
+
+ pRequest->params = fieldValue.get_array();
+ }
+ else if ( fieldName == "kwparams" )
+ {
+ if (fieldValue.type() != json::ObjectType)
+ return Error(errc::ParamTypeMismatch, ERROR_LOCATION) ;
+
+ pRequest->kwparams = fieldValue.get_obj();
+ }
+ else if (fieldName == "sourceWnd")
+ {
+ if (fieldValue.type() != json::StringType)
+ return Error(errc::InvalidRequest, ERROR_LOCATION);
+
+ pRequest->sourceWindow = fieldValue.get_str();
+ }
+ else if (fieldName == "clientId" )
+ {
+ if (fieldValue.type() != json::StringType)
+ return Error(errc::InvalidRequest, ERROR_LOCATION);
+
+ pRequest->clientId = fieldValue.get_str();
+ }
+ else if (fieldName == "version" )
+ {
+ if (!json::isType<double>(fieldValue))
+ return Error(errc::InvalidRequest, ERROR_LOCATION);
+
+ pRequest->version = fieldValue.get_value<double>();
+ }
+ }
+
+ // method is required
+ if (pRequest->method.empty() )
+ return Error(errc::InvalidRequest, ERROR_LOCATION) ;
+
+ return Success() ;
+ }
+ catch(const std::exception& e)
+ {
+ Error error = Error(errc::ParseError, ERROR_LOCATION);
+ error.addProperty("exception", e.what()) ;
+ return error ;
+ }
+}
+
+bool parseJsonRpcRequestForMethod(const std::string& input,
+ const std::string& method,
+ json::JsonRpcRequest* pRequest,
+ http::Response* pResponse)
+{
+ // parse request
+ Error parseError = parseJsonRpcRequest(input, pRequest) ;
+ if (parseError)
+ {
+ LOG_ERROR(parseError);
+ setJsonRpcError(parseError, pResponse);
+ return false;
+ }
+
+ // check for method
+ if (pRequest->method != method)
+ {
+ Error methodError = Error(errc::MethodNotFound, ERROR_LOCATION);
+ methodError.addProperty("method", pRequest->method);
+ LOG_ERROR(methodError);
+ setJsonRpcError(methodError, pResponse);
+ return false;
+ }
+
+ // success
+ return true ;
+}
+
+namespace {
+
+void copyErrorCodeToJsonError(const boost::system::error_code& code,
+ json::Object* pError)
+{
+ pError->operator[]("code") = code.value();
+ pError->operator[]("message") = code.message();
+}
+
+}
+
+void JsonRpcResponse::setAfterResponse(
+ const boost::function<void()>& afterResponse)
+{
+ afterResponse_ = afterResponse;
+}
+
+bool JsonRpcResponse::hasAfterResponse() const
+{
+ return afterResponse_;
+}
+
+
+void JsonRpcResponse::runAfterResponse()
+{
+ if (afterResponse_)
+ afterResponse_();
+}
+
+json::Object JsonRpcResponse::getRawResponse()
+{
+ return response_;
+}
+
+void JsonRpcResponse::write(std::ostream& os) const
+{
+ json::write(response_, os);
+}
+
+void JsonRpcResponse::setError(const Error& error, const json::Value& clientInfo)
+{
+ // remove result
+ response_.erase(kRpcResult);
+ response_.erase(kRpcAsyncHandle);
+
+ const boost::system::error_code& ec = error.code();
+
+ if ( ec.category() == jsonRpcCategory() )
+ {
+ setError(ec);
+ }
+ else
+ {
+ // execution error
+ json::Object jsonError ;
+ copyErrorCodeToJsonError(errc::ExecutionError, &jsonError);
+
+ // populate sub-error field with error details
+ json::Object executionError;
+ executionError["code"] = ec.value();
+ std::string errorCategoryName = ec.category().name();
+ executionError["category"] = errorCategoryName;
+ std::string errorMessage = ec.message();
+ executionError["message"] = errorMessage;
+ jsonError["error"] = executionError;
+ if (!clientInfo.is_null())
+ {
+ jsonError["client_info"] = clientInfo;
+ }
+
+ // set error
+ setField(kRpcError, jsonError);
+ }
+}
+
+void JsonRpcResponse::setError(const Error& error)
+{
+ setError(error, json::Value());
+}
+
+void JsonRpcResponse::setError(const boost::system::error_code& ec)
+{
+ // remove result
+ response_.erase(kRpcResult);
+ response_.erase(kRpcAsyncHandle);
+
+ // error from error code
+ json::Object error ;
+ copyErrorCodeToJsonError(ec, &error);
+
+ // sub-error is null
+ error["error"] = json::Value();
+
+ // set error
+ setField(kRpcError, error);
+}
+
+void JsonRpcResponse::setAsyncHandle(const std::string& handle)
+{
+ response_.erase(kRpcResult);
+ response_.erase(kRpcError);
+
+ setField(kRpcAsyncHandle, handle);
+}
+
+void setJsonRpcResponse(const core::json::JsonRpcResponse& jsonRpcResponse,
+ core::http::Response* pResponse)
+{
+ // no cache!
+ pResponse->setNoCacheHeaders();
+
+ // set content type if necessary (allows callers to override the
+ // default application/json content-type, which is necessary in some
+ // circumstances such as returning results to the GWT FileUpload widget
+ // (which expects text/html)
+ if (pResponse->contentType().empty())
+ pResponse->setContentType(kJsonContentType) ;
+
+ // set body
+ std::stringstream responseStream ;
+ jsonRpcResponse.write(responseStream);
+ Error error = pResponse->setBody(responseStream);
+
+ // report error to client if one occurred
+ if (error)
+ {
+ LOG_ERROR(error);
+ pResponse->setError(http::status::InternalServerError,
+ error.code().message());
+ }
+}
+
+
+class JsonRpcErrorCategory : public boost::system::error_category
+{
+public:
+ virtual const char * name() const;
+ virtual std::string message( int ev ) const;
+};
+
+const boost::system::error_category& jsonRpcCategory()
+{
+ static JsonRpcErrorCategory jsonRpcErrorCategoryConst ;
+ return jsonRpcErrorCategoryConst ;
+}
+
+const char * JsonRpcErrorCategory::name() const
+{
+ return "jsonrpc" ;
+}
+
+std::string JsonRpcErrorCategory::message( int ev ) const
+{
+ switch(ev)
+ {
+ case errc::Success:
+ return "Method call succeeded" ;
+
+ case errc::ConnectionError:
+ return "Unable to connect to service" ;
+
+ case errc::Unavailable:
+ return "Service currently unavailable";
+
+ case errc::Unauthorized:
+ return "Client unauthorized" ;
+
+ case errc::InvalidClientId:
+ return "Invalid client id";
+
+ case errc::ParseError:
+ return "Invalid json or unexpected error occurred while parsing" ;
+
+ case errc::InvalidRequest:
+ return "Invalid json-rpc request" ;
+
+ case errc::MethodNotFound:
+ return "Method not found" ;
+
+ case errc::ParamMissing:
+ return "Parameter missing" ;
+
+ case errc::ParamTypeMismatch:
+ return "Parameter type mismatch" ;
+
+ case errc::ParamInvalid:
+ return "Parameter value invalid";
+
+ case errc::MethodUnexpected:
+ return "Unexpected call to method" ;
+
+ case errc::ExecutionError:
+ return "Error occurred while executing method" ;
+
+ case errc::TransmissionError:
+ return "Error occurred during transmission";
+
+ case errc::InvalidClientVersion:
+ return "Invalid client version";
+
+ case errc::ServerOffline:
+ return "Server is offline";
+
+ default:
+ BOOST_ASSERT(false);
+ return "Unknown error type" ;
+ }
+}
+
+namespace {
+
+void runSynchronousFunction(const JsonRpcFunction& func,
+ const core::json::JsonRpcRequest& request,
+ const JsonRpcFunctionContinuation& continuation)
+{
+ core::json::JsonRpcResponse response;
+ if (request.isBackgroundConnection)
+ response.setSuppressDetectChanges(true);
+ core::Error error = func(request, &response);
+ continuation(error, &response);
+}
+
+} // anonymous namespace
+
+JsonRpcAsyncFunction adaptToAsync(JsonRpcFunction synchronousFunction)
+{
+ return boost::bind(runSynchronousFunction, synchronousFunction, _1, _2);
+}
+
+JsonRpcAsyncMethod adaptMethodToAsync(JsonRpcMethod synchronousMethod)
+{
+ return JsonRpcAsyncMethod(
+ synchronousMethod.first,
+ std::make_pair(true, adaptToAsync(synchronousMethod.second)));
+}
+
+} // namespace json
+} // namespace core
+
+
+
diff --git a/src/cpp/core/json/spirit/LICENSE.txt b/src/cpp/core/json/spirit/LICENSE.txt
new file mode 100644
index 0000000..797d536
--- /dev/null
+++ b/src/cpp/core/json/spirit/LICENSE.txt
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2007 - 2009 John W. Wilkinson
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/cpp/core/json/spirit/json_spirit.h b/src/cpp/core/json/spirit/json_spirit.h
new file mode 100644
index 0000000..ac1879d
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit.h
@@ -0,0 +1,18 @@
+#ifndef JSON_SPIRIT
+#define JSON_SPIRIT
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include "json_spirit_value.h"
+#include "json_spirit_reader.h"
+#include "json_spirit_writer.h"
+#include "json_spirit_utils.h"
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_error_position.h b/src/cpp/core/json/spirit/json_spirit_error_position.h
new file mode 100644
index 0000000..1720850
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_error_position.h
@@ -0,0 +1,54 @@
+#ifndef JSON_SPIRIT_ERROR_POSITION
+#define JSON_SPIRIT_ERROR_POSITION
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include <string>
+
+namespace json_spirit
+{
+ // An Error_position exception is thrown by the "read_or_throw" functions below on finding an error.
+ // Note the "read_or_throw" functions are around 3 times slower than the standard functions "read"
+ // functions that return a bool.
+ //
+ struct Error_position
+ {
+ Error_position();
+ Error_position( unsigned int line, unsigned int column, const std::string& reason );
+ bool operator==( const Error_position& lhs ) const;
+ unsigned int line_;
+ unsigned int column_;
+ std::string reason_;
+ };
+
+ inline Error_position::Error_position()
+ : line_( 0 )
+ , column_( 0 )
+ {
+ }
+
+ inline Error_position::Error_position( unsigned int line, unsigned int column, const std::string& reason )
+ : line_( line )
+ , column_( column )
+ , reason_( reason )
+ {
+ }
+
+ inline bool Error_position::operator==( const Error_position& lhs ) const
+ {
+ if( this == &lhs ) return true;
+
+ return ( reason_ == lhs.reason_ ) &&
+ ( line_ == lhs.line_ ) &&
+ ( column_ == lhs.column_ );
+}
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_reader.cpp b/src/cpp/core/json/spirit/json_spirit_reader.cpp
new file mode 100644
index 0000000..aa4f637
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_reader.cpp
@@ -0,0 +1,137 @@
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#include "json_spirit_reader.h"
+#include "json_spirit_reader_template.h"
+
+using namespace json_spirit;
+
+bool json_spirit::read( const std::string& s, Value& value )
+{
+ return read_string( s, value );
+}
+
+void json_spirit::read_or_throw( const std::string& s, Value& value )
+{
+ read_string_or_throw( s, value );
+}
+
+bool json_spirit::read( std::istream& is, Value& value )
+{
+ return read_stream( is, value );
+}
+
+void json_spirit::read_or_throw( std::istream& is, Value& value )
+{
+ read_stream_or_throw( is, value );
+}
+
+bool json_spirit::read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value )
+{
+ return read_range( begin, end, value );
+}
+
+void json_spirit::read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value )
+{
+ begin = read_range_or_throw( begin, end, value );
+}
+
+#ifndef BOOST_NO_STD_WSTRING
+
+bool json_spirit::read( const std::wstring& s, wValue& value )
+{
+ return read_string( s, value );
+}
+
+void json_spirit::read_or_throw( const std::wstring& s, wValue& value )
+{
+ read_string_or_throw( s, value );
+}
+
+bool json_spirit::read( std::wistream& is, wValue& value )
+{
+ return read_stream( is, value );
+}
+
+void json_spirit::read_or_throw( std::wistream& is, wValue& value )
+{
+ read_stream_or_throw( is, value );
+}
+
+bool json_spirit::read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value )
+{
+ return read_range( begin, end, value );
+}
+
+void json_spirit::read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value )
+{
+ begin = read_range_or_throw( begin, end, value );
+}
+
+#endif
+
+bool json_spirit::read( const std::string& s, mValue& value )
+{
+ return read_string( s, value );
+}
+
+void json_spirit::read_or_throw( const std::string& s, mValue& value )
+{
+ read_string_or_throw( s, value );
+}
+
+bool json_spirit::read( std::istream& is, mValue& value )
+{
+ return read_stream( is, value );
+}
+
+void json_spirit::read_or_throw( std::istream& is, mValue& value )
+{
+ read_stream_or_throw( is, value );
+}
+
+bool json_spirit::read( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value )
+{
+ return read_range( begin, end, value );
+}
+
+void json_spirit::read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value )
+{
+ begin = read_range_or_throw( begin, end, value );
+}
+
+#ifndef BOOST_NO_STD_WSTRING
+
+bool json_spirit::read( const std::wstring& s, wmValue& value )
+{
+ return read_string( s, value );
+}
+
+void json_spirit::read_or_throw( const std::wstring& s, wmValue& value )
+{
+ read_string_or_throw( s, value );
+}
+
+bool json_spirit::read( std::wistream& is, wmValue& value )
+{
+ return read_stream( is, value );
+}
+
+void json_spirit::read_or_throw( std::wistream& is, wmValue& value )
+{
+ read_stream_or_throw( is, value );
+}
+
+bool json_spirit::read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value )
+{
+ return read_range( begin, end, value );
+}
+
+void json_spirit::read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value )
+{
+ begin = read_range_or_throw( begin, end, value );
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_reader.h b/src/cpp/core/json/spirit/json_spirit_reader.h
new file mode 100644
index 0000000..96494a9
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_reader.h
@@ -0,0 +1,62 @@
+#ifndef JSON_SPIRIT_READER
+#define JSON_SPIRIT_READER
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include "json_spirit_value.h"
+#include "json_spirit_error_position.h"
+#include <iostream>
+
+namespace json_spirit
+{
+ // functions to reads a JSON values
+
+ bool read( const std::string& s, Value& value );
+ bool read( std::istream& is, Value& value );
+ bool read( std::string::const_iterator& begin, std::string::const_iterator end, Value& value );
+
+ void read_or_throw( const std::string& s, Value& value );
+ void read_or_throw( std::istream& is, Value& value );
+ void read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, Value& value );
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ bool read( const std::wstring& s, wValue& value );
+ bool read( std::wistream& is, wValue& value );
+ bool read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value );
+
+ void read_or_throw( const std::wstring& s, wValue& value );
+ void read_or_throw( std::wistream& is, wValue& value );
+ void read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wValue& value );
+
+#endif
+
+ bool read( const std::string& s, mValue& value );
+ bool read( std::istream& is, mValue& value );
+ bool read( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value );
+
+ void read_or_throw( const std::string& s, mValue& value );
+ void read_or_throw( std::istream& is, mValue& value );
+ void read_or_throw( std::string::const_iterator& begin, std::string::const_iterator end, mValue& value );
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ bool read( const std::wstring& s, wmValue& value );
+ bool read( std::wistream& is, wmValue& value );
+ bool read( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value );
+
+ void read_or_throw( const std::wstring& s, wmValue& value );
+ void read_or_throw( std::wistream& is, wmValue& value );
+ void read_or_throw( std::wstring::const_iterator& begin, std::wstring::const_iterator end, wmValue& value );
+
+#endif
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_reader_template.h b/src/cpp/core/json/spirit/json_spirit_reader_template.h
new file mode 100644
index 0000000..4dec00e
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_reader_template.h
@@ -0,0 +1,612 @@
+#ifndef JSON_SPIRIT_READER_TEMPLATE
+#define JSON_SPIRIT_READER_TEMPLATE
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#include "json_spirit_value.h"
+#include "json_spirit_error_position.h"
+
+//#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/version.hpp>
+
+#if BOOST_VERSION >= 103800
+ #include <boost/spirit/include/classic_core.hpp>
+ #include <boost/spirit/include/classic_confix.hpp>
+ #include <boost/spirit/include/classic_escape_char.hpp>
+ #include <boost/spirit/include/classic_multi_pass.hpp>
+ #include <boost/spirit/include/classic_position_iterator.hpp>
+ #define spirit_namespace boost::spirit::classic
+#else
+ #include <boost/spirit/core.hpp>
+ #include <boost/spirit/utility/confix.hpp>
+ #include <boost/spirit/utility/escape_char.hpp>
+ #include <boost/spirit/iterator/multi_pass.hpp>
+ #include <boost/spirit/iterator/position_iterator.hpp>
+ #define spirit_namespace boost::spirit
+#endif
+
+namespace json_spirit
+{
+ const spirit_namespace::int_parser < boost::int64_t > int64_p = spirit_namespace::int_parser < boost::int64_t >();
+ const spirit_namespace::uint_parser< boost::uint64_t > uint64_p = spirit_namespace::uint_parser< boost::uint64_t >();
+
+ template< class Iter_type >
+ bool is_eq( Iter_type first, Iter_type last, const char* c_str )
+ {
+ for( Iter_type i = first; i != last; ++i, ++c_str )
+ {
+ if( *c_str == 0 ) return false;
+
+ if( *i != *c_str ) return false;
+ }
+
+ return true;
+ }
+
+ template< class Char_type >
+ Char_type hex_to_num( const Char_type c )
+ {
+ if( ( c >= '0' ) && ( c <= '9' ) ) return c - '0';
+ if( ( c >= 'a' ) && ( c <= 'f' ) ) return c - 'a' + 10;
+ if( ( c >= 'A' ) && ( c <= 'F' ) ) return c - 'A' + 10;
+ return 0;
+ }
+
+ template< class Char_type, class Iter_type >
+ Char_type hex_str_to_char( Iter_type& begin )
+ {
+ const Char_type c1( *( ++begin ) );
+ const Char_type c2( *( ++begin ) );
+
+ return ( hex_to_num( c1 ) << 4 ) + hex_to_num( c2 );
+ }
+
+ template< class Char_type, class Iter_type >
+ Char_type unicode_str_to_char( Iter_type& begin )
+ {
+ const Char_type c1( *( ++begin ) );
+ const Char_type c2( *( ++begin ) );
+ const Char_type c3( *( ++begin ) );
+ const Char_type c4( *( ++begin ) );
+
+ return ( hex_to_num( c1 ) << 12 ) +
+ ( hex_to_num( c2 ) << 8 ) +
+ ( hex_to_num( c3 ) << 4 ) +
+ hex_to_num( c4 );
+ }
+
+ template< class String_type >
+ void append_esc_char_and_incr_iter( String_type& s,
+ typename String_type::const_iterator& begin,
+ typename String_type::const_iterator end )
+ {
+ typedef typename String_type::value_type Char_type;
+
+ const Char_type c2( *begin );
+
+ switch( c2 )
+ {
+ case 't': s += '\t'; break;
+ case 'b': s += '\b'; break;
+ case 'f': s += '\f'; break;
+ case 'n': s += '\n'; break;
+ case 'r': s += '\r'; break;
+ case '\\': s += '\\'; break;
+ case '/': s += '/'; break;
+ case '"': s += '"'; break;
+ case 'x':
+ {
+ if( end - begin >= 3 ) // expecting "xHH..."
+ {
+ s += hex_str_to_char< Char_type >( begin );
+ }
+ break;
+ }
+ case 'u':
+ {
+ if( end - begin >= 5 ) // expecting "uHHHH..."
+ {
+ s += unicode_str_to_char< Char_type >( begin );
+ }
+ break;
+ }
+ }
+ }
+
+ template< class String_type >
+ String_type substitute_esc_chars( typename String_type::const_iterator begin,
+ typename String_type::const_iterator end )
+ {
+ typedef typename String_type::const_iterator Iter_type;
+
+ if( end - begin < 2 ) return String_type( begin, end );
+
+ String_type result;
+
+ result.reserve( end - begin );
+
+ const Iter_type end_minus_1( end - 1 );
+
+ Iter_type substr_start = begin;
+ Iter_type i = begin;
+
+ for( ; i < end_minus_1; ++i )
+ {
+ if( *i == '\\' )
+ {
+ result.append( substr_start, i );
+
+ ++i; // skip the '\'
+
+ append_esc_char_and_incr_iter( result, i, end );
+
+ substr_start = i + 1;
+ }
+ }
+
+ result.append( substr_start, end );
+
+ return result;
+ }
+
+ template< class String_type >
+ String_type get_str_( typename String_type::const_iterator begin,
+ typename String_type::const_iterator end )
+ {
+ assert( end - begin >= 2 );
+
+ typedef typename String_type::const_iterator Iter_type;
+
+ Iter_type str_without_quotes( ++begin );
+ Iter_type end_without_quotes( --end );
+
+ return substitute_esc_chars< String_type >( str_without_quotes, end_without_quotes );
+ }
+
+ inline std::string get_str( std::string::const_iterator begin, std::string::const_iterator end )
+ {
+ return get_str_< std::string >( begin, end );
+ }
+
+ inline std::wstring get_str( std::wstring::const_iterator begin, std::wstring::const_iterator end )
+ {
+ return get_str_< std::wstring >( begin, end );
+ }
+
+ template< class String_type, class Iter_type >
+ String_type get_str( Iter_type begin, Iter_type end )
+ {
+ const String_type tmp( begin, end ); // convert multipass iterators to string iterators
+
+ return get_str( tmp.begin(), tmp.end() );
+ }
+
+ // this class's methods get called by the spirit parse resulting
+ // in the creation of a JSON object or array
+ //
+ // NB Iter_type could be a std::string iterator, wstring iterator, a position iterator or a multipass iterator
+ //
+ template< class Value_type, class Iter_type >
+ class Semantic_actions
+ {
+ public:
+
+ typedef typename Value_type::Config_type Config_type;
+ typedef typename Config_type::String_type String_type;
+ typedef typename Config_type::Object_type Object_type;
+ typedef typename Config_type::Array_type Array_type;
+ typedef typename String_type::value_type Char_type;
+
+ Semantic_actions( Value_type& value )
+ : value_( value )
+ , current_p_( 0 )
+ {
+ }
+
+ void begin_obj( Char_type c )
+ {
+ assert( c == '{' );
+
+ begin_compound< Object_type >();
+ }
+
+ void end_obj( Char_type c )
+ {
+ assert( c == '}' );
+
+ end_compound();
+ }
+
+ void begin_array( Char_type c )
+ {
+ assert( c == '[' );
+
+ begin_compound< Array_type >();
+ }
+
+ void end_array( Char_type c )
+ {
+ assert( c == ']' );
+
+ end_compound();
+ }
+
+ void new_name( Iter_type begin, Iter_type end )
+ {
+ assert( current_p_->type() == obj_type );
+
+ name_ = get_str< String_type >( begin, end );
+ }
+
+ void new_str( Iter_type begin, Iter_type end )
+ {
+ add_to_current( get_str< String_type >( begin, end ) );
+ }
+
+ void new_true( Iter_type begin, Iter_type end )
+ {
+ assert( is_eq( begin, end, "true" ) );
+
+ add_to_current( true );
+ }
+
+ void new_false( Iter_type begin, Iter_type end )
+ {
+ assert( is_eq( begin, end, "false" ) );
+
+ add_to_current( false );
+ }
+
+ void new_null( Iter_type begin, Iter_type end )
+ {
+ assert( is_eq( begin, end, "null" ) );
+
+ add_to_current( Value_type() );
+ }
+
+ void new_int( boost::int64_t i )
+ {
+ add_to_current( i );
+ }
+
+ void new_uint64( boost::uint64_t ui )
+ {
+ add_to_current( ui );
+ }
+
+ void new_real( double d )
+ {
+ add_to_current( d );
+ }
+
+ private:
+
+ Semantic_actions& operator=( const Semantic_actions& );
+ // to prevent "assignment operator could not be generated" warning
+
+ Value_type* add_first( const Value_type& value )
+ {
+ assert( current_p_ == 0 );
+
+ value_ = value;
+ current_p_ = &value_;
+ return current_p_;
+ }
+
+ template< class Array_or_obj >
+ void begin_compound()
+ {
+ if( current_p_ == 0 )
+ {
+ add_first( Array_or_obj() );
+ }
+ else
+ {
+ stack_.push_back( current_p_ );
+
+ Array_or_obj new_array_or_obj; // avoid copy by building new array or object in place
+
+ current_p_ = add_to_current( new_array_or_obj );
+ }
+ }
+
+ void end_compound()
+ {
+ if( current_p_ != &value_ )
+ {
+ current_p_ = stack_.back();
+
+ stack_.pop_back();
+ }
+ }
+
+ Value_type* add_to_current( const Value_type& value )
+ {
+ if( current_p_ == 0 )
+ {
+ return add_first( value );
+ }
+ else if( current_p_->type() == array_type )
+ {
+ current_p_->get_array().push_back( value );
+
+ return ¤t_p_->get_array().back();
+ }
+
+ assert( current_p_->type() == obj_type );
+
+ return &Config_type::add( current_p_->get_obj(), name_, value );
+ }
+
+ Value_type& value_; // this is the object or array that is being created
+ Value_type* current_p_; // the child object or array that is currently being constructed
+
+ std::vector< Value_type* > stack_; // previous child objects and arrays
+
+ String_type name_; // of current name/value pair
+ };
+
+ template< typename Iter_type >
+ void throw_error( spirit_namespace::position_iterator< Iter_type > i, const std::string& reason )
+ {
+ throw Error_position( i.get_position().line, i.get_position().column, reason );
+ }
+
+ template< typename Iter_type >
+ void throw_error( Iter_type i, const std::string& reason )
+ {
+ throw reason;
+ }
+
+ // the spirit grammer
+ //
+ template< class Value_type, class Iter_type >
+ class Json_grammer : public spirit_namespace::grammar< Json_grammer< Value_type, Iter_type > >
+ {
+ public:
+
+ typedef Semantic_actions< Value_type, Iter_type > Semantic_actions_t;
+
+ Json_grammer( Semantic_actions_t& semantic_actions )
+ : actions_( semantic_actions )
+ {
+ }
+
+ static void throw_not_value( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "not a value" );
+ }
+
+ static void throw_not_array( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "not an array" );
+ }
+
+ static void throw_not_object( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "not an object" );
+ }
+
+ static void throw_not_pair( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "not a pair" );
+ }
+
+ static void throw_not_colon( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "no colon in pair" );
+ }
+
+ static void throw_not_string( Iter_type begin, Iter_type end )
+ {
+ throw_error( begin, "not a string" );
+ }
+
+ template< typename ScannerT >
+ class definition
+ {
+ public:
+
+ definition( const Json_grammer& self )
+ {
+ using namespace spirit_namespace;
+
+ typedef typename Value_type::String_type::value_type Char_type;
+
+ // first we convert the semantic action class methods to functors with the
+ // parameter signature expected by spirit
+
+ typedef boost::function< void( Char_type ) > Char_action;
+ typedef boost::function< void( Iter_type, Iter_type ) > Str_action;
+ typedef boost::function< void( double ) > Real_action;
+ typedef boost::function< void( boost::int64_t ) > Int_action;
+ typedef boost::function< void( boost::uint64_t ) > Uint64_action;
+
+ Char_action begin_obj ( boost::bind( &Semantic_actions_t::begin_obj, &self.actions_, _1 ) );
+ Char_action end_obj ( boost::bind( &Semantic_actions_t::end_obj, &self.actions_, _1 ) );
+ Char_action begin_array( boost::bind( &Semantic_actions_t::begin_array, &self.actions_, _1 ) );
+ Char_action end_array ( boost::bind( &Semantic_actions_t::end_array, &self.actions_, _1 ) );
+ Str_action new_name ( boost::bind( &Semantic_actions_t::new_name, &self.actions_, _1, _2 ) );
+ Str_action new_str ( boost::bind( &Semantic_actions_t::new_str, &self.actions_, _1, _2 ) );
+ Str_action new_true ( boost::bind( &Semantic_actions_t::new_true, &self.actions_, _1, _2 ) );
+ Str_action new_false ( boost::bind( &Semantic_actions_t::new_false, &self.actions_, _1, _2 ) );
+ Str_action new_null ( boost::bind( &Semantic_actions_t::new_null, &self.actions_, _1, _2 ) );
+ Real_action new_real ( boost::bind( &Semantic_actions_t::new_real, &self.actions_, _1 ) );
+ Int_action new_int ( boost::bind( &Semantic_actions_t::new_int, &self.actions_, _1 ) );
+ Uint64_action new_uint64 ( boost::bind( &Semantic_actions_t::new_uint64, &self.actions_, _1 ) );
+
+ // actual grammer
+
+ json_
+ = value_ | eps_p[ &throw_not_value ]
+ ;
+
+ value_
+ = string_[ new_str ]
+ | number_
+ | object_
+ | array_
+ | str_p( "true" ) [ new_true ]
+ | str_p( "false" )[ new_false ]
+ | str_p( "null" ) [ new_null ]
+ ;
+
+ object_
+ = ch_p('{')[ begin_obj ]
+ >> !members_
+ >> ( ch_p('}')[ end_obj ] | eps_p[ &throw_not_object ] )
+ ;
+
+ members_
+ = pair_ >> *( ',' >> pair_ )
+ ;
+
+ pair_
+ = string_[ new_name ]
+ >> ( ':' | eps_p[ &throw_not_colon ] )
+ >> ( value_ | eps_p[ &throw_not_value ] )
+ ;
+
+ array_
+ = ch_p('[')[ begin_array ]
+ >> !elements_
+ >> ( ch_p(']')[ end_array ] | eps_p[ &throw_not_array ] )
+ ;
+
+ elements_
+ = value_ >> *( ',' >> value_ )
+ ;
+
+ string_
+ = lexeme_d // this causes white space inside a string to be retained
+ [
+ confix_p
+ (
+ '"',
+ *lex_escape_ch_p,
+ '"'
+ )
+ ]
+ ;
+
+ number_
+ = strict_real_p[ new_real ]
+ | int64_p [ new_int ]
+ | uint64_p [ new_uint64 ]
+ ;
+ }
+
+ spirit_namespace::rule< ScannerT > json_, object_, members_, pair_, array_, elements_, value_, string_, number_;
+
+ const spirit_namespace::rule< ScannerT >& start() const { return json_; }
+ };
+
+ private:
+
+ Json_grammer& operator=( const Json_grammer& ); // to prevent "assignment operator could not be generated" warning
+
+ Semantic_actions_t& actions_;
+ };
+
+ template< class Iter_type, class Value_type >
+ Iter_type read_range_or_throw( Iter_type begin, Iter_type end, Value_type& value )
+ {
+ Semantic_actions< Value_type, Iter_type > semantic_actions( value );
+
+ const spirit_namespace::parse_info< Iter_type > info =
+ spirit_namespace::parse( begin, end,
+ Json_grammer< Value_type, Iter_type >( semantic_actions ),
+ spirit_namespace::space_p );
+
+ if( !info.hit )
+ {
+ assert( false ); // in theory exception should already have been thrown
+ throw_error( info.stop, "error" );
+ }
+
+ return info.stop;
+ }
+
+ template< class Iter_type, class Value_type >
+ void add_posn_iter_and_read_range_or_throw( Iter_type begin, Iter_type end, Value_type& value )
+ {
+ typedef spirit_namespace::position_iterator< Iter_type > Posn_iter_t;
+
+ const Posn_iter_t posn_begin( begin, end );
+ const Posn_iter_t posn_end( end, end );
+
+ read_range_or_throw( posn_begin, posn_end, value );
+ }
+
+ template< class Iter_type, class Value_type >
+ bool read_range( Iter_type& begin, Iter_type end, Value_type& value )
+ {
+ try
+ {
+ begin = read_range_or_throw( begin, end, value );
+
+ return true;
+ }
+ catch( ... )
+ {
+ return false;
+ }
+ }
+
+ template< class String_type, class Value_type >
+ void read_string_or_throw( const String_type& s, Value_type& value )
+ {
+ add_posn_iter_and_read_range_or_throw( s.begin(), s.end(), value );
+ }
+
+ template< class String_type, class Value_type >
+ bool read_string( const String_type& s, Value_type& value )
+ {
+ typename String_type::const_iterator begin = s.begin();
+
+ return read_range( begin, s.end(), value );
+ }
+
+ template< class Istream_type >
+ struct Multi_pass_iters
+ {
+ typedef typename Istream_type::char_type Char_type;
+ typedef std::istream_iterator< Char_type, Char_type > istream_iter;
+ typedef spirit_namespace::multi_pass< istream_iter > Mp_iter;
+
+ Multi_pass_iters( Istream_type& is )
+ {
+ is.unsetf( std::ios::skipws );
+
+ begin_ = spirit_namespace::make_multi_pass( istream_iter( is ) );
+ end_ = spirit_namespace::make_multi_pass( istream_iter() );
+ }
+
+ Mp_iter begin_;
+ Mp_iter end_;
+ };
+
+ template< class Istream_type, class Value_type >
+ bool read_stream( Istream_type& is, Value_type& value )
+ {
+ Multi_pass_iters< Istream_type > mp_iters( is );
+
+ return read_range( mp_iters.begin_, mp_iters.end_, value );
+ }
+
+ template< class Istream_type, class Value_type >
+ void read_stream_or_throw( Istream_type& is, Value_type& value )
+ {
+ const Multi_pass_iters< Istream_type > mp_iters( is );
+
+ add_posn_iter_and_read_range_or_throw( mp_iters.begin_, mp_iters.end_, value );
+ }
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_stream_reader.h b/src/cpp/core/json/spirit/json_spirit_stream_reader.h
new file mode 100644
index 0000000..7e59c9a
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_stream_reader.h
@@ -0,0 +1,70 @@
+#ifndef JSON_SPIRIT_READ_STREAM
+#define JSON_SPIRIT_READ_STREAM
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include "json_spirit_reader_template.h"
+
+namespace json_spirit
+{
+ // these classes allows you to read multiple top level contiguous values from a stream,
+ // the normal stream read functions have a bug that prevent multiple top level values
+ // from being read unless they are separated by spaces
+
+ template< class Istream_type, class Value_type >
+ class Stream_reader
+ {
+ public:
+
+ Stream_reader( Istream_type& is )
+ : iters_( is )
+ {
+ }
+
+ bool read_next( Value_type& value )
+ {
+ return read_range( iters_.begin_, iters_.end_, value );
+ }
+
+ private:
+
+ typedef Multi_pass_iters< Istream_type > Mp_iters;
+
+ Mp_iters iters_;
+ };
+
+ template< class Istream_type, class Value_type >
+ class Stream_reader_thrower
+ {
+ public:
+
+ Stream_reader_thrower( Istream_type& is )
+ : iters_( is )
+ , posn_begin_( iters_.begin_, iters_.end_ )
+ , posn_end_( iters_.end_, iters_.end_ )
+ {
+ }
+
+ void read_next( Value_type& value )
+ {
+ posn_begin_ = read_range_or_throw( posn_begin_, posn_end_, value );
+ }
+
+ private:
+
+ typedef Multi_pass_iters< Istream_type > Mp_iters;
+ typedef spirit_namespace::position_iterator< typename Mp_iters::Mp_iter > Posn_iter_t;
+
+ Mp_iters iters_;
+ Posn_iter_t posn_begin_, posn_end_;
+ };
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_strings.h b/src/cpp/core/json/spirit/json_spirit_strings.h
new file mode 100644
index 0000000..77ee51d
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_strings.h
@@ -0,0 +1,20 @@
+#ifndef JASON_SPIRIT_ESCAPE_STRING_HPP
+#define JASON_SPIRIT_ESCAPE_STRING_HPP
+
+// NOTE: json_spirit was not properly escaping mbcs strings coming from
+// R. As a result we defined this hook which allows us to provide the
+// string escaping externally (see Json.cpp)
+
+namespace json_spirit {
+
+std::string write_escaped_string(const std::string& str);
+
+#ifndef BOOST_NO_STD_WSTRING
+
+std::wstring write_escaped_string(const std::wstring& str);
+
+#endif
+
+}
+
+#endif // JASON_SPIRIT_ESCAPE_STRING_HPP
diff --git a/src/cpp/core/json/spirit/json_spirit_utils.h b/src/cpp/core/json/spirit/json_spirit_utils.h
new file mode 100644
index 0000000..553e3b9
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_utils.h
@@ -0,0 +1,61 @@
+#ifndef JSON_SPIRIT_UTILS
+#define JSON_SPIRIT_UTILS
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include "json_spirit_value.h"
+#include <map>
+
+namespace json_spirit
+{
+ template< class Obj_t, class Map_t >
+ void obj_to_map( const Obj_t& obj, Map_t& mp_obj )
+ {
+ mp_obj.clear();
+
+ for( typename Obj_t::const_iterator i = obj.begin(); i != obj.end(); ++i )
+ {
+ mp_obj[ i->name_ ] = i->value_;
+ }
+ }
+
+ template< class Obj_t, class Map_t >
+ void map_to_obj( const Map_t& mp_obj, Obj_t& obj )
+ {
+ obj.clear();
+
+ for( typename Map_t::const_iterator i = mp_obj.begin(); i != mp_obj.end(); ++i )
+ {
+ obj.push_back( typename Obj_t::value_type( i->first, i->second ) );
+ }
+ }
+
+ typedef std::map< std::string, Value > Mapped_obj;
+
+#ifndef BOOST_NO_STD_WSTRING
+ typedef std::map< std::wstring, wValue > wMapped_obj;
+#endif
+
+ template< class Object_type, class String_type >
+ const typename Object_type::value_type::Value_type& find_value( const Object_type& obj, const String_type& name )
+ {
+ for( typename Object_type::const_iterator i = obj.begin(); i != obj.end(); ++i )
+ {
+ if( i->name_ == name )
+ {
+ return i->value_;
+ }
+ }
+
+ return Object_type::value_type::Value_type::null;
+ }
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_value.cpp b/src/cpp/core/json/spirit/json_spirit_value.cpp
new file mode 100644
index 0000000..44d2f06
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_value.cpp
@@ -0,0 +1,8 @@
+/* Copyright (c) 2007 John W Wilkinson
+
+ This source code can be used for any purpose as long as
+ this comment is retained. */
+
+// json spirit version 2.00
+
+#include "json_spirit_value.h"
diff --git a/src/cpp/core/json/spirit/json_spirit_value.h b/src/cpp/core/json/spirit/json_spirit_value.h
new file mode 100644
index 0000000..5b348bc
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_value.h
@@ -0,0 +1,15 @@
+#ifndef JSON_SPIRIT_VALUE_INC
+#define JSON_SPIRIT_VALUE_INC
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include <core/json/spirit/json_spirit_value.h>
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_writer.cpp b/src/cpp/core/json/spirit/json_spirit_writer.cpp
new file mode 100644
index 0000000..d24a632
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_writer.cpp
@@ -0,0 +1,95 @@
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#include "json_spirit_writer.h"
+#include "json_spirit_writer_template.h"
+
+void json_spirit::write( const Value& value, std::ostream& os )
+{
+ write_stream( value, os, false );
+}
+
+void json_spirit::write_formatted( const Value& value, std::ostream& os )
+{
+ write_stream( value, os, true );
+}
+
+std::string json_spirit::write( const Value& value )
+{
+ return write_string( value, false );
+}
+
+std::string json_spirit::write_formatted( const Value& value )
+{
+ return write_string( value, true );
+}
+
+#ifndef BOOST_NO_STD_WSTRING
+
+void json_spirit::write( const wValue& value, std::wostream& os )
+{
+ write_stream( value, os, false );
+}
+
+void json_spirit::write_formatted( const wValue& value, std::wostream& os )
+{
+ write_stream( value, os, true );
+}
+
+std::wstring json_spirit::write( const wValue& value )
+{
+ return write_string( value, false );
+}
+
+std::wstring json_spirit::write_formatted( const wValue& value )
+{
+ return write_string( value, true );
+}
+
+#endif
+
+void json_spirit::write( const mValue& value, std::ostream& os )
+{
+ write_stream( value, os, false );
+}
+
+void json_spirit::write_formatted( const mValue& value, std::ostream& os )
+{
+ write_stream( value, os, true );
+}
+
+std::string json_spirit::write( const mValue& value )
+{
+ return write_string( value, false );
+}
+
+std::string json_spirit::write_formatted( const mValue& value )
+{
+ return write_string( value, true );
+}
+
+#ifndef BOOST_NO_STD_WSTRING
+
+void json_spirit::write( const wmValue& value, std::wostream& os )
+{
+ write_stream( value, os, false );
+}
+
+void json_spirit::write_formatted( const wmValue& value, std::wostream& os )
+{
+ write_stream( value, os, true );
+}
+
+std::wstring json_spirit::write( const wmValue& value )
+{
+ return write_string( value, false );
+}
+
+std::wstring json_spirit::write_formatted( const wmValue& value )
+{
+ return write_string( value, true );
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_writer.h b/src/cpp/core/json/spirit/json_spirit_writer.h
new file mode 100644
index 0000000..52e1406
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_writer.h
@@ -0,0 +1,50 @@
+#ifndef JSON_SPIRIT_WRITER
+#define JSON_SPIRIT_WRITER
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+#include "json_spirit_value.h"
+#include <iostream>
+
+namespace json_spirit
+{
+ // functions to convert JSON Values to text,
+ // the "formatted" versions add whitespace to format the output nicely
+
+ void write ( const Value& value, std::ostream& os );
+ void write_formatted( const Value& value, std::ostream& os );
+ std::string write ( const Value& value );
+ std::string write_formatted( const Value& value );
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ void write ( const wValue& value, std::wostream& os );
+ void write_formatted( const wValue& value, std::wostream& os );
+ std::wstring write ( const wValue& value );
+ std::wstring write_formatted( const wValue& value );
+
+#endif
+
+ void write ( const mValue& value, std::ostream& os );
+ void write_formatted( const mValue& value, std::ostream& os );
+ std::string write ( const mValue& value );
+ std::string write_formatted( const mValue& value );
+
+#ifndef BOOST_NO_STD_WSTRING
+
+ void write ( const wmValue& value, std::wostream& os );
+ void write_formatted( const wmValue& value, std::wostream& os );
+ std::wstring write ( const wmValue& value );
+ std::wstring write_formatted( const wmValue& value );
+
+#endif
+}
+
+#endif
diff --git a/src/cpp/core/json/spirit/json_spirit_writer_template.h b/src/cpp/core/json/spirit/json_spirit_writer_template.h
new file mode 100644
index 0000000..2b5d738
--- /dev/null
+++ b/src/cpp/core/json/spirit/json_spirit_writer_template.h
@@ -0,0 +1,255 @@
+#ifndef JSON_SPIRIT_WRITER_TEMPLATE
+#define JSON_SPIRIT_WRITER_TEMPLATE
+
+// Copyright John W. Wilkinson 2007 - 2009.
+// Distributed under the MIT License, see accompanying file LICENSE.txt
+
+// json spirit version 4.03
+
+// NOTE: applied patch recommend by the author for correct UTF-8 handling:
+// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx?msg=3363806#xx3363806xx
+#define HANDLE_UTF8
+
+#include "json_spirit_value.h"
+
+#include <cassert>
+#include <sstream>
+#include <iomanip>
+
+namespace json_spirit
+{
+ inline char to_hex_char( unsigned int c )
+ {
+ assert( c <= 0xF );
+
+ const char ch = static_cast< char >( c );
+
+ if( ch < 10 ) return '0' + ch;
+
+ return 'A' - 10 + ch;
+ }
+
+ template< class String_type >
+ String_type non_printable_to_string( unsigned int c )
+ {
+ typedef typename String_type::value_type Char_type;
+
+ String_type result( 6, '\\' );
+
+ result[1] = 'u';
+
+ result[ 5 ] = to_hex_char( c & 0x000F ); c >>= 4;
+ result[ 4 ] = to_hex_char( c & 0x000F ); c >>= 4;
+ result[ 3 ] = to_hex_char( c & 0x000F ); c >>= 4;
+ result[ 2 ] = to_hex_char( c & 0x000F );
+
+ return result;
+ }
+
+ template< typename Char_type, class String_type >
+ bool add_esc_char( Char_type c, String_type& s )
+ {
+ switch( c )
+ {
+ case '"': s += to_str< String_type >( "\\\"" ); return true;
+ case '\\': s += to_str< String_type >( "\\\\" ); return true;
+ case '\b': s += to_str< String_type >( "\\b" ); return true;
+ case '\f': s += to_str< String_type >( "\\f" ); return true;
+ case '\n': s += to_str< String_type >( "\\n" ); return true;
+ case '\r': s += to_str< String_type >( "\\r" ); return true;
+ case '\t': s += to_str< String_type >( "\\t" ); return true;
+ }
+
+ return false;
+ }
+
+ template< class String_type >
+ String_type add_esc_chars( const String_type& s )
+ {
+ typedef typename String_type::const_iterator Iter_type;
+ typedef typename String_type::value_type Char_type;
+
+ String_type result;
+
+ const Iter_type end( s.end() );
+
+ for( Iter_type i = s.begin(); i != end; ++i )
+ {
+ const Char_type c( *i );
+
+ if( add_esc_char( c, result ) ) continue;
+
+#ifdef HANDLE_UTF8
+
+ result += c;
+
+#else
+ const wint_t unsigned_c( ( c >= 0 ) ? c : 256 + c );
+
+ if( iswprint( unsigned_c ) )
+ {
+ result += c;
+ }
+ else
+ {
+ result += non_printable_to_string< String_type >( unsigned_c );
+ }
+#endif
+ }
+
+ return result;
+ }
+
+ // this class generates the JSON text,
+ // it keeps track of the indentation level etc.
+ //
+ template< class Value_type, class Ostream_type >
+ class Generator
+ {
+ typedef typename Value_type::Config_type Config_type;
+ typedef typename Config_type::String_type String_type;
+ typedef typename Config_type::Object_type Object_type;
+ typedef typename Config_type::Array_type Array_type;
+ typedef typename String_type::value_type Char_type;
+ typedef typename Object_type::value_type Obj_member_type;
+
+ public:
+
+ Generator( const Value_type& value, Ostream_type& os, bool pretty )
+ : os_( os )
+ , indentation_level_( 0 )
+ , pretty_( pretty )
+ {
+ output( value );
+ }
+
+ private:
+
+ void output( const Value_type& value )
+ {
+ switch( value.type() )
+ {
+ case obj_type: output( value.get_obj() ); break;
+ case array_type: output( value.get_array() ); break;
+ case str_type: output( value.get_str() ); break;
+ case bool_type: output( value.get_bool() ); break;
+ case int_type: output_int( value ); break;
+ case real_type: os_ << std::showpoint << std::setprecision( 16 )
+ << value.get_real(); break;
+ case null_type: os_ << "null"; break;
+ default: assert( false );
+ }
+ }
+
+ void output( const Object_type& obj )
+ {
+ output_array_or_obj( obj, '{', '}' );
+ }
+
+ void output( const Array_type& arr )
+ {
+ output_array_or_obj( arr, '[', ']' );
+ }
+
+ void output( const Obj_member_type& member )
+ {
+ output( Config_type::get_name( member ) ); space();
+ os_ << ':'; space();
+ output( Config_type::get_value( member ) );
+ }
+
+ void output_int( const Value_type& value )
+ {
+ if( value.is_uint64() )
+ {
+ os_ << value.get_uint64();
+ }
+ else
+ {
+ os_ << value.get_int64();
+ }
+ }
+
+ void output( const String_type& s )
+ {
+ os_ << '"' << add_esc_chars( s ) << '"';
+ }
+
+ void output( bool b )
+ {
+ os_ << to_str< String_type >( b ? "true" : "false" );
+ }
+
+ template< class T >
+ void output_array_or_obj( const T& t, Char_type start_char, Char_type end_char )
+ {
+ os_ << start_char; new_line();
+
+ ++indentation_level_;
+
+ for( typename T::const_iterator i = t.begin(); i != t.end(); ++i )
+ {
+ indent(); output( *i );
+
+ typename T::const_iterator next = i;
+
+ if( ++next != t.end())
+ {
+ os_ << ',';
+ }
+
+ new_line();
+ }
+
+ --indentation_level_;
+
+ indent(); os_ << end_char;
+ }
+
+ void indent()
+ {
+ if( !pretty_ ) return;
+
+ for( int i = 0; i < indentation_level_; ++i )
+ {
+ os_ << " ";
+ }
+ }
+
+ void space()
+ {
+ if( pretty_ ) os_ << ' ';
+ }
+
+ void new_line()
+ {
+ if( pretty_ ) os_ << '\n';
+ }
+
+ Generator& operator=( const Generator& ); // to prevent "assignment operator could not be generated" warning
+
+ Ostream_type& os_;
+ int indentation_level_;
+ bool pretty_;
+ };
+
+ template< class Value_type, class Ostream_type >
+ void write_stream( const Value_type& value, Ostream_type& os, bool pretty )
+ {
+ Generator< Value_type, Ostream_type >( value, os, pretty );
+ }
+
+ template< class Value_type >
+ typename Value_type::String_type write_string( const Value_type& value, bool pretty )
+ {
+ typedef typename Value_type::String_type::value_type Char_type;
+
+ std::basic_ostringstream< Char_type > os;
+
+ write_stream( value, os, pretty );
+
+ return os.str();
+ }
+}
+
+#endif
diff --git a/src/cpp/core/markdown/Markdown.cpp b/src/cpp/core/markdown/Markdown.cpp
new file mode 100644
index 0000000..6a1c9c2
--- /dev/null
+++ b/src/cpp/core/markdown/Markdown.cpp
@@ -0,0 +1,461 @@
+/*
+ * Markdown.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/markdown/Markdown.hpp>
+
+#include <iostream>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+#include <core/FileSerializer.hpp>
+
+#include "MathJax.hpp"
+
+#include "sundown/markdown.h"
+#include "sundown/html.h"
+
+namespace core {
+namespace markdown {
+
+namespace {
+
+class SundownBuffer : boost::noncopyable
+{
+public:
+ explicit SundownBuffer(std::size_t unit = 128)
+ : pBuff_(NULL)
+ {
+ pBuff_ = ::bufnew(unit);
+ }
+
+ explicit SundownBuffer(const std::string& str)
+ {
+ pBuff_ = ::bufnew(str.length());
+ if (pBuff_ != NULL)
+ {
+ if (grow(str.length()) == BUF_OK)
+ {
+ put(str);
+ }
+ else
+ {
+ ::bufrelease(pBuff_);
+ pBuff_ = NULL;
+ }
+ }
+ }
+
+ ~SundownBuffer()
+ {
+ if (pBuff_)
+ ::bufrelease(pBuff_);
+ }
+
+ // COPYING: prohibited (boost::noncopyable)
+
+ bool allocated() const { return pBuff_ != NULL; }
+
+ int grow(std::size_t size)
+ {
+ return ::bufgrow(pBuff_, size);
+ }
+
+ void put(const std::string& str)
+ {
+ ::bufput(pBuff_, str.data(), str.length());
+ }
+
+ uint8_t* data() const
+ {
+ return pBuff_->data;
+ }
+
+ std::size_t size() const
+ {
+ return pBuff_->size;
+ }
+
+ const char* c_str() const
+ {
+ return ::bufcstr(pBuff_);
+ }
+
+ operator buf*() const
+ {
+ return pBuff_;
+ }
+
+private:
+ friend class SundownMarkdown;
+ buf* pBuff_;
+};
+
+class SundownMarkdown : boost::noncopyable
+{
+public:
+ SundownMarkdown(unsigned int extensions,
+ size_t maxNesting,
+ const struct sd_callbacks* pCallbacks,
+ void *pOpaque)
+ : pMD_(NULL)
+ {
+ pMD_ = ::sd_markdown_new(extensions, maxNesting, pCallbacks, pOpaque);
+ }
+
+ ~SundownMarkdown()
+ {
+ if (pMD_)
+ ::sd_markdown_free(pMD_);
+ }
+
+ // COPYING: prohibited (boost::noncopyable)
+
+ bool allocated() const { return pMD_ != NULL; }
+
+ void render(const SundownBuffer& input, SundownBuffer* pOutput)
+ {
+ ::sd_markdown_render(pOutput->pBuff_,
+ input.pBuff_->data,
+ input.pBuff_->size,
+ pMD_);
+ }
+
+private:
+ struct sd_markdown* pMD_;
+};
+
+Error allocationError(const ErrorLocation& location)
+{
+ return systemError(boost::system::errc::not_enough_memory, location);
+}
+
+Error renderMarkdown(const SundownBuffer& inputBuffer,
+ const Extensions& extensions,
+ bool smartypants,
+ struct sd_callbacks* pHtmlCallbacks,
+ struct html_renderopt* pHtmlOptions,
+ std::string* pOutput)
+{
+ // render markdown
+ const int kMaxNesting = 16;
+ int mdExt = 0;
+ if (extensions.noIntraEmphasis)
+ mdExt |= MKDEXT_NO_INTRA_EMPHASIS;
+ if (extensions.tables)
+ mdExt |= MKDEXT_TABLES;
+ if (extensions.fencedCode)
+ mdExt |= MKDEXT_FENCED_CODE;
+ if (extensions.autolink)
+ mdExt |= MKDEXT_AUTOLINK;
+ if (extensions.strikethrough)
+ mdExt |= MKDEXT_STRIKETHROUGH;
+ if (extensions.laxSpacing)
+ mdExt |= MKDEXT_LAX_SPACING;
+ if (extensions.spaceHeaders)
+ mdExt |= MKDEXT_SPACE_HEADERS;
+ if (extensions.superscript)
+ mdExt |= MKDEXT_SUPERSCRIPT;
+
+ SundownMarkdown md(mdExt, kMaxNesting, pHtmlCallbacks, pHtmlOptions);
+ if (!md.allocated())
+ return allocationError(ERROR_LOCATION);
+ SundownBuffer outputBuffer;
+ md.render(inputBuffer, &outputBuffer);
+
+ // do smartypants substitution if requested
+ if (smartypants)
+ {
+ SundownBuffer smartyBuffer;
+ if (!smartyBuffer.allocated())
+ return allocationError(ERROR_LOCATION);
+
+ ::sdhtml_smartypants(smartyBuffer,
+ outputBuffer.data(),
+ outputBuffer.size());
+
+ *pOutput = smartyBuffer.c_str();
+ }
+ else
+ {
+ *pOutput = outputBuffer.c_str();
+ }
+
+ return Success();
+}
+
+
+void stripMetadata(std::string* pInput)
+{
+ // split into lines
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines, *pInput, boost::algorithm::is_any_of("\n"));
+
+ // front matter delimiter regex
+ boost::regex frontMatterDelimiterRegex("^\\-\\-\\-\\s*$");
+
+ // check the first non-empy line for metadata
+ bool hasFrontMatter = false, hasPandocTitleBlock = false;
+ BOOST_FOREACH(const std::string& line, lines)
+ {
+ if (boost::algorithm::trim_copy(line).empty())
+ {
+ continue;
+ }
+ else if (boost::regex_search(line, frontMatterDelimiterRegex))
+ {
+ hasFrontMatter = true;
+ break;
+ }
+ else if (boost::algorithm::starts_with(line, "%"))
+ {
+ hasPandocTitleBlock = true;
+ break;
+ }
+ }
+
+ std::size_t firstDocumentLine = 0;
+ if (hasFrontMatter)
+ {
+ bool inFrontMatter = false;
+ boost::regex frontMatterFieldRegex("^[^:]+:.*$");
+ boost::regex frontMatterContinuationRegex("^\\s+[^\\s].*$");
+
+ for(std::size_t i=0; i<lines.size(); i++)
+ {
+ const std::string& line = lines[i];
+ if (boost::algorithm::trim_copy(line).empty() && !inFrontMatter)
+ {
+ continue;
+ }
+ else if (boost::regex_search(line, frontMatterDelimiterRegex))
+ {
+ if (!inFrontMatter)
+ {
+ inFrontMatter = true;
+ }
+ else if (inFrontMatter)
+ {
+ firstDocumentLine = i+1;
+ break;
+ }
+ }
+ else if (!boost::regex_search(line, frontMatterFieldRegex) &&
+ !boost::regex_search(line,frontMatterContinuationRegex))
+ {
+ break;
+ }
+ }
+ }
+ else if (hasPandocTitleBlock)
+ {
+ bool inTitleBlock = false;
+
+ boost::regex titleBlockPercentRegex("^%.*$");
+ boost::regex titleBlockContinuationRegex("^ .+$");
+
+ for(std::size_t i=0; i<lines.size(); i++)
+ {
+ const std::string& line = lines[i];
+ if (boost::algorithm::trim_copy(line).empty() && !inTitleBlock)
+ {
+ continue;
+ }
+ else if (boost::regex_search(line, titleBlockPercentRegex))
+ {
+ inTitleBlock = true;
+ }
+ else if (boost::regex_search(line, titleBlockContinuationRegex) &&
+ inTitleBlock)
+ {
+ continue;
+ }
+ else
+ {
+ firstDocumentLine = i;
+ break;
+ }
+ }
+ }
+
+ // if we detected a metadata block then trim the document as necessary
+ if (firstDocumentLine > 0 && firstDocumentLine < lines.size())
+ {
+ lines.erase(lines.begin(), lines.begin() + firstDocumentLine);
+ *pInput = boost::algorithm::join(lines, "\n");
+ }
+}
+
+} // anonymous namespace
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const FilePath& markdownFile,
+ const Extensions& extensions,
+ const HTMLOptions& options,
+ const FilePath& htmlFile)
+{
+ std::string markdownOutput;
+ Error error = markdownToHTML(markdownFile,
+ extensions,
+ options,
+ &markdownOutput);
+ if (error)
+ return error;
+
+ return core::writeStringToFile(htmlFile,
+ markdownOutput,
+ string_utils::LineEndingNative);
+}
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const FilePath& markdownFile,
+ const Extensions& extensions,
+ const HTMLOptions& options,
+ std::string* pHTMLOutput)
+{
+ std::string markdownInput;
+ Error error = core::readStringFromFile(markdownFile,
+ &markdownInput,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+ return markdownToHTML(markdownInput, extensions, options, pHTMLOutput);
+}
+
+// render markdown to HTML -- assumes UTF-8 encoding
+Error markdownToHTML(const std::string& markdownInput,
+ const Extensions& extensions,
+ const HTMLOptions& options,
+ std::string* pHTMLOutput)
+
+{
+ // exclude fenced code blocks
+ std::vector<ExcludePattern> excludePatterns;
+ excludePatterns.push_back(ExcludePattern(boost::regex("^`{3,}[^\\n]*?$"),
+ boost::regex("^`{3,}\\s*$")));
+
+ // exclude inline verbatim code
+ excludePatterns.push_back(ExcludePattern(boost::regex("`[^\\n]+?`")));
+
+ // exclude indented code blocks
+ excludePatterns.push_back(ExcludePattern(
+ boost::regex("(\\A|\\A\\s*\\n|\\n\\s*\\n)(( {4}|\\t)[^\\n]*\\n)*(( {4}|\\t)[^\\n]*)")));
+
+ std::string input = markdownInput;
+ boost::scoped_ptr<MathJaxFilter> pMathFilter;
+ if (extensions.ignoreMath)
+ {
+ pMathFilter.reset(new MathJaxFilter(excludePatterns,
+ &input,
+ pHTMLOutput));
+ }
+
+ // strip yaml front-matter / pandoc metadata if requested
+ if (extensions.stripMetadata)
+ stripMetadata(&input);
+
+ // special case of empty input after stripping metadata
+ if (input.empty())
+ {
+ *pHTMLOutput = input;
+ return Success();
+ }
+
+ // setup input buffer
+ SundownBuffer inputBuffer(input);
+ if (!inputBuffer.allocated())
+ return allocationError(ERROR_LOCATION);
+
+ // render table of contents if requested
+ if (options.toc)
+ {
+ struct sd_callbacks htmlCallbacks;
+ struct html_renderopt htmlOptions;
+ ::sdhtml_toc_renderer(&htmlCallbacks, &htmlOptions);
+ std::string tocOutput;
+ Error error = renderMarkdown(inputBuffer,
+ extensions,
+ options.smartypants,
+ &htmlCallbacks,
+ &htmlOptions,
+ &tocOutput);
+ if (error)
+ return error;
+ pHTMLOutput->append("<div id=\"toc\">\n");
+ pHTMLOutput->append("<div id=\"toc_header\">Table of Contents</div>\n");
+ pHTMLOutput->append(tocOutput);
+ pHTMLOutput->append("</div>\n");
+ pHTMLOutput->append("\n");
+ }
+
+ // setup html renderer
+ struct sd_callbacks htmlCallbacks;
+ struct html_renderopt htmlOptions;
+ int htmlRenderMode = 0;
+ if (options.useXHTML)
+ htmlRenderMode |= HTML_USE_XHTML;
+ if (options.hardWrap)
+ htmlRenderMode |= HTML_HARD_WRAP;
+ if (options.toc)
+ htmlRenderMode |= HTML_TOC;
+ if (options.safelink)
+ htmlRenderMode |= HTML_SAFELINK;
+ if (options.skipHTML)
+ htmlRenderMode |= HTML_SKIP_HTML;
+ if (options.skipStyle)
+ htmlRenderMode |= HTML_SKIP_STYLE;
+ if (options.skipImages)
+ htmlRenderMode |= HTML_SKIP_IMAGES;
+ if (options.skipLinks)
+ htmlRenderMode |= HTML_SKIP_LINKS;
+ if (options.escape)
+ htmlRenderMode |= HTML_ESCAPE;
+ ::sdhtml_renderer(&htmlCallbacks, &htmlOptions, htmlRenderMode);
+
+ // render page
+ std::string output;
+ Error error = renderMarkdown(inputBuffer,
+ extensions,
+ options.smartypants,
+ &htmlCallbacks,
+ &htmlOptions,
+ &output);
+ if (error)
+ return error;
+
+ // append output and return success
+ pHTMLOutput->append(output);
+ return Success();
+}
+
+bool isMathJaxRequired(const std::string& htmlOutput)
+{
+ return requiresMathjax(htmlOutput);
+}
+
+} // namespace markdown
+} // namespace core
+
+
+
+
diff --git a/src/cpp/core/markdown/MathJax.cpp b/src/cpp/core/markdown/MathJax.cpp
new file mode 100644
index 0000000..9d4478e
--- /dev/null
+++ b/src/cpp/core/markdown/MathJax.cpp
@@ -0,0 +1,272 @@
+/*
+ * MathJax.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "MathJax.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/system/System.hpp>
+
+namespace core {
+namespace markdown {
+
+namespace {
+
+struct TextRange
+{
+ TextRange(bool process,
+ const std::string::const_iterator& begin,
+ const std::string::const_iterator& end)
+ : process(process), begin(begin), end(end)
+ {
+ }
+
+ bool process;
+ std::string::const_iterator begin;
+ std::string::const_iterator end;
+};
+
+
+TextRange findClosestRange(std::string::const_iterator pos,
+ const std::vector<TextRange>& ranges)
+{
+ TextRange closestRange = ranges.front();
+
+ BOOST_FOREACH(const TextRange& range, ranges)
+ {
+ if (std::abs(range.begin - pos) < std::abs(closestRange.begin - pos))
+ closestRange = range;
+ }
+
+ return closestRange;
+}
+
+bool hasLessThanThreeNewlines(const std::string& str)
+{
+ return std::count(str.begin(), str.end(), '\n') < 3;
+}
+
+}
+
+MathJaxFilter::MathJaxFilter(const std::vector<ExcludePattern>& excludePatterns,
+ std::string* pInput,
+ std::string* pHTMLOutput)
+ : pHTMLOutput_(pHTMLOutput)
+{
+ // divide the document into ranges (some of which will be processed
+ // and some of which will not -- we don't process some regions so that
+ // we don't need to worry about mathjax ambiguity within code regions)
+ std::vector<TextRange> ranges;
+ std::string::const_iterator pos = pInput->begin();
+ std::string::const_iterator inputEnd = pInput->end();
+ while (pos != inputEnd)
+ {
+ // try all of the exclude patterns
+ std::vector<TextRange> matchedRanges;
+ BOOST_FOREACH(const ExcludePattern& pattern, excludePatterns)
+ {
+ boost::smatch m;
+ if (boost::regex_search(pos, inputEnd, m, pattern.begin))
+ {
+ // set begin and end (may change if there is an end pattern)
+ std::string::const_iterator begin = m[0].first;
+ std::string::const_iterator end = m[0].second;
+
+ // check for a second match
+ if (!pattern.end.empty())
+ {
+ if (boost::regex_search(end, inputEnd, m, pattern.end))
+ {
+ // update end to be the end of the match
+ end = m[0].second;
+ }
+ else
+ {
+ // didn't find a matching end pattern so set the end to the
+ // end of the document -- this will cause us to exclude the
+ // rest of the document from processing
+ end = inputEnd;
+ }
+ }
+
+ // add the matched range to our list
+ matchedRanges.push_back(TextRange(false, begin, end));
+ }
+ }
+
+ // if we found at least one matched range then find the closest one,
+ // add it to our list, and continue
+ if (!matchedRanges.empty())
+ {
+ // find the closest range
+ TextRange range = findClosestRange(pos, matchedRanges);
+
+ // mark everything before the match as requiring processing
+ ranges.push_back(TextRange(true, pos, range.begin));
+
+ // add the range
+ ranges.push_back(range);
+
+ // update the position
+ pos = range.end;
+ }
+
+ // no match -- consume remaining input and tag it for processing
+ else
+ {
+ ranges.push_back(TextRange(true, pos, pInput->end()));
+ pos = pInput->end();
+ }
+ }
+
+ // now iterate through the ranges and substitute a guid for math blocks
+ std::string filteredInput;
+ BOOST_FOREACH(const TextRange& range, ranges)
+ {
+ std::string rangeText(range.begin, range.end);
+
+ if (range.process)
+ {
+ // native mathjax display equations
+ filter(boost::regex("\\\\\\[([\\s\\S]+?)\\\\\\]"),
+ &rangeText,
+ &displayMathBlocks_);
+
+ // latex display equations (latex designator optional, used for
+ // syntactic compatiblity w/ wordpress-style inline equations)
+ filter(boost::regex("\\${2}(?:latex\\s)?([\\s\\S]+?)\\${2}"),
+ &rangeText,
+ &displayMathBlocks_);
+
+ // native mathjax inline equations
+ filter(boost::regex("\\\\\\(([\\s\\S]+?)\\\\\\)"),
+ &rangeText,
+ &inlineMathBlocks_);
+
+ // wordpress style inline equations
+ filter(boost::regex("\\$latex\\s([\\s\\S]+?)\\$"),
+ &rangeText,
+ &inlineMathBlocks_);
+
+ // Org-mode style inline equations
+ filter(boost::regex("\\$((?!\\s)[^$]*[^$\\s])\\$(?![\\w\\d`])"),
+ &hasLessThanThreeNewlines,
+ &rangeText,
+ &inlineMathBlocks_);
+ }
+
+ filteredInput.append(rangeText);
+ }
+
+ *pInput = filteredInput;
+}
+
+MathJaxFilter::~MathJaxFilter()
+{
+ try
+ {
+ std::for_each(
+ displayMathBlocks_.begin(),
+ displayMathBlocks_.end(),
+ boost::bind(&MathJaxFilter::restore, this, _1, "\\[", "\\]"));
+
+ std::for_each(
+ inlineMathBlocks_.begin(),
+ inlineMathBlocks_.end(),
+ boost::bind(&MathJaxFilter::restore, this, _1, "\\(", "\\)"));
+ }
+ catch(...)
+ {
+ }
+}
+
+void MathJaxFilter::filter(const boost::regex& re,
+ const boost::function<bool(const std::string&)>& condition,
+ std::string* pInput,
+ std::map<std::string,MathBlock>* pMathBlocks)
+{
+ // explicit function type required because the Formatter functor
+ // supports 3 distinct signatures
+ boost::function<std::string(
+ boost::match_results<std::string::const_iterator>)> formatter =
+ boost::bind(&MathJaxFilter::substitute,
+ this, condition, _1, pMathBlocks);
+
+ *pInput = boost::regex_replace(*pInput, re, formatter);
+}
+
+std::string MathJaxFilter::substitute(
+ const boost::function<bool(const std::string&)>& condition,
+ boost::match_results<std::string::const_iterator> match,
+ std::map<std::string,MathBlock>* pMathBlocks)
+{
+ // get the equation
+ std::string equation = match[1];
+
+ // apply additional condition if available
+ if (condition && !condition(equation))
+ {
+ // don't perform any substitution
+ return match[0];
+ }
+ else
+ {
+ std::string guid = core::system::generateUuid(false);
+ std::string suffix = (match.size() > 2) ? std::string(match[2]) : "";
+ pMathBlocks->insert(std::make_pair(guid, MathBlock(equation,suffix)));
+ return guid;
+ }
+}
+
+void MathJaxFilter::restore(
+ const std::map<std::string,MathBlock>::value_type& block,
+ const std::string& beginDelim,
+ const std::string& endDelim)
+{
+ boost::algorithm::replace_first(
+ *pHTMLOutput_,
+ block.first,
+ beginDelim + " " + block.second.equation + " " + endDelim +
+ block.second.suffix);
+}
+
+bool requiresMathjax(const std::string& htmlOutput)
+{
+ boost::regex inlineMathRegex("\\\\\\(([\\s\\S]+?)\\\\\\)");
+ if (boost::regex_search(htmlOutput, inlineMathRegex))
+ return true;
+
+ boost::regex displayMathRegex("\\\\\\[([\\s\\S]+?)\\\\\\]");
+ if (boost::regex_search(htmlOutput, displayMathRegex))
+ return true;
+
+ boost::regex mathmlRegex("<math[>\\s](?s).*?</math>");
+ if (boost::regex_search(htmlOutput, mathmlRegex))
+ return true;
+
+ return false;
+}
+
+} // namespace markdown
+} // namespace core
+
+
+
+
diff --git a/src/cpp/core/markdown/MathJax.hpp b/src/cpp/core/markdown/MathJax.hpp
new file mode 100644
index 0000000..bc937c4
--- /dev/null
+++ b/src/cpp/core/markdown/MathJax.hpp
@@ -0,0 +1,104 @@
+/*
+ * MathJax.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <boost/utility.hpp>
+#include <boost/regex.hpp>
+#include <boost/function.hpp>
+
+namespace core {
+namespace markdown {
+
+struct ExcludePattern
+{
+ ExcludePattern(const boost::regex& pattern)
+ : begin(pattern)
+ {
+ }
+
+ ExcludePattern(const boost::regex& beginPattern,
+ const boost::regex& endPattern)
+ : begin(beginPattern), end(endPattern)
+ {
+ }
+
+ boost::regex begin;
+ boost::regex end;
+};
+
+
+struct MathBlock
+{
+ MathBlock(const std::string& equation,
+ const std::string& suffix)
+ : equation(equation), suffix(suffix)
+ {
+ }
+
+ std::string equation;
+ std::string suffix;
+};
+
+class MathJaxFilter : boost::noncopyable
+{
+public:
+ MathJaxFilter(const std::vector<ExcludePattern>& excludePatterns,
+ std::string* pInput,
+ std::string* pHTMLOutput);
+ ~MathJaxFilter();
+
+private:
+ void filter(const boost::regex& re,
+ std::string* pInput,
+ std::map<std::string,MathBlock>* pMathBlocks)
+ {
+ filter(re,
+ boost::function<bool(const std::string&)>(),
+ pInput,
+ pMathBlocks);
+ }
+
+ void filter(const boost::regex& re,
+ const boost::function<bool(const std::string&)>& condition,
+ std::string* pInput,
+ std::map<std::string,MathBlock>* pMathBlocks);
+
+ std::string substitute(
+ const boost::function<bool(const std::string&)>& condition,
+ boost::match_results<std::string::const_iterator> match,
+ std::map<std::string,MathBlock>* pMathBlocks);
+
+ void restore(const std::map<std::string,MathBlock>::value_type& block,
+ const std::string& beginDelim,
+ const std::string& endDelim);
+
+private:
+ std::string* pHTMLOutput_;
+ std::map<std::string,MathBlock> displayMathBlocks_;
+ std::map<std::string,MathBlock> inlineMathBlocks_;
+};
+
+bool requiresMathjax(const std::string& htmlOutput);
+
+
+} // namespace markdown
+} // namespace core
+
+
+
+
diff --git a/src/cpp/core/markdown/sundown/autolink.c b/src/cpp/core/markdown/sundown/autolink.c
new file mode 100644
index 0000000..b355e42
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/autolink.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "buffer.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#if defined(_WIN32)
+#define strncasecmp _strnicmp
+#endif
+
+int
+sd_autolink_issafe(const uint8_t *link, size_t link_len)
+{
+ static const size_t valid_uris_count = 5;
+ static const char *valid_uris[] = {
+ "/", "http://", "https://", "ftp://", "mailto:"
+ };
+
+ size_t i;
+
+ for (i = 0; i < valid_uris_count; ++i) {
+ size_t len = strlen(valid_uris[i]);
+
+ if (link_len > len &&
+ strncasecmp((char *)link, valid_uris[i], len) == 0 &&
+ isalnum(link[len]))
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t
+autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size)
+{
+ uint8_t cclose, copen = 0;
+ size_t i;
+
+ for (i = 0; i < link_end; ++i)
+ if (data[i] == '<') {
+ link_end = i;
+ break;
+ }
+
+ while (link_end > 0) {
+ if (strchr("?!.,", data[link_end - 1]) != NULL)
+ link_end--;
+
+ else if (data[link_end - 1] == ';') {
+ size_t new_end = link_end - 2;
+
+ while (new_end > 0 && isalpha(data[new_end]))
+ new_end--;
+
+ if (new_end < link_end - 2 && data[new_end] == '&')
+ link_end = new_end;
+ else
+ link_end--;
+ }
+ else break;
+ }
+
+ if (link_end == 0)
+ return 0;
+
+ cclose = data[link_end - 1];
+
+ switch (cclose) {
+ case '"': copen = '"'; break;
+ case '\'': copen = '\''; break;
+ case ')': copen = '('; break;
+ case ']': copen = '['; break;
+ case '}': copen = '{'; break;
+ }
+
+ if (copen != 0) {
+ size_t closing = 0;
+ size_t opening = 0;
+ size_t i = 0;
+
+ /* Try to close the final punctuation sign in this same line;
+ * if we managed to close it outside of the URL, that means that it's
+ * not part of the URL. If it closes inside the URL, that means it
+ * is part of the URL.
+ *
+ * Examples:
+ *
+ * foo http://www.pokemon.com/Pikachu_(Electric) bar
+ * => http://www.pokemon.com/Pikachu_(Electric)
+ *
+ * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => http://www.pokemon.com/Pikachu_(Electric)
+ *
+ * foo http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => http://www.pokemon.com/Pikachu_(Electric))
+ *
+ * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => foo http://www.pokemon.com/Pikachu_(Electric)
+ */
+
+ while (i < link_end) {
+ if (data[i] == copen)
+ opening++;
+ else if (data[i] == cclose)
+ closing++;
+
+ i++;
+ }
+
+ if (closing != opening)
+ link_end--;
+ }
+
+ return link_end;
+}
+
+static size_t
+check_domain(uint8_t *data, size_t size)
+{
+ size_t i, np = 0;
+
+ if (!isalnum(data[0]))
+ return 0;
+
+ for (i = 1; i < size - 1; ++i) {
+ if (data[i] == '.') np++;
+ else if (!isalnum(data[i]) && data[i] != '-') break;
+ }
+
+ /* a valid domain needs to have at least a dot.
+ * that's as far as we get */
+ return np ? i : 0;
+}
+
+size_t
+sd_autolink__www(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
+{
+ size_t link_end;
+
+ if (offset > 0 && !ispunct(data[-1]) && !isspace(data[-1]))
+ return 0;
+
+ if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
+ return 0;
+
+ link_end = check_domain(data, size);
+
+ if (link_end == 0)
+ return 0;
+
+ while (link_end < size && !isspace(data[link_end]))
+ link_end++;
+
+ link_end = autolink_delim(data, link_end, offset, size);
+
+ if (link_end == 0)
+ return 0;
+
+ bufput(link, data, link_end);
+ *rewind_p = 0;
+
+ return (int)link_end;
+}
+
+size_t
+sd_autolink__email(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
+{
+ size_t link_end, rewind;
+ int nb = 0, np = 0;
+
+ for (rewind = 0; rewind < offset; ++rewind) {
+ uint8_t c = data[-rewind - 1];
+
+ if (isalnum(c))
+ continue;
+
+ if (strchr(".+-_", c) != NULL)
+ continue;
+
+ break;
+ }
+
+ if (rewind == 0)
+ return 0;
+
+ for (link_end = 0; link_end < size; ++link_end) {
+ uint8_t c = data[link_end];
+
+ if (isalnum(c))
+ continue;
+
+ if (c == '@')
+ nb++;
+ else if (c == '.' && link_end < size - 1)
+ np++;
+ else if (c != '-' && c != '_')
+ break;
+ }
+
+ if (link_end < 2 || nb != 1 || np == 0)
+ return 0;
+
+ link_end = autolink_delim(data, link_end, offset, size);
+
+ if (link_end == 0)
+ return 0;
+
+ bufput(link, data - rewind, link_end + rewind);
+ *rewind_p = rewind;
+
+ return link_end;
+}
+
+size_t
+sd_autolink__url(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size)
+{
+ size_t link_end, rewind = 0, domain_len;
+
+ if (size < 4 || data[1] != '/' || data[2] != '/')
+ return 0;
+
+ while (rewind < offset && isalpha(data[-rewind - 1]))
+ rewind++;
+
+ if (!sd_autolink_issafe(data - rewind, size + rewind))
+ return 0;
+ link_end = strlen("://");
+
+ domain_len = check_domain(data + link_end, size - link_end);
+ if (domain_len == 0)
+ return 0;
+
+ link_end += domain_len;
+ while (link_end < size && !isspace(data[link_end]))
+ link_end++;
+
+ link_end = autolink_delim(data, link_end, offset, size);
+
+ if (link_end == 0)
+ return 0;
+
+ bufput(link, data - rewind, link_end + rewind);
+ *rewind_p = rewind;
+
+ return link_end;
+}
+
diff --git a/src/cpp/core/markdown/sundown/autolink.h b/src/cpp/core/markdown/sundown/autolink.h
new file mode 100644
index 0000000..4482db1
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/autolink.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef UPSKIRT_AUTOLINK_H
+#define UPSKIRT_AUTOLINK_H
+
+#include "buffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int
+sd_autolink_issafe(const uint8_t *link, size_t link_len);
+
+extern size_t
+sd_autolink__www(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
+
+extern size_t
+sd_autolink__email(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
+
+extern size_t
+sd_autolink__url(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/* vim: set filetype=c: */
diff --git a/src/cpp/core/markdown/sundown/buffer.c b/src/cpp/core/markdown/sundown/buffer.c
new file mode 100644
index 0000000..47b40ce
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/buffer.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb
+
+#include "buffer.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* MSVC compat */
+#if defined(_MSC_VER)
+# define _buf_vsnprintf _vsnprintf
+#else
+# define _buf_vsnprintf vsnprintf
+#endif
+
+int
+bufprefix(const struct buf *buf, const char *prefix)
+{
+ size_t i;
+ assert(buf && buf->unit);
+
+ for (i = 0; i < buf->size; ++i) {
+ if (prefix[i] == 0)
+ return 0;
+
+ if (buf->data[i] != prefix[i])
+ return buf->data[i] - prefix[i];
+ }
+
+ return 0;
+}
+
+/* bufgrow: increasing the allocated size to the given value */
+int
+bufgrow(struct buf *buf, size_t neosz)
+{
+ size_t neoasz;
+ void *neodata;
+
+ assert(buf && buf->unit);
+
+ if (neosz > BUFFER_MAX_ALLOC_SIZE)
+ return BUF_ENOMEM;
+
+ if (buf->asize >= neosz)
+ return BUF_OK;
+
+ neoasz = buf->asize + buf->unit;
+ while (neoasz < neosz)
+ neoasz += buf->unit;
+
+ neodata = realloc(buf->data, neoasz);
+ if (!neodata)
+ return BUF_ENOMEM;
+
+ buf->data = neodata;
+ buf->asize = neoasz;
+ return BUF_OK;
+}
+
+
+/* bufnew: allocation of a new buffer */
+struct buf *
+bufnew(size_t unit)
+{
+ struct buf *ret;
+ ret = malloc(sizeof (struct buf));
+
+ if (ret) {
+ ret->data = 0;
+ ret->size = ret->asize = 0;
+ ret->unit = unit;
+ }
+ return ret;
+}
+
+/* bufnullterm: NULL-termination of the string array */
+const char *
+bufcstr(struct buf *buf)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size < buf->asize && buf->data[buf->size] == 0)
+ return (char *)buf->data;
+
+ if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
+ buf->data[buf->size] = 0;
+ return (char *)buf->data;
+ }
+
+ return NULL;
+}
+
+/* bufprintf: formatted printing to a buffer */
+void
+bufprintf(struct buf *buf, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ assert(buf && buf->unit);
+
+ if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < 0)
+ return;
+
+ va_start(ap, fmt);
+ n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+ va_end(ap);
+
+ if (n < 0) {
+#ifdef _MSC_VER
+ va_start(ap, fmt);
+ n = _vscprintf(fmt, ap);
+ va_end(ap);
+#else
+ return;
+#endif
+ }
+
+ if ((size_t)n >= buf->asize - buf->size) {
+ if (bufgrow(buf, buf->size + n + 1) < 0)
+ return;
+
+ va_start(ap, fmt);
+ n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
+ va_end(ap);
+ }
+
+ if (n < 0)
+ return;
+
+ buf->size += n;
+}
+
+/* bufput: appends raw data to a buffer */
+void
+bufput(struct buf *buf, const void *data, size_t len)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0)
+ return;
+
+ memcpy(buf->data + buf->size, data, len);
+ buf->size += len;
+}
+
+/* bufputs: appends a NUL-terminated string to a buffer */
+void
+bufputs(struct buf *buf, const char *str)
+{
+ bufput(buf, str, strlen(str));
+}
+
+
+/* bufputc: appends a single uint8_t to a buffer */
+void
+bufputc(struct buf *buf, int c)
+{
+ assert(buf && buf->unit);
+
+ if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0)
+ return;
+
+ buf->data[buf->size] = c;
+ buf->size += 1;
+}
+
+/* bufrelease: decrease the reference count and free the buffer if needed */
+void
+bufrelease(struct buf *buf)
+{
+ if (!buf)
+ return;
+
+ free(buf->data);
+ free(buf);
+}
+
+
+/* bufreset: frees internal data of the buffer */
+void
+bufreset(struct buf *buf)
+{
+ if (!buf)
+ return;
+
+ free(buf->data);
+ buf->data = NULL;
+ buf->size = buf->asize = 0;
+}
+
+/* bufslurp: removes a given number of bytes from the head of the array */
+void
+bufslurp(struct buf *buf, size_t len)
+{
+ assert(buf && buf->unit);
+
+ if (len >= buf->size) {
+ buf->size = 0;
+ return;
+ }
+
+ buf->size -= len;
+ memmove(buf->data, buf->data + len, buf->size);
+}
+
diff --git a/src/cpp/core/markdown/sundown/buffer.h b/src/cpp/core/markdown/sundown/buffer.h
new file mode 100644
index 0000000..221d142
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/buffer.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2008, Natacha Porté
+ * Copyright (c) 2011, Vicent Martí
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef BUFFER_H__
+#define BUFFER_H__
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_MSC_VER)
+#define __attribute__(x)
+#define inline
+#endif
+
+typedef enum {
+ BUF_OK = 0,
+ BUF_ENOMEM = -1,
+} buferror_t;
+
+/* struct buf: character array buffer */
+struct buf {
+ uint8_t *data; /* actual character data */
+ size_t size; /* size of the string */
+ size_t asize; /* allocated size (0 = volatile buffer) */
+ size_t unit; /* reallocation unit size (0 = read-only buffer) */
+};
+
+/* CONST_BUF: global buffer from a string litteral */
+#define BUF_STATIC(string) \
+ { (uint8_t *)string, sizeof string -1, sizeof string, 0, 0 }
+
+/* VOLATILE_BUF: macro for creating a volatile buffer on the stack */
+#define BUF_VOLATILE(strname) \
+ { (uint8_t *)strname, strlen(strname), 0, 0, 0 }
+
+/* BUFPUTSL: optimized bufputs of a string litteral */
+#define BUFPUTSL(output, literal) \
+ bufput(output, literal, sizeof literal - 1)
+
+/* bufgrow: increasing the allocated size to the given value */
+int bufgrow(struct buf *, size_t);
+
+/* bufnew: allocation of a new buffer */
+struct buf *bufnew(size_t) __attribute__ ((malloc));
+
+/* bufnullterm: NUL-termination of the string array (making a C-string) */
+const char *bufcstr(struct buf *);
+
+/* bufprefix: compare the beginning of a buffer with a string */
+int bufprefix(const struct buf *buf, const char *prefix);
+
+/* bufput: appends raw data to a buffer */
+void bufput(struct buf *, const void *, size_t);
+
+/* bufputs: appends a NUL-terminated string to a buffer */
+void bufputs(struct buf *, const char *);
+
+/* bufputc: appends a single char to a buffer */
+void bufputc(struct buf *, int);
+
+/* bufrelease: decrease the reference count and free the buffer if needed */
+void bufrelease(struct buf *);
+
+/* bufreset: frees internal data of the buffer */
+void bufreset(struct buf *);
+
+/* bufslurp: removes a given number of bytes from the head of the array */
+void bufslurp(struct buf *, size_t);
+
+/* bufprintf: formatted printing to a buffer */
+void bufprintf(struct buf *, const char *, ...) __attribute__ ((format (printf, 2, 3)));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/markdown/sundown/houdini.h b/src/cpp/core/markdown/sundown/houdini.h
new file mode 100644
index 0000000..b4954c0
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/houdini.h
@@ -0,0 +1,37 @@
+#ifndef HOUDINI_H__
+#define HOUDINI_H__
+
+#include "buffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef HOUDINI_USE_LOCALE
+# define _isxdigit(c) isxdigit(c)
+# define _isdigit(c) isdigit(c)
+#else
+/*
+ * Helper _isdigit methods -- do not trust the current locale
+ * */
+# define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL)
+# define _isdigit(c) ((c) >= '0' && (c) <= '9')
+#endif
+
+extern void houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure);
+extern void houdini_unescape_html(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_xml(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_uri(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_url(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_uri(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_url(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_escape_js(struct buf *ob, const uint8_t *src, size_t size);
+extern void houdini_unescape_js(struct buf *ob, const uint8_t *src, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/markdown/sundown/houdini_href_e.c b/src/cpp/core/markdown/sundown/houdini_href_e.c
new file mode 100644
index 0000000..981b3b1
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/houdini_href_e.c
@@ -0,0 +1,108 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "houdini.h"
+
+#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10)
+
+/*
+ * The following characters will not be escaped:
+ *
+ * -_.+!*'(),%#@?=;:/,+&$ alphanum
+ *
+ * Note that this character set is the addition of:
+ *
+ * - The characters which are safe to be in an URL
+ * - The characters which are *not* safe to be in
+ * an URL because they are RESERVED characters.
+ *
+ * We asume (lazily) that any RESERVED char that
+ * appears inside an URL is actually meant to
+ * have its native function (i.e. as an URL
+ * component/separator) and hence needs no escaping.
+ *
+ * There are two exceptions: the chacters & (amp)
+ * and ' (single quote) do not appear in the table.
+ * They are meant to appear in the URL as components,
+ * yet they require special HTML-entity escaping
+ * to generate valid HTML markup.
+ *
+ * All other characters will be escaped to %XX.
+ *
+ */
+static const char HREF_SAFE[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void
+houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size)
+{
+ static const char hex_chars[] = "0123456789ABCDEF";
+ size_t i = 0, org;
+ char hex_str[3];
+
+ bufgrow(ob, ESCAPE_GROW_FACTOR(size));
+ hex_str[0] = '%';
+
+ while (i < size) {
+ org = i;
+ while (i < size && HREF_SAFE[src[i]] != 0)
+ i++;
+
+ if (i > org)
+ bufput(ob, src + org, i - org);
+
+ /* escaping */
+ if (i >= size)
+ break;
+
+ switch (src[i]) {
+ /* amp appears all the time in URLs, but needs
+ * HTML-entity escaping to be inside an href */
+ case '&':
+ BUFPUTSL(ob, "&");
+ break;
+
+ /* the single quote is a valid URL character
+ * according to the standard; it needs HTML
+ * entity escaping too */
+ case '\'':
+ BUFPUTSL(ob, "'");
+ break;
+
+ /* the space can be escaped to %20 or a plus
+ * sign. we're going with the generic escape
+ * for now. the plus thing is more commonly seen
+ * when building GET strings */
+#if 0
+ case ' ':
+ bufputc(ob, '+');
+ break;
+#endif
+
+ /* every other character goes with a %XX escaping */
+ default:
+ hex_str[1] = hex_chars[(src[i] >> 4) & 0xF];
+ hex_str[2] = hex_chars[src[i] & 0xF];
+ bufput(ob, hex_str, 3);
+ }
+
+ i++;
+ }
+}
diff --git a/src/cpp/core/markdown/sundown/houdini_html_e.c b/src/cpp/core/markdown/sundown/houdini_html_e.c
new file mode 100644
index 0000000..d9bbf18
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/houdini_html_e.c
@@ -0,0 +1,84 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "houdini.h"
+
+#define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) /* this is very scientific, yes */
+
+/**
+ * According to the OWASP rules:
+ *
+ * & --> &
+ * < --> <
+ * > --> >
+ * " --> "
+ * ' --> ' ' is not recommended
+ * / --> / forward slash is included as it helps end an HTML entity
+ *
+ */
+static const char HTML_ESCAPE_TABLE[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const char *HTML_ESCAPES[] = {
+ "",
+ """,
+ "&",
+ "'",
+ "/",
+ "<",
+ ">"
+};
+
+void
+houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure)
+{
+ size_t i = 0, org, esc = 0;
+
+ bufgrow(ob, ESCAPE_GROW_FACTOR(size));
+
+ while (i < size) {
+ org = i;
+ while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0)
+ i++;
+
+ if (i > org)
+ bufput(ob, src + org, i - org);
+
+ /* escaping */
+ if (i >= size)
+ break;
+
+ /* The forward slash is only escaped in secure mode */
+ if (src[i] == '/' && !secure) {
+ bufputc(ob, '/');
+ } else {
+ bufputs(ob, HTML_ESCAPES[esc]);
+ }
+
+ i++;
+ }
+}
+
+void
+houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size)
+{
+ houdini_escape_html0(ob, src, size, 1);
+}
+
diff --git a/src/cpp/core/markdown/sundown/html.c b/src/cpp/core/markdown/sundown/html.c
new file mode 100755
index 0000000..7f08ee8
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/html.c
@@ -0,0 +1,635 @@
+/*
+ * Copyright (c) 2009, Natacha Porté
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "markdown.h"
+#include "html.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "houdini.h"
+
+#define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
+
+int
+sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
+{
+ size_t i;
+ int closed = 0;
+
+ if (tag_size < 3 || tag_data[0] != '<')
+ return HTML_TAG_NONE;
+
+ i = 1;
+
+ if (tag_data[i] == '/') {
+ closed = 1;
+ i++;
+ }
+
+ for (; i < tag_size; ++i, ++tagname) {
+ if (*tagname == 0)
+ break;
+
+ if (tag_data[i] != *tagname)
+ return HTML_TAG_NONE;
+ }
+
+ if (i == tag_size)
+ return HTML_TAG_NONE;
+
+ if (isspace(tag_data[i]) || tag_data[i] == '>')
+ return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
+
+ return HTML_TAG_NONE;
+}
+
+static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length)
+{
+ houdini_escape_html0(ob, source, length, 0);
+}
+
+static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length)
+{
+ houdini_escape_href(ob, source, length);
+}
+
+/********************
+ * GENERIC RENDERER *
+ ********************/
+static int
+rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ if (!link || !link->size)
+ return 0;
+
+ if ((options->flags & HTML_SAFELINK) != 0 &&
+ !sd_autolink_issafe(link->data, link->size) &&
+ type != MKDA_EMAIL)
+ return 0;
+
+ BUFPUTSL(ob, "<a href=\"");
+ if (type == MKDA_EMAIL)
+ BUFPUTSL(ob, "mailto:");
+ escape_href(ob, link->data, link->size);
+
+ if (options->link_attributes) {
+ bufputc(ob, '\"');
+ options->link_attributes(ob, link, opaque);
+ bufputc(ob, '>');
+ } else {
+ BUFPUTSL(ob, "\">");
+ }
+
+ /*
+ * Pretty printing: if we get an email address as
+ * an actual URI, e.g. `mailto:foo at bar.com`, we don't
+ * want to print the `mailto:` prefix
+ */
+ if (bufprefix(link, "mailto:") == 0) {
+ escape_html(ob, link->data + 7, link->size - 7);
+ } else {
+ escape_html(ob, link->data, link->size);
+ }
+
+ BUFPUTSL(ob, "</a>");
+
+ return 1;
+}
+
+static void
+rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque)
+{
+ if (ob->size) bufputc(ob, '\n');
+
+ if (lang && lang->size) {
+ size_t i, cls;
+ BUFPUTSL(ob, "<pre><code class=\"");
+
+ for (i = 0, cls = 0; i < lang->size; ++i, ++cls) {
+ while (i < lang->size && isspace(lang->data[i]))
+ i++;
+
+ if (i < lang->size) {
+ size_t org = i;
+ while (i < lang->size && !isspace(lang->data[i]))
+ i++;
+
+ if (lang->data[org] == '.')
+ org++;
+
+ if (cls) bufputc(ob, ' ');
+ escape_html(ob, lang->data + org, i - org);
+ }
+ }
+
+ BUFPUTSL(ob, "\">");
+ } else
+ BUFPUTSL(ob, "<pre><code>");
+
+ if (text)
+ escape_html(ob, text->data, text->size);
+
+ BUFPUTSL(ob, "</code></pre>\n");
+}
+
+static void
+rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (ob->size) bufputc(ob, '\n');
+ BUFPUTSL(ob, "<blockquote>\n");
+ if (text) bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</blockquote>\n");
+}
+
+static int
+rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
+{
+ BUFPUTSL(ob, "<code>");
+ if (text) escape_html(ob, text->data, text->size);
+ BUFPUTSL(ob, "</code>");
+ return 1;
+}
+
+static int
+rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (!text || !text->size)
+ return 0;
+
+ BUFPUTSL(ob, "<del>");
+ bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</del>");
+ return 1;
+}
+
+static int
+rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (!text || !text->size)
+ return 0;
+
+ BUFPUTSL(ob, "<strong>");
+ bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</strong>");
+
+ return 1;
+}
+
+static int
+rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (!text || !text->size) return 0;
+ BUFPUTSL(ob, "<em>");
+ if (text) bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</em>");
+ return 1;
+}
+
+static int
+rndr_linebreak(struct buf *ob, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+ bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
+ return 1;
+}
+
+static void
+rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ if (ob->size)
+ bufputc(ob, '\n');
+
+ if (options->flags & HTML_TOC)
+ bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);
+ else
+ bufprintf(ob, "<h%d>", level);
+
+ if (text) bufput(ob, text->data, text->size);
+ bufprintf(ob, "</h%d>\n", level);
+}
+
+static int
+rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
+ return 0;
+
+ BUFPUTSL(ob, "<a href=\"");
+
+ if (link && link->size)
+ escape_href(ob, link->data, link->size);
+
+ if (title && title->size) {
+ BUFPUTSL(ob, "\" title=\"");
+ escape_html(ob, title->data, title->size);
+ }
+
+ if (options->link_attributes) {
+ bufputc(ob, '\"');
+ options->link_attributes(ob, link, opaque);
+ bufputc(ob, '>');
+ } else {
+ BUFPUTSL(ob, "\">");
+ }
+
+ if (content && content->size) bufput(ob, content->data, content->size);
+ BUFPUTSL(ob, "</a>");
+ return 1;
+}
+
+static void
+rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
+{
+ if (ob->size) bufputc(ob, '\n');
+ bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);
+ if (text) bufput(ob, text->data, text->size);
+ bufput(ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6);
+}
+
+static void
+rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
+{
+ BUFPUTSL(ob, "<li>");
+ if (text) {
+ size_t size = text->size;
+ while (size && text->data[size - 1] == '\n')
+ size--;
+
+ bufput(ob, text->data, size);
+ }
+ BUFPUTSL(ob, "</li>\n");
+}
+
+static void
+rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+ size_t i = 0;
+
+ if (ob->size) bufputc(ob, '\n');
+
+ if (!text || !text->size)
+ return;
+
+ while (i < text->size && isspace(text->data[i])) i++;
+
+ if (i == text->size)
+ return;
+
+ BUFPUTSL(ob, "<p>");
+ if (options->flags & HTML_HARD_WRAP) {
+ size_t org;
+ while (i < text->size) {
+ org = i;
+ while (i < text->size && text->data[i] != '\n')
+ i++;
+
+ if (i > org)
+ bufput(ob, text->data + org, i - org);
+
+ /*
+ * do not insert a line break if this newline
+ * is the last character on the paragraph
+ */
+ if (i >= text->size - 1)
+ break;
+
+ rndr_linebreak(ob, opaque);
+ i++;
+ }
+ } else {
+ bufput(ob, &text->data[i], text->size - i);
+ }
+ BUFPUTSL(ob, "</p>\n");
+}
+
+static void
+rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
+{
+ size_t org, sz;
+ if (!text) return;
+ sz = text->size;
+ while (sz > 0 && text->data[sz - 1] == '\n') sz--;
+ org = 0;
+ while (org < sz && text->data[org] == '\n') org++;
+ if (org >= sz) return;
+ if (ob->size) bufputc(ob, '\n');
+ bufput(ob, text->data + org, sz - org);
+ bufputc(ob, '\n');
+}
+
+static int
+rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (!text || !text->size) return 0;
+ BUFPUTSL(ob, "<strong><em>");
+ bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</em></strong>");
+ return 1;
+}
+
+static void
+rndr_hrule(struct buf *ob, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+ if (ob->size) bufputc(ob, '\n');
+ bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");
+}
+
+static int
+rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+ if (!link || !link->size) return 0;
+
+ BUFPUTSL(ob, "<img src=\"");
+ escape_href(ob, link->data, link->size);
+ BUFPUTSL(ob, "\" alt=\"");
+
+ if (alt && alt->size)
+ escape_html(ob, alt->data, alt->size);
+
+ if (title && title->size) {
+ BUFPUTSL(ob, "\" title=\"");
+ escape_html(ob, title->data, title->size); }
+
+ bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
+ return 1;
+}
+
+static int
+rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
+ * It doens't see if there are any valid tags, just escape all of them. */
+ if((options->flags & HTML_ESCAPE) != 0) {
+ escape_html(ob, text->data, text->size);
+ return 1;
+ }
+
+ if ((options->flags & HTML_SKIP_HTML) != 0)
+ return 1;
+
+ if ((options->flags & HTML_SKIP_STYLE) != 0 &&
+ sdhtml_is_tag(text->data, text->size, "style"))
+ return 1;
+
+ if ((options->flags & HTML_SKIP_LINKS) != 0 &&
+ sdhtml_is_tag(text->data, text->size, "a"))
+ return 1;
+
+ if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
+ sdhtml_is_tag(text->data, text->size, "img"))
+ return 1;
+
+ bufput(ob, text->data, text->size);
+ return 1;
+}
+
+static void
+rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
+{
+ if (ob->size) bufputc(ob, '\n');
+ BUFPUTSL(ob, "<table><thead>\n");
+ if (header)
+ bufput(ob, header->data, header->size);
+ BUFPUTSL(ob, "</thead><tbody>\n");
+ if (body)
+ bufput(ob, body->data, body->size);
+ BUFPUTSL(ob, "</tbody></table>\n");
+}
+
+static void
+rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
+{
+ BUFPUTSL(ob, "<tr>\n");
+ if (text)
+ bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</tr>\n");
+}
+
+static void
+rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
+{
+ if (flags & MKD_TABLE_HEADER) {
+ BUFPUTSL(ob, "<th");
+ } else {
+ BUFPUTSL(ob, "<td");
+ }
+
+ switch (flags & MKD_TABLE_ALIGNMASK) {
+ case MKD_TABLE_ALIGN_CENTER:
+ BUFPUTSL(ob, " align=\"center\">");
+ break;
+
+ case MKD_TABLE_ALIGN_L:
+ BUFPUTSL(ob, " align=\"left\">");
+ break;
+
+ case MKD_TABLE_ALIGN_R:
+ BUFPUTSL(ob, " align=\"right\">");
+ break;
+
+ default:
+ BUFPUTSL(ob, ">");
+ }
+
+ if (text)
+ bufput(ob, text->data, text->size);
+
+ if (flags & MKD_TABLE_HEADER) {
+ BUFPUTSL(ob, "</th>\n");
+ } else {
+ BUFPUTSL(ob, "</td>\n");
+ }
+}
+
+static int
+rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (!text || !text->size) return 0;
+ BUFPUTSL(ob, "<sup>");
+ bufput(ob, text->data, text->size);
+ BUFPUTSL(ob, "</sup>");
+ return 1;
+}
+
+static void
+rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
+{
+ if (text)
+ escape_html(ob, text->data, text->size);
+}
+
+static void
+toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ /* set the level offset if this is the first header
+ * we're parsing for the document */
+ if (options->toc_data.current_level == 0) {
+ options->toc_data.level_offset = level - 1;
+ }
+ level -= options->toc_data.level_offset;
+
+ if (level > options->toc_data.current_level) {
+ while (level > options->toc_data.current_level) {
+ BUFPUTSL(ob, "<ul>\n<li>\n");
+ options->toc_data.current_level++;
+ }
+ } else if (level < options->toc_data.current_level) {
+ BUFPUTSL(ob, "</li>\n");
+ while (level < options->toc_data.current_level) {
+ BUFPUTSL(ob, "</ul>\n</li>\n");
+ options->toc_data.current_level--;
+ }
+ BUFPUTSL(ob,"<li>\n");
+ } else {
+ BUFPUTSL(ob,"</li>\n<li>\n");
+ }
+
+ bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++);
+ if (text)
+ escape_html(ob, text->data, text->size);
+ BUFPUTSL(ob, "</a>\n");
+}
+
+static int
+toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
+{
+ if (content && content->size)
+ bufput(ob, content->data, content->size);
+ return 1;
+}
+
+static void
+toc_finalize(struct buf *ob, void *opaque)
+{
+ struct html_renderopt *options = opaque;
+
+ while (options->toc_data.current_level > 0) {
+ BUFPUTSL(ob, "</li>\n</ul>\n");
+ options->toc_data.current_level--;
+ }
+}
+
+void
+sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)
+{
+ static const struct sd_callbacks cb_default = {
+ NULL,
+ NULL,
+ NULL,
+ toc_header,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ NULL,
+ rndr_codespan,
+ rndr_double_emphasis,
+ rndr_emphasis,
+ NULL,
+ NULL,
+ toc_link,
+ NULL,
+ rndr_triple_emphasis,
+ rndr_strikethrough,
+ rndr_superscript,
+
+ NULL,
+ NULL,
+
+ NULL,
+ toc_finalize,
+ };
+
+ memset(options, 0x0, sizeof(struct html_renderopt));
+ options->flags = HTML_TOC;
+
+ memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
+}
+
+void
+sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
+{
+ static const struct sd_callbacks cb_default = {
+ rndr_blockcode,
+ rndr_blockquote,
+ rndr_raw_block,
+ rndr_header,
+ rndr_hrule,
+ rndr_list,
+ rndr_listitem,
+ rndr_paragraph,
+ rndr_table,
+ rndr_tablerow,
+ rndr_tablecell,
+
+ rndr_autolink,
+ rndr_codespan,
+ rndr_double_emphasis,
+ rndr_emphasis,
+ rndr_image,
+ rndr_linebreak,
+ rndr_link,
+ rndr_raw_html,
+ rndr_triple_emphasis,
+ rndr_strikethrough,
+ rndr_superscript,
+
+ NULL,
+ rndr_normal_text,
+
+ NULL,
+ NULL,
+ };
+
+ /* Prepare the options pointer */
+ memset(options, 0x0, sizeof(struct html_renderopt));
+ options->flags = render_flags;
+
+ /* Prepare the callbacks */
+ memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
+
+ if (render_flags & HTML_SKIP_IMAGES)
+ callbacks->image = NULL;
+
+ if (render_flags & HTML_SKIP_LINKS) {
+ callbacks->link = NULL;
+ callbacks->autolink = NULL;
+ }
+
+ if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE)
+ callbacks->blockhtml = NULL;
+}
diff --git a/src/cpp/core/markdown/sundown/html.h b/src/cpp/core/markdown/sundown/html.h
new file mode 100644
index 0000000..4c8810d
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/html.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef UPSKIRT_HTML_H
+#define UPSKIRT_HTML_H
+
+#include "markdown.h"
+#include "buffer.h"
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct html_renderopt {
+ struct {
+ int header_count;
+ int current_level;
+ int level_offset;
+ } toc_data;
+
+ unsigned int flags;
+
+ /* extra callbacks */
+ void (*link_attributes)(struct buf *ob, const struct buf *url, void *self);
+};
+
+typedef enum {
+ HTML_SKIP_HTML = (1 << 0),
+ HTML_SKIP_STYLE = (1 << 1),
+ HTML_SKIP_IMAGES = (1 << 2),
+ HTML_SKIP_LINKS = (1 << 3),
+ HTML_EXPAND_TABS = (1 << 4),
+ HTML_SAFELINK = (1 << 5),
+ HTML_TOC = (1 << 6),
+ HTML_HARD_WRAP = (1 << 7),
+ HTML_USE_XHTML = (1 << 8),
+ HTML_ESCAPE = (1 << 9),
+} html_render_mode;
+
+typedef enum {
+ HTML_TAG_NONE = 0,
+ HTML_TAG_OPEN,
+ HTML_TAG_CLOSE,
+} html_tag;
+
+int
+sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname);
+
+extern void
+sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);
+
+extern void
+sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr);
+
+extern void
+sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/src/cpp/core/markdown/sundown/html_blocks.h b/src/cpp/core/markdown/sundown/html_blocks.h
new file mode 100644
index 0000000..09a758f
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/html_blocks.h
@@ -0,0 +1,206 @@
+/* C code produced by gperf version 3.0.3 */
+/* Command-line: gperf -N find_block_tag -H hash_block_tag -C -c -E --ignore-case html_block_names.txt */
+/* Computed positions: -k'1-2' */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf at gnu.org>."
+#endif
+
+/* maximum key range = 37, duplicates = 0 */
+
+#ifndef GPERF_DOWNCASE
+#define GPERF_DOWNCASE 1
+static unsigned char gperf_downcase[256] =
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+ 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255
+ };
+#endif
+
+#ifndef GPERF_CASE_STRNCMP
+#define GPERF_CASE_STRNCMP 1
+static int
+gperf_case_strncmp (s1, s2, n)
+ register const char *s1;
+ register const char *s2;
+ register unsigned int n;
+{
+ for (; n > 0;)
+ {
+ unsigned char c1 = gperf_downcase[(unsigned char)*s1++];
+ unsigned char c2 = gperf_downcase[(unsigned char)*s2++];
+ if (c1 != 0 && c1 == c2)
+ {
+ n--;
+ continue;
+ }
+ return (int)c1 - (int)c2;
+ }
+ return 0;
+}
+#endif
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static unsigned int
+hash_block_tag (str, len)
+ register const char *str;
+ register unsigned int len;
+{
+ static const unsigned char asso_values[] =
+ {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 8, 30, 25, 20, 15, 10, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 0, 38, 0, 38,
+ 5, 5, 5, 15, 0, 38, 38, 0, 15, 10,
+ 0, 38, 38, 15, 0, 5, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 0, 38,
+ 0, 38, 5, 5, 5, 15, 0, 38, 38, 0,
+ 15, 10, 0, 38, 38, 15, 0, 5, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[1]+1];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]];
+ break;
+ }
+ return hval;
+}
+
+#ifdef __GNUC__
+__inline
+#ifdef __GNUC_STDC_INLINE__
+__attribute__ ((__gnu_inline__))
+#endif
+#endif
+const char *
+find_block_tag (str, len)
+ register const char *str;
+ register unsigned int len;
+{
+ enum
+ {
+ TOTAL_KEYWORDS = 24,
+ MIN_WORD_LENGTH = 1,
+ MAX_WORD_LENGTH = 10,
+ MIN_HASH_VALUE = 1,
+ MAX_HASH_VALUE = 37
+ };
+
+ static const char * const wordlist[] =
+ {
+ "",
+ "p",
+ "dl",
+ "div",
+ "math",
+ "table",
+ "",
+ "ul",
+ "del",
+ "form",
+ "blockquote",
+ "figure",
+ "ol",
+ "fieldset",
+ "",
+ "h1",
+ "",
+ "h6",
+ "pre",
+ "", "",
+ "script",
+ "h5",
+ "noscript",
+ "",
+ "style",
+ "iframe",
+ "h4",
+ "ins",
+ "", "", "",
+ "h3",
+ "", "", "", "",
+ "h2"
+ };
+
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash_block_tag (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= 0)
+ {
+ register const char *s = wordlist[key];
+
+ if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0')
+ return s;
+ }
+ }
+ return 0;
+}
diff --git a/src/cpp/core/markdown/sundown/html_smartypants.c b/src/cpp/core/markdown/sundown/html_smartypants.c
new file mode 100644
index 0000000..367c26a
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/html_smartypants.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "buffer.h"
+#include "html.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#if defined(_WIN32)
+#define snprintf _snprintf
+#endif
+
+struct smartypants_data {
+ int in_squote;
+ int in_dquote;
+};
+
+static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
+
+static size_t (*smartypants_cb_ptrs[])
+ (struct buf *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) =
+{
+ NULL, /* 0 */
+ smartypants_cb__dash, /* 1 */
+ smartypants_cb__parens, /* 2 */
+ smartypants_cb__squote, /* 3 */
+ smartypants_cb__dquote, /* 4 */
+ smartypants_cb__amp, /* 5 */
+ smartypants_cb__period, /* 6 */
+ smartypants_cb__number, /* 7 */
+ smartypants_cb__ltag, /* 8 */
+ smartypants_cb__backtick, /* 9 */
+ smartypants_cb__escape, /* 10 */
+};
+
+static const uint8_t smartypants_cb_chars[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0,
+ 0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0,
+ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static inline int
+word_boundary(uint8_t c)
+{
+ return c == 0 || isspace(c) || ispunct(c);
+}
+
+static int
+smartypants_quotes(struct buf *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open)
+{
+ char ent[8];
+
+ if (*is_open && !word_boundary(next_char))
+ return 0;
+
+ if (!(*is_open) && !word_boundary(previous_char))
+ return 0;
+
+ snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote);
+ *is_open = !(*is_open);
+ bufputs(ob, ent);
+ return 1;
+}
+
+static size_t
+smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 2) {
+ uint8_t t1 = tolower(text[1]);
+
+ if (t1 == '\'') {
+ if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
+ return 1;
+ }
+
+ if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') &&
+ (size == 3 || word_boundary(text[2]))) {
+ BUFPUTSL(ob, "’");
+ return 0;
+ }
+
+ if (size >= 3) {
+ uint8_t t2 = tolower(text[2]);
+
+ if (((t1 == 'r' && t2 == 'e') ||
+ (t1 == 'l' && t2 == 'l') ||
+ (t1 == 'v' && t2 == 'e')) &&
+ (size == 4 || word_boundary(text[3]))) {
+ BUFPUTSL(ob, "’");
+ return 0;
+ }
+ }
+ }
+
+ if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote))
+ return 0;
+
+ bufputc(ob, text[0]);
+ return 0;
+}
+
+static size_t
+smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 3) {
+ uint8_t t1 = tolower(text[1]);
+ uint8_t t2 = tolower(text[2]);
+
+ if (t1 == 'c' && t2 == ')') {
+ BUFPUTSL(ob, "©");
+ return 2;
+ }
+
+ if (t1 == 'r' && t2 == ')') {
+ BUFPUTSL(ob, "®");
+ return 2;
+ }
+
+ if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') {
+ BUFPUTSL(ob, "™");
+ return 3;
+ }
+ }
+
+ bufputc(ob, text[0]);
+ return 0;
+}
+
+static size_t
+smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 3 && text[1] == '-' && text[2] == '-') {
+ BUFPUTSL(ob, "—");
+ return 2;
+ }
+
+ if (size >= 2 && text[1] == '-') {
+ BUFPUTSL(ob, "–");
+ return 1;
+ }
+
+ bufputc(ob, text[0]);
+ return 0;
+}
+
+static size_t
+smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 6 && memcmp(text, """, 6) == 0) {
+ if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))
+ return 5;
+ }
+
+ if (size >= 4 && memcmp(text, "", 4) == 0)
+ return 3;
+
+ bufputc(ob, '&');
+ return 0;
+}
+
+static size_t
+smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 3 && text[1] == '.' && text[2] == '.') {
+ BUFPUTSL(ob, "…");
+ return 2;
+ }
+
+ if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') {
+ BUFPUTSL(ob, "…");
+ return 4;
+ }
+
+ bufputc(ob, text[0]);
+ return 0;
+}
+
+static size_t
+smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size >= 2 && text[1] == '`') {
+ if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t
+smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (word_boundary(previous_char) && size >= 3) {
+ if (text[0] == '1' && text[1] == '/' && text[2] == '2') {
+ if (size == 3 || word_boundary(text[3])) {
+ BUFPUTSL(ob, "½");
+ return 2;
+ }
+ }
+
+ if (text[0] == '1' && text[1] == '/' && text[2] == '4') {
+ if (size == 3 || word_boundary(text[3]) ||
+ (size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) {
+ BUFPUTSL(ob, "¼");
+ return 2;
+ }
+ }
+
+ if (text[0] == '3' && text[1] == '/' && text[2] == '4') {
+ if (size == 3 || word_boundary(text[3]) ||
+ (size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) {
+ BUFPUTSL(ob, "¾");
+ return 2;
+ }
+ }
+ }
+
+ bufputc(ob, text[0]);
+ return 0;
+}
+
+static size_t
+smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote))
+ BUFPUTSL(ob, """);
+
+ return 0;
+}
+
+static size_t
+smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ static const char *skip_tags[] = {
+ "pre", "code", "var", "samp", "kbd", "math", "script", "style"
+ };
+ static const size_t skip_tags_count = 8;
+
+ size_t tag, i = 0;
+
+ while (i < size && text[i] != '>')
+ i++;
+
+ for (tag = 0; tag < skip_tags_count; ++tag) {
+ if (sdhtml_is_tag(text, size, skip_tags[tag]) == HTML_TAG_OPEN)
+ break;
+ }
+
+ if (tag < skip_tags_count) {
+ for (;;) {
+ while (i < size && text[i] != '<')
+ i++;
+
+ if (i == size)
+ break;
+
+ if (sdhtml_is_tag(text + i, size - i, skip_tags[tag]) == HTML_TAG_CLOSE)
+ break;
+
+ i++;
+ }
+
+ while (i < size && text[i] != '>')
+ i++;
+ }
+
+ bufput(ob, text, i + 1);
+ return i;
+}
+
+static size_t
+smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
+{
+ if (size < 2)
+ return 0;
+
+ switch (text[1]) {
+ case '\\':
+ case '"':
+ case '\'':
+ case '.':
+ case '-':
+ case '`':
+ bufputc(ob, text[1]);
+ return 1;
+
+ default:
+ bufputc(ob, '\\');
+ return 0;
+ }
+}
+
+#if 0
+static struct {
+ uint8_t c0;
+ const uint8_t *pattern;
+ const uint8_t *entity;
+ int skip;
+} smartypants_subs[] = {
+ { '\'', "'s>", "’", 0 },
+ { '\'', "'t>", "’", 0 },
+ { '\'', "'re>", "’", 0 },
+ { '\'', "'ll>", "’", 0 },
+ { '\'', "'ve>", "’", 0 },
+ { '\'', "'m>", "’", 0 },
+ { '\'', "'d>", "’", 0 },
+ { '-', "--", "—", 1 },
+ { '-', "<->", "–", 0 },
+ { '.', "...", "…", 2 },
+ { '.', ". . .", "…", 4 },
+ { '(', "(c)", "©", 2 },
+ { '(', "(r)", "®", 2 },
+ { '(', "(tm)", "™", 3 },
+ { '3', "<3/4>", "¾", 2 },
+ { '3', "<3/4ths>", "¾", 2 },
+ { '1', "<1/2>", "½", 2 },
+ { '1', "<1/4>", "¼", 2 },
+ { '1', "<1/4th>", "¼", 2 },
+ { '&', "", 0, 3 },
+};
+#endif
+
+void
+sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size)
+{
+ size_t i;
+ struct smartypants_data smrt = {0, 0};
+
+ if (!text)
+ return;
+
+ bufgrow(ob, size);
+
+ for (i = 0; i < size; ++i) {
+ size_t org;
+ uint8_t action = 0;
+
+ org = i;
+ while (i < size && (action = smartypants_cb_chars[text[i]]) == 0)
+ i++;
+
+ if (i > org)
+ bufput(ob, text + org, i - org);
+
+ if (i < size) {
+ i += smartypants_cb_ptrs[(int)action]
+ (ob, &smrt, i ? text[i - 1] : 0, text + i, size - i);
+ }
+ }
+}
+
+
diff --git a/src/cpp/core/markdown/sundown/markdown.c b/src/cpp/core/markdown/sundown/markdown.c
new file mode 100644
index 0000000..c546e1a
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/markdown.c
@@ -0,0 +1,2551 @@
+/* markdown.c - generic markdown parser */
+
+/*
+ * Copyright (c) 2009, Natacha Porté
+ * Copyright (c) 2011, Vicent Marti
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "markdown.h"
+#include "stack.h"
+
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#if defined(_WIN32)
+#define strncasecmp _strnicmp
+#endif
+
+#define REF_TABLE_SIZE 8
+
+#define BUFFER_BLOCK 0
+#define BUFFER_SPAN 1
+
+#define MKD_LI_END 8 /* internal list flag */
+
+#define gperf_case_strncmp(s1, s2, n) strncasecmp(s1, s2, n)
+#define GPERF_DOWNCASE 1
+#define GPERF_CASE_STRNCMP 1
+#include "html_blocks.h"
+
+/***************
+ * LOCAL TYPES *
+ ***************/
+
+/* link_ref: reference to a link */
+struct link_ref {
+ unsigned int id;
+
+ struct buf *link;
+ struct buf *title;
+
+ struct link_ref *next;
+};
+
+/* char_trigger: function pointer to render active chars */
+/* returns the number of chars taken care of */
+/* data is the pointer of the beginning of the span */
+/* offset is the number of valid chars before data */
+struct sd_markdown;
+typedef size_t
+(*char_trigger)(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+
+static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+static size_t char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
+
+enum markdown_char_t {
+ MD_CHAR_NONE = 0,
+ MD_CHAR_EMPHASIS,
+ MD_CHAR_CODESPAN,
+ MD_CHAR_LINEBREAK,
+ MD_CHAR_LINK,
+ MD_CHAR_LANGLE,
+ MD_CHAR_ESCAPE,
+ MD_CHAR_ENTITITY,
+ MD_CHAR_AUTOLINK_URL,
+ MD_CHAR_AUTOLINK_EMAIL,
+ MD_CHAR_AUTOLINK_WWW,
+ MD_CHAR_SUPERSCRIPT,
+};
+
+static char_trigger markdown_char_ptrs[] = {
+ NULL,
+ &char_emphasis,
+ &char_codespan,
+ &char_linebreak,
+ &char_link,
+ &char_langle_tag,
+ &char_escape,
+ &char_entity,
+ &char_autolink_url,
+ &char_autolink_email,
+ &char_autolink_www,
+ &char_superscript,
+};
+
+/* render • structure containing one particular render */
+struct sd_markdown {
+ struct sd_callbacks cb;
+ void *opaque;
+
+ struct link_ref *refs[REF_TABLE_SIZE];
+ uint8_t active_char[256];
+ struct stack work_bufs[2];
+ unsigned int ext_flags;
+ size_t max_nesting;
+ int in_link_body;
+};
+
+/***************************
+ * HELPER FUNCTIONS *
+ ***************************/
+
+static inline struct buf *
+rndr_newbuf(struct sd_markdown *rndr, int type)
+{
+ static const size_t buf_size[2] = {256, 64};
+ struct buf *work = NULL;
+ struct stack *pool = &rndr->work_bufs[type];
+
+ if (pool->size < pool->asize &&
+ pool->item[pool->size] != NULL) {
+ work = pool->item[pool->size++];
+ work->size = 0;
+ } else {
+ work = bufnew(buf_size[type]);
+ stack_push(pool, work);
+ }
+
+ return work;
+}
+
+static inline void
+rndr_popbuf(struct sd_markdown *rndr, int type)
+{
+ rndr->work_bufs[type].size--;
+}
+
+static void
+unscape_text(struct buf *ob, struct buf *src)
+{
+ size_t i = 0, org;
+ while (i < src->size) {
+ org = i;
+ while (i < src->size && src->data[i] != '\\')
+ i++;
+
+ if (i > org)
+ bufput(ob, src->data + org, i - org);
+
+ if (i + 1 >= src->size)
+ break;
+
+ bufputc(ob, src->data[i + 1]);
+ i += 2;
+ }
+}
+
+static unsigned int
+hash_link_ref(const uint8_t *link_ref, size_t length)
+{
+ size_t i;
+ unsigned int hash = 0;
+
+ for (i = 0; i < length; ++i)
+ hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash;
+
+ return hash;
+}
+
+static struct link_ref *
+add_link_ref(
+ struct link_ref **references,
+ const uint8_t *name, size_t name_size)
+{
+ struct link_ref *ref = calloc(1, sizeof(struct link_ref));
+
+ if (!ref)
+ return NULL;
+
+ ref->id = hash_link_ref(name, name_size);
+ ref->next = references[ref->id % REF_TABLE_SIZE];
+
+ references[ref->id % REF_TABLE_SIZE] = ref;
+ return ref;
+}
+
+static struct link_ref *
+find_link_ref(struct link_ref **references, uint8_t *name, size_t length)
+{
+ unsigned int hash = hash_link_ref(name, length);
+ struct link_ref *ref = NULL;
+
+ ref = references[hash % REF_TABLE_SIZE];
+
+ while (ref != NULL) {
+ if (ref->id == hash)
+ return ref;
+
+ ref = ref->next;
+ }
+
+ return NULL;
+}
+
+static void
+free_link_refs(struct link_ref **references)
+{
+ size_t i;
+
+ for (i = 0; i < REF_TABLE_SIZE; ++i) {
+ struct link_ref *r = references[i];
+ struct link_ref *next;
+
+ while (r) {
+ next = r->next;
+ bufrelease(r->link);
+ bufrelease(r->title);
+ free(r);
+ r = next;
+ }
+ }
+}
+
+/*
+ * Check whether a char is a Markdown space.
+
+ * Right now we only consider spaces the actual
+ * space and a newline: tabs and carriage returns
+ * are filtered out during the preprocessing phase.
+ *
+ * If we wanted to actually be UTF-8 compliant, we
+ * should instead extract an Unicode codepoint from
+ * this character and check for space properties.
+ */
+static inline int
+_isspace(int c)
+{
+ return c == ' ' || c == '\n';
+}
+
+/****************************
+ * INLINE PARSING FUNCTIONS *
+ ****************************/
+
+/* is_mail_autolink • looks for the address part of a mail autolink and '>' */
+/* this is less strict than the original markdown e-mail address matching */
+static size_t
+is_mail_autolink(uint8_t *data, size_t size)
+{
+ size_t i = 0, nb = 0;
+
+ /* address is assumed to be: [- at ._a-zA-Z0-9]+ with exactly one '@' */
+ for (i = 0; i < size; ++i) {
+ if (isalnum(data[i]))
+ continue;
+
+ switch (data[i]) {
+ case '@':
+ nb++;
+
+ case '-':
+ case '.':
+ case '_':
+ break;
+
+ case '>':
+ return (nb == 1) ? i + 1 : 0;
+
+ default:
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/* tag_length • returns the length of the given tag, or 0 is it's not valid */
+static size_t
+tag_length(uint8_t *data, size_t size, enum mkd_autolink *autolink)
+{
+ size_t i, j;
+
+ /* a valid tag can't be shorter than 3 chars */
+ if (size < 3) return 0;
+
+ /* begins with a '<' optionally followed by '/', followed by letter or number */
+ if (data[0] != '<') return 0;
+ i = (data[1] == '/') ? 2 : 1;
+
+ if (!isalnum(data[i]))
+ return 0;
+
+ /* scheme test */
+ *autolink = MKDA_NOT_AUTOLINK;
+
+ /* try to find the beginning of an URI */
+ while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-'))
+ i++;
+
+ if (i > 1 && data[i] == '@') {
+ if ((j = is_mail_autolink(data + i, size - i)) != 0) {
+ *autolink = MKDA_EMAIL;
+ return i + j;
+ }
+ }
+
+ if (i > 2 && data[i] == ':') {
+ *autolink = MKDA_NORMAL;
+ i++;
+ }
+
+ /* completing autolink test: no whitespace or ' or " */
+ if (i >= size)
+ *autolink = MKDA_NOT_AUTOLINK;
+
+ else if (*autolink) {
+ j = i;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == '>' || data[i] == '\'' ||
+ data[i] == '"' || data[i] == ' ' || data[i] == '\n')
+ break;
+ else i++;
+ }
+
+ if (i >= size) return 0;
+ if (i > j && data[i] == '>') return i + 1;
+ /* one of the forbidden chars has been found */
+ *autolink = MKDA_NOT_AUTOLINK;
+ }
+
+ /* looking for sometinhg looking like a tag end */
+ while (i < size && data[i] != '>') i++;
+ if (i >= size) return 0;
+ return i + 1;
+}
+
+/* parse_inline • parses inline markdown elements */
+static void
+parse_inline(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t i = 0, end = 0;
+ uint8_t action = 0;
+ struct buf work = { 0, 0, 0, 0 };
+
+ if (rndr->work_bufs[BUFFER_SPAN].size +
+ rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+ return;
+
+ while (i < size) {
+ /* copying inactive chars into the output */
+ while (end < size && (action = rndr->active_char[data[end]]) == 0) {
+ end++;
+ }
+
+ if (rndr->cb.normal_text) {
+ work.data = data + i;
+ work.size = end - i;
+ rndr->cb.normal_text(ob, &work, rndr->opaque);
+ }
+ else
+ bufput(ob, data + i, end - i);
+
+ if (end >= size) break;
+ i = end;
+
+ end = markdown_char_ptrs[(int)action](ob, rndr, data + i, i, size - i);
+ if (!end) /* no action from the callback */
+ end = i + 1;
+ else {
+ i += end;
+ end = i;
+ }
+ }
+}
+
+/* find_emph_char • looks for the next emph uint8_t, skipping other constructs */
+static size_t
+find_emph_char(uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 1;
+
+ while (i < size) {
+ while (i < size && data[i] != c && data[i] != '`' && data[i] != '[')
+ i++;
+
+ if (i == size)
+ return 0;
+
+ if (data[i] == c)
+ return i;
+
+ /* not counting escaped chars */
+ if (i && data[i - 1] == '\\') {
+ i++; continue;
+ }
+
+ if (data[i] == '`') {
+ size_t span_nb = 0, bt;
+ size_t tmp_i = 0;
+
+ /* counting the number of opening backticks */
+ while (i < size && data[i] == '`') {
+ i++; span_nb++;
+ }
+
+ if (i >= size) return 0;
+
+ /* finding the matching closing sequence */
+ bt = 0;
+ while (i < size && bt < span_nb) {
+ if (!tmp_i && data[i] == c) tmp_i = i;
+ if (data[i] == '`') bt++;
+ else bt = 0;
+ i++;
+ }
+
+ if (i >= size) return tmp_i;
+ }
+ /* skipping a link */
+ else if (data[i] == '[') {
+ size_t tmp_i = 0;
+ uint8_t cc;
+
+ i++;
+ while (i < size && data[i] != ']') {
+ if (!tmp_i && data[i] == c) tmp_i = i;
+ i++;
+ }
+
+ i++;
+ while (i < size && (data[i] == ' ' || data[i] == '\n'))
+ i++;
+
+ if (i >= size)
+ return tmp_i;
+
+ switch (data[i]) {
+ case '[':
+ cc = ']'; break;
+
+ case '(':
+ cc = ')'; break;
+
+ default:
+ if (tmp_i)
+ return tmp_i;
+ else
+ continue;
+ }
+
+ i++;
+ while (i < size && data[i] != cc) {
+ if (!tmp_i && data[i] == c) tmp_i = i;
+ i++;
+ }
+
+ if (i >= size)
+ return tmp_i;
+
+ i++;
+ }
+ }
+
+ return 0;
+}
+
+/* parse_emph1 • parsing single emphase */
+/* closed by a symbol not preceded by whitespace and not followed by symbol */
+static size_t
+parse_emph1(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 0, len;
+ struct buf *work = 0;
+ int r;
+
+ if (!rndr->cb.emphasis) return 0;
+
+ /* skipping one symbol if coming from emph3 */
+ if (size > 1 && data[0] == c && data[1] == c) i = 1;
+
+ while (i < size) {
+ len = find_emph_char(data + i, size - i, c);
+ if (!len) return 0;
+ i += len;
+ if (i >= size) return 0;
+
+ if (data[i] == c && !_isspace(data[i - 1])) {
+
+ if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) {
+ if (!(i + 1 == size || _isspace(data[i + 1]) || ispunct(data[i + 1])))
+ continue;
+ }
+
+ work = rndr_newbuf(rndr, BUFFER_SPAN);
+ parse_inline(work, rndr, data, i);
+ r = rndr->cb.emphasis(ob, work, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return r ? i + 1 : 0;
+ }
+ }
+
+ return 0;
+}
+
+/* parse_emph2 • parsing single emphase */
+static size_t
+parse_emph2(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
+{
+ int (*render_method)(struct buf *ob, const struct buf *text, void *opaque);
+ size_t i = 0, len;
+ struct buf *work = 0;
+ int r;
+
+ render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis;
+
+ if (!render_method)
+ return 0;
+
+ while (i < size) {
+ len = find_emph_char(data + i, size - i, c);
+ if (!len) return 0;
+ i += len;
+
+ if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
+ work = rndr_newbuf(rndr, BUFFER_SPAN);
+ parse_inline(work, rndr, data, i);
+ r = render_method(ob, work, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return r ? i + 2 : 0;
+ }
+ i++;
+ }
+ return 0;
+}
+
+/* parse_emph3 • parsing single emphase */
+/* finds the first closing tag, and delegates to the other emph */
+static size_t
+parse_emph3(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c)
+{
+ size_t i = 0, len;
+ int r;
+
+ while (i < size) {
+ len = find_emph_char(data + i, size - i, c);
+ if (!len) return 0;
+ i += len;
+
+ /* skip whitespace preceded symbols */
+ if (data[i] != c || _isspace(data[i - 1]))
+ continue;
+
+ if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) {
+ /* triple symbol found */
+ struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ parse_inline(work, rndr, data, i);
+ r = rndr->cb.triple_emphasis(ob, work, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return r ? i + 3 : 0;
+
+ } else if (i + 1 < size && data[i + 1] == c) {
+ /* double symbol found, handing over to emph1 */
+ len = parse_emph1(ob, rndr, data - 2, size + 2, c);
+ if (!len) return 0;
+ else return len - 2;
+
+ } else {
+ /* single symbol found, handing over to emph2 */
+ len = parse_emph2(ob, rndr, data - 1, size + 1, c);
+ if (!len) return 0;
+ else return len - 1;
+ }
+ }
+ return 0;
+}
+
+/* char_emphasis • single and double emphasis parsing */
+static size_t
+char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ uint8_t c = data[0];
+ size_t ret;
+
+ if (size > 2 && data[1] != c) {
+ /* whitespace cannot follow an opening emphasis;
+ * strikethrough only takes two characters '~~' */
+ if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0)
+ return 0;
+
+ return ret + 1;
+ }
+
+ if (size > 3 && data[1] == c && data[2] != c) {
+ if (_isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0)
+ return 0;
+
+ return ret + 2;
+ }
+
+ if (size > 4 && data[1] == c && data[2] == c && data[3] != c) {
+ if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0)
+ return 0;
+
+ return ret + 3;
+ }
+
+ return 0;
+}
+
+
+/* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */
+static size_t
+char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ if (offset < 2 || data[-1] != ' ' || data[-2] != ' ')
+ return 0;
+
+ /* removing the last space from ob and rendering */
+ while (ob->size && ob->data[ob->size - 1] == ' ')
+ ob->size--;
+
+ return rndr->cb.linebreak(ob, rndr->opaque) ? 1 : 0;
+}
+
+
+/* char_codespan • '`' parsing a code span (assuming codespan != 0) */
+static size_t
+char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ size_t end, nb = 0, i, f_begin, f_end;
+
+ /* counting the number of backticks in the delimiter */
+ while (nb < size && data[nb] == '`')
+ nb++;
+
+ /* finding the next delimiter */
+ i = 0;
+ for (end = nb; end < size && i < nb; end++) {
+ if (data[end] == '`') i++;
+ else i = 0;
+ }
+
+ if (i < nb && end >= size)
+ return 0; /* no matching delimiter */
+
+ /* trimming outside whitespaces */
+ f_begin = nb;
+ while (f_begin < end && data[f_begin] == ' ')
+ f_begin++;
+
+ f_end = end - nb;
+ while (f_end > nb && data[f_end-1] == ' ')
+ f_end--;
+
+ /* real code span */
+ if (f_begin < f_end) {
+ struct buf work = { data + f_begin, f_end - f_begin, 0, 0 };
+ if (!rndr->cb.codespan(ob, &work, rndr->opaque))
+ end = 0;
+ } else {
+ if (!rndr->cb.codespan(ob, 0, rndr->opaque))
+ end = 0;
+ }
+
+ return end;
+}
+
+
+/* char_escape • '\\' backslash escape */
+static size_t
+char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~$";
+ struct buf work = { 0, 0, 0, 0 };
+
+ if (size > 1) {
+ if (strchr(escape_chars, data[1]) == NULL)
+ return 0;
+
+ if (rndr->cb.normal_text) {
+ work.data = data + 1;
+ work.size = 1;
+ rndr->cb.normal_text(ob, &work, rndr->opaque);
+ }
+ else bufputc(ob, data[1]);
+ } else if (size == 1) {
+ bufputc(ob, data[0]);
+ }
+
+ return 2;
+}
+
+/* char_entity • '&' escaped when it doesn't belong to an entity */
+/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
+static size_t
+char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ size_t end = 1;
+ struct buf work = { 0, 0, 0, 0 };
+
+ if (end < size && data[end] == '#')
+ end++;
+
+ while (end < size && isalnum(data[end]))
+ end++;
+
+ if (end < size && data[end] == ';')
+ end++; /* real entity */
+ else
+ return 0; /* lone '&' */
+
+ if (rndr->cb.entity) {
+ work.data = data;
+ work.size = end;
+ rndr->cb.entity(ob, &work, rndr->opaque);
+ }
+ else bufput(ob, data, end);
+
+ return end;
+}
+
+/* char_langle_tag • '<' when tags or autolinks are allowed */
+static size_t
+char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ enum mkd_autolink altype = MKDA_NOT_AUTOLINK;
+ size_t end = tag_length(data, size, &altype);
+ struct buf work = { data, end, 0, 0 };
+ int ret = 0;
+
+ if (end > 2) {
+ if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) {
+ struct buf *u_link = rndr_newbuf(rndr, BUFFER_SPAN);
+ work.data = data + 1;
+ work.size = end - 2;
+ unscape_text(u_link, &work);
+ ret = rndr->cb.autolink(ob, u_link, altype, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ }
+ else if (rndr->cb.raw_html_tag)
+ ret = rndr->cb.raw_html_tag(ob, &work, rndr->opaque);
+ }
+
+ if (!ret) return 0;
+ else return end;
+}
+
+static size_t
+char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ struct buf *link, *link_url, *link_text;
+ size_t link_len, rewind;
+
+ if (!rndr->cb.link || rndr->in_link_body)
+ return 0;
+
+ link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ if ((link_len = sd_autolink__www(&rewind, link, data, offset, size)) > 0) {
+ link_url = rndr_newbuf(rndr, BUFFER_SPAN);
+ BUFPUTSL(link_url, "http://");
+ bufput(link_url, link->data, link->size);
+
+ ob->size -= rewind;
+ if (rndr->cb.normal_text) {
+ link_text = rndr_newbuf(rndr, BUFFER_SPAN);
+ rndr->cb.normal_text(link_text, link, rndr->opaque);
+ rndr->cb.link(ob, link_url, NULL, link_text, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ } else {
+ rndr->cb.link(ob, link_url, NULL, link, rndr->opaque);
+ }
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ }
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return link_len;
+}
+
+static size_t
+char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ struct buf *link;
+ size_t link_len, rewind;
+
+ if (!rndr->cb.autolink || rndr->in_link_body)
+ return 0;
+
+ link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ if ((link_len = sd_autolink__email(&rewind, link, data, offset, size)) > 0) {
+ ob->size -= rewind;
+ rndr->cb.autolink(ob, link, MKDA_EMAIL, rndr->opaque);
+ }
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return link_len;
+}
+
+static size_t
+char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ struct buf *link;
+ size_t link_len, rewind;
+
+ if (!rndr->cb.autolink || rndr->in_link_body)
+ return 0;
+
+ link = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ if ((link_len = sd_autolink__url(&rewind, link, data, offset, size)) > 0) {
+ ob->size -= rewind;
+ rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque);
+ }
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return link_len;
+}
+
+/* char_link • '[': parsing a link or an image */
+static size_t
+char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ int is_img = (offset && data[-1] == '!'), level;
+ size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
+ struct buf *content = 0;
+ struct buf *link = 0;
+ struct buf *title = 0;
+ struct buf *u_link = 0;
+ size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size;
+ int text_has_nl = 0, ret = 0;
+ int in_title = 0, qtype = 0;
+
+ /* checking whether the correct renderer exists */
+ if ((is_img && !rndr->cb.image) || (!is_img && !rndr->cb.link))
+ goto cleanup;
+
+ /* looking for the matching closing bracket */
+ for (level = 1; i < size; i++) {
+ if (data[i] == '\n')
+ text_has_nl = 1;
+
+ else if (data[i - 1] == '\\')
+ continue;
+
+ else if (data[i] == '[')
+ level++;
+
+ else if (data[i] == ']') {
+ level--;
+ if (level <= 0)
+ break;
+ }
+ }
+
+ if (i >= size)
+ goto cleanup;
+
+ txt_e = i;
+ i++;
+
+ /* skip any amount of whitespace or newline */
+ /* (this is much more laxist than original markdown syntax) */
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ /* inline style link */
+ if (i < size && data[i] == '(') {
+ /* skipping initial whitespace */
+ i++;
+
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ link_b = i;
+
+ /* looking for link end: ' " ) */
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == ')') break;
+ else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break;
+ else i++;
+ }
+
+ if (i >= size) goto cleanup;
+ link_e = i;
+
+ /* looking for title end if present */
+ if (data[i] == '\'' || data[i] == '"') {
+ qtype = data[i];
+ in_title = 1;
+ i++;
+ title_b = i;
+
+ while (i < size) {
+ if (data[i] == '\\') i += 2;
+ else if (data[i] == qtype) {in_title = 0; i++;}
+ else if ((data[i] == ')') && !in_title) break;
+ else i++;
+ }
+
+ if (i >= size) goto cleanup;
+
+ /* skipping whitespaces after title */
+ title_e = i - 1;
+ while (title_e > title_b && _isspace(data[title_e]))
+ title_e--;
+
+ /* checking for closing quote presence */
+ if (data[title_e] != '\'' && data[title_e] != '"') {
+ title_b = title_e = 0;
+ link_e = i;
+ }
+ }
+
+ /* remove whitespace at the end of the link */
+ while (link_e > link_b && _isspace(data[link_e - 1]))
+ link_e--;
+
+ /* remove optional angle brackets around the link */
+ if (data[link_b] == '<') link_b++;
+ if (data[link_e - 1] == '>') link_e--;
+
+ /* building escaped link and title */
+ if (link_e > link_b) {
+ link = rndr_newbuf(rndr, BUFFER_SPAN);
+ bufput(link, data + link_b, link_e - link_b);
+ }
+
+ if (title_e > title_b) {
+ title = rndr_newbuf(rndr, BUFFER_SPAN);
+ bufput(title, data + title_b, title_e - title_b);
+ }
+
+ i++;
+ }
+
+ /* reference style link */
+ else if (i < size && data[i] == '[') {
+ struct buf id = { 0, 0, 0, 0 };
+ struct link_ref *lr;
+
+ /* looking for the id */
+ i++;
+ link_b = i;
+ while (i < size && data[i] != ']') i++;
+ if (i >= size) goto cleanup;
+ link_e = i;
+
+ /* finding the link_ref */
+ if (link_b == link_e) {
+ if (text_has_nl) {
+ struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN);
+ size_t j;
+
+ for (j = 1; j < txt_e; j++) {
+ if (data[j] != '\n')
+ bufputc(b, data[j]);
+ else if (data[j - 1] != ' ')
+ bufputc(b, ' ');
+ }
+
+ id.data = b->data;
+ id.size = b->size;
+ } else {
+ id.data = data + 1;
+ id.size = txt_e - 1;
+ }
+ } else {
+ id.data = data + link_b;
+ id.size = link_e - link_b;
+ }
+
+ lr = find_link_ref(rndr->refs, id.data, id.size);
+ if (!lr)
+ goto cleanup;
+
+ /* keeping link and title from link_ref */
+ link = lr->link;
+ title = lr->title;
+ i++;
+ }
+
+ /* shortcut reference style link */
+ else {
+ struct buf id = { 0, 0, 0, 0 };
+ struct link_ref *lr;
+
+ /* crafting the id */
+ if (text_has_nl) {
+ struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN);
+ size_t j;
+
+ for (j = 1; j < txt_e; j++) {
+ if (data[j] != '\n')
+ bufputc(b, data[j]);
+ else if (data[j - 1] != ' ')
+ bufputc(b, ' ');
+ }
+
+ id.data = b->data;
+ id.size = b->size;
+ } else {
+ id.data = data + 1;
+ id.size = txt_e - 1;
+ }
+
+ /* finding the link_ref */
+ lr = find_link_ref(rndr->refs, id.data, id.size);
+ if (!lr)
+ goto cleanup;
+
+ /* keeping link and title from link_ref */
+ link = lr->link;
+ title = lr->title;
+
+ /* rewinding the whitespace */
+ i = txt_e + 1;
+ }
+
+ /* building content: img alt is escaped, link content is parsed */
+ if (txt_e > 1) {
+ content = rndr_newbuf(rndr, BUFFER_SPAN);
+ if (is_img) {
+ bufput(content, data + 1, txt_e - 1);
+ } else {
+ /* disable autolinking when parsing inline the
+ * content of a link */
+ rndr->in_link_body = 1;
+ parse_inline(content, rndr, data + 1, txt_e - 1);
+ rndr->in_link_body = 0;
+ }
+ }
+
+ if (link) {
+ u_link = rndr_newbuf(rndr, BUFFER_SPAN);
+ unscape_text(u_link, link);
+ }
+
+ /* calling the relevant rendering function */
+ if (is_img) {
+ if (ob->size && ob->data[ob->size - 1] == '!')
+ ob->size -= 1;
+
+ ret = rndr->cb.image(ob, u_link, title, content, rndr->opaque);
+ } else {
+ ret = rndr->cb.link(ob, u_link, title, content, rndr->opaque);
+ }
+
+ /* cleanup */
+cleanup:
+ rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
+ return ret ? i : 0;
+}
+
+static size_t
+char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
+{
+ size_t sup_start, sup_len;
+ struct buf *sup;
+
+ if (!rndr->cb.superscript)
+ return 0;
+
+ if (size < 2)
+ return 0;
+
+ if (data[1] == '(') {
+ sup_start = sup_len = 2;
+
+ while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\')
+ sup_len++;
+
+ if (sup_len == size)
+ return 0;
+ } else {
+ sup_start = sup_len = 1;
+
+ while (sup_len < size && !_isspace(data[sup_len]))
+ sup_len++;
+ }
+
+ if (sup_len - sup_start == 0)
+ return (sup_start == 2) ? 3 : 0;
+
+ sup = rndr_newbuf(rndr, BUFFER_SPAN);
+ parse_inline(sup, rndr, data + sup_start, sup_len - sup_start);
+ rndr->cb.superscript(ob, sup, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+
+ return (sup_start == 2) ? sup_len + 1 : sup_len;
+}
+
+/*********************************
+ * BLOCK-LEVEL PARSING FUNCTIONS *
+ *********************************/
+
+/* is_empty • returns the line length when it is empty, 0 otherwise */
+static size_t
+is_empty(uint8_t *data, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size && data[i] != '\n'; i++)
+ if (data[i] != ' ')
+ return 0;
+
+ return i + 1;
+}
+
+/* is_hrule • returns whether a line is a horizontal rule */
+static int
+is_hrule(uint8_t *data, size_t size)
+{
+ size_t i = 0, n = 0;
+ uint8_t c;
+
+ /* skipping initial spaces */
+ if (size < 3) return 0;
+ if (data[0] == ' ') { i++;
+ if (data[1] == ' ') { i++;
+ if (data[2] == ' ') { i++; } } }
+
+ /* looking at the hrule uint8_t */
+ if (i + 2 >= size
+ || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
+ return 0;
+ c = data[i];
+
+ /* the whole line must be the char or whitespace */
+ while (i < size && data[i] != '\n') {
+ if (data[i] == c) n++;
+ else if (data[i] != ' ')
+ return 0;
+
+ i++;
+ }
+
+ return n >= 3;
+}
+
+/* check if a line begins with a code fence; return the
+ * width of the code fence */
+static size_t
+prefix_codefence(uint8_t *data, size_t size)
+{
+ size_t i = 0, n = 0;
+ uint8_t c;
+
+ /* skipping initial spaces */
+ if (size < 3) return 0;
+ if (data[0] == ' ') { i++;
+ if (data[1] == ' ') { i++;
+ if (data[2] == ' ') { i++; } } }
+
+ /* looking at the hrule uint8_t */
+ if (i + 2 >= size || !(data[i] == '~' || data[i] == '`'))
+ return 0;
+
+ c = data[i];
+
+ /* the whole line must be the uint8_t or whitespace */
+ while (i < size && data[i] == c) {
+ n++; i++;
+ }
+
+ if (n < 3)
+ return 0;
+
+ return i;
+}
+
+/* check if a line is a code fence; return its size if it is */
+static size_t
+is_codefence(uint8_t *data, size_t size, struct buf *syntax)
+{
+ size_t i = 0, syn_len = 0;
+ uint8_t *syn_start;
+
+ i = prefix_codefence(data, size);
+ if (i == 0)
+ return 0;
+
+ while (i < size && data[i] == ' ')
+ i++;
+
+ syn_start = data + i;
+
+ if (i < size && data[i] == '{') {
+ i++; syn_start++;
+
+ while (i < size && data[i] != '}' && data[i] != '\n') {
+ syn_len++; i++;
+ }
+
+ if (i == size || data[i] != '}')
+ return 0;
+
+ /* strip all whitespace at the beginning and the end
+ * of the {} block */
+ while (syn_len > 0 && _isspace(syn_start[0])) {
+ syn_start++; syn_len--;
+ }
+
+ while (syn_len > 0 && _isspace(syn_start[syn_len - 1]))
+ syn_len--;
+
+ i++;
+ } else {
+ while (i < size && !_isspace(data[i])) {
+ syn_len++; i++;
+ }
+ }
+
+ if (syntax) {
+ syntax->data = syn_start;
+ syntax->size = syn_len;
+ }
+
+ while (i < size && data[i] != '\n') {
+ if (!_isspace(data[i]))
+ return 0;
+
+ i++;
+ }
+
+ return i + 1;
+}
+
+/* is_atxheader • returns whether the line is a hash-prefixed header */
+static int
+is_atxheader(struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ if (data[0] != '#')
+ return 0;
+
+ if (rndr->ext_flags & MKDEXT_SPACE_HEADERS) {
+ size_t level = 0;
+
+ while (level < size && level < 6 && data[level] == '#')
+ level++;
+
+ if (level < size && data[level] != ' ')
+ return 0;
+ }
+
+ return 1;
+}
+
+/* is_headerline • returns whether the line is a setext-style hdr underline */
+static int
+is_headerline(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+
+ /* test of level 1 header */
+ if (data[i] == '=') {
+ for (i = 1; i < size && data[i] == '='; i++);
+ while (i < size && data[i] == ' ') i++;
+ return (i >= size || data[i] == '\n') ? 1 : 0; }
+
+ /* test of level 2 header */
+ if (data[i] == '-') {
+ for (i = 1; i < size && data[i] == '-'; i++);
+ while (i < size && data[i] == ' ') i++;
+ return (i >= size || data[i] == '\n') ? 2 : 0; }
+
+ return 0;
+}
+
+static int
+is_next_headerline(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+
+ while (i < size && data[i] != '\n')
+ i++;
+
+ if (++i >= size)
+ return 0;
+
+ return is_headerline(data + i, size - i);
+}
+
+/* prefix_quote • returns blockquote prefix length */
+static size_t
+prefix_quote(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+
+ if (i < size && data[i] == '>') {
+ if (i + 1 < size && data[i + 1] == ' ')
+ return i + 2;
+
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/* prefix_code • returns prefix length for block code*/
+static size_t
+prefix_code(uint8_t *data, size_t size)
+{
+ if (size > 3 && data[0] == ' ' && data[1] == ' '
+ && data[2] == ' ' && data[3] == ' ') return 4;
+
+ return 0;
+}
+
+/* prefix_oli • returns ordered list item prefix */
+static size_t
+prefix_oli(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+
+ if (i >= size || data[i] < '0' || data[i] > '9')
+ return 0;
+
+ while (i < size && data[i] >= '0' && data[i] <= '9')
+ i++;
+
+ if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ')
+ return 0;
+
+ if (is_next_headerline(data + i, size - i))
+ return 0;
+
+ return i + 2;
+}
+
+/* prefix_uli • returns ordered list item prefix */
+static size_t
+prefix_uli(uint8_t *data, size_t size)
+{
+ size_t i = 0;
+
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+ if (i < size && data[i] == ' ') i++;
+
+ if (i + 1 >= size ||
+ (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
+ data[i + 1] != ' ')
+ return 0;
+
+ if (is_next_headerline(data + i, size - i))
+ return 0;
+
+ return i + 2;
+}
+
+
+/* parse_block • parsing of one block, returning next uint8_t to parse */
+static void parse_block(struct buf *ob, struct sd_markdown *rndr,
+ uint8_t *data, size_t size);
+
+
+/* parse_blockquote • handles parsing of a blockquote fragment */
+static size_t
+parse_blockquote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t beg, end = 0, pre, work_size = 0;
+ uint8_t *work_data = 0;
+ struct buf *out = 0;
+
+ out = rndr_newbuf(rndr, BUFFER_BLOCK);
+ beg = 0;
+ while (beg < size) {
+ for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);
+
+ pre = prefix_quote(data + beg, end - beg);
+
+ if (pre)
+ beg += pre; /* skipping prefix */
+
+ /* empty line followed by non-quote line */
+ else if (is_empty(data + beg, end - beg) &&
+ (end >= size || (prefix_quote(data + end, size - end) == 0 &&
+ !is_empty(data + end, size - end))))
+ break;
+
+ if (beg < end) { /* copy into the in-place working buffer */
+ /* bufput(work, data + beg, end - beg); */
+ if (!work_data)
+ work_data = data + beg;
+ else if (data + beg != work_data + work_size)
+ memmove(work_data + work_size, data + beg, end - beg);
+ work_size += end - beg;
+ }
+ beg = end;
+ }
+
+ parse_block(out, rndr, work_data, work_size);
+ if (rndr->cb.blockquote)
+ rndr->cb.blockquote(ob, out, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ return end;
+}
+
+static size_t
+parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render);
+
+/* parse_blockquote • handles parsing of a regular paragraph */
+static size_t
+parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t i = 0, end = 0;
+ int level = 0;
+ struct buf work = { data, 0, 0, 0 };
+
+ while (i < size) {
+ for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */;
+
+ if (is_empty(data + i, size - i))
+ break;
+
+ if ((level = is_headerline(data + i, size - i)) != 0)
+ break;
+
+ if (is_atxheader(rndr, data + i, size - i) ||
+ is_hrule(data + i, size - i) ||
+ prefix_quote(data + i, size - i)) {
+ end = i;
+ break;
+ }
+
+ /*
+ * Early termination of a paragraph with the same logic
+ * as Markdown 1.0.0. If this logic is applied, the
+ * Markdown 1.0.3 test suite won't pass cleanly
+ *
+ * :: If the first character in a new line is not a letter,
+ * let's check to see if there's some kind of block starting
+ * here
+ */
+ if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !isalnum(data[i])) {
+ if (prefix_oli(data + i, size - i) ||
+ prefix_uli(data + i, size - i)) {
+ end = i;
+ break;
+ }
+
+ /* see if an html block starts here */
+ if (data[i] == '<' && rndr->cb.blockhtml &&
+ parse_htmlblock(ob, rndr, data + i, size - i, 0)) {
+ end = i;
+ break;
+ }
+
+ /* see if a code fence starts here */
+ if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+ is_codefence(data + i, size - i, NULL) != 0) {
+ end = i;
+ break;
+ }
+ }
+
+ i = end;
+ }
+
+ work.size = i;
+ while (work.size && data[work.size - 1] == '\n')
+ work.size--;
+
+ if (!level) {
+ struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+ parse_inline(tmp, rndr, work.data, work.size);
+ if (rndr->cb.paragraph)
+ rndr->cb.paragraph(ob, tmp, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ } else {
+ struct buf *header_work;
+
+ if (work.size) {
+ size_t beg;
+ i = work.size;
+ work.size -= 1;
+
+ while (work.size && data[work.size] != '\n')
+ work.size -= 1;
+
+ beg = work.size + 1;
+ while (work.size && data[work.size - 1] == '\n')
+ work.size -= 1;
+
+ if (work.size > 0) {
+ struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK);
+ parse_inline(tmp, rndr, work.data, work.size);
+
+ if (rndr->cb.paragraph)
+ rndr->cb.paragraph(ob, tmp, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ work.data += beg;
+ work.size = i - beg;
+ }
+ else work.size = i;
+ }
+
+ header_work = rndr_newbuf(rndr, BUFFER_SPAN);
+ parse_inline(header_work, rndr, work.data, work.size);
+
+ if (rndr->cb.header)
+ rndr->cb.header(ob, header_work, (int)level, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ }
+
+ return end;
+}
+
+/* parse_fencedcode • handles parsing of a block-level code fragment */
+static size_t
+parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t beg, end;
+ struct buf *work = 0;
+ struct buf lang = { 0, 0, 0, 0 };
+
+ beg = is_codefence(data, size, &lang);
+ if (beg == 0) return 0;
+
+ work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+ while (beg < size) {
+ size_t fence_end;
+ struct buf fence_trail = { 0, 0, 0, 0 };
+
+ fence_end = is_codefence(data + beg, size - beg, &fence_trail);
+ if (fence_end != 0 && fence_trail.size == 0) {
+ beg += fence_end;
+ break;
+ }
+
+ for (end = beg + 1; end < size && data[end - 1] != '\n'; end++);
+
+ if (beg < end) {
+ /* verbatim copy to the working buffer,
+ escaping entities */
+ if (is_empty(data + beg, end - beg))
+ bufputc(work, '\n');
+ else bufput(work, data + beg, end - beg);
+ }
+ beg = end;
+ }
+
+ if (work->size && work->data[work->size - 1] != '\n')
+ bufputc(work, '\n');
+
+ if (rndr->cb.blockcode)
+ rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ return beg;
+}
+
+static size_t
+parse_blockcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t beg, end, pre;
+ struct buf *work = 0;
+
+ work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+ beg = 0;
+ while (beg < size) {
+ for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};
+ pre = prefix_code(data + beg, end - beg);
+
+ if (pre)
+ beg += pre; /* skipping prefix */
+ else if (!is_empty(data + beg, end - beg))
+ /* non-empty non-prefixed line breaks the pre */
+ break;
+
+ if (beg < end) {
+ /* verbatim copy to the working buffer,
+ escaping entities */
+ if (is_empty(data + beg, end - beg))
+ bufputc(work, '\n');
+ else bufput(work, data + beg, end - beg);
+ }
+ beg = end;
+ }
+
+ while (work->size && work->data[work->size - 1] == '\n')
+ work->size -= 1;
+
+ bufputc(work, '\n');
+
+ if (rndr->cb.blockcode)
+ rndr->cb.blockcode(ob, work, NULL, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ return beg;
+}
+
+/* parse_listitem • parsing of a single list item */
+/* assuming initial prefix is already removed */
+static size_t
+parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int *flags)
+{
+ struct buf *work = 0, *inter = 0;
+ size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
+ int in_empty = 0, has_inside_empty = 0, in_fence = 0;
+
+ /* keeping track of the first indentation prefix */
+ while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
+ orgpre++;
+
+ beg = prefix_uli(data, size);
+ if (!beg)
+ beg = prefix_oli(data, size);
+
+ if (!beg)
+ return 0;
+
+ /* skipping to the beginning of the following line */
+ end = beg;
+ while (end < size && data[end - 1] != '\n')
+ end++;
+
+ /* getting working buffers */
+ work = rndr_newbuf(rndr, BUFFER_SPAN);
+ inter = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ /* putting the first line into the working buffer */
+ bufput(work, data + beg, end - beg);
+ beg = end;
+
+ /* process the following lines */
+ while (beg < size) {
+ size_t has_next_uli = 0, has_next_oli = 0;
+
+ end++;
+
+ while (end < size && data[end - 1] != '\n')
+ end++;
+
+ /* process an empty line */
+ if (is_empty(data + beg, end - beg)) {
+ in_empty = 1;
+ beg = end;
+ continue;
+ }
+
+ /* calculating the indentation */
+ i = 0;
+ while (i < 4 && beg + i < end && data[beg + i] == ' ')
+ i++;
+
+ pre = i;
+
+ if (rndr->ext_flags & MKDEXT_FENCED_CODE) {
+ if (is_codefence(data + beg + i, end - beg - i, NULL) != 0)
+ in_fence = !in_fence;
+ }
+
+ /* Only check for new list items if we are **not** inside
+ * a fenced code block */
+ if (!in_fence) {
+ has_next_uli = prefix_uli(data + beg + i, end - beg - i);
+ has_next_oli = prefix_oli(data + beg + i, end - beg - i);
+ }
+
+ /* checking for ul/ol switch */
+ if (in_empty && (
+ ((*flags & MKD_LIST_ORDERED) && has_next_uli) ||
+ (!(*flags & MKD_LIST_ORDERED) && has_next_oli))){
+ *flags |= MKD_LI_END;
+ break; /* the following item must have same list type */
+ }
+
+ /* checking for a new item */
+ if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) {
+ if (in_empty)
+ has_inside_empty = 1;
+
+ if (pre == orgpre) /* the following item must have */
+ break; /* the same indentation */
+
+ if (!sublist)
+ sublist = work->size;
+ }
+ /* joining only indented stuff after empty lines;
+ * note that now we only require 1 space of indentation
+ * to continue a list */
+ else if (in_empty && pre == 0) {
+ *flags |= MKD_LI_END;
+ break;
+ }
+ else if (in_empty) {
+ bufputc(work, '\n');
+ has_inside_empty = 1;
+ }
+
+ in_empty = 0;
+
+ /* adding the line without prefix into the working buffer */
+ bufput(work, data + beg + i, end - beg - i);
+ beg = end;
+ }
+
+ /* render of li contents */
+ if (has_inside_empty)
+ *flags |= MKD_LI_BLOCK;
+
+ if (*flags & MKD_LI_BLOCK) {
+ /* intermediate render of block li */
+ if (sublist && sublist < work->size) {
+ parse_block(inter, rndr, work->data, sublist);
+ parse_block(inter, rndr, work->data + sublist, work->size - sublist);
+ }
+ else
+ parse_block(inter, rndr, work->data, work->size);
+ } else {
+ /* intermediate render of inline li */
+ if (sublist && sublist < work->size) {
+ parse_inline(inter, rndr, work->data, sublist);
+ parse_block(inter, rndr, work->data + sublist, work->size - sublist);
+ }
+ else
+ parse_inline(inter, rndr, work->data, work->size);
+ }
+
+ /* render of li itself */
+ if (rndr->cb.listitem)
+ rndr->cb.listitem(ob, inter, *flags, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ return beg;
+}
+
+
+/* parse_list • parsing ordered or unordered list block */
+static size_t
+parse_list(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int flags)
+{
+ struct buf *work = 0;
+ size_t i = 0, j;
+
+ work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+ while (i < size) {
+ j = parse_listitem(work, rndr, data + i, size - i, &flags);
+ i += j;
+
+ if (!j || (flags & MKD_LI_END))
+ break;
+ }
+
+ if (rndr->cb.list)
+ rndr->cb.list(ob, work, flags, rndr->opaque);
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ return i;
+}
+
+/* parse_atxheader • parsing of atx-style headers */
+static size_t
+parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t level = 0;
+ size_t i, end, skip;
+
+ while (level < size && level < 6 && data[level] == '#')
+ level++;
+
+ for (i = level; i < size && data[i] == ' '; i++);
+
+ for (end = i; end < size && data[end] != '\n'; end++);
+ skip = end;
+
+ while (end && data[end - 1] == '#')
+ end--;
+
+ while (end && data[end - 1] == ' ')
+ end--;
+
+ if (end > i) {
+ struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ parse_inline(work, rndr, data + i, end - i);
+
+ if (rndr->cb.header)
+ rndr->cb.header(ob, work, (int)level, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ }
+
+ return skip;
+}
+
+
+/* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
+/* returns the length on match, 0 otherwise */
+static size_t
+htmlblock_end_tag(
+ const char *tag,
+ size_t tag_len,
+ struct sd_markdown *rndr,
+ uint8_t *data,
+ size_t size)
+{
+ size_t i, w;
+
+ /* checking if tag is a match */
+ if (tag_len + 3 >= size ||
+ strncasecmp((char *)data + 2, tag, tag_len) != 0 ||
+ data[tag_len + 2] != '>')
+ return 0;
+
+ /* checking white lines */
+ i = tag_len + 3;
+ w = 0;
+ if (i < size && (w = is_empty(data + i, size - i)) == 0)
+ return 0; /* non-blank after tag */
+ i += w;
+ w = 0;
+
+ if (i < size)
+ w = is_empty(data + i, size - i);
+
+ return i + w;
+}
+
+static size_t
+htmlblock_end(const char *curtag,
+ struct sd_markdown *rndr,
+ uint8_t *data,
+ size_t size,
+ int start_of_line)
+{
+ size_t tag_size = strlen(curtag);
+ size_t i = 1, end_tag;
+ int block_lines = 0;
+
+ while (i < size) {
+ i++;
+ while (i < size && !(data[i - 1] == '<' && data[i] == '/')) {
+ if (data[i] == '\n')
+ block_lines++;
+
+ i++;
+ }
+
+ /* If we are only looking for unindented tags, skip the tag
+ * if it doesn't follow a newline.
+ *
+ * The only exception to this is if the tag is still on the
+ * initial line; in that case it still counts as a closing
+ * tag
+ */
+ if (start_of_line && block_lines > 0 && data[i - 2] != '\n')
+ continue;
+
+ if (i + 2 + tag_size >= size)
+ break;
+
+ end_tag = htmlblock_end_tag(curtag, tag_size, rndr, data + i - 1, size - i + 1);
+ if (end_tag)
+ return i + end_tag - 1;
+ }
+
+ return 0;
+}
+
+
+/* parse_htmlblock • parsing of inline HTML block */
+static size_t
+parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render)
+{
+ size_t i, j = 0, tag_end;
+ const char *curtag = NULL;
+ struct buf work = { data, 0, 0, 0 };
+
+ /* identification of the opening tag */
+ if (size < 2 || data[0] != '<')
+ return 0;
+
+ i = 1;
+ while (i < size && data[i] != '>' && data[i] != ' ')
+ i++;
+
+ if (i < size)
+ curtag = find_block_tag((char *)data + 1, (int)i - 1);
+
+ /* handling of special cases */
+ if (!curtag) {
+
+ /* HTML comment, laxist form */
+ if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
+ i = 5;
+
+ while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>'))
+ i++;
+
+ i++;
+
+ if (i < size)
+ j = is_empty(data + i, size - i);
+
+ if (j) {
+ work.size = i + j;
+ if (do_render && rndr->cb.blockhtml)
+ rndr->cb.blockhtml(ob, &work, rndr->opaque);
+ return work.size;
+ }
+ }
+
+ /* HR, which is the only self-closing block tag considered */
+ if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
+ i = 3;
+ while (i < size && data[i] != '>')
+ i++;
+
+ if (i + 1 < size) {
+ i++;
+ j = is_empty(data + i, size - i);
+ if (j) {
+ work.size = i + j;
+ if (do_render && rndr->cb.blockhtml)
+ rndr->cb.blockhtml(ob, &work, rndr->opaque);
+ return work.size;
+ }
+ }
+ }
+
+ /* no special case recognised */
+ return 0;
+ }
+
+ /* looking for an unindented matching closing tag */
+ /* followed by a blank line */
+ tag_end = htmlblock_end(curtag, rndr, data, size, 1);
+
+ /* if not found, trying a second pass looking for indented match */
+ /* but not if tag is "ins" or "del" (following original Markdown.pl) */
+ if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0) {
+ tag_end = htmlblock_end(curtag, rndr, data, size, 0);
+ }
+
+ if (!tag_end)
+ return 0;
+
+ /* the end of the block has been found */
+ work.size = tag_end;
+ if (do_render && rndr->cb.blockhtml)
+ rndr->cb.blockhtml(ob, &work, rndr->opaque);
+
+ return tag_end;
+}
+
+static void
+parse_table_row(
+ struct buf *ob,
+ struct sd_markdown *rndr,
+ uint8_t *data,
+ size_t size,
+ size_t columns,
+ int *col_data,
+ int header_flag)
+{
+ size_t i = 0, col;
+ struct buf *row_work = 0;
+
+ if (!rndr->cb.table_cell || !rndr->cb.table_row)
+ return;
+
+ row_work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ if (i < size && data[i] == '|')
+ i++;
+
+ for (col = 0; col < columns && i < size; ++col) {
+ size_t cell_start, cell_end;
+ struct buf *cell_work;
+
+ cell_work = rndr_newbuf(rndr, BUFFER_SPAN);
+
+ while (i < size && _isspace(data[i]))
+ i++;
+
+ cell_start = i;
+
+ while (i < size && data[i] != '|')
+ i++;
+
+ cell_end = i - 1;
+
+ while (cell_end > cell_start && _isspace(data[cell_end]))
+ cell_end--;
+
+ parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
+ rndr->cb.table_cell(row_work, cell_work, col_data[col] | header_flag, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ i++;
+ }
+
+ for (; col < columns; ++col) {
+ struct buf empty_cell = { 0, 0, 0, 0 };
+ rndr->cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->opaque);
+ }
+
+ rndr->cb.table_row(ob, row_work, rndr->opaque);
+
+ rndr_popbuf(rndr, BUFFER_SPAN);
+}
+
+static size_t
+parse_table_header(
+ struct buf *ob,
+ struct sd_markdown *rndr,
+ uint8_t *data,
+ size_t size,
+ size_t *columns,
+ int **column_data)
+{
+ int pipes;
+ size_t i = 0, col, header_end, under_end;
+
+ pipes = 0;
+ while (i < size && data[i] != '\n')
+ if (data[i++] == '|')
+ pipes++;
+
+ if (i == size || pipes == 0)
+ return 0;
+
+ header_end = i;
+
+ while (header_end > 0 && _isspace(data[header_end - 1]))
+ header_end--;
+
+ if (data[0] == '|')
+ pipes--;
+
+ if (header_end && data[header_end - 1] == '|')
+ pipes--;
+
+ *columns = pipes + 1;
+ *column_data = calloc(*columns, sizeof(int));
+
+ /* Parse the header underline */
+ i++;
+ if (i < size && data[i] == '|')
+ i++;
+
+ under_end = i;
+ while (under_end < size && data[under_end] != '\n')
+ under_end++;
+
+ for (col = 0; col < *columns && i < under_end; ++col) {
+ size_t dashes = 0;
+
+ while (i < under_end && data[i] == ' ')
+ i++;
+
+ if (data[i] == ':') {
+ i++; (*column_data)[col] |= MKD_TABLE_ALIGN_L;
+ dashes++;
+ }
+
+ while (i < under_end && data[i] == '-') {
+ i++; dashes++;
+ }
+
+ if (i < under_end && data[i] == ':') {
+ i++; (*column_data)[col] |= MKD_TABLE_ALIGN_R;
+ dashes++;
+ }
+
+ while (i < under_end && data[i] == ' ')
+ i++;
+
+ if (i < under_end && data[i] != '|')
+ break;
+
+ if (dashes < 3)
+ break;
+
+ i++;
+ }
+
+ if (col < *columns)
+ return 0;
+
+ parse_table_row(
+ ob, rndr, data,
+ header_end,
+ *columns,
+ *column_data,
+ MKD_TABLE_HEADER
+ );
+
+ return under_end + 1;
+}
+
+static size_t
+parse_table(
+ struct buf *ob,
+ struct sd_markdown *rndr,
+ uint8_t *data,
+ size_t size)
+{
+ size_t i;
+
+ struct buf *header_work = 0;
+ struct buf *body_work = 0;
+
+ size_t columns;
+ int *col_data = NULL;
+
+ header_work = rndr_newbuf(rndr, BUFFER_SPAN);
+ body_work = rndr_newbuf(rndr, BUFFER_BLOCK);
+
+ i = parse_table_header(header_work, rndr, data, size, &columns, &col_data);
+ if (i > 0) {
+
+ while (i < size) {
+ size_t row_start;
+ int pipes = 0;
+
+ row_start = i;
+
+ while (i < size && data[i] != '\n')
+ if (data[i++] == '|')
+ pipes++;
+
+ if (pipes == 0 || i == size) {
+ i = row_start;
+ break;
+ }
+
+ parse_table_row(
+ body_work,
+ rndr,
+ data + row_start,
+ i - row_start,
+ columns,
+ col_data, 0
+ );
+
+ i++;
+ }
+
+ if (rndr->cb.table)
+ rndr->cb.table(ob, header_work, body_work, rndr->opaque);
+ }
+
+ free(col_data);
+ rndr_popbuf(rndr, BUFFER_SPAN);
+ rndr_popbuf(rndr, BUFFER_BLOCK);
+ return i;
+}
+
+/* parse_block • parsing of one block, returning next uint8_t to parse */
+static void
+parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
+{
+ size_t beg, end, i;
+ uint8_t *txt_data;
+ beg = 0;
+
+ if (rndr->work_bufs[BUFFER_SPAN].size +
+ rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting)
+ return;
+
+ while (beg < size) {
+ txt_data = data + beg;
+ end = size - beg;
+
+ if (is_atxheader(rndr, txt_data, end))
+ beg += parse_atxheader(ob, rndr, txt_data, end);
+
+ else if (data[beg] == '<' && rndr->cb.blockhtml &&
+ (i = parse_htmlblock(ob, rndr, txt_data, end, 1)) != 0)
+ beg += i;
+
+ else if ((i = is_empty(txt_data, end)) != 0)
+ beg += i;
+
+ else if (is_hrule(txt_data, end)) {
+ if (rndr->cb.hrule)
+ rndr->cb.hrule(ob, rndr->opaque);
+
+ while (beg < size && data[beg] != '\n')
+ beg++;
+
+ beg++;
+ }
+
+ else if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
+ (i = parse_fencedcode(ob, rndr, txt_data, end)) != 0)
+ beg += i;
+
+ else if ((rndr->ext_flags & MKDEXT_TABLES) != 0 &&
+ (i = parse_table(ob, rndr, txt_data, end)) != 0)
+ beg += i;
+
+ else if (prefix_quote(txt_data, end))
+ beg += parse_blockquote(ob, rndr, txt_data, end);
+
+ else if (prefix_code(txt_data, end))
+ beg += parse_blockcode(ob, rndr, txt_data, end);
+
+ else if (prefix_uli(txt_data, end))
+ beg += parse_list(ob, rndr, txt_data, end, 0);
+
+ else if (prefix_oli(txt_data, end))
+ beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);
+
+ else
+ beg += parse_paragraph(ob, rndr, txt_data, end);
+ }
+}
+
+
+
+/*********************
+ * REFERENCE PARSING *
+ *********************/
+
+/* is_ref • returns whether a line is a reference or not */
+static int
+is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
+{
+/* int n; */
+ size_t i = 0;
+ size_t id_offset, id_end;
+ size_t link_offset, link_end;
+ size_t title_offset, title_end;
+ size_t line_end;
+
+ /* up to 3 optional leading spaces */
+ if (beg + 3 >= end) return 0;
+ if (data[beg] == ' ') { i = 1;
+ if (data[beg + 1] == ' ') { i = 2;
+ if (data[beg + 2] == ' ') { i = 3;
+ if (data[beg + 3] == ' ') return 0; } } }
+ i += beg;
+
+ /* id part: anything but a newline between brackets */
+ if (data[i] != '[') return 0;
+ i++;
+ id_offset = i;
+ while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
+ i++;
+ if (i >= end || data[i] != ']') return 0;
+ id_end = i;
+
+ /* spacer: colon (space | tab)* newline? (space | tab)* */
+ i++;
+ if (i >= end || data[i] != ':') return 0;
+ i++;
+ while (i < end && data[i] == ' ') i++;
+ if (i < end && (data[i] == '\n' || data[i] == '\r')) {
+ i++;
+ if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
+ while (i < end && data[i] == ' ') i++;
+ if (i >= end) return 0;
+
+ /* link: whitespace-free sequence, optionally between angle brackets */
+ if (data[i] == '<')
+ i++;
+
+ link_offset = i;
+
+ while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r')
+ i++;
+
+ if (data[i - 1] == '>') link_end = i - 1;
+ else link_end = i;
+
+ /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
+ while (i < end && data[i] == ' ') i++;
+ if (i < end && data[i] != '\n' && data[i] != '\r'
+ && data[i] != '\'' && data[i] != '"' && data[i] != '(')
+ return 0;
+ line_end = 0;
+ /* computing end-of-line */
+ if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i;
+ if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
+ line_end = i + 1;
+
+ /* optional (space|tab)* spacer after a newline */
+ if (line_end) {
+ i = line_end + 1;
+ while (i < end && data[i] == ' ') i++; }
+
+ /* optional title: any non-newline sequence enclosed in '"()
+ alone on its line */
+ title_offset = title_end = 0;
+ if (i + 1 < end
+ && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
+ i++;
+ title_offset = i;
+ /* looking for EOL */
+ while (i < end && data[i] != '\n' && data[i] != '\r') i++;
+ if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
+ title_end = i + 1;
+ else title_end = i;
+ /* stepping back */
+ i -= 1;
+ while (i > title_offset && data[i] == ' ')
+ i -= 1;
+ if (i > title_offset
+ && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
+ line_end = title_end;
+ title_end = i; } }
+
+ if (!line_end || link_end == link_offset)
+ return 0; /* garbage after the link empty link */
+
+ /* a valid ref has been found, filling-in return structures */
+ if (last)
+ *last = line_end;
+
+ if (refs) {
+ struct link_ref *ref;
+
+ ref = add_link_ref(refs, data + id_offset, id_end - id_offset);
+ if (!ref)
+ return 0;
+
+ ref->link = bufnew(link_end - link_offset);
+ bufput(ref->link, data + link_offset, link_end - link_offset);
+
+ if (title_end > title_offset) {
+ ref->title = bufnew(title_end - title_offset);
+ bufput(ref->title, data + title_offset, title_end - title_offset);
+ }
+ }
+
+ return 1;
+}
+
+static void expand_tabs(struct buf *ob, const uint8_t *line, size_t size)
+{
+ size_t i = 0, tab = 0;
+
+ while (i < size) {
+ size_t org = i;
+
+ while (i < size && line[i] != '\t') {
+ i++; tab++;
+ }
+
+ if (i > org)
+ bufput(ob, line + org, i - org);
+
+ if (i >= size)
+ break;
+
+ do {
+ bufputc(ob, ' '); tab++;
+ } while (tab % 4);
+
+ i++;
+ }
+}
+
+/**********************
+ * EXPORTED FUNCTIONS *
+ **********************/
+
+struct sd_markdown *
+sd_markdown_new(
+ unsigned int extensions,
+ size_t max_nesting,
+ const struct sd_callbacks *callbacks,
+ void *opaque)
+{
+ struct sd_markdown *md = NULL;
+
+ assert(max_nesting > 0 && callbacks);
+
+ md = malloc(sizeof(struct sd_markdown));
+ if (!md)
+ return NULL;
+
+ memcpy(&md->cb, callbacks, sizeof(struct sd_callbacks));
+
+ stack_init(&md->work_bufs[BUFFER_BLOCK], 4);
+ stack_init(&md->work_bufs[BUFFER_SPAN], 8);
+
+ memset(md->active_char, 0x0, 256);
+
+ if (md->cb.emphasis || md->cb.double_emphasis || md->cb.triple_emphasis) {
+ md->active_char['*'] = MD_CHAR_EMPHASIS;
+ md->active_char['_'] = MD_CHAR_EMPHASIS;
+ if (extensions & MKDEXT_STRIKETHROUGH)
+ md->active_char['~'] = MD_CHAR_EMPHASIS;
+ }
+
+ if (md->cb.codespan)
+ md->active_char['`'] = MD_CHAR_CODESPAN;
+
+ if (md->cb.linebreak)
+ md->active_char['\n'] = MD_CHAR_LINEBREAK;
+
+ if (md->cb.image || md->cb.link)
+ md->active_char['['] = MD_CHAR_LINK;
+
+ md->active_char['<'] = MD_CHAR_LANGLE;
+ md->active_char['\\'] = MD_CHAR_ESCAPE;
+ md->active_char['&'] = MD_CHAR_ENTITITY;
+
+ if (extensions & MKDEXT_AUTOLINK) {
+ md->active_char[':'] = MD_CHAR_AUTOLINK_URL;
+ md->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL;
+ md->active_char['w'] = MD_CHAR_AUTOLINK_WWW;
+ }
+
+ if (extensions & MKDEXT_SUPERSCRIPT)
+ md->active_char['^'] = MD_CHAR_SUPERSCRIPT;
+
+ /* Extension data */
+ md->ext_flags = extensions;
+ md->opaque = opaque;
+ md->max_nesting = max_nesting;
+ md->in_link_body = 0;
+
+ return md;
+}
+
+void
+sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md)
+{
+#define MARKDOWN_GROW(x) ((x) + ((x) >> 1))
+ static const char UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
+
+ struct buf *text;
+ size_t beg, end;
+
+ text = bufnew(64);
+ if (!text)
+ return;
+
+ /* Preallocate enough space for our buffer to avoid expanding while copying */
+ bufgrow(text, doc_size);
+
+ /* reset the references table */
+ memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
+
+ /* first pass: looking for references, copying everything else */
+ beg = 0;
+
+ /* Skip a possible UTF-8 BOM, even though the Unicode standard
+ * discourages having these in UTF-8 documents */
+ if (doc_size >= 3 && memcmp(document, UTF8_BOM, 3) == 0)
+ beg += 3;
+
+ while (beg < doc_size) /* iterating over lines */
+ if (is_ref(document, beg, doc_size, &end, md->refs))
+ beg = end;
+ else { /* skipping to the next line */
+ end = beg;
+ while (end < doc_size && document[end] != '\n' && document[end] != '\r')
+ end++;
+
+ /* adding the line body if present */
+ if (end > beg)
+ expand_tabs(text, document + beg, end - beg);
+
+ while (end < doc_size && (document[end] == '\n' || document[end] == '\r')) {
+ /* add one \n per newline */
+ if (document[end] == '\n' || (end + 1 < doc_size && document[end + 1] != '\n'))
+ bufputc(text, '\n');
+ end++;
+ }
+
+ beg = end;
+ }
+
+ /* pre-grow the output buffer to minimize allocations */
+ bufgrow(ob, MARKDOWN_GROW(text->size));
+
+ /* second pass: actual rendering */
+ if (md->cb.doc_header)
+ md->cb.doc_header(ob, md->opaque);
+
+ if (text->size) {
+ /* adding a final newline if not already present */
+ if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r')
+ bufputc(text, '\n');
+
+ parse_block(ob, md, text->data, text->size);
+ }
+
+ if (md->cb.doc_footer)
+ md->cb.doc_footer(ob, md->opaque);
+
+ /* clean-up */
+ bufrelease(text);
+ free_link_refs(md->refs);
+
+ assert(md->work_bufs[BUFFER_SPAN].size == 0);
+ assert(md->work_bufs[BUFFER_BLOCK].size == 0);
+}
+
+void
+sd_markdown_free(struct sd_markdown *md)
+{
+ size_t i;
+
+ for (i = 0; i < (size_t)md->work_bufs[BUFFER_SPAN].asize; ++i)
+ bufrelease(md->work_bufs[BUFFER_SPAN].item[i]);
+
+ for (i = 0; i < (size_t)md->work_bufs[BUFFER_BLOCK].asize; ++i)
+ bufrelease(md->work_bufs[BUFFER_BLOCK].item[i]);
+
+ stack_free(&md->work_bufs[BUFFER_SPAN]);
+ stack_free(&md->work_bufs[BUFFER_BLOCK]);
+
+ free(md);
+}
+
+void
+sd_version(int *ver_major, int *ver_minor, int *ver_revision)
+{
+ *ver_major = SUNDOWN_VER_MAJOR;
+ *ver_minor = SUNDOWN_VER_MINOR;
+ *ver_revision = SUNDOWN_VER_REVISION;
+}
+
+/* vim: set filetype=c: */
diff --git a/src/cpp/core/markdown/sundown/markdown.h b/src/cpp/core/markdown/sundown/markdown.h
new file mode 100644
index 0000000..6f6553e
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/markdown.h
@@ -0,0 +1,138 @@
+/* markdown.h - generic markdown parser */
+
+/*
+ * Copyright (c) 2009, Natacha Porté
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef UPSKIRT_MARKDOWN_H
+#define UPSKIRT_MARKDOWN_H
+
+#include "buffer.h"
+#include "autolink.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SUNDOWN_VERSION "1.16.0"
+#define SUNDOWN_VER_MAJOR 1
+#define SUNDOWN_VER_MINOR 16
+#define SUNDOWN_VER_REVISION 0
+
+/********************
+ * TYPE DEFINITIONS *
+ ********************/
+
+/* mkd_autolink - type of autolink */
+enum mkd_autolink {
+ MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
+ MKDA_NORMAL, /* normal http/http/ftp/mailto/etc link */
+ MKDA_EMAIL, /* e-mail link without explit mailto: */
+};
+
+enum mkd_tableflags {
+ MKD_TABLE_ALIGN_L = 1,
+ MKD_TABLE_ALIGN_R = 2,
+ MKD_TABLE_ALIGN_CENTER = 3,
+ MKD_TABLE_ALIGNMASK = 3,
+ MKD_TABLE_HEADER = 4
+};
+
+enum mkd_extensions {
+ MKDEXT_NO_INTRA_EMPHASIS = (1 << 0),
+ MKDEXT_TABLES = (1 << 1),
+ MKDEXT_FENCED_CODE = (1 << 2),
+ MKDEXT_AUTOLINK = (1 << 3),
+ MKDEXT_STRIKETHROUGH = (1 << 4),
+ MKDEXT_SPACE_HEADERS = (1 << 6),
+ MKDEXT_SUPERSCRIPT = (1 << 7),
+ MKDEXT_LAX_SPACING = (1 << 8),
+};
+
+/* sd_callbacks - functions for rendering parsed data */
+struct sd_callbacks {
+ /* block level callbacks - NULL skips the block */
+ void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque);
+ void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque);
+ void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque);
+ void (*header)(struct buf *ob, const struct buf *text, int level, void *opaque);
+ void (*hrule)(struct buf *ob, void *opaque);
+ void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque);
+ void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque);
+ void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque);
+ void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
+ void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
+ void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
+
+
+ /* span level callbacks - NULL or return 0 prints the span verbatim */
+ int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
+ int (*codespan)(struct buf *ob, const struct buf *text, void *opaque);
+ int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+ int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+ int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque);
+ int (*linebreak)(struct buf *ob, void *opaque);
+ int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque);
+ int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
+ int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
+ int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
+ int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
+
+ /* low level callbacks - NULL copies input directly into the output */
+ void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);
+ void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque);
+
+ /* header and footer */
+ void (*doc_header)(struct buf *ob, void *opaque);
+ void (*doc_footer)(struct buf *ob, void *opaque);
+};
+
+struct sd_markdown;
+
+/*********
+ * FLAGS *
+ *********/
+
+/* list/listitem flags */
+#define MKD_LIST_ORDERED 1
+#define MKD_LI_BLOCK 2 /* <li> containing block data */
+
+/**********************
+ * EXPORTED FUNCTIONS *
+ **********************/
+
+extern struct sd_markdown *
+sd_markdown_new(
+ unsigned int extensions,
+ size_t max_nesting,
+ const struct sd_callbacks *callbacks,
+ void *opaque);
+
+extern void
+sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md);
+
+extern void
+sd_markdown_free(struct sd_markdown *md);
+
+extern void
+sd_version(int *major, int *minor, int *revision);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/* vim: set filetype=c: */
diff --git a/src/cpp/core/markdown/sundown/stack.c b/src/cpp/core/markdown/sundown/stack.c
new file mode 100644
index 0000000..ce069ff
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/stack.c
@@ -0,0 +1,81 @@
+#include "stack.h"
+#include <string.h>
+
+int
+stack_grow(struct stack *st, size_t new_size)
+{
+ void **new_st;
+
+ if (st->asize >= new_size)
+ return 0;
+
+ new_st = realloc(st->item, new_size * sizeof(void *));
+ if (new_st == NULL)
+ return -1;
+
+ memset(new_st + st->asize, 0x0,
+ (new_size - st->asize) * sizeof(void *));
+
+ st->item = new_st;
+ st->asize = new_size;
+
+ if (st->size > new_size)
+ st->size = new_size;
+
+ return 0;
+}
+
+void
+stack_free(struct stack *st)
+{
+ if (!st)
+ return;
+
+ free(st->item);
+
+ st->item = NULL;
+ st->size = 0;
+ st->asize = 0;
+}
+
+int
+stack_init(struct stack *st, size_t initial_size)
+{
+ st->item = NULL;
+ st->size = 0;
+ st->asize = 0;
+
+ if (!initial_size)
+ initial_size = 8;
+
+ return stack_grow(st, initial_size);
+}
+
+void *
+stack_pop(struct stack *st)
+{
+ if (!st->size)
+ return NULL;
+
+ return st->item[--st->size];
+}
+
+int
+stack_push(struct stack *st, void *item)
+{
+ if (stack_grow(st, st->size * 2) < 0)
+ return -1;
+
+ st->item[st->size++] = item;
+ return 0;
+}
+
+void *
+stack_top(struct stack *st)
+{
+ if (!st->size)
+ return NULL;
+
+ return st->item[st->size - 1];
+}
+
diff --git a/src/cpp/core/markdown/sundown/stack.h b/src/cpp/core/markdown/sundown/stack.h
new file mode 100644
index 0000000..08ff030
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/stack.h
@@ -0,0 +1,29 @@
+#ifndef STACK_H__
+#define STACK_H__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct stack {
+ void **item;
+ size_t size;
+ size_t asize;
+};
+
+void stack_free(struct stack *);
+int stack_grow(struct stack *, size_t);
+int stack_init(struct stack *, size_t);
+
+int stack_push(struct stack *, void *);
+
+void *stack_pop(struct stack *);
+void *stack_top(struct stack *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/markdown/sundown/sundown_version.h b/src/cpp/core/markdown/sundown/sundown_version.h
new file mode 100644
index 0000000..2b43902
--- /dev/null
+++ b/src/cpp/core/markdown/sundown/sundown_version.h
@@ -0,0 +1 @@
+#define RSTUDIO_SUNDOWN_VERSION 07d0d98cec0df93e07debbcf562cac9eaca998f8
diff --git a/src/cpp/core/r_util/REnvironmentPosix.cpp b/src/cpp/core/r_util/REnvironmentPosix.cpp
new file mode 100644
index 0000000..519ba6a
--- /dev/null
+++ b/src/cpp/core/r_util/REnvironmentPosix.cpp
@@ -0,0 +1,709 @@
+/*
+ * REnvironmentPosix.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/REnvironment.hpp>
+
+#include <algorithm>
+
+#include <boost/tokenizer.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/ConfigUtils.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+FilePath scanForRScript(const std::vector<std::string>& rScriptPaths,
+ std::string* pErrMsg)
+{
+ // iterate over paths
+ for (std::vector<std::string>::const_iterator it = rScriptPaths.begin();
+ it != rScriptPaths.end();
+ ++it)
+ {
+ FilePath rScriptPath(*it);
+ if (rScriptPath.exists() && !rScriptPath.isDirectory())
+ {
+ // verify that the alias points to a real version of R
+ Error error = core::system::realPath(*it, &rScriptPath);
+ if (!error)
+ {
+ return rScriptPath;
+ }
+ else
+ {
+ error.addProperty("script-path", *it);
+ LOG_ERROR(error);
+ continue;
+ }
+ }
+ }
+
+ // didn't find it
+ *pErrMsg = "Unable to locate R binary by scanning standard locations";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return FilePath();
+}
+
+// MacOS X Specific
+#ifdef __APPLE__
+
+#define kLibRFileName "libR.dylib"
+#define kLibraryPathEnvVariable "DYLD_FALLBACK_LIBRARY_PATH"
+
+// no extra paths on the mac
+std::string extraLibraryPaths(const FilePath& ldPathsScript,
+ const std::string& rHome)
+{
+ return std::string();
+}
+
+FilePath systemDefaultRScript(std::string* pErrMsg)
+{
+ // define potential paths
+ std::vector<std::string> rScriptPaths;
+ rScriptPaths.push_back("/usr/bin/R");
+ rScriptPaths.push_back("/usr/local/bin/R");
+ rScriptPaths.push_back("/opt/local/bin/R");
+ return scanForRScript(rScriptPaths, pErrMsg);
+}
+
+bool getLibPathFromRHome(const FilePath& rHomePath,
+ std::string* pRLibPath,
+ std::string* pErrMsg)
+{
+ // get R lib path (probe subdiretories if necessary)
+ FilePath libPath = rHomePath.complete("lib");
+
+ // check for dylib in lib and lib/x86_64
+ if (libPath.complete(kLibRFileName).exists())
+ {
+ *pRLibPath = libPath.absolutePath();
+ return true;
+ }
+ else if (libPath.complete("x86_64/" kLibRFileName).exists())
+ {
+ *pRLibPath = libPath.complete("x86_64").absolutePath();
+ return true;
+ }
+ else
+ {
+ *pErrMsg = "Unable to find " kLibRFileName " in expected locations"
+ "within R Home directory " + rHomePath.absolutePath();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+}
+
+bool getRHomeAndLibPath(const FilePath& rScriptPath,
+ const config_utils::Variables& scriptVars,
+ std::string* pRHome,
+ std::string* pRLibPath,
+ std::string* pErrMsg)
+{
+ config_utils::Variables::const_iterator it = scriptVars.find("R_HOME_DIR");
+ if (it != scriptVars.end())
+ {
+ // get R home
+ *pRHome = it->second;
+
+ // get lib path
+ return getLibPathFromRHome(FilePath(*pRHome), pRLibPath, pErrMsg);
+ }
+ else
+ {
+ *pErrMsg = "Unable to find R_HOME_DIR in " + rScriptPath.absolutePath();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+}
+
+bool detectRLocationsUsingFramework(FilePath* pHomePath,
+ FilePath* pLibPath,
+ config_utils::Variables* pScriptVars,
+ std::string* pErrMsg)
+{
+ // home path
+ *pHomePath = FilePath("/Library/Frameworks/R.framework/Resources");
+
+ // lib path
+ std::string rLibPath;
+ if (!getLibPathFromRHome(*pHomePath, &rLibPath, pErrMsg))
+ return false;
+ *pLibPath = FilePath(rLibPath);
+
+ // other paths
+ config_utils::Variables& scriptVars = *pScriptVars;
+ scriptVars["R_HOME"] = pHomePath->absolutePath();
+ scriptVars["R_SHARE_DIR"] = pHomePath->complete("share").absolutePath();
+ scriptVars["R_INCLUDE_DIR"] = pHomePath->complete("include").absolutePath();
+ scriptVars["R_DOC_DIR"] = pHomePath->complete("doc").absolutePath();
+
+ return true;
+}
+
+// Linux specific
+#else
+
+#define kLibRFileName "libR.so"
+#define kLibraryPathEnvVariable "LD_LIBRARY_PATH"
+
+// extra paths from R (for rjava) on linux
+std::string extraLibraryPaths(const FilePath& ldPathsScript,
+ const std::string& rHome)
+{
+ // verify that script exists
+ if (!ldPathsScript.exists())
+ {
+ LOG_WARNING_MESSAGE("r-ldpaths script not found at " +
+ ldPathsScript.absolutePath());
+ return std::string();
+ }
+
+ // run script to capture paths
+ std::string command = ldPathsScript.absolutePath() + " " + rHome;
+ system::ProcessResult result;
+ Error error = runCommand(command, core::system::ProcessOptions(), &result);
+ if (error)
+ LOG_ERROR(error);
+ std::string libraryPaths = result.stdOut;
+ boost::algorithm::trim(libraryPaths);
+ return libraryPaths;
+}
+
+FilePath systemDefaultRScript(std::string* pErrMsg)
+{
+ // ask system which R to use
+ system::ProcessResult result;
+ Error error = core::system::runCommand("which R",
+ core::system::ProcessOptions(),
+ &result);
+ std::string whichR = result.stdOut;
+ boost::algorithm::trim(whichR);
+ if (error || whichR.empty())
+ {
+ // log error or failure to return output
+ if (error)
+ {
+ *pErrMsg = "Error calling which R: " + error.summary();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ }
+ else
+ {
+ *pErrMsg = "Unable to find an installation of R on the system "
+ "(which R didn't return valid output)";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ }
+
+ // scan in standard locations as a fallback
+ std::string scanErrMsg;
+ std::vector<std::string> rScriptPaths;
+ rScriptPaths.push_back("/usr/local/bin/R");
+ rScriptPaths.push_back("/usr/bin/R");
+ FilePath scriptPath = scanForRScript(rScriptPaths, &scanErrMsg);
+ if (scriptPath.empty())
+ {
+ pErrMsg->append("; " + scanErrMsg);
+ return FilePath();
+ }
+
+ // set whichR
+ whichR = scriptPath.absolutePath();
+ }
+
+
+ // return path to R script
+ return FilePath(whichR);
+}
+
+bool getRHomeAndLibPath(const FilePath& rScriptPath,
+ const config_utils::Variables& scriptVars,
+ std::string* pRHome,
+ std::string* pRLibPath,
+ std::string* pErrMsg)
+{
+ // eliminate a potentially conflicting R_HOME before calling R RHOME"
+ // (the normal semantics of invoking the R script are that it overwrites
+ // R_HOME and prints a warning -- this warning is co-mingled with the
+ // output of "R RHOME" and messes up our parsing)
+ core::system::setenv("R_HOME", "");
+
+ // run R script to detect R home
+ std::string command = rScriptPath.absolutePath() + " RHOME";
+ system::ProcessResult result;
+ Error error = runCommand(command, core::system::ProcessOptions(), &result);
+ if (error)
+ {
+ *pErrMsg = "Error running R (" + rScriptPath.absolutePath() + "): " +
+ error.summary();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ else
+ {
+ std::string rHomeOutput = result.stdOut;
+ boost::algorithm::trim(rHomeOutput);
+ *pRHome = rHomeOutput;
+ *pRLibPath = FilePath(*pRHome).complete("lib").absolutePath();
+ return true;
+ }
+}
+
+
+#endif
+
+
+
+bool validateRScriptPath(const std::string& rScriptPath,
+ std::string* pErrMsg)
+{
+ // get realpath
+ FilePath rBinaryPath;
+ Error error = core::system::realPath(rScriptPath, &rBinaryPath);
+ if (error)
+ {
+ *pErrMsg = "Unable to determine real path of R script " +
+ rScriptPath + " (" + error.summary() + ")";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // check for real path doesn't exist
+ else if (!rBinaryPath.exists())
+ {
+ *pErrMsg = "Real path of R script does not exist (" +
+ rBinaryPath.absolutePath() + ")";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // error if it is a directry
+ else if (rBinaryPath.isDirectory())
+ {
+ *pErrMsg = "R script path (" + rBinaryPath.absolutePath() +
+ ") is a directory rather than a file";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // looks good!
+ else
+ {
+ return true;
+ }
+}
+
+
+bool validateREnvironment(const EnvironmentVars& vars,
+ const FilePath& rLibPath,
+ std::string* pErrMsg)
+{
+ // first extract paths
+ FilePath rHomePath, rSharePath, rIncludePath, rDocPath, rLibRPath;
+ for (EnvironmentVars::const_iterator it = vars.begin();
+ it != vars.end();
+ ++it)
+ {
+ if (it->first == "R_HOME")
+ rHomePath = FilePath(it->second);
+ else if (it->first == "R_SHARE_DIR")
+ rSharePath = FilePath(it->second);
+ else if (it->first == "R_INCLUDE_DIR")
+ rIncludePath = FilePath(it->second);
+ else if (it->first == "R_DOC_DIR")
+ rDocPath = FilePath(it->second);
+ }
+
+ // resolve libR path
+ rLibRPath = rLibPath.complete(kLibRFileName);
+
+ // validate required paths (if these don't exist then rsession won't
+ // be able start up)
+ if (!rHomePath.exists())
+ {
+ *pErrMsg = "R Home path (" + rHomePath.absolutePath() + ") not found";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ else if (!rLibPath.exists())
+ {
+ *pErrMsg = "R lib path (" + rLibPath.absolutePath() + ") not found";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ else if (!rLibRPath.exists())
+ {
+ *pErrMsg = "R shared library (" + rLibRPath.absolutePath() + ") "
+ "not found. If this is a custom build of R, was it "
+ "built with the --enable-R-shlib option?";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ else if (!rDocPath.exists())
+ {
+ *pErrMsg = "R doc dir (" + rDocPath.absolutePath() + ") not found.";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // log warnings for other missing paths (rsession can still start but
+ // won't be able to find these env variables)
+
+ if (!rSharePath.exists())
+ {
+ LOG_WARNING_MESSAGE("R share path (" + rSharePath.absolutePath() +
+ ") not found");
+ }
+
+ if (!rIncludePath.exists())
+ {
+ LOG_WARNING_MESSAGE("R include path (" + rIncludePath.absolutePath() +
+ ") not found");
+ }
+
+ return true;
+}
+
+// resolve an R path which has been parsed from the R bash script. If
+// R is running out of the source directory (and was thus never installed)
+// then the values for R_DOC_DIR, etc. will contain unexpanded references
+// to the R_HOME_DIR, so we expand these if they are present.
+std::string resolveRPath(const FilePath& rHomePath, const std::string& path)
+{
+ std::string resolvedPath = path;
+ boost::algorithm::replace_all(resolvedPath,
+ "${R_HOME_DIR}",
+ rHomePath.absolutePath());
+ return resolvedPath;
+}
+
+bool detectRLocationsUsingScript(const FilePath& rScriptPath,
+ FilePath* pHomePath,
+ FilePath* pLibPath,
+ config_utils::Variables* pScriptVars,
+ std::string* pErrMsg)
+{
+ // scan R script for other locations and append them to our vars
+ Error error = config_utils::extractVariables(rScriptPath, pScriptVars);
+ if (error)
+ {
+ *pErrMsg = "Error reading R script (" + rScriptPath.absolutePath() +
+ "), " + error.summary();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // get r home path
+ std::string rHome, rLib;
+ if (!getRHomeAndLibPath(rScriptPath, *pScriptVars, &rHome, &rLib, pErrMsg))
+ return false;
+
+ // validate: error if we got no output
+ if (rHome.empty())
+ {
+ *pErrMsg = "Unable to determine R home directory";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // validate: error if `R RHOME` yields file that doesn't exist
+ *pHomePath = FilePath(rHome);
+ if (!pHomePath->exists())
+ {
+ *pErrMsg = "R home path (" + rHome + ") not found";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // get lib path
+ *pLibPath = FilePath(rLib);
+
+ return true;
+}
+
+#ifndef __APPLE__
+bool detectRLocationsUsingR(const std::string& rScriptPath,
+ FilePath* pHomePath,
+ FilePath* pLibPath,
+ config_utils::Variables* pScriptVars,
+ std::string* pErrMsg)
+{
+ // eliminate a potentially conflicting R_HOME before calling R
+ // (the normal semantics of invoking the R script are that it overwrites
+ // R_HOME and prints a warning -- this warning is co-mingled with the
+ // output of R and messes up our parsing)
+ core::system::setenv("R_HOME", "");
+
+ // call R to determine the locations
+ std::string command = rScriptPath +
+ " --slave --vanilla -e \"cat(paste("
+ "R.home('home'),"
+ "R.home('share'),"
+ "R.home('include'),"
+ "R.home('doc'),sep=':'))\"";
+ system::ProcessResult result;
+ Error error = runCommand(command, system::ProcessOptions(), &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ *pErrMsg = "Error calling R script (" + rScriptPath +
+ "), " + error.summary();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ std::string output = result.stdOut;
+ boost::algorithm::trim(output);
+
+ if (output.empty())
+ {
+ *pErrMsg = "R did not return any output when queried for "
+ "directory location information";
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // extract the locations
+ config_utils::Variables& scriptVars = *pScriptVars;
+ using namespace boost;
+ char_separator<char> sep(":");
+ tokenizer<char_separator<char> > tokens(output, sep);
+ tokenizer<char_separator<char> >::iterator tokenIter = tokens.begin();
+ if (tokenIter != tokens.end())
+ scriptVars["R_HOME"] = *tokenIter++;
+ if (tokenIter != tokens.end())
+ scriptVars["R_SHARE_DIR"] = *tokenIter++;
+ if (tokenIter != tokens.end())
+ scriptVars["R_INCLUDE_DIR"] = *tokenIter++;
+ if (tokenIter != tokens.end())
+ scriptVars["R_DOC_DIR"] = *tokenIter++;
+
+ if (scriptVars.size() < 4)
+ {
+ *pErrMsg = "R did not return valid directory location information; "
+ "R output was: " + output;
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ // get home and lib path
+ config_utils::Variables::const_iterator it = pScriptVars->find("R_HOME");
+ if (it != pScriptVars->end())
+ {
+ // get R home
+ *pHomePath = FilePath(it->second);
+
+ // get R lib path
+ FilePath libPath = FilePath(*pHomePath).complete("lib");
+
+ // verify we can find libR
+ if (libPath.complete(kLibRFileName).exists())
+ {
+ *pLibPath = libPath;
+ }
+
+ // sometimes on the mac an architecture specific subdirectory is used
+ else if (libPath.complete("x86_64/" kLibRFileName).exists())
+ {
+ *pLibPath = libPath.complete("x86_64");
+ }
+
+ // couldn't find libR
+ else
+ {
+ *pErrMsg = "Unable to find " kLibRFileName " in expected locations "
+ "within R Home directory " + pHomePath->absolutePath();
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+ }
+ else
+ {
+ *pErrMsg = "Unable to find R_HOME via " + rScriptPath +
+ "; R output was: " + output;
+ LOG_ERROR_MESSAGE(*pErrMsg);
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+} // anonymous namespace
+
+
+bool detectREnvironment(const FilePath& whichRScript,
+ const FilePath& ldPathsScript,
+ const std::string& ldLibraryPath,
+ std::string* pRScriptPath,
+ EnvironmentVars* pVars,
+ std::string* pErrMsg)
+{
+ // if there is a which R script override then validate it
+ if (!whichRScript.empty())
+ {
+ // validate
+ if (!validateRScriptPath(whichRScript.absolutePath(), pErrMsg))
+ return false;
+
+ // set it
+ *pRScriptPath = whichRScript.absolutePath();
+ }
+ // otherwise use the system default (after validating it as well)
+ else
+ {
+ // get system default
+ FilePath sysRScript = systemDefaultRScript(pErrMsg);
+ if (sysRScript.empty())
+ return false;
+
+ if (!validateRScriptPath(sysRScript.absolutePath(), pErrMsg))
+ return false;
+
+ // set it
+ *pRScriptPath = sysRScript.absolutePath();
+ }
+
+ // detect R locations
+ FilePath rHomePath, rLibPath;
+ config_utils::Variables scriptVars;
+#ifdef __APPLE__
+ if (!detectRLocationsUsingScript(FilePath(*pRScriptPath),
+ &rHomePath,
+ &rLibPath,
+ &scriptVars,
+ pErrMsg))
+ {
+ // fallback to detecting using Framework directory
+ rHomePath = FilePath();
+ rLibPath = FilePath();
+ scriptVars.clear();
+ std::string scriptErrMsg;
+ *pRScriptPath = "/Library/Frameworks/R.framework/Resources/bin/R";
+ if (!detectRLocationsUsingFramework(&rHomePath,
+ &rLibPath,
+ &scriptVars,
+ &scriptErrMsg))
+ {
+ pErrMsg->append("; " + scriptErrMsg);
+ return false;
+ }
+ }
+#else
+ if (!detectRLocationsUsingR(*pRScriptPath,
+ &rHomePath,
+ &rLibPath,
+ &scriptVars,
+ pErrMsg))
+ {
+ // fallback to detecting using script (sometimes we are unable to
+ // call R successfully immediately after a system reboot)
+ rHomePath = FilePath();
+ rLibPath = FilePath();
+ scriptVars.clear();
+ std::string scriptErrMsg;
+ if (!detectRLocationsUsingScript(FilePath(*pRScriptPath),
+ &rHomePath,
+ &rLibPath,
+ &scriptVars,
+ &scriptErrMsg))
+ {
+ pErrMsg->append("; " + scriptErrMsg);
+ return false;
+ }
+ }
+#endif
+
+
+ // set R home path
+ pVars->push_back(std::make_pair("R_HOME", rHomePath.absolutePath()));
+
+ // set other environment values
+ pVars->push_back(std::make_pair("R_SHARE_DIR",
+ resolveRPath(rHomePath,
+ scriptVars["R_SHARE_DIR"])));
+ pVars->push_back(std::make_pair("R_INCLUDE_DIR",
+ resolveRPath(rHomePath,
+ scriptVars["R_INCLUDE_DIR"])));
+ pVars->push_back(std::make_pair("R_DOC_DIR",
+ resolveRPath(rHomePath,
+ scriptVars["R_DOC_DIR"])));
+
+ // determine library path (existing + r lib dir + r extra lib dirs)
+ std::string libraryPath = core::system::getenv(kLibraryPathEnvVariable);
+#ifdef __APPLE__
+ // if this isn't set explicitly then initalize it with the default
+ // of $HOME/lib:/usr/local/lib:/usr/lib. See documentation here:
+ // http://developer.apple.com/library/ios/#documentation/system/conceptual/manpages_iphoneos/man3/dlopen.3.html
+ if (libraryPath.empty())
+ {
+ boost::format fmt("%1%/lib:/usr/local/lib:/usr/lib");
+ libraryPath = boost::str(fmt % core::system::getenv("HOME"));
+ }
+#endif
+ if (!libraryPath.empty())
+ libraryPath.append(":");
+ libraryPath.append(ldLibraryPath);
+ if (!libraryPath.empty())
+ libraryPath.append(":");
+ libraryPath.append(rLibPath.absolutePath());
+ std::string extraPaths = extraLibraryPaths(ldPathsScript,
+ rHomePath.absolutePath());
+ if (!extraPaths.empty())
+ libraryPath.append(":" + extraPaths);
+ pVars->push_back(std::make_pair(kLibraryPathEnvVariable, libraryPath));
+
+ // set R_ARCH on the mac if we are running against CRAN R
+#ifdef __APPLE__
+ // if it starts with the standard prefix and an etc/x86_64 directory
+ // exists then we set the R_ARCH
+ if (boost::algorithm::starts_with(rHomePath.absolutePath(),
+ "/Library/Frameworks/R.framework/") &&
+ FilePath("/Library/Frameworks/R.framework/Resources/etc/x86_64")
+ .exists())
+ {
+ pVars->push_back(std::make_pair("R_ARCH","/x86_64"));
+ }
+#endif
+
+
+ return validateREnvironment(*pVars, rLibPath, pErrMsg);
+}
+
+
+void setREnvironmentVars(const EnvironmentVars& vars)
+{
+ for (EnvironmentVars::const_iterator it = vars.begin();
+ it != vars.end();
+ ++it)
+ {
+ core::system::setenv(it->first, it->second);
+ }
+}
+
+} // namespace r_util
+} // namespace core
+
+
+
diff --git a/src/cpp/core/r_util/RPackageInfo.cpp b/src/cpp/core/r_util/RPackageInfo.cpp
new file mode 100644
index 0000000..84dea9d
--- /dev/null
+++ b/src/cpp/core/r_util/RPackageInfo.cpp
@@ -0,0 +1,123 @@
+/*
+ * RPackageInfo.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RPackageInfo.hpp>
+
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+
+#include <core/text/DcfParser.hpp>
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+const char * const kPackageType = "Package";
+
+Error fieldNotFoundError(const FilePath& descFilePath,
+ const std::string& fieldName,
+ const ErrorLocation& location)
+{
+ return systemError(
+ boost::system::errc::protocol_error,
+ fieldName + " field not found in " + descFilePath.absolutePath(),
+ location);
+}
+
+} // anonymous namespace
+
+
+Error RPackageInfo::read(const FilePath& packageDir)
+{
+ // parse DCF file
+ FilePath descFilePath = packageDir.childPath("DESCRIPTION");
+ if (!descFilePath.exists())
+ return core::fileNotFoundError(descFilePath, ERROR_LOCATION);
+ std::string errMsg;
+ std::map<std::string,std::string> fields;
+ Error error = text::parseDcfFile(descFilePath, true, &fields, &errMsg);
+ if (error)
+ return error;
+
+ // Package field
+ std::map<std::string,std::string>::const_iterator it;
+ it = fields.find("Package");
+ if (it != fields.end())
+ name_ = it->second;
+ else
+ return fieldNotFoundError(descFilePath, "Package", ERROR_LOCATION);
+
+ // Version field
+ it = fields.find("Version");
+ if (it != fields.end())
+ version_ = it->second;
+ else
+ return fieldNotFoundError(descFilePath, "Version", ERROR_LOCATION);
+
+ // Linking to field
+ it = fields.find("LinkingTo");
+ if (it != fields.end())
+ linkingTo_ = it->second;
+
+ // Type field
+ it = fields.find("Type");
+ if (it != fields.end())
+ type_ = it->second;
+ else
+ type_ = kPackageType;
+
+ return Success();
+}
+
+
+std::string RPackageInfo::sourcePackageFilename() const
+{
+ return packageFilename("tar.gz");
+}
+
+std::string RPackageInfo::packageFilename(const std::string& extension) const
+{
+ boost::format fmt("%1%_%2%.%3%");
+ return boost::str(fmt % name() % version() % extension);
+}
+
+bool isPackageDirectory(const FilePath& dir)
+{
+ if (dir.childPath("DESCRIPTION").exists())
+ {
+ RPackageInfo pkgInfo;
+ Error error = pkgInfo.read(dir);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ return pkgInfo.type() == kPackageType;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+} // namespace r_util
+} // namespace core
+
+
+
diff --git a/src/cpp/core/r_util/RProjectFile.cpp b/src/cpp/core/r_util/RProjectFile.cpp
new file mode 100644
index 0000000..23f7da4
--- /dev/null
+++ b/src/cpp/core/r_util/RProjectFile.cpp
@@ -0,0 +1,780 @@
+/*
+ * RProjectFile.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RProjectFile.hpp>
+
+#include <map>
+#include <iomanip>
+#include <ostream>
+
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/text/DcfParser.hpp>
+
+#include <core/r_util/RPackageInfo.hpp>
+
+namespace core {
+namespace r_util {
+
+const char * const kBuildTypeNone = "None";
+const char * const kBuildTypePackage = "Package";
+const char * const kBuildTypeMakefile = "Makefile";
+const char * const kBuildTypeCustom = "Custom";
+
+namespace {
+
+const char * const kPackageInstallArgsDefault = "--no-multiarch "
+ "--with-keep.source";
+const char * const kPackageInstallArgsPreviousDefault = "--no-multiarch";
+
+Error requiredFieldError(const std::string& field,
+ std::string* pUserErrMsg)
+{
+ *pUserErrMsg = field + " not correctly specified in project config file";
+ return systemError(boost::system::errc::protocol_error, ERROR_LOCATION);
+}
+
+std::string yesNoAskValueToString(int value)
+{
+ std::ostringstream ostr;
+ ostr << (YesNoAskValue) value;
+ return ostr.str();
+}
+
+bool interpretYesNoAskValue(const std::string& value,
+ bool acceptAsk,
+ int* pValue)
+{
+ std::string valueLower = string_utils::toLower(value);
+ boost::algorithm::trim(valueLower);
+ if (valueLower == "yes")
+ {
+ *pValue = YesValue;
+ return true;
+ }
+ else if (valueLower == "no")
+ {
+ *pValue = NoValue;
+ return true;
+ }
+ else if (valueLower == "default")
+ {
+ *pValue = DefaultValue;
+ return true;
+ }
+ else if (acceptAsk && (valueLower == "ask"))
+ {
+ *pValue = AskValue;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+std::string boolValueToString(bool value)
+{
+ if (value)
+ return "Yes";
+ else
+ return "No";
+}
+
+bool interpretBoolValue(const std::string& value, bool* pValue)
+{
+ std::string valueLower = string_utils::toLower(value);
+ boost::algorithm::trim(valueLower);
+ if (valueLower == "yes")
+ {
+ *pValue = true;
+ return true;
+ }
+ else if (valueLower == "no")
+ {
+ *pValue = false;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool interpretBuildTypeValue(const std::string& value, std::string* pValue)
+{
+ if (value == "" ||
+ value == kBuildTypeNone ||
+ value == kBuildTypePackage ||
+ value == kBuildTypeMakefile ||
+ value == kBuildTypeCustom)
+ {
+ *pValue = value;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool interpretIntValue(const std::string& value, int* pValue)
+{
+ try
+ {
+ *pValue = boost::lexical_cast<int>(value);
+ return true;
+ }
+ catch(const boost::bad_lexical_cast& e)
+ {
+ return false;
+ }
+}
+
+void setBuildPackageDefaults(const std::string& packagePath,
+ RProjectConfig* pConfig)
+{
+ pConfig->buildType = kBuildTypePackage;
+ pConfig->packageUseDevtools = true;
+ pConfig->packagePath = packagePath;
+ pConfig->packageInstallArgs = kPackageInstallArgsDefault;
+}
+
+std::string detectBuildType(const FilePath& projectFilePath,
+ RProjectConfig* pConfig)
+{
+ FilePath projectDir = projectFilePath.parent();
+ if (r_util::isPackageDirectory(projectDir))
+ {
+ setBuildPackageDefaults("", pConfig);
+ }
+ else if (projectDir.childPath("pkg/DESCRIPTION").exists())
+ {
+ setBuildPackageDefaults("pkg", pConfig);
+ }
+ else if (projectDir.childPath("Makefile").exists())
+ {
+ pConfig->buildType = kBuildTypeMakefile;
+ pConfig->makefilePath = "";
+
+ }
+ else
+ {
+ pConfig->buildType = kBuildTypeNone;
+ }
+
+ return pConfig->buildType;
+}
+
+std::string detectBuildType(const FilePath& projectFilePath)
+{
+ RProjectConfig config;
+ return detectBuildType(projectFilePath, &config);
+}
+
+} // anonymous namespace
+
+std::ostream& operator << (std::ostream& stream, const YesNoAskValue& val)
+{
+ switch(val)
+ {
+ case YesValue:
+ stream << "Yes";
+ break;
+ case NoValue:
+ stream << "No";
+ break;
+ case AskValue:
+ stream << "Ask";
+ break;
+ case DefaultValue:
+ default:
+ stream << "Default";
+ break;
+ }
+
+ return stream ;
+}
+
+
+Error readProjectFile(const FilePath& projectFilePath,
+ const RProjectConfig& defaultConfig,
+ RProjectConfig* pConfig,
+ bool* pProvidedDefaults,
+ std::string* pUserErrMsg)
+{
+ // default to not providing defaults
+ *pProvidedDefaults = false;
+
+ // first read the project DCF file
+ typedef std::map<std::string,std::string> Fields;
+ Fields dcfFields;
+ Error error = text::parseDcfFile(projectFilePath,
+ true,
+ &dcfFields,
+ pUserErrMsg);
+ if (error)
+ return error;
+
+ // extract version
+ Fields::const_iterator it = dcfFields.find("Version");
+
+ // no version field
+ if (it == dcfFields.end())
+ {
+ *pUserErrMsg = "The project file did not include a Version attribute "
+ "(it may have been created by a more recent version "
+ "of RStudio)";
+ return systemError(boost::system::errc::protocol_error,
+ ERROR_LOCATION);
+ }
+
+ // invalid version field
+ pConfig->version = safe_convert::stringTo<double>(it->second, 0.0);
+ if (pConfig->version == 0.0)
+ {
+ return requiredFieldError("Version", pUserErrMsg);
+ }
+
+ // version later than 1.0
+ if (pConfig->version != 1.0)
+ {
+ *pUserErrMsg = "The project file was created by a more recent "
+ "version of RStudio";
+ return systemError(boost::system::errc::protocol_error,
+ ERROR_LOCATION);
+ }
+
+ // extract restore workspace
+ it = dcfFields.find("RestoreWorkspace");
+ if (it != dcfFields.end())
+ {
+ if (!interpretYesNoAskValue(it->second, false, &(pConfig->restoreWorkspace)))
+ return requiredFieldError("RestoreWorkspace", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->restoreWorkspace = defaultConfig.restoreWorkspace;
+ *pProvidedDefaults = true;
+ }
+
+ // extract save workspace
+ it = dcfFields.find("SaveWorkspace");
+ if (it != dcfFields.end())
+ {
+ if (!interpretYesNoAskValue(it->second, true, &(pConfig->saveWorkspace)))
+ return requiredFieldError("SaveWorkspace", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->saveWorkspace = defaultConfig.saveWorkspace;
+ *pProvidedDefaults = true;
+ }
+
+ // extract always save history
+ it = dcfFields.find("AlwaysSaveHistory");
+ if (it != dcfFields.end())
+ {
+ if (!interpretYesNoAskValue(it->second, false, &(pConfig->alwaysSaveHistory)))
+ return requiredFieldError("AlwaysSaveHistory", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->alwaysSaveHistory = defaultConfig.alwaysSaveHistory;
+ *pProvidedDefaults = true;
+ }
+
+ // extract enable code indexing
+ it = dcfFields.find("EnableCodeIndexing");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBoolValue(it->second, &(pConfig->enableCodeIndexing)))
+ return requiredFieldError("EnableCodeIndexing", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->enableCodeIndexing = defaultConfig.enableCodeIndexing;
+ *pProvidedDefaults = true;
+ }
+
+ // extract spaces for tab
+ it = dcfFields.find("UseSpacesForTab");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBoolValue(it->second, &(pConfig->useSpacesForTab)))
+ return requiredFieldError("UseSpacesForTab", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->useSpacesForTab = defaultConfig.useSpacesForTab;
+ *pProvidedDefaults = true;
+ }
+
+ // extract num spaces for tab
+ it = dcfFields.find("NumSpacesForTab");
+ if (it != dcfFields.end())
+ {
+ if (!interpretIntValue(it->second, &(pConfig->numSpacesForTab)))
+ return requiredFieldError("NumSpacesForTab", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->numSpacesForTab = defaultConfig.numSpacesForTab;
+ *pProvidedDefaults = true;
+ }
+
+ // extract auto append newline
+ it = dcfFields.find("AutoAppendNewline");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBoolValue(it->second, &(pConfig->autoAppendNewline)))
+ return requiredFieldError("AutoAppendNewline", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->autoAppendNewline = false;
+ }
+
+
+ // extract strip trailing whitespace
+ it = dcfFields.find("StripTrailingWhitespace");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBoolValue(it->second, &(pConfig->stripTrailingWhitespace)))
+ return requiredFieldError("StripTrailingWhitespace", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->stripTrailingWhitespace = false;
+ }
+
+ // extract encoding
+ it = dcfFields.find("Encoding");
+ if (it != dcfFields.end())
+ {
+ pConfig->encoding = it->second;
+ }
+ else
+ {
+ pConfig->encoding = defaultConfig.encoding;
+ *pProvidedDefaults = true;
+ }
+
+ // extract default sweave engine
+ it = dcfFields.find("RnwWeave");
+ if (it != dcfFields.end())
+ {
+ pConfig->defaultSweaveEngine = it->second;
+ }
+ else
+ {
+ pConfig->defaultSweaveEngine = defaultConfig.defaultSweaveEngine;
+ *pProvidedDefaults = true;
+ }
+
+ // extract default latex program
+ it = dcfFields.find("LaTeX");
+ if (it != dcfFields.end())
+ {
+ pConfig->defaultLatexProgram = it->second;
+ }
+ else
+ {
+ pConfig->defaultLatexProgram = defaultConfig.defaultLatexProgram;
+ *pProvidedDefaults = true;
+ }
+
+ // extract root document
+ it = dcfFields.find("RootDocument");
+ if (it != dcfFields.end())
+ {
+ pConfig->rootDocument = it->second;
+ }
+ else
+ {
+ pConfig->rootDocument = "";
+ }
+
+ // extract build type
+ it = dcfFields.find("BuildType");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBuildTypeValue(it->second, &(pConfig->buildType)))
+ return requiredFieldError("BuildType", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->buildType = defaultConfig.buildType;
+ }
+
+ // extract package path
+ it = dcfFields.find("PackagePath");
+ if (it != dcfFields.end())
+ {
+ pConfig->packagePath = it->second;
+ }
+ else
+ {
+ pConfig->packagePath = "";
+ }
+
+ // extract package install args
+ it = dcfFields.find("PackageInstallArgs");
+ if (it != dcfFields.end())
+ {
+ pConfig->packageInstallArgs = it->second;
+ }
+ else
+ {
+ pConfig->packageInstallArgs = "";
+ }
+
+ // extract package build args
+ it = dcfFields.find("PackageBuildArgs");
+ if (it != dcfFields.end())
+ {
+ pConfig->packageBuildArgs = it->second;
+ }
+ else
+ {
+ pConfig->packageBuildArgs = "";
+ }
+
+ // extract package build binary args
+ it = dcfFields.find("PackageBuildBinaryArgs");
+ if (it != dcfFields.end())
+ {
+ pConfig->packageBuildBinaryArgs = it->second;
+ }
+ else
+ {
+ pConfig->packageBuildBinaryArgs = "";
+ }
+
+ // extract package check args
+ it = dcfFields.find("PackageCheckArgs");
+ if (it != dcfFields.end())
+ {
+ pConfig->packageCheckArgs = it->second;
+ }
+ else
+ {
+ pConfig->packageCheckArgs = "";
+ }
+
+ // extract package roxygenzize
+ it = dcfFields.find("PackageRoxygenize");
+ if (it != dcfFields.end())
+ {
+ pConfig->packageRoxygenize = it->second;
+ }
+ else
+ {
+ pConfig->packageRoxygenize = "";
+ }
+
+ // extract package use devtools
+ it = dcfFields.find("PackageUseDevtools");
+ if (it != dcfFields.end())
+ {
+ if (!interpretBoolValue(it->second, &(pConfig->packageUseDevtools)))
+ return requiredFieldError("PackageUseDevtools", pUserErrMsg);
+ }
+ else
+ {
+ pConfig->packageUseDevtools = false;
+ }
+
+ // extract makefile path
+ it = dcfFields.find("MakefilePath");
+ if (it != dcfFields.end())
+ {
+ pConfig->makefilePath = it->second;
+ }
+ else
+ {
+ pConfig->makefilePath = "";
+ }
+
+ // extract custom script path
+ it = dcfFields.find("CustomScriptPath");
+ if (it != dcfFields.end())
+ {
+ pConfig->customScriptPath = it->second;
+ }
+ else
+ {
+ pConfig->customScriptPath = "";
+ }
+
+ // auto-detect build type if necessary
+ if (pConfig->buildType.empty())
+ {
+ // try to detect the build type
+ pConfig->buildType = detectBuildType(projectFilePath, pConfig);
+
+ // set *pProvidedDefaults only if we successfully auto-detected
+ // (this will prevent us from writing None into the project file,
+ // thus allowing auto-detection to work in the future if the user
+ // adds a DESCRIPTION or Makefile
+ if (pConfig->buildType != kBuildTypeNone)
+ *pProvidedDefaults = true;
+ }
+
+ // extract tutorial
+ it = dcfFields.find("Tutorial");
+ if (it != dcfFields.end())
+ {
+ pConfig->tutorialPath = it->second;
+ }
+ else
+ {
+ pConfig->tutorialPath = "";
+ }
+
+ return Success();
+}
+
+
+Error writeProjectFile(const FilePath& projectFilePath,
+ const RProjectConfig& config)
+{
+ // generate project file contents
+ boost::format fmt(
+ "Version: %1%\n"
+ "\n"
+ "RestoreWorkspace: %2%\n"
+ "SaveWorkspace: %3%\n"
+ "AlwaysSaveHistory: %4%\n"
+ "\n"
+ "EnableCodeIndexing: %5%\n"
+ "UseSpacesForTab: %6%\n"
+ "NumSpacesForTab: %7%\n"
+ "Encoding: %8%\n"
+ "\n"
+ "RnwWeave: %9%\n"
+ "LaTeX: %10%\n");
+
+ std::string contents = boost::str(fmt %
+ boost::io::group(std::fixed, std::setprecision(1), config.version) %
+ yesNoAskValueToString(config.restoreWorkspace) %
+ yesNoAskValueToString(config.saveWorkspace) %
+ yesNoAskValueToString(config.alwaysSaveHistory) %
+ boolValueToString(config.enableCodeIndexing) %
+ boolValueToString(config.useSpacesForTab) %
+ config.numSpacesForTab %
+ config.encoding %
+ config.defaultSweaveEngine %
+ config.defaultLatexProgram);
+
+ // add root-document if provided
+ if (!config.rootDocument.empty())
+ {
+ boost::format rootDocFmt("RootDocument: %1%\n");
+ std::string rootDoc = boost::str(rootDocFmt % config.rootDocument);
+ contents.append(rootDoc);
+ }
+
+ // additional editor settings
+ if (config.autoAppendNewline || config.stripTrailingWhitespace)
+ {
+ contents.append("\n");
+
+ if (config.autoAppendNewline)
+ {
+ contents.append("AutoAppendNewline: Yes\n");
+ }
+
+ if (config.stripTrailingWhitespace)
+ {
+ contents.append("StripTrailingWhitespace: Yes\n");
+ }
+ }
+
+ // add build-specific settings if necessary
+ if (!config.buildType.empty())
+ {
+ // if the build type is None and the detected build type is None
+ // then don't write any build type into the file (so that auto-detection
+ // has a chance to work in the future if the user turns this project
+ // into a package or adds a Makefile)
+ if (config.buildType != kBuildTypeNone ||
+ detectBuildType(projectFilePath) != kBuildTypeNone)
+ {
+ // build type
+ boost::format buildFmt("\nBuildType: %1%\n");
+ std::string build = boost::str(buildFmt % config.buildType);
+
+ // extra fields
+ if (config.buildType == kBuildTypePackage)
+ {
+ if (config.packageUseDevtools)
+ {
+ build.append("PackageUseDevtools: Yes\n");
+ }
+
+ if (!config.packagePath.empty())
+ {
+ boost::format pkgFmt("PackagePath: %1%\n");
+ build.append(boost::str(pkgFmt % config.packagePath));
+ }
+
+ if (!config.packageInstallArgs.empty())
+ {
+ boost::format pkgFmt("PackageInstallArgs: %1%\n");
+ build.append(boost::str(pkgFmt % config.packageInstallArgs));
+ }
+
+ if (!config.packageBuildArgs.empty())
+ {
+ boost::format pkgFmt("PackageBuildArgs: %1%\n");
+ build.append(boost::str(pkgFmt % config.packageBuildArgs));
+ }
+
+ if (!config.packageBuildBinaryArgs.empty())
+ {
+ boost::format pkgFmt("PackageBuildBinaryArgs: %1%\n");
+ build.append(boost::str(pkgFmt % config.packageBuildBinaryArgs));
+ }
+
+ if (!config.packageCheckArgs.empty())
+ {
+ boost::format pkgFmt("PackageCheckArgs: %1%\n");
+ build.append(boost::str(pkgFmt % config.packageCheckArgs));
+ }
+
+ if (!config.packageRoxygenize.empty())
+ {
+ boost::format pkgFmt("PackageRoxygenize: %1%\n");
+ build.append(boost::str(pkgFmt % config.packageRoxygenize));
+ }
+
+ }
+ else if (config.buildType == kBuildTypeMakefile)
+ {
+ if (!config.makefilePath.empty())
+ {
+ boost::format makefileFmt("MakefilePath: %1%\n");
+ build.append(boost::str(makefileFmt % config.makefilePath));
+ }
+ }
+ else if (config.buildType == kBuildTypeCustom)
+ {
+ boost::format customFmt("CustomScriptPath: %1%\n");
+ build.append(boost::str(customFmt % config.customScriptPath));
+ }
+
+ // add to contents
+ contents.append(build);
+ }
+ }
+
+ // add Tutorial if it's present
+ if (!config.tutorialPath.empty())
+ {
+ boost::format tutorialFmt("\nTutorial: %1%\n");
+ contents.append(boost::str(tutorialFmt % config.tutorialPath));
+ }
+
+ // write it
+ return writeStringToFile(projectFilePath,
+ contents,
+ string_utils::LineEndingNative);
+}
+
+FilePath projectFromDirectory(const FilePath& directoryPath)
+{
+ // first use simple heuristic of a case sentitive match between
+ // directory name and project file name
+ FilePath projectFile = directoryPath.childPath(
+ directoryPath.filename() + ".Rproj");
+ if (projectFile.exists())
+ return projectFile;
+
+ // didn't satisfy it with simple check so do scan of directory
+ std::vector<FilePath> children;
+ Error error = directoryPath.children(&children);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+
+ // build a vector of children with .rproj extensions. at the same
+ // time allow for a case insensitive match with dir name and return that
+ std::string projFileLower = string_utils::toLower(projectFile.filename());
+ std::vector<FilePath> rprojFiles;
+ for (std::vector<FilePath>::const_iterator it = children.begin();
+ it != children.end();
+ ++it)
+ {
+ if (!it->isDirectory() && (it->extensionLowerCase() == ".rproj"))
+ {
+ if (string_utils::toLower(it->filename()) == projFileLower)
+ return *it;
+ else
+ rprojFiles.push_back(*it);
+ }
+ }
+
+ // if we found only one rproj file then return it
+ if (rprojFiles.size() == 1)
+ {
+ return rprojFiles.at(0);
+ }
+ // more than one, take most recent
+ else if (rprojFiles.size() > 1 )
+ {
+ projectFile = rprojFiles.at(0);
+ for (std::vector<FilePath>::const_iterator it = rprojFiles.begin();
+ it != rprojFiles.end();
+ ++it)
+ {
+ if (it->lastWriteTime() > projectFile.lastWriteTime())
+ projectFile = *it;
+ }
+
+ return projectFile;
+ }
+ // didn't find one
+ else
+ {
+ return FilePath();
+ }
+}
+
+bool updateSetPackageInstallArgsDefault(RProjectConfig* pConfig)
+{
+ if (pConfig->packageInstallArgs == kPackageInstallArgsPreviousDefault)
+ {
+ pConfig->packageInstallArgs = kPackageInstallArgsDefault;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+} // namespace r_util
+} // namespace core
+
+
+
diff --git a/src/cpp/core/r_util/RSessionLaunchProfile.cpp b/src/cpp/core/r_util/RSessionLaunchProfile.cpp
new file mode 100644
index 0000000..a031fed
--- /dev/null
+++ b/src/cpp/core/r_util/RSessionLaunchProfile.cpp
@@ -0,0 +1,107 @@
+/*
+ * RSessionLaunchProfile.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RSessionLaunchProfile.hpp>
+
+#include <boost/foreach.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+json::Object optionsAsJson(const core::system::Options& options)
+{
+ json::Object optionsJson;
+ BOOST_FOREACH(const core::system::Option& option, options)
+ {
+ optionsJson[option.first] = option.second;
+ }
+ return optionsJson;
+}
+
+core::system::Options optionsFromJson(const json::Object& optionsJson)
+{
+ core::system::Options options;
+ BOOST_FOREACH(const json::Member& member, optionsJson)
+ {
+ std::string name = member.first;
+ json::Value value = member.second;
+ if (value.type() == json::StringType)
+ options.push_back(std::make_pair(name, value.get_str()));
+ }
+ return options;
+}
+
+} // anonymous namespace
+
+
+json::Object sessionLaunchProfileToJson(const SessionLaunchProfile& profile)
+{
+ json::Object profileJson;
+ profileJson["username"] = profile.username;
+ profileJson["executablePath"] = profile.executablePath;
+ json::Object configJson;
+ configJson["args"] = optionsAsJson(profile.config.args);
+ configJson["environment"] = optionsAsJson(profile.config.environment);
+ configJson["stdStreamBehavior"] = profile.config.stdStreamBehavior;
+ profileJson["config"] = configJson;
+ return profileJson;
+}
+
+SessionLaunchProfile sessionLaunchProfileFromJson(
+ const json::Object& jsonProfile)
+{
+ SessionLaunchProfile profile;
+
+ // read top level fields
+ json::Object configJson;
+ Error error = json::readObject(jsonProfile,
+ "username", &profile.username,
+ "executablePath", &profile.executablePath,
+ "config", &configJson);
+ if (error)
+ LOG_ERROR(error);
+
+
+ // read config object
+ json::Object argsJson, envJson;
+ int stdStreamBehavior = 0;
+ error = json::readObject(configJson,
+ "args", &argsJson,
+ "environment", &envJson,
+ "stdStreamBehavior", &stdStreamBehavior);
+ if (error)
+ LOG_ERROR(error);
+
+ // populate config
+ profile.config.args = optionsFromJson(argsJson);
+ profile.config.environment = optionsFromJson(envJson);
+ profile.config.stdStreamBehavior =
+ static_cast<core::system::StdStreamBehavior>(stdStreamBehavior);
+
+ // return profile
+ return profile;
+}
+
+
+
+} // namespace r_util
+} // namespace core
+
+
+
diff --git a/src/cpp/core/r_util/RSourceIndex.cpp b/src/cpp/core/r_util/RSourceIndex.cpp
new file mode 100644
index 0000000..06ca24a
--- /dev/null
+++ b/src/cpp/core/r_util/RSourceIndex.cpp
@@ -0,0 +1,377 @@
+/*
+ * RSourceIndex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RSourceIndex.hpp>
+
+#include <boost/algorithm/string.hpp>
+
+#include <core/StringUtils.hpp>
+
+#include <core/r_util/RTokenizer.hpp>
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+std::wstring removeQuoteDelims(const std::wstring& input)
+{
+ // since we know this was parsed as a quoted string we can just remove
+ // the first and last characters
+ if (input.size() >= 2)
+ return std::wstring(input, 1, input.size() - 2);
+ else
+ return std::wstring();
+}
+
+std::string contentAsUtf8(const RToken& token)
+{
+ if (token.type() == RToken::STRING)
+ return string_utils::wideToUtf8(removeQuoteDelims(token.content()));
+ else
+ return string_utils::wideToUtf8(token.content());
+}
+
+bool isTokenType(RTokens::const_iterator begin,
+ RTokens::const_iterator end,
+ const wchar_t type)
+{
+ return begin != end && begin->type() == type;
+}
+
+bool advancePastNextToken(
+ RTokens::const_iterator* pBegin,
+ RTokens::const_iterator end,
+ const boost::function<bool(const RToken&)>& tokenCondition)
+{
+ // alias and advance past current token
+ RTokens::const_iterator& begin = *pBegin;
+ begin++;
+
+ // check for end
+ if (begin == end)
+ {
+ return false;
+ }
+
+ // check for condition
+ else if (!tokenCondition(*begin))
+ {
+ return false;
+ }
+
+ // advance and return true
+ else
+ {
+ begin++;
+ return true;
+ }
+}
+
+bool advancePastNextToken(RTokens::const_iterator* pBegin,
+ RTokens::const_iterator end,
+ const wchar_t type)
+{
+ return advancePastNextToken(pBegin,
+ end,
+ boost::bind(&RToken::isType, _1, type));
+}
+
+bool advancePastNextOperatorToken(RTokens::const_iterator* pBegin,
+ RTokens::const_iterator end,
+ const std::wstring& op)
+{
+ return advancePastNextToken(pBegin,
+ end,
+ boost::bind(&RToken::isOperator, _1, op));
+}
+
+// statics for signature parsing comparisons
+const std::wstring kOpEquals(L"=");
+const std::wstring kSignatureSymbol(L"signature");
+const std::wstring kCSymbol(L"c");
+
+void parseSignatureFunction(RTokens::const_iterator begin,
+ RTokens::const_iterator end,
+ std::vector<RS4MethodParam>* pSignature)
+{
+ // advance to args
+ if (!advancePastNextToken(&begin, end, RToken::LPAREN))
+ return;
+
+ // pick off args
+ while (isTokenType(begin, end, RToken::ID))
+ {
+ // get the name
+ std::string name = contentAsUtf8(*begin);
+
+ // advance and check for equals
+ if (!advancePastNextOperatorToken(&begin, end, kOpEquals))
+ break;
+
+ // check for string
+ if (isTokenType(begin, end, RToken::STRING))
+ {
+ // get type and add to signature
+ std::string type = contentAsUtf8(*begin);
+ pSignature->push_back(RS4MethodParam(name, type));
+
+ // advance past comma to next argument
+ if (!advancePastNextToken(&begin, end, RToken::COMMA))
+ break;
+ }
+ else
+ {
+ break;
+ }
+ }
+}
+
+void parseSignatureCharacterVector(RTokens::const_iterator begin,
+ RTokens::const_iterator end,
+ std::vector<RS4MethodParam>* pSignature)
+{
+ // advance to args
+ if (!advancePastNextToken(&begin, end, RToken::LPAREN))
+ return;
+
+ // pick off args
+ while (isTokenType(begin, end, RToken::STRING))
+ {
+ // get the type string
+ pSignature->push_back(RS4MethodParam(contentAsUtf8(*begin)));
+
+ // advance past comma to next argument
+ if (!advancePastNextToken(&begin, end, RToken::COMMA))
+ break;
+ }
+}
+
+void parseSignature(RTokens::const_iterator begin,
+ RTokens::const_iterator end,
+ std::vector<RS4MethodParam>* pSignature)
+{
+ // the signature parameter of the setMethod function can take any
+ // of the following forms
+ //
+ // setMethod("plot", signature(x="track", y="missing")
+ // setMethod("plot", c("track", "missing")
+ // setMethod("plot", "track"
+ //
+
+ if (isTokenType(begin, end, RToken::ID))
+ {
+ // call to signature function
+ if (begin->contentEquals(kSignatureSymbol))
+ parseSignatureFunction(begin, end, pSignature);
+
+ // simple list of types
+ else if (begin->contentEquals(kCSymbol))
+ parseSignatureCharacterVector(begin, end, pSignature);
+ }
+
+ // a solitary quoted string (one element character vector)
+ else if (isTokenType(begin, end, RToken::STRING))
+ {
+ pSignature->push_back(RS4MethodParam(contentAsUtf8(*begin)));
+ }
+}
+
+
+} // anonymous namespace
+
+RSourceIndex::RSourceIndex(const std::string& context,
+ const std::string& code)
+ : context_(context)
+{
+ // convert code to wide
+ std::wstring wCode = string_utils::utf8ToWide(code, context);
+
+ // determine where the linebreaks are and initialize an iterator
+ // used for scanning them
+ std::vector<std::size_t> newlineLocs;
+ std::size_t nextNL = 0;
+ while ( (nextNL = wCode.find(L'\n', nextNL)) != std::string::npos )
+ newlineLocs.push_back(nextNL++);
+ std::vector<std::size_t>::const_iterator newlineIter = newlineLocs.begin();
+ std::vector<std::size_t>::const_iterator endNewlines = newlineLocs.end();
+
+ // tokenize
+ RTokens rTokens(wCode, RTokens::StripWhitespace | RTokens::StripComments);
+
+ // scan for function, method, and class definitions (track indent level)
+ int braceLevel = 0;
+ std::wstring function(L"function");
+ std::wstring set(L"set");
+ std::wstring setGeneric(L"setGeneric");
+ std::wstring setGroupGeneric(L"setGroupGeneric");
+ std::wstring setMethod(L"setMethod");
+ std::wstring setClass(L"setClass");
+ std::wstring setClassUnion(L"setClassUnion");
+ std::wstring setRefClass(L"setRefClass");
+ std::wstring eqOp(L"=");
+ std::wstring assignOp(L"<-");
+ std::wstring parentAssignOp(L"<<-");
+ for (std::size_t i=0; i<rTokens.size(); i++)
+ {
+ // initial name, qualifer, and type are nil
+ RSourceItem::Type type = RSourceItem::None;
+ std::wstring name;
+ std::size_t tokenOffset = -1;
+ bool isSetMethod = false;
+ std::vector<RS4MethodParam> signature;
+
+ // alias the token
+ const RToken& token = rTokens.at(i);
+
+ // see if this is a begin or end brace and update the level
+ if (token.type() == RToken::LBRACE)
+ {
+ braceLevel++;
+ continue;
+ }
+
+ else if (token.type() == RToken::RBRACE)
+ {
+ braceLevel--;
+ continue;
+ }
+ // bail for non-identifiers
+ else if (token.type() != RToken::ID)
+ {
+ continue;
+ }
+
+ // is this a potential method or class definition?
+ if (token.contentStartsWith(set))
+ {
+ RSourceItem::Type setType = RSourceItem::None;
+
+ if (token.contentEquals(setMethod))
+ {
+ isSetMethod = true;
+ setType = RSourceItem::Method;
+ }
+ else if (token.contentEquals(setGeneric) ||
+ token.contentEquals(setGroupGeneric))
+ {
+ setType = RSourceItem::Method;
+ }
+ else if (token.contentEquals(setClass) ||
+ token.contentEquals(setClassUnion) ||
+ token.contentEquals(setRefClass))
+ {
+ setType = RSourceItem::Class;
+ }
+ else
+ {
+ continue;
+ }
+
+ // make sure there are at least 4 more tokens
+ if ( (i + 3) >= rTokens.size())
+ continue;
+
+ // check for the rest of the token sequene for a valid call to set*
+ if ( (rTokens.at(i+1).type() != RToken::LPAREN) ||
+ (rTokens.at(i+2).type() != RToken::STRING) ||
+ (rTokens.at(i+3).type() != RToken::COMMA))
+ continue;
+
+ // found a class or method definition (will find location below)
+ type = setType;
+ name = removeQuoteDelims(rTokens.at(i+2).content());
+ tokenOffset = token.offset();
+
+ // if this was a setMethod then try to lookahead for the signature
+ if (isSetMethod)
+ {
+ parseSignature(rTokens.begin() + (i+4),
+ rTokens.end(),
+ &signature);
+ }
+ }
+
+ // is this a function?
+ else if (token.contentEquals(function))
+ {
+ // if there is no room for an operator and identifier prior
+ // to the function then bail
+ if (i < 2)
+ continue;
+
+ // check for an assignment operator
+ const RToken& opToken = rTokens.at(i-1);
+ if ( opToken.type() != RToken::OPER)
+ continue;
+ if (!opToken.isOperator(eqOp) &&
+ !opToken.isOperator(assignOp) &&
+ !opToken.isOperator(parentAssignOp))
+ continue;
+
+ // check for an identifier
+ const RToken& idToken = rTokens.at(i-2);
+ if ( idToken.type() != RToken::ID )
+ continue;
+
+ // if there is another previous token make sure it isn't a
+ // comma or an open paren
+ if ( i > 2 )
+ {
+ const RToken& prevToken = rTokens.at(i-3);
+ if (prevToken.type() == RToken::LPAREN ||
+ prevToken.type() == RToken::COMMA)
+ continue;
+ }
+
+ // if we got this far then this is a function definition
+ type = RSourceItem::Function;
+ name = idToken.content();
+ tokenOffset = idToken.offset();
+ }
+ else
+ {
+ continue;
+ }
+
+ // compute the line by starting at the current line index and
+ // finding the first newline which is after the idToken offset
+ newlineIter = std::upper_bound(newlineIter,
+ endNewlines,
+ tokenOffset);
+ std::size_t line = newlineIter - newlineLocs.begin() + 1;
+
+ // compute column by comparing the offset to the PREVIOUS newline
+ // (guard against no previous newline)
+ std::size_t column;
+ if (line > 1)
+ column = tokenOffset - *(newlineIter - 1);
+ else
+ column = tokenOffset;
+
+ // add to index
+ items_.push_back(RSourceItem(type,
+ string_utils::wideToUtf8(name),
+ signature,
+ braceLevel,
+ line,
+ column));
+ }
+}
+
+} // namespace r_util
+} // namespace core
+
+
diff --git a/src/cpp/core/r_util/RTokenizer.cpp b/src/cpp/core/r_util/RTokenizer.cpp
new file mode 100644
index 0000000..3a89cd9
--- /dev/null
+++ b/src/cpp/core/r_util/RTokenizer.cpp
@@ -0,0 +1,373 @@
+/*
+ * RTokenizer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// A very low level assertion (line 595 of cpp_regex_traits.hpp) is firing
+// on WindowsXP when we create a boost::wregex within R session. This code
+// is part of boost attempting to detect the sort syntax, and the assertion
+// is that there are no nulls in a buffer after a call to locale->transform.
+// Within the same method there are workarounds for both borland c++ and
+// dinkumware c++ std library to strip embedded nulls, so it seems as if
+// there is some variation in whether nulls come out of the call to transforn.
+// Note also that boost has a macro titled BOOST_REGEX_NO_WIN32 which we are
+// not using, which means that boost regex does call into native windows APIs
+// for locale oriented functions (thus explaining a difference in runtime
+// behavior across platforms). Tokenization appears to continue to work
+// correctly in the presence of the assertion (with the annoying side effect
+// of logging an assertion every time the product starts up)
+
+#include <core/r_util/RTokenizer.hpp>
+
+#include <boost/regex.hpp>
+
+#include <iostream>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/StringUtils.hpp>
+
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+class TokenPatterns
+{
+private:
+ friend TokenPatterns& tokenPatterns();
+ TokenPatterns()
+ : NUMBER(L"[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*)?[Li]?"),
+ HEX_NUMBER(L"0x[0-9a-fA-F]*L?"),
+ USER_OPERATOR(L"%[^%]*%"),
+ QUOTED_IDENTIFIER(L"`[^`]*`"),
+ UNTIL_END_QUOTE(L"[\\\\\'\"]"),
+ WHITESPACE(L"[\\s\x00A0\x3000]+"),
+ COMMENT(L"#.*?$")
+ {
+ }
+
+public:
+ const boost::wregex NUMBER;
+ const boost::wregex HEX_NUMBER;
+ const boost::wregex USER_OPERATOR;
+ const boost::wregex QUOTED_IDENTIFIER;
+ const boost::wregex UNTIL_END_QUOTE;
+ const boost::wregex WHITESPACE;
+ const boost::wregex COMMENT;
+};
+
+TokenPatterns& tokenPatterns()
+{
+ static TokenPatterns instance;
+ return instance;
+}
+
+} // anonymous namespace
+
+
+const wchar_t RToken::LPAREN = L'(';
+const wchar_t RToken::RPAREN = L')';
+const wchar_t RToken::LBRACKET = L'[';
+const wchar_t RToken::RBRACKET = L']';
+const wchar_t RToken::LBRACE = L'{';
+const wchar_t RToken::RBRACE = L'}';
+const wchar_t RToken::COMMA = L',';
+const wchar_t RToken::SEMI = L';';
+const wchar_t RToken::WHITESPACE = 0x1001;
+const wchar_t RToken::STRING = 0x1002;
+const wchar_t RToken::NUMBER = 0x1003;
+const wchar_t RToken::ID = 0x1004;
+const wchar_t RToken::OPER = 0x1005;
+const wchar_t RToken::UOPER = 0x1006;
+const wchar_t RToken::ERR = 0x1007;
+const wchar_t RToken::LDBRACKET = 0x1008;
+const wchar_t RToken::RDBRACKET = 0x1009;
+const wchar_t RToken::COMMENT = 0x100A;
+
+
+RToken RTokenizer::nextToken()
+{
+ if (eol())
+ return RToken() ;
+
+ wchar_t c = peek() ;
+
+ switch (c)
+ {
+ case L'(': case L')':
+ case L'{': case L'}':
+ case L';': case L',':
+ return consumeToken(c, 1) ;
+ case L'[':
+ if (peek(1) == L'[')
+ return consumeToken(RToken::LDBRACKET, 2) ;
+ else
+ return consumeToken(c, 1) ;
+ case L']':
+ if (peek(1) == L']')
+ return consumeToken(RToken::RDBRACKET, 2) ;
+ else
+ return consumeToken(c, 1) ;
+ case L'"':
+ case L'\'':
+ return matchStringLiteral() ;
+ case L'`':
+ return matchQuotedIdentifier();
+ case L'#':
+ return matchComment();
+ case L'%':
+ return matchUserOperator();
+ case L' ': case L'\t': case L'\r': case L'\n':
+ case L'\x00A0': case L'\x3000':
+ return matchWhitespace() ;
+ }
+
+ wchar_t cNext = peek(1) ;
+
+ if ((c >= L'0' && c <= L'9')
+ || (c == L'.' && cNext >= L'0' && cNext <= L'9'))
+ {
+ RToken numberToken = matchNumber() ;
+ if (numberToken.length() > 0)
+ return numberToken ;
+ }
+
+ if (string_utils::isalnum(c) || c == L'.')
+ {
+ // From Section 10.3.2, identifiers must not start with
+ // a digit, nor may they start with a period followed by
+ // a digit.
+ //
+ // Since we're not checking for either condition, we must
+ // match on identifiers AFTER we have already tried to
+ // match on number.
+ return matchIdentifier() ;
+ }
+
+ RToken oper = matchOperator() ;
+ if (oper)
+ return oper ;
+
+ // Error!!
+ return consumeToken(RToken::ERR, 1) ;
+}
+
+
+
+RToken RTokenizer::matchWhitespace()
+{
+ std::wstring whitespace = peek(tokenPatterns().WHITESPACE) ;
+ return consumeToken(RToken::WHITESPACE, whitespace.length()) ;
+}
+
+RToken RTokenizer::matchStringLiteral()
+{
+ std::wstring::const_iterator start = pos_ ;
+ wchar_t quot = eat() ;
+
+ while (!eol())
+ {
+ eatUntil(tokenPatterns().UNTIL_END_QUOTE);
+
+ if (eol())
+ break ;
+
+ wchar_t c = eat() ;
+ if (c == quot)
+ {
+ // NOTE: this is where we used to set wellFormed = true
+ break ;
+ }
+
+ if (c == L'\\')
+ {
+ if (!eol())
+ eat() ;
+
+ // Actually the escape expression can be longer than
+ // just the backslash plus one character--but we don't
+ // need to distinguish escape expressions from other
+ // literal text other than for the purposes of breaking
+ // out of the string
+ }
+ }
+
+ // NOTE: the Java version of the tokenizer returns a special RStringToken
+ // subclass which includes the wellFormed flag as an attribute. Our
+ // implementation of RToken is stack based so doesn't support subclasses
+ // (because they will be sliced when copied). If we need the well
+ // formed flag we can just add it onto RToken.
+ return RToken(RToken::STRING,
+ start,
+ pos_,
+ start - data_.begin());
+}
+
+RToken RTokenizer::matchNumber()
+{
+ std::wstring num = peek(tokenPatterns().HEX_NUMBER) ;
+ if (num.empty())
+ num = peek(tokenPatterns().NUMBER) ;
+
+ return consumeToken(RToken::NUMBER, num.length());
+}
+
+RToken RTokenizer::matchIdentifier()
+{
+ std::wstring::const_iterator start = pos_ ;
+ eat();
+ while (string_utils::isalnum(peek()) || peek() == L'.' || peek() == L'_')
+ eat();
+ return RToken(RToken::ID,
+ start,
+ pos_,
+ start - data_.begin()) ;
+}
+
+RToken RTokenizer::matchQuotedIdentifier()
+{
+ std::wstring iden = peek(tokenPatterns().QUOTED_IDENTIFIER) ;
+ if (iden.empty())
+ return consumeToken(RToken::ERR, 1);
+ else
+ return consumeToken(RToken::ID, iden.length());
+}
+
+RToken RTokenizer::matchComment()
+{
+ std::wstring comment = peek(tokenPatterns().COMMENT);
+ return consumeToken(RToken::COMMENT, comment.length());
+}
+
+RToken RTokenizer::matchUserOperator()
+{
+ std::wstring oper = peek(tokenPatterns().USER_OPERATOR) ;
+ if (oper.empty())
+ return consumeToken(RToken::ERR, 1) ;
+ else
+ return consumeToken(RToken::UOPER, oper.length()) ;
+}
+
+
+RToken RTokenizer::matchOperator()
+{
+ wchar_t cNext = peek(1) ;
+
+ switch (peek())
+ {
+ case L'+': case L'*': case L'/':
+ case L'^': case L'&': case L'|':
+ case L'~': case L'$': case L':':
+ // single-character operators
+ return consumeToken(RToken::OPER, 1) ;
+ case L'-': // also ->
+ return consumeToken(RToken::OPER, cNext == L'>' ? 2 : 1) ;
+ case L'>': // also >=
+ return consumeToken(RToken::OPER, cNext == L'=' ? 2 : 1) ;
+ case L'<': // also <- and <=
+ return consumeToken(RToken::OPER, cNext == L'=' ? 2 :
+ cNext == L'-' ? 2 :
+ 1) ;
+ case L'=': // also ==
+ return consumeToken(RToken::OPER, cNext == L'=' ? 2 : 1) ;
+ case L'!': // also !=
+ return consumeToken(RToken::OPER, cNext == L'=' ? 2 : 1) ;
+ default:
+ return RToken() ;
+ }
+}
+
+bool RTokenizer::eol()
+{
+ return pos_ >= data_.end();
+}
+
+wchar_t RTokenizer::peek()
+{
+ return peek(0) ;
+}
+
+wchar_t RTokenizer::peek(std::size_t lookahead)
+{
+ if ((pos_ + lookahead) >= data_.end())
+ return 0 ;
+ else
+ return *(pos_ + lookahead) ;
+}
+
+wchar_t RTokenizer::eat()
+{
+ wchar_t result = *pos_;
+ pos_++ ;
+ return result ;
+}
+
+std::wstring RTokenizer::peek(const boost::wregex& regex)
+{
+ boost::wsmatch match;
+ std::wstring::const_iterator end = data_.end();
+ boost::match_flag_type flg = boost::match_default | boost::match_continuous;
+ if (boost::regex_search(pos_, end, match, regex, flg))
+ {
+ return match[0];
+ }
+ else
+ {
+ return std::wstring();
+ }
+}
+
+void RTokenizer::eatUntil(const boost::wregex& regex)
+{
+ boost::wsmatch match;
+ std::wstring::const_iterator end = data_.end();
+ if (boost::regex_search(pos_, end, match, regex))
+ {
+ pos_ = match[0].first;
+ }
+ else
+ {
+ // eat all on failure to match
+ pos_ = data_.end();
+ }
+}
+
+
+RToken RTokenizer::consumeToken(wchar_t tokenType, std::size_t length)
+{
+ if (length == 0)
+ {
+ LOG_WARNING_MESSAGE("Can't create zero-length token");
+ return RToken();
+ }
+ else if ((pos_ + length) > data_.end())
+ {
+ LOG_WARNING_MESSAGE("Premature EOF");
+ return RToken();
+ }
+
+ std::wstring::const_iterator start = pos_ ;
+ pos_ += length ;
+ return RToken(tokenType,
+ start,
+ pos_,
+ start - data_.begin()) ;
+}
+
+
+} // namespace r_util
+} // namespace core
+
+
diff --git a/src/cpp/core/r_util/RTokenizerTests.cpp b/src/cpp/core/r_util/RTokenizerTests.cpp
new file mode 100644
index 0000000..1763395
--- /dev/null
+++ b/src/cpp/core/r_util/RTokenizerTests.cpp
@@ -0,0 +1,244 @@
+/*
+ * RTokenizerTests.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RTokenizer.hpp>
+
+#include <iostream>
+
+#include <boost/assert.hpp>
+#include <boost/foreach.hpp>
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+class Verifier
+{
+public:
+ Verifier(wchar_t defaultTokenType,
+ const std::wstring& prefix,
+ const std::wstring& suffix)
+ : defaultTokenType_(defaultTokenType),
+ prefix_(prefix),
+ suffix_(suffix)
+ {
+ }
+
+ void verify(const std::wstring& value)
+ {
+ verify(defaultTokenType_, value) ;
+ }
+
+ void verify(wchar_t tokenType, const std::wstring& value)
+ {
+
+ RTokenizer rt(prefix_ + value + suffix_) ;
+ RToken t ;
+ while ((t = rt.nextToken()))
+ {
+ if (t.offset() == prefix_.length())
+ {
+ std::wcout << value << std::endl;
+ BOOST_ASSERT(tokenType == t.type());
+ BOOST_ASSERT(value.length() == t.length());
+ BOOST_ASSERT(value == t.content());
+ return ;
+ }
+ }
+
+ }
+
+ void verify(const std::deque<std::wstring>& values)
+ {
+ verify(defaultTokenType_, values);
+ }
+
+ void verify(int tokenType, const std::deque<std::wstring>& values)
+ {
+ BOOST_FOREACH(const std::wstring& value, values)
+ verify(tokenType, value);
+ }
+
+private:
+ const wchar_t defaultTokenType_ ;
+ const std::wstring prefix_ ;
+ const std::wstring suffix_ ;
+
+};
+
+
+void testVoid()
+{
+ RTokenizer rt(L"") ;
+ BOOST_ASSERT(!rt.nextToken());
+}
+
+void testSimple()
+{
+ Verifier v(RToken::ERR, L" ", L" ") ;
+ v.verify(RToken::LPAREN, L"(") ;
+ v.verify(RToken::RPAREN, L")") ;
+ v.verify(RToken::LBRACKET, L"[") ;
+ v.verify(RToken::RBRACKET, L"]") ;
+ v.verify(RToken::LBRACE, L"{") ;
+ v.verify(RToken::RBRACE, L"}") ;
+ v.verify(RToken::COMMA, L",") ;
+ v.verify(RToken::SEMI, L";") ;
+}
+
+void testError()
+{
+ Verifier v(RToken::ERR, L" ", L" ") ;
+}
+
+void testComment()
+{
+ Verifier v(RToken::COMMENT, L" ", L"\n") ;
+ v.verify(L"#");
+ v.verify(L"# foo #");
+
+ Verifier v2(RToken::COMMENT, L" ", L"\r\n") ;
+ v2.verify(L"#");
+ v2.verify(L"# foo #");
+}
+
+
+void testNumbers()
+{
+ Verifier v(RToken::NUMBER, L" ", L" ") ;
+ v.verify(L"1");
+ v.verify(L"10");
+ v.verify(L"0.1");
+ v.verify(L".2");
+ v.verify(L"1e-7");
+ v.verify(L"1.2e+7");
+ v.verify(L"2e");
+ v.verify(L"3e+");
+ v.verify(L"0x");
+ v.verify(L"0x0");
+ v.verify(L"0xDEADBEEF");
+ v.verify(L"0xcafebad");
+ v.verify(L"1L");
+ v.verify(L"0x10L");
+ v.verify(L"1000000L");
+ v.verify(L"1e6L");
+ v.verify(L"1.1L");
+ v.verify(L"1e-3L");
+ v.verify(L"2i");
+ v.verify(L"4.1i");
+ v.verify(L"1e-2i");
+}
+
+
+void testOperators()
+{
+ Verifier v(RToken::OPER, L" ", L" ") ;
+ v.verify(L"+");
+ v.verify(L"-");
+ v.verify(L"*");
+ v.verify(L"/");
+ v.verify(L"^");
+ v.verify(L">");
+ v.verify(L">=");
+ v.verify(L"<");
+ v.verify(L"<=");
+ v.verify(L"==");
+ v.verify(L"!=");
+ v.verify(L"!");
+ v.verify(L"&");
+ v.verify(L"|");
+ v.verify(L"~");
+ v.verify(L"->");
+ v.verify(L"<-");
+ v.verify(L"$");
+ v.verify(L":");
+ v.verify(L"=");
+}
+
+void testUOperators()
+{
+ Verifier v(RToken::UOPER, L" ", L" ") ;
+ v.verify(L"%%");
+ v.verify(L"%test test%");
+}
+
+void testStrings()
+{
+ Verifier v(RToken::STRING, L" ", L" ") ;
+ v.verify(L"\"test\"") ;
+ v.verify(L"\" '$\t\r\n\\\"\"") ;
+ v.verify(L"\"\"") ;
+ v.verify(L"''") ;
+ v.verify(L"'\"'") ;
+ v.verify(L"'\\\"'") ;
+ v.verify(L"'\n'") ;
+ v.verify(L"'foo bar \\U654'") ;
+}
+
+void testIdentifiers()
+{
+ Verifier v(RToken::ID, L" ", L" ") ;
+ v.verify(L".");
+ v.verify(L"...");
+ v.verify(L"..1");
+ v.verify(L"..2");
+ v.verify(L"foo");
+ v.verify(L"FOO");
+ v.verify(L"f1");
+ v.verify(L"a_b");
+ v.verify(L"ab_");
+ v.verify(L"`foo`");
+ v.verify(L"`$@!$@#$`");
+ v.verify(L"`a\n\"'b`");
+
+ v.verify(L"\x00C1" L"qc1");
+ v.verify(L"\x00C1" L"qc1" L"\x00C1");
+}
+
+void testWhitespace()
+{
+ Verifier v(RToken::WHITESPACE, L"a", L"z") ;
+ v.verify(L" ");
+ v.verify(L" ");
+ v.verify(L"\t\n");
+ v.verify(L"\x00A0") ;
+ v.verify(L" \x3000 ") ;
+ v.verify(L" \x00A0\t\x3000\r ") ;
+}
+
+
+} // anonymous namespace
+
+
+void runTokenizerTests()
+{
+ testVoid();
+ testComment();
+ testSimple();
+ testError();
+ testNumbers();
+ testOperators();
+ testUOperators();
+ testStrings();
+ testIdentifiers();
+ testWhitespace();
+}
+
+
+} // namespace r_util
+} // namespace core
+
+
diff --git a/src/cpp/core/r_util/RToolsInfo.cpp b/src/cpp/core/r_util/RToolsInfo.cpp
new file mode 100644
index 0000000..f82d5fa
--- /dev/null
+++ b/src/cpp/core/r_util/RToolsInfo.cpp
@@ -0,0 +1,174 @@
+/*
+ * RToolsInfo.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/r_util/RToolsInfo.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Log.hpp>
+#include <core/StringUtils.hpp>
+
+#include <core/system/RegistryKey.hpp>
+
+#ifndef KEY_WOW64_32KEY
+#define KEY_WOW64_32KEY 0x0200
+#endif
+
+namespace core {
+namespace r_util {
+
+namespace {
+
+} // anonymous namespace
+
+
+RToolsInfo::RToolsInfo(const std::string& name, const FilePath& installPath)
+ : name_(name), installPath_(installPath)
+{
+ std::string versionMin, versionMax;
+ std::vector<std::string> relativePathEntries;
+ if (name == "2.11")
+ {
+ versionMin = "2.10.0";
+ versionMax = "2.11.1";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("perl/bin");
+ relativePathEntries.push_back("MinGW/bin");
+ }
+ else if (name == "2.12")
+ {
+ versionMin = "2.12.0";
+ versionMax = "2.12.2";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("perl/bin");
+ relativePathEntries.push_back("MinGW/bin");
+ relativePathEntries.push_back("MinGW64/bin");
+ }
+ else if (name == "2.13")
+ {
+ versionMin = "2.13.0";
+ versionMax = "2.13.2";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("MinGW/bin");
+ relativePathEntries.push_back("MinGW64/bin");
+ }
+ else if (name == "2.14")
+ {
+ versionMin = "2.13.0";
+ versionMax = "2.14.2";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("MinGW/bin");
+ relativePathEntries.push_back("MinGW64/bin");
+ }
+ else if (name == "2.15")
+ {
+ versionMin = "2.14.2";
+ versionMax = "2.15.1";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("gcc-4.6.3/bin");
+ }
+ else if (name == "2.16" || name == "3.0")
+ {
+ versionMin = "2.15.2";
+ versionMax = "3.0.99";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("gcc-4.6.3/bin");
+ }
+ else if (name == "3.1")
+ {
+ versionMin = "3.0.0";
+ versionMax = "3.1.99";
+ relativePathEntries.push_back("bin");
+ relativePathEntries.push_back("gcc-4.6.3/bin");
+ }
+
+ // build version predicate and path list if we can
+ if (!versionMin.empty())
+ {
+ boost::format fmt("getRversion() >= \"%1%\" && getRversion() <= \"%2%\"");
+ versionPredicate_ = boost::str(fmt % versionMin % versionMax);
+
+ BOOST_FOREACH(const std::string& relativePath, relativePathEntries)
+ {
+ pathEntries_.push_back(installPath_.childPath(relativePath));
+ }
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, const RToolsInfo& info)
+{
+ os << "Rtools " << info.name() << std::endl;
+ os << info.versionPredicate() << std::endl;
+ BOOST_FOREACH(const FilePath& pathEntry, info.pathEntries())
+ {
+ os << pathEntry << std::endl;
+ }
+ return os;
+}
+
+Error scanRegistryForRTools(std::vector<RToolsInfo>* pRTools)
+{
+ core::system::RegistryKey regKey;
+ Error error = regKey.open(HKEY_LOCAL_MACHINE,
+ "Software\\R-core\\Rtools",
+ KEY_READ | KEY_WOW64_32KEY);
+ if (error)
+ {
+ if (error.code() != boost::system::errc::no_such_file_or_directory)
+ return error;
+ else
+ return Success();
+ }
+
+ std::vector<std::string> keys = regKey.keyNames();
+ for (int i = 0; i < keys.size(); i++)
+ {
+ std::string name = keys.at(i);
+ core::system::RegistryKey verKey;
+ error = verKey.open(regKey.handle(),
+ name,
+ KEY_READ | KEY_WOW64_32KEY);
+ if (error)
+ {
+ LOG_ERROR(error);
+ continue;
+ }
+
+ std::string installPath = verKey.getStringValue("InstallPath", "");
+ if (!installPath.empty())
+ {
+ std::string utf8InstallPath = string_utils::systemToUtf8(installPath);
+ RToolsInfo toolsInfo(name, FilePath(utf8InstallPath));
+ if (toolsInfo.isStillInstalled())
+ {
+ if (toolsInfo.isRecognized())
+ pRTools->push_back(toolsInfo);
+ else
+ LOG_WARNING_MESSAGE("Unknown Rtools version: " + name);
+ }
+ }
+ }
+
+ return Success();
+}
+
+
+} // namespace r_util
+} // namespace core
+
+
+
diff --git a/src/cpp/core/spelling/HunspellCustomDictionaries.cpp b/src/cpp/core/spelling/HunspellCustomDictionaries.cpp
new file mode 100644
index 0000000..54dae92
--- /dev/null
+++ b/src/cpp/core/spelling/HunspellCustomDictionaries.cpp
@@ -0,0 +1,85 @@
+/*
+ * HunspellCustomDictionaries.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/spelling/HunspellCustomDictionaries.hpp>
+
+#include <boost/bind.hpp>
+
+#include <core/Algorithm.hpp>
+
+namespace core {
+namespace spelling {
+
+std::vector<std::string> HunspellCustomDictionaries::dictionaries() const
+{
+ std::vector<std::string> dictionaries;
+ Error error = customDictionariesDir_.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return dictionaries;
+ }
+
+ std::vector<FilePath> children;
+ error = customDictionariesDir_.children(&children);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return dictionaries;
+ }
+
+ algorithm::copy_transformed_if(
+ children.begin(),
+ children.end(),
+ std::back_inserter(dictionaries),
+ boost::bind(&FilePath::hasExtensionLowerCase, _1, ".dic"),
+ boost::bind(&FilePath::stem, _1));
+
+ return dictionaries;
+}
+
+FilePath HunspellCustomDictionaries::dictionaryPath(
+ const std::string& name) const
+{
+ return customDictionariesDir_.childPath(name + ".dic");
+}
+
+Error HunspellCustomDictionaries::add(const FilePath& dicPath) const
+{
+ // validate .dic extension
+ if (!dicPath.hasExtensionLowerCase(".dic"))
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+
+ // remove existing with same name
+ std::string name = dicPath.stem();
+ Error error = remove(name);
+ if (error)
+ LOG_ERROR(error);
+
+ // add it
+ return dicPath.copy(dictionaryPath(name));
+}
+
+Error HunspellCustomDictionaries::remove(const std::string& name) const
+{
+ return dictionaryPath(name).removeIfExists();
+}
+
+
+} // namespace spelling
+} // namespace core
diff --git a/src/cpp/core/spelling/HunspellDictionaryManager.cpp b/src/cpp/core/spelling/HunspellDictionaryManager.cpp
new file mode 100644
index 0000000..f51130d
--- /dev/null
+++ b/src/cpp/core/spelling/HunspellDictionaryManager.cpp
@@ -0,0 +1,205 @@
+/*
+ * HunspellDictionaryManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/spelling/HunspellDictionaryManager.hpp>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Algorithm.hpp>
+
+namespace core {
+namespace spelling {
+
+namespace {
+
+struct KnownDictionary
+{
+ const char* id;
+ const char* name;
+} ;
+
+KnownDictionary s_knownDictionaries[] =
+{
+ { "bg_BG", "Bulgarian" },
+ { "ca_ES", "Catalan" },
+ { "cs_CZ", "Czech" },
+ { "da_DK", "Danish" },
+ { "de_DE", "German" },
+ { "de_DE_neu", "German (New)" },
+ { "el_GR", "Greek" },
+ { "en_AU", "English (Australia)" },
+ { "en_CA", "English (Canada)" },
+ { "en_GB", "English (United Kingdom)" },
+ { "en_US", "English (United States)" },
+ { "es_ES", "Spanish" },
+ { "fr_FR", "French" },
+ { "hr_HR", "Croatian" },
+ { "hu-HU", "Hungarian" },
+ { "id_ID", "Indonesian" },
+ { "it_IT", "Italian" },
+ { "lt_LT", "Lithuanian" },
+ { "lv_LV", "Latvian" },
+ { "nb_NO", "Norwegian" },
+ { "nl_NL", "Dutch" },
+ { "pl_PL", "Polish" },
+ { "pt_BR", "Portuguese (Brazil)" },
+ { "pt_PT", "Portuguese (Portugal)" },
+ { "ro_RO", "Romanian" },
+ { "ru_RU", "Russian" },
+ { "sh", "Serbo-Croatian" },
+ { "sk_SK", "Slovak" },
+ { "sl_SI", "Slovenian" },
+ { "sr", "Serbian" },
+ { "sv_SE", "Swedish" },
+ { "uk_UA", "Ukrainian" },
+ { "vi_VN", "Vietnamese" },
+ { NULL, NULL }
+};
+
+FilePath dicPathForAffPath(const FilePath& affPath)
+{
+ return affPath.parent().childPath(affPath.stem() + ".dic");
+}
+
+bool isDictionaryAff(const FilePath& filePath)
+{
+ return (filePath.extensionLowerCase() == ".aff") &&
+ dicPathForAffPath(filePath).exists();
+}
+
+HunspellDictionary fromAffFile(const FilePath& filePath)
+{
+ return HunspellDictionary(filePath);
+}
+
+Error listAffFiles(const FilePath& baseDir, std::vector<FilePath>* pAffFiles)
+{
+ if (!baseDir.exists())
+ return Success();
+
+ std::vector<FilePath> children;
+ Error error = baseDir.children(&children);
+ if (error)
+ return error;
+
+ core::algorithm::copy_if(children.begin(),
+ children.end(),
+ std::back_inserter(*pAffFiles),
+ isDictionaryAff);
+
+ return Success();
+}
+
+bool compareByName(const HunspellDictionary& dict1,
+ const HunspellDictionary& dict2)
+{
+ return dict1.name() < dict2.name();
+}
+
+} // anonymous namespace
+
+
+std::string HunspellDictionary::name() const
+{
+ std::string dictId = id();
+ for (KnownDictionary* dict = s_knownDictionaries; dict->name; ++dict)
+ {
+ if (dictId == dict->id)
+ return dict->name;
+ }
+
+ return dictId;
+}
+
+FilePath HunspellDictionary::dicPath() const
+{
+ return dicPathForAffPath(affPath_);
+}
+
+Error HunspellDictionaryManager::availableLanguages(
+ std::vector<HunspellDictionary>* pDictionaries) const
+{
+ // first try the user languages dir
+ std::vector<FilePath> affFiles;
+
+ if (allLanguagesInstalled())
+ {
+ Error error = listAffFiles(allLanguagesDir(), &affFiles);
+ if (error)
+ return error;
+ }
+ else
+ {
+ Error error = listAffFiles(coreLanguagesDir_, &affFiles);
+ if (error)
+ return error;
+ }
+
+ // always check the languages-extra directory as well (and auto-create
+ // it so users who look for it will see it)
+ FilePath userLangsDir = userLanguagesDir();
+ Error error = userLangsDir.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ error = listAffFiles(userLangsDir, &affFiles);
+ if (error)
+ LOG_ERROR(error);
+
+ // convert to dictionaries
+ std::transform(affFiles.begin(),
+ affFiles.end(),
+ std::back_inserter(*pDictionaries),
+ fromAffFile);
+
+ // sort them by name
+ std::sort(pDictionaries->begin(), pDictionaries->end(), compareByName);
+
+ return Success();
+}
+
+HunspellDictionary HunspellDictionaryManager::dictionaryForLanguageId(
+ const std::string& langId) const
+{
+ std::string affFile = langId + ".aff";
+
+ // first check to see whether it exists in the user languages directory
+ FilePath userLangsAff = userLanguagesDir().complete(affFile);
+ if (userLangsAff.exists())
+ return HunspellDictionary(userLangsAff);
+ else if (allLanguagesInstalled())
+ return HunspellDictionary(allLanguagesDir().complete(affFile));
+ else
+ return HunspellDictionary(coreLanguagesDir_.complete(affFile));
+}
+
+const HunspellCustomDictionaries& HunspellDictionaryManager::custom() const
+{
+ return customDicts_;
+}
+
+FilePath HunspellDictionaryManager::allLanguagesDir() const
+{
+ return userDir_.childPath("languages-system");
+}
+
+FilePath HunspellDictionaryManager::userLanguagesDir() const
+{
+ return userDir_.childPath("languages-user");
+}
+
+
+} // namespace spelling
+} // namespace core
diff --git a/src/cpp/core/spelling/HunspellSpellingEngine.cpp b/src/cpp/core/spelling/HunspellSpellingEngine.cpp
new file mode 100644
index 0000000..7d7111f
--- /dev/null
+++ b/src/cpp/core/spelling/HunspellSpellingEngine.cpp
@@ -0,0 +1,489 @@
+/*
+ * HunspellSpellingEngine.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/spelling/HunspellSpellingEngine.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/spelling/HunspellDictionaryManager.hpp>
+
+// Including the hunspell headers caused compilation errors for Windows 64-bit
+// builds. The trouble seemd to be a 'near' macro defined somewhere in the
+// mingw toolchain (couldn't find where). Fix this by undefining 'near' right
+// before we include hunspell.hxx.
+#if defined(near)
+#undef near
+#endif
+#include "hunspell/hunspell.hxx"
+
+namespace core {
+namespace spelling {
+
+namespace {
+
+// remove morphological description from text
+void removeMorphologicalDescription(std::string* pText)
+{
+ std::size_t tabPos = pText->find('\t');
+ if (tabPos != std::string::npos)
+ *pText = pText->substr(0, tabPos);
+}
+
+// extract the word from the dic_delta line -- remove the
+// optional affix and then replace escaped / chracters
+bool parseDicDeltaLine(std::string line,
+ std::string* pWord,
+ std::string* pAffix)
+{
+ // skip empty lines
+ boost::algorithm::trim(line);
+ if (line.empty())
+ return false;
+
+ // find delimiter
+ std::size_t wordEndPos = line.size();
+ for (std::size_t i = 0; i < line.size(); i++)
+ {
+ if (line[i] == '/' && i > 0 && line[i - 1] != '\\')
+ {
+ wordEndPos = i;
+ break;
+ }
+ }
+
+ // extract word and escape forward slashes
+ std::string word = line.substr(0, wordEndPos);
+ *pWord = boost::algorithm::replace_all_copy(word, "\\/", "/");
+
+ // extract affix (if any)
+ if (wordEndPos < line.size() - 1)
+ {
+ *pAffix = line.substr(wordEndPos + 1);
+ removeMorphologicalDescription(pAffix);
+ }
+ else
+ {
+ pAffix->clear();
+ removeMorphologicalDescription(pWord);
+ }
+
+ return true;
+}
+
+// The hunspell api allows you to add words with affixes by providing an
+// example word already in the dictionary that has the same affix. The google
+// english .dic_delta files use the hard-coded integer values 6 and 7 to
+// (respecitvely) indicate possesive (M) and possesive/plural (MS) affixes.
+// Therefore, this function needs to return words that are marked as
+// M or MS consistently in the main dictionaries of the 4 english variations.
+// If we want to extend affix support to other languages we'll need to
+// do a simillar mapping
+std::string exampleWordForEnglishAffix(const std::string& affix)
+{
+ if (affix == "6") // possesive (M)
+ return "Arcadia";
+ else if (affix == "7") // possessive or plural (MS)
+ return "beverage";
+ else
+ return std::string();
+}
+
+class SpellChecker : boost::noncopyable
+{
+public:
+ virtual ~SpellChecker() {}
+ virtual Error checkSpelling(const std::string& word, bool *pCorrect) = 0;
+ virtual Error suggestionList(const std::string& word,
+ std::vector<std::string>* pSugs) = 0;
+ virtual Error wordChars(std::wstring* pWordChars) = 0;
+};
+
+class NoSpellChecker : public SpellChecker
+{
+public:
+ Error checkSpelling(const std::string& word, bool *pCorrect)
+ {
+ *pCorrect = true;
+ return Success();
+ }
+
+ Error suggestionList(const std::string& word,
+ std::vector<std::string>* pSugs)
+ {
+ return Success();
+ }
+
+ Error wordChars(std::wstring *pWordChars)
+ {
+ return Success();
+ }
+};
+
+class HunspellSpellChecker : public SpellChecker
+{
+public:
+ HunspellSpellChecker()
+ {
+ }
+
+ virtual ~HunspellSpellChecker()
+ {
+ try
+ {
+ pHunspell_.reset();
+ }
+ catch(...)
+ {
+ }
+ }
+
+ Error initialize(const HunspellDictionary& dictionary,
+ const IconvstrFunction& iconvstrFunc)
+ {
+ // validate that dictionaries exist
+ if (!dictionary.affPath().exists())
+ return core::fileNotFoundError(dictionary.affPath(), ERROR_LOCATION);
+ if (!dictionary.dicPath().exists())
+ return core::fileNotFoundError(dictionary.dicPath(), ERROR_LOCATION);
+
+ // convert paths to system encoding before sending to external API
+ std::string systemAffPath = string_utils::utf8ToSystem(
+ dictionary.affPath().absolutePath());
+ std::string systemDicPath = string_utils::utf8ToSystem(
+ dictionary.dicPath().absolutePath());
+
+ // initialize hunspell, iconvstrFunc_, and encoding_
+ pHunspell_.reset(new Hunspell(systemAffPath.c_str(),
+ systemDicPath.c_str()));
+ iconvstrFunc_ = iconvstrFunc;
+ encoding_ = pHunspell_->get_dic_encoding();
+
+ // add words from dic_delta if available
+ FilePath dicPath = dictionary.dicPath();
+ FilePath dicDeltaPath = dicPath.parent().childPath(
+ dicPath.stem() + ".dic_delta");
+ if (dicDeltaPath.exists())
+ {
+ Error error = mergeDicDeltaFile(dicDeltaPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // return success
+ return Success();
+ }
+
+ Error wordChars(std::wstring *pWordChars)
+ {
+ int len;
+ unsigned short *pChars = pHunspell_->get_wordchars_utf16(&len);
+
+ for (int i = 0; i < len; i++)
+ pWordChars->push_back(pChars[i]);
+
+ return Success();
+ }
+
+private:
+
+ // helpers
+ void copyAndFreeHunspellVector(std::vector<std::string>* pVec,
+ char **wlst,
+ int len)
+ {
+ for (int i=0; i < len; i++)
+ {
+ pVec->push_back(wlst[i]);
+ }
+ pHunspell_->free_list(&wlst, len);
+ }
+
+ Error mergeDicDeltaFile(const FilePath& dicDeltaPath)
+ {
+ // determine whether we are going to support affixes -- we do this for
+ // english only right now because we can correctly (by inspection) map
+ // the chromium numeric affix indicators (6 and 7) to the right
+ // hunspell example words. it's worth investigating whether we can do
+ // this for other languages as well
+ bool addAffixes = boost::algorithm::starts_with(dicDeltaPath.stem(),
+ "en_");
+
+ // read the file and strip the BOM
+ std::string contents;
+ Error error = core::readStringFromFile(dicDeltaPath, &contents);
+ if (error)
+ return error;
+ core::stripBOM(&contents);
+
+ // split into lines
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines,
+ contents,
+ boost::algorithm::is_any_of("\n"));
+
+ // parse lines for words
+ bool added;
+ std::string word, affix, example;
+ BOOST_FOREACH(const std::string& line, lines)
+ {
+ if (parseDicDeltaLine(line, &word, &affix))
+ {
+ example = exampleWordForEnglishAffix(affix);
+ if (!example.empty() && addAffixes)
+ {
+ Error error = addWordWithAffix(word, example, &added);
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ Error error = addWord(word, &added);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+ }
+
+ return Success();
+ }
+
+
+public:
+ Error checkSpelling(const std::string& word, bool *pCorrect)
+ {
+ std::string encoded;
+ Error error = iconvstrFunc_(word,"UTF-8",encoding_,false,&encoded);
+ if (error)
+ return error;
+
+ *pCorrect = pHunspell_->spell(encoded.c_str());
+ return Success();
+ }
+
+ Error suggestionList(const std::string& word, std::vector<std::string>* pSug)
+ {
+ std::string encoded;
+ Error error = iconvstrFunc_(word,"UTF-8",encoding_,false,&encoded);
+ if (error)
+ return error;
+
+ char ** wlst;
+ int ns = pHunspell_->suggest(&wlst,encoded.c_str());
+ copyAndFreeHunspellVector(pSug,wlst,ns);
+
+ BOOST_FOREACH(std::string& sug, *pSug)
+ {
+ error = iconvstrFunc_(sug, encoding_, "UTF-8", true, &sug);
+ if (error)
+ return error;
+ }
+
+ return Success();
+ }
+
+ Error addWord(const std::string& word, bool *pAdded)
+ {
+ std::string encoded;
+ Error error = iconvstrFunc_(word,"UTF-8",encoding_,false,&encoded);
+ if (error)
+ return error;
+
+ // Following the Hunspell::add method through it's various code paths
+ // it seems the return value is always 0, meaning there's really no
+ // error ever thrown if the method fails.
+ *pAdded = (pHunspell_->add(encoded.c_str()) == 0);
+ return Success();
+ }
+
+ Error addWordWithAffix(const std::string& word,
+ const std::string& example,
+ bool *pAdded)
+ {
+ std::string wordEncoded;
+ Error error = iconvstrFunc_(word,
+ "UTF-8",
+ encoding_,
+ false,
+ &wordEncoded);
+ if (error)
+ return error;
+
+ std::string exampleEncoded;
+ error = iconvstrFunc_(example,
+ "UTF-8",
+ encoding_,
+ false,
+ &exampleEncoded);
+ if (error)
+ return error;
+
+ *pAdded = (pHunspell_->add_with_affix(wordEncoded.c_str(),
+ exampleEncoded.c_str()) == 0);
+ return Success();
+ }
+
+ // Hunspell dictionary files are simple: the first line is an integer
+ // indicating the number of entries (one per line), and each line contains
+ // a word followed by '/' plus modifier flags. Example user.dic:
+ // ----------
+ // 3
+ // lol/S
+ // rofl/S
+ // tl;dr/S
+ // ----------
+ // The '/S' modifier treats 'ROFL','rofl', and 'Rofl' as correct spellings.
+ Error addDictionary(const FilePath& dicPath,
+ const std::string& key,
+ bool *pAdded)
+ {
+ if (!dicPath.exists())
+ return core::fileNotFoundError(dicPath, ERROR_LOCATION);
+
+ // Convert path to system encoding before sending to external api
+ std::string systemDicPath = string_utils::utf8ToSystem(dicPath.absolutePath());
+ *pAdded = (pHunspell_->add_dic(systemDicPath.c_str(),key.c_str()) == 0);
+ return Success();
+ }
+
+private:
+ boost::scoped_ptr<Hunspell> pHunspell_;
+ IconvstrFunction iconvstrFunc_;
+ std::string encoding_;
+};
+
+} // anonymous namespace
+
+struct HunspellSpellingEngine::Impl
+{
+ Impl(const std::string& langId,
+ const HunspellDictionaryManager& dictionaryManager,
+ const IconvstrFunction& iconvstrFunction)
+ : currentLangId_(langId),
+ dictManager_(dictionaryManager),
+ iconvstrFunction_(iconvstrFunction)
+ {
+ }
+
+ void useDictionary(const std::string& langId)
+ {
+ if (dictionaryContextChanged(langId))
+ resetDictionaries(langId);
+ }
+
+ SpellChecker& spellChecker()
+ {
+ if (!pSpellChecker_)
+ resetDictionaries(currentLangId_);
+
+ return *pSpellChecker_;
+ }
+
+private:
+ bool dictionaryContextChanged(const std::string& langId)
+ {
+ return(langId != currentLangId_ ||
+ dictManager_.custom().dictionaries() != currentCustomDicts_);
+ }
+
+ void resetDictionaries(const std::string& langId)
+ {
+ HunspellDictionary dict = dictManager_.dictionaryForLanguageId(langId);
+ if (!dict.empty())
+ {
+ HunspellSpellChecker* pHunspell = new HunspellSpellChecker();
+ pSpellChecker_.reset(pHunspell);
+
+ Error error = pHunspell->initialize(dict, iconvstrFunction_);
+ if (!error)
+ {
+ currentLangId_ = langId;
+ currentCustomDicts_ = dictManager_.custom().dictionaries();
+ BOOST_FOREACH(const std::string& dict, currentCustomDicts_)
+ {
+ bool added;
+ FilePath dicPath = dictManager_.custom().dictionaryPath(dict);
+ Error error = pHunspell->addDictionary(dicPath,
+ dicPath.stem(),
+ &added);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+ else
+ {
+ LOG_ERROR(error);
+
+ pSpellChecker_.reset(new NoSpellChecker());
+ }
+ }
+ else
+ {
+ pSpellChecker_.reset(new NoSpellChecker());
+ }
+ }
+
+
+
+private:
+ std::string currentLangId_;
+ std::vector<std::string> currentCustomDicts_;
+ HunspellDictionaryManager dictManager_;
+ IconvstrFunction iconvstrFunction_;
+ boost::shared_ptr<SpellChecker> pSpellChecker_;
+};
+
+
+HunspellSpellingEngine::HunspellSpellingEngine(
+ const std::string& langId,
+ const HunspellDictionaryManager& dictionaryManager,
+ const IconvstrFunction& iconvstrFunction)
+ : pImpl_(new Impl(langId, dictionaryManager, iconvstrFunction))
+{
+}
+
+
+void HunspellSpellingEngine::useDictionary(const std::string& langId)
+{
+ pImpl_->useDictionary(langId);
+}
+
+Error HunspellSpellingEngine::checkSpelling(const std::string& word,
+ bool *pCorrect)
+{
+ return pImpl_->spellChecker().checkSpelling(word, pCorrect);
+}
+
+Error HunspellSpellingEngine::suggestionList(const std::string& word,
+ std::vector<std::string>* pSugs)
+{
+ return pImpl_->spellChecker().suggestionList(word, pSugs);
+}
+
+Error HunspellSpellingEngine::wordChars(std::wstring *pChars)
+{
+ return pImpl_->spellChecker().wordChars(pChars);
+}
+
+} // namespace spelling
+} // namespace core
+
+
+
diff --git a/src/cpp/core/spelling/hunspell/CMakeLists.txt b/src/cpp/core/spelling/hunspell/CMakeLists.txt
new file mode 100644
index 0000000..c25e9ae
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(CORE_HUNSPELL)
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+
+# disable warnings
+add_definitions(-w)
+
+# include files
+file(GLOB_RECURSE CORE_HUNSPELL_HEADER_FILES "*.h*")
+
+
+# source files
+set(CORE_HUNSPELL_SOURCE_FILES
+ affentry.cxx
+ affixmgr.cxx
+ csutil.cxx
+ dictmgr.cxx
+ hashmgr.cxx
+ suggestmgr.cxx
+ license.myspell
+ license.hunspell
+ phonet.cxx
+ filemgr.cxx
+ hunzip.cxx
+ hunspell.cxx
+ replist.cxx
+)
+
+
+# include directories
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# define library
+add_library(rstudio-core-hunspell STATIC
+ ${CORE_HUNSPELL_SOURCE_FILES}
+ ${CORE_HUNSPELL_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(rstudio-core-hunspell
+
+)
diff --git a/src/cpp/core/spelling/hunspell/README b/src/cpp/core/spelling/hunspell/README
new file mode 100644
index 0000000..b452096
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/README
@@ -0,0 +1,21 @@
+Hunspell spell checker and morphological analyser library
+
+Documentation, tests, examples: http://hunspell.sourceforge.net
+
+Author of Hunspell:
+László Németh (nemethl (at) gyorsposta.hu)
+
+Hunspell based on OpenOffice.org's Myspell. MySpell's author:
+Kevin Hendricks (kevin.hendricks (at) sympatico.ca)
+
+License: GPL 2.0/LGPL 2.1/MPL 1.1 tri-license
+
+The contents of this library may be used under the terms of
+the GNU General Public License Version 2 or later (the "GPL"), or
+the GNU Lesser General Public License Version 2.1 or later (the "LGPL",
+see http://gnu.org/copyleft/lesser.html) or the Mozilla Public License
+Version 1.1 or later (the "MPL", see http://mozilla.org/MPL/MPL-1.1.html).
+
+Software distributed under these licenses is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the licences
+for the specific language governing rights and limitations under the licenses.
diff --git a/src/cpp/core/spelling/hunspell/affentry.cxx b/src/cpp/core/spelling/hunspell/affentry.cxx
new file mode 100644
index 0000000..fef0cca
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/affentry.cxx
@@ -0,0 +1,962 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "affentry.hxx"
+#include "csutil.hxx"
+
+PfxEntry::PfxEntry(AffixMgr* pmgr, affentry* dp)
+{
+ // register affix manager
+ pmyMgr = pmgr;
+
+ // set up its initial values
+
+ aflag = dp->aflag; // flag
+ strip = dp->strip; // string to strip
+ appnd = dp->appnd; // string to append
+ stripl = dp->stripl; // length of strip string
+ appndl = dp->appndl; // length of append string
+ numconds = dp->numconds; // length of the condition
+ opts = dp->opts; // cross product flag
+ // then copy over all of the conditions
+ if (opts & aeLONGCOND) {
+ memcpy(c.conds, dp->c.l.conds1, MAXCONDLEN_1);
+ c.l.conds2 = dp->c.l.conds2;
+ } else memcpy(c.conds, dp->c.conds, MAXCONDLEN);
+ next = NULL;
+ nextne = NULL;
+ nexteq = NULL;
+ morphcode = dp->morphcode;
+ contclass = dp->contclass;
+ contclasslen = dp->contclasslen;
+}
+
+
+PfxEntry::~PfxEntry()
+{
+ aflag = 0;
+ if (appnd) free(appnd);
+ if (strip) free(strip);
+ pmyMgr = NULL;
+ appnd = NULL;
+ strip = NULL;
+ if (opts & aeLONGCOND) free(c.l.conds2);
+ if (morphcode && !(opts & aeALIASM)) free(morphcode);
+ if (contclass && !(opts & aeALIASF)) free(contclass);
+}
+
+// add prefix to this word assuming conditions hold
+char * PfxEntry::add(const char * word, int len)
+{
+ char tword[MAXWORDUTF8LEN + 4];
+
+ if ((len > stripl || (len == 0 && pmyMgr->get_fullstrip())) &&
+ (len >= numconds) && test_condition(word) &&
+ (!stripl || (strncmp(word, strip, stripl) == 0)) &&
+ ((MAXWORDUTF8LEN + 4) > (len + appndl - stripl))) {
+ /* we have a match so add prefix */
+ char * pp = tword;
+ if (appndl) {
+ strcpy(tword,appnd);
+ pp += appndl;
+ }
+ strcpy(pp, (word + stripl));
+ return mystrdup(tword);
+ }
+ return NULL;
+}
+
+inline char * PfxEntry::nextchar(char * p) {
+ if (p) {
+ p++;
+ if (opts & aeLONGCOND) {
+ // jump to the 2nd part of the condition
+ if (p == c.conds + MAXCONDLEN_1) return c.l.conds2;
+ // end of the MAXCONDLEN length condition
+ } else if (p == c.conds + MAXCONDLEN) return NULL;
+ return *p ? p : NULL;
+ }
+ return NULL;
+}
+
+inline int PfxEntry::test_condition(const char * st)
+{
+ const char * pos = NULL; // group with pos input position
+ bool neg = false; // complementer
+ bool ingroup = false; // character in the group
+ if (numconds == 0) return 1;
+ char * p = c.conds;
+ while (1) {
+ switch (*p) {
+ case '\0': return 1;
+ case '[': {
+ neg = false;
+ ingroup = false;
+ p = nextchar(p);
+ pos = st; break;
+ }
+ case '^': { p = nextchar(p); neg = true; break; }
+ case ']': {
+ if ((neg && ingroup) || (!neg && !ingroup)) return 0;
+ pos = NULL;
+ p = nextchar(p);
+ // skip the next character
+ if (!ingroup && *st) for (st++; (opts & aeUTF8) && (*st & 0xc0) == 0x80; st++);
+ if (*st == '\0' && p) return 0; // word <= condition
+ break;
+ }
+ case '.': if (!pos) { // dots are not metacharacters in groups: [.]
+ p = nextchar(p);
+ // skip the next character
+ for (st++; (opts & aeUTF8) && (*st & 0xc0) == 0x80; st++);
+ if (*st == '\0' && p) return 0; // word <= condition
+ break;
+ }
+ default: {
+ if (*st == *p) {
+ st++;
+ p = nextchar(p);
+ if ((opts & aeUTF8) && (*(st - 1) & 0x80)) { // multibyte
+ while (p && (*p & 0xc0) == 0x80) { // character
+ if (*p != *st) {
+ if (!pos) return 0;
+ st = pos;
+ break;
+ }
+ p = nextchar(p);
+ st++;
+ }
+ if (pos && st != pos) {
+ ingroup = true;
+ while (p && *p != ']' && (p = nextchar(p)));
+ }
+ } else if (pos) {
+ ingroup = true;
+ while (p && *p != ']' && (p = nextchar(p)));
+ }
+ } else if (pos) { // group
+ p = nextchar(p);
+ } else return 0;
+ }
+ }
+ if (!p) return 1;
+ }
+}
+
+// check if this prefix entry matches
+struct hentry * PfxEntry::checkword(const char * word, int len, char in_compound, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ struct hentry * he; // hash entry of root word or NULL
+ char tmpword[MAXWORDUTF8LEN + 4];
+
+ // on entry prefix is 0 length or already matches the beginning of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if (tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) {
+
+ // generate new root word by removing prefix and adding
+ // back any characters that would have been stripped
+
+ if (stripl) strcpy (tmpword, strip);
+ strcpy ((tmpword + stripl), (word + appndl));
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then check if resulting
+ // root word in the dictionary
+
+ if (test_condition(tmpword)) {
+ tmpl += stripl;
+ if ((he = pmyMgr->lookup(tmpword)) != NULL) {
+ do {
+ if (TESTAFF(he->astr, aflag, he->alen) &&
+ // forbid single prefixes with needaffix flag
+ ! TESTAFF(contclass, pmyMgr->get_needaffix(), contclasslen) &&
+ // needflag
+ ((!needflag) || TESTAFF(he->astr, needflag, he->alen) ||
+ (contclass && TESTAFF(contclass, needflag, contclasslen))))
+ return he;
+ he = he->next_homonym; // check homonyms
+ } while (he);
+ }
+
+ // prefix matched but no root word was found
+ // if aeXPRODUCT is allowed, try again but now
+ // ross checked combined with a suffix
+
+ //if ((opts & aeXPRODUCT) && in_compound) {
+ if ((opts & aeXPRODUCT)) {
+ he = pmyMgr->suffix_check(tmpword, tmpl, aeXPRODUCT, this, NULL,
+ 0, NULL, FLAG_NULL, needflag, in_compound);
+ if (he) return he;
+ }
+ }
+ }
+ return NULL;
+}
+
+// check if this prefix entry matches
+struct hentry * PfxEntry::check_twosfx(const char * word, int len,
+ char in_compound, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ struct hentry * he; // hash entry of root word or NULL
+ char tmpword[MAXWORDUTF8LEN + 4];
+
+ // on entry prefix is 0 length or already matches the beginning of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing prefix and adding
+ // back any characters that would have been stripped
+
+ if (stripl) strcpy (tmpword, strip);
+ strcpy ((tmpword + stripl), (word + appndl));
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then check if resulting
+ // root word in the dictionary
+
+ if (test_condition(tmpword)) {
+ tmpl += stripl;
+
+ // prefix matched but no root word was found
+ // if aeXPRODUCT is allowed, try again but now
+ // cross checked combined with a suffix
+
+ if ((opts & aeXPRODUCT) && (in_compound != IN_CPD_BEGIN)) {
+ he = pmyMgr->suffix_check_twosfx(tmpword, tmpl, aeXPRODUCT, this, needflag);
+ if (he) return he;
+ }
+ }
+ }
+ return NULL;
+}
+
+// check if this prefix entry matches
+char * PfxEntry::check_twosfx_morph(const char * word, int len,
+ char in_compound, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ char tmpword[MAXWORDUTF8LEN + 4];
+
+ // on entry prefix is 0 length or already matches the beginning of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing prefix and adding
+ // back any characters that would have been stripped
+
+ if (stripl) strcpy (tmpword, strip);
+ strcpy ((tmpword + stripl), (word + appndl));
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then check if resulting
+ // root word in the dictionary
+
+ if (test_condition(tmpword)) {
+ tmpl += stripl;
+
+ // prefix matched but no root word was found
+ // if aeXPRODUCT is allowed, try again but now
+ // ross checked combined with a suffix
+
+ if ((opts & aeXPRODUCT) && (in_compound != IN_CPD_BEGIN)) {
+ return pmyMgr->suffix_check_twosfx_morph(tmpword, tmpl,
+ aeXPRODUCT, this, needflag);
+ }
+ }
+ }
+ return NULL;
+}
+
+// check if this prefix entry matches
+char * PfxEntry::check_morph(const char * word, int len, char in_compound, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ struct hentry * he; // hash entry of root word or NULL
+ char tmpword[MAXWORDUTF8LEN + 4];
+ char result[MAXLNLEN];
+ char * st;
+
+ *result = '\0';
+
+ // on entry prefix is 0 length or already matches the beginning of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing prefix and adding
+ // back any characters that would have been stripped
+
+ if (stripl) strcpy (tmpword, strip);
+ strcpy ((tmpword + stripl), (word + appndl));
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then check if resulting
+ // root word in the dictionary
+
+ if (test_condition(tmpword)) {
+ tmpl += stripl;
+ if ((he = pmyMgr->lookup(tmpword)) != NULL) {
+ do {
+ if (TESTAFF(he->astr, aflag, he->alen) &&
+ // forbid single prefixes with needaffix flag
+ ! TESTAFF(contclass, pmyMgr->get_needaffix(), contclasslen) &&
+ // needflag
+ ((!needflag) || TESTAFF(he->astr, needflag, he->alen) ||
+ (contclass && TESTAFF(contclass, needflag, contclasslen)))) {
+ if (morphcode) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, morphcode, MAXLNLEN);
+ } else mystrcat(result,getKey(), MAXLNLEN);
+ if (!HENTRY_FIND(he, MORPH_STEM)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_STEM, MAXLNLEN);
+ mystrcat(result, HENTRY_WORD(he), MAXLNLEN);
+ }
+ // store the pointer of the hash entry
+ if (HENTRY_DATA(he)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, HENTRY_DATA2(he), MAXLNLEN);
+ } else {
+ // return with debug information
+ char * flag = pmyMgr->encode_flag(getFlag());
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_FLAG, MAXLNLEN);
+ mystrcat(result, flag, MAXLNLEN);
+ free(flag);
+ }
+ mystrcat(result, "\n", MAXLNLEN);
+ }
+ he = he->next_homonym;
+ } while (he);
+ }
+
+ // prefix matched but no root word was found
+ // if aeXPRODUCT is allowed, try again but now
+ // ross checked combined with a suffix
+
+ if ((opts & aeXPRODUCT) && (in_compound != IN_CPD_BEGIN)) {
+ st = pmyMgr->suffix_check_morph(tmpword, tmpl, aeXPRODUCT, this,
+ FLAG_NULL, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ }
+ }
+ }
+
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+SfxEntry::SfxEntry(AffixMgr * pmgr, affentry* dp)
+{
+ // register affix manager
+ pmyMgr = pmgr;
+
+ // set up its initial values
+ aflag = dp->aflag; // char flag
+ strip = dp->strip; // string to strip
+ appnd = dp->appnd; // string to append
+ stripl = dp->stripl; // length of strip string
+ appndl = dp->appndl; // length of append string
+ numconds = dp->numconds; // length of the condition
+ opts = dp->opts; // cross product flag
+
+ // then copy over all of the conditions
+ if (opts & aeLONGCOND) {
+ memcpy(c.l.conds1, dp->c.l.conds1, MAXCONDLEN_1);
+ c.l.conds2 = dp->c.l.conds2;
+ } else memcpy(c.conds, dp->c.conds, MAXCONDLEN);
+
+ rappnd = myrevstrdup(appnd);
+ morphcode = dp->morphcode;
+ contclass = dp->contclass;
+ contclasslen = dp->contclasslen;
+}
+
+
+SfxEntry::~SfxEntry()
+{
+ aflag = 0;
+ if (appnd) free(appnd);
+ if (rappnd) free(rappnd);
+ if (strip) free(strip);
+ pmyMgr = NULL;
+ appnd = NULL;
+ strip = NULL;
+ if (opts & aeLONGCOND) free(c.l.conds2);
+ if (morphcode && !(opts & aeALIASM)) free(morphcode);
+ if (contclass && !(opts & aeALIASF)) free(contclass);
+}
+
+// add suffix to this word assuming conditions hold
+char * SfxEntry::add(const char * word, int len)
+{
+ char tword[MAXWORDUTF8LEN + 4];
+
+ /* make sure all conditions match */
+ if ((len > stripl || (len == 0 && pmyMgr->get_fullstrip())) &&
+ (len >= numconds) && test_condition(word + len, word) &&
+ (!stripl || (strcmp(word + len - stripl, strip) == 0)) &&
+ ((MAXWORDUTF8LEN + 4) > (len + appndl - stripl))) {
+ /* we have a match so add suffix */
+ strcpy(tword,word);
+ if (appndl) {
+ strcpy(tword + len - stripl, appnd);
+ } else {
+ *(tword + len - stripl) = '\0';
+ }
+ return mystrdup(tword);
+ }
+ return NULL;
+}
+
+inline char * SfxEntry::nextchar(char * p) {
+ if (p) {
+ p++;
+ if (opts & aeLONGCOND) {
+ // jump to the 2nd part of the condition
+ if (p == c.l.conds1 + MAXCONDLEN_1) return c.l.conds2;
+ // end of the MAXCONDLEN length condition
+ } else if (p == c.conds + MAXCONDLEN) return NULL;
+ return *p ? p : NULL;
+ }
+ return NULL;
+}
+
+inline int SfxEntry::test_condition(const char * st, const char * beg)
+{
+ const char * pos = NULL; // group with pos input position
+ bool neg = false; // complementer
+ bool ingroup = false; // character in the group
+ if (numconds == 0) return 1;
+ char * p = c.conds;
+ st--;
+ int i = 1;
+ while (1) {
+ switch (*p) {
+ case '\0': return 1;
+ case '[': { p = nextchar(p); pos = st; break; }
+ case '^': { p = nextchar(p); neg = true; break; }
+ case ']': { if (!neg && !ingroup) return 0;
+ i++;
+ // skip the next character
+ if (!ingroup) {
+ for (; (opts & aeUTF8) && (st >= beg) && (*st & 0xc0) == 0x80; st--);
+ st--;
+ }
+ pos = NULL;
+ neg = false;
+ ingroup = false;
+ p = nextchar(p);
+ if (st < beg && p) return 0; // word <= condition
+ break;
+ }
+ case '.': if (!pos) { // dots are not metacharacters in groups: [.]
+ p = nextchar(p);
+ // skip the next character
+ for (st--; (opts & aeUTF8) && (st >= beg) && (*st & 0xc0) == 0x80; st--);
+ if (st < beg) { // word <= condition
+ if (p) return 0; else return 1;
+ }
+ if ((opts & aeUTF8) && (*st & 0x80)) { // head of the UTF-8 character
+ st--;
+ if (st < beg) { // word <= condition
+ if (p) return 0; else return 1;
+ }
+ }
+ break;
+ }
+ default: {
+ if (*st == *p) {
+ p = nextchar(p);
+ if ((opts & aeUTF8) && (*st & 0x80)) {
+ st--;
+ while (p && (st >= beg)) {
+ if (*p != *st) {
+ if (!pos) return 0;
+ st = pos;
+ break;
+ }
+ // first byte of the UTF-8 multibyte character
+ if ((*p & 0xc0) != 0x80) break;
+ p = nextchar(p);
+ st--;
+ }
+ if (pos && st != pos) {
+ if (neg) return 0;
+ else if (i == numconds) return 1;
+ ingroup = true;
+ while (p && *p != ']' && (p = nextchar(p)));
+ st--;
+ }
+ if (p && *p != ']') p = nextchar(p);
+ } else if (pos) {
+ if (neg) return 0;
+ else if (i == numconds) return 1;
+ ingroup = true;
+ while (p && *p != ']' && (p = nextchar(p)));
+// if (p && *p != ']') p = nextchar(p);
+ st--;
+ }
+ if (!pos) {
+ i++;
+ st--;
+ }
+ if (st < beg && p && *p != ']') return 0; // word <= condition
+ } else if (pos) { // group
+ p = nextchar(p);
+ } else return 0;
+ }
+ }
+ if (!p) return 1;
+ }
+}
+
+// see if this suffix is present in the word
+struct hentry * SfxEntry::checkword(const char * word, int len, int optflags,
+ PfxEntry* ppfx, char ** wlst, int maxSug, int * ns, const FLAG cclass, const FLAG needflag,
+ const FLAG badflag)
+{
+ int tmpl; // length of tmpword
+ struct hentry * he; // hash entry pointer
+ unsigned char * cp;
+ char tmpword[MAXWORDUTF8LEN + 4];
+ PfxEntry* ep = ppfx;
+
+ // if this suffix is being cross checked with a prefix
+ // but it does not support cross products skip it
+
+ if (((optflags & aeXPRODUCT) != 0) && ((opts & aeXPRODUCT) == 0))
+ return NULL;
+
+ // upon entry suffix is 0 length or already matches the end of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+ // the second condition is not enough for UTF-8 strings
+ // it checked in test_condition()
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing suffix and adding
+ // back any characters that would have been stripped or
+ // or null terminating the shorter string
+
+ strcpy (tmpword, word);
+ cp = (unsigned char *)(tmpword + tmpl);
+ if (stripl) {
+ strcpy ((char *)cp, strip);
+ tmpl += stripl;
+ cp = (unsigned char *)(tmpword + tmpl);
+ } else *cp = '\0';
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then check if resulting
+ // root word in the dictionary
+
+ if (test_condition((char *) cp, (char *) tmpword)) {
+
+#ifdef SZOSZABLYA_POSSIBLE_ROOTS
+ fprintf(stdout,"%s %s %c\n", word, tmpword, aflag);
+#endif
+ if ((he = pmyMgr->lookup(tmpword)) != NULL) {
+ do {
+ // check conditional suffix (enabled by prefix)
+ if ((TESTAFF(he->astr, aflag, he->alen) || (ep && ep->getCont() &&
+ TESTAFF(ep->getCont(), aflag, ep->getContLen()))) &&
+ (((optflags & aeXPRODUCT) == 0) ||
+ (ep && TESTAFF(he->astr, ep->getFlag(), he->alen)) ||
+ // enabled by prefix
+ ((contclass) && (ep && TESTAFF(contclass, ep->getFlag(), contclasslen)))
+ ) &&
+ // handle cont. class
+ ((!cclass) ||
+ ((contclass) && TESTAFF(contclass, cclass, contclasslen))
+ ) &&
+ // check only in compound homonyms (bad flags)
+ (!badflag || !TESTAFF(he->astr, badflag, he->alen)
+ ) &&
+ // handle required flag
+ ((!needflag) ||
+ (TESTAFF(he->astr, needflag, he->alen) ||
+ ((contclass) && TESTAFF(contclass, needflag, contclasslen)))
+ )
+ ) return he;
+ he = he->next_homonym; // check homonyms
+ } while (he);
+
+ // obsolote stemming code (used only by the
+ // experimental SuffixMgr:suggest_pos_stems)
+ // store resulting root in wlst
+ } else if (wlst && (*ns < maxSug)) {
+ int cwrd = 1;
+ for (int k=0; k < *ns; k++)
+ if (strcmp(tmpword, wlst[k]) == 0) cwrd = 0;
+ if (cwrd) {
+ wlst[*ns] = mystrdup(tmpword);
+ if (wlst[*ns] == NULL) {
+ for (int j=0; j<*ns; j++) free(wlst[j]);
+ *ns = -1;
+ return NULL;
+ }
+ (*ns)++;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+// see if two-level suffix is present in the word
+struct hentry * SfxEntry::check_twosfx(const char * word, int len, int optflags,
+ PfxEntry* ppfx, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ struct hentry * he; // hash entry pointer
+ unsigned char * cp;
+ char tmpword[MAXWORDUTF8LEN + 4];
+ PfxEntry* ep = ppfx;
+
+
+ // if this suffix is being cross checked with a prefix
+ // but it does not support cross products skip it
+
+ if ((optflags & aeXPRODUCT) != 0 && (opts & aeXPRODUCT) == 0)
+ return NULL;
+
+ // upon entry suffix is 0 length or already matches the end of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing suffix and adding
+ // back any characters that would have been stripped or
+ // or null terminating the shorter string
+
+ strcpy (tmpword, word);
+ cp = (unsigned char *)(tmpword + tmpl);
+ if (stripl) {
+ strcpy ((char *)cp, strip);
+ tmpl += stripl;
+ cp = (unsigned char *)(tmpword + tmpl);
+ } else *cp = '\0';
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then recall suffix_check
+
+ if (test_condition((char *) cp, (char *) tmpword)) {
+ if (ppfx) {
+ // handle conditional suffix
+ if ((contclass) && TESTAFF(contclass, ep->getFlag(), contclasslen))
+ he = pmyMgr->suffix_check(tmpword, tmpl, 0, NULL, NULL, 0, NULL, (FLAG) aflag, needflag);
+ else
+ he = pmyMgr->suffix_check(tmpword, tmpl, optflags, ppfx, NULL, 0, NULL, (FLAG) aflag, needflag);
+ } else {
+ he = pmyMgr->suffix_check(tmpword, tmpl, 0, NULL, NULL, 0, NULL, (FLAG) aflag, needflag);
+ }
+ if (he) return he;
+ }
+ }
+ return NULL;
+}
+
+// see if two-level suffix is present in the word
+char * SfxEntry::check_twosfx_morph(const char * word, int len, int optflags,
+ PfxEntry* ppfx, const FLAG needflag)
+{
+ int tmpl; // length of tmpword
+ unsigned char * cp;
+ char tmpword[MAXWORDUTF8LEN + 4];
+ PfxEntry* ep = ppfx;
+ char * st;
+
+ char result[MAXLNLEN];
+
+ *result = '\0';
+
+ // if this suffix is being cross checked with a prefix
+ // but it does not support cross products skip it
+
+ if ((optflags & aeXPRODUCT) != 0 && (opts & aeXPRODUCT) == 0)
+ return NULL;
+
+ // upon entry suffix is 0 length or already matches the end of the word.
+ // So if the remaining root word has positive length
+ // and if there are enough chars in root word and added back strip chars
+ // to meet the number of characters conditions, then test it
+
+ tmpl = len - appndl;
+
+ if ((tmpl > 0 || (tmpl == 0 && pmyMgr->get_fullstrip())) &&
+ (tmpl + stripl >= numconds)) {
+
+ // generate new root word by removing suffix and adding
+ // back any characters that would have been stripped or
+ // or null terminating the shorter string
+
+ strcpy (tmpword, word);
+ cp = (unsigned char *)(tmpword + tmpl);
+ if (stripl) {
+ strcpy ((char *)cp, strip);
+ tmpl += stripl;
+ cp = (unsigned char *)(tmpword + tmpl);
+ } else *cp = '\0';
+
+ // now make sure all of the conditions on characters
+ // are met. Please see the appendix at the end of
+ // this file for more info on exactly what is being
+ // tested
+
+ // if all conditions are met then recall suffix_check
+
+ if (test_condition((char *) cp, (char *) tmpword)) {
+ if (ppfx) {
+ // handle conditional suffix
+ if ((contclass) && TESTAFF(contclass, ep->getFlag(), contclasslen)) {
+ st = pmyMgr->suffix_check_morph(tmpword, tmpl, 0, NULL, aflag, needflag);
+ if (st) {
+ if (ppfx->getMorph()) {
+ mystrcat(result, ppfx->getMorph(), MAXLNLEN);
+ mystrcat(result, " ", MAXLNLEN);
+ }
+ mystrcat(result,st, MAXLNLEN);
+ free(st);
+ mychomp(result);
+ }
+ } else {
+ st = pmyMgr->suffix_check_morph(tmpword, tmpl, optflags, ppfx, aflag, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ mychomp(result);
+ }
+ }
+ } else {
+ st = pmyMgr->suffix_check_morph(tmpword, tmpl, 0, NULL, aflag, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ mychomp(result);
+ }
+ }
+ if (*result) return mystrdup(result);
+ }
+ }
+ return NULL;
+}
+
+// get next homonym with same affix
+struct hentry * SfxEntry::get_next_homonym(struct hentry * he, int optflags, PfxEntry* ppfx,
+ const FLAG cclass, const FLAG needflag)
+{
+ PfxEntry* ep = ppfx;
+ FLAG eFlag = ep ? ep->getFlag() : FLAG_NULL;
+
+ while (he->next_homonym) {
+ he = he->next_homonym;
+ if ((TESTAFF(he->astr, aflag, he->alen) || (ep && ep->getCont() && TESTAFF(ep->getCont(), aflag, ep->getContLen()))) &&
+ ((optflags & aeXPRODUCT) == 0 ||
+ TESTAFF(he->astr, eFlag, he->alen) ||
+ // handle conditional suffix
+ ((contclass) && TESTAFF(contclass, eFlag, contclasslen))
+ ) &&
+ // handle cont. class
+ ((!cclass) ||
+ ((contclass) && TESTAFF(contclass, cclass, contclasslen))
+ ) &&
+ // handle required flag
+ ((!needflag) ||
+ (TESTAFF(he->astr, needflag, he->alen) ||
+ ((contclass) && TESTAFF(contclass, needflag, contclasslen)))
+ )
+ ) return he;
+ }
+ return NULL;
+}
+
+
+#if 0
+
+Appendix: Understanding Affix Code
+
+
+An affix is either a prefix or a suffix attached to root words to make
+other words.
+
+Basically a Prefix or a Suffix is set of AffEntry objects
+which store information about the prefix or suffix along
+with supporting routines to check if a word has a particular
+prefix or suffix or a combination.
+
+The structure affentry is defined as follows:
+
+struct affentry
+{
+ unsigned short aflag; // ID used to represent the affix
+ char * strip; // string to strip before adding affix
+ char * appnd; // the affix string to add
+ unsigned char stripl; // length of the strip string
+ unsigned char appndl; // length of the affix string
+ char numconds; // the number of conditions that must be met
+ char opts; // flag: aeXPRODUCT- combine both prefix and suffix
+ char conds[SETSIZE]; // array which encodes the conditions to be met
+};
+
+
+Here is a suffix borrowed from the en_US.aff file. This file
+is whitespace delimited.
+
+SFX D Y 4
+SFX D 0 e d
+SFX D y ied [^aeiou]y
+SFX D 0 ed [^ey]
+SFX D 0 ed [aeiou]y
+
+This information can be interpreted as follows:
+
+In the first line has 4 fields
+
+Field
+-----
+1 SFX - indicates this is a suffix
+2 D - is the name of the character flag which represents this suffix
+3 Y - indicates it can be combined with prefixes (cross product)
+4 4 - indicates that sequence of 4 affentry structures are needed to
+ properly store the affix information
+
+The remaining lines describe the unique information for the 4 SfxEntry
+objects that make up this affix. Each line can be interpreted
+as follows: (note fields 1 and 2 are as a check against line 1 info)
+
+Field
+-----
+1 SFX - indicates this is a suffix
+2 D - is the name of the character flag for this affix
+3 y - the string of chars to strip off before adding affix
+ (a 0 here indicates the NULL string)
+4 ied - the string of affix characters to add
+5 [^aeiou]y - the conditions which must be met before the affix
+ can be applied
+
+Field 5 is interesting. Since this is a suffix, field 5 tells us that
+there are 2 conditions that must be met. The first condition is that
+the next to the last character in the word must *NOT* be any of the
+following "a", "e", "i", "o" or "u". The second condition is that
+the last character of the word must end in "y".
+
+So how can we encode this information concisely and be able to
+test for both conditions in a fast manner? The answer is found
+but studying the wonderful ispell code of Geoff Kuenning, et.al.
+(now available under a normal BSD license).
+
+If we set up a conds array of 256 bytes indexed (0 to 255) and access it
+using a character (cast to an unsigned char) of a string, we have 8 bits
+of information we can store about that character. Specifically we
+could use each bit to say if that character is allowed in any of the
+last (or first for prefixes) 8 characters of the word.
+
+Basically, each character at one end of the word (up to the number
+of conditions) is used to index into the conds array and the resulting
+value found there says whether the that character is valid for a
+specific character position in the word.
+
+For prefixes, it does this by setting bit 0 if that char is valid
+in the first position, bit 1 if valid in the second position, and so on.
+
+If a bit is not set, then that char is not valid for that postion in the
+word.
+
+If working with suffixes bit 0 is used for the character closest
+to the front, bit 1 for the next character towards the end, ...,
+with bit numconds-1 representing the last char at the end of the string.
+
+Note: since entries in the conds[] are 8 bits, only 8 conditions
+(read that only 8 character positions) can be examined at one
+end of a word (the beginning for prefixes and the end for suffixes.
+
+So to make this clearer, lets encode the conds array values for the
+first two affentries for the suffix D described earlier.
+
+
+ For the first affentry:
+ numconds = 1 (only examine the last character)
+
+ conds['e'] = (1 << 0) (the word must end in an E)
+ all others are all 0
+
+ For the second affentry:
+ numconds = 2 (only examine the last two characters)
+
+ conds[X] = conds[X] | (1 << 0) (aeiou are not allowed)
+ where X is all characters *but* a, e, i, o, or u
+
+
+ conds['y'] = (1 << 1) (the last char must be a y)
+ all other bits for all other entries in the conds array are zero
+
+
+#endif
+
diff --git a/src/cpp/core/spelling/hunspell/affentry.hxx b/src/cpp/core/spelling/hunspell/affentry.hxx
new file mode 100644
index 0000000..eaf361f
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/affentry.hxx
@@ -0,0 +1,136 @@
+#ifndef _AFFIX_HXX_
+#define _AFFIX_HXX_
+
+#include "hunvisapi.h"
+
+#include "atypes.hxx"
+#include "baseaffix.hxx"
+#include "affixmgr.hxx"
+
+/* A Prefix Entry */
+
+class LIBHUNSPELL_DLL_EXPORTED PfxEntry : protected AffEntry
+{
+ AffixMgr* pmyMgr;
+
+ PfxEntry * next;
+ PfxEntry * nexteq;
+ PfxEntry * nextne;
+ PfxEntry * flgnxt;
+
+public:
+
+ PfxEntry(AffixMgr* pmgr, affentry* dp );
+ ~PfxEntry();
+
+ inline bool allowCross() { return ((opts & aeXPRODUCT) != 0); }
+ struct hentry * checkword(const char * word, int len, char in_compound,
+ const FLAG needflag = FLAG_NULL);
+
+ struct hentry * check_twosfx(const char * word, int len, char in_compound, const FLAG needflag = NULL);
+
+ char * check_morph(const char * word, int len, char in_compound,
+ const FLAG needflag = FLAG_NULL);
+
+ char * check_twosfx_morph(const char * word, int len,
+ char in_compound, const FLAG needflag = FLAG_NULL);
+
+ inline FLAG getFlag() { return aflag; }
+ inline const char * getKey() { return appnd; }
+ char * add(const char * word, int len);
+
+ inline short getKeyLen() { return appndl; }
+
+ inline const char * getMorph() { return morphcode; }
+
+ inline const unsigned short * getCont() { return contclass; }
+ inline short getContLen() { return contclasslen; }
+
+ inline PfxEntry * getNext() { return next; }
+ inline PfxEntry * getNextNE() { return nextne; }
+ inline PfxEntry * getNextEQ() { return nexteq; }
+ inline PfxEntry * getFlgNxt() { return flgnxt; }
+
+ inline void setNext(PfxEntry * ptr) { next = ptr; }
+ inline void setNextNE(PfxEntry * ptr) { nextne = ptr; }
+ inline void setNextEQ(PfxEntry * ptr) { nexteq = ptr; }
+ inline void setFlgNxt(PfxEntry * ptr) { flgnxt = ptr; }
+
+ inline char * nextchar(char * p);
+ inline int test_condition(const char * st);
+};
+
+
+
+
+/* A Suffix Entry */
+
+class LIBHUNSPELL_DLL_EXPORTED SfxEntry : protected AffEntry
+{
+ AffixMgr* pmyMgr;
+ char * rappnd;
+
+ SfxEntry * next;
+ SfxEntry * nexteq;
+ SfxEntry * nextne;
+ SfxEntry * flgnxt;
+
+ SfxEntry * l_morph;
+ SfxEntry * r_morph;
+ SfxEntry * eq_morph;
+
+public:
+
+ SfxEntry(AffixMgr* pmgr, affentry* dp );
+ ~SfxEntry();
+
+ inline bool allowCross() { return ((opts & aeXPRODUCT) != 0); }
+ struct hentry * checkword(const char * word, int len, int optflags,
+ PfxEntry* ppfx, char ** wlst, int maxSug, int * ns,
+// const FLAG cclass = FLAG_NULL, const FLAG needflag = FLAG_NULL, char in_compound=IN_CPD_NOT);
+ const FLAG cclass = FLAG_NULL, const FLAG needflag = FLAG_NULL, const FLAG badflag = 0);
+
+ struct hentry * check_twosfx(const char * word, int len, int optflags, PfxEntry* ppfx, const FLAG needflag = NULL);
+
+ char * check_twosfx_morph(const char * word, int len, int optflags,
+ PfxEntry* ppfx, const FLAG needflag = FLAG_NULL);
+ struct hentry * get_next_homonym(struct hentry * he);
+ struct hentry * get_next_homonym(struct hentry * word, int optflags, PfxEntry* ppfx,
+ const FLAG cclass, const FLAG needflag);
+
+
+ inline FLAG getFlag() { return aflag; }
+ inline const char * getKey() { return rappnd; }
+ char * add(const char * word, int len);
+
+
+ inline const char * getMorph() { return morphcode; }
+
+ inline const unsigned short * getCont() { return contclass; }
+ inline short getContLen() { return contclasslen; }
+ inline const char * getAffix() { return appnd; }
+
+ inline short getKeyLen() { return appndl; }
+
+ inline SfxEntry * getNext() { return next; }
+ inline SfxEntry * getNextNE() { return nextne; }
+ inline SfxEntry * getNextEQ() { return nexteq; }
+
+ inline SfxEntry * getLM() { return l_morph; }
+ inline SfxEntry * getRM() { return r_morph; }
+ inline SfxEntry * getEQM() { return eq_morph; }
+ inline SfxEntry * getFlgNxt() { return flgnxt; }
+
+ inline void setNext(SfxEntry * ptr) { next = ptr; }
+ inline void setNextNE(SfxEntry * ptr) { nextne = ptr; }
+ inline void setNextEQ(SfxEntry * ptr) { nexteq = ptr; }
+ inline void setFlgNxt(SfxEntry * ptr) { flgnxt = ptr; }
+
+ inline char * nextchar(char * p);
+ inline int test_condition(const char * st, const char * begin);
+
+};
+
+#endif
+
+
diff --git a/src/cpp/core/spelling/hunspell/affixmgr.cxx b/src/cpp/core/spelling/hunspell/affixmgr.cxx
new file mode 100644
index 0000000..a1ec1d6
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/affixmgr.cxx
@@ -0,0 +1,4525 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <vector>
+
+#include "affixmgr.hxx"
+#include "affentry.hxx"
+#include "langnum.hxx"
+
+#include "csutil.hxx"
+
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wsizeof-pointer-memaccess"
+#endif
+
+AffixMgr::AffixMgr(const char * affpath, HashMgr** ptr, int * md, const char * key)
+{
+ // register hash manager and load affix data from aff file
+ pHMgr = ptr[0];
+ alldic = ptr;
+ maxdic = md;
+ keystring = NULL;
+ trystring = NULL;
+ encoding=NULL;
+ csconv=NULL;
+ utf8 = 0;
+ complexprefixes = 0;
+ maptable = NULL;
+ nummap = 0;
+ breaktable = NULL;
+ numbreak = -1;
+ reptable = NULL;
+ numrep = 0;
+ iconvtable = NULL;
+ oconvtable = NULL;
+ checkcpdtable = NULL;
+ // allow simplified compound forms (see 3rd field of CHECKCOMPOUNDPATTERN)
+ simplifiedcpd = 0;
+ numcheckcpd = 0;
+ defcpdtable = NULL;
+ numdefcpd = 0;
+ phone = NULL;
+ compoundflag = FLAG_NULL; // permits word in compound forms
+ compoundbegin = FLAG_NULL; // may be first word in compound forms
+ compoundmiddle = FLAG_NULL; // may be middle word in compound forms
+ compoundend = FLAG_NULL; // may be last word in compound forms
+ compoundroot = FLAG_NULL; // compound word signing flag
+ compoundpermitflag = FLAG_NULL; // compound permitting flag for suffixed word
+ compoundforbidflag = FLAG_NULL; // compound fordidden flag for suffixed word
+ checkcompounddup = 0; // forbid double words in compounds
+ checkcompoundrep = 0; // forbid bad compounds (may be non compound word with a REP substitution)
+ checkcompoundcase = 0; // forbid upper and lowercase combinations at word bounds
+ checkcompoundtriple = 0; // forbid compounds with triple letters
+ simplifiedtriple = 0; // allow simplified triple letters in compounds (Schiff+fahrt -> Schiffahrt)
+ forbiddenword = FORBIDDENWORD; // forbidden word signing flag
+ nosuggest = FLAG_NULL; // don't suggest words signed with NOSUGGEST flag
+ nongramsuggest = FLAG_NULL;
+ lang = NULL; // language
+ langnum = 0; // language code (see http://l10n.openoffice.org/languages.html)
+ needaffix = FLAG_NULL; // forbidden root, allowed only with suffixes
+ cpdwordmax = -1; // default: unlimited wordcount in compound words
+ cpdmin = -1; // undefined
+ cpdmaxsyllable = 0; // default: unlimited syllablecount in compound words
+ cpdvowels=NULL; // vowels (for calculating of Hungarian compounding limit, O(n) search! XXX)
+ cpdvowels_utf16=NULL; // vowels for UTF-8 encoding (bsearch instead of O(n) search)
+ cpdvowels_utf16_len=0; // vowels
+ pfxappnd=NULL; // previous prefix for counting the syllables of prefix BUG
+ sfxappnd=NULL; // previous suffix for counting a special syllables BUG
+ cpdsyllablenum=NULL; // syllable count incrementing flag
+ checknum=0; // checking numbers, and word with numbers
+ wordchars=NULL; // letters + spec. word characters
+ wordchars_utf16=NULL; // letters + spec. word characters
+ wordchars_utf16_len=0; // letters + spec. word characters
+ ignorechars=NULL; // letters + spec. word characters
+ ignorechars_utf16=NULL; // letters + spec. word characters
+ ignorechars_utf16_len=0; // letters + spec. word characters
+ version=NULL; // affix and dictionary file version string
+ havecontclass=0; // flags of possible continuing classes (double affix)
+ // LEMMA_PRESENT: not put root into the morphological output. Lemma presents
+ // in morhological description in dictionary file. It's often combined with PSEUDOROOT.
+ lemma_present = FLAG_NULL;
+ circumfix = FLAG_NULL;
+ onlyincompound = FLAG_NULL;
+ maxngramsugs = -1; // undefined
+ maxdiff = -1; // undefined
+ onlymaxdiff = 0;
+ maxcpdsugs = -1; // undefined
+ nosplitsugs = 0;
+ sugswithdots = 0;
+ keepcase = 0;
+ forceucase = 0;
+ warn = 0;
+ forbidwarn = 0;
+ checksharps = 0;
+ substandard = FLAG_NULL;
+ fullstrip = 0;
+
+ sfx = NULL;
+ pfx = NULL;
+
+ for (int i=0; i < SETSIZE; i++) {
+ pStart[i] = NULL;
+ sStart[i] = NULL;
+ pFlag[i] = NULL;
+ sFlag[i] = NULL;
+ }
+
+ for (int j=0; j < CONTSIZE; j++) {
+ contclasses[j] = 0;
+ }
+
+ if (parse_file(affpath, key)) {
+ HUNSPELL_WARNING(stderr, "Failure loading aff file %s\n",affpath);
+ }
+
+ if (cpdmin == -1) cpdmin = MINCPDLEN;
+
+}
+
+
+AffixMgr::~AffixMgr()
+{
+ // pass through linked prefix entries and clean up
+ for (int i=0; i < SETSIZE ;i++) {
+ pFlag[i] = NULL;
+ PfxEntry * ptr = pStart[i];
+ PfxEntry * nptr = NULL;
+ while (ptr) {
+ nptr = ptr->getNext();
+ delete(ptr);
+ ptr = nptr;
+ nptr = NULL;
+ }
+ }
+
+ // pass through linked suffix entries and clean up
+ for (int j=0; j < SETSIZE ; j++) {
+ sFlag[j] = NULL;
+ SfxEntry * ptr = sStart[j];
+ SfxEntry * nptr = NULL;
+ while (ptr) {
+ nptr = ptr->getNext();
+ delete(ptr);
+ ptr = nptr;
+ nptr = NULL;
+ }
+ sStart[j] = NULL;
+ }
+
+ if (keystring) free(keystring);
+ keystring=NULL;
+ if (trystring) free(trystring);
+ trystring=NULL;
+ if (encoding) free(encoding);
+ encoding=NULL;
+ if (maptable) {
+ for (int j=0; j < nummap; j++) {
+ for (int k=0; k < maptable[j].len; k++) {
+ if (maptable[j].set[k]) free(maptable[j].set[k]);
+ }
+ free(maptable[j].set);
+ maptable[j].set = NULL;
+ maptable[j].len = 0;
+ }
+ free(maptable);
+ maptable = NULL;
+ }
+ nummap = 0;
+ if (breaktable) {
+ for (int j=0; j < numbreak; j++) {
+ if (breaktable[j]) free(breaktable[j]);
+ breaktable[j] = NULL;
+ }
+ free(breaktable);
+ breaktable = NULL;
+ }
+ numbreak = 0;
+ if (reptable) {
+ for (int j=0; j < numrep; j++) {
+ free(reptable[j].pattern);
+ free(reptable[j].pattern2);
+ }
+ free(reptable);
+ reptable = NULL;
+ }
+ if (iconvtable) delete iconvtable;
+ if (oconvtable) delete oconvtable;
+ if (phone && phone->rules) {
+ for (int j=0; j < phone->num + 1; j++) {
+ free(phone->rules[j * 2]);
+ free(phone->rules[j * 2 + 1]);
+ }
+ free(phone->rules);
+ free(phone);
+ phone = NULL;
+ }
+
+ if (defcpdtable) {
+ for (int j=0; j < numdefcpd; j++) {
+ free(defcpdtable[j].def);
+ defcpdtable[j].def = NULL;
+ }
+ free(defcpdtable);
+ defcpdtable = NULL;
+ }
+ numrep = 0;
+ if (checkcpdtable) {
+ for (int j=0; j < numcheckcpd; j++) {
+ free(checkcpdtable[j].pattern);
+ free(checkcpdtable[j].pattern2);
+ free(checkcpdtable[j].pattern3);
+ checkcpdtable[j].pattern = NULL;
+ checkcpdtable[j].pattern2 = NULL;
+ checkcpdtable[j].pattern3 = NULL;
+ }
+ free(checkcpdtable);
+ checkcpdtable = NULL;
+ }
+ numcheckcpd = 0;
+ FREE_FLAG(compoundflag);
+ FREE_FLAG(compoundbegin);
+ FREE_FLAG(compoundmiddle);
+ FREE_FLAG(compoundend);
+ FREE_FLAG(compoundpermitflag);
+ FREE_FLAG(compoundforbidflag);
+ FREE_FLAG(compoundroot);
+ FREE_FLAG(forbiddenword);
+ FREE_FLAG(nosuggest);
+ FREE_FLAG(nongramsuggest);
+ FREE_FLAG(needaffix);
+ FREE_FLAG(lemma_present);
+ FREE_FLAG(circumfix);
+ FREE_FLAG(onlyincompound);
+
+ cpdwordmax = 0;
+ pHMgr = NULL;
+ cpdmin = 0;
+ cpdmaxsyllable = 0;
+ if (cpdvowels) free(cpdvowels);
+ if (cpdvowels_utf16) free(cpdvowels_utf16);
+ if (cpdsyllablenum) free(cpdsyllablenum);
+ free_utf_tbl();
+ if (lang) free(lang);
+ if (wordchars) free(wordchars);
+ if (wordchars_utf16) free(wordchars_utf16);
+ if (ignorechars) free(ignorechars);
+ if (ignorechars_utf16) free(ignorechars_utf16);
+ if (version) free(version);
+ checknum=0;
+#ifdef MOZILLA_CLIENT
+ delete [] csconv;
+#endif
+}
+
+
+// read in aff file and build up prefix and suffix entry objects
+int AffixMgr::parse_file(const char * affpath, const char * key)
+{
+ char * line; // io buffers
+ char ft; // affix type
+
+ // checking flag duplication
+ char dupflags[CONTSIZE];
+ char dupflags_ini = 1;
+
+ // first line indicator for removing byte order mark
+ int firstline = 1;
+
+ // open the affix file
+ FileMgr * afflst = new FileMgr(affpath, key);
+ if (!afflst) {
+ HUNSPELL_WARNING(stderr, "error: could not open affix description file %s\n",affpath);
+ return 1;
+ }
+
+ // step one is to parse the affix file building up the internal
+ // affix data structures
+
+ // read in each line ignoring any that do not
+ // start with a known line type indicator
+ while ((line = afflst->getline())) {
+ mychomp(line);
+
+ /* remove byte order mark */
+ if (firstline) {
+ firstline = 0;
+ // Affix file begins with byte order mark: possible incompatibility with old Hunspell versions
+ if (strncmp(line,"\xEF\xBB\xBF",3) == 0) {
+ memmove(line, line+3, strlen(line+3)+1);
+ }
+ }
+
+ /* parse in the keyboard string */
+ if (strncmp(line,"KEY",3) == 0) {
+ if (parse_string(line, &keystring, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the try string */
+ if (strncmp(line,"TRY",3) == 0) {
+ if (parse_string(line, &trystring, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the name of the character set used by the .dict and .aff */
+ if (strncmp(line,"SET",3) == 0) {
+ if (parse_string(line, &encoding, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ if (strcmp(encoding, "UTF-8") == 0) {
+ utf8 = 1;
+#ifndef OPENOFFICEORG
+#ifndef MOZILLA_CLIENT
+ if (initialize_utf_tbl()) return 1;
+#endif
+#endif
+ }
+ }
+
+ /* parse COMPLEXPREFIXES for agglutinative languages with right-to-left writing system */
+ if (strncmp(line,"COMPLEXPREFIXES",15) == 0)
+ complexprefixes = 1;
+
+ /* parse in the flag used by the controlled compound words */
+ if (strncmp(line,"COMPOUNDFLAG",12) == 0) {
+ if (parse_flag(line, &compoundflag, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by compound words */
+ if (strncmp(line,"COMPOUNDBEGIN",13) == 0) {
+ if (complexprefixes) {
+ if (parse_flag(line, &compoundend, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ } else {
+ if (parse_flag(line, &compoundbegin, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+ }
+
+ /* parse in the flag used by compound words */
+ if (strncmp(line,"COMPOUNDMIDDLE",14) == 0) {
+ if (parse_flag(line, &compoundmiddle, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+ /* parse in the flag used by compound words */
+ if (strncmp(line,"COMPOUNDEND",11) == 0) {
+ if (complexprefixes) {
+ if (parse_flag(line, &compoundbegin, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ } else {
+ if (parse_flag(line, &compoundend, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+ }
+
+ /* parse in the data used by compound_check() method */
+ if (strncmp(line,"COMPOUNDWORDMAX",15) == 0) {
+ if (parse_num(line, &cpdwordmax, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag sign compounds in dictionary */
+ if (strncmp(line,"COMPOUNDROOT",12) == 0) {
+ if (parse_flag(line, &compoundroot, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by compound_check() method */
+ if (strncmp(line,"COMPOUNDPERMITFLAG",18) == 0) {
+ if (parse_flag(line, &compoundpermitflag, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by compound_check() method */
+ if (strncmp(line,"COMPOUNDFORBIDFLAG",18) == 0) {
+ if (parse_flag(line, &compoundforbidflag, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"CHECKCOMPOUNDDUP",16) == 0) {
+ checkcompounddup = 1;
+ }
+
+ if (strncmp(line,"CHECKCOMPOUNDREP",16) == 0) {
+ checkcompoundrep = 1;
+ }
+
+ if (strncmp(line,"CHECKCOMPOUNDTRIPLE",19) == 0) {
+ checkcompoundtriple = 1;
+ }
+
+ if (strncmp(line,"SIMPLIFIEDTRIPLE",16) == 0) {
+ simplifiedtriple = 1;
+ }
+
+ if (strncmp(line,"CHECKCOMPOUNDCASE",17) == 0) {
+ checkcompoundcase = 1;
+ }
+
+ if (strncmp(line,"NOSUGGEST",9) == 0) {
+ if (parse_flag(line, &nosuggest, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"NONGRAMSUGGEST",14) == 0) {
+ if (parse_flag(line, &nongramsuggest, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by forbidden words */
+ if (strncmp(line,"FORBIDDENWORD",13) == 0) {
+ if (parse_flag(line, &forbiddenword, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by forbidden words */
+ if (strncmp(line,"LEMMA_PRESENT",13) == 0) {
+ if (parse_flag(line, &lemma_present, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by circumfixes */
+ if (strncmp(line,"CIRCUMFIX",9) == 0) {
+ if (parse_flag(line, &circumfix, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by fogemorphemes */
+ if (strncmp(line,"ONLYINCOMPOUND",14) == 0) {
+ if (parse_flag(line, &onlyincompound, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by `needaffixs' */
+ if (strncmp(line,"PSEUDOROOT",10) == 0) {
+ if (parse_flag(line, &needaffix, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by `needaffixs' */
+ if (strncmp(line,"NEEDAFFIX",9) == 0) {
+ if (parse_flag(line, &needaffix, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the minimal length for words in compounds */
+ if (strncmp(line,"COMPOUNDMIN",11) == 0) {
+ if (parse_num(line, &cpdmin, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ if (cpdmin < 1) cpdmin = 1;
+ }
+
+ /* parse in the max. words and syllables in compounds */
+ if (strncmp(line,"COMPOUNDSYLLABLE",16) == 0) {
+ if (parse_cpdsyllable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by compound_check() method */
+ if (strncmp(line,"SYLLABLENUM",11) == 0) {
+ if (parse_string(line, &cpdsyllablenum, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by the controlled compound words */
+ if (strncmp(line,"CHECKNUM",8) == 0) {
+ checknum=1;
+ }
+
+ /* parse in the extra word characters */
+ if (strncmp(line,"WORDCHARS",9) == 0) {
+ if (parse_array(line, &wordchars, &wordchars_utf16, &wordchars_utf16_len, utf8, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the ignored characters (for example, Arabic optional diacretics charachters */
+ if (strncmp(line,"IGNORE",6) == 0) {
+ if (parse_array(line, &ignorechars, &ignorechars_utf16, &ignorechars_utf16_len, utf8, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the typical fault correcting table */
+ if (strncmp(line,"REP",3) == 0) {
+ if (parse_reptable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the input conversion table */
+ if (strncmp(line,"ICONV",5) == 0) {
+ if (parse_convtable(line, afflst, &iconvtable, "ICONV")) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the input conversion table */
+ if (strncmp(line,"OCONV",5) == 0) {
+ if (parse_convtable(line, afflst, &oconvtable, "OCONV")) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the phonetic translation table */
+ if (strncmp(line,"PHONE",5) == 0) {
+ if (parse_phonetable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the checkcompoundpattern table */
+ if (strncmp(line,"CHECKCOMPOUNDPATTERN",20) == 0) {
+ if (parse_checkcpdtable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the defcompound table */
+ if (strncmp(line,"COMPOUNDRULE",12) == 0) {
+ if (parse_defcpdtable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the related character map table */
+ if (strncmp(line,"MAP",3) == 0) {
+ if (parse_maptable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the word breakpoints table */
+ if (strncmp(line,"BREAK",5) == 0) {
+ if (parse_breaktable(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the language for language specific codes */
+ if (strncmp(line,"LANG",4) == 0) {
+ if (parse_string(line, &lang, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ langnum = get_lang_num(lang);
+ }
+
+ if (strncmp(line,"VERSION",7) == 0) {
+ for(line = line + 7; *line == ' ' || *line == '\t'; line++);
+ version = mystrdup(line);
+ }
+
+ if (strncmp(line,"MAXNGRAMSUGS",12) == 0) {
+ if (parse_num(line, &maxngramsugs, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"ONLYMAXDIFF", 11) == 0)
+ onlymaxdiff = 1;
+
+ if (strncmp(line,"MAXDIFF",7) == 0) {
+ if (parse_num(line, &maxdiff, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"MAXCPDSUGS",10) == 0) {
+ if (parse_num(line, &maxcpdsugs, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"NOSPLITSUGS",11) == 0) {
+ nosplitsugs=1;
+ }
+
+ if (strncmp(line,"FULLSTRIP",9) == 0) {
+ fullstrip=1;
+ }
+
+ if (strncmp(line,"SUGSWITHDOTS",12) == 0) {
+ sugswithdots=1;
+ }
+
+ /* parse in the flag used by forbidden words */
+ if (strncmp(line,"KEEPCASE",8) == 0) {
+ if (parse_flag(line, &keepcase, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by `forceucase' */
+ if (strncmp(line,"FORCEUCASE",10) == 0) {
+ if (parse_flag(line, &forceucase, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ /* parse in the flag used by `warn' */
+ if (strncmp(line,"WARN",4) == 0) {
+ if (parse_flag(line, &warn, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"FORBIDWARN",10) == 0) {
+ forbidwarn=1;
+ }
+
+ /* parse in the flag used by the affix generator */
+ if (strncmp(line,"SUBSTANDARD",11) == 0) {
+ if (parse_flag(line, &substandard, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"CHECKSHARPS",11) == 0) {
+ checksharps=1;
+ }
+
+ /* parse this affix: P - prefix, S - suffix */
+ ft = ' ';
+ if (strncmp(line,"PFX",3) == 0) ft = complexprefixes ? 'S' : 'P';
+ if (strncmp(line,"SFX",3) == 0) ft = complexprefixes ? 'P' : 'S';
+ if (ft != ' ') {
+ if (dupflags_ini) {
+ memset(dupflags, 0, sizeof(dupflags));
+ dupflags_ini = 0;
+ }
+ if (parse_affix(line, ft, afflst, dupflags)) {
+ delete afflst;
+ process_pfx_tree_to_list();
+ process_sfx_tree_to_list();
+ return 1;
+ }
+ }
+
+ }
+ delete afflst;
+
+ // convert affix trees to sorted list
+ process_pfx_tree_to_list();
+ process_sfx_tree_to_list();
+
+ // now we can speed up performance greatly taking advantage of the
+ // relationship between the affixes and the idea of "subsets".
+
+ // View each prefix as a potential leading subset of another and view
+ // each suffix (reversed) as a potential trailing subset of another.
+
+ // To illustrate this relationship if we know the prefix "ab" is found in the
+ // word to examine, only prefixes that "ab" is a leading subset of need be examined.
+ // Furthermore is "ab" is not present then none of the prefixes that "ab" is
+ // is a subset need be examined.
+ // The same argument goes for suffix string that are reversed.
+
+ // Then to top this off why not examine the first char of the word to quickly
+ // limit the set of prefixes to examine (i.e. the prefixes to examine must
+ // be leading supersets of the first character of the word (if they exist)
+
+ // To take advantage of this "subset" relationship, we need to add two links
+ // from entry. One to take next if the current prefix is found (call it nexteq)
+ // and one to take next if the current prefix is not found (call it nextne).
+
+ // Since we have built ordered lists, all that remains is to properly initialize
+ // the nextne and nexteq pointers that relate them
+
+ process_pfx_order();
+ process_sfx_order();
+
+ /* get encoding for CHECKCOMPOUNDCASE */
+ if (!utf8) {
+ char * enc = get_encoding();
+ csconv = get_current_cs(enc);
+ free(enc);
+ enc = NULL;
+
+ char expw[MAXLNLEN];
+ if (wordchars) {
+ strcpy(expw, wordchars);
+ free(wordchars);
+ } else *expw = '\0';
+
+ for (int i = 0; i <= 255; i++) {
+ if ( (csconv[i].cupper != csconv[i].clower) &&
+ (! strchr(expw, (char) i))) {
+ *(expw + strlen(expw) + 1) = '\0';
+ *(expw + strlen(expw)) = (char) i;
+ }
+ }
+
+ wordchars = mystrdup(expw);
+ }
+
+ // default BREAK definition
+ if (numbreak == -1) {
+ breaktable = (char **) malloc(sizeof(char *) * 3);
+ if (!breaktable) return 1;
+ breaktable[0] = mystrdup("-");
+ breaktable[1] = mystrdup("^-");
+ breaktable[2] = mystrdup("-$");
+ if (breaktable[0] && breaktable[1] && breaktable[2]) numbreak = 3;
+ }
+ return 0;
+}
+
+
+// we want to be able to quickly access prefix information
+// both by prefix flag, and sorted by prefix string itself
+// so we need to set up two indexes
+
+int AffixMgr::build_pfxtree(PfxEntry* pfxptr)
+{
+ PfxEntry * ptr;
+ PfxEntry * pptr;
+ PfxEntry * ep = pfxptr;
+
+ // get the right starting points
+ const char * key = ep->getKey();
+ const unsigned char flg = (unsigned char) (ep->getFlag() & 0x00FF);
+
+ // first index by flag which must exist
+ ptr = pFlag[flg];
+ ep->setFlgNxt(ptr);
+ pFlag[flg] = ep;
+
+
+ // handle the special case of null affix string
+ if (strlen(key) == 0) {
+ // always inset them at head of list at element 0
+ ptr = pStart[0];
+ ep->setNext(ptr);
+ pStart[0] = ep;
+ return 0;
+ }
+
+ // now handle the normal case
+ ep->setNextEQ(NULL);
+ ep->setNextNE(NULL);
+
+ unsigned char sp = *((const unsigned char *)key);
+ ptr = pStart[sp];
+
+ // handle the first insert
+ if (!ptr) {
+ pStart[sp] = ep;
+ return 0;
+ }
+
+
+ // otherwise use binary tree insertion so that a sorted
+ // list can easily be generated later
+ pptr = NULL;
+ for (;;) {
+ pptr = ptr;
+ if (strcmp(ep->getKey(), ptr->getKey() ) <= 0) {
+ ptr = ptr->getNextEQ();
+ if (!ptr) {
+ pptr->setNextEQ(ep);
+ break;
+ }
+ } else {
+ ptr = ptr->getNextNE();
+ if (!ptr) {
+ pptr->setNextNE(ep);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+// we want to be able to quickly access suffix information
+// both by suffix flag, and sorted by the reverse of the
+// suffix string itself; so we need to set up two indexes
+int AffixMgr::build_sfxtree(SfxEntry* sfxptr)
+{
+ SfxEntry * ptr;
+ SfxEntry * pptr;
+ SfxEntry * ep = sfxptr;
+
+ /* get the right starting point */
+ const char * key = ep->getKey();
+ const unsigned char flg = (unsigned char) (ep->getFlag() & 0x00FF);
+
+ // first index by flag which must exist
+ ptr = sFlag[flg];
+ ep->setFlgNxt(ptr);
+ sFlag[flg] = ep;
+
+ // next index by affix string
+
+ // handle the special case of null affix string
+ if (strlen(key) == 0) {
+ // always inset them at head of list at element 0
+ ptr = sStart[0];
+ ep->setNext(ptr);
+ sStart[0] = ep;
+ return 0;
+ }
+
+ // now handle the normal case
+ ep->setNextEQ(NULL);
+ ep->setNextNE(NULL);
+
+ unsigned char sp = *((const unsigned char *)key);
+ ptr = sStart[sp];
+
+ // handle the first insert
+ if (!ptr) {
+ sStart[sp] = ep;
+ return 0;
+ }
+
+ // otherwise use binary tree insertion so that a sorted
+ // list can easily be generated later
+ pptr = NULL;
+ for (;;) {
+ pptr = ptr;
+ if (strcmp(ep->getKey(), ptr->getKey() ) <= 0) {
+ ptr = ptr->getNextEQ();
+ if (!ptr) {
+ pptr->setNextEQ(ep);
+ break;
+ }
+ } else {
+ ptr = ptr->getNextNE();
+ if (!ptr) {
+ pptr->setNextNE(ep);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+// convert from binary tree to sorted list
+int AffixMgr::process_pfx_tree_to_list()
+{
+ for (int i=1; i< SETSIZE; i++) {
+ pStart[i] = process_pfx_in_order(pStart[i],NULL);
+ }
+ return 0;
+}
+
+
+PfxEntry* AffixMgr::process_pfx_in_order(PfxEntry* ptr, PfxEntry* nptr)
+{
+ if (ptr) {
+ nptr = process_pfx_in_order(ptr->getNextNE(), nptr);
+ ptr->setNext(nptr);
+ nptr = process_pfx_in_order(ptr->getNextEQ(), ptr);
+ }
+ return nptr;
+}
+
+
+// convert from binary tree to sorted list
+int AffixMgr:: process_sfx_tree_to_list()
+{
+ for (int i=1; i< SETSIZE; i++) {
+ sStart[i] = process_sfx_in_order(sStart[i],NULL);
+ }
+ return 0;
+}
+
+SfxEntry* AffixMgr::process_sfx_in_order(SfxEntry* ptr, SfxEntry* nptr)
+{
+ if (ptr) {
+ nptr = process_sfx_in_order(ptr->getNextNE(), nptr);
+ ptr->setNext(nptr);
+ nptr = process_sfx_in_order(ptr->getNextEQ(), ptr);
+ }
+ return nptr;
+}
+
+
+// reinitialize the PfxEntry links NextEQ and NextNE to speed searching
+// using the idea of leading subsets this time
+int AffixMgr::process_pfx_order()
+{
+ PfxEntry* ptr;
+
+ // loop through each prefix list starting point
+ for (int i=1; i < SETSIZE; i++) {
+
+ ptr = pStart[i];
+
+ // look through the remainder of the list
+ // and find next entry with affix that
+ // the current one is not a subset of
+ // mark that as destination for NextNE
+ // use next in list that you are a subset
+ // of as NextEQ
+
+ for (; ptr != NULL; ptr = ptr->getNext()) {
+
+ PfxEntry * nptr = ptr->getNext();
+ for (; nptr != NULL; nptr = nptr->getNext()) {
+ if (! isSubset( ptr->getKey() , nptr->getKey() )) break;
+ }
+ ptr->setNextNE(nptr);
+ ptr->setNextEQ(NULL);
+ if ((ptr->getNext()) && isSubset(ptr->getKey() , (ptr->getNext())->getKey()))
+ ptr->setNextEQ(ptr->getNext());
+ }
+
+ // now clean up by adding smart search termination strings:
+ // if you are already a superset of the previous prefix
+ // but not a subset of the next, search can end here
+ // so set NextNE properly
+
+ ptr = pStart[i];
+ for (; ptr != NULL; ptr = ptr->getNext()) {
+ PfxEntry * nptr = ptr->getNext();
+ PfxEntry * mptr = NULL;
+ for (; nptr != NULL; nptr = nptr->getNext()) {
+ if (! isSubset(ptr->getKey(),nptr->getKey())) break;
+ mptr = nptr;
+ }
+ if (mptr) mptr->setNextNE(NULL);
+ }
+ }
+ return 0;
+}
+
+// initialize the SfxEntry links NextEQ and NextNE to speed searching
+// using the idea of leading subsets this time
+int AffixMgr::process_sfx_order()
+{
+ SfxEntry* ptr;
+
+ // loop through each prefix list starting point
+ for (int i=1; i < SETSIZE; i++) {
+
+ ptr = sStart[i];
+
+ // look through the remainder of the list
+ // and find next entry with affix that
+ // the current one is not a subset of
+ // mark that as destination for NextNE
+ // use next in list that you are a subset
+ // of as NextEQ
+
+ for (; ptr != NULL; ptr = ptr->getNext()) {
+ SfxEntry * nptr = ptr->getNext();
+ for (; nptr != NULL; nptr = nptr->getNext()) {
+ if (! isSubset(ptr->getKey(),nptr->getKey())) break;
+ }
+ ptr->setNextNE(nptr);
+ ptr->setNextEQ(NULL);
+ if ((ptr->getNext()) && isSubset(ptr->getKey(),(ptr->getNext())->getKey()))
+ ptr->setNextEQ(ptr->getNext());
+ }
+
+
+ // now clean up by adding smart search termination strings:
+ // if you are already a superset of the previous suffix
+ // but not a subset of the next, search can end here
+ // so set NextNE properly
+
+ ptr = sStart[i];
+ for (; ptr != NULL; ptr = ptr->getNext()) {
+ SfxEntry * nptr = ptr->getNext();
+ SfxEntry * mptr = NULL;
+ for (; nptr != NULL; nptr = nptr->getNext()) {
+ if (! isSubset(ptr->getKey(),nptr->getKey())) break;
+ mptr = nptr;
+ }
+ if (mptr) mptr->setNextNE(NULL);
+ }
+ }
+ return 0;
+}
+
+// add flags to the result for dictionary debugging
+void AffixMgr::debugflag(char * result, unsigned short flag) {
+ char * st = encode_flag(flag);
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_FLAG, MAXLNLEN);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+}
+
+// calculate the character length of the condition
+int AffixMgr::condlen(char * st)
+{
+ int l = 0;
+ bool group = false;
+ for(; *st; st++) {
+ if (*st == '[') {
+ group = true;
+ l++;
+ } else if (*st == ']') group = false;
+ else if (!group && (!utf8 ||
+ (!(*st & 0x80) || ((*st & 0xc0) == 0x80)))) l++;
+ }
+ return l;
+}
+
+int AffixMgr::encodeit(affentry &entry, char * cs)
+{
+ if (strcmp(cs,".") != 0) {
+ entry.numconds = (char) condlen(cs);
+ strncpy(entry.c.conds, cs, MAXCONDLEN);
+ // long condition (end of conds padded by strncpy)
+ if (entry.c.conds[MAXCONDLEN - 1] && cs[MAXCONDLEN]) {
+ entry.opts += aeLONGCOND;
+ entry.c.l.conds2 = mystrdup(cs + MAXCONDLEN_1);
+ if (!entry.c.l.conds2) return 1;
+ }
+ } else {
+ entry.numconds = 0;
+ entry.c.conds[0] = '\0';
+ }
+ return 0;
+}
+
+// return 1 if s1 is a leading subset of s2 (dots are for infixes)
+inline int AffixMgr::isSubset(const char * s1, const char * s2)
+ {
+ while (((*s1 == *s2) || (*s1 == '.')) && (*s1 != '\0')) {
+ s1++;
+ s2++;
+ }
+ return (*s1 == '\0');
+ }
+
+
+// check word for prefixes
+struct hentry * AffixMgr::prefix_check(const char * word, int len, char in_compound,
+ const FLAG needflag)
+{
+ struct hentry * rv= NULL;
+
+ pfx = NULL;
+ pfxappnd = NULL;
+ sfxappnd = NULL;
+
+ // first handle the special case of 0 length prefixes
+ PfxEntry * pe = pStart[0];
+ while (pe) {
+ if (
+ // fogemorpheme
+ ((in_compound != IN_CPD_NOT) || !(pe->getCont() &&
+ (TESTAFF(pe->getCont(), onlyincompound, pe->getContLen())))) &&
+ // permit prefixes in compounds
+ ((in_compound != IN_CPD_END) || (pe->getCont() &&
+ (TESTAFF(pe->getCont(), compoundpermitflag, pe->getContLen()))))
+ ) {
+ // check prefix
+ rv = pe->checkword(word, len, in_compound, needflag);
+ if (rv) {
+ pfx=pe; // BUG: pfx not stateless
+ return rv;
+ }
+ }
+ pe = pe->getNext();
+ }
+
+ // now handle the general case
+ unsigned char sp = *((const unsigned char *)word);
+ PfxEntry * pptr = pStart[sp];
+
+ while (pptr) {
+ if (isSubset(pptr->getKey(),word)) {
+ if (
+ // fogemorpheme
+ ((in_compound != IN_CPD_NOT) || !(pptr->getCont() &&
+ (TESTAFF(pptr->getCont(), onlyincompound, pptr->getContLen())))) &&
+ // permit prefixes in compounds
+ ((in_compound != IN_CPD_END) || (pptr->getCont() &&
+ (TESTAFF(pptr->getCont(), compoundpermitflag, pptr->getContLen()))))
+ ) {
+ // check prefix
+ rv = pptr->checkword(word, len, in_compound, needflag);
+ if (rv) {
+ pfx=pptr; // BUG: pfx not stateless
+ return rv;
+ }
+ }
+ pptr = pptr->getNextEQ();
+ } else {
+ pptr = pptr->getNextNE();
+ }
+ }
+
+ return NULL;
+}
+
+// check word for prefixes
+struct hentry * AffixMgr::prefix_check_twosfx(const char * word, int len,
+ char in_compound, const FLAG needflag)
+{
+ struct hentry * rv= NULL;
+
+ pfx = NULL;
+ sfxappnd = NULL;
+
+ // first handle the special case of 0 length prefixes
+ PfxEntry * pe = pStart[0];
+
+ while (pe) {
+ rv = pe->check_twosfx(word, len, in_compound, needflag);
+ if (rv) return rv;
+ pe = pe->getNext();
+ }
+
+ // now handle the general case
+ unsigned char sp = *((const unsigned char *)word);
+ PfxEntry * pptr = pStart[sp];
+
+ while (pptr) {
+ if (isSubset(pptr->getKey(),word)) {
+ rv = pptr->check_twosfx(word, len, in_compound, needflag);
+ if (rv) {
+ pfx = pptr;
+ return rv;
+ }
+ pptr = pptr->getNextEQ();
+ } else {
+ pptr = pptr->getNextNE();
+ }
+ }
+
+ return NULL;
+}
+
+// check word for prefixes
+char * AffixMgr::prefix_check_morph(const char * word, int len, char in_compound,
+ const FLAG needflag)
+{
+ char * st;
+
+ char result[MAXLNLEN];
+ result[0] = '\0';
+
+ pfx = NULL;
+ sfxappnd = NULL;
+
+ // first handle the special case of 0 length prefixes
+ PfxEntry * pe = pStart[0];
+ while (pe) {
+ st = pe->check_morph(word,len,in_compound, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ // if (rv) return rv;
+ pe = pe->getNext();
+ }
+
+ // now handle the general case
+ unsigned char sp = *((const unsigned char *)word);
+ PfxEntry * pptr = pStart[sp];
+
+ while (pptr) {
+ if (isSubset(pptr->getKey(),word)) {
+ st = pptr->check_morph(word,len,in_compound, needflag);
+ if (st) {
+ // fogemorpheme
+ if ((in_compound != IN_CPD_NOT) || !((pptr->getCont() &&
+ (TESTAFF(pptr->getCont(), onlyincompound, pptr->getContLen()))))) {
+ mystrcat(result, st, MAXLNLEN);
+ pfx = pptr;
+ }
+ free(st);
+ }
+ pptr = pptr->getNextEQ();
+ } else {
+ pptr = pptr->getNextNE();
+ }
+ }
+
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+
+// check word for prefixes
+char * AffixMgr::prefix_check_twosfx_morph(const char * word, int len,
+ char in_compound, const FLAG needflag)
+{
+ char * st;
+
+ char result[MAXLNLEN];
+ result[0] = '\0';
+
+ pfx = NULL;
+ sfxappnd = NULL;
+
+ // first handle the special case of 0 length prefixes
+ PfxEntry * pe = pStart[0];
+ while (pe) {
+ st = pe->check_twosfx_morph(word,len,in_compound, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ pe = pe->getNext();
+ }
+
+ // now handle the general case
+ unsigned char sp = *((const unsigned char *)word);
+ PfxEntry * pptr = pStart[sp];
+
+ while (pptr) {
+ if (isSubset(pptr->getKey(),word)) {
+ st = pptr->check_twosfx_morph(word, len, in_compound, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ pfx = pptr;
+ }
+ pptr = pptr->getNextEQ();
+ } else {
+ pptr = pptr->getNextNE();
+ }
+ }
+
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+// Is word a non compound with a REP substitution (see checkcompoundrep)?
+int AffixMgr::cpdrep_check(const char * word, int wl)
+{
+ char candidate[MAXLNLEN];
+ const char * r;
+ int lenr, lenp;
+
+ if ((wl < 2) || !numrep) return 0;
+
+ for (int i=0; i < numrep; i++ ) {
+ r = word;
+ lenr = strlen(reptable[i].pattern2);
+ lenp = strlen(reptable[i].pattern);
+ // search every occurence of the pattern in the word
+ while ((r=strstr(r, reptable[i].pattern)) != NULL) {
+ strcpy(candidate, word);
+ if (r-word + lenr + strlen(r+lenp) >= MAXLNLEN) break;
+ strcpy(candidate+(r-word),reptable[i].pattern2);
+ strcpy(candidate+(r-word)+lenr, r+lenp);
+ if (candidate_check(candidate,strlen(candidate))) return 1;
+ r++; // search for the next letter
+ }
+ }
+ return 0;
+}
+
+// forbid compoundings when there are special patterns at word bound
+int AffixMgr::cpdpat_check(const char * word, int pos, hentry * r1, hentry * r2, const char affixed)
+{
+ int len;
+ for (int i = 0; i < numcheckcpd; i++) {
+ if (isSubset(checkcpdtable[i].pattern2, word + pos) &&
+ (!r1 || !checkcpdtable[i].cond ||
+ (r1->astr && TESTAFF(r1->astr, checkcpdtable[i].cond, r1->alen))) &&
+ (!r2 || !checkcpdtable[i].cond2 ||
+ (r2->astr && TESTAFF(r2->astr, checkcpdtable[i].cond2, r2->alen))) &&
+ // zero length pattern => only TESTAFF
+ // zero pattern (0/flag) => unmodified stem (zero affixes allowed)
+ (!*(checkcpdtable[i].pattern) || (
+ (*(checkcpdtable[i].pattern)=='0' && r1->blen <= pos && strncmp(word + pos - r1->blen, r1->word, r1->blen) == 0) ||
+ (*(checkcpdtable[i].pattern)!='0' && (len = strlen(checkcpdtable[i].pattern)) &&
+ strncmp(word + pos - len, checkcpdtable[i].pattern, len) == 0)))) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// forbid compounding with neighbouring upper and lower case characters at word bounds
+int AffixMgr::cpdcase_check(const char * word, int pos)
+{
+ if (utf8) {
+ w_char u, w;
+ const char * p;
+ u8_u16(&u, 1, word + pos);
+ for (p = word + pos - 1; (*p & 0xc0) == 0x80; p--);
+ u8_u16(&w, 1, p);
+ unsigned short a = (u.h << 8) + u.l;
+ unsigned short b = (w.h << 8) + w.l;
+ if (((unicodetoupper(a, langnum) == a) || (unicodetoupper(b, langnum) == b)) &&
+ (a != '-') && (b != '-')) return 1;
+ } else {
+ unsigned char a = *(word + pos - 1);
+ unsigned char b = *(word + pos);
+ if ((csconv[a].ccase || csconv[b].ccase) && (a != '-') && (b != '-')) return 1;
+ }
+ return 0;
+}
+
+// check compound patterns
+int AffixMgr::defcpd_check(hentry *** words, short wnum, hentry * rv, hentry ** def, char all)
+{
+ signed short btpp[MAXWORDLEN]; // metacharacter (*, ?) positions for backtracking
+ signed short btwp[MAXWORDLEN]; // word positions for metacharacters
+ int btnum[MAXWORDLEN]; // number of matched characters in metacharacter positions
+ short bt = 0;
+ int i, j;
+ int ok;
+ int w = 0;
+
+ if (!*words) {
+ w = 1;
+ *words = def;
+ }
+
+ if (!*words) {
+ return 0;
+ }
+
+ (*words)[wnum] = rv;
+
+ // has the last word COMPOUNDRULE flag?
+ if (rv->alen == 0) {
+ (*words)[wnum] = NULL;
+ if (w) *words = NULL;
+ return 0;
+ }
+ ok = 0;
+ for (i = 0; i < numdefcpd; i++) {
+ for (j = 0; j < defcpdtable[i].len; j++) {
+ if (defcpdtable[i].def[j] != '*' && defcpdtable[i].def[j] != '?' &&
+ TESTAFF(rv->astr, defcpdtable[i].def[j], rv->alen)) ok = 1;
+ }
+ }
+ if (ok == 0) {
+ (*words)[wnum] = NULL;
+ if (w) *words = NULL;
+ return 0;
+ }
+
+ for (i = 0; i < numdefcpd; i++) {
+ signed short pp = 0; // pattern position
+ signed short wp = 0; // "words" position
+ int ok2;
+ ok = 1;
+ ok2 = 1;
+ do {
+ while ((pp < defcpdtable[i].len) && (wp <= wnum)) {
+ if (((pp+1) < defcpdtable[i].len) &&
+ ((defcpdtable[i].def[pp+1] == '*') || (defcpdtable[i].def[pp+1] == '?'))) {
+ int wend = (defcpdtable[i].def[pp+1] == '?') ? wp : wnum;
+ ok2 = 1;
+ pp+=2;
+ btpp[bt] = pp;
+ btwp[bt] = wp;
+ while (wp <= wend) {
+ if (!(*words)[wp]->alen ||
+ !TESTAFF((*words)[wp]->astr, defcpdtable[i].def[pp-2], (*words)[wp]->alen)) {
+ ok2 = 0;
+ break;
+ }
+ wp++;
+ }
+ if (wp <= wnum) ok2 = 0;
+ btnum[bt] = wp - btwp[bt];
+ if (btnum[bt] > 0) bt++;
+ if (ok2) break;
+ } else {
+ ok2 = 1;
+ if (!(*words)[wp] || !(*words)[wp]->alen ||
+ !TESTAFF((*words)[wp]->astr, defcpdtable[i].def[pp], (*words)[wp]->alen)) {
+ ok = 0;
+ break;
+ }
+ pp++;
+ wp++;
+ if ((defcpdtable[i].len == pp) && !(wp > wnum)) ok = 0;
+ }
+ }
+ if (ok && ok2) {
+ int r = pp;
+ while ((defcpdtable[i].len > r) && ((r+1) < defcpdtable[i].len) &&
+ ((defcpdtable[i].def[r+1] == '*') || (defcpdtable[i].def[r+1] == '?'))) r+=2;
+ if (defcpdtable[i].len <= r) return 1;
+ }
+ // backtrack
+ if (bt) do {
+ ok = 1;
+ btnum[bt - 1]--;
+ pp = btpp[bt - 1];
+ wp = btwp[bt - 1] + (signed short) btnum[bt - 1];
+ } while ((btnum[bt - 1] < 0) && --bt);
+ } while (bt);
+
+ if (ok && ok2 && (!all || (defcpdtable[i].len <= pp))) return 1;
+
+ // check zero ending
+ while (ok && ok2 && (defcpdtable[i].len > pp) && ((pp+1) < defcpdtable[i].len) &&
+ ((defcpdtable[i].def[pp+1] == '*') || (defcpdtable[i].def[pp+1] == '?'))) pp+=2;
+ if (ok && ok2 && (defcpdtable[i].len <= pp)) return 1;
+ }
+ (*words)[wnum] = NULL;
+ if (w) *words = NULL;
+ return 0;
+}
+
+inline int AffixMgr::candidate_check(const char * word, int len)
+{
+ struct hentry * rv=NULL;
+
+ rv = lookup(word);
+ if (rv) return 1;
+
+// rv = prefix_check(word,len,1);
+// if (rv) return 1;
+
+ rv = affix_check(word,len);
+ if (rv) return 1;
+ return 0;
+}
+
+// calculate number of syllable for compound-checking
+short AffixMgr::get_syllable(const char * word, int wlen)
+{
+ if (cpdmaxsyllable==0) return 0;
+
+ short num=0;
+
+ if (!utf8) {
+ for (int i=0; i<wlen; i++) {
+ if (strchr(cpdvowels, word[i])) num++;
+ }
+ } else if (cpdvowels_utf16) {
+ w_char w[MAXWORDUTF8LEN];
+ int i = u8_u16(w, MAXWORDUTF8LEN, word);
+ for (; i > 0; i--) {
+ if (flag_bsearch((unsigned short *) cpdvowels_utf16,
+ ((unsigned short *) w)[i - 1], cpdvowels_utf16_len)) num++;
+ }
+ }
+ return num;
+}
+
+void AffixMgr::setcminmax(int * cmin, int * cmax, const char * word, int len) {
+ if (utf8) {
+ int i;
+ for (*cmin = 0, i = 0; (i < cpdmin) && word[*cmin]; i++) {
+ for ((*cmin)++; (word[*cmin] & 0xc0) == 0x80; (*cmin)++);
+ }
+ for (*cmax = len, i = 0; (i < (cpdmin - 1)) && *cmax; i++) {
+ for ((*cmax)--; (word[*cmax] & 0xc0) == 0x80; (*cmax)--);
+ }
+ } else {
+ *cmin = cpdmin;
+ *cmax = len - cpdmin + 1;
+ }
+}
+
+
+// check if compound word is correctly spelled
+// hu_mov_rule = spec. Hungarian rule (XXX)
+struct hentry * AffixMgr::compound_check(const char * word, int len,
+ short wordnum, short numsyllable, short maxwordnum, short wnum, hentry ** words = NULL,
+ char hu_mov_rule = 0, char is_sug = 0, int * info = NULL)
+{
+ int i;
+ short oldnumsyllable, oldnumsyllable2, oldwordnum, oldwordnum2;
+ struct hentry * rv = NULL;
+ struct hentry * rv_first;
+ struct hentry * rwords[MAXWORDLEN]; // buffer for COMPOUND pattern checking
+ char st [MAXWORDUTF8LEN + 4];
+ char ch = '\0';
+ int cmin;
+ int cmax;
+ int striple = 0;
+ int scpd = 0;
+ int soldi = 0;
+ int oldcmin = 0;
+ int oldcmax = 0;
+ int oldlen = 0;
+ int checkedstriple = 0;
+ int onlycpdrule;
+ int affixed = 0;
+ hentry ** oldwords = words;
+
+ int checked_prefix;
+
+ setcminmax(&cmin, &cmax, word, len);
+
+ strcpy(st, word);
+
+ for (i = cmin; i < cmax; i++) {
+ // go to end of the UTF-8 character
+ if (utf8) {
+ for (; (st[i] & 0xc0) == 0x80; i++);
+ if (i >= cmax) return NULL;
+ }
+
+ words = oldwords;
+ onlycpdrule = (words) ? 1 : 0;
+
+ do { // onlycpdrule loop
+
+ oldnumsyllable = numsyllable;
+ oldwordnum = wordnum;
+ checked_prefix = 0;
+
+
+ do { // simplified checkcompoundpattern loop
+
+ if (scpd > 0) {
+ for (; scpd <= numcheckcpd && (!checkcpdtable[scpd-1].pattern3 ||
+ strncmp(word + i, checkcpdtable[scpd-1].pattern3, strlen(checkcpdtable[scpd-1].pattern3)) != 0); scpd++);
+
+ if (scpd > numcheckcpd) break; // break simplified checkcompoundpattern loop
+ strcpy(st + i, checkcpdtable[scpd-1].pattern);
+ soldi = i;
+ i += strlen(checkcpdtable[scpd-1].pattern);
+ strcpy(st + i, checkcpdtable[scpd-1].pattern2);
+ strcpy(st + i + strlen(checkcpdtable[scpd-1].pattern2), word + soldi + strlen(checkcpdtable[scpd-1].pattern3));
+
+ oldlen = len;
+ len += strlen(checkcpdtable[scpd-1].pattern) + strlen(checkcpdtable[scpd-1].pattern2) - strlen(checkcpdtable[scpd-1].pattern3);
+ oldcmin = cmin;
+ oldcmax = cmax;
+ setcminmax(&cmin, &cmax, st, len);
+
+ cmax = len - cpdmin + 1;
+ }
+
+ ch = st[i];
+ st[i] = '\0';
+
+ sfx = NULL;
+ pfx = NULL;
+
+ // FIRST WORD
+
+ affixed = 1;
+ rv = lookup(st); // perhaps without prefix
+
+ // search homonym with compound flag
+ while ((rv) && !hu_mov_rule &&
+ ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)) ||
+ !((compoundflag && !words && !onlycpdrule && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundbegin && !wordnum && !onlycpdrule &&
+ TESTAFF(rv->astr, compoundbegin, rv->alen)) ||
+ (compoundmiddle && wordnum && !words && !onlycpdrule &&
+ TESTAFF(rv->astr, compoundmiddle, rv->alen)) ||
+ (numdefcpd && onlycpdrule &&
+ ((!words && !wordnum && defcpd_check(&words, wnum, rv, (hentry **) &rwords, 0)) ||
+ (words && defcpd_check(&words, wnum, rv, (hentry **) &rwords, 0))))) ||
+ (scpd != 0 && checkcpdtable[scpd-1].cond != FLAG_NULL &&
+ !TESTAFF(rv->astr, checkcpdtable[scpd-1].cond, rv->alen)))
+ ) {
+ rv = rv->next_homonym;
+ }
+
+ if (rv) affixed = 0;
+
+ if (!rv) {
+ if (onlycpdrule) break;
+ if (compoundflag &&
+ !(rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundflag))) {
+ if ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL,
+ FLAG_NULL, compoundflag, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) && !hu_mov_rule &&
+ sfx->getCont() &&
+ ((compoundforbidflag && TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())) || (compoundend &&
+ TESTAFF(sfx->getCont(), compoundend,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+ }
+
+ if (rv ||
+ (((wordnum == 0) && compoundbegin &&
+ ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL, FLAG_NULL, compoundbegin, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) ||
+ (rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundbegin)))) ||
+ ((wordnum > 0) && compoundmiddle &&
+ ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL, FLAG_NULL, compoundmiddle, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) ||
+ (rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundmiddle)))))
+ ) checked_prefix = 1;
+ // else check forbiddenwords and needaffix
+ } else if (rv->astr && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, needaffix, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ (is_sug && nosuggest && TESTAFF(rv->astr, nosuggest, rv->alen))
+ )) {
+ st[i] = ch;
+ //continue;
+ break;
+ }
+
+ // check non_compound flag in suffix and prefix
+ if ((rv) && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundforbidflag,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check compoundend flag in suffix and prefix
+ if ((rv) && !checked_prefix && compoundend && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundend,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundend,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check compoundmiddle flag in suffix and prefix
+ if ((rv) && !checked_prefix && (wordnum==0) && compoundmiddle && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundmiddle,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundmiddle,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ (is_sug && nosuggest && TESTAFF(rv->astr, nosuggest, rv->alen)))) {
+ return NULL;
+ }
+
+ // increment word number, if the second root has a compoundroot flag
+ if ((rv) && compoundroot &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+
+ // first word is acceptable in compound words?
+ if (((rv) &&
+ ( checked_prefix || (words && words[wnum]) ||
+ (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ ((oldwordnum == 0) && compoundbegin && TESTAFF(rv->astr, compoundbegin, rv->alen)) ||
+ ((oldwordnum > 0) && compoundmiddle && TESTAFF(rv->astr, compoundmiddle, rv->alen))// ||
+// (numdefcpd && )
+
+// LANG_hu section: spec. Hungarian rule
+ || ((langnum == LANG_hu) && hu_mov_rule && (
+ TESTAFF(rv->astr, 'F', rv->alen) || // XXX hardwired Hungarian dictionary codes
+ TESTAFF(rv->astr, 'G', rv->alen) ||
+ TESTAFF(rv->astr, 'H', rv->alen)
+ )
+ )
+// END of LANG_hu section
+ ) &&
+ (
+ // test CHECKCOMPOUNDPATTERN conditions
+ scpd == 0 || checkcpdtable[scpd-1].cond == FLAG_NULL ||
+ TESTAFF(rv->astr, checkcpdtable[scpd-1].cond, rv->alen)
+ )
+ && ! (( checkcompoundtriple && scpd == 0 && !words && // test triple letters
+ (word[i-1]==word[i]) && (
+ ((i>1) && (word[i-1]==word[i-2])) ||
+ ((word[i-1]==word[i+1])) // may be word[i+1] == '\0'
+ )
+ ) ||
+ (
+ checkcompoundcase && scpd == 0 && !words && cpdcase_check(word, i)
+ ))
+ )
+// LANG_hu section: spec. Hungarian rule
+ || ((!rv) && (langnum == LANG_hu) && hu_mov_rule && (rv = affix_check(st,i)) &&
+ (sfx && sfx->getCont() && ( // XXX hardwired Hungarian dic. codes
+ TESTAFF(sfx->getCont(), (unsigned short) 'x', sfx->getContLen()) ||
+ TESTAFF(sfx->getCont(), (unsigned short) '%', sfx->getContLen())
+ )
+ )
+ )
+ ) { // first word is ok condition
+
+// LANG_hu section: spec. Hungarian rule
+ if (langnum == LANG_hu) {
+ // calculate syllable number of the word
+ numsyllable += get_syllable(st, i);
+ // + 1 word, if syllable number of the prefix > 1 (hungarian convention)
+ if (pfx && (get_syllable(pfx->getKey(),strlen(pfx->getKey())) > 1)) wordnum++;
+ }
+// END of LANG_hu section
+
+ // NEXT WORD(S)
+ rv_first = rv;
+ st[i] = ch;
+
+ do { // striple loop
+
+ // check simplifiedtriple
+ if (simplifiedtriple) {
+ if (striple) {
+ checkedstriple = 1;
+ i--; // check "fahrt" instead of "ahrt" in "Schiffahrt"
+ } else if (i > 2 && *(word+i - 1) == *(word + i - 2)) striple = 1;
+ }
+
+ rv = lookup((st+i)); // perhaps without prefix
+
+ // search homonym with compound flag
+ while ((rv) && ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)) ||
+ !((compoundflag && !words && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundend && !words && TESTAFF(rv->astr, compoundend, rv->alen)) ||
+ (numdefcpd && words && defcpd_check(&words, wnum + 1, rv, NULL,1))) ||
+ (scpd != 0 && checkcpdtable[scpd-1].cond2 != FLAG_NULL &&
+ !TESTAFF(rv->astr, checkcpdtable[scpd-1].cond2, rv->alen))
+ )) {
+ rv = rv->next_homonym;
+ }
+
+ // check FORCEUCASE
+ if (rv && forceucase && (rv) &&
+ (TESTAFF(rv->astr, forceucase, rv->alen)) && !(info && *info & SPELL_ORIGCAP)) rv = NULL;
+
+ if (rv && words && words[wnum + 1]) return rv_first;
+
+ oldnumsyllable2 = numsyllable;
+ oldwordnum2 = wordnum;
+
+
+// LANG_hu section: spec. Hungarian rule, XXX hardwired dictionary code
+ if ((rv) && (langnum == LANG_hu) && (TESTAFF(rv->astr, 'I', rv->alen)) && !(TESTAFF(rv->astr, 'J', rv->alen))) {
+ numsyllable--;
+ }
+// END of LANG_hu section
+
+ // increment word number, if the second root has a compoundroot flag
+ if ((rv) && (compoundroot) &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ (is_sug && nosuggest && TESTAFF(rv->astr, nosuggest, rv->alen)))) return NULL;
+
+ // second word is acceptable, as a root?
+ // hungarian conventions: compounding is acceptable,
+ // when compound forms consist of 2 words, or if more,
+ // then the syllable number of root words must be 6, or lesser.
+
+ if ((rv) && (
+ (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundend && TESTAFF(rv->astr, compoundend, rv->alen))
+ )
+ && (
+ ((cpdwordmax==-1) || (wordnum+1<cpdwordmax)) ||
+ ((cpdmaxsyllable!=0) &&
+ (numsyllable + get_syllable(HENTRY_WORD(rv), rv->clen)<=cpdmaxsyllable))
+ ) &&
+ (
+ // test CHECKCOMPOUNDPATTERN
+ !numcheckcpd || scpd != 0 || !cpdpat_check(word, i, rv_first, rv, 0)
+ ) &&
+ (
+ (!checkcompounddup || (rv != rv_first))
+ )
+ // test CHECKCOMPOUNDPATTERN conditions
+ && (scpd == 0 || checkcpdtable[scpd-1].cond2 == FLAG_NULL ||
+ TESTAFF(rv->astr, checkcpdtable[scpd-1].cond2, rv->alen))
+ )
+ {
+ // forbid compound word, if it is a non compound word with typical fault
+ if (checkcompoundrep && cpdrep_check(word,len)) return NULL;
+ return rv_first;
+ }
+
+ numsyllable = oldnumsyllable2;
+ wordnum = oldwordnum2;
+
+ // perhaps second word has prefix or/and suffix
+ sfx = NULL;
+ sfxflag = FLAG_NULL;
+ rv = (compoundflag && !onlycpdrule) ? affix_check((word+i),strlen(word+i), compoundflag, IN_CPD_END) : NULL;
+ if (!rv && compoundend && !onlycpdrule) {
+ sfx = NULL;
+ pfx = NULL;
+ rv = affix_check((word+i),strlen(word+i), compoundend, IN_CPD_END);
+ }
+
+ if (!rv && numdefcpd && words) {
+ rv = affix_check((word+i),strlen(word+i), 0, IN_CPD_END);
+ if (rv && defcpd_check(&words, wnum + 1, rv, NULL, 1)) return rv_first;
+ rv = NULL;
+ }
+
+ // test CHECKCOMPOUNDPATTERN conditions (allowed forms)
+ if (rv && !(scpd == 0 || checkcpdtable[scpd-1].cond2 == FLAG_NULL ||
+ TESTAFF(rv->astr, checkcpdtable[scpd-1].cond2, rv->alen))) rv = NULL;
+
+ // test CHECKCOMPOUNDPATTERN conditions (forbidden compounds)
+ if (rv && numcheckcpd && scpd == 0 && cpdpat_check(word, i, rv_first, rv, affixed)) rv = NULL;
+
+ // check non_compound flag in suffix and prefix
+ if ((rv) &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundforbidflag,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check FORCEUCASE
+ if (rv && forceucase && (rv) &&
+ (TESTAFF(rv->astr, forceucase, rv->alen)) && !(info && *info & SPELL_ORIGCAP)) rv = NULL;
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ (is_sug && nosuggest && TESTAFF(rv->astr, nosuggest, rv->alen)))) return NULL;
+
+ // pfxappnd = prefix of word+i, or NULL
+ // calculate syllable number of prefix.
+ // hungarian convention: when syllable number of prefix is more,
+ // than 1, the prefix+word counts as two words.
+
+ if (langnum == LANG_hu) {
+ // calculate syllable number of the word
+ numsyllable += get_syllable(word + i, strlen(word + i));
+
+ // - affix syllable num.
+ // XXX only second suffix (inflections, not derivations)
+ if (sfxappnd) {
+ char * tmp = myrevstrdup(sfxappnd);
+ numsyllable -= get_syllable(tmp, strlen(tmp));
+ free(tmp);
+ }
+
+ // + 1 word, if syllable number of the prefix > 1 (hungarian convention)
+ if (pfx && (get_syllable(pfx->getKey(),strlen(pfx->getKey())) > 1)) wordnum++;
+
+ // increment syllable num, if last word has a SYLLABLENUM flag
+ // and the suffix is beginning `s'
+
+ if (cpdsyllablenum) {
+ switch (sfxflag) {
+ case 'c': { numsyllable+=2; break; }
+ case 'J': { numsyllable += 1; break; }
+ case 'I': { if (rv && TESTAFF(rv->astr, 'J', rv->alen)) numsyllable += 1; break; }
+ }
+ }
+ }
+
+ // increment word number, if the second word has a compoundroot flag
+ if ((rv) && (compoundroot) &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+
+ // second word is acceptable, as a word with prefix or/and suffix?
+ // hungarian conventions: compounding is acceptable,
+ // when compound forms consist 2 word, otherwise
+ // the syllable number of root words is 6, or lesser.
+ if ((rv) &&
+ (
+ ((cpdwordmax == -1) || (wordnum + 1 < cpdwordmax)) ||
+ ((cpdmaxsyllable != 0) &&
+ (numsyllable <= cpdmaxsyllable))
+ )
+ && (
+ (!checkcompounddup || (rv != rv_first))
+ )) {
+ // forbid compound word, if it is a non compound word with typical fault
+ if (checkcompoundrep && cpdrep_check(word, len)) return NULL;
+ return rv_first;
+ }
+
+ numsyllable = oldnumsyllable2;
+ wordnum = oldwordnum2;
+
+ // perhaps second word is a compound word (recursive call)
+ if (wordnum < maxwordnum) {
+ rv = compound_check((st+i),strlen(st+i), wordnum+1,
+ numsyllable, maxwordnum, wnum + 1, words, 0, is_sug, info);
+
+ if (rv && numcheckcpd && ((scpd == 0 && cpdpat_check(word, i, rv_first, rv, affixed)) ||
+ (scpd != 0 && !cpdpat_check(word, i, rv_first, rv, affixed)))) rv = NULL;
+ } else {
+ rv=NULL;
+ }
+ if (rv) {
+ // forbid compound word, if it is a non compound word with typical fault
+ if (checkcompoundrep || forbiddenword) {
+ struct hentry * rv2 = NULL;
+
+ if (checkcompoundrep && cpdrep_check(word, len)) return NULL;
+
+ // check first part
+ if (strncmp(rv->word, word + i, rv->blen) == 0) {
+ char r = *(st + i + rv->blen);
+ *(st + i + rv->blen) = '\0';
+
+ if (checkcompoundrep && cpdrep_check(st, i + rv->blen)) {
+ *(st + i + rv->blen) = r;
+ continue;
+ }
+
+ if (forbiddenword) {
+ rv2 = lookup(word);
+ if (!rv2) rv2 = affix_check(word, len);
+ if (rv2 && rv2->astr && TESTAFF(rv2->astr, forbiddenword, rv2->alen) &&
+ (strncmp(rv2->word, st, i + rv->blen) == 0)) {
+ return NULL;
+ }
+ }
+ *(st + i + rv->blen) = r;
+ }
+ }
+ return rv_first;
+ }
+ } while (striple && !checkedstriple); // end of striple loop
+
+ if (checkedstriple) {
+ i++;
+ checkedstriple = 0;
+ striple = 0;
+ }
+
+ } // first word is ok condition
+
+ if (soldi != 0) {
+ i = soldi;
+ soldi = 0;
+ len = oldlen;
+ cmin = oldcmin;
+ cmax = oldcmax;
+ }
+ scpd++;
+
+
+ } while (!onlycpdrule && simplifiedcpd && scpd <= numcheckcpd); // end of simplifiedcpd loop
+
+ scpd = 0;
+ wordnum = oldwordnum;
+ numsyllable = oldnumsyllable;
+
+ if (soldi != 0) {
+ i = soldi;
+ strcpy(st, word); // XXX add more optim.
+ soldi = 0;
+ } else st[i] = ch;
+
+ } while (numdefcpd && oldwordnum == 0 && !onlycpdrule && (onlycpdrule = 1)); // end of onlycpd loop
+
+ }
+
+ return NULL;
+}
+
+// check if compound word is correctly spelled
+// hu_mov_rule = spec. Hungarian rule (XXX)
+int AffixMgr::compound_check_morph(const char * word, int len,
+ short wordnum, short numsyllable, short maxwordnum, short wnum, hentry ** words,
+ char hu_mov_rule = 0, char ** result = NULL, char * partresult = NULL)
+{
+ int i;
+ short oldnumsyllable, oldnumsyllable2, oldwordnum, oldwordnum2;
+ int ok = 0;
+
+ struct hentry * rv = NULL;
+ struct hentry * rv_first;
+ struct hentry * rwords[MAXWORDLEN]; // buffer for COMPOUND pattern checking
+ char st [MAXWORDUTF8LEN + 4];
+ char ch;
+
+ int checked_prefix;
+ char presult[MAXLNLEN];
+
+ int cmin;
+ int cmax;
+
+ int onlycpdrule;
+ int affixed = 0;
+ hentry ** oldwords = words;
+
+ setcminmax(&cmin, &cmax, word, len);
+
+ strcpy(st, word);
+
+ for (i = cmin; i < cmax; i++) {
+ oldnumsyllable = numsyllable;
+ oldwordnum = wordnum;
+ checked_prefix = 0;
+
+ // go to end of the UTF-8 character
+ if (utf8) {
+ for (; (st[i] & 0xc0) == 0x80; i++);
+ if (i >= cmax) return 0;
+ }
+
+ words = oldwords;
+ onlycpdrule = (words) ? 1 : 0;
+
+ do { // onlycpdrule loop
+
+ oldnumsyllable = numsyllable;
+ oldwordnum = wordnum;
+ checked_prefix = 0;
+
+ ch = st[i];
+ st[i] = '\0';
+ sfx = NULL;
+
+ // FIRST WORD
+
+ affixed = 1;
+
+ *presult = '\0';
+ if (partresult) mystrcat(presult, partresult, MAXLNLEN);
+
+ rv = lookup(st); // perhaps without prefix
+
+ // search homonym with compound flag
+ while ((rv) && !hu_mov_rule &&
+ ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)) ||
+ !((compoundflag && !words && !onlycpdrule && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundbegin && !wordnum && !onlycpdrule &&
+ TESTAFF(rv->astr, compoundbegin, rv->alen)) ||
+ (compoundmiddle && wordnum && !words && !onlycpdrule &&
+ TESTAFF(rv->astr, compoundmiddle, rv->alen)) ||
+ (numdefcpd && onlycpdrule &&
+ ((!words && !wordnum && defcpd_check(&words, wnum, rv, (hentry **) &rwords, 0)) ||
+ (words && defcpd_check(&words, wnum, rv, (hentry **) &rwords, 0))))
+ ))) {
+ rv = rv->next_homonym;
+ }
+
+ if (rv) affixed = 0;
+
+ if (rv) {
+ sprintf(presult + strlen(presult), "%c%s%s", MSEP_FLD, MORPH_PART, st);
+ if (!HENTRY_FIND(rv, MORPH_STEM)) {
+ sprintf(presult + strlen(presult), "%c%s%s", MSEP_FLD, MORPH_STEM, st);
+ }
+ // store the pointer of the hash entry
+// sprintf(presult + strlen(presult), "%c%s%p", MSEP_FLD, MORPH_HENTRY, rv);
+ if (HENTRY_DATA(rv)) {
+ sprintf(presult + strlen(presult), "%c%s", MSEP_FLD, HENTRY_DATA2(rv));
+ }
+ }
+
+ if (!rv) {
+ if (onlycpdrule) break;
+ if (compoundflag &&
+ !(rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundflag))) {
+ if ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL,
+ FLAG_NULL, compoundflag, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) && !hu_mov_rule &&
+ sfx->getCont() &&
+ ((compoundforbidflag && TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())) || (compoundend &&
+ TESTAFF(sfx->getCont(), compoundend,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+ }
+
+ if (rv ||
+ (((wordnum == 0) && compoundbegin &&
+ ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL, FLAG_NULL, compoundbegin, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) ||
+ (rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundbegin)))) ||
+ ((wordnum > 0) && compoundmiddle &&
+ ((rv = suffix_check(st, i, 0, NULL, NULL, 0, NULL, FLAG_NULL, compoundmiddle, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN)) ||
+ (rv = prefix_check(st, i, hu_mov_rule ? IN_CPD_OTHER : IN_CPD_BEGIN, compoundmiddle)))))
+ ) {
+ // char * p = prefix_check_morph(st, i, 0, compound);
+ char * p = NULL;
+ if (compoundflag) p = affix_check_morph(st, i, compoundflag);
+ if (!p || (*p == '\0')) {
+ if (p) free(p);
+ p = NULL;
+ if ((wordnum == 0) && compoundbegin) {
+ p = affix_check_morph(st, i, compoundbegin);
+ } else if ((wordnum > 0) && compoundmiddle) {
+ p = affix_check_morph(st, i, compoundmiddle);
+ }
+ }
+ if (p && (*p != '\0')) {
+ sprintf(presult + strlen(presult), "%c%s%s%s", MSEP_FLD,
+ MORPH_PART, st, line_uniq_app(&p, MSEP_REC));
+ }
+ if (p) free(p);
+ checked_prefix = 1;
+ }
+ // else check forbiddenwords
+ } else if (rv->astr && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ TESTAFF(rv->astr, needaffix, rv->alen))) {
+ st[i] = ch;
+ continue;
+ }
+
+ // check non_compound flag in suffix and prefix
+ if ((rv) && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundforbidflag,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())))) {
+ continue;
+ }
+
+ // check compoundend flag in suffix and prefix
+ if ((rv) && !checked_prefix && compoundend && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundend,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundend,
+ sfx->getContLen())))) {
+ continue;
+ }
+
+ // check compoundmiddle flag in suffix and prefix
+ if ((rv) && !checked_prefix && (wordnum==0) && compoundmiddle && !hu_mov_rule &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundmiddle,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundmiddle,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen)
+ || TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen))) continue;
+
+ // increment word number, if the second root has a compoundroot flag
+ if ((rv) && (compoundroot) &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+
+ // first word is acceptable in compound words?
+ if (((rv) &&
+ ( checked_prefix || (words && words[wnum]) ||
+ (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ ((oldwordnum == 0) && compoundbegin && TESTAFF(rv->astr, compoundbegin, rv->alen)) ||
+ ((oldwordnum > 0) && compoundmiddle && TESTAFF(rv->astr, compoundmiddle, rv->alen))
+// LANG_hu section: spec. Hungarian rule
+ || ((langnum == LANG_hu) && // hu_mov_rule
+ hu_mov_rule && (
+ TESTAFF(rv->astr, 'F', rv->alen) ||
+ TESTAFF(rv->astr, 'G', rv->alen) ||
+ TESTAFF(rv->astr, 'H', rv->alen)
+ )
+ )
+// END of LANG_hu section
+ )
+ && ! (( checkcompoundtriple && !words && // test triple letters
+ (word[i-1]==word[i]) && (
+ ((i>1) && (word[i-1]==word[i-2])) ||
+ ((word[i-1]==word[i+1])) // may be word[i+1] == '\0'
+ )
+ ) ||
+ (
+ // test CHECKCOMPOUNDPATTERN
+ numcheckcpd && !words && cpdpat_check(word, i, rv, NULL, affixed)
+ ) ||
+ (
+ checkcompoundcase && !words && cpdcase_check(word, i)
+ ))
+ )
+// LANG_hu section: spec. Hungarian rule
+ || ((!rv) && (langnum == LANG_hu) && hu_mov_rule && (rv = affix_check(st,i)) &&
+ (sfx && sfx->getCont() && (
+ TESTAFF(sfx->getCont(), (unsigned short) 'x', sfx->getContLen()) ||
+ TESTAFF(sfx->getCont(), (unsigned short) '%', sfx->getContLen())
+ )
+ )
+ )
+// END of LANG_hu section
+ ) {
+
+// LANG_hu section: spec. Hungarian rule
+ if (langnum == LANG_hu) {
+ // calculate syllable number of the word
+ numsyllable += get_syllable(st, i);
+
+ // + 1 word, if syllable number of the prefix > 1 (hungarian convention)
+ if (pfx && (get_syllable(pfx->getKey(),strlen(pfx->getKey())) > 1)) wordnum++;
+ }
+// END of LANG_hu section
+
+ // NEXT WORD(S)
+ rv_first = rv;
+ rv = lookup((word+i)); // perhaps without prefix
+
+ // search homonym with compound flag
+ while ((rv) && ((needaffix && TESTAFF(rv->astr, needaffix, rv->alen)) ||
+ !((compoundflag && !words && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundend && !words && TESTAFF(rv->astr, compoundend, rv->alen)) ||
+ (numdefcpd && words && defcpd_check(&words, wnum + 1, rv, NULL,1))))) {
+ rv = rv->next_homonym;
+ }
+
+ if (rv && words && words[wnum + 1]) {
+ mystrcat(*result, presult, MAXLNLEN);
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, MORPH_PART, MAXLNLEN);
+ mystrcat(*result, word+i, MAXLNLEN);
+ if (complexprefixes && HENTRY_DATA(rv)) mystrcat(*result, HENTRY_DATA2(rv), MAXLNLEN);
+ if (!HENTRY_FIND(rv, MORPH_STEM)) {
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, MORPH_STEM, MAXLNLEN);
+ mystrcat(*result, HENTRY_WORD(rv), MAXLNLEN);
+ }
+ // store the pointer of the hash entry
+// sprintf(*result + strlen(*result), " %s%p", MORPH_HENTRY, rv);
+ if (!complexprefixes && HENTRY_DATA(rv)) {
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, HENTRY_DATA2(rv), MAXLNLEN);
+ }
+ mystrcat(*result, "\n", MAXLNLEN);
+ ok = 1;
+ return 0;
+ }
+
+ oldnumsyllable2 = numsyllable;
+ oldwordnum2 = wordnum;
+
+// LANG_hu section: spec. Hungarian rule
+ if ((rv) && (langnum == LANG_hu) && (TESTAFF(rv->astr, 'I', rv->alen)) && !(TESTAFF(rv->astr, 'J', rv->alen))) {
+ numsyllable--;
+ }
+// END of LANG_hu section
+ // increment word number, if the second root has a compoundroot flag
+ if ((rv) && (compoundroot) &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr, forbiddenword, rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen))) {
+ st[i] = ch;
+ continue;
+ }
+
+ // second word is acceptable, as a root?
+ // hungarian conventions: compounding is acceptable,
+ // when compound forms consist of 2 words, or if more,
+ // then the syllable number of root words must be 6, or lesser.
+ if ((rv) && (
+ (compoundflag && TESTAFF(rv->astr, compoundflag, rv->alen)) ||
+ (compoundend && TESTAFF(rv->astr, compoundend, rv->alen))
+ )
+ && (
+ ((cpdwordmax==-1) || (wordnum+1<cpdwordmax)) ||
+ ((cpdmaxsyllable!=0) &&
+ (numsyllable+get_syllable(HENTRY_WORD(rv),rv->blen)<=cpdmaxsyllable))
+ )
+ && (
+ (!checkcompounddup || (rv != rv_first))
+ )
+ )
+ {
+ // bad compound word
+ mystrcat(*result, presult, MAXLNLEN);
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, MORPH_PART, MAXLNLEN);
+ mystrcat(*result, word+i, MAXLNLEN);
+
+ if (HENTRY_DATA(rv)) {
+ if (complexprefixes) mystrcat(*result, HENTRY_DATA2(rv), MAXLNLEN);
+ if (! HENTRY_FIND(rv, MORPH_STEM)) {
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, MORPH_STEM, MAXLNLEN);
+ mystrcat(*result, HENTRY_WORD(rv), MAXLNLEN);
+ }
+ // store the pointer of the hash entry
+// sprintf(*result + strlen(*result), " %s%p", MORPH_HENTRY, rv);
+ if (!complexprefixes) {
+ mystrcat(*result, " ", MAXLNLEN);
+ mystrcat(*result, HENTRY_DATA2(rv), MAXLNLEN);
+ }
+ }
+ mystrcat(*result, "\n", MAXLNLEN);
+ ok = 1;
+ }
+
+ numsyllable = oldnumsyllable2 ;
+ wordnum = oldwordnum2;
+
+ // perhaps second word has prefix or/and suffix
+ sfx = NULL;
+ sfxflag = FLAG_NULL;
+
+ if (compoundflag && !onlycpdrule) rv = affix_check((word+i),strlen(word+i), compoundflag); else rv = NULL;
+
+ if (!rv && compoundend && !onlycpdrule) {
+ sfx = NULL;
+ pfx = NULL;
+ rv = affix_check((word+i),strlen(word+i), compoundend);
+ }
+
+ if (!rv && numdefcpd && words) {
+ rv = affix_check((word+i),strlen(word+i), 0, IN_CPD_END);
+ if (rv && words && defcpd_check(&words, wnum + 1, rv, NULL, 1)) {
+ char * m = NULL;
+ if (compoundflag) m = affix_check_morph((word+i),strlen(word+i), compoundflag);
+ if ((!m || *m == '\0') && compoundend) {
+ if (m) free(m);
+ m = affix_check_morph((word+i),strlen(word+i), compoundend);
+ }
+ mystrcat(*result, presult, MAXLNLEN);
+ if (m || (*m != '\0')) {
+ sprintf(*result + strlen(*result), "%c%s%s%s", MSEP_FLD,
+ MORPH_PART, word + i, line_uniq_app(&m, MSEP_REC));
+ }
+ if (m) free(m);
+ mystrcat(*result, "\n", MAXLNLEN);
+ ok = 1;
+ }
+ }
+
+ // check non_compound flag in suffix and prefix
+ if ((rv) &&
+ ((pfx && pfx->getCont() &&
+ TESTAFF(pfx->getCont(), compoundforbidflag,
+ pfx->getContLen())) ||
+ (sfx && sfx->getCont() &&
+ TESTAFF(sfx->getCont(), compoundforbidflag,
+ sfx->getContLen())))) {
+ rv = NULL;
+ }
+
+ // check forbiddenwords
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr,forbiddenword,rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen))
+ && (! TESTAFF(rv->astr, needaffix, rv->alen))) {
+ st[i] = ch;
+ continue;
+ }
+
+ if (langnum == LANG_hu) {
+ // calculate syllable number of the word
+ numsyllable += get_syllable(word + i, strlen(word + i));
+
+ // - affix syllable num.
+ // XXX only second suffix (inflections, not derivations)
+ if (sfxappnd) {
+ char * tmp = myrevstrdup(sfxappnd);
+ numsyllable -= get_syllable(tmp, strlen(tmp));
+ free(tmp);
+ }
+
+ // + 1 word, if syllable number of the prefix > 1 (hungarian convention)
+ if (pfx && (get_syllable(pfx->getKey(),strlen(pfx->getKey())) > 1)) wordnum++;
+
+ // increment syllable num, if last word has a SYLLABLENUM flag
+ // and the suffix is beginning `s'
+
+ if (cpdsyllablenum) {
+ switch (sfxflag) {
+ case 'c': { numsyllable+=2; break; }
+ case 'J': { numsyllable += 1; break; }
+ case 'I': { if (rv && TESTAFF(rv->astr, 'J', rv->alen)) numsyllable += 1; break; }
+ }
+ }
+ }
+
+ // increment word number, if the second word has a compoundroot flag
+ if ((rv) && (compoundroot) &&
+ (TESTAFF(rv->astr, compoundroot, rv->alen))) {
+ wordnum++;
+ }
+ // second word is acceptable, as a word with prefix or/and suffix?
+ // hungarian conventions: compounding is acceptable,
+ // when compound forms consist 2 word, otherwise
+ // the syllable number of root words is 6, or lesser.
+ if ((rv) &&
+ (
+ ((cpdwordmax==-1) || (wordnum+1<cpdwordmax)) ||
+ ((cpdmaxsyllable!=0) &&
+ (numsyllable <= cpdmaxsyllable))
+ )
+ && (
+ (!checkcompounddup || (rv != rv_first))
+ )) {
+ char * m = NULL;
+ if (compoundflag) m = affix_check_morph((word+i),strlen(word+i), compoundflag);
+ if ((!m || *m == '\0') && compoundend) {
+ if (m) free(m);
+ m = affix_check_morph((word+i),strlen(word+i), compoundend);
+ }
+ mystrcat(*result, presult, MAXLNLEN);
+ if (m && (*m != '\0')) {
+ sprintf(*result + strlen(*result), "%c%s%s%s", MSEP_FLD,
+ MORPH_PART, word + i, line_uniq_app(&m, MSEP_REC));
+ }
+ if (m) free(m);
+ sprintf(*result + strlen(*result), "%c", MSEP_REC);
+ ok = 1;
+ }
+
+ numsyllable = oldnumsyllable2;
+ wordnum = oldwordnum2;
+
+ // perhaps second word is a compound word (recursive call)
+ if ((wordnum < maxwordnum) && (ok == 0)) {
+ compound_check_morph((word+i),strlen(word+i), wordnum+1,
+ numsyllable, maxwordnum, wnum + 1, words, 0, result, presult);
+ } else {
+ rv=NULL;
+ }
+ }
+ st[i] = ch;
+ wordnum = oldwordnum;
+ numsyllable = oldnumsyllable;
+
+ } while (numdefcpd && oldwordnum == 0 && !onlycpdrule && (onlycpdrule = 1)); // end of onlycpd loop
+
+ }
+ return 0;
+}
+
+ // return 1 if s1 (reversed) is a leading subset of end of s2
+/* inline int AffixMgr::isRevSubset(const char * s1, const char * end_of_s2, int len)
+ {
+ while ((len > 0) && *s1 && (*s1 == *end_of_s2)) {
+ s1++;
+ end_of_s2--;
+ len--;
+ }
+ return (*s1 == '\0');
+ }
+ */
+
+inline int AffixMgr::isRevSubset(const char * s1, const char * end_of_s2, int len)
+ {
+ while ((len > 0) && (*s1 != '\0') && ((*s1 == *end_of_s2) || (*s1 == '.'))) {
+ s1++;
+ end_of_s2--;
+ len--;
+ }
+ return (*s1 == '\0');
+ }
+
+// check word for suffixes
+
+struct hentry * AffixMgr::suffix_check (const char * word, int len,
+ int sfxopts, PfxEntry * ppfx, char ** wlst, int maxSug, int * ns,
+ const FLAG cclass, const FLAG needflag, char in_compound)
+{
+ struct hentry * rv = NULL;
+ PfxEntry* ep = ppfx;
+
+ // first handle the special case of 0 length suffixes
+ SfxEntry * se = sStart[0];
+
+ while (se) {
+ if (!cclass || se->getCont()) {
+ // suffixes are not allowed in beginning of compounds
+ if ((((in_compound != IN_CPD_BEGIN)) || // && !cclass
+ // except when signed with compoundpermitflag flag
+ (se->getCont() && compoundpermitflag &&
+ TESTAFF(se->getCont(),compoundpermitflag,se->getContLen()))) && (!circumfix ||
+ // no circumfix flag in prefix and suffix
+ ((!ppfx || !(ep->getCont()) || !TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (!se->getCont() || !(TESTAFF(se->getCont(),circumfix,se->getContLen())))) ||
+ // circumfix flag in prefix AND suffix
+ ((ppfx && (ep->getCont()) && TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (se->getCont() && (TESTAFF(se->getCont(),circumfix,se->getContLen()))))) &&
+ // fogemorpheme
+ (in_compound ||
+ !(se->getCont() && (TESTAFF(se->getCont(), onlyincompound, se->getContLen())))) &&
+ // needaffix on prefix or first suffix
+ (cclass ||
+ !(se->getCont() && TESTAFF(se->getCont(), needaffix, se->getContLen())) ||
+ (ppfx && !((ep->getCont()) &&
+ TESTAFF(ep->getCont(), needaffix,
+ ep->getContLen())))
+ )) {
+ rv = se->checkword(word,len, sfxopts, ppfx, wlst, maxSug, ns, (FLAG) cclass,
+ needflag, (in_compound ? 0 : onlyincompound));
+ if (rv) {
+ sfx=se; // BUG: sfx not stateless
+ return rv;
+ }
+ }
+ }
+ se = se->getNext();
+ }
+
+ // now handle the general case
+ if (len == 0) return NULL; // FULLSTRIP
+ unsigned char sp= *((const unsigned char *)(word + len - 1));
+ SfxEntry * sptr = sStart[sp];
+
+ while (sptr) {
+ if (isRevSubset(sptr->getKey(), word + len - 1, len)
+ ) {
+ // suffixes are not allowed in beginning of compounds
+ if ((((in_compound != IN_CPD_BEGIN)) || // && !cclass
+ // except when signed with compoundpermitflag flag
+ (sptr->getCont() && compoundpermitflag &&
+ TESTAFF(sptr->getCont(),compoundpermitflag,sptr->getContLen()))) && (!circumfix ||
+ // no circumfix flag in prefix and suffix
+ ((!ppfx || !(ep->getCont()) || !TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (!sptr->getCont() || !(TESTAFF(sptr->getCont(),circumfix,sptr->getContLen())))) ||
+ // circumfix flag in prefix AND suffix
+ ((ppfx && (ep->getCont()) && TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (sptr->getCont() && (TESTAFF(sptr->getCont(),circumfix,sptr->getContLen()))))) &&
+ // fogemorpheme
+ (in_compound ||
+ !((sptr->getCont() && (TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen()))))) &&
+ // needaffix on prefix or first suffix
+ (cclass ||
+ !(sptr->getCont() && TESTAFF(sptr->getCont(), needaffix, sptr->getContLen())) ||
+ (ppfx && !((ep->getCont()) &&
+ TESTAFF(ep->getCont(), needaffix,
+ ep->getContLen())))
+ )
+ ) if (in_compound != IN_CPD_END || ppfx || !(sptr->getCont() && TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen()))) {
+ rv = sptr->checkword(word,len, sfxopts, ppfx, wlst,
+ maxSug, ns, cclass, needflag, (in_compound ? 0 : onlyincompound));
+ if (rv) {
+ sfx=sptr; // BUG: sfx not stateless
+ sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
+ if (!sptr->getCont()) sfxappnd=sptr->getKey(); // BUG: sfxappnd not stateless
+ return rv;
+ }
+ }
+ sptr = sptr->getNextEQ();
+ } else {
+ sptr = sptr->getNextNE();
+ }
+ }
+
+ return NULL;
+}
+
+// check word for two-level suffixes
+
+struct hentry * AffixMgr::suffix_check_twosfx(const char * word, int len,
+ int sfxopts, PfxEntry * ppfx, const FLAG needflag)
+{
+ struct hentry * rv = NULL;
+
+ // first handle the special case of 0 length suffixes
+ SfxEntry * se = sStart[0];
+ while (se) {
+ if (contclasses[se->getFlag()])
+ {
+ rv = se->check_twosfx(word,len, sfxopts, ppfx, needflag);
+ if (rv) return rv;
+ }
+ se = se->getNext();
+ }
+
+ // now handle the general case
+ if (len == 0) return NULL; // FULLSTRIP
+ unsigned char sp = *((const unsigned char *)(word + len - 1));
+ SfxEntry * sptr = sStart[sp];
+
+ while (sptr) {
+ if (isRevSubset(sptr->getKey(), word + len - 1, len)) {
+ if (contclasses[sptr->getFlag()])
+ {
+ rv = sptr->check_twosfx(word,len, sfxopts, ppfx, needflag);
+ if (rv) {
+ sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
+ if (!sptr->getCont()) sfxappnd=sptr->getKey(); // BUG: sfxappnd not stateless
+ return rv;
+ }
+ }
+ sptr = sptr->getNextEQ();
+ } else {
+ sptr = sptr->getNextNE();
+ }
+ }
+
+ return NULL;
+}
+
+char * AffixMgr::suffix_check_twosfx_morph(const char * word, int len,
+ int sfxopts, PfxEntry * ppfx, const FLAG needflag)
+{
+ char result[MAXLNLEN];
+ char result2[MAXLNLEN];
+ char result3[MAXLNLEN];
+
+ char * st;
+
+ result[0] = '\0';
+ result2[0] = '\0';
+ result3[0] = '\0';
+
+ // first handle the special case of 0 length suffixes
+ SfxEntry * se = sStart[0];
+ while (se) {
+ if (contclasses[se->getFlag()])
+ {
+ st = se->check_twosfx_morph(word,len, sfxopts, ppfx, needflag);
+ if (st) {
+ if (ppfx) {
+ if (ppfx->getMorph()) {
+ mystrcat(result, ppfx->getMorph(), MAXLNLEN);
+ mystrcat(result, " ", MAXLNLEN);
+ } else debugflag(result, ppfx->getFlag());
+ }
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ if (se->getMorph()) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, se->getMorph(), MAXLNLEN);
+ } else debugflag(result, se->getFlag());
+ mystrcat(result, "\n", MAXLNLEN);
+ }
+ }
+ se = se->getNext();
+ }
+
+ // now handle the general case
+ if (len == 0) return NULL; // FULLSTRIP
+ unsigned char sp = *((const unsigned char *)(word + len - 1));
+ SfxEntry * sptr = sStart[sp];
+
+ while (sptr) {
+ if (isRevSubset(sptr->getKey(), word + len - 1, len)) {
+ if (contclasses[sptr->getFlag()])
+ {
+ st = sptr->check_twosfx_morph(word,len, sfxopts, ppfx, needflag);
+ if (st) {
+ sfxflag = sptr->getFlag(); // BUG: sfxflag not stateless
+ if (!sptr->getCont()) sfxappnd=sptr->getKey(); // BUG: sfxappnd not stateless
+ strcpy(result2, st);
+ free(st);
+
+ result3[0] = '\0';
+
+ if (sptr->getMorph()) {
+ mystrcat(result3, " ", MAXLNLEN);
+ mystrcat(result3, sptr->getMorph(), MAXLNLEN);
+ } else debugflag(result3, sptr->getFlag());
+ strlinecat(result2, result3);
+ mystrcat(result2, "\n", MAXLNLEN);
+ mystrcat(result, result2, MAXLNLEN);
+ }
+ }
+ sptr = sptr->getNextEQ();
+ } else {
+ sptr = sptr->getNextNE();
+ }
+ }
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+char * AffixMgr::suffix_check_morph(const char * word, int len,
+ int sfxopts, PfxEntry * ppfx, const FLAG cclass, const FLAG needflag, char in_compound)
+{
+ char result[MAXLNLEN];
+
+ struct hentry * rv = NULL;
+
+ result[0] = '\0';
+
+ PfxEntry* ep = ppfx;
+
+ // first handle the special case of 0 length suffixes
+ SfxEntry * se = sStart[0];
+ while (se) {
+ if (!cclass || se->getCont()) {
+ // suffixes are not allowed in beginning of compounds
+ if (((((in_compound != IN_CPD_BEGIN)) || // && !cclass
+ // except when signed with compoundpermitflag flag
+ (se->getCont() && compoundpermitflag &&
+ TESTAFF(se->getCont(),compoundpermitflag,se->getContLen()))) && (!circumfix ||
+ // no circumfix flag in prefix and suffix
+ ((!ppfx || !(ep->getCont()) || !TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (!se->getCont() || !(TESTAFF(se->getCont(),circumfix,se->getContLen())))) ||
+ // circumfix flag in prefix AND suffix
+ ((ppfx && (ep->getCont()) && TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (se->getCont() && (TESTAFF(se->getCont(),circumfix,se->getContLen()))))) &&
+ // fogemorpheme
+ (in_compound ||
+ !((se->getCont() && (TESTAFF(se->getCont(), onlyincompound, se->getContLen()))))) &&
+ // needaffix on prefix or first suffix
+ (cclass ||
+ !(se->getCont() && TESTAFF(se->getCont(), needaffix, se->getContLen())) ||
+ (ppfx && !((ep->getCont()) &&
+ TESTAFF(ep->getCont(), needaffix,
+ ep->getContLen())))
+ )
+ ))
+ rv = se->checkword(word, len, sfxopts, ppfx, NULL, 0, 0, cclass, needflag);
+ while (rv) {
+ if (ppfx) {
+ if (ppfx->getMorph()) {
+ mystrcat(result, ppfx->getMorph(), MAXLNLEN);
+ mystrcat(result, " ", MAXLNLEN);
+ } else debugflag(result, ppfx->getFlag());
+ }
+ if (complexprefixes && HENTRY_DATA(rv)) mystrcat(result, HENTRY_DATA2(rv), MAXLNLEN);
+ if (! HENTRY_FIND(rv, MORPH_STEM)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_STEM, MAXLNLEN);
+ mystrcat(result, HENTRY_WORD(rv), MAXLNLEN);
+ }
+ // store the pointer of the hash entry
+// sprintf(result + strlen(result), " %s%p", MORPH_HENTRY, rv);
+
+ if (!complexprefixes && HENTRY_DATA(rv)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, HENTRY_DATA2(rv), MAXLNLEN);
+ }
+ if (se->getMorph()) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, se->getMorph(), MAXLNLEN);
+ } else debugflag(result, se->getFlag());
+ mystrcat(result, "\n", MAXLNLEN);
+ rv = se->get_next_homonym(rv, sfxopts, ppfx, cclass, needflag);
+ }
+ }
+ se = se->getNext();
+ }
+
+ // now handle the general case
+ if (len == 0) return NULL; // FULLSTRIP
+ unsigned char sp = *((const unsigned char *)(word + len - 1));
+ SfxEntry * sptr = sStart[sp];
+
+ while (sptr) {
+ if (isRevSubset(sptr->getKey(), word + len - 1, len)
+ ) {
+ // suffixes are not allowed in beginning of compounds
+ if (((((in_compound != IN_CPD_BEGIN)) || // && !cclass
+ // except when signed with compoundpermitflag flag
+ (sptr->getCont() && compoundpermitflag &&
+ TESTAFF(sptr->getCont(),compoundpermitflag,sptr->getContLen()))) && (!circumfix ||
+ // no circumfix flag in prefix and suffix
+ ((!ppfx || !(ep->getCont()) || !TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (!sptr->getCont() || !(TESTAFF(sptr->getCont(),circumfix,sptr->getContLen())))) ||
+ // circumfix flag in prefix AND suffix
+ ((ppfx && (ep->getCont()) && TESTAFF(ep->getCont(),
+ circumfix, ep->getContLen())) &&
+ (sptr->getCont() && (TESTAFF(sptr->getCont(),circumfix,sptr->getContLen()))))) &&
+ // fogemorpheme
+ (in_compound ||
+ !((sptr->getCont() && (TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen()))))) &&
+ // needaffix on first suffix
+ (cclass || !(sptr->getCont() &&
+ TESTAFF(sptr->getCont(), needaffix, sptr->getContLen())))
+ )) rv = sptr->checkword(word,len, sfxopts, ppfx, NULL, 0, 0, cclass, needflag);
+ while (rv) {
+ if (ppfx) {
+ if (ppfx->getMorph()) {
+ mystrcat(result, ppfx->getMorph(), MAXLNLEN);
+ mystrcat(result, " ", MAXLNLEN);
+ } else debugflag(result, ppfx->getFlag());
+ }
+ if (complexprefixes && HENTRY_DATA(rv)) mystrcat(result, HENTRY_DATA2(rv), MAXLNLEN);
+ if (! HENTRY_FIND(rv, MORPH_STEM)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_STEM, MAXLNLEN);
+ mystrcat(result, HENTRY_WORD(rv), MAXLNLEN);
+ }
+ // store the pointer of the hash entry
+// sprintf(result + strlen(result), " %s%p", MORPH_HENTRY, rv);
+
+ if (!complexprefixes && HENTRY_DATA(rv)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, HENTRY_DATA2(rv), MAXLNLEN);
+ }
+
+ if (sptr->getMorph()) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, sptr->getMorph(), MAXLNLEN);
+ } else debugflag(result, sptr->getFlag());
+ mystrcat(result, "\n", MAXLNLEN);
+ rv = sptr->get_next_homonym(rv, sfxopts, ppfx, cclass, needflag);
+ }
+ sptr = sptr->getNextEQ();
+ } else {
+ sptr = sptr->getNextNE();
+ }
+ }
+
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+// check if word with affixes is correctly spelled
+struct hentry * AffixMgr::affix_check (const char * word, int len, const FLAG needflag, char in_compound)
+{
+ struct hentry * rv= NULL;
+
+ // check all prefixes (also crossed with suffixes if allowed)
+ rv = prefix_check(word, len, in_compound, needflag);
+ if (rv) return rv;
+
+ // if still not found check all suffixes
+ rv = suffix_check(word, len, 0, NULL, NULL, 0, NULL, FLAG_NULL, needflag, in_compound);
+
+ if (havecontclass) {
+ sfx = NULL;
+ pfx = NULL;
+
+ if (rv) return rv;
+ // if still not found check all two-level suffixes
+ rv = suffix_check_twosfx(word, len, 0, NULL, needflag);
+
+ if (rv) return rv;
+ // if still not found check all two-level suffixes
+ rv = prefix_check_twosfx(word, len, IN_CPD_NOT, needflag);
+ }
+
+ return rv;
+}
+
+// check if word with affixes is correctly spelled
+char * AffixMgr::affix_check_morph(const char * word, int len, const FLAG needflag, char in_compound)
+{
+ char result[MAXLNLEN];
+ char * st = NULL;
+
+ *result = '\0';
+
+ // check all prefixes (also crossed with suffixes if allowed)
+ st = prefix_check_morph(word, len, in_compound);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+
+ // if still not found check all suffixes
+ st = suffix_check_morph(word, len, 0, NULL, '\0', needflag, in_compound);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+
+ if (havecontclass) {
+ sfx = NULL;
+ pfx = NULL;
+ // if still not found check all two-level suffixes
+ st = suffix_check_twosfx_morph(word, len, 0, NULL, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+
+ // if still not found check all two-level suffixes
+ st = prefix_check_twosfx_morph(word, len, IN_CPD_NOT, needflag);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ }
+
+ return mystrdup(result);
+}
+
+char * AffixMgr::morphgen(char * ts, int wl, const unsigned short * ap,
+ unsigned short al, char * morph, char * targetmorph, int level)
+{
+ // handle suffixes
+ char * stemmorph;
+ char * stemmorphcatpos;
+ char mymorph[MAXLNLEN];
+
+ if (!morph) return NULL;
+
+ // check substandard flag
+ if (TESTAFF(ap, substandard, al)) return NULL;
+
+ if (morphcmp(morph, targetmorph) == 0) return mystrdup(ts);
+
+// int targetcount = get_sfxcount(targetmorph);
+
+ // use input suffix fields, if exist
+ if (strstr(morph, MORPH_INFL_SFX) || strstr(morph, MORPH_DERI_SFX)) {
+ stemmorph = mymorph;
+ strcpy(stemmorph, morph);
+ mystrcat(stemmorph, " ", MAXLNLEN);
+ stemmorphcatpos = stemmorph + strlen(stemmorph);
+ } else {
+ stemmorph = morph;
+ stemmorphcatpos = NULL;
+ }
+
+ for (int i = 0; i < al; i++) {
+ const unsigned char c = (unsigned char) (ap[i] & 0x00FF);
+ SfxEntry * sptr = sFlag[c];
+ while (sptr) {
+ if (sptr->getFlag() == ap[i] && sptr->getMorph() && ((sptr->getContLen() == 0) ||
+ // don't generate forms with substandard affixes
+ !TESTAFF(sptr->getCont(), substandard, sptr->getContLen()))) {
+
+ if (stemmorphcatpos) strcpy(stemmorphcatpos, sptr->getMorph());
+ else stemmorph = (char *) sptr->getMorph();
+
+ int cmp = morphcmp(stemmorph, targetmorph);
+
+ if (cmp == 0) {
+ char * newword = sptr->add(ts, wl);
+ if (newword) {
+ hentry * check = pHMgr->lookup(newword); // XXX extra dic
+ if (!check || !check->astr ||
+ !(TESTAFF(check->astr, forbiddenword, check->alen) ||
+ TESTAFF(check->astr, ONLYUPCASEFLAG, check->alen))) {
+ return newword;
+ }
+ free(newword);
+ }
+ }
+
+ // recursive call for secondary suffixes
+ if ((level == 0) && (cmp == 1) && (sptr->getContLen() > 0) &&
+// (get_sfxcount(stemmorph) < targetcount) &&
+ !TESTAFF(sptr->getCont(), substandard, sptr->getContLen())) {
+ char * newword = sptr->add(ts, wl);
+ if (newword) {
+ char * newword2 = morphgen(newword, strlen(newword), sptr->getCont(),
+ sptr->getContLen(), stemmorph, targetmorph, 1);
+
+ if (newword2) {
+ free(newword);
+ return newword2;
+ }
+ free(newword);
+ newword = NULL;
+ }
+ }
+ }
+ sptr = sptr->getFlgNxt();
+ }
+ }
+ return NULL;
+}
+
+
+int AffixMgr::expand_rootword(struct guessword * wlst, int maxn, const char * ts,
+ int wl, const unsigned short * ap, unsigned short al, char * bad, int badl,
+ char * phon)
+{
+ int nh=0;
+ // first add root word to list
+ if ((nh < maxn) && !(al && ((needaffix && TESTAFF(ap, needaffix, al)) ||
+ (onlyincompound && TESTAFF(ap, onlyincompound, al))))) {
+ wlst[nh].word = mystrdup(ts);
+ if (!wlst[nh].word) return 0;
+ wlst[nh].allow = (1 == 0);
+ wlst[nh].orig = NULL;
+ nh++;
+ // add special phonetic version
+ if (phon && (nh < maxn)) {
+ wlst[nh].word = mystrdup(phon);
+ if (!wlst[nh].word) return nh - 1;
+ wlst[nh].allow = (1 == 0);
+ wlst[nh].orig = mystrdup(ts);
+ if (!wlst[nh].orig) return nh - 1;
+ nh++;
+ }
+ }
+
+ // handle suffixes
+ for (int i = 0; i < al; i++) {
+ const unsigned char c = (unsigned char) (ap[i] & 0x00FF);
+ SfxEntry * sptr = sFlag[c];
+ while (sptr) {
+ if ((sptr->getFlag() == ap[i]) && (!sptr->getKeyLen() || ((badl > sptr->getKeyLen()) &&
+ (strcmp(sptr->getAffix(), bad + badl - sptr->getKeyLen()) == 0))) &&
+ // check needaffix flag
+ !(sptr->getCont() && ((needaffix &&
+ TESTAFF(sptr->getCont(), needaffix, sptr->getContLen())) ||
+ (circumfix &&
+ TESTAFF(sptr->getCont(), circumfix, sptr->getContLen())) ||
+ (onlyincompound &&
+ TESTAFF(sptr->getCont(), onlyincompound, sptr->getContLen()))))
+ ) {
+ char * newword = sptr->add(ts, wl);
+ if (newword) {
+ if (nh < maxn) {
+ wlst[nh].word = newword;
+ wlst[nh].allow = sptr->allowCross();
+ wlst[nh].orig = NULL;
+ nh++;
+ // add special phonetic version
+ if (phon && (nh < maxn)) {
+ char st[MAXWORDUTF8LEN];
+ strcpy(st, phon);
+ strcat(st, sptr->getKey());
+ reverseword(st + strlen(phon));
+ wlst[nh].word = mystrdup(st);
+ if (!wlst[nh].word) return nh - 1;
+ wlst[nh].allow = (1 == 0);
+ wlst[nh].orig = mystrdup(newword);
+ if (!wlst[nh].orig) return nh - 1;
+ nh++;
+ }
+ } else {
+ free(newword);
+ }
+ }
+ }
+ sptr = sptr->getFlgNxt();
+ }
+ }
+
+ int n = nh;
+
+ // handle cross products of prefixes and suffixes
+ for (int j=1;j<n ;j++)
+ if (wlst[j].allow) {
+ for (int k = 0; k < al; k++) {
+ const unsigned char c = (unsigned char) (ap[k] & 0x00FF);
+ PfxEntry * cptr = pFlag[c];
+ while (cptr) {
+ if ((cptr->getFlag() == ap[k]) && cptr->allowCross() && (!cptr->getKeyLen() || ((badl > cptr->getKeyLen()) &&
+ (strncmp(cptr->getKey(), bad, cptr->getKeyLen()) == 0)))) {
+ int l1 = strlen(wlst[j].word);
+ char * newword = cptr->add(wlst[j].word, l1);
+ if (newword) {
+ if (nh < maxn) {
+ wlst[nh].word = newword;
+ wlst[nh].allow = cptr->allowCross();
+ wlst[nh].orig = NULL;
+ nh++;
+ } else {
+ free(newword);
+ }
+ }
+ }
+ cptr = cptr->getFlgNxt();
+ }
+ }
+ }
+
+
+ // now handle pure prefixes
+ for (int m = 0; m < al; m ++) {
+ const unsigned char c = (unsigned char) (ap[m] & 0x00FF);
+ PfxEntry * ptr = pFlag[c];
+ while (ptr) {
+ if ((ptr->getFlag() == ap[m]) && (!ptr->getKeyLen() || ((badl > ptr->getKeyLen()) &&
+ (strncmp(ptr->getKey(), bad, ptr->getKeyLen()) == 0))) &&
+ // check needaffix flag
+ !(ptr->getCont() && ((needaffix &&
+ TESTAFF(ptr->getCont(), needaffix, ptr->getContLen())) ||
+ (circumfix &&
+ TESTAFF(ptr->getCont(), circumfix, ptr->getContLen())) ||
+ (onlyincompound &&
+ TESTAFF(ptr->getCont(), onlyincompound, ptr->getContLen()))))
+ ) {
+ char * newword = ptr->add(ts, wl);
+ if (newword) {
+ if (nh < maxn) {
+ wlst[nh].word = newword;
+ wlst[nh].allow = ptr->allowCross();
+ wlst[nh].orig = NULL;
+ nh++;
+ } else {
+ free(newword);
+ }
+ }
+ }
+ ptr = ptr->getFlgNxt();
+ }
+ }
+
+ return nh;
+}
+
+// return length of replacing table
+int AffixMgr::get_numrep() const
+{
+ return numrep;
+}
+
+// return replacing table
+struct replentry * AffixMgr::get_reptable() const
+{
+ if (! reptable ) return NULL;
+ return reptable;
+}
+
+// return iconv table
+RepList * AffixMgr::get_iconvtable() const
+{
+ if (! iconvtable ) return NULL;
+ return iconvtable;
+}
+
+// return oconv table
+RepList * AffixMgr::get_oconvtable() const
+{
+ if (! oconvtable ) return NULL;
+ return oconvtable;
+}
+
+// return replacing table
+struct phonetable * AffixMgr::get_phonetable() const
+{
+ if (! phone ) return NULL;
+ return phone;
+}
+
+// return length of character map table
+int AffixMgr::get_nummap() const
+{
+ return nummap;
+}
+
+// return character map table
+struct mapentry * AffixMgr::get_maptable() const
+{
+ if (! maptable ) return NULL;
+ return maptable;
+}
+
+// return length of word break table
+int AffixMgr::get_numbreak() const
+{
+ return numbreak;
+}
+
+// return character map table
+char ** AffixMgr::get_breaktable() const
+{
+ if (! breaktable ) return NULL;
+ return breaktable;
+}
+
+// return text encoding of dictionary
+char * AffixMgr::get_encoding()
+{
+ if (! encoding ) encoding = mystrdup(SPELL_ENCODING);
+ return mystrdup(encoding);
+}
+
+// return text encoding of dictionary
+int AffixMgr::get_langnum() const
+{
+ return langnum;
+}
+
+// return double prefix option
+int AffixMgr::get_complexprefixes() const
+{
+ return complexprefixes;
+}
+
+// return FULLSTRIP option
+int AffixMgr::get_fullstrip() const
+{
+ return fullstrip;
+}
+
+FLAG AffixMgr::get_keepcase() const
+{
+ return keepcase;
+}
+
+FLAG AffixMgr::get_forceucase() const
+{
+ return forceucase;
+}
+
+FLAG AffixMgr::get_warn() const
+{
+ return warn;
+}
+
+int AffixMgr::get_forbidwarn() const
+{
+ return forbidwarn;
+}
+
+int AffixMgr::get_checksharps() const
+{
+ return checksharps;
+}
+
+char * AffixMgr::encode_flag(unsigned short aflag) const
+{
+ return pHMgr->encode_flag(aflag);
+}
+
+
+// return the preferred ignore string for suggestions
+char * AffixMgr::get_ignore() const
+{
+ if (!ignorechars) return NULL;
+ return ignorechars;
+}
+
+// return the preferred ignore string for suggestions
+unsigned short * AffixMgr::get_ignore_utf16(int * len) const
+{
+ *len = ignorechars_utf16_len;
+ return ignorechars_utf16;
+}
+
+// return the keyboard string for suggestions
+char * AffixMgr::get_key_string()
+{
+ if (! keystring ) keystring = mystrdup(SPELL_KEYSTRING);
+ return mystrdup(keystring);
+}
+
+// return the preferred try string for suggestions
+char * AffixMgr::get_try_string() const
+{
+ if (! trystring ) return NULL;
+ return mystrdup(trystring);
+}
+
+// return the preferred try string for suggestions
+const char * AffixMgr::get_wordchars() const
+{
+ return wordchars;
+}
+
+unsigned short * AffixMgr::get_wordchars_utf16(int * len) const
+{
+ *len = wordchars_utf16_len;
+ return wordchars_utf16;
+}
+
+// is there compounding?
+int AffixMgr::get_compound() const
+{
+ return compoundflag || compoundbegin || numdefcpd;
+}
+
+// return the compound words control flag
+FLAG AffixMgr::get_compoundflag() const
+{
+ return compoundflag;
+}
+
+// return the forbidden words control flag
+FLAG AffixMgr::get_forbiddenword() const
+{
+ return forbiddenword;
+}
+
+// return the forbidden words control flag
+FLAG AffixMgr::get_nosuggest() const
+{
+ return nosuggest;
+}
+
+// return the forbidden words control flag
+FLAG AffixMgr::get_nongramsuggest() const
+{
+ return nongramsuggest;
+}
+
+// return the forbidden words flag modify flag
+FLAG AffixMgr::get_needaffix() const
+{
+ return needaffix;
+}
+
+// return the onlyincompound flag
+FLAG AffixMgr::get_onlyincompound() const
+{
+ return onlyincompound;
+}
+
+// return the compound word signal flag
+FLAG AffixMgr::get_compoundroot() const
+{
+ return compoundroot;
+}
+
+// return the compound begin signal flag
+FLAG AffixMgr::get_compoundbegin() const
+{
+ return compoundbegin;
+}
+
+// return the value of checknum
+int AffixMgr::get_checknum() const
+{
+ return checknum;
+}
+
+// return the value of prefix
+const char * AffixMgr::get_prefix() const
+{
+ if (pfx) return pfx->getKey();
+ return NULL;
+}
+
+// return the value of suffix
+const char * AffixMgr::get_suffix() const
+{
+ return sfxappnd;
+}
+
+// return the value of suffix
+const char * AffixMgr::get_version() const
+{
+ return version;
+}
+
+// return lemma_present flag
+FLAG AffixMgr::get_lemma_present() const
+{
+ return lemma_present;
+}
+
+// utility method to look up root words in hash table
+struct hentry * AffixMgr::lookup(const char * word)
+{
+ int i;
+ struct hentry * he = NULL;
+ for (i = 0; i < *maxdic && !he; i++) {
+ he = (alldic[i])->lookup(word);
+ }
+ return he;
+}
+
+// return the value of suffix
+int AffixMgr::have_contclass() const
+{
+ return havecontclass;
+}
+
+// return utf8
+int AffixMgr::get_utf8() const
+{
+ return utf8;
+}
+
+int AffixMgr::get_maxngramsugs(void) const
+{
+ return maxngramsugs;
+}
+
+int AffixMgr::get_maxcpdsugs(void) const
+{
+ return maxcpdsugs;
+}
+
+int AffixMgr::get_maxdiff(void) const
+{
+ return maxdiff;
+}
+
+int AffixMgr::get_onlymaxdiff(void) const
+{
+ return onlymaxdiff;
+}
+
+// return nosplitsugs
+int AffixMgr::get_nosplitsugs(void) const
+{
+ return nosplitsugs;
+}
+
+// return sugswithdots
+int AffixMgr::get_sugswithdots(void) const
+{
+ return sugswithdots;
+}
+
+/* parse flag */
+int AffixMgr::parse_flag(char * line, unsigned short * out, FileMgr * af) {
+ char * s = NULL;
+ if (*out != FLAG_NULL && !(*out >= DEFAULTFLAGS)) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple definitions of an affix file parameter\n", af->getlinenum());
+ return 1;
+ }
+ if (parse_string(line, &s, af->getlinenum())) return 1;
+ *out = pHMgr->decode_flag(s);
+ free(s);
+ return 0;
+}
+
+/* parse num */
+int AffixMgr::parse_num(char * line, int * out, FileMgr * af) {
+ char * s = NULL;
+ if (*out != -1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple definitions of an affix file parameter\n", af->getlinenum());
+ return 1;
+ }
+ if (parse_string(line, &s, af->getlinenum())) return 1;
+ *out = atoi(s);
+ free(s);
+ return 0;
+}
+
+/* parse in the max syllablecount of compound words and */
+int AffixMgr::parse_cpdsyllable(char * line, FileMgr * af)
+{
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ w_char w[MAXWORDLEN];
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: { cpdmaxsyllable = atoi(piece); np++; break; }
+ case 2: {
+ if (!utf8) {
+ cpdvowels = mystrdup(piece);
+ } else {
+ int n = u8_u16(w, MAXWORDLEN, piece);
+ if (n > 0) {
+ flag_qsort((unsigned short *) w, 0, n);
+ cpdvowels_utf16 = (w_char *) malloc(n * sizeof(w_char));
+ if (!cpdvowels_utf16) return 1;
+ memcpy(cpdvowels_utf16, w, n * sizeof(w_char));
+ }
+ cpdvowels_utf16_len = n;
+ }
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np < 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing compoundsyllable information\n", af->getlinenum());
+ return 1;
+ }
+ if (np == 2) cpdvowels = mystrdup("aeiouAEIOU");
+ return 0;
+}
+
+/* parse in the typical fault correcting table */
+int AffixMgr::parse_reptable(char * line, FileMgr * af)
+{
+ if (numrep != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numrep = atoi(piece);
+ if (numrep < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: incorrect entry number\n", af->getlinenum());
+ return 1;
+ }
+ reptable = (replentry *) malloc(numrep * sizeof(struct replentry));
+ if (!reptable) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numrep lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numrep; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ reptable[j].pattern = NULL;
+ reptable[j].pattern2 = NULL;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"REP",3) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numrep = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ if (*piece == '^') reptable[j].start = true; else reptable[j].start = false;
+ reptable[j].pattern = mystrrep(mystrdup(piece + int(reptable[j].start)),"_"," ");
+ int lr = strlen(reptable[j].pattern) - 1;
+ if (reptable[j].pattern[lr] == '$') {
+ reptable[j].end = true;
+ reptable[j].pattern[lr] = '\0';
+ } else reptable[j].end = false;
+ break;
+ }
+ case 2: { reptable[j].pattern2 = mystrrep(mystrdup(piece),"_"," "); break; }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if ((!(reptable[j].pattern)) || (!(reptable[j].pattern2))) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numrep = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* parse in the typical fault correcting table */
+int AffixMgr::parse_convtable(char * line, FileMgr * af, RepList ** rl, const char * keyword)
+{
+ if (*rl) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ int numrl = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numrl = atoi(piece);
+ if (numrl < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: incorrect entry number\n", af->getlinenum());
+ return 1;
+ }
+ *rl = new RepList(numrl);
+ if (!*rl) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the num lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numrl; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ char * pattern = NULL;
+ char * pattern2 = NULL;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece, keyword, sizeof(keyword)) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ delete *rl;
+ *rl = NULL;
+ return 1;
+ }
+ break;
+ }
+ case 1: { pattern = mystrrep(mystrdup(piece),"_"," "); break; }
+ case 2: {
+ pattern2 = mystrrep(mystrdup(piece),"_"," ");
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (!pattern || !pattern2) {
+ if (pattern)
+ free(pattern);
+ if (pattern2)
+ free(pattern2);
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ return 1;
+ }
+ (*rl)->add(pattern, pattern2);
+ }
+ return 0;
+}
+
+
+/* parse in the typical fault correcting table */
+int AffixMgr::parse_phonetable(char * line, FileMgr * af)
+{
+ if (phone) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ phone = (phonetable *) malloc(sizeof(struct phonetable));
+ if (!phone) return 1;
+ phone->num = atoi(piece);
+ phone->rules = NULL;
+ phone->utf8 = (char) utf8;
+ if (phone->num < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ phone->rules = (char * *) malloc(2 * (phone->num + 1) * sizeof(char *));
+ if (!phone->rules) {
+ free(phone);
+ phone = NULL;
+ return 1;
+ }
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the phone->num lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < phone->num; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ phone->rules[j * 2] = NULL;
+ phone->rules[j * 2 + 1] = NULL;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"PHONE",5) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ phone->num = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: { phone->rules[j * 2] = mystrrep(mystrdup(piece),"_",""); break; }
+ case 2: { phone->rules[j * 2 + 1] = mystrrep(mystrdup(piece),"_",""); break; }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if ((!(phone->rules[j * 2])) || (!(phone->rules[j * 2 + 1]))) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ phone->num = 0;
+ return 1;
+ }
+ }
+ phone->rules[phone->num * 2] = mystrdup("");
+ phone->rules[phone->num * 2 + 1] = mystrdup("");
+ init_phonet_hash(*phone);
+ return 0;
+}
+
+/* parse in the checkcompoundpattern table */
+int AffixMgr::parse_checkcpdtable(char * line, FileMgr * af)
+{
+ if (numcheckcpd != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numcheckcpd = atoi(piece);
+ if (numcheckcpd < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ checkcpdtable = (patentry *) malloc(numcheckcpd * sizeof(struct patentry));
+ if (!checkcpdtable) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numcheckcpd lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numcheckcpd; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ checkcpdtable[j].pattern = NULL;
+ checkcpdtable[j].pattern2 = NULL;
+ checkcpdtable[j].pattern3 = NULL;
+ checkcpdtable[j].cond = FLAG_NULL;
+ checkcpdtable[j].cond2 = FLAG_NULL;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"CHECKCOMPOUNDPATTERN",20) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numcheckcpd = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ checkcpdtable[j].pattern = mystrdup(piece);
+ char * p = strchr(checkcpdtable[j].pattern, '/');
+ if (p) {
+ *p = '\0';
+ checkcpdtable[j].cond = pHMgr->decode_flag(p + 1);
+ }
+ break; }
+ case 2: {
+ checkcpdtable[j].pattern2 = mystrdup(piece);
+ char * p = strchr(checkcpdtable[j].pattern2, '/');
+ if (p) {
+ *p = '\0';
+ checkcpdtable[j].cond2 = pHMgr->decode_flag(p + 1);
+ }
+ break;
+ }
+ case 3: { checkcpdtable[j].pattern3 = mystrdup(piece); simplifiedcpd = 1; break; }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if ((!(checkcpdtable[j].pattern)) || (!(checkcpdtable[j].pattern2))) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numcheckcpd = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* parse in the compound rule table */
+int AffixMgr::parse_defcpdtable(char * line, FileMgr * af)
+{
+ if (numdefcpd != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numdefcpd = atoi(piece);
+ if (numdefcpd < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ defcpdtable = (flagentry *) malloc(numdefcpd * sizeof(flagentry));
+ if (!defcpdtable) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numdefcpd lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numdefcpd; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ defcpdtable[j].def = NULL;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece, "COMPOUNDRULE", 12) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numdefcpd = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: { // handle parenthesized flags
+ if (strchr(piece, '(')) {
+ defcpdtable[j].def = (FLAG *) malloc(strlen(piece) * sizeof(FLAG));
+ defcpdtable[j].len = 0;
+ int end = 0;
+ FLAG * conv;
+ while (!end) {
+ char * par = piece + 1;
+ while (*par != '(' && *par != ')' && *par != '\0') par++;
+ if (*par == '\0') end = 1; else *par = '\0';
+ if (*piece == '(') piece++;
+ if (*piece == '*' || *piece == '?') {
+ defcpdtable[j].def[defcpdtable[j].len++] = (FLAG) *piece;
+ } else if (*piece != '\0') {
+ int l = pHMgr->decode_flags(&conv, piece, af);
+ for (int k = 0; k < l; k++) defcpdtable[j].def[defcpdtable[j].len++] = conv[k];
+ free(conv);
+ }
+ piece = par + 1;
+ }
+ } else {
+ defcpdtable[j].len = pHMgr->decode_flags(&(defcpdtable[j].def), piece, af);
+ }
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (!defcpdtable[j].len) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numdefcpd = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* parse in the character map table */
+int AffixMgr::parse_maptable(char * line, FileMgr * af)
+{
+ if (nummap != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ nummap = atoi(piece);
+ if (nummap < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ maptable = (mapentry *) malloc(nummap * sizeof(struct mapentry));
+ if (!maptable) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the nummap lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < nummap; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ maptable[j].set = NULL;
+ maptable[j].len = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"MAP",3) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ nummap = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ int setn = 0;
+ maptable[j].len = strlen(piece);
+ maptable[j].set = (char **) malloc(maptable[j].len * sizeof(char*));
+ if (!maptable[j].set) return 1;
+ for (int k = 0; k < maptable[j].len; k++) {
+ int chl = 1;
+ int chb = k;
+ if (piece[k] == '(') {
+ char * parpos = strchr(piece + k, ')');
+ if (parpos != NULL) {
+ chb = k + 1;
+ chl = (int)(parpos - piece) - k - 1;
+ k = k + chl + 1;
+ }
+ } else {
+ if (utf8 && (piece[k] & 0xc0) == 0xc0) {
+ for (k++; utf8 && (piece[k] & 0xc0) == 0x80; k++);
+ chl = k - chb;
+ k--;
+ }
+ }
+ maptable[j].set[setn] = (char *) malloc(chl + 1);
+ if (!maptable[j].set[setn]) return 1;
+ strncpy(maptable[j].set[setn], piece + chb, chl);
+ maptable[j].set[setn][chl] = '\0';
+ setn++;
+ }
+ maptable[j].len = setn;
+ break; }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (!maptable[j].set || !maptable[j].len) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ nummap = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* parse in the word breakpoint table */
+int AffixMgr::parse_breaktable(char * line, FileMgr * af)
+{
+ if (numbreak > -1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numbreak = atoi(piece);
+ if (numbreak < 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ if (numbreak == 0) return 0;
+ breaktable = (char **) malloc(numbreak * sizeof(char *));
+ if (!breaktable) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numbreak lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numbreak; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"BREAK",5) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numbreak = 0;
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ breaktable[j] = mystrdup(piece);
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (!breaktable) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numbreak = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void AffixMgr::reverse_condition(char * piece) {
+ int neg = 0;
+ for (char * k = piece + strlen(piece) - 1; k >= piece; k--) {
+ switch(*k) {
+ case '[': {
+ if (neg) *(k+1) = '['; else *k = ']';
+ break;
+ }
+ case ']': {
+ *k = '[';
+ if (neg) *(k+1) = '^';
+ neg = 0;
+ break;
+ }
+ case '^': {
+ if (*(k+1) == ']') neg = 1; else *(k+1) = *k;
+ break;
+ }
+ default: {
+ if (neg) *(k+1) = *k;
+ }
+ }
+ }
+}
+
+int AffixMgr::parse_affix(char * line, const char at, FileMgr * af, char * dupflags)
+{
+ int numents = 0; // number of affentry structures to parse
+
+ unsigned short aflag = 0; // affix char identifier
+
+ char ff=0;
+ std::vector<affentry> affentries;
+
+ char * tp = line;
+ char * nl = line;
+ char * piece;
+ int i = 0;
+
+ // checking lines with bad syntax
+#ifdef DEBUG
+ int basefieldnum = 0;
+#endif
+
+ // split affix header line into pieces
+
+ int np = 0;
+
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ // piece 1 - is type of affix
+ case 0: { np++; break; }
+
+ // piece 2 - is affix char
+ case 1: {
+ np++;
+ aflag = pHMgr->decode_flag(piece);
+ if (((at == 'S') && (dupflags[aflag] & dupSFX)) ||
+ ((at == 'P') && (dupflags[aflag] & dupPFX))) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple definitions of an affix flag\n",
+ af->getlinenum());
+ // return 1; XXX permissive mode for bad dictionaries
+ }
+ dupflags[aflag] += (char) ((at == 'S') ? dupSFX : dupPFX);
+ break;
+ }
+ // piece 3 - is cross product indicator
+ case 2: { np++; if (*piece == 'Y') ff = aeXPRODUCT; break; }
+
+ // piece 4 - is number of affentries
+ case 3: {
+ np++;
+ numents = atoi(piece);
+ if (numents == 0) {
+ char * err = pHMgr->encode_flag(aflag);
+ if (err) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n",
+ af->getlinenum());
+ free(err);
+ }
+ return 1;
+ }
+ affentries.resize(numents);
+ affentries[0].opts = ff;
+ if (utf8) affentries[0].opts += aeUTF8;
+ if (pHMgr->is_aliasf()) affentries[0].opts += aeALIASF;
+ if (pHMgr->is_aliasm()) affentries[0].opts += aeALIASM;
+ affentries[0].aflag = aflag;
+ }
+
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ // check to make sure we parsed enough pieces
+ if (np != 4) {
+ char * err = pHMgr->encode_flag(aflag);
+ if (err) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ free(err);
+ }
+ return 1;
+ }
+
+ // now parse numents affentries for this affix
+ std::vector<affentry>::iterator start = affentries.begin();
+ std::vector<affentry>::iterator end = affentries.end();
+ for (std::vector<affentry>::iterator entry = start; entry != end; ++entry) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ np = 0;
+
+ // split line into pieces
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ // piece 1 - is type
+ case 0: {
+ np++;
+ if (entry != start) entry->opts = start->opts &
+ (char) (aeXPRODUCT + aeUTF8 + aeALIASF + aeALIASM);
+ break;
+ }
+
+ // piece 2 - is affix char
+ case 1: {
+ np++;
+ if (pHMgr->decode_flag(piece) != aflag) {
+ char * err = pHMgr->encode_flag(aflag);
+ if (err) {
+ HUNSPELL_WARNING(stderr, "error: line %d: affix %s is corrupt\n",
+ af->getlinenum(), err);
+ free(err);
+ }
+ return 1;
+ }
+
+ if (entry != start) entry->aflag = start->aflag;
+ break;
+ }
+
+ // piece 3 - is string to strip or 0 for null
+ case 2: {
+ np++;
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(piece); else reverseword(piece);
+ }
+ entry->strip = mystrdup(piece);
+ entry->stripl = (unsigned char) strlen(entry->strip);
+ if (strcmp(entry->strip,"0") == 0) {
+ free(entry->strip);
+ entry->strip=mystrdup("");
+ entry->stripl = 0;
+ }
+ break;
+ }
+
+ // piece 4 - is affix string or 0 for null
+ case 3: {
+ char * dash;
+ entry->morphcode = NULL;
+ entry->contclass = NULL;
+ entry->contclasslen = 0;
+ np++;
+ dash = strchr(piece, '/');
+ if (dash) {
+ *dash = '\0';
+
+ if (ignorechars) {
+ if (utf8) {
+ remove_ignored_chars_utf(piece, ignorechars_utf16, ignorechars_utf16_len);
+ } else {
+ remove_ignored_chars(piece,ignorechars);
+ }
+ }
+
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(piece); else reverseword(piece);
+ }
+ entry->appnd = mystrdup(piece);
+
+ if (pHMgr->is_aliasf()) {
+ int index = atoi(dash + 1);
+ entry->contclasslen = (unsigned short) pHMgr->get_aliasf(index, &(entry->contclass), af);
+ if (!entry->contclasslen) HUNSPELL_WARNING(stderr, "error: bad affix flag alias: \"%s\"\n", dash+1);
+ } else {
+ entry->contclasslen = (unsigned short) pHMgr->decode_flags(&(entry->contclass), dash + 1, af);
+ flag_qsort(entry->contclass, 0, entry->contclasslen);
+ }
+ *dash = '/';
+
+ havecontclass = 1;
+ for (unsigned short _i = 0; _i < entry->contclasslen; _i++) {
+ contclasses[(entry->contclass)[_i]] = 1;
+ }
+ } else {
+ if (ignorechars) {
+ if (utf8) {
+ remove_ignored_chars_utf(piece, ignorechars_utf16, ignorechars_utf16_len);
+ } else {
+ remove_ignored_chars(piece,ignorechars);
+ }
+ }
+
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(piece); else reverseword(piece);
+ }
+ entry->appnd = mystrdup(piece);
+ }
+
+ entry->appndl = (unsigned char) strlen(entry->appnd);
+ if (strcmp(entry->appnd,"0") == 0) {
+ free(entry->appnd);
+ entry->appnd=mystrdup("");
+ entry->appndl = 0;
+ }
+ break;
+ }
+
+ // piece 5 - is the conditions descriptions
+ case 4: {
+ np++;
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(piece); else reverseword(piece);
+ reverse_condition(piece);
+ }
+ if (entry->stripl && (strcmp(piece, ".") != 0) &&
+ redundant_condition(at, entry->strip, entry->stripl, piece, af->getlinenum()))
+ strcpy(piece, ".");
+ if (at == 'S') {
+ reverseword(piece);
+ reverse_condition(piece);
+ }
+ if (encodeit(*entry, piece)) return 1;
+ break;
+ }
+
+ case 5: {
+ np++;
+ if (pHMgr->is_aliasm()) {
+ int index = atoi(piece);
+ entry->morphcode = pHMgr->get_aliasm(index);
+ } else {
+ if (complexprefixes) { // XXX - fix me for morph. gen.
+ if (utf8) reverseword_utf(piece); else reverseword(piece);
+ }
+ // add the remaining of the line
+ if (*tp) {
+ *(tp - 1) = ' ';
+ tp = tp + strlen(tp);
+ }
+ entry->morphcode = mystrdup(piece);
+ if (!entry->morphcode) return 1;
+ }
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ // check to make sure we parsed enough pieces
+ if (np < 4) {
+ char * err = pHMgr->encode_flag(aflag);
+ if (err) {
+ HUNSPELL_WARNING(stderr, "error: line %d: affix %s is corrupt\n",
+ af->getlinenum(), err);
+ free(err);
+ }
+ return 1;
+ }
+
+#ifdef DEBUG
+ // detect unnecessary fields, excepting comments
+ if (basefieldnum) {
+ int fieldnum = !(entry->morphcode) ? 5 : ((*(entry->morphcode)=='#') ? 5 : 6);
+ if (fieldnum != basefieldnum)
+ HUNSPELL_WARNING(stderr, "warning: line %d: bad field number\n", af->getlinenum());
+ } else {
+ basefieldnum = !(entry->morphcode) ? 5 : ((*(entry->morphcode)=='#') ? 5 : 6);
+ }
+#endif
+ }
+
+ // now create SfxEntry or PfxEntry objects and use links to
+ // build an ordered (sorted by affix string) list
+ for (std::vector<affentry>::iterator entry = start; entry != end; ++entry) {
+ if (at == 'P') {
+ PfxEntry * pfxptr = new PfxEntry(this,&(*entry));
+ build_pfxtree(pfxptr);
+ } else {
+ SfxEntry * sfxptr = new SfxEntry(this,&(*entry));
+ build_sfxtree(sfxptr);
+ }
+ }
+ return 0;
+}
+
+int AffixMgr::redundant_condition(char ft, char * strip, int stripl, const char * cond, int linenum) {
+ int condl = strlen(cond);
+ int i;
+ int j;
+ int neg;
+ int in;
+ if (ft == 'P') { // prefix
+ if (strncmp(strip, cond, condl) == 0) return 1;
+ if (utf8) {
+ } else {
+ for (i = 0, j = 0; (i < stripl) && (j < condl); i++, j++) {
+ if (cond[j] != '[') {
+ if (cond[j] != strip[i]) {
+ HUNSPELL_WARNING(stderr, "warning: line %d: incompatible stripping characters and condition\n", linenum);
+ return 0;
+ }
+ } else {
+ neg = (cond[j+1] == '^') ? 1 : 0;
+ in = 0;
+ do {
+ j++;
+ if (strip[i] == cond[j]) in = 1;
+ } while ((j < (condl - 1)) && (cond[j] != ']'));
+ if (j == (condl - 1) && (cond[j] != ']')) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing ] in condition:\n%s\n", linenum, cond);
+ return 0;
+ }
+ if ((!neg && !in) || (neg && in)) {
+ HUNSPELL_WARNING(stderr, "warning: line %d: incompatible stripping characters and condition\n", linenum);
+ return 0;
+ }
+ }
+ }
+ if (j >= condl) return 1;
+ }
+ } else { // suffix
+ if ((stripl >= condl) && strcmp(strip + stripl - condl, cond) == 0) return 1;
+ if (utf8) {
+ } else {
+ for (i = stripl - 1, j = condl - 1; (i >= 0) && (j >= 0); i--, j--) {
+ if (cond[j] != ']') {
+ if (cond[j] != strip[i]) {
+ HUNSPELL_WARNING(stderr, "warning: line %d: incompatible stripping characters and condition\n", linenum);
+ return 0;
+ }
+ } else {
+ in = 0;
+ do {
+ j--;
+ if (strip[i] == cond[j]) in = 1;
+ } while ((j > 0) && (cond[j] != '['));
+ if ((j == 0) && (cond[j] != '[')) {
+ HUNSPELL_WARNING(stderr, "error: line: %d: missing ] in condition:\n%s\n", linenum, cond);
+ return 0;
+ }
+ neg = (cond[j+1] == '^') ? 1 : 0;
+ if ((!neg && !in) || (neg && in)) {
+ HUNSPELL_WARNING(stderr, "warning: line %d: incompatible stripping characters and condition\n", linenum);
+ return 0;
+ }
+ }
+ }
+ if (j < 0) return 1;
+ }
+ }
+ return 0;
+}
diff --git a/src/cpp/core/spelling/hunspell/affixmgr.hxx b/src/cpp/core/spelling/hunspell/affixmgr.hxx
new file mode 100644
index 0000000..d9c625a
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/affixmgr.hxx
@@ -0,0 +1,250 @@
+#ifndef _AFFIXMGR_HXX_
+#define _AFFIXMGR_HXX_
+
+#include "hunvisapi.h"
+
+#include <stdio.h>
+
+#include "atypes.hxx"
+#include "baseaffix.hxx"
+#include "hashmgr.hxx"
+#include "phonet.hxx"
+#include "replist.hxx"
+
+// check flag duplication
+#define dupSFX (1 << 0)
+#define dupPFX (1 << 1)
+
+class PfxEntry;
+class SfxEntry;
+
+class LIBHUNSPELL_DLL_EXPORTED AffixMgr
+{
+
+ PfxEntry * pStart[SETSIZE];
+ SfxEntry * sStart[SETSIZE];
+ PfxEntry * pFlag[SETSIZE];
+ SfxEntry * sFlag[SETSIZE];
+ HashMgr * pHMgr;
+ HashMgr ** alldic;
+ int * maxdic;
+ char * keystring;
+ char * trystring;
+ char * encoding;
+ struct cs_info * csconv;
+ int utf8;
+ int complexprefixes;
+ FLAG compoundflag;
+ FLAG compoundbegin;
+ FLAG compoundmiddle;
+ FLAG compoundend;
+ FLAG compoundroot;
+ FLAG compoundforbidflag;
+ FLAG compoundpermitflag;
+ int checkcompounddup;
+ int checkcompoundrep;
+ int checkcompoundcase;
+ int checkcompoundtriple;
+ int simplifiedtriple;
+ FLAG forbiddenword;
+ FLAG nosuggest;
+ FLAG nongramsuggest;
+ FLAG needaffix;
+ int cpdmin;
+ int numrep;
+ replentry * reptable;
+ RepList * iconvtable;
+ RepList * oconvtable;
+ int nummap;
+ mapentry * maptable;
+ int numbreak;
+ char ** breaktable;
+ int numcheckcpd;
+ patentry * checkcpdtable;
+ int simplifiedcpd;
+ int numdefcpd;
+ flagentry * defcpdtable;
+ phonetable * phone;
+ int maxngramsugs;
+ int maxcpdsugs;
+ int maxdiff;
+ int onlymaxdiff;
+ int nosplitsugs;
+ int sugswithdots;
+ int cpdwordmax;
+ int cpdmaxsyllable;
+ char * cpdvowels;
+ w_char * cpdvowels_utf16;
+ int cpdvowels_utf16_len;
+ char * cpdsyllablenum;
+ const char * pfxappnd; // BUG: not stateless
+ const char * sfxappnd; // BUG: not stateless
+ FLAG sfxflag; // BUG: not stateless
+ char * derived; // BUG: not stateless
+ SfxEntry * sfx; // BUG: not stateless
+ PfxEntry * pfx; // BUG: not stateless
+ int checknum;
+ char * wordchars;
+ unsigned short * wordchars_utf16;
+ int wordchars_utf16_len;
+ char * ignorechars;
+ unsigned short * ignorechars_utf16;
+ int ignorechars_utf16_len;
+ char * version;
+ char * lang;
+ int langnum;
+ FLAG lemma_present;
+ FLAG circumfix;
+ FLAG onlyincompound;
+ FLAG keepcase;
+ FLAG forceucase;
+ FLAG warn;
+ int forbidwarn;
+ FLAG substandard;
+ int checksharps;
+ int fullstrip;
+
+ int havecontclass; // boolean variable
+ char contclasses[CONTSIZE]; // flags of possible continuing classes (twofold affix)
+
+public:
+
+ AffixMgr(const char * affpath, HashMgr** ptr, int * md,
+ const char * key = NULL);
+ ~AffixMgr();
+ struct hentry * affix_check(const char * word, int len,
+ const unsigned short needflag = (unsigned short) 0,
+ char in_compound = IN_CPD_NOT);
+ struct hentry * prefix_check(const char * word, int len,
+ char in_compound, const FLAG needflag = FLAG_NULL);
+ inline int isSubset(const char * s1, const char * s2);
+ struct hentry * prefix_check_twosfx(const char * word, int len,
+ char in_compound, const FLAG needflag = FLAG_NULL);
+ inline int isRevSubset(const char * s1, const char * end_of_s2, int len);
+ struct hentry * suffix_check(const char * word, int len, int sfxopts,
+ PfxEntry* ppfx, char ** wlst, int maxSug, int * ns,
+ const FLAG cclass = FLAG_NULL, const FLAG needflag = FLAG_NULL,
+ char in_compound = IN_CPD_NOT);
+ struct hentry * suffix_check_twosfx(const char * word, int len,
+ int sfxopts, PfxEntry* ppfx, const FLAG needflag = FLAG_NULL);
+
+ char * affix_check_morph(const char * word, int len,
+ const FLAG needflag = FLAG_NULL, char in_compound = IN_CPD_NOT);
+ char * prefix_check_morph(const char * word, int len,
+ char in_compound, const FLAG needflag = FLAG_NULL);
+ char * suffix_check_morph (const char * word, int len, int sfxopts,
+ PfxEntry * ppfx, const FLAG cclass = FLAG_NULL,
+ const FLAG needflag = FLAG_NULL, char in_compound = IN_CPD_NOT);
+
+ char * prefix_check_twosfx_morph(const char * word, int len,
+ char in_compound, const FLAG needflag = FLAG_NULL);
+ char * suffix_check_twosfx_morph(const char * word, int len,
+ int sfxopts, PfxEntry * ppfx, const FLAG needflag = FLAG_NULL);
+
+ char * morphgen(char * ts, int wl, const unsigned short * ap,
+ unsigned short al, char * morph, char * targetmorph, int level);
+
+ int expand_rootword(struct guessword * wlst, int maxn, const char * ts,
+ int wl, const unsigned short * ap, unsigned short al, char * bad,
+ int, char *);
+
+ short get_syllable (const char * word, int wlen);
+ int cpdrep_check(const char * word, int len);
+ int cpdpat_check(const char * word, int len, hentry * r1, hentry * r2,
+ const char affixed);
+ int defcpd_check(hentry *** words, short wnum, hentry * rv,
+ hentry ** rwords, char all);
+ int cpdcase_check(const char * word, int len);
+ inline int candidate_check(const char * word, int len);
+ void setcminmax(int * cmin, int * cmax, const char * word, int len);
+ struct hentry * compound_check(const char * word, int len, short wordnum,
+ short numsyllable, short maxwordnum, short wnum, hentry ** words,
+ char hu_mov_rule, char is_sug, int * info);
+
+ int compound_check_morph(const char * word, int len, short wordnum,
+ short numsyllable, short maxwordnum, short wnum, hentry ** words,
+ char hu_mov_rule, char ** result, char * partresult);
+
+ struct hentry * lookup(const char * word);
+ int get_numrep() const;
+ struct replentry * get_reptable() const;
+ RepList * get_iconvtable() const;
+ RepList * get_oconvtable() const;
+ struct phonetable * get_phonetable() const;
+ int get_nummap() const;
+ struct mapentry * get_maptable() const;
+ int get_numbreak() const;
+ char ** get_breaktable() const;
+ char * get_encoding();
+ int get_langnum() const;
+ char * get_key_string();
+ char * get_try_string() const;
+ const char * get_wordchars() const;
+ unsigned short * get_wordchars_utf16(int * len) const;
+ char * get_ignore() const;
+ unsigned short * get_ignore_utf16(int * len) const;
+ int get_compound() const;
+ FLAG get_compoundflag() const;
+ FLAG get_compoundbegin() const;
+ FLAG get_forbiddenword() const;
+ FLAG get_nosuggest() const;
+ FLAG get_nongramsuggest() const;
+ FLAG get_needaffix() const;
+ FLAG get_onlyincompound() const;
+ FLAG get_compoundroot() const;
+ FLAG get_lemma_present() const;
+ int get_checknum() const;
+ const char * get_prefix() const;
+ const char * get_suffix() const;
+ const char * get_derived() const;
+ const char * get_version() const;
+ int have_contclass() const;
+ int get_utf8() const;
+ int get_complexprefixes() const;
+ char * get_suffixed(char ) const;
+ int get_maxngramsugs() const;
+ int get_maxcpdsugs() const;
+ int get_maxdiff() const;
+ int get_onlymaxdiff() const;
+ int get_nosplitsugs() const;
+ int get_sugswithdots(void) const;
+ FLAG get_keepcase(void) const;
+ FLAG get_forceucase(void) const;
+ FLAG get_warn(void) const;
+ int get_forbidwarn(void) const;
+ int get_checksharps(void) const;
+ char * encode_flag(unsigned short aflag) const;
+ int get_fullstrip() const;
+
+private:
+ int parse_file(const char * affpath, const char * key);
+ int parse_flag(char * line, unsigned short * out, FileMgr * af);
+ int parse_num(char * line, int * out, FileMgr * af);
+ int parse_cpdsyllable(char * line, FileMgr * af);
+ int parse_reptable(char * line, FileMgr * af);
+ int parse_convtable(char * line, FileMgr * af, RepList ** rl, const char * keyword);
+ int parse_phonetable(char * line, FileMgr * af);
+ int parse_maptable(char * line, FileMgr * af);
+ int parse_breaktable(char * line, FileMgr * af);
+ int parse_checkcpdtable(char * line, FileMgr * af);
+ int parse_defcpdtable(char * line, FileMgr * af);
+ int parse_affix(char * line, const char at, FileMgr * af, char * dupflags);
+
+ void reverse_condition(char *);
+ void debugflag(char * result, unsigned short flag);
+ int condlen(char *);
+ int encodeit(affentry &entry, char * cs);
+ int build_pfxtree(PfxEntry* pfxptr);
+ int build_sfxtree(SfxEntry* sfxptr);
+ int process_pfx_order();
+ int process_sfx_order();
+ PfxEntry * process_pfx_in_order(PfxEntry * ptr, PfxEntry * nptr);
+ SfxEntry * process_sfx_in_order(SfxEntry * ptr, SfxEntry * nptr);
+ int process_pfx_tree_to_list();
+ int process_sfx_tree_to_list();
+ int redundant_condition(char, char * strip, int stripl,
+ const char * cond, int);
+};
+
+#endif
+
diff --git a/src/cpp/core/spelling/hunspell/atypes.hxx b/src/cpp/core/spelling/hunspell/atypes.hxx
new file mode 100644
index 0000000..df27c4d
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/atypes.hxx
@@ -0,0 +1,107 @@
+#ifndef _ATYPES_HXX_
+#define _ATYPES_HXX_
+
+#ifndef HUNSPELL_WARNING
+#include <stdio.h>
+#ifdef HUNSPELL_WARNING_ON
+#define HUNSPELL_WARNING fprintf
+#else
+// empty inline function to switch off warnings (instead of the C99 standard variadic macros)
+static inline void HUNSPELL_WARNING(FILE *, const char *, ...) {}
+#endif
+#endif
+
+// HUNSTEM def.
+#define HUNSTEM
+
+#include "hashmgr.hxx"
+#include "w_char.hxx"
+
+#define SETSIZE 256
+#define CONTSIZE 65536
+#define MAXWORDLEN 100
+#define MAXWORDUTF8LEN 256
+
+// affentry options
+#define aeXPRODUCT (1 << 0)
+#define aeUTF8 (1 << 1)
+#define aeALIASF (1 << 2)
+#define aeALIASM (1 << 3)
+#define aeLONGCOND (1 << 4)
+
+// compound options
+#define IN_CPD_NOT 0
+#define IN_CPD_BEGIN 1
+#define IN_CPD_END 2
+#define IN_CPD_OTHER 3
+
+// info options
+#define SPELL_COMPOUND (1 << 0)
+#define SPELL_FORBIDDEN (1 << 1)
+#define SPELL_ALLCAP (1 << 2)
+#define SPELL_NOCAP (1 << 3)
+#define SPELL_INITCAP (1 << 4)
+#define SPELL_ORIGCAP (1 << 5)
+#define SPELL_WARN (1 << 6)
+
+#define MAXLNLEN 8192
+
+#define MINCPDLEN 3
+#define MAXCOMPOUND 10
+#define MAXCONDLEN 20
+#define MAXCONDLEN_1 (MAXCONDLEN - sizeof(char *))
+
+#define MAXACC 1000
+
+#define FLAG unsigned short
+#define FLAG_NULL 0x00
+#define FREE_FLAG(a) a = 0
+
+#define TESTAFF( a, b , c ) flag_bsearch((unsigned short *) a, (unsigned short) b, c)
+
+struct affentry
+{
+ char * strip;
+ char * appnd;
+ unsigned char stripl;
+ unsigned char appndl;
+ char numconds;
+ char opts;
+ unsigned short aflag;
+ unsigned short * contclass;
+ short contclasslen;
+ union {
+ char conds[MAXCONDLEN];
+ struct {
+ char conds1[MAXCONDLEN_1];
+ char * conds2;
+ } l;
+ } c;
+ char * morphcode;
+};
+
+struct guessword {
+ char * word;
+ bool allow;
+ char * orig;
+};
+
+struct mapentry {
+ char ** set;
+ int len;
+};
+
+struct flagentry {
+ FLAG * def;
+ int len;
+};
+
+struct patentry {
+ char * pattern;
+ char * pattern2;
+ char * pattern3;
+ FLAG cond;
+ FLAG cond2;
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/baseaffix.hxx b/src/cpp/core/spelling/hunspell/baseaffix.hxx
new file mode 100644
index 0000000..ed64f3d
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/baseaffix.hxx
@@ -0,0 +1,28 @@
+#ifndef _BASEAFF_HXX_
+#define _BASEAFF_HXX_
+
+#include "hunvisapi.h"
+
+class LIBHUNSPELL_DLL_EXPORTED AffEntry
+{
+protected:
+ char * appnd;
+ char * strip;
+ unsigned char appndl;
+ unsigned char stripl;
+ char numconds;
+ char opts;
+ unsigned short aflag;
+ union {
+ char conds[MAXCONDLEN];
+ struct {
+ char conds1[MAXCONDLEN_1];
+ char * conds2;
+ } l;
+ } c;
+ char * morphcode;
+ unsigned short * contclass;
+ short contclasslen;
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/config.h.in b/src/cpp/core/spelling/hunspell/config.h.in
new file mode 100644
index 0000000..724c7fa
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/config.h.in
@@ -0,0 +1,15 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-11 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
diff --git a/src/cpp/core/spelling/hunspell/csutil.cxx b/src/cpp/core/spelling/hunspell/csutil.cxx
new file mode 100644
index 0000000..dd89c19
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/csutil.cxx
@@ -0,0 +1,5834 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "csutil.hxx"
+#include "atypes.hxx"
+#include "langnum.hxx"
+
+// Unicode character encoding information
+struct unicode_info {
+ unsigned short c;
+ unsigned short cupper;
+ unsigned short clower;
+};
+
+#ifdef OPENOFFICEORG
+# include <unicode/uchar.h>
+#else
+# ifndef MOZILLA_CLIENT
+# include "utf_info.cxx"
+# define UTF_LST_LEN (sizeof(utf_lst) / (sizeof(unicode_info)))
+# endif
+#endif
+
+#ifdef MOZILLA_CLIENT
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsUnicharUtils.h"
+#include "nsICharsetConverterManager.h"
+
+static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
+#endif
+
+struct unicode_info2 {
+ char cletter;
+ unsigned short cupper;
+ unsigned short clower;
+};
+
+static struct unicode_info2 * utf_tbl = NULL;
+static int utf_tbl_count = 0; // utf_tbl can be used by multiple Hunspell instances
+
+/* only UTF-16 (BMP) implementation */
+char * u16_u8(char * dest, int size, const w_char * src, int srclen) {
+ signed char * u8 = (signed char *)dest;
+ signed char * u8_max = (signed char *)(u8 + size);
+ const w_char * u2 = src;
+ const w_char * u2_max = src + srclen;
+ while ((u2 < u2_max) && (u8 < u8_max)) {
+ if (u2->h) { // > 0xFF
+ // XXX 4-byte haven't implemented yet.
+ if (u2->h >= 0x08) { // >= 0x800 (3-byte UTF-8 character)
+ *u8 = 0xe0 + (u2->h >> 4);
+ u8++;
+ if (u8 < u8_max) {
+ *u8 = 0x80 + ((u2->h & 0xf) << 2) + (u2->l >> 6);
+ u8++;
+ if (u8 < u8_max) {
+ *u8 = 0x80 + (u2->l & 0x3f);
+ u8++;
+ }
+ }
+ } else { // < 0x800 (2-byte UTF-8 character)
+ *u8 = 0xc0 + (u2->h << 2) + (u2->l >> 6);
+ u8++;
+ if (u8 < u8_max) {
+ *u8 = 0x80 + (u2->l & 0x3f);
+ u8++;
+ }
+ }
+ } else { // <= 0xFF
+ if (u2->l & 0x80) { // >0x80 (2-byte UTF-8 character)
+ *u8 = 0xc0 + (u2->l >> 6);
+ u8++;
+ if (u8 < u8_max) {
+ *u8 = 0x80 + (u2->l & 0x3f);
+ u8++;
+ }
+ } else { // < 0x80 (1-byte UTF-8 character)
+ *u8 = u2->l;
+ u8++;
+ }
+ }
+ u2++;
+ }
+ *u8 = '\0';
+ return dest;
+}
+
+
+/* only UTF-16 (BMP) implementation */
+int u8_u16(w_char * dest, int size, const char * src) {
+ const signed char * u8 = (const signed char *)src;
+ w_char * u2 = dest;
+ w_char * u2_max = u2 + size;
+
+ while ((u2 < u2_max) && *u8) {
+ switch ((*u8) & 0xf0) {
+ case 0x00:
+ case 0x10:
+ case 0x20:
+ case 0x30:
+ case 0x40:
+ case 0x50:
+ case 0x60:
+ case 0x70: {
+ u2->h = 0;
+ u2->l = *u8;
+ break;
+ }
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ case 0xb0: {
+ HUNSPELL_WARNING(stderr, "UTF-8 encoding error. Unexpected continuation bytes in %ld. character position\n%s\n", static_cast<long>(u8 - (signed char *)src), src);
+ u2->h = 0xff;
+ u2->l = 0xfd;
+ break;
+ }
+ case 0xc0:
+ case 0xd0: { // 2-byte UTF-8 codes
+ if ((*(u8+1) & 0xc0) == 0x80) {
+ u2->h = (*u8 & 0x1f) >> 2;
+ u2->l = (*u8 << 6) + (*(u8+1) & 0x3f);
+ u8++;
+ } else {
+ HUNSPELL_WARNING(stderr, "UTF-8 encoding error. Missing continuation byte in %ld. character position:\n%s\n", static_cast<long>(u8 - (signed char *)src), src);
+ u2->h = 0xff;
+ u2->l = 0xfd;
+ }
+ break;
+ }
+ case 0xe0: { // 3-byte UTF-8 codes
+ if ((*(u8+1) & 0xc0) == 0x80) {
+ u2->h = ((*u8 & 0x0f) << 4) + ((*(u8+1) & 0x3f) >> 2);
+ u8++;
+ if ((*(u8+1) & 0xc0) == 0x80) {
+ u2->l = (*u8 << 6) + (*(u8+1) & 0x3f);
+ u8++;
+ } else {
+ HUNSPELL_WARNING(stderr, "UTF-8 encoding error. Missing continuation byte in %ld. character position:\n%s\n", static_cast<long>(u8 - (signed char *)src), src);
+ u2->h = 0xff;
+ u2->l = 0xfd;
+ }
+ } else {
+ HUNSPELL_WARNING(stderr, "UTF-8 encoding error. Missing continuation byte in %ld. character position:\n%s\n", static_cast<long>(u8 - (signed char *)src), src);
+ u2->h = 0xff;
+ u2->l = 0xfd;
+ }
+ break;
+ }
+ case 0xf0: { // 4 or more byte UTF-8 codes
+ HUNSPELL_WARNING(stderr, "This UTF-8 encoding can't convert to UTF-16:\n%s\n", src);
+ u2->h = 0xff;
+ u2->l = 0xfd;
+ return -1;
+ }
+ }
+ u8++;
+ u2++;
+ }
+ return (int)(u2 - dest);
+}
+
+void flag_qsort(unsigned short flags[], int begin, int end) {
+ unsigned short reg;
+ if (end > begin) {
+ unsigned short pivot = flags[begin];
+ int l = begin + 1;
+ int r = end;
+ while(l < r) {
+ if (flags[l] <= pivot) {
+ l++;
+ } else {
+ r--;
+ reg = flags[l];
+ flags[l] = flags[r];
+ flags[r] = reg;
+ }
+ }
+ l--;
+ reg = flags[begin];
+ flags[begin] = flags[l];
+ flags[l] = reg;
+
+ flag_qsort(flags, begin, l);
+ flag_qsort(flags, r, end);
+ }
+ }
+
+int flag_bsearch(unsigned short flags[], unsigned short flag, int length) {
+ int mid;
+ int left = 0;
+ int right = length - 1;
+ while (left <= right) {
+ mid = (left + right) / 2;
+ if (flags[mid] == flag) return 1;
+ if (flag < flags[mid]) right = mid - 1;
+ else left = mid + 1;
+ }
+ return 0;
+}
+
+ // strip strings into token based on single char delimiter
+ // acts like strsep() but only uses a delim char and not
+ // a delim string
+ // default delimiter: white space characters
+
+ char * mystrsep(char ** stringp, const char delim)
+ {
+ char * mp = *stringp;
+ if (*mp != '\0') {
+ char * dp;
+ if (delim) {
+ dp = strchr(mp, delim);
+ } else {
+ // don't use isspace() here, the string can be in some random charset
+ // that's way different than the locale's
+ for (dp = mp; (*dp && *dp != ' ' && *dp != '\t'); dp++);
+ if (!*dp) dp = NULL;
+ }
+ if (dp) {
+ *stringp = dp+1;
+ *dp = '\0';
+ } else {
+ *stringp = mp + strlen(mp);
+ }
+ return mp;
+ }
+ return NULL;
+ }
+
+ // replaces strdup with ansi version
+ char * mystrdup(const char * s)
+ {
+ char * d = NULL;
+ if (s) {
+ size_t sl = strlen(s)+1;
+ d = (char *) malloc(sl);
+ if (d) {
+ memcpy(d,s,sl);
+ } else {
+ HUNSPELL_WARNING(stderr, "Can't allocate memory.\n");
+ }
+ }
+ return d;
+ }
+
+ // strcat for limited length destination string
+ char * mystrcat(char * dest, const char * st, int max) {
+ int len;
+ int len2;
+ if (dest == NULL || st == NULL) return dest;
+ len = strlen(dest);
+ len2 = strlen(st);
+ if (len + len2 + 1 > max) return dest;
+ strcpy(dest + len, st);
+ return dest;
+ }
+
+ // remove cross-platform text line end characters
+ void mychomp(char * s)
+ {
+ size_t k = strlen(s);
+ if ((k > 0) && ((*(s+k-1)=='\r') || (*(s+k-1)=='\n'))) *(s+k-1) = '\0';
+ if ((k > 1) && (*(s+k-2) == '\r')) *(s+k-2) = '\0';
+ }
+
+
+ // does an ansi strdup of the reverse of a string
+ char * myrevstrdup(const char * s)
+ {
+ char * d = NULL;
+ if (s) {
+ size_t sl = strlen(s);
+ d = (char *) malloc(sl+1);
+ if (d) {
+ const char * p = s + sl - 1;
+ char * q = d;
+ while (p >= s) *q++ = *p--;
+ *q = '\0';
+ } else {
+ HUNSPELL_WARNING(stderr, "Can't allocate memory.\n");
+ }
+ }
+ return d;
+ }
+
+// break text to lines
+// return number of lines
+int line_tok(const char * text, char *** lines, char breakchar) {
+ int linenum = 0;
+ if (!text) {
+ return linenum;
+ }
+ char * dup = mystrdup(text);
+ char * p = strchr(dup, breakchar);
+ while (p) {
+ linenum++;
+ *p = '\0';
+ p++;
+ p = strchr(p, breakchar);
+ }
+ linenum++;
+ *lines = (char **) malloc(linenum * sizeof(char *));
+ if (!(*lines)) {
+ free(dup);
+ return 0;
+ }
+
+ p = dup;
+ int l = 0;
+ for (int i = 0; i < linenum; i++) {
+ if (*p != '\0') {
+ (*lines)[l] = mystrdup(p);
+ if (!(*lines)[l]) {
+ for (i = 0; i < l; i++) free((*lines)[i]);
+ free(dup);
+ return 0;
+ }
+ l++;
+ }
+ p += strlen(p) + 1;
+ }
+ free(dup);
+ if (!l) free(*lines);
+ return l;
+}
+
+// uniq line in place
+char * line_uniq(char * text, char breakchar) {
+ char ** lines;
+ int linenum = line_tok(text, &lines, breakchar);
+ int i;
+ strcpy(text, lines[0]);
+ for ( i = 1; i < linenum; i++ ) {
+ int dup = 0;
+ for (int j = 0; j < i; j++) {
+ if (strcmp(lines[i], lines[j]) == 0) dup = 1;
+ }
+ if (!dup) {
+ if ((i > 1) || (*(lines[0]) != '\0')) {
+ sprintf(text + strlen(text), "%c", breakchar);
+ }
+ strcat(text, lines[i]);
+ }
+ }
+ for ( i = 0; i < linenum; i++ ) {
+ if (lines[i]) free(lines[i]);
+ }
+ if (lines) free(lines);
+ return text;
+}
+
+// uniq and boundary for compound analysis: "1\n\2\n\1" -> " ( \1 | \2 ) "
+char * line_uniq_app(char ** text, char breakchar) {
+ if (!strchr(*text, breakchar)) {
+ return *text;
+ }
+
+ char ** lines;
+ int i;
+ int linenum = line_tok(*text, &lines, breakchar);
+ int dup = 0;
+ for (i = 0; i < linenum; i++) {
+ for (int j = 0; j < (i - 1); j++) {
+ if (strcmp(lines[i], lines[j]) == 0) {
+ *(lines[i]) = '\0';
+ dup++;
+ break;
+ }
+ }
+ }
+ if ((linenum - dup) == 1) {
+ strcpy(*text, lines[0]);
+ freelist(&lines, linenum);
+ return *text;
+ }
+ char * newtext = (char *) malloc(strlen(*text) + 2 * linenum + 3 + 1);
+ if (newtext) {
+ free(*text);
+ *text = newtext;
+ } else {
+ freelist(&lines, linenum);
+ return *text;
+ }
+ strcpy(*text," ( ");
+ for (i = 0; i < linenum; i++) if (*(lines[i])) {
+ sprintf(*text + strlen(*text), "%s%s", lines[i], " | ");
+ }
+ (*text)[strlen(*text) - 2] = ')'; // " ) "
+ freelist(&lines, linenum);
+ return *text;
+}
+
+ // append s to ends of every lines in text
+ void strlinecat(char * dest, const char * s)
+ {
+ char * dup = mystrdup(dest);
+ char * source = dup;
+ int len = strlen(s);
+ if (dup) {
+ while (*source) {
+ if (*source == '\n') {
+ strncpy(dest, s, len);
+ dest += len;
+ }
+ *dest = *source;
+ source++; dest++;
+ }
+ strcpy(dest, s);
+ free(dup);
+ }
+ }
+
+// change \n to char c
+char * tr(char * text, char oldc, char newc) {
+ char * p;
+ for (p = text; *p; p++) if (*p == oldc) *p = newc;
+ return text;
+}
+
+// morphcmp(): compare MORPH_DERI_SFX, MORPH_INFL_SFX and MORPH_TERM_SFX fields
+// in the first line of the inputs
+// return 0, if inputs equal
+// return 1, if inputs may equal with a secondary suffix
+// otherwise return -1
+int morphcmp(const char * s, const char * t)
+{
+ int se = 0;
+ int te = 0;
+ const char * sl;
+ const char * tl;
+ const char * olds;
+ const char * oldt;
+ if (!s || !t) return 1;
+ olds = s;
+ sl = strchr(s, '\n');
+ s = strstr(s, MORPH_DERI_SFX);
+ if (!s || (sl && sl < s)) s = strstr(olds, MORPH_INFL_SFX);
+ if (!s || (sl && sl < s)) {
+ s= strstr(olds, MORPH_TERM_SFX);
+ olds = NULL;
+ }
+ oldt = t;
+ tl = strchr(t, '\n');
+ t = strstr(t, MORPH_DERI_SFX);
+ if (!t || (tl && tl < t)) t = strstr(oldt, MORPH_INFL_SFX);
+ if (!t || (tl && tl < t)) {
+ t = strstr(oldt, MORPH_TERM_SFX);
+ oldt = NULL;
+ }
+ while (s && t && (!sl || sl > s) && (!tl || tl > t)) {
+ s += MORPH_TAG_LEN;
+ t += MORPH_TAG_LEN;
+ se = 0;
+ te = 0;
+ while ((*s == *t) && !se && !te) {
+ s++;
+ t++;
+ switch(*s) {
+ case ' ':
+ case '\n':
+ case '\t':
+ case '\0': se = 1;
+ }
+ switch(*t) {
+ case ' ':
+ case '\n':
+ case '\t':
+ case '\0': te = 1;
+ }
+ }
+ if (!se || !te) {
+ // not terminal suffix difference
+ if (olds) return -1;
+ return 1;
+ }
+ olds = s;
+ s = strstr(s, MORPH_DERI_SFX);
+ if (!s || (sl && sl < s)) s = strstr(olds, MORPH_INFL_SFX);
+ if (!s || (sl && sl < s)) {
+ s = strstr(olds, MORPH_TERM_SFX);
+ olds = NULL;
+ }
+ oldt = t;
+ t = strstr(t, MORPH_DERI_SFX);
+ if (!t || (tl && tl < t)) t = strstr(oldt, MORPH_INFL_SFX);
+ if (!t || (tl && tl < t)) {
+ t = strstr(oldt, MORPH_TERM_SFX);
+ oldt = NULL;
+ }
+ }
+ if (!s && !t && se && te) return 0;
+ return 1;
+}
+
+int get_sfxcount(const char * morph)
+{
+ if (!morph || !*morph) return 0;
+ int n = 0;
+ const char * old = morph;
+ morph = strstr(morph, MORPH_DERI_SFX);
+ if (!morph) morph = strstr(old, MORPH_INFL_SFX);
+ if (!morph) morph = strstr(old, MORPH_TERM_SFX);
+ while (morph) {
+ n++;
+ old = morph;
+ morph = strstr(morph + 1, MORPH_DERI_SFX);
+ if (!morph) morph = strstr(old + 1, MORPH_INFL_SFX);
+ if (!morph) morph = strstr(old + 1, MORPH_TERM_SFX);
+ }
+ return n;
+}
+
+
+int fieldlen(const char * r)
+{
+ int n = 0;
+ while (r && *r != ' ' && *r != '\t' && *r != '\0' && *r != '\n') {
+ r++;
+ n++;
+ }
+ return n;
+}
+
+char * copy_field(char * dest, const char * morph, const char * var)
+{
+ if (!morph) return NULL;
+ const char * beg = strstr(morph, var);
+ if (beg) {
+ char * d = dest;
+ for (beg += MORPH_TAG_LEN; *beg != ' ' && *beg != '\t' &&
+ *beg != '\n' && *beg != '\0'; d++, beg++) {
+ *d = *beg;
+ }
+ *d = '\0';
+ return dest;
+ }
+ return NULL;
+}
+
+char * mystrrep(char * word, const char * pat, const char * rep) {
+ char * pos = strstr(word, pat);
+ if (pos) {
+ int replen = strlen(rep);
+ int patlen = strlen(pat);
+ while (pos) {
+ if (replen < patlen) {
+ char * end = word + strlen(word);
+ char * next = pos + replen;
+ char * prev = pos + strlen(pat);
+ for (; prev < end; *next = *prev, prev++, next++);
+ *next = '\0';
+ } else if (replen > patlen) {
+ char * end = pos + patlen;
+ char * next = word + strlen(word) + replen - patlen;
+ char * prev = next - replen + patlen;
+ for (; prev >= end; *next = *prev, prev--, next--);
+ }
+ strncpy(pos, rep, replen);
+ pos = strstr(word, pat);
+ }
+ }
+ return word;
+}
+
+ // reverse word
+ int reverseword(char * word) {
+ char r;
+ for (char * dest = word + strlen(word) - 1; word < dest; word++, dest--) {
+ r=*word;
+ *word = *dest;
+ *dest = r;
+ }
+ return 0;
+ }
+
+ // reverse word (error: 1)
+ int reverseword_utf(char * word) {
+ w_char w[MAXWORDLEN];
+ w_char * p;
+ w_char r;
+ int l = u8_u16(w, MAXWORDLEN, word);
+ if (l == -1) return 1;
+ p = w;
+ for (w_char * dest = w + l - 1; p < dest; p++, dest--) {
+ r=*p;
+ *p = *dest;
+ *dest = r;
+ }
+ u16_u8(word, MAXWORDUTF8LEN, w, l);
+ return 0;
+ }
+
+ int uniqlist(char ** list, int n) {
+ int i;
+ if (n < 2) return n;
+ for (i = 0; i < n; i++) {
+ for (int j = 0; j < i; j++) {
+ if (list[j] && list[i] && (strcmp(list[j], list[i]) == 0)) {
+ free(list[i]);
+ list[i] = NULL;
+ break;
+ }
+ }
+ }
+ int m = 1;
+ for (i = 1; i < n; i++) if (list[i]) {
+ list[m] = list[i];
+ m++;
+ }
+ return m;
+ }
+
+ void freelist(char *** list, int n) {
+ if (list && *list && n > 0) {
+ for (int i = 0; i < n; i++) if ((*list)[i]) free((*list)[i]);
+ free(*list);
+ *list = NULL;
+ }
+ }
+
+ // convert null terminated string to all caps
+ void mkallcap(char * p, const struct cs_info * csconv)
+ {
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].cupper;
+ p++;
+ }
+ }
+
+ // convert null terminated string to all little
+ void mkallsmall(char * p, const struct cs_info * csconv)
+ {
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].clower;
+ p++;
+ }
+ }
+
+void mkallsmall_utf(w_char * u, int nc, int langnum) {
+ for (int i = 0; i < nc; i++) {
+ unsigned short idx = (u[i].h << 8) + u[i].l;
+ if (idx != unicodetolower(idx, langnum)) {
+ u[i].h = (unsigned char) (unicodetolower(idx, langnum) >> 8);
+ u[i].l = (unsigned char) (unicodetolower(idx, langnum) & 0x00FF);
+ }
+ }
+}
+
+void mkallcap_utf(w_char * u, int nc, int langnum) {
+ for (int i = 0; i < nc; i++) {
+ unsigned short idx = (u[i].h << 8) + u[i].l;
+ if (idx != unicodetoupper(idx, langnum)) {
+ u[i].h = (unsigned char) (unicodetoupper(idx, langnum) >> 8);
+ u[i].l = (unsigned char) (unicodetoupper(idx, langnum) & 0x00FF);
+ }
+ }
+}
+
+ // convert null terminated string to have initial capital
+ void mkinitcap(char * p, const struct cs_info * csconv)
+ {
+ if (*p != '\0') *p = csconv[((unsigned char)*p)].cupper;
+ }
+
+ // conversion function for protected memory
+ void store_pointer(char * dest, char * source)
+ {
+ memcpy(dest, &source, sizeof(char *));
+ }
+
+ // conversion function for protected memory
+ char * get_stored_pointer(const char * s)
+ {
+ char * p;
+ memcpy(&p, s, sizeof(char *));
+ return p;
+ }
+
+#ifndef MOZILLA_CLIENT
+ // convert null terminated string to all caps using encoding
+ void enmkallcap(char * d, const char * p, const char * encoding)
+
+ {
+ struct cs_info * csconv = get_current_cs(encoding);
+ while (*p != '\0') {
+ *d++ = csconv[((unsigned char) *p)].cupper;
+ p++;
+ }
+ *d = '\0';
+ }
+
+ // convert null terminated string to all little using encoding
+ void enmkallsmall(char * d, const char * p, const char * encoding)
+ {
+ struct cs_info * csconv = get_current_cs(encoding);
+ while (*p != '\0') {
+ *d++ = csconv[((unsigned char) *p)].clower;
+ p++;
+ }
+ *d = '\0';
+ }
+
+ // convert null terminated string to have initial capital using encoding
+ void enmkinitcap(char * d, const char * p, const char * encoding)
+ {
+ struct cs_info * csconv = get_current_cs(encoding);
+ memcpy(d,p,(strlen(p)+1));
+ if (*p != '\0') *d= csconv[((unsigned char)*p)].cupper;
+ }
+
+// these are simple character mappings for the
+// encodings supported
+// supplying isupper, tolower, and toupper
+
+static struct cs_info iso1_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+
+static struct cs_info iso2_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xb1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x01, 0xb3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x01, 0xb5, 0xa5 },
+{ 0x01, 0xb6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x01, 0xb9, 0xa9 },
+{ 0x01, 0xba, 0xaa },
+{ 0x01, 0xbb, 0xab },
+{ 0x01, 0xbc, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x01, 0xbe, 0xae },
+{ 0x01, 0xbf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xa1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xa3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xa5 },
+{ 0x00, 0xb6, 0xa6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xa9 },
+{ 0x00, 0xba, 0xaa },
+{ 0x00, 0xbb, 0xab },
+{ 0x00, 0xbc, 0xac },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xae },
+{ 0x00, 0xbf, 0xaf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+
+static struct cs_info iso3_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xb1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x01, 0xb6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x01, 0x69, 0xa9 },
+{ 0x01, 0xba, 0xaa },
+{ 0x01, 0xbb, 0xab },
+{ 0x01, 0xbc, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x01, 0xbf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xa1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xa6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0x49 },
+{ 0x00, 0xba, 0xaa },
+{ 0x00, 0xbb, 0xab },
+{ 0x00, 0xbc, 0xac },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xaf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso4_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xb1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x01, 0xb3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x01, 0xb5, 0xa5 },
+{ 0x01, 0xb6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x01, 0xb9, 0xa9 },
+{ 0x01, 0xba, 0xaa },
+{ 0x01, 0xbb, 0xab },
+{ 0x01, 0xbc, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x01, 0xbe, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xa1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xa3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xa5 },
+{ 0x00, 0xb6, 0xa6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xa9 },
+{ 0x00, 0xba, 0xaa },
+{ 0x00, 0xbb, 0xab },
+{ 0x00, 0xbc, 0xac },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xae },
+{ 0x00, 0xbf, 0xbf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso5_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xf1, 0xa1 },
+{ 0x01, 0xf2, 0xa2 },
+{ 0x01, 0xf3, 0xa3 },
+{ 0x01, 0xf4, 0xa4 },
+{ 0x01, 0xf5, 0xa5 },
+{ 0x01, 0xf6, 0xa6 },
+{ 0x01, 0xf7, 0xa7 },
+{ 0x01, 0xf8, 0xa8 },
+{ 0x01, 0xf9, 0xa9 },
+{ 0x01, 0xfa, 0xaa },
+{ 0x01, 0xfb, 0xab },
+{ 0x01, 0xfc, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x01, 0xfe, 0xae },
+{ 0x01, 0xff, 0xaf },
+{ 0x01, 0xd0, 0xb0 },
+{ 0x01, 0xd1, 0xb1 },
+{ 0x01, 0xd2, 0xb2 },
+{ 0x01, 0xd3, 0xb3 },
+{ 0x01, 0xd4, 0xb4 },
+{ 0x01, 0xd5, 0xb5 },
+{ 0x01, 0xd6, 0xb6 },
+{ 0x01, 0xd7, 0xb7 },
+{ 0x01, 0xd8, 0xb8 },
+{ 0x01, 0xd9, 0xb9 },
+{ 0x01, 0xda, 0xba },
+{ 0x01, 0xdb, 0xbb },
+{ 0x01, 0xdc, 0xbc },
+{ 0x01, 0xdd, 0xbd },
+{ 0x01, 0xde, 0xbe },
+{ 0x01, 0xdf, 0xbf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x00, 0xd0, 0xb0 },
+{ 0x00, 0xd1, 0xb1 },
+{ 0x00, 0xd2, 0xb2 },
+{ 0x00, 0xd3, 0xb3 },
+{ 0x00, 0xd4, 0xb4 },
+{ 0x00, 0xd5, 0xb5 },
+{ 0x00, 0xd6, 0xb6 },
+{ 0x00, 0xd7, 0xb7 },
+{ 0x00, 0xd8, 0xb8 },
+{ 0x00, 0xd9, 0xb9 },
+{ 0x00, 0xda, 0xba },
+{ 0x00, 0xdb, 0xbb },
+{ 0x00, 0xdc, 0xbc },
+{ 0x00, 0xdd, 0xbd },
+{ 0x00, 0xde, 0xbe },
+{ 0x00, 0xdf, 0xbf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xa1 },
+{ 0x00, 0xf2, 0xa2 },
+{ 0x00, 0xf3, 0xa3 },
+{ 0x00, 0xf4, 0xa4 },
+{ 0x00, 0xf5, 0xa5 },
+{ 0x00, 0xf6, 0xa6 },
+{ 0x00, 0xf7, 0xa7 },
+{ 0x00, 0xf8, 0xa8 },
+{ 0x00, 0xf9, 0xa9 },
+{ 0x00, 0xfa, 0xaa },
+{ 0x00, 0xfb, 0xab },
+{ 0x00, 0xfc, 0xac },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xae },
+{ 0x00, 0xff, 0xaf }
+};
+
+static struct cs_info iso6_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x00, 0xc1, 0xc1 },
+{ 0x00, 0xc2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x00, 0xc4, 0xc4 },
+{ 0x00, 0xc5, 0xc5 },
+{ 0x00, 0xc6, 0xc6 },
+{ 0x00, 0xc7, 0xc7 },
+{ 0x00, 0xc8, 0xc8 },
+{ 0x00, 0xc9, 0xc9 },
+{ 0x00, 0xca, 0xca },
+{ 0x00, 0xcb, 0xcb },
+{ 0x00, 0xcc, 0xcc },
+{ 0x00, 0xcd, 0xcd },
+{ 0x00, 0xce, 0xce },
+{ 0x00, 0xcf, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x00, 0xd1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x00, 0xd3, 0xd3 },
+{ 0x00, 0xd4, 0xd4 },
+{ 0x00, 0xd5, 0xd5 },
+{ 0x00, 0xd6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x00, 0xd8, 0xd8 },
+{ 0x00, 0xd9, 0xd9 },
+{ 0x00, 0xda, 0xda },
+{ 0x00, 0xdb, 0xdb },
+{ 0x00, 0xdc, 0xdc },
+{ 0x00, 0xdd, 0xdd },
+{ 0x00, 0xde, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xe1 },
+{ 0x00, 0xe2, 0xe2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xe4 },
+{ 0x00, 0xe5, 0xe5 },
+{ 0x00, 0xe6, 0xe6 },
+{ 0x00, 0xe7, 0xe7 },
+{ 0x00, 0xe8, 0xe8 },
+{ 0x00, 0xe9, 0xe9 },
+{ 0x00, 0xea, 0xea },
+{ 0x00, 0xeb, 0xeb },
+{ 0x00, 0xec, 0xec },
+{ 0x00, 0xed, 0xed },
+{ 0x00, 0xee, 0xee },
+{ 0x00, 0xef, 0xef },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xf1 },
+{ 0x00, 0xf2, 0xf2 },
+{ 0x00, 0xf3, 0xf3 },
+{ 0x00, 0xf4, 0xf4 },
+{ 0x00, 0xf5, 0xf5 },
+{ 0x00, 0xf6, 0xf6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xf8 },
+{ 0x00, 0xf9, 0xf9 },
+{ 0x00, 0xfa, 0xfa },
+{ 0x00, 0xfb, 0xfb },
+{ 0x00, 0xfc, 0xfc },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xfe },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso7_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x01, 0xdc, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x01, 0xdd, 0xb8 },
+{ 0x01, 0xde, 0xb9 },
+{ 0x01, 0xdf, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x01, 0xfc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x01, 0xfd, 0xbe },
+{ 0x01, 0xfe, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x01, 0xf7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x00, 0xdc, 0xb6 },
+{ 0x00, 0xdd, 0xb8 },
+{ 0x00, 0xde, 0xb9 },
+{ 0x00, 0xdf, 0xba },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd3 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xd7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xbc },
+{ 0x00, 0xfd, 0xbe },
+{ 0x00, 0xfe, 0xbf },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso8_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x00, 0xc1, 0xc1 },
+{ 0x00, 0xc2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x00, 0xc4, 0xc4 },
+{ 0x00, 0xc5, 0xc5 },
+{ 0x00, 0xc6, 0xc6 },
+{ 0x00, 0xc7, 0xc7 },
+{ 0x00, 0xc8, 0xc8 },
+{ 0x00, 0xc9, 0xc9 },
+{ 0x00, 0xca, 0xca },
+{ 0x00, 0xcb, 0xcb },
+{ 0x00, 0xcc, 0xcc },
+{ 0x00, 0xcd, 0xcd },
+{ 0x00, 0xce, 0xce },
+{ 0x00, 0xcf, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x00, 0xd1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x00, 0xd3, 0xd3 },
+{ 0x00, 0xd4, 0xd4 },
+{ 0x00, 0xd5, 0xd5 },
+{ 0x00, 0xd6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x00, 0xd8, 0xd8 },
+{ 0x00, 0xd9, 0xd9 },
+{ 0x00, 0xda, 0xda },
+{ 0x00, 0xdb, 0xdb },
+{ 0x00, 0xdc, 0xdc },
+{ 0x00, 0xdd, 0xdd },
+{ 0x00, 0xde, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xe1 },
+{ 0x00, 0xe2, 0xe2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xe4 },
+{ 0x00, 0xe5, 0xe5 },
+{ 0x00, 0xe6, 0xe6 },
+{ 0x00, 0xe7, 0xe7 },
+{ 0x00, 0xe8, 0xe8 },
+{ 0x00, 0xe9, 0xe9 },
+{ 0x00, 0xea, 0xea },
+{ 0x00, 0xeb, 0xeb },
+{ 0x00, 0xec, 0xec },
+{ 0x00, 0xed, 0xed },
+{ 0x00, 0xee, 0xee },
+{ 0x00, 0xef, 0xef },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xf1 },
+{ 0x00, 0xf2, 0xf2 },
+{ 0x00, 0xf3, 0xf3 },
+{ 0x00, 0xf4, 0xf4 },
+{ 0x00, 0xf5, 0xf5 },
+{ 0x00, 0xf6, 0xf6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xf8 },
+{ 0x00, 0xf9, 0xf9 },
+{ 0x00, 0xfa, 0xfa },
+{ 0x00, 0xfb, 0xfb },
+{ 0x00, 0xfc, 0xfc },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xfe },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso9_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0xfd, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0xdd },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0x69, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0x49 },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso10_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x00, 0xc1, 0xc1 },
+{ 0x00, 0xc2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x00, 0xc4, 0xc4 },
+{ 0x00, 0xc5, 0xc5 },
+{ 0x00, 0xc6, 0xc6 },
+{ 0x00, 0xc7, 0xc7 },
+{ 0x00, 0xc8, 0xc8 },
+{ 0x00, 0xc9, 0xc9 },
+{ 0x00, 0xca, 0xca },
+{ 0x00, 0xcb, 0xcb },
+{ 0x00, 0xcc, 0xcc },
+{ 0x00, 0xcd, 0xcd },
+{ 0x00, 0xce, 0xce },
+{ 0x00, 0xcf, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x00, 0xd1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x00, 0xd3, 0xd3 },
+{ 0x00, 0xd4, 0xd4 },
+{ 0x00, 0xd5, 0xd5 },
+{ 0x00, 0xd6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x00, 0xd8, 0xd8 },
+{ 0x00, 0xd9, 0xd9 },
+{ 0x00, 0xda, 0xda },
+{ 0x00, 0xdb, 0xdb },
+{ 0x00, 0xdc, 0xdc },
+{ 0x00, 0xdd, 0xdd },
+{ 0x00, 0xde, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xe1 },
+{ 0x00, 0xe2, 0xe2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xe4 },
+{ 0x00, 0xe5, 0xe5 },
+{ 0x00, 0xe6, 0xe6 },
+{ 0x00, 0xe7, 0xe7 },
+{ 0x00, 0xe8, 0xe8 },
+{ 0x00, 0xe9, 0xe9 },
+{ 0x00, 0xea, 0xea },
+{ 0x00, 0xeb, 0xeb },
+{ 0x00, 0xec, 0xec },
+{ 0x00, 0xed, 0xed },
+{ 0x00, 0xee, 0xee },
+{ 0x00, 0xef, 0xef },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xf1 },
+{ 0x00, 0xf2, 0xf2 },
+{ 0x00, 0xf3, 0xf3 },
+{ 0x00, 0xf4, 0xf4 },
+{ 0x00, 0xf5, 0xf5 },
+{ 0x00, 0xf6, 0xf6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xf8 },
+{ 0x00, 0xf9, 0xf9 },
+{ 0x00, 0xfa, 0xfa },
+{ 0x00, 0xfb, 0xfb },
+{ 0x00, 0xfc, 0xfc },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xfe },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info koi8r_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xb3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x01, 0xa3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xe0 },
+{ 0x00, 0xc1, 0xe1 },
+{ 0x00, 0xc2, 0xe2 },
+{ 0x00, 0xc3, 0xe3 },
+{ 0x00, 0xc4, 0xe4 },
+{ 0x00, 0xc5, 0xe5 },
+{ 0x00, 0xc6, 0xe6 },
+{ 0x00, 0xc7, 0xe7 },
+{ 0x00, 0xc8, 0xe8 },
+{ 0x00, 0xc9, 0xe9 },
+{ 0x00, 0xca, 0xea },
+{ 0x00, 0xcb, 0xeb },
+{ 0x00, 0xcc, 0xec },
+{ 0x00, 0xcd, 0xed },
+{ 0x00, 0xce, 0xee },
+{ 0x00, 0xcf, 0xef },
+{ 0x00, 0xd0, 0xf0 },
+{ 0x00, 0xd1, 0xf1 },
+{ 0x00, 0xd2, 0xf2 },
+{ 0x00, 0xd3, 0xf3 },
+{ 0x00, 0xd4, 0xf4 },
+{ 0x00, 0xd5, 0xf5 },
+{ 0x00, 0xd6, 0xf6 },
+{ 0x00, 0xd7, 0xf7 },
+{ 0x00, 0xd8, 0xf8 },
+{ 0x00, 0xd9, 0xf9 },
+{ 0x00, 0xda, 0xfa },
+{ 0x00, 0xdb, 0xfb },
+{ 0x00, 0xdc, 0xfc },
+{ 0x00, 0xdd, 0xfd },
+{ 0x00, 0xde, 0xfe },
+{ 0x00, 0xdf, 0xff },
+{ 0x01, 0xc0, 0xe0 },
+{ 0x01, 0xc1, 0xe1 },
+{ 0x01, 0xc2, 0xe2 },
+{ 0x01, 0xc3, 0xe3 },
+{ 0x01, 0xc4, 0xe4 },
+{ 0x01, 0xc5, 0xe5 },
+{ 0x01, 0xc6, 0xe6 },
+{ 0x01, 0xc7, 0xe7 },
+{ 0x01, 0xc8, 0xe8 },
+{ 0x01, 0xc9, 0xe9 },
+{ 0x01, 0xca, 0xea },
+{ 0x01, 0xcb, 0xeb },
+{ 0x01, 0xcc, 0xec },
+{ 0x01, 0xcd, 0xed },
+{ 0x01, 0xce, 0xee },
+{ 0x01, 0xcf, 0xef },
+{ 0x01, 0xd0, 0xf0 },
+{ 0x01, 0xd1, 0xf1 },
+{ 0x01, 0xd2, 0xf2 },
+{ 0x01, 0xd3, 0xf3 },
+{ 0x01, 0xd4, 0xf4 },
+{ 0x01, 0xd5, 0xf5 },
+{ 0x01, 0xd6, 0xf6 },
+{ 0x01, 0xd7, 0xf7 },
+{ 0x01, 0xd8, 0xf8 },
+{ 0x01, 0xd9, 0xf9 },
+{ 0x01, 0xda, 0xfa },
+{ 0x01, 0xdb, 0xfb },
+{ 0x01, 0xdc, 0xfc },
+{ 0x01, 0xdd, 0xfd },
+{ 0x01, 0xde, 0xfe },
+{ 0x01, 0xdf, 0xff }
+};
+
+static struct cs_info koi8u_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xb3 },
+{ 0x00, 0xa4, 0xb4 }, /* ie */
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xb6 }, /* i */
+{ 0x00, 0xa7, 0xb7 }, /* ii */
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xbd }, /* g'' */
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x01, 0xa3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 }, /* IE */
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 }, /* I */
+{ 0x00, 0xb7, 0xb7 }, /* II */
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xe0 },
+{ 0x00, 0xc1, 0xe1 },
+{ 0x00, 0xc2, 0xe2 },
+{ 0x00, 0xc3, 0xe3 },
+{ 0x00, 0xc4, 0xe4 },
+{ 0x00, 0xc5, 0xe5 },
+{ 0x00, 0xc6, 0xe6 },
+{ 0x00, 0xc7, 0xe7 },
+{ 0x00, 0xc8, 0xe8 },
+{ 0x00, 0xc9, 0xe9 },
+{ 0x00, 0xca, 0xea },
+{ 0x00, 0xcb, 0xeb },
+{ 0x00, 0xcc, 0xec },
+{ 0x00, 0xcd, 0xed },
+{ 0x00, 0xce, 0xee },
+{ 0x00, 0xcf, 0xef },
+{ 0x00, 0xd0, 0xf0 },
+{ 0x00, 0xd1, 0xf1 },
+{ 0x00, 0xd2, 0xf2 },
+{ 0x00, 0xd3, 0xf3 },
+{ 0x00, 0xd4, 0xf4 },
+{ 0x00, 0xd5, 0xf5 },
+{ 0x00, 0xd6, 0xf6 },
+{ 0x00, 0xd7, 0xf7 },
+{ 0x00, 0xd8, 0xf8 },
+{ 0x00, 0xd9, 0xf9 },
+{ 0x00, 0xda, 0xfa },
+{ 0x00, 0xdb, 0xfb },
+{ 0x00, 0xdc, 0xfc },
+{ 0x00, 0xdd, 0xfd },
+{ 0x00, 0xde, 0xfe },
+{ 0x00, 0xdf, 0xff },
+{ 0x01, 0xc0, 0xe0 },
+{ 0x01, 0xc1, 0xe1 },
+{ 0x01, 0xc2, 0xe2 },
+{ 0x01, 0xc3, 0xe3 },
+{ 0x01, 0xc4, 0xe4 },
+{ 0x01, 0xc5, 0xe5 },
+{ 0x01, 0xc6, 0xe6 },
+{ 0x01, 0xc7, 0xe7 },
+{ 0x01, 0xc8, 0xe8 },
+{ 0x01, 0xc9, 0xe9 },
+{ 0x01, 0xca, 0xea },
+{ 0x01, 0xcb, 0xeb },
+{ 0x01, 0xcc, 0xec },
+{ 0x01, 0xcd, 0xed },
+{ 0x01, 0xce, 0xee },
+{ 0x01, 0xcf, 0xef },
+{ 0x01, 0xd0, 0xf0 },
+{ 0x01, 0xd1, 0xf1 },
+{ 0x01, 0xd2, 0xf2 },
+{ 0x01, 0xd3, 0xf3 },
+{ 0x01, 0xd4, 0xf4 },
+{ 0x01, 0xd5, 0xf5 },
+{ 0x01, 0xd6, 0xf6 },
+{ 0x01, 0xd7, 0xf7 },
+{ 0x01, 0xd8, 0xf8 },
+{ 0x01, 0xd9, 0xf9 },
+{ 0x01, 0xda, 0xfa },
+{ 0x01, 0xdb, 0xfb },
+{ 0x01, 0xdc, 0xfc },
+{ 0x01, 0xdd, 0xfd },
+{ 0x01, 0xde, 0xfe },
+{ 0x01, 0xdf, 0xff }
+};
+
+static struct cs_info cp1251_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x01, 0x90, 0x80 },
+{ 0x01, 0x83, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x81 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x01, 0x9a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x01, 0x9c, 0x8c },
+{ 0x01, 0x9d, 0x8d },
+{ 0x01, 0x9e, 0x8e },
+{ 0x01, 0x9f, 0x8f },
+{ 0x00, 0x90, 0x80 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x8a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x8c },
+{ 0x00, 0x9d, 0x8d },
+{ 0x00, 0x9e, 0x8e },
+{ 0x00, 0x9f, 0x8f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xa2, 0xa1 },
+{ 0x00, 0xa2, 0xa1 },
+{ 0x01, 0xbc, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x01, 0xb4, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x01, 0xb8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x01, 0xba, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x01, 0xbf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x01, 0xb3, 0xb2 },
+{ 0x00, 0xb3, 0xb2 },
+{ 0x00, 0xb4, 0xa5 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xa8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xaa },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xa3 },
+{ 0x01, 0xbe, 0xbd },
+{ 0x00, 0xbe, 0xbd },
+{ 0x00, 0xbf, 0xaf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x01, 0xf7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x01, 0xff, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xd7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xdf }
+};
+
+static struct cs_info iso13_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0A, 0x0A },
+{ 0x00, 0x0B, 0x0B },
+{ 0x00, 0x0C, 0x0C },
+{ 0x00, 0x0D, 0x0D },
+{ 0x00, 0x0E, 0x0E },
+{ 0x00, 0x0F, 0x0F },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1A, 0x1A },
+{ 0x00, 0x1B, 0x1B },
+{ 0x00, 0x1C, 0x1C },
+{ 0x00, 0x1D, 0x1D },
+{ 0x00, 0x1E, 0x1E },
+{ 0x00, 0x1F, 0x1F },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2A, 0x2A },
+{ 0x00, 0x2B, 0x2B },
+{ 0x00, 0x2C, 0x2C },
+{ 0x00, 0x2D, 0x2D },
+{ 0x00, 0x2E, 0x2E },
+{ 0x00, 0x2F, 0x2F },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3A, 0x3A },
+{ 0x00, 0x3B, 0x3B },
+{ 0x00, 0x3C, 0x3C },
+{ 0x00, 0x3D, 0x3D },
+{ 0x00, 0x3E, 0x3E },
+{ 0x00, 0x3F, 0x3F },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6A, 0x4A },
+{ 0x01, 0x6B, 0x4B },
+{ 0x01, 0x6C, 0x4C },
+{ 0x01, 0x6D, 0x4D },
+{ 0x01, 0x6E, 0x4E },
+{ 0x01, 0x6F, 0x4F },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7A, 0x5A },
+{ 0x00, 0x5B, 0x5B },
+{ 0x00, 0x5C, 0x5C },
+{ 0x00, 0x5D, 0x5D },
+{ 0x00, 0x5E, 0x5E },
+{ 0x00, 0x5F, 0x5F },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6A, 0x4A },
+{ 0x00, 0x6B, 0x4B },
+{ 0x00, 0x6C, 0x4C },
+{ 0x00, 0x6D, 0x4D },
+{ 0x00, 0x6E, 0x4E },
+{ 0x00, 0x6F, 0x4F },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7A, 0x5A },
+{ 0x00, 0x7B, 0x7B },
+{ 0x00, 0x7C, 0x7C },
+{ 0x00, 0x7D, 0x7D },
+{ 0x00, 0x7E, 0x7E },
+{ 0x00, 0x7F, 0x7F },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8A, 0x8A },
+{ 0x00, 0x8B, 0x8B },
+{ 0x00, 0x8C, 0x8C },
+{ 0x00, 0x8D, 0x8D },
+{ 0x00, 0x8E, 0x8E },
+{ 0x00, 0x8F, 0x8F },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9A, 0x9A },
+{ 0x00, 0x9B, 0x9B },
+{ 0x00, 0x9C, 0x9C },
+{ 0x00, 0x9D, 0x9D },
+{ 0x00, 0x9E, 0x9E },
+{ 0x00, 0x9F, 0x9F },
+{ 0x00, 0xA0, 0xA0 },
+{ 0x00, 0xA1, 0xA1 },
+{ 0x00, 0xA2, 0xA2 },
+{ 0x00, 0xA3, 0xA3 },
+{ 0x00, 0xA4, 0xA4 },
+{ 0x00, 0xA5, 0xA5 },
+{ 0x00, 0xA6, 0xA6 },
+{ 0x00, 0xA7, 0xA7 },
+{ 0x01, 0xB8, 0xA8 },
+{ 0x00, 0xA9, 0xA9 },
+{ 0x01, 0xBA, 0xAA },
+{ 0x00, 0xAB, 0xAB },
+{ 0x00, 0xAC, 0xAC },
+{ 0x00, 0xAD, 0xAD },
+{ 0x00, 0xAE, 0xAE },
+{ 0x01, 0xBF, 0xAF },
+{ 0x00, 0xB0, 0xB0 },
+{ 0x00, 0xB1, 0xB1 },
+{ 0x00, 0xB2, 0xB2 },
+{ 0x00, 0xB3, 0xB3 },
+{ 0x00, 0xB4, 0xB4 },
+{ 0x00, 0xB5, 0xB5 },
+{ 0x00, 0xB6, 0xB6 },
+{ 0x00, 0xB7, 0xB7 },
+{ 0x00, 0xB8, 0xA8 },
+{ 0x00, 0xB9, 0xB9 },
+{ 0x00, 0xBA, 0xAA },
+{ 0x00, 0xBB, 0xBB },
+{ 0x00, 0xBC, 0xBC },
+{ 0x00, 0xBD, 0xBD },
+{ 0x00, 0xBE, 0xBE },
+{ 0x00, 0xBF, 0xAF },
+{ 0x01, 0xE0, 0xC0 },
+{ 0x01, 0xE1, 0xC1 },
+{ 0x01, 0xE2, 0xC2 },
+{ 0x01, 0xE3, 0xC3 },
+{ 0x01, 0xE4, 0xC4 },
+{ 0x01, 0xE5, 0xC5 },
+{ 0x01, 0xE6, 0xC6 },
+{ 0x01, 0xE7, 0xC7 },
+{ 0x01, 0xE8, 0xC8 },
+{ 0x01, 0xE9, 0xC9 },
+{ 0x01, 0xEA, 0xCA },
+{ 0x01, 0xEB, 0xCB },
+{ 0x01, 0xEC, 0xCC },
+{ 0x01, 0xED, 0xCD },
+{ 0x01, 0xEE, 0xCE },
+{ 0x01, 0xEF, 0xCF },
+{ 0x01, 0xF0, 0xD0 },
+{ 0x01, 0xF1, 0xD1 },
+{ 0x01, 0xF2, 0xD2 },
+{ 0x01, 0xF3, 0xD3 },
+{ 0x01, 0xF4, 0xD4 },
+{ 0x01, 0xF5, 0xD5 },
+{ 0x01, 0xF6, 0xD6 },
+{ 0x00, 0xD7, 0xD7 },
+{ 0x01, 0xF8, 0xD8 },
+{ 0x01, 0xF9, 0xD9 },
+{ 0x01, 0xFA, 0xDA },
+{ 0x01, 0xFB, 0xDB },
+{ 0x01, 0xFC, 0xDC },
+{ 0x01, 0xFD, 0xDD },
+{ 0x01, 0xFE, 0xDE },
+{ 0x00, 0xDF, 0xDF },
+{ 0x00, 0xE0, 0xC0 },
+{ 0x00, 0xE1, 0xC1 },
+{ 0x00, 0xE2, 0xC2 },
+{ 0x00, 0xE3, 0xC3 },
+{ 0x00, 0xE4, 0xC4 },
+{ 0x00, 0xE5, 0xC5 },
+{ 0x00, 0xE6, 0xC6 },
+{ 0x00, 0xE7, 0xC7 },
+{ 0x00, 0xE8, 0xC8 },
+{ 0x00, 0xE9, 0xC9 },
+{ 0x00, 0xEA, 0xCA },
+{ 0x00, 0xEB, 0xCB },
+{ 0x00, 0xEC, 0xCC },
+{ 0x00, 0xED, 0xCD },
+{ 0x00, 0xEE, 0xCE },
+{ 0x00, 0xEF, 0xCF },
+{ 0x00, 0xF0, 0xD0 },
+{ 0x00, 0xF1, 0xD1 },
+{ 0x00, 0xF2, 0xD2 },
+{ 0x00, 0xF3, 0xD3 },
+{ 0x00, 0xF4, 0xD4 },
+{ 0x00, 0xF5, 0xD5 },
+{ 0x00, 0xF6, 0xD6 },
+{ 0x00, 0xF7, 0xF7 },
+{ 0x00, 0xF8, 0xD8 },
+{ 0x00, 0xF9, 0xD9 },
+{ 0x00, 0xFA, 0xDA },
+{ 0x00, 0xFB, 0xDB },
+{ 0x00, 0xFC, 0xDC },
+{ 0x00, 0xFD, 0xDD },
+{ 0x00, 0xFE, 0xDE },
+{ 0x00, 0xFF, 0xFF }
+};
+
+
+static struct cs_info iso14_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x01, 0xa2, 0xa1 },
+{ 0x00, 0xa2, 0xa1 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x01, 0xa5, 0xa4 },
+{ 0x00, 0xa5, 0xa4 },
+{ 0x01, 0xa6, 0xab },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x01, 0xb8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x01, 0xba, 0xaa },
+{ 0x00, 0xab, 0xa6 },
+{ 0x01, 0xbc, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x01, 0xff, 0xaf },
+{ 0x01, 0xb1, 0xb0 },
+{ 0x00, 0xb1, 0xb0 },
+{ 0x01, 0xb3, 0xb2 },
+{ 0x00, 0xb3, 0xb2 },
+{ 0x01, 0xb5, 0xb4 },
+{ 0x00, 0xb5, 0xb4 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x01, 0xb9, 0xb7 },
+{ 0x00, 0xb8, 0xa8 },
+{ 0x00, 0xb9, 0xb6 },
+{ 0x00, 0xba, 0xaa },
+{ 0x01, 0xbf, 0xbb },
+{ 0x00, 0xbc, 0xac },
+{ 0x01, 0xbe, 0xbd },
+{ 0x00, 0xbe, 0xbd },
+{ 0x00, 0xbf, 0xbb },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x01, 0xf7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xd7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info iso15_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x01, 0xa8, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa6 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x01, 0xb8, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb4 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x01, 0xbd, 0xbc },
+{ 0x00, 0xbd, 0xbc },
+{ 0x01, 0xff, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x01, 0xe0, 0xc0 },
+{ 0x01, 0xe1, 0xc1 },
+{ 0x01, 0xe2, 0xc2 },
+{ 0x01, 0xe3, 0xc3 },
+{ 0x01, 0xe4, 0xc4 },
+{ 0x01, 0xe5, 0xc5 },
+{ 0x01, 0xe6, 0xc6 },
+{ 0x01, 0xe7, 0xc7 },
+{ 0x01, 0xe8, 0xc8 },
+{ 0x01, 0xe9, 0xc9 },
+{ 0x01, 0xea, 0xca },
+{ 0x01, 0xeb, 0xcb },
+{ 0x01, 0xec, 0xcc },
+{ 0x01, 0xed, 0xcd },
+{ 0x01, 0xee, 0xce },
+{ 0x01, 0xef, 0xcf },
+{ 0x01, 0xf0, 0xd0 },
+{ 0x01, 0xf1, 0xd1 },
+{ 0x01, 0xf2, 0xd2 },
+{ 0x01, 0xf3, 0xd3 },
+{ 0x01, 0xf4, 0xd4 },
+{ 0x01, 0xf5, 0xd5 },
+{ 0x01, 0xf6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x01, 0xf8, 0xd8 },
+{ 0x01, 0xf9, 0xd9 },
+{ 0x01, 0xfa, 0xda },
+{ 0x01, 0xfb, 0xdb },
+{ 0x01, 0xfc, 0xdc },
+{ 0x01, 0xfd, 0xdd },
+{ 0x01, 0xfe, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xc0 },
+{ 0x00, 0xe1, 0xc1 },
+{ 0x00, 0xe2, 0xc2 },
+{ 0x00, 0xe3, 0xc3 },
+{ 0x00, 0xe4, 0xc4 },
+{ 0x00, 0xe5, 0xc5 },
+{ 0x00, 0xe6, 0xc6 },
+{ 0x00, 0xe7, 0xc7 },
+{ 0x00, 0xe8, 0xc8 },
+{ 0x00, 0xe9, 0xc9 },
+{ 0x00, 0xea, 0xca },
+{ 0x00, 0xeb, 0xcb },
+{ 0x00, 0xec, 0xcc },
+{ 0x00, 0xed, 0xcd },
+{ 0x00, 0xee, 0xce },
+{ 0x00, 0xef, 0xcf },
+{ 0x00, 0xf0, 0xd0 },
+{ 0x00, 0xf1, 0xd1 },
+{ 0x00, 0xf2, 0xd2 },
+{ 0x00, 0xf3, 0xd3 },
+{ 0x00, 0xf4, 0xd4 },
+{ 0x00, 0xf5, 0xd5 },
+{ 0x00, 0xf6, 0xd6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xd8 },
+{ 0x00, 0xf9, 0xd9 },
+{ 0x00, 0xfa, 0xda },
+{ 0x00, 0xfb, 0xdb },
+{ 0x00, 0xfc, 0xdc },
+{ 0x00, 0xfd, 0xdd },
+{ 0x00, 0xfe, 0xde },
+{ 0x00, 0xff, 0xbe }
+};
+
+static struct cs_info iscii_devanagari_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x00, 0xc1, 0xc1 },
+{ 0x00, 0xc2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x00, 0xc4, 0xc4 },
+{ 0x00, 0xc5, 0xc5 },
+{ 0x00, 0xc6, 0xc6 },
+{ 0x00, 0xc7, 0xc7 },
+{ 0x00, 0xc8, 0xc8 },
+{ 0x00, 0xc9, 0xc9 },
+{ 0x00, 0xca, 0xca },
+{ 0x00, 0xcb, 0xcb },
+{ 0x00, 0xcc, 0xcc },
+{ 0x00, 0xcd, 0xcd },
+{ 0x00, 0xce, 0xce },
+{ 0x00, 0xcf, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x00, 0xd1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x00, 0xd3, 0xd3 },
+{ 0x00, 0xd4, 0xd4 },
+{ 0x00, 0xd5, 0xd5 },
+{ 0x00, 0xd6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x00, 0xd8, 0xd8 },
+{ 0x00, 0xd9, 0xd9 },
+{ 0x00, 0xda, 0xda },
+{ 0x00, 0xdb, 0xdb },
+{ 0x00, 0xdc, 0xdc },
+{ 0x00, 0xdd, 0xdd },
+{ 0x00, 0xde, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xe1 },
+{ 0x00, 0xe2, 0xe2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xe4 },
+{ 0x00, 0xe5, 0xe5 },
+{ 0x00, 0xe6, 0xe6 },
+{ 0x00, 0xe7, 0xe7 },
+{ 0x00, 0xe8, 0xe8 },
+{ 0x00, 0xe9, 0xe9 },
+{ 0x00, 0xea, 0xea },
+{ 0x00, 0xeb, 0xeb },
+{ 0x00, 0xec, 0xec },
+{ 0x00, 0xed, 0xed },
+{ 0x00, 0xee, 0xee },
+{ 0x00, 0xef, 0xef },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xf1 },
+{ 0x00, 0xf2, 0xf2 },
+{ 0x00, 0xf3, 0xf3 },
+{ 0x00, 0xf4, 0xf4 },
+{ 0x00, 0xf5, 0xf5 },
+{ 0x00, 0xf6, 0xf6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xf8 },
+{ 0x00, 0xf9, 0xf9 },
+{ 0x00, 0xfa, 0xfa },
+{ 0x00, 0xfb, 0xfb },
+{ 0x00, 0xfc, 0xfc },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xfe },
+{ 0x00, 0xff, 0xff }
+};
+
+static struct cs_info tis620_tbl[] = {
+{ 0x00, 0x00, 0x00 },
+{ 0x00, 0x01, 0x01 },
+{ 0x00, 0x02, 0x02 },
+{ 0x00, 0x03, 0x03 },
+{ 0x00, 0x04, 0x04 },
+{ 0x00, 0x05, 0x05 },
+{ 0x00, 0x06, 0x06 },
+{ 0x00, 0x07, 0x07 },
+{ 0x00, 0x08, 0x08 },
+{ 0x00, 0x09, 0x09 },
+{ 0x00, 0x0a, 0x0a },
+{ 0x00, 0x0b, 0x0b },
+{ 0x00, 0x0c, 0x0c },
+{ 0x00, 0x0d, 0x0d },
+{ 0x00, 0x0e, 0x0e },
+{ 0x00, 0x0f, 0x0f },
+{ 0x00, 0x10, 0x10 },
+{ 0x00, 0x11, 0x11 },
+{ 0x00, 0x12, 0x12 },
+{ 0x00, 0x13, 0x13 },
+{ 0x00, 0x14, 0x14 },
+{ 0x00, 0x15, 0x15 },
+{ 0x00, 0x16, 0x16 },
+{ 0x00, 0x17, 0x17 },
+{ 0x00, 0x18, 0x18 },
+{ 0x00, 0x19, 0x19 },
+{ 0x00, 0x1a, 0x1a },
+{ 0x00, 0x1b, 0x1b },
+{ 0x00, 0x1c, 0x1c },
+{ 0x00, 0x1d, 0x1d },
+{ 0x00, 0x1e, 0x1e },
+{ 0x00, 0x1f, 0x1f },
+{ 0x00, 0x20, 0x20 },
+{ 0x00, 0x21, 0x21 },
+{ 0x00, 0x22, 0x22 },
+{ 0x00, 0x23, 0x23 },
+{ 0x00, 0x24, 0x24 },
+{ 0x00, 0x25, 0x25 },
+{ 0x00, 0x26, 0x26 },
+{ 0x00, 0x27, 0x27 },
+{ 0x00, 0x28, 0x28 },
+{ 0x00, 0x29, 0x29 },
+{ 0x00, 0x2a, 0x2a },
+{ 0x00, 0x2b, 0x2b },
+{ 0x00, 0x2c, 0x2c },
+{ 0x00, 0x2d, 0x2d },
+{ 0x00, 0x2e, 0x2e },
+{ 0x00, 0x2f, 0x2f },
+{ 0x00, 0x30, 0x30 },
+{ 0x00, 0x31, 0x31 },
+{ 0x00, 0x32, 0x32 },
+{ 0x00, 0x33, 0x33 },
+{ 0x00, 0x34, 0x34 },
+{ 0x00, 0x35, 0x35 },
+{ 0x00, 0x36, 0x36 },
+{ 0x00, 0x37, 0x37 },
+{ 0x00, 0x38, 0x38 },
+{ 0x00, 0x39, 0x39 },
+{ 0x00, 0x3a, 0x3a },
+{ 0x00, 0x3b, 0x3b },
+{ 0x00, 0x3c, 0x3c },
+{ 0x00, 0x3d, 0x3d },
+{ 0x00, 0x3e, 0x3e },
+{ 0x00, 0x3f, 0x3f },
+{ 0x00, 0x40, 0x40 },
+{ 0x01, 0x61, 0x41 },
+{ 0x01, 0x62, 0x42 },
+{ 0x01, 0x63, 0x43 },
+{ 0x01, 0x64, 0x44 },
+{ 0x01, 0x65, 0x45 },
+{ 0x01, 0x66, 0x46 },
+{ 0x01, 0x67, 0x47 },
+{ 0x01, 0x68, 0x48 },
+{ 0x01, 0x69, 0x49 },
+{ 0x01, 0x6a, 0x4a },
+{ 0x01, 0x6b, 0x4b },
+{ 0x01, 0x6c, 0x4c },
+{ 0x01, 0x6d, 0x4d },
+{ 0x01, 0x6e, 0x4e },
+{ 0x01, 0x6f, 0x4f },
+{ 0x01, 0x70, 0x50 },
+{ 0x01, 0x71, 0x51 },
+{ 0x01, 0x72, 0x52 },
+{ 0x01, 0x73, 0x53 },
+{ 0x01, 0x74, 0x54 },
+{ 0x01, 0x75, 0x55 },
+{ 0x01, 0x76, 0x56 },
+{ 0x01, 0x77, 0x57 },
+{ 0x01, 0x78, 0x58 },
+{ 0x01, 0x79, 0x59 },
+{ 0x01, 0x7a, 0x5a },
+{ 0x00, 0x5b, 0x5b },
+{ 0x00, 0x5c, 0x5c },
+{ 0x00, 0x5d, 0x5d },
+{ 0x00, 0x5e, 0x5e },
+{ 0x00, 0x5f, 0x5f },
+{ 0x00, 0x60, 0x60 },
+{ 0x00, 0x61, 0x41 },
+{ 0x00, 0x62, 0x42 },
+{ 0x00, 0x63, 0x43 },
+{ 0x00, 0x64, 0x44 },
+{ 0x00, 0x65, 0x45 },
+{ 0x00, 0x66, 0x46 },
+{ 0x00, 0x67, 0x47 },
+{ 0x00, 0x68, 0x48 },
+{ 0x00, 0x69, 0x49 },
+{ 0x00, 0x6a, 0x4a },
+{ 0x00, 0x6b, 0x4b },
+{ 0x00, 0x6c, 0x4c },
+{ 0x00, 0x6d, 0x4d },
+{ 0x00, 0x6e, 0x4e },
+{ 0x00, 0x6f, 0x4f },
+{ 0x00, 0x70, 0x50 },
+{ 0x00, 0x71, 0x51 },
+{ 0x00, 0x72, 0x52 },
+{ 0x00, 0x73, 0x53 },
+{ 0x00, 0x74, 0x54 },
+{ 0x00, 0x75, 0x55 },
+{ 0x00, 0x76, 0x56 },
+{ 0x00, 0x77, 0x57 },
+{ 0x00, 0x78, 0x58 },
+{ 0x00, 0x79, 0x59 },
+{ 0x00, 0x7a, 0x5a },
+{ 0x00, 0x7b, 0x7b },
+{ 0x00, 0x7c, 0x7c },
+{ 0x00, 0x7d, 0x7d },
+{ 0x00, 0x7e, 0x7e },
+{ 0x00, 0x7f, 0x7f },
+{ 0x00, 0x80, 0x80 },
+{ 0x00, 0x81, 0x81 },
+{ 0x00, 0x82, 0x82 },
+{ 0x00, 0x83, 0x83 },
+{ 0x00, 0x84, 0x84 },
+{ 0x00, 0x85, 0x85 },
+{ 0x00, 0x86, 0x86 },
+{ 0x00, 0x87, 0x87 },
+{ 0x00, 0x88, 0x88 },
+{ 0x00, 0x89, 0x89 },
+{ 0x00, 0x8a, 0x8a },
+{ 0x00, 0x8b, 0x8b },
+{ 0x00, 0x8c, 0x8c },
+{ 0x00, 0x8d, 0x8d },
+{ 0x00, 0x8e, 0x8e },
+{ 0x00, 0x8f, 0x8f },
+{ 0x00, 0x90, 0x90 },
+{ 0x00, 0x91, 0x91 },
+{ 0x00, 0x92, 0x92 },
+{ 0x00, 0x93, 0x93 },
+{ 0x00, 0x94, 0x94 },
+{ 0x00, 0x95, 0x95 },
+{ 0x00, 0x96, 0x96 },
+{ 0x00, 0x97, 0x97 },
+{ 0x00, 0x98, 0x98 },
+{ 0x00, 0x99, 0x99 },
+{ 0x00, 0x9a, 0x9a },
+{ 0x00, 0x9b, 0x9b },
+{ 0x00, 0x9c, 0x9c },
+{ 0x00, 0x9d, 0x9d },
+{ 0x00, 0x9e, 0x9e },
+{ 0x00, 0x9f, 0x9f },
+{ 0x00, 0xa0, 0xa0 },
+{ 0x00, 0xa1, 0xa1 },
+{ 0x00, 0xa2, 0xa2 },
+{ 0x00, 0xa3, 0xa3 },
+{ 0x00, 0xa4, 0xa4 },
+{ 0x00, 0xa5, 0xa5 },
+{ 0x00, 0xa6, 0xa6 },
+{ 0x00, 0xa7, 0xa7 },
+{ 0x00, 0xa8, 0xa8 },
+{ 0x00, 0xa9, 0xa9 },
+{ 0x00, 0xaa, 0xaa },
+{ 0x00, 0xab, 0xab },
+{ 0x00, 0xac, 0xac },
+{ 0x00, 0xad, 0xad },
+{ 0x00, 0xae, 0xae },
+{ 0x00, 0xaf, 0xaf },
+{ 0x00, 0xb0, 0xb0 },
+{ 0x00, 0xb1, 0xb1 },
+{ 0x00, 0xb2, 0xb2 },
+{ 0x00, 0xb3, 0xb3 },
+{ 0x00, 0xb4, 0xb4 },
+{ 0x00, 0xb5, 0xb5 },
+{ 0x00, 0xb6, 0xb6 },
+{ 0x00, 0xb7, 0xb7 },
+{ 0x00, 0xb8, 0xb8 },
+{ 0x00, 0xb9, 0xb9 },
+{ 0x00, 0xba, 0xba },
+{ 0x00, 0xbb, 0xbb },
+{ 0x00, 0xbc, 0xbc },
+{ 0x00, 0xbd, 0xbd },
+{ 0x00, 0xbe, 0xbe },
+{ 0x00, 0xbf, 0xbf },
+{ 0x00, 0xc0, 0xc0 },
+{ 0x00, 0xc1, 0xc1 },
+{ 0x00, 0xc2, 0xc2 },
+{ 0x00, 0xc3, 0xc3 },
+{ 0x00, 0xc4, 0xc4 },
+{ 0x00, 0xc5, 0xc5 },
+{ 0x00, 0xc6, 0xc6 },
+{ 0x00, 0xc7, 0xc7 },
+{ 0x00, 0xc8, 0xc8 },
+{ 0x00, 0xc9, 0xc9 },
+{ 0x00, 0xca, 0xca },
+{ 0x00, 0xcb, 0xcb },
+{ 0x00, 0xcc, 0xcc },
+{ 0x00, 0xcd, 0xcd },
+{ 0x00, 0xce, 0xce },
+{ 0x00, 0xcf, 0xcf },
+{ 0x00, 0xd0, 0xd0 },
+{ 0x00, 0xd1, 0xd1 },
+{ 0x00, 0xd2, 0xd2 },
+{ 0x00, 0xd3, 0xd3 },
+{ 0x00, 0xd4, 0xd4 },
+{ 0x00, 0xd5, 0xd5 },
+{ 0x00, 0xd6, 0xd6 },
+{ 0x00, 0xd7, 0xd7 },
+{ 0x00, 0xd8, 0xd8 },
+{ 0x00, 0xd9, 0xd9 },
+{ 0x00, 0xda, 0xda },
+{ 0x00, 0xdb, 0xdb },
+{ 0x00, 0xdc, 0xdc },
+{ 0x00, 0xdd, 0xdd },
+{ 0x00, 0xde, 0xde },
+{ 0x00, 0xdf, 0xdf },
+{ 0x00, 0xe0, 0xe0 },
+{ 0x00, 0xe1, 0xe1 },
+{ 0x00, 0xe2, 0xe2 },
+{ 0x00, 0xe3, 0xe3 },
+{ 0x00, 0xe4, 0xe4 },
+{ 0x00, 0xe5, 0xe5 },
+{ 0x00, 0xe6, 0xe6 },
+{ 0x00, 0xe7, 0xe7 },
+{ 0x00, 0xe8, 0xe8 },
+{ 0x00, 0xe9, 0xe9 },
+{ 0x00, 0xea, 0xea },
+{ 0x00, 0xeb, 0xeb },
+{ 0x00, 0xec, 0xec },
+{ 0x00, 0xed, 0xed },
+{ 0x00, 0xee, 0xee },
+{ 0x00, 0xef, 0xef },
+{ 0x00, 0xf0, 0xf0 },
+{ 0x00, 0xf1, 0xf1 },
+{ 0x00, 0xf2, 0xf2 },
+{ 0x00, 0xf3, 0xf3 },
+{ 0x00, 0xf4, 0xf4 },
+{ 0x00, 0xf5, 0xf5 },
+{ 0x00, 0xf6, 0xf6 },
+{ 0x00, 0xf7, 0xf7 },
+{ 0x00, 0xf8, 0xf8 },
+{ 0x00, 0xf9, 0xf9 },
+{ 0x00, 0xfa, 0xfa },
+{ 0x00, 0xfb, 0xfb },
+{ 0x00, 0xfc, 0xfc },
+{ 0x00, 0xfd, 0xfd },
+{ 0x00, 0xfe, 0xfe },
+{ 0x00, 0xff, 0xff }
+};
+
+struct enc_entry {
+ const char * enc_name;
+ struct cs_info * cs_table;
+};
+
+static struct enc_entry encds[] = {
+ {"iso88591",iso1_tbl}, //ISO-8859-1
+ {"iso88592",iso2_tbl}, //ISO-8859-2
+ {"iso88593",iso3_tbl}, //ISO-8859-3
+ {"iso88594",iso4_tbl}, //ISO-8859-4
+ {"iso88595",iso5_tbl}, //ISO-8859-5
+ {"iso88596",iso6_tbl}, //ISO-8859-6
+ {"iso88597",iso7_tbl}, //ISO-8859-7
+ {"iso88598",iso8_tbl}, //ISO-8859-8
+ {"iso88599",iso9_tbl}, //ISO-8859-9
+ {"iso885910",iso10_tbl}, //ISO-8859-10
+ {"tis620",tis620_tbl}, //TIS-620/ISO-8859-11
+ {"tis6202533",tis620_tbl}, //TIS-620/ISO-8859-11
+ {"iso885911",tis620_tbl}, //TIS-620/ISO-8859-11
+ {"iso885913", iso13_tbl}, //ISO-8859-13
+ {"iso885914", iso14_tbl}, //ISO-8859-14
+ {"iso885915", iso15_tbl}, //ISO-8859-15
+ {"koi8r",koi8r_tbl}, //KOI8-R
+ {"koi8u",koi8u_tbl}, //KOI8-U
+ {"cp1251",cp1251_tbl}, //CP-1251
+ {"microsoftcp1251",cp1251_tbl}, //microsoft-cp1251
+ {"xisciias", iscii_devanagari_tbl}, //x-iscii-as
+ {"isciidevanagari", iscii_devanagari_tbl} //ISCII-DEVANAGARI
+};
+
+/* map to lower case and remove non alphanumeric chars */
+static void toAsciiLowerAndRemoveNonAlphanumeric( const char* pName, char* pBuf )
+{
+ while ( *pName )
+ {
+ /* A-Z */
+ if ( (*pName >= 0x41) && (*pName <= 0x5A) )
+ {
+ *pBuf = (*pName)+0x20; /* toAsciiLower */
+ pBuf++;
+ }
+ /* a-z, 0-9 */
+ else if ( ((*pName >= 0x61) && (*pName <= 0x7A)) ||
+ ((*pName >= 0x30) && (*pName <= 0x39)) )
+ {
+ *pBuf = *pName;
+ pBuf++;
+ }
+
+ pName++;
+ }
+
+ *pBuf = '\0';
+}
+
+struct cs_info * get_current_cs(const char * es) {
+ char *normalized_encoding = new char[strlen(es)+1];
+ toAsciiLowerAndRemoveNonAlphanumeric(es, normalized_encoding);
+
+ struct cs_info * ccs = NULL;
+ int n = sizeof(encds) / sizeof(encds[0]);
+ for (int i = 0; i < n; i++) {
+ if (strcmp(normalized_encoding,encds[i].enc_name) == 0) {
+ ccs = encds[i].cs_table;
+ break;
+ }
+ }
+
+ delete[] normalized_encoding;
+
+ if (!ccs) {
+ HUNSPELL_WARNING(stderr, "error: unknown encoding %s: using %s as fallback\n", es, encds[0].enc_name);
+ ccs = encds[0].cs_table;
+ }
+
+ return ccs;
+}
+#else
+// XXX This function was rewritten for mozilla. Instead of storing the
+// conversion tables static in this file, create them when needed
+// with help the mozilla backend.
+struct cs_info * get_current_cs(const char * es) {
+ struct cs_info *ccs;
+
+ nsCOMPtr<nsIUnicodeEncoder> encoder;
+ nsCOMPtr<nsIUnicodeDecoder> decoder;
+
+ nsresult rv;
+ nsCOMPtr<nsICharsetConverterManager> ccm = do_GetService(kCharsetConverterManagerCID, &rv);
+ if (NS_FAILED(rv))
+ return nsnull;
+
+ rv = ccm->GetUnicodeEncoder(es, getter_AddRefs(encoder));
+ if (NS_FAILED(rv))
+ return nsnull;
+ encoder->SetOutputErrorBehavior(encoder->kOnError_Signal, nsnull, '?');
+ rv = ccm->GetUnicodeDecoder(es, getter_AddRefs(decoder));
+ if (NS_FAILED(rv))
+ return nsnull;
+ decoder->SetInputErrorBehavior(decoder->kOnError_Signal);
+
+ if (NS_FAILED(rv))
+ return nsnull;
+
+ ccs = new cs_info[256];
+
+ for (unsigned int i = 0; i <= 0xff; ++i) {
+ PRBool success = PR_FALSE;
+ // We want to find the upper/lowercase equivalents of each byte
+ // in this 1-byte character encoding. Call our encoding/decoding
+ // APIs separately for each byte since they may reject some of the
+ // bytes, and we want to handle errors separately for each byte.
+ char lower, upper;
+ do {
+ if (i == 0)
+ break;
+ const char source = char(i);
+ PRUnichar uni, uniCased;
+ PRInt32 charLength = 1, uniLength = 1;
+
+ rv = decoder->Convert(&source, &charLength, &uni, &uniLength);
+ // Explicitly check NS_OK because we don't want to allow
+ // NS_OK_UDEC_MOREOUTPUT or NS_OK_UDEC_MOREINPUT.
+ if (rv != NS_OK || charLength != 1 || uniLength != 1)
+ break;
+ uniCased = ToLowerCase(uni);
+ rv = encoder->Convert(&uniCased, &uniLength, &lower, &charLength);
+ // Explicitly check NS_OK because we don't want to allow
+ // NS_OK_UDEC_MOREOUTPUT or NS_OK_UDEC_MOREINPUT.
+ if (rv != NS_OK || charLength != 1 || uniLength != 1)
+ break;
+
+ uniCased = ToUpperCase(uni);
+ rv = encoder->Convert(&uniCased, &uniLength, &upper, &charLength);
+ // Explicitly check NS_OK because we don't want to allow
+ // NS_OK_UDEC_MOREOUTPUT or NS_OK_UDEC_MOREINPUT.
+ if (rv != NS_OK || charLength != 1 || uniLength != 1)
+ break;
+
+ success = PR_TRUE;
+ } while (0);
+
+ if (success) {
+ ccs[i].cupper = upper;
+ ccs[i].clower = lower;
+ } else {
+ ccs[i].cupper = i;
+ ccs[i].clower = i;
+ }
+
+ if (ccs[i].clower != (unsigned char)i)
+ ccs[i].ccase = true;
+ else
+ ccs[i].ccase = false;
+ }
+
+ return ccs;
+}
+#endif
+
+// primitive isalpha() replacement for tokenization
+char * get_casechars(const char * enc) {
+ struct cs_info * csconv = get_current_cs(enc);
+ char expw[MAXLNLEN];
+ char * p = expw;
+ for (int i = 0; i <= 255; i++) {
+ if ((csconv[i].cupper != csconv[i].clower)) {
+ *p = (char) i;
+ p++;
+ }
+ }
+ *p = '\0';
+#ifdef MOZILLA_CLIENT
+ delete [] csconv;
+#endif
+ return mystrdup(expw);
+}
+
+// language to encoding default map
+
+struct lang_map {
+ const char * lang;
+ int num;
+};
+
+static struct lang_map lang2enc[] = {
+{"ar", LANG_ar},
+{"az", LANG_az},
+{"az_AZ", LANG_az}, // for back-compatibility
+{"bg", LANG_bg},
+{"ca", LANG_ca},
+{"cs", LANG_cs},
+{"da", LANG_da},
+{"de", LANG_de},
+{"el", LANG_el},
+{"en", LANG_en},
+{"es", LANG_es},
+{"eu", LANG_eu},
+{"gl", LANG_gl},
+{"fr", LANG_fr},
+{"hr", LANG_hr},
+{"hu", LANG_hu},
+{"hu_HU", LANG_hu}, // for back-compatibility
+{"it", LANG_it},
+{"la", LANG_la},
+{"lv", LANG_lv},
+{"nl", LANG_nl},
+{"pl", LANG_pl},
+{"pt", LANG_pt},
+{"sv", LANG_sv},
+{"tr", LANG_tr},
+{"tr_TR", LANG_tr}, // for back-compatibility
+{"ru", LANG_ru},
+{"uk", LANG_uk}
+};
+
+
+int get_lang_num(const char * lang) {
+ int n = sizeof(lang2enc) / sizeof(lang2enc[0]);
+ for (int i = 0; i < n; i++) {
+ if (strcmp(lang, lang2enc[i].lang) == 0) {
+ return lang2enc[i].num;
+ }
+ }
+ return LANG_xx;
+}
+
+#ifndef OPENOFFICEORG
+#ifndef MOZILLA_CLIENT
+int initialize_utf_tbl() {
+ utf_tbl_count++;
+ if (utf_tbl) return 0;
+ utf_tbl = (unicode_info2 *) malloc(CONTSIZE * sizeof(unicode_info2));
+ if (utf_tbl) {
+ size_t j;
+ for (j = 0; j < CONTSIZE; j++) {
+ utf_tbl[j].cletter = 0;
+ utf_tbl[j].clower = (unsigned short) j;
+ utf_tbl[j].cupper = (unsigned short) j;
+ }
+ for (j = 0; j < UTF_LST_LEN; j++) {
+ utf_tbl[utf_lst[j].c].cletter = 1;
+ utf_tbl[utf_lst[j].c].clower = utf_lst[j].clower;
+ utf_tbl[utf_lst[j].c].cupper = utf_lst[j].cupper;
+ }
+ } else return 1;
+ return 0;
+}
+#endif
+#endif
+
+void free_utf_tbl() {
+ if (utf_tbl_count > 0) utf_tbl_count--;
+ if (utf_tbl && (utf_tbl_count == 0)) {
+ free(utf_tbl);
+ utf_tbl = NULL;
+ }
+}
+
+unsigned short unicodetoupper(unsigned short c, int langnum)
+{
+ // In Azeri and Turkish, I and i dictinct letters:
+ // There are a dotless lower case i pair of upper `I',
+ // and an upper I with dot pair of lower `i'.
+ if (c == 0x0069 && ((langnum == LANG_az) || (langnum == LANG_tr)))
+ return 0x0130;
+#ifdef OPENOFFICEORG
+ return u_toupper(c);
+#else
+#ifdef MOZILLA_CLIENT
+ return ToUpperCase((PRUnichar) c);
+#else
+ return (utf_tbl) ? utf_tbl[c].cupper : c;
+#endif
+#endif
+}
+
+unsigned short unicodetolower(unsigned short c, int langnum)
+{
+ // In Azeri and Turkish, I and i dictinct letters:
+ // There are a dotless lower case i pair of upper `I',
+ // and an upper I with dot pair of lower `i'.
+ if (c == 0x0049 && ((langnum == LANG_az) || (langnum == LANG_tr)))
+ return 0x0131;
+#ifdef OPENOFFICEORG
+ return u_tolower(c);
+#else
+#ifdef MOZILLA_CLIENT
+ return ToLowerCase((PRUnichar) c);
+#else
+ return (utf_tbl) ? utf_tbl[c].clower : c;
+#endif
+#endif
+}
+
+int unicodeisalpha(unsigned short c)
+{
+#ifdef OPENOFFICEORG
+ return u_isalpha(c);
+#else
+ return (utf_tbl) ? utf_tbl[c].cletter : 0;
+#endif
+}
+
+/* get type of capitalization */
+int get_captype(char * word, int nl, cs_info * csconv) {
+ // now determine the capitalization type of the first nl letters
+ int ncap = 0;
+ int nneutral = 0;
+ int firstcap = 0;
+ if (csconv == NULL) return NOCAP;
+ for (char * q = word; *q != '\0'; q++) {
+ if (csconv[*((unsigned char *)q)].ccase) ncap++;
+ if (csconv[*((unsigned char *)q)].cupper == csconv[*((unsigned char *)q)].clower) nneutral++;
+ }
+ if (ncap) {
+ firstcap = csconv[*((unsigned char *) word)].ccase;
+ }
+
+ // now finally set the captype
+ if (ncap == 0) {
+ return NOCAP;
+ } else if ((ncap == 1) && firstcap) {
+ return INITCAP;
+ } else if ((ncap == nl) || ((ncap + nneutral) == nl)) {
+ return ALLCAP;
+ } else if ((ncap > 1) && firstcap) {
+ return HUHINITCAP;
+ }
+ return HUHCAP;
+}
+
+int get_captype_utf8(w_char * word, int nl, int langnum) {
+ // now determine the capitalization type of the first nl letters
+ int ncap = 0;
+ int nneutral = 0;
+ int firstcap = 0;
+ unsigned short idx;
+ // don't check too long words
+ if (nl >= MAXWORDLEN) return 0;
+ // big Unicode character (non BMP area)
+ if (nl == -1) return NOCAP;
+ for (int i = 0; i < nl; i++) {
+ idx = (word[i].h << 8) + word[i].l;
+ if (idx != unicodetolower(idx, langnum)) ncap++;
+ if (unicodetoupper(idx, langnum) == unicodetolower(idx, langnum)) nneutral++;
+ }
+ if (ncap) {
+ idx = (word[0].h << 8) + word[0].l;
+ firstcap = (idx != unicodetolower(idx, langnum));
+ }
+
+ // now finally set the captype
+ if (ncap == 0) {
+ return NOCAP;
+ } else if ((ncap == 1) && firstcap) {
+ return INITCAP;
+ } else if ((ncap == nl) || ((ncap + nneutral) == nl)) {
+ return ALLCAP;
+ } else if ((ncap > 1) && firstcap) {
+ return HUHINITCAP;
+ }
+ return HUHCAP;
+}
+
+
+// strip all ignored characters in the string
+void remove_ignored_chars_utf(char * word, unsigned short ignored_chars[], int ignored_len)
+{
+ w_char w[MAXWORDLEN];
+ w_char w2[MAXWORDLEN];
+ int i;
+ int j;
+ int len = u8_u16(w, MAXWORDLEN, word);
+ for (i = 0, j = 0; i < len; i++) {
+ if (!flag_bsearch(ignored_chars, ((unsigned short *) w)[i], ignored_len)) {
+ w2[j] = w[i];
+ j++;
+ }
+ }
+ if (j < i) u16_u8(word, MAXWORDUTF8LEN, w2, j);
+}
+
+// strip all ignored characters in the string
+void remove_ignored_chars(char * word, char * ignored_chars)
+{
+ for (char * p = word; *p != '\0'; p++) {
+ if (!strchr(ignored_chars, *p)) {
+ *word = *p;
+ word++;
+ }
+ }
+ *word = '\0';
+}
+
+int parse_string(char * line, char ** out, int ln)
+{
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ if (*out) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple definitions\n", ln);
+ return 1;
+ }
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ *out = mystrdup(piece);
+ if (!*out) return 1;
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ // free(piece);
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", ln);
+ return 1;
+ }
+ return 0;
+}
+
+int parse_array(char * line, char ** out, unsigned short ** out_utf16,
+ int * out_utf16_len, int utf8, int ln) {
+ if (parse_string(line, out, ln)) return 1;
+ if (utf8) {
+ w_char w[MAXWORDLEN];
+ int n = u8_u16(w, MAXWORDLEN, *out);
+ if (n > 0) {
+ flag_qsort((unsigned short *) w, 0, n);
+ *out_utf16 = (unsigned short *) malloc(n * sizeof(unsigned short));
+ if (!*out_utf16) return 1;
+ memcpy(*out_utf16, w, n * sizeof(unsigned short));
+ }
+ *out_utf16_len = n;
+ }
+ return 0;
+}
diff --git a/src/cpp/core/spelling/hunspell/csutil.hxx b/src/cpp/core/spelling/hunspell/csutil.hxx
new file mode 100644
index 0000000..7bd0b91
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/csutil.hxx
@@ -0,0 +1,220 @@
+#ifndef __CSUTILHXX__
+#define __CSUTILHXX__
+
+#include "hunvisapi.h"
+
+// First some base level utility routines
+
+#include <string.h>
+#include "w_char.hxx"
+#include "htypes.hxx"
+
+#ifdef MOZILLA_CLIENT
+#include "nscore.h" // for mozalloc headers
+#endif
+
+// casing
+#define NOCAP 0
+#define INITCAP 1
+#define ALLCAP 2
+#define HUHCAP 3
+#define HUHINITCAP 4
+
+// default encoding and keystring
+#define SPELL_ENCODING "ISO8859-1"
+#define SPELL_KEYSTRING "qwertyuiop|asdfghjkl|zxcvbnm"
+
+// default morphological fields
+#define MORPH_STEM "st:"
+#define MORPH_ALLOMORPH "al:"
+#define MORPH_POS "po:"
+#define MORPH_DERI_PFX "dp:"
+#define MORPH_INFL_PFX "ip:"
+#define MORPH_TERM_PFX "tp:"
+#define MORPH_DERI_SFX "ds:"
+#define MORPH_INFL_SFX "is:"
+#define MORPH_TERM_SFX "ts:"
+#define MORPH_SURF_PFX "sp:"
+#define MORPH_FREQ "fr:"
+#define MORPH_PHON "ph:"
+#define MORPH_HYPH "hy:"
+#define MORPH_PART "pa:"
+#define MORPH_FLAG "fl:"
+#define MORPH_HENTRY "_H:"
+#define MORPH_TAG_LEN strlen(MORPH_STEM)
+
+#define MSEP_FLD ' '
+#define MSEP_REC '\n'
+#define MSEP_ALT '\v'
+
+// default flags
+#define DEFAULTFLAGS 65510
+#define FORBIDDENWORD 65510
+#define ONLYUPCASEFLAG 65511
+
+// convert UTF-16 characters to UTF-8
+LIBHUNSPELL_DLL_EXPORTED char * u16_u8(char * dest, int size, const w_char * src, int srclen);
+
+// convert UTF-8 characters to UTF-16
+LIBHUNSPELL_DLL_EXPORTED int u8_u16(w_char * dest, int size, const char * src);
+
+// sort 2-byte vector
+LIBHUNSPELL_DLL_EXPORTED void flag_qsort(unsigned short flags[], int begin, int end);
+
+// binary search in 2-byte vector
+LIBHUNSPELL_DLL_EXPORTED int flag_bsearch(unsigned short flags[], unsigned short flag, int right);
+
+// remove end of line char(s)
+LIBHUNSPELL_DLL_EXPORTED void mychomp(char * s);
+
+// duplicate string
+LIBHUNSPELL_DLL_EXPORTED char * mystrdup(const char * s);
+
+// strcat for limited length destination string
+LIBHUNSPELL_DLL_EXPORTED char * mystrcat(char * dest, const char * st, int max);
+
+// duplicate reverse of string
+LIBHUNSPELL_DLL_EXPORTED char * myrevstrdup(const char * s);
+
+// parse into tokens with char delimiter
+LIBHUNSPELL_DLL_EXPORTED char * mystrsep(char ** sptr, const char delim);
+// parse into tokens with char delimiter
+LIBHUNSPELL_DLL_EXPORTED char * mystrsep2(char ** sptr, const char delim);
+
+// parse into tokens with char delimiter
+LIBHUNSPELL_DLL_EXPORTED char * mystrrep(char *, const char *, const char *);
+
+// append s to ends of every lines in text
+LIBHUNSPELL_DLL_EXPORTED void strlinecat(char * lines, const char * s);
+
+// tokenize into lines with new line
+LIBHUNSPELL_DLL_EXPORTED int line_tok(const char * text, char *** lines, char breakchar);
+
+// tokenize into lines with new line and uniq in place
+LIBHUNSPELL_DLL_EXPORTED char * line_uniq(char * text, char breakchar);
+LIBHUNSPELL_DLL_EXPORTED char * line_uniq_app(char ** text, char breakchar);
+
+// change oldchar to newchar in place
+LIBHUNSPELL_DLL_EXPORTED char * tr(char * text, char oldc, char newc);
+
+// reverse word
+LIBHUNSPELL_DLL_EXPORTED int reverseword(char *);
+
+// reverse word
+LIBHUNSPELL_DLL_EXPORTED int reverseword_utf(char *);
+
+// remove duplicates
+LIBHUNSPELL_DLL_EXPORTED int uniqlist(char ** list, int n);
+
+// free character array list
+LIBHUNSPELL_DLL_EXPORTED void freelist(char *** list, int n);
+
+// character encoding information
+struct cs_info {
+ unsigned char ccase;
+ unsigned char clower;
+ unsigned char cupper;
+};
+
+LIBHUNSPELL_DLL_EXPORTED int initialize_utf_tbl();
+LIBHUNSPELL_DLL_EXPORTED void free_utf_tbl();
+LIBHUNSPELL_DLL_EXPORTED unsigned short unicodetoupper(unsigned short c, int langnum);
+LIBHUNSPELL_DLL_EXPORTED unsigned short unicodetolower(unsigned short c, int langnum);
+LIBHUNSPELL_DLL_EXPORTED int unicodeisalpha(unsigned short c);
+
+LIBHUNSPELL_DLL_EXPORTED struct cs_info * get_current_cs(const char * es);
+
+// get language identifiers of language codes
+LIBHUNSPELL_DLL_EXPORTED int get_lang_num(const char * lang);
+
+// get characters of the given 8bit encoding with lower- and uppercase forms
+LIBHUNSPELL_DLL_EXPORTED char * get_casechars(const char * enc);
+
+// convert null terminated string to all caps using encoding
+LIBHUNSPELL_DLL_EXPORTED void enmkallcap(char * d, const char * p, const char * encoding);
+
+// convert null terminated string to all little using encoding
+LIBHUNSPELL_DLL_EXPORTED void enmkallsmall(char * d, const char * p, const char * encoding);
+
+// convert null terminated string to have initial capital using encoding
+LIBHUNSPELL_DLL_EXPORTED void enmkinitcap(char * d, const char * p, const char * encoding);
+
+// convert null terminated string to all caps
+LIBHUNSPELL_DLL_EXPORTED void mkallcap(char * p, const struct cs_info * csconv);
+
+// convert null terminated string to all little
+LIBHUNSPELL_DLL_EXPORTED void mkallsmall(char * p, const struct cs_info * csconv);
+
+// convert null terminated string to have initial capital
+LIBHUNSPELL_DLL_EXPORTED void mkinitcap(char * p, const struct cs_info * csconv);
+
+// convert first nc characters of UTF-8 string to little
+LIBHUNSPELL_DLL_EXPORTED void mkallsmall_utf(w_char * u, int nc, int langnum);
+
+// convert first nc characters of UTF-8 string to capital
+LIBHUNSPELL_DLL_EXPORTED void mkallcap_utf(w_char * u, int nc, int langnum);
+
+// get type of capitalization
+LIBHUNSPELL_DLL_EXPORTED int get_captype(char * q, int nl, cs_info *);
+
+// get type of capitalization (UTF-8)
+LIBHUNSPELL_DLL_EXPORTED int get_captype_utf8(w_char * q, int nl, int langnum);
+
+// strip all ignored characters in the string
+LIBHUNSPELL_DLL_EXPORTED void remove_ignored_chars_utf(char * word, unsigned short ignored_chars[], int ignored_len);
+
+// strip all ignored characters in the string
+LIBHUNSPELL_DLL_EXPORTED void remove_ignored_chars(char * word, char * ignored_chars);
+
+LIBHUNSPELL_DLL_EXPORTED int parse_string(char * line, char ** out, int ln);
+
+LIBHUNSPELL_DLL_EXPORTED int parse_array(char * line, char ** out, unsigned short ** out_utf16,
+ int * out_utf16_len, int utf8, int ln);
+
+LIBHUNSPELL_DLL_EXPORTED int fieldlen(const char * r);
+LIBHUNSPELL_DLL_EXPORTED char * copy_field(char * dest, const char * morph, const char * var);
+
+LIBHUNSPELL_DLL_EXPORTED int morphcmp(const char * s, const char * t);
+
+LIBHUNSPELL_DLL_EXPORTED int get_sfxcount(const char * morph);
+
+// conversion function for protected memory
+LIBHUNSPELL_DLL_EXPORTED void store_pointer(char * dest, char * source);
+
+// conversion function for protected memory
+LIBHUNSPELL_DLL_EXPORTED char * get_stored_pointer(const char * s);
+
+// hash entry macros
+LIBHUNSPELL_DLL_EXPORTED inline char* HENTRY_DATA(struct hentry *h)
+{
+ char *ret;
+ if (!h->var)
+ ret = NULL;
+ else if (h->var & H_OPT_ALIASM)
+ ret = get_stored_pointer(HENTRY_WORD(h) + h->blen + 1);
+ else
+ ret = HENTRY_WORD(h) + h->blen + 1;
+ return ret;
+}
+
+// NULL-free version for warning-free OOo build
+LIBHUNSPELL_DLL_EXPORTED inline const char* HENTRY_DATA2(const struct hentry *h)
+{
+ const char *ret;
+ if (!h->var)
+ ret = "";
+ else if (h->var & H_OPT_ALIASM)
+ ret = get_stored_pointer(HENTRY_WORD(h) + h->blen + 1);
+ else
+ ret = HENTRY_WORD(h) + h->blen + 1;
+ return ret;
+}
+
+LIBHUNSPELL_DLL_EXPORTED inline char* HENTRY_FIND(struct hentry *h, const char *p)
+{
+ return (HENTRY_DATA(h) ? strstr(HENTRY_DATA(h), p) : NULL);
+}
+
+#define w_char_eq(a,b) (((a).l == (b).l) && ((a).h == (b).h))
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/dictmgr.cxx b/src/cpp/core/spelling/hunspell/dictmgr.cxx
new file mode 100644
index 0000000..b4a15b1
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/dictmgr.cxx
@@ -0,0 +1,180 @@
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "dictmgr.hxx"
+
+DictMgr::DictMgr(const char * dictpath, const char * etype) : numdict(0)
+{
+ // load list of etype entries
+ pdentry = (dictentry *)malloc(MAXDICTIONARIES*sizeof(struct dictentry));
+ if (pdentry) {
+ if (parse_file(dictpath, etype)) {
+ numdict = 0;
+ // no dictionary.lst found is okay
+ }
+ }
+}
+
+
+DictMgr::~DictMgr()
+{
+ dictentry * pdict = NULL;
+ if (pdentry) {
+ pdict = pdentry;
+ for (int i=0;i<numdict;i++) {
+ if (pdict->lang) {
+ free(pdict->lang);
+ pdict->lang = NULL;
+ }
+ if (pdict->region) {
+ free(pdict->region);
+ pdict->region=NULL;
+ }
+ if (pdict->filename) {
+ free(pdict->filename);
+ pdict->filename = NULL;
+ }
+ pdict++;
+ }
+ free(pdentry);
+ pdentry = NULL;
+ pdict = NULL;
+ }
+ numdict = 0;
+}
+
+
+// read in list of etype entries and build up structure to describe them
+int DictMgr::parse_file(const char * dictpath, const char * etype)
+{
+
+ int i;
+ char line[MAXDICTENTRYLEN+1];
+ dictentry * pdict = pdentry;
+
+ // open the dictionary list file
+ FILE * dictlst;
+ dictlst = fopen(dictpath,"r");
+ if (!dictlst) {
+ return 1;
+ }
+
+ // step one is to parse the dictionary list building up the
+ // descriptive structures
+
+ // read in each line ignoring any that dont start with etype
+ while (fgets(line,MAXDICTENTRYLEN,dictlst)) {
+ mychomp(line);
+
+ /* parse in a dictionary entry */
+ if (strncmp(line,etype,4) == 0) {
+ if (numdict < MAXDICTIONARIES) {
+ char * tp = line;
+ char * piece;
+ i = 0;
+ while ((piece=mystrsep(&tp,' '))) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: break;
+ case 1: pdict->lang = mystrdup(piece); break;
+ case 2: if (strcmp (piece, "ANY") == 0)
+ pdict->region = mystrdup("");
+ else
+ pdict->region = mystrdup(piece);
+ break;
+ case 3: pdict->filename = mystrdup(piece); break;
+ default: break;
+ }
+ i++;
+ }
+ free(piece);
+ }
+ if (i == 4) {
+ numdict++;
+ pdict++;
+ } else {
+ switch (i) {
+ case 3:
+ free(pdict->region);
+ pdict->region=NULL;
+ case 2: //deliberate fallthrough
+ free(pdict->lang);
+ pdict->lang=NULL;
+ default:
+ break;
+ }
+ fprintf(stderr,"dictionary list corruption in line \"%s\"\n",line);
+ fflush(stderr);
+ }
+ }
+ }
+ }
+ fclose(dictlst);
+ return 0;
+}
+
+// return text encoding of dictionary
+int DictMgr::get_list(dictentry ** ppentry)
+{
+ *ppentry = pdentry;
+ return numdict;
+}
+
+
+
+// strip strings into token based on single char delimiter
+// acts like strsep() but only uses a delim char and not
+// a delim string
+
+char * DictMgr::mystrsep(char ** stringp, const char delim)
+{
+ char * rv = NULL;
+ char * mp = *stringp;
+ size_t n = strlen(mp);
+ if (n > 0) {
+ char * dp = (char *)memchr(mp,(int)((unsigned char)delim),n);
+ if (dp) {
+ *stringp = dp+1;
+ size_t nc = dp - mp;
+ rv = (char *) malloc(nc+1);
+ if (rv) {
+ memcpy(rv,mp,nc);
+ *(rv+nc) = '\0';
+ }
+ } else {
+ rv = (char *) malloc(n+1);
+ if (rv) {
+ memcpy(rv, mp, n);
+ *(rv+n) = '\0';
+ *stringp = mp + n;
+ }
+ }
+ }
+ return rv;
+}
+
+
+// replaces strdup with ansi version
+char * DictMgr::mystrdup(const char * s)
+{
+ char * d = NULL;
+ if (s) {
+ int sl = strlen(s)+1;
+ d = (char *) malloc(sl);
+ if (d) memcpy(d,s,sl);
+ }
+ return d;
+}
+
+
+// remove cross-platform text line end characters
+void DictMgr:: mychomp(char * s)
+{
+ int k = strlen(s);
+ if ((k > 0) && ((*(s+k-1)=='\r') || (*(s+k-1)=='\n'))) *(s+k-1) = '\0';
+ if ((k > 1) && (*(s+k-2) == '\r')) *(s+k-2) = '\0';
+}
+
diff --git a/src/cpp/core/spelling/hunspell/dictmgr.hxx b/src/cpp/core/spelling/hunspell/dictmgr.hxx
new file mode 100644
index 0000000..bb197f8
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/dictmgr.hxx
@@ -0,0 +1,36 @@
+#ifndef _DICTMGR_HXX_
+#define _DICTMGR_HXX_
+
+#include "hunvisapi.h"
+
+#define MAXDICTIONARIES 100
+#define MAXDICTENTRYLEN 1024
+
+struct dictentry {
+ char * filename;
+ char * lang;
+ char * region;
+};
+
+
+class LIBHUNSPELL_DLL_EXPORTED DictMgr
+{
+
+ int numdict;
+ dictentry * pdentry;
+
+public:
+
+ DictMgr(const char * dictpath, const char * etype);
+ ~DictMgr();
+ int get_list(dictentry** ppentry);
+
+private:
+ int parse_file(const char * dictpath, const char * etype);
+ char * mystrsep(char ** stringp, const char delim);
+ char * mystrdup(const char * s);
+ void mychomp(char * s);
+
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/filemgr.cxx b/src/cpp/core/spelling/hunspell/filemgr.cxx
new file mode 100644
index 0000000..5fb82bc
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/filemgr.cxx
@@ -0,0 +1,49 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "filemgr.hxx"
+
+int FileMgr::fail(const char * err, const char * par) {
+ fprintf(stderr, err, par);
+ return -1;
+}
+
+FileMgr::FileMgr(const char * file, const char * key) {
+ linenum = 0;
+ hin = NULL;
+ fin = fopen(file, "r");
+ if (!fin) {
+ // check hzipped file
+ char * st = (char *) malloc(strlen(file) + strlen(HZIP_EXTENSION) + 1);
+ if (st) {
+ strcpy(st, file);
+ strcat(st, HZIP_EXTENSION);
+ hin = new Hunzip(st, key);
+ free(st);
+ }
+ }
+ if (!fin && !hin) fail(MSG_OPEN, file);
+}
+
+FileMgr::~FileMgr()
+{
+ if (fin) fclose(fin);
+ if (hin) delete hin;
+}
+
+char * FileMgr::getline() {
+ const char * l;
+ linenum++;
+ if (fin) return fgets(in, BUFSIZE - 1, fin);
+ if (hin && (l = hin->getline())) return strcpy(in, l);
+ linenum--;
+ return NULL;
+}
+
+int FileMgr::getlinenum() {
+ return linenum;
+}
diff --git a/src/cpp/core/spelling/hunspell/filemgr.hxx b/src/cpp/core/spelling/hunspell/filemgr.hxx
new file mode 100644
index 0000000..94cb723
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/filemgr.hxx
@@ -0,0 +1,25 @@
+/* file manager class - read lines of files [filename] OR [filename.hz] */
+#ifndef _FILEMGR_HXX_
+#define _FILEMGR_HXX_
+
+#include "hunvisapi.h"
+
+#include "hunzip.hxx"
+#include <stdio.h>
+
+class LIBHUNSPELL_DLL_EXPORTED FileMgr
+{
+protected:
+ FILE * fin;
+ Hunzip * hin;
+ char in[BUFSIZE + 50]; // input buffer
+ int fail(const char * err, const char * par);
+ int linenum;
+
+public:
+ FileMgr(const char * filename, const char * key = NULL);
+ ~FileMgr();
+ char * getline();
+ int getlinenum();
+};
+#endif
diff --git a/src/cpp/core/spelling/hunspell/hashmgr.cxx b/src/cpp/core/spelling/hunspell/hashmgr.cxx
new file mode 100644
index 0000000..ea93b87
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hashmgr.cxx
@@ -0,0 +1,928 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "hashmgr.hxx"
+#include "csutil.hxx"
+#include "atypes.hxx"
+
+// build a hash table from a munched word list
+
+HashMgr::HashMgr(const char * tpath, const char * apath, const char * key)
+{
+ tablesize = 0;
+ tableptr = NULL;
+ flag_mode = FLAG_CHAR;
+ complexprefixes = 0;
+ utf8 = 0;
+ langnum = 0;
+ lang = NULL;
+ enc = NULL;
+ csconv = 0;
+ ignorechars = NULL;
+ ignorechars_utf16 = NULL;
+ ignorechars_utf16_len = 0;
+ numaliasf = 0;
+ aliasf = NULL;
+ numaliasm = 0;
+ aliasm = NULL;
+ forbiddenword = FORBIDDENWORD; // forbidden word signing flag
+ load_config(apath, key);
+ int ec = load_tables(tpath, key);
+ if (ec) {
+ /* error condition - what should we do here */
+ HUNSPELL_WARNING(stderr, "Hash Manager Error : %d\n",ec);
+ if (tableptr) {
+ free(tableptr);
+ tableptr = NULL;
+ }
+ tablesize = 0;
+ }
+}
+
+
+HashMgr::~HashMgr()
+{
+ if (tableptr) {
+ // now pass through hash table freeing up everything
+ // go through column by column of the table
+ for (int i=0; i < tablesize; i++) {
+ struct hentry * pt = tableptr[i];
+ struct hentry * nt = NULL;
+ while(pt) {
+ nt = pt->next;
+ if (pt->astr && (!aliasf || TESTAFF(pt->astr, ONLYUPCASEFLAG, pt->alen))) free(pt->astr);
+ free(pt);
+ pt = nt;
+ }
+ }
+ free(tableptr);
+ }
+ tablesize = 0;
+
+ if (aliasf) {
+ for (int j = 0; j < (numaliasf); j++) free(aliasf[j]);
+ free(aliasf);
+ aliasf = NULL;
+ if (aliasflen) {
+ free(aliasflen);
+ aliasflen = NULL;
+ }
+ }
+ if (aliasm) {
+ for (int j = 0; j < (numaliasm); j++) free(aliasm[j]);
+ free(aliasm);
+ aliasm = NULL;
+ }
+
+#ifndef OPENOFFICEORG
+#ifndef MOZILLA_CLIENT
+ if (utf8) free_utf_tbl();
+#endif
+#endif
+
+ if (enc) free(enc);
+ if (lang) free(lang);
+
+ if (ignorechars) free(ignorechars);
+ if (ignorechars_utf16) free(ignorechars_utf16);
+
+#ifdef MOZILLA_CLIENT
+ delete [] csconv;
+#endif
+}
+
+// lookup a root word in the hashtable
+
+struct hentry * HashMgr::lookup(const char *word) const
+{
+ struct hentry * dp;
+ if (tableptr) {
+ dp = tableptr[hash(word)];
+ if (!dp) return NULL;
+ for ( ; dp != NULL; dp = dp->next) {
+ if (strcmp(word, dp->word) == 0) return dp;
+ }
+ }
+ return NULL;
+}
+
+// add a word to the hash table (private)
+int HashMgr::add_word(const char * word, int wbl, int wcl, unsigned short * aff,
+ int al, const char * desc, bool onlyupcase)
+{
+ bool upcasehomonym = false;
+ int descl = desc ? (aliasm ? sizeof(short) : strlen(desc) + 1) : 0;
+ // variable-length hash record with word and optional fields
+ struct hentry* hp =
+ (struct hentry *) malloc (sizeof(struct hentry) + wbl + descl);
+ if (!hp) return 1;
+ char * hpw = hp->word;
+ strcpy(hpw, word);
+ if (ignorechars != NULL) {
+ if (utf8) {
+ remove_ignored_chars_utf(hpw, ignorechars_utf16, ignorechars_utf16_len);
+ } else {
+ remove_ignored_chars(hpw, ignorechars);
+ }
+ }
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(hpw); else reverseword(hpw);
+ }
+
+ int i = hash(hpw);
+
+ hp->blen = (unsigned char) wbl;
+ hp->clen = (unsigned char) wcl;
+ hp->alen = (short) al;
+ hp->astr = aff;
+ hp->next = NULL;
+ hp->next_homonym = NULL;
+
+ // store the description string or its pointer
+ if (desc) {
+ hp->var = H_OPT;
+ if (aliasm) {
+ hp->var += H_OPT_ALIASM;
+ store_pointer(hpw + wbl + 1, get_aliasm(atoi(desc)));
+ } else {
+ strcpy(hpw + wbl + 1, desc);
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(HENTRY_DATA(hp));
+ else reverseword(HENTRY_DATA(hp));
+ }
+ }
+ if (strstr(HENTRY_DATA(hp), MORPH_PHON)) hp->var += H_OPT_PHON;
+ } else hp->var = 0;
+
+ struct hentry * dp = tableptr[i];
+ if (!dp) {
+ tableptr[i] = hp;
+ return 0;
+ }
+ while (dp->next != NULL) {
+ if ((!dp->next_homonym) && (strcmp(hp->word, dp->word) == 0)) {
+ // remove hidden onlyupcase homonym
+ if (!onlyupcase) {
+ if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) {
+ free(dp->astr);
+ dp->astr = hp->astr;
+ dp->alen = hp->alen;
+ free(hp);
+ return 0;
+ } else {
+ dp->next_homonym = hp;
+ }
+ } else {
+ upcasehomonym = true;
+ }
+ }
+ dp=dp->next;
+ }
+ if (strcmp(hp->word, dp->word) == 0) {
+ // remove hidden onlyupcase homonym
+ if (!onlyupcase) {
+ if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) {
+ free(dp->astr);
+ dp->astr = hp->astr;
+ dp->alen = hp->alen;
+ free(hp);
+ return 0;
+ } else {
+ dp->next_homonym = hp;
+ }
+ } else {
+ upcasehomonym = true;
+ }
+ }
+ if (!upcasehomonym) {
+ dp->next = hp;
+ } else {
+ // remove hidden onlyupcase homonym
+ if (hp->astr) free(hp->astr);
+ free(hp);
+ }
+ return 0;
+}
+
+int HashMgr::add_hidden_capitalized_word(char * word, int wbl, int wcl,
+ unsigned short * flags, int al, char * dp, int captype)
+{
+ // add inner capitalized forms to handle the following allcap forms:
+ // Mixed caps: OpenOffice.org -> OPENOFFICE.ORG
+ // Allcaps with suffixes: CIA's -> CIA'S
+ if (((captype == HUHCAP) || (captype == HUHINITCAP) ||
+ ((captype == ALLCAP) && (flags != NULL))) &&
+ !((flags != NULL) && TESTAFF(flags, forbiddenword, al))) {
+ unsigned short * flags2 = (unsigned short *) malloc (sizeof(unsigned short) * (al+1));
+ if (!flags2) return 1;
+ if (al) memcpy(flags2, flags, al * sizeof(unsigned short));
+ flags2[al] = ONLYUPCASEFLAG;
+ if (utf8) {
+ char st[BUFSIZE];
+ w_char w[BUFSIZE];
+ int wlen = u8_u16(w, BUFSIZE, word);
+ mkallsmall_utf(w, wlen, langnum);
+ mkallcap_utf(w, 1, langnum);
+ u16_u8(st, BUFSIZE, w, wlen);
+ return add_word(st,wbl,wcl,flags2,al+1,dp, true);
+ } else {
+ mkallsmall(word, csconv);
+ mkinitcap(word, csconv);
+ return add_word(word,wbl,wcl,flags2,al+1,dp, true);
+ }
+ }
+ return 0;
+}
+
+// detect captype and modify word length for UTF-8 encoding
+int HashMgr::get_clen_and_captype(const char * word, int wbl, int * captype) {
+ int len;
+ if (utf8) {
+ w_char dest_utf[BUFSIZE];
+ len = u8_u16(dest_utf, BUFSIZE, word);
+ *captype = get_captype_utf8(dest_utf, len, langnum);
+ } else {
+ len = wbl;
+ *captype = get_captype((char *) word, len, csconv);
+ }
+ return len;
+}
+
+// remove word (personal dictionary function for standalone applications)
+int HashMgr::remove(const char * word)
+{
+ struct hentry * dp = lookup(word);
+ while (dp) {
+ if (dp->alen == 0 || !TESTAFF(dp->astr, forbiddenword, dp->alen)) {
+ unsigned short * flags =
+ (unsigned short *) malloc(sizeof(short) * (dp->alen + 1));
+ if (!flags) return 1;
+ for (int i = 0; i < dp->alen; i++) flags[i] = dp->astr[i];
+ flags[dp->alen] = forbiddenword;
+ dp->astr = flags;
+ dp->alen++;
+ flag_qsort(flags, 0, dp->alen);
+ }
+ dp = dp->next_homonym;
+ }
+ return 0;
+}
+
+/* remove forbidden flag to add a personal word to the hash */
+int HashMgr::remove_forbidden_flag(const char * word) {
+ struct hentry * dp = lookup(word);
+ if (!dp) return 1;
+ while (dp) {
+ if (dp->astr && TESTAFF(dp->astr, forbiddenword, dp->alen)) {
+ if (dp->alen == 1) dp->alen = 0; // XXX forbidden words of personal dic.
+ else {
+ unsigned short * flags2 =
+ (unsigned short *) malloc(sizeof(short) * (dp->alen - 1));
+ if (!flags2) return 1;
+ int i, j = 0;
+ for (i = 0; i < dp->alen; i++) {
+ if (dp->astr[i] != forbiddenword) flags2[j++] = dp->astr[i];
+ }
+ dp->alen--;
+ dp->astr = flags2; // XXX allowed forbidden words
+ }
+ }
+ dp = dp->next_homonym;
+ }
+ return 0;
+}
+
+// add a custom dic. word to the hash table (public)
+int HashMgr::add(const char * word)
+{
+ unsigned short * flags = NULL;
+ int al = 0;
+ if (remove_forbidden_flag(word)) {
+ int captype;
+ int wbl = strlen(word);
+ int wcl = get_clen_and_captype(word, wbl, &captype);
+ add_word(word, wbl, wcl, flags, al, NULL, false);
+ return add_hidden_capitalized_word((char *) word, wbl, wcl, flags, al, NULL, captype);
+ }
+ return 0;
+}
+
+int HashMgr::add_with_affix(const char * word, const char * example)
+{
+ // detect captype and modify word length for UTF-8 encoding
+ struct hentry * dp = lookup(example);
+ remove_forbidden_flag(word);
+ if (dp && dp->astr) {
+ int captype;
+ int wbl = strlen(word);
+ int wcl = get_clen_and_captype(word, wbl, &captype);
+ if (aliasf) {
+ add_word(word, wbl, wcl, dp->astr, dp->alen, NULL, false);
+ } else {
+ unsigned short * flags = (unsigned short *) malloc (dp->alen * sizeof(short));
+ if (flags) {
+ memcpy((void *) flags, (void *) dp->astr, dp->alen * sizeof(short));
+ add_word(word, wbl, wcl, flags, dp->alen, NULL, false);
+ } else return 1;
+ }
+ return add_hidden_capitalized_word((char *) word, wbl, wcl, dp->astr, dp->alen, NULL, captype);
+ }
+ return 1;
+}
+
+// walk the hash table entry by entry - null at end
+// initialize: col=-1; hp = NULL; hp = walk_hashtable(&col, hp);
+struct hentry * HashMgr::walk_hashtable(int &col, struct hentry * hp) const
+{
+ if (hp && hp->next != NULL) return hp->next;
+ for (col++; col < tablesize; col++) {
+ if (tableptr[col]) return tableptr[col];
+ }
+ // null at end and reset to start
+ col = -1;
+ return NULL;
+}
+
+// load a munched word list and build a hash table on the fly
+int HashMgr::load_tables(const char * tpath, const char * key)
+{
+ int al;
+ char * ap;
+ char * dp;
+ char * dp2;
+ unsigned short * flags;
+ char * ts;
+
+ // open dictionary file
+ FileMgr * dict = new FileMgr(tpath, key);
+ if (dict == NULL) return 1;
+
+ // first read the first line of file to get hash table size */
+ if (!(ts = dict->getline())) {
+ HUNSPELL_WARNING(stderr, "error: empty dic file\n");
+ delete dict;
+ return 2;
+ }
+ mychomp(ts);
+
+ /* remove byte order mark */
+ if (strncmp(ts,"\xEF\xBB\xBF",3) == 0) {
+ memmove(ts, ts+3, strlen(ts+3)+1);
+ // warning: dic file begins with byte order mark: possible incompatibility with old Hunspell versions
+ }
+
+ tablesize = atoi(ts);
+ if (tablesize == 0) {
+ HUNSPELL_WARNING(stderr, "error: line 1: missing or bad word count in the dic file\n");
+ delete dict;
+ return 4;
+ }
+ tablesize = tablesize + 5 + USERWORD;
+ if ((tablesize %2) == 0) tablesize++;
+
+ // allocate the hash table
+ tableptr = (struct hentry **) malloc(tablesize * sizeof(struct hentry *));
+ if (! tableptr) {
+ delete dict;
+ return 3;
+ }
+ for (int i=0; i<tablesize; i++) tableptr[i] = NULL;
+
+ // loop through all words on much list and add to hash
+ // table and create word and affix strings
+
+ while ((ts = dict->getline())) {
+ mychomp(ts);
+ // split each line into word and morphological description
+ dp = ts;
+ while ((dp = strchr(dp, ':'))) {
+ if ((dp > ts + 3) && (*(dp - 3) == ' ' || *(dp - 3) == '\t')) {
+ for (dp -= 4; dp >= ts && (*dp == ' ' || *dp == '\t'); dp--);
+ if (dp < ts) { // missing word
+ dp = NULL;
+ } else {
+ *(dp + 1) = '\0';
+ dp = dp + 2;
+ }
+ break;
+ }
+ dp++;
+ }
+
+ // tabulator is the old morphological field separator
+ dp2 = strchr(ts, '\t');
+ if (dp2 && (!dp || dp2 < dp)) {
+ *dp2 = '\0';
+ dp = dp2 + 1;
+ }
+
+ // split each line into word and affix char strings
+ // "\/" signs slash in words (not affix separator)
+ // "/" at beginning of the line is word character (not affix separator)
+ ap = strchr(ts,'/');
+ while (ap) {
+ if (ap == ts) {
+ ap++;
+ continue;
+ } else if (*(ap - 1) != '\\') break;
+ // replace "\/" with "/"
+ for (char * sp = ap - 1; *sp; *sp = *(sp + 1), sp++);
+ ap = strchr(ap,'/');
+ }
+
+ if (ap) {
+ *ap = '\0';
+ if (aliasf) {
+ int index = atoi(ap + 1);
+ al = get_aliasf(index, &flags, dict);
+ if (!al) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad flag vector alias\n", dict->getlinenum());
+ *ap = '\0';
+ }
+ } else {
+ al = decode_flags(&flags, ap + 1, dict);
+ if (al == -1) {
+ HUNSPELL_WARNING(stderr, "Can't allocate memory.\n");
+ delete dict;
+ return 6;
+ }
+ flag_qsort(flags, 0, al);
+ }
+ } else {
+ al = 0;
+ ap = NULL;
+ flags = NULL;
+ }
+
+ int captype;
+ int wbl = strlen(ts);
+ int wcl = get_clen_and_captype(ts, wbl, &captype);
+ // add the word and its index plus its capitalized form optionally
+ if (add_word(ts,wbl,wcl,flags,al,dp, false) ||
+ add_hidden_capitalized_word(ts, wbl, wcl, flags, al, dp, captype)) {
+ delete dict;
+ return 5;
+ }
+ }
+
+ delete dict;
+ return 0;
+}
+
+// the hash function is a simple load and rotate
+// algorithm borrowed
+
+int HashMgr::hash(const char * word) const
+{
+ long hv = 0;
+ for (int i=0; i < 4 && *word != 0; i++)
+ hv = (hv << 8) | (*word++);
+ while (*word != 0) {
+ ROTATE(hv,ROTATE_LEN);
+ hv ^= (*word++);
+ }
+ return (unsigned long) hv % tablesize;
+}
+
+int HashMgr::decode_flags(unsigned short ** result, char * flags, FileMgr * af) {
+ int len;
+ if (*flags == '\0') {
+ *result = NULL;
+ return 0;
+ }
+ switch (flag_mode) {
+ case FLAG_LONG: { // two-character flags (1x2yZz -> 1x 2y Zz)
+ len = strlen(flags);
+ if (len%2 == 1) HUNSPELL_WARNING(stderr, "error: line %d: bad flagvector\n", af->getlinenum());
+ len /= 2;
+ *result = (unsigned short *) malloc(len * sizeof(short));
+ if (!*result) return -1;
+ for (int i = 0; i < len; i++) {
+ (*result)[i] = (((unsigned short) flags[i * 2]) << 8) + (unsigned short) flags[i * 2 + 1];
+ }
+ break;
+ }
+ case FLAG_NUM: { // decimal numbers separated by comma (4521,23,233 -> 4521 23 233)
+ int i;
+ len = 1;
+ char * src = flags;
+ unsigned short * dest;
+ char * p;
+ for (p = flags; *p; p++) {
+ if (*p == ',') len++;
+ }
+ *result = (unsigned short *) malloc(len * sizeof(short));
+ if (!*result) return -1;
+ dest = *result;
+ for (p = flags; *p; p++) {
+ if (*p == ',') {
+ i = atoi(src);
+ if (i >= DEFAULTFLAGS) HUNSPELL_WARNING(stderr, "error: line %d: flag id %d is too large (max: %d)\n",
+ af->getlinenum(), i, DEFAULTFLAGS - 1);
+ *dest = (unsigned short) i;
+ if (*dest == 0) HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", af->getlinenum());
+ src = p + 1;
+ dest++;
+ }
+ }
+ i = atoi(src);
+ if (i >= DEFAULTFLAGS) HUNSPELL_WARNING(stderr, "error: line %d: flag id %d is too large (max: %d)\n",
+ af->getlinenum(), i, DEFAULTFLAGS - 1);
+ *dest = (unsigned short) i;
+ if (*dest == 0) HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n", af->getlinenum());
+ break;
+ }
+ case FLAG_UNI: { // UTF-8 characters
+ w_char w[BUFSIZE/2];
+ len = u8_u16(w, BUFSIZE/2, flags);
+ *result = (unsigned short *) malloc(len * sizeof(short));
+ if (!*result) return -1;
+ memcpy(*result, w, len * sizeof(short));
+ break;
+ }
+ default: { // Ispell's one-character flags (erfg -> e r f g)
+ unsigned short * dest;
+ len = strlen(flags);
+ *result = (unsigned short *) malloc(len * sizeof(short));
+ if (!*result) return -1;
+ dest = *result;
+ for (unsigned char * p = (unsigned char *) flags; *p; p++) {
+ *dest = (unsigned short) *p;
+ dest++;
+ }
+ }
+ }
+ return len;
+}
+
+unsigned short HashMgr::decode_flag(const char * f) {
+ unsigned short s = 0;
+ int i;
+ switch (flag_mode) {
+ case FLAG_LONG:
+ s = ((unsigned short) f[0] << 8) + (unsigned short) f[1];
+ break;
+ case FLAG_NUM:
+ i = atoi(f);
+ if (i >= DEFAULTFLAGS) HUNSPELL_WARNING(stderr, "error: flag id %d is too large (max: %d)\n", i, DEFAULTFLAGS - 1);
+ s = (unsigned short) i;
+ break;
+ case FLAG_UNI:
+ u8_u16((w_char *) &s, 1, f);
+ break;
+ default:
+ s = (unsigned short) *((unsigned char *)f);
+ }
+ if (s == 0) HUNSPELL_WARNING(stderr, "error: 0 is wrong flag id\n");
+ return s;
+}
+
+char * HashMgr::encode_flag(unsigned short f) {
+ unsigned char ch[10];
+ if (f==0) return mystrdup("(NULL)");
+ if (flag_mode == FLAG_LONG) {
+ ch[0] = (unsigned char) (f >> 8);
+ ch[1] = (unsigned char) (f - ((f >> 8) << 8));
+ ch[2] = '\0';
+ } else if (flag_mode == FLAG_NUM) {
+ sprintf((char *) ch, "%d", f);
+ } else if (flag_mode == FLAG_UNI) {
+ u16_u8((char *) &ch, 10, (w_char *) &f, 1);
+ } else {
+ ch[0] = (unsigned char) (f);
+ ch[1] = '\0';
+ }
+ return mystrdup((char *) ch);
+}
+
+// read in aff file and set flag mode
+int HashMgr::load_config(const char * affpath, const char * key)
+{
+ char * line; // io buffers
+ int firstline = 1;
+
+ // open the affix file
+ FileMgr * afflst = new FileMgr(affpath, key);
+ if (!afflst) {
+ HUNSPELL_WARNING(stderr, "Error - could not open affix description file %s\n",affpath);
+ return 1;
+ }
+
+ // read in each line ignoring any that do not
+ // start with a known line type indicator
+
+ while ((line = afflst->getline())) {
+ mychomp(line);
+
+ /* remove byte order mark */
+ if (firstline) {
+ firstline = 0;
+ if (strncmp(line,"\xEF\xBB\xBF",3) == 0) memmove(line, line+3, strlen(line+3)+1);
+ }
+
+ /* parse in the try string */
+ if ((strncmp(line,"FLAG",4) == 0) && isspace(line[4])) {
+ if (flag_mode != FLAG_CHAR) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple definitions of the FLAG affix file parameter\n", afflst->getlinenum());
+ }
+ if (strstr(line, "long")) flag_mode = FLAG_LONG;
+ if (strstr(line, "num")) flag_mode = FLAG_NUM;
+ if (strstr(line, "UTF-8")) flag_mode = FLAG_UNI;
+ if (flag_mode == FLAG_CHAR) {
+ HUNSPELL_WARNING(stderr, "error: line %d: FLAG needs `num', `long' or `UTF-8' parameter\n", afflst->getlinenum());
+ }
+ }
+ if (strncmp(line,"FORBIDDENWORD",13) == 0) {
+ char * st = NULL;
+ if (parse_string(line, &st, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ forbiddenword = decode_flag(st);
+ free(st);
+ }
+ if (strncmp(line, "SET", 3) == 0) {
+ if (parse_string(line, &enc, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ if (strcmp(enc, "UTF-8") == 0) {
+ utf8 = 1;
+#ifndef OPENOFFICEORG
+#ifndef MOZILLA_CLIENT
+ initialize_utf_tbl();
+#endif
+#endif
+ } else csconv = get_current_cs(enc);
+ }
+ if (strncmp(line, "LANG", 4) == 0) {
+ if (parse_string(line, &lang, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ langnum = get_lang_num(lang);
+ }
+
+ /* parse in the ignored characters (for example, Arabic optional diacritics characters */
+ if (strncmp(line,"IGNORE",6) == 0) {
+ if (parse_array(line, &ignorechars, &ignorechars_utf16,
+ &ignorechars_utf16_len, utf8, afflst->getlinenum())) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if ((strncmp(line,"AF",2) == 0) && isspace(line[2])) {
+ if (parse_aliasf(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if ((strncmp(line,"AM",2) == 0) && isspace(line[2])) {
+ if (parse_aliasm(line, afflst)) {
+ delete afflst;
+ return 1;
+ }
+ }
+
+ if (strncmp(line,"COMPLEXPREFIXES",15) == 0) complexprefixes = 1;
+ if (((strncmp(line,"SFX",3) == 0) || (strncmp(line,"PFX",3) == 0)) && isspace(line[3])) break;
+ }
+ if (csconv == NULL) csconv = get_current_cs(SPELL_ENCODING);
+ delete afflst;
+ return 0;
+}
+
+/* parse in the ALIAS table */
+int HashMgr::parse_aliasf(char * line, FileMgr * af)
+{
+ if (numaliasf != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numaliasf = atoi(piece);
+ if (numaliasf < 1) {
+ numaliasf = 0;
+ aliasf = NULL;
+ aliasflen = NULL;
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ aliasf = (unsigned short **) malloc(numaliasf * sizeof(unsigned short *));
+ aliasflen = (unsigned short *) malloc(numaliasf * sizeof(short));
+ if (!aliasf || !aliasflen) {
+ numaliasf = 0;
+ if (aliasf) free(aliasf);
+ if (aliasflen) free(aliasflen);
+ aliasf = NULL;
+ aliasflen = NULL;
+ return 1;
+ }
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ numaliasf = 0;
+ free(aliasf);
+ free(aliasflen);
+ aliasf = NULL;
+ aliasflen = NULL;
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numaliasf lines to read in the remainder of the table */
+ char * nl;
+ for (int j=0; j < numaliasf; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ aliasf[j] = NULL;
+ aliasflen[j] = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"AF",2) != 0) {
+ numaliasf = 0;
+ free(aliasf);
+ free(aliasflen);
+ aliasf = NULL;
+ aliasflen = NULL;
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ aliasflen[j] = (unsigned short) decode_flags(&(aliasf[j]), piece, af);
+ flag_qsort(aliasf[j], 0, aliasflen[j]);
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (!aliasf[j]) {
+ free(aliasf);
+ free(aliasflen);
+ aliasf = NULL;
+ aliasflen = NULL;
+ numaliasf = 0;
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int HashMgr::is_aliasf() {
+ return (aliasf != NULL);
+}
+
+int HashMgr::get_aliasf(int index, unsigned short ** fvec, FileMgr * af) {
+ if ((index > 0) && (index <= numaliasf)) {
+ *fvec = aliasf[index - 1];
+ return aliasflen[index - 1];
+ }
+ HUNSPELL_WARNING(stderr, "error: line %d: bad flag alias index: %d\n", af->getlinenum(), index);
+ *fvec = NULL;
+ return 0;
+}
+
+/* parse morph alias definitions */
+int HashMgr::parse_aliasm(char * line, FileMgr * af)
+{
+ if (numaliasm != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: multiple table definitions\n", af->getlinenum());
+ return 1;
+ }
+ char * tp = line;
+ char * piece;
+ int i = 0;
+ int np = 0;
+ piece = mystrsep(&tp, 0);
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: { np++; break; }
+ case 1: {
+ numaliasm = atoi(piece);
+ if (numaliasm < 1) {
+ HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n", af->getlinenum());
+ return 1;
+ }
+ aliasm = (char **) malloc(numaliasm * sizeof(char *));
+ if (!aliasm) {
+ numaliasm = 0;
+ return 1;
+ }
+ np++;
+ break;
+ }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, 0);
+ }
+ if (np != 2) {
+ numaliasm = 0;
+ free(aliasm);
+ aliasm = NULL;
+ HUNSPELL_WARNING(stderr, "error: line %d: missing data\n", af->getlinenum());
+ return 1;
+ }
+
+ /* now parse the numaliasm lines to read in the remainder of the table */
+ char * nl = line;
+ for (int j=0; j < numaliasm; j++) {
+ if (!(nl = af->getline())) return 1;
+ mychomp(nl);
+ tp = nl;
+ i = 0;
+ aliasm[j] = NULL;
+ piece = mystrsep(&tp, ' ');
+ while (piece) {
+ if (*piece != '\0') {
+ switch(i) {
+ case 0: {
+ if (strncmp(piece,"AM",2) != 0) {
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ numaliasm = 0;
+ free(aliasm);
+ aliasm = NULL;
+ return 1;
+ }
+ break;
+ }
+ case 1: {
+ // add the remaining of the line
+ if (*tp) {
+ *(tp - 1) = ' ';
+ tp = tp + strlen(tp);
+ }
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(piece);
+ else reverseword(piece);
+ }
+ aliasm[j] = mystrdup(piece);
+ if (!aliasm[j]) {
+ numaliasm = 0;
+ free(aliasm);
+ aliasm = NULL;
+ return 1;
+ }
+ break; }
+ default: break;
+ }
+ i++;
+ }
+ piece = mystrsep(&tp, ' ');
+ }
+ if (!aliasm[j]) {
+ numaliasm = 0;
+ free(aliasm);
+ aliasm = NULL;
+ HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n", af->getlinenum());
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int HashMgr::is_aliasm() {
+ return (aliasm != NULL);
+}
+
+char * HashMgr::get_aliasm(int index) {
+ if ((index > 0) && (index <= numaliasm)) return aliasm[index - 1];
+ HUNSPELL_WARNING(stderr, "error: bad morph. alias index: %d\n", index);
+ return NULL;
+}
diff --git a/src/cpp/core/spelling/hunspell/hashmgr.hxx b/src/cpp/core/spelling/hunspell/hashmgr.hxx
new file mode 100644
index 0000000..341b081
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hashmgr.hxx
@@ -0,0 +1,69 @@
+#ifndef _HASHMGR_HXX_
+#define _HASHMGR_HXX_
+
+#include "hunvisapi.h"
+
+#include <stdio.h>
+
+#include "htypes.hxx"
+#include "filemgr.hxx"
+
+enum flag { FLAG_CHAR, FLAG_LONG, FLAG_NUM, FLAG_UNI };
+
+class LIBHUNSPELL_DLL_EXPORTED HashMgr
+{
+ int tablesize;
+ struct hentry ** tableptr;
+ int userword;
+ flag flag_mode;
+ int complexprefixes;
+ int utf8;
+ unsigned short forbiddenword;
+ int langnum;
+ char * enc;
+ char * lang;
+ struct cs_info * csconv;
+ char * ignorechars;
+ unsigned short * ignorechars_utf16;
+ int ignorechars_utf16_len;
+ int numaliasf; // flag vector `compression' with aliases
+ unsigned short ** aliasf;
+ unsigned short * aliasflen;
+ int numaliasm; // morphological desciption `compression' with aliases
+ char ** aliasm;
+
+
+public:
+ HashMgr(const char * tpath, const char * apath, const char * key = NULL);
+ ~HashMgr();
+
+ struct hentry * lookup(const char *) const;
+ int hash(const char *) const;
+ struct hentry * walk_hashtable(int & col, struct hentry * hp) const;
+
+ int add(const char * word);
+ int add_with_affix(const char * word, const char * pattern);
+ int remove(const char * word);
+ int decode_flags(unsigned short ** result, char * flags, FileMgr * af);
+ unsigned short decode_flag(const char * flag);
+ char * encode_flag(unsigned short flag);
+ int is_aliasf();
+ int get_aliasf(int index, unsigned short ** fvec, FileMgr * af);
+ int is_aliasm();
+ char * get_aliasm(int index);
+
+private:
+ int get_clen_and_captype(const char * word, int wbl, int * captype);
+ int load_tables(const char * tpath, const char * key);
+ int add_word(const char * word, int wbl, int wcl, unsigned short * ap,
+ int al, const char * desc, bool onlyupcase);
+ int load_config(const char * affpath, const char * key);
+ int parse_aliasf(char * line, FileMgr * af);
+ int add_hidden_capitalized_word(char * word, int wbl, int wcl,
+ unsigned short * flags, int al, char * dp, int captype);
+ int parse_aliasm(char * line, FileMgr * af);
+ int remove_forbidden_flag(const char * word);
+
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/htypes.hxx b/src/cpp/core/spelling/hunspell/htypes.hxx
new file mode 100644
index 0000000..5b6c909
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/htypes.hxx
@@ -0,0 +1,32 @@
+#ifndef _HTYPES_HXX_
+#define _HTYPES_HXX_
+
+#define ROTATE_LEN 5
+
+#define ROTATE(v,q) \
+ (v) = ((v) << (q)) | (((v) >> (32 - q)) & ((1 << (q))-1));
+
+// hentry options
+#define H_OPT (1 << 0)
+#define H_OPT_ALIASM (1 << 1)
+#define H_OPT_PHON (1 << 2)
+
+// see also csutil.hxx
+#define HENTRY_WORD(h) &(h->word[0])
+
+// approx. number of user defined words
+#define USERWORD 1000
+
+struct hentry
+{
+ unsigned char blen; // word length in bytes
+ unsigned char clen; // word length in characters (different for UTF-8 enc.)
+ short alen; // length of affix flag vector
+ unsigned short * astr; // affix flag vector
+ struct hentry * next; // next word with same hash code
+ struct hentry * next_homonym; // next homonym word (with same hash code)
+ char var; // variable fields (only for special pronounciation yet)
+ char word[1]; // variable-length word (8-bit or UTF-8 encoding)
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/hunspell.cxx b/src/cpp/core/spelling/hunspell/hunspell.cxx
new file mode 100644
index 0000000..a9b261a
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunspell.cxx
@@ -0,0 +1,2006 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "hunspell.hxx"
+#include "hunspell.h"
+#ifndef MOZILLA_CLIENT
+# include "config.h"
+#endif
+#include "csutil.hxx"
+
+Hunspell::Hunspell(const char * affpath, const char * dpath, const char * key)
+{
+ encoding = NULL;
+ csconv = NULL;
+ utf8 = 0;
+ complexprefixes = 0;
+ affixpath = mystrdup(affpath);
+ maxdic = 0;
+
+ /* first set up the hash manager */
+ pHMgr[0] = new HashMgr(dpath, affpath, key);
+ if (pHMgr[0]) maxdic = 1;
+
+ /* next set up the affix manager */
+ /* it needs access to the hash manager lookup methods */
+ pAMgr = new AffixMgr(affpath, pHMgr, &maxdic, key);
+
+ /* get the preferred try string and the dictionary */
+ /* encoding from the Affix Manager for that dictionary */
+ char * try_string = pAMgr->get_try_string();
+ encoding = pAMgr->get_encoding();
+ langnum = pAMgr->get_langnum();
+ utf8 = pAMgr->get_utf8();
+ if (!utf8)
+ csconv = get_current_cs(encoding);
+ complexprefixes = pAMgr->get_complexprefixes();
+ wordbreak = pAMgr->get_breaktable();
+
+ /* and finally set up the suggestion manager */
+ pSMgr = new SuggestMgr(try_string, MAXSUGGESTION, pAMgr);
+ if (try_string) free(try_string);
+}
+
+Hunspell::~Hunspell()
+{
+ if (pSMgr) delete pSMgr;
+ if (pAMgr) delete pAMgr;
+ for (int i = 0; i < maxdic; i++) delete pHMgr[i];
+ maxdic = 0;
+ pSMgr = NULL;
+ pAMgr = NULL;
+#ifdef MOZILLA_CLIENT
+ delete [] csconv;
+#endif
+ csconv= NULL;
+ if (encoding) free(encoding);
+ encoding = NULL;
+ if (affixpath) free(affixpath);
+ affixpath = NULL;
+}
+
+// load extra dictionaries
+int Hunspell::add_dic(const char * dpath, const char * key) {
+ if (maxdic == MAXDIC || !affixpath) return 1;
+ pHMgr[maxdic] = new HashMgr(dpath, affixpath, key);
+ if (pHMgr[maxdic]) maxdic++; else return 1;
+ return 0;
+}
+
+// make a copy of src at destination while removing all leading
+// blanks and removing any trailing periods after recording
+// their presence with the abbreviation flag
+// also since already going through character by character,
+// set the capitalization type
+// return the length of the "cleaned" (and UTF-8 encoded) word
+
+int Hunspell::cleanword2(char * dest, const char * src,
+ w_char * dest_utf, int * nc, int * pcaptype, int * pabbrev)
+{
+ unsigned char * p = (unsigned char *) dest;
+ const unsigned char * q = (const unsigned char * ) src;
+
+ // first skip over any leading blanks
+ while ((*q != '\0') && (*q == ' ')) q++;
+
+ // now strip off any trailing periods (recording their presence)
+ *pabbrev = 0;
+ int nl = strlen((const char *)q);
+ while ((nl > 0) && (*(q+nl-1)=='.')) {
+ nl--;
+ (*pabbrev)++;
+ }
+
+ // if no characters are left it can't be capitalized
+ if (nl <= 0) {
+ *pcaptype = NOCAP;
+ *p = '\0';
+ return 0;
+ }
+
+ strncpy(dest, (char *) q, nl);
+ *(dest + nl) = '\0';
+ nl = strlen(dest);
+ if (utf8) {
+ *nc = u8_u16(dest_utf, MAXWORDLEN, dest);
+ // don't check too long words
+ if (*nc >= MAXWORDLEN) return 0;
+ if (*nc == -1) { // big Unicode character (non BMP area)
+ *pcaptype = NOCAP;
+ return nl;
+ }
+ *pcaptype = get_captype_utf8(dest_utf, *nc, langnum);
+ } else {
+ *pcaptype = get_captype(dest, nl, csconv);
+ *nc = nl;
+ }
+ return nl;
+}
+
+int Hunspell::cleanword(char * dest, const char * src,
+ int * pcaptype, int * pabbrev)
+{
+ unsigned char * p = (unsigned char *) dest;
+ const unsigned char * q = (const unsigned char * ) src;
+ int firstcap = 0;
+
+ // first skip over any leading blanks
+ while ((*q != '\0') && (*q == ' ')) q++;
+
+ // now strip off any trailing periods (recording their presence)
+ *pabbrev = 0;
+ int nl = strlen((const char *)q);
+ while ((nl > 0) && (*(q+nl-1)=='.')) {
+ nl--;
+ (*pabbrev)++;
+ }
+
+ // if no characters are left it can't be capitalized
+ if (nl <= 0) {
+ *pcaptype = NOCAP;
+ *p = '\0';
+ return 0;
+ }
+
+ // now determine the capitalization type of the first nl letters
+ int ncap = 0;
+ int nneutral = 0;
+ int nc = 0;
+
+ if (!utf8) {
+ while (nl > 0) {
+ nc++;
+ if (csconv[(*q)].ccase) ncap++;
+ if (csconv[(*q)].cupper == csconv[(*q)].clower) nneutral++;
+ *p++ = *q++;
+ nl--;
+ }
+ // remember to terminate the destination string
+ *p = '\0';
+ firstcap = csconv[(unsigned char)(*dest)].ccase;
+ } else {
+ unsigned short idx;
+ w_char t[MAXWORDLEN];
+ nc = u8_u16(t, MAXWORDLEN, src);
+ for (int i = 0; i < nc; i++) {
+ idx = (t[i].h << 8) + t[i].l;
+ unsigned short low = unicodetolower(idx, langnum);
+ if (idx != low) ncap++;
+ if (unicodetoupper(idx, langnum) == low) nneutral++;
+ }
+ u16_u8(dest, MAXWORDUTF8LEN, t, nc);
+ if (ncap) {
+ idx = (t[0].h << 8) + t[0].l;
+ firstcap = (idx != unicodetolower(idx, langnum));
+ }
+ }
+
+ // now finally set the captype
+ if (ncap == 0) {
+ *pcaptype = NOCAP;
+ } else if ((ncap == 1) && firstcap) {
+ *pcaptype = INITCAP;
+ } else if ((ncap == nc) || ((ncap + nneutral) == nc)){
+ *pcaptype = ALLCAP;
+ } else if ((ncap > 1) && firstcap) {
+ *pcaptype = HUHINITCAP;
+ } else {
+ *pcaptype = HUHCAP;
+ }
+ return strlen(dest);
+}
+
+void Hunspell::mkallcap(char * p)
+{
+ if (utf8) {
+ w_char u[MAXWORDLEN];
+ int nc = u8_u16(u, MAXWORDLEN, p);
+ unsigned short idx;
+ for (int i = 0; i < nc; i++) {
+ idx = (u[i].h << 8) + u[i].l;
+ if (idx != unicodetoupper(idx, langnum)) {
+ u[i].h = (unsigned char) (unicodetoupper(idx, langnum) >> 8);
+ u[i].l = (unsigned char) (unicodetoupper(idx, langnum) & 0x00FF);
+ }
+ }
+ u16_u8(p, MAXWORDUTF8LEN, u, nc);
+ } else {
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].cupper;
+ p++;
+ }
+ }
+}
+
+int Hunspell::mkallcap2(char * p, w_char * u, int nc)
+{
+ if (utf8) {
+ unsigned short idx;
+ for (int i = 0; i < nc; i++) {
+ idx = (u[i].h << 8) + u[i].l;
+ unsigned short up = unicodetoupper(idx, langnum);
+ if (idx != up) {
+ u[i].h = (unsigned char) (up >> 8);
+ u[i].l = (unsigned char) (up & 0x00FF);
+ }
+ }
+ u16_u8(p, MAXWORDUTF8LEN, u, nc);
+ return strlen(p);
+ } else {
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].cupper;
+ p++;
+ }
+ }
+ return nc;
+}
+
+
+void Hunspell::mkallsmall(char * p)
+{
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].clower;
+ p++;
+ }
+}
+
+int Hunspell::mkallsmall2(char * p, w_char * u, int nc)
+{
+ if (utf8) {
+ unsigned short idx;
+ for (int i = 0; i < nc; i++) {
+ idx = (u[i].h << 8) + u[i].l;
+ unsigned short low = unicodetolower(idx, langnum);
+ if (idx != low) {
+ u[i].h = (unsigned char) (low >> 8);
+ u[i].l = (unsigned char) (low & 0x00FF);
+ }
+ }
+ u16_u8(p, MAXWORDUTF8LEN, u, nc);
+ return strlen(p);
+ } else {
+ while (*p != '\0') {
+ *p = csconv[((unsigned char) *p)].clower;
+ p++;
+ }
+ }
+ return nc;
+}
+
+// convert UTF-8 sharp S codes to latin 1
+char * Hunspell::sharps_u8_l1(char * dest, char * source) {
+ char * p = dest;
+ *p = *source;
+ for (p++, source++; *(source - 1); p++, source++) {
+ *p = *source;
+ if (*source == '\x9F') *--p = '\xDF';
+ }
+ return dest;
+}
+
+// recursive search for right ss - sharp s permutations
+hentry * Hunspell::spellsharps(char * base, char * pos, int n,
+ int repnum, char * tmp, int * info, char **root) {
+ pos = strstr(pos, "ss");
+ if (pos && (n < MAXSHARPS)) {
+ *pos = '\xC3';
+ *(pos + 1) = '\x9F';
+ hentry * h = spellsharps(base, pos + 2, n + 1, repnum + 1, tmp, info, root);
+ if (h) return h;
+ *pos = 's';
+ *(pos + 1) = 's';
+ h = spellsharps(base, pos + 2, n + 1, repnum, tmp, info, root);
+ if (h) return h;
+ } else if (repnum > 0) {
+ if (utf8) return checkword(base, info, root);
+ return checkword(sharps_u8_l1(tmp, base), info, root);
+ }
+ return NULL;
+}
+
+int Hunspell::is_keepcase(const hentry * rv) {
+ return pAMgr && rv->astr && pAMgr->get_keepcase() &&
+ TESTAFF(rv->astr, pAMgr->get_keepcase(), rv->alen);
+}
+
+/* insert a word to the beginning of the suggestion array and return ns */
+int Hunspell::insert_sug(char ***slst, char * word, int ns) {
+ char * dup = mystrdup(word);
+ if (!dup) return ns;
+ if (ns == MAXSUGGESTION) {
+ ns--;
+ free((*slst)[ns]);
+ }
+ for (int k = ns; k > 0; k--) (*slst)[k] = (*slst)[k - 1];
+ (*slst)[0] = dup;
+ return ns + 1;
+}
+
+int Hunspell::spell(const char * word, int * info, char ** root)
+{
+ struct hentry * rv=NULL;
+ // need larger vector. For example, Turkish capital letter I converted a
+ // 2-byte UTF-8 character (dotless i) by mkallsmall.
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ w_char unicw[MAXWORDLEN];
+ // Hunspell supports XML input of the simplified API (see manual)
+ if (strcmp(word, SPELL_XML) == 0) return 1;
+ int nc = strlen(word);
+ int wl2 = 0;
+ if (utf8) {
+ if (nc >= MAXWORDUTF8LEN) return 0;
+ } else {
+ if (nc >= MAXWORDLEN) return 0;
+ }
+ int captype = 0;
+ int abbv = 0;
+ int wl = 0;
+
+ // input conversion
+ RepList * rl = (pAMgr) ? pAMgr->get_iconvtable() : NULL;
+ if (rl && rl->conv(word, wspace)) wl = cleanword2(cw, wspace, unicw, &nc, &captype, &abbv);
+ else wl = cleanword2(cw, word, unicw, &nc, &captype, &abbv);
+
+ int info2 = 0;
+ if (wl == 0 || maxdic == 0) return 1;
+ if (root) *root = NULL;
+
+ // allow numbers with dots, dashes and commas (but forbid double separators: "..", "--" etc.)
+ enum { NBEGIN, NNUM, NSEP };
+ int nstate = NBEGIN;
+ int i;
+
+ for (i = 0; (i < wl); i++) {
+ if ((cw[i] <= '9') && (cw[i] >= '0')) {
+ nstate = NNUM;
+ } else if ((cw[i] == ',') || (cw[i] == '.') || (cw[i] == '-')) {
+ if ((nstate == NSEP) || (i == 0)) break;
+ nstate = NSEP;
+ } else break;
+ }
+ if ((i == wl) && (nstate == NNUM)) return 1;
+ if (!info) info = &info2; else *info = 0;
+
+ switch(captype) {
+ case HUHCAP:
+ case HUHINITCAP:
+ *info += SPELL_ORIGCAP;
+ case NOCAP: {
+ rv = checkword(cw, info, root);
+ if ((abbv) && !(rv)) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ rv = checkword(wspace, info, root);
+ }
+ break;
+ }
+ case ALLCAP: {
+ *info += SPELL_ORIGCAP;
+ rv = checkword(cw, info, root);
+ if (rv) break;
+ if (abbv) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ rv = checkword(wspace, info, root);
+ if (rv) break;
+ }
+ // Spec. prefix handling for Catalan, French, Italian:
+ // prefixes separated by apostrophe (SANT'ELIA -> Sant'+Elia).
+ if (pAMgr && strchr(cw, '\'')) {
+ wl = mkallsmall2(cw, unicw, nc);
+ //There are no really sane circumstances where this could fail,
+ //but anyway...
+ if (char * apostrophe = strchr(cw, '\'')) {
+ if (utf8) {
+ w_char tmpword[MAXWORDLEN];
+ *apostrophe = '\0';
+ wl2 = u8_u16(tmpword, MAXWORDLEN, cw);
+ *apostrophe = '\'';
+ if (wl2 < nc) {
+ mkinitcap2(apostrophe + 1, unicw + wl2 + 1, nc - wl2 - 1);
+ rv = checkword(cw, info, root);
+ if (rv) break;
+ }
+ } else {
+ mkinitcap2(apostrophe + 1, unicw, nc);
+ rv = checkword(cw, info, root);
+ if (rv) break;
+ }
+ }
+ mkinitcap2(cw, unicw, nc);
+ rv = checkword(cw, info, root);
+ if (rv) break;
+ }
+ if (pAMgr && pAMgr->get_checksharps() && strstr(cw, "SS")) {
+ char tmpword[MAXWORDUTF8LEN];
+ wl = mkallsmall2(cw, unicw, nc);
+ memcpy(wspace,cw,(wl+1));
+ rv = spellsharps(wspace, wspace, 0, 0, tmpword, info, root);
+ if (!rv) {
+ wl2 = mkinitcap2(cw, unicw, nc);
+ rv = spellsharps(cw, cw, 0, 0, tmpword, info, root);
+ }
+ if ((abbv) && !(rv)) {
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ rv = spellsharps(wspace, wspace, 0, 0, tmpword, info, root);
+ if (!rv) {
+ memcpy(wspace, cw, wl2);
+ *(wspace+wl2) = '.';
+ *(wspace+wl2+1) = '\0';
+ rv = spellsharps(wspace, wspace, 0, 0, tmpword, info, root);
+ }
+ }
+ if (rv) break;
+ }
+ }
+ case INITCAP: {
+ *info += SPELL_ORIGCAP;
+ wl = mkallsmall2(cw, unicw, nc);
+ memcpy(wspace,cw,(wl+1));
+ wl2 = mkinitcap2(cw, unicw, nc);
+ if (captype == INITCAP) *info += SPELL_INITCAP;
+ rv = checkword(cw, info, root);
+ if (captype == INITCAP) *info -= SPELL_INITCAP;
+ // forbid bad capitalization
+ // (for example, ijs -> Ijs instead of IJs in Dutch)
+ // use explicit forms in dic: Ijs/F (F = FORBIDDENWORD flag)
+ if (*info & SPELL_FORBIDDEN) {
+ rv = NULL;
+ break;
+ }
+ if (rv && is_keepcase(rv) && (captype == ALLCAP)) rv = NULL;
+ if (rv) break;
+
+ rv = checkword(wspace, info, root);
+ if (abbv && !rv) {
+
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ rv = checkword(wspace, info, root);
+ if (!rv) {
+ memcpy(wspace, cw, wl2);
+ *(wspace+wl2) = '.';
+ *(wspace+wl2+1) = '\0';
+ if (captype == INITCAP) *info += SPELL_INITCAP;
+ rv = checkword(wspace, info, root);
+ if (captype == INITCAP) *info -= SPELL_INITCAP;
+ if (rv && is_keepcase(rv) && (captype == ALLCAP)) rv = NULL;
+ break;
+ }
+ }
+ if (rv && is_keepcase(rv) &&
+ ((captype == ALLCAP) ||
+ // if CHECKSHARPS: KEEPCASE words with \xDF are allowed
+ // in INITCAP form, too.
+ !(pAMgr->get_checksharps() &&
+ ((utf8 && strstr(wspace, "\xC3\x9F")) ||
+ (!utf8 && strchr(wspace, '\xDF')))))) rv = NULL;
+ break;
+ }
+ }
+
+ if (rv) {
+ if (pAMgr && pAMgr->get_warn() && rv->astr &&
+ TESTAFF(rv->astr, pAMgr->get_warn(), rv->alen)) {
+ *info += SPELL_WARN;
+ if (pAMgr->get_forbidwarn()) return 0;
+ return HUNSPELL_OK_WARN;
+ }
+ return HUNSPELL_OK;
+ }
+
+ // recursive breaking at break points
+ if (wordbreak) {
+ char * s;
+ char r;
+ int nbr = 0;
+ wl = strlen(cw);
+ int numbreak = pAMgr ? pAMgr->get_numbreak() : 0;
+
+ // calculate break points for recursion limit
+ for (int j = 0; j < numbreak; j++) {
+ s = cw;
+ do {
+ s = (char *) strstr(s, wordbreak[j]);
+ if (s) {
+ nbr++;
+ s++;
+ }
+ } while (s);
+ }
+ if (nbr >= 10) return 0;
+
+ // check boundary patterns (^begin and end$)
+ for (int j = 0; j < numbreak; j++) {
+ int plen = strlen(wordbreak[j]);
+ if (plen == 1 || plen > wl) continue;
+ if (wordbreak[j][0] == '^' && strncmp(cw, wordbreak[j] + 1, plen - 1) == 0
+ && spell(cw + plen - 1)) return 1;
+ if (wordbreak[j][plen - 1] == '$' &&
+ strncmp(cw + wl - plen + 1, wordbreak[j], plen - 1) == 0) {
+ r = cw[wl - plen + 1];
+ cw[wl - plen + 1] = '\0';
+ if (spell(cw)) return 1;
+ cw[wl - plen + 1] = r;
+ }
+ }
+
+ // other patterns
+ for (int j = 0; j < numbreak; j++) {
+ int plen = strlen(wordbreak[j]);
+ s=(char *) strstr(cw, wordbreak[j]);
+ if (s && (s > cw) && (s < cw + wl - plen)) {
+ if (!spell(s + plen)) continue;
+ r = *s;
+ *s = '\0';
+ // examine 2 sides of the break point
+ if (spell(cw)) return 1;
+ *s = r;
+
+ // LANG_hu: spec. dash rule
+ if (langnum == LANG_hu && strcmp(wordbreak[j], "-") == 0) {
+ r = s[1];
+ s[1] = '\0';
+ if (spell(cw)) return 1; // check the first part with dash
+ s[1] = r;
+ }
+ // end of LANG speficic region
+
+ }
+ }
+ }
+
+ return 0;
+}
+
+struct hentry * Hunspell::checkword(const char * w, int * info, char ** root)
+{
+ struct hentry * he = NULL;
+ int len, i;
+ char w2[MAXWORDUTF8LEN];
+ const char * word;
+
+ char * ignoredchars = pAMgr->get_ignore();
+ if (ignoredchars != NULL) {
+ strcpy(w2, w);
+ if (utf8) {
+ int ignoredchars_utf16_len;
+ unsigned short * ignoredchars_utf16 = pAMgr->get_ignore_utf16(&ignoredchars_utf16_len);
+ remove_ignored_chars_utf(w2, ignoredchars_utf16, ignoredchars_utf16_len);
+ } else {
+ remove_ignored_chars(w2,ignoredchars);
+ }
+ word = w2;
+ } else word = w;
+
+ len = strlen(word);
+
+ if (!len)
+ return NULL;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ if (word != w2) {
+ strcpy(w2, word);
+ word = w2;
+ }
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ }
+
+ // look word in hash table
+ for (i = 0; (i < maxdic) && !he; i ++) {
+ he = (pHMgr[i])->lookup(word);
+
+ // check forbidden and onlyincompound words
+ if ((he) && (he->astr) && (pAMgr) && TESTAFF(he->astr, pAMgr->get_forbiddenword(), he->alen)) {
+ if (info) *info += SPELL_FORBIDDEN;
+ // LANG_hu section: set dash information for suggestions
+ if (langnum == LANG_hu) {
+ if (pAMgr->get_compoundflag() &&
+ TESTAFF(he->astr, pAMgr->get_compoundflag(), he->alen)) {
+ if (info) *info += SPELL_COMPOUND;
+ }
+ }
+ return NULL;
+ }
+
+ // he = next not needaffix, onlyincompound homonym or onlyupcase word
+ while (he && (he->astr) &&
+ ((pAMgr->get_needaffix() && TESTAFF(he->astr, pAMgr->get_needaffix(), he->alen)) ||
+ (pAMgr->get_onlyincompound() && TESTAFF(he->astr, pAMgr->get_onlyincompound(), he->alen)) ||
+ (info && (*info & SPELL_INITCAP) && TESTAFF(he->astr, ONLYUPCASEFLAG, he->alen))
+ )) he = he->next_homonym;
+ }
+
+ // check with affixes
+ if (!he && pAMgr) {
+ // try stripping off affixes */
+ he = pAMgr->affix_check(word, len, 0);
+
+ // check compound restriction and onlyupcase
+ if (he && he->astr && (
+ (pAMgr->get_onlyincompound() &&
+ TESTAFF(he->astr, pAMgr->get_onlyincompound(), he->alen)) ||
+ (info && (*info & SPELL_INITCAP) &&
+ TESTAFF(he->astr, ONLYUPCASEFLAG, he->alen)))) {
+ he = NULL;
+ }
+
+ if (he) {
+ if ((he->astr) && (pAMgr) && TESTAFF(he->astr, pAMgr->get_forbiddenword(), he->alen)) {
+ if (info) *info += SPELL_FORBIDDEN;
+ return NULL;
+ }
+ if (root) {
+ *root = mystrdup(he->word);
+ if (*root && complexprefixes) {
+ if (utf8) reverseword_utf(*root); else reverseword(*root);
+ }
+ }
+ // try check compound word
+ } else if (pAMgr->get_compound()) {
+ he = pAMgr->compound_check(word, len, 0, 0, 100, 0, NULL, 0, 0, info);
+ // LANG_hu section: `moving rule' with last dash
+ if ((!he) && (langnum == LANG_hu) && (word[len-1] == '-')) {
+ char * dup = mystrdup(word);
+ if (!dup) return NULL;
+ dup[len-1] = '\0';
+ he = pAMgr->compound_check(dup, len-1, -5, 0, 100, 0, NULL, 1, 0, info);
+ free(dup);
+ }
+ // end of LANG speficic region
+ if (he) {
+ if (root) {
+ *root = mystrdup(he->word);
+ if (*root && complexprefixes) {
+ if (utf8) reverseword_utf(*root); else reverseword(*root);
+ }
+ }
+ if (info) *info += SPELL_COMPOUND;
+ }
+ }
+
+ }
+
+ return he;
+}
+
+int Hunspell::suggest(char*** slst, const char * word)
+{
+ int onlycmpdsug = 0;
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ if (!pSMgr || maxdic == 0) return 0;
+ w_char unicw[MAXWORDLEN];
+ *slst = NULL;
+ // process XML input of the simplified API (see manual)
+ if (strncmp(word, SPELL_XML, sizeof(SPELL_XML) - 3) == 0) {
+ return spellml(slst, word);
+ }
+ int nc = strlen(word);
+ if (utf8) {
+ if (nc >= MAXWORDUTF8LEN) return 0;
+ } else {
+ if (nc >= MAXWORDLEN) return 0;
+ }
+ int captype = 0;
+ int abbv = 0;
+ int wl = 0;
+
+ // input conversion
+ RepList * rl = (pAMgr) ? pAMgr->get_iconvtable() : NULL;
+ if (rl && rl->conv(word, wspace)) wl = cleanword2(cw, wspace, unicw, &nc, &captype, &abbv);
+ else wl = cleanword2(cw, word, unicw, &nc, &captype, &abbv);
+
+ if (wl == 0) return 0;
+ int ns = 0;
+ int capwords = 0;
+
+ // check capitalized form for FORCEUCASE
+ if (pAMgr && captype == NOCAP && pAMgr->get_forceucase()) {
+ int info = SPELL_ORIGCAP;
+ char ** wlst;
+ if (checkword(cw, &info, NULL)) {
+ if (*slst) {
+ wlst = *slst;
+ } else {
+ wlst = (char **) malloc(MAXSUGGESTION * sizeof(char *));
+ if (wlst == NULL) return -1;
+ *slst = wlst;
+ for (int i = 0; i < MAXSUGGESTION; i++) {
+ wlst[i] = NULL;
+ }
+ }
+ wlst[0] = mystrdup(cw);
+ mkinitcap(wlst[0]);
+ return 1;
+ }
+ }
+
+ switch(captype) {
+ case NOCAP: {
+ ns = pSMgr->suggest(slst, cw, ns, &onlycmpdsug);
+ break;
+ }
+
+ case INITCAP: {
+ capwords = 1;
+ ns = pSMgr->suggest(slst, cw, ns, &onlycmpdsug);
+ if (ns == -1) break;
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ break;
+ }
+ case HUHINITCAP:
+ capwords = 1;
+ case HUHCAP: {
+ ns = pSMgr->suggest(slst, cw, ns, &onlycmpdsug);
+ if (ns != -1) {
+ int prevns;
+ // something.The -> something. The
+ char * dot = strchr(cw, '.');
+ if (dot && (dot > cw)) {
+ int captype_;
+ if (utf8) {
+ w_char w_[MAXWORDLEN];
+ int wl_ = u8_u16(w_, MAXWORDLEN, dot + 1);
+ captype_ = get_captype_utf8(w_, wl_, langnum);
+ } else captype_ = get_captype(dot+1, strlen(dot+1), csconv);
+ if (captype_ == INITCAP) {
+ char * st = mystrdup(cw);
+ if (st) st = (char *) realloc(st, wl + 2);
+ if (st) {
+ st[(dot - cw) + 1] = ' ';
+ strcpy(st + (dot - cw) + 2, dot + 1);
+ ns = insert_sug(slst, st, ns);
+ free(st);
+ }
+ }
+ }
+ if (captype == HUHINITCAP) {
+ // TheOpenOffice.org -> The OpenOffice.org
+ memcpy(wspace,cw,(wl+1));
+ mkinitsmall2(wspace, unicw, nc);
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ }
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ if (spell(wspace)) ns = insert_sug(slst, wspace, ns);
+ prevns = ns;
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ if (captype == HUHINITCAP) {
+ mkinitcap2(wspace, unicw, nc);
+ if (spell(wspace)) ns = insert_sug(slst, wspace, ns);
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ }
+ // aNew -> "a New" (instead of "a new")
+ for (int j = prevns; j < ns; j++) {
+ char * space = strchr((*slst)[j],' ');
+ if (space) {
+ int slen = strlen(space + 1);
+ // different case after space (need capitalisation)
+ if ((slen < wl) && strcmp(cw + wl - slen, space + 1)) {
+ w_char w[MAXWORDLEN];
+ int wc = 0;
+ char * r = (*slst)[j];
+ if (utf8) wc = u8_u16(w, MAXWORDLEN, space + 1);
+ mkinitcap2(space + 1, w, wc);
+ // set as first suggestion
+ for (int k = j; k > 0; k--) (*slst)[k] = (*slst)[k - 1];
+ (*slst)[0] = r;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case ALLCAP: {
+ memcpy(wspace, cw, (wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ if (ns == -1) break;
+ if (pAMgr && pAMgr->get_keepcase() && spell(wspace))
+ ns = insert_sug(slst, wspace, ns);
+ mkinitcap2(wspace, unicw, nc);
+ ns = pSMgr->suggest(slst, wspace, ns, &onlycmpdsug);
+ for (int j=0; j < ns; j++) {
+ mkallcap((*slst)[j]);
+ if (pAMgr && pAMgr->get_checksharps()) {
+ char * pos;
+ if (utf8) {
+ pos = strstr((*slst)[j], "\xC3\x9F");
+ while (pos) {
+ *pos = 'S';
+ *(pos+1) = 'S';
+ pos = strstr(pos+2, "\xC3\x9F");
+ }
+ } else {
+ pos = strchr((*slst)[j], '\xDF');
+ while (pos) {
+ (*slst)[j] = (char *) realloc((*slst)[j], strlen((*slst)[j]) + 2);
+ mystrrep((*slst)[j], "\xDF", "SS");
+ pos = strchr((*slst)[j], '\xDF');
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ // LANG_hu section: replace '-' with ' ' in Hungarian
+ if (langnum == LANG_hu) {
+ for (int j=0; j < ns; j++) {
+ char * pos = strchr((*slst)[j],'-');
+ if (pos) {
+ int info;
+ char w[MAXWORDUTF8LEN];
+ *pos = '\0';
+ strcpy(w, (*slst)[j]);
+ strcat(w, pos + 1);
+ spell(w, &info, NULL);
+ if ((info & SPELL_COMPOUND) && (info & SPELL_FORBIDDEN)) {
+ *pos = ' ';
+ } else *pos = '-';
+ }
+ }
+ }
+ // END OF LANG_hu section
+
+ // try ngram approach since found nothing or only compound words
+ if (pAMgr && (ns == 0 || onlycmpdsug) && (pAMgr->get_maxngramsugs() != 0) && (*slst)) {
+ switch(captype) {
+ case NOCAP: {
+ ns = pSMgr->ngsuggest(*slst, cw, ns, pHMgr, maxdic);
+ break;
+ }
+ case HUHINITCAP:
+ capwords = 1;
+ case HUHCAP: {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ ns = pSMgr->ngsuggest(*slst, wspace, ns, pHMgr, maxdic);
+ break;
+ }
+ case INITCAP: {
+ capwords = 1;
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ ns = pSMgr->ngsuggest(*slst, wspace, ns, pHMgr, maxdic);
+ break;
+ }
+ case ALLCAP: {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall2(wspace, unicw, nc);
+ int oldns = ns;
+ ns = pSMgr->ngsuggest(*slst, wspace, ns, pHMgr, maxdic);
+ for (int j = oldns; j < ns; j++)
+ mkallcap((*slst)[j]);
+ break;
+ }
+ }
+ }
+
+ // try dash suggestion (Afo-American -> Afro-American)
+ if (char * pos = strchr(cw, '-')) {
+ char * ppos = cw;
+ int nodashsug = 1;
+ char ** nlst = NULL;
+ int nn = 0;
+ int last = 0;
+ if (*slst) {
+ for (int j = 0; j < ns && nodashsug == 1; j++) {
+ if (strchr((*slst)[j], '-')) nodashsug = 0;
+ }
+ }
+ while (nodashsug && !last) {
+ if (*pos == '\0') last = 1; else *pos = '\0';
+ if (!spell(ppos)) {
+ nn = suggest(&nlst, ppos);
+ for (int j = nn - 1; j >= 0; j--) {
+ strncpy(wspace, cw, ppos - cw);
+ strcpy(wspace + (ppos - cw), nlst[j]);
+ if (!last) {
+ strcat(wspace, "-");
+ strcat(wspace, pos + 1);
+ }
+ ns = insert_sug(slst, wspace, ns);
+ free(nlst[j]);
+ }
+ if (nlst != NULL) free(nlst);
+ nodashsug = 0;
+ }
+ if (!last) {
+ *pos = '-';
+ ppos = pos + 1;
+ pos = strchr(ppos, '-');
+ }
+ if (!pos) pos = cw + strlen(cw);
+ }
+ }
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ for (int j = 0; j < ns; j++) {
+ if (utf8) reverseword_utf((*slst)[j]); else reverseword((*slst)[j]);
+ }
+ }
+
+ // capitalize
+ if (capwords) for (int j=0; j < ns; j++) {
+ mkinitcap((*slst)[j]);
+ }
+
+ // expand suggestions with dot(s)
+ if (abbv && pAMgr && pAMgr->get_sugswithdots()) {
+ for (int j = 0; j < ns; j++) {
+ (*slst)[j] = (char *) realloc((*slst)[j], strlen((*slst)[j]) + 1 + abbv);
+ strcat((*slst)[j], word + strlen(word) - abbv);
+ }
+ }
+
+ // remove bad capitalized and forbidden forms
+ if (pAMgr && (pAMgr->get_keepcase() || pAMgr->get_forbiddenword())) {
+ switch (captype) {
+ case INITCAP:
+ case ALLCAP: {
+ int l = 0;
+ for (int j=0; j < ns; j++) {
+ if (!strchr((*slst)[j],' ') && !spell((*slst)[j])) {
+ char s[MAXSWUTF8L];
+ w_char w[MAXSWL];
+ int len;
+ if (utf8) {
+ len = u8_u16(w, MAXSWL, (*slst)[j]);
+ } else {
+ strcpy(s, (*slst)[j]);
+ len = strlen(s);
+ }
+ mkallsmall2(s, w, len);
+ free((*slst)[j]);
+ if (spell(s)) {
+ (*slst)[l] = mystrdup(s);
+ if ((*slst)[l]) l++;
+ } else {
+ mkinitcap2(s, w, len);
+ if (spell(s)) {
+ (*slst)[l] = mystrdup(s);
+ if ((*slst)[l]) l++;
+ }
+ }
+ } else {
+ (*slst)[l] = (*slst)[j];
+ l++;
+ }
+ }
+ ns = l;
+ }
+ }
+ }
+
+ // remove duplications
+ int l = 0;
+ for (int j = 0; j < ns; j++) {
+ (*slst)[l] = (*slst)[j];
+ for (int k = 0; k < l; k++) {
+ if (strcmp((*slst)[k], (*slst)[j]) == 0) {
+ free((*slst)[j]);
+ l--;
+ break;
+ }
+ }
+ l++;
+ }
+ ns = l;
+
+ // output conversion
+ rl = (pAMgr) ? pAMgr->get_oconvtable() : NULL;
+ for (int j = 0; rl && j < ns; j++) {
+ if (rl->conv((*slst)[j], wspace)) {
+ free((*slst)[j]);
+ (*slst)[j] = mystrdup(wspace);
+ }
+ }
+
+ // if suggestions removed by nosuggest, onlyincompound parameters
+ if (l == 0 && *slst) {
+ free(*slst);
+ *slst = NULL;
+ }
+ return l;
+}
+
+void Hunspell::free_list(char *** slst, int n) {
+ freelist(slst, n);
+}
+
+char * Hunspell::get_dic_encoding()
+{
+ return encoding;
+}
+
+#ifdef HUNSPELL_EXPERIMENTAL
+// XXX need UTF-8 support
+int Hunspell::suggest_auto(char*** slst, const char * word)
+{
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ if (!pSMgr || maxdic == 0) return 0;
+ int wl = strlen(word);
+ if (utf8) {
+ if (wl >= MAXWORDUTF8LEN) return 0;
+ } else {
+ if (wl >= MAXWORDLEN) return 0;
+ }
+ int captype = 0;
+ int abbv = 0;
+ wl = cleanword(cw, word, &captype, &abbv);
+ if (wl == 0) return 0;
+ int ns = 0;
+ *slst = NULL; // HU, nsug in pSMgr->suggest
+
+ switch(captype) {
+ case NOCAP: {
+ ns = pSMgr->suggest_auto(slst, cw, ns);
+ if (ns>0) break;
+ break;
+ }
+
+ case INITCAP: {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ ns = pSMgr->suggest_auto(slst, wspace, ns);
+ for (int j=0; j < ns; j++)
+ mkinitcap((*slst)[j]);
+ ns = pSMgr->suggest_auto(slst, cw, ns);
+ break;
+
+ }
+
+ case HUHINITCAP:
+ case HUHCAP: {
+ ns = pSMgr->suggest_auto(slst, cw, ns);
+ if (ns == 0) {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ ns = pSMgr->suggest_auto(slst, wspace, ns);
+ }
+ break;
+ }
+
+ case ALLCAP: {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ ns = pSMgr->suggest_auto(slst, wspace, ns);
+
+ mkinitcap(wspace);
+ ns = pSMgr->suggest_auto(slst, wspace, ns);
+
+ for (int j=0; j < ns; j++)
+ mkallcap((*slst)[j]);
+ break;
+ }
+ }
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ for (int j = 0; j < ns; j++) {
+ if (utf8) reverseword_utf((*slst)[j]); else reverseword((*slst)[j]);
+ }
+ }
+
+ // expand suggestions with dot(s)
+ if (abbv && pAMgr && pAMgr->get_sugswithdots()) {
+ for (int j = 0; j < ns; j++) {
+ (*slst)[j] = (char *) realloc((*slst)[j], strlen((*slst)[j]) + 1 + abbv);
+ strcat((*slst)[j], word + strlen(word) - abbv);
+ }
+ }
+
+ // LANG_hu section: replace '-' with ' ' in Hungarian
+ if (langnum == LANG_hu) {
+ for (int j=0; j < ns; j++) {
+ char * pos = strchr((*slst)[j],'-');
+ if (pos) {
+ int info;
+ char w[MAXWORDUTF8LEN];
+ *pos = '\0';
+ strcpy(w, (*slst)[j]);
+ strcat(w, pos + 1);
+ spell(w, &info, NULL);
+ if ((info & SPELL_COMPOUND) && (info & SPELL_FORBIDDEN)) {
+ *pos = ' ';
+ } else *pos = '-';
+ }
+ }
+ }
+ // END OF LANG_hu section
+ return ns;
+}
+#endif
+
+int Hunspell::stem(char*** slst, char ** desc, int n)
+{
+ char result[MAXLNLEN];
+ char result2[MAXLNLEN];
+ *slst = NULL;
+ if (n == 0) return 0;
+ *result2 = '\0';
+ for (int i = 0; i < n; i++) {
+ *result = '\0';
+ // add compound word parts (except the last one)
+ char * s = (char *) desc[i];
+ char * part = strstr(s, MORPH_PART);
+ if (part) {
+ char * nextpart = strstr(part + 1, MORPH_PART);
+ while (nextpart) {
+ copy_field(result + strlen(result), part, MORPH_PART);
+ part = nextpart;
+ nextpart = strstr(part + 1, MORPH_PART);
+ }
+ s = part;
+ }
+
+ char **pl;
+ char tok[MAXLNLEN];
+ strcpy(tok, s);
+ char * alt = strstr(tok, " | ");
+ while (alt) {
+ alt[1] = MSEP_ALT;
+ alt = strstr(alt, " | ");
+ }
+ int pln = line_tok(tok, &pl, MSEP_ALT);
+ for (int k = 0; k < pln; k++) {
+ // add derivational suffixes
+ if (strstr(pl[k], MORPH_DERI_SFX)) {
+ // remove inflectional suffixes
+ char * is = strstr(pl[k], MORPH_INFL_SFX);
+ if (is) *is = '\0';
+ char * sg = pSMgr->suggest_gen(&(pl[k]), 1, pl[k]);
+ if (sg) {
+ char ** gen;
+ int genl = line_tok(sg, &gen, MSEP_REC);
+ free(sg);
+ for (int j = 0; j < genl; j++) {
+ sprintf(result2 + strlen(result2), "%c%s%s",
+ MSEP_REC, result, gen[j]);
+ }
+ freelist(&gen, genl);
+ }
+ } else {
+ sprintf(result2 + strlen(result2), "%c%s", MSEP_REC, result);
+ if (strstr(pl[k], MORPH_SURF_PFX)) {
+ copy_field(result2 + strlen(result2), pl[k], MORPH_SURF_PFX);
+ }
+ copy_field(result2 + strlen(result2), pl[k], MORPH_STEM);
+ }
+ }
+ freelist(&pl, pln);
+ }
+ int sln = line_tok(result2, slst, MSEP_REC);
+ return uniqlist(*slst, sln);
+
+}
+
+int Hunspell::stem(char*** slst, const char * word)
+{
+ char ** pl;
+ int pln = analyze(&pl, word);
+ int pln2 = stem(slst, pl, pln);
+ freelist(&pl, pln);
+ return pln2;
+}
+
+#ifdef HUNSPELL_EXPERIMENTAL
+int Hunspell::suggest_pos_stems(char*** slst, const char * word)
+{
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ if (! pSMgr || maxdic == 0) return 0;
+ int wl = strlen(word);
+ if (utf8) {
+ if (wl >= MAXWORDUTF8LEN) return 0;
+ } else {
+ if (wl >= MAXWORDLEN) return 0;
+ }
+ int captype = 0;
+ int abbv = 0;
+ wl = cleanword(cw, word, &captype, &abbv);
+ if (wl == 0) return 0;
+
+ int ns = 0; // ns=0 = normalized input
+
+ *slst = NULL; // HU, nsug in pSMgr->suggest
+
+ switch(captype) {
+ case HUHCAP:
+ case NOCAP: {
+ ns = pSMgr->suggest_pos_stems(slst, cw, ns);
+
+ if ((abbv) && (ns == 0)) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ ns = pSMgr->suggest_pos_stems(slst, wspace, ns);
+ }
+
+ break;
+ }
+
+ case INITCAP: {
+
+ ns = pSMgr->suggest_pos_stems(slst, cw, ns);
+
+ if (ns == 0 || ((*slst)[0][0] == '#')) {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ ns = pSMgr->suggest_pos_stems(slst, wspace, ns);
+ }
+
+ break;
+
+ }
+
+ case ALLCAP: {
+ ns = pSMgr->suggest_pos_stems(slst, cw, ns);
+ if (ns != 0) break;
+
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ ns = pSMgr->suggest_pos_stems(slst, wspace, ns);
+
+ if (ns == 0) {
+ mkinitcap(wspace);
+ ns = pSMgr->suggest_pos_stems(slst, wspace, ns);
+ }
+ break;
+ }
+ }
+
+ return ns;
+}
+#endif // END OF HUNSPELL_EXPERIMENTAL CODE
+
+const char * Hunspell::get_wordchars()
+{
+ return pAMgr->get_wordchars();
+}
+
+unsigned short * Hunspell::get_wordchars_utf16(int * len)
+{
+ return pAMgr->get_wordchars_utf16(len);
+}
+
+void Hunspell::mkinitcap(char * p)
+{
+ if (!utf8) {
+ if (*p != '\0') *p = csconv[((unsigned char)*p)].cupper;
+ } else {
+ int len;
+ w_char u[MAXWORDLEN];
+ len = u8_u16(u, MAXWORDLEN, p);
+ unsigned short i = unicodetoupper((u[0].h << 8) + u[0].l, langnum);
+ u[0].h = (unsigned char) (i >> 8);
+ u[0].l = (unsigned char) (i & 0x00FF);
+ u16_u8(p, MAXWORDUTF8LEN, u, len);
+ }
+}
+
+int Hunspell::mkinitcap2(char * p, w_char * u, int nc)
+{
+ if (!utf8) {
+ if (*p != '\0') *p = csconv[((unsigned char)*p)].cupper;
+ } else if (nc > 0) {
+ unsigned short i = unicodetoupper((u[0].h << 8) + u[0].l, langnum);
+ u[0].h = (unsigned char) (i >> 8);
+ u[0].l = (unsigned char) (i & 0x00FF);
+ u16_u8(p, MAXWORDUTF8LEN, u, nc);
+ return strlen(p);
+ }
+ return nc;
+}
+
+int Hunspell::mkinitsmall2(char * p, w_char * u, int nc)
+{
+ if (!utf8) {
+ if (*p != '\0') *p = csconv[((unsigned char)*p)].clower;
+ } else if (nc > 0) {
+ unsigned short i = unicodetolower((u[0].h << 8) + u[0].l, langnum);
+ u[0].h = (unsigned char) (i >> 8);
+ u[0].l = (unsigned char) (i & 0x00FF);
+ u16_u8(p, MAXWORDUTF8LEN, u, nc);
+ return strlen(p);
+ }
+ return nc;
+}
+
+int Hunspell::add(const char * word)
+{
+ if (pHMgr[0]) return (pHMgr[0])->add(word);
+ return 0;
+}
+
+int Hunspell::add_with_affix(const char * word, const char * example)
+{
+ if (pHMgr[0]) return (pHMgr[0])->add_with_affix(word, example);
+ return 0;
+}
+
+int Hunspell::remove(const char * word)
+{
+ if (pHMgr[0]) return (pHMgr[0])->remove(word);
+ return 0;
+}
+
+const char * Hunspell::get_version()
+{
+ return pAMgr->get_version();
+}
+
+struct cs_info * Hunspell::get_csconv()
+{
+ return csconv;
+}
+
+void Hunspell::cat_result(char * result, char * st)
+{
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+}
+
+int Hunspell::analyze(char*** slst, const char * word)
+{
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ w_char unicw[MAXWORDLEN];
+ int wl2 = 0;
+ *slst = NULL;
+ if (! pSMgr || maxdic == 0) return 0;
+ int nc = strlen(word);
+ if (utf8) {
+ if (nc >= MAXWORDUTF8LEN) return 0;
+ } else {
+ if (nc >= MAXWORDLEN) return 0;
+ }
+ int captype = 0;
+ int abbv = 0;
+ int wl = 0;
+
+ // input conversion
+ RepList * rl = (pAMgr) ? pAMgr->get_iconvtable() : NULL;
+ if (rl && rl->conv(word, wspace)) wl = cleanword2(cw, wspace, unicw, &nc, &captype, &abbv);
+ else wl = cleanword2(cw, word, unicw, &nc, &captype, &abbv);
+
+ if (wl == 0) {
+ if (abbv) {
+ for (wl = 0; wl < abbv; wl++) cw[wl] = '.';
+ cw[wl] = '\0';
+ abbv = 0;
+ } else return 0;
+ }
+
+ char result[MAXLNLEN];
+ char * st = NULL;
+
+ *result = '\0';
+
+ int n = 0;
+ int n2 = 0;
+ int n3 = 0;
+
+ // test numbers
+ // LANG_hu section: set dash information for suggestions
+ if (langnum == LANG_hu) {
+ while ((n < wl) &&
+ (((cw[n] <= '9') && (cw[n] >= '0')) || (((cw[n] == '.') || (cw[n] == ',')) && (n > 0)))) {
+ n++;
+ if ((cw[n] == '.') || (cw[n] == ',')) {
+ if (((n2 == 0) && (n > 3)) ||
+ ((n2 > 0) && ((cw[n-1] == '.') || (cw[n-1] == ',')))) break;
+ n2++;
+ n3 = n;
+ }
+ }
+
+ if ((n == wl) && (n3 > 0) && (n - n3 > 3)) return 0;
+ if ((n == wl) || ((n>0) && ((cw[n]=='%') || (cw[n]=='\xB0')) && checkword(cw+n, NULL, NULL))) {
+ mystrcat(result, cw, MAXLNLEN);
+ result[n - 1] = '\0';
+ if (n == wl) cat_result(result, pSMgr->suggest_morph(cw + n - 1));
+ else {
+ char sign = cw[n];
+ cw[n] = '\0';
+ cat_result(result, pSMgr->suggest_morph(cw + n - 1));
+ mystrcat(result, "+", MAXLNLEN); // XXX SPEC. MORPHCODE
+ cw[n] = sign;
+ cat_result(result, pSMgr->suggest_morph(cw + n));
+ }
+ return line_tok(result, slst, MSEP_REC);
+ }
+ }
+ // END OF LANG_hu section
+
+ switch(captype) {
+ case HUHCAP:
+ case HUHINITCAP:
+ case NOCAP: {
+ cat_result(result, pSMgr->suggest_morph(cw));
+ if (abbv) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ cat_result(result, pSMgr->suggest_morph(wspace));
+ }
+ break;
+ }
+ case INITCAP: {
+ wl = mkallsmall2(cw, unicw, nc);
+ memcpy(wspace,cw,(wl+1));
+ wl2 = mkinitcap2(cw, unicw, nc);
+ cat_result(result, pSMgr->suggest_morph(wspace));
+ cat_result(result, pSMgr->suggest_morph(cw));
+ if (abbv) {
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ cat_result(result, pSMgr->suggest_morph(wspace));
+
+ memcpy(wspace, cw, wl2);
+ *(wspace+wl2) = '.';
+ *(wspace+wl2+1) = '\0';
+
+ cat_result(result, pSMgr->suggest_morph(wspace));
+ }
+ break;
+ }
+ case ALLCAP: {
+ cat_result(result, pSMgr->suggest_morph(cw));
+ if (abbv) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ cat_result(result, pSMgr->suggest_morph(cw));
+ }
+ wl = mkallsmall2(cw, unicw, nc);
+ memcpy(wspace,cw,(wl+1));
+ wl2 = mkinitcap2(cw, unicw, nc);
+
+ cat_result(result, pSMgr->suggest_morph(wspace));
+ cat_result(result, pSMgr->suggest_morph(cw));
+ if (abbv) {
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ cat_result(result, pSMgr->suggest_morph(wspace));
+
+ memcpy(wspace, cw, wl2);
+ *(wspace+wl2) = '.';
+ *(wspace+wl2+1) = '\0';
+
+ cat_result(result, pSMgr->suggest_morph(wspace));
+ }
+ break;
+ }
+ }
+
+ if (*result) {
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ if (utf8) reverseword_utf(result); else reverseword(result);
+ }
+ return line_tok(result, slst, MSEP_REC);
+ }
+
+ // compound word with dash (HU) I18n
+ char * dash = NULL;
+ int nresult = 0;
+ // LANG_hu section: set dash information for suggestions
+ if (langnum == LANG_hu) dash = (char *) strchr(cw,'-');
+ if ((langnum == LANG_hu) && dash) {
+ *dash='\0';
+ // examine 2 sides of the dash
+ if (dash[1] == '\0') { // base word ending with dash
+ if (spell(cw)) {
+ char * p = pSMgr->suggest_morph(cw);
+ if (p) {
+ int ret = line_tok(p, slst, MSEP_REC);
+ free(p);
+ return ret;
+ }
+
+ }
+ } else if ((dash[1] == 'e') && (dash[2] == '\0')) { // XXX (HU) -e hat.
+ if (spell(cw) && (spell("-e"))) {
+ st = pSMgr->suggest_morph(cw);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mystrcat(result,"+", MAXLNLEN); // XXX spec. separator in MORPHCODE
+ st = pSMgr->suggest_morph("-e");
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ return line_tok(result, slst, MSEP_REC);
+ }
+ } else {
+ // first word ending with dash: word- XXX ???
+ char r2 = *(dash + 1);
+ dash[0]='-';
+ dash[1]='\0';
+ nresult = spell(cw);
+ dash[1] = r2;
+ dash[0]='\0';
+ if (nresult && spell(dash+1) && ((strlen(dash+1) > 1) ||
+ ((dash[1] > '0') && (dash[1] < '9')))) {
+ st = pSMgr->suggest_morph(cw);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ mystrcat(result,"+", MAXLNLEN); // XXX spec. separator in MORPHCODE
+ }
+ st = pSMgr->suggest_morph(dash+1);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ return line_tok(result, slst, MSEP_REC);
+ }
+ }
+ // affixed number in correct word
+ if (nresult && (dash > cw) && (((*(dash-1)<='9') &&
+ (*(dash-1)>='0')) || (*(dash-1)=='.'))) {
+ *dash='-';
+ n = 1;
+ if (*(dash - n) == '.') n++;
+ // search first not a number character to left from dash
+ while (((dash - n)>=cw) && ((*(dash - n)=='0') || (n < 3)) && (n < 6)) {
+ n++;
+ }
+ if ((dash - n) < cw) n--;
+ // numbers: valami1000000-hoz
+ // examine 100000-hoz, 10000-hoz 1000-hoz, 10-hoz,
+ // 56-hoz, 6-hoz
+ for(; n >= 1; n--) {
+ if ((*(dash - n) >= '0') && (*(dash - n) <= '9') && checkword(dash - n, NULL, NULL)) {
+ mystrcat(result, cw, MAXLNLEN);
+ result[dash - cw - n] = '\0';
+ st = pSMgr->suggest_morph(dash - n);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ return line_tok(result, slst, MSEP_REC);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int Hunspell::generate(char*** slst, const char * word, char ** pl, int pln)
+{
+ *slst = NULL;
+ if (!pSMgr || !pln) return 0;
+ char **pl2;
+ int pl2n = analyze(&pl2, word);
+ int captype = 0;
+ int abbv = 0;
+ char cw[MAXWORDUTF8LEN];
+ cleanword(cw, word, &captype, &abbv);
+ char result[MAXLNLEN];
+ *result = '\0';
+
+ for (int i = 0; i < pln; i++) {
+ cat_result(result, pSMgr->suggest_gen(pl2, pl2n, pl[i]));
+ }
+ freelist(&pl2, pl2n);
+
+ if (*result) {
+ // allcap
+ if (captype == ALLCAP) mkallcap(result);
+
+ // line split
+ int linenum = line_tok(result, slst, MSEP_REC);
+
+ // capitalize
+ if (captype == INITCAP || captype == HUHINITCAP) {
+ for (int j=0; j < linenum; j++) mkinitcap((*slst)[j]);
+ }
+
+ // temporary filtering of prefix related errors (eg.
+ // generate("undrinkable", "eats") --> "undrinkables" and "*undrinks")
+
+ int r = 0;
+ for (int j=0; j < linenum; j++) {
+ if (!spell((*slst)[j])) {
+ free((*slst)[j]);
+ (*slst)[j] = NULL;
+ } else {
+ if (r < j) (*slst)[r] = (*slst)[j];
+ r++;
+ }
+ }
+ if (r > 0) return r;
+ free(*slst);
+ *slst = NULL;
+ }
+ return 0;
+}
+
+int Hunspell::generate(char*** slst, const char * word, const char * pattern)
+{
+ char **pl;
+ int pln = analyze(&pl, pattern);
+ int n = generate(slst, word, pl, pln);
+ freelist(&pl, pln);
+ return uniqlist(*slst, n);
+}
+
+// minimal XML parser functions
+int Hunspell::get_xml_par(char * dest, const char * par, int max)
+{
+ char * d = dest;
+ if (!par) return 0;
+ char end = *par;
+ char * dmax = dest + max;
+ if (end == '>') end = '<';
+ else if (end != '\'' && end != '"') return 0; // bad XML
+ for (par++; d < dmax && *par != '\0' && *par != end; par++, d++) *d = *par;
+ *d = '\0';
+ mystrrep(dest, "<", "<");
+ mystrrep(dest, "&", "&");
+ return (int)(d - dest);
+}
+
+int Hunspell::get_langnum() const
+{
+ return langnum;
+}
+
+// return the beginning of the element (attr == NULL) or the attribute
+const char * Hunspell::get_xml_pos(const char * s, const char * attr)
+{
+ const char * end = strchr(s, '>');
+ const char * p = s;
+ if (attr == NULL) return end;
+ do {
+ p = strstr(p, attr);
+ if (!p || p >= end) return 0;
+ } while (*(p-1) != ' ' && *(p-1) != '\n');
+ return p + strlen(attr);
+}
+
+int Hunspell::check_xml_par(const char * q, const char * attr, const char * value) {
+ char cw[MAXWORDUTF8LEN];
+ if (get_xml_par(cw, get_xml_pos(q, attr), MAXWORDUTF8LEN - 1) &&
+ strcmp(cw, value) == 0) return 1;
+ return 0;
+}
+
+int Hunspell::get_xml_list(char ***slst, char * list, const char * tag) {
+ int n = 0;
+ char * p;
+ if (!list) return 0;
+ for (p = list; (p = strstr(p, tag)); p++) n++;
+ if (n == 0) return 0;
+ *slst = (char **) malloc(sizeof(char *) * n);
+ if (!*slst) return 0;
+ for (p = list, n = 0; (p = strstr(p, tag)); p++, n++) {
+ int l = strlen(p);
+ (*slst)[n] = (char *) malloc(l + 1);
+ if (!(*slst)[n]) return n;
+ if (!get_xml_par((*slst)[n], p + strlen(tag) - 1, l)) {
+ free((*slst)[n]);
+ break;
+ }
+ }
+ return n;
+}
+
+int Hunspell::spellml(char*** slst, const char * word)
+{
+ char *q, *q2;
+ char cw[MAXWORDUTF8LEN], cw2[MAXWORDUTF8LEN];
+ q = (char *) strstr(word, "<query");
+ if (!q) return 0; // bad XML input
+ q2 = strchr(q, '>');
+ if (!q2) return 0; // bad XML input
+ q2 = strstr(q2, "<word");
+ if (!q2) return 0; // bad XML input
+ if (check_xml_par(q, "type=", "analyze")) {
+ int n = 0, s = 0;
+ if (get_xml_par(cw, strchr(q2, '>'), MAXWORDUTF8LEN - 10)) n = analyze(slst, cw);
+ if (n == 0) return 0;
+ // convert the result to <code><a>ana1</a><a>ana2</a></code> format
+ for (int i = 0; i < n; i++) s+= strlen((*slst)[i]);
+ char * r = (char *) malloc(6 + 5 * s + 7 * n + 7 + 1); // XXX 5*s->&->&
+ if (!r) return 0;
+ strcpy(r, "<code>");
+ for (int i = 0; i < n; i++) {
+ int l = strlen(r);
+ strcpy(r + l, "<a>");
+ strcpy(r + l + 3, (*slst)[i]);
+ mystrrep(r + l + 3, "\t", " ");
+ mystrrep(r + l + 3, "<", "<");
+ mystrrep(r + l + 3, "&", "&");
+ strcat(r, "</a>");
+ free((*slst)[i]);
+ }
+ strcat(r, "</code>");
+ (*slst)[0] = r;
+ return 1;
+ } else if (check_xml_par(q, "type=", "stem")) {
+ if (get_xml_par(cw, strchr(q2, '>'), MAXWORDUTF8LEN - 1)) return stem(slst, cw);
+ } else if (check_xml_par(q, "type=", "generate")) {
+ int n = get_xml_par(cw, strchr(q2, '>'), MAXWORDUTF8LEN - 1);
+ if (n == 0) return 0;
+ char * q3 = strstr(q2 + 1, "<word");
+ if (q3) {
+ if (get_xml_par(cw2, strchr(q3, '>'), MAXWORDUTF8LEN - 1)) {
+ return generate(slst, cw, cw2);
+ }
+ } else {
+ if ((q2 = strstr(q2 + 1, "<code"))) {
+ char ** slst2;
+ if ((n = get_xml_list(&slst2, strchr(q2, '>'), "<a>"))) {
+ int n2 = generate(slst, cw, slst2, n);
+ freelist(&slst2, n);
+ return uniqlist(*slst, n2);
+ }
+ freelist(&slst2, n);
+ }
+ }
+ }
+ return 0;
+}
+
+
+#ifdef HUNSPELL_EXPERIMENTAL
+// XXX need UTF-8 support
+char * Hunspell::morph_with_correction(const char * word)
+{
+ char cw[MAXWORDUTF8LEN];
+ char wspace[MAXWORDUTF8LEN];
+ if (! pSMgr || maxdic == 0) return NULL;
+ int wl = strlen(word);
+ if (utf8) {
+ if (wl >= MAXWORDUTF8LEN) return NULL;
+ } else {
+ if (wl >= MAXWORDLEN) return NULL;
+ }
+ int captype = 0;
+ int abbv = 0;
+ wl = cleanword(cw, word, &captype, &abbv);
+ if (wl == 0) return NULL;
+
+ char result[MAXLNLEN];
+ char * st = NULL;
+
+ *result = '\0';
+
+
+ switch(captype) {
+ case NOCAP: {
+ st = pSMgr->suggest_morph_for_spelling_error(cw);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ if (abbv) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ }
+ break;
+ }
+ case INITCAP: {
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ st = pSMgr->suggest_morph_for_spelling_error(cw);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ if (abbv) {
+ memcpy(wspace,cw,wl);
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ mkallsmall(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mkinitcap(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ }
+ break;
+ }
+ case HUHCAP: {
+ st = pSMgr->suggest_morph_for_spelling_error(cw);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ memcpy(wspace,cw,(wl+1));
+ mkallsmall(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ break;
+ }
+ case ALLCAP: {
+ memcpy(wspace,cw,(wl+1));
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mkallsmall(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mkinitcap(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ if (abbv) {
+ memcpy(wspace,cw,(wl+1));
+ *(wspace+wl) = '.';
+ *(wspace+wl+1) = '\0';
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mkallsmall(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ mkinitcap(wspace);
+ st = pSMgr->suggest_morph_for_spelling_error(wspace);
+ if (st) {
+ if (*result) mystrcat(result, "\n", MAXLNLEN);
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+ }
+ break;
+ }
+ }
+
+ if (*result) return mystrdup(result);
+ return NULL;
+}
+
+#endif // END OF HUNSPELL_EXPERIMENTAL CODE
+
+Hunhandle *Hunspell_create(const char * affpath, const char * dpath)
+{
+ return (Hunhandle*)(new Hunspell(affpath, dpath));
+}
+
+Hunhandle *Hunspell_create_key(const char * affpath, const char * dpath,
+ const char * key)
+{
+ return (Hunhandle*)(new Hunspell(affpath, dpath, key));
+}
+
+void Hunspell_destroy(Hunhandle *pHunspell)
+{
+ delete (Hunspell*)(pHunspell);
+}
+
+int Hunspell_spell(Hunhandle *pHunspell, const char *word)
+{
+ return ((Hunspell*)pHunspell)->spell(word);
+}
+
+char *Hunspell_get_dic_encoding(Hunhandle *pHunspell)
+{
+ return ((Hunspell*)pHunspell)->get_dic_encoding();
+}
+
+int Hunspell_suggest(Hunhandle *pHunspell, char*** slst, const char * word)
+{
+ return ((Hunspell*)pHunspell)->suggest(slst, word);
+}
+
+int Hunspell_analyze(Hunhandle *pHunspell, char*** slst, const char * word)
+{
+ return ((Hunspell*)pHunspell)->analyze(slst, word);
+}
+
+int Hunspell_stem(Hunhandle *pHunspell, char*** slst, const char * word)
+{
+ return ((Hunspell*)pHunspell)->stem(slst, word);
+}
+
+int Hunspell_stem2(Hunhandle *pHunspell, char*** slst, char** desc, int n)
+{
+ return ((Hunspell*)pHunspell)->stem(slst, desc, n);
+}
+
+int Hunspell_generate(Hunhandle *pHunspell, char*** slst, const char * word,
+ const char * word2)
+{
+ return ((Hunspell*)pHunspell)->generate(slst, word, word2);
+}
+
+int Hunspell_generate2(Hunhandle *pHunspell, char*** slst, const char * word,
+ char** desc, int n)
+{
+ return ((Hunspell*)pHunspell)->generate(slst, word, desc, n);
+}
+
+ /* functions for run-time modification of the dictionary */
+
+ /* add word to the run-time dictionary */
+
+int Hunspell_add(Hunhandle *pHunspell, const char * word) {
+ return ((Hunspell*)pHunspell)->add(word);
+}
+
+ /* add word to the run-time dictionary with affix flags of
+ * the example (a dictionary word): Hunspell will recognize
+ * affixed forms of the new word, too.
+ */
+
+int Hunspell_add_with_affix(Hunhandle *pHunspell, const char * word,
+ const char * example) {
+ return ((Hunspell*)pHunspell)->add_with_affix(word, example);
+}
+
+ /* remove word from the run-time dictionary */
+
+int Hunspell_remove(Hunhandle *pHunspell, const char * word) {
+ return ((Hunspell*)pHunspell)->remove(word);
+}
+
+void Hunspell_free_list(Hunhandle *, char *** slst, int n) {
+ freelist(slst, n);
+}
diff --git a/src/cpp/core/spelling/hunspell/hunspell.h b/src/cpp/core/spelling/hunspell/hunspell.h
new file mode 100644
index 0000000..627968a
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunspell.h
@@ -0,0 +1,95 @@
+#ifndef _MYSPELLMGR_H_
+#define _MYSPELLMGR_H_
+
+#include "hunvisapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Hunhandle Hunhandle;
+
+LIBHUNSPELL_DLL_EXPORTED Hunhandle *Hunspell_create(const char * affpath, const char * dpath);
+
+LIBHUNSPELL_DLL_EXPORTED Hunhandle *Hunspell_create_key(const char * affpath, const char * dpath,
+ const char * key);
+
+LIBHUNSPELL_DLL_EXPORTED void Hunspell_destroy(Hunhandle *pHunspell);
+
+/* spell(word) - spellcheck word
+ * output: 0 = bad word, not 0 = good word
+ */
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_spell(Hunhandle *pHunspell, const char *);
+
+LIBHUNSPELL_DLL_EXPORTED char *Hunspell_get_dic_encoding(Hunhandle *pHunspell);
+
+/* suggest(suggestions, word) - search suggestions
+ * input: pointer to an array of strings pointer and the (bad) word
+ * array of strings pointer (here *slst) may not be initialized
+ * output: number of suggestions in string array, and suggestions in
+ * a newly allocated array of strings (*slts will be NULL when number
+ * of suggestion equals 0.)
+ */
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_suggest(Hunhandle *pHunspell, char*** slst, const char * word);
+
+ /* morphological functions */
+
+ /* analyze(result, word) - morphological analysis of the word */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_analyze(Hunhandle *pHunspell, char*** slst, const char * word);
+
+ /* stem(result, word) - stemmer function */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_stem(Hunhandle *pHunspell, char*** slst, const char * word);
+
+ /* stem(result, analysis, n) - get stems from a morph. analysis
+ * example:
+ * char ** result, result2;
+ * int n1 = Hunspell_analyze(result, "words");
+ * int n2 = Hunspell_stem2(result2, result, n1);
+ */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_stem2(Hunhandle *pHunspell, char*** slst, char** desc, int n);
+
+ /* generate(result, word, word2) - morphological generation by example(s) */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_generate(Hunhandle *pHunspell, char*** slst, const char * word,
+ const char * word2);
+
+ /* generate(result, word, desc, n) - generation by morph. description(s)
+ * example:
+ * char ** result;
+ * char * affix = "is:plural"; // description depends from dictionaries, too
+ * int n = Hunspell_generate2(result, "word", &affix, 1);
+ * for (int i = 0; i < n; i++) printf("%s\n", result[i]);
+ */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_generate2(Hunhandle *pHunspell, char*** slst, const char * word,
+ char** desc, int n);
+
+ /* functions for run-time modification of the dictionary */
+
+ /* add word to the run-time dictionary */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_add(Hunhandle *pHunspell, const char * word);
+
+ /* add word to the run-time dictionary with affix flags of
+ * the example (a dictionary word): Hunspell will recognize
+ * affixed forms of the new word, too.
+ */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_add_with_affix(Hunhandle *pHunspell, const char * word, const char * example);
+
+ /* remove word from the run-time dictionary */
+
+LIBHUNSPELL_DLL_EXPORTED int Hunspell_remove(Hunhandle *pHunspell, const char * word);
+
+ /* free suggestion lists */
+
+LIBHUNSPELL_DLL_EXPORTED void Hunspell_free_list(Hunhandle *pHunspell, char *** slst, int n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/hunspell.hxx b/src/cpp/core/spelling/hunspell/hunspell.hxx
new file mode 100644
index 0000000..9b6c388
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunspell.hxx
@@ -0,0 +1,172 @@
+#include "hunvisapi.h"
+
+#include "hashmgr.hxx"
+#include "affixmgr.hxx"
+#include "suggestmgr.hxx"
+#include "langnum.hxx"
+
+#define SPELL_XML "<?xml?>"
+
+#define MAXDIC 20
+#define MAXSUGGESTION 15
+#define MAXSHARPS 5
+
+#define HUNSPELL_OK (1 << 0)
+#define HUNSPELL_OK_WARN (1 << 1)
+
+#ifndef _MYSPELLMGR_HXX_
+#define _MYSPELLMGR_HXX_
+
+class LIBHUNSPELL_DLL_EXPORTED Hunspell
+{
+ AffixMgr* pAMgr;
+ HashMgr* pHMgr[MAXDIC];
+ int maxdic;
+ SuggestMgr* pSMgr;
+ char * affixpath;
+ char * encoding;
+ struct cs_info * csconv;
+ int langnum;
+ int utf8;
+ int complexprefixes;
+ char** wordbreak;
+
+public:
+
+ /* Hunspell(aff, dic) - constructor of Hunspell class
+ * input: path of affix file and dictionary file
+ */
+
+ Hunspell(const char * affpath, const char * dpath, const char * key = NULL);
+ ~Hunspell();
+
+ /* load extra dictionaries (only dic files) */
+ int add_dic(const char * dpath, const char * key = NULL);
+
+ /* spell(word) - spellcheck word
+ * output: 0 = bad word, not 0 = good word
+ *
+ * plus output:
+ * info: information bit array, fields:
+ * SPELL_COMPOUND = a compound word
+ * SPELL_FORBIDDEN = an explicit forbidden word
+ * root: root (stem), when input is a word with affix(es)
+ */
+
+ int spell(const char * word, int * info = NULL, char ** root = NULL);
+
+ /* suggest(suggestions, word) - search suggestions
+ * input: pointer to an array of strings pointer and the (bad) word
+ * array of strings pointer (here *slst) may not be initialized
+ * output: number of suggestions in string array, and suggestions in
+ * a newly allocated array of strings (*slts will be NULL when number
+ * of suggestion equals 0.)
+ */
+
+ int suggest(char*** slst, const char * word);
+
+ /* deallocate suggestion lists */
+
+ void free_list(char *** slst, int n);
+
+ char * get_dic_encoding();
+
+ /* morphological functions */
+
+ /* analyze(result, word) - morphological analysis of the word */
+
+ int analyze(char*** slst, const char * word);
+
+ /* stem(result, word) - stemmer function */
+
+ int stem(char*** slst, const char * word);
+
+ /* stem(result, analysis, n) - get stems from a morph. analysis
+ * example:
+ * char ** result, result2;
+ * int n1 = analyze(&result, "words");
+ * int n2 = stem(&result2, result, n1);
+ */
+
+ int stem(char*** slst, char ** morph, int n);
+
+ /* generate(result, word, word2) - morphological generation by example(s) */
+
+ int generate(char*** slst, const char * word, const char * word2);
+
+ /* generate(result, word, desc, n) - generation by morph. description(s)
+ * example:
+ * char ** result;
+ * char * affix = "is:plural"; // description depends from dictionaries, too
+ * int n = generate(&result, "word", &affix, 1);
+ * for (int i = 0; i < n; i++) printf("%s\n", result[i]);
+ */
+
+ int generate(char*** slst, const char * word, char ** desc, int n);
+
+ /* functions for run-time modification of the dictionary */
+
+ /* add word to the run-time dictionary */
+
+ int add(const char * word);
+
+ /* add word to the run-time dictionary with affix flags of
+ * the example (a dictionary word): Hunspell will recognize
+ * affixed forms of the new word, too.
+ */
+
+ int add_with_affix(const char * word, const char * example);
+
+ /* remove word from the run-time dictionary */
+
+ int remove(const char * word);
+
+ /* other */
+
+ /* get extra word characters definied in affix file for tokenization */
+ const char * get_wordchars();
+ unsigned short * get_wordchars_utf16(int * len);
+
+ struct cs_info * get_csconv();
+ const char * get_version();
+
+ int get_langnum() const;
+
+ /* experimental and deprecated functions */
+
+#ifdef HUNSPELL_EXPERIMENTAL
+ /* suffix is an affix flag string, similarly in dictionary files */
+ int put_word_suffix(const char * word, const char * suffix);
+ char * morph_with_correction(const char * word);
+
+ /* spec. suggestions */
+ int suggest_auto(char*** slst, const char * word);
+ int suggest_pos_stems(char*** slst, const char * word);
+#endif
+
+private:
+ int cleanword(char *, const char *, int * pcaptype, int * pabbrev);
+ int cleanword2(char *, const char *, w_char *, int * w_len, int * pcaptype, int * pabbrev);
+ void mkinitcap(char *);
+ int mkinitcap2(char * p, w_char * u, int nc);
+ int mkinitsmall2(char * p, w_char * u, int nc);
+ void mkallcap(char *);
+ int mkallcap2(char * p, w_char * u, int nc);
+ void mkallsmall(char *);
+ int mkallsmall2(char * p, w_char * u, int nc);
+ struct hentry * checkword(const char *, int * info, char **root);
+ char * sharps_u8_l1(char * dest, char * source);
+ hentry * spellsharps(char * base, char *, int, int, char * tmp, int * info, char **root);
+ int is_keepcase(const hentry * rv);
+ int insert_sug(char ***slst, char * word, int ns);
+ void cat_result(char * result, char * st);
+ char * stem_description(const char * desc);
+ int spellml(char*** slst, const char * word);
+ int get_xml_par(char * dest, const char * par, int maxl);
+ const char * get_xml_pos(const char * s, const char * attr);
+ int get_xml_list(char ***slst, char * list, const char * tag);
+ int check_xml_par(const char * q, const char * attr, const char * value);
+
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/hunvisapi.h b/src/cpp/core/spelling/hunspell/hunvisapi.h
new file mode 100644
index 0000000..4712280
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunvisapi.h
@@ -0,0 +1,18 @@
+#ifndef _HUNSPELL_VISIBILITY_H_
+#define _HUNSPELL_VISIBILITY_H_
+
+#if defined(HUNSPELL_STATIC)
+# define LIBHUNSPELL_DLL_EXPORTED
+#elif defined(_MSC_VER)
+# if defined(BUILDING_LIBHUNSPELL)
+# define LIBHUNSPELL_DLL_EXPORTED __declspec(dllexport)
+# else
+# define LIBHUNSPELL_DLL_EXPORTED __declspec(dllimport)
+# endif
+#elif BUILDING_LIBHUNSPELL && 1
+# define LIBHUNSPELL_DLL_EXPORTED __attribute__((__visibility__("default")))
+#else
+# define LIBHUNSPELL_DLL_EXPORTED
+#endif
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/hunzip.cxx b/src/cpp/core/spelling/hunspell/hunzip.cxx
new file mode 100644
index 0000000..b50599f
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunzip.cxx
@@ -0,0 +1,193 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "hunzip.hxx"
+
+#define CODELEN 65536
+#define BASEBITREC 5000
+
+#define UNCOMPRESSED '\002'
+#define MAGIC "hz0"
+#define MAGIC_ENCRYPT "hz1"
+#define MAGICLEN (sizeof(MAGIC) - 1)
+
+int Hunzip::fail(const char * err, const char * par) {
+ fprintf(stderr, err, par);
+ return -1;
+}
+
+Hunzip::Hunzip(const char * file, const char * key) {
+ bufsiz = 0;
+ lastbit = 0;
+ inc = 0;
+ outc = 0;
+ dec = NULL;
+ fin = NULL;
+ filename = (char *) malloc(strlen(file) + 1);
+ if (filename) strcpy(filename, file);
+ if (getcode(key) == -1) bufsiz = -1;
+ else bufsiz = getbuf();
+}
+
+int Hunzip::getcode(const char * key) {
+ unsigned char c[2];
+ int i, j, n, p;
+ int allocatedbit = BASEBITREC;
+ const char * enc = key;
+
+ if (!filename) return -1;
+
+ fin = fopen(filename, "rb");
+ if (!fin) return -1;
+
+ // read magic number
+ if ((fread(in, 1, 3, fin) < MAGICLEN)
+ || !(strncmp(MAGIC, in, MAGICLEN) == 0 ||
+ strncmp(MAGIC_ENCRYPT, in, MAGICLEN) == 0)) {
+ return fail(MSG_FORMAT, filename);
+ }
+
+ // check encryption
+ if (strncmp(MAGIC_ENCRYPT, in, MAGICLEN) == 0) {
+ unsigned char cs;
+ if (!key) return fail(MSG_KEY, filename);
+ if (fread(&c, 1, 1, fin) < 1) return fail(MSG_FORMAT, filename);
+ for (cs = 0; *enc; enc++) cs ^= *enc;
+ if (cs != c[0]) return fail(MSG_KEY, filename);
+ enc = key;
+ } else key = NULL;
+
+ // read record count
+ if (fread(&c, 1, 2, fin) < 2) return fail(MSG_FORMAT, filename);
+
+ if (key) {
+ c[0] ^= *enc;
+ if (*(++enc) == '\0') enc = key;
+ c[1] ^= *enc;
+ }
+
+ n = ((int) c[0] << 8) + c[1];
+ dec = (struct bit *) malloc(BASEBITREC * sizeof(struct bit));
+ if (!dec) return fail(MSG_MEMORY, filename);
+ dec[0].v[0] = 0;
+ dec[0].v[1] = 0;
+
+ // read codes
+ for (i = 0; i < n; i++) {
+ unsigned char l;
+ if (fread(c, 1, 2, fin) < 2) return fail(MSG_FORMAT, filename);
+ if (key) {
+ if (*(++enc) == '\0') enc = key;
+ c[0] ^= *enc;
+ if (*(++enc) == '\0') enc = key;
+ c[1] ^= *enc;
+ }
+ if (fread(&l, 1, 1, fin) < 1) return fail(MSG_FORMAT, filename);
+ if (key) {
+ if (*(++enc) == '\0') enc = key;
+ l ^= *enc;
+ }
+ if (fread(in, 1, l/8+1, fin) < (size_t) l/8+1) return fail(MSG_FORMAT, filename);
+ if (key) for (j = 0; j <= l/8; j++) {
+ if (*(++enc) == '\0') enc = key;
+ in[j] ^= *enc;
+ }
+ p = 0;
+ for (j = 0; j < l; j++) {
+ int b = (in[j/8] & (1 << (7 - (j % 8)))) ? 1 : 0;
+ int oldp = p;
+ p = dec[p].v[b];
+ if (p == 0) {
+ lastbit++;
+ if (lastbit == allocatedbit) {
+ allocatedbit += BASEBITREC;
+ dec = (struct bit *) realloc(dec, allocatedbit * sizeof(struct bit));
+ }
+ dec[lastbit].v[0] = 0;
+ dec[lastbit].v[1] = 0;
+ dec[oldp].v[b] = lastbit;
+ p = lastbit;
+ }
+ }
+ dec[p].c[0] = c[0];
+ dec[p].c[1] = c[1];
+ }
+ return 0;
+}
+
+Hunzip::~Hunzip()
+{
+ if (dec) free(dec);
+ if (fin) fclose(fin);
+ if (filename) free(filename);
+}
+
+int Hunzip::getbuf() {
+ int p = 0;
+ int o = 0;
+ do {
+ if (inc == 0) inbits = fread(in, 1, BUFSIZE, fin) * 8;
+ for (; inc < inbits; inc++) {
+ int b = (in[inc / 8] & (1 << (7 - (inc % 8)))) ? 1 : 0;
+ int oldp = p;
+ p = dec[p].v[b];
+ if (p == 0) {
+ if (oldp == lastbit) {
+ fclose(fin);
+ fin = NULL;
+ // add last odd byte
+ if (dec[lastbit].c[0]) out[o++] = dec[lastbit].c[1];
+ return o;
+ }
+ out[o++] = dec[oldp].c[0];
+ out[o++] = dec[oldp].c[1];
+ if (o == BUFSIZE) return o;
+ p = dec[p].v[b];
+ }
+ }
+ inc = 0;
+ } while (inbits == BUFSIZE * 8);
+ return fail(MSG_FORMAT, filename);
+}
+
+const char * Hunzip::getline() {
+ char linebuf[BUFSIZE];
+ int l = 0, eol = 0, left = 0, right = 0;
+ if (bufsiz == -1) return NULL;
+ while (l < bufsiz && !eol) {
+ linebuf[l++] = out[outc];
+ switch (out[outc]) {
+ case '\t': break;
+ case 31: { // escape
+ if (++outc == bufsiz) {
+ bufsiz = getbuf();
+ outc = 0;
+ }
+ linebuf[l - 1] = out[outc];
+ break;
+ }
+ case ' ': break;
+ default: if (((unsigned char) out[outc]) < 47) {
+ if (out[outc] > 32) {
+ right = out[outc] - 31;
+ if (++outc == bufsiz) {
+ bufsiz = getbuf();
+ outc = 0;
+ }
+ }
+ if (out[outc] == 30) left = 9; else left = out[outc];
+ linebuf[l-1] = '\n';
+ eol = 1;
+ }
+ }
+ if (++outc == bufsiz) {
+ outc = 0;
+ bufsiz = fin ? getbuf(): -1;
+ }
+ }
+ if (right) strcpy(linebuf + l - 1, line + strlen(line) - right - 1);
+ else linebuf[l] = '\0';
+ strcpy(line + left, linebuf);
+ return line;
+}
diff --git a/src/cpp/core/spelling/hunspell/hunzip.hxx b/src/cpp/core/spelling/hunspell/hunzip.hxx
new file mode 100644
index 0000000..b58e3ab
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/hunzip.hxx
@@ -0,0 +1,45 @@
+/* hunzip: file decompression for sorted dictionaries with optional encryption,
+ * algorithm: prefix-suffix encoding and 16-bit Huffman encoding */
+
+#ifndef _HUNZIP_HXX_
+#define _HUNZIP_HXX_
+
+#include "hunvisapi.h"
+
+#include <stdio.h>
+
+#define BUFSIZE 65536
+#define HZIP_EXTENSION ".hz"
+
+#define MSG_OPEN "error: %s: cannot open\n"
+#define MSG_FORMAT "error: %s: not in hzip format\n"
+#define MSG_MEMORY "error: %s: missing memory\n"
+#define MSG_KEY "error: %s: missing or bad password\n"
+
+struct bit {
+ unsigned char c[2];
+ int v[2];
+};
+
+class LIBHUNSPELL_DLL_EXPORTED Hunzip
+{
+
+protected:
+ char * filename;
+ FILE * fin;
+ int bufsiz, lastbit, inc, inbits, outc;
+ struct bit * dec; // code table
+ char in[BUFSIZE]; // input buffer
+ char out[BUFSIZE + 1]; // Huffman-decoded buffer
+ char line[BUFSIZE + 50]; // decoded line
+ int getcode(const char * key);
+ int getbuf();
+ int fail(const char * err, const char * par);
+
+public:
+ Hunzip(const char * filename, const char * key = NULL);
+ ~Hunzip();
+ const char * getline();
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/langnum.hxx b/src/cpp/core/spelling/hunspell/langnum.hxx
new file mode 100644
index 0000000..1d140a7
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/langnum.hxx
@@ -0,0 +1,38 @@
+#ifndef _LANGNUM_HXX_
+#define _LANGNUM_HXX_
+
+/*
+ language numbers for language specific codes
+ see http://l10n.openoffice.org/languages.html
+*/
+
+enum {
+LANG_ar=96,
+LANG_az=100, // custom number
+LANG_bg=41,
+LANG_ca=37,
+LANG_cs=42,
+LANG_da=45,
+LANG_de=49,
+LANG_el=30,
+LANG_en=01,
+LANG_es=34,
+LANG_eu=10,
+LANG_fr=02,
+LANG_gl=38,
+LANG_hr=78,
+LANG_hu=36,
+LANG_it=39,
+LANG_la=99, // custom number
+LANG_lv=101, // custom number
+LANG_nl=31,
+LANG_pl=48,
+LANG_pt=03,
+LANG_ru=07,
+LANG_sv=50,
+LANG_tr=90,
+LANG_uk=80,
+LANG_xx=999
+};
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/license.hunspell b/src/cpp/core/spelling/hunspell/license.hunspell
new file mode 100644
index 0000000..490e440
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/license.hunspell
@@ -0,0 +1,59 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Hunspell, based on MySpell.
+ *
+ * The Initial Developers of the Original Code are
+ * Kevin Hendricks (MySpell) and Laszlo Nemeth (Hunspell).
+ * Portions created by the Initial Developers are Copyright (C) 2002-2005
+ * the Initial Developers. All Rights Reserved.
+ *
+ * Contributor(s):
+ * David Einstein
+ * Davide Prina
+ * Giuseppe Modugno
+ * Gianluca Turconi
+ * Simon Brouwer
+ * Noll Janos
+ * Biro Arpad
+ * Goldman Eleonora
+ * Sarlos Tamas
+ * Bencsath Boldizsar
+ * Halacsy Peter
+ * Dvornik Laszlo
+ * Gefferth Andras
+ * Nagy Viktor
+ * Varga Daniel
+ * Chris Halls
+ * Rene Engelhard
+ * Bram Moolenaar
+ * Dafydd Jones
+ * Harri Pitkanen
+ * Andras Timar
+ * Tor Lillqvist
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "config.h"
diff --git a/src/cpp/core/spelling/hunspell/license.myspell b/src/cpp/core/spelling/hunspell/license.myspell
new file mode 100644
index 0000000..2da5330
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/license.myspell
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
+ * And Contributors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 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.
+ *
+ * 3. All modifications to the source code must be clearly marked as
+ * such. Binary redistributions based on modified source code
+ * must be clearly marked as modified versions in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS 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
+ * KEVIN B. HENDRICKS 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.
+ *
+ *
+ * NOTE: A special thanks and credit goes to Geoff Kuenning
+ * the creator of ispell. MySpell's affix algorithms were
+ * based on those of ispell which should be noted is
+ * copyright Geoff Kuenning et.al. and now available
+ * under a BSD style license. For more information on ispell
+ * and affix compression in general, please see:
+ * http://www.cs.ucla.edu/ficus-members/geoff/ispell.html
+ * (the home page for ispell)
+ *
+ * An almost complete rewrite of MySpell for use by
+ * the Mozilla project has been developed by David Einstein
+ * (Deinst at world.std.com). David and I are now
+ * working on parallel development tracks to help
+ * our respective projects (Mozilla and OpenOffice.org
+ * and we will maintain full affix file and dictionary
+ * file compatibility and work on merging our versions
+ * of MySpell back into a single tree. David has been
+ * a significant help in improving MySpell.
+ *
+ * Special thanks also go to La'szlo' Ne'meth
+ * <nemethl at gyorsposta.hu> who is the author of the
+ * Hungarian dictionary and who developed and contributed
+ * the code to support compound words in MySpell
+ * and fixed numerous problems with the encoding
+ * case conversion tables.
+ *
+ */
diff --git a/src/cpp/core/spelling/hunspell/phonet.cxx b/src/cpp/core/spelling/hunspell/phonet.cxx
new file mode 100644
index 0000000..144bd40
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/phonet.cxx
@@ -0,0 +1,292 @@
+/* phonetic.c - generic replacement aglogithms for phonetic transformation
+ Copyright (C) 2000 Bjoern Jacke
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2.1 as published by the Free Software Foundation;
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; If not, see
+ <http://www.gnu.org/licenses/>.
+
+ Changelog:
+
+ 2000-01-05 Bjoern Jacke <bjoern at j3e.de>
+ Initial Release insprired by the article about phonetic
+ transformations out of c't 25/1999
+
+ 2007-07-26 Bjoern Jacke <bjoern at j3e.de>
+ Released under MPL/GPL/LGPL tri-license for Hunspell
+
+ 2007-08-23 Laszlo Nemeth <nemeth at OOo>
+ Porting from Aspell to Hunspell using C-like structs
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "csutil.hxx"
+#include "phonet.hxx"
+
+void init_phonet_hash(phonetable & parms)
+ {
+ int i, k;
+
+ for (i = 0; i < HASHSIZE; i++) {
+ parms.hash[i] = -1;
+ }
+
+ for (i = 0; parms.rules[i][0] != '\0'; i += 2) {
+ /** set hash value **/
+ k = (unsigned char) parms.rules[i][0];
+
+ if (parms.hash[k] < 0) {
+ parms.hash[k] = i;
+ }
+ }
+ }
+
+// like strcpy but safe if the strings overlap
+// but only if dest < src
+static inline void strmove(char * dest, char * src) {
+ while (*src)
+ *dest++ = *src++;
+ *dest = '\0';
+}
+
+static int myisalpha(char ch) {
+ if ((unsigned char) ch < 128) return isalpha(ch);
+ return 1;
+}
+
+/* phonetic transcription algorithm */
+/* see: http://aspell.net/man-html/Phonetic-Code.html */
+/* convert string to uppercase before this call */
+int phonet (const char * inword, char * target,
+ int len,
+ phonetable & parms)
+ {
+ /** Do phonetic transformation. **/
+ /** "len" = length of "inword" incl. '\0'. **/
+
+ /** result: >= 0: length of "target" **/
+ /** otherwise: error **/
+
+ int i,j,k=0,n,p,z;
+ int k0,n0,p0=-333,z0;
+ char c, c0;
+ const char * s;
+ typedef unsigned char uchar;
+ char word[MAXPHONETUTF8LEN + 1];
+ if (len == -1) len = strlen(inword);
+ if (len > MAXPHONETUTF8LEN) return 0;
+ strcpy(word, inword);
+
+ /** check word **/
+ i = j = z = 0;
+ while ((c = word[i]) != '\0') {
+ n = parms.hash[(uchar) c];
+ z0 = 0;
+
+ if (n >= 0) {
+ /** check all rules for the same letter **/
+ while (parms.rules[n][0] == c) {
+
+ /** check whole string **/
+ k = 1; /** number of found letters **/
+ p = 5; /** default priority **/
+ s = parms.rules[n];
+ s++; /** important for (see below) "*(s-1)" **/
+
+ while (*s != '\0' && word[i+k] == *s
+ && !isdigit ((unsigned char) *s) && strchr ("(-<^$", *s) == NULL) {
+ k++;
+ s++;
+ }
+ if (*s == '(') {
+ /** check letters in "(..)" **/
+ if (myisalpha(word[i+k]) // ...could be implied?
+ && strchr(s+1, word[i+k]) != NULL) {
+ k++;
+ while (*s != ')')
+ s++;
+ s++;
+ }
+ }
+ p0 = (int) *s;
+ k0 = k;
+ while (*s == '-' && k > 1) {
+ k--;
+ s++;
+ }
+ if (*s == '<')
+ s++;
+ if (isdigit ((unsigned char) *s)) {
+ /** determine priority **/
+ p = *s - '0';
+ s++;
+ }
+ if (*s == '^' && *(s+1) == '^')
+ s++;
+
+ if (*s == '\0'
+ || (*s == '^'
+ && (i == 0 || ! myisalpha(word[i-1]))
+ && (*(s+1) != '$'
+ || (! myisalpha(word[i+k0]) )))
+ || (*s == '$' && i > 0
+ && myisalpha(word[i-1])
+ && (! myisalpha(word[i+k0]) )))
+ {
+ /** search for followup rules, if: **/
+ /** parms.followup and k > 1 and NO '-' in searchstring **/
+ c0 = word[i+k-1];
+ n0 = parms.hash[(uchar) c0];
+
+// if (parms.followup && k > 1 && n0 >= 0
+ if (k > 1 && n0 >= 0
+ && p0 != (int) '-' && word[i+k] != '\0') {
+ /** test follow-up rule for "word[i+k]" **/
+ while (parms.rules[n0][0] == c0) {
+
+ /** check whole string **/
+ k0 = k;
+ p0 = 5;
+ s = parms.rules[n0];
+ s++;
+ while (*s != '\0' && word[i+k0] == *s
+ && ! isdigit((unsigned char) *s) && strchr("(-<^$",*s) == NULL) {
+ k0++;
+ s++;
+ }
+ if (*s == '(') {
+ /** check letters **/
+ if (myisalpha(word[i+k0])
+ && strchr (s+1, word[i+k0]) != NULL) {
+ k0++;
+ while (*s != ')' && *s != '\0')
+ s++;
+ if (*s == ')')
+ s++;
+ }
+ }
+ while (*s == '-') {
+ /** "k0" gets NOT reduced **/
+ /** because "if (k0 == k)" **/
+ s++;
+ }
+ if (*s == '<')
+ s++;
+ if (isdigit ((unsigned char) *s)) {
+ p0 = *s - '0';
+ s++;
+ }
+
+ if (*s == '\0'
+ /** *s == '^' cuts **/
+ || (*s == '$' && ! myisalpha(word[i+k0])))
+ {
+ if (k0 == k) {
+ /** this is just a piece of the string **/
+ n0 += 2;
+ continue;
+ }
+
+ if (p0 < p) {
+ /** priority too low **/
+ n0 += 2;
+ continue;
+ }
+ /** rule fits; stop search **/
+ break;
+ }
+ n0 += 2;
+ } /** End of "while (parms.rules[n0][0] == c0)" **/
+
+ if (p0 >= p && parms.rules[n0][0] == c0) {
+ n += 2;
+ continue;
+ }
+ } /** end of follow-up stuff **/
+
+ /** replace string **/
+ s = parms.rules[n+1];
+ p0 = (parms.rules[n][0] != '\0'
+ && strchr (parms.rules[n]+1,'<') != NULL) ? 1:0;
+ if (p0 == 1 && z == 0) {
+ /** rule with '<' is used **/
+ if (j > 0 && *s != '\0'
+ && (target[j-1] == c || target[j-1] == *s)) {
+ j--;
+ }
+ z0 = 1;
+ z = 1;
+ k0 = 0;
+ while (*s != '\0' && word[i+k0] != '\0') {
+ word[i+k0] = *s;
+ k0++;
+ s++;
+ }
+ if (k > k0)
+ strmove (&word[0]+i+k0, &word[0]+i+k);
+
+ /** new "actual letter" **/
+ c = word[i];
+ }
+ else { /** no '<' rule used **/
+ i += k - 1;
+ z = 0;
+ while (*s != '\0'
+ && *(s+1) != '\0' && j < len) {
+ if (j == 0 || target[j-1] != *s) {
+ target[j] = *s;
+ j++;
+ }
+ s++;
+ }
+ /** new "actual letter" **/
+ c = *s;
+ if (parms.rules[n][0] != '\0'
+ && strstr (parms.rules[n]+1, "^^") != NULL) {
+ if (c != '\0') {
+ target[j] = c;
+ j++;
+ }
+ strmove (&word[0], &word[0]+i+1);
+ i = 0;
+ z0 = 1;
+ }
+ }
+ break;
+ } /** end of follow-up stuff **/
+ n += 2;
+ } /** end of while (parms.rules[n][0] == c) **/
+ } /** end of if (n >= 0) **/
+ if (z0 == 0) {
+// if (k && (assert(p0!=-333),!p0) && j < len && c != '\0'
+// && (!parms.collapse_result || j == 0 || target[j-1] != c)){
+ if (k && !p0 && j < len && c != '\0'
+ && (1 || j == 0 || target[j-1] != c)){
+ /** condense only double letters **/
+ target[j] = c;
+ ///printf("\n setting \n");
+ j++;
+ }
+
+ i++;
+ z = 0;
+ k=0;
+ }
+ } /** end of while ((c = word[i]) != '\0') **/
+
+ target[j] = '\0';
+ return (j);
+
+ } /** end of function "phonet" **/
diff --git a/src/cpp/core/spelling/hunspell/phonet.hxx b/src/cpp/core/spelling/hunspell/phonet.hxx
new file mode 100644
index 0000000..f91d3b0
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/phonet.hxx
@@ -0,0 +1,52 @@
+/* phonetic.c - generic replacement aglogithms for phonetic transformation
+ Copyright (C) 2000 Bjoern Jacke
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2.1 as published by the Free Software Foundation;
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; If not, see
+ <http://www.gnu.org/licenses/>.
+
+ Changelog:
+
+ 2000-01-05 Bjoern Jacke <bjoern at j3e.de>
+ Initial Release insprired by the article about phonetic
+ transformations out of c't 25/1999
+
+ 2007-07-26 Bjoern Jacke <bjoern at j3e.de>
+ Released under MPL/GPL/LGPL tri-license for Hunspell
+
+ 2007-08-23 Laszlo Nemeth <nemeth at OOo>
+ Porting from Aspell to Hunspell using C-like structs
+*/
+
+#ifndef __PHONETHXX__
+#define __PHONETHXX__
+
+#define HASHSIZE 256
+#define MAXPHONETLEN 256
+#define MAXPHONETUTF8LEN (MAXPHONETLEN * 4)
+
+#include "hunvisapi.h"
+
+struct phonetable {
+ char utf8;
+ cs_info * lang;
+ int num;
+ char * * rules;
+ int hash[HASHSIZE];
+};
+
+LIBHUNSPELL_DLL_EXPORTED void init_phonet_hash(phonetable & parms);
+
+LIBHUNSPELL_DLL_EXPORTED int phonet (const char * inword, char * target,
+ int len, phonetable & phone);
+
+#endif
diff --git a/src/cpp/core/spelling/hunspell/replist.cxx b/src/cpp/core/spelling/hunspell/replist.cxx
new file mode 100644
index 0000000..080cd68
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/replist.cxx
@@ -0,0 +1,87 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "replist.hxx"
+#include "csutil.hxx"
+
+RepList::RepList(int n) {
+ dat = (replentry **) malloc(sizeof(replentry *) * n);
+ if (dat == 0) size = 0; else size = n;
+ pos = 0;
+}
+
+RepList::~RepList()
+{
+ for (int i = 0; i < pos; i++) {
+ free(dat[i]->pattern);
+ free(dat[i]->pattern2);
+ free(dat[i]);
+ }
+ free(dat);
+}
+
+int RepList::get_pos() {
+ return pos;
+}
+
+replentry * RepList::item(int n) {
+ return dat[n];
+}
+
+int RepList::near(const char * word) {
+ int p1 = 0;
+ int p2 = pos;
+ while ((p2 - p1) > 1) {
+ int m = (p1 + p2) / 2;
+ int c = strcmp(word, dat[m]->pattern);
+ if (c <= 0) {
+ if (c < 0) p2 = m; else p1 = p2 = m;
+ } else p1 = m;
+ }
+ return p1;
+}
+
+int RepList::match(const char * word, int n) {
+ if (strncmp(word, dat[n]->pattern, strlen(dat[n]->pattern)) == 0) return strlen(dat[n]->pattern);
+ return 0;
+}
+
+int RepList::add(char * pat1, char * pat2) {
+ if (pos >= size || pat1 == NULL || pat2 == NULL) return 1;
+ replentry * r = (replentry *) malloc(sizeof(replentry));
+ if (r == NULL) return 1;
+ r->pattern = mystrrep(pat1, "_", " ");
+ r->pattern2 = mystrrep(pat2, "_", " ");
+ r->start = false;
+ r->end = false;
+ dat[pos++] = r;
+ for (int i = pos - 1; i > 0; i--) {
+ r = dat[i];
+ if (strcmp(r->pattern, dat[i - 1]->pattern) < 0) {
+ dat[i] = dat[i - 1];
+ dat[i - 1] = r;
+ } else break;
+ }
+ return 0;
+}
+
+int RepList::conv(const char * word, char * dest) {
+ int stl = 0;
+ int change = 0;
+ for (size_t i = 0; i < strlen(word); i++) {
+ int n = near(word + i);
+ int l = match(word + i, n);
+ if (l) {
+ strcpy(dest + stl, dat[n]->pattern2);
+ stl += strlen(dat[n]->pattern2);
+ i += l - 1;
+ change = 1;
+ } else dest[stl++] = word[i];
+ }
+ dest[stl] = '\0';
+ return change;
+}
diff --git a/src/cpp/core/spelling/hunspell/replist.hxx b/src/cpp/core/spelling/hunspell/replist.hxx
new file mode 100644
index 0000000..9c37e29
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/replist.hxx
@@ -0,0 +1,27 @@
+/* string replacement list class */
+#ifndef _REPLIST_HXX_
+#define _REPLIST_HXX_
+
+#include "hunvisapi.h"
+
+#include "w_char.hxx"
+
+class LIBHUNSPELL_DLL_EXPORTED RepList
+{
+protected:
+ replentry ** dat;
+ int size;
+ int pos;
+
+public:
+ RepList(int n);
+ ~RepList();
+
+ int get_pos();
+ int add(char * pat1, char * pat2);
+ replentry * item(int n);
+ int near(const char * word);
+ int match(const char * word, int n);
+ int conv(const char * word, char * dest);
+};
+#endif
diff --git a/src/cpp/core/spelling/hunspell/suggestmgr.cxx b/src/cpp/core/spelling/hunspell/suggestmgr.cxx
new file mode 100644
index 0000000..ebf9bc0
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/suggestmgr.cxx
@@ -0,0 +1,2004 @@
+#include "license.hunspell"
+#include "license.myspell"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "suggestmgr.hxx"
+#include "htypes.hxx"
+#include "csutil.hxx"
+
+const w_char W_VLINE = { '\0', '|' };
+
+SuggestMgr::SuggestMgr(const char * tryme, int maxn,
+ AffixMgr * aptr)
+{
+
+ // register affix manager and check in string of chars to
+ // try when building candidate suggestions
+ pAMgr = aptr;
+
+ csconv = NULL;
+
+ ckeyl = 0;
+ ckey = NULL;
+ ckey_utf = NULL;
+
+ ctryl = 0;
+ ctry = NULL;
+ ctry_utf = NULL;
+
+ utf8 = 0;
+ langnum = 0;
+ complexprefixes = 0;
+
+ maxSug = maxn;
+ nosplitsugs = 0;
+ maxngramsugs = MAXNGRAMSUGS;
+ maxcpdsugs = MAXCOMPOUNDSUGS;
+
+ if (pAMgr) {
+ langnum = pAMgr->get_langnum();
+ ckey = pAMgr->get_key_string();
+ nosplitsugs = pAMgr->get_nosplitsugs();
+ if (pAMgr->get_maxngramsugs() >= 0)
+ maxngramsugs = pAMgr->get_maxngramsugs();
+ utf8 = pAMgr->get_utf8();
+ if (pAMgr->get_maxcpdsugs() >= 0)
+ maxcpdsugs = pAMgr->get_maxcpdsugs();
+ if (!utf8)
+ {
+ char * enc = pAMgr->get_encoding();
+ csconv = get_current_cs(enc);
+ free(enc);
+ }
+ complexprefixes = pAMgr->get_complexprefixes();
+ }
+
+ if (ckey) {
+ if (utf8) {
+ w_char t[MAXSWL];
+ ckeyl = u8_u16(t, MAXSWL, ckey);
+ ckey_utf = (w_char *) malloc(ckeyl * sizeof(w_char));
+ if (ckey_utf) memcpy(ckey_utf, t, ckeyl * sizeof(w_char));
+ else ckeyl = 0;
+ } else {
+ ckeyl = strlen(ckey);
+ }
+ }
+
+ if (tryme) {
+ ctry = mystrdup(tryme);
+ if (ctry) ctryl = strlen(ctry);
+ if (ctry && utf8) {
+ w_char t[MAXSWL];
+ ctryl = u8_u16(t, MAXSWL, tryme);
+ ctry_utf = (w_char *) malloc(ctryl * sizeof(w_char));
+ if (ctry_utf) memcpy(ctry_utf, t, ctryl * sizeof(w_char));
+ else ctryl = 0;
+ }
+ }
+}
+
+
+SuggestMgr::~SuggestMgr()
+{
+ pAMgr = NULL;
+ if (ckey) free(ckey);
+ ckey = NULL;
+ if (ckey_utf) free(ckey_utf);
+ ckey_utf = NULL;
+ ckeyl = 0;
+ if (ctry) free(ctry);
+ ctry = NULL;
+ if (ctry_utf) free(ctry_utf);
+ ctry_utf = NULL;
+ ctryl = 0;
+ maxSug = 0;
+#ifdef MOZILLA_CLIENT
+ delete [] csconv;
+#endif
+}
+
+int SuggestMgr::testsug(char** wlst, const char * candidate, int wl, int ns, int cpdsuggest,
+ int * timer, clock_t * timelimit) {
+ int cwrd = 1;
+ if (ns == maxSug) return maxSug;
+ for (int k=0; k < ns; k++) {
+ if (strcmp(candidate,wlst[k]) == 0) cwrd = 0;
+ }
+ if ((cwrd) && checkword(candidate, wl, cpdsuggest, timer, timelimit)) {
+ wlst[ns] = mystrdup(candidate);
+ if (wlst[ns] == NULL) {
+ for (int j=0; j<ns; j++) free(wlst[j]);
+ return -1;
+ }
+ ns++;
+ }
+ return ns;
+}
+
+// generate suggestions for a misspelled word
+// pass in address of array of char * pointers
+// onlycompoundsug: probably bad suggestions (need for ngram sugs, too)
+
+int SuggestMgr::suggest(char*** slst, const char * w, int nsug,
+ int * onlycompoundsug)
+{
+ int nocompoundtwowords = 0;
+ char ** wlst;
+ w_char word_utf[MAXSWL];
+ int wl = 0;
+ int nsugorig = nsug;
+ char w2[MAXWORDUTF8LEN];
+ const char * word = w;
+ int oldSug = 0;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ strcpy(w2, w);
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ word = w2;
+ }
+
+ if (*slst) {
+ wlst = *slst;
+ } else {
+ wlst = (char **) malloc(maxSug * sizeof(char *));
+ if (wlst == NULL) return -1;
+ for (int i = 0; i < maxSug; i++) {
+ wlst[i] = NULL;
+ }
+ }
+
+ if (utf8) {
+ wl = u8_u16(word_utf, MAXSWL, word);
+ if (wl == -1) {
+ *slst = wlst;
+ return nsug;
+ }
+ }
+
+ for (int cpdsuggest=0; (cpdsuggest<2) && (nocompoundtwowords==0); cpdsuggest++) {
+
+ // limit compound suggestion
+ if (cpdsuggest > 0) oldSug = nsug;
+
+ // suggestions for an uppercase word (html -> HTML)
+ if ((nsug < maxSug) && (nsug > -1)) {
+ nsug = (utf8) ? capchars_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ capchars(wlst, word, nsug, cpdsuggest);
+ }
+
+ // perhaps we made a typical fault of spelling
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = replchars(wlst, word, nsug, cpdsuggest);
+ }
+
+ // perhaps we made chose the wrong char from a related set
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = mapchars(wlst, word, nsug, cpdsuggest);
+ }
+
+ // only suggest compound words when no other suggestion
+ if ((cpdsuggest == 0) && (nsug > nsugorig)) nocompoundtwowords=1;
+
+ // did we swap the order of chars by mistake
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? swapchar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ swapchar(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we swap the order of non adjacent chars by mistake
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? longswapchar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ longswapchar(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we just hit the wrong key in place of a good char (case and keyboard)
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? badcharkey_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ badcharkey(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we add a char that should not be there
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? extrachar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ extrachar(wlst, word, nsug, cpdsuggest);
+ }
+
+
+ // did we forgot a char
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? forgotchar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ forgotchar(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we move a char
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? movechar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ movechar(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we just hit the wrong key in place of a good char
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? badchar_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ badchar(wlst, word, nsug, cpdsuggest);
+ }
+
+ // did we double two characters
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = (utf8) ? doubletwochars_utf(wlst, word_utf, wl, nsug, cpdsuggest) :
+ doubletwochars(wlst, word, nsug, cpdsuggest);
+ }
+
+ // perhaps we forgot to hit space and two words ran together
+ if (!nosplitsugs && (nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs))) {
+ nsug = twowords(wlst, word, nsug, cpdsuggest);
+ }
+
+ } // repeating ``for'' statement compounding support
+
+ if (nsug < 0) {
+ // we ran out of memory - we should free up as much as possible
+ for (int i = 0; i < maxSug; i++)
+ if (wlst[i] != NULL) free(wlst[i]);
+ free(wlst);
+ wlst = NULL;
+ }
+
+ if (!nocompoundtwowords && (nsug > 0) && onlycompoundsug) *onlycompoundsug = 1;
+
+ *slst = wlst;
+ return nsug;
+}
+
+// generate suggestions for a word with typical mistake
+// pass in address of array of char * pointers
+#ifdef HUNSPELL_EXPERIMENTAL
+int SuggestMgr::suggest_auto(char*** slst, const char * w, int nsug)
+{
+ int nocompoundtwowords = 0;
+ char ** wlst;
+ int oldSug;
+
+ char w2[MAXWORDUTF8LEN];
+ const char * word = w;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ strcpy(w2, w);
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ word = w2;
+ }
+
+ if (*slst) {
+ wlst = *slst;
+ } else {
+ wlst = (char **) malloc(maxSug * sizeof(char *));
+ if (wlst == NULL) return -1;
+ }
+
+ for (int cpdsuggest=0; (cpdsuggest<2) && (nocompoundtwowords==0); cpdsuggest++) {
+
+ // limit compound suggestion
+ if (cpdsuggest > 0) oldSug = nsug;
+
+ // perhaps we made a typical fault of spelling
+ if ((nsug < maxSug) && (nsug > -1))
+ nsug = replchars(wlst, word, nsug, cpdsuggest);
+
+ // perhaps we made chose the wrong char from a related set
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs)))
+ nsug = mapchars(wlst, word, nsug, cpdsuggest);
+
+ if ((cpdsuggest==0) && (nsug>0)) nocompoundtwowords=1;
+
+ // perhaps we forgot to hit space and two words ran together
+
+ if ((nsug < maxSug) && (nsug > -1) && (!cpdsuggest || (nsug < oldSug + maxcpdsugs)) && check_forbidden(word, strlen(word))) {
+ nsug = twowords(wlst, word, nsug, cpdsuggest);
+ }
+
+ } // repeating ``for'' statement compounding support
+
+ if (nsug < 0) {
+ for (int i=0;i<maxSug; i++)
+ if (wlst[i] != NULL) free(wlst[i]);
+ free(wlst);
+ return -1;
+ }
+
+ *slst = wlst;
+ return nsug;
+}
+#endif // END OF HUNSPELL_EXPERIMENTAL CODE
+
+// suggestions for an uppercase word (html -> HTML)
+int SuggestMgr::capchars_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ w_char candidate_utf[MAXSWL];
+ memcpy(candidate_utf, word, wl * sizeof(w_char));
+ mkallcap_utf(candidate_utf, wl, langnum);
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ return testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+}
+
+// suggestions for an uppercase word (html -> HTML)
+int SuggestMgr::capchars(char** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ strcpy(candidate, word);
+ mkallcap(candidate, csconv);
+ return testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+}
+
+// suggestions for when chose the wrong char out of a related set
+int SuggestMgr::mapchars(char** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ clock_t timelimit;
+ int timer;
+ candidate[0] = '\0';
+
+ int wl = strlen(word);
+ if (wl < 2 || ! pAMgr) return ns;
+
+ int nummap = pAMgr->get_nummap();
+ struct mapentry* maptable = pAMgr->get_maptable();
+ if (maptable==NULL) return ns;
+
+ timelimit = clock();
+ timer = MINTIMER;
+ return map_related(word, (char *) &candidate, 0, 0, wlst, cpdsuggest, ns, maptable, nummap, &timer, &timelimit);
+}
+
+int SuggestMgr::map_related(const char * word, char * candidate, int wn, int cn,
+ char** wlst, int cpdsuggest, int ns,
+ const mapentry* maptable, int nummap, int * timer, clock_t * timelimit)
+{
+ if (*(word + wn) == '\0') {
+ int cwrd = 1;
+ *(candidate + cn) = '\0';
+ int wl = strlen(candidate);
+ for (int m=0; m < ns; m++)
+ if (strcmp(candidate, wlst[m]) == 0) cwrd = 0;
+ if ((cwrd) && checkword(candidate, wl, cpdsuggest, timer, timelimit)) {
+ if (ns < maxSug) {
+ wlst[ns] = mystrdup(candidate);
+ if (wlst[ns] == NULL) return -1;
+ ns++;
+ }
+ }
+ return ns;
+ }
+ int in_map = 0;
+ for (int j = 0; j < nummap; j++) {
+ for (int k = 0; k < maptable[j].len; k++) {
+ int len = strlen(maptable[j].set[k]);
+ if (strncmp(maptable[j].set[k], word + wn, len) == 0) {
+ in_map = 1;
+ for (int l = 0; l < maptable[j].len; l++) {
+ strcpy(candidate + cn, maptable[j].set[l]);
+ ns = map_related(word, candidate, wn + len, strlen(candidate), wlst,
+ cpdsuggest, ns, maptable, nummap, timer, timelimit);
+ if (!(*timer)) return ns;
+ }
+ }
+ }
+ }
+ if (!in_map) {
+ *(candidate + cn) = *(word + wn);
+ ns = map_related(word, candidate, wn + 1, cn + 1, wlst, cpdsuggest,
+ ns, maptable, nummap, timer, timelimit);
+ }
+ return ns;
+}
+
+// suggestions for a typical fault of spelling, that
+// differs with more, than 1 letter from the right form.
+int SuggestMgr::replchars(char** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ const char * r;
+ int lenr, lenp;
+ int wl = strlen(word);
+ if (wl < 2 || ! pAMgr) return ns;
+ int numrep = pAMgr->get_numrep();
+ struct replentry* reptable = pAMgr->get_reptable();
+ if (reptable==NULL) return ns;
+ for (int i=0; i < numrep; i++ ) {
+ r = word;
+ lenr = strlen(reptable[i].pattern2);
+ lenp = strlen(reptable[i].pattern);
+ // search every occurence of the pattern in the word
+ while ((r=strstr(r, reptable[i].pattern)) != NULL && (!reptable[i].end || strlen(r) == strlen(reptable[i].pattern)) &&
+ (!reptable[i].start || r == word)) {
+ strcpy(candidate, word);
+ if (r-word + lenr + strlen(r+lenp) >= MAXSWUTF8L) break;
+ strcpy(candidate+(r-word),reptable[i].pattern2);
+ strcpy(candidate+(r-word)+lenr, r+lenp);
+ ns = testsug(wlst, candidate, wl-lenp+lenr, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ // check REP suggestions with space
+ char * sp = strchr(candidate, ' ');
+ if (sp) {
+ char * prev = candidate;
+ while (sp) {
+ *sp = '\0';
+ if (checkword(prev, strlen(prev), 0, NULL, NULL)) {
+ int oldns = ns;
+ *sp = ' ';
+ ns = testsug(wlst, sp + 1, strlen(sp + 1), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ if (oldns < ns) {
+ free(wlst[ns - 1]);
+ wlst[ns - 1] = mystrdup(candidate);
+ if (!wlst[ns - 1]) return -1;
+ }
+ }
+ *sp = ' ';
+ prev = sp + 1;
+ sp = strchr(prev, ' ');
+ }
+ }
+ r++; // search for the next letter
+ }
+ }
+ return ns;
+}
+
+// perhaps we doubled two characters (pattern aba -> ababa, for example vacation -> vacacation)
+int SuggestMgr::doubletwochars(char** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ int state=0;
+ int wl = strlen(word);
+ if (wl < 5 || ! pAMgr) return ns;
+ for (int i=2; i < wl; i++ ) {
+ if (word[i]==word[i-2]) {
+ state++;
+ if (state==3) {
+ strcpy(candidate,word);
+ strcpy(candidate+i-1,word+i+1);
+ ns = testsug(wlst, candidate, wl-2, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ state=0;
+ }
+ } else {
+ state=0;
+ }
+ }
+ return ns;
+}
+
+// perhaps we doubled two characters (pattern aba -> ababa, for example vacation -> vacacation)
+int SuggestMgr::doubletwochars_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ int state=0;
+ if (wl < 5 || ! pAMgr) return ns;
+ for (int i=2; i < wl; i++) {
+ if (w_char_eq(word[i], word[i-2])) {
+ state++;
+ if (state==3) {
+ memcpy(candidate_utf, word, (i - 1) * sizeof(w_char));
+ memcpy(candidate_utf+i-1, word+i+1, (wl-i-1) * sizeof(w_char));
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl-2);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ state=0;
+ }
+ } else {
+ state=0;
+ }
+ }
+ return ns;
+}
+
+// error is wrong char in place of correct one (case and keyboard related version)
+int SuggestMgr::badcharkey(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char tmpc;
+ char candidate[MAXSWUTF8L];
+ int wl = strlen(word);
+ strcpy(candidate, word);
+ // swap out each char one by one and try uppercase and neighbor
+ // keyboard chars in its place to see if that makes a good word
+
+ for (int i=0; i < wl; i++) {
+ tmpc = candidate[i];
+ // check with uppercase letters
+ candidate[i] = csconv[((unsigned char)tmpc)].cupper;
+ if (tmpc != candidate[i]) {
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ candidate[i] = tmpc;
+ }
+ // check neighbor characters in keyboard string
+ if (!ckey) continue;
+ char * loc = strchr(ckey, tmpc);
+ while (loc) {
+ if ((loc > ckey) && (*(loc - 1) != '|')) {
+ candidate[i] = *(loc - 1);
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ if ((*(loc + 1) != '|') && (*(loc + 1) != '\0')) {
+ candidate[i] = *(loc + 1);
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ loc = strchr(loc + 1, tmpc);
+ }
+ candidate[i] = tmpc;
+ }
+ return ns;
+}
+
+// error is wrong char in place of correct one (case and keyboard related version)
+int SuggestMgr::badcharkey_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char tmpc;
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ memcpy(candidate_utf, word, wl * sizeof(w_char));
+ // swap out each char one by one and try all the tryme
+ // chars in its place to see if that makes a good word
+ for (int i=0; i < wl; i++) {
+ tmpc = candidate_utf[i];
+ // check with uppercase letters
+ mkallcap_utf(candidate_utf + i, 1, langnum);
+ if (!w_char_eq(tmpc, candidate_utf[i])) {
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ candidate_utf[i] = tmpc;
+ }
+ // check neighbor characters in keyboard string
+ if (!ckey) continue;
+ w_char * loc = ckey_utf;
+ while ((loc < (ckey_utf + ckeyl)) && !w_char_eq(*loc, tmpc)) loc++;
+ while (loc < (ckey_utf + ckeyl)) {
+ if ((loc > ckey_utf) && !w_char_eq(*(loc - 1), W_VLINE)) {
+ candidate_utf[i] = *(loc - 1);
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ if (((loc + 1) < (ckey_utf + ckeyl)) && !w_char_eq(*(loc + 1), W_VLINE)) {
+ candidate_utf[i] = *(loc + 1);
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ do { loc++; } while ((loc < (ckey_utf + ckeyl)) && !w_char_eq(*loc, tmpc));
+ }
+ candidate_utf[i] = tmpc;
+ }
+ return ns;
+}
+
+// error is wrong char in place of correct one
+int SuggestMgr::badchar(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char tmpc;
+ char candidate[MAXSWUTF8L];
+ clock_t timelimit = clock();
+ int timer = MINTIMER;
+ int wl = strlen(word);
+ strcpy(candidate, word);
+ // swap out each char one by one and try all the tryme
+ // chars in its place to see if that makes a good word
+ for (int j=0; j < ctryl; j++) {
+ for (int i=wl-1; i >= 0; i--) {
+ tmpc = candidate[i];
+ if (ctry[j] == tmpc) continue;
+ candidate[i] = ctry[j];
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, &timer, &timelimit);
+ if (ns == -1) return -1;
+ if (!timer) return ns;
+ candidate[i] = tmpc;
+ }
+ }
+ return ns;
+}
+
+// error is wrong char in place of correct one
+int SuggestMgr::badchar_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char tmpc;
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ clock_t timelimit = clock();
+ int timer = MINTIMER;
+ memcpy(candidate_utf, word, wl * sizeof(w_char));
+ // swap out each char one by one and try all the tryme
+ // chars in its place to see if that makes a good word
+ for (int j=0; j < ctryl; j++) {
+ for (int i=wl-1; i >= 0; i--) {
+ tmpc = candidate_utf[i];
+ if (w_char_eq(tmpc, ctry_utf[j])) continue;
+ candidate_utf[i] = ctry_utf[j];
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, &timer, &timelimit);
+ if (ns == -1) return -1;
+ if (!timer) return ns;
+ candidate_utf[i] = tmpc;
+ }
+ }
+ return ns;
+}
+
+// error is word has an extra letter it does not need
+int SuggestMgr::extrachar_utf(char** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ w_char candidate_utf[MAXSWL];
+ w_char * p;
+ w_char tmpc = W_VLINE; // not used value, only for VCC warning message
+ if (wl < 2) return ns;
+ // try omitting one char of word at a time
+ memcpy(candidate_utf, word, wl * sizeof(w_char));
+ for (p = candidate_utf + wl - 1; p >= candidate_utf; p--) {
+ w_char tmpc2 = *p;
+ if (p < candidate_utf + wl - 1) *p = tmpc;
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl - 1);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ tmpc = tmpc2;
+ }
+ return ns;
+}
+
+// error is word has an extra letter it does not need
+int SuggestMgr::extrachar(char** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char tmpc = '\0';
+ char candidate[MAXSWUTF8L];
+ char * p;
+ int wl = strlen(word);
+ if (wl < 2) return ns;
+ // try omitting one char of word at a time
+ strcpy (candidate, word);
+ for (p = candidate + wl - 1; p >=candidate; p--) {
+ char tmpc2 = *p;
+ *p = tmpc;
+ ns = testsug(wlst, candidate, wl-1, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ tmpc = tmpc2;
+ }
+ return ns;
+}
+
+// error is missing a letter it needs
+int SuggestMgr::forgotchar(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ char * p;
+ clock_t timelimit = clock();
+ int timer = MINTIMER;
+ int wl = strlen(word);
+ // try inserting a tryme character before every letter (and the null terminator)
+ for (int i = 0; i < ctryl; i++) {
+ strcpy(candidate, word);
+ for (p = candidate + wl; p >= candidate; p--) {
+ *(p+1) = *p;
+ *p = ctry[i];
+ ns = testsug(wlst, candidate, wl+1, ns, cpdsuggest, &timer, &timelimit);
+ if (ns == -1) return -1;
+ if (!timer) return ns;
+ }
+ }
+ return ns;
+}
+
+// error is missing a letter it needs
+int SuggestMgr::forgotchar_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ w_char * p;
+ clock_t timelimit = clock();
+ int timer = MINTIMER;
+ // try inserting a tryme character at the end of the word and before every letter
+ for (int i = 0; i < ctryl; i++) {
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ for (p = candidate_utf + wl; p >= candidate_utf; p--) {
+ *(p + 1) = *p;
+ *p = ctry_utf[i];
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl + 1);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, &timer, &timelimit);
+ if (ns == -1) return -1;
+ if (!timer) return ns;
+ }
+ }
+ return ns;
+}
+
+
+/* error is should have been two words */
+int SuggestMgr::twowords(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ char * p;
+ int c1, c2;
+ int forbidden = 0;
+ int cwrd;
+
+ int wl=strlen(word);
+ if (wl < 3) return ns;
+
+ if (langnum == LANG_hu) forbidden = check_forbidden(word, wl);
+
+ strcpy(candidate + 1, word);
+ // split the string into two pieces after every char
+ // if both pieces are good words make them a suggestion
+ for (p = candidate + 1; p[1] != '\0'; p++) {
+ p[-1] = *p;
+ // go to end of the UTF-8 character
+ while (utf8 && ((p[1] & 0xc0) == 0x80)) {
+ *p = p[1];
+ p++;
+ }
+ if (utf8 && p[1] == '\0') break; // last UTF-8 character
+ *p = '\0';
+ c1 = checkword(candidate,strlen(candidate), cpdsuggest, NULL, NULL);
+ if (c1) {
+ c2 = checkword((p+1),strlen(p+1), cpdsuggest, NULL, NULL);
+ if (c2) {
+ *p = ' ';
+
+ // spec. Hungarian code (need a better compound word support)
+ if ((langnum == LANG_hu) && !forbidden &&
+ // if 3 repeating letter, use - instead of space
+ (((p[-1] == p[1]) && (((p>candidate+1) && (p[-1] == p[-2])) || (p[-1] == p[2]))) ||
+ // or multiple compounding, with more, than 6 syllables
+ ((c1 == 3) && (c2 >= 2)))) *p = '-';
+
+ cwrd = 1;
+ for (int k=0; k < ns; k++)
+ if (strcmp(candidate,wlst[k]) == 0) cwrd = 0;
+ if (ns < maxSug) {
+ if (cwrd) {
+ wlst[ns] = mystrdup(candidate);
+ if (wlst[ns] == NULL) return -1;
+ ns++;
+ }
+ } else return ns;
+ // add two word suggestion with dash, if TRY string contains
+ // "a" or "-"
+ // NOTE: cwrd doesn't modified for REP twoword sugg.
+ if (ctry && (strchr(ctry, 'a') || strchr(ctry, '-')) &&
+ mystrlen(p + 1) > 1 &&
+ mystrlen(candidate) - mystrlen(p) > 1) {
+ *p = '-';
+ for (int k=0; k < ns; k++)
+ if (strcmp(candidate,wlst[k]) == 0) cwrd = 0;
+ if (ns < maxSug) {
+ if (cwrd) {
+ wlst[ns] = mystrdup(candidate);
+ if (wlst[ns] == NULL) return -1;
+ ns++;
+ }
+ } else return ns;
+ }
+ }
+ }
+ }
+ return ns;
+}
+
+
+// error is adjacent letter were swapped
+int SuggestMgr::swapchar(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ char * p;
+ char tmpc;
+ int wl=strlen(word);
+ // try swapping adjacent chars one by one
+ strcpy(candidate, word);
+ for (p = candidate; p[1] != 0; p++) {
+ tmpc = *p;
+ *p = p[1];
+ p[1] = tmpc;
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ p[1] = *p;
+ *p = tmpc;
+ }
+ // try double swaps for short words
+ // ahev -> have, owudl -> would
+ if (wl == 4 || wl == 5) {
+ candidate[0] = word[1];
+ candidate[1] = word[0];
+ candidate[2] = word[2];
+ candidate[wl - 2] = word[wl - 1];
+ candidate[wl - 1] = word[wl - 2];
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ if (wl == 5) {
+ candidate[0] = word[0];
+ candidate[1] = word[2];
+ candidate[2] = word[1];
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ }
+ return ns;
+}
+
+// error is adjacent letter were swapped
+int SuggestMgr::swapchar_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ w_char * p;
+ w_char tmpc;
+ int len = 0;
+ // try swapping adjacent chars one by one
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ for (p = candidate_utf; p < (candidate_utf + wl - 1); p++) {
+ tmpc = *p;
+ *p = p[1];
+ p[1] = tmpc;
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ if (len == 0) len = strlen(candidate);
+ ns = testsug(wlst, candidate, len, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ p[1] = *p;
+ *p = tmpc;
+ }
+ // try double swaps for short words
+ // ahev -> have, owudl -> would, suodn -> sound
+ if (wl == 4 || wl == 5) {
+ candidate_utf[0] = word[1];
+ candidate_utf[1] = word[0];
+ candidate_utf[2] = word[2];
+ candidate_utf[wl - 2] = word[wl - 1];
+ candidate_utf[wl - 1] = word[wl - 2];
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, len, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ if (wl == 5) {
+ candidate_utf[0] = word[0];
+ candidate_utf[1] = word[2];
+ candidate_utf[2] = word[1];
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, len, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ }
+ return ns;
+}
+
+// error is not adjacent letter were swapped
+int SuggestMgr::longswapchar(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ char * p;
+ char * q;
+ char tmpc;
+ int wl=strlen(word);
+ // try swapping not adjacent chars one by one
+ strcpy(candidate, word);
+ for (p = candidate; *p != 0; p++) {
+ for (q = candidate; *q != 0; q++) {
+ if (abs((int)(p-q)) > 1) {
+ tmpc = *p;
+ *p = *q;
+ *q = tmpc;
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ *q = *p;
+ *p = tmpc;
+ }
+ }
+ }
+ return ns;
+}
+
+
+// error is adjacent letter were swapped
+int SuggestMgr::longswapchar_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ w_char * p;
+ w_char * q;
+ w_char tmpc;
+ // try swapping not adjacent chars
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ for (p = candidate_utf; p < (candidate_utf + wl); p++) {
+ for (q = candidate_utf; q < (candidate_utf + wl); q++) {
+ if (abs((int)(p-q)) > 1) {
+ tmpc = *p;
+ *p = *q;
+ *q = tmpc;
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ *q = *p;
+ *p = tmpc;
+ }
+ }
+ }
+ return ns;
+}
+
+// error is a letter was moved
+int SuggestMgr::movechar(char ** wlst, const char * word, int ns, int cpdsuggest)
+{
+ char candidate[MAXSWUTF8L];
+ char * p;
+ char * q;
+ char tmpc;
+
+ int wl=strlen(word);
+ // try moving a char
+ strcpy(candidate, word);
+ for (p = candidate; *p != 0; p++) {
+ for (q = p + 1; (*q != 0) && ((q - p) < 10); q++) {
+ tmpc = *(q-1);
+ *(q-1) = *q;
+ *q = tmpc;
+ if ((q-p) < 2) continue; // omit swap char
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ strcpy(candidate, word);
+ }
+ for (p = candidate + wl - 1; p > candidate; p--) {
+ for (q = p - 1; (q >= candidate) && ((p - q) < 10); q--) {
+ tmpc = *(q+1);
+ *(q+1) = *q;
+ *q = tmpc;
+ if ((p-q) < 2) continue; // omit swap char
+ ns = testsug(wlst, candidate, wl, ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ strcpy(candidate, word);
+ }
+ return ns;
+}
+
+// error is a letter was moved
+int SuggestMgr::movechar_utf(char ** wlst, const w_char * word, int wl, int ns, int cpdsuggest)
+{
+ w_char candidate_utf[MAXSWL];
+ char candidate[MAXSWUTF8L];
+ w_char * p;
+ w_char * q;
+ w_char tmpc;
+ // try moving a char
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ for (p = candidate_utf; p < (candidate_utf + wl); p++) {
+ for (q = p + 1; (q < (candidate_utf + wl)) && ((q - p) < 10); q++) {
+ tmpc = *(q-1);
+ *(q-1) = *q;
+ *q = tmpc;
+ if ((q-p) < 2) continue; // omit swap char
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ }
+ for (p = candidate_utf + wl - 1; p > candidate_utf; p--) {
+ for (q = p - 1; (q >= candidate_utf) && ((p - q) < 10); q--) {
+ tmpc = *(q+1);
+ *(q+1) = *q;
+ *q = tmpc;
+ if ((p-q) < 2) continue; // omit swap char
+ u16_u8(candidate, MAXSWUTF8L, candidate_utf, wl);
+ ns = testsug(wlst, candidate, strlen(candidate), ns, cpdsuggest, NULL, NULL);
+ if (ns == -1) return -1;
+ }
+ memcpy (candidate_utf, word, wl * sizeof(w_char));
+ }
+ return ns;
+}
+
+// generate a set of suggestions for very poorly spelled words
+int SuggestMgr::ngsuggest(char** wlst, char * w, int ns, HashMgr** pHMgr, int md)
+{
+
+ int i, j;
+ int lval;
+ int sc, scphon;
+ int lp, lpphon;
+ int nonbmp = 0;
+
+ // exhaustively search through all root words
+ // keeping track of the MAX_ROOTS most similar root words
+ struct hentry * roots[MAX_ROOTS];
+ char * rootsphon[MAX_ROOTS];
+ int scores[MAX_ROOTS];
+ int scoresphon[MAX_ROOTS];
+ for (i = 0; i < MAX_ROOTS; i++) {
+ roots[i] = NULL;
+ scores[i] = -100 * i;
+ rootsphon[i] = NULL;
+ scoresphon[i] = -100 * i;
+ }
+ lp = MAX_ROOTS - 1;
+ lpphon = MAX_ROOTS - 1;
+ scphon = -20000;
+ int low = NGRAM_LOWERING;
+
+ char w2[MAXWORDUTF8LEN];
+ char f[MAXSWUTF8L];
+ char * word = w;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ strcpy(w2, w);
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ word = w2;
+ }
+
+ char mw[MAXSWUTF8L];
+ w_char u8[MAXSWL];
+ int nc = strlen(word);
+ int n = (utf8) ? u8_u16(u8, MAXSWL, word) : nc;
+
+ // set character based ngram suggestion for words with non-BMP Unicode characters
+ if (n == -1) {
+ utf8 = 0; // XXX not state-free
+ n = nc;
+ nonbmp = 1;
+ low = 0;
+ }
+
+ struct hentry* hp = NULL;
+ int col = -1;
+ phonetable * ph = (pAMgr) ? pAMgr->get_phonetable() : NULL;
+ char target[MAXSWUTF8L];
+ char candidate[MAXSWUTF8L];
+ if (ph) {
+ if (utf8) {
+ w_char _w[MAXSWL];
+ int _wl = u8_u16(_w, MAXSWL, word);
+ mkallcap_utf(_w, _wl, langnum);
+ u16_u8(candidate, MAXSWUTF8L, _w, _wl);
+ } else {
+ strcpy(candidate, word);
+ if (!nonbmp) mkallcap(candidate, csconv);
+ }
+ phonet(candidate, target, nc, *ph); // XXX phonet() is 8-bit (nc, not n)
+ }
+
+ FLAG forbiddenword = pAMgr ? pAMgr->get_forbiddenword() : FLAG_NULL;
+ FLAG nosuggest = pAMgr ? pAMgr->get_nosuggest() : FLAG_NULL;
+ FLAG nongramsuggest = pAMgr ? pAMgr->get_nongramsuggest() : FLAG_NULL;
+ FLAG onlyincompound = pAMgr ? pAMgr->get_onlyincompound() : FLAG_NULL;
+
+ for (i = 0; i < md; i++) {
+ while (0 != (hp = (pHMgr[i])->walk_hashtable(col, hp))) {
+ if ((hp->astr) && (pAMgr) &&
+ (TESTAFF(hp->astr, forbiddenword, hp->alen) ||
+ TESTAFF(hp->astr, ONLYUPCASEFLAG, hp->alen) ||
+ TESTAFF(hp->astr, nosuggest, hp->alen) ||
+ TESTAFF(hp->astr, nongramsuggest, hp->alen) ||
+ TESTAFF(hp->astr, onlyincompound, hp->alen))) continue;
+
+ sc = ngram(3, word, HENTRY_WORD(hp), NGRAM_LONGER_WORSE + low) +
+ leftcommonsubstring(word, HENTRY_WORD(hp));
+
+ // check special pronounciation
+ if ((hp->var & H_OPT_PHON) && copy_field(f, HENTRY_DATA(hp), MORPH_PHON)) {
+ int sc2 = ngram(3, word, f, NGRAM_LONGER_WORSE + low) +
+ + leftcommonsubstring(word, f);
+ if (sc2 > sc) sc = sc2;
+ }
+
+ scphon = -20000;
+ if (ph && (sc > 2) && (abs(n - (int) hp->clen) <= 3)) {
+ char target2[MAXSWUTF8L];
+ if (utf8) {
+ w_char _w[MAXSWL];
+ int _wl = u8_u16(_w, MAXSWL, HENTRY_WORD(hp));
+ mkallcap_utf(_w, _wl, langnum);
+ u16_u8(candidate, MAXSWUTF8L, _w, _wl);
+ } else {
+ strcpy(candidate, HENTRY_WORD(hp));
+ mkallcap(candidate, csconv);
+ }
+ phonet(candidate, target2, -1, *ph);
+ scphon = 2 * ngram(3, target, target2, NGRAM_LONGER_WORSE);
+ }
+
+ if (sc > scores[lp]) {
+ scores[lp] = sc;
+ roots[lp] = hp;
+ lval = sc;
+ for (j=0; j < MAX_ROOTS; j++)
+ if (scores[j] < lval) {
+ lp = j;
+ lval = scores[j];
+ }
+ }
+
+
+ if (scphon > scoresphon[lpphon]) {
+ scoresphon[lpphon] = scphon;
+ rootsphon[lpphon] = HENTRY_WORD(hp);
+ lval = scphon;
+ for (j=0; j < MAX_ROOTS; j++)
+ if (scoresphon[j] < lval) {
+ lpphon = j;
+ lval = scoresphon[j];
+ }
+ }
+ }}
+
+ // find minimum threshold for a passable suggestion
+ // mangle original word three differnt ways
+ // and score them to generate a minimum acceptable score
+ int thresh = 0;
+ for (int sp = 1; sp < 4; sp++) {
+ if (utf8) {
+ for (int k=sp; k < n; k+=4) *((unsigned short *) u8 + k) = '*';
+ u16_u8(mw, MAXSWUTF8L, u8, n);
+ thresh = thresh + ngram(n, word, mw, NGRAM_ANY_MISMATCH + low);
+ } else {
+ strcpy(mw, word);
+ for (int k=sp; k < n; k+=4) *(mw + k) = '*';
+ thresh = thresh + ngram(n, word, mw, NGRAM_ANY_MISMATCH + low);
+ }
+ }
+ thresh = thresh / 3;
+ thresh--;
+
+ // now expand affixes on each of these root words and
+ // and use length adjusted ngram scores to select
+ // possible suggestions
+ char * guess[MAX_GUESS];
+ char * guessorig[MAX_GUESS];
+ int gscore[MAX_GUESS];
+ for(i=0;i<MAX_GUESS;i++) {
+ guess[i] = NULL;
+ guessorig[i] = NULL;
+ gscore[i] = -100 * i;
+ }
+
+ lp = MAX_GUESS - 1;
+
+ struct guessword * glst;
+ glst = (struct guessword *) calloc(MAX_WORDS,sizeof(struct guessword));
+ if (! glst) {
+ if (nonbmp) utf8 = 1;
+ return ns;
+ }
+
+ for (i = 0; i < MAX_ROOTS; i++) {
+ if (roots[i]) {
+ struct hentry * rp = roots[i];
+ int nw = pAMgr->expand_rootword(glst, MAX_WORDS, HENTRY_WORD(rp), rp->blen,
+ rp->astr, rp->alen, word, nc,
+ ((rp->var & H_OPT_PHON) ? copy_field(f, HENTRY_DATA(rp), MORPH_PHON) : NULL));
+
+ for (int k = 0; k < nw ; k++) {
+ sc = ngram(n, word, glst[k].word, NGRAM_ANY_MISMATCH + low) +
+ leftcommonsubstring(word, glst[k].word);
+
+ if (sc > thresh) {
+ if (sc > gscore[lp]) {
+ if (guess[lp]) {
+ free (guess[lp]);
+ if (guessorig[lp]) {
+ free(guessorig[lp]);
+ guessorig[lp] = NULL;
+ }
+ }
+ gscore[lp] = sc;
+ guess[lp] = glst[k].word;
+ guessorig[lp] = glst[k].orig;
+ lval = sc;
+ for (j=0; j < MAX_GUESS; j++)
+ if (gscore[j] < lval) {
+ lp = j;
+ lval = gscore[j];
+ }
+ } else {
+ free(glst[k].word);
+ if (glst[k].orig) free(glst[k].orig);
+ }
+ } else {
+ free(glst[k].word);
+ if (glst[k].orig) free(glst[k].orig);
+ }
+ }
+ }
+ }
+ free(glst);
+
+ // now we are done generating guesses
+ // sort in order of decreasing score
+
+
+ bubblesort(&guess[0], &guessorig[0], &gscore[0], MAX_GUESS);
+ if (ph) bubblesort(&rootsphon[0], NULL, &scoresphon[0], MAX_ROOTS);
+
+ // weight suggestions with a similarity index, based on
+ // the longest common subsequent algorithm and resort
+
+ int is_swap = 0;
+ int re = 0;
+ double fact = 1.0;
+ if (pAMgr) {
+ int maxd = pAMgr->get_maxdiff();
+ if (maxd >= 0) fact = (10.0 - maxd)/5.0;
+ }
+
+ for (i=0; i < MAX_GUESS; i++) {
+ if (guess[i]) {
+ // lowering guess[i]
+ char gl[MAXSWUTF8L];
+ int len;
+ if (utf8) {
+ w_char _w[MAXSWL];
+ len = u8_u16(_w, MAXSWL, guess[i]);
+ mkallsmall_utf(_w, len, langnum);
+ u16_u8(gl, MAXSWUTF8L, _w, len);
+ } else {
+ strcpy(gl, guess[i]);
+ if (!nonbmp) mkallsmall(gl, csconv);
+ len = strlen(guess[i]);
+ }
+
+ int _lcs = lcslen(word, gl);
+
+ // same characters with different casing
+ if ((n == len) && (n == _lcs)) {
+ gscore[i] += 2000;
+ break;
+ }
+ // using 2-gram instead of 3, and other weightening
+
+ re = ngram(2, word, gl, NGRAM_ANY_MISMATCH + low + NGRAM_WEIGHTED) +
+ ngram(2, gl, word, NGRAM_ANY_MISMATCH + low + NGRAM_WEIGHTED);
+
+ gscore[i] =
+ // length of longest common subsequent minus length difference
+ 2 * _lcs - abs((int) (n - len)) +
+ // weight length of the left common substring
+ leftcommonsubstring(word, gl) +
+ // weight equal character positions
+ (!nonbmp && commoncharacterpositions(word, gl, &is_swap) ? 1: 0) +
+ // swap character (not neighboring)
+ ((is_swap) ? 10 : 0) +
+ // ngram
+ ngram(4, word, gl, NGRAM_ANY_MISMATCH + low) +
+ // weighted ngrams
+ re +
+ // different limit for dictionaries with PHONE rules
+ (ph ? (re < len * fact ? -1000 : 0) : (re < (n + len)*fact? -1000 : 0));
+ }
+ }
+
+ bubblesort(&guess[0], &guessorig[0], &gscore[0], MAX_GUESS);
+
+// phonetic version
+ if (ph) for (i=0; i < MAX_ROOTS; i++) {
+ if (rootsphon[i]) {
+ // lowering rootphon[i]
+ char gl[MAXSWUTF8L];
+ int len;
+ if (utf8) {
+ w_char _w[MAXSWL];
+ len = u8_u16(_w, MAXSWL, rootsphon[i]);
+ mkallsmall_utf(_w, len, langnum);
+ u16_u8(gl, MAXSWUTF8L, _w, len);
+ } else {
+ strcpy(gl, rootsphon[i]);
+ if (!nonbmp) mkallsmall(gl, csconv);
+ len = strlen(rootsphon[i]);
+ }
+
+ // heuristic weigthing of ngram scores
+ scoresphon[i] += 2 * lcslen(word, gl) - abs((int) (n - len)) +
+ // weight length of the left common substring
+ leftcommonsubstring(word, gl);
+ }
+ }
+
+ if (ph) bubblesort(&rootsphon[0], NULL, &scoresphon[0], MAX_ROOTS);
+
+ // copy over
+ int oldns = ns;
+
+ int same = 0;
+ for (i=0; i < MAX_GUESS; i++) {
+ if (guess[i]) {
+ if ((ns < oldns + maxngramsugs) && (ns < maxSug) && (!same || (gscore[i] > 1000))) {
+ int unique = 1;
+ // leave only excellent suggestions, if exists
+ if (gscore[i] > 1000) same = 1; else if (gscore[i] < -100) {
+ same = 1;
+ // keep the best ngram suggestions, unless in ONLYMAXDIFF mode
+ if (ns > oldns || (pAMgr && pAMgr->get_onlymaxdiff())) {
+ free(guess[i]);
+ if (guessorig[i]) free(guessorig[i]);
+ continue;
+ }
+ }
+ for (j = 0; j < ns; j++) {
+ // don't suggest previous suggestions or a previous suggestion with prefixes or affixes
+ if ((!guessorig[i] && strstr(guess[i], wlst[j])) ||
+ (guessorig[i] && strstr(guessorig[i], wlst[j])) ||
+ // check forbidden words
+ !checkword(guess[i], strlen(guess[i]), 0, NULL, NULL)) unique = 0;
+ }
+ if (unique) {
+ wlst[ns++] = guess[i];
+ if (guessorig[i]) {
+ free(guess[i]);
+ wlst[ns-1] = guessorig[i];
+ }
+ } else {
+ free(guess[i]);
+ if (guessorig[i]) free(guessorig[i]);
+ }
+ } else {
+ free(guess[i]);
+ if (guessorig[i]) free(guessorig[i]);
+ }
+ }
+ }
+
+ oldns = ns;
+ if (ph) for (i=0; i < MAX_ROOTS; i++) {
+ if (rootsphon[i]) {
+ if ((ns < oldns + MAXPHONSUGS) && (ns < maxSug)) {
+ int unique = 1;
+ for (j = 0; j < ns; j++) {
+ // don't suggest previous suggestions or a previous suggestion with prefixes or affixes
+ if (strstr(rootsphon[i], wlst[j]) ||
+ // check forbidden words
+ !checkword(rootsphon[i], strlen(rootsphon[i]), 0, NULL, NULL)) unique = 0;
+ }
+ if (unique) {
+ wlst[ns++] = mystrdup(rootsphon[i]);
+ if (!wlst[ns - 1]) return ns - 1;
+ }
+ }
+ }
+ }
+
+ if (nonbmp) utf8 = 1;
+ return ns;
+}
+
+
+// see if a candidate suggestion is spelled correctly
+// needs to check both root words and words with affixes
+
+// obsolote MySpell-HU modifications:
+// return value 2 and 3 marks compounding with hyphen (-)
+// `3' marks roots without suffix
+int SuggestMgr::checkword(const char * word, int len, int cpdsuggest, int * timer, clock_t * timelimit)
+{
+ struct hentry * rv=NULL;
+ struct hentry * rv2=NULL;
+ int nosuffix = 0;
+
+ // check time limit
+ if (timer) {
+ (*timer)--;
+ if (!(*timer) && timelimit) {
+ if ((clock() - *timelimit) > TIMELIMIT) return 0;
+ *timer = MAXPLUSTIMER;
+ }
+ }
+
+ if (pAMgr) {
+ if (cpdsuggest==1) {
+ if (pAMgr->get_compound()) {
+ rv = pAMgr->compound_check(word, len, 0, 0, 100, 0, NULL, 0, 1, 0); //EXT
+ if (rv && (!(rv2 = pAMgr->lookup(word)) || !rv2->astr ||
+ !(TESTAFF(rv2->astr,pAMgr->get_forbiddenword(),rv2->alen) ||
+ TESTAFF(rv2->astr,pAMgr->get_nosuggest(),rv2->alen)))) return 3; // XXX obsolote categorisation + only ICONV needs affix flag check?
+ }
+ return 0;
+ }
+
+ rv = pAMgr->lookup(word);
+
+ if (rv) {
+ if ((rv->astr) && (TESTAFF(rv->astr,pAMgr->get_forbiddenword(),rv->alen)
+ || TESTAFF(rv->astr,pAMgr->get_nosuggest(),rv->alen))) return 0;
+ while (rv) {
+ if (rv->astr && (TESTAFF(rv->astr,pAMgr->get_needaffix(),rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ TESTAFF(rv->astr,pAMgr->get_onlyincompound(),rv->alen))) {
+ rv = rv->next_homonym;
+ } else break;
+ }
+ } else rv = pAMgr->prefix_check(word, len, 0); // only prefix, and prefix + suffix XXX
+
+ if (rv) {
+ nosuffix=1;
+ } else {
+ rv = pAMgr->suffix_check(word, len, 0, NULL, NULL, 0, NULL); // only suffix
+ }
+
+ if (!rv && pAMgr->have_contclass()) {
+ rv = pAMgr->suffix_check_twosfx(word, len, 0, NULL, FLAG_NULL);
+ if (!rv) rv = pAMgr->prefix_check_twosfx(word, len, 1, FLAG_NULL);
+ }
+
+ // check forbidden words
+ if ((rv) && (rv->astr) && (TESTAFF(rv->astr,pAMgr->get_forbiddenword(),rv->alen) ||
+ TESTAFF(rv->astr, ONLYUPCASEFLAG, rv->alen) ||
+ TESTAFF(rv->astr,pAMgr->get_nosuggest(),rv->alen) ||
+ TESTAFF(rv->astr,pAMgr->get_onlyincompound(),rv->alen))) return 0;
+
+ if (rv) { // XXX obsolote
+ if ((pAMgr->get_compoundflag()) &&
+ TESTAFF(rv->astr, pAMgr->get_compoundflag(), rv->alen)) return 2 + nosuffix;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int SuggestMgr::check_forbidden(const char * word, int len)
+{
+ struct hentry * rv = NULL;
+
+ if (pAMgr) {
+ rv = pAMgr->lookup(word);
+ if (rv && rv->astr && (TESTAFF(rv->astr,pAMgr->get_needaffix(),rv->alen) ||
+ TESTAFF(rv->astr,pAMgr->get_onlyincompound(),rv->alen))) rv = NULL;
+ if (!(pAMgr->prefix_check(word,len,1)))
+ rv = pAMgr->suffix_check(word,len, 0, NULL, NULL, 0, NULL); // prefix+suffix, suffix
+ // check forbidden words
+ if ((rv) && (rv->astr) && TESTAFF(rv->astr,pAMgr->get_forbiddenword(),rv->alen)) return 1;
+ }
+ return 0;
+}
+
+#ifdef HUNSPELL_EXPERIMENTAL
+// suggest possible stems
+int SuggestMgr::suggest_pos_stems(char*** slst, const char * w, int nsug)
+{
+ char ** wlst;
+
+ struct hentry * rv = NULL;
+
+ char w2[MAXSWUTF8L];
+ const char * word = w;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ strcpy(w2, w);
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ word = w2;
+ }
+
+ int wl = strlen(word);
+
+
+ if (*slst) {
+ wlst = *slst;
+ } else {
+ wlst = (char **) calloc(maxSug, sizeof(char *));
+ if (wlst == NULL) return -1;
+ }
+
+ rv = pAMgr->suffix_check(word, wl, 0, NULL, wlst, maxSug, &nsug);
+
+ // delete dash from end of word
+ if (nsug > 0) {
+ for (int j=0; j < nsug; j++) {
+ if (wlst[j][strlen(wlst[j]) - 1] == '-') wlst[j][strlen(wlst[j]) - 1] = '\0';
+ }
+ }
+
+ *slst = wlst;
+ return nsug;
+}
+#endif // END OF HUNSPELL_EXPERIMENTAL CODE
+
+
+char * SuggestMgr::suggest_morph(const char * w)
+{
+ char result[MAXLNLEN];
+ char * r = (char *) result;
+ char * st;
+
+ struct hentry * rv = NULL;
+
+ *result = '\0';
+
+ if (! pAMgr) return NULL;
+
+ char w2[MAXSWUTF8L];
+ const char * word = w;
+
+ // word reversing wrapper for complex prefixes
+ if (complexprefixes) {
+ strcpy(w2, w);
+ if (utf8) reverseword_utf(w2); else reverseword(w2);
+ word = w2;
+ }
+
+ rv = pAMgr->lookup(word);
+
+ while (rv) {
+ if ((!rv->astr) || !(TESTAFF(rv->astr, pAMgr->get_forbiddenword(), rv->alen) ||
+ TESTAFF(rv->astr, pAMgr->get_needaffix(), rv->alen) ||
+ TESTAFF(rv->astr,pAMgr->get_onlyincompound(),rv->alen))) {
+ if (!HENTRY_FIND(rv, MORPH_STEM)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, MORPH_STEM, MAXLNLEN);
+ mystrcat(result, word, MAXLNLEN);
+ }
+ if (HENTRY_DATA(rv)) {
+ mystrcat(result, " ", MAXLNLEN);
+ mystrcat(result, HENTRY_DATA2(rv), MAXLNLEN);
+ }
+ mystrcat(result, "\n", MAXLNLEN);
+ }
+ rv = rv->next_homonym;
+ }
+
+ st = pAMgr->affix_check_morph(word,strlen(word));
+ if (st) {
+ mystrcat(result, st, MAXLNLEN);
+ free(st);
+ }
+
+ if (pAMgr->get_compound() && (*result == '\0'))
+ pAMgr->compound_check_morph(word, strlen(word),
+ 0, 0, 100, 0,NULL, 0, &r, NULL);
+
+ return (*result) ? mystrdup(line_uniq(result, MSEP_REC)) : NULL;
+}
+
+#ifdef HUNSPELL_EXPERIMENTAL
+char * SuggestMgr::suggest_morph_for_spelling_error(const char * word)
+{
+ char * p = NULL;
+ char ** wlst = (char **) calloc(maxSug, sizeof(char *));
+ if (!**wlst) return NULL;
+ // we will use only the first suggestion
+ for (int i = 0; i < maxSug - 1; i++) wlst[i] = "";
+ int ns = suggest(&wlst, word, maxSug - 1, NULL);
+ if (ns == maxSug) {
+ p = suggest_morph(wlst[maxSug - 1]);
+ free(wlst[maxSug - 1]);
+ }
+ if (wlst) free(wlst);
+ return p;
+}
+#endif // END OF HUNSPELL_EXPERIMENTAL CODE
+
+/* affixation */
+char * SuggestMgr::suggest_hentry_gen(hentry * rv, char * pattern)
+{
+ char result[MAXLNLEN];
+ *result = '\0';
+ int sfxcount = get_sfxcount(pattern);
+
+ if (get_sfxcount(HENTRY_DATA(rv)) > sfxcount) return NULL;
+
+ if (HENTRY_DATA(rv)) {
+ char * aff = pAMgr->morphgen(HENTRY_WORD(rv), rv->blen, rv->astr, rv->alen,
+ HENTRY_DATA(rv), pattern, 0);
+ if (aff) {
+ mystrcat(result, aff, MAXLNLEN);
+ mystrcat(result, "\n", MAXLNLEN);
+ free(aff);
+ }
+ }
+
+ // check all allomorphs
+ char allomorph[MAXLNLEN];
+ char * p = NULL;
+ if (HENTRY_DATA(rv)) p = (char *) strstr(HENTRY_DATA2(rv), MORPH_ALLOMORPH);
+ while (p) {
+ struct hentry * rv2 = NULL;
+ p += MORPH_TAG_LEN;
+ int plen = fieldlen(p);
+ strncpy(allomorph, p, plen);
+ allomorph[plen] = '\0';
+ rv2 = pAMgr->lookup(allomorph);
+ while (rv2) {
+// if (HENTRY_DATA(rv2) && get_sfxcount(HENTRY_DATA(rv2)) <= sfxcount) {
+ if (HENTRY_DATA(rv2)) {
+ char * st = (char *) strstr(HENTRY_DATA2(rv2), MORPH_STEM);
+ if (st && (strncmp(st + MORPH_TAG_LEN,
+ HENTRY_WORD(rv), fieldlen(st + MORPH_TAG_LEN)) == 0)) {
+ char * aff = pAMgr->morphgen(HENTRY_WORD(rv2), rv2->blen, rv2->astr, rv2->alen,
+ HENTRY_DATA(rv2), pattern, 0);
+ if (aff) {
+ mystrcat(result, aff, MAXLNLEN);
+ mystrcat(result, "\n", MAXLNLEN);
+ free(aff);
+ }
+ }
+ }
+ rv2 = rv2->next_homonym;
+ }
+ p = strstr(p + plen, MORPH_ALLOMORPH);
+ }
+
+ return (*result) ? mystrdup(result) : NULL;
+}
+
+char * SuggestMgr::suggest_gen(char ** desc, int n, char * pattern) {
+ char result[MAXLNLEN];
+ char result2[MAXLNLEN];
+ char newpattern[MAXLNLEN];
+ *newpattern = '\0';
+ if (n == 0) return 0;
+ *result2 = '\0';
+ struct hentry * rv = NULL;
+ if (!pAMgr) return NULL;
+
+// search affixed forms with and without derivational suffixes
+ while(1) {
+
+ for (int k = 0; k < n; k++) {
+ *result = '\0';
+ // add compound word parts (except the last one)
+ char * s = (char *) desc[k];
+ char * part = strstr(s, MORPH_PART);
+ if (part) {
+ char * nextpart = strstr(part + 1, MORPH_PART);
+ while (nextpart) {
+ copy_field(result + strlen(result), part, MORPH_PART);
+ part = nextpart;
+ nextpart = strstr(part + 1, MORPH_PART);
+ }
+ s = part;
+ }
+
+ char **pl;
+ char tok[MAXLNLEN];
+ strcpy(tok, s);
+ char * alt = strstr(tok, " | ");
+ while (alt) {
+ alt[1] = MSEP_ALT;
+ alt = strstr(alt, " | ");
+ }
+ int pln = line_tok(tok, &pl, MSEP_ALT);
+ for (int i = 0; i < pln; i++) {
+ // remove inflectional and terminal suffixes
+ char * is = strstr(pl[i], MORPH_INFL_SFX);
+ if (is) *is = '\0';
+ char * ts = strstr(pl[i], MORPH_TERM_SFX);
+ while (ts) {
+ *ts = '_';
+ ts = strstr(pl[i], MORPH_TERM_SFX);
+ }
+ char * st = strstr(s, MORPH_STEM);
+ if (st) {
+ copy_field(tok, st, MORPH_STEM);
+ rv = pAMgr->lookup(tok);
+ while (rv) {
+ char newpat[MAXLNLEN];
+ strcpy(newpat, pl[i]);
+ strcat(newpat, pattern);
+ char * sg = suggest_hentry_gen(rv, newpat);
+ if (!sg) sg = suggest_hentry_gen(rv, pattern);
+ if (sg) {
+ char ** gen;
+ int genl = line_tok(sg, &gen, MSEP_REC);
+ free(sg);
+ sg = NULL;
+ for (int j = 0; j < genl; j++) {
+ if (strstr(pl[i], MORPH_SURF_PFX)) {
+ int r2l = strlen(result2);
+ result2[r2l] = MSEP_REC;
+ strcpy(result2 + r2l + 1, result);
+ copy_field(result2 + strlen(result2), pl[i], MORPH_SURF_PFX);
+ mystrcat(result2, gen[j], MAXLNLEN);
+ } else {
+ sprintf(result2 + strlen(result2), "%c%s%s",
+ MSEP_REC, result, gen[j]);
+ }
+ }
+ freelist(&gen, genl);
+ }
+ rv = rv->next_homonym;
+ }
+ }
+ }
+ freelist(&pl, pln);
+ }
+
+ if (*result2 || !strstr(pattern, MORPH_DERI_SFX)) break;
+ strcpy(newpattern, pattern);
+ pattern = newpattern;
+ char * ds = strstr(pattern, MORPH_DERI_SFX);
+ while (ds) {
+ strncpy(ds, MORPH_TERM_SFX, MORPH_TAG_LEN);
+ ds = strstr(pattern, MORPH_DERI_SFX);
+ }
+ }
+ return (*result2 ? mystrdup(result2) : NULL);
+}
+
+
+// generate an n-gram score comparing s1 and s2
+int SuggestMgr::ngram(int n, char * s1, const char * s2, int opt)
+{
+ int nscore = 0;
+ int ns;
+ int l1;
+ int l2;
+ int test = 0;
+
+ if (utf8) {
+ w_char su1[MAXSWL];
+ w_char su2[MAXSWL];
+ l1 = u8_u16(su1, MAXSWL, s1);
+ l2 = u8_u16(su2, MAXSWL, s2);
+ if ((l2 <= 0) || (l1 == -1)) return 0;
+ // lowering dictionary word
+ if (opt & NGRAM_LOWERING) mkallsmall_utf(su2, l2, langnum);
+ for (int j = 1; j <= n; j++) {
+ ns = 0;
+ for (int i = 0; i <= (l1-j); i++) {
+ int k = 0;
+ for (int l = 0; l <= (l2-j); l++) {
+ for (k = 0; k < j; k++) {
+ w_char * c1 = su1 + i + k;
+ w_char * c2 = su2 + l + k;
+ if ((c1->l != c2->l) || (c1->h != c2->h)) break;
+ }
+ if (k == j) {
+ ns++;
+ break;
+ }
+ }
+ if (k != j && opt & NGRAM_WEIGHTED) {
+ ns--;
+ test++;
+ if (i == 0 || i == l1-j) ns--; // side weight
+ }
+ }
+ nscore = nscore + ns;
+ if (ns < 2 && !(opt & NGRAM_WEIGHTED)) break;
+ }
+ } else {
+ l2 = strlen(s2);
+ if (l2 == 0) return 0;
+ l1 = strlen(s1);
+ char *t = mystrdup(s2);
+ if (opt & NGRAM_LOWERING) mkallsmall(t, csconv);
+ for (int j = 1; j <= n; j++) {
+ ns = 0;
+ for (int i = 0; i <= (l1-j); i++) {
+ char c = *(s1 + i + j);
+ *(s1 + i + j) = '\0';
+ if (strstr(t,(s1+i))) {
+ ns++;
+ } else if (opt & NGRAM_WEIGHTED) {
+ ns--;
+test++;
+ if (i == 0 || i == l1-j) ns--; // side weight
+ }
+ *(s1 + i + j ) = c;
+ }
+ nscore = nscore + ns;
+ if (ns < 2 && !(opt & NGRAM_WEIGHTED)) break;
+ }
+ free(t);
+ }
+
+ ns = 0;
+ if (opt & NGRAM_LONGER_WORSE) ns = (l2-l1)-2;
+ if (opt & NGRAM_ANY_MISMATCH) ns = abs(l2-l1)-2;
+ ns = (nscore - ((ns > 0) ? ns : 0));
+ return ns;
+}
+
+// length of the left common substring of s1 and (decapitalised) s2
+int SuggestMgr::leftcommonsubstring(char * s1, const char * s2) {
+ if (utf8) {
+ w_char su1[MAXSWL];
+ w_char su2[MAXSWL];
+ su1[0].l = su2[0].l = su1[0].h = su2[0].h = 0;
+ // decapitalize dictionary word
+ if (complexprefixes) {
+ int l1 = u8_u16(su1, MAXSWL, s1);
+ int l2 = u8_u16(su2, MAXSWL, s2);
+ if (*((short *)su1+l1-1) == *((short *)su2+l2-1)) return 1;
+ } else {
+ int i;
+ u8_u16(su1, 1, s1);
+ u8_u16(su2, 1, s2);
+ unsigned short idx = (su2->h << 8) + su2->l;
+ unsigned short otheridx = (su1->h << 8) + su1->l;
+ if (otheridx != idx &&
+ (otheridx != unicodetolower(idx, langnum))) return 0;
+ int l1 = u8_u16(su1, MAXSWL, s1);
+ int l2 = u8_u16(su2, MAXSWL, s2);
+ for(i = 1; (i < l1) && (i < l2) &&
+ (su1[i].l == su2[i].l) && (su1[i].h == su2[i].h); i++);
+ return i;
+ }
+ } else {
+ if (complexprefixes) {
+ int l1 = strlen(s1);
+ int l2 = strlen(s2);
+ if (*(s2+l1-1) == *(s2+l2-1)) return 1;
+ } else {
+ char * olds = s1;
+ // decapitalise dictionary word
+ if ((*s1 != *s2) && (*s1 != csconv[((unsigned char)*s2)].clower)) return 0;
+ do {
+ s1++; s2++;
+ } while ((*s1 == *s2) && (*s1 != '\0'));
+ return (int)(s1 - olds);
+ }
+ }
+ return 0;
+}
+
+int SuggestMgr::commoncharacterpositions(char * s1, const char * s2, int * is_swap) {
+ int num = 0;
+ int diff = 0;
+ int diffpos[2];
+ *is_swap = 0;
+ if (utf8) {
+ w_char su1[MAXSWL];
+ w_char su2[MAXSWL];
+ int l1 = u8_u16(su1, MAXSWL, s1);
+ int l2 = u8_u16(su2, MAXSWL, s2);
+ // decapitalize dictionary word
+ if (complexprefixes) {
+ mkallsmall_utf(su2+l2-1, 1, langnum);
+ } else {
+ mkallsmall_utf(su2, 1, langnum);
+ }
+ for (int i = 0; (i < l1) && (i < l2); i++) {
+ if (((short *) su1)[i] == ((short *) su2)[i]) {
+ num++;
+ } else {
+ if (diff < 2) diffpos[diff] = i;
+ diff++;
+ }
+ }
+ if ((diff == 2) && (l1 == l2) &&
+ (((short *) su1)[diffpos[0]] == ((short *) su2)[diffpos[1]]) &&
+ (((short *) su1)[diffpos[1]] == ((short *) su2)[diffpos[0]])) *is_swap = 1;
+ } else {
+ int i;
+ char t[MAXSWUTF8L];
+ strcpy(t, s2);
+ // decapitalize dictionary word
+ if (complexprefixes) {
+ int l2 = strlen(t);
+ *(t+l2-1) = csconv[((unsigned char)*(t+l2-1))].clower;
+ } else {
+ mkallsmall(t, csconv);
+ }
+ for (i = 0; (*(s1+i) != 0) && (*(t+i) != 0); i++) {
+ if (*(s1+i) == *(t+i)) {
+ num++;
+ } else {
+ if (diff < 2) diffpos[diff] = i;
+ diff++;
+ }
+ }
+ if ((diff == 2) && (*(s1+i) == 0) && (*(t+i) == 0) &&
+ (*(s1+diffpos[0]) == *(t+diffpos[1])) &&
+ (*(s1+diffpos[1]) == *(t+diffpos[0]))) *is_swap = 1;
+ }
+ return num;
+}
+
+int SuggestMgr::mystrlen(const char * word) {
+ if (utf8) {
+ w_char w[MAXSWL];
+ return u8_u16(w, MAXSWL, word);
+ } else return strlen(word);
+}
+
+// sort in decreasing order of score
+void SuggestMgr::bubblesort(char** rword, char** rword2, int* rsc, int n )
+{
+ int m = 1;
+ while (m < n) {
+ int j = m;
+ while (j > 0) {
+ if (rsc[j-1] < rsc[j]) {
+ int sctmp = rsc[j-1];
+ char * wdtmp = rword[j-1];
+ rsc[j-1] = rsc[j];
+ rword[j-1] = rword[j];
+ rsc[j] = sctmp;
+ rword[j] = wdtmp;
+ if (rword2) {
+ wdtmp = rword2[j-1];
+ rword2[j-1] = rword2[j];
+ rword2[j] = wdtmp;
+ }
+ j--;
+ } else break;
+ }
+ m++;
+ }
+ return;
+}
+
+// longest common subsequence
+void SuggestMgr::lcs(const char * s, const char * s2, int * l1, int * l2, char ** result) {
+ int n, m;
+ w_char su[MAXSWL];
+ w_char su2[MAXSWL];
+ char * b;
+ char * c;
+ int i;
+ int j;
+ if (utf8) {
+ m = u8_u16(su, MAXSWL, s);
+ n = u8_u16(su2, MAXSWL, s2);
+ } else {
+ m = strlen(s);
+ n = strlen(s2);
+ }
+ c = (char *) malloc((m + 1) * (n + 1));
+ b = (char *) malloc((m + 1) * (n + 1));
+ if (!c || !b) {
+ if (c) free(c);
+ if (b) free(b);
+ *result = NULL;
+ return;
+ }
+ for (i = 1; i <= m; i++) c[i*(n+1)] = 0;
+ for (j = 0; j <= n; j++) c[j] = 0;
+ for (i = 1; i <= m; i++) {
+ for (j = 1; j <= n; j++) {
+ if ( ((utf8) && (*((short *) su+i-1) == *((short *)su2+j-1)))
+ || ((!utf8) && ((*(s+i-1)) == (*(s2+j-1))))) {
+ c[i*(n+1) + j] = c[(i-1)*(n+1) + j-1]+1;
+ b[i*(n+1) + j] = LCS_UPLEFT;
+ } else if (c[(i-1)*(n+1) + j] >= c[i*(n+1) + j-1]) {
+ c[i*(n+1) + j] = c[(i-1)*(n+1) + j];
+ b[i*(n+1) + j] = LCS_UP;
+ } else {
+ c[i*(n+1) + j] = c[i*(n+1) + j-1];
+ b[i*(n+1) + j] = LCS_LEFT;
+ }
+ }
+ }
+ *result = b;
+ free(c);
+ *l1 = m;
+ *l2 = n;
+}
+
+int SuggestMgr::lcslen(const char * s, const char* s2) {
+ int m;
+ int n;
+ int i;
+ int j;
+ char * result;
+ int len = 0;
+ lcs(s, s2, &m, &n, &result);
+ if (!result) return 0;
+ i = m;
+ j = n;
+ while ((i != 0) && (j != 0)) {
+ if (result[i*(n+1) + j] == LCS_UPLEFT) {
+ len++;
+ i--;
+ j--;
+ } else if (result[i*(n+1) + j] == LCS_UP) {
+ i--;
+ } else j--;
+ }
+ free(result);
+ return len;
+}
diff --git a/src/cpp/core/spelling/hunspell/suggestmgr.hxx b/src/cpp/core/spelling/hunspell/suggestmgr.hxx
new file mode 100644
index 0000000..5f043fd
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/suggestmgr.hxx
@@ -0,0 +1,111 @@
+#ifndef _SUGGESTMGR_HXX_
+#define _SUGGESTMGR_HXX_
+
+#define MAXSWL 100
+#define MAXSWUTF8L (MAXSWL * 4)
+#define MAX_ROOTS 100
+#define MAX_WORDS 100
+#define MAX_GUESS 200
+#define MAXNGRAMSUGS 4
+#define MAXPHONSUGS 2
+#define MAXCOMPOUNDSUGS 3
+
+// timelimit: max ~1/4 sec (process time on Linux) for a time consuming function
+#define TIMELIMIT (CLOCKS_PER_SEC >> 2)
+#define MINTIMER 100
+#define MAXPLUSTIMER 100
+
+#define NGRAM_LONGER_WORSE (1 << 0)
+#define NGRAM_ANY_MISMATCH (1 << 1)
+#define NGRAM_LOWERING (1 << 2)
+#define NGRAM_WEIGHTED (1 << 3)
+
+#include "hunvisapi.h"
+
+#include "atypes.hxx"
+#include "affixmgr.hxx"
+#include "hashmgr.hxx"
+#include "langnum.hxx"
+#include <time.h>
+
+enum { LCS_UP, LCS_LEFT, LCS_UPLEFT };
+
+class LIBHUNSPELL_DLL_EXPORTED SuggestMgr
+{
+ char * ckey;
+ int ckeyl;
+ w_char * ckey_utf;
+
+ char * ctry;
+ int ctryl;
+ w_char * ctry_utf;
+
+ AffixMgr* pAMgr;
+ int maxSug;
+ struct cs_info * csconv;
+ int utf8;
+ int langnum;
+ int nosplitsugs;
+ int maxngramsugs;
+ int maxcpdsugs;
+ int complexprefixes;
+
+
+public:
+ SuggestMgr(const char * tryme, int maxn, AffixMgr *aptr);
+ ~SuggestMgr();
+
+ int suggest(char*** slst, const char * word, int nsug, int * onlycmpdsug);
+ int ngsuggest(char ** wlst, char * word, int ns, HashMgr** pHMgr, int md);
+ int suggest_auto(char*** slst, const char * word, int nsug);
+ int suggest_stems(char*** slst, const char * word, int nsug);
+ int suggest_pos_stems(char*** slst, const char * word, int nsug);
+
+ char * suggest_morph(const char * word);
+ char * suggest_gen(char ** pl, int pln, char * pattern);
+ char * suggest_morph_for_spelling_error(const char * word);
+
+private:
+ int testsug(char** wlst, const char * candidate, int wl, int ns, int cpdsuggest,
+ int * timer, clock_t * timelimit);
+ int checkword(const char *, int, int, int *, clock_t *);
+ int check_forbidden(const char *, int);
+
+ int capchars(char **, const char *, int, int);
+ int replchars(char**, const char *, int, int);
+ int doubletwochars(char**, const char *, int, int);
+ int forgotchar(char **, const char *, int, int);
+ int swapchar(char **, const char *, int, int);
+ int longswapchar(char **, const char *, int, int);
+ int movechar(char **, const char *, int, int);
+ int extrachar(char **, const char *, int, int);
+ int badcharkey(char **, const char *, int, int);
+ int badchar(char **, const char *, int, int);
+ int twowords(char **, const char *, int, int);
+ int fixstems(char **, const char *, int);
+
+ int capchars_utf(char **, const w_char *, int wl, int, int);
+ int doubletwochars_utf(char**, const w_char *, int wl, int, int);
+ int forgotchar_utf(char**, const w_char *, int wl, int, int);
+ int extrachar_utf(char**, const w_char *, int wl, int, int);
+ int badcharkey_utf(char **, const w_char *, int wl, int, int);
+ int badchar_utf(char **, const w_char *, int wl, int, int);
+ int swapchar_utf(char **, const w_char *, int wl, int, int);
+ int longswapchar_utf(char **, const w_char *, int, int, int);
+ int movechar_utf(char **, const w_char *, int, int, int);
+
+ int mapchars(char**, const char *, int, int);
+ int map_related(const char *, char *, int, int, char ** wlst, int, int, const mapentry*, int, int *, clock_t *);
+ int ngram(int n, char * s1, const char * s2, int opt);
+ int mystrlen(const char * word);
+ int leftcommonsubstring(char * s1, const char * s2);
+ int commoncharacterpositions(char * s1, const char * s2, int * is_swap);
+ void bubblesort( char ** rwd, char ** rwd2, int * rsc, int n);
+ void lcs(const char * s, const char * s2, int * l1, int * l2, char ** result);
+ int lcslen(const char * s, const char* s2);
+ char * suggest_hentry_gen(hentry * rv, char * pattern);
+
+};
+
+#endif
+
diff --git a/src/cpp/core/spelling/hunspell/utf_info.cxx b/src/cpp/core/spelling/hunspell/utf_info.cxx
new file mode 100644
index 0000000..4a8e203
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/utf_info.cxx
@@ -0,0 +1,19676 @@
+#include "csutil.hxx"
+/* fields: Unicode letter, toupper, tolower */
+static struct unicode_info utf_lst[] = {
+{ 0x0041, 0x0041, 0x0061 },
+{ 0x0042, 0x0042, 0x0062 },
+{ 0x0043, 0x0043, 0x0063 },
+{ 0x0044, 0x0044, 0x0064 },
+{ 0x0045, 0x0045, 0x0065 },
+{ 0x0046, 0x0046, 0x0066 },
+{ 0x0047, 0x0047, 0x0067 },
+{ 0x0048, 0x0048, 0x0068 },
+{ 0x0049, 0x0049, 0x0069 },
+{ 0x004A, 0x004A, 0x006A },
+{ 0x004B, 0x004B, 0x006B },
+{ 0x004C, 0x004C, 0x006C },
+{ 0x004D, 0x004D, 0x006D },
+{ 0x004E, 0x004E, 0x006E },
+{ 0x004F, 0x004F, 0x006F },
+{ 0x0050, 0x0050, 0x0070 },
+{ 0x0051, 0x0051, 0x0071 },
+{ 0x0052, 0x0052, 0x0072 },
+{ 0x0053, 0x0053, 0x0073 },
+{ 0x0054, 0x0054, 0x0074 },
+{ 0x0055, 0x0055, 0x0075 },
+{ 0x0056, 0x0056, 0x0076 },
+{ 0x0057, 0x0057, 0x0077 },
+{ 0x0058, 0x0058, 0x0078 },
+{ 0x0059, 0x0059, 0x0079 },
+{ 0x005A, 0x005A, 0x007A },
+{ 0x0061, 0x0041, 0x0061 },
+{ 0x0062, 0x0042, 0x0062 },
+{ 0x0063, 0x0043, 0x0063 },
+{ 0x0064, 0x0044, 0x0064 },
+{ 0x0065, 0x0045, 0x0065 },
+{ 0x0066, 0x0046, 0x0066 },
+{ 0x0067, 0x0047, 0x0067 },
+{ 0x0068, 0x0048, 0x0068 },
+{ 0x0069, 0x0049, 0x0069 },
+{ 0x006A, 0x004A, 0x006A },
+{ 0x006B, 0x004B, 0x006B },
+{ 0x006C, 0x004C, 0x006C },
+{ 0x006D, 0x004D, 0x006D },
+{ 0x006E, 0x004E, 0x006E },
+{ 0x006F, 0x004F, 0x006F },
+{ 0x0070, 0x0050, 0x0070 },
+{ 0x0071, 0x0051, 0x0071 },
+{ 0x0072, 0x0052, 0x0072 },
+{ 0x0073, 0x0053, 0x0073 },
+{ 0x0074, 0x0054, 0x0074 },
+{ 0x0075, 0x0055, 0x0075 },
+{ 0x0076, 0x0056, 0x0076 },
+{ 0x0077, 0x0057, 0x0077 },
+{ 0x0078, 0x0058, 0x0078 },
+{ 0x0079, 0x0059, 0x0079 },
+{ 0x007A, 0x005A, 0x007A },
+{ 0x00AA, 0x00AA, 0x00AA },
+{ 0x00B5, 0x039C, 0x00B5 },
+{ 0x00BA, 0x00BA, 0x00BA },
+{ 0x00C0, 0x00C0, 0x00E0 },
+{ 0x00C1, 0x00C1, 0x00E1 },
+{ 0x00C2, 0x00C2, 0x00E2 },
+{ 0x00C3, 0x00C3, 0x00E3 },
+{ 0x00C4, 0x00C4, 0x00E4 },
+{ 0x00C5, 0x00C5, 0x00E5 },
+{ 0x00C6, 0x00C6, 0x00E6 },
+{ 0x00C7, 0x00C7, 0x00E7 },
+{ 0x00C8, 0x00C8, 0x00E8 },
+{ 0x00C9, 0x00C9, 0x00E9 },
+{ 0x00CA, 0x00CA, 0x00EA },
+{ 0x00CB, 0x00CB, 0x00EB },
+{ 0x00CC, 0x00CC, 0x00EC },
+{ 0x00CD, 0x00CD, 0x00ED },
+{ 0x00CE, 0x00CE, 0x00EE },
+{ 0x00CF, 0x00CF, 0x00EF },
+{ 0x00D0, 0x00D0, 0x00F0 },
+{ 0x00D1, 0x00D1, 0x00F1 },
+{ 0x00D2, 0x00D2, 0x00F2 },
+{ 0x00D3, 0x00D3, 0x00F3 },
+{ 0x00D4, 0x00D4, 0x00F4 },
+{ 0x00D5, 0x00D5, 0x00F5 },
+{ 0x00D6, 0x00D6, 0x00F6 },
+{ 0x00D8, 0x00D8, 0x00F8 },
+{ 0x00D9, 0x00D9, 0x00F9 },
+{ 0x00DA, 0x00DA, 0x00FA },
+{ 0x00DB, 0x00DB, 0x00FB },
+{ 0x00DC, 0x00DC, 0x00FC },
+{ 0x00DD, 0x00DD, 0x00FD },
+{ 0x00DE, 0x00DE, 0x00FE },
+{ 0x00DF, 0x00DF, 0x00DF },
+{ 0x00E0, 0x00C0, 0x00E0 },
+{ 0x00E1, 0x00C1, 0x00E1 },
+{ 0x00E2, 0x00C2, 0x00E2 },
+{ 0x00E3, 0x00C3, 0x00E3 },
+{ 0x00E4, 0x00C4, 0x00E4 },
+{ 0x00E5, 0x00C5, 0x00E5 },
+{ 0x00E6, 0x00C6, 0x00E6 },
+{ 0x00E7, 0x00C7, 0x00E7 },
+{ 0x00E8, 0x00C8, 0x00E8 },
+{ 0x00E9, 0x00C9, 0x00E9 },
+{ 0x00EA, 0x00CA, 0x00EA },
+{ 0x00EB, 0x00CB, 0x00EB },
+{ 0x00EC, 0x00CC, 0x00EC },
+{ 0x00ED, 0x00CD, 0x00ED },
+{ 0x00EE, 0x00CE, 0x00EE },
+{ 0x00EF, 0x00CF, 0x00EF },
+{ 0x00F0, 0x00D0, 0x00F0 },
+{ 0x00F1, 0x00D1, 0x00F1 },
+{ 0x00F2, 0x00D2, 0x00F2 },
+{ 0x00F3, 0x00D3, 0x00F3 },
+{ 0x00F4, 0x00D4, 0x00F4 },
+{ 0x00F5, 0x00D5, 0x00F5 },
+{ 0x00F6, 0x00D6, 0x00F6 },
+{ 0x00F8, 0x00D8, 0x00F8 },
+{ 0x00F9, 0x00D9, 0x00F9 },
+{ 0x00FA, 0x00DA, 0x00FA },
+{ 0x00FB, 0x00DB, 0x00FB },
+{ 0x00FC, 0x00DC, 0x00FC },
+{ 0x00FD, 0x00DD, 0x00FD },
+{ 0x00FE, 0x00DE, 0x00FE },
+{ 0x00FF, 0x0178, 0x00FF },
+{ 0x0100, 0x0100, 0x0101 },
+{ 0x0101, 0x0100, 0x0101 },
+{ 0x0102, 0x0102, 0x0103 },
+{ 0x0103, 0x0102, 0x0103 },
+{ 0x0104, 0x0104, 0x0105 },
+{ 0x0105, 0x0104, 0x0105 },
+{ 0x0106, 0x0106, 0x0107 },
+{ 0x0107, 0x0106, 0x0107 },
+{ 0x0108, 0x0108, 0x0109 },
+{ 0x0109, 0x0108, 0x0109 },
+{ 0x010A, 0x010A, 0x010B },
+{ 0x010B, 0x010A, 0x010B },
+{ 0x010C, 0x010C, 0x010D },
+{ 0x010D, 0x010C, 0x010D },
+{ 0x010E, 0x010E, 0x010F },
+{ 0x010F, 0x010E, 0x010F },
+{ 0x0110, 0x0110, 0x0111 },
+{ 0x0111, 0x0110, 0x0111 },
+{ 0x0112, 0x0112, 0x0113 },
+{ 0x0113, 0x0112, 0x0113 },
+{ 0x0114, 0x0114, 0x0115 },
+{ 0x0115, 0x0114, 0x0115 },
+{ 0x0116, 0x0116, 0x0117 },
+{ 0x0117, 0x0116, 0x0117 },
+{ 0x0118, 0x0118, 0x0119 },
+{ 0x0119, 0x0118, 0x0119 },
+{ 0x011A, 0x011A, 0x011B },
+{ 0x011B, 0x011A, 0x011B },
+{ 0x011C, 0x011C, 0x011D },
+{ 0x011D, 0x011C, 0x011D },
+{ 0x011E, 0x011E, 0x011F },
+{ 0x011F, 0x011E, 0x011F },
+{ 0x0120, 0x0120, 0x0121 },
+{ 0x0121, 0x0120, 0x0121 },
+{ 0x0122, 0x0122, 0x0123 },
+{ 0x0123, 0x0122, 0x0123 },
+{ 0x0124, 0x0124, 0x0125 },
+{ 0x0125, 0x0124, 0x0125 },
+{ 0x0126, 0x0126, 0x0127 },
+{ 0x0127, 0x0126, 0x0127 },
+{ 0x0128, 0x0128, 0x0129 },
+{ 0x0129, 0x0128, 0x0129 },
+{ 0x012A, 0x012A, 0x012B },
+{ 0x012B, 0x012A, 0x012B },
+{ 0x012C, 0x012C, 0x012D },
+{ 0x012D, 0x012C, 0x012D },
+{ 0x012E, 0x012E, 0x012F },
+{ 0x012F, 0x012E, 0x012F },
+{ 0x0130, 0x0130, 0x0069 },
+{ 0x0131, 0x0049, 0x0131 },
+{ 0x0132, 0x0132, 0x0133 },
+{ 0x0133, 0x0132, 0x0133 },
+{ 0x0134, 0x0134, 0x0135 },
+{ 0x0135, 0x0134, 0x0135 },
+{ 0x0136, 0x0136, 0x0137 },
+{ 0x0137, 0x0136, 0x0137 },
+{ 0x0138, 0x0138, 0x0138 },
+{ 0x0139, 0x0139, 0x013A },
+{ 0x013A, 0x0139, 0x013A },
+{ 0x013B, 0x013B, 0x013C },
+{ 0x013C, 0x013B, 0x013C },
+{ 0x013D, 0x013D, 0x013E },
+{ 0x013E, 0x013D, 0x013E },
+{ 0x013F, 0x013F, 0x0140 },
+{ 0x0140, 0x013F, 0x0140 },
+{ 0x0141, 0x0141, 0x0142 },
+{ 0x0142, 0x0141, 0x0142 },
+{ 0x0143, 0x0143, 0x0144 },
+{ 0x0144, 0x0143, 0x0144 },
+{ 0x0145, 0x0145, 0x0146 },
+{ 0x0146, 0x0145, 0x0146 },
+{ 0x0147, 0x0147, 0x0148 },
+{ 0x0148, 0x0147, 0x0148 },
+{ 0x0149, 0x0149, 0x0149 },
+{ 0x014A, 0x014A, 0x014B },
+{ 0x014B, 0x014A, 0x014B },
+{ 0x014C, 0x014C, 0x014D },
+{ 0x014D, 0x014C, 0x014D },
+{ 0x014E, 0x014E, 0x014F },
+{ 0x014F, 0x014E, 0x014F },
+{ 0x0150, 0x0150, 0x0151 },
+{ 0x0151, 0x0150, 0x0151 },
+{ 0x0152, 0x0152, 0x0153 },
+{ 0x0153, 0x0152, 0x0153 },
+{ 0x0154, 0x0154, 0x0155 },
+{ 0x0155, 0x0154, 0x0155 },
+{ 0x0156, 0x0156, 0x0157 },
+{ 0x0157, 0x0156, 0x0157 },
+{ 0x0158, 0x0158, 0x0159 },
+{ 0x0159, 0x0158, 0x0159 },
+{ 0x015A, 0x015A, 0x015B },
+{ 0x015B, 0x015A, 0x015B },
+{ 0x015C, 0x015C, 0x015D },
+{ 0x015D, 0x015C, 0x015D },
+{ 0x015E, 0x015E, 0x015F },
+{ 0x015F, 0x015E, 0x015F },
+{ 0x0160, 0x0160, 0x0161 },
+{ 0x0161, 0x0160, 0x0161 },
+{ 0x0162, 0x0162, 0x0163 },
+{ 0x0163, 0x0162, 0x0163 },
+{ 0x0164, 0x0164, 0x0165 },
+{ 0x0165, 0x0164, 0x0165 },
+{ 0x0166, 0x0166, 0x0167 },
+{ 0x0167, 0x0166, 0x0167 },
+{ 0x0168, 0x0168, 0x0169 },
+{ 0x0169, 0x0168, 0x0169 },
+{ 0x016A, 0x016A, 0x016B },
+{ 0x016B, 0x016A, 0x016B },
+{ 0x016C, 0x016C, 0x016D },
+{ 0x016D, 0x016C, 0x016D },
+{ 0x016E, 0x016E, 0x016F },
+{ 0x016F, 0x016E, 0x016F },
+{ 0x0170, 0x0170, 0x0171 },
+{ 0x0171, 0x0170, 0x0171 },
+{ 0x0172, 0x0172, 0x0173 },
+{ 0x0173, 0x0172, 0x0173 },
+{ 0x0174, 0x0174, 0x0175 },
+{ 0x0175, 0x0174, 0x0175 },
+{ 0x0176, 0x0176, 0x0177 },
+{ 0x0177, 0x0176, 0x0177 },
+{ 0x0178, 0x0178, 0x00FF },
+{ 0x0179, 0x0179, 0x017A },
+{ 0x017A, 0x0179, 0x017A },
+{ 0x017B, 0x017B, 0x017C },
+{ 0x017C, 0x017B, 0x017C },
+{ 0x017D, 0x017D, 0x017E },
+{ 0x017E, 0x017D, 0x017E },
+{ 0x017F, 0x0053, 0x017F },
+{ 0x0180, 0x0180, 0x0180 },
+{ 0x0181, 0x0181, 0x0253 },
+{ 0x0182, 0x0182, 0x0183 },
+{ 0x0183, 0x0182, 0x0183 },
+{ 0x0184, 0x0184, 0x0185 },
+{ 0x0185, 0x0184, 0x0185 },
+{ 0x0186, 0x0186, 0x0254 },
+{ 0x0187, 0x0187, 0x0188 },
+{ 0x0188, 0x0187, 0x0188 },
+{ 0x0189, 0x0189, 0x0256 },
+{ 0x018A, 0x018A, 0x0257 },
+{ 0x018B, 0x018B, 0x018C },
+{ 0x018C, 0x018B, 0x018C },
+{ 0x018D, 0x018D, 0x018D },
+{ 0x018E, 0x018E, 0x01DD },
+{ 0x018F, 0x018F, 0x0259 },
+{ 0x0190, 0x0190, 0x025B },
+{ 0x0191, 0x0191, 0x0192 },
+{ 0x0192, 0x0191, 0x0192 },
+{ 0x0193, 0x0193, 0x0260 },
+{ 0x0194, 0x0194, 0x0263 },
+{ 0x0195, 0x01F6, 0x0195 },
+{ 0x0196, 0x0196, 0x0269 },
+{ 0x0197, 0x0197, 0x0268 },
+{ 0x0198, 0x0198, 0x0199 },
+{ 0x0199, 0x0198, 0x0199 },
+{ 0x019A, 0x023D, 0x019A },
+{ 0x019B, 0x019B, 0x019B },
+{ 0x019C, 0x019C, 0x026F },
+{ 0x019D, 0x019D, 0x0272 },
+{ 0x019E, 0x0220, 0x019E },
+{ 0x019F, 0x019F, 0x0275 },
+{ 0x01A0, 0x01A0, 0x01A1 },
+{ 0x01A1, 0x01A0, 0x01A1 },
+{ 0x01A2, 0x01A2, 0x01A3 },
+{ 0x01A3, 0x01A2, 0x01A3 },
+{ 0x01A4, 0x01A4, 0x01A5 },
+{ 0x01A5, 0x01A4, 0x01A5 },
+{ 0x01A6, 0x01A6, 0x0280 },
+{ 0x01A7, 0x01A7, 0x01A8 },
+{ 0x01A8, 0x01A7, 0x01A8 },
+{ 0x01A9, 0x01A9, 0x0283 },
+{ 0x01AA, 0x01AA, 0x01AA },
+{ 0x01AB, 0x01AB, 0x01AB },
+{ 0x01AC, 0x01AC, 0x01AD },
+{ 0x01AD, 0x01AC, 0x01AD },
+{ 0x01AE, 0x01AE, 0x0288 },
+{ 0x01AF, 0x01AF, 0x01B0 },
+{ 0x01B0, 0x01AF, 0x01B0 },
+{ 0x01B1, 0x01B1, 0x028A },
+{ 0x01B2, 0x01B2, 0x028B },
+{ 0x01B3, 0x01B3, 0x01B4 },
+{ 0x01B4, 0x01B3, 0x01B4 },
+{ 0x01B5, 0x01B5, 0x01B6 },
+{ 0x01B6, 0x01B5, 0x01B6 },
+{ 0x01B7, 0x01B7, 0x0292 },
+{ 0x01B8, 0x01B8, 0x01B9 },
+{ 0x01B9, 0x01B8, 0x01B9 },
+{ 0x01BA, 0x01BA, 0x01BA },
+{ 0x01BB, 0x01BB, 0x01BB },
+{ 0x01BC, 0x01BC, 0x01BD },
+{ 0x01BD, 0x01BC, 0x01BD },
+{ 0x01BE, 0x01BE, 0x01BE },
+{ 0x01BF, 0x01F7, 0x01BF },
+{ 0x01C0, 0x01C0, 0x01C0 },
+{ 0x01C1, 0x01C1, 0x01C1 },
+{ 0x01C2, 0x01C2, 0x01C2 },
+{ 0x01C3, 0x01C3, 0x01C3 },
+{ 0x01C4, 0x01C4, 0x01C6 },
+{ 0x01C5, 0x01C4, 0x01C6 },
+{ 0x01C6, 0x01C4, 0x01C6 },
+{ 0x01C7, 0x01C7, 0x01C9 },
+{ 0x01C8, 0x01C7, 0x01C9 },
+{ 0x01C9, 0x01C7, 0x01C9 },
+{ 0x01CA, 0x01CA, 0x01CC },
+{ 0x01CB, 0x01CA, 0x01CC },
+{ 0x01CC, 0x01CA, 0x01CC },
+{ 0x01CD, 0x01CD, 0x01CE },
+{ 0x01CE, 0x01CD, 0x01CE },
+{ 0x01CF, 0x01CF, 0x01D0 },
+{ 0x01D0, 0x01CF, 0x01D0 },
+{ 0x01D1, 0x01D1, 0x01D2 },
+{ 0x01D2, 0x01D1, 0x01D2 },
+{ 0x01D3, 0x01D3, 0x01D4 },
+{ 0x01D4, 0x01D3, 0x01D4 },
+{ 0x01D5, 0x01D5, 0x01D6 },
+{ 0x01D6, 0x01D5, 0x01D6 },
+{ 0x01D7, 0x01D7, 0x01D8 },
+{ 0x01D8, 0x01D7, 0x01D8 },
+{ 0x01D9, 0x01D9, 0x01DA },
+{ 0x01DA, 0x01D9, 0x01DA },
+{ 0x01DB, 0x01DB, 0x01DC },
+{ 0x01DC, 0x01DB, 0x01DC },
+{ 0x01DD, 0x018E, 0x01DD },
+{ 0x01DE, 0x01DE, 0x01DF },
+{ 0x01DF, 0x01DE, 0x01DF },
+{ 0x01E0, 0x01E0, 0x01E1 },
+{ 0x01E1, 0x01E0, 0x01E1 },
+{ 0x01E2, 0x01E2, 0x01E3 },
+{ 0x01E3, 0x01E2, 0x01E3 },
+{ 0x01E4, 0x01E4, 0x01E5 },
+{ 0x01E5, 0x01E4, 0x01E5 },
+{ 0x01E6, 0x01E6, 0x01E7 },
+{ 0x01E7, 0x01E6, 0x01E7 },
+{ 0x01E8, 0x01E8, 0x01E9 },
+{ 0x01E9, 0x01E8, 0x01E9 },
+{ 0x01EA, 0x01EA, 0x01EB },
+{ 0x01EB, 0x01EA, 0x01EB },
+{ 0x01EC, 0x01EC, 0x01ED },
+{ 0x01ED, 0x01EC, 0x01ED },
+{ 0x01EE, 0x01EE, 0x01EF },
+{ 0x01EF, 0x01EE, 0x01EF },
+{ 0x01F0, 0x01F0, 0x01F0 },
+{ 0x01F1, 0x01F1, 0x01F3 },
+{ 0x01F2, 0x01F1, 0x01F3 },
+{ 0x01F3, 0x01F1, 0x01F3 },
+{ 0x01F4, 0x01F4, 0x01F5 },
+{ 0x01F5, 0x01F4, 0x01F5 },
+{ 0x01F6, 0x01F6, 0x0195 },
+{ 0x01F7, 0x01F7, 0x01BF },
+{ 0x01F8, 0x01F8, 0x01F9 },
+{ 0x01F9, 0x01F8, 0x01F9 },
+{ 0x01FA, 0x01FA, 0x01FB },
+{ 0x01FB, 0x01FA, 0x01FB },
+{ 0x01FC, 0x01FC, 0x01FD },
+{ 0x01FD, 0x01FC, 0x01FD },
+{ 0x01FE, 0x01FE, 0x01FF },
+{ 0x01FF, 0x01FE, 0x01FF },
+{ 0x0200, 0x0200, 0x0201 },
+{ 0x0201, 0x0200, 0x0201 },
+{ 0x0202, 0x0202, 0x0203 },
+{ 0x0203, 0x0202, 0x0203 },
+{ 0x0204, 0x0204, 0x0205 },
+{ 0x0205, 0x0204, 0x0205 },
+{ 0x0206, 0x0206, 0x0207 },
+{ 0x0207, 0x0206, 0x0207 },
+{ 0x0208, 0x0208, 0x0209 },
+{ 0x0209, 0x0208, 0x0209 },
+{ 0x020A, 0x020A, 0x020B },
+{ 0x020B, 0x020A, 0x020B },
+{ 0x020C, 0x020C, 0x020D },
+{ 0x020D, 0x020C, 0x020D },
+{ 0x020E, 0x020E, 0x020F },
+{ 0x020F, 0x020E, 0x020F },
+{ 0x0210, 0x0210, 0x0211 },
+{ 0x0211, 0x0210, 0x0211 },
+{ 0x0212, 0x0212, 0x0213 },
+{ 0x0213, 0x0212, 0x0213 },
+{ 0x0214, 0x0214, 0x0215 },
+{ 0x0215, 0x0214, 0x0215 },
+{ 0x0216, 0x0216, 0x0217 },
+{ 0x0217, 0x0216, 0x0217 },
+{ 0x0218, 0x0218, 0x0219 },
+{ 0x0219, 0x0218, 0x0219 },
+{ 0x021A, 0x021A, 0x021B },
+{ 0x021B, 0x021A, 0x021B },
+{ 0x021C, 0x021C, 0x021D },
+{ 0x021D, 0x021C, 0x021D },
+{ 0x021E, 0x021E, 0x021F },
+{ 0x021F, 0x021E, 0x021F },
+{ 0x0220, 0x0220, 0x019E },
+{ 0x0221, 0x0221, 0x0221 },
+{ 0x0222, 0x0222, 0x0223 },
+{ 0x0223, 0x0222, 0x0223 },
+{ 0x0224, 0x0224, 0x0225 },
+{ 0x0225, 0x0224, 0x0225 },
+{ 0x0226, 0x0226, 0x0227 },
+{ 0x0227, 0x0226, 0x0227 },
+{ 0x0228, 0x0228, 0x0229 },
+{ 0x0229, 0x0228, 0x0229 },
+{ 0x022A, 0x022A, 0x022B },
+{ 0x022B, 0x022A, 0x022B },
+{ 0x022C, 0x022C, 0x022D },
+{ 0x022D, 0x022C, 0x022D },
+{ 0x022E, 0x022E, 0x022F },
+{ 0x022F, 0x022E, 0x022F },
+{ 0x0230, 0x0230, 0x0231 },
+{ 0x0231, 0x0230, 0x0231 },
+{ 0x0232, 0x0232, 0x0233 },
+{ 0x0233, 0x0232, 0x0233 },
+{ 0x0234, 0x0234, 0x0234 },
+{ 0x0235, 0x0235, 0x0235 },
+{ 0x0236, 0x0236, 0x0236 },
+{ 0x0237, 0x0237, 0x0237 },
+{ 0x0238, 0x0238, 0x0238 },
+{ 0x0239, 0x0239, 0x0239 },
+{ 0x023A, 0x023A, 0x023A },
+{ 0x023B, 0x023B, 0x023C },
+{ 0x023C, 0x023B, 0x023C },
+{ 0x023D, 0x023D, 0x019A },
+{ 0x023E, 0x023E, 0x023E },
+{ 0x023F, 0x023F, 0x023F },
+{ 0x0240, 0x0240, 0x0240 },
+{ 0x0241, 0x0241, 0x0294 },
+{ 0x0250, 0x0250, 0x0250 },
+{ 0x0251, 0x0251, 0x0251 },
+{ 0x0252, 0x0252, 0x0252 },
+{ 0x0253, 0x0181, 0x0253 },
+{ 0x0254, 0x0186, 0x0254 },
+{ 0x0255, 0x0255, 0x0255 },
+{ 0x0256, 0x0189, 0x0256 },
+{ 0x0257, 0x018A, 0x0257 },
+{ 0x0258, 0x0258, 0x0258 },
+{ 0x0259, 0x018F, 0x0259 },
+{ 0x025A, 0x025A, 0x025A },
+{ 0x025B, 0x0190, 0x025B },
+{ 0x025C, 0x025C, 0x025C },
+{ 0x025D, 0x025D, 0x025D },
+{ 0x025E, 0x025E, 0x025E },
+{ 0x025F, 0x025F, 0x025F },
+{ 0x0260, 0x0193, 0x0260 },
+{ 0x0261, 0x0261, 0x0261 },
+{ 0x0262, 0x0262, 0x0262 },
+{ 0x0263, 0x0194, 0x0263 },
+{ 0x0264, 0x0264, 0x0264 },
+{ 0x0265, 0x0265, 0x0265 },
+{ 0x0266, 0x0266, 0x0266 },
+{ 0x0267, 0x0267, 0x0267 },
+{ 0x0268, 0x0197, 0x0268 },
+{ 0x0269, 0x0196, 0x0269 },
+{ 0x026A, 0x026A, 0x026A },
+{ 0x026B, 0x026B, 0x026B },
+{ 0x026C, 0x026C, 0x026C },
+{ 0x026D, 0x026D, 0x026D },
+{ 0x026E, 0x026E, 0x026E },
+{ 0x026F, 0x019C, 0x026F },
+{ 0x0270, 0x0270, 0x0270 },
+{ 0x0271, 0x0271, 0x0271 },
+{ 0x0272, 0x019D, 0x0272 },
+{ 0x0273, 0x0273, 0x0273 },
+{ 0x0274, 0x0274, 0x0274 },
+{ 0x0275, 0x019F, 0x0275 },
+{ 0x0276, 0x0276, 0x0276 },
+{ 0x0277, 0x0277, 0x0277 },
+{ 0x0278, 0x0278, 0x0278 },
+{ 0x0279, 0x0279, 0x0279 },
+{ 0x027A, 0x027A, 0x027A },
+{ 0x027B, 0x027B, 0x027B },
+{ 0x027C, 0x027C, 0x027C },
+{ 0x027D, 0x027D, 0x027D },
+{ 0x027E, 0x027E, 0x027E },
+{ 0x027F, 0x027F, 0x027F },
+{ 0x0280, 0x01A6, 0x0280 },
+{ 0x0281, 0x0281, 0x0281 },
+{ 0x0282, 0x0282, 0x0282 },
+{ 0x0283, 0x01A9, 0x0283 },
+{ 0x0284, 0x0284, 0x0284 },
+{ 0x0285, 0x0285, 0x0285 },
+{ 0x0286, 0x0286, 0x0286 },
+{ 0x0287, 0x0287, 0x0287 },
+{ 0x0288, 0x01AE, 0x0288 },
+{ 0x0289, 0x0289, 0x0289 },
+{ 0x028A, 0x01B1, 0x028A },
+{ 0x028B, 0x01B2, 0x028B },
+{ 0x028C, 0x028C, 0x028C },
+{ 0x028D, 0x028D, 0x028D },
+{ 0x028E, 0x028E, 0x028E },
+{ 0x028F, 0x028F, 0x028F },
+{ 0x0290, 0x0290, 0x0290 },
+{ 0x0291, 0x0291, 0x0291 },
+{ 0x0292, 0x01B7, 0x0292 },
+{ 0x0293, 0x0293, 0x0293 },
+{ 0x0294, 0x0241, 0x0294 },
+{ 0x0295, 0x0295, 0x0295 },
+{ 0x0296, 0x0296, 0x0296 },
+{ 0x0297, 0x0297, 0x0297 },
+{ 0x0298, 0x0298, 0x0298 },
+{ 0x0299, 0x0299, 0x0299 },
+{ 0x029A, 0x029A, 0x029A },
+{ 0x029B, 0x029B, 0x029B },
+{ 0x029C, 0x029C, 0x029C },
+{ 0x029D, 0x029D, 0x029D },
+{ 0x029E, 0x029E, 0x029E },
+{ 0x029F, 0x029F, 0x029F },
+{ 0x02A0, 0x02A0, 0x02A0 },
+{ 0x02A1, 0x02A1, 0x02A1 },
+{ 0x02A2, 0x02A2, 0x02A2 },
+{ 0x02A3, 0x02A3, 0x02A3 },
+{ 0x02A4, 0x02A4, 0x02A4 },
+{ 0x02A5, 0x02A5, 0x02A5 },
+{ 0x02A6, 0x02A6, 0x02A6 },
+{ 0x02A7, 0x02A7, 0x02A7 },
+{ 0x02A8, 0x02A8, 0x02A8 },
+{ 0x02A9, 0x02A9, 0x02A9 },
+{ 0x02AA, 0x02AA, 0x02AA },
+{ 0x02AB, 0x02AB, 0x02AB },
+{ 0x02AC, 0x02AC, 0x02AC },
+{ 0x02AD, 0x02AD, 0x02AD },
+{ 0x02AE, 0x02AE, 0x02AE },
+{ 0x02AF, 0x02AF, 0x02AF },
+{ 0x02B0, 0x02B0, 0x02B0 },
+{ 0x02B1, 0x02B1, 0x02B1 },
+{ 0x02B2, 0x02B2, 0x02B2 },
+{ 0x02B3, 0x02B3, 0x02B3 },
+{ 0x02B4, 0x02B4, 0x02B4 },
+{ 0x02B5, 0x02B5, 0x02B5 },
+{ 0x02B6, 0x02B6, 0x02B6 },
+{ 0x02B7, 0x02B7, 0x02B7 },
+{ 0x02B8, 0x02B8, 0x02B8 },
+{ 0x02B9, 0x02B9, 0x02B9 },
+{ 0x02BA, 0x02BA, 0x02BA },
+{ 0x02BB, 0x02BB, 0x02BB },
+{ 0x02BC, 0x02BC, 0x02BC },
+{ 0x02BD, 0x02BD, 0x02BD },
+{ 0x02BE, 0x02BE, 0x02BE },
+{ 0x02BF, 0x02BF, 0x02BF },
+{ 0x02C0, 0x02C0, 0x02C0 },
+{ 0x02C1, 0x02C1, 0x02C1 },
+{ 0x02C6, 0x02C6, 0x02C6 },
+{ 0x02C7, 0x02C7, 0x02C7 },
+{ 0x02C8, 0x02C8, 0x02C8 },
+{ 0x02C9, 0x02C9, 0x02C9 },
+{ 0x02CA, 0x02CA, 0x02CA },
+{ 0x02CB, 0x02CB, 0x02CB },
+{ 0x02CC, 0x02CC, 0x02CC },
+{ 0x02CD, 0x02CD, 0x02CD },
+{ 0x02CE, 0x02CE, 0x02CE },
+{ 0x02CF, 0x02CF, 0x02CF },
+{ 0x02D0, 0x02D0, 0x02D0 },
+{ 0x02D1, 0x02D1, 0x02D1 },
+{ 0x02E0, 0x02E0, 0x02E0 },
+{ 0x02E1, 0x02E1, 0x02E1 },
+{ 0x02E2, 0x02E2, 0x02E2 },
+{ 0x02E3, 0x02E3, 0x02E3 },
+{ 0x02E4, 0x02E4, 0x02E4 },
+{ 0x02EE, 0x02EE, 0x02EE },
+{ 0x0300, 0x0300, 0x0300 },
+{ 0x0301, 0x0301, 0x0301 },
+{ 0x0302, 0x0302, 0x0302 },
+{ 0x0303, 0x0303, 0x0303 },
+{ 0x0304, 0x0304, 0x0304 },
+{ 0x0305, 0x0305, 0x0305 },
+{ 0x0306, 0x0306, 0x0306 },
+{ 0x0307, 0x0307, 0x0307 },
+{ 0x0308, 0x0308, 0x0308 },
+{ 0x0309, 0x0309, 0x0309 },
+{ 0x030A, 0x030A, 0x030A },
+{ 0x030B, 0x030B, 0x030B },
+{ 0x030C, 0x030C, 0x030C },
+{ 0x030D, 0x030D, 0x030D },
+{ 0x030E, 0x030E, 0x030E },
+{ 0x030F, 0x030F, 0x030F },
+{ 0x0310, 0x0310, 0x0310 },
+{ 0x0311, 0x0311, 0x0311 },
+{ 0x0312, 0x0312, 0x0312 },
+{ 0x0313, 0x0313, 0x0313 },
+{ 0x0314, 0x0314, 0x0314 },
+{ 0x0315, 0x0315, 0x0315 },
+{ 0x0316, 0x0316, 0x0316 },
+{ 0x0317, 0x0317, 0x0317 },
+{ 0x0318, 0x0318, 0x0318 },
+{ 0x0319, 0x0319, 0x0319 },
+{ 0x031A, 0x031A, 0x031A },
+{ 0x031B, 0x031B, 0x031B },
+{ 0x031C, 0x031C, 0x031C },
+{ 0x031D, 0x031D, 0x031D },
+{ 0x031E, 0x031E, 0x031E },
+{ 0x031F, 0x031F, 0x031F },
+{ 0x0320, 0x0320, 0x0320 },
+{ 0x0321, 0x0321, 0x0321 },
+{ 0x0322, 0x0322, 0x0322 },
+{ 0x0323, 0x0323, 0x0323 },
+{ 0x0324, 0x0324, 0x0324 },
+{ 0x0325, 0x0325, 0x0325 },
+{ 0x0326, 0x0326, 0x0326 },
+{ 0x0327, 0x0327, 0x0327 },
+{ 0x0328, 0x0328, 0x0328 },
+{ 0x0329, 0x0329, 0x0329 },
+{ 0x032A, 0x032A, 0x032A },
+{ 0x032B, 0x032B, 0x032B },
+{ 0x032C, 0x032C, 0x032C },
+{ 0x032D, 0x032D, 0x032D },
+{ 0x032E, 0x032E, 0x032E },
+{ 0x032F, 0x032F, 0x032F },
+{ 0x0330, 0x0330, 0x0330 },
+{ 0x0331, 0x0331, 0x0331 },
+{ 0x0332, 0x0332, 0x0332 },
+{ 0x0333, 0x0333, 0x0333 },
+{ 0x0334, 0x0334, 0x0334 },
+{ 0x0335, 0x0335, 0x0335 },
+{ 0x0336, 0x0336, 0x0336 },
+{ 0x0337, 0x0337, 0x0337 },
+{ 0x0338, 0x0338, 0x0338 },
+{ 0x0339, 0x0339, 0x0339 },
+{ 0x033A, 0x033A, 0x033A },
+{ 0x033B, 0x033B, 0x033B },
+{ 0x033C, 0x033C, 0x033C },
+{ 0x033D, 0x033D, 0x033D },
+{ 0x033E, 0x033E, 0x033E },
+{ 0x033F, 0x033F, 0x033F },
+{ 0x0340, 0x0340, 0x0340 },
+{ 0x0341, 0x0341, 0x0341 },
+{ 0x0342, 0x0342, 0x0342 },
+{ 0x0343, 0x0343, 0x0343 },
+{ 0x0344, 0x0344, 0x0344 },
+{ 0x0345, 0x0399, 0x0345 },
+{ 0x0346, 0x0346, 0x0346 },
+{ 0x0347, 0x0347, 0x0347 },
+{ 0x0348, 0x0348, 0x0348 },
+{ 0x0349, 0x0349, 0x0349 },
+{ 0x034A, 0x034A, 0x034A },
+{ 0x034B, 0x034B, 0x034B },
+{ 0x034C, 0x034C, 0x034C },
+{ 0x034D, 0x034D, 0x034D },
+{ 0x034E, 0x034E, 0x034E },
+{ 0x034F, 0x034F, 0x034F },
+{ 0x0350, 0x0350, 0x0350 },
+{ 0x0351, 0x0351, 0x0351 },
+{ 0x0352, 0x0352, 0x0352 },
+{ 0x0353, 0x0353, 0x0353 },
+{ 0x0354, 0x0354, 0x0354 },
+{ 0x0355, 0x0355, 0x0355 },
+{ 0x0356, 0x0356, 0x0356 },
+{ 0x0357, 0x0357, 0x0357 },
+{ 0x0358, 0x0358, 0x0358 },
+{ 0x0359, 0x0359, 0x0359 },
+{ 0x035A, 0x035A, 0x035A },
+{ 0x035B, 0x035B, 0x035B },
+{ 0x035C, 0x035C, 0x035C },
+{ 0x035D, 0x035D, 0x035D },
+{ 0x035E, 0x035E, 0x035E },
+{ 0x035F, 0x035F, 0x035F },
+{ 0x0360, 0x0360, 0x0360 },
+{ 0x0361, 0x0361, 0x0361 },
+{ 0x0362, 0x0362, 0x0362 },
+{ 0x0363, 0x0363, 0x0363 },
+{ 0x0364, 0x0364, 0x0364 },
+{ 0x0365, 0x0365, 0x0365 },
+{ 0x0366, 0x0366, 0x0366 },
+{ 0x0367, 0x0367, 0x0367 },
+{ 0x0368, 0x0368, 0x0368 },
+{ 0x0369, 0x0369, 0x0369 },
+{ 0x036A, 0x036A, 0x036A },
+{ 0x036B, 0x036B, 0x036B },
+{ 0x036C, 0x036C, 0x036C },
+{ 0x036D, 0x036D, 0x036D },
+{ 0x036E, 0x036E, 0x036E },
+{ 0x036F, 0x036F, 0x036F },
+{ 0x037A, 0x037A, 0x037A },
+{ 0x0386, 0x0386, 0x03AC },
+{ 0x0388, 0x0388, 0x03AD },
+{ 0x0389, 0x0389, 0x03AE },
+{ 0x038A, 0x038A, 0x03AF },
+{ 0x038C, 0x038C, 0x03CC },
+{ 0x038E, 0x038E, 0x03CD },
+{ 0x038F, 0x038F, 0x03CE },
+{ 0x0390, 0x0390, 0x0390 },
+{ 0x0391, 0x0391, 0x03B1 },
+{ 0x0392, 0x0392, 0x03B2 },
+{ 0x0393, 0x0393, 0x03B3 },
+{ 0x0394, 0x0394, 0x03B4 },
+{ 0x0395, 0x0395, 0x03B5 },
+{ 0x0396, 0x0396, 0x03B6 },
+{ 0x0397, 0x0397, 0x03B7 },
+{ 0x0398, 0x0398, 0x03B8 },
+{ 0x0399, 0x0399, 0x03B9 },
+{ 0x039A, 0x039A, 0x03BA },
+{ 0x039B, 0x039B, 0x03BB },
+{ 0x039C, 0x039C, 0x03BC },
+{ 0x039D, 0x039D, 0x03BD },
+{ 0x039E, 0x039E, 0x03BE },
+{ 0x039F, 0x039F, 0x03BF },
+{ 0x03A0, 0x03A0, 0x03C0 },
+{ 0x03A1, 0x03A1, 0x03C1 },
+{ 0x03A3, 0x03A3, 0x03C3 },
+{ 0x03A4, 0x03A4, 0x03C4 },
+{ 0x03A5, 0x03A5, 0x03C5 },
+{ 0x03A6, 0x03A6, 0x03C6 },
+{ 0x03A7, 0x03A7, 0x03C7 },
+{ 0x03A8, 0x03A8, 0x03C8 },
+{ 0x03A9, 0x03A9, 0x03C9 },
+{ 0x03AA, 0x03AA, 0x03CA },
+{ 0x03AB, 0x03AB, 0x03CB },
+{ 0x03AC, 0x0386, 0x03AC },
+{ 0x03AD, 0x0388, 0x03AD },
+{ 0x03AE, 0x0389, 0x03AE },
+{ 0x03AF, 0x038A, 0x03AF },
+{ 0x03B0, 0x03B0, 0x03B0 },
+{ 0x03B1, 0x0391, 0x03B1 },
+{ 0x03B2, 0x0392, 0x03B2 },
+{ 0x03B3, 0x0393, 0x03B3 },
+{ 0x03B4, 0x0394, 0x03B4 },
+{ 0x03B5, 0x0395, 0x03B5 },
+{ 0x03B6, 0x0396, 0x03B6 },
+{ 0x03B7, 0x0397, 0x03B7 },
+{ 0x03B8, 0x0398, 0x03B8 },
+{ 0x03B9, 0x0399, 0x03B9 },
+{ 0x03BA, 0x039A, 0x03BA },
+{ 0x03BB, 0x039B, 0x03BB },
+{ 0x03BC, 0x039C, 0x03BC },
+{ 0x03BD, 0x039D, 0x03BD },
+{ 0x03BE, 0x039E, 0x03BE },
+{ 0x03BF, 0x039F, 0x03BF },
+{ 0x03C0, 0x03A0, 0x03C0 },
+{ 0x03C1, 0x03A1, 0x03C1 },
+{ 0x03C2, 0x03A3, 0x03C2 },
+{ 0x03C3, 0x03A3, 0x03C3 },
+{ 0x03C4, 0x03A4, 0x03C4 },
+{ 0x03C5, 0x03A5, 0x03C5 },
+{ 0x03C6, 0x03A6, 0x03C6 },
+{ 0x03C7, 0x03A7, 0x03C7 },
+{ 0x03C8, 0x03A8, 0x03C8 },
+{ 0x03C9, 0x03A9, 0x03C9 },
+{ 0x03CA, 0x03AA, 0x03CA },
+{ 0x03CB, 0x03AB, 0x03CB },
+{ 0x03CC, 0x038C, 0x03CC },
+{ 0x03CD, 0x038E, 0x03CD },
+{ 0x03CE, 0x038F, 0x03CE },
+{ 0x03D0, 0x0392, 0x03D0 },
+{ 0x03D1, 0x0398, 0x03D1 },
+{ 0x03D2, 0x03D2, 0x03D2 },
+{ 0x03D3, 0x03D3, 0x03D3 },
+{ 0x03D4, 0x03D4, 0x03D4 },
+{ 0x03D5, 0x03A6, 0x03D5 },
+{ 0x03D6, 0x03A0, 0x03D6 },
+{ 0x03D7, 0x03D7, 0x03D7 },
+{ 0x03D8, 0x03D8, 0x03D9 },
+{ 0x03D9, 0x03D8, 0x03D9 },
+{ 0x03DA, 0x03DA, 0x03DB },
+{ 0x03DB, 0x03DA, 0x03DB },
+{ 0x03DC, 0x03DC, 0x03DD },
+{ 0x03DD, 0x03DC, 0x03DD },
+{ 0x03DE, 0x03DE, 0x03DF },
+{ 0x03DF, 0x03DE, 0x03DF },
+{ 0x03E0, 0x03E0, 0x03E1 },
+{ 0x03E1, 0x03E0, 0x03E1 },
+{ 0x03E2, 0x03E2, 0x03E3 },
+{ 0x03E3, 0x03E2, 0x03E3 },
+{ 0x03E4, 0x03E4, 0x03E5 },
+{ 0x03E5, 0x03E4, 0x03E5 },
+{ 0x03E6, 0x03E6, 0x03E7 },
+{ 0x03E7, 0x03E6, 0x03E7 },
+{ 0x03E8, 0x03E8, 0x03E9 },
+{ 0x03E9, 0x03E8, 0x03E9 },
+{ 0x03EA, 0x03EA, 0x03EB },
+{ 0x03EB, 0x03EA, 0x03EB },
+{ 0x03EC, 0x03EC, 0x03ED },
+{ 0x03ED, 0x03EC, 0x03ED },
+{ 0x03EE, 0x03EE, 0x03EF },
+{ 0x03EF, 0x03EE, 0x03EF },
+{ 0x03F0, 0x039A, 0x03F0 },
+{ 0x03F1, 0x03A1, 0x03F1 },
+{ 0x03F2, 0x03F9, 0x03F2 },
+{ 0x03F3, 0x03F3, 0x03F3 },
+{ 0x03F4, 0x03F4, 0x03B8 },
+{ 0x03F5, 0x0395, 0x03F5 },
+{ 0x03F7, 0x03F7, 0x03F8 },
+{ 0x03F8, 0x03F7, 0x03F8 },
+{ 0x03F9, 0x03F9, 0x03F2 },
+{ 0x03FA, 0x03FA, 0x03FB },
+{ 0x03FB, 0x03FA, 0x03FB },
+{ 0x03FC, 0x03FC, 0x03FC },
+{ 0x03FD, 0x03FD, 0x03FD },
+{ 0x03FE, 0x03FE, 0x03FE },
+{ 0x03FF, 0x03FF, 0x03FF },
+{ 0x0400, 0x0400, 0x0450 },
+{ 0x0401, 0x0401, 0x0451 },
+{ 0x0402, 0x0402, 0x0452 },
+{ 0x0403, 0x0403, 0x0453 },
+{ 0x0404, 0x0404, 0x0454 },
+{ 0x0405, 0x0405, 0x0455 },
+{ 0x0406, 0x0406, 0x0456 },
+{ 0x0407, 0x0407, 0x0457 },
+{ 0x0408, 0x0408, 0x0458 },
+{ 0x0409, 0x0409, 0x0459 },
+{ 0x040A, 0x040A, 0x045A },
+{ 0x040B, 0x040B, 0x045B },
+{ 0x040C, 0x040C, 0x045C },
+{ 0x040D, 0x040D, 0x045D },
+{ 0x040E, 0x040E, 0x045E },
+{ 0x040F, 0x040F, 0x045F },
+{ 0x0410, 0x0410, 0x0430 },
+{ 0x0411, 0x0411, 0x0431 },
+{ 0x0412, 0x0412, 0x0432 },
+{ 0x0413, 0x0413, 0x0433 },
+{ 0x0414, 0x0414, 0x0434 },
+{ 0x0415, 0x0415, 0x0435 },
+{ 0x0416, 0x0416, 0x0436 },
+{ 0x0417, 0x0417, 0x0437 },
+{ 0x0418, 0x0418, 0x0438 },
+{ 0x0419, 0x0419, 0x0439 },
+{ 0x041A, 0x041A, 0x043A },
+{ 0x041B, 0x041B, 0x043B },
+{ 0x041C, 0x041C, 0x043C },
+{ 0x041D, 0x041D, 0x043D },
+{ 0x041E, 0x041E, 0x043E },
+{ 0x041F, 0x041F, 0x043F },
+{ 0x0420, 0x0420, 0x0440 },
+{ 0x0421, 0x0421, 0x0441 },
+{ 0x0422, 0x0422, 0x0442 },
+{ 0x0423, 0x0423, 0x0443 },
+{ 0x0424, 0x0424, 0x0444 },
+{ 0x0425, 0x0425, 0x0445 },
+{ 0x0426, 0x0426, 0x0446 },
+{ 0x0427, 0x0427, 0x0447 },
+{ 0x0428, 0x0428, 0x0448 },
+{ 0x0429, 0x0429, 0x0449 },
+{ 0x042A, 0x042A, 0x044A },
+{ 0x042B, 0x042B, 0x044B },
+{ 0x042C, 0x042C, 0x044C },
+{ 0x042D, 0x042D, 0x044D },
+{ 0x042E, 0x042E, 0x044E },
+{ 0x042F, 0x042F, 0x044F },
+{ 0x0430, 0x0410, 0x0430 },
+{ 0x0431, 0x0411, 0x0431 },
+{ 0x0432, 0x0412, 0x0432 },
+{ 0x0433, 0x0413, 0x0433 },
+{ 0x0434, 0x0414, 0x0434 },
+{ 0x0435, 0x0415, 0x0435 },
+{ 0x0436, 0x0416, 0x0436 },
+{ 0x0437, 0x0417, 0x0437 },
+{ 0x0438, 0x0418, 0x0438 },
+{ 0x0439, 0x0419, 0x0439 },
+{ 0x043A, 0x041A, 0x043A },
+{ 0x043B, 0x041B, 0x043B },
+{ 0x043C, 0x041C, 0x043C },
+{ 0x043D, 0x041D, 0x043D },
+{ 0x043E, 0x041E, 0x043E },
+{ 0x043F, 0x041F, 0x043F },
+{ 0x0440, 0x0420, 0x0440 },
+{ 0x0441, 0x0421, 0x0441 },
+{ 0x0442, 0x0422, 0x0442 },
+{ 0x0443, 0x0423, 0x0443 },
+{ 0x0444, 0x0424, 0x0444 },
+{ 0x0445, 0x0425, 0x0445 },
+{ 0x0446, 0x0426, 0x0446 },
+{ 0x0447, 0x0427, 0x0447 },
+{ 0x0448, 0x0428, 0x0448 },
+{ 0x0449, 0x0429, 0x0449 },
+{ 0x044A, 0x042A, 0x044A },
+{ 0x044B, 0x042B, 0x044B },
+{ 0x044C, 0x042C, 0x044C },
+{ 0x044D, 0x042D, 0x044D },
+{ 0x044E, 0x042E, 0x044E },
+{ 0x044F, 0x042F, 0x044F },
+{ 0x0450, 0x0400, 0x0450 },
+{ 0x0451, 0x0401, 0x0451 },
+{ 0x0452, 0x0402, 0x0452 },
+{ 0x0453, 0x0403, 0x0453 },
+{ 0x0454, 0x0404, 0x0454 },
+{ 0x0455, 0x0405, 0x0455 },
+{ 0x0456, 0x0406, 0x0456 },
+{ 0x0457, 0x0407, 0x0457 },
+{ 0x0458, 0x0408, 0x0458 },
+{ 0x0459, 0x0409, 0x0459 },
+{ 0x045A, 0x040A, 0x045A },
+{ 0x045B, 0x040B, 0x045B },
+{ 0x045C, 0x040C, 0x045C },
+{ 0x045D, 0x040D, 0x045D },
+{ 0x045E, 0x040E, 0x045E },
+{ 0x045F, 0x040F, 0x045F },
+{ 0x0460, 0x0460, 0x0461 },
+{ 0x0461, 0x0460, 0x0461 },
+{ 0x0462, 0x0462, 0x0463 },
+{ 0x0463, 0x0462, 0x0463 },
+{ 0x0464, 0x0464, 0x0465 },
+{ 0x0465, 0x0464, 0x0465 },
+{ 0x0466, 0x0466, 0x0467 },
+{ 0x0467, 0x0466, 0x0467 },
+{ 0x0468, 0x0468, 0x0469 },
+{ 0x0469, 0x0468, 0x0469 },
+{ 0x046A, 0x046A, 0x046B },
+{ 0x046B, 0x046A, 0x046B },
+{ 0x046C, 0x046C, 0x046D },
+{ 0x046D, 0x046C, 0x046D },
+{ 0x046E, 0x046E, 0x046F },
+{ 0x046F, 0x046E, 0x046F },
+{ 0x0470, 0x0470, 0x0471 },
+{ 0x0471, 0x0470, 0x0471 },
+{ 0x0472, 0x0472, 0x0473 },
+{ 0x0473, 0x0472, 0x0473 },
+{ 0x0474, 0x0474, 0x0475 },
+{ 0x0475, 0x0474, 0x0475 },
+{ 0x0476, 0x0476, 0x0477 },
+{ 0x0477, 0x0476, 0x0477 },
+{ 0x0478, 0x0478, 0x0479 },
+{ 0x0479, 0x0478, 0x0479 },
+{ 0x047A, 0x047A, 0x047B },
+{ 0x047B, 0x047A, 0x047B },
+{ 0x047C, 0x047C, 0x047D },
+{ 0x047D, 0x047C, 0x047D },
+{ 0x047E, 0x047E, 0x047F },
+{ 0x047F, 0x047E, 0x047F },
+{ 0x0480, 0x0480, 0x0481 },
+{ 0x0481, 0x0480, 0x0481 },
+{ 0x0483, 0x0483, 0x0483 },
+{ 0x0484, 0x0484, 0x0484 },
+{ 0x0485, 0x0485, 0x0485 },
+{ 0x0486, 0x0486, 0x0486 },
+{ 0x048A, 0x048A, 0x048B },
+{ 0x048B, 0x048A, 0x048B },
+{ 0x048C, 0x048C, 0x048D },
+{ 0x048D, 0x048C, 0x048D },
+{ 0x048E, 0x048E, 0x048F },
+{ 0x048F, 0x048E, 0x048F },
+{ 0x0490, 0x0490, 0x0491 },
+{ 0x0491, 0x0490, 0x0491 },
+{ 0x0492, 0x0492, 0x0493 },
+{ 0x0493, 0x0492, 0x0493 },
+{ 0x0494, 0x0494, 0x0495 },
+{ 0x0495, 0x0494, 0x0495 },
+{ 0x0496, 0x0496, 0x0497 },
+{ 0x0497, 0x0496, 0x0497 },
+{ 0x0498, 0x0498, 0x0499 },
+{ 0x0499, 0x0498, 0x0499 },
+{ 0x049A, 0x049A, 0x049B },
+{ 0x049B, 0x049A, 0x049B },
+{ 0x049C, 0x049C, 0x049D },
+{ 0x049D, 0x049C, 0x049D },
+{ 0x049E, 0x049E, 0x049F },
+{ 0x049F, 0x049E, 0x049F },
+{ 0x04A0, 0x04A0, 0x04A1 },
+{ 0x04A1, 0x04A0, 0x04A1 },
+{ 0x04A2, 0x04A2, 0x04A3 },
+{ 0x04A3, 0x04A2, 0x04A3 },
+{ 0x04A4, 0x04A4, 0x04A5 },
+{ 0x04A5, 0x04A4, 0x04A5 },
+{ 0x04A6, 0x04A6, 0x04A7 },
+{ 0x04A7, 0x04A6, 0x04A7 },
+{ 0x04A8, 0x04A8, 0x04A9 },
+{ 0x04A9, 0x04A8, 0x04A9 },
+{ 0x04AA, 0x04AA, 0x04AB },
+{ 0x04AB, 0x04AA, 0x04AB },
+{ 0x04AC, 0x04AC, 0x04AD },
+{ 0x04AD, 0x04AC, 0x04AD },
+{ 0x04AE, 0x04AE, 0x04AF },
+{ 0x04AF, 0x04AE, 0x04AF },
+{ 0x04B0, 0x04B0, 0x04B1 },
+{ 0x04B1, 0x04B0, 0x04B1 },
+{ 0x04B2, 0x04B2, 0x04B3 },
+{ 0x04B3, 0x04B2, 0x04B3 },
+{ 0x04B4, 0x04B4, 0x04B5 },
+{ 0x04B5, 0x04B4, 0x04B5 },
+{ 0x04B6, 0x04B6, 0x04B7 },
+{ 0x04B7, 0x04B6, 0x04B7 },
+{ 0x04B8, 0x04B8, 0x04B9 },
+{ 0x04B9, 0x04B8, 0x04B9 },
+{ 0x04BA, 0x04BA, 0x04BB },
+{ 0x04BB, 0x04BA, 0x04BB },
+{ 0x04BC, 0x04BC, 0x04BD },
+{ 0x04BD, 0x04BC, 0x04BD },
+{ 0x04BE, 0x04BE, 0x04BF },
+{ 0x04BF, 0x04BE, 0x04BF },
+{ 0x04C0, 0x04C0, 0x04C0 },
+{ 0x04C1, 0x04C1, 0x04C2 },
+{ 0x04C2, 0x04C1, 0x04C2 },
+{ 0x04C3, 0x04C3, 0x04C4 },
+{ 0x04C4, 0x04C3, 0x04C4 },
+{ 0x04C5, 0x04C5, 0x04C6 },
+{ 0x04C6, 0x04C5, 0x04C6 },
+{ 0x04C7, 0x04C7, 0x04C8 },
+{ 0x04C8, 0x04C7, 0x04C8 },
+{ 0x04C9, 0x04C9, 0x04CA },
+{ 0x04CA, 0x04C9, 0x04CA },
+{ 0x04CB, 0x04CB, 0x04CC },
+{ 0x04CC, 0x04CB, 0x04CC },
+{ 0x04CD, 0x04CD, 0x04CE },
+{ 0x04CE, 0x04CD, 0x04CE },
+{ 0x04D0, 0x04D0, 0x04D1 },
+{ 0x04D1, 0x04D0, 0x04D1 },
+{ 0x04D2, 0x04D2, 0x04D3 },
+{ 0x04D3, 0x04D2, 0x04D3 },
+{ 0x04D4, 0x04D4, 0x04D5 },
+{ 0x04D5, 0x04D4, 0x04D5 },
+{ 0x04D6, 0x04D6, 0x04D7 },
+{ 0x04D7, 0x04D6, 0x04D7 },
+{ 0x04D8, 0x04D8, 0x04D9 },
+{ 0x04D9, 0x04D8, 0x04D9 },
+{ 0x04DA, 0x04DA, 0x04DB },
+{ 0x04DB, 0x04DA, 0x04DB },
+{ 0x04DC, 0x04DC, 0x04DD },
+{ 0x04DD, 0x04DC, 0x04DD },
+{ 0x04DE, 0x04DE, 0x04DF },
+{ 0x04DF, 0x04DE, 0x04DF },
+{ 0x04E0, 0x04E0, 0x04E1 },
+{ 0x04E1, 0x04E0, 0x04E1 },
+{ 0x04E2, 0x04E2, 0x04E3 },
+{ 0x04E3, 0x04E2, 0x04E3 },
+{ 0x04E4, 0x04E4, 0x04E5 },
+{ 0x04E5, 0x04E4, 0x04E5 },
+{ 0x04E6, 0x04E6, 0x04E7 },
+{ 0x04E7, 0x04E6, 0x04E7 },
+{ 0x04E8, 0x04E8, 0x04E9 },
+{ 0x04E9, 0x04E8, 0x04E9 },
+{ 0x04EA, 0x04EA, 0x04EB },
+{ 0x04EB, 0x04EA, 0x04EB },
+{ 0x04EC, 0x04EC, 0x04ED },
+{ 0x04ED, 0x04EC, 0x04ED },
+{ 0x04EE, 0x04EE, 0x04EF },
+{ 0x04EF, 0x04EE, 0x04EF },
+{ 0x04F0, 0x04F0, 0x04F1 },
+{ 0x04F1, 0x04F0, 0x04F1 },
+{ 0x04F2, 0x04F2, 0x04F3 },
+{ 0x04F3, 0x04F2, 0x04F3 },
+{ 0x04F4, 0x04F4, 0x04F5 },
+{ 0x04F5, 0x04F4, 0x04F5 },
+{ 0x04F6, 0x04F6, 0x04F7 },
+{ 0x04F7, 0x04F6, 0x04F7 },
+{ 0x04F8, 0x04F8, 0x04F9 },
+{ 0x04F9, 0x04F8, 0x04F9 },
+{ 0x0500, 0x0500, 0x0501 },
+{ 0x0501, 0x0500, 0x0501 },
+{ 0x0502, 0x0502, 0x0503 },
+{ 0x0503, 0x0502, 0x0503 },
+{ 0x0504, 0x0504, 0x0505 },
+{ 0x0505, 0x0504, 0x0505 },
+{ 0x0506, 0x0506, 0x0507 },
+{ 0x0507, 0x0506, 0x0507 },
+{ 0x0508, 0x0508, 0x0509 },
+{ 0x0509, 0x0508, 0x0509 },
+{ 0x050A, 0x050A, 0x050B },
+{ 0x050B, 0x050A, 0x050B },
+{ 0x050C, 0x050C, 0x050D },
+{ 0x050D, 0x050C, 0x050D },
+{ 0x050E, 0x050E, 0x050F },
+{ 0x050F, 0x050E, 0x050F },
+{ 0x0531, 0x0531, 0x0561 },
+{ 0x0532, 0x0532, 0x0562 },
+{ 0x0533, 0x0533, 0x0563 },
+{ 0x0534, 0x0534, 0x0564 },
+{ 0x0535, 0x0535, 0x0565 },
+{ 0x0536, 0x0536, 0x0566 },
+{ 0x0537, 0x0537, 0x0567 },
+{ 0x0538, 0x0538, 0x0568 },
+{ 0x0539, 0x0539, 0x0569 },
+{ 0x053A, 0x053A, 0x056A },
+{ 0x053B, 0x053B, 0x056B },
+{ 0x053C, 0x053C, 0x056C },
+{ 0x053D, 0x053D, 0x056D },
+{ 0x053E, 0x053E, 0x056E },
+{ 0x053F, 0x053F, 0x056F },
+{ 0x0540, 0x0540, 0x0570 },
+{ 0x0541, 0x0541, 0x0571 },
+{ 0x0542, 0x0542, 0x0572 },
+{ 0x0543, 0x0543, 0x0573 },
+{ 0x0544, 0x0544, 0x0574 },
+{ 0x0545, 0x0545, 0x0575 },
+{ 0x0546, 0x0546, 0x0576 },
+{ 0x0547, 0x0547, 0x0577 },
+{ 0x0548, 0x0548, 0x0578 },
+{ 0x0549, 0x0549, 0x0579 },
+{ 0x054A, 0x054A, 0x057A },
+{ 0x054B, 0x054B, 0x057B },
+{ 0x054C, 0x054C, 0x057C },
+{ 0x054D, 0x054D, 0x057D },
+{ 0x054E, 0x054E, 0x057E },
+{ 0x054F, 0x054F, 0x057F },
+{ 0x0550, 0x0550, 0x0580 },
+{ 0x0551, 0x0551, 0x0581 },
+{ 0x0552, 0x0552, 0x0582 },
+{ 0x0553, 0x0553, 0x0583 },
+{ 0x0554, 0x0554, 0x0584 },
+{ 0x0555, 0x0555, 0x0585 },
+{ 0x0556, 0x0556, 0x0586 },
+{ 0x0559, 0x0559, 0x0559 },
+{ 0x0561, 0x0531, 0x0561 },
+{ 0x0562, 0x0532, 0x0562 },
+{ 0x0563, 0x0533, 0x0563 },
+{ 0x0564, 0x0534, 0x0564 },
+{ 0x0565, 0x0535, 0x0565 },
+{ 0x0566, 0x0536, 0x0566 },
+{ 0x0567, 0x0537, 0x0567 },
+{ 0x0568, 0x0538, 0x0568 },
+{ 0x0569, 0x0539, 0x0569 },
+{ 0x056A, 0x053A, 0x056A },
+{ 0x056B, 0x053B, 0x056B },
+{ 0x056C, 0x053C, 0x056C },
+{ 0x056D, 0x053D, 0x056D },
+{ 0x056E, 0x053E, 0x056E },
+{ 0x056F, 0x053F, 0x056F },
+{ 0x0570, 0x0540, 0x0570 },
+{ 0x0571, 0x0541, 0x0571 },
+{ 0x0572, 0x0542, 0x0572 },
+{ 0x0573, 0x0543, 0x0573 },
+{ 0x0574, 0x0544, 0x0574 },
+{ 0x0575, 0x0545, 0x0575 },
+{ 0x0576, 0x0546, 0x0576 },
+{ 0x0577, 0x0547, 0x0577 },
+{ 0x0578, 0x0548, 0x0578 },
+{ 0x0579, 0x0549, 0x0579 },
+{ 0x057A, 0x054A, 0x057A },
+{ 0x057B, 0x054B, 0x057B },
+{ 0x057C, 0x054C, 0x057C },
+{ 0x057D, 0x054D, 0x057D },
+{ 0x057E, 0x054E, 0x057E },
+{ 0x057F, 0x054F, 0x057F },
+{ 0x0580, 0x0550, 0x0580 },
+{ 0x0581, 0x0551, 0x0581 },
+{ 0x0582, 0x0552, 0x0582 },
+{ 0x0583, 0x0553, 0x0583 },
+{ 0x0584, 0x0554, 0x0584 },
+{ 0x0585, 0x0555, 0x0585 },
+{ 0x0586, 0x0556, 0x0586 },
+{ 0x0587, 0x0587, 0x0587 },
+{ 0x0591, 0x0591, 0x0591 },
+{ 0x0592, 0x0592, 0x0592 },
+{ 0x0593, 0x0593, 0x0593 },
+{ 0x0594, 0x0594, 0x0594 },
+{ 0x0595, 0x0595, 0x0595 },
+{ 0x0596, 0x0596, 0x0596 },
+{ 0x0597, 0x0597, 0x0597 },
+{ 0x0598, 0x0598, 0x0598 },
+{ 0x0599, 0x0599, 0x0599 },
+{ 0x059A, 0x059A, 0x059A },
+{ 0x059B, 0x059B, 0x059B },
+{ 0x059C, 0x059C, 0x059C },
+{ 0x059D, 0x059D, 0x059D },
+{ 0x059E, 0x059E, 0x059E },
+{ 0x059F, 0x059F, 0x059F },
+{ 0x05A0, 0x05A0, 0x05A0 },
+{ 0x05A1, 0x05A1, 0x05A1 },
+{ 0x05A2, 0x05A2, 0x05A2 },
+{ 0x05A3, 0x05A3, 0x05A3 },
+{ 0x05A4, 0x05A4, 0x05A4 },
+{ 0x05A5, 0x05A5, 0x05A5 },
+{ 0x05A6, 0x05A6, 0x05A6 },
+{ 0x05A7, 0x05A7, 0x05A7 },
+{ 0x05A8, 0x05A8, 0x05A8 },
+{ 0x05A9, 0x05A9, 0x05A9 },
+{ 0x05AA, 0x05AA, 0x05AA },
+{ 0x05AB, 0x05AB, 0x05AB },
+{ 0x05AC, 0x05AC, 0x05AC },
+{ 0x05AD, 0x05AD, 0x05AD },
+{ 0x05AE, 0x05AE, 0x05AE },
+{ 0x05AF, 0x05AF, 0x05AF },
+{ 0x05B0, 0x05B0, 0x05B0 },
+{ 0x05B1, 0x05B1, 0x05B1 },
+{ 0x05B2, 0x05B2, 0x05B2 },
+{ 0x05B3, 0x05B3, 0x05B3 },
+{ 0x05B4, 0x05B4, 0x05B4 },
+{ 0x05B5, 0x05B5, 0x05B5 },
+{ 0x05B6, 0x05B6, 0x05B6 },
+{ 0x05B7, 0x05B7, 0x05B7 },
+{ 0x05B8, 0x05B8, 0x05B8 },
+{ 0x05B9, 0x05B9, 0x05B9 },
+{ 0x05BB, 0x05BB, 0x05BB },
+{ 0x05BC, 0x05BC, 0x05BC },
+{ 0x05BD, 0x05BD, 0x05BD },
+{ 0x05BF, 0x05BF, 0x05BF },
+{ 0x05C1, 0x05C1, 0x05C1 },
+{ 0x05C2, 0x05C2, 0x05C2 },
+{ 0x05C4, 0x05C4, 0x05C4 },
+{ 0x05C5, 0x05C5, 0x05C5 },
+{ 0x05C7, 0x05C7, 0x05C7 },
+{ 0x05D0, 0x05D0, 0x05D0 },
+{ 0x05D1, 0x05D1, 0x05D1 },
+{ 0x05D2, 0x05D2, 0x05D2 },
+{ 0x05D3, 0x05D3, 0x05D3 },
+{ 0x05D4, 0x05D4, 0x05D4 },
+{ 0x05D5, 0x05D5, 0x05D5 },
+{ 0x05D6, 0x05D6, 0x05D6 },
+{ 0x05D7, 0x05D7, 0x05D7 },
+{ 0x05D8, 0x05D8, 0x05D8 },
+{ 0x05D9, 0x05D9, 0x05D9 },
+{ 0x05DA, 0x05DA, 0x05DA },
+{ 0x05DB, 0x05DB, 0x05DB },
+{ 0x05DC, 0x05DC, 0x05DC },
+{ 0x05DD, 0x05DD, 0x05DD },
+{ 0x05DE, 0x05DE, 0x05DE },
+{ 0x05DF, 0x05DF, 0x05DF },
+{ 0x05E0, 0x05E0, 0x05E0 },
+{ 0x05E1, 0x05E1, 0x05E1 },
+{ 0x05E2, 0x05E2, 0x05E2 },
+{ 0x05E3, 0x05E3, 0x05E3 },
+{ 0x05E4, 0x05E4, 0x05E4 },
+{ 0x05E5, 0x05E5, 0x05E5 },
+{ 0x05E6, 0x05E6, 0x05E6 },
+{ 0x05E7, 0x05E7, 0x05E7 },
+{ 0x05E8, 0x05E8, 0x05E8 },
+{ 0x05E9, 0x05E9, 0x05E9 },
+{ 0x05EA, 0x05EA, 0x05EA },
+{ 0x05F0, 0x05F0, 0x05F0 },
+{ 0x05F1, 0x05F1, 0x05F1 },
+{ 0x05F2, 0x05F2, 0x05F2 },
+{ 0x0610, 0x0610, 0x0610 },
+{ 0x0611, 0x0611, 0x0611 },
+{ 0x0612, 0x0612, 0x0612 },
+{ 0x0613, 0x0613, 0x0613 },
+{ 0x0614, 0x0614, 0x0614 },
+{ 0x0615, 0x0615, 0x0615 },
+{ 0x0621, 0x0621, 0x0621 },
+{ 0x0622, 0x0622, 0x0622 },
+{ 0x0623, 0x0623, 0x0623 },
+{ 0x0624, 0x0624, 0x0624 },
+{ 0x0625, 0x0625, 0x0625 },
+{ 0x0626, 0x0626, 0x0626 },
+{ 0x0627, 0x0627, 0x0627 },
+{ 0x0628, 0x0628, 0x0628 },
+{ 0x0629, 0x0629, 0x0629 },
+{ 0x062A, 0x062A, 0x062A },
+{ 0x062B, 0x062B, 0x062B },
+{ 0x062C, 0x062C, 0x062C },
+{ 0x062D, 0x062D, 0x062D },
+{ 0x062E, 0x062E, 0x062E },
+{ 0x062F, 0x062F, 0x062F },
+{ 0x0630, 0x0630, 0x0630 },
+{ 0x0631, 0x0631, 0x0631 },
+{ 0x0632, 0x0632, 0x0632 },
+{ 0x0633, 0x0633, 0x0633 },
+{ 0x0634, 0x0634, 0x0634 },
+{ 0x0635, 0x0635, 0x0635 },
+{ 0x0636, 0x0636, 0x0636 },
+{ 0x0637, 0x0637, 0x0637 },
+{ 0x0638, 0x0638, 0x0638 },
+{ 0x0639, 0x0639, 0x0639 },
+{ 0x063A, 0x063A, 0x063A },
+{ 0x0640, 0x0640, 0x0640 },
+{ 0x0641, 0x0641, 0x0641 },
+{ 0x0642, 0x0642, 0x0642 },
+{ 0x0643, 0x0643, 0x0643 },
+{ 0x0644, 0x0644, 0x0644 },
+{ 0x0645, 0x0645, 0x0645 },
+{ 0x0646, 0x0646, 0x0646 },
+{ 0x0647, 0x0647, 0x0647 },
+{ 0x0648, 0x0648, 0x0648 },
+{ 0x0649, 0x0649, 0x0649 },
+{ 0x064A, 0x064A, 0x064A },
+{ 0x064B, 0x064B, 0x064B },
+{ 0x064C, 0x064C, 0x064C },
+{ 0x064D, 0x064D, 0x064D },
+{ 0x064E, 0x064E, 0x064E },
+{ 0x064F, 0x064F, 0x064F },
+{ 0x0650, 0x0650, 0x0650 },
+{ 0x0651, 0x0651, 0x0651 },
+{ 0x0652, 0x0652, 0x0652 },
+{ 0x0653, 0x0653, 0x0653 },
+{ 0x0654, 0x0654, 0x0654 },
+{ 0x0655, 0x0655, 0x0655 },
+{ 0x0656, 0x0656, 0x0656 },
+{ 0x0657, 0x0657, 0x0657 },
+{ 0x0658, 0x0658, 0x0658 },
+{ 0x0659, 0x0659, 0x0659 },
+{ 0x065A, 0x065A, 0x065A },
+{ 0x065B, 0x065B, 0x065B },
+{ 0x065C, 0x065C, 0x065C },
+{ 0x065D, 0x065D, 0x065D },
+{ 0x065E, 0x065E, 0x065E },
+{ 0x066E, 0x066E, 0x066E },
+{ 0x066F, 0x066F, 0x066F },
+{ 0x0670, 0x0670, 0x0670 },
+{ 0x0671, 0x0671, 0x0671 },
+{ 0x0672, 0x0672, 0x0672 },
+{ 0x0673, 0x0673, 0x0673 },
+{ 0x0674, 0x0674, 0x0674 },
+{ 0x0675, 0x0675, 0x0675 },
+{ 0x0676, 0x0676, 0x0676 },
+{ 0x0677, 0x0677, 0x0677 },
+{ 0x0678, 0x0678, 0x0678 },
+{ 0x0679, 0x0679, 0x0679 },
+{ 0x067A, 0x067A, 0x067A },
+{ 0x067B, 0x067B, 0x067B },
+{ 0x067C, 0x067C, 0x067C },
+{ 0x067D, 0x067D, 0x067D },
+{ 0x067E, 0x067E, 0x067E },
+{ 0x067F, 0x067F, 0x067F },
+{ 0x0680, 0x0680, 0x0680 },
+{ 0x0681, 0x0681, 0x0681 },
+{ 0x0682, 0x0682, 0x0682 },
+{ 0x0683, 0x0683, 0x0683 },
+{ 0x0684, 0x0684, 0x0684 },
+{ 0x0685, 0x0685, 0x0685 },
+{ 0x0686, 0x0686, 0x0686 },
+{ 0x0687, 0x0687, 0x0687 },
+{ 0x0688, 0x0688, 0x0688 },
+{ 0x0689, 0x0689, 0x0689 },
+{ 0x068A, 0x068A, 0x068A },
+{ 0x068B, 0x068B, 0x068B },
+{ 0x068C, 0x068C, 0x068C },
+{ 0x068D, 0x068D, 0x068D },
+{ 0x068E, 0x068E, 0x068E },
+{ 0x068F, 0x068F, 0x068F },
+{ 0x0690, 0x0690, 0x0690 },
+{ 0x0691, 0x0691, 0x0691 },
+{ 0x0692, 0x0692, 0x0692 },
+{ 0x0693, 0x0693, 0x0693 },
+{ 0x0694, 0x0694, 0x0694 },
+{ 0x0695, 0x0695, 0x0695 },
+{ 0x0696, 0x0696, 0x0696 },
+{ 0x0697, 0x0697, 0x0697 },
+{ 0x0698, 0x0698, 0x0698 },
+{ 0x0699, 0x0699, 0x0699 },
+{ 0x069A, 0x069A, 0x069A },
+{ 0x069B, 0x069B, 0x069B },
+{ 0x069C, 0x069C, 0x069C },
+{ 0x069D, 0x069D, 0x069D },
+{ 0x069E, 0x069E, 0x069E },
+{ 0x069F, 0x069F, 0x069F },
+{ 0x06A0, 0x06A0, 0x06A0 },
+{ 0x06A1, 0x06A1, 0x06A1 },
+{ 0x06A2, 0x06A2, 0x06A2 },
+{ 0x06A3, 0x06A3, 0x06A3 },
+{ 0x06A4, 0x06A4, 0x06A4 },
+{ 0x06A5, 0x06A5, 0x06A5 },
+{ 0x06A6, 0x06A6, 0x06A6 },
+{ 0x06A7, 0x06A7, 0x06A7 },
+{ 0x06A8, 0x06A8, 0x06A8 },
+{ 0x06A9, 0x06A9, 0x06A9 },
+{ 0x06AA, 0x06AA, 0x06AA },
+{ 0x06AB, 0x06AB, 0x06AB },
+{ 0x06AC, 0x06AC, 0x06AC },
+{ 0x06AD, 0x06AD, 0x06AD },
+{ 0x06AE, 0x06AE, 0x06AE },
+{ 0x06AF, 0x06AF, 0x06AF },
+{ 0x06B0, 0x06B0, 0x06B0 },
+{ 0x06B1, 0x06B1, 0x06B1 },
+{ 0x06B2, 0x06B2, 0x06B2 },
+{ 0x06B3, 0x06B3, 0x06B3 },
+{ 0x06B4, 0x06B4, 0x06B4 },
+{ 0x06B5, 0x06B5, 0x06B5 },
+{ 0x06B6, 0x06B6, 0x06B6 },
+{ 0x06B7, 0x06B7, 0x06B7 },
+{ 0x06B8, 0x06B8, 0x06B8 },
+{ 0x06B9, 0x06B9, 0x06B9 },
+{ 0x06BA, 0x06BA, 0x06BA },
+{ 0x06BB, 0x06BB, 0x06BB },
+{ 0x06BC, 0x06BC, 0x06BC },
+{ 0x06BD, 0x06BD, 0x06BD },
+{ 0x06BE, 0x06BE, 0x06BE },
+{ 0x06BF, 0x06BF, 0x06BF },
+{ 0x06C0, 0x06C0, 0x06C0 },
+{ 0x06C1, 0x06C1, 0x06C1 },
+{ 0x06C2, 0x06C2, 0x06C2 },
+{ 0x06C3, 0x06C3, 0x06C3 },
+{ 0x06C4, 0x06C4, 0x06C4 },
+{ 0x06C5, 0x06C5, 0x06C5 },
+{ 0x06C6, 0x06C6, 0x06C6 },
+{ 0x06C7, 0x06C7, 0x06C7 },
+{ 0x06C8, 0x06C8, 0x06C8 },
+{ 0x06C9, 0x06C9, 0x06C9 },
+{ 0x06CA, 0x06CA, 0x06CA },
+{ 0x06CB, 0x06CB, 0x06CB },
+{ 0x06CC, 0x06CC, 0x06CC },
+{ 0x06CD, 0x06CD, 0x06CD },
+{ 0x06CE, 0x06CE, 0x06CE },
+{ 0x06CF, 0x06CF, 0x06CF },
+{ 0x06D0, 0x06D0, 0x06D0 },
+{ 0x06D1, 0x06D1, 0x06D1 },
+{ 0x06D2, 0x06D2, 0x06D2 },
+{ 0x06D3, 0x06D3, 0x06D3 },
+{ 0x06D5, 0x06D5, 0x06D5 },
+{ 0x06D6, 0x06D6, 0x06D6 },
+{ 0x06D7, 0x06D7, 0x06D7 },
+{ 0x06D8, 0x06D8, 0x06D8 },
+{ 0x06D9, 0x06D9, 0x06D9 },
+{ 0x06DA, 0x06DA, 0x06DA },
+{ 0x06DB, 0x06DB, 0x06DB },
+{ 0x06DC, 0x06DC, 0x06DC },
+{ 0x06DF, 0x06DF, 0x06DF },
+{ 0x06E0, 0x06E0, 0x06E0 },
+{ 0x06E1, 0x06E1, 0x06E1 },
+{ 0x06E2, 0x06E2, 0x06E2 },
+{ 0x06E3, 0x06E3, 0x06E3 },
+{ 0x06E4, 0x06E4, 0x06E4 },
+{ 0x06E5, 0x06E5, 0x06E5 },
+{ 0x06E6, 0x06E6, 0x06E6 },
+{ 0x06E7, 0x06E7, 0x06E7 },
+{ 0x06E8, 0x06E8, 0x06E8 },
+{ 0x06EA, 0x06EA, 0x06EA },
+{ 0x06EB, 0x06EB, 0x06EB },
+{ 0x06EC, 0x06EC, 0x06EC },
+{ 0x06ED, 0x06ED, 0x06ED },
+{ 0x06EE, 0x06EE, 0x06EE },
+{ 0x06EF, 0x06EF, 0x06EF },
+{ 0x06FA, 0x06FA, 0x06FA },
+{ 0x06FB, 0x06FB, 0x06FB },
+{ 0x06FC, 0x06FC, 0x06FC },
+{ 0x06FF, 0x06FF, 0x06FF },
+{ 0x0710, 0x0710, 0x0710 },
+{ 0x0711, 0x0711, 0x0711 },
+{ 0x0712, 0x0712, 0x0712 },
+{ 0x0713, 0x0713, 0x0713 },
+{ 0x0714, 0x0714, 0x0714 },
+{ 0x0715, 0x0715, 0x0715 },
+{ 0x0716, 0x0716, 0x0716 },
+{ 0x0717, 0x0717, 0x0717 },
+{ 0x0718, 0x0718, 0x0718 },
+{ 0x0719, 0x0719, 0x0719 },
+{ 0x071A, 0x071A, 0x071A },
+{ 0x071B, 0x071B, 0x071B },
+{ 0x071C, 0x071C, 0x071C },
+{ 0x071D, 0x071D, 0x071D },
+{ 0x071E, 0x071E, 0x071E },
+{ 0x071F, 0x071F, 0x071F },
+{ 0x0720, 0x0720, 0x0720 },
+{ 0x0721, 0x0721, 0x0721 },
+{ 0x0722, 0x0722, 0x0722 },
+{ 0x0723, 0x0723, 0x0723 },
+{ 0x0724, 0x0724, 0x0724 },
+{ 0x0725, 0x0725, 0x0725 },
+{ 0x0726, 0x0726, 0x0726 },
+{ 0x0727, 0x0727, 0x0727 },
+{ 0x0728, 0x0728, 0x0728 },
+{ 0x0729, 0x0729, 0x0729 },
+{ 0x072A, 0x072A, 0x072A },
+{ 0x072B, 0x072B, 0x072B },
+{ 0x072C, 0x072C, 0x072C },
+{ 0x072D, 0x072D, 0x072D },
+{ 0x072E, 0x072E, 0x072E },
+{ 0x072F, 0x072F, 0x072F },
+{ 0x0730, 0x0730, 0x0730 },
+{ 0x0731, 0x0731, 0x0731 },
+{ 0x0732, 0x0732, 0x0732 },
+{ 0x0733, 0x0733, 0x0733 },
+{ 0x0734, 0x0734, 0x0734 },
+{ 0x0735, 0x0735, 0x0735 },
+{ 0x0736, 0x0736, 0x0736 },
+{ 0x0737, 0x0737, 0x0737 },
+{ 0x0738, 0x0738, 0x0738 },
+{ 0x0739, 0x0739, 0x0739 },
+{ 0x073A, 0x073A, 0x073A },
+{ 0x073B, 0x073B, 0x073B },
+{ 0x073C, 0x073C, 0x073C },
+{ 0x073D, 0x073D, 0x073D },
+{ 0x073E, 0x073E, 0x073E },
+{ 0x073F, 0x073F, 0x073F },
+{ 0x0740, 0x0740, 0x0740 },
+{ 0x0741, 0x0741, 0x0741 },
+{ 0x0742, 0x0742, 0x0742 },
+{ 0x0743, 0x0743, 0x0743 },
+{ 0x0744, 0x0744, 0x0744 },
+{ 0x0745, 0x0745, 0x0745 },
+{ 0x0746, 0x0746, 0x0746 },
+{ 0x0747, 0x0747, 0x0747 },
+{ 0x0748, 0x0748, 0x0748 },
+{ 0x0749, 0x0749, 0x0749 },
+{ 0x074A, 0x074A, 0x074A },
+{ 0x074D, 0x074D, 0x074D },
+{ 0x074E, 0x074E, 0x074E },
+{ 0x074F, 0x074F, 0x074F },
+{ 0x0750, 0x0750, 0x0750 },
+{ 0x0751, 0x0751, 0x0751 },
+{ 0x0752, 0x0752, 0x0752 },
+{ 0x0753, 0x0753, 0x0753 },
+{ 0x0754, 0x0754, 0x0754 },
+{ 0x0755, 0x0755, 0x0755 },
+{ 0x0756, 0x0756, 0x0756 },
+{ 0x0757, 0x0757, 0x0757 },
+{ 0x0758, 0x0758, 0x0758 },
+{ 0x0759, 0x0759, 0x0759 },
+{ 0x075A, 0x075A, 0x075A },
+{ 0x075B, 0x075B, 0x075B },
+{ 0x075C, 0x075C, 0x075C },
+{ 0x075D, 0x075D, 0x075D },
+{ 0x075E, 0x075E, 0x075E },
+{ 0x075F, 0x075F, 0x075F },
+{ 0x0760, 0x0760, 0x0760 },
+{ 0x0761, 0x0761, 0x0761 },
+{ 0x0762, 0x0762, 0x0762 },
+{ 0x0763, 0x0763, 0x0763 },
+{ 0x0764, 0x0764, 0x0764 },
+{ 0x0765, 0x0765, 0x0765 },
+{ 0x0766, 0x0766, 0x0766 },
+{ 0x0767, 0x0767, 0x0767 },
+{ 0x0768, 0x0768, 0x0768 },
+{ 0x0769, 0x0769, 0x0769 },
+{ 0x076A, 0x076A, 0x076A },
+{ 0x076B, 0x076B, 0x076B },
+{ 0x076C, 0x076C, 0x076C },
+{ 0x076D, 0x076D, 0x076D },
+{ 0x0780, 0x0780, 0x0780 },
+{ 0x0781, 0x0781, 0x0781 },
+{ 0x0782, 0x0782, 0x0782 },
+{ 0x0783, 0x0783, 0x0783 },
+{ 0x0784, 0x0784, 0x0784 },
+{ 0x0785, 0x0785, 0x0785 },
+{ 0x0786, 0x0786, 0x0786 },
+{ 0x0787, 0x0787, 0x0787 },
+{ 0x0788, 0x0788, 0x0788 },
+{ 0x0789, 0x0789, 0x0789 },
+{ 0x078A, 0x078A, 0x078A },
+{ 0x078B, 0x078B, 0x078B },
+{ 0x078C, 0x078C, 0x078C },
+{ 0x078D, 0x078D, 0x078D },
+{ 0x078E, 0x078E, 0x078E },
+{ 0x078F, 0x078F, 0x078F },
+{ 0x0790, 0x0790, 0x0790 },
+{ 0x0791, 0x0791, 0x0791 },
+{ 0x0792, 0x0792, 0x0792 },
+{ 0x0793, 0x0793, 0x0793 },
+{ 0x0794, 0x0794, 0x0794 },
+{ 0x0795, 0x0795, 0x0795 },
+{ 0x0796, 0x0796, 0x0796 },
+{ 0x0797, 0x0797, 0x0797 },
+{ 0x0798, 0x0798, 0x0798 },
+{ 0x0799, 0x0799, 0x0799 },
+{ 0x079A, 0x079A, 0x079A },
+{ 0x079B, 0x079B, 0x079B },
+{ 0x079C, 0x079C, 0x079C },
+{ 0x079D, 0x079D, 0x079D },
+{ 0x079E, 0x079E, 0x079E },
+{ 0x079F, 0x079F, 0x079F },
+{ 0x07A0, 0x07A0, 0x07A0 },
+{ 0x07A1, 0x07A1, 0x07A1 },
+{ 0x07A2, 0x07A2, 0x07A2 },
+{ 0x07A3, 0x07A3, 0x07A3 },
+{ 0x07A4, 0x07A4, 0x07A4 },
+{ 0x07A5, 0x07A5, 0x07A5 },
+{ 0x07A6, 0x07A6, 0x07A6 },
+{ 0x07A7, 0x07A7, 0x07A7 },
+{ 0x07A8, 0x07A8, 0x07A8 },
+{ 0x07A9, 0x07A9, 0x07A9 },
+{ 0x07AA, 0x07AA, 0x07AA },
+{ 0x07AB, 0x07AB, 0x07AB },
+{ 0x07AC, 0x07AC, 0x07AC },
+{ 0x07AD, 0x07AD, 0x07AD },
+{ 0x07AE, 0x07AE, 0x07AE },
+{ 0x07AF, 0x07AF, 0x07AF },
+{ 0x07B0, 0x07B0, 0x07B0 },
+{ 0x07B1, 0x07B1, 0x07B1 },
+{ 0x0901, 0x0901, 0x0901 },
+{ 0x0902, 0x0902, 0x0902 },
+{ 0x0904, 0x0904, 0x0904 },
+{ 0x0905, 0x0905, 0x0905 },
+{ 0x0906, 0x0906, 0x0906 },
+{ 0x0907, 0x0907, 0x0907 },
+{ 0x0908, 0x0908, 0x0908 },
+{ 0x0909, 0x0909, 0x0909 },
+{ 0x090A, 0x090A, 0x090A },
+{ 0x090B, 0x090B, 0x090B },
+{ 0x090C, 0x090C, 0x090C },
+{ 0x090D, 0x090D, 0x090D },
+{ 0x090E, 0x090E, 0x090E },
+{ 0x090F, 0x090F, 0x090F },
+{ 0x0910, 0x0910, 0x0910 },
+{ 0x0911, 0x0911, 0x0911 },
+{ 0x0912, 0x0912, 0x0912 },
+{ 0x0913, 0x0913, 0x0913 },
+{ 0x0914, 0x0914, 0x0914 },
+{ 0x0915, 0x0915, 0x0915 },
+{ 0x0916, 0x0916, 0x0916 },
+{ 0x0917, 0x0917, 0x0917 },
+{ 0x0918, 0x0918, 0x0918 },
+{ 0x0919, 0x0919, 0x0919 },
+{ 0x091A, 0x091A, 0x091A },
+{ 0x091B, 0x091B, 0x091B },
+{ 0x091C, 0x091C, 0x091C },
+{ 0x091D, 0x091D, 0x091D },
+{ 0x091E, 0x091E, 0x091E },
+{ 0x091F, 0x091F, 0x091F },
+{ 0x0920, 0x0920, 0x0920 },
+{ 0x0921, 0x0921, 0x0921 },
+{ 0x0922, 0x0922, 0x0922 },
+{ 0x0923, 0x0923, 0x0923 },
+{ 0x0924, 0x0924, 0x0924 },
+{ 0x0925, 0x0925, 0x0925 },
+{ 0x0926, 0x0926, 0x0926 },
+{ 0x0927, 0x0927, 0x0927 },
+{ 0x0928, 0x0928, 0x0928 },
+{ 0x0929, 0x0929, 0x0929 },
+{ 0x092A, 0x092A, 0x092A },
+{ 0x092B, 0x092B, 0x092B },
+{ 0x092C, 0x092C, 0x092C },
+{ 0x092D, 0x092D, 0x092D },
+{ 0x092E, 0x092E, 0x092E },
+{ 0x092F, 0x092F, 0x092F },
+{ 0x0930, 0x0930, 0x0930 },
+{ 0x0931, 0x0931, 0x0931 },
+{ 0x0932, 0x0932, 0x0932 },
+{ 0x0933, 0x0933, 0x0933 },
+{ 0x0934, 0x0934, 0x0934 },
+{ 0x0935, 0x0935, 0x0935 },
+{ 0x0936, 0x0936, 0x0936 },
+{ 0x0937, 0x0937, 0x0937 },
+{ 0x0938, 0x0938, 0x0938 },
+{ 0x0939, 0x0939, 0x0939 },
+{ 0x093C, 0x093C, 0x093C },
+{ 0x093D, 0x093D, 0x093D },
+{ 0x0941, 0x0941, 0x0941 },
+{ 0x0942, 0x0942, 0x0942 },
+{ 0x0943, 0x0943, 0x0943 },
+{ 0x0944, 0x0944, 0x0944 },
+{ 0x0945, 0x0945, 0x0945 },
+{ 0x0946, 0x0946, 0x0946 },
+{ 0x0947, 0x0947, 0x0947 },
+{ 0x0948, 0x0948, 0x0948 },
+{ 0x094D, 0x094D, 0x094D },
+{ 0x0950, 0x0950, 0x0950 },
+{ 0x0951, 0x0951, 0x0951 },
+{ 0x0952, 0x0952, 0x0952 },
+{ 0x0953, 0x0953, 0x0953 },
+{ 0x0954, 0x0954, 0x0954 },
+{ 0x0958, 0x0958, 0x0958 },
+{ 0x0959, 0x0959, 0x0959 },
+{ 0x095A, 0x095A, 0x095A },
+{ 0x095B, 0x095B, 0x095B },
+{ 0x095C, 0x095C, 0x095C },
+{ 0x095D, 0x095D, 0x095D },
+{ 0x095E, 0x095E, 0x095E },
+{ 0x095F, 0x095F, 0x095F },
+{ 0x0960, 0x0960, 0x0960 },
+{ 0x0961, 0x0961, 0x0961 },
+{ 0x0962, 0x0962, 0x0962 },
+{ 0x0963, 0x0963, 0x0963 },
+{ 0x097D, 0x097D, 0x097D },
+{ 0x0981, 0x0981, 0x0981 },
+{ 0x0985, 0x0985, 0x0985 },
+{ 0x0986, 0x0986, 0x0986 },
+{ 0x0987, 0x0987, 0x0987 },
+{ 0x0988, 0x0988, 0x0988 },
+{ 0x0989, 0x0989, 0x0989 },
+{ 0x098A, 0x098A, 0x098A },
+{ 0x098B, 0x098B, 0x098B },
+{ 0x098C, 0x098C, 0x098C },
+{ 0x098F, 0x098F, 0x098F },
+{ 0x0990, 0x0990, 0x0990 },
+{ 0x0993, 0x0993, 0x0993 },
+{ 0x0994, 0x0994, 0x0994 },
+{ 0x0995, 0x0995, 0x0995 },
+{ 0x0996, 0x0996, 0x0996 },
+{ 0x0997, 0x0997, 0x0997 },
+{ 0x0998, 0x0998, 0x0998 },
+{ 0x0999, 0x0999, 0x0999 },
+{ 0x099A, 0x099A, 0x099A },
+{ 0x099B, 0x099B, 0x099B },
+{ 0x099C, 0x099C, 0x099C },
+{ 0x099D, 0x099D, 0x099D },
+{ 0x099E, 0x099E, 0x099E },
+{ 0x099F, 0x099F, 0x099F },
+{ 0x09A0, 0x09A0, 0x09A0 },
+{ 0x09A1, 0x09A1, 0x09A1 },
+{ 0x09A2, 0x09A2, 0x09A2 },
+{ 0x09A3, 0x09A3, 0x09A3 },
+{ 0x09A4, 0x09A4, 0x09A4 },
+{ 0x09A5, 0x09A5, 0x09A5 },
+{ 0x09A6, 0x09A6, 0x09A6 },
+{ 0x09A7, 0x09A7, 0x09A7 },
+{ 0x09A8, 0x09A8, 0x09A8 },
+{ 0x09AA, 0x09AA, 0x09AA },
+{ 0x09AB, 0x09AB, 0x09AB },
+{ 0x09AC, 0x09AC, 0x09AC },
+{ 0x09AD, 0x09AD, 0x09AD },
+{ 0x09AE, 0x09AE, 0x09AE },
+{ 0x09AF, 0x09AF, 0x09AF },
+{ 0x09B0, 0x09B0, 0x09B0 },
+{ 0x09B2, 0x09B2, 0x09B2 },
+{ 0x09B6, 0x09B6, 0x09B6 },
+{ 0x09B7, 0x09B7, 0x09B7 },
+{ 0x09B8, 0x09B8, 0x09B8 },
+{ 0x09B9, 0x09B9, 0x09B9 },
+{ 0x09BC, 0x09BC, 0x09BC },
+{ 0x09BD, 0x09BD, 0x09BD },
+{ 0x09C1, 0x09C1, 0x09C1 },
+{ 0x09C2, 0x09C2, 0x09C2 },
+{ 0x09C3, 0x09C3, 0x09C3 },
+{ 0x09C4, 0x09C4, 0x09C4 },
+{ 0x09CD, 0x09CD, 0x09CD },
+{ 0x09CE, 0x09CE, 0x09CE },
+{ 0x09DC, 0x09DC, 0x09DC },
+{ 0x09DD, 0x09DD, 0x09DD },
+{ 0x09DF, 0x09DF, 0x09DF },
+{ 0x09E0, 0x09E0, 0x09E0 },
+{ 0x09E1, 0x09E1, 0x09E1 },
+{ 0x09E2, 0x09E2, 0x09E2 },
+{ 0x09E3, 0x09E3, 0x09E3 },
+{ 0x09F0, 0x09F0, 0x09F0 },
+{ 0x09F1, 0x09F1, 0x09F1 },
+{ 0x0A01, 0x0A01, 0x0A01 },
+{ 0x0A02, 0x0A02, 0x0A02 },
+{ 0x0A05, 0x0A05, 0x0A05 },
+{ 0x0A06, 0x0A06, 0x0A06 },
+{ 0x0A07, 0x0A07, 0x0A07 },
+{ 0x0A08, 0x0A08, 0x0A08 },
+{ 0x0A09, 0x0A09, 0x0A09 },
+{ 0x0A0A, 0x0A0A, 0x0A0A },
+{ 0x0A0F, 0x0A0F, 0x0A0F },
+{ 0x0A10, 0x0A10, 0x0A10 },
+{ 0x0A13, 0x0A13, 0x0A13 },
+{ 0x0A14, 0x0A14, 0x0A14 },
+{ 0x0A15, 0x0A15, 0x0A15 },
+{ 0x0A16, 0x0A16, 0x0A16 },
+{ 0x0A17, 0x0A17, 0x0A17 },
+{ 0x0A18, 0x0A18, 0x0A18 },
+{ 0x0A19, 0x0A19, 0x0A19 },
+{ 0x0A1A, 0x0A1A, 0x0A1A },
+{ 0x0A1B, 0x0A1B, 0x0A1B },
+{ 0x0A1C, 0x0A1C, 0x0A1C },
+{ 0x0A1D, 0x0A1D, 0x0A1D },
+{ 0x0A1E, 0x0A1E, 0x0A1E },
+{ 0x0A1F, 0x0A1F, 0x0A1F },
+{ 0x0A20, 0x0A20, 0x0A20 },
+{ 0x0A21, 0x0A21, 0x0A21 },
+{ 0x0A22, 0x0A22, 0x0A22 },
+{ 0x0A23, 0x0A23, 0x0A23 },
+{ 0x0A24, 0x0A24, 0x0A24 },
+{ 0x0A25, 0x0A25, 0x0A25 },
+{ 0x0A26, 0x0A26, 0x0A26 },
+{ 0x0A27, 0x0A27, 0x0A27 },
+{ 0x0A28, 0x0A28, 0x0A28 },
+{ 0x0A2A, 0x0A2A, 0x0A2A },
+{ 0x0A2B, 0x0A2B, 0x0A2B },
+{ 0x0A2C, 0x0A2C, 0x0A2C },
+{ 0x0A2D, 0x0A2D, 0x0A2D },
+{ 0x0A2E, 0x0A2E, 0x0A2E },
+{ 0x0A2F, 0x0A2F, 0x0A2F },
+{ 0x0A30, 0x0A30, 0x0A30 },
+{ 0x0A32, 0x0A32, 0x0A32 },
+{ 0x0A33, 0x0A33, 0x0A33 },
+{ 0x0A35, 0x0A35, 0x0A35 },
+{ 0x0A36, 0x0A36, 0x0A36 },
+{ 0x0A38, 0x0A38, 0x0A38 },
+{ 0x0A39, 0x0A39, 0x0A39 },
+{ 0x0A3C, 0x0A3C, 0x0A3C },
+{ 0x0A41, 0x0A41, 0x0A41 },
+{ 0x0A42, 0x0A42, 0x0A42 },
+{ 0x0A47, 0x0A47, 0x0A47 },
+{ 0x0A48, 0x0A48, 0x0A48 },
+{ 0x0A4B, 0x0A4B, 0x0A4B },
+{ 0x0A4C, 0x0A4C, 0x0A4C },
+{ 0x0A4D, 0x0A4D, 0x0A4D },
+{ 0x0A59, 0x0A59, 0x0A59 },
+{ 0x0A5A, 0x0A5A, 0x0A5A },
+{ 0x0A5B, 0x0A5B, 0x0A5B },
+{ 0x0A5C, 0x0A5C, 0x0A5C },
+{ 0x0A5E, 0x0A5E, 0x0A5E },
+{ 0x0A70, 0x0A70, 0x0A70 },
+{ 0x0A71, 0x0A71, 0x0A71 },
+{ 0x0A72, 0x0A72, 0x0A72 },
+{ 0x0A73, 0x0A73, 0x0A73 },
+{ 0x0A74, 0x0A74, 0x0A74 },
+{ 0x0A81, 0x0A81, 0x0A81 },
+{ 0x0A82, 0x0A82, 0x0A82 },
+{ 0x0A85, 0x0A85, 0x0A85 },
+{ 0x0A86, 0x0A86, 0x0A86 },
+{ 0x0A87, 0x0A87, 0x0A87 },
+{ 0x0A88, 0x0A88, 0x0A88 },
+{ 0x0A89, 0x0A89, 0x0A89 },
+{ 0x0A8A, 0x0A8A, 0x0A8A },
+{ 0x0A8B, 0x0A8B, 0x0A8B },
+{ 0x0A8C, 0x0A8C, 0x0A8C },
+{ 0x0A8D, 0x0A8D, 0x0A8D },
+{ 0x0A8F, 0x0A8F, 0x0A8F },
+{ 0x0A90, 0x0A90, 0x0A90 },
+{ 0x0A91, 0x0A91, 0x0A91 },
+{ 0x0A93, 0x0A93, 0x0A93 },
+{ 0x0A94, 0x0A94, 0x0A94 },
+{ 0x0A95, 0x0A95, 0x0A95 },
+{ 0x0A96, 0x0A96, 0x0A96 },
+{ 0x0A97, 0x0A97, 0x0A97 },
+{ 0x0A98, 0x0A98, 0x0A98 },
+{ 0x0A99, 0x0A99, 0x0A99 },
+{ 0x0A9A, 0x0A9A, 0x0A9A },
+{ 0x0A9B, 0x0A9B, 0x0A9B },
+{ 0x0A9C, 0x0A9C, 0x0A9C },
+{ 0x0A9D, 0x0A9D, 0x0A9D },
+{ 0x0A9E, 0x0A9E, 0x0A9E },
+{ 0x0A9F, 0x0A9F, 0x0A9F },
+{ 0x0AA0, 0x0AA0, 0x0AA0 },
+{ 0x0AA1, 0x0AA1, 0x0AA1 },
+{ 0x0AA2, 0x0AA2, 0x0AA2 },
+{ 0x0AA3, 0x0AA3, 0x0AA3 },
+{ 0x0AA4, 0x0AA4, 0x0AA4 },
+{ 0x0AA5, 0x0AA5, 0x0AA5 },
+{ 0x0AA6, 0x0AA6, 0x0AA6 },
+{ 0x0AA7, 0x0AA7, 0x0AA7 },
+{ 0x0AA8, 0x0AA8, 0x0AA8 },
+{ 0x0AAA, 0x0AAA, 0x0AAA },
+{ 0x0AAB, 0x0AAB, 0x0AAB },
+{ 0x0AAC, 0x0AAC, 0x0AAC },
+{ 0x0AAD, 0x0AAD, 0x0AAD },
+{ 0x0AAE, 0x0AAE, 0x0AAE },
+{ 0x0AAF, 0x0AAF, 0x0AAF },
+{ 0x0AB0, 0x0AB0, 0x0AB0 },
+{ 0x0AB2, 0x0AB2, 0x0AB2 },
+{ 0x0AB3, 0x0AB3, 0x0AB3 },
+{ 0x0AB5, 0x0AB5, 0x0AB5 },
+{ 0x0AB6, 0x0AB6, 0x0AB6 },
+{ 0x0AB7, 0x0AB7, 0x0AB7 },
+{ 0x0AB8, 0x0AB8, 0x0AB8 },
+{ 0x0AB9, 0x0AB9, 0x0AB9 },
+{ 0x0ABC, 0x0ABC, 0x0ABC },
+{ 0x0ABD, 0x0ABD, 0x0ABD },
+{ 0x0AC1, 0x0AC1, 0x0AC1 },
+{ 0x0AC2, 0x0AC2, 0x0AC2 },
+{ 0x0AC3, 0x0AC3, 0x0AC3 },
+{ 0x0AC4, 0x0AC4, 0x0AC4 },
+{ 0x0AC5, 0x0AC5, 0x0AC5 },
+{ 0x0AC7, 0x0AC7, 0x0AC7 },
+{ 0x0AC8, 0x0AC8, 0x0AC8 },
+{ 0x0ACD, 0x0ACD, 0x0ACD },
+{ 0x0AD0, 0x0AD0, 0x0AD0 },
+{ 0x0AE0, 0x0AE0, 0x0AE0 },
+{ 0x0AE1, 0x0AE1, 0x0AE1 },
+{ 0x0AE2, 0x0AE2, 0x0AE2 },
+{ 0x0AE3, 0x0AE3, 0x0AE3 },
+{ 0x0B01, 0x0B01, 0x0B01 },
+{ 0x0B05, 0x0B05, 0x0B05 },
+{ 0x0B06, 0x0B06, 0x0B06 },
+{ 0x0B07, 0x0B07, 0x0B07 },
+{ 0x0B08, 0x0B08, 0x0B08 },
+{ 0x0B09, 0x0B09, 0x0B09 },
+{ 0x0B0A, 0x0B0A, 0x0B0A },
+{ 0x0B0B, 0x0B0B, 0x0B0B },
+{ 0x0B0C, 0x0B0C, 0x0B0C },
+{ 0x0B0F, 0x0B0F, 0x0B0F },
+{ 0x0B10, 0x0B10, 0x0B10 },
+{ 0x0B13, 0x0B13, 0x0B13 },
+{ 0x0B14, 0x0B14, 0x0B14 },
+{ 0x0B15, 0x0B15, 0x0B15 },
+{ 0x0B16, 0x0B16, 0x0B16 },
+{ 0x0B17, 0x0B17, 0x0B17 },
+{ 0x0B18, 0x0B18, 0x0B18 },
+{ 0x0B19, 0x0B19, 0x0B19 },
+{ 0x0B1A, 0x0B1A, 0x0B1A },
+{ 0x0B1B, 0x0B1B, 0x0B1B },
+{ 0x0B1C, 0x0B1C, 0x0B1C },
+{ 0x0B1D, 0x0B1D, 0x0B1D },
+{ 0x0B1E, 0x0B1E, 0x0B1E },
+{ 0x0B1F, 0x0B1F, 0x0B1F },
+{ 0x0B20, 0x0B20, 0x0B20 },
+{ 0x0B21, 0x0B21, 0x0B21 },
+{ 0x0B22, 0x0B22, 0x0B22 },
+{ 0x0B23, 0x0B23, 0x0B23 },
+{ 0x0B24, 0x0B24, 0x0B24 },
+{ 0x0B25, 0x0B25, 0x0B25 },
+{ 0x0B26, 0x0B26, 0x0B26 },
+{ 0x0B27, 0x0B27, 0x0B27 },
+{ 0x0B28, 0x0B28, 0x0B28 },
+{ 0x0B2A, 0x0B2A, 0x0B2A },
+{ 0x0B2B, 0x0B2B, 0x0B2B },
+{ 0x0B2C, 0x0B2C, 0x0B2C },
+{ 0x0B2D, 0x0B2D, 0x0B2D },
+{ 0x0B2E, 0x0B2E, 0x0B2E },
+{ 0x0B2F, 0x0B2F, 0x0B2F },
+{ 0x0B30, 0x0B30, 0x0B30 },
+{ 0x0B32, 0x0B32, 0x0B32 },
+{ 0x0B33, 0x0B33, 0x0B33 },
+{ 0x0B35, 0x0B35, 0x0B35 },
+{ 0x0B36, 0x0B36, 0x0B36 },
+{ 0x0B37, 0x0B37, 0x0B37 },
+{ 0x0B38, 0x0B38, 0x0B38 },
+{ 0x0B39, 0x0B39, 0x0B39 },
+{ 0x0B3C, 0x0B3C, 0x0B3C },
+{ 0x0B3D, 0x0B3D, 0x0B3D },
+{ 0x0B3F, 0x0B3F, 0x0B3F },
+{ 0x0B41, 0x0B41, 0x0B41 },
+{ 0x0B42, 0x0B42, 0x0B42 },
+{ 0x0B43, 0x0B43, 0x0B43 },
+{ 0x0B4D, 0x0B4D, 0x0B4D },
+{ 0x0B56, 0x0B56, 0x0B56 },
+{ 0x0B5C, 0x0B5C, 0x0B5C },
+{ 0x0B5D, 0x0B5D, 0x0B5D },
+{ 0x0B5F, 0x0B5F, 0x0B5F },
+{ 0x0B60, 0x0B60, 0x0B60 },
+{ 0x0B61, 0x0B61, 0x0B61 },
+{ 0x0B71, 0x0B71, 0x0B71 },
+{ 0x0B82, 0x0B82, 0x0B82 },
+{ 0x0B83, 0x0B83, 0x0B83 },
+{ 0x0B85, 0x0B85, 0x0B85 },
+{ 0x0B86, 0x0B86, 0x0B86 },
+{ 0x0B87, 0x0B87, 0x0B87 },
+{ 0x0B88, 0x0B88, 0x0B88 },
+{ 0x0B89, 0x0B89, 0x0B89 },
+{ 0x0B8A, 0x0B8A, 0x0B8A },
+{ 0x0B8E, 0x0B8E, 0x0B8E },
+{ 0x0B8F, 0x0B8F, 0x0B8F },
+{ 0x0B90, 0x0B90, 0x0B90 },
+{ 0x0B92, 0x0B92, 0x0B92 },
+{ 0x0B93, 0x0B93, 0x0B93 },
+{ 0x0B94, 0x0B94, 0x0B94 },
+{ 0x0B95, 0x0B95, 0x0B95 },
+{ 0x0B99, 0x0B99, 0x0B99 },
+{ 0x0B9A, 0x0B9A, 0x0B9A },
+{ 0x0B9C, 0x0B9C, 0x0B9C },
+{ 0x0B9E, 0x0B9E, 0x0B9E },
+{ 0x0B9F, 0x0B9F, 0x0B9F },
+{ 0x0BA3, 0x0BA3, 0x0BA3 },
+{ 0x0BA4, 0x0BA4, 0x0BA4 },
+{ 0x0BA8, 0x0BA8, 0x0BA8 },
+{ 0x0BA9, 0x0BA9, 0x0BA9 },
+{ 0x0BAA, 0x0BAA, 0x0BAA },
+{ 0x0BAE, 0x0BAE, 0x0BAE },
+{ 0x0BAF, 0x0BAF, 0x0BAF },
+{ 0x0BB0, 0x0BB0, 0x0BB0 },
+{ 0x0BB1, 0x0BB1, 0x0BB1 },
+{ 0x0BB2, 0x0BB2, 0x0BB2 },
+{ 0x0BB3, 0x0BB3, 0x0BB3 },
+{ 0x0BB4, 0x0BB4, 0x0BB4 },
+{ 0x0BB5, 0x0BB5, 0x0BB5 },
+{ 0x0BB6, 0x0BB6, 0x0BB6 },
+{ 0x0BB7, 0x0BB7, 0x0BB7 },
+{ 0x0BB8, 0x0BB8, 0x0BB8 },
+{ 0x0BB9, 0x0BB9, 0x0BB9 },
+{ 0x0BC0, 0x0BC0, 0x0BC0 },
+{ 0x0BCD, 0x0BCD, 0x0BCD },
+{ 0x0C05, 0x0C05, 0x0C05 },
+{ 0x0C06, 0x0C06, 0x0C06 },
+{ 0x0C07, 0x0C07, 0x0C07 },
+{ 0x0C08, 0x0C08, 0x0C08 },
+{ 0x0C09, 0x0C09, 0x0C09 },
+{ 0x0C0A, 0x0C0A, 0x0C0A },
+{ 0x0C0B, 0x0C0B, 0x0C0B },
+{ 0x0C0C, 0x0C0C, 0x0C0C },
+{ 0x0C0E, 0x0C0E, 0x0C0E },
+{ 0x0C0F, 0x0C0F, 0x0C0F },
+{ 0x0C10, 0x0C10, 0x0C10 },
+{ 0x0C12, 0x0C12, 0x0C12 },
+{ 0x0C13, 0x0C13, 0x0C13 },
+{ 0x0C14, 0x0C14, 0x0C14 },
+{ 0x0C15, 0x0C15, 0x0C15 },
+{ 0x0C16, 0x0C16, 0x0C16 },
+{ 0x0C17, 0x0C17, 0x0C17 },
+{ 0x0C18, 0x0C18, 0x0C18 },
+{ 0x0C19, 0x0C19, 0x0C19 },
+{ 0x0C1A, 0x0C1A, 0x0C1A },
+{ 0x0C1B, 0x0C1B, 0x0C1B },
+{ 0x0C1C, 0x0C1C, 0x0C1C },
+{ 0x0C1D, 0x0C1D, 0x0C1D },
+{ 0x0C1E, 0x0C1E, 0x0C1E },
+{ 0x0C1F, 0x0C1F, 0x0C1F },
+{ 0x0C20, 0x0C20, 0x0C20 },
+{ 0x0C21, 0x0C21, 0x0C21 },
+{ 0x0C22, 0x0C22, 0x0C22 },
+{ 0x0C23, 0x0C23, 0x0C23 },
+{ 0x0C24, 0x0C24, 0x0C24 },
+{ 0x0C25, 0x0C25, 0x0C25 },
+{ 0x0C26, 0x0C26, 0x0C26 },
+{ 0x0C27, 0x0C27, 0x0C27 },
+{ 0x0C28, 0x0C28, 0x0C28 },
+{ 0x0C2A, 0x0C2A, 0x0C2A },
+{ 0x0C2B, 0x0C2B, 0x0C2B },
+{ 0x0C2C, 0x0C2C, 0x0C2C },
+{ 0x0C2D, 0x0C2D, 0x0C2D },
+{ 0x0C2E, 0x0C2E, 0x0C2E },
+{ 0x0C2F, 0x0C2F, 0x0C2F },
+{ 0x0C30, 0x0C30, 0x0C30 },
+{ 0x0C31, 0x0C31, 0x0C31 },
+{ 0x0C32, 0x0C32, 0x0C32 },
+{ 0x0C33, 0x0C33, 0x0C33 },
+{ 0x0C35, 0x0C35, 0x0C35 },
+{ 0x0C36, 0x0C36, 0x0C36 },
+{ 0x0C37, 0x0C37, 0x0C37 },
+{ 0x0C38, 0x0C38, 0x0C38 },
+{ 0x0C39, 0x0C39, 0x0C39 },
+{ 0x0C3E, 0x0C3E, 0x0C3E },
+{ 0x0C3F, 0x0C3F, 0x0C3F },
+{ 0x0C40, 0x0C40, 0x0C40 },
+{ 0x0C46, 0x0C46, 0x0C46 },
+{ 0x0C47, 0x0C47, 0x0C47 },
+{ 0x0C48, 0x0C48, 0x0C48 },
+{ 0x0C4A, 0x0C4A, 0x0C4A },
+{ 0x0C4B, 0x0C4B, 0x0C4B },
+{ 0x0C4C, 0x0C4C, 0x0C4C },
+{ 0x0C4D, 0x0C4D, 0x0C4D },
+{ 0x0C55, 0x0C55, 0x0C55 },
+{ 0x0C56, 0x0C56, 0x0C56 },
+{ 0x0C60, 0x0C60, 0x0C60 },
+{ 0x0C61, 0x0C61, 0x0C61 },
+{ 0x0C85, 0x0C85, 0x0C85 },
+{ 0x0C86, 0x0C86, 0x0C86 },
+{ 0x0C87, 0x0C87, 0x0C87 },
+{ 0x0C88, 0x0C88, 0x0C88 },
+{ 0x0C89, 0x0C89, 0x0C89 },
+{ 0x0C8A, 0x0C8A, 0x0C8A },
+{ 0x0C8B, 0x0C8B, 0x0C8B },
+{ 0x0C8C, 0x0C8C, 0x0C8C },
+{ 0x0C8E, 0x0C8E, 0x0C8E },
+{ 0x0C8F, 0x0C8F, 0x0C8F },
+{ 0x0C90, 0x0C90, 0x0C90 },
+{ 0x0C92, 0x0C92, 0x0C92 },
+{ 0x0C93, 0x0C93, 0x0C93 },
+{ 0x0C94, 0x0C94, 0x0C94 },
+{ 0x0C95, 0x0C95, 0x0C95 },
+{ 0x0C96, 0x0C96, 0x0C96 },
+{ 0x0C97, 0x0C97, 0x0C97 },
+{ 0x0C98, 0x0C98, 0x0C98 },
+{ 0x0C99, 0x0C99, 0x0C99 },
+{ 0x0C9A, 0x0C9A, 0x0C9A },
+{ 0x0C9B, 0x0C9B, 0x0C9B },
+{ 0x0C9C, 0x0C9C, 0x0C9C },
+{ 0x0C9D, 0x0C9D, 0x0C9D },
+{ 0x0C9E, 0x0C9E, 0x0C9E },
+{ 0x0C9F, 0x0C9F, 0x0C9F },
+{ 0x0CA0, 0x0CA0, 0x0CA0 },
+{ 0x0CA1, 0x0CA1, 0x0CA1 },
+{ 0x0CA2, 0x0CA2, 0x0CA2 },
+{ 0x0CA3, 0x0CA3, 0x0CA3 },
+{ 0x0CA4, 0x0CA4, 0x0CA4 },
+{ 0x0CA5, 0x0CA5, 0x0CA5 },
+{ 0x0CA6, 0x0CA6, 0x0CA6 },
+{ 0x0CA7, 0x0CA7, 0x0CA7 },
+{ 0x0CA8, 0x0CA8, 0x0CA8 },
+{ 0x0CAA, 0x0CAA, 0x0CAA },
+{ 0x0CAB, 0x0CAB, 0x0CAB },
+{ 0x0CAC, 0x0CAC, 0x0CAC },
+{ 0x0CAD, 0x0CAD, 0x0CAD },
+{ 0x0CAE, 0x0CAE, 0x0CAE },
+{ 0x0CAF, 0x0CAF, 0x0CAF },
+{ 0x0CB0, 0x0CB0, 0x0CB0 },
+{ 0x0CB1, 0x0CB1, 0x0CB1 },
+{ 0x0CB2, 0x0CB2, 0x0CB2 },
+{ 0x0CB3, 0x0CB3, 0x0CB3 },
+{ 0x0CB5, 0x0CB5, 0x0CB5 },
+{ 0x0CB6, 0x0CB6, 0x0CB6 },
+{ 0x0CB7, 0x0CB7, 0x0CB7 },
+{ 0x0CB8, 0x0CB8, 0x0CB8 },
+{ 0x0CB9, 0x0CB9, 0x0CB9 },
+{ 0x0CBC, 0x0CBC, 0x0CBC },
+{ 0x0CBD, 0x0CBD, 0x0CBD },
+{ 0x0CBF, 0x0CBF, 0x0CBF },
+{ 0x0CC6, 0x0CC6, 0x0CC6 },
+{ 0x0CCC, 0x0CCC, 0x0CCC },
+{ 0x0CCD, 0x0CCD, 0x0CCD },
+{ 0x0CDE, 0x0CDE, 0x0CDE },
+{ 0x0CE0, 0x0CE0, 0x0CE0 },
+{ 0x0CE1, 0x0CE1, 0x0CE1 },
+{ 0x0D05, 0x0D05, 0x0D05 },
+{ 0x0D06, 0x0D06, 0x0D06 },
+{ 0x0D07, 0x0D07, 0x0D07 },
+{ 0x0D08, 0x0D08, 0x0D08 },
+{ 0x0D09, 0x0D09, 0x0D09 },
+{ 0x0D0A, 0x0D0A, 0x0D0A },
+{ 0x0D0B, 0x0D0B, 0x0D0B },
+{ 0x0D0C, 0x0D0C, 0x0D0C },
+{ 0x0D0E, 0x0D0E, 0x0D0E },
+{ 0x0D0F, 0x0D0F, 0x0D0F },
+{ 0x0D10, 0x0D10, 0x0D10 },
+{ 0x0D12, 0x0D12, 0x0D12 },
+{ 0x0D13, 0x0D13, 0x0D13 },
+{ 0x0D14, 0x0D14, 0x0D14 },
+{ 0x0D15, 0x0D15, 0x0D15 },
+{ 0x0D16, 0x0D16, 0x0D16 },
+{ 0x0D17, 0x0D17, 0x0D17 },
+{ 0x0D18, 0x0D18, 0x0D18 },
+{ 0x0D19, 0x0D19, 0x0D19 },
+{ 0x0D1A, 0x0D1A, 0x0D1A },
+{ 0x0D1B, 0x0D1B, 0x0D1B },
+{ 0x0D1C, 0x0D1C, 0x0D1C },
+{ 0x0D1D, 0x0D1D, 0x0D1D },
+{ 0x0D1E, 0x0D1E, 0x0D1E },
+{ 0x0D1F, 0x0D1F, 0x0D1F },
+{ 0x0D20, 0x0D20, 0x0D20 },
+{ 0x0D21, 0x0D21, 0x0D21 },
+{ 0x0D22, 0x0D22, 0x0D22 },
+{ 0x0D23, 0x0D23, 0x0D23 },
+{ 0x0D24, 0x0D24, 0x0D24 },
+{ 0x0D25, 0x0D25, 0x0D25 },
+{ 0x0D26, 0x0D26, 0x0D26 },
+{ 0x0D27, 0x0D27, 0x0D27 },
+{ 0x0D28, 0x0D28, 0x0D28 },
+{ 0x0D2A, 0x0D2A, 0x0D2A },
+{ 0x0D2B, 0x0D2B, 0x0D2B },
+{ 0x0D2C, 0x0D2C, 0x0D2C },
+{ 0x0D2D, 0x0D2D, 0x0D2D },
+{ 0x0D2E, 0x0D2E, 0x0D2E },
+{ 0x0D2F, 0x0D2F, 0x0D2F },
+{ 0x0D30, 0x0D30, 0x0D30 },
+{ 0x0D31, 0x0D31, 0x0D31 },
+{ 0x0D32, 0x0D32, 0x0D32 },
+{ 0x0D33, 0x0D33, 0x0D33 },
+{ 0x0D34, 0x0D34, 0x0D34 },
+{ 0x0D35, 0x0D35, 0x0D35 },
+{ 0x0D36, 0x0D36, 0x0D36 },
+{ 0x0D37, 0x0D37, 0x0D37 },
+{ 0x0D38, 0x0D38, 0x0D38 },
+{ 0x0D39, 0x0D39, 0x0D39 },
+{ 0x0D41, 0x0D41, 0x0D41 },
+{ 0x0D42, 0x0D42, 0x0D42 },
+{ 0x0D43, 0x0D43, 0x0D43 },
+{ 0x0D4D, 0x0D4D, 0x0D4D },
+{ 0x0D60, 0x0D60, 0x0D60 },
+{ 0x0D61, 0x0D61, 0x0D61 },
+{ 0x0D85, 0x0D85, 0x0D85 },
+{ 0x0D86, 0x0D86, 0x0D86 },
+{ 0x0D87, 0x0D87, 0x0D87 },
+{ 0x0D88, 0x0D88, 0x0D88 },
+{ 0x0D89, 0x0D89, 0x0D89 },
+{ 0x0D8A, 0x0D8A, 0x0D8A },
+{ 0x0D8B, 0x0D8B, 0x0D8B },
+{ 0x0D8C, 0x0D8C, 0x0D8C },
+{ 0x0D8D, 0x0D8D, 0x0D8D },
+{ 0x0D8E, 0x0D8E, 0x0D8E },
+{ 0x0D8F, 0x0D8F, 0x0D8F },
+{ 0x0D90, 0x0D90, 0x0D90 },
+{ 0x0D91, 0x0D91, 0x0D91 },
+{ 0x0D92, 0x0D92, 0x0D92 },
+{ 0x0D93, 0x0D93, 0x0D93 },
+{ 0x0D94, 0x0D94, 0x0D94 },
+{ 0x0D95, 0x0D95, 0x0D95 },
+{ 0x0D96, 0x0D96, 0x0D96 },
+{ 0x0D9A, 0x0D9A, 0x0D9A },
+{ 0x0D9B, 0x0D9B, 0x0D9B },
+{ 0x0D9C, 0x0D9C, 0x0D9C },
+{ 0x0D9D, 0x0D9D, 0x0D9D },
+{ 0x0D9E, 0x0D9E, 0x0D9E },
+{ 0x0D9F, 0x0D9F, 0x0D9F },
+{ 0x0DA0, 0x0DA0, 0x0DA0 },
+{ 0x0DA1, 0x0DA1, 0x0DA1 },
+{ 0x0DA2, 0x0DA2, 0x0DA2 },
+{ 0x0DA3, 0x0DA3, 0x0DA3 },
+{ 0x0DA4, 0x0DA4, 0x0DA4 },
+{ 0x0DA5, 0x0DA5, 0x0DA5 },
+{ 0x0DA6, 0x0DA6, 0x0DA6 },
+{ 0x0DA7, 0x0DA7, 0x0DA7 },
+{ 0x0DA8, 0x0DA8, 0x0DA8 },
+{ 0x0DA9, 0x0DA9, 0x0DA9 },
+{ 0x0DAA, 0x0DAA, 0x0DAA },
+{ 0x0DAB, 0x0DAB, 0x0DAB },
+{ 0x0DAC, 0x0DAC, 0x0DAC },
+{ 0x0DAD, 0x0DAD, 0x0DAD },
+{ 0x0DAE, 0x0DAE, 0x0DAE },
+{ 0x0DAF, 0x0DAF, 0x0DAF },
+{ 0x0DB0, 0x0DB0, 0x0DB0 },
+{ 0x0DB1, 0x0DB1, 0x0DB1 },
+{ 0x0DB3, 0x0DB3, 0x0DB3 },
+{ 0x0DB4, 0x0DB4, 0x0DB4 },
+{ 0x0DB5, 0x0DB5, 0x0DB5 },
+{ 0x0DB6, 0x0DB6, 0x0DB6 },
+{ 0x0DB7, 0x0DB7, 0x0DB7 },
+{ 0x0DB8, 0x0DB8, 0x0DB8 },
+{ 0x0DB9, 0x0DB9, 0x0DB9 },
+{ 0x0DBA, 0x0DBA, 0x0DBA },
+{ 0x0DBB, 0x0DBB, 0x0DBB },
+{ 0x0DBD, 0x0DBD, 0x0DBD },
+{ 0x0DC0, 0x0DC0, 0x0DC0 },
+{ 0x0DC1, 0x0DC1, 0x0DC1 },
+{ 0x0DC2, 0x0DC2, 0x0DC2 },
+{ 0x0DC3, 0x0DC3, 0x0DC3 },
+{ 0x0DC4, 0x0DC4, 0x0DC4 },
+{ 0x0DC5, 0x0DC5, 0x0DC5 },
+{ 0x0DC6, 0x0DC6, 0x0DC6 },
+{ 0x0DCA, 0x0DCA, 0x0DCA },
+{ 0x0DD2, 0x0DD2, 0x0DD2 },
+{ 0x0DD3, 0x0DD3, 0x0DD3 },
+{ 0x0DD4, 0x0DD4, 0x0DD4 },
+{ 0x0DD6, 0x0DD6, 0x0DD6 },
+{ 0x0E01, 0x0E01, 0x0E01 },
+{ 0x0E02, 0x0E02, 0x0E02 },
+{ 0x0E03, 0x0E03, 0x0E03 },
+{ 0x0E04, 0x0E04, 0x0E04 },
+{ 0x0E05, 0x0E05, 0x0E05 },
+{ 0x0E06, 0x0E06, 0x0E06 },
+{ 0x0E07, 0x0E07, 0x0E07 },
+{ 0x0E08, 0x0E08, 0x0E08 },
+{ 0x0E09, 0x0E09, 0x0E09 },
+{ 0x0E0A, 0x0E0A, 0x0E0A },
+{ 0x0E0B, 0x0E0B, 0x0E0B },
+{ 0x0E0C, 0x0E0C, 0x0E0C },
+{ 0x0E0D, 0x0E0D, 0x0E0D },
+{ 0x0E0E, 0x0E0E, 0x0E0E },
+{ 0x0E0F, 0x0E0F, 0x0E0F },
+{ 0x0E10, 0x0E10, 0x0E10 },
+{ 0x0E11, 0x0E11, 0x0E11 },
+{ 0x0E12, 0x0E12, 0x0E12 },
+{ 0x0E13, 0x0E13, 0x0E13 },
+{ 0x0E14, 0x0E14, 0x0E14 },
+{ 0x0E15, 0x0E15, 0x0E15 },
+{ 0x0E16, 0x0E16, 0x0E16 },
+{ 0x0E17, 0x0E17, 0x0E17 },
+{ 0x0E18, 0x0E18, 0x0E18 },
+{ 0x0E19, 0x0E19, 0x0E19 },
+{ 0x0E1A, 0x0E1A, 0x0E1A },
+{ 0x0E1B, 0x0E1B, 0x0E1B },
+{ 0x0E1C, 0x0E1C, 0x0E1C },
+{ 0x0E1D, 0x0E1D, 0x0E1D },
+{ 0x0E1E, 0x0E1E, 0x0E1E },
+{ 0x0E1F, 0x0E1F, 0x0E1F },
+{ 0x0E20, 0x0E20, 0x0E20 },
+{ 0x0E21, 0x0E21, 0x0E21 },
+{ 0x0E22, 0x0E22, 0x0E22 },
+{ 0x0E23, 0x0E23, 0x0E23 },
+{ 0x0E24, 0x0E24, 0x0E24 },
+{ 0x0E25, 0x0E25, 0x0E25 },
+{ 0x0E26, 0x0E26, 0x0E26 },
+{ 0x0E27, 0x0E27, 0x0E27 },
+{ 0x0E28, 0x0E28, 0x0E28 },
+{ 0x0E29, 0x0E29, 0x0E29 },
+{ 0x0E2A, 0x0E2A, 0x0E2A },
+{ 0x0E2B, 0x0E2B, 0x0E2B },
+{ 0x0E2C, 0x0E2C, 0x0E2C },
+{ 0x0E2D, 0x0E2D, 0x0E2D },
+{ 0x0E2E, 0x0E2E, 0x0E2E },
+{ 0x0E2F, 0x0E2F, 0x0E2F },
+{ 0x0E30, 0x0E30, 0x0E30 },
+{ 0x0E31, 0x0E31, 0x0E31 },
+{ 0x0E32, 0x0E32, 0x0E32 },
+{ 0x0E33, 0x0E33, 0x0E33 },
+{ 0x0E34, 0x0E34, 0x0E34 },
+{ 0x0E35, 0x0E35, 0x0E35 },
+{ 0x0E36, 0x0E36, 0x0E36 },
+{ 0x0E37, 0x0E37, 0x0E37 },
+{ 0x0E38, 0x0E38, 0x0E38 },
+{ 0x0E39, 0x0E39, 0x0E39 },
+{ 0x0E3A, 0x0E3A, 0x0E3A },
+{ 0x0E40, 0x0E40, 0x0E40 },
+{ 0x0E41, 0x0E41, 0x0E41 },
+{ 0x0E42, 0x0E42, 0x0E42 },
+{ 0x0E43, 0x0E43, 0x0E43 },
+{ 0x0E44, 0x0E44, 0x0E44 },
+{ 0x0E45, 0x0E45, 0x0E45 },
+{ 0x0E46, 0x0E46, 0x0E46 },
+{ 0x0E47, 0x0E47, 0x0E47 },
+{ 0x0E48, 0x0E48, 0x0E48 },
+{ 0x0E49, 0x0E49, 0x0E49 },
+{ 0x0E4A, 0x0E4A, 0x0E4A },
+{ 0x0E4B, 0x0E4B, 0x0E4B },
+{ 0x0E4C, 0x0E4C, 0x0E4C },
+{ 0x0E4D, 0x0E4D, 0x0E4D },
+{ 0x0E4E, 0x0E4E, 0x0E4E },
+{ 0x0E81, 0x0E81, 0x0E81 },
+{ 0x0E82, 0x0E82, 0x0E82 },
+{ 0x0E84, 0x0E84, 0x0E84 },
+{ 0x0E87, 0x0E87, 0x0E87 },
+{ 0x0E88, 0x0E88, 0x0E88 },
+{ 0x0E8A, 0x0E8A, 0x0E8A },
+{ 0x0E8D, 0x0E8D, 0x0E8D },
+{ 0x0E94, 0x0E94, 0x0E94 },
+{ 0x0E95, 0x0E95, 0x0E95 },
+{ 0x0E96, 0x0E96, 0x0E96 },
+{ 0x0E97, 0x0E97, 0x0E97 },
+{ 0x0E99, 0x0E99, 0x0E99 },
+{ 0x0E9A, 0x0E9A, 0x0E9A },
+{ 0x0E9B, 0x0E9B, 0x0E9B },
+{ 0x0E9C, 0x0E9C, 0x0E9C },
+{ 0x0E9D, 0x0E9D, 0x0E9D },
+{ 0x0E9E, 0x0E9E, 0x0E9E },
+{ 0x0E9F, 0x0E9F, 0x0E9F },
+{ 0x0EA1, 0x0EA1, 0x0EA1 },
+{ 0x0EA2, 0x0EA2, 0x0EA2 },
+{ 0x0EA3, 0x0EA3, 0x0EA3 },
+{ 0x0EA5, 0x0EA5, 0x0EA5 },
+{ 0x0EA7, 0x0EA7, 0x0EA7 },
+{ 0x0EAA, 0x0EAA, 0x0EAA },
+{ 0x0EAB, 0x0EAB, 0x0EAB },
+{ 0x0EAD, 0x0EAD, 0x0EAD },
+{ 0x0EAE, 0x0EAE, 0x0EAE },
+{ 0x0EAF, 0x0EAF, 0x0EAF },
+{ 0x0EB0, 0x0EB0, 0x0EB0 },
+{ 0x0EB1, 0x0EB1, 0x0EB1 },
+{ 0x0EB2, 0x0EB2, 0x0EB2 },
+{ 0x0EB3, 0x0EB3, 0x0EB3 },
+{ 0x0EB4, 0x0EB4, 0x0EB4 },
+{ 0x0EB5, 0x0EB5, 0x0EB5 },
+{ 0x0EB6, 0x0EB6, 0x0EB6 },
+{ 0x0EB7, 0x0EB7, 0x0EB7 },
+{ 0x0EB8, 0x0EB8, 0x0EB8 },
+{ 0x0EB9, 0x0EB9, 0x0EB9 },
+{ 0x0EBB, 0x0EBB, 0x0EBB },
+{ 0x0EBC, 0x0EBC, 0x0EBC },
+{ 0x0EBD, 0x0EBD, 0x0EBD },
+{ 0x0EC0, 0x0EC0, 0x0EC0 },
+{ 0x0EC1, 0x0EC1, 0x0EC1 },
+{ 0x0EC2, 0x0EC2, 0x0EC2 },
+{ 0x0EC3, 0x0EC3, 0x0EC3 },
+{ 0x0EC4, 0x0EC4, 0x0EC4 },
+{ 0x0EC6, 0x0EC6, 0x0EC6 },
+{ 0x0EC8, 0x0EC8, 0x0EC8 },
+{ 0x0EC9, 0x0EC9, 0x0EC9 },
+{ 0x0ECA, 0x0ECA, 0x0ECA },
+{ 0x0ECB, 0x0ECB, 0x0ECB },
+{ 0x0ECC, 0x0ECC, 0x0ECC },
+{ 0x0ECD, 0x0ECD, 0x0ECD },
+{ 0x0EDC, 0x0EDC, 0x0EDC },
+{ 0x0EDD, 0x0EDD, 0x0EDD },
+{ 0x0F00, 0x0F00, 0x0F00 },
+{ 0x0F18, 0x0F18, 0x0F18 },
+{ 0x0F19, 0x0F19, 0x0F19 },
+{ 0x0F35, 0x0F35, 0x0F35 },
+{ 0x0F37, 0x0F37, 0x0F37 },
+{ 0x0F39, 0x0F39, 0x0F39 },
+{ 0x0F40, 0x0F40, 0x0F40 },
+{ 0x0F41, 0x0F41, 0x0F41 },
+{ 0x0F42, 0x0F42, 0x0F42 },
+{ 0x0F43, 0x0F43, 0x0F43 },
+{ 0x0F44, 0x0F44, 0x0F44 },
+{ 0x0F45, 0x0F45, 0x0F45 },
+{ 0x0F46, 0x0F46, 0x0F46 },
+{ 0x0F47, 0x0F47, 0x0F47 },
+{ 0x0F49, 0x0F49, 0x0F49 },
+{ 0x0F4A, 0x0F4A, 0x0F4A },
+{ 0x0F4B, 0x0F4B, 0x0F4B },
+{ 0x0F4C, 0x0F4C, 0x0F4C },
+{ 0x0F4D, 0x0F4D, 0x0F4D },
+{ 0x0F4E, 0x0F4E, 0x0F4E },
+{ 0x0F4F, 0x0F4F, 0x0F4F },
+{ 0x0F50, 0x0F50, 0x0F50 },
+{ 0x0F51, 0x0F51, 0x0F51 },
+{ 0x0F52, 0x0F52, 0x0F52 },
+{ 0x0F53, 0x0F53, 0x0F53 },
+{ 0x0F54, 0x0F54, 0x0F54 },
+{ 0x0F55, 0x0F55, 0x0F55 },
+{ 0x0F56, 0x0F56, 0x0F56 },
+{ 0x0F57, 0x0F57, 0x0F57 },
+{ 0x0F58, 0x0F58, 0x0F58 },
+{ 0x0F59, 0x0F59, 0x0F59 },
+{ 0x0F5A, 0x0F5A, 0x0F5A },
+{ 0x0F5B, 0x0F5B, 0x0F5B },
+{ 0x0F5C, 0x0F5C, 0x0F5C },
+{ 0x0F5D, 0x0F5D, 0x0F5D },
+{ 0x0F5E, 0x0F5E, 0x0F5E },
+{ 0x0F5F, 0x0F5F, 0x0F5F },
+{ 0x0F60, 0x0F60, 0x0F60 },
+{ 0x0F61, 0x0F61, 0x0F61 },
+{ 0x0F62, 0x0F62, 0x0F62 },
+{ 0x0F63, 0x0F63, 0x0F63 },
+{ 0x0F64, 0x0F64, 0x0F64 },
+{ 0x0F65, 0x0F65, 0x0F65 },
+{ 0x0F66, 0x0F66, 0x0F66 },
+{ 0x0F67, 0x0F67, 0x0F67 },
+{ 0x0F68, 0x0F68, 0x0F68 },
+{ 0x0F69, 0x0F69, 0x0F69 },
+{ 0x0F6A, 0x0F6A, 0x0F6A },
+{ 0x0F71, 0x0F71, 0x0F71 },
+{ 0x0F72, 0x0F72, 0x0F72 },
+{ 0x0F73, 0x0F73, 0x0F73 },
+{ 0x0F74, 0x0F74, 0x0F74 },
+{ 0x0F75, 0x0F75, 0x0F75 },
+{ 0x0F76, 0x0F76, 0x0F76 },
+{ 0x0F77, 0x0F77, 0x0F77 },
+{ 0x0F78, 0x0F78, 0x0F78 },
+{ 0x0F79, 0x0F79, 0x0F79 },
+{ 0x0F7A, 0x0F7A, 0x0F7A },
+{ 0x0F7B, 0x0F7B, 0x0F7B },
+{ 0x0F7C, 0x0F7C, 0x0F7C },
+{ 0x0F7D, 0x0F7D, 0x0F7D },
+{ 0x0F7E, 0x0F7E, 0x0F7E },
+{ 0x0F80, 0x0F80, 0x0F80 },
+{ 0x0F81, 0x0F81, 0x0F81 },
+{ 0x0F82, 0x0F82, 0x0F82 },
+{ 0x0F83, 0x0F83, 0x0F83 },
+{ 0x0F84, 0x0F84, 0x0F84 },
+{ 0x0F86, 0x0F86, 0x0F86 },
+{ 0x0F87, 0x0F87, 0x0F87 },
+{ 0x0F88, 0x0F88, 0x0F88 },
+{ 0x0F89, 0x0F89, 0x0F89 },
+{ 0x0F8A, 0x0F8A, 0x0F8A },
+{ 0x0F8B, 0x0F8B, 0x0F8B },
+{ 0x0F90, 0x0F90, 0x0F90 },
+{ 0x0F91, 0x0F91, 0x0F91 },
+{ 0x0F92, 0x0F92, 0x0F92 },
+{ 0x0F93, 0x0F93, 0x0F93 },
+{ 0x0F94, 0x0F94, 0x0F94 },
+{ 0x0F95, 0x0F95, 0x0F95 },
+{ 0x0F96, 0x0F96, 0x0F96 },
+{ 0x0F97, 0x0F97, 0x0F97 },
+{ 0x0F99, 0x0F99, 0x0F99 },
+{ 0x0F9A, 0x0F9A, 0x0F9A },
+{ 0x0F9B, 0x0F9B, 0x0F9B },
+{ 0x0F9C, 0x0F9C, 0x0F9C },
+{ 0x0F9D, 0x0F9D, 0x0F9D },
+{ 0x0F9E, 0x0F9E, 0x0F9E },
+{ 0x0F9F, 0x0F9F, 0x0F9F },
+{ 0x0FA0, 0x0FA0, 0x0FA0 },
+{ 0x0FA1, 0x0FA1, 0x0FA1 },
+{ 0x0FA2, 0x0FA2, 0x0FA2 },
+{ 0x0FA3, 0x0FA3, 0x0FA3 },
+{ 0x0FA4, 0x0FA4, 0x0FA4 },
+{ 0x0FA5, 0x0FA5, 0x0FA5 },
+{ 0x0FA6, 0x0FA6, 0x0FA6 },
+{ 0x0FA7, 0x0FA7, 0x0FA7 },
+{ 0x0FA8, 0x0FA8, 0x0FA8 },
+{ 0x0FA9, 0x0FA9, 0x0FA9 },
+{ 0x0FAA, 0x0FAA, 0x0FAA },
+{ 0x0FAB, 0x0FAB, 0x0FAB },
+{ 0x0FAC, 0x0FAC, 0x0FAC },
+{ 0x0FAD, 0x0FAD, 0x0FAD },
+{ 0x0FAE, 0x0FAE, 0x0FAE },
+{ 0x0FAF, 0x0FAF, 0x0FAF },
+{ 0x0FB0, 0x0FB0, 0x0FB0 },
+{ 0x0FB1, 0x0FB1, 0x0FB1 },
+{ 0x0FB2, 0x0FB2, 0x0FB2 },
+{ 0x0FB3, 0x0FB3, 0x0FB3 },
+{ 0x0FB4, 0x0FB4, 0x0FB4 },
+{ 0x0FB5, 0x0FB5, 0x0FB5 },
+{ 0x0FB6, 0x0FB6, 0x0FB6 },
+{ 0x0FB7, 0x0FB7, 0x0FB7 },
+{ 0x0FB8, 0x0FB8, 0x0FB8 },
+{ 0x0FB9, 0x0FB9, 0x0FB9 },
+{ 0x0FBA, 0x0FBA, 0x0FBA },
+{ 0x0FBB, 0x0FBB, 0x0FBB },
+{ 0x0FBC, 0x0FBC, 0x0FBC },
+{ 0x0FC6, 0x0FC6, 0x0FC6 },
+{ 0x1000, 0x1000, 0x1000 },
+{ 0x1001, 0x1001, 0x1001 },
+{ 0x1002, 0x1002, 0x1002 },
+{ 0x1003, 0x1003, 0x1003 },
+{ 0x1004, 0x1004, 0x1004 },
+{ 0x1005, 0x1005, 0x1005 },
+{ 0x1006, 0x1006, 0x1006 },
+{ 0x1007, 0x1007, 0x1007 },
+{ 0x1008, 0x1008, 0x1008 },
+{ 0x1009, 0x1009, 0x1009 },
+{ 0x100A, 0x100A, 0x100A },
+{ 0x100B, 0x100B, 0x100B },
+{ 0x100C, 0x100C, 0x100C },
+{ 0x100D, 0x100D, 0x100D },
+{ 0x100E, 0x100E, 0x100E },
+{ 0x100F, 0x100F, 0x100F },
+{ 0x1010, 0x1010, 0x1010 },
+{ 0x1011, 0x1011, 0x1011 },
+{ 0x1012, 0x1012, 0x1012 },
+{ 0x1013, 0x1013, 0x1013 },
+{ 0x1014, 0x1014, 0x1014 },
+{ 0x1015, 0x1015, 0x1015 },
+{ 0x1016, 0x1016, 0x1016 },
+{ 0x1017, 0x1017, 0x1017 },
+{ 0x1018, 0x1018, 0x1018 },
+{ 0x1019, 0x1019, 0x1019 },
+{ 0x101A, 0x101A, 0x101A },
+{ 0x101B, 0x101B, 0x101B },
+{ 0x101C, 0x101C, 0x101C },
+{ 0x101D, 0x101D, 0x101D },
+{ 0x101E, 0x101E, 0x101E },
+{ 0x101F, 0x101F, 0x101F },
+{ 0x1020, 0x1020, 0x1020 },
+{ 0x1021, 0x1021, 0x1021 },
+{ 0x1023, 0x1023, 0x1023 },
+{ 0x1024, 0x1024, 0x1024 },
+{ 0x1025, 0x1025, 0x1025 },
+{ 0x1026, 0x1026, 0x1026 },
+{ 0x1027, 0x1027, 0x1027 },
+{ 0x1029, 0x1029, 0x1029 },
+{ 0x102A, 0x102A, 0x102A },
+{ 0x102D, 0x102D, 0x102D },
+{ 0x102E, 0x102E, 0x102E },
+{ 0x102F, 0x102F, 0x102F },
+{ 0x1030, 0x1030, 0x1030 },
+{ 0x1032, 0x1032, 0x1032 },
+{ 0x1036, 0x1036, 0x1036 },
+{ 0x1037, 0x1037, 0x1037 },
+{ 0x1039, 0x1039, 0x1039 },
+{ 0x1050, 0x1050, 0x1050 },
+{ 0x1051, 0x1051, 0x1051 },
+{ 0x1052, 0x1052, 0x1052 },
+{ 0x1053, 0x1053, 0x1053 },
+{ 0x1054, 0x1054, 0x1054 },
+{ 0x1055, 0x1055, 0x1055 },
+{ 0x1058, 0x1058, 0x1058 },
+{ 0x1059, 0x1059, 0x1059 },
+{ 0x10A0, 0x10A0, 0x2D00 },
+{ 0x10A1, 0x10A1, 0x2D01 },
+{ 0x10A2, 0x10A2, 0x2D02 },
+{ 0x10A3, 0x10A3, 0x2D03 },
+{ 0x10A4, 0x10A4, 0x2D04 },
+{ 0x10A5, 0x10A5, 0x2D05 },
+{ 0x10A6, 0x10A6, 0x2D06 },
+{ 0x10A7, 0x10A7, 0x2D07 },
+{ 0x10A8, 0x10A8, 0x2D08 },
+{ 0x10A9, 0x10A9, 0x2D09 },
+{ 0x10AA, 0x10AA, 0x2D0A },
+{ 0x10AB, 0x10AB, 0x2D0B },
+{ 0x10AC, 0x10AC, 0x2D0C },
+{ 0x10AD, 0x10AD, 0x2D0D },
+{ 0x10AE, 0x10AE, 0x2D0E },
+{ 0x10AF, 0x10AF, 0x2D0F },
+{ 0x10B0, 0x10B0, 0x2D10 },
+{ 0x10B1, 0x10B1, 0x2D11 },
+{ 0x10B2, 0x10B2, 0x2D12 },
+{ 0x10B3, 0x10B3, 0x2D13 },
+{ 0x10B4, 0x10B4, 0x2D14 },
+{ 0x10B5, 0x10B5, 0x2D15 },
+{ 0x10B6, 0x10B6, 0x2D16 },
+{ 0x10B7, 0x10B7, 0x2D17 },
+{ 0x10B8, 0x10B8, 0x2D18 },
+{ 0x10B9, 0x10B9, 0x2D19 },
+{ 0x10BA, 0x10BA, 0x2D1A },
+{ 0x10BB, 0x10BB, 0x2D1B },
+{ 0x10BC, 0x10BC, 0x2D1C },
+{ 0x10BD, 0x10BD, 0x2D1D },
+{ 0x10BE, 0x10BE, 0x2D1E },
+{ 0x10BF, 0x10BF, 0x2D1F },
+{ 0x10C0, 0x10C0, 0x2D20 },
+{ 0x10C1, 0x10C1, 0x2D21 },
+{ 0x10C2, 0x10C2, 0x2D22 },
+{ 0x10C3, 0x10C3, 0x2D23 },
+{ 0x10C4, 0x10C4, 0x2D24 },
+{ 0x10C5, 0x10C5, 0x2D25 },
+{ 0x10D0, 0x10D0, 0x10D0 },
+{ 0x10D1, 0x10D1, 0x10D1 },
+{ 0x10D2, 0x10D2, 0x10D2 },
+{ 0x10D3, 0x10D3, 0x10D3 },
+{ 0x10D4, 0x10D4, 0x10D4 },
+{ 0x10D5, 0x10D5, 0x10D5 },
+{ 0x10D6, 0x10D6, 0x10D6 },
+{ 0x10D7, 0x10D7, 0x10D7 },
+{ 0x10D8, 0x10D8, 0x10D8 },
+{ 0x10D9, 0x10D9, 0x10D9 },
+{ 0x10DA, 0x10DA, 0x10DA },
+{ 0x10DB, 0x10DB, 0x10DB },
+{ 0x10DC, 0x10DC, 0x10DC },
+{ 0x10DD, 0x10DD, 0x10DD },
+{ 0x10DE, 0x10DE, 0x10DE },
+{ 0x10DF, 0x10DF, 0x10DF },
+{ 0x10E0, 0x10E0, 0x10E0 },
+{ 0x10E1, 0x10E1, 0x10E1 },
+{ 0x10E2, 0x10E2, 0x10E2 },
+{ 0x10E3, 0x10E3, 0x10E3 },
+{ 0x10E4, 0x10E4, 0x10E4 },
+{ 0x10E5, 0x10E5, 0x10E5 },
+{ 0x10E6, 0x10E6, 0x10E6 },
+{ 0x10E7, 0x10E7, 0x10E7 },
+{ 0x10E8, 0x10E8, 0x10E8 },
+{ 0x10E9, 0x10E9, 0x10E9 },
+{ 0x10EA, 0x10EA, 0x10EA },
+{ 0x10EB, 0x10EB, 0x10EB },
+{ 0x10EC, 0x10EC, 0x10EC },
+{ 0x10ED, 0x10ED, 0x10ED },
+{ 0x10EE, 0x10EE, 0x10EE },
+{ 0x10EF, 0x10EF, 0x10EF },
+{ 0x10F0, 0x10F0, 0x10F0 },
+{ 0x10F1, 0x10F1, 0x10F1 },
+{ 0x10F2, 0x10F2, 0x10F2 },
+{ 0x10F3, 0x10F3, 0x10F3 },
+{ 0x10F4, 0x10F4, 0x10F4 },
+{ 0x10F5, 0x10F5, 0x10F5 },
+{ 0x10F6, 0x10F6, 0x10F6 },
+{ 0x10F7, 0x10F7, 0x10F7 },
+{ 0x10F8, 0x10F8, 0x10F8 },
+{ 0x10F9, 0x10F9, 0x10F9 },
+{ 0x10FA, 0x10FA, 0x10FA },
+{ 0x10FC, 0x10FC, 0x10FC },
+{ 0x1100, 0x1100, 0x1100 },
+{ 0x1101, 0x1101, 0x1101 },
+{ 0x1102, 0x1102, 0x1102 },
+{ 0x1103, 0x1103, 0x1103 },
+{ 0x1104, 0x1104, 0x1104 },
+{ 0x1105, 0x1105, 0x1105 },
+{ 0x1106, 0x1106, 0x1106 },
+{ 0x1107, 0x1107, 0x1107 },
+{ 0x1108, 0x1108, 0x1108 },
+{ 0x1109, 0x1109, 0x1109 },
+{ 0x110A, 0x110A, 0x110A },
+{ 0x110B, 0x110B, 0x110B },
+{ 0x110C, 0x110C, 0x110C },
+{ 0x110D, 0x110D, 0x110D },
+{ 0x110E, 0x110E, 0x110E },
+{ 0x110F, 0x110F, 0x110F },
+{ 0x1110, 0x1110, 0x1110 },
+{ 0x1111, 0x1111, 0x1111 },
+{ 0x1112, 0x1112, 0x1112 },
+{ 0x1113, 0x1113, 0x1113 },
+{ 0x1114, 0x1114, 0x1114 },
+{ 0x1115, 0x1115, 0x1115 },
+{ 0x1116, 0x1116, 0x1116 },
+{ 0x1117, 0x1117, 0x1117 },
+{ 0x1118, 0x1118, 0x1118 },
+{ 0x1119, 0x1119, 0x1119 },
+{ 0x111A, 0x111A, 0x111A },
+{ 0x111B, 0x111B, 0x111B },
+{ 0x111C, 0x111C, 0x111C },
+{ 0x111D, 0x111D, 0x111D },
+{ 0x111E, 0x111E, 0x111E },
+{ 0x111F, 0x111F, 0x111F },
+{ 0x1120, 0x1120, 0x1120 },
+{ 0x1121, 0x1121, 0x1121 },
+{ 0x1122, 0x1122, 0x1122 },
+{ 0x1123, 0x1123, 0x1123 },
+{ 0x1124, 0x1124, 0x1124 },
+{ 0x1125, 0x1125, 0x1125 },
+{ 0x1126, 0x1126, 0x1126 },
+{ 0x1127, 0x1127, 0x1127 },
+{ 0x1128, 0x1128, 0x1128 },
+{ 0x1129, 0x1129, 0x1129 },
+{ 0x112A, 0x112A, 0x112A },
+{ 0x112B, 0x112B, 0x112B },
+{ 0x112C, 0x112C, 0x112C },
+{ 0x112D, 0x112D, 0x112D },
+{ 0x112E, 0x112E, 0x112E },
+{ 0x112F, 0x112F, 0x112F },
+{ 0x1130, 0x1130, 0x1130 },
+{ 0x1131, 0x1131, 0x1131 },
+{ 0x1132, 0x1132, 0x1132 },
+{ 0x1133, 0x1133, 0x1133 },
+{ 0x1134, 0x1134, 0x1134 },
+{ 0x1135, 0x1135, 0x1135 },
+{ 0x1136, 0x1136, 0x1136 },
+{ 0x1137, 0x1137, 0x1137 },
+{ 0x1138, 0x1138, 0x1138 },
+{ 0x1139, 0x1139, 0x1139 },
+{ 0x113A, 0x113A, 0x113A },
+{ 0x113B, 0x113B, 0x113B },
+{ 0x113C, 0x113C, 0x113C },
+{ 0x113D, 0x113D, 0x113D },
+{ 0x113E, 0x113E, 0x113E },
+{ 0x113F, 0x113F, 0x113F },
+{ 0x1140, 0x1140, 0x1140 },
+{ 0x1141, 0x1141, 0x1141 },
+{ 0x1142, 0x1142, 0x1142 },
+{ 0x1143, 0x1143, 0x1143 },
+{ 0x1144, 0x1144, 0x1144 },
+{ 0x1145, 0x1145, 0x1145 },
+{ 0x1146, 0x1146, 0x1146 },
+{ 0x1147, 0x1147, 0x1147 },
+{ 0x1148, 0x1148, 0x1148 },
+{ 0x1149, 0x1149, 0x1149 },
+{ 0x114A, 0x114A, 0x114A },
+{ 0x114B, 0x114B, 0x114B },
+{ 0x114C, 0x114C, 0x114C },
+{ 0x114D, 0x114D, 0x114D },
+{ 0x114E, 0x114E, 0x114E },
+{ 0x114F, 0x114F, 0x114F },
+{ 0x1150, 0x1150, 0x1150 },
+{ 0x1151, 0x1151, 0x1151 },
+{ 0x1152, 0x1152, 0x1152 },
+{ 0x1153, 0x1153, 0x1153 },
+{ 0x1154, 0x1154, 0x1154 },
+{ 0x1155, 0x1155, 0x1155 },
+{ 0x1156, 0x1156, 0x1156 },
+{ 0x1157, 0x1157, 0x1157 },
+{ 0x1158, 0x1158, 0x1158 },
+{ 0x1159, 0x1159, 0x1159 },
+{ 0x115F, 0x115F, 0x115F },
+{ 0x1160, 0x1160, 0x1160 },
+{ 0x1161, 0x1161, 0x1161 },
+{ 0x1162, 0x1162, 0x1162 },
+{ 0x1163, 0x1163, 0x1163 },
+{ 0x1164, 0x1164, 0x1164 },
+{ 0x1165, 0x1165, 0x1165 },
+{ 0x1166, 0x1166, 0x1166 },
+{ 0x1167, 0x1167, 0x1167 },
+{ 0x1168, 0x1168, 0x1168 },
+{ 0x1169, 0x1169, 0x1169 },
+{ 0x116A, 0x116A, 0x116A },
+{ 0x116B, 0x116B, 0x116B },
+{ 0x116C, 0x116C, 0x116C },
+{ 0x116D, 0x116D, 0x116D },
+{ 0x116E, 0x116E, 0x116E },
+{ 0x116F, 0x116F, 0x116F },
+{ 0x1170, 0x1170, 0x1170 },
+{ 0x1171, 0x1171, 0x1171 },
+{ 0x1172, 0x1172, 0x1172 },
+{ 0x1173, 0x1173, 0x1173 },
+{ 0x1174, 0x1174, 0x1174 },
+{ 0x1175, 0x1175, 0x1175 },
+{ 0x1176, 0x1176, 0x1176 },
+{ 0x1177, 0x1177, 0x1177 },
+{ 0x1178, 0x1178, 0x1178 },
+{ 0x1179, 0x1179, 0x1179 },
+{ 0x117A, 0x117A, 0x117A },
+{ 0x117B, 0x117B, 0x117B },
+{ 0x117C, 0x117C, 0x117C },
+{ 0x117D, 0x117D, 0x117D },
+{ 0x117E, 0x117E, 0x117E },
+{ 0x117F, 0x117F, 0x117F },
+{ 0x1180, 0x1180, 0x1180 },
+{ 0x1181, 0x1181, 0x1181 },
+{ 0x1182, 0x1182, 0x1182 },
+{ 0x1183, 0x1183, 0x1183 },
+{ 0x1184, 0x1184, 0x1184 },
+{ 0x1185, 0x1185, 0x1185 },
+{ 0x1186, 0x1186, 0x1186 },
+{ 0x1187, 0x1187, 0x1187 },
+{ 0x1188, 0x1188, 0x1188 },
+{ 0x1189, 0x1189, 0x1189 },
+{ 0x118A, 0x118A, 0x118A },
+{ 0x118B, 0x118B, 0x118B },
+{ 0x118C, 0x118C, 0x118C },
+{ 0x118D, 0x118D, 0x118D },
+{ 0x118E, 0x118E, 0x118E },
+{ 0x118F, 0x118F, 0x118F },
+{ 0x1190, 0x1190, 0x1190 },
+{ 0x1191, 0x1191, 0x1191 },
+{ 0x1192, 0x1192, 0x1192 },
+{ 0x1193, 0x1193, 0x1193 },
+{ 0x1194, 0x1194, 0x1194 },
+{ 0x1195, 0x1195, 0x1195 },
+{ 0x1196, 0x1196, 0x1196 },
+{ 0x1197, 0x1197, 0x1197 },
+{ 0x1198, 0x1198, 0x1198 },
+{ 0x1199, 0x1199, 0x1199 },
+{ 0x119A, 0x119A, 0x119A },
+{ 0x119B, 0x119B, 0x119B },
+{ 0x119C, 0x119C, 0x119C },
+{ 0x119D, 0x119D, 0x119D },
+{ 0x119E, 0x119E, 0x119E },
+{ 0x119F, 0x119F, 0x119F },
+{ 0x11A0, 0x11A0, 0x11A0 },
+{ 0x11A1, 0x11A1, 0x11A1 },
+{ 0x11A2, 0x11A2, 0x11A2 },
+{ 0x11A8, 0x11A8, 0x11A8 },
+{ 0x11A9, 0x11A9, 0x11A9 },
+{ 0x11AA, 0x11AA, 0x11AA },
+{ 0x11AB, 0x11AB, 0x11AB },
+{ 0x11AC, 0x11AC, 0x11AC },
+{ 0x11AD, 0x11AD, 0x11AD },
+{ 0x11AE, 0x11AE, 0x11AE },
+{ 0x11AF, 0x11AF, 0x11AF },
+{ 0x11B0, 0x11B0, 0x11B0 },
+{ 0x11B1, 0x11B1, 0x11B1 },
+{ 0x11B2, 0x11B2, 0x11B2 },
+{ 0x11B3, 0x11B3, 0x11B3 },
+{ 0x11B4, 0x11B4, 0x11B4 },
+{ 0x11B5, 0x11B5, 0x11B5 },
+{ 0x11B6, 0x11B6, 0x11B6 },
+{ 0x11B7, 0x11B7, 0x11B7 },
+{ 0x11B8, 0x11B8, 0x11B8 },
+{ 0x11B9, 0x11B9, 0x11B9 },
+{ 0x11BA, 0x11BA, 0x11BA },
+{ 0x11BB, 0x11BB, 0x11BB },
+{ 0x11BC, 0x11BC, 0x11BC },
+{ 0x11BD, 0x11BD, 0x11BD },
+{ 0x11BE, 0x11BE, 0x11BE },
+{ 0x11BF, 0x11BF, 0x11BF },
+{ 0x11C0, 0x11C0, 0x11C0 },
+{ 0x11C1, 0x11C1, 0x11C1 },
+{ 0x11C2, 0x11C2, 0x11C2 },
+{ 0x11C3, 0x11C3, 0x11C3 },
+{ 0x11C4, 0x11C4, 0x11C4 },
+{ 0x11C5, 0x11C5, 0x11C5 },
+{ 0x11C6, 0x11C6, 0x11C6 },
+{ 0x11C7, 0x11C7, 0x11C7 },
+{ 0x11C8, 0x11C8, 0x11C8 },
+{ 0x11C9, 0x11C9, 0x11C9 },
+{ 0x11CA, 0x11CA, 0x11CA },
+{ 0x11CB, 0x11CB, 0x11CB },
+{ 0x11CC, 0x11CC, 0x11CC },
+{ 0x11CD, 0x11CD, 0x11CD },
+{ 0x11CE, 0x11CE, 0x11CE },
+{ 0x11CF, 0x11CF, 0x11CF },
+{ 0x11D0, 0x11D0, 0x11D0 },
+{ 0x11D1, 0x11D1, 0x11D1 },
+{ 0x11D2, 0x11D2, 0x11D2 },
+{ 0x11D3, 0x11D3, 0x11D3 },
+{ 0x11D4, 0x11D4, 0x11D4 },
+{ 0x11D5, 0x11D5, 0x11D5 },
+{ 0x11D6, 0x11D6, 0x11D6 },
+{ 0x11D7, 0x11D7, 0x11D7 },
+{ 0x11D8, 0x11D8, 0x11D8 },
+{ 0x11D9, 0x11D9, 0x11D9 },
+{ 0x11DA, 0x11DA, 0x11DA },
+{ 0x11DB, 0x11DB, 0x11DB },
+{ 0x11DC, 0x11DC, 0x11DC },
+{ 0x11DD, 0x11DD, 0x11DD },
+{ 0x11DE, 0x11DE, 0x11DE },
+{ 0x11DF, 0x11DF, 0x11DF },
+{ 0x11E0, 0x11E0, 0x11E0 },
+{ 0x11E1, 0x11E1, 0x11E1 },
+{ 0x11E2, 0x11E2, 0x11E2 },
+{ 0x11E3, 0x11E3, 0x11E3 },
+{ 0x11E4, 0x11E4, 0x11E4 },
+{ 0x11E5, 0x11E5, 0x11E5 },
+{ 0x11E6, 0x11E6, 0x11E6 },
+{ 0x11E7, 0x11E7, 0x11E7 },
+{ 0x11E8, 0x11E8, 0x11E8 },
+{ 0x11E9, 0x11E9, 0x11E9 },
+{ 0x11EA, 0x11EA, 0x11EA },
+{ 0x11EB, 0x11EB, 0x11EB },
+{ 0x11EC, 0x11EC, 0x11EC },
+{ 0x11ED, 0x11ED, 0x11ED },
+{ 0x11EE, 0x11EE, 0x11EE },
+{ 0x11EF, 0x11EF, 0x11EF },
+{ 0x11F0, 0x11F0, 0x11F0 },
+{ 0x11F1, 0x11F1, 0x11F1 },
+{ 0x11F2, 0x11F2, 0x11F2 },
+{ 0x11F3, 0x11F3, 0x11F3 },
+{ 0x11F4, 0x11F4, 0x11F4 },
+{ 0x11F5, 0x11F5, 0x11F5 },
+{ 0x11F6, 0x11F6, 0x11F6 },
+{ 0x11F7, 0x11F7, 0x11F7 },
+{ 0x11F8, 0x11F8, 0x11F8 },
+{ 0x11F9, 0x11F9, 0x11F9 },
+{ 0x1200, 0x1200, 0x1200 },
+{ 0x1201, 0x1201, 0x1201 },
+{ 0x1202, 0x1202, 0x1202 },
+{ 0x1203, 0x1203, 0x1203 },
+{ 0x1204, 0x1204, 0x1204 },
+{ 0x1205, 0x1205, 0x1205 },
+{ 0x1206, 0x1206, 0x1206 },
+{ 0x1207, 0x1207, 0x1207 },
+{ 0x1208, 0x1208, 0x1208 },
+{ 0x1209, 0x1209, 0x1209 },
+{ 0x120A, 0x120A, 0x120A },
+{ 0x120B, 0x120B, 0x120B },
+{ 0x120C, 0x120C, 0x120C },
+{ 0x120D, 0x120D, 0x120D },
+{ 0x120E, 0x120E, 0x120E },
+{ 0x120F, 0x120F, 0x120F },
+{ 0x1210, 0x1210, 0x1210 },
+{ 0x1211, 0x1211, 0x1211 },
+{ 0x1212, 0x1212, 0x1212 },
+{ 0x1213, 0x1213, 0x1213 },
+{ 0x1214, 0x1214, 0x1214 },
+{ 0x1215, 0x1215, 0x1215 },
+{ 0x1216, 0x1216, 0x1216 },
+{ 0x1217, 0x1217, 0x1217 },
+{ 0x1218, 0x1218, 0x1218 },
+{ 0x1219, 0x1219, 0x1219 },
+{ 0x121A, 0x121A, 0x121A },
+{ 0x121B, 0x121B, 0x121B },
+{ 0x121C, 0x121C, 0x121C },
+{ 0x121D, 0x121D, 0x121D },
+{ 0x121E, 0x121E, 0x121E },
+{ 0x121F, 0x121F, 0x121F },
+{ 0x1220, 0x1220, 0x1220 },
+{ 0x1221, 0x1221, 0x1221 },
+{ 0x1222, 0x1222, 0x1222 },
+{ 0x1223, 0x1223, 0x1223 },
+{ 0x1224, 0x1224, 0x1224 },
+{ 0x1225, 0x1225, 0x1225 },
+{ 0x1226, 0x1226, 0x1226 },
+{ 0x1227, 0x1227, 0x1227 },
+{ 0x1228, 0x1228, 0x1228 },
+{ 0x1229, 0x1229, 0x1229 },
+{ 0x122A, 0x122A, 0x122A },
+{ 0x122B, 0x122B, 0x122B },
+{ 0x122C, 0x122C, 0x122C },
+{ 0x122D, 0x122D, 0x122D },
+{ 0x122E, 0x122E, 0x122E },
+{ 0x122F, 0x122F, 0x122F },
+{ 0x1230, 0x1230, 0x1230 },
+{ 0x1231, 0x1231, 0x1231 },
+{ 0x1232, 0x1232, 0x1232 },
+{ 0x1233, 0x1233, 0x1233 },
+{ 0x1234, 0x1234, 0x1234 },
+{ 0x1235, 0x1235, 0x1235 },
+{ 0x1236, 0x1236, 0x1236 },
+{ 0x1237, 0x1237, 0x1237 },
+{ 0x1238, 0x1238, 0x1238 },
+{ 0x1239, 0x1239, 0x1239 },
+{ 0x123A, 0x123A, 0x123A },
+{ 0x123B, 0x123B, 0x123B },
+{ 0x123C, 0x123C, 0x123C },
+{ 0x123D, 0x123D, 0x123D },
+{ 0x123E, 0x123E, 0x123E },
+{ 0x123F, 0x123F, 0x123F },
+{ 0x1240, 0x1240, 0x1240 },
+{ 0x1241, 0x1241, 0x1241 },
+{ 0x1242, 0x1242, 0x1242 },
+{ 0x1243, 0x1243, 0x1243 },
+{ 0x1244, 0x1244, 0x1244 },
+{ 0x1245, 0x1245, 0x1245 },
+{ 0x1246, 0x1246, 0x1246 },
+{ 0x1247, 0x1247, 0x1247 },
+{ 0x1248, 0x1248, 0x1248 },
+{ 0x124A, 0x124A, 0x124A },
+{ 0x124B, 0x124B, 0x124B },
+{ 0x124C, 0x124C, 0x124C },
+{ 0x124D, 0x124D, 0x124D },
+{ 0x1250, 0x1250, 0x1250 },
+{ 0x1251, 0x1251, 0x1251 },
+{ 0x1252, 0x1252, 0x1252 },
+{ 0x1253, 0x1253, 0x1253 },
+{ 0x1254, 0x1254, 0x1254 },
+{ 0x1255, 0x1255, 0x1255 },
+{ 0x1256, 0x1256, 0x1256 },
+{ 0x1258, 0x1258, 0x1258 },
+{ 0x125A, 0x125A, 0x125A },
+{ 0x125B, 0x125B, 0x125B },
+{ 0x125C, 0x125C, 0x125C },
+{ 0x125D, 0x125D, 0x125D },
+{ 0x1260, 0x1260, 0x1260 },
+{ 0x1261, 0x1261, 0x1261 },
+{ 0x1262, 0x1262, 0x1262 },
+{ 0x1263, 0x1263, 0x1263 },
+{ 0x1264, 0x1264, 0x1264 },
+{ 0x1265, 0x1265, 0x1265 },
+{ 0x1266, 0x1266, 0x1266 },
+{ 0x1267, 0x1267, 0x1267 },
+{ 0x1268, 0x1268, 0x1268 },
+{ 0x1269, 0x1269, 0x1269 },
+{ 0x126A, 0x126A, 0x126A },
+{ 0x126B, 0x126B, 0x126B },
+{ 0x126C, 0x126C, 0x126C },
+{ 0x126D, 0x126D, 0x126D },
+{ 0x126E, 0x126E, 0x126E },
+{ 0x126F, 0x126F, 0x126F },
+{ 0x1270, 0x1270, 0x1270 },
+{ 0x1271, 0x1271, 0x1271 },
+{ 0x1272, 0x1272, 0x1272 },
+{ 0x1273, 0x1273, 0x1273 },
+{ 0x1274, 0x1274, 0x1274 },
+{ 0x1275, 0x1275, 0x1275 },
+{ 0x1276, 0x1276, 0x1276 },
+{ 0x1277, 0x1277, 0x1277 },
+{ 0x1278, 0x1278, 0x1278 },
+{ 0x1279, 0x1279, 0x1279 },
+{ 0x127A, 0x127A, 0x127A },
+{ 0x127B, 0x127B, 0x127B },
+{ 0x127C, 0x127C, 0x127C },
+{ 0x127D, 0x127D, 0x127D },
+{ 0x127E, 0x127E, 0x127E },
+{ 0x127F, 0x127F, 0x127F },
+{ 0x1280, 0x1280, 0x1280 },
+{ 0x1281, 0x1281, 0x1281 },
+{ 0x1282, 0x1282, 0x1282 },
+{ 0x1283, 0x1283, 0x1283 },
+{ 0x1284, 0x1284, 0x1284 },
+{ 0x1285, 0x1285, 0x1285 },
+{ 0x1286, 0x1286, 0x1286 },
+{ 0x1287, 0x1287, 0x1287 },
+{ 0x1288, 0x1288, 0x1288 },
+{ 0x128A, 0x128A, 0x128A },
+{ 0x128B, 0x128B, 0x128B },
+{ 0x128C, 0x128C, 0x128C },
+{ 0x128D, 0x128D, 0x128D },
+{ 0x1290, 0x1290, 0x1290 },
+{ 0x1291, 0x1291, 0x1291 },
+{ 0x1292, 0x1292, 0x1292 },
+{ 0x1293, 0x1293, 0x1293 },
+{ 0x1294, 0x1294, 0x1294 },
+{ 0x1295, 0x1295, 0x1295 },
+{ 0x1296, 0x1296, 0x1296 },
+{ 0x1297, 0x1297, 0x1297 },
+{ 0x1298, 0x1298, 0x1298 },
+{ 0x1299, 0x1299, 0x1299 },
+{ 0x129A, 0x129A, 0x129A },
+{ 0x129B, 0x129B, 0x129B },
+{ 0x129C, 0x129C, 0x129C },
+{ 0x129D, 0x129D, 0x129D },
+{ 0x129E, 0x129E, 0x129E },
+{ 0x129F, 0x129F, 0x129F },
+{ 0x12A0, 0x12A0, 0x12A0 },
+{ 0x12A1, 0x12A1, 0x12A1 },
+{ 0x12A2, 0x12A2, 0x12A2 },
+{ 0x12A3, 0x12A3, 0x12A3 },
+{ 0x12A4, 0x12A4, 0x12A4 },
+{ 0x12A5, 0x12A5, 0x12A5 },
+{ 0x12A6, 0x12A6, 0x12A6 },
+{ 0x12A7, 0x12A7, 0x12A7 },
+{ 0x12A8, 0x12A8, 0x12A8 },
+{ 0x12A9, 0x12A9, 0x12A9 },
+{ 0x12AA, 0x12AA, 0x12AA },
+{ 0x12AB, 0x12AB, 0x12AB },
+{ 0x12AC, 0x12AC, 0x12AC },
+{ 0x12AD, 0x12AD, 0x12AD },
+{ 0x12AE, 0x12AE, 0x12AE },
+{ 0x12AF, 0x12AF, 0x12AF },
+{ 0x12B0, 0x12B0, 0x12B0 },
+{ 0x12B2, 0x12B2, 0x12B2 },
+{ 0x12B3, 0x12B3, 0x12B3 },
+{ 0x12B4, 0x12B4, 0x12B4 },
+{ 0x12B5, 0x12B5, 0x12B5 },
+{ 0x12B8, 0x12B8, 0x12B8 },
+{ 0x12B9, 0x12B9, 0x12B9 },
+{ 0x12BA, 0x12BA, 0x12BA },
+{ 0x12BB, 0x12BB, 0x12BB },
+{ 0x12BC, 0x12BC, 0x12BC },
+{ 0x12BD, 0x12BD, 0x12BD },
+{ 0x12BE, 0x12BE, 0x12BE },
+{ 0x12C0, 0x12C0, 0x12C0 },
+{ 0x12C2, 0x12C2, 0x12C2 },
+{ 0x12C3, 0x12C3, 0x12C3 },
+{ 0x12C4, 0x12C4, 0x12C4 },
+{ 0x12C5, 0x12C5, 0x12C5 },
+{ 0x12C8, 0x12C8, 0x12C8 },
+{ 0x12C9, 0x12C9, 0x12C9 },
+{ 0x12CA, 0x12CA, 0x12CA },
+{ 0x12CB, 0x12CB, 0x12CB },
+{ 0x12CC, 0x12CC, 0x12CC },
+{ 0x12CD, 0x12CD, 0x12CD },
+{ 0x12CE, 0x12CE, 0x12CE },
+{ 0x12CF, 0x12CF, 0x12CF },
+{ 0x12D0, 0x12D0, 0x12D0 },
+{ 0x12D1, 0x12D1, 0x12D1 },
+{ 0x12D2, 0x12D2, 0x12D2 },
+{ 0x12D3, 0x12D3, 0x12D3 },
+{ 0x12D4, 0x12D4, 0x12D4 },
+{ 0x12D5, 0x12D5, 0x12D5 },
+{ 0x12D6, 0x12D6, 0x12D6 },
+{ 0x12D8, 0x12D8, 0x12D8 },
+{ 0x12D9, 0x12D9, 0x12D9 },
+{ 0x12DA, 0x12DA, 0x12DA },
+{ 0x12DB, 0x12DB, 0x12DB },
+{ 0x12DC, 0x12DC, 0x12DC },
+{ 0x12DD, 0x12DD, 0x12DD },
+{ 0x12DE, 0x12DE, 0x12DE },
+{ 0x12DF, 0x12DF, 0x12DF },
+{ 0x12E0, 0x12E0, 0x12E0 },
+{ 0x12E1, 0x12E1, 0x12E1 },
+{ 0x12E2, 0x12E2, 0x12E2 },
+{ 0x12E3, 0x12E3, 0x12E3 },
+{ 0x12E4, 0x12E4, 0x12E4 },
+{ 0x12E5, 0x12E5, 0x12E5 },
+{ 0x12E6, 0x12E6, 0x12E6 },
+{ 0x12E7, 0x12E7, 0x12E7 },
+{ 0x12E8, 0x12E8, 0x12E8 },
+{ 0x12E9, 0x12E9, 0x12E9 },
+{ 0x12EA, 0x12EA, 0x12EA },
+{ 0x12EB, 0x12EB, 0x12EB },
+{ 0x12EC, 0x12EC, 0x12EC },
+{ 0x12ED, 0x12ED, 0x12ED },
+{ 0x12EE, 0x12EE, 0x12EE },
+{ 0x12EF, 0x12EF, 0x12EF },
+{ 0x12F0, 0x12F0, 0x12F0 },
+{ 0x12F1, 0x12F1, 0x12F1 },
+{ 0x12F2, 0x12F2, 0x12F2 },
+{ 0x12F3, 0x12F3, 0x12F3 },
+{ 0x12F4, 0x12F4, 0x12F4 },
+{ 0x12F5, 0x12F5, 0x12F5 },
+{ 0x12F6, 0x12F6, 0x12F6 },
+{ 0x12F7, 0x12F7, 0x12F7 },
+{ 0x12F8, 0x12F8, 0x12F8 },
+{ 0x12F9, 0x12F9, 0x12F9 },
+{ 0x12FA, 0x12FA, 0x12FA },
+{ 0x12FB, 0x12FB, 0x12FB },
+{ 0x12FC, 0x12FC, 0x12FC },
+{ 0x12FD, 0x12FD, 0x12FD },
+{ 0x12FE, 0x12FE, 0x12FE },
+{ 0x12FF, 0x12FF, 0x12FF },
+{ 0x1300, 0x1300, 0x1300 },
+{ 0x1301, 0x1301, 0x1301 },
+{ 0x1302, 0x1302, 0x1302 },
+{ 0x1303, 0x1303, 0x1303 },
+{ 0x1304, 0x1304, 0x1304 },
+{ 0x1305, 0x1305, 0x1305 },
+{ 0x1306, 0x1306, 0x1306 },
+{ 0x1307, 0x1307, 0x1307 },
+{ 0x1308, 0x1308, 0x1308 },
+{ 0x1309, 0x1309, 0x1309 },
+{ 0x130A, 0x130A, 0x130A },
+{ 0x130B, 0x130B, 0x130B },
+{ 0x130C, 0x130C, 0x130C },
+{ 0x130D, 0x130D, 0x130D },
+{ 0x130E, 0x130E, 0x130E },
+{ 0x130F, 0x130F, 0x130F },
+{ 0x1310, 0x1310, 0x1310 },
+{ 0x1312, 0x1312, 0x1312 },
+{ 0x1313, 0x1313, 0x1313 },
+{ 0x1314, 0x1314, 0x1314 },
+{ 0x1315, 0x1315, 0x1315 },
+{ 0x1318, 0x1318, 0x1318 },
+{ 0x1319, 0x1319, 0x1319 },
+{ 0x131A, 0x131A, 0x131A },
+{ 0x131B, 0x131B, 0x131B },
+{ 0x131C, 0x131C, 0x131C },
+{ 0x131D, 0x131D, 0x131D },
+{ 0x131E, 0x131E, 0x131E },
+{ 0x131F, 0x131F, 0x131F },
+{ 0x1320, 0x1320, 0x1320 },
+{ 0x1321, 0x1321, 0x1321 },
+{ 0x1322, 0x1322, 0x1322 },
+{ 0x1323, 0x1323, 0x1323 },
+{ 0x1324, 0x1324, 0x1324 },
+{ 0x1325, 0x1325, 0x1325 },
+{ 0x1326, 0x1326, 0x1326 },
+{ 0x1327, 0x1327, 0x1327 },
+{ 0x1328, 0x1328, 0x1328 },
+{ 0x1329, 0x1329, 0x1329 },
+{ 0x132A, 0x132A, 0x132A },
+{ 0x132B, 0x132B, 0x132B },
+{ 0x132C, 0x132C, 0x132C },
+{ 0x132D, 0x132D, 0x132D },
+{ 0x132E, 0x132E, 0x132E },
+{ 0x132F, 0x132F, 0x132F },
+{ 0x1330, 0x1330, 0x1330 },
+{ 0x1331, 0x1331, 0x1331 },
+{ 0x1332, 0x1332, 0x1332 },
+{ 0x1333, 0x1333, 0x1333 },
+{ 0x1334, 0x1334, 0x1334 },
+{ 0x1335, 0x1335, 0x1335 },
+{ 0x1336, 0x1336, 0x1336 },
+{ 0x1337, 0x1337, 0x1337 },
+{ 0x1338, 0x1338, 0x1338 },
+{ 0x1339, 0x1339, 0x1339 },
+{ 0x133A, 0x133A, 0x133A },
+{ 0x133B, 0x133B, 0x133B },
+{ 0x133C, 0x133C, 0x133C },
+{ 0x133D, 0x133D, 0x133D },
+{ 0x133E, 0x133E, 0x133E },
+{ 0x133F, 0x133F, 0x133F },
+{ 0x1340, 0x1340, 0x1340 },
+{ 0x1341, 0x1341, 0x1341 },
+{ 0x1342, 0x1342, 0x1342 },
+{ 0x1343, 0x1343, 0x1343 },
+{ 0x1344, 0x1344, 0x1344 },
+{ 0x1345, 0x1345, 0x1345 },
+{ 0x1346, 0x1346, 0x1346 },
+{ 0x1347, 0x1347, 0x1347 },
+{ 0x1348, 0x1348, 0x1348 },
+{ 0x1349, 0x1349, 0x1349 },
+{ 0x134A, 0x134A, 0x134A },
+{ 0x134B, 0x134B, 0x134B },
+{ 0x134C, 0x134C, 0x134C },
+{ 0x134D, 0x134D, 0x134D },
+{ 0x134E, 0x134E, 0x134E },
+{ 0x134F, 0x134F, 0x134F },
+{ 0x1350, 0x1350, 0x1350 },
+{ 0x1351, 0x1351, 0x1351 },
+{ 0x1352, 0x1352, 0x1352 },
+{ 0x1353, 0x1353, 0x1353 },
+{ 0x1354, 0x1354, 0x1354 },
+{ 0x1355, 0x1355, 0x1355 },
+{ 0x1356, 0x1356, 0x1356 },
+{ 0x1357, 0x1357, 0x1357 },
+{ 0x1358, 0x1358, 0x1358 },
+{ 0x1359, 0x1359, 0x1359 },
+{ 0x135A, 0x135A, 0x135A },
+{ 0x135F, 0x135F, 0x135F },
+{ 0x1380, 0x1380, 0x1380 },
+{ 0x1381, 0x1381, 0x1381 },
+{ 0x1382, 0x1382, 0x1382 },
+{ 0x1383, 0x1383, 0x1383 },
+{ 0x1384, 0x1384, 0x1384 },
+{ 0x1385, 0x1385, 0x1385 },
+{ 0x1386, 0x1386, 0x1386 },
+{ 0x1387, 0x1387, 0x1387 },
+{ 0x1388, 0x1388, 0x1388 },
+{ 0x1389, 0x1389, 0x1389 },
+{ 0x138A, 0x138A, 0x138A },
+{ 0x138B, 0x138B, 0x138B },
+{ 0x138C, 0x138C, 0x138C },
+{ 0x138D, 0x138D, 0x138D },
+{ 0x138E, 0x138E, 0x138E },
+{ 0x138F, 0x138F, 0x138F },
+{ 0x13A0, 0x13A0, 0x13A0 },
+{ 0x13A1, 0x13A1, 0x13A1 },
+{ 0x13A2, 0x13A2, 0x13A2 },
+{ 0x13A3, 0x13A3, 0x13A3 },
+{ 0x13A4, 0x13A4, 0x13A4 },
+{ 0x13A5, 0x13A5, 0x13A5 },
+{ 0x13A6, 0x13A6, 0x13A6 },
+{ 0x13A7, 0x13A7, 0x13A7 },
+{ 0x13A8, 0x13A8, 0x13A8 },
+{ 0x13A9, 0x13A9, 0x13A9 },
+{ 0x13AA, 0x13AA, 0x13AA },
+{ 0x13AB, 0x13AB, 0x13AB },
+{ 0x13AC, 0x13AC, 0x13AC },
+{ 0x13AD, 0x13AD, 0x13AD },
+{ 0x13AE, 0x13AE, 0x13AE },
+{ 0x13AF, 0x13AF, 0x13AF },
+{ 0x13B0, 0x13B0, 0x13B0 },
+{ 0x13B1, 0x13B1, 0x13B1 },
+{ 0x13B2, 0x13B2, 0x13B2 },
+{ 0x13B3, 0x13B3, 0x13B3 },
+{ 0x13B4, 0x13B4, 0x13B4 },
+{ 0x13B5, 0x13B5, 0x13B5 },
+{ 0x13B6, 0x13B6, 0x13B6 },
+{ 0x13B7, 0x13B7, 0x13B7 },
+{ 0x13B8, 0x13B8, 0x13B8 },
+{ 0x13B9, 0x13B9, 0x13B9 },
+{ 0x13BA, 0x13BA, 0x13BA },
+{ 0x13BB, 0x13BB, 0x13BB },
+{ 0x13BC, 0x13BC, 0x13BC },
+{ 0x13BD, 0x13BD, 0x13BD },
+{ 0x13BE, 0x13BE, 0x13BE },
+{ 0x13BF, 0x13BF, 0x13BF },
+{ 0x13C0, 0x13C0, 0x13C0 },
+{ 0x13C1, 0x13C1, 0x13C1 },
+{ 0x13C2, 0x13C2, 0x13C2 },
+{ 0x13C3, 0x13C3, 0x13C3 },
+{ 0x13C4, 0x13C4, 0x13C4 },
+{ 0x13C5, 0x13C5, 0x13C5 },
+{ 0x13C6, 0x13C6, 0x13C6 },
+{ 0x13C7, 0x13C7, 0x13C7 },
+{ 0x13C8, 0x13C8, 0x13C8 },
+{ 0x13C9, 0x13C9, 0x13C9 },
+{ 0x13CA, 0x13CA, 0x13CA },
+{ 0x13CB, 0x13CB, 0x13CB },
+{ 0x13CC, 0x13CC, 0x13CC },
+{ 0x13CD, 0x13CD, 0x13CD },
+{ 0x13CE, 0x13CE, 0x13CE },
+{ 0x13CF, 0x13CF, 0x13CF },
+{ 0x13D0, 0x13D0, 0x13D0 },
+{ 0x13D1, 0x13D1, 0x13D1 },
+{ 0x13D2, 0x13D2, 0x13D2 },
+{ 0x13D3, 0x13D3, 0x13D3 },
+{ 0x13D4, 0x13D4, 0x13D4 },
+{ 0x13D5, 0x13D5, 0x13D5 },
+{ 0x13D6, 0x13D6, 0x13D6 },
+{ 0x13D7, 0x13D7, 0x13D7 },
+{ 0x13D8, 0x13D8, 0x13D8 },
+{ 0x13D9, 0x13D9, 0x13D9 },
+{ 0x13DA, 0x13DA, 0x13DA },
+{ 0x13DB, 0x13DB, 0x13DB },
+{ 0x13DC, 0x13DC, 0x13DC },
+{ 0x13DD, 0x13DD, 0x13DD },
+{ 0x13DE, 0x13DE, 0x13DE },
+{ 0x13DF, 0x13DF, 0x13DF },
+{ 0x13E0, 0x13E0, 0x13E0 },
+{ 0x13E1, 0x13E1, 0x13E1 },
+{ 0x13E2, 0x13E2, 0x13E2 },
+{ 0x13E3, 0x13E3, 0x13E3 },
+{ 0x13E4, 0x13E4, 0x13E4 },
+{ 0x13E5, 0x13E5, 0x13E5 },
+{ 0x13E6, 0x13E6, 0x13E6 },
+{ 0x13E7, 0x13E7, 0x13E7 },
+{ 0x13E8, 0x13E8, 0x13E8 },
+{ 0x13E9, 0x13E9, 0x13E9 },
+{ 0x13EA, 0x13EA, 0x13EA },
+{ 0x13EB, 0x13EB, 0x13EB },
+{ 0x13EC, 0x13EC, 0x13EC },
+{ 0x13ED, 0x13ED, 0x13ED },
+{ 0x13EE, 0x13EE, 0x13EE },
+{ 0x13EF, 0x13EF, 0x13EF },
+{ 0x13F0, 0x13F0, 0x13F0 },
+{ 0x13F1, 0x13F1, 0x13F1 },
+{ 0x13F2, 0x13F2, 0x13F2 },
+{ 0x13F3, 0x13F3, 0x13F3 },
+{ 0x13F4, 0x13F4, 0x13F4 },
+{ 0x1401, 0x1401, 0x1401 },
+{ 0x1402, 0x1402, 0x1402 },
+{ 0x1403, 0x1403, 0x1403 },
+{ 0x1404, 0x1404, 0x1404 },
+{ 0x1405, 0x1405, 0x1405 },
+{ 0x1406, 0x1406, 0x1406 },
+{ 0x1407, 0x1407, 0x1407 },
+{ 0x1408, 0x1408, 0x1408 },
+{ 0x1409, 0x1409, 0x1409 },
+{ 0x140A, 0x140A, 0x140A },
+{ 0x140B, 0x140B, 0x140B },
+{ 0x140C, 0x140C, 0x140C },
+{ 0x140D, 0x140D, 0x140D },
+{ 0x140E, 0x140E, 0x140E },
+{ 0x140F, 0x140F, 0x140F },
+{ 0x1410, 0x1410, 0x1410 },
+{ 0x1411, 0x1411, 0x1411 },
+{ 0x1412, 0x1412, 0x1412 },
+{ 0x1413, 0x1413, 0x1413 },
+{ 0x1414, 0x1414, 0x1414 },
+{ 0x1415, 0x1415, 0x1415 },
+{ 0x1416, 0x1416, 0x1416 },
+{ 0x1417, 0x1417, 0x1417 },
+{ 0x1418, 0x1418, 0x1418 },
+{ 0x1419, 0x1419, 0x1419 },
+{ 0x141A, 0x141A, 0x141A },
+{ 0x141B, 0x141B, 0x141B },
+{ 0x141C, 0x141C, 0x141C },
+{ 0x141D, 0x141D, 0x141D },
+{ 0x141E, 0x141E, 0x141E },
+{ 0x141F, 0x141F, 0x141F },
+{ 0x1420, 0x1420, 0x1420 },
+{ 0x1421, 0x1421, 0x1421 },
+{ 0x1422, 0x1422, 0x1422 },
+{ 0x1423, 0x1423, 0x1423 },
+{ 0x1424, 0x1424, 0x1424 },
+{ 0x1425, 0x1425, 0x1425 },
+{ 0x1426, 0x1426, 0x1426 },
+{ 0x1427, 0x1427, 0x1427 },
+{ 0x1428, 0x1428, 0x1428 },
+{ 0x1429, 0x1429, 0x1429 },
+{ 0x142A, 0x142A, 0x142A },
+{ 0x142B, 0x142B, 0x142B },
+{ 0x142C, 0x142C, 0x142C },
+{ 0x142D, 0x142D, 0x142D },
+{ 0x142E, 0x142E, 0x142E },
+{ 0x142F, 0x142F, 0x142F },
+{ 0x1430, 0x1430, 0x1430 },
+{ 0x1431, 0x1431, 0x1431 },
+{ 0x1432, 0x1432, 0x1432 },
+{ 0x1433, 0x1433, 0x1433 },
+{ 0x1434, 0x1434, 0x1434 },
+{ 0x1435, 0x1435, 0x1435 },
+{ 0x1436, 0x1436, 0x1436 },
+{ 0x1437, 0x1437, 0x1437 },
+{ 0x1438, 0x1438, 0x1438 },
+{ 0x1439, 0x1439, 0x1439 },
+{ 0x143A, 0x143A, 0x143A },
+{ 0x143B, 0x143B, 0x143B },
+{ 0x143C, 0x143C, 0x143C },
+{ 0x143D, 0x143D, 0x143D },
+{ 0x143E, 0x143E, 0x143E },
+{ 0x143F, 0x143F, 0x143F },
+{ 0x1440, 0x1440, 0x1440 },
+{ 0x1441, 0x1441, 0x1441 },
+{ 0x1442, 0x1442, 0x1442 },
+{ 0x1443, 0x1443, 0x1443 },
+{ 0x1444, 0x1444, 0x1444 },
+{ 0x1445, 0x1445, 0x1445 },
+{ 0x1446, 0x1446, 0x1446 },
+{ 0x1447, 0x1447, 0x1447 },
+{ 0x1448, 0x1448, 0x1448 },
+{ 0x1449, 0x1449, 0x1449 },
+{ 0x144A, 0x144A, 0x144A },
+{ 0x144B, 0x144B, 0x144B },
+{ 0x144C, 0x144C, 0x144C },
+{ 0x144D, 0x144D, 0x144D },
+{ 0x144E, 0x144E, 0x144E },
+{ 0x144F, 0x144F, 0x144F },
+{ 0x1450, 0x1450, 0x1450 },
+{ 0x1451, 0x1451, 0x1451 },
+{ 0x1452, 0x1452, 0x1452 },
+{ 0x1453, 0x1453, 0x1453 },
+{ 0x1454, 0x1454, 0x1454 },
+{ 0x1455, 0x1455, 0x1455 },
+{ 0x1456, 0x1456, 0x1456 },
+{ 0x1457, 0x1457, 0x1457 },
+{ 0x1458, 0x1458, 0x1458 },
+{ 0x1459, 0x1459, 0x1459 },
+{ 0x145A, 0x145A, 0x145A },
+{ 0x145B, 0x145B, 0x145B },
+{ 0x145C, 0x145C, 0x145C },
+{ 0x145D, 0x145D, 0x145D },
+{ 0x145E, 0x145E, 0x145E },
+{ 0x145F, 0x145F, 0x145F },
+{ 0x1460, 0x1460, 0x1460 },
+{ 0x1461, 0x1461, 0x1461 },
+{ 0x1462, 0x1462, 0x1462 },
+{ 0x1463, 0x1463, 0x1463 },
+{ 0x1464, 0x1464, 0x1464 },
+{ 0x1465, 0x1465, 0x1465 },
+{ 0x1466, 0x1466, 0x1466 },
+{ 0x1467, 0x1467, 0x1467 },
+{ 0x1468, 0x1468, 0x1468 },
+{ 0x1469, 0x1469, 0x1469 },
+{ 0x146A, 0x146A, 0x146A },
+{ 0x146B, 0x146B, 0x146B },
+{ 0x146C, 0x146C, 0x146C },
+{ 0x146D, 0x146D, 0x146D },
+{ 0x146E, 0x146E, 0x146E },
+{ 0x146F, 0x146F, 0x146F },
+{ 0x1470, 0x1470, 0x1470 },
+{ 0x1471, 0x1471, 0x1471 },
+{ 0x1472, 0x1472, 0x1472 },
+{ 0x1473, 0x1473, 0x1473 },
+{ 0x1474, 0x1474, 0x1474 },
+{ 0x1475, 0x1475, 0x1475 },
+{ 0x1476, 0x1476, 0x1476 },
+{ 0x1477, 0x1477, 0x1477 },
+{ 0x1478, 0x1478, 0x1478 },
+{ 0x1479, 0x1479, 0x1479 },
+{ 0x147A, 0x147A, 0x147A },
+{ 0x147B, 0x147B, 0x147B },
+{ 0x147C, 0x147C, 0x147C },
+{ 0x147D, 0x147D, 0x147D },
+{ 0x147E, 0x147E, 0x147E },
+{ 0x147F, 0x147F, 0x147F },
+{ 0x1480, 0x1480, 0x1480 },
+{ 0x1481, 0x1481, 0x1481 },
+{ 0x1482, 0x1482, 0x1482 },
+{ 0x1483, 0x1483, 0x1483 },
+{ 0x1484, 0x1484, 0x1484 },
+{ 0x1485, 0x1485, 0x1485 },
+{ 0x1486, 0x1486, 0x1486 },
+{ 0x1487, 0x1487, 0x1487 },
+{ 0x1488, 0x1488, 0x1488 },
+{ 0x1489, 0x1489, 0x1489 },
+{ 0x148A, 0x148A, 0x148A },
+{ 0x148B, 0x148B, 0x148B },
+{ 0x148C, 0x148C, 0x148C },
+{ 0x148D, 0x148D, 0x148D },
+{ 0x148E, 0x148E, 0x148E },
+{ 0x148F, 0x148F, 0x148F },
+{ 0x1490, 0x1490, 0x1490 },
+{ 0x1491, 0x1491, 0x1491 },
+{ 0x1492, 0x1492, 0x1492 },
+{ 0x1493, 0x1493, 0x1493 },
+{ 0x1494, 0x1494, 0x1494 },
+{ 0x1495, 0x1495, 0x1495 },
+{ 0x1496, 0x1496, 0x1496 },
+{ 0x1497, 0x1497, 0x1497 },
+{ 0x1498, 0x1498, 0x1498 },
+{ 0x1499, 0x1499, 0x1499 },
+{ 0x149A, 0x149A, 0x149A },
+{ 0x149B, 0x149B, 0x149B },
+{ 0x149C, 0x149C, 0x149C },
+{ 0x149D, 0x149D, 0x149D },
+{ 0x149E, 0x149E, 0x149E },
+{ 0x149F, 0x149F, 0x149F },
+{ 0x14A0, 0x14A0, 0x14A0 },
+{ 0x14A1, 0x14A1, 0x14A1 },
+{ 0x14A2, 0x14A2, 0x14A2 },
+{ 0x14A3, 0x14A3, 0x14A3 },
+{ 0x14A4, 0x14A4, 0x14A4 },
+{ 0x14A5, 0x14A5, 0x14A5 },
+{ 0x14A6, 0x14A6, 0x14A6 },
+{ 0x14A7, 0x14A7, 0x14A7 },
+{ 0x14A8, 0x14A8, 0x14A8 },
+{ 0x14A9, 0x14A9, 0x14A9 },
+{ 0x14AA, 0x14AA, 0x14AA },
+{ 0x14AB, 0x14AB, 0x14AB },
+{ 0x14AC, 0x14AC, 0x14AC },
+{ 0x14AD, 0x14AD, 0x14AD },
+{ 0x14AE, 0x14AE, 0x14AE },
+{ 0x14AF, 0x14AF, 0x14AF },
+{ 0x14B0, 0x14B0, 0x14B0 },
+{ 0x14B1, 0x14B1, 0x14B1 },
+{ 0x14B2, 0x14B2, 0x14B2 },
+{ 0x14B3, 0x14B3, 0x14B3 },
+{ 0x14B4, 0x14B4, 0x14B4 },
+{ 0x14B5, 0x14B5, 0x14B5 },
+{ 0x14B6, 0x14B6, 0x14B6 },
+{ 0x14B7, 0x14B7, 0x14B7 },
+{ 0x14B8, 0x14B8, 0x14B8 },
+{ 0x14B9, 0x14B9, 0x14B9 },
+{ 0x14BA, 0x14BA, 0x14BA },
+{ 0x14BB, 0x14BB, 0x14BB },
+{ 0x14BC, 0x14BC, 0x14BC },
+{ 0x14BD, 0x14BD, 0x14BD },
+{ 0x14BE, 0x14BE, 0x14BE },
+{ 0x14BF, 0x14BF, 0x14BF },
+{ 0x14C0, 0x14C0, 0x14C0 },
+{ 0x14C1, 0x14C1, 0x14C1 },
+{ 0x14C2, 0x14C2, 0x14C2 },
+{ 0x14C3, 0x14C3, 0x14C3 },
+{ 0x14C4, 0x14C4, 0x14C4 },
+{ 0x14C5, 0x14C5, 0x14C5 },
+{ 0x14C6, 0x14C6, 0x14C6 },
+{ 0x14C7, 0x14C7, 0x14C7 },
+{ 0x14C8, 0x14C8, 0x14C8 },
+{ 0x14C9, 0x14C9, 0x14C9 },
+{ 0x14CA, 0x14CA, 0x14CA },
+{ 0x14CB, 0x14CB, 0x14CB },
+{ 0x14CC, 0x14CC, 0x14CC },
+{ 0x14CD, 0x14CD, 0x14CD },
+{ 0x14CE, 0x14CE, 0x14CE },
+{ 0x14CF, 0x14CF, 0x14CF },
+{ 0x14D0, 0x14D0, 0x14D0 },
+{ 0x14D1, 0x14D1, 0x14D1 },
+{ 0x14D2, 0x14D2, 0x14D2 },
+{ 0x14D3, 0x14D3, 0x14D3 },
+{ 0x14D4, 0x14D4, 0x14D4 },
+{ 0x14D5, 0x14D5, 0x14D5 },
+{ 0x14D6, 0x14D6, 0x14D6 },
+{ 0x14D7, 0x14D7, 0x14D7 },
+{ 0x14D8, 0x14D8, 0x14D8 },
+{ 0x14D9, 0x14D9, 0x14D9 },
+{ 0x14DA, 0x14DA, 0x14DA },
+{ 0x14DB, 0x14DB, 0x14DB },
+{ 0x14DC, 0x14DC, 0x14DC },
+{ 0x14DD, 0x14DD, 0x14DD },
+{ 0x14DE, 0x14DE, 0x14DE },
+{ 0x14DF, 0x14DF, 0x14DF },
+{ 0x14E0, 0x14E0, 0x14E0 },
+{ 0x14E1, 0x14E1, 0x14E1 },
+{ 0x14E2, 0x14E2, 0x14E2 },
+{ 0x14E3, 0x14E3, 0x14E3 },
+{ 0x14E4, 0x14E4, 0x14E4 },
+{ 0x14E5, 0x14E5, 0x14E5 },
+{ 0x14E6, 0x14E6, 0x14E6 },
+{ 0x14E7, 0x14E7, 0x14E7 },
+{ 0x14E8, 0x14E8, 0x14E8 },
+{ 0x14E9, 0x14E9, 0x14E9 },
+{ 0x14EA, 0x14EA, 0x14EA },
+{ 0x14EB, 0x14EB, 0x14EB },
+{ 0x14EC, 0x14EC, 0x14EC },
+{ 0x14ED, 0x14ED, 0x14ED },
+{ 0x14EE, 0x14EE, 0x14EE },
+{ 0x14EF, 0x14EF, 0x14EF },
+{ 0x14F0, 0x14F0, 0x14F0 },
+{ 0x14F1, 0x14F1, 0x14F1 },
+{ 0x14F2, 0x14F2, 0x14F2 },
+{ 0x14F3, 0x14F3, 0x14F3 },
+{ 0x14F4, 0x14F4, 0x14F4 },
+{ 0x14F5, 0x14F5, 0x14F5 },
+{ 0x14F6, 0x14F6, 0x14F6 },
+{ 0x14F7, 0x14F7, 0x14F7 },
+{ 0x14F8, 0x14F8, 0x14F8 },
+{ 0x14F9, 0x14F9, 0x14F9 },
+{ 0x14FA, 0x14FA, 0x14FA },
+{ 0x14FB, 0x14FB, 0x14FB },
+{ 0x14FC, 0x14FC, 0x14FC },
+{ 0x14FD, 0x14FD, 0x14FD },
+{ 0x14FE, 0x14FE, 0x14FE },
+{ 0x14FF, 0x14FF, 0x14FF },
+{ 0x1500, 0x1500, 0x1500 },
+{ 0x1501, 0x1501, 0x1501 },
+{ 0x1502, 0x1502, 0x1502 },
+{ 0x1503, 0x1503, 0x1503 },
+{ 0x1504, 0x1504, 0x1504 },
+{ 0x1505, 0x1505, 0x1505 },
+{ 0x1506, 0x1506, 0x1506 },
+{ 0x1507, 0x1507, 0x1507 },
+{ 0x1508, 0x1508, 0x1508 },
+{ 0x1509, 0x1509, 0x1509 },
+{ 0x150A, 0x150A, 0x150A },
+{ 0x150B, 0x150B, 0x150B },
+{ 0x150C, 0x150C, 0x150C },
+{ 0x150D, 0x150D, 0x150D },
+{ 0x150E, 0x150E, 0x150E },
+{ 0x150F, 0x150F, 0x150F },
+{ 0x1510, 0x1510, 0x1510 },
+{ 0x1511, 0x1511, 0x1511 },
+{ 0x1512, 0x1512, 0x1512 },
+{ 0x1513, 0x1513, 0x1513 },
+{ 0x1514, 0x1514, 0x1514 },
+{ 0x1515, 0x1515, 0x1515 },
+{ 0x1516, 0x1516, 0x1516 },
+{ 0x1517, 0x1517, 0x1517 },
+{ 0x1518, 0x1518, 0x1518 },
+{ 0x1519, 0x1519, 0x1519 },
+{ 0x151A, 0x151A, 0x151A },
+{ 0x151B, 0x151B, 0x151B },
+{ 0x151C, 0x151C, 0x151C },
+{ 0x151D, 0x151D, 0x151D },
+{ 0x151E, 0x151E, 0x151E },
+{ 0x151F, 0x151F, 0x151F },
+{ 0x1520, 0x1520, 0x1520 },
+{ 0x1521, 0x1521, 0x1521 },
+{ 0x1522, 0x1522, 0x1522 },
+{ 0x1523, 0x1523, 0x1523 },
+{ 0x1524, 0x1524, 0x1524 },
+{ 0x1525, 0x1525, 0x1525 },
+{ 0x1526, 0x1526, 0x1526 },
+{ 0x1527, 0x1527, 0x1527 },
+{ 0x1528, 0x1528, 0x1528 },
+{ 0x1529, 0x1529, 0x1529 },
+{ 0x152A, 0x152A, 0x152A },
+{ 0x152B, 0x152B, 0x152B },
+{ 0x152C, 0x152C, 0x152C },
+{ 0x152D, 0x152D, 0x152D },
+{ 0x152E, 0x152E, 0x152E },
+{ 0x152F, 0x152F, 0x152F },
+{ 0x1530, 0x1530, 0x1530 },
+{ 0x1531, 0x1531, 0x1531 },
+{ 0x1532, 0x1532, 0x1532 },
+{ 0x1533, 0x1533, 0x1533 },
+{ 0x1534, 0x1534, 0x1534 },
+{ 0x1535, 0x1535, 0x1535 },
+{ 0x1536, 0x1536, 0x1536 },
+{ 0x1537, 0x1537, 0x1537 },
+{ 0x1538, 0x1538, 0x1538 },
+{ 0x1539, 0x1539, 0x1539 },
+{ 0x153A, 0x153A, 0x153A },
+{ 0x153B, 0x153B, 0x153B },
+{ 0x153C, 0x153C, 0x153C },
+{ 0x153D, 0x153D, 0x153D },
+{ 0x153E, 0x153E, 0x153E },
+{ 0x153F, 0x153F, 0x153F },
+{ 0x1540, 0x1540, 0x1540 },
+{ 0x1541, 0x1541, 0x1541 },
+{ 0x1542, 0x1542, 0x1542 },
+{ 0x1543, 0x1543, 0x1543 },
+{ 0x1544, 0x1544, 0x1544 },
+{ 0x1545, 0x1545, 0x1545 },
+{ 0x1546, 0x1546, 0x1546 },
+{ 0x1547, 0x1547, 0x1547 },
+{ 0x1548, 0x1548, 0x1548 },
+{ 0x1549, 0x1549, 0x1549 },
+{ 0x154A, 0x154A, 0x154A },
+{ 0x154B, 0x154B, 0x154B },
+{ 0x154C, 0x154C, 0x154C },
+{ 0x154D, 0x154D, 0x154D },
+{ 0x154E, 0x154E, 0x154E },
+{ 0x154F, 0x154F, 0x154F },
+{ 0x1550, 0x1550, 0x1550 },
+{ 0x1551, 0x1551, 0x1551 },
+{ 0x1552, 0x1552, 0x1552 },
+{ 0x1553, 0x1553, 0x1553 },
+{ 0x1554, 0x1554, 0x1554 },
+{ 0x1555, 0x1555, 0x1555 },
+{ 0x1556, 0x1556, 0x1556 },
+{ 0x1557, 0x1557, 0x1557 },
+{ 0x1558, 0x1558, 0x1558 },
+{ 0x1559, 0x1559, 0x1559 },
+{ 0x155A, 0x155A, 0x155A },
+{ 0x155B, 0x155B, 0x155B },
+{ 0x155C, 0x155C, 0x155C },
+{ 0x155D, 0x155D, 0x155D },
+{ 0x155E, 0x155E, 0x155E },
+{ 0x155F, 0x155F, 0x155F },
+{ 0x1560, 0x1560, 0x1560 },
+{ 0x1561, 0x1561, 0x1561 },
+{ 0x1562, 0x1562, 0x1562 },
+{ 0x1563, 0x1563, 0x1563 },
+{ 0x1564, 0x1564, 0x1564 },
+{ 0x1565, 0x1565, 0x1565 },
+{ 0x1566, 0x1566, 0x1566 },
+{ 0x1567, 0x1567, 0x1567 },
+{ 0x1568, 0x1568, 0x1568 },
+{ 0x1569, 0x1569, 0x1569 },
+{ 0x156A, 0x156A, 0x156A },
+{ 0x156B, 0x156B, 0x156B },
+{ 0x156C, 0x156C, 0x156C },
+{ 0x156D, 0x156D, 0x156D },
+{ 0x156E, 0x156E, 0x156E },
+{ 0x156F, 0x156F, 0x156F },
+{ 0x1570, 0x1570, 0x1570 },
+{ 0x1571, 0x1571, 0x1571 },
+{ 0x1572, 0x1572, 0x1572 },
+{ 0x1573, 0x1573, 0x1573 },
+{ 0x1574, 0x1574, 0x1574 },
+{ 0x1575, 0x1575, 0x1575 },
+{ 0x1576, 0x1576, 0x1576 },
+{ 0x1577, 0x1577, 0x1577 },
+{ 0x1578, 0x1578, 0x1578 },
+{ 0x1579, 0x1579, 0x1579 },
+{ 0x157A, 0x157A, 0x157A },
+{ 0x157B, 0x157B, 0x157B },
+{ 0x157C, 0x157C, 0x157C },
+{ 0x157D, 0x157D, 0x157D },
+{ 0x157E, 0x157E, 0x157E },
+{ 0x157F, 0x157F, 0x157F },
+{ 0x1580, 0x1580, 0x1580 },
+{ 0x1581, 0x1581, 0x1581 },
+{ 0x1582, 0x1582, 0x1582 },
+{ 0x1583, 0x1583, 0x1583 },
+{ 0x1584, 0x1584, 0x1584 },
+{ 0x1585, 0x1585, 0x1585 },
+{ 0x1586, 0x1586, 0x1586 },
+{ 0x1587, 0x1587, 0x1587 },
+{ 0x1588, 0x1588, 0x1588 },
+{ 0x1589, 0x1589, 0x1589 },
+{ 0x158A, 0x158A, 0x158A },
+{ 0x158B, 0x158B, 0x158B },
+{ 0x158C, 0x158C, 0x158C },
+{ 0x158D, 0x158D, 0x158D },
+{ 0x158E, 0x158E, 0x158E },
+{ 0x158F, 0x158F, 0x158F },
+{ 0x1590, 0x1590, 0x1590 },
+{ 0x1591, 0x1591, 0x1591 },
+{ 0x1592, 0x1592, 0x1592 },
+{ 0x1593, 0x1593, 0x1593 },
+{ 0x1594, 0x1594, 0x1594 },
+{ 0x1595, 0x1595, 0x1595 },
+{ 0x1596, 0x1596, 0x1596 },
+{ 0x1597, 0x1597, 0x1597 },
+{ 0x1598, 0x1598, 0x1598 },
+{ 0x1599, 0x1599, 0x1599 },
+{ 0x159A, 0x159A, 0x159A },
+{ 0x159B, 0x159B, 0x159B },
+{ 0x159C, 0x159C, 0x159C },
+{ 0x159D, 0x159D, 0x159D },
+{ 0x159E, 0x159E, 0x159E },
+{ 0x159F, 0x159F, 0x159F },
+{ 0x15A0, 0x15A0, 0x15A0 },
+{ 0x15A1, 0x15A1, 0x15A1 },
+{ 0x15A2, 0x15A2, 0x15A2 },
+{ 0x15A3, 0x15A3, 0x15A3 },
+{ 0x15A4, 0x15A4, 0x15A4 },
+{ 0x15A5, 0x15A5, 0x15A5 },
+{ 0x15A6, 0x15A6, 0x15A6 },
+{ 0x15A7, 0x15A7, 0x15A7 },
+{ 0x15A8, 0x15A8, 0x15A8 },
+{ 0x15A9, 0x15A9, 0x15A9 },
+{ 0x15AA, 0x15AA, 0x15AA },
+{ 0x15AB, 0x15AB, 0x15AB },
+{ 0x15AC, 0x15AC, 0x15AC },
+{ 0x15AD, 0x15AD, 0x15AD },
+{ 0x15AE, 0x15AE, 0x15AE },
+{ 0x15AF, 0x15AF, 0x15AF },
+{ 0x15B0, 0x15B0, 0x15B0 },
+{ 0x15B1, 0x15B1, 0x15B1 },
+{ 0x15B2, 0x15B2, 0x15B2 },
+{ 0x15B3, 0x15B3, 0x15B3 },
+{ 0x15B4, 0x15B4, 0x15B4 },
+{ 0x15B5, 0x15B5, 0x15B5 },
+{ 0x15B6, 0x15B6, 0x15B6 },
+{ 0x15B7, 0x15B7, 0x15B7 },
+{ 0x15B8, 0x15B8, 0x15B8 },
+{ 0x15B9, 0x15B9, 0x15B9 },
+{ 0x15BA, 0x15BA, 0x15BA },
+{ 0x15BB, 0x15BB, 0x15BB },
+{ 0x15BC, 0x15BC, 0x15BC },
+{ 0x15BD, 0x15BD, 0x15BD },
+{ 0x15BE, 0x15BE, 0x15BE },
+{ 0x15BF, 0x15BF, 0x15BF },
+{ 0x15C0, 0x15C0, 0x15C0 },
+{ 0x15C1, 0x15C1, 0x15C1 },
+{ 0x15C2, 0x15C2, 0x15C2 },
+{ 0x15C3, 0x15C3, 0x15C3 },
+{ 0x15C4, 0x15C4, 0x15C4 },
+{ 0x15C5, 0x15C5, 0x15C5 },
+{ 0x15C6, 0x15C6, 0x15C6 },
+{ 0x15C7, 0x15C7, 0x15C7 },
+{ 0x15C8, 0x15C8, 0x15C8 },
+{ 0x15C9, 0x15C9, 0x15C9 },
+{ 0x15CA, 0x15CA, 0x15CA },
+{ 0x15CB, 0x15CB, 0x15CB },
+{ 0x15CC, 0x15CC, 0x15CC },
+{ 0x15CD, 0x15CD, 0x15CD },
+{ 0x15CE, 0x15CE, 0x15CE },
+{ 0x15CF, 0x15CF, 0x15CF },
+{ 0x15D0, 0x15D0, 0x15D0 },
+{ 0x15D1, 0x15D1, 0x15D1 },
+{ 0x15D2, 0x15D2, 0x15D2 },
+{ 0x15D3, 0x15D3, 0x15D3 },
+{ 0x15D4, 0x15D4, 0x15D4 },
+{ 0x15D5, 0x15D5, 0x15D5 },
+{ 0x15D6, 0x15D6, 0x15D6 },
+{ 0x15D7, 0x15D7, 0x15D7 },
+{ 0x15D8, 0x15D8, 0x15D8 },
+{ 0x15D9, 0x15D9, 0x15D9 },
+{ 0x15DA, 0x15DA, 0x15DA },
+{ 0x15DB, 0x15DB, 0x15DB },
+{ 0x15DC, 0x15DC, 0x15DC },
+{ 0x15DD, 0x15DD, 0x15DD },
+{ 0x15DE, 0x15DE, 0x15DE },
+{ 0x15DF, 0x15DF, 0x15DF },
+{ 0x15E0, 0x15E0, 0x15E0 },
+{ 0x15E1, 0x15E1, 0x15E1 },
+{ 0x15E2, 0x15E2, 0x15E2 },
+{ 0x15E3, 0x15E3, 0x15E3 },
+{ 0x15E4, 0x15E4, 0x15E4 },
+{ 0x15E5, 0x15E5, 0x15E5 },
+{ 0x15E6, 0x15E6, 0x15E6 },
+{ 0x15E7, 0x15E7, 0x15E7 },
+{ 0x15E8, 0x15E8, 0x15E8 },
+{ 0x15E9, 0x15E9, 0x15E9 },
+{ 0x15EA, 0x15EA, 0x15EA },
+{ 0x15EB, 0x15EB, 0x15EB },
+{ 0x15EC, 0x15EC, 0x15EC },
+{ 0x15ED, 0x15ED, 0x15ED },
+{ 0x15EE, 0x15EE, 0x15EE },
+{ 0x15EF, 0x15EF, 0x15EF },
+{ 0x15F0, 0x15F0, 0x15F0 },
+{ 0x15F1, 0x15F1, 0x15F1 },
+{ 0x15F2, 0x15F2, 0x15F2 },
+{ 0x15F3, 0x15F3, 0x15F3 },
+{ 0x15F4, 0x15F4, 0x15F4 },
+{ 0x15F5, 0x15F5, 0x15F5 },
+{ 0x15F6, 0x15F6, 0x15F6 },
+{ 0x15F7, 0x15F7, 0x15F7 },
+{ 0x15F8, 0x15F8, 0x15F8 },
+{ 0x15F9, 0x15F9, 0x15F9 },
+{ 0x15FA, 0x15FA, 0x15FA },
+{ 0x15FB, 0x15FB, 0x15FB },
+{ 0x15FC, 0x15FC, 0x15FC },
+{ 0x15FD, 0x15FD, 0x15FD },
+{ 0x15FE, 0x15FE, 0x15FE },
+{ 0x15FF, 0x15FF, 0x15FF },
+{ 0x1600, 0x1600, 0x1600 },
+{ 0x1601, 0x1601, 0x1601 },
+{ 0x1602, 0x1602, 0x1602 },
+{ 0x1603, 0x1603, 0x1603 },
+{ 0x1604, 0x1604, 0x1604 },
+{ 0x1605, 0x1605, 0x1605 },
+{ 0x1606, 0x1606, 0x1606 },
+{ 0x1607, 0x1607, 0x1607 },
+{ 0x1608, 0x1608, 0x1608 },
+{ 0x1609, 0x1609, 0x1609 },
+{ 0x160A, 0x160A, 0x160A },
+{ 0x160B, 0x160B, 0x160B },
+{ 0x160C, 0x160C, 0x160C },
+{ 0x160D, 0x160D, 0x160D },
+{ 0x160E, 0x160E, 0x160E },
+{ 0x160F, 0x160F, 0x160F },
+{ 0x1610, 0x1610, 0x1610 },
+{ 0x1611, 0x1611, 0x1611 },
+{ 0x1612, 0x1612, 0x1612 },
+{ 0x1613, 0x1613, 0x1613 },
+{ 0x1614, 0x1614, 0x1614 },
+{ 0x1615, 0x1615, 0x1615 },
+{ 0x1616, 0x1616, 0x1616 },
+{ 0x1617, 0x1617, 0x1617 },
+{ 0x1618, 0x1618, 0x1618 },
+{ 0x1619, 0x1619, 0x1619 },
+{ 0x161A, 0x161A, 0x161A },
+{ 0x161B, 0x161B, 0x161B },
+{ 0x161C, 0x161C, 0x161C },
+{ 0x161D, 0x161D, 0x161D },
+{ 0x161E, 0x161E, 0x161E },
+{ 0x161F, 0x161F, 0x161F },
+{ 0x1620, 0x1620, 0x1620 },
+{ 0x1621, 0x1621, 0x1621 },
+{ 0x1622, 0x1622, 0x1622 },
+{ 0x1623, 0x1623, 0x1623 },
+{ 0x1624, 0x1624, 0x1624 },
+{ 0x1625, 0x1625, 0x1625 },
+{ 0x1626, 0x1626, 0x1626 },
+{ 0x1627, 0x1627, 0x1627 },
+{ 0x1628, 0x1628, 0x1628 },
+{ 0x1629, 0x1629, 0x1629 },
+{ 0x162A, 0x162A, 0x162A },
+{ 0x162B, 0x162B, 0x162B },
+{ 0x162C, 0x162C, 0x162C },
+{ 0x162D, 0x162D, 0x162D },
+{ 0x162E, 0x162E, 0x162E },
+{ 0x162F, 0x162F, 0x162F },
+{ 0x1630, 0x1630, 0x1630 },
+{ 0x1631, 0x1631, 0x1631 },
+{ 0x1632, 0x1632, 0x1632 },
+{ 0x1633, 0x1633, 0x1633 },
+{ 0x1634, 0x1634, 0x1634 },
+{ 0x1635, 0x1635, 0x1635 },
+{ 0x1636, 0x1636, 0x1636 },
+{ 0x1637, 0x1637, 0x1637 },
+{ 0x1638, 0x1638, 0x1638 },
+{ 0x1639, 0x1639, 0x1639 },
+{ 0x163A, 0x163A, 0x163A },
+{ 0x163B, 0x163B, 0x163B },
+{ 0x163C, 0x163C, 0x163C },
+{ 0x163D, 0x163D, 0x163D },
+{ 0x163E, 0x163E, 0x163E },
+{ 0x163F, 0x163F, 0x163F },
+{ 0x1640, 0x1640, 0x1640 },
+{ 0x1641, 0x1641, 0x1641 },
+{ 0x1642, 0x1642, 0x1642 },
+{ 0x1643, 0x1643, 0x1643 },
+{ 0x1644, 0x1644, 0x1644 },
+{ 0x1645, 0x1645, 0x1645 },
+{ 0x1646, 0x1646, 0x1646 },
+{ 0x1647, 0x1647, 0x1647 },
+{ 0x1648, 0x1648, 0x1648 },
+{ 0x1649, 0x1649, 0x1649 },
+{ 0x164A, 0x164A, 0x164A },
+{ 0x164B, 0x164B, 0x164B },
+{ 0x164C, 0x164C, 0x164C },
+{ 0x164D, 0x164D, 0x164D },
+{ 0x164E, 0x164E, 0x164E },
+{ 0x164F, 0x164F, 0x164F },
+{ 0x1650, 0x1650, 0x1650 },
+{ 0x1651, 0x1651, 0x1651 },
+{ 0x1652, 0x1652, 0x1652 },
+{ 0x1653, 0x1653, 0x1653 },
+{ 0x1654, 0x1654, 0x1654 },
+{ 0x1655, 0x1655, 0x1655 },
+{ 0x1656, 0x1656, 0x1656 },
+{ 0x1657, 0x1657, 0x1657 },
+{ 0x1658, 0x1658, 0x1658 },
+{ 0x1659, 0x1659, 0x1659 },
+{ 0x165A, 0x165A, 0x165A },
+{ 0x165B, 0x165B, 0x165B },
+{ 0x165C, 0x165C, 0x165C },
+{ 0x165D, 0x165D, 0x165D },
+{ 0x165E, 0x165E, 0x165E },
+{ 0x165F, 0x165F, 0x165F },
+{ 0x1660, 0x1660, 0x1660 },
+{ 0x1661, 0x1661, 0x1661 },
+{ 0x1662, 0x1662, 0x1662 },
+{ 0x1663, 0x1663, 0x1663 },
+{ 0x1664, 0x1664, 0x1664 },
+{ 0x1665, 0x1665, 0x1665 },
+{ 0x1666, 0x1666, 0x1666 },
+{ 0x1667, 0x1667, 0x1667 },
+{ 0x1668, 0x1668, 0x1668 },
+{ 0x1669, 0x1669, 0x1669 },
+{ 0x166A, 0x166A, 0x166A },
+{ 0x166B, 0x166B, 0x166B },
+{ 0x166C, 0x166C, 0x166C },
+{ 0x166F, 0x166F, 0x166F },
+{ 0x1670, 0x1670, 0x1670 },
+{ 0x1671, 0x1671, 0x1671 },
+{ 0x1672, 0x1672, 0x1672 },
+{ 0x1673, 0x1673, 0x1673 },
+{ 0x1674, 0x1674, 0x1674 },
+{ 0x1675, 0x1675, 0x1675 },
+{ 0x1676, 0x1676, 0x1676 },
+{ 0x1681, 0x1681, 0x1681 },
+{ 0x1682, 0x1682, 0x1682 },
+{ 0x1683, 0x1683, 0x1683 },
+{ 0x1684, 0x1684, 0x1684 },
+{ 0x1685, 0x1685, 0x1685 },
+{ 0x1686, 0x1686, 0x1686 },
+{ 0x1687, 0x1687, 0x1687 },
+{ 0x1688, 0x1688, 0x1688 },
+{ 0x1689, 0x1689, 0x1689 },
+{ 0x168A, 0x168A, 0x168A },
+{ 0x168B, 0x168B, 0x168B },
+{ 0x168C, 0x168C, 0x168C },
+{ 0x168D, 0x168D, 0x168D },
+{ 0x168E, 0x168E, 0x168E },
+{ 0x168F, 0x168F, 0x168F },
+{ 0x1690, 0x1690, 0x1690 },
+{ 0x1691, 0x1691, 0x1691 },
+{ 0x1692, 0x1692, 0x1692 },
+{ 0x1693, 0x1693, 0x1693 },
+{ 0x1694, 0x1694, 0x1694 },
+{ 0x1695, 0x1695, 0x1695 },
+{ 0x1696, 0x1696, 0x1696 },
+{ 0x1697, 0x1697, 0x1697 },
+{ 0x1698, 0x1698, 0x1698 },
+{ 0x1699, 0x1699, 0x1699 },
+{ 0x169A, 0x169A, 0x169A },
+{ 0x16A0, 0x16A0, 0x16A0 },
+{ 0x16A1, 0x16A1, 0x16A1 },
+{ 0x16A2, 0x16A2, 0x16A2 },
+{ 0x16A3, 0x16A3, 0x16A3 },
+{ 0x16A4, 0x16A4, 0x16A4 },
+{ 0x16A5, 0x16A5, 0x16A5 },
+{ 0x16A6, 0x16A6, 0x16A6 },
+{ 0x16A7, 0x16A7, 0x16A7 },
+{ 0x16A8, 0x16A8, 0x16A8 },
+{ 0x16A9, 0x16A9, 0x16A9 },
+{ 0x16AA, 0x16AA, 0x16AA },
+{ 0x16AB, 0x16AB, 0x16AB },
+{ 0x16AC, 0x16AC, 0x16AC },
+{ 0x16AD, 0x16AD, 0x16AD },
+{ 0x16AE, 0x16AE, 0x16AE },
+{ 0x16AF, 0x16AF, 0x16AF },
+{ 0x16B0, 0x16B0, 0x16B0 },
+{ 0x16B1, 0x16B1, 0x16B1 },
+{ 0x16B2, 0x16B2, 0x16B2 },
+{ 0x16B3, 0x16B3, 0x16B3 },
+{ 0x16B4, 0x16B4, 0x16B4 },
+{ 0x16B5, 0x16B5, 0x16B5 },
+{ 0x16B6, 0x16B6, 0x16B6 },
+{ 0x16B7, 0x16B7, 0x16B7 },
+{ 0x16B8, 0x16B8, 0x16B8 },
+{ 0x16B9, 0x16B9, 0x16B9 },
+{ 0x16BA, 0x16BA, 0x16BA },
+{ 0x16BB, 0x16BB, 0x16BB },
+{ 0x16BC, 0x16BC, 0x16BC },
+{ 0x16BD, 0x16BD, 0x16BD },
+{ 0x16BE, 0x16BE, 0x16BE },
+{ 0x16BF, 0x16BF, 0x16BF },
+{ 0x16C0, 0x16C0, 0x16C0 },
+{ 0x16C1, 0x16C1, 0x16C1 },
+{ 0x16C2, 0x16C2, 0x16C2 },
+{ 0x16C3, 0x16C3, 0x16C3 },
+{ 0x16C4, 0x16C4, 0x16C4 },
+{ 0x16C5, 0x16C5, 0x16C5 },
+{ 0x16C6, 0x16C6, 0x16C6 },
+{ 0x16C7, 0x16C7, 0x16C7 },
+{ 0x16C8, 0x16C8, 0x16C8 },
+{ 0x16C9, 0x16C9, 0x16C9 },
+{ 0x16CA, 0x16CA, 0x16CA },
+{ 0x16CB, 0x16CB, 0x16CB },
+{ 0x16CC, 0x16CC, 0x16CC },
+{ 0x16CD, 0x16CD, 0x16CD },
+{ 0x16CE, 0x16CE, 0x16CE },
+{ 0x16CF, 0x16CF, 0x16CF },
+{ 0x16D0, 0x16D0, 0x16D0 },
+{ 0x16D1, 0x16D1, 0x16D1 },
+{ 0x16D2, 0x16D2, 0x16D2 },
+{ 0x16D3, 0x16D3, 0x16D3 },
+{ 0x16D4, 0x16D4, 0x16D4 },
+{ 0x16D5, 0x16D5, 0x16D5 },
+{ 0x16D6, 0x16D6, 0x16D6 },
+{ 0x16D7, 0x16D7, 0x16D7 },
+{ 0x16D8, 0x16D8, 0x16D8 },
+{ 0x16D9, 0x16D9, 0x16D9 },
+{ 0x16DA, 0x16DA, 0x16DA },
+{ 0x16DB, 0x16DB, 0x16DB },
+{ 0x16DC, 0x16DC, 0x16DC },
+{ 0x16DD, 0x16DD, 0x16DD },
+{ 0x16DE, 0x16DE, 0x16DE },
+{ 0x16DF, 0x16DF, 0x16DF },
+{ 0x16E0, 0x16E0, 0x16E0 },
+{ 0x16E1, 0x16E1, 0x16E1 },
+{ 0x16E2, 0x16E2, 0x16E2 },
+{ 0x16E3, 0x16E3, 0x16E3 },
+{ 0x16E4, 0x16E4, 0x16E4 },
+{ 0x16E5, 0x16E5, 0x16E5 },
+{ 0x16E6, 0x16E6, 0x16E6 },
+{ 0x16E7, 0x16E7, 0x16E7 },
+{ 0x16E8, 0x16E8, 0x16E8 },
+{ 0x16E9, 0x16E9, 0x16E9 },
+{ 0x16EA, 0x16EA, 0x16EA },
+{ 0x1700, 0x1700, 0x1700 },
+{ 0x1701, 0x1701, 0x1701 },
+{ 0x1702, 0x1702, 0x1702 },
+{ 0x1703, 0x1703, 0x1703 },
+{ 0x1704, 0x1704, 0x1704 },
+{ 0x1705, 0x1705, 0x1705 },
+{ 0x1706, 0x1706, 0x1706 },
+{ 0x1707, 0x1707, 0x1707 },
+{ 0x1708, 0x1708, 0x1708 },
+{ 0x1709, 0x1709, 0x1709 },
+{ 0x170A, 0x170A, 0x170A },
+{ 0x170B, 0x170B, 0x170B },
+{ 0x170C, 0x170C, 0x170C },
+{ 0x170E, 0x170E, 0x170E },
+{ 0x170F, 0x170F, 0x170F },
+{ 0x1710, 0x1710, 0x1710 },
+{ 0x1711, 0x1711, 0x1711 },
+{ 0x1712, 0x1712, 0x1712 },
+{ 0x1713, 0x1713, 0x1713 },
+{ 0x1714, 0x1714, 0x1714 },
+{ 0x1720, 0x1720, 0x1720 },
+{ 0x1721, 0x1721, 0x1721 },
+{ 0x1722, 0x1722, 0x1722 },
+{ 0x1723, 0x1723, 0x1723 },
+{ 0x1724, 0x1724, 0x1724 },
+{ 0x1725, 0x1725, 0x1725 },
+{ 0x1726, 0x1726, 0x1726 },
+{ 0x1727, 0x1727, 0x1727 },
+{ 0x1728, 0x1728, 0x1728 },
+{ 0x1729, 0x1729, 0x1729 },
+{ 0x172A, 0x172A, 0x172A },
+{ 0x172B, 0x172B, 0x172B },
+{ 0x172C, 0x172C, 0x172C },
+{ 0x172D, 0x172D, 0x172D },
+{ 0x172E, 0x172E, 0x172E },
+{ 0x172F, 0x172F, 0x172F },
+{ 0x1730, 0x1730, 0x1730 },
+{ 0x1731, 0x1731, 0x1731 },
+{ 0x1732, 0x1732, 0x1732 },
+{ 0x1733, 0x1733, 0x1733 },
+{ 0x1734, 0x1734, 0x1734 },
+{ 0x1740, 0x1740, 0x1740 },
+{ 0x1741, 0x1741, 0x1741 },
+{ 0x1742, 0x1742, 0x1742 },
+{ 0x1743, 0x1743, 0x1743 },
+{ 0x1744, 0x1744, 0x1744 },
+{ 0x1745, 0x1745, 0x1745 },
+{ 0x1746, 0x1746, 0x1746 },
+{ 0x1747, 0x1747, 0x1747 },
+{ 0x1748, 0x1748, 0x1748 },
+{ 0x1749, 0x1749, 0x1749 },
+{ 0x174A, 0x174A, 0x174A },
+{ 0x174B, 0x174B, 0x174B },
+{ 0x174C, 0x174C, 0x174C },
+{ 0x174D, 0x174D, 0x174D },
+{ 0x174E, 0x174E, 0x174E },
+{ 0x174F, 0x174F, 0x174F },
+{ 0x1750, 0x1750, 0x1750 },
+{ 0x1751, 0x1751, 0x1751 },
+{ 0x1752, 0x1752, 0x1752 },
+{ 0x1753, 0x1753, 0x1753 },
+{ 0x1760, 0x1760, 0x1760 },
+{ 0x1761, 0x1761, 0x1761 },
+{ 0x1762, 0x1762, 0x1762 },
+{ 0x1763, 0x1763, 0x1763 },
+{ 0x1764, 0x1764, 0x1764 },
+{ 0x1765, 0x1765, 0x1765 },
+{ 0x1766, 0x1766, 0x1766 },
+{ 0x1767, 0x1767, 0x1767 },
+{ 0x1768, 0x1768, 0x1768 },
+{ 0x1769, 0x1769, 0x1769 },
+{ 0x176A, 0x176A, 0x176A },
+{ 0x176B, 0x176B, 0x176B },
+{ 0x176C, 0x176C, 0x176C },
+{ 0x176E, 0x176E, 0x176E },
+{ 0x176F, 0x176F, 0x176F },
+{ 0x1770, 0x1770, 0x1770 },
+{ 0x1772, 0x1772, 0x1772 },
+{ 0x1773, 0x1773, 0x1773 },
+{ 0x1780, 0x1780, 0x1780 },
+{ 0x1781, 0x1781, 0x1781 },
+{ 0x1782, 0x1782, 0x1782 },
+{ 0x1783, 0x1783, 0x1783 },
+{ 0x1784, 0x1784, 0x1784 },
+{ 0x1785, 0x1785, 0x1785 },
+{ 0x1786, 0x1786, 0x1786 },
+{ 0x1787, 0x1787, 0x1787 },
+{ 0x1788, 0x1788, 0x1788 },
+{ 0x1789, 0x1789, 0x1789 },
+{ 0x178A, 0x178A, 0x178A },
+{ 0x178B, 0x178B, 0x178B },
+{ 0x178C, 0x178C, 0x178C },
+{ 0x178D, 0x178D, 0x178D },
+{ 0x178E, 0x178E, 0x178E },
+{ 0x178F, 0x178F, 0x178F },
+{ 0x1790, 0x1790, 0x1790 },
+{ 0x1791, 0x1791, 0x1791 },
+{ 0x1792, 0x1792, 0x1792 },
+{ 0x1793, 0x1793, 0x1793 },
+{ 0x1794, 0x1794, 0x1794 },
+{ 0x1795, 0x1795, 0x1795 },
+{ 0x1796, 0x1796, 0x1796 },
+{ 0x1797, 0x1797, 0x1797 },
+{ 0x1798, 0x1798, 0x1798 },
+{ 0x1799, 0x1799, 0x1799 },
+{ 0x179A, 0x179A, 0x179A },
+{ 0x179B, 0x179B, 0x179B },
+{ 0x179C, 0x179C, 0x179C },
+{ 0x179D, 0x179D, 0x179D },
+{ 0x179E, 0x179E, 0x179E },
+{ 0x179F, 0x179F, 0x179F },
+{ 0x17A0, 0x17A0, 0x17A0 },
+{ 0x17A1, 0x17A1, 0x17A1 },
+{ 0x17A2, 0x17A2, 0x17A2 },
+{ 0x17A3, 0x17A3, 0x17A3 },
+{ 0x17A4, 0x17A4, 0x17A4 },
+{ 0x17A5, 0x17A5, 0x17A5 },
+{ 0x17A6, 0x17A6, 0x17A6 },
+{ 0x17A7, 0x17A7, 0x17A7 },
+{ 0x17A8, 0x17A8, 0x17A8 },
+{ 0x17A9, 0x17A9, 0x17A9 },
+{ 0x17AA, 0x17AA, 0x17AA },
+{ 0x17AB, 0x17AB, 0x17AB },
+{ 0x17AC, 0x17AC, 0x17AC },
+{ 0x17AD, 0x17AD, 0x17AD },
+{ 0x17AE, 0x17AE, 0x17AE },
+{ 0x17AF, 0x17AF, 0x17AF },
+{ 0x17B0, 0x17B0, 0x17B0 },
+{ 0x17B1, 0x17B1, 0x17B1 },
+{ 0x17B2, 0x17B2, 0x17B2 },
+{ 0x17B3, 0x17B3, 0x17B3 },
+{ 0x17B7, 0x17B7, 0x17B7 },
+{ 0x17B8, 0x17B8, 0x17B8 },
+{ 0x17B9, 0x17B9, 0x17B9 },
+{ 0x17BA, 0x17BA, 0x17BA },
+{ 0x17BB, 0x17BB, 0x17BB },
+{ 0x17BC, 0x17BC, 0x17BC },
+{ 0x17BD, 0x17BD, 0x17BD },
+{ 0x17C6, 0x17C6, 0x17C6 },
+{ 0x17C9, 0x17C9, 0x17C9 },
+{ 0x17CA, 0x17CA, 0x17CA },
+{ 0x17CB, 0x17CB, 0x17CB },
+{ 0x17CC, 0x17CC, 0x17CC },
+{ 0x17CD, 0x17CD, 0x17CD },
+{ 0x17CE, 0x17CE, 0x17CE },
+{ 0x17CF, 0x17CF, 0x17CF },
+{ 0x17D0, 0x17D0, 0x17D0 },
+{ 0x17D1, 0x17D1, 0x17D1 },
+{ 0x17D2, 0x17D2, 0x17D2 },
+{ 0x17D3, 0x17D3, 0x17D3 },
+{ 0x17D7, 0x17D7, 0x17D7 },
+{ 0x17DC, 0x17DC, 0x17DC },
+{ 0x17DD, 0x17DD, 0x17DD },
+{ 0x180B, 0x180B, 0x180B },
+{ 0x180C, 0x180C, 0x180C },
+{ 0x180D, 0x180D, 0x180D },
+{ 0x1820, 0x1820, 0x1820 },
+{ 0x1821, 0x1821, 0x1821 },
+{ 0x1822, 0x1822, 0x1822 },
+{ 0x1823, 0x1823, 0x1823 },
+{ 0x1824, 0x1824, 0x1824 },
+{ 0x1825, 0x1825, 0x1825 },
+{ 0x1826, 0x1826, 0x1826 },
+{ 0x1827, 0x1827, 0x1827 },
+{ 0x1828, 0x1828, 0x1828 },
+{ 0x1829, 0x1829, 0x1829 },
+{ 0x182A, 0x182A, 0x182A },
+{ 0x182B, 0x182B, 0x182B },
+{ 0x182C, 0x182C, 0x182C },
+{ 0x182D, 0x182D, 0x182D },
+{ 0x182E, 0x182E, 0x182E },
+{ 0x182F, 0x182F, 0x182F },
+{ 0x1830, 0x1830, 0x1830 },
+{ 0x1831, 0x1831, 0x1831 },
+{ 0x1832, 0x1832, 0x1832 },
+{ 0x1833, 0x1833, 0x1833 },
+{ 0x1834, 0x1834, 0x1834 },
+{ 0x1835, 0x1835, 0x1835 },
+{ 0x1836, 0x1836, 0x1836 },
+{ 0x1837, 0x1837, 0x1837 },
+{ 0x1838, 0x1838, 0x1838 },
+{ 0x1839, 0x1839, 0x1839 },
+{ 0x183A, 0x183A, 0x183A },
+{ 0x183B, 0x183B, 0x183B },
+{ 0x183C, 0x183C, 0x183C },
+{ 0x183D, 0x183D, 0x183D },
+{ 0x183E, 0x183E, 0x183E },
+{ 0x183F, 0x183F, 0x183F },
+{ 0x1840, 0x1840, 0x1840 },
+{ 0x1841, 0x1841, 0x1841 },
+{ 0x1842, 0x1842, 0x1842 },
+{ 0x1843, 0x1843, 0x1843 },
+{ 0x1844, 0x1844, 0x1844 },
+{ 0x1845, 0x1845, 0x1845 },
+{ 0x1846, 0x1846, 0x1846 },
+{ 0x1847, 0x1847, 0x1847 },
+{ 0x1848, 0x1848, 0x1848 },
+{ 0x1849, 0x1849, 0x1849 },
+{ 0x184A, 0x184A, 0x184A },
+{ 0x184B, 0x184B, 0x184B },
+{ 0x184C, 0x184C, 0x184C },
+{ 0x184D, 0x184D, 0x184D },
+{ 0x184E, 0x184E, 0x184E },
+{ 0x184F, 0x184F, 0x184F },
+{ 0x1850, 0x1850, 0x1850 },
+{ 0x1851, 0x1851, 0x1851 },
+{ 0x1852, 0x1852, 0x1852 },
+{ 0x1853, 0x1853, 0x1853 },
+{ 0x1854, 0x1854, 0x1854 },
+{ 0x1855, 0x1855, 0x1855 },
+{ 0x1856, 0x1856, 0x1856 },
+{ 0x1857, 0x1857, 0x1857 },
+{ 0x1858, 0x1858, 0x1858 },
+{ 0x1859, 0x1859, 0x1859 },
+{ 0x185A, 0x185A, 0x185A },
+{ 0x185B, 0x185B, 0x185B },
+{ 0x185C, 0x185C, 0x185C },
+{ 0x185D, 0x185D, 0x185D },
+{ 0x185E, 0x185E, 0x185E },
+{ 0x185F, 0x185F, 0x185F },
+{ 0x1860, 0x1860, 0x1860 },
+{ 0x1861, 0x1861, 0x1861 },
+{ 0x1862, 0x1862, 0x1862 },
+{ 0x1863, 0x1863, 0x1863 },
+{ 0x1864, 0x1864, 0x1864 },
+{ 0x1865, 0x1865, 0x1865 },
+{ 0x1866, 0x1866, 0x1866 },
+{ 0x1867, 0x1867, 0x1867 },
+{ 0x1868, 0x1868, 0x1868 },
+{ 0x1869, 0x1869, 0x1869 },
+{ 0x186A, 0x186A, 0x186A },
+{ 0x186B, 0x186B, 0x186B },
+{ 0x186C, 0x186C, 0x186C },
+{ 0x186D, 0x186D, 0x186D },
+{ 0x186E, 0x186E, 0x186E },
+{ 0x186F, 0x186F, 0x186F },
+{ 0x1870, 0x1870, 0x1870 },
+{ 0x1871, 0x1871, 0x1871 },
+{ 0x1872, 0x1872, 0x1872 },
+{ 0x1873, 0x1873, 0x1873 },
+{ 0x1874, 0x1874, 0x1874 },
+{ 0x1875, 0x1875, 0x1875 },
+{ 0x1876, 0x1876, 0x1876 },
+{ 0x1877, 0x1877, 0x1877 },
+{ 0x1880, 0x1880, 0x1880 },
+{ 0x1881, 0x1881, 0x1881 },
+{ 0x1882, 0x1882, 0x1882 },
+{ 0x1883, 0x1883, 0x1883 },
+{ 0x1884, 0x1884, 0x1884 },
+{ 0x1885, 0x1885, 0x1885 },
+{ 0x1886, 0x1886, 0x1886 },
+{ 0x1887, 0x1887, 0x1887 },
+{ 0x1888, 0x1888, 0x1888 },
+{ 0x1889, 0x1889, 0x1889 },
+{ 0x188A, 0x188A, 0x188A },
+{ 0x188B, 0x188B, 0x188B },
+{ 0x188C, 0x188C, 0x188C },
+{ 0x188D, 0x188D, 0x188D },
+{ 0x188E, 0x188E, 0x188E },
+{ 0x188F, 0x188F, 0x188F },
+{ 0x1890, 0x1890, 0x1890 },
+{ 0x1891, 0x1891, 0x1891 },
+{ 0x1892, 0x1892, 0x1892 },
+{ 0x1893, 0x1893, 0x1893 },
+{ 0x1894, 0x1894, 0x1894 },
+{ 0x1895, 0x1895, 0x1895 },
+{ 0x1896, 0x1896, 0x1896 },
+{ 0x1897, 0x1897, 0x1897 },
+{ 0x1898, 0x1898, 0x1898 },
+{ 0x1899, 0x1899, 0x1899 },
+{ 0x189A, 0x189A, 0x189A },
+{ 0x189B, 0x189B, 0x189B },
+{ 0x189C, 0x189C, 0x189C },
+{ 0x189D, 0x189D, 0x189D },
+{ 0x189E, 0x189E, 0x189E },
+{ 0x189F, 0x189F, 0x189F },
+{ 0x18A0, 0x18A0, 0x18A0 },
+{ 0x18A1, 0x18A1, 0x18A1 },
+{ 0x18A2, 0x18A2, 0x18A2 },
+{ 0x18A3, 0x18A3, 0x18A3 },
+{ 0x18A4, 0x18A4, 0x18A4 },
+{ 0x18A5, 0x18A5, 0x18A5 },
+{ 0x18A6, 0x18A6, 0x18A6 },
+{ 0x18A7, 0x18A7, 0x18A7 },
+{ 0x18A8, 0x18A8, 0x18A8 },
+{ 0x18A9, 0x18A9, 0x18A9 },
+{ 0x1900, 0x1900, 0x1900 },
+{ 0x1901, 0x1901, 0x1901 },
+{ 0x1902, 0x1902, 0x1902 },
+{ 0x1903, 0x1903, 0x1903 },
+{ 0x1904, 0x1904, 0x1904 },
+{ 0x1905, 0x1905, 0x1905 },
+{ 0x1906, 0x1906, 0x1906 },
+{ 0x1907, 0x1907, 0x1907 },
+{ 0x1908, 0x1908, 0x1908 },
+{ 0x1909, 0x1909, 0x1909 },
+{ 0x190A, 0x190A, 0x190A },
+{ 0x190B, 0x190B, 0x190B },
+{ 0x190C, 0x190C, 0x190C },
+{ 0x190D, 0x190D, 0x190D },
+{ 0x190E, 0x190E, 0x190E },
+{ 0x190F, 0x190F, 0x190F },
+{ 0x1910, 0x1910, 0x1910 },
+{ 0x1911, 0x1911, 0x1911 },
+{ 0x1912, 0x1912, 0x1912 },
+{ 0x1913, 0x1913, 0x1913 },
+{ 0x1914, 0x1914, 0x1914 },
+{ 0x1915, 0x1915, 0x1915 },
+{ 0x1916, 0x1916, 0x1916 },
+{ 0x1917, 0x1917, 0x1917 },
+{ 0x1918, 0x1918, 0x1918 },
+{ 0x1919, 0x1919, 0x1919 },
+{ 0x191A, 0x191A, 0x191A },
+{ 0x191B, 0x191B, 0x191B },
+{ 0x191C, 0x191C, 0x191C },
+{ 0x1920, 0x1920, 0x1920 },
+{ 0x1921, 0x1921, 0x1921 },
+{ 0x1922, 0x1922, 0x1922 },
+{ 0x1927, 0x1927, 0x1927 },
+{ 0x1928, 0x1928, 0x1928 },
+{ 0x1932, 0x1932, 0x1932 },
+{ 0x1939, 0x1939, 0x1939 },
+{ 0x193A, 0x193A, 0x193A },
+{ 0x193B, 0x193B, 0x193B },
+{ 0x1950, 0x1950, 0x1950 },
+{ 0x1951, 0x1951, 0x1951 },
+{ 0x1952, 0x1952, 0x1952 },
+{ 0x1953, 0x1953, 0x1953 },
+{ 0x1954, 0x1954, 0x1954 },
+{ 0x1955, 0x1955, 0x1955 },
+{ 0x1956, 0x1956, 0x1956 },
+{ 0x1957, 0x1957, 0x1957 },
+{ 0x1958, 0x1958, 0x1958 },
+{ 0x1959, 0x1959, 0x1959 },
+{ 0x195A, 0x195A, 0x195A },
+{ 0x195B, 0x195B, 0x195B },
+{ 0x195C, 0x195C, 0x195C },
+{ 0x195D, 0x195D, 0x195D },
+{ 0x195E, 0x195E, 0x195E },
+{ 0x195F, 0x195F, 0x195F },
+{ 0x1960, 0x1960, 0x1960 },
+{ 0x1961, 0x1961, 0x1961 },
+{ 0x1962, 0x1962, 0x1962 },
+{ 0x1963, 0x1963, 0x1963 },
+{ 0x1964, 0x1964, 0x1964 },
+{ 0x1965, 0x1965, 0x1965 },
+{ 0x1966, 0x1966, 0x1966 },
+{ 0x1967, 0x1967, 0x1967 },
+{ 0x1968, 0x1968, 0x1968 },
+{ 0x1969, 0x1969, 0x1969 },
+{ 0x196A, 0x196A, 0x196A },
+{ 0x196B, 0x196B, 0x196B },
+{ 0x196C, 0x196C, 0x196C },
+{ 0x196D, 0x196D, 0x196D },
+{ 0x1970, 0x1970, 0x1970 },
+{ 0x1971, 0x1971, 0x1971 },
+{ 0x1972, 0x1972, 0x1972 },
+{ 0x1973, 0x1973, 0x1973 },
+{ 0x1974, 0x1974, 0x1974 },
+{ 0x1980, 0x1980, 0x1980 },
+{ 0x1981, 0x1981, 0x1981 },
+{ 0x1982, 0x1982, 0x1982 },
+{ 0x1983, 0x1983, 0x1983 },
+{ 0x1984, 0x1984, 0x1984 },
+{ 0x1985, 0x1985, 0x1985 },
+{ 0x1986, 0x1986, 0x1986 },
+{ 0x1987, 0x1987, 0x1987 },
+{ 0x1988, 0x1988, 0x1988 },
+{ 0x1989, 0x1989, 0x1989 },
+{ 0x198A, 0x198A, 0x198A },
+{ 0x198B, 0x198B, 0x198B },
+{ 0x198C, 0x198C, 0x198C },
+{ 0x198D, 0x198D, 0x198D },
+{ 0x198E, 0x198E, 0x198E },
+{ 0x198F, 0x198F, 0x198F },
+{ 0x1990, 0x1990, 0x1990 },
+{ 0x1991, 0x1991, 0x1991 },
+{ 0x1992, 0x1992, 0x1992 },
+{ 0x1993, 0x1993, 0x1993 },
+{ 0x1994, 0x1994, 0x1994 },
+{ 0x1995, 0x1995, 0x1995 },
+{ 0x1996, 0x1996, 0x1996 },
+{ 0x1997, 0x1997, 0x1997 },
+{ 0x1998, 0x1998, 0x1998 },
+{ 0x1999, 0x1999, 0x1999 },
+{ 0x199A, 0x199A, 0x199A },
+{ 0x199B, 0x199B, 0x199B },
+{ 0x199C, 0x199C, 0x199C },
+{ 0x199D, 0x199D, 0x199D },
+{ 0x199E, 0x199E, 0x199E },
+{ 0x199F, 0x199F, 0x199F },
+{ 0x19A0, 0x19A0, 0x19A0 },
+{ 0x19A1, 0x19A1, 0x19A1 },
+{ 0x19A2, 0x19A2, 0x19A2 },
+{ 0x19A3, 0x19A3, 0x19A3 },
+{ 0x19A4, 0x19A4, 0x19A4 },
+{ 0x19A5, 0x19A5, 0x19A5 },
+{ 0x19A6, 0x19A6, 0x19A6 },
+{ 0x19A7, 0x19A7, 0x19A7 },
+{ 0x19A8, 0x19A8, 0x19A8 },
+{ 0x19A9, 0x19A9, 0x19A9 },
+{ 0x19C1, 0x19C1, 0x19C1 },
+{ 0x19C2, 0x19C2, 0x19C2 },
+{ 0x19C3, 0x19C3, 0x19C3 },
+{ 0x19C4, 0x19C4, 0x19C4 },
+{ 0x19C5, 0x19C5, 0x19C5 },
+{ 0x19C6, 0x19C6, 0x19C6 },
+{ 0x19C7, 0x19C7, 0x19C7 },
+{ 0x1A00, 0x1A00, 0x1A00 },
+{ 0x1A01, 0x1A01, 0x1A01 },
+{ 0x1A02, 0x1A02, 0x1A02 },
+{ 0x1A03, 0x1A03, 0x1A03 },
+{ 0x1A04, 0x1A04, 0x1A04 },
+{ 0x1A05, 0x1A05, 0x1A05 },
+{ 0x1A06, 0x1A06, 0x1A06 },
+{ 0x1A07, 0x1A07, 0x1A07 },
+{ 0x1A08, 0x1A08, 0x1A08 },
+{ 0x1A09, 0x1A09, 0x1A09 },
+{ 0x1A0A, 0x1A0A, 0x1A0A },
+{ 0x1A0B, 0x1A0B, 0x1A0B },
+{ 0x1A0C, 0x1A0C, 0x1A0C },
+{ 0x1A0D, 0x1A0D, 0x1A0D },
+{ 0x1A0E, 0x1A0E, 0x1A0E },
+{ 0x1A0F, 0x1A0F, 0x1A0F },
+{ 0x1A10, 0x1A10, 0x1A10 },
+{ 0x1A11, 0x1A11, 0x1A11 },
+{ 0x1A12, 0x1A12, 0x1A12 },
+{ 0x1A13, 0x1A13, 0x1A13 },
+{ 0x1A14, 0x1A14, 0x1A14 },
+{ 0x1A15, 0x1A15, 0x1A15 },
+{ 0x1A16, 0x1A16, 0x1A16 },
+{ 0x1A17, 0x1A17, 0x1A17 },
+{ 0x1A18, 0x1A18, 0x1A18 },
+{ 0x1D00, 0x1D00, 0x1D00 },
+{ 0x1D01, 0x1D01, 0x1D01 },
+{ 0x1D02, 0x1D02, 0x1D02 },
+{ 0x1D03, 0x1D03, 0x1D03 },
+{ 0x1D04, 0x1D04, 0x1D04 },
+{ 0x1D05, 0x1D05, 0x1D05 },
+{ 0x1D06, 0x1D06, 0x1D06 },
+{ 0x1D07, 0x1D07, 0x1D07 },
+{ 0x1D08, 0x1D08, 0x1D08 },
+{ 0x1D09, 0x1D09, 0x1D09 },
+{ 0x1D0A, 0x1D0A, 0x1D0A },
+{ 0x1D0B, 0x1D0B, 0x1D0B },
+{ 0x1D0C, 0x1D0C, 0x1D0C },
+{ 0x1D0D, 0x1D0D, 0x1D0D },
+{ 0x1D0E, 0x1D0E, 0x1D0E },
+{ 0x1D0F, 0x1D0F, 0x1D0F },
+{ 0x1D10, 0x1D10, 0x1D10 },
+{ 0x1D11, 0x1D11, 0x1D11 },
+{ 0x1D12, 0x1D12, 0x1D12 },
+{ 0x1D13, 0x1D13, 0x1D13 },
+{ 0x1D14, 0x1D14, 0x1D14 },
+{ 0x1D15, 0x1D15, 0x1D15 },
+{ 0x1D16, 0x1D16, 0x1D16 },
+{ 0x1D17, 0x1D17, 0x1D17 },
+{ 0x1D18, 0x1D18, 0x1D18 },
+{ 0x1D19, 0x1D19, 0x1D19 },
+{ 0x1D1A, 0x1D1A, 0x1D1A },
+{ 0x1D1B, 0x1D1B, 0x1D1B },
+{ 0x1D1C, 0x1D1C, 0x1D1C },
+{ 0x1D1D, 0x1D1D, 0x1D1D },
+{ 0x1D1E, 0x1D1E, 0x1D1E },
+{ 0x1D1F, 0x1D1F, 0x1D1F },
+{ 0x1D20, 0x1D20, 0x1D20 },
+{ 0x1D21, 0x1D21, 0x1D21 },
+{ 0x1D22, 0x1D22, 0x1D22 },
+{ 0x1D23, 0x1D23, 0x1D23 },
+{ 0x1D24, 0x1D24, 0x1D24 },
+{ 0x1D25, 0x1D25, 0x1D25 },
+{ 0x1D26, 0x1D26, 0x1D26 },
+{ 0x1D27, 0x1D27, 0x1D27 },
+{ 0x1D28, 0x1D28, 0x1D28 },
+{ 0x1D29, 0x1D29, 0x1D29 },
+{ 0x1D2A, 0x1D2A, 0x1D2A },
+{ 0x1D2B, 0x1D2B, 0x1D2B },
+{ 0x1D2C, 0x1D2C, 0x1D2C },
+{ 0x1D2D, 0x1D2D, 0x1D2D },
+{ 0x1D2E, 0x1D2E, 0x1D2E },
+{ 0x1D2F, 0x1D2F, 0x1D2F },
+{ 0x1D30, 0x1D30, 0x1D30 },
+{ 0x1D31, 0x1D31, 0x1D31 },
+{ 0x1D32, 0x1D32, 0x1D32 },
+{ 0x1D33, 0x1D33, 0x1D33 },
+{ 0x1D34, 0x1D34, 0x1D34 },
+{ 0x1D35, 0x1D35, 0x1D35 },
+{ 0x1D36, 0x1D36, 0x1D36 },
+{ 0x1D37, 0x1D37, 0x1D37 },
+{ 0x1D38, 0x1D38, 0x1D38 },
+{ 0x1D39, 0x1D39, 0x1D39 },
+{ 0x1D3A, 0x1D3A, 0x1D3A },
+{ 0x1D3B, 0x1D3B, 0x1D3B },
+{ 0x1D3C, 0x1D3C, 0x1D3C },
+{ 0x1D3D, 0x1D3D, 0x1D3D },
+{ 0x1D3E, 0x1D3E, 0x1D3E },
+{ 0x1D3F, 0x1D3F, 0x1D3F },
+{ 0x1D40, 0x1D40, 0x1D40 },
+{ 0x1D41, 0x1D41, 0x1D41 },
+{ 0x1D42, 0x1D42, 0x1D42 },
+{ 0x1D43, 0x1D43, 0x1D43 },
+{ 0x1D44, 0x1D44, 0x1D44 },
+{ 0x1D45, 0x1D45, 0x1D45 },
+{ 0x1D46, 0x1D46, 0x1D46 },
+{ 0x1D47, 0x1D47, 0x1D47 },
+{ 0x1D48, 0x1D48, 0x1D48 },
+{ 0x1D49, 0x1D49, 0x1D49 },
+{ 0x1D4A, 0x1D4A, 0x1D4A },
+{ 0x1D4B, 0x1D4B, 0x1D4B },
+{ 0x1D4C, 0x1D4C, 0x1D4C },
+{ 0x1D4D, 0x1D4D, 0x1D4D },
+{ 0x1D4E, 0x1D4E, 0x1D4E },
+{ 0x1D4F, 0x1D4F, 0x1D4F },
+{ 0x1D50, 0x1D50, 0x1D50 },
+{ 0x1D51, 0x1D51, 0x1D51 },
+{ 0x1D52, 0x1D52, 0x1D52 },
+{ 0x1D53, 0x1D53, 0x1D53 },
+{ 0x1D54, 0x1D54, 0x1D54 },
+{ 0x1D55, 0x1D55, 0x1D55 },
+{ 0x1D56, 0x1D56, 0x1D56 },
+{ 0x1D57, 0x1D57, 0x1D57 },
+{ 0x1D58, 0x1D58, 0x1D58 },
+{ 0x1D59, 0x1D59, 0x1D59 },
+{ 0x1D5A, 0x1D5A, 0x1D5A },
+{ 0x1D5B, 0x1D5B, 0x1D5B },
+{ 0x1D5C, 0x1D5C, 0x1D5C },
+{ 0x1D5D, 0x1D5D, 0x1D5D },
+{ 0x1D5E, 0x1D5E, 0x1D5E },
+{ 0x1D5F, 0x1D5F, 0x1D5F },
+{ 0x1D60, 0x1D60, 0x1D60 },
+{ 0x1D61, 0x1D61, 0x1D61 },
+{ 0x1D62, 0x1D62, 0x1D62 },
+{ 0x1D63, 0x1D63, 0x1D63 },
+{ 0x1D64, 0x1D64, 0x1D64 },
+{ 0x1D65, 0x1D65, 0x1D65 },
+{ 0x1D66, 0x1D66, 0x1D66 },
+{ 0x1D67, 0x1D67, 0x1D67 },
+{ 0x1D68, 0x1D68, 0x1D68 },
+{ 0x1D69, 0x1D69, 0x1D69 },
+{ 0x1D6A, 0x1D6A, 0x1D6A },
+{ 0x1D6B, 0x1D6B, 0x1D6B },
+{ 0x1D6C, 0x1D6C, 0x1D6C },
+{ 0x1D6D, 0x1D6D, 0x1D6D },
+{ 0x1D6E, 0x1D6E, 0x1D6E },
+{ 0x1D6F, 0x1D6F, 0x1D6F },
+{ 0x1D70, 0x1D70, 0x1D70 },
+{ 0x1D71, 0x1D71, 0x1D71 },
+{ 0x1D72, 0x1D72, 0x1D72 },
+{ 0x1D73, 0x1D73, 0x1D73 },
+{ 0x1D74, 0x1D74, 0x1D74 },
+{ 0x1D75, 0x1D75, 0x1D75 },
+{ 0x1D76, 0x1D76, 0x1D76 },
+{ 0x1D77, 0x1D77, 0x1D77 },
+{ 0x1D78, 0x1D78, 0x1D78 },
+{ 0x1D79, 0x1D79, 0x1D79 },
+{ 0x1D7A, 0x1D7A, 0x1D7A },
+{ 0x1D7B, 0x1D7B, 0x1D7B },
+{ 0x1D7C, 0x1D7C, 0x1D7C },
+{ 0x1D7D, 0x1D7D, 0x1D7D },
+{ 0x1D7E, 0x1D7E, 0x1D7E },
+{ 0x1D7F, 0x1D7F, 0x1D7F },
+{ 0x1D80, 0x1D80, 0x1D80 },
+{ 0x1D81, 0x1D81, 0x1D81 },
+{ 0x1D82, 0x1D82, 0x1D82 },
+{ 0x1D83, 0x1D83, 0x1D83 },
+{ 0x1D84, 0x1D84, 0x1D84 },
+{ 0x1D85, 0x1D85, 0x1D85 },
+{ 0x1D86, 0x1D86, 0x1D86 },
+{ 0x1D87, 0x1D87, 0x1D87 },
+{ 0x1D88, 0x1D88, 0x1D88 },
+{ 0x1D89, 0x1D89, 0x1D89 },
+{ 0x1D8A, 0x1D8A, 0x1D8A },
+{ 0x1D8B, 0x1D8B, 0x1D8B },
+{ 0x1D8C, 0x1D8C, 0x1D8C },
+{ 0x1D8D, 0x1D8D, 0x1D8D },
+{ 0x1D8E, 0x1D8E, 0x1D8E },
+{ 0x1D8F, 0x1D8F, 0x1D8F },
+{ 0x1D90, 0x1D90, 0x1D90 },
+{ 0x1D91, 0x1D91, 0x1D91 },
+{ 0x1D92, 0x1D92, 0x1D92 },
+{ 0x1D93, 0x1D93, 0x1D93 },
+{ 0x1D94, 0x1D94, 0x1D94 },
+{ 0x1D95, 0x1D95, 0x1D95 },
+{ 0x1D96, 0x1D96, 0x1D96 },
+{ 0x1D97, 0x1D97, 0x1D97 },
+{ 0x1D98, 0x1D98, 0x1D98 },
+{ 0x1D99, 0x1D99, 0x1D99 },
+{ 0x1D9A, 0x1D9A, 0x1D9A },
+{ 0x1D9B, 0x1D9B, 0x1D9B },
+{ 0x1D9C, 0x1D9C, 0x1D9C },
+{ 0x1D9D, 0x1D9D, 0x1D9D },
+{ 0x1D9E, 0x1D9E, 0x1D9E },
+{ 0x1D9F, 0x1D9F, 0x1D9F },
+{ 0x1DA0, 0x1DA0, 0x1DA0 },
+{ 0x1DA1, 0x1DA1, 0x1DA1 },
+{ 0x1DA2, 0x1DA2, 0x1DA2 },
+{ 0x1DA3, 0x1DA3, 0x1DA3 },
+{ 0x1DA4, 0x1DA4, 0x1DA4 },
+{ 0x1DA5, 0x1DA5, 0x1DA5 },
+{ 0x1DA6, 0x1DA6, 0x1DA6 },
+{ 0x1DA7, 0x1DA7, 0x1DA7 },
+{ 0x1DA8, 0x1DA8, 0x1DA8 },
+{ 0x1DA9, 0x1DA9, 0x1DA9 },
+{ 0x1DAA, 0x1DAA, 0x1DAA },
+{ 0x1DAB, 0x1DAB, 0x1DAB },
+{ 0x1DAC, 0x1DAC, 0x1DAC },
+{ 0x1DAD, 0x1DAD, 0x1DAD },
+{ 0x1DAE, 0x1DAE, 0x1DAE },
+{ 0x1DAF, 0x1DAF, 0x1DAF },
+{ 0x1DB0, 0x1DB0, 0x1DB0 },
+{ 0x1DB1, 0x1DB1, 0x1DB1 },
+{ 0x1DB2, 0x1DB2, 0x1DB2 },
+{ 0x1DB3, 0x1DB3, 0x1DB3 },
+{ 0x1DB4, 0x1DB4, 0x1DB4 },
+{ 0x1DB5, 0x1DB5, 0x1DB5 },
+{ 0x1DB6, 0x1DB6, 0x1DB6 },
+{ 0x1DB7, 0x1DB7, 0x1DB7 },
+{ 0x1DB8, 0x1DB8, 0x1DB8 },
+{ 0x1DB9, 0x1DB9, 0x1DB9 },
+{ 0x1DBA, 0x1DBA, 0x1DBA },
+{ 0x1DBB, 0x1DBB, 0x1DBB },
+{ 0x1DBC, 0x1DBC, 0x1DBC },
+{ 0x1DBD, 0x1DBD, 0x1DBD },
+{ 0x1DBE, 0x1DBE, 0x1DBE },
+{ 0x1DBF, 0x1DBF, 0x1DBF },
+{ 0x1DC0, 0x1DC0, 0x1DC0 },
+{ 0x1DC1, 0x1DC1, 0x1DC1 },
+{ 0x1DC2, 0x1DC2, 0x1DC2 },
+{ 0x1DC3, 0x1DC3, 0x1DC3 },
+{ 0x1E00, 0x1E00, 0x1E01 },
+{ 0x1E01, 0x1E00, 0x1E01 },
+{ 0x1E02, 0x1E02, 0x1E03 },
+{ 0x1E03, 0x1E02, 0x1E03 },
+{ 0x1E04, 0x1E04, 0x1E05 },
+{ 0x1E05, 0x1E04, 0x1E05 },
+{ 0x1E06, 0x1E06, 0x1E07 },
+{ 0x1E07, 0x1E06, 0x1E07 },
+{ 0x1E08, 0x1E08, 0x1E09 },
+{ 0x1E09, 0x1E08, 0x1E09 },
+{ 0x1E0A, 0x1E0A, 0x1E0B },
+{ 0x1E0B, 0x1E0A, 0x1E0B },
+{ 0x1E0C, 0x1E0C, 0x1E0D },
+{ 0x1E0D, 0x1E0C, 0x1E0D },
+{ 0x1E0E, 0x1E0E, 0x1E0F },
+{ 0x1E0F, 0x1E0E, 0x1E0F },
+{ 0x1E10, 0x1E10, 0x1E11 },
+{ 0x1E11, 0x1E10, 0x1E11 },
+{ 0x1E12, 0x1E12, 0x1E13 },
+{ 0x1E13, 0x1E12, 0x1E13 },
+{ 0x1E14, 0x1E14, 0x1E15 },
+{ 0x1E15, 0x1E14, 0x1E15 },
+{ 0x1E16, 0x1E16, 0x1E17 },
+{ 0x1E17, 0x1E16, 0x1E17 },
+{ 0x1E18, 0x1E18, 0x1E19 },
+{ 0x1E19, 0x1E18, 0x1E19 },
+{ 0x1E1A, 0x1E1A, 0x1E1B },
+{ 0x1E1B, 0x1E1A, 0x1E1B },
+{ 0x1E1C, 0x1E1C, 0x1E1D },
+{ 0x1E1D, 0x1E1C, 0x1E1D },
+{ 0x1E1E, 0x1E1E, 0x1E1F },
+{ 0x1E1F, 0x1E1E, 0x1E1F },
+{ 0x1E20, 0x1E20, 0x1E21 },
+{ 0x1E21, 0x1E20, 0x1E21 },
+{ 0x1E22, 0x1E22, 0x1E23 },
+{ 0x1E23, 0x1E22, 0x1E23 },
+{ 0x1E24, 0x1E24, 0x1E25 },
+{ 0x1E25, 0x1E24, 0x1E25 },
+{ 0x1E26, 0x1E26, 0x1E27 },
+{ 0x1E27, 0x1E26, 0x1E27 },
+{ 0x1E28, 0x1E28, 0x1E29 },
+{ 0x1E29, 0x1E28, 0x1E29 },
+{ 0x1E2A, 0x1E2A, 0x1E2B },
+{ 0x1E2B, 0x1E2A, 0x1E2B },
+{ 0x1E2C, 0x1E2C, 0x1E2D },
+{ 0x1E2D, 0x1E2C, 0x1E2D },
+{ 0x1E2E, 0x1E2E, 0x1E2F },
+{ 0x1E2F, 0x1E2E, 0x1E2F },
+{ 0x1E30, 0x1E30, 0x1E31 },
+{ 0x1E31, 0x1E30, 0x1E31 },
+{ 0x1E32, 0x1E32, 0x1E33 },
+{ 0x1E33, 0x1E32, 0x1E33 },
+{ 0x1E34, 0x1E34, 0x1E35 },
+{ 0x1E35, 0x1E34, 0x1E35 },
+{ 0x1E36, 0x1E36, 0x1E37 },
+{ 0x1E37, 0x1E36, 0x1E37 },
+{ 0x1E38, 0x1E38, 0x1E39 },
+{ 0x1E39, 0x1E38, 0x1E39 },
+{ 0x1E3A, 0x1E3A, 0x1E3B },
+{ 0x1E3B, 0x1E3A, 0x1E3B },
+{ 0x1E3C, 0x1E3C, 0x1E3D },
+{ 0x1E3D, 0x1E3C, 0x1E3D },
+{ 0x1E3E, 0x1E3E, 0x1E3F },
+{ 0x1E3F, 0x1E3E, 0x1E3F },
+{ 0x1E40, 0x1E40, 0x1E41 },
+{ 0x1E41, 0x1E40, 0x1E41 },
+{ 0x1E42, 0x1E42, 0x1E43 },
+{ 0x1E43, 0x1E42, 0x1E43 },
+{ 0x1E44, 0x1E44, 0x1E45 },
+{ 0x1E45, 0x1E44, 0x1E45 },
+{ 0x1E46, 0x1E46, 0x1E47 },
+{ 0x1E47, 0x1E46, 0x1E47 },
+{ 0x1E48, 0x1E48, 0x1E49 },
+{ 0x1E49, 0x1E48, 0x1E49 },
+{ 0x1E4A, 0x1E4A, 0x1E4B },
+{ 0x1E4B, 0x1E4A, 0x1E4B },
+{ 0x1E4C, 0x1E4C, 0x1E4D },
+{ 0x1E4D, 0x1E4C, 0x1E4D },
+{ 0x1E4E, 0x1E4E, 0x1E4F },
+{ 0x1E4F, 0x1E4E, 0x1E4F },
+{ 0x1E50, 0x1E50, 0x1E51 },
+{ 0x1E51, 0x1E50, 0x1E51 },
+{ 0x1E52, 0x1E52, 0x1E53 },
+{ 0x1E53, 0x1E52, 0x1E53 },
+{ 0x1E54, 0x1E54, 0x1E55 },
+{ 0x1E55, 0x1E54, 0x1E55 },
+{ 0x1E56, 0x1E56, 0x1E57 },
+{ 0x1E57, 0x1E56, 0x1E57 },
+{ 0x1E58, 0x1E58, 0x1E59 },
+{ 0x1E59, 0x1E58, 0x1E59 },
+{ 0x1E5A, 0x1E5A, 0x1E5B },
+{ 0x1E5B, 0x1E5A, 0x1E5B },
+{ 0x1E5C, 0x1E5C, 0x1E5D },
+{ 0x1E5D, 0x1E5C, 0x1E5D },
+{ 0x1E5E, 0x1E5E, 0x1E5F },
+{ 0x1E5F, 0x1E5E, 0x1E5F },
+{ 0x1E60, 0x1E60, 0x1E61 },
+{ 0x1E61, 0x1E60, 0x1E61 },
+{ 0x1E62, 0x1E62, 0x1E63 },
+{ 0x1E63, 0x1E62, 0x1E63 },
+{ 0x1E64, 0x1E64, 0x1E65 },
+{ 0x1E65, 0x1E64, 0x1E65 },
+{ 0x1E66, 0x1E66, 0x1E67 },
+{ 0x1E67, 0x1E66, 0x1E67 },
+{ 0x1E68, 0x1E68, 0x1E69 },
+{ 0x1E69, 0x1E68, 0x1E69 },
+{ 0x1E6A, 0x1E6A, 0x1E6B },
+{ 0x1E6B, 0x1E6A, 0x1E6B },
+{ 0x1E6C, 0x1E6C, 0x1E6D },
+{ 0x1E6D, 0x1E6C, 0x1E6D },
+{ 0x1E6E, 0x1E6E, 0x1E6F },
+{ 0x1E6F, 0x1E6E, 0x1E6F },
+{ 0x1E70, 0x1E70, 0x1E71 },
+{ 0x1E71, 0x1E70, 0x1E71 },
+{ 0x1E72, 0x1E72, 0x1E73 },
+{ 0x1E73, 0x1E72, 0x1E73 },
+{ 0x1E74, 0x1E74, 0x1E75 },
+{ 0x1E75, 0x1E74, 0x1E75 },
+{ 0x1E76, 0x1E76, 0x1E77 },
+{ 0x1E77, 0x1E76, 0x1E77 },
+{ 0x1E78, 0x1E78, 0x1E79 },
+{ 0x1E79, 0x1E78, 0x1E79 },
+{ 0x1E7A, 0x1E7A, 0x1E7B },
+{ 0x1E7B, 0x1E7A, 0x1E7B },
+{ 0x1E7C, 0x1E7C, 0x1E7D },
+{ 0x1E7D, 0x1E7C, 0x1E7D },
+{ 0x1E7E, 0x1E7E, 0x1E7F },
+{ 0x1E7F, 0x1E7E, 0x1E7F },
+{ 0x1E80, 0x1E80, 0x1E81 },
+{ 0x1E81, 0x1E80, 0x1E81 },
+{ 0x1E82, 0x1E82, 0x1E83 },
+{ 0x1E83, 0x1E82, 0x1E83 },
+{ 0x1E84, 0x1E84, 0x1E85 },
+{ 0x1E85, 0x1E84, 0x1E85 },
+{ 0x1E86, 0x1E86, 0x1E87 },
+{ 0x1E87, 0x1E86, 0x1E87 },
+{ 0x1E88, 0x1E88, 0x1E89 },
+{ 0x1E89, 0x1E88, 0x1E89 },
+{ 0x1E8A, 0x1E8A, 0x1E8B },
+{ 0x1E8B, 0x1E8A, 0x1E8B },
+{ 0x1E8C, 0x1E8C, 0x1E8D },
+{ 0x1E8D, 0x1E8C, 0x1E8D },
+{ 0x1E8E, 0x1E8E, 0x1E8F },
+{ 0x1E8F, 0x1E8E, 0x1E8F },
+{ 0x1E90, 0x1E90, 0x1E91 },
+{ 0x1E91, 0x1E90, 0x1E91 },
+{ 0x1E92, 0x1E92, 0x1E93 },
+{ 0x1E93, 0x1E92, 0x1E93 },
+{ 0x1E94, 0x1E94, 0x1E95 },
+{ 0x1E95, 0x1E94, 0x1E95 },
+{ 0x1E96, 0x1E96, 0x1E96 },
+{ 0x1E97, 0x1E97, 0x1E97 },
+{ 0x1E98, 0x1E98, 0x1E98 },
+{ 0x1E99, 0x1E99, 0x1E99 },
+{ 0x1E9A, 0x1E9A, 0x1E9A },
+{ 0x1E9B, 0x1E60, 0x1E9B },
+{ 0x1EA0, 0x1EA0, 0x1EA1 },
+{ 0x1EA1, 0x1EA0, 0x1EA1 },
+{ 0x1EA2, 0x1EA2, 0x1EA3 },
+{ 0x1EA3, 0x1EA2, 0x1EA3 },
+{ 0x1EA4, 0x1EA4, 0x1EA5 },
+{ 0x1EA5, 0x1EA4, 0x1EA5 },
+{ 0x1EA6, 0x1EA6, 0x1EA7 },
+{ 0x1EA7, 0x1EA6, 0x1EA7 },
+{ 0x1EA8, 0x1EA8, 0x1EA9 },
+{ 0x1EA9, 0x1EA8, 0x1EA9 },
+{ 0x1EAA, 0x1EAA, 0x1EAB },
+{ 0x1EAB, 0x1EAA, 0x1EAB },
+{ 0x1EAC, 0x1EAC, 0x1EAD },
+{ 0x1EAD, 0x1EAC, 0x1EAD },
+{ 0x1EAE, 0x1EAE, 0x1EAF },
+{ 0x1EAF, 0x1EAE, 0x1EAF },
+{ 0x1EB0, 0x1EB0, 0x1EB1 },
+{ 0x1EB1, 0x1EB0, 0x1EB1 },
+{ 0x1EB2, 0x1EB2, 0x1EB3 },
+{ 0x1EB3, 0x1EB2, 0x1EB3 },
+{ 0x1EB4, 0x1EB4, 0x1EB5 },
+{ 0x1EB5, 0x1EB4, 0x1EB5 },
+{ 0x1EB6, 0x1EB6, 0x1EB7 },
+{ 0x1EB7, 0x1EB6, 0x1EB7 },
+{ 0x1EB8, 0x1EB8, 0x1EB9 },
+{ 0x1EB9, 0x1EB8, 0x1EB9 },
+{ 0x1EBA, 0x1EBA, 0x1EBB },
+{ 0x1EBB, 0x1EBA, 0x1EBB },
+{ 0x1EBC, 0x1EBC, 0x1EBD },
+{ 0x1EBD, 0x1EBC, 0x1EBD },
+{ 0x1EBE, 0x1EBE, 0x1EBF },
+{ 0x1EBF, 0x1EBE, 0x1EBF },
+{ 0x1EC0, 0x1EC0, 0x1EC1 },
+{ 0x1EC1, 0x1EC0, 0x1EC1 },
+{ 0x1EC2, 0x1EC2, 0x1EC3 },
+{ 0x1EC3, 0x1EC2, 0x1EC3 },
+{ 0x1EC4, 0x1EC4, 0x1EC5 },
+{ 0x1EC5, 0x1EC4, 0x1EC5 },
+{ 0x1EC6, 0x1EC6, 0x1EC7 },
+{ 0x1EC7, 0x1EC6, 0x1EC7 },
+{ 0x1EC8, 0x1EC8, 0x1EC9 },
+{ 0x1EC9, 0x1EC8, 0x1EC9 },
+{ 0x1ECA, 0x1ECA, 0x1ECB },
+{ 0x1ECB, 0x1ECA, 0x1ECB },
+{ 0x1ECC, 0x1ECC, 0x1ECD },
+{ 0x1ECD, 0x1ECC, 0x1ECD },
+{ 0x1ECE, 0x1ECE, 0x1ECF },
+{ 0x1ECF, 0x1ECE, 0x1ECF },
+{ 0x1ED0, 0x1ED0, 0x1ED1 },
+{ 0x1ED1, 0x1ED0, 0x1ED1 },
+{ 0x1ED2, 0x1ED2, 0x1ED3 },
+{ 0x1ED3, 0x1ED2, 0x1ED3 },
+{ 0x1ED4, 0x1ED4, 0x1ED5 },
+{ 0x1ED5, 0x1ED4, 0x1ED5 },
+{ 0x1ED6, 0x1ED6, 0x1ED7 },
+{ 0x1ED7, 0x1ED6, 0x1ED7 },
+{ 0x1ED8, 0x1ED8, 0x1ED9 },
+{ 0x1ED9, 0x1ED8, 0x1ED9 },
+{ 0x1EDA, 0x1EDA, 0x1EDB },
+{ 0x1EDB, 0x1EDA, 0x1EDB },
+{ 0x1EDC, 0x1EDC, 0x1EDD },
+{ 0x1EDD, 0x1EDC, 0x1EDD },
+{ 0x1EDE, 0x1EDE, 0x1EDF },
+{ 0x1EDF, 0x1EDE, 0x1EDF },
+{ 0x1EE0, 0x1EE0, 0x1EE1 },
+{ 0x1EE1, 0x1EE0, 0x1EE1 },
+{ 0x1EE2, 0x1EE2, 0x1EE3 },
+{ 0x1EE3, 0x1EE2, 0x1EE3 },
+{ 0x1EE4, 0x1EE4, 0x1EE5 },
+{ 0x1EE5, 0x1EE4, 0x1EE5 },
+{ 0x1EE6, 0x1EE6, 0x1EE7 },
+{ 0x1EE7, 0x1EE6, 0x1EE7 },
+{ 0x1EE8, 0x1EE8, 0x1EE9 },
+{ 0x1EE9, 0x1EE8, 0x1EE9 },
+{ 0x1EEA, 0x1EEA, 0x1EEB },
+{ 0x1EEB, 0x1EEA, 0x1EEB },
+{ 0x1EEC, 0x1EEC, 0x1EED },
+{ 0x1EED, 0x1EEC, 0x1EED },
+{ 0x1EEE, 0x1EEE, 0x1EEF },
+{ 0x1EEF, 0x1EEE, 0x1EEF },
+{ 0x1EF0, 0x1EF0, 0x1EF1 },
+{ 0x1EF1, 0x1EF0, 0x1EF1 },
+{ 0x1EF2, 0x1EF2, 0x1EF3 },
+{ 0x1EF3, 0x1EF2, 0x1EF3 },
+{ 0x1EF4, 0x1EF4, 0x1EF5 },
+{ 0x1EF5, 0x1EF4, 0x1EF5 },
+{ 0x1EF6, 0x1EF6, 0x1EF7 },
+{ 0x1EF7, 0x1EF6, 0x1EF7 },
+{ 0x1EF8, 0x1EF8, 0x1EF9 },
+{ 0x1EF9, 0x1EF8, 0x1EF9 },
+{ 0x1F00, 0x1F08, 0x1F00 },
+{ 0x1F01, 0x1F09, 0x1F01 },
+{ 0x1F02, 0x1F0A, 0x1F02 },
+{ 0x1F03, 0x1F0B, 0x1F03 },
+{ 0x1F04, 0x1F0C, 0x1F04 },
+{ 0x1F05, 0x1F0D, 0x1F05 },
+{ 0x1F06, 0x1F0E, 0x1F06 },
+{ 0x1F07, 0x1F0F, 0x1F07 },
+{ 0x1F08, 0x1F08, 0x1F00 },
+{ 0x1F09, 0x1F09, 0x1F01 },
+{ 0x1F0A, 0x1F0A, 0x1F02 },
+{ 0x1F0B, 0x1F0B, 0x1F03 },
+{ 0x1F0C, 0x1F0C, 0x1F04 },
+{ 0x1F0D, 0x1F0D, 0x1F05 },
+{ 0x1F0E, 0x1F0E, 0x1F06 },
+{ 0x1F0F, 0x1F0F, 0x1F07 },
+{ 0x1F10, 0x1F18, 0x1F10 },
+{ 0x1F11, 0x1F19, 0x1F11 },
+{ 0x1F12, 0x1F1A, 0x1F12 },
+{ 0x1F13, 0x1F1B, 0x1F13 },
+{ 0x1F14, 0x1F1C, 0x1F14 },
+{ 0x1F15, 0x1F1D, 0x1F15 },
+{ 0x1F18, 0x1F18, 0x1F10 },
+{ 0x1F19, 0x1F19, 0x1F11 },
+{ 0x1F1A, 0x1F1A, 0x1F12 },
+{ 0x1F1B, 0x1F1B, 0x1F13 },
+{ 0x1F1C, 0x1F1C, 0x1F14 },
+{ 0x1F1D, 0x1F1D, 0x1F15 },
+{ 0x1F20, 0x1F28, 0x1F20 },
+{ 0x1F21, 0x1F29, 0x1F21 },
+{ 0x1F22, 0x1F2A, 0x1F22 },
+{ 0x1F23, 0x1F2B, 0x1F23 },
+{ 0x1F24, 0x1F2C, 0x1F24 },
+{ 0x1F25, 0x1F2D, 0x1F25 },
+{ 0x1F26, 0x1F2E, 0x1F26 },
+{ 0x1F27, 0x1F2F, 0x1F27 },
+{ 0x1F28, 0x1F28, 0x1F20 },
+{ 0x1F29, 0x1F29, 0x1F21 },
+{ 0x1F2A, 0x1F2A, 0x1F22 },
+{ 0x1F2B, 0x1F2B, 0x1F23 },
+{ 0x1F2C, 0x1F2C, 0x1F24 },
+{ 0x1F2D, 0x1F2D, 0x1F25 },
+{ 0x1F2E, 0x1F2E, 0x1F26 },
+{ 0x1F2F, 0x1F2F, 0x1F27 },
+{ 0x1F30, 0x1F38, 0x1F30 },
+{ 0x1F31, 0x1F39, 0x1F31 },
+{ 0x1F32, 0x1F3A, 0x1F32 },
+{ 0x1F33, 0x1F3B, 0x1F33 },
+{ 0x1F34, 0x1F3C, 0x1F34 },
+{ 0x1F35, 0x1F3D, 0x1F35 },
+{ 0x1F36, 0x1F3E, 0x1F36 },
+{ 0x1F37, 0x1F3F, 0x1F37 },
+{ 0x1F38, 0x1F38, 0x1F30 },
+{ 0x1F39, 0x1F39, 0x1F31 },
+{ 0x1F3A, 0x1F3A, 0x1F32 },
+{ 0x1F3B, 0x1F3B, 0x1F33 },
+{ 0x1F3C, 0x1F3C, 0x1F34 },
+{ 0x1F3D, 0x1F3D, 0x1F35 },
+{ 0x1F3E, 0x1F3E, 0x1F36 },
+{ 0x1F3F, 0x1F3F, 0x1F37 },
+{ 0x1F40, 0x1F48, 0x1F40 },
+{ 0x1F41, 0x1F49, 0x1F41 },
+{ 0x1F42, 0x1F4A, 0x1F42 },
+{ 0x1F43, 0x1F4B, 0x1F43 },
+{ 0x1F44, 0x1F4C, 0x1F44 },
+{ 0x1F45, 0x1F4D, 0x1F45 },
+{ 0x1F48, 0x1F48, 0x1F40 },
+{ 0x1F49, 0x1F49, 0x1F41 },
+{ 0x1F4A, 0x1F4A, 0x1F42 },
+{ 0x1F4B, 0x1F4B, 0x1F43 },
+{ 0x1F4C, 0x1F4C, 0x1F44 },
+{ 0x1F4D, 0x1F4D, 0x1F45 },
+{ 0x1F50, 0x1F50, 0x1F50 },
+{ 0x1F51, 0x1F59, 0x1F51 },
+{ 0x1F52, 0x1F52, 0x1F52 },
+{ 0x1F53, 0x1F5B, 0x1F53 },
+{ 0x1F54, 0x1F54, 0x1F54 },
+{ 0x1F55, 0x1F5D, 0x1F55 },
+{ 0x1F56, 0x1F56, 0x1F56 },
+{ 0x1F57, 0x1F5F, 0x1F57 },
+{ 0x1F59, 0x1F59, 0x1F51 },
+{ 0x1F5B, 0x1F5B, 0x1F53 },
+{ 0x1F5D, 0x1F5D, 0x1F55 },
+{ 0x1F5F, 0x1F5F, 0x1F57 },
+{ 0x1F60, 0x1F68, 0x1F60 },
+{ 0x1F61, 0x1F69, 0x1F61 },
+{ 0x1F62, 0x1F6A, 0x1F62 },
+{ 0x1F63, 0x1F6B, 0x1F63 },
+{ 0x1F64, 0x1F6C, 0x1F64 },
+{ 0x1F65, 0x1F6D, 0x1F65 },
+{ 0x1F66, 0x1F6E, 0x1F66 },
+{ 0x1F67, 0x1F6F, 0x1F67 },
+{ 0x1F68, 0x1F68, 0x1F60 },
+{ 0x1F69, 0x1F69, 0x1F61 },
+{ 0x1F6A, 0x1F6A, 0x1F62 },
+{ 0x1F6B, 0x1F6B, 0x1F63 },
+{ 0x1F6C, 0x1F6C, 0x1F64 },
+{ 0x1F6D, 0x1F6D, 0x1F65 },
+{ 0x1F6E, 0x1F6E, 0x1F66 },
+{ 0x1F6F, 0x1F6F, 0x1F67 },
+{ 0x1F70, 0x1FBA, 0x1F70 },
+{ 0x1F71, 0x1FBB, 0x1F71 },
+{ 0x1F72, 0x1FC8, 0x1F72 },
+{ 0x1F73, 0x1FC9, 0x1F73 },
+{ 0x1F74, 0x1FCA, 0x1F74 },
+{ 0x1F75, 0x1FCB, 0x1F75 },
+{ 0x1F76, 0x1FDA, 0x1F76 },
+{ 0x1F77, 0x1FDB, 0x1F77 },
+{ 0x1F78, 0x1FF8, 0x1F78 },
+{ 0x1F79, 0x1FF9, 0x1F79 },
+{ 0x1F7A, 0x1FEA, 0x1F7A },
+{ 0x1F7B, 0x1FEB, 0x1F7B },
+{ 0x1F7C, 0x1FFA, 0x1F7C },
+{ 0x1F7D, 0x1FFB, 0x1F7D },
+{ 0x1F80, 0x1F88, 0x1F80 },
+{ 0x1F81, 0x1F89, 0x1F81 },
+{ 0x1F82, 0x1F8A, 0x1F82 },
+{ 0x1F83, 0x1F8B, 0x1F83 },
+{ 0x1F84, 0x1F8C, 0x1F84 },
+{ 0x1F85, 0x1F8D, 0x1F85 },
+{ 0x1F86, 0x1F8E, 0x1F86 },
+{ 0x1F87, 0x1F8F, 0x1F87 },
+{ 0x1F88, 0x1F88, 0x1F80 },
+{ 0x1F89, 0x1F89, 0x1F81 },
+{ 0x1F8A, 0x1F8A, 0x1F82 },
+{ 0x1F8B, 0x1F8B, 0x1F83 },
+{ 0x1F8C, 0x1F8C, 0x1F84 },
+{ 0x1F8D, 0x1F8D, 0x1F85 },
+{ 0x1F8E, 0x1F8E, 0x1F86 },
+{ 0x1F8F, 0x1F8F, 0x1F87 },
+{ 0x1F90, 0x1F98, 0x1F90 },
+{ 0x1F91, 0x1F99, 0x1F91 },
+{ 0x1F92, 0x1F9A, 0x1F92 },
+{ 0x1F93, 0x1F9B, 0x1F93 },
+{ 0x1F94, 0x1F9C, 0x1F94 },
+{ 0x1F95, 0x1F9D, 0x1F95 },
+{ 0x1F96, 0x1F9E, 0x1F96 },
+{ 0x1F97, 0x1F9F, 0x1F97 },
+{ 0x1F98, 0x1F98, 0x1F90 },
+{ 0x1F99, 0x1F99, 0x1F91 },
+{ 0x1F9A, 0x1F9A, 0x1F92 },
+{ 0x1F9B, 0x1F9B, 0x1F93 },
+{ 0x1F9C, 0x1F9C, 0x1F94 },
+{ 0x1F9D, 0x1F9D, 0x1F95 },
+{ 0x1F9E, 0x1F9E, 0x1F96 },
+{ 0x1F9F, 0x1F9F, 0x1F97 },
+{ 0x1FA0, 0x1FA8, 0x1FA0 },
+{ 0x1FA1, 0x1FA9, 0x1FA1 },
+{ 0x1FA2, 0x1FAA, 0x1FA2 },
+{ 0x1FA3, 0x1FAB, 0x1FA3 },
+{ 0x1FA4, 0x1FAC, 0x1FA4 },
+{ 0x1FA5, 0x1FAD, 0x1FA5 },
+{ 0x1FA6, 0x1FAE, 0x1FA6 },
+{ 0x1FA7, 0x1FAF, 0x1FA7 },
+{ 0x1FA8, 0x1FA8, 0x1FA0 },
+{ 0x1FA9, 0x1FA9, 0x1FA1 },
+{ 0x1FAA, 0x1FAA, 0x1FA2 },
+{ 0x1FAB, 0x1FAB, 0x1FA3 },
+{ 0x1FAC, 0x1FAC, 0x1FA4 },
+{ 0x1FAD, 0x1FAD, 0x1FA5 },
+{ 0x1FAE, 0x1FAE, 0x1FA6 },
+{ 0x1FAF, 0x1FAF, 0x1FA7 },
+{ 0x1FB0, 0x1FB8, 0x1FB0 },
+{ 0x1FB1, 0x1FB9, 0x1FB1 },
+{ 0x1FB2, 0x1FB2, 0x1FB2 },
+{ 0x1FB3, 0x1FBC, 0x1FB3 },
+{ 0x1FB4, 0x1FB4, 0x1FB4 },
+{ 0x1FB6, 0x1FB6, 0x1FB6 },
+{ 0x1FB7, 0x1FB7, 0x1FB7 },
+{ 0x1FB8, 0x1FB8, 0x1FB0 },
+{ 0x1FB9, 0x1FB9, 0x1FB1 },
+{ 0x1FBA, 0x1FBA, 0x1F70 },
+{ 0x1FBB, 0x1FBB, 0x1F71 },
+{ 0x1FBC, 0x1FBC, 0x1FB3 },
+{ 0x1FBE, 0x0399, 0x1FBE },
+{ 0x1FC2, 0x1FC2, 0x1FC2 },
+{ 0x1FC3, 0x1FCC, 0x1FC3 },
+{ 0x1FC4, 0x1FC4, 0x1FC4 },
+{ 0x1FC6, 0x1FC6, 0x1FC6 },
+{ 0x1FC7, 0x1FC7, 0x1FC7 },
+{ 0x1FC8, 0x1FC8, 0x1F72 },
+{ 0x1FC9, 0x1FC9, 0x1F73 },
+{ 0x1FCA, 0x1FCA, 0x1F74 },
+{ 0x1FCB, 0x1FCB, 0x1F75 },
+{ 0x1FCC, 0x1FCC, 0x1FC3 },
+{ 0x1FD0, 0x1FD8, 0x1FD0 },
+{ 0x1FD1, 0x1FD9, 0x1FD1 },
+{ 0x1FD2, 0x1FD2, 0x1FD2 },
+{ 0x1FD3, 0x1FD3, 0x1FD3 },
+{ 0x1FD6, 0x1FD6, 0x1FD6 },
+{ 0x1FD7, 0x1FD7, 0x1FD7 },
+{ 0x1FD8, 0x1FD8, 0x1FD0 },
+{ 0x1FD9, 0x1FD9, 0x1FD1 },
+{ 0x1FDA, 0x1FDA, 0x1F76 },
+{ 0x1FDB, 0x1FDB, 0x1F77 },
+{ 0x1FE0, 0x1FE8, 0x1FE0 },
+{ 0x1FE1, 0x1FE9, 0x1FE1 },
+{ 0x1FE2, 0x1FE2, 0x1FE2 },
+{ 0x1FE3, 0x1FE3, 0x1FE3 },
+{ 0x1FE4, 0x1FE4, 0x1FE4 },
+{ 0x1FE5, 0x1FEC, 0x1FE5 },
+{ 0x1FE6, 0x1FE6, 0x1FE6 },
+{ 0x1FE7, 0x1FE7, 0x1FE7 },
+{ 0x1FE8, 0x1FE8, 0x1FE0 },
+{ 0x1FE9, 0x1FE9, 0x1FE1 },
+{ 0x1FEA, 0x1FEA, 0x1F7A },
+{ 0x1FEB, 0x1FEB, 0x1F7B },
+{ 0x1FEC, 0x1FEC, 0x1FE5 },
+{ 0x1FF2, 0x1FF2, 0x1FF2 },
+{ 0x1FF3, 0x1FFC, 0x1FF3 },
+{ 0x1FF4, 0x1FF4, 0x1FF4 },
+{ 0x1FF6, 0x1FF6, 0x1FF6 },
+{ 0x1FF7, 0x1FF7, 0x1FF7 },
+{ 0x1FF8, 0x1FF8, 0x1F78 },
+{ 0x1FF9, 0x1FF9, 0x1F79 },
+{ 0x1FFA, 0x1FFA, 0x1F7C },
+{ 0x1FFB, 0x1FFB, 0x1F7D },
+{ 0x1FFC, 0x1FFC, 0x1FF3 },
+{ 0x2071, 0x2071, 0x2071 },
+{ 0x207F, 0x207F, 0x207F },
+{ 0x2090, 0x2090, 0x2090 },
+{ 0x2091, 0x2091, 0x2091 },
+{ 0x2092, 0x2092, 0x2092 },
+{ 0x2093, 0x2093, 0x2093 },
+{ 0x2094, 0x2094, 0x2094 },
+{ 0x20D0, 0x20D0, 0x20D0 },
+{ 0x20D1, 0x20D1, 0x20D1 },
+{ 0x20D2, 0x20D2, 0x20D2 },
+{ 0x20D3, 0x20D3, 0x20D3 },
+{ 0x20D4, 0x20D4, 0x20D4 },
+{ 0x20D5, 0x20D5, 0x20D5 },
+{ 0x20D6, 0x20D6, 0x20D6 },
+{ 0x20D7, 0x20D7, 0x20D7 },
+{ 0x20D8, 0x20D8, 0x20D8 },
+{ 0x20D9, 0x20D9, 0x20D9 },
+{ 0x20DA, 0x20DA, 0x20DA },
+{ 0x20DB, 0x20DB, 0x20DB },
+{ 0x20DC, 0x20DC, 0x20DC },
+{ 0x20E1, 0x20E1, 0x20E1 },
+{ 0x20E5, 0x20E5, 0x20E5 },
+{ 0x20E6, 0x20E6, 0x20E6 },
+{ 0x20E7, 0x20E7, 0x20E7 },
+{ 0x20E8, 0x20E8, 0x20E8 },
+{ 0x20E9, 0x20E9, 0x20E9 },
+{ 0x20EA, 0x20EA, 0x20EA },
+{ 0x20EB, 0x20EB, 0x20EB },
+{ 0x2102, 0x2102, 0x2102 },
+{ 0x2107, 0x2107, 0x2107 },
+{ 0x210A, 0x210A, 0x210A },
+{ 0x210B, 0x210B, 0x210B },
+{ 0x210C, 0x210C, 0x210C },
+{ 0x210D, 0x210D, 0x210D },
+{ 0x210E, 0x210E, 0x210E },
+{ 0x210F, 0x210F, 0x210F },
+{ 0x2110, 0x2110, 0x2110 },
+{ 0x2111, 0x2111, 0x2111 },
+{ 0x2112, 0x2112, 0x2112 },
+{ 0x2113, 0x2113, 0x2113 },
+{ 0x2115, 0x2115, 0x2115 },
+{ 0x2119, 0x2119, 0x2119 },
+{ 0x211A, 0x211A, 0x211A },
+{ 0x211B, 0x211B, 0x211B },
+{ 0x211C, 0x211C, 0x211C },
+{ 0x211D, 0x211D, 0x211D },
+{ 0x2124, 0x2124, 0x2124 },
+{ 0x2126, 0x2126, 0x03C9 },
+{ 0x2128, 0x2128, 0x2128 },
+{ 0x212A, 0x212A, 0x006B },
+{ 0x212B, 0x212B, 0x00E5 },
+{ 0x212C, 0x212C, 0x212C },
+{ 0x212D, 0x212D, 0x212D },
+{ 0x212F, 0x212F, 0x212F },
+{ 0x2130, 0x2130, 0x2130 },
+{ 0x2131, 0x2131, 0x2131 },
+{ 0x2133, 0x2133, 0x2133 },
+{ 0x2134, 0x2134, 0x2134 },
+{ 0x2135, 0x2135, 0x2135 },
+{ 0x2136, 0x2136, 0x2136 },
+{ 0x2137, 0x2137, 0x2137 },
+{ 0x2138, 0x2138, 0x2138 },
+{ 0x2139, 0x2139, 0x2139 },
+{ 0x213C, 0x213C, 0x213C },
+{ 0x213D, 0x213D, 0x213D },
+{ 0x213E, 0x213E, 0x213E },
+{ 0x213F, 0x213F, 0x213F },
+{ 0x2145, 0x2145, 0x2145 },
+{ 0x2146, 0x2146, 0x2146 },
+{ 0x2147, 0x2147, 0x2147 },
+{ 0x2148, 0x2148, 0x2148 },
+{ 0x2149, 0x2149, 0x2149 },
+{ 0x2C00, 0x2C00, 0x2C30 },
+{ 0x2C01, 0x2C01, 0x2C31 },
+{ 0x2C02, 0x2C02, 0x2C32 },
+{ 0x2C03, 0x2C03, 0x2C33 },
+{ 0x2C04, 0x2C04, 0x2C34 },
+{ 0x2C05, 0x2C05, 0x2C35 },
+{ 0x2C06, 0x2C06, 0x2C36 },
+{ 0x2C07, 0x2C07, 0x2C37 },
+{ 0x2C08, 0x2C08, 0x2C38 },
+{ 0x2C09, 0x2C09, 0x2C39 },
+{ 0x2C0A, 0x2C0A, 0x2C3A },
+{ 0x2C0B, 0x2C0B, 0x2C3B },
+{ 0x2C0C, 0x2C0C, 0x2C3C },
+{ 0x2C0D, 0x2C0D, 0x2C3D },
+{ 0x2C0E, 0x2C0E, 0x2C3E },
+{ 0x2C0F, 0x2C0F, 0x2C3F },
+{ 0x2C10, 0x2C10, 0x2C40 },
+{ 0x2C11, 0x2C11, 0x2C41 },
+{ 0x2C12, 0x2C12, 0x2C42 },
+{ 0x2C13, 0x2C13, 0x2C43 },
+{ 0x2C14, 0x2C14, 0x2C44 },
+{ 0x2C15, 0x2C15, 0x2C45 },
+{ 0x2C16, 0x2C16, 0x2C46 },
+{ 0x2C17, 0x2C17, 0x2C47 },
+{ 0x2C18, 0x2C18, 0x2C48 },
+{ 0x2C19, 0x2C19, 0x2C49 },
+{ 0x2C1A, 0x2C1A, 0x2C4A },
+{ 0x2C1B, 0x2C1B, 0x2C4B },
+{ 0x2C1C, 0x2C1C, 0x2C4C },
+{ 0x2C1D, 0x2C1D, 0x2C4D },
+{ 0x2C1E, 0x2C1E, 0x2C4E },
+{ 0x2C1F, 0x2C1F, 0x2C4F },
+{ 0x2C20, 0x2C20, 0x2C50 },
+{ 0x2C21, 0x2C21, 0x2C51 },
+{ 0x2C22, 0x2C22, 0x2C52 },
+{ 0x2C23, 0x2C23, 0x2C53 },
+{ 0x2C24, 0x2C24, 0x2C54 },
+{ 0x2C25, 0x2C25, 0x2C55 },
+{ 0x2C26, 0x2C26, 0x2C56 },
+{ 0x2C27, 0x2C27, 0x2C57 },
+{ 0x2C28, 0x2C28, 0x2C58 },
+{ 0x2C29, 0x2C29, 0x2C59 },
+{ 0x2C2A, 0x2C2A, 0x2C5A },
+{ 0x2C2B, 0x2C2B, 0x2C5B },
+{ 0x2C2C, 0x2C2C, 0x2C5C },
+{ 0x2C2D, 0x2C2D, 0x2C5D },
+{ 0x2C2E, 0x2C2E, 0x2C5E },
+{ 0x2C30, 0x2C00, 0x2C30 },
+{ 0x2C31, 0x2C01, 0x2C31 },
+{ 0x2C32, 0x2C02, 0x2C32 },
+{ 0x2C33, 0x2C03, 0x2C33 },
+{ 0x2C34, 0x2C04, 0x2C34 },
+{ 0x2C35, 0x2C05, 0x2C35 },
+{ 0x2C36, 0x2C06, 0x2C36 },
+{ 0x2C37, 0x2C07, 0x2C37 },
+{ 0x2C38, 0x2C08, 0x2C38 },
+{ 0x2C39, 0x2C09, 0x2C39 },
+{ 0x2C3A, 0x2C0A, 0x2C3A },
+{ 0x2C3B, 0x2C0B, 0x2C3B },
+{ 0x2C3C, 0x2C0C, 0x2C3C },
+{ 0x2C3D, 0x2C0D, 0x2C3D },
+{ 0x2C3E, 0x2C0E, 0x2C3E },
+{ 0x2C3F, 0x2C0F, 0x2C3F },
+{ 0x2C40, 0x2C10, 0x2C40 },
+{ 0x2C41, 0x2C11, 0x2C41 },
+{ 0x2C42, 0x2C12, 0x2C42 },
+{ 0x2C43, 0x2C13, 0x2C43 },
+{ 0x2C44, 0x2C14, 0x2C44 },
+{ 0x2C45, 0x2C15, 0x2C45 },
+{ 0x2C46, 0x2C16, 0x2C46 },
+{ 0x2C47, 0x2C17, 0x2C47 },
+{ 0x2C48, 0x2C18, 0x2C48 },
+{ 0x2C49, 0x2C19, 0x2C49 },
+{ 0x2C4A, 0x2C1A, 0x2C4A },
+{ 0x2C4B, 0x2C1B, 0x2C4B },
+{ 0x2C4C, 0x2C1C, 0x2C4C },
+{ 0x2C4D, 0x2C1D, 0x2C4D },
+{ 0x2C4E, 0x2C1E, 0x2C4E },
+{ 0x2C4F, 0x2C1F, 0x2C4F },
+{ 0x2C50, 0x2C20, 0x2C50 },
+{ 0x2C51, 0x2C21, 0x2C51 },
+{ 0x2C52, 0x2C22, 0x2C52 },
+{ 0x2C53, 0x2C23, 0x2C53 },
+{ 0x2C54, 0x2C24, 0x2C54 },
+{ 0x2C55, 0x2C25, 0x2C55 },
+{ 0x2C56, 0x2C26, 0x2C56 },
+{ 0x2C57, 0x2C27, 0x2C57 },
+{ 0x2C58, 0x2C28, 0x2C58 },
+{ 0x2C59, 0x2C29, 0x2C59 },
+{ 0x2C5A, 0x2C2A, 0x2C5A },
+{ 0x2C5B, 0x2C2B, 0x2C5B },
+{ 0x2C5C, 0x2C2C, 0x2C5C },
+{ 0x2C5D, 0x2C2D, 0x2C5D },
+{ 0x2C5E, 0x2C2E, 0x2C5E },
+{ 0x2C80, 0x2C80, 0x2C81 },
+{ 0x2C81, 0x2C80, 0x2C81 },
+{ 0x2C82, 0x2C82, 0x2C83 },
+{ 0x2C83, 0x2C82, 0x2C83 },
+{ 0x2C84, 0x2C84, 0x2C85 },
+{ 0x2C85, 0x2C84, 0x2C85 },
+{ 0x2C86, 0x2C86, 0x2C87 },
+{ 0x2C87, 0x2C86, 0x2C87 },
+{ 0x2C88, 0x2C88, 0x2C89 },
+{ 0x2C89, 0x2C88, 0x2C89 },
+{ 0x2C8A, 0x2C8A, 0x2C8B },
+{ 0x2C8B, 0x2C8A, 0x2C8B },
+{ 0x2C8C, 0x2C8C, 0x2C8D },
+{ 0x2C8D, 0x2C8C, 0x2C8D },
+{ 0x2C8E, 0x2C8E, 0x2C8F },
+{ 0x2C8F, 0x2C8E, 0x2C8F },
+{ 0x2C90, 0x2C90, 0x2C91 },
+{ 0x2C91, 0x2C90, 0x2C91 },
+{ 0x2C92, 0x2C92, 0x2C93 },
+{ 0x2C93, 0x2C92, 0x2C93 },
+{ 0x2C94, 0x2C94, 0x2C95 },
+{ 0x2C95, 0x2C94, 0x2C95 },
+{ 0x2C96, 0x2C96, 0x2C97 },
+{ 0x2C97, 0x2C96, 0x2C97 },
+{ 0x2C98, 0x2C98, 0x2C99 },
+{ 0x2C99, 0x2C98, 0x2C99 },
+{ 0x2C9A, 0x2C9A, 0x2C9B },
+{ 0x2C9B, 0x2C9A, 0x2C9B },
+{ 0x2C9C, 0x2C9C, 0x2C9D },
+{ 0x2C9D, 0x2C9C, 0x2C9D },
+{ 0x2C9E, 0x2C9E, 0x2C9F },
+{ 0x2C9F, 0x2C9E, 0x2C9F },
+{ 0x2CA0, 0x2CA0, 0x2CA1 },
+{ 0x2CA1, 0x2CA0, 0x2CA1 },
+{ 0x2CA2, 0x2CA2, 0x2CA3 },
+{ 0x2CA3, 0x2CA2, 0x2CA3 },
+{ 0x2CA4, 0x2CA4, 0x2CA5 },
+{ 0x2CA5, 0x2CA4, 0x2CA5 },
+{ 0x2CA6, 0x2CA6, 0x2CA7 },
+{ 0x2CA7, 0x2CA6, 0x2CA7 },
+{ 0x2CA8, 0x2CA8, 0x2CA9 },
+{ 0x2CA9, 0x2CA8, 0x2CA9 },
+{ 0x2CAA, 0x2CAA, 0x2CAB },
+{ 0x2CAB, 0x2CAA, 0x2CAB },
+{ 0x2CAC, 0x2CAC, 0x2CAD },
+{ 0x2CAD, 0x2CAC, 0x2CAD },
+{ 0x2CAE, 0x2CAE, 0x2CAF },
+{ 0x2CAF, 0x2CAE, 0x2CAF },
+{ 0x2CB0, 0x2CB0, 0x2CB1 },
+{ 0x2CB1, 0x2CB0, 0x2CB1 },
+{ 0x2CB2, 0x2CB2, 0x2CB3 },
+{ 0x2CB3, 0x2CB2, 0x2CB3 },
+{ 0x2CB4, 0x2CB4, 0x2CB5 },
+{ 0x2CB5, 0x2CB4, 0x2CB5 },
+{ 0x2CB6, 0x2CB6, 0x2CB7 },
+{ 0x2CB7, 0x2CB6, 0x2CB7 },
+{ 0x2CB8, 0x2CB8, 0x2CB9 },
+{ 0x2CB9, 0x2CB8, 0x2CB9 },
+{ 0x2CBA, 0x2CBA, 0x2CBB },
+{ 0x2CBB, 0x2CBA, 0x2CBB },
+{ 0x2CBC, 0x2CBC, 0x2CBD },
+{ 0x2CBD, 0x2CBC, 0x2CBD },
+{ 0x2CBE, 0x2CBE, 0x2CBF },
+{ 0x2CBF, 0x2CBE, 0x2CBF },
+{ 0x2CC0, 0x2CC0, 0x2CC1 },
+{ 0x2CC1, 0x2CC0, 0x2CC1 },
+{ 0x2CC2, 0x2CC2, 0x2CC3 },
+{ 0x2CC3, 0x2CC2, 0x2CC3 },
+{ 0x2CC4, 0x2CC4, 0x2CC5 },
+{ 0x2CC5, 0x2CC4, 0x2CC5 },
+{ 0x2CC6, 0x2CC6, 0x2CC7 },
+{ 0x2CC7, 0x2CC6, 0x2CC7 },
+{ 0x2CC8, 0x2CC8, 0x2CC9 },
+{ 0x2CC9, 0x2CC8, 0x2CC9 },
+{ 0x2CCA, 0x2CCA, 0x2CCB },
+{ 0x2CCB, 0x2CCA, 0x2CCB },
+{ 0x2CCC, 0x2CCC, 0x2CCD },
+{ 0x2CCD, 0x2CCC, 0x2CCD },
+{ 0x2CCE, 0x2CCE, 0x2CCF },
+{ 0x2CCF, 0x2CCE, 0x2CCF },
+{ 0x2CD0, 0x2CD0, 0x2CD1 },
+{ 0x2CD1, 0x2CD0, 0x2CD1 },
+{ 0x2CD2, 0x2CD2, 0x2CD3 },
+{ 0x2CD3, 0x2CD2, 0x2CD3 },
+{ 0x2CD4, 0x2CD4, 0x2CD5 },
+{ 0x2CD5, 0x2CD4, 0x2CD5 },
+{ 0x2CD6, 0x2CD6, 0x2CD7 },
+{ 0x2CD7, 0x2CD6, 0x2CD7 },
+{ 0x2CD8, 0x2CD8, 0x2CD9 },
+{ 0x2CD9, 0x2CD8, 0x2CD9 },
+{ 0x2CDA, 0x2CDA, 0x2CDB },
+{ 0x2CDB, 0x2CDA, 0x2CDB },
+{ 0x2CDC, 0x2CDC, 0x2CDD },
+{ 0x2CDD, 0x2CDC, 0x2CDD },
+{ 0x2CDE, 0x2CDE, 0x2CDF },
+{ 0x2CDF, 0x2CDE, 0x2CDF },
+{ 0x2CE0, 0x2CE0, 0x2CE1 },
+{ 0x2CE1, 0x2CE0, 0x2CE1 },
+{ 0x2CE2, 0x2CE2, 0x2CE3 },
+{ 0x2CE3, 0x2CE2, 0x2CE3 },
+{ 0x2CE4, 0x2CE4, 0x2CE4 },
+{ 0x2D00, 0x10A0, 0x2D00 },
+{ 0x2D01, 0x10A1, 0x2D01 },
+{ 0x2D02, 0x10A2, 0x2D02 },
+{ 0x2D03, 0x10A3, 0x2D03 },
+{ 0x2D04, 0x10A4, 0x2D04 },
+{ 0x2D05, 0x10A5, 0x2D05 },
+{ 0x2D06, 0x10A6, 0x2D06 },
+{ 0x2D07, 0x10A7, 0x2D07 },
+{ 0x2D08, 0x10A8, 0x2D08 },
+{ 0x2D09, 0x10A9, 0x2D09 },
+{ 0x2D0A, 0x10AA, 0x2D0A },
+{ 0x2D0B, 0x10AB, 0x2D0B },
+{ 0x2D0C, 0x10AC, 0x2D0C },
+{ 0x2D0D, 0x10AD, 0x2D0D },
+{ 0x2D0E, 0x10AE, 0x2D0E },
+{ 0x2D0F, 0x10AF, 0x2D0F },
+{ 0x2D10, 0x10B0, 0x2D10 },
+{ 0x2D11, 0x10B1, 0x2D11 },
+{ 0x2D12, 0x10B2, 0x2D12 },
+{ 0x2D13, 0x10B3, 0x2D13 },
+{ 0x2D14, 0x10B4, 0x2D14 },
+{ 0x2D15, 0x10B5, 0x2D15 },
+{ 0x2D16, 0x10B6, 0x2D16 },
+{ 0x2D17, 0x10B7, 0x2D17 },
+{ 0x2D18, 0x10B8, 0x2D18 },
+{ 0x2D19, 0x10B9, 0x2D19 },
+{ 0x2D1A, 0x10BA, 0x2D1A },
+{ 0x2D1B, 0x10BB, 0x2D1B },
+{ 0x2D1C, 0x10BC, 0x2D1C },
+{ 0x2D1D, 0x10BD, 0x2D1D },
+{ 0x2D1E, 0x10BE, 0x2D1E },
+{ 0x2D1F, 0x10BF, 0x2D1F },
+{ 0x2D20, 0x10C0, 0x2D20 },
+{ 0x2D21, 0x10C1, 0x2D21 },
+{ 0x2D22, 0x10C2, 0x2D22 },
+{ 0x2D23, 0x10C3, 0x2D23 },
+{ 0x2D24, 0x10C4, 0x2D24 },
+{ 0x2D25, 0x10C5, 0x2D25 },
+{ 0x2D30, 0x2D30, 0x2D30 },
+{ 0x2D31, 0x2D31, 0x2D31 },
+{ 0x2D32, 0x2D32, 0x2D32 },
+{ 0x2D33, 0x2D33, 0x2D33 },
+{ 0x2D34, 0x2D34, 0x2D34 },
+{ 0x2D35, 0x2D35, 0x2D35 },
+{ 0x2D36, 0x2D36, 0x2D36 },
+{ 0x2D37, 0x2D37, 0x2D37 },
+{ 0x2D38, 0x2D38, 0x2D38 },
+{ 0x2D39, 0x2D39, 0x2D39 },
+{ 0x2D3A, 0x2D3A, 0x2D3A },
+{ 0x2D3B, 0x2D3B, 0x2D3B },
+{ 0x2D3C, 0x2D3C, 0x2D3C },
+{ 0x2D3D, 0x2D3D, 0x2D3D },
+{ 0x2D3E, 0x2D3E, 0x2D3E },
+{ 0x2D3F, 0x2D3F, 0x2D3F },
+{ 0x2D40, 0x2D40, 0x2D40 },
+{ 0x2D41, 0x2D41, 0x2D41 },
+{ 0x2D42, 0x2D42, 0x2D42 },
+{ 0x2D43, 0x2D43, 0x2D43 },
+{ 0x2D44, 0x2D44, 0x2D44 },
+{ 0x2D45, 0x2D45, 0x2D45 },
+{ 0x2D46, 0x2D46, 0x2D46 },
+{ 0x2D47, 0x2D47, 0x2D47 },
+{ 0x2D48, 0x2D48, 0x2D48 },
+{ 0x2D49, 0x2D49, 0x2D49 },
+{ 0x2D4A, 0x2D4A, 0x2D4A },
+{ 0x2D4B, 0x2D4B, 0x2D4B },
+{ 0x2D4C, 0x2D4C, 0x2D4C },
+{ 0x2D4D, 0x2D4D, 0x2D4D },
+{ 0x2D4E, 0x2D4E, 0x2D4E },
+{ 0x2D4F, 0x2D4F, 0x2D4F },
+{ 0x2D50, 0x2D50, 0x2D50 },
+{ 0x2D51, 0x2D51, 0x2D51 },
+{ 0x2D52, 0x2D52, 0x2D52 },
+{ 0x2D53, 0x2D53, 0x2D53 },
+{ 0x2D54, 0x2D54, 0x2D54 },
+{ 0x2D55, 0x2D55, 0x2D55 },
+{ 0x2D56, 0x2D56, 0x2D56 },
+{ 0x2D57, 0x2D57, 0x2D57 },
+{ 0x2D58, 0x2D58, 0x2D58 },
+{ 0x2D59, 0x2D59, 0x2D59 },
+{ 0x2D5A, 0x2D5A, 0x2D5A },
+{ 0x2D5B, 0x2D5B, 0x2D5B },
+{ 0x2D5C, 0x2D5C, 0x2D5C },
+{ 0x2D5D, 0x2D5D, 0x2D5D },
+{ 0x2D5E, 0x2D5E, 0x2D5E },
+{ 0x2D5F, 0x2D5F, 0x2D5F },
+{ 0x2D60, 0x2D60, 0x2D60 },
+{ 0x2D61, 0x2D61, 0x2D61 },
+{ 0x2D62, 0x2D62, 0x2D62 },
+{ 0x2D63, 0x2D63, 0x2D63 },
+{ 0x2D64, 0x2D64, 0x2D64 },
+{ 0x2D65, 0x2D65, 0x2D65 },
+{ 0x2D6F, 0x2D6F, 0x2D6F },
+{ 0x2D80, 0x2D80, 0x2D80 },
+{ 0x2D81, 0x2D81, 0x2D81 },
+{ 0x2D82, 0x2D82, 0x2D82 },
+{ 0x2D83, 0x2D83, 0x2D83 },
+{ 0x2D84, 0x2D84, 0x2D84 },
+{ 0x2D85, 0x2D85, 0x2D85 },
+{ 0x2D86, 0x2D86, 0x2D86 },
+{ 0x2D87, 0x2D87, 0x2D87 },
+{ 0x2D88, 0x2D88, 0x2D88 },
+{ 0x2D89, 0x2D89, 0x2D89 },
+{ 0x2D8A, 0x2D8A, 0x2D8A },
+{ 0x2D8B, 0x2D8B, 0x2D8B },
+{ 0x2D8C, 0x2D8C, 0x2D8C },
+{ 0x2D8D, 0x2D8D, 0x2D8D },
+{ 0x2D8E, 0x2D8E, 0x2D8E },
+{ 0x2D8F, 0x2D8F, 0x2D8F },
+{ 0x2D90, 0x2D90, 0x2D90 },
+{ 0x2D91, 0x2D91, 0x2D91 },
+{ 0x2D92, 0x2D92, 0x2D92 },
+{ 0x2D93, 0x2D93, 0x2D93 },
+{ 0x2D94, 0x2D94, 0x2D94 },
+{ 0x2D95, 0x2D95, 0x2D95 },
+{ 0x2D96, 0x2D96, 0x2D96 },
+{ 0x2DA0, 0x2DA0, 0x2DA0 },
+{ 0x2DA1, 0x2DA1, 0x2DA1 },
+{ 0x2DA2, 0x2DA2, 0x2DA2 },
+{ 0x2DA3, 0x2DA3, 0x2DA3 },
+{ 0x2DA4, 0x2DA4, 0x2DA4 },
+{ 0x2DA5, 0x2DA5, 0x2DA5 },
+{ 0x2DA6, 0x2DA6, 0x2DA6 },
+{ 0x2DA8, 0x2DA8, 0x2DA8 },
+{ 0x2DA9, 0x2DA9, 0x2DA9 },
+{ 0x2DAA, 0x2DAA, 0x2DAA },
+{ 0x2DAB, 0x2DAB, 0x2DAB },
+{ 0x2DAC, 0x2DAC, 0x2DAC },
+{ 0x2DAD, 0x2DAD, 0x2DAD },
+{ 0x2DAE, 0x2DAE, 0x2DAE },
+{ 0x2DB0, 0x2DB0, 0x2DB0 },
+{ 0x2DB1, 0x2DB1, 0x2DB1 },
+{ 0x2DB2, 0x2DB2, 0x2DB2 },
+{ 0x2DB3, 0x2DB3, 0x2DB3 },
+{ 0x2DB4, 0x2DB4, 0x2DB4 },
+{ 0x2DB5, 0x2DB5, 0x2DB5 },
+{ 0x2DB6, 0x2DB6, 0x2DB6 },
+{ 0x2DB8, 0x2DB8, 0x2DB8 },
+{ 0x2DB9, 0x2DB9, 0x2DB9 },
+{ 0x2DBA, 0x2DBA, 0x2DBA },
+{ 0x2DBB, 0x2DBB, 0x2DBB },
+{ 0x2DBC, 0x2DBC, 0x2DBC },
+{ 0x2DBD, 0x2DBD, 0x2DBD },
+{ 0x2DBE, 0x2DBE, 0x2DBE },
+{ 0x2DC0, 0x2DC0, 0x2DC0 },
+{ 0x2DC1, 0x2DC1, 0x2DC1 },
+{ 0x2DC2, 0x2DC2, 0x2DC2 },
+{ 0x2DC3, 0x2DC3, 0x2DC3 },
+{ 0x2DC4, 0x2DC4, 0x2DC4 },
+{ 0x2DC5, 0x2DC5, 0x2DC5 },
+{ 0x2DC6, 0x2DC6, 0x2DC6 },
+{ 0x2DC8, 0x2DC8, 0x2DC8 },
+{ 0x2DC9, 0x2DC9, 0x2DC9 },
+{ 0x2DCA, 0x2DCA, 0x2DCA },
+{ 0x2DCB, 0x2DCB, 0x2DCB },
+{ 0x2DCC, 0x2DCC, 0x2DCC },
+{ 0x2DCD, 0x2DCD, 0x2DCD },
+{ 0x2DCE, 0x2DCE, 0x2DCE },
+{ 0x2DD0, 0x2DD0, 0x2DD0 },
+{ 0x2DD1, 0x2DD1, 0x2DD1 },
+{ 0x2DD2, 0x2DD2, 0x2DD2 },
+{ 0x2DD3, 0x2DD3, 0x2DD3 },
+{ 0x2DD4, 0x2DD4, 0x2DD4 },
+{ 0x2DD5, 0x2DD5, 0x2DD5 },
+{ 0x2DD6, 0x2DD6, 0x2DD6 },
+{ 0x2DD8, 0x2DD8, 0x2DD8 },
+{ 0x2DD9, 0x2DD9, 0x2DD9 },
+{ 0x2DDA, 0x2DDA, 0x2DDA },
+{ 0x2DDB, 0x2DDB, 0x2DDB },
+{ 0x2DDC, 0x2DDC, 0x2DDC },
+{ 0x2DDD, 0x2DDD, 0x2DDD },
+{ 0x2DDE, 0x2DDE, 0x2DDE },
+{ 0x3005, 0x3005, 0x3005 },
+{ 0x3006, 0x3006, 0x3006 },
+{ 0x302A, 0x302A, 0x302A },
+{ 0x302B, 0x302B, 0x302B },
+{ 0x302C, 0x302C, 0x302C },
+{ 0x302D, 0x302D, 0x302D },
+{ 0x302E, 0x302E, 0x302E },
+{ 0x302F, 0x302F, 0x302F },
+{ 0x3031, 0x3031, 0x3031 },
+{ 0x3032, 0x3032, 0x3032 },
+{ 0x3033, 0x3033, 0x3033 },
+{ 0x3034, 0x3034, 0x3034 },
+{ 0x3035, 0x3035, 0x3035 },
+{ 0x303B, 0x303B, 0x303B },
+{ 0x303C, 0x303C, 0x303C },
+{ 0x3041, 0x3041, 0x3041 },
+{ 0x3042, 0x3042, 0x3042 },
+{ 0x3043, 0x3043, 0x3043 },
+{ 0x3044, 0x3044, 0x3044 },
+{ 0x3045, 0x3045, 0x3045 },
+{ 0x3046, 0x3046, 0x3046 },
+{ 0x3047, 0x3047, 0x3047 },
+{ 0x3048, 0x3048, 0x3048 },
+{ 0x3049, 0x3049, 0x3049 },
+{ 0x304A, 0x304A, 0x304A },
+{ 0x304B, 0x304B, 0x304B },
+{ 0x304C, 0x304C, 0x304C },
+{ 0x304D, 0x304D, 0x304D },
+{ 0x304E, 0x304E, 0x304E },
+{ 0x304F, 0x304F, 0x304F },
+{ 0x3050, 0x3050, 0x3050 },
+{ 0x3051, 0x3051, 0x3051 },
+{ 0x3052, 0x3052, 0x3052 },
+{ 0x3053, 0x3053, 0x3053 },
+{ 0x3054, 0x3054, 0x3054 },
+{ 0x3055, 0x3055, 0x3055 },
+{ 0x3056, 0x3056, 0x3056 },
+{ 0x3057, 0x3057, 0x3057 },
+{ 0x3058, 0x3058, 0x3058 },
+{ 0x3059, 0x3059, 0x3059 },
+{ 0x305A, 0x305A, 0x305A },
+{ 0x305B, 0x305B, 0x305B },
+{ 0x305C, 0x305C, 0x305C },
+{ 0x305D, 0x305D, 0x305D },
+{ 0x305E, 0x305E, 0x305E },
+{ 0x305F, 0x305F, 0x305F },
+{ 0x3060, 0x3060, 0x3060 },
+{ 0x3061, 0x3061, 0x3061 },
+{ 0x3062, 0x3062, 0x3062 },
+{ 0x3063, 0x3063, 0x3063 },
+{ 0x3064, 0x3064, 0x3064 },
+{ 0x3065, 0x3065, 0x3065 },
+{ 0x3066, 0x3066, 0x3066 },
+{ 0x3067, 0x3067, 0x3067 },
+{ 0x3068, 0x3068, 0x3068 },
+{ 0x3069, 0x3069, 0x3069 },
+{ 0x306A, 0x306A, 0x306A },
+{ 0x306B, 0x306B, 0x306B },
+{ 0x306C, 0x306C, 0x306C },
+{ 0x306D, 0x306D, 0x306D },
+{ 0x306E, 0x306E, 0x306E },
+{ 0x306F, 0x306F, 0x306F },
+{ 0x3070, 0x3070, 0x3070 },
+{ 0x3071, 0x3071, 0x3071 },
+{ 0x3072, 0x3072, 0x3072 },
+{ 0x3073, 0x3073, 0x3073 },
+{ 0x3074, 0x3074, 0x3074 },
+{ 0x3075, 0x3075, 0x3075 },
+{ 0x3076, 0x3076, 0x3076 },
+{ 0x3077, 0x3077, 0x3077 },
+{ 0x3078, 0x3078, 0x3078 },
+{ 0x3079, 0x3079, 0x3079 },
+{ 0x307A, 0x307A, 0x307A },
+{ 0x307B, 0x307B, 0x307B },
+{ 0x307C, 0x307C, 0x307C },
+{ 0x307D, 0x307D, 0x307D },
+{ 0x307E, 0x307E, 0x307E },
+{ 0x307F, 0x307F, 0x307F },
+{ 0x3080, 0x3080, 0x3080 },
+{ 0x3081, 0x3081, 0x3081 },
+{ 0x3082, 0x3082, 0x3082 },
+{ 0x3083, 0x3083, 0x3083 },
+{ 0x3084, 0x3084, 0x3084 },
+{ 0x3085, 0x3085, 0x3085 },
+{ 0x3086, 0x3086, 0x3086 },
+{ 0x3087, 0x3087, 0x3087 },
+{ 0x3088, 0x3088, 0x3088 },
+{ 0x3089, 0x3089, 0x3089 },
+{ 0x308A, 0x308A, 0x308A },
+{ 0x308B, 0x308B, 0x308B },
+{ 0x308C, 0x308C, 0x308C },
+{ 0x308D, 0x308D, 0x308D },
+{ 0x308E, 0x308E, 0x308E },
+{ 0x308F, 0x308F, 0x308F },
+{ 0x3090, 0x3090, 0x3090 },
+{ 0x3091, 0x3091, 0x3091 },
+{ 0x3092, 0x3092, 0x3092 },
+{ 0x3093, 0x3093, 0x3093 },
+{ 0x3094, 0x3094, 0x3094 },
+{ 0x3095, 0x3095, 0x3095 },
+{ 0x3096, 0x3096, 0x3096 },
+{ 0x3099, 0x3099, 0x3099 },
+{ 0x309A, 0x309A, 0x309A },
+{ 0x309D, 0x309D, 0x309D },
+{ 0x309E, 0x309E, 0x309E },
+{ 0x309F, 0x309F, 0x309F },
+{ 0x30A1, 0x30A1, 0x30A1 },
+{ 0x30A2, 0x30A2, 0x30A2 },
+{ 0x30A3, 0x30A3, 0x30A3 },
+{ 0x30A4, 0x30A4, 0x30A4 },
+{ 0x30A5, 0x30A5, 0x30A5 },
+{ 0x30A6, 0x30A6, 0x30A6 },
+{ 0x30A7, 0x30A7, 0x30A7 },
+{ 0x30A8, 0x30A8, 0x30A8 },
+{ 0x30A9, 0x30A9, 0x30A9 },
+{ 0x30AA, 0x30AA, 0x30AA },
+{ 0x30AB, 0x30AB, 0x30AB },
+{ 0x30AC, 0x30AC, 0x30AC },
+{ 0x30AD, 0x30AD, 0x30AD },
+{ 0x30AE, 0x30AE, 0x30AE },
+{ 0x30AF, 0x30AF, 0x30AF },
+{ 0x30B0, 0x30B0, 0x30B0 },
+{ 0x30B1, 0x30B1, 0x30B1 },
+{ 0x30B2, 0x30B2, 0x30B2 },
+{ 0x30B3, 0x30B3, 0x30B3 },
+{ 0x30B4, 0x30B4, 0x30B4 },
+{ 0x30B5, 0x30B5, 0x30B5 },
+{ 0x30B6, 0x30B6, 0x30B6 },
+{ 0x30B7, 0x30B7, 0x30B7 },
+{ 0x30B8, 0x30B8, 0x30B8 },
+{ 0x30B9, 0x30B9, 0x30B9 },
+{ 0x30BA, 0x30BA, 0x30BA },
+{ 0x30BB, 0x30BB, 0x30BB },
+{ 0x30BC, 0x30BC, 0x30BC },
+{ 0x30BD, 0x30BD, 0x30BD },
+{ 0x30BE, 0x30BE, 0x30BE },
+{ 0x30BF, 0x30BF, 0x30BF },
+{ 0x30C0, 0x30C0, 0x30C0 },
+{ 0x30C1, 0x30C1, 0x30C1 },
+{ 0x30C2, 0x30C2, 0x30C2 },
+{ 0x30C3, 0x30C3, 0x30C3 },
+{ 0x30C4, 0x30C4, 0x30C4 },
+{ 0x30C5, 0x30C5, 0x30C5 },
+{ 0x30C6, 0x30C6, 0x30C6 },
+{ 0x30C7, 0x30C7, 0x30C7 },
+{ 0x30C8, 0x30C8, 0x30C8 },
+{ 0x30C9, 0x30C9, 0x30C9 },
+{ 0x30CA, 0x30CA, 0x30CA },
+{ 0x30CB, 0x30CB, 0x30CB },
+{ 0x30CC, 0x30CC, 0x30CC },
+{ 0x30CD, 0x30CD, 0x30CD },
+{ 0x30CE, 0x30CE, 0x30CE },
+{ 0x30CF, 0x30CF, 0x30CF },
+{ 0x30D0, 0x30D0, 0x30D0 },
+{ 0x30D1, 0x30D1, 0x30D1 },
+{ 0x30D2, 0x30D2, 0x30D2 },
+{ 0x30D3, 0x30D3, 0x30D3 },
+{ 0x30D4, 0x30D4, 0x30D4 },
+{ 0x30D5, 0x30D5, 0x30D5 },
+{ 0x30D6, 0x30D6, 0x30D6 },
+{ 0x30D7, 0x30D7, 0x30D7 },
+{ 0x30D8, 0x30D8, 0x30D8 },
+{ 0x30D9, 0x30D9, 0x30D9 },
+{ 0x30DA, 0x30DA, 0x30DA },
+{ 0x30DB, 0x30DB, 0x30DB },
+{ 0x30DC, 0x30DC, 0x30DC },
+{ 0x30DD, 0x30DD, 0x30DD },
+{ 0x30DE, 0x30DE, 0x30DE },
+{ 0x30DF, 0x30DF, 0x30DF },
+{ 0x30E0, 0x30E0, 0x30E0 },
+{ 0x30E1, 0x30E1, 0x30E1 },
+{ 0x30E2, 0x30E2, 0x30E2 },
+{ 0x30E3, 0x30E3, 0x30E3 },
+{ 0x30E4, 0x30E4, 0x30E4 },
+{ 0x30E5, 0x30E5, 0x30E5 },
+{ 0x30E6, 0x30E6, 0x30E6 },
+{ 0x30E7, 0x30E7, 0x30E7 },
+{ 0x30E8, 0x30E8, 0x30E8 },
+{ 0x30E9, 0x30E9, 0x30E9 },
+{ 0x30EA, 0x30EA, 0x30EA },
+{ 0x30EB, 0x30EB, 0x30EB },
+{ 0x30EC, 0x30EC, 0x30EC },
+{ 0x30ED, 0x30ED, 0x30ED },
+{ 0x30EE, 0x30EE, 0x30EE },
+{ 0x30EF, 0x30EF, 0x30EF },
+{ 0x30F0, 0x30F0, 0x30F0 },
+{ 0x30F1, 0x30F1, 0x30F1 },
+{ 0x30F2, 0x30F2, 0x30F2 },
+{ 0x30F3, 0x30F3, 0x30F3 },
+{ 0x30F4, 0x30F4, 0x30F4 },
+{ 0x30F5, 0x30F5, 0x30F5 },
+{ 0x30F6, 0x30F6, 0x30F6 },
+{ 0x30F7, 0x30F7, 0x30F7 },
+{ 0x30F8, 0x30F8, 0x30F8 },
+{ 0x30F9, 0x30F9, 0x30F9 },
+{ 0x30FA, 0x30FA, 0x30FA },
+{ 0x30FC, 0x30FC, 0x30FC },
+{ 0x30FD, 0x30FD, 0x30FD },
+{ 0x30FE, 0x30FE, 0x30FE },
+{ 0x30FF, 0x30FF, 0x30FF },
+{ 0x3105, 0x3105, 0x3105 },
+{ 0x3106, 0x3106, 0x3106 },
+{ 0x3107, 0x3107, 0x3107 },
+{ 0x3108, 0x3108, 0x3108 },
+{ 0x3109, 0x3109, 0x3109 },
+{ 0x310A, 0x310A, 0x310A },
+{ 0x310B, 0x310B, 0x310B },
+{ 0x310C, 0x310C, 0x310C },
+{ 0x310D, 0x310D, 0x310D },
+{ 0x310E, 0x310E, 0x310E },
+{ 0x310F, 0x310F, 0x310F },
+{ 0x3110, 0x3110, 0x3110 },
+{ 0x3111, 0x3111, 0x3111 },
+{ 0x3112, 0x3112, 0x3112 },
+{ 0x3113, 0x3113, 0x3113 },
+{ 0x3114, 0x3114, 0x3114 },
+{ 0x3115, 0x3115, 0x3115 },
+{ 0x3116, 0x3116, 0x3116 },
+{ 0x3117, 0x3117, 0x3117 },
+{ 0x3118, 0x3118, 0x3118 },
+{ 0x3119, 0x3119, 0x3119 },
+{ 0x311A, 0x311A, 0x311A },
+{ 0x311B, 0x311B, 0x311B },
+{ 0x311C, 0x311C, 0x311C },
+{ 0x311D, 0x311D, 0x311D },
+{ 0x311E, 0x311E, 0x311E },
+{ 0x311F, 0x311F, 0x311F },
+{ 0x3120, 0x3120, 0x3120 },
+{ 0x3121, 0x3121, 0x3121 },
+{ 0x3122, 0x3122, 0x3122 },
+{ 0x3123, 0x3123, 0x3123 },
+{ 0x3124, 0x3124, 0x3124 },
+{ 0x3125, 0x3125, 0x3125 },
+{ 0x3126, 0x3126, 0x3126 },
+{ 0x3127, 0x3127, 0x3127 },
+{ 0x3128, 0x3128, 0x3128 },
+{ 0x3129, 0x3129, 0x3129 },
+{ 0x312A, 0x312A, 0x312A },
+{ 0x312B, 0x312B, 0x312B },
+{ 0x312C, 0x312C, 0x312C },
+{ 0x3131, 0x3131, 0x3131 },
+{ 0x3132, 0x3132, 0x3132 },
+{ 0x3133, 0x3133, 0x3133 },
+{ 0x3134, 0x3134, 0x3134 },
+{ 0x3135, 0x3135, 0x3135 },
+{ 0x3136, 0x3136, 0x3136 },
+{ 0x3137, 0x3137, 0x3137 },
+{ 0x3138, 0x3138, 0x3138 },
+{ 0x3139, 0x3139, 0x3139 },
+{ 0x313A, 0x313A, 0x313A },
+{ 0x313B, 0x313B, 0x313B },
+{ 0x313C, 0x313C, 0x313C },
+{ 0x313D, 0x313D, 0x313D },
+{ 0x313E, 0x313E, 0x313E },
+{ 0x313F, 0x313F, 0x313F },
+{ 0x3140, 0x3140, 0x3140 },
+{ 0x3141, 0x3141, 0x3141 },
+{ 0x3142, 0x3142, 0x3142 },
+{ 0x3143, 0x3143, 0x3143 },
+{ 0x3144, 0x3144, 0x3144 },
+{ 0x3145, 0x3145, 0x3145 },
+{ 0x3146, 0x3146, 0x3146 },
+{ 0x3147, 0x3147, 0x3147 },
+{ 0x3148, 0x3148, 0x3148 },
+{ 0x3149, 0x3149, 0x3149 },
+{ 0x314A, 0x314A, 0x314A },
+{ 0x314B, 0x314B, 0x314B },
+{ 0x314C, 0x314C, 0x314C },
+{ 0x314D, 0x314D, 0x314D },
+{ 0x314E, 0x314E, 0x314E },
+{ 0x314F, 0x314F, 0x314F },
+{ 0x3150, 0x3150, 0x3150 },
+{ 0x3151, 0x3151, 0x3151 },
+{ 0x3152, 0x3152, 0x3152 },
+{ 0x3153, 0x3153, 0x3153 },
+{ 0x3154, 0x3154, 0x3154 },
+{ 0x3155, 0x3155, 0x3155 },
+{ 0x3156, 0x3156, 0x3156 },
+{ 0x3157, 0x3157, 0x3157 },
+{ 0x3158, 0x3158, 0x3158 },
+{ 0x3159, 0x3159, 0x3159 },
+{ 0x315A, 0x315A, 0x315A },
+{ 0x315B, 0x315B, 0x315B },
+{ 0x315C, 0x315C, 0x315C },
+{ 0x315D, 0x315D, 0x315D },
+{ 0x315E, 0x315E, 0x315E },
+{ 0x315F, 0x315F, 0x315F },
+{ 0x3160, 0x3160, 0x3160 },
+{ 0x3161, 0x3161, 0x3161 },
+{ 0x3162, 0x3162, 0x3162 },
+{ 0x3163, 0x3163, 0x3163 },
+{ 0x3164, 0x3164, 0x3164 },
+{ 0x3165, 0x3165, 0x3165 },
+{ 0x3166, 0x3166, 0x3166 },
+{ 0x3167, 0x3167, 0x3167 },
+{ 0x3168, 0x3168, 0x3168 },
+{ 0x3169, 0x3169, 0x3169 },
+{ 0x316A, 0x316A, 0x316A },
+{ 0x316B, 0x316B, 0x316B },
+{ 0x316C, 0x316C, 0x316C },
+{ 0x316D, 0x316D, 0x316D },
+{ 0x316E, 0x316E, 0x316E },
+{ 0x316F, 0x316F, 0x316F },
+{ 0x3170, 0x3170, 0x3170 },
+{ 0x3171, 0x3171, 0x3171 },
+{ 0x3172, 0x3172, 0x3172 },
+{ 0x3173, 0x3173, 0x3173 },
+{ 0x3174, 0x3174, 0x3174 },
+{ 0x3175, 0x3175, 0x3175 },
+{ 0x3176, 0x3176, 0x3176 },
+{ 0x3177, 0x3177, 0x3177 },
+{ 0x3178, 0x3178, 0x3178 },
+{ 0x3179, 0x3179, 0x3179 },
+{ 0x317A, 0x317A, 0x317A },
+{ 0x317B, 0x317B, 0x317B },
+{ 0x317C, 0x317C, 0x317C },
+{ 0x317D, 0x317D, 0x317D },
+{ 0x317E, 0x317E, 0x317E },
+{ 0x317F, 0x317F, 0x317F },
+{ 0x3180, 0x3180, 0x3180 },
+{ 0x3181, 0x3181, 0x3181 },
+{ 0x3182, 0x3182, 0x3182 },
+{ 0x3183, 0x3183, 0x3183 },
+{ 0x3184, 0x3184, 0x3184 },
+{ 0x3185, 0x3185, 0x3185 },
+{ 0x3186, 0x3186, 0x3186 },
+{ 0x3187, 0x3187, 0x3187 },
+{ 0x3188, 0x3188, 0x3188 },
+{ 0x3189, 0x3189, 0x3189 },
+{ 0x318A, 0x318A, 0x318A },
+{ 0x318B, 0x318B, 0x318B },
+{ 0x318C, 0x318C, 0x318C },
+{ 0x318D, 0x318D, 0x318D },
+{ 0x318E, 0x318E, 0x318E },
+{ 0x31A0, 0x31A0, 0x31A0 },
+{ 0x31A1, 0x31A1, 0x31A1 },
+{ 0x31A2, 0x31A2, 0x31A2 },
+{ 0x31A3, 0x31A3, 0x31A3 },
+{ 0x31A4, 0x31A4, 0x31A4 },
+{ 0x31A5, 0x31A5, 0x31A5 },
+{ 0x31A6, 0x31A6, 0x31A6 },
+{ 0x31A7, 0x31A7, 0x31A7 },
+{ 0x31A8, 0x31A8, 0x31A8 },
+{ 0x31A9, 0x31A9, 0x31A9 },
+{ 0x31AA, 0x31AA, 0x31AA },
+{ 0x31AB, 0x31AB, 0x31AB },
+{ 0x31AC, 0x31AC, 0x31AC },
+{ 0x31AD, 0x31AD, 0x31AD },
+{ 0x31AE, 0x31AE, 0x31AE },
+{ 0x31AF, 0x31AF, 0x31AF },
+{ 0x31B0, 0x31B0, 0x31B0 },
+{ 0x31B1, 0x31B1, 0x31B1 },
+{ 0x31B2, 0x31B2, 0x31B2 },
+{ 0x31B3, 0x31B3, 0x31B3 },
+{ 0x31B4, 0x31B4, 0x31B4 },
+{ 0x31B5, 0x31B5, 0x31B5 },
+{ 0x31B6, 0x31B6, 0x31B6 },
+{ 0x31B7, 0x31B7, 0x31B7 },
+{ 0x31F0, 0x31F0, 0x31F0 },
+{ 0x31F1, 0x31F1, 0x31F1 },
+{ 0x31F2, 0x31F2, 0x31F2 },
+{ 0x31F3, 0x31F3, 0x31F3 },
+{ 0x31F4, 0x31F4, 0x31F4 },
+{ 0x31F5, 0x31F5, 0x31F5 },
+{ 0x31F6, 0x31F6, 0x31F6 },
+{ 0x31F7, 0x31F7, 0x31F7 },
+{ 0x31F8, 0x31F8, 0x31F8 },
+{ 0x31F9, 0x31F9, 0x31F9 },
+{ 0x31FA, 0x31FA, 0x31FA },
+{ 0x31FB, 0x31FB, 0x31FB },
+{ 0x31FC, 0x31FC, 0x31FC },
+{ 0x31FD, 0x31FD, 0x31FD },
+{ 0x31FE, 0x31FE, 0x31FE },
+{ 0x31FF, 0x31FF, 0x31FF },
+{ 0x3400, 0x3400, 0x3400 },
+{ 0x4DB5, 0x4DB5, 0x4DB5 },
+{ 0x4E00, 0x4E00, 0x4E00 },
+{ 0x9FBB, 0x9FBB, 0x9FBB },
+{ 0xA000, 0xA000, 0xA000 },
+{ 0xA001, 0xA001, 0xA001 },
+{ 0xA002, 0xA002, 0xA002 },
+{ 0xA003, 0xA003, 0xA003 },
+{ 0xA004, 0xA004, 0xA004 },
+{ 0xA005, 0xA005, 0xA005 },
+{ 0xA006, 0xA006, 0xA006 },
+{ 0xA007, 0xA007, 0xA007 },
+{ 0xA008, 0xA008, 0xA008 },
+{ 0xA009, 0xA009, 0xA009 },
+{ 0xA00A, 0xA00A, 0xA00A },
+{ 0xA00B, 0xA00B, 0xA00B },
+{ 0xA00C, 0xA00C, 0xA00C },
+{ 0xA00D, 0xA00D, 0xA00D },
+{ 0xA00E, 0xA00E, 0xA00E },
+{ 0xA00F, 0xA00F, 0xA00F },
+{ 0xA010, 0xA010, 0xA010 },
+{ 0xA011, 0xA011, 0xA011 },
+{ 0xA012, 0xA012, 0xA012 },
+{ 0xA013, 0xA013, 0xA013 },
+{ 0xA014, 0xA014, 0xA014 },
+{ 0xA015, 0xA015, 0xA015 },
+{ 0xA016, 0xA016, 0xA016 },
+{ 0xA017, 0xA017, 0xA017 },
+{ 0xA018, 0xA018, 0xA018 },
+{ 0xA019, 0xA019, 0xA019 },
+{ 0xA01A, 0xA01A, 0xA01A },
+{ 0xA01B, 0xA01B, 0xA01B },
+{ 0xA01C, 0xA01C, 0xA01C },
+{ 0xA01D, 0xA01D, 0xA01D },
+{ 0xA01E, 0xA01E, 0xA01E },
+{ 0xA01F, 0xA01F, 0xA01F },
+{ 0xA020, 0xA020, 0xA020 },
+{ 0xA021, 0xA021, 0xA021 },
+{ 0xA022, 0xA022, 0xA022 },
+{ 0xA023, 0xA023, 0xA023 },
+{ 0xA024, 0xA024, 0xA024 },
+{ 0xA025, 0xA025, 0xA025 },
+{ 0xA026, 0xA026, 0xA026 },
+{ 0xA027, 0xA027, 0xA027 },
+{ 0xA028, 0xA028, 0xA028 },
+{ 0xA029, 0xA029, 0xA029 },
+{ 0xA02A, 0xA02A, 0xA02A },
+{ 0xA02B, 0xA02B, 0xA02B },
+{ 0xA02C, 0xA02C, 0xA02C },
+{ 0xA02D, 0xA02D, 0xA02D },
+{ 0xA02E, 0xA02E, 0xA02E },
+{ 0xA02F, 0xA02F, 0xA02F },
+{ 0xA030, 0xA030, 0xA030 },
+{ 0xA031, 0xA031, 0xA031 },
+{ 0xA032, 0xA032, 0xA032 },
+{ 0xA033, 0xA033, 0xA033 },
+{ 0xA034, 0xA034, 0xA034 },
+{ 0xA035, 0xA035, 0xA035 },
+{ 0xA036, 0xA036, 0xA036 },
+{ 0xA037, 0xA037, 0xA037 },
+{ 0xA038, 0xA038, 0xA038 },
+{ 0xA039, 0xA039, 0xA039 },
+{ 0xA03A, 0xA03A, 0xA03A },
+{ 0xA03B, 0xA03B, 0xA03B },
+{ 0xA03C, 0xA03C, 0xA03C },
+{ 0xA03D, 0xA03D, 0xA03D },
+{ 0xA03E, 0xA03E, 0xA03E },
+{ 0xA03F, 0xA03F, 0xA03F },
+{ 0xA040, 0xA040, 0xA040 },
+{ 0xA041, 0xA041, 0xA041 },
+{ 0xA042, 0xA042, 0xA042 },
+{ 0xA043, 0xA043, 0xA043 },
+{ 0xA044, 0xA044, 0xA044 },
+{ 0xA045, 0xA045, 0xA045 },
+{ 0xA046, 0xA046, 0xA046 },
+{ 0xA047, 0xA047, 0xA047 },
+{ 0xA048, 0xA048, 0xA048 },
+{ 0xA049, 0xA049, 0xA049 },
+{ 0xA04A, 0xA04A, 0xA04A },
+{ 0xA04B, 0xA04B, 0xA04B },
+{ 0xA04C, 0xA04C, 0xA04C },
+{ 0xA04D, 0xA04D, 0xA04D },
+{ 0xA04E, 0xA04E, 0xA04E },
+{ 0xA04F, 0xA04F, 0xA04F },
+{ 0xA050, 0xA050, 0xA050 },
+{ 0xA051, 0xA051, 0xA051 },
+{ 0xA052, 0xA052, 0xA052 },
+{ 0xA053, 0xA053, 0xA053 },
+{ 0xA054, 0xA054, 0xA054 },
+{ 0xA055, 0xA055, 0xA055 },
+{ 0xA056, 0xA056, 0xA056 },
+{ 0xA057, 0xA057, 0xA057 },
+{ 0xA058, 0xA058, 0xA058 },
+{ 0xA059, 0xA059, 0xA059 },
+{ 0xA05A, 0xA05A, 0xA05A },
+{ 0xA05B, 0xA05B, 0xA05B },
+{ 0xA05C, 0xA05C, 0xA05C },
+{ 0xA05D, 0xA05D, 0xA05D },
+{ 0xA05E, 0xA05E, 0xA05E },
+{ 0xA05F, 0xA05F, 0xA05F },
+{ 0xA060, 0xA060, 0xA060 },
+{ 0xA061, 0xA061, 0xA061 },
+{ 0xA062, 0xA062, 0xA062 },
+{ 0xA063, 0xA063, 0xA063 },
+{ 0xA064, 0xA064, 0xA064 },
+{ 0xA065, 0xA065, 0xA065 },
+{ 0xA066, 0xA066, 0xA066 },
+{ 0xA067, 0xA067, 0xA067 },
+{ 0xA068, 0xA068, 0xA068 },
+{ 0xA069, 0xA069, 0xA069 },
+{ 0xA06A, 0xA06A, 0xA06A },
+{ 0xA06B, 0xA06B, 0xA06B },
+{ 0xA06C, 0xA06C, 0xA06C },
+{ 0xA06D, 0xA06D, 0xA06D },
+{ 0xA06E, 0xA06E, 0xA06E },
+{ 0xA06F, 0xA06F, 0xA06F },
+{ 0xA070, 0xA070, 0xA070 },
+{ 0xA071, 0xA071, 0xA071 },
+{ 0xA072, 0xA072, 0xA072 },
+{ 0xA073, 0xA073, 0xA073 },
+{ 0xA074, 0xA074, 0xA074 },
+{ 0xA075, 0xA075, 0xA075 },
+{ 0xA076, 0xA076, 0xA076 },
+{ 0xA077, 0xA077, 0xA077 },
+{ 0xA078, 0xA078, 0xA078 },
+{ 0xA079, 0xA079, 0xA079 },
+{ 0xA07A, 0xA07A, 0xA07A },
+{ 0xA07B, 0xA07B, 0xA07B },
+{ 0xA07C, 0xA07C, 0xA07C },
+{ 0xA07D, 0xA07D, 0xA07D },
+{ 0xA07E, 0xA07E, 0xA07E },
+{ 0xA07F, 0xA07F, 0xA07F },
+{ 0xA080, 0xA080, 0xA080 },
+{ 0xA081, 0xA081, 0xA081 },
+{ 0xA082, 0xA082, 0xA082 },
+{ 0xA083, 0xA083, 0xA083 },
+{ 0xA084, 0xA084, 0xA084 },
+{ 0xA085, 0xA085, 0xA085 },
+{ 0xA086, 0xA086, 0xA086 },
+{ 0xA087, 0xA087, 0xA087 },
+{ 0xA088, 0xA088, 0xA088 },
+{ 0xA089, 0xA089, 0xA089 },
+{ 0xA08A, 0xA08A, 0xA08A },
+{ 0xA08B, 0xA08B, 0xA08B },
+{ 0xA08C, 0xA08C, 0xA08C },
+{ 0xA08D, 0xA08D, 0xA08D },
+{ 0xA08E, 0xA08E, 0xA08E },
+{ 0xA08F, 0xA08F, 0xA08F },
+{ 0xA090, 0xA090, 0xA090 },
+{ 0xA091, 0xA091, 0xA091 },
+{ 0xA092, 0xA092, 0xA092 },
+{ 0xA093, 0xA093, 0xA093 },
+{ 0xA094, 0xA094, 0xA094 },
+{ 0xA095, 0xA095, 0xA095 },
+{ 0xA096, 0xA096, 0xA096 },
+{ 0xA097, 0xA097, 0xA097 },
+{ 0xA098, 0xA098, 0xA098 },
+{ 0xA099, 0xA099, 0xA099 },
+{ 0xA09A, 0xA09A, 0xA09A },
+{ 0xA09B, 0xA09B, 0xA09B },
+{ 0xA09C, 0xA09C, 0xA09C },
+{ 0xA09D, 0xA09D, 0xA09D },
+{ 0xA09E, 0xA09E, 0xA09E },
+{ 0xA09F, 0xA09F, 0xA09F },
+{ 0xA0A0, 0xA0A0, 0xA0A0 },
+{ 0xA0A1, 0xA0A1, 0xA0A1 },
+{ 0xA0A2, 0xA0A2, 0xA0A2 },
+{ 0xA0A3, 0xA0A3, 0xA0A3 },
+{ 0xA0A4, 0xA0A4, 0xA0A4 },
+{ 0xA0A5, 0xA0A5, 0xA0A5 },
+{ 0xA0A6, 0xA0A6, 0xA0A6 },
+{ 0xA0A7, 0xA0A7, 0xA0A7 },
+{ 0xA0A8, 0xA0A8, 0xA0A8 },
+{ 0xA0A9, 0xA0A9, 0xA0A9 },
+{ 0xA0AA, 0xA0AA, 0xA0AA },
+{ 0xA0AB, 0xA0AB, 0xA0AB },
+{ 0xA0AC, 0xA0AC, 0xA0AC },
+{ 0xA0AD, 0xA0AD, 0xA0AD },
+{ 0xA0AE, 0xA0AE, 0xA0AE },
+{ 0xA0AF, 0xA0AF, 0xA0AF },
+{ 0xA0B0, 0xA0B0, 0xA0B0 },
+{ 0xA0B1, 0xA0B1, 0xA0B1 },
+{ 0xA0B2, 0xA0B2, 0xA0B2 },
+{ 0xA0B3, 0xA0B3, 0xA0B3 },
+{ 0xA0B4, 0xA0B4, 0xA0B4 },
+{ 0xA0B5, 0xA0B5, 0xA0B5 },
+{ 0xA0B6, 0xA0B6, 0xA0B6 },
+{ 0xA0B7, 0xA0B7, 0xA0B7 },
+{ 0xA0B8, 0xA0B8, 0xA0B8 },
+{ 0xA0B9, 0xA0B9, 0xA0B9 },
+{ 0xA0BA, 0xA0BA, 0xA0BA },
+{ 0xA0BB, 0xA0BB, 0xA0BB },
+{ 0xA0BC, 0xA0BC, 0xA0BC },
+{ 0xA0BD, 0xA0BD, 0xA0BD },
+{ 0xA0BE, 0xA0BE, 0xA0BE },
+{ 0xA0BF, 0xA0BF, 0xA0BF },
+{ 0xA0C0, 0xA0C0, 0xA0C0 },
+{ 0xA0C1, 0xA0C1, 0xA0C1 },
+{ 0xA0C2, 0xA0C2, 0xA0C2 },
+{ 0xA0C3, 0xA0C3, 0xA0C3 },
+{ 0xA0C4, 0xA0C4, 0xA0C4 },
+{ 0xA0C5, 0xA0C5, 0xA0C5 },
+{ 0xA0C6, 0xA0C6, 0xA0C6 },
+{ 0xA0C7, 0xA0C7, 0xA0C7 },
+{ 0xA0C8, 0xA0C8, 0xA0C8 },
+{ 0xA0C9, 0xA0C9, 0xA0C9 },
+{ 0xA0CA, 0xA0CA, 0xA0CA },
+{ 0xA0CB, 0xA0CB, 0xA0CB },
+{ 0xA0CC, 0xA0CC, 0xA0CC },
+{ 0xA0CD, 0xA0CD, 0xA0CD },
+{ 0xA0CE, 0xA0CE, 0xA0CE },
+{ 0xA0CF, 0xA0CF, 0xA0CF },
+{ 0xA0D0, 0xA0D0, 0xA0D0 },
+{ 0xA0D1, 0xA0D1, 0xA0D1 },
+{ 0xA0D2, 0xA0D2, 0xA0D2 },
+{ 0xA0D3, 0xA0D3, 0xA0D3 },
+{ 0xA0D4, 0xA0D4, 0xA0D4 },
+{ 0xA0D5, 0xA0D5, 0xA0D5 },
+{ 0xA0D6, 0xA0D6, 0xA0D6 },
+{ 0xA0D7, 0xA0D7, 0xA0D7 },
+{ 0xA0D8, 0xA0D8, 0xA0D8 },
+{ 0xA0D9, 0xA0D9, 0xA0D9 },
+{ 0xA0DA, 0xA0DA, 0xA0DA },
+{ 0xA0DB, 0xA0DB, 0xA0DB },
+{ 0xA0DC, 0xA0DC, 0xA0DC },
+{ 0xA0DD, 0xA0DD, 0xA0DD },
+{ 0xA0DE, 0xA0DE, 0xA0DE },
+{ 0xA0DF, 0xA0DF, 0xA0DF },
+{ 0xA0E0, 0xA0E0, 0xA0E0 },
+{ 0xA0E1, 0xA0E1, 0xA0E1 },
+{ 0xA0E2, 0xA0E2, 0xA0E2 },
+{ 0xA0E3, 0xA0E3, 0xA0E3 },
+{ 0xA0E4, 0xA0E4, 0xA0E4 },
+{ 0xA0E5, 0xA0E5, 0xA0E5 },
+{ 0xA0E6, 0xA0E6, 0xA0E6 },
+{ 0xA0E7, 0xA0E7, 0xA0E7 },
+{ 0xA0E8, 0xA0E8, 0xA0E8 },
+{ 0xA0E9, 0xA0E9, 0xA0E9 },
+{ 0xA0EA, 0xA0EA, 0xA0EA },
+{ 0xA0EB, 0xA0EB, 0xA0EB },
+{ 0xA0EC, 0xA0EC, 0xA0EC },
+{ 0xA0ED, 0xA0ED, 0xA0ED },
+{ 0xA0EE, 0xA0EE, 0xA0EE },
+{ 0xA0EF, 0xA0EF, 0xA0EF },
+{ 0xA0F0, 0xA0F0, 0xA0F0 },
+{ 0xA0F1, 0xA0F1, 0xA0F1 },
+{ 0xA0F2, 0xA0F2, 0xA0F2 },
+{ 0xA0F3, 0xA0F3, 0xA0F3 },
+{ 0xA0F4, 0xA0F4, 0xA0F4 },
+{ 0xA0F5, 0xA0F5, 0xA0F5 },
+{ 0xA0F6, 0xA0F6, 0xA0F6 },
+{ 0xA0F7, 0xA0F7, 0xA0F7 },
+{ 0xA0F8, 0xA0F8, 0xA0F8 },
+{ 0xA0F9, 0xA0F9, 0xA0F9 },
+{ 0xA0FA, 0xA0FA, 0xA0FA },
+{ 0xA0FB, 0xA0FB, 0xA0FB },
+{ 0xA0FC, 0xA0FC, 0xA0FC },
+{ 0xA0FD, 0xA0FD, 0xA0FD },
+{ 0xA0FE, 0xA0FE, 0xA0FE },
+{ 0xA0FF, 0xA0FF, 0xA0FF },
+{ 0xA100, 0xA100, 0xA100 },
+{ 0xA101, 0xA101, 0xA101 },
+{ 0xA102, 0xA102, 0xA102 },
+{ 0xA103, 0xA103, 0xA103 },
+{ 0xA104, 0xA104, 0xA104 },
+{ 0xA105, 0xA105, 0xA105 },
+{ 0xA106, 0xA106, 0xA106 },
+{ 0xA107, 0xA107, 0xA107 },
+{ 0xA108, 0xA108, 0xA108 },
+{ 0xA109, 0xA109, 0xA109 },
+{ 0xA10A, 0xA10A, 0xA10A },
+{ 0xA10B, 0xA10B, 0xA10B },
+{ 0xA10C, 0xA10C, 0xA10C },
+{ 0xA10D, 0xA10D, 0xA10D },
+{ 0xA10E, 0xA10E, 0xA10E },
+{ 0xA10F, 0xA10F, 0xA10F },
+{ 0xA110, 0xA110, 0xA110 },
+{ 0xA111, 0xA111, 0xA111 },
+{ 0xA112, 0xA112, 0xA112 },
+{ 0xA113, 0xA113, 0xA113 },
+{ 0xA114, 0xA114, 0xA114 },
+{ 0xA115, 0xA115, 0xA115 },
+{ 0xA116, 0xA116, 0xA116 },
+{ 0xA117, 0xA117, 0xA117 },
+{ 0xA118, 0xA118, 0xA118 },
+{ 0xA119, 0xA119, 0xA119 },
+{ 0xA11A, 0xA11A, 0xA11A },
+{ 0xA11B, 0xA11B, 0xA11B },
+{ 0xA11C, 0xA11C, 0xA11C },
+{ 0xA11D, 0xA11D, 0xA11D },
+{ 0xA11E, 0xA11E, 0xA11E },
+{ 0xA11F, 0xA11F, 0xA11F },
+{ 0xA120, 0xA120, 0xA120 },
+{ 0xA121, 0xA121, 0xA121 },
+{ 0xA122, 0xA122, 0xA122 },
+{ 0xA123, 0xA123, 0xA123 },
+{ 0xA124, 0xA124, 0xA124 },
+{ 0xA125, 0xA125, 0xA125 },
+{ 0xA126, 0xA126, 0xA126 },
+{ 0xA127, 0xA127, 0xA127 },
+{ 0xA128, 0xA128, 0xA128 },
+{ 0xA129, 0xA129, 0xA129 },
+{ 0xA12A, 0xA12A, 0xA12A },
+{ 0xA12B, 0xA12B, 0xA12B },
+{ 0xA12C, 0xA12C, 0xA12C },
+{ 0xA12D, 0xA12D, 0xA12D },
+{ 0xA12E, 0xA12E, 0xA12E },
+{ 0xA12F, 0xA12F, 0xA12F },
+{ 0xA130, 0xA130, 0xA130 },
+{ 0xA131, 0xA131, 0xA131 },
+{ 0xA132, 0xA132, 0xA132 },
+{ 0xA133, 0xA133, 0xA133 },
+{ 0xA134, 0xA134, 0xA134 },
+{ 0xA135, 0xA135, 0xA135 },
+{ 0xA136, 0xA136, 0xA136 },
+{ 0xA137, 0xA137, 0xA137 },
+{ 0xA138, 0xA138, 0xA138 },
+{ 0xA139, 0xA139, 0xA139 },
+{ 0xA13A, 0xA13A, 0xA13A },
+{ 0xA13B, 0xA13B, 0xA13B },
+{ 0xA13C, 0xA13C, 0xA13C },
+{ 0xA13D, 0xA13D, 0xA13D },
+{ 0xA13E, 0xA13E, 0xA13E },
+{ 0xA13F, 0xA13F, 0xA13F },
+{ 0xA140, 0xA140, 0xA140 },
+{ 0xA141, 0xA141, 0xA141 },
+{ 0xA142, 0xA142, 0xA142 },
+{ 0xA143, 0xA143, 0xA143 },
+{ 0xA144, 0xA144, 0xA144 },
+{ 0xA145, 0xA145, 0xA145 },
+{ 0xA146, 0xA146, 0xA146 },
+{ 0xA147, 0xA147, 0xA147 },
+{ 0xA148, 0xA148, 0xA148 },
+{ 0xA149, 0xA149, 0xA149 },
+{ 0xA14A, 0xA14A, 0xA14A },
+{ 0xA14B, 0xA14B, 0xA14B },
+{ 0xA14C, 0xA14C, 0xA14C },
+{ 0xA14D, 0xA14D, 0xA14D },
+{ 0xA14E, 0xA14E, 0xA14E },
+{ 0xA14F, 0xA14F, 0xA14F },
+{ 0xA150, 0xA150, 0xA150 },
+{ 0xA151, 0xA151, 0xA151 },
+{ 0xA152, 0xA152, 0xA152 },
+{ 0xA153, 0xA153, 0xA153 },
+{ 0xA154, 0xA154, 0xA154 },
+{ 0xA155, 0xA155, 0xA155 },
+{ 0xA156, 0xA156, 0xA156 },
+{ 0xA157, 0xA157, 0xA157 },
+{ 0xA158, 0xA158, 0xA158 },
+{ 0xA159, 0xA159, 0xA159 },
+{ 0xA15A, 0xA15A, 0xA15A },
+{ 0xA15B, 0xA15B, 0xA15B },
+{ 0xA15C, 0xA15C, 0xA15C },
+{ 0xA15D, 0xA15D, 0xA15D },
+{ 0xA15E, 0xA15E, 0xA15E },
+{ 0xA15F, 0xA15F, 0xA15F },
+{ 0xA160, 0xA160, 0xA160 },
+{ 0xA161, 0xA161, 0xA161 },
+{ 0xA162, 0xA162, 0xA162 },
+{ 0xA163, 0xA163, 0xA163 },
+{ 0xA164, 0xA164, 0xA164 },
+{ 0xA165, 0xA165, 0xA165 },
+{ 0xA166, 0xA166, 0xA166 },
+{ 0xA167, 0xA167, 0xA167 },
+{ 0xA168, 0xA168, 0xA168 },
+{ 0xA169, 0xA169, 0xA169 },
+{ 0xA16A, 0xA16A, 0xA16A },
+{ 0xA16B, 0xA16B, 0xA16B },
+{ 0xA16C, 0xA16C, 0xA16C },
+{ 0xA16D, 0xA16D, 0xA16D },
+{ 0xA16E, 0xA16E, 0xA16E },
+{ 0xA16F, 0xA16F, 0xA16F },
+{ 0xA170, 0xA170, 0xA170 },
+{ 0xA171, 0xA171, 0xA171 },
+{ 0xA172, 0xA172, 0xA172 },
+{ 0xA173, 0xA173, 0xA173 },
+{ 0xA174, 0xA174, 0xA174 },
+{ 0xA175, 0xA175, 0xA175 },
+{ 0xA176, 0xA176, 0xA176 },
+{ 0xA177, 0xA177, 0xA177 },
+{ 0xA178, 0xA178, 0xA178 },
+{ 0xA179, 0xA179, 0xA179 },
+{ 0xA17A, 0xA17A, 0xA17A },
+{ 0xA17B, 0xA17B, 0xA17B },
+{ 0xA17C, 0xA17C, 0xA17C },
+{ 0xA17D, 0xA17D, 0xA17D },
+{ 0xA17E, 0xA17E, 0xA17E },
+{ 0xA17F, 0xA17F, 0xA17F },
+{ 0xA180, 0xA180, 0xA180 },
+{ 0xA181, 0xA181, 0xA181 },
+{ 0xA182, 0xA182, 0xA182 },
+{ 0xA183, 0xA183, 0xA183 },
+{ 0xA184, 0xA184, 0xA184 },
+{ 0xA185, 0xA185, 0xA185 },
+{ 0xA186, 0xA186, 0xA186 },
+{ 0xA187, 0xA187, 0xA187 },
+{ 0xA188, 0xA188, 0xA188 },
+{ 0xA189, 0xA189, 0xA189 },
+{ 0xA18A, 0xA18A, 0xA18A },
+{ 0xA18B, 0xA18B, 0xA18B },
+{ 0xA18C, 0xA18C, 0xA18C },
+{ 0xA18D, 0xA18D, 0xA18D },
+{ 0xA18E, 0xA18E, 0xA18E },
+{ 0xA18F, 0xA18F, 0xA18F },
+{ 0xA190, 0xA190, 0xA190 },
+{ 0xA191, 0xA191, 0xA191 },
+{ 0xA192, 0xA192, 0xA192 },
+{ 0xA193, 0xA193, 0xA193 },
+{ 0xA194, 0xA194, 0xA194 },
+{ 0xA195, 0xA195, 0xA195 },
+{ 0xA196, 0xA196, 0xA196 },
+{ 0xA197, 0xA197, 0xA197 },
+{ 0xA198, 0xA198, 0xA198 },
+{ 0xA199, 0xA199, 0xA199 },
+{ 0xA19A, 0xA19A, 0xA19A },
+{ 0xA19B, 0xA19B, 0xA19B },
+{ 0xA19C, 0xA19C, 0xA19C },
+{ 0xA19D, 0xA19D, 0xA19D },
+{ 0xA19E, 0xA19E, 0xA19E },
+{ 0xA19F, 0xA19F, 0xA19F },
+{ 0xA1A0, 0xA1A0, 0xA1A0 },
+{ 0xA1A1, 0xA1A1, 0xA1A1 },
+{ 0xA1A2, 0xA1A2, 0xA1A2 },
+{ 0xA1A3, 0xA1A3, 0xA1A3 },
+{ 0xA1A4, 0xA1A4, 0xA1A4 },
+{ 0xA1A5, 0xA1A5, 0xA1A5 },
+{ 0xA1A6, 0xA1A6, 0xA1A6 },
+{ 0xA1A7, 0xA1A7, 0xA1A7 },
+{ 0xA1A8, 0xA1A8, 0xA1A8 },
+{ 0xA1A9, 0xA1A9, 0xA1A9 },
+{ 0xA1AA, 0xA1AA, 0xA1AA },
+{ 0xA1AB, 0xA1AB, 0xA1AB },
+{ 0xA1AC, 0xA1AC, 0xA1AC },
+{ 0xA1AD, 0xA1AD, 0xA1AD },
+{ 0xA1AE, 0xA1AE, 0xA1AE },
+{ 0xA1AF, 0xA1AF, 0xA1AF },
+{ 0xA1B0, 0xA1B0, 0xA1B0 },
+{ 0xA1B1, 0xA1B1, 0xA1B1 },
+{ 0xA1B2, 0xA1B2, 0xA1B2 },
+{ 0xA1B3, 0xA1B3, 0xA1B3 },
+{ 0xA1B4, 0xA1B4, 0xA1B4 },
+{ 0xA1B5, 0xA1B5, 0xA1B5 },
+{ 0xA1B6, 0xA1B6, 0xA1B6 },
+{ 0xA1B7, 0xA1B7, 0xA1B7 },
+{ 0xA1B8, 0xA1B8, 0xA1B8 },
+{ 0xA1B9, 0xA1B9, 0xA1B9 },
+{ 0xA1BA, 0xA1BA, 0xA1BA },
+{ 0xA1BB, 0xA1BB, 0xA1BB },
+{ 0xA1BC, 0xA1BC, 0xA1BC },
+{ 0xA1BD, 0xA1BD, 0xA1BD },
+{ 0xA1BE, 0xA1BE, 0xA1BE },
+{ 0xA1BF, 0xA1BF, 0xA1BF },
+{ 0xA1C0, 0xA1C0, 0xA1C0 },
+{ 0xA1C1, 0xA1C1, 0xA1C1 },
+{ 0xA1C2, 0xA1C2, 0xA1C2 },
+{ 0xA1C3, 0xA1C3, 0xA1C3 },
+{ 0xA1C4, 0xA1C4, 0xA1C4 },
+{ 0xA1C5, 0xA1C5, 0xA1C5 },
+{ 0xA1C6, 0xA1C6, 0xA1C6 },
+{ 0xA1C7, 0xA1C7, 0xA1C7 },
+{ 0xA1C8, 0xA1C8, 0xA1C8 },
+{ 0xA1C9, 0xA1C9, 0xA1C9 },
+{ 0xA1CA, 0xA1CA, 0xA1CA },
+{ 0xA1CB, 0xA1CB, 0xA1CB },
+{ 0xA1CC, 0xA1CC, 0xA1CC },
+{ 0xA1CD, 0xA1CD, 0xA1CD },
+{ 0xA1CE, 0xA1CE, 0xA1CE },
+{ 0xA1CF, 0xA1CF, 0xA1CF },
+{ 0xA1D0, 0xA1D0, 0xA1D0 },
+{ 0xA1D1, 0xA1D1, 0xA1D1 },
+{ 0xA1D2, 0xA1D2, 0xA1D2 },
+{ 0xA1D3, 0xA1D3, 0xA1D3 },
+{ 0xA1D4, 0xA1D4, 0xA1D4 },
+{ 0xA1D5, 0xA1D5, 0xA1D5 },
+{ 0xA1D6, 0xA1D6, 0xA1D6 },
+{ 0xA1D7, 0xA1D7, 0xA1D7 },
+{ 0xA1D8, 0xA1D8, 0xA1D8 },
+{ 0xA1D9, 0xA1D9, 0xA1D9 },
+{ 0xA1DA, 0xA1DA, 0xA1DA },
+{ 0xA1DB, 0xA1DB, 0xA1DB },
+{ 0xA1DC, 0xA1DC, 0xA1DC },
+{ 0xA1DD, 0xA1DD, 0xA1DD },
+{ 0xA1DE, 0xA1DE, 0xA1DE },
+{ 0xA1DF, 0xA1DF, 0xA1DF },
+{ 0xA1E0, 0xA1E0, 0xA1E0 },
+{ 0xA1E1, 0xA1E1, 0xA1E1 },
+{ 0xA1E2, 0xA1E2, 0xA1E2 },
+{ 0xA1E3, 0xA1E3, 0xA1E3 },
+{ 0xA1E4, 0xA1E4, 0xA1E4 },
+{ 0xA1E5, 0xA1E5, 0xA1E5 },
+{ 0xA1E6, 0xA1E6, 0xA1E6 },
+{ 0xA1E7, 0xA1E7, 0xA1E7 },
+{ 0xA1E8, 0xA1E8, 0xA1E8 },
+{ 0xA1E9, 0xA1E9, 0xA1E9 },
+{ 0xA1EA, 0xA1EA, 0xA1EA },
+{ 0xA1EB, 0xA1EB, 0xA1EB },
+{ 0xA1EC, 0xA1EC, 0xA1EC },
+{ 0xA1ED, 0xA1ED, 0xA1ED },
+{ 0xA1EE, 0xA1EE, 0xA1EE },
+{ 0xA1EF, 0xA1EF, 0xA1EF },
+{ 0xA1F0, 0xA1F0, 0xA1F0 },
+{ 0xA1F1, 0xA1F1, 0xA1F1 },
+{ 0xA1F2, 0xA1F2, 0xA1F2 },
+{ 0xA1F3, 0xA1F3, 0xA1F3 },
+{ 0xA1F4, 0xA1F4, 0xA1F4 },
+{ 0xA1F5, 0xA1F5, 0xA1F5 },
+{ 0xA1F6, 0xA1F6, 0xA1F6 },
+{ 0xA1F7, 0xA1F7, 0xA1F7 },
+{ 0xA1F8, 0xA1F8, 0xA1F8 },
+{ 0xA1F9, 0xA1F9, 0xA1F9 },
+{ 0xA1FA, 0xA1FA, 0xA1FA },
+{ 0xA1FB, 0xA1FB, 0xA1FB },
+{ 0xA1FC, 0xA1FC, 0xA1FC },
+{ 0xA1FD, 0xA1FD, 0xA1FD },
+{ 0xA1FE, 0xA1FE, 0xA1FE },
+{ 0xA1FF, 0xA1FF, 0xA1FF },
+{ 0xA200, 0xA200, 0xA200 },
+{ 0xA201, 0xA201, 0xA201 },
+{ 0xA202, 0xA202, 0xA202 },
+{ 0xA203, 0xA203, 0xA203 },
+{ 0xA204, 0xA204, 0xA204 },
+{ 0xA205, 0xA205, 0xA205 },
+{ 0xA206, 0xA206, 0xA206 },
+{ 0xA207, 0xA207, 0xA207 },
+{ 0xA208, 0xA208, 0xA208 },
+{ 0xA209, 0xA209, 0xA209 },
+{ 0xA20A, 0xA20A, 0xA20A },
+{ 0xA20B, 0xA20B, 0xA20B },
+{ 0xA20C, 0xA20C, 0xA20C },
+{ 0xA20D, 0xA20D, 0xA20D },
+{ 0xA20E, 0xA20E, 0xA20E },
+{ 0xA20F, 0xA20F, 0xA20F },
+{ 0xA210, 0xA210, 0xA210 },
+{ 0xA211, 0xA211, 0xA211 },
+{ 0xA212, 0xA212, 0xA212 },
+{ 0xA213, 0xA213, 0xA213 },
+{ 0xA214, 0xA214, 0xA214 },
+{ 0xA215, 0xA215, 0xA215 },
+{ 0xA216, 0xA216, 0xA216 },
+{ 0xA217, 0xA217, 0xA217 },
+{ 0xA218, 0xA218, 0xA218 },
+{ 0xA219, 0xA219, 0xA219 },
+{ 0xA21A, 0xA21A, 0xA21A },
+{ 0xA21B, 0xA21B, 0xA21B },
+{ 0xA21C, 0xA21C, 0xA21C },
+{ 0xA21D, 0xA21D, 0xA21D },
+{ 0xA21E, 0xA21E, 0xA21E },
+{ 0xA21F, 0xA21F, 0xA21F },
+{ 0xA220, 0xA220, 0xA220 },
+{ 0xA221, 0xA221, 0xA221 },
+{ 0xA222, 0xA222, 0xA222 },
+{ 0xA223, 0xA223, 0xA223 },
+{ 0xA224, 0xA224, 0xA224 },
+{ 0xA225, 0xA225, 0xA225 },
+{ 0xA226, 0xA226, 0xA226 },
+{ 0xA227, 0xA227, 0xA227 },
+{ 0xA228, 0xA228, 0xA228 },
+{ 0xA229, 0xA229, 0xA229 },
+{ 0xA22A, 0xA22A, 0xA22A },
+{ 0xA22B, 0xA22B, 0xA22B },
+{ 0xA22C, 0xA22C, 0xA22C },
+{ 0xA22D, 0xA22D, 0xA22D },
+{ 0xA22E, 0xA22E, 0xA22E },
+{ 0xA22F, 0xA22F, 0xA22F },
+{ 0xA230, 0xA230, 0xA230 },
+{ 0xA231, 0xA231, 0xA231 },
+{ 0xA232, 0xA232, 0xA232 },
+{ 0xA233, 0xA233, 0xA233 },
+{ 0xA234, 0xA234, 0xA234 },
+{ 0xA235, 0xA235, 0xA235 },
+{ 0xA236, 0xA236, 0xA236 },
+{ 0xA237, 0xA237, 0xA237 },
+{ 0xA238, 0xA238, 0xA238 },
+{ 0xA239, 0xA239, 0xA239 },
+{ 0xA23A, 0xA23A, 0xA23A },
+{ 0xA23B, 0xA23B, 0xA23B },
+{ 0xA23C, 0xA23C, 0xA23C },
+{ 0xA23D, 0xA23D, 0xA23D },
+{ 0xA23E, 0xA23E, 0xA23E },
+{ 0xA23F, 0xA23F, 0xA23F },
+{ 0xA240, 0xA240, 0xA240 },
+{ 0xA241, 0xA241, 0xA241 },
+{ 0xA242, 0xA242, 0xA242 },
+{ 0xA243, 0xA243, 0xA243 },
+{ 0xA244, 0xA244, 0xA244 },
+{ 0xA245, 0xA245, 0xA245 },
+{ 0xA246, 0xA246, 0xA246 },
+{ 0xA247, 0xA247, 0xA247 },
+{ 0xA248, 0xA248, 0xA248 },
+{ 0xA249, 0xA249, 0xA249 },
+{ 0xA24A, 0xA24A, 0xA24A },
+{ 0xA24B, 0xA24B, 0xA24B },
+{ 0xA24C, 0xA24C, 0xA24C },
+{ 0xA24D, 0xA24D, 0xA24D },
+{ 0xA24E, 0xA24E, 0xA24E },
+{ 0xA24F, 0xA24F, 0xA24F },
+{ 0xA250, 0xA250, 0xA250 },
+{ 0xA251, 0xA251, 0xA251 },
+{ 0xA252, 0xA252, 0xA252 },
+{ 0xA253, 0xA253, 0xA253 },
+{ 0xA254, 0xA254, 0xA254 },
+{ 0xA255, 0xA255, 0xA255 },
+{ 0xA256, 0xA256, 0xA256 },
+{ 0xA257, 0xA257, 0xA257 },
+{ 0xA258, 0xA258, 0xA258 },
+{ 0xA259, 0xA259, 0xA259 },
+{ 0xA25A, 0xA25A, 0xA25A },
+{ 0xA25B, 0xA25B, 0xA25B },
+{ 0xA25C, 0xA25C, 0xA25C },
+{ 0xA25D, 0xA25D, 0xA25D },
+{ 0xA25E, 0xA25E, 0xA25E },
+{ 0xA25F, 0xA25F, 0xA25F },
+{ 0xA260, 0xA260, 0xA260 },
+{ 0xA261, 0xA261, 0xA261 },
+{ 0xA262, 0xA262, 0xA262 },
+{ 0xA263, 0xA263, 0xA263 },
+{ 0xA264, 0xA264, 0xA264 },
+{ 0xA265, 0xA265, 0xA265 },
+{ 0xA266, 0xA266, 0xA266 },
+{ 0xA267, 0xA267, 0xA267 },
+{ 0xA268, 0xA268, 0xA268 },
+{ 0xA269, 0xA269, 0xA269 },
+{ 0xA26A, 0xA26A, 0xA26A },
+{ 0xA26B, 0xA26B, 0xA26B },
+{ 0xA26C, 0xA26C, 0xA26C },
+{ 0xA26D, 0xA26D, 0xA26D },
+{ 0xA26E, 0xA26E, 0xA26E },
+{ 0xA26F, 0xA26F, 0xA26F },
+{ 0xA270, 0xA270, 0xA270 },
+{ 0xA271, 0xA271, 0xA271 },
+{ 0xA272, 0xA272, 0xA272 },
+{ 0xA273, 0xA273, 0xA273 },
+{ 0xA274, 0xA274, 0xA274 },
+{ 0xA275, 0xA275, 0xA275 },
+{ 0xA276, 0xA276, 0xA276 },
+{ 0xA277, 0xA277, 0xA277 },
+{ 0xA278, 0xA278, 0xA278 },
+{ 0xA279, 0xA279, 0xA279 },
+{ 0xA27A, 0xA27A, 0xA27A },
+{ 0xA27B, 0xA27B, 0xA27B },
+{ 0xA27C, 0xA27C, 0xA27C },
+{ 0xA27D, 0xA27D, 0xA27D },
+{ 0xA27E, 0xA27E, 0xA27E },
+{ 0xA27F, 0xA27F, 0xA27F },
+{ 0xA280, 0xA280, 0xA280 },
+{ 0xA281, 0xA281, 0xA281 },
+{ 0xA282, 0xA282, 0xA282 },
+{ 0xA283, 0xA283, 0xA283 },
+{ 0xA284, 0xA284, 0xA284 },
+{ 0xA285, 0xA285, 0xA285 },
+{ 0xA286, 0xA286, 0xA286 },
+{ 0xA287, 0xA287, 0xA287 },
+{ 0xA288, 0xA288, 0xA288 },
+{ 0xA289, 0xA289, 0xA289 },
+{ 0xA28A, 0xA28A, 0xA28A },
+{ 0xA28B, 0xA28B, 0xA28B },
+{ 0xA28C, 0xA28C, 0xA28C },
+{ 0xA28D, 0xA28D, 0xA28D },
+{ 0xA28E, 0xA28E, 0xA28E },
+{ 0xA28F, 0xA28F, 0xA28F },
+{ 0xA290, 0xA290, 0xA290 },
+{ 0xA291, 0xA291, 0xA291 },
+{ 0xA292, 0xA292, 0xA292 },
+{ 0xA293, 0xA293, 0xA293 },
+{ 0xA294, 0xA294, 0xA294 },
+{ 0xA295, 0xA295, 0xA295 },
+{ 0xA296, 0xA296, 0xA296 },
+{ 0xA297, 0xA297, 0xA297 },
+{ 0xA298, 0xA298, 0xA298 },
+{ 0xA299, 0xA299, 0xA299 },
+{ 0xA29A, 0xA29A, 0xA29A },
+{ 0xA29B, 0xA29B, 0xA29B },
+{ 0xA29C, 0xA29C, 0xA29C },
+{ 0xA29D, 0xA29D, 0xA29D },
+{ 0xA29E, 0xA29E, 0xA29E },
+{ 0xA29F, 0xA29F, 0xA29F },
+{ 0xA2A0, 0xA2A0, 0xA2A0 },
+{ 0xA2A1, 0xA2A1, 0xA2A1 },
+{ 0xA2A2, 0xA2A2, 0xA2A2 },
+{ 0xA2A3, 0xA2A3, 0xA2A3 },
+{ 0xA2A4, 0xA2A4, 0xA2A4 },
+{ 0xA2A5, 0xA2A5, 0xA2A5 },
+{ 0xA2A6, 0xA2A6, 0xA2A6 },
+{ 0xA2A7, 0xA2A7, 0xA2A7 },
+{ 0xA2A8, 0xA2A8, 0xA2A8 },
+{ 0xA2A9, 0xA2A9, 0xA2A9 },
+{ 0xA2AA, 0xA2AA, 0xA2AA },
+{ 0xA2AB, 0xA2AB, 0xA2AB },
+{ 0xA2AC, 0xA2AC, 0xA2AC },
+{ 0xA2AD, 0xA2AD, 0xA2AD },
+{ 0xA2AE, 0xA2AE, 0xA2AE },
+{ 0xA2AF, 0xA2AF, 0xA2AF },
+{ 0xA2B0, 0xA2B0, 0xA2B0 },
+{ 0xA2B1, 0xA2B1, 0xA2B1 },
+{ 0xA2B2, 0xA2B2, 0xA2B2 },
+{ 0xA2B3, 0xA2B3, 0xA2B3 },
+{ 0xA2B4, 0xA2B4, 0xA2B4 },
+{ 0xA2B5, 0xA2B5, 0xA2B5 },
+{ 0xA2B6, 0xA2B6, 0xA2B6 },
+{ 0xA2B7, 0xA2B7, 0xA2B7 },
+{ 0xA2B8, 0xA2B8, 0xA2B8 },
+{ 0xA2B9, 0xA2B9, 0xA2B9 },
+{ 0xA2BA, 0xA2BA, 0xA2BA },
+{ 0xA2BB, 0xA2BB, 0xA2BB },
+{ 0xA2BC, 0xA2BC, 0xA2BC },
+{ 0xA2BD, 0xA2BD, 0xA2BD },
+{ 0xA2BE, 0xA2BE, 0xA2BE },
+{ 0xA2BF, 0xA2BF, 0xA2BF },
+{ 0xA2C0, 0xA2C0, 0xA2C0 },
+{ 0xA2C1, 0xA2C1, 0xA2C1 },
+{ 0xA2C2, 0xA2C2, 0xA2C2 },
+{ 0xA2C3, 0xA2C3, 0xA2C3 },
+{ 0xA2C4, 0xA2C4, 0xA2C4 },
+{ 0xA2C5, 0xA2C5, 0xA2C5 },
+{ 0xA2C6, 0xA2C6, 0xA2C6 },
+{ 0xA2C7, 0xA2C7, 0xA2C7 },
+{ 0xA2C8, 0xA2C8, 0xA2C8 },
+{ 0xA2C9, 0xA2C9, 0xA2C9 },
+{ 0xA2CA, 0xA2CA, 0xA2CA },
+{ 0xA2CB, 0xA2CB, 0xA2CB },
+{ 0xA2CC, 0xA2CC, 0xA2CC },
+{ 0xA2CD, 0xA2CD, 0xA2CD },
+{ 0xA2CE, 0xA2CE, 0xA2CE },
+{ 0xA2CF, 0xA2CF, 0xA2CF },
+{ 0xA2D0, 0xA2D0, 0xA2D0 },
+{ 0xA2D1, 0xA2D1, 0xA2D1 },
+{ 0xA2D2, 0xA2D2, 0xA2D2 },
+{ 0xA2D3, 0xA2D3, 0xA2D3 },
+{ 0xA2D4, 0xA2D4, 0xA2D4 },
+{ 0xA2D5, 0xA2D5, 0xA2D5 },
+{ 0xA2D6, 0xA2D6, 0xA2D6 },
+{ 0xA2D7, 0xA2D7, 0xA2D7 },
+{ 0xA2D8, 0xA2D8, 0xA2D8 },
+{ 0xA2D9, 0xA2D9, 0xA2D9 },
+{ 0xA2DA, 0xA2DA, 0xA2DA },
+{ 0xA2DB, 0xA2DB, 0xA2DB },
+{ 0xA2DC, 0xA2DC, 0xA2DC },
+{ 0xA2DD, 0xA2DD, 0xA2DD },
+{ 0xA2DE, 0xA2DE, 0xA2DE },
+{ 0xA2DF, 0xA2DF, 0xA2DF },
+{ 0xA2E0, 0xA2E0, 0xA2E0 },
+{ 0xA2E1, 0xA2E1, 0xA2E1 },
+{ 0xA2E2, 0xA2E2, 0xA2E2 },
+{ 0xA2E3, 0xA2E3, 0xA2E3 },
+{ 0xA2E4, 0xA2E4, 0xA2E4 },
+{ 0xA2E5, 0xA2E5, 0xA2E5 },
+{ 0xA2E6, 0xA2E6, 0xA2E6 },
+{ 0xA2E7, 0xA2E7, 0xA2E7 },
+{ 0xA2E8, 0xA2E8, 0xA2E8 },
+{ 0xA2E9, 0xA2E9, 0xA2E9 },
+{ 0xA2EA, 0xA2EA, 0xA2EA },
+{ 0xA2EB, 0xA2EB, 0xA2EB },
+{ 0xA2EC, 0xA2EC, 0xA2EC },
+{ 0xA2ED, 0xA2ED, 0xA2ED },
+{ 0xA2EE, 0xA2EE, 0xA2EE },
+{ 0xA2EF, 0xA2EF, 0xA2EF },
+{ 0xA2F0, 0xA2F0, 0xA2F0 },
+{ 0xA2F1, 0xA2F1, 0xA2F1 },
+{ 0xA2F2, 0xA2F2, 0xA2F2 },
+{ 0xA2F3, 0xA2F3, 0xA2F3 },
+{ 0xA2F4, 0xA2F4, 0xA2F4 },
+{ 0xA2F5, 0xA2F5, 0xA2F5 },
+{ 0xA2F6, 0xA2F6, 0xA2F6 },
+{ 0xA2F7, 0xA2F7, 0xA2F7 },
+{ 0xA2F8, 0xA2F8, 0xA2F8 },
+{ 0xA2F9, 0xA2F9, 0xA2F9 },
+{ 0xA2FA, 0xA2FA, 0xA2FA },
+{ 0xA2FB, 0xA2FB, 0xA2FB },
+{ 0xA2FC, 0xA2FC, 0xA2FC },
+{ 0xA2FD, 0xA2FD, 0xA2FD },
+{ 0xA2FE, 0xA2FE, 0xA2FE },
+{ 0xA2FF, 0xA2FF, 0xA2FF },
+{ 0xA300, 0xA300, 0xA300 },
+{ 0xA301, 0xA301, 0xA301 },
+{ 0xA302, 0xA302, 0xA302 },
+{ 0xA303, 0xA303, 0xA303 },
+{ 0xA304, 0xA304, 0xA304 },
+{ 0xA305, 0xA305, 0xA305 },
+{ 0xA306, 0xA306, 0xA306 },
+{ 0xA307, 0xA307, 0xA307 },
+{ 0xA308, 0xA308, 0xA308 },
+{ 0xA309, 0xA309, 0xA309 },
+{ 0xA30A, 0xA30A, 0xA30A },
+{ 0xA30B, 0xA30B, 0xA30B },
+{ 0xA30C, 0xA30C, 0xA30C },
+{ 0xA30D, 0xA30D, 0xA30D },
+{ 0xA30E, 0xA30E, 0xA30E },
+{ 0xA30F, 0xA30F, 0xA30F },
+{ 0xA310, 0xA310, 0xA310 },
+{ 0xA311, 0xA311, 0xA311 },
+{ 0xA312, 0xA312, 0xA312 },
+{ 0xA313, 0xA313, 0xA313 },
+{ 0xA314, 0xA314, 0xA314 },
+{ 0xA315, 0xA315, 0xA315 },
+{ 0xA316, 0xA316, 0xA316 },
+{ 0xA317, 0xA317, 0xA317 },
+{ 0xA318, 0xA318, 0xA318 },
+{ 0xA319, 0xA319, 0xA319 },
+{ 0xA31A, 0xA31A, 0xA31A },
+{ 0xA31B, 0xA31B, 0xA31B },
+{ 0xA31C, 0xA31C, 0xA31C },
+{ 0xA31D, 0xA31D, 0xA31D },
+{ 0xA31E, 0xA31E, 0xA31E },
+{ 0xA31F, 0xA31F, 0xA31F },
+{ 0xA320, 0xA320, 0xA320 },
+{ 0xA321, 0xA321, 0xA321 },
+{ 0xA322, 0xA322, 0xA322 },
+{ 0xA323, 0xA323, 0xA323 },
+{ 0xA324, 0xA324, 0xA324 },
+{ 0xA325, 0xA325, 0xA325 },
+{ 0xA326, 0xA326, 0xA326 },
+{ 0xA327, 0xA327, 0xA327 },
+{ 0xA328, 0xA328, 0xA328 },
+{ 0xA329, 0xA329, 0xA329 },
+{ 0xA32A, 0xA32A, 0xA32A },
+{ 0xA32B, 0xA32B, 0xA32B },
+{ 0xA32C, 0xA32C, 0xA32C },
+{ 0xA32D, 0xA32D, 0xA32D },
+{ 0xA32E, 0xA32E, 0xA32E },
+{ 0xA32F, 0xA32F, 0xA32F },
+{ 0xA330, 0xA330, 0xA330 },
+{ 0xA331, 0xA331, 0xA331 },
+{ 0xA332, 0xA332, 0xA332 },
+{ 0xA333, 0xA333, 0xA333 },
+{ 0xA334, 0xA334, 0xA334 },
+{ 0xA335, 0xA335, 0xA335 },
+{ 0xA336, 0xA336, 0xA336 },
+{ 0xA337, 0xA337, 0xA337 },
+{ 0xA338, 0xA338, 0xA338 },
+{ 0xA339, 0xA339, 0xA339 },
+{ 0xA33A, 0xA33A, 0xA33A },
+{ 0xA33B, 0xA33B, 0xA33B },
+{ 0xA33C, 0xA33C, 0xA33C },
+{ 0xA33D, 0xA33D, 0xA33D },
+{ 0xA33E, 0xA33E, 0xA33E },
+{ 0xA33F, 0xA33F, 0xA33F },
+{ 0xA340, 0xA340, 0xA340 },
+{ 0xA341, 0xA341, 0xA341 },
+{ 0xA342, 0xA342, 0xA342 },
+{ 0xA343, 0xA343, 0xA343 },
+{ 0xA344, 0xA344, 0xA344 },
+{ 0xA345, 0xA345, 0xA345 },
+{ 0xA346, 0xA346, 0xA346 },
+{ 0xA347, 0xA347, 0xA347 },
+{ 0xA348, 0xA348, 0xA348 },
+{ 0xA349, 0xA349, 0xA349 },
+{ 0xA34A, 0xA34A, 0xA34A },
+{ 0xA34B, 0xA34B, 0xA34B },
+{ 0xA34C, 0xA34C, 0xA34C },
+{ 0xA34D, 0xA34D, 0xA34D },
+{ 0xA34E, 0xA34E, 0xA34E },
+{ 0xA34F, 0xA34F, 0xA34F },
+{ 0xA350, 0xA350, 0xA350 },
+{ 0xA351, 0xA351, 0xA351 },
+{ 0xA352, 0xA352, 0xA352 },
+{ 0xA353, 0xA353, 0xA353 },
+{ 0xA354, 0xA354, 0xA354 },
+{ 0xA355, 0xA355, 0xA355 },
+{ 0xA356, 0xA356, 0xA356 },
+{ 0xA357, 0xA357, 0xA357 },
+{ 0xA358, 0xA358, 0xA358 },
+{ 0xA359, 0xA359, 0xA359 },
+{ 0xA35A, 0xA35A, 0xA35A },
+{ 0xA35B, 0xA35B, 0xA35B },
+{ 0xA35C, 0xA35C, 0xA35C },
+{ 0xA35D, 0xA35D, 0xA35D },
+{ 0xA35E, 0xA35E, 0xA35E },
+{ 0xA35F, 0xA35F, 0xA35F },
+{ 0xA360, 0xA360, 0xA360 },
+{ 0xA361, 0xA361, 0xA361 },
+{ 0xA362, 0xA362, 0xA362 },
+{ 0xA363, 0xA363, 0xA363 },
+{ 0xA364, 0xA364, 0xA364 },
+{ 0xA365, 0xA365, 0xA365 },
+{ 0xA366, 0xA366, 0xA366 },
+{ 0xA367, 0xA367, 0xA367 },
+{ 0xA368, 0xA368, 0xA368 },
+{ 0xA369, 0xA369, 0xA369 },
+{ 0xA36A, 0xA36A, 0xA36A },
+{ 0xA36B, 0xA36B, 0xA36B },
+{ 0xA36C, 0xA36C, 0xA36C },
+{ 0xA36D, 0xA36D, 0xA36D },
+{ 0xA36E, 0xA36E, 0xA36E },
+{ 0xA36F, 0xA36F, 0xA36F },
+{ 0xA370, 0xA370, 0xA370 },
+{ 0xA371, 0xA371, 0xA371 },
+{ 0xA372, 0xA372, 0xA372 },
+{ 0xA373, 0xA373, 0xA373 },
+{ 0xA374, 0xA374, 0xA374 },
+{ 0xA375, 0xA375, 0xA375 },
+{ 0xA376, 0xA376, 0xA376 },
+{ 0xA377, 0xA377, 0xA377 },
+{ 0xA378, 0xA378, 0xA378 },
+{ 0xA379, 0xA379, 0xA379 },
+{ 0xA37A, 0xA37A, 0xA37A },
+{ 0xA37B, 0xA37B, 0xA37B },
+{ 0xA37C, 0xA37C, 0xA37C },
+{ 0xA37D, 0xA37D, 0xA37D },
+{ 0xA37E, 0xA37E, 0xA37E },
+{ 0xA37F, 0xA37F, 0xA37F },
+{ 0xA380, 0xA380, 0xA380 },
+{ 0xA381, 0xA381, 0xA381 },
+{ 0xA382, 0xA382, 0xA382 },
+{ 0xA383, 0xA383, 0xA383 },
+{ 0xA384, 0xA384, 0xA384 },
+{ 0xA385, 0xA385, 0xA385 },
+{ 0xA386, 0xA386, 0xA386 },
+{ 0xA387, 0xA387, 0xA387 },
+{ 0xA388, 0xA388, 0xA388 },
+{ 0xA389, 0xA389, 0xA389 },
+{ 0xA38A, 0xA38A, 0xA38A },
+{ 0xA38B, 0xA38B, 0xA38B },
+{ 0xA38C, 0xA38C, 0xA38C },
+{ 0xA38D, 0xA38D, 0xA38D },
+{ 0xA38E, 0xA38E, 0xA38E },
+{ 0xA38F, 0xA38F, 0xA38F },
+{ 0xA390, 0xA390, 0xA390 },
+{ 0xA391, 0xA391, 0xA391 },
+{ 0xA392, 0xA392, 0xA392 },
+{ 0xA393, 0xA393, 0xA393 },
+{ 0xA394, 0xA394, 0xA394 },
+{ 0xA395, 0xA395, 0xA395 },
+{ 0xA396, 0xA396, 0xA396 },
+{ 0xA397, 0xA397, 0xA397 },
+{ 0xA398, 0xA398, 0xA398 },
+{ 0xA399, 0xA399, 0xA399 },
+{ 0xA39A, 0xA39A, 0xA39A },
+{ 0xA39B, 0xA39B, 0xA39B },
+{ 0xA39C, 0xA39C, 0xA39C },
+{ 0xA39D, 0xA39D, 0xA39D },
+{ 0xA39E, 0xA39E, 0xA39E },
+{ 0xA39F, 0xA39F, 0xA39F },
+{ 0xA3A0, 0xA3A0, 0xA3A0 },
+{ 0xA3A1, 0xA3A1, 0xA3A1 },
+{ 0xA3A2, 0xA3A2, 0xA3A2 },
+{ 0xA3A3, 0xA3A3, 0xA3A3 },
+{ 0xA3A4, 0xA3A4, 0xA3A4 },
+{ 0xA3A5, 0xA3A5, 0xA3A5 },
+{ 0xA3A6, 0xA3A6, 0xA3A6 },
+{ 0xA3A7, 0xA3A7, 0xA3A7 },
+{ 0xA3A8, 0xA3A8, 0xA3A8 },
+{ 0xA3A9, 0xA3A9, 0xA3A9 },
+{ 0xA3AA, 0xA3AA, 0xA3AA },
+{ 0xA3AB, 0xA3AB, 0xA3AB },
+{ 0xA3AC, 0xA3AC, 0xA3AC },
+{ 0xA3AD, 0xA3AD, 0xA3AD },
+{ 0xA3AE, 0xA3AE, 0xA3AE },
+{ 0xA3AF, 0xA3AF, 0xA3AF },
+{ 0xA3B0, 0xA3B0, 0xA3B0 },
+{ 0xA3B1, 0xA3B1, 0xA3B1 },
+{ 0xA3B2, 0xA3B2, 0xA3B2 },
+{ 0xA3B3, 0xA3B3, 0xA3B3 },
+{ 0xA3B4, 0xA3B4, 0xA3B4 },
+{ 0xA3B5, 0xA3B5, 0xA3B5 },
+{ 0xA3B6, 0xA3B6, 0xA3B6 },
+{ 0xA3B7, 0xA3B7, 0xA3B7 },
+{ 0xA3B8, 0xA3B8, 0xA3B8 },
+{ 0xA3B9, 0xA3B9, 0xA3B9 },
+{ 0xA3BA, 0xA3BA, 0xA3BA },
+{ 0xA3BB, 0xA3BB, 0xA3BB },
+{ 0xA3BC, 0xA3BC, 0xA3BC },
+{ 0xA3BD, 0xA3BD, 0xA3BD },
+{ 0xA3BE, 0xA3BE, 0xA3BE },
+{ 0xA3BF, 0xA3BF, 0xA3BF },
+{ 0xA3C0, 0xA3C0, 0xA3C0 },
+{ 0xA3C1, 0xA3C1, 0xA3C1 },
+{ 0xA3C2, 0xA3C2, 0xA3C2 },
+{ 0xA3C3, 0xA3C3, 0xA3C3 },
+{ 0xA3C4, 0xA3C4, 0xA3C4 },
+{ 0xA3C5, 0xA3C5, 0xA3C5 },
+{ 0xA3C6, 0xA3C6, 0xA3C6 },
+{ 0xA3C7, 0xA3C7, 0xA3C7 },
+{ 0xA3C8, 0xA3C8, 0xA3C8 },
+{ 0xA3C9, 0xA3C9, 0xA3C9 },
+{ 0xA3CA, 0xA3CA, 0xA3CA },
+{ 0xA3CB, 0xA3CB, 0xA3CB },
+{ 0xA3CC, 0xA3CC, 0xA3CC },
+{ 0xA3CD, 0xA3CD, 0xA3CD },
+{ 0xA3CE, 0xA3CE, 0xA3CE },
+{ 0xA3CF, 0xA3CF, 0xA3CF },
+{ 0xA3D0, 0xA3D0, 0xA3D0 },
+{ 0xA3D1, 0xA3D1, 0xA3D1 },
+{ 0xA3D2, 0xA3D2, 0xA3D2 },
+{ 0xA3D3, 0xA3D3, 0xA3D3 },
+{ 0xA3D4, 0xA3D4, 0xA3D4 },
+{ 0xA3D5, 0xA3D5, 0xA3D5 },
+{ 0xA3D6, 0xA3D6, 0xA3D6 },
+{ 0xA3D7, 0xA3D7, 0xA3D7 },
+{ 0xA3D8, 0xA3D8, 0xA3D8 },
+{ 0xA3D9, 0xA3D9, 0xA3D9 },
+{ 0xA3DA, 0xA3DA, 0xA3DA },
+{ 0xA3DB, 0xA3DB, 0xA3DB },
+{ 0xA3DC, 0xA3DC, 0xA3DC },
+{ 0xA3DD, 0xA3DD, 0xA3DD },
+{ 0xA3DE, 0xA3DE, 0xA3DE },
+{ 0xA3DF, 0xA3DF, 0xA3DF },
+{ 0xA3E0, 0xA3E0, 0xA3E0 },
+{ 0xA3E1, 0xA3E1, 0xA3E1 },
+{ 0xA3E2, 0xA3E2, 0xA3E2 },
+{ 0xA3E3, 0xA3E3, 0xA3E3 },
+{ 0xA3E4, 0xA3E4, 0xA3E4 },
+{ 0xA3E5, 0xA3E5, 0xA3E5 },
+{ 0xA3E6, 0xA3E6, 0xA3E6 },
+{ 0xA3E7, 0xA3E7, 0xA3E7 },
+{ 0xA3E8, 0xA3E8, 0xA3E8 },
+{ 0xA3E9, 0xA3E9, 0xA3E9 },
+{ 0xA3EA, 0xA3EA, 0xA3EA },
+{ 0xA3EB, 0xA3EB, 0xA3EB },
+{ 0xA3EC, 0xA3EC, 0xA3EC },
+{ 0xA3ED, 0xA3ED, 0xA3ED },
+{ 0xA3EE, 0xA3EE, 0xA3EE },
+{ 0xA3EF, 0xA3EF, 0xA3EF },
+{ 0xA3F0, 0xA3F0, 0xA3F0 },
+{ 0xA3F1, 0xA3F1, 0xA3F1 },
+{ 0xA3F2, 0xA3F2, 0xA3F2 },
+{ 0xA3F3, 0xA3F3, 0xA3F3 },
+{ 0xA3F4, 0xA3F4, 0xA3F4 },
+{ 0xA3F5, 0xA3F5, 0xA3F5 },
+{ 0xA3F6, 0xA3F6, 0xA3F6 },
+{ 0xA3F7, 0xA3F7, 0xA3F7 },
+{ 0xA3F8, 0xA3F8, 0xA3F8 },
+{ 0xA3F9, 0xA3F9, 0xA3F9 },
+{ 0xA3FA, 0xA3FA, 0xA3FA },
+{ 0xA3FB, 0xA3FB, 0xA3FB },
+{ 0xA3FC, 0xA3FC, 0xA3FC },
+{ 0xA3FD, 0xA3FD, 0xA3FD },
+{ 0xA3FE, 0xA3FE, 0xA3FE },
+{ 0xA3FF, 0xA3FF, 0xA3FF },
+{ 0xA400, 0xA400, 0xA400 },
+{ 0xA401, 0xA401, 0xA401 },
+{ 0xA402, 0xA402, 0xA402 },
+{ 0xA403, 0xA403, 0xA403 },
+{ 0xA404, 0xA404, 0xA404 },
+{ 0xA405, 0xA405, 0xA405 },
+{ 0xA406, 0xA406, 0xA406 },
+{ 0xA407, 0xA407, 0xA407 },
+{ 0xA408, 0xA408, 0xA408 },
+{ 0xA409, 0xA409, 0xA409 },
+{ 0xA40A, 0xA40A, 0xA40A },
+{ 0xA40B, 0xA40B, 0xA40B },
+{ 0xA40C, 0xA40C, 0xA40C },
+{ 0xA40D, 0xA40D, 0xA40D },
+{ 0xA40E, 0xA40E, 0xA40E },
+{ 0xA40F, 0xA40F, 0xA40F },
+{ 0xA410, 0xA410, 0xA410 },
+{ 0xA411, 0xA411, 0xA411 },
+{ 0xA412, 0xA412, 0xA412 },
+{ 0xA413, 0xA413, 0xA413 },
+{ 0xA414, 0xA414, 0xA414 },
+{ 0xA415, 0xA415, 0xA415 },
+{ 0xA416, 0xA416, 0xA416 },
+{ 0xA417, 0xA417, 0xA417 },
+{ 0xA418, 0xA418, 0xA418 },
+{ 0xA419, 0xA419, 0xA419 },
+{ 0xA41A, 0xA41A, 0xA41A },
+{ 0xA41B, 0xA41B, 0xA41B },
+{ 0xA41C, 0xA41C, 0xA41C },
+{ 0xA41D, 0xA41D, 0xA41D },
+{ 0xA41E, 0xA41E, 0xA41E },
+{ 0xA41F, 0xA41F, 0xA41F },
+{ 0xA420, 0xA420, 0xA420 },
+{ 0xA421, 0xA421, 0xA421 },
+{ 0xA422, 0xA422, 0xA422 },
+{ 0xA423, 0xA423, 0xA423 },
+{ 0xA424, 0xA424, 0xA424 },
+{ 0xA425, 0xA425, 0xA425 },
+{ 0xA426, 0xA426, 0xA426 },
+{ 0xA427, 0xA427, 0xA427 },
+{ 0xA428, 0xA428, 0xA428 },
+{ 0xA429, 0xA429, 0xA429 },
+{ 0xA42A, 0xA42A, 0xA42A },
+{ 0xA42B, 0xA42B, 0xA42B },
+{ 0xA42C, 0xA42C, 0xA42C },
+{ 0xA42D, 0xA42D, 0xA42D },
+{ 0xA42E, 0xA42E, 0xA42E },
+{ 0xA42F, 0xA42F, 0xA42F },
+{ 0xA430, 0xA430, 0xA430 },
+{ 0xA431, 0xA431, 0xA431 },
+{ 0xA432, 0xA432, 0xA432 },
+{ 0xA433, 0xA433, 0xA433 },
+{ 0xA434, 0xA434, 0xA434 },
+{ 0xA435, 0xA435, 0xA435 },
+{ 0xA436, 0xA436, 0xA436 },
+{ 0xA437, 0xA437, 0xA437 },
+{ 0xA438, 0xA438, 0xA438 },
+{ 0xA439, 0xA439, 0xA439 },
+{ 0xA43A, 0xA43A, 0xA43A },
+{ 0xA43B, 0xA43B, 0xA43B },
+{ 0xA43C, 0xA43C, 0xA43C },
+{ 0xA43D, 0xA43D, 0xA43D },
+{ 0xA43E, 0xA43E, 0xA43E },
+{ 0xA43F, 0xA43F, 0xA43F },
+{ 0xA440, 0xA440, 0xA440 },
+{ 0xA441, 0xA441, 0xA441 },
+{ 0xA442, 0xA442, 0xA442 },
+{ 0xA443, 0xA443, 0xA443 },
+{ 0xA444, 0xA444, 0xA444 },
+{ 0xA445, 0xA445, 0xA445 },
+{ 0xA446, 0xA446, 0xA446 },
+{ 0xA447, 0xA447, 0xA447 },
+{ 0xA448, 0xA448, 0xA448 },
+{ 0xA449, 0xA449, 0xA449 },
+{ 0xA44A, 0xA44A, 0xA44A },
+{ 0xA44B, 0xA44B, 0xA44B },
+{ 0xA44C, 0xA44C, 0xA44C },
+{ 0xA44D, 0xA44D, 0xA44D },
+{ 0xA44E, 0xA44E, 0xA44E },
+{ 0xA44F, 0xA44F, 0xA44F },
+{ 0xA450, 0xA450, 0xA450 },
+{ 0xA451, 0xA451, 0xA451 },
+{ 0xA452, 0xA452, 0xA452 },
+{ 0xA453, 0xA453, 0xA453 },
+{ 0xA454, 0xA454, 0xA454 },
+{ 0xA455, 0xA455, 0xA455 },
+{ 0xA456, 0xA456, 0xA456 },
+{ 0xA457, 0xA457, 0xA457 },
+{ 0xA458, 0xA458, 0xA458 },
+{ 0xA459, 0xA459, 0xA459 },
+{ 0xA45A, 0xA45A, 0xA45A },
+{ 0xA45B, 0xA45B, 0xA45B },
+{ 0xA45C, 0xA45C, 0xA45C },
+{ 0xA45D, 0xA45D, 0xA45D },
+{ 0xA45E, 0xA45E, 0xA45E },
+{ 0xA45F, 0xA45F, 0xA45F },
+{ 0xA460, 0xA460, 0xA460 },
+{ 0xA461, 0xA461, 0xA461 },
+{ 0xA462, 0xA462, 0xA462 },
+{ 0xA463, 0xA463, 0xA463 },
+{ 0xA464, 0xA464, 0xA464 },
+{ 0xA465, 0xA465, 0xA465 },
+{ 0xA466, 0xA466, 0xA466 },
+{ 0xA467, 0xA467, 0xA467 },
+{ 0xA468, 0xA468, 0xA468 },
+{ 0xA469, 0xA469, 0xA469 },
+{ 0xA46A, 0xA46A, 0xA46A },
+{ 0xA46B, 0xA46B, 0xA46B },
+{ 0xA46C, 0xA46C, 0xA46C },
+{ 0xA46D, 0xA46D, 0xA46D },
+{ 0xA46E, 0xA46E, 0xA46E },
+{ 0xA46F, 0xA46F, 0xA46F },
+{ 0xA470, 0xA470, 0xA470 },
+{ 0xA471, 0xA471, 0xA471 },
+{ 0xA472, 0xA472, 0xA472 },
+{ 0xA473, 0xA473, 0xA473 },
+{ 0xA474, 0xA474, 0xA474 },
+{ 0xA475, 0xA475, 0xA475 },
+{ 0xA476, 0xA476, 0xA476 },
+{ 0xA477, 0xA477, 0xA477 },
+{ 0xA478, 0xA478, 0xA478 },
+{ 0xA479, 0xA479, 0xA479 },
+{ 0xA47A, 0xA47A, 0xA47A },
+{ 0xA47B, 0xA47B, 0xA47B },
+{ 0xA47C, 0xA47C, 0xA47C },
+{ 0xA47D, 0xA47D, 0xA47D },
+{ 0xA47E, 0xA47E, 0xA47E },
+{ 0xA47F, 0xA47F, 0xA47F },
+{ 0xA480, 0xA480, 0xA480 },
+{ 0xA481, 0xA481, 0xA481 },
+{ 0xA482, 0xA482, 0xA482 },
+{ 0xA483, 0xA483, 0xA483 },
+{ 0xA484, 0xA484, 0xA484 },
+{ 0xA485, 0xA485, 0xA485 },
+{ 0xA486, 0xA486, 0xA486 },
+{ 0xA487, 0xA487, 0xA487 },
+{ 0xA488, 0xA488, 0xA488 },
+{ 0xA489, 0xA489, 0xA489 },
+{ 0xA48A, 0xA48A, 0xA48A },
+{ 0xA48B, 0xA48B, 0xA48B },
+{ 0xA48C, 0xA48C, 0xA48C },
+{ 0xA800, 0xA800, 0xA800 },
+{ 0xA801, 0xA801, 0xA801 },
+{ 0xA803, 0xA803, 0xA803 },
+{ 0xA804, 0xA804, 0xA804 },
+{ 0xA805, 0xA805, 0xA805 },
+{ 0xA806, 0xA806, 0xA806 },
+{ 0xA807, 0xA807, 0xA807 },
+{ 0xA808, 0xA808, 0xA808 },
+{ 0xA809, 0xA809, 0xA809 },
+{ 0xA80A, 0xA80A, 0xA80A },
+{ 0xA80B, 0xA80B, 0xA80B },
+{ 0xA80C, 0xA80C, 0xA80C },
+{ 0xA80D, 0xA80D, 0xA80D },
+{ 0xA80E, 0xA80E, 0xA80E },
+{ 0xA80F, 0xA80F, 0xA80F },
+{ 0xA810, 0xA810, 0xA810 },
+{ 0xA811, 0xA811, 0xA811 },
+{ 0xA812, 0xA812, 0xA812 },
+{ 0xA813, 0xA813, 0xA813 },
+{ 0xA814, 0xA814, 0xA814 },
+{ 0xA815, 0xA815, 0xA815 },
+{ 0xA816, 0xA816, 0xA816 },
+{ 0xA817, 0xA817, 0xA817 },
+{ 0xA818, 0xA818, 0xA818 },
+{ 0xA819, 0xA819, 0xA819 },
+{ 0xA81A, 0xA81A, 0xA81A },
+{ 0xA81B, 0xA81B, 0xA81B },
+{ 0xA81C, 0xA81C, 0xA81C },
+{ 0xA81D, 0xA81D, 0xA81D },
+{ 0xA81E, 0xA81E, 0xA81E },
+{ 0xA81F, 0xA81F, 0xA81F },
+{ 0xA820, 0xA820, 0xA820 },
+{ 0xA821, 0xA821, 0xA821 },
+{ 0xA822, 0xA822, 0xA822 },
+{ 0xA825, 0xA825, 0xA825 },
+{ 0xA826, 0xA826, 0xA826 },
+{ 0xAC00, 0xAC00, 0xAC00 },
+{ 0xAC01, 0xAC01, 0xAC01 },
+{ 0xAC02, 0xAC02, 0xAC02 },
+{ 0xAC03, 0xAC03, 0xAC03 },
+{ 0xAC04, 0xAC04, 0xAC04 },
+{ 0xAC05, 0xAC05, 0xAC05 },
+{ 0xAC06, 0xAC06, 0xAC06 },
+{ 0xAC07, 0xAC07, 0xAC07 },
+{ 0xAC08, 0xAC08, 0xAC08 },
+{ 0xAC09, 0xAC09, 0xAC09 },
+{ 0xAC0A, 0xAC0A, 0xAC0A },
+{ 0xAC0B, 0xAC0B, 0xAC0B },
+{ 0xAC0C, 0xAC0C, 0xAC0C },
+{ 0xAC0D, 0xAC0D, 0xAC0D },
+{ 0xAC0E, 0xAC0E, 0xAC0E },
+{ 0xAC0F, 0xAC0F, 0xAC0F },
+{ 0xAC10, 0xAC10, 0xAC10 },
+{ 0xAC11, 0xAC11, 0xAC11 },
+{ 0xAC12, 0xAC12, 0xAC12 },
+{ 0xAC13, 0xAC13, 0xAC13 },
+{ 0xAC14, 0xAC14, 0xAC14 },
+{ 0xAC15, 0xAC15, 0xAC15 },
+{ 0xAC16, 0xAC16, 0xAC16 },
+{ 0xAC17, 0xAC17, 0xAC17 },
+{ 0xAC18, 0xAC18, 0xAC18 },
+{ 0xAC19, 0xAC19, 0xAC19 },
+{ 0xAC1A, 0xAC1A, 0xAC1A },
+{ 0xAC1B, 0xAC1B, 0xAC1B },
+{ 0xAC1C, 0xAC1C, 0xAC1C },
+{ 0xAC1D, 0xAC1D, 0xAC1D },
+{ 0xAC1E, 0xAC1E, 0xAC1E },
+{ 0xAC1F, 0xAC1F, 0xAC1F },
+{ 0xAC20, 0xAC20, 0xAC20 },
+{ 0xAC21, 0xAC21, 0xAC21 },
+{ 0xAC22, 0xAC22, 0xAC22 },
+{ 0xAC23, 0xAC23, 0xAC23 },
+{ 0xAC24, 0xAC24, 0xAC24 },
+{ 0xAC25, 0xAC25, 0xAC25 },
+{ 0xAC26, 0xAC26, 0xAC26 },
+{ 0xAC27, 0xAC27, 0xAC27 },
+{ 0xAC28, 0xAC28, 0xAC28 },
+{ 0xAC29, 0xAC29, 0xAC29 },
+{ 0xAC2A, 0xAC2A, 0xAC2A },
+{ 0xAC2B, 0xAC2B, 0xAC2B },
+{ 0xAC2C, 0xAC2C, 0xAC2C },
+{ 0xAC2D, 0xAC2D, 0xAC2D },
+{ 0xAC2E, 0xAC2E, 0xAC2E },
+{ 0xAC2F, 0xAC2F, 0xAC2F },
+{ 0xAC30, 0xAC30, 0xAC30 },
+{ 0xAC31, 0xAC31, 0xAC31 },
+{ 0xAC32, 0xAC32, 0xAC32 },
+{ 0xAC33, 0xAC33, 0xAC33 },
+{ 0xAC34, 0xAC34, 0xAC34 },
+{ 0xAC35, 0xAC35, 0xAC35 },
+{ 0xAC36, 0xAC36, 0xAC36 },
+{ 0xAC37, 0xAC37, 0xAC37 },
+{ 0xAC38, 0xAC38, 0xAC38 },
+{ 0xAC39, 0xAC39, 0xAC39 },
+{ 0xAC3A, 0xAC3A, 0xAC3A },
+{ 0xAC3B, 0xAC3B, 0xAC3B },
+{ 0xAC3C, 0xAC3C, 0xAC3C },
+{ 0xAC3D, 0xAC3D, 0xAC3D },
+{ 0xAC3E, 0xAC3E, 0xAC3E },
+{ 0xAC3F, 0xAC3F, 0xAC3F },
+{ 0xAC40, 0xAC40, 0xAC40 },
+{ 0xAC41, 0xAC41, 0xAC41 },
+{ 0xAC42, 0xAC42, 0xAC42 },
+{ 0xAC43, 0xAC43, 0xAC43 },
+{ 0xAC44, 0xAC44, 0xAC44 },
+{ 0xAC45, 0xAC45, 0xAC45 },
+{ 0xAC46, 0xAC46, 0xAC46 },
+{ 0xAC47, 0xAC47, 0xAC47 },
+{ 0xAC48, 0xAC48, 0xAC48 },
+{ 0xAC49, 0xAC49, 0xAC49 },
+{ 0xAC4A, 0xAC4A, 0xAC4A },
+{ 0xAC4B, 0xAC4B, 0xAC4B },
+{ 0xAC4C, 0xAC4C, 0xAC4C },
+{ 0xAC4D, 0xAC4D, 0xAC4D },
+{ 0xAC4E, 0xAC4E, 0xAC4E },
+{ 0xAC4F, 0xAC4F, 0xAC4F },
+{ 0xAC50, 0xAC50, 0xAC50 },
+{ 0xAC51, 0xAC51, 0xAC51 },
+{ 0xAC52, 0xAC52, 0xAC52 },
+{ 0xAC53, 0xAC53, 0xAC53 },
+{ 0xAC54, 0xAC54, 0xAC54 },
+{ 0xAC55, 0xAC55, 0xAC55 },
+{ 0xAC56, 0xAC56, 0xAC56 },
+{ 0xAC57, 0xAC57, 0xAC57 },
+{ 0xAC58, 0xAC58, 0xAC58 },
+{ 0xAC59, 0xAC59, 0xAC59 },
+{ 0xAC5A, 0xAC5A, 0xAC5A },
+{ 0xAC5B, 0xAC5B, 0xAC5B },
+{ 0xAC5C, 0xAC5C, 0xAC5C },
+{ 0xAC5D, 0xAC5D, 0xAC5D },
+{ 0xAC5E, 0xAC5E, 0xAC5E },
+{ 0xAC5F, 0xAC5F, 0xAC5F },
+{ 0xAC60, 0xAC60, 0xAC60 },
+{ 0xAC61, 0xAC61, 0xAC61 },
+{ 0xAC62, 0xAC62, 0xAC62 },
+{ 0xAC63, 0xAC63, 0xAC63 },
+{ 0xAC64, 0xAC64, 0xAC64 },
+{ 0xAC65, 0xAC65, 0xAC65 },
+{ 0xAC66, 0xAC66, 0xAC66 },
+{ 0xAC67, 0xAC67, 0xAC67 },
+{ 0xAC68, 0xAC68, 0xAC68 },
+{ 0xAC69, 0xAC69, 0xAC69 },
+{ 0xAC6A, 0xAC6A, 0xAC6A },
+{ 0xAC6B, 0xAC6B, 0xAC6B },
+{ 0xAC6C, 0xAC6C, 0xAC6C },
+{ 0xAC6D, 0xAC6D, 0xAC6D },
+{ 0xAC6E, 0xAC6E, 0xAC6E },
+{ 0xAC6F, 0xAC6F, 0xAC6F },
+{ 0xAC70, 0xAC70, 0xAC70 },
+{ 0xAC71, 0xAC71, 0xAC71 },
+{ 0xAC72, 0xAC72, 0xAC72 },
+{ 0xAC73, 0xAC73, 0xAC73 },
+{ 0xAC74, 0xAC74, 0xAC74 },
+{ 0xAC75, 0xAC75, 0xAC75 },
+{ 0xAC76, 0xAC76, 0xAC76 },
+{ 0xAC77, 0xAC77, 0xAC77 },
+{ 0xAC78, 0xAC78, 0xAC78 },
+{ 0xAC79, 0xAC79, 0xAC79 },
+{ 0xAC7A, 0xAC7A, 0xAC7A },
+{ 0xAC7B, 0xAC7B, 0xAC7B },
+{ 0xAC7C, 0xAC7C, 0xAC7C },
+{ 0xAC7D, 0xAC7D, 0xAC7D },
+{ 0xAC7E, 0xAC7E, 0xAC7E },
+{ 0xAC7F, 0xAC7F, 0xAC7F },
+{ 0xAC80, 0xAC80, 0xAC80 },
+{ 0xAC81, 0xAC81, 0xAC81 },
+{ 0xAC82, 0xAC82, 0xAC82 },
+{ 0xAC83, 0xAC83, 0xAC83 },
+{ 0xAC84, 0xAC84, 0xAC84 },
+{ 0xAC85, 0xAC85, 0xAC85 },
+{ 0xAC86, 0xAC86, 0xAC86 },
+{ 0xAC87, 0xAC87, 0xAC87 },
+{ 0xAC88, 0xAC88, 0xAC88 },
+{ 0xAC89, 0xAC89, 0xAC89 },
+{ 0xAC8A, 0xAC8A, 0xAC8A },
+{ 0xAC8B, 0xAC8B, 0xAC8B },
+{ 0xAC8C, 0xAC8C, 0xAC8C },
+{ 0xAC8D, 0xAC8D, 0xAC8D },
+{ 0xAC8E, 0xAC8E, 0xAC8E },
+{ 0xAC8F, 0xAC8F, 0xAC8F },
+{ 0xAC90, 0xAC90, 0xAC90 },
+{ 0xAC91, 0xAC91, 0xAC91 },
+{ 0xAC92, 0xAC92, 0xAC92 },
+{ 0xAC93, 0xAC93, 0xAC93 },
+{ 0xAC94, 0xAC94, 0xAC94 },
+{ 0xAC95, 0xAC95, 0xAC95 },
+{ 0xAC96, 0xAC96, 0xAC96 },
+{ 0xAC97, 0xAC97, 0xAC97 },
+{ 0xAC98, 0xAC98, 0xAC98 },
+{ 0xAC99, 0xAC99, 0xAC99 },
+{ 0xAC9A, 0xAC9A, 0xAC9A },
+{ 0xAC9B, 0xAC9B, 0xAC9B },
+{ 0xAC9C, 0xAC9C, 0xAC9C },
+{ 0xAC9D, 0xAC9D, 0xAC9D },
+{ 0xAC9E, 0xAC9E, 0xAC9E },
+{ 0xAC9F, 0xAC9F, 0xAC9F },
+{ 0xACA0, 0xACA0, 0xACA0 },
+{ 0xACA1, 0xACA1, 0xACA1 },
+{ 0xACA2, 0xACA2, 0xACA2 },
+{ 0xACA3, 0xACA3, 0xACA3 },
+{ 0xACA4, 0xACA4, 0xACA4 },
+{ 0xACA5, 0xACA5, 0xACA5 },
+{ 0xACA6, 0xACA6, 0xACA6 },
+{ 0xACA7, 0xACA7, 0xACA7 },
+{ 0xACA8, 0xACA8, 0xACA8 },
+{ 0xACA9, 0xACA9, 0xACA9 },
+{ 0xACAA, 0xACAA, 0xACAA },
+{ 0xACAB, 0xACAB, 0xACAB },
+{ 0xACAC, 0xACAC, 0xACAC },
+{ 0xACAD, 0xACAD, 0xACAD },
+{ 0xACAE, 0xACAE, 0xACAE },
+{ 0xACAF, 0xACAF, 0xACAF },
+{ 0xACB0, 0xACB0, 0xACB0 },
+{ 0xACB1, 0xACB1, 0xACB1 },
+{ 0xACB2, 0xACB2, 0xACB2 },
+{ 0xACB3, 0xACB3, 0xACB3 },
+{ 0xACB4, 0xACB4, 0xACB4 },
+{ 0xACB5, 0xACB5, 0xACB5 },
+{ 0xACB6, 0xACB6, 0xACB6 },
+{ 0xACB7, 0xACB7, 0xACB7 },
+{ 0xACB8, 0xACB8, 0xACB8 },
+{ 0xACB9, 0xACB9, 0xACB9 },
+{ 0xACBA, 0xACBA, 0xACBA },
+{ 0xACBB, 0xACBB, 0xACBB },
+{ 0xACBC, 0xACBC, 0xACBC },
+{ 0xACBD, 0xACBD, 0xACBD },
+{ 0xACBE, 0xACBE, 0xACBE },
+{ 0xACBF, 0xACBF, 0xACBF },
+{ 0xACC0, 0xACC0, 0xACC0 },
+{ 0xACC1, 0xACC1, 0xACC1 },
+{ 0xACC2, 0xACC2, 0xACC2 },
+{ 0xACC3, 0xACC3, 0xACC3 },
+{ 0xACC4, 0xACC4, 0xACC4 },
+{ 0xACC5, 0xACC5, 0xACC5 },
+{ 0xACC6, 0xACC6, 0xACC6 },
+{ 0xACC7, 0xACC7, 0xACC7 },
+{ 0xACC8, 0xACC8, 0xACC8 },
+{ 0xACC9, 0xACC9, 0xACC9 },
+{ 0xACCA, 0xACCA, 0xACCA },
+{ 0xACCB, 0xACCB, 0xACCB },
+{ 0xACCC, 0xACCC, 0xACCC },
+{ 0xACCD, 0xACCD, 0xACCD },
+{ 0xACCE, 0xACCE, 0xACCE },
+{ 0xACCF, 0xACCF, 0xACCF },
+{ 0xACD0, 0xACD0, 0xACD0 },
+{ 0xACD1, 0xACD1, 0xACD1 },
+{ 0xACD2, 0xACD2, 0xACD2 },
+{ 0xACD3, 0xACD3, 0xACD3 },
+{ 0xACD4, 0xACD4, 0xACD4 },
+{ 0xACD5, 0xACD5, 0xACD5 },
+{ 0xACD6, 0xACD6, 0xACD6 },
+{ 0xACD7, 0xACD7, 0xACD7 },
+{ 0xACD8, 0xACD8, 0xACD8 },
+{ 0xACD9, 0xACD9, 0xACD9 },
+{ 0xACDA, 0xACDA, 0xACDA },
+{ 0xACDB, 0xACDB, 0xACDB },
+{ 0xACDC, 0xACDC, 0xACDC },
+{ 0xACDD, 0xACDD, 0xACDD },
+{ 0xACDE, 0xACDE, 0xACDE },
+{ 0xACDF, 0xACDF, 0xACDF },
+{ 0xACE0, 0xACE0, 0xACE0 },
+{ 0xACE1, 0xACE1, 0xACE1 },
+{ 0xACE2, 0xACE2, 0xACE2 },
+{ 0xACE3, 0xACE3, 0xACE3 },
+{ 0xACE4, 0xACE4, 0xACE4 },
+{ 0xACE5, 0xACE5, 0xACE5 },
+{ 0xACE6, 0xACE6, 0xACE6 },
+{ 0xACE7, 0xACE7, 0xACE7 },
+{ 0xACE8, 0xACE8, 0xACE8 },
+{ 0xACE9, 0xACE9, 0xACE9 },
+{ 0xACEA, 0xACEA, 0xACEA },
+{ 0xACEB, 0xACEB, 0xACEB },
+{ 0xACEC, 0xACEC, 0xACEC },
+{ 0xACED, 0xACED, 0xACED },
+{ 0xACEE, 0xACEE, 0xACEE },
+{ 0xACEF, 0xACEF, 0xACEF },
+{ 0xACF0, 0xACF0, 0xACF0 },
+{ 0xACF1, 0xACF1, 0xACF1 },
+{ 0xACF2, 0xACF2, 0xACF2 },
+{ 0xACF3, 0xACF3, 0xACF3 },
+{ 0xACF4, 0xACF4, 0xACF4 },
+{ 0xACF5, 0xACF5, 0xACF5 },
+{ 0xACF6, 0xACF6, 0xACF6 },
+{ 0xACF7, 0xACF7, 0xACF7 },
+{ 0xACF8, 0xACF8, 0xACF8 },
+{ 0xACF9, 0xACF9, 0xACF9 },
+{ 0xACFA, 0xACFA, 0xACFA },
+{ 0xACFB, 0xACFB, 0xACFB },
+{ 0xACFC, 0xACFC, 0xACFC },
+{ 0xACFD, 0xACFD, 0xACFD },
+{ 0xACFE, 0xACFE, 0xACFE },
+{ 0xACFF, 0xACFF, 0xACFF },
+{ 0xAD00, 0xAD00, 0xAD00 },
+{ 0xAD01, 0xAD01, 0xAD01 },
+{ 0xAD02, 0xAD02, 0xAD02 },
+{ 0xAD03, 0xAD03, 0xAD03 },
+{ 0xAD04, 0xAD04, 0xAD04 },
+{ 0xAD05, 0xAD05, 0xAD05 },
+{ 0xAD06, 0xAD06, 0xAD06 },
+{ 0xAD07, 0xAD07, 0xAD07 },
+{ 0xAD08, 0xAD08, 0xAD08 },
+{ 0xAD09, 0xAD09, 0xAD09 },
+{ 0xAD0A, 0xAD0A, 0xAD0A },
+{ 0xAD0B, 0xAD0B, 0xAD0B },
+{ 0xAD0C, 0xAD0C, 0xAD0C },
+{ 0xAD0D, 0xAD0D, 0xAD0D },
+{ 0xAD0E, 0xAD0E, 0xAD0E },
+{ 0xAD0F, 0xAD0F, 0xAD0F },
+{ 0xAD10, 0xAD10, 0xAD10 },
+{ 0xAD11, 0xAD11, 0xAD11 },
+{ 0xAD12, 0xAD12, 0xAD12 },
+{ 0xAD13, 0xAD13, 0xAD13 },
+{ 0xAD14, 0xAD14, 0xAD14 },
+{ 0xAD15, 0xAD15, 0xAD15 },
+{ 0xAD16, 0xAD16, 0xAD16 },
+{ 0xAD17, 0xAD17, 0xAD17 },
+{ 0xAD18, 0xAD18, 0xAD18 },
+{ 0xAD19, 0xAD19, 0xAD19 },
+{ 0xAD1A, 0xAD1A, 0xAD1A },
+{ 0xAD1B, 0xAD1B, 0xAD1B },
+{ 0xAD1C, 0xAD1C, 0xAD1C },
+{ 0xAD1D, 0xAD1D, 0xAD1D },
+{ 0xAD1E, 0xAD1E, 0xAD1E },
+{ 0xAD1F, 0xAD1F, 0xAD1F },
+{ 0xAD20, 0xAD20, 0xAD20 },
+{ 0xAD21, 0xAD21, 0xAD21 },
+{ 0xAD22, 0xAD22, 0xAD22 },
+{ 0xAD23, 0xAD23, 0xAD23 },
+{ 0xAD24, 0xAD24, 0xAD24 },
+{ 0xAD25, 0xAD25, 0xAD25 },
+{ 0xAD26, 0xAD26, 0xAD26 },
+{ 0xAD27, 0xAD27, 0xAD27 },
+{ 0xAD28, 0xAD28, 0xAD28 },
+{ 0xAD29, 0xAD29, 0xAD29 },
+{ 0xAD2A, 0xAD2A, 0xAD2A },
+{ 0xAD2B, 0xAD2B, 0xAD2B },
+{ 0xAD2C, 0xAD2C, 0xAD2C },
+{ 0xAD2D, 0xAD2D, 0xAD2D },
+{ 0xAD2E, 0xAD2E, 0xAD2E },
+{ 0xAD2F, 0xAD2F, 0xAD2F },
+{ 0xAD30, 0xAD30, 0xAD30 },
+{ 0xAD31, 0xAD31, 0xAD31 },
+{ 0xAD32, 0xAD32, 0xAD32 },
+{ 0xAD33, 0xAD33, 0xAD33 },
+{ 0xAD34, 0xAD34, 0xAD34 },
+{ 0xAD35, 0xAD35, 0xAD35 },
+{ 0xAD36, 0xAD36, 0xAD36 },
+{ 0xAD37, 0xAD37, 0xAD37 },
+{ 0xAD38, 0xAD38, 0xAD38 },
+{ 0xAD39, 0xAD39, 0xAD39 },
+{ 0xAD3A, 0xAD3A, 0xAD3A },
+{ 0xAD3B, 0xAD3B, 0xAD3B },
+{ 0xAD3C, 0xAD3C, 0xAD3C },
+{ 0xAD3D, 0xAD3D, 0xAD3D },
+{ 0xAD3E, 0xAD3E, 0xAD3E },
+{ 0xAD3F, 0xAD3F, 0xAD3F },
+{ 0xAD40, 0xAD40, 0xAD40 },
+{ 0xAD41, 0xAD41, 0xAD41 },
+{ 0xAD42, 0xAD42, 0xAD42 },
+{ 0xAD43, 0xAD43, 0xAD43 },
+{ 0xAD44, 0xAD44, 0xAD44 },
+{ 0xAD45, 0xAD45, 0xAD45 },
+{ 0xAD46, 0xAD46, 0xAD46 },
+{ 0xAD47, 0xAD47, 0xAD47 },
+{ 0xAD48, 0xAD48, 0xAD48 },
+{ 0xAD49, 0xAD49, 0xAD49 },
+{ 0xAD4A, 0xAD4A, 0xAD4A },
+{ 0xAD4B, 0xAD4B, 0xAD4B },
+{ 0xAD4C, 0xAD4C, 0xAD4C },
+{ 0xAD4D, 0xAD4D, 0xAD4D },
+{ 0xAD4E, 0xAD4E, 0xAD4E },
+{ 0xAD4F, 0xAD4F, 0xAD4F },
+{ 0xAD50, 0xAD50, 0xAD50 },
+{ 0xAD51, 0xAD51, 0xAD51 },
+{ 0xAD52, 0xAD52, 0xAD52 },
+{ 0xAD53, 0xAD53, 0xAD53 },
+{ 0xAD54, 0xAD54, 0xAD54 },
+{ 0xAD55, 0xAD55, 0xAD55 },
+{ 0xAD56, 0xAD56, 0xAD56 },
+{ 0xAD57, 0xAD57, 0xAD57 },
+{ 0xAD58, 0xAD58, 0xAD58 },
+{ 0xAD59, 0xAD59, 0xAD59 },
+{ 0xAD5A, 0xAD5A, 0xAD5A },
+{ 0xAD5B, 0xAD5B, 0xAD5B },
+{ 0xAD5C, 0xAD5C, 0xAD5C },
+{ 0xAD5D, 0xAD5D, 0xAD5D },
+{ 0xAD5E, 0xAD5E, 0xAD5E },
+{ 0xAD5F, 0xAD5F, 0xAD5F },
+{ 0xAD60, 0xAD60, 0xAD60 },
+{ 0xAD61, 0xAD61, 0xAD61 },
+{ 0xAD62, 0xAD62, 0xAD62 },
+{ 0xAD63, 0xAD63, 0xAD63 },
+{ 0xAD64, 0xAD64, 0xAD64 },
+{ 0xAD65, 0xAD65, 0xAD65 },
+{ 0xAD66, 0xAD66, 0xAD66 },
+{ 0xAD67, 0xAD67, 0xAD67 },
+{ 0xAD68, 0xAD68, 0xAD68 },
+{ 0xAD69, 0xAD69, 0xAD69 },
+{ 0xAD6A, 0xAD6A, 0xAD6A },
+{ 0xAD6B, 0xAD6B, 0xAD6B },
+{ 0xAD6C, 0xAD6C, 0xAD6C },
+{ 0xAD6D, 0xAD6D, 0xAD6D },
+{ 0xAD6E, 0xAD6E, 0xAD6E },
+{ 0xAD6F, 0xAD6F, 0xAD6F },
+{ 0xAD70, 0xAD70, 0xAD70 },
+{ 0xAD71, 0xAD71, 0xAD71 },
+{ 0xAD72, 0xAD72, 0xAD72 },
+{ 0xAD73, 0xAD73, 0xAD73 },
+{ 0xAD74, 0xAD74, 0xAD74 },
+{ 0xAD75, 0xAD75, 0xAD75 },
+{ 0xAD76, 0xAD76, 0xAD76 },
+{ 0xAD77, 0xAD77, 0xAD77 },
+{ 0xAD78, 0xAD78, 0xAD78 },
+{ 0xAD79, 0xAD79, 0xAD79 },
+{ 0xAD7A, 0xAD7A, 0xAD7A },
+{ 0xAD7B, 0xAD7B, 0xAD7B },
+{ 0xAD7C, 0xAD7C, 0xAD7C },
+{ 0xAD7D, 0xAD7D, 0xAD7D },
+{ 0xAD7E, 0xAD7E, 0xAD7E },
+{ 0xAD7F, 0xAD7F, 0xAD7F },
+{ 0xAD80, 0xAD80, 0xAD80 },
+{ 0xAD81, 0xAD81, 0xAD81 },
+{ 0xAD82, 0xAD82, 0xAD82 },
+{ 0xAD83, 0xAD83, 0xAD83 },
+{ 0xAD84, 0xAD84, 0xAD84 },
+{ 0xAD85, 0xAD85, 0xAD85 },
+{ 0xAD86, 0xAD86, 0xAD86 },
+{ 0xAD87, 0xAD87, 0xAD87 },
+{ 0xAD88, 0xAD88, 0xAD88 },
+{ 0xAD89, 0xAD89, 0xAD89 },
+{ 0xAD8A, 0xAD8A, 0xAD8A },
+{ 0xAD8B, 0xAD8B, 0xAD8B },
+{ 0xAD8C, 0xAD8C, 0xAD8C },
+{ 0xAD8D, 0xAD8D, 0xAD8D },
+{ 0xAD8E, 0xAD8E, 0xAD8E },
+{ 0xAD8F, 0xAD8F, 0xAD8F },
+{ 0xAD90, 0xAD90, 0xAD90 },
+{ 0xAD91, 0xAD91, 0xAD91 },
+{ 0xAD92, 0xAD92, 0xAD92 },
+{ 0xAD93, 0xAD93, 0xAD93 },
+{ 0xAD94, 0xAD94, 0xAD94 },
+{ 0xAD95, 0xAD95, 0xAD95 },
+{ 0xAD96, 0xAD96, 0xAD96 },
+{ 0xAD97, 0xAD97, 0xAD97 },
+{ 0xAD98, 0xAD98, 0xAD98 },
+{ 0xAD99, 0xAD99, 0xAD99 },
+{ 0xAD9A, 0xAD9A, 0xAD9A },
+{ 0xAD9B, 0xAD9B, 0xAD9B },
+{ 0xAD9C, 0xAD9C, 0xAD9C },
+{ 0xAD9D, 0xAD9D, 0xAD9D },
+{ 0xAD9E, 0xAD9E, 0xAD9E },
+{ 0xAD9F, 0xAD9F, 0xAD9F },
+{ 0xADA0, 0xADA0, 0xADA0 },
+{ 0xADA1, 0xADA1, 0xADA1 },
+{ 0xADA2, 0xADA2, 0xADA2 },
+{ 0xADA3, 0xADA3, 0xADA3 },
+{ 0xADA4, 0xADA4, 0xADA4 },
+{ 0xADA5, 0xADA5, 0xADA5 },
+{ 0xADA6, 0xADA6, 0xADA6 },
+{ 0xADA7, 0xADA7, 0xADA7 },
+{ 0xADA8, 0xADA8, 0xADA8 },
+{ 0xADA9, 0xADA9, 0xADA9 },
+{ 0xADAA, 0xADAA, 0xADAA },
+{ 0xADAB, 0xADAB, 0xADAB },
+{ 0xADAC, 0xADAC, 0xADAC },
+{ 0xADAD, 0xADAD, 0xADAD },
+{ 0xADAE, 0xADAE, 0xADAE },
+{ 0xADAF, 0xADAF, 0xADAF },
+{ 0xADB0, 0xADB0, 0xADB0 },
+{ 0xADB1, 0xADB1, 0xADB1 },
+{ 0xADB2, 0xADB2, 0xADB2 },
+{ 0xADB3, 0xADB3, 0xADB3 },
+{ 0xADB4, 0xADB4, 0xADB4 },
+{ 0xADB5, 0xADB5, 0xADB5 },
+{ 0xADB6, 0xADB6, 0xADB6 },
+{ 0xADB7, 0xADB7, 0xADB7 },
+{ 0xADB8, 0xADB8, 0xADB8 },
+{ 0xADB9, 0xADB9, 0xADB9 },
+{ 0xADBA, 0xADBA, 0xADBA },
+{ 0xADBB, 0xADBB, 0xADBB },
+{ 0xADBC, 0xADBC, 0xADBC },
+{ 0xADBD, 0xADBD, 0xADBD },
+{ 0xADBE, 0xADBE, 0xADBE },
+{ 0xADBF, 0xADBF, 0xADBF },
+{ 0xADC0, 0xADC0, 0xADC0 },
+{ 0xADC1, 0xADC1, 0xADC1 },
+{ 0xADC2, 0xADC2, 0xADC2 },
+{ 0xADC3, 0xADC3, 0xADC3 },
+{ 0xADC4, 0xADC4, 0xADC4 },
+{ 0xADC5, 0xADC5, 0xADC5 },
+{ 0xADC6, 0xADC6, 0xADC6 },
+{ 0xADC7, 0xADC7, 0xADC7 },
+{ 0xADC8, 0xADC8, 0xADC8 },
+{ 0xADC9, 0xADC9, 0xADC9 },
+{ 0xADCA, 0xADCA, 0xADCA },
+{ 0xADCB, 0xADCB, 0xADCB },
+{ 0xADCC, 0xADCC, 0xADCC },
+{ 0xADCD, 0xADCD, 0xADCD },
+{ 0xADCE, 0xADCE, 0xADCE },
+{ 0xADCF, 0xADCF, 0xADCF },
+{ 0xADD0, 0xADD0, 0xADD0 },
+{ 0xADD1, 0xADD1, 0xADD1 },
+{ 0xADD2, 0xADD2, 0xADD2 },
+{ 0xADD3, 0xADD3, 0xADD3 },
+{ 0xADD4, 0xADD4, 0xADD4 },
+{ 0xADD5, 0xADD5, 0xADD5 },
+{ 0xADD6, 0xADD6, 0xADD6 },
+{ 0xADD7, 0xADD7, 0xADD7 },
+{ 0xADD8, 0xADD8, 0xADD8 },
+{ 0xADD9, 0xADD9, 0xADD9 },
+{ 0xADDA, 0xADDA, 0xADDA },
+{ 0xADDB, 0xADDB, 0xADDB },
+{ 0xADDC, 0xADDC, 0xADDC },
+{ 0xADDD, 0xADDD, 0xADDD },
+{ 0xADDE, 0xADDE, 0xADDE },
+{ 0xADDF, 0xADDF, 0xADDF },
+{ 0xADE0, 0xADE0, 0xADE0 },
+{ 0xADE1, 0xADE1, 0xADE1 },
+{ 0xADE2, 0xADE2, 0xADE2 },
+{ 0xADE3, 0xADE3, 0xADE3 },
+{ 0xADE4, 0xADE4, 0xADE4 },
+{ 0xADE5, 0xADE5, 0xADE5 },
+{ 0xADE6, 0xADE6, 0xADE6 },
+{ 0xADE7, 0xADE7, 0xADE7 },
+{ 0xADE8, 0xADE8, 0xADE8 },
+{ 0xADE9, 0xADE9, 0xADE9 },
+{ 0xADEA, 0xADEA, 0xADEA },
+{ 0xADEB, 0xADEB, 0xADEB },
+{ 0xADEC, 0xADEC, 0xADEC },
+{ 0xADED, 0xADED, 0xADED },
+{ 0xADEE, 0xADEE, 0xADEE },
+{ 0xADEF, 0xADEF, 0xADEF },
+{ 0xADF0, 0xADF0, 0xADF0 },
+{ 0xADF1, 0xADF1, 0xADF1 },
+{ 0xADF2, 0xADF2, 0xADF2 },
+{ 0xADF3, 0xADF3, 0xADF3 },
+{ 0xADF4, 0xADF4, 0xADF4 },
+{ 0xADF5, 0xADF5, 0xADF5 },
+{ 0xADF6, 0xADF6, 0xADF6 },
+{ 0xADF7, 0xADF7, 0xADF7 },
+{ 0xADF8, 0xADF8, 0xADF8 },
+{ 0xADF9, 0xADF9, 0xADF9 },
+{ 0xADFA, 0xADFA, 0xADFA },
+{ 0xADFB, 0xADFB, 0xADFB },
+{ 0xADFC, 0xADFC, 0xADFC },
+{ 0xADFD, 0xADFD, 0xADFD },
+{ 0xADFE, 0xADFE, 0xADFE },
+{ 0xADFF, 0xADFF, 0xADFF },
+{ 0xAE00, 0xAE00, 0xAE00 },
+{ 0xAE01, 0xAE01, 0xAE01 },
+{ 0xAE02, 0xAE02, 0xAE02 },
+{ 0xAE03, 0xAE03, 0xAE03 },
+{ 0xAE04, 0xAE04, 0xAE04 },
+{ 0xAE05, 0xAE05, 0xAE05 },
+{ 0xAE06, 0xAE06, 0xAE06 },
+{ 0xAE07, 0xAE07, 0xAE07 },
+{ 0xAE08, 0xAE08, 0xAE08 },
+{ 0xAE09, 0xAE09, 0xAE09 },
+{ 0xAE0A, 0xAE0A, 0xAE0A },
+{ 0xAE0B, 0xAE0B, 0xAE0B },
+{ 0xAE0C, 0xAE0C, 0xAE0C },
+{ 0xAE0D, 0xAE0D, 0xAE0D },
+{ 0xAE0E, 0xAE0E, 0xAE0E },
+{ 0xAE0F, 0xAE0F, 0xAE0F },
+{ 0xAE10, 0xAE10, 0xAE10 },
+{ 0xAE11, 0xAE11, 0xAE11 },
+{ 0xAE12, 0xAE12, 0xAE12 },
+{ 0xAE13, 0xAE13, 0xAE13 },
+{ 0xAE14, 0xAE14, 0xAE14 },
+{ 0xAE15, 0xAE15, 0xAE15 },
+{ 0xAE16, 0xAE16, 0xAE16 },
+{ 0xAE17, 0xAE17, 0xAE17 },
+{ 0xAE18, 0xAE18, 0xAE18 },
+{ 0xAE19, 0xAE19, 0xAE19 },
+{ 0xAE1A, 0xAE1A, 0xAE1A },
+{ 0xAE1B, 0xAE1B, 0xAE1B },
+{ 0xAE1C, 0xAE1C, 0xAE1C },
+{ 0xAE1D, 0xAE1D, 0xAE1D },
+{ 0xAE1E, 0xAE1E, 0xAE1E },
+{ 0xAE1F, 0xAE1F, 0xAE1F },
+{ 0xAE20, 0xAE20, 0xAE20 },
+{ 0xAE21, 0xAE21, 0xAE21 },
+{ 0xAE22, 0xAE22, 0xAE22 },
+{ 0xAE23, 0xAE23, 0xAE23 },
+{ 0xAE24, 0xAE24, 0xAE24 },
+{ 0xAE25, 0xAE25, 0xAE25 },
+{ 0xAE26, 0xAE26, 0xAE26 },
+{ 0xAE27, 0xAE27, 0xAE27 },
+{ 0xAE28, 0xAE28, 0xAE28 },
+{ 0xAE29, 0xAE29, 0xAE29 },
+{ 0xAE2A, 0xAE2A, 0xAE2A },
+{ 0xAE2B, 0xAE2B, 0xAE2B },
+{ 0xAE2C, 0xAE2C, 0xAE2C },
+{ 0xAE2D, 0xAE2D, 0xAE2D },
+{ 0xAE2E, 0xAE2E, 0xAE2E },
+{ 0xAE2F, 0xAE2F, 0xAE2F },
+{ 0xAE30, 0xAE30, 0xAE30 },
+{ 0xAE31, 0xAE31, 0xAE31 },
+{ 0xAE32, 0xAE32, 0xAE32 },
+{ 0xAE33, 0xAE33, 0xAE33 },
+{ 0xAE34, 0xAE34, 0xAE34 },
+{ 0xAE35, 0xAE35, 0xAE35 },
+{ 0xAE36, 0xAE36, 0xAE36 },
+{ 0xAE37, 0xAE37, 0xAE37 },
+{ 0xAE38, 0xAE38, 0xAE38 },
+{ 0xAE39, 0xAE39, 0xAE39 },
+{ 0xAE3A, 0xAE3A, 0xAE3A },
+{ 0xAE3B, 0xAE3B, 0xAE3B },
+{ 0xAE3C, 0xAE3C, 0xAE3C },
+{ 0xAE3D, 0xAE3D, 0xAE3D },
+{ 0xAE3E, 0xAE3E, 0xAE3E },
+{ 0xAE3F, 0xAE3F, 0xAE3F },
+{ 0xAE40, 0xAE40, 0xAE40 },
+{ 0xAE41, 0xAE41, 0xAE41 },
+{ 0xAE42, 0xAE42, 0xAE42 },
+{ 0xAE43, 0xAE43, 0xAE43 },
+{ 0xAE44, 0xAE44, 0xAE44 },
+{ 0xAE45, 0xAE45, 0xAE45 },
+{ 0xAE46, 0xAE46, 0xAE46 },
+{ 0xAE47, 0xAE47, 0xAE47 },
+{ 0xAE48, 0xAE48, 0xAE48 },
+{ 0xAE49, 0xAE49, 0xAE49 },
+{ 0xAE4A, 0xAE4A, 0xAE4A },
+{ 0xAE4B, 0xAE4B, 0xAE4B },
+{ 0xAE4C, 0xAE4C, 0xAE4C },
+{ 0xAE4D, 0xAE4D, 0xAE4D },
+{ 0xAE4E, 0xAE4E, 0xAE4E },
+{ 0xAE4F, 0xAE4F, 0xAE4F },
+{ 0xAE50, 0xAE50, 0xAE50 },
+{ 0xAE51, 0xAE51, 0xAE51 },
+{ 0xAE52, 0xAE52, 0xAE52 },
+{ 0xAE53, 0xAE53, 0xAE53 },
+{ 0xAE54, 0xAE54, 0xAE54 },
+{ 0xAE55, 0xAE55, 0xAE55 },
+{ 0xAE56, 0xAE56, 0xAE56 },
+{ 0xAE57, 0xAE57, 0xAE57 },
+{ 0xAE58, 0xAE58, 0xAE58 },
+{ 0xAE59, 0xAE59, 0xAE59 },
+{ 0xAE5A, 0xAE5A, 0xAE5A },
+{ 0xAE5B, 0xAE5B, 0xAE5B },
+{ 0xAE5C, 0xAE5C, 0xAE5C },
+{ 0xAE5D, 0xAE5D, 0xAE5D },
+{ 0xAE5E, 0xAE5E, 0xAE5E },
+{ 0xAE5F, 0xAE5F, 0xAE5F },
+{ 0xAE60, 0xAE60, 0xAE60 },
+{ 0xAE61, 0xAE61, 0xAE61 },
+{ 0xAE62, 0xAE62, 0xAE62 },
+{ 0xAE63, 0xAE63, 0xAE63 },
+{ 0xAE64, 0xAE64, 0xAE64 },
+{ 0xAE65, 0xAE65, 0xAE65 },
+{ 0xAE66, 0xAE66, 0xAE66 },
+{ 0xAE67, 0xAE67, 0xAE67 },
+{ 0xAE68, 0xAE68, 0xAE68 },
+{ 0xAE69, 0xAE69, 0xAE69 },
+{ 0xAE6A, 0xAE6A, 0xAE6A },
+{ 0xAE6B, 0xAE6B, 0xAE6B },
+{ 0xAE6C, 0xAE6C, 0xAE6C },
+{ 0xAE6D, 0xAE6D, 0xAE6D },
+{ 0xAE6E, 0xAE6E, 0xAE6E },
+{ 0xAE6F, 0xAE6F, 0xAE6F },
+{ 0xAE70, 0xAE70, 0xAE70 },
+{ 0xAE71, 0xAE71, 0xAE71 },
+{ 0xAE72, 0xAE72, 0xAE72 },
+{ 0xAE73, 0xAE73, 0xAE73 },
+{ 0xAE74, 0xAE74, 0xAE74 },
+{ 0xAE75, 0xAE75, 0xAE75 },
+{ 0xAE76, 0xAE76, 0xAE76 },
+{ 0xAE77, 0xAE77, 0xAE77 },
+{ 0xAE78, 0xAE78, 0xAE78 },
+{ 0xAE79, 0xAE79, 0xAE79 },
+{ 0xAE7A, 0xAE7A, 0xAE7A },
+{ 0xAE7B, 0xAE7B, 0xAE7B },
+{ 0xAE7C, 0xAE7C, 0xAE7C },
+{ 0xAE7D, 0xAE7D, 0xAE7D },
+{ 0xAE7E, 0xAE7E, 0xAE7E },
+{ 0xAE7F, 0xAE7F, 0xAE7F },
+{ 0xAE80, 0xAE80, 0xAE80 },
+{ 0xAE81, 0xAE81, 0xAE81 },
+{ 0xAE82, 0xAE82, 0xAE82 },
+{ 0xAE83, 0xAE83, 0xAE83 },
+{ 0xAE84, 0xAE84, 0xAE84 },
+{ 0xAE85, 0xAE85, 0xAE85 },
+{ 0xAE86, 0xAE86, 0xAE86 },
+{ 0xAE87, 0xAE87, 0xAE87 },
+{ 0xAE88, 0xAE88, 0xAE88 },
+{ 0xAE89, 0xAE89, 0xAE89 },
+{ 0xAE8A, 0xAE8A, 0xAE8A },
+{ 0xAE8B, 0xAE8B, 0xAE8B },
+{ 0xAE8C, 0xAE8C, 0xAE8C },
+{ 0xAE8D, 0xAE8D, 0xAE8D },
+{ 0xAE8E, 0xAE8E, 0xAE8E },
+{ 0xAE8F, 0xAE8F, 0xAE8F },
+{ 0xAE90, 0xAE90, 0xAE90 },
+{ 0xAE91, 0xAE91, 0xAE91 },
+{ 0xAE92, 0xAE92, 0xAE92 },
+{ 0xAE93, 0xAE93, 0xAE93 },
+{ 0xAE94, 0xAE94, 0xAE94 },
+{ 0xAE95, 0xAE95, 0xAE95 },
+{ 0xAE96, 0xAE96, 0xAE96 },
+{ 0xAE97, 0xAE97, 0xAE97 },
+{ 0xAE98, 0xAE98, 0xAE98 },
+{ 0xAE99, 0xAE99, 0xAE99 },
+{ 0xAE9A, 0xAE9A, 0xAE9A },
+{ 0xAE9B, 0xAE9B, 0xAE9B },
+{ 0xAE9C, 0xAE9C, 0xAE9C },
+{ 0xAE9D, 0xAE9D, 0xAE9D },
+{ 0xAE9E, 0xAE9E, 0xAE9E },
+{ 0xAE9F, 0xAE9F, 0xAE9F },
+{ 0xAEA0, 0xAEA0, 0xAEA0 },
+{ 0xAEA1, 0xAEA1, 0xAEA1 },
+{ 0xAEA2, 0xAEA2, 0xAEA2 },
+{ 0xAEA3, 0xAEA3, 0xAEA3 },
+{ 0xAEA4, 0xAEA4, 0xAEA4 },
+{ 0xAEA5, 0xAEA5, 0xAEA5 },
+{ 0xAEA6, 0xAEA6, 0xAEA6 },
+{ 0xAEA7, 0xAEA7, 0xAEA7 },
+{ 0xAEA8, 0xAEA8, 0xAEA8 },
+{ 0xAEA9, 0xAEA9, 0xAEA9 },
+{ 0xAEAA, 0xAEAA, 0xAEAA },
+{ 0xAEAB, 0xAEAB, 0xAEAB },
+{ 0xAEAC, 0xAEAC, 0xAEAC },
+{ 0xAEAD, 0xAEAD, 0xAEAD },
+{ 0xAEAE, 0xAEAE, 0xAEAE },
+{ 0xAEAF, 0xAEAF, 0xAEAF },
+{ 0xAEB0, 0xAEB0, 0xAEB0 },
+{ 0xAEB1, 0xAEB1, 0xAEB1 },
+{ 0xAEB2, 0xAEB2, 0xAEB2 },
+{ 0xAEB3, 0xAEB3, 0xAEB3 },
+{ 0xAEB4, 0xAEB4, 0xAEB4 },
+{ 0xAEB5, 0xAEB5, 0xAEB5 },
+{ 0xAEB6, 0xAEB6, 0xAEB6 },
+{ 0xAEB7, 0xAEB7, 0xAEB7 },
+{ 0xAEB8, 0xAEB8, 0xAEB8 },
+{ 0xAEB9, 0xAEB9, 0xAEB9 },
+{ 0xAEBA, 0xAEBA, 0xAEBA },
+{ 0xAEBB, 0xAEBB, 0xAEBB },
+{ 0xAEBC, 0xAEBC, 0xAEBC },
+{ 0xAEBD, 0xAEBD, 0xAEBD },
+{ 0xAEBE, 0xAEBE, 0xAEBE },
+{ 0xAEBF, 0xAEBF, 0xAEBF },
+{ 0xAEC0, 0xAEC0, 0xAEC0 },
+{ 0xAEC1, 0xAEC1, 0xAEC1 },
+{ 0xAEC2, 0xAEC2, 0xAEC2 },
+{ 0xAEC3, 0xAEC3, 0xAEC3 },
+{ 0xAEC4, 0xAEC4, 0xAEC4 },
+{ 0xAEC5, 0xAEC5, 0xAEC5 },
+{ 0xAEC6, 0xAEC6, 0xAEC6 },
+{ 0xAEC7, 0xAEC7, 0xAEC7 },
+{ 0xAEC8, 0xAEC8, 0xAEC8 },
+{ 0xAEC9, 0xAEC9, 0xAEC9 },
+{ 0xAECA, 0xAECA, 0xAECA },
+{ 0xAECB, 0xAECB, 0xAECB },
+{ 0xAECC, 0xAECC, 0xAECC },
+{ 0xAECD, 0xAECD, 0xAECD },
+{ 0xAECE, 0xAECE, 0xAECE },
+{ 0xAECF, 0xAECF, 0xAECF },
+{ 0xAED0, 0xAED0, 0xAED0 },
+{ 0xAED1, 0xAED1, 0xAED1 },
+{ 0xAED2, 0xAED2, 0xAED2 },
+{ 0xAED3, 0xAED3, 0xAED3 },
+{ 0xAED4, 0xAED4, 0xAED4 },
+{ 0xAED5, 0xAED5, 0xAED5 },
+{ 0xAED6, 0xAED6, 0xAED6 },
+{ 0xAED7, 0xAED7, 0xAED7 },
+{ 0xAED8, 0xAED8, 0xAED8 },
+{ 0xAED9, 0xAED9, 0xAED9 },
+{ 0xAEDA, 0xAEDA, 0xAEDA },
+{ 0xAEDB, 0xAEDB, 0xAEDB },
+{ 0xAEDC, 0xAEDC, 0xAEDC },
+{ 0xAEDD, 0xAEDD, 0xAEDD },
+{ 0xAEDE, 0xAEDE, 0xAEDE },
+{ 0xAEDF, 0xAEDF, 0xAEDF },
+{ 0xAEE0, 0xAEE0, 0xAEE0 },
+{ 0xAEE1, 0xAEE1, 0xAEE1 },
+{ 0xAEE2, 0xAEE2, 0xAEE2 },
+{ 0xAEE3, 0xAEE3, 0xAEE3 },
+{ 0xAEE4, 0xAEE4, 0xAEE4 },
+{ 0xAEE5, 0xAEE5, 0xAEE5 },
+{ 0xAEE6, 0xAEE6, 0xAEE6 },
+{ 0xAEE7, 0xAEE7, 0xAEE7 },
+{ 0xAEE8, 0xAEE8, 0xAEE8 },
+{ 0xAEE9, 0xAEE9, 0xAEE9 },
+{ 0xAEEA, 0xAEEA, 0xAEEA },
+{ 0xAEEB, 0xAEEB, 0xAEEB },
+{ 0xAEEC, 0xAEEC, 0xAEEC },
+{ 0xAEED, 0xAEED, 0xAEED },
+{ 0xAEEE, 0xAEEE, 0xAEEE },
+{ 0xAEEF, 0xAEEF, 0xAEEF },
+{ 0xAEF0, 0xAEF0, 0xAEF0 },
+{ 0xAEF1, 0xAEF1, 0xAEF1 },
+{ 0xAEF2, 0xAEF2, 0xAEF2 },
+{ 0xAEF3, 0xAEF3, 0xAEF3 },
+{ 0xAEF4, 0xAEF4, 0xAEF4 },
+{ 0xAEF5, 0xAEF5, 0xAEF5 },
+{ 0xAEF6, 0xAEF6, 0xAEF6 },
+{ 0xAEF7, 0xAEF7, 0xAEF7 },
+{ 0xAEF8, 0xAEF8, 0xAEF8 },
+{ 0xAEF9, 0xAEF9, 0xAEF9 },
+{ 0xAEFA, 0xAEFA, 0xAEFA },
+{ 0xAEFB, 0xAEFB, 0xAEFB },
+{ 0xAEFC, 0xAEFC, 0xAEFC },
+{ 0xAEFD, 0xAEFD, 0xAEFD },
+{ 0xAEFE, 0xAEFE, 0xAEFE },
+{ 0xAEFF, 0xAEFF, 0xAEFF },
+{ 0xAF00, 0xAF00, 0xAF00 },
+{ 0xAF01, 0xAF01, 0xAF01 },
+{ 0xAF02, 0xAF02, 0xAF02 },
+{ 0xAF03, 0xAF03, 0xAF03 },
+{ 0xAF04, 0xAF04, 0xAF04 },
+{ 0xAF05, 0xAF05, 0xAF05 },
+{ 0xAF06, 0xAF06, 0xAF06 },
+{ 0xAF07, 0xAF07, 0xAF07 },
+{ 0xAF08, 0xAF08, 0xAF08 },
+{ 0xAF09, 0xAF09, 0xAF09 },
+{ 0xAF0A, 0xAF0A, 0xAF0A },
+{ 0xAF0B, 0xAF0B, 0xAF0B },
+{ 0xAF0C, 0xAF0C, 0xAF0C },
+{ 0xAF0D, 0xAF0D, 0xAF0D },
+{ 0xAF0E, 0xAF0E, 0xAF0E },
+{ 0xAF0F, 0xAF0F, 0xAF0F },
+{ 0xAF10, 0xAF10, 0xAF10 },
+{ 0xAF11, 0xAF11, 0xAF11 },
+{ 0xAF12, 0xAF12, 0xAF12 },
+{ 0xAF13, 0xAF13, 0xAF13 },
+{ 0xAF14, 0xAF14, 0xAF14 },
+{ 0xAF15, 0xAF15, 0xAF15 },
+{ 0xAF16, 0xAF16, 0xAF16 },
+{ 0xAF17, 0xAF17, 0xAF17 },
+{ 0xAF18, 0xAF18, 0xAF18 },
+{ 0xAF19, 0xAF19, 0xAF19 },
+{ 0xAF1A, 0xAF1A, 0xAF1A },
+{ 0xAF1B, 0xAF1B, 0xAF1B },
+{ 0xAF1C, 0xAF1C, 0xAF1C },
+{ 0xAF1D, 0xAF1D, 0xAF1D },
+{ 0xAF1E, 0xAF1E, 0xAF1E },
+{ 0xAF1F, 0xAF1F, 0xAF1F },
+{ 0xAF20, 0xAF20, 0xAF20 },
+{ 0xAF21, 0xAF21, 0xAF21 },
+{ 0xAF22, 0xAF22, 0xAF22 },
+{ 0xAF23, 0xAF23, 0xAF23 },
+{ 0xAF24, 0xAF24, 0xAF24 },
+{ 0xAF25, 0xAF25, 0xAF25 },
+{ 0xAF26, 0xAF26, 0xAF26 },
+{ 0xAF27, 0xAF27, 0xAF27 },
+{ 0xAF28, 0xAF28, 0xAF28 },
+{ 0xAF29, 0xAF29, 0xAF29 },
+{ 0xAF2A, 0xAF2A, 0xAF2A },
+{ 0xAF2B, 0xAF2B, 0xAF2B },
+{ 0xAF2C, 0xAF2C, 0xAF2C },
+{ 0xAF2D, 0xAF2D, 0xAF2D },
+{ 0xAF2E, 0xAF2E, 0xAF2E },
+{ 0xAF2F, 0xAF2F, 0xAF2F },
+{ 0xAF30, 0xAF30, 0xAF30 },
+{ 0xAF31, 0xAF31, 0xAF31 },
+{ 0xAF32, 0xAF32, 0xAF32 },
+{ 0xAF33, 0xAF33, 0xAF33 },
+{ 0xAF34, 0xAF34, 0xAF34 },
+{ 0xAF35, 0xAF35, 0xAF35 },
+{ 0xAF36, 0xAF36, 0xAF36 },
+{ 0xAF37, 0xAF37, 0xAF37 },
+{ 0xAF38, 0xAF38, 0xAF38 },
+{ 0xAF39, 0xAF39, 0xAF39 },
+{ 0xAF3A, 0xAF3A, 0xAF3A },
+{ 0xAF3B, 0xAF3B, 0xAF3B },
+{ 0xAF3C, 0xAF3C, 0xAF3C },
+{ 0xAF3D, 0xAF3D, 0xAF3D },
+{ 0xAF3E, 0xAF3E, 0xAF3E },
+{ 0xAF3F, 0xAF3F, 0xAF3F },
+{ 0xAF40, 0xAF40, 0xAF40 },
+{ 0xAF41, 0xAF41, 0xAF41 },
+{ 0xAF42, 0xAF42, 0xAF42 },
+{ 0xAF43, 0xAF43, 0xAF43 },
+{ 0xAF44, 0xAF44, 0xAF44 },
+{ 0xAF45, 0xAF45, 0xAF45 },
+{ 0xAF46, 0xAF46, 0xAF46 },
+{ 0xAF47, 0xAF47, 0xAF47 },
+{ 0xAF48, 0xAF48, 0xAF48 },
+{ 0xAF49, 0xAF49, 0xAF49 },
+{ 0xAF4A, 0xAF4A, 0xAF4A },
+{ 0xAF4B, 0xAF4B, 0xAF4B },
+{ 0xAF4C, 0xAF4C, 0xAF4C },
+{ 0xAF4D, 0xAF4D, 0xAF4D },
+{ 0xAF4E, 0xAF4E, 0xAF4E },
+{ 0xAF4F, 0xAF4F, 0xAF4F },
+{ 0xAF50, 0xAF50, 0xAF50 },
+{ 0xAF51, 0xAF51, 0xAF51 },
+{ 0xAF52, 0xAF52, 0xAF52 },
+{ 0xAF53, 0xAF53, 0xAF53 },
+{ 0xAF54, 0xAF54, 0xAF54 },
+{ 0xAF55, 0xAF55, 0xAF55 },
+{ 0xAF56, 0xAF56, 0xAF56 },
+{ 0xAF57, 0xAF57, 0xAF57 },
+{ 0xAF58, 0xAF58, 0xAF58 },
+{ 0xAF59, 0xAF59, 0xAF59 },
+{ 0xAF5A, 0xAF5A, 0xAF5A },
+{ 0xAF5B, 0xAF5B, 0xAF5B },
+{ 0xAF5C, 0xAF5C, 0xAF5C },
+{ 0xAF5D, 0xAF5D, 0xAF5D },
+{ 0xAF5E, 0xAF5E, 0xAF5E },
+{ 0xAF5F, 0xAF5F, 0xAF5F },
+{ 0xAF60, 0xAF60, 0xAF60 },
+{ 0xAF61, 0xAF61, 0xAF61 },
+{ 0xAF62, 0xAF62, 0xAF62 },
+{ 0xAF63, 0xAF63, 0xAF63 },
+{ 0xAF64, 0xAF64, 0xAF64 },
+{ 0xAF65, 0xAF65, 0xAF65 },
+{ 0xAF66, 0xAF66, 0xAF66 },
+{ 0xAF67, 0xAF67, 0xAF67 },
+{ 0xAF68, 0xAF68, 0xAF68 },
+{ 0xAF69, 0xAF69, 0xAF69 },
+{ 0xAF6A, 0xAF6A, 0xAF6A },
+{ 0xAF6B, 0xAF6B, 0xAF6B },
+{ 0xAF6C, 0xAF6C, 0xAF6C },
+{ 0xAF6D, 0xAF6D, 0xAF6D },
+{ 0xAF6E, 0xAF6E, 0xAF6E },
+{ 0xAF6F, 0xAF6F, 0xAF6F },
+{ 0xAF70, 0xAF70, 0xAF70 },
+{ 0xAF71, 0xAF71, 0xAF71 },
+{ 0xAF72, 0xAF72, 0xAF72 },
+{ 0xAF73, 0xAF73, 0xAF73 },
+{ 0xAF74, 0xAF74, 0xAF74 },
+{ 0xAF75, 0xAF75, 0xAF75 },
+{ 0xAF76, 0xAF76, 0xAF76 },
+{ 0xAF77, 0xAF77, 0xAF77 },
+{ 0xAF78, 0xAF78, 0xAF78 },
+{ 0xAF79, 0xAF79, 0xAF79 },
+{ 0xAF7A, 0xAF7A, 0xAF7A },
+{ 0xAF7B, 0xAF7B, 0xAF7B },
+{ 0xAF7C, 0xAF7C, 0xAF7C },
+{ 0xAF7D, 0xAF7D, 0xAF7D },
+{ 0xAF7E, 0xAF7E, 0xAF7E },
+{ 0xAF7F, 0xAF7F, 0xAF7F },
+{ 0xAF80, 0xAF80, 0xAF80 },
+{ 0xAF81, 0xAF81, 0xAF81 },
+{ 0xAF82, 0xAF82, 0xAF82 },
+{ 0xAF83, 0xAF83, 0xAF83 },
+{ 0xAF84, 0xAF84, 0xAF84 },
+{ 0xAF85, 0xAF85, 0xAF85 },
+{ 0xAF86, 0xAF86, 0xAF86 },
+{ 0xAF87, 0xAF87, 0xAF87 },
+{ 0xAF88, 0xAF88, 0xAF88 },
+{ 0xAF89, 0xAF89, 0xAF89 },
+{ 0xAF8A, 0xAF8A, 0xAF8A },
+{ 0xAF8B, 0xAF8B, 0xAF8B },
+{ 0xAF8C, 0xAF8C, 0xAF8C },
+{ 0xAF8D, 0xAF8D, 0xAF8D },
+{ 0xAF8E, 0xAF8E, 0xAF8E },
+{ 0xAF8F, 0xAF8F, 0xAF8F },
+{ 0xAF90, 0xAF90, 0xAF90 },
+{ 0xAF91, 0xAF91, 0xAF91 },
+{ 0xAF92, 0xAF92, 0xAF92 },
+{ 0xAF93, 0xAF93, 0xAF93 },
+{ 0xAF94, 0xAF94, 0xAF94 },
+{ 0xAF95, 0xAF95, 0xAF95 },
+{ 0xAF96, 0xAF96, 0xAF96 },
+{ 0xAF97, 0xAF97, 0xAF97 },
+{ 0xAF98, 0xAF98, 0xAF98 },
+{ 0xAF99, 0xAF99, 0xAF99 },
+{ 0xAF9A, 0xAF9A, 0xAF9A },
+{ 0xAF9B, 0xAF9B, 0xAF9B },
+{ 0xAF9C, 0xAF9C, 0xAF9C },
+{ 0xAF9D, 0xAF9D, 0xAF9D },
+{ 0xAF9E, 0xAF9E, 0xAF9E },
+{ 0xAF9F, 0xAF9F, 0xAF9F },
+{ 0xAFA0, 0xAFA0, 0xAFA0 },
+{ 0xAFA1, 0xAFA1, 0xAFA1 },
+{ 0xAFA2, 0xAFA2, 0xAFA2 },
+{ 0xAFA3, 0xAFA3, 0xAFA3 },
+{ 0xAFA4, 0xAFA4, 0xAFA4 },
+{ 0xAFA5, 0xAFA5, 0xAFA5 },
+{ 0xAFA6, 0xAFA6, 0xAFA6 },
+{ 0xAFA7, 0xAFA7, 0xAFA7 },
+{ 0xAFA8, 0xAFA8, 0xAFA8 },
+{ 0xAFA9, 0xAFA9, 0xAFA9 },
+{ 0xAFAA, 0xAFAA, 0xAFAA },
+{ 0xAFAB, 0xAFAB, 0xAFAB },
+{ 0xAFAC, 0xAFAC, 0xAFAC },
+{ 0xAFAD, 0xAFAD, 0xAFAD },
+{ 0xAFAE, 0xAFAE, 0xAFAE },
+{ 0xAFAF, 0xAFAF, 0xAFAF },
+{ 0xAFB0, 0xAFB0, 0xAFB0 },
+{ 0xAFB1, 0xAFB1, 0xAFB1 },
+{ 0xAFB2, 0xAFB2, 0xAFB2 },
+{ 0xAFB3, 0xAFB3, 0xAFB3 },
+{ 0xAFB4, 0xAFB4, 0xAFB4 },
+{ 0xAFB5, 0xAFB5, 0xAFB5 },
+{ 0xAFB6, 0xAFB6, 0xAFB6 },
+{ 0xAFB7, 0xAFB7, 0xAFB7 },
+{ 0xAFB8, 0xAFB8, 0xAFB8 },
+{ 0xAFB9, 0xAFB9, 0xAFB9 },
+{ 0xAFBA, 0xAFBA, 0xAFBA },
+{ 0xAFBB, 0xAFBB, 0xAFBB },
+{ 0xAFBC, 0xAFBC, 0xAFBC },
+{ 0xAFBD, 0xAFBD, 0xAFBD },
+{ 0xAFBE, 0xAFBE, 0xAFBE },
+{ 0xAFBF, 0xAFBF, 0xAFBF },
+{ 0xAFC0, 0xAFC0, 0xAFC0 },
+{ 0xAFC1, 0xAFC1, 0xAFC1 },
+{ 0xAFC2, 0xAFC2, 0xAFC2 },
+{ 0xAFC3, 0xAFC3, 0xAFC3 },
+{ 0xAFC4, 0xAFC4, 0xAFC4 },
+{ 0xAFC5, 0xAFC5, 0xAFC5 },
+{ 0xAFC6, 0xAFC6, 0xAFC6 },
+{ 0xAFC7, 0xAFC7, 0xAFC7 },
+{ 0xAFC8, 0xAFC8, 0xAFC8 },
+{ 0xAFC9, 0xAFC9, 0xAFC9 },
+{ 0xAFCA, 0xAFCA, 0xAFCA },
+{ 0xAFCB, 0xAFCB, 0xAFCB },
+{ 0xAFCC, 0xAFCC, 0xAFCC },
+{ 0xAFCD, 0xAFCD, 0xAFCD },
+{ 0xAFCE, 0xAFCE, 0xAFCE },
+{ 0xAFCF, 0xAFCF, 0xAFCF },
+{ 0xAFD0, 0xAFD0, 0xAFD0 },
+{ 0xAFD1, 0xAFD1, 0xAFD1 },
+{ 0xAFD2, 0xAFD2, 0xAFD2 },
+{ 0xAFD3, 0xAFD3, 0xAFD3 },
+{ 0xAFD4, 0xAFD4, 0xAFD4 },
+{ 0xAFD5, 0xAFD5, 0xAFD5 },
+{ 0xAFD6, 0xAFD6, 0xAFD6 },
+{ 0xAFD7, 0xAFD7, 0xAFD7 },
+{ 0xAFD8, 0xAFD8, 0xAFD8 },
+{ 0xAFD9, 0xAFD9, 0xAFD9 },
+{ 0xAFDA, 0xAFDA, 0xAFDA },
+{ 0xAFDB, 0xAFDB, 0xAFDB },
+{ 0xAFDC, 0xAFDC, 0xAFDC },
+{ 0xAFDD, 0xAFDD, 0xAFDD },
+{ 0xAFDE, 0xAFDE, 0xAFDE },
+{ 0xAFDF, 0xAFDF, 0xAFDF },
+{ 0xAFE0, 0xAFE0, 0xAFE0 },
+{ 0xAFE1, 0xAFE1, 0xAFE1 },
+{ 0xAFE2, 0xAFE2, 0xAFE2 },
+{ 0xAFE3, 0xAFE3, 0xAFE3 },
+{ 0xAFE4, 0xAFE4, 0xAFE4 },
+{ 0xAFE5, 0xAFE5, 0xAFE5 },
+{ 0xAFE6, 0xAFE6, 0xAFE6 },
+{ 0xAFE7, 0xAFE7, 0xAFE7 },
+{ 0xAFE8, 0xAFE8, 0xAFE8 },
+{ 0xAFE9, 0xAFE9, 0xAFE9 },
+{ 0xAFEA, 0xAFEA, 0xAFEA },
+{ 0xAFEB, 0xAFEB, 0xAFEB },
+{ 0xAFEC, 0xAFEC, 0xAFEC },
+{ 0xAFED, 0xAFED, 0xAFED },
+{ 0xAFEE, 0xAFEE, 0xAFEE },
+{ 0xAFEF, 0xAFEF, 0xAFEF },
+{ 0xAFF0, 0xAFF0, 0xAFF0 },
+{ 0xAFF1, 0xAFF1, 0xAFF1 },
+{ 0xAFF2, 0xAFF2, 0xAFF2 },
+{ 0xAFF3, 0xAFF3, 0xAFF3 },
+{ 0xAFF4, 0xAFF4, 0xAFF4 },
+{ 0xAFF5, 0xAFF5, 0xAFF5 },
+{ 0xAFF6, 0xAFF6, 0xAFF6 },
+{ 0xAFF7, 0xAFF7, 0xAFF7 },
+{ 0xAFF8, 0xAFF8, 0xAFF8 },
+{ 0xAFF9, 0xAFF9, 0xAFF9 },
+{ 0xAFFA, 0xAFFA, 0xAFFA },
+{ 0xAFFB, 0xAFFB, 0xAFFB },
+{ 0xAFFC, 0xAFFC, 0xAFFC },
+{ 0xAFFD, 0xAFFD, 0xAFFD },
+{ 0xAFFE, 0xAFFE, 0xAFFE },
+{ 0xAFFF, 0xAFFF, 0xAFFF },
+{ 0xB000, 0xB000, 0xB000 },
+{ 0xB001, 0xB001, 0xB001 },
+{ 0xB002, 0xB002, 0xB002 },
+{ 0xB003, 0xB003, 0xB003 },
+{ 0xB004, 0xB004, 0xB004 },
+{ 0xB005, 0xB005, 0xB005 },
+{ 0xB006, 0xB006, 0xB006 },
+{ 0xB007, 0xB007, 0xB007 },
+{ 0xB008, 0xB008, 0xB008 },
+{ 0xB009, 0xB009, 0xB009 },
+{ 0xB00A, 0xB00A, 0xB00A },
+{ 0xB00B, 0xB00B, 0xB00B },
+{ 0xB00C, 0xB00C, 0xB00C },
+{ 0xB00D, 0xB00D, 0xB00D },
+{ 0xB00E, 0xB00E, 0xB00E },
+{ 0xB00F, 0xB00F, 0xB00F },
+{ 0xB010, 0xB010, 0xB010 },
+{ 0xB011, 0xB011, 0xB011 },
+{ 0xB012, 0xB012, 0xB012 },
+{ 0xB013, 0xB013, 0xB013 },
+{ 0xB014, 0xB014, 0xB014 },
+{ 0xB015, 0xB015, 0xB015 },
+{ 0xB016, 0xB016, 0xB016 },
+{ 0xB017, 0xB017, 0xB017 },
+{ 0xB018, 0xB018, 0xB018 },
+{ 0xB019, 0xB019, 0xB019 },
+{ 0xB01A, 0xB01A, 0xB01A },
+{ 0xB01B, 0xB01B, 0xB01B },
+{ 0xB01C, 0xB01C, 0xB01C },
+{ 0xB01D, 0xB01D, 0xB01D },
+{ 0xB01E, 0xB01E, 0xB01E },
+{ 0xB01F, 0xB01F, 0xB01F },
+{ 0xB020, 0xB020, 0xB020 },
+{ 0xB021, 0xB021, 0xB021 },
+{ 0xB022, 0xB022, 0xB022 },
+{ 0xB023, 0xB023, 0xB023 },
+{ 0xB024, 0xB024, 0xB024 },
+{ 0xB025, 0xB025, 0xB025 },
+{ 0xB026, 0xB026, 0xB026 },
+{ 0xB027, 0xB027, 0xB027 },
+{ 0xB028, 0xB028, 0xB028 },
+{ 0xB029, 0xB029, 0xB029 },
+{ 0xB02A, 0xB02A, 0xB02A },
+{ 0xB02B, 0xB02B, 0xB02B },
+{ 0xB02C, 0xB02C, 0xB02C },
+{ 0xB02D, 0xB02D, 0xB02D },
+{ 0xB02E, 0xB02E, 0xB02E },
+{ 0xB02F, 0xB02F, 0xB02F },
+{ 0xB030, 0xB030, 0xB030 },
+{ 0xB031, 0xB031, 0xB031 },
+{ 0xB032, 0xB032, 0xB032 },
+{ 0xB033, 0xB033, 0xB033 },
+{ 0xB034, 0xB034, 0xB034 },
+{ 0xB035, 0xB035, 0xB035 },
+{ 0xB036, 0xB036, 0xB036 },
+{ 0xB037, 0xB037, 0xB037 },
+{ 0xB038, 0xB038, 0xB038 },
+{ 0xB039, 0xB039, 0xB039 },
+{ 0xB03A, 0xB03A, 0xB03A },
+{ 0xB03B, 0xB03B, 0xB03B },
+{ 0xB03C, 0xB03C, 0xB03C },
+{ 0xB03D, 0xB03D, 0xB03D },
+{ 0xB03E, 0xB03E, 0xB03E },
+{ 0xB03F, 0xB03F, 0xB03F },
+{ 0xB040, 0xB040, 0xB040 },
+{ 0xB041, 0xB041, 0xB041 },
+{ 0xB042, 0xB042, 0xB042 },
+{ 0xB043, 0xB043, 0xB043 },
+{ 0xB044, 0xB044, 0xB044 },
+{ 0xB045, 0xB045, 0xB045 },
+{ 0xB046, 0xB046, 0xB046 },
+{ 0xB047, 0xB047, 0xB047 },
+{ 0xB048, 0xB048, 0xB048 },
+{ 0xB049, 0xB049, 0xB049 },
+{ 0xB04A, 0xB04A, 0xB04A },
+{ 0xB04B, 0xB04B, 0xB04B },
+{ 0xB04C, 0xB04C, 0xB04C },
+{ 0xB04D, 0xB04D, 0xB04D },
+{ 0xB04E, 0xB04E, 0xB04E },
+{ 0xB04F, 0xB04F, 0xB04F },
+{ 0xB050, 0xB050, 0xB050 },
+{ 0xB051, 0xB051, 0xB051 },
+{ 0xB052, 0xB052, 0xB052 },
+{ 0xB053, 0xB053, 0xB053 },
+{ 0xB054, 0xB054, 0xB054 },
+{ 0xB055, 0xB055, 0xB055 },
+{ 0xB056, 0xB056, 0xB056 },
+{ 0xB057, 0xB057, 0xB057 },
+{ 0xB058, 0xB058, 0xB058 },
+{ 0xB059, 0xB059, 0xB059 },
+{ 0xB05A, 0xB05A, 0xB05A },
+{ 0xB05B, 0xB05B, 0xB05B },
+{ 0xB05C, 0xB05C, 0xB05C },
+{ 0xB05D, 0xB05D, 0xB05D },
+{ 0xB05E, 0xB05E, 0xB05E },
+{ 0xB05F, 0xB05F, 0xB05F },
+{ 0xB060, 0xB060, 0xB060 },
+{ 0xB061, 0xB061, 0xB061 },
+{ 0xB062, 0xB062, 0xB062 },
+{ 0xB063, 0xB063, 0xB063 },
+{ 0xB064, 0xB064, 0xB064 },
+{ 0xB065, 0xB065, 0xB065 },
+{ 0xB066, 0xB066, 0xB066 },
+{ 0xB067, 0xB067, 0xB067 },
+{ 0xB068, 0xB068, 0xB068 },
+{ 0xB069, 0xB069, 0xB069 },
+{ 0xB06A, 0xB06A, 0xB06A },
+{ 0xB06B, 0xB06B, 0xB06B },
+{ 0xB06C, 0xB06C, 0xB06C },
+{ 0xB06D, 0xB06D, 0xB06D },
+{ 0xB06E, 0xB06E, 0xB06E },
+{ 0xB06F, 0xB06F, 0xB06F },
+{ 0xB070, 0xB070, 0xB070 },
+{ 0xB071, 0xB071, 0xB071 },
+{ 0xB072, 0xB072, 0xB072 },
+{ 0xB073, 0xB073, 0xB073 },
+{ 0xB074, 0xB074, 0xB074 },
+{ 0xB075, 0xB075, 0xB075 },
+{ 0xB076, 0xB076, 0xB076 },
+{ 0xB077, 0xB077, 0xB077 },
+{ 0xB078, 0xB078, 0xB078 },
+{ 0xB079, 0xB079, 0xB079 },
+{ 0xB07A, 0xB07A, 0xB07A },
+{ 0xB07B, 0xB07B, 0xB07B },
+{ 0xB07C, 0xB07C, 0xB07C },
+{ 0xB07D, 0xB07D, 0xB07D },
+{ 0xB07E, 0xB07E, 0xB07E },
+{ 0xB07F, 0xB07F, 0xB07F },
+{ 0xB080, 0xB080, 0xB080 },
+{ 0xB081, 0xB081, 0xB081 },
+{ 0xB082, 0xB082, 0xB082 },
+{ 0xB083, 0xB083, 0xB083 },
+{ 0xB084, 0xB084, 0xB084 },
+{ 0xB085, 0xB085, 0xB085 },
+{ 0xB086, 0xB086, 0xB086 },
+{ 0xB087, 0xB087, 0xB087 },
+{ 0xB088, 0xB088, 0xB088 },
+{ 0xB089, 0xB089, 0xB089 },
+{ 0xB08A, 0xB08A, 0xB08A },
+{ 0xB08B, 0xB08B, 0xB08B },
+{ 0xB08C, 0xB08C, 0xB08C },
+{ 0xB08D, 0xB08D, 0xB08D },
+{ 0xB08E, 0xB08E, 0xB08E },
+{ 0xB08F, 0xB08F, 0xB08F },
+{ 0xB090, 0xB090, 0xB090 },
+{ 0xB091, 0xB091, 0xB091 },
+{ 0xB092, 0xB092, 0xB092 },
+{ 0xB093, 0xB093, 0xB093 },
+{ 0xB094, 0xB094, 0xB094 },
+{ 0xB095, 0xB095, 0xB095 },
+{ 0xB096, 0xB096, 0xB096 },
+{ 0xB097, 0xB097, 0xB097 },
+{ 0xB098, 0xB098, 0xB098 },
+{ 0xB099, 0xB099, 0xB099 },
+{ 0xB09A, 0xB09A, 0xB09A },
+{ 0xB09B, 0xB09B, 0xB09B },
+{ 0xB09C, 0xB09C, 0xB09C },
+{ 0xB09D, 0xB09D, 0xB09D },
+{ 0xB09E, 0xB09E, 0xB09E },
+{ 0xB09F, 0xB09F, 0xB09F },
+{ 0xB0A0, 0xB0A0, 0xB0A0 },
+{ 0xB0A1, 0xB0A1, 0xB0A1 },
+{ 0xB0A2, 0xB0A2, 0xB0A2 },
+{ 0xB0A3, 0xB0A3, 0xB0A3 },
+{ 0xB0A4, 0xB0A4, 0xB0A4 },
+{ 0xB0A5, 0xB0A5, 0xB0A5 },
+{ 0xB0A6, 0xB0A6, 0xB0A6 },
+{ 0xB0A7, 0xB0A7, 0xB0A7 },
+{ 0xB0A8, 0xB0A8, 0xB0A8 },
+{ 0xB0A9, 0xB0A9, 0xB0A9 },
+{ 0xB0AA, 0xB0AA, 0xB0AA },
+{ 0xB0AB, 0xB0AB, 0xB0AB },
+{ 0xB0AC, 0xB0AC, 0xB0AC },
+{ 0xB0AD, 0xB0AD, 0xB0AD },
+{ 0xB0AE, 0xB0AE, 0xB0AE },
+{ 0xB0AF, 0xB0AF, 0xB0AF },
+{ 0xB0B0, 0xB0B0, 0xB0B0 },
+{ 0xB0B1, 0xB0B1, 0xB0B1 },
+{ 0xB0B2, 0xB0B2, 0xB0B2 },
+{ 0xB0B3, 0xB0B3, 0xB0B3 },
+{ 0xB0B4, 0xB0B4, 0xB0B4 },
+{ 0xB0B5, 0xB0B5, 0xB0B5 },
+{ 0xB0B6, 0xB0B6, 0xB0B6 },
+{ 0xB0B7, 0xB0B7, 0xB0B7 },
+{ 0xB0B8, 0xB0B8, 0xB0B8 },
+{ 0xB0B9, 0xB0B9, 0xB0B9 },
+{ 0xB0BA, 0xB0BA, 0xB0BA },
+{ 0xB0BB, 0xB0BB, 0xB0BB },
+{ 0xB0BC, 0xB0BC, 0xB0BC },
+{ 0xB0BD, 0xB0BD, 0xB0BD },
+{ 0xB0BE, 0xB0BE, 0xB0BE },
+{ 0xB0BF, 0xB0BF, 0xB0BF },
+{ 0xB0C0, 0xB0C0, 0xB0C0 },
+{ 0xB0C1, 0xB0C1, 0xB0C1 },
+{ 0xB0C2, 0xB0C2, 0xB0C2 },
+{ 0xB0C3, 0xB0C3, 0xB0C3 },
+{ 0xB0C4, 0xB0C4, 0xB0C4 },
+{ 0xB0C5, 0xB0C5, 0xB0C5 },
+{ 0xB0C6, 0xB0C6, 0xB0C6 },
+{ 0xB0C7, 0xB0C7, 0xB0C7 },
+{ 0xB0C8, 0xB0C8, 0xB0C8 },
+{ 0xB0C9, 0xB0C9, 0xB0C9 },
+{ 0xB0CA, 0xB0CA, 0xB0CA },
+{ 0xB0CB, 0xB0CB, 0xB0CB },
+{ 0xB0CC, 0xB0CC, 0xB0CC },
+{ 0xB0CD, 0xB0CD, 0xB0CD },
+{ 0xB0CE, 0xB0CE, 0xB0CE },
+{ 0xB0CF, 0xB0CF, 0xB0CF },
+{ 0xB0D0, 0xB0D0, 0xB0D0 },
+{ 0xB0D1, 0xB0D1, 0xB0D1 },
+{ 0xB0D2, 0xB0D2, 0xB0D2 },
+{ 0xB0D3, 0xB0D3, 0xB0D3 },
+{ 0xB0D4, 0xB0D4, 0xB0D4 },
+{ 0xB0D5, 0xB0D5, 0xB0D5 },
+{ 0xB0D6, 0xB0D6, 0xB0D6 },
+{ 0xB0D7, 0xB0D7, 0xB0D7 },
+{ 0xB0D8, 0xB0D8, 0xB0D8 },
+{ 0xB0D9, 0xB0D9, 0xB0D9 },
+{ 0xB0DA, 0xB0DA, 0xB0DA },
+{ 0xB0DB, 0xB0DB, 0xB0DB },
+{ 0xB0DC, 0xB0DC, 0xB0DC },
+{ 0xB0DD, 0xB0DD, 0xB0DD },
+{ 0xB0DE, 0xB0DE, 0xB0DE },
+{ 0xB0DF, 0xB0DF, 0xB0DF },
+{ 0xB0E0, 0xB0E0, 0xB0E0 },
+{ 0xB0E1, 0xB0E1, 0xB0E1 },
+{ 0xB0E2, 0xB0E2, 0xB0E2 },
+{ 0xB0E3, 0xB0E3, 0xB0E3 },
+{ 0xB0E4, 0xB0E4, 0xB0E4 },
+{ 0xB0E5, 0xB0E5, 0xB0E5 },
+{ 0xB0E6, 0xB0E6, 0xB0E6 },
+{ 0xB0E7, 0xB0E7, 0xB0E7 },
+{ 0xB0E8, 0xB0E8, 0xB0E8 },
+{ 0xB0E9, 0xB0E9, 0xB0E9 },
+{ 0xB0EA, 0xB0EA, 0xB0EA },
+{ 0xB0EB, 0xB0EB, 0xB0EB },
+{ 0xB0EC, 0xB0EC, 0xB0EC },
+{ 0xB0ED, 0xB0ED, 0xB0ED },
+{ 0xB0EE, 0xB0EE, 0xB0EE },
+{ 0xB0EF, 0xB0EF, 0xB0EF },
+{ 0xB0F0, 0xB0F0, 0xB0F0 },
+{ 0xB0F1, 0xB0F1, 0xB0F1 },
+{ 0xB0F2, 0xB0F2, 0xB0F2 },
+{ 0xB0F3, 0xB0F3, 0xB0F3 },
+{ 0xB0F4, 0xB0F4, 0xB0F4 },
+{ 0xB0F5, 0xB0F5, 0xB0F5 },
+{ 0xB0F6, 0xB0F6, 0xB0F6 },
+{ 0xB0F7, 0xB0F7, 0xB0F7 },
+{ 0xB0F8, 0xB0F8, 0xB0F8 },
+{ 0xB0F9, 0xB0F9, 0xB0F9 },
+{ 0xB0FA, 0xB0FA, 0xB0FA },
+{ 0xB0FB, 0xB0FB, 0xB0FB },
+{ 0xB0FC, 0xB0FC, 0xB0FC },
+{ 0xB0FD, 0xB0FD, 0xB0FD },
+{ 0xB0FE, 0xB0FE, 0xB0FE },
+{ 0xB0FF, 0xB0FF, 0xB0FF },
+{ 0xB100, 0xB100, 0xB100 },
+{ 0xB101, 0xB101, 0xB101 },
+{ 0xB102, 0xB102, 0xB102 },
+{ 0xB103, 0xB103, 0xB103 },
+{ 0xB104, 0xB104, 0xB104 },
+{ 0xB105, 0xB105, 0xB105 },
+{ 0xB106, 0xB106, 0xB106 },
+{ 0xB107, 0xB107, 0xB107 },
+{ 0xB108, 0xB108, 0xB108 },
+{ 0xB109, 0xB109, 0xB109 },
+{ 0xB10A, 0xB10A, 0xB10A },
+{ 0xB10B, 0xB10B, 0xB10B },
+{ 0xB10C, 0xB10C, 0xB10C },
+{ 0xB10D, 0xB10D, 0xB10D },
+{ 0xB10E, 0xB10E, 0xB10E },
+{ 0xB10F, 0xB10F, 0xB10F },
+{ 0xB110, 0xB110, 0xB110 },
+{ 0xB111, 0xB111, 0xB111 },
+{ 0xB112, 0xB112, 0xB112 },
+{ 0xB113, 0xB113, 0xB113 },
+{ 0xB114, 0xB114, 0xB114 },
+{ 0xB115, 0xB115, 0xB115 },
+{ 0xB116, 0xB116, 0xB116 },
+{ 0xB117, 0xB117, 0xB117 },
+{ 0xB118, 0xB118, 0xB118 },
+{ 0xB119, 0xB119, 0xB119 },
+{ 0xB11A, 0xB11A, 0xB11A },
+{ 0xB11B, 0xB11B, 0xB11B },
+{ 0xB11C, 0xB11C, 0xB11C },
+{ 0xB11D, 0xB11D, 0xB11D },
+{ 0xB11E, 0xB11E, 0xB11E },
+{ 0xB11F, 0xB11F, 0xB11F },
+{ 0xB120, 0xB120, 0xB120 },
+{ 0xB121, 0xB121, 0xB121 },
+{ 0xB122, 0xB122, 0xB122 },
+{ 0xB123, 0xB123, 0xB123 },
+{ 0xB124, 0xB124, 0xB124 },
+{ 0xB125, 0xB125, 0xB125 },
+{ 0xB126, 0xB126, 0xB126 },
+{ 0xB127, 0xB127, 0xB127 },
+{ 0xB128, 0xB128, 0xB128 },
+{ 0xB129, 0xB129, 0xB129 },
+{ 0xB12A, 0xB12A, 0xB12A },
+{ 0xB12B, 0xB12B, 0xB12B },
+{ 0xB12C, 0xB12C, 0xB12C },
+{ 0xB12D, 0xB12D, 0xB12D },
+{ 0xB12E, 0xB12E, 0xB12E },
+{ 0xB12F, 0xB12F, 0xB12F },
+{ 0xB130, 0xB130, 0xB130 },
+{ 0xB131, 0xB131, 0xB131 },
+{ 0xB132, 0xB132, 0xB132 },
+{ 0xB133, 0xB133, 0xB133 },
+{ 0xB134, 0xB134, 0xB134 },
+{ 0xB135, 0xB135, 0xB135 },
+{ 0xB136, 0xB136, 0xB136 },
+{ 0xB137, 0xB137, 0xB137 },
+{ 0xB138, 0xB138, 0xB138 },
+{ 0xB139, 0xB139, 0xB139 },
+{ 0xB13A, 0xB13A, 0xB13A },
+{ 0xB13B, 0xB13B, 0xB13B },
+{ 0xB13C, 0xB13C, 0xB13C },
+{ 0xB13D, 0xB13D, 0xB13D },
+{ 0xB13E, 0xB13E, 0xB13E },
+{ 0xB13F, 0xB13F, 0xB13F },
+{ 0xB140, 0xB140, 0xB140 },
+{ 0xB141, 0xB141, 0xB141 },
+{ 0xB142, 0xB142, 0xB142 },
+{ 0xB143, 0xB143, 0xB143 },
+{ 0xB144, 0xB144, 0xB144 },
+{ 0xB145, 0xB145, 0xB145 },
+{ 0xB146, 0xB146, 0xB146 },
+{ 0xB147, 0xB147, 0xB147 },
+{ 0xB148, 0xB148, 0xB148 },
+{ 0xB149, 0xB149, 0xB149 },
+{ 0xB14A, 0xB14A, 0xB14A },
+{ 0xB14B, 0xB14B, 0xB14B },
+{ 0xB14C, 0xB14C, 0xB14C },
+{ 0xB14D, 0xB14D, 0xB14D },
+{ 0xB14E, 0xB14E, 0xB14E },
+{ 0xB14F, 0xB14F, 0xB14F },
+{ 0xB150, 0xB150, 0xB150 },
+{ 0xB151, 0xB151, 0xB151 },
+{ 0xB152, 0xB152, 0xB152 },
+{ 0xB153, 0xB153, 0xB153 },
+{ 0xB154, 0xB154, 0xB154 },
+{ 0xB155, 0xB155, 0xB155 },
+{ 0xB156, 0xB156, 0xB156 },
+{ 0xB157, 0xB157, 0xB157 },
+{ 0xB158, 0xB158, 0xB158 },
+{ 0xB159, 0xB159, 0xB159 },
+{ 0xB15A, 0xB15A, 0xB15A },
+{ 0xB15B, 0xB15B, 0xB15B },
+{ 0xB15C, 0xB15C, 0xB15C },
+{ 0xB15D, 0xB15D, 0xB15D },
+{ 0xB15E, 0xB15E, 0xB15E },
+{ 0xB15F, 0xB15F, 0xB15F },
+{ 0xB160, 0xB160, 0xB160 },
+{ 0xB161, 0xB161, 0xB161 },
+{ 0xB162, 0xB162, 0xB162 },
+{ 0xB163, 0xB163, 0xB163 },
+{ 0xB164, 0xB164, 0xB164 },
+{ 0xB165, 0xB165, 0xB165 },
+{ 0xB166, 0xB166, 0xB166 },
+{ 0xB167, 0xB167, 0xB167 },
+{ 0xB168, 0xB168, 0xB168 },
+{ 0xB169, 0xB169, 0xB169 },
+{ 0xB16A, 0xB16A, 0xB16A },
+{ 0xB16B, 0xB16B, 0xB16B },
+{ 0xB16C, 0xB16C, 0xB16C },
+{ 0xB16D, 0xB16D, 0xB16D },
+{ 0xB16E, 0xB16E, 0xB16E },
+{ 0xB16F, 0xB16F, 0xB16F },
+{ 0xB170, 0xB170, 0xB170 },
+{ 0xB171, 0xB171, 0xB171 },
+{ 0xB172, 0xB172, 0xB172 },
+{ 0xB173, 0xB173, 0xB173 },
+{ 0xB174, 0xB174, 0xB174 },
+{ 0xB175, 0xB175, 0xB175 },
+{ 0xB176, 0xB176, 0xB176 },
+{ 0xB177, 0xB177, 0xB177 },
+{ 0xB178, 0xB178, 0xB178 },
+{ 0xB179, 0xB179, 0xB179 },
+{ 0xB17A, 0xB17A, 0xB17A },
+{ 0xB17B, 0xB17B, 0xB17B },
+{ 0xB17C, 0xB17C, 0xB17C },
+{ 0xB17D, 0xB17D, 0xB17D },
+{ 0xB17E, 0xB17E, 0xB17E },
+{ 0xB17F, 0xB17F, 0xB17F },
+{ 0xB180, 0xB180, 0xB180 },
+{ 0xB181, 0xB181, 0xB181 },
+{ 0xB182, 0xB182, 0xB182 },
+{ 0xB183, 0xB183, 0xB183 },
+{ 0xB184, 0xB184, 0xB184 },
+{ 0xB185, 0xB185, 0xB185 },
+{ 0xB186, 0xB186, 0xB186 },
+{ 0xB187, 0xB187, 0xB187 },
+{ 0xB188, 0xB188, 0xB188 },
+{ 0xB189, 0xB189, 0xB189 },
+{ 0xB18A, 0xB18A, 0xB18A },
+{ 0xB18B, 0xB18B, 0xB18B },
+{ 0xB18C, 0xB18C, 0xB18C },
+{ 0xB18D, 0xB18D, 0xB18D },
+{ 0xB18E, 0xB18E, 0xB18E },
+{ 0xB18F, 0xB18F, 0xB18F },
+{ 0xB190, 0xB190, 0xB190 },
+{ 0xB191, 0xB191, 0xB191 },
+{ 0xB192, 0xB192, 0xB192 },
+{ 0xB193, 0xB193, 0xB193 },
+{ 0xB194, 0xB194, 0xB194 },
+{ 0xB195, 0xB195, 0xB195 },
+{ 0xB196, 0xB196, 0xB196 },
+{ 0xB197, 0xB197, 0xB197 },
+{ 0xB198, 0xB198, 0xB198 },
+{ 0xB199, 0xB199, 0xB199 },
+{ 0xB19A, 0xB19A, 0xB19A },
+{ 0xB19B, 0xB19B, 0xB19B },
+{ 0xB19C, 0xB19C, 0xB19C },
+{ 0xB19D, 0xB19D, 0xB19D },
+{ 0xB19E, 0xB19E, 0xB19E },
+{ 0xB19F, 0xB19F, 0xB19F },
+{ 0xB1A0, 0xB1A0, 0xB1A0 },
+{ 0xB1A1, 0xB1A1, 0xB1A1 },
+{ 0xB1A2, 0xB1A2, 0xB1A2 },
+{ 0xB1A3, 0xB1A3, 0xB1A3 },
+{ 0xB1A4, 0xB1A4, 0xB1A4 },
+{ 0xB1A5, 0xB1A5, 0xB1A5 },
+{ 0xB1A6, 0xB1A6, 0xB1A6 },
+{ 0xB1A7, 0xB1A7, 0xB1A7 },
+{ 0xB1A8, 0xB1A8, 0xB1A8 },
+{ 0xB1A9, 0xB1A9, 0xB1A9 },
+{ 0xB1AA, 0xB1AA, 0xB1AA },
+{ 0xB1AB, 0xB1AB, 0xB1AB },
+{ 0xB1AC, 0xB1AC, 0xB1AC },
+{ 0xB1AD, 0xB1AD, 0xB1AD },
+{ 0xB1AE, 0xB1AE, 0xB1AE },
+{ 0xB1AF, 0xB1AF, 0xB1AF },
+{ 0xB1B0, 0xB1B0, 0xB1B0 },
+{ 0xB1B1, 0xB1B1, 0xB1B1 },
+{ 0xB1B2, 0xB1B2, 0xB1B2 },
+{ 0xB1B3, 0xB1B3, 0xB1B3 },
+{ 0xB1B4, 0xB1B4, 0xB1B4 },
+{ 0xB1B5, 0xB1B5, 0xB1B5 },
+{ 0xB1B6, 0xB1B6, 0xB1B6 },
+{ 0xB1B7, 0xB1B7, 0xB1B7 },
+{ 0xB1B8, 0xB1B8, 0xB1B8 },
+{ 0xB1B9, 0xB1B9, 0xB1B9 },
+{ 0xB1BA, 0xB1BA, 0xB1BA },
+{ 0xB1BB, 0xB1BB, 0xB1BB },
+{ 0xB1BC, 0xB1BC, 0xB1BC },
+{ 0xB1BD, 0xB1BD, 0xB1BD },
+{ 0xB1BE, 0xB1BE, 0xB1BE },
+{ 0xB1BF, 0xB1BF, 0xB1BF },
+{ 0xB1C0, 0xB1C0, 0xB1C0 },
+{ 0xB1C1, 0xB1C1, 0xB1C1 },
+{ 0xB1C2, 0xB1C2, 0xB1C2 },
+{ 0xB1C3, 0xB1C3, 0xB1C3 },
+{ 0xB1C4, 0xB1C4, 0xB1C4 },
+{ 0xB1C5, 0xB1C5, 0xB1C5 },
+{ 0xB1C6, 0xB1C6, 0xB1C6 },
+{ 0xB1C7, 0xB1C7, 0xB1C7 },
+{ 0xB1C8, 0xB1C8, 0xB1C8 },
+{ 0xB1C9, 0xB1C9, 0xB1C9 },
+{ 0xB1CA, 0xB1CA, 0xB1CA },
+{ 0xB1CB, 0xB1CB, 0xB1CB },
+{ 0xB1CC, 0xB1CC, 0xB1CC },
+{ 0xB1CD, 0xB1CD, 0xB1CD },
+{ 0xB1CE, 0xB1CE, 0xB1CE },
+{ 0xB1CF, 0xB1CF, 0xB1CF },
+{ 0xB1D0, 0xB1D0, 0xB1D0 },
+{ 0xB1D1, 0xB1D1, 0xB1D1 },
+{ 0xB1D2, 0xB1D2, 0xB1D2 },
+{ 0xB1D3, 0xB1D3, 0xB1D3 },
+{ 0xB1D4, 0xB1D4, 0xB1D4 },
+{ 0xB1D5, 0xB1D5, 0xB1D5 },
+{ 0xB1D6, 0xB1D6, 0xB1D6 },
+{ 0xB1D7, 0xB1D7, 0xB1D7 },
+{ 0xB1D8, 0xB1D8, 0xB1D8 },
+{ 0xB1D9, 0xB1D9, 0xB1D9 },
+{ 0xB1DA, 0xB1DA, 0xB1DA },
+{ 0xB1DB, 0xB1DB, 0xB1DB },
+{ 0xB1DC, 0xB1DC, 0xB1DC },
+{ 0xB1DD, 0xB1DD, 0xB1DD },
+{ 0xB1DE, 0xB1DE, 0xB1DE },
+{ 0xB1DF, 0xB1DF, 0xB1DF },
+{ 0xB1E0, 0xB1E0, 0xB1E0 },
+{ 0xB1E1, 0xB1E1, 0xB1E1 },
+{ 0xB1E2, 0xB1E2, 0xB1E2 },
+{ 0xB1E3, 0xB1E3, 0xB1E3 },
+{ 0xB1E4, 0xB1E4, 0xB1E4 },
+{ 0xB1E5, 0xB1E5, 0xB1E5 },
+{ 0xB1E6, 0xB1E6, 0xB1E6 },
+{ 0xB1E7, 0xB1E7, 0xB1E7 },
+{ 0xB1E8, 0xB1E8, 0xB1E8 },
+{ 0xB1E9, 0xB1E9, 0xB1E9 },
+{ 0xB1EA, 0xB1EA, 0xB1EA },
+{ 0xB1EB, 0xB1EB, 0xB1EB },
+{ 0xB1EC, 0xB1EC, 0xB1EC },
+{ 0xB1ED, 0xB1ED, 0xB1ED },
+{ 0xB1EE, 0xB1EE, 0xB1EE },
+{ 0xB1EF, 0xB1EF, 0xB1EF },
+{ 0xB1F0, 0xB1F0, 0xB1F0 },
+{ 0xB1F1, 0xB1F1, 0xB1F1 },
+{ 0xB1F2, 0xB1F2, 0xB1F2 },
+{ 0xB1F3, 0xB1F3, 0xB1F3 },
+{ 0xB1F4, 0xB1F4, 0xB1F4 },
+{ 0xB1F5, 0xB1F5, 0xB1F5 },
+{ 0xB1F6, 0xB1F6, 0xB1F6 },
+{ 0xB1F7, 0xB1F7, 0xB1F7 },
+{ 0xB1F8, 0xB1F8, 0xB1F8 },
+{ 0xB1F9, 0xB1F9, 0xB1F9 },
+{ 0xB1FA, 0xB1FA, 0xB1FA },
+{ 0xB1FB, 0xB1FB, 0xB1FB },
+{ 0xB1FC, 0xB1FC, 0xB1FC },
+{ 0xB1FD, 0xB1FD, 0xB1FD },
+{ 0xB1FE, 0xB1FE, 0xB1FE },
+{ 0xB1FF, 0xB1FF, 0xB1FF },
+{ 0xB200, 0xB200, 0xB200 },
+{ 0xB201, 0xB201, 0xB201 },
+{ 0xB202, 0xB202, 0xB202 },
+{ 0xB203, 0xB203, 0xB203 },
+{ 0xB204, 0xB204, 0xB204 },
+{ 0xB205, 0xB205, 0xB205 },
+{ 0xB206, 0xB206, 0xB206 },
+{ 0xB207, 0xB207, 0xB207 },
+{ 0xB208, 0xB208, 0xB208 },
+{ 0xB209, 0xB209, 0xB209 },
+{ 0xB20A, 0xB20A, 0xB20A },
+{ 0xB20B, 0xB20B, 0xB20B },
+{ 0xB20C, 0xB20C, 0xB20C },
+{ 0xB20D, 0xB20D, 0xB20D },
+{ 0xB20E, 0xB20E, 0xB20E },
+{ 0xB20F, 0xB20F, 0xB20F },
+{ 0xB210, 0xB210, 0xB210 },
+{ 0xB211, 0xB211, 0xB211 },
+{ 0xB212, 0xB212, 0xB212 },
+{ 0xB213, 0xB213, 0xB213 },
+{ 0xB214, 0xB214, 0xB214 },
+{ 0xB215, 0xB215, 0xB215 },
+{ 0xB216, 0xB216, 0xB216 },
+{ 0xB217, 0xB217, 0xB217 },
+{ 0xB218, 0xB218, 0xB218 },
+{ 0xB219, 0xB219, 0xB219 },
+{ 0xB21A, 0xB21A, 0xB21A },
+{ 0xB21B, 0xB21B, 0xB21B },
+{ 0xB21C, 0xB21C, 0xB21C },
+{ 0xB21D, 0xB21D, 0xB21D },
+{ 0xB21E, 0xB21E, 0xB21E },
+{ 0xB21F, 0xB21F, 0xB21F },
+{ 0xB220, 0xB220, 0xB220 },
+{ 0xB221, 0xB221, 0xB221 },
+{ 0xB222, 0xB222, 0xB222 },
+{ 0xB223, 0xB223, 0xB223 },
+{ 0xB224, 0xB224, 0xB224 },
+{ 0xB225, 0xB225, 0xB225 },
+{ 0xB226, 0xB226, 0xB226 },
+{ 0xB227, 0xB227, 0xB227 },
+{ 0xB228, 0xB228, 0xB228 },
+{ 0xB229, 0xB229, 0xB229 },
+{ 0xB22A, 0xB22A, 0xB22A },
+{ 0xB22B, 0xB22B, 0xB22B },
+{ 0xB22C, 0xB22C, 0xB22C },
+{ 0xB22D, 0xB22D, 0xB22D },
+{ 0xB22E, 0xB22E, 0xB22E },
+{ 0xB22F, 0xB22F, 0xB22F },
+{ 0xB230, 0xB230, 0xB230 },
+{ 0xB231, 0xB231, 0xB231 },
+{ 0xB232, 0xB232, 0xB232 },
+{ 0xB233, 0xB233, 0xB233 },
+{ 0xB234, 0xB234, 0xB234 },
+{ 0xB235, 0xB235, 0xB235 },
+{ 0xB236, 0xB236, 0xB236 },
+{ 0xB237, 0xB237, 0xB237 },
+{ 0xB238, 0xB238, 0xB238 },
+{ 0xB239, 0xB239, 0xB239 },
+{ 0xB23A, 0xB23A, 0xB23A },
+{ 0xB23B, 0xB23B, 0xB23B },
+{ 0xB23C, 0xB23C, 0xB23C },
+{ 0xB23D, 0xB23D, 0xB23D },
+{ 0xB23E, 0xB23E, 0xB23E },
+{ 0xB23F, 0xB23F, 0xB23F },
+{ 0xB240, 0xB240, 0xB240 },
+{ 0xB241, 0xB241, 0xB241 },
+{ 0xB242, 0xB242, 0xB242 },
+{ 0xB243, 0xB243, 0xB243 },
+{ 0xB244, 0xB244, 0xB244 },
+{ 0xB245, 0xB245, 0xB245 },
+{ 0xB246, 0xB246, 0xB246 },
+{ 0xB247, 0xB247, 0xB247 },
+{ 0xB248, 0xB248, 0xB248 },
+{ 0xB249, 0xB249, 0xB249 },
+{ 0xB24A, 0xB24A, 0xB24A },
+{ 0xB24B, 0xB24B, 0xB24B },
+{ 0xB24C, 0xB24C, 0xB24C },
+{ 0xB24D, 0xB24D, 0xB24D },
+{ 0xB24E, 0xB24E, 0xB24E },
+{ 0xB24F, 0xB24F, 0xB24F },
+{ 0xB250, 0xB250, 0xB250 },
+{ 0xB251, 0xB251, 0xB251 },
+{ 0xB252, 0xB252, 0xB252 },
+{ 0xB253, 0xB253, 0xB253 },
+{ 0xB254, 0xB254, 0xB254 },
+{ 0xB255, 0xB255, 0xB255 },
+{ 0xB256, 0xB256, 0xB256 },
+{ 0xB257, 0xB257, 0xB257 },
+{ 0xB258, 0xB258, 0xB258 },
+{ 0xB259, 0xB259, 0xB259 },
+{ 0xB25A, 0xB25A, 0xB25A },
+{ 0xB25B, 0xB25B, 0xB25B },
+{ 0xB25C, 0xB25C, 0xB25C },
+{ 0xB25D, 0xB25D, 0xB25D },
+{ 0xB25E, 0xB25E, 0xB25E },
+{ 0xB25F, 0xB25F, 0xB25F },
+{ 0xB260, 0xB260, 0xB260 },
+{ 0xB261, 0xB261, 0xB261 },
+{ 0xB262, 0xB262, 0xB262 },
+{ 0xB263, 0xB263, 0xB263 },
+{ 0xB264, 0xB264, 0xB264 },
+{ 0xB265, 0xB265, 0xB265 },
+{ 0xB266, 0xB266, 0xB266 },
+{ 0xB267, 0xB267, 0xB267 },
+{ 0xB268, 0xB268, 0xB268 },
+{ 0xB269, 0xB269, 0xB269 },
+{ 0xB26A, 0xB26A, 0xB26A },
+{ 0xB26B, 0xB26B, 0xB26B },
+{ 0xB26C, 0xB26C, 0xB26C },
+{ 0xB26D, 0xB26D, 0xB26D },
+{ 0xB26E, 0xB26E, 0xB26E },
+{ 0xB26F, 0xB26F, 0xB26F },
+{ 0xB270, 0xB270, 0xB270 },
+{ 0xB271, 0xB271, 0xB271 },
+{ 0xB272, 0xB272, 0xB272 },
+{ 0xB273, 0xB273, 0xB273 },
+{ 0xB274, 0xB274, 0xB274 },
+{ 0xB275, 0xB275, 0xB275 },
+{ 0xB276, 0xB276, 0xB276 },
+{ 0xB277, 0xB277, 0xB277 },
+{ 0xB278, 0xB278, 0xB278 },
+{ 0xB279, 0xB279, 0xB279 },
+{ 0xB27A, 0xB27A, 0xB27A },
+{ 0xB27B, 0xB27B, 0xB27B },
+{ 0xB27C, 0xB27C, 0xB27C },
+{ 0xB27D, 0xB27D, 0xB27D },
+{ 0xB27E, 0xB27E, 0xB27E },
+{ 0xB27F, 0xB27F, 0xB27F },
+{ 0xB280, 0xB280, 0xB280 },
+{ 0xB281, 0xB281, 0xB281 },
+{ 0xB282, 0xB282, 0xB282 },
+{ 0xB283, 0xB283, 0xB283 },
+{ 0xB284, 0xB284, 0xB284 },
+{ 0xB285, 0xB285, 0xB285 },
+{ 0xB286, 0xB286, 0xB286 },
+{ 0xB287, 0xB287, 0xB287 },
+{ 0xB288, 0xB288, 0xB288 },
+{ 0xB289, 0xB289, 0xB289 },
+{ 0xB28A, 0xB28A, 0xB28A },
+{ 0xB28B, 0xB28B, 0xB28B },
+{ 0xB28C, 0xB28C, 0xB28C },
+{ 0xB28D, 0xB28D, 0xB28D },
+{ 0xB28E, 0xB28E, 0xB28E },
+{ 0xB28F, 0xB28F, 0xB28F },
+{ 0xB290, 0xB290, 0xB290 },
+{ 0xB291, 0xB291, 0xB291 },
+{ 0xB292, 0xB292, 0xB292 },
+{ 0xB293, 0xB293, 0xB293 },
+{ 0xB294, 0xB294, 0xB294 },
+{ 0xB295, 0xB295, 0xB295 },
+{ 0xB296, 0xB296, 0xB296 },
+{ 0xB297, 0xB297, 0xB297 },
+{ 0xB298, 0xB298, 0xB298 },
+{ 0xB299, 0xB299, 0xB299 },
+{ 0xB29A, 0xB29A, 0xB29A },
+{ 0xB29B, 0xB29B, 0xB29B },
+{ 0xB29C, 0xB29C, 0xB29C },
+{ 0xB29D, 0xB29D, 0xB29D },
+{ 0xB29E, 0xB29E, 0xB29E },
+{ 0xB29F, 0xB29F, 0xB29F },
+{ 0xB2A0, 0xB2A0, 0xB2A0 },
+{ 0xB2A1, 0xB2A1, 0xB2A1 },
+{ 0xB2A2, 0xB2A2, 0xB2A2 },
+{ 0xB2A3, 0xB2A3, 0xB2A3 },
+{ 0xB2A4, 0xB2A4, 0xB2A4 },
+{ 0xB2A5, 0xB2A5, 0xB2A5 },
+{ 0xB2A6, 0xB2A6, 0xB2A6 },
+{ 0xB2A7, 0xB2A7, 0xB2A7 },
+{ 0xB2A8, 0xB2A8, 0xB2A8 },
+{ 0xB2A9, 0xB2A9, 0xB2A9 },
+{ 0xB2AA, 0xB2AA, 0xB2AA },
+{ 0xB2AB, 0xB2AB, 0xB2AB },
+{ 0xB2AC, 0xB2AC, 0xB2AC },
+{ 0xB2AD, 0xB2AD, 0xB2AD },
+{ 0xB2AE, 0xB2AE, 0xB2AE },
+{ 0xB2AF, 0xB2AF, 0xB2AF },
+{ 0xB2B0, 0xB2B0, 0xB2B0 },
+{ 0xB2B1, 0xB2B1, 0xB2B1 },
+{ 0xB2B2, 0xB2B2, 0xB2B2 },
+{ 0xB2B3, 0xB2B3, 0xB2B3 },
+{ 0xB2B4, 0xB2B4, 0xB2B4 },
+{ 0xB2B5, 0xB2B5, 0xB2B5 },
+{ 0xB2B6, 0xB2B6, 0xB2B6 },
+{ 0xB2B7, 0xB2B7, 0xB2B7 },
+{ 0xB2B8, 0xB2B8, 0xB2B8 },
+{ 0xB2B9, 0xB2B9, 0xB2B9 },
+{ 0xB2BA, 0xB2BA, 0xB2BA },
+{ 0xB2BB, 0xB2BB, 0xB2BB },
+{ 0xB2BC, 0xB2BC, 0xB2BC },
+{ 0xB2BD, 0xB2BD, 0xB2BD },
+{ 0xB2BE, 0xB2BE, 0xB2BE },
+{ 0xB2BF, 0xB2BF, 0xB2BF },
+{ 0xB2C0, 0xB2C0, 0xB2C0 },
+{ 0xB2C1, 0xB2C1, 0xB2C1 },
+{ 0xB2C2, 0xB2C2, 0xB2C2 },
+{ 0xB2C3, 0xB2C3, 0xB2C3 },
+{ 0xB2C4, 0xB2C4, 0xB2C4 },
+{ 0xB2C5, 0xB2C5, 0xB2C5 },
+{ 0xB2C6, 0xB2C6, 0xB2C6 },
+{ 0xB2C7, 0xB2C7, 0xB2C7 },
+{ 0xB2C8, 0xB2C8, 0xB2C8 },
+{ 0xB2C9, 0xB2C9, 0xB2C9 },
+{ 0xB2CA, 0xB2CA, 0xB2CA },
+{ 0xB2CB, 0xB2CB, 0xB2CB },
+{ 0xB2CC, 0xB2CC, 0xB2CC },
+{ 0xB2CD, 0xB2CD, 0xB2CD },
+{ 0xB2CE, 0xB2CE, 0xB2CE },
+{ 0xB2CF, 0xB2CF, 0xB2CF },
+{ 0xB2D0, 0xB2D0, 0xB2D0 },
+{ 0xB2D1, 0xB2D1, 0xB2D1 },
+{ 0xB2D2, 0xB2D2, 0xB2D2 },
+{ 0xB2D3, 0xB2D3, 0xB2D3 },
+{ 0xB2D4, 0xB2D4, 0xB2D4 },
+{ 0xB2D5, 0xB2D5, 0xB2D5 },
+{ 0xB2D6, 0xB2D6, 0xB2D6 },
+{ 0xB2D7, 0xB2D7, 0xB2D7 },
+{ 0xB2D8, 0xB2D8, 0xB2D8 },
+{ 0xB2D9, 0xB2D9, 0xB2D9 },
+{ 0xB2DA, 0xB2DA, 0xB2DA },
+{ 0xB2DB, 0xB2DB, 0xB2DB },
+{ 0xB2DC, 0xB2DC, 0xB2DC },
+{ 0xB2DD, 0xB2DD, 0xB2DD },
+{ 0xB2DE, 0xB2DE, 0xB2DE },
+{ 0xB2DF, 0xB2DF, 0xB2DF },
+{ 0xB2E0, 0xB2E0, 0xB2E0 },
+{ 0xB2E1, 0xB2E1, 0xB2E1 },
+{ 0xB2E2, 0xB2E2, 0xB2E2 },
+{ 0xB2E3, 0xB2E3, 0xB2E3 },
+{ 0xB2E4, 0xB2E4, 0xB2E4 },
+{ 0xB2E5, 0xB2E5, 0xB2E5 },
+{ 0xB2E6, 0xB2E6, 0xB2E6 },
+{ 0xB2E7, 0xB2E7, 0xB2E7 },
+{ 0xB2E8, 0xB2E8, 0xB2E8 },
+{ 0xB2E9, 0xB2E9, 0xB2E9 },
+{ 0xB2EA, 0xB2EA, 0xB2EA },
+{ 0xB2EB, 0xB2EB, 0xB2EB },
+{ 0xB2EC, 0xB2EC, 0xB2EC },
+{ 0xB2ED, 0xB2ED, 0xB2ED },
+{ 0xB2EE, 0xB2EE, 0xB2EE },
+{ 0xB2EF, 0xB2EF, 0xB2EF },
+{ 0xB2F0, 0xB2F0, 0xB2F0 },
+{ 0xB2F1, 0xB2F1, 0xB2F1 },
+{ 0xB2F2, 0xB2F2, 0xB2F2 },
+{ 0xB2F3, 0xB2F3, 0xB2F3 },
+{ 0xB2F4, 0xB2F4, 0xB2F4 },
+{ 0xB2F5, 0xB2F5, 0xB2F5 },
+{ 0xB2F6, 0xB2F6, 0xB2F6 },
+{ 0xB2F7, 0xB2F7, 0xB2F7 },
+{ 0xB2F8, 0xB2F8, 0xB2F8 },
+{ 0xB2F9, 0xB2F9, 0xB2F9 },
+{ 0xB2FA, 0xB2FA, 0xB2FA },
+{ 0xB2FB, 0xB2FB, 0xB2FB },
+{ 0xB2FC, 0xB2FC, 0xB2FC },
+{ 0xB2FD, 0xB2FD, 0xB2FD },
+{ 0xB2FE, 0xB2FE, 0xB2FE },
+{ 0xB2FF, 0xB2FF, 0xB2FF },
+{ 0xB300, 0xB300, 0xB300 },
+{ 0xB301, 0xB301, 0xB301 },
+{ 0xB302, 0xB302, 0xB302 },
+{ 0xB303, 0xB303, 0xB303 },
+{ 0xB304, 0xB304, 0xB304 },
+{ 0xB305, 0xB305, 0xB305 },
+{ 0xB306, 0xB306, 0xB306 },
+{ 0xB307, 0xB307, 0xB307 },
+{ 0xB308, 0xB308, 0xB308 },
+{ 0xB309, 0xB309, 0xB309 },
+{ 0xB30A, 0xB30A, 0xB30A },
+{ 0xB30B, 0xB30B, 0xB30B },
+{ 0xB30C, 0xB30C, 0xB30C },
+{ 0xB30D, 0xB30D, 0xB30D },
+{ 0xB30E, 0xB30E, 0xB30E },
+{ 0xB30F, 0xB30F, 0xB30F },
+{ 0xB310, 0xB310, 0xB310 },
+{ 0xB311, 0xB311, 0xB311 },
+{ 0xB312, 0xB312, 0xB312 },
+{ 0xB313, 0xB313, 0xB313 },
+{ 0xB314, 0xB314, 0xB314 },
+{ 0xB315, 0xB315, 0xB315 },
+{ 0xB316, 0xB316, 0xB316 },
+{ 0xB317, 0xB317, 0xB317 },
+{ 0xB318, 0xB318, 0xB318 },
+{ 0xB319, 0xB319, 0xB319 },
+{ 0xB31A, 0xB31A, 0xB31A },
+{ 0xB31B, 0xB31B, 0xB31B },
+{ 0xB31C, 0xB31C, 0xB31C },
+{ 0xB31D, 0xB31D, 0xB31D },
+{ 0xB31E, 0xB31E, 0xB31E },
+{ 0xB31F, 0xB31F, 0xB31F },
+{ 0xB320, 0xB320, 0xB320 },
+{ 0xB321, 0xB321, 0xB321 },
+{ 0xB322, 0xB322, 0xB322 },
+{ 0xB323, 0xB323, 0xB323 },
+{ 0xB324, 0xB324, 0xB324 },
+{ 0xB325, 0xB325, 0xB325 },
+{ 0xB326, 0xB326, 0xB326 },
+{ 0xB327, 0xB327, 0xB327 },
+{ 0xB328, 0xB328, 0xB328 },
+{ 0xB329, 0xB329, 0xB329 },
+{ 0xB32A, 0xB32A, 0xB32A },
+{ 0xB32B, 0xB32B, 0xB32B },
+{ 0xB32C, 0xB32C, 0xB32C },
+{ 0xB32D, 0xB32D, 0xB32D },
+{ 0xB32E, 0xB32E, 0xB32E },
+{ 0xB32F, 0xB32F, 0xB32F },
+{ 0xB330, 0xB330, 0xB330 },
+{ 0xB331, 0xB331, 0xB331 },
+{ 0xB332, 0xB332, 0xB332 },
+{ 0xB333, 0xB333, 0xB333 },
+{ 0xB334, 0xB334, 0xB334 },
+{ 0xB335, 0xB335, 0xB335 },
+{ 0xB336, 0xB336, 0xB336 },
+{ 0xB337, 0xB337, 0xB337 },
+{ 0xB338, 0xB338, 0xB338 },
+{ 0xB339, 0xB339, 0xB339 },
+{ 0xB33A, 0xB33A, 0xB33A },
+{ 0xB33B, 0xB33B, 0xB33B },
+{ 0xB33C, 0xB33C, 0xB33C },
+{ 0xB33D, 0xB33D, 0xB33D },
+{ 0xB33E, 0xB33E, 0xB33E },
+{ 0xB33F, 0xB33F, 0xB33F },
+{ 0xB340, 0xB340, 0xB340 },
+{ 0xB341, 0xB341, 0xB341 },
+{ 0xB342, 0xB342, 0xB342 },
+{ 0xB343, 0xB343, 0xB343 },
+{ 0xB344, 0xB344, 0xB344 },
+{ 0xB345, 0xB345, 0xB345 },
+{ 0xB346, 0xB346, 0xB346 },
+{ 0xB347, 0xB347, 0xB347 },
+{ 0xB348, 0xB348, 0xB348 },
+{ 0xB349, 0xB349, 0xB349 },
+{ 0xB34A, 0xB34A, 0xB34A },
+{ 0xB34B, 0xB34B, 0xB34B },
+{ 0xB34C, 0xB34C, 0xB34C },
+{ 0xB34D, 0xB34D, 0xB34D },
+{ 0xB34E, 0xB34E, 0xB34E },
+{ 0xB34F, 0xB34F, 0xB34F },
+{ 0xB350, 0xB350, 0xB350 },
+{ 0xB351, 0xB351, 0xB351 },
+{ 0xB352, 0xB352, 0xB352 },
+{ 0xB353, 0xB353, 0xB353 },
+{ 0xB354, 0xB354, 0xB354 },
+{ 0xB355, 0xB355, 0xB355 },
+{ 0xB356, 0xB356, 0xB356 },
+{ 0xB357, 0xB357, 0xB357 },
+{ 0xB358, 0xB358, 0xB358 },
+{ 0xB359, 0xB359, 0xB359 },
+{ 0xB35A, 0xB35A, 0xB35A },
+{ 0xB35B, 0xB35B, 0xB35B },
+{ 0xB35C, 0xB35C, 0xB35C },
+{ 0xB35D, 0xB35D, 0xB35D },
+{ 0xB35E, 0xB35E, 0xB35E },
+{ 0xB35F, 0xB35F, 0xB35F },
+{ 0xB360, 0xB360, 0xB360 },
+{ 0xB361, 0xB361, 0xB361 },
+{ 0xB362, 0xB362, 0xB362 },
+{ 0xB363, 0xB363, 0xB363 },
+{ 0xB364, 0xB364, 0xB364 },
+{ 0xB365, 0xB365, 0xB365 },
+{ 0xB366, 0xB366, 0xB366 },
+{ 0xB367, 0xB367, 0xB367 },
+{ 0xB368, 0xB368, 0xB368 },
+{ 0xB369, 0xB369, 0xB369 },
+{ 0xB36A, 0xB36A, 0xB36A },
+{ 0xB36B, 0xB36B, 0xB36B },
+{ 0xB36C, 0xB36C, 0xB36C },
+{ 0xB36D, 0xB36D, 0xB36D },
+{ 0xB36E, 0xB36E, 0xB36E },
+{ 0xB36F, 0xB36F, 0xB36F },
+{ 0xB370, 0xB370, 0xB370 },
+{ 0xB371, 0xB371, 0xB371 },
+{ 0xB372, 0xB372, 0xB372 },
+{ 0xB373, 0xB373, 0xB373 },
+{ 0xB374, 0xB374, 0xB374 },
+{ 0xB375, 0xB375, 0xB375 },
+{ 0xB376, 0xB376, 0xB376 },
+{ 0xB377, 0xB377, 0xB377 },
+{ 0xB378, 0xB378, 0xB378 },
+{ 0xB379, 0xB379, 0xB379 },
+{ 0xB37A, 0xB37A, 0xB37A },
+{ 0xB37B, 0xB37B, 0xB37B },
+{ 0xB37C, 0xB37C, 0xB37C },
+{ 0xB37D, 0xB37D, 0xB37D },
+{ 0xB37E, 0xB37E, 0xB37E },
+{ 0xB37F, 0xB37F, 0xB37F },
+{ 0xB380, 0xB380, 0xB380 },
+{ 0xB381, 0xB381, 0xB381 },
+{ 0xB382, 0xB382, 0xB382 },
+{ 0xB383, 0xB383, 0xB383 },
+{ 0xB384, 0xB384, 0xB384 },
+{ 0xB385, 0xB385, 0xB385 },
+{ 0xB386, 0xB386, 0xB386 },
+{ 0xB387, 0xB387, 0xB387 },
+{ 0xB388, 0xB388, 0xB388 },
+{ 0xB389, 0xB389, 0xB389 },
+{ 0xB38A, 0xB38A, 0xB38A },
+{ 0xB38B, 0xB38B, 0xB38B },
+{ 0xB38C, 0xB38C, 0xB38C },
+{ 0xB38D, 0xB38D, 0xB38D },
+{ 0xB38E, 0xB38E, 0xB38E },
+{ 0xB38F, 0xB38F, 0xB38F },
+{ 0xB390, 0xB390, 0xB390 },
+{ 0xB391, 0xB391, 0xB391 },
+{ 0xB392, 0xB392, 0xB392 },
+{ 0xB393, 0xB393, 0xB393 },
+{ 0xB394, 0xB394, 0xB394 },
+{ 0xB395, 0xB395, 0xB395 },
+{ 0xB396, 0xB396, 0xB396 },
+{ 0xB397, 0xB397, 0xB397 },
+{ 0xB398, 0xB398, 0xB398 },
+{ 0xB399, 0xB399, 0xB399 },
+{ 0xB39A, 0xB39A, 0xB39A },
+{ 0xB39B, 0xB39B, 0xB39B },
+{ 0xB39C, 0xB39C, 0xB39C },
+{ 0xB39D, 0xB39D, 0xB39D },
+{ 0xB39E, 0xB39E, 0xB39E },
+{ 0xB39F, 0xB39F, 0xB39F },
+{ 0xB3A0, 0xB3A0, 0xB3A0 },
+{ 0xB3A1, 0xB3A1, 0xB3A1 },
+{ 0xB3A2, 0xB3A2, 0xB3A2 },
+{ 0xB3A3, 0xB3A3, 0xB3A3 },
+{ 0xB3A4, 0xB3A4, 0xB3A4 },
+{ 0xB3A5, 0xB3A5, 0xB3A5 },
+{ 0xB3A6, 0xB3A6, 0xB3A6 },
+{ 0xB3A7, 0xB3A7, 0xB3A7 },
+{ 0xB3A8, 0xB3A8, 0xB3A8 },
+{ 0xB3A9, 0xB3A9, 0xB3A9 },
+{ 0xB3AA, 0xB3AA, 0xB3AA },
+{ 0xB3AB, 0xB3AB, 0xB3AB },
+{ 0xB3AC, 0xB3AC, 0xB3AC },
+{ 0xB3AD, 0xB3AD, 0xB3AD },
+{ 0xB3AE, 0xB3AE, 0xB3AE },
+{ 0xB3AF, 0xB3AF, 0xB3AF },
+{ 0xB3B0, 0xB3B0, 0xB3B0 },
+{ 0xB3B1, 0xB3B1, 0xB3B1 },
+{ 0xB3B2, 0xB3B2, 0xB3B2 },
+{ 0xB3B3, 0xB3B3, 0xB3B3 },
+{ 0xB3B4, 0xB3B4, 0xB3B4 },
+{ 0xB3B5, 0xB3B5, 0xB3B5 },
+{ 0xB3B6, 0xB3B6, 0xB3B6 },
+{ 0xB3B7, 0xB3B7, 0xB3B7 },
+{ 0xB3B8, 0xB3B8, 0xB3B8 },
+{ 0xB3B9, 0xB3B9, 0xB3B9 },
+{ 0xB3BA, 0xB3BA, 0xB3BA },
+{ 0xB3BB, 0xB3BB, 0xB3BB },
+{ 0xB3BC, 0xB3BC, 0xB3BC },
+{ 0xB3BD, 0xB3BD, 0xB3BD },
+{ 0xB3BE, 0xB3BE, 0xB3BE },
+{ 0xB3BF, 0xB3BF, 0xB3BF },
+{ 0xB3C0, 0xB3C0, 0xB3C0 },
+{ 0xB3C1, 0xB3C1, 0xB3C1 },
+{ 0xB3C2, 0xB3C2, 0xB3C2 },
+{ 0xB3C3, 0xB3C3, 0xB3C3 },
+{ 0xB3C4, 0xB3C4, 0xB3C4 },
+{ 0xB3C5, 0xB3C5, 0xB3C5 },
+{ 0xB3C6, 0xB3C6, 0xB3C6 },
+{ 0xB3C7, 0xB3C7, 0xB3C7 },
+{ 0xB3C8, 0xB3C8, 0xB3C8 },
+{ 0xB3C9, 0xB3C9, 0xB3C9 },
+{ 0xB3CA, 0xB3CA, 0xB3CA },
+{ 0xB3CB, 0xB3CB, 0xB3CB },
+{ 0xB3CC, 0xB3CC, 0xB3CC },
+{ 0xB3CD, 0xB3CD, 0xB3CD },
+{ 0xB3CE, 0xB3CE, 0xB3CE },
+{ 0xB3CF, 0xB3CF, 0xB3CF },
+{ 0xB3D0, 0xB3D0, 0xB3D0 },
+{ 0xB3D1, 0xB3D1, 0xB3D1 },
+{ 0xB3D2, 0xB3D2, 0xB3D2 },
+{ 0xB3D3, 0xB3D3, 0xB3D3 },
+{ 0xB3D4, 0xB3D4, 0xB3D4 },
+{ 0xB3D5, 0xB3D5, 0xB3D5 },
+{ 0xB3D6, 0xB3D6, 0xB3D6 },
+{ 0xB3D7, 0xB3D7, 0xB3D7 },
+{ 0xB3D8, 0xB3D8, 0xB3D8 },
+{ 0xB3D9, 0xB3D9, 0xB3D9 },
+{ 0xB3DA, 0xB3DA, 0xB3DA },
+{ 0xB3DB, 0xB3DB, 0xB3DB },
+{ 0xB3DC, 0xB3DC, 0xB3DC },
+{ 0xB3DD, 0xB3DD, 0xB3DD },
+{ 0xB3DE, 0xB3DE, 0xB3DE },
+{ 0xB3DF, 0xB3DF, 0xB3DF },
+{ 0xB3E0, 0xB3E0, 0xB3E0 },
+{ 0xB3E1, 0xB3E1, 0xB3E1 },
+{ 0xB3E2, 0xB3E2, 0xB3E2 },
+{ 0xB3E3, 0xB3E3, 0xB3E3 },
+{ 0xB3E4, 0xB3E4, 0xB3E4 },
+{ 0xB3E5, 0xB3E5, 0xB3E5 },
+{ 0xB3E6, 0xB3E6, 0xB3E6 },
+{ 0xB3E7, 0xB3E7, 0xB3E7 },
+{ 0xB3E8, 0xB3E8, 0xB3E8 },
+{ 0xB3E9, 0xB3E9, 0xB3E9 },
+{ 0xB3EA, 0xB3EA, 0xB3EA },
+{ 0xB3EB, 0xB3EB, 0xB3EB },
+{ 0xB3EC, 0xB3EC, 0xB3EC },
+{ 0xB3ED, 0xB3ED, 0xB3ED },
+{ 0xB3EE, 0xB3EE, 0xB3EE },
+{ 0xB3EF, 0xB3EF, 0xB3EF },
+{ 0xB3F0, 0xB3F0, 0xB3F0 },
+{ 0xB3F1, 0xB3F1, 0xB3F1 },
+{ 0xB3F2, 0xB3F2, 0xB3F2 },
+{ 0xB3F3, 0xB3F3, 0xB3F3 },
+{ 0xB3F4, 0xB3F4, 0xB3F4 },
+{ 0xB3F5, 0xB3F5, 0xB3F5 },
+{ 0xB3F6, 0xB3F6, 0xB3F6 },
+{ 0xB3F7, 0xB3F7, 0xB3F7 },
+{ 0xB3F8, 0xB3F8, 0xB3F8 },
+{ 0xB3F9, 0xB3F9, 0xB3F9 },
+{ 0xB3FA, 0xB3FA, 0xB3FA },
+{ 0xB3FB, 0xB3FB, 0xB3FB },
+{ 0xB3FC, 0xB3FC, 0xB3FC },
+{ 0xB3FD, 0xB3FD, 0xB3FD },
+{ 0xB3FE, 0xB3FE, 0xB3FE },
+{ 0xB3FF, 0xB3FF, 0xB3FF },
+{ 0xB400, 0xB400, 0xB400 },
+{ 0xB401, 0xB401, 0xB401 },
+{ 0xB402, 0xB402, 0xB402 },
+{ 0xB403, 0xB403, 0xB403 },
+{ 0xB404, 0xB404, 0xB404 },
+{ 0xB405, 0xB405, 0xB405 },
+{ 0xB406, 0xB406, 0xB406 },
+{ 0xB407, 0xB407, 0xB407 },
+{ 0xB408, 0xB408, 0xB408 },
+{ 0xB409, 0xB409, 0xB409 },
+{ 0xB40A, 0xB40A, 0xB40A },
+{ 0xB40B, 0xB40B, 0xB40B },
+{ 0xB40C, 0xB40C, 0xB40C },
+{ 0xB40D, 0xB40D, 0xB40D },
+{ 0xB40E, 0xB40E, 0xB40E },
+{ 0xB40F, 0xB40F, 0xB40F },
+{ 0xB410, 0xB410, 0xB410 },
+{ 0xB411, 0xB411, 0xB411 },
+{ 0xB412, 0xB412, 0xB412 },
+{ 0xB413, 0xB413, 0xB413 },
+{ 0xB414, 0xB414, 0xB414 },
+{ 0xB415, 0xB415, 0xB415 },
+{ 0xB416, 0xB416, 0xB416 },
+{ 0xB417, 0xB417, 0xB417 },
+{ 0xB418, 0xB418, 0xB418 },
+{ 0xB419, 0xB419, 0xB419 },
+{ 0xB41A, 0xB41A, 0xB41A },
+{ 0xB41B, 0xB41B, 0xB41B },
+{ 0xB41C, 0xB41C, 0xB41C },
+{ 0xB41D, 0xB41D, 0xB41D },
+{ 0xB41E, 0xB41E, 0xB41E },
+{ 0xB41F, 0xB41F, 0xB41F },
+{ 0xB420, 0xB420, 0xB420 },
+{ 0xB421, 0xB421, 0xB421 },
+{ 0xB422, 0xB422, 0xB422 },
+{ 0xB423, 0xB423, 0xB423 },
+{ 0xB424, 0xB424, 0xB424 },
+{ 0xB425, 0xB425, 0xB425 },
+{ 0xB426, 0xB426, 0xB426 },
+{ 0xB427, 0xB427, 0xB427 },
+{ 0xB428, 0xB428, 0xB428 },
+{ 0xB429, 0xB429, 0xB429 },
+{ 0xB42A, 0xB42A, 0xB42A },
+{ 0xB42B, 0xB42B, 0xB42B },
+{ 0xB42C, 0xB42C, 0xB42C },
+{ 0xB42D, 0xB42D, 0xB42D },
+{ 0xB42E, 0xB42E, 0xB42E },
+{ 0xB42F, 0xB42F, 0xB42F },
+{ 0xB430, 0xB430, 0xB430 },
+{ 0xB431, 0xB431, 0xB431 },
+{ 0xB432, 0xB432, 0xB432 },
+{ 0xB433, 0xB433, 0xB433 },
+{ 0xB434, 0xB434, 0xB434 },
+{ 0xB435, 0xB435, 0xB435 },
+{ 0xB436, 0xB436, 0xB436 },
+{ 0xB437, 0xB437, 0xB437 },
+{ 0xB438, 0xB438, 0xB438 },
+{ 0xB439, 0xB439, 0xB439 },
+{ 0xB43A, 0xB43A, 0xB43A },
+{ 0xB43B, 0xB43B, 0xB43B },
+{ 0xB43C, 0xB43C, 0xB43C },
+{ 0xB43D, 0xB43D, 0xB43D },
+{ 0xB43E, 0xB43E, 0xB43E },
+{ 0xB43F, 0xB43F, 0xB43F },
+{ 0xB440, 0xB440, 0xB440 },
+{ 0xB441, 0xB441, 0xB441 },
+{ 0xB442, 0xB442, 0xB442 },
+{ 0xB443, 0xB443, 0xB443 },
+{ 0xB444, 0xB444, 0xB444 },
+{ 0xB445, 0xB445, 0xB445 },
+{ 0xB446, 0xB446, 0xB446 },
+{ 0xB447, 0xB447, 0xB447 },
+{ 0xB448, 0xB448, 0xB448 },
+{ 0xB449, 0xB449, 0xB449 },
+{ 0xB44A, 0xB44A, 0xB44A },
+{ 0xB44B, 0xB44B, 0xB44B },
+{ 0xB44C, 0xB44C, 0xB44C },
+{ 0xB44D, 0xB44D, 0xB44D },
+{ 0xB44E, 0xB44E, 0xB44E },
+{ 0xB44F, 0xB44F, 0xB44F },
+{ 0xB450, 0xB450, 0xB450 },
+{ 0xB451, 0xB451, 0xB451 },
+{ 0xB452, 0xB452, 0xB452 },
+{ 0xB453, 0xB453, 0xB453 },
+{ 0xB454, 0xB454, 0xB454 },
+{ 0xB455, 0xB455, 0xB455 },
+{ 0xB456, 0xB456, 0xB456 },
+{ 0xB457, 0xB457, 0xB457 },
+{ 0xB458, 0xB458, 0xB458 },
+{ 0xB459, 0xB459, 0xB459 },
+{ 0xB45A, 0xB45A, 0xB45A },
+{ 0xB45B, 0xB45B, 0xB45B },
+{ 0xB45C, 0xB45C, 0xB45C },
+{ 0xB45D, 0xB45D, 0xB45D },
+{ 0xB45E, 0xB45E, 0xB45E },
+{ 0xB45F, 0xB45F, 0xB45F },
+{ 0xB460, 0xB460, 0xB460 },
+{ 0xB461, 0xB461, 0xB461 },
+{ 0xB462, 0xB462, 0xB462 },
+{ 0xB463, 0xB463, 0xB463 },
+{ 0xB464, 0xB464, 0xB464 },
+{ 0xB465, 0xB465, 0xB465 },
+{ 0xB466, 0xB466, 0xB466 },
+{ 0xB467, 0xB467, 0xB467 },
+{ 0xB468, 0xB468, 0xB468 },
+{ 0xB469, 0xB469, 0xB469 },
+{ 0xB46A, 0xB46A, 0xB46A },
+{ 0xB46B, 0xB46B, 0xB46B },
+{ 0xB46C, 0xB46C, 0xB46C },
+{ 0xB46D, 0xB46D, 0xB46D },
+{ 0xB46E, 0xB46E, 0xB46E },
+{ 0xB46F, 0xB46F, 0xB46F },
+{ 0xB470, 0xB470, 0xB470 },
+{ 0xB471, 0xB471, 0xB471 },
+{ 0xB472, 0xB472, 0xB472 },
+{ 0xB473, 0xB473, 0xB473 },
+{ 0xB474, 0xB474, 0xB474 },
+{ 0xB475, 0xB475, 0xB475 },
+{ 0xB476, 0xB476, 0xB476 },
+{ 0xB477, 0xB477, 0xB477 },
+{ 0xB478, 0xB478, 0xB478 },
+{ 0xB479, 0xB479, 0xB479 },
+{ 0xB47A, 0xB47A, 0xB47A },
+{ 0xB47B, 0xB47B, 0xB47B },
+{ 0xB47C, 0xB47C, 0xB47C },
+{ 0xB47D, 0xB47D, 0xB47D },
+{ 0xB47E, 0xB47E, 0xB47E },
+{ 0xB47F, 0xB47F, 0xB47F },
+{ 0xB480, 0xB480, 0xB480 },
+{ 0xB481, 0xB481, 0xB481 },
+{ 0xB482, 0xB482, 0xB482 },
+{ 0xB483, 0xB483, 0xB483 },
+{ 0xB484, 0xB484, 0xB484 },
+{ 0xB485, 0xB485, 0xB485 },
+{ 0xB486, 0xB486, 0xB486 },
+{ 0xB487, 0xB487, 0xB487 },
+{ 0xB488, 0xB488, 0xB488 },
+{ 0xB489, 0xB489, 0xB489 },
+{ 0xB48A, 0xB48A, 0xB48A },
+{ 0xB48B, 0xB48B, 0xB48B },
+{ 0xB48C, 0xB48C, 0xB48C },
+{ 0xB48D, 0xB48D, 0xB48D },
+{ 0xB48E, 0xB48E, 0xB48E },
+{ 0xB48F, 0xB48F, 0xB48F },
+{ 0xB490, 0xB490, 0xB490 },
+{ 0xB491, 0xB491, 0xB491 },
+{ 0xB492, 0xB492, 0xB492 },
+{ 0xB493, 0xB493, 0xB493 },
+{ 0xB494, 0xB494, 0xB494 },
+{ 0xB495, 0xB495, 0xB495 },
+{ 0xB496, 0xB496, 0xB496 },
+{ 0xB497, 0xB497, 0xB497 },
+{ 0xB498, 0xB498, 0xB498 },
+{ 0xB499, 0xB499, 0xB499 },
+{ 0xB49A, 0xB49A, 0xB49A },
+{ 0xB49B, 0xB49B, 0xB49B },
+{ 0xB49C, 0xB49C, 0xB49C },
+{ 0xB49D, 0xB49D, 0xB49D },
+{ 0xB49E, 0xB49E, 0xB49E },
+{ 0xB49F, 0xB49F, 0xB49F },
+{ 0xB4A0, 0xB4A0, 0xB4A0 },
+{ 0xB4A1, 0xB4A1, 0xB4A1 },
+{ 0xB4A2, 0xB4A2, 0xB4A2 },
+{ 0xB4A3, 0xB4A3, 0xB4A3 },
+{ 0xB4A4, 0xB4A4, 0xB4A4 },
+{ 0xB4A5, 0xB4A5, 0xB4A5 },
+{ 0xB4A6, 0xB4A6, 0xB4A6 },
+{ 0xB4A7, 0xB4A7, 0xB4A7 },
+{ 0xB4A8, 0xB4A8, 0xB4A8 },
+{ 0xB4A9, 0xB4A9, 0xB4A9 },
+{ 0xB4AA, 0xB4AA, 0xB4AA },
+{ 0xB4AB, 0xB4AB, 0xB4AB },
+{ 0xB4AC, 0xB4AC, 0xB4AC },
+{ 0xB4AD, 0xB4AD, 0xB4AD },
+{ 0xB4AE, 0xB4AE, 0xB4AE },
+{ 0xB4AF, 0xB4AF, 0xB4AF },
+{ 0xB4B0, 0xB4B0, 0xB4B0 },
+{ 0xB4B1, 0xB4B1, 0xB4B1 },
+{ 0xB4B2, 0xB4B2, 0xB4B2 },
+{ 0xB4B3, 0xB4B3, 0xB4B3 },
+{ 0xB4B4, 0xB4B4, 0xB4B4 },
+{ 0xB4B5, 0xB4B5, 0xB4B5 },
+{ 0xB4B6, 0xB4B6, 0xB4B6 },
+{ 0xB4B7, 0xB4B7, 0xB4B7 },
+{ 0xB4B8, 0xB4B8, 0xB4B8 },
+{ 0xB4B9, 0xB4B9, 0xB4B9 },
+{ 0xB4BA, 0xB4BA, 0xB4BA },
+{ 0xB4BB, 0xB4BB, 0xB4BB },
+{ 0xB4BC, 0xB4BC, 0xB4BC },
+{ 0xB4BD, 0xB4BD, 0xB4BD },
+{ 0xB4BE, 0xB4BE, 0xB4BE },
+{ 0xB4BF, 0xB4BF, 0xB4BF },
+{ 0xB4C0, 0xB4C0, 0xB4C0 },
+{ 0xB4C1, 0xB4C1, 0xB4C1 },
+{ 0xB4C2, 0xB4C2, 0xB4C2 },
+{ 0xB4C3, 0xB4C3, 0xB4C3 },
+{ 0xB4C4, 0xB4C4, 0xB4C4 },
+{ 0xB4C5, 0xB4C5, 0xB4C5 },
+{ 0xB4C6, 0xB4C6, 0xB4C6 },
+{ 0xB4C7, 0xB4C7, 0xB4C7 },
+{ 0xB4C8, 0xB4C8, 0xB4C8 },
+{ 0xB4C9, 0xB4C9, 0xB4C9 },
+{ 0xB4CA, 0xB4CA, 0xB4CA },
+{ 0xB4CB, 0xB4CB, 0xB4CB },
+{ 0xB4CC, 0xB4CC, 0xB4CC },
+{ 0xB4CD, 0xB4CD, 0xB4CD },
+{ 0xB4CE, 0xB4CE, 0xB4CE },
+{ 0xB4CF, 0xB4CF, 0xB4CF },
+{ 0xB4D0, 0xB4D0, 0xB4D0 },
+{ 0xB4D1, 0xB4D1, 0xB4D1 },
+{ 0xB4D2, 0xB4D2, 0xB4D2 },
+{ 0xB4D3, 0xB4D3, 0xB4D3 },
+{ 0xB4D4, 0xB4D4, 0xB4D4 },
+{ 0xB4D5, 0xB4D5, 0xB4D5 },
+{ 0xB4D6, 0xB4D6, 0xB4D6 },
+{ 0xB4D7, 0xB4D7, 0xB4D7 },
+{ 0xB4D8, 0xB4D8, 0xB4D8 },
+{ 0xB4D9, 0xB4D9, 0xB4D9 },
+{ 0xB4DA, 0xB4DA, 0xB4DA },
+{ 0xB4DB, 0xB4DB, 0xB4DB },
+{ 0xB4DC, 0xB4DC, 0xB4DC },
+{ 0xB4DD, 0xB4DD, 0xB4DD },
+{ 0xB4DE, 0xB4DE, 0xB4DE },
+{ 0xB4DF, 0xB4DF, 0xB4DF },
+{ 0xB4E0, 0xB4E0, 0xB4E0 },
+{ 0xB4E1, 0xB4E1, 0xB4E1 },
+{ 0xB4E2, 0xB4E2, 0xB4E2 },
+{ 0xB4E3, 0xB4E3, 0xB4E3 },
+{ 0xB4E4, 0xB4E4, 0xB4E4 },
+{ 0xB4E5, 0xB4E5, 0xB4E5 },
+{ 0xB4E6, 0xB4E6, 0xB4E6 },
+{ 0xB4E7, 0xB4E7, 0xB4E7 },
+{ 0xB4E8, 0xB4E8, 0xB4E8 },
+{ 0xB4E9, 0xB4E9, 0xB4E9 },
+{ 0xB4EA, 0xB4EA, 0xB4EA },
+{ 0xB4EB, 0xB4EB, 0xB4EB },
+{ 0xB4EC, 0xB4EC, 0xB4EC },
+{ 0xB4ED, 0xB4ED, 0xB4ED },
+{ 0xB4EE, 0xB4EE, 0xB4EE },
+{ 0xB4EF, 0xB4EF, 0xB4EF },
+{ 0xB4F0, 0xB4F0, 0xB4F0 },
+{ 0xB4F1, 0xB4F1, 0xB4F1 },
+{ 0xB4F2, 0xB4F2, 0xB4F2 },
+{ 0xB4F3, 0xB4F3, 0xB4F3 },
+{ 0xB4F4, 0xB4F4, 0xB4F4 },
+{ 0xB4F5, 0xB4F5, 0xB4F5 },
+{ 0xB4F6, 0xB4F6, 0xB4F6 },
+{ 0xB4F7, 0xB4F7, 0xB4F7 },
+{ 0xB4F8, 0xB4F8, 0xB4F8 },
+{ 0xB4F9, 0xB4F9, 0xB4F9 },
+{ 0xB4FA, 0xB4FA, 0xB4FA },
+{ 0xB4FB, 0xB4FB, 0xB4FB },
+{ 0xB4FC, 0xB4FC, 0xB4FC },
+{ 0xB4FD, 0xB4FD, 0xB4FD },
+{ 0xB4FE, 0xB4FE, 0xB4FE },
+{ 0xB4FF, 0xB4FF, 0xB4FF },
+{ 0xB500, 0xB500, 0xB500 },
+{ 0xB501, 0xB501, 0xB501 },
+{ 0xB502, 0xB502, 0xB502 },
+{ 0xB503, 0xB503, 0xB503 },
+{ 0xB504, 0xB504, 0xB504 },
+{ 0xB505, 0xB505, 0xB505 },
+{ 0xB506, 0xB506, 0xB506 },
+{ 0xB507, 0xB507, 0xB507 },
+{ 0xB508, 0xB508, 0xB508 },
+{ 0xB509, 0xB509, 0xB509 },
+{ 0xB50A, 0xB50A, 0xB50A },
+{ 0xB50B, 0xB50B, 0xB50B },
+{ 0xB50C, 0xB50C, 0xB50C },
+{ 0xB50D, 0xB50D, 0xB50D },
+{ 0xB50E, 0xB50E, 0xB50E },
+{ 0xB50F, 0xB50F, 0xB50F },
+{ 0xB510, 0xB510, 0xB510 },
+{ 0xB511, 0xB511, 0xB511 },
+{ 0xB512, 0xB512, 0xB512 },
+{ 0xB513, 0xB513, 0xB513 },
+{ 0xB514, 0xB514, 0xB514 },
+{ 0xB515, 0xB515, 0xB515 },
+{ 0xB516, 0xB516, 0xB516 },
+{ 0xB517, 0xB517, 0xB517 },
+{ 0xB518, 0xB518, 0xB518 },
+{ 0xB519, 0xB519, 0xB519 },
+{ 0xB51A, 0xB51A, 0xB51A },
+{ 0xB51B, 0xB51B, 0xB51B },
+{ 0xB51C, 0xB51C, 0xB51C },
+{ 0xB51D, 0xB51D, 0xB51D },
+{ 0xB51E, 0xB51E, 0xB51E },
+{ 0xB51F, 0xB51F, 0xB51F },
+{ 0xB520, 0xB520, 0xB520 },
+{ 0xB521, 0xB521, 0xB521 },
+{ 0xB522, 0xB522, 0xB522 },
+{ 0xB523, 0xB523, 0xB523 },
+{ 0xB524, 0xB524, 0xB524 },
+{ 0xB525, 0xB525, 0xB525 },
+{ 0xB526, 0xB526, 0xB526 },
+{ 0xB527, 0xB527, 0xB527 },
+{ 0xB528, 0xB528, 0xB528 },
+{ 0xB529, 0xB529, 0xB529 },
+{ 0xB52A, 0xB52A, 0xB52A },
+{ 0xB52B, 0xB52B, 0xB52B },
+{ 0xB52C, 0xB52C, 0xB52C },
+{ 0xB52D, 0xB52D, 0xB52D },
+{ 0xB52E, 0xB52E, 0xB52E },
+{ 0xB52F, 0xB52F, 0xB52F },
+{ 0xB530, 0xB530, 0xB530 },
+{ 0xB531, 0xB531, 0xB531 },
+{ 0xB532, 0xB532, 0xB532 },
+{ 0xB533, 0xB533, 0xB533 },
+{ 0xB534, 0xB534, 0xB534 },
+{ 0xB535, 0xB535, 0xB535 },
+{ 0xB536, 0xB536, 0xB536 },
+{ 0xB537, 0xB537, 0xB537 },
+{ 0xB538, 0xB538, 0xB538 },
+{ 0xB539, 0xB539, 0xB539 },
+{ 0xB53A, 0xB53A, 0xB53A },
+{ 0xB53B, 0xB53B, 0xB53B },
+{ 0xB53C, 0xB53C, 0xB53C },
+{ 0xB53D, 0xB53D, 0xB53D },
+{ 0xB53E, 0xB53E, 0xB53E },
+{ 0xB53F, 0xB53F, 0xB53F },
+{ 0xB540, 0xB540, 0xB540 },
+{ 0xB541, 0xB541, 0xB541 },
+{ 0xB542, 0xB542, 0xB542 },
+{ 0xB543, 0xB543, 0xB543 },
+{ 0xB544, 0xB544, 0xB544 },
+{ 0xB545, 0xB545, 0xB545 },
+{ 0xB546, 0xB546, 0xB546 },
+{ 0xB547, 0xB547, 0xB547 },
+{ 0xB548, 0xB548, 0xB548 },
+{ 0xB549, 0xB549, 0xB549 },
+{ 0xB54A, 0xB54A, 0xB54A },
+{ 0xB54B, 0xB54B, 0xB54B },
+{ 0xB54C, 0xB54C, 0xB54C },
+{ 0xB54D, 0xB54D, 0xB54D },
+{ 0xB54E, 0xB54E, 0xB54E },
+{ 0xB54F, 0xB54F, 0xB54F },
+{ 0xB550, 0xB550, 0xB550 },
+{ 0xB551, 0xB551, 0xB551 },
+{ 0xB552, 0xB552, 0xB552 },
+{ 0xB553, 0xB553, 0xB553 },
+{ 0xB554, 0xB554, 0xB554 },
+{ 0xB555, 0xB555, 0xB555 },
+{ 0xB556, 0xB556, 0xB556 },
+{ 0xB557, 0xB557, 0xB557 },
+{ 0xB558, 0xB558, 0xB558 },
+{ 0xB559, 0xB559, 0xB559 },
+{ 0xB55A, 0xB55A, 0xB55A },
+{ 0xB55B, 0xB55B, 0xB55B },
+{ 0xB55C, 0xB55C, 0xB55C },
+{ 0xB55D, 0xB55D, 0xB55D },
+{ 0xB55E, 0xB55E, 0xB55E },
+{ 0xB55F, 0xB55F, 0xB55F },
+{ 0xB560, 0xB560, 0xB560 },
+{ 0xB561, 0xB561, 0xB561 },
+{ 0xB562, 0xB562, 0xB562 },
+{ 0xB563, 0xB563, 0xB563 },
+{ 0xB564, 0xB564, 0xB564 },
+{ 0xB565, 0xB565, 0xB565 },
+{ 0xB566, 0xB566, 0xB566 },
+{ 0xB567, 0xB567, 0xB567 },
+{ 0xB568, 0xB568, 0xB568 },
+{ 0xB569, 0xB569, 0xB569 },
+{ 0xB56A, 0xB56A, 0xB56A },
+{ 0xB56B, 0xB56B, 0xB56B },
+{ 0xB56C, 0xB56C, 0xB56C },
+{ 0xB56D, 0xB56D, 0xB56D },
+{ 0xB56E, 0xB56E, 0xB56E },
+{ 0xB56F, 0xB56F, 0xB56F },
+{ 0xB570, 0xB570, 0xB570 },
+{ 0xB571, 0xB571, 0xB571 },
+{ 0xB572, 0xB572, 0xB572 },
+{ 0xB573, 0xB573, 0xB573 },
+{ 0xB574, 0xB574, 0xB574 },
+{ 0xB575, 0xB575, 0xB575 },
+{ 0xB576, 0xB576, 0xB576 },
+{ 0xB577, 0xB577, 0xB577 },
+{ 0xB578, 0xB578, 0xB578 },
+{ 0xB579, 0xB579, 0xB579 },
+{ 0xB57A, 0xB57A, 0xB57A },
+{ 0xB57B, 0xB57B, 0xB57B },
+{ 0xB57C, 0xB57C, 0xB57C },
+{ 0xB57D, 0xB57D, 0xB57D },
+{ 0xB57E, 0xB57E, 0xB57E },
+{ 0xB57F, 0xB57F, 0xB57F },
+{ 0xB580, 0xB580, 0xB580 },
+{ 0xB581, 0xB581, 0xB581 },
+{ 0xB582, 0xB582, 0xB582 },
+{ 0xB583, 0xB583, 0xB583 },
+{ 0xB584, 0xB584, 0xB584 },
+{ 0xB585, 0xB585, 0xB585 },
+{ 0xB586, 0xB586, 0xB586 },
+{ 0xB587, 0xB587, 0xB587 },
+{ 0xB588, 0xB588, 0xB588 },
+{ 0xB589, 0xB589, 0xB589 },
+{ 0xB58A, 0xB58A, 0xB58A },
+{ 0xB58B, 0xB58B, 0xB58B },
+{ 0xB58C, 0xB58C, 0xB58C },
+{ 0xB58D, 0xB58D, 0xB58D },
+{ 0xB58E, 0xB58E, 0xB58E },
+{ 0xB58F, 0xB58F, 0xB58F },
+{ 0xB590, 0xB590, 0xB590 },
+{ 0xB591, 0xB591, 0xB591 },
+{ 0xB592, 0xB592, 0xB592 },
+{ 0xB593, 0xB593, 0xB593 },
+{ 0xB594, 0xB594, 0xB594 },
+{ 0xB595, 0xB595, 0xB595 },
+{ 0xB596, 0xB596, 0xB596 },
+{ 0xB597, 0xB597, 0xB597 },
+{ 0xB598, 0xB598, 0xB598 },
+{ 0xB599, 0xB599, 0xB599 },
+{ 0xB59A, 0xB59A, 0xB59A },
+{ 0xB59B, 0xB59B, 0xB59B },
+{ 0xB59C, 0xB59C, 0xB59C },
+{ 0xB59D, 0xB59D, 0xB59D },
+{ 0xB59E, 0xB59E, 0xB59E },
+{ 0xB59F, 0xB59F, 0xB59F },
+{ 0xB5A0, 0xB5A0, 0xB5A0 },
+{ 0xB5A1, 0xB5A1, 0xB5A1 },
+{ 0xB5A2, 0xB5A2, 0xB5A2 },
+{ 0xB5A3, 0xB5A3, 0xB5A3 },
+{ 0xB5A4, 0xB5A4, 0xB5A4 },
+{ 0xB5A5, 0xB5A5, 0xB5A5 },
+{ 0xB5A6, 0xB5A6, 0xB5A6 },
+{ 0xB5A7, 0xB5A7, 0xB5A7 },
+{ 0xB5A8, 0xB5A8, 0xB5A8 },
+{ 0xB5A9, 0xB5A9, 0xB5A9 },
+{ 0xB5AA, 0xB5AA, 0xB5AA },
+{ 0xB5AB, 0xB5AB, 0xB5AB },
+{ 0xB5AC, 0xB5AC, 0xB5AC },
+{ 0xB5AD, 0xB5AD, 0xB5AD },
+{ 0xB5AE, 0xB5AE, 0xB5AE },
+{ 0xB5AF, 0xB5AF, 0xB5AF },
+{ 0xB5B0, 0xB5B0, 0xB5B0 },
+{ 0xB5B1, 0xB5B1, 0xB5B1 },
+{ 0xB5B2, 0xB5B2, 0xB5B2 },
+{ 0xB5B3, 0xB5B3, 0xB5B3 },
+{ 0xB5B4, 0xB5B4, 0xB5B4 },
+{ 0xB5B5, 0xB5B5, 0xB5B5 },
+{ 0xB5B6, 0xB5B6, 0xB5B6 },
+{ 0xB5B7, 0xB5B7, 0xB5B7 },
+{ 0xB5B8, 0xB5B8, 0xB5B8 },
+{ 0xB5B9, 0xB5B9, 0xB5B9 },
+{ 0xB5BA, 0xB5BA, 0xB5BA },
+{ 0xB5BB, 0xB5BB, 0xB5BB },
+{ 0xB5BC, 0xB5BC, 0xB5BC },
+{ 0xB5BD, 0xB5BD, 0xB5BD },
+{ 0xB5BE, 0xB5BE, 0xB5BE },
+{ 0xB5BF, 0xB5BF, 0xB5BF },
+{ 0xB5C0, 0xB5C0, 0xB5C0 },
+{ 0xB5C1, 0xB5C1, 0xB5C1 },
+{ 0xB5C2, 0xB5C2, 0xB5C2 },
+{ 0xB5C3, 0xB5C3, 0xB5C3 },
+{ 0xB5C4, 0xB5C4, 0xB5C4 },
+{ 0xB5C5, 0xB5C5, 0xB5C5 },
+{ 0xB5C6, 0xB5C6, 0xB5C6 },
+{ 0xB5C7, 0xB5C7, 0xB5C7 },
+{ 0xB5C8, 0xB5C8, 0xB5C8 },
+{ 0xB5C9, 0xB5C9, 0xB5C9 },
+{ 0xB5CA, 0xB5CA, 0xB5CA },
+{ 0xB5CB, 0xB5CB, 0xB5CB },
+{ 0xB5CC, 0xB5CC, 0xB5CC },
+{ 0xB5CD, 0xB5CD, 0xB5CD },
+{ 0xB5CE, 0xB5CE, 0xB5CE },
+{ 0xB5CF, 0xB5CF, 0xB5CF },
+{ 0xB5D0, 0xB5D0, 0xB5D0 },
+{ 0xB5D1, 0xB5D1, 0xB5D1 },
+{ 0xB5D2, 0xB5D2, 0xB5D2 },
+{ 0xB5D3, 0xB5D3, 0xB5D3 },
+{ 0xB5D4, 0xB5D4, 0xB5D4 },
+{ 0xB5D5, 0xB5D5, 0xB5D5 },
+{ 0xB5D6, 0xB5D6, 0xB5D6 },
+{ 0xB5D7, 0xB5D7, 0xB5D7 },
+{ 0xB5D8, 0xB5D8, 0xB5D8 },
+{ 0xB5D9, 0xB5D9, 0xB5D9 },
+{ 0xB5DA, 0xB5DA, 0xB5DA },
+{ 0xB5DB, 0xB5DB, 0xB5DB },
+{ 0xB5DC, 0xB5DC, 0xB5DC },
+{ 0xB5DD, 0xB5DD, 0xB5DD },
+{ 0xB5DE, 0xB5DE, 0xB5DE },
+{ 0xB5DF, 0xB5DF, 0xB5DF },
+{ 0xB5E0, 0xB5E0, 0xB5E0 },
+{ 0xB5E1, 0xB5E1, 0xB5E1 },
+{ 0xB5E2, 0xB5E2, 0xB5E2 },
+{ 0xB5E3, 0xB5E3, 0xB5E3 },
+{ 0xB5E4, 0xB5E4, 0xB5E4 },
+{ 0xB5E5, 0xB5E5, 0xB5E5 },
+{ 0xB5E6, 0xB5E6, 0xB5E6 },
+{ 0xB5E7, 0xB5E7, 0xB5E7 },
+{ 0xB5E8, 0xB5E8, 0xB5E8 },
+{ 0xB5E9, 0xB5E9, 0xB5E9 },
+{ 0xB5EA, 0xB5EA, 0xB5EA },
+{ 0xB5EB, 0xB5EB, 0xB5EB },
+{ 0xB5EC, 0xB5EC, 0xB5EC },
+{ 0xB5ED, 0xB5ED, 0xB5ED },
+{ 0xB5EE, 0xB5EE, 0xB5EE },
+{ 0xB5EF, 0xB5EF, 0xB5EF },
+{ 0xB5F0, 0xB5F0, 0xB5F0 },
+{ 0xB5F1, 0xB5F1, 0xB5F1 },
+{ 0xB5F2, 0xB5F2, 0xB5F2 },
+{ 0xB5F3, 0xB5F3, 0xB5F3 },
+{ 0xB5F4, 0xB5F4, 0xB5F4 },
+{ 0xB5F5, 0xB5F5, 0xB5F5 },
+{ 0xB5F6, 0xB5F6, 0xB5F6 },
+{ 0xB5F7, 0xB5F7, 0xB5F7 },
+{ 0xB5F8, 0xB5F8, 0xB5F8 },
+{ 0xB5F9, 0xB5F9, 0xB5F9 },
+{ 0xB5FA, 0xB5FA, 0xB5FA },
+{ 0xB5FB, 0xB5FB, 0xB5FB },
+{ 0xB5FC, 0xB5FC, 0xB5FC },
+{ 0xB5FD, 0xB5FD, 0xB5FD },
+{ 0xB5FE, 0xB5FE, 0xB5FE },
+{ 0xB5FF, 0xB5FF, 0xB5FF },
+{ 0xB600, 0xB600, 0xB600 },
+{ 0xB601, 0xB601, 0xB601 },
+{ 0xB602, 0xB602, 0xB602 },
+{ 0xB603, 0xB603, 0xB603 },
+{ 0xB604, 0xB604, 0xB604 },
+{ 0xB605, 0xB605, 0xB605 },
+{ 0xB606, 0xB606, 0xB606 },
+{ 0xB607, 0xB607, 0xB607 },
+{ 0xB608, 0xB608, 0xB608 },
+{ 0xB609, 0xB609, 0xB609 },
+{ 0xB60A, 0xB60A, 0xB60A },
+{ 0xB60B, 0xB60B, 0xB60B },
+{ 0xB60C, 0xB60C, 0xB60C },
+{ 0xB60D, 0xB60D, 0xB60D },
+{ 0xB60E, 0xB60E, 0xB60E },
+{ 0xB60F, 0xB60F, 0xB60F },
+{ 0xB610, 0xB610, 0xB610 },
+{ 0xB611, 0xB611, 0xB611 },
+{ 0xB612, 0xB612, 0xB612 },
+{ 0xB613, 0xB613, 0xB613 },
+{ 0xB614, 0xB614, 0xB614 },
+{ 0xB615, 0xB615, 0xB615 },
+{ 0xB616, 0xB616, 0xB616 },
+{ 0xB617, 0xB617, 0xB617 },
+{ 0xB618, 0xB618, 0xB618 },
+{ 0xB619, 0xB619, 0xB619 },
+{ 0xB61A, 0xB61A, 0xB61A },
+{ 0xB61B, 0xB61B, 0xB61B },
+{ 0xB61C, 0xB61C, 0xB61C },
+{ 0xB61D, 0xB61D, 0xB61D },
+{ 0xB61E, 0xB61E, 0xB61E },
+{ 0xB61F, 0xB61F, 0xB61F },
+{ 0xB620, 0xB620, 0xB620 },
+{ 0xB621, 0xB621, 0xB621 },
+{ 0xB622, 0xB622, 0xB622 },
+{ 0xB623, 0xB623, 0xB623 },
+{ 0xB624, 0xB624, 0xB624 },
+{ 0xB625, 0xB625, 0xB625 },
+{ 0xB626, 0xB626, 0xB626 },
+{ 0xB627, 0xB627, 0xB627 },
+{ 0xB628, 0xB628, 0xB628 },
+{ 0xB629, 0xB629, 0xB629 },
+{ 0xB62A, 0xB62A, 0xB62A },
+{ 0xB62B, 0xB62B, 0xB62B },
+{ 0xB62C, 0xB62C, 0xB62C },
+{ 0xB62D, 0xB62D, 0xB62D },
+{ 0xB62E, 0xB62E, 0xB62E },
+{ 0xB62F, 0xB62F, 0xB62F },
+{ 0xB630, 0xB630, 0xB630 },
+{ 0xB631, 0xB631, 0xB631 },
+{ 0xB632, 0xB632, 0xB632 },
+{ 0xB633, 0xB633, 0xB633 },
+{ 0xB634, 0xB634, 0xB634 },
+{ 0xB635, 0xB635, 0xB635 },
+{ 0xB636, 0xB636, 0xB636 },
+{ 0xB637, 0xB637, 0xB637 },
+{ 0xB638, 0xB638, 0xB638 },
+{ 0xB639, 0xB639, 0xB639 },
+{ 0xB63A, 0xB63A, 0xB63A },
+{ 0xB63B, 0xB63B, 0xB63B },
+{ 0xB63C, 0xB63C, 0xB63C },
+{ 0xB63D, 0xB63D, 0xB63D },
+{ 0xB63E, 0xB63E, 0xB63E },
+{ 0xB63F, 0xB63F, 0xB63F },
+{ 0xB640, 0xB640, 0xB640 },
+{ 0xB641, 0xB641, 0xB641 },
+{ 0xB642, 0xB642, 0xB642 },
+{ 0xB643, 0xB643, 0xB643 },
+{ 0xB644, 0xB644, 0xB644 },
+{ 0xB645, 0xB645, 0xB645 },
+{ 0xB646, 0xB646, 0xB646 },
+{ 0xB647, 0xB647, 0xB647 },
+{ 0xB648, 0xB648, 0xB648 },
+{ 0xB649, 0xB649, 0xB649 },
+{ 0xB64A, 0xB64A, 0xB64A },
+{ 0xB64B, 0xB64B, 0xB64B },
+{ 0xB64C, 0xB64C, 0xB64C },
+{ 0xB64D, 0xB64D, 0xB64D },
+{ 0xB64E, 0xB64E, 0xB64E },
+{ 0xB64F, 0xB64F, 0xB64F },
+{ 0xB650, 0xB650, 0xB650 },
+{ 0xB651, 0xB651, 0xB651 },
+{ 0xB652, 0xB652, 0xB652 },
+{ 0xB653, 0xB653, 0xB653 },
+{ 0xB654, 0xB654, 0xB654 },
+{ 0xB655, 0xB655, 0xB655 },
+{ 0xB656, 0xB656, 0xB656 },
+{ 0xB657, 0xB657, 0xB657 },
+{ 0xB658, 0xB658, 0xB658 },
+{ 0xB659, 0xB659, 0xB659 },
+{ 0xB65A, 0xB65A, 0xB65A },
+{ 0xB65B, 0xB65B, 0xB65B },
+{ 0xB65C, 0xB65C, 0xB65C },
+{ 0xB65D, 0xB65D, 0xB65D },
+{ 0xB65E, 0xB65E, 0xB65E },
+{ 0xB65F, 0xB65F, 0xB65F },
+{ 0xB660, 0xB660, 0xB660 },
+{ 0xB661, 0xB661, 0xB661 },
+{ 0xB662, 0xB662, 0xB662 },
+{ 0xB663, 0xB663, 0xB663 },
+{ 0xB664, 0xB664, 0xB664 },
+{ 0xB665, 0xB665, 0xB665 },
+{ 0xB666, 0xB666, 0xB666 },
+{ 0xB667, 0xB667, 0xB667 },
+{ 0xB668, 0xB668, 0xB668 },
+{ 0xB669, 0xB669, 0xB669 },
+{ 0xB66A, 0xB66A, 0xB66A },
+{ 0xB66B, 0xB66B, 0xB66B },
+{ 0xB66C, 0xB66C, 0xB66C },
+{ 0xB66D, 0xB66D, 0xB66D },
+{ 0xB66E, 0xB66E, 0xB66E },
+{ 0xB66F, 0xB66F, 0xB66F },
+{ 0xB670, 0xB670, 0xB670 },
+{ 0xB671, 0xB671, 0xB671 },
+{ 0xB672, 0xB672, 0xB672 },
+{ 0xB673, 0xB673, 0xB673 },
+{ 0xB674, 0xB674, 0xB674 },
+{ 0xB675, 0xB675, 0xB675 },
+{ 0xB676, 0xB676, 0xB676 },
+{ 0xB677, 0xB677, 0xB677 },
+{ 0xB678, 0xB678, 0xB678 },
+{ 0xB679, 0xB679, 0xB679 },
+{ 0xB67A, 0xB67A, 0xB67A },
+{ 0xB67B, 0xB67B, 0xB67B },
+{ 0xB67C, 0xB67C, 0xB67C },
+{ 0xB67D, 0xB67D, 0xB67D },
+{ 0xB67E, 0xB67E, 0xB67E },
+{ 0xB67F, 0xB67F, 0xB67F },
+{ 0xB680, 0xB680, 0xB680 },
+{ 0xB681, 0xB681, 0xB681 },
+{ 0xB682, 0xB682, 0xB682 },
+{ 0xB683, 0xB683, 0xB683 },
+{ 0xB684, 0xB684, 0xB684 },
+{ 0xB685, 0xB685, 0xB685 },
+{ 0xB686, 0xB686, 0xB686 },
+{ 0xB687, 0xB687, 0xB687 },
+{ 0xB688, 0xB688, 0xB688 },
+{ 0xB689, 0xB689, 0xB689 },
+{ 0xB68A, 0xB68A, 0xB68A },
+{ 0xB68B, 0xB68B, 0xB68B },
+{ 0xB68C, 0xB68C, 0xB68C },
+{ 0xB68D, 0xB68D, 0xB68D },
+{ 0xB68E, 0xB68E, 0xB68E },
+{ 0xB68F, 0xB68F, 0xB68F },
+{ 0xB690, 0xB690, 0xB690 },
+{ 0xB691, 0xB691, 0xB691 },
+{ 0xB692, 0xB692, 0xB692 },
+{ 0xB693, 0xB693, 0xB693 },
+{ 0xB694, 0xB694, 0xB694 },
+{ 0xB695, 0xB695, 0xB695 },
+{ 0xB696, 0xB696, 0xB696 },
+{ 0xB697, 0xB697, 0xB697 },
+{ 0xB698, 0xB698, 0xB698 },
+{ 0xB699, 0xB699, 0xB699 },
+{ 0xB69A, 0xB69A, 0xB69A },
+{ 0xB69B, 0xB69B, 0xB69B },
+{ 0xB69C, 0xB69C, 0xB69C },
+{ 0xB69D, 0xB69D, 0xB69D },
+{ 0xB69E, 0xB69E, 0xB69E },
+{ 0xB69F, 0xB69F, 0xB69F },
+{ 0xB6A0, 0xB6A0, 0xB6A0 },
+{ 0xB6A1, 0xB6A1, 0xB6A1 },
+{ 0xB6A2, 0xB6A2, 0xB6A2 },
+{ 0xB6A3, 0xB6A3, 0xB6A3 },
+{ 0xB6A4, 0xB6A4, 0xB6A4 },
+{ 0xB6A5, 0xB6A5, 0xB6A5 },
+{ 0xB6A6, 0xB6A6, 0xB6A6 },
+{ 0xB6A7, 0xB6A7, 0xB6A7 },
+{ 0xB6A8, 0xB6A8, 0xB6A8 },
+{ 0xB6A9, 0xB6A9, 0xB6A9 },
+{ 0xB6AA, 0xB6AA, 0xB6AA },
+{ 0xB6AB, 0xB6AB, 0xB6AB },
+{ 0xB6AC, 0xB6AC, 0xB6AC },
+{ 0xB6AD, 0xB6AD, 0xB6AD },
+{ 0xB6AE, 0xB6AE, 0xB6AE },
+{ 0xB6AF, 0xB6AF, 0xB6AF },
+{ 0xB6B0, 0xB6B0, 0xB6B0 },
+{ 0xB6B1, 0xB6B1, 0xB6B1 },
+{ 0xB6B2, 0xB6B2, 0xB6B2 },
+{ 0xB6B3, 0xB6B3, 0xB6B3 },
+{ 0xB6B4, 0xB6B4, 0xB6B4 },
+{ 0xB6B5, 0xB6B5, 0xB6B5 },
+{ 0xB6B6, 0xB6B6, 0xB6B6 },
+{ 0xB6B7, 0xB6B7, 0xB6B7 },
+{ 0xB6B8, 0xB6B8, 0xB6B8 },
+{ 0xB6B9, 0xB6B9, 0xB6B9 },
+{ 0xB6BA, 0xB6BA, 0xB6BA },
+{ 0xB6BB, 0xB6BB, 0xB6BB },
+{ 0xB6BC, 0xB6BC, 0xB6BC },
+{ 0xB6BD, 0xB6BD, 0xB6BD },
+{ 0xB6BE, 0xB6BE, 0xB6BE },
+{ 0xB6BF, 0xB6BF, 0xB6BF },
+{ 0xB6C0, 0xB6C0, 0xB6C0 },
+{ 0xB6C1, 0xB6C1, 0xB6C1 },
+{ 0xB6C2, 0xB6C2, 0xB6C2 },
+{ 0xB6C3, 0xB6C3, 0xB6C3 },
+{ 0xB6C4, 0xB6C4, 0xB6C4 },
+{ 0xB6C5, 0xB6C5, 0xB6C5 },
+{ 0xB6C6, 0xB6C6, 0xB6C6 },
+{ 0xB6C7, 0xB6C7, 0xB6C7 },
+{ 0xB6C8, 0xB6C8, 0xB6C8 },
+{ 0xB6C9, 0xB6C9, 0xB6C9 },
+{ 0xB6CA, 0xB6CA, 0xB6CA },
+{ 0xB6CB, 0xB6CB, 0xB6CB },
+{ 0xB6CC, 0xB6CC, 0xB6CC },
+{ 0xB6CD, 0xB6CD, 0xB6CD },
+{ 0xB6CE, 0xB6CE, 0xB6CE },
+{ 0xB6CF, 0xB6CF, 0xB6CF },
+{ 0xB6D0, 0xB6D0, 0xB6D0 },
+{ 0xB6D1, 0xB6D1, 0xB6D1 },
+{ 0xB6D2, 0xB6D2, 0xB6D2 },
+{ 0xB6D3, 0xB6D3, 0xB6D3 },
+{ 0xB6D4, 0xB6D4, 0xB6D4 },
+{ 0xB6D5, 0xB6D5, 0xB6D5 },
+{ 0xB6D6, 0xB6D6, 0xB6D6 },
+{ 0xB6D7, 0xB6D7, 0xB6D7 },
+{ 0xB6D8, 0xB6D8, 0xB6D8 },
+{ 0xB6D9, 0xB6D9, 0xB6D9 },
+{ 0xB6DA, 0xB6DA, 0xB6DA },
+{ 0xB6DB, 0xB6DB, 0xB6DB },
+{ 0xB6DC, 0xB6DC, 0xB6DC },
+{ 0xB6DD, 0xB6DD, 0xB6DD },
+{ 0xB6DE, 0xB6DE, 0xB6DE },
+{ 0xB6DF, 0xB6DF, 0xB6DF },
+{ 0xB6E0, 0xB6E0, 0xB6E0 },
+{ 0xB6E1, 0xB6E1, 0xB6E1 },
+{ 0xB6E2, 0xB6E2, 0xB6E2 },
+{ 0xB6E3, 0xB6E3, 0xB6E3 },
+{ 0xB6E4, 0xB6E4, 0xB6E4 },
+{ 0xB6E5, 0xB6E5, 0xB6E5 },
+{ 0xB6E6, 0xB6E6, 0xB6E6 },
+{ 0xB6E7, 0xB6E7, 0xB6E7 },
+{ 0xB6E8, 0xB6E8, 0xB6E8 },
+{ 0xB6E9, 0xB6E9, 0xB6E9 },
+{ 0xB6EA, 0xB6EA, 0xB6EA },
+{ 0xB6EB, 0xB6EB, 0xB6EB },
+{ 0xB6EC, 0xB6EC, 0xB6EC },
+{ 0xB6ED, 0xB6ED, 0xB6ED },
+{ 0xB6EE, 0xB6EE, 0xB6EE },
+{ 0xB6EF, 0xB6EF, 0xB6EF },
+{ 0xB6F0, 0xB6F0, 0xB6F0 },
+{ 0xB6F1, 0xB6F1, 0xB6F1 },
+{ 0xB6F2, 0xB6F2, 0xB6F2 },
+{ 0xB6F3, 0xB6F3, 0xB6F3 },
+{ 0xB6F4, 0xB6F4, 0xB6F4 },
+{ 0xB6F5, 0xB6F5, 0xB6F5 },
+{ 0xB6F6, 0xB6F6, 0xB6F6 },
+{ 0xB6F7, 0xB6F7, 0xB6F7 },
+{ 0xB6F8, 0xB6F8, 0xB6F8 },
+{ 0xB6F9, 0xB6F9, 0xB6F9 },
+{ 0xB6FA, 0xB6FA, 0xB6FA },
+{ 0xB6FB, 0xB6FB, 0xB6FB },
+{ 0xB6FC, 0xB6FC, 0xB6FC },
+{ 0xB6FD, 0xB6FD, 0xB6FD },
+{ 0xB6FE, 0xB6FE, 0xB6FE },
+{ 0xB6FF, 0xB6FF, 0xB6FF },
+{ 0xB700, 0xB700, 0xB700 },
+{ 0xB701, 0xB701, 0xB701 },
+{ 0xB702, 0xB702, 0xB702 },
+{ 0xB703, 0xB703, 0xB703 },
+{ 0xB704, 0xB704, 0xB704 },
+{ 0xB705, 0xB705, 0xB705 },
+{ 0xB706, 0xB706, 0xB706 },
+{ 0xB707, 0xB707, 0xB707 },
+{ 0xB708, 0xB708, 0xB708 },
+{ 0xB709, 0xB709, 0xB709 },
+{ 0xB70A, 0xB70A, 0xB70A },
+{ 0xB70B, 0xB70B, 0xB70B },
+{ 0xB70C, 0xB70C, 0xB70C },
+{ 0xB70D, 0xB70D, 0xB70D },
+{ 0xB70E, 0xB70E, 0xB70E },
+{ 0xB70F, 0xB70F, 0xB70F },
+{ 0xB710, 0xB710, 0xB710 },
+{ 0xB711, 0xB711, 0xB711 },
+{ 0xB712, 0xB712, 0xB712 },
+{ 0xB713, 0xB713, 0xB713 },
+{ 0xB714, 0xB714, 0xB714 },
+{ 0xB715, 0xB715, 0xB715 },
+{ 0xB716, 0xB716, 0xB716 },
+{ 0xB717, 0xB717, 0xB717 },
+{ 0xB718, 0xB718, 0xB718 },
+{ 0xB719, 0xB719, 0xB719 },
+{ 0xB71A, 0xB71A, 0xB71A },
+{ 0xB71B, 0xB71B, 0xB71B },
+{ 0xB71C, 0xB71C, 0xB71C },
+{ 0xB71D, 0xB71D, 0xB71D },
+{ 0xB71E, 0xB71E, 0xB71E },
+{ 0xB71F, 0xB71F, 0xB71F },
+{ 0xB720, 0xB720, 0xB720 },
+{ 0xB721, 0xB721, 0xB721 },
+{ 0xB722, 0xB722, 0xB722 },
+{ 0xB723, 0xB723, 0xB723 },
+{ 0xB724, 0xB724, 0xB724 },
+{ 0xB725, 0xB725, 0xB725 },
+{ 0xB726, 0xB726, 0xB726 },
+{ 0xB727, 0xB727, 0xB727 },
+{ 0xB728, 0xB728, 0xB728 },
+{ 0xB729, 0xB729, 0xB729 },
+{ 0xB72A, 0xB72A, 0xB72A },
+{ 0xB72B, 0xB72B, 0xB72B },
+{ 0xB72C, 0xB72C, 0xB72C },
+{ 0xB72D, 0xB72D, 0xB72D },
+{ 0xB72E, 0xB72E, 0xB72E },
+{ 0xB72F, 0xB72F, 0xB72F },
+{ 0xB730, 0xB730, 0xB730 },
+{ 0xB731, 0xB731, 0xB731 },
+{ 0xB732, 0xB732, 0xB732 },
+{ 0xB733, 0xB733, 0xB733 },
+{ 0xB734, 0xB734, 0xB734 },
+{ 0xB735, 0xB735, 0xB735 },
+{ 0xB736, 0xB736, 0xB736 },
+{ 0xB737, 0xB737, 0xB737 },
+{ 0xB738, 0xB738, 0xB738 },
+{ 0xB739, 0xB739, 0xB739 },
+{ 0xB73A, 0xB73A, 0xB73A },
+{ 0xB73B, 0xB73B, 0xB73B },
+{ 0xB73C, 0xB73C, 0xB73C },
+{ 0xB73D, 0xB73D, 0xB73D },
+{ 0xB73E, 0xB73E, 0xB73E },
+{ 0xB73F, 0xB73F, 0xB73F },
+{ 0xB740, 0xB740, 0xB740 },
+{ 0xB741, 0xB741, 0xB741 },
+{ 0xB742, 0xB742, 0xB742 },
+{ 0xB743, 0xB743, 0xB743 },
+{ 0xB744, 0xB744, 0xB744 },
+{ 0xB745, 0xB745, 0xB745 },
+{ 0xB746, 0xB746, 0xB746 },
+{ 0xB747, 0xB747, 0xB747 },
+{ 0xB748, 0xB748, 0xB748 },
+{ 0xB749, 0xB749, 0xB749 },
+{ 0xB74A, 0xB74A, 0xB74A },
+{ 0xB74B, 0xB74B, 0xB74B },
+{ 0xB74C, 0xB74C, 0xB74C },
+{ 0xB74D, 0xB74D, 0xB74D },
+{ 0xB74E, 0xB74E, 0xB74E },
+{ 0xB74F, 0xB74F, 0xB74F },
+{ 0xB750, 0xB750, 0xB750 },
+{ 0xB751, 0xB751, 0xB751 },
+{ 0xB752, 0xB752, 0xB752 },
+{ 0xB753, 0xB753, 0xB753 },
+{ 0xB754, 0xB754, 0xB754 },
+{ 0xB755, 0xB755, 0xB755 },
+{ 0xB756, 0xB756, 0xB756 },
+{ 0xB757, 0xB757, 0xB757 },
+{ 0xB758, 0xB758, 0xB758 },
+{ 0xB759, 0xB759, 0xB759 },
+{ 0xB75A, 0xB75A, 0xB75A },
+{ 0xB75B, 0xB75B, 0xB75B },
+{ 0xB75C, 0xB75C, 0xB75C },
+{ 0xB75D, 0xB75D, 0xB75D },
+{ 0xB75E, 0xB75E, 0xB75E },
+{ 0xB75F, 0xB75F, 0xB75F },
+{ 0xB760, 0xB760, 0xB760 },
+{ 0xB761, 0xB761, 0xB761 },
+{ 0xB762, 0xB762, 0xB762 },
+{ 0xB763, 0xB763, 0xB763 },
+{ 0xB764, 0xB764, 0xB764 },
+{ 0xB765, 0xB765, 0xB765 },
+{ 0xB766, 0xB766, 0xB766 },
+{ 0xB767, 0xB767, 0xB767 },
+{ 0xB768, 0xB768, 0xB768 },
+{ 0xB769, 0xB769, 0xB769 },
+{ 0xB76A, 0xB76A, 0xB76A },
+{ 0xB76B, 0xB76B, 0xB76B },
+{ 0xB76C, 0xB76C, 0xB76C },
+{ 0xB76D, 0xB76D, 0xB76D },
+{ 0xB76E, 0xB76E, 0xB76E },
+{ 0xB76F, 0xB76F, 0xB76F },
+{ 0xB770, 0xB770, 0xB770 },
+{ 0xB771, 0xB771, 0xB771 },
+{ 0xB772, 0xB772, 0xB772 },
+{ 0xB773, 0xB773, 0xB773 },
+{ 0xB774, 0xB774, 0xB774 },
+{ 0xB775, 0xB775, 0xB775 },
+{ 0xB776, 0xB776, 0xB776 },
+{ 0xB777, 0xB777, 0xB777 },
+{ 0xB778, 0xB778, 0xB778 },
+{ 0xB779, 0xB779, 0xB779 },
+{ 0xB77A, 0xB77A, 0xB77A },
+{ 0xB77B, 0xB77B, 0xB77B },
+{ 0xB77C, 0xB77C, 0xB77C },
+{ 0xB77D, 0xB77D, 0xB77D },
+{ 0xB77E, 0xB77E, 0xB77E },
+{ 0xB77F, 0xB77F, 0xB77F },
+{ 0xB780, 0xB780, 0xB780 },
+{ 0xB781, 0xB781, 0xB781 },
+{ 0xB782, 0xB782, 0xB782 },
+{ 0xB783, 0xB783, 0xB783 },
+{ 0xB784, 0xB784, 0xB784 },
+{ 0xB785, 0xB785, 0xB785 },
+{ 0xB786, 0xB786, 0xB786 },
+{ 0xB787, 0xB787, 0xB787 },
+{ 0xB788, 0xB788, 0xB788 },
+{ 0xB789, 0xB789, 0xB789 },
+{ 0xB78A, 0xB78A, 0xB78A },
+{ 0xB78B, 0xB78B, 0xB78B },
+{ 0xB78C, 0xB78C, 0xB78C },
+{ 0xB78D, 0xB78D, 0xB78D },
+{ 0xB78E, 0xB78E, 0xB78E },
+{ 0xB78F, 0xB78F, 0xB78F },
+{ 0xB790, 0xB790, 0xB790 },
+{ 0xB791, 0xB791, 0xB791 },
+{ 0xB792, 0xB792, 0xB792 },
+{ 0xB793, 0xB793, 0xB793 },
+{ 0xB794, 0xB794, 0xB794 },
+{ 0xB795, 0xB795, 0xB795 },
+{ 0xB796, 0xB796, 0xB796 },
+{ 0xB797, 0xB797, 0xB797 },
+{ 0xB798, 0xB798, 0xB798 },
+{ 0xB799, 0xB799, 0xB799 },
+{ 0xB79A, 0xB79A, 0xB79A },
+{ 0xB79B, 0xB79B, 0xB79B },
+{ 0xB79C, 0xB79C, 0xB79C },
+{ 0xB79D, 0xB79D, 0xB79D },
+{ 0xB79E, 0xB79E, 0xB79E },
+{ 0xB79F, 0xB79F, 0xB79F },
+{ 0xB7A0, 0xB7A0, 0xB7A0 },
+{ 0xB7A1, 0xB7A1, 0xB7A1 },
+{ 0xB7A2, 0xB7A2, 0xB7A2 },
+{ 0xB7A3, 0xB7A3, 0xB7A3 },
+{ 0xB7A4, 0xB7A4, 0xB7A4 },
+{ 0xB7A5, 0xB7A5, 0xB7A5 },
+{ 0xB7A6, 0xB7A6, 0xB7A6 },
+{ 0xB7A7, 0xB7A7, 0xB7A7 },
+{ 0xB7A8, 0xB7A8, 0xB7A8 },
+{ 0xB7A9, 0xB7A9, 0xB7A9 },
+{ 0xB7AA, 0xB7AA, 0xB7AA },
+{ 0xB7AB, 0xB7AB, 0xB7AB },
+{ 0xB7AC, 0xB7AC, 0xB7AC },
+{ 0xB7AD, 0xB7AD, 0xB7AD },
+{ 0xB7AE, 0xB7AE, 0xB7AE },
+{ 0xB7AF, 0xB7AF, 0xB7AF },
+{ 0xB7B0, 0xB7B0, 0xB7B0 },
+{ 0xB7B1, 0xB7B1, 0xB7B1 },
+{ 0xB7B2, 0xB7B2, 0xB7B2 },
+{ 0xB7B3, 0xB7B3, 0xB7B3 },
+{ 0xB7B4, 0xB7B4, 0xB7B4 },
+{ 0xB7B5, 0xB7B5, 0xB7B5 },
+{ 0xB7B6, 0xB7B6, 0xB7B6 },
+{ 0xB7B7, 0xB7B7, 0xB7B7 },
+{ 0xB7B8, 0xB7B8, 0xB7B8 },
+{ 0xB7B9, 0xB7B9, 0xB7B9 },
+{ 0xB7BA, 0xB7BA, 0xB7BA },
+{ 0xB7BB, 0xB7BB, 0xB7BB },
+{ 0xB7BC, 0xB7BC, 0xB7BC },
+{ 0xB7BD, 0xB7BD, 0xB7BD },
+{ 0xB7BE, 0xB7BE, 0xB7BE },
+{ 0xB7BF, 0xB7BF, 0xB7BF },
+{ 0xB7C0, 0xB7C0, 0xB7C0 },
+{ 0xB7C1, 0xB7C1, 0xB7C1 },
+{ 0xB7C2, 0xB7C2, 0xB7C2 },
+{ 0xB7C3, 0xB7C3, 0xB7C3 },
+{ 0xB7C4, 0xB7C4, 0xB7C4 },
+{ 0xB7C5, 0xB7C5, 0xB7C5 },
+{ 0xB7C6, 0xB7C6, 0xB7C6 },
+{ 0xB7C7, 0xB7C7, 0xB7C7 },
+{ 0xB7C8, 0xB7C8, 0xB7C8 },
+{ 0xB7C9, 0xB7C9, 0xB7C9 },
+{ 0xB7CA, 0xB7CA, 0xB7CA },
+{ 0xB7CB, 0xB7CB, 0xB7CB },
+{ 0xB7CC, 0xB7CC, 0xB7CC },
+{ 0xB7CD, 0xB7CD, 0xB7CD },
+{ 0xB7CE, 0xB7CE, 0xB7CE },
+{ 0xB7CF, 0xB7CF, 0xB7CF },
+{ 0xB7D0, 0xB7D0, 0xB7D0 },
+{ 0xB7D1, 0xB7D1, 0xB7D1 },
+{ 0xB7D2, 0xB7D2, 0xB7D2 },
+{ 0xB7D3, 0xB7D3, 0xB7D3 },
+{ 0xB7D4, 0xB7D4, 0xB7D4 },
+{ 0xB7D5, 0xB7D5, 0xB7D5 },
+{ 0xB7D6, 0xB7D6, 0xB7D6 },
+{ 0xB7D7, 0xB7D7, 0xB7D7 },
+{ 0xB7D8, 0xB7D8, 0xB7D8 },
+{ 0xB7D9, 0xB7D9, 0xB7D9 },
+{ 0xB7DA, 0xB7DA, 0xB7DA },
+{ 0xB7DB, 0xB7DB, 0xB7DB },
+{ 0xB7DC, 0xB7DC, 0xB7DC },
+{ 0xB7DD, 0xB7DD, 0xB7DD },
+{ 0xB7DE, 0xB7DE, 0xB7DE },
+{ 0xB7DF, 0xB7DF, 0xB7DF },
+{ 0xB7E0, 0xB7E0, 0xB7E0 },
+{ 0xB7E1, 0xB7E1, 0xB7E1 },
+{ 0xB7E2, 0xB7E2, 0xB7E2 },
+{ 0xB7E3, 0xB7E3, 0xB7E3 },
+{ 0xB7E4, 0xB7E4, 0xB7E4 },
+{ 0xB7E5, 0xB7E5, 0xB7E5 },
+{ 0xB7E6, 0xB7E6, 0xB7E6 },
+{ 0xB7E7, 0xB7E7, 0xB7E7 },
+{ 0xB7E8, 0xB7E8, 0xB7E8 },
+{ 0xB7E9, 0xB7E9, 0xB7E9 },
+{ 0xB7EA, 0xB7EA, 0xB7EA },
+{ 0xB7EB, 0xB7EB, 0xB7EB },
+{ 0xB7EC, 0xB7EC, 0xB7EC },
+{ 0xB7ED, 0xB7ED, 0xB7ED },
+{ 0xB7EE, 0xB7EE, 0xB7EE },
+{ 0xB7EF, 0xB7EF, 0xB7EF },
+{ 0xB7F0, 0xB7F0, 0xB7F0 },
+{ 0xB7F1, 0xB7F1, 0xB7F1 },
+{ 0xB7F2, 0xB7F2, 0xB7F2 },
+{ 0xB7F3, 0xB7F3, 0xB7F3 },
+{ 0xB7F4, 0xB7F4, 0xB7F4 },
+{ 0xB7F5, 0xB7F5, 0xB7F5 },
+{ 0xB7F6, 0xB7F6, 0xB7F6 },
+{ 0xB7F7, 0xB7F7, 0xB7F7 },
+{ 0xB7F8, 0xB7F8, 0xB7F8 },
+{ 0xB7F9, 0xB7F9, 0xB7F9 },
+{ 0xB7FA, 0xB7FA, 0xB7FA },
+{ 0xB7FB, 0xB7FB, 0xB7FB },
+{ 0xB7FC, 0xB7FC, 0xB7FC },
+{ 0xB7FD, 0xB7FD, 0xB7FD },
+{ 0xB7FE, 0xB7FE, 0xB7FE },
+{ 0xB7FF, 0xB7FF, 0xB7FF },
+{ 0xB800, 0xB800, 0xB800 },
+{ 0xB801, 0xB801, 0xB801 },
+{ 0xB802, 0xB802, 0xB802 },
+{ 0xB803, 0xB803, 0xB803 },
+{ 0xB804, 0xB804, 0xB804 },
+{ 0xB805, 0xB805, 0xB805 },
+{ 0xB806, 0xB806, 0xB806 },
+{ 0xB807, 0xB807, 0xB807 },
+{ 0xB808, 0xB808, 0xB808 },
+{ 0xB809, 0xB809, 0xB809 },
+{ 0xB80A, 0xB80A, 0xB80A },
+{ 0xB80B, 0xB80B, 0xB80B },
+{ 0xB80C, 0xB80C, 0xB80C },
+{ 0xB80D, 0xB80D, 0xB80D },
+{ 0xB80E, 0xB80E, 0xB80E },
+{ 0xB80F, 0xB80F, 0xB80F },
+{ 0xB810, 0xB810, 0xB810 },
+{ 0xB811, 0xB811, 0xB811 },
+{ 0xB812, 0xB812, 0xB812 },
+{ 0xB813, 0xB813, 0xB813 },
+{ 0xB814, 0xB814, 0xB814 },
+{ 0xB815, 0xB815, 0xB815 },
+{ 0xB816, 0xB816, 0xB816 },
+{ 0xB817, 0xB817, 0xB817 },
+{ 0xB818, 0xB818, 0xB818 },
+{ 0xB819, 0xB819, 0xB819 },
+{ 0xB81A, 0xB81A, 0xB81A },
+{ 0xB81B, 0xB81B, 0xB81B },
+{ 0xB81C, 0xB81C, 0xB81C },
+{ 0xB81D, 0xB81D, 0xB81D },
+{ 0xB81E, 0xB81E, 0xB81E },
+{ 0xB81F, 0xB81F, 0xB81F },
+{ 0xB820, 0xB820, 0xB820 },
+{ 0xB821, 0xB821, 0xB821 },
+{ 0xB822, 0xB822, 0xB822 },
+{ 0xB823, 0xB823, 0xB823 },
+{ 0xB824, 0xB824, 0xB824 },
+{ 0xB825, 0xB825, 0xB825 },
+{ 0xB826, 0xB826, 0xB826 },
+{ 0xB827, 0xB827, 0xB827 },
+{ 0xB828, 0xB828, 0xB828 },
+{ 0xB829, 0xB829, 0xB829 },
+{ 0xB82A, 0xB82A, 0xB82A },
+{ 0xB82B, 0xB82B, 0xB82B },
+{ 0xB82C, 0xB82C, 0xB82C },
+{ 0xB82D, 0xB82D, 0xB82D },
+{ 0xB82E, 0xB82E, 0xB82E },
+{ 0xB82F, 0xB82F, 0xB82F },
+{ 0xB830, 0xB830, 0xB830 },
+{ 0xB831, 0xB831, 0xB831 },
+{ 0xB832, 0xB832, 0xB832 },
+{ 0xB833, 0xB833, 0xB833 },
+{ 0xB834, 0xB834, 0xB834 },
+{ 0xB835, 0xB835, 0xB835 },
+{ 0xB836, 0xB836, 0xB836 },
+{ 0xB837, 0xB837, 0xB837 },
+{ 0xB838, 0xB838, 0xB838 },
+{ 0xB839, 0xB839, 0xB839 },
+{ 0xB83A, 0xB83A, 0xB83A },
+{ 0xB83B, 0xB83B, 0xB83B },
+{ 0xB83C, 0xB83C, 0xB83C },
+{ 0xB83D, 0xB83D, 0xB83D },
+{ 0xB83E, 0xB83E, 0xB83E },
+{ 0xB83F, 0xB83F, 0xB83F },
+{ 0xB840, 0xB840, 0xB840 },
+{ 0xB841, 0xB841, 0xB841 },
+{ 0xB842, 0xB842, 0xB842 },
+{ 0xB843, 0xB843, 0xB843 },
+{ 0xB844, 0xB844, 0xB844 },
+{ 0xB845, 0xB845, 0xB845 },
+{ 0xB846, 0xB846, 0xB846 },
+{ 0xB847, 0xB847, 0xB847 },
+{ 0xB848, 0xB848, 0xB848 },
+{ 0xB849, 0xB849, 0xB849 },
+{ 0xB84A, 0xB84A, 0xB84A },
+{ 0xB84B, 0xB84B, 0xB84B },
+{ 0xB84C, 0xB84C, 0xB84C },
+{ 0xB84D, 0xB84D, 0xB84D },
+{ 0xB84E, 0xB84E, 0xB84E },
+{ 0xB84F, 0xB84F, 0xB84F },
+{ 0xB850, 0xB850, 0xB850 },
+{ 0xB851, 0xB851, 0xB851 },
+{ 0xB852, 0xB852, 0xB852 },
+{ 0xB853, 0xB853, 0xB853 },
+{ 0xB854, 0xB854, 0xB854 },
+{ 0xB855, 0xB855, 0xB855 },
+{ 0xB856, 0xB856, 0xB856 },
+{ 0xB857, 0xB857, 0xB857 },
+{ 0xB858, 0xB858, 0xB858 },
+{ 0xB859, 0xB859, 0xB859 },
+{ 0xB85A, 0xB85A, 0xB85A },
+{ 0xB85B, 0xB85B, 0xB85B },
+{ 0xB85C, 0xB85C, 0xB85C },
+{ 0xB85D, 0xB85D, 0xB85D },
+{ 0xB85E, 0xB85E, 0xB85E },
+{ 0xB85F, 0xB85F, 0xB85F },
+{ 0xB860, 0xB860, 0xB860 },
+{ 0xB861, 0xB861, 0xB861 },
+{ 0xB862, 0xB862, 0xB862 },
+{ 0xB863, 0xB863, 0xB863 },
+{ 0xB864, 0xB864, 0xB864 },
+{ 0xB865, 0xB865, 0xB865 },
+{ 0xB866, 0xB866, 0xB866 },
+{ 0xB867, 0xB867, 0xB867 },
+{ 0xB868, 0xB868, 0xB868 },
+{ 0xB869, 0xB869, 0xB869 },
+{ 0xB86A, 0xB86A, 0xB86A },
+{ 0xB86B, 0xB86B, 0xB86B },
+{ 0xB86C, 0xB86C, 0xB86C },
+{ 0xB86D, 0xB86D, 0xB86D },
+{ 0xB86E, 0xB86E, 0xB86E },
+{ 0xB86F, 0xB86F, 0xB86F },
+{ 0xB870, 0xB870, 0xB870 },
+{ 0xB871, 0xB871, 0xB871 },
+{ 0xB872, 0xB872, 0xB872 },
+{ 0xB873, 0xB873, 0xB873 },
+{ 0xB874, 0xB874, 0xB874 },
+{ 0xB875, 0xB875, 0xB875 },
+{ 0xB876, 0xB876, 0xB876 },
+{ 0xB877, 0xB877, 0xB877 },
+{ 0xB878, 0xB878, 0xB878 },
+{ 0xB879, 0xB879, 0xB879 },
+{ 0xB87A, 0xB87A, 0xB87A },
+{ 0xB87B, 0xB87B, 0xB87B },
+{ 0xB87C, 0xB87C, 0xB87C },
+{ 0xB87D, 0xB87D, 0xB87D },
+{ 0xB87E, 0xB87E, 0xB87E },
+{ 0xB87F, 0xB87F, 0xB87F },
+{ 0xB880, 0xB880, 0xB880 },
+{ 0xB881, 0xB881, 0xB881 },
+{ 0xB882, 0xB882, 0xB882 },
+{ 0xB883, 0xB883, 0xB883 },
+{ 0xB884, 0xB884, 0xB884 },
+{ 0xB885, 0xB885, 0xB885 },
+{ 0xB886, 0xB886, 0xB886 },
+{ 0xB887, 0xB887, 0xB887 },
+{ 0xB888, 0xB888, 0xB888 },
+{ 0xB889, 0xB889, 0xB889 },
+{ 0xB88A, 0xB88A, 0xB88A },
+{ 0xB88B, 0xB88B, 0xB88B },
+{ 0xB88C, 0xB88C, 0xB88C },
+{ 0xB88D, 0xB88D, 0xB88D },
+{ 0xB88E, 0xB88E, 0xB88E },
+{ 0xB88F, 0xB88F, 0xB88F },
+{ 0xB890, 0xB890, 0xB890 },
+{ 0xB891, 0xB891, 0xB891 },
+{ 0xB892, 0xB892, 0xB892 },
+{ 0xB893, 0xB893, 0xB893 },
+{ 0xB894, 0xB894, 0xB894 },
+{ 0xB895, 0xB895, 0xB895 },
+{ 0xB896, 0xB896, 0xB896 },
+{ 0xB897, 0xB897, 0xB897 },
+{ 0xB898, 0xB898, 0xB898 },
+{ 0xB899, 0xB899, 0xB899 },
+{ 0xB89A, 0xB89A, 0xB89A },
+{ 0xB89B, 0xB89B, 0xB89B },
+{ 0xB89C, 0xB89C, 0xB89C },
+{ 0xB89D, 0xB89D, 0xB89D },
+{ 0xB89E, 0xB89E, 0xB89E },
+{ 0xB89F, 0xB89F, 0xB89F },
+{ 0xB8A0, 0xB8A0, 0xB8A0 },
+{ 0xB8A1, 0xB8A1, 0xB8A1 },
+{ 0xB8A2, 0xB8A2, 0xB8A2 },
+{ 0xB8A3, 0xB8A3, 0xB8A3 },
+{ 0xB8A4, 0xB8A4, 0xB8A4 },
+{ 0xB8A5, 0xB8A5, 0xB8A5 },
+{ 0xB8A6, 0xB8A6, 0xB8A6 },
+{ 0xB8A7, 0xB8A7, 0xB8A7 },
+{ 0xB8A8, 0xB8A8, 0xB8A8 },
+{ 0xB8A9, 0xB8A9, 0xB8A9 },
+{ 0xB8AA, 0xB8AA, 0xB8AA },
+{ 0xB8AB, 0xB8AB, 0xB8AB },
+{ 0xB8AC, 0xB8AC, 0xB8AC },
+{ 0xB8AD, 0xB8AD, 0xB8AD },
+{ 0xB8AE, 0xB8AE, 0xB8AE },
+{ 0xB8AF, 0xB8AF, 0xB8AF },
+{ 0xB8B0, 0xB8B0, 0xB8B0 },
+{ 0xB8B1, 0xB8B1, 0xB8B1 },
+{ 0xB8B2, 0xB8B2, 0xB8B2 },
+{ 0xB8B3, 0xB8B3, 0xB8B3 },
+{ 0xB8B4, 0xB8B4, 0xB8B4 },
+{ 0xB8B5, 0xB8B5, 0xB8B5 },
+{ 0xB8B6, 0xB8B6, 0xB8B6 },
+{ 0xB8B7, 0xB8B7, 0xB8B7 },
+{ 0xB8B8, 0xB8B8, 0xB8B8 },
+{ 0xB8B9, 0xB8B9, 0xB8B9 },
+{ 0xB8BA, 0xB8BA, 0xB8BA },
+{ 0xB8BB, 0xB8BB, 0xB8BB },
+{ 0xB8BC, 0xB8BC, 0xB8BC },
+{ 0xB8BD, 0xB8BD, 0xB8BD },
+{ 0xB8BE, 0xB8BE, 0xB8BE },
+{ 0xB8BF, 0xB8BF, 0xB8BF },
+{ 0xB8C0, 0xB8C0, 0xB8C0 },
+{ 0xB8C1, 0xB8C1, 0xB8C1 },
+{ 0xB8C2, 0xB8C2, 0xB8C2 },
+{ 0xB8C3, 0xB8C3, 0xB8C3 },
+{ 0xB8C4, 0xB8C4, 0xB8C4 },
+{ 0xB8C5, 0xB8C5, 0xB8C5 },
+{ 0xB8C6, 0xB8C6, 0xB8C6 },
+{ 0xB8C7, 0xB8C7, 0xB8C7 },
+{ 0xB8C8, 0xB8C8, 0xB8C8 },
+{ 0xB8C9, 0xB8C9, 0xB8C9 },
+{ 0xB8CA, 0xB8CA, 0xB8CA },
+{ 0xB8CB, 0xB8CB, 0xB8CB },
+{ 0xB8CC, 0xB8CC, 0xB8CC },
+{ 0xB8CD, 0xB8CD, 0xB8CD },
+{ 0xB8CE, 0xB8CE, 0xB8CE },
+{ 0xB8CF, 0xB8CF, 0xB8CF },
+{ 0xB8D0, 0xB8D0, 0xB8D0 },
+{ 0xB8D1, 0xB8D1, 0xB8D1 },
+{ 0xB8D2, 0xB8D2, 0xB8D2 },
+{ 0xB8D3, 0xB8D3, 0xB8D3 },
+{ 0xB8D4, 0xB8D4, 0xB8D4 },
+{ 0xB8D5, 0xB8D5, 0xB8D5 },
+{ 0xB8D6, 0xB8D6, 0xB8D6 },
+{ 0xB8D7, 0xB8D7, 0xB8D7 },
+{ 0xB8D8, 0xB8D8, 0xB8D8 },
+{ 0xB8D9, 0xB8D9, 0xB8D9 },
+{ 0xB8DA, 0xB8DA, 0xB8DA },
+{ 0xB8DB, 0xB8DB, 0xB8DB },
+{ 0xB8DC, 0xB8DC, 0xB8DC },
+{ 0xB8DD, 0xB8DD, 0xB8DD },
+{ 0xB8DE, 0xB8DE, 0xB8DE },
+{ 0xB8DF, 0xB8DF, 0xB8DF },
+{ 0xB8E0, 0xB8E0, 0xB8E0 },
+{ 0xB8E1, 0xB8E1, 0xB8E1 },
+{ 0xB8E2, 0xB8E2, 0xB8E2 },
+{ 0xB8E3, 0xB8E3, 0xB8E3 },
+{ 0xB8E4, 0xB8E4, 0xB8E4 },
+{ 0xB8E5, 0xB8E5, 0xB8E5 },
+{ 0xB8E6, 0xB8E6, 0xB8E6 },
+{ 0xB8E7, 0xB8E7, 0xB8E7 },
+{ 0xB8E8, 0xB8E8, 0xB8E8 },
+{ 0xB8E9, 0xB8E9, 0xB8E9 },
+{ 0xB8EA, 0xB8EA, 0xB8EA },
+{ 0xB8EB, 0xB8EB, 0xB8EB },
+{ 0xB8EC, 0xB8EC, 0xB8EC },
+{ 0xB8ED, 0xB8ED, 0xB8ED },
+{ 0xB8EE, 0xB8EE, 0xB8EE },
+{ 0xB8EF, 0xB8EF, 0xB8EF },
+{ 0xB8F0, 0xB8F0, 0xB8F0 },
+{ 0xB8F1, 0xB8F1, 0xB8F1 },
+{ 0xB8F2, 0xB8F2, 0xB8F2 },
+{ 0xB8F3, 0xB8F3, 0xB8F3 },
+{ 0xB8F4, 0xB8F4, 0xB8F4 },
+{ 0xB8F5, 0xB8F5, 0xB8F5 },
+{ 0xB8F6, 0xB8F6, 0xB8F6 },
+{ 0xB8F7, 0xB8F7, 0xB8F7 },
+{ 0xB8F8, 0xB8F8, 0xB8F8 },
+{ 0xB8F9, 0xB8F9, 0xB8F9 },
+{ 0xB8FA, 0xB8FA, 0xB8FA },
+{ 0xB8FB, 0xB8FB, 0xB8FB },
+{ 0xB8FC, 0xB8FC, 0xB8FC },
+{ 0xB8FD, 0xB8FD, 0xB8FD },
+{ 0xB8FE, 0xB8FE, 0xB8FE },
+{ 0xB8FF, 0xB8FF, 0xB8FF },
+{ 0xB900, 0xB900, 0xB900 },
+{ 0xB901, 0xB901, 0xB901 },
+{ 0xB902, 0xB902, 0xB902 },
+{ 0xB903, 0xB903, 0xB903 },
+{ 0xB904, 0xB904, 0xB904 },
+{ 0xB905, 0xB905, 0xB905 },
+{ 0xB906, 0xB906, 0xB906 },
+{ 0xB907, 0xB907, 0xB907 },
+{ 0xB908, 0xB908, 0xB908 },
+{ 0xB909, 0xB909, 0xB909 },
+{ 0xB90A, 0xB90A, 0xB90A },
+{ 0xB90B, 0xB90B, 0xB90B },
+{ 0xB90C, 0xB90C, 0xB90C },
+{ 0xB90D, 0xB90D, 0xB90D },
+{ 0xB90E, 0xB90E, 0xB90E },
+{ 0xB90F, 0xB90F, 0xB90F },
+{ 0xB910, 0xB910, 0xB910 },
+{ 0xB911, 0xB911, 0xB911 },
+{ 0xB912, 0xB912, 0xB912 },
+{ 0xB913, 0xB913, 0xB913 },
+{ 0xB914, 0xB914, 0xB914 },
+{ 0xB915, 0xB915, 0xB915 },
+{ 0xB916, 0xB916, 0xB916 },
+{ 0xB917, 0xB917, 0xB917 },
+{ 0xB918, 0xB918, 0xB918 },
+{ 0xB919, 0xB919, 0xB919 },
+{ 0xB91A, 0xB91A, 0xB91A },
+{ 0xB91B, 0xB91B, 0xB91B },
+{ 0xB91C, 0xB91C, 0xB91C },
+{ 0xB91D, 0xB91D, 0xB91D },
+{ 0xB91E, 0xB91E, 0xB91E },
+{ 0xB91F, 0xB91F, 0xB91F },
+{ 0xB920, 0xB920, 0xB920 },
+{ 0xB921, 0xB921, 0xB921 },
+{ 0xB922, 0xB922, 0xB922 },
+{ 0xB923, 0xB923, 0xB923 },
+{ 0xB924, 0xB924, 0xB924 },
+{ 0xB925, 0xB925, 0xB925 },
+{ 0xB926, 0xB926, 0xB926 },
+{ 0xB927, 0xB927, 0xB927 },
+{ 0xB928, 0xB928, 0xB928 },
+{ 0xB929, 0xB929, 0xB929 },
+{ 0xB92A, 0xB92A, 0xB92A },
+{ 0xB92B, 0xB92B, 0xB92B },
+{ 0xB92C, 0xB92C, 0xB92C },
+{ 0xB92D, 0xB92D, 0xB92D },
+{ 0xB92E, 0xB92E, 0xB92E },
+{ 0xB92F, 0xB92F, 0xB92F },
+{ 0xB930, 0xB930, 0xB930 },
+{ 0xB931, 0xB931, 0xB931 },
+{ 0xB932, 0xB932, 0xB932 },
+{ 0xB933, 0xB933, 0xB933 },
+{ 0xB934, 0xB934, 0xB934 },
+{ 0xB935, 0xB935, 0xB935 },
+{ 0xB936, 0xB936, 0xB936 },
+{ 0xB937, 0xB937, 0xB937 },
+{ 0xB938, 0xB938, 0xB938 },
+{ 0xB939, 0xB939, 0xB939 },
+{ 0xB93A, 0xB93A, 0xB93A },
+{ 0xB93B, 0xB93B, 0xB93B },
+{ 0xB93C, 0xB93C, 0xB93C },
+{ 0xB93D, 0xB93D, 0xB93D },
+{ 0xB93E, 0xB93E, 0xB93E },
+{ 0xB93F, 0xB93F, 0xB93F },
+{ 0xB940, 0xB940, 0xB940 },
+{ 0xB941, 0xB941, 0xB941 },
+{ 0xB942, 0xB942, 0xB942 },
+{ 0xB943, 0xB943, 0xB943 },
+{ 0xB944, 0xB944, 0xB944 },
+{ 0xB945, 0xB945, 0xB945 },
+{ 0xB946, 0xB946, 0xB946 },
+{ 0xB947, 0xB947, 0xB947 },
+{ 0xB948, 0xB948, 0xB948 },
+{ 0xB949, 0xB949, 0xB949 },
+{ 0xB94A, 0xB94A, 0xB94A },
+{ 0xB94B, 0xB94B, 0xB94B },
+{ 0xB94C, 0xB94C, 0xB94C },
+{ 0xB94D, 0xB94D, 0xB94D },
+{ 0xB94E, 0xB94E, 0xB94E },
+{ 0xB94F, 0xB94F, 0xB94F },
+{ 0xB950, 0xB950, 0xB950 },
+{ 0xB951, 0xB951, 0xB951 },
+{ 0xB952, 0xB952, 0xB952 },
+{ 0xB953, 0xB953, 0xB953 },
+{ 0xB954, 0xB954, 0xB954 },
+{ 0xB955, 0xB955, 0xB955 },
+{ 0xB956, 0xB956, 0xB956 },
+{ 0xB957, 0xB957, 0xB957 },
+{ 0xB958, 0xB958, 0xB958 },
+{ 0xB959, 0xB959, 0xB959 },
+{ 0xB95A, 0xB95A, 0xB95A },
+{ 0xB95B, 0xB95B, 0xB95B },
+{ 0xB95C, 0xB95C, 0xB95C },
+{ 0xB95D, 0xB95D, 0xB95D },
+{ 0xB95E, 0xB95E, 0xB95E },
+{ 0xB95F, 0xB95F, 0xB95F },
+{ 0xB960, 0xB960, 0xB960 },
+{ 0xB961, 0xB961, 0xB961 },
+{ 0xB962, 0xB962, 0xB962 },
+{ 0xB963, 0xB963, 0xB963 },
+{ 0xB964, 0xB964, 0xB964 },
+{ 0xB965, 0xB965, 0xB965 },
+{ 0xB966, 0xB966, 0xB966 },
+{ 0xB967, 0xB967, 0xB967 },
+{ 0xB968, 0xB968, 0xB968 },
+{ 0xB969, 0xB969, 0xB969 },
+{ 0xB96A, 0xB96A, 0xB96A },
+{ 0xB96B, 0xB96B, 0xB96B },
+{ 0xB96C, 0xB96C, 0xB96C },
+{ 0xB96D, 0xB96D, 0xB96D },
+{ 0xB96E, 0xB96E, 0xB96E },
+{ 0xB96F, 0xB96F, 0xB96F },
+{ 0xB970, 0xB970, 0xB970 },
+{ 0xB971, 0xB971, 0xB971 },
+{ 0xB972, 0xB972, 0xB972 },
+{ 0xB973, 0xB973, 0xB973 },
+{ 0xB974, 0xB974, 0xB974 },
+{ 0xB975, 0xB975, 0xB975 },
+{ 0xB976, 0xB976, 0xB976 },
+{ 0xB977, 0xB977, 0xB977 },
+{ 0xB978, 0xB978, 0xB978 },
+{ 0xB979, 0xB979, 0xB979 },
+{ 0xB97A, 0xB97A, 0xB97A },
+{ 0xB97B, 0xB97B, 0xB97B },
+{ 0xB97C, 0xB97C, 0xB97C },
+{ 0xB97D, 0xB97D, 0xB97D },
+{ 0xB97E, 0xB97E, 0xB97E },
+{ 0xB97F, 0xB97F, 0xB97F },
+{ 0xB980, 0xB980, 0xB980 },
+{ 0xB981, 0xB981, 0xB981 },
+{ 0xB982, 0xB982, 0xB982 },
+{ 0xB983, 0xB983, 0xB983 },
+{ 0xB984, 0xB984, 0xB984 },
+{ 0xB985, 0xB985, 0xB985 },
+{ 0xB986, 0xB986, 0xB986 },
+{ 0xB987, 0xB987, 0xB987 },
+{ 0xB988, 0xB988, 0xB988 },
+{ 0xB989, 0xB989, 0xB989 },
+{ 0xB98A, 0xB98A, 0xB98A },
+{ 0xB98B, 0xB98B, 0xB98B },
+{ 0xB98C, 0xB98C, 0xB98C },
+{ 0xB98D, 0xB98D, 0xB98D },
+{ 0xB98E, 0xB98E, 0xB98E },
+{ 0xB98F, 0xB98F, 0xB98F },
+{ 0xB990, 0xB990, 0xB990 },
+{ 0xB991, 0xB991, 0xB991 },
+{ 0xB992, 0xB992, 0xB992 },
+{ 0xB993, 0xB993, 0xB993 },
+{ 0xB994, 0xB994, 0xB994 },
+{ 0xB995, 0xB995, 0xB995 },
+{ 0xB996, 0xB996, 0xB996 },
+{ 0xB997, 0xB997, 0xB997 },
+{ 0xB998, 0xB998, 0xB998 },
+{ 0xB999, 0xB999, 0xB999 },
+{ 0xB99A, 0xB99A, 0xB99A },
+{ 0xB99B, 0xB99B, 0xB99B },
+{ 0xB99C, 0xB99C, 0xB99C },
+{ 0xB99D, 0xB99D, 0xB99D },
+{ 0xB99E, 0xB99E, 0xB99E },
+{ 0xB99F, 0xB99F, 0xB99F },
+{ 0xB9A0, 0xB9A0, 0xB9A0 },
+{ 0xB9A1, 0xB9A1, 0xB9A1 },
+{ 0xB9A2, 0xB9A2, 0xB9A2 },
+{ 0xB9A3, 0xB9A3, 0xB9A3 },
+{ 0xB9A4, 0xB9A4, 0xB9A4 },
+{ 0xB9A5, 0xB9A5, 0xB9A5 },
+{ 0xB9A6, 0xB9A6, 0xB9A6 },
+{ 0xB9A7, 0xB9A7, 0xB9A7 },
+{ 0xB9A8, 0xB9A8, 0xB9A8 },
+{ 0xB9A9, 0xB9A9, 0xB9A9 },
+{ 0xB9AA, 0xB9AA, 0xB9AA },
+{ 0xB9AB, 0xB9AB, 0xB9AB },
+{ 0xB9AC, 0xB9AC, 0xB9AC },
+{ 0xB9AD, 0xB9AD, 0xB9AD },
+{ 0xB9AE, 0xB9AE, 0xB9AE },
+{ 0xB9AF, 0xB9AF, 0xB9AF },
+{ 0xB9B0, 0xB9B0, 0xB9B0 },
+{ 0xB9B1, 0xB9B1, 0xB9B1 },
+{ 0xB9B2, 0xB9B2, 0xB9B2 },
+{ 0xB9B3, 0xB9B3, 0xB9B3 },
+{ 0xB9B4, 0xB9B4, 0xB9B4 },
+{ 0xB9B5, 0xB9B5, 0xB9B5 },
+{ 0xB9B6, 0xB9B6, 0xB9B6 },
+{ 0xB9B7, 0xB9B7, 0xB9B7 },
+{ 0xB9B8, 0xB9B8, 0xB9B8 },
+{ 0xB9B9, 0xB9B9, 0xB9B9 },
+{ 0xB9BA, 0xB9BA, 0xB9BA },
+{ 0xB9BB, 0xB9BB, 0xB9BB },
+{ 0xB9BC, 0xB9BC, 0xB9BC },
+{ 0xB9BD, 0xB9BD, 0xB9BD },
+{ 0xB9BE, 0xB9BE, 0xB9BE },
+{ 0xB9BF, 0xB9BF, 0xB9BF },
+{ 0xB9C0, 0xB9C0, 0xB9C0 },
+{ 0xB9C1, 0xB9C1, 0xB9C1 },
+{ 0xB9C2, 0xB9C2, 0xB9C2 },
+{ 0xB9C3, 0xB9C3, 0xB9C3 },
+{ 0xB9C4, 0xB9C4, 0xB9C4 },
+{ 0xB9C5, 0xB9C5, 0xB9C5 },
+{ 0xB9C6, 0xB9C6, 0xB9C6 },
+{ 0xB9C7, 0xB9C7, 0xB9C7 },
+{ 0xB9C8, 0xB9C8, 0xB9C8 },
+{ 0xB9C9, 0xB9C9, 0xB9C9 },
+{ 0xB9CA, 0xB9CA, 0xB9CA },
+{ 0xB9CB, 0xB9CB, 0xB9CB },
+{ 0xB9CC, 0xB9CC, 0xB9CC },
+{ 0xB9CD, 0xB9CD, 0xB9CD },
+{ 0xB9CE, 0xB9CE, 0xB9CE },
+{ 0xB9CF, 0xB9CF, 0xB9CF },
+{ 0xB9D0, 0xB9D0, 0xB9D0 },
+{ 0xB9D1, 0xB9D1, 0xB9D1 },
+{ 0xB9D2, 0xB9D2, 0xB9D2 },
+{ 0xB9D3, 0xB9D3, 0xB9D3 },
+{ 0xB9D4, 0xB9D4, 0xB9D4 },
+{ 0xB9D5, 0xB9D5, 0xB9D5 },
+{ 0xB9D6, 0xB9D6, 0xB9D6 },
+{ 0xB9D7, 0xB9D7, 0xB9D7 },
+{ 0xB9D8, 0xB9D8, 0xB9D8 },
+{ 0xB9D9, 0xB9D9, 0xB9D9 },
+{ 0xB9DA, 0xB9DA, 0xB9DA },
+{ 0xB9DB, 0xB9DB, 0xB9DB },
+{ 0xB9DC, 0xB9DC, 0xB9DC },
+{ 0xB9DD, 0xB9DD, 0xB9DD },
+{ 0xB9DE, 0xB9DE, 0xB9DE },
+{ 0xB9DF, 0xB9DF, 0xB9DF },
+{ 0xB9E0, 0xB9E0, 0xB9E0 },
+{ 0xB9E1, 0xB9E1, 0xB9E1 },
+{ 0xB9E2, 0xB9E2, 0xB9E2 },
+{ 0xB9E3, 0xB9E3, 0xB9E3 },
+{ 0xB9E4, 0xB9E4, 0xB9E4 },
+{ 0xB9E5, 0xB9E5, 0xB9E5 },
+{ 0xB9E6, 0xB9E6, 0xB9E6 },
+{ 0xB9E7, 0xB9E7, 0xB9E7 },
+{ 0xB9E8, 0xB9E8, 0xB9E8 },
+{ 0xB9E9, 0xB9E9, 0xB9E9 },
+{ 0xB9EA, 0xB9EA, 0xB9EA },
+{ 0xB9EB, 0xB9EB, 0xB9EB },
+{ 0xB9EC, 0xB9EC, 0xB9EC },
+{ 0xB9ED, 0xB9ED, 0xB9ED },
+{ 0xB9EE, 0xB9EE, 0xB9EE },
+{ 0xB9EF, 0xB9EF, 0xB9EF },
+{ 0xB9F0, 0xB9F0, 0xB9F0 },
+{ 0xB9F1, 0xB9F1, 0xB9F1 },
+{ 0xB9F2, 0xB9F2, 0xB9F2 },
+{ 0xB9F3, 0xB9F3, 0xB9F3 },
+{ 0xB9F4, 0xB9F4, 0xB9F4 },
+{ 0xB9F5, 0xB9F5, 0xB9F5 },
+{ 0xB9F6, 0xB9F6, 0xB9F6 },
+{ 0xB9F7, 0xB9F7, 0xB9F7 },
+{ 0xB9F8, 0xB9F8, 0xB9F8 },
+{ 0xB9F9, 0xB9F9, 0xB9F9 },
+{ 0xB9FA, 0xB9FA, 0xB9FA },
+{ 0xB9FB, 0xB9FB, 0xB9FB },
+{ 0xB9FC, 0xB9FC, 0xB9FC },
+{ 0xB9FD, 0xB9FD, 0xB9FD },
+{ 0xB9FE, 0xB9FE, 0xB9FE },
+{ 0xB9FF, 0xB9FF, 0xB9FF },
+{ 0xBA00, 0xBA00, 0xBA00 },
+{ 0xBA01, 0xBA01, 0xBA01 },
+{ 0xBA02, 0xBA02, 0xBA02 },
+{ 0xBA03, 0xBA03, 0xBA03 },
+{ 0xBA04, 0xBA04, 0xBA04 },
+{ 0xBA05, 0xBA05, 0xBA05 },
+{ 0xBA06, 0xBA06, 0xBA06 },
+{ 0xBA07, 0xBA07, 0xBA07 },
+{ 0xBA08, 0xBA08, 0xBA08 },
+{ 0xBA09, 0xBA09, 0xBA09 },
+{ 0xBA0A, 0xBA0A, 0xBA0A },
+{ 0xBA0B, 0xBA0B, 0xBA0B },
+{ 0xBA0C, 0xBA0C, 0xBA0C },
+{ 0xBA0D, 0xBA0D, 0xBA0D },
+{ 0xBA0E, 0xBA0E, 0xBA0E },
+{ 0xBA0F, 0xBA0F, 0xBA0F },
+{ 0xBA10, 0xBA10, 0xBA10 },
+{ 0xBA11, 0xBA11, 0xBA11 },
+{ 0xBA12, 0xBA12, 0xBA12 },
+{ 0xBA13, 0xBA13, 0xBA13 },
+{ 0xBA14, 0xBA14, 0xBA14 },
+{ 0xBA15, 0xBA15, 0xBA15 },
+{ 0xBA16, 0xBA16, 0xBA16 },
+{ 0xBA17, 0xBA17, 0xBA17 },
+{ 0xBA18, 0xBA18, 0xBA18 },
+{ 0xBA19, 0xBA19, 0xBA19 },
+{ 0xBA1A, 0xBA1A, 0xBA1A },
+{ 0xBA1B, 0xBA1B, 0xBA1B },
+{ 0xBA1C, 0xBA1C, 0xBA1C },
+{ 0xBA1D, 0xBA1D, 0xBA1D },
+{ 0xBA1E, 0xBA1E, 0xBA1E },
+{ 0xBA1F, 0xBA1F, 0xBA1F },
+{ 0xBA20, 0xBA20, 0xBA20 },
+{ 0xBA21, 0xBA21, 0xBA21 },
+{ 0xBA22, 0xBA22, 0xBA22 },
+{ 0xBA23, 0xBA23, 0xBA23 },
+{ 0xBA24, 0xBA24, 0xBA24 },
+{ 0xBA25, 0xBA25, 0xBA25 },
+{ 0xBA26, 0xBA26, 0xBA26 },
+{ 0xBA27, 0xBA27, 0xBA27 },
+{ 0xBA28, 0xBA28, 0xBA28 },
+{ 0xBA29, 0xBA29, 0xBA29 },
+{ 0xBA2A, 0xBA2A, 0xBA2A },
+{ 0xBA2B, 0xBA2B, 0xBA2B },
+{ 0xBA2C, 0xBA2C, 0xBA2C },
+{ 0xBA2D, 0xBA2D, 0xBA2D },
+{ 0xBA2E, 0xBA2E, 0xBA2E },
+{ 0xBA2F, 0xBA2F, 0xBA2F },
+{ 0xBA30, 0xBA30, 0xBA30 },
+{ 0xBA31, 0xBA31, 0xBA31 },
+{ 0xBA32, 0xBA32, 0xBA32 },
+{ 0xBA33, 0xBA33, 0xBA33 },
+{ 0xBA34, 0xBA34, 0xBA34 },
+{ 0xBA35, 0xBA35, 0xBA35 },
+{ 0xBA36, 0xBA36, 0xBA36 },
+{ 0xBA37, 0xBA37, 0xBA37 },
+{ 0xBA38, 0xBA38, 0xBA38 },
+{ 0xBA39, 0xBA39, 0xBA39 },
+{ 0xBA3A, 0xBA3A, 0xBA3A },
+{ 0xBA3B, 0xBA3B, 0xBA3B },
+{ 0xBA3C, 0xBA3C, 0xBA3C },
+{ 0xBA3D, 0xBA3D, 0xBA3D },
+{ 0xBA3E, 0xBA3E, 0xBA3E },
+{ 0xBA3F, 0xBA3F, 0xBA3F },
+{ 0xBA40, 0xBA40, 0xBA40 },
+{ 0xBA41, 0xBA41, 0xBA41 },
+{ 0xBA42, 0xBA42, 0xBA42 },
+{ 0xBA43, 0xBA43, 0xBA43 },
+{ 0xBA44, 0xBA44, 0xBA44 },
+{ 0xBA45, 0xBA45, 0xBA45 },
+{ 0xBA46, 0xBA46, 0xBA46 },
+{ 0xBA47, 0xBA47, 0xBA47 },
+{ 0xBA48, 0xBA48, 0xBA48 },
+{ 0xBA49, 0xBA49, 0xBA49 },
+{ 0xBA4A, 0xBA4A, 0xBA4A },
+{ 0xBA4B, 0xBA4B, 0xBA4B },
+{ 0xBA4C, 0xBA4C, 0xBA4C },
+{ 0xBA4D, 0xBA4D, 0xBA4D },
+{ 0xBA4E, 0xBA4E, 0xBA4E },
+{ 0xBA4F, 0xBA4F, 0xBA4F },
+{ 0xBA50, 0xBA50, 0xBA50 },
+{ 0xBA51, 0xBA51, 0xBA51 },
+{ 0xBA52, 0xBA52, 0xBA52 },
+{ 0xBA53, 0xBA53, 0xBA53 },
+{ 0xBA54, 0xBA54, 0xBA54 },
+{ 0xBA55, 0xBA55, 0xBA55 },
+{ 0xBA56, 0xBA56, 0xBA56 },
+{ 0xBA57, 0xBA57, 0xBA57 },
+{ 0xBA58, 0xBA58, 0xBA58 },
+{ 0xBA59, 0xBA59, 0xBA59 },
+{ 0xBA5A, 0xBA5A, 0xBA5A },
+{ 0xBA5B, 0xBA5B, 0xBA5B },
+{ 0xBA5C, 0xBA5C, 0xBA5C },
+{ 0xBA5D, 0xBA5D, 0xBA5D },
+{ 0xBA5E, 0xBA5E, 0xBA5E },
+{ 0xBA5F, 0xBA5F, 0xBA5F },
+{ 0xBA60, 0xBA60, 0xBA60 },
+{ 0xBA61, 0xBA61, 0xBA61 },
+{ 0xBA62, 0xBA62, 0xBA62 },
+{ 0xBA63, 0xBA63, 0xBA63 },
+{ 0xBA64, 0xBA64, 0xBA64 },
+{ 0xBA65, 0xBA65, 0xBA65 },
+{ 0xBA66, 0xBA66, 0xBA66 },
+{ 0xBA67, 0xBA67, 0xBA67 },
+{ 0xBA68, 0xBA68, 0xBA68 },
+{ 0xBA69, 0xBA69, 0xBA69 },
+{ 0xBA6A, 0xBA6A, 0xBA6A },
+{ 0xBA6B, 0xBA6B, 0xBA6B },
+{ 0xBA6C, 0xBA6C, 0xBA6C },
+{ 0xBA6D, 0xBA6D, 0xBA6D },
+{ 0xBA6E, 0xBA6E, 0xBA6E },
+{ 0xBA6F, 0xBA6F, 0xBA6F },
+{ 0xBA70, 0xBA70, 0xBA70 },
+{ 0xBA71, 0xBA71, 0xBA71 },
+{ 0xBA72, 0xBA72, 0xBA72 },
+{ 0xBA73, 0xBA73, 0xBA73 },
+{ 0xBA74, 0xBA74, 0xBA74 },
+{ 0xBA75, 0xBA75, 0xBA75 },
+{ 0xBA76, 0xBA76, 0xBA76 },
+{ 0xBA77, 0xBA77, 0xBA77 },
+{ 0xBA78, 0xBA78, 0xBA78 },
+{ 0xBA79, 0xBA79, 0xBA79 },
+{ 0xBA7A, 0xBA7A, 0xBA7A },
+{ 0xBA7B, 0xBA7B, 0xBA7B },
+{ 0xBA7C, 0xBA7C, 0xBA7C },
+{ 0xBA7D, 0xBA7D, 0xBA7D },
+{ 0xBA7E, 0xBA7E, 0xBA7E },
+{ 0xBA7F, 0xBA7F, 0xBA7F },
+{ 0xBA80, 0xBA80, 0xBA80 },
+{ 0xBA81, 0xBA81, 0xBA81 },
+{ 0xBA82, 0xBA82, 0xBA82 },
+{ 0xBA83, 0xBA83, 0xBA83 },
+{ 0xBA84, 0xBA84, 0xBA84 },
+{ 0xBA85, 0xBA85, 0xBA85 },
+{ 0xBA86, 0xBA86, 0xBA86 },
+{ 0xBA87, 0xBA87, 0xBA87 },
+{ 0xBA88, 0xBA88, 0xBA88 },
+{ 0xBA89, 0xBA89, 0xBA89 },
+{ 0xBA8A, 0xBA8A, 0xBA8A },
+{ 0xBA8B, 0xBA8B, 0xBA8B },
+{ 0xBA8C, 0xBA8C, 0xBA8C },
+{ 0xBA8D, 0xBA8D, 0xBA8D },
+{ 0xBA8E, 0xBA8E, 0xBA8E },
+{ 0xBA8F, 0xBA8F, 0xBA8F },
+{ 0xBA90, 0xBA90, 0xBA90 },
+{ 0xBA91, 0xBA91, 0xBA91 },
+{ 0xBA92, 0xBA92, 0xBA92 },
+{ 0xBA93, 0xBA93, 0xBA93 },
+{ 0xBA94, 0xBA94, 0xBA94 },
+{ 0xBA95, 0xBA95, 0xBA95 },
+{ 0xBA96, 0xBA96, 0xBA96 },
+{ 0xBA97, 0xBA97, 0xBA97 },
+{ 0xBA98, 0xBA98, 0xBA98 },
+{ 0xBA99, 0xBA99, 0xBA99 },
+{ 0xBA9A, 0xBA9A, 0xBA9A },
+{ 0xBA9B, 0xBA9B, 0xBA9B },
+{ 0xBA9C, 0xBA9C, 0xBA9C },
+{ 0xBA9D, 0xBA9D, 0xBA9D },
+{ 0xBA9E, 0xBA9E, 0xBA9E },
+{ 0xBA9F, 0xBA9F, 0xBA9F },
+{ 0xBAA0, 0xBAA0, 0xBAA0 },
+{ 0xBAA1, 0xBAA1, 0xBAA1 },
+{ 0xBAA2, 0xBAA2, 0xBAA2 },
+{ 0xBAA3, 0xBAA3, 0xBAA3 },
+{ 0xBAA4, 0xBAA4, 0xBAA4 },
+{ 0xBAA5, 0xBAA5, 0xBAA5 },
+{ 0xBAA6, 0xBAA6, 0xBAA6 },
+{ 0xBAA7, 0xBAA7, 0xBAA7 },
+{ 0xBAA8, 0xBAA8, 0xBAA8 },
+{ 0xBAA9, 0xBAA9, 0xBAA9 },
+{ 0xBAAA, 0xBAAA, 0xBAAA },
+{ 0xBAAB, 0xBAAB, 0xBAAB },
+{ 0xBAAC, 0xBAAC, 0xBAAC },
+{ 0xBAAD, 0xBAAD, 0xBAAD },
+{ 0xBAAE, 0xBAAE, 0xBAAE },
+{ 0xBAAF, 0xBAAF, 0xBAAF },
+{ 0xBAB0, 0xBAB0, 0xBAB0 },
+{ 0xBAB1, 0xBAB1, 0xBAB1 },
+{ 0xBAB2, 0xBAB2, 0xBAB2 },
+{ 0xBAB3, 0xBAB3, 0xBAB3 },
+{ 0xBAB4, 0xBAB4, 0xBAB4 },
+{ 0xBAB5, 0xBAB5, 0xBAB5 },
+{ 0xBAB6, 0xBAB6, 0xBAB6 },
+{ 0xBAB7, 0xBAB7, 0xBAB7 },
+{ 0xBAB8, 0xBAB8, 0xBAB8 },
+{ 0xBAB9, 0xBAB9, 0xBAB9 },
+{ 0xBABA, 0xBABA, 0xBABA },
+{ 0xBABB, 0xBABB, 0xBABB },
+{ 0xBABC, 0xBABC, 0xBABC },
+{ 0xBABD, 0xBABD, 0xBABD },
+{ 0xBABE, 0xBABE, 0xBABE },
+{ 0xBABF, 0xBABF, 0xBABF },
+{ 0xBAC0, 0xBAC0, 0xBAC0 },
+{ 0xBAC1, 0xBAC1, 0xBAC1 },
+{ 0xBAC2, 0xBAC2, 0xBAC2 },
+{ 0xBAC3, 0xBAC3, 0xBAC3 },
+{ 0xBAC4, 0xBAC4, 0xBAC4 },
+{ 0xBAC5, 0xBAC5, 0xBAC5 },
+{ 0xBAC6, 0xBAC6, 0xBAC6 },
+{ 0xBAC7, 0xBAC7, 0xBAC7 },
+{ 0xBAC8, 0xBAC8, 0xBAC8 },
+{ 0xBAC9, 0xBAC9, 0xBAC9 },
+{ 0xBACA, 0xBACA, 0xBACA },
+{ 0xBACB, 0xBACB, 0xBACB },
+{ 0xBACC, 0xBACC, 0xBACC },
+{ 0xBACD, 0xBACD, 0xBACD },
+{ 0xBACE, 0xBACE, 0xBACE },
+{ 0xBACF, 0xBACF, 0xBACF },
+{ 0xBAD0, 0xBAD0, 0xBAD0 },
+{ 0xBAD1, 0xBAD1, 0xBAD1 },
+{ 0xBAD2, 0xBAD2, 0xBAD2 },
+{ 0xBAD3, 0xBAD3, 0xBAD3 },
+{ 0xBAD4, 0xBAD4, 0xBAD4 },
+{ 0xBAD5, 0xBAD5, 0xBAD5 },
+{ 0xBAD6, 0xBAD6, 0xBAD6 },
+{ 0xBAD7, 0xBAD7, 0xBAD7 },
+{ 0xBAD8, 0xBAD8, 0xBAD8 },
+{ 0xBAD9, 0xBAD9, 0xBAD9 },
+{ 0xBADA, 0xBADA, 0xBADA },
+{ 0xBADB, 0xBADB, 0xBADB },
+{ 0xBADC, 0xBADC, 0xBADC },
+{ 0xBADD, 0xBADD, 0xBADD },
+{ 0xBADE, 0xBADE, 0xBADE },
+{ 0xBADF, 0xBADF, 0xBADF },
+{ 0xBAE0, 0xBAE0, 0xBAE0 },
+{ 0xBAE1, 0xBAE1, 0xBAE1 },
+{ 0xBAE2, 0xBAE2, 0xBAE2 },
+{ 0xBAE3, 0xBAE3, 0xBAE3 },
+{ 0xBAE4, 0xBAE4, 0xBAE4 },
+{ 0xBAE5, 0xBAE5, 0xBAE5 },
+{ 0xBAE6, 0xBAE6, 0xBAE6 },
+{ 0xBAE7, 0xBAE7, 0xBAE7 },
+{ 0xBAE8, 0xBAE8, 0xBAE8 },
+{ 0xBAE9, 0xBAE9, 0xBAE9 },
+{ 0xBAEA, 0xBAEA, 0xBAEA },
+{ 0xBAEB, 0xBAEB, 0xBAEB },
+{ 0xBAEC, 0xBAEC, 0xBAEC },
+{ 0xBAED, 0xBAED, 0xBAED },
+{ 0xBAEE, 0xBAEE, 0xBAEE },
+{ 0xBAEF, 0xBAEF, 0xBAEF },
+{ 0xBAF0, 0xBAF0, 0xBAF0 },
+{ 0xBAF1, 0xBAF1, 0xBAF1 },
+{ 0xBAF2, 0xBAF2, 0xBAF2 },
+{ 0xBAF3, 0xBAF3, 0xBAF3 },
+{ 0xBAF4, 0xBAF4, 0xBAF4 },
+{ 0xBAF5, 0xBAF5, 0xBAF5 },
+{ 0xBAF6, 0xBAF6, 0xBAF6 },
+{ 0xBAF7, 0xBAF7, 0xBAF7 },
+{ 0xBAF8, 0xBAF8, 0xBAF8 },
+{ 0xBAF9, 0xBAF9, 0xBAF9 },
+{ 0xBAFA, 0xBAFA, 0xBAFA },
+{ 0xBAFB, 0xBAFB, 0xBAFB },
+{ 0xBAFC, 0xBAFC, 0xBAFC },
+{ 0xBAFD, 0xBAFD, 0xBAFD },
+{ 0xBAFE, 0xBAFE, 0xBAFE },
+{ 0xBAFF, 0xBAFF, 0xBAFF },
+{ 0xBB00, 0xBB00, 0xBB00 },
+{ 0xBB01, 0xBB01, 0xBB01 },
+{ 0xBB02, 0xBB02, 0xBB02 },
+{ 0xBB03, 0xBB03, 0xBB03 },
+{ 0xBB04, 0xBB04, 0xBB04 },
+{ 0xBB05, 0xBB05, 0xBB05 },
+{ 0xBB06, 0xBB06, 0xBB06 },
+{ 0xBB07, 0xBB07, 0xBB07 },
+{ 0xBB08, 0xBB08, 0xBB08 },
+{ 0xBB09, 0xBB09, 0xBB09 },
+{ 0xBB0A, 0xBB0A, 0xBB0A },
+{ 0xBB0B, 0xBB0B, 0xBB0B },
+{ 0xBB0C, 0xBB0C, 0xBB0C },
+{ 0xBB0D, 0xBB0D, 0xBB0D },
+{ 0xBB0E, 0xBB0E, 0xBB0E },
+{ 0xBB0F, 0xBB0F, 0xBB0F },
+{ 0xBB10, 0xBB10, 0xBB10 },
+{ 0xBB11, 0xBB11, 0xBB11 },
+{ 0xBB12, 0xBB12, 0xBB12 },
+{ 0xBB13, 0xBB13, 0xBB13 },
+{ 0xBB14, 0xBB14, 0xBB14 },
+{ 0xBB15, 0xBB15, 0xBB15 },
+{ 0xBB16, 0xBB16, 0xBB16 },
+{ 0xBB17, 0xBB17, 0xBB17 },
+{ 0xBB18, 0xBB18, 0xBB18 },
+{ 0xBB19, 0xBB19, 0xBB19 },
+{ 0xBB1A, 0xBB1A, 0xBB1A },
+{ 0xBB1B, 0xBB1B, 0xBB1B },
+{ 0xBB1C, 0xBB1C, 0xBB1C },
+{ 0xBB1D, 0xBB1D, 0xBB1D },
+{ 0xBB1E, 0xBB1E, 0xBB1E },
+{ 0xBB1F, 0xBB1F, 0xBB1F },
+{ 0xBB20, 0xBB20, 0xBB20 },
+{ 0xBB21, 0xBB21, 0xBB21 },
+{ 0xBB22, 0xBB22, 0xBB22 },
+{ 0xBB23, 0xBB23, 0xBB23 },
+{ 0xBB24, 0xBB24, 0xBB24 },
+{ 0xBB25, 0xBB25, 0xBB25 },
+{ 0xBB26, 0xBB26, 0xBB26 },
+{ 0xBB27, 0xBB27, 0xBB27 },
+{ 0xBB28, 0xBB28, 0xBB28 },
+{ 0xBB29, 0xBB29, 0xBB29 },
+{ 0xBB2A, 0xBB2A, 0xBB2A },
+{ 0xBB2B, 0xBB2B, 0xBB2B },
+{ 0xBB2C, 0xBB2C, 0xBB2C },
+{ 0xBB2D, 0xBB2D, 0xBB2D },
+{ 0xBB2E, 0xBB2E, 0xBB2E },
+{ 0xBB2F, 0xBB2F, 0xBB2F },
+{ 0xBB30, 0xBB30, 0xBB30 },
+{ 0xBB31, 0xBB31, 0xBB31 },
+{ 0xBB32, 0xBB32, 0xBB32 },
+{ 0xBB33, 0xBB33, 0xBB33 },
+{ 0xBB34, 0xBB34, 0xBB34 },
+{ 0xBB35, 0xBB35, 0xBB35 },
+{ 0xBB36, 0xBB36, 0xBB36 },
+{ 0xBB37, 0xBB37, 0xBB37 },
+{ 0xBB38, 0xBB38, 0xBB38 },
+{ 0xBB39, 0xBB39, 0xBB39 },
+{ 0xBB3A, 0xBB3A, 0xBB3A },
+{ 0xBB3B, 0xBB3B, 0xBB3B },
+{ 0xBB3C, 0xBB3C, 0xBB3C },
+{ 0xBB3D, 0xBB3D, 0xBB3D },
+{ 0xBB3E, 0xBB3E, 0xBB3E },
+{ 0xBB3F, 0xBB3F, 0xBB3F },
+{ 0xBB40, 0xBB40, 0xBB40 },
+{ 0xBB41, 0xBB41, 0xBB41 },
+{ 0xBB42, 0xBB42, 0xBB42 },
+{ 0xBB43, 0xBB43, 0xBB43 },
+{ 0xBB44, 0xBB44, 0xBB44 },
+{ 0xBB45, 0xBB45, 0xBB45 },
+{ 0xBB46, 0xBB46, 0xBB46 },
+{ 0xBB47, 0xBB47, 0xBB47 },
+{ 0xBB48, 0xBB48, 0xBB48 },
+{ 0xBB49, 0xBB49, 0xBB49 },
+{ 0xBB4A, 0xBB4A, 0xBB4A },
+{ 0xBB4B, 0xBB4B, 0xBB4B },
+{ 0xBB4C, 0xBB4C, 0xBB4C },
+{ 0xBB4D, 0xBB4D, 0xBB4D },
+{ 0xBB4E, 0xBB4E, 0xBB4E },
+{ 0xBB4F, 0xBB4F, 0xBB4F },
+{ 0xBB50, 0xBB50, 0xBB50 },
+{ 0xBB51, 0xBB51, 0xBB51 },
+{ 0xBB52, 0xBB52, 0xBB52 },
+{ 0xBB53, 0xBB53, 0xBB53 },
+{ 0xBB54, 0xBB54, 0xBB54 },
+{ 0xBB55, 0xBB55, 0xBB55 },
+{ 0xBB56, 0xBB56, 0xBB56 },
+{ 0xBB57, 0xBB57, 0xBB57 },
+{ 0xBB58, 0xBB58, 0xBB58 },
+{ 0xBB59, 0xBB59, 0xBB59 },
+{ 0xBB5A, 0xBB5A, 0xBB5A },
+{ 0xBB5B, 0xBB5B, 0xBB5B },
+{ 0xBB5C, 0xBB5C, 0xBB5C },
+{ 0xBB5D, 0xBB5D, 0xBB5D },
+{ 0xBB5E, 0xBB5E, 0xBB5E },
+{ 0xBB5F, 0xBB5F, 0xBB5F },
+{ 0xBB60, 0xBB60, 0xBB60 },
+{ 0xBB61, 0xBB61, 0xBB61 },
+{ 0xBB62, 0xBB62, 0xBB62 },
+{ 0xBB63, 0xBB63, 0xBB63 },
+{ 0xBB64, 0xBB64, 0xBB64 },
+{ 0xBB65, 0xBB65, 0xBB65 },
+{ 0xBB66, 0xBB66, 0xBB66 },
+{ 0xBB67, 0xBB67, 0xBB67 },
+{ 0xBB68, 0xBB68, 0xBB68 },
+{ 0xBB69, 0xBB69, 0xBB69 },
+{ 0xBB6A, 0xBB6A, 0xBB6A },
+{ 0xBB6B, 0xBB6B, 0xBB6B },
+{ 0xBB6C, 0xBB6C, 0xBB6C },
+{ 0xBB6D, 0xBB6D, 0xBB6D },
+{ 0xBB6E, 0xBB6E, 0xBB6E },
+{ 0xBB6F, 0xBB6F, 0xBB6F },
+{ 0xBB70, 0xBB70, 0xBB70 },
+{ 0xBB71, 0xBB71, 0xBB71 },
+{ 0xBB72, 0xBB72, 0xBB72 },
+{ 0xBB73, 0xBB73, 0xBB73 },
+{ 0xBB74, 0xBB74, 0xBB74 },
+{ 0xBB75, 0xBB75, 0xBB75 },
+{ 0xBB76, 0xBB76, 0xBB76 },
+{ 0xBB77, 0xBB77, 0xBB77 },
+{ 0xBB78, 0xBB78, 0xBB78 },
+{ 0xBB79, 0xBB79, 0xBB79 },
+{ 0xBB7A, 0xBB7A, 0xBB7A },
+{ 0xBB7B, 0xBB7B, 0xBB7B },
+{ 0xBB7C, 0xBB7C, 0xBB7C },
+{ 0xBB7D, 0xBB7D, 0xBB7D },
+{ 0xBB7E, 0xBB7E, 0xBB7E },
+{ 0xBB7F, 0xBB7F, 0xBB7F },
+{ 0xBB80, 0xBB80, 0xBB80 },
+{ 0xBB81, 0xBB81, 0xBB81 },
+{ 0xBB82, 0xBB82, 0xBB82 },
+{ 0xBB83, 0xBB83, 0xBB83 },
+{ 0xBB84, 0xBB84, 0xBB84 },
+{ 0xBB85, 0xBB85, 0xBB85 },
+{ 0xBB86, 0xBB86, 0xBB86 },
+{ 0xBB87, 0xBB87, 0xBB87 },
+{ 0xBB88, 0xBB88, 0xBB88 },
+{ 0xBB89, 0xBB89, 0xBB89 },
+{ 0xBB8A, 0xBB8A, 0xBB8A },
+{ 0xBB8B, 0xBB8B, 0xBB8B },
+{ 0xBB8C, 0xBB8C, 0xBB8C },
+{ 0xBB8D, 0xBB8D, 0xBB8D },
+{ 0xBB8E, 0xBB8E, 0xBB8E },
+{ 0xBB8F, 0xBB8F, 0xBB8F },
+{ 0xBB90, 0xBB90, 0xBB90 },
+{ 0xBB91, 0xBB91, 0xBB91 },
+{ 0xBB92, 0xBB92, 0xBB92 },
+{ 0xBB93, 0xBB93, 0xBB93 },
+{ 0xBB94, 0xBB94, 0xBB94 },
+{ 0xBB95, 0xBB95, 0xBB95 },
+{ 0xBB96, 0xBB96, 0xBB96 },
+{ 0xBB97, 0xBB97, 0xBB97 },
+{ 0xBB98, 0xBB98, 0xBB98 },
+{ 0xBB99, 0xBB99, 0xBB99 },
+{ 0xBB9A, 0xBB9A, 0xBB9A },
+{ 0xBB9B, 0xBB9B, 0xBB9B },
+{ 0xBB9C, 0xBB9C, 0xBB9C },
+{ 0xBB9D, 0xBB9D, 0xBB9D },
+{ 0xBB9E, 0xBB9E, 0xBB9E },
+{ 0xBB9F, 0xBB9F, 0xBB9F },
+{ 0xBBA0, 0xBBA0, 0xBBA0 },
+{ 0xBBA1, 0xBBA1, 0xBBA1 },
+{ 0xBBA2, 0xBBA2, 0xBBA2 },
+{ 0xBBA3, 0xBBA3, 0xBBA3 },
+{ 0xBBA4, 0xBBA4, 0xBBA4 },
+{ 0xBBA5, 0xBBA5, 0xBBA5 },
+{ 0xBBA6, 0xBBA6, 0xBBA6 },
+{ 0xBBA7, 0xBBA7, 0xBBA7 },
+{ 0xBBA8, 0xBBA8, 0xBBA8 },
+{ 0xBBA9, 0xBBA9, 0xBBA9 },
+{ 0xBBAA, 0xBBAA, 0xBBAA },
+{ 0xBBAB, 0xBBAB, 0xBBAB },
+{ 0xBBAC, 0xBBAC, 0xBBAC },
+{ 0xBBAD, 0xBBAD, 0xBBAD },
+{ 0xBBAE, 0xBBAE, 0xBBAE },
+{ 0xBBAF, 0xBBAF, 0xBBAF },
+{ 0xBBB0, 0xBBB0, 0xBBB0 },
+{ 0xBBB1, 0xBBB1, 0xBBB1 },
+{ 0xBBB2, 0xBBB2, 0xBBB2 },
+{ 0xBBB3, 0xBBB3, 0xBBB3 },
+{ 0xBBB4, 0xBBB4, 0xBBB4 },
+{ 0xBBB5, 0xBBB5, 0xBBB5 },
+{ 0xBBB6, 0xBBB6, 0xBBB6 },
+{ 0xBBB7, 0xBBB7, 0xBBB7 },
+{ 0xBBB8, 0xBBB8, 0xBBB8 },
+{ 0xBBB9, 0xBBB9, 0xBBB9 },
+{ 0xBBBA, 0xBBBA, 0xBBBA },
+{ 0xBBBB, 0xBBBB, 0xBBBB },
+{ 0xBBBC, 0xBBBC, 0xBBBC },
+{ 0xBBBD, 0xBBBD, 0xBBBD },
+{ 0xBBBE, 0xBBBE, 0xBBBE },
+{ 0xBBBF, 0xBBBF, 0xBBBF },
+{ 0xBBC0, 0xBBC0, 0xBBC0 },
+{ 0xBBC1, 0xBBC1, 0xBBC1 },
+{ 0xBBC2, 0xBBC2, 0xBBC2 },
+{ 0xBBC3, 0xBBC3, 0xBBC3 },
+{ 0xBBC4, 0xBBC4, 0xBBC4 },
+{ 0xBBC5, 0xBBC5, 0xBBC5 },
+{ 0xBBC6, 0xBBC6, 0xBBC6 },
+{ 0xBBC7, 0xBBC7, 0xBBC7 },
+{ 0xBBC8, 0xBBC8, 0xBBC8 },
+{ 0xBBC9, 0xBBC9, 0xBBC9 },
+{ 0xBBCA, 0xBBCA, 0xBBCA },
+{ 0xBBCB, 0xBBCB, 0xBBCB },
+{ 0xBBCC, 0xBBCC, 0xBBCC },
+{ 0xBBCD, 0xBBCD, 0xBBCD },
+{ 0xBBCE, 0xBBCE, 0xBBCE },
+{ 0xBBCF, 0xBBCF, 0xBBCF },
+{ 0xBBD0, 0xBBD0, 0xBBD0 },
+{ 0xBBD1, 0xBBD1, 0xBBD1 },
+{ 0xBBD2, 0xBBD2, 0xBBD2 },
+{ 0xBBD3, 0xBBD3, 0xBBD3 },
+{ 0xBBD4, 0xBBD4, 0xBBD4 },
+{ 0xBBD5, 0xBBD5, 0xBBD5 },
+{ 0xBBD6, 0xBBD6, 0xBBD6 },
+{ 0xBBD7, 0xBBD7, 0xBBD7 },
+{ 0xBBD8, 0xBBD8, 0xBBD8 },
+{ 0xBBD9, 0xBBD9, 0xBBD9 },
+{ 0xBBDA, 0xBBDA, 0xBBDA },
+{ 0xBBDB, 0xBBDB, 0xBBDB },
+{ 0xBBDC, 0xBBDC, 0xBBDC },
+{ 0xBBDD, 0xBBDD, 0xBBDD },
+{ 0xBBDE, 0xBBDE, 0xBBDE },
+{ 0xBBDF, 0xBBDF, 0xBBDF },
+{ 0xBBE0, 0xBBE0, 0xBBE0 },
+{ 0xBBE1, 0xBBE1, 0xBBE1 },
+{ 0xBBE2, 0xBBE2, 0xBBE2 },
+{ 0xBBE3, 0xBBE3, 0xBBE3 },
+{ 0xBBE4, 0xBBE4, 0xBBE4 },
+{ 0xBBE5, 0xBBE5, 0xBBE5 },
+{ 0xBBE6, 0xBBE6, 0xBBE6 },
+{ 0xBBE7, 0xBBE7, 0xBBE7 },
+{ 0xBBE8, 0xBBE8, 0xBBE8 },
+{ 0xBBE9, 0xBBE9, 0xBBE9 },
+{ 0xBBEA, 0xBBEA, 0xBBEA },
+{ 0xBBEB, 0xBBEB, 0xBBEB },
+{ 0xBBEC, 0xBBEC, 0xBBEC },
+{ 0xBBED, 0xBBED, 0xBBED },
+{ 0xBBEE, 0xBBEE, 0xBBEE },
+{ 0xBBEF, 0xBBEF, 0xBBEF },
+{ 0xBBF0, 0xBBF0, 0xBBF0 },
+{ 0xBBF1, 0xBBF1, 0xBBF1 },
+{ 0xBBF2, 0xBBF2, 0xBBF2 },
+{ 0xBBF3, 0xBBF3, 0xBBF3 },
+{ 0xBBF4, 0xBBF4, 0xBBF4 },
+{ 0xBBF5, 0xBBF5, 0xBBF5 },
+{ 0xBBF6, 0xBBF6, 0xBBF6 },
+{ 0xBBF7, 0xBBF7, 0xBBF7 },
+{ 0xBBF8, 0xBBF8, 0xBBF8 },
+{ 0xBBF9, 0xBBF9, 0xBBF9 },
+{ 0xBBFA, 0xBBFA, 0xBBFA },
+{ 0xBBFB, 0xBBFB, 0xBBFB },
+{ 0xBBFC, 0xBBFC, 0xBBFC },
+{ 0xBBFD, 0xBBFD, 0xBBFD },
+{ 0xBBFE, 0xBBFE, 0xBBFE },
+{ 0xBBFF, 0xBBFF, 0xBBFF },
+{ 0xBC00, 0xBC00, 0xBC00 },
+{ 0xBC01, 0xBC01, 0xBC01 },
+{ 0xBC02, 0xBC02, 0xBC02 },
+{ 0xBC03, 0xBC03, 0xBC03 },
+{ 0xBC04, 0xBC04, 0xBC04 },
+{ 0xBC05, 0xBC05, 0xBC05 },
+{ 0xBC06, 0xBC06, 0xBC06 },
+{ 0xBC07, 0xBC07, 0xBC07 },
+{ 0xBC08, 0xBC08, 0xBC08 },
+{ 0xBC09, 0xBC09, 0xBC09 },
+{ 0xBC0A, 0xBC0A, 0xBC0A },
+{ 0xBC0B, 0xBC0B, 0xBC0B },
+{ 0xBC0C, 0xBC0C, 0xBC0C },
+{ 0xBC0D, 0xBC0D, 0xBC0D },
+{ 0xBC0E, 0xBC0E, 0xBC0E },
+{ 0xBC0F, 0xBC0F, 0xBC0F },
+{ 0xBC10, 0xBC10, 0xBC10 },
+{ 0xBC11, 0xBC11, 0xBC11 },
+{ 0xBC12, 0xBC12, 0xBC12 },
+{ 0xBC13, 0xBC13, 0xBC13 },
+{ 0xBC14, 0xBC14, 0xBC14 },
+{ 0xBC15, 0xBC15, 0xBC15 },
+{ 0xBC16, 0xBC16, 0xBC16 },
+{ 0xBC17, 0xBC17, 0xBC17 },
+{ 0xBC18, 0xBC18, 0xBC18 },
+{ 0xBC19, 0xBC19, 0xBC19 },
+{ 0xBC1A, 0xBC1A, 0xBC1A },
+{ 0xBC1B, 0xBC1B, 0xBC1B },
+{ 0xBC1C, 0xBC1C, 0xBC1C },
+{ 0xBC1D, 0xBC1D, 0xBC1D },
+{ 0xBC1E, 0xBC1E, 0xBC1E },
+{ 0xBC1F, 0xBC1F, 0xBC1F },
+{ 0xBC20, 0xBC20, 0xBC20 },
+{ 0xBC21, 0xBC21, 0xBC21 },
+{ 0xBC22, 0xBC22, 0xBC22 },
+{ 0xBC23, 0xBC23, 0xBC23 },
+{ 0xBC24, 0xBC24, 0xBC24 },
+{ 0xBC25, 0xBC25, 0xBC25 },
+{ 0xBC26, 0xBC26, 0xBC26 },
+{ 0xBC27, 0xBC27, 0xBC27 },
+{ 0xBC28, 0xBC28, 0xBC28 },
+{ 0xBC29, 0xBC29, 0xBC29 },
+{ 0xBC2A, 0xBC2A, 0xBC2A },
+{ 0xBC2B, 0xBC2B, 0xBC2B },
+{ 0xBC2C, 0xBC2C, 0xBC2C },
+{ 0xBC2D, 0xBC2D, 0xBC2D },
+{ 0xBC2E, 0xBC2E, 0xBC2E },
+{ 0xBC2F, 0xBC2F, 0xBC2F },
+{ 0xBC30, 0xBC30, 0xBC30 },
+{ 0xBC31, 0xBC31, 0xBC31 },
+{ 0xBC32, 0xBC32, 0xBC32 },
+{ 0xBC33, 0xBC33, 0xBC33 },
+{ 0xBC34, 0xBC34, 0xBC34 },
+{ 0xBC35, 0xBC35, 0xBC35 },
+{ 0xBC36, 0xBC36, 0xBC36 },
+{ 0xBC37, 0xBC37, 0xBC37 },
+{ 0xBC38, 0xBC38, 0xBC38 },
+{ 0xBC39, 0xBC39, 0xBC39 },
+{ 0xBC3A, 0xBC3A, 0xBC3A },
+{ 0xBC3B, 0xBC3B, 0xBC3B },
+{ 0xBC3C, 0xBC3C, 0xBC3C },
+{ 0xBC3D, 0xBC3D, 0xBC3D },
+{ 0xBC3E, 0xBC3E, 0xBC3E },
+{ 0xBC3F, 0xBC3F, 0xBC3F },
+{ 0xBC40, 0xBC40, 0xBC40 },
+{ 0xBC41, 0xBC41, 0xBC41 },
+{ 0xBC42, 0xBC42, 0xBC42 },
+{ 0xBC43, 0xBC43, 0xBC43 },
+{ 0xBC44, 0xBC44, 0xBC44 },
+{ 0xBC45, 0xBC45, 0xBC45 },
+{ 0xBC46, 0xBC46, 0xBC46 },
+{ 0xBC47, 0xBC47, 0xBC47 },
+{ 0xBC48, 0xBC48, 0xBC48 },
+{ 0xBC49, 0xBC49, 0xBC49 },
+{ 0xBC4A, 0xBC4A, 0xBC4A },
+{ 0xBC4B, 0xBC4B, 0xBC4B },
+{ 0xBC4C, 0xBC4C, 0xBC4C },
+{ 0xBC4D, 0xBC4D, 0xBC4D },
+{ 0xBC4E, 0xBC4E, 0xBC4E },
+{ 0xBC4F, 0xBC4F, 0xBC4F },
+{ 0xBC50, 0xBC50, 0xBC50 },
+{ 0xBC51, 0xBC51, 0xBC51 },
+{ 0xBC52, 0xBC52, 0xBC52 },
+{ 0xBC53, 0xBC53, 0xBC53 },
+{ 0xBC54, 0xBC54, 0xBC54 },
+{ 0xBC55, 0xBC55, 0xBC55 },
+{ 0xBC56, 0xBC56, 0xBC56 },
+{ 0xBC57, 0xBC57, 0xBC57 },
+{ 0xBC58, 0xBC58, 0xBC58 },
+{ 0xBC59, 0xBC59, 0xBC59 },
+{ 0xBC5A, 0xBC5A, 0xBC5A },
+{ 0xBC5B, 0xBC5B, 0xBC5B },
+{ 0xBC5C, 0xBC5C, 0xBC5C },
+{ 0xBC5D, 0xBC5D, 0xBC5D },
+{ 0xBC5E, 0xBC5E, 0xBC5E },
+{ 0xBC5F, 0xBC5F, 0xBC5F },
+{ 0xBC60, 0xBC60, 0xBC60 },
+{ 0xBC61, 0xBC61, 0xBC61 },
+{ 0xBC62, 0xBC62, 0xBC62 },
+{ 0xBC63, 0xBC63, 0xBC63 },
+{ 0xBC64, 0xBC64, 0xBC64 },
+{ 0xBC65, 0xBC65, 0xBC65 },
+{ 0xBC66, 0xBC66, 0xBC66 },
+{ 0xBC67, 0xBC67, 0xBC67 },
+{ 0xBC68, 0xBC68, 0xBC68 },
+{ 0xBC69, 0xBC69, 0xBC69 },
+{ 0xBC6A, 0xBC6A, 0xBC6A },
+{ 0xBC6B, 0xBC6B, 0xBC6B },
+{ 0xBC6C, 0xBC6C, 0xBC6C },
+{ 0xBC6D, 0xBC6D, 0xBC6D },
+{ 0xBC6E, 0xBC6E, 0xBC6E },
+{ 0xBC6F, 0xBC6F, 0xBC6F },
+{ 0xBC70, 0xBC70, 0xBC70 },
+{ 0xBC71, 0xBC71, 0xBC71 },
+{ 0xBC72, 0xBC72, 0xBC72 },
+{ 0xBC73, 0xBC73, 0xBC73 },
+{ 0xBC74, 0xBC74, 0xBC74 },
+{ 0xBC75, 0xBC75, 0xBC75 },
+{ 0xBC76, 0xBC76, 0xBC76 },
+{ 0xBC77, 0xBC77, 0xBC77 },
+{ 0xBC78, 0xBC78, 0xBC78 },
+{ 0xBC79, 0xBC79, 0xBC79 },
+{ 0xBC7A, 0xBC7A, 0xBC7A },
+{ 0xBC7B, 0xBC7B, 0xBC7B },
+{ 0xBC7C, 0xBC7C, 0xBC7C },
+{ 0xBC7D, 0xBC7D, 0xBC7D },
+{ 0xBC7E, 0xBC7E, 0xBC7E },
+{ 0xBC7F, 0xBC7F, 0xBC7F },
+{ 0xBC80, 0xBC80, 0xBC80 },
+{ 0xBC81, 0xBC81, 0xBC81 },
+{ 0xBC82, 0xBC82, 0xBC82 },
+{ 0xBC83, 0xBC83, 0xBC83 },
+{ 0xBC84, 0xBC84, 0xBC84 },
+{ 0xBC85, 0xBC85, 0xBC85 },
+{ 0xBC86, 0xBC86, 0xBC86 },
+{ 0xBC87, 0xBC87, 0xBC87 },
+{ 0xBC88, 0xBC88, 0xBC88 },
+{ 0xBC89, 0xBC89, 0xBC89 },
+{ 0xBC8A, 0xBC8A, 0xBC8A },
+{ 0xBC8B, 0xBC8B, 0xBC8B },
+{ 0xBC8C, 0xBC8C, 0xBC8C },
+{ 0xBC8D, 0xBC8D, 0xBC8D },
+{ 0xBC8E, 0xBC8E, 0xBC8E },
+{ 0xBC8F, 0xBC8F, 0xBC8F },
+{ 0xBC90, 0xBC90, 0xBC90 },
+{ 0xBC91, 0xBC91, 0xBC91 },
+{ 0xBC92, 0xBC92, 0xBC92 },
+{ 0xBC93, 0xBC93, 0xBC93 },
+{ 0xBC94, 0xBC94, 0xBC94 },
+{ 0xBC95, 0xBC95, 0xBC95 },
+{ 0xBC96, 0xBC96, 0xBC96 },
+{ 0xBC97, 0xBC97, 0xBC97 },
+{ 0xBC98, 0xBC98, 0xBC98 },
+{ 0xBC99, 0xBC99, 0xBC99 },
+{ 0xBC9A, 0xBC9A, 0xBC9A },
+{ 0xBC9B, 0xBC9B, 0xBC9B },
+{ 0xBC9C, 0xBC9C, 0xBC9C },
+{ 0xBC9D, 0xBC9D, 0xBC9D },
+{ 0xBC9E, 0xBC9E, 0xBC9E },
+{ 0xBC9F, 0xBC9F, 0xBC9F },
+{ 0xBCA0, 0xBCA0, 0xBCA0 },
+{ 0xBCA1, 0xBCA1, 0xBCA1 },
+{ 0xBCA2, 0xBCA2, 0xBCA2 },
+{ 0xBCA3, 0xBCA3, 0xBCA3 },
+{ 0xBCA4, 0xBCA4, 0xBCA4 },
+{ 0xBCA5, 0xBCA5, 0xBCA5 },
+{ 0xBCA6, 0xBCA6, 0xBCA6 },
+{ 0xBCA7, 0xBCA7, 0xBCA7 },
+{ 0xBCA8, 0xBCA8, 0xBCA8 },
+{ 0xBCA9, 0xBCA9, 0xBCA9 },
+{ 0xBCAA, 0xBCAA, 0xBCAA },
+{ 0xBCAB, 0xBCAB, 0xBCAB },
+{ 0xBCAC, 0xBCAC, 0xBCAC },
+{ 0xBCAD, 0xBCAD, 0xBCAD },
+{ 0xBCAE, 0xBCAE, 0xBCAE },
+{ 0xBCAF, 0xBCAF, 0xBCAF },
+{ 0xBCB0, 0xBCB0, 0xBCB0 },
+{ 0xBCB1, 0xBCB1, 0xBCB1 },
+{ 0xBCB2, 0xBCB2, 0xBCB2 },
+{ 0xBCB3, 0xBCB3, 0xBCB3 },
+{ 0xBCB4, 0xBCB4, 0xBCB4 },
+{ 0xBCB5, 0xBCB5, 0xBCB5 },
+{ 0xBCB6, 0xBCB6, 0xBCB6 },
+{ 0xBCB7, 0xBCB7, 0xBCB7 },
+{ 0xBCB8, 0xBCB8, 0xBCB8 },
+{ 0xBCB9, 0xBCB9, 0xBCB9 },
+{ 0xBCBA, 0xBCBA, 0xBCBA },
+{ 0xBCBB, 0xBCBB, 0xBCBB },
+{ 0xBCBC, 0xBCBC, 0xBCBC },
+{ 0xBCBD, 0xBCBD, 0xBCBD },
+{ 0xBCBE, 0xBCBE, 0xBCBE },
+{ 0xBCBF, 0xBCBF, 0xBCBF },
+{ 0xBCC0, 0xBCC0, 0xBCC0 },
+{ 0xBCC1, 0xBCC1, 0xBCC1 },
+{ 0xBCC2, 0xBCC2, 0xBCC2 },
+{ 0xBCC3, 0xBCC3, 0xBCC3 },
+{ 0xBCC4, 0xBCC4, 0xBCC4 },
+{ 0xBCC5, 0xBCC5, 0xBCC5 },
+{ 0xBCC6, 0xBCC6, 0xBCC6 },
+{ 0xBCC7, 0xBCC7, 0xBCC7 },
+{ 0xBCC8, 0xBCC8, 0xBCC8 },
+{ 0xBCC9, 0xBCC9, 0xBCC9 },
+{ 0xBCCA, 0xBCCA, 0xBCCA },
+{ 0xBCCB, 0xBCCB, 0xBCCB },
+{ 0xBCCC, 0xBCCC, 0xBCCC },
+{ 0xBCCD, 0xBCCD, 0xBCCD },
+{ 0xBCCE, 0xBCCE, 0xBCCE },
+{ 0xBCCF, 0xBCCF, 0xBCCF },
+{ 0xBCD0, 0xBCD0, 0xBCD0 },
+{ 0xBCD1, 0xBCD1, 0xBCD1 },
+{ 0xBCD2, 0xBCD2, 0xBCD2 },
+{ 0xBCD3, 0xBCD3, 0xBCD3 },
+{ 0xBCD4, 0xBCD4, 0xBCD4 },
+{ 0xBCD5, 0xBCD5, 0xBCD5 },
+{ 0xBCD6, 0xBCD6, 0xBCD6 },
+{ 0xBCD7, 0xBCD7, 0xBCD7 },
+{ 0xBCD8, 0xBCD8, 0xBCD8 },
+{ 0xBCD9, 0xBCD9, 0xBCD9 },
+{ 0xBCDA, 0xBCDA, 0xBCDA },
+{ 0xBCDB, 0xBCDB, 0xBCDB },
+{ 0xBCDC, 0xBCDC, 0xBCDC },
+{ 0xBCDD, 0xBCDD, 0xBCDD },
+{ 0xBCDE, 0xBCDE, 0xBCDE },
+{ 0xBCDF, 0xBCDF, 0xBCDF },
+{ 0xBCE0, 0xBCE0, 0xBCE0 },
+{ 0xBCE1, 0xBCE1, 0xBCE1 },
+{ 0xBCE2, 0xBCE2, 0xBCE2 },
+{ 0xBCE3, 0xBCE3, 0xBCE3 },
+{ 0xBCE4, 0xBCE4, 0xBCE4 },
+{ 0xBCE5, 0xBCE5, 0xBCE5 },
+{ 0xBCE6, 0xBCE6, 0xBCE6 },
+{ 0xBCE7, 0xBCE7, 0xBCE7 },
+{ 0xBCE8, 0xBCE8, 0xBCE8 },
+{ 0xBCE9, 0xBCE9, 0xBCE9 },
+{ 0xBCEA, 0xBCEA, 0xBCEA },
+{ 0xBCEB, 0xBCEB, 0xBCEB },
+{ 0xBCEC, 0xBCEC, 0xBCEC },
+{ 0xBCED, 0xBCED, 0xBCED },
+{ 0xBCEE, 0xBCEE, 0xBCEE },
+{ 0xBCEF, 0xBCEF, 0xBCEF },
+{ 0xBCF0, 0xBCF0, 0xBCF0 },
+{ 0xBCF1, 0xBCF1, 0xBCF1 },
+{ 0xBCF2, 0xBCF2, 0xBCF2 },
+{ 0xBCF3, 0xBCF3, 0xBCF3 },
+{ 0xBCF4, 0xBCF4, 0xBCF4 },
+{ 0xBCF5, 0xBCF5, 0xBCF5 },
+{ 0xBCF6, 0xBCF6, 0xBCF6 },
+{ 0xBCF7, 0xBCF7, 0xBCF7 },
+{ 0xBCF8, 0xBCF8, 0xBCF8 },
+{ 0xBCF9, 0xBCF9, 0xBCF9 },
+{ 0xBCFA, 0xBCFA, 0xBCFA },
+{ 0xBCFB, 0xBCFB, 0xBCFB },
+{ 0xBCFC, 0xBCFC, 0xBCFC },
+{ 0xBCFD, 0xBCFD, 0xBCFD },
+{ 0xBCFE, 0xBCFE, 0xBCFE },
+{ 0xBCFF, 0xBCFF, 0xBCFF },
+{ 0xBD00, 0xBD00, 0xBD00 },
+{ 0xBD01, 0xBD01, 0xBD01 },
+{ 0xBD02, 0xBD02, 0xBD02 },
+{ 0xBD03, 0xBD03, 0xBD03 },
+{ 0xBD04, 0xBD04, 0xBD04 },
+{ 0xBD05, 0xBD05, 0xBD05 },
+{ 0xBD06, 0xBD06, 0xBD06 },
+{ 0xBD07, 0xBD07, 0xBD07 },
+{ 0xBD08, 0xBD08, 0xBD08 },
+{ 0xBD09, 0xBD09, 0xBD09 },
+{ 0xBD0A, 0xBD0A, 0xBD0A },
+{ 0xBD0B, 0xBD0B, 0xBD0B },
+{ 0xBD0C, 0xBD0C, 0xBD0C },
+{ 0xBD0D, 0xBD0D, 0xBD0D },
+{ 0xBD0E, 0xBD0E, 0xBD0E },
+{ 0xBD0F, 0xBD0F, 0xBD0F },
+{ 0xBD10, 0xBD10, 0xBD10 },
+{ 0xBD11, 0xBD11, 0xBD11 },
+{ 0xBD12, 0xBD12, 0xBD12 },
+{ 0xBD13, 0xBD13, 0xBD13 },
+{ 0xBD14, 0xBD14, 0xBD14 },
+{ 0xBD15, 0xBD15, 0xBD15 },
+{ 0xBD16, 0xBD16, 0xBD16 },
+{ 0xBD17, 0xBD17, 0xBD17 },
+{ 0xBD18, 0xBD18, 0xBD18 },
+{ 0xBD19, 0xBD19, 0xBD19 },
+{ 0xBD1A, 0xBD1A, 0xBD1A },
+{ 0xBD1B, 0xBD1B, 0xBD1B },
+{ 0xBD1C, 0xBD1C, 0xBD1C },
+{ 0xBD1D, 0xBD1D, 0xBD1D },
+{ 0xBD1E, 0xBD1E, 0xBD1E },
+{ 0xBD1F, 0xBD1F, 0xBD1F },
+{ 0xBD20, 0xBD20, 0xBD20 },
+{ 0xBD21, 0xBD21, 0xBD21 },
+{ 0xBD22, 0xBD22, 0xBD22 },
+{ 0xBD23, 0xBD23, 0xBD23 },
+{ 0xBD24, 0xBD24, 0xBD24 },
+{ 0xBD25, 0xBD25, 0xBD25 },
+{ 0xBD26, 0xBD26, 0xBD26 },
+{ 0xBD27, 0xBD27, 0xBD27 },
+{ 0xBD28, 0xBD28, 0xBD28 },
+{ 0xBD29, 0xBD29, 0xBD29 },
+{ 0xBD2A, 0xBD2A, 0xBD2A },
+{ 0xBD2B, 0xBD2B, 0xBD2B },
+{ 0xBD2C, 0xBD2C, 0xBD2C },
+{ 0xBD2D, 0xBD2D, 0xBD2D },
+{ 0xBD2E, 0xBD2E, 0xBD2E },
+{ 0xBD2F, 0xBD2F, 0xBD2F },
+{ 0xBD30, 0xBD30, 0xBD30 },
+{ 0xBD31, 0xBD31, 0xBD31 },
+{ 0xBD32, 0xBD32, 0xBD32 },
+{ 0xBD33, 0xBD33, 0xBD33 },
+{ 0xBD34, 0xBD34, 0xBD34 },
+{ 0xBD35, 0xBD35, 0xBD35 },
+{ 0xBD36, 0xBD36, 0xBD36 },
+{ 0xBD37, 0xBD37, 0xBD37 },
+{ 0xBD38, 0xBD38, 0xBD38 },
+{ 0xBD39, 0xBD39, 0xBD39 },
+{ 0xBD3A, 0xBD3A, 0xBD3A },
+{ 0xBD3B, 0xBD3B, 0xBD3B },
+{ 0xBD3C, 0xBD3C, 0xBD3C },
+{ 0xBD3D, 0xBD3D, 0xBD3D },
+{ 0xBD3E, 0xBD3E, 0xBD3E },
+{ 0xBD3F, 0xBD3F, 0xBD3F },
+{ 0xBD40, 0xBD40, 0xBD40 },
+{ 0xBD41, 0xBD41, 0xBD41 },
+{ 0xBD42, 0xBD42, 0xBD42 },
+{ 0xBD43, 0xBD43, 0xBD43 },
+{ 0xBD44, 0xBD44, 0xBD44 },
+{ 0xBD45, 0xBD45, 0xBD45 },
+{ 0xBD46, 0xBD46, 0xBD46 },
+{ 0xBD47, 0xBD47, 0xBD47 },
+{ 0xBD48, 0xBD48, 0xBD48 },
+{ 0xBD49, 0xBD49, 0xBD49 },
+{ 0xBD4A, 0xBD4A, 0xBD4A },
+{ 0xBD4B, 0xBD4B, 0xBD4B },
+{ 0xBD4C, 0xBD4C, 0xBD4C },
+{ 0xBD4D, 0xBD4D, 0xBD4D },
+{ 0xBD4E, 0xBD4E, 0xBD4E },
+{ 0xBD4F, 0xBD4F, 0xBD4F },
+{ 0xBD50, 0xBD50, 0xBD50 },
+{ 0xBD51, 0xBD51, 0xBD51 },
+{ 0xBD52, 0xBD52, 0xBD52 },
+{ 0xBD53, 0xBD53, 0xBD53 },
+{ 0xBD54, 0xBD54, 0xBD54 },
+{ 0xBD55, 0xBD55, 0xBD55 },
+{ 0xBD56, 0xBD56, 0xBD56 },
+{ 0xBD57, 0xBD57, 0xBD57 },
+{ 0xBD58, 0xBD58, 0xBD58 },
+{ 0xBD59, 0xBD59, 0xBD59 },
+{ 0xBD5A, 0xBD5A, 0xBD5A },
+{ 0xBD5B, 0xBD5B, 0xBD5B },
+{ 0xBD5C, 0xBD5C, 0xBD5C },
+{ 0xBD5D, 0xBD5D, 0xBD5D },
+{ 0xBD5E, 0xBD5E, 0xBD5E },
+{ 0xBD5F, 0xBD5F, 0xBD5F },
+{ 0xBD60, 0xBD60, 0xBD60 },
+{ 0xBD61, 0xBD61, 0xBD61 },
+{ 0xBD62, 0xBD62, 0xBD62 },
+{ 0xBD63, 0xBD63, 0xBD63 },
+{ 0xBD64, 0xBD64, 0xBD64 },
+{ 0xBD65, 0xBD65, 0xBD65 },
+{ 0xBD66, 0xBD66, 0xBD66 },
+{ 0xBD67, 0xBD67, 0xBD67 },
+{ 0xBD68, 0xBD68, 0xBD68 },
+{ 0xBD69, 0xBD69, 0xBD69 },
+{ 0xBD6A, 0xBD6A, 0xBD6A },
+{ 0xBD6B, 0xBD6B, 0xBD6B },
+{ 0xBD6C, 0xBD6C, 0xBD6C },
+{ 0xBD6D, 0xBD6D, 0xBD6D },
+{ 0xBD6E, 0xBD6E, 0xBD6E },
+{ 0xBD6F, 0xBD6F, 0xBD6F },
+{ 0xBD70, 0xBD70, 0xBD70 },
+{ 0xBD71, 0xBD71, 0xBD71 },
+{ 0xBD72, 0xBD72, 0xBD72 },
+{ 0xBD73, 0xBD73, 0xBD73 },
+{ 0xBD74, 0xBD74, 0xBD74 },
+{ 0xBD75, 0xBD75, 0xBD75 },
+{ 0xBD76, 0xBD76, 0xBD76 },
+{ 0xBD77, 0xBD77, 0xBD77 },
+{ 0xBD78, 0xBD78, 0xBD78 },
+{ 0xBD79, 0xBD79, 0xBD79 },
+{ 0xBD7A, 0xBD7A, 0xBD7A },
+{ 0xBD7B, 0xBD7B, 0xBD7B },
+{ 0xBD7C, 0xBD7C, 0xBD7C },
+{ 0xBD7D, 0xBD7D, 0xBD7D },
+{ 0xBD7E, 0xBD7E, 0xBD7E },
+{ 0xBD7F, 0xBD7F, 0xBD7F },
+{ 0xBD80, 0xBD80, 0xBD80 },
+{ 0xBD81, 0xBD81, 0xBD81 },
+{ 0xBD82, 0xBD82, 0xBD82 },
+{ 0xBD83, 0xBD83, 0xBD83 },
+{ 0xBD84, 0xBD84, 0xBD84 },
+{ 0xBD85, 0xBD85, 0xBD85 },
+{ 0xBD86, 0xBD86, 0xBD86 },
+{ 0xBD87, 0xBD87, 0xBD87 },
+{ 0xBD88, 0xBD88, 0xBD88 },
+{ 0xBD89, 0xBD89, 0xBD89 },
+{ 0xBD8A, 0xBD8A, 0xBD8A },
+{ 0xBD8B, 0xBD8B, 0xBD8B },
+{ 0xBD8C, 0xBD8C, 0xBD8C },
+{ 0xBD8D, 0xBD8D, 0xBD8D },
+{ 0xBD8E, 0xBD8E, 0xBD8E },
+{ 0xBD8F, 0xBD8F, 0xBD8F },
+{ 0xBD90, 0xBD90, 0xBD90 },
+{ 0xBD91, 0xBD91, 0xBD91 },
+{ 0xBD92, 0xBD92, 0xBD92 },
+{ 0xBD93, 0xBD93, 0xBD93 },
+{ 0xBD94, 0xBD94, 0xBD94 },
+{ 0xBD95, 0xBD95, 0xBD95 },
+{ 0xBD96, 0xBD96, 0xBD96 },
+{ 0xBD97, 0xBD97, 0xBD97 },
+{ 0xBD98, 0xBD98, 0xBD98 },
+{ 0xBD99, 0xBD99, 0xBD99 },
+{ 0xBD9A, 0xBD9A, 0xBD9A },
+{ 0xBD9B, 0xBD9B, 0xBD9B },
+{ 0xBD9C, 0xBD9C, 0xBD9C },
+{ 0xBD9D, 0xBD9D, 0xBD9D },
+{ 0xBD9E, 0xBD9E, 0xBD9E },
+{ 0xBD9F, 0xBD9F, 0xBD9F },
+{ 0xBDA0, 0xBDA0, 0xBDA0 },
+{ 0xBDA1, 0xBDA1, 0xBDA1 },
+{ 0xBDA2, 0xBDA2, 0xBDA2 },
+{ 0xBDA3, 0xBDA3, 0xBDA3 },
+{ 0xBDA4, 0xBDA4, 0xBDA4 },
+{ 0xBDA5, 0xBDA5, 0xBDA5 },
+{ 0xBDA6, 0xBDA6, 0xBDA6 },
+{ 0xBDA7, 0xBDA7, 0xBDA7 },
+{ 0xBDA8, 0xBDA8, 0xBDA8 },
+{ 0xBDA9, 0xBDA9, 0xBDA9 },
+{ 0xBDAA, 0xBDAA, 0xBDAA },
+{ 0xBDAB, 0xBDAB, 0xBDAB },
+{ 0xBDAC, 0xBDAC, 0xBDAC },
+{ 0xBDAD, 0xBDAD, 0xBDAD },
+{ 0xBDAE, 0xBDAE, 0xBDAE },
+{ 0xBDAF, 0xBDAF, 0xBDAF },
+{ 0xBDB0, 0xBDB0, 0xBDB0 },
+{ 0xBDB1, 0xBDB1, 0xBDB1 },
+{ 0xBDB2, 0xBDB2, 0xBDB2 },
+{ 0xBDB3, 0xBDB3, 0xBDB3 },
+{ 0xBDB4, 0xBDB4, 0xBDB4 },
+{ 0xBDB5, 0xBDB5, 0xBDB5 },
+{ 0xBDB6, 0xBDB6, 0xBDB6 },
+{ 0xBDB7, 0xBDB7, 0xBDB7 },
+{ 0xBDB8, 0xBDB8, 0xBDB8 },
+{ 0xBDB9, 0xBDB9, 0xBDB9 },
+{ 0xBDBA, 0xBDBA, 0xBDBA },
+{ 0xBDBB, 0xBDBB, 0xBDBB },
+{ 0xBDBC, 0xBDBC, 0xBDBC },
+{ 0xBDBD, 0xBDBD, 0xBDBD },
+{ 0xBDBE, 0xBDBE, 0xBDBE },
+{ 0xBDBF, 0xBDBF, 0xBDBF },
+{ 0xBDC0, 0xBDC0, 0xBDC0 },
+{ 0xBDC1, 0xBDC1, 0xBDC1 },
+{ 0xBDC2, 0xBDC2, 0xBDC2 },
+{ 0xBDC3, 0xBDC3, 0xBDC3 },
+{ 0xBDC4, 0xBDC4, 0xBDC4 },
+{ 0xBDC5, 0xBDC5, 0xBDC5 },
+{ 0xBDC6, 0xBDC6, 0xBDC6 },
+{ 0xBDC7, 0xBDC7, 0xBDC7 },
+{ 0xBDC8, 0xBDC8, 0xBDC8 },
+{ 0xBDC9, 0xBDC9, 0xBDC9 },
+{ 0xBDCA, 0xBDCA, 0xBDCA },
+{ 0xBDCB, 0xBDCB, 0xBDCB },
+{ 0xBDCC, 0xBDCC, 0xBDCC },
+{ 0xBDCD, 0xBDCD, 0xBDCD },
+{ 0xBDCE, 0xBDCE, 0xBDCE },
+{ 0xBDCF, 0xBDCF, 0xBDCF },
+{ 0xBDD0, 0xBDD0, 0xBDD0 },
+{ 0xBDD1, 0xBDD1, 0xBDD1 },
+{ 0xBDD2, 0xBDD2, 0xBDD2 },
+{ 0xBDD3, 0xBDD3, 0xBDD3 },
+{ 0xBDD4, 0xBDD4, 0xBDD4 },
+{ 0xBDD5, 0xBDD5, 0xBDD5 },
+{ 0xBDD6, 0xBDD6, 0xBDD6 },
+{ 0xBDD7, 0xBDD7, 0xBDD7 },
+{ 0xBDD8, 0xBDD8, 0xBDD8 },
+{ 0xBDD9, 0xBDD9, 0xBDD9 },
+{ 0xBDDA, 0xBDDA, 0xBDDA },
+{ 0xBDDB, 0xBDDB, 0xBDDB },
+{ 0xBDDC, 0xBDDC, 0xBDDC },
+{ 0xBDDD, 0xBDDD, 0xBDDD },
+{ 0xBDDE, 0xBDDE, 0xBDDE },
+{ 0xBDDF, 0xBDDF, 0xBDDF },
+{ 0xBDE0, 0xBDE0, 0xBDE0 },
+{ 0xBDE1, 0xBDE1, 0xBDE1 },
+{ 0xBDE2, 0xBDE2, 0xBDE2 },
+{ 0xBDE3, 0xBDE3, 0xBDE3 },
+{ 0xBDE4, 0xBDE4, 0xBDE4 },
+{ 0xBDE5, 0xBDE5, 0xBDE5 },
+{ 0xBDE6, 0xBDE6, 0xBDE6 },
+{ 0xBDE7, 0xBDE7, 0xBDE7 },
+{ 0xBDE8, 0xBDE8, 0xBDE8 },
+{ 0xBDE9, 0xBDE9, 0xBDE9 },
+{ 0xBDEA, 0xBDEA, 0xBDEA },
+{ 0xBDEB, 0xBDEB, 0xBDEB },
+{ 0xBDEC, 0xBDEC, 0xBDEC },
+{ 0xBDED, 0xBDED, 0xBDED },
+{ 0xBDEE, 0xBDEE, 0xBDEE },
+{ 0xBDEF, 0xBDEF, 0xBDEF },
+{ 0xBDF0, 0xBDF0, 0xBDF0 },
+{ 0xBDF1, 0xBDF1, 0xBDF1 },
+{ 0xBDF2, 0xBDF2, 0xBDF2 },
+{ 0xBDF3, 0xBDF3, 0xBDF3 },
+{ 0xBDF4, 0xBDF4, 0xBDF4 },
+{ 0xBDF5, 0xBDF5, 0xBDF5 },
+{ 0xBDF6, 0xBDF6, 0xBDF6 },
+{ 0xBDF7, 0xBDF7, 0xBDF7 },
+{ 0xBDF8, 0xBDF8, 0xBDF8 },
+{ 0xBDF9, 0xBDF9, 0xBDF9 },
+{ 0xBDFA, 0xBDFA, 0xBDFA },
+{ 0xBDFB, 0xBDFB, 0xBDFB },
+{ 0xBDFC, 0xBDFC, 0xBDFC },
+{ 0xBDFD, 0xBDFD, 0xBDFD },
+{ 0xBDFE, 0xBDFE, 0xBDFE },
+{ 0xBDFF, 0xBDFF, 0xBDFF },
+{ 0xBE00, 0xBE00, 0xBE00 },
+{ 0xBE01, 0xBE01, 0xBE01 },
+{ 0xBE02, 0xBE02, 0xBE02 },
+{ 0xBE03, 0xBE03, 0xBE03 },
+{ 0xBE04, 0xBE04, 0xBE04 },
+{ 0xBE05, 0xBE05, 0xBE05 },
+{ 0xBE06, 0xBE06, 0xBE06 },
+{ 0xBE07, 0xBE07, 0xBE07 },
+{ 0xBE08, 0xBE08, 0xBE08 },
+{ 0xBE09, 0xBE09, 0xBE09 },
+{ 0xBE0A, 0xBE0A, 0xBE0A },
+{ 0xBE0B, 0xBE0B, 0xBE0B },
+{ 0xBE0C, 0xBE0C, 0xBE0C },
+{ 0xBE0D, 0xBE0D, 0xBE0D },
+{ 0xBE0E, 0xBE0E, 0xBE0E },
+{ 0xBE0F, 0xBE0F, 0xBE0F },
+{ 0xBE10, 0xBE10, 0xBE10 },
+{ 0xBE11, 0xBE11, 0xBE11 },
+{ 0xBE12, 0xBE12, 0xBE12 },
+{ 0xBE13, 0xBE13, 0xBE13 },
+{ 0xBE14, 0xBE14, 0xBE14 },
+{ 0xBE15, 0xBE15, 0xBE15 },
+{ 0xBE16, 0xBE16, 0xBE16 },
+{ 0xBE17, 0xBE17, 0xBE17 },
+{ 0xBE18, 0xBE18, 0xBE18 },
+{ 0xBE19, 0xBE19, 0xBE19 },
+{ 0xBE1A, 0xBE1A, 0xBE1A },
+{ 0xBE1B, 0xBE1B, 0xBE1B },
+{ 0xBE1C, 0xBE1C, 0xBE1C },
+{ 0xBE1D, 0xBE1D, 0xBE1D },
+{ 0xBE1E, 0xBE1E, 0xBE1E },
+{ 0xBE1F, 0xBE1F, 0xBE1F },
+{ 0xBE20, 0xBE20, 0xBE20 },
+{ 0xBE21, 0xBE21, 0xBE21 },
+{ 0xBE22, 0xBE22, 0xBE22 },
+{ 0xBE23, 0xBE23, 0xBE23 },
+{ 0xBE24, 0xBE24, 0xBE24 },
+{ 0xBE25, 0xBE25, 0xBE25 },
+{ 0xBE26, 0xBE26, 0xBE26 },
+{ 0xBE27, 0xBE27, 0xBE27 },
+{ 0xBE28, 0xBE28, 0xBE28 },
+{ 0xBE29, 0xBE29, 0xBE29 },
+{ 0xBE2A, 0xBE2A, 0xBE2A },
+{ 0xBE2B, 0xBE2B, 0xBE2B },
+{ 0xBE2C, 0xBE2C, 0xBE2C },
+{ 0xBE2D, 0xBE2D, 0xBE2D },
+{ 0xBE2E, 0xBE2E, 0xBE2E },
+{ 0xBE2F, 0xBE2F, 0xBE2F },
+{ 0xBE30, 0xBE30, 0xBE30 },
+{ 0xBE31, 0xBE31, 0xBE31 },
+{ 0xBE32, 0xBE32, 0xBE32 },
+{ 0xBE33, 0xBE33, 0xBE33 },
+{ 0xBE34, 0xBE34, 0xBE34 },
+{ 0xBE35, 0xBE35, 0xBE35 },
+{ 0xBE36, 0xBE36, 0xBE36 },
+{ 0xBE37, 0xBE37, 0xBE37 },
+{ 0xBE38, 0xBE38, 0xBE38 },
+{ 0xBE39, 0xBE39, 0xBE39 },
+{ 0xBE3A, 0xBE3A, 0xBE3A },
+{ 0xBE3B, 0xBE3B, 0xBE3B },
+{ 0xBE3C, 0xBE3C, 0xBE3C },
+{ 0xBE3D, 0xBE3D, 0xBE3D },
+{ 0xBE3E, 0xBE3E, 0xBE3E },
+{ 0xBE3F, 0xBE3F, 0xBE3F },
+{ 0xBE40, 0xBE40, 0xBE40 },
+{ 0xBE41, 0xBE41, 0xBE41 },
+{ 0xBE42, 0xBE42, 0xBE42 },
+{ 0xBE43, 0xBE43, 0xBE43 },
+{ 0xBE44, 0xBE44, 0xBE44 },
+{ 0xBE45, 0xBE45, 0xBE45 },
+{ 0xBE46, 0xBE46, 0xBE46 },
+{ 0xBE47, 0xBE47, 0xBE47 },
+{ 0xBE48, 0xBE48, 0xBE48 },
+{ 0xBE49, 0xBE49, 0xBE49 },
+{ 0xBE4A, 0xBE4A, 0xBE4A },
+{ 0xBE4B, 0xBE4B, 0xBE4B },
+{ 0xBE4C, 0xBE4C, 0xBE4C },
+{ 0xBE4D, 0xBE4D, 0xBE4D },
+{ 0xBE4E, 0xBE4E, 0xBE4E },
+{ 0xBE4F, 0xBE4F, 0xBE4F },
+{ 0xBE50, 0xBE50, 0xBE50 },
+{ 0xBE51, 0xBE51, 0xBE51 },
+{ 0xBE52, 0xBE52, 0xBE52 },
+{ 0xBE53, 0xBE53, 0xBE53 },
+{ 0xBE54, 0xBE54, 0xBE54 },
+{ 0xBE55, 0xBE55, 0xBE55 },
+{ 0xBE56, 0xBE56, 0xBE56 },
+{ 0xBE57, 0xBE57, 0xBE57 },
+{ 0xBE58, 0xBE58, 0xBE58 },
+{ 0xBE59, 0xBE59, 0xBE59 },
+{ 0xBE5A, 0xBE5A, 0xBE5A },
+{ 0xBE5B, 0xBE5B, 0xBE5B },
+{ 0xBE5C, 0xBE5C, 0xBE5C },
+{ 0xBE5D, 0xBE5D, 0xBE5D },
+{ 0xBE5E, 0xBE5E, 0xBE5E },
+{ 0xBE5F, 0xBE5F, 0xBE5F },
+{ 0xBE60, 0xBE60, 0xBE60 },
+{ 0xBE61, 0xBE61, 0xBE61 },
+{ 0xBE62, 0xBE62, 0xBE62 },
+{ 0xBE63, 0xBE63, 0xBE63 },
+{ 0xBE64, 0xBE64, 0xBE64 },
+{ 0xBE65, 0xBE65, 0xBE65 },
+{ 0xBE66, 0xBE66, 0xBE66 },
+{ 0xBE67, 0xBE67, 0xBE67 },
+{ 0xBE68, 0xBE68, 0xBE68 },
+{ 0xBE69, 0xBE69, 0xBE69 },
+{ 0xBE6A, 0xBE6A, 0xBE6A },
+{ 0xBE6B, 0xBE6B, 0xBE6B },
+{ 0xBE6C, 0xBE6C, 0xBE6C },
+{ 0xBE6D, 0xBE6D, 0xBE6D },
+{ 0xBE6E, 0xBE6E, 0xBE6E },
+{ 0xBE6F, 0xBE6F, 0xBE6F },
+{ 0xBE70, 0xBE70, 0xBE70 },
+{ 0xBE71, 0xBE71, 0xBE71 },
+{ 0xBE72, 0xBE72, 0xBE72 },
+{ 0xBE73, 0xBE73, 0xBE73 },
+{ 0xBE74, 0xBE74, 0xBE74 },
+{ 0xBE75, 0xBE75, 0xBE75 },
+{ 0xBE76, 0xBE76, 0xBE76 },
+{ 0xBE77, 0xBE77, 0xBE77 },
+{ 0xBE78, 0xBE78, 0xBE78 },
+{ 0xBE79, 0xBE79, 0xBE79 },
+{ 0xBE7A, 0xBE7A, 0xBE7A },
+{ 0xBE7B, 0xBE7B, 0xBE7B },
+{ 0xBE7C, 0xBE7C, 0xBE7C },
+{ 0xBE7D, 0xBE7D, 0xBE7D },
+{ 0xBE7E, 0xBE7E, 0xBE7E },
+{ 0xBE7F, 0xBE7F, 0xBE7F },
+{ 0xBE80, 0xBE80, 0xBE80 },
+{ 0xBE81, 0xBE81, 0xBE81 },
+{ 0xBE82, 0xBE82, 0xBE82 },
+{ 0xBE83, 0xBE83, 0xBE83 },
+{ 0xBE84, 0xBE84, 0xBE84 },
+{ 0xBE85, 0xBE85, 0xBE85 },
+{ 0xBE86, 0xBE86, 0xBE86 },
+{ 0xBE87, 0xBE87, 0xBE87 },
+{ 0xBE88, 0xBE88, 0xBE88 },
+{ 0xBE89, 0xBE89, 0xBE89 },
+{ 0xBE8A, 0xBE8A, 0xBE8A },
+{ 0xBE8B, 0xBE8B, 0xBE8B },
+{ 0xBE8C, 0xBE8C, 0xBE8C },
+{ 0xBE8D, 0xBE8D, 0xBE8D },
+{ 0xBE8E, 0xBE8E, 0xBE8E },
+{ 0xBE8F, 0xBE8F, 0xBE8F },
+{ 0xBE90, 0xBE90, 0xBE90 },
+{ 0xBE91, 0xBE91, 0xBE91 },
+{ 0xBE92, 0xBE92, 0xBE92 },
+{ 0xBE93, 0xBE93, 0xBE93 },
+{ 0xBE94, 0xBE94, 0xBE94 },
+{ 0xBE95, 0xBE95, 0xBE95 },
+{ 0xBE96, 0xBE96, 0xBE96 },
+{ 0xBE97, 0xBE97, 0xBE97 },
+{ 0xBE98, 0xBE98, 0xBE98 },
+{ 0xBE99, 0xBE99, 0xBE99 },
+{ 0xBE9A, 0xBE9A, 0xBE9A },
+{ 0xBE9B, 0xBE9B, 0xBE9B },
+{ 0xBE9C, 0xBE9C, 0xBE9C },
+{ 0xBE9D, 0xBE9D, 0xBE9D },
+{ 0xBE9E, 0xBE9E, 0xBE9E },
+{ 0xBE9F, 0xBE9F, 0xBE9F },
+{ 0xBEA0, 0xBEA0, 0xBEA0 },
+{ 0xBEA1, 0xBEA1, 0xBEA1 },
+{ 0xBEA2, 0xBEA2, 0xBEA2 },
+{ 0xBEA3, 0xBEA3, 0xBEA3 },
+{ 0xBEA4, 0xBEA4, 0xBEA4 },
+{ 0xBEA5, 0xBEA5, 0xBEA5 },
+{ 0xBEA6, 0xBEA6, 0xBEA6 },
+{ 0xBEA7, 0xBEA7, 0xBEA7 },
+{ 0xBEA8, 0xBEA8, 0xBEA8 },
+{ 0xBEA9, 0xBEA9, 0xBEA9 },
+{ 0xBEAA, 0xBEAA, 0xBEAA },
+{ 0xBEAB, 0xBEAB, 0xBEAB },
+{ 0xBEAC, 0xBEAC, 0xBEAC },
+{ 0xBEAD, 0xBEAD, 0xBEAD },
+{ 0xBEAE, 0xBEAE, 0xBEAE },
+{ 0xBEAF, 0xBEAF, 0xBEAF },
+{ 0xBEB0, 0xBEB0, 0xBEB0 },
+{ 0xBEB1, 0xBEB1, 0xBEB1 },
+{ 0xBEB2, 0xBEB2, 0xBEB2 },
+{ 0xBEB3, 0xBEB3, 0xBEB3 },
+{ 0xBEB4, 0xBEB4, 0xBEB4 },
+{ 0xBEB5, 0xBEB5, 0xBEB5 },
+{ 0xBEB6, 0xBEB6, 0xBEB6 },
+{ 0xBEB7, 0xBEB7, 0xBEB7 },
+{ 0xBEB8, 0xBEB8, 0xBEB8 },
+{ 0xBEB9, 0xBEB9, 0xBEB9 },
+{ 0xBEBA, 0xBEBA, 0xBEBA },
+{ 0xBEBB, 0xBEBB, 0xBEBB },
+{ 0xBEBC, 0xBEBC, 0xBEBC },
+{ 0xBEBD, 0xBEBD, 0xBEBD },
+{ 0xBEBE, 0xBEBE, 0xBEBE },
+{ 0xBEBF, 0xBEBF, 0xBEBF },
+{ 0xBEC0, 0xBEC0, 0xBEC0 },
+{ 0xBEC1, 0xBEC1, 0xBEC1 },
+{ 0xBEC2, 0xBEC2, 0xBEC2 },
+{ 0xBEC3, 0xBEC3, 0xBEC3 },
+{ 0xBEC4, 0xBEC4, 0xBEC4 },
+{ 0xBEC5, 0xBEC5, 0xBEC5 },
+{ 0xBEC6, 0xBEC6, 0xBEC6 },
+{ 0xBEC7, 0xBEC7, 0xBEC7 },
+{ 0xBEC8, 0xBEC8, 0xBEC8 },
+{ 0xBEC9, 0xBEC9, 0xBEC9 },
+{ 0xBECA, 0xBECA, 0xBECA },
+{ 0xBECB, 0xBECB, 0xBECB },
+{ 0xBECC, 0xBECC, 0xBECC },
+{ 0xBECD, 0xBECD, 0xBECD },
+{ 0xBECE, 0xBECE, 0xBECE },
+{ 0xBECF, 0xBECF, 0xBECF },
+{ 0xBED0, 0xBED0, 0xBED0 },
+{ 0xBED1, 0xBED1, 0xBED1 },
+{ 0xBED2, 0xBED2, 0xBED2 },
+{ 0xBED3, 0xBED3, 0xBED3 },
+{ 0xBED4, 0xBED4, 0xBED4 },
+{ 0xBED5, 0xBED5, 0xBED5 },
+{ 0xBED6, 0xBED6, 0xBED6 },
+{ 0xBED7, 0xBED7, 0xBED7 },
+{ 0xBED8, 0xBED8, 0xBED8 },
+{ 0xBED9, 0xBED9, 0xBED9 },
+{ 0xBEDA, 0xBEDA, 0xBEDA },
+{ 0xBEDB, 0xBEDB, 0xBEDB },
+{ 0xBEDC, 0xBEDC, 0xBEDC },
+{ 0xBEDD, 0xBEDD, 0xBEDD },
+{ 0xBEDE, 0xBEDE, 0xBEDE },
+{ 0xBEDF, 0xBEDF, 0xBEDF },
+{ 0xBEE0, 0xBEE0, 0xBEE0 },
+{ 0xBEE1, 0xBEE1, 0xBEE1 },
+{ 0xBEE2, 0xBEE2, 0xBEE2 },
+{ 0xBEE3, 0xBEE3, 0xBEE3 },
+{ 0xBEE4, 0xBEE4, 0xBEE4 },
+{ 0xBEE5, 0xBEE5, 0xBEE5 },
+{ 0xBEE6, 0xBEE6, 0xBEE6 },
+{ 0xBEE7, 0xBEE7, 0xBEE7 },
+{ 0xBEE8, 0xBEE8, 0xBEE8 },
+{ 0xBEE9, 0xBEE9, 0xBEE9 },
+{ 0xBEEA, 0xBEEA, 0xBEEA },
+{ 0xBEEB, 0xBEEB, 0xBEEB },
+{ 0xBEEC, 0xBEEC, 0xBEEC },
+{ 0xBEED, 0xBEED, 0xBEED },
+{ 0xBEEE, 0xBEEE, 0xBEEE },
+{ 0xBEEF, 0xBEEF, 0xBEEF },
+{ 0xBEF0, 0xBEF0, 0xBEF0 },
+{ 0xBEF1, 0xBEF1, 0xBEF1 },
+{ 0xBEF2, 0xBEF2, 0xBEF2 },
+{ 0xBEF3, 0xBEF3, 0xBEF3 },
+{ 0xBEF4, 0xBEF4, 0xBEF4 },
+{ 0xBEF5, 0xBEF5, 0xBEF5 },
+{ 0xBEF6, 0xBEF6, 0xBEF6 },
+{ 0xBEF7, 0xBEF7, 0xBEF7 },
+{ 0xBEF8, 0xBEF8, 0xBEF8 },
+{ 0xBEF9, 0xBEF9, 0xBEF9 },
+{ 0xBEFA, 0xBEFA, 0xBEFA },
+{ 0xBEFB, 0xBEFB, 0xBEFB },
+{ 0xBEFC, 0xBEFC, 0xBEFC },
+{ 0xBEFD, 0xBEFD, 0xBEFD },
+{ 0xBEFE, 0xBEFE, 0xBEFE },
+{ 0xBEFF, 0xBEFF, 0xBEFF },
+{ 0xBF00, 0xBF00, 0xBF00 },
+{ 0xBF01, 0xBF01, 0xBF01 },
+{ 0xBF02, 0xBF02, 0xBF02 },
+{ 0xBF03, 0xBF03, 0xBF03 },
+{ 0xBF04, 0xBF04, 0xBF04 },
+{ 0xBF05, 0xBF05, 0xBF05 },
+{ 0xBF06, 0xBF06, 0xBF06 },
+{ 0xBF07, 0xBF07, 0xBF07 },
+{ 0xBF08, 0xBF08, 0xBF08 },
+{ 0xBF09, 0xBF09, 0xBF09 },
+{ 0xBF0A, 0xBF0A, 0xBF0A },
+{ 0xBF0B, 0xBF0B, 0xBF0B },
+{ 0xBF0C, 0xBF0C, 0xBF0C },
+{ 0xBF0D, 0xBF0D, 0xBF0D },
+{ 0xBF0E, 0xBF0E, 0xBF0E },
+{ 0xBF0F, 0xBF0F, 0xBF0F },
+{ 0xBF10, 0xBF10, 0xBF10 },
+{ 0xBF11, 0xBF11, 0xBF11 },
+{ 0xBF12, 0xBF12, 0xBF12 },
+{ 0xBF13, 0xBF13, 0xBF13 },
+{ 0xBF14, 0xBF14, 0xBF14 },
+{ 0xBF15, 0xBF15, 0xBF15 },
+{ 0xBF16, 0xBF16, 0xBF16 },
+{ 0xBF17, 0xBF17, 0xBF17 },
+{ 0xBF18, 0xBF18, 0xBF18 },
+{ 0xBF19, 0xBF19, 0xBF19 },
+{ 0xBF1A, 0xBF1A, 0xBF1A },
+{ 0xBF1B, 0xBF1B, 0xBF1B },
+{ 0xBF1C, 0xBF1C, 0xBF1C },
+{ 0xBF1D, 0xBF1D, 0xBF1D },
+{ 0xBF1E, 0xBF1E, 0xBF1E },
+{ 0xBF1F, 0xBF1F, 0xBF1F },
+{ 0xBF20, 0xBF20, 0xBF20 },
+{ 0xBF21, 0xBF21, 0xBF21 },
+{ 0xBF22, 0xBF22, 0xBF22 },
+{ 0xBF23, 0xBF23, 0xBF23 },
+{ 0xBF24, 0xBF24, 0xBF24 },
+{ 0xBF25, 0xBF25, 0xBF25 },
+{ 0xBF26, 0xBF26, 0xBF26 },
+{ 0xBF27, 0xBF27, 0xBF27 },
+{ 0xBF28, 0xBF28, 0xBF28 },
+{ 0xBF29, 0xBF29, 0xBF29 },
+{ 0xBF2A, 0xBF2A, 0xBF2A },
+{ 0xBF2B, 0xBF2B, 0xBF2B },
+{ 0xBF2C, 0xBF2C, 0xBF2C },
+{ 0xBF2D, 0xBF2D, 0xBF2D },
+{ 0xBF2E, 0xBF2E, 0xBF2E },
+{ 0xBF2F, 0xBF2F, 0xBF2F },
+{ 0xBF30, 0xBF30, 0xBF30 },
+{ 0xBF31, 0xBF31, 0xBF31 },
+{ 0xBF32, 0xBF32, 0xBF32 },
+{ 0xBF33, 0xBF33, 0xBF33 },
+{ 0xBF34, 0xBF34, 0xBF34 },
+{ 0xBF35, 0xBF35, 0xBF35 },
+{ 0xBF36, 0xBF36, 0xBF36 },
+{ 0xBF37, 0xBF37, 0xBF37 },
+{ 0xBF38, 0xBF38, 0xBF38 },
+{ 0xBF39, 0xBF39, 0xBF39 },
+{ 0xBF3A, 0xBF3A, 0xBF3A },
+{ 0xBF3B, 0xBF3B, 0xBF3B },
+{ 0xBF3C, 0xBF3C, 0xBF3C },
+{ 0xBF3D, 0xBF3D, 0xBF3D },
+{ 0xBF3E, 0xBF3E, 0xBF3E },
+{ 0xBF3F, 0xBF3F, 0xBF3F },
+{ 0xBF40, 0xBF40, 0xBF40 },
+{ 0xBF41, 0xBF41, 0xBF41 },
+{ 0xBF42, 0xBF42, 0xBF42 },
+{ 0xBF43, 0xBF43, 0xBF43 },
+{ 0xBF44, 0xBF44, 0xBF44 },
+{ 0xBF45, 0xBF45, 0xBF45 },
+{ 0xBF46, 0xBF46, 0xBF46 },
+{ 0xBF47, 0xBF47, 0xBF47 },
+{ 0xBF48, 0xBF48, 0xBF48 },
+{ 0xBF49, 0xBF49, 0xBF49 },
+{ 0xBF4A, 0xBF4A, 0xBF4A },
+{ 0xBF4B, 0xBF4B, 0xBF4B },
+{ 0xBF4C, 0xBF4C, 0xBF4C },
+{ 0xBF4D, 0xBF4D, 0xBF4D },
+{ 0xBF4E, 0xBF4E, 0xBF4E },
+{ 0xBF4F, 0xBF4F, 0xBF4F },
+{ 0xBF50, 0xBF50, 0xBF50 },
+{ 0xBF51, 0xBF51, 0xBF51 },
+{ 0xBF52, 0xBF52, 0xBF52 },
+{ 0xBF53, 0xBF53, 0xBF53 },
+{ 0xBF54, 0xBF54, 0xBF54 },
+{ 0xBF55, 0xBF55, 0xBF55 },
+{ 0xBF56, 0xBF56, 0xBF56 },
+{ 0xBF57, 0xBF57, 0xBF57 },
+{ 0xBF58, 0xBF58, 0xBF58 },
+{ 0xBF59, 0xBF59, 0xBF59 },
+{ 0xBF5A, 0xBF5A, 0xBF5A },
+{ 0xBF5B, 0xBF5B, 0xBF5B },
+{ 0xBF5C, 0xBF5C, 0xBF5C },
+{ 0xBF5D, 0xBF5D, 0xBF5D },
+{ 0xBF5E, 0xBF5E, 0xBF5E },
+{ 0xBF5F, 0xBF5F, 0xBF5F },
+{ 0xBF60, 0xBF60, 0xBF60 },
+{ 0xBF61, 0xBF61, 0xBF61 },
+{ 0xBF62, 0xBF62, 0xBF62 },
+{ 0xBF63, 0xBF63, 0xBF63 },
+{ 0xBF64, 0xBF64, 0xBF64 },
+{ 0xBF65, 0xBF65, 0xBF65 },
+{ 0xBF66, 0xBF66, 0xBF66 },
+{ 0xBF67, 0xBF67, 0xBF67 },
+{ 0xBF68, 0xBF68, 0xBF68 },
+{ 0xBF69, 0xBF69, 0xBF69 },
+{ 0xBF6A, 0xBF6A, 0xBF6A },
+{ 0xBF6B, 0xBF6B, 0xBF6B },
+{ 0xBF6C, 0xBF6C, 0xBF6C },
+{ 0xBF6D, 0xBF6D, 0xBF6D },
+{ 0xBF6E, 0xBF6E, 0xBF6E },
+{ 0xBF6F, 0xBF6F, 0xBF6F },
+{ 0xBF70, 0xBF70, 0xBF70 },
+{ 0xBF71, 0xBF71, 0xBF71 },
+{ 0xBF72, 0xBF72, 0xBF72 },
+{ 0xBF73, 0xBF73, 0xBF73 },
+{ 0xBF74, 0xBF74, 0xBF74 },
+{ 0xBF75, 0xBF75, 0xBF75 },
+{ 0xBF76, 0xBF76, 0xBF76 },
+{ 0xBF77, 0xBF77, 0xBF77 },
+{ 0xBF78, 0xBF78, 0xBF78 },
+{ 0xBF79, 0xBF79, 0xBF79 },
+{ 0xBF7A, 0xBF7A, 0xBF7A },
+{ 0xBF7B, 0xBF7B, 0xBF7B },
+{ 0xBF7C, 0xBF7C, 0xBF7C },
+{ 0xBF7D, 0xBF7D, 0xBF7D },
+{ 0xBF7E, 0xBF7E, 0xBF7E },
+{ 0xBF7F, 0xBF7F, 0xBF7F },
+{ 0xBF80, 0xBF80, 0xBF80 },
+{ 0xBF81, 0xBF81, 0xBF81 },
+{ 0xBF82, 0xBF82, 0xBF82 },
+{ 0xBF83, 0xBF83, 0xBF83 },
+{ 0xBF84, 0xBF84, 0xBF84 },
+{ 0xBF85, 0xBF85, 0xBF85 },
+{ 0xBF86, 0xBF86, 0xBF86 },
+{ 0xBF87, 0xBF87, 0xBF87 },
+{ 0xBF88, 0xBF88, 0xBF88 },
+{ 0xBF89, 0xBF89, 0xBF89 },
+{ 0xBF8A, 0xBF8A, 0xBF8A },
+{ 0xBF8B, 0xBF8B, 0xBF8B },
+{ 0xBF8C, 0xBF8C, 0xBF8C },
+{ 0xBF8D, 0xBF8D, 0xBF8D },
+{ 0xBF8E, 0xBF8E, 0xBF8E },
+{ 0xBF8F, 0xBF8F, 0xBF8F },
+{ 0xBF90, 0xBF90, 0xBF90 },
+{ 0xBF91, 0xBF91, 0xBF91 },
+{ 0xBF92, 0xBF92, 0xBF92 },
+{ 0xBF93, 0xBF93, 0xBF93 },
+{ 0xBF94, 0xBF94, 0xBF94 },
+{ 0xBF95, 0xBF95, 0xBF95 },
+{ 0xBF96, 0xBF96, 0xBF96 },
+{ 0xBF97, 0xBF97, 0xBF97 },
+{ 0xBF98, 0xBF98, 0xBF98 },
+{ 0xBF99, 0xBF99, 0xBF99 },
+{ 0xBF9A, 0xBF9A, 0xBF9A },
+{ 0xBF9B, 0xBF9B, 0xBF9B },
+{ 0xBF9C, 0xBF9C, 0xBF9C },
+{ 0xBF9D, 0xBF9D, 0xBF9D },
+{ 0xBF9E, 0xBF9E, 0xBF9E },
+{ 0xBF9F, 0xBF9F, 0xBF9F },
+{ 0xBFA0, 0xBFA0, 0xBFA0 },
+{ 0xBFA1, 0xBFA1, 0xBFA1 },
+{ 0xBFA2, 0xBFA2, 0xBFA2 },
+{ 0xBFA3, 0xBFA3, 0xBFA3 },
+{ 0xBFA4, 0xBFA4, 0xBFA4 },
+{ 0xBFA5, 0xBFA5, 0xBFA5 },
+{ 0xBFA6, 0xBFA6, 0xBFA6 },
+{ 0xBFA7, 0xBFA7, 0xBFA7 },
+{ 0xBFA8, 0xBFA8, 0xBFA8 },
+{ 0xBFA9, 0xBFA9, 0xBFA9 },
+{ 0xBFAA, 0xBFAA, 0xBFAA },
+{ 0xBFAB, 0xBFAB, 0xBFAB },
+{ 0xBFAC, 0xBFAC, 0xBFAC },
+{ 0xBFAD, 0xBFAD, 0xBFAD },
+{ 0xBFAE, 0xBFAE, 0xBFAE },
+{ 0xBFAF, 0xBFAF, 0xBFAF },
+{ 0xBFB0, 0xBFB0, 0xBFB0 },
+{ 0xBFB1, 0xBFB1, 0xBFB1 },
+{ 0xBFB2, 0xBFB2, 0xBFB2 },
+{ 0xBFB3, 0xBFB3, 0xBFB3 },
+{ 0xBFB4, 0xBFB4, 0xBFB4 },
+{ 0xBFB5, 0xBFB5, 0xBFB5 },
+{ 0xBFB6, 0xBFB6, 0xBFB6 },
+{ 0xBFB7, 0xBFB7, 0xBFB7 },
+{ 0xBFB8, 0xBFB8, 0xBFB8 },
+{ 0xBFB9, 0xBFB9, 0xBFB9 },
+{ 0xBFBA, 0xBFBA, 0xBFBA },
+{ 0xBFBB, 0xBFBB, 0xBFBB },
+{ 0xBFBC, 0xBFBC, 0xBFBC },
+{ 0xBFBD, 0xBFBD, 0xBFBD },
+{ 0xBFBE, 0xBFBE, 0xBFBE },
+{ 0xBFBF, 0xBFBF, 0xBFBF },
+{ 0xBFC0, 0xBFC0, 0xBFC0 },
+{ 0xBFC1, 0xBFC1, 0xBFC1 },
+{ 0xBFC2, 0xBFC2, 0xBFC2 },
+{ 0xBFC3, 0xBFC3, 0xBFC3 },
+{ 0xBFC4, 0xBFC4, 0xBFC4 },
+{ 0xBFC5, 0xBFC5, 0xBFC5 },
+{ 0xBFC6, 0xBFC6, 0xBFC6 },
+{ 0xBFC7, 0xBFC7, 0xBFC7 },
+{ 0xBFC8, 0xBFC8, 0xBFC8 },
+{ 0xBFC9, 0xBFC9, 0xBFC9 },
+{ 0xBFCA, 0xBFCA, 0xBFCA },
+{ 0xBFCB, 0xBFCB, 0xBFCB },
+{ 0xBFCC, 0xBFCC, 0xBFCC },
+{ 0xBFCD, 0xBFCD, 0xBFCD },
+{ 0xBFCE, 0xBFCE, 0xBFCE },
+{ 0xBFCF, 0xBFCF, 0xBFCF },
+{ 0xBFD0, 0xBFD0, 0xBFD0 },
+{ 0xBFD1, 0xBFD1, 0xBFD1 },
+{ 0xBFD2, 0xBFD2, 0xBFD2 },
+{ 0xBFD3, 0xBFD3, 0xBFD3 },
+{ 0xBFD4, 0xBFD4, 0xBFD4 },
+{ 0xBFD5, 0xBFD5, 0xBFD5 },
+{ 0xBFD6, 0xBFD6, 0xBFD6 },
+{ 0xBFD7, 0xBFD7, 0xBFD7 },
+{ 0xBFD8, 0xBFD8, 0xBFD8 },
+{ 0xBFD9, 0xBFD9, 0xBFD9 },
+{ 0xBFDA, 0xBFDA, 0xBFDA },
+{ 0xBFDB, 0xBFDB, 0xBFDB },
+{ 0xBFDC, 0xBFDC, 0xBFDC },
+{ 0xBFDD, 0xBFDD, 0xBFDD },
+{ 0xBFDE, 0xBFDE, 0xBFDE },
+{ 0xBFDF, 0xBFDF, 0xBFDF },
+{ 0xBFE0, 0xBFE0, 0xBFE0 },
+{ 0xBFE1, 0xBFE1, 0xBFE1 },
+{ 0xBFE2, 0xBFE2, 0xBFE2 },
+{ 0xBFE3, 0xBFE3, 0xBFE3 },
+{ 0xBFE4, 0xBFE4, 0xBFE4 },
+{ 0xBFE5, 0xBFE5, 0xBFE5 },
+{ 0xBFE6, 0xBFE6, 0xBFE6 },
+{ 0xBFE7, 0xBFE7, 0xBFE7 },
+{ 0xBFE8, 0xBFE8, 0xBFE8 },
+{ 0xBFE9, 0xBFE9, 0xBFE9 },
+{ 0xBFEA, 0xBFEA, 0xBFEA },
+{ 0xBFEB, 0xBFEB, 0xBFEB },
+{ 0xBFEC, 0xBFEC, 0xBFEC },
+{ 0xBFED, 0xBFED, 0xBFED },
+{ 0xBFEE, 0xBFEE, 0xBFEE },
+{ 0xBFEF, 0xBFEF, 0xBFEF },
+{ 0xBFF0, 0xBFF0, 0xBFF0 },
+{ 0xBFF1, 0xBFF1, 0xBFF1 },
+{ 0xBFF2, 0xBFF2, 0xBFF2 },
+{ 0xBFF3, 0xBFF3, 0xBFF3 },
+{ 0xBFF4, 0xBFF4, 0xBFF4 },
+{ 0xBFF5, 0xBFF5, 0xBFF5 },
+{ 0xBFF6, 0xBFF6, 0xBFF6 },
+{ 0xBFF7, 0xBFF7, 0xBFF7 },
+{ 0xBFF8, 0xBFF8, 0xBFF8 },
+{ 0xBFF9, 0xBFF9, 0xBFF9 },
+{ 0xBFFA, 0xBFFA, 0xBFFA },
+{ 0xBFFB, 0xBFFB, 0xBFFB },
+{ 0xBFFC, 0xBFFC, 0xBFFC },
+{ 0xBFFD, 0xBFFD, 0xBFFD },
+{ 0xBFFE, 0xBFFE, 0xBFFE },
+{ 0xBFFF, 0xBFFF, 0xBFFF },
+{ 0xC000, 0xC000, 0xC000 },
+{ 0xC001, 0xC001, 0xC001 },
+{ 0xC002, 0xC002, 0xC002 },
+{ 0xC003, 0xC003, 0xC003 },
+{ 0xC004, 0xC004, 0xC004 },
+{ 0xC005, 0xC005, 0xC005 },
+{ 0xC006, 0xC006, 0xC006 },
+{ 0xC007, 0xC007, 0xC007 },
+{ 0xC008, 0xC008, 0xC008 },
+{ 0xC009, 0xC009, 0xC009 },
+{ 0xC00A, 0xC00A, 0xC00A },
+{ 0xC00B, 0xC00B, 0xC00B },
+{ 0xC00C, 0xC00C, 0xC00C },
+{ 0xC00D, 0xC00D, 0xC00D },
+{ 0xC00E, 0xC00E, 0xC00E },
+{ 0xC00F, 0xC00F, 0xC00F },
+{ 0xC010, 0xC010, 0xC010 },
+{ 0xC011, 0xC011, 0xC011 },
+{ 0xC012, 0xC012, 0xC012 },
+{ 0xC013, 0xC013, 0xC013 },
+{ 0xC014, 0xC014, 0xC014 },
+{ 0xC015, 0xC015, 0xC015 },
+{ 0xC016, 0xC016, 0xC016 },
+{ 0xC017, 0xC017, 0xC017 },
+{ 0xC018, 0xC018, 0xC018 },
+{ 0xC019, 0xC019, 0xC019 },
+{ 0xC01A, 0xC01A, 0xC01A },
+{ 0xC01B, 0xC01B, 0xC01B },
+{ 0xC01C, 0xC01C, 0xC01C },
+{ 0xC01D, 0xC01D, 0xC01D },
+{ 0xC01E, 0xC01E, 0xC01E },
+{ 0xC01F, 0xC01F, 0xC01F },
+{ 0xC020, 0xC020, 0xC020 },
+{ 0xC021, 0xC021, 0xC021 },
+{ 0xC022, 0xC022, 0xC022 },
+{ 0xC023, 0xC023, 0xC023 },
+{ 0xC024, 0xC024, 0xC024 },
+{ 0xC025, 0xC025, 0xC025 },
+{ 0xC026, 0xC026, 0xC026 },
+{ 0xC027, 0xC027, 0xC027 },
+{ 0xC028, 0xC028, 0xC028 },
+{ 0xC029, 0xC029, 0xC029 },
+{ 0xC02A, 0xC02A, 0xC02A },
+{ 0xC02B, 0xC02B, 0xC02B },
+{ 0xC02C, 0xC02C, 0xC02C },
+{ 0xC02D, 0xC02D, 0xC02D },
+{ 0xC02E, 0xC02E, 0xC02E },
+{ 0xC02F, 0xC02F, 0xC02F },
+{ 0xC030, 0xC030, 0xC030 },
+{ 0xC031, 0xC031, 0xC031 },
+{ 0xC032, 0xC032, 0xC032 },
+{ 0xC033, 0xC033, 0xC033 },
+{ 0xC034, 0xC034, 0xC034 },
+{ 0xC035, 0xC035, 0xC035 },
+{ 0xC036, 0xC036, 0xC036 },
+{ 0xC037, 0xC037, 0xC037 },
+{ 0xC038, 0xC038, 0xC038 },
+{ 0xC039, 0xC039, 0xC039 },
+{ 0xC03A, 0xC03A, 0xC03A },
+{ 0xC03B, 0xC03B, 0xC03B },
+{ 0xC03C, 0xC03C, 0xC03C },
+{ 0xC03D, 0xC03D, 0xC03D },
+{ 0xC03E, 0xC03E, 0xC03E },
+{ 0xC03F, 0xC03F, 0xC03F },
+{ 0xC040, 0xC040, 0xC040 },
+{ 0xC041, 0xC041, 0xC041 },
+{ 0xC042, 0xC042, 0xC042 },
+{ 0xC043, 0xC043, 0xC043 },
+{ 0xC044, 0xC044, 0xC044 },
+{ 0xC045, 0xC045, 0xC045 },
+{ 0xC046, 0xC046, 0xC046 },
+{ 0xC047, 0xC047, 0xC047 },
+{ 0xC048, 0xC048, 0xC048 },
+{ 0xC049, 0xC049, 0xC049 },
+{ 0xC04A, 0xC04A, 0xC04A },
+{ 0xC04B, 0xC04B, 0xC04B },
+{ 0xC04C, 0xC04C, 0xC04C },
+{ 0xC04D, 0xC04D, 0xC04D },
+{ 0xC04E, 0xC04E, 0xC04E },
+{ 0xC04F, 0xC04F, 0xC04F },
+{ 0xC050, 0xC050, 0xC050 },
+{ 0xC051, 0xC051, 0xC051 },
+{ 0xC052, 0xC052, 0xC052 },
+{ 0xC053, 0xC053, 0xC053 },
+{ 0xC054, 0xC054, 0xC054 },
+{ 0xC055, 0xC055, 0xC055 },
+{ 0xC056, 0xC056, 0xC056 },
+{ 0xC057, 0xC057, 0xC057 },
+{ 0xC058, 0xC058, 0xC058 },
+{ 0xC059, 0xC059, 0xC059 },
+{ 0xC05A, 0xC05A, 0xC05A },
+{ 0xC05B, 0xC05B, 0xC05B },
+{ 0xC05C, 0xC05C, 0xC05C },
+{ 0xC05D, 0xC05D, 0xC05D },
+{ 0xC05E, 0xC05E, 0xC05E },
+{ 0xC05F, 0xC05F, 0xC05F },
+{ 0xC060, 0xC060, 0xC060 },
+{ 0xC061, 0xC061, 0xC061 },
+{ 0xC062, 0xC062, 0xC062 },
+{ 0xC063, 0xC063, 0xC063 },
+{ 0xC064, 0xC064, 0xC064 },
+{ 0xC065, 0xC065, 0xC065 },
+{ 0xC066, 0xC066, 0xC066 },
+{ 0xC067, 0xC067, 0xC067 },
+{ 0xC068, 0xC068, 0xC068 },
+{ 0xC069, 0xC069, 0xC069 },
+{ 0xC06A, 0xC06A, 0xC06A },
+{ 0xC06B, 0xC06B, 0xC06B },
+{ 0xC06C, 0xC06C, 0xC06C },
+{ 0xC06D, 0xC06D, 0xC06D },
+{ 0xC06E, 0xC06E, 0xC06E },
+{ 0xC06F, 0xC06F, 0xC06F },
+{ 0xC070, 0xC070, 0xC070 },
+{ 0xC071, 0xC071, 0xC071 },
+{ 0xC072, 0xC072, 0xC072 },
+{ 0xC073, 0xC073, 0xC073 },
+{ 0xC074, 0xC074, 0xC074 },
+{ 0xC075, 0xC075, 0xC075 },
+{ 0xC076, 0xC076, 0xC076 },
+{ 0xC077, 0xC077, 0xC077 },
+{ 0xC078, 0xC078, 0xC078 },
+{ 0xC079, 0xC079, 0xC079 },
+{ 0xC07A, 0xC07A, 0xC07A },
+{ 0xC07B, 0xC07B, 0xC07B },
+{ 0xC07C, 0xC07C, 0xC07C },
+{ 0xC07D, 0xC07D, 0xC07D },
+{ 0xC07E, 0xC07E, 0xC07E },
+{ 0xC07F, 0xC07F, 0xC07F },
+{ 0xC080, 0xC080, 0xC080 },
+{ 0xC081, 0xC081, 0xC081 },
+{ 0xC082, 0xC082, 0xC082 },
+{ 0xC083, 0xC083, 0xC083 },
+{ 0xC084, 0xC084, 0xC084 },
+{ 0xC085, 0xC085, 0xC085 },
+{ 0xC086, 0xC086, 0xC086 },
+{ 0xC087, 0xC087, 0xC087 },
+{ 0xC088, 0xC088, 0xC088 },
+{ 0xC089, 0xC089, 0xC089 },
+{ 0xC08A, 0xC08A, 0xC08A },
+{ 0xC08B, 0xC08B, 0xC08B },
+{ 0xC08C, 0xC08C, 0xC08C },
+{ 0xC08D, 0xC08D, 0xC08D },
+{ 0xC08E, 0xC08E, 0xC08E },
+{ 0xC08F, 0xC08F, 0xC08F },
+{ 0xC090, 0xC090, 0xC090 },
+{ 0xC091, 0xC091, 0xC091 },
+{ 0xC092, 0xC092, 0xC092 },
+{ 0xC093, 0xC093, 0xC093 },
+{ 0xC094, 0xC094, 0xC094 },
+{ 0xC095, 0xC095, 0xC095 },
+{ 0xC096, 0xC096, 0xC096 },
+{ 0xC097, 0xC097, 0xC097 },
+{ 0xC098, 0xC098, 0xC098 },
+{ 0xC099, 0xC099, 0xC099 },
+{ 0xC09A, 0xC09A, 0xC09A },
+{ 0xC09B, 0xC09B, 0xC09B },
+{ 0xC09C, 0xC09C, 0xC09C },
+{ 0xC09D, 0xC09D, 0xC09D },
+{ 0xC09E, 0xC09E, 0xC09E },
+{ 0xC09F, 0xC09F, 0xC09F },
+{ 0xC0A0, 0xC0A0, 0xC0A0 },
+{ 0xC0A1, 0xC0A1, 0xC0A1 },
+{ 0xC0A2, 0xC0A2, 0xC0A2 },
+{ 0xC0A3, 0xC0A3, 0xC0A3 },
+{ 0xC0A4, 0xC0A4, 0xC0A4 },
+{ 0xC0A5, 0xC0A5, 0xC0A5 },
+{ 0xC0A6, 0xC0A6, 0xC0A6 },
+{ 0xC0A7, 0xC0A7, 0xC0A7 },
+{ 0xC0A8, 0xC0A8, 0xC0A8 },
+{ 0xC0A9, 0xC0A9, 0xC0A9 },
+{ 0xC0AA, 0xC0AA, 0xC0AA },
+{ 0xC0AB, 0xC0AB, 0xC0AB },
+{ 0xC0AC, 0xC0AC, 0xC0AC },
+{ 0xC0AD, 0xC0AD, 0xC0AD },
+{ 0xC0AE, 0xC0AE, 0xC0AE },
+{ 0xC0AF, 0xC0AF, 0xC0AF },
+{ 0xC0B0, 0xC0B0, 0xC0B0 },
+{ 0xC0B1, 0xC0B1, 0xC0B1 },
+{ 0xC0B2, 0xC0B2, 0xC0B2 },
+{ 0xC0B3, 0xC0B3, 0xC0B3 },
+{ 0xC0B4, 0xC0B4, 0xC0B4 },
+{ 0xC0B5, 0xC0B5, 0xC0B5 },
+{ 0xC0B6, 0xC0B6, 0xC0B6 },
+{ 0xC0B7, 0xC0B7, 0xC0B7 },
+{ 0xC0B8, 0xC0B8, 0xC0B8 },
+{ 0xC0B9, 0xC0B9, 0xC0B9 },
+{ 0xC0BA, 0xC0BA, 0xC0BA },
+{ 0xC0BB, 0xC0BB, 0xC0BB },
+{ 0xC0BC, 0xC0BC, 0xC0BC },
+{ 0xC0BD, 0xC0BD, 0xC0BD },
+{ 0xC0BE, 0xC0BE, 0xC0BE },
+{ 0xC0BF, 0xC0BF, 0xC0BF },
+{ 0xC0C0, 0xC0C0, 0xC0C0 },
+{ 0xC0C1, 0xC0C1, 0xC0C1 },
+{ 0xC0C2, 0xC0C2, 0xC0C2 },
+{ 0xC0C3, 0xC0C3, 0xC0C3 },
+{ 0xC0C4, 0xC0C4, 0xC0C4 },
+{ 0xC0C5, 0xC0C5, 0xC0C5 },
+{ 0xC0C6, 0xC0C6, 0xC0C6 },
+{ 0xC0C7, 0xC0C7, 0xC0C7 },
+{ 0xC0C8, 0xC0C8, 0xC0C8 },
+{ 0xC0C9, 0xC0C9, 0xC0C9 },
+{ 0xC0CA, 0xC0CA, 0xC0CA },
+{ 0xC0CB, 0xC0CB, 0xC0CB },
+{ 0xC0CC, 0xC0CC, 0xC0CC },
+{ 0xC0CD, 0xC0CD, 0xC0CD },
+{ 0xC0CE, 0xC0CE, 0xC0CE },
+{ 0xC0CF, 0xC0CF, 0xC0CF },
+{ 0xC0D0, 0xC0D0, 0xC0D0 },
+{ 0xC0D1, 0xC0D1, 0xC0D1 },
+{ 0xC0D2, 0xC0D2, 0xC0D2 },
+{ 0xC0D3, 0xC0D3, 0xC0D3 },
+{ 0xC0D4, 0xC0D4, 0xC0D4 },
+{ 0xC0D5, 0xC0D5, 0xC0D5 },
+{ 0xC0D6, 0xC0D6, 0xC0D6 },
+{ 0xC0D7, 0xC0D7, 0xC0D7 },
+{ 0xC0D8, 0xC0D8, 0xC0D8 },
+{ 0xC0D9, 0xC0D9, 0xC0D9 },
+{ 0xC0DA, 0xC0DA, 0xC0DA },
+{ 0xC0DB, 0xC0DB, 0xC0DB },
+{ 0xC0DC, 0xC0DC, 0xC0DC },
+{ 0xC0DD, 0xC0DD, 0xC0DD },
+{ 0xC0DE, 0xC0DE, 0xC0DE },
+{ 0xC0DF, 0xC0DF, 0xC0DF },
+{ 0xC0E0, 0xC0E0, 0xC0E0 },
+{ 0xC0E1, 0xC0E1, 0xC0E1 },
+{ 0xC0E2, 0xC0E2, 0xC0E2 },
+{ 0xC0E3, 0xC0E3, 0xC0E3 },
+{ 0xC0E4, 0xC0E4, 0xC0E4 },
+{ 0xC0E5, 0xC0E5, 0xC0E5 },
+{ 0xC0E6, 0xC0E6, 0xC0E6 },
+{ 0xC0E7, 0xC0E7, 0xC0E7 },
+{ 0xC0E8, 0xC0E8, 0xC0E8 },
+{ 0xC0E9, 0xC0E9, 0xC0E9 },
+{ 0xC0EA, 0xC0EA, 0xC0EA },
+{ 0xC0EB, 0xC0EB, 0xC0EB },
+{ 0xC0EC, 0xC0EC, 0xC0EC },
+{ 0xC0ED, 0xC0ED, 0xC0ED },
+{ 0xC0EE, 0xC0EE, 0xC0EE },
+{ 0xC0EF, 0xC0EF, 0xC0EF },
+{ 0xC0F0, 0xC0F0, 0xC0F0 },
+{ 0xC0F1, 0xC0F1, 0xC0F1 },
+{ 0xC0F2, 0xC0F2, 0xC0F2 },
+{ 0xC0F3, 0xC0F3, 0xC0F3 },
+{ 0xC0F4, 0xC0F4, 0xC0F4 },
+{ 0xC0F5, 0xC0F5, 0xC0F5 },
+{ 0xC0F6, 0xC0F6, 0xC0F6 },
+{ 0xC0F7, 0xC0F7, 0xC0F7 },
+{ 0xC0F8, 0xC0F8, 0xC0F8 },
+{ 0xC0F9, 0xC0F9, 0xC0F9 },
+{ 0xC0FA, 0xC0FA, 0xC0FA },
+{ 0xC0FB, 0xC0FB, 0xC0FB },
+{ 0xC0FC, 0xC0FC, 0xC0FC },
+{ 0xC0FD, 0xC0FD, 0xC0FD },
+{ 0xC0FE, 0xC0FE, 0xC0FE },
+{ 0xC0FF, 0xC0FF, 0xC0FF },
+{ 0xC100, 0xC100, 0xC100 },
+{ 0xC101, 0xC101, 0xC101 },
+{ 0xC102, 0xC102, 0xC102 },
+{ 0xC103, 0xC103, 0xC103 },
+{ 0xC104, 0xC104, 0xC104 },
+{ 0xC105, 0xC105, 0xC105 },
+{ 0xC106, 0xC106, 0xC106 },
+{ 0xC107, 0xC107, 0xC107 },
+{ 0xC108, 0xC108, 0xC108 },
+{ 0xC109, 0xC109, 0xC109 },
+{ 0xC10A, 0xC10A, 0xC10A },
+{ 0xC10B, 0xC10B, 0xC10B },
+{ 0xC10C, 0xC10C, 0xC10C },
+{ 0xC10D, 0xC10D, 0xC10D },
+{ 0xC10E, 0xC10E, 0xC10E },
+{ 0xC10F, 0xC10F, 0xC10F },
+{ 0xC110, 0xC110, 0xC110 },
+{ 0xC111, 0xC111, 0xC111 },
+{ 0xC112, 0xC112, 0xC112 },
+{ 0xC113, 0xC113, 0xC113 },
+{ 0xC114, 0xC114, 0xC114 },
+{ 0xC115, 0xC115, 0xC115 },
+{ 0xC116, 0xC116, 0xC116 },
+{ 0xC117, 0xC117, 0xC117 },
+{ 0xC118, 0xC118, 0xC118 },
+{ 0xC119, 0xC119, 0xC119 },
+{ 0xC11A, 0xC11A, 0xC11A },
+{ 0xC11B, 0xC11B, 0xC11B },
+{ 0xC11C, 0xC11C, 0xC11C },
+{ 0xC11D, 0xC11D, 0xC11D },
+{ 0xC11E, 0xC11E, 0xC11E },
+{ 0xC11F, 0xC11F, 0xC11F },
+{ 0xC120, 0xC120, 0xC120 },
+{ 0xC121, 0xC121, 0xC121 },
+{ 0xC122, 0xC122, 0xC122 },
+{ 0xC123, 0xC123, 0xC123 },
+{ 0xC124, 0xC124, 0xC124 },
+{ 0xC125, 0xC125, 0xC125 },
+{ 0xC126, 0xC126, 0xC126 },
+{ 0xC127, 0xC127, 0xC127 },
+{ 0xC128, 0xC128, 0xC128 },
+{ 0xC129, 0xC129, 0xC129 },
+{ 0xC12A, 0xC12A, 0xC12A },
+{ 0xC12B, 0xC12B, 0xC12B },
+{ 0xC12C, 0xC12C, 0xC12C },
+{ 0xC12D, 0xC12D, 0xC12D },
+{ 0xC12E, 0xC12E, 0xC12E },
+{ 0xC12F, 0xC12F, 0xC12F },
+{ 0xC130, 0xC130, 0xC130 },
+{ 0xC131, 0xC131, 0xC131 },
+{ 0xC132, 0xC132, 0xC132 },
+{ 0xC133, 0xC133, 0xC133 },
+{ 0xC134, 0xC134, 0xC134 },
+{ 0xC135, 0xC135, 0xC135 },
+{ 0xC136, 0xC136, 0xC136 },
+{ 0xC137, 0xC137, 0xC137 },
+{ 0xC138, 0xC138, 0xC138 },
+{ 0xC139, 0xC139, 0xC139 },
+{ 0xC13A, 0xC13A, 0xC13A },
+{ 0xC13B, 0xC13B, 0xC13B },
+{ 0xC13C, 0xC13C, 0xC13C },
+{ 0xC13D, 0xC13D, 0xC13D },
+{ 0xC13E, 0xC13E, 0xC13E },
+{ 0xC13F, 0xC13F, 0xC13F },
+{ 0xC140, 0xC140, 0xC140 },
+{ 0xC141, 0xC141, 0xC141 },
+{ 0xC142, 0xC142, 0xC142 },
+{ 0xC143, 0xC143, 0xC143 },
+{ 0xC144, 0xC144, 0xC144 },
+{ 0xC145, 0xC145, 0xC145 },
+{ 0xC146, 0xC146, 0xC146 },
+{ 0xC147, 0xC147, 0xC147 },
+{ 0xC148, 0xC148, 0xC148 },
+{ 0xC149, 0xC149, 0xC149 },
+{ 0xC14A, 0xC14A, 0xC14A },
+{ 0xC14B, 0xC14B, 0xC14B },
+{ 0xC14C, 0xC14C, 0xC14C },
+{ 0xC14D, 0xC14D, 0xC14D },
+{ 0xC14E, 0xC14E, 0xC14E },
+{ 0xC14F, 0xC14F, 0xC14F },
+{ 0xC150, 0xC150, 0xC150 },
+{ 0xC151, 0xC151, 0xC151 },
+{ 0xC152, 0xC152, 0xC152 },
+{ 0xC153, 0xC153, 0xC153 },
+{ 0xC154, 0xC154, 0xC154 },
+{ 0xC155, 0xC155, 0xC155 },
+{ 0xC156, 0xC156, 0xC156 },
+{ 0xC157, 0xC157, 0xC157 },
+{ 0xC158, 0xC158, 0xC158 },
+{ 0xC159, 0xC159, 0xC159 },
+{ 0xC15A, 0xC15A, 0xC15A },
+{ 0xC15B, 0xC15B, 0xC15B },
+{ 0xC15C, 0xC15C, 0xC15C },
+{ 0xC15D, 0xC15D, 0xC15D },
+{ 0xC15E, 0xC15E, 0xC15E },
+{ 0xC15F, 0xC15F, 0xC15F },
+{ 0xC160, 0xC160, 0xC160 },
+{ 0xC161, 0xC161, 0xC161 },
+{ 0xC162, 0xC162, 0xC162 },
+{ 0xC163, 0xC163, 0xC163 },
+{ 0xC164, 0xC164, 0xC164 },
+{ 0xC165, 0xC165, 0xC165 },
+{ 0xC166, 0xC166, 0xC166 },
+{ 0xC167, 0xC167, 0xC167 },
+{ 0xC168, 0xC168, 0xC168 },
+{ 0xC169, 0xC169, 0xC169 },
+{ 0xC16A, 0xC16A, 0xC16A },
+{ 0xC16B, 0xC16B, 0xC16B },
+{ 0xC16C, 0xC16C, 0xC16C },
+{ 0xC16D, 0xC16D, 0xC16D },
+{ 0xC16E, 0xC16E, 0xC16E },
+{ 0xC16F, 0xC16F, 0xC16F },
+{ 0xC170, 0xC170, 0xC170 },
+{ 0xC171, 0xC171, 0xC171 },
+{ 0xC172, 0xC172, 0xC172 },
+{ 0xC173, 0xC173, 0xC173 },
+{ 0xC174, 0xC174, 0xC174 },
+{ 0xC175, 0xC175, 0xC175 },
+{ 0xC176, 0xC176, 0xC176 },
+{ 0xC177, 0xC177, 0xC177 },
+{ 0xC178, 0xC178, 0xC178 },
+{ 0xC179, 0xC179, 0xC179 },
+{ 0xC17A, 0xC17A, 0xC17A },
+{ 0xC17B, 0xC17B, 0xC17B },
+{ 0xC17C, 0xC17C, 0xC17C },
+{ 0xC17D, 0xC17D, 0xC17D },
+{ 0xC17E, 0xC17E, 0xC17E },
+{ 0xC17F, 0xC17F, 0xC17F },
+{ 0xC180, 0xC180, 0xC180 },
+{ 0xC181, 0xC181, 0xC181 },
+{ 0xC182, 0xC182, 0xC182 },
+{ 0xC183, 0xC183, 0xC183 },
+{ 0xC184, 0xC184, 0xC184 },
+{ 0xC185, 0xC185, 0xC185 },
+{ 0xC186, 0xC186, 0xC186 },
+{ 0xC187, 0xC187, 0xC187 },
+{ 0xC188, 0xC188, 0xC188 },
+{ 0xC189, 0xC189, 0xC189 },
+{ 0xC18A, 0xC18A, 0xC18A },
+{ 0xC18B, 0xC18B, 0xC18B },
+{ 0xC18C, 0xC18C, 0xC18C },
+{ 0xC18D, 0xC18D, 0xC18D },
+{ 0xC18E, 0xC18E, 0xC18E },
+{ 0xC18F, 0xC18F, 0xC18F },
+{ 0xC190, 0xC190, 0xC190 },
+{ 0xC191, 0xC191, 0xC191 },
+{ 0xC192, 0xC192, 0xC192 },
+{ 0xC193, 0xC193, 0xC193 },
+{ 0xC194, 0xC194, 0xC194 },
+{ 0xC195, 0xC195, 0xC195 },
+{ 0xC196, 0xC196, 0xC196 },
+{ 0xC197, 0xC197, 0xC197 },
+{ 0xC198, 0xC198, 0xC198 },
+{ 0xC199, 0xC199, 0xC199 },
+{ 0xC19A, 0xC19A, 0xC19A },
+{ 0xC19B, 0xC19B, 0xC19B },
+{ 0xC19C, 0xC19C, 0xC19C },
+{ 0xC19D, 0xC19D, 0xC19D },
+{ 0xC19E, 0xC19E, 0xC19E },
+{ 0xC19F, 0xC19F, 0xC19F },
+{ 0xC1A0, 0xC1A0, 0xC1A0 },
+{ 0xC1A1, 0xC1A1, 0xC1A1 },
+{ 0xC1A2, 0xC1A2, 0xC1A2 },
+{ 0xC1A3, 0xC1A3, 0xC1A3 },
+{ 0xC1A4, 0xC1A4, 0xC1A4 },
+{ 0xC1A5, 0xC1A5, 0xC1A5 },
+{ 0xC1A6, 0xC1A6, 0xC1A6 },
+{ 0xC1A7, 0xC1A7, 0xC1A7 },
+{ 0xC1A8, 0xC1A8, 0xC1A8 },
+{ 0xC1A9, 0xC1A9, 0xC1A9 },
+{ 0xC1AA, 0xC1AA, 0xC1AA },
+{ 0xC1AB, 0xC1AB, 0xC1AB },
+{ 0xC1AC, 0xC1AC, 0xC1AC },
+{ 0xC1AD, 0xC1AD, 0xC1AD },
+{ 0xC1AE, 0xC1AE, 0xC1AE },
+{ 0xC1AF, 0xC1AF, 0xC1AF },
+{ 0xC1B0, 0xC1B0, 0xC1B0 },
+{ 0xC1B1, 0xC1B1, 0xC1B1 },
+{ 0xC1B2, 0xC1B2, 0xC1B2 },
+{ 0xC1B3, 0xC1B3, 0xC1B3 },
+{ 0xC1B4, 0xC1B4, 0xC1B4 },
+{ 0xC1B5, 0xC1B5, 0xC1B5 },
+{ 0xC1B6, 0xC1B6, 0xC1B6 },
+{ 0xC1B7, 0xC1B7, 0xC1B7 },
+{ 0xC1B8, 0xC1B8, 0xC1B8 },
+{ 0xC1B9, 0xC1B9, 0xC1B9 },
+{ 0xC1BA, 0xC1BA, 0xC1BA },
+{ 0xC1BB, 0xC1BB, 0xC1BB },
+{ 0xC1BC, 0xC1BC, 0xC1BC },
+{ 0xC1BD, 0xC1BD, 0xC1BD },
+{ 0xC1BE, 0xC1BE, 0xC1BE },
+{ 0xC1BF, 0xC1BF, 0xC1BF },
+{ 0xC1C0, 0xC1C0, 0xC1C0 },
+{ 0xC1C1, 0xC1C1, 0xC1C1 },
+{ 0xC1C2, 0xC1C2, 0xC1C2 },
+{ 0xC1C3, 0xC1C3, 0xC1C3 },
+{ 0xC1C4, 0xC1C4, 0xC1C4 },
+{ 0xC1C5, 0xC1C5, 0xC1C5 },
+{ 0xC1C6, 0xC1C6, 0xC1C6 },
+{ 0xC1C7, 0xC1C7, 0xC1C7 },
+{ 0xC1C8, 0xC1C8, 0xC1C8 },
+{ 0xC1C9, 0xC1C9, 0xC1C9 },
+{ 0xC1CA, 0xC1CA, 0xC1CA },
+{ 0xC1CB, 0xC1CB, 0xC1CB },
+{ 0xC1CC, 0xC1CC, 0xC1CC },
+{ 0xC1CD, 0xC1CD, 0xC1CD },
+{ 0xC1CE, 0xC1CE, 0xC1CE },
+{ 0xC1CF, 0xC1CF, 0xC1CF },
+{ 0xC1D0, 0xC1D0, 0xC1D0 },
+{ 0xC1D1, 0xC1D1, 0xC1D1 },
+{ 0xC1D2, 0xC1D2, 0xC1D2 },
+{ 0xC1D3, 0xC1D3, 0xC1D3 },
+{ 0xC1D4, 0xC1D4, 0xC1D4 },
+{ 0xC1D5, 0xC1D5, 0xC1D5 },
+{ 0xC1D6, 0xC1D6, 0xC1D6 },
+{ 0xC1D7, 0xC1D7, 0xC1D7 },
+{ 0xC1D8, 0xC1D8, 0xC1D8 },
+{ 0xC1D9, 0xC1D9, 0xC1D9 },
+{ 0xC1DA, 0xC1DA, 0xC1DA },
+{ 0xC1DB, 0xC1DB, 0xC1DB },
+{ 0xC1DC, 0xC1DC, 0xC1DC },
+{ 0xC1DD, 0xC1DD, 0xC1DD },
+{ 0xC1DE, 0xC1DE, 0xC1DE },
+{ 0xC1DF, 0xC1DF, 0xC1DF },
+{ 0xC1E0, 0xC1E0, 0xC1E0 },
+{ 0xC1E1, 0xC1E1, 0xC1E1 },
+{ 0xC1E2, 0xC1E2, 0xC1E2 },
+{ 0xC1E3, 0xC1E3, 0xC1E3 },
+{ 0xC1E4, 0xC1E4, 0xC1E4 },
+{ 0xC1E5, 0xC1E5, 0xC1E5 },
+{ 0xC1E6, 0xC1E6, 0xC1E6 },
+{ 0xC1E7, 0xC1E7, 0xC1E7 },
+{ 0xC1E8, 0xC1E8, 0xC1E8 },
+{ 0xC1E9, 0xC1E9, 0xC1E9 },
+{ 0xC1EA, 0xC1EA, 0xC1EA },
+{ 0xC1EB, 0xC1EB, 0xC1EB },
+{ 0xC1EC, 0xC1EC, 0xC1EC },
+{ 0xC1ED, 0xC1ED, 0xC1ED },
+{ 0xC1EE, 0xC1EE, 0xC1EE },
+{ 0xC1EF, 0xC1EF, 0xC1EF },
+{ 0xC1F0, 0xC1F0, 0xC1F0 },
+{ 0xC1F1, 0xC1F1, 0xC1F1 },
+{ 0xC1F2, 0xC1F2, 0xC1F2 },
+{ 0xC1F3, 0xC1F3, 0xC1F3 },
+{ 0xC1F4, 0xC1F4, 0xC1F4 },
+{ 0xC1F5, 0xC1F5, 0xC1F5 },
+{ 0xC1F6, 0xC1F6, 0xC1F6 },
+{ 0xC1F7, 0xC1F7, 0xC1F7 },
+{ 0xC1F8, 0xC1F8, 0xC1F8 },
+{ 0xC1F9, 0xC1F9, 0xC1F9 },
+{ 0xC1FA, 0xC1FA, 0xC1FA },
+{ 0xC1FB, 0xC1FB, 0xC1FB },
+{ 0xC1FC, 0xC1FC, 0xC1FC },
+{ 0xC1FD, 0xC1FD, 0xC1FD },
+{ 0xC1FE, 0xC1FE, 0xC1FE },
+{ 0xC1FF, 0xC1FF, 0xC1FF },
+{ 0xC200, 0xC200, 0xC200 },
+{ 0xC201, 0xC201, 0xC201 },
+{ 0xC202, 0xC202, 0xC202 },
+{ 0xC203, 0xC203, 0xC203 },
+{ 0xC204, 0xC204, 0xC204 },
+{ 0xC205, 0xC205, 0xC205 },
+{ 0xC206, 0xC206, 0xC206 },
+{ 0xC207, 0xC207, 0xC207 },
+{ 0xC208, 0xC208, 0xC208 },
+{ 0xC209, 0xC209, 0xC209 },
+{ 0xC20A, 0xC20A, 0xC20A },
+{ 0xC20B, 0xC20B, 0xC20B },
+{ 0xC20C, 0xC20C, 0xC20C },
+{ 0xC20D, 0xC20D, 0xC20D },
+{ 0xC20E, 0xC20E, 0xC20E },
+{ 0xC20F, 0xC20F, 0xC20F },
+{ 0xC210, 0xC210, 0xC210 },
+{ 0xC211, 0xC211, 0xC211 },
+{ 0xC212, 0xC212, 0xC212 },
+{ 0xC213, 0xC213, 0xC213 },
+{ 0xC214, 0xC214, 0xC214 },
+{ 0xC215, 0xC215, 0xC215 },
+{ 0xC216, 0xC216, 0xC216 },
+{ 0xC217, 0xC217, 0xC217 },
+{ 0xC218, 0xC218, 0xC218 },
+{ 0xC219, 0xC219, 0xC219 },
+{ 0xC21A, 0xC21A, 0xC21A },
+{ 0xC21B, 0xC21B, 0xC21B },
+{ 0xC21C, 0xC21C, 0xC21C },
+{ 0xC21D, 0xC21D, 0xC21D },
+{ 0xC21E, 0xC21E, 0xC21E },
+{ 0xC21F, 0xC21F, 0xC21F },
+{ 0xC220, 0xC220, 0xC220 },
+{ 0xC221, 0xC221, 0xC221 },
+{ 0xC222, 0xC222, 0xC222 },
+{ 0xC223, 0xC223, 0xC223 },
+{ 0xC224, 0xC224, 0xC224 },
+{ 0xC225, 0xC225, 0xC225 },
+{ 0xC226, 0xC226, 0xC226 },
+{ 0xC227, 0xC227, 0xC227 },
+{ 0xC228, 0xC228, 0xC228 },
+{ 0xC229, 0xC229, 0xC229 },
+{ 0xC22A, 0xC22A, 0xC22A },
+{ 0xC22B, 0xC22B, 0xC22B },
+{ 0xC22C, 0xC22C, 0xC22C },
+{ 0xC22D, 0xC22D, 0xC22D },
+{ 0xC22E, 0xC22E, 0xC22E },
+{ 0xC22F, 0xC22F, 0xC22F },
+{ 0xC230, 0xC230, 0xC230 },
+{ 0xC231, 0xC231, 0xC231 },
+{ 0xC232, 0xC232, 0xC232 },
+{ 0xC233, 0xC233, 0xC233 },
+{ 0xC234, 0xC234, 0xC234 },
+{ 0xC235, 0xC235, 0xC235 },
+{ 0xC236, 0xC236, 0xC236 },
+{ 0xC237, 0xC237, 0xC237 },
+{ 0xC238, 0xC238, 0xC238 },
+{ 0xC239, 0xC239, 0xC239 },
+{ 0xC23A, 0xC23A, 0xC23A },
+{ 0xC23B, 0xC23B, 0xC23B },
+{ 0xC23C, 0xC23C, 0xC23C },
+{ 0xC23D, 0xC23D, 0xC23D },
+{ 0xC23E, 0xC23E, 0xC23E },
+{ 0xC23F, 0xC23F, 0xC23F },
+{ 0xC240, 0xC240, 0xC240 },
+{ 0xC241, 0xC241, 0xC241 },
+{ 0xC242, 0xC242, 0xC242 },
+{ 0xC243, 0xC243, 0xC243 },
+{ 0xC244, 0xC244, 0xC244 },
+{ 0xC245, 0xC245, 0xC245 },
+{ 0xC246, 0xC246, 0xC246 },
+{ 0xC247, 0xC247, 0xC247 },
+{ 0xC248, 0xC248, 0xC248 },
+{ 0xC249, 0xC249, 0xC249 },
+{ 0xC24A, 0xC24A, 0xC24A },
+{ 0xC24B, 0xC24B, 0xC24B },
+{ 0xC24C, 0xC24C, 0xC24C },
+{ 0xC24D, 0xC24D, 0xC24D },
+{ 0xC24E, 0xC24E, 0xC24E },
+{ 0xC24F, 0xC24F, 0xC24F },
+{ 0xC250, 0xC250, 0xC250 },
+{ 0xC251, 0xC251, 0xC251 },
+{ 0xC252, 0xC252, 0xC252 },
+{ 0xC253, 0xC253, 0xC253 },
+{ 0xC254, 0xC254, 0xC254 },
+{ 0xC255, 0xC255, 0xC255 },
+{ 0xC256, 0xC256, 0xC256 },
+{ 0xC257, 0xC257, 0xC257 },
+{ 0xC258, 0xC258, 0xC258 },
+{ 0xC259, 0xC259, 0xC259 },
+{ 0xC25A, 0xC25A, 0xC25A },
+{ 0xC25B, 0xC25B, 0xC25B },
+{ 0xC25C, 0xC25C, 0xC25C },
+{ 0xC25D, 0xC25D, 0xC25D },
+{ 0xC25E, 0xC25E, 0xC25E },
+{ 0xC25F, 0xC25F, 0xC25F },
+{ 0xC260, 0xC260, 0xC260 },
+{ 0xC261, 0xC261, 0xC261 },
+{ 0xC262, 0xC262, 0xC262 },
+{ 0xC263, 0xC263, 0xC263 },
+{ 0xC264, 0xC264, 0xC264 },
+{ 0xC265, 0xC265, 0xC265 },
+{ 0xC266, 0xC266, 0xC266 },
+{ 0xC267, 0xC267, 0xC267 },
+{ 0xC268, 0xC268, 0xC268 },
+{ 0xC269, 0xC269, 0xC269 },
+{ 0xC26A, 0xC26A, 0xC26A },
+{ 0xC26B, 0xC26B, 0xC26B },
+{ 0xC26C, 0xC26C, 0xC26C },
+{ 0xC26D, 0xC26D, 0xC26D },
+{ 0xC26E, 0xC26E, 0xC26E },
+{ 0xC26F, 0xC26F, 0xC26F },
+{ 0xC270, 0xC270, 0xC270 },
+{ 0xC271, 0xC271, 0xC271 },
+{ 0xC272, 0xC272, 0xC272 },
+{ 0xC273, 0xC273, 0xC273 },
+{ 0xC274, 0xC274, 0xC274 },
+{ 0xC275, 0xC275, 0xC275 },
+{ 0xC276, 0xC276, 0xC276 },
+{ 0xC277, 0xC277, 0xC277 },
+{ 0xC278, 0xC278, 0xC278 },
+{ 0xC279, 0xC279, 0xC279 },
+{ 0xC27A, 0xC27A, 0xC27A },
+{ 0xC27B, 0xC27B, 0xC27B },
+{ 0xC27C, 0xC27C, 0xC27C },
+{ 0xC27D, 0xC27D, 0xC27D },
+{ 0xC27E, 0xC27E, 0xC27E },
+{ 0xC27F, 0xC27F, 0xC27F },
+{ 0xC280, 0xC280, 0xC280 },
+{ 0xC281, 0xC281, 0xC281 },
+{ 0xC282, 0xC282, 0xC282 },
+{ 0xC283, 0xC283, 0xC283 },
+{ 0xC284, 0xC284, 0xC284 },
+{ 0xC285, 0xC285, 0xC285 },
+{ 0xC286, 0xC286, 0xC286 },
+{ 0xC287, 0xC287, 0xC287 },
+{ 0xC288, 0xC288, 0xC288 },
+{ 0xC289, 0xC289, 0xC289 },
+{ 0xC28A, 0xC28A, 0xC28A },
+{ 0xC28B, 0xC28B, 0xC28B },
+{ 0xC28C, 0xC28C, 0xC28C },
+{ 0xC28D, 0xC28D, 0xC28D },
+{ 0xC28E, 0xC28E, 0xC28E },
+{ 0xC28F, 0xC28F, 0xC28F },
+{ 0xC290, 0xC290, 0xC290 },
+{ 0xC291, 0xC291, 0xC291 },
+{ 0xC292, 0xC292, 0xC292 },
+{ 0xC293, 0xC293, 0xC293 },
+{ 0xC294, 0xC294, 0xC294 },
+{ 0xC295, 0xC295, 0xC295 },
+{ 0xC296, 0xC296, 0xC296 },
+{ 0xC297, 0xC297, 0xC297 },
+{ 0xC298, 0xC298, 0xC298 },
+{ 0xC299, 0xC299, 0xC299 },
+{ 0xC29A, 0xC29A, 0xC29A },
+{ 0xC29B, 0xC29B, 0xC29B },
+{ 0xC29C, 0xC29C, 0xC29C },
+{ 0xC29D, 0xC29D, 0xC29D },
+{ 0xC29E, 0xC29E, 0xC29E },
+{ 0xC29F, 0xC29F, 0xC29F },
+{ 0xC2A0, 0xC2A0, 0xC2A0 },
+{ 0xC2A1, 0xC2A1, 0xC2A1 },
+{ 0xC2A2, 0xC2A2, 0xC2A2 },
+{ 0xC2A3, 0xC2A3, 0xC2A3 },
+{ 0xC2A4, 0xC2A4, 0xC2A4 },
+{ 0xC2A5, 0xC2A5, 0xC2A5 },
+{ 0xC2A6, 0xC2A6, 0xC2A6 },
+{ 0xC2A7, 0xC2A7, 0xC2A7 },
+{ 0xC2A8, 0xC2A8, 0xC2A8 },
+{ 0xC2A9, 0xC2A9, 0xC2A9 },
+{ 0xC2AA, 0xC2AA, 0xC2AA },
+{ 0xC2AB, 0xC2AB, 0xC2AB },
+{ 0xC2AC, 0xC2AC, 0xC2AC },
+{ 0xC2AD, 0xC2AD, 0xC2AD },
+{ 0xC2AE, 0xC2AE, 0xC2AE },
+{ 0xC2AF, 0xC2AF, 0xC2AF },
+{ 0xC2B0, 0xC2B0, 0xC2B0 },
+{ 0xC2B1, 0xC2B1, 0xC2B1 },
+{ 0xC2B2, 0xC2B2, 0xC2B2 },
+{ 0xC2B3, 0xC2B3, 0xC2B3 },
+{ 0xC2B4, 0xC2B4, 0xC2B4 },
+{ 0xC2B5, 0xC2B5, 0xC2B5 },
+{ 0xC2B6, 0xC2B6, 0xC2B6 },
+{ 0xC2B7, 0xC2B7, 0xC2B7 },
+{ 0xC2B8, 0xC2B8, 0xC2B8 },
+{ 0xC2B9, 0xC2B9, 0xC2B9 },
+{ 0xC2BA, 0xC2BA, 0xC2BA },
+{ 0xC2BB, 0xC2BB, 0xC2BB },
+{ 0xC2BC, 0xC2BC, 0xC2BC },
+{ 0xC2BD, 0xC2BD, 0xC2BD },
+{ 0xC2BE, 0xC2BE, 0xC2BE },
+{ 0xC2BF, 0xC2BF, 0xC2BF },
+{ 0xC2C0, 0xC2C0, 0xC2C0 },
+{ 0xC2C1, 0xC2C1, 0xC2C1 },
+{ 0xC2C2, 0xC2C2, 0xC2C2 },
+{ 0xC2C3, 0xC2C3, 0xC2C3 },
+{ 0xC2C4, 0xC2C4, 0xC2C4 },
+{ 0xC2C5, 0xC2C5, 0xC2C5 },
+{ 0xC2C6, 0xC2C6, 0xC2C6 },
+{ 0xC2C7, 0xC2C7, 0xC2C7 },
+{ 0xC2C8, 0xC2C8, 0xC2C8 },
+{ 0xC2C9, 0xC2C9, 0xC2C9 },
+{ 0xC2CA, 0xC2CA, 0xC2CA },
+{ 0xC2CB, 0xC2CB, 0xC2CB },
+{ 0xC2CC, 0xC2CC, 0xC2CC },
+{ 0xC2CD, 0xC2CD, 0xC2CD },
+{ 0xC2CE, 0xC2CE, 0xC2CE },
+{ 0xC2CF, 0xC2CF, 0xC2CF },
+{ 0xC2D0, 0xC2D0, 0xC2D0 },
+{ 0xC2D1, 0xC2D1, 0xC2D1 },
+{ 0xC2D2, 0xC2D2, 0xC2D2 },
+{ 0xC2D3, 0xC2D3, 0xC2D3 },
+{ 0xC2D4, 0xC2D4, 0xC2D4 },
+{ 0xC2D5, 0xC2D5, 0xC2D5 },
+{ 0xC2D6, 0xC2D6, 0xC2D6 },
+{ 0xC2D7, 0xC2D7, 0xC2D7 },
+{ 0xC2D8, 0xC2D8, 0xC2D8 },
+{ 0xC2D9, 0xC2D9, 0xC2D9 },
+{ 0xC2DA, 0xC2DA, 0xC2DA },
+{ 0xC2DB, 0xC2DB, 0xC2DB },
+{ 0xC2DC, 0xC2DC, 0xC2DC },
+{ 0xC2DD, 0xC2DD, 0xC2DD },
+{ 0xC2DE, 0xC2DE, 0xC2DE },
+{ 0xC2DF, 0xC2DF, 0xC2DF },
+{ 0xC2E0, 0xC2E0, 0xC2E0 },
+{ 0xC2E1, 0xC2E1, 0xC2E1 },
+{ 0xC2E2, 0xC2E2, 0xC2E2 },
+{ 0xC2E3, 0xC2E3, 0xC2E3 },
+{ 0xC2E4, 0xC2E4, 0xC2E4 },
+{ 0xC2E5, 0xC2E5, 0xC2E5 },
+{ 0xC2E6, 0xC2E6, 0xC2E6 },
+{ 0xC2E7, 0xC2E7, 0xC2E7 },
+{ 0xC2E8, 0xC2E8, 0xC2E8 },
+{ 0xC2E9, 0xC2E9, 0xC2E9 },
+{ 0xC2EA, 0xC2EA, 0xC2EA },
+{ 0xC2EB, 0xC2EB, 0xC2EB },
+{ 0xC2EC, 0xC2EC, 0xC2EC },
+{ 0xC2ED, 0xC2ED, 0xC2ED },
+{ 0xC2EE, 0xC2EE, 0xC2EE },
+{ 0xC2EF, 0xC2EF, 0xC2EF },
+{ 0xC2F0, 0xC2F0, 0xC2F0 },
+{ 0xC2F1, 0xC2F1, 0xC2F1 },
+{ 0xC2F2, 0xC2F2, 0xC2F2 },
+{ 0xC2F3, 0xC2F3, 0xC2F3 },
+{ 0xC2F4, 0xC2F4, 0xC2F4 },
+{ 0xC2F5, 0xC2F5, 0xC2F5 },
+{ 0xC2F6, 0xC2F6, 0xC2F6 },
+{ 0xC2F7, 0xC2F7, 0xC2F7 },
+{ 0xC2F8, 0xC2F8, 0xC2F8 },
+{ 0xC2F9, 0xC2F9, 0xC2F9 },
+{ 0xC2FA, 0xC2FA, 0xC2FA },
+{ 0xC2FB, 0xC2FB, 0xC2FB },
+{ 0xC2FC, 0xC2FC, 0xC2FC },
+{ 0xC2FD, 0xC2FD, 0xC2FD },
+{ 0xC2FE, 0xC2FE, 0xC2FE },
+{ 0xC2FF, 0xC2FF, 0xC2FF },
+{ 0xC300, 0xC300, 0xC300 },
+{ 0xC301, 0xC301, 0xC301 },
+{ 0xC302, 0xC302, 0xC302 },
+{ 0xC303, 0xC303, 0xC303 },
+{ 0xC304, 0xC304, 0xC304 },
+{ 0xC305, 0xC305, 0xC305 },
+{ 0xC306, 0xC306, 0xC306 },
+{ 0xC307, 0xC307, 0xC307 },
+{ 0xC308, 0xC308, 0xC308 },
+{ 0xC309, 0xC309, 0xC309 },
+{ 0xC30A, 0xC30A, 0xC30A },
+{ 0xC30B, 0xC30B, 0xC30B },
+{ 0xC30C, 0xC30C, 0xC30C },
+{ 0xC30D, 0xC30D, 0xC30D },
+{ 0xC30E, 0xC30E, 0xC30E },
+{ 0xC30F, 0xC30F, 0xC30F },
+{ 0xC310, 0xC310, 0xC310 },
+{ 0xC311, 0xC311, 0xC311 },
+{ 0xC312, 0xC312, 0xC312 },
+{ 0xC313, 0xC313, 0xC313 },
+{ 0xC314, 0xC314, 0xC314 },
+{ 0xC315, 0xC315, 0xC315 },
+{ 0xC316, 0xC316, 0xC316 },
+{ 0xC317, 0xC317, 0xC317 },
+{ 0xC318, 0xC318, 0xC318 },
+{ 0xC319, 0xC319, 0xC319 },
+{ 0xC31A, 0xC31A, 0xC31A },
+{ 0xC31B, 0xC31B, 0xC31B },
+{ 0xC31C, 0xC31C, 0xC31C },
+{ 0xC31D, 0xC31D, 0xC31D },
+{ 0xC31E, 0xC31E, 0xC31E },
+{ 0xC31F, 0xC31F, 0xC31F },
+{ 0xC320, 0xC320, 0xC320 },
+{ 0xC321, 0xC321, 0xC321 },
+{ 0xC322, 0xC322, 0xC322 },
+{ 0xC323, 0xC323, 0xC323 },
+{ 0xC324, 0xC324, 0xC324 },
+{ 0xC325, 0xC325, 0xC325 },
+{ 0xC326, 0xC326, 0xC326 },
+{ 0xC327, 0xC327, 0xC327 },
+{ 0xC328, 0xC328, 0xC328 },
+{ 0xC329, 0xC329, 0xC329 },
+{ 0xC32A, 0xC32A, 0xC32A },
+{ 0xC32B, 0xC32B, 0xC32B },
+{ 0xC32C, 0xC32C, 0xC32C },
+{ 0xC32D, 0xC32D, 0xC32D },
+{ 0xC32E, 0xC32E, 0xC32E },
+{ 0xC32F, 0xC32F, 0xC32F },
+{ 0xC330, 0xC330, 0xC330 },
+{ 0xC331, 0xC331, 0xC331 },
+{ 0xC332, 0xC332, 0xC332 },
+{ 0xC333, 0xC333, 0xC333 },
+{ 0xC334, 0xC334, 0xC334 },
+{ 0xC335, 0xC335, 0xC335 },
+{ 0xC336, 0xC336, 0xC336 },
+{ 0xC337, 0xC337, 0xC337 },
+{ 0xC338, 0xC338, 0xC338 },
+{ 0xC339, 0xC339, 0xC339 },
+{ 0xC33A, 0xC33A, 0xC33A },
+{ 0xC33B, 0xC33B, 0xC33B },
+{ 0xC33C, 0xC33C, 0xC33C },
+{ 0xC33D, 0xC33D, 0xC33D },
+{ 0xC33E, 0xC33E, 0xC33E },
+{ 0xC33F, 0xC33F, 0xC33F },
+{ 0xC340, 0xC340, 0xC340 },
+{ 0xC341, 0xC341, 0xC341 },
+{ 0xC342, 0xC342, 0xC342 },
+{ 0xC343, 0xC343, 0xC343 },
+{ 0xC344, 0xC344, 0xC344 },
+{ 0xC345, 0xC345, 0xC345 },
+{ 0xC346, 0xC346, 0xC346 },
+{ 0xC347, 0xC347, 0xC347 },
+{ 0xC348, 0xC348, 0xC348 },
+{ 0xC349, 0xC349, 0xC349 },
+{ 0xC34A, 0xC34A, 0xC34A },
+{ 0xC34B, 0xC34B, 0xC34B },
+{ 0xC34C, 0xC34C, 0xC34C },
+{ 0xC34D, 0xC34D, 0xC34D },
+{ 0xC34E, 0xC34E, 0xC34E },
+{ 0xC34F, 0xC34F, 0xC34F },
+{ 0xC350, 0xC350, 0xC350 },
+{ 0xC351, 0xC351, 0xC351 },
+{ 0xC352, 0xC352, 0xC352 },
+{ 0xC353, 0xC353, 0xC353 },
+{ 0xC354, 0xC354, 0xC354 },
+{ 0xC355, 0xC355, 0xC355 },
+{ 0xC356, 0xC356, 0xC356 },
+{ 0xC357, 0xC357, 0xC357 },
+{ 0xC358, 0xC358, 0xC358 },
+{ 0xC359, 0xC359, 0xC359 },
+{ 0xC35A, 0xC35A, 0xC35A },
+{ 0xC35B, 0xC35B, 0xC35B },
+{ 0xC35C, 0xC35C, 0xC35C },
+{ 0xC35D, 0xC35D, 0xC35D },
+{ 0xC35E, 0xC35E, 0xC35E },
+{ 0xC35F, 0xC35F, 0xC35F },
+{ 0xC360, 0xC360, 0xC360 },
+{ 0xC361, 0xC361, 0xC361 },
+{ 0xC362, 0xC362, 0xC362 },
+{ 0xC363, 0xC363, 0xC363 },
+{ 0xC364, 0xC364, 0xC364 },
+{ 0xC365, 0xC365, 0xC365 },
+{ 0xC366, 0xC366, 0xC366 },
+{ 0xC367, 0xC367, 0xC367 },
+{ 0xC368, 0xC368, 0xC368 },
+{ 0xC369, 0xC369, 0xC369 },
+{ 0xC36A, 0xC36A, 0xC36A },
+{ 0xC36B, 0xC36B, 0xC36B },
+{ 0xC36C, 0xC36C, 0xC36C },
+{ 0xC36D, 0xC36D, 0xC36D },
+{ 0xC36E, 0xC36E, 0xC36E },
+{ 0xC36F, 0xC36F, 0xC36F },
+{ 0xC370, 0xC370, 0xC370 },
+{ 0xC371, 0xC371, 0xC371 },
+{ 0xC372, 0xC372, 0xC372 },
+{ 0xC373, 0xC373, 0xC373 },
+{ 0xC374, 0xC374, 0xC374 },
+{ 0xC375, 0xC375, 0xC375 },
+{ 0xC376, 0xC376, 0xC376 },
+{ 0xC377, 0xC377, 0xC377 },
+{ 0xC378, 0xC378, 0xC378 },
+{ 0xC379, 0xC379, 0xC379 },
+{ 0xC37A, 0xC37A, 0xC37A },
+{ 0xC37B, 0xC37B, 0xC37B },
+{ 0xC37C, 0xC37C, 0xC37C },
+{ 0xC37D, 0xC37D, 0xC37D },
+{ 0xC37E, 0xC37E, 0xC37E },
+{ 0xC37F, 0xC37F, 0xC37F },
+{ 0xC380, 0xC380, 0xC380 },
+{ 0xC381, 0xC381, 0xC381 },
+{ 0xC382, 0xC382, 0xC382 },
+{ 0xC383, 0xC383, 0xC383 },
+{ 0xC384, 0xC384, 0xC384 },
+{ 0xC385, 0xC385, 0xC385 },
+{ 0xC386, 0xC386, 0xC386 },
+{ 0xC387, 0xC387, 0xC387 },
+{ 0xC388, 0xC388, 0xC388 },
+{ 0xC389, 0xC389, 0xC389 },
+{ 0xC38A, 0xC38A, 0xC38A },
+{ 0xC38B, 0xC38B, 0xC38B },
+{ 0xC38C, 0xC38C, 0xC38C },
+{ 0xC38D, 0xC38D, 0xC38D },
+{ 0xC38E, 0xC38E, 0xC38E },
+{ 0xC38F, 0xC38F, 0xC38F },
+{ 0xC390, 0xC390, 0xC390 },
+{ 0xC391, 0xC391, 0xC391 },
+{ 0xC392, 0xC392, 0xC392 },
+{ 0xC393, 0xC393, 0xC393 },
+{ 0xC394, 0xC394, 0xC394 },
+{ 0xC395, 0xC395, 0xC395 },
+{ 0xC396, 0xC396, 0xC396 },
+{ 0xC397, 0xC397, 0xC397 },
+{ 0xC398, 0xC398, 0xC398 },
+{ 0xC399, 0xC399, 0xC399 },
+{ 0xC39A, 0xC39A, 0xC39A },
+{ 0xC39B, 0xC39B, 0xC39B },
+{ 0xC39C, 0xC39C, 0xC39C },
+{ 0xC39D, 0xC39D, 0xC39D },
+{ 0xC39E, 0xC39E, 0xC39E },
+{ 0xC39F, 0xC39F, 0xC39F },
+{ 0xC3A0, 0xC3A0, 0xC3A0 },
+{ 0xC3A1, 0xC3A1, 0xC3A1 },
+{ 0xC3A2, 0xC3A2, 0xC3A2 },
+{ 0xC3A3, 0xC3A3, 0xC3A3 },
+{ 0xC3A4, 0xC3A4, 0xC3A4 },
+{ 0xC3A5, 0xC3A5, 0xC3A5 },
+{ 0xC3A6, 0xC3A6, 0xC3A6 },
+{ 0xC3A7, 0xC3A7, 0xC3A7 },
+{ 0xC3A8, 0xC3A8, 0xC3A8 },
+{ 0xC3A9, 0xC3A9, 0xC3A9 },
+{ 0xC3AA, 0xC3AA, 0xC3AA },
+{ 0xC3AB, 0xC3AB, 0xC3AB },
+{ 0xC3AC, 0xC3AC, 0xC3AC },
+{ 0xC3AD, 0xC3AD, 0xC3AD },
+{ 0xC3AE, 0xC3AE, 0xC3AE },
+{ 0xC3AF, 0xC3AF, 0xC3AF },
+{ 0xC3B0, 0xC3B0, 0xC3B0 },
+{ 0xC3B1, 0xC3B1, 0xC3B1 },
+{ 0xC3B2, 0xC3B2, 0xC3B2 },
+{ 0xC3B3, 0xC3B3, 0xC3B3 },
+{ 0xC3B4, 0xC3B4, 0xC3B4 },
+{ 0xC3B5, 0xC3B5, 0xC3B5 },
+{ 0xC3B6, 0xC3B6, 0xC3B6 },
+{ 0xC3B7, 0xC3B7, 0xC3B7 },
+{ 0xC3B8, 0xC3B8, 0xC3B8 },
+{ 0xC3B9, 0xC3B9, 0xC3B9 },
+{ 0xC3BA, 0xC3BA, 0xC3BA },
+{ 0xC3BB, 0xC3BB, 0xC3BB },
+{ 0xC3BC, 0xC3BC, 0xC3BC },
+{ 0xC3BD, 0xC3BD, 0xC3BD },
+{ 0xC3BE, 0xC3BE, 0xC3BE },
+{ 0xC3BF, 0xC3BF, 0xC3BF },
+{ 0xC3C0, 0xC3C0, 0xC3C0 },
+{ 0xC3C1, 0xC3C1, 0xC3C1 },
+{ 0xC3C2, 0xC3C2, 0xC3C2 },
+{ 0xC3C3, 0xC3C3, 0xC3C3 },
+{ 0xC3C4, 0xC3C4, 0xC3C4 },
+{ 0xC3C5, 0xC3C5, 0xC3C5 },
+{ 0xC3C6, 0xC3C6, 0xC3C6 },
+{ 0xC3C7, 0xC3C7, 0xC3C7 },
+{ 0xC3C8, 0xC3C8, 0xC3C8 },
+{ 0xC3C9, 0xC3C9, 0xC3C9 },
+{ 0xC3CA, 0xC3CA, 0xC3CA },
+{ 0xC3CB, 0xC3CB, 0xC3CB },
+{ 0xC3CC, 0xC3CC, 0xC3CC },
+{ 0xC3CD, 0xC3CD, 0xC3CD },
+{ 0xC3CE, 0xC3CE, 0xC3CE },
+{ 0xC3CF, 0xC3CF, 0xC3CF },
+{ 0xC3D0, 0xC3D0, 0xC3D0 },
+{ 0xC3D1, 0xC3D1, 0xC3D1 },
+{ 0xC3D2, 0xC3D2, 0xC3D2 },
+{ 0xC3D3, 0xC3D3, 0xC3D3 },
+{ 0xC3D4, 0xC3D4, 0xC3D4 },
+{ 0xC3D5, 0xC3D5, 0xC3D5 },
+{ 0xC3D6, 0xC3D6, 0xC3D6 },
+{ 0xC3D7, 0xC3D7, 0xC3D7 },
+{ 0xC3D8, 0xC3D8, 0xC3D8 },
+{ 0xC3D9, 0xC3D9, 0xC3D9 },
+{ 0xC3DA, 0xC3DA, 0xC3DA },
+{ 0xC3DB, 0xC3DB, 0xC3DB },
+{ 0xC3DC, 0xC3DC, 0xC3DC },
+{ 0xC3DD, 0xC3DD, 0xC3DD },
+{ 0xC3DE, 0xC3DE, 0xC3DE },
+{ 0xC3DF, 0xC3DF, 0xC3DF },
+{ 0xC3E0, 0xC3E0, 0xC3E0 },
+{ 0xC3E1, 0xC3E1, 0xC3E1 },
+{ 0xC3E2, 0xC3E2, 0xC3E2 },
+{ 0xC3E3, 0xC3E3, 0xC3E3 },
+{ 0xC3E4, 0xC3E4, 0xC3E4 },
+{ 0xC3E5, 0xC3E5, 0xC3E5 },
+{ 0xC3E6, 0xC3E6, 0xC3E6 },
+{ 0xC3E7, 0xC3E7, 0xC3E7 },
+{ 0xC3E8, 0xC3E8, 0xC3E8 },
+{ 0xC3E9, 0xC3E9, 0xC3E9 },
+{ 0xC3EA, 0xC3EA, 0xC3EA },
+{ 0xC3EB, 0xC3EB, 0xC3EB },
+{ 0xC3EC, 0xC3EC, 0xC3EC },
+{ 0xC3ED, 0xC3ED, 0xC3ED },
+{ 0xC3EE, 0xC3EE, 0xC3EE },
+{ 0xC3EF, 0xC3EF, 0xC3EF },
+{ 0xC3F0, 0xC3F0, 0xC3F0 },
+{ 0xC3F1, 0xC3F1, 0xC3F1 },
+{ 0xC3F2, 0xC3F2, 0xC3F2 },
+{ 0xC3F3, 0xC3F3, 0xC3F3 },
+{ 0xC3F4, 0xC3F4, 0xC3F4 },
+{ 0xC3F5, 0xC3F5, 0xC3F5 },
+{ 0xC3F6, 0xC3F6, 0xC3F6 },
+{ 0xC3F7, 0xC3F7, 0xC3F7 },
+{ 0xC3F8, 0xC3F8, 0xC3F8 },
+{ 0xC3F9, 0xC3F9, 0xC3F9 },
+{ 0xC3FA, 0xC3FA, 0xC3FA },
+{ 0xC3FB, 0xC3FB, 0xC3FB },
+{ 0xC3FC, 0xC3FC, 0xC3FC },
+{ 0xC3FD, 0xC3FD, 0xC3FD },
+{ 0xC3FE, 0xC3FE, 0xC3FE },
+{ 0xC3FF, 0xC3FF, 0xC3FF },
+{ 0xC400, 0xC400, 0xC400 },
+{ 0xC401, 0xC401, 0xC401 },
+{ 0xC402, 0xC402, 0xC402 },
+{ 0xC403, 0xC403, 0xC403 },
+{ 0xC404, 0xC404, 0xC404 },
+{ 0xC405, 0xC405, 0xC405 },
+{ 0xC406, 0xC406, 0xC406 },
+{ 0xC407, 0xC407, 0xC407 },
+{ 0xC408, 0xC408, 0xC408 },
+{ 0xC409, 0xC409, 0xC409 },
+{ 0xC40A, 0xC40A, 0xC40A },
+{ 0xC40B, 0xC40B, 0xC40B },
+{ 0xC40C, 0xC40C, 0xC40C },
+{ 0xC40D, 0xC40D, 0xC40D },
+{ 0xC40E, 0xC40E, 0xC40E },
+{ 0xC40F, 0xC40F, 0xC40F },
+{ 0xC410, 0xC410, 0xC410 },
+{ 0xC411, 0xC411, 0xC411 },
+{ 0xC412, 0xC412, 0xC412 },
+{ 0xC413, 0xC413, 0xC413 },
+{ 0xC414, 0xC414, 0xC414 },
+{ 0xC415, 0xC415, 0xC415 },
+{ 0xC416, 0xC416, 0xC416 },
+{ 0xC417, 0xC417, 0xC417 },
+{ 0xC418, 0xC418, 0xC418 },
+{ 0xC419, 0xC419, 0xC419 },
+{ 0xC41A, 0xC41A, 0xC41A },
+{ 0xC41B, 0xC41B, 0xC41B },
+{ 0xC41C, 0xC41C, 0xC41C },
+{ 0xC41D, 0xC41D, 0xC41D },
+{ 0xC41E, 0xC41E, 0xC41E },
+{ 0xC41F, 0xC41F, 0xC41F },
+{ 0xC420, 0xC420, 0xC420 },
+{ 0xC421, 0xC421, 0xC421 },
+{ 0xC422, 0xC422, 0xC422 },
+{ 0xC423, 0xC423, 0xC423 },
+{ 0xC424, 0xC424, 0xC424 },
+{ 0xC425, 0xC425, 0xC425 },
+{ 0xC426, 0xC426, 0xC426 },
+{ 0xC427, 0xC427, 0xC427 },
+{ 0xC428, 0xC428, 0xC428 },
+{ 0xC429, 0xC429, 0xC429 },
+{ 0xC42A, 0xC42A, 0xC42A },
+{ 0xC42B, 0xC42B, 0xC42B },
+{ 0xC42C, 0xC42C, 0xC42C },
+{ 0xC42D, 0xC42D, 0xC42D },
+{ 0xC42E, 0xC42E, 0xC42E },
+{ 0xC42F, 0xC42F, 0xC42F },
+{ 0xC430, 0xC430, 0xC430 },
+{ 0xC431, 0xC431, 0xC431 },
+{ 0xC432, 0xC432, 0xC432 },
+{ 0xC433, 0xC433, 0xC433 },
+{ 0xC434, 0xC434, 0xC434 },
+{ 0xC435, 0xC435, 0xC435 },
+{ 0xC436, 0xC436, 0xC436 },
+{ 0xC437, 0xC437, 0xC437 },
+{ 0xC438, 0xC438, 0xC438 },
+{ 0xC439, 0xC439, 0xC439 },
+{ 0xC43A, 0xC43A, 0xC43A },
+{ 0xC43B, 0xC43B, 0xC43B },
+{ 0xC43C, 0xC43C, 0xC43C },
+{ 0xC43D, 0xC43D, 0xC43D },
+{ 0xC43E, 0xC43E, 0xC43E },
+{ 0xC43F, 0xC43F, 0xC43F },
+{ 0xC440, 0xC440, 0xC440 },
+{ 0xC441, 0xC441, 0xC441 },
+{ 0xC442, 0xC442, 0xC442 },
+{ 0xC443, 0xC443, 0xC443 },
+{ 0xC444, 0xC444, 0xC444 },
+{ 0xC445, 0xC445, 0xC445 },
+{ 0xC446, 0xC446, 0xC446 },
+{ 0xC447, 0xC447, 0xC447 },
+{ 0xC448, 0xC448, 0xC448 },
+{ 0xC449, 0xC449, 0xC449 },
+{ 0xC44A, 0xC44A, 0xC44A },
+{ 0xC44B, 0xC44B, 0xC44B },
+{ 0xC44C, 0xC44C, 0xC44C },
+{ 0xC44D, 0xC44D, 0xC44D },
+{ 0xC44E, 0xC44E, 0xC44E },
+{ 0xC44F, 0xC44F, 0xC44F },
+{ 0xC450, 0xC450, 0xC450 },
+{ 0xC451, 0xC451, 0xC451 },
+{ 0xC452, 0xC452, 0xC452 },
+{ 0xC453, 0xC453, 0xC453 },
+{ 0xC454, 0xC454, 0xC454 },
+{ 0xC455, 0xC455, 0xC455 },
+{ 0xC456, 0xC456, 0xC456 },
+{ 0xC457, 0xC457, 0xC457 },
+{ 0xC458, 0xC458, 0xC458 },
+{ 0xC459, 0xC459, 0xC459 },
+{ 0xC45A, 0xC45A, 0xC45A },
+{ 0xC45B, 0xC45B, 0xC45B },
+{ 0xC45C, 0xC45C, 0xC45C },
+{ 0xC45D, 0xC45D, 0xC45D },
+{ 0xC45E, 0xC45E, 0xC45E },
+{ 0xC45F, 0xC45F, 0xC45F },
+{ 0xC460, 0xC460, 0xC460 },
+{ 0xC461, 0xC461, 0xC461 },
+{ 0xC462, 0xC462, 0xC462 },
+{ 0xC463, 0xC463, 0xC463 },
+{ 0xC464, 0xC464, 0xC464 },
+{ 0xC465, 0xC465, 0xC465 },
+{ 0xC466, 0xC466, 0xC466 },
+{ 0xC467, 0xC467, 0xC467 },
+{ 0xC468, 0xC468, 0xC468 },
+{ 0xC469, 0xC469, 0xC469 },
+{ 0xC46A, 0xC46A, 0xC46A },
+{ 0xC46B, 0xC46B, 0xC46B },
+{ 0xC46C, 0xC46C, 0xC46C },
+{ 0xC46D, 0xC46D, 0xC46D },
+{ 0xC46E, 0xC46E, 0xC46E },
+{ 0xC46F, 0xC46F, 0xC46F },
+{ 0xC470, 0xC470, 0xC470 },
+{ 0xC471, 0xC471, 0xC471 },
+{ 0xC472, 0xC472, 0xC472 },
+{ 0xC473, 0xC473, 0xC473 },
+{ 0xC474, 0xC474, 0xC474 },
+{ 0xC475, 0xC475, 0xC475 },
+{ 0xC476, 0xC476, 0xC476 },
+{ 0xC477, 0xC477, 0xC477 },
+{ 0xC478, 0xC478, 0xC478 },
+{ 0xC479, 0xC479, 0xC479 },
+{ 0xC47A, 0xC47A, 0xC47A },
+{ 0xC47B, 0xC47B, 0xC47B },
+{ 0xC47C, 0xC47C, 0xC47C },
+{ 0xC47D, 0xC47D, 0xC47D },
+{ 0xC47E, 0xC47E, 0xC47E },
+{ 0xC47F, 0xC47F, 0xC47F },
+{ 0xC480, 0xC480, 0xC480 },
+{ 0xC481, 0xC481, 0xC481 },
+{ 0xC482, 0xC482, 0xC482 },
+{ 0xC483, 0xC483, 0xC483 },
+{ 0xC484, 0xC484, 0xC484 },
+{ 0xC485, 0xC485, 0xC485 },
+{ 0xC486, 0xC486, 0xC486 },
+{ 0xC487, 0xC487, 0xC487 },
+{ 0xC488, 0xC488, 0xC488 },
+{ 0xC489, 0xC489, 0xC489 },
+{ 0xC48A, 0xC48A, 0xC48A },
+{ 0xC48B, 0xC48B, 0xC48B },
+{ 0xC48C, 0xC48C, 0xC48C },
+{ 0xC48D, 0xC48D, 0xC48D },
+{ 0xC48E, 0xC48E, 0xC48E },
+{ 0xC48F, 0xC48F, 0xC48F },
+{ 0xC490, 0xC490, 0xC490 },
+{ 0xC491, 0xC491, 0xC491 },
+{ 0xC492, 0xC492, 0xC492 },
+{ 0xC493, 0xC493, 0xC493 },
+{ 0xC494, 0xC494, 0xC494 },
+{ 0xC495, 0xC495, 0xC495 },
+{ 0xC496, 0xC496, 0xC496 },
+{ 0xC497, 0xC497, 0xC497 },
+{ 0xC498, 0xC498, 0xC498 },
+{ 0xC499, 0xC499, 0xC499 },
+{ 0xC49A, 0xC49A, 0xC49A },
+{ 0xC49B, 0xC49B, 0xC49B },
+{ 0xC49C, 0xC49C, 0xC49C },
+{ 0xC49D, 0xC49D, 0xC49D },
+{ 0xC49E, 0xC49E, 0xC49E },
+{ 0xC49F, 0xC49F, 0xC49F },
+{ 0xC4A0, 0xC4A0, 0xC4A0 },
+{ 0xC4A1, 0xC4A1, 0xC4A1 },
+{ 0xC4A2, 0xC4A2, 0xC4A2 },
+{ 0xC4A3, 0xC4A3, 0xC4A3 },
+{ 0xC4A4, 0xC4A4, 0xC4A4 },
+{ 0xC4A5, 0xC4A5, 0xC4A5 },
+{ 0xC4A6, 0xC4A6, 0xC4A6 },
+{ 0xC4A7, 0xC4A7, 0xC4A7 },
+{ 0xC4A8, 0xC4A8, 0xC4A8 },
+{ 0xC4A9, 0xC4A9, 0xC4A9 },
+{ 0xC4AA, 0xC4AA, 0xC4AA },
+{ 0xC4AB, 0xC4AB, 0xC4AB },
+{ 0xC4AC, 0xC4AC, 0xC4AC },
+{ 0xC4AD, 0xC4AD, 0xC4AD },
+{ 0xC4AE, 0xC4AE, 0xC4AE },
+{ 0xC4AF, 0xC4AF, 0xC4AF },
+{ 0xC4B0, 0xC4B0, 0xC4B0 },
+{ 0xC4B1, 0xC4B1, 0xC4B1 },
+{ 0xC4B2, 0xC4B2, 0xC4B2 },
+{ 0xC4B3, 0xC4B3, 0xC4B3 },
+{ 0xC4B4, 0xC4B4, 0xC4B4 },
+{ 0xC4B5, 0xC4B5, 0xC4B5 },
+{ 0xC4B6, 0xC4B6, 0xC4B6 },
+{ 0xC4B7, 0xC4B7, 0xC4B7 },
+{ 0xC4B8, 0xC4B8, 0xC4B8 },
+{ 0xC4B9, 0xC4B9, 0xC4B9 },
+{ 0xC4BA, 0xC4BA, 0xC4BA },
+{ 0xC4BB, 0xC4BB, 0xC4BB },
+{ 0xC4BC, 0xC4BC, 0xC4BC },
+{ 0xC4BD, 0xC4BD, 0xC4BD },
+{ 0xC4BE, 0xC4BE, 0xC4BE },
+{ 0xC4BF, 0xC4BF, 0xC4BF },
+{ 0xC4C0, 0xC4C0, 0xC4C0 },
+{ 0xC4C1, 0xC4C1, 0xC4C1 },
+{ 0xC4C2, 0xC4C2, 0xC4C2 },
+{ 0xC4C3, 0xC4C3, 0xC4C3 },
+{ 0xC4C4, 0xC4C4, 0xC4C4 },
+{ 0xC4C5, 0xC4C5, 0xC4C5 },
+{ 0xC4C6, 0xC4C6, 0xC4C6 },
+{ 0xC4C7, 0xC4C7, 0xC4C7 },
+{ 0xC4C8, 0xC4C8, 0xC4C8 },
+{ 0xC4C9, 0xC4C9, 0xC4C9 },
+{ 0xC4CA, 0xC4CA, 0xC4CA },
+{ 0xC4CB, 0xC4CB, 0xC4CB },
+{ 0xC4CC, 0xC4CC, 0xC4CC },
+{ 0xC4CD, 0xC4CD, 0xC4CD },
+{ 0xC4CE, 0xC4CE, 0xC4CE },
+{ 0xC4CF, 0xC4CF, 0xC4CF },
+{ 0xC4D0, 0xC4D0, 0xC4D0 },
+{ 0xC4D1, 0xC4D1, 0xC4D1 },
+{ 0xC4D2, 0xC4D2, 0xC4D2 },
+{ 0xC4D3, 0xC4D3, 0xC4D3 },
+{ 0xC4D4, 0xC4D4, 0xC4D4 },
+{ 0xC4D5, 0xC4D5, 0xC4D5 },
+{ 0xC4D6, 0xC4D6, 0xC4D6 },
+{ 0xC4D7, 0xC4D7, 0xC4D7 },
+{ 0xC4D8, 0xC4D8, 0xC4D8 },
+{ 0xC4D9, 0xC4D9, 0xC4D9 },
+{ 0xC4DA, 0xC4DA, 0xC4DA },
+{ 0xC4DB, 0xC4DB, 0xC4DB },
+{ 0xC4DC, 0xC4DC, 0xC4DC },
+{ 0xC4DD, 0xC4DD, 0xC4DD },
+{ 0xC4DE, 0xC4DE, 0xC4DE },
+{ 0xC4DF, 0xC4DF, 0xC4DF },
+{ 0xC4E0, 0xC4E0, 0xC4E0 },
+{ 0xC4E1, 0xC4E1, 0xC4E1 },
+{ 0xC4E2, 0xC4E2, 0xC4E2 },
+{ 0xC4E3, 0xC4E3, 0xC4E3 },
+{ 0xC4E4, 0xC4E4, 0xC4E4 },
+{ 0xC4E5, 0xC4E5, 0xC4E5 },
+{ 0xC4E6, 0xC4E6, 0xC4E6 },
+{ 0xC4E7, 0xC4E7, 0xC4E7 },
+{ 0xC4E8, 0xC4E8, 0xC4E8 },
+{ 0xC4E9, 0xC4E9, 0xC4E9 },
+{ 0xC4EA, 0xC4EA, 0xC4EA },
+{ 0xC4EB, 0xC4EB, 0xC4EB },
+{ 0xC4EC, 0xC4EC, 0xC4EC },
+{ 0xC4ED, 0xC4ED, 0xC4ED },
+{ 0xC4EE, 0xC4EE, 0xC4EE },
+{ 0xC4EF, 0xC4EF, 0xC4EF },
+{ 0xC4F0, 0xC4F0, 0xC4F0 },
+{ 0xC4F1, 0xC4F1, 0xC4F1 },
+{ 0xC4F2, 0xC4F2, 0xC4F2 },
+{ 0xC4F3, 0xC4F3, 0xC4F3 },
+{ 0xC4F4, 0xC4F4, 0xC4F4 },
+{ 0xC4F5, 0xC4F5, 0xC4F5 },
+{ 0xC4F6, 0xC4F6, 0xC4F6 },
+{ 0xC4F7, 0xC4F7, 0xC4F7 },
+{ 0xC4F8, 0xC4F8, 0xC4F8 },
+{ 0xC4F9, 0xC4F9, 0xC4F9 },
+{ 0xC4FA, 0xC4FA, 0xC4FA },
+{ 0xC4FB, 0xC4FB, 0xC4FB },
+{ 0xC4FC, 0xC4FC, 0xC4FC },
+{ 0xC4FD, 0xC4FD, 0xC4FD },
+{ 0xC4FE, 0xC4FE, 0xC4FE },
+{ 0xC4FF, 0xC4FF, 0xC4FF },
+{ 0xC500, 0xC500, 0xC500 },
+{ 0xC501, 0xC501, 0xC501 },
+{ 0xC502, 0xC502, 0xC502 },
+{ 0xC503, 0xC503, 0xC503 },
+{ 0xC504, 0xC504, 0xC504 },
+{ 0xC505, 0xC505, 0xC505 },
+{ 0xC506, 0xC506, 0xC506 },
+{ 0xC507, 0xC507, 0xC507 },
+{ 0xC508, 0xC508, 0xC508 },
+{ 0xC509, 0xC509, 0xC509 },
+{ 0xC50A, 0xC50A, 0xC50A },
+{ 0xC50B, 0xC50B, 0xC50B },
+{ 0xC50C, 0xC50C, 0xC50C },
+{ 0xC50D, 0xC50D, 0xC50D },
+{ 0xC50E, 0xC50E, 0xC50E },
+{ 0xC50F, 0xC50F, 0xC50F },
+{ 0xC510, 0xC510, 0xC510 },
+{ 0xC511, 0xC511, 0xC511 },
+{ 0xC512, 0xC512, 0xC512 },
+{ 0xC513, 0xC513, 0xC513 },
+{ 0xC514, 0xC514, 0xC514 },
+{ 0xC515, 0xC515, 0xC515 },
+{ 0xC516, 0xC516, 0xC516 },
+{ 0xC517, 0xC517, 0xC517 },
+{ 0xC518, 0xC518, 0xC518 },
+{ 0xC519, 0xC519, 0xC519 },
+{ 0xC51A, 0xC51A, 0xC51A },
+{ 0xC51B, 0xC51B, 0xC51B },
+{ 0xC51C, 0xC51C, 0xC51C },
+{ 0xC51D, 0xC51D, 0xC51D },
+{ 0xC51E, 0xC51E, 0xC51E },
+{ 0xC51F, 0xC51F, 0xC51F },
+{ 0xC520, 0xC520, 0xC520 },
+{ 0xC521, 0xC521, 0xC521 },
+{ 0xC522, 0xC522, 0xC522 },
+{ 0xC523, 0xC523, 0xC523 },
+{ 0xC524, 0xC524, 0xC524 },
+{ 0xC525, 0xC525, 0xC525 },
+{ 0xC526, 0xC526, 0xC526 },
+{ 0xC527, 0xC527, 0xC527 },
+{ 0xC528, 0xC528, 0xC528 },
+{ 0xC529, 0xC529, 0xC529 },
+{ 0xC52A, 0xC52A, 0xC52A },
+{ 0xC52B, 0xC52B, 0xC52B },
+{ 0xC52C, 0xC52C, 0xC52C },
+{ 0xC52D, 0xC52D, 0xC52D },
+{ 0xC52E, 0xC52E, 0xC52E },
+{ 0xC52F, 0xC52F, 0xC52F },
+{ 0xC530, 0xC530, 0xC530 },
+{ 0xC531, 0xC531, 0xC531 },
+{ 0xC532, 0xC532, 0xC532 },
+{ 0xC533, 0xC533, 0xC533 },
+{ 0xC534, 0xC534, 0xC534 },
+{ 0xC535, 0xC535, 0xC535 },
+{ 0xC536, 0xC536, 0xC536 },
+{ 0xC537, 0xC537, 0xC537 },
+{ 0xC538, 0xC538, 0xC538 },
+{ 0xC539, 0xC539, 0xC539 },
+{ 0xC53A, 0xC53A, 0xC53A },
+{ 0xC53B, 0xC53B, 0xC53B },
+{ 0xC53C, 0xC53C, 0xC53C },
+{ 0xC53D, 0xC53D, 0xC53D },
+{ 0xC53E, 0xC53E, 0xC53E },
+{ 0xC53F, 0xC53F, 0xC53F },
+{ 0xC540, 0xC540, 0xC540 },
+{ 0xC541, 0xC541, 0xC541 },
+{ 0xC542, 0xC542, 0xC542 },
+{ 0xC543, 0xC543, 0xC543 },
+{ 0xC544, 0xC544, 0xC544 },
+{ 0xC545, 0xC545, 0xC545 },
+{ 0xC546, 0xC546, 0xC546 },
+{ 0xC547, 0xC547, 0xC547 },
+{ 0xC548, 0xC548, 0xC548 },
+{ 0xC549, 0xC549, 0xC549 },
+{ 0xC54A, 0xC54A, 0xC54A },
+{ 0xC54B, 0xC54B, 0xC54B },
+{ 0xC54C, 0xC54C, 0xC54C },
+{ 0xC54D, 0xC54D, 0xC54D },
+{ 0xC54E, 0xC54E, 0xC54E },
+{ 0xC54F, 0xC54F, 0xC54F },
+{ 0xC550, 0xC550, 0xC550 },
+{ 0xC551, 0xC551, 0xC551 },
+{ 0xC552, 0xC552, 0xC552 },
+{ 0xC553, 0xC553, 0xC553 },
+{ 0xC554, 0xC554, 0xC554 },
+{ 0xC555, 0xC555, 0xC555 },
+{ 0xC556, 0xC556, 0xC556 },
+{ 0xC557, 0xC557, 0xC557 },
+{ 0xC558, 0xC558, 0xC558 },
+{ 0xC559, 0xC559, 0xC559 },
+{ 0xC55A, 0xC55A, 0xC55A },
+{ 0xC55B, 0xC55B, 0xC55B },
+{ 0xC55C, 0xC55C, 0xC55C },
+{ 0xC55D, 0xC55D, 0xC55D },
+{ 0xC55E, 0xC55E, 0xC55E },
+{ 0xC55F, 0xC55F, 0xC55F },
+{ 0xC560, 0xC560, 0xC560 },
+{ 0xC561, 0xC561, 0xC561 },
+{ 0xC562, 0xC562, 0xC562 },
+{ 0xC563, 0xC563, 0xC563 },
+{ 0xC564, 0xC564, 0xC564 },
+{ 0xC565, 0xC565, 0xC565 },
+{ 0xC566, 0xC566, 0xC566 },
+{ 0xC567, 0xC567, 0xC567 },
+{ 0xC568, 0xC568, 0xC568 },
+{ 0xC569, 0xC569, 0xC569 },
+{ 0xC56A, 0xC56A, 0xC56A },
+{ 0xC56B, 0xC56B, 0xC56B },
+{ 0xC56C, 0xC56C, 0xC56C },
+{ 0xC56D, 0xC56D, 0xC56D },
+{ 0xC56E, 0xC56E, 0xC56E },
+{ 0xC56F, 0xC56F, 0xC56F },
+{ 0xC570, 0xC570, 0xC570 },
+{ 0xC571, 0xC571, 0xC571 },
+{ 0xC572, 0xC572, 0xC572 },
+{ 0xC573, 0xC573, 0xC573 },
+{ 0xC574, 0xC574, 0xC574 },
+{ 0xC575, 0xC575, 0xC575 },
+{ 0xC576, 0xC576, 0xC576 },
+{ 0xC577, 0xC577, 0xC577 },
+{ 0xC578, 0xC578, 0xC578 },
+{ 0xC579, 0xC579, 0xC579 },
+{ 0xC57A, 0xC57A, 0xC57A },
+{ 0xC57B, 0xC57B, 0xC57B },
+{ 0xC57C, 0xC57C, 0xC57C },
+{ 0xC57D, 0xC57D, 0xC57D },
+{ 0xC57E, 0xC57E, 0xC57E },
+{ 0xC57F, 0xC57F, 0xC57F },
+{ 0xC580, 0xC580, 0xC580 },
+{ 0xC581, 0xC581, 0xC581 },
+{ 0xC582, 0xC582, 0xC582 },
+{ 0xC583, 0xC583, 0xC583 },
+{ 0xC584, 0xC584, 0xC584 },
+{ 0xC585, 0xC585, 0xC585 },
+{ 0xC586, 0xC586, 0xC586 },
+{ 0xC587, 0xC587, 0xC587 },
+{ 0xC588, 0xC588, 0xC588 },
+{ 0xC589, 0xC589, 0xC589 },
+{ 0xC58A, 0xC58A, 0xC58A },
+{ 0xC58B, 0xC58B, 0xC58B },
+{ 0xC58C, 0xC58C, 0xC58C },
+{ 0xC58D, 0xC58D, 0xC58D },
+{ 0xC58E, 0xC58E, 0xC58E },
+{ 0xC58F, 0xC58F, 0xC58F },
+{ 0xC590, 0xC590, 0xC590 },
+{ 0xC591, 0xC591, 0xC591 },
+{ 0xC592, 0xC592, 0xC592 },
+{ 0xC593, 0xC593, 0xC593 },
+{ 0xC594, 0xC594, 0xC594 },
+{ 0xC595, 0xC595, 0xC595 },
+{ 0xC596, 0xC596, 0xC596 },
+{ 0xC597, 0xC597, 0xC597 },
+{ 0xC598, 0xC598, 0xC598 },
+{ 0xC599, 0xC599, 0xC599 },
+{ 0xC59A, 0xC59A, 0xC59A },
+{ 0xC59B, 0xC59B, 0xC59B },
+{ 0xC59C, 0xC59C, 0xC59C },
+{ 0xC59D, 0xC59D, 0xC59D },
+{ 0xC59E, 0xC59E, 0xC59E },
+{ 0xC59F, 0xC59F, 0xC59F },
+{ 0xC5A0, 0xC5A0, 0xC5A0 },
+{ 0xC5A1, 0xC5A1, 0xC5A1 },
+{ 0xC5A2, 0xC5A2, 0xC5A2 },
+{ 0xC5A3, 0xC5A3, 0xC5A3 },
+{ 0xC5A4, 0xC5A4, 0xC5A4 },
+{ 0xC5A5, 0xC5A5, 0xC5A5 },
+{ 0xC5A6, 0xC5A6, 0xC5A6 },
+{ 0xC5A7, 0xC5A7, 0xC5A7 },
+{ 0xC5A8, 0xC5A8, 0xC5A8 },
+{ 0xC5A9, 0xC5A9, 0xC5A9 },
+{ 0xC5AA, 0xC5AA, 0xC5AA },
+{ 0xC5AB, 0xC5AB, 0xC5AB },
+{ 0xC5AC, 0xC5AC, 0xC5AC },
+{ 0xC5AD, 0xC5AD, 0xC5AD },
+{ 0xC5AE, 0xC5AE, 0xC5AE },
+{ 0xC5AF, 0xC5AF, 0xC5AF },
+{ 0xC5B0, 0xC5B0, 0xC5B0 },
+{ 0xC5B1, 0xC5B1, 0xC5B1 },
+{ 0xC5B2, 0xC5B2, 0xC5B2 },
+{ 0xC5B3, 0xC5B3, 0xC5B3 },
+{ 0xC5B4, 0xC5B4, 0xC5B4 },
+{ 0xC5B5, 0xC5B5, 0xC5B5 },
+{ 0xC5B6, 0xC5B6, 0xC5B6 },
+{ 0xC5B7, 0xC5B7, 0xC5B7 },
+{ 0xC5B8, 0xC5B8, 0xC5B8 },
+{ 0xC5B9, 0xC5B9, 0xC5B9 },
+{ 0xC5BA, 0xC5BA, 0xC5BA },
+{ 0xC5BB, 0xC5BB, 0xC5BB },
+{ 0xC5BC, 0xC5BC, 0xC5BC },
+{ 0xC5BD, 0xC5BD, 0xC5BD },
+{ 0xC5BE, 0xC5BE, 0xC5BE },
+{ 0xC5BF, 0xC5BF, 0xC5BF },
+{ 0xC5C0, 0xC5C0, 0xC5C0 },
+{ 0xC5C1, 0xC5C1, 0xC5C1 },
+{ 0xC5C2, 0xC5C2, 0xC5C2 },
+{ 0xC5C3, 0xC5C3, 0xC5C3 },
+{ 0xC5C4, 0xC5C4, 0xC5C4 },
+{ 0xC5C5, 0xC5C5, 0xC5C5 },
+{ 0xC5C6, 0xC5C6, 0xC5C6 },
+{ 0xC5C7, 0xC5C7, 0xC5C7 },
+{ 0xC5C8, 0xC5C8, 0xC5C8 },
+{ 0xC5C9, 0xC5C9, 0xC5C9 },
+{ 0xC5CA, 0xC5CA, 0xC5CA },
+{ 0xC5CB, 0xC5CB, 0xC5CB },
+{ 0xC5CC, 0xC5CC, 0xC5CC },
+{ 0xC5CD, 0xC5CD, 0xC5CD },
+{ 0xC5CE, 0xC5CE, 0xC5CE },
+{ 0xC5CF, 0xC5CF, 0xC5CF },
+{ 0xC5D0, 0xC5D0, 0xC5D0 },
+{ 0xC5D1, 0xC5D1, 0xC5D1 },
+{ 0xC5D2, 0xC5D2, 0xC5D2 },
+{ 0xC5D3, 0xC5D3, 0xC5D3 },
+{ 0xC5D4, 0xC5D4, 0xC5D4 },
+{ 0xC5D5, 0xC5D5, 0xC5D5 },
+{ 0xC5D6, 0xC5D6, 0xC5D6 },
+{ 0xC5D7, 0xC5D7, 0xC5D7 },
+{ 0xC5D8, 0xC5D8, 0xC5D8 },
+{ 0xC5D9, 0xC5D9, 0xC5D9 },
+{ 0xC5DA, 0xC5DA, 0xC5DA },
+{ 0xC5DB, 0xC5DB, 0xC5DB },
+{ 0xC5DC, 0xC5DC, 0xC5DC },
+{ 0xC5DD, 0xC5DD, 0xC5DD },
+{ 0xC5DE, 0xC5DE, 0xC5DE },
+{ 0xC5DF, 0xC5DF, 0xC5DF },
+{ 0xC5E0, 0xC5E0, 0xC5E0 },
+{ 0xC5E1, 0xC5E1, 0xC5E1 },
+{ 0xC5E2, 0xC5E2, 0xC5E2 },
+{ 0xC5E3, 0xC5E3, 0xC5E3 },
+{ 0xC5E4, 0xC5E4, 0xC5E4 },
+{ 0xC5E5, 0xC5E5, 0xC5E5 },
+{ 0xC5E6, 0xC5E6, 0xC5E6 },
+{ 0xC5E7, 0xC5E7, 0xC5E7 },
+{ 0xC5E8, 0xC5E8, 0xC5E8 },
+{ 0xC5E9, 0xC5E9, 0xC5E9 },
+{ 0xC5EA, 0xC5EA, 0xC5EA },
+{ 0xC5EB, 0xC5EB, 0xC5EB },
+{ 0xC5EC, 0xC5EC, 0xC5EC },
+{ 0xC5ED, 0xC5ED, 0xC5ED },
+{ 0xC5EE, 0xC5EE, 0xC5EE },
+{ 0xC5EF, 0xC5EF, 0xC5EF },
+{ 0xC5F0, 0xC5F0, 0xC5F0 },
+{ 0xC5F1, 0xC5F1, 0xC5F1 },
+{ 0xC5F2, 0xC5F2, 0xC5F2 },
+{ 0xC5F3, 0xC5F3, 0xC5F3 },
+{ 0xC5F4, 0xC5F4, 0xC5F4 },
+{ 0xC5F5, 0xC5F5, 0xC5F5 },
+{ 0xC5F6, 0xC5F6, 0xC5F6 },
+{ 0xC5F7, 0xC5F7, 0xC5F7 },
+{ 0xC5F8, 0xC5F8, 0xC5F8 },
+{ 0xC5F9, 0xC5F9, 0xC5F9 },
+{ 0xC5FA, 0xC5FA, 0xC5FA },
+{ 0xC5FB, 0xC5FB, 0xC5FB },
+{ 0xC5FC, 0xC5FC, 0xC5FC },
+{ 0xC5FD, 0xC5FD, 0xC5FD },
+{ 0xC5FE, 0xC5FE, 0xC5FE },
+{ 0xC5FF, 0xC5FF, 0xC5FF },
+{ 0xC600, 0xC600, 0xC600 },
+{ 0xC601, 0xC601, 0xC601 },
+{ 0xC602, 0xC602, 0xC602 },
+{ 0xC603, 0xC603, 0xC603 },
+{ 0xC604, 0xC604, 0xC604 },
+{ 0xC605, 0xC605, 0xC605 },
+{ 0xC606, 0xC606, 0xC606 },
+{ 0xC607, 0xC607, 0xC607 },
+{ 0xC608, 0xC608, 0xC608 },
+{ 0xC609, 0xC609, 0xC609 },
+{ 0xC60A, 0xC60A, 0xC60A },
+{ 0xC60B, 0xC60B, 0xC60B },
+{ 0xC60C, 0xC60C, 0xC60C },
+{ 0xC60D, 0xC60D, 0xC60D },
+{ 0xC60E, 0xC60E, 0xC60E },
+{ 0xC60F, 0xC60F, 0xC60F },
+{ 0xC610, 0xC610, 0xC610 },
+{ 0xC611, 0xC611, 0xC611 },
+{ 0xC612, 0xC612, 0xC612 },
+{ 0xC613, 0xC613, 0xC613 },
+{ 0xC614, 0xC614, 0xC614 },
+{ 0xC615, 0xC615, 0xC615 },
+{ 0xC616, 0xC616, 0xC616 },
+{ 0xC617, 0xC617, 0xC617 },
+{ 0xC618, 0xC618, 0xC618 },
+{ 0xC619, 0xC619, 0xC619 },
+{ 0xC61A, 0xC61A, 0xC61A },
+{ 0xC61B, 0xC61B, 0xC61B },
+{ 0xC61C, 0xC61C, 0xC61C },
+{ 0xC61D, 0xC61D, 0xC61D },
+{ 0xC61E, 0xC61E, 0xC61E },
+{ 0xC61F, 0xC61F, 0xC61F },
+{ 0xC620, 0xC620, 0xC620 },
+{ 0xC621, 0xC621, 0xC621 },
+{ 0xC622, 0xC622, 0xC622 },
+{ 0xC623, 0xC623, 0xC623 },
+{ 0xC624, 0xC624, 0xC624 },
+{ 0xC625, 0xC625, 0xC625 },
+{ 0xC626, 0xC626, 0xC626 },
+{ 0xC627, 0xC627, 0xC627 },
+{ 0xC628, 0xC628, 0xC628 },
+{ 0xC629, 0xC629, 0xC629 },
+{ 0xC62A, 0xC62A, 0xC62A },
+{ 0xC62B, 0xC62B, 0xC62B },
+{ 0xC62C, 0xC62C, 0xC62C },
+{ 0xC62D, 0xC62D, 0xC62D },
+{ 0xC62E, 0xC62E, 0xC62E },
+{ 0xC62F, 0xC62F, 0xC62F },
+{ 0xC630, 0xC630, 0xC630 },
+{ 0xC631, 0xC631, 0xC631 },
+{ 0xC632, 0xC632, 0xC632 },
+{ 0xC633, 0xC633, 0xC633 },
+{ 0xC634, 0xC634, 0xC634 },
+{ 0xC635, 0xC635, 0xC635 },
+{ 0xC636, 0xC636, 0xC636 },
+{ 0xC637, 0xC637, 0xC637 },
+{ 0xC638, 0xC638, 0xC638 },
+{ 0xC639, 0xC639, 0xC639 },
+{ 0xC63A, 0xC63A, 0xC63A },
+{ 0xC63B, 0xC63B, 0xC63B },
+{ 0xC63C, 0xC63C, 0xC63C },
+{ 0xC63D, 0xC63D, 0xC63D },
+{ 0xC63E, 0xC63E, 0xC63E },
+{ 0xC63F, 0xC63F, 0xC63F },
+{ 0xC640, 0xC640, 0xC640 },
+{ 0xC641, 0xC641, 0xC641 },
+{ 0xC642, 0xC642, 0xC642 },
+{ 0xC643, 0xC643, 0xC643 },
+{ 0xC644, 0xC644, 0xC644 },
+{ 0xC645, 0xC645, 0xC645 },
+{ 0xC646, 0xC646, 0xC646 },
+{ 0xC647, 0xC647, 0xC647 },
+{ 0xC648, 0xC648, 0xC648 },
+{ 0xC649, 0xC649, 0xC649 },
+{ 0xC64A, 0xC64A, 0xC64A },
+{ 0xC64B, 0xC64B, 0xC64B },
+{ 0xC64C, 0xC64C, 0xC64C },
+{ 0xC64D, 0xC64D, 0xC64D },
+{ 0xC64E, 0xC64E, 0xC64E },
+{ 0xC64F, 0xC64F, 0xC64F },
+{ 0xC650, 0xC650, 0xC650 },
+{ 0xC651, 0xC651, 0xC651 },
+{ 0xC652, 0xC652, 0xC652 },
+{ 0xC653, 0xC653, 0xC653 },
+{ 0xC654, 0xC654, 0xC654 },
+{ 0xC655, 0xC655, 0xC655 },
+{ 0xC656, 0xC656, 0xC656 },
+{ 0xC657, 0xC657, 0xC657 },
+{ 0xC658, 0xC658, 0xC658 },
+{ 0xC659, 0xC659, 0xC659 },
+{ 0xC65A, 0xC65A, 0xC65A },
+{ 0xC65B, 0xC65B, 0xC65B },
+{ 0xC65C, 0xC65C, 0xC65C },
+{ 0xC65D, 0xC65D, 0xC65D },
+{ 0xC65E, 0xC65E, 0xC65E },
+{ 0xC65F, 0xC65F, 0xC65F },
+{ 0xC660, 0xC660, 0xC660 },
+{ 0xC661, 0xC661, 0xC661 },
+{ 0xC662, 0xC662, 0xC662 },
+{ 0xC663, 0xC663, 0xC663 },
+{ 0xC664, 0xC664, 0xC664 },
+{ 0xC665, 0xC665, 0xC665 },
+{ 0xC666, 0xC666, 0xC666 },
+{ 0xC667, 0xC667, 0xC667 },
+{ 0xC668, 0xC668, 0xC668 },
+{ 0xC669, 0xC669, 0xC669 },
+{ 0xC66A, 0xC66A, 0xC66A },
+{ 0xC66B, 0xC66B, 0xC66B },
+{ 0xC66C, 0xC66C, 0xC66C },
+{ 0xC66D, 0xC66D, 0xC66D },
+{ 0xC66E, 0xC66E, 0xC66E },
+{ 0xC66F, 0xC66F, 0xC66F },
+{ 0xC670, 0xC670, 0xC670 },
+{ 0xC671, 0xC671, 0xC671 },
+{ 0xC672, 0xC672, 0xC672 },
+{ 0xC673, 0xC673, 0xC673 },
+{ 0xC674, 0xC674, 0xC674 },
+{ 0xC675, 0xC675, 0xC675 },
+{ 0xC676, 0xC676, 0xC676 },
+{ 0xC677, 0xC677, 0xC677 },
+{ 0xC678, 0xC678, 0xC678 },
+{ 0xC679, 0xC679, 0xC679 },
+{ 0xC67A, 0xC67A, 0xC67A },
+{ 0xC67B, 0xC67B, 0xC67B },
+{ 0xC67C, 0xC67C, 0xC67C },
+{ 0xC67D, 0xC67D, 0xC67D },
+{ 0xC67E, 0xC67E, 0xC67E },
+{ 0xC67F, 0xC67F, 0xC67F },
+{ 0xC680, 0xC680, 0xC680 },
+{ 0xC681, 0xC681, 0xC681 },
+{ 0xC682, 0xC682, 0xC682 },
+{ 0xC683, 0xC683, 0xC683 },
+{ 0xC684, 0xC684, 0xC684 },
+{ 0xC685, 0xC685, 0xC685 },
+{ 0xC686, 0xC686, 0xC686 },
+{ 0xC687, 0xC687, 0xC687 },
+{ 0xC688, 0xC688, 0xC688 },
+{ 0xC689, 0xC689, 0xC689 },
+{ 0xC68A, 0xC68A, 0xC68A },
+{ 0xC68B, 0xC68B, 0xC68B },
+{ 0xC68C, 0xC68C, 0xC68C },
+{ 0xC68D, 0xC68D, 0xC68D },
+{ 0xC68E, 0xC68E, 0xC68E },
+{ 0xC68F, 0xC68F, 0xC68F },
+{ 0xC690, 0xC690, 0xC690 },
+{ 0xC691, 0xC691, 0xC691 },
+{ 0xC692, 0xC692, 0xC692 },
+{ 0xC693, 0xC693, 0xC693 },
+{ 0xC694, 0xC694, 0xC694 },
+{ 0xC695, 0xC695, 0xC695 },
+{ 0xC696, 0xC696, 0xC696 },
+{ 0xC697, 0xC697, 0xC697 },
+{ 0xC698, 0xC698, 0xC698 },
+{ 0xC699, 0xC699, 0xC699 },
+{ 0xC69A, 0xC69A, 0xC69A },
+{ 0xC69B, 0xC69B, 0xC69B },
+{ 0xC69C, 0xC69C, 0xC69C },
+{ 0xC69D, 0xC69D, 0xC69D },
+{ 0xC69E, 0xC69E, 0xC69E },
+{ 0xC69F, 0xC69F, 0xC69F },
+{ 0xC6A0, 0xC6A0, 0xC6A0 },
+{ 0xC6A1, 0xC6A1, 0xC6A1 },
+{ 0xC6A2, 0xC6A2, 0xC6A2 },
+{ 0xC6A3, 0xC6A3, 0xC6A3 },
+{ 0xC6A4, 0xC6A4, 0xC6A4 },
+{ 0xC6A5, 0xC6A5, 0xC6A5 },
+{ 0xC6A6, 0xC6A6, 0xC6A6 },
+{ 0xC6A7, 0xC6A7, 0xC6A7 },
+{ 0xC6A8, 0xC6A8, 0xC6A8 },
+{ 0xC6A9, 0xC6A9, 0xC6A9 },
+{ 0xC6AA, 0xC6AA, 0xC6AA },
+{ 0xC6AB, 0xC6AB, 0xC6AB },
+{ 0xC6AC, 0xC6AC, 0xC6AC },
+{ 0xC6AD, 0xC6AD, 0xC6AD },
+{ 0xC6AE, 0xC6AE, 0xC6AE },
+{ 0xC6AF, 0xC6AF, 0xC6AF },
+{ 0xC6B0, 0xC6B0, 0xC6B0 },
+{ 0xC6B1, 0xC6B1, 0xC6B1 },
+{ 0xC6B2, 0xC6B2, 0xC6B2 },
+{ 0xC6B3, 0xC6B3, 0xC6B3 },
+{ 0xC6B4, 0xC6B4, 0xC6B4 },
+{ 0xC6B5, 0xC6B5, 0xC6B5 },
+{ 0xC6B6, 0xC6B6, 0xC6B6 },
+{ 0xC6B7, 0xC6B7, 0xC6B7 },
+{ 0xC6B8, 0xC6B8, 0xC6B8 },
+{ 0xC6B9, 0xC6B9, 0xC6B9 },
+{ 0xC6BA, 0xC6BA, 0xC6BA },
+{ 0xC6BB, 0xC6BB, 0xC6BB },
+{ 0xC6BC, 0xC6BC, 0xC6BC },
+{ 0xC6BD, 0xC6BD, 0xC6BD },
+{ 0xC6BE, 0xC6BE, 0xC6BE },
+{ 0xC6BF, 0xC6BF, 0xC6BF },
+{ 0xC6C0, 0xC6C0, 0xC6C0 },
+{ 0xC6C1, 0xC6C1, 0xC6C1 },
+{ 0xC6C2, 0xC6C2, 0xC6C2 },
+{ 0xC6C3, 0xC6C3, 0xC6C3 },
+{ 0xC6C4, 0xC6C4, 0xC6C4 },
+{ 0xC6C5, 0xC6C5, 0xC6C5 },
+{ 0xC6C6, 0xC6C6, 0xC6C6 },
+{ 0xC6C7, 0xC6C7, 0xC6C7 },
+{ 0xC6C8, 0xC6C8, 0xC6C8 },
+{ 0xC6C9, 0xC6C9, 0xC6C9 },
+{ 0xC6CA, 0xC6CA, 0xC6CA },
+{ 0xC6CB, 0xC6CB, 0xC6CB },
+{ 0xC6CC, 0xC6CC, 0xC6CC },
+{ 0xC6CD, 0xC6CD, 0xC6CD },
+{ 0xC6CE, 0xC6CE, 0xC6CE },
+{ 0xC6CF, 0xC6CF, 0xC6CF },
+{ 0xC6D0, 0xC6D0, 0xC6D0 },
+{ 0xC6D1, 0xC6D1, 0xC6D1 },
+{ 0xC6D2, 0xC6D2, 0xC6D2 },
+{ 0xC6D3, 0xC6D3, 0xC6D3 },
+{ 0xC6D4, 0xC6D4, 0xC6D4 },
+{ 0xC6D5, 0xC6D5, 0xC6D5 },
+{ 0xC6D6, 0xC6D6, 0xC6D6 },
+{ 0xC6D7, 0xC6D7, 0xC6D7 },
+{ 0xC6D8, 0xC6D8, 0xC6D8 },
+{ 0xC6D9, 0xC6D9, 0xC6D9 },
+{ 0xC6DA, 0xC6DA, 0xC6DA },
+{ 0xC6DB, 0xC6DB, 0xC6DB },
+{ 0xC6DC, 0xC6DC, 0xC6DC },
+{ 0xC6DD, 0xC6DD, 0xC6DD },
+{ 0xC6DE, 0xC6DE, 0xC6DE },
+{ 0xC6DF, 0xC6DF, 0xC6DF },
+{ 0xC6E0, 0xC6E0, 0xC6E0 },
+{ 0xC6E1, 0xC6E1, 0xC6E1 },
+{ 0xC6E2, 0xC6E2, 0xC6E2 },
+{ 0xC6E3, 0xC6E3, 0xC6E3 },
+{ 0xC6E4, 0xC6E4, 0xC6E4 },
+{ 0xC6E5, 0xC6E5, 0xC6E5 },
+{ 0xC6E6, 0xC6E6, 0xC6E6 },
+{ 0xC6E7, 0xC6E7, 0xC6E7 },
+{ 0xC6E8, 0xC6E8, 0xC6E8 },
+{ 0xC6E9, 0xC6E9, 0xC6E9 },
+{ 0xC6EA, 0xC6EA, 0xC6EA },
+{ 0xC6EB, 0xC6EB, 0xC6EB },
+{ 0xC6EC, 0xC6EC, 0xC6EC },
+{ 0xC6ED, 0xC6ED, 0xC6ED },
+{ 0xC6EE, 0xC6EE, 0xC6EE },
+{ 0xC6EF, 0xC6EF, 0xC6EF },
+{ 0xC6F0, 0xC6F0, 0xC6F0 },
+{ 0xC6F1, 0xC6F1, 0xC6F1 },
+{ 0xC6F2, 0xC6F2, 0xC6F2 },
+{ 0xC6F3, 0xC6F3, 0xC6F3 },
+{ 0xC6F4, 0xC6F4, 0xC6F4 },
+{ 0xC6F5, 0xC6F5, 0xC6F5 },
+{ 0xC6F6, 0xC6F6, 0xC6F6 },
+{ 0xC6F7, 0xC6F7, 0xC6F7 },
+{ 0xC6F8, 0xC6F8, 0xC6F8 },
+{ 0xC6F9, 0xC6F9, 0xC6F9 },
+{ 0xC6FA, 0xC6FA, 0xC6FA },
+{ 0xC6FB, 0xC6FB, 0xC6FB },
+{ 0xC6FC, 0xC6FC, 0xC6FC },
+{ 0xC6FD, 0xC6FD, 0xC6FD },
+{ 0xC6FE, 0xC6FE, 0xC6FE },
+{ 0xC6FF, 0xC6FF, 0xC6FF },
+{ 0xC700, 0xC700, 0xC700 },
+{ 0xC701, 0xC701, 0xC701 },
+{ 0xC702, 0xC702, 0xC702 },
+{ 0xC703, 0xC703, 0xC703 },
+{ 0xC704, 0xC704, 0xC704 },
+{ 0xC705, 0xC705, 0xC705 },
+{ 0xC706, 0xC706, 0xC706 },
+{ 0xC707, 0xC707, 0xC707 },
+{ 0xC708, 0xC708, 0xC708 },
+{ 0xC709, 0xC709, 0xC709 },
+{ 0xC70A, 0xC70A, 0xC70A },
+{ 0xC70B, 0xC70B, 0xC70B },
+{ 0xC70C, 0xC70C, 0xC70C },
+{ 0xC70D, 0xC70D, 0xC70D },
+{ 0xC70E, 0xC70E, 0xC70E },
+{ 0xC70F, 0xC70F, 0xC70F },
+{ 0xC710, 0xC710, 0xC710 },
+{ 0xC711, 0xC711, 0xC711 },
+{ 0xC712, 0xC712, 0xC712 },
+{ 0xC713, 0xC713, 0xC713 },
+{ 0xC714, 0xC714, 0xC714 },
+{ 0xC715, 0xC715, 0xC715 },
+{ 0xC716, 0xC716, 0xC716 },
+{ 0xC717, 0xC717, 0xC717 },
+{ 0xC718, 0xC718, 0xC718 },
+{ 0xC719, 0xC719, 0xC719 },
+{ 0xC71A, 0xC71A, 0xC71A },
+{ 0xC71B, 0xC71B, 0xC71B },
+{ 0xC71C, 0xC71C, 0xC71C },
+{ 0xC71D, 0xC71D, 0xC71D },
+{ 0xC71E, 0xC71E, 0xC71E },
+{ 0xC71F, 0xC71F, 0xC71F },
+{ 0xC720, 0xC720, 0xC720 },
+{ 0xC721, 0xC721, 0xC721 },
+{ 0xC722, 0xC722, 0xC722 },
+{ 0xC723, 0xC723, 0xC723 },
+{ 0xC724, 0xC724, 0xC724 },
+{ 0xC725, 0xC725, 0xC725 },
+{ 0xC726, 0xC726, 0xC726 },
+{ 0xC727, 0xC727, 0xC727 },
+{ 0xC728, 0xC728, 0xC728 },
+{ 0xC729, 0xC729, 0xC729 },
+{ 0xC72A, 0xC72A, 0xC72A },
+{ 0xC72B, 0xC72B, 0xC72B },
+{ 0xC72C, 0xC72C, 0xC72C },
+{ 0xC72D, 0xC72D, 0xC72D },
+{ 0xC72E, 0xC72E, 0xC72E },
+{ 0xC72F, 0xC72F, 0xC72F },
+{ 0xC730, 0xC730, 0xC730 },
+{ 0xC731, 0xC731, 0xC731 },
+{ 0xC732, 0xC732, 0xC732 },
+{ 0xC733, 0xC733, 0xC733 },
+{ 0xC734, 0xC734, 0xC734 },
+{ 0xC735, 0xC735, 0xC735 },
+{ 0xC736, 0xC736, 0xC736 },
+{ 0xC737, 0xC737, 0xC737 },
+{ 0xC738, 0xC738, 0xC738 },
+{ 0xC739, 0xC739, 0xC739 },
+{ 0xC73A, 0xC73A, 0xC73A },
+{ 0xC73B, 0xC73B, 0xC73B },
+{ 0xC73C, 0xC73C, 0xC73C },
+{ 0xC73D, 0xC73D, 0xC73D },
+{ 0xC73E, 0xC73E, 0xC73E },
+{ 0xC73F, 0xC73F, 0xC73F },
+{ 0xC740, 0xC740, 0xC740 },
+{ 0xC741, 0xC741, 0xC741 },
+{ 0xC742, 0xC742, 0xC742 },
+{ 0xC743, 0xC743, 0xC743 },
+{ 0xC744, 0xC744, 0xC744 },
+{ 0xC745, 0xC745, 0xC745 },
+{ 0xC746, 0xC746, 0xC746 },
+{ 0xC747, 0xC747, 0xC747 },
+{ 0xC748, 0xC748, 0xC748 },
+{ 0xC749, 0xC749, 0xC749 },
+{ 0xC74A, 0xC74A, 0xC74A },
+{ 0xC74B, 0xC74B, 0xC74B },
+{ 0xC74C, 0xC74C, 0xC74C },
+{ 0xC74D, 0xC74D, 0xC74D },
+{ 0xC74E, 0xC74E, 0xC74E },
+{ 0xC74F, 0xC74F, 0xC74F },
+{ 0xC750, 0xC750, 0xC750 },
+{ 0xC751, 0xC751, 0xC751 },
+{ 0xC752, 0xC752, 0xC752 },
+{ 0xC753, 0xC753, 0xC753 },
+{ 0xC754, 0xC754, 0xC754 },
+{ 0xC755, 0xC755, 0xC755 },
+{ 0xC756, 0xC756, 0xC756 },
+{ 0xC757, 0xC757, 0xC757 },
+{ 0xC758, 0xC758, 0xC758 },
+{ 0xC759, 0xC759, 0xC759 },
+{ 0xC75A, 0xC75A, 0xC75A },
+{ 0xC75B, 0xC75B, 0xC75B },
+{ 0xC75C, 0xC75C, 0xC75C },
+{ 0xC75D, 0xC75D, 0xC75D },
+{ 0xC75E, 0xC75E, 0xC75E },
+{ 0xC75F, 0xC75F, 0xC75F },
+{ 0xC760, 0xC760, 0xC760 },
+{ 0xC761, 0xC761, 0xC761 },
+{ 0xC762, 0xC762, 0xC762 },
+{ 0xC763, 0xC763, 0xC763 },
+{ 0xC764, 0xC764, 0xC764 },
+{ 0xC765, 0xC765, 0xC765 },
+{ 0xC766, 0xC766, 0xC766 },
+{ 0xC767, 0xC767, 0xC767 },
+{ 0xC768, 0xC768, 0xC768 },
+{ 0xC769, 0xC769, 0xC769 },
+{ 0xC76A, 0xC76A, 0xC76A },
+{ 0xC76B, 0xC76B, 0xC76B },
+{ 0xC76C, 0xC76C, 0xC76C },
+{ 0xC76D, 0xC76D, 0xC76D },
+{ 0xC76E, 0xC76E, 0xC76E },
+{ 0xC76F, 0xC76F, 0xC76F },
+{ 0xC770, 0xC770, 0xC770 },
+{ 0xC771, 0xC771, 0xC771 },
+{ 0xC772, 0xC772, 0xC772 },
+{ 0xC773, 0xC773, 0xC773 },
+{ 0xC774, 0xC774, 0xC774 },
+{ 0xC775, 0xC775, 0xC775 },
+{ 0xC776, 0xC776, 0xC776 },
+{ 0xC777, 0xC777, 0xC777 },
+{ 0xC778, 0xC778, 0xC778 },
+{ 0xC779, 0xC779, 0xC779 },
+{ 0xC77A, 0xC77A, 0xC77A },
+{ 0xC77B, 0xC77B, 0xC77B },
+{ 0xC77C, 0xC77C, 0xC77C },
+{ 0xC77D, 0xC77D, 0xC77D },
+{ 0xC77E, 0xC77E, 0xC77E },
+{ 0xC77F, 0xC77F, 0xC77F },
+{ 0xC780, 0xC780, 0xC780 },
+{ 0xC781, 0xC781, 0xC781 },
+{ 0xC782, 0xC782, 0xC782 },
+{ 0xC783, 0xC783, 0xC783 },
+{ 0xC784, 0xC784, 0xC784 },
+{ 0xC785, 0xC785, 0xC785 },
+{ 0xC786, 0xC786, 0xC786 },
+{ 0xC787, 0xC787, 0xC787 },
+{ 0xC788, 0xC788, 0xC788 },
+{ 0xC789, 0xC789, 0xC789 },
+{ 0xC78A, 0xC78A, 0xC78A },
+{ 0xC78B, 0xC78B, 0xC78B },
+{ 0xC78C, 0xC78C, 0xC78C },
+{ 0xC78D, 0xC78D, 0xC78D },
+{ 0xC78E, 0xC78E, 0xC78E },
+{ 0xC78F, 0xC78F, 0xC78F },
+{ 0xC790, 0xC790, 0xC790 },
+{ 0xC791, 0xC791, 0xC791 },
+{ 0xC792, 0xC792, 0xC792 },
+{ 0xC793, 0xC793, 0xC793 },
+{ 0xC794, 0xC794, 0xC794 },
+{ 0xC795, 0xC795, 0xC795 },
+{ 0xC796, 0xC796, 0xC796 },
+{ 0xC797, 0xC797, 0xC797 },
+{ 0xC798, 0xC798, 0xC798 },
+{ 0xC799, 0xC799, 0xC799 },
+{ 0xC79A, 0xC79A, 0xC79A },
+{ 0xC79B, 0xC79B, 0xC79B },
+{ 0xC79C, 0xC79C, 0xC79C },
+{ 0xC79D, 0xC79D, 0xC79D },
+{ 0xC79E, 0xC79E, 0xC79E },
+{ 0xC79F, 0xC79F, 0xC79F },
+{ 0xC7A0, 0xC7A0, 0xC7A0 },
+{ 0xC7A1, 0xC7A1, 0xC7A1 },
+{ 0xC7A2, 0xC7A2, 0xC7A2 },
+{ 0xC7A3, 0xC7A3, 0xC7A3 },
+{ 0xC7A4, 0xC7A4, 0xC7A4 },
+{ 0xC7A5, 0xC7A5, 0xC7A5 },
+{ 0xC7A6, 0xC7A6, 0xC7A6 },
+{ 0xC7A7, 0xC7A7, 0xC7A7 },
+{ 0xC7A8, 0xC7A8, 0xC7A8 },
+{ 0xC7A9, 0xC7A9, 0xC7A9 },
+{ 0xC7AA, 0xC7AA, 0xC7AA },
+{ 0xC7AB, 0xC7AB, 0xC7AB },
+{ 0xC7AC, 0xC7AC, 0xC7AC },
+{ 0xC7AD, 0xC7AD, 0xC7AD },
+{ 0xC7AE, 0xC7AE, 0xC7AE },
+{ 0xC7AF, 0xC7AF, 0xC7AF },
+{ 0xC7B0, 0xC7B0, 0xC7B0 },
+{ 0xC7B1, 0xC7B1, 0xC7B1 },
+{ 0xC7B2, 0xC7B2, 0xC7B2 },
+{ 0xC7B3, 0xC7B3, 0xC7B3 },
+{ 0xC7B4, 0xC7B4, 0xC7B4 },
+{ 0xC7B5, 0xC7B5, 0xC7B5 },
+{ 0xC7B6, 0xC7B6, 0xC7B6 },
+{ 0xC7B7, 0xC7B7, 0xC7B7 },
+{ 0xC7B8, 0xC7B8, 0xC7B8 },
+{ 0xC7B9, 0xC7B9, 0xC7B9 },
+{ 0xC7BA, 0xC7BA, 0xC7BA },
+{ 0xC7BB, 0xC7BB, 0xC7BB },
+{ 0xC7BC, 0xC7BC, 0xC7BC },
+{ 0xC7BD, 0xC7BD, 0xC7BD },
+{ 0xC7BE, 0xC7BE, 0xC7BE },
+{ 0xC7BF, 0xC7BF, 0xC7BF },
+{ 0xC7C0, 0xC7C0, 0xC7C0 },
+{ 0xC7C1, 0xC7C1, 0xC7C1 },
+{ 0xC7C2, 0xC7C2, 0xC7C2 },
+{ 0xC7C3, 0xC7C3, 0xC7C3 },
+{ 0xC7C4, 0xC7C4, 0xC7C4 },
+{ 0xC7C5, 0xC7C5, 0xC7C5 },
+{ 0xC7C6, 0xC7C6, 0xC7C6 },
+{ 0xC7C7, 0xC7C7, 0xC7C7 },
+{ 0xC7C8, 0xC7C8, 0xC7C8 },
+{ 0xC7C9, 0xC7C9, 0xC7C9 },
+{ 0xC7CA, 0xC7CA, 0xC7CA },
+{ 0xC7CB, 0xC7CB, 0xC7CB },
+{ 0xC7CC, 0xC7CC, 0xC7CC },
+{ 0xC7CD, 0xC7CD, 0xC7CD },
+{ 0xC7CE, 0xC7CE, 0xC7CE },
+{ 0xC7CF, 0xC7CF, 0xC7CF },
+{ 0xC7D0, 0xC7D0, 0xC7D0 },
+{ 0xC7D1, 0xC7D1, 0xC7D1 },
+{ 0xC7D2, 0xC7D2, 0xC7D2 },
+{ 0xC7D3, 0xC7D3, 0xC7D3 },
+{ 0xC7D4, 0xC7D4, 0xC7D4 },
+{ 0xC7D5, 0xC7D5, 0xC7D5 },
+{ 0xC7D6, 0xC7D6, 0xC7D6 },
+{ 0xC7D7, 0xC7D7, 0xC7D7 },
+{ 0xC7D8, 0xC7D8, 0xC7D8 },
+{ 0xC7D9, 0xC7D9, 0xC7D9 },
+{ 0xC7DA, 0xC7DA, 0xC7DA },
+{ 0xC7DB, 0xC7DB, 0xC7DB },
+{ 0xC7DC, 0xC7DC, 0xC7DC },
+{ 0xC7DD, 0xC7DD, 0xC7DD },
+{ 0xC7DE, 0xC7DE, 0xC7DE },
+{ 0xC7DF, 0xC7DF, 0xC7DF },
+{ 0xC7E0, 0xC7E0, 0xC7E0 },
+{ 0xC7E1, 0xC7E1, 0xC7E1 },
+{ 0xC7E2, 0xC7E2, 0xC7E2 },
+{ 0xC7E3, 0xC7E3, 0xC7E3 },
+{ 0xC7E4, 0xC7E4, 0xC7E4 },
+{ 0xC7E5, 0xC7E5, 0xC7E5 },
+{ 0xC7E6, 0xC7E6, 0xC7E6 },
+{ 0xC7E7, 0xC7E7, 0xC7E7 },
+{ 0xC7E8, 0xC7E8, 0xC7E8 },
+{ 0xC7E9, 0xC7E9, 0xC7E9 },
+{ 0xC7EA, 0xC7EA, 0xC7EA },
+{ 0xC7EB, 0xC7EB, 0xC7EB },
+{ 0xC7EC, 0xC7EC, 0xC7EC },
+{ 0xC7ED, 0xC7ED, 0xC7ED },
+{ 0xC7EE, 0xC7EE, 0xC7EE },
+{ 0xC7EF, 0xC7EF, 0xC7EF },
+{ 0xC7F0, 0xC7F0, 0xC7F0 },
+{ 0xC7F1, 0xC7F1, 0xC7F1 },
+{ 0xC7F2, 0xC7F2, 0xC7F2 },
+{ 0xC7F3, 0xC7F3, 0xC7F3 },
+{ 0xC7F4, 0xC7F4, 0xC7F4 },
+{ 0xC7F5, 0xC7F5, 0xC7F5 },
+{ 0xC7F6, 0xC7F6, 0xC7F6 },
+{ 0xC7F7, 0xC7F7, 0xC7F7 },
+{ 0xC7F8, 0xC7F8, 0xC7F8 },
+{ 0xC7F9, 0xC7F9, 0xC7F9 },
+{ 0xC7FA, 0xC7FA, 0xC7FA },
+{ 0xC7FB, 0xC7FB, 0xC7FB },
+{ 0xC7FC, 0xC7FC, 0xC7FC },
+{ 0xC7FD, 0xC7FD, 0xC7FD },
+{ 0xC7FE, 0xC7FE, 0xC7FE },
+{ 0xC7FF, 0xC7FF, 0xC7FF },
+{ 0xC800, 0xC800, 0xC800 },
+{ 0xC801, 0xC801, 0xC801 },
+{ 0xC802, 0xC802, 0xC802 },
+{ 0xC803, 0xC803, 0xC803 },
+{ 0xC804, 0xC804, 0xC804 },
+{ 0xC805, 0xC805, 0xC805 },
+{ 0xC806, 0xC806, 0xC806 },
+{ 0xC807, 0xC807, 0xC807 },
+{ 0xC808, 0xC808, 0xC808 },
+{ 0xC809, 0xC809, 0xC809 },
+{ 0xC80A, 0xC80A, 0xC80A },
+{ 0xC80B, 0xC80B, 0xC80B },
+{ 0xC80C, 0xC80C, 0xC80C },
+{ 0xC80D, 0xC80D, 0xC80D },
+{ 0xC80E, 0xC80E, 0xC80E },
+{ 0xC80F, 0xC80F, 0xC80F },
+{ 0xC810, 0xC810, 0xC810 },
+{ 0xC811, 0xC811, 0xC811 },
+{ 0xC812, 0xC812, 0xC812 },
+{ 0xC813, 0xC813, 0xC813 },
+{ 0xC814, 0xC814, 0xC814 },
+{ 0xC815, 0xC815, 0xC815 },
+{ 0xC816, 0xC816, 0xC816 },
+{ 0xC817, 0xC817, 0xC817 },
+{ 0xC818, 0xC818, 0xC818 },
+{ 0xC819, 0xC819, 0xC819 },
+{ 0xC81A, 0xC81A, 0xC81A },
+{ 0xC81B, 0xC81B, 0xC81B },
+{ 0xC81C, 0xC81C, 0xC81C },
+{ 0xC81D, 0xC81D, 0xC81D },
+{ 0xC81E, 0xC81E, 0xC81E },
+{ 0xC81F, 0xC81F, 0xC81F },
+{ 0xC820, 0xC820, 0xC820 },
+{ 0xC821, 0xC821, 0xC821 },
+{ 0xC822, 0xC822, 0xC822 },
+{ 0xC823, 0xC823, 0xC823 },
+{ 0xC824, 0xC824, 0xC824 },
+{ 0xC825, 0xC825, 0xC825 },
+{ 0xC826, 0xC826, 0xC826 },
+{ 0xC827, 0xC827, 0xC827 },
+{ 0xC828, 0xC828, 0xC828 },
+{ 0xC829, 0xC829, 0xC829 },
+{ 0xC82A, 0xC82A, 0xC82A },
+{ 0xC82B, 0xC82B, 0xC82B },
+{ 0xC82C, 0xC82C, 0xC82C },
+{ 0xC82D, 0xC82D, 0xC82D },
+{ 0xC82E, 0xC82E, 0xC82E },
+{ 0xC82F, 0xC82F, 0xC82F },
+{ 0xC830, 0xC830, 0xC830 },
+{ 0xC831, 0xC831, 0xC831 },
+{ 0xC832, 0xC832, 0xC832 },
+{ 0xC833, 0xC833, 0xC833 },
+{ 0xC834, 0xC834, 0xC834 },
+{ 0xC835, 0xC835, 0xC835 },
+{ 0xC836, 0xC836, 0xC836 },
+{ 0xC837, 0xC837, 0xC837 },
+{ 0xC838, 0xC838, 0xC838 },
+{ 0xC839, 0xC839, 0xC839 },
+{ 0xC83A, 0xC83A, 0xC83A },
+{ 0xC83B, 0xC83B, 0xC83B },
+{ 0xC83C, 0xC83C, 0xC83C },
+{ 0xC83D, 0xC83D, 0xC83D },
+{ 0xC83E, 0xC83E, 0xC83E },
+{ 0xC83F, 0xC83F, 0xC83F },
+{ 0xC840, 0xC840, 0xC840 },
+{ 0xC841, 0xC841, 0xC841 },
+{ 0xC842, 0xC842, 0xC842 },
+{ 0xC843, 0xC843, 0xC843 },
+{ 0xC844, 0xC844, 0xC844 },
+{ 0xC845, 0xC845, 0xC845 },
+{ 0xC846, 0xC846, 0xC846 },
+{ 0xC847, 0xC847, 0xC847 },
+{ 0xC848, 0xC848, 0xC848 },
+{ 0xC849, 0xC849, 0xC849 },
+{ 0xC84A, 0xC84A, 0xC84A },
+{ 0xC84B, 0xC84B, 0xC84B },
+{ 0xC84C, 0xC84C, 0xC84C },
+{ 0xC84D, 0xC84D, 0xC84D },
+{ 0xC84E, 0xC84E, 0xC84E },
+{ 0xC84F, 0xC84F, 0xC84F },
+{ 0xC850, 0xC850, 0xC850 },
+{ 0xC851, 0xC851, 0xC851 },
+{ 0xC852, 0xC852, 0xC852 },
+{ 0xC853, 0xC853, 0xC853 },
+{ 0xC854, 0xC854, 0xC854 },
+{ 0xC855, 0xC855, 0xC855 },
+{ 0xC856, 0xC856, 0xC856 },
+{ 0xC857, 0xC857, 0xC857 },
+{ 0xC858, 0xC858, 0xC858 },
+{ 0xC859, 0xC859, 0xC859 },
+{ 0xC85A, 0xC85A, 0xC85A },
+{ 0xC85B, 0xC85B, 0xC85B },
+{ 0xC85C, 0xC85C, 0xC85C },
+{ 0xC85D, 0xC85D, 0xC85D },
+{ 0xC85E, 0xC85E, 0xC85E },
+{ 0xC85F, 0xC85F, 0xC85F },
+{ 0xC860, 0xC860, 0xC860 },
+{ 0xC861, 0xC861, 0xC861 },
+{ 0xC862, 0xC862, 0xC862 },
+{ 0xC863, 0xC863, 0xC863 },
+{ 0xC864, 0xC864, 0xC864 },
+{ 0xC865, 0xC865, 0xC865 },
+{ 0xC866, 0xC866, 0xC866 },
+{ 0xC867, 0xC867, 0xC867 },
+{ 0xC868, 0xC868, 0xC868 },
+{ 0xC869, 0xC869, 0xC869 },
+{ 0xC86A, 0xC86A, 0xC86A },
+{ 0xC86B, 0xC86B, 0xC86B },
+{ 0xC86C, 0xC86C, 0xC86C },
+{ 0xC86D, 0xC86D, 0xC86D },
+{ 0xC86E, 0xC86E, 0xC86E },
+{ 0xC86F, 0xC86F, 0xC86F },
+{ 0xC870, 0xC870, 0xC870 },
+{ 0xC871, 0xC871, 0xC871 },
+{ 0xC872, 0xC872, 0xC872 },
+{ 0xC873, 0xC873, 0xC873 },
+{ 0xC874, 0xC874, 0xC874 },
+{ 0xC875, 0xC875, 0xC875 },
+{ 0xC876, 0xC876, 0xC876 },
+{ 0xC877, 0xC877, 0xC877 },
+{ 0xC878, 0xC878, 0xC878 },
+{ 0xC879, 0xC879, 0xC879 },
+{ 0xC87A, 0xC87A, 0xC87A },
+{ 0xC87B, 0xC87B, 0xC87B },
+{ 0xC87C, 0xC87C, 0xC87C },
+{ 0xC87D, 0xC87D, 0xC87D },
+{ 0xC87E, 0xC87E, 0xC87E },
+{ 0xC87F, 0xC87F, 0xC87F },
+{ 0xC880, 0xC880, 0xC880 },
+{ 0xC881, 0xC881, 0xC881 },
+{ 0xC882, 0xC882, 0xC882 },
+{ 0xC883, 0xC883, 0xC883 },
+{ 0xC884, 0xC884, 0xC884 },
+{ 0xC885, 0xC885, 0xC885 },
+{ 0xC886, 0xC886, 0xC886 },
+{ 0xC887, 0xC887, 0xC887 },
+{ 0xC888, 0xC888, 0xC888 },
+{ 0xC889, 0xC889, 0xC889 },
+{ 0xC88A, 0xC88A, 0xC88A },
+{ 0xC88B, 0xC88B, 0xC88B },
+{ 0xC88C, 0xC88C, 0xC88C },
+{ 0xC88D, 0xC88D, 0xC88D },
+{ 0xC88E, 0xC88E, 0xC88E },
+{ 0xC88F, 0xC88F, 0xC88F },
+{ 0xC890, 0xC890, 0xC890 },
+{ 0xC891, 0xC891, 0xC891 },
+{ 0xC892, 0xC892, 0xC892 },
+{ 0xC893, 0xC893, 0xC893 },
+{ 0xC894, 0xC894, 0xC894 },
+{ 0xC895, 0xC895, 0xC895 },
+{ 0xC896, 0xC896, 0xC896 },
+{ 0xC897, 0xC897, 0xC897 },
+{ 0xC898, 0xC898, 0xC898 },
+{ 0xC899, 0xC899, 0xC899 },
+{ 0xC89A, 0xC89A, 0xC89A },
+{ 0xC89B, 0xC89B, 0xC89B },
+{ 0xC89C, 0xC89C, 0xC89C },
+{ 0xC89D, 0xC89D, 0xC89D },
+{ 0xC89E, 0xC89E, 0xC89E },
+{ 0xC89F, 0xC89F, 0xC89F },
+{ 0xC8A0, 0xC8A0, 0xC8A0 },
+{ 0xC8A1, 0xC8A1, 0xC8A1 },
+{ 0xC8A2, 0xC8A2, 0xC8A2 },
+{ 0xC8A3, 0xC8A3, 0xC8A3 },
+{ 0xC8A4, 0xC8A4, 0xC8A4 },
+{ 0xC8A5, 0xC8A5, 0xC8A5 },
+{ 0xC8A6, 0xC8A6, 0xC8A6 },
+{ 0xC8A7, 0xC8A7, 0xC8A7 },
+{ 0xC8A8, 0xC8A8, 0xC8A8 },
+{ 0xC8A9, 0xC8A9, 0xC8A9 },
+{ 0xC8AA, 0xC8AA, 0xC8AA },
+{ 0xC8AB, 0xC8AB, 0xC8AB },
+{ 0xC8AC, 0xC8AC, 0xC8AC },
+{ 0xC8AD, 0xC8AD, 0xC8AD },
+{ 0xC8AE, 0xC8AE, 0xC8AE },
+{ 0xC8AF, 0xC8AF, 0xC8AF },
+{ 0xC8B0, 0xC8B0, 0xC8B0 },
+{ 0xC8B1, 0xC8B1, 0xC8B1 },
+{ 0xC8B2, 0xC8B2, 0xC8B2 },
+{ 0xC8B3, 0xC8B3, 0xC8B3 },
+{ 0xC8B4, 0xC8B4, 0xC8B4 },
+{ 0xC8B5, 0xC8B5, 0xC8B5 },
+{ 0xC8B6, 0xC8B6, 0xC8B6 },
+{ 0xC8B7, 0xC8B7, 0xC8B7 },
+{ 0xC8B8, 0xC8B8, 0xC8B8 },
+{ 0xC8B9, 0xC8B9, 0xC8B9 },
+{ 0xC8BA, 0xC8BA, 0xC8BA },
+{ 0xC8BB, 0xC8BB, 0xC8BB },
+{ 0xC8BC, 0xC8BC, 0xC8BC },
+{ 0xC8BD, 0xC8BD, 0xC8BD },
+{ 0xC8BE, 0xC8BE, 0xC8BE },
+{ 0xC8BF, 0xC8BF, 0xC8BF },
+{ 0xC8C0, 0xC8C0, 0xC8C0 },
+{ 0xC8C1, 0xC8C1, 0xC8C1 },
+{ 0xC8C2, 0xC8C2, 0xC8C2 },
+{ 0xC8C3, 0xC8C3, 0xC8C3 },
+{ 0xC8C4, 0xC8C4, 0xC8C4 },
+{ 0xC8C5, 0xC8C5, 0xC8C5 },
+{ 0xC8C6, 0xC8C6, 0xC8C6 },
+{ 0xC8C7, 0xC8C7, 0xC8C7 },
+{ 0xC8C8, 0xC8C8, 0xC8C8 },
+{ 0xC8C9, 0xC8C9, 0xC8C9 },
+{ 0xC8CA, 0xC8CA, 0xC8CA },
+{ 0xC8CB, 0xC8CB, 0xC8CB },
+{ 0xC8CC, 0xC8CC, 0xC8CC },
+{ 0xC8CD, 0xC8CD, 0xC8CD },
+{ 0xC8CE, 0xC8CE, 0xC8CE },
+{ 0xC8CF, 0xC8CF, 0xC8CF },
+{ 0xC8D0, 0xC8D0, 0xC8D0 },
+{ 0xC8D1, 0xC8D1, 0xC8D1 },
+{ 0xC8D2, 0xC8D2, 0xC8D2 },
+{ 0xC8D3, 0xC8D3, 0xC8D3 },
+{ 0xC8D4, 0xC8D4, 0xC8D4 },
+{ 0xC8D5, 0xC8D5, 0xC8D5 },
+{ 0xC8D6, 0xC8D6, 0xC8D6 },
+{ 0xC8D7, 0xC8D7, 0xC8D7 },
+{ 0xC8D8, 0xC8D8, 0xC8D8 },
+{ 0xC8D9, 0xC8D9, 0xC8D9 },
+{ 0xC8DA, 0xC8DA, 0xC8DA },
+{ 0xC8DB, 0xC8DB, 0xC8DB },
+{ 0xC8DC, 0xC8DC, 0xC8DC },
+{ 0xC8DD, 0xC8DD, 0xC8DD },
+{ 0xC8DE, 0xC8DE, 0xC8DE },
+{ 0xC8DF, 0xC8DF, 0xC8DF },
+{ 0xC8E0, 0xC8E0, 0xC8E0 },
+{ 0xC8E1, 0xC8E1, 0xC8E1 },
+{ 0xC8E2, 0xC8E2, 0xC8E2 },
+{ 0xC8E3, 0xC8E3, 0xC8E3 },
+{ 0xC8E4, 0xC8E4, 0xC8E4 },
+{ 0xC8E5, 0xC8E5, 0xC8E5 },
+{ 0xC8E6, 0xC8E6, 0xC8E6 },
+{ 0xC8E7, 0xC8E7, 0xC8E7 },
+{ 0xC8E8, 0xC8E8, 0xC8E8 },
+{ 0xC8E9, 0xC8E9, 0xC8E9 },
+{ 0xC8EA, 0xC8EA, 0xC8EA },
+{ 0xC8EB, 0xC8EB, 0xC8EB },
+{ 0xC8EC, 0xC8EC, 0xC8EC },
+{ 0xC8ED, 0xC8ED, 0xC8ED },
+{ 0xC8EE, 0xC8EE, 0xC8EE },
+{ 0xC8EF, 0xC8EF, 0xC8EF },
+{ 0xC8F0, 0xC8F0, 0xC8F0 },
+{ 0xC8F1, 0xC8F1, 0xC8F1 },
+{ 0xC8F2, 0xC8F2, 0xC8F2 },
+{ 0xC8F3, 0xC8F3, 0xC8F3 },
+{ 0xC8F4, 0xC8F4, 0xC8F4 },
+{ 0xC8F5, 0xC8F5, 0xC8F5 },
+{ 0xC8F6, 0xC8F6, 0xC8F6 },
+{ 0xC8F7, 0xC8F7, 0xC8F7 },
+{ 0xC8F8, 0xC8F8, 0xC8F8 },
+{ 0xC8F9, 0xC8F9, 0xC8F9 },
+{ 0xC8FA, 0xC8FA, 0xC8FA },
+{ 0xC8FB, 0xC8FB, 0xC8FB },
+{ 0xC8FC, 0xC8FC, 0xC8FC },
+{ 0xC8FD, 0xC8FD, 0xC8FD },
+{ 0xC8FE, 0xC8FE, 0xC8FE },
+{ 0xC8FF, 0xC8FF, 0xC8FF },
+{ 0xC900, 0xC900, 0xC900 },
+{ 0xC901, 0xC901, 0xC901 },
+{ 0xC902, 0xC902, 0xC902 },
+{ 0xC903, 0xC903, 0xC903 },
+{ 0xC904, 0xC904, 0xC904 },
+{ 0xC905, 0xC905, 0xC905 },
+{ 0xC906, 0xC906, 0xC906 },
+{ 0xC907, 0xC907, 0xC907 },
+{ 0xC908, 0xC908, 0xC908 },
+{ 0xC909, 0xC909, 0xC909 },
+{ 0xC90A, 0xC90A, 0xC90A },
+{ 0xC90B, 0xC90B, 0xC90B },
+{ 0xC90C, 0xC90C, 0xC90C },
+{ 0xC90D, 0xC90D, 0xC90D },
+{ 0xC90E, 0xC90E, 0xC90E },
+{ 0xC90F, 0xC90F, 0xC90F },
+{ 0xC910, 0xC910, 0xC910 },
+{ 0xC911, 0xC911, 0xC911 },
+{ 0xC912, 0xC912, 0xC912 },
+{ 0xC913, 0xC913, 0xC913 },
+{ 0xC914, 0xC914, 0xC914 },
+{ 0xC915, 0xC915, 0xC915 },
+{ 0xC916, 0xC916, 0xC916 },
+{ 0xC917, 0xC917, 0xC917 },
+{ 0xC918, 0xC918, 0xC918 },
+{ 0xC919, 0xC919, 0xC919 },
+{ 0xC91A, 0xC91A, 0xC91A },
+{ 0xC91B, 0xC91B, 0xC91B },
+{ 0xC91C, 0xC91C, 0xC91C },
+{ 0xC91D, 0xC91D, 0xC91D },
+{ 0xC91E, 0xC91E, 0xC91E },
+{ 0xC91F, 0xC91F, 0xC91F },
+{ 0xC920, 0xC920, 0xC920 },
+{ 0xC921, 0xC921, 0xC921 },
+{ 0xC922, 0xC922, 0xC922 },
+{ 0xC923, 0xC923, 0xC923 },
+{ 0xC924, 0xC924, 0xC924 },
+{ 0xC925, 0xC925, 0xC925 },
+{ 0xC926, 0xC926, 0xC926 },
+{ 0xC927, 0xC927, 0xC927 },
+{ 0xC928, 0xC928, 0xC928 },
+{ 0xC929, 0xC929, 0xC929 },
+{ 0xC92A, 0xC92A, 0xC92A },
+{ 0xC92B, 0xC92B, 0xC92B },
+{ 0xC92C, 0xC92C, 0xC92C },
+{ 0xC92D, 0xC92D, 0xC92D },
+{ 0xC92E, 0xC92E, 0xC92E },
+{ 0xC92F, 0xC92F, 0xC92F },
+{ 0xC930, 0xC930, 0xC930 },
+{ 0xC931, 0xC931, 0xC931 },
+{ 0xC932, 0xC932, 0xC932 },
+{ 0xC933, 0xC933, 0xC933 },
+{ 0xC934, 0xC934, 0xC934 },
+{ 0xC935, 0xC935, 0xC935 },
+{ 0xC936, 0xC936, 0xC936 },
+{ 0xC937, 0xC937, 0xC937 },
+{ 0xC938, 0xC938, 0xC938 },
+{ 0xC939, 0xC939, 0xC939 },
+{ 0xC93A, 0xC93A, 0xC93A },
+{ 0xC93B, 0xC93B, 0xC93B },
+{ 0xC93C, 0xC93C, 0xC93C },
+{ 0xC93D, 0xC93D, 0xC93D },
+{ 0xC93E, 0xC93E, 0xC93E },
+{ 0xC93F, 0xC93F, 0xC93F },
+{ 0xC940, 0xC940, 0xC940 },
+{ 0xC941, 0xC941, 0xC941 },
+{ 0xC942, 0xC942, 0xC942 },
+{ 0xC943, 0xC943, 0xC943 },
+{ 0xC944, 0xC944, 0xC944 },
+{ 0xC945, 0xC945, 0xC945 },
+{ 0xC946, 0xC946, 0xC946 },
+{ 0xC947, 0xC947, 0xC947 },
+{ 0xC948, 0xC948, 0xC948 },
+{ 0xC949, 0xC949, 0xC949 },
+{ 0xC94A, 0xC94A, 0xC94A },
+{ 0xC94B, 0xC94B, 0xC94B },
+{ 0xC94C, 0xC94C, 0xC94C },
+{ 0xC94D, 0xC94D, 0xC94D },
+{ 0xC94E, 0xC94E, 0xC94E },
+{ 0xC94F, 0xC94F, 0xC94F },
+{ 0xC950, 0xC950, 0xC950 },
+{ 0xC951, 0xC951, 0xC951 },
+{ 0xC952, 0xC952, 0xC952 },
+{ 0xC953, 0xC953, 0xC953 },
+{ 0xC954, 0xC954, 0xC954 },
+{ 0xC955, 0xC955, 0xC955 },
+{ 0xC956, 0xC956, 0xC956 },
+{ 0xC957, 0xC957, 0xC957 },
+{ 0xC958, 0xC958, 0xC958 },
+{ 0xC959, 0xC959, 0xC959 },
+{ 0xC95A, 0xC95A, 0xC95A },
+{ 0xC95B, 0xC95B, 0xC95B },
+{ 0xC95C, 0xC95C, 0xC95C },
+{ 0xC95D, 0xC95D, 0xC95D },
+{ 0xC95E, 0xC95E, 0xC95E },
+{ 0xC95F, 0xC95F, 0xC95F },
+{ 0xC960, 0xC960, 0xC960 },
+{ 0xC961, 0xC961, 0xC961 },
+{ 0xC962, 0xC962, 0xC962 },
+{ 0xC963, 0xC963, 0xC963 },
+{ 0xC964, 0xC964, 0xC964 },
+{ 0xC965, 0xC965, 0xC965 },
+{ 0xC966, 0xC966, 0xC966 },
+{ 0xC967, 0xC967, 0xC967 },
+{ 0xC968, 0xC968, 0xC968 },
+{ 0xC969, 0xC969, 0xC969 },
+{ 0xC96A, 0xC96A, 0xC96A },
+{ 0xC96B, 0xC96B, 0xC96B },
+{ 0xC96C, 0xC96C, 0xC96C },
+{ 0xC96D, 0xC96D, 0xC96D },
+{ 0xC96E, 0xC96E, 0xC96E },
+{ 0xC96F, 0xC96F, 0xC96F },
+{ 0xC970, 0xC970, 0xC970 },
+{ 0xC971, 0xC971, 0xC971 },
+{ 0xC972, 0xC972, 0xC972 },
+{ 0xC973, 0xC973, 0xC973 },
+{ 0xC974, 0xC974, 0xC974 },
+{ 0xC975, 0xC975, 0xC975 },
+{ 0xC976, 0xC976, 0xC976 },
+{ 0xC977, 0xC977, 0xC977 },
+{ 0xC978, 0xC978, 0xC978 },
+{ 0xC979, 0xC979, 0xC979 },
+{ 0xC97A, 0xC97A, 0xC97A },
+{ 0xC97B, 0xC97B, 0xC97B },
+{ 0xC97C, 0xC97C, 0xC97C },
+{ 0xC97D, 0xC97D, 0xC97D },
+{ 0xC97E, 0xC97E, 0xC97E },
+{ 0xC97F, 0xC97F, 0xC97F },
+{ 0xC980, 0xC980, 0xC980 },
+{ 0xC981, 0xC981, 0xC981 },
+{ 0xC982, 0xC982, 0xC982 },
+{ 0xC983, 0xC983, 0xC983 },
+{ 0xC984, 0xC984, 0xC984 },
+{ 0xC985, 0xC985, 0xC985 },
+{ 0xC986, 0xC986, 0xC986 },
+{ 0xC987, 0xC987, 0xC987 },
+{ 0xC988, 0xC988, 0xC988 },
+{ 0xC989, 0xC989, 0xC989 },
+{ 0xC98A, 0xC98A, 0xC98A },
+{ 0xC98B, 0xC98B, 0xC98B },
+{ 0xC98C, 0xC98C, 0xC98C },
+{ 0xC98D, 0xC98D, 0xC98D },
+{ 0xC98E, 0xC98E, 0xC98E },
+{ 0xC98F, 0xC98F, 0xC98F },
+{ 0xC990, 0xC990, 0xC990 },
+{ 0xC991, 0xC991, 0xC991 },
+{ 0xC992, 0xC992, 0xC992 },
+{ 0xC993, 0xC993, 0xC993 },
+{ 0xC994, 0xC994, 0xC994 },
+{ 0xC995, 0xC995, 0xC995 },
+{ 0xC996, 0xC996, 0xC996 },
+{ 0xC997, 0xC997, 0xC997 },
+{ 0xC998, 0xC998, 0xC998 },
+{ 0xC999, 0xC999, 0xC999 },
+{ 0xC99A, 0xC99A, 0xC99A },
+{ 0xC99B, 0xC99B, 0xC99B },
+{ 0xC99C, 0xC99C, 0xC99C },
+{ 0xC99D, 0xC99D, 0xC99D },
+{ 0xC99E, 0xC99E, 0xC99E },
+{ 0xC99F, 0xC99F, 0xC99F },
+{ 0xC9A0, 0xC9A0, 0xC9A0 },
+{ 0xC9A1, 0xC9A1, 0xC9A1 },
+{ 0xC9A2, 0xC9A2, 0xC9A2 },
+{ 0xC9A3, 0xC9A3, 0xC9A3 },
+{ 0xC9A4, 0xC9A4, 0xC9A4 },
+{ 0xC9A5, 0xC9A5, 0xC9A5 },
+{ 0xC9A6, 0xC9A6, 0xC9A6 },
+{ 0xC9A7, 0xC9A7, 0xC9A7 },
+{ 0xC9A8, 0xC9A8, 0xC9A8 },
+{ 0xC9A9, 0xC9A9, 0xC9A9 },
+{ 0xC9AA, 0xC9AA, 0xC9AA },
+{ 0xC9AB, 0xC9AB, 0xC9AB },
+{ 0xC9AC, 0xC9AC, 0xC9AC },
+{ 0xC9AD, 0xC9AD, 0xC9AD },
+{ 0xC9AE, 0xC9AE, 0xC9AE },
+{ 0xC9AF, 0xC9AF, 0xC9AF },
+{ 0xC9B0, 0xC9B0, 0xC9B0 },
+{ 0xC9B1, 0xC9B1, 0xC9B1 },
+{ 0xC9B2, 0xC9B2, 0xC9B2 },
+{ 0xC9B3, 0xC9B3, 0xC9B3 },
+{ 0xC9B4, 0xC9B4, 0xC9B4 },
+{ 0xC9B5, 0xC9B5, 0xC9B5 },
+{ 0xC9B6, 0xC9B6, 0xC9B6 },
+{ 0xC9B7, 0xC9B7, 0xC9B7 },
+{ 0xC9B8, 0xC9B8, 0xC9B8 },
+{ 0xC9B9, 0xC9B9, 0xC9B9 },
+{ 0xC9BA, 0xC9BA, 0xC9BA },
+{ 0xC9BB, 0xC9BB, 0xC9BB },
+{ 0xC9BC, 0xC9BC, 0xC9BC },
+{ 0xC9BD, 0xC9BD, 0xC9BD },
+{ 0xC9BE, 0xC9BE, 0xC9BE },
+{ 0xC9BF, 0xC9BF, 0xC9BF },
+{ 0xC9C0, 0xC9C0, 0xC9C0 },
+{ 0xC9C1, 0xC9C1, 0xC9C1 },
+{ 0xC9C2, 0xC9C2, 0xC9C2 },
+{ 0xC9C3, 0xC9C3, 0xC9C3 },
+{ 0xC9C4, 0xC9C4, 0xC9C4 },
+{ 0xC9C5, 0xC9C5, 0xC9C5 },
+{ 0xC9C6, 0xC9C6, 0xC9C6 },
+{ 0xC9C7, 0xC9C7, 0xC9C7 },
+{ 0xC9C8, 0xC9C8, 0xC9C8 },
+{ 0xC9C9, 0xC9C9, 0xC9C9 },
+{ 0xC9CA, 0xC9CA, 0xC9CA },
+{ 0xC9CB, 0xC9CB, 0xC9CB },
+{ 0xC9CC, 0xC9CC, 0xC9CC },
+{ 0xC9CD, 0xC9CD, 0xC9CD },
+{ 0xC9CE, 0xC9CE, 0xC9CE },
+{ 0xC9CF, 0xC9CF, 0xC9CF },
+{ 0xC9D0, 0xC9D0, 0xC9D0 },
+{ 0xC9D1, 0xC9D1, 0xC9D1 },
+{ 0xC9D2, 0xC9D2, 0xC9D2 },
+{ 0xC9D3, 0xC9D3, 0xC9D3 },
+{ 0xC9D4, 0xC9D4, 0xC9D4 },
+{ 0xC9D5, 0xC9D5, 0xC9D5 },
+{ 0xC9D6, 0xC9D6, 0xC9D6 },
+{ 0xC9D7, 0xC9D7, 0xC9D7 },
+{ 0xC9D8, 0xC9D8, 0xC9D8 },
+{ 0xC9D9, 0xC9D9, 0xC9D9 },
+{ 0xC9DA, 0xC9DA, 0xC9DA },
+{ 0xC9DB, 0xC9DB, 0xC9DB },
+{ 0xC9DC, 0xC9DC, 0xC9DC },
+{ 0xC9DD, 0xC9DD, 0xC9DD },
+{ 0xC9DE, 0xC9DE, 0xC9DE },
+{ 0xC9DF, 0xC9DF, 0xC9DF },
+{ 0xC9E0, 0xC9E0, 0xC9E0 },
+{ 0xC9E1, 0xC9E1, 0xC9E1 },
+{ 0xC9E2, 0xC9E2, 0xC9E2 },
+{ 0xC9E3, 0xC9E3, 0xC9E3 },
+{ 0xC9E4, 0xC9E4, 0xC9E4 },
+{ 0xC9E5, 0xC9E5, 0xC9E5 },
+{ 0xC9E6, 0xC9E6, 0xC9E6 },
+{ 0xC9E7, 0xC9E7, 0xC9E7 },
+{ 0xC9E8, 0xC9E8, 0xC9E8 },
+{ 0xC9E9, 0xC9E9, 0xC9E9 },
+{ 0xC9EA, 0xC9EA, 0xC9EA },
+{ 0xC9EB, 0xC9EB, 0xC9EB },
+{ 0xC9EC, 0xC9EC, 0xC9EC },
+{ 0xC9ED, 0xC9ED, 0xC9ED },
+{ 0xC9EE, 0xC9EE, 0xC9EE },
+{ 0xC9EF, 0xC9EF, 0xC9EF },
+{ 0xC9F0, 0xC9F0, 0xC9F0 },
+{ 0xC9F1, 0xC9F1, 0xC9F1 },
+{ 0xC9F2, 0xC9F2, 0xC9F2 },
+{ 0xC9F3, 0xC9F3, 0xC9F3 },
+{ 0xC9F4, 0xC9F4, 0xC9F4 },
+{ 0xC9F5, 0xC9F5, 0xC9F5 },
+{ 0xC9F6, 0xC9F6, 0xC9F6 },
+{ 0xC9F7, 0xC9F7, 0xC9F7 },
+{ 0xC9F8, 0xC9F8, 0xC9F8 },
+{ 0xC9F9, 0xC9F9, 0xC9F9 },
+{ 0xC9FA, 0xC9FA, 0xC9FA },
+{ 0xC9FB, 0xC9FB, 0xC9FB },
+{ 0xC9FC, 0xC9FC, 0xC9FC },
+{ 0xC9FD, 0xC9FD, 0xC9FD },
+{ 0xC9FE, 0xC9FE, 0xC9FE },
+{ 0xC9FF, 0xC9FF, 0xC9FF },
+{ 0xCA00, 0xCA00, 0xCA00 },
+{ 0xCA01, 0xCA01, 0xCA01 },
+{ 0xCA02, 0xCA02, 0xCA02 },
+{ 0xCA03, 0xCA03, 0xCA03 },
+{ 0xCA04, 0xCA04, 0xCA04 },
+{ 0xCA05, 0xCA05, 0xCA05 },
+{ 0xCA06, 0xCA06, 0xCA06 },
+{ 0xCA07, 0xCA07, 0xCA07 },
+{ 0xCA08, 0xCA08, 0xCA08 },
+{ 0xCA09, 0xCA09, 0xCA09 },
+{ 0xCA0A, 0xCA0A, 0xCA0A },
+{ 0xCA0B, 0xCA0B, 0xCA0B },
+{ 0xCA0C, 0xCA0C, 0xCA0C },
+{ 0xCA0D, 0xCA0D, 0xCA0D },
+{ 0xCA0E, 0xCA0E, 0xCA0E },
+{ 0xCA0F, 0xCA0F, 0xCA0F },
+{ 0xCA10, 0xCA10, 0xCA10 },
+{ 0xCA11, 0xCA11, 0xCA11 },
+{ 0xCA12, 0xCA12, 0xCA12 },
+{ 0xCA13, 0xCA13, 0xCA13 },
+{ 0xCA14, 0xCA14, 0xCA14 },
+{ 0xCA15, 0xCA15, 0xCA15 },
+{ 0xCA16, 0xCA16, 0xCA16 },
+{ 0xCA17, 0xCA17, 0xCA17 },
+{ 0xCA18, 0xCA18, 0xCA18 },
+{ 0xCA19, 0xCA19, 0xCA19 },
+{ 0xCA1A, 0xCA1A, 0xCA1A },
+{ 0xCA1B, 0xCA1B, 0xCA1B },
+{ 0xCA1C, 0xCA1C, 0xCA1C },
+{ 0xCA1D, 0xCA1D, 0xCA1D },
+{ 0xCA1E, 0xCA1E, 0xCA1E },
+{ 0xCA1F, 0xCA1F, 0xCA1F },
+{ 0xCA20, 0xCA20, 0xCA20 },
+{ 0xCA21, 0xCA21, 0xCA21 },
+{ 0xCA22, 0xCA22, 0xCA22 },
+{ 0xCA23, 0xCA23, 0xCA23 },
+{ 0xCA24, 0xCA24, 0xCA24 },
+{ 0xCA25, 0xCA25, 0xCA25 },
+{ 0xCA26, 0xCA26, 0xCA26 },
+{ 0xCA27, 0xCA27, 0xCA27 },
+{ 0xCA28, 0xCA28, 0xCA28 },
+{ 0xCA29, 0xCA29, 0xCA29 },
+{ 0xCA2A, 0xCA2A, 0xCA2A },
+{ 0xCA2B, 0xCA2B, 0xCA2B },
+{ 0xCA2C, 0xCA2C, 0xCA2C },
+{ 0xCA2D, 0xCA2D, 0xCA2D },
+{ 0xCA2E, 0xCA2E, 0xCA2E },
+{ 0xCA2F, 0xCA2F, 0xCA2F },
+{ 0xCA30, 0xCA30, 0xCA30 },
+{ 0xCA31, 0xCA31, 0xCA31 },
+{ 0xCA32, 0xCA32, 0xCA32 },
+{ 0xCA33, 0xCA33, 0xCA33 },
+{ 0xCA34, 0xCA34, 0xCA34 },
+{ 0xCA35, 0xCA35, 0xCA35 },
+{ 0xCA36, 0xCA36, 0xCA36 },
+{ 0xCA37, 0xCA37, 0xCA37 },
+{ 0xCA38, 0xCA38, 0xCA38 },
+{ 0xCA39, 0xCA39, 0xCA39 },
+{ 0xCA3A, 0xCA3A, 0xCA3A },
+{ 0xCA3B, 0xCA3B, 0xCA3B },
+{ 0xCA3C, 0xCA3C, 0xCA3C },
+{ 0xCA3D, 0xCA3D, 0xCA3D },
+{ 0xCA3E, 0xCA3E, 0xCA3E },
+{ 0xCA3F, 0xCA3F, 0xCA3F },
+{ 0xCA40, 0xCA40, 0xCA40 },
+{ 0xCA41, 0xCA41, 0xCA41 },
+{ 0xCA42, 0xCA42, 0xCA42 },
+{ 0xCA43, 0xCA43, 0xCA43 },
+{ 0xCA44, 0xCA44, 0xCA44 },
+{ 0xCA45, 0xCA45, 0xCA45 },
+{ 0xCA46, 0xCA46, 0xCA46 },
+{ 0xCA47, 0xCA47, 0xCA47 },
+{ 0xCA48, 0xCA48, 0xCA48 },
+{ 0xCA49, 0xCA49, 0xCA49 },
+{ 0xCA4A, 0xCA4A, 0xCA4A },
+{ 0xCA4B, 0xCA4B, 0xCA4B },
+{ 0xCA4C, 0xCA4C, 0xCA4C },
+{ 0xCA4D, 0xCA4D, 0xCA4D },
+{ 0xCA4E, 0xCA4E, 0xCA4E },
+{ 0xCA4F, 0xCA4F, 0xCA4F },
+{ 0xCA50, 0xCA50, 0xCA50 },
+{ 0xCA51, 0xCA51, 0xCA51 },
+{ 0xCA52, 0xCA52, 0xCA52 },
+{ 0xCA53, 0xCA53, 0xCA53 },
+{ 0xCA54, 0xCA54, 0xCA54 },
+{ 0xCA55, 0xCA55, 0xCA55 },
+{ 0xCA56, 0xCA56, 0xCA56 },
+{ 0xCA57, 0xCA57, 0xCA57 },
+{ 0xCA58, 0xCA58, 0xCA58 },
+{ 0xCA59, 0xCA59, 0xCA59 },
+{ 0xCA5A, 0xCA5A, 0xCA5A },
+{ 0xCA5B, 0xCA5B, 0xCA5B },
+{ 0xCA5C, 0xCA5C, 0xCA5C },
+{ 0xCA5D, 0xCA5D, 0xCA5D },
+{ 0xCA5E, 0xCA5E, 0xCA5E },
+{ 0xCA5F, 0xCA5F, 0xCA5F },
+{ 0xCA60, 0xCA60, 0xCA60 },
+{ 0xCA61, 0xCA61, 0xCA61 },
+{ 0xCA62, 0xCA62, 0xCA62 },
+{ 0xCA63, 0xCA63, 0xCA63 },
+{ 0xCA64, 0xCA64, 0xCA64 },
+{ 0xCA65, 0xCA65, 0xCA65 },
+{ 0xCA66, 0xCA66, 0xCA66 },
+{ 0xCA67, 0xCA67, 0xCA67 },
+{ 0xCA68, 0xCA68, 0xCA68 },
+{ 0xCA69, 0xCA69, 0xCA69 },
+{ 0xCA6A, 0xCA6A, 0xCA6A },
+{ 0xCA6B, 0xCA6B, 0xCA6B },
+{ 0xCA6C, 0xCA6C, 0xCA6C },
+{ 0xCA6D, 0xCA6D, 0xCA6D },
+{ 0xCA6E, 0xCA6E, 0xCA6E },
+{ 0xCA6F, 0xCA6F, 0xCA6F },
+{ 0xCA70, 0xCA70, 0xCA70 },
+{ 0xCA71, 0xCA71, 0xCA71 },
+{ 0xCA72, 0xCA72, 0xCA72 },
+{ 0xCA73, 0xCA73, 0xCA73 },
+{ 0xCA74, 0xCA74, 0xCA74 },
+{ 0xCA75, 0xCA75, 0xCA75 },
+{ 0xCA76, 0xCA76, 0xCA76 },
+{ 0xCA77, 0xCA77, 0xCA77 },
+{ 0xCA78, 0xCA78, 0xCA78 },
+{ 0xCA79, 0xCA79, 0xCA79 },
+{ 0xCA7A, 0xCA7A, 0xCA7A },
+{ 0xCA7B, 0xCA7B, 0xCA7B },
+{ 0xCA7C, 0xCA7C, 0xCA7C },
+{ 0xCA7D, 0xCA7D, 0xCA7D },
+{ 0xCA7E, 0xCA7E, 0xCA7E },
+{ 0xCA7F, 0xCA7F, 0xCA7F },
+{ 0xCA80, 0xCA80, 0xCA80 },
+{ 0xCA81, 0xCA81, 0xCA81 },
+{ 0xCA82, 0xCA82, 0xCA82 },
+{ 0xCA83, 0xCA83, 0xCA83 },
+{ 0xCA84, 0xCA84, 0xCA84 },
+{ 0xCA85, 0xCA85, 0xCA85 },
+{ 0xCA86, 0xCA86, 0xCA86 },
+{ 0xCA87, 0xCA87, 0xCA87 },
+{ 0xCA88, 0xCA88, 0xCA88 },
+{ 0xCA89, 0xCA89, 0xCA89 },
+{ 0xCA8A, 0xCA8A, 0xCA8A },
+{ 0xCA8B, 0xCA8B, 0xCA8B },
+{ 0xCA8C, 0xCA8C, 0xCA8C },
+{ 0xCA8D, 0xCA8D, 0xCA8D },
+{ 0xCA8E, 0xCA8E, 0xCA8E },
+{ 0xCA8F, 0xCA8F, 0xCA8F },
+{ 0xCA90, 0xCA90, 0xCA90 },
+{ 0xCA91, 0xCA91, 0xCA91 },
+{ 0xCA92, 0xCA92, 0xCA92 },
+{ 0xCA93, 0xCA93, 0xCA93 },
+{ 0xCA94, 0xCA94, 0xCA94 },
+{ 0xCA95, 0xCA95, 0xCA95 },
+{ 0xCA96, 0xCA96, 0xCA96 },
+{ 0xCA97, 0xCA97, 0xCA97 },
+{ 0xCA98, 0xCA98, 0xCA98 },
+{ 0xCA99, 0xCA99, 0xCA99 },
+{ 0xCA9A, 0xCA9A, 0xCA9A },
+{ 0xCA9B, 0xCA9B, 0xCA9B },
+{ 0xCA9C, 0xCA9C, 0xCA9C },
+{ 0xCA9D, 0xCA9D, 0xCA9D },
+{ 0xCA9E, 0xCA9E, 0xCA9E },
+{ 0xCA9F, 0xCA9F, 0xCA9F },
+{ 0xCAA0, 0xCAA0, 0xCAA0 },
+{ 0xCAA1, 0xCAA1, 0xCAA1 },
+{ 0xCAA2, 0xCAA2, 0xCAA2 },
+{ 0xCAA3, 0xCAA3, 0xCAA3 },
+{ 0xCAA4, 0xCAA4, 0xCAA4 },
+{ 0xCAA5, 0xCAA5, 0xCAA5 },
+{ 0xCAA6, 0xCAA6, 0xCAA6 },
+{ 0xCAA7, 0xCAA7, 0xCAA7 },
+{ 0xCAA8, 0xCAA8, 0xCAA8 },
+{ 0xCAA9, 0xCAA9, 0xCAA9 },
+{ 0xCAAA, 0xCAAA, 0xCAAA },
+{ 0xCAAB, 0xCAAB, 0xCAAB },
+{ 0xCAAC, 0xCAAC, 0xCAAC },
+{ 0xCAAD, 0xCAAD, 0xCAAD },
+{ 0xCAAE, 0xCAAE, 0xCAAE },
+{ 0xCAAF, 0xCAAF, 0xCAAF },
+{ 0xCAB0, 0xCAB0, 0xCAB0 },
+{ 0xCAB1, 0xCAB1, 0xCAB1 },
+{ 0xCAB2, 0xCAB2, 0xCAB2 },
+{ 0xCAB3, 0xCAB3, 0xCAB3 },
+{ 0xCAB4, 0xCAB4, 0xCAB4 },
+{ 0xCAB5, 0xCAB5, 0xCAB5 },
+{ 0xCAB6, 0xCAB6, 0xCAB6 },
+{ 0xCAB7, 0xCAB7, 0xCAB7 },
+{ 0xCAB8, 0xCAB8, 0xCAB8 },
+{ 0xCAB9, 0xCAB9, 0xCAB9 },
+{ 0xCABA, 0xCABA, 0xCABA },
+{ 0xCABB, 0xCABB, 0xCABB },
+{ 0xCABC, 0xCABC, 0xCABC },
+{ 0xCABD, 0xCABD, 0xCABD },
+{ 0xCABE, 0xCABE, 0xCABE },
+{ 0xCABF, 0xCABF, 0xCABF },
+{ 0xCAC0, 0xCAC0, 0xCAC0 },
+{ 0xCAC1, 0xCAC1, 0xCAC1 },
+{ 0xCAC2, 0xCAC2, 0xCAC2 },
+{ 0xCAC3, 0xCAC3, 0xCAC3 },
+{ 0xCAC4, 0xCAC4, 0xCAC4 },
+{ 0xCAC5, 0xCAC5, 0xCAC5 },
+{ 0xCAC6, 0xCAC6, 0xCAC6 },
+{ 0xCAC7, 0xCAC7, 0xCAC7 },
+{ 0xCAC8, 0xCAC8, 0xCAC8 },
+{ 0xCAC9, 0xCAC9, 0xCAC9 },
+{ 0xCACA, 0xCACA, 0xCACA },
+{ 0xCACB, 0xCACB, 0xCACB },
+{ 0xCACC, 0xCACC, 0xCACC },
+{ 0xCACD, 0xCACD, 0xCACD },
+{ 0xCACE, 0xCACE, 0xCACE },
+{ 0xCACF, 0xCACF, 0xCACF },
+{ 0xCAD0, 0xCAD0, 0xCAD0 },
+{ 0xCAD1, 0xCAD1, 0xCAD1 },
+{ 0xCAD2, 0xCAD2, 0xCAD2 },
+{ 0xCAD3, 0xCAD3, 0xCAD3 },
+{ 0xCAD4, 0xCAD4, 0xCAD4 },
+{ 0xCAD5, 0xCAD5, 0xCAD5 },
+{ 0xCAD6, 0xCAD6, 0xCAD6 },
+{ 0xCAD7, 0xCAD7, 0xCAD7 },
+{ 0xCAD8, 0xCAD8, 0xCAD8 },
+{ 0xCAD9, 0xCAD9, 0xCAD9 },
+{ 0xCADA, 0xCADA, 0xCADA },
+{ 0xCADB, 0xCADB, 0xCADB },
+{ 0xCADC, 0xCADC, 0xCADC },
+{ 0xCADD, 0xCADD, 0xCADD },
+{ 0xCADE, 0xCADE, 0xCADE },
+{ 0xCADF, 0xCADF, 0xCADF },
+{ 0xCAE0, 0xCAE0, 0xCAE0 },
+{ 0xCAE1, 0xCAE1, 0xCAE1 },
+{ 0xCAE2, 0xCAE2, 0xCAE2 },
+{ 0xCAE3, 0xCAE3, 0xCAE3 },
+{ 0xCAE4, 0xCAE4, 0xCAE4 },
+{ 0xCAE5, 0xCAE5, 0xCAE5 },
+{ 0xCAE6, 0xCAE6, 0xCAE6 },
+{ 0xCAE7, 0xCAE7, 0xCAE7 },
+{ 0xCAE8, 0xCAE8, 0xCAE8 },
+{ 0xCAE9, 0xCAE9, 0xCAE9 },
+{ 0xCAEA, 0xCAEA, 0xCAEA },
+{ 0xCAEB, 0xCAEB, 0xCAEB },
+{ 0xCAEC, 0xCAEC, 0xCAEC },
+{ 0xCAED, 0xCAED, 0xCAED },
+{ 0xCAEE, 0xCAEE, 0xCAEE },
+{ 0xCAEF, 0xCAEF, 0xCAEF },
+{ 0xCAF0, 0xCAF0, 0xCAF0 },
+{ 0xCAF1, 0xCAF1, 0xCAF1 },
+{ 0xCAF2, 0xCAF2, 0xCAF2 },
+{ 0xCAF3, 0xCAF3, 0xCAF3 },
+{ 0xCAF4, 0xCAF4, 0xCAF4 },
+{ 0xCAF5, 0xCAF5, 0xCAF5 },
+{ 0xCAF6, 0xCAF6, 0xCAF6 },
+{ 0xCAF7, 0xCAF7, 0xCAF7 },
+{ 0xCAF8, 0xCAF8, 0xCAF8 },
+{ 0xCAF9, 0xCAF9, 0xCAF9 },
+{ 0xCAFA, 0xCAFA, 0xCAFA },
+{ 0xCAFB, 0xCAFB, 0xCAFB },
+{ 0xCAFC, 0xCAFC, 0xCAFC },
+{ 0xCAFD, 0xCAFD, 0xCAFD },
+{ 0xCAFE, 0xCAFE, 0xCAFE },
+{ 0xCAFF, 0xCAFF, 0xCAFF },
+{ 0xCB00, 0xCB00, 0xCB00 },
+{ 0xCB01, 0xCB01, 0xCB01 },
+{ 0xCB02, 0xCB02, 0xCB02 },
+{ 0xCB03, 0xCB03, 0xCB03 },
+{ 0xCB04, 0xCB04, 0xCB04 },
+{ 0xCB05, 0xCB05, 0xCB05 },
+{ 0xCB06, 0xCB06, 0xCB06 },
+{ 0xCB07, 0xCB07, 0xCB07 },
+{ 0xCB08, 0xCB08, 0xCB08 },
+{ 0xCB09, 0xCB09, 0xCB09 },
+{ 0xCB0A, 0xCB0A, 0xCB0A },
+{ 0xCB0B, 0xCB0B, 0xCB0B },
+{ 0xCB0C, 0xCB0C, 0xCB0C },
+{ 0xCB0D, 0xCB0D, 0xCB0D },
+{ 0xCB0E, 0xCB0E, 0xCB0E },
+{ 0xCB0F, 0xCB0F, 0xCB0F },
+{ 0xCB10, 0xCB10, 0xCB10 },
+{ 0xCB11, 0xCB11, 0xCB11 },
+{ 0xCB12, 0xCB12, 0xCB12 },
+{ 0xCB13, 0xCB13, 0xCB13 },
+{ 0xCB14, 0xCB14, 0xCB14 },
+{ 0xCB15, 0xCB15, 0xCB15 },
+{ 0xCB16, 0xCB16, 0xCB16 },
+{ 0xCB17, 0xCB17, 0xCB17 },
+{ 0xCB18, 0xCB18, 0xCB18 },
+{ 0xCB19, 0xCB19, 0xCB19 },
+{ 0xCB1A, 0xCB1A, 0xCB1A },
+{ 0xCB1B, 0xCB1B, 0xCB1B },
+{ 0xCB1C, 0xCB1C, 0xCB1C },
+{ 0xCB1D, 0xCB1D, 0xCB1D },
+{ 0xCB1E, 0xCB1E, 0xCB1E },
+{ 0xCB1F, 0xCB1F, 0xCB1F },
+{ 0xCB20, 0xCB20, 0xCB20 },
+{ 0xCB21, 0xCB21, 0xCB21 },
+{ 0xCB22, 0xCB22, 0xCB22 },
+{ 0xCB23, 0xCB23, 0xCB23 },
+{ 0xCB24, 0xCB24, 0xCB24 },
+{ 0xCB25, 0xCB25, 0xCB25 },
+{ 0xCB26, 0xCB26, 0xCB26 },
+{ 0xCB27, 0xCB27, 0xCB27 },
+{ 0xCB28, 0xCB28, 0xCB28 },
+{ 0xCB29, 0xCB29, 0xCB29 },
+{ 0xCB2A, 0xCB2A, 0xCB2A },
+{ 0xCB2B, 0xCB2B, 0xCB2B },
+{ 0xCB2C, 0xCB2C, 0xCB2C },
+{ 0xCB2D, 0xCB2D, 0xCB2D },
+{ 0xCB2E, 0xCB2E, 0xCB2E },
+{ 0xCB2F, 0xCB2F, 0xCB2F },
+{ 0xCB30, 0xCB30, 0xCB30 },
+{ 0xCB31, 0xCB31, 0xCB31 },
+{ 0xCB32, 0xCB32, 0xCB32 },
+{ 0xCB33, 0xCB33, 0xCB33 },
+{ 0xCB34, 0xCB34, 0xCB34 },
+{ 0xCB35, 0xCB35, 0xCB35 },
+{ 0xCB36, 0xCB36, 0xCB36 },
+{ 0xCB37, 0xCB37, 0xCB37 },
+{ 0xCB38, 0xCB38, 0xCB38 },
+{ 0xCB39, 0xCB39, 0xCB39 },
+{ 0xCB3A, 0xCB3A, 0xCB3A },
+{ 0xCB3B, 0xCB3B, 0xCB3B },
+{ 0xCB3C, 0xCB3C, 0xCB3C },
+{ 0xCB3D, 0xCB3D, 0xCB3D },
+{ 0xCB3E, 0xCB3E, 0xCB3E },
+{ 0xCB3F, 0xCB3F, 0xCB3F },
+{ 0xCB40, 0xCB40, 0xCB40 },
+{ 0xCB41, 0xCB41, 0xCB41 },
+{ 0xCB42, 0xCB42, 0xCB42 },
+{ 0xCB43, 0xCB43, 0xCB43 },
+{ 0xCB44, 0xCB44, 0xCB44 },
+{ 0xCB45, 0xCB45, 0xCB45 },
+{ 0xCB46, 0xCB46, 0xCB46 },
+{ 0xCB47, 0xCB47, 0xCB47 },
+{ 0xCB48, 0xCB48, 0xCB48 },
+{ 0xCB49, 0xCB49, 0xCB49 },
+{ 0xCB4A, 0xCB4A, 0xCB4A },
+{ 0xCB4B, 0xCB4B, 0xCB4B },
+{ 0xCB4C, 0xCB4C, 0xCB4C },
+{ 0xCB4D, 0xCB4D, 0xCB4D },
+{ 0xCB4E, 0xCB4E, 0xCB4E },
+{ 0xCB4F, 0xCB4F, 0xCB4F },
+{ 0xCB50, 0xCB50, 0xCB50 },
+{ 0xCB51, 0xCB51, 0xCB51 },
+{ 0xCB52, 0xCB52, 0xCB52 },
+{ 0xCB53, 0xCB53, 0xCB53 },
+{ 0xCB54, 0xCB54, 0xCB54 },
+{ 0xCB55, 0xCB55, 0xCB55 },
+{ 0xCB56, 0xCB56, 0xCB56 },
+{ 0xCB57, 0xCB57, 0xCB57 },
+{ 0xCB58, 0xCB58, 0xCB58 },
+{ 0xCB59, 0xCB59, 0xCB59 },
+{ 0xCB5A, 0xCB5A, 0xCB5A },
+{ 0xCB5B, 0xCB5B, 0xCB5B },
+{ 0xCB5C, 0xCB5C, 0xCB5C },
+{ 0xCB5D, 0xCB5D, 0xCB5D },
+{ 0xCB5E, 0xCB5E, 0xCB5E },
+{ 0xCB5F, 0xCB5F, 0xCB5F },
+{ 0xCB60, 0xCB60, 0xCB60 },
+{ 0xCB61, 0xCB61, 0xCB61 },
+{ 0xCB62, 0xCB62, 0xCB62 },
+{ 0xCB63, 0xCB63, 0xCB63 },
+{ 0xCB64, 0xCB64, 0xCB64 },
+{ 0xCB65, 0xCB65, 0xCB65 },
+{ 0xCB66, 0xCB66, 0xCB66 },
+{ 0xCB67, 0xCB67, 0xCB67 },
+{ 0xCB68, 0xCB68, 0xCB68 },
+{ 0xCB69, 0xCB69, 0xCB69 },
+{ 0xCB6A, 0xCB6A, 0xCB6A },
+{ 0xCB6B, 0xCB6B, 0xCB6B },
+{ 0xCB6C, 0xCB6C, 0xCB6C },
+{ 0xCB6D, 0xCB6D, 0xCB6D },
+{ 0xCB6E, 0xCB6E, 0xCB6E },
+{ 0xCB6F, 0xCB6F, 0xCB6F },
+{ 0xCB70, 0xCB70, 0xCB70 },
+{ 0xCB71, 0xCB71, 0xCB71 },
+{ 0xCB72, 0xCB72, 0xCB72 },
+{ 0xCB73, 0xCB73, 0xCB73 },
+{ 0xCB74, 0xCB74, 0xCB74 },
+{ 0xCB75, 0xCB75, 0xCB75 },
+{ 0xCB76, 0xCB76, 0xCB76 },
+{ 0xCB77, 0xCB77, 0xCB77 },
+{ 0xCB78, 0xCB78, 0xCB78 },
+{ 0xCB79, 0xCB79, 0xCB79 },
+{ 0xCB7A, 0xCB7A, 0xCB7A },
+{ 0xCB7B, 0xCB7B, 0xCB7B },
+{ 0xCB7C, 0xCB7C, 0xCB7C },
+{ 0xCB7D, 0xCB7D, 0xCB7D },
+{ 0xCB7E, 0xCB7E, 0xCB7E },
+{ 0xCB7F, 0xCB7F, 0xCB7F },
+{ 0xCB80, 0xCB80, 0xCB80 },
+{ 0xCB81, 0xCB81, 0xCB81 },
+{ 0xCB82, 0xCB82, 0xCB82 },
+{ 0xCB83, 0xCB83, 0xCB83 },
+{ 0xCB84, 0xCB84, 0xCB84 },
+{ 0xCB85, 0xCB85, 0xCB85 },
+{ 0xCB86, 0xCB86, 0xCB86 },
+{ 0xCB87, 0xCB87, 0xCB87 },
+{ 0xCB88, 0xCB88, 0xCB88 },
+{ 0xCB89, 0xCB89, 0xCB89 },
+{ 0xCB8A, 0xCB8A, 0xCB8A },
+{ 0xCB8B, 0xCB8B, 0xCB8B },
+{ 0xCB8C, 0xCB8C, 0xCB8C },
+{ 0xCB8D, 0xCB8D, 0xCB8D },
+{ 0xCB8E, 0xCB8E, 0xCB8E },
+{ 0xCB8F, 0xCB8F, 0xCB8F },
+{ 0xCB90, 0xCB90, 0xCB90 },
+{ 0xCB91, 0xCB91, 0xCB91 },
+{ 0xCB92, 0xCB92, 0xCB92 },
+{ 0xCB93, 0xCB93, 0xCB93 },
+{ 0xCB94, 0xCB94, 0xCB94 },
+{ 0xCB95, 0xCB95, 0xCB95 },
+{ 0xCB96, 0xCB96, 0xCB96 },
+{ 0xCB97, 0xCB97, 0xCB97 },
+{ 0xCB98, 0xCB98, 0xCB98 },
+{ 0xCB99, 0xCB99, 0xCB99 },
+{ 0xCB9A, 0xCB9A, 0xCB9A },
+{ 0xCB9B, 0xCB9B, 0xCB9B },
+{ 0xCB9C, 0xCB9C, 0xCB9C },
+{ 0xCB9D, 0xCB9D, 0xCB9D },
+{ 0xCB9E, 0xCB9E, 0xCB9E },
+{ 0xCB9F, 0xCB9F, 0xCB9F },
+{ 0xCBA0, 0xCBA0, 0xCBA0 },
+{ 0xCBA1, 0xCBA1, 0xCBA1 },
+{ 0xCBA2, 0xCBA2, 0xCBA2 },
+{ 0xCBA3, 0xCBA3, 0xCBA3 },
+{ 0xCBA4, 0xCBA4, 0xCBA4 },
+{ 0xCBA5, 0xCBA5, 0xCBA5 },
+{ 0xCBA6, 0xCBA6, 0xCBA6 },
+{ 0xCBA7, 0xCBA7, 0xCBA7 },
+{ 0xCBA8, 0xCBA8, 0xCBA8 },
+{ 0xCBA9, 0xCBA9, 0xCBA9 },
+{ 0xCBAA, 0xCBAA, 0xCBAA },
+{ 0xCBAB, 0xCBAB, 0xCBAB },
+{ 0xCBAC, 0xCBAC, 0xCBAC },
+{ 0xCBAD, 0xCBAD, 0xCBAD },
+{ 0xCBAE, 0xCBAE, 0xCBAE },
+{ 0xCBAF, 0xCBAF, 0xCBAF },
+{ 0xCBB0, 0xCBB0, 0xCBB0 },
+{ 0xCBB1, 0xCBB1, 0xCBB1 },
+{ 0xCBB2, 0xCBB2, 0xCBB2 },
+{ 0xCBB3, 0xCBB3, 0xCBB3 },
+{ 0xCBB4, 0xCBB4, 0xCBB4 },
+{ 0xCBB5, 0xCBB5, 0xCBB5 },
+{ 0xCBB6, 0xCBB6, 0xCBB6 },
+{ 0xCBB7, 0xCBB7, 0xCBB7 },
+{ 0xCBB8, 0xCBB8, 0xCBB8 },
+{ 0xCBB9, 0xCBB9, 0xCBB9 },
+{ 0xCBBA, 0xCBBA, 0xCBBA },
+{ 0xCBBB, 0xCBBB, 0xCBBB },
+{ 0xCBBC, 0xCBBC, 0xCBBC },
+{ 0xCBBD, 0xCBBD, 0xCBBD },
+{ 0xCBBE, 0xCBBE, 0xCBBE },
+{ 0xCBBF, 0xCBBF, 0xCBBF },
+{ 0xCBC0, 0xCBC0, 0xCBC0 },
+{ 0xCBC1, 0xCBC1, 0xCBC1 },
+{ 0xCBC2, 0xCBC2, 0xCBC2 },
+{ 0xCBC3, 0xCBC3, 0xCBC3 },
+{ 0xCBC4, 0xCBC4, 0xCBC4 },
+{ 0xCBC5, 0xCBC5, 0xCBC5 },
+{ 0xCBC6, 0xCBC6, 0xCBC6 },
+{ 0xCBC7, 0xCBC7, 0xCBC7 },
+{ 0xCBC8, 0xCBC8, 0xCBC8 },
+{ 0xCBC9, 0xCBC9, 0xCBC9 },
+{ 0xCBCA, 0xCBCA, 0xCBCA },
+{ 0xCBCB, 0xCBCB, 0xCBCB },
+{ 0xCBCC, 0xCBCC, 0xCBCC },
+{ 0xCBCD, 0xCBCD, 0xCBCD },
+{ 0xCBCE, 0xCBCE, 0xCBCE },
+{ 0xCBCF, 0xCBCF, 0xCBCF },
+{ 0xCBD0, 0xCBD0, 0xCBD0 },
+{ 0xCBD1, 0xCBD1, 0xCBD1 },
+{ 0xCBD2, 0xCBD2, 0xCBD2 },
+{ 0xCBD3, 0xCBD3, 0xCBD3 },
+{ 0xCBD4, 0xCBD4, 0xCBD4 },
+{ 0xCBD5, 0xCBD5, 0xCBD5 },
+{ 0xCBD6, 0xCBD6, 0xCBD6 },
+{ 0xCBD7, 0xCBD7, 0xCBD7 },
+{ 0xCBD8, 0xCBD8, 0xCBD8 },
+{ 0xCBD9, 0xCBD9, 0xCBD9 },
+{ 0xCBDA, 0xCBDA, 0xCBDA },
+{ 0xCBDB, 0xCBDB, 0xCBDB },
+{ 0xCBDC, 0xCBDC, 0xCBDC },
+{ 0xCBDD, 0xCBDD, 0xCBDD },
+{ 0xCBDE, 0xCBDE, 0xCBDE },
+{ 0xCBDF, 0xCBDF, 0xCBDF },
+{ 0xCBE0, 0xCBE0, 0xCBE0 },
+{ 0xCBE1, 0xCBE1, 0xCBE1 },
+{ 0xCBE2, 0xCBE2, 0xCBE2 },
+{ 0xCBE3, 0xCBE3, 0xCBE3 },
+{ 0xCBE4, 0xCBE4, 0xCBE4 },
+{ 0xCBE5, 0xCBE5, 0xCBE5 },
+{ 0xCBE6, 0xCBE6, 0xCBE6 },
+{ 0xCBE7, 0xCBE7, 0xCBE7 },
+{ 0xCBE8, 0xCBE8, 0xCBE8 },
+{ 0xCBE9, 0xCBE9, 0xCBE9 },
+{ 0xCBEA, 0xCBEA, 0xCBEA },
+{ 0xCBEB, 0xCBEB, 0xCBEB },
+{ 0xCBEC, 0xCBEC, 0xCBEC },
+{ 0xCBED, 0xCBED, 0xCBED },
+{ 0xCBEE, 0xCBEE, 0xCBEE },
+{ 0xCBEF, 0xCBEF, 0xCBEF },
+{ 0xCBF0, 0xCBF0, 0xCBF0 },
+{ 0xCBF1, 0xCBF1, 0xCBF1 },
+{ 0xCBF2, 0xCBF2, 0xCBF2 },
+{ 0xCBF3, 0xCBF3, 0xCBF3 },
+{ 0xCBF4, 0xCBF4, 0xCBF4 },
+{ 0xCBF5, 0xCBF5, 0xCBF5 },
+{ 0xCBF6, 0xCBF6, 0xCBF6 },
+{ 0xCBF7, 0xCBF7, 0xCBF7 },
+{ 0xCBF8, 0xCBF8, 0xCBF8 },
+{ 0xCBF9, 0xCBF9, 0xCBF9 },
+{ 0xCBFA, 0xCBFA, 0xCBFA },
+{ 0xCBFB, 0xCBFB, 0xCBFB },
+{ 0xCBFC, 0xCBFC, 0xCBFC },
+{ 0xCBFD, 0xCBFD, 0xCBFD },
+{ 0xCBFE, 0xCBFE, 0xCBFE },
+{ 0xCBFF, 0xCBFF, 0xCBFF },
+{ 0xCC00, 0xCC00, 0xCC00 },
+{ 0xCC01, 0xCC01, 0xCC01 },
+{ 0xCC02, 0xCC02, 0xCC02 },
+{ 0xCC03, 0xCC03, 0xCC03 },
+{ 0xCC04, 0xCC04, 0xCC04 },
+{ 0xCC05, 0xCC05, 0xCC05 },
+{ 0xCC06, 0xCC06, 0xCC06 },
+{ 0xCC07, 0xCC07, 0xCC07 },
+{ 0xCC08, 0xCC08, 0xCC08 },
+{ 0xCC09, 0xCC09, 0xCC09 },
+{ 0xCC0A, 0xCC0A, 0xCC0A },
+{ 0xCC0B, 0xCC0B, 0xCC0B },
+{ 0xCC0C, 0xCC0C, 0xCC0C },
+{ 0xCC0D, 0xCC0D, 0xCC0D },
+{ 0xCC0E, 0xCC0E, 0xCC0E },
+{ 0xCC0F, 0xCC0F, 0xCC0F },
+{ 0xCC10, 0xCC10, 0xCC10 },
+{ 0xCC11, 0xCC11, 0xCC11 },
+{ 0xCC12, 0xCC12, 0xCC12 },
+{ 0xCC13, 0xCC13, 0xCC13 },
+{ 0xCC14, 0xCC14, 0xCC14 },
+{ 0xCC15, 0xCC15, 0xCC15 },
+{ 0xCC16, 0xCC16, 0xCC16 },
+{ 0xCC17, 0xCC17, 0xCC17 },
+{ 0xCC18, 0xCC18, 0xCC18 },
+{ 0xCC19, 0xCC19, 0xCC19 },
+{ 0xCC1A, 0xCC1A, 0xCC1A },
+{ 0xCC1B, 0xCC1B, 0xCC1B },
+{ 0xCC1C, 0xCC1C, 0xCC1C },
+{ 0xCC1D, 0xCC1D, 0xCC1D },
+{ 0xCC1E, 0xCC1E, 0xCC1E },
+{ 0xCC1F, 0xCC1F, 0xCC1F },
+{ 0xCC20, 0xCC20, 0xCC20 },
+{ 0xCC21, 0xCC21, 0xCC21 },
+{ 0xCC22, 0xCC22, 0xCC22 },
+{ 0xCC23, 0xCC23, 0xCC23 },
+{ 0xCC24, 0xCC24, 0xCC24 },
+{ 0xCC25, 0xCC25, 0xCC25 },
+{ 0xCC26, 0xCC26, 0xCC26 },
+{ 0xCC27, 0xCC27, 0xCC27 },
+{ 0xCC28, 0xCC28, 0xCC28 },
+{ 0xCC29, 0xCC29, 0xCC29 },
+{ 0xCC2A, 0xCC2A, 0xCC2A },
+{ 0xCC2B, 0xCC2B, 0xCC2B },
+{ 0xCC2C, 0xCC2C, 0xCC2C },
+{ 0xCC2D, 0xCC2D, 0xCC2D },
+{ 0xCC2E, 0xCC2E, 0xCC2E },
+{ 0xCC2F, 0xCC2F, 0xCC2F },
+{ 0xCC30, 0xCC30, 0xCC30 },
+{ 0xCC31, 0xCC31, 0xCC31 },
+{ 0xCC32, 0xCC32, 0xCC32 },
+{ 0xCC33, 0xCC33, 0xCC33 },
+{ 0xCC34, 0xCC34, 0xCC34 },
+{ 0xCC35, 0xCC35, 0xCC35 },
+{ 0xCC36, 0xCC36, 0xCC36 },
+{ 0xCC37, 0xCC37, 0xCC37 },
+{ 0xCC38, 0xCC38, 0xCC38 },
+{ 0xCC39, 0xCC39, 0xCC39 },
+{ 0xCC3A, 0xCC3A, 0xCC3A },
+{ 0xCC3B, 0xCC3B, 0xCC3B },
+{ 0xCC3C, 0xCC3C, 0xCC3C },
+{ 0xCC3D, 0xCC3D, 0xCC3D },
+{ 0xCC3E, 0xCC3E, 0xCC3E },
+{ 0xCC3F, 0xCC3F, 0xCC3F },
+{ 0xCC40, 0xCC40, 0xCC40 },
+{ 0xCC41, 0xCC41, 0xCC41 },
+{ 0xCC42, 0xCC42, 0xCC42 },
+{ 0xCC43, 0xCC43, 0xCC43 },
+{ 0xCC44, 0xCC44, 0xCC44 },
+{ 0xCC45, 0xCC45, 0xCC45 },
+{ 0xCC46, 0xCC46, 0xCC46 },
+{ 0xCC47, 0xCC47, 0xCC47 },
+{ 0xCC48, 0xCC48, 0xCC48 },
+{ 0xCC49, 0xCC49, 0xCC49 },
+{ 0xCC4A, 0xCC4A, 0xCC4A },
+{ 0xCC4B, 0xCC4B, 0xCC4B },
+{ 0xCC4C, 0xCC4C, 0xCC4C },
+{ 0xCC4D, 0xCC4D, 0xCC4D },
+{ 0xCC4E, 0xCC4E, 0xCC4E },
+{ 0xCC4F, 0xCC4F, 0xCC4F },
+{ 0xCC50, 0xCC50, 0xCC50 },
+{ 0xCC51, 0xCC51, 0xCC51 },
+{ 0xCC52, 0xCC52, 0xCC52 },
+{ 0xCC53, 0xCC53, 0xCC53 },
+{ 0xCC54, 0xCC54, 0xCC54 },
+{ 0xCC55, 0xCC55, 0xCC55 },
+{ 0xCC56, 0xCC56, 0xCC56 },
+{ 0xCC57, 0xCC57, 0xCC57 },
+{ 0xCC58, 0xCC58, 0xCC58 },
+{ 0xCC59, 0xCC59, 0xCC59 },
+{ 0xCC5A, 0xCC5A, 0xCC5A },
+{ 0xCC5B, 0xCC5B, 0xCC5B },
+{ 0xCC5C, 0xCC5C, 0xCC5C },
+{ 0xCC5D, 0xCC5D, 0xCC5D },
+{ 0xCC5E, 0xCC5E, 0xCC5E },
+{ 0xCC5F, 0xCC5F, 0xCC5F },
+{ 0xCC60, 0xCC60, 0xCC60 },
+{ 0xCC61, 0xCC61, 0xCC61 },
+{ 0xCC62, 0xCC62, 0xCC62 },
+{ 0xCC63, 0xCC63, 0xCC63 },
+{ 0xCC64, 0xCC64, 0xCC64 },
+{ 0xCC65, 0xCC65, 0xCC65 },
+{ 0xCC66, 0xCC66, 0xCC66 },
+{ 0xCC67, 0xCC67, 0xCC67 },
+{ 0xCC68, 0xCC68, 0xCC68 },
+{ 0xCC69, 0xCC69, 0xCC69 },
+{ 0xCC6A, 0xCC6A, 0xCC6A },
+{ 0xCC6B, 0xCC6B, 0xCC6B },
+{ 0xCC6C, 0xCC6C, 0xCC6C },
+{ 0xCC6D, 0xCC6D, 0xCC6D },
+{ 0xCC6E, 0xCC6E, 0xCC6E },
+{ 0xCC6F, 0xCC6F, 0xCC6F },
+{ 0xCC70, 0xCC70, 0xCC70 },
+{ 0xCC71, 0xCC71, 0xCC71 },
+{ 0xCC72, 0xCC72, 0xCC72 },
+{ 0xCC73, 0xCC73, 0xCC73 },
+{ 0xCC74, 0xCC74, 0xCC74 },
+{ 0xCC75, 0xCC75, 0xCC75 },
+{ 0xCC76, 0xCC76, 0xCC76 },
+{ 0xCC77, 0xCC77, 0xCC77 },
+{ 0xCC78, 0xCC78, 0xCC78 },
+{ 0xCC79, 0xCC79, 0xCC79 },
+{ 0xCC7A, 0xCC7A, 0xCC7A },
+{ 0xCC7B, 0xCC7B, 0xCC7B },
+{ 0xCC7C, 0xCC7C, 0xCC7C },
+{ 0xCC7D, 0xCC7D, 0xCC7D },
+{ 0xCC7E, 0xCC7E, 0xCC7E },
+{ 0xCC7F, 0xCC7F, 0xCC7F },
+{ 0xCC80, 0xCC80, 0xCC80 },
+{ 0xCC81, 0xCC81, 0xCC81 },
+{ 0xCC82, 0xCC82, 0xCC82 },
+{ 0xCC83, 0xCC83, 0xCC83 },
+{ 0xCC84, 0xCC84, 0xCC84 },
+{ 0xCC85, 0xCC85, 0xCC85 },
+{ 0xCC86, 0xCC86, 0xCC86 },
+{ 0xCC87, 0xCC87, 0xCC87 },
+{ 0xCC88, 0xCC88, 0xCC88 },
+{ 0xCC89, 0xCC89, 0xCC89 },
+{ 0xCC8A, 0xCC8A, 0xCC8A },
+{ 0xCC8B, 0xCC8B, 0xCC8B },
+{ 0xCC8C, 0xCC8C, 0xCC8C },
+{ 0xCC8D, 0xCC8D, 0xCC8D },
+{ 0xCC8E, 0xCC8E, 0xCC8E },
+{ 0xCC8F, 0xCC8F, 0xCC8F },
+{ 0xCC90, 0xCC90, 0xCC90 },
+{ 0xCC91, 0xCC91, 0xCC91 },
+{ 0xCC92, 0xCC92, 0xCC92 },
+{ 0xCC93, 0xCC93, 0xCC93 },
+{ 0xCC94, 0xCC94, 0xCC94 },
+{ 0xCC95, 0xCC95, 0xCC95 },
+{ 0xCC96, 0xCC96, 0xCC96 },
+{ 0xCC97, 0xCC97, 0xCC97 },
+{ 0xCC98, 0xCC98, 0xCC98 },
+{ 0xCC99, 0xCC99, 0xCC99 },
+{ 0xCC9A, 0xCC9A, 0xCC9A },
+{ 0xCC9B, 0xCC9B, 0xCC9B },
+{ 0xCC9C, 0xCC9C, 0xCC9C },
+{ 0xCC9D, 0xCC9D, 0xCC9D },
+{ 0xCC9E, 0xCC9E, 0xCC9E },
+{ 0xCC9F, 0xCC9F, 0xCC9F },
+{ 0xCCA0, 0xCCA0, 0xCCA0 },
+{ 0xCCA1, 0xCCA1, 0xCCA1 },
+{ 0xCCA2, 0xCCA2, 0xCCA2 },
+{ 0xCCA3, 0xCCA3, 0xCCA3 },
+{ 0xCCA4, 0xCCA4, 0xCCA4 },
+{ 0xCCA5, 0xCCA5, 0xCCA5 },
+{ 0xCCA6, 0xCCA6, 0xCCA6 },
+{ 0xCCA7, 0xCCA7, 0xCCA7 },
+{ 0xCCA8, 0xCCA8, 0xCCA8 },
+{ 0xCCA9, 0xCCA9, 0xCCA9 },
+{ 0xCCAA, 0xCCAA, 0xCCAA },
+{ 0xCCAB, 0xCCAB, 0xCCAB },
+{ 0xCCAC, 0xCCAC, 0xCCAC },
+{ 0xCCAD, 0xCCAD, 0xCCAD },
+{ 0xCCAE, 0xCCAE, 0xCCAE },
+{ 0xCCAF, 0xCCAF, 0xCCAF },
+{ 0xCCB0, 0xCCB0, 0xCCB0 },
+{ 0xCCB1, 0xCCB1, 0xCCB1 },
+{ 0xCCB2, 0xCCB2, 0xCCB2 },
+{ 0xCCB3, 0xCCB3, 0xCCB3 },
+{ 0xCCB4, 0xCCB4, 0xCCB4 },
+{ 0xCCB5, 0xCCB5, 0xCCB5 },
+{ 0xCCB6, 0xCCB6, 0xCCB6 },
+{ 0xCCB7, 0xCCB7, 0xCCB7 },
+{ 0xCCB8, 0xCCB8, 0xCCB8 },
+{ 0xCCB9, 0xCCB9, 0xCCB9 },
+{ 0xCCBA, 0xCCBA, 0xCCBA },
+{ 0xCCBB, 0xCCBB, 0xCCBB },
+{ 0xCCBC, 0xCCBC, 0xCCBC },
+{ 0xCCBD, 0xCCBD, 0xCCBD },
+{ 0xCCBE, 0xCCBE, 0xCCBE },
+{ 0xCCBF, 0xCCBF, 0xCCBF },
+{ 0xCCC0, 0xCCC0, 0xCCC0 },
+{ 0xCCC1, 0xCCC1, 0xCCC1 },
+{ 0xCCC2, 0xCCC2, 0xCCC2 },
+{ 0xCCC3, 0xCCC3, 0xCCC3 },
+{ 0xCCC4, 0xCCC4, 0xCCC4 },
+{ 0xCCC5, 0xCCC5, 0xCCC5 },
+{ 0xCCC6, 0xCCC6, 0xCCC6 },
+{ 0xCCC7, 0xCCC7, 0xCCC7 },
+{ 0xCCC8, 0xCCC8, 0xCCC8 },
+{ 0xCCC9, 0xCCC9, 0xCCC9 },
+{ 0xCCCA, 0xCCCA, 0xCCCA },
+{ 0xCCCB, 0xCCCB, 0xCCCB },
+{ 0xCCCC, 0xCCCC, 0xCCCC },
+{ 0xCCCD, 0xCCCD, 0xCCCD },
+{ 0xCCCE, 0xCCCE, 0xCCCE },
+{ 0xCCCF, 0xCCCF, 0xCCCF },
+{ 0xCCD0, 0xCCD0, 0xCCD0 },
+{ 0xCCD1, 0xCCD1, 0xCCD1 },
+{ 0xCCD2, 0xCCD2, 0xCCD2 },
+{ 0xCCD3, 0xCCD3, 0xCCD3 },
+{ 0xCCD4, 0xCCD4, 0xCCD4 },
+{ 0xCCD5, 0xCCD5, 0xCCD5 },
+{ 0xCCD6, 0xCCD6, 0xCCD6 },
+{ 0xCCD7, 0xCCD7, 0xCCD7 },
+{ 0xCCD8, 0xCCD8, 0xCCD8 },
+{ 0xCCD9, 0xCCD9, 0xCCD9 },
+{ 0xCCDA, 0xCCDA, 0xCCDA },
+{ 0xCCDB, 0xCCDB, 0xCCDB },
+{ 0xCCDC, 0xCCDC, 0xCCDC },
+{ 0xCCDD, 0xCCDD, 0xCCDD },
+{ 0xCCDE, 0xCCDE, 0xCCDE },
+{ 0xCCDF, 0xCCDF, 0xCCDF },
+{ 0xCCE0, 0xCCE0, 0xCCE0 },
+{ 0xCCE1, 0xCCE1, 0xCCE1 },
+{ 0xCCE2, 0xCCE2, 0xCCE2 },
+{ 0xCCE3, 0xCCE3, 0xCCE3 },
+{ 0xCCE4, 0xCCE4, 0xCCE4 },
+{ 0xCCE5, 0xCCE5, 0xCCE5 },
+{ 0xCCE6, 0xCCE6, 0xCCE6 },
+{ 0xCCE7, 0xCCE7, 0xCCE7 },
+{ 0xCCE8, 0xCCE8, 0xCCE8 },
+{ 0xCCE9, 0xCCE9, 0xCCE9 },
+{ 0xCCEA, 0xCCEA, 0xCCEA },
+{ 0xCCEB, 0xCCEB, 0xCCEB },
+{ 0xCCEC, 0xCCEC, 0xCCEC },
+{ 0xCCED, 0xCCED, 0xCCED },
+{ 0xCCEE, 0xCCEE, 0xCCEE },
+{ 0xCCEF, 0xCCEF, 0xCCEF },
+{ 0xCCF0, 0xCCF0, 0xCCF0 },
+{ 0xCCF1, 0xCCF1, 0xCCF1 },
+{ 0xCCF2, 0xCCF2, 0xCCF2 },
+{ 0xCCF3, 0xCCF3, 0xCCF3 },
+{ 0xCCF4, 0xCCF4, 0xCCF4 },
+{ 0xCCF5, 0xCCF5, 0xCCF5 },
+{ 0xCCF6, 0xCCF6, 0xCCF6 },
+{ 0xCCF7, 0xCCF7, 0xCCF7 },
+{ 0xCCF8, 0xCCF8, 0xCCF8 },
+{ 0xCCF9, 0xCCF9, 0xCCF9 },
+{ 0xCCFA, 0xCCFA, 0xCCFA },
+{ 0xCCFB, 0xCCFB, 0xCCFB },
+{ 0xCCFC, 0xCCFC, 0xCCFC },
+{ 0xCCFD, 0xCCFD, 0xCCFD },
+{ 0xCCFE, 0xCCFE, 0xCCFE },
+{ 0xCCFF, 0xCCFF, 0xCCFF },
+{ 0xCD00, 0xCD00, 0xCD00 },
+{ 0xCD01, 0xCD01, 0xCD01 },
+{ 0xCD02, 0xCD02, 0xCD02 },
+{ 0xCD03, 0xCD03, 0xCD03 },
+{ 0xCD04, 0xCD04, 0xCD04 },
+{ 0xCD05, 0xCD05, 0xCD05 },
+{ 0xCD06, 0xCD06, 0xCD06 },
+{ 0xCD07, 0xCD07, 0xCD07 },
+{ 0xCD08, 0xCD08, 0xCD08 },
+{ 0xCD09, 0xCD09, 0xCD09 },
+{ 0xCD0A, 0xCD0A, 0xCD0A },
+{ 0xCD0B, 0xCD0B, 0xCD0B },
+{ 0xCD0C, 0xCD0C, 0xCD0C },
+{ 0xCD0D, 0xCD0D, 0xCD0D },
+{ 0xCD0E, 0xCD0E, 0xCD0E },
+{ 0xCD0F, 0xCD0F, 0xCD0F },
+{ 0xCD10, 0xCD10, 0xCD10 },
+{ 0xCD11, 0xCD11, 0xCD11 },
+{ 0xCD12, 0xCD12, 0xCD12 },
+{ 0xCD13, 0xCD13, 0xCD13 },
+{ 0xCD14, 0xCD14, 0xCD14 },
+{ 0xCD15, 0xCD15, 0xCD15 },
+{ 0xCD16, 0xCD16, 0xCD16 },
+{ 0xCD17, 0xCD17, 0xCD17 },
+{ 0xCD18, 0xCD18, 0xCD18 },
+{ 0xCD19, 0xCD19, 0xCD19 },
+{ 0xCD1A, 0xCD1A, 0xCD1A },
+{ 0xCD1B, 0xCD1B, 0xCD1B },
+{ 0xCD1C, 0xCD1C, 0xCD1C },
+{ 0xCD1D, 0xCD1D, 0xCD1D },
+{ 0xCD1E, 0xCD1E, 0xCD1E },
+{ 0xCD1F, 0xCD1F, 0xCD1F },
+{ 0xCD20, 0xCD20, 0xCD20 },
+{ 0xCD21, 0xCD21, 0xCD21 },
+{ 0xCD22, 0xCD22, 0xCD22 },
+{ 0xCD23, 0xCD23, 0xCD23 },
+{ 0xCD24, 0xCD24, 0xCD24 },
+{ 0xCD25, 0xCD25, 0xCD25 },
+{ 0xCD26, 0xCD26, 0xCD26 },
+{ 0xCD27, 0xCD27, 0xCD27 },
+{ 0xCD28, 0xCD28, 0xCD28 },
+{ 0xCD29, 0xCD29, 0xCD29 },
+{ 0xCD2A, 0xCD2A, 0xCD2A },
+{ 0xCD2B, 0xCD2B, 0xCD2B },
+{ 0xCD2C, 0xCD2C, 0xCD2C },
+{ 0xCD2D, 0xCD2D, 0xCD2D },
+{ 0xCD2E, 0xCD2E, 0xCD2E },
+{ 0xCD2F, 0xCD2F, 0xCD2F },
+{ 0xCD30, 0xCD30, 0xCD30 },
+{ 0xCD31, 0xCD31, 0xCD31 },
+{ 0xCD32, 0xCD32, 0xCD32 },
+{ 0xCD33, 0xCD33, 0xCD33 },
+{ 0xCD34, 0xCD34, 0xCD34 },
+{ 0xCD35, 0xCD35, 0xCD35 },
+{ 0xCD36, 0xCD36, 0xCD36 },
+{ 0xCD37, 0xCD37, 0xCD37 },
+{ 0xCD38, 0xCD38, 0xCD38 },
+{ 0xCD39, 0xCD39, 0xCD39 },
+{ 0xCD3A, 0xCD3A, 0xCD3A },
+{ 0xCD3B, 0xCD3B, 0xCD3B },
+{ 0xCD3C, 0xCD3C, 0xCD3C },
+{ 0xCD3D, 0xCD3D, 0xCD3D },
+{ 0xCD3E, 0xCD3E, 0xCD3E },
+{ 0xCD3F, 0xCD3F, 0xCD3F },
+{ 0xCD40, 0xCD40, 0xCD40 },
+{ 0xCD41, 0xCD41, 0xCD41 },
+{ 0xCD42, 0xCD42, 0xCD42 },
+{ 0xCD43, 0xCD43, 0xCD43 },
+{ 0xCD44, 0xCD44, 0xCD44 },
+{ 0xCD45, 0xCD45, 0xCD45 },
+{ 0xCD46, 0xCD46, 0xCD46 },
+{ 0xCD47, 0xCD47, 0xCD47 },
+{ 0xCD48, 0xCD48, 0xCD48 },
+{ 0xCD49, 0xCD49, 0xCD49 },
+{ 0xCD4A, 0xCD4A, 0xCD4A },
+{ 0xCD4B, 0xCD4B, 0xCD4B },
+{ 0xCD4C, 0xCD4C, 0xCD4C },
+{ 0xCD4D, 0xCD4D, 0xCD4D },
+{ 0xCD4E, 0xCD4E, 0xCD4E },
+{ 0xCD4F, 0xCD4F, 0xCD4F },
+{ 0xCD50, 0xCD50, 0xCD50 },
+{ 0xCD51, 0xCD51, 0xCD51 },
+{ 0xCD52, 0xCD52, 0xCD52 },
+{ 0xCD53, 0xCD53, 0xCD53 },
+{ 0xCD54, 0xCD54, 0xCD54 },
+{ 0xCD55, 0xCD55, 0xCD55 },
+{ 0xCD56, 0xCD56, 0xCD56 },
+{ 0xCD57, 0xCD57, 0xCD57 },
+{ 0xCD58, 0xCD58, 0xCD58 },
+{ 0xCD59, 0xCD59, 0xCD59 },
+{ 0xCD5A, 0xCD5A, 0xCD5A },
+{ 0xCD5B, 0xCD5B, 0xCD5B },
+{ 0xCD5C, 0xCD5C, 0xCD5C },
+{ 0xCD5D, 0xCD5D, 0xCD5D },
+{ 0xCD5E, 0xCD5E, 0xCD5E },
+{ 0xCD5F, 0xCD5F, 0xCD5F },
+{ 0xCD60, 0xCD60, 0xCD60 },
+{ 0xCD61, 0xCD61, 0xCD61 },
+{ 0xCD62, 0xCD62, 0xCD62 },
+{ 0xCD63, 0xCD63, 0xCD63 },
+{ 0xCD64, 0xCD64, 0xCD64 },
+{ 0xCD65, 0xCD65, 0xCD65 },
+{ 0xCD66, 0xCD66, 0xCD66 },
+{ 0xCD67, 0xCD67, 0xCD67 },
+{ 0xCD68, 0xCD68, 0xCD68 },
+{ 0xCD69, 0xCD69, 0xCD69 },
+{ 0xCD6A, 0xCD6A, 0xCD6A },
+{ 0xCD6B, 0xCD6B, 0xCD6B },
+{ 0xCD6C, 0xCD6C, 0xCD6C },
+{ 0xCD6D, 0xCD6D, 0xCD6D },
+{ 0xCD6E, 0xCD6E, 0xCD6E },
+{ 0xCD6F, 0xCD6F, 0xCD6F },
+{ 0xCD70, 0xCD70, 0xCD70 },
+{ 0xCD71, 0xCD71, 0xCD71 },
+{ 0xCD72, 0xCD72, 0xCD72 },
+{ 0xCD73, 0xCD73, 0xCD73 },
+{ 0xCD74, 0xCD74, 0xCD74 },
+{ 0xCD75, 0xCD75, 0xCD75 },
+{ 0xCD76, 0xCD76, 0xCD76 },
+{ 0xCD77, 0xCD77, 0xCD77 },
+{ 0xCD78, 0xCD78, 0xCD78 },
+{ 0xCD79, 0xCD79, 0xCD79 },
+{ 0xCD7A, 0xCD7A, 0xCD7A },
+{ 0xCD7B, 0xCD7B, 0xCD7B },
+{ 0xCD7C, 0xCD7C, 0xCD7C },
+{ 0xCD7D, 0xCD7D, 0xCD7D },
+{ 0xCD7E, 0xCD7E, 0xCD7E },
+{ 0xCD7F, 0xCD7F, 0xCD7F },
+{ 0xCD80, 0xCD80, 0xCD80 },
+{ 0xCD81, 0xCD81, 0xCD81 },
+{ 0xCD82, 0xCD82, 0xCD82 },
+{ 0xCD83, 0xCD83, 0xCD83 },
+{ 0xCD84, 0xCD84, 0xCD84 },
+{ 0xCD85, 0xCD85, 0xCD85 },
+{ 0xCD86, 0xCD86, 0xCD86 },
+{ 0xCD87, 0xCD87, 0xCD87 },
+{ 0xCD88, 0xCD88, 0xCD88 },
+{ 0xCD89, 0xCD89, 0xCD89 },
+{ 0xCD8A, 0xCD8A, 0xCD8A },
+{ 0xCD8B, 0xCD8B, 0xCD8B },
+{ 0xCD8C, 0xCD8C, 0xCD8C },
+{ 0xCD8D, 0xCD8D, 0xCD8D },
+{ 0xCD8E, 0xCD8E, 0xCD8E },
+{ 0xCD8F, 0xCD8F, 0xCD8F },
+{ 0xCD90, 0xCD90, 0xCD90 },
+{ 0xCD91, 0xCD91, 0xCD91 },
+{ 0xCD92, 0xCD92, 0xCD92 },
+{ 0xCD93, 0xCD93, 0xCD93 },
+{ 0xCD94, 0xCD94, 0xCD94 },
+{ 0xCD95, 0xCD95, 0xCD95 },
+{ 0xCD96, 0xCD96, 0xCD96 },
+{ 0xCD97, 0xCD97, 0xCD97 },
+{ 0xCD98, 0xCD98, 0xCD98 },
+{ 0xCD99, 0xCD99, 0xCD99 },
+{ 0xCD9A, 0xCD9A, 0xCD9A },
+{ 0xCD9B, 0xCD9B, 0xCD9B },
+{ 0xCD9C, 0xCD9C, 0xCD9C },
+{ 0xCD9D, 0xCD9D, 0xCD9D },
+{ 0xCD9E, 0xCD9E, 0xCD9E },
+{ 0xCD9F, 0xCD9F, 0xCD9F },
+{ 0xCDA0, 0xCDA0, 0xCDA0 },
+{ 0xCDA1, 0xCDA1, 0xCDA1 },
+{ 0xCDA2, 0xCDA2, 0xCDA2 },
+{ 0xCDA3, 0xCDA3, 0xCDA3 },
+{ 0xCDA4, 0xCDA4, 0xCDA4 },
+{ 0xCDA5, 0xCDA5, 0xCDA5 },
+{ 0xCDA6, 0xCDA6, 0xCDA6 },
+{ 0xCDA7, 0xCDA7, 0xCDA7 },
+{ 0xCDA8, 0xCDA8, 0xCDA8 },
+{ 0xCDA9, 0xCDA9, 0xCDA9 },
+{ 0xCDAA, 0xCDAA, 0xCDAA },
+{ 0xCDAB, 0xCDAB, 0xCDAB },
+{ 0xCDAC, 0xCDAC, 0xCDAC },
+{ 0xCDAD, 0xCDAD, 0xCDAD },
+{ 0xCDAE, 0xCDAE, 0xCDAE },
+{ 0xCDAF, 0xCDAF, 0xCDAF },
+{ 0xCDB0, 0xCDB0, 0xCDB0 },
+{ 0xCDB1, 0xCDB1, 0xCDB1 },
+{ 0xCDB2, 0xCDB2, 0xCDB2 },
+{ 0xCDB3, 0xCDB3, 0xCDB3 },
+{ 0xCDB4, 0xCDB4, 0xCDB4 },
+{ 0xCDB5, 0xCDB5, 0xCDB5 },
+{ 0xCDB6, 0xCDB6, 0xCDB6 },
+{ 0xCDB7, 0xCDB7, 0xCDB7 },
+{ 0xCDB8, 0xCDB8, 0xCDB8 },
+{ 0xCDB9, 0xCDB9, 0xCDB9 },
+{ 0xCDBA, 0xCDBA, 0xCDBA },
+{ 0xCDBB, 0xCDBB, 0xCDBB },
+{ 0xCDBC, 0xCDBC, 0xCDBC },
+{ 0xCDBD, 0xCDBD, 0xCDBD },
+{ 0xCDBE, 0xCDBE, 0xCDBE },
+{ 0xCDBF, 0xCDBF, 0xCDBF },
+{ 0xCDC0, 0xCDC0, 0xCDC0 },
+{ 0xCDC1, 0xCDC1, 0xCDC1 },
+{ 0xCDC2, 0xCDC2, 0xCDC2 },
+{ 0xCDC3, 0xCDC3, 0xCDC3 },
+{ 0xCDC4, 0xCDC4, 0xCDC4 },
+{ 0xCDC5, 0xCDC5, 0xCDC5 },
+{ 0xCDC6, 0xCDC6, 0xCDC6 },
+{ 0xCDC7, 0xCDC7, 0xCDC7 },
+{ 0xCDC8, 0xCDC8, 0xCDC8 },
+{ 0xCDC9, 0xCDC9, 0xCDC9 },
+{ 0xCDCA, 0xCDCA, 0xCDCA },
+{ 0xCDCB, 0xCDCB, 0xCDCB },
+{ 0xCDCC, 0xCDCC, 0xCDCC },
+{ 0xCDCD, 0xCDCD, 0xCDCD },
+{ 0xCDCE, 0xCDCE, 0xCDCE },
+{ 0xCDCF, 0xCDCF, 0xCDCF },
+{ 0xCDD0, 0xCDD0, 0xCDD0 },
+{ 0xCDD1, 0xCDD1, 0xCDD1 },
+{ 0xCDD2, 0xCDD2, 0xCDD2 },
+{ 0xCDD3, 0xCDD3, 0xCDD3 },
+{ 0xCDD4, 0xCDD4, 0xCDD4 },
+{ 0xCDD5, 0xCDD5, 0xCDD5 },
+{ 0xCDD6, 0xCDD6, 0xCDD6 },
+{ 0xCDD7, 0xCDD7, 0xCDD7 },
+{ 0xCDD8, 0xCDD8, 0xCDD8 },
+{ 0xCDD9, 0xCDD9, 0xCDD9 },
+{ 0xCDDA, 0xCDDA, 0xCDDA },
+{ 0xCDDB, 0xCDDB, 0xCDDB },
+{ 0xCDDC, 0xCDDC, 0xCDDC },
+{ 0xCDDD, 0xCDDD, 0xCDDD },
+{ 0xCDDE, 0xCDDE, 0xCDDE },
+{ 0xCDDF, 0xCDDF, 0xCDDF },
+{ 0xCDE0, 0xCDE0, 0xCDE0 },
+{ 0xCDE1, 0xCDE1, 0xCDE1 },
+{ 0xCDE2, 0xCDE2, 0xCDE2 },
+{ 0xCDE3, 0xCDE3, 0xCDE3 },
+{ 0xCDE4, 0xCDE4, 0xCDE4 },
+{ 0xCDE5, 0xCDE5, 0xCDE5 },
+{ 0xCDE6, 0xCDE6, 0xCDE6 },
+{ 0xCDE7, 0xCDE7, 0xCDE7 },
+{ 0xCDE8, 0xCDE8, 0xCDE8 },
+{ 0xCDE9, 0xCDE9, 0xCDE9 },
+{ 0xCDEA, 0xCDEA, 0xCDEA },
+{ 0xCDEB, 0xCDEB, 0xCDEB },
+{ 0xCDEC, 0xCDEC, 0xCDEC },
+{ 0xCDED, 0xCDED, 0xCDED },
+{ 0xCDEE, 0xCDEE, 0xCDEE },
+{ 0xCDEF, 0xCDEF, 0xCDEF },
+{ 0xCDF0, 0xCDF0, 0xCDF0 },
+{ 0xCDF1, 0xCDF1, 0xCDF1 },
+{ 0xCDF2, 0xCDF2, 0xCDF2 },
+{ 0xCDF3, 0xCDF3, 0xCDF3 },
+{ 0xCDF4, 0xCDF4, 0xCDF4 },
+{ 0xCDF5, 0xCDF5, 0xCDF5 },
+{ 0xCDF6, 0xCDF6, 0xCDF6 },
+{ 0xCDF7, 0xCDF7, 0xCDF7 },
+{ 0xCDF8, 0xCDF8, 0xCDF8 },
+{ 0xCDF9, 0xCDF9, 0xCDF9 },
+{ 0xCDFA, 0xCDFA, 0xCDFA },
+{ 0xCDFB, 0xCDFB, 0xCDFB },
+{ 0xCDFC, 0xCDFC, 0xCDFC },
+{ 0xCDFD, 0xCDFD, 0xCDFD },
+{ 0xCDFE, 0xCDFE, 0xCDFE },
+{ 0xCDFF, 0xCDFF, 0xCDFF },
+{ 0xCE00, 0xCE00, 0xCE00 },
+{ 0xCE01, 0xCE01, 0xCE01 },
+{ 0xCE02, 0xCE02, 0xCE02 },
+{ 0xCE03, 0xCE03, 0xCE03 },
+{ 0xCE04, 0xCE04, 0xCE04 },
+{ 0xCE05, 0xCE05, 0xCE05 },
+{ 0xCE06, 0xCE06, 0xCE06 },
+{ 0xCE07, 0xCE07, 0xCE07 },
+{ 0xCE08, 0xCE08, 0xCE08 },
+{ 0xCE09, 0xCE09, 0xCE09 },
+{ 0xCE0A, 0xCE0A, 0xCE0A },
+{ 0xCE0B, 0xCE0B, 0xCE0B },
+{ 0xCE0C, 0xCE0C, 0xCE0C },
+{ 0xCE0D, 0xCE0D, 0xCE0D },
+{ 0xCE0E, 0xCE0E, 0xCE0E },
+{ 0xCE0F, 0xCE0F, 0xCE0F },
+{ 0xCE10, 0xCE10, 0xCE10 },
+{ 0xCE11, 0xCE11, 0xCE11 },
+{ 0xCE12, 0xCE12, 0xCE12 },
+{ 0xCE13, 0xCE13, 0xCE13 },
+{ 0xCE14, 0xCE14, 0xCE14 },
+{ 0xCE15, 0xCE15, 0xCE15 },
+{ 0xCE16, 0xCE16, 0xCE16 },
+{ 0xCE17, 0xCE17, 0xCE17 },
+{ 0xCE18, 0xCE18, 0xCE18 },
+{ 0xCE19, 0xCE19, 0xCE19 },
+{ 0xCE1A, 0xCE1A, 0xCE1A },
+{ 0xCE1B, 0xCE1B, 0xCE1B },
+{ 0xCE1C, 0xCE1C, 0xCE1C },
+{ 0xCE1D, 0xCE1D, 0xCE1D },
+{ 0xCE1E, 0xCE1E, 0xCE1E },
+{ 0xCE1F, 0xCE1F, 0xCE1F },
+{ 0xCE20, 0xCE20, 0xCE20 },
+{ 0xCE21, 0xCE21, 0xCE21 },
+{ 0xCE22, 0xCE22, 0xCE22 },
+{ 0xCE23, 0xCE23, 0xCE23 },
+{ 0xCE24, 0xCE24, 0xCE24 },
+{ 0xCE25, 0xCE25, 0xCE25 },
+{ 0xCE26, 0xCE26, 0xCE26 },
+{ 0xCE27, 0xCE27, 0xCE27 },
+{ 0xCE28, 0xCE28, 0xCE28 },
+{ 0xCE29, 0xCE29, 0xCE29 },
+{ 0xCE2A, 0xCE2A, 0xCE2A },
+{ 0xCE2B, 0xCE2B, 0xCE2B },
+{ 0xCE2C, 0xCE2C, 0xCE2C },
+{ 0xCE2D, 0xCE2D, 0xCE2D },
+{ 0xCE2E, 0xCE2E, 0xCE2E },
+{ 0xCE2F, 0xCE2F, 0xCE2F },
+{ 0xCE30, 0xCE30, 0xCE30 },
+{ 0xCE31, 0xCE31, 0xCE31 },
+{ 0xCE32, 0xCE32, 0xCE32 },
+{ 0xCE33, 0xCE33, 0xCE33 },
+{ 0xCE34, 0xCE34, 0xCE34 },
+{ 0xCE35, 0xCE35, 0xCE35 },
+{ 0xCE36, 0xCE36, 0xCE36 },
+{ 0xCE37, 0xCE37, 0xCE37 },
+{ 0xCE38, 0xCE38, 0xCE38 },
+{ 0xCE39, 0xCE39, 0xCE39 },
+{ 0xCE3A, 0xCE3A, 0xCE3A },
+{ 0xCE3B, 0xCE3B, 0xCE3B },
+{ 0xCE3C, 0xCE3C, 0xCE3C },
+{ 0xCE3D, 0xCE3D, 0xCE3D },
+{ 0xCE3E, 0xCE3E, 0xCE3E },
+{ 0xCE3F, 0xCE3F, 0xCE3F },
+{ 0xCE40, 0xCE40, 0xCE40 },
+{ 0xCE41, 0xCE41, 0xCE41 },
+{ 0xCE42, 0xCE42, 0xCE42 },
+{ 0xCE43, 0xCE43, 0xCE43 },
+{ 0xCE44, 0xCE44, 0xCE44 },
+{ 0xCE45, 0xCE45, 0xCE45 },
+{ 0xCE46, 0xCE46, 0xCE46 },
+{ 0xCE47, 0xCE47, 0xCE47 },
+{ 0xCE48, 0xCE48, 0xCE48 },
+{ 0xCE49, 0xCE49, 0xCE49 },
+{ 0xCE4A, 0xCE4A, 0xCE4A },
+{ 0xCE4B, 0xCE4B, 0xCE4B },
+{ 0xCE4C, 0xCE4C, 0xCE4C },
+{ 0xCE4D, 0xCE4D, 0xCE4D },
+{ 0xCE4E, 0xCE4E, 0xCE4E },
+{ 0xCE4F, 0xCE4F, 0xCE4F },
+{ 0xCE50, 0xCE50, 0xCE50 },
+{ 0xCE51, 0xCE51, 0xCE51 },
+{ 0xCE52, 0xCE52, 0xCE52 },
+{ 0xCE53, 0xCE53, 0xCE53 },
+{ 0xCE54, 0xCE54, 0xCE54 },
+{ 0xCE55, 0xCE55, 0xCE55 },
+{ 0xCE56, 0xCE56, 0xCE56 },
+{ 0xCE57, 0xCE57, 0xCE57 },
+{ 0xCE58, 0xCE58, 0xCE58 },
+{ 0xCE59, 0xCE59, 0xCE59 },
+{ 0xCE5A, 0xCE5A, 0xCE5A },
+{ 0xCE5B, 0xCE5B, 0xCE5B },
+{ 0xCE5C, 0xCE5C, 0xCE5C },
+{ 0xCE5D, 0xCE5D, 0xCE5D },
+{ 0xCE5E, 0xCE5E, 0xCE5E },
+{ 0xCE5F, 0xCE5F, 0xCE5F },
+{ 0xCE60, 0xCE60, 0xCE60 },
+{ 0xCE61, 0xCE61, 0xCE61 },
+{ 0xCE62, 0xCE62, 0xCE62 },
+{ 0xCE63, 0xCE63, 0xCE63 },
+{ 0xCE64, 0xCE64, 0xCE64 },
+{ 0xCE65, 0xCE65, 0xCE65 },
+{ 0xCE66, 0xCE66, 0xCE66 },
+{ 0xCE67, 0xCE67, 0xCE67 },
+{ 0xCE68, 0xCE68, 0xCE68 },
+{ 0xCE69, 0xCE69, 0xCE69 },
+{ 0xCE6A, 0xCE6A, 0xCE6A },
+{ 0xCE6B, 0xCE6B, 0xCE6B },
+{ 0xCE6C, 0xCE6C, 0xCE6C },
+{ 0xCE6D, 0xCE6D, 0xCE6D },
+{ 0xCE6E, 0xCE6E, 0xCE6E },
+{ 0xCE6F, 0xCE6F, 0xCE6F },
+{ 0xCE70, 0xCE70, 0xCE70 },
+{ 0xCE71, 0xCE71, 0xCE71 },
+{ 0xCE72, 0xCE72, 0xCE72 },
+{ 0xCE73, 0xCE73, 0xCE73 },
+{ 0xCE74, 0xCE74, 0xCE74 },
+{ 0xCE75, 0xCE75, 0xCE75 },
+{ 0xCE76, 0xCE76, 0xCE76 },
+{ 0xCE77, 0xCE77, 0xCE77 },
+{ 0xCE78, 0xCE78, 0xCE78 },
+{ 0xCE79, 0xCE79, 0xCE79 },
+{ 0xCE7A, 0xCE7A, 0xCE7A },
+{ 0xCE7B, 0xCE7B, 0xCE7B },
+{ 0xCE7C, 0xCE7C, 0xCE7C },
+{ 0xCE7D, 0xCE7D, 0xCE7D },
+{ 0xCE7E, 0xCE7E, 0xCE7E },
+{ 0xCE7F, 0xCE7F, 0xCE7F },
+{ 0xCE80, 0xCE80, 0xCE80 },
+{ 0xCE81, 0xCE81, 0xCE81 },
+{ 0xCE82, 0xCE82, 0xCE82 },
+{ 0xCE83, 0xCE83, 0xCE83 },
+{ 0xCE84, 0xCE84, 0xCE84 },
+{ 0xCE85, 0xCE85, 0xCE85 },
+{ 0xCE86, 0xCE86, 0xCE86 },
+{ 0xCE87, 0xCE87, 0xCE87 },
+{ 0xCE88, 0xCE88, 0xCE88 },
+{ 0xCE89, 0xCE89, 0xCE89 },
+{ 0xCE8A, 0xCE8A, 0xCE8A },
+{ 0xCE8B, 0xCE8B, 0xCE8B },
+{ 0xCE8C, 0xCE8C, 0xCE8C },
+{ 0xCE8D, 0xCE8D, 0xCE8D },
+{ 0xCE8E, 0xCE8E, 0xCE8E },
+{ 0xCE8F, 0xCE8F, 0xCE8F },
+{ 0xCE90, 0xCE90, 0xCE90 },
+{ 0xCE91, 0xCE91, 0xCE91 },
+{ 0xCE92, 0xCE92, 0xCE92 },
+{ 0xCE93, 0xCE93, 0xCE93 },
+{ 0xCE94, 0xCE94, 0xCE94 },
+{ 0xCE95, 0xCE95, 0xCE95 },
+{ 0xCE96, 0xCE96, 0xCE96 },
+{ 0xCE97, 0xCE97, 0xCE97 },
+{ 0xCE98, 0xCE98, 0xCE98 },
+{ 0xCE99, 0xCE99, 0xCE99 },
+{ 0xCE9A, 0xCE9A, 0xCE9A },
+{ 0xCE9B, 0xCE9B, 0xCE9B },
+{ 0xCE9C, 0xCE9C, 0xCE9C },
+{ 0xCE9D, 0xCE9D, 0xCE9D },
+{ 0xCE9E, 0xCE9E, 0xCE9E },
+{ 0xCE9F, 0xCE9F, 0xCE9F },
+{ 0xCEA0, 0xCEA0, 0xCEA0 },
+{ 0xCEA1, 0xCEA1, 0xCEA1 },
+{ 0xCEA2, 0xCEA2, 0xCEA2 },
+{ 0xCEA3, 0xCEA3, 0xCEA3 },
+{ 0xCEA4, 0xCEA4, 0xCEA4 },
+{ 0xCEA5, 0xCEA5, 0xCEA5 },
+{ 0xCEA6, 0xCEA6, 0xCEA6 },
+{ 0xCEA7, 0xCEA7, 0xCEA7 },
+{ 0xCEA8, 0xCEA8, 0xCEA8 },
+{ 0xCEA9, 0xCEA9, 0xCEA9 },
+{ 0xCEAA, 0xCEAA, 0xCEAA },
+{ 0xCEAB, 0xCEAB, 0xCEAB },
+{ 0xCEAC, 0xCEAC, 0xCEAC },
+{ 0xCEAD, 0xCEAD, 0xCEAD },
+{ 0xCEAE, 0xCEAE, 0xCEAE },
+{ 0xCEAF, 0xCEAF, 0xCEAF },
+{ 0xCEB0, 0xCEB0, 0xCEB0 },
+{ 0xCEB1, 0xCEB1, 0xCEB1 },
+{ 0xCEB2, 0xCEB2, 0xCEB2 },
+{ 0xCEB3, 0xCEB3, 0xCEB3 },
+{ 0xCEB4, 0xCEB4, 0xCEB4 },
+{ 0xCEB5, 0xCEB5, 0xCEB5 },
+{ 0xCEB6, 0xCEB6, 0xCEB6 },
+{ 0xCEB7, 0xCEB7, 0xCEB7 },
+{ 0xCEB8, 0xCEB8, 0xCEB8 },
+{ 0xCEB9, 0xCEB9, 0xCEB9 },
+{ 0xCEBA, 0xCEBA, 0xCEBA },
+{ 0xCEBB, 0xCEBB, 0xCEBB },
+{ 0xCEBC, 0xCEBC, 0xCEBC },
+{ 0xCEBD, 0xCEBD, 0xCEBD },
+{ 0xCEBE, 0xCEBE, 0xCEBE },
+{ 0xCEBF, 0xCEBF, 0xCEBF },
+{ 0xCEC0, 0xCEC0, 0xCEC0 },
+{ 0xCEC1, 0xCEC1, 0xCEC1 },
+{ 0xCEC2, 0xCEC2, 0xCEC2 },
+{ 0xCEC3, 0xCEC3, 0xCEC3 },
+{ 0xCEC4, 0xCEC4, 0xCEC4 },
+{ 0xCEC5, 0xCEC5, 0xCEC5 },
+{ 0xCEC6, 0xCEC6, 0xCEC6 },
+{ 0xCEC7, 0xCEC7, 0xCEC7 },
+{ 0xCEC8, 0xCEC8, 0xCEC8 },
+{ 0xCEC9, 0xCEC9, 0xCEC9 },
+{ 0xCECA, 0xCECA, 0xCECA },
+{ 0xCECB, 0xCECB, 0xCECB },
+{ 0xCECC, 0xCECC, 0xCECC },
+{ 0xCECD, 0xCECD, 0xCECD },
+{ 0xCECE, 0xCECE, 0xCECE },
+{ 0xCECF, 0xCECF, 0xCECF },
+{ 0xCED0, 0xCED0, 0xCED0 },
+{ 0xCED1, 0xCED1, 0xCED1 },
+{ 0xCED2, 0xCED2, 0xCED2 },
+{ 0xCED3, 0xCED3, 0xCED3 },
+{ 0xCED4, 0xCED4, 0xCED4 },
+{ 0xCED5, 0xCED5, 0xCED5 },
+{ 0xCED6, 0xCED6, 0xCED6 },
+{ 0xCED7, 0xCED7, 0xCED7 },
+{ 0xCED8, 0xCED8, 0xCED8 },
+{ 0xCED9, 0xCED9, 0xCED9 },
+{ 0xCEDA, 0xCEDA, 0xCEDA },
+{ 0xCEDB, 0xCEDB, 0xCEDB },
+{ 0xCEDC, 0xCEDC, 0xCEDC },
+{ 0xCEDD, 0xCEDD, 0xCEDD },
+{ 0xCEDE, 0xCEDE, 0xCEDE },
+{ 0xCEDF, 0xCEDF, 0xCEDF },
+{ 0xCEE0, 0xCEE0, 0xCEE0 },
+{ 0xCEE1, 0xCEE1, 0xCEE1 },
+{ 0xCEE2, 0xCEE2, 0xCEE2 },
+{ 0xCEE3, 0xCEE3, 0xCEE3 },
+{ 0xCEE4, 0xCEE4, 0xCEE4 },
+{ 0xCEE5, 0xCEE5, 0xCEE5 },
+{ 0xCEE6, 0xCEE6, 0xCEE6 },
+{ 0xCEE7, 0xCEE7, 0xCEE7 },
+{ 0xCEE8, 0xCEE8, 0xCEE8 },
+{ 0xCEE9, 0xCEE9, 0xCEE9 },
+{ 0xCEEA, 0xCEEA, 0xCEEA },
+{ 0xCEEB, 0xCEEB, 0xCEEB },
+{ 0xCEEC, 0xCEEC, 0xCEEC },
+{ 0xCEED, 0xCEED, 0xCEED },
+{ 0xCEEE, 0xCEEE, 0xCEEE },
+{ 0xCEEF, 0xCEEF, 0xCEEF },
+{ 0xCEF0, 0xCEF0, 0xCEF0 },
+{ 0xCEF1, 0xCEF1, 0xCEF1 },
+{ 0xCEF2, 0xCEF2, 0xCEF2 },
+{ 0xCEF3, 0xCEF3, 0xCEF3 },
+{ 0xCEF4, 0xCEF4, 0xCEF4 },
+{ 0xCEF5, 0xCEF5, 0xCEF5 },
+{ 0xCEF6, 0xCEF6, 0xCEF6 },
+{ 0xCEF7, 0xCEF7, 0xCEF7 },
+{ 0xCEF8, 0xCEF8, 0xCEF8 },
+{ 0xCEF9, 0xCEF9, 0xCEF9 },
+{ 0xCEFA, 0xCEFA, 0xCEFA },
+{ 0xCEFB, 0xCEFB, 0xCEFB },
+{ 0xCEFC, 0xCEFC, 0xCEFC },
+{ 0xCEFD, 0xCEFD, 0xCEFD },
+{ 0xCEFE, 0xCEFE, 0xCEFE },
+{ 0xCEFF, 0xCEFF, 0xCEFF },
+{ 0xCF00, 0xCF00, 0xCF00 },
+{ 0xCF01, 0xCF01, 0xCF01 },
+{ 0xCF02, 0xCF02, 0xCF02 },
+{ 0xCF03, 0xCF03, 0xCF03 },
+{ 0xCF04, 0xCF04, 0xCF04 },
+{ 0xCF05, 0xCF05, 0xCF05 },
+{ 0xCF06, 0xCF06, 0xCF06 },
+{ 0xCF07, 0xCF07, 0xCF07 },
+{ 0xCF08, 0xCF08, 0xCF08 },
+{ 0xCF09, 0xCF09, 0xCF09 },
+{ 0xCF0A, 0xCF0A, 0xCF0A },
+{ 0xCF0B, 0xCF0B, 0xCF0B },
+{ 0xCF0C, 0xCF0C, 0xCF0C },
+{ 0xCF0D, 0xCF0D, 0xCF0D },
+{ 0xCF0E, 0xCF0E, 0xCF0E },
+{ 0xCF0F, 0xCF0F, 0xCF0F },
+{ 0xCF10, 0xCF10, 0xCF10 },
+{ 0xCF11, 0xCF11, 0xCF11 },
+{ 0xCF12, 0xCF12, 0xCF12 },
+{ 0xCF13, 0xCF13, 0xCF13 },
+{ 0xCF14, 0xCF14, 0xCF14 },
+{ 0xCF15, 0xCF15, 0xCF15 },
+{ 0xCF16, 0xCF16, 0xCF16 },
+{ 0xCF17, 0xCF17, 0xCF17 },
+{ 0xCF18, 0xCF18, 0xCF18 },
+{ 0xCF19, 0xCF19, 0xCF19 },
+{ 0xCF1A, 0xCF1A, 0xCF1A },
+{ 0xCF1B, 0xCF1B, 0xCF1B },
+{ 0xCF1C, 0xCF1C, 0xCF1C },
+{ 0xCF1D, 0xCF1D, 0xCF1D },
+{ 0xCF1E, 0xCF1E, 0xCF1E },
+{ 0xCF1F, 0xCF1F, 0xCF1F },
+{ 0xCF20, 0xCF20, 0xCF20 },
+{ 0xCF21, 0xCF21, 0xCF21 },
+{ 0xCF22, 0xCF22, 0xCF22 },
+{ 0xCF23, 0xCF23, 0xCF23 },
+{ 0xCF24, 0xCF24, 0xCF24 },
+{ 0xCF25, 0xCF25, 0xCF25 },
+{ 0xCF26, 0xCF26, 0xCF26 },
+{ 0xCF27, 0xCF27, 0xCF27 },
+{ 0xCF28, 0xCF28, 0xCF28 },
+{ 0xCF29, 0xCF29, 0xCF29 },
+{ 0xCF2A, 0xCF2A, 0xCF2A },
+{ 0xCF2B, 0xCF2B, 0xCF2B },
+{ 0xCF2C, 0xCF2C, 0xCF2C },
+{ 0xCF2D, 0xCF2D, 0xCF2D },
+{ 0xCF2E, 0xCF2E, 0xCF2E },
+{ 0xCF2F, 0xCF2F, 0xCF2F },
+{ 0xCF30, 0xCF30, 0xCF30 },
+{ 0xCF31, 0xCF31, 0xCF31 },
+{ 0xCF32, 0xCF32, 0xCF32 },
+{ 0xCF33, 0xCF33, 0xCF33 },
+{ 0xCF34, 0xCF34, 0xCF34 },
+{ 0xCF35, 0xCF35, 0xCF35 },
+{ 0xCF36, 0xCF36, 0xCF36 },
+{ 0xCF37, 0xCF37, 0xCF37 },
+{ 0xCF38, 0xCF38, 0xCF38 },
+{ 0xCF39, 0xCF39, 0xCF39 },
+{ 0xCF3A, 0xCF3A, 0xCF3A },
+{ 0xCF3B, 0xCF3B, 0xCF3B },
+{ 0xCF3C, 0xCF3C, 0xCF3C },
+{ 0xCF3D, 0xCF3D, 0xCF3D },
+{ 0xCF3E, 0xCF3E, 0xCF3E },
+{ 0xCF3F, 0xCF3F, 0xCF3F },
+{ 0xCF40, 0xCF40, 0xCF40 },
+{ 0xCF41, 0xCF41, 0xCF41 },
+{ 0xCF42, 0xCF42, 0xCF42 },
+{ 0xCF43, 0xCF43, 0xCF43 },
+{ 0xCF44, 0xCF44, 0xCF44 },
+{ 0xCF45, 0xCF45, 0xCF45 },
+{ 0xCF46, 0xCF46, 0xCF46 },
+{ 0xCF47, 0xCF47, 0xCF47 },
+{ 0xCF48, 0xCF48, 0xCF48 },
+{ 0xCF49, 0xCF49, 0xCF49 },
+{ 0xCF4A, 0xCF4A, 0xCF4A },
+{ 0xCF4B, 0xCF4B, 0xCF4B },
+{ 0xCF4C, 0xCF4C, 0xCF4C },
+{ 0xCF4D, 0xCF4D, 0xCF4D },
+{ 0xCF4E, 0xCF4E, 0xCF4E },
+{ 0xCF4F, 0xCF4F, 0xCF4F },
+{ 0xCF50, 0xCF50, 0xCF50 },
+{ 0xCF51, 0xCF51, 0xCF51 },
+{ 0xCF52, 0xCF52, 0xCF52 },
+{ 0xCF53, 0xCF53, 0xCF53 },
+{ 0xCF54, 0xCF54, 0xCF54 },
+{ 0xCF55, 0xCF55, 0xCF55 },
+{ 0xCF56, 0xCF56, 0xCF56 },
+{ 0xCF57, 0xCF57, 0xCF57 },
+{ 0xCF58, 0xCF58, 0xCF58 },
+{ 0xCF59, 0xCF59, 0xCF59 },
+{ 0xCF5A, 0xCF5A, 0xCF5A },
+{ 0xCF5B, 0xCF5B, 0xCF5B },
+{ 0xCF5C, 0xCF5C, 0xCF5C },
+{ 0xCF5D, 0xCF5D, 0xCF5D },
+{ 0xCF5E, 0xCF5E, 0xCF5E },
+{ 0xCF5F, 0xCF5F, 0xCF5F },
+{ 0xCF60, 0xCF60, 0xCF60 },
+{ 0xCF61, 0xCF61, 0xCF61 },
+{ 0xCF62, 0xCF62, 0xCF62 },
+{ 0xCF63, 0xCF63, 0xCF63 },
+{ 0xCF64, 0xCF64, 0xCF64 },
+{ 0xCF65, 0xCF65, 0xCF65 },
+{ 0xCF66, 0xCF66, 0xCF66 },
+{ 0xCF67, 0xCF67, 0xCF67 },
+{ 0xCF68, 0xCF68, 0xCF68 },
+{ 0xCF69, 0xCF69, 0xCF69 },
+{ 0xCF6A, 0xCF6A, 0xCF6A },
+{ 0xCF6B, 0xCF6B, 0xCF6B },
+{ 0xCF6C, 0xCF6C, 0xCF6C },
+{ 0xCF6D, 0xCF6D, 0xCF6D },
+{ 0xCF6E, 0xCF6E, 0xCF6E },
+{ 0xCF6F, 0xCF6F, 0xCF6F },
+{ 0xCF70, 0xCF70, 0xCF70 },
+{ 0xCF71, 0xCF71, 0xCF71 },
+{ 0xCF72, 0xCF72, 0xCF72 },
+{ 0xCF73, 0xCF73, 0xCF73 },
+{ 0xCF74, 0xCF74, 0xCF74 },
+{ 0xCF75, 0xCF75, 0xCF75 },
+{ 0xCF76, 0xCF76, 0xCF76 },
+{ 0xCF77, 0xCF77, 0xCF77 },
+{ 0xCF78, 0xCF78, 0xCF78 },
+{ 0xCF79, 0xCF79, 0xCF79 },
+{ 0xCF7A, 0xCF7A, 0xCF7A },
+{ 0xCF7B, 0xCF7B, 0xCF7B },
+{ 0xCF7C, 0xCF7C, 0xCF7C },
+{ 0xCF7D, 0xCF7D, 0xCF7D },
+{ 0xCF7E, 0xCF7E, 0xCF7E },
+{ 0xCF7F, 0xCF7F, 0xCF7F },
+{ 0xCF80, 0xCF80, 0xCF80 },
+{ 0xCF81, 0xCF81, 0xCF81 },
+{ 0xCF82, 0xCF82, 0xCF82 },
+{ 0xCF83, 0xCF83, 0xCF83 },
+{ 0xCF84, 0xCF84, 0xCF84 },
+{ 0xCF85, 0xCF85, 0xCF85 },
+{ 0xCF86, 0xCF86, 0xCF86 },
+{ 0xCF87, 0xCF87, 0xCF87 },
+{ 0xCF88, 0xCF88, 0xCF88 },
+{ 0xCF89, 0xCF89, 0xCF89 },
+{ 0xCF8A, 0xCF8A, 0xCF8A },
+{ 0xCF8B, 0xCF8B, 0xCF8B },
+{ 0xCF8C, 0xCF8C, 0xCF8C },
+{ 0xCF8D, 0xCF8D, 0xCF8D },
+{ 0xCF8E, 0xCF8E, 0xCF8E },
+{ 0xCF8F, 0xCF8F, 0xCF8F },
+{ 0xCF90, 0xCF90, 0xCF90 },
+{ 0xCF91, 0xCF91, 0xCF91 },
+{ 0xCF92, 0xCF92, 0xCF92 },
+{ 0xCF93, 0xCF93, 0xCF93 },
+{ 0xCF94, 0xCF94, 0xCF94 },
+{ 0xCF95, 0xCF95, 0xCF95 },
+{ 0xCF96, 0xCF96, 0xCF96 },
+{ 0xCF97, 0xCF97, 0xCF97 },
+{ 0xCF98, 0xCF98, 0xCF98 },
+{ 0xCF99, 0xCF99, 0xCF99 },
+{ 0xCF9A, 0xCF9A, 0xCF9A },
+{ 0xCF9B, 0xCF9B, 0xCF9B },
+{ 0xCF9C, 0xCF9C, 0xCF9C },
+{ 0xCF9D, 0xCF9D, 0xCF9D },
+{ 0xCF9E, 0xCF9E, 0xCF9E },
+{ 0xCF9F, 0xCF9F, 0xCF9F },
+{ 0xCFA0, 0xCFA0, 0xCFA0 },
+{ 0xCFA1, 0xCFA1, 0xCFA1 },
+{ 0xCFA2, 0xCFA2, 0xCFA2 },
+{ 0xCFA3, 0xCFA3, 0xCFA3 },
+{ 0xCFA4, 0xCFA4, 0xCFA4 },
+{ 0xCFA5, 0xCFA5, 0xCFA5 },
+{ 0xCFA6, 0xCFA6, 0xCFA6 },
+{ 0xCFA7, 0xCFA7, 0xCFA7 },
+{ 0xCFA8, 0xCFA8, 0xCFA8 },
+{ 0xCFA9, 0xCFA9, 0xCFA9 },
+{ 0xCFAA, 0xCFAA, 0xCFAA },
+{ 0xCFAB, 0xCFAB, 0xCFAB },
+{ 0xCFAC, 0xCFAC, 0xCFAC },
+{ 0xCFAD, 0xCFAD, 0xCFAD },
+{ 0xCFAE, 0xCFAE, 0xCFAE },
+{ 0xCFAF, 0xCFAF, 0xCFAF },
+{ 0xCFB0, 0xCFB0, 0xCFB0 },
+{ 0xCFB1, 0xCFB1, 0xCFB1 },
+{ 0xCFB2, 0xCFB2, 0xCFB2 },
+{ 0xCFB3, 0xCFB3, 0xCFB3 },
+{ 0xCFB4, 0xCFB4, 0xCFB4 },
+{ 0xCFB5, 0xCFB5, 0xCFB5 },
+{ 0xCFB6, 0xCFB6, 0xCFB6 },
+{ 0xCFB7, 0xCFB7, 0xCFB7 },
+{ 0xCFB8, 0xCFB8, 0xCFB8 },
+{ 0xCFB9, 0xCFB9, 0xCFB9 },
+{ 0xCFBA, 0xCFBA, 0xCFBA },
+{ 0xCFBB, 0xCFBB, 0xCFBB },
+{ 0xCFBC, 0xCFBC, 0xCFBC },
+{ 0xCFBD, 0xCFBD, 0xCFBD },
+{ 0xCFBE, 0xCFBE, 0xCFBE },
+{ 0xCFBF, 0xCFBF, 0xCFBF },
+{ 0xCFC0, 0xCFC0, 0xCFC0 },
+{ 0xCFC1, 0xCFC1, 0xCFC1 },
+{ 0xCFC2, 0xCFC2, 0xCFC2 },
+{ 0xCFC3, 0xCFC3, 0xCFC3 },
+{ 0xCFC4, 0xCFC4, 0xCFC4 },
+{ 0xCFC5, 0xCFC5, 0xCFC5 },
+{ 0xCFC6, 0xCFC6, 0xCFC6 },
+{ 0xCFC7, 0xCFC7, 0xCFC7 },
+{ 0xCFC8, 0xCFC8, 0xCFC8 },
+{ 0xCFC9, 0xCFC9, 0xCFC9 },
+{ 0xCFCA, 0xCFCA, 0xCFCA },
+{ 0xCFCB, 0xCFCB, 0xCFCB },
+{ 0xCFCC, 0xCFCC, 0xCFCC },
+{ 0xCFCD, 0xCFCD, 0xCFCD },
+{ 0xCFCE, 0xCFCE, 0xCFCE },
+{ 0xCFCF, 0xCFCF, 0xCFCF },
+{ 0xCFD0, 0xCFD0, 0xCFD0 },
+{ 0xCFD1, 0xCFD1, 0xCFD1 },
+{ 0xCFD2, 0xCFD2, 0xCFD2 },
+{ 0xCFD3, 0xCFD3, 0xCFD3 },
+{ 0xCFD4, 0xCFD4, 0xCFD4 },
+{ 0xCFD5, 0xCFD5, 0xCFD5 },
+{ 0xCFD6, 0xCFD6, 0xCFD6 },
+{ 0xCFD7, 0xCFD7, 0xCFD7 },
+{ 0xCFD8, 0xCFD8, 0xCFD8 },
+{ 0xCFD9, 0xCFD9, 0xCFD9 },
+{ 0xCFDA, 0xCFDA, 0xCFDA },
+{ 0xCFDB, 0xCFDB, 0xCFDB },
+{ 0xCFDC, 0xCFDC, 0xCFDC },
+{ 0xCFDD, 0xCFDD, 0xCFDD },
+{ 0xCFDE, 0xCFDE, 0xCFDE },
+{ 0xCFDF, 0xCFDF, 0xCFDF },
+{ 0xCFE0, 0xCFE0, 0xCFE0 },
+{ 0xCFE1, 0xCFE1, 0xCFE1 },
+{ 0xCFE2, 0xCFE2, 0xCFE2 },
+{ 0xCFE3, 0xCFE3, 0xCFE3 },
+{ 0xCFE4, 0xCFE4, 0xCFE4 },
+{ 0xCFE5, 0xCFE5, 0xCFE5 },
+{ 0xCFE6, 0xCFE6, 0xCFE6 },
+{ 0xCFE7, 0xCFE7, 0xCFE7 },
+{ 0xCFE8, 0xCFE8, 0xCFE8 },
+{ 0xCFE9, 0xCFE9, 0xCFE9 },
+{ 0xCFEA, 0xCFEA, 0xCFEA },
+{ 0xCFEB, 0xCFEB, 0xCFEB },
+{ 0xCFEC, 0xCFEC, 0xCFEC },
+{ 0xCFED, 0xCFED, 0xCFED },
+{ 0xCFEE, 0xCFEE, 0xCFEE },
+{ 0xCFEF, 0xCFEF, 0xCFEF },
+{ 0xCFF0, 0xCFF0, 0xCFF0 },
+{ 0xCFF1, 0xCFF1, 0xCFF1 },
+{ 0xCFF2, 0xCFF2, 0xCFF2 },
+{ 0xCFF3, 0xCFF3, 0xCFF3 },
+{ 0xCFF4, 0xCFF4, 0xCFF4 },
+{ 0xCFF5, 0xCFF5, 0xCFF5 },
+{ 0xCFF6, 0xCFF6, 0xCFF6 },
+{ 0xCFF7, 0xCFF7, 0xCFF7 },
+{ 0xCFF8, 0xCFF8, 0xCFF8 },
+{ 0xCFF9, 0xCFF9, 0xCFF9 },
+{ 0xCFFA, 0xCFFA, 0xCFFA },
+{ 0xCFFB, 0xCFFB, 0xCFFB },
+{ 0xCFFC, 0xCFFC, 0xCFFC },
+{ 0xCFFD, 0xCFFD, 0xCFFD },
+{ 0xCFFE, 0xCFFE, 0xCFFE },
+{ 0xCFFF, 0xCFFF, 0xCFFF },
+{ 0xD000, 0xD000, 0xD000 },
+{ 0xD001, 0xD001, 0xD001 },
+{ 0xD002, 0xD002, 0xD002 },
+{ 0xD003, 0xD003, 0xD003 },
+{ 0xD004, 0xD004, 0xD004 },
+{ 0xD005, 0xD005, 0xD005 },
+{ 0xD006, 0xD006, 0xD006 },
+{ 0xD007, 0xD007, 0xD007 },
+{ 0xD008, 0xD008, 0xD008 },
+{ 0xD009, 0xD009, 0xD009 },
+{ 0xD00A, 0xD00A, 0xD00A },
+{ 0xD00B, 0xD00B, 0xD00B },
+{ 0xD00C, 0xD00C, 0xD00C },
+{ 0xD00D, 0xD00D, 0xD00D },
+{ 0xD00E, 0xD00E, 0xD00E },
+{ 0xD00F, 0xD00F, 0xD00F },
+{ 0xD010, 0xD010, 0xD010 },
+{ 0xD011, 0xD011, 0xD011 },
+{ 0xD012, 0xD012, 0xD012 },
+{ 0xD013, 0xD013, 0xD013 },
+{ 0xD014, 0xD014, 0xD014 },
+{ 0xD015, 0xD015, 0xD015 },
+{ 0xD016, 0xD016, 0xD016 },
+{ 0xD017, 0xD017, 0xD017 },
+{ 0xD018, 0xD018, 0xD018 },
+{ 0xD019, 0xD019, 0xD019 },
+{ 0xD01A, 0xD01A, 0xD01A },
+{ 0xD01B, 0xD01B, 0xD01B },
+{ 0xD01C, 0xD01C, 0xD01C },
+{ 0xD01D, 0xD01D, 0xD01D },
+{ 0xD01E, 0xD01E, 0xD01E },
+{ 0xD01F, 0xD01F, 0xD01F },
+{ 0xD020, 0xD020, 0xD020 },
+{ 0xD021, 0xD021, 0xD021 },
+{ 0xD022, 0xD022, 0xD022 },
+{ 0xD023, 0xD023, 0xD023 },
+{ 0xD024, 0xD024, 0xD024 },
+{ 0xD025, 0xD025, 0xD025 },
+{ 0xD026, 0xD026, 0xD026 },
+{ 0xD027, 0xD027, 0xD027 },
+{ 0xD028, 0xD028, 0xD028 },
+{ 0xD029, 0xD029, 0xD029 },
+{ 0xD02A, 0xD02A, 0xD02A },
+{ 0xD02B, 0xD02B, 0xD02B },
+{ 0xD02C, 0xD02C, 0xD02C },
+{ 0xD02D, 0xD02D, 0xD02D },
+{ 0xD02E, 0xD02E, 0xD02E },
+{ 0xD02F, 0xD02F, 0xD02F },
+{ 0xD030, 0xD030, 0xD030 },
+{ 0xD031, 0xD031, 0xD031 },
+{ 0xD032, 0xD032, 0xD032 },
+{ 0xD033, 0xD033, 0xD033 },
+{ 0xD034, 0xD034, 0xD034 },
+{ 0xD035, 0xD035, 0xD035 },
+{ 0xD036, 0xD036, 0xD036 },
+{ 0xD037, 0xD037, 0xD037 },
+{ 0xD038, 0xD038, 0xD038 },
+{ 0xD039, 0xD039, 0xD039 },
+{ 0xD03A, 0xD03A, 0xD03A },
+{ 0xD03B, 0xD03B, 0xD03B },
+{ 0xD03C, 0xD03C, 0xD03C },
+{ 0xD03D, 0xD03D, 0xD03D },
+{ 0xD03E, 0xD03E, 0xD03E },
+{ 0xD03F, 0xD03F, 0xD03F },
+{ 0xD040, 0xD040, 0xD040 },
+{ 0xD041, 0xD041, 0xD041 },
+{ 0xD042, 0xD042, 0xD042 },
+{ 0xD043, 0xD043, 0xD043 },
+{ 0xD044, 0xD044, 0xD044 },
+{ 0xD045, 0xD045, 0xD045 },
+{ 0xD046, 0xD046, 0xD046 },
+{ 0xD047, 0xD047, 0xD047 },
+{ 0xD048, 0xD048, 0xD048 },
+{ 0xD049, 0xD049, 0xD049 },
+{ 0xD04A, 0xD04A, 0xD04A },
+{ 0xD04B, 0xD04B, 0xD04B },
+{ 0xD04C, 0xD04C, 0xD04C },
+{ 0xD04D, 0xD04D, 0xD04D },
+{ 0xD04E, 0xD04E, 0xD04E },
+{ 0xD04F, 0xD04F, 0xD04F },
+{ 0xD050, 0xD050, 0xD050 },
+{ 0xD051, 0xD051, 0xD051 },
+{ 0xD052, 0xD052, 0xD052 },
+{ 0xD053, 0xD053, 0xD053 },
+{ 0xD054, 0xD054, 0xD054 },
+{ 0xD055, 0xD055, 0xD055 },
+{ 0xD056, 0xD056, 0xD056 },
+{ 0xD057, 0xD057, 0xD057 },
+{ 0xD058, 0xD058, 0xD058 },
+{ 0xD059, 0xD059, 0xD059 },
+{ 0xD05A, 0xD05A, 0xD05A },
+{ 0xD05B, 0xD05B, 0xD05B },
+{ 0xD05C, 0xD05C, 0xD05C },
+{ 0xD05D, 0xD05D, 0xD05D },
+{ 0xD05E, 0xD05E, 0xD05E },
+{ 0xD05F, 0xD05F, 0xD05F },
+{ 0xD060, 0xD060, 0xD060 },
+{ 0xD061, 0xD061, 0xD061 },
+{ 0xD062, 0xD062, 0xD062 },
+{ 0xD063, 0xD063, 0xD063 },
+{ 0xD064, 0xD064, 0xD064 },
+{ 0xD065, 0xD065, 0xD065 },
+{ 0xD066, 0xD066, 0xD066 },
+{ 0xD067, 0xD067, 0xD067 },
+{ 0xD068, 0xD068, 0xD068 },
+{ 0xD069, 0xD069, 0xD069 },
+{ 0xD06A, 0xD06A, 0xD06A },
+{ 0xD06B, 0xD06B, 0xD06B },
+{ 0xD06C, 0xD06C, 0xD06C },
+{ 0xD06D, 0xD06D, 0xD06D },
+{ 0xD06E, 0xD06E, 0xD06E },
+{ 0xD06F, 0xD06F, 0xD06F },
+{ 0xD070, 0xD070, 0xD070 },
+{ 0xD071, 0xD071, 0xD071 },
+{ 0xD072, 0xD072, 0xD072 },
+{ 0xD073, 0xD073, 0xD073 },
+{ 0xD074, 0xD074, 0xD074 },
+{ 0xD075, 0xD075, 0xD075 },
+{ 0xD076, 0xD076, 0xD076 },
+{ 0xD077, 0xD077, 0xD077 },
+{ 0xD078, 0xD078, 0xD078 },
+{ 0xD079, 0xD079, 0xD079 },
+{ 0xD07A, 0xD07A, 0xD07A },
+{ 0xD07B, 0xD07B, 0xD07B },
+{ 0xD07C, 0xD07C, 0xD07C },
+{ 0xD07D, 0xD07D, 0xD07D },
+{ 0xD07E, 0xD07E, 0xD07E },
+{ 0xD07F, 0xD07F, 0xD07F },
+{ 0xD080, 0xD080, 0xD080 },
+{ 0xD081, 0xD081, 0xD081 },
+{ 0xD082, 0xD082, 0xD082 },
+{ 0xD083, 0xD083, 0xD083 },
+{ 0xD084, 0xD084, 0xD084 },
+{ 0xD085, 0xD085, 0xD085 },
+{ 0xD086, 0xD086, 0xD086 },
+{ 0xD087, 0xD087, 0xD087 },
+{ 0xD088, 0xD088, 0xD088 },
+{ 0xD089, 0xD089, 0xD089 },
+{ 0xD08A, 0xD08A, 0xD08A },
+{ 0xD08B, 0xD08B, 0xD08B },
+{ 0xD08C, 0xD08C, 0xD08C },
+{ 0xD08D, 0xD08D, 0xD08D },
+{ 0xD08E, 0xD08E, 0xD08E },
+{ 0xD08F, 0xD08F, 0xD08F },
+{ 0xD090, 0xD090, 0xD090 },
+{ 0xD091, 0xD091, 0xD091 },
+{ 0xD092, 0xD092, 0xD092 },
+{ 0xD093, 0xD093, 0xD093 },
+{ 0xD094, 0xD094, 0xD094 },
+{ 0xD095, 0xD095, 0xD095 },
+{ 0xD096, 0xD096, 0xD096 },
+{ 0xD097, 0xD097, 0xD097 },
+{ 0xD098, 0xD098, 0xD098 },
+{ 0xD099, 0xD099, 0xD099 },
+{ 0xD09A, 0xD09A, 0xD09A },
+{ 0xD09B, 0xD09B, 0xD09B },
+{ 0xD09C, 0xD09C, 0xD09C },
+{ 0xD09D, 0xD09D, 0xD09D },
+{ 0xD09E, 0xD09E, 0xD09E },
+{ 0xD09F, 0xD09F, 0xD09F },
+{ 0xD0A0, 0xD0A0, 0xD0A0 },
+{ 0xD0A1, 0xD0A1, 0xD0A1 },
+{ 0xD0A2, 0xD0A2, 0xD0A2 },
+{ 0xD0A3, 0xD0A3, 0xD0A3 },
+{ 0xD0A4, 0xD0A4, 0xD0A4 },
+{ 0xD0A5, 0xD0A5, 0xD0A5 },
+{ 0xD0A6, 0xD0A6, 0xD0A6 },
+{ 0xD0A7, 0xD0A7, 0xD0A7 },
+{ 0xD0A8, 0xD0A8, 0xD0A8 },
+{ 0xD0A9, 0xD0A9, 0xD0A9 },
+{ 0xD0AA, 0xD0AA, 0xD0AA },
+{ 0xD0AB, 0xD0AB, 0xD0AB },
+{ 0xD0AC, 0xD0AC, 0xD0AC },
+{ 0xD0AD, 0xD0AD, 0xD0AD },
+{ 0xD0AE, 0xD0AE, 0xD0AE },
+{ 0xD0AF, 0xD0AF, 0xD0AF },
+{ 0xD0B0, 0xD0B0, 0xD0B0 },
+{ 0xD0B1, 0xD0B1, 0xD0B1 },
+{ 0xD0B2, 0xD0B2, 0xD0B2 },
+{ 0xD0B3, 0xD0B3, 0xD0B3 },
+{ 0xD0B4, 0xD0B4, 0xD0B4 },
+{ 0xD0B5, 0xD0B5, 0xD0B5 },
+{ 0xD0B6, 0xD0B6, 0xD0B6 },
+{ 0xD0B7, 0xD0B7, 0xD0B7 },
+{ 0xD0B8, 0xD0B8, 0xD0B8 },
+{ 0xD0B9, 0xD0B9, 0xD0B9 },
+{ 0xD0BA, 0xD0BA, 0xD0BA },
+{ 0xD0BB, 0xD0BB, 0xD0BB },
+{ 0xD0BC, 0xD0BC, 0xD0BC },
+{ 0xD0BD, 0xD0BD, 0xD0BD },
+{ 0xD0BE, 0xD0BE, 0xD0BE },
+{ 0xD0BF, 0xD0BF, 0xD0BF },
+{ 0xD0C0, 0xD0C0, 0xD0C0 },
+{ 0xD0C1, 0xD0C1, 0xD0C1 },
+{ 0xD0C2, 0xD0C2, 0xD0C2 },
+{ 0xD0C3, 0xD0C3, 0xD0C3 },
+{ 0xD0C4, 0xD0C4, 0xD0C4 },
+{ 0xD0C5, 0xD0C5, 0xD0C5 },
+{ 0xD0C6, 0xD0C6, 0xD0C6 },
+{ 0xD0C7, 0xD0C7, 0xD0C7 },
+{ 0xD0C8, 0xD0C8, 0xD0C8 },
+{ 0xD0C9, 0xD0C9, 0xD0C9 },
+{ 0xD0CA, 0xD0CA, 0xD0CA },
+{ 0xD0CB, 0xD0CB, 0xD0CB },
+{ 0xD0CC, 0xD0CC, 0xD0CC },
+{ 0xD0CD, 0xD0CD, 0xD0CD },
+{ 0xD0CE, 0xD0CE, 0xD0CE },
+{ 0xD0CF, 0xD0CF, 0xD0CF },
+{ 0xD0D0, 0xD0D0, 0xD0D0 },
+{ 0xD0D1, 0xD0D1, 0xD0D1 },
+{ 0xD0D2, 0xD0D2, 0xD0D2 },
+{ 0xD0D3, 0xD0D3, 0xD0D3 },
+{ 0xD0D4, 0xD0D4, 0xD0D4 },
+{ 0xD0D5, 0xD0D5, 0xD0D5 },
+{ 0xD0D6, 0xD0D6, 0xD0D6 },
+{ 0xD0D7, 0xD0D7, 0xD0D7 },
+{ 0xD0D8, 0xD0D8, 0xD0D8 },
+{ 0xD0D9, 0xD0D9, 0xD0D9 },
+{ 0xD0DA, 0xD0DA, 0xD0DA },
+{ 0xD0DB, 0xD0DB, 0xD0DB },
+{ 0xD0DC, 0xD0DC, 0xD0DC },
+{ 0xD0DD, 0xD0DD, 0xD0DD },
+{ 0xD0DE, 0xD0DE, 0xD0DE },
+{ 0xD0DF, 0xD0DF, 0xD0DF },
+{ 0xD0E0, 0xD0E0, 0xD0E0 },
+{ 0xD0E1, 0xD0E1, 0xD0E1 },
+{ 0xD0E2, 0xD0E2, 0xD0E2 },
+{ 0xD0E3, 0xD0E3, 0xD0E3 },
+{ 0xD0E4, 0xD0E4, 0xD0E4 },
+{ 0xD0E5, 0xD0E5, 0xD0E5 },
+{ 0xD0E6, 0xD0E6, 0xD0E6 },
+{ 0xD0E7, 0xD0E7, 0xD0E7 },
+{ 0xD0E8, 0xD0E8, 0xD0E8 },
+{ 0xD0E9, 0xD0E9, 0xD0E9 },
+{ 0xD0EA, 0xD0EA, 0xD0EA },
+{ 0xD0EB, 0xD0EB, 0xD0EB },
+{ 0xD0EC, 0xD0EC, 0xD0EC },
+{ 0xD0ED, 0xD0ED, 0xD0ED },
+{ 0xD0EE, 0xD0EE, 0xD0EE },
+{ 0xD0EF, 0xD0EF, 0xD0EF },
+{ 0xD0F0, 0xD0F0, 0xD0F0 },
+{ 0xD0F1, 0xD0F1, 0xD0F1 },
+{ 0xD0F2, 0xD0F2, 0xD0F2 },
+{ 0xD0F3, 0xD0F3, 0xD0F3 },
+{ 0xD0F4, 0xD0F4, 0xD0F4 },
+{ 0xD0F5, 0xD0F5, 0xD0F5 },
+{ 0xD0F6, 0xD0F6, 0xD0F6 },
+{ 0xD0F7, 0xD0F7, 0xD0F7 },
+{ 0xD0F8, 0xD0F8, 0xD0F8 },
+{ 0xD0F9, 0xD0F9, 0xD0F9 },
+{ 0xD0FA, 0xD0FA, 0xD0FA },
+{ 0xD0FB, 0xD0FB, 0xD0FB },
+{ 0xD0FC, 0xD0FC, 0xD0FC },
+{ 0xD0FD, 0xD0FD, 0xD0FD },
+{ 0xD0FE, 0xD0FE, 0xD0FE },
+{ 0xD0FF, 0xD0FF, 0xD0FF },
+{ 0xD100, 0xD100, 0xD100 },
+{ 0xD101, 0xD101, 0xD101 },
+{ 0xD102, 0xD102, 0xD102 },
+{ 0xD103, 0xD103, 0xD103 },
+{ 0xD104, 0xD104, 0xD104 },
+{ 0xD105, 0xD105, 0xD105 },
+{ 0xD106, 0xD106, 0xD106 },
+{ 0xD107, 0xD107, 0xD107 },
+{ 0xD108, 0xD108, 0xD108 },
+{ 0xD109, 0xD109, 0xD109 },
+{ 0xD10A, 0xD10A, 0xD10A },
+{ 0xD10B, 0xD10B, 0xD10B },
+{ 0xD10C, 0xD10C, 0xD10C },
+{ 0xD10D, 0xD10D, 0xD10D },
+{ 0xD10E, 0xD10E, 0xD10E },
+{ 0xD10F, 0xD10F, 0xD10F },
+{ 0xD110, 0xD110, 0xD110 },
+{ 0xD111, 0xD111, 0xD111 },
+{ 0xD112, 0xD112, 0xD112 },
+{ 0xD113, 0xD113, 0xD113 },
+{ 0xD114, 0xD114, 0xD114 },
+{ 0xD115, 0xD115, 0xD115 },
+{ 0xD116, 0xD116, 0xD116 },
+{ 0xD117, 0xD117, 0xD117 },
+{ 0xD118, 0xD118, 0xD118 },
+{ 0xD119, 0xD119, 0xD119 },
+{ 0xD11A, 0xD11A, 0xD11A },
+{ 0xD11B, 0xD11B, 0xD11B },
+{ 0xD11C, 0xD11C, 0xD11C },
+{ 0xD11D, 0xD11D, 0xD11D },
+{ 0xD11E, 0xD11E, 0xD11E },
+{ 0xD11F, 0xD11F, 0xD11F },
+{ 0xD120, 0xD120, 0xD120 },
+{ 0xD121, 0xD121, 0xD121 },
+{ 0xD122, 0xD122, 0xD122 },
+{ 0xD123, 0xD123, 0xD123 },
+{ 0xD124, 0xD124, 0xD124 },
+{ 0xD125, 0xD125, 0xD125 },
+{ 0xD126, 0xD126, 0xD126 },
+{ 0xD127, 0xD127, 0xD127 },
+{ 0xD128, 0xD128, 0xD128 },
+{ 0xD129, 0xD129, 0xD129 },
+{ 0xD12A, 0xD12A, 0xD12A },
+{ 0xD12B, 0xD12B, 0xD12B },
+{ 0xD12C, 0xD12C, 0xD12C },
+{ 0xD12D, 0xD12D, 0xD12D },
+{ 0xD12E, 0xD12E, 0xD12E },
+{ 0xD12F, 0xD12F, 0xD12F },
+{ 0xD130, 0xD130, 0xD130 },
+{ 0xD131, 0xD131, 0xD131 },
+{ 0xD132, 0xD132, 0xD132 },
+{ 0xD133, 0xD133, 0xD133 },
+{ 0xD134, 0xD134, 0xD134 },
+{ 0xD135, 0xD135, 0xD135 },
+{ 0xD136, 0xD136, 0xD136 },
+{ 0xD137, 0xD137, 0xD137 },
+{ 0xD138, 0xD138, 0xD138 },
+{ 0xD139, 0xD139, 0xD139 },
+{ 0xD13A, 0xD13A, 0xD13A },
+{ 0xD13B, 0xD13B, 0xD13B },
+{ 0xD13C, 0xD13C, 0xD13C },
+{ 0xD13D, 0xD13D, 0xD13D },
+{ 0xD13E, 0xD13E, 0xD13E },
+{ 0xD13F, 0xD13F, 0xD13F },
+{ 0xD140, 0xD140, 0xD140 },
+{ 0xD141, 0xD141, 0xD141 },
+{ 0xD142, 0xD142, 0xD142 },
+{ 0xD143, 0xD143, 0xD143 },
+{ 0xD144, 0xD144, 0xD144 },
+{ 0xD145, 0xD145, 0xD145 },
+{ 0xD146, 0xD146, 0xD146 },
+{ 0xD147, 0xD147, 0xD147 },
+{ 0xD148, 0xD148, 0xD148 },
+{ 0xD149, 0xD149, 0xD149 },
+{ 0xD14A, 0xD14A, 0xD14A },
+{ 0xD14B, 0xD14B, 0xD14B },
+{ 0xD14C, 0xD14C, 0xD14C },
+{ 0xD14D, 0xD14D, 0xD14D },
+{ 0xD14E, 0xD14E, 0xD14E },
+{ 0xD14F, 0xD14F, 0xD14F },
+{ 0xD150, 0xD150, 0xD150 },
+{ 0xD151, 0xD151, 0xD151 },
+{ 0xD152, 0xD152, 0xD152 },
+{ 0xD153, 0xD153, 0xD153 },
+{ 0xD154, 0xD154, 0xD154 },
+{ 0xD155, 0xD155, 0xD155 },
+{ 0xD156, 0xD156, 0xD156 },
+{ 0xD157, 0xD157, 0xD157 },
+{ 0xD158, 0xD158, 0xD158 },
+{ 0xD159, 0xD159, 0xD159 },
+{ 0xD15A, 0xD15A, 0xD15A },
+{ 0xD15B, 0xD15B, 0xD15B },
+{ 0xD15C, 0xD15C, 0xD15C },
+{ 0xD15D, 0xD15D, 0xD15D },
+{ 0xD15E, 0xD15E, 0xD15E },
+{ 0xD15F, 0xD15F, 0xD15F },
+{ 0xD160, 0xD160, 0xD160 },
+{ 0xD161, 0xD161, 0xD161 },
+{ 0xD162, 0xD162, 0xD162 },
+{ 0xD163, 0xD163, 0xD163 },
+{ 0xD164, 0xD164, 0xD164 },
+{ 0xD165, 0xD165, 0xD165 },
+{ 0xD166, 0xD166, 0xD166 },
+{ 0xD167, 0xD167, 0xD167 },
+{ 0xD168, 0xD168, 0xD168 },
+{ 0xD169, 0xD169, 0xD169 },
+{ 0xD16A, 0xD16A, 0xD16A },
+{ 0xD16B, 0xD16B, 0xD16B },
+{ 0xD16C, 0xD16C, 0xD16C },
+{ 0xD16D, 0xD16D, 0xD16D },
+{ 0xD16E, 0xD16E, 0xD16E },
+{ 0xD16F, 0xD16F, 0xD16F },
+{ 0xD170, 0xD170, 0xD170 },
+{ 0xD171, 0xD171, 0xD171 },
+{ 0xD172, 0xD172, 0xD172 },
+{ 0xD173, 0xD173, 0xD173 },
+{ 0xD174, 0xD174, 0xD174 },
+{ 0xD175, 0xD175, 0xD175 },
+{ 0xD176, 0xD176, 0xD176 },
+{ 0xD177, 0xD177, 0xD177 },
+{ 0xD178, 0xD178, 0xD178 },
+{ 0xD179, 0xD179, 0xD179 },
+{ 0xD17A, 0xD17A, 0xD17A },
+{ 0xD17B, 0xD17B, 0xD17B },
+{ 0xD17C, 0xD17C, 0xD17C },
+{ 0xD17D, 0xD17D, 0xD17D },
+{ 0xD17E, 0xD17E, 0xD17E },
+{ 0xD17F, 0xD17F, 0xD17F },
+{ 0xD180, 0xD180, 0xD180 },
+{ 0xD181, 0xD181, 0xD181 },
+{ 0xD182, 0xD182, 0xD182 },
+{ 0xD183, 0xD183, 0xD183 },
+{ 0xD184, 0xD184, 0xD184 },
+{ 0xD185, 0xD185, 0xD185 },
+{ 0xD186, 0xD186, 0xD186 },
+{ 0xD187, 0xD187, 0xD187 },
+{ 0xD188, 0xD188, 0xD188 },
+{ 0xD189, 0xD189, 0xD189 },
+{ 0xD18A, 0xD18A, 0xD18A },
+{ 0xD18B, 0xD18B, 0xD18B },
+{ 0xD18C, 0xD18C, 0xD18C },
+{ 0xD18D, 0xD18D, 0xD18D },
+{ 0xD18E, 0xD18E, 0xD18E },
+{ 0xD18F, 0xD18F, 0xD18F },
+{ 0xD190, 0xD190, 0xD190 },
+{ 0xD191, 0xD191, 0xD191 },
+{ 0xD192, 0xD192, 0xD192 },
+{ 0xD193, 0xD193, 0xD193 },
+{ 0xD194, 0xD194, 0xD194 },
+{ 0xD195, 0xD195, 0xD195 },
+{ 0xD196, 0xD196, 0xD196 },
+{ 0xD197, 0xD197, 0xD197 },
+{ 0xD198, 0xD198, 0xD198 },
+{ 0xD199, 0xD199, 0xD199 },
+{ 0xD19A, 0xD19A, 0xD19A },
+{ 0xD19B, 0xD19B, 0xD19B },
+{ 0xD19C, 0xD19C, 0xD19C },
+{ 0xD19D, 0xD19D, 0xD19D },
+{ 0xD19E, 0xD19E, 0xD19E },
+{ 0xD19F, 0xD19F, 0xD19F },
+{ 0xD1A0, 0xD1A0, 0xD1A0 },
+{ 0xD1A1, 0xD1A1, 0xD1A1 },
+{ 0xD1A2, 0xD1A2, 0xD1A2 },
+{ 0xD1A3, 0xD1A3, 0xD1A3 },
+{ 0xD1A4, 0xD1A4, 0xD1A4 },
+{ 0xD1A5, 0xD1A5, 0xD1A5 },
+{ 0xD1A6, 0xD1A6, 0xD1A6 },
+{ 0xD1A7, 0xD1A7, 0xD1A7 },
+{ 0xD1A8, 0xD1A8, 0xD1A8 },
+{ 0xD1A9, 0xD1A9, 0xD1A9 },
+{ 0xD1AA, 0xD1AA, 0xD1AA },
+{ 0xD1AB, 0xD1AB, 0xD1AB },
+{ 0xD1AC, 0xD1AC, 0xD1AC },
+{ 0xD1AD, 0xD1AD, 0xD1AD },
+{ 0xD1AE, 0xD1AE, 0xD1AE },
+{ 0xD1AF, 0xD1AF, 0xD1AF },
+{ 0xD1B0, 0xD1B0, 0xD1B0 },
+{ 0xD1B1, 0xD1B1, 0xD1B1 },
+{ 0xD1B2, 0xD1B2, 0xD1B2 },
+{ 0xD1B3, 0xD1B3, 0xD1B3 },
+{ 0xD1B4, 0xD1B4, 0xD1B4 },
+{ 0xD1B5, 0xD1B5, 0xD1B5 },
+{ 0xD1B6, 0xD1B6, 0xD1B6 },
+{ 0xD1B7, 0xD1B7, 0xD1B7 },
+{ 0xD1B8, 0xD1B8, 0xD1B8 },
+{ 0xD1B9, 0xD1B9, 0xD1B9 },
+{ 0xD1BA, 0xD1BA, 0xD1BA },
+{ 0xD1BB, 0xD1BB, 0xD1BB },
+{ 0xD1BC, 0xD1BC, 0xD1BC },
+{ 0xD1BD, 0xD1BD, 0xD1BD },
+{ 0xD1BE, 0xD1BE, 0xD1BE },
+{ 0xD1BF, 0xD1BF, 0xD1BF },
+{ 0xD1C0, 0xD1C0, 0xD1C0 },
+{ 0xD1C1, 0xD1C1, 0xD1C1 },
+{ 0xD1C2, 0xD1C2, 0xD1C2 },
+{ 0xD1C3, 0xD1C3, 0xD1C3 },
+{ 0xD1C4, 0xD1C4, 0xD1C4 },
+{ 0xD1C5, 0xD1C5, 0xD1C5 },
+{ 0xD1C6, 0xD1C6, 0xD1C6 },
+{ 0xD1C7, 0xD1C7, 0xD1C7 },
+{ 0xD1C8, 0xD1C8, 0xD1C8 },
+{ 0xD1C9, 0xD1C9, 0xD1C9 },
+{ 0xD1CA, 0xD1CA, 0xD1CA },
+{ 0xD1CB, 0xD1CB, 0xD1CB },
+{ 0xD1CC, 0xD1CC, 0xD1CC },
+{ 0xD1CD, 0xD1CD, 0xD1CD },
+{ 0xD1CE, 0xD1CE, 0xD1CE },
+{ 0xD1CF, 0xD1CF, 0xD1CF },
+{ 0xD1D0, 0xD1D0, 0xD1D0 },
+{ 0xD1D1, 0xD1D1, 0xD1D1 },
+{ 0xD1D2, 0xD1D2, 0xD1D2 },
+{ 0xD1D3, 0xD1D3, 0xD1D3 },
+{ 0xD1D4, 0xD1D4, 0xD1D4 },
+{ 0xD1D5, 0xD1D5, 0xD1D5 },
+{ 0xD1D6, 0xD1D6, 0xD1D6 },
+{ 0xD1D7, 0xD1D7, 0xD1D7 },
+{ 0xD1D8, 0xD1D8, 0xD1D8 },
+{ 0xD1D9, 0xD1D9, 0xD1D9 },
+{ 0xD1DA, 0xD1DA, 0xD1DA },
+{ 0xD1DB, 0xD1DB, 0xD1DB },
+{ 0xD1DC, 0xD1DC, 0xD1DC },
+{ 0xD1DD, 0xD1DD, 0xD1DD },
+{ 0xD1DE, 0xD1DE, 0xD1DE },
+{ 0xD1DF, 0xD1DF, 0xD1DF },
+{ 0xD1E0, 0xD1E0, 0xD1E0 },
+{ 0xD1E1, 0xD1E1, 0xD1E1 },
+{ 0xD1E2, 0xD1E2, 0xD1E2 },
+{ 0xD1E3, 0xD1E3, 0xD1E3 },
+{ 0xD1E4, 0xD1E4, 0xD1E4 },
+{ 0xD1E5, 0xD1E5, 0xD1E5 },
+{ 0xD1E6, 0xD1E6, 0xD1E6 },
+{ 0xD1E7, 0xD1E7, 0xD1E7 },
+{ 0xD1E8, 0xD1E8, 0xD1E8 },
+{ 0xD1E9, 0xD1E9, 0xD1E9 },
+{ 0xD1EA, 0xD1EA, 0xD1EA },
+{ 0xD1EB, 0xD1EB, 0xD1EB },
+{ 0xD1EC, 0xD1EC, 0xD1EC },
+{ 0xD1ED, 0xD1ED, 0xD1ED },
+{ 0xD1EE, 0xD1EE, 0xD1EE },
+{ 0xD1EF, 0xD1EF, 0xD1EF },
+{ 0xD1F0, 0xD1F0, 0xD1F0 },
+{ 0xD1F1, 0xD1F1, 0xD1F1 },
+{ 0xD1F2, 0xD1F2, 0xD1F2 },
+{ 0xD1F3, 0xD1F3, 0xD1F3 },
+{ 0xD1F4, 0xD1F4, 0xD1F4 },
+{ 0xD1F5, 0xD1F5, 0xD1F5 },
+{ 0xD1F6, 0xD1F6, 0xD1F6 },
+{ 0xD1F7, 0xD1F7, 0xD1F7 },
+{ 0xD1F8, 0xD1F8, 0xD1F8 },
+{ 0xD1F9, 0xD1F9, 0xD1F9 },
+{ 0xD1FA, 0xD1FA, 0xD1FA },
+{ 0xD1FB, 0xD1FB, 0xD1FB },
+{ 0xD1FC, 0xD1FC, 0xD1FC },
+{ 0xD1FD, 0xD1FD, 0xD1FD },
+{ 0xD1FE, 0xD1FE, 0xD1FE },
+{ 0xD1FF, 0xD1FF, 0xD1FF },
+{ 0xD200, 0xD200, 0xD200 },
+{ 0xD201, 0xD201, 0xD201 },
+{ 0xD202, 0xD202, 0xD202 },
+{ 0xD203, 0xD203, 0xD203 },
+{ 0xD204, 0xD204, 0xD204 },
+{ 0xD205, 0xD205, 0xD205 },
+{ 0xD206, 0xD206, 0xD206 },
+{ 0xD207, 0xD207, 0xD207 },
+{ 0xD208, 0xD208, 0xD208 },
+{ 0xD209, 0xD209, 0xD209 },
+{ 0xD20A, 0xD20A, 0xD20A },
+{ 0xD20B, 0xD20B, 0xD20B },
+{ 0xD20C, 0xD20C, 0xD20C },
+{ 0xD20D, 0xD20D, 0xD20D },
+{ 0xD20E, 0xD20E, 0xD20E },
+{ 0xD20F, 0xD20F, 0xD20F },
+{ 0xD210, 0xD210, 0xD210 },
+{ 0xD211, 0xD211, 0xD211 },
+{ 0xD212, 0xD212, 0xD212 },
+{ 0xD213, 0xD213, 0xD213 },
+{ 0xD214, 0xD214, 0xD214 },
+{ 0xD215, 0xD215, 0xD215 },
+{ 0xD216, 0xD216, 0xD216 },
+{ 0xD217, 0xD217, 0xD217 },
+{ 0xD218, 0xD218, 0xD218 },
+{ 0xD219, 0xD219, 0xD219 },
+{ 0xD21A, 0xD21A, 0xD21A },
+{ 0xD21B, 0xD21B, 0xD21B },
+{ 0xD21C, 0xD21C, 0xD21C },
+{ 0xD21D, 0xD21D, 0xD21D },
+{ 0xD21E, 0xD21E, 0xD21E },
+{ 0xD21F, 0xD21F, 0xD21F },
+{ 0xD220, 0xD220, 0xD220 },
+{ 0xD221, 0xD221, 0xD221 },
+{ 0xD222, 0xD222, 0xD222 },
+{ 0xD223, 0xD223, 0xD223 },
+{ 0xD224, 0xD224, 0xD224 },
+{ 0xD225, 0xD225, 0xD225 },
+{ 0xD226, 0xD226, 0xD226 },
+{ 0xD227, 0xD227, 0xD227 },
+{ 0xD228, 0xD228, 0xD228 },
+{ 0xD229, 0xD229, 0xD229 },
+{ 0xD22A, 0xD22A, 0xD22A },
+{ 0xD22B, 0xD22B, 0xD22B },
+{ 0xD22C, 0xD22C, 0xD22C },
+{ 0xD22D, 0xD22D, 0xD22D },
+{ 0xD22E, 0xD22E, 0xD22E },
+{ 0xD22F, 0xD22F, 0xD22F },
+{ 0xD230, 0xD230, 0xD230 },
+{ 0xD231, 0xD231, 0xD231 },
+{ 0xD232, 0xD232, 0xD232 },
+{ 0xD233, 0xD233, 0xD233 },
+{ 0xD234, 0xD234, 0xD234 },
+{ 0xD235, 0xD235, 0xD235 },
+{ 0xD236, 0xD236, 0xD236 },
+{ 0xD237, 0xD237, 0xD237 },
+{ 0xD238, 0xD238, 0xD238 },
+{ 0xD239, 0xD239, 0xD239 },
+{ 0xD23A, 0xD23A, 0xD23A },
+{ 0xD23B, 0xD23B, 0xD23B },
+{ 0xD23C, 0xD23C, 0xD23C },
+{ 0xD23D, 0xD23D, 0xD23D },
+{ 0xD23E, 0xD23E, 0xD23E },
+{ 0xD23F, 0xD23F, 0xD23F },
+{ 0xD240, 0xD240, 0xD240 },
+{ 0xD241, 0xD241, 0xD241 },
+{ 0xD242, 0xD242, 0xD242 },
+{ 0xD243, 0xD243, 0xD243 },
+{ 0xD244, 0xD244, 0xD244 },
+{ 0xD245, 0xD245, 0xD245 },
+{ 0xD246, 0xD246, 0xD246 },
+{ 0xD247, 0xD247, 0xD247 },
+{ 0xD248, 0xD248, 0xD248 },
+{ 0xD249, 0xD249, 0xD249 },
+{ 0xD24A, 0xD24A, 0xD24A },
+{ 0xD24B, 0xD24B, 0xD24B },
+{ 0xD24C, 0xD24C, 0xD24C },
+{ 0xD24D, 0xD24D, 0xD24D },
+{ 0xD24E, 0xD24E, 0xD24E },
+{ 0xD24F, 0xD24F, 0xD24F },
+{ 0xD250, 0xD250, 0xD250 },
+{ 0xD251, 0xD251, 0xD251 },
+{ 0xD252, 0xD252, 0xD252 },
+{ 0xD253, 0xD253, 0xD253 },
+{ 0xD254, 0xD254, 0xD254 },
+{ 0xD255, 0xD255, 0xD255 },
+{ 0xD256, 0xD256, 0xD256 },
+{ 0xD257, 0xD257, 0xD257 },
+{ 0xD258, 0xD258, 0xD258 },
+{ 0xD259, 0xD259, 0xD259 },
+{ 0xD25A, 0xD25A, 0xD25A },
+{ 0xD25B, 0xD25B, 0xD25B },
+{ 0xD25C, 0xD25C, 0xD25C },
+{ 0xD25D, 0xD25D, 0xD25D },
+{ 0xD25E, 0xD25E, 0xD25E },
+{ 0xD25F, 0xD25F, 0xD25F },
+{ 0xD260, 0xD260, 0xD260 },
+{ 0xD261, 0xD261, 0xD261 },
+{ 0xD262, 0xD262, 0xD262 },
+{ 0xD263, 0xD263, 0xD263 },
+{ 0xD264, 0xD264, 0xD264 },
+{ 0xD265, 0xD265, 0xD265 },
+{ 0xD266, 0xD266, 0xD266 },
+{ 0xD267, 0xD267, 0xD267 },
+{ 0xD268, 0xD268, 0xD268 },
+{ 0xD269, 0xD269, 0xD269 },
+{ 0xD26A, 0xD26A, 0xD26A },
+{ 0xD26B, 0xD26B, 0xD26B },
+{ 0xD26C, 0xD26C, 0xD26C },
+{ 0xD26D, 0xD26D, 0xD26D },
+{ 0xD26E, 0xD26E, 0xD26E },
+{ 0xD26F, 0xD26F, 0xD26F },
+{ 0xD270, 0xD270, 0xD270 },
+{ 0xD271, 0xD271, 0xD271 },
+{ 0xD272, 0xD272, 0xD272 },
+{ 0xD273, 0xD273, 0xD273 },
+{ 0xD274, 0xD274, 0xD274 },
+{ 0xD275, 0xD275, 0xD275 },
+{ 0xD276, 0xD276, 0xD276 },
+{ 0xD277, 0xD277, 0xD277 },
+{ 0xD278, 0xD278, 0xD278 },
+{ 0xD279, 0xD279, 0xD279 },
+{ 0xD27A, 0xD27A, 0xD27A },
+{ 0xD27B, 0xD27B, 0xD27B },
+{ 0xD27C, 0xD27C, 0xD27C },
+{ 0xD27D, 0xD27D, 0xD27D },
+{ 0xD27E, 0xD27E, 0xD27E },
+{ 0xD27F, 0xD27F, 0xD27F },
+{ 0xD280, 0xD280, 0xD280 },
+{ 0xD281, 0xD281, 0xD281 },
+{ 0xD282, 0xD282, 0xD282 },
+{ 0xD283, 0xD283, 0xD283 },
+{ 0xD284, 0xD284, 0xD284 },
+{ 0xD285, 0xD285, 0xD285 },
+{ 0xD286, 0xD286, 0xD286 },
+{ 0xD287, 0xD287, 0xD287 },
+{ 0xD288, 0xD288, 0xD288 },
+{ 0xD289, 0xD289, 0xD289 },
+{ 0xD28A, 0xD28A, 0xD28A },
+{ 0xD28B, 0xD28B, 0xD28B },
+{ 0xD28C, 0xD28C, 0xD28C },
+{ 0xD28D, 0xD28D, 0xD28D },
+{ 0xD28E, 0xD28E, 0xD28E },
+{ 0xD28F, 0xD28F, 0xD28F },
+{ 0xD290, 0xD290, 0xD290 },
+{ 0xD291, 0xD291, 0xD291 },
+{ 0xD292, 0xD292, 0xD292 },
+{ 0xD293, 0xD293, 0xD293 },
+{ 0xD294, 0xD294, 0xD294 },
+{ 0xD295, 0xD295, 0xD295 },
+{ 0xD296, 0xD296, 0xD296 },
+{ 0xD297, 0xD297, 0xD297 },
+{ 0xD298, 0xD298, 0xD298 },
+{ 0xD299, 0xD299, 0xD299 },
+{ 0xD29A, 0xD29A, 0xD29A },
+{ 0xD29B, 0xD29B, 0xD29B },
+{ 0xD29C, 0xD29C, 0xD29C },
+{ 0xD29D, 0xD29D, 0xD29D },
+{ 0xD29E, 0xD29E, 0xD29E },
+{ 0xD29F, 0xD29F, 0xD29F },
+{ 0xD2A0, 0xD2A0, 0xD2A0 },
+{ 0xD2A1, 0xD2A1, 0xD2A1 },
+{ 0xD2A2, 0xD2A2, 0xD2A2 },
+{ 0xD2A3, 0xD2A3, 0xD2A3 },
+{ 0xD2A4, 0xD2A4, 0xD2A4 },
+{ 0xD2A5, 0xD2A5, 0xD2A5 },
+{ 0xD2A6, 0xD2A6, 0xD2A6 },
+{ 0xD2A7, 0xD2A7, 0xD2A7 },
+{ 0xD2A8, 0xD2A8, 0xD2A8 },
+{ 0xD2A9, 0xD2A9, 0xD2A9 },
+{ 0xD2AA, 0xD2AA, 0xD2AA },
+{ 0xD2AB, 0xD2AB, 0xD2AB },
+{ 0xD2AC, 0xD2AC, 0xD2AC },
+{ 0xD2AD, 0xD2AD, 0xD2AD },
+{ 0xD2AE, 0xD2AE, 0xD2AE },
+{ 0xD2AF, 0xD2AF, 0xD2AF },
+{ 0xD2B0, 0xD2B0, 0xD2B0 },
+{ 0xD2B1, 0xD2B1, 0xD2B1 },
+{ 0xD2B2, 0xD2B2, 0xD2B2 },
+{ 0xD2B3, 0xD2B3, 0xD2B3 },
+{ 0xD2B4, 0xD2B4, 0xD2B4 },
+{ 0xD2B5, 0xD2B5, 0xD2B5 },
+{ 0xD2B6, 0xD2B6, 0xD2B6 },
+{ 0xD2B7, 0xD2B7, 0xD2B7 },
+{ 0xD2B8, 0xD2B8, 0xD2B8 },
+{ 0xD2B9, 0xD2B9, 0xD2B9 },
+{ 0xD2BA, 0xD2BA, 0xD2BA },
+{ 0xD2BB, 0xD2BB, 0xD2BB },
+{ 0xD2BC, 0xD2BC, 0xD2BC },
+{ 0xD2BD, 0xD2BD, 0xD2BD },
+{ 0xD2BE, 0xD2BE, 0xD2BE },
+{ 0xD2BF, 0xD2BF, 0xD2BF },
+{ 0xD2C0, 0xD2C0, 0xD2C0 },
+{ 0xD2C1, 0xD2C1, 0xD2C1 },
+{ 0xD2C2, 0xD2C2, 0xD2C2 },
+{ 0xD2C3, 0xD2C3, 0xD2C3 },
+{ 0xD2C4, 0xD2C4, 0xD2C4 },
+{ 0xD2C5, 0xD2C5, 0xD2C5 },
+{ 0xD2C6, 0xD2C6, 0xD2C6 },
+{ 0xD2C7, 0xD2C7, 0xD2C7 },
+{ 0xD2C8, 0xD2C8, 0xD2C8 },
+{ 0xD2C9, 0xD2C9, 0xD2C9 },
+{ 0xD2CA, 0xD2CA, 0xD2CA },
+{ 0xD2CB, 0xD2CB, 0xD2CB },
+{ 0xD2CC, 0xD2CC, 0xD2CC },
+{ 0xD2CD, 0xD2CD, 0xD2CD },
+{ 0xD2CE, 0xD2CE, 0xD2CE },
+{ 0xD2CF, 0xD2CF, 0xD2CF },
+{ 0xD2D0, 0xD2D0, 0xD2D0 },
+{ 0xD2D1, 0xD2D1, 0xD2D1 },
+{ 0xD2D2, 0xD2D2, 0xD2D2 },
+{ 0xD2D3, 0xD2D3, 0xD2D3 },
+{ 0xD2D4, 0xD2D4, 0xD2D4 },
+{ 0xD2D5, 0xD2D5, 0xD2D5 },
+{ 0xD2D6, 0xD2D6, 0xD2D6 },
+{ 0xD2D7, 0xD2D7, 0xD2D7 },
+{ 0xD2D8, 0xD2D8, 0xD2D8 },
+{ 0xD2D9, 0xD2D9, 0xD2D9 },
+{ 0xD2DA, 0xD2DA, 0xD2DA },
+{ 0xD2DB, 0xD2DB, 0xD2DB },
+{ 0xD2DC, 0xD2DC, 0xD2DC },
+{ 0xD2DD, 0xD2DD, 0xD2DD },
+{ 0xD2DE, 0xD2DE, 0xD2DE },
+{ 0xD2DF, 0xD2DF, 0xD2DF },
+{ 0xD2E0, 0xD2E0, 0xD2E0 },
+{ 0xD2E1, 0xD2E1, 0xD2E1 },
+{ 0xD2E2, 0xD2E2, 0xD2E2 },
+{ 0xD2E3, 0xD2E3, 0xD2E3 },
+{ 0xD2E4, 0xD2E4, 0xD2E4 },
+{ 0xD2E5, 0xD2E5, 0xD2E5 },
+{ 0xD2E6, 0xD2E6, 0xD2E6 },
+{ 0xD2E7, 0xD2E7, 0xD2E7 },
+{ 0xD2E8, 0xD2E8, 0xD2E8 },
+{ 0xD2E9, 0xD2E9, 0xD2E9 },
+{ 0xD2EA, 0xD2EA, 0xD2EA },
+{ 0xD2EB, 0xD2EB, 0xD2EB },
+{ 0xD2EC, 0xD2EC, 0xD2EC },
+{ 0xD2ED, 0xD2ED, 0xD2ED },
+{ 0xD2EE, 0xD2EE, 0xD2EE },
+{ 0xD2EF, 0xD2EF, 0xD2EF },
+{ 0xD2F0, 0xD2F0, 0xD2F0 },
+{ 0xD2F1, 0xD2F1, 0xD2F1 },
+{ 0xD2F2, 0xD2F2, 0xD2F2 },
+{ 0xD2F3, 0xD2F3, 0xD2F3 },
+{ 0xD2F4, 0xD2F4, 0xD2F4 },
+{ 0xD2F5, 0xD2F5, 0xD2F5 },
+{ 0xD2F6, 0xD2F6, 0xD2F6 },
+{ 0xD2F7, 0xD2F7, 0xD2F7 },
+{ 0xD2F8, 0xD2F8, 0xD2F8 },
+{ 0xD2F9, 0xD2F9, 0xD2F9 },
+{ 0xD2FA, 0xD2FA, 0xD2FA },
+{ 0xD2FB, 0xD2FB, 0xD2FB },
+{ 0xD2FC, 0xD2FC, 0xD2FC },
+{ 0xD2FD, 0xD2FD, 0xD2FD },
+{ 0xD2FE, 0xD2FE, 0xD2FE },
+{ 0xD2FF, 0xD2FF, 0xD2FF },
+{ 0xD300, 0xD300, 0xD300 },
+{ 0xD301, 0xD301, 0xD301 },
+{ 0xD302, 0xD302, 0xD302 },
+{ 0xD303, 0xD303, 0xD303 },
+{ 0xD304, 0xD304, 0xD304 },
+{ 0xD305, 0xD305, 0xD305 },
+{ 0xD306, 0xD306, 0xD306 },
+{ 0xD307, 0xD307, 0xD307 },
+{ 0xD308, 0xD308, 0xD308 },
+{ 0xD309, 0xD309, 0xD309 },
+{ 0xD30A, 0xD30A, 0xD30A },
+{ 0xD30B, 0xD30B, 0xD30B },
+{ 0xD30C, 0xD30C, 0xD30C },
+{ 0xD30D, 0xD30D, 0xD30D },
+{ 0xD30E, 0xD30E, 0xD30E },
+{ 0xD30F, 0xD30F, 0xD30F },
+{ 0xD310, 0xD310, 0xD310 },
+{ 0xD311, 0xD311, 0xD311 },
+{ 0xD312, 0xD312, 0xD312 },
+{ 0xD313, 0xD313, 0xD313 },
+{ 0xD314, 0xD314, 0xD314 },
+{ 0xD315, 0xD315, 0xD315 },
+{ 0xD316, 0xD316, 0xD316 },
+{ 0xD317, 0xD317, 0xD317 },
+{ 0xD318, 0xD318, 0xD318 },
+{ 0xD319, 0xD319, 0xD319 },
+{ 0xD31A, 0xD31A, 0xD31A },
+{ 0xD31B, 0xD31B, 0xD31B },
+{ 0xD31C, 0xD31C, 0xD31C },
+{ 0xD31D, 0xD31D, 0xD31D },
+{ 0xD31E, 0xD31E, 0xD31E },
+{ 0xD31F, 0xD31F, 0xD31F },
+{ 0xD320, 0xD320, 0xD320 },
+{ 0xD321, 0xD321, 0xD321 },
+{ 0xD322, 0xD322, 0xD322 },
+{ 0xD323, 0xD323, 0xD323 },
+{ 0xD324, 0xD324, 0xD324 },
+{ 0xD325, 0xD325, 0xD325 },
+{ 0xD326, 0xD326, 0xD326 },
+{ 0xD327, 0xD327, 0xD327 },
+{ 0xD328, 0xD328, 0xD328 },
+{ 0xD329, 0xD329, 0xD329 },
+{ 0xD32A, 0xD32A, 0xD32A },
+{ 0xD32B, 0xD32B, 0xD32B },
+{ 0xD32C, 0xD32C, 0xD32C },
+{ 0xD32D, 0xD32D, 0xD32D },
+{ 0xD32E, 0xD32E, 0xD32E },
+{ 0xD32F, 0xD32F, 0xD32F },
+{ 0xD330, 0xD330, 0xD330 },
+{ 0xD331, 0xD331, 0xD331 },
+{ 0xD332, 0xD332, 0xD332 },
+{ 0xD333, 0xD333, 0xD333 },
+{ 0xD334, 0xD334, 0xD334 },
+{ 0xD335, 0xD335, 0xD335 },
+{ 0xD336, 0xD336, 0xD336 },
+{ 0xD337, 0xD337, 0xD337 },
+{ 0xD338, 0xD338, 0xD338 },
+{ 0xD339, 0xD339, 0xD339 },
+{ 0xD33A, 0xD33A, 0xD33A },
+{ 0xD33B, 0xD33B, 0xD33B },
+{ 0xD33C, 0xD33C, 0xD33C },
+{ 0xD33D, 0xD33D, 0xD33D },
+{ 0xD33E, 0xD33E, 0xD33E },
+{ 0xD33F, 0xD33F, 0xD33F },
+{ 0xD340, 0xD340, 0xD340 },
+{ 0xD341, 0xD341, 0xD341 },
+{ 0xD342, 0xD342, 0xD342 },
+{ 0xD343, 0xD343, 0xD343 },
+{ 0xD344, 0xD344, 0xD344 },
+{ 0xD345, 0xD345, 0xD345 },
+{ 0xD346, 0xD346, 0xD346 },
+{ 0xD347, 0xD347, 0xD347 },
+{ 0xD348, 0xD348, 0xD348 },
+{ 0xD349, 0xD349, 0xD349 },
+{ 0xD34A, 0xD34A, 0xD34A },
+{ 0xD34B, 0xD34B, 0xD34B },
+{ 0xD34C, 0xD34C, 0xD34C },
+{ 0xD34D, 0xD34D, 0xD34D },
+{ 0xD34E, 0xD34E, 0xD34E },
+{ 0xD34F, 0xD34F, 0xD34F },
+{ 0xD350, 0xD350, 0xD350 },
+{ 0xD351, 0xD351, 0xD351 },
+{ 0xD352, 0xD352, 0xD352 },
+{ 0xD353, 0xD353, 0xD353 },
+{ 0xD354, 0xD354, 0xD354 },
+{ 0xD355, 0xD355, 0xD355 },
+{ 0xD356, 0xD356, 0xD356 },
+{ 0xD357, 0xD357, 0xD357 },
+{ 0xD358, 0xD358, 0xD358 },
+{ 0xD359, 0xD359, 0xD359 },
+{ 0xD35A, 0xD35A, 0xD35A },
+{ 0xD35B, 0xD35B, 0xD35B },
+{ 0xD35C, 0xD35C, 0xD35C },
+{ 0xD35D, 0xD35D, 0xD35D },
+{ 0xD35E, 0xD35E, 0xD35E },
+{ 0xD35F, 0xD35F, 0xD35F },
+{ 0xD360, 0xD360, 0xD360 },
+{ 0xD361, 0xD361, 0xD361 },
+{ 0xD362, 0xD362, 0xD362 },
+{ 0xD363, 0xD363, 0xD363 },
+{ 0xD364, 0xD364, 0xD364 },
+{ 0xD365, 0xD365, 0xD365 },
+{ 0xD366, 0xD366, 0xD366 },
+{ 0xD367, 0xD367, 0xD367 },
+{ 0xD368, 0xD368, 0xD368 },
+{ 0xD369, 0xD369, 0xD369 },
+{ 0xD36A, 0xD36A, 0xD36A },
+{ 0xD36B, 0xD36B, 0xD36B },
+{ 0xD36C, 0xD36C, 0xD36C },
+{ 0xD36D, 0xD36D, 0xD36D },
+{ 0xD36E, 0xD36E, 0xD36E },
+{ 0xD36F, 0xD36F, 0xD36F },
+{ 0xD370, 0xD370, 0xD370 },
+{ 0xD371, 0xD371, 0xD371 },
+{ 0xD372, 0xD372, 0xD372 },
+{ 0xD373, 0xD373, 0xD373 },
+{ 0xD374, 0xD374, 0xD374 },
+{ 0xD375, 0xD375, 0xD375 },
+{ 0xD376, 0xD376, 0xD376 },
+{ 0xD377, 0xD377, 0xD377 },
+{ 0xD378, 0xD378, 0xD378 },
+{ 0xD379, 0xD379, 0xD379 },
+{ 0xD37A, 0xD37A, 0xD37A },
+{ 0xD37B, 0xD37B, 0xD37B },
+{ 0xD37C, 0xD37C, 0xD37C },
+{ 0xD37D, 0xD37D, 0xD37D },
+{ 0xD37E, 0xD37E, 0xD37E },
+{ 0xD37F, 0xD37F, 0xD37F },
+{ 0xD380, 0xD380, 0xD380 },
+{ 0xD381, 0xD381, 0xD381 },
+{ 0xD382, 0xD382, 0xD382 },
+{ 0xD383, 0xD383, 0xD383 },
+{ 0xD384, 0xD384, 0xD384 },
+{ 0xD385, 0xD385, 0xD385 },
+{ 0xD386, 0xD386, 0xD386 },
+{ 0xD387, 0xD387, 0xD387 },
+{ 0xD388, 0xD388, 0xD388 },
+{ 0xD389, 0xD389, 0xD389 },
+{ 0xD38A, 0xD38A, 0xD38A },
+{ 0xD38B, 0xD38B, 0xD38B },
+{ 0xD38C, 0xD38C, 0xD38C },
+{ 0xD38D, 0xD38D, 0xD38D },
+{ 0xD38E, 0xD38E, 0xD38E },
+{ 0xD38F, 0xD38F, 0xD38F },
+{ 0xD390, 0xD390, 0xD390 },
+{ 0xD391, 0xD391, 0xD391 },
+{ 0xD392, 0xD392, 0xD392 },
+{ 0xD393, 0xD393, 0xD393 },
+{ 0xD394, 0xD394, 0xD394 },
+{ 0xD395, 0xD395, 0xD395 },
+{ 0xD396, 0xD396, 0xD396 },
+{ 0xD397, 0xD397, 0xD397 },
+{ 0xD398, 0xD398, 0xD398 },
+{ 0xD399, 0xD399, 0xD399 },
+{ 0xD39A, 0xD39A, 0xD39A },
+{ 0xD39B, 0xD39B, 0xD39B },
+{ 0xD39C, 0xD39C, 0xD39C },
+{ 0xD39D, 0xD39D, 0xD39D },
+{ 0xD39E, 0xD39E, 0xD39E },
+{ 0xD39F, 0xD39F, 0xD39F },
+{ 0xD3A0, 0xD3A0, 0xD3A0 },
+{ 0xD3A1, 0xD3A1, 0xD3A1 },
+{ 0xD3A2, 0xD3A2, 0xD3A2 },
+{ 0xD3A3, 0xD3A3, 0xD3A3 },
+{ 0xD3A4, 0xD3A4, 0xD3A4 },
+{ 0xD3A5, 0xD3A5, 0xD3A5 },
+{ 0xD3A6, 0xD3A6, 0xD3A6 },
+{ 0xD3A7, 0xD3A7, 0xD3A7 },
+{ 0xD3A8, 0xD3A8, 0xD3A8 },
+{ 0xD3A9, 0xD3A9, 0xD3A9 },
+{ 0xD3AA, 0xD3AA, 0xD3AA },
+{ 0xD3AB, 0xD3AB, 0xD3AB },
+{ 0xD3AC, 0xD3AC, 0xD3AC },
+{ 0xD3AD, 0xD3AD, 0xD3AD },
+{ 0xD3AE, 0xD3AE, 0xD3AE },
+{ 0xD3AF, 0xD3AF, 0xD3AF },
+{ 0xD3B0, 0xD3B0, 0xD3B0 },
+{ 0xD3B1, 0xD3B1, 0xD3B1 },
+{ 0xD3B2, 0xD3B2, 0xD3B2 },
+{ 0xD3B3, 0xD3B3, 0xD3B3 },
+{ 0xD3B4, 0xD3B4, 0xD3B4 },
+{ 0xD3B5, 0xD3B5, 0xD3B5 },
+{ 0xD3B6, 0xD3B6, 0xD3B6 },
+{ 0xD3B7, 0xD3B7, 0xD3B7 },
+{ 0xD3B8, 0xD3B8, 0xD3B8 },
+{ 0xD3B9, 0xD3B9, 0xD3B9 },
+{ 0xD3BA, 0xD3BA, 0xD3BA },
+{ 0xD3BB, 0xD3BB, 0xD3BB },
+{ 0xD3BC, 0xD3BC, 0xD3BC },
+{ 0xD3BD, 0xD3BD, 0xD3BD },
+{ 0xD3BE, 0xD3BE, 0xD3BE },
+{ 0xD3BF, 0xD3BF, 0xD3BF },
+{ 0xD3C0, 0xD3C0, 0xD3C0 },
+{ 0xD3C1, 0xD3C1, 0xD3C1 },
+{ 0xD3C2, 0xD3C2, 0xD3C2 },
+{ 0xD3C3, 0xD3C3, 0xD3C3 },
+{ 0xD3C4, 0xD3C4, 0xD3C4 },
+{ 0xD3C5, 0xD3C5, 0xD3C5 },
+{ 0xD3C6, 0xD3C6, 0xD3C6 },
+{ 0xD3C7, 0xD3C7, 0xD3C7 },
+{ 0xD3C8, 0xD3C8, 0xD3C8 },
+{ 0xD3C9, 0xD3C9, 0xD3C9 },
+{ 0xD3CA, 0xD3CA, 0xD3CA },
+{ 0xD3CB, 0xD3CB, 0xD3CB },
+{ 0xD3CC, 0xD3CC, 0xD3CC },
+{ 0xD3CD, 0xD3CD, 0xD3CD },
+{ 0xD3CE, 0xD3CE, 0xD3CE },
+{ 0xD3CF, 0xD3CF, 0xD3CF },
+{ 0xD3D0, 0xD3D0, 0xD3D0 },
+{ 0xD3D1, 0xD3D1, 0xD3D1 },
+{ 0xD3D2, 0xD3D2, 0xD3D2 },
+{ 0xD3D3, 0xD3D3, 0xD3D3 },
+{ 0xD3D4, 0xD3D4, 0xD3D4 },
+{ 0xD3D5, 0xD3D5, 0xD3D5 },
+{ 0xD3D6, 0xD3D6, 0xD3D6 },
+{ 0xD3D7, 0xD3D7, 0xD3D7 },
+{ 0xD3D8, 0xD3D8, 0xD3D8 },
+{ 0xD3D9, 0xD3D9, 0xD3D9 },
+{ 0xD3DA, 0xD3DA, 0xD3DA },
+{ 0xD3DB, 0xD3DB, 0xD3DB },
+{ 0xD3DC, 0xD3DC, 0xD3DC },
+{ 0xD3DD, 0xD3DD, 0xD3DD },
+{ 0xD3DE, 0xD3DE, 0xD3DE },
+{ 0xD3DF, 0xD3DF, 0xD3DF },
+{ 0xD3E0, 0xD3E0, 0xD3E0 },
+{ 0xD3E1, 0xD3E1, 0xD3E1 },
+{ 0xD3E2, 0xD3E2, 0xD3E2 },
+{ 0xD3E3, 0xD3E3, 0xD3E3 },
+{ 0xD3E4, 0xD3E4, 0xD3E4 },
+{ 0xD3E5, 0xD3E5, 0xD3E5 },
+{ 0xD3E6, 0xD3E6, 0xD3E6 },
+{ 0xD3E7, 0xD3E7, 0xD3E7 },
+{ 0xD3E8, 0xD3E8, 0xD3E8 },
+{ 0xD3E9, 0xD3E9, 0xD3E9 },
+{ 0xD3EA, 0xD3EA, 0xD3EA },
+{ 0xD3EB, 0xD3EB, 0xD3EB },
+{ 0xD3EC, 0xD3EC, 0xD3EC },
+{ 0xD3ED, 0xD3ED, 0xD3ED },
+{ 0xD3EE, 0xD3EE, 0xD3EE },
+{ 0xD3EF, 0xD3EF, 0xD3EF },
+{ 0xD3F0, 0xD3F0, 0xD3F0 },
+{ 0xD3F1, 0xD3F1, 0xD3F1 },
+{ 0xD3F2, 0xD3F2, 0xD3F2 },
+{ 0xD3F3, 0xD3F3, 0xD3F3 },
+{ 0xD3F4, 0xD3F4, 0xD3F4 },
+{ 0xD3F5, 0xD3F5, 0xD3F5 },
+{ 0xD3F6, 0xD3F6, 0xD3F6 },
+{ 0xD3F7, 0xD3F7, 0xD3F7 },
+{ 0xD3F8, 0xD3F8, 0xD3F8 },
+{ 0xD3F9, 0xD3F9, 0xD3F9 },
+{ 0xD3FA, 0xD3FA, 0xD3FA },
+{ 0xD3FB, 0xD3FB, 0xD3FB },
+{ 0xD3FC, 0xD3FC, 0xD3FC },
+{ 0xD3FD, 0xD3FD, 0xD3FD },
+{ 0xD3FE, 0xD3FE, 0xD3FE },
+{ 0xD3FF, 0xD3FF, 0xD3FF },
+{ 0xD400, 0xD400, 0xD400 },
+{ 0xD401, 0xD401, 0xD401 },
+{ 0xD402, 0xD402, 0xD402 },
+{ 0xD403, 0xD403, 0xD403 },
+{ 0xD404, 0xD404, 0xD404 },
+{ 0xD405, 0xD405, 0xD405 },
+{ 0xD406, 0xD406, 0xD406 },
+{ 0xD407, 0xD407, 0xD407 },
+{ 0xD408, 0xD408, 0xD408 },
+{ 0xD409, 0xD409, 0xD409 },
+{ 0xD40A, 0xD40A, 0xD40A },
+{ 0xD40B, 0xD40B, 0xD40B },
+{ 0xD40C, 0xD40C, 0xD40C },
+{ 0xD40D, 0xD40D, 0xD40D },
+{ 0xD40E, 0xD40E, 0xD40E },
+{ 0xD40F, 0xD40F, 0xD40F },
+{ 0xD410, 0xD410, 0xD410 },
+{ 0xD411, 0xD411, 0xD411 },
+{ 0xD412, 0xD412, 0xD412 },
+{ 0xD413, 0xD413, 0xD413 },
+{ 0xD414, 0xD414, 0xD414 },
+{ 0xD415, 0xD415, 0xD415 },
+{ 0xD416, 0xD416, 0xD416 },
+{ 0xD417, 0xD417, 0xD417 },
+{ 0xD418, 0xD418, 0xD418 },
+{ 0xD419, 0xD419, 0xD419 },
+{ 0xD41A, 0xD41A, 0xD41A },
+{ 0xD41B, 0xD41B, 0xD41B },
+{ 0xD41C, 0xD41C, 0xD41C },
+{ 0xD41D, 0xD41D, 0xD41D },
+{ 0xD41E, 0xD41E, 0xD41E },
+{ 0xD41F, 0xD41F, 0xD41F },
+{ 0xD420, 0xD420, 0xD420 },
+{ 0xD421, 0xD421, 0xD421 },
+{ 0xD422, 0xD422, 0xD422 },
+{ 0xD423, 0xD423, 0xD423 },
+{ 0xD424, 0xD424, 0xD424 },
+{ 0xD425, 0xD425, 0xD425 },
+{ 0xD426, 0xD426, 0xD426 },
+{ 0xD427, 0xD427, 0xD427 },
+{ 0xD428, 0xD428, 0xD428 },
+{ 0xD429, 0xD429, 0xD429 },
+{ 0xD42A, 0xD42A, 0xD42A },
+{ 0xD42B, 0xD42B, 0xD42B },
+{ 0xD42C, 0xD42C, 0xD42C },
+{ 0xD42D, 0xD42D, 0xD42D },
+{ 0xD42E, 0xD42E, 0xD42E },
+{ 0xD42F, 0xD42F, 0xD42F },
+{ 0xD430, 0xD430, 0xD430 },
+{ 0xD431, 0xD431, 0xD431 },
+{ 0xD432, 0xD432, 0xD432 },
+{ 0xD433, 0xD433, 0xD433 },
+{ 0xD434, 0xD434, 0xD434 },
+{ 0xD435, 0xD435, 0xD435 },
+{ 0xD436, 0xD436, 0xD436 },
+{ 0xD437, 0xD437, 0xD437 },
+{ 0xD438, 0xD438, 0xD438 },
+{ 0xD439, 0xD439, 0xD439 },
+{ 0xD43A, 0xD43A, 0xD43A },
+{ 0xD43B, 0xD43B, 0xD43B },
+{ 0xD43C, 0xD43C, 0xD43C },
+{ 0xD43D, 0xD43D, 0xD43D },
+{ 0xD43E, 0xD43E, 0xD43E },
+{ 0xD43F, 0xD43F, 0xD43F },
+{ 0xD440, 0xD440, 0xD440 },
+{ 0xD441, 0xD441, 0xD441 },
+{ 0xD442, 0xD442, 0xD442 },
+{ 0xD443, 0xD443, 0xD443 },
+{ 0xD444, 0xD444, 0xD444 },
+{ 0xD445, 0xD445, 0xD445 },
+{ 0xD446, 0xD446, 0xD446 },
+{ 0xD447, 0xD447, 0xD447 },
+{ 0xD448, 0xD448, 0xD448 },
+{ 0xD449, 0xD449, 0xD449 },
+{ 0xD44A, 0xD44A, 0xD44A },
+{ 0xD44B, 0xD44B, 0xD44B },
+{ 0xD44C, 0xD44C, 0xD44C },
+{ 0xD44D, 0xD44D, 0xD44D },
+{ 0xD44E, 0xD44E, 0xD44E },
+{ 0xD44F, 0xD44F, 0xD44F },
+{ 0xD450, 0xD450, 0xD450 },
+{ 0xD451, 0xD451, 0xD451 },
+{ 0xD452, 0xD452, 0xD452 },
+{ 0xD453, 0xD453, 0xD453 },
+{ 0xD454, 0xD454, 0xD454 },
+{ 0xD455, 0xD455, 0xD455 },
+{ 0xD456, 0xD456, 0xD456 },
+{ 0xD457, 0xD457, 0xD457 },
+{ 0xD458, 0xD458, 0xD458 },
+{ 0xD459, 0xD459, 0xD459 },
+{ 0xD45A, 0xD45A, 0xD45A },
+{ 0xD45B, 0xD45B, 0xD45B },
+{ 0xD45C, 0xD45C, 0xD45C },
+{ 0xD45D, 0xD45D, 0xD45D },
+{ 0xD45E, 0xD45E, 0xD45E },
+{ 0xD45F, 0xD45F, 0xD45F },
+{ 0xD460, 0xD460, 0xD460 },
+{ 0xD461, 0xD461, 0xD461 },
+{ 0xD462, 0xD462, 0xD462 },
+{ 0xD463, 0xD463, 0xD463 },
+{ 0xD464, 0xD464, 0xD464 },
+{ 0xD465, 0xD465, 0xD465 },
+{ 0xD466, 0xD466, 0xD466 },
+{ 0xD467, 0xD467, 0xD467 },
+{ 0xD468, 0xD468, 0xD468 },
+{ 0xD469, 0xD469, 0xD469 },
+{ 0xD46A, 0xD46A, 0xD46A },
+{ 0xD46B, 0xD46B, 0xD46B },
+{ 0xD46C, 0xD46C, 0xD46C },
+{ 0xD46D, 0xD46D, 0xD46D },
+{ 0xD46E, 0xD46E, 0xD46E },
+{ 0xD46F, 0xD46F, 0xD46F },
+{ 0xD470, 0xD470, 0xD470 },
+{ 0xD471, 0xD471, 0xD471 },
+{ 0xD472, 0xD472, 0xD472 },
+{ 0xD473, 0xD473, 0xD473 },
+{ 0xD474, 0xD474, 0xD474 },
+{ 0xD475, 0xD475, 0xD475 },
+{ 0xD476, 0xD476, 0xD476 },
+{ 0xD477, 0xD477, 0xD477 },
+{ 0xD478, 0xD478, 0xD478 },
+{ 0xD479, 0xD479, 0xD479 },
+{ 0xD47A, 0xD47A, 0xD47A },
+{ 0xD47B, 0xD47B, 0xD47B },
+{ 0xD47C, 0xD47C, 0xD47C },
+{ 0xD47D, 0xD47D, 0xD47D },
+{ 0xD47E, 0xD47E, 0xD47E },
+{ 0xD47F, 0xD47F, 0xD47F },
+{ 0xD480, 0xD480, 0xD480 },
+{ 0xD481, 0xD481, 0xD481 },
+{ 0xD482, 0xD482, 0xD482 },
+{ 0xD483, 0xD483, 0xD483 },
+{ 0xD484, 0xD484, 0xD484 },
+{ 0xD485, 0xD485, 0xD485 },
+{ 0xD486, 0xD486, 0xD486 },
+{ 0xD487, 0xD487, 0xD487 },
+{ 0xD488, 0xD488, 0xD488 },
+{ 0xD489, 0xD489, 0xD489 },
+{ 0xD48A, 0xD48A, 0xD48A },
+{ 0xD48B, 0xD48B, 0xD48B },
+{ 0xD48C, 0xD48C, 0xD48C },
+{ 0xD48D, 0xD48D, 0xD48D },
+{ 0xD48E, 0xD48E, 0xD48E },
+{ 0xD48F, 0xD48F, 0xD48F },
+{ 0xD490, 0xD490, 0xD490 },
+{ 0xD491, 0xD491, 0xD491 },
+{ 0xD492, 0xD492, 0xD492 },
+{ 0xD493, 0xD493, 0xD493 },
+{ 0xD494, 0xD494, 0xD494 },
+{ 0xD495, 0xD495, 0xD495 },
+{ 0xD496, 0xD496, 0xD496 },
+{ 0xD497, 0xD497, 0xD497 },
+{ 0xD498, 0xD498, 0xD498 },
+{ 0xD499, 0xD499, 0xD499 },
+{ 0xD49A, 0xD49A, 0xD49A },
+{ 0xD49B, 0xD49B, 0xD49B },
+{ 0xD49C, 0xD49C, 0xD49C },
+{ 0xD49D, 0xD49D, 0xD49D },
+{ 0xD49E, 0xD49E, 0xD49E },
+{ 0xD49F, 0xD49F, 0xD49F },
+{ 0xD4A0, 0xD4A0, 0xD4A0 },
+{ 0xD4A1, 0xD4A1, 0xD4A1 },
+{ 0xD4A2, 0xD4A2, 0xD4A2 },
+{ 0xD4A3, 0xD4A3, 0xD4A3 },
+{ 0xD4A4, 0xD4A4, 0xD4A4 },
+{ 0xD4A5, 0xD4A5, 0xD4A5 },
+{ 0xD4A6, 0xD4A6, 0xD4A6 },
+{ 0xD4A7, 0xD4A7, 0xD4A7 },
+{ 0xD4A8, 0xD4A8, 0xD4A8 },
+{ 0xD4A9, 0xD4A9, 0xD4A9 },
+{ 0xD4AA, 0xD4AA, 0xD4AA },
+{ 0xD4AB, 0xD4AB, 0xD4AB },
+{ 0xD4AC, 0xD4AC, 0xD4AC },
+{ 0xD4AD, 0xD4AD, 0xD4AD },
+{ 0xD4AE, 0xD4AE, 0xD4AE },
+{ 0xD4AF, 0xD4AF, 0xD4AF },
+{ 0xD4B0, 0xD4B0, 0xD4B0 },
+{ 0xD4B1, 0xD4B1, 0xD4B1 },
+{ 0xD4B2, 0xD4B2, 0xD4B2 },
+{ 0xD4B3, 0xD4B3, 0xD4B3 },
+{ 0xD4B4, 0xD4B4, 0xD4B4 },
+{ 0xD4B5, 0xD4B5, 0xD4B5 },
+{ 0xD4B6, 0xD4B6, 0xD4B6 },
+{ 0xD4B7, 0xD4B7, 0xD4B7 },
+{ 0xD4B8, 0xD4B8, 0xD4B8 },
+{ 0xD4B9, 0xD4B9, 0xD4B9 },
+{ 0xD4BA, 0xD4BA, 0xD4BA },
+{ 0xD4BB, 0xD4BB, 0xD4BB },
+{ 0xD4BC, 0xD4BC, 0xD4BC },
+{ 0xD4BD, 0xD4BD, 0xD4BD },
+{ 0xD4BE, 0xD4BE, 0xD4BE },
+{ 0xD4BF, 0xD4BF, 0xD4BF },
+{ 0xD4C0, 0xD4C0, 0xD4C0 },
+{ 0xD4C1, 0xD4C1, 0xD4C1 },
+{ 0xD4C2, 0xD4C2, 0xD4C2 },
+{ 0xD4C3, 0xD4C3, 0xD4C3 },
+{ 0xD4C4, 0xD4C4, 0xD4C4 },
+{ 0xD4C5, 0xD4C5, 0xD4C5 },
+{ 0xD4C6, 0xD4C6, 0xD4C6 },
+{ 0xD4C7, 0xD4C7, 0xD4C7 },
+{ 0xD4C8, 0xD4C8, 0xD4C8 },
+{ 0xD4C9, 0xD4C9, 0xD4C9 },
+{ 0xD4CA, 0xD4CA, 0xD4CA },
+{ 0xD4CB, 0xD4CB, 0xD4CB },
+{ 0xD4CC, 0xD4CC, 0xD4CC },
+{ 0xD4CD, 0xD4CD, 0xD4CD },
+{ 0xD4CE, 0xD4CE, 0xD4CE },
+{ 0xD4CF, 0xD4CF, 0xD4CF },
+{ 0xD4D0, 0xD4D0, 0xD4D0 },
+{ 0xD4D1, 0xD4D1, 0xD4D1 },
+{ 0xD4D2, 0xD4D2, 0xD4D2 },
+{ 0xD4D3, 0xD4D3, 0xD4D3 },
+{ 0xD4D4, 0xD4D4, 0xD4D4 },
+{ 0xD4D5, 0xD4D5, 0xD4D5 },
+{ 0xD4D6, 0xD4D6, 0xD4D6 },
+{ 0xD4D7, 0xD4D7, 0xD4D7 },
+{ 0xD4D8, 0xD4D8, 0xD4D8 },
+{ 0xD4D9, 0xD4D9, 0xD4D9 },
+{ 0xD4DA, 0xD4DA, 0xD4DA },
+{ 0xD4DB, 0xD4DB, 0xD4DB },
+{ 0xD4DC, 0xD4DC, 0xD4DC },
+{ 0xD4DD, 0xD4DD, 0xD4DD },
+{ 0xD4DE, 0xD4DE, 0xD4DE },
+{ 0xD4DF, 0xD4DF, 0xD4DF },
+{ 0xD4E0, 0xD4E0, 0xD4E0 },
+{ 0xD4E1, 0xD4E1, 0xD4E1 },
+{ 0xD4E2, 0xD4E2, 0xD4E2 },
+{ 0xD4E3, 0xD4E3, 0xD4E3 },
+{ 0xD4E4, 0xD4E4, 0xD4E4 },
+{ 0xD4E5, 0xD4E5, 0xD4E5 },
+{ 0xD4E6, 0xD4E6, 0xD4E6 },
+{ 0xD4E7, 0xD4E7, 0xD4E7 },
+{ 0xD4E8, 0xD4E8, 0xD4E8 },
+{ 0xD4E9, 0xD4E9, 0xD4E9 },
+{ 0xD4EA, 0xD4EA, 0xD4EA },
+{ 0xD4EB, 0xD4EB, 0xD4EB },
+{ 0xD4EC, 0xD4EC, 0xD4EC },
+{ 0xD4ED, 0xD4ED, 0xD4ED },
+{ 0xD4EE, 0xD4EE, 0xD4EE },
+{ 0xD4EF, 0xD4EF, 0xD4EF },
+{ 0xD4F0, 0xD4F0, 0xD4F0 },
+{ 0xD4F1, 0xD4F1, 0xD4F1 },
+{ 0xD4F2, 0xD4F2, 0xD4F2 },
+{ 0xD4F3, 0xD4F3, 0xD4F3 },
+{ 0xD4F4, 0xD4F4, 0xD4F4 },
+{ 0xD4F5, 0xD4F5, 0xD4F5 },
+{ 0xD4F6, 0xD4F6, 0xD4F6 },
+{ 0xD4F7, 0xD4F7, 0xD4F7 },
+{ 0xD4F8, 0xD4F8, 0xD4F8 },
+{ 0xD4F9, 0xD4F9, 0xD4F9 },
+{ 0xD4FA, 0xD4FA, 0xD4FA },
+{ 0xD4FB, 0xD4FB, 0xD4FB },
+{ 0xD4FC, 0xD4FC, 0xD4FC },
+{ 0xD4FD, 0xD4FD, 0xD4FD },
+{ 0xD4FE, 0xD4FE, 0xD4FE },
+{ 0xD4FF, 0xD4FF, 0xD4FF },
+{ 0xD500, 0xD500, 0xD500 },
+{ 0xD501, 0xD501, 0xD501 },
+{ 0xD502, 0xD502, 0xD502 },
+{ 0xD503, 0xD503, 0xD503 },
+{ 0xD504, 0xD504, 0xD504 },
+{ 0xD505, 0xD505, 0xD505 },
+{ 0xD506, 0xD506, 0xD506 },
+{ 0xD507, 0xD507, 0xD507 },
+{ 0xD508, 0xD508, 0xD508 },
+{ 0xD509, 0xD509, 0xD509 },
+{ 0xD50A, 0xD50A, 0xD50A },
+{ 0xD50B, 0xD50B, 0xD50B },
+{ 0xD50C, 0xD50C, 0xD50C },
+{ 0xD50D, 0xD50D, 0xD50D },
+{ 0xD50E, 0xD50E, 0xD50E },
+{ 0xD50F, 0xD50F, 0xD50F },
+{ 0xD510, 0xD510, 0xD510 },
+{ 0xD511, 0xD511, 0xD511 },
+{ 0xD512, 0xD512, 0xD512 },
+{ 0xD513, 0xD513, 0xD513 },
+{ 0xD514, 0xD514, 0xD514 },
+{ 0xD515, 0xD515, 0xD515 },
+{ 0xD516, 0xD516, 0xD516 },
+{ 0xD517, 0xD517, 0xD517 },
+{ 0xD518, 0xD518, 0xD518 },
+{ 0xD519, 0xD519, 0xD519 },
+{ 0xD51A, 0xD51A, 0xD51A },
+{ 0xD51B, 0xD51B, 0xD51B },
+{ 0xD51C, 0xD51C, 0xD51C },
+{ 0xD51D, 0xD51D, 0xD51D },
+{ 0xD51E, 0xD51E, 0xD51E },
+{ 0xD51F, 0xD51F, 0xD51F },
+{ 0xD520, 0xD520, 0xD520 },
+{ 0xD521, 0xD521, 0xD521 },
+{ 0xD522, 0xD522, 0xD522 },
+{ 0xD523, 0xD523, 0xD523 },
+{ 0xD524, 0xD524, 0xD524 },
+{ 0xD525, 0xD525, 0xD525 },
+{ 0xD526, 0xD526, 0xD526 },
+{ 0xD527, 0xD527, 0xD527 },
+{ 0xD528, 0xD528, 0xD528 },
+{ 0xD529, 0xD529, 0xD529 },
+{ 0xD52A, 0xD52A, 0xD52A },
+{ 0xD52B, 0xD52B, 0xD52B },
+{ 0xD52C, 0xD52C, 0xD52C },
+{ 0xD52D, 0xD52D, 0xD52D },
+{ 0xD52E, 0xD52E, 0xD52E },
+{ 0xD52F, 0xD52F, 0xD52F },
+{ 0xD530, 0xD530, 0xD530 },
+{ 0xD531, 0xD531, 0xD531 },
+{ 0xD532, 0xD532, 0xD532 },
+{ 0xD533, 0xD533, 0xD533 },
+{ 0xD534, 0xD534, 0xD534 },
+{ 0xD535, 0xD535, 0xD535 },
+{ 0xD536, 0xD536, 0xD536 },
+{ 0xD537, 0xD537, 0xD537 },
+{ 0xD538, 0xD538, 0xD538 },
+{ 0xD539, 0xD539, 0xD539 },
+{ 0xD53A, 0xD53A, 0xD53A },
+{ 0xD53B, 0xD53B, 0xD53B },
+{ 0xD53C, 0xD53C, 0xD53C },
+{ 0xD53D, 0xD53D, 0xD53D },
+{ 0xD53E, 0xD53E, 0xD53E },
+{ 0xD53F, 0xD53F, 0xD53F },
+{ 0xD540, 0xD540, 0xD540 },
+{ 0xD541, 0xD541, 0xD541 },
+{ 0xD542, 0xD542, 0xD542 },
+{ 0xD543, 0xD543, 0xD543 },
+{ 0xD544, 0xD544, 0xD544 },
+{ 0xD545, 0xD545, 0xD545 },
+{ 0xD546, 0xD546, 0xD546 },
+{ 0xD547, 0xD547, 0xD547 },
+{ 0xD548, 0xD548, 0xD548 },
+{ 0xD549, 0xD549, 0xD549 },
+{ 0xD54A, 0xD54A, 0xD54A },
+{ 0xD54B, 0xD54B, 0xD54B },
+{ 0xD54C, 0xD54C, 0xD54C },
+{ 0xD54D, 0xD54D, 0xD54D },
+{ 0xD54E, 0xD54E, 0xD54E },
+{ 0xD54F, 0xD54F, 0xD54F },
+{ 0xD550, 0xD550, 0xD550 },
+{ 0xD551, 0xD551, 0xD551 },
+{ 0xD552, 0xD552, 0xD552 },
+{ 0xD553, 0xD553, 0xD553 },
+{ 0xD554, 0xD554, 0xD554 },
+{ 0xD555, 0xD555, 0xD555 },
+{ 0xD556, 0xD556, 0xD556 },
+{ 0xD557, 0xD557, 0xD557 },
+{ 0xD558, 0xD558, 0xD558 },
+{ 0xD559, 0xD559, 0xD559 },
+{ 0xD55A, 0xD55A, 0xD55A },
+{ 0xD55B, 0xD55B, 0xD55B },
+{ 0xD55C, 0xD55C, 0xD55C },
+{ 0xD55D, 0xD55D, 0xD55D },
+{ 0xD55E, 0xD55E, 0xD55E },
+{ 0xD55F, 0xD55F, 0xD55F },
+{ 0xD560, 0xD560, 0xD560 },
+{ 0xD561, 0xD561, 0xD561 },
+{ 0xD562, 0xD562, 0xD562 },
+{ 0xD563, 0xD563, 0xD563 },
+{ 0xD564, 0xD564, 0xD564 },
+{ 0xD565, 0xD565, 0xD565 },
+{ 0xD566, 0xD566, 0xD566 },
+{ 0xD567, 0xD567, 0xD567 },
+{ 0xD568, 0xD568, 0xD568 },
+{ 0xD569, 0xD569, 0xD569 },
+{ 0xD56A, 0xD56A, 0xD56A },
+{ 0xD56B, 0xD56B, 0xD56B },
+{ 0xD56C, 0xD56C, 0xD56C },
+{ 0xD56D, 0xD56D, 0xD56D },
+{ 0xD56E, 0xD56E, 0xD56E },
+{ 0xD56F, 0xD56F, 0xD56F },
+{ 0xD570, 0xD570, 0xD570 },
+{ 0xD571, 0xD571, 0xD571 },
+{ 0xD572, 0xD572, 0xD572 },
+{ 0xD573, 0xD573, 0xD573 },
+{ 0xD574, 0xD574, 0xD574 },
+{ 0xD575, 0xD575, 0xD575 },
+{ 0xD576, 0xD576, 0xD576 },
+{ 0xD577, 0xD577, 0xD577 },
+{ 0xD578, 0xD578, 0xD578 },
+{ 0xD579, 0xD579, 0xD579 },
+{ 0xD57A, 0xD57A, 0xD57A },
+{ 0xD57B, 0xD57B, 0xD57B },
+{ 0xD57C, 0xD57C, 0xD57C },
+{ 0xD57D, 0xD57D, 0xD57D },
+{ 0xD57E, 0xD57E, 0xD57E },
+{ 0xD57F, 0xD57F, 0xD57F },
+{ 0xD580, 0xD580, 0xD580 },
+{ 0xD581, 0xD581, 0xD581 },
+{ 0xD582, 0xD582, 0xD582 },
+{ 0xD583, 0xD583, 0xD583 },
+{ 0xD584, 0xD584, 0xD584 },
+{ 0xD585, 0xD585, 0xD585 },
+{ 0xD586, 0xD586, 0xD586 },
+{ 0xD587, 0xD587, 0xD587 },
+{ 0xD588, 0xD588, 0xD588 },
+{ 0xD589, 0xD589, 0xD589 },
+{ 0xD58A, 0xD58A, 0xD58A },
+{ 0xD58B, 0xD58B, 0xD58B },
+{ 0xD58C, 0xD58C, 0xD58C },
+{ 0xD58D, 0xD58D, 0xD58D },
+{ 0xD58E, 0xD58E, 0xD58E },
+{ 0xD58F, 0xD58F, 0xD58F },
+{ 0xD590, 0xD590, 0xD590 },
+{ 0xD591, 0xD591, 0xD591 },
+{ 0xD592, 0xD592, 0xD592 },
+{ 0xD593, 0xD593, 0xD593 },
+{ 0xD594, 0xD594, 0xD594 },
+{ 0xD595, 0xD595, 0xD595 },
+{ 0xD596, 0xD596, 0xD596 },
+{ 0xD597, 0xD597, 0xD597 },
+{ 0xD598, 0xD598, 0xD598 },
+{ 0xD599, 0xD599, 0xD599 },
+{ 0xD59A, 0xD59A, 0xD59A },
+{ 0xD59B, 0xD59B, 0xD59B },
+{ 0xD59C, 0xD59C, 0xD59C },
+{ 0xD59D, 0xD59D, 0xD59D },
+{ 0xD59E, 0xD59E, 0xD59E },
+{ 0xD59F, 0xD59F, 0xD59F },
+{ 0xD5A0, 0xD5A0, 0xD5A0 },
+{ 0xD5A1, 0xD5A1, 0xD5A1 },
+{ 0xD5A2, 0xD5A2, 0xD5A2 },
+{ 0xD5A3, 0xD5A3, 0xD5A3 },
+{ 0xD5A4, 0xD5A4, 0xD5A4 },
+{ 0xD5A5, 0xD5A5, 0xD5A5 },
+{ 0xD5A6, 0xD5A6, 0xD5A6 },
+{ 0xD5A7, 0xD5A7, 0xD5A7 },
+{ 0xD5A8, 0xD5A8, 0xD5A8 },
+{ 0xD5A9, 0xD5A9, 0xD5A9 },
+{ 0xD5AA, 0xD5AA, 0xD5AA },
+{ 0xD5AB, 0xD5AB, 0xD5AB },
+{ 0xD5AC, 0xD5AC, 0xD5AC },
+{ 0xD5AD, 0xD5AD, 0xD5AD },
+{ 0xD5AE, 0xD5AE, 0xD5AE },
+{ 0xD5AF, 0xD5AF, 0xD5AF },
+{ 0xD5B0, 0xD5B0, 0xD5B0 },
+{ 0xD5B1, 0xD5B1, 0xD5B1 },
+{ 0xD5B2, 0xD5B2, 0xD5B2 },
+{ 0xD5B3, 0xD5B3, 0xD5B3 },
+{ 0xD5B4, 0xD5B4, 0xD5B4 },
+{ 0xD5B5, 0xD5B5, 0xD5B5 },
+{ 0xD5B6, 0xD5B6, 0xD5B6 },
+{ 0xD5B7, 0xD5B7, 0xD5B7 },
+{ 0xD5B8, 0xD5B8, 0xD5B8 },
+{ 0xD5B9, 0xD5B9, 0xD5B9 },
+{ 0xD5BA, 0xD5BA, 0xD5BA },
+{ 0xD5BB, 0xD5BB, 0xD5BB },
+{ 0xD5BC, 0xD5BC, 0xD5BC },
+{ 0xD5BD, 0xD5BD, 0xD5BD },
+{ 0xD5BE, 0xD5BE, 0xD5BE },
+{ 0xD5BF, 0xD5BF, 0xD5BF },
+{ 0xD5C0, 0xD5C0, 0xD5C0 },
+{ 0xD5C1, 0xD5C1, 0xD5C1 },
+{ 0xD5C2, 0xD5C2, 0xD5C2 },
+{ 0xD5C3, 0xD5C3, 0xD5C3 },
+{ 0xD5C4, 0xD5C4, 0xD5C4 },
+{ 0xD5C5, 0xD5C5, 0xD5C5 },
+{ 0xD5C6, 0xD5C6, 0xD5C6 },
+{ 0xD5C7, 0xD5C7, 0xD5C7 },
+{ 0xD5C8, 0xD5C8, 0xD5C8 },
+{ 0xD5C9, 0xD5C9, 0xD5C9 },
+{ 0xD5CA, 0xD5CA, 0xD5CA },
+{ 0xD5CB, 0xD5CB, 0xD5CB },
+{ 0xD5CC, 0xD5CC, 0xD5CC },
+{ 0xD5CD, 0xD5CD, 0xD5CD },
+{ 0xD5CE, 0xD5CE, 0xD5CE },
+{ 0xD5CF, 0xD5CF, 0xD5CF },
+{ 0xD5D0, 0xD5D0, 0xD5D0 },
+{ 0xD5D1, 0xD5D1, 0xD5D1 },
+{ 0xD5D2, 0xD5D2, 0xD5D2 },
+{ 0xD5D3, 0xD5D3, 0xD5D3 },
+{ 0xD5D4, 0xD5D4, 0xD5D4 },
+{ 0xD5D5, 0xD5D5, 0xD5D5 },
+{ 0xD5D6, 0xD5D6, 0xD5D6 },
+{ 0xD5D7, 0xD5D7, 0xD5D7 },
+{ 0xD5D8, 0xD5D8, 0xD5D8 },
+{ 0xD5D9, 0xD5D9, 0xD5D9 },
+{ 0xD5DA, 0xD5DA, 0xD5DA },
+{ 0xD5DB, 0xD5DB, 0xD5DB },
+{ 0xD5DC, 0xD5DC, 0xD5DC },
+{ 0xD5DD, 0xD5DD, 0xD5DD },
+{ 0xD5DE, 0xD5DE, 0xD5DE },
+{ 0xD5DF, 0xD5DF, 0xD5DF },
+{ 0xD5E0, 0xD5E0, 0xD5E0 },
+{ 0xD5E1, 0xD5E1, 0xD5E1 },
+{ 0xD5E2, 0xD5E2, 0xD5E2 },
+{ 0xD5E3, 0xD5E3, 0xD5E3 },
+{ 0xD5E4, 0xD5E4, 0xD5E4 },
+{ 0xD5E5, 0xD5E5, 0xD5E5 },
+{ 0xD5E6, 0xD5E6, 0xD5E6 },
+{ 0xD5E7, 0xD5E7, 0xD5E7 },
+{ 0xD5E8, 0xD5E8, 0xD5E8 },
+{ 0xD5E9, 0xD5E9, 0xD5E9 },
+{ 0xD5EA, 0xD5EA, 0xD5EA },
+{ 0xD5EB, 0xD5EB, 0xD5EB },
+{ 0xD5EC, 0xD5EC, 0xD5EC },
+{ 0xD5ED, 0xD5ED, 0xD5ED },
+{ 0xD5EE, 0xD5EE, 0xD5EE },
+{ 0xD5EF, 0xD5EF, 0xD5EF },
+{ 0xD5F0, 0xD5F0, 0xD5F0 },
+{ 0xD5F1, 0xD5F1, 0xD5F1 },
+{ 0xD5F2, 0xD5F2, 0xD5F2 },
+{ 0xD5F3, 0xD5F3, 0xD5F3 },
+{ 0xD5F4, 0xD5F4, 0xD5F4 },
+{ 0xD5F5, 0xD5F5, 0xD5F5 },
+{ 0xD5F6, 0xD5F6, 0xD5F6 },
+{ 0xD5F7, 0xD5F7, 0xD5F7 },
+{ 0xD5F8, 0xD5F8, 0xD5F8 },
+{ 0xD5F9, 0xD5F9, 0xD5F9 },
+{ 0xD5FA, 0xD5FA, 0xD5FA },
+{ 0xD5FB, 0xD5FB, 0xD5FB },
+{ 0xD5FC, 0xD5FC, 0xD5FC },
+{ 0xD5FD, 0xD5FD, 0xD5FD },
+{ 0xD5FE, 0xD5FE, 0xD5FE },
+{ 0xD5FF, 0xD5FF, 0xD5FF },
+{ 0xD600, 0xD600, 0xD600 },
+{ 0xD601, 0xD601, 0xD601 },
+{ 0xD602, 0xD602, 0xD602 },
+{ 0xD603, 0xD603, 0xD603 },
+{ 0xD604, 0xD604, 0xD604 },
+{ 0xD605, 0xD605, 0xD605 },
+{ 0xD606, 0xD606, 0xD606 },
+{ 0xD607, 0xD607, 0xD607 },
+{ 0xD608, 0xD608, 0xD608 },
+{ 0xD609, 0xD609, 0xD609 },
+{ 0xD60A, 0xD60A, 0xD60A },
+{ 0xD60B, 0xD60B, 0xD60B },
+{ 0xD60C, 0xD60C, 0xD60C },
+{ 0xD60D, 0xD60D, 0xD60D },
+{ 0xD60E, 0xD60E, 0xD60E },
+{ 0xD60F, 0xD60F, 0xD60F },
+{ 0xD610, 0xD610, 0xD610 },
+{ 0xD611, 0xD611, 0xD611 },
+{ 0xD612, 0xD612, 0xD612 },
+{ 0xD613, 0xD613, 0xD613 },
+{ 0xD614, 0xD614, 0xD614 },
+{ 0xD615, 0xD615, 0xD615 },
+{ 0xD616, 0xD616, 0xD616 },
+{ 0xD617, 0xD617, 0xD617 },
+{ 0xD618, 0xD618, 0xD618 },
+{ 0xD619, 0xD619, 0xD619 },
+{ 0xD61A, 0xD61A, 0xD61A },
+{ 0xD61B, 0xD61B, 0xD61B },
+{ 0xD61C, 0xD61C, 0xD61C },
+{ 0xD61D, 0xD61D, 0xD61D },
+{ 0xD61E, 0xD61E, 0xD61E },
+{ 0xD61F, 0xD61F, 0xD61F },
+{ 0xD620, 0xD620, 0xD620 },
+{ 0xD621, 0xD621, 0xD621 },
+{ 0xD622, 0xD622, 0xD622 },
+{ 0xD623, 0xD623, 0xD623 },
+{ 0xD624, 0xD624, 0xD624 },
+{ 0xD625, 0xD625, 0xD625 },
+{ 0xD626, 0xD626, 0xD626 },
+{ 0xD627, 0xD627, 0xD627 },
+{ 0xD628, 0xD628, 0xD628 },
+{ 0xD629, 0xD629, 0xD629 },
+{ 0xD62A, 0xD62A, 0xD62A },
+{ 0xD62B, 0xD62B, 0xD62B },
+{ 0xD62C, 0xD62C, 0xD62C },
+{ 0xD62D, 0xD62D, 0xD62D },
+{ 0xD62E, 0xD62E, 0xD62E },
+{ 0xD62F, 0xD62F, 0xD62F },
+{ 0xD630, 0xD630, 0xD630 },
+{ 0xD631, 0xD631, 0xD631 },
+{ 0xD632, 0xD632, 0xD632 },
+{ 0xD633, 0xD633, 0xD633 },
+{ 0xD634, 0xD634, 0xD634 },
+{ 0xD635, 0xD635, 0xD635 },
+{ 0xD636, 0xD636, 0xD636 },
+{ 0xD637, 0xD637, 0xD637 },
+{ 0xD638, 0xD638, 0xD638 },
+{ 0xD639, 0xD639, 0xD639 },
+{ 0xD63A, 0xD63A, 0xD63A },
+{ 0xD63B, 0xD63B, 0xD63B },
+{ 0xD63C, 0xD63C, 0xD63C },
+{ 0xD63D, 0xD63D, 0xD63D },
+{ 0xD63E, 0xD63E, 0xD63E },
+{ 0xD63F, 0xD63F, 0xD63F },
+{ 0xD640, 0xD640, 0xD640 },
+{ 0xD641, 0xD641, 0xD641 },
+{ 0xD642, 0xD642, 0xD642 },
+{ 0xD643, 0xD643, 0xD643 },
+{ 0xD644, 0xD644, 0xD644 },
+{ 0xD645, 0xD645, 0xD645 },
+{ 0xD646, 0xD646, 0xD646 },
+{ 0xD647, 0xD647, 0xD647 },
+{ 0xD648, 0xD648, 0xD648 },
+{ 0xD649, 0xD649, 0xD649 },
+{ 0xD64A, 0xD64A, 0xD64A },
+{ 0xD64B, 0xD64B, 0xD64B },
+{ 0xD64C, 0xD64C, 0xD64C },
+{ 0xD64D, 0xD64D, 0xD64D },
+{ 0xD64E, 0xD64E, 0xD64E },
+{ 0xD64F, 0xD64F, 0xD64F },
+{ 0xD650, 0xD650, 0xD650 },
+{ 0xD651, 0xD651, 0xD651 },
+{ 0xD652, 0xD652, 0xD652 },
+{ 0xD653, 0xD653, 0xD653 },
+{ 0xD654, 0xD654, 0xD654 },
+{ 0xD655, 0xD655, 0xD655 },
+{ 0xD656, 0xD656, 0xD656 },
+{ 0xD657, 0xD657, 0xD657 },
+{ 0xD658, 0xD658, 0xD658 },
+{ 0xD659, 0xD659, 0xD659 },
+{ 0xD65A, 0xD65A, 0xD65A },
+{ 0xD65B, 0xD65B, 0xD65B },
+{ 0xD65C, 0xD65C, 0xD65C },
+{ 0xD65D, 0xD65D, 0xD65D },
+{ 0xD65E, 0xD65E, 0xD65E },
+{ 0xD65F, 0xD65F, 0xD65F },
+{ 0xD660, 0xD660, 0xD660 },
+{ 0xD661, 0xD661, 0xD661 },
+{ 0xD662, 0xD662, 0xD662 },
+{ 0xD663, 0xD663, 0xD663 },
+{ 0xD664, 0xD664, 0xD664 },
+{ 0xD665, 0xD665, 0xD665 },
+{ 0xD666, 0xD666, 0xD666 },
+{ 0xD667, 0xD667, 0xD667 },
+{ 0xD668, 0xD668, 0xD668 },
+{ 0xD669, 0xD669, 0xD669 },
+{ 0xD66A, 0xD66A, 0xD66A },
+{ 0xD66B, 0xD66B, 0xD66B },
+{ 0xD66C, 0xD66C, 0xD66C },
+{ 0xD66D, 0xD66D, 0xD66D },
+{ 0xD66E, 0xD66E, 0xD66E },
+{ 0xD66F, 0xD66F, 0xD66F },
+{ 0xD670, 0xD670, 0xD670 },
+{ 0xD671, 0xD671, 0xD671 },
+{ 0xD672, 0xD672, 0xD672 },
+{ 0xD673, 0xD673, 0xD673 },
+{ 0xD674, 0xD674, 0xD674 },
+{ 0xD675, 0xD675, 0xD675 },
+{ 0xD676, 0xD676, 0xD676 },
+{ 0xD677, 0xD677, 0xD677 },
+{ 0xD678, 0xD678, 0xD678 },
+{ 0xD679, 0xD679, 0xD679 },
+{ 0xD67A, 0xD67A, 0xD67A },
+{ 0xD67B, 0xD67B, 0xD67B },
+{ 0xD67C, 0xD67C, 0xD67C },
+{ 0xD67D, 0xD67D, 0xD67D },
+{ 0xD67E, 0xD67E, 0xD67E },
+{ 0xD67F, 0xD67F, 0xD67F },
+{ 0xD680, 0xD680, 0xD680 },
+{ 0xD681, 0xD681, 0xD681 },
+{ 0xD682, 0xD682, 0xD682 },
+{ 0xD683, 0xD683, 0xD683 },
+{ 0xD684, 0xD684, 0xD684 },
+{ 0xD685, 0xD685, 0xD685 },
+{ 0xD686, 0xD686, 0xD686 },
+{ 0xD687, 0xD687, 0xD687 },
+{ 0xD688, 0xD688, 0xD688 },
+{ 0xD689, 0xD689, 0xD689 },
+{ 0xD68A, 0xD68A, 0xD68A },
+{ 0xD68B, 0xD68B, 0xD68B },
+{ 0xD68C, 0xD68C, 0xD68C },
+{ 0xD68D, 0xD68D, 0xD68D },
+{ 0xD68E, 0xD68E, 0xD68E },
+{ 0xD68F, 0xD68F, 0xD68F },
+{ 0xD690, 0xD690, 0xD690 },
+{ 0xD691, 0xD691, 0xD691 },
+{ 0xD692, 0xD692, 0xD692 },
+{ 0xD693, 0xD693, 0xD693 },
+{ 0xD694, 0xD694, 0xD694 },
+{ 0xD695, 0xD695, 0xD695 },
+{ 0xD696, 0xD696, 0xD696 },
+{ 0xD697, 0xD697, 0xD697 },
+{ 0xD698, 0xD698, 0xD698 },
+{ 0xD699, 0xD699, 0xD699 },
+{ 0xD69A, 0xD69A, 0xD69A },
+{ 0xD69B, 0xD69B, 0xD69B },
+{ 0xD69C, 0xD69C, 0xD69C },
+{ 0xD69D, 0xD69D, 0xD69D },
+{ 0xD69E, 0xD69E, 0xD69E },
+{ 0xD69F, 0xD69F, 0xD69F },
+{ 0xD6A0, 0xD6A0, 0xD6A0 },
+{ 0xD6A1, 0xD6A1, 0xD6A1 },
+{ 0xD6A2, 0xD6A2, 0xD6A2 },
+{ 0xD6A3, 0xD6A3, 0xD6A3 },
+{ 0xD6A4, 0xD6A4, 0xD6A4 },
+{ 0xD6A5, 0xD6A5, 0xD6A5 },
+{ 0xD6A6, 0xD6A6, 0xD6A6 },
+{ 0xD6A7, 0xD6A7, 0xD6A7 },
+{ 0xD6A8, 0xD6A8, 0xD6A8 },
+{ 0xD6A9, 0xD6A9, 0xD6A9 },
+{ 0xD6AA, 0xD6AA, 0xD6AA },
+{ 0xD6AB, 0xD6AB, 0xD6AB },
+{ 0xD6AC, 0xD6AC, 0xD6AC },
+{ 0xD6AD, 0xD6AD, 0xD6AD },
+{ 0xD6AE, 0xD6AE, 0xD6AE },
+{ 0xD6AF, 0xD6AF, 0xD6AF },
+{ 0xD6B0, 0xD6B0, 0xD6B0 },
+{ 0xD6B1, 0xD6B1, 0xD6B1 },
+{ 0xD6B2, 0xD6B2, 0xD6B2 },
+{ 0xD6B3, 0xD6B3, 0xD6B3 },
+{ 0xD6B4, 0xD6B4, 0xD6B4 },
+{ 0xD6B5, 0xD6B5, 0xD6B5 },
+{ 0xD6B6, 0xD6B6, 0xD6B6 },
+{ 0xD6B7, 0xD6B7, 0xD6B7 },
+{ 0xD6B8, 0xD6B8, 0xD6B8 },
+{ 0xD6B9, 0xD6B9, 0xD6B9 },
+{ 0xD6BA, 0xD6BA, 0xD6BA },
+{ 0xD6BB, 0xD6BB, 0xD6BB },
+{ 0xD6BC, 0xD6BC, 0xD6BC },
+{ 0xD6BD, 0xD6BD, 0xD6BD },
+{ 0xD6BE, 0xD6BE, 0xD6BE },
+{ 0xD6BF, 0xD6BF, 0xD6BF },
+{ 0xD6C0, 0xD6C0, 0xD6C0 },
+{ 0xD6C1, 0xD6C1, 0xD6C1 },
+{ 0xD6C2, 0xD6C2, 0xD6C2 },
+{ 0xD6C3, 0xD6C3, 0xD6C3 },
+{ 0xD6C4, 0xD6C4, 0xD6C4 },
+{ 0xD6C5, 0xD6C5, 0xD6C5 },
+{ 0xD6C6, 0xD6C6, 0xD6C6 },
+{ 0xD6C7, 0xD6C7, 0xD6C7 },
+{ 0xD6C8, 0xD6C8, 0xD6C8 },
+{ 0xD6C9, 0xD6C9, 0xD6C9 },
+{ 0xD6CA, 0xD6CA, 0xD6CA },
+{ 0xD6CB, 0xD6CB, 0xD6CB },
+{ 0xD6CC, 0xD6CC, 0xD6CC },
+{ 0xD6CD, 0xD6CD, 0xD6CD },
+{ 0xD6CE, 0xD6CE, 0xD6CE },
+{ 0xD6CF, 0xD6CF, 0xD6CF },
+{ 0xD6D0, 0xD6D0, 0xD6D0 },
+{ 0xD6D1, 0xD6D1, 0xD6D1 },
+{ 0xD6D2, 0xD6D2, 0xD6D2 },
+{ 0xD6D3, 0xD6D3, 0xD6D3 },
+{ 0xD6D4, 0xD6D4, 0xD6D4 },
+{ 0xD6D5, 0xD6D5, 0xD6D5 },
+{ 0xD6D6, 0xD6D6, 0xD6D6 },
+{ 0xD6D7, 0xD6D7, 0xD6D7 },
+{ 0xD6D8, 0xD6D8, 0xD6D8 },
+{ 0xD6D9, 0xD6D9, 0xD6D9 },
+{ 0xD6DA, 0xD6DA, 0xD6DA },
+{ 0xD6DB, 0xD6DB, 0xD6DB },
+{ 0xD6DC, 0xD6DC, 0xD6DC },
+{ 0xD6DD, 0xD6DD, 0xD6DD },
+{ 0xD6DE, 0xD6DE, 0xD6DE },
+{ 0xD6DF, 0xD6DF, 0xD6DF },
+{ 0xD6E0, 0xD6E0, 0xD6E0 },
+{ 0xD6E1, 0xD6E1, 0xD6E1 },
+{ 0xD6E2, 0xD6E2, 0xD6E2 },
+{ 0xD6E3, 0xD6E3, 0xD6E3 },
+{ 0xD6E4, 0xD6E4, 0xD6E4 },
+{ 0xD6E5, 0xD6E5, 0xD6E5 },
+{ 0xD6E6, 0xD6E6, 0xD6E6 },
+{ 0xD6E7, 0xD6E7, 0xD6E7 },
+{ 0xD6E8, 0xD6E8, 0xD6E8 },
+{ 0xD6E9, 0xD6E9, 0xD6E9 },
+{ 0xD6EA, 0xD6EA, 0xD6EA },
+{ 0xD6EB, 0xD6EB, 0xD6EB },
+{ 0xD6EC, 0xD6EC, 0xD6EC },
+{ 0xD6ED, 0xD6ED, 0xD6ED },
+{ 0xD6EE, 0xD6EE, 0xD6EE },
+{ 0xD6EF, 0xD6EF, 0xD6EF },
+{ 0xD6F0, 0xD6F0, 0xD6F0 },
+{ 0xD6F1, 0xD6F1, 0xD6F1 },
+{ 0xD6F2, 0xD6F2, 0xD6F2 },
+{ 0xD6F3, 0xD6F3, 0xD6F3 },
+{ 0xD6F4, 0xD6F4, 0xD6F4 },
+{ 0xD6F5, 0xD6F5, 0xD6F5 },
+{ 0xD6F6, 0xD6F6, 0xD6F6 },
+{ 0xD6F7, 0xD6F7, 0xD6F7 },
+{ 0xD6F8, 0xD6F8, 0xD6F8 },
+{ 0xD6F9, 0xD6F9, 0xD6F9 },
+{ 0xD6FA, 0xD6FA, 0xD6FA },
+{ 0xD6FB, 0xD6FB, 0xD6FB },
+{ 0xD6FC, 0xD6FC, 0xD6FC },
+{ 0xD6FD, 0xD6FD, 0xD6FD },
+{ 0xD6FE, 0xD6FE, 0xD6FE },
+{ 0xD6FF, 0xD6FF, 0xD6FF },
+{ 0xD700, 0xD700, 0xD700 },
+{ 0xD701, 0xD701, 0xD701 },
+{ 0xD702, 0xD702, 0xD702 },
+{ 0xD703, 0xD703, 0xD703 },
+{ 0xD704, 0xD704, 0xD704 },
+{ 0xD705, 0xD705, 0xD705 },
+{ 0xD706, 0xD706, 0xD706 },
+{ 0xD707, 0xD707, 0xD707 },
+{ 0xD708, 0xD708, 0xD708 },
+{ 0xD709, 0xD709, 0xD709 },
+{ 0xD70A, 0xD70A, 0xD70A },
+{ 0xD70B, 0xD70B, 0xD70B },
+{ 0xD70C, 0xD70C, 0xD70C },
+{ 0xD70D, 0xD70D, 0xD70D },
+{ 0xD70E, 0xD70E, 0xD70E },
+{ 0xD70F, 0xD70F, 0xD70F },
+{ 0xD710, 0xD710, 0xD710 },
+{ 0xD711, 0xD711, 0xD711 },
+{ 0xD712, 0xD712, 0xD712 },
+{ 0xD713, 0xD713, 0xD713 },
+{ 0xD714, 0xD714, 0xD714 },
+{ 0xD715, 0xD715, 0xD715 },
+{ 0xD716, 0xD716, 0xD716 },
+{ 0xD717, 0xD717, 0xD717 },
+{ 0xD718, 0xD718, 0xD718 },
+{ 0xD719, 0xD719, 0xD719 },
+{ 0xD71A, 0xD71A, 0xD71A },
+{ 0xD71B, 0xD71B, 0xD71B },
+{ 0xD71C, 0xD71C, 0xD71C },
+{ 0xD71D, 0xD71D, 0xD71D },
+{ 0xD71E, 0xD71E, 0xD71E },
+{ 0xD71F, 0xD71F, 0xD71F },
+{ 0xD720, 0xD720, 0xD720 },
+{ 0xD721, 0xD721, 0xD721 },
+{ 0xD722, 0xD722, 0xD722 },
+{ 0xD723, 0xD723, 0xD723 },
+{ 0xD724, 0xD724, 0xD724 },
+{ 0xD725, 0xD725, 0xD725 },
+{ 0xD726, 0xD726, 0xD726 },
+{ 0xD727, 0xD727, 0xD727 },
+{ 0xD728, 0xD728, 0xD728 },
+{ 0xD729, 0xD729, 0xD729 },
+{ 0xD72A, 0xD72A, 0xD72A },
+{ 0xD72B, 0xD72B, 0xD72B },
+{ 0xD72C, 0xD72C, 0xD72C },
+{ 0xD72D, 0xD72D, 0xD72D },
+{ 0xD72E, 0xD72E, 0xD72E },
+{ 0xD72F, 0xD72F, 0xD72F },
+{ 0xD730, 0xD730, 0xD730 },
+{ 0xD731, 0xD731, 0xD731 },
+{ 0xD732, 0xD732, 0xD732 },
+{ 0xD733, 0xD733, 0xD733 },
+{ 0xD734, 0xD734, 0xD734 },
+{ 0xD735, 0xD735, 0xD735 },
+{ 0xD736, 0xD736, 0xD736 },
+{ 0xD737, 0xD737, 0xD737 },
+{ 0xD738, 0xD738, 0xD738 },
+{ 0xD739, 0xD739, 0xD739 },
+{ 0xD73A, 0xD73A, 0xD73A },
+{ 0xD73B, 0xD73B, 0xD73B },
+{ 0xD73C, 0xD73C, 0xD73C },
+{ 0xD73D, 0xD73D, 0xD73D },
+{ 0xD73E, 0xD73E, 0xD73E },
+{ 0xD73F, 0xD73F, 0xD73F },
+{ 0xD740, 0xD740, 0xD740 },
+{ 0xD741, 0xD741, 0xD741 },
+{ 0xD742, 0xD742, 0xD742 },
+{ 0xD743, 0xD743, 0xD743 },
+{ 0xD744, 0xD744, 0xD744 },
+{ 0xD745, 0xD745, 0xD745 },
+{ 0xD746, 0xD746, 0xD746 },
+{ 0xD747, 0xD747, 0xD747 },
+{ 0xD748, 0xD748, 0xD748 },
+{ 0xD749, 0xD749, 0xD749 },
+{ 0xD74A, 0xD74A, 0xD74A },
+{ 0xD74B, 0xD74B, 0xD74B },
+{ 0xD74C, 0xD74C, 0xD74C },
+{ 0xD74D, 0xD74D, 0xD74D },
+{ 0xD74E, 0xD74E, 0xD74E },
+{ 0xD74F, 0xD74F, 0xD74F },
+{ 0xD750, 0xD750, 0xD750 },
+{ 0xD751, 0xD751, 0xD751 },
+{ 0xD752, 0xD752, 0xD752 },
+{ 0xD753, 0xD753, 0xD753 },
+{ 0xD754, 0xD754, 0xD754 },
+{ 0xD755, 0xD755, 0xD755 },
+{ 0xD756, 0xD756, 0xD756 },
+{ 0xD757, 0xD757, 0xD757 },
+{ 0xD758, 0xD758, 0xD758 },
+{ 0xD759, 0xD759, 0xD759 },
+{ 0xD75A, 0xD75A, 0xD75A },
+{ 0xD75B, 0xD75B, 0xD75B },
+{ 0xD75C, 0xD75C, 0xD75C },
+{ 0xD75D, 0xD75D, 0xD75D },
+{ 0xD75E, 0xD75E, 0xD75E },
+{ 0xD75F, 0xD75F, 0xD75F },
+{ 0xD760, 0xD760, 0xD760 },
+{ 0xD761, 0xD761, 0xD761 },
+{ 0xD762, 0xD762, 0xD762 },
+{ 0xD763, 0xD763, 0xD763 },
+{ 0xD764, 0xD764, 0xD764 },
+{ 0xD765, 0xD765, 0xD765 },
+{ 0xD766, 0xD766, 0xD766 },
+{ 0xD767, 0xD767, 0xD767 },
+{ 0xD768, 0xD768, 0xD768 },
+{ 0xD769, 0xD769, 0xD769 },
+{ 0xD76A, 0xD76A, 0xD76A },
+{ 0xD76B, 0xD76B, 0xD76B },
+{ 0xD76C, 0xD76C, 0xD76C },
+{ 0xD76D, 0xD76D, 0xD76D },
+{ 0xD76E, 0xD76E, 0xD76E },
+{ 0xD76F, 0xD76F, 0xD76F },
+{ 0xD770, 0xD770, 0xD770 },
+{ 0xD771, 0xD771, 0xD771 },
+{ 0xD772, 0xD772, 0xD772 },
+{ 0xD773, 0xD773, 0xD773 },
+{ 0xD774, 0xD774, 0xD774 },
+{ 0xD775, 0xD775, 0xD775 },
+{ 0xD776, 0xD776, 0xD776 },
+{ 0xD777, 0xD777, 0xD777 },
+{ 0xD778, 0xD778, 0xD778 },
+{ 0xD779, 0xD779, 0xD779 },
+{ 0xD77A, 0xD77A, 0xD77A },
+{ 0xD77B, 0xD77B, 0xD77B },
+{ 0xD77C, 0xD77C, 0xD77C },
+{ 0xD77D, 0xD77D, 0xD77D },
+{ 0xD77E, 0xD77E, 0xD77E },
+{ 0xD77F, 0xD77F, 0xD77F },
+{ 0xD780, 0xD780, 0xD780 },
+{ 0xD781, 0xD781, 0xD781 },
+{ 0xD782, 0xD782, 0xD782 },
+{ 0xD783, 0xD783, 0xD783 },
+{ 0xD784, 0xD784, 0xD784 },
+{ 0xD785, 0xD785, 0xD785 },
+{ 0xD786, 0xD786, 0xD786 },
+{ 0xD787, 0xD787, 0xD787 },
+{ 0xD788, 0xD788, 0xD788 },
+{ 0xD789, 0xD789, 0xD789 },
+{ 0xD78A, 0xD78A, 0xD78A },
+{ 0xD78B, 0xD78B, 0xD78B },
+{ 0xD78C, 0xD78C, 0xD78C },
+{ 0xD78D, 0xD78D, 0xD78D },
+{ 0xD78E, 0xD78E, 0xD78E },
+{ 0xD78F, 0xD78F, 0xD78F },
+{ 0xD790, 0xD790, 0xD790 },
+{ 0xD791, 0xD791, 0xD791 },
+{ 0xD792, 0xD792, 0xD792 },
+{ 0xD793, 0xD793, 0xD793 },
+{ 0xD794, 0xD794, 0xD794 },
+{ 0xD795, 0xD795, 0xD795 },
+{ 0xD796, 0xD796, 0xD796 },
+{ 0xD797, 0xD797, 0xD797 },
+{ 0xD798, 0xD798, 0xD798 },
+{ 0xD799, 0xD799, 0xD799 },
+{ 0xD79A, 0xD79A, 0xD79A },
+{ 0xD79B, 0xD79B, 0xD79B },
+{ 0xD79C, 0xD79C, 0xD79C },
+{ 0xD79D, 0xD79D, 0xD79D },
+{ 0xD79E, 0xD79E, 0xD79E },
+{ 0xD79F, 0xD79F, 0xD79F },
+{ 0xD7A0, 0xD7A0, 0xD7A0 },
+{ 0xD7A1, 0xD7A1, 0xD7A1 },
+{ 0xD7A2, 0xD7A2, 0xD7A2 },
+{ 0xD7A3, 0xD7A3, 0xD7A3 },
+{ 0xF900, 0xF900, 0xF900 },
+{ 0xF901, 0xF901, 0xF901 },
+{ 0xF902, 0xF902, 0xF902 },
+{ 0xF903, 0xF903, 0xF903 },
+{ 0xF904, 0xF904, 0xF904 },
+{ 0xF905, 0xF905, 0xF905 },
+{ 0xF906, 0xF906, 0xF906 },
+{ 0xF907, 0xF907, 0xF907 },
+{ 0xF908, 0xF908, 0xF908 },
+{ 0xF909, 0xF909, 0xF909 },
+{ 0xF90A, 0xF90A, 0xF90A },
+{ 0xF90B, 0xF90B, 0xF90B },
+{ 0xF90C, 0xF90C, 0xF90C },
+{ 0xF90D, 0xF90D, 0xF90D },
+{ 0xF90E, 0xF90E, 0xF90E },
+{ 0xF90F, 0xF90F, 0xF90F },
+{ 0xF910, 0xF910, 0xF910 },
+{ 0xF911, 0xF911, 0xF911 },
+{ 0xF912, 0xF912, 0xF912 },
+{ 0xF913, 0xF913, 0xF913 },
+{ 0xF914, 0xF914, 0xF914 },
+{ 0xF915, 0xF915, 0xF915 },
+{ 0xF916, 0xF916, 0xF916 },
+{ 0xF917, 0xF917, 0xF917 },
+{ 0xF918, 0xF918, 0xF918 },
+{ 0xF919, 0xF919, 0xF919 },
+{ 0xF91A, 0xF91A, 0xF91A },
+{ 0xF91B, 0xF91B, 0xF91B },
+{ 0xF91C, 0xF91C, 0xF91C },
+{ 0xF91D, 0xF91D, 0xF91D },
+{ 0xF91E, 0xF91E, 0xF91E },
+{ 0xF91F, 0xF91F, 0xF91F },
+{ 0xF920, 0xF920, 0xF920 },
+{ 0xF921, 0xF921, 0xF921 },
+{ 0xF922, 0xF922, 0xF922 },
+{ 0xF923, 0xF923, 0xF923 },
+{ 0xF924, 0xF924, 0xF924 },
+{ 0xF925, 0xF925, 0xF925 },
+{ 0xF926, 0xF926, 0xF926 },
+{ 0xF927, 0xF927, 0xF927 },
+{ 0xF928, 0xF928, 0xF928 },
+{ 0xF929, 0xF929, 0xF929 },
+{ 0xF92A, 0xF92A, 0xF92A },
+{ 0xF92B, 0xF92B, 0xF92B },
+{ 0xF92C, 0xF92C, 0xF92C },
+{ 0xF92D, 0xF92D, 0xF92D },
+{ 0xF92E, 0xF92E, 0xF92E },
+{ 0xF92F, 0xF92F, 0xF92F },
+{ 0xF930, 0xF930, 0xF930 },
+{ 0xF931, 0xF931, 0xF931 },
+{ 0xF932, 0xF932, 0xF932 },
+{ 0xF933, 0xF933, 0xF933 },
+{ 0xF934, 0xF934, 0xF934 },
+{ 0xF935, 0xF935, 0xF935 },
+{ 0xF936, 0xF936, 0xF936 },
+{ 0xF937, 0xF937, 0xF937 },
+{ 0xF938, 0xF938, 0xF938 },
+{ 0xF939, 0xF939, 0xF939 },
+{ 0xF93A, 0xF93A, 0xF93A },
+{ 0xF93B, 0xF93B, 0xF93B },
+{ 0xF93C, 0xF93C, 0xF93C },
+{ 0xF93D, 0xF93D, 0xF93D },
+{ 0xF93E, 0xF93E, 0xF93E },
+{ 0xF93F, 0xF93F, 0xF93F },
+{ 0xF940, 0xF940, 0xF940 },
+{ 0xF941, 0xF941, 0xF941 },
+{ 0xF942, 0xF942, 0xF942 },
+{ 0xF943, 0xF943, 0xF943 },
+{ 0xF944, 0xF944, 0xF944 },
+{ 0xF945, 0xF945, 0xF945 },
+{ 0xF946, 0xF946, 0xF946 },
+{ 0xF947, 0xF947, 0xF947 },
+{ 0xF948, 0xF948, 0xF948 },
+{ 0xF949, 0xF949, 0xF949 },
+{ 0xF94A, 0xF94A, 0xF94A },
+{ 0xF94B, 0xF94B, 0xF94B },
+{ 0xF94C, 0xF94C, 0xF94C },
+{ 0xF94D, 0xF94D, 0xF94D },
+{ 0xF94E, 0xF94E, 0xF94E },
+{ 0xF94F, 0xF94F, 0xF94F },
+{ 0xF950, 0xF950, 0xF950 },
+{ 0xF951, 0xF951, 0xF951 },
+{ 0xF952, 0xF952, 0xF952 },
+{ 0xF953, 0xF953, 0xF953 },
+{ 0xF954, 0xF954, 0xF954 },
+{ 0xF955, 0xF955, 0xF955 },
+{ 0xF956, 0xF956, 0xF956 },
+{ 0xF957, 0xF957, 0xF957 },
+{ 0xF958, 0xF958, 0xF958 },
+{ 0xF959, 0xF959, 0xF959 },
+{ 0xF95A, 0xF95A, 0xF95A },
+{ 0xF95B, 0xF95B, 0xF95B },
+{ 0xF95C, 0xF95C, 0xF95C },
+{ 0xF95D, 0xF95D, 0xF95D },
+{ 0xF95E, 0xF95E, 0xF95E },
+{ 0xF95F, 0xF95F, 0xF95F },
+{ 0xF960, 0xF960, 0xF960 },
+{ 0xF961, 0xF961, 0xF961 },
+{ 0xF962, 0xF962, 0xF962 },
+{ 0xF963, 0xF963, 0xF963 },
+{ 0xF964, 0xF964, 0xF964 },
+{ 0xF965, 0xF965, 0xF965 },
+{ 0xF966, 0xF966, 0xF966 },
+{ 0xF967, 0xF967, 0xF967 },
+{ 0xF968, 0xF968, 0xF968 },
+{ 0xF969, 0xF969, 0xF969 },
+{ 0xF96A, 0xF96A, 0xF96A },
+{ 0xF96B, 0xF96B, 0xF96B },
+{ 0xF96C, 0xF96C, 0xF96C },
+{ 0xF96D, 0xF96D, 0xF96D },
+{ 0xF96E, 0xF96E, 0xF96E },
+{ 0xF96F, 0xF96F, 0xF96F },
+{ 0xF970, 0xF970, 0xF970 },
+{ 0xF971, 0xF971, 0xF971 },
+{ 0xF972, 0xF972, 0xF972 },
+{ 0xF973, 0xF973, 0xF973 },
+{ 0xF974, 0xF974, 0xF974 },
+{ 0xF975, 0xF975, 0xF975 },
+{ 0xF976, 0xF976, 0xF976 },
+{ 0xF977, 0xF977, 0xF977 },
+{ 0xF978, 0xF978, 0xF978 },
+{ 0xF979, 0xF979, 0xF979 },
+{ 0xF97A, 0xF97A, 0xF97A },
+{ 0xF97B, 0xF97B, 0xF97B },
+{ 0xF97C, 0xF97C, 0xF97C },
+{ 0xF97D, 0xF97D, 0xF97D },
+{ 0xF97E, 0xF97E, 0xF97E },
+{ 0xF97F, 0xF97F, 0xF97F },
+{ 0xF980, 0xF980, 0xF980 },
+{ 0xF981, 0xF981, 0xF981 },
+{ 0xF982, 0xF982, 0xF982 },
+{ 0xF983, 0xF983, 0xF983 },
+{ 0xF984, 0xF984, 0xF984 },
+{ 0xF985, 0xF985, 0xF985 },
+{ 0xF986, 0xF986, 0xF986 },
+{ 0xF987, 0xF987, 0xF987 },
+{ 0xF988, 0xF988, 0xF988 },
+{ 0xF989, 0xF989, 0xF989 },
+{ 0xF98A, 0xF98A, 0xF98A },
+{ 0xF98B, 0xF98B, 0xF98B },
+{ 0xF98C, 0xF98C, 0xF98C },
+{ 0xF98D, 0xF98D, 0xF98D },
+{ 0xF98E, 0xF98E, 0xF98E },
+{ 0xF98F, 0xF98F, 0xF98F },
+{ 0xF990, 0xF990, 0xF990 },
+{ 0xF991, 0xF991, 0xF991 },
+{ 0xF992, 0xF992, 0xF992 },
+{ 0xF993, 0xF993, 0xF993 },
+{ 0xF994, 0xF994, 0xF994 },
+{ 0xF995, 0xF995, 0xF995 },
+{ 0xF996, 0xF996, 0xF996 },
+{ 0xF997, 0xF997, 0xF997 },
+{ 0xF998, 0xF998, 0xF998 },
+{ 0xF999, 0xF999, 0xF999 },
+{ 0xF99A, 0xF99A, 0xF99A },
+{ 0xF99B, 0xF99B, 0xF99B },
+{ 0xF99C, 0xF99C, 0xF99C },
+{ 0xF99D, 0xF99D, 0xF99D },
+{ 0xF99E, 0xF99E, 0xF99E },
+{ 0xF99F, 0xF99F, 0xF99F },
+{ 0xF9A0, 0xF9A0, 0xF9A0 },
+{ 0xF9A1, 0xF9A1, 0xF9A1 },
+{ 0xF9A2, 0xF9A2, 0xF9A2 },
+{ 0xF9A3, 0xF9A3, 0xF9A3 },
+{ 0xF9A4, 0xF9A4, 0xF9A4 },
+{ 0xF9A5, 0xF9A5, 0xF9A5 },
+{ 0xF9A6, 0xF9A6, 0xF9A6 },
+{ 0xF9A7, 0xF9A7, 0xF9A7 },
+{ 0xF9A8, 0xF9A8, 0xF9A8 },
+{ 0xF9A9, 0xF9A9, 0xF9A9 },
+{ 0xF9AA, 0xF9AA, 0xF9AA },
+{ 0xF9AB, 0xF9AB, 0xF9AB },
+{ 0xF9AC, 0xF9AC, 0xF9AC },
+{ 0xF9AD, 0xF9AD, 0xF9AD },
+{ 0xF9AE, 0xF9AE, 0xF9AE },
+{ 0xF9AF, 0xF9AF, 0xF9AF },
+{ 0xF9B0, 0xF9B0, 0xF9B0 },
+{ 0xF9B1, 0xF9B1, 0xF9B1 },
+{ 0xF9B2, 0xF9B2, 0xF9B2 },
+{ 0xF9B3, 0xF9B3, 0xF9B3 },
+{ 0xF9B4, 0xF9B4, 0xF9B4 },
+{ 0xF9B5, 0xF9B5, 0xF9B5 },
+{ 0xF9B6, 0xF9B6, 0xF9B6 },
+{ 0xF9B7, 0xF9B7, 0xF9B7 },
+{ 0xF9B8, 0xF9B8, 0xF9B8 },
+{ 0xF9B9, 0xF9B9, 0xF9B9 },
+{ 0xF9BA, 0xF9BA, 0xF9BA },
+{ 0xF9BB, 0xF9BB, 0xF9BB },
+{ 0xF9BC, 0xF9BC, 0xF9BC },
+{ 0xF9BD, 0xF9BD, 0xF9BD },
+{ 0xF9BE, 0xF9BE, 0xF9BE },
+{ 0xF9BF, 0xF9BF, 0xF9BF },
+{ 0xF9C0, 0xF9C0, 0xF9C0 },
+{ 0xF9C1, 0xF9C1, 0xF9C1 },
+{ 0xF9C2, 0xF9C2, 0xF9C2 },
+{ 0xF9C3, 0xF9C3, 0xF9C3 },
+{ 0xF9C4, 0xF9C4, 0xF9C4 },
+{ 0xF9C5, 0xF9C5, 0xF9C5 },
+{ 0xF9C6, 0xF9C6, 0xF9C6 },
+{ 0xF9C7, 0xF9C7, 0xF9C7 },
+{ 0xF9C8, 0xF9C8, 0xF9C8 },
+{ 0xF9C9, 0xF9C9, 0xF9C9 },
+{ 0xF9CA, 0xF9CA, 0xF9CA },
+{ 0xF9CB, 0xF9CB, 0xF9CB },
+{ 0xF9CC, 0xF9CC, 0xF9CC },
+{ 0xF9CD, 0xF9CD, 0xF9CD },
+{ 0xF9CE, 0xF9CE, 0xF9CE },
+{ 0xF9CF, 0xF9CF, 0xF9CF },
+{ 0xF9D0, 0xF9D0, 0xF9D0 },
+{ 0xF9D1, 0xF9D1, 0xF9D1 },
+{ 0xF9D2, 0xF9D2, 0xF9D2 },
+{ 0xF9D3, 0xF9D3, 0xF9D3 },
+{ 0xF9D4, 0xF9D4, 0xF9D4 },
+{ 0xF9D5, 0xF9D5, 0xF9D5 },
+{ 0xF9D6, 0xF9D6, 0xF9D6 },
+{ 0xF9D7, 0xF9D7, 0xF9D7 },
+{ 0xF9D8, 0xF9D8, 0xF9D8 },
+{ 0xF9D9, 0xF9D9, 0xF9D9 },
+{ 0xF9DA, 0xF9DA, 0xF9DA },
+{ 0xF9DB, 0xF9DB, 0xF9DB },
+{ 0xF9DC, 0xF9DC, 0xF9DC },
+{ 0xF9DD, 0xF9DD, 0xF9DD },
+{ 0xF9DE, 0xF9DE, 0xF9DE },
+{ 0xF9DF, 0xF9DF, 0xF9DF },
+{ 0xF9E0, 0xF9E0, 0xF9E0 },
+{ 0xF9E1, 0xF9E1, 0xF9E1 },
+{ 0xF9E2, 0xF9E2, 0xF9E2 },
+{ 0xF9E3, 0xF9E3, 0xF9E3 },
+{ 0xF9E4, 0xF9E4, 0xF9E4 },
+{ 0xF9E5, 0xF9E5, 0xF9E5 },
+{ 0xF9E6, 0xF9E6, 0xF9E6 },
+{ 0xF9E7, 0xF9E7, 0xF9E7 },
+{ 0xF9E8, 0xF9E8, 0xF9E8 },
+{ 0xF9E9, 0xF9E9, 0xF9E9 },
+{ 0xF9EA, 0xF9EA, 0xF9EA },
+{ 0xF9EB, 0xF9EB, 0xF9EB },
+{ 0xF9EC, 0xF9EC, 0xF9EC },
+{ 0xF9ED, 0xF9ED, 0xF9ED },
+{ 0xF9EE, 0xF9EE, 0xF9EE },
+{ 0xF9EF, 0xF9EF, 0xF9EF },
+{ 0xF9F0, 0xF9F0, 0xF9F0 },
+{ 0xF9F1, 0xF9F1, 0xF9F1 },
+{ 0xF9F2, 0xF9F2, 0xF9F2 },
+{ 0xF9F3, 0xF9F3, 0xF9F3 },
+{ 0xF9F4, 0xF9F4, 0xF9F4 },
+{ 0xF9F5, 0xF9F5, 0xF9F5 },
+{ 0xF9F6, 0xF9F6, 0xF9F6 },
+{ 0xF9F7, 0xF9F7, 0xF9F7 },
+{ 0xF9F8, 0xF9F8, 0xF9F8 },
+{ 0xF9F9, 0xF9F9, 0xF9F9 },
+{ 0xF9FA, 0xF9FA, 0xF9FA },
+{ 0xF9FB, 0xF9FB, 0xF9FB },
+{ 0xF9FC, 0xF9FC, 0xF9FC },
+{ 0xF9FD, 0xF9FD, 0xF9FD },
+{ 0xF9FE, 0xF9FE, 0xF9FE },
+{ 0xF9FF, 0xF9FF, 0xF9FF },
+{ 0xFA00, 0xFA00, 0xFA00 },
+{ 0xFA01, 0xFA01, 0xFA01 },
+{ 0xFA02, 0xFA02, 0xFA02 },
+{ 0xFA03, 0xFA03, 0xFA03 },
+{ 0xFA04, 0xFA04, 0xFA04 },
+{ 0xFA05, 0xFA05, 0xFA05 },
+{ 0xFA06, 0xFA06, 0xFA06 },
+{ 0xFA07, 0xFA07, 0xFA07 },
+{ 0xFA08, 0xFA08, 0xFA08 },
+{ 0xFA09, 0xFA09, 0xFA09 },
+{ 0xFA0A, 0xFA0A, 0xFA0A },
+{ 0xFA0B, 0xFA0B, 0xFA0B },
+{ 0xFA0C, 0xFA0C, 0xFA0C },
+{ 0xFA0D, 0xFA0D, 0xFA0D },
+{ 0xFA0E, 0xFA0E, 0xFA0E },
+{ 0xFA0F, 0xFA0F, 0xFA0F },
+{ 0xFA10, 0xFA10, 0xFA10 },
+{ 0xFA11, 0xFA11, 0xFA11 },
+{ 0xFA12, 0xFA12, 0xFA12 },
+{ 0xFA13, 0xFA13, 0xFA13 },
+{ 0xFA14, 0xFA14, 0xFA14 },
+{ 0xFA15, 0xFA15, 0xFA15 },
+{ 0xFA16, 0xFA16, 0xFA16 },
+{ 0xFA17, 0xFA17, 0xFA17 },
+{ 0xFA18, 0xFA18, 0xFA18 },
+{ 0xFA19, 0xFA19, 0xFA19 },
+{ 0xFA1A, 0xFA1A, 0xFA1A },
+{ 0xFA1B, 0xFA1B, 0xFA1B },
+{ 0xFA1C, 0xFA1C, 0xFA1C },
+{ 0xFA1D, 0xFA1D, 0xFA1D },
+{ 0xFA1E, 0xFA1E, 0xFA1E },
+{ 0xFA1F, 0xFA1F, 0xFA1F },
+{ 0xFA20, 0xFA20, 0xFA20 },
+{ 0xFA21, 0xFA21, 0xFA21 },
+{ 0xFA22, 0xFA22, 0xFA22 },
+{ 0xFA23, 0xFA23, 0xFA23 },
+{ 0xFA24, 0xFA24, 0xFA24 },
+{ 0xFA25, 0xFA25, 0xFA25 },
+{ 0xFA26, 0xFA26, 0xFA26 },
+{ 0xFA27, 0xFA27, 0xFA27 },
+{ 0xFA28, 0xFA28, 0xFA28 },
+{ 0xFA29, 0xFA29, 0xFA29 },
+{ 0xFA2A, 0xFA2A, 0xFA2A },
+{ 0xFA2B, 0xFA2B, 0xFA2B },
+{ 0xFA2C, 0xFA2C, 0xFA2C },
+{ 0xFA2D, 0xFA2D, 0xFA2D },
+{ 0xFA30, 0xFA30, 0xFA30 },
+{ 0xFA31, 0xFA31, 0xFA31 },
+{ 0xFA32, 0xFA32, 0xFA32 },
+{ 0xFA33, 0xFA33, 0xFA33 },
+{ 0xFA34, 0xFA34, 0xFA34 },
+{ 0xFA35, 0xFA35, 0xFA35 },
+{ 0xFA36, 0xFA36, 0xFA36 },
+{ 0xFA37, 0xFA37, 0xFA37 },
+{ 0xFA38, 0xFA38, 0xFA38 },
+{ 0xFA39, 0xFA39, 0xFA39 },
+{ 0xFA3A, 0xFA3A, 0xFA3A },
+{ 0xFA3B, 0xFA3B, 0xFA3B },
+{ 0xFA3C, 0xFA3C, 0xFA3C },
+{ 0xFA3D, 0xFA3D, 0xFA3D },
+{ 0xFA3E, 0xFA3E, 0xFA3E },
+{ 0xFA3F, 0xFA3F, 0xFA3F },
+{ 0xFA40, 0xFA40, 0xFA40 },
+{ 0xFA41, 0xFA41, 0xFA41 },
+{ 0xFA42, 0xFA42, 0xFA42 },
+{ 0xFA43, 0xFA43, 0xFA43 },
+{ 0xFA44, 0xFA44, 0xFA44 },
+{ 0xFA45, 0xFA45, 0xFA45 },
+{ 0xFA46, 0xFA46, 0xFA46 },
+{ 0xFA47, 0xFA47, 0xFA47 },
+{ 0xFA48, 0xFA48, 0xFA48 },
+{ 0xFA49, 0xFA49, 0xFA49 },
+{ 0xFA4A, 0xFA4A, 0xFA4A },
+{ 0xFA4B, 0xFA4B, 0xFA4B },
+{ 0xFA4C, 0xFA4C, 0xFA4C },
+{ 0xFA4D, 0xFA4D, 0xFA4D },
+{ 0xFA4E, 0xFA4E, 0xFA4E },
+{ 0xFA4F, 0xFA4F, 0xFA4F },
+{ 0xFA50, 0xFA50, 0xFA50 },
+{ 0xFA51, 0xFA51, 0xFA51 },
+{ 0xFA52, 0xFA52, 0xFA52 },
+{ 0xFA53, 0xFA53, 0xFA53 },
+{ 0xFA54, 0xFA54, 0xFA54 },
+{ 0xFA55, 0xFA55, 0xFA55 },
+{ 0xFA56, 0xFA56, 0xFA56 },
+{ 0xFA57, 0xFA57, 0xFA57 },
+{ 0xFA58, 0xFA58, 0xFA58 },
+{ 0xFA59, 0xFA59, 0xFA59 },
+{ 0xFA5A, 0xFA5A, 0xFA5A },
+{ 0xFA5B, 0xFA5B, 0xFA5B },
+{ 0xFA5C, 0xFA5C, 0xFA5C },
+{ 0xFA5D, 0xFA5D, 0xFA5D },
+{ 0xFA5E, 0xFA5E, 0xFA5E },
+{ 0xFA5F, 0xFA5F, 0xFA5F },
+{ 0xFA60, 0xFA60, 0xFA60 },
+{ 0xFA61, 0xFA61, 0xFA61 },
+{ 0xFA62, 0xFA62, 0xFA62 },
+{ 0xFA63, 0xFA63, 0xFA63 },
+{ 0xFA64, 0xFA64, 0xFA64 },
+{ 0xFA65, 0xFA65, 0xFA65 },
+{ 0xFA66, 0xFA66, 0xFA66 },
+{ 0xFA67, 0xFA67, 0xFA67 },
+{ 0xFA68, 0xFA68, 0xFA68 },
+{ 0xFA69, 0xFA69, 0xFA69 },
+{ 0xFA6A, 0xFA6A, 0xFA6A },
+{ 0xFA70, 0xFA70, 0xFA70 },
+{ 0xFA71, 0xFA71, 0xFA71 },
+{ 0xFA72, 0xFA72, 0xFA72 },
+{ 0xFA73, 0xFA73, 0xFA73 },
+{ 0xFA74, 0xFA74, 0xFA74 },
+{ 0xFA75, 0xFA75, 0xFA75 },
+{ 0xFA76, 0xFA76, 0xFA76 },
+{ 0xFA77, 0xFA77, 0xFA77 },
+{ 0xFA78, 0xFA78, 0xFA78 },
+{ 0xFA79, 0xFA79, 0xFA79 },
+{ 0xFA7A, 0xFA7A, 0xFA7A },
+{ 0xFA7B, 0xFA7B, 0xFA7B },
+{ 0xFA7C, 0xFA7C, 0xFA7C },
+{ 0xFA7D, 0xFA7D, 0xFA7D },
+{ 0xFA7E, 0xFA7E, 0xFA7E },
+{ 0xFA7F, 0xFA7F, 0xFA7F },
+{ 0xFA80, 0xFA80, 0xFA80 },
+{ 0xFA81, 0xFA81, 0xFA81 },
+{ 0xFA82, 0xFA82, 0xFA82 },
+{ 0xFA83, 0xFA83, 0xFA83 },
+{ 0xFA84, 0xFA84, 0xFA84 },
+{ 0xFA85, 0xFA85, 0xFA85 },
+{ 0xFA86, 0xFA86, 0xFA86 },
+{ 0xFA87, 0xFA87, 0xFA87 },
+{ 0xFA88, 0xFA88, 0xFA88 },
+{ 0xFA89, 0xFA89, 0xFA89 },
+{ 0xFA8A, 0xFA8A, 0xFA8A },
+{ 0xFA8B, 0xFA8B, 0xFA8B },
+{ 0xFA8C, 0xFA8C, 0xFA8C },
+{ 0xFA8D, 0xFA8D, 0xFA8D },
+{ 0xFA8E, 0xFA8E, 0xFA8E },
+{ 0xFA8F, 0xFA8F, 0xFA8F },
+{ 0xFA90, 0xFA90, 0xFA90 },
+{ 0xFA91, 0xFA91, 0xFA91 },
+{ 0xFA92, 0xFA92, 0xFA92 },
+{ 0xFA93, 0xFA93, 0xFA93 },
+{ 0xFA94, 0xFA94, 0xFA94 },
+{ 0xFA95, 0xFA95, 0xFA95 },
+{ 0xFA96, 0xFA96, 0xFA96 },
+{ 0xFA97, 0xFA97, 0xFA97 },
+{ 0xFA98, 0xFA98, 0xFA98 },
+{ 0xFA99, 0xFA99, 0xFA99 },
+{ 0xFA9A, 0xFA9A, 0xFA9A },
+{ 0xFA9B, 0xFA9B, 0xFA9B },
+{ 0xFA9C, 0xFA9C, 0xFA9C },
+{ 0xFA9D, 0xFA9D, 0xFA9D },
+{ 0xFA9E, 0xFA9E, 0xFA9E },
+{ 0xFA9F, 0xFA9F, 0xFA9F },
+{ 0xFAA0, 0xFAA0, 0xFAA0 },
+{ 0xFAA1, 0xFAA1, 0xFAA1 },
+{ 0xFAA2, 0xFAA2, 0xFAA2 },
+{ 0xFAA3, 0xFAA3, 0xFAA3 },
+{ 0xFAA4, 0xFAA4, 0xFAA4 },
+{ 0xFAA5, 0xFAA5, 0xFAA5 },
+{ 0xFAA6, 0xFAA6, 0xFAA6 },
+{ 0xFAA7, 0xFAA7, 0xFAA7 },
+{ 0xFAA8, 0xFAA8, 0xFAA8 },
+{ 0xFAA9, 0xFAA9, 0xFAA9 },
+{ 0xFAAA, 0xFAAA, 0xFAAA },
+{ 0xFAAB, 0xFAAB, 0xFAAB },
+{ 0xFAAC, 0xFAAC, 0xFAAC },
+{ 0xFAAD, 0xFAAD, 0xFAAD },
+{ 0xFAAE, 0xFAAE, 0xFAAE },
+{ 0xFAAF, 0xFAAF, 0xFAAF },
+{ 0xFAB0, 0xFAB0, 0xFAB0 },
+{ 0xFAB1, 0xFAB1, 0xFAB1 },
+{ 0xFAB2, 0xFAB2, 0xFAB2 },
+{ 0xFAB3, 0xFAB3, 0xFAB3 },
+{ 0xFAB4, 0xFAB4, 0xFAB4 },
+{ 0xFAB5, 0xFAB5, 0xFAB5 },
+{ 0xFAB6, 0xFAB6, 0xFAB6 },
+{ 0xFAB7, 0xFAB7, 0xFAB7 },
+{ 0xFAB8, 0xFAB8, 0xFAB8 },
+{ 0xFAB9, 0xFAB9, 0xFAB9 },
+{ 0xFABA, 0xFABA, 0xFABA },
+{ 0xFABB, 0xFABB, 0xFABB },
+{ 0xFABC, 0xFABC, 0xFABC },
+{ 0xFABD, 0xFABD, 0xFABD },
+{ 0xFABE, 0xFABE, 0xFABE },
+{ 0xFABF, 0xFABF, 0xFABF },
+{ 0xFAC0, 0xFAC0, 0xFAC0 },
+{ 0xFAC1, 0xFAC1, 0xFAC1 },
+{ 0xFAC2, 0xFAC2, 0xFAC2 },
+{ 0xFAC3, 0xFAC3, 0xFAC3 },
+{ 0xFAC4, 0xFAC4, 0xFAC4 },
+{ 0xFAC5, 0xFAC5, 0xFAC5 },
+{ 0xFAC6, 0xFAC6, 0xFAC6 },
+{ 0xFAC7, 0xFAC7, 0xFAC7 },
+{ 0xFAC8, 0xFAC8, 0xFAC8 },
+{ 0xFAC9, 0xFAC9, 0xFAC9 },
+{ 0xFACA, 0xFACA, 0xFACA },
+{ 0xFACB, 0xFACB, 0xFACB },
+{ 0xFACC, 0xFACC, 0xFACC },
+{ 0xFACD, 0xFACD, 0xFACD },
+{ 0xFACE, 0xFACE, 0xFACE },
+{ 0xFACF, 0xFACF, 0xFACF },
+{ 0xFAD0, 0xFAD0, 0xFAD0 },
+{ 0xFAD1, 0xFAD1, 0xFAD1 },
+{ 0xFAD2, 0xFAD2, 0xFAD2 },
+{ 0xFAD3, 0xFAD3, 0xFAD3 },
+{ 0xFAD4, 0xFAD4, 0xFAD4 },
+{ 0xFAD5, 0xFAD5, 0xFAD5 },
+{ 0xFAD6, 0xFAD6, 0xFAD6 },
+{ 0xFAD7, 0xFAD7, 0xFAD7 },
+{ 0xFAD8, 0xFAD8, 0xFAD8 },
+{ 0xFAD9, 0xFAD9, 0xFAD9 },
+{ 0xFB00, 0xFB00, 0xFB00 },
+{ 0xFB01, 0xFB01, 0xFB01 },
+{ 0xFB02, 0xFB02, 0xFB02 },
+{ 0xFB03, 0xFB03, 0xFB03 },
+{ 0xFB04, 0xFB04, 0xFB04 },
+{ 0xFB05, 0xFB05, 0xFB05 },
+{ 0xFB06, 0xFB06, 0xFB06 },
+{ 0xFB13, 0xFB13, 0xFB13 },
+{ 0xFB14, 0xFB14, 0xFB14 },
+{ 0xFB15, 0xFB15, 0xFB15 },
+{ 0xFB16, 0xFB16, 0xFB16 },
+{ 0xFB17, 0xFB17, 0xFB17 },
+{ 0xFB1D, 0xFB1D, 0xFB1D },
+{ 0xFB1E, 0xFB1E, 0xFB1E },
+{ 0xFB1F, 0xFB1F, 0xFB1F },
+{ 0xFB20, 0xFB20, 0xFB20 },
+{ 0xFB21, 0xFB21, 0xFB21 },
+{ 0xFB22, 0xFB22, 0xFB22 },
+{ 0xFB23, 0xFB23, 0xFB23 },
+{ 0xFB24, 0xFB24, 0xFB24 },
+{ 0xFB25, 0xFB25, 0xFB25 },
+{ 0xFB26, 0xFB26, 0xFB26 },
+{ 0xFB27, 0xFB27, 0xFB27 },
+{ 0xFB28, 0xFB28, 0xFB28 },
+{ 0xFB2A, 0xFB2A, 0xFB2A },
+{ 0xFB2B, 0xFB2B, 0xFB2B },
+{ 0xFB2C, 0xFB2C, 0xFB2C },
+{ 0xFB2D, 0xFB2D, 0xFB2D },
+{ 0xFB2E, 0xFB2E, 0xFB2E },
+{ 0xFB2F, 0xFB2F, 0xFB2F },
+{ 0xFB30, 0xFB30, 0xFB30 },
+{ 0xFB31, 0xFB31, 0xFB31 },
+{ 0xFB32, 0xFB32, 0xFB32 },
+{ 0xFB33, 0xFB33, 0xFB33 },
+{ 0xFB34, 0xFB34, 0xFB34 },
+{ 0xFB35, 0xFB35, 0xFB35 },
+{ 0xFB36, 0xFB36, 0xFB36 },
+{ 0xFB38, 0xFB38, 0xFB38 },
+{ 0xFB39, 0xFB39, 0xFB39 },
+{ 0xFB3A, 0xFB3A, 0xFB3A },
+{ 0xFB3B, 0xFB3B, 0xFB3B },
+{ 0xFB3C, 0xFB3C, 0xFB3C },
+{ 0xFB3E, 0xFB3E, 0xFB3E },
+{ 0xFB40, 0xFB40, 0xFB40 },
+{ 0xFB41, 0xFB41, 0xFB41 },
+{ 0xFB43, 0xFB43, 0xFB43 },
+{ 0xFB44, 0xFB44, 0xFB44 },
+{ 0xFB46, 0xFB46, 0xFB46 },
+{ 0xFB47, 0xFB47, 0xFB47 },
+{ 0xFB48, 0xFB48, 0xFB48 },
+{ 0xFB49, 0xFB49, 0xFB49 },
+{ 0xFB4A, 0xFB4A, 0xFB4A },
+{ 0xFB4B, 0xFB4B, 0xFB4B },
+{ 0xFB4C, 0xFB4C, 0xFB4C },
+{ 0xFB4D, 0xFB4D, 0xFB4D },
+{ 0xFB4E, 0xFB4E, 0xFB4E },
+{ 0xFB4F, 0xFB4F, 0xFB4F },
+{ 0xFB50, 0xFB50, 0xFB50 },
+{ 0xFB51, 0xFB51, 0xFB51 },
+{ 0xFB52, 0xFB52, 0xFB52 },
+{ 0xFB53, 0xFB53, 0xFB53 },
+{ 0xFB54, 0xFB54, 0xFB54 },
+{ 0xFB55, 0xFB55, 0xFB55 },
+{ 0xFB56, 0xFB56, 0xFB56 },
+{ 0xFB57, 0xFB57, 0xFB57 },
+{ 0xFB58, 0xFB58, 0xFB58 },
+{ 0xFB59, 0xFB59, 0xFB59 },
+{ 0xFB5A, 0xFB5A, 0xFB5A },
+{ 0xFB5B, 0xFB5B, 0xFB5B },
+{ 0xFB5C, 0xFB5C, 0xFB5C },
+{ 0xFB5D, 0xFB5D, 0xFB5D },
+{ 0xFB5E, 0xFB5E, 0xFB5E },
+{ 0xFB5F, 0xFB5F, 0xFB5F },
+{ 0xFB60, 0xFB60, 0xFB60 },
+{ 0xFB61, 0xFB61, 0xFB61 },
+{ 0xFB62, 0xFB62, 0xFB62 },
+{ 0xFB63, 0xFB63, 0xFB63 },
+{ 0xFB64, 0xFB64, 0xFB64 },
+{ 0xFB65, 0xFB65, 0xFB65 },
+{ 0xFB66, 0xFB66, 0xFB66 },
+{ 0xFB67, 0xFB67, 0xFB67 },
+{ 0xFB68, 0xFB68, 0xFB68 },
+{ 0xFB69, 0xFB69, 0xFB69 },
+{ 0xFB6A, 0xFB6A, 0xFB6A },
+{ 0xFB6B, 0xFB6B, 0xFB6B },
+{ 0xFB6C, 0xFB6C, 0xFB6C },
+{ 0xFB6D, 0xFB6D, 0xFB6D },
+{ 0xFB6E, 0xFB6E, 0xFB6E },
+{ 0xFB6F, 0xFB6F, 0xFB6F },
+{ 0xFB70, 0xFB70, 0xFB70 },
+{ 0xFB71, 0xFB71, 0xFB71 },
+{ 0xFB72, 0xFB72, 0xFB72 },
+{ 0xFB73, 0xFB73, 0xFB73 },
+{ 0xFB74, 0xFB74, 0xFB74 },
+{ 0xFB75, 0xFB75, 0xFB75 },
+{ 0xFB76, 0xFB76, 0xFB76 },
+{ 0xFB77, 0xFB77, 0xFB77 },
+{ 0xFB78, 0xFB78, 0xFB78 },
+{ 0xFB79, 0xFB79, 0xFB79 },
+{ 0xFB7A, 0xFB7A, 0xFB7A },
+{ 0xFB7B, 0xFB7B, 0xFB7B },
+{ 0xFB7C, 0xFB7C, 0xFB7C },
+{ 0xFB7D, 0xFB7D, 0xFB7D },
+{ 0xFB7E, 0xFB7E, 0xFB7E },
+{ 0xFB7F, 0xFB7F, 0xFB7F },
+{ 0xFB80, 0xFB80, 0xFB80 },
+{ 0xFB81, 0xFB81, 0xFB81 },
+{ 0xFB82, 0xFB82, 0xFB82 },
+{ 0xFB83, 0xFB83, 0xFB83 },
+{ 0xFB84, 0xFB84, 0xFB84 },
+{ 0xFB85, 0xFB85, 0xFB85 },
+{ 0xFB86, 0xFB86, 0xFB86 },
+{ 0xFB87, 0xFB87, 0xFB87 },
+{ 0xFB88, 0xFB88, 0xFB88 },
+{ 0xFB89, 0xFB89, 0xFB89 },
+{ 0xFB8A, 0xFB8A, 0xFB8A },
+{ 0xFB8B, 0xFB8B, 0xFB8B },
+{ 0xFB8C, 0xFB8C, 0xFB8C },
+{ 0xFB8D, 0xFB8D, 0xFB8D },
+{ 0xFB8E, 0xFB8E, 0xFB8E },
+{ 0xFB8F, 0xFB8F, 0xFB8F },
+{ 0xFB90, 0xFB90, 0xFB90 },
+{ 0xFB91, 0xFB91, 0xFB91 },
+{ 0xFB92, 0xFB92, 0xFB92 },
+{ 0xFB93, 0xFB93, 0xFB93 },
+{ 0xFB94, 0xFB94, 0xFB94 },
+{ 0xFB95, 0xFB95, 0xFB95 },
+{ 0xFB96, 0xFB96, 0xFB96 },
+{ 0xFB97, 0xFB97, 0xFB97 },
+{ 0xFB98, 0xFB98, 0xFB98 },
+{ 0xFB99, 0xFB99, 0xFB99 },
+{ 0xFB9A, 0xFB9A, 0xFB9A },
+{ 0xFB9B, 0xFB9B, 0xFB9B },
+{ 0xFB9C, 0xFB9C, 0xFB9C },
+{ 0xFB9D, 0xFB9D, 0xFB9D },
+{ 0xFB9E, 0xFB9E, 0xFB9E },
+{ 0xFB9F, 0xFB9F, 0xFB9F },
+{ 0xFBA0, 0xFBA0, 0xFBA0 },
+{ 0xFBA1, 0xFBA1, 0xFBA1 },
+{ 0xFBA2, 0xFBA2, 0xFBA2 },
+{ 0xFBA3, 0xFBA3, 0xFBA3 },
+{ 0xFBA4, 0xFBA4, 0xFBA4 },
+{ 0xFBA5, 0xFBA5, 0xFBA5 },
+{ 0xFBA6, 0xFBA6, 0xFBA6 },
+{ 0xFBA7, 0xFBA7, 0xFBA7 },
+{ 0xFBA8, 0xFBA8, 0xFBA8 },
+{ 0xFBA9, 0xFBA9, 0xFBA9 },
+{ 0xFBAA, 0xFBAA, 0xFBAA },
+{ 0xFBAB, 0xFBAB, 0xFBAB },
+{ 0xFBAC, 0xFBAC, 0xFBAC },
+{ 0xFBAD, 0xFBAD, 0xFBAD },
+{ 0xFBAE, 0xFBAE, 0xFBAE },
+{ 0xFBAF, 0xFBAF, 0xFBAF },
+{ 0xFBB0, 0xFBB0, 0xFBB0 },
+{ 0xFBB1, 0xFBB1, 0xFBB1 },
+{ 0xFBD3, 0xFBD3, 0xFBD3 },
+{ 0xFBD4, 0xFBD4, 0xFBD4 },
+{ 0xFBD5, 0xFBD5, 0xFBD5 },
+{ 0xFBD6, 0xFBD6, 0xFBD6 },
+{ 0xFBD7, 0xFBD7, 0xFBD7 },
+{ 0xFBD8, 0xFBD8, 0xFBD8 },
+{ 0xFBD9, 0xFBD9, 0xFBD9 },
+{ 0xFBDA, 0xFBDA, 0xFBDA },
+{ 0xFBDB, 0xFBDB, 0xFBDB },
+{ 0xFBDC, 0xFBDC, 0xFBDC },
+{ 0xFBDD, 0xFBDD, 0xFBDD },
+{ 0xFBDE, 0xFBDE, 0xFBDE },
+{ 0xFBDF, 0xFBDF, 0xFBDF },
+{ 0xFBE0, 0xFBE0, 0xFBE0 },
+{ 0xFBE1, 0xFBE1, 0xFBE1 },
+{ 0xFBE2, 0xFBE2, 0xFBE2 },
+{ 0xFBE3, 0xFBE3, 0xFBE3 },
+{ 0xFBE4, 0xFBE4, 0xFBE4 },
+{ 0xFBE5, 0xFBE5, 0xFBE5 },
+{ 0xFBE6, 0xFBE6, 0xFBE6 },
+{ 0xFBE7, 0xFBE7, 0xFBE7 },
+{ 0xFBE8, 0xFBE8, 0xFBE8 },
+{ 0xFBE9, 0xFBE9, 0xFBE9 },
+{ 0xFBEA, 0xFBEA, 0xFBEA },
+{ 0xFBEB, 0xFBEB, 0xFBEB },
+{ 0xFBEC, 0xFBEC, 0xFBEC },
+{ 0xFBED, 0xFBED, 0xFBED },
+{ 0xFBEE, 0xFBEE, 0xFBEE },
+{ 0xFBEF, 0xFBEF, 0xFBEF },
+{ 0xFBF0, 0xFBF0, 0xFBF0 },
+{ 0xFBF1, 0xFBF1, 0xFBF1 },
+{ 0xFBF2, 0xFBF2, 0xFBF2 },
+{ 0xFBF3, 0xFBF3, 0xFBF3 },
+{ 0xFBF4, 0xFBF4, 0xFBF4 },
+{ 0xFBF5, 0xFBF5, 0xFBF5 },
+{ 0xFBF6, 0xFBF6, 0xFBF6 },
+{ 0xFBF7, 0xFBF7, 0xFBF7 },
+{ 0xFBF8, 0xFBF8, 0xFBF8 },
+{ 0xFBF9, 0xFBF9, 0xFBF9 },
+{ 0xFBFA, 0xFBFA, 0xFBFA },
+{ 0xFBFB, 0xFBFB, 0xFBFB },
+{ 0xFBFC, 0xFBFC, 0xFBFC },
+{ 0xFBFD, 0xFBFD, 0xFBFD },
+{ 0xFBFE, 0xFBFE, 0xFBFE },
+{ 0xFBFF, 0xFBFF, 0xFBFF },
+{ 0xFC00, 0xFC00, 0xFC00 },
+{ 0xFC01, 0xFC01, 0xFC01 },
+{ 0xFC02, 0xFC02, 0xFC02 },
+{ 0xFC03, 0xFC03, 0xFC03 },
+{ 0xFC04, 0xFC04, 0xFC04 },
+{ 0xFC05, 0xFC05, 0xFC05 },
+{ 0xFC06, 0xFC06, 0xFC06 },
+{ 0xFC07, 0xFC07, 0xFC07 },
+{ 0xFC08, 0xFC08, 0xFC08 },
+{ 0xFC09, 0xFC09, 0xFC09 },
+{ 0xFC0A, 0xFC0A, 0xFC0A },
+{ 0xFC0B, 0xFC0B, 0xFC0B },
+{ 0xFC0C, 0xFC0C, 0xFC0C },
+{ 0xFC0D, 0xFC0D, 0xFC0D },
+{ 0xFC0E, 0xFC0E, 0xFC0E },
+{ 0xFC0F, 0xFC0F, 0xFC0F },
+{ 0xFC10, 0xFC10, 0xFC10 },
+{ 0xFC11, 0xFC11, 0xFC11 },
+{ 0xFC12, 0xFC12, 0xFC12 },
+{ 0xFC13, 0xFC13, 0xFC13 },
+{ 0xFC14, 0xFC14, 0xFC14 },
+{ 0xFC15, 0xFC15, 0xFC15 },
+{ 0xFC16, 0xFC16, 0xFC16 },
+{ 0xFC17, 0xFC17, 0xFC17 },
+{ 0xFC18, 0xFC18, 0xFC18 },
+{ 0xFC19, 0xFC19, 0xFC19 },
+{ 0xFC1A, 0xFC1A, 0xFC1A },
+{ 0xFC1B, 0xFC1B, 0xFC1B },
+{ 0xFC1C, 0xFC1C, 0xFC1C },
+{ 0xFC1D, 0xFC1D, 0xFC1D },
+{ 0xFC1E, 0xFC1E, 0xFC1E },
+{ 0xFC1F, 0xFC1F, 0xFC1F },
+{ 0xFC20, 0xFC20, 0xFC20 },
+{ 0xFC21, 0xFC21, 0xFC21 },
+{ 0xFC22, 0xFC22, 0xFC22 },
+{ 0xFC23, 0xFC23, 0xFC23 },
+{ 0xFC24, 0xFC24, 0xFC24 },
+{ 0xFC25, 0xFC25, 0xFC25 },
+{ 0xFC26, 0xFC26, 0xFC26 },
+{ 0xFC27, 0xFC27, 0xFC27 },
+{ 0xFC28, 0xFC28, 0xFC28 },
+{ 0xFC29, 0xFC29, 0xFC29 },
+{ 0xFC2A, 0xFC2A, 0xFC2A },
+{ 0xFC2B, 0xFC2B, 0xFC2B },
+{ 0xFC2C, 0xFC2C, 0xFC2C },
+{ 0xFC2D, 0xFC2D, 0xFC2D },
+{ 0xFC2E, 0xFC2E, 0xFC2E },
+{ 0xFC2F, 0xFC2F, 0xFC2F },
+{ 0xFC30, 0xFC30, 0xFC30 },
+{ 0xFC31, 0xFC31, 0xFC31 },
+{ 0xFC32, 0xFC32, 0xFC32 },
+{ 0xFC33, 0xFC33, 0xFC33 },
+{ 0xFC34, 0xFC34, 0xFC34 },
+{ 0xFC35, 0xFC35, 0xFC35 },
+{ 0xFC36, 0xFC36, 0xFC36 },
+{ 0xFC37, 0xFC37, 0xFC37 },
+{ 0xFC38, 0xFC38, 0xFC38 },
+{ 0xFC39, 0xFC39, 0xFC39 },
+{ 0xFC3A, 0xFC3A, 0xFC3A },
+{ 0xFC3B, 0xFC3B, 0xFC3B },
+{ 0xFC3C, 0xFC3C, 0xFC3C },
+{ 0xFC3D, 0xFC3D, 0xFC3D },
+{ 0xFC3E, 0xFC3E, 0xFC3E },
+{ 0xFC3F, 0xFC3F, 0xFC3F },
+{ 0xFC40, 0xFC40, 0xFC40 },
+{ 0xFC41, 0xFC41, 0xFC41 },
+{ 0xFC42, 0xFC42, 0xFC42 },
+{ 0xFC43, 0xFC43, 0xFC43 },
+{ 0xFC44, 0xFC44, 0xFC44 },
+{ 0xFC45, 0xFC45, 0xFC45 },
+{ 0xFC46, 0xFC46, 0xFC46 },
+{ 0xFC47, 0xFC47, 0xFC47 },
+{ 0xFC48, 0xFC48, 0xFC48 },
+{ 0xFC49, 0xFC49, 0xFC49 },
+{ 0xFC4A, 0xFC4A, 0xFC4A },
+{ 0xFC4B, 0xFC4B, 0xFC4B },
+{ 0xFC4C, 0xFC4C, 0xFC4C },
+{ 0xFC4D, 0xFC4D, 0xFC4D },
+{ 0xFC4E, 0xFC4E, 0xFC4E },
+{ 0xFC4F, 0xFC4F, 0xFC4F },
+{ 0xFC50, 0xFC50, 0xFC50 },
+{ 0xFC51, 0xFC51, 0xFC51 },
+{ 0xFC52, 0xFC52, 0xFC52 },
+{ 0xFC53, 0xFC53, 0xFC53 },
+{ 0xFC54, 0xFC54, 0xFC54 },
+{ 0xFC55, 0xFC55, 0xFC55 },
+{ 0xFC56, 0xFC56, 0xFC56 },
+{ 0xFC57, 0xFC57, 0xFC57 },
+{ 0xFC58, 0xFC58, 0xFC58 },
+{ 0xFC59, 0xFC59, 0xFC59 },
+{ 0xFC5A, 0xFC5A, 0xFC5A },
+{ 0xFC5B, 0xFC5B, 0xFC5B },
+{ 0xFC5C, 0xFC5C, 0xFC5C },
+{ 0xFC5D, 0xFC5D, 0xFC5D },
+{ 0xFC5E, 0xFC5E, 0xFC5E },
+{ 0xFC5F, 0xFC5F, 0xFC5F },
+{ 0xFC60, 0xFC60, 0xFC60 },
+{ 0xFC61, 0xFC61, 0xFC61 },
+{ 0xFC62, 0xFC62, 0xFC62 },
+{ 0xFC63, 0xFC63, 0xFC63 },
+{ 0xFC64, 0xFC64, 0xFC64 },
+{ 0xFC65, 0xFC65, 0xFC65 },
+{ 0xFC66, 0xFC66, 0xFC66 },
+{ 0xFC67, 0xFC67, 0xFC67 },
+{ 0xFC68, 0xFC68, 0xFC68 },
+{ 0xFC69, 0xFC69, 0xFC69 },
+{ 0xFC6A, 0xFC6A, 0xFC6A },
+{ 0xFC6B, 0xFC6B, 0xFC6B },
+{ 0xFC6C, 0xFC6C, 0xFC6C },
+{ 0xFC6D, 0xFC6D, 0xFC6D },
+{ 0xFC6E, 0xFC6E, 0xFC6E },
+{ 0xFC6F, 0xFC6F, 0xFC6F },
+{ 0xFC70, 0xFC70, 0xFC70 },
+{ 0xFC71, 0xFC71, 0xFC71 },
+{ 0xFC72, 0xFC72, 0xFC72 },
+{ 0xFC73, 0xFC73, 0xFC73 },
+{ 0xFC74, 0xFC74, 0xFC74 },
+{ 0xFC75, 0xFC75, 0xFC75 },
+{ 0xFC76, 0xFC76, 0xFC76 },
+{ 0xFC77, 0xFC77, 0xFC77 },
+{ 0xFC78, 0xFC78, 0xFC78 },
+{ 0xFC79, 0xFC79, 0xFC79 },
+{ 0xFC7A, 0xFC7A, 0xFC7A },
+{ 0xFC7B, 0xFC7B, 0xFC7B },
+{ 0xFC7C, 0xFC7C, 0xFC7C },
+{ 0xFC7D, 0xFC7D, 0xFC7D },
+{ 0xFC7E, 0xFC7E, 0xFC7E },
+{ 0xFC7F, 0xFC7F, 0xFC7F },
+{ 0xFC80, 0xFC80, 0xFC80 },
+{ 0xFC81, 0xFC81, 0xFC81 },
+{ 0xFC82, 0xFC82, 0xFC82 },
+{ 0xFC83, 0xFC83, 0xFC83 },
+{ 0xFC84, 0xFC84, 0xFC84 },
+{ 0xFC85, 0xFC85, 0xFC85 },
+{ 0xFC86, 0xFC86, 0xFC86 },
+{ 0xFC87, 0xFC87, 0xFC87 },
+{ 0xFC88, 0xFC88, 0xFC88 },
+{ 0xFC89, 0xFC89, 0xFC89 },
+{ 0xFC8A, 0xFC8A, 0xFC8A },
+{ 0xFC8B, 0xFC8B, 0xFC8B },
+{ 0xFC8C, 0xFC8C, 0xFC8C },
+{ 0xFC8D, 0xFC8D, 0xFC8D },
+{ 0xFC8E, 0xFC8E, 0xFC8E },
+{ 0xFC8F, 0xFC8F, 0xFC8F },
+{ 0xFC90, 0xFC90, 0xFC90 },
+{ 0xFC91, 0xFC91, 0xFC91 },
+{ 0xFC92, 0xFC92, 0xFC92 },
+{ 0xFC93, 0xFC93, 0xFC93 },
+{ 0xFC94, 0xFC94, 0xFC94 },
+{ 0xFC95, 0xFC95, 0xFC95 },
+{ 0xFC96, 0xFC96, 0xFC96 },
+{ 0xFC97, 0xFC97, 0xFC97 },
+{ 0xFC98, 0xFC98, 0xFC98 },
+{ 0xFC99, 0xFC99, 0xFC99 },
+{ 0xFC9A, 0xFC9A, 0xFC9A },
+{ 0xFC9B, 0xFC9B, 0xFC9B },
+{ 0xFC9C, 0xFC9C, 0xFC9C },
+{ 0xFC9D, 0xFC9D, 0xFC9D },
+{ 0xFC9E, 0xFC9E, 0xFC9E },
+{ 0xFC9F, 0xFC9F, 0xFC9F },
+{ 0xFCA0, 0xFCA0, 0xFCA0 },
+{ 0xFCA1, 0xFCA1, 0xFCA1 },
+{ 0xFCA2, 0xFCA2, 0xFCA2 },
+{ 0xFCA3, 0xFCA3, 0xFCA3 },
+{ 0xFCA4, 0xFCA4, 0xFCA4 },
+{ 0xFCA5, 0xFCA5, 0xFCA5 },
+{ 0xFCA6, 0xFCA6, 0xFCA6 },
+{ 0xFCA7, 0xFCA7, 0xFCA7 },
+{ 0xFCA8, 0xFCA8, 0xFCA8 },
+{ 0xFCA9, 0xFCA9, 0xFCA9 },
+{ 0xFCAA, 0xFCAA, 0xFCAA },
+{ 0xFCAB, 0xFCAB, 0xFCAB },
+{ 0xFCAC, 0xFCAC, 0xFCAC },
+{ 0xFCAD, 0xFCAD, 0xFCAD },
+{ 0xFCAE, 0xFCAE, 0xFCAE },
+{ 0xFCAF, 0xFCAF, 0xFCAF },
+{ 0xFCB0, 0xFCB0, 0xFCB0 },
+{ 0xFCB1, 0xFCB1, 0xFCB1 },
+{ 0xFCB2, 0xFCB2, 0xFCB2 },
+{ 0xFCB3, 0xFCB3, 0xFCB3 },
+{ 0xFCB4, 0xFCB4, 0xFCB4 },
+{ 0xFCB5, 0xFCB5, 0xFCB5 },
+{ 0xFCB6, 0xFCB6, 0xFCB6 },
+{ 0xFCB7, 0xFCB7, 0xFCB7 },
+{ 0xFCB8, 0xFCB8, 0xFCB8 },
+{ 0xFCB9, 0xFCB9, 0xFCB9 },
+{ 0xFCBA, 0xFCBA, 0xFCBA },
+{ 0xFCBB, 0xFCBB, 0xFCBB },
+{ 0xFCBC, 0xFCBC, 0xFCBC },
+{ 0xFCBD, 0xFCBD, 0xFCBD },
+{ 0xFCBE, 0xFCBE, 0xFCBE },
+{ 0xFCBF, 0xFCBF, 0xFCBF },
+{ 0xFCC0, 0xFCC0, 0xFCC0 },
+{ 0xFCC1, 0xFCC1, 0xFCC1 },
+{ 0xFCC2, 0xFCC2, 0xFCC2 },
+{ 0xFCC3, 0xFCC3, 0xFCC3 },
+{ 0xFCC4, 0xFCC4, 0xFCC4 },
+{ 0xFCC5, 0xFCC5, 0xFCC5 },
+{ 0xFCC6, 0xFCC6, 0xFCC6 },
+{ 0xFCC7, 0xFCC7, 0xFCC7 },
+{ 0xFCC8, 0xFCC8, 0xFCC8 },
+{ 0xFCC9, 0xFCC9, 0xFCC9 },
+{ 0xFCCA, 0xFCCA, 0xFCCA },
+{ 0xFCCB, 0xFCCB, 0xFCCB },
+{ 0xFCCC, 0xFCCC, 0xFCCC },
+{ 0xFCCD, 0xFCCD, 0xFCCD },
+{ 0xFCCE, 0xFCCE, 0xFCCE },
+{ 0xFCCF, 0xFCCF, 0xFCCF },
+{ 0xFCD0, 0xFCD0, 0xFCD0 },
+{ 0xFCD1, 0xFCD1, 0xFCD1 },
+{ 0xFCD2, 0xFCD2, 0xFCD2 },
+{ 0xFCD3, 0xFCD3, 0xFCD3 },
+{ 0xFCD4, 0xFCD4, 0xFCD4 },
+{ 0xFCD5, 0xFCD5, 0xFCD5 },
+{ 0xFCD6, 0xFCD6, 0xFCD6 },
+{ 0xFCD7, 0xFCD7, 0xFCD7 },
+{ 0xFCD8, 0xFCD8, 0xFCD8 },
+{ 0xFCD9, 0xFCD9, 0xFCD9 },
+{ 0xFCDA, 0xFCDA, 0xFCDA },
+{ 0xFCDB, 0xFCDB, 0xFCDB },
+{ 0xFCDC, 0xFCDC, 0xFCDC },
+{ 0xFCDD, 0xFCDD, 0xFCDD },
+{ 0xFCDE, 0xFCDE, 0xFCDE },
+{ 0xFCDF, 0xFCDF, 0xFCDF },
+{ 0xFCE0, 0xFCE0, 0xFCE0 },
+{ 0xFCE1, 0xFCE1, 0xFCE1 },
+{ 0xFCE2, 0xFCE2, 0xFCE2 },
+{ 0xFCE3, 0xFCE3, 0xFCE3 },
+{ 0xFCE4, 0xFCE4, 0xFCE4 },
+{ 0xFCE5, 0xFCE5, 0xFCE5 },
+{ 0xFCE6, 0xFCE6, 0xFCE6 },
+{ 0xFCE7, 0xFCE7, 0xFCE7 },
+{ 0xFCE8, 0xFCE8, 0xFCE8 },
+{ 0xFCE9, 0xFCE9, 0xFCE9 },
+{ 0xFCEA, 0xFCEA, 0xFCEA },
+{ 0xFCEB, 0xFCEB, 0xFCEB },
+{ 0xFCEC, 0xFCEC, 0xFCEC },
+{ 0xFCED, 0xFCED, 0xFCED },
+{ 0xFCEE, 0xFCEE, 0xFCEE },
+{ 0xFCEF, 0xFCEF, 0xFCEF },
+{ 0xFCF0, 0xFCF0, 0xFCF0 },
+{ 0xFCF1, 0xFCF1, 0xFCF1 },
+{ 0xFCF2, 0xFCF2, 0xFCF2 },
+{ 0xFCF3, 0xFCF3, 0xFCF3 },
+{ 0xFCF4, 0xFCF4, 0xFCF4 },
+{ 0xFCF5, 0xFCF5, 0xFCF5 },
+{ 0xFCF6, 0xFCF6, 0xFCF6 },
+{ 0xFCF7, 0xFCF7, 0xFCF7 },
+{ 0xFCF8, 0xFCF8, 0xFCF8 },
+{ 0xFCF9, 0xFCF9, 0xFCF9 },
+{ 0xFCFA, 0xFCFA, 0xFCFA },
+{ 0xFCFB, 0xFCFB, 0xFCFB },
+{ 0xFCFC, 0xFCFC, 0xFCFC },
+{ 0xFCFD, 0xFCFD, 0xFCFD },
+{ 0xFCFE, 0xFCFE, 0xFCFE },
+{ 0xFCFF, 0xFCFF, 0xFCFF },
+{ 0xFD00, 0xFD00, 0xFD00 },
+{ 0xFD01, 0xFD01, 0xFD01 },
+{ 0xFD02, 0xFD02, 0xFD02 },
+{ 0xFD03, 0xFD03, 0xFD03 },
+{ 0xFD04, 0xFD04, 0xFD04 },
+{ 0xFD05, 0xFD05, 0xFD05 },
+{ 0xFD06, 0xFD06, 0xFD06 },
+{ 0xFD07, 0xFD07, 0xFD07 },
+{ 0xFD08, 0xFD08, 0xFD08 },
+{ 0xFD09, 0xFD09, 0xFD09 },
+{ 0xFD0A, 0xFD0A, 0xFD0A },
+{ 0xFD0B, 0xFD0B, 0xFD0B },
+{ 0xFD0C, 0xFD0C, 0xFD0C },
+{ 0xFD0D, 0xFD0D, 0xFD0D },
+{ 0xFD0E, 0xFD0E, 0xFD0E },
+{ 0xFD0F, 0xFD0F, 0xFD0F },
+{ 0xFD10, 0xFD10, 0xFD10 },
+{ 0xFD11, 0xFD11, 0xFD11 },
+{ 0xFD12, 0xFD12, 0xFD12 },
+{ 0xFD13, 0xFD13, 0xFD13 },
+{ 0xFD14, 0xFD14, 0xFD14 },
+{ 0xFD15, 0xFD15, 0xFD15 },
+{ 0xFD16, 0xFD16, 0xFD16 },
+{ 0xFD17, 0xFD17, 0xFD17 },
+{ 0xFD18, 0xFD18, 0xFD18 },
+{ 0xFD19, 0xFD19, 0xFD19 },
+{ 0xFD1A, 0xFD1A, 0xFD1A },
+{ 0xFD1B, 0xFD1B, 0xFD1B },
+{ 0xFD1C, 0xFD1C, 0xFD1C },
+{ 0xFD1D, 0xFD1D, 0xFD1D },
+{ 0xFD1E, 0xFD1E, 0xFD1E },
+{ 0xFD1F, 0xFD1F, 0xFD1F },
+{ 0xFD20, 0xFD20, 0xFD20 },
+{ 0xFD21, 0xFD21, 0xFD21 },
+{ 0xFD22, 0xFD22, 0xFD22 },
+{ 0xFD23, 0xFD23, 0xFD23 },
+{ 0xFD24, 0xFD24, 0xFD24 },
+{ 0xFD25, 0xFD25, 0xFD25 },
+{ 0xFD26, 0xFD26, 0xFD26 },
+{ 0xFD27, 0xFD27, 0xFD27 },
+{ 0xFD28, 0xFD28, 0xFD28 },
+{ 0xFD29, 0xFD29, 0xFD29 },
+{ 0xFD2A, 0xFD2A, 0xFD2A },
+{ 0xFD2B, 0xFD2B, 0xFD2B },
+{ 0xFD2C, 0xFD2C, 0xFD2C },
+{ 0xFD2D, 0xFD2D, 0xFD2D },
+{ 0xFD2E, 0xFD2E, 0xFD2E },
+{ 0xFD2F, 0xFD2F, 0xFD2F },
+{ 0xFD30, 0xFD30, 0xFD30 },
+{ 0xFD31, 0xFD31, 0xFD31 },
+{ 0xFD32, 0xFD32, 0xFD32 },
+{ 0xFD33, 0xFD33, 0xFD33 },
+{ 0xFD34, 0xFD34, 0xFD34 },
+{ 0xFD35, 0xFD35, 0xFD35 },
+{ 0xFD36, 0xFD36, 0xFD36 },
+{ 0xFD37, 0xFD37, 0xFD37 },
+{ 0xFD38, 0xFD38, 0xFD38 },
+{ 0xFD39, 0xFD39, 0xFD39 },
+{ 0xFD3A, 0xFD3A, 0xFD3A },
+{ 0xFD3B, 0xFD3B, 0xFD3B },
+{ 0xFD3C, 0xFD3C, 0xFD3C },
+{ 0xFD3D, 0xFD3D, 0xFD3D },
+{ 0xFD50, 0xFD50, 0xFD50 },
+{ 0xFD51, 0xFD51, 0xFD51 },
+{ 0xFD52, 0xFD52, 0xFD52 },
+{ 0xFD53, 0xFD53, 0xFD53 },
+{ 0xFD54, 0xFD54, 0xFD54 },
+{ 0xFD55, 0xFD55, 0xFD55 },
+{ 0xFD56, 0xFD56, 0xFD56 },
+{ 0xFD57, 0xFD57, 0xFD57 },
+{ 0xFD58, 0xFD58, 0xFD58 },
+{ 0xFD59, 0xFD59, 0xFD59 },
+{ 0xFD5A, 0xFD5A, 0xFD5A },
+{ 0xFD5B, 0xFD5B, 0xFD5B },
+{ 0xFD5C, 0xFD5C, 0xFD5C },
+{ 0xFD5D, 0xFD5D, 0xFD5D },
+{ 0xFD5E, 0xFD5E, 0xFD5E },
+{ 0xFD5F, 0xFD5F, 0xFD5F },
+{ 0xFD60, 0xFD60, 0xFD60 },
+{ 0xFD61, 0xFD61, 0xFD61 },
+{ 0xFD62, 0xFD62, 0xFD62 },
+{ 0xFD63, 0xFD63, 0xFD63 },
+{ 0xFD64, 0xFD64, 0xFD64 },
+{ 0xFD65, 0xFD65, 0xFD65 },
+{ 0xFD66, 0xFD66, 0xFD66 },
+{ 0xFD67, 0xFD67, 0xFD67 },
+{ 0xFD68, 0xFD68, 0xFD68 },
+{ 0xFD69, 0xFD69, 0xFD69 },
+{ 0xFD6A, 0xFD6A, 0xFD6A },
+{ 0xFD6B, 0xFD6B, 0xFD6B },
+{ 0xFD6C, 0xFD6C, 0xFD6C },
+{ 0xFD6D, 0xFD6D, 0xFD6D },
+{ 0xFD6E, 0xFD6E, 0xFD6E },
+{ 0xFD6F, 0xFD6F, 0xFD6F },
+{ 0xFD70, 0xFD70, 0xFD70 },
+{ 0xFD71, 0xFD71, 0xFD71 },
+{ 0xFD72, 0xFD72, 0xFD72 },
+{ 0xFD73, 0xFD73, 0xFD73 },
+{ 0xFD74, 0xFD74, 0xFD74 },
+{ 0xFD75, 0xFD75, 0xFD75 },
+{ 0xFD76, 0xFD76, 0xFD76 },
+{ 0xFD77, 0xFD77, 0xFD77 },
+{ 0xFD78, 0xFD78, 0xFD78 },
+{ 0xFD79, 0xFD79, 0xFD79 },
+{ 0xFD7A, 0xFD7A, 0xFD7A },
+{ 0xFD7B, 0xFD7B, 0xFD7B },
+{ 0xFD7C, 0xFD7C, 0xFD7C },
+{ 0xFD7D, 0xFD7D, 0xFD7D },
+{ 0xFD7E, 0xFD7E, 0xFD7E },
+{ 0xFD7F, 0xFD7F, 0xFD7F },
+{ 0xFD80, 0xFD80, 0xFD80 },
+{ 0xFD81, 0xFD81, 0xFD81 },
+{ 0xFD82, 0xFD82, 0xFD82 },
+{ 0xFD83, 0xFD83, 0xFD83 },
+{ 0xFD84, 0xFD84, 0xFD84 },
+{ 0xFD85, 0xFD85, 0xFD85 },
+{ 0xFD86, 0xFD86, 0xFD86 },
+{ 0xFD87, 0xFD87, 0xFD87 },
+{ 0xFD88, 0xFD88, 0xFD88 },
+{ 0xFD89, 0xFD89, 0xFD89 },
+{ 0xFD8A, 0xFD8A, 0xFD8A },
+{ 0xFD8B, 0xFD8B, 0xFD8B },
+{ 0xFD8C, 0xFD8C, 0xFD8C },
+{ 0xFD8D, 0xFD8D, 0xFD8D },
+{ 0xFD8E, 0xFD8E, 0xFD8E },
+{ 0xFD8F, 0xFD8F, 0xFD8F },
+{ 0xFD92, 0xFD92, 0xFD92 },
+{ 0xFD93, 0xFD93, 0xFD93 },
+{ 0xFD94, 0xFD94, 0xFD94 },
+{ 0xFD95, 0xFD95, 0xFD95 },
+{ 0xFD96, 0xFD96, 0xFD96 },
+{ 0xFD97, 0xFD97, 0xFD97 },
+{ 0xFD98, 0xFD98, 0xFD98 },
+{ 0xFD99, 0xFD99, 0xFD99 },
+{ 0xFD9A, 0xFD9A, 0xFD9A },
+{ 0xFD9B, 0xFD9B, 0xFD9B },
+{ 0xFD9C, 0xFD9C, 0xFD9C },
+{ 0xFD9D, 0xFD9D, 0xFD9D },
+{ 0xFD9E, 0xFD9E, 0xFD9E },
+{ 0xFD9F, 0xFD9F, 0xFD9F },
+{ 0xFDA0, 0xFDA0, 0xFDA0 },
+{ 0xFDA1, 0xFDA1, 0xFDA1 },
+{ 0xFDA2, 0xFDA2, 0xFDA2 },
+{ 0xFDA3, 0xFDA3, 0xFDA3 },
+{ 0xFDA4, 0xFDA4, 0xFDA4 },
+{ 0xFDA5, 0xFDA5, 0xFDA5 },
+{ 0xFDA6, 0xFDA6, 0xFDA6 },
+{ 0xFDA7, 0xFDA7, 0xFDA7 },
+{ 0xFDA8, 0xFDA8, 0xFDA8 },
+{ 0xFDA9, 0xFDA9, 0xFDA9 },
+{ 0xFDAA, 0xFDAA, 0xFDAA },
+{ 0xFDAB, 0xFDAB, 0xFDAB },
+{ 0xFDAC, 0xFDAC, 0xFDAC },
+{ 0xFDAD, 0xFDAD, 0xFDAD },
+{ 0xFDAE, 0xFDAE, 0xFDAE },
+{ 0xFDAF, 0xFDAF, 0xFDAF },
+{ 0xFDB0, 0xFDB0, 0xFDB0 },
+{ 0xFDB1, 0xFDB1, 0xFDB1 },
+{ 0xFDB2, 0xFDB2, 0xFDB2 },
+{ 0xFDB3, 0xFDB3, 0xFDB3 },
+{ 0xFDB4, 0xFDB4, 0xFDB4 },
+{ 0xFDB5, 0xFDB5, 0xFDB5 },
+{ 0xFDB6, 0xFDB6, 0xFDB6 },
+{ 0xFDB7, 0xFDB7, 0xFDB7 },
+{ 0xFDB8, 0xFDB8, 0xFDB8 },
+{ 0xFDB9, 0xFDB9, 0xFDB9 },
+{ 0xFDBA, 0xFDBA, 0xFDBA },
+{ 0xFDBB, 0xFDBB, 0xFDBB },
+{ 0xFDBC, 0xFDBC, 0xFDBC },
+{ 0xFDBD, 0xFDBD, 0xFDBD },
+{ 0xFDBE, 0xFDBE, 0xFDBE },
+{ 0xFDBF, 0xFDBF, 0xFDBF },
+{ 0xFDC0, 0xFDC0, 0xFDC0 },
+{ 0xFDC1, 0xFDC1, 0xFDC1 },
+{ 0xFDC2, 0xFDC2, 0xFDC2 },
+{ 0xFDC3, 0xFDC3, 0xFDC3 },
+{ 0xFDC4, 0xFDC4, 0xFDC4 },
+{ 0xFDC5, 0xFDC5, 0xFDC5 },
+{ 0xFDC6, 0xFDC6, 0xFDC6 },
+{ 0xFDC7, 0xFDC7, 0xFDC7 },
+{ 0xFDF0, 0xFDF0, 0xFDF0 },
+{ 0xFDF1, 0xFDF1, 0xFDF1 },
+{ 0xFDF2, 0xFDF2, 0xFDF2 },
+{ 0xFDF3, 0xFDF3, 0xFDF3 },
+{ 0xFDF4, 0xFDF4, 0xFDF4 },
+{ 0xFDF5, 0xFDF5, 0xFDF5 },
+{ 0xFDF6, 0xFDF6, 0xFDF6 },
+{ 0xFDF7, 0xFDF7, 0xFDF7 },
+{ 0xFDF8, 0xFDF8, 0xFDF8 },
+{ 0xFDF9, 0xFDF9, 0xFDF9 },
+{ 0xFDFA, 0xFDFA, 0xFDFA },
+{ 0xFDFB, 0xFDFB, 0xFDFB },
+{ 0xFE00, 0xFE00, 0xFE00 },
+{ 0xFE01, 0xFE01, 0xFE01 },
+{ 0xFE02, 0xFE02, 0xFE02 },
+{ 0xFE03, 0xFE03, 0xFE03 },
+{ 0xFE04, 0xFE04, 0xFE04 },
+{ 0xFE05, 0xFE05, 0xFE05 },
+{ 0xFE06, 0xFE06, 0xFE06 },
+{ 0xFE07, 0xFE07, 0xFE07 },
+{ 0xFE08, 0xFE08, 0xFE08 },
+{ 0xFE09, 0xFE09, 0xFE09 },
+{ 0xFE0A, 0xFE0A, 0xFE0A },
+{ 0xFE0B, 0xFE0B, 0xFE0B },
+{ 0xFE0C, 0xFE0C, 0xFE0C },
+{ 0xFE0D, 0xFE0D, 0xFE0D },
+{ 0xFE0E, 0xFE0E, 0xFE0E },
+{ 0xFE0F, 0xFE0F, 0xFE0F },
+{ 0xFE20, 0xFE20, 0xFE20 },
+{ 0xFE21, 0xFE21, 0xFE21 },
+{ 0xFE22, 0xFE22, 0xFE22 },
+{ 0xFE23, 0xFE23, 0xFE23 },
+{ 0xFE70, 0xFE70, 0xFE70 },
+{ 0xFE71, 0xFE71, 0xFE71 },
+{ 0xFE72, 0xFE72, 0xFE72 },
+{ 0xFE73, 0xFE73, 0xFE73 },
+{ 0xFE74, 0xFE74, 0xFE74 },
+{ 0xFE76, 0xFE76, 0xFE76 },
+{ 0xFE77, 0xFE77, 0xFE77 },
+{ 0xFE78, 0xFE78, 0xFE78 },
+{ 0xFE79, 0xFE79, 0xFE79 },
+{ 0xFE7A, 0xFE7A, 0xFE7A },
+{ 0xFE7B, 0xFE7B, 0xFE7B },
+{ 0xFE7C, 0xFE7C, 0xFE7C },
+{ 0xFE7D, 0xFE7D, 0xFE7D },
+{ 0xFE7E, 0xFE7E, 0xFE7E },
+{ 0xFE7F, 0xFE7F, 0xFE7F },
+{ 0xFE80, 0xFE80, 0xFE80 },
+{ 0xFE81, 0xFE81, 0xFE81 },
+{ 0xFE82, 0xFE82, 0xFE82 },
+{ 0xFE83, 0xFE83, 0xFE83 },
+{ 0xFE84, 0xFE84, 0xFE84 },
+{ 0xFE85, 0xFE85, 0xFE85 },
+{ 0xFE86, 0xFE86, 0xFE86 },
+{ 0xFE87, 0xFE87, 0xFE87 },
+{ 0xFE88, 0xFE88, 0xFE88 },
+{ 0xFE89, 0xFE89, 0xFE89 },
+{ 0xFE8A, 0xFE8A, 0xFE8A },
+{ 0xFE8B, 0xFE8B, 0xFE8B },
+{ 0xFE8C, 0xFE8C, 0xFE8C },
+{ 0xFE8D, 0xFE8D, 0xFE8D },
+{ 0xFE8E, 0xFE8E, 0xFE8E },
+{ 0xFE8F, 0xFE8F, 0xFE8F },
+{ 0xFE90, 0xFE90, 0xFE90 },
+{ 0xFE91, 0xFE91, 0xFE91 },
+{ 0xFE92, 0xFE92, 0xFE92 },
+{ 0xFE93, 0xFE93, 0xFE93 },
+{ 0xFE94, 0xFE94, 0xFE94 },
+{ 0xFE95, 0xFE95, 0xFE95 },
+{ 0xFE96, 0xFE96, 0xFE96 },
+{ 0xFE97, 0xFE97, 0xFE97 },
+{ 0xFE98, 0xFE98, 0xFE98 },
+{ 0xFE99, 0xFE99, 0xFE99 },
+{ 0xFE9A, 0xFE9A, 0xFE9A },
+{ 0xFE9B, 0xFE9B, 0xFE9B },
+{ 0xFE9C, 0xFE9C, 0xFE9C },
+{ 0xFE9D, 0xFE9D, 0xFE9D },
+{ 0xFE9E, 0xFE9E, 0xFE9E },
+{ 0xFE9F, 0xFE9F, 0xFE9F },
+{ 0xFEA0, 0xFEA0, 0xFEA0 },
+{ 0xFEA1, 0xFEA1, 0xFEA1 },
+{ 0xFEA2, 0xFEA2, 0xFEA2 },
+{ 0xFEA3, 0xFEA3, 0xFEA3 },
+{ 0xFEA4, 0xFEA4, 0xFEA4 },
+{ 0xFEA5, 0xFEA5, 0xFEA5 },
+{ 0xFEA6, 0xFEA6, 0xFEA6 },
+{ 0xFEA7, 0xFEA7, 0xFEA7 },
+{ 0xFEA8, 0xFEA8, 0xFEA8 },
+{ 0xFEA9, 0xFEA9, 0xFEA9 },
+{ 0xFEAA, 0xFEAA, 0xFEAA },
+{ 0xFEAB, 0xFEAB, 0xFEAB },
+{ 0xFEAC, 0xFEAC, 0xFEAC },
+{ 0xFEAD, 0xFEAD, 0xFEAD },
+{ 0xFEAE, 0xFEAE, 0xFEAE },
+{ 0xFEAF, 0xFEAF, 0xFEAF },
+{ 0xFEB0, 0xFEB0, 0xFEB0 },
+{ 0xFEB1, 0xFEB1, 0xFEB1 },
+{ 0xFEB2, 0xFEB2, 0xFEB2 },
+{ 0xFEB3, 0xFEB3, 0xFEB3 },
+{ 0xFEB4, 0xFEB4, 0xFEB4 },
+{ 0xFEB5, 0xFEB5, 0xFEB5 },
+{ 0xFEB6, 0xFEB6, 0xFEB6 },
+{ 0xFEB7, 0xFEB7, 0xFEB7 },
+{ 0xFEB8, 0xFEB8, 0xFEB8 },
+{ 0xFEB9, 0xFEB9, 0xFEB9 },
+{ 0xFEBA, 0xFEBA, 0xFEBA },
+{ 0xFEBB, 0xFEBB, 0xFEBB },
+{ 0xFEBC, 0xFEBC, 0xFEBC },
+{ 0xFEBD, 0xFEBD, 0xFEBD },
+{ 0xFEBE, 0xFEBE, 0xFEBE },
+{ 0xFEBF, 0xFEBF, 0xFEBF },
+{ 0xFEC0, 0xFEC0, 0xFEC0 },
+{ 0xFEC1, 0xFEC1, 0xFEC1 },
+{ 0xFEC2, 0xFEC2, 0xFEC2 },
+{ 0xFEC3, 0xFEC3, 0xFEC3 },
+{ 0xFEC4, 0xFEC4, 0xFEC4 },
+{ 0xFEC5, 0xFEC5, 0xFEC5 },
+{ 0xFEC6, 0xFEC6, 0xFEC6 },
+{ 0xFEC7, 0xFEC7, 0xFEC7 },
+{ 0xFEC8, 0xFEC8, 0xFEC8 },
+{ 0xFEC9, 0xFEC9, 0xFEC9 },
+{ 0xFECA, 0xFECA, 0xFECA },
+{ 0xFECB, 0xFECB, 0xFECB },
+{ 0xFECC, 0xFECC, 0xFECC },
+{ 0xFECD, 0xFECD, 0xFECD },
+{ 0xFECE, 0xFECE, 0xFECE },
+{ 0xFECF, 0xFECF, 0xFECF },
+{ 0xFED0, 0xFED0, 0xFED0 },
+{ 0xFED1, 0xFED1, 0xFED1 },
+{ 0xFED2, 0xFED2, 0xFED2 },
+{ 0xFED3, 0xFED3, 0xFED3 },
+{ 0xFED4, 0xFED4, 0xFED4 },
+{ 0xFED5, 0xFED5, 0xFED5 },
+{ 0xFED6, 0xFED6, 0xFED6 },
+{ 0xFED7, 0xFED7, 0xFED7 },
+{ 0xFED8, 0xFED8, 0xFED8 },
+{ 0xFED9, 0xFED9, 0xFED9 },
+{ 0xFEDA, 0xFEDA, 0xFEDA },
+{ 0xFEDB, 0xFEDB, 0xFEDB },
+{ 0xFEDC, 0xFEDC, 0xFEDC },
+{ 0xFEDD, 0xFEDD, 0xFEDD },
+{ 0xFEDE, 0xFEDE, 0xFEDE },
+{ 0xFEDF, 0xFEDF, 0xFEDF },
+{ 0xFEE0, 0xFEE0, 0xFEE0 },
+{ 0xFEE1, 0xFEE1, 0xFEE1 },
+{ 0xFEE2, 0xFEE2, 0xFEE2 },
+{ 0xFEE3, 0xFEE3, 0xFEE3 },
+{ 0xFEE4, 0xFEE4, 0xFEE4 },
+{ 0xFEE5, 0xFEE5, 0xFEE5 },
+{ 0xFEE6, 0xFEE6, 0xFEE6 },
+{ 0xFEE7, 0xFEE7, 0xFEE7 },
+{ 0xFEE8, 0xFEE8, 0xFEE8 },
+{ 0xFEE9, 0xFEE9, 0xFEE9 },
+{ 0xFEEA, 0xFEEA, 0xFEEA },
+{ 0xFEEB, 0xFEEB, 0xFEEB },
+{ 0xFEEC, 0xFEEC, 0xFEEC },
+{ 0xFEED, 0xFEED, 0xFEED },
+{ 0xFEEE, 0xFEEE, 0xFEEE },
+{ 0xFEEF, 0xFEEF, 0xFEEF },
+{ 0xFEF0, 0xFEF0, 0xFEF0 },
+{ 0xFEF1, 0xFEF1, 0xFEF1 },
+{ 0xFEF2, 0xFEF2, 0xFEF2 },
+{ 0xFEF3, 0xFEF3, 0xFEF3 },
+{ 0xFEF4, 0xFEF4, 0xFEF4 },
+{ 0xFEF5, 0xFEF5, 0xFEF5 },
+{ 0xFEF6, 0xFEF6, 0xFEF6 },
+{ 0xFEF7, 0xFEF7, 0xFEF7 },
+{ 0xFEF8, 0xFEF8, 0xFEF8 },
+{ 0xFEF9, 0xFEF9, 0xFEF9 },
+{ 0xFEFA, 0xFEFA, 0xFEFA },
+{ 0xFEFB, 0xFEFB, 0xFEFB },
+{ 0xFEFC, 0xFEFC, 0xFEFC },
+{ 0xFF21, 0xFF21, 0xFF41 },
+{ 0xFF22, 0xFF22, 0xFF42 },
+{ 0xFF23, 0xFF23, 0xFF43 },
+{ 0xFF24, 0xFF24, 0xFF44 },
+{ 0xFF25, 0xFF25, 0xFF45 },
+{ 0xFF26, 0xFF26, 0xFF46 },
+{ 0xFF27, 0xFF27, 0xFF47 },
+{ 0xFF28, 0xFF28, 0xFF48 },
+{ 0xFF29, 0xFF29, 0xFF49 },
+{ 0xFF2A, 0xFF2A, 0xFF4A },
+{ 0xFF2B, 0xFF2B, 0xFF4B },
+{ 0xFF2C, 0xFF2C, 0xFF4C },
+{ 0xFF2D, 0xFF2D, 0xFF4D },
+{ 0xFF2E, 0xFF2E, 0xFF4E },
+{ 0xFF2F, 0xFF2F, 0xFF4F },
+{ 0xFF30, 0xFF30, 0xFF50 },
+{ 0xFF31, 0xFF31, 0xFF51 },
+{ 0xFF32, 0xFF32, 0xFF52 },
+{ 0xFF33, 0xFF33, 0xFF53 },
+{ 0xFF34, 0xFF34, 0xFF54 },
+{ 0xFF35, 0xFF35, 0xFF55 },
+{ 0xFF36, 0xFF36, 0xFF56 },
+{ 0xFF37, 0xFF37, 0xFF57 },
+{ 0xFF38, 0xFF38, 0xFF58 },
+{ 0xFF39, 0xFF39, 0xFF59 },
+{ 0xFF3A, 0xFF3A, 0xFF5A },
+{ 0xFF41, 0xFF21, 0xFF41 },
+{ 0xFF42, 0xFF22, 0xFF42 },
+{ 0xFF43, 0xFF23, 0xFF43 },
+{ 0xFF44, 0xFF24, 0xFF44 },
+{ 0xFF45, 0xFF25, 0xFF45 },
+{ 0xFF46, 0xFF26, 0xFF46 },
+{ 0xFF47, 0xFF27, 0xFF47 },
+{ 0xFF48, 0xFF28, 0xFF48 },
+{ 0xFF49, 0xFF29, 0xFF49 },
+{ 0xFF4A, 0xFF2A, 0xFF4A },
+{ 0xFF4B, 0xFF2B, 0xFF4B },
+{ 0xFF4C, 0xFF2C, 0xFF4C },
+{ 0xFF4D, 0xFF2D, 0xFF4D },
+{ 0xFF4E, 0xFF2E, 0xFF4E },
+{ 0xFF4F, 0xFF2F, 0xFF4F },
+{ 0xFF50, 0xFF30, 0xFF50 },
+{ 0xFF51, 0xFF31, 0xFF51 },
+{ 0xFF52, 0xFF32, 0xFF52 },
+{ 0xFF53, 0xFF33, 0xFF53 },
+{ 0xFF54, 0xFF34, 0xFF54 },
+{ 0xFF55, 0xFF35, 0xFF55 },
+{ 0xFF56, 0xFF36, 0xFF56 },
+{ 0xFF57, 0xFF37, 0xFF57 },
+{ 0xFF58, 0xFF38, 0xFF58 },
+{ 0xFF59, 0xFF39, 0xFF59 },
+{ 0xFF5A, 0xFF3A, 0xFF5A },
+{ 0xFF66, 0xFF66, 0xFF66 },
+{ 0xFF67, 0xFF67, 0xFF67 },
+{ 0xFF68, 0xFF68, 0xFF68 },
+{ 0xFF69, 0xFF69, 0xFF69 },
+{ 0xFF6A, 0xFF6A, 0xFF6A },
+{ 0xFF6B, 0xFF6B, 0xFF6B },
+{ 0xFF6C, 0xFF6C, 0xFF6C },
+{ 0xFF6D, 0xFF6D, 0xFF6D },
+{ 0xFF6E, 0xFF6E, 0xFF6E },
+{ 0xFF6F, 0xFF6F, 0xFF6F },
+{ 0xFF70, 0xFF70, 0xFF70 },
+{ 0xFF71, 0xFF71, 0xFF71 },
+{ 0xFF72, 0xFF72, 0xFF72 },
+{ 0xFF73, 0xFF73, 0xFF73 },
+{ 0xFF74, 0xFF74, 0xFF74 },
+{ 0xFF75, 0xFF75, 0xFF75 },
+{ 0xFF76, 0xFF76, 0xFF76 },
+{ 0xFF77, 0xFF77, 0xFF77 },
+{ 0xFF78, 0xFF78, 0xFF78 },
+{ 0xFF79, 0xFF79, 0xFF79 },
+{ 0xFF7A, 0xFF7A, 0xFF7A },
+{ 0xFF7B, 0xFF7B, 0xFF7B },
+{ 0xFF7C, 0xFF7C, 0xFF7C },
+{ 0xFF7D, 0xFF7D, 0xFF7D },
+{ 0xFF7E, 0xFF7E, 0xFF7E },
+{ 0xFF7F, 0xFF7F, 0xFF7F },
+{ 0xFF80, 0xFF80, 0xFF80 },
+{ 0xFF81, 0xFF81, 0xFF81 },
+{ 0xFF82, 0xFF82, 0xFF82 },
+{ 0xFF83, 0xFF83, 0xFF83 },
+{ 0xFF84, 0xFF84, 0xFF84 },
+{ 0xFF85, 0xFF85, 0xFF85 },
+{ 0xFF86, 0xFF86, 0xFF86 },
+{ 0xFF87, 0xFF87, 0xFF87 },
+{ 0xFF88, 0xFF88, 0xFF88 },
+{ 0xFF89, 0xFF89, 0xFF89 },
+{ 0xFF8A, 0xFF8A, 0xFF8A },
+{ 0xFF8B, 0xFF8B, 0xFF8B },
+{ 0xFF8C, 0xFF8C, 0xFF8C },
+{ 0xFF8D, 0xFF8D, 0xFF8D },
+{ 0xFF8E, 0xFF8E, 0xFF8E },
+{ 0xFF8F, 0xFF8F, 0xFF8F },
+{ 0xFF90, 0xFF90, 0xFF90 },
+{ 0xFF91, 0xFF91, 0xFF91 },
+{ 0xFF92, 0xFF92, 0xFF92 },
+{ 0xFF93, 0xFF93, 0xFF93 },
+{ 0xFF94, 0xFF94, 0xFF94 },
+{ 0xFF95, 0xFF95, 0xFF95 },
+{ 0xFF96, 0xFF96, 0xFF96 },
+{ 0xFF97, 0xFF97, 0xFF97 },
+{ 0xFF98, 0xFF98, 0xFF98 },
+{ 0xFF99, 0xFF99, 0xFF99 },
+{ 0xFF9A, 0xFF9A, 0xFF9A },
+{ 0xFF9B, 0xFF9B, 0xFF9B },
+{ 0xFF9C, 0xFF9C, 0xFF9C },
+{ 0xFF9D, 0xFF9D, 0xFF9D },
+{ 0xFF9E, 0xFF9E, 0xFF9E },
+{ 0xFF9F, 0xFF9F, 0xFF9F },
+{ 0xFFA0, 0xFFA0, 0xFFA0 },
+{ 0xFFA1, 0xFFA1, 0xFFA1 },
+{ 0xFFA2, 0xFFA2, 0xFFA2 },
+{ 0xFFA3, 0xFFA3, 0xFFA3 },
+{ 0xFFA4, 0xFFA4, 0xFFA4 },
+{ 0xFFA5, 0xFFA5, 0xFFA5 },
+{ 0xFFA6, 0xFFA6, 0xFFA6 },
+{ 0xFFA7, 0xFFA7, 0xFFA7 },
+{ 0xFFA8, 0xFFA8, 0xFFA8 },
+{ 0xFFA9, 0xFFA9, 0xFFA9 },
+{ 0xFFAA, 0xFFAA, 0xFFAA },
+{ 0xFFAB, 0xFFAB, 0xFFAB },
+{ 0xFFAC, 0xFFAC, 0xFFAC },
+{ 0xFFAD, 0xFFAD, 0xFFAD },
+{ 0xFFAE, 0xFFAE, 0xFFAE },
+{ 0xFFAF, 0xFFAF, 0xFFAF },
+{ 0xFFB0, 0xFFB0, 0xFFB0 },
+{ 0xFFB1, 0xFFB1, 0xFFB1 },
+{ 0xFFB2, 0xFFB2, 0xFFB2 },
+{ 0xFFB3, 0xFFB3, 0xFFB3 },
+{ 0xFFB4, 0xFFB4, 0xFFB4 },
+{ 0xFFB5, 0xFFB5, 0xFFB5 },
+{ 0xFFB6, 0xFFB6, 0xFFB6 },
+{ 0xFFB7, 0xFFB7, 0xFFB7 },
+{ 0xFFB8, 0xFFB8, 0xFFB8 },
+{ 0xFFB9, 0xFFB9, 0xFFB9 },
+{ 0xFFBA, 0xFFBA, 0xFFBA },
+{ 0xFFBB, 0xFFBB, 0xFFBB },
+{ 0xFFBC, 0xFFBC, 0xFFBC },
+{ 0xFFBD, 0xFFBD, 0xFFBD },
+{ 0xFFBE, 0xFFBE, 0xFFBE },
+{ 0xFFC2, 0xFFC2, 0xFFC2 },
+{ 0xFFC3, 0xFFC3, 0xFFC3 },
+{ 0xFFC4, 0xFFC4, 0xFFC4 },
+{ 0xFFC5, 0xFFC5, 0xFFC5 },
+{ 0xFFC6, 0xFFC6, 0xFFC6 },
+{ 0xFFC7, 0xFFC7, 0xFFC7 },
+{ 0xFFCA, 0xFFCA, 0xFFCA },
+{ 0xFFCB, 0xFFCB, 0xFFCB },
+{ 0xFFCC, 0xFFCC, 0xFFCC },
+{ 0xFFCD, 0xFFCD, 0xFFCD },
+{ 0xFFCE, 0xFFCE, 0xFFCE },
+{ 0xFFCF, 0xFFCF, 0xFFCF },
+{ 0xFFD2, 0xFFD2, 0xFFD2 },
+{ 0xFFD3, 0xFFD3, 0xFFD3 },
+{ 0xFFD4, 0xFFD4, 0xFFD4 },
+{ 0xFFD5, 0xFFD5, 0xFFD5 },
+{ 0xFFD6, 0xFFD6, 0xFFD6 },
+{ 0xFFD7, 0xFFD7, 0xFFD7 },
+{ 0xFFDA, 0xFFDA, 0xFFDA },
+{ 0xFFDB, 0xFFDB, 0xFFDB },
+{ 0xFFDC, 0xFFDC, 0xFFDC }
+};
diff --git a/src/cpp/core/spelling/hunspell/w_char.hxx b/src/cpp/core/spelling/hunspell/w_char.hxx
new file mode 100644
index 0000000..3719dd3
--- /dev/null
+++ b/src/cpp/core/spelling/hunspell/w_char.hxx
@@ -0,0 +1,21 @@
+#ifndef __WCHARHXX__
+#define __WCHARHXX__
+
+#ifndef GCC
+typedef struct {
+#else
+typedef struct __attribute__ ((packed)) {
+#endif
+ unsigned char l;
+ unsigned char h;
+} w_char;
+
+// two character arrays
+struct replentry {
+ char * pattern;
+ char * pattern2;
+ bool start;
+ bool end;
+};
+
+#endif
diff --git a/src/cpp/core/system/ChildProcess.hpp b/src/cpp/core/system/ChildProcess.hpp
new file mode 100644
index 0000000..493727e
--- /dev/null
+++ b/src/cpp/core/system/ChildProcess.hpp
@@ -0,0 +1,236 @@
+/*
+ * ChildProcess.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_CHILD_PROCESS_HPP
+#define CORE_SYSTEM_CHILD_PROCESS_HPP
+
+#include <core/system/Process.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+namespace core {
+
+class ErrorLocation;
+
+namespace system {
+
+// Base class for child processes
+class ChildProcess : boost::noncopyable, public ProcessOperations
+{
+protected:
+ ChildProcess();
+
+ // separate init from construction so subclassees can use custom
+ // processing to calculate exe and args (e.g. lookup paths or
+ // invoke within a command processor)
+ void init(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options);
+
+ // init from a command (platform specific)
+ void init(const std::string& command,
+ const ProcessOptions& options);
+
+public:
+ virtual ~ChildProcess();
+
+public:
+ // write (synchronously) to std input
+ virtual Error writeToStdin(const std::string& input, bool eof);
+
+ // set the size of the pseudoterminal
+ virtual Error ptySetSize(int cols, int rows);
+
+ // interrupt the pseudoterminal
+ virtual Error ptyInterrupt();
+
+ // terminate the process
+ virtual Error terminate();
+
+protected:
+ Error run();
+
+ const ProcessOptions& options() const { return options_; }
+
+protected:
+ // platform specific impl
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+
+private:
+ // command and args
+ std::string exe_;
+ std::vector<std::string> args_;
+ ProcessOptions options_;
+};
+
+
+// Child process which can be run synchronously
+class SyncChildProcess : public ChildProcess
+{
+public:
+ SyncChildProcess(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options)
+ : ChildProcess()
+ {
+ init(exe, args, options);
+ if (!options.stdOutFile.empty() || !options.stdErrFile.empty())
+ {
+ LOG_ERROR_MESSAGE(
+ "stdOutFile/stdErrFile options cannot be used with runProgram");
+ }
+ }
+
+ SyncChildProcess(const std::string& command,
+ const ProcessOptions& options)
+ : ChildProcess()
+ {
+ init(command, options);
+ }
+
+ Error run(const std::string& input, ProcessResult* pResult)
+ {
+ // sync child processes don't support pseudoterminal mode
+#ifndef _WIN32
+ if (options().pseudoterminal)
+ {
+ return systemError(boost::system::errc::not_supported,
+ ERROR_LOCATION);
+ }
+#endif
+
+ // run the process
+ Error error = ChildProcess::run();
+ if (error)
+ return error;
+
+ // write input
+ if (!input.empty())
+ {
+ error = writeToStdin(input, true);
+ if (error)
+ {
+ Error terminateError = terminate();
+ if (terminateError)
+ LOG_ERROR(terminateError);
+ }
+ }
+
+ // read standard out if we didn't have a previous problem
+ if (!error)
+ error = readStdOut(&(pResult->stdOut));
+
+ // read standard error if we didn't have a previous problem
+ if (!error)
+ error = readStdErr(&(pResult->stdErr));
+
+ // wait on exit and get exit status. note we always need to do this
+ // even if we called terminate due to an earlier error (so we always
+ // reap the child)
+ Error waitError = waitForExit(&(pResult->exitStatus));
+ if (waitError)
+ {
+ if (!error)
+ error = waitError;
+ else
+ LOG_ERROR(waitError);
+ }
+
+ // return error status
+ return error;
+ }
+
+private:
+ Error readStdOut(std::string* pOutput);
+ Error readStdErr(std::string* pOutput);
+ Error waitForExit(int* pExitStatus);
+};
+
+
+// Child process which can be run asynchronously
+class AsyncChildProcess : public ChildProcess
+{
+public:
+ AsyncChildProcess(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options);
+ AsyncChildProcess(const std::string& command,
+ const ProcessOptions& options);
+ virtual ~AsyncChildProcess();
+
+ // run process asynchronously
+ Error run(const ProcessCallbacks& callbacks)
+ {
+ Error error = ChildProcess::run();
+ if (!error)
+ {
+ callbacks_ = callbacks;
+ return Success();
+ }
+ else
+ {
+ return error;
+ }
+ }
+
+ // poll for input and exit status
+ void poll();
+
+ // has it exited?
+ bool exited();
+
+ // override of terminate (allow special handling for unix pty termination)
+ virtual Error terminate();
+
+private:
+
+ void reportError(const Error& error)
+ {
+ if (callbacks_.onError)
+ {
+ callbacks_.onError(*this, error);
+ }
+ else
+ {
+ LOG_ERROR(error);
+ Error termError = terminate();
+ if (termError)
+ LOG_ERROR(termError);
+ }
+ }
+
+ void reportIOError(const char* what, const ErrorLocation& location)
+ {
+ Error error = systemError(boost::system::errc::io_error, location);
+ if (what != NULL)
+ error.addProperty("what", what);
+ reportError(error);
+ }
+
+private:
+ // callbacks
+ ProcessCallbacks callbacks_;
+
+ // platform specific impl
+ struct AsyncImpl;
+ boost::scoped_ptr<AsyncImpl> pAsyncImpl_;
+};
+
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_CHILD_PROCESS_HPP
diff --git a/src/cpp/core/system/CriticalSection.hpp b/src/cpp/core/system/CriticalSection.hpp
new file mode 100644
index 0000000..ebabdee
--- /dev/null
+++ b/src/cpp/core/system/CriticalSection.hpp
@@ -0,0 +1,78 @@
+/*
+ * CriticalSection.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_CRITICAL_SECTION_HPP
+#define CORE_SYSTEM_CRITICAL_SECTION_HPP
+
+#ifdef _WIN32
+
+#include <windows.h>
+
+namespace core {
+namespace system {
+
+// critical section wrapper
+class CriticalSection
+{
+public:
+ CriticalSection()
+ {
+ ::InitializeCriticalSection(&criticalSection_);
+ }
+
+ class Scope
+ {
+ public:
+ explicit Scope(CriticalSection& cs)
+ : cs_(cs)
+ {
+ cs_.enter();
+ }
+
+ virtual ~Scope()
+ {
+ try
+ {
+ cs_.leave();
+ }
+ catch(...)
+ {
+ }
+ }
+
+ private:
+ CriticalSection& cs_;
+ };
+
+private:
+ void enter()
+ {
+ ::EnterCriticalSection(&criticalSection_);
+ }
+ void leave()
+ {
+ ::LeaveCriticalSection(&criticalSection_);
+ }
+
+ CRITICAL_SECTION criticalSection_;
+};
+
+
+} // namespace system
+} // namespace core
+
+#endif // _WIN32
+
+#endif // CORE_SYSTEM_CRITICAL_SECTION_HPP
diff --git a/src/cpp/core/system/Environment.cpp b/src/cpp/core/system/Environment.cpp
new file mode 100644
index 0000000..a8ce240
--- /dev/null
+++ b/src/cpp/core/system/Environment.cpp
@@ -0,0 +1,138 @@
+/*
+ * Environment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/Environment.hpp>
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#ifdef _WIN32
+#define kPathSeparator ";"
+#else
+#define kPathSeparator ":"
+#endif
+
+namespace core {
+namespace system {
+
+namespace impl {
+
+// platform-specific name matching (Win32 is case-insensitive)
+bool optionIsNamed(const Option& option, const std::string& name);
+
+} // namespace impl
+
+// get an environment variable within an Options structure
+std::string getenv(const Options& environment, const std::string& name)
+{
+ Options::const_iterator it = std::find_if(
+ environment.begin(),
+ environment.end(),
+ boost::bind(impl::optionIsNamed, _1, name));
+
+ if (it != environment.end())
+ return it->second;
+ else
+ return std::string();
+}
+
+void getModifiedEnv(const Options& extraVars, Options* pEnv)
+{
+ core::system::environment(pEnv);
+ BOOST_FOREACH(const Option& var, extraVars)
+ {
+ core::system::setenv(pEnv, var.first, var.second);
+ }
+}
+
+// set an environment variable within an Options structure (replaces
+// any existing value)
+void setenv(Options* pEnvironment,
+ const std::string& name,
+ const std::string& value)
+{
+ Options::iterator it = std::find_if(
+ pEnvironment->begin(),
+ pEnvironment->end(),
+ boost::bind(impl::optionIsNamed, _1, name));
+ if (it != pEnvironment->end())
+ *it = std::make_pair(name, value);
+ else
+ pEnvironment->push_back(std::make_pair(name, value));
+}
+
+// remove an enviroment variable from an Options structure
+void unsetenv(Options* pEnvironment, const std::string& name)
+{
+ pEnvironment->erase(std::remove_if(pEnvironment->begin(),
+ pEnvironment->end(),
+ boost::bind(impl::optionIsNamed,
+ _1,
+ name)),
+ pEnvironment->end());
+}
+
+void addToPath(std::string* pPath,
+ const std::string& filePath,
+ bool prepend)
+{
+ if (prepend)
+ {
+ *pPath = filePath + kPathSeparator + *pPath;
+ }
+ else
+ {
+ if (!pPath->empty())
+ pPath->append(kPathSeparator);
+
+ pPath->append(filePath);
+ }
+}
+
+// add to the PATH within an Options struture
+void addToPath(Options* pEnvironment,
+ const std::string& filePath,
+ bool prepend)
+{
+ std::string path = getenv(*pEnvironment, "PATH");
+ if (prepend)
+ path = filePath + kPathSeparator + path;
+ else
+ path = path + kPathSeparator + filePath;
+ setenv(pEnvironment, "PATH", path);
+}
+
+bool parseEnvVar(const std::string envVar, Option* pEnvVar)
+{
+ std::string::size_type pos = envVar.find("=") ;
+ if ( pos != std::string::npos )
+ {
+ std::string key = envVar.substr(0, pos) ;
+ std::string value;
+ if ( (pos + 1) < envVar.size() )
+ value = envVar.substr(pos + 1) ;
+ *pEnvVar = std::make_pair(key,value);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/system/Pam.cpp b/src/cpp/core/system/Pam.cpp
new file mode 100644
index 0000000..73fbb9d
--- /dev/null
+++ b/src/cpp/core/system/Pam.cpp
@@ -0,0 +1,210 @@
+/*
+ * Pam.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/Pam.hpp>
+
+#include <boost/utility.hpp>
+#include <boost/regex.hpp>
+
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+namespace system {
+
+namespace {
+
+class MemoryPool : boost::noncopyable {
+
+ typedef boost::function<void*(size_t)> Alloc;
+ typedef boost::function<void(void*)> Free;
+
+public:
+ MemoryPool(Alloc allocFunc = ::malloc, Free freeFunc = ::free) :
+ alloc_(allocFunc),
+ free_(freeFunc)
+ {}
+
+ ~MemoryPool()
+ {
+ try
+ {
+ for (size_t i = 0; i < buffers_.size(); i++)
+ {
+ free_(buffers_.at(i));
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+ void* alloc(size_t size)
+ {
+ void* p = alloc_(size);
+ if (p)
+ buffers_.push_back(p);
+ return p;
+ }
+
+ void relinquishOwnership()
+ {
+ buffers_.clear();
+ }
+
+private:
+ std::vector<void*> buffers_;
+ Alloc alloc_;
+ Free free_;
+};
+
+int conv(int num_msg,
+ const struct pam_message** msg,
+ struct pam_response** resp,
+ void * appdata_ptr)
+{
+ try
+ {
+ MemoryPool pool;
+
+ // resp will be freed by the caller
+ *resp = static_cast<pam_response*>(pool.alloc(sizeof(pam_response) * num_msg));
+ if (!*resp)
+ return PAM_BUF_ERR;
+
+ ::memset(*resp, 0, sizeof(pam_response) * num_msg);
+
+ for (int i = 0; i < num_msg; i++)
+ {
+ const pam_message* input = msg[i];
+ std::string msgText = input->msg;
+
+ switch (input->msg_style)
+ {
+ case PAM_PROMPT_ECHO_OFF:
+ {
+ boost::regex passwordRegex("\\bpassword:\\s*$",
+ boost::regex_constants::icase);
+ boost::smatch match;
+ if (regex_search(msgText, match, passwordRegex))
+ {
+ resp[i]->resp_retcode = 0;
+ char* password = static_cast<char*>(appdata_ptr);
+ // respBuf will be freed by the caller
+ char* respBuf = static_cast<char*>(pool.alloc(strlen(password) + 1));
+ resp[i]->resp = ::strcpy(respBuf, password);
+ }
+ else
+ return PAM_CONV_ERR;
+ break;
+ }
+ case PAM_TEXT_INFO:
+ {
+ resp[i]->resp_retcode = 0;
+ char* respBuf = static_cast<char*>(pool.alloc(1));
+ respBuf[0] = '\0';
+ resp[i]->resp = respBuf;
+ break;
+ }
+ case PAM_PROMPT_ECHO_ON:
+ case PAM_ERROR_MSG:
+ default:
+ return PAM_CONV_ERR;
+ }
+ }
+
+ // The caller will free all the memory we allocated
+ pool.relinquishOwnership();
+
+ return PAM_SUCCESS;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ return PAM_CONV_ERR;
+}
+
+} // anonymous namespace
+
+
+PAM::PAM(const std::string& service, bool silent) :
+ service_(service),
+ defaultFlags_(silent ? PAM_SILENT : 0),
+ pamh_(NULL),
+ status_(PAM_SUCCESS)
+{
+}
+
+PAM::~PAM()
+{
+ try
+ {
+ close();
+ }
+ catch(...)
+ {
+ }
+}
+
+std::pair<int, const std::string> PAM::lastError()
+{
+ return std::pair<int, const std::string>(
+ status_,
+ std::string(::pam_strerror(pamh_, status_)));
+}
+
+int PAM::login(const std::string& username,
+ const std::string& password)
+{
+ struct pam_conv myConv;
+ myConv.conv = conv;
+ myConv.appdata_ptr = const_cast<void*>(static_cast<const void*>(password.c_str()));
+ status_ = ::pam_start(service_.c_str(),
+ username.c_str(),
+ &myConv,
+ &pamh_);
+ if (status_ != PAM_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE("pam_start failed: " + lastError().second);
+ return status_;
+ }
+
+ status_ = ::pam_authenticate(pamh_, defaultFlags_);
+ if (status_ != PAM_SUCCESS)
+ {
+ if (status_ != PAM_AUTH_ERR)
+ LOG_ERROR_MESSAGE("pam_authenticate failed: " + lastError().second);
+ return status_;
+ }
+
+ status_ = ::pam_acct_mgmt(pamh_, defaultFlags_);
+ if (status_ != PAM_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE("pam_acct_mgmt failed: " + lastError().second);
+ return status_;
+ }
+
+ return PAM_SUCCESS;
+}
+
+void PAM::close()
+{
+ if (pamh_)
+ {
+ ::pam_end(pamh_, status_ | (defaultFlags_ & PAM_SILENT));
+ pamh_ = NULL;
+ }
+}
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/system/PosixChildProcess.cpp b/src/cpp/core/system/PosixChildProcess.cpp
new file mode 100644
index 0000000..b592ff1
--- /dev/null
+++ b/src/cpp/core/system/PosixChildProcess.cpp
@@ -0,0 +1,823 @@
+/*
+ * PosixChildProcess.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ChildProcess.hpp"
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#ifdef __APPLE__
+#include <util.h>
+#include <sys/ttycom.h>
+#include <sys/ioctl.h>
+#else
+#include <pty.h>
+#include <asm/ioctls.h>
+#endif
+
+#include <sys/wait.h>
+#include <sys/types.h>
+
+#include <boost/bind.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+#include <core/system/ProcessArgs.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <core/PerformanceTimer.hpp>
+
+#include "ChildProcess.hpp"
+
+namespace core {
+namespace system {
+
+namespace {
+
+// pipe handle indexes
+const int READ = 0;
+const int WRITE = 1;
+const std::size_t READ_ERR = -1;
+
+int resolveExitStatus(int status)
+{
+ return WIFEXITED(status) ? WEXITSTATUS(status) : status;
+}
+
+void setPipeNonBlocking(int pipeFd)
+{
+ int flags = ::fcntl(pipeFd, F_GETFL);
+ if ( (flags != -1) && !(flags & O_NONBLOCK) )
+ ::fcntl(pipeFd, F_SETFL, flags | O_NONBLOCK);
+}
+
+void closePipe(int pipeFd, const ErrorLocation& location)
+{
+ safePosixCall<int>(boost::bind(::close, pipeFd), location);
+}
+
+
+void closePipe(int* pipeDescriptors, const ErrorLocation& location)
+{
+ closePipe(pipeDescriptors[READ], location);
+ closePipe(pipeDescriptors[WRITE], location);
+}
+
+Error readPipe(int pipeFd, std::string* pOutput, bool *pEOF = NULL)
+{
+ // default to not eof
+ if (pEOF)
+ *pEOF = false;
+
+ // setup and read into buffer
+ const std::size_t kBufferSize = 512;
+ char buffer[kBufferSize];
+ std::size_t bytesRead = posixCall<std::size_t>(
+ boost::bind(::read, pipeFd, buffer, kBufferSize));
+ while (true)
+ {
+ // check for error
+ if (bytesRead == READ_ERR)
+ {
+ if (errno == EAGAIN) // carve-out for O_NONBLOCK pipes
+ return Success();
+
+ // on linux slave terminals return EIO rather than bytesRead == 0
+ // to indicate end of file
+ else if ((errno == EIO) && ::isatty(pipeFd))
+ {
+ if (pEOF)
+ *pEOF = true;
+
+ return Success();
+ }
+ else
+ return systemError(errno, ERROR_LOCATION);
+ }
+
+ // check for eof
+ else if (bytesRead == 0)
+ {
+ if (pEOF)
+ *pEOF = true;
+
+ return Success();
+ }
+
+ // append to output
+ pOutput->append(buffer, bytesRead);
+
+ // read more bytes
+ bytesRead = posixCall<std::size_t>(
+ boost::bind(::read, pipeFd, buffer, kBufferSize));
+ }
+
+ // keep compiler happy
+ return Success();
+}
+
+} // anonymous namespace
+
+
+
+struct ChildProcess::Impl
+{
+ Impl() :
+ pid(-1),
+ fdStdin(-1),
+ fdStdout(-1),
+ fdStderr(-1),
+ fdMaster(-1),
+ ctrlC(0x03)
+ {
+ }
+
+ pid_t pid;
+ int fdStdin;
+ int fdStdout;
+ int fdStderr;
+
+ // pty related
+ int fdMaster;
+ char ctrlC;
+
+ void init(pid_t pid, int fdStdin, int fdStdout, int fdStderr)
+ {
+ this->pid = pid;
+ this->fdStdin = fdStdin;
+ this->fdStdout = fdStdout;
+ this->fdStderr = fdStderr;
+ this->fdMaster = -1;
+ }
+
+ void init(pid_t pid, int fdMaster)
+ {
+ this->pid = pid;
+ this->fdStdin = fdMaster;
+ this->fdStdout = fdMaster;
+ this->fdStderr = -1;
+ this->fdMaster = fdMaster;
+ }
+
+ void closeAll(const ErrorLocation &location)
+ {
+ closeAll(true, location);
+ }
+
+ void closeAll(bool clearPid, const ErrorLocation& location)
+ {
+ if (clearPid)
+ pid = -1;
+
+ if (fdMaster != -1)
+ {
+ closeFD(&fdMaster, location);
+ fdStdin = -1;
+ fdStdout = -1;
+ }
+ else
+ {
+ closeFD(&fdStdin, location);
+ closeFD(&fdStdout, location);
+ closeFD(&fdStderr, location);
+ }
+ }
+
+ void closeFD(int* pFD, const ErrorLocation& location)
+ {
+ if (*pFD >= 0)
+ {
+ closePipe(*pFD, location);
+ *pFD = -1;
+ }
+ }
+};
+
+
+ChildProcess::ChildProcess()
+ : pImpl_(new Impl())
+{
+}
+
+void ChildProcess::init(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options)
+{
+ exe_ = exe;
+ args_ = args;
+ options_ = options;
+}
+
+void ChildProcess::init(const std::string& command,
+ const ProcessOptions& options)
+{
+ std::vector<std::string> args;
+ args.push_back("-c");
+
+ std::string realCommand = command;
+ if (!options.stdOutFile.empty())
+ realCommand += " > " + shell_utils::escape(options.stdOutFile);
+ if (!options.stdErrFile.empty())
+ realCommand += " 2> " + shell_utils::escape(options.stdErrFile);
+ args.push_back(realCommand);
+
+ init("/bin/sh", args, options);
+}
+
+ChildProcess::~ChildProcess()
+{
+}
+
+Error ChildProcess::writeToStdin(const std::string& input, bool eof)
+{
+ std::size_t written;
+ Error error = posixCall<std::size_t>(boost::bind(::write,
+ pImpl_->fdStdin,
+ input.c_str(),
+ input.length()),
+ ERROR_LOCATION,
+ &written);
+ if (error)
+ return error;
+
+ // close if requested
+ if (eof)
+ pImpl_->closeFD(&pImpl_->fdStdin, ERROR_LOCATION);
+
+ // check for correct bytes written
+ if (written != static_cast<std::size_t>(input.length()))
+ return systemError(boost::system::errc::io_error, ERROR_LOCATION);
+
+ // return success
+ return Success();
+}
+
+Error ChildProcess::ptySetSize(int cols, int rows)
+{
+ // verify we are dealing with a pseudoterminal
+ if (!options().pseudoterminal)
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+
+ // define winsize structure
+ struct winsize winp;
+ winp.ws_col = cols;
+ winp.ws_row = rows;
+ winp.ws_xpixel = 0;
+ winp.ws_ypixel = 0;
+
+ // set it
+ int res = ::ioctl(pImpl_->fdMaster, TIOCSWINSZ, &winp);
+ if (res == -1)
+ return systemError(errno, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+Error ChildProcess::ptyInterrupt()
+{
+ // verify we are dealing with a pseudoterminal
+ if (!options().pseudoterminal)
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+
+ // write control-c to the slave
+ return posixCall<int>(boost::bind(::write,
+ pImpl_->fdMaster,
+ &pImpl_->ctrlC,
+ sizeof(pImpl_->ctrlC)),
+ ERROR_LOCATION);
+}
+
+Error ChildProcess::terminate()
+{
+ // only send signal if the process is open
+ if (pImpl_->pid == -1)
+ return systemError(ESRCH, ERROR_LOCATION);
+
+ // special code path for pseudoterminal
+ if (options_.pseudoterminal)
+ {
+ // on OSX you need to close all of the terminal handles to get
+ // bash to quit, however some other processes (like svn+ssh
+ // require the signal)
+#ifdef __APPLE__
+ pImpl_->closeAll(false, ERROR_LOCATION);
+#endif
+
+ if (::killpg(::getpgid(pImpl_->pid), SIGTERM) == -1)
+ {
+ if (errno == EPERM) // see note below on carve out for EPERM
+ return Success();
+ else
+ return systemError(errno, ERROR_LOCATION);
+ }
+ else
+ return Success();
+ }
+ else
+ {
+ // determine target pid (kill just this pid or pid + children)
+ pid_t pid = pImpl_->pid;
+ if (options_.detachSession || options_.terminateChildren)
+ {
+ pid = -pid;
+ }
+
+ // send signal
+ if (::kill(pid, SIGTERM) == -1)
+ {
+ // when killing an entire process group EPERM can be returned if even
+ // a single one of the subprocesses couldn't be killed. in this case
+ // the signal is still delivered and other subprocesses may have been
+ // killed so we don't log an error
+ if (pid < 0 && errno == EPERM)
+ return Success();
+ else
+ return systemError(errno, ERROR_LOCATION);
+ }
+ else
+ return Success();
+ }
+}
+
+
+Error ChildProcess::run()
+{
+ // declarations
+ pid_t pid = 0;
+ int fdInput[2] = {0,0};
+ int fdOutput[2] = {0,0};
+ int fdError[2] = {0,0};
+ int fdMaster = 0;
+
+ // pseudoterminal mode: fork using the special forkpty call
+ if (options_.pseudoterminal)
+ {
+ char* nullName = NULL;
+ struct termios* nullTermp = NULL;
+ struct winsize winSize;
+ winSize.ws_col = options_.pseudoterminal.get().cols;
+ winSize.ws_row = options_.pseudoterminal.get().rows;
+ winSize.ws_xpixel = 0;
+ winSize.ws_ypixel = 0;
+ Error error = posixCall<pid_t>(
+ boost::bind(::forkpty, &fdMaster, nullName, nullTermp, &winSize),
+ ERROR_LOCATION,
+ &pid);
+ if (error)
+ return error;
+ }
+
+ // standard mode: use conventional fork + stream redirection
+ else
+ {
+ // standard input
+ Error error = posixCall<int>(boost::bind(::pipe, fdInput), ERROR_LOCATION);
+ if (error)
+ return error;
+
+ // standard output
+ error = posixCall<int>(boost::bind(::pipe, fdOutput), ERROR_LOCATION);
+ if (error)
+ {
+ closePipe(fdInput, ERROR_LOCATION);
+ return error;
+ }
+
+ // standard error
+ error = posixCall<int>(boost::bind(::pipe, fdError), ERROR_LOCATION);
+ if (error)
+ {
+ closePipe(fdInput, ERROR_LOCATION);
+ closePipe(fdOutput, ERROR_LOCATION);
+ return error;
+ }
+
+ // fork
+ error = posixCall<pid_t>(::fork, ERROR_LOCATION, &pid);
+ if (error)
+ {
+ closePipe(fdInput, ERROR_LOCATION);
+ closePipe(fdOutput, ERROR_LOCATION);
+ closePipe(fdError, ERROR_LOCATION);
+ return error;
+ }
+ }
+
+ // child
+ if (pid == 0)
+ {
+ // NOTE: within the child we want to make sure in all cases that
+ // we call ::execv to execute the program. as a result if any
+ // errors occur while we are setting up for the ::execv we log
+ // and continue rather than calling ::exit (we do this to avoid
+ // strange error conditions related to global c++ objects being
+ // torn down in a non-standard sequence).
+
+ // check for an onAfterFork function
+ if (options_.onAfterFork)
+ options_.onAfterFork();
+
+ // if we didn't create a pseudoterminal then check the detachSession
+ // and terminateChildren options to see whether we need to setsid
+ // or setpgid(0,0). we skip the check for pseudoterminals because
+ // forkpty calls setsid internally
+ if (!options_.pseudoterminal)
+ {
+ // If options.detachSession is requested then separate.
+ if (options_.detachSession)
+ {
+ if (::setsid() == -1)
+ {
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+ // intentionally fail forward (see note above)
+ }
+ }
+ else if (options_.terminateChildren)
+ {
+ // No need to call ::setpgid(0,0) if ::setsid() was already called
+
+ // if options.terminateChildren is requested then obtain a new
+ // process group (using our own process id). this enables terminate
+ // to specify -pid to kill which will kill this process and all of
+ // its children. note that another side-effect is that this process
+ // will not automatically die with its parent, so the parent
+ // may want to kill all children from the processSupervisor on exit
+ if (::setpgid(0,0) == -1)
+ {
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+ // intentionally fail forward (see note above)
+ }
+ }
+ }
+
+ // clear the child signal mask
+ Error error = core::system::clearSignalMask();
+ if (error)
+ {
+ LOG_ERROR(error);
+ // intentionally fail forward (see note above)
+ }
+
+ // pseudoterminal mode: file descriptor work is already handled
+ // by forkpty, all we need to do is configure terminal behavior
+ if (options_.pseudoterminal)
+ {
+ // get current attributes
+ struct termios termp;
+ Error error = posixCall<int>(
+ boost::bind(::tcgetattr, STDIN_FILENO, &termp),
+ ERROR_LOCATION);
+ if (!error)
+ {
+ // specify raw mode (but don't ignore signals -- this is done
+ // so we can send Ctrl-C for interrupts)
+ ::cfmakeraw(&termp);
+ termp.c_lflag |= ISIG;
+
+ // set attribs
+ safePosixCall<int>(
+ boost::bind(::tcsetattr, STDIN_FILENO, TCSANOW, &termp),
+ ERROR_LOCATION);
+
+ // save the VINTR character
+ pImpl_->ctrlC = termp.c_cc[VINTR];
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+
+ // standard mode: close/redirect pipes
+ else
+ {
+ // close unused pipes -- intentionally fail forward (see note above)
+ closePipe(fdInput[WRITE], ERROR_LOCATION);
+ closePipe(fdOutput[READ], ERROR_LOCATION);
+ closePipe(fdError[READ], ERROR_LOCATION);
+
+ // wire standard streams (intentionally fail forward)
+ safePosixCall<int>(boost::bind(::dup2, fdInput[READ], STDIN_FILENO),
+ ERROR_LOCATION);
+ safePosixCall<int>(boost::bind(::dup2, fdOutput[WRITE], STDOUT_FILENO),
+ ERROR_LOCATION);
+ safePosixCall<int>(
+ boost::bind(::dup2,
+ options_.redirectStdErrToStdOut ? fdOutput[WRITE]
+ : fdError[WRITE],
+ STDERR_FILENO),
+ ERROR_LOCATION);
+ }
+
+ // close all open file descriptors other than std streams
+ error = core::system::closeNonStdFileDescriptors();
+ if (error)
+ {
+ LOG_ERROR(error);
+ // intentionally fail forward (see note above)
+ }
+
+ if (!options_.workingDir.empty())
+ {
+ if (::chdir(options_.workingDir.absolutePath().c_str()))
+ {
+ LOG_ERROR(systemError(errno, "Error changing directory", ERROR_LOCATION));
+ }
+ }
+
+ // build args (on heap so they stay around after exec)
+ // create set of args to pass (needs to include the cmd)
+ std::vector<std::string> args;
+ args.push_back(exe_);
+ args.insert(args.end(), args_.begin(), args_.end());
+ using core::system::ProcessArgs;
+ ProcessArgs* pProcessArgs = new ProcessArgs(args);
+
+ if (options_.environment)
+ {
+ // build env (on heap, see comment above)
+ std::vector<std::string> env;
+ const Options& options = options_.environment.get();
+ for (Options::const_iterator
+ it = options.begin(); it != options.end(); ++it)
+ {
+ env.push_back(it->first + "=" + it->second);
+ }
+ ProcessArgs* pEnvironment = new ProcessArgs(env);
+
+ // execute
+ ::execve(exe_.c_str(), pProcessArgs->args(), pEnvironment->args());
+ }
+ else
+ {
+ // execute
+ ::execv(exe_.c_str(), pProcessArgs->args()) ;
+ }
+
+ // in the normal case control should never return from execv (it starts
+ // anew at main of the process pointed to by path). therefore, if we get
+ // here then there was an error
+ error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("exe", exe_);
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // parent
+ else
+ {
+ // pseudoterminal mode: wire input/output streams to fdMaster
+ // returned from forkpty
+ if (options_.pseudoterminal)
+ {
+ // record masterFd as our handles
+ pImpl_->init(pid, fdMaster);
+ }
+
+ // standard mode: close unused pipes & wire streams to approprite fds
+ else
+ {
+ // close unused pipes
+ closePipe(fdInput[READ], ERROR_LOCATION);
+ closePipe(fdOutput[WRITE], ERROR_LOCATION);
+ closePipe(fdError[WRITE], ERROR_LOCATION);
+
+ // record pipe handles
+ pImpl_->init(pid, fdInput[WRITE], fdOutput[READ], fdError[READ]);
+ }
+
+ return Success();
+ }
+
+ // keep compiler happy
+ return Success();
+}
+
+Error SyncChildProcess::readStdOut(std::string* pOutput)
+{
+ return readPipe(pImpl_->fdStdout, pOutput);
+}
+
+Error SyncChildProcess::readStdErr(std::string* pOutput)
+{
+ return readPipe(pImpl_->fdStderr, pOutput);
+}
+
+Error SyncChildProcess::waitForExit(int* pExitStatus)
+{
+ // blocking wait for exit
+ int status;
+ pid_t result = posixCall<pid_t>(
+ boost::bind(::waitpid, pImpl_->pid, &status, 0));
+
+ // always close all of the pipes
+ pImpl_->closeAll(ERROR_LOCATION);
+
+ // check result
+ if (result == -1)
+ {
+ *pExitStatus = -1;
+
+ if (errno == ECHILD) // carve out for child already reaped
+ return Success();
+ else
+ return systemError(errno, ERROR_LOCATION);
+ }
+ else
+ {
+ *pExitStatus = resolveExitStatus(status);
+ return Success();
+ }
+}
+
+struct AsyncChildProcess::AsyncImpl
+{
+ AsyncImpl()
+ : calledOnStarted_(false),
+ finishedStdout_(false),
+ finishedStderr_(false),
+ exited_(false)
+ {
+ }
+
+ bool calledOnStarted_;
+ bool finishedStdout_;
+ bool finishedStderr_;
+ bool exited_;
+};
+
+AsyncChildProcess::AsyncChildProcess(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options)
+ : ChildProcess(), pAsyncImpl_(new AsyncImpl())
+{
+ init(exe, args, options);
+ if (!options.stdOutFile.empty() || !options.stdErrFile.empty())
+ {
+ LOG_ERROR_MESSAGE(
+ "stdOutFile/stdErrFile options cannot be used with runProgram");
+ }
+}
+
+AsyncChildProcess::AsyncChildProcess(const std::string& command,
+ const ProcessOptions& options)
+ : ChildProcess(), pAsyncImpl_(new AsyncImpl())
+{
+ init(command, options);
+}
+
+AsyncChildProcess::~AsyncChildProcess()
+{
+}
+
+Error AsyncChildProcess::terminate()
+{
+#ifdef __APPLE__
+ if (options().pseudoterminal)
+ {
+ pAsyncImpl_->finishedStderr_ = true;
+ pAsyncImpl_->finishedStdout_ = true;
+ }
+#endif
+
+ return ChildProcess::terminate();
+}
+
+
+void AsyncChildProcess::poll()
+{
+ // call onStarted if we haven't yet
+ if (!(pAsyncImpl_->calledOnStarted_))
+ {
+ // make sure the output pipes are setup for async reading
+ setPipeNonBlocking(pImpl_->fdStdout);
+
+ // if we are providing a pseudoterminal then stderr is disabled
+ // so mark it finished. otherwise, configure it for non-blocking io
+ if (options().pseudoterminal)
+ pAsyncImpl_->finishedStderr_ = true;
+ else
+ setPipeNonBlocking(pImpl_->fdStderr);
+
+ if (callbacks_.onStarted)
+ callbacks_.onStarted(*this);
+ pAsyncImpl_->calledOnStarted_ = true;
+ }
+
+ // call onContinue
+ if (callbacks_.onContinue)
+ {
+ if (!callbacks_.onContinue(*this))
+ {
+ // terminate the proces
+ Error error = terminate();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ // check stdout and fire event if we got output
+ if (!pAsyncImpl_->finishedStdout_)
+ {
+ bool eof;
+ std::string out;
+ Error error = readPipe(pImpl_->fdStdout, &out, &eof);
+ if (error)
+ {
+ reportError(error);
+ }
+ else
+ {
+ if (!out.empty() && callbacks_.onStdout)
+ callbacks_.onStdout(*this, out);
+
+ if (eof)
+ pAsyncImpl_->finishedStdout_ = true;
+ }
+ }
+
+ // check stderr and fire event if we got output
+ if (!pAsyncImpl_->finishedStderr_)
+ {
+ bool eof;
+ std::string err;
+ Error error = readPipe(pImpl_->fdStderr, &err, &eof);
+
+ if (error)
+ {
+ reportError(error);
+ }
+ else
+ {
+ if (!err.empty() && callbacks_.onStderr)
+ callbacks_.onStderr(*this, err);
+
+ if (eof)
+ pAsyncImpl_->finishedStderr_ = true;
+ }
+ }
+
+
+ // Check for exited. Note that this method specifies WNOHANG
+ // so we don't block forever waiting for a process the exit. We may
+ // not be able to reap the child due to an error (typically ECHILD,
+ // which occurs if the child was reaped by a global handler) in which
+ // case we'll allow the exit sequence to proceed and simply pass -1 as
+ // the exit status.
+ int status;
+ pid_t result = posixCall<pid_t>(
+ boost::bind(::waitpid, pImpl_->pid, &status, WNOHANG));
+
+ // either a normal exit or an error while waiting
+ if (result != 0)
+ {
+ // close all of our pipes
+ pImpl_->closeAll(ERROR_LOCATION);
+
+ // fire exit event
+ if (callbacks_.onExit)
+ {
+ // resolve exit status
+ if (result > 0)
+ status = resolveExitStatus(status);
+ else
+ status = -1;
+
+ // call onExit
+ callbacks_.onExit(status);
+ }
+
+ // set exited_ flag so that our exited function always
+ // returns the right value
+ pAsyncImpl_->exited_ = true;
+
+ // if this is an error that isn't ECHILD then log it (we never
+ // expect this to occur as the only documented error codes are
+ // EINTR and ECHILD, and EINTR is handled internally by posixCall)
+ if (result == -1 && errno != ECHILD)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+ }
+}
+
+bool AsyncChildProcess::exited()
+{
+ return pAsyncImpl_->exited_;
+}
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixChildProcessTracker.cpp b/src/cpp/core/system/PosixChildProcessTracker.cpp
new file mode 100644
index 0000000..ef23751
--- /dev/null
+++ b/src/cpp/core/system/PosixChildProcessTracker.cpp
@@ -0,0 +1,140 @@
+/*
+ * PosixChildProcessTracker.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/PosixChildProcessTracker.hpp>
+
+#include <sys/wait.h>
+
+#include <boost/format.hpp>
+
+namespace core {
+namespace system {
+
+namespace {
+
+// wraper for waitPid which tries again for EINTR
+int waitPid(PidType pid, int* pStatus)
+{
+ for (;;)
+ {
+ int result = ::waitpid(pid, pStatus, WNOHANG);
+ if (result == -1 && errno == EINTR)
+ continue;
+ return result;
+ }
+}
+
+} // anonymous namespace
+
+
+void ChildProcessTracker::addProcess(PidType pid, ExitHandler exitHandler)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ processes_.insert(std::make_pair(pid, exitHandler));
+ }
+ END_LOCK_MUTEX
+}
+
+void ChildProcessTracker::notifySIGCHILD()
+{
+ // We make a copy of hte active pids so that we can do the reaping
+ // outside of the pidsMutex_. This is an extra conservative precaution
+ // in case there is ever an issue with waitpid blocking.
+ std::map<PidType,ExitHandler> processes = activeProcesses();
+
+ // attempt to reap each process
+ std::for_each(processes.begin(),
+ processes.end(),
+ boost::bind(&ChildProcessTracker::attemptToReapProcess,
+ this, _1));
+}
+
+void ChildProcessTracker::attemptToReapProcess(
+ const std::pair<PidType,ExitHandler>& process)
+{
+ // non-blocking wait for the child
+ int pid = process.first;
+ int status;
+ int result = waitPid(pid, &status);
+
+ // reaped the child
+ if (result == pid)
+ {
+ // confirm this was a real exit
+ bool exited = false;
+ if (WIFEXITED(status))
+ {
+ exited = true;
+ status = WEXITSTATUS(status);
+ }
+ else if (WIFSIGNALED(status))
+ {
+ exited = true;
+ }
+
+ // if it was a real exit (as opposed to a SIGSTOP or SIGCONT)
+ // then remove the pid from our table and fire the event
+ if (exited)
+ {
+ // all done with this pid
+ removeProcess(pid);
+
+ // call exit handler if we have one
+ ExitHandler exitHandler = process.second;
+ if (exitHandler)
+ exitHandler(pid, status);
+ }
+ else
+ {
+ boost::format fmt("Received SIGCHLD when child did not "
+ "actually exit (pid=%1%, status=%2%");
+ LOG_WARNING_MESSAGE(boost::str(fmt % pid % status));
+ }
+ }
+ // error occured
+ else if (result == -1)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("pid", pid);
+ LOG_ERROR(error);
+ }
+}
+
+void ChildProcessTracker::removeProcess(PidType pid)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ processes_.erase(pid);
+ }
+ END_LOCK_MUTEX
+}
+
+std::map<PidType,ChildProcessTracker::ExitHandler>
+ ChildProcessTracker::activeProcesses()
+{
+ LOCK_MUTEX(mutex_)
+ {
+ return processes_;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return std::map<PidType,ExitHandler>();
+}
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixCrypto.cpp b/src/cpp/core/system/PosixCrypto.cpp
new file mode 100644
index 0000000..4c17e5d
--- /dev/null
+++ b/src/cpp/core/system/PosixCrypto.cpp
@@ -0,0 +1,320 @@
+/*
+ * PosixCrypto.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/system/Crypto.hpp>
+
+#include <fcntl.h>
+
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+
+#include <algorithm>
+
+#include <boost/utility.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+// openssl calls on lion are are all marked as deprecated
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+using namespace core;
+
+namespace core {
+namespace system {
+namespace crypto {
+
+namespace {
+
+// NOTE: we've never see the error codepath in spite of trying to get it to
+// return an error by tweaking params -- NULL params caused a crash rather
+// than returning an error). this is presumably because we are calling
+// such low level functions. we may want to look at the src code to see
+// if there is a way to test/engineer an error (or remove the error
+// checking if there is no way to get one)
+
+Error lastCryptoError(const ErrorLocation& location)
+{
+ // get the error code
+ unsigned long ec = ::ERR_get_error();
+ if (ec == 0)
+ {
+ LOG_WARNING_MESSAGE("lastCrytpoError called with no pending error");
+ return systemError(boost::system::errc::not_supported,
+ "lastCrytpoError called with no pending error",
+ location);
+ }
+
+ // get the error message (docs say max len is 120)
+ const int ERR_BUFF_SIZE = 250;
+ char errorBuffer[ERR_BUFF_SIZE];
+ ::ERR_error_string_n(ec, errorBuffer, ERR_BUFF_SIZE);
+
+ // return the error
+ return systemError(boost::system::errc::bad_message,
+ errorBuffer,
+ location);
+}
+
+class BIOFreeAllScope : boost::noncopyable
+{
+public:
+ BIOFreeAllScope(BIO* pMem)
+ : pMem_(pMem)
+ {
+ }
+
+ ~BIOFreeAllScope()
+ {
+ try
+ {
+ ::BIO_free_all(pMem_);
+ }
+ catch(...)
+ {
+ }
+ }
+private:
+ BIO* pMem_;
+};
+
+} // anonymous namespace
+
+void initialize()
+{
+ // load global error string table
+ ::ERR_load_crypto_strings();
+}
+
+Error HMAC_SHA1(const std::string& data,
+ const std::string& key,
+ std::vector<unsigned char>* pHMAC)
+{
+ // copy data into vector
+ std::vector<unsigned char> keyVector;
+ std::copy(key.begin(), key.end(), std::back_inserter(keyVector));
+
+ // call core
+ return HMAC_SHA1(data, keyVector, pHMAC);
+}
+
+Error HMAC_SHA1(const std::string& data,
+ const std::vector<unsigned char>& key,
+ std::vector<unsigned char>* pHMAC)
+{
+ // copy data into data vector
+ std::vector<unsigned char> dataVector;
+ std::copy(data.begin(), data.end(), std::back_inserter(dataVector));
+
+ // perform the hash
+ unsigned int md_len = 0;
+ pHMAC->resize(EVP_MAX_MD_SIZE);
+ unsigned char* pResult = ::HMAC(EVP_sha1(),
+ &(key[0]),
+ key.size(),
+ &(dataVector[0]),
+ dataVector.size(),
+ &(pHMAC->operator[](0)),
+ &md_len);
+ if (pResult != NULL)
+ {
+ pHMAC->resize(md_len);
+ return Success();
+ }
+ else
+ {
+ return lastCryptoError(ERROR_LOCATION);
+ }
+}
+
+Error base64Encode(const std::vector<unsigned char>& data,
+ std::string* pEncoded)
+{
+ return base64Encode(&(data[0]), data.size(), pEncoded);
+}
+
+Error base64Encode(const unsigned char* pData,
+ int len,
+ std::string* pEncoded)
+{
+ // allocate BIO
+ BIO* pB64 = ::BIO_new(BIO_f_base64());
+ if (pB64 == NULL)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // no newlines
+ BIO_set_flags(pB64, BIO_FLAGS_BASE64_NO_NL);
+
+ // make sure it is freed prior to exit from the function
+ BIOFreeAllScope freeB64Scope(pB64);
+
+ // allocate memory stream
+ BIO* pMem = ::BIO_new(BIO_s_mem());
+ if (pMem == NULL)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // tie the stream to the b64 stream
+ pB64 = ::BIO_push(pB64, pMem);
+
+ // perform the encoding
+ int written = ::BIO_write(pB64, pData, len);
+ if (written != len)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // flush all writes
+ int result = BIO_flush(pB64);
+ if (result <= 0)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // seek to beginning of memory stream
+ result = BIO_seek(pMem, 0);
+ if (result == -1)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // read the memory stream
+ std::vector<char> buffer(len *2); // plenty more than len * 1.37 + padding
+ int bytesRead = ::BIO_read(pMem, &(buffer[0]), buffer.capacity());
+ if (bytesRead < 0 && ::ERR_get_error() != 0)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // copy to out param
+ buffer.resize(bytesRead);
+ pEncoded->assign(buffer.begin(), buffer.end());
+
+ // return success
+ return Success();
+}
+
+
+Error base64Decode(const std::string& data,
+ std::vector<unsigned char>* pDecoded)
+{
+ // allocate b64 BIO
+ BIO* pB64 = ::BIO_new(BIO_f_base64());
+ if (pB64 == NULL)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // no newlines
+ BIO_set_flags(pB64, BIO_FLAGS_BASE64_NO_NL);
+
+ // make sure it is freed prior to exit from the function
+ BIOFreeAllScope freeB64Scope(pB64);
+
+ // allocate buffer
+ BIO* pMem = BIO_new_mem_buf((void*)data.data(), data.length());
+ if (pMem == NULL)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // tie the stream to the b64 stream
+ pB64 = ::BIO_push(pB64, pMem);
+
+ // reserve adequate memory in the decoded buffer and read into it
+ pDecoded->clear();
+ pDecoded->resize(data.length());
+ int bytesRead = ::BIO_read(pB64,
+ &(pDecoded->operator[](0)),
+ pDecoded->size());
+ if (bytesRead < 0)
+ return lastCryptoError(ERROR_LOCATION);
+
+ // resize the out buffer to the number of bytes actually read
+ pDecoded->resize(bytesRead);
+
+ // return success
+ return Success();
+
+}
+
+namespace {
+RSA* s_pRSA;
+std::string s_modulo;
+std::string s_exponent;
+}
+
+core::Error rsaInit()
+{
+ const int KEY_SIZE = 1024;
+ const int ENTROPY_BYTES = 4096;
+
+ int rnd = ::open("/dev/urandom", O_RDONLY);
+ if (rnd == -1)
+ return systemError(errno, ERROR_LOCATION);
+
+ char entropy[ENTROPY_BYTES];
+ if (-1 == ::read(rnd, entropy, ENTROPY_BYTES))
+ {
+ ::close(rnd);
+ return systemError(errno, ERROR_LOCATION);
+ }
+ ::close(rnd);
+
+ RAND_seed(entropy, ENTROPY_BYTES);
+
+ s_pRSA = ::RSA_generate_key(KEY_SIZE, 0x10001, NULL, NULL);
+ if (!s_pRSA)
+ return lastCryptoError(ERROR_LOCATION);
+
+ char* n = BN_bn2hex(s_pRSA->n);
+ s_modulo = n;
+ OPENSSL_free(n);
+ char* e = BN_bn2hex(s_pRSA->e);
+ s_exponent = e;
+ OPENSSL_free(e);
+
+ return Success();
+}
+
+void rsaPublicKey(std::string* pExponent, std::string* pModulo)
+{
+ pModulo->assign(s_modulo.begin(), s_modulo.end());
+ pExponent->assign(s_exponent.begin(), s_exponent.end());
+}
+
+core::Error rsaPrivateDecrypt(const std::string& cipherText, std::string* pPlainText)
+{
+ std::vector<unsigned char> cipherTextBytes;
+ Error error = base64Decode(cipherText, &cipherTextBytes);
+ if (error)
+ return error;
+
+ int size = RSA_size(s_pRSA);
+ std::vector<unsigned char> plainTextBytes(size);
+ int bytesRead = RSA_private_decrypt(cipherTextBytes.size(),
+ &cipherTextBytes[0],
+ &plainTextBytes[0],
+ s_pRSA,
+ RSA_PKCS1_PADDING);
+ if (bytesRead == -1)
+ return lastCryptoError(ERROR_LOCATION);
+
+ plainTextBytes.resize(bytesRead);
+ pPlainText->assign(plainTextBytes.begin(), plainTextBytes.end());
+
+ return Success();
+}
+
+
+} // namespace crypto
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixEnvironment.cpp b/src/cpp/core/system/PosixEnvironment.cpp
new file mode 100644
index 0000000..ff33749
--- /dev/null
+++ b/src/cpp/core/system/PosixEnvironment.cpp
@@ -0,0 +1,68 @@
+/*
+ * PosixEnvironment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/Environment.hpp>
+
+#include <stdlib.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+extern char **environ;
+
+namespace core {
+namespace system {
+
+namespace impl {
+
+bool optionIsNamed(const Option& option, const std::string& name)
+{
+ return boost::algorithm::equals(option.first, name);
+}
+
+} // namespace impl
+
+void environment(Options* pEnvironment)
+{
+ for (char **env = environ; *env; ++env)
+ {
+ Option envVar;
+ if (parseEnvVar(std::string(*env), &envVar))
+ pEnvironment->push_back(envVar);
+ }
+}
+
+std::string getenv(const std::string& name)
+{
+ char * value = ::getenv(name.c_str());
+ if (value)
+ return std::string(value);
+ else
+ return std::string();
+}
+
+void setenv(const std::string& name, const std::string& value)
+{
+ ::setenv(name.c_str(), value.c_str(), 1);
+}
+
+void unsetenv(const std::string& name)
+{
+ ::unsetenv(name.c_str());
+}
+
+
+} // namespace sytem
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixFileScanner.cpp b/src/cpp/core/system/PosixFileScanner.cpp
new file mode 100644
index 0000000..e8d3f1b
--- /dev/null
+++ b/src/cpp/core/system/PosixFileScanner.cpp
@@ -0,0 +1,186 @@
+/*
+ * PosixFileScanner.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/FileScanner.hpp>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/BoostThread.hpp>
+
+#include "config.h"
+
+namespace core {
+namespace system {
+
+namespace {
+#if defined(__APPLE__) && !defined(HAVE_SCANDIR_POSIX)
+int entryFilter(struct dirent *entry)
+#else
+int entryFilter(const struct dirent *entry)
+#endif
+{
+ if (::strcmp(entry->d_name, ".") == 0 || ::strcmp(entry->d_name, "..") == 0)
+ return 0;
+ else
+ return 1;
+}
+
+// wrapper for scandir api
+Error scanDir(const std::string& dirPath, std::vector<std::string>* pNames)
+{
+ // read directory contents into namelist
+ struct dirent **namelist;
+ int entries = ::scandir(dirPath.c_str(),
+ &namelist,
+ entryFilter,
+ ::alphasort);
+ if (entries == -1)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", dirPath);
+ return error;
+ }
+
+ // extract the namelist then free it
+ for(int i=0; i<entries; i++)
+ {
+ // get the name (then free it)
+ std::string name(namelist[i]->d_name,
+#ifdef __APPLE__
+ namelist[i]->d_namlen);
+#else
+ namelist[i]->d_reclen);
+#endif
+ ::free(namelist[i]);
+
+ // add to the vector
+ pNames->push_back(name);
+ }
+ ::free(namelist);
+
+ return Success();
+}
+
+} // anonymous namespace
+
+Error scanFiles(const tree<FileInfo>::iterator_base& fromNode,
+ const FileScannerOptions& options,
+ tree<FileInfo>* pTree)
+{
+ // clear all existing
+ pTree->erase_children(fromNode);
+
+ // create FilePath for root
+ FilePath rootPath(fromNode->absolutePath());
+
+ // yield if requested (only applies to recursive scans)
+ if (options.recursive && options.yield)
+ boost::this_thread::yield();
+
+ // call onBeforeScanDir hook
+ if (options.onBeforeScanDir)
+ {
+ Error error = options.onBeforeScanDir(*fromNode);
+ if (error)
+ return error;
+ }
+
+ // read directory contents
+ std::vector<std::string> names;
+ Error error = scanDir(fromNode->absolutePath(), &names);
+ if (error)
+ return error;
+
+ // iterate over the names
+ BOOST_FOREACH(const std::string& name, names)
+ {
+ // compute the path
+ std::string path = rootPath.childPath(name).absolutePath();
+
+ // get the attributes
+ struct stat st;
+ int res = ::lstat(path.c_str(), &st);
+ if (res == -1)
+ {
+ if (errno != ENOENT && errno != EACCES)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", path);
+ LOG_ERROR(error);
+ }
+ continue;
+ }
+
+ // create the FileInfo
+ FileInfo fileInfo;
+ bool isSymlink = S_ISLNK(st.st_mode);
+ if (S_ISDIR(st.st_mode))
+ {
+ fileInfo = FileInfo(path, true, isSymlink);
+ }
+ else
+ {
+ fileInfo = FileInfo(path,
+ false,
+ st.st_size,
+#ifdef __APPLE__
+ st.st_mtimespec.tv_sec,
+#else
+ st.st_mtime,
+#endif
+ isSymlink);
+ }
+
+ // apply the filter (if any)
+ if (!options.filter || options.filter(fileInfo))
+ {
+ // add the correct type of FileEntry
+ if (fileInfo.isDirectory())
+ {
+ tree<FileInfo>::iterator_base child = pTree->append_child(fromNode,
+ fileInfo);
+ // recurse if requested and this isn't a link
+ if (options.recursive && !fileInfo.isSymlink())
+ {
+ // try to scan the files in the subdirectory -- if we fail
+ // we continue because we don't want one "bad" directory
+ // to cause us to abort the entire scan. yes the tree
+ // will be incomplete however it will be even more incompete
+ // if we fail entirely
+ Error error = scanFiles(child, options, pTree);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+ else
+ {
+ pTree->append_child(fromNode, fileInfo);
+ }
+ }
+ }
+
+ // return success
+ return Success();
+}
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixLibraryLoader.cpp b/src/cpp/core/system/PosixLibraryLoader.cpp
new file mode 100644
index 0000000..f40a58f
--- /dev/null
+++ b/src/cpp/core/system/PosixLibraryLoader.cpp
@@ -0,0 +1,90 @@
+/*
+ * PosixLibraryLoader.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/LibraryLoader.hpp>
+
+#include <dlfcn.h>
+
+#include <core/Error.hpp>
+
+namespace core {
+namespace system {
+
+namespace {
+
+void addLastDLErrorMessage(Error* pError)
+{
+ const char* msg = ::dlerror();
+ if (msg != NULL)
+ pError->addProperty("dlerror", std::string(msg));
+}
+
+} // anonymous namespace
+
+Error loadLibrary(const std::string& libPath, int options, void** ppLib)
+{
+ *ppLib = NULL;
+ *ppLib = ::dlopen(libPath.c_str(), options);
+ if (*ppLib == NULL)
+ {
+ Error error = systemError(
+ boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ error.addProperty("lib-path", libPath);
+ addLastDLErrorMessage(&error);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error loadSymbol(void* pLib, const std::string& name, void** ppSymbol)
+{
+ *ppSymbol = NULL;
+ *ppSymbol = ::dlsym(pLib, name.c_str());
+ if (*ppSymbol == NULL)
+ {
+ Error error = systemError(boost::system::errc::not_supported,
+ ERROR_LOCATION);
+ error.addProperty("symbol", name);
+ addLastDLErrorMessage(&error);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error closeLibrary(void* pLib)
+{
+ if (::dlclose(pLib) != 0)
+ {
+ Error error = systemError(
+ boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ addLastDLErrorMessage(&error);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/system/PosixOutputCapture.cpp b/src/cpp/core/system/PosixOutputCapture.cpp
new file mode 100644
index 0000000..aaf06fa
--- /dev/null
+++ b/src/cpp/core/system/PosixOutputCapture.cpp
@@ -0,0 +1,179 @@
+/*
+ * PosixOutputCapture.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/OutputCapture.hpp>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <iostream>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/BoostThread.hpp>
+#include <core/BoostErrors.hpp>
+
+#include <core/system/System.hpp>
+
+namespace core {
+namespace system {
+
+namespace {
+
+void readFromPipe(
+ int pipeFd,
+ const boost::function<void(const std::string&)>& outputFunction)
+{
+ const int kBufferSize = 512;
+ char buffer[kBufferSize];
+ int bytesRead = 0;
+ while ( (bytesRead = ::read(pipeFd, buffer, kBufferSize)) > 0 )
+ {
+ std::string output(buffer, bytesRead);
+ outputFunction(output);
+ }
+
+ // log unexpected errors
+ if (bytesRead == -1)
+ {
+ if (errno != EAGAIN && errno != EINTR)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+ }
+}
+
+void standardStreamCaptureThread(
+ int stdoutFd,
+ const boost::function<void(const std::string&)>& stdoutHandler,
+ int stderrFd,
+ const boost::function<void(const std::string&)>& stderrHandler)
+{
+ try
+ {
+ while(true)
+ {
+ // create fd set
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(stdoutFd, &fds);
+ if (stderrFd != -1)
+ FD_SET(stderrFd, &fds);
+
+ // wait
+ int highFd = std::max(stdoutFd, stderrFd);
+ int result = ::select(highFd+1, &fds, NULL, NULL, NULL);
+ if (result != -1)
+ {
+ if (FD_ISSET(stdoutFd, &fds))
+ readFromPipe(stdoutFd, stdoutHandler);
+
+ if (stderrFd != -1)
+ {
+ if (FD_ISSET(stderrFd, &fds))
+ readFromPipe(stderrFd, stderrHandler);
+ }
+ }
+ else if (errno != EINTR)
+ {
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+ }
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+
+Error redirectToPipe(int fd, int* pPipeReadFd)
+{
+ // create pipe
+ int pipe[2];
+ if (::pipe(pipe) == -1)
+ return systemError(errno, ERROR_LOCATION);
+
+ // redirect
+ if (::dup2(pipe[1], fd) == -1)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set read fds to non-blocking
+ if (::fcntl(pipe[0],
+ F_SETFL,
+ ::fcntl(pipe[0], F_GETFL) | O_NONBLOCK) == -1)
+ {
+ return systemError(errno, ERROR_LOCATION);
+ }
+
+ // return read fds
+ *pPipeReadFd = pipe[0];
+ return Success();
+}
+
+} // anonymous namespace
+
+Error captureStandardStreams(
+ const boost::function<void(const std::string&)>& stdoutHandler,
+ const boost::function<void(const std::string&)>& stderrHandler)
+{
+
+ // redirect stdout
+ int stdoutReadPipe = 0;
+ Error error = redirectToPipe(STDOUT_FILENO, &stdoutReadPipe);
+ if (error)
+ return error;
+
+ // set stdout to use unbuffered io
+ ::setvbuf(stdout, NULL, _IONBF, 0);
+
+ // optionally oredirect stderror if handler was provided
+ int stderrReadPipe = -1;
+ if (stderrHandler)
+ {
+ error = redirectToPipe(STDERR_FILENO, &stderrReadPipe);
+ if (error)
+ return error;
+
+ // set stderr to use unbuffered io
+ ::setvbuf(stderr, NULL, _IONBF, 0);
+ }
+
+ // sync c++ iostreams
+ std::ios::sync_with_stdio();
+
+ // launch the monitor thread
+ try
+ {
+ // block all signals for launch of background thread (will cause it
+ // to never receive signals)
+ core::system::SignalBlocker signalBlocker;
+ Error error = signalBlocker.blockAll();
+ if (error)
+ LOG_ERROR(error);
+
+ boost::thread t(boost::bind(standardStreamCaptureThread,
+ stdoutReadPipe,
+ stdoutHandler,
+ stderrReadPipe,
+ stderrHandler));
+
+ return Success();
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ return Error(boost::thread_error::ec_from_exception(e), ERROR_LOCATION);
+ }
+}
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixParentProcessMonitor.cpp b/src/cpp/core/system/PosixParentProcessMonitor.cpp
new file mode 100644
index 0000000..ca49505
--- /dev/null
+++ b/src/cpp/core/system/PosixParentProcessMonitor.cpp
@@ -0,0 +1,108 @@
+/*
+ * PosixParentProcessMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/ParentProcessMonitor.hpp>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <boost/assert.hpp>
+
+#include <core/SafeConvert.hpp>
+#include <core/Log.hpp>
+
+namespace core {
+namespace parent_process_monitor {
+
+namespace {
+
+std::vector<int> s_writeOnExit;
+
+int setFdEnv(std::string name, int val)
+{
+ std::string strVal = safe_convert::numberToString(val);
+ return ::setenv(name.c_str(), strVal.c_str(), strVal.size());
+}
+
+int getFdEnv(std::string name, int defaultVal)
+{
+ char* result = ::getenv(name.c_str());
+ if (!result)
+ return defaultVal;
+ return core::safe_convert::stringTo(result, defaultVal);
+}
+
+void exitHandler()
+{
+ // Signal normal termination to all child processes
+ // that may be waiting
+ for (size_t i = 0; i < s_writeOnExit.size(); i++)
+ {
+ // write to child (don't bother with checking error as there may
+ // be one in the case that the child is already gone)
+ ::write(s_writeOnExit.at(i), "done", 4);
+ }
+}
+
+} // anonymous namespace
+
+Error wrapFork(boost::function<void()> func)
+{
+ int fds[2];
+ int result = ::pipe(fds);
+ if (result != 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ result = setFdEnv("RS_PPM_FD_READ", fds[0]);
+ if (result != 0)
+ return systemError(errno, ERROR_LOCATION);
+ result = setFdEnv("RS_PPM_FD_WRITE", fds[1]);
+ if (result != 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ func();
+
+ ::close(fds[0]);
+
+ ::atexit(exitHandler);
+ s_writeOnExit.push_back(fds[1]);
+
+ return Success();
+}
+
+ParentTermination waitForParentTermination()
+{
+ int fds[2];
+ fds[0] = getFdEnv("RS_PPM_FD_READ", -1);
+ fds[1] = getFdEnv("RS_PPM_FD_WRITE", -1);
+
+ if (fds[0] < 0 || fds[1] < 0)
+ return ParentTerminationNoParent;
+
+ ::close(fds[1]);
+
+ char buf[256];
+ int result = ::read(fds[0], buf, 256);
+
+ if (result == 0)
+ return ParentTerminationAbnormal;
+ else if (result > 0)
+ return ParentTerminationNormal;
+ else
+ return ParentTerminationWaitFailure;
+}
+
+} // namespace parent_process_monitor
+} // namespace core
diff --git a/src/cpp/core/system/PosixShellUtils.cpp b/src/cpp/core/system/PosixShellUtils.cpp
new file mode 100644
index 0000000..2dbeaf7
--- /dev/null
+++ b/src/cpp/core/system/PosixShellUtils.cpp
@@ -0,0 +1,82 @@
+/*
+ * PosixShellUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StringUtils.hpp>
+#include <boost/regex.hpp>
+#include <core/system/ShellUtils.hpp>
+
+namespace core {
+namespace shell_utils {
+
+std::string escape(const std::string& arg)
+{
+ using namespace boost;
+ regex pattern("[\\\\$`!\\n\"]", regex_constants::normal);
+ return "\"" + regex_replace(arg, pattern, "\\$1") + "\"";
+}
+
+std::string escape(const core::FilePath &path)
+{
+ return escape(string_utils::utf8ToSystem(path.absolutePath()));
+}
+
+std::string join(const std::string& command1, const std::string& command2)
+{
+ return command1 + "; " + command2;
+}
+
+std::string join_and(const std::string& command1, const std::string& command2)
+{
+ return command1 + " && " + command2;
+}
+
+std::string join_or(const std::string& command1, const std::string& command2)
+{
+ return "(" + command1 + ") || (" + command2 + ")";
+}
+
+std::string sendStdErrToStdOut(const std::string& command)
+{
+ return "(" + command + ") 2>&1";
+}
+
+std::string sendAllOutputToNull(const std::string& command)
+{
+ return "(" + command + ") > /dev/null 2>&1";
+}
+
+std::string sendStdErrToNull(const std::string& command)
+{
+ return "(" + command + ") 2> /dev/null";
+}
+
+std::string sendNullToStdIn(const std::string& command)
+{
+ return "(" + command + ") < /dev/null";
+}
+
+namespace {
+FilePath s_devnull("/dev/null");
+} // namespace
+
+const FilePath& devnull()
+{
+ return s_devnull;
+}
+
+
+}
+}
+
diff --git a/src/cpp/core/system/PosixSystem.cpp b/src/cpp/core/system/PosixSystem.cpp
new file mode 100644
index 0000000..0d9dee6
--- /dev/null
+++ b/src/cpp/core/system/PosixSystem.cpp
@@ -0,0 +1,1432 @@
+/*
+ * PosixSystem.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/PosixSystem.hpp>
+
+#include <stdio.h>
+
+#include <iostream>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
+
+#include <signal.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <uuid/uuid.h>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+
+#include <boost/thread.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/range.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FileLogWriter.hpp>
+#include <core/Exec.hpp>
+#include <core/SyslogLogWriter.hpp>
+#include <core/StderrLogWriter.hpp>
+#include <core/StringUtils.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/system/ProcessArgs.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/PosixUser.hpp>
+
+#include "config.h"
+
+namespace core {
+namespace system {
+
+namespace {
+
+Error ignoreSig(int signal)
+{
+ struct sigaction sa;
+ ::memset(&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_IGN;
+ int result = ::sigaction(signal, &sa, NULL);
+ if (result != 0)
+ {
+ Error error = systemError(result, ERROR_LOCATION);
+ error.addProperty("signal", signal);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+
+
+int signalForType(SignalType type)
+{
+ switch(type)
+ {
+ case SigInt:
+ return SIGINT;
+
+ case SigHup:
+ return SIGHUP;
+
+ case SigAbrt:
+ return SIGABRT;
+
+ case SigSegv:
+ return SIGSEGV;
+
+ case SigIll:
+ return SIGILL;
+
+ case SigUsr1:
+ return SIGUSR1;
+
+ case SigUsr2:
+ return SIGUSR2;
+
+ case SigPipe:
+ return SIGPIPE;
+
+ case SigChld:
+ return SIGCHLD;
+
+ default:
+ return -1;
+ }
+}
+
+} // anonymous namespace
+
+Error realPath(const FilePath& filePath, FilePath* pRealPath)
+{
+ std::string path = string_utils::utf8ToSystem(filePath.absolutePath());
+
+ char buffer[PATH_MAX*2];
+ char* realPath = ::realpath(path.c_str(), buffer);
+ if (realPath == NULL)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", filePath);
+ return error;
+ }
+
+ *pRealPath = FilePath(string_utils::systemToUtf8(realPath));
+ return Success();
+}
+
+Error realPath(const std::string& path, FilePath* pRealPath)
+{
+ char buffer[PATH_MAX*2];
+ char* realPath = ::realpath(path.c_str(), buffer);
+ if (realPath == NULL)
+ return systemError(errno, ERROR_LOCATION);
+ *pRealPath = FilePath(realPath);
+ return Success();
+}
+
+
+namespace {
+
+// main log writer
+LogWriter* s_pLogWriter = NULL;
+
+// additional log writers
+std::vector<boost::shared_ptr<LogWriter> > s_logWriters;
+
+} // anonymous namespace
+
+void initHook()
+{
+}
+
+void initializeSystemLog(const std::string& programIdentity, int logLevel)
+{
+ if (s_pLogWriter)
+ delete s_pLogWriter;
+
+ s_pLogWriter = new SyslogLogWriter(programIdentity, logLevel);
+}
+
+void initializeStderrLog(const std::string& programIdentity, int logLevel)
+{
+ if (s_pLogWriter)
+ delete s_pLogWriter;
+
+ s_pLogWriter = new StderrLogWriter(programIdentity, logLevel);
+}
+
+void initializeLog(const std::string& programIdentity,
+ int logLevel,
+ const FilePath& logDir)
+{
+ if (s_pLogWriter)
+ delete s_pLogWriter;
+
+ s_pLogWriter = new FileLogWriter(programIdentity, logLevel, logDir);
+}
+
+void addLogWriter(boost::shared_ptr<core::LogWriter> pLogWriter)
+{
+ s_logWriters.push_back(pLogWriter);
+}
+
+void log(LogLevel logLevel, const std::string& message)
+{
+ if (s_pLogWriter)
+ s_pLogWriter->log(logLevel, message);
+
+ std::for_each(s_logWriters.begin(),
+ s_logWriters.end(),
+ boost::bind(&LogWriter::log, _1, logLevel, message));
+}
+
+Error ignoreTerminalSignals()
+{
+ ExecBlock ignoreBlock ;
+ ignoreBlock.addFunctions()
+ (boost::bind(ignoreSig, SIGHUP))
+ (boost::bind(ignoreSig, SIGTSTP))
+ (boost::bind(ignoreSig, SIGTTOU))
+ (boost::bind(ignoreSig, SIGTTIN));
+ return ignoreBlock.execute();
+}
+
+Error ignoreChildExits()
+{
+#if defined(HAVE_SA_NOCLDWAIT) // POSIX compliant
+ struct sigaction reapchildren;
+ ::memset( &reapchildren, 0, sizeof reapchildren );
+ reapchildren.sa_flags = SA_NOCLDWAIT;
+ int result = ::sigaction( SIGCHLD, &reapchildren, 0);
+ if (result != 0)
+ return systemError(result, ERROR_LOCATION);
+#else // other systems
+ if (::signal(SIGCHLD, SIG_IGN) == SIG_ERR)
+ return systemError(errno, ERROR_LOCATION);
+#endif // NO_CLDWAIT
+
+ return Success();
+}
+
+namespace {
+
+void handleSIGCHLD(int sig)
+{
+ int stat;
+ while (::waitpid (-1, &stat, WNOHANG) > 0)
+ {
+ }
+}
+
+}
+
+Error reapChildren()
+{
+ // setup signal handler
+ struct sigaction sa;
+ ::memset(&sa, 0, sizeof sa);
+ sa.sa_handler = handleSIGCHLD;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ // install it
+ int result = ::sigaction(SIGCHLD, &sa, NULL);
+ if (result != 0)
+ return systemError(errno, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+
+
+// SignalBlocker -- block signals in a scope
+//
+// NOTE: boost has a portable signal_blocker class however we don't use it b/c:
+// 1) it is in the detail namespace (which isn't a public api)
+// 2) it doesn't check or fail on errors
+//
+// NOTE: blocking signals in threads and then handling them on either the
+// main thread or a dedicated thread will only work properly on Linux kernel
+// v2.6 or higher (because it supports the Native POSIX Thread Library and
+// thus delivers signals to multi-threaded programs in a POSIX compliant way)
+
+struct SignalBlocker::Impl
+{
+ Impl() : blocked(false) {}
+ bool blocked;
+ sigset_t oldMask;
+
+ Error block(sigset_t* pBlockMask)
+ {
+ // install mask
+ int result = ::pthread_sigmask(SIG_BLOCK, pBlockMask, &oldMask);
+ if (result != 0)
+ return systemError(result, ERROR_LOCATION);
+
+ // set restore bit and return success
+ blocked = true;
+ return Success();
+ }
+};
+
+SignalBlocker::SignalBlocker()
+ : pImpl_(new Impl())
+{
+}
+
+
+Error SignalBlocker::block(SignalType signal)
+{
+ // get signal
+ int sig = signalForType(signal);
+ if (sig < 0)
+ return systemError(EINVAL, ERROR_LOCATION);
+
+ // create a mask for blocking the signal
+ sigset_t blockMask;
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, sig);
+
+ // block
+ return pImpl_->block(&blockMask);
+}
+
+Error SignalBlocker::blockAll()
+{
+ // create mask to block all signals
+ sigset_t blockMask;
+ sigfillset(&blockMask);
+
+ // block
+ return pImpl_->block(&blockMask);
+}
+
+SignalBlocker::~SignalBlocker()
+{
+ try
+ {
+ if (pImpl_->blocked)
+ {
+ // restore old mask
+ int result = ::pthread_sigmask(SIG_SETMASK, &(pImpl_->oldMask), NULL);
+ if (result != 0)
+ LOG_ERROR(systemError(result, ERROR_LOCATION));
+ }
+ }
+ catch(...)
+ {
+ }
+}
+
+Error clearSignalMask()
+{
+ sigset_t blockNoneMask;
+ sigemptyset(&blockNoneMask);
+ int result = ::pthread_sigmask(SIG_SETMASK, &blockNoneMask, NULL);
+ if (result != 0)
+ return systemError(result, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+Error handleSignal(SignalType signal, void (*handler)(int))
+{
+ int sig = signalForType(signal);
+ if (sig < 0)
+ return systemError(EINVAL, ERROR_LOCATION);
+
+ ::signal(sig, handler);
+
+ return Success();
+}
+
+core::Error ignoreSignal(SignalType signal)
+{
+ int sig = signalForType(signal);
+ if (sig < 0)
+ return systemError(EINVAL, ERROR_LOCATION);
+
+ return ignoreSig(sig);
+}
+
+
+Error useDefaultSignalHandler(SignalType signal)
+{
+ int sig = signalForType(signal);
+ if (sig < 0) return systemError(EINVAL, ERROR_LOCATION);
+
+ struct sigaction sa;
+ ::memset(&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_DFL;
+ int result = ::sigaction(sig, &sa, NULL);
+ if (result != 0)
+ {
+ Error error = systemError(result, ERROR_LOCATION);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+void sendSignalToSelf(SignalType signal)
+{
+ ::kill(::getpid(), signalForType(signal));
+}
+
+std::string username()
+{
+ return system::getenv("USER");
+}
+
+FilePath userHomePath(std::string envOverride)
+{
+ using namespace boost::algorithm;
+
+ // use environment override if specified
+ if (!envOverride.empty())
+ {
+ for (split_iterator<std::string::iterator> it =
+ make_split_iterator(envOverride, first_finder("|", is_iequal()));
+ it != split_iterator<std::string::iterator>();
+ ++it)
+ {
+ std::string envHomePath = system::getenv(boost::copy_range<std::string>(*it));
+ if (!envHomePath.empty())
+ {
+ FilePath userHomePath(envHomePath);
+ if (userHomePath.exists())
+ return userHomePath;
+ }
+ }
+ }
+
+ // otherwise use standard unix HOME
+ return FilePath(system::getenv("HOME"));
+}
+
+FilePath userSettingsPath(const FilePath& userHomeDirectory,
+ const std::string& appName)
+{
+ std::string lower = appName;
+ boost::to_lower(lower);
+
+ FilePath path = userHomeDirectory.childPath("." + lower);
+ Error error = path.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ return path;
+}
+
+bool currentUserIsPrivilleged(unsigned int minimumUserId)
+{
+ return ::geteuid() < minimumUserId;
+}
+
+namespace {
+
+// NOTE: this function is duplicated between here and core::system
+// Did this to prevent the "system" interface from allowing Posix
+// constructs with Win32 no-ops to creep in (since this is used on
+// Posix for forking and has no purpose on Win32)
+
+Error closeFileDescriptorsFrom(int fdStart)
+{
+ // There is no fully reliable and cross-platform way to do this, see:
+ //
+ // Various potential mechanisms include:
+ //
+ // - closefrom
+ // - fcntl(0, F_MAXFD)
+ // - sysconf(_SC_OPEN_MAX)
+ // - getrlimit(RLIMIT_NOFILE, &rl)
+ // - gettdtablesize
+ // - read from /proc/self/fd, /proc/<pid>/fd, or /dev/fd
+ //
+ // Note that the above functions may return either -1 or MAX_INT, in
+ // which case substituting/truncating to an appropriate number (1024?)
+ // is still required
+
+ // get limit
+ struct rlimit rl;
+ if (::getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (rl.rlim_max == RLIM_INFINITY)
+ rl.rlim_max = 1024; // default on linux
+
+ // close file descriptors
+ for (int i=fdStart; i< (int)rl.rlim_max; i++)
+ {
+ if (::close(i) < 0 && errno != EBADF)
+ return systemError(errno, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+
+} // anonymous namespace
+
+Error closeAllFileDescriptors()
+{
+ return closeFileDescriptorsFrom(0);
+}
+
+Error closeNonStdFileDescriptors()
+{
+ return closeFileDescriptorsFrom(STDERR_FILENO+1);
+}
+
+void closeStdFileDescriptors()
+{
+ ::close(STDIN_FILENO);
+ ::close(STDOUT_FILENO);
+ ::close(STDERR_FILENO);
+}
+
+
+void attachStdFileDescriptorsToDevNull()
+{
+ int fd0 = ::open("/dev/null", O_RDWR);
+ if (fd0 == -1)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+
+ int fd1 = ::dup(fd0);
+ if (fd1 == -1)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+
+ int fd2 = ::dup(fd0);
+ if (fd2 == -1)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+}
+
+void setStandardStreamsToDevNull()
+{
+ core::system::closeStdFileDescriptors();
+ core::system::attachStdFileDescriptorsToDevNull();
+ std::ios::sync_with_stdio();
+}
+
+
+bool isHiddenFile(const FilePath& filePath)
+{
+ std::string filename = filePath.filename() ;
+ return (!filename.empty() && (filename[0] == '.')) ;
+}
+
+bool isHiddenFile(const FileInfo& fileInfo)
+{
+ return isHiddenFile(FilePath(fileInfo.absolutePath()));
+}
+
+bool isReadOnly(const FilePath& filePath)
+{
+ if (::access(filePath.absolutePath().c_str(), W_OK) == -1)
+ {
+ if (errno == EACCES)
+ {
+ return true;
+ }
+ else
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", filePath);
+ LOG_ERROR(error);
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool stderrIsTerminal()
+{
+ return ::isatty(STDERR_FILENO) == 1;
+}
+
+bool stdoutIsTerminal()
+{
+ return ::isatty(STDOUT_FILENO) == 1;
+}
+
+std::string generateUuid(bool includeDashes)
+{
+ // generaate the uuid and convert it to a strting
+ uuid_t uuid ;
+ ::uuid_generate_random(uuid);
+ char uuidBuffer[40];
+ ::uuid_unparse_lower(uuid, uuidBuffer);
+ std::string uuidStr(uuidBuffer);
+
+ // remove dashes if requested
+ if (!includeDashes)
+ boost::algorithm::replace_all(uuidStr, "-", "");
+
+ return uuidStr;
+}
+
+PidType currentProcessId()
+{
+ return ::getpid();
+}
+
+Error executablePath(int argc, char * const argv[],
+ FilePath* pExecutablePath)
+{
+ return executablePath(argv[0], pExecutablePath);
+}
+
+Error executablePath(const char * argv0,
+ FilePath* pExecutablePath)
+{
+ std::string executablePath;
+
+#if defined(__APPLE__)
+
+ // get path to current executable
+ uint32_t buffSize = 2048;
+ std::vector<char> buffer(buffSize);
+ if (_NSGetExecutablePath(&(buffer[0]), &buffSize) == -1)
+ {
+ buffer.resize(buffSize);
+ _NSGetExecutablePath(&(buffer[0]), &buffSize);
+ }
+
+ // set it
+ executablePath = std::string(&(buffer[0]));
+
+
+#elif defined(HAVE_PROCSELF)
+
+ executablePath = std::string("/proc/self/exe");
+
+#else
+
+ // Note that this technique will NOT work if the executable was located
+ // via a search of the PATH. To make this fallback fully robust we would
+ // need to also search the PATH for the exe name in argv[0]
+ //
+
+ // use argv[0] and initial path
+ FilePath initialPath = FilePath::initialPath();
+ executablePath = initialPath.complete(argv0).absolutePath();
+
+#endif
+
+ // return realPath of executable path
+ return realPath(executablePath, pExecutablePath);
+}
+
+// installation path
+Error installPath(const std::string& relativeToExecutable,
+ const char * argv0,
+ FilePath* pInstallPath)
+{
+ // get executable path
+ FilePath executablePath;
+ Error error = system::executablePath(argv0, &executablePath);
+ if (error)
+ return error;
+
+ // fully resolve installation path relative to executable
+ FilePath installPath = executablePath.parent().complete(relativeToExecutable);
+ return realPath(installPath.absolutePath(), pInstallPath);
+}
+
+void fixupExecutablePath(FilePath* pExePath)
+{
+ // do nothing on posix
+}
+
+void abort()
+{
+ ::abort();
+}
+
+Error terminateProcess(PidType pid)
+{
+ if (::kill(pid, SIGTERM))
+ return systemError(errno, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+
+Error daemonize()
+{
+ // fork
+ pid_t pid = ::fork();
+ if (pid < 0)
+ return systemError(errno, ERROR_LOCATION); // fork error
+ else if (pid > 0)
+ ::exit(EXIT_SUCCESS); // parent exits
+
+ // obtain a new process group
+ ::setsid();
+
+ // close all file descriptors
+ Error error = closeFileDescriptorsFrom(0);
+ if (error)
+ return error;
+
+ // attach file descriptors 0, 1, and 2 to /dev/null
+ core::system::attachStdFileDescriptorsToDevNull();
+
+ // note: ignoring of terminal signals are handled by an optional
+ // separate call (ignoreTerminalSignals)
+
+ return Success();
+}
+
+void setUMask(UMask mask)
+{
+ switch (mask)
+ {
+ case OthersNoWriteMask:
+ ::umask(S_IWGRP | S_IWOTH);
+ break;
+
+ case OthersNoneMask:
+ ::umask(S_IWGRP | S_IRWXO);
+ break;
+
+ default:
+ BOOST_ASSERT(false);
+ break;
+ }
+}
+
+namespace {
+
+Error osResourceLimit(ResourceLimit limit, int* pLimit)
+{
+ switch(limit)
+ {
+ case MemoryLimit:
+ *pLimit = RLIMIT_AS;
+ break;
+ case FilesLimit:
+ *pLimit = RLIMIT_NOFILE;
+ break;
+ case UserProcessesLimit:
+ *pLimit = RLIMIT_NPROC;
+ break;
+ case StackLimit:
+ *pLimit = RLIMIT_STACK;
+ break;
+ default:
+ *pLimit = -1;
+ break;
+ }
+
+ if (*pLimit == -1)
+ return systemError(EINVAL, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+} // anonumous namespace
+
+bool resourceIsUnlimited(RLimitType resourceValue)
+{
+ return resourceValue == RLIM_INFINITY;
+}
+
+Error getResourceLimit(ResourceLimit resourceLimit,
+ RLimitType* pSoft,
+ RLimitType* pHard)
+{
+ // determine resource
+ int resource;
+ Error error = osResourceLimit(resourceLimit, &resource);
+ if (error)
+ return error;
+
+ // get limits
+ struct rlimit rl;
+ if (::getrlimit(resource, &rl) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set out params and return success
+ *pSoft = rl.rlim_cur;
+ *pHard = rl.rlim_max;
+ return Success();
+}
+
+Error setResourceLimit(ResourceLimit resourceLimit, RLimitType limit)
+{
+ return setResourceLimit(resourceLimit, limit, limit);
+}
+
+Error setResourceLimit(ResourceLimit resourceLimit,
+ RLimitType soft,
+ RLimitType hard)
+{
+ // determine resource
+ int resource;
+ Error error = osResourceLimit(resourceLimit, &resource);
+ if (error)
+ return error;
+
+ // set limit
+ struct rlimit rl;
+ rl.rlim_cur = soft;
+ rl.rlim_max = hard;
+ if (::setrlimit(resource, &rl) == -1)
+ return systemError(errno, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+
+namespace {
+
+Error setProcessLimits(RLimitType memoryLimitBytes,
+ RLimitType stackLimitBytes,
+ RLimitType userProcessesLimit)
+{
+ // set memory limit
+ Error memoryError;
+ if (memoryLimitBytes != 0)
+ memoryError = setResourceLimit(MemoryLimit, memoryLimitBytes);
+
+ Error stackError;
+ if (stackLimitBytes != 0)
+ stackError = setResourceLimit(StackLimit, stackLimitBytes);
+
+ // user processes limit
+ Error processesError;
+ if (userProcessesLimit != 0)
+ processesError = setResourceLimit(UserProcessesLimit, userProcessesLimit);
+
+ // if both had errors then log one and return the other
+ if (memoryError)
+ {
+ if (stackError)
+ LOG_ERROR(stackError);
+
+ if (processesError)
+ LOG_ERROR(processesError);
+
+ return memoryError;
+ }
+ else if (stackError)
+ {
+ if (processesError)
+ LOG_ERROR(processesError);
+
+ return stackError;
+ }
+ else if (processesError)
+ {
+ return processesError;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+void copyEnvironmentVar(const std::string& name,
+ core::system::Options* pVars,
+ bool evenIfEmpty = false)
+{
+ std::string value = core::system::getenv(name);
+ if (!value.empty() || evenIfEmpty)
+ core::system::setenv(pVars, name, value);
+}
+
+}
+
+
+Error waitForProcessExit(PidType processId)
+{
+ while (true)
+ {
+ // call waitpid
+ int stat;
+ int result = ::waitpid (processId, &stat, 0);
+ if (result == processId)
+ {
+ // process "status changed" (i.e. exited, core dumped, terminated
+ // by a signal, or stopped). in all cases we stop waiting
+ return Success();
+ }
+ else if (result == 0)
+ {
+ // no status change, keep waiting...
+ continue;
+ }
+ else if (result < 0)
+ {
+ if (errno == EINTR)
+ {
+ // system call interrupted, keep waiting...
+ continue;
+ }
+ else if (errno == ECHILD)
+ {
+ // the child was already reaped (probably by a global handler)
+ return Success();
+ }
+ else
+ {
+ // some other unexpected error
+ return systemError(errno, ERROR_LOCATION);
+ }
+ }
+ else // a totally unexpected return from waitpid
+ {
+ Error error = systemError(
+ boost::system::errc::state_not_recoverable,
+ ERROR_LOCATION);
+ error.addProperty("result",
+ safe_convert::numberToString(result));
+ return error;
+ }
+ }
+
+ // keep compiler happy (we can't reach this code)
+ return Success();
+}
+
+Error launchChildProcess(std::string path,
+ std::string runAsUser,
+ ProcessConfig config,
+ PidType* pProcessId)
+{
+ pid_t pid = ::fork();
+
+ // error
+ if (pid < 0)
+ {
+ Error error = systemError(errno, ERROR_LOCATION) ;
+ error.addProperty("commmand", path) ;
+ return error ;
+ }
+
+ // child
+ else if (pid == 0)
+ {
+ // obtain a new process group (using our own process id) so our
+ // lifetime isn't tied to our parent's lifetime
+ if (::setpgid(0,0) == -1)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // change user here if requested
+ if (!runAsUser.empty())
+ {
+ // restore root
+ Error error = restorePriv();
+ if (error)
+ {
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // set limits
+ error = setProcessLimits(config.memoryLimitBytes,
+ config.stackLimitBytes,
+ config.userProcessesLimit);
+ if (error)
+ LOG_ERROR(error);
+
+ // switch user
+ error = permanentlyDropPriv(runAsUser);
+ if (error)
+ {
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+ }
+
+ // clear the signal mask so the child process can handle whatever
+ // signals it wishes to
+ Error error = core::system::clearSignalMask();
+ if (error)
+ {
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // get current user (before closing file handles since
+ // we might be using a PAM module that has open FDs...)
+ user::User user;
+ error = user::currentUser(&user);
+ if (error)
+ {
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // close all open file descriptors other than std streams
+ error = closeNonStdFileDescriptors();
+ if (error)
+ {
+ LOG_ERROR(error);
+ ::exit(EXIT_FAILURE);
+ }
+
+ // handle std streams
+ switch(config.stdStreamBehavior)
+ {
+ case StdStreamClose:
+ core::system::closeStdFileDescriptors();
+ break;
+
+ case StdStreamDevNull:
+ core::system::closeStdFileDescriptors();
+ core::system::attachStdFileDescriptorsToDevNull();
+ break;
+
+ case StdStreamInherit:
+ default:
+ // do nothing to inherit the streams
+ break;
+ }
+
+ // setup environment
+ core::system::Options env;
+ copyEnvironmentVar("PATH", &env);
+ copyEnvironmentVar("MANPATH", &env);
+ copyEnvironmentVar("LANG", &env);
+ core::system::setenv(&env, "USER", user.username);
+ core::system::setenv(&env, "LOGNAME", user.username);
+ core::system::setenv(&env, "HOME", user.homeDirectory);
+ copyEnvironmentVar("SHELL", &env);
+
+ // add custom environment vars (overriding as necessary)
+ for (core::system::Options::const_iterator it = config.environment.begin();
+ it != config.environment.end();
+ ++it)
+ {
+ core::system::setenv(&env, it->first, it->second);
+ }
+
+ // format as ProcessArgs expects
+ boost::format fmt("%1%=%2%");
+ std::vector<std::string> envVars;
+ for(core::system::Options::const_iterator it = env.begin();
+ it != env.end();
+ ++it)
+ {
+ envVars.push_back(boost::str(fmt % it->first % it->second));
+ }
+
+ // create environment args (allocate on heap so memory stays around
+ // after we exec (some systems including OSX seem to require this)
+ core::system::ProcessArgs* pEnvironment = new core::system::ProcessArgs(
+ envVars);
+
+ // build process args
+ std::vector<std::string> argVector;
+ argVector.push_back(path);
+ for (core::system::Options::const_iterator it = config.args.begin();
+ it != config.args.end();
+ ++it)
+ {
+ argVector.push_back(it->first);
+ argVector.push_back(it->second);
+ }
+
+ // allocate ProcessArgs on heap so memory stays around after we exec
+ // (some systems including OSX seem to require this)
+ core::system::ProcessArgs* pProcessArgs = new core::system::ProcessArgs(
+ argVector);
+
+ // execute child
+ ::execve(path.c_str(), pProcessArgs->args(), pEnvironment->args());
+
+ // in the normal case control should never return from execv (it starts
+ // anew at main of the process pointed to by path). therefore, if we get
+ // here then there was an error
+ error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("child-path", path);
+ LOG_ERROR(error) ;
+ ::exit(EXIT_FAILURE) ;
+ }
+
+ // parent
+ if (pProcessId)
+ *pProcessId = pid ;
+
+ return Success() ;
+}
+
+
+
+bool isUserNotFoundError(const Error& error)
+{
+ return error.code() == boost::system::errc::permission_denied;
+}
+
+Error userBelongsToGroup(const user::User& user,
+ const std::string& groupName,
+ bool* pBelongs)
+{
+ struct group grp;
+ struct group* ptrGrp = &grp;
+ struct group* tempPtrGrp ;
+
+ // get the estimated size of the groups data
+ int buffSize = ::sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (buffSize == -1)
+ buffSize = 4096; // some systems return -1, be conservative!
+
+ // call until we pass a buffer large enough for the data
+ std::vector<char> buffer;
+ int result = 0;
+ do
+ {
+ // double the size of the suggested/previous buffer
+ buffSize *= 2;
+ buffer.reserve(buffSize);
+
+ // attempt the read
+ result = ::getgrnam_r(groupName.c_str(),
+ ptrGrp,
+ &(buffer[0]),
+ buffSize,
+ &tempPtrGrp);
+
+ } while (result == ERANGE);
+
+ // check for no group data
+ if (tempPtrGrp == NULL)
+ {
+ if (result == 0) // will happen if group is simply not found
+ result = EACCES;
+ Error error = systemError(result, ERROR_LOCATION);
+ error.addProperty("group-name", groupName);
+ return error;
+ }
+
+ *pBelongs = false; // default to not found
+
+ // see if the group id matches the user's group id
+ if (user.groupId == grp.gr_gid)
+ {
+ *pBelongs = true;
+ }
+ // else scan the list of member names for this user
+ else
+ {
+ char** pUsers = grp.gr_mem;
+ while (*pUsers)
+ {
+ const char* pUser = *(pUsers++);
+ if (user.username.compare(pUser) == 0)
+ {
+ *pBelongs = true;
+ break;
+ }
+ }
+ }
+
+ return Success();
+}
+
+
+
+/////////////////////////////////////////////////////////////////////
+//
+// setuid privilege manipulation
+//
+
+bool realUserIsRoot()
+{
+ return ::getuid() == 0;
+}
+
+bool effectiveUserIsRoot()
+{
+ return ::geteuid() == 0;
+}
+
+// privilege manipulation for systems that support setresuid/getresuid
+#if defined(HAVE_SETRESUID)
+
+Error temporarilyDropPriv(const std::string& newUsername)
+{
+ // clear error state
+ errno = 0;
+
+ // get user info
+ user::User user;
+ Error error = userFromUsername(newUsername, &user);
+ if (error)
+ return error;
+
+ // init supplemental group list
+ // NOTE: if porting to CYGWIN may need to call getgroups/setgroups
+ // after initgroups -- more research required to confirm
+ if (::initgroups(user.username.c_str(), user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set group and verify
+ if (::setresgid(-1, user.groupId, ::getegid()) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (::getegid() != user.groupId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // set user and verify
+ if (::setresuid(-1, user.userId, ::geteuid()) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (::geteuid() != user.userId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // success
+ return Success();
+}
+
+Error permanentlyDropPriv(const std::string& newUsername)
+{
+ // clear error state
+ errno = 0;
+
+ // get user info
+ user::User user;
+ Error error = userFromUsername(newUsername, &user);
+ if (error)
+ return error;
+
+ // supplemental group list
+ if (::initgroups(user.username.c_str(), user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set group
+ if (::setresgid(user.groupId, user.groupId, user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ gid_t rgid, egid, sgid;
+ if (::getresgid(&rgid, &egid, &sgid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (rgid != user.groupId || egid != user.groupId || sgid != user.groupId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // set user
+ if (::setresuid(user.userId, user.userId, user.userId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ uid_t ruid, euid, suid;
+ if (::getresuid(&ruid, &euid, &suid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (ruid != user.userId || euid != user.userId || suid != user.userId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // success
+ return Success();
+}
+
+Error restorePriv()
+{
+ // reset error state
+ errno = 0;
+
+ // set user
+ uid_t ruid, euid, suid;
+ if (::getresuid(&ruid, &euid, &suid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (::setresuid(-1, suid, -1) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::geteuid() != suid)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // get saved user info to use in group calls
+ struct passwd* pPrivPasswd = ::getpwuid(suid);
+ if (pPrivPasswd == NULL)
+ return systemError(errno, ERROR_LOCATION);
+
+ // supplemental groups
+ if (::initgroups(pPrivPasswd->pw_name, pPrivPasswd->pw_gid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set group
+ gid_t rgid, egid, sgid;
+ if (::getresgid(&rgid, &egid, &sgid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ if (::setresgid(-1, sgid, -1) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::getegid() != sgid)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // success
+ return Success();
+}
+
+// privilege manipulation for systems that don't support setresuid/getresuid
+#else
+
+namespace {
+ uid_t s_privUid ;
+}
+
+Error temporarilyDropPriv(const std::string& newUsername)
+{
+ // clear error state
+ errno = 0;
+
+ // get user info
+ user::User user;
+ Error error = userFromUsername(newUsername, &user);
+ if (error)
+ return error;
+
+ // init supplemental group list
+ if (::initgroups(user.username.c_str(), user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set group
+
+ // save old EGUID
+ gid_t oldEGUID = ::getegid();
+
+ // copy EGUID to SGID
+ if (::setregid(::getgid(), oldEGUID) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set new EGID
+ if (::setegid(user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // verify
+ if (::getegid() != user.groupId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+
+ // set user
+
+ // save old EUID
+ uid_t oldEUID = ::geteuid();
+
+ // copy EUID to SUID
+ if (::setreuid(::getuid(), oldEUID) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set new EUID
+ if (::seteuid(user.userId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // verify
+ if (::geteuid() != user.userId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // save privilleged user id
+ s_privUid = oldEUID;
+
+ // success
+ return Success();
+}
+
+
+Error permanentlyDropPriv(const std::string& newUsername)
+{
+ // clear error state
+ errno = 0;
+
+ // get user info
+ user::User user;
+ Error error = userFromUsername(newUsername, &user);
+ if (error)
+ return error;
+
+ // supplemental group list
+ if (::initgroups(user.username.c_str(), user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set group
+ if (::setregid(user.groupId, user.groupId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::getgid() != user.groupId || ::getegid() != user.groupId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // set user
+ if (::setreuid(user.userId, user.userId) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::getuid() != user.userId || ::geteuid() != user.userId)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // success
+ return Success();
+}
+
+
+Error restorePriv()
+{
+ // reset error state
+ errno = 0;
+
+ // set effective user to saved privid
+ if (::seteuid(s_privUid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::geteuid() != s_privUid)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // get user info to use in group calls
+ struct passwd* pPrivPasswd = ::getpwuid(s_privUid);
+ if (pPrivPasswd == NULL)
+ return systemError(errno, ERROR_LOCATION);
+
+ // supplemental groups
+ if (::initgroups(pPrivPasswd->pw_name, pPrivPasswd->pw_gid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // set effective group
+ if (::setegid(pPrivPasswd->pw_gid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+ // verify
+ if (::getegid() != pPrivPasswd->pw_gid)
+ return systemError(EACCES, ERROR_LOCATION);
+
+ // success
+ return Success();
+}
+
+#endif
+
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/PosixUser.cpp b/src/cpp/core/system/PosixUser.cpp
new file mode 100644
index 0000000..c0d9768
--- /dev/null
+++ b/src/cpp/core/system/PosixUser.cpp
@@ -0,0 +1,149 @@
+/*
+ * PosixUser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/PosixUser.hpp>
+
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <iostream>
+
+#include <boost/lexical_cast.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+
+#include "config.h"
+
+namespace core {
+namespace system {
+namespace user {
+
+
+UserIdentity currentUserIdentity()
+{
+ UserIdentity userIdentity;
+ userIdentity.userId = ::geteuid();
+ userIdentity.groupId = ::getegid();
+ return userIdentity;
+}
+
+#if defined(HAVE_SO_PEERCRED)
+
+Error socketPeerIdentity(int socket, UserIdentity* pIdentity)
+{
+ struct ucred cred;
+ socklen_t length = sizeof(struct ucred);
+ if (::getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cred, &length) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ pIdentity->userId = cred.uid;
+ pIdentity->groupId = cred.gid;
+ return Success();
+}
+
+#elif defined(HAVE_GETPEEREID)
+
+Error socketPeerIdentity(int socket, UserIdentity* pIdentity)
+{
+ uid_t uid;
+ gid_t gid;
+ if (::getpeereid(socket, &uid, &gid) < 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ pIdentity->userId = uid;
+ pIdentity->groupId = gid;
+ return Success();
+}
+
+#else
+ #error "No way to discover socket peer identity found on this platform"
+#endif
+
+
+namespace {
+
+const int kNotFoundError = EACCES;
+
+// re-use scaffolding for calls to getpwnam_r and getpwuid_r
+template <typename T>
+Error userFrom(const boost::function<int(
+ T, struct passwd*, char*, size_t, struct passwd**)>& getPasswd,
+ T value,
+ User* pUser)
+{
+ struct passwd pwd;
+ struct passwd* ptrPwd = &pwd;
+ struct passwd* tempPtrPwd ;
+ int buffSize = ::sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buffSize == -1)
+ buffSize = 4096; // some systems return -1, be conservative!
+ std::vector<char> buffer(buffSize);
+ int result = getPasswd(value, ptrPwd, &(buffer[0]), buffSize, &tempPtrPwd) ;
+ if (tempPtrPwd == NULL)
+ {
+ if (result == 0) // will happen if user is simply not found
+ result = kNotFoundError;
+ Error error = systemError(result, ERROR_LOCATION);
+ error.addProperty("user-value", safe_convert::numberToString(value));
+ return error;
+ }
+ else
+ {
+ pUser->userId = pwd.pw_uid;
+ pUser->groupId = pwd.pw_gid;
+ pUser->username = pwd.pw_name;
+ pUser->homeDirectory = pwd.pw_dir;
+ return Success();
+ }
+}
+
+} // anonymous namespace
+
+
+Error currentUser(User* pUser)
+{
+ return userFromId(::geteuid(), pUser);
+}
+
+bool exists(const std::string& username)
+{
+ User user;
+ Error error = userFromUsername(username, &user);
+ return !error;
+}
+
+Error userFromUsername(const std::string& username, User* pUser)
+{
+ return userFrom<const char *>(::getpwnam_r, username.c_str(), pUser);
+}
+
+Error userFromId(uid_t uid, User* pUser)
+{
+ return userFrom<uid_t>(::getpwuid_r, uid, pUser);
+}
+
+
+} // namespace user
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/Process.cpp b/src/cpp/core/system/Process.cpp
new file mode 100644
index 0000000..283e5b7
--- /dev/null
+++ b/src/cpp/core/system/Process.cpp
@@ -0,0 +1,336 @@
+/*
+ * Process.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/Process.hpp>
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Scope.hpp>
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/BoostThread.hpp>
+
+#include <core/PerformanceTimer.hpp>
+
+#include "ChildProcess.hpp"
+
+namespace core {
+namespace system {
+
+
+Error runProgram(const std::string& executable,
+ const std::vector<std::string>& args,
+ const std::string& input,
+ const ProcessOptions& options,
+ ProcessResult* pResult)
+{
+ SyncChildProcess child(executable, args, options);
+ return child.run(input, pResult);
+}
+
+Error runCommand(const std::string& command,
+ const ProcessOptions& options,
+ ProcessResult* pResult)
+{
+ return runCommand(command, "", options, pResult);
+}
+
+Error runCommand(const std::string& command,
+ const std::string& input,
+ const ProcessOptions& options,
+ ProcessResult* pResult)
+{
+ SyncChildProcess child(command, options);
+ return child.run(input, pResult);
+}
+
+
+struct ProcessSupervisor::Impl
+{
+ Impl() : isPolling(false) {}
+ bool isPolling;
+ std::vector<boost::shared_ptr<AsyncChildProcess> > children;
+};
+
+ProcessSupervisor::ProcessSupervisor()
+ : pImpl_(new Impl())
+{
+}
+
+ProcessSupervisor::~ProcessSupervisor()
+{
+}
+
+namespace {
+
+Error runChild(boost::shared_ptr<AsyncChildProcess> pChild,
+ std::vector<boost::shared_ptr<AsyncChildProcess> >* pChildren,
+ const ProcessCallbacks& callbacks)
+{
+ // run the child
+ Error error = pChild->run(callbacks);
+ if (error)
+ return error;
+
+ // add to the list of children
+ pChildren->push_back(pChild);
+
+ // success
+ return Success();
+}
+
+} // anonymous namespace
+
+Error ProcessSupervisor::runProgram(const std::string& executable,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options,
+ const ProcessCallbacks& callbacks)
+{
+ // create the child
+ boost::shared_ptr<AsyncChildProcess> pChild(
+ new AsyncChildProcess(executable,
+ args,
+ options));
+
+ // run the child
+ return runChild(pChild, &(pImpl_->children), callbacks);
+}
+
+Error ProcessSupervisor::runCommand(const std::string& command,
+ const ProcessOptions& options,
+ const ProcessCallbacks& callbacks)
+{
+ // create the child
+ boost::shared_ptr<AsyncChildProcess> pChild(
+ new AsyncChildProcess(command, options));
+
+ // run the child
+ return runChild(pChild, &(pImpl_->children), callbacks);
+}
+
+namespace {
+
+// class which implements all of the callbacks
+struct ChildCallbacks
+{
+ ChildCallbacks(const std::string& input,
+ const boost::function<void(const ProcessResult&)>& onCompleted,
+ const boost::function<void(const Error&)>& onErrored)
+ : input(input), onCompleted(onCompleted), onErrored(onErrored)
+ {
+ }
+
+ void onStarted(ProcessOperations& operations)
+ {
+ if (!input.empty())
+ {
+ Error error = operations.writeToStdin(input, true);
+ if (error)
+ {
+ LOG_ERROR(error);
+
+ error = operations.terminate();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+ }
+
+ void onStdout(ProcessOperations&, const std::string& output)
+ {
+ stdOut.append(output);
+ }
+
+ void onStderr(ProcessOperations&, const std::string& output)
+ {
+ stdErr.append(output);
+ }
+
+ void onError(ProcessOperations&, const Error& error)
+ {
+ onErrored(error);
+ }
+
+ void onConsoleOutputSnapshot(ProcessOperations&,
+ const std::vector<char>& output)
+ {
+ consoleOutputSnapshot = output;
+ }
+
+ void onExit(int exitStatus)
+ {
+ ProcessResult result;
+ result.exitStatus = exitStatus;
+ result.stdOut = stdOut;
+ result.stdErr = stdErr;
+ onCompleted(result);
+ }
+
+ std::string input;
+ std::string stdOut;
+ std::string stdErr;
+ std::vector<char> consoleOutputSnapshot;
+ boost::function<void(const ProcessResult&)> onCompleted;
+ boost::function<void(const Error&)> onErrored;
+};
+
+} // anonymous namespace
+
+
+ProcessCallbacks createProcessCallbacks(
+ const std::string& input,
+ const boost::function<void(const ProcessResult&)>& onCompleted,
+ const boost::function<void(const Error&)>& onError)
+{
+ // create a shared_ptr to the ChildCallbacks. it will stay alive
+ // as long as one of its members is referenced in a bind context
+ boost::shared_ptr<ChildCallbacks> pCC(new ChildCallbacks(input,
+ onCompleted,
+ onError));
+
+ // bind in the callbacks
+ using boost::bind;
+ ProcessCallbacks cb;
+ cb.onStarted = bind(&ChildCallbacks::onStarted, pCC, _1);
+ cb.onStdout = bind(&ChildCallbacks::onStdout, pCC, _1, _2);
+ cb.onStderr = bind(&ChildCallbacks::onStderr, pCC, _1, _2);
+ cb.onConsoleOutputSnapshot =
+ bind(&ChildCallbacks::onConsoleOutputSnapshot, pCC, _1, _2);
+ cb.onExit = bind(&ChildCallbacks::onExit, pCC, _1);
+ cb.onError = bind(&ChildCallbacks::onError, pCC, _1, _2);
+
+ // return it
+ return cb;
+}
+
+
+Error ProcessSupervisor::runProgram(
+ const std::string& executable,
+ const std::vector<std::string>& args,
+ const std::string& input,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted)
+{
+ // create proces callbacks
+ ProcessCallbacks cb = createProcessCallbacks(
+ input, onCompleted, boost::function<void(const Error&)>());
+
+ // run the child
+ return runProgram(executable, args, options, cb);
+}
+
+Error ProcessSupervisor::runCommand(
+ const std::string& command,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted)
+{
+ return runCommand(command, "", options, onCompleted);
+}
+
+Error ProcessSupervisor::runCommand(
+ const std::string& command,
+ const std::string& input,
+ const ProcessOptions& options,
+ const boost::function<void(const ProcessResult&)>& onCompleted)
+{
+ // create proces callbacks
+ ProcessCallbacks cb = createProcessCallbacks(input, onCompleted);
+
+ // run the child
+ return runCommand(command, options, cb);
+}
+
+
+
+bool ProcessSupervisor::hasRunningChildren()
+{
+ return !pImpl_->children.empty();
+}
+
+bool ProcessSupervisor::poll()
+{
+ // bail immediately if we have no children
+ if (!hasRunningChildren())
+ return false;
+
+ // never allow re-entrancy (could occur if one of the output
+ // handlers called from poll executes a waitForMethod which
+ // results in additional polling during idle/wait time)
+ if (pImpl_->isPolling)
+ return true;
+
+ // set isPolling then clear it on exit
+ pImpl_->isPolling = true;
+ scope::SetOnExit<bool> setOnExit(&pImpl_->isPolling, false);
+
+ // call poll on all of our children
+ std::for_each(pImpl_->children.begin(),
+ pImpl_->children.end(),
+ boost::bind(&AsyncChildProcess::poll, _1));
+
+ // remove any children who have exited from our list
+ pImpl_->children.erase(std::remove_if(
+ pImpl_->children.begin(),
+ pImpl_->children.end(),
+ boost::bind(&AsyncChildProcess::exited, _1)),
+ pImpl_->children.end());
+
+ // return status
+ return hasRunningChildren();
+}
+
+void ProcessSupervisor::terminateAll()
+{
+ // call terminate on all of our children
+ BOOST_FOREACH(boost::shared_ptr<AsyncChildProcess> pChild,
+ pImpl_->children)
+ {
+ Error error = pChild->terminate();
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+bool ProcessSupervisor::wait(
+ const boost::posix_time::time_duration& pollingInterval,
+ const boost::posix_time::time_duration& maxWait)
+{
+ boost::posix_time::ptime timeoutTime(boost::posix_time::not_a_date_time);
+ if (!maxWait.is_not_a_date_time())
+ timeoutTime = boost::get_system_time() + maxWait;
+
+ while (poll())
+ {
+ // wait the specified polling interval
+ boost::this_thread::sleep(pollingInterval);
+
+ // check for timeout if appropriate
+ if (!timeoutTime.is_not_a_date_time())
+ {
+ if (boost::get_system_time() > timeoutTime)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace system
+} // namespace core
+
+
diff --git a/src/cpp/core/system/RegistryKey.cpp b/src/cpp/core/system/RegistryKey.cpp
new file mode 100644
index 0000000..b4da6a8
--- /dev/null
+++ b/src/cpp/core/system/RegistryKey.cpp
@@ -0,0 +1,202 @@
+/*
+ * RegistryKey.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <core/system/RegistryKey.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+namespace core {
+namespace system {
+
+RegistryKey::RegistryKey()
+ : hKey_(NULL)
+{
+}
+
+RegistryKey::~RegistryKey()
+{
+ try
+ {
+ if (hKey_)
+ ::RegCloseKey(hKey_);
+ hKey_ = NULL;
+ }
+ catch (...)
+ {
+ }
+}
+
+Error RegistryKey::open(HKEY hKey, std::string subKey, REGSAM samDesired)
+{
+ if (hKey_)
+ ::RegCloseKey(hKey_);
+ hKey_ = NULL;
+
+ LONG error = ::RegOpenKeyEx(hKey, subKey.c_str(), 0, samDesired, &hKey_);
+ if (error != ERROR_SUCCESS)
+ {
+ hKey_ = NULL;
+ return systemError(error, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+bool RegistryKey::isOpen()
+{
+ return hKey_;
+}
+
+HKEY RegistryKey::handle()
+{
+ return hKey_;
+}
+
+namespace {
+Error expandEnvironmentVariables(std::string value, std::string* pResult)
+{
+ if (value.empty())
+ {
+ *pResult = value;
+ return Success();
+ }
+
+ size_t sizeRequired = ::ExpandEnvironmentStrings(value.c_str(), NULL, 0);
+ if (!sizeRequired)
+ return systemError(GetLastError(), ERROR_LOCATION);
+
+ std::vector<char> buffer;
+ buffer.reserve(sizeRequired);
+ int result = ::ExpandEnvironmentStrings(value.c_str(),
+ &buffer[0],
+ buffer.capacity());
+
+ if (!result)
+ return systemError(GetLastError(), ERROR_LOCATION);
+ else if (result > buffer.capacity())
+ return systemError(ERROR_MORE_DATA, ERROR_LOCATION); // not expected
+
+ *pResult = std::string(&buffer[0]);
+ return Success();
+}
+} // anonymous namespace
+
+std::string RegistryKey::getStringValue(std::string name,
+ std::string defaultValue)
+{
+ std::string value;
+ Error error = getStringValue(name, &value);
+ if (!error)
+ return value;
+ return defaultValue;
+}
+
+Error RegistryKey::getStringValue(std::string name, std::string *pValue)
+{
+ if (!hKey_)
+ return systemError(ERROR_INVALID_HANDLE, ERROR_LOCATION);
+
+ std::vector<char> buffer;
+ buffer.reserve(260);
+ while (true)
+ {
+ DWORD type;
+ DWORD size = buffer.capacity();
+
+ LONG result = ::RegQueryValueEx(hKey_,
+ name.c_str(),
+ NULL,
+ &type,
+ (LPBYTE)(&buffer[0]),
+ &size);
+ switch (result)
+ {
+ case ERROR_SUCCESS:
+ {
+ if (type != REG_SZ && type != REG_EXPAND_SZ)
+ return systemError(ERROR_INVALID_DATATYPE, ERROR_LOCATION);
+
+ *pValue = std::string(&buffer[0], buffer.capacity());
+
+ // REG_SZ and friends may or may not be null-terminated.
+ // So trim the string at the first null, if any.
+ size_t idxNull = pValue->find('\0');
+ if (idxNull != std::string::npos)
+ pValue->resize(idxNull);
+
+ if (type == REG_EXPAND_SZ)
+ {
+ Error error = expandEnvironmentVariables(*pValue, pValue);
+ if (error)
+ return error;
+ }
+
+ return Success();
+ }
+
+ case ERROR_MORE_DATA:
+ buffer.reserve(size);
+ continue;
+
+ default:
+ return systemError(result, ERROR_LOCATION);
+ }
+ }
+}
+
+std::vector<std::string> RegistryKey::keyNames()
+{
+ if (!hKey_)
+ return std::vector<std::string>();
+
+ LONG result;
+
+ DWORD subKeys, maxLen;
+ result = ::RegQueryInfoKey(hKey_, NULL, NULL, NULL, &subKeys, &maxLen,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+
+
+ std::vector<char> nameBuffer(maxLen+2);
+ std::vector<std::string> results;
+ results.reserve(subKeys);
+
+ for (DWORD i = 0; ; i++)
+ {
+ DWORD size = nameBuffer.capacity();
+ LONG result = ::RegEnumKeyEx(hKey_,
+ i,
+ &nameBuffer[0],
+ &size,
+ NULL, NULL, NULL, NULL);
+ switch (result)
+ {
+ case ERROR_SUCCESS:
+ results.push_back(std::string(&nameBuffer[0], size));
+ break;
+ case ERROR_NO_MORE_ITEMS:
+ return results;
+ default:
+ Error error = systemError(result, ERROR_LOCATION);
+ LOG_ERROR(error);
+ break;
+ }
+
+ }
+
+ return std::vector<std::string>();
+}
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/system/ShellUtils.cpp b/src/cpp/core/system/ShellUtils.cpp
new file mode 100644
index 0000000..b9254b4
--- /dev/null
+++ b/src/cpp/core/system/ShellUtils.cpp
@@ -0,0 +1,127 @@
+/*
+ * ShellUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/ShellUtils.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+
+namespace core {
+namespace shell_utils {
+
+std::string pipe(const std::string& command1, const std::string& command2)
+{
+ return command1 + " | " + command2;
+}
+
+std::string ShellCommand::maybeEscape(const std::string &value)
+{
+ if (escapeMode_ == EscapeAll)
+ return escape(value);
+ else
+ return value;
+}
+
+ShellCommand& ShellCommand::operator<<(EscapeMode escapeMode)
+{
+ escapeMode_ = escapeMode;
+ return *this;
+}
+
+ShellCommand& ShellCommand::operator<<(const std::string& arg)
+{
+ output_.push_back(' ');
+ output_.append(maybeEscape(arg));
+ return *this;
+}
+
+ShellCommand& ShellCommand::operator<<(int arg)
+{
+ output_.push_back(' ');
+ output_.append(safe_convert::numberToString(arg));
+ return *this;
+}
+
+ShellCommand& ShellCommand::operator<<(const FilePath& path)
+{
+ output_.push_back(' ');
+ // TODO: check encoding
+ output_.append(escape(path.absolutePath()));
+ return *this;
+}
+
+ShellCommand& ShellCommand::operator<<(const std::vector<std::string> args)
+{
+ for (std::vector<std::string>::const_iterator it = args.begin();
+ it != args.end();
+ it++)
+ {
+ *this << *it;
+ }
+ return *this;
+}
+
+ShellCommand& ShellCommand::operator<<(const std::vector<FilePath> args)
+{
+ for (std::vector<FilePath>::const_iterator it = args.begin();
+ it != args.end();
+ it++)
+ {
+ *this << *it;
+ }
+ return *this;
+}
+
+ShellArgs& ShellArgs::operator<<(const std::string& arg)
+{
+ args_.push_back(arg);
+ return *this;
+}
+
+ShellArgs& ShellArgs::operator<<(int arg)
+{
+ args_.push_back(safe_convert::numberToString(arg));
+ return *this;
+}
+
+ShellArgs& ShellArgs::operator<<(const FilePath& path)
+{
+ return *this << string_utils::utf8ToSystem(path.absolutePath());
+}
+
+ShellArgs& ShellArgs::operator<<(const std::vector<std::string> args)
+{
+ for (std::vector<std::string>::const_iterator it = args.begin();
+ it != args.end();
+ it++)
+ {
+ *this << *it;
+ }
+ return *this;
+}
+
+ShellArgs& ShellArgs::operator<<(const std::vector<FilePath> args)
+{
+ for (std::vector<FilePath>::const_iterator it = args.begin();
+ it != args.end();
+ it++)
+ {
+ *this << *it;
+ }
+ return *this;
+}
+
+}
+}
diff --git a/src/cpp/core/system/System.cpp b/src/cpp/core/system/System.cpp
new file mode 100644
index 0000000..865df02
--- /dev/null
+++ b/src/cpp/core/system/System.cpp
@@ -0,0 +1,72 @@
+/*
+ * System.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/System.hpp>
+
+#include <core/Hash.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/system/Environment.hpp>
+
+namespace core {
+namespace system {
+
+#ifdef _WIN32
+#define kPathSeparator ";"
+#else
+#define kPathSeparator ":"
+#endif
+
+
+void addToSystemPath(const FilePath& path, bool prepend)
+{
+ std::string systemPath = system::getenv("PATH");
+ if (prepend)
+ systemPath = path.absolutePath() + kPathSeparator + systemPath;
+ else
+ systemPath = systemPath + kPathSeparator + path.absolutePath();
+ system::setenv("PATH", systemPath);
+}
+
+
+int exitFailure(const Error& error, const ErrorLocation& loggedFromLocation)
+{
+ core::log::logError(error, loggedFromLocation);
+ return EXIT_FAILURE;
+}
+
+int exitFailure(const std::string& errMsg,
+ const ErrorLocation& loggedFromLocation)
+{
+ core::log::logErrorMessage(errMsg, loggedFromLocation);
+ return EXIT_FAILURE;
+}
+
+int exitSuccess()
+{
+ return EXIT_SUCCESS;
+}
+
+std::string generateShortenedUuid()
+{
+ std::string uuid = core::system::generateUuid(false);
+ return core::hash::crc32HexHash(uuid);
+}
+
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/Win32ChildProcess.cpp b/src/cpp/core/system/Win32ChildProcess.cpp
new file mode 100644
index 0000000..136bb59
--- /dev/null
+++ b/src/cpp/core/system/Win32ChildProcess.cpp
@@ -0,0 +1,647 @@
+/*
+ * Win32ChildProcess.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ChildProcess.hpp"
+
+#include <iostream>
+
+#include <windows.h>
+#include <Shlwapi.h>
+
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+#include "CriticalSection.hpp"
+
+namespace core {
+namespace system {
+
+namespace {
+
+
+std::string findOnPath(const std::string& exe,
+ const std::string& appendExt = "")
+{
+ // make sure it has the specified extension
+ std::string resolvedExe = exe;
+ if (!appendExt.empty() &&
+ !boost::algorithm::ends_with(resolvedExe, appendExt))
+ {
+ resolvedExe += appendExt;
+ }
+
+ // do the search
+ std::vector<TCHAR> exeBuffer(MAX_PATH*4);
+ exeBuffer.insert(exeBuffer.begin(), resolvedExe.begin(), resolvedExe.end());
+ exeBuffer.push_back('\0');
+ if (::PathFindOnPath(&(exeBuffer[0]), NULL))
+ {
+ return std::string(&(exeBuffer[0]));
+ }
+ else
+ {
+ return std::string();
+ }
+}
+
+
+// resolve the passed command and arguments to the form required for a
+// call to CreateProcess (do path lookup if necessary and invoke the
+// command within a command processor if it is a batch file)
+void resolveCommand(std::string* pExecutable, std::vector<std::string>* pArgs)
+{
+ // if this is a root path or it exists then leave it as is
+ if (!FilePath::isRootPath(*pExecutable) && !FilePath::exists(*pExecutable))
+ {
+ // try to find it on the path as a .exe
+ std::string exePath = findOnPath(*pExecutable, ".exe");
+ if (!exePath.empty())
+ {
+ *pExecutable = exePath;
+ }
+ else
+ {
+ // try to find it on the path as a cmd
+ std::string cmdPath = findOnPath(*pExecutable, ".cmd");
+ if (!cmdPath.empty())
+ {
+ // set the pCmd to cmd.exe
+ std::string cmdExePath = findOnPath("cmd.exe");
+ if (!cmdExePath.empty())
+ {
+ // set to cmd.exe
+ *pExecutable = cmdExePath;
+
+ // manipulate args to have cmd.exe invoke the batch file
+ pArgs->insert(pArgs->begin(), cmdPath);
+ pArgs->insert(pArgs->begin(), "/C");
+
+ }
+ }
+ }
+ }
+}
+
+
+Error readPipeAvailableBytes(HANDLE hPipe, std::string* pOutput)
+{
+ // check for available bytes
+ DWORD dwAvail = 0;
+ if (!::PeekNamedPipe(hPipe, NULL, 0, NULL, &dwAvail, NULL))
+ {
+ if (::GetLastError() == ERROR_BROKEN_PIPE)
+ return Success();
+ else
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ }
+
+ // no data available
+ if (dwAvail == 0)
+ return Success();
+
+ // read data which is available
+ DWORD nBytesRead;
+ std::vector<CHAR> buffer(dwAvail, 0);
+ if (!::ReadFile(hPipe, &(buffer[0]), dwAvail, &nBytesRead, NULL))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // append to output
+ pOutput->append(&(buffer[0]), nBytesRead);
+
+ // success
+ return Success();
+}
+
+Error readPipeUntilDone(HANDLE hPipe, std::string* pOutput)
+{
+ CHAR buff[256];
+ DWORD nBytesRead;
+
+ while(TRUE)
+ {
+ // read from pipe
+ BOOL result = ::ReadFile(hPipe, buff, sizeof(buff), &nBytesRead, NULL);
+
+ // end of file
+ if (nBytesRead == 0)
+ break;
+
+ // pipe broken
+ else if (!result && (::GetLastError() == ERROR_BROKEN_PIPE))
+ break;
+
+ // unexpected error
+ else if (!result)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // got input, append it
+ else
+ pOutput->append(buff, nBytesRead);
+ }
+
+ return Success();
+}
+
+} // anonymous namespace
+
+struct ChildProcess::Impl
+{
+ Impl()
+ : hStdInWrite(NULL),
+ hStdOutRead(NULL),
+ hStdErrRead(NULL),
+ hProcess(NULL),
+ closeStdIn_(&hStdInWrite, ERROR_LOCATION),
+ closeStdOut_(&hStdOutRead, ERROR_LOCATION),
+ closeStdErr_(&hStdErrRead, ERROR_LOCATION),
+ closeProcess_(&hProcess, ERROR_LOCATION)
+ {
+ }
+
+ HANDLE hStdInWrite;
+ HANDLE hStdOutRead;
+ HANDLE hStdErrRead;
+ HANDLE hProcess;
+
+private:
+ CloseHandleOnExitScope closeStdIn_;
+ CloseHandleOnExitScope closeStdOut_;
+ CloseHandleOnExitScope closeStdErr_;
+ CloseHandleOnExitScope closeProcess_;
+};
+
+
+ChildProcess::ChildProcess()
+ : pImpl_(new Impl())
+{
+}
+
+
+void ChildProcess::init(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options)
+{
+ exe_ = exe;
+ args_ = args;
+ options_ = options;
+ resolveCommand(&exe_, &args_);
+
+ if (!options.stdOutFile.empty() || !options.stdErrFile.empty())
+ {
+ LOG_ERROR_MESSAGE(
+ "stdOutFile/stdErrFile options cannot be used with runProgram");
+ }
+}
+
+void ChildProcess::init(const std::string& command,
+ const ProcessOptions& options)
+{
+ exe_ = findOnPath("cmd.exe");
+ args_.push_back("/S");
+ args_.push_back("/C");
+ args_.push_back("\"" + command + "\"");
+ options_ = options;
+}
+
+ChildProcess::~ChildProcess()
+{
+}
+
+Error ChildProcess::writeToStdin(const std::string& input, bool eof)
+{
+ // write synchronously to the pipe
+ if (!input.empty())
+ {
+ DWORD dwWritten;
+ BOOL bSuccess = ::WriteFile(pImpl_->hStdInWrite,
+ input.data(),
+ input.length(),
+ &dwWritten,
+ NULL);
+ if (!bSuccess)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ }
+
+ // close pipe if requested
+ if (eof)
+ return closeHandle(&pImpl_->hStdInWrite, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+Error ChildProcess::ptySetSize(int cols, int rows)
+{
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+}
+
+Error ChildProcess::ptyInterrupt()
+{
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+}
+
+
+Error ChildProcess::terminate()
+{
+ // terminate with exit code 15 (15 is SIGTERM on posix)
+ if (!::TerminateProcess(pImpl_->hProcess, 15))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ else
+ return Success();
+}
+
+namespace {
+
+Error openFile(const FilePath& file, bool inheritable, HANDLE* phFile)
+{
+ HANDLE hFile = ::CreateFileW(file.absolutePathW().c_str(),
+ GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ if (inheritable)
+ {
+ if (!::SetHandleInformation(hFile,
+ HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT))
+ {
+ Error err = systemError(::GetLastError(), ERROR_LOCATION);
+ ::CloseHandle(hFile);
+ return err;
+ }
+ }
+
+ *phFile = hFile;
+
+ return Success();
+}
+
+} // namespace
+
+Error ChildProcess::run()
+{
+ Error error;
+
+ // NOTE: if the run method is called from multiple threads in single app
+ // concurrently then a race condition can cause handles to get incorrectly
+ // directed. the workaround suggested by microsoft is to wrap the process
+ // creation code in a critical section. see this article for details:
+ // http://support.microsoft.com/kb/315939
+ static CriticalSection s_runCriticalSection;
+ CriticalSection::Scope csScope(s_runCriticalSection);
+
+ // Standard input pipe
+ HANDLE hStdInRead;
+ if (!::CreatePipe(&hStdInRead, &pImpl_->hStdInWrite, NULL, 0))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ CloseHandleOnExitScope closeStdIn(&hStdInRead, ERROR_LOCATION);
+ if (!::SetHandleInformation(hStdInRead,
+ HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // Standard output pipe
+ HANDLE hStdOutWrite;
+ if (!::CreatePipe(&pImpl_->hStdOutRead, &hStdOutWrite, NULL, 0))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ CloseHandleOnExitScope closeStdOut(&hStdOutWrite, ERROR_LOCATION);
+ if (!::SetHandleInformation(hStdOutWrite,
+ HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT) )
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // Standard error pipe
+ HANDLE hStdErrWrite;
+ if (!::CreatePipe(&pImpl_->hStdErrRead, &hStdErrWrite, NULL, 0))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ CloseHandleOnExitScope closeStdErr(&hStdErrWrite, ERROR_LOCATION);
+ if (!::SetHandleInformation(hStdErrWrite,
+ HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT) )
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // populate startup info
+ STARTUPINFO si = { sizeof(STARTUPINFO) };
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ si.hStdOutput = hStdOutWrite;
+ si.hStdError = options_.redirectStdErrToStdOut ? hStdOutWrite
+ : hStdErrWrite;
+ si.hStdInput = hStdInRead;
+
+ HANDLE hStdOutWriteFile = INVALID_HANDLE_VALUE;
+ if (!options_.stdOutFile.empty())
+ {
+ error = openFile(options_.stdOutFile, true, &hStdOutWriteFile);
+ if (error)
+ return error;
+ si.hStdOutput = hStdOutWriteFile;
+ }
+ CloseHandleOnExitScope closeStdOutFile(&hStdOutWriteFile, ERROR_LOCATION);
+
+ HANDLE hStdErrWriteFile = INVALID_HANDLE_VALUE;
+ if (!options_.stdErrFile.empty())
+ {
+ error = openFile(options_.stdErrFile, true, &hStdErrWriteFile);
+ if (error)
+ return error;
+ si.hStdOutput = hStdErrWriteFile;
+ }
+ CloseHandleOnExitScope closeStdErrFile(&hStdErrWriteFile, ERROR_LOCATION);
+
+ // build command line
+ std::vector<TCHAR> cmdLine;
+
+ bool exeQuot = std::string::npos != exe_.find(' ')
+ && std::string::npos == exe_.find('"');
+ if (exeQuot)
+ cmdLine.push_back('"');
+ std::copy(exe_.begin(), exe_.end(), std::back_inserter(cmdLine));
+ if (exeQuot)
+ cmdLine.push_back('"');
+
+ BOOST_FOREACH(std::string& arg, args_)
+ {
+ cmdLine.push_back(' ');
+
+ // This is kind of gross. Ideally we would be more deterministic
+ // than this.
+ bool quot = std::string::npos != arg.find(' ')
+ && std::string::npos == arg.find('"');
+
+ if (quot)
+ cmdLine.push_back('"');
+ std::copy(arg.begin(), arg.end(), std::back_inserter(cmdLine));
+ if (quot)
+ cmdLine.push_back('"');
+ }
+ cmdLine.push_back('\0');
+
+ // specify custom environment if requested
+ DWORD dwFlags = 0;
+ LPVOID lpEnv = NULL;
+ std::vector<wchar_t> envBlock;
+ if (options_.environment)
+ {
+ const Options& env = options_.environment.get();
+ BOOST_FOREACH(const Option& envVar, env)
+ {
+ std::wstring key = string_utils::utf8ToWide(envVar.first);
+ std::wstring value = string_utils::utf8ToWide(envVar.second);
+ std::copy(key.begin(), key.end(), std::back_inserter(envBlock));
+ envBlock.push_back(L'=');
+ std::copy(value.begin(), value.end(), std::back_inserter(envBlock));
+ envBlock.push_back(L'\0');
+ }
+ envBlock.push_back(L'\0');
+
+ dwFlags |= CREATE_UNICODE_ENVIRONMENT;
+ lpEnv = &envBlock[0];
+ }
+
+ if (options_.createNewConsole)
+ {
+ dwFlags |= CREATE_NEW_CONSOLE;
+ si.dwFlags |= STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ }
+ else if (options_.detachProcess)
+ {
+ dwFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
+ si.dwFlags |= STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ }
+
+ if (options_.breakawayFromJob)
+ dwFlags |= CREATE_BREAKAWAY_FROM_JOB;
+
+ std::string workingDir;
+ if (!options_.workingDir.empty())
+ {
+ workingDir = string_utils::utf8ToSystem(
+ options_.workingDir.absolutePathNative());
+ }
+
+ // Start the child process.
+ PROCESS_INFORMATION pi;
+ ::ZeroMemory( &pi, sizeof(PROCESS_INFORMATION));
+ BOOL success = ::CreateProcess(
+ exe_.c_str(), // Process
+ &(cmdLine[0]), // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ TRUE, // Set handle inheritance to TRUE
+ dwFlags, // Creation flags
+ lpEnv, // Environment block
+ // Use parent's starting directory
+ workingDir.empty() ? NULL : workingDir.c_str(),
+ &si, // Pointer to STARTUPINFO structure
+ &pi ); // Pointer to PROCESS_INFORMATION structure
+
+ if (!success)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // close thread handle on exit
+ CloseHandleOnExitScope closeThread(&pi.hThread, ERROR_LOCATION);
+
+ // save handle to process
+ pImpl_->hProcess = pi.hProcess;
+
+ // success
+ return Success();
+}
+
+
+Error SyncChildProcess::readStdOut(std::string* pOutput)
+{
+ return readPipeUntilDone(pImpl_->hStdOutRead, pOutput);
+}
+
+Error SyncChildProcess::readStdErr(std::string* pOutput)
+{
+ return readPipeUntilDone(pImpl_->hStdErrRead, pOutput);
+}
+
+Error SyncChildProcess::waitForExit(int* pExitStatus)
+{
+ // wait
+ DWORD result = ::WaitForSingleObject(pImpl_->hProcess, INFINITE);
+
+ // check for error
+ if (result != WAIT_OBJECT_0)
+ {
+ if (result == WAIT_FAILED)
+ {
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ }
+ else
+ {
+ Error error = systemError(boost::system::errc::result_out_of_range,
+ ERROR_LOCATION);
+ error.addProperty("result", result);
+ return error;
+ }
+ }
+ else
+ {
+ // get exit code
+ DWORD dwStatus;
+ if (!::GetExitCodeProcess(pImpl_->hProcess, &dwStatus))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ *pExitStatus = dwStatus;
+ return Success();
+ }
+}
+
+struct AsyncChildProcess::AsyncImpl
+{
+ AsyncImpl()
+ : calledOnStarted_(false)
+ {
+ }
+
+ bool calledOnStarted_;
+};
+
+AsyncChildProcess::AsyncChildProcess(const std::string& exe,
+ const std::vector<std::string>& args,
+ const ProcessOptions& options)
+ : ChildProcess(), pAsyncImpl_(new AsyncImpl())
+{
+ init(exe, args, options);
+}
+
+AsyncChildProcess::AsyncChildProcess(const std::string& command,
+ const ProcessOptions& options)
+ : ChildProcess(), pAsyncImpl_(new AsyncImpl())
+{
+ init(command, options);
+}
+
+AsyncChildProcess::~AsyncChildProcess()
+{
+}
+
+
+Error AsyncChildProcess::terminate()
+{
+ return ChildProcess::terminate();
+}
+
+
+void AsyncChildProcess::poll()
+{
+ // call onStarted if we haven't yet
+ if (!(pAsyncImpl_->calledOnStarted_))
+ {
+ if (callbacks_.onStarted)
+ callbacks_.onStarted(*this);
+ pAsyncImpl_->calledOnStarted_ = true;
+ }
+
+ // call onContinue
+ if (callbacks_.onContinue)
+ {
+ if (!callbacks_.onContinue(*this))
+ {
+ // terminate the proces
+ Error error = terminate();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ // check stdout
+ std::string stdOut;
+ Error error = readPipeAvailableBytes(pImpl_->hStdOutRead, &stdOut);
+ if (error)
+ reportError(error);
+ if (!stdOut.empty() && callbacks_.onStdout)
+ callbacks_.onStdout(*this, stdOut);
+
+ // check stderr
+ std::string stdErr;
+ error = readPipeAvailableBytes(pImpl_->hStdErrRead, &stdErr);
+ if (error)
+ reportError(error);
+ if (!stdErr.empty() && callbacks_.onStderr)
+ callbacks_.onStderr(*this, stdErr);
+
+ // check for process exit
+ DWORD result = ::WaitForSingleObject(pImpl_->hProcess, 0);
+
+ // check for process exit (or error waiting)
+ if (result != WAIT_TIMEOUT)
+ {
+ // try to get exit status
+ int exitStatus = -1;
+
+ // normal wait for process state
+ if (result == WAIT_OBJECT_0)
+ {
+ // get the exit status
+ DWORD dwStatus;
+ if (!::GetExitCodeProcess(pImpl_->hProcess, &dwStatus))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ exitStatus = dwStatus;
+ }
+
+ // error state, return -1 and try to log a meaningful error
+ else
+ {
+ Error error;
+ if (result == WAIT_FAILED)
+ {
+ error = systemError(::GetLastError(), ERROR_LOCATION);
+ }
+ else
+ {
+ error = systemError(boost::system::errc::result_out_of_range,
+ ERROR_LOCATION);
+ error.addProperty("result", result);
+ }
+ LOG_ERROR(error);
+ }
+
+ // close the process handle
+ Error error = closeHandle(&pImpl_->hProcess, ERROR_LOCATION);
+ if (error)
+ LOG_ERROR(error);
+
+ // call onExit
+ if (callbacks_.onExit)
+ callbacks_.onExit(exitStatus);
+ }
+}
+
+bool AsyncChildProcess::exited()
+{
+ return pImpl_->hProcess == NULL;
+}
+
+} // namespace system
+} // namespace core
+
+
diff --git a/src/cpp/core/system/Win32Environment.cpp b/src/cpp/core/system/Win32Environment.cpp
new file mode 100644
index 0000000..fcf1621
--- /dev/null
+++ b/src/cpp/core/system/Win32Environment.cpp
@@ -0,0 +1,115 @@
+/*
+ * Win32Environment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/system/Environment.hpp>
+
+#include <windows.h>
+
+#include <vector>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/StringUtils.hpp>
+
+namespace core {
+namespace system {
+
+namespace impl {
+
+bool optionIsNamed(const Option& option, const std::string& name)
+{
+ return boost::algorithm::iequals(option.first, name);
+}
+
+} // namespace impl
+
+
+void environment(Options* pEnvironment)
+{
+ // get all environment strings (as unicode)
+ LPWSTR lpEnv = ::GetEnvironmentStringsW();
+ if (lpEnv == NULL)
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return;
+ }
+
+ // iterate over them
+ LPWSTR lpszEnvVar = NULL;
+ for (lpszEnvVar = lpEnv; *lpszEnvVar; lpszEnvVar++)
+ {
+ // get the variable
+ std::wstring envVarWide;
+ while (*lpszEnvVar)
+ {
+ wchar_t ch = *lpszEnvVar;
+ envVarWide.append(1, ch);
+ lpszEnvVar++;
+ }
+
+ // convert to utf8 and parse
+ Option envVar;
+ if (parseEnvVar(string_utils::wideToUtf8(envVarWide), &envVar))
+ pEnvironment->push_back(envVar);
+ }
+
+
+ // free environment strings
+ if (!::FreeEnvironmentStringsW(lpEnv))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+}
+
+// Value returned is UTF-8 encoded
+std::string getenv(const std::string& name)
+{
+ std::wstring nameWide(name.begin(), name.end());
+
+ // get the variable
+ DWORD nSize = 256;
+ std::vector<wchar_t> buffer(nSize);
+ DWORD result = ::GetEnvironmentVariableW(nameWide.c_str(), &(buffer[0]), nSize);
+ if (result == 0) // not found
+ {
+ return std::string();
+ }
+ if (result > nSize) // not enough space in buffer
+ {
+ nSize = result;
+ buffer.resize(nSize);
+ result = ::GetEnvironmentVariableW(nameWide.c_str(), &(buffer[0]), nSize);
+ if (result == 0 || result > nSize)
+ return std::string(); // VERY unexpected failure case
+ }
+
+ // return it
+ return string_utils::wideToUtf8(&(buffer[0]));
+}
+
+void setenv(const std::string& name, const std::string& value)
+{
+ ::SetEnvironmentVariableW(string_utils::utf8ToWide(name).c_str(),
+ string_utils::utf8ToWide(value).c_str());
+}
+
+void unsetenv(const std::string& name)
+{
+ ::SetEnvironmentVariable(name.c_str(), NULL);
+}
+
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/Win32FileScanner.cpp b/src/cpp/core/system/Win32FileScanner.cpp
new file mode 100644
index 0000000..381b14e
--- /dev/null
+++ b/src/cpp/core/system/Win32FileScanner.cpp
@@ -0,0 +1,146 @@
+/*
+ * Win32FileScanner.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/FileScanner.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/system/windows_error.hpp>
+
+#include <core/Error.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FilePath.hpp>
+#include <core/BoostThread.hpp>
+
+namespace core {
+namespace system {
+
+namespace {
+
+FileInfo convertToFileInfo(const FilePath& filePath, bool yield, int *pCount)
+{
+ // yield every 10 files (defend against pegging the cpu for directories
+ // with a huge number of files)
+ if (yield)
+ {
+ *pCount = *pCount + 1;
+ if (*pCount % 10 == 0)
+ boost::this_thread::yield();
+
+ }
+
+ if (filePath.isDirectory())
+ {
+ return FileInfo(filePath.absolutePath(), true, filePath.isSymlink());
+ }
+ else if (filePath.exists())
+ {
+ return FileInfo(filePath.absolutePath(),
+ false,
+ filePath.size(),
+ filePath.lastWriteTime(),
+ filePath.isSymlink());
+ }
+ else
+ {
+ return FileInfo(filePath.absolutePath(), false);
+ }
+}
+
+} // anonymous namespace
+
+
+// NOTE: we bail with an error if the top level directory can't be
+// enumerated however we merely log errors for children. this reflects
+// the notion that a top-level failure will report major problems
+// (e.g. permission to access a volume/drive) whereas errors which
+// occur in children are more likely to refect some idiosyncratic
+// problem with a child dir or file, and we don't want that to
+// interfere with the caller getting a listing of everything else
+// and proceeding with its work
+Error scanFiles(const tree<FileInfo>::iterator_base& fromNode,
+ const core::system::FileScannerOptions& options,
+ tree<FileInfo>* pTree)
+{
+ // clear all existing
+ pTree->erase_children(fromNode);
+
+ // create FilePath for root
+ FilePath rootPath(fromNode->absolutePath());
+
+ // yield if requested (only applies to recursive scans)
+ if (options.recursive && options.yield)
+ boost::this_thread::yield();
+
+ // call onBeforeScanDir hook
+ if (options.onBeforeScanDir)
+ {
+ Error error = options.onBeforeScanDir(*fromNode);
+ if (error)
+ return error;
+ }
+
+ // read directory entries
+ std::vector<FilePath> children;
+ Error error = rootPath.children(&children);
+ if (error)
+ return error;
+
+ // convert to FileInfo and sort using alphasort equivilant (for
+ // compatability with scandir, which is what is used in our
+ // posix-specific implementation
+ int count = 0;
+ std::vector<FileInfo> childrenFileInfo;
+ std::transform(children.begin(),
+ children.end(),
+ std::back_inserter(childrenFileInfo),
+ boost::bind(convertToFileInfo, _1, options.yield, &count));
+ std::sort(childrenFileInfo.begin(),
+ childrenFileInfo.end(),
+ fileInfoPathLessThan);
+
+ // iterate over entries
+ BOOST_FOREACH(const FileInfo& childFileInfo, childrenFileInfo)
+ {
+ // apply filter if we have one
+ if (options.filter && !options.filter(childFileInfo))
+ continue;
+
+ // add the correct type of FileEntry
+ if (childFileInfo.isDirectory())
+ {
+ tree<FileInfo>::iterator_base child =
+ pTree->append_child(fromNode, childFileInfo);
+ if (options.recursive && !childFileInfo.isSymlink())
+ {
+ Error error = scanFiles(child, options, pTree);
+ if (error &&
+ (error.code() != boost::system::windows_error::path_not_found))
+ LOG_ERROR(error);
+ }
+ }
+ else
+ {
+ pTree->append_child(fromNode, childFileInfo);
+ }
+ }
+
+ // return success
+ return Success();
+}
+
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/Win32LibraryLoader.cpp b/src/cpp/core/system/Win32LibraryLoader.cpp
new file mode 100644
index 0000000..c0816a7
--- /dev/null
+++ b/src/cpp/core/system/Win32LibraryLoader.cpp
@@ -0,0 +1,67 @@
+/*
+ * Win32LibraryLoader.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/LibraryLoader.hpp>
+
+#include <windows.h>
+
+#include <core/Error.hpp>
+
+namespace core {
+namespace system {
+
+Error loadLibrary(const std::string& libPath, int options, void** ppLib)
+{
+ // use
+ *ppLib = NULL;
+ *ppLib = (void*)::LoadLibraryEx(libPath.c_str(), NULL, options);
+ if (*ppLib == NULL)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("lib-path", libPath);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error loadSymbol(void* pLib, const std::string& name, void** ppSymbol)
+{
+ *ppSymbol = NULL;
+ *ppSymbol = (void*)::GetProcAddress((HINSTANCE)pLib, name.c_str());
+ if (*ppSymbol == NULL)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("symbol", name);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error closeLibrary(void* pLib)
+{
+ if (!::FreeLibrary((HMODULE)pLib))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ else
+ return Success();
+}
+
+} // namespace system
+} // namespace core
diff --git a/src/cpp/core/system/Win32OutputCapture.cpp b/src/cpp/core/system/Win32OutputCapture.cpp
new file mode 100644
index 0000000..04a49b2
--- /dev/null
+++ b/src/cpp/core/system/Win32OutputCapture.cpp
@@ -0,0 +1,147 @@
+/*
+ * Win32OutputCapture.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/system/OutputCapture.hpp>
+
+#include <windows.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/BoostThread.hpp>
+#include <core/BoostErrors.hpp>
+
+namespace core {
+namespace system {
+
+
+namespace {
+
+void standardStreamCaptureThread(
+ HANDLE hReadPipe,
+ const boost::function<void(const std::string&)>& outputHandler)
+{
+ try
+ {
+ while(true)
+ {
+ const int kBufferSize = 512;
+ char buffer[kBufferSize];
+ DWORD bytesRead = 0;
+ if (::ReadFile(hReadPipe, &buffer, kBufferSize, &bytesRead, NULL))
+ {
+ if (bytesRead > 0)
+ outputHandler(std::string(buffer, bytesRead));
+ }
+ else
+ {
+ // we don't expect errors to ever occur (since the standard
+ // streams are never closed) so log any that do and continue
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ }
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+Error ioError(const std::string& description, const ErrorLocation& location)
+{
+ boost::system::error_code ec(boost::system::errc::io_error,
+ boost::system::get_system_category());
+ Error error(ec, location);
+ error.addProperty("description", description);
+ return error;
+}
+
+Error redirectToPipe(DWORD stdHandle,
+ FILE* fpStdFile,
+ HANDLE* phReadPipe)
+{
+ // create pipe
+ HANDLE hWritePipe;
+ if (!::CreatePipe(phReadPipe, &hWritePipe, NULL, 0))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // reset win32 standard handle
+ if (!::SetStdHandle(stdHandle, hWritePipe))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // reset c runtime library handle
+ int fd = ::_open_osfhandle((intptr_t)hWritePipe, _O_TEXT);
+ if (fd == -1)
+ return ioError("_open_osfhandle", ERROR_LOCATION);
+ if (::_dup2(fd, _fileno(fpStdFile)) != 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // turn off buffering
+ if (::setvbuf(fpStdFile, NULL, _IONBF, 0) != 0)
+ return ioError("setvbuf", ERROR_LOCATION);
+
+ // sync c++ std streams
+ std::ios::sync_with_stdio();
+
+ return Success();
+}
+
+
+} // anonymous namespace
+
+Error captureStandardStreams(
+ const boost::function<void(const std::string&)>& stdoutHandler,
+ const boost::function<void(const std::string&)>& stderrHandler)
+{
+ try
+ {
+ // redirect stdout
+ HANDLE hReadStdoutPipe = NULL;
+ Error error = redirectToPipe(STD_OUTPUT_HANDLE, stdout, &hReadStdoutPipe);
+ if (error)
+ return error;
+
+ // capture stdout
+ boost::thread stdoutThread(boost::bind(standardStreamCaptureThread,
+ hReadStdoutPipe,
+ stdoutHandler));
+
+ // optionally redirect stderror if handler was provided
+ HANDLE hReadStderrPipe = NULL;
+ if (stderrHandler)
+ {
+ // redirect stderr
+ error = redirectToPipe(STD_ERROR_HANDLE, stderr, &hReadStderrPipe);
+ if (error)
+ return error;
+
+ // capture stderr
+ boost::thread stderrThread(boost::bind(standardStreamCaptureThread,
+ hReadStderrPipe,
+ stderrHandler));
+ }
+
+ return Success();
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ return Error(boost::thread_error::ec_from_exception(e), ERROR_LOCATION);
+ }
+}
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/Win32ParentProcessMonitor.cpp b/src/cpp/core/system/Win32ParentProcessMonitor.cpp
new file mode 100644
index 0000000..9a9af25
--- /dev/null
+++ b/src/cpp/core/system/Win32ParentProcessMonitor.cpp
@@ -0,0 +1,40 @@
+/*
+ * Win32ParentProcessMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <core/system/ParentProcessMonitor.hpp>
+
+#include <windows.h>
+#include <stdio.h>
+
+#include <core/Log.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/Error.hpp>
+
+namespace core {
+namespace parent_process_monitor {
+
+Error wrapFork(boost::function<void()> func)
+{
+ func();
+
+ return Success();
+}
+
+ParentTermination waitForParentTermination()
+{
+ return ParentTerminationNoParent;
+}
+
+} // namespace parent_process_monitor
+} // namespace core
diff --git a/src/cpp/core/system/Win32ShellUtils.cpp b/src/cpp/core/system/Win32ShellUtils.cpp
new file mode 100644
index 0000000..861c476
--- /dev/null
+++ b/src/cpp/core/system/Win32ShellUtils.cpp
@@ -0,0 +1,83 @@
+/*
+ * Win32ShellUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/StringUtils.hpp>
+#include <core/system/ShellUtils.hpp>
+
+namespace core {
+namespace shell_utils {
+
+std::string escape(const std::string& arg)
+{
+ /* NOTE: This may be broken in cases where the arg contains special
+ characters like %, ", etc. However, unlike with any sane shell, the
+ Windows shell does not have a consistent way to escape out special
+ characters. And what it does have is horribly under-documented. I
+ thought it best to leave this simple to start and see if anything
+ comes up from field testing. */
+ return "\"" + arg + "\"";
+}
+
+std::string escape(const core::FilePath& path)
+{
+ return escape(string_utils::utf8ToSystem(path.absolutePath()));
+}
+
+std::string join(const std::string& command1, const std::string& command2)
+{
+ return "(" + command1 + ") & (" + command2 + ")";
+}
+
+std::string join_and(const std::string& command1, const std::string& command2)
+{
+ return "(" + command1 + ") && (" + command2 + ")";
+}
+
+std::string join_or(const std::string& command1, const std::string& command2)
+{
+ return "(" + command1 + ") || (" + command2 + ")";
+}
+
+std::string sendStdErrToStdOut(const std::string& command)
+{
+ return "(" + command + ") 2>&1";
+}
+
+std::string sendAllOutputToNull(const std::string& command)
+{
+ return "(" + command + ") >NUL 2>&1";
+}
+
+std::string sendStdErrToNull(const std::string& command)
+{
+ return "(" + command + ") 2> NUL";
+}
+
+std::string sendNullToStdIn(const std::string& command)
+{
+ return "(" + command + ") < NUL";
+}
+
+namespace {
+FilePath s_devnull("NUL");
+} // namespace
+
+const FilePath& devnull()
+{
+ return s_devnull;
+}
+
+}
+}
diff --git a/src/cpp/core/system/Win32System.cpp b/src/cpp/core/system/Win32System.cpp
new file mode 100644
index 0000000..dd4eb4d
--- /dev/null
+++ b/src/cpp/core/system/Win32System.cpp
@@ -0,0 +1,830 @@
+/*
+ * Win32System.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/System.hpp>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <io.h>
+
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+
+#include <windows.h>
+#include <shlobj.h>
+
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <boost/system/windows_error.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/range.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/Log.hpp>
+#include <core/LogWriter.hpp>
+#include <core/Error.hpp>
+#include <core/FileLogWriter.hpp>
+#include <core/StderrLogWriter.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileInfo.hpp>
+#include <core/DateTime.hpp>
+#include <core/StringUtils.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/Environment.hpp>
+
+#ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
+#define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x2000
+#endif
+#ifndef JOB_OBJECT_LIMIT_BREAKAWAY_OK
+#define JOB_OBJECT_LIMIT_BREAKAWAY_OK 0x00000800
+#endif
+
+namespace core {
+namespace system {
+
+namespace {
+// main log writer
+LogWriter* s_pLogWriter = NULL;
+
+// additional log writers
+std::vector<boost::shared_ptr<LogWriter> > s_logWriters;
+
+Error initJobObject(bool* detachFromJob)
+{
+ /*
+ * Create a Job object and assign this process to it. This will
+ * cause all child processes to be assigned to the same job.
+ * With JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE set, all the child
+ * processes will be killed when this process terminates (since
+ * it is the only one holding a handle to the job). With
+ * JOB_OBJECT_LIMIT_BREAKAWAY_OK set it is possible to pass
+ * CREATE_BREAKAWAY_FROM_JOB to CreateProcess (this is required
+ * by Chrome for creating its sub-processes)
+ */
+
+ // If detachFromJob is true, it means we need to relaunch this
+ // executable with CREATE_BREAKAWAY_FROM_JOB
+ *detachFromJob = false;
+
+ HANDLE hJob = ::CreateJobObject(NULL, NULL);
+ if (!hJob)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ ::SetInformationJobObject(hJob,
+ JobObjectExtendedLimitInformation,
+ &jeli,
+ sizeof(jeli));
+
+ if (::AssignProcessToJobObject(hJob, ::GetCurrentProcess()))
+ {
+ DWORD error = ::GetLastError();
+ if (error == ERROR_ACCESS_DENIED)
+ {
+ // Use an environment variable to prevent us from somehow
+ // getting into an infinite loop of detaching (which would
+ // otherwise occur if ERROR_ACCESS_DENIED is being returned
+ // for some reason other than an existing job object being
+ // attached). This works because environment variables are
+ // inherited by our job-detached child process.
+ if (getenv("_RSTUDIO_LEVEL").empty())
+ {
+ setenv("_RSTUDIO_LEVEL", "1");
+ *detachFromJob = true;
+ }
+ }
+ return systemError(error, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+bool isHiddenFile(const std::string& path)
+{
+ DWORD attribs = ::GetFileAttributesA(path.c_str());
+ if (attribs == INVALID_FILE_ATTRIBUTES)
+ return false;
+ else if (attribs & FILE_ATTRIBUTE_HIDDEN)
+ return true;
+ else
+ return false;
+}
+
+} // anonymous namespace
+
+void initHook()
+{
+ // Logging will NOT work in this function!!
+
+ bool detachFromJob;
+ Error error = initJobObject(&detachFromJob);
+ if (!detachFromJob)
+ return;
+
+ TCHAR path[MAX_PATH];
+ if (!::GetModuleFileName(NULL, path, MAX_PATH))
+ return; // Couldn't get the path of the current .exe
+
+ STARTUPINFO startupInfo;
+ memset(&startupInfo, 0, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ PROCESS_INFORMATION procInfo;
+ memset(&procInfo, 0, sizeof(procInfo));
+
+ if (!::CreateProcess(NULL,
+ ::GetCommandLine(),
+ NULL,
+ NULL,
+ TRUE,
+ CREATE_BREAKAWAY_FROM_JOB | ::GetPriorityClass(::GetCurrentProcess()),
+ NULL,
+ NULL,
+ &startupInfo,
+ &procInfo))
+ {
+ return; // Couldn't execute
+ }
+
+ ::AllowSetForegroundWindow(procInfo.dwProcessId);
+ ::WaitForSingleObject(procInfo.hProcess, INFINITE);
+
+ DWORD exitCode;
+ if (!::GetExitCodeProcess(procInfo.hProcess, &exitCode))
+ exitCode = ::GetLastError();
+
+ ::CloseHandle(procInfo.hProcess);
+ ::CloseHandle(procInfo.hThread);
+
+ ::ExitProcess(exitCode);
+}
+
+void initializeSystemLog(const std::string& programIdentity, int logLevel)
+{
+}
+
+void initializeStderrLog(const std::string& programIdentity, int logLevel)
+{
+ if (s_pLogWriter)
+ delete s_pLogWriter;
+
+ s_pLogWriter = new StderrLogWriter(programIdentity, logLevel);
+}
+
+
+void initializeLog(const std::string& programIdentity, int logLevel, const FilePath& settingsDir)
+{
+ if (s_pLogWriter)
+ delete s_pLogWriter;
+
+ s_pLogWriter = new FileLogWriter(programIdentity, logLevel, settingsDir);
+}
+
+void addLogWriter(boost::shared_ptr<core::LogWriter> pLogWriter)
+{
+ s_logWriters.push_back(pLogWriter);
+}
+
+void log(LogLevel logLevel, const std::string& message)
+{
+ if (s_pLogWriter)
+ s_pLogWriter->log(logLevel, message);
+
+ std::for_each(s_logWriters.begin(),
+ s_logWriters.end(),
+ boost::bind(&LogWriter::log, _1, logLevel, message));
+}
+
+bool isWin64()
+{
+ return !getenv("PROCESSOR_ARCHITEW6432").empty()
+ || getenv("PROCESSOR_ARCHITECTURE") == "AMD64";
+}
+
+bool isVistaOrLater()
+{
+ OSVERSIONINFOA osVersion;
+ ZeroMemory(&osVersion, sizeof(OSVERSIONINFOA));
+ osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
+
+ if (::GetVersionExA(&osVersion))
+ {
+ return osVersion.dwMajorVersion >= 6;
+ }
+ else
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return false;
+ }
+}
+
+std::string username()
+{
+ return system::getenv("USERNAME");
+}
+
+// home path strategies
+namespace {
+
+FilePath environmentHomePath(std::string envVariables)
+{
+ using namespace boost::algorithm;
+
+ // use environment override if specified
+ if (!envVariables.empty())
+ {
+ for (split_iterator<std::string::iterator> it =
+ make_split_iterator(envVariables, first_finder("|", is_iequal()));
+ it != split_iterator<std::string::iterator>();
+ ++it)
+ {
+ std::string envHomePath =
+ system::getenv(boost::copy_range<std::string>(*it));
+ if (!envHomePath.empty())
+ {
+ FilePath userHomePath(envHomePath);
+ if (userHomePath.exists())
+ return userHomePath;
+ }
+ }
+ }
+
+ // no override
+ return FilePath();
+}
+
+FilePath currentCSIDLPersonalHomePath()
+{
+ // query for My Documents directory
+ const DWORD SHGFP_TYPE_CURRENT = 0;
+ wchar_t homePath[MAX_PATH];
+ HRESULT hr = ::SHGetFolderPathW(NULL,
+ CSIDL_PERSONAL,
+ NULL,
+ SHGFP_TYPE_CURRENT,
+ homePath);
+ if (SUCCEEDED(hr))
+ {
+ return FilePath(homePath);
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("Unable to retreive user home path. HRESULT: " +
+ safe_convert::numberToString(hr));
+ return FilePath();
+ }
+}
+
+FilePath defaultCSIDLPersonalHomePath()
+{
+ // query for default and force creation (works around situations
+ // where redirected path is not available)
+ const DWORD SHGFP_TYPE_DEFAULT = 1;
+ wchar_t homePath[MAX_PATH];
+ HRESULT hr = ::SHGetFolderPathW(NULL,
+ CSIDL_PERSONAL|CSIDL_FLAG_CREATE,
+ NULL,
+ SHGFP_TYPE_DEFAULT,
+ homePath);
+ if (SUCCEEDED(hr))
+ {
+ return FilePath(homePath);
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("Unable to retreive user home path. HRESULT: " +
+ safe_convert::numberToString(hr));
+ return FilePath();
+ }
+}
+
+FilePath homepathHomePath()
+{
+ std::string homeDrive = core::system::getenv("HOMEDRIVE");
+ std::string homePath = core::system::getenv("HOMEPATH");
+ if (!homeDrive.empty() && !homePath.empty())
+ return FilePath(homeDrive + homePath);
+ else
+ return FilePath();
+}
+
+FilePath homedriveHomePath()
+{
+ std::string homeDrive = core::system::getenv("HOMEDRIVE");
+ if (homeDrive.empty())
+ homeDrive = "C:";
+ return FilePath(homeDrive);
+}
+
+typedef std::pair<std::string,boost::function<FilePath()> > HomePathSource;
+
+} // anonymous namespace
+
+FilePath userHomePath(std::string envOverride)
+{
+ using boost::bind;
+ std::vector<HomePathSource> sources;
+ sources.push_back(std::make_pair("R_USER|HOME",
+ bind(environmentHomePath, envOverride)));
+ sources.push_back(std::make_pair("SHGFP_TYPE_CURRENT",
+ currentCSIDLPersonalHomePath));
+ sources.push_back(std::make_pair("SHGFP_TYPE_DEFAULT",
+ defaultCSIDLPersonalHomePath));
+ std::string envFallback = "USERPROFILE";
+ sources.push_back(std::make_pair(envFallback,
+ bind(environmentHomePath, envFallback)));
+ sources.push_back(std::make_pair("HOMEPATH",
+ homepathHomePath));
+ sources.push_back(std::make_pair("HOMEDRIVE",
+ homedriveHomePath));
+
+ BOOST_FOREACH(const HomePathSource& source, sources)
+ {
+ FilePath homePath = source.second();
+ if (!homePath.empty())
+ {
+ // return if we found one that exists
+ if (homePath.exists())
+ return homePath;
+
+ // otherwise warn that we got a value that didn't exist
+ LOG_WARNING_MESSAGE("Home path returned by " + source.first + " (" +
+ homePath.absolutePath() + ") does not exist.");
+ }
+ }
+
+ // no luck!
+ LOG_ERROR_MESSAGE("No valid home path found for user");
+ return FilePath();
+}
+
+FilePath userSettingsPath(const FilePath& userHomeDirectory,
+ const std::string& appName)
+{
+ wchar_t path[MAX_PATH + 1];
+ std::wstring appNameWide(appName.begin(), appName.end());
+ HRESULT hr = ::SHGetFolderPathAndSubDirW(
+ NULL,
+ CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE,
+ NULL,
+ SHGFP_TYPE_CURRENT,
+ appNameWide.c_str(),
+ path);
+
+ if (hr != S_OK)
+ {
+ LOG_ERROR_MESSAGE("Unable to retreive user home path. HRESULT: " +
+ safe_convert::numberToString(hr));
+ return FilePath();
+ }
+
+ return FilePath(std::wstring(path));
+}
+
+bool currentUserIsPrivilleged(unsigned int minimumUserId)
+{
+ return false;
+}
+
+
+
+Error captureCommand(const std::string& command, std::string* pOutput)
+{
+ // WIN32 popen docs:
+ // http://msdn.microsoft.com/en-us/library/96ayss4b(VS.80).aspx
+
+ // NOTE: note that popen only works from win32 console applications!
+
+ // start process
+ FILE* fp = ::_popen(command.c_str(), "r");
+ if (fp == NULL)
+ return systemError(errno, ERROR_LOCATION);
+
+ // collect output
+ const int kBuffSize = 1024;
+ char buffer[kBuffSize];
+ while (::fgets(buffer, kBuffSize, fp) != NULL)
+ *pOutput += buffer;
+
+ // check if an error terminated our output
+ Error error ;
+ if (::ferror(fp))
+ error = systemError(boost::system::errc::io_error, ERROR_LOCATION);
+
+ // close file
+ if (::_pclose(fp) == -1)
+ {
+ // log existing error before overwriting it
+ if (error)
+ LOG_ERROR(error);
+
+ error = systemError(errno, ERROR_LOCATION);
+ }
+
+ // return status
+ return error;
+}
+
+Error realPath(const FilePath& filePath, FilePath* pRealPath)
+{
+ std::wstring wPath = filePath.absolutePathW();
+ std::vector<wchar_t> buffer(512);
+ DWORD res = ::GetFullPathNameW(wPath.c_str(),
+ buffer.size(),
+ &(buffer[0]),
+ NULL);
+ if (res == 0)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("path", filePath);
+ return error;
+ }
+ else if (res > buffer.size())
+ {
+ buffer.resize(res);
+ res = ::GetFullPathNameW(wPath.c_str(),
+ buffer.size(),
+ &(buffer[0]),
+ NULL);
+ if (res == 0)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ else if (res > buffer.size())
+ return systemError(boost::system::windows_error::bad_length,
+ ERROR_LOCATION);
+ }
+
+ wPath = std::wstring(&(buffer[0]), res);
+ *pRealPath = FilePath(wPath);
+ return Success();
+}
+
+bool isHiddenFile(const FilePath& filePath)
+{
+ return isHiddenFile(filePath.absolutePath());
+}
+
+bool isHiddenFile(const FileInfo& fileInfo)
+{
+ return isHiddenFile(fileInfo.absolutePath());
+}
+
+bool isReadOnly(const FilePath& filePath)
+{
+ // TODO: readonly detection for windows
+ return false;
+}
+
+Error makeFileHidden(const FilePath& path)
+{
+ std::wstring filePath = path.absolutePathW();
+ LPCWSTR lpszPath = filePath.c_str();
+
+ DWORD attribs = ::GetFileAttributesW(lpszPath);
+ if (attribs == INVALID_FILE_ATTRIBUTES)
+ return systemError(GetLastError(), ERROR_LOCATION);
+
+ if (!::SetFileAttributesW(lpszPath, attribs | FILE_ATTRIBUTE_HIDDEN))
+ return systemError(GetLastError(), ERROR_LOCATION);
+
+ return Success();
+}
+
+
+
+
+bool stderrIsTerminal()
+{
+ return _isatty(_fileno(stderr));
+}
+
+bool stdoutIsTerminal()
+{
+ return _isatty(_fileno(stdout));
+}
+
+// uuid
+std::string generateUuid(bool includeDashes)
+{
+ // create the uuid
+ UUID uuid = {0};
+ ::UuidCreate(&uuid);
+ PUCHAR pChar = NULL;
+ ::UuidToStringA(&uuid, &pChar);
+ std::string uuidStr((char*)pChar);
+ ::RpcStringFreeA(&pChar);
+
+ // remove dashes if requested
+ if (!includeDashes)
+ boost::algorithm::replace_all(uuidStr, "-", "");
+
+ // return
+ return uuidStr;
+}
+
+PidType currentProcessId()
+{
+ return ::GetCurrentProcessId();
+}
+
+Error executablePath(const char * argv0,
+ FilePath* pExecutablePath)
+{
+ *pExecutablePath = FilePath(_pgmptr);
+ return Success();
+}
+
+// installation path
+Error installPath(const std::string& relativeToExecutable,
+ const char * argv0,
+ FilePath* pInstallationPath)
+{
+ // get full executable path
+ FilePath exePath;
+ Error error = executablePath(argv0, &exePath);
+ if (error)
+ return error;
+
+ // resolve to install path using given relative path
+ if (relativeToExecutable == "..") // common case
+ *pInstallationPath = exePath.parent().parent();
+ else
+ *pInstallationPath = exePath.parent().complete(relativeToExecutable);
+
+ return Success();
+}
+
+void fixupExecutablePath(FilePath* pExePath)
+{
+ if (pExePath->extension().empty())
+ *pExePath = pExePath->parent().complete(pExePath->filename() + ".exe");
+}
+
+void abort()
+{
+ ::exit(1);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+// No signals on Win32 so all of these are no-ops
+//
+//
+
+
+Error ignoreTerminalSignals()
+{
+ return Success();
+}
+
+Error ignoreChildExits()
+{
+ return Success();
+}
+
+Error reapChildren()
+{
+ return Success();
+}
+
+struct SignalBlocker::Impl
+{
+};
+
+SignalBlocker::SignalBlocker()
+ : pImpl_(new Impl())
+{
+}
+
+
+Error SignalBlocker::block(SignalType signal)
+{
+ return Success();
+}
+
+Error SignalBlocker::blockAll()
+{
+ return Success();
+}
+
+SignalBlocker::~SignalBlocker()
+{
+ try
+ {
+ }
+ catch(...)
+ {
+ }
+}
+
+Error clearSignalMask()
+{
+ return Success();
+}
+
+Error handleSignal(SignalType signal, void (*handler)(int))
+{
+ return Success();
+}
+
+core::Error ignoreSignal(SignalType signal)
+{
+ return Success();
+}
+
+
+Error useDefaultSignalHandler(SignalType signal)
+{
+ return Success();
+}
+
+void sendSignalToSelf(SignalType signal)
+{
+}
+
+class ClipboardScope : boost::noncopyable
+{
+public:
+ ClipboardScope() : opened_(false) {}
+
+ Error open()
+ {
+ if (!::OpenClipboard(NULL))
+ {
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ }
+ else
+ {
+ opened_ = true;
+ return Success();
+ }
+ }
+
+ ~ClipboardScope()
+ {
+ try
+ {
+ if (opened_)
+ {
+ if (!::CloseClipboard())
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+private:
+ bool opened_;
+};
+
+class EnhMetaFile : boost::noncopyable
+{
+public:
+ EnhMetaFile() : hMF_(NULL) {}
+
+ Error open(const FilePath& path)
+ {
+ hMF_ = ::GetEnhMetaFileW(path.absolutePathW().c_str());
+ if (hMF_ == NULL)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ else
+ return Success();
+ }
+
+ ~EnhMetaFile()
+ {
+ try
+ {
+ if (hMF_ != NULL)
+ {
+ if (!::DeleteEnhMetaFile(hMF_))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ }
+ }
+ catch(...)
+ {
+ }
+ }
+
+ HENHMETAFILE handle() const { return hMF_; }
+
+ void release()
+ {
+ hMF_ = NULL;
+ }
+
+private:
+ HENHMETAFILE hMF_;
+};
+
+
+Error copyMetafileToClipboard(const FilePath& path)
+{
+ // open metafile
+ EnhMetaFile enhMetaFile;
+ Error error = enhMetaFile.open(path);
+ if (error)
+ return error;
+
+ // open the clipboard
+ ClipboardScope clipboardScope;
+ error = clipboardScope.open();
+ if (error)
+ return error;
+
+ // emtpy the clipboard
+ if (!::EmptyClipboard())
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // set the clipboard data
+ if (!::SetClipboardData(CF_ENHMETAFILE, enhMetaFile.handle()))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // release the handle (because the clipboard now owns it)
+ enhMetaFile.release();
+
+ // return success
+ return Success();
+}
+
+void ensureLongPath(FilePath* pFilePath)
+{
+ const std::size_t kBuffSize = (MAX_PATH*2) + 1;
+ char buffer[kBuffSize];
+ std::string path = string_utils::utf8ToSystem(pFilePath->absolutePath());
+ if (::GetLongPathName(path.c_str(),
+ buffer,
+ kBuffSize) > 0)
+ {
+ *pFilePath = FilePath(string_utils::systemToUtf8(buffer));
+ }
+}
+
+Error terminateProcess(PidType pid)
+{
+ HANDLE hProc = ::OpenProcess(PROCESS_TERMINATE, false, pid);
+ if (!hProc)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ if (!::TerminateProcess(hProc, 1))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ return Success();
+}
+
+
+Error closeHandle(HANDLE* pHandle, const ErrorLocation& location)
+{
+ if (*pHandle != NULL)
+ {
+ BOOL result = ::CloseHandle(*pHandle);
+ *pHandle = NULL;
+
+ if (!result)
+ return systemError(::GetLastError(), location);
+ else
+ return Success();
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+CloseHandleOnExitScope::~CloseHandleOnExitScope()
+{
+ try
+ {
+ if (!pHandle_ || *pHandle_ == INVALID_HANDLE_VALUE)
+ return;
+
+ Error error = closeHandle(pHandle_, location_);
+ if (error)
+ LOG_ERROR(error);
+ }
+ catch(...)
+ {
+ }
+}
+
+
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/file_monitor/FileMonitor.cpp b/src/cpp/core/system/file_monitor/FileMonitor.cpp
new file mode 100644
index 0000000..6175236
--- /dev/null
+++ b/src/cpp/core/system/file_monitor/FileMonitor.cpp
@@ -0,0 +1,687 @@
+/*
+ * FileMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/system/FileMonitor.hpp>
+
+#include <list>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+#include <core/Thread.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/FileScanner.hpp>
+
+#include "FileMonitorImpl.hpp"
+
+// NOTE: the functions below assume case-sensitive file names. this could
+// in theory cause us to lose notifications on Win32 and OS X however in
+// practice we can't think of an easy way for the user to specify the
+// non case-sensitive variant of a file
+
+namespace core {
+namespace system {
+namespace file_monitor {
+
+namespace {
+
+// track active handles so we can implement unregisterAll and
+// activeEventContexts. note that this list is accessed from
+// the platform-specific file-monitor thread (checkForInput and
+// catch clause of fileMonitorMainThread. this is a naked pointer
+// because it is static and accessed from multiple threads (so
+// we don't want it to ever be destructed)
+std::list<Handle>* s_pActiveHandles;
+
+void addEvent(FileChangeEvent::Type type,
+ const FileInfo& fileInfo,
+ std::vector<FileChangeEvent>* pEvents)
+{
+ pEvents->push_back(FileChangeEvent(type, fileInfo));
+}
+
+bool notDirectories(const FileInfo& fileInfo,
+ const std::vector<std::string>& dirNames)
+{
+ std::string path = fileInfo.absolutePath();
+
+ for (std::size_t i=0; i<dirNames.size(); i++)
+ {
+ if (fileInfo.isDirectory() && boost::algorithm::ends_with(path,
+ dirNames[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string prefixString(const std::string& str, char ch)
+{
+ std::string prefixed;
+ prefixed.reserve(str.length() + 1);
+ prefixed.append(1, ch);
+ prefixed.append(str);
+ return prefixed;
+}
+
+bool notHidden(const FileInfo& fileInfo)
+{
+ return !core::system::isHiddenFile(fileInfo);
+}
+
+bool shouldTraverse(const FileInfo& fileInfo)
+{
+ return fileInfo.isDirectory() &&
+ !FilePath(fileInfo.absolutePath()).isSymlink();
+}
+
+bool sizeAndLastWriteTimeAreEqual(const FileInfo& a, const FileInfo& b)
+{
+ return a.size() == b.size() && a.lastWriteTime() == b.lastWriteTime();
+}
+
+} // anonymous namespace
+
+
+boost::function<bool(const FileInfo&)> excludeDirectoryFilter(
+ const std::string& name)
+{
+ std::vector<std::string> names;
+ names.push_back(name);
+ return excludeDirectoriesFilter(names);
+}
+
+boost::function<bool(const FileInfo&)> excludeDirectoriesFilter(
+ const std::vector<std::string>& names)
+{
+ std::vector<std::string> dirNames;
+ std::transform(names.begin(),
+ names.end(),
+ std::back_inserter(dirNames),
+ boost::bind(prefixString, _1, '/'));
+
+ return boost::bind(notDirectories, _1, dirNames);
+}
+
+boost::function<bool(const FileInfo&)> excludeHiddenFilter()
+{
+ return boost::bind(notHidden, _1);
+}
+
+
+// helpers for platform-specific implementations
+namespace impl {
+
+Error processFileAdded(
+ tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const boost::function<Error(const FileInfo&)>& onBeforeScanDir,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ // see if this node already exists. if it does then check it for changes
+ // (if there are no changes then ignore). we do this because some editors
+ // (for example gedit) actually save files in such a way that FileAdded
+ // is generated (because they overwrite the old file with a move)
+ tree<FileInfo>::sibling_iterator it = impl::findFile(pTree->begin(parentIt),
+ pTree->end(parentIt),
+ fileChange.fileInfo());
+ if (it != pTree->end(parentIt))
+ {
+ if (fileChange.fileInfo() != *it)
+ {
+ pTree->replace(it, fileChange.fileInfo());
+
+ // add it to the fileChanges
+ pFileChanges->push_back(FileChangeEvent(FileChangeEvent::FileModified,
+ fileChange.fileInfo()));
+ }
+ return Success();
+
+ }
+
+ if (recursive && shouldTraverse(fileChange.fileInfo()))
+ {
+ tree<FileInfo> subTree;
+ FileScannerOptions options;
+ options.recursive = true;
+ options.yield = true;
+ options.filter = filter;
+ options.onBeforeScanDir = onBeforeScanDir;
+ Error error = scanFiles(fileChange.fileInfo(), options, &subTree);
+ if (error)
+ return error;
+
+ // merge in the sub-tree
+ tree<FileInfo>::sibling_iterator addedIter =
+ pTree->append_child(parentIt, fileChange.fileInfo());
+ pTree->insert_subtree_after(addedIter, subTree.begin());
+ pTree->erase(addedIter);
+
+ // generate events
+ std::for_each(subTree.begin(),
+ subTree.end(),
+ boost::bind(addEvent,
+ FileChangeEvent::FileAdded,
+ _1,
+ pFileChanges));
+ }
+ else
+ {
+ pTree->append_child(parentIt, fileChange.fileInfo());
+ pFileChanges->push_back(fileChange);
+ }
+
+ // sort the container after insert
+ pTree->sort(pTree->begin(parentIt),
+ pTree->end(parentIt),
+ fileInfoPathLessThan,
+ false);
+
+ return Success();
+}
+
+void processFileModified(tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ // search for a child with this path
+ tree<FileInfo>::sibling_iterator modIt = impl::findFile(
+ pTree->begin(parentIt),
+ pTree->end(parentIt),
+ fileChange.fileInfo());
+
+ // only generate actions if the data is actually new (win32 file monitoring
+ // can generate redundant modified events for save operations as well as
+ // when directories are copied and pasted, in which case an add is followed
+ // by a modified)
+ if ((modIt != pTree->end(parentIt)) &&
+ !sizeAndLastWriteTimeAreEqual(fileChange.fileInfo(), *modIt))
+ {
+ pTree->replace(modIt, fileChange.fileInfo());
+
+ // add it to the fileChanges
+ pFileChanges->push_back(fileChange);
+ }
+}
+
+void processFileRemoved(tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ bool recursive,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ // search for a child with this path
+ tree<FileInfo>::sibling_iterator remIt = findFile(pTree->begin(parentIt),
+ pTree->end(parentIt),
+ fileChange.fileInfo());
+
+ // only generate actions if the item was found in the tree
+ if (remIt != pTree->end(parentIt))
+ {
+ // if this is folder then we need to generate recursive
+ // remove events, otherwise can just add single event
+ if (recursive && shouldTraverse(*remIt))
+ {
+ tree<FileInfo> subTree(remIt);
+ std::for_each(subTree.begin(),
+ subTree.end(),
+ boost::bind(addEvent,
+ FileChangeEvent::FileRemoved,
+ _1,
+ pFileChanges));
+ }
+ else
+ {
+ // use the previous FileInfo for the event payload (since the
+ // passed FileInfo might not have a correct value for isDirectory
+ // since we couldn't read it from the filesystem)
+ pFileChanges->push_back(FileChangeEvent(FileChangeEvent::FileRemoved,
+ *remIt));
+ }
+
+ // remove it from the tree
+ pTree->erase(remIt);
+ }
+}
+
+Error discoverAndProcessFileChanges(
+ const FileInfo& fileInfo,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const boost::function<Error(const FileInfo&)>& onBeforeScanDir,
+ tree<FileInfo>* pTree,
+ const boost::function<void(const std::vector<FileChangeEvent>&)>&
+ onFilesChanged)
+{
+ // find this path in our fileTree
+ tree<FileInfo>::iterator it = std::find(pTree->begin(),
+ pTree->end(),
+ fileInfo);
+
+ // if we don't find it then it may have been excluded by a filter, just bail
+ if (it == pTree->end())
+ return Success();
+
+ // scan this directory into a new tree which we can compare to the old tree
+ tree<FileInfo> subdirTree;
+ FileScannerOptions options;
+ options.recursive = recursive;
+ options.yield = true;
+ options.filter = filter;
+ options.onBeforeScanDir = onBeforeScanDir;
+ Error error = scanFiles(fileInfo, options, &subdirTree);
+ if (error)
+ return error;
+
+ // handle recursive vs. non-recursive scan differnetly
+ if (recursive)
+ {
+ // check for changes on full subtree
+ std::vector<FileChangeEvent> fileChanges;
+ tree<FileInfo> existingSubtree(it);
+ collectFileChangeEvents(existingSubtree.begin(),
+ existingSubtree.end(),
+ subdirTree.begin(),
+ subdirTree.end(),
+ &fileChanges);
+
+ // fire events
+ onFilesChanged(fileChanges);
+
+ // wholesale replace subtree
+ pTree->insert_subtree_after(it, subdirTree.begin());
+ pTree->erase(it);
+ }
+ else
+ {
+ // scan for changes on just the children
+ std::vector<FileChangeEvent> childrenFileChanges;
+ collectFileChangeEvents(pTree->begin(it),
+ pTree->end(it),
+ subdirTree.begin(subdirTree.begin()),
+ subdirTree.end(subdirTree.begin()),
+ &childrenFileChanges);
+
+ // build up actual file changes and mutate the tree as appropriate
+ std::vector<FileChangeEvent> fileChanges;
+ BOOST_FOREACH(const FileChangeEvent& fileChange, childrenFileChanges)
+ {
+ switch(fileChange.type())
+ {
+ case FileChangeEvent::FileAdded:
+ {
+ Error error = processFileAdded(it,
+ fileChange,
+ recursive,
+ filter,
+ pTree,
+ &fileChanges);
+ if (error)
+ LOG_ERROR(error);
+ break;
+ }
+ case FileChangeEvent::FileModified:
+ {
+ processFileModified(it, fileChange, pTree, &fileChanges);
+ break;
+ }
+ case FileChangeEvent::FileRemoved:
+ {
+ processFileRemoved(it,
+ fileChange,
+ recursive,
+ pTree,
+ &fileChanges);
+ break;
+ }
+ case FileChangeEvent::None:
+ default:
+ break;
+ }
+ }
+
+ // fire events
+ onFilesChanged(fileChanges);
+ }
+
+ return Success();
+}
+
+std::list<void*> activeEventContexts()
+{
+ std::list<void*> contexts;
+ std::transform(s_pActiveHandles->begin(),
+ s_pActiveHandles->end(),
+ std::back_inserter(contexts),
+ boost::bind(&Handle::pData, _1));
+
+ return contexts;
+}
+
+
+} // namespace impl
+
+
+// these are implemented per-platform
+namespace detail {
+
+// run the monitor, calling back checkForInput periodically to see if there are
+// new registrations or unregistrations
+void run(const boost::function<void()>& checkForInput);
+
+// register a new file monitor
+Handle registerMonitor(const core::FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks);
+
+// unregister a file monitor
+void unregisterMonitor(Handle handle);
+
+// stop the monitor. allows for optinal global cleanup and/or waiting
+// for termination state on the monitor thread
+void stop();
+
+} // namespace detail
+
+
+namespace {
+
+class RegistrationCommand
+{
+public:
+ enum Type { None, Register, Unregister };
+
+public:
+ RegistrationCommand()
+ : type_(None)
+ {
+ }
+
+ RegistrationCommand(const core::FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks)
+ : type_(Register),
+ filePath_(filePath),
+ recursive_(recursive),
+ filter_(filter),
+ callbacks_(callbacks)
+ {
+ }
+
+ explicit RegistrationCommand(Handle handle)
+ : type_(Unregister), handle_(handle)
+ {
+ }
+
+ Type type() const { return type_; }
+
+ const core::FilePath& filePath() const { return filePath_; }
+ bool recursive() const { return recursive_; }
+ const boost::function<bool(const FileInfo&)>& filter() const
+ {
+ return filter_;
+ }
+ const Callbacks& callbacks() const { return callbacks_; }
+
+ Handle handle() const
+ {
+ return handle_;
+ }
+
+private:
+ // command type
+ Type type_;
+
+ // register command data
+ core::FilePath filePath_;
+ bool recursive_;
+ boost::function<bool(const FileInfo&)> filter_;
+ Callbacks callbacks_;
+
+ // unregister command data
+ Handle handle_;
+};
+
+typedef core::thread::ThreadsafeQueue<RegistrationCommand>
+ RegistrationCommandQueue;
+RegistrationCommandQueue& registrationCommandQueue()
+{
+ static core::thread::ThreadsafeQueue<RegistrationCommand> instance;
+ return instance;
+}
+
+typedef core::thread::ThreadsafeQueue<boost::function<void()> > CallbackQueue;
+CallbackQueue& callbackQueue()
+{
+ static core::thread::ThreadsafeQueue<boost::function<void()> > instance;
+ return instance;
+}
+
+
+void checkForInput()
+{
+ // wait for up to 250ms for new input (we can't block indefinitely because this
+ // code runs within the context of the monitoring thread which also needs to free
+ // up so that filesystem change notifications can be received)
+ RegistrationCommand command;
+ while (registrationCommandQueue().deque(
+ &command,
+ boost::posix_time::milliseconds(250)))
+ {
+ switch(command.type())
+ {
+ case RegistrationCommand::Register:
+ {
+ Handle handle = detail::registerMonitor(command.filePath(),
+ command.recursive(),
+ command.filter(),
+ command.callbacks());
+ if (!handle.empty())
+ s_pActiveHandles->push_back(handle);
+ break;
+ }
+
+ case RegistrationCommand::Unregister:
+ {
+ // first ensure that this handle is active (protects against double
+ // unregister, which can occur if we've automatically unregistered
+ // as a result of an error or a call to file_monitor::stop)
+ std::list<Handle>::iterator it = std::find(s_pActiveHandles->begin(),
+ s_pActiveHandles->end(),
+ command.handle());
+ if (it != s_pActiveHandles->end())
+ {
+ detail::unregisterMonitor(*it);
+ s_pActiveHandles->erase(it);
+ }
+ break;
+ }
+
+ case RegistrationCommand::None:
+ break;
+ }
+ }
+}
+
+void fileMonitorThreadMain()
+{
+ // run the file monitor thread
+ bool running = false;
+ try
+ {
+ // first wait until there is at least one command to process
+ // (makes us immediately responsive to the first request)
+ if (registrationCommandQueue().isEmpty())
+ registrationCommandQueue().wait();
+
+ // now run the monitoring thread
+ running = true;
+ file_monitor::detail::run(boost::bind(checkForInput));
+ }
+ catch(const boost::thread_interrupted& e)
+ {
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // always clean up (even for unexpected exception case)
+ try
+ {
+ // unregister all active handles. these are direct calls to
+ // detail::unregisterMonitor (on the background thread)
+ std::for_each(s_pActiveHandles->begin(),
+ s_pActiveHandles->end(),
+ detail::unregisterMonitor);
+
+ // clear the list
+ s_pActiveHandles->clear();
+
+ // allow the implementation a chance to stop completely (e.g. may
+ // need to wait for pending async operations to complete)
+ if (running)
+ detail::stop();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+void enqueOnRegistered(const Callbacks& callbacks,
+ Handle handle,
+ const tree<FileInfo>& fileTree)
+{
+ if (callbacks.onRegistered)
+ {
+ callbackQueue().enque(boost::bind(callbacks.onRegistered,
+ handle,
+ fileTree));
+ }
+}
+
+void enqueOnRegistrationError(const Callbacks& callbacks, const Error& error)
+{
+ if (callbacks.onRegistrationError)
+ {
+ callbackQueue().enque(boost::bind(callbacks.onRegistrationError, error));
+ }
+}
+
+void enqueOnMonitoringError(const Callbacks& callbacks, const Error& error)
+{
+ if (callbacks.onMonitoringError)
+ {
+ callbackQueue().enque(boost::bind(callbacks.onMonitoringError, error));
+ }
+}
+
+void enqueOnFilesChanged(const Callbacks& callbacks,
+ const std::vector<FileChangeEvent>& fileChanges)
+{
+ if (callbacks.onFilesChanged)
+ {
+ callbackQueue().enque(boost::bind(callbacks.onFilesChanged, fileChanges));
+ }
+}
+
+void enqueOnUnregistered(const Callbacks& callbacks, Handle handle)
+{
+ if (callbacks.onUnregistered)
+ {
+ callbackQueue().enque(boost::bind(callbacks.onUnregistered, handle));
+ }
+}
+
+boost::thread s_fileMonitorThread;
+
+} // anonymous namespace
+
+
+void initialize()
+{
+ s_pActiveHandles = new std::list<Handle>();
+ core::thread::safeLaunchThread(fileMonitorThreadMain, &s_fileMonitorThread);
+}
+
+void stop()
+{
+ if (s_fileMonitorThread.joinable())
+ {
+ s_fileMonitorThread.interrupt();
+
+ // wait for for the thread to stop
+ if (!s_fileMonitorThread.timed_join(boost::posix_time::seconds(3)))
+ {
+ LOG_WARNING_MESSAGE("file monitor thread didn't stop on its own");
+ }
+
+ s_fileMonitorThread.detach();
+ }
+}
+
+void registerMonitor(const FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks)
+{
+ // bind a new version of the callbacks that puts them on the callback queue
+ Callbacks qCallbacks;
+ qCallbacks.onRegistered = boost::bind(enqueOnRegistered, callbacks, _1, _2);
+ qCallbacks.onRegistrationError = boost::bind(enqueOnRegistrationError,
+ callbacks,
+ _1);
+ qCallbacks.onMonitoringError = boost::bind(enqueOnMonitoringError,
+ callbacks,
+ _1);
+ qCallbacks.onFilesChanged = boost::bind(enqueOnFilesChanged, callbacks, _1);
+ qCallbacks.onUnregistered = boost::bind(enqueOnUnregistered, callbacks, _1);
+
+ // enque the registration
+ registrationCommandQueue().enque(RegistrationCommand(filePath,
+ recursive,
+ filter,
+ qCallbacks));
+}
+
+void unregisterMonitor(Handle handle)
+{
+ registrationCommandQueue().enque(RegistrationCommand(handle));
+}
+
+void checkForChanges()
+{
+ boost::function<void()> callback;
+ while (callbackQueue().deque(&callback))
+ callback();
+}
+
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+
+
+
+
diff --git a/src/cpp/core/system/file_monitor/FileMonitorImpl.hpp b/src/cpp/core/system/file_monitor/FileMonitorImpl.hpp
new file mode 100644
index 0000000..17d3215
--- /dev/null
+++ b/src/cpp/core/system/file_monitor/FileMonitorImpl.hpp
@@ -0,0 +1,124 @@
+/*
+ * FileMonitorImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef CORE_SYSTEM_FILE_MONITOR_IMPL_HPP
+#define CORE_SYSTEM_FILE_MONITOR_IMPL_HPP
+
+#include <string>
+#include <algorithm>
+#include <list>
+
+#include <boost/bind.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/collection/Tree.hpp>
+
+#include <core/system/FileChangeEvent.hpp>
+
+#include <core/system/FileMonitor.hpp>
+
+namespace core {
+namespace system {
+namespace file_monitor {
+namespace impl {
+
+Error processFileAdded(
+ tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const boost::function<Error(const FileInfo&)>& onBeforeScanDir,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges);
+
+inline Error processFileAdded(
+ tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ return processFileAdded(parentIt,
+ fileChange,
+ recursive,
+ filter,
+ boost::function<Error(const FileInfo&)>(),
+ pTree,
+ pFileChanges);
+}
+
+void processFileModified(tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges);
+
+void processFileRemoved(tree<FileInfo>::iterator parentIt,
+ const FileChangeEvent& fileChange,
+ bool recursive,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges);
+
+Error discoverAndProcessFileChanges(
+ const FileInfo& fileInfo,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const boost::function<Error(const FileInfo&)>& onBeforeScanDir,
+ tree<FileInfo>* pTree,
+ const boost::function<void(const std::vector<FileChangeEvent>&)>&
+ onFilesChanged);
+
+inline Error discoverAndProcessFileChanges(
+ const FileInfo& fileInfo,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ tree<FileInfo>* pTree,
+ const boost::function<void(const std::vector<FileChangeEvent>&)>&
+ onFilesChanged)
+{
+ return discoverAndProcessFileChanges(
+ fileInfo,
+ recursive,
+ filter,
+ boost::function<Error(const FileInfo&)>(),
+ pTree,
+ onFilesChanged);
+}
+
+template <typename Iterator>
+Iterator findFile(Iterator begin, Iterator end, const std::string& path)
+{
+ return std::find_if(begin, end, boost::bind(fileInfoHasPath,
+ _1,
+ path));
+}
+
+template <typename Iterator>
+Iterator findFile(Iterator begin, Iterator end, const FileInfo& fileInfo)
+{
+ return findFile(begin, end, fileInfo.absolutePath());
+}
+
+std::list<void*> activeEventContexts();
+
+
+} // namespace impl
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+#endif // CORE_SYSTEM_FILE_MONITOR_IMPL_HPP
+
+
diff --git a/src/cpp/core/system/file_monitor/LinuxFileMonitor.cpp b/src/cpp/core/system/file_monitor/LinuxFileMonitor.cpp
new file mode 100644
index 0000000..ecb8b85
--- /dev/null
+++ b/src/cpp/core/system/file_monitor/LinuxFileMonitor.cpp
@@ -0,0 +1,647 @@
+/*
+ * LinuxFileMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/FileMonitor.hpp>
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/inotify.h>
+
+#include <set>
+
+#include <boost/utility.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FileInfo.hpp>
+
+#include <core/system/FileScanner.hpp>
+#include <core/system/System.hpp>
+
+#include "FileMonitorImpl.hpp"
+
+#include "config.h"
+
+namespace core {
+namespace system {
+namespace file_monitor {
+
+namespace {
+
+struct Watch
+{
+ Watch()
+ : wd(-1), path()
+ {
+ }
+
+ Watch(int wd, const std::string& path)
+ : wd(wd), path(path)
+ {
+ }
+
+ bool empty() const { return path.empty(); }
+
+ int wd;
+ std::string path;
+
+ bool operator < (const Watch& other) const
+ {
+ return this->wd < other.wd;
+ }
+};
+
+// boost::multi_index_container based class for managing a set of watches
+class Watches
+{
+public:
+
+ void insert(const Watch& watch)
+ {
+ watches_.insert(watch);
+ }
+
+ void erase(const Watch& watch)
+ {
+ watches_.get<wd>().erase(watch.wd);
+ }
+
+ Watch find(int wd) const
+ {
+ WatchesByDescriptor::const_iterator it = descriptorIndex().find(wd);
+ if (it != descriptorIndex().end())
+ return *it;
+ else
+ return Watch();
+ }
+
+ Watch find(const std::string& path) const
+ {
+ WatchesByPath::const_iterator it = pathIndex().find(path);
+ if (it != pathIndex().end())
+ return *it;
+ else
+ return Watch();
+ }
+
+ void forEach(const boost::function<void(const Watch&)> op) const
+ {
+ std::for_each(descriptorIndex().begin(), descriptorIndex().end(), op);
+ }
+
+ void clear()
+ {
+ watches_ = WatchesContainer();
+ }
+
+private:
+
+ struct wd {};
+ struct path {};
+
+ typedef boost::multi_index::multi_index_container<
+
+ Watch,
+
+ boost::multi_index::indexed_by<
+
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<wd>,
+ boost::multi_index::member<Watch,
+ int,
+ &Watch::wd>
+ >,
+
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<path>,
+ boost::multi_index::member<Watch,
+ std::string,
+ &Watch::path>
+ >
+ >
+ > WatchesContainer;
+
+ typedef WatchesContainer::index<wd>::type WatchesByDescriptor;
+ typedef WatchesContainer::index<path>::type WatchesByPath;
+
+ const WatchesByDescriptor& descriptorIndex() const
+ {
+ return watches_.get<wd>();
+ }
+
+ const WatchesByPath& pathIndex() const
+ {
+ return watches_.get<path>();
+ }
+
+ WatchesContainer watches_;
+};
+
+
+class FileEventContext : boost::noncopyable
+{
+public:
+ FileEventContext()
+ : fd(-1),
+ recursive(false)
+ {
+ handle = Handle((void*)this);
+ }
+ virtual ~FileEventContext() {}
+ Handle handle;
+ int fd;
+ Watches watches;
+ FilePath rootPath;
+ bool recursive;
+ boost::function<bool(const FileInfo&)> filter;
+ tree<FileInfo> fileTree;
+ Callbacks callbacks;
+};
+
+void terminateWithMonitoringError(FileEventContext* pContext,
+ const Error& error)
+{
+ pContext->callbacks.onMonitoringError(error);
+
+ // unregister this monitor (this is done via postback from the
+ // main file_monitor loop so that the monitor Handle can be tracked)
+ file_monitor::unregisterMonitor(pContext->handle);
+}
+
+Error addWatch(const FileInfo& fileInfo,
+ const FilePath& rootPath,
+ bool allowRootSymlink,
+ int fd,
+ Watches* pWatches)
+{
+ // NOTE: both inotify_add_watch and std::set::insert gracefully
+ // handle duplicate additions, inotify_add_watch by modifying the
+ // existing watch and returning the same watch descriptor, and
+ // set::set by simply doing nothing. therefore, we don't bother
+ // checking to see if the watch exists and don't generally worry
+ // about adding duplicate watches
+
+ // define watch mask
+ uint32_t mask = 0 ;
+ mask |= IN_CREATE;
+ mask |= IN_DELETE;
+ mask |= IN_MODIFY;
+ mask |= IN_MOVED_TO;
+ mask |= IN_MOVED_FROM;
+ mask |= IN_Q_OVERFLOW;
+
+ // add IN_DONT_FOLLOW unless we are explicitly allowing root symlinks
+ // and this is a watch for the root path
+ if (!allowRootSymlink ||
+ (fileInfo.absolutePath() != rootPath.absolutePath()))
+ {
+ mask |= IN_DONT_FOLLOW;
+ }
+
+ // initialize watch
+ int wd = ::inotify_add_watch(fd, fileInfo.absolutePath().c_str(), mask);
+ if (wd < 0)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", fileInfo.absolutePath());
+ return error;
+ }
+
+ // record it
+ pWatches->insert(Watch(wd, fileInfo.absolutePath()));
+
+ // return success
+ return Success();
+}
+
+boost::function<Error(const FileInfo&)> addWatchFunction(
+ FileEventContext* pContext,
+ bool allowRootSymlink = false)
+{
+ return boost::bind(addWatch,
+ _1,
+ pContext->rootPath,
+ allowRootSymlink,
+ pContext->fd,
+ &pContext->watches);
+}
+
+void removeWatch(int fd, const Watch& watch)
+{
+ // remove the watch
+ int result = ::inotify_rm_watch(fd, watch.wd);
+
+ // log error if it isn't EINVAL (which is expected if e.g. the
+ // filesystem has been unmounted or the root directory has been deleted)
+ if (result < 0 && errno != EINVAL)
+ {
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("path", watch.path);
+ LOG_ERROR(error);
+ }
+}
+
+void removeAllWatches(FileEventContext* pContext)
+{
+ pContext->watches.forEach(boost::bind(removeWatch,
+ pContext->fd,
+ _1));
+ pContext->watches.clear();
+}
+
+void closeContext(FileEventContext* pContext)
+{
+ // remove all watches
+ removeAllWatches(pContext);
+
+ // close the file descriptor
+ if (pContext->fd >= 0)
+ {
+ // close the descriptor
+ safePosixCall<int>(boost::bind(::close, pContext->fd), ERROR_LOCATION);
+
+ // reset file descriptor
+ pContext->fd = -1;
+ }
+}
+
+Error processEvent(FileEventContext* pContext,
+ struct inotify_event* pEvent,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ // determine event type
+ FileChangeEvent::Type eventType = FileChangeEvent::None;
+ if (pEvent->mask & IN_CREATE)
+ eventType = FileChangeEvent::FileAdded;
+ else if (pEvent->mask & IN_DELETE)
+ eventType = FileChangeEvent::FileRemoved;
+ else if (pEvent->mask & IN_MODIFY)
+ eventType = FileChangeEvent::FileModified;
+ else if (pEvent->mask & IN_MOVED_TO)
+ eventType = FileChangeEvent::FileAdded;
+ else if (pEvent->mask & IN_MOVED_FROM)
+ eventType = FileChangeEvent::FileRemoved;
+
+ // return event if we got a valid event type and the event applies to a
+ // child of the monitored directory (len == 0 occurs for root element)
+ if ((eventType != FileChangeEvent::None) && (pEvent->len > 0))
+ {
+ // find the FileInfo for this wd (ignore if we can't find one)
+ Watch watch = pContext->watches.find(pEvent->wd);
+ if (watch.empty())
+ return Success();
+
+ // get an iterator to the parent dir
+ tree<FileInfo>::iterator parentIt = impl::findFile(
+ pContext->fileTree.begin(),
+ pContext->fileTree.end(),
+ watch.path);
+
+ // if we can't find a parent then return (this directory may have
+ // been excluded from scanning due to a filter)
+ if (parentIt == pContext->fileTree.end())
+ return Success();
+
+ // get file info
+ FilePath filePath = FilePath(parentIt->absolutePath()).complete(
+ pEvent->name);
+
+
+ // if the file exists then collect as many extended attributes
+ // as necessary -- otherwise just record path and dir status
+ FileInfo fileInfo;
+ if (filePath.exists())
+ {
+ fileInfo = FileInfo(filePath, filePath.isSymlink());
+ }
+ else
+ {
+ fileInfo = FileInfo(filePath.absolutePath(), pEvent->mask & IN_ISDIR);
+ }
+
+ // if this doesn't meet the filter then ignore
+ if (pContext->filter && !pContext->filter(fileInfo))
+ return Success();
+
+ // handle the various types of actions
+ switch(eventType)
+ {
+ case FileChangeEvent::FileRemoved:
+ {
+ // generate events
+ FileChangeEvent event(FileChangeEvent::FileRemoved, fileInfo);
+ std::vector<FileChangeEvent> removeEvents;
+ impl::processFileRemoved(parentIt,
+ event,
+ pContext->recursive,
+ &pContext->fileTree,
+ &removeEvents);
+
+ // for each directory remove event remove any watches we have for it
+ BOOST_FOREACH(const FileChangeEvent& event, removeEvents)
+ {
+ if (event.fileInfo().isDirectory())
+ {
+ Watch watch = pContext->watches.find(
+ event.fileInfo().absolutePath());
+ if (!watch.empty())
+ {
+ removeWatch(pContext->fd, watch);
+ pContext->watches.erase(watch);
+ }
+ }
+ }
+
+ // copy to the target events
+ std::copy(removeEvents.begin(),
+ removeEvents.end(),
+ std::back_inserter(*pFileChanges));
+
+ break;
+ }
+ case FileChangeEvent::FileAdded:
+ {
+ FileChangeEvent event(FileChangeEvent::FileAdded, fileInfo);
+ Error error = impl::processFileAdded(parentIt,
+ event,
+ pContext->recursive,
+ pContext->filter,
+ addWatchFunction(pContext),
+ &pContext->fileTree,
+ pFileChanges);
+ // log the error if it wasn't no such file/dir (this can happen
+ // in the normal course of business if a file is deleted between
+ // the time the change is detected and we try to inspect it)
+ if (error &&
+ (error.code() != boost::system::errc::no_such_file_or_directory))
+ {
+ LOG_ERROR(error);
+ }
+ break;
+ }
+ case FileChangeEvent::FileModified:
+ {
+ FileChangeEvent event(FileChangeEvent::FileModified, fileInfo);
+ impl::processFileModified(parentIt,
+ event,
+ &pContext->fileTree,
+ pFileChanges);
+ break;
+ }
+ case FileChangeEvent::None:
+ break;
+ }
+ }
+
+
+
+ return Success();
+}
+
+
+Handle registrationFailure(int errorNumber,
+ FileEventContext* pContext,
+ const Callbacks& callbacks,
+ const ErrorLocation& location)
+{
+ closeContext(pContext);
+ callbacks.onRegistrationError(systemError(errorNumber, location));
+ return Handle();
+}
+
+
+} // anonymous namespace
+
+namespace detail {
+
+// register a new file monitor
+Handle registerMonitor(const core::FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks)
+{
+ // create and allocate FileEventContext (create auto-ptr in case we
+ // return early, we'll call release later before returning)
+ FileEventContext* pContext = new FileEventContext();
+ pContext->rootPath = filePath;
+ pContext->recursive = recursive;
+ pContext->filter = filter;
+ std::auto_ptr<FileEventContext> autoPtrContext(pContext);
+
+ // init file descriptor
+#ifdef HAVE_INOTIFY_INIT1
+ pContext->fd = ::inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+ if (pContext->fd < 0)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+#else
+ // init file descriptor
+ pContext->fd = ::inotify_init();
+ if (pContext->fd < 0)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+
+ // set non-blocking
+ int flags = ::fcntl(pContext->fd, F_GETFL);
+ if (flags == -1)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+ if (::fcntl(pContext->fd, F_SETFL, flags | O_NONBLOCK) == -1)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+
+ // set close on exec
+ int fdFlags = ::fcntl(pContext->fd, F_GETFD);
+ if (fdFlags == -1)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+ if (::fcntl(pContext->fd, F_SETFD, fdFlags | FD_CLOEXEC) == -1)
+ return registrationFailure(errno, pContext, callbacks, ERROR_LOCATION);
+#endif
+
+ // scan the files (use callback to setup watches)
+ FileScannerOptions options;
+ options.recursive = recursive;
+ options.yield = true;
+ options.filter = filter;
+ options.onBeforeScanDir = addWatchFunction(pContext, true);
+ Error error = scanFiles(FileInfo(filePath), options, &pContext->fileTree);
+ if (error)
+ {
+ // close context
+ closeContext(pContext);
+
+ // return error
+ callbacks.onRegistrationError(error);
+ return Handle();
+ }
+
+ // now that we have finished the file listing we know we have a valid
+ // file-monitor so set the callbacks
+ pContext->callbacks = callbacks;
+
+ // we are going to pass the context pointer to the client (as the Handle)
+ // so we release it here to relinquish ownership
+ autoPtrContext.release();
+
+ // notify the caller that we have successfully registered
+ callbacks.onRegistered(pContext->handle, pContext->fileTree);
+
+ // return the handle
+ return pContext->handle;
+}
+
+// unregister a file monitor
+void unregisterMonitor(Handle handle)
+{
+ // cast to context
+ FileEventContext* pContext = (FileEventContext*)(handle.pData);
+
+ // close context
+ closeContext(pContext);
+
+ // let the client know we are unregistered (note this call should always
+ // be prior to delete pContext below!)
+ pContext->callbacks.onUnregistered(handle);
+
+ // delete the context
+ delete pContext;
+}
+
+void run(const boost::function<void()>& checkForInput)
+{
+ // create event buffer (enough to hold 5000 events)
+ const int kEventSize = sizeof(struct inotify_event);
+ const int kFilenameSizeEstimate = 20;
+ const int kEventBufferLength = 5000 * (kEventSize+kFilenameSizeEstimate);
+ char eventBuffer[kEventBufferLength];
+
+ while(true)
+ {
+ std::list<void*> contexts = impl::activeEventContexts();
+ BOOST_FOREACH(void* ctx, contexts)
+ {
+ // cast to context
+ FileEventContext* pContext = (FileEventContext*)ctx;
+
+ // bail if we don't have callbacks (we wouldn't if a callback snuck
+ // through to us even after we failed to fully initialize the
+ // file monitor (e.g. if there was an error during file listing)
+ if (!pContext->callbacks.onFilesChanged)
+ continue;
+
+ // check for context root directory deleted
+ if (!pContext->rootPath.exists())
+ {
+ Error error = fileNotFoundError(pContext->rootPath.absolutePath(),
+ ERROR_LOCATION);
+ terminateWithMonitoringError(pContext, error);
+ continue;
+ }
+
+ // loop reading from this context's fd until EAGAIN or EWOULDBLOCK
+ std::vector<FileChangeEvent> fileChanges;
+ while (true)
+ {
+ // read
+ int len = posixCall<int>(boost::bind(::read,
+ pContext->fd,
+ eventBuffer,
+ kEventBufferLength));
+ if (len < 0)
+ {
+ // don't terminate for errors indicating no events available
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+
+ // otherwise terminate this watch (notify user and break
+ // out of the read loop for this context)
+ terminateWithMonitoringError(pContext,
+ systemError(errno, ERROR_LOCATION));
+ break;
+ }
+
+ // iterate through the events
+ int i = 0;
+ while (i < len)
+ {
+ // get the event
+ typedef struct inotify_event* EventPtr;
+ EventPtr pEvent = (EventPtr)&eventBuffer[i];
+
+ // buffer overflow is handled specially -- basically
+ // we start over because we missed events
+ if (pEvent->mask & IN_Q_OVERFLOW)
+ {
+ // remove all watches
+ removeAllWatches(pContext);
+
+ // generate events based on scanning
+ Error error =impl::discoverAndProcessFileChanges(
+ FileInfo(pContext->rootPath),
+ pContext->recursive,
+ pContext->filter,
+ addWatchFunction(pContext, true),
+ &pContext->fileTree,
+ pContext->callbacks.onFilesChanged);
+ if (error)
+ terminateWithMonitoringError(pContext, error);
+
+ // always break here -- we've generated events based on
+ // a fresh scan so any other events in the queue would
+ // be duplicates
+ break;
+ }
+
+ // process the event
+ Error error = processEvent(pContext, pEvent, &fileChanges);
+ if (error)
+ {
+ terminateWithMonitoringError(pContext, error);
+ break;
+ }
+
+ // advance to next event
+ i += kEventSize + pEvent->len;
+ }
+ }
+
+ // fire any events we got
+ if (!fileChanges.empty())
+ pContext->callbacks.onFilesChanged(fileChanges);
+ }
+
+ // check for input (register/unregister of monitors)
+ checkForInput();
+ }
+}
+
+void stop()
+{
+ // nothing to do here
+}
+
+} // namespace detail
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+
+
+
+
diff --git a/src/cpp/core/system/file_monitor/MacFileMonitor.cpp b/src/cpp/core/system/file_monitor/MacFileMonitor.cpp
new file mode 100644
index 0000000..9ebc937
--- /dev/null
+++ b/src/cpp/core/system/file_monitor/MacFileMonitor.cpp
@@ -0,0 +1,337 @@
+/*
+ * MacFileMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/FileMonitor.hpp>
+
+#include <CoreServices/CoreServices.h>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FileInfo.hpp>
+#include <core/Thread.hpp>
+
+#include <core/system/FileScanner.hpp>
+#include <core/system/System.hpp>
+
+#include "FileMonitorImpl.hpp"
+
+namespace core {
+namespace system {
+namespace file_monitor {
+
+namespace {
+
+class FileEventContext : boost::noncopyable
+{
+public:
+ FileEventContext()
+ : streamRef(NULL), recursive(false)
+ {
+ handle = Handle((void*)this);
+ }
+ virtual ~FileEventContext() {}
+ Handle handle;
+ FSEventStreamRef streamRef;
+ FilePath rootPath;
+ bool recursive;
+ boost::function<bool(const FileInfo&)> filter;
+ tree<FileInfo> fileTree;
+ Callbacks callbacks;
+};
+
+void fileEventCallback(ConstFSEventStreamRef streamRef,
+ void *pCallbackInfo,
+ size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[])
+{
+ // get context
+ FileEventContext* pContext = (FileEventContext*)pCallbackInfo;
+
+ // bail if we don't have callbacks (we wouldn't if a callback snuck
+ // through to us even after we failed to fully initialize the file monitor
+ // (e.g. if there was an error during file listing)
+ if (!pContext->callbacks.onFilesChanged)
+ return;
+
+ char **paths = (char**)eventPaths;
+ for (std::size_t i=0; i<numEvents; i++)
+ {
+ // check for root changed (unregister)
+ if (eventFlags[i] & kFSEventStreamEventFlagRootChanged)
+ {
+ // propagate error to client
+ Error error = fileNotFoundError(pContext->rootPath.absolutePath(),
+ ERROR_LOCATION);
+ pContext->callbacks.onMonitoringError(error);
+
+ // unregister this monitor (this is done via postback from the
+ // main file_monitor loop so that the monitor Handle can be tracked)
+ file_monitor::unregisterMonitor(pContext->handle);
+
+ return;
+ }
+
+ // make a copy of the path and strip off trailing / if necessary
+ std::string path(paths[i]);
+ boost::algorithm::trim_right_if(path, boost::algorithm::is_any_of("/"));
+
+ // if we aren't in recursive mode then ignore this if it isn't for
+ // the root directory
+ if (!pContext->recursive && (path != pContext->rootPath.absolutePath()))
+ continue;
+
+ // get FileInfo for this directory
+ FileInfo fileInfo(path, true);
+
+ // apply the filter (if any)
+ if (!pContext->filter || pContext->filter(fileInfo))
+ {
+ // check for need to do recursive scan
+ bool recursive = pContext->recursive &&
+ (eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs);
+
+ // process changes
+ Error error = impl::discoverAndProcessFileChanges(
+ fileInfo,
+ recursive,
+ pContext->filter,
+ &(pContext->fileTree),
+ pContext->callbacks.onFilesChanged);
+ if (error &&
+ (error.code() != boost::system::errc::no_such_file_or_directory))
+ {
+ LOG_ERROR(error);
+ }
+ }
+ }
+}
+
+class CFRefScope : boost::noncopyable
+{
+public:
+ explicit CFRefScope(CFTypeRef ref)
+ : ref_(ref)
+ {
+ }
+ virtual ~CFRefScope()
+ {
+ try
+ {
+ ::CFRelease(ref_);
+ }
+ catch(...)
+ {
+ }
+ }
+private:
+ CFTypeRef ref_;
+};
+
+void invalidateAndReleaseEventStream(FSEventStreamRef streamRef)
+{
+ ::FSEventStreamInvalidate(streamRef);
+ ::FSEventStreamRelease(streamRef);
+}
+
+void stopInvalidateAndReleaseEventStream(FSEventStreamRef streamRef)
+{
+ ::FSEventStreamStop(streamRef);
+ invalidateAndReleaseEventStream(streamRef);
+}
+
+} // anonymous namespace
+
+namespace detail {
+
+// register a new file monitor
+Handle registerMonitor(const FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks)
+{
+ // allocate file path
+ CFStringRef filePathRef = ::CFStringCreateWithCString(
+ kCFAllocatorDefault,
+ filePath.absolutePath().c_str(),
+ kCFStringEncodingUTF8);
+ if (filePathRef == NULL)
+ {
+ callbacks.onRegistrationError(systemError(
+ boost::system::errc::not_enough_memory,
+ ERROR_LOCATION));
+ return Handle();
+ }
+ CFRefScope filePathRefScope(filePathRef);
+
+ // allocate paths array
+ CFArrayRef pathsArrayRef = ::CFArrayCreate(kCFAllocatorDefault,
+ (const void **)&filePathRef,
+ 1,
+ NULL);
+ if (pathsArrayRef == NULL)
+ {
+ callbacks.onRegistrationError(systemError(
+ boost::system::errc::not_enough_memory,
+ ERROR_LOCATION));
+ return Handle();
+ }
+ CFRefScope pathsArrayRefScope(pathsArrayRef);
+
+
+ // create and allocate FileEventContext (create auto-ptr in case we
+ // return early, we'll call release later before returning)
+ FileEventContext* pContext = new FileEventContext();
+ pContext->rootPath = filePath;
+ pContext->recursive = recursive;
+ pContext->filter = filter;
+ std::auto_ptr<FileEventContext> autoPtrContext(pContext);
+ FSEventStreamContext context;
+ context.version = 0;
+ context.info = (void*) pContext;
+ context.retain = NULL;
+ context.release = NULL;
+ context.copyDescription = NULL;
+
+ // create the stream and save a reference to it
+ pContext->streamRef = ::FSEventStreamCreate(
+ kCFAllocatorDefault,
+ &fileEventCallback,
+ &context,
+ pathsArrayRef,
+ kFSEventStreamEventIdSinceNow,
+ 1,
+ kFSEventStreamCreateFlagNoDefer |
+ kFSEventStreamCreateFlagWatchRoot);
+ if (pContext->streamRef == NULL)
+ {
+ callbacks.onRegistrationError(systemError(
+ boost::system::errc::no_stream_resources,
+ ERROR_LOCATION));
+ return Handle();
+ }
+
+ // schedule with the run loop
+ ::FSEventStreamScheduleWithRunLoop(pContext->streamRef,
+ ::CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+
+ // start the event stream (check for errors and release if necessary
+ if (!::FSEventStreamStart(pContext->streamRef))
+ {
+ invalidateAndReleaseEventStream(pContext->streamRef);
+
+ callbacks.onRegistrationError(systemError(
+ boost::system::errc::no_stream_resources,
+ ERROR_LOCATION));
+ return Handle();
+ }
+
+ // scan the files
+ core::system::FileScannerOptions options;
+ options.recursive = recursive;
+ options.yield = true;
+ options.filter = filter;
+ Error error = scanFiles(FileInfo(filePath), options, &pContext->fileTree);
+ if (error)
+ {
+ // stop, invalidate, release
+ stopInvalidateAndReleaseEventStream(pContext->streamRef);
+
+ // return error
+ callbacks.onRegistrationError(error);
+ return Handle();
+ }
+
+ // now that we have finished the file listing we know we have a valid
+ // file-monitor so set the callbacks
+ pContext->callbacks = callbacks;
+
+ // we are going to pass the context pointer to the client (as the Handle)
+ // so we release it here to relinquish ownership
+ autoPtrContext.release();
+
+ // notify the caller that we have successfully registered
+ callbacks.onRegistered(pContext->handle, pContext->fileTree);
+
+ // return the handle
+ return pContext->handle;
+}
+
+// unregister a file monitor
+void unregisterMonitor(Handle handle)
+{
+ // cast to context
+ FileEventContext* pContext = (FileEventContext*)(handle.pData);
+
+ // stop, invalidate, release
+ stopInvalidateAndReleaseEventStream(pContext->streamRef);
+
+ // let the client know we are unregistered (note this call should always
+ // be prior to delete pContext below!)
+ pContext->callbacks.onUnregistered(handle);
+
+ // delete the context
+ delete pContext;
+}
+
+void run(const boost::function<void()>& checkForInput)
+{
+ // ensure we have a run loop for this thread (not sure if this is
+ // strictly necessary but it is not harmful)
+ ::CFRunLoopGetCurrent();
+
+ while (true)
+ {
+ // check the run loop
+ SInt32 reason = ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
+
+ // if we handled a source then run again
+ if (reason == kCFRunLoopRunHandledSource)
+ {
+ continue;
+ }
+
+ else if (reason == kCFRunLoopRunStopped)
+ {
+ LOG_WARNING_MESSAGE("Unexpected stop of file monitor run loop");
+ break;
+ }
+
+ // check for input
+ checkForInput();
+ }
+}
+
+void stop()
+{
+ // no need to call CFRunLoopStop(CFRunLoopGetCurrent()) because control
+ // is already outside of the run loop logic (above).
+}
+
+} // namespace detail
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+
+
+
+
diff --git a/src/cpp/core/system/file_monitor/Win32FileMonitor.cpp b/src/cpp/core/system/file_monitor/Win32FileMonitor.cpp
new file mode 100644
index 0000000..f653121
--- /dev/null
+++ b/src/cpp/core/system/file_monitor/Win32FileMonitor.cpp
@@ -0,0 +1,624 @@
+/*
+ * Win32FileMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/system/FileMonitor.hpp>
+
+#include <windows.h>
+
+#include <memory>
+
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <core/system/FileScanner.hpp>
+#include <core/system/System.hpp>
+
+#include "FileMonitorImpl.hpp"
+
+
+namespace core {
+namespace system {
+namespace file_monitor {
+
+namespace {
+
+// buffer size for notifications (cannot be > 64kb for network drives)
+const std::size_t kBuffSize = 32768;
+
+class FileEventContext : boost::noncopyable
+{
+public:
+ FileEventContext()
+ : recursive(false),
+ hDirectory(NULL),
+ readDirChangesPending(false),
+ hRestartTimer(NULL),
+ restartCount(0)
+ {
+ receiveBuffer.resize(kBuffSize);
+ handlingBuffer.resize(kBuffSize);
+ handle = Handle((void*)this);
+ }
+ virtual ~FileEventContext() {}
+
+ // handle
+ Handle handle;
+
+ // path we are monitoring, recursive flag, and handle to the directory
+ FilePath rootPath;
+ bool recursive;
+ HANDLE hDirectory;
+
+ // structures/buffers used to reach changes (and flag used to
+ // determine whether the system may write into these buffers)
+ OVERLAPPED overlapped;
+ std::vector<BYTE> receiveBuffer;
+ std::vector<BYTE> handlingBuffer;
+ bool readDirChangesPending;
+
+ // our own snapshot of the file tree
+ tree<FileInfo> fileTree;
+
+ // timer for attempting restarts on a delayed basis (and counter
+ // to enforce a maximum number of retries)
+ HANDLE hRestartTimer;
+ int restartCount;
+
+ // filter/callbacks
+ boost::function<bool(const FileInfo&)> filter;
+ Callbacks callbacks;
+};
+
+void safeCloseHandle(HANDLE hObject, const ErrorLocation& location)
+{
+ if (hObject != NULL)
+ {
+ if (!::CloseHandle(hObject))
+ LOG_ERROR(systemError(::GetLastError(), location));
+ }
+}
+
+void cleanupContext(FileEventContext* pContext)
+{
+ if (pContext->hDirectory != NULL)
+ {
+ if (!::CancelIo(pContext->hDirectory))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+
+ safeCloseHandle(pContext->hDirectory, ERROR_LOCATION);
+
+ pContext->hDirectory = NULL;
+ }
+
+ if (pContext->hRestartTimer != NULL)
+ {
+ // make sure timer APC is never called after a cleanupContext
+ if (!::CancelWaitableTimer(pContext->hRestartTimer))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+
+ safeCloseHandle(pContext->hRestartTimer, ERROR_LOCATION);
+
+ pContext->hRestartTimer = NULL;
+ }
+
+ // delete pContext only if there are no read dir changes operations
+ // pending -- if there are then we wait to delete pContext until
+ // the completion routine gets ERROR_OPERATION_ABORTED
+ if (!pContext->readDirChangesPending)
+ delete pContext;
+}
+
+void removeTrailingSlash(std::wstring* pPath)
+{
+ boost::algorithm::trim_right_if(*pPath, boost::algorithm::is_any_of(L"\\"));
+}
+
+void ensureLongFilePath(FilePath* pFilePath)
+{
+ // get the filename, if it is 12 characters or less and it contains
+ // a "~" then it may be a short file name. in that case do the conversion
+ std::string filename = pFilePath->filename();
+ if (filename.length() <= 12 && filename.find('~') != std::string::npos)
+ {
+ const std::size_t kBuffSize = (MAX_PATH*2) + 1;
+ char buffer[kBuffSize];
+ if (::GetLongPathName(pFilePath->absolutePath().c_str(),
+ buffer,
+ kBuffSize) > 0)
+ {
+ *pFilePath = FilePath(buffer);
+ }
+ }
+}
+
+void processFileChange(DWORD action,
+ const FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ tree<FileInfo>* pTree,
+ std::vector<FileChangeEvent>* pFileChanges)
+{
+ // ignore all directory modified actions (we rely instead on the
+ // actions which occur inside the directory)
+ if (filePath.isDirectory() && (action == FILE_ACTION_MODIFIED))
+ return;
+
+ // screen out the root directory (this should never occur but if it
+ // does for any reason we want to prevent it from interfering
+ // with the logic below (which assumes a child path)
+ if (filePath.isDirectory() &&
+ (filePath.absolutePath() == pTree->begin()->absolutePath()))
+ {
+ return;
+ }
+
+ // get an iterator to this file's parent
+ FileInfo parentFileInfo = FileInfo(filePath.parent());
+ tree<FileInfo>::iterator parentIt = impl::findFile(pTree->begin(),
+ pTree->end(),
+ parentFileInfo);
+
+ // if we can't find a parent then return (this directory may have
+ // been excluded from scanning due to a filter)
+ if (parentIt == pTree->end())
+ return;
+
+ // get the file info
+ FileInfo fileInfo(filePath);
+
+ // apply the filter
+ if (filter && !filter(fileInfo))
+ return;
+
+ switch(action)
+ {
+ case FILE_ACTION_ADDED:
+ case FILE_ACTION_RENAMED_NEW_NAME:
+ {
+ FileChangeEvent event(FileChangeEvent::FileAdded, fileInfo);
+ Error error = impl::processFileAdded(parentIt,
+ event,
+ recursive,
+ filter,
+ pTree,
+ pFileChanges);
+ if (error)
+ LOG_ERROR(error);
+ break;
+ }
+ case FILE_ACTION_REMOVED:
+ case FILE_ACTION_RENAMED_OLD_NAME:
+ {
+ FileChangeEvent event(FileChangeEvent::FileRemoved, fileInfo);
+ impl::processFileRemoved(parentIt,
+ event,
+ recursive,
+ pTree,
+ pFileChanges);
+ break;
+ }
+ case FILE_ACTION_MODIFIED:
+ {
+ FileChangeEvent event(FileChangeEvent::FileModified, fileInfo);
+ impl::processFileModified(parentIt, event, pTree, pFileChanges);
+ break;
+ }
+ }
+}
+
+void processFileChanges(FileEventContext* pContext,
+ DWORD dwNumberOfBytesTransfered)
+{
+ // accumulate file changes
+ std::vector<FileChangeEvent> fileChanges;
+
+ // cycle through the entries in the buffer
+ char* pBuffer = (char*)&pContext->handlingBuffer[0];
+ while(true)
+ {
+ // check for buffer pointer which has overflowed the end (apparently this
+ // can happen if the underlying directory is deleted)
+ if( (DWORD)((BYTE*)pBuffer - &(pContext->handlingBuffer[0])) >
+ dwNumberOfBytesTransfered )
+ {
+ Error error = systemError(ERROR_BUFFER_OVERFLOW, ERROR_LOCATION);
+ LOG_ERROR(error);
+ break;
+ }
+
+ // get file notify struct
+ FILE_NOTIFY_INFORMATION& fileNotify = (FILE_NOTIFY_INFORMATION&)*pBuffer;
+
+ // compute a full wide path
+ std::wstring name(fileNotify.FileName,
+ fileNotify.FileNameLength/sizeof(wchar_t));
+ removeTrailingSlash(&name);
+ FilePath filePath(pContext->rootPath.absolutePathW() + L"\\" + name);
+
+ // ensure this is a long file name (docs say it could be short or long!)
+ // (note that the call to GetLongFileNameW will fail if the file has
+ // already been deleted, therefore if a delete notification using a
+ // short file name comes in we may not successfully match it to
+ // our in-memory tree and thus "miss" the delete
+ ensureLongFilePath(&filePath);
+
+ // apply filter if we have one
+ if (!pContext->filter || pContext->filter(FileInfo(filePath)))
+ {
+ // process the file change
+ processFileChange(fileNotify.Action,
+ filePath,
+ pContext->recursive,
+ pContext->filter,
+ &(pContext->fileTree),
+ &fileChanges);
+ }
+
+ // break or advance to next notification as necessary
+ if (!fileNotify.NextEntryOffset)
+ break;
+ else
+ pBuffer += fileNotify.NextEntryOffset;
+ };
+
+ // notify client of file changes
+ pContext->callbacks.onFilesChanged(fileChanges);
+}
+
+void terminateWithMonitoringError(FileEventContext* pContext,
+ const Error& error)
+{
+ pContext->callbacks.onMonitoringError(error);
+
+ // unregister this monitor (this is done via postback from the
+ // main file_monitor loop so that the monitor Handle can be tracked)
+ file_monitor::unregisterMonitor(pContext->handle);
+}
+
+
+bool isRecoverableByRestart(const Error& error)
+{
+ return
+ // undocumented return value that indicates we should do a restart
+ // (see: http://blogs.msdn.com/b/oldnewthing/archive/2011/08/12/10195186.aspx)
+ error.code().value() == ERROR_NOTIFY_ENUM_DIR ||
+
+ // error which some users have observed occuring if a network
+ // volume is being monitored and there are too many simultaneous
+ // reads and writes
+ error.code().value() == ERROR_TOO_MANY_CMDS;
+}
+
+Error readDirectoryChanges(FileEventContext* pContext);
+void enqueRestartMonitoring(FileEventContext* pContext);
+
+void restartMonitoring(FileEventContext* pContext)
+{
+ // start monitoring again (always do this before the scan so we don't
+ // miss any events which occur while we are scanning)
+ Error error = readDirectoryChanges(pContext);
+ if (error)
+ {
+ // try to recover up to 10 times if the error is known to be recoverable
+ // (note the enque delays the attempted restart by 1 second to give
+ // the volume/system the chance to catch up from too many file changes)
+ if (isRecoverableByRestart(error) && (++(pContext->restartCount) <= 10))
+ enqueRestartMonitoring(pContext);
+ else
+ terminateWithMonitoringError(pContext, error);
+
+ return;
+ }
+
+ // successfully restarted monitoring, reset the restart count to 0
+ pContext->restartCount = 0;
+
+ // full recursive scan to detect changes and refresh the tree
+ error = impl::discoverAndProcessFileChanges(
+ *(pContext->fileTree.begin()),
+ pContext->recursive,
+ pContext->filter,
+ &(pContext->fileTree),
+ pContext->callbacks.onFilesChanged);
+ if (error)
+ terminateWithMonitoringError(pContext, error);
+}
+
+VOID CALLBACK restartMonitoringApcProc(LPVOID lpArg, DWORD, DWORD)
+{
+ // get context
+ FileEventContext* pContext = (FileEventContext*)lpArg;
+
+ // close the timer handle
+ safeCloseHandle(pContext->hRestartTimer, ERROR_LOCATION);
+ pContext->hRestartTimer = NULL;
+
+ // attempt the restart
+ restartMonitoring(pContext);
+}
+
+
+void enqueRestartMonitoring(FileEventContext* pContext)
+{
+ // create the restart timer (1 second from now)
+ pContext->hRestartTimer = ::CreateWaitableTimer(NULL, true, NULL);
+ if (pContext->hRestartTimer == NULL)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ terminateWithMonitoringError(pContext, error);
+ return;
+ }
+
+ // setup large integer to indicate 1 second from now
+ const __int64 kSECOND = 10000000;
+ __int64 qwDueTime = -1 * kSECOND;
+ LARGE_INTEGER dueTime;
+ dueTime.LowPart = (DWORD) ( qwDueTime & 0xFFFFFFFF );
+ dueTime.HighPart = (LONG) ( qwDueTime >> 32 );
+
+ // enque the restart proc to run after time timer expires
+ BOOL success = ::SetWaitableTimer(pContext->hRestartTimer,
+ &dueTime,
+ 0,
+ restartMonitoringApcProc,
+ (PVOID)pContext,
+ FALSE);
+
+ if (!success)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ terminateWithMonitoringError(pContext, error);
+ }
+}
+
+// track number of active requests (we wait for this to get to zero before
+// allowing the exit of the monitoring thread -- this ensures that we have
+// performed full cleanup for all monitoring contexts before exiting.
+volatile LONG s_activeRequests = 0;
+
+VOID CALLBACK FileChangeCompletionRoutine(DWORD dwErrorCode, // completion code
+ DWORD dwNumberOfBytesTransfered,
+ LPOVERLAPPED lpOverlapped)
+{
+ // get the context
+ FileEventContext* pContext = (FileEventContext*)(lpOverlapped->hEvent);
+
+ // note that read changes is no longer pending
+ pContext->readDirChangesPending = false;
+
+ // check for aborted
+ if (dwErrorCode == ERROR_OPERATION_ABORTED)
+ {
+ // decrement the active request counter
+ ::InterlockedDecrement(&s_activeRequests);
+
+ // let the client know we are unregistered (note this call should always
+ // be prior to delete pContext below!)
+ pContext->callbacks.onUnregistered(pContext->handle);
+
+ // we wait to delete the pContext until here because it owns the
+ // OVERLAPPED structure and buffers, and so if we deleted it earlier
+ // and the OS tried to access those memory regions we would crash
+ delete pContext;
+
+ return;
+ }
+
+ // bail if we don't have callbacks installed yet (could have occurred
+ // if we encountered an error during file scanning which caused us to
+ // fail but then a file notification still snuck through)
+ if (!pContext->callbacks.onFilesChanged)
+ return;
+
+ // make sure the root path still exists (if it doesn't then bail)
+ if (!pContext->rootPath.exists())
+ {
+ Error error = fileNotFoundError(pContext->rootPath.absolutePath(),
+ ERROR_LOCATION);
+ terminateWithMonitoringError(pContext, error);
+ return;
+ }
+
+ // check for buffer overflow. this means there are too many file changes
+ // for the systme to keep up with -- in this case try to restart monitoring
+ // (after a 1 second delay) and repeat the restart up to 10 times
+ if(dwNumberOfBytesTransfered == 0)
+ {
+ // attempt to restart monitoring
+ enqueRestartMonitoring(pContext);
+ return;
+ }
+
+ // copy to processing buffer (so we can immediately begin another read)
+ ::CopyMemory(&(pContext->handlingBuffer[0]),
+ &(pContext->receiveBuffer[0]),
+ dwNumberOfBytesTransfered);
+
+ // begin the next read -- if this fails then enque a restart
+ Error error = readDirectoryChanges(pContext);
+ if (isRecoverableByRestart(error))
+ {
+ enqueRestartMonitoring(pContext);
+ error = Success();
+ }
+
+ // process file changes
+ processFileChanges(pContext, dwNumberOfBytesTransfered);
+
+ // report the (fatal) error if necessary
+ if (error)
+ terminateWithMonitoringError(pContext, error);
+}
+
+Error readDirectoryChanges(FileEventContext* pContext)
+{
+ DWORD dwBytes = 0;
+ if(!::ReadDirectoryChangesW(pContext->hDirectory,
+ &(pContext->receiveBuffer[0]),
+ pContext->receiveBuffer.size(),
+ pContext->recursive ? TRUE : FALSE,
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_LAST_WRITE,
+ &dwBytes,
+ &(pContext->overlapped),
+ &FileChangeCompletionRoutine))
+ {
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ }
+ else
+ {
+ pContext->readDirChangesPending = true;
+ return Success();
+ }
+}
+
+} // anonymous namespace
+
+namespace detail {
+
+// register a new file monitor
+Handle registerMonitor(const core::FilePath& filePath,
+ bool recursive,
+ const boost::function<bool(const FileInfo&)>& filter,
+ const Callbacks& callbacks)
+{
+ // create and allocate FileEventContext (create auto-ptr in case we
+ // return early, we'll call release later before returning)
+ FileEventContext* pContext = new FileEventContext();
+ std::auto_ptr<FileEventContext> autoPtrContext(pContext);
+
+ // save the wide absolute path (notifications only come in wide strings)
+ // strip any trailing slash for predictable append semantics
+ std::wstring wpath = filePath.absolutePathW();
+ removeTrailingSlash(&wpath);
+ pContext->rootPath = FilePath(wpath);
+ pContext->recursive = recursive;
+
+ // open the directory
+ pContext->hDirectory = ::CreateFileW(
+ filePath.absolutePathW().c_str(),
+ FILE_LIST_DIRECTORY,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+ NULL);
+ if (pContext->hDirectory == INVALID_HANDLE_VALUE)
+ {
+ callbacks.onRegistrationError(
+ systemError(::GetLastError(),ERROR_LOCATION));
+ return Handle();
+ }
+
+ // initialize overlapped structure to point to our context
+ ::ZeroMemory(&(pContext->overlapped), sizeof(OVERLAPPED));
+ pContext->overlapped.hEvent = pContext;
+
+ // get the monitoring started
+ Error error = readDirectoryChanges(pContext);
+ if (error)
+ {
+ // cleanup
+ safeCloseHandle(pContext->hDirectory, ERROR_LOCATION);
+
+ // return error
+ callbacks.onRegistrationError(error);
+
+ return Handle();
+ }
+
+ // we have passed the pContext into the system so it's ownership will
+ // now be governed by the receipt of ERROR_OPERATION_ABORTED within
+ // the completion callback
+ autoPtrContext.release();
+
+ // increment the number of active requests
+ ::InterlockedIncrement(&s_activeRequests);
+
+ // scan the files
+ core::system::FileScannerOptions options;
+ options.recursive = recursive;
+ options.yield = true;
+ options.filter = filter;
+ error = scanFiles(FileInfo(filePath), options, &pContext->fileTree);
+ if (error)
+ {
+ // cleanup
+ cleanupContext(pContext);
+
+ // return error
+ callbacks.onRegistrationError(error);
+
+ return Handle();
+ }
+
+ // now that we have finished the file listing we know we have a valid
+ // file-monitor so set the callbacks
+ pContext->filter = filter;
+ pContext->callbacks = callbacks;
+
+ // notify the caller that we have successfully registered
+ callbacks.onRegistered(pContext->handle, pContext->fileTree);
+
+ // return the handle
+ return pContext->handle;
+}
+
+// unregister a file monitor
+void unregisterMonitor(Handle handle)
+{
+ // this will end up calling the completion routine with
+ // ERROR_OPERATION_ABORTED at which point we'll delete the context
+ cleanupContext((FileEventContext*)(handle.pData));
+}
+
+void run(const boost::function<void()>& checkForInput)
+{
+ // initialize active requests to zero
+ s_activeRequests = 0;
+
+ // loop waiting for:
+ // - completion routine callbacks (occur during SleepEx); or
+ // - inbound commands (occur during checkForInput)
+ while (true)
+ {
+ // look for changes and keep calling SleepEx as long as we have them
+ while(::SleepEx(1, TRUE) == WAIT_IO_COMPLETION) ;
+
+ checkForInput();
+ }
+}
+
+void stop()
+{
+ // call ::SleepEx until all active requests hae terminated
+ while (s_activeRequests > 0)
+ {
+ ::SleepEx(100, TRUE);
+ }
+}
+
+} // namespace detail
+} // namespace file_monitor
+} // namespace system
+} // namespace core
+
+
+
+
+
diff --git a/src/cpp/core/system/recycle_bin/LinuxRecycleBin.cpp b/src/cpp/core/system/recycle_bin/LinuxRecycleBin.cpp
new file mode 100644
index 0000000..ae033fa
--- /dev/null
+++ b/src/cpp/core/system/recycle_bin/LinuxRecycleBin.cpp
@@ -0,0 +1,31 @@
+/*
+ * LinuxRecycleBin.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+namespace core {
+namespace system {
+namespace recycle_bin {
+
+Error sendTo(const FilePath& filePath)
+{
+ return filePath.remove();
+}
+
+} // namespace recycle_bin
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/recycle_bin/MacRecycleBin.cpp b/src/cpp/core/system/recycle_bin/MacRecycleBin.cpp
new file mode 100644
index 0000000..19a358c
--- /dev/null
+++ b/src/cpp/core/system/recycle_bin/MacRecycleBin.cpp
@@ -0,0 +1,73 @@
+/*
+ * MacRecycleBin.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <CoreServices/CoreServices.h>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/StringUtils.hpp>
+
+// GetMacOSStatusCommentString and FSMoveObjectToTrashSync deprecated
+// as of Mac OS X 10.8
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+namespace core {
+namespace system {
+namespace recycle_bin {
+
+namespace {
+
+Error errorForStatus(OSStatus status,
+ const FilePath& filePath,
+ const ErrorLocation& location)
+{
+ Error error = systemError(boost::system::errc::protocol_error,
+ ::GetMacOSStatusCommentString(status),
+ location);
+ error.addProperty("OSStatus", status);
+ error.addProperty("path", filePath);
+ return error;
+}
+
+} // anonymous namespace
+
+Error sendTo(const FilePath& filePath)
+{
+ FSRef ref;
+ std::string sysPath = string_utils::utf8ToSystem(filePath.absolutePath());
+ OSStatus status = ::FSPathMakeRefWithOptions(
+ (const UInt8*)sysPath.c_str(),
+ kFSPathMakeRefDoNotFollowLeafSymlink,
+ &ref,
+ NULL);
+ if (status != 0)
+ return errorForStatus(status, filePath, ERROR_LOCATION);
+
+ status = ::FSMoveObjectToTrashSync(&ref,
+ NULL,
+ kFSFileOperationDefaultOptions);
+ if (status != 0)
+ return errorForStatus(status, filePath, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+} // namespace recycle_bin
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/system/recycle_bin/Win32RecycleBin.cpp b/src/cpp/core/system/recycle_bin/Win32RecycleBin.cpp
new file mode 100644
index 0000000..ec8fe48
--- /dev/null
+++ b/src/cpp/core/system/recycle_bin/Win32RecycleBin.cpp
@@ -0,0 +1,66 @@
+/*
+ * Win32RecycleBin.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <windows.h>
+#include <shellapi.h>
+
+namespace core {
+namespace system {
+namespace recycle_bin {
+
+Error sendTo(const FilePath& filePath)
+{
+ // get the path and double-null terminate
+ std::wstring wPath = filePath.absolutePathW();
+ std::vector<wchar_t> buffPath;
+ std::copy(wPath.begin(), wPath.end(), std::back_inserter(buffPath));
+ buffPath.push_back(L'\0');
+ buffPath.push_back(L'\0');
+
+ SHFILEOPSTRUCTW fileOp;
+ fileOp.hwnd = NULL;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = &(buffPath[0]);
+ fileOp.pTo = L"";
+ fileOp.fFlags = FOF_ALLOWUNDO |
+ FOF_NOCONFIRMATION |
+ FOF_NOERRORUI |
+ FOF_SILENT;
+ fileOp.fAnyOperationsAborted = FALSE;
+ fileOp.hNameMappings = NULL;
+ fileOp.lpszProgressTitle = L"";
+
+ int result = ::SHFileOperationW(&fileOp);
+ if (result != 0)
+ {
+ Error error = systemError(boost::system::errc::protocol_error,
+ ERROR_LOCATION);
+ error.addProperty("result", result);
+ error.addProperty("path", filePath);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+} // namespace recycle_bin
+} // namespace system
+} // namespace core
+
diff --git a/src/cpp/core/tex/TexLogParser.cpp b/src/cpp/core/tex/TexLogParser.cpp
new file mode 100644
index 0000000..285dd75
--- /dev/null
+++ b/src/cpp/core/tex/TexLogParser.cpp
@@ -0,0 +1,545 @@
+/*
+ * TexLogParser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/tex/TexLogParser.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+
+namespace core {
+namespace tex {
+
+namespace {
+
+// Helper function, returns true if str begins with any of these values
+bool beginsWith(const std::string& str,
+ const std::string& test1,
+ const std::string& test2=std::string(),
+ const std::string& test3=std::string(),
+ const std::string& test4=std::string())
+{
+ using namespace boost::algorithm;
+ if (starts_with(str, test1))
+ return true;
+
+ if (test2.empty())
+ return false;
+ else if (starts_with(str, test2))
+ return true;
+
+ if (test3.empty())
+ return false;
+ else if (starts_with(str, test3))
+ return true;
+
+ if (test4.empty())
+ return false;
+ else if (starts_with(str, test4))
+ return true;
+
+ return false;
+}
+
+// Finds unmatched parens in `line` and puts them in pParens. Can be either
+// ( or ). Logically the result can only be [zero or more ')'] followed by
+// [zero or more '('].
+void findUnmatchedParens(const std::string& line,
+ std::vector<std::string::const_iterator>* pParens)
+{
+ // We need to ignore close parens unless they are at the start of a line,
+ // preceded by nothing but whitespace and/or other close parens. Without
+ // this, sample.Rnw has some false positives due to some math errors, e.g.:
+ //
+ // l.204 (x + (y^
+ // 2))
+ //
+ // The first line is ignored because it's part of an error message. The rest
+ // gets parsed and underflows the file stack.
+ bool ignoreCloseParens = false;
+
+ // NOTE: I don't know if it's possible for (<filename> to appear anywhere
+ // but the beginning of the line (preceded only by whitespace(?) and close
+ // parens). But the Sublime Text 2 plugin code seemed to imply that it is
+ // possible.
+
+ for (std::string::const_iterator it = line.begin(); it != line.end(); it++)
+ {
+ switch (*it)
+ {
+ case '(':
+ pParens->push_back(it);
+ ignoreCloseParens = true;
+ break;
+ case ')':
+ if (pParens->empty() || *(pParens->back()) == ')')
+ {
+ if (!ignoreCloseParens)
+ pParens->push_back(it);
+ }
+ else
+ pParens->pop_back();
+ break;
+ case ' ':
+ case '\t':
+ break;
+ default:
+ ignoreCloseParens = true;
+ break;
+ }
+ }
+}
+
+FilePath resolveFilename(const FilePath& rootDir,
+ const std::string& filename)
+{
+ std::string result = filename;
+
+ // Remove quotes if necessary
+ if (result.size() > 2 &&
+ boost::algorithm::starts_with(result, "\"") &&
+ boost::algorithm::ends_with(result, "\""))
+ {
+ result.erase(result.size()-1, 1);
+ }
+
+ // Strip leading ./
+ if (boost::algorithm::starts_with(result, "./"))
+ result.erase(0, 2);
+
+ if (result.empty())
+ return FilePath();
+
+ // Check for existence of file
+ FilePath file = rootDir.complete(result);
+ if (file.exists() && !file.isDirectory())
+ return file;
+ else
+ return FilePath();
+}
+
+// TeX wraps lines hard at 79 characters. We use heuristics as described in
+// Sublime Text's TeX plugin to determine where these breaks are.
+void unwrapLines(std::vector<std::string>* pLines,
+ std::vector<size_t>* pLinesUnwrapped=NULL)
+{
+ static boost::regex regexLine("^l\\.(\\d+)\\s");
+ static boost::regex regexAssignment("^\\\\.*?=");
+
+ std::vector<std::string>::iterator pos = pLines->begin();
+
+ for ( ; pos != pLines->end(); pos++)
+ {
+ // The first line is always long, and not artificially wrapped
+ if (pos == pLines->begin())
+ continue;
+
+ if (pos->length() != 79)
+ continue;
+
+ // The **<filename> line may be long, but we don't care about it
+ if (beginsWith(*pos, "**"))
+ continue;
+
+ while (true)
+ {
+ std::vector<std::string>::iterator nextPos = pos + 1;
+ // No more lines to add
+ if (nextPos == pLines->end())
+ break;
+
+ if (nextPos->empty())
+ break;
+
+ // Underfull/Overfull terminator
+ if (*nextPos == " []")
+ break;
+
+ // Common prefixes
+ if (beginsWith(*nextPos, "File:", "Package:", "Document Class:"))
+ break;
+
+ // More prefixes
+ if (beginsWith(*nextPos, "LaTeX Warning:", "LaTeX Info:", "LaTeX2e <"))
+ break;
+
+ if (boost::regex_search(*nextPos, regexAssignment))
+ break;
+
+ if (boost::regex_search(*nextPos, regexLine))
+ break;
+
+ bool breakAfterAppend = nextPos->length() != 79;
+
+ pos->append(*nextPos);
+ // NOTE: Erase is a simple but inefficient way of handling this. Would
+ // be way faster to maintain an output iterator that points to the
+ // correct point in pLines, and when finished, truncate whatever
+ // elements come after the final position of the output iterator.
+ pLines->erase(nextPos, nextPos+1);
+ if (pLinesUnwrapped)
+ pLinesUnwrapped->push_back(1 + (pos - pLines->begin()));
+
+ if (breakAfterAppend)
+ break;
+ }
+ }
+}
+
+class FileStack : public boost::noncopyable
+{
+public:
+ explicit FileStack(FilePath rootDir) : rootDir_(rootDir)
+ {
+ }
+
+ FilePath currentFile()
+ {
+ return currentFile_;
+ }
+
+ void processLine(const std::string& line)
+ {
+ typedef std::vector<std::string::const_iterator> Iterators;
+ Iterators parens;
+ findUnmatchedParens(line, &parens);
+ for (Iterators::const_iterator itParen = parens.begin();
+ itParen != parens.end();
+ itParen++)
+ {
+ std::string::const_iterator it = *itParen;
+
+ if (*it == ')')
+ {
+ if (!fileStack_.empty())
+ {
+ fileStack_.pop_back();
+ updateCurrentFile();
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("File context stack underflow while parsing "
+ "TeX log");
+ }
+ }
+ else if (*it == '(')
+ {
+ std::string::const_iterator itFilenameEnd =
+ // case: no other ( on this line
+ (itParen + 1 == parens.end()) ? line.end() :
+ // case: space before next paren, eat it
+ *(*(itParen+1)-1) == ' ' ? *(itParen+1)-1 :
+ // case: other
+ *(itParen+1);
+
+ std::string filename = std::string(it+1, itFilenameEnd);
+ fileStack_.push_back(resolveFilename(rootDir_, filename));
+
+ updateCurrentFile();
+ }
+ else
+ BOOST_ASSERT(false);
+ }
+ }
+
+private:
+
+ void updateCurrentFile()
+ {
+ for (std::vector<FilePath>::reverse_iterator it = fileStack_.rbegin();
+ it != fileStack_.rend();
+ it++)
+ {
+ if (!it->empty())
+ {
+ currentFile_ = *it;
+ return;
+ }
+ }
+ currentFile_ = FilePath();
+ }
+
+ FilePath rootDir_;
+ FilePath currentFile_;
+ std::vector<FilePath> fileStack_;
+};
+
+FilePath texFilePath(const std::string& logPath, const FilePath& compileDir)
+{
+ // some tex compilers report file names with absolute paths and some
+ // report them relative to the compilation directory -- on Posix use
+ // realPath to get a clean full path back
+
+ FilePath path = compileDir.complete(logPath);
+ FilePath realPath;
+ Error error = core::system::realPath(path, &realPath);
+ if (error)
+ {
+ // log any error which isn't no such file or directory
+ if (error.code() !=
+ boost::system::errc::no_such_file_or_directory)
+ {
+ LOG_ERROR(error);
+ }
+
+ return path;
+ }
+ else
+ {
+ return realPath;
+ }
+}
+
+size_t calculateWrappedLine(const std::vector<size_t>& unwrappedLines,
+ size_t unwrappedLineNum)
+{
+ for (std::vector<size_t>::const_iterator it = unwrappedLines.begin();
+ it != unwrappedLines.end();
+ it++)
+ {
+ if (*it >= unwrappedLineNum)
+ {
+ return unwrappedLineNum + (it - unwrappedLines.begin());
+ }
+ }
+
+ return unwrappedLineNum + unwrappedLines.size();
+}
+
+} // anonymous namespace
+
+Error parseLatexLog(const FilePath& logFilePath, LogEntries* pLogEntries)
+{
+ static boost::regex regexOverUnderfullLines(" at lines (\\d+)--(\\d+)\\s*(?:\\[])?$");
+ static boost::regex regexWarning("^(?:.*?) Warning: (.+)");
+ static boost::regex regexWarningEnd(" input line (\\d+)\\.$");
+ static boost::regex regexLnn("^l\\.(\\d+)\\s");
+ static boost::regex regexCStyleError("^(.+):(\\d+):\\s(.+)$");
+
+ std::vector<std::string> lines;
+ Error error = readStringVectorFromFile(logFilePath, &lines, false);
+ if (error)
+ return error;
+
+ std::vector<size_t> linesUnwrapped;
+ unwrapLines(&lines, &linesUnwrapped);
+
+ FilePath rootDir = logFilePath.parent();
+ FileStack fileStack(rootDir);
+
+ for (std::vector<std::string>::const_iterator it = lines.begin();
+ it != lines.end();
+ it++)
+ {
+ const std::string& line = *it;
+ int logLineNum = (it - lines.begin()) + 1;
+
+ // We slurp overfull/underfull messages with no further processing
+ // (i.e. not manipulating the file stack)
+
+ if (beginsWith(line, "Overfull ", "Underfull "))
+ {
+ std::string msg = line;
+ int lineNum = -1;
+
+ // Parse lines, if present
+ boost::smatch overUnderfullLinesMatch;
+ if (boost::regex_search(line,
+ overUnderfullLinesMatch,
+ regexOverUnderfullLines))
+ {
+ lineNum = safe_convert::stringTo<int>(overUnderfullLinesMatch[1],
+ -1);
+ }
+
+ // Single line case
+ bool singleLine = boost::algorithm::ends_with(line, "[]");
+
+ if (singleLine)
+ {
+ msg.erase(line.size()-2, 2);
+ boost::algorithm::trim_right(msg);
+ }
+
+ pLogEntries->push_back(LogEntry(logFilePath,
+ calculateWrappedLine(linesUnwrapped,
+ logLineNum),
+ LogEntry::Box,
+ fileStack.currentFile(),
+ lineNum,
+ msg));
+
+ if (singleLine)
+ continue;
+
+ for (; it != lines.end(); it++)
+ {
+ // For multi-line case, we're looking for " []" on a line by itself
+ if (*it == " []")
+ break;
+ }
+
+ // The iterator would be incremented by the outer for loop, must not
+ // let it go past the end! (If we did get to the end, it would
+ // mean the log file was malformed, but we still can't crash in this
+ // situation.)
+ if (it == lines.end())
+ break;
+ else
+ continue;
+ }
+
+ fileStack.processLine(line);
+
+ // Now see if it's an error or warning
+
+ if (beginsWith(line, "! "))
+ {
+ std::string errorMsg = line.substr(2);
+ int lineNum = -1;
+
+ boost::smatch match;
+ for (it++; it != lines.end(); it++)
+ {
+ if (boost::regex_search(*it, match, regexLnn))
+ {
+ lineNum = safe_convert::stringTo<int>(match[1], -1);
+ break;
+ }
+ }
+
+ pLogEntries->push_back(LogEntry(logFilePath,
+ calculateWrappedLine(linesUnwrapped,
+ logLineNum),
+ LogEntry::Error,
+ fileStack.currentFile(),
+ lineNum,
+ errorMsg));
+
+ // The iterator would be incremented by the outer for loop, must not
+ // let it go past the end! (If we did get to the end, it would
+ // mean the log file was malformed, but we still can't crash in this
+ // situation.)
+ if (it == lines.end())
+ break;
+ else
+ continue;
+ }
+
+ boost::smatch warningMatch;
+ if (boost::regex_search(line, warningMatch, regexWarning))
+ {
+ std::string warningMsg = warningMatch[1];
+ int lineNum = -1;
+ while (true)
+ {
+ if (boost::algorithm::ends_with(warningMsg, "."))
+ {
+ boost::smatch warningEndMatch;
+ if (boost::regex_search(*it, warningEndMatch, regexWarningEnd))
+ {
+ lineNum = safe_convert::stringTo<int>(warningEndMatch[1], -1);
+ }
+ break;
+ }
+
+ if (++it == lines.end())
+ break;
+ warningMsg.append(*it);
+ }
+
+ pLogEntries->push_back(LogEntry(logFilePath,
+ calculateWrappedLine(linesUnwrapped,
+ logLineNum),
+ LogEntry::Warning,
+ fileStack.currentFile(),
+ lineNum,
+ warningMsg));
+
+ // The iterator would be incremented by the outer for loop, must not
+ // let it go past the end! (If we did get to the end, it would
+ // mean the log file was malformed, but we still can't crash in this
+ // situation.)
+ if (it == lines.end())
+ break;
+ else
+ continue;
+ }
+
+ boost::smatch cStyleErrorMatch;
+ if (boost::regex_search(line, cStyleErrorMatch, regexCStyleError))
+ {
+ FilePath cstyleFile = resolveFilename(rootDir, cStyleErrorMatch[1]);
+ if (cstyleFile.exists())
+ {
+ int lineNum = safe_convert::stringTo<int>(cStyleErrorMatch[2], -1);
+ pLogEntries->push_back(LogEntry(logFilePath,
+ calculateWrappedLine(linesUnwrapped,
+ logLineNum),
+ LogEntry::Error,
+ cstyleFile,
+ lineNum,
+ cStyleErrorMatch[3]));
+ }
+ }
+ }
+
+ return Success();
+}
+
+Error parseBibtexLog(const FilePath& logFilePath, LogEntries* pLogEntries)
+{
+ boost::regex re("^(.*)---line ([0-9]+) of file (.*)$");
+
+ // get the lines
+ std::vector<std::string> lines;
+ Error error = core::readStringVectorFromFile(logFilePath, &lines, false);
+ if (error)
+ return error;
+
+ // look for error messages
+ for (std::vector<std::string>::const_iterator it = lines.begin();
+ it != lines.end();
+ it++)
+ {
+ boost::smatch match;
+ if (regex_match(*it, match, re))
+ {
+ pLogEntries->push_back(
+ LogEntry(
+ logFilePath,
+ (it - lines.begin()) + 1,
+ LogEntry::Error,
+ texFilePath(match[3], logFilePath.parent()),
+ boost::lexical_cast<int>(match[2]),
+ match[1]));
+ }
+ }
+
+ return Success();
+}
+
+} // namespace tex
+} // namespace core
+
+
+
diff --git a/src/cpp/core/tex/TexMagicComment.cpp b/src/cpp/core/tex/TexMagicComment.cpp
new file mode 100644
index 0000000..93ed259
--- /dev/null
+++ b/src/cpp/core/tex/TexMagicComment.cpp
@@ -0,0 +1,69 @@
+/*
+ * TexMagicComment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/tex/TexMagicComment.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/regex.hpp>
+
+namespace core {
+namespace tex {
+
+Error parseMagicComments(const FilePath& texFile,
+ TexMagicComments* pComments)
+{
+ std::vector<std::string> lines;
+ Error error = core::readStringVectorFromFile(texFile, &lines);
+ if (error)
+ return error;
+
+ boost::regex mcRegex("%\\s*!(\\w+)\\s+(\\w+)\\s*=\\s*(.*)$");
+ BOOST_FOREACH(std::string line, lines)
+ {
+ boost::algorithm::trim(line);
+ if (line.empty())
+ {
+ continue;
+ }
+ else if (boost::algorithm::starts_with(line, "%"))
+ {
+ boost::smatch match;
+ if (regex_match(line, match, mcRegex))
+ {
+ pComments->push_back(
+ TexMagicComment(match[1], match[2], match[3]));
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return Success();
+}
+
+
+} // namespace tex
+} // namespace core
+
+
+
diff --git a/src/cpp/core/tex/TexSynctex.cpp b/src/cpp/core/tex/TexSynctex.cpp
new file mode 100644
index 0000000..33edf66
--- /dev/null
+++ b/src/cpp/core/tex/TexSynctex.cpp
@@ -0,0 +1,242 @@
+/*
+ * TexSynctex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/tex/TexSynctex.hpp>
+
+#include <iostream>
+
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+#include <core/system/System.hpp>
+
+#include "synctex/synctex_parser.h"
+
+namespace core {
+namespace tex {
+
+namespace {
+
+PdfLocation pdfLocationFromNode(synctex_node_t node)
+{
+ int page = ::synctex_node_page(node);
+
+ float x = ::synctex_node_box_visible_h(node);
+ float y = ::synctex_node_box_visible_v(node) -
+ ::synctex_node_box_visible_height(node);
+ float w = ::synctex_node_box_visible_width(node);
+ float h = ::synctex_node_box_visible_depth(node) +
+ ::synctex_node_box_visible_height(node);
+
+ return PdfLocation(page, x, y, w, h);
+}
+
+} // anonymous namespace
+
+std::ostream& operator << (std::ostream& stream, const SourceLocation& loc)
+{
+ stream << loc.file() << " [" << loc.line() << ", " << loc.column() << "]";
+ return stream;
+}
+
+std::ostream& operator << (std::ostream& stream, const PdfLocation& loc)
+{
+ stream << loc.page() << " {" <<
+ loc.x() << ", " <<
+ loc.y() << ", " <<
+ loc.width() << ", " <<
+ loc.height() << "}";
+
+ return stream;
+}
+
+struct Synctex::Impl
+{
+ Impl() : scanner(NULL) {}
+
+ FilePath pdfPath;
+ synctex_scanner_t scanner;
+};
+
+
+Synctex::Synctex()
+ : pImpl_(new Impl())
+{
+}
+
+Synctex::~Synctex()
+{
+ try
+ {
+ if (pImpl_->scanner != NULL)
+ {
+ ::synctex_scanner_free(pImpl_->scanner);
+ pImpl_->scanner = NULL;
+ }
+ }
+ catch(...)
+ {
+
+ }
+}
+
+
+bool Synctex::parse(const FilePath& pdfPath)
+{
+ using namespace core::string_utils;
+ pImpl_->pdfPath = pdfPath;
+ std::string path = utf8ToSystem(pdfPath.absolutePath());
+ std::string buildDir = utf8ToSystem(pdfPath.parent().absolutePath());
+
+ pImpl_->scanner = ::synctex_scanner_new_with_output_file(path.c_str(),
+ buildDir.c_str(),
+ 1);
+ return pImpl_->scanner != NULL;
+}
+
+PdfLocation Synctex::forwardSearch(const SourceLocation& location)
+{
+ PdfLocation pdfLocation;
+
+ // first determine the synctex local name for the input file
+ std::string name = synctexNameForInputFile(location.file());
+ if (name.empty())
+ return PdfLocation();
+
+ // run the query
+ int result = ::synctex_display_query(pImpl_->scanner,
+ name.c_str(),
+ location.line(),
+ location.column());
+ if (result > 0)
+ {
+ synctex_node_t node = synctex_next_result(pImpl_->scanner);
+ if (node != NULL)
+ pdfLocation = pdfLocationFromNode(node);
+ }
+
+ return pdfLocation;
+}
+
+SourceLocation Synctex::inverseSearch(const PdfLocation& location)
+{
+ SourceLocation sourceLocation;
+
+ int result = ::synctex_edit_query(pImpl_->scanner,
+ location.page(),
+ location.x(),
+ location.y());
+ if (result > 0)
+ {
+ synctex_node_t node = synctex_next_result(pImpl_->scanner);
+ if (node != NULL)
+ {
+ // get the filename then normalize it
+ std::string name = ::synctex_scanner_get_name(
+ pImpl_->scanner,
+ ::synctex_node_tag(node));
+ std::string adjustedName = normalizeSynctexName(name);
+
+ // might be relative or might be absolute, complete it against the
+ // pdf's parent directory to cover both cases
+ FilePath filePath = pImpl_->pdfPath.parent().complete(adjustedName);
+
+ // fully normalize
+ Error error = core::system::realPath(filePath, &filePath);
+ if (error)
+ LOG_ERROR(error);
+
+ // return source location
+ sourceLocation = SourceLocation(filePath,
+ ::synctex_node_line(node),
+ ::synctex_node_column(node));
+ }
+ }
+
+ return sourceLocation;
+}
+
+
+PdfLocation Synctex::topOfPageContent(int page)
+{
+ // get the sheet contents
+ synctex_node_t sheetNode = ::synctex_sheet_content(pImpl_->scanner, page);
+ if (sheetNode == NULL)
+ return PdfLocation();
+
+ // iterate through the nodes looking for a box
+ synctex_node_t node = sheetNode;
+ while((node = ::synctex_node_next(node)))
+ {
+ // look for the first hbox
+ synctex_node_type_t nodeType = ::synctex_node_type(node);
+ if (nodeType == synctex_node_type_hbox)
+ return pdfLocationFromNode(node);
+ }
+
+ // couldn't find a box, just return the sheet
+ return pdfLocationFromNode(sheetNode);
+}
+
+std::string Synctex::synctexNameForInputFile(const FilePath& inputFile)
+{
+ // get the base directory for the input file
+ FilePath parentPath = inputFile.parent();
+
+ // iterate through the known input files looking for a match
+ synctex_node_t node = ::synctex_scanner_input(pImpl_->scanner);
+ while (node != NULL)
+ {
+ // get tex name then normalize it for comparisons
+ std::string name = ::synctex_scanner_get_name(pImpl_->scanner,
+ ::synctex_node_tag(node));
+ std::string adjustedName = normalizeSynctexName(name);
+
+ // complete the name against the parent path -- if it is equal to
+ // the input file that that's the one we are looking for
+ FilePath synctexPath = parentPath.complete(adjustedName);
+ if (synctexPath.isEquivalentTo(inputFile))
+ return name;
+
+ // next node
+ node = ::synctex_node_sibling(node);
+ }
+
+ // not found
+ return std::string();
+}
+
+std::string normalizeSynctexName(const std::string& name)
+{
+ // trim it (on windows if it's the last available name it can include
+ // some additional whitespace at the end)
+ std::string adjustedName = boost::algorithm::trim_copy(name);
+
+ // if it starts with a ./ then trim that
+ if (boost::algorithm::starts_with(name, "./") && (name.length() > 2))
+ adjustedName = name.substr(2);
+
+ return adjustedName;
+}
+
+} // namespace tex
+} // namespace core
+
+
+
diff --git a/src/cpp/core/tex/synctex/CMakeLists.txt b/src/cpp/core/tex/synctex/CMakeLists.txt
new file mode 100644
index 0000000..b74dd11
--- /dev/null
+++ b/src/cpp/core/tex/synctex/CMakeLists.txt
@@ -0,0 +1,44 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (SYNCTEX)
+
+# include files
+file(GLOB_RECURSE SYNCTEX_HEADER_FILES "*.h*")
+
+# source files
+set(SYNCTEX_SOURCE_FILES
+ synctex_parser.c
+ synctex_parser_utils.c
+)
+
+# include directories
+set(SYNCTEX_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
+if(WIN32)
+ set(SYNCTEX_INCLUDE_DIRS ${SYNCTEX_INCLUDE_DIRS}
+ "${CMAKE_CURRENT_SOURCE_DIR}/../../zlib")
+endif()
+include_directories(${SYNCTEX_INCLUDE_DIRS})
+
+# define library
+add_library(rstudio-core-synctex STATIC
+ ${SYNCTEX_SOURCE_FILES}
+ ${SYNCTEX_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(rstudio-core-synctex
+
+)
+
diff --git a/src/cpp/core/tex/synctex/README.txt b/src/cpp/core/tex/synctex/README.txt
new file mode 100644
index 0000000..f8008e3
--- /dev/null
+++ b/src/cpp/core/tex/synctex/README.txt
@@ -0,0 +1,176 @@
+# Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr
+#
+# This file is part of the SyncTeX package.
+#
+# License:
+# --------
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE
+#
+# Except as contained in this notice, the name of the copyright holder
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from the copyright holder.
+#
+# Acknowledgments:
+# ----------------
+# The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
+# and significant help from XeTeX developer Jonathan Kew
+#
+# Nota Bene:
+# ----------
+# If you include or use a significant part of the synctex package into a software,
+# I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
+#
+# Version 1
+# Thu Jun 19 09:39:21 UTC 2008
+
+
+This file gives the .synctex file specifications.
+
+SyncTeX output file specifications, Version 2, Lun 31 mar 2008 13:50:31 UTC
+===========================================================================
+This is an EBNF file specification extended by ICU regex patterns
+(enclosed between 2 '/'s)
+
+The whole synctex file is made of various records gathered into four different sections:
+
+<synctex file> ::= <preamble> <contents> <postamble> <post scriptum>
+
+Each record is a sequence of text characters following an end of record mark or starting the file,
+and ending with an end of record mark.
+The first characters of a record will determine the type of the record.
+
+<end of record mark> ::= /\n/
+<end of record> ::= /[^\n]*/<end of record mark>
+
+The preamble:
+-------------
+
+<preamble> ::= <control record> <input record>* <informations> <forthcoming records>
+
+<control record> ::= /SyncTeX Version:/<version><end of record mark>
+<SyncTeX> ::=
+<version> ::= /1/
+
+<input record> ::= /Input:/<tag information><end of record mark>
+<tag information> ::= <tag>/:/<file name><end of record mark>
+<tag> ::= <integer>
+<file name> ::= /[^<end of record mark>]*/
+This is used to give a shortcut to filenames.
+
+<informations> ::= <output record>? <magnification record>? <unit record>? <x offset record>? <y offset record>?
+
+<output record> ::= /Output:/<output><end of record mark>
+<output> ::= /dvi|pdf|xdv|[0-9a-zA-Z]*/
+
+<magnification record> ::= /Magnification:/<magnification><end of record>
+<magnification> ::= <integer>
+This is the TeX magnification.
+
+<unit record> ::= /Unit:<unit><end of record>/
+<unit> ::= <integer>
+The SyncTeX unit is <unit> scaled point, 1 in general, 8192 when not given.
+
+<x offset record> ::= /X Offset:/<x offset><end of record>
+<x offset> ::= <integer>
+<y offset record> ::= /Y Offset:/<y offset><end of record>
+<y offset> ::= <integer>
+The offset or the origin of the system of coordinates from the top left point of the page.
+This defaults to 1in for both the vertical and horizontal offsets.
+Both offsets are given in this section in scaled point unit.
+
+<forthcoming records>: The preamble, like any other section may contain in the future any other kind of record,
+except the one starting the next section. In order to ensure some forwards compatibility,
+parsers should anticipate and parse unknown records: an unexpected record should be silently ignored by the parser.
+This means that this format is somehow open an more types of records can be added without breaking existing software.
+
+The preamble ends when a record is found that fits the following section.
+
+The contents:
+-------------
+<contents> ::= <contents record><sheet(1)><i line>*<sheet(2)><i line>*...<sheet(N)><i line>*
+
+<contents record>::=/Contents:/<byte offset><end of record>/
+<offset> ::= <byte offset>
+<sheet(n)> ::= <sheet(n) start><v content>*<sheet end>
+<sheet(n) start> ::= /{/<byte offset>/:/<the integer n><end of record>/
+<sheet end> ::= /}/<byte offset><end of record>
+
+<box content> ::= <vbox section>|<hbox section>|<void vbox record>|<void hbox record>
+<vbox section> ::= (<vbox start record><vbox content>*<vbox stop record>)|<box content>*
+<hbox content> ::= <glue record>|<kern record>|<math record>|<box content>
+<hbox section> ::= <hbox start record><box content>*<hbox stop record>
+
+<void vbox record> ::= /v/<link>/:/<point>/:/<size><end of record>
+<void hbox record> ::= /h/<link>/:/<point>/:/<size><end of record>
+<size> ::= <W>/,/<H>/,/<D>
+<W> ::= <integer>
+<H> ::= <integer>
+<D> ::= <integer>
+<link> ::= <tag>/,/<line>(/,/<column>)?
+<line> ::= <integer>
+<column> ::= <integer>
+
+<current record> ::= /x/<link>/:/<point><end of record>
+<kern record> ::= /k/<link>/:/<point>/:/<W><end of record>
+<glue record> ::= /g/<link>/:/<point><end of record>
+<math record> ::= /$/<link>/:/<point><end of record>
+
+The byte offset is an implicit anchor to navigate the synctex file from sheet to sheet.
+
+The postamble:
+--------------
+The postamble closes the file
+If there is no postamble, it means that the typesetting process did not end correctly.
+<postamble> ::= <postamble record><number of objects record>
+
+<postamble record> ::= /Postamble:/<byte offset><end of record>
+<number of objects record> ::= /Count:/<integer><end of record>
+
+The post scriptum:
+------------------
+The post scriptum contains material possibly added by 3rd parties.
+It allows to append some transformation (shift and magnify).
+Typically, one applies a dvi to pdf filter with offset options and magnification,
+then he appends the same options to the synctex file, for example
+
+ dvipdfmx -m 0.486 -x 9472573sp -y 13.3dd source.dvi
+ echo "X Offset:9472573" >> source.synctex
+ echo "Y Offset:13.3dd" >> source.synctex
+ echo "Magnification:0.486" >> source.synctex
+
+
+<post scriptum> ::= (<magnification line>|<x offset line>|<y offset line>)*
+<magnification line> ::= /Magnification:/<post magnification><end of line mark>
+<post magnification> ::= <unsigned decimal float>
+<x offset line> ::= /X Offset:/<post x offset><end of line mark>
+<post x offset> ::= <sign><unsigned decimal float><offset unit>
+<y offset> ::= <x offset>
+<sign> ::= /(+|-)?/
+<offset unit> ::= /(in|cm|mm|pt|bp|pc|sp|dd|cc|nd|nc)?/
+<end of line mark> ::= /[\n\r]*/
+<y offset line> ::= /Y Offset:/<post y offset><end of line mark>
+<post y offset> ::= <sign><unsigned decimal float><offset unit>
+
+This second information will override the offset and magnification previously available in the preamble section.
+All the numbers are encoded using the decimal representation with "C" locale.
+
diff --git a/src/cpp/core/tex/synctex/synctex_parser.c b/src/cpp/core/tex/synctex/synctex_parser.c
new file mode 100644
index 0000000..a7e6eaa
--- /dev/null
+++ b/src/cpp/core/tex/synctex/synctex_parser.c
@@ -0,0 +1,4429 @@
+/*
+Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
+
+This file is part of the SyncTeX package.
+
+Latest Revision: Tue Jun 14 08:23:30 UTC 2011
+
+Version: 1.17
+
+See synctex_parser_readme.txt for more details
+
+License:
+--------
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE
+
+Except as contained in this notice, the name of the copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization from the copyright holder.
+
+Acknowledgments:
+----------------
+The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
+and significant help from XeTeX developer Jonathan Kew
+
+Nota Bene:
+----------
+If you include or use a significant part of the synctex package into a software,
+I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
+
+Version 1
+Thu Jun 19 09:39:21 UTC 2008
+
+*/
+
+/* We assume that high level application like pdf viewers will want
+ * to embed this code as is. We assume that they also have locale.h and setlocale.
+ * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER,
+ * when building. You also have to create and customize synctex_parser_local.h to fit your system.
+ * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined.
+ * With this design, you should not need to edit this file. */
+
+# if defined(SYNCTEX_USE_LOCAL_HEADER)
+# include "synctex_parser_local.h"
+# else
+# define HAVE_LOCALE_H 1
+# define HAVE_SETLOCALE 1
+# if defined(_MSC_VER)
+# define SYNCTEX_INLINE __inline
+# else
+# define SYNCTEX_INLINE inline
+# endif
+# endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#if defined(HAVE_LOCALE_H)
+#include <locale.h>
+#endif
+
+/* The data is organized in a graph with multiple entries.
+ * The root object is a scanner, it is created with the contents on a synctex file.
+ * Each leaf of the tree is a synctex_node_t object.
+ * There are 3 subtrees, two of them sharing the same leaves.
+ * The first tree is the list of input records, where input file names are associated with tags.
+ * The second tree is the box tree as given by TeX when shipping pages out.
+ * First level objects are sheets, containing boxes, glues, kerns...
+ * The third tree allows to browse leaves according to tag and line.
+ */
+
+#include "synctex_parser.h"
+#include "synctex_parser_utils.h"
+
+/* These are the possible extensions of the synctex file */
+const char * synctex_suffix = ".synctex";
+const char * synctex_suffix_gz = ".gz";
+
+/* each synctex node has a class */
+typedef struct __synctex_class_t _synctex_class_t;
+typedef _synctex_class_t * synctex_class_t;
+
+
+/* synctex_node_t is a pointer to a node
+ * _synctex_node is the target of the synctex_node_t pointer
+ * It is a pseudo object oriented program.
+ * class is a pointer to the class object the node belongs to.
+ * implementation is meant to contain the private data of the node
+ * basically, there are 2 kinds of information: navigation information and
+ * synctex information. Both will depend on the type of the node,
+ * thus different nodes will have different private data.
+ * There is no inheritancy overhead.
+ */
+typedef union _synctex_info_t {
+ int INT;
+ char * PTR;
+} synctex_info_t;
+
+# if defined(SYNCTEX_USE_CHARINDEX)
+# define SYNCTEX_DECLARE_CHARINDEX synctex_charindex_t char_index
+# define SYNCTEX_CHARINDEX(NODE) (NODE->char_index)
+# define SYNCTEX_PRINT_CHARINDEX printf("#%i\n",SYNCTEX_CHARINDEX(node))
+# define SYNCTEX_DECLARE_CHAR_OFFSET synctex_charindex_t charindex_offset
+# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION) NODE->char_index = (synctex_charindex_t)(scanner->charindex_offset+SYNCTEX_CUR-SYNCTEX_START+(CORRECTION));
+# else
+# define SYNCTEX_DECLARE_CHARINDEX
+# define SYNCTEX_CHARINDEX(NODE) 0
+# define SYNCTEX_PRINT_CHARINDEX printf("\n")
+# define SYNCTEX_DECLARE_CHAR_OFFSET synctex_charindex_t charindex_offset
+# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION)
+# endif
+
+struct _synctex_node {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t * implementation;
+};
+
+/* Each node of the tree, except the scanner itself belongs to a class.
+ * The class object is just a struct declaring the owning scanner
+ * This is a pointer to the scanner as root of the tree.
+ * The type is used to identify the kind of node.
+ * The class declares pointers to a creator and a destructor method.
+ * The log and display fields are used to log and display the node.
+ * display will also display the child, sibling and parent sibling.
+ * parent, child and sibling are used to navigate the tree,
+ * from TeX box hierarchy point of view.
+ * The friend field points to a method which allows to navigate from friend to friend.
+ * A friend is a node with very close tag and line numbers.
+ * Finally, the info field point to a method giving the private node info offset.
+ */
+
+typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t);
+typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t);
+
+struct __synctex_class_t {
+ synctex_scanner_t scanner;
+ int type;
+ synctex_node_t (*new)(synctex_scanner_t scanner);
+ void (*free)(synctex_node_t);
+ void (*log)(synctex_node_t);
+ void (*display)(synctex_node_t);
+ _synctex_node_getter_t parent;
+ _synctex_node_getter_t child;
+ _synctex_node_getter_t sibling;
+ _synctex_node_getter_t friend;
+ _synctex_node_getter_t next_hbox;
+ _synctex_info_getter_t info;
+};
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Abstract OBJECTS and METHODS
+# endif
+
+/* These macros are shortcuts
+ * This macro checks if a message can be sent.
+ */
+# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\
+ (NULL!=((((NODE)->class))->SELECTOR))
+
+/* This macro is some kind of objc_msg_send.
+ * It takes care of sending the proper message if possible.
+ */
+# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if (NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\
+ (*((((NODE)->class))->SELECTOR))(NODE);\
+ }
+
+/* read only safe getter
+ */
+# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL))
+
+/* read/write getter
+ */
+# define SYNCTEX_GETTER(NODE,SELECTOR)\
+ ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE)))
+
+# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free);
+
+/* Parent getter and setter
+ */
+# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent)
+# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if (NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\
+ SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\
+ }
+
+/* Child getter and setter
+ */
+# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child)
+# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if (NODE && NEW_CHILD){\
+ SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\
+ SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\
+ }
+
+/* Sibling getter and setter
+ */
+# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling)
+# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if (NODE && NEW_SIBLING) {\
+ SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\
+ if (SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\
+ SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\
+ }\
+ }
+/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar.
+ * This is a first filter on the nodes that avoids testing all of them.
+ * Friends are used mainly in forward synchronization aka from source to output.
+ */
+# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend)
+# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if (NODE && NEW_FRIEND){\
+ SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\
+ }
+
+/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other.
+ * Navigation starts with the deeper boxes.
+ */
+# define SYNCTEX_NEXT_hbox(NODE) SYNCTEX_GET(NODE,next_hbox)
+# define SYNCTEX_SET_NEXT_hbox(NODE,NEXT_HBOX) if (NODE && NEXT_HBOX){\
+ SYNCTEX_GETTER(NODE,next_hbox)[0]=NEXT_HBOX;\
+ }
+
+void _synctex_free_node(synctex_node_t node);
+void _synctex_free_leaf(synctex_node_t node);
+
+/* A node is meant to own its child and sibling.
+ * It is not owned by its parent, unless it is its first child.
+ * This destructor is for all nodes with children.
+ */
+void _synctex_free_node(synctex_node_t node) {
+ if (node) {
+ (*((node->class)->sibling))(node);
+ SYNCTEX_FREE(SYNCTEX_SIBLING(node));
+ SYNCTEX_FREE(SYNCTEX_CHILD(node));
+ free(node);
+ }
+ return;
+}
+
+/* A node is meant to own its child and sibling.
+ * It is not owned by its parent, unless it is its first child.
+ * This destructor is for nodes with no child.
+ * The first sheet is onwned by the scanner.
+ */
+void _synctex_free_leaf(synctex_node_t node) {
+ if (node) {
+ SYNCTEX_FREE(SYNCTEX_SIBLING(node));
+ free(node);
+ }
+ return;
+}
+# ifdef __SYNCTEX_WORK__
+# include "/usr/include/zlib.h"
+# else
+# include <zlib.h>
+# endif
+
+/* The synctex scanner is the root object.
+ * Is is initialized with the contents of a text file or a gzipped file.
+ * The buffer_? are first used to parse the text.
+ */
+struct __synctex_scanner_t {
+ gzFile file; /* The (possibly compressed) file */
+ SYNCTEX_DECLARE_CHAR_OFFSET;
+ char * buffer_cur; /* current location in the buffer */
+ char * buffer_start; /* start of the buffer */
+ char * buffer_end; /* end of the buffer */
+ char * output_fmt; /* dvi or pdf, not yet used */
+ char * output; /* the output name used to create the scanner */
+ char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */
+ int version; /* 1, not yet used */
+ struct {
+ unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */
+ unsigned reserved:sizeof(unsigned)-1; /* alignment */
+ } flags;
+ int pre_magnification; /* magnification from the synctex preamble */
+ int pre_unit; /* unit from the synctex preamble */
+ int pre_x_offset; /* X offste from the synctex preamble */
+ int pre_y_offset; /* Y offset from the synctex preamble */
+ int count; /* Number of records, from the synctex postamble */
+ float unit; /* real unit, from synctex preamble or post scriptum */
+ float x_offset; /* X offset, from synctex preamble or post scriptum */
+ float y_offset; /* Y Offset, from synctex preamble or post scriptum */
+ synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */
+ synctex_node_t input; /* The first input node, its siblings are the other input nodes */
+ int number_of_lists; /* The number of friend lists */
+ synctex_node_t * lists_of_friends;/* The friend lists */
+ _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */
+};
+
+/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts
+ */
+# define SYNCTEX_CUR (scanner->buffer_cur)
+# define SYNCTEX_START (scanner->buffer_start)
+# define SYNCTEX_END (scanner->buffer_end)
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark OBJECTS, their creators and destructors.
+# endif
+
+/* Here, we define the indices for the different informations.
+ * They are used to declare the size of the implementation.
+ * For example, if one object uses SYNCTEX_HORIZ_IDX is its size,
+ * then its info will contain a tag, line, column, horiz but no width nor height nor depth
+ */
+
+/* The sheet is a first level node.
+ * It has no parent (the parent is the scanner itself)
+ * Its sibling points to another sheet.
+ * Its child points to its first child, in general a box.
+ * A sheet node contains only one synctex information: the page.
+ * This is the 1 based page index as given by TeX.
+ */
+/* The next macros are used to access the node info
+ * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node
+ * SYNCTEX_INFO(node)[index] is the information at index
+ * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX]
+ */
+# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE))
+# define SYNCTEX_PAGE_IDX 0
+# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT
+
+/* This macro defines implementation offsets
+ * It is only used for pointer values
+ */
+# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\
+synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\
+synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\
+ return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\
+}
+SYNCTEX_MAKE_GET(_synctex_implementation_0,0)
+SYNCTEX_MAKE_GET(_synctex_implementation_1,1)
+SYNCTEX_MAKE_GET(_synctex_implementation_2,2)
+SYNCTEX_MAKE_GET(_synctex_implementation_3,3)
+SYNCTEX_MAKE_GET(_synctex_implementation_4,4)
+SYNCTEX_MAKE_GET(_synctex_implementation_5,5)
+
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box,
+ * SYNCTEX_PAGE_IDX */
+} synctex_node_sheet_t;
+
+synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner);
+void _synctex_display_sheet(synctex_node_t node);
+void _synctex_log_sheet(synctex_node_t node);
+
+static _synctex_class_t synctex_class_sheet = {
+ NULL, /* No scanner yet */
+ synctex_node_type_sheet, /* Node type */
+ &_synctex_new_sheet, /* creator */
+ &_synctex_free_node, /* destructor */
+ &_synctex_log_sheet, /* log */
+ &_synctex_display_sheet, /* display */
+ NULL, /* No parent */
+ &_synctex_implementation_0, /* child */
+ &_synctex_implementation_1, /* sibling */
+ NULL, /* No friend */
+ &_synctex_implementation_2, /* Next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3 /* info */
+};
+
+/* sheet node creator */
+
+#define DEFINE_synctex_new_NODE(NAME)\
+synctex_node_t _synctex_new_##NAME(synctex_scanner_t scanner) {\
+ if (scanner) {\
+ synctex_node_t node = _synctex_malloc(sizeof(synctex_node_##NAME##_t));\
+ if (node) {\
+ SYNCTEX_IMPLEMENT_CHARINDEX(node,0);\
+ ++SYNCTEX_CUR;\
+ node->class = scanner->class+synctex_node_type_##NAME;\
+ }\
+ return node;\
+ }\
+ return NULL;\
+}
+DEFINE_synctex_new_NODE(sheet)
+
+/* A box node contains navigation and synctex information
+ * There are different kind of boxes.
+ * Only horizontal boxes are treated differently because of their visible size.
+ */
+# define SYNCTEX_TAG_IDX 0
+# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1)
+# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1)
+# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1)
+# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1)
+# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1)
+# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1)
+# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1)
+/* the corresponding info accessors */
+# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT
+# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT
+# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT
+# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT
+# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT
+# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT
+# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT
+# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT
+# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE)))
+# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE)))
+# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE)))
+
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box,
+ * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN,
+ * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */
+} synctex_node_vbox_t;
+
+synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner);
+void _synctex_log_vbox(synctex_node_t node);
+void _synctex_display_vbox(synctex_node_t node);
+
+/* These are static class objects, each scanner will make a copy of them and setup the scanner field.
+ */
+static _synctex_class_t synctex_class_vbox = {
+ NULL, /* No scanner yet */
+ synctex_node_type_vbox, /* Node type */
+ &_synctex_new_vbox, /* creator */
+ &_synctex_free_node, /* destructor */
+ &_synctex_log_vbox, /* log */
+ &_synctex_display_vbox, /* display */
+ &_synctex_implementation_0, /* parent */
+ &_synctex_implementation_1, /* child */
+ &_synctex_implementation_2, /* sibling */
+ &_synctex_implementation_3, /* friend */
+ &_synctex_implementation_4, /* next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_5
+};
+
+/* vertical box node creator */
+DEFINE_synctex_new_NODE(vbox)
+
+/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness.
+ * They also contain an average of the line numbers of the containing nodes. */
+# define SYNCTEX_MEAN_LINE_IDX (SYNCTEX_DEPTH_IDX+1)
+# define SYNCTEX_NODE_WEIGHT_IDX (SYNCTEX_MEAN_LINE_IDX+1)
+# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_NODE_WEIGHT_IDX+1)
+# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1)
+# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1)
+# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1)
+# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1)
+/* the corresponding info accessors */
+# define SYNCTEX_MEAN_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_MEAN_LINE_IDX].INT
+# define SYNCTEX_NODE_WEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NODE_WEIGHT_IDX].INT
+# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT
+# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT
+# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT
+# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT
+# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT
+# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE)))
+# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE)))
+# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE)))
+
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box,
+ * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN,
+ * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH,
+ * SYNCTEX_MEAN_LINE,SYNCTEX_NODE_WEIGHT,
+ * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/
+} synctex_node_hbox_t;
+
+synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner);
+void _synctex_display_hbox(synctex_node_t node);
+void _synctex_log_hbox(synctex_node_t node);
+
+
+static _synctex_class_t synctex_class_hbox = {
+ NULL, /* No scanner yet */
+ synctex_node_type_hbox, /* Node type */
+ &_synctex_new_hbox, /* creator */
+ &_synctex_free_node, /* destructor */
+ &_synctex_log_hbox, /* log */
+ &_synctex_display_hbox, /* display */
+ &_synctex_implementation_0, /* parent */
+ &_synctex_implementation_1, /* child */
+ &_synctex_implementation_2, /* sibling */
+ &_synctex_implementation_3, /* friend */
+ &_synctex_implementation_4, /* next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_5
+};
+
+/* horizontal box node creator */
+DEFINE_synctex_new_NODE(hbox)
+
+/* This void box node implementation is either horizontal or vertical
+ * It does not contain a child field.
+ */
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend,
+ * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN,
+ * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/
+} synctex_node_void_vbox_t;
+
+synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner);
+void _synctex_log_void_box(synctex_node_t node);
+void _synctex_display_void_vbox(synctex_node_t node);
+
+static _synctex_class_t synctex_class_void_vbox = {
+ NULL, /* No scanner yet */
+ synctex_node_type_void_vbox,/* Node type */
+ &_synctex_new_void_vbox, /* creator */
+ &_synctex_free_node, /* destructor */
+ &_synctex_log_void_box, /* log */
+ &_synctex_display_void_vbox,/* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+
+/* vertical void box node creator */
+DEFINE_synctex_new_NODE(void_vbox)
+
+typedef synctex_node_void_vbox_t synctex_node_void_hbox_t;
+
+synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner);
+void _synctex_display_void_hbox(synctex_node_t node);
+
+static _synctex_class_t synctex_class_void_hbox = {
+ NULL, /* No scanner yet */
+ synctex_node_type_void_hbox,/* Node type */
+ &_synctex_new_void_hbox, /* creator */
+ &_synctex_free_node, /* destructor */
+ &_synctex_log_void_box, /* log */
+ &_synctex_display_void_hbox,/* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+
+/* horizontal void box node creator */
+DEFINE_synctex_new_NODE(void_hbox)
+
+/* The medium nodes correspond to kern, glue, penalty and math nodes.
+ * In LuaTeX, the size of the nodes may have changed. */
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend,
+ * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN,
+ * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */
+} synctex_node_medium_t;
+
+#define SYNCTEX_IS_BOX(NODE)\
+ ((NODE->class->type == synctex_node_type_vbox)\
+ || (NODE->class->type == synctex_node_type_void_vbox)\
+ || (NODE->class->type == synctex_node_type_hbox)\
+ || (NODE->class->type == synctex_node_type_void_hbox))
+
+#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE))
+
+void _synctex_log_medium_node(synctex_node_t node);
+
+typedef synctex_node_medium_t synctex_node_math_t;
+
+/* math node creator */
+synctex_node_t _synctex_new_math(synctex_scanner_t scanner);
+void _synctex_display_math(synctex_node_t node);
+
+static _synctex_class_t synctex_class_math = {
+ NULL, /* No scanner yet */
+ synctex_node_type_math, /* Node type */
+ &_synctex_new_math, /* creator */
+ &_synctex_free_leaf, /* destructor */
+ &_synctex_log_medium_node, /* log */
+ &_synctex_display_math, /* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+
+DEFINE_synctex_new_NODE(math)
+
+typedef synctex_node_medium_t synctex_node_kern_t;
+
+/* kern node creator */
+synctex_node_t _synctex_new_kern(synctex_scanner_t scanner);
+void _synctex_display_kern(synctex_node_t node);
+
+static _synctex_class_t synctex_class_kern = {
+ NULL, /* No scanner yet */
+ synctex_node_type_kern, /* Node type */
+ &_synctex_new_kern, /* creator */
+ &_synctex_free_leaf, /* destructor */
+ &_synctex_log_medium_node, /* log */
+ &_synctex_display_kern, /* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+
+DEFINE_synctex_new_NODE(kern)
+
+/* The small nodes correspond to glue and boundary nodes. */
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend,
+ * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN,
+ * SYNCTEX_HORIZ,SYNCTEX_VERT */
+} synctex_node_small_t;
+
+void _synctex_log_small_node(synctex_node_t node);
+
+/* glue node creator */
+typedef synctex_node_small_t synctex_node_glue_t;
+synctex_node_t _synctex_new_glue(synctex_scanner_t scanner);
+void _synctex_display_glue(synctex_node_t node);
+
+static _synctex_class_t synctex_class_glue = {
+ NULL, /* No scanner yet */
+ synctex_node_type_glue, /* Node type */
+ &_synctex_new_glue, /* creator */
+ &_synctex_free_leaf, /* destructor */
+ &_synctex_log_medium_node, /* log */
+ &_synctex_display_glue, /* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+DEFINE_synctex_new_NODE(glue)
+
+/* boundary node creator */
+typedef synctex_node_small_t synctex_node_boundary_t;
+synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner);
+void _synctex_display_boundary(synctex_node_t node);
+
+static _synctex_class_t synctex_class_boundary = {
+ NULL, /* No scanner yet */
+ synctex_node_type_boundary, /* Node type */
+ &_synctex_new_boundary, /* creator */
+ &_synctex_free_leaf, /* destructor */
+ &_synctex_log_small_node, /* log */
+ &_synctex_display_boundary, /* display */
+ &_synctex_implementation_0, /* parent */
+ NULL, /* No child */
+ &_synctex_implementation_1, /* sibling */
+ &_synctex_implementation_2, /* friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_3
+};
+
+DEFINE_synctex_new_NODE(boundary)
+
+# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1)
+# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR
+
+/* Input nodes only know about their sibling, which is another input node.
+ * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/
+typedef struct {
+ SYNCTEX_DECLARE_CHARINDEX;
+ synctex_class_t class;
+ synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling,
+ * SYNCTEX_TAG,SYNCTEX_NAME */
+} synctex_input_t;
+
+synctex_node_t _synctex_new_input(synctex_scanner_t scanner);
+void _synctex_free_input(synctex_node_t node);
+void _synctex_display_input(synctex_node_t node);
+void _synctex_log_input(synctex_node_t node);
+
+static _synctex_class_t synctex_class_input = {
+ NULL, /* No scanner yet */
+ synctex_node_type_input, /* Node type */
+ &_synctex_new_input, /* creator */
+ &_synctex_free_input, /* destructor */
+ &_synctex_log_input, /* log */
+ &_synctex_display_input, /* display */
+ NULL, /* No parent */
+ NULL, /* No child */
+ &_synctex_implementation_0, /* sibling */
+ NULL, /* No friend */
+ NULL, /* No next hbox */
+ (_synctex_info_getter_t)&_synctex_implementation_1
+};
+
+# define SYNCTEX_INPUT_MARK "Input:"
+
+synctex_node_t _synctex_new_input(synctex_scanner_t scanner) {
+ if (scanner) {
+ synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t));
+ if (node) {
+ SYNCTEX_IMPLEMENT_CHARINDEX(node,strlen(SYNCTEX_INPUT_MARK));
+ node->class = scanner->class+synctex_node_type_input;
+ }
+ return node;
+ }
+ return NULL;
+}
+void _synctex_free_input(synctex_node_t node){
+ if (node) {
+ SYNCTEX_FREE(SYNCTEX_SIBLING(node));
+ free(SYNCTEX_NAME(node));
+ free(node);
+ }
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Navigation
+# endif
+synctex_node_t synctex_node_parent(synctex_node_t node)
+{
+ return SYNCTEX_PARENT(node);
+}
+synctex_node_t synctex_node_sheet(synctex_node_t node)
+{
+ while(node && node->class->type != synctex_node_type_sheet) {
+ node = SYNCTEX_PARENT(node);
+ }
+ /* exit the while loop either when node is NULL or node is a sheet */
+ return node;
+}
+synctex_node_t synctex_node_child(synctex_node_t node)
+{
+ return SYNCTEX_CHILD(node);
+}
+synctex_node_t synctex_node_sibling(synctex_node_t node)
+{
+ return SYNCTEX_SIBLING(node);
+}
+synctex_node_t synctex_node_next(synctex_node_t node) {
+ if (SYNCTEX_CHILD(node)) {
+ return SYNCTEX_CHILD(node);
+ }
+sibling:
+ if (SYNCTEX_SIBLING(node)) {
+ return SYNCTEX_SIBLING(node);
+ }
+ if ((node = SYNCTEX_PARENT(node))) {
+ if (node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */
+ return NULL;
+ }
+ goto sibling;
+ }
+ return NULL;
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark CLASS
+# endif
+
+/* Public node accessor: the type */
+synctex_node_type_t synctex_node_type(synctex_node_t node) {
+ if (node) {
+ return (((node)->class))->type;
+ }
+ return synctex_node_type_error;
+}
+
+/* Public node accessor: the human readable type */
+const char * synctex_node_isa(synctex_node_t node) {
+static const char * isa[synctex_node_number_of_types] =
+ {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"};
+ return isa[synctex_node_type(node)];
+}
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark SYNCTEX_LOG
+# endif
+
+# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log)
+
+/* Public node logger */
+void synctex_node_log(synctex_node_t node) {
+ SYNCTEX_LOG(node);
+}
+
+void _synctex_log_input(synctex_node_t node) {
+ if (node) {
+ printf("%s:%i,%s",synctex_node_isa(node),SYNCTEX_TAG(node),SYNCTEX_NAME(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_log_sheet(synctex_node_t node) {
+ if (node) {
+ printf("%s:%i",synctex_node_isa(node),SYNCTEX_PAGE(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p",(void *)SYNCTEX_FRIEND(node));
+ printf(" SYNCTEX_NEXT_hbox:%p\n",(void *)SYNCTEX_NEXT_hbox(node));
+ }
+}
+
+void _synctex_log_small_node(synctex_node_t node) {
+ if (node) {
+ printf("%s:%i,%i:%i,%i",
+ synctex_node_isa(node),
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node));
+ }
+}
+
+void _synctex_log_medium_node(synctex_node_t node) {
+ if (node) {
+ printf("%s:%i,%i:%i,%i:%i",
+ synctex_node_isa(node),
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node));
+ }
+}
+
+void _synctex_log_void_box(synctex_node_t node) {
+ if (node) {
+ printf("%s",synctex_node_isa(node));
+ printf(":%i",SYNCTEX_TAG(node));
+ printf(",%i",SYNCTEX_LINE(node));
+ printf(",%i",0);
+ printf(":%i",SYNCTEX_HORIZ(node));
+ printf(",%i",SYNCTEX_VERT(node));
+ printf(":%i",SYNCTEX_WIDTH(node));
+ printf(",%i",SYNCTEX_HEIGHT(node));
+ printf(",%i",SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node));
+ }
+}
+
+void _synctex_log_vbox(synctex_node_t node) {
+ if (node) {
+ printf("%s",synctex_node_isa(node));
+ printf(":%i",SYNCTEX_TAG(node));
+ printf(",%i",SYNCTEX_LINE(node));
+ printf(",%i",0);
+ printf(":%i",SYNCTEX_HORIZ(node));
+ printf(",%i",SYNCTEX_VERT(node));
+ printf(":%i",SYNCTEX_WIDTH(node));
+ printf(",%i",SYNCTEX_HEIGHT(node));
+ printf(",%i",SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p",(void *)SYNCTEX_FRIEND(node));
+ printf(" SYNCTEX_NEXT_hbox:%p\n",(void *)SYNCTEX_NEXT_hbox(node));
+ }
+}
+
+void _synctex_log_hbox(synctex_node_t node) {
+ if (node) {
+ printf("%s",synctex_node_isa(node));
+ printf(":%i",SYNCTEX_TAG(node));
+ printf(",%i~%i*%i",SYNCTEX_LINE(node),SYNCTEX_MEAN_LINE(node),SYNCTEX_NODE_WEIGHT(node));
+ printf(",%i",0);
+ printf(":%i",SYNCTEX_HORIZ(node));
+ printf(",%i",SYNCTEX_VERT(node));
+ printf(":%i",SYNCTEX_WIDTH(node));
+ printf(",%i",SYNCTEX_HEIGHT(node));
+ printf(",%i",SYNCTEX_DEPTH(node));
+ printf("/%i",SYNCTEX_HORIZ_V(node));
+ printf(",%i",SYNCTEX_VERT_V(node));
+ printf(":%i",SYNCTEX_WIDTH_V(node));
+ printf(",%i",SYNCTEX_HEIGHT_V(node));
+ printf(",%i",SYNCTEX_DEPTH_V(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ printf("SELF:%p",(void *)node);
+ printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node));
+ printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node));
+ printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node));
+ printf(" SYNCTEX_FRIEND:%p",(void *)SYNCTEX_FRIEND(node));
+ printf(" SYNCTEX_NEXT_hbox:%p\n",(void *)SYNCTEX_NEXT_hbox(node));
+ }
+}
+
+# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display)
+
+void synctex_node_display(synctex_node_t node) {
+ SYNCTEX_DISPLAY(node);
+}
+
+void _synctex_display_input(synctex_node_t node) {
+ if (node) {
+ printf("....Input:%i:%s",
+ SYNCTEX_TAG(node),
+ SYNCTEX_NAME(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_sheet(synctex_node_t node) {
+ if (node) {
+ printf("....{%i",SYNCTEX_PAGE(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_CHILD(node));
+ printf("....}\n");
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_vbox(synctex_node_t node) {
+ if (node) {
+ printf("....[%i,%i:%i,%i:%i,%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node),
+ SYNCTEX_HEIGHT(node),
+ SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_CHILD(node));
+ printf("....]\n");
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_hbox(synctex_node_t node) {
+ if (node) {
+ printf("....(%i,%i~%i*%i:%i,%i:%i,%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_MEAN_LINE(node),
+ SYNCTEX_NODE_WEIGHT(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node),
+ SYNCTEX_HEIGHT(node),
+ SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_CHILD(node));
+ printf("....)\n");
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_void_vbox(synctex_node_t node) {
+ if (node) {
+ printf("....v%i,%i;%i,%i:%i,%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node),
+ SYNCTEX_HEIGHT(node),
+ SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_void_hbox(synctex_node_t node) {
+ if (node) {
+ printf("....h%i,%i:%i,%i:%i,%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node),
+ SYNCTEX_HEIGHT(node),
+ SYNCTEX_DEPTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_glue(synctex_node_t node) {
+ if (node) {
+ printf("....glue:%i,%i:%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_math(synctex_node_t node) {
+ if (node) {
+ printf("....math:%i,%i:%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_kern(synctex_node_t node) {
+ if (node) {
+ printf("....kern:%i,%i:%i,%i:%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node),
+ SYNCTEX_WIDTH(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+void _synctex_display_boundary(synctex_node_t node) {
+ if (node) {
+ printf("....boundary:%i,%i:%i,%i",
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node),
+ SYNCTEX_HORIZ(node),
+ SYNCTEX_VERT(node));
+ SYNCTEX_PRINT_CHARINDEX;
+ SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node));
+ }
+}
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark SCANNER
+# endif
+
+/* Here are gathered all the possible status that the next scanning functions will return.
+ * All these functions return a status, and pass their result through pointers.
+ * Negative values correspond to errors.
+ * The management of the buffer is causing some significant overhead.
+ * Every function that may access the buffer returns a status related to the buffer and file state.
+ * status >= SYNCTEX_STATUS_OK means the function worked as expected
+ * status < SYNCTEX_STATUS_OK means the function did not work as expected
+ * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse.
+ * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material.
+ * status<SYNCTEX_STATUS_EOF means an error
+ */
+typedef int synctex_status_t;
+/* When the end of the synctex file has been reached: */
+# define SYNCTEX_STATUS_EOF 0
+/* When the function could not return the value it was asked for: */
+# define SYNCTEX_STATUS_NOT_OK (SYNCTEX_STATUS_EOF+1)
+/* When the function returns the value it was asked for: */
+# define SYNCTEX_STATUS_OK (SYNCTEX_STATUS_NOT_OK+1)
+/* Generic error: */
+# define SYNCTEX_STATUS_ERROR -1
+/* Parameter error: */
+# define SYNCTEX_STATUS_BAD_ARGUMENT -2
+
+# define SYNCTEX_FILE (scanner->file)
+
+/* Actually, the minimum buffer size is driven by integer and float parsing.
+ * ±0.123456789e123
+ */
+# define SYNCTEX_BUFFER_MIN_SIZE 16
+# define SYNCTEX_BUFFER_SIZE 32768
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Prototypes
+# endif
+synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr);
+synctex_status_t _synctex_next_line(synctex_scanner_t scanner);
+synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string);
+synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref);
+synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref);
+synctex_status_t _synctex_scan_input(synctex_scanner_t scanner);
+synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner);
+synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref);
+synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner);
+int _synctex_scan_postamble(synctex_scanner_t scanner);
+synctex_status_t _synctex_setup_visible_box(synctex_node_t box);
+synctex_status_t _synctex_hbox_setup_visible(synctex_node_t node,int h, int v);
+synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent);
+synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner);
+synctex_status_t _synctex_scan_content(synctex_scanner_t scanner);
+int synctex_scanner_pre_x_offset(synctex_scanner_t scanner);
+int synctex_scanner_pre_y_offset(synctex_scanner_t scanner);
+const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner);
+int _synctex_node_is_box(synctex_node_t node);
+int _synctex_bail(void);
+
+/* Try to ensure that the buffer contains at least size bytes.
+ * Passing a huge size argument means the whole buffer length.
+ * Passing a null size argument means return the available buffer length, without reading the file.
+ * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL,
+ * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned.
+ * The value returned in size_ptr is the number of bytes now available in the buffer.
+ * This is a nonnegative integer, it may take the value 0.
+ * It is the responsibility of the caller to test whether this size is conforming to its needs.
+ * Negative values may return in case of error, actually
+ * when there was an error reading the synctex file. */
+synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) {
+ size_t available = 0;
+ if (NULL == scanner || NULL == size_ptr) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+# define size (* size_ptr)
+ if (size>SYNCTEX_BUFFER_SIZE){
+ size = SYNCTEX_BUFFER_SIZE;
+ }
+ available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */
+ if (size<=available) {
+ /* There are already sufficiently many characters in the buffer */
+ size = available;
+ return SYNCTEX_STATUS_OK;
+ }
+ if (SYNCTEX_FILE) {
+ /* Copy the remaining part of the buffer to the beginning,
+ * then read the next part of the file */
+ int already_read = 0;
+# if defined(SYNCTEX_USE_CHARINDEX)
+ scanner->charindex_offset += SYNCTEX_CUR - SYNCTEX_START;
+# endif
+ if (available) {
+ memmove(SYNCTEX_START, SYNCTEX_CUR, available);
+ }
+ SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */
+ /* Fill the buffer up to its end */
+ already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available);
+ if (already_read>0) {
+ /* We assume that 0<already_read<=SYNCTEX_BUFFER_SIZE - available, such that
+ * SYNCTEX_CUR + already_read = SYNCTEX_START + available + already_read <= SYNCTEX_START + SYNCTEX_BUFFER_SIZE */
+ SYNCTEX_END = SYNCTEX_CUR + already_read;
+ /* If the end of the file was reached, all the required SYNCTEX_BUFFER_SIZE - available
+ * may not be filled with values from the file.
+ * In that case, the buffer should stop properly after already_read characters. */
+ * SYNCTEX_END = '\0';
+ SYNCTEX_CUR = SYNCTEX_START;
+ size = SYNCTEX_END - SYNCTEX_CUR; /* == old available + already_read*/
+ return SYNCTEX_STATUS_OK; /* May be available is less than size, the caller will have to test. */
+ } else if (0>already_read) {
+ /* There is a possible error in reading the file */
+ int errnum = 0;
+ const char * error_string = gzerror(SYNCTEX_FILE, &errnum);
+ if (Z_ERRNO == errnum) {
+ /* There is an error in zlib caused by the file system */
+ _synctex_error("gzread error from the file system (%i)",errno);
+ return SYNCTEX_STATUS_ERROR;
+ } else if (errnum) {
+ _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string);
+ return SYNCTEX_STATUS_ERROR;
+ }
+ }
+ /* Nothing was read, we are at the end of the file. */
+ gzclose(SYNCTEX_FILE);
+ SYNCTEX_FILE = NULL;
+ SYNCTEX_END = SYNCTEX_CUR;
+ SYNCTEX_CUR = SYNCTEX_START;
+ * SYNCTEX_END = '\0';/* Terminate the string properly.*/
+ size = SYNCTEX_END - SYNCTEX_CUR;
+ return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */
+ }
+ /* We cannot enlarge the buffer because the end of the file was reached. */
+ size = available;
+ return SYNCTEX_STATUS_EOF;
+# undef size
+}
+
+/* Used when parsing the synctex file.
+ * Advance to the next character starting a line.
+ * Actually, only '\n' is recognized as end of line marker.
+ * On normal completion, the returned value is the number of unparsed characters available in the buffer.
+ * In general, it is a positive value, 0 meaning that the end of file was reached.
+ * -1 is returned in case of error, actually because there was an error while feeding the buffer.
+ * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any.
+ * J. Laurens: Sat May 10 07:52:31 UTC 2008
+ */
+synctex_status_t _synctex_next_line(synctex_scanner_t scanner) {
+ synctex_status_t status = SYNCTEX_STATUS_OK;
+ size_t available = 0;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+infinite_loop:
+ while(SYNCTEX_CUR<SYNCTEX_END) {
+ if (*SYNCTEX_CUR == '\n') {
+ ++SYNCTEX_CUR;
+ available = 1;
+ return _synctex_buffer_get_available_size(scanner, &available);
+ }
+ ++SYNCTEX_CUR;
+ }
+ /* Here, we have SYNCTEX_CUR == SYNCTEX_END, such that the next call to _synctex_buffer_get_available_size
+ * will read another bunch of synctex file. Little by little, we advance to the end of the file. */
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner, &available);
+ if (status<=0) {
+ return status;
+ }
+ goto infinite_loop;
+}
+
+/* Scan the given string.
+ * Both scanner and the_string must not be NULL, and the_string must not be 0 length.
+ * SYNCTEX_STATUS_OK is returned if the string is found,
+ * SYNCTEX_STATUS_EOF is returned when the EOF is reached,
+ * SYNCTEX_STATUS_NOT_OK is returned is the string is not found,
+ * an error status is returned otherwise.
+ * This is a critical method because buffering renders things more difficult.
+ * The given string might be as long as the maximum size_t value.
+ * As side effect, the buffer state may have changed if the given argument string can't fit into the buffer.
+ */
+synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string) {
+ size_t tested_len = 0; /* the number of characters at the beginning of the_string that match */
+ size_t remaining_len = 0; /* the number of remaining characters of the_string that should match */
+ size_t available = 0;
+ synctex_status_t status = 0;
+ if (NULL == scanner || NULL == the_string) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ remaining_len = strlen(the_string); /* All the_string should match */
+ if (0 == remaining_len) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ /* How many characters available in the buffer? */
+ available = remaining_len;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status;
+ }
+ /* Maybe we have less characters than expected because the buffer is too small. */
+ if (available>=remaining_len) {
+ /* The buffer is sufficiently big to hold the expected number of characters. */
+ if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) {
+ return SYNCTEX_STATUS_NOT_OK;
+ }
+return_OK:
+ /* Advance SYNCTEX_CUR to the next character after the_string. */
+ SYNCTEX_CUR += remaining_len;
+ return SYNCTEX_STATUS_OK;
+ } else if (strncmp((char *)SYNCTEX_CUR,the_string,available)) {
+ /* No need to go further, this is not the expected string in the buffer. */
+ return SYNCTEX_STATUS_NOT_OK;
+ } else if (SYNCTEX_FILE) {
+ /* The buffer was too small to contain remaining_len characters.
+ * We have to cut the string into pieces. */
+ z_off_t offset = 0L;
+ /* the first part of the string is found, advance the_string to the next untested character. */
+ the_string += available;
+ /* update the remaining length and the parsed length. */
+ remaining_len -= available;
+ tested_len += available;
+ SYNCTEX_CUR += available; /* We validate the tested characters. */
+ if (0 == remaining_len) {
+ /* Nothing left to test, we have found the given string, we return the length. */
+ return tested_len;
+ }
+ /* We also have to record the current state of the file cursor because
+ * if the_string does not match, all this should be a totally blank operation,
+ * for which the file and buffer states should not be modified at all.
+ * In fact, the states of the buffer before and after this function are in general different
+ * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR
+ * can be safely discarded. */
+ offset = gztell(SYNCTEX_FILE);
+ /* offset now corresponds to the first character of the file that was not buffered. */
+ available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */
+ /* available now corresponds to the number of chars that where already buffered and
+ * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered
+ * because the buffer contents is completely replaced by _synctex_buffer_get_available_size.
+ * They were buffered from offset-len location in the file. */
+ offset -= available;
+more_characters:
+ /* There is still some work to be done, so read another bunch of file.
+ * This is the second call to _synctex_buffer_get_available_size,
+ * which means that the actual contents of the buffer will be discarded.
+ * We will definitely have to recover the previous state in case we do not find the expected string. */
+ available = remaining_len;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status; /* This is an error, no need to go further. */
+ }
+ if (available==0) {
+ /* Missing characters: recover the initial state of the file and return. */
+return_NOT_OK:
+ if (offset != gzseek(SYNCTEX_FILE,offset,SEEK_SET)) {
+ /* This is a critical error, we could not recover the previous state. */
+ _synctex_error("can't seek file");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ /* Next time we are asked to fill the buffer,
+ * we will read a complete bunch of text from the file. */
+ SYNCTEX_CUR = SYNCTEX_END;
+ return SYNCTEX_STATUS_NOT_OK;
+ }
+ if (available<remaining_len) {
+ /* We'll have to loop one more time. */
+ if (strncmp((char *)SYNCTEX_CUR,the_string,available)) {
+ /* This is not the expected string, recover the previous state and return. */
+ goto return_NOT_OK;
+ }
+ /* Advance the_string to the first untested character. */
+ the_string += available;
+ /* update the remaining length and the parsed length. */
+ remaining_len -= available;
+ tested_len += available;
+ SYNCTEX_CUR += available; /* We validate the tested characters. */
+ if (0 == remaining_len) {
+ /* Nothing left to test, we have found the given string. */
+ return SYNCTEX_STATUS_OK;
+ }
+ goto more_characters;
+ }
+ /* This is the last step. */
+ if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) {
+ /* This is not the expected string, recover the previous state and return. */
+ goto return_NOT_OK;
+ }
+ goto return_OK;
+ } else {
+ /* The buffer can't contain the given string argument, and the EOF was reached */
+ return SYNCTEX_STATUS_EOF;
+ }
+}
+
+/* Used when parsing the synctex file.
+ * Decode an integer.
+ * First, field separators, namely ':' and ',' characters are skipped
+ * The returned value is negative if there is an unrecoverable error.
+ * It is SYNCTEX_STATUS_NOT_OK if an integer could not be parsed, for example
+ * if the characters at the current cursor position are not digits or
+ * if the end of the file has been reached.
+ * It is SYNCTEX_STATUS_OK if an int has been successfully parsed.
+ * The given scanner argument must not be NULL, on the contrary, value_ref may be NULL.
+ */
+synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref) {
+ char * ptr = NULL;
+ char * end = NULL;
+ int result = 0;
+ size_t available = 0;
+ synctex_status_t status = 0;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ available = SYNCTEX_BUFFER_MIN_SIZE;
+ status = _synctex_buffer_get_available_size(scanner, &available);
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status;/* Forward error. */
+ }
+ if (available==0) {
+ return SYNCTEX_STATUS_EOF;/* it is the end of file. */
+ }
+ ptr = SYNCTEX_CUR;
+ if (*ptr==':' || *ptr==',') {
+ ++ptr;
+ --available;
+ if (available==0) {
+ return SYNCTEX_STATUS_NOT_OK;/* It is not possible to scan an int */
+ }
+ }
+ result = (int)strtol(ptr, &end, 10);
+ if (end>ptr) {
+ SYNCTEX_CUR = end;
+ if (value_ref) {
+ * value_ref = result;
+ }
+ return SYNCTEX_STATUS_OK;/* Successfully scanned an int */
+ }
+ return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */
+}
+
+/* The purpose of this function is to read a string.
+ * A string is an array of characters from the current parser location
+ * and before the next '\n' character.
+ * If a string was properly decoded, it is returned in value_ref and
+ * the cursor points to the new line marker.
+ * The returned string was alloced on the heap, the caller is the owner and
+ * is responsible to free it in due time.
+ * If no string is parsed, * value_ref is undefined.
+ * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX.
+ * If you just want to blindly parse the file up to the end of the current line,
+ * use _synctex_next_line instead.
+ * On return, the scanner cursor is unchanged if a string could not be scanned or
+ * points to the terminating '\n' character otherwise. As a consequence,
+ * _synctex_next_line is necessary after.
+ * If either scanner or value_ref is NULL, it is considered as an error and
+ * SYNCTEX_STATUS_BAD_ARGUMENT is returned.
+ */
+synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) {
+ char * end = NULL;
+ size_t current_size = 0;
+ size_t new_size = 0;
+ size_t len = 0;/* The number of bytes to copy */
+ size_t available = 0;
+ synctex_status_t status = 0;
+ if (NULL == scanner || NULL == value_ref) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ /* The buffer must at least contain one character: the '\n' end of line marker */
+ if (SYNCTEX_CUR>=SYNCTEX_END) {
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status < 0) {
+ return status;
+ }
+ if (0 == available) {
+ return SYNCTEX_STATUS_EOF;
+ }
+ }
+ /* Now we are sure that there is at least one available character, either because
+ * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */
+ /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */
+ end = SYNCTEX_CUR;
+ * value_ref = NULL;/* Initialize, it will be realloc'ed */
+ /* We scan all the characters up to the next '\n' */
+next_character:
+ if (end<SYNCTEX_END) {
+ if (*end == '\n') {
+ /* OK, we found where to stop */
+ len = end - SYNCTEX_CUR;
+ if (current_size>UINT_MAX-len-1) {
+ /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX.
+ * We return the missing amount of memory.
+ * This will never occur in practice. */
+ return UINT_MAX-len-1 - current_size;
+ }
+ new_size = current_size+len;
+ /* We have current_size+len+1<=UINT_MAX
+ * or equivalently new_size<UINT_MAX,
+ * where we have assumed that len<UINT_MAX */
+ if ((* value_ref = realloc(* value_ref,new_size+1)) != NULL) {
+ if (memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) {
+ (* value_ref)[new_size]='\0'; /* Terminate the string */
+ SYNCTEX_CUR += len;/* Advance to the terminating '\n' */
+ return SYNCTEX_STATUS_OK;
+ }
+ free(* value_ref);
+ * value_ref = NULL;
+ _synctex_error("could not copy memory (1).");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ _synctex_error("could not allocate memory (1).");
+ return SYNCTEX_STATUS_ERROR;
+ } else {
+ ++end;
+ goto next_character;
+ }
+ } else {
+ /* end == SYNCTEX_END */
+ len = SYNCTEX_END - SYNCTEX_CUR;
+ if (current_size>UINT_MAX-len-1) {
+ /* We have reached the limit. */
+ _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1));
+ return SYNCTEX_STATUS_ERROR;
+ }
+ new_size = current_size+len;
+ if ((* value_ref = realloc(* value_ref,new_size+1)) != NULL) {
+ if (memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) {
+ (* value_ref)[new_size]='\0'; /* Terminate the string */
+ SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */
+ return SYNCTEX_STATUS_OK;
+ }
+ free(* value_ref);
+ * value_ref = NULL;
+ _synctex_error("could not copy memory (2).");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ /* Huge memory problem */
+ _synctex_error("could not allocate memory (2).");
+ return SYNCTEX_STATUS_ERROR;
+ }
+}
+
+/* Used when parsing the synctex file.
+ * Read an Input record.
+ */
+synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) {
+ synctex_status_t status = 0;
+ size_t available = 0;
+ synctex_node_t input = NULL;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ status = _synctex_match_string(scanner,SYNCTEX_INPUT_MARK);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ /* Create a node */
+ input = _synctex_new_input(scanner);
+ if (NULL == input) {
+ _synctex_error("could not create an input node.");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ /* Decode the synctag */
+ status = _synctex_decode_int(scanner,&(SYNCTEX_TAG(input)));
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("bad format of input node.");
+ SYNCTEX_FREE(input);
+ return status;
+ }
+ /* The next character is a field separator, we expect one character in the buffer. */
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner, &available);
+ if (status<=SYNCTEX_STATUS_ERROR) {
+ return status;
+ }
+ if (0 == available) {
+ return SYNCTEX_STATUS_EOF;
+ }
+ /* We can now safely advance to the next character, stepping over the field separator. */
+ ++SYNCTEX_CUR;
+ --available;
+ /* Then we scan the file name */
+ status = _synctex_decode_string(scanner,&(SYNCTEX_NAME(input)));
+ if (status<SYNCTEX_STATUS_OK) {
+ SYNCTEX_FREE(input);
+ return status;
+ }
+ /* Prepend this input node to the input linked list of the scanner */
+ SYNCTEX_SET_SIBLING(input,scanner->input);
+ scanner->input = input;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(input);
+# endif
+ return _synctex_next_line(scanner);/* read the line termination character, if any */
+ /* Now, set up the path */
+}
+
+typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *);
+
+synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder);
+
+/* Used when parsing the synctex file.
+ * Read one of the settings.
+ * On normal completion, returns SYNCTEX_STATUS_OK.
+ * On error, returns SYNCTEX_STATUS_ERROR.
+ * Both arguments must not be NULL.
+ * On return, the scanner points to the next character after the decoded object whatever it is.
+ * It is the responsibility of the caller to prepare the scanner for the next line.
+ */
+synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) {
+ synctex_status_t status = 0;
+ if (NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+not_found:
+ status = _synctex_match_string(scanner,name);
+ if (status<SYNCTEX_STATUS_NOT_OK) {
+ return status;
+ } else if (status == SYNCTEX_STATUS_NOT_OK) {
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ goto not_found;
+ }
+ /* A line is found, scan the value */
+ return (*decoder)(scanner,value_ref);
+}
+
+/* Used when parsing the synctex file.
+ * Read the preamble.
+ */
+synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner) {
+ synctex_status_t status = 0;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ status = _synctex_scan_named(scanner,"SyncTeX Version:",&(scanner->version),(synctex_decoder_t)&_synctex_decode_int);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ /* Read all the input records */
+ do {
+ status = _synctex_scan_input(scanner);
+ if (status<SYNCTEX_STATUS_NOT_OK) {
+ return status;
+ }
+ } while(status == SYNCTEX_STATUS_OK);
+ /* the loop exits when status == SYNCTEX_STATUS_NOT_OK */
+ /* Now read all the required settings. */
+ status = _synctex_scan_named(scanner,"Output:",&(scanner->output_fmt),(synctex_decoder_t)&_synctex_decode_string);
+ if (status<SYNCTEX_STATUS_NOT_OK) {
+ return status;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_scan_named(scanner,"Magnification:",&(scanner->pre_magnification),(synctex_decoder_t)&_synctex_decode_int);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_scan_named(scanner,"Unit:",&(scanner->pre_unit),(synctex_decoder_t)&_synctex_decode_int);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_scan_named(scanner,"X Offset:",&(scanner->pre_x_offset),(synctex_decoder_t)&_synctex_decode_int);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_scan_named(scanner,"Y Offset:",&(scanner->pre_y_offset),(synctex_decoder_t)&_synctex_decode_int);
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ return _synctex_next_line(scanner);
+}
+
+/* parse a float with a dimension */
+synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref) {
+ synctex_status_t status = 0;
+ char * endptr = NULL;
+ float f = 0;
+#ifdef HAVE_SETLOCALE
+ char * loc = setlocale(LC_NUMERIC, NULL);
+#endif
+ size_t available = 0;
+ if (NULL == scanner || NULL == value_ref) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ available = SYNCTEX_BUFFER_MIN_SIZE;
+ status = _synctex_buffer_get_available_size(scanner, &available);
+ if (status<SYNCTEX_STATUS_EOF) {
+ _synctex_error("problem with float.");
+ return status;
+ }
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_NUMERIC, "C");
+#endif
+ f = strtod(SYNCTEX_CUR,&endptr);
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_NUMERIC, loc);
+#endif
+ if (endptr == SYNCTEX_CUR) {
+ _synctex_error("a float was expected.");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ SYNCTEX_CUR = endptr;
+ if ((status = _synctex_match_string(scanner,"in")) >= SYNCTEX_STATUS_OK) {
+ f *= 72.27f*65536;
+ } else if (status<SYNCTEX_STATUS_EOF) {
+report_unit_error:
+ _synctex_error("problem with unit.");
+ return status;
+ } else if ((status = _synctex_match_string(scanner,"cm")) >= SYNCTEX_STATUS_OK) {
+ f *= 72.27f*65536/2.54f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) {
+ f *= 72.27f*65536/25.4f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) {
+ f *= 65536.0f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) {
+ f *= 72.27f/72*65536.0f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) {
+ f *= 12.0*65536.0f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) {
+ f *= 1.0f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) {
+ f *= 1238.0f/1157*65536.0f;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) {
+ f *= 14856.0f/1157*65536;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) {
+ f *= 685.0f/642*65536;
+ } else if (status<0) {
+ goto report_unit_error;
+ } else if ((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) {
+ f *= 1370.0f/107*65536;
+ } else if (status<0) {
+ goto report_unit_error;
+ }
+ *value_ref = f;
+ return SYNCTEX_STATUS_OK;
+}
+
+/* parse the post scriptum
+ * SYNCTEX_STATUS_OK is returned on completion
+ * a negative error is returned otherwise */
+synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) {
+ synctex_status_t status = 0;
+ char * endptr = NULL;
+#ifdef HAVE_SETLOCALE
+ char * loc = setlocale(LC_NUMERIC, NULL);
+#endif
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ /* Scan the file until a post scriptum line is found */
+post_scriptum_not_found:
+ status = _synctex_match_string(scanner,"Post scriptum:");
+ if (status<SYNCTEX_STATUS_NOT_OK) {
+ return status;
+ }
+ if (status == SYNCTEX_STATUS_NOT_OK) {
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status;
+ } else if (status<SYNCTEX_STATUS_OK) {
+ return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */
+ }
+ goto post_scriptum_not_found;
+ }
+ /* We found the name, advance to the next line. */
+next_line:
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status;
+ } else if (status<SYNCTEX_STATUS_OK) {
+ return SYNCTEX_STATUS_OK;/* The EOF is found, we have properly scanned the file */
+ }
+ /* Scanning the information */
+ status = _synctex_match_string(scanner,"Magnification:");
+ if (status == SYNCTEX_STATUS_OK ) {
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_NUMERIC, "C");
+#endif
+ scanner->unit = strtod(SYNCTEX_CUR,&endptr);
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_NUMERIC, loc);
+#endif
+ if (endptr == SYNCTEX_CUR) {
+ _synctex_error("bad magnification in the post scriptum, a float was expected.");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ if (scanner->unit<=0) {
+ _synctex_error("bad magnification in the post scriptum, a positive float was expected.");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ SYNCTEX_CUR = endptr;
+ goto next_line;
+ }
+ if (status<SYNCTEX_STATUS_EOF){
+report_record_problem:
+ _synctex_error("Problem reading the Post Scriptum records");
+ return status; /* echo the error. */
+ }
+ status = _synctex_match_string(scanner,"X Offset:");
+ if (status == SYNCTEX_STATUS_OK) {
+ status = _synctex_scan_float_and_dimension(scanner, &(scanner->x_offset));
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("problem with X offset in the Post Scriptum.");
+ return status;
+ }
+ goto next_line;
+ } else if (status<SYNCTEX_STATUS_EOF){
+ goto report_record_problem;
+ }
+ status = _synctex_match_string(scanner,"Y Offset:");
+ if (status==SYNCTEX_STATUS_OK) {
+ status = _synctex_scan_float_and_dimension(scanner, &(scanner->y_offset));
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("problem with Y offset in the Post Scriptum.");
+ return status;
+ }
+ goto next_line;
+ } else if (status<SYNCTEX_STATUS_EOF){
+ goto report_record_problem;
+ }
+ goto next_line;
+}
+
+/* SYNCTEX_STATUS_OK is returned if the postamble is read
+ * SYNCTEX_STATUS_NOT_OK is returned if the postamble is not at the current location
+ * a negative error otherwise
+ * The postamble comprises the post scriptum section.
+ */
+int _synctex_scan_postamble(synctex_scanner_t scanner) {
+ int status = 0;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ status = _synctex_match_string(scanner,"Postamble:");
+ if (status < SYNCTEX_STATUS_OK) {
+ return status;
+ }
+count_again:
+ status = _synctex_next_line(scanner);
+ if (status < SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ status = _synctex_scan_named(scanner,"Count:",&(scanner->count),(synctex_decoder_t)&_synctex_decode_int);
+ if (status < SYNCTEX_STATUS_EOF) {
+ return status; /* forward the error */
+ } else if (status < SYNCTEX_STATUS_OK) { /* No Count record found */
+ status = _synctex_next_line(scanner); /* Advance one more line */
+ if (status<SYNCTEX_STATUS_OK) {
+ return status;
+ }
+ goto count_again;
+ }
+ /* Now we scan the last part of the SyncTeX file: the Post Scriptum section. */
+ return _synctex_scan_post_scriptum(scanner);
+}
+
+/* Horizontal boxes also have visible size.
+ * Visible size are bigger than real size.
+ * For example 0 width boxes may contain text.
+ * At creation time, the visible size is set to the values of the real size.
+ */
+synctex_status_t _synctex_setup_visible_box(synctex_node_t box) {
+ if (box) {
+ switch(box->class->type) {
+ case synctex_node_type_hbox:
+ if (SYNCTEX_INFO(box) != NULL) {
+ SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box);
+ SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box);
+ SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box);
+ SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box);
+ SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box);
+ return SYNCTEX_STATUS_OK;
+ }
+ return SYNCTEX_STATUS_ERROR;
+ }
+ }
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+}
+
+/* This method is sent to an horizontal box to setup the visible size
+ * Some box have 0 width but do contain text material.
+ * With this method, one can enlarge the box to contain the given point (h,v).
+ */
+synctex_status_t _synctex_hbox_setup_visible(synctex_node_t node,int h, int v) {
+# ifdef __DARWIN_UNIX03
+# pragma unused(v)
+# endif
+ int itsBtm, itsTop;
+ if (NULL == node || node->class->type != synctex_node_type_hbox) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ if (SYNCTEX_WIDTH_V(node)<0) {
+ itsBtm = SYNCTEX_HORIZ_V(node);
+ itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node);
+ if (h<itsBtm) {
+ SYNCTEX_HORIZ_V(node) = h;
+ SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - itsTop;
+ } else if (h>itsTop) {
+ SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h;
+ }
+ } else {
+ itsBtm = SYNCTEX_HORIZ_V(node);
+ itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node);
+ if (h<itsBtm) {
+ SYNCTEX_HORIZ_V(node) = h;
+ SYNCTEX_WIDTH_V(node) = itsTop - SYNCTEX_HORIZ_V(node);
+ } else if (h>itsTop) {
+ SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node);
+ }
+ }
+ return SYNCTEX_STATUS_OK;
+}
+
+/* Here are the control characters that strat each line of the synctex output file.
+ * Their values define the meaning of the line.
+ */
+# define SYNCTEX_CHAR_BEGIN_SHEET '{'
+# define SYNCTEX_CHAR_END_SHEET '}'
+# define SYNCTEX_CHAR_BEGIN_VBOX '['
+# define SYNCTEX_CHAR_END_VBOX ']'
+# define SYNCTEX_CHAR_BEGIN_HBOX '('
+# define SYNCTEX_CHAR_END_HBOX ')'
+# define SYNCTEX_CHAR_ANCHOR '!'
+# define SYNCTEX_CHAR_VOID_VBOX 'v'
+# define SYNCTEX_CHAR_VOID_HBOX 'h'
+# define SYNCTEX_CHAR_KERN 'k'
+# define SYNCTEX_CHAR_GLUE 'g'
+# define SYNCTEX_CHAR_MATH '$'
+# define SYNCTEX_CHAR_BOUNDARY 'x'
+
+# define SYNCTEX_RETURN(STATUS) return STATUS;
+
+/* Used when parsing the synctex file. A '{' character has just been parsed.
+ * The purpose is to gobble everything until the closing '}'.
+ * Actually only one nesting depth has been observed when using the clip option
+ * of \includegraphics option. Here we use arbitrary level of depth.
+ */
+synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner) {
+ unsigned int depth = 0;
+deeper:
+ ++depth;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end of nested sheet (1).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+scan_next_line:
+ if (SYNCTEX_CUR<SYNCTEX_END) {
+ if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_SHEET) {
+ ++SYNCTEX_CUR;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end of nested sheet (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ if (--depth>0) {
+ goto scan_next_line;
+ } else {
+ SYNCTEX_RETURN(SYNCTEX_STATUS_OK);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) {
+ ++SYNCTEX_CUR;
+ goto deeper;
+
+ } else if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end of nested sheet (3).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ }
+ _synctex_error("Unexpected end of nested sheet (4).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+}
+
+/* Used when parsing the synctex file.
+ * The sheet argument is a newly created sheet node that will hold the contents.
+ * Something is returned in case of error.
+ */
+synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t sheet) {
+ synctex_node_t parent = sheet;
+ synctex_node_t child = NULL;
+ synctex_node_t sibling = NULL;
+ synctex_node_t box = sheet;
+ int friend_index = 0;
+ synctex_info_t * info = NULL;
+ synctex_status_t status = 0;
+ size_t available = 0;
+ if ((NULL == scanner) || (NULL == sheet)) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ /* We MUST start with a box, so at this level, the unique possibility is '[', '(' or "}". */
+prepare_loop:
+ if (SYNCTEX_CUR<SYNCTEX_END) {
+ if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_VBOX) {
+scan_vbox:
+ if ((child = _synctex_new_vbox(scanner)) && (info = SYNCTEX_INFO(child))) {
+# define SYNCTEX_DECODE_FAILED(WHAT) \
+ (_synctex_decode_int(scanner,&(info[WHAT].INT))<SYNCTEX_STATUS_OK)
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad vbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ parent = child;
+ child = NULL;
+ goto child_loop;/* next created node will be a child */
+ } else {
+ _synctex_error("Can't create vbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_HBOX) {
+scan_hbox:
+ if ((child = _synctex_new_hbox(scanner)) && (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_setup_visible_box(child)<SYNCTEX_STATUS_OK
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad hbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child));
+ SYNCTEX_SET_CHILD(parent,child);
+ parent = child;
+ child = NULL;
+ goto child_loop;/* next created node will be a child */
+ } else {
+ _synctex_error("Can't create hbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_SHEET) {
+scan_teehs:
+ ++SYNCTEX_CUR;
+ if (NULL == parent || parent->class->type != synctex_node_type_sheet
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end of sheet.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+# if SYNCTEX_VERBOSE
+ synctex_node_log(parent);
+# endif
+ SYNCTEX_RETURN(SYNCTEX_STATUS_OK);
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) {
+ /* Addendum to version 1.10 to manage nested sheets */
+ ++SYNCTEX_CUR;
+ if (_synctex_scan_nested_sheet(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected nested sheet.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto prepare_loop;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_ANCHOR) {
+scan_anchor:
+ ++SYNCTEX_CUR;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Missing anchor.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto prepare_loop;
+ } else {
+ /* _synctex_error("Ignored record %c\n",*SYNCTEX_CUR); */
+ ++SYNCTEX_CUR;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto prepare_loop;
+ }
+ } else {
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status<SYNCTEX_STATUS_OK && available>0){
+ _synctex_error("Uncomplete sheet(0)");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto prepare_loop;
+ }
+ _synctex_bail();
+/* The child loop means that we go down one level, when we just created a box node,
+ * the next node created is a child of this box. */
+child_loop:
+ if (SYNCTEX_CUR<SYNCTEX_END) {
+ if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_VBOX) {
+ goto scan_vbox;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_VBOX) {
+scan_xobv:
+ ++SYNCTEX_CUR;
+ if (NULL != parent && parent->class->type == synctex_node_type_vbox) {
+ #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\
+ friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\
+ SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\
+ (scanner->lists_of_friends)[friend_index] = NODE;
+ if (NULL == SYNCTEX_CHILD(parent)) {
+ /* only void boxes are friends */
+ SYNCTEX_UPDATE_BOX_FRIEND(parent);
+ }
+ child = parent;
+ parent = SYNCTEX_PARENT(child);
+ } else {
+ _synctex_error("Unexpected end of vbox, ignored.");
+ }
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Uncomplete sheet.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_HBOX) {
+ goto scan_hbox;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_HBOX) {
+scan_xobh:
+ ++SYNCTEX_CUR;
+ if ((parent) && parent->class->type == synctex_node_type_hbox) {
+ /* Update the mean line number */
+ synctex_node_t node = SYNCTEX_CHILD(parent);
+ if (node) {
+ unsigned int node_weight = 0;
+ unsigned int cumulated_line_numbers = 0;
+ do {
+ if (synctex_node_type(node)==synctex_node_type_hbox) {
+ if (SYNCTEX_NODE_WEIGHT(node)) {
+ node_weight += SYNCTEX_NODE_WEIGHT(node);
+ cumulated_line_numbers += SYNCTEX_MEAN_LINE(node)*SYNCTEX_NODE_WEIGHT(node);
+ } else {
+ ++node_weight;
+ cumulated_line_numbers += SYNCTEX_MEAN_LINE(node);
+ }
+ } else {
+ ++node_weight;
+ cumulated_line_numbers += SYNCTEX_LINE(node);
+ }
+ } while ((node = SYNCTEX_SIBLING(node)));
+ SYNCTEX_MEAN_LINE(parent)=(cumulated_line_numbers + node_weight/2)/node_weight;
+ SYNCTEX_NODE_WEIGHT(parent)=node_weight;
+ } else {
+ SYNCTEX_MEAN_LINE(parent)=SYNCTEX_LINE(parent);
+ SYNCTEX_NODE_WEIGHT(parent)=1;
+ }
+ if (NULL == child) {
+ /* Only boxes with no children are friends,
+ * boxes with children are indirectly friends through one of their contained nodes. */
+ SYNCTEX_UPDATE_BOX_FRIEND(parent);
+ }
+ /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */
+ SYNCTEX_SET_NEXT_hbox(box,parent);
+ box = parent;
+ child = parent;
+ parent = SYNCTEX_PARENT(child);
+ } else {
+ _synctex_error("Unexpected end of hbox, ignored.");
+ }
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Uncomplete sheet.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_VBOX) {
+ if (NULL != (child = _synctex_new_void_vbox(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad void vbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ #define SYNCTEX_UPDATE_FRIEND(NODE)\
+ friend_index = (info[SYNCTEX_TAG_IDX].INT+info[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\
+ SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\
+ (scanner->lists_of_friends)[friend_index] = NODE;
+ SYNCTEX_UPDATE_FRIEND(child);
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create vbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_HBOX) {
+ if (NULL != (child = _synctex_new_void_hbox(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad void hbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child));
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create void hbox record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_KERN) {
+ if (NULL != (child = _synctex_new_kern(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad kern record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)-SYNCTEX_WIDTH(child),SYNCTEX_VERT(child));
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create kern record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_GLUE) {
+ if (NULL != (child = _synctex_new_glue(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad glue record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ SYNCTEX_UPDATE_FRIEND(child);
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create glue record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_MATH) {
+ if (NULL != (child = _synctex_new_math(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad math record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ SYNCTEX_UPDATE_FRIEND(child);
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create math record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BOUNDARY) {
+ if (NULL != (child = _synctex_new_boundary(scanner))
+ && NULL != (info = SYNCTEX_INFO(child))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad boundary record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_CHILD(parent,child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ SYNCTEX_UPDATE_FRIEND(child);
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create math record.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_SHEET) {
+ goto scan_teehs;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) {
+ /* Addendum to version 1.10 to manage nested sheets */
+ ++SYNCTEX_CUR;
+ if (_synctex_scan_nested_sheet(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected nested sheet.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto child_loop;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_ANCHOR) {
+ goto scan_anchor;
+ } else {
+ /* _synctex_error("Ignored record %c\n",*SYNCTEX_CUR); */
+ ++SYNCTEX_CUR;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Unexpected end.");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto child_loop;
+ }
+ } else {
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status<SYNCTEX_STATUS_OK && available>0){
+ _synctex_error("Uncomplete sheet(0)");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ } else {
+ goto child_loop;
+ }
+ }
+ _synctex_bail();
+/* The vertical loop means that we are on the same level, for example when we just ended a box.
+ * If a node is created now, it will be a sibling of the current node, sharing the same parent. */
+sibling_loop:
+ if (SYNCTEX_CUR<SYNCTEX_END) {
+ if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_VBOX) {
+ if (NULL != (sibling = _synctex_new_vbox(scanner))
+ && NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad vbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ parent = sibling;
+ child = NULL;
+ goto child_loop;
+ } else {
+ _synctex_error("Can't create vbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_VBOX) {
+ goto scan_xobv;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_HBOX) {
+ if (NULL != (sibling = _synctex_new_hbox(scanner)) &&
+ NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_setup_visible_box(sibling)<SYNCTEX_STATUS_OK
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad hbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child));
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ parent = child;
+ child = NULL;
+ goto child_loop;
+ } else {
+ _synctex_error("Can't create hbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_HBOX) {
+ goto scan_xobh;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_VBOX) {
+ if (NULL != (sibling = _synctex_new_void_vbox(scanner)) &&
+ NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad void vbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ goto sibling_loop;
+ } else {
+ _synctex_error("can't create void vbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_HBOX) {
+ if (NULL != (sibling = _synctex_new_void_hbox(scanner)) &&
+ NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad void hbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)+SYNCTEX_ABS_WIDTH(child),SYNCTEX_VERT(child));
+ goto sibling_loop;
+ } else {
+ _synctex_error("can't create void hbox record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_KERN) {
+ if (NULL != (sibling = _synctex_new_kern(scanner))
+ && NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad kern record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child)-SYNCTEX_WIDTH(child),SYNCTEX_VERT(child));
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create kern record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_GLUE) {
+ if (NULL != (sibling = _synctex_new_glue(scanner))
+ && NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad glue record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create glue record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_MATH) {
+ if (NULL != (sibling = _synctex_new_math(scanner))
+ && NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad math record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create math record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BOUNDARY) {
+ if (NULL != (sibling = _synctex_new_boundary(scanner))
+ && NULL != (info = SYNCTEX_INFO(sibling))) {
+ if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX)
+ || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX)
+ || _synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad boundary record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ SYNCTEX_SET_SIBLING(child,sibling);
+ child = sibling;
+# if SYNCTEX_VERBOSE
+ synctex_node_log(child);
+# endif
+ SYNCTEX_UPDATE_FRIEND(child);
+ _synctex_hbox_setup_visible(parent,SYNCTEX_HORIZ(child),SYNCTEX_VERT(child));
+ goto sibling_loop;
+ } else {
+ _synctex_error("Can't create boundary record (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_END_SHEET) {
+ goto scan_teehs;
+ } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_ANCHOR) {
+ ++SYNCTEX_CUR;
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Missing anchor (2).");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto sibling_loop;
+ } else {
+ ++SYNCTEX_CUR;
+ /* _synctex_error("Ignored record %c(2)\n",*SYNCTEX_CUR); */
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ goto sibling_loop;
+ }
+ } else {
+ available = 1;
+ status = _synctex_buffer_get_available_size(scanner,&available);
+ if (status<SYNCTEX_STATUS_OK && available>0){
+ goto sibling_loop;
+ } else {
+ _synctex_error("Uncomplete sheet(2)");
+ SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR);
+ }
+ }
+# undef SYNCTEX_DECODE_FAILED
+}
+
+# define SYNCTEX_APPEND_SHEET(SCANNER,SHEET) if (SCANNER->sheet) {\
+ synctex_node_t last_sheet = SCANNER->sheet;\
+ synctex_node_t next_sheet = NULL;\
+ while ((next_sheet = SYNCTEX_SIBLING(last_sheet))) {\
+ last_sheet = next_sheet;\
+ }\
+ SYNCTEX_SET_SIBLING(last_sheet,SHEET);\
+ } else {\
+ SCANNER->sheet = SHEET;\
+ }
+
+/* Used when parsing the synctex file
+ */
+synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) {
+ synctex_node_t sheet = NULL;
+ synctex_status_t status = 0;
+ if (NULL == scanner) {
+ return SYNCTEX_STATUS_BAD_ARGUMENT;
+ }
+ /* set up the lists of friends */
+ if (NULL == scanner->lists_of_friends) {
+ scanner->number_of_lists = 1024;
+ scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t));
+ if (NULL == scanner->lists_of_friends) {
+ _synctex_error("malloc:2");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ }
+ /* Find where this section starts */
+content_not_found:
+ status = _synctex_match_string(scanner,"Content:");
+ if (status<SYNCTEX_STATUS_EOF) {
+ return status;
+ }
+ if (_synctex_next_line(scanner)<SYNCTEX_STATUS_OK) {
+ _synctex_error("Uncomplete Content.");
+ return SYNCTEX_STATUS_ERROR;
+ }
+ if (status == SYNCTEX_STATUS_NOT_OK) {
+ goto content_not_found;
+ }
+next_sheet:
+ if (*SYNCTEX_CUR != SYNCTEX_CHAR_BEGIN_SHEET) {
+ status = _synctex_scan_postamble(scanner);
+ if (status < SYNCTEX_STATUS_EOF) {
+ _synctex_error("Bad content.");
+ return status;
+ }
+ if (status<SYNCTEX_STATUS_OK) {
+ status = _synctex_next_line(scanner);
+ if (status < SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad content.");
+ return status;
+ }
+ goto next_sheet;
+ }
+ return SYNCTEX_STATUS_OK;
+ }
+ /* Create a new sheet node */
+ sheet = _synctex_new_sheet(scanner);
+ status = _synctex_decode_int(scanner,&(SYNCTEX_PAGE(sheet)));
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("Missing sheet number.");
+bail:
+ SYNCTEX_FREE(sheet);
+ return SYNCTEX_STATUS_ERROR;
+ }
+ status = _synctex_next_line(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("Uncomplete file.");
+ goto bail;
+ }
+ status = _synctex_scan_sheet(scanner,sheet);
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("Bad sheet content.");
+ goto bail;
+ }
+ SYNCTEX_APPEND_SHEET(scanner,sheet);
+ sheet = NULL;
+ /* Now read the list of Inputs between 2 sheets. */
+ do {
+ status = _synctex_scan_input(scanner);
+ if (status<SYNCTEX_STATUS_EOF) {
+ _synctex_error("Bad input section.");
+ goto bail;
+ }
+ }
+ while(status >= SYNCTEX_STATUS_OK);
+ goto next_sheet;
+}
+
+int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef);
+
+/* Where the synctex scanner is created. */
+synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) {
+ gzFile file = NULL;
+ char * synctex = NULL;
+ synctex_scanner_t scanner = NULL;
+ synctex_io_mode_t io_mode = 0;
+ /* Here we assume that int are smaller than void * */
+ if (sizeof(int)>sizeof(void*)) {
+ _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out.");
+ return NULL;
+ }
+ /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */
+ if (SYNCTEX_BUFFER_SIZE >= UINT_MAX) {
+ _synctex_error("Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)");
+ return NULL;
+ }
+ /* for integers: */
+ if (SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) {
+ _synctex_error("Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)");
+ return NULL;
+ }
+ /* now open the synctex file */
+ if (_synctex_open(output,build_directory,&synctex,&file,synctex_ADD_QUOTES,&io_mode) || !file) {
+ if (_synctex_open(output,build_directory,&synctex,&file,synctex_DONT_ADD_QUOTES,&io_mode) || !file) {
+ return NULL;
+ }
+ }
+ scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t));
+ if (NULL == scanner) {
+ _synctex_error("malloc problem");
+ free(synctex);
+ gzclose(file);
+ return NULL;
+ }
+ /* make a private copy of output for the scanner */
+ if (NULL == (scanner->output = (char *)malloc(strlen(output)+1))){
+ _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable.");
+ } else if (scanner->output != strcpy(scanner->output,output)) {
+ _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable.");
+ }
+ scanner->synctex = synctex;/* Now the scanner owns synctex */
+ SYNCTEX_FILE = file;
+ return parse? synctex_scanner_parse(scanner):scanner;
+}
+
+int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref);
+
+/* This functions opens the file at the "output" given location.
+ * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character.
+ * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes.
+ * This function will remove them if possible.
+ * All the reference arguments will take a value on return. They must be non NULL.
+ * 0 on success, non 0 on error. */
+int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) {
+ if (synctex_name_ref && file_ref && io_mode_ref) {
+ /* 1 local variables that uses dynamic memory */
+ char * synctex_name = NULL;
+ gzFile the_file = NULL;
+ char * quoteless_synctex_name = NULL;
+ size_t size = 0;
+ synctex_io_mode_t io_mode = *io_mode_ref;
+ const char * mode = _synctex_get_io_mode_name(io_mode);
+ /* now create the synctex file name */
+ size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1;
+ synctex_name = (char *)malloc(size);
+ if (NULL == synctex_name) {
+ _synctex_error("! __synctex_open: Memory problem (1)\n");
+ return 1;
+ }
+ /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices,
+ * including the terminating character. size is free now. */
+ if (synctex_name != strcpy(synctex_name,output)) {
+ _synctex_error("! __synctex_open: Copy problem\n");
+return_on_error:
+ free(synctex_name);
+ free(quoteless_synctex_name);
+ return 2;
+ }
+ /* remove the last path extension if any */
+ _synctex_strip_last_path_extension(synctex_name);
+ if (!strlen(synctex_name)) {
+ goto return_on_error;
+ }
+ /* now insert quotes. */
+ if (add_quotes) {
+ char * quoted = NULL;
+ if (_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) {
+ /* There was an error or quoting does not make sense: */
+ goto return_on_error;
+ }
+ quoteless_synctex_name = synctex_name;
+ synctex_name = quoted;
+ }
+ /* Now add to synctex_name the first path extension. */
+ if (synctex_name != strcat(synctex_name,synctex_suffix)){
+ _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix);
+ goto return_on_error;
+ }
+ /* Add to quoteless_synctex_name as well, if relevant. */
+ if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){
+ free(quoteless_synctex_name);
+ quoteless_synctex_name = NULL;
+ }
+ if (NULL == (the_file = gzopen(synctex_name,mode))) {
+ /* Could not open this file */
+ if (errno != ENOENT) {
+ /* The file does exist, this is a lower level error, I can't do anything. */
+ _synctex_error("could not open %s, error %i\n",synctex_name,errno);
+ goto return_on_error;
+ }
+ /* Apparently, there is no uncompressed synctex file. Try the compressed version */
+ if (synctex_name != strcat(synctex_name,synctex_suffix_gz)){
+ _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz);
+ goto return_on_error;
+ }
+ io_mode |= synctex_io_gz_mask;
+ mode = _synctex_get_io_mode_name(io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */
+ /* Add the suffix to the quoteless_synctex_name as well. */
+ if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){
+ free(quoteless_synctex_name);
+ quoteless_synctex_name = NULL;
+ }
+ if (NULL == (the_file = gzopen(synctex_name,mode))) {
+ /* Could not open this file */
+ if (errno != ENOENT) {
+ /* The file does exist, this is a lower level error, I can't do anything. */
+ _synctex_error("Could not open %s, error %i\n",synctex_name,errno);
+ }
+ goto return_on_error;
+ }
+ }
+ /* At this point, the file is properly open.
+ * If we are in the add_quotes mode, we change the file name by removing the quotes. */
+ if (quoteless_synctex_name) {
+ gzclose(the_file);
+ if (rename(synctex_name,quoteless_synctex_name)) {
+ _synctex_error("Could not rename %s to %s, error %i\n",synctex_name,quoteless_synctex_name,errno);
+ /* We could not rename, reopen the file with the quoted name. */
+ if (NULL == (the_file = gzopen(synctex_name,mode))) {
+ /* No luck, could not re open this file, something has happened meanwhile */
+ if (errno != ENOENT) {
+ /* The file does not exist any more, it has certainly be removed somehow
+ * this is a lower level error, I can't do anything. */
+ _synctex_error("Could not open again %s, error %i\n",synctex_name,errno);
+ }
+ goto return_on_error;
+ }
+ } else {
+ /* The file has been successfully renamed */
+ if (NULL == (the_file = gzopen(quoteless_synctex_name,mode))) {
+ /* Could not open this file */
+ if (errno != ENOENT) {
+ /* The file does exist, this is a lower level error, I can't do anything. */
+ _synctex_error("Could not open renamed %s, error %i\n",quoteless_synctex_name,errno);
+ }
+ goto return_on_error;
+ }
+ /* The quote free file name should replace the old one:*/
+ free(synctex_name);
+ synctex_name = quoteless_synctex_name;
+ quoteless_synctex_name = NULL;
+ }
+ }
+ /* The operation is successfull, return the arguments by value. */
+ * file_ref = the_file;
+ * io_mode_ref = io_mode;
+ * synctex_name_ref = synctex_name;
+ return 0;
+ }
+ return 3; /* Bad parameter. */
+}
+
+/* Opens the ouput file, taking into account the eventual build_directory.
+ * 0 on success, non 0 on error. */
+int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) {
+# define synctex_name (*synctex_name_ref)
+# define the_file (*file_ref)
+ int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_mode_ref);
+ if ((result || !*file_ref) && build_directory && strlen(build_directory)) {
+ char * build_output;
+ const char *lpc;
+ size_t size;
+ synctex_bool_t is_absolute;
+ build_output = NULL;
+ lpc = _synctex_last_path_component(output);
+ size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */
+ is_absolute = _synctex_path_is_absolute(build_directory);
+ if (!is_absolute) {
+ size += strlen(output);
+ }
+ if ((build_output = (char *)malloc(size))) {
+ if (is_absolute) {
+ build_output[0] = '\0';
+ } else {
+ if (build_output != strcpy(build_output,output)) {
+ return -4;
+ }
+ build_output[lpc-output]='\0';
+ }
+ if (build_output == strcat(build_output,build_directory)) {
+ /* Append a path separator if necessary. */
+ if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) {
+ if (build_output != strcat(build_output,"/")) {
+ return -2;
+ }
+ }
+ /* Append the last path component of the output. */
+ if (build_output != strcat(build_output,lpc)) {
+ return -3;
+ }
+ return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_mode_ref);
+ }
+ }
+ return -1;
+ }
+ return result;
+# undef synctex_name
+# undef the_file
+}
+
+/* The scanner destructor
+ */
+void synctex_scanner_free(synctex_scanner_t scanner) {
+ if (NULL == scanner) {
+ return;
+ }
+ if (SYNCTEX_FILE) {
+ gzclose(SYNCTEX_FILE);
+ SYNCTEX_FILE = NULL;
+ }
+ SYNCTEX_FREE(scanner->sheet);
+ SYNCTEX_FREE(scanner->input);
+ free(SYNCTEX_START);
+ free(scanner->output_fmt);
+ free(scanner->output);
+ free(scanner->synctex);
+ free(scanner->lists_of_friends);
+ free(scanner);
+}
+
+/* Where the synctex scanner parses the contents of the file. */
+synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner) {
+ synctex_status_t status = 0;
+ if (!scanner || scanner->flags.has_parsed) {
+ return scanner;
+ }
+ scanner->flags.has_parsed=1;
+ scanner->pre_magnification = 1000;
+ scanner->pre_unit = 8192;
+ scanner->pre_x_offset = scanner->pre_y_offset = 578;
+ /* initialize the offset with a fake unprobable value,
+ * If there is a post scriptum section, this value will be overriden by the real life value */
+ scanner->x_offset = scanner->y_offset = 6.027e23f;
+# define DEFINE_synctex_scanner_class(NAME)\
+ scanner->class[synctex_node_type_##NAME] = synctex_class_##NAME;\
+ (scanner->class[synctex_node_type_##NAME]).scanner = scanner
+ DEFINE_synctex_scanner_class(sheet);
+ DEFINE_synctex_scanner_class(input);
+ DEFINE_synctex_scanner_class(hbox);
+ DEFINE_synctex_scanner_class(void_hbox);
+ DEFINE_synctex_scanner_class(vbox);
+ DEFINE_synctex_scanner_class(void_vbox);
+ DEFINE_synctex_scanner_class(kern);
+ DEFINE_synctex_scanner_class(glue);
+ DEFINE_synctex_scanner_class(math);
+ DEFINE_synctex_scanner_class(boundary);
+ SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */
+ if (NULL == SYNCTEX_START) {
+ _synctex_error("malloc error");
+ synctex_scanner_free(scanner);
+ return NULL;
+ }
+ SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE;
+ /* SYNCTEX_END always points to a null terminating character.
+ * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1.
+ * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */
+ *SYNCTEX_END = '\0';
+ SYNCTEX_CUR = SYNCTEX_END;
+# if defined(SYNCTEX_USE_CHARINDEX)
+ scanner->charindex_offset = -SYNCTEX_BUFFER_SIZE;
+# endif
+ status = _synctex_scan_preamble(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("SyncTeX Error: Bad preamble\n");
+bailey:
+ synctex_scanner_free(scanner);
+ return NULL;
+ }
+ status = _synctex_scan_content(scanner);
+ if (status<SYNCTEX_STATUS_OK) {
+ _synctex_error("SyncTeX Error: Bad content\n");
+ goto bailey;
+ }
+ /* Everything is finished, free the buffer, close the file */
+ free((void *)SYNCTEX_START);
+ SYNCTEX_START = SYNCTEX_CUR = SYNCTEX_END = NULL;
+ gzclose(SYNCTEX_FILE);
+ SYNCTEX_FILE = NULL;
+ /* Final tuning: set the default values for various parameters */
+ /* 1 pre_unit = (scanner->pre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp
+ * 1 pt = 65536 sp */
+ if (scanner->pre_unit<=0) {
+ scanner->pre_unit = 8192;
+ }
+ if (scanner->pre_magnification<=0) {
+ scanner->pre_magnification = 1000;
+ }
+ if (scanner->unit <= 0) {
+ /* no post magnification */
+ scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/
+ } else {
+ /* post magnification */
+ scanner->unit *= scanner->pre_unit / 65781.76;
+ }
+ scanner->unit *= scanner->pre_magnification / 1000.0;
+ if (scanner->x_offset > 6e23) {
+ /* no post offset */
+ scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76);
+ scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76);
+ } else {
+ /* post offset */
+ scanner->x_offset /= 65781.76f;
+ scanner->y_offset /= 65781.76f;
+ }
+ return scanner;
+ #undef SYNCTEX_FILE
+}
+
+/* Scanner accessors.
+ */
+int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){
+ return scanner?scanner->pre_x_offset:0;
+}
+int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){
+ return scanner?scanner->pre_y_offset:0;
+}
+int synctex_scanner_x_offset(synctex_scanner_t scanner){
+ return scanner?scanner->x_offset:0;
+}
+int synctex_scanner_y_offset(synctex_scanner_t scanner){
+ return scanner?scanner->y_offset:0;
+}
+float synctex_scanner_magnification(synctex_scanner_t scanner){
+ return scanner?scanner->unit:1;
+}
+void synctex_scanner_display(synctex_scanner_t scanner) {
+ if (NULL == scanner) {
+ return;
+ }
+ printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version);
+ printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset);
+ printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n",
+ scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset);
+ printf("The input:\n");
+ SYNCTEX_DISPLAY(scanner->input);
+ if (scanner->count<1000) {
+ printf("The sheets:\n");
+ SYNCTEX_DISPLAY(scanner->sheet);
+ printf("The friends:\n");
+ if (scanner->lists_of_friends) {
+ int i = scanner->number_of_lists;
+ synctex_node_t node;
+ while(i--) {
+ printf("Friend index:%i\n",i);
+ node = (scanner->lists_of_friends)[i];
+ while(node) {
+ printf("%s:%i,%i\n",
+ synctex_node_isa(node),
+ SYNCTEX_TAG(node),
+ SYNCTEX_LINE(node)
+ );
+ node = SYNCTEX_FRIEND(node);
+ }
+ }
+ }
+ } else {
+ printf("SyncTeX Warning: Too many objects\n");
+ }
+}
+/* Public*/
+const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) {
+ synctex_node_t input = NULL;
+ if (NULL == scanner) {
+ return NULL;
+ }
+ input = scanner->input;
+ do {
+ if (tag == SYNCTEX_TAG(input)) {
+ return (SYNCTEX_NAME(input));
+ }
+ } while((input = SYNCTEX_SIBLING(input)) != NULL);
+ return NULL;
+}
+
+int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name);
+int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) {
+ synctex_node_t input = NULL;
+ if (NULL == scanner) {
+ return 0;
+ }
+ input = scanner->input;
+ do {
+ if (_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) {
+ return SYNCTEX_TAG(input);
+ }
+ } while((input = SYNCTEX_SIBLING(input)) != NULL);
+ // 2011 version
+ name = _synctex_base_name(name);
+ input = scanner->input;
+ do {
+ if (_synctex_is_equivalent_file_name(name,_synctex_base_name(SYNCTEX_NAME(input)))) {
+ synctex_node_t other_input = input;
+ while((other_input = SYNCTEX_SIBLING(other_input)) != NULL) {
+ if (_synctex_is_equivalent_file_name(name,_synctex_base_name(SYNCTEX_NAME(other_input)))
+ && (strlen(SYNCTEX_NAME(input))!=strlen(SYNCTEX_NAME(other_input))
+ || strncmp(SYNCTEX_NAME(other_input),SYNCTEX_NAME(input),strlen(SYNCTEX_NAME(input))))) {
+ // There is a second possible candidate
+ return 0;
+ }
+ }
+ return SYNCTEX_TAG(input);
+ }
+ } while((input = SYNCTEX_SIBLING(input)) != NULL);
+ return 0;
+}
+
+int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) {
+ size_t char_index = strlen(name);
+ if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) {
+ /* the name is not void */
+ char_index -= 1;
+ if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) {
+ /* the last character of name is not a path separator */
+ int result = _synctex_scanner_get_tag(scanner,name);
+ if (result) {
+ return result;
+ } else {
+ /* the given name was not the one known by TeX
+ * try a name relative to the enclosing directory of the scanner->output file */
+ const char * relative = name;
+ const char * ptr = scanner->output;
+ while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr))
+ {
+ relative += 1;
+ ptr += 1;
+ }
+ /* Find the last path separator before relative */
+ while(relative > name) {
+ if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) {
+ break;
+ }
+ relative -= 1;
+ }
+ if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) {
+ return result;
+ }
+ if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) {
+ /* No tag found for the given absolute name,
+ * Try each relative path starting from the shortest one */
+ while(0<char_index) {
+ char_index -= 1;
+ if (SYNCTEX_IS_PATH_SEPARATOR(name[char_index])
+ && (result = _synctex_scanner_get_tag(scanner,name+char_index+1))) {
+ return result;
+ }
+ }
+ }
+ }
+ return result;
+ }
+ }
+ return 0;
+}
+synctex_node_t synctex_scanner_input(synctex_scanner_t scanner) {
+ return scanner?scanner->input:NULL;
+}
+const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) {
+ return NULL != scanner && scanner->output_fmt?scanner->output_fmt:"";
+}
+const char * synctex_scanner_get_output(synctex_scanner_t scanner) {
+ return NULL != scanner && scanner->output?scanner->output:"";
+}
+const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) {
+ return NULL != scanner && scanner->synctex?scanner->synctex:"";
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Public node attributes
+# endif
+int synctex_node_h(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_HORIZ(node);
+}
+int synctex_node_v(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_VERT(node);
+}
+int synctex_node_width(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_WIDTH(node);
+}
+int synctex_node_box_h(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ if (SYNCTEX_IS_BOX(node)) {
+result:
+ return SYNCTEX_HORIZ(node);
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+int synctex_node_box_v(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ if (SYNCTEX_IS_BOX(node)) {
+result:
+ return SYNCTEX_VERT(node);
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+int synctex_node_box_width(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ if (SYNCTEX_IS_BOX(node)) {
+result:
+ return SYNCTEX_WIDTH(node);
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+int synctex_node_box_height(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ if (SYNCTEX_IS_BOX(node)) {
+result:
+ return SYNCTEX_HEIGHT(node);
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+int synctex_node_box_depth(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ if (SYNCTEX_IS_BOX(node)) {
+result:
+ return SYNCTEX_DEPTH(node);
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Public node visible attributes
+# endif
+float synctex_node_visible_h(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset;
+}
+float synctex_node_visible_v(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset;
+}
+float synctex_node_visible_width(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ return SYNCTEX_WIDTH(node)*node->class->scanner->unit;
+}
+float synctex_node_box_visible_h(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset;
+ case synctex_node_type_hbox:
+result:
+ return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset;
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+float synctex_node_box_visible_v(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset;
+ case synctex_node_type_hbox:
+result:
+ return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset;
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+float synctex_node_box_visible_width(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ return SYNCTEX_WIDTH(node)*node->class->scanner->unit;
+ case synctex_node_type_hbox:
+result:
+ return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit;
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+float synctex_node_box_visible_height(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ return SYNCTEX_HEIGHT(node)*node->class->scanner->unit;
+ case synctex_node_type_hbox:
+result:
+ return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit;
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+float synctex_node_box_visible_depth(synctex_node_t node){
+ if (!node) {
+ return 0;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ return SYNCTEX_DEPTH(node)*node->class->scanner->unit;
+ case synctex_node_type_hbox:
+result:
+ return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit;
+ }
+ if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) {
+ goto result;
+ }
+ return 0;
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Other public node attributes
+# endif
+
+int synctex_node_page(synctex_node_t node){
+ synctex_node_t parent = NULL;
+ if (!node) {
+ return -1;
+ }
+ parent = SYNCTEX_PARENT(node);
+ while(parent) {
+ node = parent;
+ parent = SYNCTEX_PARENT(node);
+ }
+ if (node->class->type == synctex_node_type_sheet) {
+ return SYNCTEX_PAGE(node);
+ }
+ return -1;
+}
+synctex_charindex_t synctex_node_charindex(synctex_node_t node) {
+ return node?SYNCTEX_CHARINDEX(node):0;
+}
+int synctex_node_tag(synctex_node_t node) {
+ return node?SYNCTEX_TAG(node):-1;
+}
+int synctex_node_line(synctex_node_t node) {
+ return node?SYNCTEX_LINE(node):-1;
+}
+int synctex_node_mean_line(synctex_node_t node) {
+ return node?(node->class->type==synctex_node_type_hbox?SYNCTEX_MEAN_LINE(node):SYNCTEX_LINE(node)):-1;
+}
+int synctex_node_child_count(synctex_node_t node) {
+ return node?(node->class->type==synctex_node_type_hbox?SYNCTEX_NODE_WEIGHT(node):0):-1;
+}
+int synctex_node_column(synctex_node_t node) {
+# ifdef __DARWIN_UNIX03
+# pragma unused(node)
+# endif
+ return -1;
+}
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Sheet
+# endif
+
+synctex_node_t synctex_sheet(synctex_scanner_t scanner,int page) {
+ if (scanner) {
+ synctex_node_t sheet = scanner->sheet;
+ while(sheet) {
+ if (page == SYNCTEX_PAGE(sheet)) {
+ return sheet;
+ }
+ sheet = SYNCTEX_SIBLING(sheet);
+ }
+ }
+ return NULL;
+}
+
+synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) {
+ if (scanner) {
+ return SYNCTEX_CHILD(synctex_sheet(scanner,page));
+ }
+ return NULL;
+}
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Query
+# endif
+
+int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) {
+# ifdef __DARWIN_UNIX03
+# pragma unused(column)
+# endif
+ int tag = synctex_scanner_get_tag(scanner,name);
+ size_t size = 0;
+ int friend_index = 0;
+ int max_line = 0;
+ synctex_node_t node = NULL;
+ if (tag == 0) {
+ printf("SyncTeX Warning: No tag for %s\n",name);
+ return -1;
+ }
+ free(SYNCTEX_START);
+ SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL;
+ max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX;
+ while(line<max_line) {
+ /* This loop will only be performed once for advanced viewers */
+ friend_index = (tag+line)%(scanner->number_of_lists);
+ if ((node = (scanner->lists_of_friends)[friend_index])) {
+ do {
+ if ((synctex_node_type(node)>=synctex_node_type_boundary)
+ && (tag == SYNCTEX_TAG(node))
+ && (line == SYNCTEX_LINE(node))) {
+ if (SYNCTEX_CUR == SYNCTEX_END) {
+ size += 16;
+ SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *));
+ SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START;
+ SYNCTEX_START = SYNCTEX_END;
+ SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *);
+ }
+ *(synctex_node_t *)SYNCTEX_CUR = node;
+ SYNCTEX_CUR += sizeof(synctex_node_t);
+ }
+ } while ((node = SYNCTEX_FRIEND(node)));
+ if (SYNCTEX_START == NULL) {
+ /* We did not find any matching boundary, retry with glue or kern */
+ node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */
+ do {
+ if ((synctex_node_type(node)>=synctex_node_type_kern)
+ && (tag == SYNCTEX_TAG(node))
+ && (line == SYNCTEX_LINE(node))) {
+ if (SYNCTEX_CUR == SYNCTEX_END) {
+ size += 16;
+ SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *));
+ SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START;
+ SYNCTEX_START = SYNCTEX_END;
+ SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *);
+ }
+ *(synctex_node_t *)SYNCTEX_CUR = node;
+ SYNCTEX_CUR += sizeof(synctex_node_t);
+ }
+ } while ((node = SYNCTEX_FRIEND(node)));
+ if (SYNCTEX_START == NULL) {
+ /* We did not find any matching glue or kern, retry with boxes */
+ node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */
+ do {
+ if ((tag == SYNCTEX_TAG(node))
+ && (line == SYNCTEX_LINE(node))) {
+ if (SYNCTEX_CUR == SYNCTEX_END) {
+ size += 16;
+ SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *));
+ SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START;
+ SYNCTEX_START = SYNCTEX_END;
+ SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *);
+ }
+ *(synctex_node_t *)SYNCTEX_CUR = node;
+ SYNCTEX_CUR += sizeof(synctex_node_t);
+ }
+ } while((node = SYNCTEX_FRIEND(node)));
+ }
+ }
+ SYNCTEX_END = SYNCTEX_CUR;
+ /* Now reverse the order to have nodes in display order, and then keep just a few nodes.
+ * Order first the best node. */
+ if ((SYNCTEX_START) && (SYNCTEX_END)) {
+ unsigned int best_match = -1;
+ unsigned int next_match = -1;
+ unsigned int best_weight = 0;
+ synctex_node_t * best_ref = NULL;
+ synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START;
+ synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END;
+ --end_ref;
+ while (start_ref < end_ref) {
+ node = *start_ref;
+ *start_ref = *end_ref;
+ *end_ref = node;
+ ++start_ref;
+ --end_ref;
+ }
+ /* Now reorder the nodes to put first the one which fits best.
+ * The idea is to walk along the list of nodes and pick up the first one
+ * which line info is exactly the mean line of its parent, or at least very close.
+ * Then we choose among all such node the one with the maximum number of child nodes.
+ * Then we switch with the first node.
+ */
+ best_ref = start_ref = (synctex_node_t *)SYNCTEX_START;
+ node = *start_ref;
+ best_match = abs(SYNCTEX_LINE(node)-SYNCTEX_MEAN_LINE(SYNCTEX_PARENT(node)));
+ end_ref = (synctex_node_t *)SYNCTEX_END;
+ while (++start_ref<end_ref) {
+ synctex_node_t parent = NULL;
+ node = *start_ref;
+ parent = SYNCTEX_PARENT(node);
+ next_match = abs(SYNCTEX_LINE(node)-SYNCTEX_MEAN_LINE(parent));
+ if (next_match < best_match
+ || (next_match == best_match && SYNCTEX_NODE_WEIGHT(parent)>best_weight)) {
+ best_match = next_match;
+ best_ref = start_ref;
+ best_weight = SYNCTEX_NODE_WEIGHT(parent);
+ }
+ }
+ node = *best_ref;
+ *best_ref = *(synctex_node_t *)SYNCTEX_START;
+ *(synctex_node_t *)SYNCTEX_START = node;
+ /* Basically, we keep the first node for each parent.
+ * More precisely, we keep only nodes that are not children of
+ * their predecessor's parent. */
+ start_ref = (synctex_node_t *)SYNCTEX_START;
+ end_ref = (synctex_node_t *)SYNCTEX_START;
+next_end:
+ end_ref += 1; /* we allways have start_ref<= end_ref*/
+ if (end_ref < (synctex_node_t *)SYNCTEX_END) {
+ node = *end_ref;
+ while ((node = SYNCTEX_PARENT(node))) {
+ if (SYNCTEX_PARENT(*start_ref) == node) {
+ goto next_end;
+ }
+ }
+ start_ref += 1;
+ *start_ref = *end_ref;
+ goto next_end;
+ }
+ start_ref += 1;
+ SYNCTEX_END = (char *)start_ref;
+ SYNCTEX_CUR = NULL;// added on behalf of Jose Alliste
+ return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);// added on behalf Jan Sundermeyer
+ }
+ SYNCTEX_CUR = NULL;
+ // return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer
+ }
+# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__)
+ break;
+# else
+ ++line;
+# endif
+ }
+ return 0;
+}
+
+synctex_node_t synctex_next_result(synctex_scanner_t scanner) {
+ if (NULL == SYNCTEX_CUR) {
+ SYNCTEX_CUR = SYNCTEX_START;
+ } else {
+ SYNCTEX_CUR+=sizeof(synctex_node_t);
+ }
+ if (SYNCTEX_CUR<SYNCTEX_END) {
+ return *(synctex_node_t*)SYNCTEX_CUR;
+ } else {
+ return NULL;
+ }
+}
+
+/* This struct records a point in TeX coordinates.*/
+typedef struct {
+ int h;
+ int v;
+} synctex_point_t;
+
+/* This struct records distances, the left one is positive or 0 and the right one is negative or 0.
+ * When comparing the locations of 2 different graphical objects on the page, we will have to also record the
+ * horizontal distance as signed to keep track of the typesetting order.*/
+typedef struct {
+ int left;
+ int right;
+} synctex_distances_t;
+
+typedef struct {
+ synctex_point_t left;
+ synctex_point_t right;
+} synctex_offsets_t;
+
+
+typedef struct {
+ synctex_node_t left;
+ synctex_node_t right;
+} synctex_node_set_t;
+
+/* The smallest container between two has the smallest width or height.
+ * This comparison is used when there are 2 overlapping boxes that contain the hit point.
+ * For ConTeXt, the problem appears at each page.
+ * The chosen box is the one with the smallest height, then the smallest width. */
+SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node);
+
+/* Returns the distance between the hit point hitPoint=(H,V) and the given node. */
+synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible);
+int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible);
+
+/* The best container is the deeper box that contains the hit point (H,V).
+ * _synctex_eq_deepest_container starts with node whereas
+ * _synctex_box_child_deepest starts with node's children, if any
+ * if node is not a box, or a void box, NULL is returned.
+ * We traverse the node tree in a deep first manner and stop as soon as a result is found. */
+static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible);
+
+/* Once a best container is found, the closest children are the closest nodes to the left or right of the hit point.
+ * Only horizontal and vertical offsets are used to compare the positions of the nodes. */
+SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible);
+
+/* The closest container is the box that is the one closest to the given point.
+ * The "visible" version takes into account the visible dimensions instead of the real ones given by TeX. */
+SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible);
+
+#define SYNCTEX_MASK_LEFT 1
+#define SYNCTEX_MASK_RIGHT 2
+
+int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v) {
+ synctex_node_t sheet = NULL;
+ synctex_node_t node = NULL; /* placeholder */
+ synctex_node_t other_node = NULL; /* placeholder */
+ synctex_point_t hitPoint = {0,0}; /* placeholder */
+ synctex_node_set_t bestNodes = {NULL,NULL}; /* holds the best node */
+ synctex_distances_t bestDistances = {INT_MAX,INT_MAX}; /* holds the best distances for the best node */
+ synctex_node_t bestContainer = NULL; /* placeholder */
+ if (NULL == (scanner = synctex_scanner_parse(scanner)) || 0 >= scanner->unit) {/* scanner->unit must be >0 */
+ return 0;
+ }
+ /* Convert the given point to scanner integer coordinates */
+ hitPoint.h = (h-scanner->x_offset)/scanner->unit;
+ hitPoint.v = (v-scanner->y_offset)/scanner->unit;
+ /* We will store in the scanner's buffer the result of the query. */
+ free(SYNCTEX_START);
+ SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL;
+ /* Find the proper sheet */
+ sheet = scanner->sheet;
+ while((sheet) && SYNCTEX_PAGE(sheet) != page) {
+ sheet = SYNCTEX_SIBLING(sheet);
+ }
+ if (NULL == sheet) {
+ return -1;
+ }
+ /* Now sheet points to the sheet node with proper page number */
+ /* Here is how we work:
+ * At first we do not consider the visible box dimensions. This will cover the most frequent cases.
+ * Then we try with the visible box dimensions.
+ * We try to find a non void box containing the hit point.
+ * We browse all the horizontal boxes until we find one containing the hit point. */
+ if ((node = SYNCTEX_NEXT_hbox(sheet))) {
+ do {
+ if (_synctex_point_in_box(hitPoint,node,synctex_YES)) {
+ /* Maybe the hitPoint belongs to a contained vertical box. */
+end:
+ /* This trick is for catching overlapping boxes */
+ if ((other_node = SYNCTEX_NEXT_hbox(node))) {
+ do {
+ if (_synctex_point_in_box(hitPoint,other_node,synctex_YES)) {
+ node = _synctex_smallest_container(other_node,node);
+ }
+ } while((other_node = SYNCTEX_NEXT_hbox(other_node)));
+ }
+ /* node is the smallest horizontal box that contains hitPoint. */
+ if ((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) {
+ node = bestContainer;
+ }
+ _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES);
+ if (bestNodes.right && bestNodes.left) {
+ if ((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left))
+ || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left))
+ || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) {
+ if ((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) {
+ if (bestDistances.left>bestDistances.right) {
+ ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right;
+ ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left;
+ } else {
+ ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left;
+ ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right;
+ }
+ SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t);
+ SYNCTEX_CUR = NULL;
+ return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);
+ }
+ return SYNCTEX_STATUS_ERROR;
+ }
+ /* both nodes have the same input coordinates
+ * We choose the one closest to the hit point */
+ if (bestDistances.left>bestDistances.right) {
+ bestNodes.left = bestNodes.right;
+ }
+ bestNodes.right = NULL;
+ } else if (bestNodes.right) {
+ bestNodes.left = bestNodes.right;
+ } else if (!bestNodes.left){
+ bestNodes.left = node;
+ }
+ if ((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) {
+ * (synctex_node_t *)SYNCTEX_START = bestNodes.left;
+ SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t);
+ SYNCTEX_CUR = NULL;
+ return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);
+ }
+ return SYNCTEX_STATUS_ERROR;
+ }
+ } while ((node = SYNCTEX_NEXT_hbox(node)));
+ /* All the horizontal boxes have been tested,
+ * None of them contains the hit point.
+ */
+ }
+ /* We are not lucky */
+ if ((node = SYNCTEX_CHILD(sheet))) {
+ goto end;
+ }
+ return 0;
+}
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Utilities
+# endif
+
+int _synctex_bail(void) {
+ _synctex_error("SyncTeX ERROR\n");
+ return -1;
+}
+/* Rougly speaking, this is:
+ * node's h coordinate - hitPoint's h coordinate.
+ * If node is to the right of the hit point, then this distance is positive,
+ * if node is to the left of the hit point, this distance is negative.*/
+int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible);
+int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) {
+ if (node) {
+ int min,med,max;
+ switch(node->class->type) {
+ /* The distance between a point and a box is special.
+ * It is not the euclidian distance, nor something similar.
+ * We have to take into account the particular layout,
+ * and the box hierarchy.
+ * Given a box, there are 9 regions delimited by the lines of the edges of the box.
+ * The origin being at the top left corner of the page,
+ * we also give names to the vertices of the box.
+ *
+ * 1 | 2 | 3
+ * ---A---B--->
+ * 4 | 5 | 6
+ * ---C---D--->
+ * 7 | 8 | 9
+ * v v
+ */
+ case synctex_node_type_hbox:
+ /* getting the box bounds, taking into account negative width, height and depth. */
+ min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node);
+ max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node));
+ /* We allways have min <= max */
+ if (hitPoint.h<min) {
+ return min - hitPoint.h; /* regions 1+4+7, result is > 0 */
+ } else if (hitPoint.h>max) {
+ return max - hitPoint.h; /* regions 3+6+9, result is < 0 */
+ } else {
+ return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */
+ }
+ break;
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ /* getting the box bounds, taking into account negative width, height and depth.
+ * For these boxes, no visible dimension available */
+ min = SYNCTEX_HORIZ(node);
+ max = min + SYNCTEX_ABS_WIDTH(node);
+ /* We allways have min <= max */
+ if (hitPoint.h<min) {
+ return min - hitPoint.h; /* regions 1+4+7, result is > 0 */
+ } else if (hitPoint.h>max) {
+ return max - hitPoint.h; /* regions 3+6+9, result is < 0 */
+ } else {
+ return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */
+ }
+ break;
+ case synctex_node_type_kern:
+ /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move.
+ * The distance to the kern is very special,
+ * in general, there is no text material in the kern,
+ * this is why we compute the offset relative to the closest edge of the kern.*/
+ max = SYNCTEX_WIDTH(node);
+ if (max<0) {
+ min = SYNCTEX_HORIZ(node);
+ max = min - max;
+ } else {
+ min = -max;
+ max = SYNCTEX_HORIZ(node);
+ min += max;
+ }
+ med = (min+max)/2;
+ /* positive kern: '.' means text, '>' means kern offset
+ * .............
+ * min>>>>med>>>>max
+ * ...............
+ * negative kern: '.' means text, '<' means kern offset
+ * ............................
+ * min<<<<med<<<<max
+ * .................................
+ * Actually, we do not take into account negative widths.
+ * There is a problem for such situation when there is efectively overlapping text.
+ * But this should be extremely rare. I guess that in that case, many different choices
+ * could be made, one being in contradiction of the other.
+ * It means that the best choice should be made according to the situation that occurs
+ * most frequently.
+ */
+ if (hitPoint.h<min) {
+ return min - hitPoint.h + 1; /* penalty to ensure other nodes are chosen first in case of overlapping ones */
+ } else if (hitPoint.h>max) {
+ return max - hitPoint.h - 1; /* same kind of penalty */
+ } else if (hitPoint.h>med) {
+ /* do things like if the node had 0 width and was placed at the max edge + 1*/
+ return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */
+ } else {
+ return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */
+ }
+ case synctex_node_type_glue:
+ case synctex_node_type_math:
+ return SYNCTEX_HORIZ(node) - hitPoint.h;
+ }
+ }
+ return INT_MAX;/* We always assume that the node is faraway to the right*/
+}
+/* Rougly speaking, this is:
+ * node's v coordinate - hitPoint's v coordinate.
+ * If node is at the top of the hit point, then this distance is positive,
+ * if node is at the bottom of the hit point, this distance is negative.*/
+int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible);
+int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) {
+# ifdef __DARWIN_UNIX03
+# pragma unused(visible)
+# endif
+ if (node) {
+ int min,max;
+ switch(node->class->type) {
+ /* The distance between a point and a box is special.
+ * It is not the euclidian distance, nor something similar.
+ * We have to take into account the particular layout,
+ * and the box hierarchy.
+ * Given a box, there are 9 regions delimited by the lines of the edges of the box.
+ * The origin being at the top left corner of the page,
+ * we also give names to the vertices of the box.
+ *
+ * 1 | 2 | 3
+ * ---A---B--->
+ * 4 | 5 | 6
+ * ---C---D--->
+ * 7 | 8 | 9
+ * v v
+ */
+ case synctex_node_type_hbox:
+ /* getting the box bounds, taking into account negative width, height and depth. */
+ min = SYNCTEX_VERT_V(node);
+ max = min + SYNCTEX_ABS_DEPTH_V(node);
+ min -= SYNCTEX_ABS_HEIGHT_V(node);
+ /* We allways have min <= max */
+ if (hitPoint.v<min) {
+ return min - hitPoint.v; /* regions 1+2+3, result is > 0 */
+ } else if (hitPoint.v>max) {
+ return max - hitPoint.v; /* regions 7+8+9, result is < 0 */
+ } else {
+ return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */
+ }
+ break;
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_void_hbox:
+ /* getting the box bounds, taking into account negative width, height and depth. */
+ min = SYNCTEX_VERT(node);
+ max = min + SYNCTEX_ABS_DEPTH(node);
+ min -= SYNCTEX_ABS_HEIGHT(node);
+ /* We allways have min <= max */
+ if (hitPoint.v<min) {
+ return min - hitPoint.v; /* regions 1+2+3, result is > 0 */
+ } else if (hitPoint.v>max) {
+ return max - hitPoint.v; /* regions 7+8+9, result is < 0 */
+ } else {
+ return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */
+ }
+ break;
+ case synctex_node_type_kern:
+ case synctex_node_type_glue:
+ case synctex_node_type_math:
+ return SYNCTEX_VERT(node) - hitPoint.v;
+ }
+ }
+ return INT_MAX;/* We always assume that the node is faraway to the top*/
+}
+
+SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) {
+ float height, other_height;
+ if (SYNCTEX_ABS_WIDTH(node)<SYNCTEX_ABS_WIDTH(other_node)) {
+ return node;
+ }
+ if (SYNCTEX_ABS_WIDTH(node)>SYNCTEX_ABS_WIDTH(other_node)) {
+ return other_node;
+ }
+ height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node);
+ other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node);
+ if (height<other_height) {
+ return node;
+ }
+ if (height>other_height) {
+ return other_node;
+ }
+ return node;
+}
+
+synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) {
+ if (node) {
+ if (0 == _synctex_point_h_distance(hitPoint,node,visible)
+ && 0 == _synctex_point_v_distance(hitPoint,node,visible)) {
+ return synctex_YES;
+ }
+ }
+ return synctex_NO;
+}
+
+int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) {
+# ifdef __DARWIN_UNIX03
+# pragma unused(visible)
+# endif
+ int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */
+ if (node) {
+ int minH,maxH,minV,maxV;
+ switch(node->class->type) {
+ /* The distance between a point and a box is special.
+ * It is not the euclidian distance, nor something similar.
+ * We have to take into account the particular layout,
+ * and the box hierarchy.
+ * Given a box, there are 9 regions delimited by the lines of the edges of the box.
+ * The origin being at the top left corner of the page,
+ * we also give names to the vertices of the box.
+ *
+ * 1 | 2 | 3
+ * ---A---B--->
+ * 4 | 5 | 6
+ * ---C---D--->
+ * 7 | 8 | 9
+ * v v
+ * In each region, there is a different formula.
+ * In the end we have a continuous distance which may not be a mathematical distance but who cares. */
+ case synctex_node_type_vbox:
+ case synctex_node_type_void_vbox:
+ case synctex_node_type_hbox:
+ case synctex_node_type_void_hbox:
+ /* getting the box bounds, taking into account negative widths. */
+ minH = SYNCTEX_HORIZ(node);
+ maxH = minH + SYNCTEX_ABS_WIDTH(node);
+ minV = SYNCTEX_VERT(node);
+ maxV = minV + SYNCTEX_ABS_DEPTH(node);
+ minV -= SYNCTEX_ABS_HEIGHT(node);
+ /* In what region is the point hitPoint=(H,V) ? */
+ if (hitPoint.v<minV) {
+ if (hitPoint.h<minH) {
+ /* This is region 1. The distance to the box is the L1 distance PA. */
+ result = minV - hitPoint.v + minH - hitPoint.h;/* Integer overflow? probability epsilon */
+ } else if (hitPoint.h<=maxH) {
+ /* This is region 2. The distance to the box is the geometrical distance to the top edge. */
+ result = minV - hitPoint.v;
+ } else {
+ /* This is region 3. The distance to the box is the L1 distance PB. */
+ result = minV - hitPoint.v + hitPoint.h - maxH;
+ }
+ } else if (hitPoint.v<=maxV) {
+ if (hitPoint.h<minH) {
+ /* This is region 4. The distance to the box is the geometrical distance to the left edge. */
+ result = minH - hitPoint.h;
+ } else if (hitPoint.h<=maxH) {
+ /* This is region 4. We are inside the box. */
+ result = 0;
+ } else {
+ /* This is region 6. The distance to the box is the geometrical distance to the right edge. */
+ result = hitPoint.h - maxH;
+ }
+ } else {
+ if (hitPoint.h<minH) {
+ /* This is region 7. The distance to the box is the L1 distance PC. */
+ result = hitPoint.v - maxV + minH - hitPoint.h;
+ } else if (hitPoint.h<=maxH) {
+ /* This is region 8. The distance to the box is the geometrical distance to the top edge. */
+ result = hitPoint.v - maxV;
+ } else {
+ /* This is region 9. The distance to the box is the L1 distance PD. */
+ result = hitPoint.v - maxV + hitPoint.h - maxH;
+ }
+ }
+ break;
+ case synctex_node_type_kern:
+ maxH = SYNCTEX_WIDTH(node);
+ if (maxH<0) {
+ minH = SYNCTEX_HORIZ(node);
+ maxH = minH - maxH;
+ } else {
+ minH = -maxH;
+ maxH = SYNCTEX_HORIZ(node);
+ minH += maxH;
+ }
+ minV = SYNCTEX_VERT(node);
+ if (hitPoint.h<minH) {
+ if (hitPoint.v>minV) {
+ result = hitPoint.v - minV + minH - hitPoint.h;
+ } else {
+ result = minV - hitPoint.v + minH - hitPoint.h;
+ }
+ } else if (hitPoint.h>maxH) {
+ if (hitPoint.v>minV) {
+ result = hitPoint.v - minV + hitPoint.h - maxH;
+ } else {
+ result = minV - hitPoint.v + hitPoint.h - maxH;
+ }
+ } else if (hitPoint.v>minV) {
+ result = hitPoint.v - minV;
+ } else {
+ result = minV - hitPoint.v;
+ }
+ break;
+ case synctex_node_type_glue:
+ case synctex_node_type_math:
+ minH = SYNCTEX_HORIZ(node);
+ minV = SYNCTEX_VERT(node);
+ if (hitPoint.h<minH) {
+ if (hitPoint.v>minV) {
+ result = hitPoint.v - minV + minH - hitPoint.h;
+ } else {
+ result = minV - hitPoint.v + minH - hitPoint.h;
+ }
+ } else if (hitPoint.v>minV) {
+ result = hitPoint.v - minV + hitPoint.h - minH;
+ } else {
+ result = minV - hitPoint.v + hitPoint.h - minH;
+ }
+ break;
+ }
+ }
+ return result;
+}
+
+static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) {
+ if (node) {
+ synctex_node_t result = NULL;
+ synctex_node_t child = NULL;
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_hbox:
+ /* test the deep nodes first */
+ if ((child = SYNCTEX_CHILD(node))) {
+ do {
+ if ((result = _synctex_eq_deepest_container(hitPoint,child,visible))) {
+ return result;
+ }
+ } while((child = SYNCTEX_SIBLING(child)));
+ }
+ /* is the hit point inside the box? */
+ if (_synctex_point_in_box(hitPoint,node,visible)) {
+ /* for vboxes we try to use some node inside.
+ * Walk through the list of siblings until we find the closest one.
+ * Only consider siblings with children. */
+ if ((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) {
+ int bestDistance = INT_MAX;
+ do {
+ if (SYNCTEX_CHILD(child)) {
+ int distance = _synctex_node_distance_to_point(hitPoint,child,visible);
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ node = child;
+ }
+ }
+ } while((child = SYNCTEX_SIBLING(child)));
+ }
+ return node;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box.
+ * As it is an horizontal box, we only compare horizontal coordinates. */
+SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible);
+SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) {
+ int result = 0;
+ if ((node = SYNCTEX_CHILD(node))) {
+ do {
+ int off7 = _synctex_point_h_distance(hitPoint,node,visible);
+ if (off7 > 0) {
+ /* node is to the right of the hit point.
+ * We compare node and the previously recorded one, through the recorded distance.
+ * If the nodes have the same tag, prefer the one with the smallest line number,
+ * if the nodes also have the same line number, prefer the one with the smallest column. */
+ if (bestDistancesRef->right > off7) {
+ bestDistancesRef->right = off7;
+ bestNodesRef->right = node;
+ result |= SYNCTEX_MASK_RIGHT;
+ } else if (bestDistancesRef->right == off7 && bestNodesRef->right) {
+ if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node)
+ && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node)
+ || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node)
+ && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) {
+ bestNodesRef->right = node;
+ result |= SYNCTEX_MASK_RIGHT;
+ }
+ }
+ } else if (off7 == 0) {
+ /* hitPoint is inside node. */
+ bestDistancesRef->left = bestDistancesRef->right = 0;
+ bestNodesRef->left = node;
+ bestNodesRef->right = NULL;
+ result |= SYNCTEX_MASK_LEFT;
+ } else { /* here off7 < 0, hitPoint is to the right of node */
+ off7 = -off7;
+ if (bestDistancesRef->left > off7) {
+ bestDistancesRef->left = off7;
+ bestNodesRef->left = node;
+ result |= SYNCTEX_MASK_LEFT;
+ } else if (bestDistancesRef->left == off7 && bestNodesRef->left) {
+ if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node)
+ && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node)
+ || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node)
+ && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) {
+ bestNodesRef->left = node;
+ result |= SYNCTEX_MASK_LEFT;
+ }
+ }
+ }
+ } while((node = SYNCTEX_SIBLING(node)));
+ if (result & SYNCTEX_MASK_LEFT) {
+ /* the left node is new, try to narrow the result */
+ if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) {
+ bestNodesRef->left = node;
+ }
+ if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) {
+ bestNodesRef->left = node;
+ }
+ }
+ if (result & SYNCTEX_MASK_RIGHT) {
+ /* the right node is new, try to narrow the result */
+ if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) {
+ bestNodesRef->right = node;
+ }
+ if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) {
+ bestNodesRef->right = node;
+ }
+ }
+ }
+ return result;
+}
+SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible);
+SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) {
+ int result = 0;
+ if ((node = SYNCTEX_CHILD(node))) {
+ do {
+ int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */
+ if (off7 > 0) {
+ /* node is to the top of the hit point (below because TeX is oriented from top to bottom.
+ * We compare node and the previously recorded one, through the recorded distance.
+ * If the nodes have the same tag, prefer the one with the smallest line number,
+ * if the nodes also have the same line number, prefer the one with the smallest column. */
+ if (bestDistancesRef->right > off7) {
+ bestDistancesRef->right = off7;
+ bestNodesRef->right = node;
+ result |= SYNCTEX_MASK_RIGHT;
+ } else if (bestDistancesRef->right == off7 && bestNodesRef->right) {
+ if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node)
+ && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node)
+ || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node)
+ && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) {
+ bestNodesRef->right = node;
+ result |= SYNCTEX_MASK_RIGHT;
+ }
+ }
+ } else if (off7 == 0) {
+ bestDistancesRef->left = bestDistancesRef->right = 0;
+ bestNodesRef->left = node;
+ bestNodesRef->right = NULL;
+ result |= SYNCTEX_MASK_LEFT;
+ } else { /* here off7 < 0 */
+ off7 = -off7;
+ if (bestDistancesRef->left > off7) {
+ bestDistancesRef->left = off7;
+ bestNodesRef->left = node;
+ result |= SYNCTEX_MASK_LEFT;
+ } else if (bestDistancesRef->left == off7 && bestNodesRef->left) {
+ if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node)
+ && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node)
+ || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node)
+ && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) {
+ bestNodesRef->left = node;
+ result |= SYNCTEX_MASK_LEFT;
+ }
+ }
+ }
+ } while((node = SYNCTEX_SIBLING(node)));
+ if (result & SYNCTEX_MASK_LEFT) {
+ /* the left node is new, try to narrow the result */
+ if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) {
+ bestNodesRef->left = node;
+ }
+ if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) {
+ bestNodesRef->left = node;
+ }
+ }
+ if (result & SYNCTEX_MASK_RIGHT) {
+ /* the right node is new, try to narrow the result */
+ if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) {
+ bestNodesRef->right = node;
+ }
+ if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) {
+ bestNodesRef->right = node;
+ }
+ }
+ }
+ return result;
+}
+SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) {
+ if (node) {
+ switch(node->class->type) {
+ case synctex_node_type_hbox:
+ return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible);
+ case synctex_node_type_vbox:
+ return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible);
+ }
+ }
+ return 0;
+}
+
+SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible);
+SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) {
+ synctex_node_t best_node = NULL;
+ if ((node = SYNCTEX_CHILD(node))) {
+ do {
+ int distance = _synctex_node_distance_to_point(hitPoint,node,visible);
+ synctex_node_t candidate = NULL;
+ if (distance<=*distanceRef) {
+ *distanceRef = distance;
+ best_node = node;
+ }
+ switch(node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_hbox:
+ if ((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) {
+ best_node = candidate;
+ }
+ }
+ } while((node = SYNCTEX_SIBLING(node)));
+ }
+ return best_node;
+}
+SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) {
+ if (node) {
+ switch(node->class->type) {
+ case synctex_node_type_hbox:
+ case synctex_node_type_vbox:
+ {
+ int best_distance = INT_MAX;
+ synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible);
+ if ((best_node)) {
+ synctex_node_t child = NULL;
+ switch(best_node->class->type) {
+ case synctex_node_type_vbox:
+ case synctex_node_type_hbox:
+ if ((child = SYNCTEX_CHILD(best_node))) {
+ best_distance = _synctex_node_distance_to_point(hitPoint,child,visible);
+ while((child = SYNCTEX_SIBLING(child))) {
+ int distance = _synctex_node_distance_to_point(hitPoint,child,visible);
+ if (distance<=best_distance) {
+ best_distance = distance;
+ best_node = child;
+ }
+ }
+ }
+ }
+ }
+ return best_node;
+ }
+ }
+ }
+ return NULL;
+}
+
+# ifdef SYNCTEX_NOTHING
+# pragma mark -
+# pragma mark Updater
+# endif
+
+typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */
+
+# define SYNCTEX_BITS_PER_BYTE 8
+
+struct __synctex_updater_t {
+ gzFile file; /* the foo.synctex or foo.synctex.gz I/O identifier */
+ synctex_fprintf_t fprintf; /* either fprintf or gzprintf */
+ int length; /* the number of chars appended */
+ struct _flags {
+ unsigned int no_gz:1; /* Whether zlib is used or not */
+ unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */
+ } flags;
+};
+# define SYNCTEX_FILE updater->file
+# define SYNCTEX_NO_GZ ((updater->flags).no_gz)
+# define SYNCTEX_fprintf (*(updater->fprintf))
+
+synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) {
+ synctex_updater_t updater = NULL;
+ char * synctex = NULL;
+ synctex_io_mode_t io_mode = 0;
+ const char * mode = NULL;
+ /* prepare the updater, the memory is the only one dynamically allocated */
+ updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t));
+ if (NULL == updater) {
+ _synctex_error("! synctex_updater_new_with_file: malloc problem");
+ return NULL;
+ }
+ if (_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_ADD_QUOTES,&io_mode)
+ && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_DONT_ADD_QUOTES,&io_mode)) {
+return_on_error:
+ free(updater);
+ updater = NULL;
+ return NULL;
+ }
+ /* OK, the file exists, we close it and reopen it with the correct mode.
+ * The receiver is now the owner of the "synctex" variable. */
+ gzclose(SYNCTEX_FILE);
+ SYNCTEX_FILE = NULL;
+ SYNCTEX_NO_GZ = (io_mode&synctex_io_gz_mask)?synctex_NO:synctex_YES;
+ mode = _synctex_get_io_mode_name(io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */
+ if (SYNCTEX_NO_GZ) {
+ if (NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) {
+no_write_error:
+ _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex);
+ free(synctex);
+ goto return_on_error;
+ }
+ updater->fprintf = (synctex_fprintf_t)(&fprintf);
+ } else {
+ if (NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) {
+ goto no_write_error;
+ }
+ updater->fprintf = (synctex_fprintf_t)(&gzprintf);
+ }
+ printf("SyncTeX: updating %s...",synctex);
+ free(synctex);
+ return updater;
+}
+
+
+void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){
+ if (NULL==updater) {
+ return;
+ }
+ if (magnification && strlen(magnification)) {
+ updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification);
+ }
+}
+
+void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){
+ if (NULL==updater) {
+ return;
+ }
+ if (x_offset && strlen(x_offset)) {
+ updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset);
+ }
+}
+
+void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){
+ if (NULL==updater) {
+ return;
+ }
+ if (y_offset && strlen(y_offset)) {
+ updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset);
+ }
+}
+
+void synctex_updater_free(synctex_updater_t updater){
+ if (NULL==updater) {
+ return;
+ }
+ if (updater->length>0) {
+ SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length);
+ }
+ if (SYNCTEX_NO_GZ) {
+ fclose((FILE *)SYNCTEX_FILE);
+ } else {
+ gzclose((gzFile)SYNCTEX_FILE);
+ }
+ free(updater);
+ printf("... done.\n");
+ return;
+}
diff --git a/src/cpp/core/tex/synctex/synctex_parser.h b/src/cpp/core/tex/synctex/synctex_parser.h
new file mode 100644
index 0000000..3151707
--- /dev/null
+++ b/src/cpp/core/tex/synctex/synctex_parser.h
@@ -0,0 +1,360 @@
+/*
+Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
+
+This file is part of the SyncTeX package.
+
+Latest Revision: Tue Jun 14 08:23:30 UTC 2011
+
+Version: 1.17
+
+See synctex_parser_readme.txt for more details
+
+License:
+--------
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE
+
+Except as contained in this notice, the name of the copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization from the copyright holder.
+
+Acknowledgments:
+----------------
+The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
+and significant help from XeTeX developer Jonathan Kew
+
+Nota Bene:
+----------
+If you include or use a significant part of the synctex package into a software,
+I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
+
+Version 1
+Thu Jun 19 09:39:21 UTC 2008
+
+*/
+
+#ifndef __SYNCTEX_PARSER__
+# define __SYNCTEX_PARSER__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* synctex_node_t is the type for all synctex nodes.
+ * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */
+typedef struct _synctex_node * synctex_node_t;
+
+/* The main synctex object is a scanner
+ * Its implementation is considered private.
+ * The basic workflow is
+ * - create a "synctex scanner" with the contents of a file
+ * - perform actions on that scanner like display or edit queries
+ * - free the scanner when the work is done
+ */
+typedef struct __synctex_scanner_t _synctex_scanner_t;
+typedef _synctex_scanner_t * synctex_scanner_t;
+
+/* This is the designated method to create a new synctex scanner object.
+ * output is the pdf/dvi/xdv file associated to the synctex file.
+ * If necessary, it can be the tex file that originated the synctex file
+ * but this might cause problems if the \jobname has a custom value.
+ * Despite this method can accept a relative path in practice,
+ * you should only pass a full path name.
+ * The path should be encoded by the underlying file system,
+ * assuming that it is based on 8 bits characters, including UTF8,
+ * not 16 bits nor 32 bits.
+ * The last file extension is removed and replaced by the proper extension.
+ * Then the private method _synctex_scanner_new_with_contents_of_file is called.
+ * NULL is returned in case of an error or non existent file.
+ * Once you have a scanner, use the synctex_display_query and synctex_edit_query below.
+ * The new "build_directory" argument is available since version 1.5.
+ * It is the directory where all the auxiliary stuff is created.
+ * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory.
+ * This is the case in MikTeX (I will include this into TeX Live).
+ * This directory path can be nil, it will be ignored then.
+ * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file.
+ * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory.
+ * Please note that this new "build_directory" is provided as a convenient argument but should not be used.
+ * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file.
+ * The new "parse" argument is available since version 1.5. In general, use 1.
+ * Use 0 only if you do not want to parse the content but just check the existence.
+ */
+synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse);
+
+/* This is the designated method to delete a synctex scanner object.
+ * Frees all the memory, you must call it when you are finished with the scanner.
+ */
+void synctex_scanner_free(synctex_scanner_t scanner);
+
+/* Send this message to force the scanner to parse the contents of the synctex output file.
+ * Nothing is performed if the file was already parsed.
+ * In each query below, this message is sent, but if you need to access information more directly,
+ * you must be sure that the parsing did occur.
+ * Usage:
+ * if((my_scanner = synctex_scanner_parse(my_scanner))) {
+ * continue with my_scanner...
+ * } else {
+ * there was a problem
+ * }
+ */
+synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner);
+
+/* The main entry points.
+ * Given the file name, a line and a column number, synctex_display_query returns the number of nodes
+ * satisfying the contrain. Use code like
+ *
+ * if(synctex_display_query(scanner,name,line,column)>0) {
+ * synctex_node_t node;
+ * while((node = synctex_next_result(scanner))) {
+ * // do something with node
+ * ...
+ * }
+ * }
+ *
+ * For example, one can
+ * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v
+ * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions
+ * - highlight just the character using that information
+ *
+ * Given the page and the position in the page, synctex_edit_query returns the number of nodes
+ * satisfying the contrain. Use code like
+ *
+ * if(synctex_edit_query(scanner,page,h,v)>0) {
+ * synctex_node_t node;
+ * while(node = synctex_next_result(scanner)) {
+ * // do something with node
+ * ...
+ * }
+ * }
+ *
+ * For example, one can
+ * - highlight each resulting line in the input,
+ * - highlight just the character using that information
+ *
+ * page is 1 based
+ * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page.
+ * If you make a new query, the result of the previous one is discarded.
+ * If one of this function returns a non positive integer,
+ * it means that an error occurred.
+ *
+ * Both methods are conservative, in the sense that matching is weak.
+ * If the exact column number is not found, there will be an answer with the whole line.
+ *
+ * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library.
+ * You can browse their code for a concrete implementation.
+ */
+int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column);
+int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v);
+synctex_node_t synctex_next_result(synctex_scanner_t scanner);
+
+/* Display all the information contained in the scanner object.
+ * If the records are too numerous, only the first ones are displayed.
+ * This is mainly for informatinal purpose to help developers.
+ */
+void synctex_scanner_display(synctex_scanner_t scanner);
+
+/* The x and y offset of the origin in TeX coordinates. The magnification
+ These are used by pdf viewers that want to display the real box size.
+ For example, getting the horizontal coordinates of a node would require
+ synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner)
+ Getting its TeX width would simply require
+ synctex_node_box_width(node)*synctex_scanner_magnification(scanner)
+ but direct methods are available for that below.
+ */
+int synctex_scanner_x_offset(synctex_scanner_t scanner);
+int synctex_scanner_y_offset(synctex_scanner_t scanner);
+float synctex_scanner_magnification(synctex_scanner_t scanner);
+
+/* Managing the input file names.
+ * Given a tag, synctex_scanner_get_name will return the corresponding file name.
+ * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag.
+ * The file name must be the very same as understood by TeX.
+ * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex.
+ * No automatic path expansion is performed.
+ * Finally, synctex_scanner_input is the first input node of the scanner.
+ * To browse all the input node, use a loop like
+ *
+ * if((input_node = synctex_scanner_input(scanner))){
+ * do {
+ * blah
+ * } while((input_node=synctex_node_sibling(input_node)));
+ * }
+ *
+ * The output is the name that was used to create the scanner.
+ * The synctex is the real name of the synctex file,
+ * it was obtained from output by setting the proper file extension.
+ */
+const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag);
+int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name);
+synctex_node_t synctex_scanner_input(synctex_scanner_t scanner);
+const char * synctex_scanner_get_output(synctex_scanner_t scanner);
+const char * synctex_scanner_get_synctex(synctex_scanner_t scanner);
+
+/* Browsing the nodes
+ * parent, child and sibling are standard names for tree nodes.
+ * The parent is one level higher, the child is one level deeper,
+ * and the sibling is at the same level.
+ * The sheet of a node is the first ancestor, it is of type sheet.
+ * A node and its sibling have the same parent.
+ * A node is the parent of its child.
+ * A node is either the child of its parent,
+ * or belongs to the sibling chain of its parent's child.
+ * The next node is either the child, the sibling or the parent's sibling,
+ * unless the parent is a sheet.
+ * This allows to navigate through all the nodes of a given sheet node:
+ *
+ * synctex_node_t node = sheet;
+ * while((node = synctex_node_next(node))) {
+ * // do something with node
+ * }
+ *
+ * With synctex_sheet_content, you can retrieve the sheet node given the page.
+ * The page is 1 based, according to TeX standards.
+ * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node.
+ */
+synctex_node_t synctex_node_parent(synctex_node_t node);
+synctex_node_t synctex_node_sheet(synctex_node_t node);
+synctex_node_t synctex_node_child(synctex_node_t node);
+synctex_node_t synctex_node_sibling(synctex_node_t node);
+synctex_node_t synctex_node_next(synctex_node_t node);
+synctex_node_t synctex_sheet(synctex_scanner_t scanner,int page);
+synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page);
+
+/* These are the types of the synctex nodes */
+typedef enum {
+ synctex_node_type_error = 0,
+ synctex_node_type_input,
+ synctex_node_type_sheet,
+ synctex_node_type_vbox,
+ synctex_node_type_void_vbox,
+ synctex_node_type_hbox,
+ synctex_node_type_void_hbox,
+ synctex_node_type_kern,
+ synctex_node_type_glue,
+ synctex_node_type_math,
+ synctex_node_type_boundary,
+ synctex_node_number_of_types
+} synctex_node_type_t;
+
+/* synctex_node_type gives the type of a given node,
+ * synctex_node_isa gives the same information as a human readable text. */
+synctex_node_type_t synctex_node_type(synctex_node_t node);
+const char * synctex_node_isa(synctex_node_t node);
+
+/* This is primarily used for debugging purpose.
+ * The second one logs information for the node and recursively displays information for its next node */
+void synctex_node_log(synctex_node_t node);
+void synctex_node_display(synctex_node_t node);
+
+/* Given a node, access to the location in the synctex file where it is defined.
+ */
+typedef unsigned int synctex_charindex_t;
+synctex_charindex_t synctex_node_charindex(synctex_node_t node);
+
+/* Given a node, access to its tag, line and column.
+ * The line and column numbers are 1 based.
+ * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line.
+ * When the tag is known, the scanner of the node will give the corresponding file name.
+ * When the tag is known, the scanner of the node will give the name.
+ */
+int synctex_node_tag(synctex_node_t node);
+int synctex_node_line(synctex_node_t node);
+int synctex_node_column(synctex_node_t node);
+
+/* In order to enhance forward synchronization,
+ * non void horizontal boxes have supplemental cached information.
+ * The mean line is the average of the line numbers of the included nodes.
+ * The child count is the number of chidren.
+ */
+int synctex_node_mean_line(synctex_node_t node);
+int synctex_node_child_count(synctex_node_t node);
+
+/* This is the page where the node appears.
+ * This is a 1 based index as given by TeX.
+ */
+int synctex_node_page(synctex_node_t node);
+
+/* For quite all nodes, horizontal, vertical coordinates, and width.
+ * These are expressed in TeX small points coordinates, with origin at the top left corner.
+ */
+int synctex_node_h(synctex_node_t node);
+int synctex_node_v(synctex_node_t node);
+int synctex_node_width(synctex_node_t node);
+
+/* For all nodes, dimensions of the enclosing box.
+ * These are expressed in TeX small points coordinates, with origin at the top left corner.
+ * A box is enclosing itself.
+ */
+int synctex_node_box_h(synctex_node_t node);
+int synctex_node_box_v(synctex_node_t node);
+int synctex_node_box_width(synctex_node_t node);
+int synctex_node_box_height(synctex_node_t node);
+int synctex_node_box_depth(synctex_node_t node);
+
+/* For quite all nodes, horizontal, vertical coordinates, and width.
+ * The visible dimensions are bigger than real ones to compensate 0 width boxes
+ * that do contain nodes.
+ * These are expressed in page coordinates, with origin at the top left corner.
+ * A box is enclosing itself.
+ */
+float synctex_node_visible_h(synctex_node_t node);
+float synctex_node_visible_v(synctex_node_t node);
+float synctex_node_visible_width(synctex_node_t node);
+/* For all nodes, visible dimensions of the enclosing box.
+ * A box is enclosing itself.
+ * The visible dimensions are bigger than real ones to compensate 0 width boxes
+ * that do contain nodes.
+ */
+float synctex_node_box_visible_h(synctex_node_t node);
+float synctex_node_box_visible_v(synctex_node_t node);
+float synctex_node_box_visible_width(synctex_node_t node);
+float synctex_node_box_visible_height(synctex_node_t node);
+float synctex_node_box_visible_depth(synctex_node_t node);
+
+/* The main synctex updater object.
+ * This object is used to append information to the synctex file.
+ * Its implementation is considered private.
+ * It is used by the synctex command line tool to take into account modifications
+ * that could occur while postprocessing files by dvipdf like filters.
+ */
+typedef struct __synctex_updater_t _synctex_updater_t;
+typedef _synctex_updater_t * synctex_updater_t;
+
+/* Designated initializer.
+ * Once you are done with your whole job,
+ * free the updater */
+synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory);
+
+/* Use the next functions to append records to the synctex file,
+ * no consistency tests made on the arguments */
+void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification);
+void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset);
+void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset);
+
+/* You MUST free the updater, once everything is properly appended */
+void synctex_updater_free(synctex_updater_t updater);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/tex/synctex/synctex_parser_readme.txt b/src/cpp/core/tex/synctex/synctex_parser_readme.txt
new file mode 100644
index 0000000..991262c
--- /dev/null
+++ b/src/cpp/core/tex/synctex/synctex_parser_readme.txt
@@ -0,0 +1,169 @@
+This file is part of the SyncTeX package.
+
+The Synchronization TeXnology named SyncTeX is a new feature
+of recent TeX engines designed by Jerome Laurens.
+It allows to synchronize between input and output, which means to
+navigate from the source document to the typeset material and vice versa.
+More informations on http://itexmac2.sourceforge.net/SyncTeX.html
+
+This package is mainly for developers, it mainly contains the following files:
+
+synctex_parser_readme.txt
+synctex_parser_version.txt
+synctex_parser_utils.c
+synctex_parser_utils.h
+synctex_parser_local.h
+synctex_parser.h
+synctex_parser.c
+
+The file you are reading contains more informations about the SyncTeX parser history.
+
+In order to support SyncTeX in a viewer, it is sufficient to include
+in the source the files synctex_parser.h and synctex_parser.c.
+The synctex parser usage is described in synctex_parser.h header file.
+
+The other files are used by tex engines or by the synctex command line utility:
+
+ChangeLog
+README.txt
+am
+man1
+man5
+synctex-common.h
+synctex-convert.sh
+synctex-e-mem.ch0
+synctex-e-mem.ch1
+synctex-e-rec.ch0
+synctex-e-rec.ch1
+synctex-etex.h
+synctex-mem.ch0
+synctex-mem.ch1
+synctex-mem.ch2
+synctex-pdf-rec.ch2
+synctex-pdftex.h
+synctex-rec.ch0
+synctex-rec.ch1
+synctex-rec.ch2
+synctex-tex.h
+synctex-xe-mem.ch2
+synctex-xe-rec.ch2
+synctex-xe-rec.ch3
+synctex-xetex.h
+synctex.c
+synctex.defines
+synctex.h
+synctex_main.c
+tests
+
+
+Version:
+--------
+This is version 1, which refers to the synctex output file format.
+The files are identified by a build number.
+In order to help developers to automatically manage the version and build numbers
+and download the parser only when necessary, the synctex_parser.version
+is an ASCII text file just containing the current version and build numbers.
+
+History:
+--------
+1.1: Thu Jul 17 09:28:13 UTC 2008
+- First official version available in TeXLive 2008 DVD.
+ Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below.
+1.2: Tue Sep 2 10:28:32 UTC 2008
+- Correction for ConTeXt support in the edit query.
+ The previous method was assuming that TeX boxes do not overlap,
+ which is reasonable for LaTeX but not for ConTeXt.
+ This assumption is no longer considered.
+1.3: Fri Sep 5 09:39:57 UTC 2008
+- Local variable "read" renamed to "already_read" to avoid conflicts.
+- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance
+- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman)
+- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization
+1.4: Fri Sep 12 08:12:34 UTC 2008
+- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747).
+ As a consequence, a crash was observed.
+- Some typos are fixed.
+1.6: Mon Nov 3 20:20:02 UTC 2008
+- The bug that prevented synchronization with compressed files on windows has been fixed.
+- New interface to allow system specific customization.
+- Note that some APIs have changed.
+1.8: Mer 8 jul 2009 11:32:38 UTC
+Note that version 1.7 was delivered privately.
+- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation
+- bug fix: the synctex command line tool was broken when updating a .synctex file
+- enhancement: better accuracy of the synchronization process
+- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory.
+ The new -d option of the synctex command line tool manages this situation.
+ This is handy when using something like tex -output-directory=DIR ...
+1.9: Wed Nov 4 11:52:35 UTC 2009
+- Various typo fixed
+- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing
+- New conditional created because OutputDebugStringA is only available since Windows 2K professional
+1.10: Sun Jan 10 10:12:32 UTC 2010
+- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment.
+ Concerns the synctex tool.
+1.11: Sun Jan 17 09:12:31 UTC 2010
+- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'.
+ Only 3rd party tools are concerned.
+1.12: Mon Jul 19 21:52:10 UTC 2010
+- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return,
+causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince.
+1.13: Fri Mar 11 07:39:12 UTC 2011
+- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388).
+- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior).
+ Only 3rd party tools are concerned.
+1.14: Fri Apr 15 19:10:57 UTC 2011
+- taking output_directory into account
+- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file.
+- Merging with LuaTeX's version of synctex.c
+1.15: Fri Jun 10 14:10:17 UTC 2011
+This concerns the synctex command line tool and 3rd party developers.
+TeX and friends are not concerned by these changes.
+- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned
+- Support for LuaTeX convention of './' file prefixing
+1.16: Tue Jun 14 08:23:30 UTC 2011
+This concerns the synctex command line tool and 3rd party developers.
+TeX and friends are not concerned by these changes.
+- Better forward search (thanks Jose Alliste)
+- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows
+1.17: Fri Oct 14 08:15:16 UTC 2011
+This concerns the synctex command line tool and 3rd party developers.
+TeX and friends are not concerned by these changes.
+- synctex_parser.c: cosmetic changes to enhance code readability
+- Better forward synchronization.
+ The problem occurs for example with LaTeX \item command.
+ The fact is that this command creates nodes at parse time but these nodes are used only
+ after the text material of the \item is displayed on the page. The consequence is that sometimes,
+ forward synchronization spots an irrelevant point from the point of view of the editing process.
+ This was due to some very basic filtering policy, where a somehow arbitrary choice was made when
+ many different possibilities where offered for synchronisation.
+ Now, forward synchronization prefers nodes inside an hbox with as many acceptable spots as possible.
+ This is achieved with the notion of mean line and node weight.
+- Adding support for the new file naming convention with './'
+ + function synctex_ignore_leading_dot_slash_in_path replaces synctex_ignore_leading_dot_slash
+ + function _synctex_is_equivalent_file_name is more permissive
+ Previously, the function synctex_scanner_get_tag would give an answer only when
+ the given file name was EXACTLY one of the file names listed in the synctex file.
+ The we added some changes accepting for example 'foo.tex' instead of './foo.tex'.
+ Now we have an even looser policy for dealing with file names.
+ If the given file name does not match exactly one the file names of the synctex file,
+ then we try to match the base names. If there is only one match of the base names,
+ then it is taken as a match for the whole names.
+ The base name is defined as following:
+ ./foo => foo
+ /my///.////foo => foo
+ /foo => /foo
+ /my//.foo => /my//.foo
+
+Acknowledgments:
+----------------
+The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
+and significant help from XeTeX developer Jonathan Kew
+
+Nota Bene:
+----------
+If you include or use a significant part of the synctex package into a software,
+I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
+
+Copyright (c) 2008-2011 jerome DOT laurens AT u-bourgogne DOT fr
+
diff --git a/src/cpp/core/tex/synctex/synctex_parser_utils.c b/src/cpp/core/tex/synctex/synctex_parser_utils.c
new file mode 100644
index 0000000..0000f7e
--- /dev/null
+++ b/src/cpp/core/tex/synctex/synctex_parser_utils.c
@@ -0,0 +1,497 @@
+/*
+Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
+
+This file is part of the SyncTeX package.
+
+Latest Revision: Tue Jun 14 08:23:30 UTC 2011
+
+Version: 1.17
+
+See synctex_parser_readme.txt for more details
+
+License:
+--------
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE
+
+Except as contained in this notice, the name of the copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization from the copyright holder.
+
+*/
+
+/* In this file, we find all the functions that may depend on the operating system. */
+
+#include <synctex_parser_utils.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <limits.h>
+#include <ctype.h>
+#include <string.h>
+
+#include <sys/stat.h>
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)
+#define SYNCTEX_WINDOWS 1
+#endif
+
+#ifdef _WIN32_WINNT_WINXP
+#define SYNCTEX_RECENT_WINDOWS 1
+#endif
+
+#ifdef SYNCTEX_WINDOWS
+#include <windows.h>
+#endif
+
+void *_synctex_malloc(size_t size) {
+ void * ptr = malloc(size);
+ if(ptr) {
+/* There used to be a switch to use bzero because it is more secure. JL */
+ memset(ptr,0, size);
+ }
+ return (void *)ptr;
+}
+
+int _synctex_error(const char * reason,...) {
+ va_list arg;
+ int result;
+ va_start (arg, reason);
+# ifdef SYNCTEX_RECENT_WINDOWS
+ {/* This code is contributed by William Blum.
+ As it does not work on some older computers,
+ the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one.
+ According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx
+ Minimum supported client Windows 2000 Professional
+ Minimum supported server Windows 2000 Server
+ People running Windows 2K standard edition will not have OutputDebugStringA.
+ JL.*/
+ char *buff;
+ size_t len;
+ OutputDebugStringA("SyncTeX ERROR: ");
+ len = _vscprintf(reason, arg) + 1;
+ buff = (char*)malloc( len * sizeof(char) );
+ result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: ");
+ OutputDebugStringA(buff);
+ OutputDebugStringA("\n");
+ free(buff);
+ }
+# else
+ result = fprintf(stderr,"SyncTeX ERROR: ");
+ result += vfprintf(stderr, reason, arg);
+ result += fprintf(stderr,"\n");
+# endif
+ va_end (arg);
+ return result;
+}
+
+/* strip the last extension of the given string, this string is modified! */
+void _synctex_strip_last_path_extension(char * string) {
+ if(NULL != string){
+ char * last_component = NULL;
+ char * last_extension = NULL;
+ char * next = NULL;
+ /* first we find the last path component */
+ if(NULL == (last_component = strstr(string,"/"))){
+ last_component = string;
+ } else {
+ ++last_component;
+ while((next = strstr(last_component,"/"))){
+ last_component = next+1;
+ }
+ }
+# ifdef SYNCTEX_WINDOWS
+ /* On Windows, the '\' is also a path separator. */
+ while((next = strstr(last_component,"\\"))){
+ last_component = next+1;
+ }
+# endif
+ /* then we find the last path extension */
+ if((last_extension = strstr(last_component,"."))){
+ ++last_extension;
+ while((next = strstr(last_extension,"."))){
+ last_extension = next+1;
+ }
+ --last_extension;/* back to the "." */
+ if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/
+ last_extension[0] = '\0';
+ }
+ }
+ }
+}
+
+synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name_ref)
+{
+ if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) {
+ do {
+ (*name_ref) += 2;
+ while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) {
+ ++(*name_ref);
+ }
+ } while(SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1]));
+ return synctex_YES;
+ }
+ return synctex_NO;
+}
+
+/* The base name is necessary to deal with the 2011 file naming convention...
+ * path is a '\0' terminated string
+ * The return value is the trailing part of the argument,
+ * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/
+const char * _synctex_base_name(const char *path) {
+ const char * ptr = path;
+ do {
+ if (synctex_ignore_leading_dot_slash_in_path(&ptr)) {
+ return ptr;
+ }
+ do {
+ if (!*(++ptr)) {
+ return path;
+ }
+ } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr));
+ } while (*(++ptr));
+ return path;
+}
+
+/* Compare two file names, windows is sometimes case insensitive... */
+synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) {
+ /* Remove the leading regex '(\./+)*' in both rhs and lhs */
+ synctex_ignore_leading_dot_slash_in_path(&lhs);
+ synctex_ignore_leading_dot_slash_in_path(&rhs);
+next_character:
+ if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */
+ if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */
+ return synctex_NO;
+ }
+ ++lhs;
+ ++rhs;
+ synctex_ignore_leading_dot_slash_in_path(&lhs);
+ synctex_ignore_leading_dot_slash_in_path(&rhs);
+ goto next_character;
+ } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */
+ return synctex_NO;
+ } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs,*rhs)){/* uppercase do not match */
+ return synctex_NO;
+ } else if (!*lhs) {/* lhs is at the end of the string */
+ return *rhs ? synctex_NO : synctex_YES;
+ } else if(!*rhs) {/* rhs is at the end of the string but not lhs */
+ return synctex_NO;
+ }
+ ++lhs;
+ ++rhs;
+ goto next_character;
+}
+
+synctex_bool_t _synctex_path_is_absolute(const char * name) {
+ if(!strlen(name)) {
+ return synctex_NO;
+ }
+# if SYNCTEX_WINDOWS
+ if(strlen(name)>2) {
+ return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO;
+ }
+ return synctex_NO;
+# else
+ return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO;
+# endif
+}
+
+/* We do not take care of UTF-8 */
+const char * _synctex_last_path_component(const char * name) {
+ const char * c = name+strlen(name);
+ if(c>name) {
+ if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) {
+ do {
+ --c;
+ if(SYNCTEX_IS_PATH_SEPARATOR(*c)) {
+ return c+1;
+ }
+ } while(c>name);
+ }
+ return c;/* the last path component is the void string*/
+ }
+ return c;
+}
+
+int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) {
+ const char * lpc;
+ if(src && dest_ref) {
+# define dest (*dest_ref)
+ dest = NULL; /* Default behavior: no change and sucess. */
+ lpc = _synctex_last_path_component(src);
+ if(strlen(lpc)) {
+ if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') {
+ /* We are in the situation where adding the quotes is allowed. */
+ /* Time to add the quotes. */
+ /* Consistency test: we must have dest+size>dest+strlen(dest)+2
+ * or equivalently: strlen(dest)+2<size (see below) */
+ if(strlen(src)<size) {
+ if((dest = (char *)malloc(size+2))) {
+ char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */
+ if(dest != strncpy(dest,src,size)) {
+ _synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem");
+ free(dest);
+ dest = NULL;/* Don't forget to reinitialize. */
+ return -2;
+ }
+ memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */
+ dpc[0]='"';
+ dpc[strlen(dpc)+1]='\0';/* Consistency test */
+ dpc[strlen(dpc)]='"';
+ return 0; /* Success. */
+ }
+ return -1; /* Memory allocation error. */
+ }
+ _synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency");
+ return -3;
+ }
+ return 0; /* Success. */
+ }
+ return 0; /* No last path component. */
+# undef dest
+ }
+ return 1; /* Bad parameter, this value is subject to changes. */
+}
+
+/* The client is responsible of the management of the returned string, if any. */
+char * _synctex_merge_strings(const char * first,...);
+
+char * _synctex_merge_strings(const char * first,...) {
+ va_list arg;
+ size_t size = 0;
+ const char * temp;
+ /* First retrieve the size necessary to store the merged string */
+ va_start (arg, first);
+ temp = first;
+ do {
+ size_t len = strlen(temp);
+ if(UINT_MAX-len<size) {
+ _synctex_error("! _synctex_merge_strings: Capacity exceeded.");
+ return NULL;
+ }
+ size+=len;
+ } while( (temp = va_arg(arg, const char *)) != NULL);
+ va_end(arg);
+ if(size>0) {
+ char * result = NULL;
+ ++size;
+ /* Create the memory storage */
+ if(NULL!=(result = (char *)malloc(size))) {
+ char * dest = result;
+ va_start (arg, first);
+ temp = first;
+ do {
+ if((size = strlen(temp))>0) {
+ /* There is something to merge */
+ if(dest != strncpy(dest,temp,size)) {
+ _synctex_error("! _synctex_merge_strings: Copy problem");
+ free(result);
+ result = NULL;
+ return NULL;
+ }
+ dest += size;
+ }
+ } while( (temp = va_arg(arg, const char *)) != NULL);
+ va_end(arg);
+ dest[0]='\0';/* Terminate the merged string */
+ return result;
+ }
+ _synctex_error("! _synctex_merge_strings: Memory problem");
+ return NULL;
+ }
+ return NULL;
+}
+
+/* The purpose of _synctex_get_name is to find the name of the synctex file.
+ * There is a list of possible filenames from which we return the most recent one and try to remove all the others.
+ * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate.
+ */
+int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref)
+{
+ if(output && synctex_name_ref && io_mode_ref) {
+ /* If output is already absolute, we just have to manage the quotes and the compress mode */
+ size_t size = 0;
+ char * synctex_name = NULL;
+ synctex_io_mode_t io_mode = *io_mode_ref;
+ const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/
+ /* Do we have a real base name ? */
+ if(strlen(base_name)>0) {
+ /* Yes, we do. */
+ const char * temp = NULL;
+ char * core_name = NULL; /* base name of output without path extension. */
+ char * dir_name = NULL; /* dir name of output */
+ char * quoted_core_name = NULL;
+ char * basic_name = NULL;
+ char * gz_name = NULL;
+ char * quoted_name = NULL;
+ char * quoted_gz_name = NULL;
+ char * build_name = NULL;
+ char * build_gz_name = NULL;
+ char * build_quoted_name = NULL;
+ char * build_quoted_gz_name = NULL;
+ struct stat buf;
+ time_t the_time = 0;
+ /* Create core_name: let temp point to the dot before the path extension of base_name;
+ * We start form the \0 terminating character and scan the string upward until we find a dot.
+ * The leading dot is not accepted. */
+ if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) {
+ /* There is a dot and it is not at the leading position */
+ if(NULL == (core_name = (char *)malloc(size+1))) {
+ _synctex_error("! _synctex_get_name: Memory problem 1");
+ return -1;
+ }
+ if(core_name != strncpy(core_name,base_name,size)) {
+ _synctex_error("! _synctex_get_name: Copy problem 1");
+ free(core_name);
+ dir_name = NULL;
+ return -2;
+ }
+ core_name[size] = '\0';
+ } else {
+ /* There is no path extension,
+ * Just make a copy of base_name */
+ core_name = _synctex_merge_strings(base_name);
+ }
+ /* core_name is properly set up, owned by "self". */
+ /* creating dir_name. */
+ size = strlen(output)-strlen(base_name);
+ if(size>0) {
+ /* output contains more than one path component */
+ if(NULL == (dir_name = (char *)malloc(size+1))) {
+ _synctex_error("! _synctex_get_name: Memory problem");
+ free(core_name);
+ dir_name = NULL;
+ return -1;
+ }
+ if(dir_name != strncpy(dir_name,output,size)) {
+ _synctex_error("! _synctex_get_name: Copy problem");
+ free(dir_name);
+ dir_name = NULL;
+ free(core_name);
+ dir_name = NULL;
+ return -2;
+ }
+ dir_name[size] = '\0';
+ }
+ /* dir_name is properly set up. It ends with a path separator, if non void. */
+ /* creating quoted_core_name. */
+ if(strchr(core_name,' ')) {
+ quoted_core_name = _synctex_merge_strings("\"",core_name,"\"");
+ }
+ /* quoted_core_name is properly set up. */
+ if(dir_name &&strlen(dir_name)>0) {
+ basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL);
+ if(quoted_core_name && strlen(quoted_core_name)>0) {
+ quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL);
+ }
+ } else {
+ basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL);
+ if(quoted_core_name && strlen(quoted_core_name)>0) {
+ quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL);
+ }
+ }
+ if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) {
+ temp = build_directory + size - 1;
+ if(_synctex_path_is_absolute(temp)) {
+ build_name = _synctex_merge_strings(build_directory,basic_name,NULL);
+ if(quoted_core_name && strlen(quoted_core_name)>0) {
+ build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL);
+ }
+ } else {
+ build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL);
+ if(quoted_core_name && strlen(quoted_core_name)>0) {
+ build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL);
+ }
+ }
+ }
+ if(basic_name) {
+ gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL);
+ }
+ if(quoted_name) {
+ quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL);
+ }
+ if(build_name) {
+ build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL);
+ }
+ if(build_quoted_name) {
+ build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL);
+ }
+ /* All the others names are properly set up... */
+ /* retain the most recently modified file */
+# define TEST(FILENAME,COMPRESS_MODE) \
+ if(FILENAME) {\
+ if (stat(FILENAME, &buf)) { \
+ free(FILENAME);\
+ FILENAME = NULL;\
+ } else if (buf.st_mtime>the_time) { \
+ the_time=buf.st_mtime; \
+ synctex_name = FILENAME; \
+ if (COMPRESS_MODE) { \
+ io_mode |= synctex_io_gz_mask; \
+ } else { \
+ io_mode &= ~synctex_io_gz_mask; \
+ } \
+ } \
+ }
+ TEST(basic_name,synctex_DONT_COMPRESS);
+ TEST(gz_name,synctex_COMPRESS);
+ TEST(quoted_name,synctex_DONT_COMPRESS);
+ TEST(quoted_gz_name,synctex_COMPRESS);
+ TEST(build_name,synctex_DONT_COMPRESS);
+ TEST(build_gz_name,synctex_COMPRESS);
+ TEST(build_quoted_name,synctex_DONT_COMPRESS);
+ TEST(build_quoted_gz_name,synctex_COMPRESS);
+# undef TEST
+ /* Free all the intermediate filenames, except the one that will be used as returned value. */
+# define CLEAN_AND_REMOVE(FILENAME) \
+ if(FILENAME && (FILENAME!=synctex_name)) {\
+ remove(FILENAME);\
+ printf("synctex tool info: %s removed\n",FILENAME);\
+ free(FILENAME);\
+ FILENAME = NULL;\
+ }
+ CLEAN_AND_REMOVE(basic_name);
+ CLEAN_AND_REMOVE(gz_name);
+ CLEAN_AND_REMOVE(quoted_name);
+ CLEAN_AND_REMOVE(quoted_gz_name);
+ CLEAN_AND_REMOVE(build_name);
+ CLEAN_AND_REMOVE(build_gz_name);
+ CLEAN_AND_REMOVE(build_quoted_name);
+ CLEAN_AND_REMOVE(build_quoted_gz_name);
+# undef CLEAN_AND_REMOVE
+ /* set up the returned values */
+ * synctex_name_ref = synctex_name;
+ * io_mode_ref = io_mode;
+ return 0;
+ }
+ return -1;/* bad argument */
+ }
+ return -2;
+}
+
+const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) {
+ static const char * synctex_io_modes[4] = {"r","rb","a","ab"};
+ unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste
+ return synctex_io_modes[index];
+}
diff --git a/src/cpp/core/tex/synctex/synctex_parser_utils.h b/src/cpp/core/tex/synctex/synctex_parser_utils.h
new file mode 100644
index 0000000..18348aa
--- /dev/null
+++ b/src/cpp/core/tex/synctex/synctex_parser_utils.h
@@ -0,0 +1,155 @@
+/*
+Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr
+
+This file is part of the SyncTeX package.
+
+Latest Revision: Tue Jun 14 08:23:30 UTC 2011
+
+Version: 1.17
+
+See synctex_parser_readme.txt for more details
+
+License:
+--------
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE
+
+Except as contained in this notice, the name of the copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization from the copyright holder.
+
+*/
+
+/* The utilities declared here are subject to conditional implementation.
+ * All the operating system special stuff goes here.
+ * The problem mainly comes from file name management: path separator, encoding...
+ */
+
+# define synctex_bool_t int
+# define synctex_YES -1
+# define synctex_ADD_QUOTES -1
+# define synctex_COMPRESS -1
+# define synctex_NO 0
+# define synctex_DONT_ADD_QUOTES 0
+# define synctex_DONT_COMPRESS 0
+
+#ifndef __SYNCTEX_PARSER_UTILS__
+# define __SYNCTEX_PARSER_UTILS__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FALSE 0
+#define TRUE !FALSE
+
+# if _WIN32
+# define SYNCTEX_CASE_SENSITIVE_PATH FALSE
+# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c)
+# else
+# define SYNCTEX_CASE_SENSITIVE_PATH TRUE
+# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c)
+# endif
+
+# if _WIN32
+# define SYNCTEX_IS_DOT(c) ('.' == c)
+# else
+# define SYNCTEX_IS_DOT(c) ('.' == c)
+# endif
+
+# if SYNCTEX_CASE_SENSITIVE_PATH
+# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right)
+# else
+# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right))
+# endif
+
+/* This custom malloc functions initializes to 0 the newly allocated memory.
+ * There is no bzero function on windows. */
+void *_synctex_malloc(size_t size);
+
+/* This is used to log some informational message to the standard error stream.
+ * On Windows, the stderr stream is not exposed and another method is used.
+ * The return value is the number of characters printed. */
+int _synctex_error(const char * reason,...);
+
+/* strip the last extension of the given string, this string is modified!
+ * This function depends on the OS because the path separator may differ.
+ * This should be discussed more precisely. */
+void _synctex_strip_last_path_extension(char * string);
+
+/* Compare two file names, windows is sometimes case insensitive...
+ * The given strings may differ stricto sensu, but represent the same file name.
+ * It might not be the real way of doing things.
+ * The return value is an undefined non 0 value when the two file names are equivalent.
+ * It is 0 otherwise. */
+synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs);
+
+/* Description forthcoming.*/
+synctex_bool_t _synctex_path_is_absolute(const char * name);
+
+/* Description forthcoming...*/
+const char * _synctex_last_path_component(const char * name);
+
+/* Description forthcoming...*/
+const char * _synctex_base_name(const char *path);
+
+/* If the core of the last path component of src is not already enclosed with double quotes ('"')
+ * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added.
+ * In all other cases, no destination buffer is created and the src is not copied.
+ * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter.
+ * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces
+ * were not managed in a standard way.
+ * On success, the caller owns the buffer pointed to by dest_ref (is any) and
+ * is responsible of freeing the memory when done.
+ * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/
+int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size);
+
+/* These are the possible extensions of the synctex file */
+extern const char * synctex_suffix;
+extern const char * synctex_suffix_gz;
+
+typedef unsigned int synctex_io_mode_t;
+
+typedef enum {
+ synctex_io_append_mask = 1,
+ synctex_io_gz_mask = synctex_io_append_mask<<1
+} synctex_io_mode_masks_t;
+
+typedef enum {
+ synctex_compress_mode_none = 0,
+ synctex_compress_mode_gz = 1
+} synctex_compress_mode_t;
+
+int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref);
+
+/* returns the correct mode required by fopen and gzopen from the given io_mode */
+const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode);
+
+synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cpp/core/text/DcfParser.cpp b/src/cpp/core/text/DcfParser.cpp
new file mode 100644
index 0000000..f451d81
--- /dev/null
+++ b/src/cpp/core/text/DcfParser.cpp
@@ -0,0 +1,177 @@
+/*
+ * DcfParser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/text/DcfParser.hpp>
+
+#include <iostream>
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <boost/regex.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+
+namespace core {
+namespace text {
+
+const char * const kDcfFieldRegex = "([^\\s]+?)\\s*\\:\\s*(.*)$";
+
+Error parseDcfFile(const std::string& dcfFileContents,
+ bool preserveKeyCase,
+ DcfFieldRecorder recordField,
+ std::string* pUserErrMsg)
+{
+ // split into lines
+ std::vector<std::string> dcfLines;
+ boost::algorithm::split(dcfLines,
+ dcfFileContents,
+ boost::algorithm::is_any_of("\n"));
+
+ // iterate over lines
+ int lineNumber = 0;
+ std::string currentKey;
+ std::string currentValue;
+ for(std::vector<std::string>::const_iterator it = dcfLines.begin();
+ it != dcfLines.end();
+ ++it)
+ {
+ lineNumber++;
+
+ // skip blank lines
+ if (it->empty() || boost::algorithm::trim_copy(*it).empty())
+ continue;
+
+ // skip comment lines
+ if (it->at(0) == '#')
+ continue;
+
+ // define regexes
+ boost::regex keyValueRegx(kDcfFieldRegex);
+ boost::regex continuationRegex("[\t\\s](.*)");
+
+ // look for a key-value pair line
+ boost::smatch keyValueMatch, continuationMatch;
+ if (regex_match(*it, keyValueMatch, keyValueRegx))
+ {
+ // if we have a pending key & value then resolve it
+ if (!currentKey.empty())
+ {
+ recordField(std::make_pair(currentKey,currentValue));
+ currentKey.clear();
+ currentValue.clear();
+ }
+
+ // update the current key and value
+ currentKey = preserveKeyCase ?
+ keyValueMatch[1] :
+ string_utils::toLower(keyValueMatch[1]);
+ currentValue = keyValueMatch[2];
+ }
+
+ // look for a continuation
+ else if (!currentKey.empty() &&
+ regex_match(*it, continuationMatch, continuationRegex))
+ {
+ currentValue.append("\n");
+ currentValue.append(continuationMatch[1]);
+ }
+
+ // invalid line
+ else
+ {
+ Error error = systemError(boost::system::errc::protocol_error,
+ ERROR_LOCATION);
+ boost::format fmt("file line number %1% is invalid");
+ *pUserErrMsg = boost::str(fmt % lineNumber);
+ error.addProperty("parse-error", *pUserErrMsg);
+ error.addProperty("line-contents", *it);
+ return error;
+ }
+ }
+
+ // resolve any pending key and value
+ if (!currentKey.empty())
+ recordField(std::make_pair(currentKey,currentValue));
+
+ return Success();
+}
+
+
+Error parseDcfFile(const FilePath& dcfFilePath,
+ bool preserveKeyCase,
+ DcfFieldRecorder recordField,
+ std::string* pUserErrMsg)
+{
+ // read the file
+ std::string dcfFileContents;
+ Error error = readStringFromFile(dcfFilePath,
+ &dcfFileContents,
+ string_utils::LineEndingPosix);
+ if (error)
+ {
+ error.addProperty("dcf-file", dcfFilePath.absolutePath());
+ *pUserErrMsg = error.summary();
+ return error;
+ }
+
+ return parseDcfFile(dcfFileContents,
+ preserveKeyCase,
+ recordField,
+ pUserErrMsg);
+}
+
+
+namespace {
+
+void mapInsert(std::map<std::string,std::string>* pMap,
+ const std::pair<std::string,std::string>& field)
+{
+ pMap->insert(field);
+}
+
+} // anonymous namespace
+
+
+Error parseDcfFile(const FilePath& dcfFilePath,
+ bool preserveKeyCase,
+ std::map<std::string,std::string>* pFields,
+ std::string* pUserErrMsg)
+{
+ return parseDcfFile(dcfFilePath,
+ preserveKeyCase,
+ boost::bind(mapInsert, pFields, _1),
+ pUserErrMsg);
+}
+
+std::string dcfMultilineAsFolded(const std::string& line)
+{
+ return boost::algorithm::trim_copy(
+ boost::regex_replace(line, boost::regex("\\s*\r?\n\\s*"), " "));
+}
+
+
+} // namespace dcf
+} // namespace core
diff --git a/src/cpp/core/text/TemplateFilter.cpp b/src/cpp/core/text/TemplateFilter.cpp
new file mode 100644
index 0000000..bd2c511
--- /dev/null
+++ b/src/cpp/core/text/TemplateFilter.cpp
@@ -0,0 +1,60 @@
+/*
+ * TemplateFilter.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/text/TemplateFilter.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+namespace core {
+namespace text {
+
+void handleTemplateRequest(const FilePath& templatePath,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // setup template variables
+ std::map<std::string,std::string> variables ;
+ http::Fields queryParams = request.queryParams();
+ for (http::Fields::const_iterator it = queryParams.begin();
+ it != queryParams.end();
+ ++it)
+ {
+ variables[it->first] = it->second;
+ }
+
+ // return browser page (processing template)
+ pResponse->setNoCacheHeaders();
+ text::TemplateFilter templateFilter(variables);
+ pResponse->setFile(templatePath, request, templateFilter);
+ pResponse->setContentType("text/html");
+
+}
+
+void handleSecureTemplateRequest(const std::string& username,
+ const FilePath& progressPagePath,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ handleTemplateRequest(progressPagePath, request, pResponse);
+}
+
+
+} // namespace text
+} // namespace core
+
+
diff --git a/src/cpp/core/zlib/.gitignore b/src/cpp/core/zlib/.gitignore
new file mode 100644
index 0000000..421d725
--- /dev/null
+++ b/src/cpp/core/zlib/.gitignore
@@ -0,0 +1 @@
+zconf.h
diff --git a/src/cpp/core/zlib/CMakeLists.txt b/src/cpp/core/zlib/CMakeLists.txt
new file mode 100644
index 0000000..0339318
--- /dev/null
+++ b/src/cpp/core/zlib/CMakeLists.txt
@@ -0,0 +1,104 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# NOTE: this is an embedded version of zlib (used on Windows only) based
+# on zlib 1.2.6 from http://zlib.net/zlib-1.2.6.tar.gz. To update to a new
+# version of zlib you can just copy the .h and .c files from the root of
+# the zlib distribution (but note that rather than copying zconf.h you should
+# copy zconf.h.cmakein so zconf.h can by dynamically configured
+
+project(CORE_ZLIB C)
+
+include(CheckTypeSize)
+include(CheckFunctionExists)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+# disable gcc visiblity attribute (not supported by some mingw versinos)
+set(EXTRA_COMPILE_DEFINITIONS "NO_VIZ")
+
+# add largefile support if we can
+check_type_size(off64_t OFF64_T)
+if(HAVE_OFF64_T)
+ set(EXTRA_COMPILE_DEFINITIONS
+ "${EXTRA_COMPILE_DEFINITIONS}; _LARGEFILE64_SOURCE=1")
+endif()
+
+
+#
+# Check for fseeko
+#
+check_function_exists(fseeko HAVE_FSEEKO)
+if(NOT HAVE_FSEEKO)
+ add_definitions(-DNO_FSEEKO)
+endif()
+
+#
+# Check for unistd.h
+#
+check_include_file(unistd.h Z_HAVE_UNISTD_H)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein
+ ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h @ONLY)
+
+set(ZLIB_PUBLIC_HDRS
+ zconf.h
+ zlib.h
+)
+set(ZLIB_PRIVATE_HDRS
+ crc32.h
+ deflate.h
+ gzguts.h
+ inffast.h
+ inffixed.h
+ inflate.h
+ inftrees.h
+ trees.h
+ zutil.h
+)
+set(ZLIB_SRCS
+ adler32.c
+ compress.c
+ crc32.c
+ deflate.c
+ gzclose.c
+ gzlib.c
+ gzread.c
+ gzwrite.c
+ inflate.c
+ infback.c
+ inftrees.c
+ inffast.c
+ trees.c
+ uncompr.c
+ zutil.c
+)
+
+# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION
+file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents)
+string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([0-9A-Za-z.]+)\".*"
+ "\\1" ZLIB_FULL_VERSION ${_zlib_h_contents})
+
+
+# define library
+add_library(rstudio-core-zlib STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
+
+# set extra compile definitions
+set_property(TARGET rstudio-core-zlib
+ PROPERTY COMPILE_DEFINITIONS ${EXTRA_COMPILE_DEFINITIONS})
+
+# link dependencies
+target_link_libraries(rstudio-core-zlib
+)
diff --git a/src/cpp/core/zlib/adler32.c b/src/cpp/core/zlib/adler32.c
new file mode 100644
index 0000000..a868f07
--- /dev/null
+++ b/src/cpp/core/zlib/adler32.c
@@ -0,0 +1,179 @@
+/* adler32.c -- compute the Adler-32 checksum of a data stream
+ * Copyright (C) 1995-2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+
+#define local static
+
+local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
+
+#define BASE 65521 /* largest prime smaller than 65536 */
+#define NMAX 5552
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+
+#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;}
+#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1);
+#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2);
+#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4);
+#define DO16(buf) DO8(buf,0); DO8(buf,8);
+
+/* use NO_DIVIDE if your processor does not do division in hardware --
+ try it both ways to see which is faster */
+#ifdef NO_DIVIDE
+/* note that this assumes BASE is 65521, where 65536 % 65521 == 15
+ (thank you to John Reiser for pointing this out) */
+# define CHOP(a) \
+ do { \
+ unsigned long tmp = a >> 16; \
+ a &= 0xffffUL; \
+ a += (tmp << 4) - tmp; \
+ } while (0)
+# define MOD28(a) \
+ do { \
+ CHOP(a); \
+ if (a >= BASE) a -= BASE; \
+ } while (0)
+# define MOD(a) \
+ do { \
+ CHOP(a); \
+ MOD28(a); \
+ } while (0)
+# define MOD63(a) \
+ do { /* this assumes a is not negative */ \
+ z_off64_t tmp = a >> 32; \
+ a &= 0xffffffffL; \
+ a += (tmp << 8) - (tmp << 5) + tmp; \
+ tmp = a >> 16; \
+ a &= 0xffffL; \
+ a += (tmp << 4) - tmp; \
+ tmp = a >> 16; \
+ a &= 0xffffL; \
+ a += (tmp << 4) - tmp; \
+ if (a >= BASE) a -= BASE; \
+ } while (0)
+#else
+# define MOD(a) a %= BASE
+# define MOD28(a) a %= BASE
+# define MOD63(a) a %= BASE
+#endif
+
+/* ========================================================================= */
+uLong ZEXPORT adler32(adler, buf, len)
+ uLong adler;
+ const Bytef *buf;
+ uInt len;
+{
+ unsigned long sum2;
+ unsigned n;
+
+ /* split Adler-32 into component sums */
+ sum2 = (adler >> 16) & 0xffff;
+ adler &= 0xffff;
+
+ /* in case user likes doing a byte at a time, keep it fast */
+ if (len == 1) {
+ adler += buf[0];
+ if (adler >= BASE)
+ adler -= BASE;
+ sum2 += adler;
+ if (sum2 >= BASE)
+ sum2 -= BASE;
+ return adler | (sum2 << 16);
+ }
+
+ /* initial Adler-32 value (deferred check for len == 1 speed) */
+ if (buf == Z_NULL)
+ return 1L;
+
+ /* in case short lengths are provided, keep it somewhat fast */
+ if (len < 16) {
+ while (len--) {
+ adler += *buf++;
+ sum2 += adler;
+ }
+ if (adler >= BASE)
+ adler -= BASE;
+ MOD28(sum2); /* only added so many BASE's */
+ return adler | (sum2 << 16);
+ }
+
+ /* do length NMAX blocks -- requires just one modulo operation */
+ while (len >= NMAX) {
+ len -= NMAX;
+ n = NMAX / 16; /* NMAX is divisible by 16 */
+ do {
+ DO16(buf); /* 16 sums unrolled */
+ buf += 16;
+ } while (--n);
+ MOD(adler);
+ MOD(sum2);
+ }
+
+ /* do remaining bytes (less than NMAX, still just one modulo) */
+ if (len) { /* avoid modulos if none remaining */
+ while (len >= 16) {
+ len -= 16;
+ DO16(buf);
+ buf += 16;
+ }
+ while (len--) {
+ adler += *buf++;
+ sum2 += adler;
+ }
+ MOD(adler);
+ MOD(sum2);
+ }
+
+ /* return recombined sums */
+ return adler | (sum2 << 16);
+}
+
+/* ========================================================================= */
+local uLong adler32_combine_(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off64_t len2;
+{
+ unsigned long sum1;
+ unsigned long sum2;
+ unsigned rem;
+
+ /* for negative len, return invalid adler32 as a clue for debugging */
+ if (len2 < 0)
+ return 0xffffffffUL;
+
+ /* the derivation of this formula is left as an exercise for the reader */
+ MOD63(len2); /* assumes len2 >= 0 */
+ rem = (unsigned)len2;
+ sum1 = adler1 & 0xffff;
+ sum2 = rem * sum1;
+ MOD(sum2);
+ sum1 += (adler2 & 0xffff) + BASE - 1;
+ sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
+ if (sum1 >= BASE) sum1 -= BASE;
+ if (sum1 >= BASE) sum1 -= BASE;
+ if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1);
+ if (sum2 >= BASE) sum2 -= BASE;
+ return sum1 | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_combine(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off_t len2;
+{
+ return adler32_combine_(adler1, adler2, len2);
+}
+
+uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
+ uLong adler1;
+ uLong adler2;
+ z_off64_t len2;
+{
+ return adler32_combine_(adler1, adler2, len2);
+}
diff --git a/src/cpp/core/zlib/compress.c b/src/cpp/core/zlib/compress.c
new file mode 100644
index 0000000..ea4dfbe
--- /dev/null
+++ b/src/cpp/core/zlib/compress.c
@@ -0,0 +1,80 @@
+/* compress.c -- compress a memory buffer
+ * Copyright (C) 1995-2005 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+ Compresses the source buffer into the destination buffer. The level
+ parameter has the same meaning as in deflateInit. sourceLen is the byte
+ length of the source buffer. Upon entry, destLen is the total size of the
+ destination buffer, which must be at least 0.1% larger than sourceLen plus
+ 12 bytes. Upon exit, destLen is the actual size of the compressed buffer.
+
+ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+ Z_STREAM_ERROR if the level parameter is invalid.
+*/
+int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+ int level;
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+#ifdef MAXSEG_64K
+ /* Check for source > 64K on 16-bit machine: */
+ if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
+#endif
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = deflateInit(&stream, level);
+ if (err != Z_OK) return err;
+
+ err = deflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ deflateEnd(&stream);
+ return err == Z_OK ? Z_BUF_ERROR : err;
+ }
+ *destLen = stream.total_out;
+
+ err = deflateEnd(&stream);
+ return err;
+}
+
+/* ===========================================================================
+ */
+int ZEXPORT compress (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+{
+ return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
+}
+
+/* ===========================================================================
+ If the default memLevel or windowBits for deflateInit() is changed, then
+ this function needs to be updated.
+ */
+uLong ZEXPORT compressBound (sourceLen)
+ uLong sourceLen;
+{
+ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+ (sourceLen >> 25) + 13;
+}
diff --git a/src/cpp/core/zlib/crc32.c b/src/cpp/core/zlib/crc32.c
new file mode 100644
index 0000000..c12471e
--- /dev/null
+++ b/src/cpp/core/zlib/crc32.c
@@ -0,0 +1,447 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-2006, 2010, 2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ *
+ * Thanks to Rodney Brown <rbrown64 at csc.com.au> for his contribution of faster
+ * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing
+ * tables for updating the shift register in one step with three exclusive-ors
+ * instead of four steps with four exclusive-ors. This results in about a
+ * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3.
+ */
+
+/* @(#) $Id$ */
+
+/*
+ Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore
+ protection on the static variables used to control the first-use generation
+ of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should
+ first call get_crc_table() to initialize the tables before allowing more than
+ one thread to use crc32().
+
+ DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h.
+ */
+
+#ifdef MAKECRCH
+# include <stdio.h>
+# ifndef DYNAMIC_CRC_TABLE
+# define DYNAMIC_CRC_TABLE
+# endif /* !DYNAMIC_CRC_TABLE */
+#endif /* MAKECRCH */
+
+#include "zutil.h" /* for STDC and FAR definitions */
+
+#define local static
+
+/* Find a four-byte integer type for crc32_little() and crc32_big(). */
+#ifndef NOBYFOUR
+# ifdef STDC /* need ANSI C limits.h to determine sizes */
+# include <limits.h>
+# define BYFOUR
+# if (UINT_MAX == 0xffffffffUL)
+ typedef unsigned int u4;
+# else
+# if (ULONG_MAX == 0xffffffffUL)
+ typedef unsigned long u4;
+# else
+# if (USHRT_MAX == 0xffffffffUL)
+ typedef unsigned short u4;
+# else
+# undef BYFOUR /* can't find a four-byte integer type! */
+# endif
+# endif
+# endif
+# endif /* STDC */
+#endif /* !NOBYFOUR */
+
+/* Definitions for doing the crc four data bytes at a time. */
+#ifdef BYFOUR
+ typedef u4 crc_table_t;
+# define REV(w) ((((w)>>24)&0xff)+(((w)>>8)&0xff00)+ \
+ (((w)&0xff00)<<8)+(((w)&0xff)<<24))
+ local unsigned long crc32_little OF((unsigned long,
+ const unsigned char FAR *, unsigned));
+ local unsigned long crc32_big OF((unsigned long,
+ const unsigned char FAR *, unsigned));
+# define TBLS 8
+#else
+ typedef unsigned long crc_table_t;
+# define TBLS 1
+#endif /* BYFOUR */
+
+/* Local functions for crc concatenation */
+local unsigned long gf2_matrix_times OF((unsigned long *mat,
+ unsigned long vec));
+local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat));
+local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2));
+
+
+#ifdef DYNAMIC_CRC_TABLE
+
+local volatile int crc_table_empty = 1;
+local crc_table_t FAR crc_table[TBLS][256];
+local void make_crc_table OF((void));
+#ifdef MAKECRCH
+ local void write_table OF((FILE *, const crc_table_t FAR *));
+#endif /* MAKECRCH */
+/*
+ Generate tables for a byte-wise 32-bit CRC calculation on the polynomial:
+ x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
+
+ Polynomials over GF(2) are represented in binary, one bit per coefficient,
+ with the lowest powers in the most significant bit. Then adding polynomials
+ is just exclusive-or, and multiplying a polynomial by x is a right shift by
+ one. If we call the above polynomial p, and represent a byte as the
+ polynomial q, also with the lowest power in the most significant bit (so the
+ byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
+ where a mod b means the remainder after dividing a by b.
+
+ This calculation is done using the shift-register method of multiplying and
+ taking the remainder. The register is initialized to zero, and for each
+ incoming bit, x^32 is added mod p to the register if the bit is a one (where
+ x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
+ x (which is shifting right by one and adding x^32 mod p if the bit shifted
+ out is a one). We start with the highest power (least significant bit) of
+ q and repeat for all eight bits of q.
+
+ The first table is simply the CRC of all possible eight bit values. This is
+ all the information needed to generate CRCs on data a byte at a time for all
+ combinations of CRC register values and incoming bytes. The remaining tables
+ allow for word-at-a-time CRC calculation for both big-endian and little-
+ endian machines, where a word is four bytes.
+*/
+local void make_crc_table()
+{
+ crc_table_t c;
+ int n, k;
+ crc_table_t poly; /* polynomial exclusive-or pattern */
+ /* terms of polynomial defining this crc (except x^32): */
+ static volatile int first = 1; /* flag to limit concurrent making */
+ static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};
+
+ /* See if another task is already doing this (not thread-safe, but better
+ than nothing -- significantly reduces duration of vulnerability in
+ case the advice about DYNAMIC_CRC_TABLE is ignored) */
+ if (first) {
+ first = 0;
+
+ /* make exclusive-or pattern from polynomial (0xedb88320UL) */
+ poly = 0;
+ for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++)
+ poly |= (crc_table_t)1 << (31 - p[n]);
+
+ /* generate a crc for every 8-bit value */
+ for (n = 0; n < 256; n++) {
+ c = (crc_table_t)n;
+ for (k = 0; k < 8; k++)
+ c = c & 1 ? poly ^ (c >> 1) : c >> 1;
+ crc_table[0][n] = c;
+ }
+
+#ifdef BYFOUR
+ /* generate crc for each value followed by one, two, and three zeros,
+ and then the byte reversal of those as well as the first table */
+ for (n = 0; n < 256; n++) {
+ c = crc_table[0][n];
+ crc_table[4][n] = REV(c);
+ for (k = 1; k < 4; k++) {
+ c = crc_table[0][c & 0xff] ^ (c >> 8);
+ crc_table[k][n] = c;
+ crc_table[k + 4][n] = REV(c);
+ }
+ }
+#endif /* BYFOUR */
+
+ crc_table_empty = 0;
+ }
+ else { /* not first */
+ /* wait for the other guy to finish (not efficient, but rare) */
+ while (crc_table_empty)
+ ;
+ }
+
+#ifdef MAKECRCH
+ /* write out CRC tables to crc32.h */
+ {
+ FILE *out;
+
+ out = fopen("crc32.h", "w");
+ if (out == NULL) return;
+ fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n");
+ fprintf(out, " * Generated automatically by crc32.c\n */\n\n");
+ fprintf(out, "local const crc_table_t FAR ");
+ fprintf(out, "crc_table[TBLS][256] =\n{\n {\n");
+ write_table(out, crc_table[0]);
+# ifdef BYFOUR
+ fprintf(out, "#ifdef BYFOUR\n");
+ for (k = 1; k < 8; k++) {
+ fprintf(out, " },\n {\n");
+ write_table(out, crc_table[k]);
+ }
+ fprintf(out, "#endif\n");
+# endif /* BYFOUR */
+ fprintf(out, " }\n};\n");
+ fclose(out);
+ }
+#endif /* MAKECRCH */
+}
+
+#ifdef MAKECRCH
+local void write_table(out, table)
+ FILE *out;
+ const crc_table_t FAR *table;
+{
+ int n;
+
+ for (n = 0; n < 256; n++)
+ fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ",
+ (unsigned long)(table[n]),
+ n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", "));
+}
+#endif /* MAKECRCH */
+
+#else /* !DYNAMIC_CRC_TABLE */
+/* ========================================================================
+ * Tables of CRC-32s of all single-byte values, made by make_crc_table().
+ */
+#include "crc32.h"
+#endif /* DYNAMIC_CRC_TABLE */
+
+/* =========================================================================
+ * This function can be used by asm versions of crc32()
+ */
+const unsigned long FAR * ZEXPORT get_crc_table()
+{
+#ifdef DYNAMIC_CRC_TABLE
+ if (crc_table_empty)
+ make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+ return (const unsigned long FAR *)crc_table;
+}
+
+/* ========================================================================= */
+#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8)
+#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ uInt len;
+{
+ if (buf == Z_NULL) return 0UL;
+
+#ifdef DYNAMIC_CRC_TABLE
+ if (crc_table_empty)
+ make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+
+#ifdef BYFOUR
+ if (sizeof(void *) == sizeof(ptrdiff_t)) {
+ u4 endian;
+
+ endian = 1;
+ if (*((unsigned char *)(&endian)))
+ return crc32_little(crc, buf, len);
+ else
+ return crc32_big(crc, buf, len);
+ }
+#endif /* BYFOUR */
+ crc = crc ^ 0xffffffffUL;
+ while (len >= 8) {
+ DO8;
+ len -= 8;
+ }
+ if (len) do {
+ DO1;
+ } while (--len);
+ return crc ^ 0xffffffffUL;
+}
+
+#ifdef BYFOUR
+
+/* ========================================================================= */
+#define DOLIT4 c ^= *buf4++; \
+ c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \
+ crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24]
+#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
+
+/* ========================================================================= */
+local unsigned long crc32_little(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ unsigned len;
+{
+ register u4 c;
+ register const u4 FAR *buf4;
+
+ c = (u4)crc;
+ c = ~c;
+ while (len && ((ptrdiff_t)buf & 3)) {
+ c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+ len--;
+ }
+
+ buf4 = (const u4 FAR *)(const void FAR *)buf;
+ while (len >= 32) {
+ DOLIT32;
+ len -= 32;
+ }
+ while (len >= 4) {
+ DOLIT4;
+ len -= 4;
+ }
+ buf = (const unsigned char FAR *)buf4;
+
+ if (len) do {
+ c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+ } while (--len);
+ c = ~c;
+ return (unsigned long)c;
+}
+
+/* ========================================================================= */
+#define DOBIG4 c ^= *++buf4; \
+ c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \
+ crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]
+#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
+
+/* ========================================================================= */
+local unsigned long crc32_big(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ unsigned len;
+{
+ register u4 c;
+ register const u4 FAR *buf4;
+
+ c = REV((u4)crc);
+ c = ~c;
+ while (len && ((ptrdiff_t)buf & 3)) {
+ c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+ len--;
+ }
+
+ buf4 = (const u4 FAR *)(const void FAR *)buf;
+ buf4--;
+ while (len >= 32) {
+ DOBIG32;
+ len -= 32;
+ }
+ while (len >= 4) {
+ DOBIG4;
+ len -= 4;
+ }
+ buf4++;
+ buf = (const unsigned char FAR *)buf4;
+
+ if (len) do {
+ c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+ } while (--len);
+ c = ~c;
+ return (unsigned long)(REV(c));
+}
+
+#endif /* BYFOUR */
+
+#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */
+
+/* ========================================================================= */
+local unsigned long gf2_matrix_times(mat, vec)
+ unsigned long *mat;
+ unsigned long vec;
+{
+ unsigned long sum;
+
+ sum = 0;
+ while (vec) {
+ if (vec & 1)
+ sum ^= *mat;
+ vec >>= 1;
+ mat++;
+ }
+ return sum;
+}
+
+/* ========================================================================= */
+local void gf2_matrix_square(square, mat)
+ unsigned long *square;
+ unsigned long *mat;
+{
+ int n;
+
+ for (n = 0; n < GF2_DIM; n++)
+ square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+/* ========================================================================= */
+local uLong crc32_combine_(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off64_t len2;
+{
+ int n;
+ unsigned long row;
+ unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */
+ unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */
+
+ /* degenerate case (also disallow negative lengths) */
+ if (len2 <= 0)
+ return crc1;
+
+ /* put operator for one zero bit in odd */
+ odd[0] = 0xedb88320UL; /* CRC-32 polynomial */
+ row = 1;
+ for (n = 1; n < GF2_DIM; n++) {
+ odd[n] = row;
+ row <<= 1;
+ }
+
+ /* put operator for two zero bits in even */
+ gf2_matrix_square(even, odd);
+
+ /* put operator for four zero bits in odd */
+ gf2_matrix_square(odd, even);
+
+ /* apply len2 zeros to crc1 (first square will put the operator for one
+ zero byte, eight zero bits, in even) */
+ do {
+ /* apply zeros operator for this bit of len2 */
+ gf2_matrix_square(even, odd);
+ if (len2 & 1)
+ crc1 = gf2_matrix_times(even, crc1);
+ len2 >>= 1;
+
+ /* if no more bits set, then done */
+ if (len2 == 0)
+ break;
+
+ /* another iteration of the loop with odd and even swapped */
+ gf2_matrix_square(odd, even);
+ if (len2 & 1)
+ crc1 = gf2_matrix_times(odd, crc1);
+ len2 >>= 1;
+
+ /* if no more bits set, then done */
+ } while (len2 != 0);
+
+ /* return combined crc */
+ crc1 ^= crc2;
+ return crc1;
+}
+
+/* ========================================================================= */
+uLong ZEXPORT crc32_combine(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off_t len2;
+{
+ return crc32_combine_(crc1, crc2, len2);
+}
+
+uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
+ uLong crc1;
+ uLong crc2;
+ z_off64_t len2;
+{
+ return crc32_combine_(crc1, crc2, len2);
+}
diff --git a/src/cpp/core/zlib/crc32.h b/src/cpp/core/zlib/crc32.h
new file mode 100644
index 0000000..c3e7171
--- /dev/null
+++ b/src/cpp/core/zlib/crc32.h
@@ -0,0 +1,441 @@
+/* crc32.h -- tables for rapid CRC calculation
+ * Generated automatically by crc32.c
+ */
+
+local const crc_table_t FAR crc_table[TBLS][256] =
+{
+ {
+ 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL,
+ 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL,
+ 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL,
+ 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL,
+ 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL,
+ 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL,
+ 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL,
+ 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL,
+ 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL,
+ 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL,
+ 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL,
+ 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL,
+ 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL,
+ 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL,
+ 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL,
+ 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL,
+ 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL,
+ 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL,
+ 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL,
+ 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL,
+ 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL,
+ 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL,
+ 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL,
+ 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL,
+ 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL,
+ 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL,
+ 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL,
+ 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL,
+ 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL,
+ 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL,
+ 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL,
+ 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL,
+ 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL,
+ 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL,
+ 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL,
+ 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL,
+ 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL,
+ 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL,
+ 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL,
+ 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL,
+ 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL,
+ 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL,
+ 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL,
+ 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL,
+ 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL,
+ 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL,
+ 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL,
+ 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL,
+ 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL,
+ 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL,
+ 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL,
+ 0x2d02ef8dUL
+#ifdef BYFOUR
+ },
+ {
+ 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL,
+ 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL,
+ 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL,
+ 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL,
+ 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL,
+ 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL,
+ 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL,
+ 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL,
+ 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL,
+ 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL,
+ 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL,
+ 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL,
+ 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL,
+ 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL,
+ 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL,
+ 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL,
+ 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL,
+ 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL,
+ 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL,
+ 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL,
+ 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL,
+ 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL,
+ 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL,
+ 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL,
+ 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL,
+ 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL,
+ 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL,
+ 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL,
+ 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL,
+ 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL,
+ 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL,
+ 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL,
+ 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL,
+ 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL,
+ 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL,
+ 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL,
+ 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL,
+ 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL,
+ 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL,
+ 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL,
+ 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL,
+ 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL,
+ 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL,
+ 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL,
+ 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL,
+ 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL,
+ 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL,
+ 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL,
+ 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL,
+ 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL,
+ 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL,
+ 0x9324fd72UL
+ },
+ {
+ 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL,
+ 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL,
+ 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL,
+ 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL,
+ 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL,
+ 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL,
+ 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL,
+ 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL,
+ 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL,
+ 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL,
+ 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL,
+ 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL,
+ 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL,
+ 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL,
+ 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL,
+ 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL,
+ 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL,
+ 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL,
+ 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL,
+ 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL,
+ 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL,
+ 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL,
+ 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL,
+ 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL,
+ 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL,
+ 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL,
+ 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL,
+ 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL,
+ 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL,
+ 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL,
+ 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL,
+ 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL,
+ 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL,
+ 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL,
+ 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL,
+ 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL,
+ 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL,
+ 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL,
+ 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL,
+ 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL,
+ 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL,
+ 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL,
+ 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL,
+ 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL,
+ 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL,
+ 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL,
+ 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL,
+ 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL,
+ 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL,
+ 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL,
+ 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL,
+ 0xbe9834edUL
+ },
+ {
+ 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL,
+ 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL,
+ 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL,
+ 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL,
+ 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL,
+ 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL,
+ 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL,
+ 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL,
+ 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL,
+ 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL,
+ 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL,
+ 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL,
+ 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL,
+ 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL,
+ 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL,
+ 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL,
+ 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL,
+ 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL,
+ 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL,
+ 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL,
+ 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL,
+ 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL,
+ 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL,
+ 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL,
+ 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL,
+ 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL,
+ 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL,
+ 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL,
+ 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL,
+ 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL,
+ 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL,
+ 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL,
+ 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL,
+ 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL,
+ 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL,
+ 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL,
+ 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL,
+ 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL,
+ 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL,
+ 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL,
+ 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL,
+ 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL,
+ 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL,
+ 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL,
+ 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL,
+ 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL,
+ 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL,
+ 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL,
+ 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL,
+ 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL,
+ 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL,
+ 0xde0506f1UL
+ },
+ {
+ 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL,
+ 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL,
+ 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL,
+ 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL,
+ 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL,
+ 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL,
+ 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL,
+ 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL,
+ 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL,
+ 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL,
+ 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL,
+ 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL,
+ 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL,
+ 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL,
+ 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL,
+ 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL,
+ 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL,
+ 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL,
+ 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL,
+ 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL,
+ 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL,
+ 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL,
+ 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL,
+ 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL,
+ 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL,
+ 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL,
+ 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL,
+ 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL,
+ 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL,
+ 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL,
+ 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL,
+ 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL,
+ 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL,
+ 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL,
+ 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL,
+ 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL,
+ 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL,
+ 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL,
+ 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL,
+ 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL,
+ 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL,
+ 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL,
+ 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL,
+ 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL,
+ 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL,
+ 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL,
+ 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL,
+ 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL,
+ 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL,
+ 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL,
+ 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL,
+ 0x8def022dUL
+ },
+ {
+ 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL,
+ 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL,
+ 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL,
+ 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL,
+ 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL,
+ 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL,
+ 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL,
+ 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL,
+ 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL,
+ 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL,
+ 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL,
+ 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL,
+ 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL,
+ 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL,
+ 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL,
+ 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL,
+ 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL,
+ 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL,
+ 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL,
+ 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL,
+ 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL,
+ 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL,
+ 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL,
+ 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL,
+ 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL,
+ 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL,
+ 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL,
+ 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL,
+ 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL,
+ 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL,
+ 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL,
+ 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL,
+ 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL,
+ 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL,
+ 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL,
+ 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL,
+ 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL,
+ 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL,
+ 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL,
+ 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL,
+ 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL,
+ 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL,
+ 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL,
+ 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL,
+ 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL,
+ 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL,
+ 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL,
+ 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL,
+ 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL,
+ 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL,
+ 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL,
+ 0x72fd2493UL
+ },
+ {
+ 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL,
+ 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL,
+ 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL,
+ 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL,
+ 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL,
+ 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL,
+ 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL,
+ 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL,
+ 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL,
+ 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL,
+ 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL,
+ 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL,
+ 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL,
+ 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL,
+ 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL,
+ 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL,
+ 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL,
+ 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL,
+ 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL,
+ 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL,
+ 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL,
+ 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL,
+ 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL,
+ 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL,
+ 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL,
+ 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL,
+ 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL,
+ 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL,
+ 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL,
+ 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL,
+ 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL,
+ 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL,
+ 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL,
+ 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL,
+ 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL,
+ 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL,
+ 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL,
+ 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL,
+ 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL,
+ 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL,
+ 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL,
+ 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL,
+ 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL,
+ 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL,
+ 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL,
+ 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL,
+ 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL,
+ 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL,
+ 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL,
+ 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL,
+ 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL,
+ 0xed3498beUL
+ },
+ {
+ 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL,
+ 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL,
+ 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL,
+ 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL,
+ 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL,
+ 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL,
+ 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL,
+ 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL,
+ 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL,
+ 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL,
+ 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL,
+ 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL,
+ 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL,
+ 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL,
+ 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL,
+ 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL,
+ 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL,
+ 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL,
+ 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL,
+ 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL,
+ 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL,
+ 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL,
+ 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL,
+ 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL,
+ 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL,
+ 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL,
+ 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL,
+ 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL,
+ 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL,
+ 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL,
+ 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL,
+ 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL,
+ 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL,
+ 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL,
+ 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL,
+ 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL,
+ 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL,
+ 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL,
+ 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL,
+ 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL,
+ 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL,
+ 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL,
+ 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL,
+ 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL,
+ 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL,
+ 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL,
+ 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL,
+ 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL,
+ 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL,
+ 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL,
+ 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL,
+ 0xf10605deUL
+#endif
+ }
+};
diff --git a/src/cpp/core/zlib/deflate.c b/src/cpp/core/zlib/deflate.c
new file mode 100644
index 0000000..8bd480e
--- /dev/null
+++ b/src/cpp/core/zlib/deflate.c
@@ -0,0 +1,1965 @@
+/* deflate.c -- compress data using the deflation algorithm
+ * Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * ALGORITHM
+ *
+ * The "deflation" process depends on being able to identify portions
+ * of the input text which are identical to earlier input (within a
+ * sliding window trailing behind the input currently being processed).
+ *
+ * The most straightforward technique turns out to be the fastest for
+ * most input files: try all possible matches and select the longest.
+ * The key feature of this algorithm is that insertions into the string
+ * dictionary are very simple and thus fast, and deletions are avoided
+ * completely. Insertions are performed at each input character, whereas
+ * string matches are performed only when the previous match ends. So it
+ * is preferable to spend more time in matches to allow very fast string
+ * insertions and avoid deletions. The matching algorithm for small
+ * strings is inspired from that of Rabin & Karp. A brute force approach
+ * is used to find longer strings when a small match has been found.
+ * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze
+ * (by Leonid Broukhis).
+ * A previous version of this file used a more sophisticated algorithm
+ * (by Fiala and Greene) which is guaranteed to run in linear amortized
+ * time, but has a larger average cost, uses more memory and is patented.
+ * However the F&G algorithm may be faster for some highly redundant
+ * files if the parameter max_chain_length (described below) is too large.
+ *
+ * ACKNOWLEDGEMENTS
+ *
+ * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and
+ * I found it in 'freeze' written by Leonid Broukhis.
+ * Thanks to many people for bug reports and testing.
+ *
+ * REFERENCES
+ *
+ * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification".
+ * Available in http://tools.ietf.org/html/rfc1951
+ *
+ * A description of the Rabin and Karp algorithm is given in the book
+ * "Algorithms" by R. Sedgewick, Addison-Wesley, p252.
+ *
+ * Fiala,E.R., and Greene,D.H.
+ * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595
+ *
+ */
+
+/* @(#) $Id$ */
+
+#include "deflate.h"
+
+const char deflate_copyright[] =
+ " deflate 1.2.6 Copyright 1995-2012 Jean-loup Gailly and Mark Adler ";
+/*
+ If you use the zlib library in a product, an acknowledgment is welcome
+ in the documentation of your product. If for some reason you cannot
+ include such an acknowledgment, I would appreciate that you keep this
+ copyright string in the executable of your product.
+ */
+
+/* ===========================================================================
+ * Function prototypes.
+ */
+typedef enum {
+ need_more, /* block not completed, need more input or more output */
+ block_done, /* block flush performed */
+ finish_started, /* finish started, need only more output at next deflate */
+ finish_done /* finish done, accept no more input or output */
+} block_state;
+
+typedef block_state (*compress_func) OF((deflate_state *s, int flush));
+/* Compression function. Returns the block state after the call. */
+
+local void fill_window OF((deflate_state *s));
+local block_state deflate_stored OF((deflate_state *s, int flush));
+local block_state deflate_fast OF((deflate_state *s, int flush));
+#ifndef FASTEST
+local block_state deflate_slow OF((deflate_state *s, int flush));
+#endif
+local block_state deflate_rle OF((deflate_state *s, int flush));
+local block_state deflate_huff OF((deflate_state *s, int flush));
+local void lm_init OF((deflate_state *s));
+local void putShortMSB OF((deflate_state *s, uInt b));
+local void flush_pending OF((z_streamp strm));
+local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size));
+#ifdef ASMV
+ void match_init OF((void)); /* asm code initialization */
+ uInt longest_match OF((deflate_state *s, IPos cur_match));
+#else
+local uInt longest_match OF((deflate_state *s, IPos cur_match));
+#endif
+
+#ifdef DEBUG
+local void check_match OF((deflate_state *s, IPos start, IPos match,
+ int length));
+#endif
+
+/* ===========================================================================
+ * Local data
+ */
+
+#define NIL 0
+/* Tail of hash chains */
+
+#ifndef TOO_FAR
+# define TOO_FAR 4096
+#endif
+/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+typedef struct config_s {
+ ush good_length; /* reduce lazy search above this match length */
+ ush max_lazy; /* do not perform lazy search above this match length */
+ ush nice_length; /* quit search above this match length */
+ ush max_chain;
+ compress_func func;
+} config;
+
+#ifdef FASTEST
+local const config configuration_table[2] = {
+/* good lazy nice chain */
+/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */
+/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */
+#else
+local const config configuration_table[10] = {
+/* good lazy nice chain */
+/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */
+/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */
+/* 2 */ {4, 5, 16, 8, deflate_fast},
+/* 3 */ {4, 6, 32, 32, deflate_fast},
+
+/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */
+/* 5 */ {8, 16, 32, 32, deflate_slow},
+/* 6 */ {8, 16, 128, 128, deflate_slow},
+/* 7 */ {8, 32, 128, 256, deflate_slow},
+/* 8 */ {32, 128, 258, 1024, deflate_slow},
+/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */
+#endif
+
+/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4
+ * For deflate_fast() (levels <= 3) good is ignored and lazy has a different
+ * meaning.
+ */
+
+#define EQUAL 0
+/* result of memcmp for equal strings */
+
+#ifndef NO_DUMMY_DECL
+struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
+#endif
+
+/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */
+#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0))
+
+/* ===========================================================================
+ * Update a hash value with the given input byte
+ * IN assertion: all calls to to UPDATE_HASH are made with consecutive
+ * input characters, so that a running hash key can be computed from the
+ * previous key instead of complete recalculation each time.
+ */
+#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
+
+
+/* ===========================================================================
+ * Insert string str in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * If this file is compiled with -DFASTEST, the compression level is forced
+ * to 1, and no hash chains are maintained.
+ * IN assertion: all calls to to INSERT_STRING are made with consecutive
+ * input characters and the first MIN_MATCH bytes of str are valid
+ * (except for the last MIN_MATCH-1 bytes of the input file).
+ */
+#ifdef FASTEST
+#define INSERT_STRING(s, str, match_head) \
+ (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+ match_head = s->head[s->ins_h], \
+ s->head[s->ins_h] = (Pos)(str))
+#else
+#define INSERT_STRING(s, str, match_head) \
+ (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+ match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \
+ s->head[s->ins_h] = (Pos)(str))
+#endif
+
+/* ===========================================================================
+ * Initialize the hash table (avoiding 64K overflow for 16 bit systems).
+ * prev[] will be initialized on the fly.
+ */
+#define CLEAR_HASH(s) \
+ s->head[s->hash_size-1] = NIL; \
+ zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+
+/* ========================================================================= */
+int ZEXPORT deflateInit_(strm, level, version, stream_size)
+ z_streamp strm;
+ int level;
+ const char *version;
+ int stream_size;
+{
+ return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY, version, stream_size);
+ /* To do: ignore strm->next_in if we use it as window */
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
+ version, stream_size)
+ z_streamp strm;
+ int level;
+ int method;
+ int windowBits;
+ int memLevel;
+ int strategy;
+ const char *version;
+ int stream_size;
+{
+ deflate_state *s;
+ int wrap = 1;
+ static const char my_version[] = ZLIB_VERSION;
+
+ ushf *overlay;
+ /* We overlay pending_buf and d_buf+l_buf. This works since the average
+ * output size for (length,distance) codes is <= 24 bits.
+ */
+
+ if (version == Z_NULL || version[0] != my_version[0] ||
+ stream_size != sizeof(z_stream)) {
+ return Z_VERSION_ERROR;
+ }
+ if (strm == Z_NULL) return Z_STREAM_ERROR;
+
+ strm->msg = Z_NULL;
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+
+#ifdef FASTEST
+ if (level != 0) level = 1;
+#else
+ if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+
+ if (windowBits < 0) { /* suppress zlib wrapper */
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+#ifdef GZIP
+ else if (windowBits > 15) {
+ wrap = 2; /* write gzip wrapper instead */
+ windowBits -= 16;
+ }
+#endif
+ if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
+ windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+ strategy < 0 || strategy > Z_FIXED) {
+ return Z_STREAM_ERROR;
+ }
+ if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */
+ s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state));
+ if (s == Z_NULL) return Z_MEM_ERROR;
+ strm->state = (struct internal_state FAR *)s;
+ s->strm = strm;
+
+ s->wrap = wrap;
+ s->gzhead = Z_NULL;
+ s->w_bits = windowBits;
+ s->w_size = 1 << s->w_bits;
+ s->w_mask = s->w_size - 1;
+
+ s->hash_bits = memLevel + 7;
+ s->hash_size = 1 << s->hash_bits;
+ s->hash_mask = s->hash_size - 1;
+ s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH);
+
+ s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
+ s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos));
+ s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos));
+
+ s->high_water = 0; /* nothing written to s->window yet */
+
+ s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+ overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+ s->pending_buf = (uchf *) overlay;
+ s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+
+ if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
+ s->pending_buf == Z_NULL) {
+ s->status = FINISH_STATE;
+ strm->msg = (char*)ERR_MSG(Z_MEM_ERROR);
+ deflateEnd (strm);
+ return Z_MEM_ERROR;
+ }
+ s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+ s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+
+ s->level = level;
+ s->strategy = strategy;
+ s->method = (Byte)method;
+
+ return deflateReset(strm);
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
+ z_streamp strm;
+ const Bytef *dictionary;
+ uInt dictLength;
+{
+ deflate_state *s;
+ uInt str, n;
+ int wrap;
+ unsigned avail;
+ unsigned char *next;
+
+ if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL)
+ return Z_STREAM_ERROR;
+ s = strm->state;
+ wrap = s->wrap;
+ if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead)
+ return Z_STREAM_ERROR;
+
+ /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+ if (wrap == 1)
+ strm->adler = adler32(strm->adler, dictionary, dictLength);
+ s->wrap = 0; /* avoid computing Adler-32 in read_buf */
+
+ /* if dictionary would fill window, just replace the history */
+ if (dictLength >= s->w_size) {
+ if (wrap == 0) { /* already empty otherwise */
+ CLEAR_HASH(s);
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->insert = 0;
+ }
+ dictionary += dictLength - s->w_size; /* use the tail */
+ dictLength = s->w_size;
+ }
+
+ /* insert dictionary into window and hash */
+ avail = strm->avail_in;
+ next = strm->next_in;
+ strm->avail_in = dictLength;
+ strm->next_in = (Bytef *)dictionary;
+ fill_window(s);
+ while (s->lookahead >= MIN_MATCH) {
+ str = s->strstart;
+ n = s->lookahead - (MIN_MATCH-1);
+ do {
+ UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+ s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+ s->head[s->ins_h] = (Pos)str;
+ str++;
+ } while (--n);
+ s->strstart = str;
+ s->lookahead = MIN_MATCH-1;
+ fill_window(s);
+ }
+ s->strstart += s->lookahead;
+ s->block_start = (long)s->strstart;
+ s->insert = s->lookahead;
+ s->lookahead = 0;
+ s->match_length = s->prev_length = MIN_MATCH-1;
+ s->match_available = 0;
+ strm->next_in = next;
+ strm->avail_in = avail;
+ s->wrap = wrap;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateResetKeep (strm)
+ z_streamp strm;
+{
+ deflate_state *s;
+
+ if (strm == Z_NULL || strm->state == Z_NULL ||
+ strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) {
+ return Z_STREAM_ERROR;
+ }
+
+ strm->total_in = strm->total_out = 0;
+ strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */
+ strm->data_type = Z_UNKNOWN;
+
+ s = (deflate_state *)strm->state;
+ s->pending = 0;
+ s->pending_out = s->pending_buf;
+
+ if (s->wrap < 0) {
+ s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */
+ }
+ s->status = s->wrap ? INIT_STATE : BUSY_STATE;
+ strm->adler =
+#ifdef GZIP
+ s->wrap == 2 ? crc32(0L, Z_NULL, 0) :
+#endif
+ adler32(0L, Z_NULL, 0);
+ s->last_flush = Z_NO_FLUSH;
+
+ _tr_init(s);
+
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateReset (strm)
+ z_streamp strm;
+{
+ int ret;
+
+ ret = deflateResetKeep(strm);
+ if (ret == Z_OK)
+ lm_init(strm->state);
+ return ret;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetHeader (strm, head)
+ z_streamp strm;
+ gz_headerp head;
+{
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (strm->state->wrap != 2) return Z_STREAM_ERROR;
+ strm->state->gzhead = head;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePending (strm, pending, bits)
+ unsigned *pending;
+ int *bits;
+ z_streamp strm;
+{
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (pending != Z_NULL)
+ *pending = strm->state->pending;
+ if (bits != Z_NULL)
+ *bits = strm->state->bi_valid;
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePrime (strm, bits, value)
+ z_streamp strm;
+ int bits;
+ int value;
+{
+ deflate_state *s;
+ int put;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ s = strm->state;
+ if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
+ return Z_BUF_ERROR;
+ do {
+ put = Buf_size - s->bi_valid;
+ if (put > bits)
+ put = bits;
+ s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid);
+ s->bi_valid += put;
+ _tr_flush_bits(s);
+ value >>= put;
+ bits -= put;
+ } while (bits);
+ return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateParams(strm, level, strategy)
+ z_streamp strm;
+ int level;
+ int strategy;
+{
+ deflate_state *s;
+ compress_func func;
+ int err = Z_OK;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ s = strm->state;
+
+#ifdef FASTEST
+ if (level != 0) level = 1;
+#else
+ if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+ if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
+ return Z_STREAM_ERROR;
+ }
+ func = configuration_table[s->level].func;
+
+ if ((strategy != s->strategy || func != configuration_table[level].func) &&
+ strm->total_in != 0) {
+ /* Flush the last buffer: */
+ err = deflate(strm, Z_BLOCK);
+ }
+ if (s->level != level) {
+ s->level = level;
+ s->max_lazy_match = configuration_table[level].max_lazy;
+ s->good_match = configuration_table[level].good_length;
+ s->nice_match = configuration_table[level].nice_length;
+ s->max_chain_length = configuration_table[level].max_chain;
+ }
+ s->strategy = strategy;
+ return err;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
+ z_streamp strm;
+ int good_length;
+ int max_lazy;
+ int nice_length;
+ int max_chain;
+{
+ deflate_state *s;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ s = strm->state;
+ s->good_match = good_length;
+ s->max_lazy_match = max_lazy;
+ s->nice_match = nice_length;
+ s->max_chain_length = max_chain;
+ return Z_OK;
+}
+
+/* =========================================================================
+ * For the default windowBits of 15 and memLevel of 8, this function returns
+ * a close to exact, as well as small, upper bound on the compressed size.
+ * They are coded as constants here for a reason--if the #define's are
+ * changed, then this function needs to be changed as well. The return
+ * value for 15 and 8 only works for those exact settings.
+ *
+ * For any setting other than those defaults for windowBits and memLevel,
+ * the value returned is a conservative worst case for the maximum expansion
+ * resulting from using fixed blocks instead of stored blocks, which deflate
+ * can emit on compressed data for some combinations of the parameters.
+ *
+ * This function could be more sophisticated to provide closer upper bounds for
+ * every combination of windowBits and memLevel. But even the conservative
+ * upper bound of about 14% expansion does not seem onerous for output buffer
+ * allocation.
+ */
+uLong ZEXPORT deflateBound(strm, sourceLen)
+ z_streamp strm;
+ uLong sourceLen;
+{
+ deflate_state *s;
+ uLong complen, wraplen;
+ Bytef *str;
+
+ /* conservative upper bound for compressed data */
+ complen = sourceLen +
+ ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
+
+ /* if can't get parameters, return conservative bound plus zlib wrapper */
+ if (strm == Z_NULL || strm->state == Z_NULL)
+ return complen + 6;
+
+ /* compute wrapper length */
+ s = strm->state;
+ switch (s->wrap) {
+ case 0: /* raw deflate */
+ wraplen = 0;
+ break;
+ case 1: /* zlib wrapper */
+ wraplen = 6 + (s->strstart ? 4 : 0);
+ break;
+ case 2: /* gzip wrapper */
+ wraplen = 18;
+ if (s->gzhead != Z_NULL) { /* user-supplied gzip header */
+ if (s->gzhead->extra != Z_NULL)
+ wraplen += 2 + s->gzhead->extra_len;
+ str = s->gzhead->name;
+ if (str != Z_NULL)
+ do {
+ wraplen++;
+ } while (*str++);
+ str = s->gzhead->comment;
+ if (str != Z_NULL)
+ do {
+ wraplen++;
+ } while (*str++);
+ if (s->gzhead->hcrc)
+ wraplen += 2;
+ }
+ break;
+ default: /* for compiler happiness */
+ wraplen = 6;
+ }
+
+ /* if not default parameters, return conservative bound */
+ if (s->w_bits != 15 || s->hash_bits != 8 + 7)
+ return complen + wraplen;
+
+ /* default settings: return tight bound for that case */
+ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+ (sourceLen >> 25) + 13 - 6 + wraplen;
+}
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+local void putShortMSB (s, b)
+ deflate_state *s;
+ uInt b;
+{
+ put_byte(s, (Byte)(b >> 8));
+ put_byte(s, (Byte)(b & 0xff));
+}
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->next_out buffer and copying into it.
+ * (See also read_buf()).
+ */
+local void flush_pending(strm)
+ z_streamp strm;
+{
+ unsigned len;
+ deflate_state *s = strm->state;
+
+ _tr_flush_bits(s);
+ len = s->pending;
+ if (len > strm->avail_out) len = strm->avail_out;
+ if (len == 0) return;
+
+ zmemcpy(strm->next_out, s->pending_out, len);
+ strm->next_out += len;
+ s->pending_out += len;
+ strm->total_out += len;
+ strm->avail_out -= len;
+ s->pending -= len;
+ if (s->pending == 0) {
+ s->pending_out = s->pending_buf;
+ }
+}
+
+/* ========================================================================= */
+int ZEXPORT deflate (strm, flush)
+ z_streamp strm;
+ int flush;
+{
+ int old_flush; /* value of flush param for previous deflate call */
+ deflate_state *s;
+
+ if (strm == Z_NULL || strm->state == Z_NULL ||
+ flush > Z_BLOCK || flush < 0) {
+ return Z_STREAM_ERROR;
+ }
+ s = strm->state;
+
+ if (strm->next_out == Z_NULL ||
+ (strm->next_in == Z_NULL && strm->avail_in != 0) ||
+ (s->status == FINISH_STATE && flush != Z_FINISH)) {
+ ERR_RETURN(strm, Z_STREAM_ERROR);
+ }
+ if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR);
+
+ s->strm = strm; /* just in case */
+ old_flush = s->last_flush;
+ s->last_flush = flush;
+
+ /* Write the header */
+ if (s->status == INIT_STATE) {
+#ifdef GZIP
+ if (s->wrap == 2) {
+ strm->adler = crc32(0L, Z_NULL, 0);
+ put_byte(s, 31);
+ put_byte(s, 139);
+ put_byte(s, 8);
+ if (s->gzhead == Z_NULL) {
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, OS_CODE);
+ s->status = BUSY_STATE;
+ }
+ else {
+ put_byte(s, (s->gzhead->text ? 1 : 0) +
+ (s->gzhead->hcrc ? 2 : 0) +
+ (s->gzhead->extra == Z_NULL ? 0 : 4) +
+ (s->gzhead->name == Z_NULL ? 0 : 8) +
+ (s->gzhead->comment == Z_NULL ? 0 : 16)
+ );
+ put_byte(s, (Byte)(s->gzhead->time & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, s->gzhead->os & 0xff);
+ if (s->gzhead->extra != Z_NULL) {
+ put_byte(s, s->gzhead->extra_len & 0xff);
+ put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
+ }
+ if (s->gzhead->hcrc)
+ strm->adler = crc32(strm->adler, s->pending_buf,
+ s->pending);
+ s->gzindex = 0;
+ s->status = EXTRA_STATE;
+ }
+ }
+ else
+#endif
+ {
+ uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+ uInt level_flags;
+
+ if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
+ level_flags = 0;
+ else if (s->level < 6)
+ level_flags = 1;
+ else if (s->level == 6)
+ level_flags = 2;
+ else
+ level_flags = 3;
+ header |= (level_flags << 6);
+ if (s->strstart != 0) header |= PRESET_DICT;
+ header += 31 - (header % 31);
+
+ s->status = BUSY_STATE;
+ putShortMSB(s, header);
+
+ /* Save the adler32 of the preset dictionary: */
+ if (s->strstart != 0) {
+ putShortMSB(s, (uInt)(strm->adler >> 16));
+ putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ strm->adler = adler32(0L, Z_NULL, 0);
+ }
+ }
+#ifdef GZIP
+ if (s->status == EXTRA_STATE) {
+ if (s->gzhead->extra != Z_NULL) {
+ uInt beg = s->pending; /* start of bytes to update crc */
+
+ while (s->gzindex < (s->gzhead->extra_len & 0xffff)) {
+ if (s->pending == s->pending_buf_size) {
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ flush_pending(strm);
+ beg = s->pending;
+ if (s->pending == s->pending_buf_size)
+ break;
+ }
+ put_byte(s, s->gzhead->extra[s->gzindex]);
+ s->gzindex++;
+ }
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ if (s->gzindex == s->gzhead->extra_len) {
+ s->gzindex = 0;
+ s->status = NAME_STATE;
+ }
+ }
+ else
+ s->status = NAME_STATE;
+ }
+ if (s->status == NAME_STATE) {
+ if (s->gzhead->name != Z_NULL) {
+ uInt beg = s->pending; /* start of bytes to update crc */
+ int val;
+
+ do {
+ if (s->pending == s->pending_buf_size) {
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ flush_pending(strm);
+ beg = s->pending;
+ if (s->pending == s->pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ val = s->gzhead->name[s->gzindex++];
+ put_byte(s, val);
+ } while (val != 0);
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ if (val == 0) {
+ s->gzindex = 0;
+ s->status = COMMENT_STATE;
+ }
+ }
+ else
+ s->status = COMMENT_STATE;
+ }
+ if (s->status == COMMENT_STATE) {
+ if (s->gzhead->comment != Z_NULL) {
+ uInt beg = s->pending; /* start of bytes to update crc */
+ int val;
+
+ do {
+ if (s->pending == s->pending_buf_size) {
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ flush_pending(strm);
+ beg = s->pending;
+ if (s->pending == s->pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ val = s->gzhead->comment[s->gzindex++];
+ put_byte(s, val);
+ } while (val != 0);
+ if (s->gzhead->hcrc && s->pending > beg)
+ strm->adler = crc32(strm->adler, s->pending_buf + beg,
+ s->pending - beg);
+ if (val == 0)
+ s->status = HCRC_STATE;
+ }
+ else
+ s->status = HCRC_STATE;
+ }
+ if (s->status == HCRC_STATE) {
+ if (s->gzhead->hcrc) {
+ if (s->pending + 2 > s->pending_buf_size)
+ flush_pending(strm);
+ if (s->pending + 2 <= s->pending_buf_size) {
+ put_byte(s, (Byte)(strm->adler & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+ strm->adler = crc32(0L, Z_NULL, 0);
+ s->status = BUSY_STATE;
+ }
+ }
+ else
+ s->status = BUSY_STATE;
+ }
+#endif
+
+ /* Flush as much pending output as possible */
+ if (s->pending != 0) {
+ flush_pending(strm);
+ if (strm->avail_out == 0) {
+ /* Since avail_out is 0, deflate will be called again with
+ * more output space, but possibly with both pending and
+ * avail_in equal to zero. There won't be anything to do,
+ * but this is not an error situation so make sure we
+ * return OK instead of BUF_ERROR at next call of deflate:
+ */
+ s->last_flush = -1;
+ return Z_OK;
+ }
+
+ /* Make sure there is something to do and avoid duplicate consecutive
+ * flushes. For repeated and useless calls with Z_FINISH, we keep
+ * returning Z_STREAM_END instead of Z_BUF_ERROR.
+ */
+ } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) &&
+ flush != Z_FINISH) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
+ /* User must not provide more input after the first FINISH: */
+ if (s->status == FINISH_STATE && strm->avail_in != 0) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
+ /* Start a new block or continue the current one.
+ */
+ if (strm->avail_in != 0 || s->lookahead != 0 ||
+ (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) {
+ block_state bstate;
+
+ bstate = s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
+ (s->strategy == Z_RLE ? deflate_rle(s, flush) :
+ (*(configuration_table[s->level].func))(s, flush));
+
+ if (bstate == finish_started || bstate == finish_done) {
+ s->status = FINISH_STATE;
+ }
+ if (bstate == need_more || bstate == finish_started) {
+ if (strm->avail_out == 0) {
+ s->last_flush = -1; /* avoid BUF_ERROR next call, see above */
+ }
+ return Z_OK;
+ /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+ * of deflate should use the same flush parameter to make sure
+ * that the flush is complete. So we don't have to output an
+ * empty block here, this will be done at next call. This also
+ * ensures that for a very small output buffer, we emit at most
+ * one empty block.
+ */
+ }
+ if (bstate == block_done) {
+ if (flush == Z_PARTIAL_FLUSH) {
+ _tr_align(s);
+ } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+ _tr_stored_block(s, (char*)0, 0L, 0);
+ /* For a full flush, this empty block will be recognized
+ * as a special marker by inflate_sync().
+ */
+ if (flush == Z_FULL_FLUSH) {
+ CLEAR_HASH(s); /* forget history */
+ if (s->lookahead == 0) {
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->insert = 0;
+ }
+ }
+ }
+ flush_pending(strm);
+ if (strm->avail_out == 0) {
+ s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+ return Z_OK;
+ }
+ }
+ }
+ Assert(strm->avail_out > 0, "bug2");
+
+ if (flush != Z_FINISH) return Z_OK;
+ if (s->wrap <= 0) return Z_STREAM_END;
+
+ /* Write the trailer */
+#ifdef GZIP
+ if (s->wrap == 2) {
+ put_byte(s, (Byte)(strm->adler & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 16) & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 24) & 0xff));
+ put_byte(s, (Byte)(strm->total_in & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 8) & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 16) & 0xff));
+ put_byte(s, (Byte)((strm->total_in >> 24) & 0xff));
+ }
+ else
+#endif
+ {
+ putShortMSB(s, (uInt)(strm->adler >> 16));
+ putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ flush_pending(strm);
+ /* If avail_out is zero, the application will call deflate again
+ * to flush the rest.
+ */
+ if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */
+ return s->pending != 0 ? Z_OK : Z_STREAM_END;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateEnd (strm)
+ z_streamp strm;
+{
+ int status;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+
+ status = strm->state->status;
+ if (status != INIT_STATE &&
+ status != EXTRA_STATE &&
+ status != NAME_STATE &&
+ status != COMMENT_STATE &&
+ status != HCRC_STATE &&
+ status != BUSY_STATE &&
+ status != FINISH_STATE) {
+ return Z_STREAM_ERROR;
+ }
+
+ /* Deallocate in reverse order of allocations: */
+ TRY_FREE(strm, strm->state->pending_buf);
+ TRY_FREE(strm, strm->state->head);
+ TRY_FREE(strm, strm->state->prev);
+ TRY_FREE(strm, strm->state->window);
+
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+
+ return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+}
+
+/* =========================================================================
+ * Copy the source state to the destination state.
+ * To simplify the source, this is not supported for 16-bit MSDOS (which
+ * doesn't have enough memory anyway to duplicate compression states).
+ */
+int ZEXPORT deflateCopy (dest, source)
+ z_streamp dest;
+ z_streamp source;
+{
+#ifdef MAXSEG_64K
+ return Z_STREAM_ERROR;
+#else
+ deflate_state *ds;
+ deflate_state *ss;
+ ushf *overlay;
+
+
+ if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) {
+ return Z_STREAM_ERROR;
+ }
+
+ ss = source->state;
+
+ zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+
+ ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state));
+ if (ds == Z_NULL) return Z_MEM_ERROR;
+ dest->state = (struct internal_state FAR *) ds;
+ zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state));
+ ds->strm = dest;
+
+ ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
+ ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos));
+ ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos));
+ overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
+ ds->pending_buf = (uchf *) overlay;
+
+ if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
+ ds->pending_buf == Z_NULL) {
+ deflateEnd (dest);
+ return Z_MEM_ERROR;
+ }
+ /* following zmemcpy do not work for 16-bit MSDOS */
+ zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte));
+ zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos));
+ zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos));
+ zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
+
+ ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
+ ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
+ ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+
+ ds->l_desc.dyn_tree = ds->dyn_ltree;
+ ds->d_desc.dyn_tree = ds->dyn_dtree;
+ ds->bl_desc.dyn_tree = ds->bl_tree;
+
+ return Z_OK;
+#endif /* MAXSEG_64K */
+}
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read. All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->next_in buffer and copying from it.
+ * (See also flush_pending()).
+ */
+local int read_buf(strm, buf, size)
+ z_streamp strm;
+ Bytef *buf;
+ unsigned size;
+{
+ unsigned len = strm->avail_in;
+
+ if (len > size) len = size;
+ if (len == 0) return 0;
+
+ strm->avail_in -= len;
+
+ zmemcpy(buf, strm->next_in, len);
+ if (strm->state->wrap == 1) {
+ strm->adler = adler32(strm->adler, buf, len);
+ }
+#ifdef GZIP
+ else if (strm->state->wrap == 2) {
+ strm->adler = crc32(strm->adler, buf, len);
+ }
+#endif
+ strm->next_in += len;
+ strm->total_in += len;
+
+ return (int)len;
+}
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+local void lm_init (s)
+ deflate_state *s;
+{
+ s->window_size = (ulg)2L*s->w_size;
+
+ CLEAR_HASH(s);
+
+ /* Set the default configuration parameters:
+ */
+ s->max_lazy_match = configuration_table[s->level].max_lazy;
+ s->good_match = configuration_table[s->level].good_length;
+ s->nice_match = configuration_table[s->level].nice_length;
+ s->max_chain_length = configuration_table[s->level].max_chain;
+
+ s->strstart = 0;
+ s->block_start = 0L;
+ s->lookahead = 0;
+ s->insert = 0;
+ s->match_length = s->prev_length = MIN_MATCH-1;
+ s->match_available = 0;
+ s->ins_h = 0;
+#ifndef FASTEST
+#ifdef ASMV
+ match_init(); /* initialize the asm code */
+#endif
+#endif
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+#ifndef ASMV
+/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
+ * match.S. The code will be functionally equivalent.
+ */
+local uInt longest_match(s, cur_match)
+ deflate_state *s;
+ IPos cur_match; /* current match */
+{
+ unsigned chain_length = s->max_chain_length;/* max hash chain length */
+ register Bytef *scan = s->window + s->strstart; /* current string */
+ register Bytef *match; /* matched string */
+ register int len; /* length of current match */
+ int best_len = s->prev_length; /* best match length so far */
+ int nice_match = s->nice_match; /* stop if match long enough */
+ IPos limit = s->strstart > (IPos)MAX_DIST(s) ?
+ s->strstart - (IPos)MAX_DIST(s) : NIL;
+ /* Stop when cur_match becomes <= limit. To simplify the code,
+ * we prevent matches with the string of window index 0.
+ */
+ Posf *prev = s->prev;
+ uInt wmask = s->w_mask;
+
+#ifdef UNALIGNED_OK
+ /* Compare two bytes at a time. Note: this is not always beneficial.
+ * Try with and without -DUNALIGNED_OK to check.
+ */
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1;
+ register ush scan_start = *(ushf*)scan;
+ register ush scan_end = *(ushf*)(scan+best_len-1);
+#else
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+ register Byte scan_end1 = scan[best_len-1];
+ register Byte scan_end = scan[best_len];
+#endif
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ /* Do not waste too much time if we already have a good match: */
+ if (s->prev_length >= s->good_match) {
+ chain_length >>= 2;
+ }
+ /* Do not look for matches beyond the end of the input. This is necessary
+ * to make deflate deterministic.
+ */
+ if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead;
+
+ Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ do {
+ Assert(cur_match < s->strstart, "no future");
+ match = s->window + cur_match;
+
+ /* Skip to next match if the match length cannot increase
+ * or if the match length is less than 2. Note that the checks below
+ * for insufficient lookahead only occur occasionally for performance
+ * reasons. Therefore uninitialized memory will be accessed, and
+ * conditional jumps will be made that depend on those values.
+ * However the length of the match is limited to the lookahead, so
+ * the output of deflate is not affected by the uninitialized values.
+ */
+#if (defined(UNALIGNED_OK) && MAX_MATCH == 258)
+ /* This code assumes sizeof(unsigned short) == 2. Do not use
+ * UNALIGNED_OK if your compiler uses a different size.
+ */
+ if (*(ushf*)(match+best_len-1) != scan_end ||
+ *(ushf*)match != scan_start) continue;
+
+ /* It is not necessary to compare scan[2] and match[2] since they are
+ * always equal when the other bytes match, given that the hash keys
+ * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at
+ * strstart+3, +5, ... up to strstart+257. We check for insufficient
+ * lookahead only every 4th comparison; the 128th check will be made
+ * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is
+ * necessary to put more guard bytes at the end of the window, or
+ * to check more often for insufficient lookahead.
+ */
+ Assert(scan[2] == match[2], "scan[2]?");
+ scan++, match++;
+ do {
+ } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+ scan < strend);
+ /* The funny "do {}" generates better code on most compilers */
+
+ /* Here, scan <= window+strstart+257 */
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+ if (*scan == *match) scan++;
+
+ len = (MAX_MATCH - 1) - (int)(strend-scan);
+ scan = strend - (MAX_MATCH-1);
+
+#else /* UNALIGNED_OK */
+
+ if (match[best_len] != scan_end ||
+ match[best_len-1] != scan_end1 ||
+ *match != *scan ||
+ *++match != scan[1]) continue;
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2, match++;
+ Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ } while (*++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ scan < strend);
+
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH - (int)(strend - scan);
+ scan = strend - MAX_MATCH;
+
+#endif /* UNALIGNED_OK */
+
+ if (len > best_len) {
+ s->match_start = cur_match;
+ best_len = len;
+ if (len >= nice_match) break;
+#ifdef UNALIGNED_OK
+ scan_end = *(ushf*)(scan+best_len-1);
+#else
+ scan_end1 = scan[best_len-1];
+ scan_end = scan[best_len];
+#endif
+ }
+ } while ((cur_match = prev[cur_match & wmask]) > limit
+ && --chain_length != 0);
+
+ if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
+ return s->lookahead;
+}
+#endif /* ASMV */
+
+#else /* FASTEST */
+
+/* ---------------------------------------------------------------------------
+ * Optimized version for FASTEST only
+ */
+local uInt longest_match(s, cur_match)
+ deflate_state *s;
+ IPos cur_match; /* current match */
+{
+ register Bytef *scan = s->window + s->strstart; /* current string */
+ register Bytef *match; /* matched string */
+ register int len; /* length of current match */
+ register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ Assert(cur_match < s->strstart, "no future");
+
+ match = s->window + cur_match;
+
+ /* Return failure if the match length is less than 2:
+ */
+ if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1;
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2, match += 2;
+ Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ } while (*++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ *++scan == *++match && *++scan == *++match &&
+ scan < strend);
+
+ Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH - (int)(strend - scan);
+
+ if (len < MIN_MATCH) return MIN_MATCH - 1;
+
+ s->match_start = cur_match;
+ return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead;
+}
+
+#endif /* FASTEST */
+
+#ifdef DEBUG
+/* ===========================================================================
+ * Check that the match at match_start is indeed a match.
+ */
+local void check_match(s, start, match, length)
+ deflate_state *s;
+ IPos start, match;
+ int length;
+{
+ /* check that the match is indeed a match */
+ if (zmemcmp(s->window + match,
+ s->window + start, length) != EQUAL) {
+ fprintf(stderr, " start %u, match %u, length %d\n",
+ start, match, length);
+ do {
+ fprintf(stderr, "%c%c", s->window[match++], s->window[start++]);
+ } while (--length != 0);
+ z_error("invalid match");
+ }
+ if (z_verbose > 1) {
+ fprintf(stderr,"\\[%d,%d]", start-match, length);
+ do { putc(s->window[start++], stderr); } while (--length != 0);
+ }
+}
+#else
+# define check_match(s, start, match, length)
+#endif /* DEBUG */
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ * At least one byte has been read, or avail_in == 0; reads are
+ * performed for at least two bytes (required for the zip translate_eol
+ * option -- not supported here).
+ */
+local void fill_window(s)
+ deflate_state *s;
+{
+ register unsigned n, m;
+ register Posf *p;
+ unsigned more; /* Amount of free space at the end of the window. */
+ uInt wsize = s->w_size;
+
+ Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+ do {
+ more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart);
+
+ /* Deal with !@#$% 64K limit: */
+ if (sizeof(int) <= 2) {
+ if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+ more = wsize;
+
+ } else if (more == (unsigned)(-1)) {
+ /* Very unlikely, but possible on 16 bit machine if
+ * strstart == 0 && lookahead == 1 (input done a byte at time)
+ */
+ more--;
+ }
+ }
+
+ /* If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (s->strstart >= wsize+MAX_DIST(s)) {
+
+ zmemcpy(s->window, s->window+wsize, (unsigned)wsize);
+ s->match_start -= wsize;
+ s->strstart -= wsize; /* we now have strstart >= MAX_DIST */
+ s->block_start -= (long) wsize;
+
+ /* Slide the hash table (could be avoided with 32 bit values
+ at the expense of memory usage). We slide even when level == 0
+ to keep the hash table consistent if we switch back to level > 0
+ later. (Using level 0 permanently is not an optimal usage of
+ zlib, so we don't care about this pathological case.)
+ */
+ n = s->hash_size;
+ p = &s->head[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m-wsize : NIL);
+ } while (--n);
+
+ n = wsize;
+#ifndef FASTEST
+ p = &s->prev[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m-wsize : NIL);
+ /* If n is not on any hash chain, prev[n] is garbage but
+ * its value will never be used.
+ */
+ } while (--n);
+#endif
+ more += wsize;
+ }
+ if (s->strm->avail_in == 0) break;
+
+ /* If there was no sliding:
+ * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ * more == window_size - lookahead - strstart
+ * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ * => more >= window_size - 2*WSIZE + 2
+ * In the BIG_MEM or MMAP case (not yet supported),
+ * window_size == input_size + MIN_LOOKAHEAD &&
+ * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ * Otherwise, window_size == 2*WSIZE so more >= 2.
+ * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+ */
+ Assert(more >= 2, "more < 2");
+
+ n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more);
+ s->lookahead += n;
+
+ /* Initialize the hash value now that we have some input: */
+ if (s->lookahead + s->insert >= MIN_MATCH) {
+ uInt str = s->strstart - s->insert;
+ s->ins_h = s->window[str];
+ UPDATE_HASH(s, s->ins_h, s->window[str + 1]);
+#if MIN_MATCH != 3
+ Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+ while (s->insert) {
+ UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+ s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+ s->head[s->ins_h] = (Pos)str;
+ str++;
+ s->insert--;
+ if (s->lookahead + s->insert < MIN_MATCH)
+ break;
+ }
+ }
+ /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+ * but this is not important since only literal bytes will be emitted.
+ */
+
+ } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0);
+
+ /* If the WIN_INIT bytes after the end of the current data have never been
+ * written, then zero those bytes in order to avoid memory check reports of
+ * the use of uninitialized (or uninitialised as Julian writes) bytes by
+ * the longest match routines. Update the high water mark for the next
+ * time through here. WIN_INIT is set to MAX_MATCH since the longest match
+ * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+ */
+ if (s->high_water < s->window_size) {
+ ulg curr = s->strstart + (ulg)(s->lookahead);
+ ulg init;
+
+ if (s->high_water < curr) {
+ /* Previous high water mark below current data -- zero WIN_INIT
+ * bytes or up to end of window, whichever is less.
+ */
+ init = s->window_size - curr;
+ if (init > WIN_INIT)
+ init = WIN_INIT;
+ zmemzero(s->window + curr, (unsigned)init);
+ s->high_water = curr + init;
+ }
+ else if (s->high_water < (ulg)curr + WIN_INIT) {
+ /* High water mark at or above current data, but below current data
+ * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+ * to end of window, whichever is less.
+ */
+ init = (ulg)curr + WIN_INIT - s->high_water;
+ if (init > s->window_size - s->high_water)
+ init = s->window_size - s->high_water;
+ zmemzero(s->window + s->high_water, (unsigned)init);
+ s->high_water += init;
+ }
+ }
+
+ Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+ "not enough room for search");
+}
+
+/* ===========================================================================
+ * Flush the current block, with given end-of-file flag.
+ * IN assertion: strstart is set to the end of the current match.
+ */
+#define FLUSH_BLOCK_ONLY(s, last) { \
+ _tr_flush_block(s, (s->block_start >= 0L ? \
+ (charf *)&s->window[(unsigned)s->block_start] : \
+ (charf *)Z_NULL), \
+ (ulg)((long)s->strstart - s->block_start), \
+ (last)); \
+ s->block_start = s->strstart; \
+ flush_pending(s->strm); \
+ Tracev((stderr,"[FLUSH]")); \
+}
+
+/* Same but force premature exit if necessary. */
+#define FLUSH_BLOCK(s, last) { \
+ FLUSH_BLOCK_ONLY(s, last); \
+ if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \
+}
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+local block_state deflate_stored(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+ * to pending_buf_size, and each stored block has a 5 byte header:
+ */
+ ulg max_block_size = 0xffff;
+ ulg max_start;
+
+ if (max_block_size > s->pending_buf_size - 5) {
+ max_block_size = s->pending_buf_size - 5;
+ }
+
+ /* Copy as much as possible from input to output: */
+ for (;;) {
+ /* Fill the window as much as possible: */
+ if (s->lookahead <= 1) {
+
+ Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+ s->block_start >= (long)s->w_size, "slide too late");
+
+ fill_window(s);
+ if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more;
+
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+ Assert(s->block_start >= 0L, "block gone");
+
+ s->strstart += s->lookahead;
+ s->lookahead = 0;
+
+ /* Emit a stored block if pending_buf will be full: */
+ max_start = s->block_start + max_block_size;
+ if (s->strstart == 0 || (ulg)s->strstart >= max_start) {
+ /* strstart == 0 is possible when wraparound on 16-bit machine */
+ s->lookahead = (uInt)(s->strstart - max_start);
+ s->strstart = (uInt)max_start;
+ FLUSH_BLOCK(s, 0);
+ }
+ /* Flush if we may have to slide, otherwise block_start may become
+ * negative and the data will be gone:
+ */
+ if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) {
+ FLUSH_BLOCK(s, 0);
+ }
+ }
+ s->insert = 0;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if ((long)s->strstart > s->block_start)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+local block_state deflate_fast(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ IPos hash_head; /* head of the hash chain */
+ int bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s->lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = NIL;
+ if (s->lookahead >= MIN_MATCH) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ * At this point we have always match_length < MIN_MATCH
+ */
+ if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s->match_length = longest_match (s, hash_head);
+ /* longest_match() sets match_start */
+ }
+ if (s->match_length >= MIN_MATCH) {
+ check_match(s, s->strstart, s->match_start, s->match_length);
+
+ _tr_tally_dist(s, s->strstart - s->match_start,
+ s->match_length - MIN_MATCH, bflush);
+
+ s->lookahead -= s->match_length;
+
+ /* Insert new strings in the hash table only if the match length
+ * is not too large. This saves time but degrades compression.
+ */
+#ifndef FASTEST
+ if (s->match_length <= s->max_insert_length &&
+ s->lookahead >= MIN_MATCH) {
+ s->match_length--; /* string at strstart already in table */
+ do {
+ s->strstart++;
+ INSERT_STRING(s, s->strstart, hash_head);
+ /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+ * always MIN_MATCH bytes ahead.
+ */
+ } while (--s->match_length != 0);
+ s->strstart++;
+ } else
+#endif
+ {
+ s->strstart += s->match_length;
+ s->match_length = 0;
+ s->ins_h = s->window[s->strstart];
+ UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+#if MIN_MATCH != 3
+ Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+ /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+ * matter since it will be recomputed at next deflate call.
+ */
+ }
+ } else {
+ /* No match, output a literal byte */
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ }
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+local block_state deflate_slow(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ IPos hash_head; /* head of hash chain */
+ int bflush; /* set if current block must be flushed */
+
+ /* Process the input block. */
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s->lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = NIL;
+ if (s->lookahead >= MIN_MATCH) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ */
+ s->prev_length = s->match_length, s->prev_match = s->match_start;
+ s->match_length = MIN_MATCH-1;
+
+ if (hash_head != NIL && s->prev_length < s->max_lazy_match &&
+ s->strstart - hash_head <= MAX_DIST(s)) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s->match_length = longest_match (s, hash_head);
+ /* longest_match() sets match_start */
+
+ if (s->match_length <= 5 && (s->strategy == Z_FILTERED
+#if TOO_FAR <= 32767
+ || (s->match_length == MIN_MATCH &&
+ s->strstart - s->match_start > TOO_FAR)
+#endif
+ )) {
+
+ /* If prev_match is also MIN_MATCH, match_start is garbage
+ * but we will ignore the current match anyway.
+ */
+ s->match_length = MIN_MATCH-1;
+ }
+ }
+ /* If there was a match at the previous step and the current
+ * match is not better, output the previous match:
+ */
+ if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) {
+ uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;
+ /* Do not insert strings in hash table beyond this. */
+
+ check_match(s, s->strstart-1, s->prev_match, s->prev_length);
+
+ _tr_tally_dist(s, s->strstart -1 - s->prev_match,
+ s->prev_length - MIN_MATCH, bflush);
+
+ /* Insert in hash table all strings up to the end of the match.
+ * strstart-1 and strstart are already inserted. If there is not
+ * enough lookahead, the last two strings are not inserted in
+ * the hash table.
+ */
+ s->lookahead -= s->prev_length-1;
+ s->prev_length -= 2;
+ do {
+ if (++s->strstart <= max_insert) {
+ INSERT_STRING(s, s->strstart, hash_head);
+ }
+ } while (--s->prev_length != 0);
+ s->match_available = 0;
+ s->match_length = MIN_MATCH-1;
+ s->strstart++;
+
+ if (bflush) FLUSH_BLOCK(s, 0);
+
+ } else if (s->match_available) {
+ /* If there was no match at the previous position, output a
+ * single literal. If there was a match but the current match
+ * is longer, truncate the previous match to a single literal.
+ */
+ Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ if (bflush) {
+ FLUSH_BLOCK_ONLY(s, 0);
+ }
+ s->strstart++;
+ s->lookahead--;
+ if (s->strm->avail_out == 0) return need_more;
+ } else {
+ /* There is no previous match to compare with, wait for
+ * the next step to decide.
+ */
+ s->match_available = 1;
+ s->strstart++;
+ s->lookahead--;
+ }
+ }
+ Assert (flush != Z_NO_FLUSH, "no flush?");
+ if (s->match_available) {
+ Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+ s->match_available = 0;
+ }
+ s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+#endif /* FASTEST */
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one. Do not maintain a hash table. (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+local block_state deflate_rle(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ int bflush; /* set if current block must be flushed */
+ uInt prev; /* byte at distance one to match */
+ Bytef *scan, *strend; /* scan goes up to strend for length of run */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the longest run, plus one for the unrolled loop.
+ */
+ if (s->lookahead <= MAX_MATCH) {
+ fill_window(s);
+ if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) {
+ return need_more;
+ }
+ if (s->lookahead == 0) break; /* flush the current block */
+ }
+
+ /* See how many times the previous byte repeats */
+ s->match_length = 0;
+ if (s->lookahead >= MIN_MATCH && s->strstart > 0) {
+ scan = s->window + s->strstart - 1;
+ prev = *scan;
+ if (prev == *++scan && prev == *++scan && prev == *++scan) {
+ strend = s->window + s->strstart + MAX_MATCH;
+ do {
+ } while (prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ prev == *++scan && prev == *++scan &&
+ scan < strend);
+ s->match_length = MAX_MATCH - (int)(strend - scan);
+ if (s->match_length > s->lookahead)
+ s->match_length = s->lookahead;
+ }
+ Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+ }
+
+ /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+ if (s->match_length >= MIN_MATCH) {
+ check_match(s, s->strstart, s->strstart - 1, s->match_length);
+
+ _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush);
+
+ s->lookahead -= s->match_length;
+ s->strstart += s->match_length;
+ s->match_length = 0;
+ } else {
+ /* No match, output a literal byte */
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ }
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = 0;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+local block_state deflate_huff(s, flush)
+ deflate_state *s;
+ int flush;
+{
+ int bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we have a literal to write. */
+ if (s->lookahead == 0) {
+ fill_window(s);
+ if (s->lookahead == 0) {
+ if (flush == Z_NO_FLUSH)
+ return need_more;
+ break; /* flush the current block */
+ }
+ }
+
+ /* Output a literal byte */
+ s->match_length = 0;
+ Tracevv((stderr,"%c", s->window[s->strstart]));
+ _tr_tally_lit (s, s->window[s->strstart], bflush);
+ s->lookahead--;
+ s->strstart++;
+ if (bflush) FLUSH_BLOCK(s, 0);
+ }
+ s->insert = 0;
+ if (flush == Z_FINISH) {
+ FLUSH_BLOCK(s, 1);
+ return finish_done;
+ }
+ if (s->last_lit)
+ FLUSH_BLOCK(s, 0);
+ return block_done;
+}
diff --git a/src/cpp/core/zlib/deflate.h b/src/cpp/core/zlib/deflate.h
new file mode 100644
index 0000000..fbac44d
--- /dev/null
+++ b/src/cpp/core/zlib/deflate.h
@@ -0,0 +1,346 @@
+/* deflate.h -- internal compression state
+ * Copyright (C) 1995-2012 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef DEFLATE_H
+#define DEFLATE_H
+
+#include "zutil.h"
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+ trailer creation by deflate(). NO_GZIP would be used to avoid linking in
+ the crc code when it is not needed. For shared libraries, gzip encoding
+ should be left enabled. */
+#ifndef NO_GZIP
+# define GZIP
+#endif
+
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+#define LENGTH_CODES 29
+/* number of length codes, not counting the special END_BLOCK code */
+
+#define LITERALS 256
+/* number of literal bytes 0..255 */
+
+#define L_CODES (LITERALS+1+LENGTH_CODES)
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+#define D_CODES 30
+/* number of distance codes */
+
+#define BL_CODES 19
+/* number of codes used to transfer the bit lengths */
+
+#define HEAP_SIZE (2*L_CODES+1)
+/* maximum heap size */
+
+#define MAX_BITS 15
+/* All codes must not exceed MAX_BITS bits */
+
+#define Buf_size 16
+/* size of bit buffer in bi_buf */
+
+#define INIT_STATE 42
+#define EXTRA_STATE 69
+#define NAME_STATE 73
+#define COMMENT_STATE 91
+#define HCRC_STATE 103
+#define BUSY_STATE 113
+#define FINISH_STATE 666
+/* Stream status */
+
+
+/* Data structure describing a single value and its code string. */
+typedef struct ct_data_s {
+ union {
+ ush freq; /* frequency count */
+ ush code; /* bit string */
+ } fc;
+ union {
+ ush dad; /* father node in Huffman tree */
+ ush len; /* length of bit string */
+ } dl;
+} FAR ct_data;
+
+#define Freq fc.freq
+#define Code fc.code
+#define Dad dl.dad
+#define Len dl.len
+
+typedef struct static_tree_desc_s static_tree_desc;
+
+typedef struct tree_desc_s {
+ ct_data *dyn_tree; /* the dynamic tree */
+ int max_code; /* largest code with non zero frequency */
+ static_tree_desc *stat_desc; /* the corresponding static tree */
+} FAR tree_desc;
+
+typedef ush Pos;
+typedef Pos FAR Posf;
+typedef unsigned IPos;
+
+/* A Pos is an index in the character window. We use short instead of int to
+ * save space in the various tables. IPos is used only for parameter passing.
+ */
+
+typedef struct internal_state {
+ z_streamp strm; /* pointer back to this zlib stream */
+ int status; /* as the name implies */
+ Bytef *pending_buf; /* output still pending */
+ ulg pending_buf_size; /* size of pending_buf */
+ Bytef *pending_out; /* next pending byte to output to the stream */
+ uInt pending; /* nb of bytes in the pending buffer */
+ int wrap; /* bit 0 true for zlib, bit 1 true for gzip */
+ gz_headerp gzhead; /* gzip header information to write */
+ uInt gzindex; /* where in extra, name, or comment */
+ Byte method; /* STORED (for zip only) or DEFLATED */
+ int last_flush; /* value of flush param for previous deflate call */
+
+ /* used by deflate.c: */
+
+ uInt w_size; /* LZ77 window size (32K by default) */
+ uInt w_bits; /* log2(w_size) (8..16) */
+ uInt w_mask; /* w_size - 1 */
+
+ Bytef *window;
+ /* Sliding window. Input bytes are read into the second half of the window,
+ * and move to the first half later to keep a dictionary of at least wSize
+ * bytes. With this organization, matches are limited to a distance of
+ * wSize-MAX_MATCH bytes, but this ensures that IO is always
+ * performed with a length multiple of the block size. Also, it limits
+ * the window size to 64K, which is quite useful on MSDOS.
+ * To do: use the user input buffer as sliding window.
+ */
+
+ ulg window_size;
+ /* Actual size of window: 2*wSize, except when the user input buffer
+ * is directly used as sliding window.
+ */
+
+ Posf *prev;
+ /* Link to older string with same hash index. To limit the size of this
+ * array to 64K, this link is maintained only for the last 32K strings.
+ * An index in this array is thus a window index modulo 32K.
+ */
+
+ Posf *head; /* Heads of the hash chains or NIL. */
+
+ uInt ins_h; /* hash index of string to be inserted */
+ uInt hash_size; /* number of elements in hash table */
+ uInt hash_bits; /* log2(hash_size) */
+ uInt hash_mask; /* hash_size-1 */
+
+ uInt hash_shift;
+ /* Number of bits by which ins_h must be shifted at each input
+ * step. It must be such that after MIN_MATCH steps, the oldest
+ * byte no longer takes part in the hash key, that is:
+ * hash_shift * MIN_MATCH >= hash_bits
+ */
+
+ long block_start;
+ /* Window position at the beginning of the current output block. Gets
+ * negative when the window is moved backwards.
+ */
+
+ uInt match_length; /* length of best match */
+ IPos prev_match; /* previous match */
+ int match_available; /* set if previous match exists */
+ uInt strstart; /* start of string to insert */
+ uInt match_start; /* start of matching string */
+ uInt lookahead; /* number of valid bytes ahead in window */
+
+ uInt prev_length;
+ /* Length of the best match at previous step. Matches not greater than this
+ * are discarded. This is used in the lazy match evaluation.
+ */
+
+ uInt max_chain_length;
+ /* To speed up deflation, hash chains are never searched beyond this
+ * length. A higher limit improves compression ratio but degrades the
+ * speed.
+ */
+
+ uInt max_lazy_match;
+ /* Attempt to find a better match only when the current match is strictly
+ * smaller than this value. This mechanism is used only for compression
+ * levels >= 4.
+ */
+# define max_insert_length max_lazy_match
+ /* Insert new strings in the hash table only if the match length is not
+ * greater than this length. This saves time but degrades compression.
+ * max_insert_length is used only for compression levels <= 3.
+ */
+
+ int level; /* compression level (1..9) */
+ int strategy; /* favor or force Huffman coding*/
+
+ uInt good_match;
+ /* Use a faster search when the previous match is longer than this */
+
+ int nice_match; /* Stop searching when current match exceeds this */
+
+ /* used by trees.c: */
+ /* Didn't use ct_data typedef below to suppress compiler warning */
+ struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */
+ struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+ struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */
+
+ struct tree_desc_s l_desc; /* desc. for literal tree */
+ struct tree_desc_s d_desc; /* desc. for distance tree */
+ struct tree_desc_s bl_desc; /* desc. for bit length tree */
+
+ ush bl_count[MAX_BITS+1];
+ /* number of codes at each bit length for an optimal tree */
+
+ int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */
+ int heap_len; /* number of elements in the heap */
+ int heap_max; /* element of largest frequency */
+ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ * The same heap array is used to build all trees.
+ */
+
+ uch depth[2*L_CODES+1];
+ /* Depth of each subtree used as tie breaker for trees of equal frequency
+ */
+
+ uchf *l_buf; /* buffer for literals or lengths */
+
+ uInt lit_bufsize;
+ /* Size of match buffer for literals/lengths. There are 4 reasons for
+ * limiting lit_bufsize to 64K:
+ * - frequencies can be kept in 16 bit counters
+ * - if compression is not successful for the first block, all input
+ * data is still in the window so we can still emit a stored block even
+ * when input comes from standard input. (This can also be done for
+ * all blocks if lit_bufsize is not greater than 32K.)
+ * - if compression is not successful for a file smaller than 64K, we can
+ * even emit a stored file instead of a stored block (saving 5 bytes).
+ * This is applicable only for zip (not gzip or zlib).
+ * - creating new Huffman trees less frequently may not provide fast
+ * adaptation to changes in the input data statistics. (Take for
+ * example a binary file with poorly compressible code followed by
+ * a highly compressible string table.) Smaller buffer sizes give
+ * fast adaptation but have of course the overhead of transmitting
+ * trees more frequently.
+ * - I can't count above 4
+ */
+
+ uInt last_lit; /* running index in l_buf */
+
+ ushf *d_buf;
+ /* Buffer for distances. To simplify the code, d_buf and l_buf have
+ * the same number of elements. To use different lengths, an extra flag
+ * array would be necessary.
+ */
+
+ ulg opt_len; /* bit length of current block with optimal trees */
+ ulg static_len; /* bit length of current block with static trees */
+ uInt matches; /* number of string matches in current block */
+ uInt insert; /* bytes at end of window left to insert */
+
+#ifdef DEBUG
+ ulg compressed_len; /* total bit length of compressed file mod 2^32 */
+ ulg bits_sent; /* bit length of compressed data sent mod 2^32 */
+#endif
+
+ ush bi_buf;
+ /* Output buffer. bits are inserted starting at the bottom (least
+ * significant bits).
+ */
+ int bi_valid;
+ /* Number of valid bits in bi_buf. All bits above the last valid bit
+ * are always zero.
+ */
+
+ ulg high_water;
+ /* High water mark offset in window for initialized bytes -- bytes above
+ * this are set to zero in order to avoid memory check warnings when
+ * longest match routines access bytes past the input. This is then
+ * updated to the new high water mark.
+ */
+
+} FAR deflate_state;
+
+/* Output a byte on the stream.
+ * IN assertion: there is enough room in pending_buf.
+ */
+#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);}
+
+
+#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1)
+/* Minimum amount of lookahead, except at the end of the input file.
+ * See deflate.c for comments about the MIN_MATCH+1.
+ */
+
+#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD)
+/* In order to simplify the code, particularly on 16 bit machines, match
+ * distances are limited to MAX_DIST instead of WSIZE.
+ */
+
+#define WIN_INIT MAX_MATCH
+/* Number of bytes after end of data in window to initialize in order to avoid
+ memory checker errors from longest match routines */
+
+ /* in trees.c */
+void ZLIB_INTERNAL _tr_init OF((deflate_state *s));
+int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc));
+void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf,
+ ulg stored_len, int last));
+void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_align OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
+ ulg stored_len, int last));
+
+#define d_code(dist) \
+ ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. _dist_code[256] and _dist_code[257] are never
+ * used.
+ */
+
+#ifndef DEBUG
+/* Inline versions of _tr_tally for speed: */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+ extern uch ZLIB_INTERNAL _length_code[];
+ extern uch ZLIB_INTERNAL _dist_code[];
+#else
+ extern const uch ZLIB_INTERNAL _length_code[];
+ extern const uch ZLIB_INTERNAL _dist_code[];
+#endif
+
+# define _tr_tally_lit(s, c, flush) \
+ { uch cc = (c); \
+ s->d_buf[s->last_lit] = 0; \
+ s->l_buf[s->last_lit++] = cc; \
+ s->dyn_ltree[cc].Freq++; \
+ flush = (s->last_lit == s->lit_bufsize-1); \
+ }
+# define _tr_tally_dist(s, distance, length, flush) \
+ { uch len = (length); \
+ ush dist = (distance); \
+ s->d_buf[s->last_lit] = dist; \
+ s->l_buf[s->last_lit++] = len; \
+ dist--; \
+ s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
+ s->dyn_dtree[d_code(dist)].Freq++; \
+ flush = (s->last_lit == s->lit_bufsize-1); \
+ }
+#else
+# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
+# define _tr_tally_dist(s, distance, length, flush) \
+ flush = _tr_tally(s, distance, length)
+#endif
+
+#endif /* DEFLATE_H */
diff --git a/src/cpp/core/zlib/gzclose.c b/src/cpp/core/zlib/gzclose.c
new file mode 100644
index 0000000..caeb99a
--- /dev/null
+++ b/src/cpp/core/zlib/gzclose.c
@@ -0,0 +1,25 @@
+/* gzclose.c -- zlib gzclose() function
+ * Copyright (C) 2004, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* gzclose() is in a separate file so that it is linked in only if it is used.
+ That way the other gzclose functions can be used instead to avoid linking in
+ unneeded compression or decompression routines. */
+int ZEXPORT gzclose(file)
+ gzFile file;
+{
+#ifndef NO_GZCOMPRESS
+ gz_statep state;
+
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file);
+#else
+ return gzclose_r(file);
+#endif
+}
diff --git a/src/cpp/core/zlib/gzguts.h b/src/cpp/core/zlib/gzguts.h
new file mode 100644
index 0000000..3107c36
--- /dev/null
+++ b/src/cpp/core/zlib/gzguts.h
@@ -0,0 +1,190 @@
+/* gzguts.h -- zlib internal header definitions for gz* operations
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#ifdef _LARGEFILE64_SOURCE
+# ifndef _LARGEFILE_SOURCE
+# define _LARGEFILE_SOURCE 1
+# endif
+# ifdef _FILE_OFFSET_BITS
+# undef _FILE_OFFSET_BITS
+# endif
+#endif
+
+#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ)
+# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+# define ZLIB_INTERNAL
+#endif
+
+#include <stdio.h>
+#include "zlib.h"
+#ifdef STDC
+# include <string.h>
+# include <stdlib.h>
+# include <limits.h>
+#endif
+#include <fcntl.h>
+
+#ifdef __TURBOC__
+# include <io.h>
+#endif
+
+#ifdef NO_DEFLATE /* for compatibility with old definition */
+# define NO_GZCOMPRESS
+#endif
+
+#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#if defined(__CYGWIN__)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410)
+# ifndef HAVE_VSNPRINTF
+# define HAVE_VSNPRINTF
+# endif
+#endif
+
+#ifndef HAVE_VSNPRINTF
+# ifdef MSDOS
+/* vsnprintf may exist on some MS-DOS compilers (DJGPP?),
+ but for now we just assume it doesn't. */
+# define NO_vsnprintf
+# endif
+# ifdef __TURBOC__
+# define NO_vsnprintf
+# endif
+# ifdef WIN32
+/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */
+# if !defined(vsnprintf) && !defined(NO_vsnprintf)
+# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 )
+# include <io.h>
+# define vsnprintf _vsnprintf
+# endif
+# endif
+# endif
+# ifdef __SASC
+# define NO_vsnprintf
+# endif
+# ifdef VMS
+# define NO_vsnprintf
+# endif
+# ifdef __OS400__
+# define NO_vsnprintf
+# endif
+# ifdef __MVS__
+# define NO_vsnprintf
+# endif
+#endif
+
+#ifndef local
+# define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+/* gz* functions always use library allocation functions */
+#ifndef STDC
+ extern voidp malloc OF((uInt size));
+ extern void free OF((voidpf ptr));
+#endif
+
+/* get errno and strerror definition */
+#if defined UNDER_CE
+# include <windows.h>
+# define zstrerror() gz_strwinerror((DWORD)GetLastError())
+#else
+# ifdef STDC
+# include <errno.h>
+# define zstrerror() strerror(errno)
+# else
+# define zstrerror() "stdio error (consult errno)"
+# endif
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+ ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+#endif
+
+/* default memLevel */
+#if MAX_MEM_LEVEL >= 8
+# define DEF_MEM_LEVEL 8
+#else
+# define DEF_MEM_LEVEL MAX_MEM_LEVEL
+#endif
+
+/* default i/o buffer size -- double this for output when reading */
+#define GZBUFSIZE 8192
+
+/* gzip modes, also provide a little integrity check on the passed structure */
+#define GZ_NONE 0
+#define GZ_READ 7247
+#define GZ_WRITE 31153
+#define GZ_APPEND 1 /* mode set to GZ_WRITE after the file is opened */
+
+/* values for gz_state how */
+#define LOOK 0 /* look for a gzip header */
+#define COPY 1 /* copy input directly */
+#define GZIP 2 /* decompress a gzip stream */
+
+/* internal gzip file state data structure */
+typedef struct {
+ /* exposed contents for gzgetc() macro */
+ struct gzFile_s x; /* "x" for exposed */
+ /* x.have: number of bytes available at x.next */
+ /* x.next: next output data to deliver or write */
+ /* x.pos: current position in uncompressed data */
+ /* used for both reading and writing */
+ int mode; /* see gzip modes above */
+ int fd; /* file descriptor */
+ char *path; /* path or fd for error messages */
+ unsigned size; /* buffer size, zero if not allocated yet */
+ unsigned want; /* requested buffer size, default is GZBUFSIZE */
+ unsigned char *in; /* input buffer */
+ unsigned char *out; /* output buffer (double-sized when reading) */
+ int direct; /* 0 if processing gzip, 1 if transparent */
+ /* just for reading */
+ int how; /* 0: get header, 1: copy, 2: decompress */
+ z_off64_t start; /* where the gzip data started, for rewinding */
+ int eof; /* true if end of input file reached */
+ int past; /* true if read requested past end */
+ /* just for writing */
+ int level; /* compression level */
+ int strategy; /* compression strategy */
+ /* seek request */
+ z_off64_t skip; /* amount to skip (already rewound if backwards) */
+ int seek; /* true if seek request pending */
+ /* error information */
+ int err; /* error code */
+ char *msg; /* error message */
+ /* zlib inflate or deflate stream */
+ z_stream strm; /* stream structure in-place (not a pointer) */
+} gz_state;
+typedef gz_state FAR *gz_statep;
+
+/* shared functions */
+void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *));
+#if defined UNDER_CE
+char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error));
+#endif
+
+/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t
+ value -- needed when comparing unsigned to z_off64_t, which is signed
+ (possible z_off64_t types off_t, off64_t, and long are all signed) */
+#ifdef INT_MAX
+# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX)
+#else
+unsigned ZLIB_INTERNAL gz_intmax OF((void));
+# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax())
+#endif
diff --git a/src/cpp/core/zlib/gzlib.c b/src/cpp/core/zlib/gzlib.c
new file mode 100644
index 0000000..7aedab8
--- /dev/null
+++ b/src/cpp/core/zlib/gzlib.c
@@ -0,0 +1,564 @@
+/* gzlib.c -- zlib functions common to reading and writing gzip files
+ * Copyright (C) 2004, 2010, 2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+#if defined(_WIN32) && !defined(__BORLANDC__)
+# define LSEEK _lseeki64
+#else
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+# define LSEEK lseek64
+#else
+# define LSEEK lseek
+#endif
+#endif
+
+/* Local functions */
+local void gz_reset OF((gz_statep));
+local gzFile gz_open OF((const char *, int, const char *));
+
+#if defined UNDER_CE
+
+/* Map the Windows error number in ERROR to a locale-dependent error message
+ string and return a pointer to it. Typically, the values for ERROR come
+ from GetLastError.
+
+ The string pointed to shall not be modified by the application, but may be
+ overwritten by a subsequent call to gz_strwinerror
+
+ The gz_strwinerror function does not change the current setting of
+ GetLastError. */
+char ZLIB_INTERNAL *gz_strwinerror (error)
+ DWORD error;
+{
+ static char buf[1024];
+
+ wchar_t *msgbuf;
+ DWORD lasterr = GetLastError();
+ DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
+ | FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ NULL,
+ error,
+ 0, /* Default language */
+ (LPVOID)&msgbuf,
+ 0,
+ NULL);
+ if (chars != 0) {
+ /* If there is an \r\n appended, zap it. */
+ if (chars >= 2
+ && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
+ chars -= 2;
+ msgbuf[chars] = 0;
+ }
+
+ if (chars > sizeof (buf) - 1) {
+ chars = sizeof (buf) - 1;
+ msgbuf[chars] = 0;
+ }
+
+ wcstombs(buf, msgbuf, chars + 1);
+ LocalFree(msgbuf);
+ }
+ else {
+ sprintf(buf, "unknown win32 error (%ld)", error);
+ }
+
+ SetLastError(lasterr);
+ return buf;
+}
+
+#endif /* UNDER_CE */
+
+/* Reset gzip file state */
+local void gz_reset(state)
+ gz_statep state;
+{
+ state->x.have = 0; /* no output data available */
+ if (state->mode == GZ_READ) { /* for reading ... */
+ state->eof = 0; /* not at end of file */
+ state->past = 0; /* have not read past end yet */
+ state->how = LOOK; /* look for gzip header */
+ }
+ state->seek = 0; /* no seek request pending */
+ gz_error(state, Z_OK, NULL); /* clear error */
+ state->x.pos = 0; /* no uncompressed data yet */
+ state->strm.avail_in = 0; /* no input data yet */
+}
+
+/* Open a gzip file either by name or file descriptor. */
+local gzFile gz_open(path, fd, mode)
+ const char *path;
+ int fd;
+ const char *mode;
+{
+ gz_statep state;
+
+ /* check input */
+ if (path == NULL)
+ return NULL;
+
+ /* allocate gzFile structure to return */
+ state = malloc(sizeof(gz_state));
+ if (state == NULL)
+ return NULL;
+ state->size = 0; /* no buffers allocated yet */
+ state->want = GZBUFSIZE; /* requested buffer size */
+ state->msg = NULL; /* no error message yet */
+
+ /* interpret mode */
+ state->mode = GZ_NONE;
+ state->level = Z_DEFAULT_COMPRESSION;
+ state->strategy = Z_DEFAULT_STRATEGY;
+ state->direct = 0;
+ while (*mode) {
+ if (*mode >= '0' && *mode <= '9')
+ state->level = *mode - '0';
+ else
+ switch (*mode) {
+ case 'r':
+ state->mode = GZ_READ;
+ break;
+#ifndef NO_GZCOMPRESS
+ case 'w':
+ state->mode = GZ_WRITE;
+ break;
+ case 'a':
+ state->mode = GZ_APPEND;
+ break;
+#endif
+ case '+': /* can't read and write at the same time */
+ free(state);
+ return NULL;
+ case 'b': /* ignore -- will request binary anyway */
+ break;
+ case 'f':
+ state->strategy = Z_FILTERED;
+ break;
+ case 'h':
+ state->strategy = Z_HUFFMAN_ONLY;
+ break;
+ case 'R':
+ state->strategy = Z_RLE;
+ break;
+ case 'F':
+ state->strategy = Z_FIXED;
+ case 'T':
+ state->direct = 1;
+ default: /* could consider as an error, but just ignore */
+ ;
+ }
+ mode++;
+ }
+
+ /* must provide an "r", "w", or "a" */
+ if (state->mode == GZ_NONE) {
+ free(state);
+ return NULL;
+ }
+
+ /* can't force transparent read */
+ if (state->mode == GZ_READ) {
+ if (state->direct) {
+ free(state);
+ return NULL;
+ }
+ state->direct = 1; /* for empty file */
+ }
+
+ /* save the path name for error messages */
+ state->path = malloc(strlen(path) + 1);
+ if (state->path == NULL) {
+ free(state);
+ return NULL;
+ }
+ strcpy(state->path, path);
+
+ /* open the file with the appropriate mode (or just use fd) */
+ state->fd = fd != -1 ? fd :
+ open(path,
+#ifdef O_LARGEFILE
+ O_LARGEFILE |
+#endif
+#ifdef O_BINARY
+ O_BINARY |
+#endif
+ (state->mode == GZ_READ ?
+ O_RDONLY :
+ (O_WRONLY | O_CREAT | (
+ state->mode == GZ_WRITE ?
+ O_TRUNC :
+ O_APPEND))),
+ 0666);
+ if (state->fd == -1) {
+ free(state->path);
+ free(state);
+ return NULL;
+ }
+ if (state->mode == GZ_APPEND)
+ state->mode = GZ_WRITE; /* simplify later checks */
+
+ /* save the current position for rewinding (only if reading) */
+ if (state->mode == GZ_READ) {
+ state->start = LSEEK(state->fd, 0, SEEK_CUR);
+ if (state->start == -1) state->start = 0;
+ }
+
+ /* initialize stream */
+ gz_reset(state);
+
+ /* return stream */
+ return (gzFile)state;
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen(path, mode)
+ const char *path;
+ const char *mode;
+{
+ return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen64(path, mode)
+ const char *path;
+ const char *mode;
+{
+ return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzdopen(fd, mode)
+ int fd;
+ const char *mode;
+{
+ char *path; /* identifier for error messages */
+ gzFile gz;
+
+ if (fd == -1 || (path = malloc(7 + 3 * sizeof(int))) == NULL)
+ return NULL;
+ sprintf(path, "<fd:%d>", fd); /* for debugging */
+ gz = gz_open(path, fd, mode);
+ free(path);
+ return gz;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzbuffer(file, size)
+ gzFile file;
+ unsigned size;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* make sure we haven't already allocated memory */
+ if (state->size != 0)
+ return -1;
+
+ /* check and set requested size */
+ if (size < 2)
+ size = 2; /* need two bytes to check magic header */
+ state->want = size;
+ return 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzrewind(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* back up and start over */
+ if (LSEEK(state->fd, state->start, SEEK_SET) == -1)
+ return -1;
+ gz_reset(state);
+ return 0;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzseek64(file, offset, whence)
+ gzFile file;
+ z_off64_t offset;
+ int whence;
+{
+ unsigned n;
+ z_off64_t ret;
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* check that there's no error */
+ if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+ return -1;
+
+ /* can only seek from start or relative to current position */
+ if (whence != SEEK_SET && whence != SEEK_CUR)
+ return -1;
+
+ /* normalize offset to a SEEK_CUR specification */
+ if (whence == SEEK_SET)
+ offset -= state->x.pos;
+ else if (state->seek)
+ offset += state->skip;
+ state->seek = 0;
+
+ /* if within raw area while reading, just go there */
+ if (state->mode == GZ_READ && state->how == COPY &&
+ state->x.pos + offset >= 0) {
+ ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR);
+ if (ret == -1)
+ return -1;
+ state->x.have = 0;
+ state->eof = 0;
+ state->past = 0;
+ state->seek = 0;
+ gz_error(state, Z_OK, NULL);
+ state->strm.avail_in = 0;
+ state->x.pos += offset;
+ return state->x.pos;
+ }
+
+ /* calculate skip amount, rewinding if needed for back seek when reading */
+ if (offset < 0) {
+ if (state->mode != GZ_READ) /* writing -- can't go backwards */
+ return -1;
+ offset += state->x.pos;
+ if (offset < 0) /* before start of file! */
+ return -1;
+ if (gzrewind(file) == -1) /* rewind, then skip to offset */
+ return -1;
+ }
+
+ /* if reading, skip what's in output buffer (one less gzgetc() check) */
+ if (state->mode == GZ_READ) {
+ n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ?
+ (unsigned)offset : state->x.have;
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ offset -= n;
+ }
+
+ /* request skip (if not zero) */
+ if (offset) {
+ state->seek = 1;
+ state->skip = offset;
+ }
+ return state->x.pos + offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzseek(file, offset, whence)
+ gzFile file;
+ z_off_t offset;
+ int whence;
+{
+ z_off64_t ret;
+
+ ret = gzseek64(file, (z_off64_t)offset, whence);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gztell64(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* return position */
+ return state->x.pos + (state->seek ? state->skip : 0);
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gztell(file)
+ gzFile file;
+{
+ z_off64_t ret;
+
+ ret = gztell64(file);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzoffset64(file)
+ gzFile file;
+{
+ z_off64_t offset;
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return -1;
+
+ /* compute and return effective offset in file */
+ offset = LSEEK(state->fd, 0, SEEK_CUR);
+ if (offset == -1)
+ return -1;
+ if (state->mode == GZ_READ) /* reading */
+ offset -= state->strm.avail_in; /* don't count buffered input */
+ return offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzoffset(file)
+ gzFile file;
+{
+ z_off64_t ret;
+
+ ret = gzoffset64(file);
+ return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzeof(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return 0;
+
+ /* return end-of-file state */
+ return state->mode == GZ_READ ? state->past : 0;
+}
+
+/* -- see zlib.h -- */
+const char * ZEXPORT gzerror(file, errnum)
+ gzFile file;
+ int *errnum;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return NULL;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return NULL;
+
+ /* return error information */
+ if (errnum != NULL)
+ *errnum = state->err;
+ return state->msg == NULL ? "" : state->msg;
+}
+
+/* -- see zlib.h -- */
+void ZEXPORT gzclearerr(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure and check integrity */
+ if (file == NULL)
+ return;
+ state = (gz_statep)file;
+ if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+ return;
+
+ /* clear error and end-of-file */
+ if (state->mode == GZ_READ) {
+ state->eof = 0;
+ state->past = 0;
+ }
+ gz_error(state, Z_OK, NULL);
+}
+
+/* Create an error message in allocated memory and set state->err and
+ state->msg accordingly. Free any previous error message already there. Do
+ not try to free or allocate space if the error is Z_MEM_ERROR (out of
+ memory). Simply save the error message as a static string. If there is an
+ allocation failure constructing the error message, then convert the error to
+ out of memory. */
+void ZLIB_INTERNAL gz_error(state, err, msg)
+ gz_statep state;
+ int err;
+ const char *msg;
+{
+ /* free previously allocated message and clear */
+ if (state->msg != NULL) {
+ if (state->err != Z_MEM_ERROR)
+ free(state->msg);
+ state->msg = NULL;
+ }
+
+ /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
+ if (err != Z_OK && err != Z_BUF_ERROR)
+ state->x.have = 0;
+
+ /* set error code, and if no message, then done */
+ state->err = err;
+ if (msg == NULL)
+ return;
+
+ /* for an out of memory error, save as static string */
+ if (err == Z_MEM_ERROR) {
+ state->msg = (char *)msg;
+ return;
+ }
+
+ /* construct error message with path */
+ if ((state->msg = malloc(strlen(state->path) + strlen(msg) + 3)) == NULL) {
+ state->err = Z_MEM_ERROR;
+ state->msg = (char *)"out of memory";
+ return;
+ }
+ strcpy(state->msg, state->path);
+ strcat(state->msg, ": ");
+ strcat(state->msg, msg);
+ return;
+}
+
+#ifndef INT_MAX
+/* portably return maximum value for an int (when limits.h presumed not
+ available) -- we need to do this to cover cases where 2's complement not
+ used, since C standard permits 1's complement and sign-bit representations,
+ otherwise we could just use ((unsigned)-1) >> 1 */
+unsigned ZLIB_INTERNAL gz_intmax()
+{
+ unsigned p, q;
+
+ p = 1;
+ do {
+ q = p;
+ p <<= 1;
+ p++;
+ } while (p > q);
+ return q >> 1;
+}
+#endif
diff --git a/src/cpp/core/zlib/gzread.c b/src/cpp/core/zlib/gzread.c
new file mode 100644
index 0000000..46d40e0
--- /dev/null
+++ b/src/cpp/core/zlib/gzread.c
@@ -0,0 +1,584 @@
+/* gzread.c -- zlib functions for reading gzip files
+ * Copyright (C) 2004, 2005, 2010, 2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *));
+local int gz_avail OF((gz_statep));
+local int gz_look OF((gz_statep));
+local int gz_decomp OF((gz_statep));
+local int gz_fetch OF((gz_statep));
+local int gz_skip OF((gz_statep, z_off64_t));
+
+/* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from
+ state->fd, and update state->eof, state->err, and state->msg as appropriate.
+ This function needs to loop on read(), since read() is not guaranteed to
+ read the number of bytes requested, depending on the type of descriptor. */
+local int gz_load(state, buf, len, have)
+ gz_statep state;
+ unsigned char *buf;
+ unsigned len;
+ unsigned *have;
+{
+ int ret;
+
+ *have = 0;
+ do {
+ ret = read(state->fd, buf + *have, len - *have);
+ if (ret <= 0)
+ break;
+ *have += ret;
+ } while (*have < len);
+ if (ret < 0) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ if (ret == 0)
+ state->eof = 1;
+ return 0;
+}
+
+/* Load up input buffer and set eof flag if last data loaded -- return -1 on
+ error, 0 otherwise. Note that the eof flag is set when the end of the input
+ file is reached, even though there may be unused data in the buffer. Once
+ that data has been used, no more attempts will be made to read the file.
+ If strm->avail_in != 0, then the current data is moved to the beginning of
+ the input buffer, and then the remainder of the buffer is loaded with the
+ available data from the input file. */
+local int gz_avail(state)
+ gz_statep state;
+{
+ unsigned got;
+ z_streamp strm = &(state->strm);
+
+ if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+ return -1;
+ if (state->eof == 0) {
+ if (strm->avail_in)
+ memmove(state->in, strm->next_in, strm->avail_in);
+ if (gz_load(state, state->in + strm->avail_in,
+ state->size - strm->avail_in, &got) == -1)
+ return -1;
+ strm->avail_in += got;
+ strm->next_in = state->in;
+ }
+ return 0;
+}
+
+/* Look for gzip header, set up for inflate or copy. state->x.have must be 0.
+ If this is the first time in, allocate required memory. state->how will be
+ left unchanged if there is no more input data available, will be set to COPY
+ if there is no gzip header and direct copying will be performed, or it will
+ be set to GZIP for decompression. If direct copying, then leftover input
+ data from the input buffer will be copied to the output buffer. In that
+ case, all further file reads will be directly to either the output buffer or
+ a user buffer. If decompressing, the inflate state will be initialized.
+ gz_look() will return 0 on success or -1 on failure. */
+local int gz_look(state)
+ gz_statep state;
+{
+ z_streamp strm = &(state->strm);
+
+ /* allocate read buffers and inflate memory */
+ if (state->size == 0) {
+ /* allocate buffers */
+ state->in = malloc(state->want);
+ state->out = malloc(state->want << 1);
+ if (state->in == NULL || state->out == NULL) {
+ if (state->out != NULL)
+ free(state->out);
+ if (state->in != NULL)
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ state->size = state->want;
+
+ /* allocate inflate memory */
+ state->strm.zalloc = Z_NULL;
+ state->strm.zfree = Z_NULL;
+ state->strm.opaque = Z_NULL;
+ state->strm.avail_in = 0;
+ state->strm.next_in = Z_NULL;
+ if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */
+ free(state->out);
+ free(state->in);
+ state->size = 0;
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ }
+
+ /* get at least the magic bytes in the input buffer */
+ if (strm->avail_in < 2) {
+ if (gz_avail(state) == -1)
+ return -1;
+ if (strm->avail_in == 0)
+ return 0;
+ }
+
+ /* look for gzip magic bytes -- if there, do gzip decoding (note: there is
+ a logical dilemma here when considering the case of a partially written
+ gzip file, to wit, if a single 31 byte is written, then we cannot tell
+ whether this is a single-byte file, or just a partially written gzip
+ file -- for here we assume that if a gzip file is being written, then
+ the header will be written in a single operation, so that reading a
+ single byte is sufficient indication that it is not a gzip file) */
+ if (strm->avail_in > 1 &&
+ strm->next_in[0] == 31 && strm->next_in[1] == 139) {
+ inflateReset(strm);
+ state->how = GZIP;
+ state->direct = 0;
+ return 0;
+ }
+
+ /* no gzip header -- if we were decoding gzip before, then this is trailing
+ garbage. Ignore the trailing garbage and finish. */
+ if (state->direct == 0) {
+ strm->avail_in = 0;
+ state->eof = 1;
+ state->x.have = 0;
+ return 0;
+ }
+
+ /* doing raw i/o, copy any leftover input to output -- this assumes that
+ the output buffer is larger than the input buffer, which also assures
+ space for gzungetc() */
+ state->x.next = state->out;
+ if (strm->avail_in) {
+ memcpy(state->x.next, strm->next_in, strm->avail_in);
+ state->x.have = strm->avail_in;
+ strm->avail_in = 0;
+ }
+ state->how = COPY;
+ state->direct = 1;
+ return 0;
+}
+
+/* Decompress from input to the provided next_out and avail_out in the state.
+ On return, state->x.have and state->x.next point to the just decompressed
+ data. If the gzip stream completes, state->how is reset to LOOK to look for
+ the next gzip stream or raw data, once state->x.have is depleted. Returns 0
+ on success, -1 on failure. */
+local int gz_decomp(state)
+ gz_statep state;
+{
+ int ret = Z_OK;
+ unsigned had;
+ z_streamp strm = &(state->strm);
+
+ /* fill output buffer up to end of deflate stream */
+ had = strm->avail_out;
+ do {
+ /* get more input for inflate() */
+ if (strm->avail_in == 0 && gz_avail(state) == -1)
+ return -1;
+ if (strm->avail_in == 0) {
+ gz_error(state, Z_BUF_ERROR, "unexpected end of file");
+ break;
+ }
+
+ /* decompress and handle errors */
+ ret = inflate(strm, Z_NO_FLUSH);
+ if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
+ gz_error(state, Z_STREAM_ERROR,
+ "internal error: inflate stream corrupt");
+ return -1;
+ }
+ if (ret == Z_MEM_ERROR) {
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ if (ret == Z_DATA_ERROR) { /* deflate stream invalid */
+ gz_error(state, Z_DATA_ERROR,
+ strm->msg == NULL ? "compressed data error" : strm->msg);
+ return -1;
+ }
+ } while (strm->avail_out && ret != Z_STREAM_END);
+
+ /* update available output */
+ state->x.have = had - strm->avail_out;
+ state->x.next = strm->next_out - state->x.have;
+
+ /* if the gzip stream completed successfully, look for another */
+ if (ret == Z_STREAM_END)
+ state->how = LOOK;
+
+ /* good decompression */
+ return 0;
+}
+
+/* Fetch data and put it in the output buffer. Assumes state->x.have is 0.
+ Data is either copied from the input file or decompressed from the input
+ file depending on state->how. If state->how is LOOK, then a gzip header is
+ looked for to determine whether to copy or decompress. Returns -1 on error,
+ otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the
+ end of the input file has been reached and all data has been processed. */
+local int gz_fetch(state)
+ gz_statep state;
+{
+ z_streamp strm = &(state->strm);
+
+ do {
+ switch(state->how) {
+ case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */
+ if (gz_look(state) == -1)
+ return -1;
+ if (state->how == LOOK)
+ return 0;
+ break;
+ case COPY: /* -> COPY */
+ if (gz_load(state, state->out, state->size << 1, &(state->x.have))
+ == -1)
+ return -1;
+ state->x.next = state->out;
+ return 0;
+ case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */
+ strm->avail_out = state->size << 1;
+ strm->next_out = state->out;
+ if (gz_decomp(state) == -1)
+ return -1;
+ }
+ } while (state->x.have == 0 && (!state->eof || strm->avail_in));
+ return 0;
+}
+
+/* Skip len uncompressed bytes of output. Return -1 on error, 0 on success. */
+local int gz_skip(state, len)
+ gz_statep state;
+ z_off64_t len;
+{
+ unsigned n;
+
+ /* skip over len bytes or reach end-of-file, whichever comes first */
+ while (len)
+ /* skip over whatever is in output buffer */
+ if (state->x.have) {
+ n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ?
+ (unsigned)len : state->x.have;
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ len -= n;
+ }
+
+ /* output buffer empty -- return if we're at the end of the input */
+ else if (state->eof && state->strm.avail_in == 0)
+ break;
+
+ /* need more data to skip -- load up output buffer */
+ else {
+ /* get more output, looking for header if required */
+ if (gz_fetch(state) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzread(file, buf, len)
+ gzFile file;
+ voidp buf;
+ unsigned len;
+{
+ unsigned got, n;
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* since an int is returned, make sure len fits in one, otherwise return
+ with an error (this avoids the flaw in the interface) */
+ if ((int)len < 0) {
+ gz_error(state, Z_DATA_ERROR, "requested length does not fit in int");
+ return -1;
+ }
+
+ /* if len is zero, avoid unnecessary operations */
+ if (len == 0)
+ return 0;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* get len bytes to buf, or less than len if at the end */
+ got = 0;
+ do {
+ /* first just try copying data from the output buffer */
+ if (state->x.have) {
+ n = state->x.have > len ? len : state->x.have;
+ memcpy(buf, state->x.next, n);
+ state->x.next += n;
+ state->x.have -= n;
+ }
+
+ /* output buffer empty -- return if we're at the end of the input */
+ else if (state->eof && strm->avail_in == 0) {
+ state->past = 1; /* tried to read past end */
+ break;
+ }
+
+ /* need output data -- for small len or new stream load up our output
+ buffer */
+ else if (state->how == LOOK || len < (state->size << 1)) {
+ /* get more output, looking for header if required */
+ if (gz_fetch(state) == -1)
+ return -1;
+ continue; /* no progress yet -- go back to memcpy() above */
+ /* the copy above assures that we will leave with space in the
+ output buffer, allowing at least one gzungetc() to succeed */
+ }
+
+ /* large len -- read directly into user buffer */
+ else if (state->how == COPY) { /* read directly */
+ if (gz_load(state, buf, len, &n) == -1)
+ return -1;
+ }
+
+ /* large len -- decompress directly into user buffer */
+ else { /* state->how == GZIP */
+ strm->avail_out = len;
+ strm->next_out = buf;
+ if (gz_decomp(state) == -1)
+ return -1;
+ n = state->x.have;
+ state->x.have = 0;
+ }
+
+ /* update progress */
+ len -= n;
+ buf = (char *)buf + n;
+ got += n;
+ state->x.pos += n;
+ } while (len);
+
+ /* return number of bytes read into user buffer (will fit in int) */
+ return (int)got;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzgetc_(file)
+ gzFile file;
+{
+ int ret;
+ unsigned char buf[1];
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* try output buffer (no need to check for skip request) */
+ if (state->x.have) {
+ state->x.have--;
+ state->x.pos++;
+ return *(state->x.next)++;
+ }
+
+ /* nothing there -- try gzread() */
+ ret = gzread(file, buf, 1);
+ return ret < 1 ? -1 : buf[0];
+}
+
+#undef gzgetc
+int ZEXPORT gzgetc(file)
+gzFile file;
+{
+ return gzgetc_(file);
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzungetc(c, file)
+ int c;
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return -1;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* can't push EOF */
+ if (c < 0)
+ return -1;
+
+ /* if output buffer empty, put byte at end (allows more pushing) */
+ if (state->x.have == 0) {
+ state->x.have = 1;
+ state->x.next = state->out + (state->size << 1) - 1;
+ state->x.next[0] = c;
+ state->x.pos--;
+ state->past = 0;
+ return c;
+ }
+
+ /* if no room, give up (must have already done a gzungetc()) */
+ if (state->x.have == (state->size << 1)) {
+ gz_error(state, Z_DATA_ERROR, "out of room to push characters");
+ return -1;
+ }
+
+ /* slide output data if needed and insert byte before existing data */
+ if (state->x.next == state->out) {
+ unsigned char *src = state->out + state->x.have;
+ unsigned char *dest = state->out + (state->size << 1);
+ while (src > state->out)
+ *--dest = *--src;
+ state->x.next = dest;
+ }
+ state->x.have++;
+ state->x.next--;
+ state->x.next[0] = c;
+ state->x.pos--;
+ state->past = 0;
+ return c;
+}
+
+/* -- see zlib.h -- */
+char * ZEXPORT gzgets(file, buf, len)
+ gzFile file;
+ char *buf;
+ int len;
+{
+ unsigned left, n;
+ char *str;
+ unsigned char *eol;
+ gz_statep state;
+
+ /* check parameters and get internal structure */
+ if (file == NULL || buf == NULL || len < 1)
+ return NULL;
+ state = (gz_statep)file;
+
+ /* check that we're reading and that there's no (serious) error */
+ if (state->mode != GZ_READ ||
+ (state->err != Z_OK && state->err != Z_BUF_ERROR))
+ return NULL;
+
+ /* process a skip request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_skip(state, state->skip) == -1)
+ return NULL;
+ }
+
+ /* copy output bytes up to new line or len - 1, whichever comes first --
+ append a terminating zero to the string (we don't check for a zero in
+ the contents, let the user worry about that) */
+ str = buf;
+ left = (unsigned)len - 1;
+ if (left) do {
+ /* assure that something is in the output buffer */
+ if (state->x.have == 0 && gz_fetch(state) == -1)
+ return NULL; /* error */
+ if (state->x.have == 0) { /* end of file */
+ state->past = 1; /* read past end */
+ break; /* return what we have */
+ }
+
+ /* look for end-of-line in current output buffer */
+ n = state->x.have > left ? left : state->x.have;
+ eol = memchr(state->x.next, '\n', n);
+ if (eol != NULL)
+ n = (unsigned)(eol - state->x.next) + 1;
+
+ /* copy through end-of-line, or remainder if not found */
+ memcpy(buf, state->x.next, n);
+ state->x.have -= n;
+ state->x.next += n;
+ state->x.pos += n;
+ left -= n;
+ buf += n;
+ } while (left && eol == NULL);
+
+ /* return terminated string, or if nothing, end of file */
+ if (buf == str)
+ return NULL;
+ buf[0] = 0;
+ return str;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzdirect(file)
+ gzFile file;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+
+ /* if the state is not known, but we can find out, then do so (this is
+ mainly for right after a gzopen() or gzdopen()) */
+ if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0)
+ (void)gz_look(state);
+
+ /* return 1 if transparent, 0 if processing a gzip stream */
+ return state->direct;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_r(file)
+ gzFile file;
+{
+ int ret, err;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ /* check that we're reading */
+ if (state->mode != GZ_READ)
+ return Z_STREAM_ERROR;
+
+ /* free memory and close file */
+ if (state->size) {
+ inflateEnd(&(state->strm));
+ free(state->out);
+ free(state->in);
+ }
+ err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK;
+ gz_error(state, Z_OK, NULL);
+ free(state->path);
+ ret = close(state->fd);
+ free(state);
+ return ret ? Z_ERRNO : err;
+}
diff --git a/src/cpp/core/zlib/gzwrite.c b/src/cpp/core/zlib/gzwrite.c
new file mode 100644
index 0000000..caa35b6
--- /dev/null
+++ b/src/cpp/core/zlib/gzwrite.c
@@ -0,0 +1,593 @@
+/* gzwrite.c -- zlib functions for writing gzip files
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_init OF((gz_statep));
+local int gz_comp OF((gz_statep, int));
+local int gz_zero OF((gz_statep, z_off64_t));
+
+/* Initialize state for writing a gzip file. Mark initialization by setting
+ state->size to non-zero. Return -1 on failure or 0 on success. */
+local int gz_init(state)
+ gz_statep state;
+{
+ int ret;
+ z_streamp strm = &(state->strm);
+
+ /* allocate input buffer */
+ state->in = malloc(state->want);
+ if (state->in == NULL) {
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+
+ /* only need output buffer and deflate state if compressing */
+ if (!state->direct) {
+ /* allocate output buffer */
+ state->out = malloc(state->want);
+ if (state->out == NULL) {
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+
+ /* allocate deflate memory, set up for gzip compression */
+ strm->zalloc = Z_NULL;
+ strm->zfree = Z_NULL;
+ strm->opaque = Z_NULL;
+ ret = deflateInit2(strm, state->level, Z_DEFLATED,
+ MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy);
+ if (ret != Z_OK) {
+ free(state->out);
+ free(state->in);
+ gz_error(state, Z_MEM_ERROR, "out of memory");
+ return -1;
+ }
+ }
+
+ /* mark state as initialized */
+ state->size = state->want;
+
+ /* initialize write buffer if compressing */
+ if (!state->direct) {
+ strm->avail_out = state->size;
+ strm->next_out = state->out;
+ state->x.next = strm->next_out;
+ }
+ return 0;
+}
+
+/* Compress whatever is at avail_in and next_in and write to the output file.
+ Return -1 if there is an error writing to the output file, otherwise 0.
+ flush is assumed to be a valid deflate() flush value. If flush is Z_FINISH,
+ then the deflate() state is reset to start a new gzip stream. If gz->direct
+ is true, then simply write to the output file without compressing, and
+ ignore flush. */
+local int gz_comp(state, flush)
+ gz_statep state;
+ int flush;
+{
+ int ret, got;
+ unsigned have;
+ z_streamp strm = &(state->strm);
+
+ /* allocate memory if this is the first time through */
+ if (state->size == 0 && gz_init(state) == -1)
+ return -1;
+
+ /* write directly if requested */
+ if (state->direct) {
+ got = write(state->fd, strm->next_in, strm->avail_in);
+ if (got < 0 || (unsigned)got != strm->avail_in) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ strm->avail_in = 0;
+ return 0;
+ }
+
+ /* run deflate() on provided input until it produces no more output */
+ ret = Z_OK;
+ do {
+ /* write out current buffer contents if full, or if flushing, but if
+ doing Z_FINISH then don't write until we get to Z_STREAM_END */
+ if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
+ (flush != Z_FINISH || ret == Z_STREAM_END))) {
+ have = (unsigned)(strm->next_out - state->x.next);
+ if (have && ((got = write(state->fd, state->x.next, have)) < 0 ||
+ (unsigned)got != have)) {
+ gz_error(state, Z_ERRNO, zstrerror());
+ return -1;
+ }
+ if (strm->avail_out == 0) {
+ strm->avail_out = state->size;
+ strm->next_out = state->out;
+ }
+ state->x.next = strm->next_out;
+ }
+
+ /* compress */
+ have = strm->avail_out;
+ ret = deflate(strm, flush);
+ if (ret == Z_STREAM_ERROR) {
+ gz_error(state, Z_STREAM_ERROR,
+ "internal error: deflate stream corrupt");
+ return -1;
+ }
+ have -= strm->avail_out;
+ } while (have);
+
+ /* if that completed a deflate stream, allow another to start */
+ if (flush == Z_FINISH)
+ deflateReset(strm);
+
+ /* all done, no errors */
+ return 0;
+}
+
+/* Compress len zeros to output. Return -1 on error, 0 on success. */
+local int gz_zero(state, len)
+ gz_statep state;
+ z_off64_t len;
+{
+ int first;
+ unsigned n;
+ z_streamp strm = &(state->strm);
+
+ /* consume whatever's left in the input buffer */
+ if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return -1;
+
+ /* compress len zeros (len guaranteed > 0) */
+ first = 1;
+ while (len) {
+ n = GT_OFF(state->size) || (z_off64_t)state->size > len ?
+ (unsigned)len : state->size;
+ if (first) {
+ memset(state->in, 0, n);
+ first = 0;
+ }
+ strm->avail_in = n;
+ strm->next_in = state->in;
+ state->x.pos += n;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return -1;
+ len -= n;
+ }
+ return 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzwrite(file, buf, len)
+ gzFile file;
+ voidpc buf;
+ unsigned len;
+{
+ unsigned put = len;
+ unsigned n;
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return 0;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return 0;
+
+ /* since an int is returned, make sure len fits in one, otherwise return
+ with an error (this avoids the flaw in the interface) */
+ if ((int)len < 0) {
+ gz_error(state, Z_DATA_ERROR, "requested length does not fit in int");
+ return 0;
+ }
+
+ /* if len is zero, avoid unnecessary operations */
+ if (len == 0)
+ return 0;
+
+ /* allocate memory if this is the first time through */
+ if (state->size == 0 && gz_init(state) == -1)
+ return 0;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return 0;
+ }
+
+ /* for small len, copy to input buffer, otherwise compress directly */
+ if (len < state->size) {
+ /* copy to input buffer, compress when full */
+ do {
+ if (strm->avail_in == 0)
+ strm->next_in = state->in;
+ n = state->size - strm->avail_in;
+ if (n > len)
+ n = len;
+ memcpy(strm->next_in + strm->avail_in, buf, n);
+ strm->avail_in += n;
+ state->x.pos += n;
+ buf = (char *)buf + n;
+ len -= n;
+ if (len && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+ } while (len);
+ }
+ else {
+ /* consume whatever's left in the input buffer */
+ if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+
+ /* directly compress user buffer to file */
+ strm->avail_in = len;
+ strm->next_in = (voidp)buf;
+ state->x.pos += len;
+ if (gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+ }
+
+ /* input was all buffered or compressed (put will fit in int) */
+ return (int)put;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputc(file, c)
+ gzFile file;
+ int c;
+{
+ unsigned char buf[1];
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return -1;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* try writing to input buffer for speed (state->size == 0 if buffer not
+ initialized) */
+ if (strm->avail_in < state->size) {
+ if (strm->avail_in == 0)
+ strm->next_in = state->in;
+ strm->next_in[strm->avail_in++] = c;
+ state->x.pos++;
+ return c & 0xff;
+ }
+
+ /* no room in buffer or not initialized, use gz_write() */
+ buf[0] = c;
+ if (gzwrite(file, buf, 1) != 1)
+ return -1;
+ return c & 0xff;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputs(file, str)
+ gzFile file;
+ const char *str;
+{
+ int ret;
+ unsigned len;
+
+ /* write string */
+ len = (unsigned)strlen(str);
+ ret = gzwrite(file, str, len);
+ return ret == 0 && len != 0 ? -1 : ret;
+}
+
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#include <stdarg.h>
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzprintf (gzFile file, const char *format, ...)
+{
+ int size, len;
+ gz_statep state;
+ z_streamp strm;
+ va_list va;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return 0;
+
+ /* make sure we have some buffer space */
+ if (state->size == 0 && gz_init(state) == -1)
+ return 0;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return 0;
+ }
+
+ /* consume whatever's left in the input buffer */
+ if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+
+ /* do the printf() into the input buffer, put length in len */
+ size = (int)(state->size);
+ state->in[size - 1] = 0;
+ va_start(va, format);
+#ifdef NO_vsnprintf
+# ifdef HAS_vsprintf_void
+ (void)vsprintf(state->in, format, va);
+ va_end(va);
+ for (len = 0; len < size; len++)
+ if (state->in[len] == 0) break;
+# else
+ len = vsprintf(state->in, format, va);
+ va_end(va);
+# endif
+#else
+# ifdef HAS_vsnprintf_void
+ (void)vsnprintf(state->in, size, format, va);
+ va_end(va);
+ len = strlen(state->in);
+# else
+ len = vsnprintf((char *)(state->in), size, format, va);
+ va_end(va);
+# endif
+#endif
+
+ /* check that printf() results fit in buffer */
+ if (len <= 0 || len >= (int)size || state->in[size - 1] != 0)
+ return 0;
+
+ /* update buffer and position, defer compression until needed */
+ strm->avail_in = (unsigned)len;
+ strm->next_in = state->in;
+ state->x.pos += len;
+ return len;
+}
+
+#else /* !STDC && !Z_HAVE_STDARG_H */
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ gzFile file;
+ const char *format;
+ int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;
+{
+ int size, len;
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that can really pass pointer in ints */
+ if (sizeof(int) != sizeof(void *))
+ return 0;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return 0;
+
+ /* make sure we have some buffer space */
+ if (state->size == 0 && gz_init(state) == -1)
+ return 0;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return 0;
+ }
+
+ /* consume whatever's left in the input buffer */
+ if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+ return 0;
+
+ /* do the printf() into the input buffer, put length in len */
+ size = (int)(state->size);
+ state->in[size - 1] = 0;
+#ifdef NO_snprintf
+# ifdef HAS_sprintf_void
+ sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8,
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+ for (len = 0; len < size; len++)
+ if (state->in[len] == 0) break;
+# else
+ len = sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8,
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+# endif
+#else
+# ifdef HAS_snprintf_void
+ snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8,
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+ len = strlen(state->in);
+# else
+ len = snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8,
+ a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+# endif
+#endif
+
+ /* check that printf() results fit in buffer */
+ if (len <= 0 || len >= (int)size || state->in[size - 1] != 0)
+ return 0;
+
+ /* update buffer and position, defer compression until needed */
+ strm->avail_in = (unsigned)len;
+ strm->next_in = state->in;
+ state->x.pos += len;
+ return len;
+}
+
+#endif
+
+/* -- see zlib.h -- */
+int ZEXPORT gzflush(file, flush)
+ gzFile file;
+ int flush;
+{
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return -1;
+ state = (gz_statep)file;
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* check flush parameter */
+ if (flush < 0 || flush > Z_FINISH)
+ return Z_STREAM_ERROR;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* compress remaining data with requested flush */
+ gz_comp(state, flush);
+ return state->err;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzsetparams(file, level, strategy)
+ gzFile file;
+ int level;
+ int strategy;
+{
+ gz_statep state;
+ z_streamp strm;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+ strm = &(state->strm);
+
+ /* check that we're writing and that there's no error */
+ if (state->mode != GZ_WRITE || state->err != Z_OK)
+ return Z_STREAM_ERROR;
+
+ /* if no change is requested, then do nothing */
+ if (level == state->level && strategy == state->strategy)
+ return Z_OK;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ return -1;
+ }
+
+ /* change compression parameters for subsequent input */
+ if (state->size) {
+ /* flush previous input with previous parameters before changing */
+ if (strm->avail_in && gz_comp(state, Z_PARTIAL_FLUSH) == -1)
+ return state->err;
+ deflateParams(strm, level, strategy);
+ }
+ state->level = level;
+ state->strategy = strategy;
+ return Z_OK;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_w(file)
+ gzFile file;
+{
+ int ret = Z_OK;
+ gz_statep state;
+
+ /* get internal structure */
+ if (file == NULL)
+ return Z_STREAM_ERROR;
+ state = (gz_statep)file;
+
+ /* check that we're writing */
+ if (state->mode != GZ_WRITE)
+ return Z_STREAM_ERROR;
+
+ /* check for seek request */
+ if (state->seek) {
+ state->seek = 0;
+ if (gz_zero(state, state->skip) == -1)
+ ret = state->err;
+ }
+
+ /* flush, free memory, and close file */
+ if (gz_comp(state, Z_FINISH) == -1)
+ ret = state->err;
+ if (!state->direct) {
+ (void)deflateEnd(&(state->strm));
+ free(state->out);
+ }
+ free(state->in);
+ gz_error(state, Z_OK, NULL);
+ free(state->path);
+ if (close(state->fd) == -1)
+ ret = Z_ERRNO;
+ free(state);
+ return ret;
+}
+
+/* used by zlibVersion() to get the vsnprintf story from the horse's mouth */
+unsigned long ZEXPORT gzflags()
+{
+ unsigned long flags = 0;
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# ifdef NO_vsnprintf
+ flags += 1L << 25;
+# ifdef HAS_vsprintf_void
+ flags += 1L << 26;
+# endif
+# else
+# ifdef HAS_vsnprintf_void
+ flags += 1L << 26;
+# endif
+# endif
+#else
+ flags += 1L << 24;
+# ifdef NO_snprintf
+ flags += 1L << 25;
+# ifdef HAS_sprintf_void
+ flags += 1L << 26;
+# endif
+# else
+# ifdef HAS_snprintf_void
+ flags += 1L << 26;
+# endif
+# endif
+#endif
+ return flags;
+}
diff --git a/src/cpp/core/zlib/infback.c b/src/cpp/core/zlib/infback.c
new file mode 100644
index 0000000..981aff1
--- /dev/null
+++ b/src/cpp/core/zlib/infback.c
@@ -0,0 +1,640 @@
+/* infback.c -- inflate using a call-back interface
+ * Copyright (C) 1995-2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ This code is largely copied from inflate.c. Normally either infback.o or
+ inflate.o would be linked into an application--not both. The interface
+ with inffast.c is retained so that optimized assembler-coded versions of
+ inflate_fast() can be used with either inflate.c or infback.c.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+
+/*
+ strm provides memory allocation functions in zalloc and zfree, or
+ Z_NULL to use the library memory allocation functions.
+
+ windowBits is in the range 8..15, and window is a user-supplied
+ window and output buffer that is 2**windowBits bytes.
+ */
+int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size)
+z_streamp strm;
+int windowBits;
+unsigned char FAR *window;
+const char *version;
+int stream_size;
+{
+ struct inflate_state FAR *state;
+
+ if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+ stream_size != (int)(sizeof(z_stream)))
+ return Z_VERSION_ERROR;
+ if (strm == Z_NULL || window == Z_NULL ||
+ windowBits < 8 || windowBits > 15)
+ return Z_STREAM_ERROR;
+ strm->msg = Z_NULL; /* in case we return an error */
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+ state = (struct inflate_state FAR *)ZALLOC(strm, 1,
+ sizeof(struct inflate_state));
+ if (state == Z_NULL) return Z_MEM_ERROR;
+ Tracev((stderr, "inflate: allocated\n"));
+ strm->state = (struct internal_state FAR *)state;
+ state->dmax = 32768U;
+ state->wbits = windowBits;
+ state->wsize = 1U << windowBits;
+ state->window = window;
+ state->wnext = 0;
+ state->whave = 0;
+ return Z_OK;
+}
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+ static int virgin = 1;
+ static code *lenfix, *distfix;
+ static code fixed[544];
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ unsigned sym, bits;
+ static code *next;
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) state->lens[sym++] = 8;
+ while (sym < 256) state->lens[sym++] = 9;
+ while (sym < 280) state->lens[sym++] = 7;
+ while (sym < 288) state->lens[sym++] = 8;
+ next = fixed;
+ lenfix = next;
+ bits = 9;
+ inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) state->lens[sym++] = 5;
+ distfix = next;
+ bits = 5;
+ inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+ /* do this just once */
+ virgin = 0;
+ }
+#else /* !BUILDFIXED */
+# include "inffixed.h"
+#endif /* BUILDFIXED */
+ state->lencode = lenfix;
+ state->lenbits = 9;
+ state->distcode = distfix;
+ state->distbits = 5;
+}
+
+/* Macros for inflateBack(): */
+
+/* Load returned state from inflate_fast() */
+#define LOAD() \
+ do { \
+ put = strm->next_out; \
+ left = strm->avail_out; \
+ next = strm->next_in; \
+ have = strm->avail_in; \
+ hold = state->hold; \
+ bits = state->bits; \
+ } while (0)
+
+/* Set state from registers for inflate_fast() */
+#define RESTORE() \
+ do { \
+ strm->next_out = put; \
+ strm->avail_out = left; \
+ strm->next_in = next; \
+ strm->avail_in = have; \
+ state->hold = hold; \
+ state->bits = bits; \
+ } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+ do { \
+ hold = 0; \
+ bits = 0; \
+ } while (0)
+
+/* Assure that some input is available. If input is requested, but denied,
+ then return a Z_BUF_ERROR from inflateBack(). */
+#define PULL() \
+ do { \
+ if (have == 0) { \
+ have = in(in_desc, &next); \
+ if (have == 0) { \
+ next = Z_NULL; \
+ ret = Z_BUF_ERROR; \
+ goto inf_leave; \
+ } \
+ } \
+ } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflateBack()
+ with an error if there is no input available. */
+#define PULLBYTE() \
+ do { \
+ PULL(); \
+ have--; \
+ hold += (unsigned long)(*next++) << bits; \
+ bits += 8; \
+ } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator. If there is
+ not enough available input to do that, then return from inflateBack() with
+ an error. */
+#define NEEDBITS(n) \
+ do { \
+ while (bits < (unsigned)(n)) \
+ PULLBYTE(); \
+ } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+ ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+ do { \
+ hold >>= (n); \
+ bits -= (unsigned)(n); \
+ } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+ do { \
+ hold >>= bits & 7; \
+ bits -= bits & 7; \
+ } while (0)
+
+/* Assure that some output space is available, by writing out the window
+ if it's full. If the write fails, return from inflateBack() with a
+ Z_BUF_ERROR. */
+#define ROOM() \
+ do { \
+ if (left == 0) { \
+ put = state->window; \
+ left = state->wsize; \
+ state->whave = left; \
+ if (out(out_desc, put, left)) { \
+ ret = Z_BUF_ERROR; \
+ goto inf_leave; \
+ } \
+ } \
+ } while (0)
+
+/*
+ strm provides the memory allocation functions and window buffer on input,
+ and provides information on the unused input on return. For Z_DATA_ERROR
+ returns, strm will also provide an error message.
+
+ in() and out() are the call-back input and output functions. When
+ inflateBack() needs more input, it calls in(). When inflateBack() has
+ filled the window with output, or when it completes with data in the
+ window, it calls out() to write out the data. The application must not
+ change the provided input until in() is called again or inflateBack()
+ returns. The application must not change the window/output buffer until
+ inflateBack() returns.
+
+ in() and out() are called with a descriptor parameter provided in the
+ inflateBack() call. This parameter can be a structure that provides the
+ information required to do the read or write, as well as accumulated
+ information on the input and output such as totals and check values.
+
+ in() should return zero on failure. out() should return non-zero on
+ failure. If either in() or out() fails, than inflateBack() returns a
+ Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it
+ was in() or out() that caused in the error. Otherwise, inflateBack()
+ returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format
+ error, or Z_MEM_ERROR if it could not allocate memory for the state.
+ inflateBack() can also return Z_STREAM_ERROR if the input parameters
+ are not correct, i.e. strm is Z_NULL or the state was not initialized.
+ */
+int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc)
+z_streamp strm;
+in_func in;
+void FAR *in_desc;
+out_func out;
+void FAR *out_desc;
+{
+ struct inflate_state FAR *state;
+ unsigned char FAR *next; /* next input */
+ unsigned char FAR *put; /* next output */
+ unsigned have, left; /* available input and output */
+ unsigned long hold; /* bit buffer */
+ unsigned bits; /* bits in bit buffer */
+ unsigned copy; /* number of stored or match bytes to copy */
+ unsigned char FAR *from; /* where to copy match bytes from */
+ code here; /* current decoding table entry */
+ code last; /* parent table entry */
+ unsigned len; /* length to copy for repeats, bits to drop */
+ int ret; /* return code */
+ static const unsigned short order[19] = /* permutation of code lengths */
+ {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+ /* Check that the strm exists and that the state was initialized */
+ if (strm == Z_NULL || strm->state == Z_NULL)
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* Reset the state */
+ strm->msg = Z_NULL;
+ state->mode = TYPE;
+ state->last = 0;
+ state->whave = 0;
+ next = strm->next_in;
+ have = next != Z_NULL ? strm->avail_in : 0;
+ hold = 0;
+ bits = 0;
+ put = state->window;
+ left = state->wsize;
+
+ /* Inflate until end of block marked as last */
+ for (;;)
+ switch (state->mode) {
+ case TYPE:
+ /* determine and dispatch block type */
+ if (state->last) {
+ BYTEBITS();
+ state->mode = DONE;
+ break;
+ }
+ NEEDBITS(3);
+ state->last = BITS(1);
+ DROPBITS(1);
+ switch (BITS(2)) {
+ case 0: /* stored block */
+ Tracev((stderr, "inflate: stored block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ Tracev((stderr, "inflate: fixed codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = LEN; /* decode codes */
+ break;
+ case 2: /* dynamic block */
+ Tracev((stderr, "inflate: dynamic codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = TABLE;
+ break;
+ case 3:
+ strm->msg = (char *)"invalid block type";
+ state->mode = BAD;
+ }
+ DROPBITS(2);
+ break;
+
+ case STORED:
+ /* get and verify stored block length */
+ BYTEBITS(); /* go to byte boundary */
+ NEEDBITS(32);
+ if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+ strm->msg = (char *)"invalid stored block lengths";
+ state->mode = BAD;
+ break;
+ }
+ state->length = (unsigned)hold & 0xffff;
+ Tracev((stderr, "inflate: stored length %u\n",
+ state->length));
+ INITBITS();
+
+ /* copy stored block from input to output */
+ while (state->length != 0) {
+ copy = state->length;
+ PULL();
+ ROOM();
+ if (copy > have) copy = have;
+ if (copy > left) copy = left;
+ zmemcpy(put, next, copy);
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state->length -= copy;
+ }
+ Tracev((stderr, "inflate: stored end\n"));
+ state->mode = TYPE;
+ break;
+
+ case TABLE:
+ /* get dynamic table entries descriptor */
+ NEEDBITS(14);
+ state->nlen = BITS(5) + 257;
+ DROPBITS(5);
+ state->ndist = BITS(5) + 1;
+ DROPBITS(5);
+ state->ncode = BITS(4) + 4;
+ DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+ if (state->nlen > 286 || state->ndist > 30) {
+ strm->msg = (char *)"too many length or distance symbols";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracev((stderr, "inflate: table sizes ok\n"));
+
+ /* get code length code lengths (not a typo) */
+ state->have = 0;
+ while (state->have < state->ncode) {
+ NEEDBITS(3);
+ state->lens[order[state->have++]] = (unsigned short)BITS(3);
+ DROPBITS(3);
+ }
+ while (state->have < 19)
+ state->lens[order[state->have++]] = 0;
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 7;
+ ret = inflate_table(CODES, state->lens, 19, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid code lengths set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: code lengths ok\n"));
+
+ /* get length and distance code code lengths */
+ state->have = 0;
+ while (state->have < state->nlen + state->ndist) {
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.val < 16) {
+ DROPBITS(here.bits);
+ state->lens[state->have++] = here.val;
+ }
+ else {
+ if (here.val == 16) {
+ NEEDBITS(here.bits + 2);
+ DROPBITS(here.bits);
+ if (state->have == 0) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ len = (unsigned)(state->lens[state->have - 1]);
+ copy = 3 + BITS(2);
+ DROPBITS(2);
+ }
+ else if (here.val == 17) {
+ NEEDBITS(here.bits + 3);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 3 + BITS(3);
+ DROPBITS(3);
+ }
+ else {
+ NEEDBITS(here.bits + 7);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 11 + BITS(7);
+ DROPBITS(7);
+ }
+ if (state->have + copy > state->nlen + state->ndist) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ while (copy--)
+ state->lens[state->have++] = (unsigned short)len;
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state->mode == BAD) break;
+
+ /* check for end-of-block code (better have one) */
+ if (state->lens[256] == 0) {
+ strm->msg = (char *)"invalid code -- missing end-of-block";
+ state->mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 9;
+ ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid literal/lengths set";
+ state->mode = BAD;
+ break;
+ }
+ state->distcode = (code const FAR *)(state->next);
+ state->distbits = 6;
+ ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+ &(state->next), &(state->distbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid distances set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: codes ok\n"));
+ state->mode = LEN;
+
+ case LEN:
+ /* use inflate_fast() if we have enough input and output */
+ if (have >= 6 && left >= 258) {
+ RESTORE();
+ if (state->whave < state->wsize)
+ state->whave = state->wsize - left;
+ inflate_fast(strm, state->wsize);
+ LOAD();
+ break;
+ }
+
+ /* get a literal, length, or end-of-block code */
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.op && (here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->lencode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ }
+ DROPBITS(here.bits);
+ state->length = (unsigned)here.val;
+
+ /* process literal */
+ if (here.op == 0) {
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ ROOM();
+ *put++ = (unsigned char)(state->length);
+ left--;
+ state->mode = LEN;
+ break;
+ }
+
+ /* process end of block */
+ if (here.op & 32) {
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->mode = TYPE;
+ break;
+ }
+
+ /* invalid code */
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+
+ /* length code -- get extra bits, if any */
+ state->extra = (unsigned)(here.op) & 15;
+ if (state->extra != 0) {
+ NEEDBITS(state->extra);
+ state->length += BITS(state->extra);
+ DROPBITS(state->extra);
+ }
+ Tracevv((stderr, "inflate: length %u\n", state->length));
+
+ /* get distance code */
+ for (;;) {
+ here = state->distcode[BITS(state->distbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if ((here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->distcode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ }
+ DROPBITS(here.bits);
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ state->offset = (unsigned)here.val;
+
+ /* get distance extra bits, if any */
+ state->extra = (unsigned)(here.op) & 15;
+ if (state->extra != 0) {
+ NEEDBITS(state->extra);
+ state->offset += BITS(state->extra);
+ DROPBITS(state->extra);
+ }
+ if (state->offset > state->wsize - (state->whave < state->wsize ?
+ left : 0)) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+ Tracevv((stderr, "inflate: distance %u\n", state->offset));
+
+ /* copy match from window to output */
+ do {
+ ROOM();
+ copy = state->wsize - state->offset;
+ if (copy < left) {
+ from = put + copy;
+ copy = left - copy;
+ }
+ else {
+ from = put - state->offset;
+ copy = left;
+ }
+ if (copy > state->length) copy = state->length;
+ state->length -= copy;
+ left -= copy;
+ do {
+ *put++ = *from++;
+ } while (--copy);
+ } while (state->length != 0);
+ break;
+
+ case DONE:
+ /* inflate stream terminated properly -- write leftover output */
+ ret = Z_STREAM_END;
+ if (left < state->wsize) {
+ if (out(out_desc, state->window, state->wsize - left))
+ ret = Z_BUF_ERROR;
+ }
+ goto inf_leave;
+
+ case BAD:
+ ret = Z_DATA_ERROR;
+ goto inf_leave;
+
+ default: /* can't happen, but makes compilers happy */
+ ret = Z_STREAM_ERROR;
+ goto inf_leave;
+ }
+
+ /* Return unused input */
+ inf_leave:
+ strm->next_in = next;
+ strm->avail_in = have;
+ return ret;
+}
+
+int ZEXPORT inflateBackEnd(strm)
+z_streamp strm;
+{
+ if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+ return Z_STREAM_ERROR;
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+ Tracev((stderr, "inflate: end\n"));
+ return Z_OK;
+}
diff --git a/src/cpp/core/zlib/inffast.c b/src/cpp/core/zlib/inffast.c
new file mode 100644
index 0000000..2f1d60b
--- /dev/null
+++ b/src/cpp/core/zlib/inffast.c
@@ -0,0 +1,340 @@
+/* inffast.c -- fast decoding
+ * Copyright (C) 1995-2008, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifndef ASMINF
+
+/* Allow machine dependent optimization for post-increment or pre-increment.
+ Based on testing to date,
+ Pre-increment preferred for:
+ - PowerPC G3 (Adler)
+ - MIPS R5000 (Randers-Pehrson)
+ Post-increment preferred for:
+ - none
+ No measurable difference:
+ - Pentium III (Anderson)
+ - M68060 (Nikl)
+ */
+#ifdef POSTINC
+# define OFF 0
+# define PUP(a) *(a)++
+#else
+# define OFF 1
+# define PUP(a) *++(a)
+#endif
+
+/*
+ Decode literal, length, and distance codes and write out the resulting
+ literal and match bytes until either not enough input or output is
+ available, an end-of-block is encountered, or a data error is encountered.
+ When large enough input and output buffers are supplied to inflate(), for
+ example, a 16K input buffer and a 64K output buffer, more than 95% of the
+ inflate execution time is spent in this routine.
+
+ Entry assumptions:
+
+ state->mode == LEN
+ strm->avail_in >= 6
+ strm->avail_out >= 258
+ start >= strm->avail_out
+ state->bits < 8
+
+ On return, state->mode is one of:
+
+ LEN -- ran out of enough output space or enough available input
+ TYPE -- reached end of block code, inflate() to interpret next block
+ BAD -- error in block data
+
+ Notes:
+
+ - The maximum input bits used by a length/distance pair is 15 bits for the
+ length code, 5 bits for the length extra, 15 bits for the distance code,
+ and 13 bits for the distance extra. This totals 48 bits, or six bytes.
+ Therefore if strm->avail_in >= 6, then there is enough input to avoid
+ checking for available input while decoding.
+
+ - The maximum bytes that a single length/distance pair can output is 258
+ bytes, which is the maximum length that can be coded. inflate_fast()
+ requires strm->avail_out >= 258 for each loop to avoid checking for
+ output space.
+ */
+void ZLIB_INTERNAL inflate_fast(strm, start)
+z_streamp strm;
+unsigned start; /* inflate()'s starting value for strm->avail_out */
+{
+ struct inflate_state FAR *state;
+ unsigned char FAR *in; /* local strm->next_in */
+ unsigned char FAR *last; /* while in < last, enough input available */
+ unsigned char FAR *out; /* local strm->next_out */
+ unsigned char FAR *beg; /* inflate()'s initial strm->next_out */
+ unsigned char FAR *end; /* while out < end, enough space available */
+#ifdef INFLATE_STRICT
+ unsigned dmax; /* maximum distance from zlib header */
+#endif
+ unsigned wsize; /* window size or zero if not using window */
+ unsigned whave; /* valid bytes in the window */
+ unsigned wnext; /* window write index */
+ unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */
+ unsigned long hold; /* local strm->hold */
+ unsigned bits; /* local strm->bits */
+ code const FAR *lcode; /* local strm->lencode */
+ code const FAR *dcode; /* local strm->distcode */
+ unsigned lmask; /* mask for first level of length codes */
+ unsigned dmask; /* mask for first level of distance codes */
+ code here; /* retrieved table entry */
+ unsigned op; /* code bits, operation, extra bits, or */
+ /* window position, window bytes to copy */
+ unsigned len; /* match length, unused bytes */
+ unsigned dist; /* match distance */
+ unsigned char FAR *from; /* where to copy match from */
+
+ /* copy state to local variables */
+ state = (struct inflate_state FAR *)strm->state;
+ in = strm->next_in - OFF;
+ last = in + (strm->avail_in - 5);
+ out = strm->next_out - OFF;
+ beg = out - (start - strm->avail_out);
+ end = out + (strm->avail_out - 257);
+#ifdef INFLATE_STRICT
+ dmax = state->dmax;
+#endif
+ wsize = state->wsize;
+ whave = state->whave;
+ wnext = state->wnext;
+ window = state->window;
+ hold = state->hold;
+ bits = state->bits;
+ lcode = state->lencode;
+ dcode = state->distcode;
+ lmask = (1U << state->lenbits) - 1;
+ dmask = (1U << state->distbits) - 1;
+
+ /* decode literals and length/distances until end-of-block or not enough
+ input data or output space */
+ do {
+ if (bits < 15) {
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ }
+ here = lcode[hold & lmask];
+ dolen:
+ op = (unsigned)(here.bits);
+ hold >>= op;
+ bits -= op;
+ op = (unsigned)(here.op);
+ if (op == 0) { /* literal */
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ PUP(out) = (unsigned char)(here.val);
+ }
+ else if (op & 16) { /* length base */
+ len = (unsigned)(here.val);
+ op &= 15; /* number of extra bits */
+ if (op) {
+ if (bits < op) {
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ }
+ len += (unsigned)hold & ((1U << op) - 1);
+ hold >>= op;
+ bits -= op;
+ }
+ Tracevv((stderr, "inflate: length %u\n", len));
+ if (bits < 15) {
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ }
+ here = dcode[hold & dmask];
+ dodist:
+ op = (unsigned)(here.bits);
+ hold >>= op;
+ bits -= op;
+ op = (unsigned)(here.op);
+ if (op & 16) { /* distance base */
+ dist = (unsigned)(here.val);
+ op &= 15; /* number of extra bits */
+ if (bits < op) {
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ if (bits < op) {
+ hold += (unsigned long)(PUP(in)) << bits;
+ bits += 8;
+ }
+ }
+ dist += (unsigned)hold & ((1U << op) - 1);
+#ifdef INFLATE_STRICT
+ if (dist > dmax) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ hold >>= op;
+ bits -= op;
+ Tracevv((stderr, "inflate: distance %u\n", dist));
+ op = (unsigned)(out - beg); /* max distance in output */
+ if (dist > op) { /* see if copy from window */
+ op = dist - op; /* distance back in window */
+ if (op > whave) {
+ if (state->sane) {
+ strm->msg =
+ (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ if (len <= op - whave) {
+ do {
+ PUP(out) = 0;
+ } while (--len);
+ continue;
+ }
+ len -= op - whave;
+ do {
+ PUP(out) = 0;
+ } while (--op > whave);
+ if (op == 0) {
+ from = out - dist;
+ do {
+ PUP(out) = PUP(from);
+ } while (--len);
+ continue;
+ }
+#endif
+ }
+ from = window - OFF;
+ if (wnext == 0) { /* very common case */
+ from += wsize - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ PUP(out) = PUP(from);
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ else if (wnext < op) { /* wrap around window */
+ from += wsize + wnext - op;
+ op -= wnext;
+ if (op < len) { /* some from end of window */
+ len -= op;
+ do {
+ PUP(out) = PUP(from);
+ } while (--op);
+ from = window - OFF;
+ if (wnext < len) { /* some from start of window */
+ op = wnext;
+ len -= op;
+ do {
+ PUP(out) = PUP(from);
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ }
+ else { /* contiguous in window */
+ from += wnext - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ PUP(out) = PUP(from);
+ } while (--op);
+ from = out - dist; /* rest from output */
+ }
+ }
+ while (len > 2) {
+ PUP(out) = PUP(from);
+ PUP(out) = PUP(from);
+ PUP(out) = PUP(from);
+ len -= 3;
+ }
+ if (len) {
+ PUP(out) = PUP(from);
+ if (len > 1)
+ PUP(out) = PUP(from);
+ }
+ }
+ else {
+ from = out - dist; /* copy direct from output */
+ do { /* minimum length is three */
+ PUP(out) = PUP(from);
+ PUP(out) = PUP(from);
+ PUP(out) = PUP(from);
+ len -= 3;
+ } while (len > 2);
+ if (len) {
+ PUP(out) = PUP(from);
+ if (len > 1)
+ PUP(out) = PUP(from);
+ }
+ }
+ }
+ else if ((op & 64) == 0) { /* 2nd level distance code */
+ here = dcode[here.val + (hold & ((1U << op) - 1))];
+ goto dodist;
+ }
+ else {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ }
+ else if ((op & 64) == 0) { /* 2nd level length code */
+ here = lcode[here.val + (hold & ((1U << op) - 1))];
+ goto dolen;
+ }
+ else if (op & 32) { /* end-of-block */
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->mode = TYPE;
+ break;
+ }
+ else {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+ } while (in < last && out < end);
+
+ /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+ len = bits >> 3;
+ in -= len;
+ bits -= len << 3;
+ hold &= (1U << bits) - 1;
+
+ /* update state and return */
+ strm->next_in = in + OFF;
+ strm->next_out = out + OFF;
+ strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
+ strm->avail_out = (unsigned)(out < end ?
+ 257 + (end - out) : 257 - (out - end));
+ state->hold = hold;
+ state->bits = bits;
+ return;
+}
+
+/*
+ inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe):
+ - Using bit fields for code structure
+ - Different op definition to avoid & for extra bits (do & for table bits)
+ - Three separate decoding do-loops for direct, window, and wnext == 0
+ - Special case for distance > 1 copies to do overlapped load and store copy
+ - Explicit branch predictions (based on measured branch probabilities)
+ - Deferring match copy and interspersed it with decoding subsequent codes
+ - Swapping literal/length else
+ - Swapping window/direct else
+ - Larger unrolled copy loops (three is about right)
+ - Moving len -= 3 statement into middle of loop
+ */
+
+#endif /* !ASMINF */
diff --git a/src/cpp/core/zlib/inffast.h b/src/cpp/core/zlib/inffast.h
new file mode 100644
index 0000000..e5c1aa4
--- /dev/null
+++ b/src/cpp/core/zlib/inffast.h
@@ -0,0 +1,11 @@
+/* inffast.h -- header to use inffast.c
+ * Copyright (C) 1995-2003, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start));
diff --git a/src/cpp/core/zlib/inffixed.h b/src/cpp/core/zlib/inffixed.h
new file mode 100644
index 0000000..d628327
--- /dev/null
+++ b/src/cpp/core/zlib/inffixed.h
@@ -0,0 +1,94 @@
+ /* inffixed.h -- table for decoding fixed codes
+ * Generated automatically by makefixed().
+ */
+
+ /* WARNING: this file should *not* be used by applications.
+ It is part of the implementation of this library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+ static const code lenfix[512] = {
+ {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48},
+ {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128},
+ {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59},
+ {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176},
+ {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20},
+ {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100},
+ {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8},
+ {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216},
+ {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76},
+ {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114},
+ {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},
+ {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148},
+ {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42},
+ {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86},
+ {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15},
+ {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236},
+ {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62},
+ {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},
+ {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31},
+ {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162},
+ {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25},
+ {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105},
+ {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4},
+ {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202},
+ {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69},
+ {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125},
+ {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13},
+ {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195},
+ {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35},
+ {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91},
+ {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19},
+ {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246},
+ {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55},
+ {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135},
+ {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99},
+ {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190},
+ {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16},
+ {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96},
+ {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6},
+ {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209},
+ {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},
+ {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116},
+ {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4},
+ {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153},
+ {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44},
+ {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82},
+ {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11},
+ {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},
+ {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58},
+ {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138},
+ {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51},
+ {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173},
+ {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30},
+ {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110},
+ {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0},
+ {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195},
+ {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65},
+ {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121},
+ {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},
+ {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258},
+ {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37},
+ {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93},
+ {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23},
+ {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251},
+ {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51},
+ {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},
+ {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67},
+ {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183},
+ {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23},
+ {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103},
+ {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9},
+ {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223},
+ {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79},
+ {0,9,255}
+ };
+
+ static const code distfix[32] = {
+ {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025},
+ {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193},
+ {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385},
+ {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577},
+ {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073},
+ {22,5,193},{64,5,0}
+ };
diff --git a/src/cpp/core/zlib/inflate.c b/src/cpp/core/zlib/inflate.c
new file mode 100644
index 0000000..cc89517
--- /dev/null
+++ b/src/cpp/core/zlib/inflate.c
@@ -0,0 +1,1501 @@
+/* inflate.c -- zlib decompression
+ * Copyright (C) 1995-2011 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * Change history:
+ *
+ * 1.2.beta0 24 Nov 2002
+ * - First version -- complete rewrite of inflate to simplify code, avoid
+ * creation of window when not needed, minimize use of window when it is
+ * needed, make inffast.c even faster, implement gzip decoding, and to
+ * improve code readability and style over the previous zlib inflate code
+ *
+ * 1.2.beta1 25 Nov 2002
+ * - Use pointers for available input and output checking in inffast.c
+ * - Remove input and output counters in inffast.c
+ * - Change inffast.c entry and loop from avail_in >= 7 to >= 6
+ * - Remove unnecessary second byte pull from length extra in inffast.c
+ * - Unroll direct copy to three copies per loop in inffast.c
+ *
+ * 1.2.beta2 4 Dec 2002
+ * - Change external routine names to reduce potential conflicts
+ * - Correct filename to inffixed.h for fixed tables in inflate.c
+ * - Make hbuf[] unsigned char to match parameter type in inflate.c
+ * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset)
+ * to avoid negation problem on Alphas (64 bit) in inflate.c
+ *
+ * 1.2.beta3 22 Dec 2002
+ * - Add comments on state->bits assertion in inffast.c
+ * - Add comments on op field in inftrees.h
+ * - Fix bug in reuse of allocated window after inflateReset()
+ * - Remove bit fields--back to byte structure for speed
+ * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths
+ * - Change post-increments to pre-increments in inflate_fast(), PPC biased?
+ * - Add compile time option, POSTINC, to use post-increments instead (Intel?)
+ * - Make MATCH copy in inflate() much faster for when inflate_fast() not used
+ * - Use local copies of stream next and avail values, as well as local bit
+ * buffer and bit count in inflate()--for speed when inflate_fast() not used
+ *
+ * 1.2.beta4 1 Jan 2003
+ * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings
+ * - Move a comment on output buffer sizes from inffast.c to inflate.c
+ * - Add comments in inffast.c to introduce the inflate_fast() routine
+ * - Rearrange window copies in inflate_fast() for speed and simplification
+ * - Unroll last copy for window match in inflate_fast()
+ * - Use local copies of window variables in inflate_fast() for speed
+ * - Pull out common wnext == 0 case for speed in inflate_fast()
+ * - Make op and len in inflate_fast() unsigned for consistency
+ * - Add FAR to lcode and dcode declarations in inflate_fast()
+ * - Simplified bad distance check in inflate_fast()
+ * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new
+ * source file infback.c to provide a call-back interface to inflate for
+ * programs like gzip and unzip -- uses window as output buffer to avoid
+ * window copying
+ *
+ * 1.2.beta5 1 Jan 2003
+ * - Improved inflateBack() interface to allow the caller to provide initial
+ * input in strm.
+ * - Fixed stored blocks bug in inflateBack()
+ *
+ * 1.2.beta6 4 Jan 2003
+ * - Added comments in inffast.c on effectiveness of POSTINC
+ * - Typecasting all around to reduce compiler warnings
+ * - Changed loops from while (1) or do {} while (1) to for (;;), again to
+ * make compilers happy
+ * - Changed type of window in inflateBackInit() to unsigned char *
+ *
+ * 1.2.beta7 27 Jan 2003
+ * - Changed many types to unsigned or unsigned short to avoid warnings
+ * - Added inflateCopy() function
+ *
+ * 1.2.0 9 Mar 2003
+ * - Changed inflateBack() interface to provide separate opaque descriptors
+ * for the in() and out() functions
+ * - Changed inflateBack() argument and in_func typedef to swap the length
+ * and buffer address return values for the input function
+ * - Check next_in and next_out for Z_NULL on entry to inflate()
+ *
+ * The history for versions after 1.2.0 are in ChangeLog in zlib distribution.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef MAKEFIXED
+# ifndef BUILDFIXED
+# define BUILDFIXED
+# endif
+#endif
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+local int updatewindow OF((z_streamp strm, unsigned out));
+#ifdef BUILDFIXED
+ void makefixed OF((void));
+#endif
+local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf,
+ unsigned len));
+
+int ZEXPORT inflateResetKeep(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ strm->total_in = strm->total_out = state->total = 0;
+ strm->msg = Z_NULL;
+ if (state->wrap) /* to support ill-conceived Java test suite */
+ strm->adler = state->wrap & 1;
+ state->mode = HEAD;
+ state->last = 0;
+ state->havedict = 0;
+ state->dmax = 32768U;
+ state->head = Z_NULL;
+ state->hold = 0;
+ state->bits = 0;
+ state->lencode = state->distcode = state->next = state->codes;
+ state->sane = 1;
+ state->back = -1;
+ Tracev((stderr, "inflate: reset\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateReset(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ state->wsize = 0;
+ state->whave = 0;
+ state->wnext = 0;
+ return inflateResetKeep(strm);
+}
+
+int ZEXPORT inflateReset2(strm, windowBits)
+z_streamp strm;
+int windowBits;
+{
+ int wrap;
+ struct inflate_state FAR *state;
+
+ /* get the state */
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* extract wrap request from windowBits parameter */
+ if (windowBits < 0) {
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+ else {
+ wrap = (windowBits >> 4) + 1;
+#ifdef GUNZIP
+ if (windowBits < 48)
+ windowBits &= 15;
+#endif
+ }
+
+ /* set number of window bits, free window if different */
+ if (windowBits && (windowBits < 8 || windowBits > 15))
+ return Z_STREAM_ERROR;
+ if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) {
+ ZFREE(strm, state->window);
+ state->window = Z_NULL;
+ }
+
+ /* update state and reset the rest of it */
+ state->wrap = wrap;
+ state->wbits = (unsigned)windowBits;
+ return inflateReset(strm);
+}
+
+int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
+z_streamp strm;
+int windowBits;
+const char *version;
+int stream_size;
+{
+ int ret;
+ struct inflate_state FAR *state;
+
+ if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+ stream_size != (int)(sizeof(z_stream)))
+ return Z_VERSION_ERROR;
+ if (strm == Z_NULL) return Z_STREAM_ERROR;
+ strm->msg = Z_NULL; /* in case we return an error */
+ if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+#endif
+ }
+ if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+ return Z_STREAM_ERROR;
+#else
+ strm->zfree = zcfree;
+#endif
+ state = (struct inflate_state FAR *)
+ ZALLOC(strm, 1, sizeof(struct inflate_state));
+ if (state == Z_NULL) return Z_MEM_ERROR;
+ Tracev((stderr, "inflate: allocated\n"));
+ strm->state = (struct internal_state FAR *)state;
+ state->window = Z_NULL;
+ ret = inflateReset2(strm, windowBits);
+ if (ret != Z_OK) {
+ ZFREE(strm, state);
+ strm->state = Z_NULL;
+ }
+ return ret;
+}
+
+int ZEXPORT inflateInit_(strm, version, stream_size)
+z_streamp strm;
+const char *version;
+int stream_size;
+{
+ return inflateInit2_(strm, DEF_WBITS, version, stream_size);
+}
+
+int ZEXPORT inflatePrime(strm, bits, value)
+z_streamp strm;
+int bits;
+int value;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (bits < 0) {
+ state->hold = 0;
+ state->bits = 0;
+ return Z_OK;
+ }
+ if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR;
+ value &= (1L << bits) - 1;
+ state->hold += value << state->bits;
+ state->bits += bits;
+ return Z_OK;
+}
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+ static int virgin = 1;
+ static code *lenfix, *distfix;
+ static code fixed[544];
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ unsigned sym, bits;
+ static code *next;
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) state->lens[sym++] = 8;
+ while (sym < 256) state->lens[sym++] = 9;
+ while (sym < 280) state->lens[sym++] = 7;
+ while (sym < 288) state->lens[sym++] = 8;
+ next = fixed;
+ lenfix = next;
+ bits = 9;
+ inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) state->lens[sym++] = 5;
+ distfix = next;
+ bits = 5;
+ inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+ /* do this just once */
+ virgin = 0;
+ }
+#else /* !BUILDFIXED */
+# include "inffixed.h"
+#endif /* BUILDFIXED */
+ state->lencode = lenfix;
+ state->lenbits = 9;
+ state->distcode = distfix;
+ state->distbits = 5;
+}
+
+#ifdef MAKEFIXED
+#include <stdio.h>
+
+/*
+ Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also
+ defines BUILDFIXED, so the tables are built on the fly. makefixed() writes
+ those tables to stdout, which would be piped to inffixed.h. A small program
+ can simply call makefixed to do this:
+
+ void makefixed(void);
+
+ int main(void)
+ {
+ makefixed();
+ return 0;
+ }
+
+ Then that can be linked with zlib built with MAKEFIXED defined and run:
+
+ a.out > inffixed.h
+ */
+void makefixed()
+{
+ unsigned low, size;
+ struct inflate_state state;
+
+ fixedtables(&state);
+ puts(" /* inffixed.h -- table for decoding fixed codes");
+ puts(" * Generated automatically by makefixed().");
+ puts(" */");
+ puts("");
+ puts(" /* WARNING: this file should *not* be used by applications.");
+ puts(" It is part of the implementation of this library and is");
+ puts(" subject to change. Applications should only use zlib.h.");
+ puts(" */");
+ puts("");
+ size = 1U << 9;
+ printf(" static const code lenfix[%u] = {", size);
+ low = 0;
+ for (;;) {
+ if ((low % 7) == 0) printf("\n ");
+ printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op,
+ state.lencode[low].bits, state.lencode[low].val);
+ if (++low == size) break;
+ putchar(',');
+ }
+ puts("\n };");
+ size = 1U << 5;
+ printf("\n static const code distfix[%u] = {", size);
+ low = 0;
+ for (;;) {
+ if ((low % 6) == 0) printf("\n ");
+ printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits,
+ state.distcode[low].val);
+ if (++low == size) break;
+ putchar(',');
+ }
+ puts("\n };");
+}
+#endif /* MAKEFIXED */
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning. If window does not exist yet, create it. This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+local int updatewindow(strm, out)
+z_streamp strm;
+unsigned out;
+{
+ struct inflate_state FAR *state;
+ unsigned copy, dist;
+
+ state = (struct inflate_state FAR *)strm->state;
+
+ /* if it hasn't been done already, allocate space for the window */
+ if (state->window == Z_NULL) {
+ state->window = (unsigned char FAR *)
+ ZALLOC(strm, 1U << state->wbits,
+ sizeof(unsigned char));
+ if (state->window == Z_NULL) return 1;
+ }
+
+ /* if window not in use yet, initialize */
+ if (state->wsize == 0) {
+ state->wsize = 1U << state->wbits;
+ state->wnext = 0;
+ state->whave = 0;
+ }
+
+ /* copy state->wsize or less output bytes into the circular window */
+ copy = out - strm->avail_out;
+ if (copy >= state->wsize) {
+ zmemcpy(state->window, strm->next_out - state->wsize, state->wsize);
+ state->wnext = 0;
+ state->whave = state->wsize;
+ }
+ else {
+ dist = state->wsize - state->wnext;
+ if (dist > copy) dist = copy;
+ zmemcpy(state->window + state->wnext, strm->next_out - copy, dist);
+ copy -= dist;
+ if (copy) {
+ zmemcpy(state->window, strm->next_out - copy, copy);
+ state->wnext = copy;
+ state->whave = state->wsize;
+ }
+ else {
+ state->wnext += dist;
+ if (state->wnext == state->wsize) state->wnext = 0;
+ if (state->whave < state->wsize) state->whave += dist;
+ }
+ }
+ return 0;
+}
+
+/* Macros for inflate(): */
+
+/* check function to use adler32() for zlib or crc32() for gzip */
+#ifdef GUNZIP
+# define UPDATE(check, buf, len) \
+ (state->flags ? crc32(check, buf, len) : adler32(check, buf, len))
+#else
+# define UPDATE(check, buf, len) adler32(check, buf, len)
+#endif
+
+/* check macros for header crc */
+#ifdef GUNZIP
+# define CRC2(check, word) \
+ do { \
+ hbuf[0] = (unsigned char)(word); \
+ hbuf[1] = (unsigned char)((word) >> 8); \
+ check = crc32(check, hbuf, 2); \
+ } while (0)
+
+# define CRC4(check, word) \
+ do { \
+ hbuf[0] = (unsigned char)(word); \
+ hbuf[1] = (unsigned char)((word) >> 8); \
+ hbuf[2] = (unsigned char)((word) >> 16); \
+ hbuf[3] = (unsigned char)((word) >> 24); \
+ check = crc32(check, hbuf, 4); \
+ } while (0)
+#endif
+
+/* Load registers with state in inflate() for speed */
+#define LOAD() \
+ do { \
+ put = strm->next_out; \
+ left = strm->avail_out; \
+ next = strm->next_in; \
+ have = strm->avail_in; \
+ hold = state->hold; \
+ bits = state->bits; \
+ } while (0)
+
+/* Restore state from registers in inflate() */
+#define RESTORE() \
+ do { \
+ strm->next_out = put; \
+ strm->avail_out = left; \
+ strm->next_in = next; \
+ strm->avail_in = have; \
+ state->hold = hold; \
+ state->bits = bits; \
+ } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+ do { \
+ hold = 0; \
+ bits = 0; \
+ } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflate()
+ if there is no input available. */
+#define PULLBYTE() \
+ do { \
+ if (have == 0) goto inf_leave; \
+ have--; \
+ hold += (unsigned long)(*next++) << bits; \
+ bits += 8; \
+ } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator. If there is
+ not enough available input to do that, then return from inflate(). */
+#define NEEDBITS(n) \
+ do { \
+ while (bits < (unsigned)(n)) \
+ PULLBYTE(); \
+ } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+ ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+ do { \
+ hold >>= (n); \
+ bits -= (unsigned)(n); \
+ } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+ do { \
+ hold >>= bits & 7; \
+ bits -= bits & 7; \
+ } while (0)
+
+/* Reverse the bytes in a 32-bit value */
+#define REVERSE(q) \
+ ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \
+ (((q) & 0xff00) << 8) + (((q) & 0xff) << 24))
+
+/*
+ inflate() uses a state machine to process as much input data and generate as
+ much output data as possible before returning. The state machine is
+ structured roughly as follows:
+
+ for (;;) switch (state) {
+ ...
+ case STATEn:
+ if (not enough input data or output space to make progress)
+ return;
+ ... make progress ...
+ state = STATEm;
+ break;
+ ...
+ }
+
+ so when inflate() is called again, the same case is attempted again, and
+ if the appropriate resources are provided, the machine proceeds to the
+ next state. The NEEDBITS() macro is usually the way the state evaluates
+ whether it can proceed or should return. NEEDBITS() does the return if
+ the requested bits are not available. The typical use of the BITS macros
+ is:
+
+ NEEDBITS(n);
+ ... do something with BITS(n) ...
+ DROPBITS(n);
+
+ where NEEDBITS(n) either returns from inflate() if there isn't enough
+ input left to load n bits into the accumulator, or it continues. BITS(n)
+ gives the low n bits in the accumulator. When done, DROPBITS(n) drops
+ the low n bits off the accumulator. INITBITS() clears the accumulator
+ and sets the number of available bits to zero. BYTEBITS() discards just
+ enough bits to put the accumulator on a byte boundary. After BYTEBITS()
+ and a NEEDBITS(8), then BITS(8) would return the next byte in the stream.
+
+ NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return
+ if there is no input available. The decoding of variable length codes uses
+ PULLBYTE() directly in order to pull just enough bytes to decode the next
+ code, and no more.
+
+ Some states loop until they get enough input, making sure that enough
+ state information is maintained to continue the loop where it left off
+ if NEEDBITS() returns in the loop. For example, want, need, and keep
+ would all have to actually be part of the saved state in case NEEDBITS()
+ returns:
+
+ case STATEw:
+ while (want < need) {
+ NEEDBITS(n);
+ keep[want++] = BITS(n);
+ DROPBITS(n);
+ }
+ state = STATEx;
+ case STATEx:
+
+ As shown above, if the next state is also the next case, then the break
+ is omitted.
+
+ A state may also return if there is not enough output space available to
+ complete that state. Those states are copying stored data, writing a
+ literal byte, and copying a matching string.
+
+ When returning, a "goto inf_leave" is used to update the total counters,
+ update the check value, and determine whether any progress has been made
+ during that inflate() call in order to return the proper return code.
+ Progress is defined as a change in either strm->avail_in or strm->avail_out.
+ When there is a window, goto inf_leave will update the window with the last
+ output written. If a goto inf_leave occurs in the middle of decompression
+ and there is no window currently, goto inf_leave will create one and copy
+ output to the window for the next call of inflate().
+
+ In this implementation, the flush parameter of inflate() only affects the
+ return code (per zlib.h). inflate() always writes as much as possible to
+ strm->next_out, given the space available and the provided input--the effect
+ documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers
+ the allocation of and copying into a sliding window until necessary, which
+ provides the effect documented in zlib.h for Z_FINISH when the entire input
+ stream available. So the only thing the flush parameter actually does is:
+ when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it
+ will return Z_BUF_ERROR if it has not reached the end of the stream.
+ */
+
+int ZEXPORT inflate(strm, flush)
+z_streamp strm;
+int flush;
+{
+ struct inflate_state FAR *state;
+ unsigned char FAR *next; /* next input */
+ unsigned char FAR *put; /* next output */
+ unsigned have, left; /* available input and output */
+ unsigned long hold; /* bit buffer */
+ unsigned bits; /* bits in bit buffer */
+ unsigned in, out; /* save starting available input and output */
+ unsigned copy; /* number of stored or match bytes to copy */
+ unsigned char FAR *from; /* where to copy match bytes from */
+ code here; /* current decoding table entry */
+ code last; /* parent table entry */
+ unsigned len; /* length to copy for repeats, bits to drop */
+ int ret; /* return code */
+#ifdef GUNZIP
+ unsigned char hbuf[4]; /* buffer for gzip header crc calculation */
+#endif
+ static const unsigned short order[19] = /* permutation of code lengths */
+ {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+ if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL ||
+ (strm->next_in == Z_NULL && strm->avail_in != 0))
+ return Z_STREAM_ERROR;
+
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */
+ LOAD();
+ in = have;
+ out = left;
+ ret = Z_OK;
+ for (;;)
+ switch (state->mode) {
+ case HEAD:
+ if (state->wrap == 0) {
+ state->mode = TYPEDO;
+ break;
+ }
+ NEEDBITS(16);
+#ifdef GUNZIP
+ if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */
+ state->check = crc32(0L, Z_NULL, 0);
+ CRC2(state->check, hold);
+ INITBITS();
+ state->mode = FLAGS;
+ break;
+ }
+ state->flags = 0; /* expect zlib header */
+ if (state->head != Z_NULL)
+ state->head->done = -1;
+ if (!(state->wrap & 1) || /* check if zlib header allowed */
+#else
+ if (
+#endif
+ ((BITS(8) << 8) + (hold >> 8)) % 31) {
+ strm->msg = (char *)"incorrect header check";
+ state->mode = BAD;
+ break;
+ }
+ if (BITS(4) != Z_DEFLATED) {
+ strm->msg = (char *)"unknown compression method";
+ state->mode = BAD;
+ break;
+ }
+ DROPBITS(4);
+ len = BITS(4) + 8;
+ if (state->wbits == 0)
+ state->wbits = len;
+ else if (len > state->wbits) {
+ strm->msg = (char *)"invalid window size";
+ state->mode = BAD;
+ break;
+ }
+ state->dmax = 1U << len;
+ Tracev((stderr, "inflate: zlib header ok\n"));
+ strm->adler = state->check = adler32(0L, Z_NULL, 0);
+ state->mode = hold & 0x200 ? DICTID : TYPE;
+ INITBITS();
+ break;
+#ifdef GUNZIP
+ case FLAGS:
+ NEEDBITS(16);
+ state->flags = (int)(hold);
+ if ((state->flags & 0xff) != Z_DEFLATED) {
+ strm->msg = (char *)"unknown compression method";
+ state->mode = BAD;
+ break;
+ }
+ if (state->flags & 0xe000) {
+ strm->msg = (char *)"unknown header flags set";
+ state->mode = BAD;
+ break;
+ }
+ if (state->head != Z_NULL)
+ state->head->text = (int)((hold >> 8) & 1);
+ if (state->flags & 0x0200) CRC2(state->check, hold);
+ INITBITS();
+ state->mode = TIME;
+ case TIME:
+ NEEDBITS(32);
+ if (state->head != Z_NULL)
+ state->head->time = hold;
+ if (state->flags & 0x0200) CRC4(state->check, hold);
+ INITBITS();
+ state->mode = OS;
+ case OS:
+ NEEDBITS(16);
+ if (state->head != Z_NULL) {
+ state->head->xflags = (int)(hold & 0xff);
+ state->head->os = (int)(hold >> 8);
+ }
+ if (state->flags & 0x0200) CRC2(state->check, hold);
+ INITBITS();
+ state->mode = EXLEN;
+ case EXLEN:
+ if (state->flags & 0x0400) {
+ NEEDBITS(16);
+ state->length = (unsigned)(hold);
+ if (state->head != Z_NULL)
+ state->head->extra_len = (unsigned)hold;
+ if (state->flags & 0x0200) CRC2(state->check, hold);
+ INITBITS();
+ }
+ else if (state->head != Z_NULL)
+ state->head->extra = Z_NULL;
+ state->mode = EXTRA;
+ case EXTRA:
+ if (state->flags & 0x0400) {
+ copy = state->length;
+ if (copy > have) copy = have;
+ if (copy) {
+ if (state->head != Z_NULL &&
+ state->head->extra != Z_NULL) {
+ len = state->head->extra_len - state->length;
+ zmemcpy(state->head->extra + len, next,
+ len + copy > state->head->extra_max ?
+ state->head->extra_max - len : copy);
+ }
+ if (state->flags & 0x0200)
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ state->length -= copy;
+ }
+ if (state->length) goto inf_leave;
+ }
+ state->length = 0;
+ state->mode = NAME;
+ case NAME:
+ if (state->flags & 0x0800) {
+ if (have == 0) goto inf_leave;
+ copy = 0;
+ do {
+ len = (unsigned)(next[copy++]);
+ if (state->head != Z_NULL &&
+ state->head->name != Z_NULL &&
+ state->length < state->head->name_max)
+ state->head->name[state->length++] = len;
+ } while (len && copy < have);
+ if (state->flags & 0x0200)
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ if (len) goto inf_leave;
+ }
+ else if (state->head != Z_NULL)
+ state->head->name = Z_NULL;
+ state->length = 0;
+ state->mode = COMMENT;
+ case COMMENT:
+ if (state->flags & 0x1000) {
+ if (have == 0) goto inf_leave;
+ copy = 0;
+ do {
+ len = (unsigned)(next[copy++]);
+ if (state->head != Z_NULL &&
+ state->head->comment != Z_NULL &&
+ state->length < state->head->comm_max)
+ state->head->comment[state->length++] = len;
+ } while (len && copy < have);
+ if (state->flags & 0x0200)
+ state->check = crc32(state->check, next, copy);
+ have -= copy;
+ next += copy;
+ if (len) goto inf_leave;
+ }
+ else if (state->head != Z_NULL)
+ state->head->comment = Z_NULL;
+ state->mode = HCRC;
+ case HCRC:
+ if (state->flags & 0x0200) {
+ NEEDBITS(16);
+ if (hold != (state->check & 0xffff)) {
+ strm->msg = (char *)"header crc mismatch";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ }
+ if (state->head != Z_NULL) {
+ state->head->hcrc = (int)((state->flags >> 9) & 1);
+ state->head->done = 1;
+ }
+ strm->adler = state->check = crc32(0L, Z_NULL, 0);
+ state->mode = TYPE;
+ break;
+#endif
+ case DICTID:
+ NEEDBITS(32);
+ strm->adler = state->check = REVERSE(hold);
+ INITBITS();
+ state->mode = DICT;
+ case DICT:
+ if (state->havedict == 0) {
+ RESTORE();
+ return Z_NEED_DICT;
+ }
+ strm->adler = state->check = adler32(0L, Z_NULL, 0);
+ state->mode = TYPE;
+ case TYPE:
+ if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave;
+ case TYPEDO:
+ if (state->last) {
+ BYTEBITS();
+ state->mode = CHECK;
+ break;
+ }
+ NEEDBITS(3);
+ state->last = BITS(1);
+ DROPBITS(1);
+ switch (BITS(2)) {
+ case 0: /* stored block */
+ Tracev((stderr, "inflate: stored block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ Tracev((stderr, "inflate: fixed codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = LEN_; /* decode codes */
+ if (flush == Z_TREES) {
+ DROPBITS(2);
+ goto inf_leave;
+ }
+ break;
+ case 2: /* dynamic block */
+ Tracev((stderr, "inflate: dynamic codes block%s\n",
+ state->last ? " (last)" : ""));
+ state->mode = TABLE;
+ break;
+ case 3:
+ strm->msg = (char *)"invalid block type";
+ state->mode = BAD;
+ }
+ DROPBITS(2);
+ break;
+ case STORED:
+ BYTEBITS(); /* go to byte boundary */
+ NEEDBITS(32);
+ if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+ strm->msg = (char *)"invalid stored block lengths";
+ state->mode = BAD;
+ break;
+ }
+ state->length = (unsigned)hold & 0xffff;
+ Tracev((stderr, "inflate: stored length %u\n",
+ state->length));
+ INITBITS();
+ state->mode = COPY_;
+ if (flush == Z_TREES) goto inf_leave;
+ case COPY_:
+ state->mode = COPY;
+ case COPY:
+ copy = state->length;
+ if (copy) {
+ if (copy > have) copy = have;
+ if (copy > left) copy = left;
+ if (copy == 0) goto inf_leave;
+ zmemcpy(put, next, copy);
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state->length -= copy;
+ break;
+ }
+ Tracev((stderr, "inflate: stored end\n"));
+ state->mode = TYPE;
+ break;
+ case TABLE:
+ NEEDBITS(14);
+ state->nlen = BITS(5) + 257;
+ DROPBITS(5);
+ state->ndist = BITS(5) + 1;
+ DROPBITS(5);
+ state->ncode = BITS(4) + 4;
+ DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+ if (state->nlen > 286 || state->ndist > 30) {
+ strm->msg = (char *)"too many length or distance symbols";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracev((stderr, "inflate: table sizes ok\n"));
+ state->have = 0;
+ state->mode = LENLENS;
+ case LENLENS:
+ while (state->have < state->ncode) {
+ NEEDBITS(3);
+ state->lens[order[state->have++]] = (unsigned short)BITS(3);
+ DROPBITS(3);
+ }
+ while (state->have < 19)
+ state->lens[order[state->have++]] = 0;
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 7;
+ ret = inflate_table(CODES, state->lens, 19, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid code lengths set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: code lengths ok\n"));
+ state->have = 0;
+ state->mode = CODELENS;
+ case CODELENS:
+ while (state->have < state->nlen + state->ndist) {
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.val < 16) {
+ DROPBITS(here.bits);
+ state->lens[state->have++] = here.val;
+ }
+ else {
+ if (here.val == 16) {
+ NEEDBITS(here.bits + 2);
+ DROPBITS(here.bits);
+ if (state->have == 0) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ len = state->lens[state->have - 1];
+ copy = 3 + BITS(2);
+ DROPBITS(2);
+ }
+ else if (here.val == 17) {
+ NEEDBITS(here.bits + 3);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 3 + BITS(3);
+ DROPBITS(3);
+ }
+ else {
+ NEEDBITS(here.bits + 7);
+ DROPBITS(here.bits);
+ len = 0;
+ copy = 11 + BITS(7);
+ DROPBITS(7);
+ }
+ if (state->have + copy > state->nlen + state->ndist) {
+ strm->msg = (char *)"invalid bit length repeat";
+ state->mode = BAD;
+ break;
+ }
+ while (copy--)
+ state->lens[state->have++] = (unsigned short)len;
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state->mode == BAD) break;
+
+ /* check for end-of-block code (better have one) */
+ if (state->lens[256] == 0) {
+ strm->msg = (char *)"invalid code -- missing end-of-block";
+ state->mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state->next = state->codes;
+ state->lencode = (code const FAR *)(state->next);
+ state->lenbits = 9;
+ ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+ &(state->lenbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid literal/lengths set";
+ state->mode = BAD;
+ break;
+ }
+ state->distcode = (code const FAR *)(state->next);
+ state->distbits = 6;
+ ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+ &(state->next), &(state->distbits), state->work);
+ if (ret) {
+ strm->msg = (char *)"invalid distances set";
+ state->mode = BAD;
+ break;
+ }
+ Tracev((stderr, "inflate: codes ok\n"));
+ state->mode = LEN_;
+ if (flush == Z_TREES) goto inf_leave;
+ case LEN_:
+ state->mode = LEN;
+ case LEN:
+ if (have >= 6 && left >= 258) {
+ RESTORE();
+ inflate_fast(strm, out);
+ LOAD();
+ if (state->mode == TYPE)
+ state->back = -1;
+ break;
+ }
+ state->back = 0;
+ for (;;) {
+ here = state->lencode[BITS(state->lenbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if (here.op && (here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->lencode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ state->back += last.bits;
+ }
+ DROPBITS(here.bits);
+ state->back += here.bits;
+ state->length = (unsigned)here.val;
+ if ((int)(here.op) == 0) {
+ Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ "inflate: literal '%c'\n" :
+ "inflate: literal 0x%02x\n", here.val));
+ state->mode = LIT;
+ break;
+ }
+ if (here.op & 32) {
+ Tracevv((stderr, "inflate: end of block\n"));
+ state->back = -1;
+ state->mode = TYPE;
+ break;
+ }
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid literal/length code";
+ state->mode = BAD;
+ break;
+ }
+ state->extra = (unsigned)(here.op) & 15;
+ state->mode = LENEXT;
+ case LENEXT:
+ if (state->extra) {
+ NEEDBITS(state->extra);
+ state->length += BITS(state->extra);
+ DROPBITS(state->extra);
+ state->back += state->extra;
+ }
+ Tracevv((stderr, "inflate: length %u\n", state->length));
+ state->was = state->length;
+ state->mode = DIST;
+ case DIST:
+ for (;;) {
+ here = state->distcode[BITS(state->distbits)];
+ if ((unsigned)(here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ if ((here.op & 0xf0) == 0) {
+ last = here;
+ for (;;) {
+ here = state->distcode[last.val +
+ (BITS(last.bits + last.op) >> last.bits)];
+ if ((unsigned)(last.bits + here.bits) <= bits) break;
+ PULLBYTE();
+ }
+ DROPBITS(last.bits);
+ state->back += last.bits;
+ }
+ DROPBITS(here.bits);
+ state->back += here.bits;
+ if (here.op & 64) {
+ strm->msg = (char *)"invalid distance code";
+ state->mode = BAD;
+ break;
+ }
+ state->offset = (unsigned)here.val;
+ state->extra = (unsigned)(here.op) & 15;
+ state->mode = DISTEXT;
+ case DISTEXT:
+ if (state->extra) {
+ NEEDBITS(state->extra);
+ state->offset += BITS(state->extra);
+ DROPBITS(state->extra);
+ state->back += state->extra;
+ }
+#ifdef INFLATE_STRICT
+ if (state->offset > state->dmax) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#endif
+ Tracevv((stderr, "inflate: distance %u\n", state->offset));
+ state->mode = MATCH;
+ case MATCH:
+ if (left == 0) goto inf_leave;
+ copy = out - left;
+ if (state->offset > copy) { /* copy from window */
+ copy = state->offset - copy;
+ if (copy > state->whave) {
+ if (state->sane) {
+ strm->msg = (char *)"invalid distance too far back";
+ state->mode = BAD;
+ break;
+ }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ Trace((stderr, "inflate.c too far\n"));
+ copy -= state->whave;
+ if (copy > state->length) copy = state->length;
+ if (copy > left) copy = left;
+ left -= copy;
+ state->length -= copy;
+ do {
+ *put++ = 0;
+ } while (--copy);
+ if (state->length == 0) state->mode = LEN;
+ break;
+#endif
+ }
+ if (copy > state->wnext) {
+ copy -= state->wnext;
+ from = state->window + (state->wsize - copy);
+ }
+ else
+ from = state->window + (state->wnext - copy);
+ if (copy > state->length) copy = state->length;
+ }
+ else { /* copy from output */
+ from = put - state->offset;
+ copy = state->length;
+ }
+ if (copy > left) copy = left;
+ left -= copy;
+ state->length -= copy;
+ do {
+ *put++ = *from++;
+ } while (--copy);
+ if (state->length == 0) state->mode = LEN;
+ break;
+ case LIT:
+ if (left == 0) goto inf_leave;
+ *put++ = (unsigned char)(state->length);
+ left--;
+ state->mode = LEN;
+ break;
+ case CHECK:
+ if (state->wrap) {
+ NEEDBITS(32);
+ out -= left;
+ strm->total_out += out;
+ state->total += out;
+ if (out)
+ strm->adler = state->check =
+ UPDATE(state->check, put - out, out);
+ out = left;
+ if ((
+#ifdef GUNZIP
+ state->flags ? hold :
+#endif
+ REVERSE(hold)) != state->check) {
+ strm->msg = (char *)"incorrect data check";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ Tracev((stderr, "inflate: check matches trailer\n"));
+ }
+#ifdef GUNZIP
+ state->mode = LENGTH;
+ case LENGTH:
+ if (state->wrap && state->flags) {
+ NEEDBITS(32);
+ if (hold != (state->total & 0xffffffffUL)) {
+ strm->msg = (char *)"incorrect length check";
+ state->mode = BAD;
+ break;
+ }
+ INITBITS();
+ Tracev((stderr, "inflate: length matches trailer\n"));
+ }
+#endif
+ state->mode = DONE;
+ case DONE:
+ ret = Z_STREAM_END;
+ goto inf_leave;
+ case BAD:
+ ret = Z_DATA_ERROR;
+ goto inf_leave;
+ case MEM:
+ return Z_MEM_ERROR;
+ case SYNC:
+ default:
+ return Z_STREAM_ERROR;
+ }
+
+ /*
+ Return from inflate(), updating the total counts and the check value.
+ If there was no progress during the inflate() call, return a buffer
+ error. Call updatewindow() to create and/or update the window state.
+ Note: a memory error from inflate() is non-recoverable.
+ */
+ inf_leave:
+ RESTORE();
+ if (state->wsize || (out != strm->avail_out && state->mode < BAD &&
+ (state->mode < CHECK || flush != Z_FINISH)))
+ if (updatewindow(strm, out)) {
+ state->mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ in -= strm->avail_in;
+ out -= strm->avail_out;
+ strm->total_in += in;
+ strm->total_out += out;
+ state->total += out;
+ if (state->wrap && out)
+ strm->adler = state->check =
+ UPDATE(state->check, strm->next_out - out, out);
+ strm->data_type = state->bits + (state->last ? 64 : 0) +
+ (state->mode == TYPE ? 128 : 0) +
+ (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0);
+ if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK)
+ ret = Z_BUF_ERROR;
+ return ret;
+}
+
+int ZEXPORT inflateEnd(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->window != Z_NULL) ZFREE(strm, state->window);
+ ZFREE(strm, strm->state);
+ strm->state = Z_NULL;
+ Tracev((stderr, "inflate: end\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+const Bytef *dictionary;
+uInt dictLength;
+{
+ struct inflate_state FAR *state;
+ unsigned long id;
+ unsigned char *next;
+ unsigned avail;
+ int ret;
+
+ /* check state */
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state->wrap != 0 && state->mode != DICT)
+ return Z_STREAM_ERROR;
+
+ /* check for correct dictionary id */
+ if (state->mode == DICT) {
+ id = adler32(0L, Z_NULL, 0);
+ id = adler32(id, dictionary, dictLength);
+ if (id != state->check)
+ return Z_DATA_ERROR;
+ }
+
+ /* copy dictionary to window using updatewindow(), which will amend the
+ existing dictionary if appropriate */
+ next = strm->next_out;
+ avail = strm->avail_out;
+ strm->next_out = (Bytef *)dictionary + dictLength;
+ strm->avail_out = 0;
+ ret = updatewindow(strm, dictLength);
+ strm->avail_out = avail;
+ strm->next_out = next;
+ if (ret) {
+ state->mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ state->havedict = 1;
+ Tracev((stderr, "inflate: dictionary set\n"));
+ return Z_OK;
+}
+
+int ZEXPORT inflateGetHeader(strm, head)
+z_streamp strm;
+gz_headerp head;
+{
+ struct inflate_state FAR *state;
+
+ /* check state */
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if ((state->wrap & 2) == 0) return Z_STREAM_ERROR;
+
+ /* save header structure */
+ state->head = head;
+ head->done = 0;
+ return Z_OK;
+}
+
+/*
+ Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found
+ or when out of input. When called, *have is the number of pattern bytes
+ found in order so far, in 0..3. On return *have is updated to the new
+ state. If on return *have equals four, then the pattern was found and the
+ return value is how many bytes were read including the last byte of the
+ pattern. If *have is less than four, then the pattern has not been found
+ yet and the return value is len. In the latter case, syncsearch() can be
+ called again with more data and the *have state. *have is initialized to
+ zero for the first call.
+ */
+local unsigned syncsearch(have, buf, len)
+unsigned FAR *have;
+unsigned char FAR *buf;
+unsigned len;
+{
+ unsigned got;
+ unsigned next;
+
+ got = *have;
+ next = 0;
+ while (next < len && got < 4) {
+ if ((int)(buf[next]) == (got < 2 ? 0 : 0xff))
+ got++;
+ else if (buf[next])
+ got = 0;
+ else
+ got = 4 - got;
+ next++;
+ }
+ *have = got;
+ return next;
+}
+
+int ZEXPORT inflateSync(strm)
+z_streamp strm;
+{
+ unsigned len; /* number of bytes to look at or looked at */
+ unsigned long in, out; /* temporary to save total_in and total_out */
+ unsigned char buf[4]; /* to restore bit buffer to byte string */
+ struct inflate_state FAR *state;
+
+ /* check parameters */
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR;
+
+ /* if first time, start search in bit buffer */
+ if (state->mode != SYNC) {
+ state->mode = SYNC;
+ state->hold <<= state->bits & 7;
+ state->bits -= state->bits & 7;
+ len = 0;
+ while (state->bits >= 8) {
+ buf[len++] = (unsigned char)(state->hold);
+ state->hold >>= 8;
+ state->bits -= 8;
+ }
+ state->have = 0;
+ syncsearch(&(state->have), buf, len);
+ }
+
+ /* search available input */
+ len = syncsearch(&(state->have), strm->next_in, strm->avail_in);
+ strm->avail_in -= len;
+ strm->next_in += len;
+ strm->total_in += len;
+
+ /* return no joy or set up to restart inflate() on a new block */
+ if (state->have != 4) return Z_DATA_ERROR;
+ in = strm->total_in; out = strm->total_out;
+ inflateReset(strm);
+ strm->total_in = in; strm->total_out = out;
+ state->mode = TYPE;
+ return Z_OK;
+}
+
+/*
+ Returns true if inflate is currently at the end of a block generated by
+ Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+ implementation to provide an additional safety check. PPP uses
+ Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored
+ block. When decompressing, PPP checks that at the end of input packet,
+ inflate is waiting for these length bytes.
+ */
+int ZEXPORT inflateSyncPoint(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ return state->mode == STORED && state->bits == 0;
+}
+
+int ZEXPORT inflateCopy(dest, source)
+z_streamp dest;
+z_streamp source;
+{
+ struct inflate_state FAR *state;
+ struct inflate_state FAR *copy;
+ unsigned char FAR *window;
+ unsigned wsize;
+
+ /* check input */
+ if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL ||
+ source->zalloc == (alloc_func)0 || source->zfree == (free_func)0)
+ return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)source->state;
+
+ /* allocate space */
+ copy = (struct inflate_state FAR *)
+ ZALLOC(source, 1, sizeof(struct inflate_state));
+ if (copy == Z_NULL) return Z_MEM_ERROR;
+ window = Z_NULL;
+ if (state->window != Z_NULL) {
+ window = (unsigned char FAR *)
+ ZALLOC(source, 1U << state->wbits, sizeof(unsigned char));
+ if (window == Z_NULL) {
+ ZFREE(source, copy);
+ return Z_MEM_ERROR;
+ }
+ }
+
+ /* copy state */
+ zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+ zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state));
+ if (state->lencode >= state->codes &&
+ state->lencode <= state->codes + ENOUGH - 1) {
+ copy->lencode = copy->codes + (state->lencode - state->codes);
+ copy->distcode = copy->codes + (state->distcode - state->codes);
+ }
+ copy->next = copy->codes + (state->next - state->codes);
+ if (window != Z_NULL) {
+ wsize = 1U << state->wbits;
+ zmemcpy(window, state->window, wsize);
+ }
+ copy->window = window;
+ dest->state = (struct internal_state FAR *)copy;
+ return Z_OK;
+}
+
+int ZEXPORT inflateUndermine(strm, subvert)
+z_streamp strm;
+int subvert;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ state->sane = !subvert;
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ return Z_OK;
+#else
+ state->sane = 1;
+ return Z_DATA_ERROR;
+#endif
+}
+
+long ZEXPORT inflateMark(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+
+ if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16;
+ state = (struct inflate_state FAR *)strm->state;
+ return ((long)(state->back) << 16) +
+ (state->mode == COPY ? state->length :
+ (state->mode == MATCH ? state->was - state->length : 0));
+}
diff --git a/src/cpp/core/zlib/inflate.h b/src/cpp/core/zlib/inflate.h
new file mode 100644
index 0000000..95f4986
--- /dev/null
+++ b/src/cpp/core/zlib/inflate.h
@@ -0,0 +1,122 @@
+/* inflate.h -- internal inflate state definition
+ * Copyright (C) 1995-2009 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+ trailer decoding by inflate(). NO_GZIP would be used to avoid linking in
+ the crc code when it is not needed. For shared libraries, gzip decoding
+ should be left enabled. */
+#ifndef NO_GZIP
+# define GUNZIP
+#endif
+
+/* Possible inflate modes between inflate() calls */
+typedef enum {
+ HEAD, /* i: waiting for magic header */
+ FLAGS, /* i: waiting for method and flags (gzip) */
+ TIME, /* i: waiting for modification time (gzip) */
+ OS, /* i: waiting for extra flags and operating system (gzip) */
+ EXLEN, /* i: waiting for extra length (gzip) */
+ EXTRA, /* i: waiting for extra bytes (gzip) */
+ NAME, /* i: waiting for end of file name (gzip) */
+ COMMENT, /* i: waiting for end of comment (gzip) */
+ HCRC, /* i: waiting for header crc (gzip) */
+ DICTID, /* i: waiting for dictionary check value */
+ DICT, /* waiting for inflateSetDictionary() call */
+ TYPE, /* i: waiting for type bits, including last-flag bit */
+ TYPEDO, /* i: same, but skip check to exit inflate on new block */
+ STORED, /* i: waiting for stored size (length and complement) */
+ COPY_, /* i/o: same as COPY below, but only first time in */
+ COPY, /* i/o: waiting for input or output to copy stored block */
+ TABLE, /* i: waiting for dynamic block table lengths */
+ LENLENS, /* i: waiting for code length code lengths */
+ CODELENS, /* i: waiting for length/lit and distance code lengths */
+ LEN_, /* i: same as LEN below, but only first time in */
+ LEN, /* i: waiting for length/lit/eob code */
+ LENEXT, /* i: waiting for length extra bits */
+ DIST, /* i: waiting for distance code */
+ DISTEXT, /* i: waiting for distance extra bits */
+ MATCH, /* o: waiting for output space to copy string */
+ LIT, /* o: waiting for output space to write literal */
+ CHECK, /* i: waiting for 32-bit check value */
+ LENGTH, /* i: waiting for 32-bit length (gzip) */
+ DONE, /* finished check, done -- remain here until reset */
+ BAD, /* got a data error -- remain here until reset */
+ MEM, /* got an inflate() memory error -- remain here until reset */
+ SYNC /* looking for synchronization bytes to restart inflate() */
+} inflate_mode;
+
+/*
+ State transitions between above modes -
+
+ (most modes can go to BAD or MEM on error -- not shown for clarity)
+
+ Process header:
+ HEAD -> (gzip) or (zlib) or (raw)
+ (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT ->
+ HCRC -> TYPE
+ (zlib) -> DICTID or TYPE
+ DICTID -> DICT -> TYPE
+ (raw) -> TYPEDO
+ Read deflate blocks:
+ TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK
+ STORED -> COPY_ -> COPY -> TYPE
+ TABLE -> LENLENS -> CODELENS -> LEN_
+ LEN_ -> LEN
+ Read deflate codes in fixed or dynamic block:
+ LEN -> LENEXT or LIT or TYPE
+ LENEXT -> DIST -> DISTEXT -> MATCH -> LEN
+ LIT -> LEN
+ Process trailer:
+ CHECK -> LENGTH -> DONE
+ */
+
+/* state maintained between inflate() calls. Approximately 10K bytes. */
+struct inflate_state {
+ inflate_mode mode; /* current inflate mode */
+ int last; /* true if processing last block */
+ int wrap; /* bit 0 true for zlib, bit 1 true for gzip */
+ int havedict; /* true if dictionary provided */
+ int flags; /* gzip header method and flags (0 if zlib) */
+ unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */
+ unsigned long check; /* protected copy of check value */
+ unsigned long total; /* protected copy of output count */
+ gz_headerp head; /* where to save gzip header information */
+ /* sliding window */
+ unsigned wbits; /* log base 2 of requested window size */
+ unsigned wsize; /* window size or zero if not using window */
+ unsigned whave; /* valid bytes in the window */
+ unsigned wnext; /* window write index */
+ unsigned char FAR *window; /* allocated sliding window, if needed */
+ /* bit accumulator */
+ unsigned long hold; /* input bit accumulator */
+ unsigned bits; /* number of bits in "in" */
+ /* for string and stored block copying */
+ unsigned length; /* literal or length of data to copy */
+ unsigned offset; /* distance back to copy string from */
+ /* for table and code decoding */
+ unsigned extra; /* extra bits needed */
+ /* fixed and dynamic code tables */
+ code const FAR *lencode; /* starting table for length/literal codes */
+ code const FAR *distcode; /* starting table for distance codes */
+ unsigned lenbits; /* index bits for lencode */
+ unsigned distbits; /* index bits for distcode */
+ /* dynamic table building */
+ unsigned ncode; /* number of code length code lengths */
+ unsigned nlen; /* number of length code lengths */
+ unsigned ndist; /* number of distance code lengths */
+ unsigned have; /* number of code lengths in lens[] */
+ code FAR *next; /* next available space in codes[] */
+ unsigned short lens[320]; /* temporary storage for code lengths */
+ unsigned short work[288]; /* work area for code table building */
+ code codes[ENOUGH]; /* space for code tables */
+ int sane; /* if false, allow invalid distance too far */
+ int back; /* bits back of last unprocessed length/lit */
+ unsigned was; /* initial length of match */
+};
diff --git a/src/cpp/core/zlib/inftrees.c b/src/cpp/core/zlib/inftrees.c
new file mode 100644
index 0000000..60bbd58
--- /dev/null
+++ b/src/cpp/core/zlib/inftrees.c
@@ -0,0 +1,306 @@
+/* inftrees.c -- generate Huffman trees for efficient decoding
+ * Copyright (C) 1995-2012 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+
+#define MAXBITS 15
+
+const char inflate_copyright[] =
+ " inflate 1.2.6 Copyright 1995-2012 Mark Adler ";
+/*
+ If you use the zlib library in a product, an acknowledgment is welcome
+ in the documentation of your product. If for some reason you cannot
+ include such an acknowledgment, I would appreciate that you keep this
+ copyright string in the executable of your product.
+ */
+
+/*
+ Build a set of tables to decode the provided canonical Huffman code.
+ The code lengths are lens[0..codes-1]. The result starts at *table,
+ whose indices are 0..2^bits-1. work is a writable array of at least
+ lens shorts, which is used as a work area. type is the type of code
+ to be generated, CODES, LENS, or DISTS. On return, zero is success,
+ -1 is an invalid code, and +1 means that ENOUGH isn't enough. table
+ on return points to the next available entry's address. bits is the
+ requested root table index bits, and on return it is the actual root
+ table index bits. It will differ if the request is greater than the
+ longest code or if it is less than the shortest code.
+ */
+int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
+codetype type;
+unsigned short FAR *lens;
+unsigned codes;
+code FAR * FAR *table;
+unsigned FAR *bits;
+unsigned short FAR *work;
+{
+ unsigned len; /* a code's length in bits */
+ unsigned sym; /* index of code symbols */
+ unsigned min, max; /* minimum and maximum code lengths */
+ unsigned root; /* number of index bits for root table */
+ unsigned curr; /* number of index bits for current table */
+ unsigned drop; /* code bits to drop for sub-table */
+ int left; /* number of prefix codes available */
+ unsigned used; /* code entries in table used */
+ unsigned huff; /* Huffman code */
+ unsigned incr; /* for incrementing code, index */
+ unsigned fill; /* index for replicating entries */
+ unsigned low; /* low bits for current root entry */
+ unsigned mask; /* mask for low root bits */
+ code here; /* table entry for duplication */
+ code FAR *next; /* next available space in table */
+ const unsigned short FAR *base; /* base value table to use */
+ const unsigned short FAR *extra; /* extra bits table to use */
+ int end; /* use base and extra for symbol > end */
+ unsigned short count[MAXBITS+1]; /* number of codes of each length */
+ unsigned short offs[MAXBITS+1]; /* offsets in table for each length */
+ static const unsigned short lbase[31] = { /* Length codes 257..285 base */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+ static const unsigned short lext[31] = { /* Length codes 257..285 extra */
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 203, 69};
+ static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577, 0, 0};
+ static const unsigned short dext[32] = { /* Distance codes 0..29 extra */
+ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+ 28, 28, 29, 29, 64, 64};
+
+ /*
+ Process a set of code lengths to create a canonical Huffman code. The
+ code lengths are lens[0..codes-1]. Each length corresponds to the
+ symbols 0..codes-1. The Huffman code is generated by first sorting the
+ symbols by length from short to long, and retaining the symbol order
+ for codes with equal lengths. Then the code starts with all zero bits
+ for the first code of the shortest length, and the codes are integer
+ increments for the same length, and zeros are appended as the length
+ increases. For the deflate format, these bits are stored backwards
+ from their more natural integer increment ordering, and so when the
+ decoding tables are built in the large loop below, the integer codes
+ are incremented backwards.
+
+ This routine assumes, but does not check, that all of the entries in
+ lens[] are in the range 0..MAXBITS. The caller must assure this.
+ 1..MAXBITS is interpreted as that code length. zero means that that
+ symbol does not occur in this code.
+
+ The codes are sorted by computing a count of codes for each length,
+ creating from that a table of starting indices for each length in the
+ sorted table, and then entering the symbols in order in the sorted
+ table. The sorted table is work[], with that space being provided by
+ the caller.
+
+ The length counts are used for other purposes as well, i.e. finding
+ the minimum and maximum length codes, determining if there are any
+ codes at all, checking for a valid set of lengths, and looking ahead
+ at length counts to determine sub-table sizes when building the
+ decoding tables.
+ */
+
+ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+ for (len = 0; len <= MAXBITS; len++)
+ count[len] = 0;
+ for (sym = 0; sym < codes; sym++)
+ count[lens[sym]]++;
+
+ /* bound code lengths, force root to be within code lengths */
+ root = *bits;
+ for (max = MAXBITS; max >= 1; max--)
+ if (count[max] != 0) break;
+ if (root > max) root = max;
+ if (max == 0) { /* no symbols to code at all */
+ here.op = (unsigned char)64; /* invalid code marker */
+ here.bits = (unsigned char)1;
+ here.val = (unsigned short)0;
+ *(*table)++ = here; /* make a table to force an error */
+ *(*table)++ = here;
+ *bits = 1;
+ return 0; /* no symbols, but wait for decoding to report error */
+ }
+ for (min = 1; min < max; min++)
+ if (count[min] != 0) break;
+ if (root < min) root = min;
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= count[len];
+ if (left < 0) return -1; /* over-subscribed */
+ }
+ if (left > 0 && (type == CODES || max != 1))
+ return -1; /* incomplete set */
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++)
+ offs[len + 1] = offs[len] + count[len];
+
+ /* sort symbols by length, by symbol order within each length */
+ for (sym = 0; sym < codes; sym++)
+ if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;
+
+ /*
+ Create and fill in decoding tables. In this loop, the table being
+ filled is at next and has curr index bits. The code being used is huff
+ with length len. That code is converted to an index by dropping drop
+ bits off of the bottom. For codes where len is less than drop + curr,
+ those top drop + curr - len bits are incremented through all values to
+ fill the table with replicated entries.
+
+ root is the number of index bits for the root table. When len exceeds
+ root, sub-tables are created pointed to by the root entry with an index
+ of the low root bits of huff. This is saved in low to check for when a
+ new sub-table should be started. drop is zero when the root table is
+ being filled, and drop is root when sub-tables are being filled.
+
+ When a new sub-table is needed, it is necessary to look ahead in the
+ code lengths to determine what size sub-table is needed. The length
+ counts are used for this, and so count[] is decremented as codes are
+ entered in the tables.
+
+ used keeps track of how many table entries have been allocated from the
+ provided *table space. It is checked for LENS and DIST tables against
+ the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+ the initial root table size constants. See the comments in inftrees.h
+ for more information.
+
+ sym increments through all symbols, and the loop terminates when
+ all codes of length max, i.e. all codes, have been processed. This
+ routine permits incomplete codes, so another loop after this one fills
+ in the rest of the decoding tables with invalid code markers.
+ */
+
+ /* set up for code type */
+ switch (type) {
+ case CODES:
+ base = extra = work; /* dummy value--not used */
+ end = 19;
+ break;
+ case LENS:
+ base = lbase;
+ base -= 257;
+ extra = lext;
+ extra -= 257;
+ end = 256;
+ break;
+ default: /* DISTS */
+ base = dbase;
+ extra = dext;
+ end = -1;
+ }
+
+ /* initialize state for loop */
+ huff = 0; /* starting code */
+ sym = 0; /* starting code symbol */
+ len = min; /* starting code length */
+ next = *table; /* current table to fill in */
+ curr = root; /* current table index bits */
+ drop = 0; /* current bits to drop from code for index */
+ low = (unsigned)(-1); /* trigger new sub-table when len > root */
+ used = 1U << root; /* use root table entries */
+ mask = used - 1; /* mask for comparing low */
+
+ /* check available table space */
+ if ((type == LENS && used >= ENOUGH_LENS) ||
+ (type == DISTS && used >= ENOUGH_DISTS))
+ return 1;
+
+ /* process all codes and make table entries */
+ for (;;) {
+ /* create table entry */
+ here.bits = (unsigned char)(len - drop);
+ if ((int)(work[sym]) < end) {
+ here.op = (unsigned char)0;
+ here.val = work[sym];
+ }
+ else if ((int)(work[sym]) > end) {
+ here.op = (unsigned char)(extra[work[sym]]);
+ here.val = base[work[sym]];
+ }
+ else {
+ here.op = (unsigned char)(32 + 64); /* end of block */
+ here.val = 0;
+ }
+
+ /* replicate for those indices with low len bits equal to huff */
+ incr = 1U << (len - drop);
+ fill = 1U << curr;
+ min = fill; /* save offset to next table */
+ do {
+ fill -= incr;
+ next[(huff >> drop) + fill] = here;
+ } while (fill != 0);
+
+ /* backwards increment the len-bit code huff */
+ incr = 1U << (len - 1);
+ while (huff & incr)
+ incr >>= 1;
+ if (incr != 0) {
+ huff &= incr - 1;
+ huff += incr;
+ }
+ else
+ huff = 0;
+
+ /* go to next symbol, update count, len */
+ sym++;
+ if (--(count[len]) == 0) {
+ if (len == max) break;
+ len = lens[work[sym]];
+ }
+
+ /* create new sub-table if needed */
+ if (len > root && (huff & mask) != low) {
+ /* if first time, transition to sub-tables */
+ if (drop == 0)
+ drop = root;
+
+ /* increment past last table */
+ next += min; /* here min is 1 << curr */
+
+ /* determine length of next table */
+ curr = len - drop;
+ left = (int)(1 << curr);
+ while (curr + drop < max) {
+ left -= count[curr + drop];
+ if (left <= 0) break;
+ curr++;
+ left <<= 1;
+ }
+
+ /* check for enough space */
+ used += 1U << curr;
+ if ((type == LENS && used >= ENOUGH_LENS) ||
+ (type == DISTS && used >= ENOUGH_DISTS))
+ return 1;
+
+ /* point entry in root table to sub-table */
+ low = huff & mask;
+ (*table)[low].op = (unsigned char)curr;
+ (*table)[low].bits = (unsigned char)root;
+ (*table)[low].val = (unsigned short)(next - *table);
+ }
+ }
+
+ /* fill in remaining table entry if code is incomplete (guaranteed to have
+ at most one remaining entry, since if the code is incomplete, the
+ maximum code length that was allowed to get this far is one bit) */
+ if (huff != 0) {
+ here.op = (unsigned char)64; /* invalid code marker */
+ here.bits = (unsigned char)(len - drop);
+ here.val = (unsigned short)0;
+ next[huff] = here;
+ }
+
+ /* set return parameters */
+ *table += used;
+ *bits = root;
+ return 0;
+}
diff --git a/src/cpp/core/zlib/inftrees.h b/src/cpp/core/zlib/inftrees.h
new file mode 100644
index 0000000..baa53a0
--- /dev/null
+++ b/src/cpp/core/zlib/inftrees.h
@@ -0,0 +1,62 @@
+/* inftrees.h -- header to use inftrees.c
+ * Copyright (C) 1995-2005, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* Structure for decoding tables. Each entry provides either the
+ information needed to do the operation requested by the code that
+ indexed that table entry, or it provides a pointer to another
+ table that indexes more bits of the code. op indicates whether
+ the entry is a pointer to another table, a literal, a length or
+ distance, an end-of-block, or an invalid code. For a table
+ pointer, the low four bits of op is the number of index bits of
+ that table. For a length or distance, the low four bits of op
+ is the number of extra bits to get after the code. bits is
+ the number of bits in this code or part of the code to drop off
+ of the bit buffer. val is the actual byte to output in the case
+ of a literal, the base length or distance, or the offset from
+ the current table to the next table. Each entry is four bytes. */
+typedef struct {
+ unsigned char op; /* operation, extra bits, table bits */
+ unsigned char bits; /* bits in this part of the code */
+ unsigned short val; /* offset in table or code value */
+} code;
+
+/* op values as set by inflate_table():
+ 00000000 - literal
+ 0000tttt - table link, tttt != 0 is the number of table index bits
+ 0001eeee - length or distance, eeee is the number of extra bits
+ 01100000 - end of block
+ 01000000 - invalid code
+ */
+
+/* Maximum size of the dynamic table. The maximum number of code structures is
+ 1444, which is the sum of 852 for literal/length codes and 592 for distance
+ codes. These values were found by exhaustive searches using the program
+ examples/enough.c found in the zlib distribtution. The arguments to that
+ program are the number of symbols, the initial root table size, and the
+ maximum bit length of a code. "enough 286 9 15" for literal/length codes
+ returns returns 852, and "enough 30 6 15" for distance codes returns 592.
+ The initial root table size (9 or 6) is found in the fifth argument of the
+ inflate_table() calls in inflate.c and infback.c. If the root table size is
+ changed, then these maximum sizes would be need to be recalculated and
+ updated. */
+#define ENOUGH_LENS 852
+#define ENOUGH_DISTS 592
+#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
+
+/* Type of code to build for inflate_table() */
+typedef enum {
+ CODES,
+ LENS,
+ DISTS
+} codetype;
+
+int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens,
+ unsigned codes, code FAR * FAR *table,
+ unsigned FAR *bits, unsigned short FAR *work));
diff --git a/src/cpp/core/zlib/trees.c b/src/cpp/core/zlib/trees.c
new file mode 100644
index 0000000..8c32b21
--- /dev/null
+++ b/src/cpp/core/zlib/trees.c
@@ -0,0 +1,1224 @@
+/* trees.c -- output deflated data using Huffman coding
+ * Copyright (C) 1995-2012 Jean-loup Gailly
+ * detect_data_type() function provided freely by Cosmin Truta, 2006
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * ALGORITHM
+ *
+ * The "deflation" process uses several Huffman trees. The more
+ * common source values are represented by shorter bit sequences.
+ *
+ * Each code tree is stored in a compressed form which is itself
+ * a Huffman encoding of the lengths of all the code strings (in
+ * ascending order by source values). The actual code strings are
+ * reconstructed from the lengths in the inflate process, as described
+ * in the deflate specification.
+ *
+ * REFERENCES
+ *
+ * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification".
+ * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc
+ *
+ * Storer, James A.
+ * Data Compression: Methods and Theory, pp. 49-50.
+ * Computer Science Press, 1988. ISBN 0-7167-8156-5.
+ *
+ * Sedgewick, R.
+ * Algorithms, p290.
+ * Addison-Wesley, 1983. ISBN 0-201-06672-6.
+ */
+
+/* @(#) $Id$ */
+
+/* #define GEN_TREES_H */
+
+#include "deflate.h"
+
+#ifdef DEBUG
+# include <ctype.h>
+#endif
+
+/* ===========================================================================
+ * Constants
+ */
+
+#define MAX_BL_BITS 7
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+#define END_BLOCK 256
+/* end of block literal code */
+
+#define REP_3_6 16
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+#define REPZ_3_10 17
+/* repeat a zero length 3-10 times (3 bits of repeat count) */
+
+#define REPZ_11_138 18
+/* repeat a zero length 11-138 times (7 bits of repeat count) */
+
+local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */
+ = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
+
+local const int extra_dbits[D_CODES] /* extra bits for each distance code */
+ = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */
+ = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7};
+
+local const uch bl_order[BL_CODES]
+ = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+#define DIST_CODE_LEN 512 /* see definition of array dist_code below */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+/* non ANSI compilers may not accept trees.h */
+
+local ct_data static_ltree[L_CODES+2];
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+local ct_data static_dtree[D_CODES];
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+uch _dist_code[DIST_CODE_LEN];
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+uch _length_code[MAX_MATCH-MIN_MATCH+1];
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+local int base_length[LENGTH_CODES];
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+local int base_dist[D_CODES];
+/* First normalized distance for each code (0 = distance of 1) */
+
+#else
+# include "trees.h"
+#endif /* GEN_TREES_H */
+
+struct static_tree_desc_s {
+ const ct_data *static_tree; /* static tree or NULL */
+ const intf *extra_bits; /* extra bits for each code or NULL */
+ int extra_base; /* base index for extra_bits */
+ int elems; /* max number of elements in the tree */
+ int max_length; /* max bit length for the codes */
+};
+
+local static_tree_desc static_l_desc =
+{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS};
+
+local static_tree_desc static_d_desc =
+{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS};
+
+local static_tree_desc static_bl_desc =
+{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS};
+
+/* ===========================================================================
+ * Local (static) routines in this file.
+ */
+
+local void tr_static_init OF((void));
+local void init_block OF((deflate_state *s));
+local void pqdownheap OF((deflate_state *s, ct_data *tree, int k));
+local void gen_bitlen OF((deflate_state *s, tree_desc *desc));
+local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count));
+local void build_tree OF((deflate_state *s, tree_desc *desc));
+local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code));
+local void send_tree OF((deflate_state *s, ct_data *tree, int max_code));
+local int build_bl_tree OF((deflate_state *s));
+local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes,
+ int blcodes));
+local void compress_block OF((deflate_state *s, ct_data *ltree,
+ ct_data *dtree));
+local int detect_data_type OF((deflate_state *s));
+local unsigned bi_reverse OF((unsigned value, int length));
+local void bi_windup OF((deflate_state *s));
+local void bi_flush OF((deflate_state *s));
+local void copy_block OF((deflate_state *s, charf *buf, unsigned len,
+ int header));
+
+#ifdef GEN_TREES_H
+local void gen_trees_header OF((void));
+#endif
+
+#ifndef DEBUG
+# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len)
+ /* Send a code of the given tree. c and tree must not have side effects */
+
+#else /* DEBUG */
+# define send_code(s, c, tree) \
+ { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \
+ send_bits(s, tree[c].Code, tree[c].Len); }
+#endif
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+#define put_short(s, w) { \
+ put_byte(s, (uch)((w) & 0xff)); \
+ put_byte(s, (uch)((ush)(w) >> 8)); \
+}
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+#ifdef DEBUG
+local void send_bits OF((deflate_state *s, int value, int length));
+
+local void send_bits(s, value, length)
+ deflate_state *s;
+ int value; /* value to send */
+ int length; /* number of bits */
+{
+ Tracevv((stderr," l %2d v %4x ", length, value));
+ Assert(length > 0 && length <= 15, "invalid length");
+ s->bits_sent += (ulg)length;
+
+ /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+ * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+ * unused bits in value.
+ */
+ if (s->bi_valid > (int)Buf_size - length) {
+ s->bi_buf |= (ush)value << s->bi_valid;
+ put_short(s, s->bi_buf);
+ s->bi_buf = (ush)value >> (Buf_size - s->bi_valid);
+ s->bi_valid += length - Buf_size;
+ } else {
+ s->bi_buf |= (ush)value << s->bi_valid;
+ s->bi_valid += length;
+ }
+}
+#else /* !DEBUG */
+
+#define send_bits(s, value, length) \
+{ int len = length;\
+ if (s->bi_valid > (int)Buf_size - len) {\
+ int val = value;\
+ s->bi_buf |= (ush)val << s->bi_valid;\
+ put_short(s, s->bi_buf);\
+ s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\
+ s->bi_valid += len - Buf_size;\
+ } else {\
+ s->bi_buf |= (ush)(value) << s->bi_valid;\
+ s->bi_valid += len;\
+ }\
+}
+#endif /* DEBUG */
+
+
+/* the arguments must not have side effects */
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+local void tr_static_init()
+{
+#if defined(GEN_TREES_H) || !defined(STDC)
+ static int static_init_done = 0;
+ int n; /* iterates over tree elements */
+ int bits; /* bit counter */
+ int length; /* length value */
+ int code; /* code value */
+ int dist; /* distance index */
+ ush bl_count[MAX_BITS+1];
+ /* number of codes at each bit length for an optimal tree */
+
+ if (static_init_done) return;
+
+ /* For some embedded targets, global variables are not initialized: */
+#ifdef NO_INIT_GLOBAL_POINTERS
+ static_l_desc.static_tree = static_ltree;
+ static_l_desc.extra_bits = extra_lbits;
+ static_d_desc.static_tree = static_dtree;
+ static_d_desc.extra_bits = extra_dbits;
+ static_bl_desc.extra_bits = extra_blbits;
+#endif
+
+ /* Initialize the mapping length (0..255) -> length code (0..28) */
+ length = 0;
+ for (code = 0; code < LENGTH_CODES-1; code++) {
+ base_length[code] = length;
+ for (n = 0; n < (1<<extra_lbits[code]); n++) {
+ _length_code[length++] = (uch)code;
+ }
+ }
+ Assert (length == 256, "tr_static_init: length != 256");
+ /* Note that the length 255 (match length 258) can be represented
+ * in two different ways: code 284 + 5 bits or code 285, so we
+ * overwrite length_code[255] to use the best encoding:
+ */
+ _length_code[length-1] = (uch)code;
+
+ /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+ dist = 0;
+ for (code = 0 ; code < 16; code++) {
+ base_dist[code] = dist;
+ for (n = 0; n < (1<<extra_dbits[code]); n++) {
+ _dist_code[dist++] = (uch)code;
+ }
+ }
+ Assert (dist == 256, "tr_static_init: dist != 256");
+ dist >>= 7; /* from now on, all distances are divided by 128 */
+ for ( ; code < D_CODES; code++) {
+ base_dist[code] = dist << 7;
+ for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) {
+ _dist_code[256 + dist++] = (uch)code;
+ }
+ }
+ Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+ /* Construct the codes of the static literal tree */
+ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0;
+ n = 0;
+ while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++;
+ while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++;
+ while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++;
+ while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++;
+ /* Codes 286 and 287 do not exist, but we must include them in the
+ * tree construction to get a canonical Huffman tree (longest code
+ * all ones)
+ */
+ gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count);
+
+ /* The static distance tree is trivial: */
+ for (n = 0; n < D_CODES; n++) {
+ static_dtree[n].Len = 5;
+ static_dtree[n].Code = bi_reverse((unsigned)n, 5);
+ }
+ static_init_done = 1;
+
+# ifdef GEN_TREES_H
+ gen_trees_header();
+# endif
+#endif /* defined(GEN_TREES_H) || !defined(STDC) */
+}
+
+/* ===========================================================================
+ * Genererate the file trees.h describing the static trees.
+ */
+#ifdef GEN_TREES_H
+# ifndef DEBUG
+# include <stdio.h>
+# endif
+
+# define SEPARATOR(i, last, width) \
+ ((i) == (last)? "\n};\n\n" : \
+ ((i) % (width) == (width)-1 ? ",\n" : ", "))
+
+void gen_trees_header()
+{
+ FILE *header = fopen("trees.h", "w");
+ int i;
+
+ Assert (header != NULL, "Can't open trees.h");
+ fprintf(header,
+ "/* header created automatically with -DGEN_TREES_H */\n\n");
+
+ fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n");
+ for (i = 0; i < L_CODES+2; i++) {
+ fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code,
+ static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5));
+ }
+
+ fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n");
+ for (i = 0; i < D_CODES; i++) {
+ fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code,
+ static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5));
+ }
+
+ fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n");
+ for (i = 0; i < DIST_CODE_LEN; i++) {
+ fprintf(header, "%2u%s", _dist_code[i],
+ SEPARATOR(i, DIST_CODE_LEN-1, 20));
+ }
+
+ fprintf(header,
+ "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n");
+ for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) {
+ fprintf(header, "%2u%s", _length_code[i],
+ SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20));
+ }
+
+ fprintf(header, "local const int base_length[LENGTH_CODES] = {\n");
+ for (i = 0; i < LENGTH_CODES; i++) {
+ fprintf(header, "%1u%s", base_length[i],
+ SEPARATOR(i, LENGTH_CODES-1, 20));
+ }
+
+ fprintf(header, "local const int base_dist[D_CODES] = {\n");
+ for (i = 0; i < D_CODES; i++) {
+ fprintf(header, "%5u%s", base_dist[i],
+ SEPARATOR(i, D_CODES-1, 10));
+ }
+
+ fclose(header);
+}
+#endif /* GEN_TREES_H */
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+void ZLIB_INTERNAL _tr_init(s)
+ deflate_state *s;
+{
+ tr_static_init();
+
+ s->l_desc.dyn_tree = s->dyn_ltree;
+ s->l_desc.stat_desc = &static_l_desc;
+
+ s->d_desc.dyn_tree = s->dyn_dtree;
+ s->d_desc.stat_desc = &static_d_desc;
+
+ s->bl_desc.dyn_tree = s->bl_tree;
+ s->bl_desc.stat_desc = &static_bl_desc;
+
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+#ifdef DEBUG
+ s->compressed_len = 0L;
+ s->bits_sent = 0L;
+#endif
+
+ /* Initialize the first block of the first file: */
+ init_block(s);
+}
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+local void init_block(s)
+ deflate_state *s;
+{
+ int n; /* iterates over tree elements */
+
+ /* Initialize the trees. */
+ for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0;
+ for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0;
+ for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0;
+
+ s->dyn_ltree[END_BLOCK].Freq = 1;
+ s->opt_len = s->static_len = 0L;
+ s->last_lit = s->matches = 0;
+}
+
+#define SMALLEST 1
+/* Index within the heap array of least frequent node in the Huffman tree */
+
+
+/* ===========================================================================
+ * Remove the smallest element from the heap and recreate the heap with
+ * one less element. Updates heap and heap_len.
+ */
+#define pqremove(s, tree, top) \
+{\
+ top = s->heap[SMALLEST]; \
+ s->heap[SMALLEST] = s->heap[s->heap_len--]; \
+ pqdownheap(s, tree, SMALLEST); \
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+#define smaller(tree, n, m, depth) \
+ (tree[n].Freq < tree[m].Freq || \
+ (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m]))
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+local void pqdownheap(s, tree, k)
+ deflate_state *s;
+ ct_data *tree; /* the tree to restore */
+ int k; /* node to move down */
+{
+ int v = s->heap[k];
+ int j = k << 1; /* left son of k */
+ while (j <= s->heap_len) {
+ /* Set j to the smallest of the two sons: */
+ if (j < s->heap_len &&
+ smaller(tree, s->heap[j+1], s->heap[j], s->depth)) {
+ j++;
+ }
+ /* Exit if v is smaller than both sons */
+ if (smaller(tree, v, s->heap[j], s->depth)) break;
+
+ /* Exchange v with the smallest son */
+ s->heap[k] = s->heap[j]; k = j;
+
+ /* And continue down the tree, setting j to the left son of k */
+ j <<= 1;
+ }
+ s->heap[k] = v;
+}
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ * above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ * array bl_count contains the frequencies for each bit length.
+ * The length opt_len is updated; static_len is also updated if stree is
+ * not null.
+ */
+local void gen_bitlen(s, desc)
+ deflate_state *s;
+ tree_desc *desc; /* the tree descriptor */
+{
+ ct_data *tree = desc->dyn_tree;
+ int max_code = desc->max_code;
+ const ct_data *stree = desc->stat_desc->static_tree;
+ const intf *extra = desc->stat_desc->extra_bits;
+ int base = desc->stat_desc->extra_base;
+ int max_length = desc->stat_desc->max_length;
+ int h; /* heap index */
+ int n, m; /* iterate over the tree elements */
+ int bits; /* bit length */
+ int xbits; /* extra bits */
+ ush f; /* frequency */
+ int overflow = 0; /* number of elements with bit length too large */
+
+ for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0;
+
+ /* In a first pass, compute the optimal bit lengths (which may
+ * overflow in the case of the bit length tree).
+ */
+ tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */
+
+ for (h = s->heap_max+1; h < HEAP_SIZE; h++) {
+ n = s->heap[h];
+ bits = tree[tree[n].Dad].Len + 1;
+ if (bits > max_length) bits = max_length, overflow++;
+ tree[n].Len = (ush)bits;
+ /* We overwrite tree[n].Dad which is no longer needed */
+
+ if (n > max_code) continue; /* not a leaf node */
+
+ s->bl_count[bits]++;
+ xbits = 0;
+ if (n >= base) xbits = extra[n-base];
+ f = tree[n].Freq;
+ s->opt_len += (ulg)f * (bits + xbits);
+ if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits);
+ }
+ if (overflow == 0) return;
+
+ Trace((stderr,"\nbit length overflow\n"));
+ /* This happens for example on obj2 and pic of the Calgary corpus */
+
+ /* Find the first bit length which could increase: */
+ do {
+ bits = max_length-1;
+ while (s->bl_count[bits] == 0) bits--;
+ s->bl_count[bits]--; /* move one leaf down the tree */
+ s->bl_count[bits+1] += 2; /* move one overflow item as its brother */
+ s->bl_count[max_length]--;
+ /* The brother of the overflow item also moves one step up,
+ * but this does not affect bl_count[max_length]
+ */
+ overflow -= 2;
+ } while (overflow > 0);
+
+ /* Now recompute all bit lengths, scanning in increasing frequency.
+ * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+ * lengths instead of fixing only the wrong ones. This idea is taken
+ * from 'ar' written by Haruhiko Okumura.)
+ */
+ for (bits = max_length; bits != 0; bits--) {
+ n = s->bl_count[bits];
+ while (n != 0) {
+ m = s->heap[--h];
+ if (m > max_code) continue;
+ if ((unsigned) tree[m].Len != (unsigned) bits) {
+ Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+ s->opt_len += ((long)bits - (long)tree[m].Len)
+ *(long)tree[m].Freq;
+ tree[m].Len = (ush)bits;
+ }
+ n--;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ * zero code length.
+ */
+local void gen_codes (tree, max_code, bl_count)
+ ct_data *tree; /* the tree to decorate */
+ int max_code; /* largest code with non zero frequency */
+ ushf *bl_count; /* number of codes at each bit length */
+{
+ ush next_code[MAX_BITS+1]; /* next code value for each bit length */
+ ush code = 0; /* running code value */
+ int bits; /* bit index */
+ int n; /* code index */
+
+ /* The distribution counts are first used to generate the code values
+ * without bit reversal.
+ */
+ for (bits = 1; bits <= MAX_BITS; bits++) {
+ next_code[bits] = code = (code + bl_count[bits-1]) << 1;
+ }
+ /* Check that the bit counts in bl_count are consistent. The last code
+ * must be all ones.
+ */
+ Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+ "inconsistent bit counts");
+ Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+ for (n = 0; n <= max_code; n++) {
+ int len = tree[n].Len;
+ if (len == 0) continue;
+ /* Now reverse the bits */
+ tree[n].Code = bi_reverse(next_code[len]++, len);
+
+ Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+ n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+ }
+}
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ * and corresponding code. The length opt_len is updated; static_len is
+ * also updated if stree is not null. The field max_code is set.
+ */
+local void build_tree(s, desc)
+ deflate_state *s;
+ tree_desc *desc; /* the tree descriptor */
+{
+ ct_data *tree = desc->dyn_tree;
+ const ct_data *stree = desc->stat_desc->static_tree;
+ int elems = desc->stat_desc->elems;
+ int n, m; /* iterate over heap elements */
+ int max_code = -1; /* largest code with non zero frequency */
+ int node; /* new node being created */
+
+ /* Construct the initial heap, with least frequent element in
+ * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ * heap[0] is not used.
+ */
+ s->heap_len = 0, s->heap_max = HEAP_SIZE;
+
+ for (n = 0; n < elems; n++) {
+ if (tree[n].Freq != 0) {
+ s->heap[++(s->heap_len)] = max_code = n;
+ s->depth[n] = 0;
+ } else {
+ tree[n].Len = 0;
+ }
+ }
+
+ /* The pkzip format requires that at least one distance code exists,
+ * and that at least one bit should be sent even if there is only one
+ * possible code. So to avoid special checks later on we force at least
+ * two codes of non zero frequency.
+ */
+ while (s->heap_len < 2) {
+ node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0);
+ tree[node].Freq = 1;
+ s->depth[node] = 0;
+ s->opt_len--; if (stree) s->static_len -= stree[node].Len;
+ /* node is 0 or 1 so it does not have extra bits */
+ }
+ desc->max_code = max_code;
+
+ /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ * establish sub-heaps of increasing lengths:
+ */
+ for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n);
+
+ /* Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ node = elems; /* next internal node of the tree */
+ do {
+ pqremove(s, tree, n); /* n = node of least frequency */
+ m = s->heap[SMALLEST]; /* m = node of next least frequency */
+
+ s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */
+ s->heap[--(s->heap_max)] = m;
+
+ /* Create a new node father of n and m */
+ tree[node].Freq = tree[n].Freq + tree[m].Freq;
+ s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ?
+ s->depth[n] : s->depth[m]) + 1);
+ tree[n].Dad = tree[m].Dad = (ush)node;
+#ifdef DUMP_BL_TREE
+ if (tree == s->bl_tree) {
+ fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)",
+ node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq);
+ }
+#endif
+ /* and insert the new node in the heap */
+ s->heap[SMALLEST] = node++;
+ pqdownheap(s, tree, SMALLEST);
+
+ } while (s->heap_len >= 2);
+
+ s->heap[--(s->heap_max)] = s->heap[SMALLEST];
+
+ /* At this point, the fields freq and dad are set. We can now
+ * generate the bit lengths.
+ */
+ gen_bitlen(s, (tree_desc *)desc);
+
+ /* The field len is now set, we can generate the bit codes */
+ gen_codes ((ct_data *)tree, max_code, s->bl_count);
+}
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+local void scan_tree (s, tree, max_code)
+ deflate_state *s;
+ ct_data *tree; /* the tree to be scanned */
+ int max_code; /* and its largest code of non zero frequency */
+{
+ int n; /* iterates over all tree elements */
+ int prevlen = -1; /* last emitted length */
+ int curlen; /* length of current code */
+ int nextlen = tree[0].Len; /* length of next code */
+ int count = 0; /* repeat count of the current code */
+ int max_count = 7; /* max repeat count */
+ int min_count = 4; /* min repeat count */
+
+ if (nextlen == 0) max_count = 138, min_count = 3;
+ tree[max_code+1].Len = (ush)0xffff; /* guard */
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen; nextlen = tree[n+1].Len;
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ s->bl_tree[curlen].Freq += count;
+ } else if (curlen != 0) {
+ if (curlen != prevlen) s->bl_tree[curlen].Freq++;
+ s->bl_tree[REP_3_6].Freq++;
+ } else if (count <= 10) {
+ s->bl_tree[REPZ_3_10].Freq++;
+ } else {
+ s->bl_tree[REPZ_11_138].Freq++;
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0) {
+ max_count = 138, min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6, min_count = 3;
+ } else {
+ max_count = 7, min_count = 4;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+local void send_tree (s, tree, max_code)
+ deflate_state *s;
+ ct_data *tree; /* the tree to be scanned */
+ int max_code; /* and its largest code of non zero frequency */
+{
+ int n; /* iterates over all tree elements */
+ int prevlen = -1; /* last emitted length */
+ int curlen; /* length of current code */
+ int nextlen = tree[0].Len; /* length of next code */
+ int count = 0; /* repeat count of the current code */
+ int max_count = 7; /* max repeat count */
+ int min_count = 4; /* min repeat count */
+
+ /* tree[max_code+1].Len = -1; */ /* guard already set */
+ if (nextlen == 0) max_count = 138, min_count = 3;
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen; nextlen = tree[n+1].Len;
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ do { send_code(s, curlen, s->bl_tree); } while (--count != 0);
+
+ } else if (curlen != 0) {
+ if (curlen != prevlen) {
+ send_code(s, curlen, s->bl_tree); count--;
+ }
+ Assert(count >= 3 && count <= 6, " 3_6?");
+ send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2);
+
+ } else if (count <= 10) {
+ send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3);
+
+ } else {
+ send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7);
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0) {
+ max_count = 138, min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6, min_count = 3;
+ } else {
+ max_count = 7, min_count = 4;
+ }
+ }
+}
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+local int build_bl_tree(s)
+ deflate_state *s;
+{
+ int max_blindex; /* index of last bit length code of non zero freq */
+
+ /* Determine the bit length frequencies for literal and distance trees */
+ scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code);
+ scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code);
+
+ /* Build the bit length tree: */
+ build_tree(s, (tree_desc *)(&(s->bl_desc)));
+ /* opt_len now includes the length of the tree representations, except
+ * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+ */
+
+ /* Determine the number of bit length codes to send. The pkzip format
+ * requires that at least 4 bit length codes be sent. (appnote.txt says
+ * 3 but the actual value used is 4.)
+ */
+ for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) {
+ if (s->bl_tree[bl_order[max_blindex]].Len != 0) break;
+ }
+ /* Update opt_len to include the bit length tree and counts */
+ s->opt_len += 3*(max_blindex+1) + 5+5+4;
+ Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+ s->opt_len, s->static_len));
+
+ return max_blindex;
+}
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+local void send_all_trees(s, lcodes, dcodes, blcodes)
+ deflate_state *s;
+ int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+ int rank; /* index in bl_order */
+
+ Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+ Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+ "too many codes");
+ Tracev((stderr, "\nbl counts: "));
+ send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */
+ send_bits(s, dcodes-1, 5);
+ send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */
+ for (rank = 0; rank < blcodes; rank++) {
+ Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+ send_bits(s, s->bl_tree[bl_order[rank]].Len, 3);
+ }
+ Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+ send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */
+ Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+ send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */
+ Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+/* ===========================================================================
+ * Send a stored block
+ */
+void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
+ deflate_state *s;
+ charf *buf; /* input block */
+ ulg stored_len; /* length of input block */
+ int last; /* one if this is the last block for a file */
+{
+ send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
+#ifdef DEBUG
+ s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L;
+ s->compressed_len += (stored_len + 4) << 3;
+#endif
+ copy_block(s, buf, (unsigned)stored_len, 1); /* with header */
+}
+
+/* ===========================================================================
+ * Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
+ */
+void ZLIB_INTERNAL _tr_flush_bits(s)
+ deflate_state *s;
+{
+ bi_flush(s);
+}
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+void ZLIB_INTERNAL _tr_align(s)
+ deflate_state *s;
+{
+ send_bits(s, STATIC_TREES<<1, 3);
+ send_code(s, END_BLOCK, static_ltree);
+#ifdef DEBUG
+ s->compressed_len += 10L; /* 3 for block type, 7 for EOB */
+#endif
+ bi_flush(s);
+}
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
+ deflate_state *s;
+ charf *buf; /* input block, or NULL if too old */
+ ulg stored_len; /* length of input block */
+ int last; /* one if this is the last block for a file */
+{
+ ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+ int max_blindex = 0; /* index of last bit length code of non zero freq */
+
+ /* Build the Huffman trees unless a stored block is forced */
+ if (s->level > 0) {
+
+ /* Check if the file is binary or text */
+ if (s->strm->data_type == Z_UNKNOWN)
+ s->strm->data_type = detect_data_type(s);
+
+ /* Construct the literal and distance trees */
+ build_tree(s, (tree_desc *)(&(s->l_desc)));
+ Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+ s->static_len));
+
+ build_tree(s, (tree_desc *)(&(s->d_desc)));
+ Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+ s->static_len));
+ /* At this point, opt_len and static_len are the total bit lengths of
+ * the compressed block data, excluding the tree representations.
+ */
+
+ /* Build the bit length tree for the above two trees, and get the index
+ * in bl_order of the last bit length code to send.
+ */
+ max_blindex = build_bl_tree(s);
+
+ /* Determine the best encoding. Compute the block lengths in bytes. */
+ opt_lenb = (s->opt_len+3+7)>>3;
+ static_lenb = (s->static_len+3+7)>>3;
+
+ Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+ opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+ s->last_lit));
+
+ if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+
+ } else {
+ Assert(buf != (char*)0, "lost buf");
+ opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+ }
+
+#ifdef FORCE_STORED
+ if (buf != (char*)0) { /* force stored block */
+#else
+ if (stored_len+4 <= opt_lenb && buf != (char*)0) {
+ /* 4: two words for the lengths */
+#endif
+ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ * Otherwise we can't have processed more than WSIZE input bytes since
+ * the last block flush, because compression would have been
+ * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ * transform a block into a stored block.
+ */
+ _tr_stored_block(s, buf, stored_len, last);
+
+#ifdef FORCE_STATIC
+ } else if (static_lenb >= 0) { /* force static trees */
+#else
+ } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) {
+#endif
+ send_bits(s, (STATIC_TREES<<1)+last, 3);
+ compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree);
+#ifdef DEBUG
+ s->compressed_len += 3 + s->static_len;
+#endif
+ } else {
+ send_bits(s, (DYN_TREES<<1)+last, 3);
+ send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1,
+ max_blindex+1);
+ compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree);
+#ifdef DEBUG
+ s->compressed_len += 3 + s->opt_len;
+#endif
+ }
+ Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+ /* The above check is made mod 2^32, for files larger than 512 MB
+ * and uLong implemented on 32 bits.
+ */
+ init_block(s);
+
+ if (last) {
+ bi_windup(s);
+#ifdef DEBUG
+ s->compressed_len += 7; /* align on byte boundary */
+#endif
+ }
+ Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+ s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+int ZLIB_INTERNAL _tr_tally (s, dist, lc)
+ deflate_state *s;
+ unsigned dist; /* distance of matched string */
+ unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+ s->d_buf[s->last_lit] = (ush)dist;
+ s->l_buf[s->last_lit++] = (uch)lc;
+ if (dist == 0) {
+ /* lc is the unmatched char */
+ s->dyn_ltree[lc].Freq++;
+ } else {
+ s->matches++;
+ /* Here, lc is the match length - MIN_MATCH */
+ dist--; /* dist = match distance - 1 */
+ Assert((ush)dist < (ush)MAX_DIST(s) &&
+ (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+ (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match");
+
+ s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
+ s->dyn_dtree[d_code(dist)].Freq++;
+ }
+
+#ifdef TRUNCATE_BLOCK
+ /* Try to guess if it is profitable to stop the current block here */
+ if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
+ /* Compute an upper bound for the compressed length */
+ ulg out_length = (ulg)s->last_lit*8L;
+ ulg in_length = (ulg)((long)s->strstart - s->block_start);
+ int dcode;
+ for (dcode = 0; dcode < D_CODES; dcode++) {
+ out_length += (ulg)s->dyn_dtree[dcode].Freq *
+ (5L+extra_dbits[dcode]);
+ }
+ out_length >>= 3;
+ Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+ s->last_lit, in_length, out_length,
+ 100L - out_length*100L/in_length));
+ if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
+ }
+#endif
+ return (s->last_lit == s->lit_bufsize-1);
+ /* We avoid equality with lit_bufsize because of wraparound at 64K
+ * on 16 bit machines and because stored blocks are restricted to
+ * 64K-1 bytes.
+ */
+}
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+local void compress_block(s, ltree, dtree)
+ deflate_state *s;
+ ct_data *ltree; /* literal tree */
+ ct_data *dtree; /* distance tree */
+{
+ unsigned dist; /* distance of matched string */
+ int lc; /* match length or unmatched char (if dist == 0) */
+ unsigned lx = 0; /* running index in l_buf */
+ unsigned code; /* the code to send */
+ int extra; /* number of extra bits to send */
+
+ if (s->last_lit != 0) do {
+ dist = s->d_buf[lx];
+ lc = s->l_buf[lx++];
+ if (dist == 0) {
+ send_code(s, lc, ltree); /* send a literal byte */
+ Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+ } else {
+ /* Here, lc is the match length - MIN_MATCH */
+ code = _length_code[lc];
+ send_code(s, code+LITERALS+1, ltree); /* send the length code */
+ extra = extra_lbits[code];
+ if (extra != 0) {
+ lc -= base_length[code];
+ send_bits(s, lc, extra); /* send the extra length bits */
+ }
+ dist--; /* dist is now the match distance - 1 */
+ code = d_code(dist);
+ Assert (code < D_CODES, "bad d_code");
+
+ send_code(s, code, dtree); /* send the distance code */
+ extra = extra_dbits[code];
+ if (extra != 0) {
+ dist -= base_dist[code];
+ send_bits(s, dist, extra); /* send the extra distance bits */
+ }
+ } /* literal or match pair ? */
+
+ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+ Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+ "pendingBuf overflow");
+
+ } while (lx < s->last_lit);
+
+ send_code(s, END_BLOCK, ltree);
+}
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ * a) There are no non-portable control characters belonging to the
+ * "black list" (0..6, 14..25, 28..31).
+ * b) There is at least one printable character belonging to the
+ * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ * "gray list" that is ignored in this detection algorithm:
+ * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+local int detect_data_type(s)
+ deflate_state *s;
+{
+ /* black_mask is the bit mask of black-listed bytes
+ * set bits 0..6, 14..25, and 28..31
+ * 0xf3ffc07f = binary 11110011111111111100000001111111
+ */
+ unsigned long black_mask = 0xf3ffc07fUL;
+ int n;
+
+ /* Check for non-textual ("black-listed") bytes. */
+ for (n = 0; n <= 31; n++, black_mask >>= 1)
+ if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0))
+ return Z_BINARY;
+
+ /* Check for textual ("white-listed") bytes. */
+ if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0
+ || s->dyn_ltree[13].Freq != 0)
+ return Z_TEXT;
+ for (n = 32; n < LITERALS; n++)
+ if (s->dyn_ltree[n].Freq != 0)
+ return Z_TEXT;
+
+ /* There are no "black-listed" or "white-listed" bytes:
+ * this stream either is empty or has tolerated ("gray-listed") bytes only.
+ */
+ return Z_BINARY;
+}
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+local unsigned bi_reverse(code, len)
+ unsigned code; /* the value to invert */
+ int len; /* its bit length */
+{
+ register unsigned res = 0;
+ do {
+ res |= code & 1;
+ code >>= 1, res <<= 1;
+ } while (--len > 0);
+ return res >> 1;
+}
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+local void bi_flush(s)
+ deflate_state *s;
+{
+ if (s->bi_valid == 16) {
+ put_short(s, s->bi_buf);
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+ } else if (s->bi_valid >= 8) {
+ put_byte(s, (Byte)s->bi_buf);
+ s->bi_buf >>= 8;
+ s->bi_valid -= 8;
+ }
+}
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+local void bi_windup(s)
+ deflate_state *s;
+{
+ if (s->bi_valid > 8) {
+ put_short(s, s->bi_buf);
+ } else if (s->bi_valid > 0) {
+ put_byte(s, (Byte)s->bi_buf);
+ }
+ s->bi_buf = 0;
+ s->bi_valid = 0;
+#ifdef DEBUG
+ s->bits_sent = (s->bits_sent+7) & ~7;
+#endif
+}
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+local void copy_block(s, buf, len, header)
+ deflate_state *s;
+ charf *buf; /* the input data */
+ unsigned len; /* its length */
+ int header; /* true if block header must be written */
+{
+ bi_windup(s); /* align on byte boundary */
+
+ if (header) {
+ put_short(s, (ush)len);
+ put_short(s, (ush)~len);
+#ifdef DEBUG
+ s->bits_sent += 2*16;
+#endif
+ }
+#ifdef DEBUG
+ s->bits_sent += (ulg)len<<3;
+#endif
+ while (len--) {
+ put_byte(s, *buf++);
+ }
+}
diff --git a/src/cpp/core/zlib/trees.h b/src/cpp/core/zlib/trees.h
new file mode 100644
index 0000000..d35639d
--- /dev/null
+++ b/src/cpp/core/zlib/trees.h
@@ -0,0 +1,128 @@
+/* header created automatically with -DGEN_TREES_H */
+
+local const ct_data static_ltree[L_CODES+2] = {
+{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}},
+{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}},
+{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}},
+{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}},
+{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}},
+{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}},
+{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}},
+{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}},
+{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}},
+{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}},
+{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}},
+{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}},
+{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}},
+{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}},
+{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}},
+{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}},
+{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}},
+{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}},
+{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}},
+{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}},
+{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}},
+{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}},
+{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}},
+{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}},
+{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}},
+{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}},
+{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}},
+{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}},
+{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}},
+{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}},
+{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}},
+{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}},
+{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}},
+{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}},
+{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}},
+{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}},
+{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}},
+{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}},
+{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}},
+{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}},
+{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}},
+{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}},
+{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}},
+{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}},
+{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}},
+{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}},
+{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}},
+{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}},
+{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}},
+{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}},
+{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}},
+{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}},
+{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}},
+{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}},
+{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}},
+{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}},
+{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}},
+{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}}
+};
+
+local const ct_data static_dtree[D_CODES] = {
+{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}},
+{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}},
+{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}},
+{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}},
+{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}},
+{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}}
+};
+
+const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {
+ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
+ 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10,
+10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17,
+18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+};
+
+const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
+13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
+17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
+19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
+22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
+};
+
+local const int base_length[LENGTH_CODES] = {
+0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
+64, 80, 96, 112, 128, 160, 192, 224, 0
+};
+
+local const int base_dist[D_CODES] = {
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24,
+ 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
+ 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
+};
+
diff --git a/src/cpp/core/zlib/uncompr.c b/src/cpp/core/zlib/uncompr.c
new file mode 100644
index 0000000..ad98be3
--- /dev/null
+++ b/src/cpp/core/zlib/uncompr.c
@@ -0,0 +1,59 @@
+/* uncompr.c -- decompress a memory buffer
+ * Copyright (C) 1995-2003, 2010 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+ Decompresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total
+ size of the destination buffer, which must be large enough to hold the
+ entire uncompressed data. (The size of the uncompressed data must have
+ been saved previously by the compressor and transmitted to the decompressor
+ by some mechanism outside the scope of this compression library.)
+ Upon exit, destLen is the actual size of the compressed buffer.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer, or Z_DATA_ERROR if the input data was corrupted.
+*/
+int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+ /* Check for source > 64K on 16-bit machine: */
+ if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
+
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+
+ err = inflateInit(&stream);
+ if (err != Z_OK) return err;
+
+ err = inflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ inflateEnd(&stream);
+ if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
+ return Z_DATA_ERROR;
+ return err;
+ }
+ *destLen = stream.total_out;
+
+ err = inflateEnd(&stream);
+ return err;
+}
diff --git a/src/cpp/core/zlib/zconf.h b/src/cpp/core/zlib/zconf.h
new file mode 100644
index 0000000..4fdf9d9
--- /dev/null
+++ b/src/cpp/core/zlib/zconf.h
@@ -0,0 +1,468 @@
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-2011 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZCONF_H
+#define ZCONF_H
+/* #undef Z_PREFIX */
+#define Z_HAVE_UNISTD_H
+
+/*
+ * If you *really* need a unique prefix for all types and library functions,
+ * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
+ * Even better than compiling with -DZ_PREFIX would be to use configure to set
+ * this permanently in zconf.h using "./configure --zprefix".
+ */
+#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
+# define Z_PREFIX_SET
+
+/* all linked symbols */
+# define _dist_code z__dist_code
+# define _length_code z__length_code
+# define _tr_align z__tr_align
+# define _tr_flush_block z__tr_flush_block
+# define _tr_init z__tr_init
+# define _tr_stored_block z__tr_stored_block
+# define _tr_tally z__tr_tally
+# define adler32 z_adler32
+# define adler32_combine z_adler32_combine
+# define adler32_combine64 z_adler32_combine64
+# ifndef Z_SOLO
+# define compress z_compress
+# define compress2 z_compress2
+# define compressBound z_compressBound
+# endif
+# define crc32 z_crc32
+# define crc32_combine z_crc32_combine
+# define crc32_combine64 z_crc32_combine64
+# define deflate z_deflate
+# define deflateBound z_deflateBound
+# define deflateCopy z_deflateCopy
+# define deflateEnd z_deflateEnd
+# define deflateInit2_ z_deflateInit2_
+# define deflateInit_ z_deflateInit_
+# define deflateParams z_deflateParams
+# define deflatePending z_deflatePending
+# define deflatePrime z_deflatePrime
+# define deflateReset z_deflateReset
+# define deflateResetKeep z_deflateResetKeep
+# define deflateSetDictionary z_deflateSetDictionary
+# define deflateSetHeader z_deflateSetHeader
+# define deflateTune z_deflateTune
+# define deflate_copyright z_deflate_copyright
+# define get_crc_table z_get_crc_table
+# ifndef Z_SOLO
+# define gz_error z_gz_error
+# define gz_intmax z_gz_intmax
+# define gz_strwinerror z_gz_strwinerror
+# define gzbuffer z_gzbuffer
+# define gzclearerr z_gzclearerr
+# define gzclose z_gzclose
+# define gzclose_r z_gzclose_r
+# define gzclose_w z_gzclose_w
+# define gzdirect z_gzdirect
+# define gzdopen z_gzdopen
+# define gzeof z_gzeof
+# define gzerror z_gzerror
+# define gzflags z_gzflags
+# define gzflush z_gzflush
+# define gzgetc z_gzgetc
+# define gzgetc_ z_gzgetc_
+# define gzgets z_gzgets
+# define gzoffset z_gzoffset
+# define gzoffset64 z_gzoffset64
+# define gzopen z_gzopen
+# define gzopen64 z_gzopen64
+# define gzprintf z_gzprintf
+# define gzputc z_gzputc
+# define gzputs z_gzputs
+# define gzread z_gzread
+# define gzrewind z_gzrewind
+# define gzseek z_gzseek
+# define gzseek64 z_gzseek64
+# define gzsetparams z_gzsetparams
+# define gztell z_gztell
+# define gztell64 z_gztell64
+# define gzungetc z_gzungetc
+# define gzwrite z_gzwrite
+# endif
+# define inflate z_inflate
+# define inflateBack z_inflateBack
+# define inflateBackEnd z_inflateBackEnd
+# define inflateBackInit_ z_inflateBackInit_
+# define inflateCopy z_inflateCopy
+# define inflateEnd z_inflateEnd
+# define inflateGetHeader z_inflateGetHeader
+# define inflateInit2_ z_inflateInit2_
+# define inflateInit_ z_inflateInit_
+# define inflateMark z_inflateMark
+# define inflatePrime z_inflatePrime
+# define inflateReset z_inflateReset
+# define inflateReset2 z_inflateReset2
+# define inflateSetDictionary z_inflateSetDictionary
+# define inflateSync z_inflateSync
+# define inflateSyncPoint z_inflateSyncPoint
+# define inflateUndermine z_inflateUndermine
+# define inflateResetKeep z_inflateResetKeep
+# define inflate_copyright z_inflate_copyright
+# define inflate_fast z_inflate_fast
+# define inflate_table z_inflate_table
+# ifndef Z_SOLO
+# define uncompress z_uncompress
+# endif
+# define zError z_zError
+# ifndef Z_SOLO
+# define zcalloc z_zcalloc
+# define zcfree z_zcfree
+# endif
+# define zlibCompileFlags z_zlibCompileFlags
+# define zlibVersion z_zlibVersion
+
+/* all zlib typedefs in zlib.h and zconf.h */
+# define Byte z_Byte
+# define Bytef z_Bytef
+# define alloc_func z_alloc_func
+# define charf z_charf
+# define free_func z_free_func
+# ifndef Z_SOLO
+# define gzFile z_gzFile
+# define gz_header z_gz_header
+# define gz_headerp z_gz_headerp
+# endif
+# define in_func z_in_func
+# define intf z_intf
+# define out_func z_out_func
+# define uInt z_uInt
+# define uIntf z_uIntf
+# define uLong z_uLong
+# define uLongf z_uLongf
+# define voidp z_voidp
+# define voidpc z_voidpc
+# define voidpf z_voidpf
+
+/* all zlib structs in zlib.h and zconf.h */
+# ifndef Z_SOLO
+# define gz_header_s z_gz_header_s
+# endif
+# define internal_state z_internal_state
+
+#endif
+
+#if defined(__MSDOS__) && !defined(MSDOS)
+# define MSDOS
+#endif
+#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
+# define OS2
+#endif
+#if defined(_WINDOWS) && !defined(WINDOWS)
+# define WINDOWS
+#endif
+#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
+# ifndef WIN32
+# define WIN32
+# endif
+#endif
+#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
+# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
+# ifndef SYS16BIT
+# define SYS16BIT
+# endif
+# endif
+#endif
+
+/*
+ * Compile with -DMAXSEG_64K if the alloc function cannot allocate more
+ * than 64k bytes at a time (needed on systems with 16-bit int).
+ */
+#ifdef SYS16BIT
+# define MAXSEG_64K
+#endif
+#ifdef MSDOS
+# define UNALIGNED_OK
+#endif
+
+#ifdef __STDC_VERSION__
+# ifndef STDC
+# define STDC
+# endif
+# if __STDC_VERSION__ >= 199901L
+# ifndef STDC99
+# define STDC99
+# endif
+# endif
+#endif
+#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
+# define STDC
+#endif
+
+#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
+# define STDC
+#endif
+
+#ifndef STDC
+# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
+# define const /* note: need a more gentle solution here */
+# endif
+#endif
+
+#if defined(ZLIB_CONST) && !defined(z_const)
+# define z_const const
+#else
+# define z_const
+#endif
+
+/* Some Mac compilers merge all .h files incorrectly: */
+#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
+# define NO_DUMMY_DECL
+#endif
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+# ifdef MAXSEG_64K
+# define MAX_MEM_LEVEL 8
+# else
+# define MAX_MEM_LEVEL 9
+# endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+# define MAX_WBITS 15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+ (1 << (windowBits+2)) + (1 << (memLevel+9))
+ that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+ make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+ The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ for small objects.
+*/
+
+ /* Type declarations */
+
+#ifndef OF /* function prototypes */
+# ifdef STDC
+# define OF(args) args
+# else
+# define OF(args) ()
+# endif
+#endif
+
+#ifndef Z_ARG /* function prototypes for stdarg */
+# if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# define Z_ARG(args) args
+# else
+# define Z_ARG(args) ()
+# endif
+#endif
+
+/* The following definitions for FAR are needed only for MSDOS mixed
+ * model programming (small or medium model with some far allocations).
+ * This was tested only with MSC; for other MSDOS compilers you may have
+ * to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
+ * just define FAR to be empty.
+ */
+#ifdef SYS16BIT
+# if defined(M_I86SM) || defined(M_I86MM)
+ /* MSC small or medium model */
+# define SMALL_MEDIUM
+# ifdef _MSC_VER
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+# if (defined(__SMALL__) || defined(__MEDIUM__))
+ /* Turbo C small or medium model */
+# define SMALL_MEDIUM
+# ifdef __BORLANDC__
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+#endif
+
+#if defined(WINDOWS) || defined(WIN32)
+ /* If building or using zlib as a DLL, define ZLIB_DLL.
+ * This is not mandatory, but it offers a little performance increase.
+ */
+# ifdef ZLIB_DLL
+# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
+# ifdef ZLIB_INTERNAL
+# define ZEXTERN extern __declspec(dllexport)
+# else
+# define ZEXTERN extern __declspec(dllimport)
+# endif
+# endif
+# endif /* ZLIB_DLL */
+ /* If building or using zlib with the WINAPI/WINAPIV calling convention,
+ * define ZLIB_WINAPI.
+ * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
+ */
+# ifdef ZLIB_WINAPI
+# ifdef FAR
+# undef FAR
+# endif
+# include <windows.h>
+ /* No need for _export, use ZLIB.DEF instead. */
+ /* For complete Windows compatibility, use WINAPI, not __stdcall. */
+# define ZEXPORT WINAPI
+# ifdef WIN32
+# define ZEXPORTVA WINAPIV
+# else
+# define ZEXPORTVA FAR CDECL
+# endif
+# endif
+#endif
+
+#if defined (__BEOS__)
+# ifdef ZLIB_DLL
+# ifdef ZLIB_INTERNAL
+# define ZEXPORT __declspec(dllexport)
+# define ZEXPORTVA __declspec(dllexport)
+# else
+# define ZEXPORT __declspec(dllimport)
+# define ZEXPORTVA __declspec(dllimport)
+# endif
+# endif
+#endif
+
+#ifndef ZEXTERN
+# define ZEXTERN extern
+#endif
+#ifndef ZEXPORT
+# define ZEXPORT
+#endif
+#ifndef ZEXPORTVA
+# define ZEXPORTVA
+#endif
+
+#ifndef FAR
+# define FAR
+#endif
+
+#if !defined(__MACTYPES__)
+typedef unsigned char Byte; /* 8 bits */
+#endif
+typedef unsigned int uInt; /* 16 bits or more */
+typedef unsigned long uLong; /* 32 bits or more */
+
+#ifdef SMALL_MEDIUM
+ /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
+# define Bytef Byte FAR
+#else
+ typedef Byte FAR Bytef;
+#endif
+typedef char FAR charf;
+typedef int FAR intf;
+typedef uInt FAR uIntf;
+typedef uLong FAR uLongf;
+
+#ifdef STDC
+ typedef void const *voidpc;
+ typedef void FAR *voidpf;
+ typedef void *voidp;
+#else
+ typedef Byte const *voidpc;
+ typedef Byte FAR *voidpf;
+ typedef Byte *voidp;
+#endif
+
+#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_UNISTD_H
+#endif
+
+#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_STDARG_H
+#endif
+
+#ifdef STDC
+# ifndef Z_SOLO
+# include <sys/types.h> /* for off_t */
+# endif
+#endif
+
+/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
+ * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
+ * though the former does not conform to the LFS document), but considering
+ * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
+ * equivalently requesting no 64-bit operations
+ */
+#if -_LARGEFILE64_SOURCE - -1 == 1
+# undef _LARGEFILE64_SOURCE
+#endif
+
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+# define Z_LARGE
+#endif
+
+#if (defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE)) && !defined(Z_SOLO)
+# include <unistd.h> /* for SEEK_* and off_t */
+# ifdef VMS
+# include <unixio.h> /* for off_t */
+# endif
+# ifndef z_off_t
+# define z_off_t off_t
+# endif
+#endif
+
+#if !defined(SEEK_SET) && !defined(Z_SOLO)
+# define SEEK_SET 0 /* Seek from beginning of file. */
+# define SEEK_CUR 1 /* Seek from current position. */
+# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
+#endif
+
+#ifndef z_off_t
+# define z_off_t long
+#endif
+
+#if !defined(_WIN32) && (defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0)
+# define z_off64_t off64_t
+#else
+# if defined(_WIN32)
+# define z_off64_t __int64
+# else
+# define z_off64_t z_off_t
+#endif
+#endif
+
+/* MVS linker does not support external names larger than 8 bytes */
+#if defined(__MVS__)
+ #pragma map(deflateInit_,"DEIN")
+ #pragma map(deflateInit2_,"DEIN2")
+ #pragma map(deflateEnd,"DEEND")
+ #pragma map(deflateBound,"DEBND")
+ #pragma map(inflateInit_,"ININ")
+ #pragma map(inflateInit2_,"ININ2")
+ #pragma map(inflateEnd,"INEND")
+ #pragma map(inflateSync,"INSY")
+ #pragma map(inflateSetDictionary,"INSEDI")
+ #pragma map(compressBound,"CMBND")
+ #pragma map(inflate_table,"INTABL")
+ #pragma map(inflate_fast,"INFA")
+ #pragma map(inflate_copyright,"INCOPY")
+#endif
+
+#endif /* ZCONF_H */
diff --git a/src/cpp/core/zlib/zconf.h.cmakein b/src/cpp/core/zlib/zconf.h.cmakein
new file mode 100644
index 0000000..3ea5531
--- /dev/null
+++ b/src/cpp/core/zlib/zconf.h.cmakein
@@ -0,0 +1,468 @@
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-2011 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZCONF_H
+#define ZCONF_H
+#cmakedefine Z_PREFIX
+#cmakedefine Z_HAVE_UNISTD_H
+
+/*
+ * If you *really* need a unique prefix for all types and library functions,
+ * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
+ * Even better than compiling with -DZ_PREFIX would be to use configure to set
+ * this permanently in zconf.h using "./configure --zprefix".
+ */
+#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
+# define Z_PREFIX_SET
+
+/* all linked symbols */
+# define _dist_code z__dist_code
+# define _length_code z__length_code
+# define _tr_align z__tr_align
+# define _tr_flush_block z__tr_flush_block
+# define _tr_init z__tr_init
+# define _tr_stored_block z__tr_stored_block
+# define _tr_tally z__tr_tally
+# define adler32 z_adler32
+# define adler32_combine z_adler32_combine
+# define adler32_combine64 z_adler32_combine64
+# ifndef Z_SOLO
+# define compress z_compress
+# define compress2 z_compress2
+# define compressBound z_compressBound
+# endif
+# define crc32 z_crc32
+# define crc32_combine z_crc32_combine
+# define crc32_combine64 z_crc32_combine64
+# define deflate z_deflate
+# define deflateBound z_deflateBound
+# define deflateCopy z_deflateCopy
+# define deflateEnd z_deflateEnd
+# define deflateInit2_ z_deflateInit2_
+# define deflateInit_ z_deflateInit_
+# define deflateParams z_deflateParams
+# define deflatePending z_deflatePending
+# define deflatePrime z_deflatePrime
+# define deflateReset z_deflateReset
+# define deflateResetKeep z_deflateResetKeep
+# define deflateSetDictionary z_deflateSetDictionary
+# define deflateSetHeader z_deflateSetHeader
+# define deflateTune z_deflateTune
+# define deflate_copyright z_deflate_copyright
+# define get_crc_table z_get_crc_table
+# ifndef Z_SOLO
+# define gz_error z_gz_error
+# define gz_intmax z_gz_intmax
+# define gz_strwinerror z_gz_strwinerror
+# define gzbuffer z_gzbuffer
+# define gzclearerr z_gzclearerr
+# define gzclose z_gzclose
+# define gzclose_r z_gzclose_r
+# define gzclose_w z_gzclose_w
+# define gzdirect z_gzdirect
+# define gzdopen z_gzdopen
+# define gzeof z_gzeof
+# define gzerror z_gzerror
+# define gzflags z_gzflags
+# define gzflush z_gzflush
+# define gzgetc z_gzgetc
+# define gzgetc_ z_gzgetc_
+# define gzgets z_gzgets
+# define gzoffset z_gzoffset
+# define gzoffset64 z_gzoffset64
+# define gzopen z_gzopen
+# define gzopen64 z_gzopen64
+# define gzprintf z_gzprintf
+# define gzputc z_gzputc
+# define gzputs z_gzputs
+# define gzread z_gzread
+# define gzrewind z_gzrewind
+# define gzseek z_gzseek
+# define gzseek64 z_gzseek64
+# define gzsetparams z_gzsetparams
+# define gztell z_gztell
+# define gztell64 z_gztell64
+# define gzungetc z_gzungetc
+# define gzwrite z_gzwrite
+# endif
+# define inflate z_inflate
+# define inflateBack z_inflateBack
+# define inflateBackEnd z_inflateBackEnd
+# define inflateBackInit_ z_inflateBackInit_
+# define inflateCopy z_inflateCopy
+# define inflateEnd z_inflateEnd
+# define inflateGetHeader z_inflateGetHeader
+# define inflateInit2_ z_inflateInit2_
+# define inflateInit_ z_inflateInit_
+# define inflateMark z_inflateMark
+# define inflatePrime z_inflatePrime
+# define inflateReset z_inflateReset
+# define inflateReset2 z_inflateReset2
+# define inflateSetDictionary z_inflateSetDictionary
+# define inflateSync z_inflateSync
+# define inflateSyncPoint z_inflateSyncPoint
+# define inflateUndermine z_inflateUndermine
+# define inflateResetKeep z_inflateResetKeep
+# define inflate_copyright z_inflate_copyright
+# define inflate_fast z_inflate_fast
+# define inflate_table z_inflate_table
+# ifndef Z_SOLO
+# define uncompress z_uncompress
+# endif
+# define zError z_zError
+# ifndef Z_SOLO
+# define zcalloc z_zcalloc
+# define zcfree z_zcfree
+# endif
+# define zlibCompileFlags z_zlibCompileFlags
+# define zlibVersion z_zlibVersion
+
+/* all zlib typedefs in zlib.h and zconf.h */
+# define Byte z_Byte
+# define Bytef z_Bytef
+# define alloc_func z_alloc_func
+# define charf z_charf
+# define free_func z_free_func
+# ifndef Z_SOLO
+# define gzFile z_gzFile
+# define gz_header z_gz_header
+# define gz_headerp z_gz_headerp
+# endif
+# define in_func z_in_func
+# define intf z_intf
+# define out_func z_out_func
+# define uInt z_uInt
+# define uIntf z_uIntf
+# define uLong z_uLong
+# define uLongf z_uLongf
+# define voidp z_voidp
+# define voidpc z_voidpc
+# define voidpf z_voidpf
+
+/* all zlib structs in zlib.h and zconf.h */
+# ifndef Z_SOLO
+# define gz_header_s z_gz_header_s
+# endif
+# define internal_state z_internal_state
+
+#endif
+
+#if defined(__MSDOS__) && !defined(MSDOS)
+# define MSDOS
+#endif
+#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
+# define OS2
+#endif
+#if defined(_WINDOWS) && !defined(WINDOWS)
+# define WINDOWS
+#endif
+#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
+# ifndef WIN32
+# define WIN32
+# endif
+#endif
+#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
+# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
+# ifndef SYS16BIT
+# define SYS16BIT
+# endif
+# endif
+#endif
+
+/*
+ * Compile with -DMAXSEG_64K if the alloc function cannot allocate more
+ * than 64k bytes at a time (needed on systems with 16-bit int).
+ */
+#ifdef SYS16BIT
+# define MAXSEG_64K
+#endif
+#ifdef MSDOS
+# define UNALIGNED_OK
+#endif
+
+#ifdef __STDC_VERSION__
+# ifndef STDC
+# define STDC
+# endif
+# if __STDC_VERSION__ >= 199901L
+# ifndef STDC99
+# define STDC99
+# endif
+# endif
+#endif
+#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
+# define STDC
+#endif
+#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
+# define STDC
+#endif
+
+#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
+# define STDC
+#endif
+
+#ifndef STDC
+# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
+# define const /* note: need a more gentle solution here */
+# endif
+#endif
+
+#if defined(ZLIB_CONST) && !defined(z_const)
+# define z_const const
+#else
+# define z_const
+#endif
+
+/* Some Mac compilers merge all .h files incorrectly: */
+#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
+# define NO_DUMMY_DECL
+#endif
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+# ifdef MAXSEG_64K
+# define MAX_MEM_LEVEL 8
+# else
+# define MAX_MEM_LEVEL 9
+# endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+# define MAX_WBITS 15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+ (1 << (windowBits+2)) + (1 << (memLevel+9))
+ that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+ make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+ The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ for small objects.
+*/
+
+ /* Type declarations */
+
+#ifndef OF /* function prototypes */
+# ifdef STDC
+# define OF(args) args
+# else
+# define OF(args) ()
+# endif
+#endif
+
+#ifndef Z_ARG /* function prototypes for stdarg */
+# if defined(STDC) || defined(Z_HAVE_STDARG_H)
+# define Z_ARG(args) args
+# else
+# define Z_ARG(args) ()
+# endif
+#endif
+
+/* The following definitions for FAR are needed only for MSDOS mixed
+ * model programming (small or medium model with some far allocations).
+ * This was tested only with MSC; for other MSDOS compilers you may have
+ * to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
+ * just define FAR to be empty.
+ */
+#ifdef SYS16BIT
+# if defined(M_I86SM) || defined(M_I86MM)
+ /* MSC small or medium model */
+# define SMALL_MEDIUM
+# ifdef _MSC_VER
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+# if (defined(__SMALL__) || defined(__MEDIUM__))
+ /* Turbo C small or medium model */
+# define SMALL_MEDIUM
+# ifdef __BORLANDC__
+# define FAR _far
+# else
+# define FAR far
+# endif
+# endif
+#endif
+
+#if defined(WINDOWS) || defined(WIN32)
+ /* If building or using zlib as a DLL, define ZLIB_DLL.
+ * This is not mandatory, but it offers a little performance increase.
+ */
+# ifdef ZLIB_DLL
+# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
+# ifdef ZLIB_INTERNAL
+# define ZEXTERN extern __declspec(dllexport)
+# else
+# define ZEXTERN extern __declspec(dllimport)
+# endif
+# endif
+# endif /* ZLIB_DLL */
+ /* If building or using zlib with the WINAPI/WINAPIV calling convention,
+ * define ZLIB_WINAPI.
+ * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
+ */
+# ifdef ZLIB_WINAPI
+# ifdef FAR
+# undef FAR
+# endif
+# include <windows.h>
+ /* No need for _export, use ZLIB.DEF instead. */
+ /* For complete Windows compatibility, use WINAPI, not __stdcall. */
+# define ZEXPORT WINAPI
+# ifdef WIN32
+# define ZEXPORTVA WINAPIV
+# else
+# define ZEXPORTVA FAR CDECL
+# endif
+# endif
+#endif
+
+#if defined (__BEOS__)
+# ifdef ZLIB_DLL
+# ifdef ZLIB_INTERNAL
+# define ZEXPORT __declspec(dllexport)
+# define ZEXPORTVA __declspec(dllexport)
+# else
+# define ZEXPORT __declspec(dllimport)
+# define ZEXPORTVA __declspec(dllimport)
+# endif
+# endif
+#endif
+
+#ifndef ZEXTERN
+# define ZEXTERN extern
+#endif
+#ifndef ZEXPORT
+# define ZEXPORT
+#endif
+#ifndef ZEXPORTVA
+# define ZEXPORTVA
+#endif
+
+#ifndef FAR
+# define FAR
+#endif
+
+#if !defined(__MACTYPES__)
+typedef unsigned char Byte; /* 8 bits */
+#endif
+typedef unsigned int uInt; /* 16 bits or more */
+typedef unsigned long uLong; /* 32 bits or more */
+
+#ifdef SMALL_MEDIUM
+ /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
+# define Bytef Byte FAR
+#else
+ typedef Byte FAR Bytef;
+#endif
+typedef char FAR charf;
+typedef int FAR intf;
+typedef uInt FAR uIntf;
+typedef uLong FAR uLongf;
+
+#ifdef STDC
+ typedef void const *voidpc;
+ typedef void FAR *voidpf;
+ typedef void *voidp;
+#else
+ typedef Byte const *voidpc;
+ typedef Byte FAR *voidpf;
+ typedef Byte *voidp;
+#endif
+
+#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_UNISTD_H
+#endif
+
+#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
+# define Z_HAVE_STDARG_H
+#endif
+
+#ifdef STDC
+# ifndef Z_SOLO
+# include <sys/types.h> /* for off_t */
+# endif
+#endif
+
+/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
+ * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
+ * though the former does not conform to the LFS document), but considering
+ * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
+ * equivalently requesting no 64-bit operations
+ */
+#if -_LARGEFILE64_SOURCE - -1 == 1
+# undef _LARGEFILE64_SOURCE
+#endif
+
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+# define Z_LARGE
+#endif
+
+#if (defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE)) && !defined(Z_SOLO)
+# include <unistd.h> /* for SEEK_* and off_t */
+# ifdef VMS
+# include <unixio.h> /* for off_t */
+# endif
+# ifndef z_off_t
+# define z_off_t off_t
+# endif
+#endif
+
+#if !defined(SEEK_SET) && !defined(Z_SOLO)
+# define SEEK_SET 0 /* Seek from beginning of file. */
+# define SEEK_CUR 1 /* Seek from current position. */
+# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
+#endif
+
+#ifndef z_off_t
+# define z_off_t long
+#endif
+
+#if !defined(_WIN32) && (defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0)
+# define z_off64_t off64_t
+#else
+# if defined(_WIN32)
+# define z_off64_t __int64
+# else
+# define z_off64_t z_off_t
+#endif
+#endif
+
+/* MVS linker does not support external names larger than 8 bytes */
+#if defined(__MVS__)
+ #pragma map(deflateInit_,"DEIN")
+ #pragma map(deflateInit2_,"DEIN2")
+ #pragma map(deflateEnd,"DEEND")
+ #pragma map(deflateBound,"DEBND")
+ #pragma map(inflateInit_,"ININ")
+ #pragma map(inflateInit2_,"ININ2")
+ #pragma map(inflateEnd,"INEND")
+ #pragma map(inflateSync,"INSY")
+ #pragma map(inflateSetDictionary,"INSEDI")
+ #pragma map(compressBound,"CMBND")
+ #pragma map(inflate_table,"INTABL")
+ #pragma map(inflate_fast,"INFA")
+ #pragma map(inflate_copyright,"INCOPY")
+#endif
+
+#endif /* ZCONF_H */
diff --git a/src/cpp/core/zlib/zlib.h b/src/cpp/core/zlib/zlib.h
new file mode 100644
index 0000000..79142d1
--- /dev/null
+++ b/src/cpp/core/zlib/zlib.h
@@ -0,0 +1,1732 @@
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+ version 1.2.6, January 29th, 2012
+
+ Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup at gzip.org madler at alumni.caltech.edu
+
+
+ The data format used by the zlib library is described by RFCs (Request for
+ Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950
+ (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format).
+*/
+
+#ifndef ZLIB_H
+#define ZLIB_H
+
+#include "zconf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZLIB_VERSION "1.2.6"
+#define ZLIB_VERNUM 0x1260
+#define ZLIB_VER_MAJOR 1
+#define ZLIB_VER_MINOR 2
+#define ZLIB_VER_REVISION 6
+#define ZLIB_VER_SUBREVISION 0
+
+/*
+ The 'zlib' compression library provides in-memory compression and
+ decompression functions, including integrity checks of the uncompressed data.
+ This version of the library supports only one compression method (deflation)
+ but other algorithms will be added later and will have the same stream
+ interface.
+
+ Compression can be done in a single step if the buffers are large enough,
+ or can be done by repeated calls of the compression function. In the latter
+ case, the application must provide more input and/or consume the output
+ (providing more output space) before each call.
+
+ The compressed data format used by default by the in-memory functions is
+ the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
+ around a deflate stream, which is itself documented in RFC 1951.
+
+ The library also supports reading and writing files in gzip (.gz) format
+ with an interface similar to that of stdio using the functions that start
+ with "gz". The gzip format is different from the zlib format. gzip is a
+ gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
+
+ This library can optionally read and write gzip streams in memory as well.
+
+ The zlib format was designed to be compact and fast for use in memory
+ and on communications channels. The gzip format was designed for single-
+ file compression on file systems, has a larger header than zlib to maintain
+ directory information, and uses a different, slower check method than zlib.
+
+ The library does not install any signal handler. The decoder checks
+ the consistency of the compressed data, so the library should never crash
+ even in case of corrupted input.
+*/
+
+typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
+typedef void (*free_func) OF((voidpf opaque, voidpf address));
+
+struct internal_state;
+
+typedef struct z_stream_s {
+ z_const Bytef *next_in; /* next input byte */
+ uInt avail_in; /* number of bytes available at next_in */
+ uLong total_in; /* total number of input bytes read so far */
+
+ Bytef *next_out; /* next output byte should be put there */
+ uInt avail_out; /* remaining free space at next_out */
+ uLong total_out; /* total number of bytes output so far */
+
+ z_const char *msg; /* last error message, NULL if no error */
+ struct internal_state FAR *state; /* not visible by applications */
+
+ alloc_func zalloc; /* used to allocate the internal state */
+ free_func zfree; /* used to free the internal state */
+ voidpf opaque; /* private data object passed to zalloc and zfree */
+
+ int data_type; /* best guess about the data type: binary or text */
+ uLong adler; /* adler32 value of the uncompressed data */
+ uLong reserved; /* reserved for future use */
+} z_stream;
+
+typedef z_stream FAR *z_streamp;
+
+/*
+ gzip header information passed to and from zlib routines. See RFC 1952
+ for more details on the meanings of these fields.
+*/
+typedef struct gz_header_s {
+ int text; /* true if compressed data believed to be text */
+ uLong time; /* modification time */
+ int xflags; /* extra flags (not used when writing a gzip file) */
+ int os; /* operating system */
+ Bytef *extra; /* pointer to extra field or Z_NULL if none */
+ uInt extra_len; /* extra field length (valid if extra != Z_NULL) */
+ uInt extra_max; /* space at extra (only when reading header) */
+ Bytef *name; /* pointer to zero-terminated file name or Z_NULL */
+ uInt name_max; /* space at name (only when reading header) */
+ Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */
+ uInt comm_max; /* space at comment (only when reading header) */
+ int hcrc; /* true if there was or will be a header crc */
+ int done; /* true when done reading gzip header (not used
+ when writing a gzip file) */
+} gz_header;
+
+typedef gz_header FAR *gz_headerp;
+
+/*
+ The application must update next_in and avail_in when avail_in has dropped
+ to zero. It must update next_out and avail_out when avail_out has dropped
+ to zero. The application must initialize zalloc, zfree and opaque before
+ calling the init function. All other fields are set by the compression
+ library and must not be updated by the application.
+
+ The opaque value provided by the application will be passed as the first
+ parameter for calls of zalloc and zfree. This can be useful for custom
+ memory management. The compression library attaches no meaning to the
+ opaque value.
+
+ zalloc must return Z_NULL if there is not enough memory for the object.
+ If zlib is used in a multi-threaded application, zalloc and zfree must be
+ thread safe.
+
+ On 16-bit systems, the functions zalloc and zfree must be able to allocate
+ exactly 65536 bytes, but will not be required to allocate more than this if
+ the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers
+ returned by zalloc for objects of exactly 65536 bytes *must* have their
+ offset normalized to zero. The default allocation function provided by this
+ library ensures this (see zutil.c). To reduce memory requirements and avoid
+ any allocation of 64K objects, at the expense of compression ratio, compile
+ the library with -DMAX_WBITS=14 (see zconf.h).
+
+ The fields total_in and total_out can be used for statistics or progress
+ reports. After compression, total_in holds the total size of the
+ uncompressed data and may be saved for use in the decompressor (particularly
+ if the decompressor wants to decompress everything in a single step).
+*/
+
+ /* constants */
+
+#define Z_NO_FLUSH 0
+#define Z_PARTIAL_FLUSH 1
+#define Z_SYNC_FLUSH 2
+#define Z_FULL_FLUSH 3
+#define Z_FINISH 4
+#define Z_BLOCK 5
+#define Z_TREES 6
+/* Allowed flush values; see deflate() and inflate() below for details */
+
+#define Z_OK 0
+#define Z_STREAM_END 1
+#define Z_NEED_DICT 2
+#define Z_ERRNO (-1)
+#define Z_STREAM_ERROR (-2)
+#define Z_DATA_ERROR (-3)
+#define Z_MEM_ERROR (-4)
+#define Z_BUF_ERROR (-5)
+#define Z_VERSION_ERROR (-6)
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+
+#define Z_NO_COMPRESSION 0
+#define Z_BEST_SPEED 1
+#define Z_BEST_COMPRESSION 9
+#define Z_DEFAULT_COMPRESSION (-1)
+/* compression levels */
+
+#define Z_FILTERED 1
+#define Z_HUFFMAN_ONLY 2
+#define Z_RLE 3
+#define Z_FIXED 4
+#define Z_DEFAULT_STRATEGY 0
+/* compression strategy; see deflateInit2() below for details */
+
+#define Z_BINARY 0
+#define Z_TEXT 1
+#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */
+#define Z_UNKNOWN 2
+/* Possible values of the data_type field (though see inflate()) */
+
+#define Z_DEFLATED 8
+/* The deflate compression method (the only one supported in this version) */
+
+#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */
+
+#define zlib_version zlibVersion()
+/* for compatibility with versions < 1.0.2 */
+
+
+ /* basic functions */
+
+ZEXTERN const char * ZEXPORT zlibVersion OF((void));
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+ If the first character differs, the library code actually used is not
+ compatible with the zlib.h header file used by the application. This check
+ is automatically made by deflateInit and inflateInit.
+ */
+
+/*
+ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
+
+ Initializes the internal stream state for compression. The fields
+ zalloc, zfree and opaque must be initialized before by the caller. If
+ zalloc and zfree are set to Z_NULL, deflateInit updates them to use default
+ allocation functions.
+
+ The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+ 1 gives best speed, 9 gives best compression, 0 gives no compression at all
+ (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION
+ requests a default compromise between speed and compression (currently
+ equivalent to level 6).
+
+ deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if level is not a valid compression level, or
+ Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+ with the version assumed by the caller (ZLIB_VERSION). msg is set to null
+ if there is no error message. deflateInit does not perform any compression:
+ this will be done by deflate().
+*/
+
+
+ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
+/*
+ deflate compresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may introduce
+ some output latency (reading input without producing any output) except when
+ forced to flush.
+
+ The detailed semantics are as follows. deflate performs one or both of the
+ following actions:
+
+ - Compress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), next_in and avail_in are updated and
+ processing will resume at this point for the next call of deflate().
+
+ - Provide more output starting at next_out and update next_out and avail_out
+ accordingly. This action is forced if the parameter flush is non zero.
+ Forcing flush frequently degrades the compression ratio, so this parameter
+ should be set only when necessary (in interactive applications). Some
+ output may be provided even if flush is not set.
+
+ Before the call of deflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming more
+ output, and updating avail_in or avail_out accordingly; avail_out should
+ never be zero before the call. The application can consume the compressed
+ output when it wants, for example when the output buffer is full (avail_out
+ == 0), or after each call of deflate(). If deflate returns Z_OK and with
+ zero avail_out, it must be called again after making room in the output
+ buffer because there might be more output pending.
+
+ Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
+ decide how much data to accumulate before producing output, in order to
+ maximize compression.
+
+ If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+ flushed to the output buffer and the output is aligned on a byte boundary, so
+ that the decompressor can get all input data available so far. (In
+ particular avail_in is zero after the call if enough output space has been
+ provided before the call.) Flushing may degrade compression for some
+ compression algorithms and so it should be used only when necessary. This
+ completes the current deflate block and follows it with an empty stored block
+ that is three bits plus filler bits to the next byte, followed by four bytes
+ (00 00 ff ff).
+
+ If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the
+ output buffer, but the output is not aligned to a byte boundary. All of the
+ input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
+ This completes the current deflate block and follows it with an empty fixed
+ codes block that is 10 bits long. This assures that enough bytes are output
+ in order for the decompressor to finish the block before the empty fixed code
+ block.
+
+ If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
+ for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
+ seven bits of the current block are held to be written as the next byte after
+ the next deflate block is completed. In this case, the decompressor may not
+ be provided enough bits at this point in order to complete decompression of
+ the data provided so far to the compressor. It may need to wait for the next
+ block to be emitted. This is for advanced applications that need to control
+ the emission of deflate blocks.
+
+ If flush is set to Z_FULL_FLUSH, all output is flushed as with
+ Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+ restart from this point if previous compressed data has been damaged or if
+ random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
+ compression.
+
+ If deflate returns with avail_out == 0, this function must be called again
+ with the same value of the flush parameter and more output space (updated
+ avail_out), until the flush is complete (deflate returns with non-zero
+ avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
+ avail_out is greater than six to avoid repeated flush markers due to
+ avail_out == 0 on return.
+
+ If the parameter flush is set to Z_FINISH, pending input is processed,
+ pending output is flushed and deflate returns with Z_STREAM_END if there was
+ enough output space; if deflate returns with Z_OK, this function must be
+ called again with Z_FINISH and more output space (updated avail_out) but no
+ more input data, until it returns with Z_STREAM_END or an error. After
+ deflate has returned Z_STREAM_END, the only possible operations on the stream
+ are deflateReset or deflateEnd.
+
+ Z_FINISH can be used immediately after deflateInit if all the compression
+ is to be done in a single step. In this case, avail_out must be at least the
+ value returned by deflateBound (see below). Then deflate is guaranteed to
+ return Z_STREAM_END. If not enough output space is provided, deflate will
+ not return Z_STREAM_END, and it must be called again as described above.
+
+ deflate() sets strm->adler to the adler32 checksum of all input read
+ so far (that is, total_in bytes).
+
+ deflate() may update strm->data_type if it can make a good guess about
+ the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
+ binary. This field is only for information purposes and does not affect the
+ compression algorithm in any manner.
+
+ deflate() returns Z_OK if some progress has been made (more input
+ processed or more output produced), Z_STREAM_END if all input has been
+ consumed and all output has been produced (only when flush is set to
+ Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+ if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible
+ (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
+ fatal, and deflate() can be called again with more input and more output
+ space to continue compressing.
+*/
+
+
+ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any pending
+ output.
+
+ deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+ stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+ prematurely (some input or output was discarded). In the error case, msg
+ may be set but then points to a static string (which must not be
+ deallocated).
+*/
+
+
+/*
+ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
+
+ Initializes the internal stream state for decompression. The fields
+ next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+ the caller. If next_in is not Z_NULL and avail_in is large enough (the
+ exact value depends on the compression method), inflateInit determines the
+ compression method from the zlib header and allocates all data structures
+ accordingly; otherwise the allocation will be deferred to the first call of
+ inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to
+ use default allocation functions.
+
+ inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+ version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+ invalid, such as a null pointer to the structure. msg is set to null if
+ there is no error message. inflateInit does not perform any decompression
+ apart from possibly reading the zlib header if present: actual decompression
+ will be done by inflate(). (So next_in and avail_in may be modified, but
+ next_out and avail_out are unused and unchanged.) The current implementation
+ of inflateInit() does not process any header information -- that is deferred
+ until inflate() is called.
+*/
+
+
+ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
+/*
+ inflate decompresses as much data as possible, and stops when the input
+ buffer becomes empty or the output buffer becomes full. It may introduce
+ some output latency (reading input without producing any output) except when
+ forced to flush.
+
+ The detailed semantics are as follows. inflate performs one or both of the
+ following actions:
+
+ - Decompress more input starting at next_in and update next_in and avail_in
+ accordingly. If not all input can be processed (because there is not
+ enough room in the output buffer), next_in is updated and processing will
+ resume at this point for the next call of inflate().
+
+ - Provide more output starting at next_out and update next_out and avail_out
+ accordingly. inflate() provides as much output as possible, until there is
+ no more input data or no more space in the output buffer (see below about
+ the flush parameter).
+
+ Before the call of inflate(), the application should ensure that at least
+ one of the actions is possible, by providing more input and/or consuming more
+ output, and updating the next_* and avail_* values accordingly. The
+ application can consume the uncompressed output when it wants, for example
+ when the output buffer is full (avail_out == 0), or after each call of
+ inflate(). If inflate returns Z_OK and with zero avail_out, it must be
+ called again after making room in the output buffer because there might be
+ more output pending.
+
+ The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH,
+ Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much
+ output as possible to the output buffer. Z_BLOCK requests that inflate()
+ stop if and when it gets to the next deflate block boundary. When decoding
+ the zlib or gzip format, this will cause inflate() to return immediately
+ after the header and before the first block. When doing a raw inflate,
+ inflate() will go ahead and process the first block, and will return when it
+ gets to the end of that block, or when it runs out of data.
+
+ The Z_BLOCK option assists in appending to or combining deflate streams.
+ Also to assist in this, on return inflate() will set strm->data_type to the
+ number of unused bits in the last byte taken from strm->next_in, plus 64 if
+ inflate() is currently decoding the last block in the deflate stream, plus
+ 128 if inflate() returned immediately after decoding an end-of-block code or
+ decoding the complete header up to just before the first byte of the deflate
+ stream. The end-of-block will not be indicated until all of the uncompressed
+ data from that block has been written to strm->next_out. The number of
+ unused bits may in general be greater than seven, except when bit 7 of
+ data_type is set, in which case the number of unused bits will be less than
+ eight. data_type is set as noted here every time inflate() returns for all
+ flush options, and so can be used to determine the amount of currently
+ consumed input in bits.
+
+ The Z_TREES option behaves as Z_BLOCK does, but it also returns when the
+ end of each deflate block header is reached, before any actual data in that
+ block is decoded. This allows the caller to determine the length of the
+ deflate block header for later use in random access within a deflate block.
+ 256 is added to the value of strm->data_type when inflate() returns
+ immediately after reaching the end of the deflate block header.
+
+ inflate() should normally be called until it returns Z_STREAM_END or an
+ error. However if all decompression is to be performed in a single step (a
+ single call of inflate), the parameter flush should be set to Z_FINISH. In
+ this case all pending input is processed and all pending output is flushed;
+ avail_out must be large enough to hold all the uncompressed data. (The size
+ of the uncompressed data may have been saved by the compressor for this
+ purpose.) The next operation on this stream must be inflateEnd to deallocate
+ the decompression state. The use of Z_FINISH is not required to perform an
+ inflation in one step. However it may be used to inform inflate that a
+ faster approach can be used for the single inflate() call. Z_FINISH also
+ informs inflate to not maintain a sliding window if the stream completes,
+ which reduces inflate's memory footprint.
+
+ In this implementation, inflate() always flushes as much output as
+ possible to the output buffer, and always uses the faster approach on the
+ first call. So the effects of the flush parameter in this implementation are
+ on the return value of inflate() as noted below, when inflate() returns early
+ when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of
+ memory for a sliding window when Z_FINISH is used.
+
+ If a preset dictionary is needed after this call (see inflateSetDictionary
+ below), inflate sets strm->adler to the Adler-32 checksum of the dictionary
+ chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
+ strm->adler to the Adler-32 checksum of all output produced so far (that is,
+ total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
+ below. At the end of the stream, inflate() checks that its computed adler32
+ checksum is equal to that saved by the compressor and returns Z_STREAM_END
+ only if the checksum is correct.
+
+ inflate() can decompress and check either zlib-wrapped or gzip-wrapped
+ deflate data. The header type is detected automatically, if requested when
+ initializing with inflateInit2(). Any information contained in the gzip
+ header is not retained, so applications that need that information should
+ instead use raw inflate, see inflateInit2() below, or inflateBack() and
+ perform their own processing of the gzip header and trailer. When processing
+ gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output
+ producted so far. The CRC-32 is checked against the gzip trailer.
+
+ inflate() returns Z_OK if some progress has been made (more input processed
+ or more output produced), Z_STREAM_END if the end of the compressed data has
+ been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+ preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+ corrupted (input stream not conforming to the zlib format or incorrect check
+ value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+ next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory,
+ Z_BUF_ERROR if no progress is possible or if there was not enough room in the
+ output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
+ inflate() can be called again with more input and more output space to
+ continue decompressing. If Z_DATA_ERROR is returned, the application may
+ then call inflateSync() to look for a good compression block if a partial
+ recovery of the data is desired.
+*/
+
+
+ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
+/*
+ All dynamically allocated data structures for this stream are freed.
+ This function discards any unprocessed input and does not flush any pending
+ output.
+
+ inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
+ was inconsistent. In the error case, msg may be set but then points to a
+ static string (which must not be deallocated).
+*/
+
+
+ /* Advanced functions */
+
+/*
+ The following functions are needed only in some special applications.
+*/
+
+/*
+ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
+ int level,
+ int method,
+ int windowBits,
+ int memLevel,
+ int strategy));
+
+ This is another version of deflateInit with more compression options. The
+ fields next_in, zalloc, zfree and opaque must be initialized before by the
+ caller.
+
+ The method parameter is the compression method. It must be Z_DEFLATED in
+ this version of the library.
+
+ The windowBits parameter is the base two logarithm of the window size
+ (the size of the history buffer). It should be in the range 8..15 for this
+ version of the library. Larger values of this parameter result in better
+ compression at the expense of memory usage. The default value is 15 if
+ deflateInit is used instead.
+
+ windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
+ determines the window size. deflate() will then generate raw deflate data
+ with no zlib header or trailer, and will not compute an adler32 check value.
+
+ windowBits can also be greater than 15 for optional gzip encoding. Add
+ 16 to windowBits to write a simple gzip header and trailer around the
+ compressed data instead of a zlib wrapper. The gzip header will have no
+ file name, no extra data, no comment, no modification time (set to zero), no
+ header crc, and the operating system will be set to 255 (unknown). If a
+ gzip stream is being written, strm->adler is a crc32 instead of an adler32.
+
+ The memLevel parameter specifies how much memory should be allocated
+ for the internal compression state. memLevel=1 uses minimum memory but is
+ slow and reduces compression ratio; memLevel=9 uses maximum memory for
+ optimal speed. The default value is 8. See zconf.h for total memory usage
+ as a function of windowBits and memLevel.
+
+ The strategy parameter is used to tune the compression algorithm. Use the
+ value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+ filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
+ string match), or Z_RLE to limit match distances to one (run-length
+ encoding). Filtered data consists mostly of small values with a somewhat
+ random distribution. In this case, the compression algorithm is tuned to
+ compress them better. The effect of Z_FILTERED is to force more Huffman
+ coding and less string matching; it is somewhat intermediate between
+ Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as
+ fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The
+ strategy parameter only affects the compression ratio but not the
+ correctness of the compressed output even if it is not set appropriately.
+ Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler
+ decoder for special applications.
+
+ deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid
+ method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is
+ incompatible with the version assumed by the caller (ZLIB_VERSION). msg is
+ set to null if there is no error message. deflateInit2 does not perform any
+ compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
+ const Bytef *dictionary,
+ uInt dictLength));
+/*
+ Initializes the compression dictionary from the given byte sequence
+ without producing any compressed output. When using the zlib format, this
+ function must be called immediately after deflateInit, deflateInit2 or
+ deflateReset, and before any call of deflate. When doing raw deflate, this
+ function must be called either before any call of deflate, or immediately
+ after the completion of a deflate block, i.e. after all input has been
+ consumed and all output has been delivered when using any of the flush
+ options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The
+ compressor and decompressor must use exactly the same dictionary (see
+ inflateSetDictionary).
+
+ The dictionary should consist of strings (byte sequences) that are likely
+ to be encountered later in the data to be compressed, with the most commonly
+ used strings preferably put towards the end of the dictionary. Using a
+ dictionary is most useful when the data to be compressed is short and can be
+ predicted with good accuracy; the data can then be compressed better than
+ with the default empty dictionary.
+
+ Depending on the size of the compression data structures selected by
+ deflateInit or deflateInit2, a part of the dictionary may in effect be
+ discarded, for example if the dictionary is larger than the window size
+ provided in deflateInit or deflateInit2. Thus the strings most likely to be
+ useful should be put at the end of the dictionary, not at the front. In
+ addition, the current implementation of deflate will use at most the window
+ size minus 262 bytes of the provided dictionary.
+
+ Upon return of this function, strm->adler is set to the adler32 value
+ of the dictionary; the decompressor may later use this value to determine
+ which dictionary has been used by the compressor. (The adler32 value
+ applies to the whole dictionary even if only a subset of the dictionary is
+ actually used by the compressor.) If a raw deflate was requested, then the
+ adler32 value is not computed and strm->adler is not set.
+
+ deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+ parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
+ inconsistent (for example if deflate has already been called for this stream
+ or if not at a block boundary for raw deflate). deflateSetDictionary does
+ not perform any compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
+ z_streamp source));
+/*
+ Sets the destination stream as a complete copy of the source stream.
+
+ This function can be useful when several compression strategies will be
+ tried, for example when there are several ways of pre-processing the input
+ data with a filter. The streams that will be discarded should then be freed
+ by calling deflateEnd. Note that deflateCopy duplicates the internal
+ compression state which can be quite large, so this strategy is slow and can
+ consume lots of memory.
+
+ deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+ (such as zalloc being Z_NULL). msg is left unchanged in both source and
+ destination.
+*/
+
+ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to deflateEnd followed by deflateInit,
+ but does not free and reallocate all the internal compression state. The
+ stream will keep the same compression level and any other attributes that
+ may have been set by deflateInit2.
+
+ deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
+ int level,
+ int strategy));
+/*
+ Dynamically update the compression level and compression strategy. The
+ interpretation of level and strategy is as in deflateInit2. This can be
+ used to switch between compression and straight copy of the input data, or
+ to switch to a different kind of input data requiring a different strategy.
+ If the compression level is changed, the input available so far is
+ compressed with the old level (and may be flushed); the new level will take
+ effect only at the next call of deflate().
+
+ Before the call of deflateParams, the stream state must be set as for
+ a call of deflate(), since the currently available input may have to be
+ compressed and flushed. In particular, strm->avail_out must be non-zero.
+
+ deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
+ stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if
+ strm->avail_out was zero.
+*/
+
+ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
+ int good_length,
+ int max_lazy,
+ int nice_length,
+ int max_chain));
+/*
+ Fine tune deflate's internal compression parameters. This should only be
+ used by someone who understands the algorithm used by zlib's deflate for
+ searching for the best matching string, and even then only by the most
+ fanatic optimizer trying to squeeze out the last compressed bit for their
+ specific input data. Read the deflate.c source code for the meaning of the
+ max_lazy, good_length, nice_length, and max_chain parameters.
+
+ deflateTune() can be called after deflateInit() or deflateInit2(), and
+ returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
+ */
+
+ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
+ uLong sourceLen));
+/*
+ deflateBound() returns an upper bound on the compressed size after
+ deflation of sourceLen bytes. It must be called after deflateInit() or
+ deflateInit2(), and after deflateSetHeader(), if used. This would be used
+ to allocate an output buffer for deflation in a single pass, and so would be
+ called before deflate(). If that first deflate() call is provided the
+ sourceLen input bytes, an output buffer allocated to the size returned by
+ deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed
+ to return Z_STREAM_END. Note that it is possible for the compressed size to
+ be larger than the value returned by deflateBound() if flush options other
+ than Z_FINISH or Z_NO_FLUSH are used.
+*/
+
+ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm,
+ unsigned *pending,
+ int *bits));
+/*
+ deflatePending() returns the number of bytes and bits of output that have
+ been generated, but not yet provided in the available output. The bytes not
+ provided would be due to the available output space having being consumed.
+ The number of bits of output not provided are between 0 and 7, where they
+ await more bits to join them in order to fill out a full byte. If pending
+ or bits are Z_NULL, then those values are not set.
+
+ deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+ */
+
+ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
+ int bits,
+ int value));
+/*
+ deflatePrime() inserts bits in the deflate output stream. The intent
+ is that this function is used to start off the deflate output with the bits
+ leftover from a previous deflate stream when appending to it. As such, this
+ function can only be used for raw deflate, and must be used before the first
+ deflate() call after a deflateInit2() or deflateReset(). bits must be less
+ than or equal to 16, and that many of the least significant bits of value
+ will be inserted in the output.
+
+ deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough
+ room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the
+ source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
+ gz_headerp head));
+/*
+ deflateSetHeader() provides gzip header information for when a gzip
+ stream is requested by deflateInit2(). deflateSetHeader() may be called
+ after deflateInit2() or deflateReset() and before the first call of
+ deflate(). The text, time, os, extra field, name, and comment information
+ in the provided gz_header structure are written to the gzip header (xflag is
+ ignored -- the extra flags are set according to the compression level). The
+ caller must assure that, if not Z_NULL, name and comment are terminated with
+ a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
+ available there. If hcrc is true, a gzip header crc is included. Note that
+ the current versions of the command-line version of gzip (up through version
+ 1.3.x) do not support header crc's, and will report that it is a "multi-part
+ gzip file" and give up.
+
+ If deflateSetHeader is not used, the default gzip header has text false,
+ the time set to zero, and os set to 255, with no extra, name, or comment
+ fields. The gzip header is returned to the default state by deflateReset().
+
+ deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
+ int windowBits));
+
+ This is another version of inflateInit with an extra parameter. The
+ fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+ before by the caller.
+
+ The windowBits parameter is the base two logarithm of the maximum window
+ size (the size of the history buffer). It should be in the range 8..15 for
+ this version of the library. The default value is 15 if inflateInit is used
+ instead. windowBits must be greater than or equal to the windowBits value
+ provided to deflateInit2() while compressing, or it must be equal to 15 if
+ deflateInit2() was not used. If a compressed stream with a larger window
+ size is given as input, inflate() will return with the error code
+ Z_DATA_ERROR instead of trying to allocate a larger window.
+
+ windowBits can also be zero to request that inflate use the window size in
+ the zlib header of the compressed stream.
+
+ windowBits can also be -8..-15 for raw inflate. In this case, -windowBits
+ determines the window size. inflate() will then process raw deflate data,
+ not looking for a zlib or gzip header, not generating a check value, and not
+ looking for any check values for comparison at the end of the stream. This
+ is for use with other formats that use the deflate compressed data format
+ such as zip. Those formats provide their own check values. If a custom
+ format is developed using the raw deflate format for compressed data, it is
+ recommended that a check value such as an adler32 or a crc32 be applied to
+ the uncompressed data as is done in the zlib, gzip, and zip formats. For
+ most applications, the zlib format should be used as is. Note that comments
+ above on the use in deflateInit2() applies to the magnitude of windowBits.
+
+ windowBits can also be greater than 15 for optional gzip decoding. Add
+ 32 to windowBits to enable zlib and gzip decoding with automatic header
+ detection, or add 16 to decode only the gzip format (the zlib format will
+ return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a
+ crc32 instead of an adler32.
+
+ inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+ version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+ invalid, such as a null pointer to the structure. msg is set to null if
+ there is no error message. inflateInit2 does not perform any decompression
+ apart from possibly reading the zlib header if present: actual decompression
+ will be done by inflate(). (So next_in and avail_in may be modified, but
+ next_out and avail_out are unused and unchanged.) The current implementation
+ of inflateInit2() does not process any header information -- that is
+ deferred until inflate() is called.
+*/
+
+ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
+ const Bytef *dictionary,
+ uInt dictLength));
+/*
+ Initializes the decompression dictionary from the given uncompressed byte
+ sequence. This function must be called immediately after a call of inflate,
+ if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
+ can be determined from the adler32 value returned by that call of inflate.
+ The compressor and decompressor must use exactly the same dictionary (see
+ deflateSetDictionary). For raw inflate, this function can be called at any
+ time to set the dictionary. If the provided dictionary is smaller than the
+ window and there is already data in the window, then the provided dictionary
+ will amend what's there. The application must insure that the dictionary
+ that was used for compression is provided.
+
+ inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+ parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
+ inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+ expected one (incorrect adler32 value). inflateSetDictionary does not
+ perform any decompression: this will be done by subsequent calls of
+ inflate().
+*/
+
+ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
+/*
+ Skips invalid compressed data until a possible full flush point (see above
+ for the description of deflate with Z_FULL_FLUSH) can be found, or until all
+ available input is skipped. No output is provided.
+
+ inflateSync searches for a 00 00 FF FF pattern in the compressed data.
+ All full flush points have this pattern, but not all occurences of this
+ pattern are full flush points.
+
+ inflateSync returns Z_OK if a possible full flush point has been found,
+ Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point
+ has been found, or Z_STREAM_ERROR if the stream structure was inconsistent.
+ In the success case, the application may save the current current value of
+ total_in which indicates where valid compressed data was found. In the
+ error case, the application may repeatedly call inflateSync, providing more
+ input each time, until success or end of the input data.
+*/
+
+ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
+ z_streamp source));
+/*
+ Sets the destination stream as a complete copy of the source stream.
+
+ This function can be useful when randomly accessing a large stream. The
+ first pass through the stream can periodically record the inflate state,
+ allowing restarting inflate at those points when randomly accessing the
+ stream.
+
+ inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+ (such as zalloc being Z_NULL). msg is left unchanged in both source and
+ destination.
+*/
+
+ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
+/*
+ This function is equivalent to inflateEnd followed by inflateInit,
+ but does not free and reallocate all the internal decompression state. The
+ stream will keep attributes that may have been set by inflateInit2.
+
+ inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
+ int windowBits));
+/*
+ This function is the same as inflateReset, but it also permits changing
+ the wrap and window size requests. The windowBits parameter is interpreted
+ the same as it is for inflateInit2.
+
+ inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent (such as zalloc or state being Z_NULL), or if
+ the windowBits parameter is invalid.
+*/
+
+ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
+ int bits,
+ int value));
+/*
+ This function inserts bits in the inflate input stream. The intent is
+ that this function is used to start inflating at a bit position in the
+ middle of a byte. The provided bits will be used before any bytes are used
+ from next_in. This function should only be used with raw inflate, and
+ should be used before the first inflate() call after inflateInit2() or
+ inflateReset(). bits must be less than or equal to 16, and that many of the
+ least significant bits of value will be inserted in the input.
+
+ If bits is negative, then the input stream bit buffer is emptied. Then
+ inflatePrime() can be called again to put bits in the buffer. This is used
+ to clear out bits leftover after feeding inflate a block description prior
+ to feeding inflate codes.
+
+ inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
+/*
+ This function returns two values, one in the lower 16 bits of the return
+ value, and the other in the remaining upper bits, obtained by shifting the
+ return value down 16 bits. If the upper value is -1 and the lower value is
+ zero, then inflate() is currently decoding information outside of a block.
+ If the upper value is -1 and the lower value is non-zero, then inflate is in
+ the middle of a stored block, with the lower value equaling the number of
+ bytes from the input remaining to copy. If the upper value is not -1, then
+ it is the number of bits back from the current bit position in the input of
+ the code (literal or length/distance pair) currently being processed. In
+ that case the lower value is the number of bytes already emitted for that
+ code.
+
+ A code is being processed if inflate is waiting for more input to complete
+ decoding of the code, or if it has completed decoding but is waiting for
+ more output space to write the literal or match data.
+
+ inflateMark() is used to mark locations in the input data for random
+ access, which may be at bit positions, and to note those cases where the
+ output of a code may span boundaries of random access blocks. The current
+ location in the input stream can be determined from avail_in and data_type
+ as noted in the description for the Z_BLOCK flush parameter for inflate.
+
+ inflateMark returns the value noted above or -1 << 16 if the provided
+ source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
+ gz_headerp head));
+/*
+ inflateGetHeader() requests that gzip header information be stored in the
+ provided gz_header structure. inflateGetHeader() may be called after
+ inflateInit2() or inflateReset(), and before the first call of inflate().
+ As inflate() processes the gzip stream, head->done is zero until the header
+ is completed, at which time head->done is set to one. If a zlib stream is
+ being decoded, then head->done is set to -1 to indicate that there will be
+ no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be
+ used to force inflate() to return immediately after header processing is
+ complete and before any actual data is decompressed.
+
+ The text, time, xflags, and os fields are filled in with the gzip header
+ contents. hcrc is set to true if there is a header CRC. (The header CRC
+ was valid if done is set to one.) If extra is not Z_NULL, then extra_max
+ contains the maximum number of bytes to write to extra. Once done is true,
+ extra_len contains the actual extra field length, and extra contains the
+ extra field, or that field truncated if extra_max is less than extra_len.
+ If name is not Z_NULL, then up to name_max characters are written there,
+ terminated with a zero unless the length is greater than name_max. If
+ comment is not Z_NULL, then up to comm_max characters are written there,
+ terminated with a zero unless the length is greater than comm_max. When any
+ of extra, name, or comment are not Z_NULL and the respective field is not
+ present in the header, then that field is set to Z_NULL to signal its
+ absence. This allows the use of deflateSetHeader() with the returned
+ structure to duplicate the header. However if those fields are set to
+ allocated memory, then the application will need to save those pointers
+ elsewhere so that they can be eventually freed.
+
+ If inflateGetHeader is not used, then the header information is simply
+ discarded. The header is always checked for validity, including the header
+ CRC if present. inflateReset() will reset the process to discard the header
+ information. The application would need to call inflateGetHeader() again to
+ retrieve the header from the next gzip stream.
+
+ inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+ stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
+ unsigned char FAR *window));
+
+ Initialize the internal stream state for decompression using inflateBack()
+ calls. The fields zalloc, zfree and opaque in strm must be initialized
+ before the call. If zalloc and zfree are Z_NULL, then the default library-
+ derived memory allocation routines are used. windowBits is the base two
+ logarithm of the window size, in the range 8..15. window is a caller
+ supplied buffer of that size. Except for special applications where it is
+ assured that deflate was used with small window sizes, windowBits must be 15
+ and a 32K byte window must be supplied to be able to decompress general
+ deflate streams.
+
+ See inflateBack() for the usage of these routines.
+
+ inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
+ the parameters are invalid, Z_MEM_ERROR if the internal state could not be
+ allocated, or Z_VERSION_ERROR if the version of the library does not match
+ the version of the header file.
+*/
+
+typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *));
+typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
+
+ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
+ in_func in, void FAR *in_desc,
+ out_func out, void FAR *out_desc));
+/*
+ inflateBack() does a raw inflate with a single call using a call-back
+ interface for input and output. This is more efficient than inflate() for
+ file i/o applications in that it avoids copying between the output and the
+ sliding window by simply making the window itself the output buffer. This
+ function trusts the application to not change the output buffer passed by
+ the output function, at least until inflateBack() returns.
+
+ inflateBackInit() must be called first to allocate the internal state
+ and to initialize the state with the user-provided window buffer.
+ inflateBack() may then be used multiple times to inflate a complete, raw
+ deflate stream with each call. inflateBackEnd() is then called to free the
+ allocated state.
+
+ A raw deflate stream is one with no zlib or gzip header or trailer.
+ This routine would normally be used in a utility that reads zip or gzip
+ files and writes out uncompressed files. The utility would decode the
+ header and process the trailer on its own, hence this routine expects only
+ the raw deflate stream to decompress. This is different from the normal
+ behavior of inflate(), which expects either a zlib or gzip header and
+ trailer around the deflate stream.
+
+ inflateBack() uses two subroutines supplied by the caller that are then
+ called by inflateBack() for input and output. inflateBack() calls those
+ routines until it reads a complete deflate stream and writes out all of the
+ uncompressed data, or until it encounters an error. The function's
+ parameters and return types are defined above in the in_func and out_func
+ typedefs. inflateBack() will call in(in_desc, &buf) which should return the
+ number of bytes of provided input, and a pointer to that input in buf. If
+ there is no input available, in() must return zero--buf is ignored in that
+ case--and inflateBack() will return a buffer error. inflateBack() will call
+ out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out()
+ should return zero on success, or non-zero on failure. If out() returns
+ non-zero, inflateBack() will return with an error. Neither in() nor out()
+ are permitted to change the contents of the window provided to
+ inflateBackInit(), which is also the buffer that out() uses to write from.
+ The length written by out() will be at most the window size. Any non-zero
+ amount of input may be provided by in().
+
+ For convenience, inflateBack() can be provided input on the first call by
+ setting strm->next_in and strm->avail_in. If that input is exhausted, then
+ in() will be called. Therefore strm->next_in must be initialized before
+ calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called
+ immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in
+ must also be initialized, and then if strm->avail_in is not zero, input will
+ initially be taken from strm->next_in[0 .. strm->avail_in - 1].
+
+ The in_desc and out_desc parameters of inflateBack() is passed as the
+ first parameter of in() and out() respectively when they are called. These
+ descriptors can be optionally used to pass any information that the caller-
+ supplied in() and out() functions need to do their job.
+
+ On return, inflateBack() will set strm->next_in and strm->avail_in to
+ pass back any unused input that was provided by the last in() call. The
+ return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
+ if in() or out() returned an error, Z_DATA_ERROR if there was a format error
+ in the deflate stream (in which case strm->msg is set to indicate the nature
+ of the error), or Z_STREAM_ERROR if the stream was not properly initialized.
+ In the case of Z_BUF_ERROR, an input or output error can be distinguished
+ using strm->next_in which will be Z_NULL only if in() returned an error. If
+ strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
+ non-zero. (in() will always be called before out(), so strm->next_in is
+ assured to be defined if out() returns non-zero.) Note that inflateBack()
+ cannot return Z_OK.
+*/
+
+ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
+/*
+ All memory allocated by inflateBackInit() is freed.
+
+ inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
+ state was inconsistent.
+*/
+
+ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
+/* Return flags indicating compile-time options.
+
+ Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
+ 1.0: size of uInt
+ 3.2: size of uLong
+ 5.4: size of voidpf (pointer)
+ 7.6: size of z_off_t
+
+ Compiler, assembler, and debug options:
+ 8: DEBUG
+ 9: ASMV or ASMINF -- use ASM code
+ 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
+ 11: 0 (reserved)
+
+ One-time table building (smaller code, but not thread-safe if true):
+ 12: BUILDFIXED -- build static block decoding tables when needed
+ 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
+ 14,15: 0 (reserved)
+
+ Library content (indicates missing functionality):
+ 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
+ deflate code when not needed)
+ 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
+ and decode gzip streams (to avoid linking crc code)
+ 18-19: 0 (reserved)
+
+ Operation variations (changes in library functionality):
+ 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
+ 21: FASTEST -- deflate algorithm with only one, lowest compression level
+ 22,23: 0 (reserved)
+
+ The sprintf variant used by gzprintf (zero is best):
+ 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
+ 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
+ 26: 0 = returns value, 1 = void -- 1 means inferred string length returned
+
+ Remainder:
+ 27-31: 0 (reserved)
+ */
+
+#ifndef Z_SOLO
+
+ /* utility functions */
+
+/*
+ The following utility functions are implemented on top of the basic
+ stream-oriented functions. To simplify the interface, some default options
+ are assumed (compression level and memory usage, standard memory allocation
+ functions). The source code of these utility functions can be modified if
+ you need special options.
+*/
+
+ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen));
+/*
+ Compresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total size
+ of the destination buffer, which must be at least the value returned by
+ compressBound(sourceLen). Upon exit, destLen is the actual size of the
+ compressed buffer.
+
+ compress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer.
+*/
+
+ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen,
+ int level));
+/*
+ Compresses the source buffer into the destination buffer. The level
+ parameter has the same meaning as in deflateInit. sourceLen is the byte
+ length of the source buffer. Upon entry, destLen is the total size of the
+ destination buffer, which must be at least the value returned by
+ compressBound(sourceLen). Upon exit, destLen is the actual size of the
+ compressed buffer.
+
+ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+ Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
+/*
+ compressBound() returns an upper bound on the compressed size after
+ compress() or compress2() on sourceLen bytes. It would be used before a
+ compress() or compress2() call to allocate the destination buffer.
+*/
+
+ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong sourceLen));
+/*
+ Decompresses the source buffer into the destination buffer. sourceLen is
+ the byte length of the source buffer. Upon entry, destLen is the total size
+ of the destination buffer, which must be large enough to hold the entire
+ uncompressed data. (The size of the uncompressed data must have been saved
+ previously by the compressor and transmitted to the decompressor by some
+ mechanism outside the scope of this compression library.) Upon exit, destLen
+ is the actual size of the uncompressed buffer.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+ enough memory, Z_BUF_ERROR if there was not enough room in the output
+ buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In
+ the case where there is not enough room, uncompress() will fill the output
+ buffer with the uncompressed data up to that point.
+*/
+
+ /* gzip file access functions */
+
+/*
+ This library supports reading and writing files in gzip (.gz) format with
+ an interface similar to that of stdio, using the functions that start with
+ "gz". The gzip format is different from the zlib format. gzip is a gzip
+ wrapper, documented in RFC 1952, wrapped around a deflate stream.
+*/
+
+typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */
+
+/*
+ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
+
+ Opens a gzip (.gz) file for reading or writing. The mode parameter is as
+ in fopen ("rb" or "wb") but can also include a compression level ("wb9") or
+ a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only
+ compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F'
+ for fixed code compression as in "wb9F". (See the description of
+ deflateInit2 for more information about the strategy parameter.) 'T' will
+ request transparent writing or appending with no compression and not using
+ the gzip format.
+
+ "a" can be used instead of "w" to request that the gzip stream that will
+ be written be appended to the file. "+" will result in an error, since
+ reading and writing to the same gzip file is not supported.
+
+ These functions, as well as gzip, will read and decode a sequence of gzip
+ streams in a file. The append function of gzopen() can be used to create
+ such a file. (Also see gzflush() for another way to do this.) When
+ appending, gzopen does not test whether the file begins with a gzip stream,
+ nor does it look for the end of the gzip streams to begin appending. gzopen
+ will simply append a gzip stream to the existing file.
+
+ gzopen can be used to read a file which is not in gzip format; in this
+ case gzread will directly read from the file without decompression. When
+ reading, this will be detected automatically by looking for the magic two-
+ byte gzip header.
+
+ gzopen returns NULL if the file could not be opened, if there was
+ insufficient memory to allocate the gzFile state, or if an invalid mode was
+ specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
+ errno can be checked to determine if the reason gzopen failed was that the
+ file could not be opened.
+*/
+
+ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
+/*
+ gzdopen associates a gzFile with the file descriptor fd. File descriptors
+ are obtained from calls like open, dup, creat, pipe or fileno (if the file
+ has been previously opened with fopen). The mode parameter is as in gzopen.
+
+ The next call of gzclose on the returned gzFile will also close the file
+ descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
+ fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd,
+ mode);. The duplicated descriptor should be saved to avoid a leak, since
+ gzdopen does not close fd if it fails. If you are using fileno() to get the
+ file descriptor from a FILE *, then you will have to use dup() to avoid
+ double-close()ing the file descriptor. Both gzclose() and fclose() will
+ close the associated file descriptor, so they need to have different file
+ descriptors.
+
+ gzdopen returns NULL if there was insufficient memory to allocate the
+ gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not
+ provided, or '+' was provided), or if fd is -1. The file descriptor is not
+ used until the next gz* read, write, seek, or close operation, so gzdopen
+ will not detect if fd is invalid (unless fd is -1).
+*/
+
+ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
+/*
+ Set the internal buffer size used by this library's functions. The
+ default buffer size is 8192 bytes. This function must be called after
+ gzopen() or gzdopen(), and before any other calls that read or write the
+ file. The buffer memory allocation is always deferred to the first read or
+ write. Two buffers are allocated, either both of the specified size when
+ writing, or one of the specified size and the other twice that size when
+ reading. A larger buffer size of, for example, 64K or 128K bytes will
+ noticeably increase the speed of decompression (reading).
+
+ The new buffer size also affects the maximum length for gzprintf().
+
+ gzbuffer() returns 0 on success, or -1 on failure, such as being called
+ too late.
+*/
+
+ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
+/*
+ Dynamically update the compression level or strategy. See the description
+ of deflateInit2 for the meaning of these parameters.
+
+ gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
+ opened for writing.
+*/
+
+ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
+/*
+ Reads the given number of uncompressed bytes from the compressed file. If
+ the input file is not in gzip format, gzread copies the given number of
+ bytes into the buffer directly from the file.
+
+ After reaching the end of a gzip stream in the input, gzread will continue
+ to read, looking for another gzip stream. Any number of gzip streams may be
+ concatenated in the input file, and will all be decompressed by gzread().
+ If something other than a gzip stream is encountered after a gzip stream,
+ that remaining trailing garbage is ignored (and no error is returned).
+
+ gzread can be used to read a gzip file that is being concurrently written.
+ Upon reaching the end of the input, gzread will return with the available
+ data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then
+ gzclearerr can be used to clear the end of file indicator in order to permit
+ gzread to be tried again. Z_OK indicates that a gzip stream was completed
+ on the last gzread. Z_BUF_ERROR indicates that the input file ended in the
+ middle of a gzip stream. Note that gzread does not return -1 in the event
+ of an incomplete gzip stream. This error is deferred until gzclose(), which
+ will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip
+ stream. Alternatively, gzerror can be used before gzclose to detect this
+ case.
+
+ gzread returns the number of uncompressed bytes actually read, less than
+ len for end of file, or -1 for error.
+*/
+
+ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
+ voidpc buf, unsigned len));
+/*
+ Writes the given number of uncompressed bytes into the compressed file.
+ gzwrite returns the number of uncompressed bytes written or 0 in case of
+ error.
+*/
+
+ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...));
+/*
+ Converts, formats, and writes the arguments to the compressed file under
+ control of the format string, as in fprintf. gzprintf returns the number of
+ uncompressed bytes actually written, or 0 in case of error. The number of
+ uncompressed bytes written is limited to 8191, or one less than the buffer
+ size given to gzbuffer(). The caller should assure that this limit is not
+ exceeded. If it is exceeded, then gzprintf() will return an error (0) with
+ nothing written. In this case, there may also be a buffer overflow with
+ unpredictable consequences, which is possible only if zlib was compiled with
+ the insecure functions sprintf() or vsprintf() because the secure snprintf()
+ or vsnprintf() functions were not available. This can be determined using
+ zlibCompileFlags().
+*/
+
+ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
+/*
+ Writes the given null-terminated string to the compressed file, excluding
+ the terminating null character.
+
+ gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
+/*
+ Reads bytes from the compressed file until len-1 characters are read, or a
+ newline character is read and transferred to buf, or an end-of-file
+ condition is encountered. If any characters are read or if len == 1, the
+ string is terminated with a null character. If no characters are read due
+ to an end-of-file or len < 1, then the buffer is left untouched.
+
+ gzgets returns buf which is a null-terminated string, or it returns NULL
+ for end-of-file or in case of error. If there was an error, the contents at
+ buf are indeterminate.
+*/
+
+ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
+/*
+ Writes c, converted to an unsigned char, into the compressed file. gzputc
+ returns the value that was written, or -1 in case of error.
+*/
+
+ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
+/*
+ Reads one byte from the compressed file. gzgetc returns this byte or -1
+ in case of end of file or error. This is implemented as a macro for speed.
+ As such, it does not do all of the checking the other functions do. I.e.
+ it does not check to see if file is NULL, nor whether the structure file
+ points to has been clobbered or not.
+*/
+
+ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
+/*
+ Push one character back onto the stream to be read as the first character
+ on the next read. At least one character of push-back is allowed.
+ gzungetc() returns the character pushed, or -1 on failure. gzungetc() will
+ fail if c is -1, and may fail if a character has been pushed but not read
+ yet. If gzungetc is used immediately after gzopen or gzdopen, at least the
+ output buffer size of pushed characters is allowed. (See gzbuffer above.)
+ The pushed character will be discarded if the stream is repositioned with
+ gzseek() or gzrewind().
+*/
+
+ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
+/*
+ Flushes all pending output into the compressed file. The parameter flush
+ is as in the deflate() function. The return value is the zlib error number
+ (see function gzerror below). gzflush is only permitted when writing.
+
+ If the flush parameter is Z_FINISH, the remaining data is written and the
+ gzip stream is completed in the output. If gzwrite() is called again, a new
+ gzip stream will be started in the output. gzread() is able to read such
+ concatented gzip streams.
+
+ gzflush should be called only when strictly necessary because it will
+ degrade compression if called too often.
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
+ z_off_t offset, int whence));
+
+ Sets the starting position for the next gzread or gzwrite on the given
+ compressed file. The offset represents a number of bytes in the
+ uncompressed data stream. The whence parameter is defined as in lseek(2);
+ the value SEEK_END is not supported.
+
+ If the file is opened for reading, this function is emulated but can be
+ extremely slow. If the file is opened for writing, only forward seeks are
+ supported; gzseek then compresses a sequence of zeroes up to the new
+ starting position.
+
+ gzseek returns the resulting offset location as measured in bytes from
+ the beginning of the uncompressed stream, or -1 in case of error, in
+ particular if the file is opened for writing and the new starting position
+ would be before the current position.
+*/
+
+ZEXTERN int ZEXPORT gzrewind OF((gzFile file));
+/*
+ Rewinds the given file. This function is supported only for reading.
+
+ gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file));
+
+ Returns the starting position for the next gzread or gzwrite on the given
+ compressed file. This position represents a number of bytes in the
+ uncompressed data stream, and is zero when starting, even if appending or
+ reading a gzip stream from the middle of a file using gzdopen().
+
+ gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file));
+
+ Returns the current offset in the file being read or written. This offset
+ includes the count of bytes that precede the gzip stream, for example when
+ appending or when using gzdopen() for reading. When reading, the offset
+ does not include as yet unused buffered input. This information can be used
+ for a progress indicator. On error, gzoffset() returns -1.
+*/
+
+ZEXTERN int ZEXPORT gzeof OF((gzFile file));
+/*
+ Returns true (1) if the end-of-file indicator has been set while reading,
+ false (0) otherwise. Note that the end-of-file indicator is set only if the
+ read tried to go past the end of the input, but came up short. Therefore,
+ just like feof(), gzeof() may return false even if there is no more data to
+ read, in the event that the last read request was for the exact number of
+ bytes remaining in the input file. This will happen if the input file size
+ is an exact multiple of the buffer size.
+
+ If gzeof() returns true, then the read functions will return no more data,
+ unless the end-of-file indicator is reset by gzclearerr() and the input file
+ has grown since the previous end of file was detected.
+*/
+
+ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
+/*
+ Returns true (1) if file is being copied directly while reading, or false
+ (0) if file is a gzip stream being decompressed.
+
+ If the input file is empty, gzdirect() will return true, since the input
+ does not contain a gzip stream.
+
+ If gzdirect() is used immediately after gzopen() or gzdopen() it will
+ cause buffers to be allocated to allow reading the file to determine if it
+ is a gzip file. Therefore if gzbuffer() is used, it should be called before
+ gzdirect().
+
+ When writing, gzdirect() returns true (1) if transparent writing was
+ requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note:
+ gzdirect() is not needed when writing. Transparent writing must be
+ explicitly requested, so the application already knows the answer. When
+ linking statically, using gzdirect() will include all of the zlib code for
+ gzip file reading and decompression, which may not be desired.)
+*/
+
+ZEXTERN int ZEXPORT gzclose OF((gzFile file));
+/*
+ Flushes all pending output if necessary, closes the compressed file and
+ deallocates the (de)compression state. Note that once file is closed, you
+ cannot call gzerror with file, since its structures have been deallocated.
+ gzclose must not be called more than once on the same file, just as free
+ must not be called more than once on the same allocation.
+
+ gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a
+ file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the
+ last read ended in the middle of a gzip stream, or Z_OK on success.
+*/
+
+ZEXTERN int ZEXPORT gzclose_r OF((gzFile file));
+ZEXTERN int ZEXPORT gzclose_w OF((gzFile file));
+/*
+ Same as gzclose(), but gzclose_r() is only for use when reading, and
+ gzclose_w() is only for use when writing or appending. The advantage to
+ using these instead of gzclose() is that they avoid linking in zlib
+ compression or decompression code that is not used when only reading or only
+ writing respectively. If gzclose() is used, then both compression and
+ decompression code will be included the application when linking to a static
+ zlib library.
+*/
+
+ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
+/*
+ Returns the error message for the last error which occurred on the given
+ compressed file. errnum is set to zlib error number. If an error occurred
+ in the file system and not in the compression library, errnum is set to
+ Z_ERRNO and the application may consult errno to get the exact error code.
+
+ The application must not modify the returned string. Future calls to
+ this function may invalidate the previously returned string. If file is
+ closed, then the string previously returned by gzerror will no longer be
+ available.
+
+ gzerror() should be used to distinguish errors from end-of-file for those
+ functions above that do not distinguish those cases in their return values.
+*/
+
+ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
+/*
+ Clears the error and end-of-file flags for file. This is analogous to the
+ clearerr() function in stdio. This is useful for continuing to read a gzip
+ file that is being written concurrently.
+*/
+
+#endif /* !Z_SOLO */
+
+ /* checksum functions */
+
+/*
+ These functions are not related to compression but are exported
+ anyway because they might be useful in applications using the compression
+ library.
+*/
+
+ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
+/*
+ Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+ return the updated checksum. If buf is Z_NULL, this function returns the
+ required initial value for the checksum.
+
+ An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
+ much faster.
+
+ Usage example:
+
+ uLong adler = adler32(0L, Z_NULL, 0);
+
+ while (read_buffer(buffer, length) != EOF) {
+ adler = adler32(adler, buffer, length);
+ }
+ if (adler != original_adler) error();
+*/
+
+/*
+ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
+ z_off_t len2));
+
+ Combine two Adler-32 checksums into one. For two sequences of bytes, seq1
+ and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
+ each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of
+ seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note
+ that the z_off_t type (like off_t) is a signed integer. If len2 is
+ negative, the result has no meaning or utility.
+*/
+
+ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len));
+/*
+ Update a running CRC-32 with the bytes buf[0..len-1] and return the
+ updated CRC-32. If buf is Z_NULL, this function returns the required
+ initial value for the for the crc. Pre- and post-conditioning (one's
+ complement) is performed within this function so it shouldn't be done by the
+ application.
+
+ Usage example:
+
+ uLong crc = crc32(0L, Z_NULL, 0);
+
+ while (read_buffer(buffer, length) != EOF) {
+ crc = crc32(crc, buffer, length);
+ }
+ if (crc != original_crc) error();
+*/
+
+/*
+ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
+
+ Combine two CRC-32 check values into one. For two sequences of bytes,
+ seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
+ calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32
+ check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
+ len2.
+*/
+
+
+ /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method,
+ int windowBits, int memLevel,
+ int strategy, const char *version,
+ int stream_size));
+ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits,
+ const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
+ unsigned char FAR *window,
+ const char *version,
+ int stream_size));
+#define deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+#define inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+#define inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+ (int)sizeof(z_stream))
+#define inflateBackInit(strm, windowBits, window) \
+ inflateBackInit_((strm), (windowBits), (window), \
+ ZLIB_VERSION, (int)sizeof(z_stream))
+
+#ifndef Z_SOLO
+
+/* gzgetc() macro and its supporting function and exposed data structure. Note
+ * that the real internal state is much larger than the exposed structure.
+ * This abbreviated structure exposes just enough for the gzgetc() macro. The
+ * user should not mess with these exposed elements, since their names or
+ * behavior could change in the future, perhaps even capriciously. They can
+ * only be used by the gzgetc() macro. You have been warned.
+ */
+struct gzFile_s {
+ unsigned have;
+ unsigned char *next;
+ z_off64_t pos;
+};
+ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file));
+#define gzgetc(g) \
+ ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc_(g))
+
+/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
+ * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
+ * both are true, the application gets the *64 functions, and the regular
+ * functions are changed to 64 bits) -- in case these are set on systems
+ * without large file support, _LFS64_LARGEFILE must also be true
+ */
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+ ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t));
+#endif
+
+#if !defined(ZLIB_INTERNAL) && _FILE_OFFSET_BITS-0 == 64 && _LFS64_LARGEFILE-0
+# ifdef Z_PREFIX_SET
+# define z_gzopen z_gzopen64
+# define z_gzseek z_gzseek64
+# define z_gztell z_gztell64
+# define z_gzoffset z_gzoffset64
+# define z_adler32_combine z_adler32_combine64
+# define z_crc32_combine z_crc32_combine64
+# else
+# define gzopen gzopen64
+# define gzseek gzseek64
+# define gztell gztell64
+# define gzoffset gzoffset64
+# define adler32_combine adler32_combine64
+# define crc32_combine crc32_combine64
+# endif
+# ifndef _LARGEFILE64_SOURCE
+ ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+ ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int));
+ ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile));
+ ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+# endif
+#else
+ ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *));
+ ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int));
+ ZEXTERN z_off_t ZEXPORT gztell OF((gzFile));
+ ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile));
+ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+#endif
+
+#else /* Z_SOLO */
+
+ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+
+#endif /* !Z_SOLO */
+
+/* hack for buggy compilers */
+#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL)
+ struct internal_state {int dummy;};
+#endif
+
+/* undocumented functions */
+ZEXTERN const char * ZEXPORT zError OF((int));
+ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp));
+ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void));
+ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int));
+ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp));
+ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
+#ifndef Z_SOLO
+ ZEXTERN unsigned long ZEXPORT gzflags OF((void));
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZLIB_H */
diff --git a/src/cpp/core/zlib/zutil.c b/src/cpp/core/zlib/zutil.c
new file mode 100644
index 0000000..8a1d242
--- /dev/null
+++ b/src/cpp/core/zlib/zutil.c
@@ -0,0 +1,301 @@
+/* zutil.c -- target dependent utility functions for the compression library
+ * Copyright (C) 1995-2005, 2010, 2011 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+
+#ifndef NO_DUMMY_DECL
+struct internal_state {int dummy;}; /* for buggy compilers */
+#endif
+
+const char * const z_errmsg[10] = {
+"need dictionary", /* Z_NEED_DICT 2 */
+"stream end", /* Z_STREAM_END 1 */
+"", /* Z_OK 0 */
+"file error", /* Z_ERRNO (-1) */
+"stream error", /* Z_STREAM_ERROR (-2) */
+"data error", /* Z_DATA_ERROR (-3) */
+"insufficient memory", /* Z_MEM_ERROR (-4) */
+"buffer error", /* Z_BUF_ERROR (-5) */
+"incompatible version",/* Z_VERSION_ERROR (-6) */
+""};
+
+
+const char * ZEXPORT zlibVersion()
+{
+ return ZLIB_VERSION;
+}
+
+uLong ZEXPORT zlibCompileFlags()
+{
+ uLong flags;
+
+ flags = 0;
+ switch ((int)(sizeof(uInt))) {
+ case 2: break;
+ case 4: flags += 1; break;
+ case 8: flags += 2; break;
+ default: flags += 3;
+ }
+ switch ((int)(sizeof(uLong))) {
+ case 2: break;
+ case 4: flags += 1 << 2; break;
+ case 8: flags += 2 << 2; break;
+ default: flags += 3 << 2;
+ }
+ switch ((int)(sizeof(voidpf))) {
+ case 2: break;
+ case 4: flags += 1 << 4; break;
+ case 8: flags += 2 << 4; break;
+ default: flags += 3 << 4;
+ }
+ switch ((int)(sizeof(z_off_t))) {
+ case 2: break;
+ case 4: flags += 1 << 6; break;
+ case 8: flags += 2 << 6; break;
+ default: flags += 3 << 6;
+ }
+#ifdef DEBUG
+ flags += 1 << 8;
+#endif
+#if defined(ASMV) || defined(ASMINF)
+ flags += 1 << 9;
+#endif
+#ifdef ZLIB_WINAPI
+ flags += 1 << 10;
+#endif
+#ifdef BUILDFIXED
+ flags += 1 << 12;
+#endif
+#ifdef DYNAMIC_CRC_TABLE
+ flags += 1 << 13;
+#endif
+#ifdef NO_GZCOMPRESS
+ flags += 1L << 16;
+#endif
+#ifdef NO_GZIP
+ flags += 1L << 17;
+#endif
+#ifdef PKZIP_BUG_WORKAROUND
+ flags += 1L << 20;
+#endif
+#ifdef FASTEST
+ flags += 1L << 21;
+#endif
+#ifdef Z_SOLO
+ return flags;
+#else
+ return flags + gzflags();
+#endif
+}
+
+#ifdef DEBUG
+
+# ifndef verbose
+# define verbose 0
+# endif
+int ZLIB_INTERNAL z_verbose = verbose;
+
+void ZLIB_INTERNAL z_error (m)
+ char *m;
+{
+ fprintf(stderr, "%s\n", m);
+ exit(1);
+}
+#endif
+
+/* exported to allow conversion of error code to string for compress() and
+ * uncompress()
+ */
+const char * ZEXPORT zError(err)
+ int err;
+{
+ return ERR_MSG(err);
+}
+
+#if defined(_WIN32_WCE)
+ /* The Microsoft C Run-Time Library for Windows CE doesn't have
+ * errno. We define it as a global variable to simplify porting.
+ * Its value is always 0 and should not be used.
+ */
+ int errno = 0;
+#endif
+
+#ifndef HAVE_MEMCPY
+
+void ZLIB_INTERNAL zmemcpy(dest, source, len)
+ Bytef* dest;
+ const Bytef* source;
+ uInt len;
+{
+ if (len == 0) return;
+ do {
+ *dest++ = *source++; /* ??? to be unrolled */
+ } while (--len != 0);
+}
+
+int ZLIB_INTERNAL zmemcmp(s1, s2, len)
+ const Bytef* s1;
+ const Bytef* s2;
+ uInt len;
+{
+ uInt j;
+
+ for (j = 0; j < len; j++) {
+ if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1;
+ }
+ return 0;
+}
+
+void ZLIB_INTERNAL zmemzero(dest, len)
+ Bytef* dest;
+ uInt len;
+{
+ if (len == 0) return;
+ do {
+ *dest++ = 0; /* ??? to be unrolled */
+ } while (--len != 0);
+}
+#endif
+
+#ifndef Z_SOLO
+
+#ifdef SYS16BIT
+
+#ifdef __TURBOC__
+/* Turbo C in 16-bit mode */
+
+# define MY_ZCALLOC
+
+/* Turbo C malloc() does not allow dynamic allocation of 64K bytes
+ * and farmalloc(64K) returns a pointer with an offset of 8, so we
+ * must fix the pointer. Warning: the pointer must be put back to its
+ * original form in order to free it, use zcfree().
+ */
+
+#define MAX_PTR 10
+/* 10*64K = 640K */
+
+local int next_ptr = 0;
+
+typedef struct ptr_table_s {
+ voidpf org_ptr;
+ voidpf new_ptr;
+} ptr_table;
+
+local ptr_table table[MAX_PTR];
+/* This table is used to remember the original form of pointers
+ * to large buffers (64K). Such pointers are normalized with a zero offset.
+ * Since MSDOS is not a preemptive multitasking OS, this table is not
+ * protected from concurrent access. This hack doesn't work anyway on
+ * a protected system like OS/2. Use Microsoft C instead.
+ */
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
+{
+ voidpf buf = opaque; /* just to make some compilers happy */
+ ulg bsize = (ulg)items*size;
+
+ /* If we allocate less than 65520 bytes, we assume that farmalloc
+ * will return a usable pointer which doesn't have to be normalized.
+ */
+ if (bsize < 65520L) {
+ buf = farmalloc(bsize);
+ if (*(ush*)&buf != 0) return buf;
+ } else {
+ buf = farmalloc(bsize + 16L);
+ }
+ if (buf == NULL || next_ptr >= MAX_PTR) return NULL;
+ table[next_ptr].org_ptr = buf;
+
+ /* Normalize the pointer to seg:0 */
+ *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4;
+ *(ush*)&buf = 0;
+ table[next_ptr++].new_ptr = buf;
+ return buf;
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+ int n;
+ if (*(ush*)&ptr != 0) { /* object < 64K */
+ farfree(ptr);
+ return;
+ }
+ /* Find the original pointer */
+ for (n = 0; n < next_ptr; n++) {
+ if (ptr != table[n].new_ptr) continue;
+
+ farfree(table[n].org_ptr);
+ while (++n < next_ptr) {
+ table[n-1] = table[n];
+ }
+ next_ptr--;
+ return;
+ }
+ ptr = opaque; /* just to make some compilers happy */
+ Assert(0, "zcfree: ptr not found");
+}
+
+#endif /* __TURBOC__ */
+
+
+#ifdef M_I86
+/* Microsoft C in 16-bit mode */
+
+# define MY_ZCALLOC
+
+#if (!defined(_MSC_VER) || (_MSC_VER <= 600))
+# define _halloc halloc
+# define _hfree hfree
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
+{
+ if (opaque) opaque = 0; /* to make compiler happy */
+ return _halloc((long)items, size);
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+ if (opaque) opaque = 0; /* to make compiler happy */
+ _hfree(ptr);
+}
+
+#endif /* M_I86 */
+
+#endif /* SYS16BIT */
+
+
+#ifndef MY_ZCALLOC /* Any system without a special alloc function */
+
+#ifndef STDC
+extern voidp malloc OF((uInt size));
+extern voidp calloc OF((uInt items, uInt size));
+extern void free OF((voidpf ptr));
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
+ voidpf opaque;
+ unsigned items;
+ unsigned size;
+{
+ if (opaque) items += size - size; /* make compiler happy */
+ return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
+ (voidpf)calloc(items, size);
+}
+
+void ZLIB_INTERNAL zcfree (opaque, ptr)
+ voidpf opaque;
+ voidpf ptr;
+{
+ free(ptr);
+ if (opaque) return; /* make compiler happy */
+}
+
+#endif /* MY_ZCALLOC */
+
+#endif /* !Z_SOLO */
diff --git a/src/cpp/core/zlib/zutil.h b/src/cpp/core/zlib/zutil.h
new file mode 100644
index 0000000..dff1112
--- /dev/null
+++ b/src/cpp/core/zlib/zutil.h
@@ -0,0 +1,248 @@
+/* zutil.h -- internal interface and configuration of the compression library
+ * Copyright (C) 1995-2011 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+ part of the implementation of the compression library and is
+ subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZUTIL_H
+#define ZUTIL_H
+
+#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ)
+# define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+# define ZLIB_INTERNAL
+#endif
+
+#include "zlib.h"
+
+#if defined(STDC) && !defined(Z_SOLO)
+# if !(defined(_WIN32_WCE) && defined(_MSC_VER))
+# include <stddef.h>
+# endif
+# include <string.h>
+# include <stdlib.h>
+#endif
+
+#ifdef Z_SOLO
+ typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */
+#endif
+
+#ifndef local
+# define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+typedef unsigned char uch;
+typedef uch FAR uchf;
+typedef unsigned short ush;
+typedef ush FAR ushf;
+typedef unsigned long ulg;
+
+extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
+/* (size given to avoid silly warnings with Visual C++) */
+
+#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)]
+
+#define ERR_RETURN(strm,err) \
+ return (strm->msg = (char*)ERR_MSG(err), (err))
+/* To be used only when the state is known to be valid */
+
+ /* common constants */
+
+#ifndef DEF_WBITS
+# define DEF_WBITS MAX_WBITS
+#endif
+/* default windowBits for decompression. MAX_WBITS is for compression only */
+
+#if MAX_MEM_LEVEL >= 8
+# define DEF_MEM_LEVEL 8
+#else
+# define DEF_MEM_LEVEL MAX_MEM_LEVEL
+#endif
+/* default memLevel */
+
+#define STORED_BLOCK 0
+#define STATIC_TREES 1
+#define DYN_TREES 2
+/* The three kinds of block type */
+
+#define MIN_MATCH 3
+#define MAX_MATCH 258
+/* The minimum and maximum match lengths */
+
+#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
+
+ /* target dependencies */
+
+#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32))
+# define OS_CODE 0x00
+# ifndef Z_SOLO
+# if defined(__TURBOC__) || defined(__BORLANDC__)
+# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__))
+ /* Allow compilation with ANSI keywords only enabled */
+ void _Cdecl farfree( void *block );
+ void *_Cdecl farmalloc( unsigned long nbytes );
+# else
+# include <alloc.h>
+# endif
+# else /* MSC or DJGPP */
+# include <malloc.h>
+# endif
+# endif
+#endif
+
+#ifdef AMIGA
+# define OS_CODE 0x01
+#endif
+
+#if defined(VAXC) || defined(VMS)
+# define OS_CODE 0x02
+# define F_OPEN(name, mode) \
+ fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
+#endif
+
+#if defined(ATARI) || defined(atarist)
+# define OS_CODE 0x05
+#endif
+
+#ifdef OS2
+# define OS_CODE 0x06
+# if defined(M_I86) && !defined(Z_SOLO)
+# include <malloc.h>
+# endif
+#endif
+
+#if defined(MACOS) || defined(TARGET_OS_MAC)
+# define OS_CODE 0x07
+# ifndef Z_SOLO
+# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
+# include <unix.h> /* for fdopen */
+# else
+# ifndef fdopen
+# define fdopen(fd,mode) NULL /* No fdopen() */
+# endif
+# endif
+# endif
+#endif
+
+#ifdef TOPS20
+# define OS_CODE 0x0a
+#endif
+
+#ifdef WIN32
+# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */
+# define OS_CODE 0x0b
+# endif
+#endif
+
+#ifdef __50SERIES /* Prime/PRIMOS */
+# define OS_CODE 0x0f
+#endif
+
+#if defined(_BEOS_) || defined(RISCOS)
+# define fdopen(fd,mode) NULL /* No fdopen() */
+#endif
+
+#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX
+# if defined(_WIN32_WCE)
+# define fdopen(fd,mode) NULL /* No fdopen() */
+# ifndef _PTRDIFF_T_DEFINED
+ typedef int ptrdiff_t;
+# define _PTRDIFF_T_DEFINED
+# endif
+# else
+# define fdopen(fd,type) _fdopen(fd,type)
+# endif
+#endif
+
+#if defined(__BORLANDC__) && !defined(MSDOS)
+ #pragma warn -8004
+ #pragma warn -8008
+ #pragma warn -8066
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_WIN32) && (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0)
+ ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+ ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#endif
+
+ /* common defaults */
+
+#ifndef OS_CODE
+# define OS_CODE 0x03 /* assume Unix */
+#endif
+
+#ifndef F_OPEN
+# define F_OPEN(name, mode) fopen((name), (mode))
+#endif
+
+ /* functions */
+
+#if defined(pyr) || defined(Z_SOLO)
+# define NO_MEMCPY
+#endif
+#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__)
+ /* Use our own functions for small and medium model with MSC <= 5.0.
+ * You may have to use the same strategy for Borland C (untested).
+ * The __SC__ check is for Symantec.
+ */
+# define NO_MEMCPY
+#endif
+#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY)
+# define HAVE_MEMCPY
+#endif
+#ifdef HAVE_MEMCPY
+# ifdef SMALL_MEDIUM /* MSDOS small or medium model */
+# define zmemcpy _fmemcpy
+# define zmemcmp _fmemcmp
+# define zmemzero(dest, len) _fmemset(dest, 0, len)
+# else
+# define zmemcpy memcpy
+# define zmemcmp memcmp
+# define zmemzero(dest, len) memset(dest, 0, len)
+# endif
+#else
+ void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len));
+ int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len));
+ void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len));
+#endif
+
+/* Diagnostic functions */
+#ifdef DEBUG
+# include <stdio.h>
+ extern int ZLIB_INTERNAL z_verbose;
+ extern void ZLIB_INTERNAL z_error OF((char *m));
+# define Assert(cond,msg) {if(!(cond)) z_error(msg);}
+# define Trace(x) {if (z_verbose>=0) fprintf x ;}
+# define Tracev(x) {if (z_verbose>0) fprintf x ;}
+# define Tracevv(x) {if (z_verbose>1) fprintf x ;}
+# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;}
+# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;}
+#else
+# define Assert(cond,msg)
+# define Trace(x)
+# define Tracev(x)
+# define Tracevv(x)
+# define Tracec(c,x)
+# define Tracecv(c,x)
+#endif
+
+#ifndef Z_SOLO
+ voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items,
+ unsigned size));
+ void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr));
+#endif
+
+#define ZALLOC(strm, items, size) \
+ (*((strm)->zalloc))((strm)->opaque, (items), (size))
+#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))
+#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
+
+#endif /* ZUTIL_H */
diff --git a/src/cpp/desktop-mac/AppDelegate.h b/src/cpp/desktop-mac/AppDelegate.h
new file mode 100644
index 0000000..da62321
--- /dev/null
+++ b/src/cpp/desktop-mac/AppDelegate.h
@@ -0,0 +1,11 @@
+
+
+
+#import <AppKit/NSApplication.h>
+
+ at interface AppDelegate : NSObject <NSApplicationDelegate> {
+ NSString* openFile_;
+ NSMenu* dockMenu_;
+}
+ at end
+
diff --git a/src/cpp/desktop-mac/AppDelegate.mm b/src/cpp/desktop-mac/AppDelegate.mm
new file mode 100644
index 0000000..0bca73d
--- /dev/null
+++ b/src/cpp/desktop-mac/AppDelegate.mm
@@ -0,0 +1,433 @@
+
+#include <iostream>
+
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <core/r_util/RProjectFile.hpp>
+#include <core/r_util/REnvironment.hpp>
+
+#import <AppKit/AppKit.h>
+
+#import <Foundation/NSTask.h>
+
+#import "AppDelegate.h"
+#import "Options.hpp"
+#import "SessionLauncher.hpp"
+#import "Utils.hpp"
+#import "MainFrameController.h"
+
+using namespace core;
+using namespace desktop;
+
+NSString* executablePath()
+{
+ FilePath exePath;
+ Error error = core::system::executablePath(NULL, &exePath);
+ if (error)
+ LOG_ERROR(error);
+ return [NSString stringWithUTF8String: exePath.absolutePath().c_str()];
+}
+
+BOOL isProjectFilename(NSString* filename)
+{
+ if (!filename)
+ return NO;
+
+ FilePath filePath([filename UTF8String]);
+ return filePath.exists() && filePath.extensionLowerCase() == ".rproj";
+}
+
+NSString* openFileCommandLineArgument()
+{
+ NSArray *arguments = [[NSProcessInfo processInfo] arguments];
+ int count = [arguments count];
+ if (count > 1) // executable name doesn't count as an argument
+ {
+ for (int i=(count-1); i>0; --i)
+ {
+ NSString* arg = [arguments objectAtIndex: i];
+ if (![arg hasPrefix: @"-psn"] && // avoid process serial number arg
+ ![arg isEqualToString: @"--run-diagnostics"])
+ {
+ return arg;
+ }
+ }
+ }
+
+ return nil;
+}
+
+// PORT: From DesktopMain.cpp
+NSString* verifyAndNormalizeFilename(NSString* filename)
+{
+ if (filename)
+ {
+ // resolve relative path
+ std::string path([filename UTF8String]);
+ if (!FilePath::isRootPath(path))
+ {
+ path = FilePath::safeCurrentPath(
+ FilePath("/")).childPath(path).absolutePath();
+ }
+
+ if (FilePath(path).exists())
+ return [NSString stringWithUTF8String: path.c_str()];
+ else
+ return nil;
+ }
+ else
+ {
+ return nil;
+ }
+}
+
+void initializeSharedSecret()
+{
+ std::string sharedSecret = core::system::generateUuid();
+ desktop::options().setSharedSecret(sharedSecret);
+ core::system::setenv("RS_SHARED_SECRET", sharedSecret);
+}
+
+
+// PORT: from DesktopMain.cpp
+void initializeWorkingDirectory(const std::string& filename)
+{
+ // calculate what our initial working directory should be
+ std::string workingDir;
+
+ // if there is a filename passed to us then use it's path
+ if (!filename.empty())
+ {
+ FilePath filePath(filename);
+ if (filePath.exists())
+ {
+ if (filePath.isDirectory())
+ workingDir = filePath.absolutePath();
+ else
+ workingDir = filePath.parent().absolutePath();
+ }
+ }
+
+ // do additinal detection if necessary
+ if (workingDir.empty())
+ {
+ // get current path
+ FilePath currentPath = FilePath::safeCurrentPath(
+ core::system::userHomePath());
+
+ // detect whether we were launched from the system application menu
+ // (e.g. Dock, Program File icon, etc.). we do this by checking
+ // whether the executable path is within the current path. if we
+ // weren't launched from the system app menu that set the initial
+ // wd to the current path
+ NSString* exePathStr = executablePath();
+ FilePath exePath([exePathStr UTF8String]);
+ if (!exePath.isWithin(currentPath))
+ workingDir = currentPath.absolutePath();
+ }
+
+ // set the working dir if we have one
+ if (!workingDir.empty())
+ core::system::setenv("RS_INITIAL_WD", workingDir);
+}
+
+// PORT: from DesktopMain.cpp
+void setInitialProject(const FilePath& projectFile, std::string* pFilename)
+{
+ core::system::setenv("RS_INITIAL_PROJECT", projectFile.absolutePath());
+ pFilename->clear();
+}
+
+// PORT: from DesktopMain.cpp
+void initializeStartupEnvironment(std::string* pFilename)
+{
+ // if the filename ends with .RData or .rda then this is an
+ // environment file. if it ends with .Rproj then it is
+ // a project file. we handle both cases by setting an environment
+ // var and then resetting the pFilename so it isn't processed
+ // using the standard open file logic
+ FilePath filePath(*pFilename);
+ if (filePath.exists())
+ {
+ std::string ext = filePath.extensionLowerCase();
+
+ // if it is a directory or just an .rdata file then we can see
+ // whether there is a project file we can automatically attach to
+ if (filePath.isDirectory())
+ {
+ FilePath projectFile = r_util::projectFromDirectory(filePath);
+ if (!projectFile.empty())
+ {
+ setInitialProject(projectFile, pFilename);
+ }
+ }
+ else if (ext == ".rproj")
+ {
+ setInitialProject(filePath, pFilename);
+ }
+ else if (ext == ".rdata" || ext == ".rda")
+ {
+ core::system::setenv("RS_INITIAL_ENV", filePath.absolutePath());
+ pFilename->clear();
+ }
+
+ }
+}
+
+// PORT: from DesktopPosixDetectRHome
+bool prepareEnvironment(Options& options)
+{
+ // check for which R override
+ FilePath rWhichRPath;
+ std::string whichROverride = core::system::getenv("RSTUDIO_WHICH_R");
+ if (!whichROverride.empty())
+ rWhichRPath = FilePath(whichROverride);
+
+ // determine rLdPaths script location
+ FilePath supportingFilePath = options.supportingFilePath();
+ FilePath rLdScriptPath = supportingFilePath.complete("bin/r-ldpath");
+ if (!rLdScriptPath.exists())
+ rLdScriptPath = supportingFilePath.complete("session/r-ldpath");
+
+ // attempt to detect R environment
+ std::string rScriptPath, errMsg;
+ r_util::EnvironmentVars rEnvVars;
+ bool success = r_util::detectREnvironment(rWhichRPath,
+ rLdScriptPath,
+ std::string(),
+ &rScriptPath,
+ &rEnvVars,
+ &errMsg);
+ if (!success)
+ {
+ [NSApp activateIgnoringOtherApps: YES];
+ utils::showMessageBox(NSCriticalAlertStyle,
+ @"R Not Found",
+ [NSString stringWithUTF8String: errMsg.c_str()]);
+ return false;
+ }
+
+ if (desktop::options().runDiagnostics())
+ {
+ std::cout << std::endl << "Using R script: " << rScriptPath
+ << std::endl;
+ }
+
+ // set environment and return true
+ r_util::setREnvironmentVars(rEnvVars);
+ return true;
+}
+
+
+
+ at implementation AppDelegate
+
+- (void)dealloc
+{
+ [dockMenu_ release];
+ [openFile_ release];
+ [super dealloc];
+}
+
+- (BOOL) application: (NSApplication *) theApplication
+ openFile:(NSString *) filename
+{
+ // open file and application together
+ if (!openFile_)
+ {
+ openFile_ = [filename copy];
+ }
+ // attemping to open a project in an existing instance, force a new instance
+ else if (isProjectFilename(filename))
+ {
+ NSArray* args = [NSArray arrayWithObject: filename];
+ [NSTask launchedTaskWithLaunchPath: executablePath()
+ arguments: args];
+
+ }
+ // attempt to open a file in an existing instance
+ else
+ {
+ [[MainFrameController instance] openFileInRStudio: filename];
+ }
+
+ return YES;
+}
+
+- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
+{
+ // check for open file request (either apple event or command line)
+ NSString* openFile = verifyAndNormalizeFilename(openFile_);
+ if (!openFile)
+ openFile = verifyAndNormalizeFilename(openFileCommandLineArgument());
+ std::string filename;
+ if (openFile)
+ filename = [openFile UTF8String];
+
+ // intialize options
+ NSArray* arguments = [[NSProcessInfo processInfo] arguments];
+ desktop::options().initFromCommandLine(arguments);
+
+ // reset log if we are in run-diagnostics mode
+ if (desktop::options().runDiagnostics())
+ initializeStderrLog("rdesktop", core::system::kLogLevelWarning);
+
+ // initialize startup environment
+ initializeSharedSecret();
+ initializeWorkingDirectory(filename);
+ initializeStartupEnvironment(&filename);
+ desktop::Options& options = desktop::options();
+ if (!prepareEnvironment(options))
+ {
+ [NSApp terminate: self];
+ return;
+ }
+
+ // get install path
+ FilePath installPath;
+ Error error = core::system::installPath("..", NULL, &installPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ [NSApp terminate: self];
+ return;
+ }
+
+ // calculate paths to config file, rsession, and desktop scripts
+ FilePath confPath, sessionPath, scriptsPath;
+
+ // check for debug configuration
+#ifndef NDEBUG
+ FilePath currentPath = FilePath::safeCurrentPath(installPath);
+ if (currentPath.complete("conf/rdesktop-dev.conf").exists())
+ {
+ confPath = currentPath.complete("conf/rdesktop-dev.conf");
+ sessionPath = currentPath.complete("session/Debug/rsession");
+ scriptsPath = currentPath.complete("desktop-mac");
+ }
+#endif
+
+ // if there is no conf path then release mode
+ if (confPath.empty())
+ {
+ // default paths (then tweak)
+ sessionPath = installPath.complete("bin/rsession");
+ scriptsPath = installPath.complete("bin");
+
+ // check for running in a bundle
+ if (installPath.complete("Info.plist").exists())
+ {
+ sessionPath = installPath.complete("MacOS/rsession");
+ scriptsPath = installPath.complete("MacOS");
+ }
+ }
+
+ // set the scripts path in options
+ desktop::options().setScriptsPath(scriptsPath);
+
+ // setup timer for polling process supervisor
+ [NSTimer scheduledTimerWithTimeInterval: 0.1
+ target: self
+ selector:@selector(pollProcessSupervisor)
+ userInfo:nil
+ repeats: YES];
+
+ // initailize the session launcher and launch the first session
+ sessionLauncher().init(sessionPath, confPath);
+ error = sessionLauncher().launchFirstSession(filename);
+ if (error)
+ {
+ LOG_ERROR(error);
+
+ std::string msg = sessionLauncher().launchFailedErrorMessage();
+
+ [NSApp activateIgnoringOtherApps: YES];
+ utils::showMessageBox(NSCriticalAlertStyle,
+ @"RStudio",
+ [NSString stringWithUTF8String: msg.c_str()]);
+ [NSApp terminate: self];
+ }
+}
+
+- (void) pollProcessSupervisor
+{
+ utils::processSupervisor().poll();
+}
+
+- (NSApplicationTerminateReply) applicationShouldTerminate:
+ (NSApplication *) sender
+{
+ // this code enables us to interrupt a shutdown sequence. ideally we'd
+ // return NSTerminateLater and then later confirm the shutdown via
+ // replyToApplicationShouldTerminate however the fact that initiateQuit
+ // begins an asynchronous process makes that very complicated. the only
+ // downside of return NSTerminateCancel is that in the case where we
+ // block a shutdown an extra dialog appears indicating that RStudio
+ // blocked shutdown. this is considered the lesser of two other evils:
+ // (1) blowing over unsaved changes at shutdown; or (2) introducing
+ // new codepaths into an already complex set of async shutdown codepaths
+ // to handle things correctly
+
+ MainFrameController* mainFrame = [MainFrameController instance];
+ if (!sessionLauncher().sessionProcessActive())
+ {
+ return NSTerminateNow;
+ }
+ else if (mainFrame != nil)
+ {
+ [mainFrame initiateQuit];
+ return NSTerminateCancel;
+ }
+ else
+ {
+ return NSTerminateNow;
+ }
+}
+
+- (void) applicationWillTerminate: (NSNotification *) notification
+{
+ // check for launchFailedErrorMessage
+ std::string err = sessionLauncher().launchFailedErrorMessage();
+ if (!err.empty())
+ {
+ utils::showMessageBox(NSCriticalAlertStyle,
+ @"RStudio",
+ [NSString stringWithUTF8String: err.c_str()]);
+ }
+
+ // cleanup
+ sessionLauncher().cleanupAtExit();
+}
+
+- (BOOL) canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) s
+{
+ return YES;
+}
+
+- (NSMenu *) applicationDockMenu: (NSApplication *) sender
+{
+ if (dockMenu_ == nil)
+ {
+ dockMenu_ = [[[NSMenu alloc] init] retain];
+ [dockMenu_ addItem: [[NSMenuItem alloc]
+ initWithTitle: @"New RStudio Window"
+ action:@selector(launchNewRStudioWindow)
+ keyEquivalent:@""]];
+ }
+ return dockMenu_;
+}
+
+- (void) launchNewRStudioWindow
+{
+ [NSTask launchedTaskWithLaunchPath: executablePath()
+ arguments: [NSArray new]];
+}
+
+ at end
diff --git a/src/cpp/desktop-mac/CMakeLists.txt b/src/cpp/desktop-mac/CMakeLists.txt
new file mode 100644
index 0000000..3a2d95a
--- /dev/null
+++ b/src/cpp/desktop-mac/CMakeLists.txt
@@ -0,0 +1,106 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(DESKTOP_MAC)
+
+# libraries
+find_library(APPKIT_LIBRARY NAMES AppKit)
+find_library(OPENGL_LIBRARY NAMES OpenGL)
+find_library(WEBKIT_LIBRARY NAMES WebKit)
+
+# include files
+file(GLOB_RECURSE DESKTOP_MAC_HEADER_FILES "*.h*")
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/desktop-config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/desktop-config.h)
+
+# source files
+set(DESKTOP_MAC_SOURCE_FILES
+ AppDelegate.mm
+ DockTileView.mm
+ FileDownloader.mm
+ GwtCallbacks.mm
+ Main.mm
+ MainFrameController.mm
+ MainFrameMenu.mm
+ WebViewWithKeyEquiv.mm
+ Options.mm
+ SatelliteController.mm
+ SecondaryWindowController.mm
+ SessionLauncher.mm
+ Utils.mm
+ WebViewController.mm
+
+)
+
+# include directories
+include_directories(
+ include
+ ${CORE_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# configure Info.plist
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+ ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
+
+# collect list of icns files
+file(GLOB ICNS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/icns/*.icns)
+
+# set our icns as the bundle icon
+set(MACOSX_BUNDLE_ICON_FILE RStudio.icns)
+set_source_files_properties(${ICNS_FILES}
+ PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+
+# add image resources to the bundle
+file(GLOB PNG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/png/*.png)
+set_source_files_properties(${PNG_FILES}
+ PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+
+# define bundle name and executable
+set(MACOSX_BUNDLE_BUNDLE_NAME "RStudio")
+
+add_executable(RStudio MACOSX_BUNDLE
+ ${DESKTOP_MAC_SOURCE_FILES}
+ ${DESKTOP_MAC_HEADER_FILES}
+ ${ICNS_FILES}
+ ${PNG_FILES})
+
+
+target_link_libraries(RStudio
+ rstudio-core
+ ${APPKIT_LIBRARY}
+ ${OPENGL_LIBRARY}
+ ${WEBKIT_LIBRARY})
+
+# install target (OSX install goes into the bundle)
+set_target_properties(RStudio PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
+install(TARGETS RStudio BUNDLE DESTINATION .)
+
+# install mac-terminal script
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/mac-terminal.in
+ ${CMAKE_CURRENT_BINARY_DIR}/mac-terminal)
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/mac-terminal
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+# enable developer extras
+exec_program(defaults
+ ARGS write org.rstudio.RStudio WebKitDeveloperExtras -bool true
+ OUTPUT_VARIABLE DEV_EXTRAS)
+
+
+
+
diff --git a/src/cpp/desktop-mac/DockTileView.h b/src/cpp/desktop-mac/DockTileView.h
new file mode 100644
index 0000000..f1e3e85
--- /dev/null
+++ b/src/cpp/desktop-mac/DockTileView.h
@@ -0,0 +1,27 @@
+/*
+ * DockTileView.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#import <Cocoa/Cocoa.h>
+
+ at interface DockTileView : NSView {
+ NSString* label_;
+ BOOL showLabel_;
+}
+
+- (void) setLabel: (NSString*) label;
+- (void) setShowLabel: (BOOL) show;
+
+ at end
diff --git a/src/cpp/desktop-mac/DockTileView.mm b/src/cpp/desktop-mac/DockTileView.mm
new file mode 100644
index 0000000..74cfef7
--- /dev/null
+++ b/src/cpp/desktop-mac/DockTileView.mm
@@ -0,0 +1,258 @@
+/*
+ * DockTileView.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "DockTileView.h"
+#import <Cocoa/Cocoa.h>
+
+// Implementation based on https://github.com/jessesquires/JSCustomBadge
+
+ at implementation DockTileView
+
+- (id) init
+{
+ if (self = [super init])
+ {
+ showLabel_ = FALSE;
+ }
+ return self;
+}
+
+- (void) setLabel: (NSString*) label
+{
+ if(label != label_)
+ {
+ [label retain];
+ [label_ release];
+ label_ = label;
+ }
+}
+
+- (void) setShowLabel: (BOOL) show
+{
+ showLabel_ = show;
+}
+
+- (void) drawRect: (NSRect)rect
+{
+ // get the current bounds
+ NSRect bounds = [self bounds];
+
+ // draw the icon
+ NSImage* icon = [NSImage imageNamed:@"NSApplicationIcon"];
+ [icon setSize:bounds.size];
+ [icon drawAtPoint:NSZeroPoint fromRect:NSZeroRect
+ operation:NSCompositeCopy fraction:1.0];
+
+ // draw the label if needed
+ if (showLabel_ && (label_ != nil))
+ {
+ // sizes all based on the containing bounds
+ const CGFloat kBaseSize = 128;
+ const CGFloat kHeightFactor = 28 / kBaseSize;
+ CGFloat height = kHeightFactor * bounds.size.height;
+ const CGFloat kFontSizeFactor = 16 / kBaseSize;
+ CGFloat fontSize = kFontSizeFactor * bounds.size.height;
+ const CGFloat kInsetFactor = 5 / kBaseSize;
+ CGFloat inset = kInsetFactor * bounds.size.width;
+
+
+ // get the graphics context
+ NSGraphicsContext* nsGraphicsContext = [NSGraphicsContext currentContext];
+ CGContextRef context = (CGContextRef) [nsGraphicsContext graphicsPort];
+
+ // draw the badge rounded rect
+ NSRect badgeRect = NSMakeRect(0, 0, bounds.size.width, height);
+
+ [self drawRoundedRectWithContext: context inRect: badgeRect];
+
+ // shine wasn't adding anything so we eliminated it...
+ //self drawShineWithContext: context inRect: badgeRect];
+
+ [self drawFrameWithContext: context inRect: badgeRect];
+
+
+ NSMutableParagraphStyle *paraStyle=[[NSMutableParagraphStyle alloc] init];
+ [paraStyle setAlignment:NSCenterTextAlignment];
+
+ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSColor whiteColor], NSForegroundColorAttributeName,
+ [NSFont systemFontOfSize: fontSize], NSFontAttributeName,
+ paraStyle, NSParagraphStyleAttributeName,
+ nil];
+
+ NSMutableAttributedString *as = [[NSMutableAttributedString alloc]
+ initWithString: label_
+ attributes:attributes];
+
+ NSRect textRect = NSMakeRect(badgeRect.origin.x + inset,
+ badgeRect.origin.y - (.25 * fontSize),
+ badgeRect.size.width - (2 * inset),
+ badgeRect.size.height);
+ [as drawInRect: textRect];
+ }
+}
+
+
+#define kBadgeCornerRoundness 0.25f
+
+- (void)drawRoundedRectWithContext:(CGContextRef)context inRect:(CGRect)rect
+{
+ CGContextSaveGState(context);
+
+ CGFloat radius = CGRectGetMaxY(rect) * kBadgeCornerRoundness;
+ CGFloat puffer = CGRectGetMaxY(rect) * 0.1f;
+ CGFloat maxX = CGRectGetMaxX(rect) - puffer;
+ CGFloat maxY = CGRectGetMaxY(rect) - puffer;
+ CGFloat minX = CGRectGetMinX(rect) + puffer;
+ CGFloat minY = CGRectGetMinY(rect) + puffer;
+
+ CGContextBeginPath(context);
+ NSColor* fillNsColor = [NSColor colorWithCalibratedRed: 0.05f // iOS badge
+ green: 0.58f
+ blue: 1.0f
+ alpha: 1.0f];
+ CGColorRef fillColor = [self CGColorFromNSColor: fillNsColor];
+ CGContextSetFillColorWithColor(context, fillColor);
+ CGContextAddArc(context, maxX-radius, minY+radius, radius,
+ M_PI+(M_PI/2.0f), 0.0f, 0.0f);
+ CGContextAddArc(context, maxX-radius, maxY-radius, radius,
+ 0.0f, M_PI/2.0f, 0.0f);
+ CGContextAddArc(context, minX+radius, maxY-radius, radius,
+ M_PI/2.0f, M_PI, 0.0f);
+ CGContextAddArc(context, minX+radius, minY+radius, radius,
+ M_PI, M_PI+M_PI/2.0f, 0.0f);
+
+
+ // draw shadow
+ NSColor* shadowNsColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.75f];
+ CGColorRef shadowColor = [self CGColorFromNSColor: shadowNsColor];
+ CGContextSetShadowWithColor(context,
+ CGSizeMake(0.0f, 1.0f),
+ 2.0f,
+ shadowColor);
+
+
+ CGContextFillPath(context);
+
+ CGContextRestoreGState(context);
+
+ CGColorRelease(fillColor);
+ CGColorRelease(shadowColor);
+}
+
+- (void)drawShineWithContext:(CGContextRef)context inRect:(CGRect)rect
+{
+ CGContextSaveGState(context);
+
+ CGContextTranslateCTM(context, 0.0, rect.size.height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+
+ CGFloat radius = CGRectGetMaxY(rect) * kBadgeCornerRoundness;
+ CGFloat puffer = CGRectGetMaxY(rect) * 0.1f;
+ CGFloat maxX = CGRectGetMaxX(rect) - puffer;
+ CGFloat maxY = CGRectGetMaxY(rect) - puffer;
+ CGFloat minX = CGRectGetMinX(rect) + puffer;
+ CGFloat minY = CGRectGetMinY(rect) + puffer;
+
+ CGContextBeginPath(context);
+ CGContextAddArc(context, maxX-radius, minY+radius, radius,
+ M_PI+(M_PI/2.0f), 0.0f, 0.0f);
+ CGContextAddArc(context, maxX-radius, maxY-radius, radius,
+ 0.0f, M_PI/2.0f, 0.0f);
+ CGContextAddArc(context, minX+radius, maxY-radius, radius,
+ M_PI/2.0f, M_PI, 0.0f);
+ CGContextAddArc(context, minX+radius, minY+radius, radius,
+ M_PI, M_PI+M_PI/2.0f, 0.0f);
+ CGContextClip(context);
+
+ size_t num_locations = 2.0f;
+ CGFloat locations[2] = { 0.0f, 0.4f };
+ CGFloat components[8] = { 0.92f, 0.92f, 0.92f, 1.0f,
+ 0.82f, 0.82f, 0.82f, 0.4f };
+
+ CGColorSpaceRef cspace;
+ CGGradientRef gradient;
+ cspace = CGColorSpaceCreateDeviceRGB();
+ gradient = CGGradientCreateWithColorComponents(cspace,
+ components,
+ locations,
+ num_locations);
+
+ CGPoint sPoint, ePoint;
+ sPoint.x = 0.0f;
+ sPoint.y = 0.0f;
+ ePoint.x = 0.0f;
+ ePoint.y = maxY;
+ CGContextDrawLinearGradient (context, gradient, sPoint, ePoint, 0.0f);
+
+ CGColorSpaceRelease(cspace);
+ CGGradientRelease(gradient);
+
+ CGContextRestoreGState(context);
+}
+
+
+- (void)drawFrameWithContext:(CGContextRef)context inRect:(CGRect)rect
+{
+ CGFloat radius = CGRectGetMaxY(rect) * kBadgeCornerRoundness;
+ CGFloat puffer = CGRectGetMaxY(rect) * 0.1f;
+
+ CGFloat maxX = CGRectGetMaxX(rect) - puffer;
+ CGFloat maxY = CGRectGetMaxY(rect) - puffer;
+ CGFloat minX = CGRectGetMinX(rect) + puffer;
+ CGFloat minY = CGRectGetMinY(rect) + puffer;
+
+ CGContextBeginPath(context);
+ CGFloat lineSize = 1.5f;
+
+ CGContextSetLineWidth(context, lineSize);
+ CGColorRef strokeColor = [self CGColorFromNSColor: [NSColor whiteColor]];
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ CGContextAddArc(context, maxX-radius, minY+radius, radius,
+ M_PI + (M_PI/2.0f), 0.0f, 0.0f);
+ CGContextAddArc(context, maxX-radius, maxY-radius, radius, 0.0f,
+ M_PI/2.0f, 0.0f);
+ CGContextAddArc(context, minX+radius, maxY-radius, radius,
+ M_PI/2.0f, M_PI, 0.0f);
+ CGContextAddArc(context, minX+radius, minY+radius, radius,
+ M_PI, M_PI+M_PI/2.0f, 0.0f);
+
+ CGContextClosePath(context);
+ CGContextStrokePath(context);
+
+ CGColorRelease(strokeColor);
+}
+
+
+- (CGColorRef) CGColorFromNSColor: (NSColor*) color
+{
+ NSColor* deviceColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ CGFloat red = [deviceColor redComponent];
+ CGFloat green = [deviceColor greenComponent];
+ CGFloat blue = [deviceColor blueComponent];
+ CGFloat alpha = [deviceColor alphaComponent];
+ const CGFloat components[4] = { red, green, blue, alpha };
+ CGColorSpaceRef deviceRGBColorSpace = CGColorSpaceCreateDeviceRGB();
+ CGColorRef cgColor = CGColorCreate(deviceRGBColorSpace, components);
+ CGColorSpaceRelease(deviceRGBColorSpace);
+ return cgColor;
+}
+
+
+
+
+
+
+ at end
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/FileDownloader.h b/src/cpp/desktop-mac/FileDownloader.h
new file mode 100644
index 0000000..294b697
--- /dev/null
+++ b/src/cpp/desktop-mac/FileDownloader.h
@@ -0,0 +1,23 @@
+/*
+ * FileDownloader.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#import <Cocoa/Cocoa.h>
+
+namespace desktop {
+
+void downloadAndShowFile(NSURLRequest* request);
+
+} // namespace desktop
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/FileDownloader.mm b/src/cpp/desktop-mac/FileDownloader.mm
new file mode 100644
index 0000000..a399fc1
--- /dev/null
+++ b/src/cpp/desktop-mac/FileDownloader.mm
@@ -0,0 +1,125 @@
+/*
+ * FileDownloader.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "FileDownloader.h"
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include "Utils.hpp"
+
+
+ at interface FileDownloader : NSObject<NSURLDownloadDelegate> {
+ NSString* targetPath_;
+}
+
+ at end
+
+ at implementation FileDownloader
+
+- (id) init: (NSURLRequest*) request
+{
+ if (self = [super init])
+ {
+ // create a temp directory
+ core::FilePath tempPath;
+ core::Error error = core::FilePath::tempFilePath(&tempPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ tempPath = core::FilePath("/tmp");
+ }
+ error = tempPath.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ tempPath = core::FilePath("/tmp");
+ }
+ NSString* tempDir = [NSString stringWithUTF8String:
+ tempPath.absolutePath().c_str()];
+
+ // initialize the download and set it's path
+ NSURLDownload *download = [[NSURLDownload alloc] initWithRequest: request
+ delegate: self];
+ if (download)
+ {
+ NSString* filename = [[request URL] lastPathComponent];
+ targetPath_ = [[tempDir stringByAppendingPathComponent: filename] retain];
+
+ [download setDestination: targetPath_ allowOverwrite: YES];
+ }
+ else
+ {
+ NSString* message = [NSString stringWithFormat:
+ @"Unable to Download %@",
+ [[request URL] absoluteURL]];
+
+ desktop::utils::showMessageBox(NSCriticalAlertStyle,
+ @"File Download Failed",
+ message);
+ }
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [targetPath_ release];
+ [super dealloc];
+}
+
+- (void)download:(NSURLDownload *) download didFailWithError: (NSError *) error
+{
+ NSString* message = [NSString stringWithFormat: @"Error - %@ %@",
+ [error localizedDescription],
+ [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]];
+
+ desktop::utils::showMessageBox(NSCriticalAlertStyle,
+ @"File Download Failed",
+ message);
+ [download release];
+ [self release];
+}
+
+- (void)downloadDidFinish: (NSURLDownload *) download
+{
+ if ([targetPath_ hasSuffix: @".pdf"])
+ {
+ [[NSWorkspace sharedWorkspace] openFile: targetPath_
+ withApplication: @"Preview"];
+ }
+ else
+ {
+ [[NSWorkspace sharedWorkspace] openFile: targetPath_];
+ }
+
+ [download release];
+ [self release];
+}
+
+ at end
+
+
+namespace desktop {
+
+void downloadAndShowFile(NSURLRequest* request)
+{
+ [[FileDownloader alloc] init: request]; // self-freeing
+}
+
+} // namespace desktop
+
+
diff --git a/src/cpp/desktop-mac/GwtCallbacks.h b/src/cpp/desktop-mac/GwtCallbacks.h
new file mode 100644
index 0000000..85cb5bb
--- /dev/null
+++ b/src/cpp/desktop-mac/GwtCallbacks.h
@@ -0,0 +1,30 @@
+
+
+#import <Foundation/NSObject.h>
+
+#import "AppDelegate.h"
+
+// An enumeration of message types used by the client (passed to showMessageBox)
+enum MessageType
+{
+ MSG_POPUP_BLOCKED = 0,
+ MSG_INFO = 1,
+ MSG_WARNING = 2,
+ MSG_ERROR = 3,
+ MSG_QUESTION = 4
+};
+
+ at protocol GwtCallbacksUIDelegate
+-(NSWindow*) uiWindow;
+ at end
+
+ at interface GwtCallbacks : NSObject {
+ id<GwtCallbacksUIDelegate> uiDelegate_;
+ id<NSObject> busyActivity_;
+}
+
+// designated initializer
+- (id) initWithUIDelegate: (id<GwtCallbacksUIDelegate>) uiDelegate;
+
+
+ at end
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/GwtCallbacks.mm b/src/cpp/desktop-mac/GwtCallbacks.mm
new file mode 100644
index 0000000..9eda976
--- /dev/null
+++ b/src/cpp/desktop-mac/GwtCallbacks.mm
@@ -0,0 +1,818 @@
+
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#import "GwtCallbacks.h"
+#import "Options.hpp"
+
+#import <Foundation/NSString.h>
+
+#include "SessionLauncher.hpp"
+#include "Utils.hpp"
+
+#import "MainFrameController.h"
+
+using namespace core;
+using namespace desktop;
+
+namespace {
+
+FilePath userHomePath()
+{
+ return core::system::userHomePath("R_USER|HOME");
+}
+
+NSString* createAliasedPath(NSString* path)
+{
+ if (path == nil || [path length] == 0)
+ return @"";
+
+ std::string aliased = FilePath::createAliasedPath(
+ FilePath([path UTF8String]), userHomePath());
+
+ return [NSString stringWithUTF8String: aliased.c_str()];
+}
+
+
+NSString* resolveAliasedPath(NSString* path)
+{
+ if (path == nil)
+ path = @"";
+
+ FilePath resolved = FilePath::resolveAliasedPath([path UTF8String],
+ userHomePath());
+ return [NSString stringWithUTF8String: resolved.absolutePath().c_str()];
+}
+
+} // anonymous namespace
+
+ at implementation GwtCallbacks
+
+- (id) initWithUIDelegate: (id<GwtCallbacksUIDelegate>) uiDelegate
+{
+ if (self = [super init])
+ {
+ uiDelegate_ = uiDelegate;
+ busyActivity_ = nil;
+ }
+ return self;
+}
+
+// sentinel function for gwt deferred binding
+- (Boolean) isCocoa
+{
+ return true;
+}
+
+- (NSString*) proportionalFont
+{
+ return [NSString stringWithUTF8String: options().proportionalFont().c_str()];
+}
+
+- (NSString*) fixedWidthFont
+{
+ return [NSString stringWithUTF8String: options().fixedWidthFont().c_str()];
+}
+
+- (void) browseUrl: (NSString*) url
+{
+ NSURL* nsurl = [NSURL URLWithString: url];
+ desktop::utils::browseURL(nsurl);
+}
+
+- (NSString*) runSheetFileDialog: (NSSavePanel*) panel
+{
+ NSString* path = @"";
+ [panel beginSheetModalForWindow: [uiDelegate_ uiWindow]
+ completionHandler: nil];
+ long int result = [panel runModal];
+ @try
+ {
+ if (result == NSOKButton)
+ {
+ path = [[panel URL] path];
+ }
+ }
+ @catch (NSException* e)
+ {
+ throw e;
+ }
+ @finally
+ {
+ [NSApp endSheet:panel];
+ }
+ return createAliasedPath(path);
+}
+
+- (NSString*) getOpenFileName: (NSString*) caption
+ dir: (NSString*) dir
+ filter: (NSString*) filter
+{
+ dir = resolveAliasedPath(dir);
+
+ NSOpenPanel *open = [NSOpenPanel openPanel];
+ [open setTitle: caption];
+ [open setDirectoryURL: [NSURL fileURLWithPath:
+ [dir stringByStandardizingPath]]];
+ // If the filter was specified and looks like a filter string
+ // (i.e. "R Projects (*.RProj)"), extract just the extension ("RProj") to
+ // pass to the open dialog.
+ if ([filter length] > 0 &&
+ [filter rangeOfString: @"*."].location != NSNotFound)
+ {
+ NSString* toExt = [filter substringFromIndex:
+ [filter rangeOfString: @"*."].location + 2];
+ NSString* fromExt = [toExt substringToIndex:
+ [toExt rangeOfString: @")"].location];
+ [open setAllowedFileTypes: [NSArray arrayWithObject: fromExt]];
+ }
+ return [self runSheetFileDialog: open];
+}
+
+- (NSString*) getSaveFileName: (NSString*) caption
+ dir: (NSString* ) dir
+ defaultExtension: (NSString*) defaultExtension
+ forceDefaultExtension: (Boolean) forceDefaultExtension
+{
+ dir = resolveAliasedPath(dir);
+
+ NSSavePanel *save = [NSSavePanel savePanel];
+
+ BOOL hasDefaultExtension = defaultExtension != nil &&
+ [defaultExtension length] > 0;
+ if (hasDefaultExtension)
+ {
+ // The method is invoked with an extension like ".R", but NSSavePanel
+ // expects extensions to look like "R" (i.e. no leading period).
+ NSArray *extensions = [NSArray arrayWithObject:
+ [defaultExtension substringFromIndex: 1]];
+
+ [save setAllowedFileTypes: extensions];
+ [save setAllowsOtherFileTypes: !forceDefaultExtension];
+ }
+
+ // determine the default filename
+ FilePath filePath([dir UTF8String]);
+ if (!filePath.isDirectory())
+ {
+ std::string filename;
+ if (hasDefaultExtension)
+ filename = filePath.stem();
+ else
+ filename = filePath.filename();
+ [save setNameFieldStringValue:
+ [NSString stringWithUTF8String: filename.c_str()]];
+
+ // In OSX 10.6, leaving the filename as part of the directory (in the
+ // argument to setDirectoryURL below) causes the file to be treated as
+ // though it were a directory itself. Remove it to avoid confusion.
+ NSRange idx = [dir rangeOfString: @"/"
+ options: NSBackwardsSearch];
+ if (idx.location != NSNotFound)
+ dir = [dir substringToIndex: idx.location];
+ }
+
+ NSURL *path = [NSURL fileURLWithPath:
+ [dir stringByStandardizingPath]];
+
+ [save setTitle: caption];
+ [save setDirectoryURL: path];
+ return [self runSheetFileDialog: save];
+}
+
+- (NSString*) getExistingDirectory: (NSString*) caption dir: (NSString*) dir
+{
+ dir = resolveAliasedPath(dir);
+ NSOpenPanel *open = [NSOpenPanel openPanel];
+ [open setTitle: caption];
+ [open setDirectoryURL: [NSURL fileURLWithPath:
+ [dir stringByStandardizingPath]]];
+ [open setCanChooseFiles: false];
+ [open setCanChooseDirectories: true];
+ return [self runSheetFileDialog: open];
+}
+
+- (void) undo
+{
+ if ([NSApp mainWindow] == [[MainFrameController instance] window])
+ {
+ // It appears that using the webView's undoManager doesn't work (for what we want it to do).
+ // It doesn't do anything in the main window when we use the menu to invoke.
+ // However the native handling of Cmd+Z seems to do the right thing.
+ CGEventRef event1, event2, event3, event4;
+ event1 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)55, true);
+ event2 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)6, true);
+ event3 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)6, false);
+ event4 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)55, false);
+ CGEventSetFlags(event2, kCGEventFlagMaskCommand);
+ CGEventSetFlags(event3, kCGEventFlagMaskCommand);
+ CGEventPost(kCGHIDEventTap, event1);
+ CGEventPost(kCGHIDEventTap, event2);
+ CGEventPost(kCGHIDEventTap, event3);
+ CGEventPost(kCGHIDEventTap, event4);
+ }
+ else
+ {
+ // undoManager works just fine on secondary windows, and sending Cmd+Z sends us into an
+ // endless loop of Cmd+Z-ing.
+ WebViewController* webViewController = (WebViewController*)[[NSApp mainWindow] delegate];
+ [[[webViewController webView] undoManager] undo];
+ }
+}
+
+- (void) redo
+{
+ if ([NSApp mainWindow] == [[MainFrameController instance] window])
+ {
+ // It appears that using the webView's undoManager doesn't work (for what we want it to do).
+ // It doesn't do anything in the main window when we use the menu to invoke.
+ // However the native handling of Cmd+Shift+Z seems to do the right thing.
+ CGEventRef event1, event2, event3, event4, event5, event6;
+ event1 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)55, true);
+ event2 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)56, true);
+ event3 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)6, true);
+ event4 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)6, false);
+ event5 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)56, false);
+ event6 = CGEventCreateKeyboardEvent (NULL, (CGKeyCode)55, false);
+ CGEventSetFlags(event3, kCGEventFlagMaskCommand | kCGEventFlagMaskShift);
+ CGEventSetFlags(event4, kCGEventFlagMaskCommand | kCGEventFlagMaskShift);
+ CGEventPost(kCGHIDEventTap, event1);
+ CGEventPost(kCGHIDEventTap, event2);
+ CGEventPost(kCGHIDEventTap, event3);
+ CGEventPost(kCGHIDEventTap, event4);
+ CGEventPost(kCGHIDEventTap, event5);
+ CGEventPost(kCGHIDEventTap, event6);
+ }
+ else
+ {
+ // undoManager works just fine on secondary windows, and sending Cmd+Z sends us into an
+ // endless loop of Cmd+Shift+Z-ing.
+ WebViewController* webViewController = (WebViewController*)[[NSApp mainWindow] delegate];
+ [[[webViewController webView] undoManager] redo];
+ }
+}
+
+- (void) clipboardCut
+{
+ [[[[NSApp mainWindow] windowController] webView] cut: self];
+}
+
+- (void) clipboardCopy
+{
+ [[[[NSApp mainWindow] windowController] webView] copy: self];
+}
+
+- (void) clipboardPaste
+{
+ [[[[NSApp mainWindow] windowController] webView] paste: self];
+}
+
+- (NSString*) getUriForPath: (NSString*) path
+{
+ NSURL* url = [NSURL fileURLWithPath: resolveAliasedPath(path)];
+ return [url absoluteString];
+}
+
+
+- (void) onWorkbenchInitialized: (NSString*) scratchPath
+{
+ [[MainFrameController instance] onWorkbenchInitialized];
+}
+
+- (void) showFolder: (NSString*) path
+{
+ if (path == nil || [path length] == 0)
+ return;
+
+ path = resolveAliasedPath(path);
+
+ [[NSWorkspace sharedWorkspace] openFile: path];
+}
+
+- (void) showFile: (NSString*) path
+{
+ if (path == nil || [path length] == 0)
+ return;
+
+ path = resolveAliasedPath(path);
+
+ // force preview for pdfs
+ if ([path hasSuffix: @".pdf"])
+ {
+ [[NSWorkspace sharedWorkspace] openFile: path
+ withApplication: @"Preview"];
+ }
+ else
+ {
+ [[NSWorkspace sharedWorkspace] openFile: path];
+ }
+}
+
+- (Boolean) isRetina
+{
+ NSWindow* mainWindow = [[MainFrameController instance] window];
+ if ([mainWindow respondsToSelector:@selector(backingScaleFactor)])
+ {
+ double scaleFactor = [mainWindow backingScaleFactor];
+ return scaleFactor == 2.0;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+- (void) openMinimalWindow: (NSString*) name url: (NSString*) url
+ width: (int) width height: (int) height
+{
+ // adjust name to scope within minimal windows
+ name = [name stringByAppendingString: @"_minimal"];
+
+ // check for an existing window with this name
+ WebViewController* controller = [WebViewController windowNamed: name];
+
+ // create a new window if necessary
+ if (!controller)
+ {
+ // self-freeing so don't auto-release
+ controller = [[WebViewController alloc] initWithURLRequest:
+ [NSURLRequest requestWithURL: [NSURL URLWithString: url]]
+ name: name];
+ }
+
+ // reset window size (adjust for title bar height)
+ NSRect frame = [[controller window] frame];
+ NSPoint origin = frame.origin;
+ height += desktop::utils::titleBarHeight();
+ frame = NSMakeRect(origin.x, origin.y, width, height);
+ [[controller window] setFrame: frame display: NO];
+
+ // load url
+ NSURL* nsurl = [NSURL URLWithString: url];
+ [controller loadURL: nsurl];
+
+ // bring to front
+ [[controller window] makeKeyAndOrderFront: self];
+}
+
+- (void) activateSatelliteWindow: (NSString*) name
+{
+ [WebViewController activateSatelliteWindow: name];
+}
+
+- (void) prepareForSatelliteWindow: (NSString*) name
+ width: (int) width height: (int) height
+{
+ [WebViewController prepareForSatelliteWindow: name
+ width: width
+ height: height];
+}
+
+- (void) copyImageToClipboard: (int) left top: (int) top
+ width: (int) width height: (int) height
+{
+ // Unlike the Qt implementation, the Cocoa implementation relies on the
+ // webpage having selected the desired image first.
+ [[[MainFrameController instance] webView] copy: self];
+}
+
+- (Boolean) supportsClipboardMetafile
+{
+ return false;
+}
+
+- (void) modalAlertDidEnd: (void *) alert
+ returnCode: (int) returnCode
+ contextInfo: (int *) contextInfo
+{
+ [NSApp stopModalWithCode: returnCode];
+}
+
+- (int) showMessageBox: (int) type
+ caption: (NSString*) caption
+ message: (NSString*) message
+ buttons: (NSString*) buttons // Pipe-delimited
+ defaultButton: (int) defaultButton
+ cancelButton: (int) cancelButton
+{
+ NSArray *dialogButtons = [buttons componentsSeparatedByString: @"|"];
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+
+ // Translate the message type requested by the client to the appropriate
+ // type of NSAlert
+ NSAlertStyle style = NSInformationalAlertStyle;
+ if (type == MSG_WARNING || type == MSG_ERROR)
+ style = NSWarningAlertStyle;
+
+ // Choose an image type appropriate to the alert
+ NSString* imageName = @"";
+ if (type == MSG_POPUP_BLOCKED)
+ imageName = @"dialog_popup_blocked";
+ else if (type == MSG_INFO)
+ imageName = @"dialog_info";
+ else if (type == MSG_WARNING)
+ imageName = @"dialog_warning";
+ else if (type == MSG_ERROR)
+ imageName = @"dialog_error";
+ else if (type == MSG_QUESTION)
+ imageName = @"dialog_question";
+
+ if ([imageName length] > 0)
+ {
+ [alert setIcon: [NSImage imageNamed: imageName]];
+ }
+
+ [alert setMessageText:caption];
+ [alert setInformativeText:message];
+ for (NSString* buttonText in dialogButtons)
+ {
+ [alert addButtonWithTitle: buttonText];
+ }
+
+ // Make Enter invoke the default button, and ESC the cancel button.
+ [[[alert buttons] objectAtIndex:defaultButton] setKeyEquivalent: @"\r"];
+ [[[alert buttons] objectAtIndex:cancelButton] setKeyEquivalent: @"\033"];
+ [alert beginSheetModalForWindow: [uiDelegate_ uiWindow]
+ modalDelegate: self
+ didEndSelector: @selector(modalAlertDidEnd:returnCode:contextInfo:)
+ contextInfo: nil];
+ // Run the dialog and translate the result
+ int clicked = [NSApp runModalForWindow: [alert window]];
+ switch(clicked)
+ {
+ case NSAlertFirstButtonReturn:
+ return 0;
+ case NSAlertSecondButtonReturn:
+ return 1;
+ }
+ return (clicked - NSAlertThirdButtonReturn) + 2;
+}
+
+- (void) showAboutDialog
+{
+ [NSApp orderFrontStandardAboutPanel: self];
+}
+
+- (void) bringMainFrameToFront
+{
+ [NSApp activateIgnoringOtherApps: YES];
+ [[[MainFrameController instance] window] makeKeyAndOrderFront: self];
+}
+
+- (void) cleanClipboard: (Boolean) stripHtml
+{
+ // Remove all but plain-text and (optionally) HTML data from the pasteboard.
+
+ NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+ if ([[pasteboard pasteboardItems] count] == 0)
+ return;
+
+ NSString* data = [pasteboard stringForType: NSStringPboardType];
+ if (data == nil)
+ return;
+
+ NSString* htmlData = nil;
+ if (!stripHtml)
+ htmlData = [pasteboard stringForType: NSHTMLPboardType];
+
+ [pasteboard clearContents];
+
+ [pasteboard setString: data forType: NSStringPboardType];
+ if (htmlData != nil)
+ [pasteboard setString: htmlData forType: NSHTMLPboardType];
+}
+
+- (void) setPendingQuit: (int) pendingQuit
+{
+ sessionLauncher().setPendingQuit((PendingQuit)pendingQuit);
+}
+
+- (void) openProjectInNewWindow: (NSString*) projectFilePath
+{
+ projectFilePath = resolveAliasedPath(projectFilePath);
+
+ NSString* exePath = [NSString stringWithUTF8String:
+ desktop::options().executablePath().absolutePath().c_str()];
+ NSArray* args = [NSArray arrayWithObject: projectFilePath];
+
+ [NSTask launchedTaskWithLaunchPath: exePath arguments: args];
+}
+
+- (void) openTerminal: (NSString*) terminalPath
+ workingDirectory: (NSString*) workingDirectory
+ extraPathEntries: (NSString*) extraPathEntries
+{
+ // append extra path entries to our path before launching
+ if ([extraPathEntries length] > 0)
+ {
+ std::string path = core::system::getenv("PATH");
+ std::string previousPath = path;
+ core::system::addToPath(&path, [extraPathEntries UTF8String]);
+ core::system::setenv("PATH", path);
+ }
+
+ // call Terminal.app with an applescript that navigates it
+ // to the specified directory. note we don't reference the
+ // passed terminalPath because this setting isn't respected
+ // on the Mac (we always use Terminal.app)
+ FilePath macTermScriptFilePath =
+ desktop::options().scriptsPath().complete("mac-terminal");
+ NSString* exePath = [NSString stringWithUTF8String:
+ macTermScriptFilePath.absolutePath().c_str()];
+ workingDirectory = resolveAliasedPath(workingDirectory);
+ NSArray* args = [NSArray arrayWithObject: workingDirectory];
+ [NSTask launchedTaskWithLaunchPath: exePath arguments: args];
+}
+
+- (NSString*) getFixedWidthFontList
+{
+ NSArray* fonts = [[NSFontManager sharedFontManager]
+ availableFontNamesWithTraits: NSFixedPitchFontMask];
+ return [fonts componentsJoinedByString: @"\n"];
+}
+
+- (NSString*) getFixedWidthFont
+{
+ return [NSString stringWithUTF8String:
+ desktop::options().fixedWidthFont().c_str()];
+}
+
+- (void) setFixedWidthFont: (NSString*) font
+{
+ desktop::options().setFixedWidthFont([font UTF8String]);
+}
+
+- (void) macZoomActualSize
+{
+ // reset the zoom level
+ desktop::options().setZoomLevel(0);
+ [[MainFrameController instance] syncZoomLevel];
+}
+
+
+- (void) macZoomIn
+{
+ // increment the current zoom level
+ desktop::options().setZoomLevel(desktop::options().zoomLevel() + 1);
+ [[MainFrameController instance] syncZoomLevel];
+}
+
+- (void) macZoomOut
+{
+ // decrement the current zoom level
+ desktop::options().setZoomLevel(desktop::options().zoomLevel() - 1);
+ [[MainFrameController instance] syncZoomLevel];
+}
+
+
+- (Boolean) supportsFullscreenMode
+{
+ NSWindow* mainWindow = [[MainFrameController instance] window];
+ return desktop::utils::supportsFullscreenMode(mainWindow);
+}
+
+- (void) toggleFullscreenMode
+{
+ NSWindow* mainWindow = [[MainFrameController instance] window];
+ desktop::utils::toggleFullscreenMode(mainWindow);
+}
+
+- (void) showKeyboardShortcutHelp
+{
+ FilePath keyboardHelpPath = options().wwwDocsPath().complete("keyboard.htm");
+ NSURL* url = [NSURL fileURLWithPath:
+ [NSString stringWithUTF8String: keyboardHelpPath.absolutePath().c_str()]];
+ [[NSWorkspace sharedWorkspace] openURL: url];
+}
+
+- (void) launchSession: (Boolean) reload
+{
+ sessionLauncher().launchNextSession(reload);
+}
+
+- (void) reloadZoomWindow
+{
+ WebViewController* controller =
+ [WebViewController windowNamed: @"_rstudio_zoom_minimal"];
+ if (controller)
+ [[[controller webView] mainFrame] reload];
+}
+
+- (void) setViewerUrl: (NSString*) url
+{
+ [[MainFrameController instance] setViewerURL: url];
+}
+
+- (NSString*) getScrollingCompensationType
+{
+ return @"None";
+}
+
+- (Boolean) isOSXMavericks
+{
+ NSDictionary *systemVersionDictionary =
+ [NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"];
+
+ NSString *systemVersion =
+ [systemVersionDictionary objectForKey:@"ProductVersion"];
+
+ std::string version(
+ [systemVersion cStringUsingEncoding:NSASCIIStringEncoding]);
+
+ return boost::algorithm::starts_with(version, "10.9");
+}
+
+// On Mavericks we need to tell the OS that we are busy so that
+// AppNap doesn't kick in. Declare a local version of NSActivityOptions
+// so we can build this on non-Mavericks systems
+enum RS_NSActivityOptions : uint64_t
+{
+ RS_NSActivityIdleDisplaySleepDisabled = (1ULL << 40),
+ RS_NSActivityIdleSystemSleepDisabled = (1ULL << 20),
+ RS_NSActivitySuddenTerminationDisabled = (1ULL << 14),
+ RS_NSActivityAutomaticTerminationDisabled = (1ULL << 15),
+ RS_NSActivityUserInitiated = (0x00FFFFFFULL | RS_NSActivityIdleSystemSleepDisabled),
+ RS_NSActivityUserInitiatedAllowingIdleSystemSleep = (RS_NSActivityUserInitiated & ~RS_NSActivityIdleSystemSleepDisabled),
+ RS_NSActivityBackground = 0x000000FFULL,
+ RS_NSActivityLatencyCritical = 0xFF00000000ULL,
+};
+
+- (void) setBusy: (Boolean) busy
+{
+ id pi = [NSProcessInfo processInfo];
+ if ([pi respondsToSelector: @selector(beginActivityWithOptions:reason:)])
+ {
+ if (busy && busyActivity_ == nil)
+ {
+ busyActivity_ = [[pi performSelector: @selector(beginActivityWithOptions:reason:)
+ withObject: [NSNumber numberWithInt:
+ RS_NSActivityUserInitiatedAllowingIdleSystemSleep]
+ withObject: @"R Computation"] retain];
+ }
+ else if (!busy && busyActivity_ != nil)
+ {
+ [pi performSelector: @selector(endActivity:) withObject: busyActivity_];
+ [busyActivity_ release];
+ busyActivity_ = nil;
+ }
+ }
+}
+
+- (void) setWindowTitle: (NSString*) title
+{
+ [[MainFrameController instance] setWindowTitle: title];
+}
+
+- (NSString*) filterText: (NSString*) text
+{
+ // Normalize NFD Unicode text. I couldn't reproduce the behavior that made this
+ // necessary in the first place but just in case, and for symmetry with the Qt
+ // code, do the normalization anyway.
+ return [text precomposedStringWithCanonicalMapping];
+}
+
+
+// R version methods are only implemented for front-ends that
+// enable the user to choose from multiple R versions
+
+- (NSString*) getRVersion
+{
+ return @"";
+}
+
+- (NSString*) chooseRVersion
+{
+ return @"";
+}
+
+- (Boolean) canChooseRVersion
+{
+ return false;
+}
+
+// No desktop synctex on the Mac
+
+- (NSString*) getDesktopSynctexViewer
+{
+ return @"";
+}
+
+- (void) externalSynctexPreview: (NSString*) pdfPath page: (int) page
+{
+}
+
+- (void) externalSynctexView: (NSString*) pdfFile
+ srcFile: (NSString*) srcFile
+ line: (int) line
+ column: (int) column
+{
+}
+
+// Custom zoom implementation on the Mac
+
+- (NSString*) getZoomLevels
+{
+ return @"";
+}
+
+- (double) getZoomLevel
+{
+ return 1.0;
+}
+
+- (void) setZoomLevel: (double) zoomLevel
+{
+}
+
+
+// We allow WebTextInput to handle prompt for text in the Cocoa port
+
+- (NSString*) promptForText: (NSString*) title
+ caption: (NSString*) caption
+ defaultValue: (NSString*) defaultValue
+ usePasswordMask: (Boolean) usePasswordMask
+ rememberPasswordPrompt: (NSString*) rememberPasswordPrompt
+ rememberByDefault: (Boolean) rememberByDefault
+ numbersOnly: (Boolean) numbersOnly
+ selectionStart: (int) selectionStart
+ selectionLength: (int) selectionLength
+{
+ return @"";
+}
+
+
++ (NSString *) webScriptNameForSelector: (SEL) sel
+{
+ if (sel == @selector(browseUrl:))
+ return @"browseUrl";
+ else if (sel == @selector(getOpenFileName:dir:filter:))
+ return @"getOpenFileName";
+ else if (sel == @selector(getSaveFileName:dir:defaultExtension:forceDefaultExtension:))
+ return @"getSaveFileName";
+ else if (sel == @selector(getExistingDirectory:dir:))
+ return @"getExistingDirectory";
+ else if (sel == @selector(getUriForPath:))
+ return @"getUriForPath";
+ else if (sel == @selector(onWorkbenchInitialized:))
+ return @"onWorkbenchInitialized";
+ else if (sel == @selector(showFolder:))
+ return @"showFolder";
+ else if (sel == @selector(showFile:))
+ return @"showFile";
+ else if (sel == @selector(openMinimalWindow:url:width:height:))
+ return @"openMinimalWindow";
+ else if (sel == @selector(activateSatelliteWindow:))
+ return @"activateSatelliteWindow";
+ else if (sel == @selector(prepareForSatelliteWindow:width:height:))
+ return @"prepareForSatelliteWindow";
+ else if (sel == @selector(copyImageToClipboard:top:width:height:))
+ return @"copyImageToClipboard";
+ else if (sel == @selector(showMessageBox:caption:message:buttons:defaultButton:cancelButton:))
+ return @"showMessageBox";
+ else if (sel == @selector(promptForText:caption:defaultValue:usePasswordMask:rememberPasswordPrompt:rememberByDefault:numbersOnly:selectionStart:selectionLength:))
+ return @"promptForText";
+ else if (sel == @selector(cleanClipboard:))
+ return @"cleanClipboard";
+ else if (sel == @selector(setPendingQuit:))
+ return @"setPendingQuit";
+ else if (sel == @selector(openProjectInNewWindow:))
+ return @"openProjectInNewWindow";
+ else if (sel == @selector(openTerminal:workingDirectory:extraPathEntries:))
+ return @"openTerminal";
+ else if (sel == @selector(setFixedWidthFont:))
+ return @"setFixedWidthFont";
+ else if (sel == @selector(setZoomLevel:))
+ return @"setZoomLevel";
+ else if (sel == @selector(externalSynctexPreview:page:))
+ return @"externalSynctexPreview";
+ else if (sel == @selector(externalSynctexView:srcFile:line:column:))
+ return @"externalSynctexView";
+ else if (sel == @selector(launchSession:))
+ return @"launchSession";
+ else if (sel == @selector(setViewerUrl:))
+ return @"setViewerUrl";
+ else if (sel == @selector(filterText:))
+ return @"filterText";
+ else if (sel == @selector(setBusy:))
+ return @"setBusy";
+ else if (sel == @selector(setWindowTitle:))
+ return @"setWindowTitle";
+
+ return nil;
+}
+
++ (BOOL)isSelectorExcludedFromWebScript: (SEL) sel
+{
+ if (sel == @selector(setUIDelegate:))
+ return YES;
+ else
+ return NO;
+}
+
+ at end
+
diff --git a/src/cpp/desktop-mac/Info.plist.in b/src/cpp/desktop-mac/Info.plist.in
new file mode 100644
index 0000000..5a7b1e8
--- /dev/null
+++ b/src/cpp/desktop-mac/Info.plist.in
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>fold</string>
+ </array>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>RData</string>
+ <string>Rdata</string>
+ <string>rdata</string>
+ <string>rda</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RData.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Data File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rproj</string>
+ <string>RProj</string>
+ <string>rproj</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RProject.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Project</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>R</string>
+ <string>r</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RSource.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Source File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rd</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RDoc.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Doc File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rnw</string>
+ <string>rnw</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RSweave.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Sweave File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rmd</string>
+ <string>rmd</string>
+ <string>Rmarkdown</string>
+ <string>rmarkdown</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RMarkdown.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Markdown File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rhtml</string>
+ <string>rhtml</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RHTML.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R HTML File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rpres</string>
+ <string>rpres</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RPresentation.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Presentation File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>tex</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RTex.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>TeX File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>md</string>
+ <string>mdtxt</string>
+ <string>markdown</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Markdown.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Markdown File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>htm</string>
+ <string>html</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>HTML.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>HTML File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>css</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>CSS.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>CSS File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>js</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>JS.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Javascript File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ </array>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>RStudio</string>
+ <key>CFBundleGetInfoString</key>
+ <string>RStudio ${CPACK_PACKAGE_VERSION}, © 2009-2012 RStudio, Inc.</string>
+ <key>CFBundleIconFile</key>
+ <string>RStudio.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.rstudio.RStudio</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CFBundleName</key>
+ <string>RStudio</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CFBundleSignature</key>
+ <string>Rstd</string>
+ <key>CFBundleVersion</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CSResourcesFileMapped</key>
+ <true/>
+ <key>LSRequiresCarbon</key>
+ <true/>
+ <key>NSHumanReadableCopyright</key>
+ <string>RStudio ${CPACK_PACKAGE_VERSION}, © 2009-2012 RStudio, Inc.</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSEnvironment</key>
+ <dict>
+ <key>DYLD_VERSIONED_FRAMEWORK_PATH</key>
+ <string>/System/Library/StagedFrameworks/Safari</string>
+ </dict>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.6.0</string>
+</dict>
+</plist>
diff --git a/src/cpp/desktop-mac/Main.mm b/src/cpp/desktop-mac/Main.mm
new file mode 100644
index 0000000..83b3e78
--- /dev/null
+++ b/src/cpp/desktop-mac/Main.mm
@@ -0,0 +1,50 @@
+
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+
+#import <AppKit/NSApplication.h>
+#import <Foundation/NSAutoreleasePool.h>
+
+#import "AppDelegate.h"
+#import "Utils.hpp"
+
+using namespace core;
+
+int main(int argc, char* argv[])
+{
+ // initialize autorelease pool
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+ // initialize language environment variables
+ desktop::utils::initializeLang();
+
+ // initialize log
+ core::system::initializeLog("rdesktop",
+ core::system::kLogLevelWarning,
+ desktop::utils::userLogPath());
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ // initialize application instance
+ [NSApplication sharedApplication];
+
+ // create our app delegate
+ AppDelegate* appDelegate = [[[AppDelegate alloc] init] autorelease];
+ [NSApp setDelegate: appDelegate];
+
+ // run the event loop
+ [NSApp run];
+
+ // free the autorelease pool
+ [pool drain];
+
+ return EXIT_SUCCESS;
+}
+
+
diff --git a/src/cpp/desktop-mac/MainFrameController.h b/src/cpp/desktop-mac/MainFrameController.h
new file mode 100644
index 0000000..1913a27
--- /dev/null
+++ b/src/cpp/desktop-mac/MainFrameController.h
@@ -0,0 +1,58 @@
+/*
+ * MainFrameController.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "WebViewController.h"
+
+#import "MainFrameMenu.h"
+#import "DockTileView.h"
+
+ at interface MainFrameController : WebViewController {
+ BOOL quitConfirmed_;
+ BOOL firstWorkbenchInitialized_;
+ NSString* openFile_;
+ MainFrameMenu* menu_;
+ DockTileView* dockTile_;
+}
+
+// access single instance
++ (MainFrameController*) instance;
+
+// designated initializer
+- (id) initWithURL: (NSURL*) url openFile: (NSString*) openFile;
+
+// noification of workbench initialized
+- (void) onWorkbenchInitialized;
+
+// set the window title
+- (void) setWindowTitle: (NSString*) title;
+
+// open a file association file
+- (void) openFileInRStudio: (NSString*) filename;
+
+// evaluate javascript
+- (id) evaluateJavaScript: (NSString*) js;
+
+- (id) invokeCommand: (NSString*) command;
+
+- (BOOL) isCommandEnabled: (NSString*) command;
+
+
+// initiate a quit sequence
+- (void) initiateQuit;
+
+// quit for real
+- (void) quit;
+
+ at end
diff --git a/src/cpp/desktop-mac/MainFrameController.mm b/src/cpp/desktop-mac/MainFrameController.mm
new file mode 100644
index 0000000..09044c3
--- /dev/null
+++ b/src/cpp/desktop-mac/MainFrameController.mm
@@ -0,0 +1,370 @@
+/*
+ * MainFrameController.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "MainFrameController.h"
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+
+#import "GwtCallbacks.h"
+#import "MainFrameMenu.h"
+#import "Utils.hpp"
+
+#include "SessionLauncher.hpp"
+
+
+
+ at implementation MainFrameController
+
+static MainFrameController* instance_;
+
+// context for tracking all running applications
+const static NSString *kRunningApplicationsContext = @"RunningAppsContext";
+
++ (MainFrameController*) instance
+{
+ return instance_;
+}
+
+- (id) initWithURL: (NSURL*) url openFile: (NSString*) openFile
+{
+ if (self = [super initWithURLRequest: [NSURLRequest requestWithURL: url]
+ name: nil])
+ {
+ // initialize the global instance
+ instance_ = self;
+
+ // initialize flags
+ quitConfirmed_ = NO;
+ firstWorkbenchInitialized_ = NO;
+
+ // retain openFile request
+ if (openFile)
+ openFile_ = [openFile retain];
+
+ // create the main menu
+ menu_ = [[MainFrameMenu alloc] init];
+
+ // auto-save window position
+ [self setWindowFrameAutosaveName: @"RStudio"];
+
+ // set title
+ [[self window] setTitle: @"RStudio"];
+
+ // set dock tile for application
+ dockTile_ = [[DockTileView alloc] init];
+ [[NSApp dockTile] setContentView: dockTile_];
+ [[NSApp dockTile] display];
+
+ // set primary fullscreen mode
+ desktop::utils::enableFullscreenMode([self window], true);
+
+ // webkit version check
+ NSString* userAgent = [webView_
+ stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
+ [self checkWebkitVersion: userAgent];
+
+ // signup for changes in the list of running applications
+ [[NSWorkspace sharedWorkspace] addObserver:self
+ forKeyPath:@"runningApplications"
+ options:NSKeyValueObservingOptionNew
+ context:&kRunningApplicationsContext];
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ // unsubscribe to changes in the list of running applications
+ [[NSWorkspace sharedWorkspace] removeObserver:self
+ forKeyPath:@"runningApplications"
+ context:&kRunningApplicationsContext];
+
+ instance_ = nil;
+ [dockTile_ release];
+ [menu_ release];
+ [openFile_ release];
+ [super dealloc];
+}
+
+- (void) onWorkbenchInitialized
+{
+ // reset state (in case this occurred in response to a manual reload
+ // or reload for a new project context)
+ quitConfirmed_ = NO;
+
+ // determine whether we should show a DockTile label
+ [self updateDockTileShowLabel];
+
+ // see if there is a project dir to display in the titlebar
+ NSString* projectDir = [self evaluateJavaScript:
+ @"window.desktopHooks.getActiveProjectDir()"] ;
+ if ([projectDir length] > 0)
+ {
+ [self setWindowTitle: projectDir];
+ [self updateDockTile: projectDir];
+ }
+ else
+ {
+ [self setWindowTitle: nil];
+ [self updateDockTile: nil];
+ }
+
+ // open file if requested for first workbench
+ if (!firstWorkbenchInitialized_)
+ {
+ if (openFile_)
+ [self openFileInRStudio: openFile_];
+
+ firstWorkbenchInitialized_ = YES;
+ }
+}
+
+- (void) setWindowTitle: (NSString*) title
+{
+ if (title == nil)
+ title = @"RStudio";
+ else
+ title = [title stringByAppendingString: @" - RStudio"];
+
+ [[self window] setTitle: title];
+}
+
+
+// whenever the list of running applications changes then check to see
+// whether we should show project name labels on our dock tile (do it
+// if there is more than one instance of RStudio active)
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ if (context == &kRunningApplicationsContext)
+ [self updateDockTileShowLabel];
+}
+
+
+- (void) updateDockTile: (NSString*) projectDir
+{
+ if (projectDir != nil)
+ [dockTile_ setLabel: [projectDir lastPathComponent]];
+ else
+ [dockTile_ setLabel: nil];
+
+ [[NSApp dockTile] display];
+}
+
+- (void) updateDockTileShowLabel
+{
+ if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:
+ [[NSBundle mainBundle] bundleIdentifier]] count] > 1) {
+ [dockTile_ setShowLabel: TRUE];
+ }
+ else {
+ [dockTile_ setShowLabel: FALSE];
+ }
+
+ [[NSApp dockTile] display];
+}
+
+- (void) openFileInRStudio: (NSString*) openFile
+{
+ // must be absolute
+ std::string filename = [openFile UTF8String];
+ if (!core::FilePath::isRootPath(filename))
+ return;
+
+ // must exist and be a standard file rather than a directory
+ core::FilePath filePath(filename);
+ if (!filePath.exists() || filePath.isDirectory())
+ return;
+
+ // fixup for passing as a javascript string
+ boost::algorithm::replace_all(filename, "\\", "\\\\");
+ boost::algorithm::replace_all(filename, "\"", "\\\"");
+ boost::algorithm::replace_all(filename, "\n", "\\n");
+
+ // execute the openFile command
+ std::string js = "window.desktopHooks.openFile(\"" + filename + "\")";
+ [self evaluateJavaScript: [NSString stringWithUTF8String: js.c_str()]];
+}
+
+
+- (id) evaluateJavaScript: (NSString*) js
+{
+ id win = [webView_ windowScriptObject];
+ return [win evaluateWebScript: js];
+}
+
+- (id) invokeCommand: (NSString*) command
+{
+ static NSArray* noRefocusCommands = [[NSArray alloc] initWithObjects:
+ @"undoDummy", @"redoDummy",
+ @"cutDummy", @"copyDummy", @"pasteDummy",
+ nil];
+
+ if (![noRefocusCommands containsObject: command])
+ [[self window] makeKeyAndOrderFront: self];
+
+ return [self evaluateJavaScript: [NSString stringWithFormat: @"window.desktopHooks.invokeCommand(\"%@\");",
+ command]];
+}
+
+- (BOOL) isCommandEnabled: (NSString*) command
+{
+ return [[self evaluateJavaScript: [NSString stringWithFormat: @"window.desktopHooks.isCommandEnabled(\"%@\");",
+ command]] boolValue];
+}
+
+- (BOOL) hasDesktopObject
+{
+ WebScriptObject* script = [webView_ windowScriptObject];
+ if (script == nil)
+ return NO;
+
+ return [[script evaluateWebScript: @"!!window.desktopHooks"] boolValue];
+}
+
+- (void) initiateQuit
+{
+ [[self window] performClose: self];
+}
+
+- (void) quit
+{
+ quitConfirmed_ = YES;
+ [[self window] performClose: self];
+}
+
+
+- (void) windowDidLoad
+{
+ [super windowDidLoad];
+
+
+}
+
+- (void) checkWebkitVersion: (NSString*) userAgent
+{
+ // parse version info out of user agent string
+ boost::regex re("^.*?AppleWebKit/(\\d+).*$");
+ boost::smatch match;
+ if (boost::regex_match(std::string([userAgent UTF8String]), match, re))
+ {
+ int version = core::safe_convert::stringTo<int>(match[1], 0);
+ if (version < 534)
+ {
+ desktop::utils::showMessageBox(
+ NSWarningAlertStyle,
+ @"Older Version of Safari Detected",
+ @"RStudio uses the Safari WebKit browser engine for rendering "
+ @"its user interface. The minimum required version of Safari is "
+ @"5.1 and an earlier version was detected on your system.\n\n"
+ @"In order to ensure that all RStudio features work correctly "
+ @"please run System Update to install a more recent version "
+ @"of Safari.");
+ }
+ }
+}
+
+// Inject our script object when the window object becomes available
+- (void) webView: (WebView*) webView
+didClearWindowObject:(WebScriptObject *)windowObject
+ forFrame:(WebFrame *)frame
+{
+ // only set the Desktop object for the top level frame
+ if (frame == [webView mainFrame])
+ {
+ // register desktop object
+ [self registerDesktopObject];
+
+ // register main menu callback
+ WebScriptObject* win = [webView_ windowScriptObject];
+ [win setValue: menu_ forKey:@"desktopMenuCallback"];
+ }
+}
+
+- (void) windowDidBecomeMain: (NSNotification *) notification
+{
+ if ([self hasDesktopObject])
+ [self invokeCommand: @"vcsRefreshNoError"];
+}
+
+- (BOOL) windowShouldClose: (id) sender
+{
+ if (quitConfirmed_)
+ {
+ return YES;
+ }
+ else if (!desktop::sessionLauncher().sessionProcessActive())
+ {
+ return YES;
+ }
+ else if (![self hasDesktopObject])
+ {
+ return YES;
+ }
+ else
+ {
+ [self evaluateJavaScript: @"window.desktopHooks.quitR()"];
+ return NO;
+ }
+}
+
+- (BOOL) performKeyEquivalent: (NSEvent *) theEvent
+{
+ NSString* chr = [theEvent charactersIgnoringModifiers];
+ NSUInteger mod = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+
+ if ([chr isEqualToString: @"w"] && mod == NSCommandKeyMask)
+ {
+ if ([self isCommandEnabled: @"closeSourceDoc"])
+ {
+ [self invokeCommand: @"closeSourceDoc"];
+ }
+ else
+ {
+ [[webView_ window] performClose: self];
+ }
+ return YES;
+ }
+ else if (([chr isEqualToString: @"0"] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @"="] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @"-"] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @","] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @"h"] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @"h"] && mod == (NSCommandKeyMask | NSAlternateKeyMask)) ||
+ ([chr isEqualToString: @"m"] && mod == NSCommandKeyMask) ||
+ ([chr isEqualToString: @"m"] && mod == (NSCommandKeyMask | NSAlternateKeyMask)))
+ {
+ // These are shortcuts that only exist on the menu, so they must be invoked there rather
+ // than letting the in-page shortcut manager handle it.
+ // It's possible we could let all key-equivs get dispatched through the menu, but our Qt
+ // desktop code works pretty hard to avoid that and I can't remember why (quite likely
+ // to do with copy/paste I think). Safer to just keep the behavior the same except for
+ // these few cases that are different.
+ if ([[NSApp mainMenu] performKeyEquivalent: theEvent])
+ return YES;
+ }
+
+ return [super performKeyEquivalent: theEvent];
+}
+
+
+ at end
diff --git a/src/cpp/desktop-mac/MainFrameMenu.h b/src/cpp/desktop-mac/MainFrameMenu.h
new file mode 100644
index 0000000..dd917d3
--- /dev/null
+++ b/src/cpp/desktop-mac/MainFrameMenu.h
@@ -0,0 +1,36 @@
+/*
+ * MainFrameMenu.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#import <Foundation/NSObject.h>
+#import <Cocoa/Cocoa.h>
+
+ at interface MainFrameMenu : NSObject <NSMenuDelegate> {
+ NSMenu* mainMenu_;
+ NSMutableArray* menuStack_;
+
+ // Stores a list of the commands that were added as menu items. Each NSMenuItem has
+ // a "tag" property that is an NSInt, and the value of the tag is the index into
+ // this array. In other words, to get the tag for a menu item you would do
+ // [commands_ objectAtIndex: [item tag]]
+ NSMutableArray* commands_;
+
+ NSDictionary* shortcutMap_;
+ NSDictionary* customShortcuts_;
+}
+
+- (BOOL) validateMenuItem: (NSMenuItem *) item;
+
+ at end
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/MainFrameMenu.mm b/src/cpp/desktop-mac/MainFrameMenu.mm
new file mode 100644
index 0000000..1a23d9c
--- /dev/null
+++ b/src/cpp/desktop-mac/MainFrameMenu.mm
@@ -0,0 +1,484 @@
+/*
+ * MenuCallbacks.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#import <Foundation/NSString.h>
+#import <Cocoa/Cocoa.h>
+
+#import "MainFrameController.h"
+
+#import "MainFrameMenu.h"
+
+ at implementation MainFrameMenu
+
+NSString* charToStr(unichar c) {
+ return [[NSString stringWithCharacters: &c length: 1] autorelease];
+}
+
+- (id)init
+{
+ if (self = [super init])
+ {
+ menuStack_ = [[NSMutableArray alloc] init];
+ commands_ = [[NSMutableArray alloc] init];
+ [commands_ addObject: @""]; // Make sure index 0 is not taken
+
+ shortcutMap_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+ @"\uF700", @"Up",
+ @"\uF701", @"Down",
+ @"\uF702", @"Left",
+ @"\uF703", @"Right",
+ @"\uF704", @"F1",
+ @"\uF705", @"F2",
+ @"\uF706", @"F3",
+ @"\uF707", @"F4",
+ @"\uF708", @"F5",
+ @"\uF709", @"F6",
+ @"\uF70A", @"F7",
+ @"\uF70B", @"F8",
+ @"\uF70C", @"F9",
+ @"\uF70D", @"F10",
+ @"\uF70E", @"F11",
+ @"\uF70F", @"F12",
+ @"\uF72C", @"PageUp",
+ @"\uF72D", @"PageDown",
+ @"\n", @"Enter",
+ @"\t", @"Tab",
+ @"\b", @"Backspace",
+ nil];
+
+ customShortcuts_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+ @"Meta+0", @"zoomActualSize",
+ @"Meta+=", @"zoomIn",
+ @"Meta+-", @"zoomOut",
+ @"Meta+Z", @"undoDummy",
+ @"Meta+Shift+Z", @"redoDummy",
+ @"Meta+X", @"cutDummy",
+ @"Meta+C", @"copyDummy",
+ @"Meta+V", @"pasteDummy",
+ nil];
+ // Undo/Redo can't have normal keyboard shortcuts assigned--their keystrokes need to be
+ // handled by WebKit itself. But we still want it to show up in the menu. So just special
+ // case them.
+
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [mainMenu_ release];
+ [menuStack_ release];
+ [commands_ release];
+ [shortcutMap_ release];
+ [customShortcuts_ release];
+ [super dealloc];
+}
+
+- (void) beginMainMenu
+{
+ // create main menu
+ mainMenu_ = [[NSMenu alloc] initWithTitle: @"MainMenu"];
+ [self addAppMenu];
+}
+
+- (void) beginMenu: (NSString*) menuName
+{
+ // remove ampersand
+ menuName = [menuName stringByReplacingOccurrencesOfString:@"&"
+ withString:@""];
+
+ if ([menuName isEqualToString: @"Help"]) {
+ [self addWindowMenu];
+ }
+
+ // create the menu item and add it to the target
+ NSMenuItem* menuItem = [[NSMenuItem new] autorelease];
+ [menuItem setTitleWithMnemonic: menuName];
+ [[self currentTargetMenu] addItem: menuItem];
+
+ // create the menu and associate it with the menu item. we also
+ // turn off "autoenable" so we can manage command states explicitly
+ NSMenu* menu = [[[NSMenu alloc] initWithTitle: menuName] autorelease];
+ [[self currentTargetMenu] setSubmenu: menu forItem: menuItem];
+ [menu setDelegate: self];
+
+ // update the menu stack
+ [menuStack_ addObject: menu];
+}
+
+- (void) addCommand: (NSString*) commandId
+ label: (NSString*) label
+ tooltip: (NSString*) tooltip
+ shortcut: (NSString*) shortcut
+ isCheckable: (Boolean) isCheckable
+{
+ // placeholder text for empty labels (can happen for MRU entries)
+ if ([label length] == 0)
+ label = @"Placeholder";
+
+ // create menu item
+ NSMenuItem* menuItem = [[NSMenuItem new] autorelease];
+ [menuItem setTitleWithMnemonic: label];
+ [menuItem setToolTip: tooltip];
+
+ [menuItem setTag: [commands_ count]];
+ [commands_ addObject: commandId];
+ [menuItem setTarget: self];
+ [menuItem setAction: @selector(invoke:)];
+
+ if ([customShortcuts_ objectForKey: commandId] != nil)
+ shortcut = [customShortcuts_ objectForKey: commandId];
+
+ [self assignShortcut: shortcut toMenuItem: menuItem];
+
+ // add it to the menu
+ [[self currentTargetMenu] addItem: menuItem];
+}
+
+- (void) addSeparator
+{
+ [[self currentTargetMenu] addItem: [NSMenuItem separatorItem]];
+}
+
+- (void) endMenu
+{
+ [menuStack_ removeLastObject];
+}
+
+- (void) endMainMenu
+{
+ [NSApp setMainMenu: mainMenu_];
+}
+
+- (NSMenu*) currentTargetMenu
+{
+ if ([menuStack_ count] == 0)
+ return mainMenu_;
+ else
+ return [menuStack_ lastObject];
+}
+
+- (void) invoke: (id) sender {
+ NSString* command = [commands_ objectAtIndex: [sender tag]];
+ [[MainFrameController instance] invokeCommand: command];
+}
+
+- (void) assignShortcut: (NSString*) shortcut toMenuItem: (NSMenuItem*) menuItem {
+ if ([shortcut length] == 0)
+ return;
+
+ NSArray* parts = [shortcut componentsSeparatedByString: @"+"];
+ NSUInteger modifiers = 0;
+ for (NSUInteger i = 0; i < [parts count] - 1; i++) {
+ NSString* mod = [parts objectAtIndex: i];
+ if ([mod isEqualToString: @"Ctrl"])
+ modifiers |= NSControlKeyMask;
+ else if ([mod isEqualToString: @"Shift"])
+ modifiers |= NSShiftKeyMask;
+ else if ([mod isEqualToString: @"Alt"])
+ modifiers |= NSAlternateKeyMask;
+ else if ([mod isEqualToString: @"Meta"])
+ modifiers |= NSCommandKeyMask;
+ }
+ NSString* key = [parts lastObject];
+ [menuItem setKeyEquivalentModifierMask: modifiers];
+ if ([key length] == 1) {
+ [menuItem setKeyEquivalent: [key lowercaseString]];
+ } else {
+ NSString* keyEquiv = [shortcutMap_ objectForKey: key];
+ assert(keyEquiv != Nil);
+ [menuItem setKeyEquivalent: keyEquiv];
+ }
+}
+
+- (BOOL) validateMenuItem: (NSMenuItem *) item {
+ // no need to further validate items that aren't associated with a command
+ if ([item tag] == 0) {
+ return YES;
+ }
+
+ NSString* command = [commands_ objectAtIndex: [item tag]];
+
+ NSString* labelJs = [NSString stringWithFormat: @"window.desktopHooks.getCommandLabel(\"%@\");", command];
+ NSString* title = [[MainFrameController instance] evaluateJavaScript: labelJs];
+ if (title == nil) // setTitleWithMnemonic raises exception if passed nil
+ title = @"";
+ [item setTitleWithMnemonic: title];
+
+ NSString* checkedJs = [NSString stringWithFormat: @"window.desktopHooks.isCommandChecked(\"%@\");", command];
+ if ([[[MainFrameController instance] evaluateJavaScript: checkedJs] boolValue])
+ [item setState: NSOnState];
+ else
+ [item setState: NSOffState];
+
+ NSString* visibleJs = [NSString stringWithFormat: @"window.desktopHooks.isCommandVisible(\"%@\");", command];
+ [item setHidden: ![[[MainFrameController instance] evaluateJavaScript: visibleJs] boolValue]];
+
+ // Suppress any unnecessary separators. This code will run once per menu item which seems more
+ // effort than necessary, but there's no guarantee that I know of that validateMenuItem will be
+ // called from top to bottom, and it's fast anyway.
+ NSMenu* menu = [item menu];
+ bool suppressSep = TRUE; // When TRUE, we don't need any more seps at this point in the menu.
+ NSMenuItem* trailingSep = Nil; // If non-null when we're done looping, an extraneous trailing sep.
+ for (NSMenuItem* i in [menu itemArray]) {
+ if ([i isSeparatorItem]) {
+ [i setHidden: suppressSep];
+ if (!suppressSep) {
+ trailingSep = i;
+ suppressSep = TRUE;
+ }
+ } else if (![i isHidden]) {
+ // We've encountered a non-hidden, non-sep menu entry; the next sep should be shown.
+ suppressSep = FALSE;
+ trailingSep = Nil;
+ }
+ }
+ if (trailingSep != Nil)
+ [trailingSep setHidden: YES];
+
+ if ([[MainFrameController instance] isCommandEnabled: command])
+ return YES;
+ else
+ return NO;
+}
+
+- (void) addAppMenu {
+ NSMenuItem* appMenuItem = [[NSMenuItem new] autorelease];
+ [mainMenu_ addItem: appMenuItem];
+ [NSApp setMainMenu: mainMenu_];
+
+ // create app menu (currently just has quit)
+ NSMenu* appMenu = [[NSMenu new] autorelease];
+ [appMenuItem setSubmenu: appMenu];
+
+ // "About RStudio"
+ NSMenuItem* aboutMenuItem = [[NSMenuItem alloc]
+ initWithTitle: @"About RStudio"
+ action: @selector(showAbout:)
+ keyEquivalent: @""];
+ [aboutMenuItem setTarget: self];
+ [appMenu addItem: aboutMenuItem];
+
+ [appMenu addItem: [NSMenuItem separatorItem]];
+
+ // "Preferences..."
+ NSMenuItem* prefsMenuItem = [[NSMenuItem alloc]
+ initWithTitle: @"Preferences\u2026"
+ action: @selector(showPrefs:)
+ keyEquivalent: @","];
+ [prefsMenuItem setTarget: self];
+ [prefsMenuItem setKeyEquivalentModifierMask: NSCommandKeyMask];
+ [appMenu addItem: prefsMenuItem];
+
+ [appMenu addItem: [NSMenuItem separatorItem]];
+
+ /*
+ // "Services"
+ // These don't currently appear to actually work correctly; text in the console input
+ // area and in Ace aren't picked up (though console output works fine).
+ NSMenuItem* servicesMenuItem = [[NSMenuItem new] autorelease];
+ [servicesMenuItem setTitle: @"Services"];
+ [appMenu addItem: servicesMenuItem];
+ NSMenu* servicesMenu = [[NSMenu new] autorelease];
+ [NSApp setServicesMenu: servicesMenu];
+ [servicesMenuItem setSubmenu: servicesMenu];
+
+ [appMenu addItem: [NSMenuItem separatorItem]];
+ */
+
+ // "Hide RStudio"
+ NSMenuItem* hideMenuItem = [[[NSMenuItem alloc] initWithTitle: @"Hide RStudio"
+ action: @selector(hide:)
+ keyEquivalent: @"h"] autorelease];
+ [hideMenuItem setTarget: NSApp];
+ [hideMenuItem setKeyEquivalentModifierMask: NSCommandKeyMask];
+ [appMenu addItem: hideMenuItem];
+
+ // "Hide Others"
+ NSMenuItem* hideOthersMenuItem = [[[NSMenuItem alloc] initWithTitle: @"Hide Others"
+ action: @selector(hideOtherApplications:)
+ keyEquivalent: @"h"] autorelease];
+ [hideOthersMenuItem setTarget: NSApp];
+ [hideOthersMenuItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
+ [appMenu addItem: hideOthersMenuItem];
+
+ // "Show All"
+ NSMenuItem* showAllMenuItem = [[[NSMenuItem alloc] initWithTitle: @"Show All"
+ action: @selector(unhideAllApplications:)
+ keyEquivalent: @""] autorelease];
+ [showAllMenuItem setTarget: NSApp];
+ [appMenu addItem: showAllMenuItem];
+
+ [appMenu addItem: [NSMenuItem separatorItem]];
+
+ // "Quit RStudio"
+ NSMenuItem* quitMenuItem = [[[NSMenuItem alloc]
+ initWithTitle: @"Quit RStudio"
+ action: @selector(initiateQuit)
+ keyEquivalent:@"q"] autorelease];
+ [quitMenuItem setTarget: [MainFrameController instance]];
+ [appMenu addItem: quitMenuItem];
+}
+
+- (void) addWindowMenu {
+ NSMenuItem* windowMenuItem = [[NSMenuItem new] autorelease];
+ [windowMenuItem setTitleWithMnemonic: @"Window"];
+ [[self currentTargetMenu] addItem: windowMenuItem];
+
+ NSMenu* windowMenu = [[[NSMenu alloc] initWithTitle: @"Window"] autorelease];
+ [[self currentTargetMenu] setSubmenu: windowMenu forItem: windowMenuItem];
+
+ NSMenuItem* minimize = [[NSMenuItem new] autorelease];
+ [minimize setTitle: @"Minimize"];
+ [minimize setTarget: self];
+ [minimize setAction: @selector(minimize:)];
+ [minimize setKeyEquivalent: @"m"];
+ [minimize setKeyEquivalentModifierMask: NSCommandKeyMask];
+ [minimize setAlternate: NO];
+ [minimize setTag: 0];
+ [windowMenu addItem: minimize];
+
+ NSMenuItem* minimizeAll = [[NSMenuItem new] autorelease];
+ [minimizeAll setTitle: @"Minimize All"];
+ [minimizeAll setTarget: NSApp];
+ [minimizeAll setAction: @selector(miniaturizeAll:)];
+ [minimizeAll setKeyEquivalent: @"m"];
+ [minimizeAll setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
+ [minimizeAll setAlternate: YES];
+ [minimizeAll setTag: 0];
+ [windowMenu addItem: minimizeAll];
+
+ NSMenuItem* zoom = [[NSMenuItem new] autorelease];
+ [zoom setTitle: @"Zoom"];
+ [zoom setTarget: self];
+ [zoom setAction: @selector(zoom:)];
+ [zoom setAlternate: NO];
+ [zoom setTag: 0];
+ [windowMenu addItem: zoom];
+
+ NSMenuItem* zoomAll = [[NSMenuItem new] autorelease];
+ [zoomAll setTitle: @"Zoom All"];
+ [zoomAll setTarget: NSApp];
+ [zoomAll setAction: @selector(zoomAll:)];
+ [zoomAll setKeyEquivalentModifierMask: NSAlternateKeyMask];
+ [zoomAll setAlternate: YES];
+ [zoomAll setTag: 0];
+ [windowMenu addItem: zoomAll];
+
+ [windowMenu addItem: [NSMenuItem separatorItem]];
+
+ NSMenuItem* bringAllToFront = [[NSMenuItem new] autorelease];
+ [bringAllToFront setTitle: @"Bring All to Front"];
+ [bringAllToFront setTarget: self];
+ [bringAllToFront setAction: @selector(bringAllToFront:)];
+ [bringAllToFront setKeyEquivalentModifierMask: NSAlternateKeyMask];
+ [bringAllToFront setAlternate: YES];
+ [bringAllToFront setTag: 0];
+ [windowMenu addItem: bringAllToFront];
+
+ [windowMenu addItem: [NSMenuItem separatorItem]];
+
+ [NSApp setWindowsMenu: windowMenu];
+}
+
+- (void) minimize: (id) sender {
+ [[NSApp keyWindow] performMiniaturize: sender];
+}
+
+- (void) zoom: (id) sender {
+ [[NSApp keyWindow] performZoom: sender];
+}
+
+- (void) bringAllToFront: (id) sender {
+ for (NSWindow* window in [NSApp windows]) {
+ [window orderFront: self];
+ }
+ [[NSApp mainWindow] makeKeyAndOrderFront: self];
+}
+
+- (void) showAbout: (id) sender {
+ [[MainFrameController instance] invokeCommand: @"showAboutDialog"];
+}
+
+- (void) showPrefs: (id) sender {
+ [[MainFrameController instance] invokeCommand: @"showOptions"];
+}
+
+- (void) menuNeedsUpdate: (NSMenu *) menu
+{
+ for (NSMenuItem* item in [menu itemArray])
+ {
+ if ([item hasSubmenu])
+ {
+ // check to see if we need to hide the submenu because all the
+ // commands in the submenu are hidden
+ bool hide = true;
+ NSMenu* submenu = [item submenu];
+ for (NSMenuItem* i in [submenu itemArray])
+ {
+ if ([i tag] != 0)
+ {
+ // it's a command, check visibility state
+ NSString* command = [commands_ objectAtIndex: [i tag]];
+ NSString* visibleJs =
+ [NSString stringWithFormat:
+ @"window.desktopHooks.isCommandVisible(\"%@\");", command];
+ id result = [[MainFrameController instance] evaluateJavaScript: visibleJs];
+ if ([result boolValue])
+ {
+ hide = false;
+ break;
+ }
+ }
+ else if (![i isSeparatorItem])
+ {
+ // not a command or a separator, so avoid hiding it
+ hide = false;
+ break;
+ }
+ }
+ [item setHidden: hide];
+ }
+ }
+}
+
++ (NSString *) webScriptNameForSelector: (SEL) sel
+{
+ if (sel == @selector(beginMenu:))
+ return @"beginMenu";
+ else if (sel == @selector(addCommand:label:tooltip:shortcut:isCheckable:))
+ return @"addCommand";
+
+ return nil;
+}
+
++ (BOOL)isSelectorExcludedFromWebScript: (SEL) sel
+{
+ if (sel == @selector(beginMainMenu) ||
+ sel == @selector(beginMenu:) ||
+ sel == @selector(addCommand:label:tooltip:shortcut:isCheckable:) ||
+ sel == @selector(addSeparator) ||
+ sel == @selector(endMenu) ||
+ sel == @selector(endMainMenu)) {
+ return NO;
+ }
+ else {
+ return YES;
+ }
+}
+
+ at end
+
diff --git a/src/cpp/desktop-mac/Options.hpp b/src/cpp/desktop-mac/Options.hpp
new file mode 100644
index 0000000..46c9597
--- /dev/null
+++ b/src/cpp/desktop-mac/Options.hpp
@@ -0,0 +1,81 @@
+/*
+ * Options.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_OPTIONS_HPP
+#define DESKTOP_OPTIONS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <core/FilePath.hpp>
+
+#import <Foundation/NSArray.h>
+
+namespace desktop {
+
+class Options;
+Options& options();
+
+class Options : boost::noncopyable
+{
+private:
+ Options();
+ friend Options& options();
+
+public:
+ void initFromCommandLine(NSArray* arguments);
+
+ std::string portNumber() const;
+ std::string newPortNumber();
+
+ std::string sharedSecret() const { return sharedSecret_; }
+ void setSharedSecret(const std::string& secret) { sharedSecret_ = secret; }
+
+ std::string proportionalFont() const;
+ std::string fixedWidthFont() const;
+ void setFixedWidthFont(std::string font);
+
+ int zoomLevel() const;
+ void setZoomLevel(int zoomLevel);
+
+ core::FilePath scriptsPath() const;
+ void setScriptsPath(const core::FilePath& scriptsPath);
+
+ core::FilePath executablePath() const;
+ core::FilePath supportingFilePath() const;
+
+ core::FilePath wwwDocsPath() const;
+
+ std::vector<std::string> ignoredUpdateVersions() const;
+ void setIgnoredUpdateVersions(const std::vector<std::string>& ignored);
+
+ bool runDiagnostics() { return runDiagnostics_; }
+
+private:
+ std::string sharedSecret_;
+ core::FilePath scriptsPath_;
+ mutable core::FilePath executablePath_;
+ mutable core::FilePath supportingFilePath_;
+ mutable std::string portNumber_;
+ bool runDiagnostics_;
+
+
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_OPTIONS_HPP
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/Options.mm b/src/cpp/desktop-mac/Options.mm
new file mode 100644
index 0000000..7d83a16
--- /dev/null
+++ b/src/cpp/desktop-mac/Options.mm
@@ -0,0 +1,186 @@
+/*
+ * Options.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <core/FilePath.hpp>
+#include <core/Random.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+
+#import <Foundation/NSString.h>
+#import <Foundation/NSUserDefaults.h>
+#import <Foundation/NSDictionary.h>
+
+#import "Options.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+Options& options()
+{
+ static Options instance;
+ return instance;
+}
+
+Options::Options()
+ : runDiagnostics_(false)
+{
+ // initialize user defaults
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ NSDictionary* defs = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"Lucida Grande", @"font.proportional",
+ @"Monaco", @"font.fixedwidth",
+ @"0", @"view.maczoomlevel",
+ [NSArray array], @"updates.ignored",
+ nil];
+
+ [prefs registerDefaults:defs];
+}
+
+void Options::initFromCommandLine(NSArray* arguments)
+{
+ for (NSString* arg in arguments)
+ {
+ if ([arg isEqualToString: @"--run-diagnostics"])
+ runDiagnostics_ = true;
+ }
+}
+
+std::string Options::portNumber() const
+{
+ // lookup / generate on demand
+ if (portNumber_.empty())
+ {
+ // Use a random-ish port number to avoid collisions between different
+ // instances of rdesktop-launched rsessions
+ int base = std::abs(random::uniformRandomInteger<int>());
+ int port = (base % 40000) + 8080;
+ portNumber_ = safe_convert::numberToString(port);
+ }
+ return portNumber_;
+}
+
+std::string Options::newPortNumber()
+{
+ portNumber_.clear();
+ return portNumber();
+}
+
+std::string Options::proportionalFont() const
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ return [[prefs stringForKey: @"font.proportional"] UTF8String];
+}
+
+std::string Options::fixedWidthFont() const
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ return [[prefs stringForKey: @"font.fixedwidth"] UTF8String];
+}
+
+void Options::setFixedWidthFont(std::string font)
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ [prefs setObject: [NSString stringWithUTF8String: font.c_str()]
+ forKey: @"font.fixedwidth"];
+}
+
+int Options::zoomLevel() const
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ NSString* zoomLevel = [prefs stringForKey: @"view.maczoomlevel"];
+ return [zoomLevel intValue];
+}
+
+void Options::setZoomLevel(int zoomLevel)
+{
+ // enforce min and max
+ if (zoomLevel > 6)
+ zoomLevel = 6;
+ else if (zoomLevel < -3)
+ zoomLevel = -3;
+
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ NSNumber* zoom = [NSNumber numberWithInt: zoomLevel];
+ [prefs setObject: [zoom stringValue] forKey: @"view.maczoomlevel"];
+}
+
+FilePath Options::scriptsPath() const
+{
+ return scriptsPath_;
+}
+
+void Options::setScriptsPath(const FilePath& scriptsPath)
+{
+ scriptsPath_ = scriptsPath;
+}
+
+core::FilePath Options::executablePath() const
+{
+ if (executablePath_.empty())
+ {
+ Error error = core::system::executablePath(NULL, &executablePath_);
+ if (error)
+ LOG_ERROR(error);
+ }
+ return executablePath_;
+}
+
+FilePath Options::supportingFilePath() const
+{
+ if (supportingFilePath_.empty())
+ {
+ // default to install path
+ core::system::installPath("..", NULL, &supportingFilePath_);
+
+ // adapt for OSX resource bundles
+ if (supportingFilePath_.complete("Info.plist").exists())
+ supportingFilePath_ = supportingFilePath_.complete("Resources");
+ }
+ return supportingFilePath_;
+}
+
+FilePath Options::wwwDocsPath() const
+{
+ FilePath supportingPath = desktop::options().supportingFilePath();
+ FilePath wwwDocsPath = supportingPath.complete("www/docs");
+ if (!wwwDocsPath.exists())
+ wwwDocsPath = supportingPath.complete("../../../../../../gwt/www/docs");
+ return wwwDocsPath;
+}
+
+std::vector<std::string> Options::ignoredUpdateVersions() const
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ NSArray* ignored = [prefs objectForKey: @"updates.ignored"];
+ std::vector<std::string> ignoredUpdates;
+ for (NSString* ver in ignored)
+ ignoredUpdates.push_back([ver UTF8String]);
+ return ignoredUpdates;
+}
+
+void Options::setIgnoredUpdateVersions(const std::vector<std::string>& ignored)
+{
+ NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
+ NSMutableArray* ign = [NSMutableArray array];
+ for (size_t i = 0; i<ignored.size(); i++)
+ [ign addObject: [NSString stringWithUTF8String: ignored[i].c_str()]];
+ [prefs setObject: ign forKey: @"updates.ignored"];
+}
+
+
+} // namespace desktop
+
diff --git a/src/cpp/desktop-mac/RProject.ico b/src/cpp/desktop-mac/RProject.ico
new file mode 100644
index 0000000..c86785f
Binary files /dev/null and b/src/cpp/desktop-mac/RProject.ico differ
diff --git a/src/cpp/desktop-mac/RStudio.ico b/src/cpp/desktop-mac/RStudio.ico
new file mode 100644
index 0000000..8644e72
Binary files /dev/null and b/src/cpp/desktop-mac/RStudio.ico differ
diff --git a/src/cpp/desktop-mac/SatelliteController.h b/src/cpp/desktop-mac/SatelliteController.h
new file mode 100644
index 0000000..badcf12
--- /dev/null
+++ b/src/cpp/desktop-mac/SatelliteController.h
@@ -0,0 +1,23 @@
+/*
+ * SatelliteController.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "WebViewController.h"
+
+
+
+ at interface SatelliteController : WebViewController
+
+
+ at end
diff --git a/src/cpp/desktop-mac/SatelliteController.mm b/src/cpp/desktop-mac/SatelliteController.mm
new file mode 100644
index 0000000..dab2174
--- /dev/null
+++ b/src/cpp/desktop-mac/SatelliteController.mm
@@ -0,0 +1,53 @@
+/*
+ * SatelliteController.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "SatelliteController.h"
+
+#import "GwtCallbacks.h"
+
+ at implementation SatelliteController
+
+// Inject our script ojbect when the window object becomes available
+- (void) webView: (WebView*) webView
+didClearWindowObject:(WebScriptObject *)windowObject
+ forFrame:(WebFrame *)frame
+{
+ // only set the Desktop object for the top level frame
+ if (frame == [webView mainFrame])
+ {
+ // register objective-c objects with javascript
+ [self registerDesktopObject];
+ }
+}
+
+- (BOOL) windowShouldClose: (id) sender
+{
+ id win = [webView_ windowScriptObject];
+ [win evaluateWebScript:
+ @"if (window.notifyRStudioSatelliteClosing) "
+ " window.notifyRStudioSatelliteClosing();"];
+
+ return YES;
+}
+
+- (void) windowDidBecomeMain: (NSNotification *) notification
+{
+ id win = [webView_ windowScriptObject];
+ [win evaluateWebScript:
+ @"if (window.notifyRStudioSatelliteReactivated) "
+ " window.notifyRStudioSatelliteReactivated(null);"];
+}
+
+ at end
diff --git a/src/cpp/desktop-mac/SecondaryWindowController.h b/src/cpp/desktop-mac/SecondaryWindowController.h
new file mode 100644
index 0000000..1c51ad9
--- /dev/null
+++ b/src/cpp/desktop-mac/SecondaryWindowController.h
@@ -0,0 +1,22 @@
+/*
+ * SecondaryWindowController.h
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "WebViewController.h"
+
+ at interface SecondaryWindowController : WebViewController <NSToolbarDelegate> {
+ NSArray* toolbarItems_;
+}
+
+ at end
diff --git a/src/cpp/desktop-mac/SecondaryWindowController.mm b/src/cpp/desktop-mac/SecondaryWindowController.mm
new file mode 100644
index 0000000..c47b398
--- /dev/null
+++ b/src/cpp/desktop-mac/SecondaryWindowController.mm
@@ -0,0 +1,113 @@
+/*
+ * SecondaryWindowController.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "SecondaryWindowController.h"
+
+#define kBackCommand @"Back"
+#define kForwardCommand @"Foward"
+#define kRefreshCommand @"Refresh"
+#define kPrintCommand @"Print"
+
+ at implementation SecondaryWindowController
+
+
+- (id)initWithURLRequest: (NSURLRequest*) request
+ name: (NSString*) name
+{
+ if (self = [super initWithURLRequest: request name: name])
+ {
+ toolbarItems_ = [[NSArray alloc] initWithObjects: kBackCommand,
+ kForwardCommand,
+ kRefreshCommand,
+ kPrintCommand,
+ nil];
+
+ NSToolbar *toolbar = [[[NSToolbar alloc]
+ initWithIdentifier:@"PreferencesToolbar"] autorelease];
+ [toolbar setSizeMode: NSToolbarSizeModeSmall];
+ [toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
+ [toolbar setAllowsUserCustomization: NO];
+ [toolbar setAutosavesConfiguration: NO];
+
+ [toolbar setDelegate: self];
+
+ [[self window] setToolbar:toolbar];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [toolbarItems_ release];
+ [super dealloc];
+}
+
+- (NSToolbarItem *)toolbar: (NSToolbar *) toolbar
+ itemForItemIdentifier: (NSString *) identifier
+ willBeInsertedIntoToolbar: (BOOL) flag
+{
+ NSToolbarItem *toolbarItem =
+ [[[NSToolbarItem alloc] initWithItemIdentifier: identifier] autorelease];
+
+
+ if ([identifier isEqual: kBackCommand])
+ {
+ [toolbarItem setLabel: kBackCommand];
+ [toolbarItem setImage: [NSImage imageNamed: @"back_mac"]];
+ [toolbarItem setTarget: [self webView]];
+ [toolbarItem setAction: @selector(goBack:)];
+ }
+ else if ([identifier isEqual: kForwardCommand])
+ {
+ [toolbarItem setLabel: kForwardCommand];
+ [toolbarItem setImage: [NSImage imageNamed: @"forward_mac"]];
+ [toolbarItem setTarget: [self webView]];
+ [toolbarItem setAction: @selector(goForward:)];
+ }
+ else if ([identifier isEqual: kRefreshCommand])
+ {
+ [toolbarItem setLabel: kRefreshCommand];
+ [toolbarItem setImage: [NSImage imageNamed: @"reload_mac"]];
+ [toolbarItem setTarget: [self webView]];
+ [toolbarItem setAction: @selector(reload:)];
+ }
+ else if ([identifier isEqual: kPrintCommand])
+ {
+ [toolbarItem setLabel: kPrintCommand];
+ [toolbarItem setImage: [NSImage imageNamed: @"print_mac"]];
+ [toolbarItem setTarget: self];
+ [toolbarItem setAction: @selector(printMainFrame)];
+ }
+
+ return toolbarItem;
+}
+
+- (void) printMainFrame
+{
+ [self printFrameView: self.webView.mainFrame.frameView];
+}
+
+- (NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar*) toolbar
+{
+ return toolbarItems_;
+}
+
+-(NSArray *) toolbarDefaultItemIdentifiers:(NSToolbar*) toolbar
+{
+ return toolbarItems_;
+}
+
+
+ at end
diff --git a/src/cpp/desktop-mac/SessionLauncher.hpp b/src/cpp/desktop-mac/SessionLauncher.hpp
new file mode 100644
index 0000000..7cb9085
--- /dev/null
+++ b/src/cpp/desktop-mac/SessionLauncher.hpp
@@ -0,0 +1,98 @@
+/*
+ * SessionLauncher.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SESSION_LAUNCHER_HPP
+#define DESKTOP_SESSION_LAUNCHER_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/Process.hpp>
+
+namespace desktop {
+
+enum PendingQuit {
+ PendingQuitNone = 0,
+ PendingQuitAndExit = 1,
+ PendingQuitAndRestart = 2,
+ PendingQuitRestartAndReload = 3
+};
+
+
+// singleton
+class SessionLauncher;
+SessionLauncher& sessionLauncher();
+
+class SessionLauncher : boost::noncopyable
+{
+private:
+ SessionLauncher()
+ : pendingQuit_(PendingQuitNone), sessionProcessActive_(false)
+ {
+ }
+ friend SessionLauncher& sessionLauncher();
+
+public:
+ void init(const core::FilePath& sessionPath,
+ const core::FilePath& confPath);
+
+ bool sessionProcessActive() { return sessionProcessActive_; }
+
+ void setPendingQuit(PendingQuit pendingQuit);
+
+ core::Error launchFirstSession(const std::string& filename);
+
+ void launchNextSession(bool reload);
+
+ std::string launchFailedErrorMessage();
+
+ void cleanupAtExit();
+
+private:
+
+ PendingQuit collectPendingQuit();
+
+ void buildLaunchContext(std::string* pHost,
+ std::string* pPort,
+ std::vector<std::string>* pArgList,
+ std::string* pUrl) const;
+
+ core::Error launchSession(const std::string& host,
+ const std::string& port,
+ std::vector<std::string> args);
+
+
+ void onRSessionExited(const core::system::ProcessResult& result);
+
+ std::string collectAbendLogMessage();
+
+ void closeAllWindows();
+
+private:
+ core::FilePath confPath_;
+ core::FilePath sessionPath_;
+ std::string sessionStderr_;
+ PendingQuit pendingQuit_;
+ bool sessionProcessActive_;
+};
+
+
+
+} // namespace desktop
+
+#endif // DESKTOP_SESSION_LAUNCHER_HPP
diff --git a/src/cpp/desktop-mac/SessionLauncher.mm b/src/cpp/desktop-mac/SessionLauncher.mm
new file mode 100644
index 0000000..622fadc
--- /dev/null
+++ b/src/cpp/desktop-mac/SessionLauncher.mm
@@ -0,0 +1,394 @@
+/*
+ * SessionLauncher.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+
+
+#include <boost/thread.hpp>
+
+#include <core/FileSerializer.hpp>
+#include <core/WaitUtils.hpp>
+
+#include <core/http/TcpIpBlockingClient.hpp>
+
+#include <core/system/Environment.hpp>
+
+#import "SessionLauncher.hpp"
+
+#import "MainFrameController.h"
+
+#import "Options.hpp"
+#import "Utils.hpp"
+
+#define RUN_DIAGNOSTICS_LOG(message) if (desktop::options().runDiagnostics()) \
+ std::cout << (message) << std::endl;
+
+
+using namespace core;
+
+namespace desktop {
+
+namespace {
+
+Error launchProcess(
+ std::string absPath,
+ std::vector<std::string> argList,
+ boost::function<void(const core::system::ProcessResult&)> onCompleted)
+{
+ core::system::ProcessOptions options;
+ return utils::processSupervisor().runProgram(absPath,
+ argList,
+ "",
+ options,
+ onCompleted);
+}
+
+FilePath abendLogPath()
+{
+ return desktop::utils::userLogPath().complete("rsession_abort_msg.log");
+}
+
+void logEnvVar(const std::string& name)
+{
+ std::string value = core::system::getenv(name);
+ if (!value.empty())
+ RUN_DIAGNOSTICS_LOG(" " + name + "=" + value);
+}
+
+core::WaitResult serverReady(const std::string& host,
+ const std::string& port)
+{
+ core::http::Request request;
+ request.setMethod("GET");
+ request.setHost("host");
+ request.setUri("/");
+ request.setHeader("Accept", "*/*");
+ request.setHeader("Connection", "close");
+
+ core::http::Response response;
+ Error error = core::http::sendRequest(host, port, request, &response);
+ if (error)
+ return WaitResult(WaitContinue, Success());
+ else
+ return WaitResult(WaitSuccess, Success());
+}
+
+} // anonymous namespace
+
+SessionLauncher& sessionLauncher()
+{
+ static SessionLauncher instance;
+ return instance;
+}
+
+void SessionLauncher::init(const core::FilePath& sessionPath,
+ const core::FilePath& confPath)
+{
+ sessionPath_ = sessionPath;
+ confPath_ = confPath;
+}
+
+Error SessionLauncher::launchFirstSession(const std::string& filename)
+{
+ // remove the DYLD_VERSIONED_FRAMEWORK_PATH so it doesn't interact
+ // with the rsession process or any of it's child processes
+ core::system::unsetenv("DYLD_VERSIONED_FRAMEWORK_PATH");
+
+ // build a new new launch context
+ std::string host, port, appUrl;
+ std::vector<std::string> argList;
+ buildLaunchContext(&host, &port, &argList, &appUrl);
+
+ RUN_DIAGNOSTICS_LOG("\nAttempting to launch R session...");
+ logEnvVar("RSTUDIO_WHICH_R");
+ logEnvVar("R_HOME");
+ logEnvVar("R_DOC_DIR");
+ logEnvVar("R_INCLUDE_DIR");
+ logEnvVar("R_SHARE_DIR");
+ logEnvVar("R_LIBS");
+ logEnvVar("R_LIBS_USER");
+ logEnvVar("DYLD_LIBRARY_PATH");
+ logEnvVar("LD_LIBRARY_PATH");
+ logEnvVar("PATH");
+ logEnvVar("HOME");
+ logEnvVar("R_USER");
+
+ // launch the session
+ Error error = launchSession(host, port, argList);
+ if (error)
+ return error;
+
+
+ // load the main window if we aren't running diagnostics
+ if (!desktop::options().runDiagnostics())
+ {
+ NSString* openFile = [NSString stringWithUTF8String: filename.c_str()];
+ NSString* url = [NSString stringWithUTF8String: appUrl.c_str()];
+ [[MainFrameController alloc] initWithURL: [NSURL URLWithString: url]
+ openFile: openFile];
+
+ // activate the app
+ [NSApp activateIgnoringOtherApps: YES];
+ }
+
+ return Success();
+}
+
+void SessionLauncher::launchNextSession(bool reload)
+{
+ // build a new launch context -- re-use the same port if we aren't reloading
+ std::string port = !reload ? options().portNumber() : "";
+ std::string host, url;
+ std::vector<std::string> argList;
+ buildLaunchContext(&host, &port, &argList, &url);
+
+ // launch the process
+ Error error = launchSession(host, port, argList);
+ if (!error)
+ {
+ // reload if necessary
+ if (reload)
+ {
+ NSURL* nsurl = [NSURL URLWithString:
+ [NSString stringWithUTF8String: url.c_str()]];
+ [[MainFrameController instance] loadURL: nsurl];
+ }
+ }
+ else
+ {
+ LOG_ERROR(error);
+
+ std::string errMsg = launchFailedErrorMessage();
+ utils::showMessageBox(NSCriticalAlertStyle,
+ @"RStudio",
+ [NSString stringWithUTF8String: errMsg.c_str()]);
+
+ [[MainFrameController instance] quit];
+ }
+}
+
+
+std::string SessionLauncher::launchFailedErrorMessage()
+{
+ // check for abend log -- bail if there is none
+ std::string abendLog = collectAbendLogMessage();
+ if (abendLog.empty())
+ return std::string();
+
+ // build message
+ std::string errMsg = "The R session had a fatal error.";
+
+ // check for R version mismatch
+ if (abendLog.find("arguments passed to .Internal") != std::string::npos)
+ {
+ errMsg.append("\n\nThis error was very likely caused "
+ "by R attempting to load packages from a different "
+ "incompatible version of R on your system. Please remove "
+ "other versions of R and/or remove environment variables "
+ "that reference libraries from other versions of R "
+ "before proceeding.");
+ }
+
+ errMsg.append("\n\n" + abendLog);
+
+ // check for stderr
+ if (!sessionStderr_.empty())
+ errMsg.append("\n\n" + sessionStderr_);
+
+ return errMsg;
+}
+
+
+void SessionLauncher::cleanupAtExit()
+{
+ // currently does nothing
+}
+
+void SessionLauncher::setPendingQuit(PendingQuit pendingQuit)
+{
+ pendingQuit_= pendingQuit;
+}
+
+PendingQuit SessionLauncher::collectPendingQuit()
+{
+ if (pendingQuit_ != PendingQuitNone)
+ {
+ PendingQuit pendingQuit = pendingQuit_;
+ pendingQuit_ = PendingQuitNone;
+ return pendingQuit;
+ }
+ else
+ {
+ return PendingQuitNone;
+ }
+}
+
+void SessionLauncher::onRSessionExited(
+ const core::system::ProcessResult& result)
+{
+ // set flag indicating a process is no longer active
+ sessionProcessActive_ = false;
+
+ // write output to stdout and quit if we were running diagnostics
+ if (desktop::options().runDiagnostics())
+ {
+ std::cout << result.stdOut << std::endl << result.stdErr << std::endl;
+ [NSApp terminate: nil];
+ return;
+ }
+
+ // collect stderr
+ sessionStderr_ = result.stdErr;
+
+ // get pending quit status
+ int pendingQuit = collectPendingQuit();
+
+ // if there was no pending quit set then this is a crash
+ if (pendingQuit == PendingQuitNone)
+ {
+ closeAllWindows();
+
+ [[MainFrameController instance]
+ evaluateJavaScript: @"window.desktopHooks.notifyRCrashed()"];
+
+ if (abendLogPath().exists())
+ {
+ std::string errMsg = collectAbendLogMessage();
+ utils::showMessageBox(NSCriticalAlertStyle,
+ @"RStudio",
+ [NSString stringWithUTF8String: errMsg.c_str()]);
+ }
+
+ }
+
+ // quit and exit means close all the windows and quit
+ else if (pendingQuit == PendingQuitAndExit)
+ {
+ closeAllWindows();
+
+ [[MainFrameController instance] quit];
+ }
+
+ // otherwise this is a restart so we need to launch the next session
+ else
+ {
+ // close all satellite windows if we are reloading
+ bool reload = (pendingQuit == PendingQuitRestartAndReload);
+ if (reload)
+ closeAllWindows();
+
+ // launch next session
+ launchNextSession(reload);
+ }
+}
+
+void SessionLauncher::buildLaunchContext(std::string* pHost,
+ std::string* pPort,
+ std::vector<std::string>* pArgList,
+ std::string* pUrl) const
+{
+ *pHost = "127.0.0.1";
+ if (pPort->empty())
+ *pPort = desktop::options().newPortNumber();
+ *pUrl = "http://" + *pHost + ":" + *pPort + "/";
+
+
+ pArgList->push_back("--config-file");
+ if (!confPath_.empty())
+ {
+ pArgList->push_back(confPath_.absolutePath());
+ }
+ else
+ {
+ // explicitly pass "none" so that rsession doesn't read an
+ // /etc/rstudio/rsession.conf file which may be sitting around
+ // from a previous configuratin or install
+ pArgList->push_back("none");
+ }
+
+ pArgList->push_back("--program-mode");
+ pArgList->push_back("desktop");
+
+ pArgList->push_back("--www-port");
+ pArgList->push_back(*pPort);
+
+ if (desktop::options().runDiagnostics())
+ {
+ pArgList->push_back("--verify-installation");
+ pArgList->push_back("1");
+ }
+}
+
+Error SessionLauncher::launchSession(const std::string& host,
+ const std::string& port,
+ std::vector<std::string> args)
+{
+ // always reset the abend log path and stderr before launching
+ Error error = abendLogPath().removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ sessionStderr_.clear();
+
+ boost::function<void(const core::system::ProcessResult&)> onCompleted =
+ boost::bind(&SessionLauncher::onRSessionExited, this, _1);
+
+ error = launchProcess(sessionPath_.absolutePath(), args, onCompleted);
+ if (error)
+ return error;
+
+ // session process is active
+ sessionProcessActive_ = true;
+
+ // wait for process to be available
+ return waitWithTimeout(boost::bind(serverReady, host, port), 50, 25, 10);
+}
+
+
+std::string SessionLauncher::collectAbendLogMessage()
+{
+ std::string contents;
+ FilePath abendLog = abendLogPath();
+ if (abendLog.exists())
+ {
+ Error error = core::readStringFromFile(abendLog, &contents);
+ if (error)
+ LOG_ERROR(error);
+
+ error = abendLog.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return contents;
+}
+
+void SessionLauncher::closeAllWindows()
+{
+ NSWindow* mainWindow = [[MainFrameController instance] window];
+ NSArray* windows = [NSApp windows];
+ for (NSWindow* window in windows)
+ {
+ if (window != mainWindow)
+ [window close];
+ }
+}
+
+
+
+} // namespace desktop
+
diff --git a/src/cpp/desktop-mac/Utils.hpp b/src/cpp/desktop-mac/Utils.hpp
new file mode 100644
index 0000000..a97f777
--- /dev/null
+++ b/src/cpp/desktop-mac/Utils.hpp
@@ -0,0 +1,53 @@
+
+/*
+ * Utils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_UTILS_HPP
+#define DESKTOP_UTILS_HPP
+
+#include <core/system/Process.hpp>
+
+#import <AppKit/NSAlert.h>
+
+namespace core {
+ class FilePath;
+}
+
+
+namespace desktop {
+namespace utils {
+
+void initializeLang();
+
+core::FilePath userLogPath();
+
+void showMessageBox(NSAlertStyle style, NSString* title, NSString* message);
+
+void browseURL(NSURL* url);
+
+core::system::ProcessSupervisor& processSupervisor();
+
+bool supportsFullscreenMode(NSWindow* window);
+void enableFullscreenMode(NSWindow* window, bool primary);
+void toggleFullscreenMode(NSWindow* window);
+
+float titleBarHeight();
+
+NSData *base64Decode(NSString *input);
+
+} // namespace utils
+} // namespace desktop
+
+#endif // DESKTOP_UTILS_HPP
\ No newline at end of file
diff --git a/src/cpp/desktop-mac/Utils.mm b/src/cpp/desktop-mac/Utils.mm
new file mode 100644
index 0000000..f2dfd45
--- /dev/null
+++ b/src/cpp/desktop-mac/Utils.mm
@@ -0,0 +1,234 @@
+
+
+#include <string>
+#include <vector>
+
+#include <core/FilePath.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#import <Cocoa/Cocoa.h>
+
+#import "Utils.hpp"
+
+using namespace core;
+
+namespace desktop {
+namespace utils {
+
+// PORT: from DesktopUtilsMac.mm
+void initializeLang()
+{
+ // Not sure what the memory management rules are here, i.e. whether an
+ // autorelease pool is active. Just let it leak, since we're only calling
+ // this once (at the time of this writing).
+
+ // We try to simulate the behavior of R.app.
+
+ NSString* lang = nil;
+
+ // Highest precedence: force.LANG. If it has a value, use it.
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ [defaults addSuiteNamed:@"org.R-project.R"];
+ lang = [defaults stringForKey:@"force.LANG"];
+ if (lang && ![lang length])
+ {
+ // If force.LANG is present but empty, don't touch LANG at all.
+ return;
+ }
+
+ // Next highest precedence: ignore.system.locale. If it has a value,
+ // hardcode to en_US.UTF-8.
+ if (!lang && [defaults boolForKey:@"ignore.system.locale"])
+ {
+ lang = @"en_US.UTF-8";
+ }
+
+ // Next highest precedence: LANG environment variable.
+ if (!lang)
+ {
+ std::string envLang = core::system::getenv("LANG");
+ if (!envLang.empty())
+ {
+ lang = [NSString stringWithCString:envLang.c_str()
+ encoding:NSASCIIStringEncoding];
+ }
+ }
+
+ // Next highest precedence: Try to figure out language from the current
+ // locale.
+ if (!lang)
+ {
+ NSString* lcid = [[NSLocale currentLocale] localeIdentifier];
+ if (lcid)
+ {
+ // Eliminate trailing @ components (OS X uses the @ suffix to append
+ // locale overrides like alternate currency formats)
+ std::string localeId = std::string([lcid UTF8String]);
+ std::size_t atLoc = localeId.find('@');
+ if (atLoc != std::string::npos)
+ {
+ localeId = localeId.substr(0, atLoc);
+ lcid = [NSString stringWithUTF8String: localeId.c_str()];
+ }
+
+ lang = [lcid stringByAppendingString:@".UTF-8"];
+ }
+ }
+
+ // None of the above worked. Just hard code it.
+ if (!lang)
+ {
+ lang = @"en_US.UTF-8";
+ }
+
+ const char* clang = [lang cStringUsingEncoding:NSASCIIStringEncoding];
+ core::system::setenv("LANG", clang);
+ core::system::setenv("LC_CTYPE", clang);
+}
+
+// PORT: from DesktopUtils.cpp
+FilePath userLogPath()
+{
+ FilePath userHomePath = core::system::userHomePath("R_USER|HOME");
+ FilePath logPath = core::system::userSettingsPath(
+ userHomePath,
+ "RStudio-Desktop").childPath("log");
+ return logPath;
+}
+
+void showMessageBox(NSAlertStyle style, NSString* title, NSString* message)
+{
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setAlertStyle: style];
+ [alert setMessageText: title];
+ [alert setInformativeText: message];
+ [alert runModal];
+}
+
+void browseURL(NSURL* nsurl)
+{
+ // check for a pdf and force use of preview (prevent crash that can
+ // occur with certain versions of acrobat reader)
+ if ([nsurl isFileURL] && [[nsurl absoluteString] hasSuffix: @".pdf"])
+ {
+ [[NSWorkspace sharedWorkspace] openFile: nsurl.path
+ withApplication: @"Preview"];
+ }
+ else
+ {
+ [[NSWorkspace sharedWorkspace] openURL: nsurl];
+ }
+}
+
+core::system::ProcessSupervisor& processSupervisor()
+{
+ static core::system::ProcessSupervisor instance;
+ return instance;
+}
+
+bool supportsFullscreenMode(NSWindow* window)
+{
+ return [window respondsToSelector:@selector(toggleFullScreen:)];
+}
+
+
+// see: https://developer.apple.com/library/mac/#documentation/General/Conceptual/MOSXAppProgrammingGuide/FullScreenApp/FullScreenApp.html
+void enableFullscreenMode(NSWindow* window, bool primary)
+{
+ if (supportsFullscreenMode(window))
+ {
+ NSWindowCollectionBehavior behavior = [window collectionBehavior];
+ behavior = behavior | (primary ?
+ NSWindowCollectionBehaviorFullScreenPrimary :
+ NSWindowCollectionBehaviorFullScreenAuxiliary);
+ [window setCollectionBehavior:behavior];
+ }
+}
+
+void toggleFullscreenMode(NSWindow* window)
+{
+ if (supportsFullscreenMode(window))
+ [window toggleFullScreen:nil];
+}
+
+
+float titleBarHeight()
+{
+ NSRect frame = NSMakeRect (0, 0, 100, 100);
+
+ NSRect contentRect;
+ contentRect = [NSWindow contentRectForFrameRect: frame
+ styleMask: NSTitledWindowMask];
+
+ return (frame.size.height - contentRect.size.height);
+
+}
+
+namespace {
+int charToB64Val(unsigned char c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c - 'A';
+ if (c >= 'a' && c <= 'z')
+ return 26 + (c - 'a');
+ if (c >= '0' && c <= '9')
+ return 52 + (c - '0');
+ if (c == '+')
+ return 62;
+ if (c == '/')
+ return 63;
+ if (c == '=')
+ return 0;
+ return -1;
+}
+}
+
+NSData *base64Decode(NSString *input)
+{
+ int paddingChars = 0;
+ std::vector<unsigned char> output;
+ output.reserve(static_cast<int>([input length] * 0.75 + 2));
+ std::vector<unsigned int> buffer;
+ buffer.reserve(4);
+ NSUInteger pos = 0;
+ NSUInteger len = [input length];
+ while (pos < len)
+ {
+ unichar c = [input characterAtIndex: pos++];
+ // ignore whitespace
+ if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
+ continue;
+
+ if (c == '=')
+ {
+ paddingChars++;
+ if (paddingChars > 2)
+ return nil;
+ }
+ else if (paddingChars > 0)
+ return nil; // padding chars must only appear at the end
+
+ int decodedVal = charToB64Val(c);
+ if (decodedVal < 0)
+ return nil; // invalid data
+ buffer.push_back(decodedVal);
+ if (buffer.size() == 4)
+ {
+ // We have a quartet; align and flush to output
+ output.push_back( (buffer[0]<<2) | buffer[1]>>4);
+ output.push_back( ((buffer[1]&0xF)<<4) | (buffer[2]>>2) );
+ output.push_back( ((buffer[2]&0x3)<<6) | (buffer[3]) );
+ buffer.clear();
+ }
+ }
+ while (paddingChars-- > 0)
+ output.pop_back();
+ return [NSData dataWithBytes: &output[0] length: output.size()];
+}
+
+
+} // namespace utils
+} // namespace desktop
+
diff --git a/src/cpp/desktop-mac/WebViewController.h b/src/cpp/desktop-mac/WebViewController.h
new file mode 100644
index 0000000..d8ed953
--- /dev/null
+++ b/src/cpp/desktop-mac/WebViewController.h
@@ -0,0 +1,48 @@
+
+
+#import <Cocoa/Cocoa.h>
+#import <Webkit/WebKit.h>
+
+#import "WebViewWithKeyEquiv.h"
+#import "GwtCallbacks.h"
+
+ at interface WebViewController :
+ NSWindowController<NSWindowDelegate,GwtCallbacksUIDelegate> {
+ WebViewWithKeyEquiv* webView_;
+ NSString* name_;
+ NSURL* baseUrl_;
+ NSString* viewerUrl_;
+}
+
++ (WebViewController*) windowNamed: (NSString*) name;
+
++ (void) activateSatelliteWindow: (NSString*) name;
+
++ (void) prepareForSatelliteWindow: (NSString*) name
+ width: (int) width
+ height: (int) height;
+
+
+// The designated initializer
+- (id)initWithURLRequest: (NSURLRequest*) request
+ name: (NSString*) name;
+
+// load a new url
+- (void) loadURL: (NSURL*) url;
+
+// set the current viewer url
+- (void) setViewerURL: (NSString*) url;
+
+// Get the embedded WebView
+- (WebView*) webView;
+
+// sync the web view's zoom level
+- (void) syncZoomLevel;
+
+// print
+- (void) printFrameView: (WebFrameView*) frameView;
+
+// subclass methods for registering javascript callbacks
+- (void) registerDesktopObject;
+ at end
+
diff --git a/src/cpp/desktop-mac/WebViewController.mm b/src/cpp/desktop-mac/WebViewController.mm
new file mode 100644
index 0000000..dd7b82a
--- /dev/null
+++ b/src/cpp/desktop-mac/WebViewController.mm
@@ -0,0 +1,577 @@
+#import <Cocoa/Cocoa.h>
+
+#import <WebKit/WebFrame.h>
+#import <Webkit/WebUIDelegate.h>
+
+
+#import "Options.hpp"
+#import "WebViewController.h"
+#import "GwtCallbacks.h"
+#import "MainFrameMenu.h"
+#import "SatelliteController.h"
+#import "SecondaryWindowController.h"
+#import "Utils.hpp"
+#import "WebViewWithKeyEquiv.h"
+#import "FileDownloader.h"
+
+struct PendingSatelliteWindow
+{
+ PendingSatelliteWindow()
+ : name(), width(-1), height(-1)
+ {
+ }
+
+ PendingSatelliteWindow(std::string name, int width, int height)
+ : name(name), width(width), height(height)
+ {
+ }
+
+ bool empty() const { return name.empty(); }
+
+ std::string name;
+ int width;
+ int height;
+};
+
+// get access to private webview zoom apis
+ at interface WebView (Zoom)
+- (IBAction)zoomPageIn:(id)sender;
+- (IBAction)zoomPageOut:(id)sender;
+- (IBAction)resetPageZoom:(id)sender;
+ at end
+
+ at implementation WebViewController
+
+static NSMutableDictionary* namedWindows_;
+static PendingSatelliteWindow pendingWindow_;
+
++ (void) initialize
+{
+ namedWindows_ = [[NSMutableDictionary alloc] init];
+ pendingWindow_ = PendingSatelliteWindow();
+}
+
++ (void) activateSatelliteWindow: (NSString*) name
+{
+ WebViewController* controller = [namedWindows_ objectForKey: name];
+ if (controller)
+ [[controller window] makeKeyAndOrderFront: self];
+}
+
++ (void) prepareForSatelliteWindow: (NSString*) name
+ width: (int) width
+ height: (int) height
+{
+ pendingWindow_ = PendingSatelliteWindow([name UTF8String], width, height);
+}
+
++ (WebViewController*) windowNamed: (NSString*) name
+{
+ return [namedWindows_ objectForKey: name];
+}
+
+- (WebView*) webView
+{
+ return webView_;
+}
+
+- (void) dealloc
+{
+ [name_ release];
+ [webView_ release];
+ [baseUrl_ release];
+ [super dealloc];
+}
+
+- (id) init
+{
+ @throw [NSException exceptionWithName: @"WebViewControllerInitialization"
+ reason: @"Use initWithURLRequest"
+ userInfo: nil];
+}
+
+- (id)initWithURLRequest: (NSURLRequest*) request
+ name: (NSString*) name
+{
+ // record base url
+ baseUrl_ = [[request URL] retain];
+
+ // create window and become it's delegate
+ NSRect frameRect = NSMakeRect(20, 20, 1024, 768);
+ NSWindow* window = [[[NSWindow alloc] initWithContentRect: frameRect
+ styleMask: NSTitledWindowMask |
+ NSClosableWindowMask |
+ NSResizableWindowMask |
+ NSMiniaturizableWindowMask
+ backing: NSBackingStoreBuffered
+ defer: NO] autorelease];
+ [window setDelegate: self];
+ [window setTitle: @"RStudio"];
+
+ // initialize superclass then continue
+ if (self = [super initWithWindow: window])
+ {
+ // set autosave name if this is a named window
+ if (name)
+ [self setWindowFrameAutosaveName: name];
+
+ // create web view, save it as a member, and register as it's delegate,
+ webView_ = [[WebViewWithKeyEquiv alloc] initWithFrame: frameRect];
+ [webView_ setUIDelegate: self];
+ [webView_ setFrameLoadDelegate: self];
+ [webView_ setResourceLoadDelegate: self];
+ [webView_ setPolicyDelegate: self];
+ [webView_ setKeyEquivDelegate: self];
+
+ // respect the current zoom level
+ [self syncZoomLevel];
+
+ // load the request
+ [[webView_ mainFrame] loadRequest: request];
+
+ // add the webview to the window
+ [window setContentView: webView_];
+
+ // bring the window to the front
+ [window makeKeyAndOrderFront: self];
+
+ // track named windows for reactivation
+ if (name)
+ {
+ // track it (for reactivation)
+ name_ = [name copy];
+ [namedWindows_ setValue: self forKey: name_];
+ }
+
+ // set fullscreen mode (defualt to non-primary)
+ desktop::utils::enableFullscreenMode(window, false);
+
+ }
+
+ return self;
+}
+
+- (void) loadURL: (NSURL*) url
+{
+ // record base url
+ if(url != baseUrl_)
+ {
+ [url retain];
+ [baseUrl_ release];
+ baseUrl_ = url;
+ }
+
+ // load the webview
+ NSURLRequest* request = [NSURLRequest requestWithURL: baseUrl_];
+ [[[self webView] mainFrame] loadRequest: request];
+}
+
+- (void) syncZoomLevel
+{
+ // reset to a known baseline
+ [webView_ resetPageZoom: self];
+
+ // get the zoom level
+ int zoomLevel = desktop::options().zoomLevel();
+
+ // zoom in
+ if (zoomLevel > 0)
+ {
+ for (int i=0; i<zoomLevel; i++)
+ [webView_ zoomPageIn: self];
+ }
+ // zoom out
+ else if (zoomLevel < 0)
+ {
+ zoomLevel = std::abs(zoomLevel);
+ for (int i=0; i<zoomLevel; i++)
+ [webView_ zoomPageOut: self];
+ }
+}
+
+// set the current viewer url
+- (void) setViewerURL: (NSString*) url
+{
+ // record viewer url
+ if(url != viewerUrl_)
+ {
+ [url retain];
+ [viewerUrl_ release];
+ viewerUrl_ = url;
+ }
+}
+
+- (void) windowDidLoad
+{
+ [super windowDidLoad];
+
+ // more post-load initialization
+}
+
+- (void) webView:(WebView *) sender
+ didReceiveTitle:(NSString *) title
+ forFrame:(WebFrame *) frame
+{
+ // set window title when main frame title is available
+ if (frame == [webView_ mainFrame])
+ [[self window] setTitle: title];
+}
+
+- (BOOL) webView: (WebView*) sender
+runJavaScriptConfirmPanelWithMessage: (NSString*) message
+ initiatedByFrame: (WebFrame *) frame
+{
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText:message];
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ if ([alert runModal] == NSAlertFirstButtonReturn)
+ return YES;
+ else
+ return NO;
+}
+
+- (void) webView: (WebView *) sender
+runJavaScriptAlertPanelWithMessage: (NSString *) message
+ initiatedByFrame: (WebFrame *) frame
+{
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText: message];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+}
+
+- (void) webView: (WebView *)sender
+ printFrameView: (WebFrameView *) frameView
+{
+ if ([frameView documentViewShouldHandlePrint])
+ {
+ [frameView printDocumentView];
+ }
+ else
+ {
+ [self printFrameView: frameView];
+ }
+}
+
+- (void) printFrameView: (WebFrameView*) frameView
+{
+ NSPrintOperation* printOperation =
+ [frameView printOperationWithPrintInfo: [NSPrintInfo sharedPrintInfo]];
+ [printOperation runOperation];
+}
+
+
+// WebViewController is a self-freeing object so free it when the window closes
+- (void)windowWillClose:(NSNotification *) notification
+{
+ // if we were named then remove from the tracker
+ if (name_)
+ [namedWindows_ removeObjectForKey: name_];
+
+ // unsubscribe observers
+ [[self window] setDelegate: nil];
+ [webView_ setUIDelegate: nil];
+ [webView_ setFrameLoadDelegate: nil];
+ [webView_ setResourceLoadDelegate: nil];
+ [webView_ setPolicyDelegate: nil];
+ [webView_ setKeyEquivDelegate: nil];
+
+ [self autorelease];
+}
+
+- (BOOL) isSupportedScheme: (NSString*) scheme
+{
+ return ([scheme isEqualTo: @"http"] ||
+ [scheme isEqualTo: @"https"] ||
+ [scheme isEqualTo: @"mailto"] ||
+ [scheme isEqualTo: @"data"]);
+}
+
+- (BOOL) isApplicationURL: (NSURL*) url
+{
+ if (([[url scheme] isEqualTo: [baseUrl_ scheme]] &&
+ [[url host] isEqualTo: [baseUrl_ host]] &&
+ [[url port] isEqualTo: [baseUrl_ port]]))
+ {
+ return YES;
+ }
+ else
+ {
+ return NO;
+ }
+}
+
+-(void) decidePolicyFor: (WebView *) webView
+ actionInformation: (NSDictionary *) actionInformation
+ request: (NSURLRequest *) request
+ iframe: (BOOL) iframe
+ decisionListener: (id <WebPolicyDecisionListener>) listener
+{
+ // get the url for comparison to the base url
+ NSURL* url = [request URL];
+ if ([[url absoluteString] isEqualTo: @"about:blank"])
+ {
+ [listener use];
+ return;
+ }
+
+ // ensure this is a supported scheme
+ NSString* scheme = [url scheme];
+ if (![self isSupportedScheme: scheme])
+ {
+ [[NSWorkspace sharedWorkspace] openURL: url];
+ [listener ignore];
+ return;
+ }
+
+ // open PDFs externally
+ if ([[url pathExtension] isEqual: @"pdf"])
+ {
+ desktop::downloadAndShowFile([self rsessionRequest: request]);
+ [listener ignore];
+ return;
+ }
+
+ NSString* host = [url host];
+ BOOL isLocal = [host isEqualTo: @"localhost"] ||
+ [host isEqualTo: @"127.0.0.1"];
+
+ if ((!baseUrl_ && isLocal) || [self isApplicationURL: url])
+ {
+ [listener use];
+ }
+ else if (isLocal && (viewerUrl_ != nil) &&
+ [[url absoluteString] hasPrefix: viewerUrl_])
+ {
+ [listener use];
+ }
+ else
+ {
+ // perform a base64 download if necessary
+ WebNavigationType navType = (WebNavigationType)[[actionInformation
+ objectForKey:WebActionNavigationTypeKey] intValue];
+ if ([scheme isEqualToString: @"data"] &&
+ (navType == WebNavigationTypeLinkClicked ||
+ navType == WebNavigationTypeFormSubmitted))
+ {
+ [self handleBase64Download: url
+ forElement: [actionInformation
+ objectForKey:WebActionElementKey]];
+ }
+ else
+ {
+ // show urls that aren't in iframes externall
+ if (!iframe)
+ desktop::utils::browseURL(url);
+ }
+
+ [listener ignore];
+ }
+}
+
+- (void) webView: (WebView *) webView
+decidePolicyForNewWindowAction: (NSDictionary *) actionInformation
+ request: (NSURLRequest *) request
+ newFrameName: (NSString *)frameName
+ decisionListener: (id < WebPolicyDecisionListener >)listener
+{
+ [self decidePolicyFor: webView
+ actionInformation: actionInformation
+ request: request
+ iframe: FALSE
+ decisionListener: listener];
+}
+
+- (void) webView:(WebView *) webView
+decidePolicyForNavigationAction: (NSDictionary *) actionInformation
+ request: (NSURLRequest *) request
+ frame: (WebFrame *) frame
+ decisionListener:(id < WebPolicyDecisionListener >)listener
+{
+ [self decidePolicyFor: webView
+ actionInformation: actionInformation
+ request: request
+ iframe: frame != [webView_ mainFrame]
+ decisionListener: listener];
+}
+
+- (NSWindow*) uiWindow
+{
+ return [self window];
+}
+
+// Handle new window request by creating another controller
+- (WebView *) webView: (WebView *) sender
+ createWebViewWithRequest:(NSURLRequest *)request
+{
+ // check for a pending satellite request
+ if (!pendingWindow_.empty())
+ {
+ // capture and then clear the pending window
+ PendingSatelliteWindow pendingWindow = pendingWindow_;
+ pendingWindow_ = PendingSatelliteWindow();
+
+ // get the name
+ NSString* name =
+ [NSString stringWithUTF8String: pendingWindow.name.c_str()];
+
+ // check for an existing window
+ WebViewController* controller = [namedWindows_ objectForKey: name];
+ if (controller)
+ {
+ [[controller window] makeKeyAndOrderFront: self];
+ return nil;
+ }
+ else
+ {
+ // self-freeing so don't auto-release
+ SatelliteController* satelliteController =
+ [[SatelliteController alloc] initWithURLRequest: request
+ name: name];
+
+ // return it
+ return [satelliteController webView];
+ }
+ }
+ else
+ {
+ // self-freeing so don't auto-release
+ SecondaryWindowController * controller =
+ [[SecondaryWindowController alloc] initWithURLRequest: request
+ name: nil];
+ return [controller webView];
+ }
+}
+
+- (NSURLRequest*) webView:(WebView *) sender
+ resource:(id) identifier
+ willSendRequest:(NSURLRequest *)request
+ redirectResponse:(NSURLResponse *) redirectResponse
+ fromDataSource:(WebDataSource *) dataSource
+{
+ return [self rsessionRequest: request];
+}
+
+- (NSURLRequest*) rsessionRequest: (NSURLRequest*) request
+{
+ NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease];
+ std::string secret = desktop::options().sharedSecret();
+ [mutableRequest setValue: [NSString stringWithUTF8String: secret.c_str()]
+ forHTTPHeaderField:@"X-Shared-Secret"];
+ [mutableRequest setTimeoutInterval: 300];
+ return mutableRequest;
+}
+
+- (void) registerDesktopObject
+{
+ id win = [webView_ windowScriptObject];
+ GwtCallbacks* gwtCallbacks =
+ [[[GwtCallbacks alloc] initWithUIDelegate: self] autorelease];
+ [win setValue: gwtCallbacks forKey:@"desktop"];
+}
+
+- (BOOL) performKeyEquivalent: (NSEvent *)theEvent
+{
+ NSString* chr = [theEvent charactersIgnoringModifiers];
+ // Get all the modifier flags except caps lock, which we don't care about for
+ // the sake of these comparisons
+ NSUInteger mod = [theEvent modifierFlags] &
+ (NSDeviceIndependentModifierFlagsMask ^ NSAlphaShiftKeyMask);
+ if ([chr isEqualToString: @"w"] && mod == NSCommandKeyMask)
+ {
+ [[webView_ window] performClose: self];
+ return YES;
+ }
+
+ // Without these, secondary/satellite windows don't respond to clipboard shortcuts
+ if ([chr isEqualToString: @"x"] && mod == NSCommandKeyMask)
+ {
+ [webView_ cut: self];
+ return YES;
+ }
+ if ([chr isEqualToString: @"c"] && mod == NSCommandKeyMask)
+ {
+ [webView_ copy: self];
+ return YES;
+ }
+ if ([chr isEqualToString: @"v"] && mod == NSCommandKeyMask)
+ {
+ [webView_ paste: self];
+ return YES;
+ }
+ if ([chr isEqualToString: @"a"] && mod == NSCommandKeyMask)
+ {
+ if ([webView_ respondsToSelector: @selector(selectAll:)])
+ [webView_ selectAll: self];
+
+ // ACE needs to handle this event to do custom logic for Select All, so
+ // continue to process the event as though it were unhandled here.
+ }
+
+ return NO;
+}
+
+- (void) handleBase64Download: (NSURL*) url
+ forElement: (NSDictionary*) elementDict
+{
+ // TODO: handle base64 download
+ // (see WebPage::handleBase64Download in Qt version)
+ NSString* absUrl = [url absoluteString];
+ NSArray* parts = [absUrl
+ componentsSeparatedByCharactersInSet: [NSCharacterSet
+ characterSetWithCharactersInString: @":;,"]];
+ if ([parts count] != 4
+ || ![@"data" isEqualToString: [parts objectAtIndex: 0]]
+ || ![@"base64" isEqualToString: [parts objectAtIndex: 2]])
+ {
+ NSLog(@"Invalid data URL");
+ return;
+ }
+
+ DOMNode* node = [elementDict objectForKey: @"WebElementDOMNode"];
+ // The DOM node that was clicked might be the DOMText or another element that was inside the anchor.
+ // Walk the parents until we get to an anchor.
+ while (node && ([node nodeType] != 1 || ![[(DOMElement*)node tagName] isEqualToString: @"A"]))
+ node = [node parentElement];
+ if (!node)
+ {
+ NSLog(@"Data URI's originating anchor not found");
+ return;
+ }
+
+ DOMElement* el = (DOMElement*)node;
+
+ DOMNode* downloadAttrib = [[el attributes] getNamedItem: @"download"];
+ if (downloadAttrib == nil)
+ {
+ NSLog(@"'download' attribute not found on anchor");
+ return;
+ }
+ NSString* filename = [downloadAttrib nodeValue];
+
+ NSData* data = desktop::utils::base64Decode([parts objectAtIndex: 3]);
+ if (data == nil)
+ {
+ NSLog(@"Failed to decode data URL");
+ return;
+ }
+
+ NSSavePanel* dlSavePanel = [NSSavePanel savePanel];
+ [dlSavePanel setNameFieldStringValue: filename];
+
+ [dlSavePanel beginSheetModalForWindow: [self window] completionHandler: nil];
+ long int result = [dlSavePanel runModal];
+ [NSApp endSheet: dlSavePanel];
+
+ if (result != NSFileHandlingPanelOKButton)
+ return;
+
+ [data writeToURL: [dlSavePanel URL] atomically: FALSE];
+}
+
+ at end
+
+
+
diff --git a/src/cpp/desktop-mac/WebViewWithKeyEquiv.h b/src/cpp/desktop-mac/WebViewWithKeyEquiv.h
new file mode 100644
index 0000000..5afecd6
--- /dev/null
+++ b/src/cpp/desktop-mac/WebViewWithKeyEquiv.h
@@ -0,0 +1,25 @@
+/*
+ * WebViewWithKeyEquiv.h
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import <WebKit/WebKit.h>
+
+ at interface WebViewWithKeyEquiv : WebView
+{
+ id keyEquivDelegate_;
+}
+
+- (void) setKeyEquivDelegate: (id) delegate;
+
+ at end
diff --git a/src/cpp/desktop-mac/WebViewWithKeyEquiv.mm b/src/cpp/desktop-mac/WebViewWithKeyEquiv.mm
new file mode 100644
index 0000000..ae2d3e0
--- /dev/null
+++ b/src/cpp/desktop-mac/WebViewWithKeyEquiv.mm
@@ -0,0 +1,32 @@
+/*
+ * WebViewWithKeyEquiv.mm
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#import "WebViewWithKeyEquiv.h"
+
+ at implementation WebViewWithKeyEquiv
+
+- (void) setKeyEquivDelegate: (id) delegate
+{
+ keyEquivDelegate_ = delegate;
+}
+
+- (BOOL) performKeyEquivalent: (NSEvent *) theEvent
+{
+ if (keyEquivDelegate_ != nil && [keyEquivDelegate_ performKeyEquivalent: theEvent])
+ return YES;
+ return [super performKeyEquivalent: theEvent];
+}
+
+ at end
diff --git a/src/cpp/desktop-mac/desktop-config.h.in b/src/cpp/desktop-mac/desktop-config.h.in
new file mode 100644
index 0000000..4d270bc
--- /dev/null
+++ b/src/cpp/desktop-mac/desktop-config.h.in
@@ -0,0 +1,21 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define RSTUDIO_VERSION "${CPACK_PACKAGE_VERSION}"
+
+#define RSTUDIO_R_MAJOR_VERSION_REQUIRED ${RSTUDIO_R_MAJOR_VERSION_REQUIRED}
+#define RSTUDIO_R_MINOR_VERSION_REQUIRED ${RSTUDIO_R_MINOR_VERSION_REQUIRED}
+#define RSTUDIO_R_PATCH_VERSION_REQUIRED ${RSTUDIO_R_PATCH_VERSION_REQUIRED}
+
diff --git a/src/cpp/desktop-mac/mac-terminal.in b/src/cpp/desktop-mac/mac-terminal.in
new file mode 100755
index 0000000..7f95797
--- /dev/null
+++ b/src/cpp/desktop-mac/mac-terminal.in
@@ -0,0 +1,9 @@
+#!/usr/bin/osascript
+on run argv
+ set dir to quoted form of (first item of argv)
+ tell app "Terminal"
+ activate
+ do script "cd " & dir
+ end tell
+end run
+
diff --git a/src/cpp/desktop-mac/resources/icns/CSS.icns b/src/cpp/desktop-mac/resources/icns/CSS.icns
new file mode 100644
index 0000000..0190563
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/CSS.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/HTML.icns b/src/cpp/desktop-mac/resources/icns/HTML.icns
new file mode 100644
index 0000000..3b9bbc8
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/HTML.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/JS.icns b/src/cpp/desktop-mac/resources/icns/JS.icns
new file mode 100644
index 0000000..90c78a9
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/JS.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/Markdown.icns b/src/cpp/desktop-mac/resources/icns/Markdown.icns
new file mode 100644
index 0000000..ad1d220
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/Markdown.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RData.icns b/src/cpp/desktop-mac/resources/icns/RData.icns
new file mode 100644
index 0000000..b75ff91
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RData.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RDoc.icns b/src/cpp/desktop-mac/resources/icns/RDoc.icns
new file mode 100644
index 0000000..e8cf6f4
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RDoc.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RHTML.icns b/src/cpp/desktop-mac/resources/icns/RHTML.icns
new file mode 100644
index 0000000..e0273a7
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RHTML.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RMarkdown.icns b/src/cpp/desktop-mac/resources/icns/RMarkdown.icns
new file mode 100644
index 0000000..4267626
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RMarkdown.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RPresentation.icns b/src/cpp/desktop-mac/resources/icns/RPresentation.icns
new file mode 100644
index 0000000..4b26c7d
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RPresentation.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RProject.icns b/src/cpp/desktop-mac/resources/icns/RProject.icns
new file mode 100644
index 0000000..8a38727
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RProject.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RSource.icns b/src/cpp/desktop-mac/resources/icns/RSource.icns
new file mode 100644
index 0000000..303ba2b
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RSource.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RStudio.icns b/src/cpp/desktop-mac/resources/icns/RStudio.icns
new file mode 100644
index 0000000..c7b14c7
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RStudio.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RSweave.icns b/src/cpp/desktop-mac/resources/icns/RSweave.icns
new file mode 100644
index 0000000..51194ac
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RSweave.icns differ
diff --git a/src/cpp/desktop-mac/resources/icns/RTex.icns b/src/cpp/desktop-mac/resources/icns/RTex.icns
new file mode 100644
index 0000000..da031f7
Binary files /dev/null and b/src/cpp/desktop-mac/resources/icns/RTex.icns differ
diff --git a/src/cpp/desktop-mac/resources/png/back_mac.png b/src/cpp/desktop-mac/resources/png/back_mac.png
new file mode 100644
index 0000000..4aab3f3
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/back_mac.png differ
diff --git a/src/cpp/desktop-mac/resources/png/back_mac at 2x.png b/src/cpp/desktop-mac/resources/png/back_mac at 2x.png
new file mode 100644
index 0000000..01ad2c7
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/back_mac at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_error.png b/src/cpp/desktop-mac/resources/png/dialog_error.png
new file mode 100644
index 0000000..dbfbf25
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_error.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_error at 2x.png b/src/cpp/desktop-mac/resources/png/dialog_error at 2x.png
new file mode 100644
index 0000000..51901a4
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_error at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_info.png b/src/cpp/desktop-mac/resources/png/dialog_info.png
new file mode 100644
index 0000000..d3cee62
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_info.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_info at 2x.png b/src/cpp/desktop-mac/resources/png/dialog_info at 2x.png
new file mode 100644
index 0000000..31e7b52
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_info at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_popup_blocked.png b/src/cpp/desktop-mac/resources/png/dialog_popup_blocked.png
new file mode 100644
index 0000000..30c88a5
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_popup_blocked.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_popup_blocked at 2x.png b/src/cpp/desktop-mac/resources/png/dialog_popup_blocked at 2x.png
new file mode 100644
index 0000000..38c84c8
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_popup_blocked at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_question.png b/src/cpp/desktop-mac/resources/png/dialog_question.png
new file mode 100644
index 0000000..674b0ce
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_question.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_question at 2x.png b/src/cpp/desktop-mac/resources/png/dialog_question at 2x.png
new file mode 100644
index 0000000..ed83d1b
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_question at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_warning.png b/src/cpp/desktop-mac/resources/png/dialog_warning.png
new file mode 100644
index 0000000..fdccc5b
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_warning.png differ
diff --git a/src/cpp/desktop-mac/resources/png/dialog_warning at 2x.png b/src/cpp/desktop-mac/resources/png/dialog_warning at 2x.png
new file mode 100644
index 0000000..04ce719
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/dialog_warning at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/forward_mac.png b/src/cpp/desktop-mac/resources/png/forward_mac.png
new file mode 100644
index 0000000..dc73cbd
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/forward_mac.png differ
diff --git a/src/cpp/desktop-mac/resources/png/forward_mac at 2x.png b/src/cpp/desktop-mac/resources/png/forward_mac at 2x.png
new file mode 100644
index 0000000..7fe86a0
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/forward_mac at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/print_mac.png b/src/cpp/desktop-mac/resources/png/print_mac.png
new file mode 100644
index 0000000..1b1331d
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/print_mac.png differ
diff --git a/src/cpp/desktop-mac/resources/png/print_mac at 2x.png b/src/cpp/desktop-mac/resources/png/print_mac at 2x.png
new file mode 100644
index 0000000..bb76746
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/print_mac at 2x.png differ
diff --git a/src/cpp/desktop-mac/resources/png/reload_mac.png b/src/cpp/desktop-mac/resources/png/reload_mac.png
new file mode 100644
index 0000000..5b7f0dd
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/reload_mac.png differ
diff --git a/src/cpp/desktop-mac/resources/png/reload_mac at 2x.png b/src/cpp/desktop-mac/resources/png/reload_mac at 2x.png
new file mode 100644
index 0000000..4121ed9
Binary files /dev/null and b/src/cpp/desktop-mac/resources/png/reload_mac at 2x.png differ
diff --git a/src/cpp/desktop/.gitignore b/src/cpp/desktop/.gitignore
new file mode 100644
index 0000000..1f8ad2d
--- /dev/null
+++ b/src/cpp/desktop/.gitignore
@@ -0,0 +1,2 @@
+moc_*.cxx
+
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/QtLockedFile b/src/cpp/desktop/3rdparty/qtsingleapplication/QtLockedFile
new file mode 100644
index 0000000..16b48ba
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/QtLockedFile
@@ -0,0 +1 @@
+#include "qtlockedfile.h"
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/QtSingleApplication b/src/cpp/desktop/3rdparty/qtsingleapplication/QtSingleApplication
new file mode 100644
index 0000000..d111bf7
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/QtSingleApplication
@@ -0,0 +1 @@
+#include "qtsingleapplication.h"
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.cpp b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.cpp
new file mode 100644
index 0000000..019108d
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+
+#include "qtlocalpeer.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTime>
+
+#if defined(Q_OS_WIN)
+#include <QtCore/QLibrary>
+#include <QtCore/qt_windows.h>
+typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
+static PProcessIdToSessionId pProcessIdToSessionId = 0;
+#endif
+#if defined(Q_OS_UNIX)
+#include <time.h>
+#include <unistd.h>
+#endif
+
+namespace QtLP_Private {
+#include "qtlockedfile.cpp"
+#if defined(Q_OS_WIN)
+#include "qtlockedfile_win.cpp"
+#else
+#include "qtlockedfile_unix.cpp"
+#endif
+}
+
+const char* QtLocalPeer::ack = "ack";
+
+QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
+ : QObject(parent), id(appId)
+{
+ QString prefix = id;
+ if (id.isEmpty()) {
+ id = QCoreApplication::applicationFilePath();
+#if defined(Q_OS_WIN)
+ id = id.toLower();
+#endif
+ prefix = id.section(QLatin1Char('/'), -1);
+ }
+ prefix.remove(QRegExp(QString::fromAscii("[^a-zA-Z]")));
+ prefix.truncate(6);
+
+ QByteArray idc = id.toUtf8();
+ quint16 idNum = qChecksum(idc.constData(), idc.size());
+ socketName = QLatin1String("qtsingleapp-") + prefix
+ + QLatin1Char('-') + QString::number(idNum, 16);
+
+#if defined(Q_OS_WIN)
+ if (!pProcessIdToSessionId) {
+ QLibrary lib(QString::fromAscii("kernel32"));
+ pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
+ }
+ if (pProcessIdToSessionId) {
+ DWORD sessionId = 0;
+ pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
+ socketName += QLatin1Char('-') + QString::number(sessionId, 16);
+ }
+#else
+ socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
+#endif
+
+ server = new QLocalServer(this);
+ QString lockName = QDir(QDir::tempPath()).absolutePath()
+ + QLatin1Char('/') + socketName
+ + QLatin1String("-lockfile");
+ lockFile.setFileName(lockName);
+ lockFile.open(QIODevice::ReadWrite);
+}
+
+
+
+bool QtLocalPeer::isClient()
+{
+ if (lockFile.isLocked())
+ return false;
+
+ if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
+ return true;
+
+ bool res = server->listen(socketName);
+#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
+ // ### Workaround
+ if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
+ QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
+ res = server->listen(socketName);
+ }
+#endif
+ if (!res)
+ qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
+ QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
+ return false;
+}
+
+
+bool QtLocalPeer::sendMessage(const QString &message, int timeout)
+{
+ if (!isClient())
+ return false;
+
+ QLocalSocket socket;
+ bool connOk = false;
+ for(int i = 0; i < 2; i++) {
+ // Try twice, in case the other instance is just starting up
+ socket.connectToServer(socketName);
+ connOk = socket.waitForConnected(timeout/2);
+ if (connOk || i)
+ break;
+ int ms = 250;
+#if defined(Q_OS_WIN)
+ Sleep(DWORD(ms));
+#else
+ struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
+ nanosleep(&ts, NULL);
+#endif
+ }
+ if (!connOk)
+ return false;
+
+ QByteArray uMsg(message.toUtf8());
+ QDataStream ds(&socket);
+ ds.writeBytes(uMsg.constData(), uMsg.size());
+ bool res = socket.waitForBytesWritten(timeout);
+ if (res) {
+ res &= socket.waitForReadyRead(timeout); // wait for ack
+ if (res)
+ res &= (socket.read(qstrlen(ack)) == ack);
+ }
+ return res;
+}
+
+
+void QtLocalPeer::receiveConnection()
+{
+ QLocalSocket* socket = server->nextPendingConnection();
+ if (!socket)
+ return;
+
+ while (socket->bytesAvailable() < (int)sizeof(quint32))
+ socket->waitForReadyRead();
+ QDataStream ds(socket);
+ QByteArray uMsg;
+ quint32 remaining;
+ ds >> remaining;
+ uMsg.resize(remaining);
+ int got = 0;
+ char* uMsgBuf = uMsg.data();
+ do {
+ got = ds.readRawData(uMsgBuf, remaining);
+ remaining -= got;
+ uMsgBuf += got;
+ } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
+ if (got < 0) {
+ qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
+ delete socket;
+ return;
+ }
+ QString message(QString::fromUtf8(uMsg));
+ socket->write(ack, qstrlen(ack));
+ socket->waitForBytesWritten(1000);
+ delete socket;
+ emit messageReceived(message); //### (might take a long time to return)
+}
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.h b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.h
new file mode 100644
index 0000000..e834b73
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlocalpeer.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#ifndef QTLOCALPEER_H
+#define QTLOCALPEER_H
+
+#include <QtNetwork/QLocalServer>
+#include <QtNetwork/QLocalSocket>
+#include <QtCore/QDir>
+
+#include "qtlockedfile.h"
+
+class QtLocalPeer : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
+ bool isClient();
+ bool sendMessage(const QString &message, int timeout);
+ QString applicationId() const
+ { return id; }
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+protected Q_SLOTS:
+ void receiveConnection();
+
+protected:
+ QString id;
+ QString socketName;
+ QLocalServer* server;
+ QtLP_Private::QtLockedFile lockFile;
+
+private:
+ static const char* ack;
+};
+
+#endif // QTLOCALPEER_H
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.cpp b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.cpp
new file mode 100644
index 0000000..3e73ba6
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.cpp
@@ -0,0 +1,192 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+/*!
+ \class QtLockedFile
+
+ \brief The QtLockedFile class extends QFile with advisory locking
+ functions.
+
+ A file may be locked in read or write mode. Multiple instances of
+ \e QtLockedFile, created in multiple processes running on the same
+ machine, may have a file locked in read mode. Exactly one instance
+ may have it locked in write mode. A read and a write lock cannot
+ exist simultaneously on the same file.
+
+ The file locks are advisory. This means that nothing prevents
+ another process from manipulating a locked file using QFile or
+ file system functions offered by the OS. Serialization is only
+ guaranteed if all processes that access the file use
+ QLockedFile. Also, while holding a lock on a file, a process
+ must not open the same file again (through any API), or locks
+ can be unexpectedly lost.
+
+ The lock provided by an instance of \e QtLockedFile is released
+ whenever the program terminates. This is true even when the
+ program crashes and no destructors are called.
+*/
+
+/*! \enum QtLockedFile::LockMode
+
+ This enum describes the available lock modes.
+
+ \value ReadLock A read lock.
+ \value WriteLock A write lock.
+ \value NoLock Neither a read lock nor a write lock.
+*/
+
+/*!
+ Constructs an unlocked \e QtLockedFile object. This constructor
+ behaves in the same way as \e QFile::QFile().
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile()
+ : QFile()
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Constructs an unlocked QtLockedFile object with file \a name. This
+ constructor behaves in the same way as \e QFile::QFile(const
+ QString&).
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile(const QString &name)
+ : QFile(name)
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Opens the file in OpenMode \a mode.
+
+ This is identical to QFile::open(), with the one exception that the
+ Truncate mode flag is disallowed. Truncation would conflict with the
+ advisory file locking, since the file would be modified before the
+ write lock is obtained. If truncation is required, use resize(0)
+ after obtaining the write lock.
+
+ Returns true if successful; otherwise false.
+
+ \sa QFile::open(), QFile::resize()
+*/
+bool QtLockedFile::open(OpenMode mode)
+{
+ if (mode & QIODevice::Truncate) {
+ qWarning("QtLockedFile::open(): Truncate mode not allowed.");
+ return false;
+ }
+ return QFile::open(mode);
+}
+
+/*!
+ Returns \e true if this object has a in read or write lock;
+ otherwise returns \e false.
+
+ \sa lockMode()
+*/
+bool QtLockedFile::isLocked() const
+{
+ return m_lock_mode != NoLock;
+}
+
+/*!
+ Returns the type of lock currently held by this object, or \e
+ QtLockedFile::NoLock.
+
+ \sa isLocked()
+*/
+QtLockedFile::LockMode QtLockedFile::lockMode() const
+{
+ return m_lock_mode;
+}
+
+/*!
+ \fn bool QtLockedFile::lock(LockMode mode, bool block = true)
+
+ Obtains a lock of type \a mode. The file must be opened before it
+ can be locked.
+
+ If \a block is true, this function will block until the lock is
+ aquired. If \a block is false, this function returns \e false
+ immediately if the lock cannot be aquired.
+
+ If this object already has a lock of type \a mode, this function
+ returns \e true immediately. If this object has a lock of a
+ different type than \a mode, the lock is first released and then a
+ new lock is obtained.
+
+ This function returns \e true if, after it executes, the file is
+ locked by this object, and \e false otherwise.
+
+ \sa unlock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn bool QtLockedFile::unlock()
+
+ Releases a lock.
+
+ If the object has no lock, this function returns immediately.
+
+ This function returns \e true if, after it executes, the file is
+ not locked by this object, and \e false otherwise.
+
+ \sa lock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn QtLockedFile::~QtLockedFile()
+
+ Destroys the \e QtLockedFile object. If any locks were held, they
+ are released.
+*/
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.h b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.h
new file mode 100644
index 0000000..07a42bf
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#ifndef QTLOCKEDFILE_H
+#define QTLOCKEDFILE_H
+
+#include <QtCore/QFile>
+#ifdef Q_OS_WIN
+#include <QtCore/QVector>
+#endif
+
+#if defined(Q_WS_WIN)
+# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
+# define QT_QTLOCKEDFILE_EXPORT
+# elif defined(QT_QTLOCKEDFILE_IMPORT)
+# if defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# endif
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
+# elif defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
+# endif
+#else
+# define QT_QTLOCKEDFILE_EXPORT
+#endif
+
+namespace QtLP_Private {
+
+class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
+{
+public:
+ enum LockMode { NoLock = 0, ReadLock, WriteLock };
+
+ QtLockedFile();
+ QtLockedFile(const QString &name);
+ ~QtLockedFile();
+
+ bool open(OpenMode mode);
+
+ bool lock(LockMode mode, bool block = true);
+ bool unlock();
+ bool isLocked() const;
+ LockMode lockMode() const;
+
+private:
+#ifdef Q_OS_WIN
+ Qt::HANDLE wmutex;
+ Qt::HANDLE rmutex;
+ QVector<Qt::HANDLE> rmutexes;
+ QString mutexname;
+
+ Qt::HANDLE getMutexHandle(int idx, bool doCreate);
+ bool waitMutex(Qt::HANDLE mutex, bool doBlock);
+
+#endif
+ LockMode m_lock_mode;
+};
+}
+#endif
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp
new file mode 100644
index 0000000..715c7d9
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_unix.cpp
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "qtlockedfile.h"
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
+ int cmd = block ? F_SETLKW : F_SETLK;
+ int ret = fcntl(handle(), cmd, &fl);
+
+ if (ret == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+
+ m_lock_mode = mode;
+ return true;
+}
+
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ int ret = fcntl(handle(), F_SETLKW, &fl);
+
+ if (ret == -1) {
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+ m_lock_mode = NoLock;
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+}
+
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_win.cpp b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_win.cpp
new file mode 100644
index 0000000..0a32b71
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtlockedfile_win.cpp
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+#include <qt_windows.h>
+#include <QtCore/QFileInfo>
+
+#undef QT_WA
+#define QT_WA(unicode, ansi) ansi
+
+#define MUTEX_PREFIX "QtLockedFile mutex "
+// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
+#define MAX_READERS MAXIMUM_WAIT_OBJECTS
+
+Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
+{
+ if (mutexname.isEmpty()) {
+ QFileInfo fi(*this);
+ mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ + fi.absoluteFilePath().toLower();
+ }
+ QString mname(mutexname);
+ if (idx >= 0)
+ mname += QString::number(idx);
+
+ Qt::HANDLE mutex;
+ if (doCreate) {
+ QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
+ { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
+ if (!mutex) {
+ qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
+ return 0;
+ }
+ }
+ else {
+ QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
+ { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
+ if (!mutex) {
+ if (GetLastError() != ERROR_FILE_NOT_FOUND)
+ qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
+ return 0;
+ }
+ }
+ return mutex;
+}
+
+bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
+{
+ Q_ASSERT(mutex);
+ DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
+ switch (res) {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED:
+ return true;
+ break;
+ case WAIT_TIMEOUT:
+ break;
+ default:
+ qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
+ }
+ return false;
+}
+
+
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
+ return false;
+
+ if (!waitMutex(wmutex, block))
+ return false;
+
+ if (mode == ReadLock) {
+ int idx = 0;
+ for (; idx < MAX_READERS; idx++) {
+ rmutex = getMutexHandle(idx, false);
+ if (!rmutex || waitMutex(rmutex, false))
+ break;
+ CloseHandle(rmutex);
+ }
+ bool ok = true;
+ if (idx >= MAX_READERS) {
+ qWarning("QtLockedFile::lock(): too many readers");
+ rmutex = 0;
+ ok = false;
+ }
+ else if (!rmutex) {
+ rmutex = getMutexHandle(idx, true);
+ if (!rmutex || !waitMutex(rmutex, false))
+ ok = false;
+ }
+ if (!ok && rmutex) {
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ ReleaseMutex(wmutex);
+ if (!ok)
+ return false;
+ }
+ else {
+ Q_ASSERT(rmutexes.isEmpty());
+ for (int i = 0; i < MAX_READERS; i++) {
+ Qt::HANDLE mutex = getMutexHandle(i, false);
+ if (mutex)
+ rmutexes.append(mutex);
+ }
+ if (rmutexes.size()) {
+ DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
+ TRUE, block ? INFINITE : 0);
+ if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
+ if (res != WAIT_TIMEOUT)
+ qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
+ m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
+ unlock();
+ return false;
+ }
+ }
+ }
+
+ m_lock_mode = mode;
+ return true;
+}
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ if (m_lock_mode == ReadLock) {
+ ReleaseMutex(rmutex);
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ else {
+ foreach(Qt::HANDLE mutex, rmutexes) {
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ }
+ rmutexes.clear();
+ ReleaseMutex(wmutex);
+ }
+
+ m_lock_mode = QtLockedFile::NoLock;
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+ if (wmutex)
+ CloseHandle(wmutex);
+}
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.cpp b/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.cpp
new file mode 100644
index 0000000..5a8f1b0
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.cpp
@@ -0,0 +1,344 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
+#include <QtGui/QWidget>
+
+
+/*!
+ \class QtSingleApplication qtsingleapplication.h
+ \brief The QtSingleApplication class provides an API to detect and
+ communicate with running instances of an application.
+
+ This class allows you to create applications where only one
+ instance should be running at a time. I.e., if the user tries to
+ launch another instance, the already running instance will be
+ activated instead. Another usecase is a client-server system,
+ where the first started instance will assume the role of server,
+ and the later instances will act as clients of that server.
+
+ By default, the full path of the executable file is used to
+ determine whether two processes are instances of the same
+ application. You can also provide an explicit identifier string
+ that will be compared instead.
+
+ The application should create the QtSingleApplication object early
+ in the startup phase, and call isRunning() to find out if another
+ instance of this application is already running. If isRunning()
+ returns false, it means that no other instance is running, and
+ this instance has assumed the role as the running instance. In
+ this case, the application should continue with the initialization
+ of the application user interface before entering the event loop
+ with exec(), as normal.
+
+ The messageReceived() signal will be emitted when the running
+ application receives messages from another instance of the same
+ application. When a message is received it might be helpful to the
+ user to raise the application so that it becomes visible. To
+ facilitate this, QtSingleApplication provides the
+ setActivationWindow() function and the activateWindow() slot.
+
+ If isRunning() returns true, another instance is already
+ running. It may be alerted to the fact that another instance has
+ started by using the sendMessage() function. Also data such as
+ startup parameters (e.g. the name of the file the user wanted this
+ new instance to open) can be passed to the running instance with
+ this function. Then, the application should terminate (or enter
+ client mode).
+
+ If isRunning() returns true, but sendMessage() fails, that is an
+ indication that the running instance is frozen.
+
+ Here's an example that shows how to convert an existing
+ application to use QtSingleApplication. It is very simple and does
+ not make use of all QtSingleApplication's functionality (see the
+ examples for that).
+
+ \code
+ // Original
+ int main(int argc, char **argv)
+ {
+ QApplication app(argc, argv);
+
+ MyMainWidget mmw;
+ mmw.show();
+ return app.exec();
+ }
+
+ // Single instance
+ int main(int argc, char **argv)
+ {
+ QtSingleApplication app(argc, argv);
+
+ if (app.isRunning())
+ return !app.sendMessage(someDataString);
+
+ MyMainWidget mmw;
+ app.setActivationWindow(&mmw);
+ mmw.show();
+ return app.exec();
+ }
+ \endcode
+
+ Once this QtSingleApplication instance is destroyed (normally when
+ the process exits or crashes), when the user next attempts to run the
+ application this instance will not, of course, be encountered. The
+ next instance to call isRunning() or sendMessage() will assume the
+ role as the new running instance.
+
+ For console (non-GUI) applications, QtSingleCoreApplication may be
+ used instead of this class, to avoid the dependency on the QtGui
+ library.
+
+ \sa QtSingleCoreApplication
+*/
+
+
+void QtSingleApplication::sysInit(const QString &appId)
+{
+ actWin = 0;
+ peer = new QtLocalPeer(this, appId);
+ connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+ Creates a QtSingleApplication object. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a argc, \a
+ argv, and \a GUIenabled are passed on to the QAppliation constructor.
+
+ If you are creating a console application (i.e. setting \a
+ GUIenabled to false), you may consider using
+ QtSingleCoreApplication instead.
+*/
+
+QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
+ : QApplication(argc, argv, GUIenabled)
+{
+ sysInit();
+}
+
+
+/*!
+ Creates a QtSingleApplication object with the application
+ identifier \a appId. \a argc and \a argv are passed on to the
+ QAppliation constructor.
+*/
+
+QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
+ : QApplication(argc, argv)
+{
+ sysInit(appId);
+}
+
+
+/*!
+ Creates a QtSingleApplication object. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a argc, \a
+ argv, and \a type are passed on to the QAppliation constructor.
+*/
+QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
+ : QApplication(argc, argv, type)
+{
+ sysInit();
+}
+
+
+#if defined(Q_WS_X11)
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
+ and \a cmap are passed on to the QApplication constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, visual, cmap)
+{
+ sysInit();
+}
+
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
+ argv, \a visual, and \a cmap are passed on to the QApplication
+ constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, argc, argv, visual, cmap)
+{
+ sysInit();
+}
+
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be \a appId. \a dpy, \a argc, \a
+ argv, \a visual, and \a cmap are passed on to the QApplication
+ constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, argc, argv, visual, cmap)
+{
+ sysInit(appId);
+}
+#endif
+
+
+/*!
+ Returns true if another instance of this application is running;
+ otherwise false.
+
+ This function does not find instances of this application that are
+ being run by a different user (on Windows: that are running in
+ another session).
+
+ \sa sendMessage()
+*/
+
+bool QtSingleApplication::isRunning()
+{
+ return peer->isClient();
+}
+
+
+/*!
+ Tries to send the text \a message to the currently running
+ instance. The QtSingleApplication object in the running instance
+ will emit the messageReceived() signal when it receives the
+ message.
+
+ This function returns true if the message has been sent to, and
+ processed by, the current instance. If there is no instance
+ currently running, or if the running instance fails to process the
+ message within \a timeout milliseconds, this function return false.
+
+ \sa isRunning(), messageReceived()
+*/
+bool QtSingleApplication::sendMessage(const QString &message, int timeout)
+{
+ return peer->sendMessage(message, timeout);
+}
+
+
+/*!
+ Returns the application identifier. Two processes with the same
+ identifier will be regarded as instances of the same application.
+*/
+QString QtSingleApplication::id() const
+{
+ return peer->applicationId();
+}
+
+
+/*!
+ Sets the activation window of this application to \a aw. The
+ activation window is the widget that will be activated by
+ activateWindow(). This is typically the application's main window.
+
+ If \a activateOnMessage is true (the default), the window will be
+ activated automatically every time a message is received, just prior
+ to the messageReceived() signal being emitted.
+
+ \sa activateWindow(), messageReceived()
+*/
+
+void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
+{
+ actWin = aw;
+ if (activateOnMessage)
+ connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+ else
+ disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+}
+
+
+/*!
+ Returns the applications activation window if one has been set by
+ calling setActivationWindow(), otherwise returns 0.
+
+ \sa setActivationWindow()
+*/
+QWidget* QtSingleApplication::activationWindow() const
+{
+ return actWin;
+}
+
+
+/*!
+ De-minimizes, raises, and activates this application's activation window.
+ This function does nothing if no activation window has been set.
+
+ This is a convenience function to show the user that this
+ application instance has been activated when he has tried to start
+ another instance.
+
+ This function should typically be called in response to the
+ messageReceived() signal. By default, that will happen
+ automatically, if an activation window has been set.
+
+ \sa setActivationWindow(), messageReceived(), initialize()
+*/
+void QtSingleApplication::activateWindow()
+{
+ if (actWin) {
+ actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
+ actWin->raise();
+ actWin->activateWindow();
+ }
+}
+
+
+/*!
+ \fn void QtSingleApplication::messageReceived(const QString& message)
+
+ This signal is emitted when the current instance receives a \a
+ message from another instance of this application.
+
+ \sa sendMessage(), setActivationWindow(), activateWindow()
+*/
+
+
+/*!
+ \fn void QtSingleApplication::initialize(bool dummy = true)
+
+ \obsolete
+*/
diff --git a/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.h b/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.h
new file mode 100644
index 0000000..d1613a4
--- /dev/null
+++ b/src/cpp/desktop/3rdparty/qtsingleapplication/qtsingleapplication.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * 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.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** 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
+** OWNER 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."
+**
+****************************************************************************/
+
+#ifndef QTSINGLEAPPLICATION_H
+#define QTSINGLEAPPLICATION_H
+
+#include <QtGui/QApplication>
+
+class QtLocalPeer;
+
+#if defined(Q_WS_WIN)
+# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
+# define QT_QTSINGLEAPPLICATION_EXPORT
+# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
+# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
+# undef QT_QTSINGLEAPPLICATION_EXPORT
+# endif
+# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
+# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
+# undef QT_QTSINGLEAPPLICATION_EXPORT
+# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
+# endif
+#else
+# define QT_QTSINGLEAPPLICATION_EXPORT
+#endif
+
+class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
+{
+ Q_OBJECT
+
+public:
+ QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
+ QtSingleApplication(const QString &id, int &argc, char **argv);
+ QtSingleApplication(int &argc, char **argv, Type type);
+#if defined(Q_WS_X11)
+ QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+ QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
+ QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+#endif
+
+ bool isRunning();
+ QString id() const;
+
+ void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
+ QWidget* activationWindow() const;
+
+ // Obsolete:
+ void initialize(bool dummy = true)
+ { isRunning(); Q_UNUSED(dummy) }
+
+public Q_SLOTS:
+ bool sendMessage(const QString &message, int timeout = 5000);
+ void activateWindow();
+
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+
+private:
+ void sysInit(const QString &appId = QString());
+ QtLocalPeer *peer;
+ QWidget *actWin;
+};
+
+#endif // QTSINGLEAPPLICATION_H
diff --git a/src/cpp/desktop/CMakeLists.txt b/src/cpp/desktop/CMakeLists.txt
new file mode 100644
index 0000000..c89318d
--- /dev/null
+++ b/src/cpp/desktop/CMakeLists.txt
@@ -0,0 +1,481 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(DESKTOP)
+
+# require Qt 4.8
+set(QT_VERSION 4.8.0)
+
+# on unix prefer qtsdk installs over system-level libraries. note this
+# can be overriden by defining QT_QMAKE_EXECUTABLE when invoking cmake
+if(NOT WIN32)
+ # prefer rstudio qtsdk install then home qtsdk install
+ if(NOT QT_QMAKE_EXECUTABLE)
+ set(QMAKE_QT48_SDK "/opt/RStudio-QtSDK/Desktop/Qt/4.8.0/gcc/bin/qmake")
+ if(EXISTS ${QMAKE_QT48_SDK})
+ set(QT_QMAKE_EXECUTABLE ${QMAKE_QT48_SDK})
+ else()
+ set(QMAKE_QT48_SDK "$ENV{HOME}/QtSDK/Desktop/Qt/4.8.0/gcc/bin/qmake")
+ if(EXISTS ${QMAKE_QT48_SDK})
+ set(QT_QMAKE_EXECUTABLE ${QMAKE_QT48_SDK})
+ endif()
+ endif()
+ endif()
+else()
+ if(NOT QT_QMAKE_EXECUTABLE)
+ set(QMAKE_QT48_SDK "C:\\Qt\\4.8.3\\bin\\qmake.exe")
+ if(EXISTS ${QMAKE_QT48_SDK})
+ set(QT_QMAKE_EXECUTABLE ${QMAKE_QT48_SDK})
+ endif()
+ endif()
+endif()
+
+# find and include Qt
+add_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII)
+list(APPEND RSTUDIO_QT_COMPONENTS QtCore QtGui QtNetwork QtWebKit)
+if(UNIX AND NOT APPLE)
+ list(APPEND RSTUDIO_QT_COMPONENTS QtDBus)
+endif()
+find_package(Qt4 ${QT_VERSION} COMPONENTS ${RSTUDIO_QT_COMPONENTS} REQUIRED)
+include(${QT_USE_FILE})
+
+# disable clang warnings for qt sources
+if(APPLE)
+ add_definitions(-Wno-unused-private-field
+ -Wno-uninitialized)
+endif()
+
+# configure NOTICE file into build directory so it can be read at dev time
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../../../NOTICE
+ ${CMAKE_CURRENT_BINARY_DIR}/.. COPYONLY)
+
+# include files
+file(GLOB_RECURSE DESKTOP_HEADER_FILES "*.h*")
+if (NOT WIN32)
+ list(REMOVE_ITEM DESKTOP_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/DesktopChooseRHome.hpp)
+endif (NOT WIN32)
+
+set(MOC_DESKTOP_HEADER_FILES ${DESKTOP_HEADER_FILES})
+list(REMOVE_ITEM MOC_DESKTOP_HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qtsingleapplication/qtlockedfile.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopDetectRHome.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopOptions.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopRVersion.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopNetworkIOService.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopNetworkProxyFactory.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopGwtCallbackOwner.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopUtils.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/desktop-config.h.in
+)
+
+if(WIN32)
+ list(REMOVE_ITEM MOC_DESKTOP_HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopPosixApplication.hpp)
+endif()
+if(WIN32 OR APPLE)
+ list(REMOVE_ITEM MOC_DESKTOP_HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/synctex/evince/EvinceDaemon.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/synctex/evince/EvinceWindow.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/synctex/evince/EvinceSynctex.hpp
+ )
+endif()
+if(NOT WIN32)
+ list(REMOVE_ITEM MOC_DESKTOP_HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/synctex/sumatra/SumatraSynctex.hpp
+ )
+endif()
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/desktop-config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/desktop-config.h)
+
+# source files
+set(DESKTOP_SOURCE_FILES
+ DesktopAboutDialog.cpp
+ DesktopBrowserWindow.cpp
+ DesktopCommandInvoker.cpp
+ DesktopDataUriNetworkReply.cpp
+ DesktopDownloadHelper.cpp
+ DesktopGwtCallback.cpp
+ DesktopGwtWindow.cpp
+ DesktopInputDialog.cpp
+ DesktopMain.cpp
+ DesktopMainWindow.cpp
+ DesktopMenuCallback.cpp
+ DesktopNetworkIOService.cpp
+ DesktopNetworkReply.cpp
+ DesktopNetworkProxyFactory.cpp
+ DesktopOptions.cpp
+ DesktopURLDownloader.cpp
+ DesktopUtils.cpp
+ DesktopWebPage.cpp
+ DesktopWebView.cpp
+ DesktopWindowTracker.cpp
+ DesktopSatelliteWindow.cpp
+ DesktopSecondaryWindow.cpp
+ DesktopSessionLauncher.cpp
+ DesktopSlotBinders.cpp
+ DesktopSubMenu.cpp
+ DesktopSynctex.cpp
+ DesktopNetworkAccessManager.cpp
+ 3rdparty/qtsingleapplication/qtsingleapplication.cpp
+ 3rdparty/qtsingleapplication/qtlocalpeer.cpp
+)
+
+if(WIN32)
+ set(DESKTOP_SOURCE_FILES ${DESKTOP_SOURCE_FILES}
+ DesktopChooseRHome.cpp
+ DesktopWin32ApplicationLaunch.cpp
+ DesktopRVersion.cpp
+ DesktopWin32DetectRHome.cpp
+ synctex/sumatra/SumatraSynctex.cpp
+ )
+ list(REMOVE_ITEM MOC_DESKTOP_HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/DesktopPosixApplication.hpp
+ )
+else()
+ set(DESKTOP_SOURCE_FILES ${DESKTOP_SOURCE_FILES}
+ DesktopPosixApplication.cpp
+ DesktopPosixApplicationLaunch.cpp
+ DesktopPosixDetectRHome.cpp
+ )
+ if(NOT APPLE)
+ set(DESKTOP_SOURCE_FILES ${DESKTOP_SOURCE_FILES}
+ synctex/evince/EvinceDaemon.cpp
+ synctex/evince/EvinceSynctex.cpp
+ synctex/evince/EvinceWindow.cpp
+ )
+ endif()
+endif(WIN32)
+
+if(APPLE)
+ set(DESKTOP_SOURCE_FILES ${DESKTOP_SOURCE_FILES}
+ DesktopUtilsMac.mm
+ )
+endif()
+
+# include directories
+include_directories(
+ include
+ ${CORE_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# ensure moc files are generated
+qt4_wrap_cpp(MOC_DESKTOP_SOURCE_FILES ${MOC_DESKTOP_HEADER_FILES})
+qt4_wrap_ui(DESKTOP_UI_SOURCES
+ DesktopAboutDialog.ui
+ DesktopChooseRHome.ui
+ DesktopInputDialog.ui
+)
+
+set(DESKTOP_RESOURCES_FILES desktop.qrc)
+qt4_add_resources(DESKTOP_RESOURCES_SOURCES ${DESKTOP_RESOURCES_FILES})
+
+if(WIN32)
+
+ # configure rstudio.rc
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rstudio.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rstudio.rc)
+
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/RStudio.ico
+ ${CMAKE_CURRENT_BINARY_DIR}/RStudio.ico COPYONLY)
+
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/RProject.ico
+ ${CMAKE_CURRENT_BINARY_DIR}/RProject.ico COPYONLY)
+
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rstudio.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/rstudio.exe.manifest COPYONLY)
+
+ if(MINGW)
+ set(LINK_FLAGS -Wl,-subsystem,windows -lversion)
+ add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "rstudio.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/rstudio.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/rstudio.exe.manifest"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RStudio.ico"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RProject.ico")
+ set(DESKTOP_SOURCE_FILES
+ ${DESKTOP_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+ endif(MINGW)
+
+ if(NOT RSTUDIO_SESSION_WIN64)
+ add_subdirectory(urlopener)
+ add_subdirectory(synctex/rsinverse)
+ endif()
+
+elseif(APPLE)
+ find_library(APPLICATION_SERVICES_LIBRARY NAMES ApplicationServices)
+ find_library(COCOA_LIBRARY NAMES Cocoa)
+ find_library(SECURITY_LIBRARY NAMES Security)
+endif(WIN32)
+
+
+# determine whether we should bundle Qt. we pretty much always want to
+# bundle it unless we are building on linux and NOT linking against the
+# qtsdk -- in that case we are linking against system versions of the
+# qt libraries which should therefore not be bundled
+if(APPLE OR WIN32 OR
+ RSTUDIO_PACKAGE_BUILD OR
+ (${QT_LIBRARY_DIR} MATCHES ".*QtSDK.*"))
+ set(RSTUDIO_BUNDLE_QT TRUE)
+endif()
+
+# define executable (Windows & Linux)
+if(NOT APPLE)
+
+ add_executable(rstudio
+ ${DESKTOP_SOURCE_FILES}
+ ${MOC_DESKTOP_SOURCE_FILES}
+ ${DESKTOP_RESOURCES_SOURCES}
+ ${DESKTOP_UI_SOURCES}
+ )
+
+ # add rpath for linux so we can find qt libraries in our bin dir
+ if(UNIX AND RSTUDIO_BUNDLE_QT)
+ set_target_properties(rstudio PROPERTIES
+ INSTALL_RPATH \$ORIGIN)
+ endif()
+
+ # set link dependencies
+ target_link_libraries(rstudio
+ ${QT_LIBRARIES}
+ rstudio-core
+ ${LINK_FLAGS}
+ )
+
+# for OSX we create a bundle
+else()
+
+ # configure Info.plist
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+ ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
+
+ # collect list of icns files
+ file(GLOB ICNS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/icns/*.icns)
+
+ # set our icns as the bundle icon
+ set(MACOSX_BUNDLE_ICON_FILE RStudio.icns)
+ set_source_files_properties(${ICNS_FILES}
+ PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+
+ # define bundle name and executable
+ set(MACOSX_BUNDLE_BUNDLE_NAME "RStudio")
+
+ add_executable(RStudio MACOSX_BUNDLE
+ ${DESKTOP_SOURCE_FILES}
+ ${MOC_DESKTOP_SOURCE_FILES}
+ ${DESKTOP_RESOURCES_SOURCES}
+ ${DESKTOP_UI_SOURCES}
+ ${ICNS_FILES})
+
+ target_link_libraries(RStudio
+ ${QT_LIBRARIES}
+ rstudio-core
+ ${APPLICATION_SERVICES_LIBRARY}
+ ${COCOA_LIBRARY}
+ ${SECURITY_LIBRARY})
+
+endif()
+
+# install target (OSX install goes into the bundle)
+if(APPLE)
+ set_target_properties(RStudio PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
+ install(TARGETS RStudio BUNDLE DESTINATION .)
+else()
+ install(TARGETS rstudio DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
+
+# bundle qt dependencies if this is a package build
+if(RSTUDIO_BUNDLE_QT)
+ # set lib suffix for windows
+ if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
+ set(LIB_SUFFIX "d4")
+ else()
+ set(LIB_SUFFIX "4")
+ endif()
+
+ # install qt conf
+ if(APPLE)
+ set(QTCONF_DEST_DIR ${RSTUDIO_INSTALL_SUPPORTING})
+ set(QTPLUGINS_DEST_DIR RStudio.app/Contents)
+ else()
+ set(QTCONF_DEST_DIR ${RSTUDIO_INSTALL_BIN})
+ set(QTPLUGINS_DEST_DIR ${RSTUDIO_INSTALL_BIN})
+ endif()
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/qt.conf
+ DESTINATION ${QTCONF_DEST_DIR})
+
+ # copy qt plugins
+ if(WIN32)
+ set(QT_IMAGEFORMATS_DIR "${QT_PLUGINS_DIR}/imageformats")
+ install(PROGRAMS "${QT_IMAGEFORMATS_DIR}/qgif${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qico${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qjpeg${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qmng${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qsvg${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qtga${LIB_SUFFIX}.dll"
+ "${QT_IMAGEFORMATS_DIR}/qtiff${LIB_SUFFIX}.dll"
+ DESTINATION ${QTPLUGINS_DEST_DIR}/plugins/imageformats)
+ else()
+ install(DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
+ DESTINATION ${QTPLUGINS_DEST_DIR}/plugins
+ PATTERN "*.a" EXCLUDE)
+ endif()
+
+ # fixup bundle on osx
+ if(APPLE)
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/RStudio.app")
+ set(DIRS ${QT_LIBRARY_DIRS})
+ INSTALL(CODE "
+ file(GLOB_RECURSE QTPLUGINS
+ \"\${CMAKE_INSTALL_PREFIX}/${QTPLUGINS_DEST_DIR}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
+ include(BundleUtilities)
+ file(REMOVE \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/MacOS/rsession\")
+ file(REMOVE \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/MacOS/rserver\")
+ file(REMOVE \"\${CMAKE_INSTALL_PREFIX}/RStudio.app/Contents/MacOS/rserver-pam\")
+ fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\")
+ ")
+
+ # copy qt shared objects directly to bin on unix (fixed up rpath above)
+ elseif(UNIX)
+ # install dependent libraries
+ set(QT_FULL_VERSION "${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
+ install(PROGRAMS ${QT_LIBRARY_DIR}/libQtCore.so.4
+ ${QT_LIBRARY_DIR}/libQtCore.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtDBus.so.4
+ ${QT_LIBRARY_DIR}/libQtDBus.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtGui.so.4
+ ${QT_LIBRARY_DIR}/libQtGui.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtNetwork.so.4
+ ${QT_LIBRARY_DIR}/libQtNetwork.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtWebKit.so.4
+ ${QT_LIBRARY_DIR}/libQtWebKit.so.4.9.0
+ ${QT_LIBRARY_DIR}/libQtSvg.so.4
+ ${QT_LIBRARY_DIR}/libQtSvg.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtXml.so.4
+ ${QT_LIBRARY_DIR}/libQtXml.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libQtXmlPatterns.so.4
+ ${QT_LIBRARY_DIR}/libQtXmlPatterns.so.${QT_FULL_VERSION}
+ ${QT_LIBRARY_DIR}/libphonon.so.4
+ ${QT_LIBRARY_DIR}/libphonon.so.4.4.0
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+ # copy qt dlls directly to bin on win32
+ elseif(WIN32)
+ install(PROGRAMS ${QT_BINARY_DIR}/QtCore${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/QtGui${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/QtNetwork${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/QtWebKit${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/QtSvg${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/QtXml${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/phonon${LIB_SUFFIX}.dll
+ ${QT_BINARY_DIR}/mingwm10.dll
+ ${QT_BINARY_DIR}/libgcc_s_dw2-1.dll
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+ endif()
+endif(RSTUDIO_BUNDLE_QT)
+
+if (UNIX AND NOT APPLE)
+ # add rstudio icon to root (so people installing from source or tar.gz can find it
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/freedesktop/icons/48x48/rstudio.png
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING})
+
+ # install configured backtrace utility on linux
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rstudio-backtrace.sh.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rstudio-backtrace.sh)
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/rstudio-backtrace.sh
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
+
+# install mac-terminal script on apple
+if(APPLE)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/mac-terminal.in
+ ${CMAKE_CURRENT_BINARY_DIR}/mac-terminal)
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/mac-terminal
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
+
+# install desktop integration files on linux
+if(RSTUDIO_INSTALL_FREEDESKTOP)
+
+ # define freedesktop dirs
+ set(RSTUDIO_FREEDESKTOP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources/freedesktop)
+ set(RSTUDIO_FREEDESKTOP_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/resources/freedesktop)
+
+ # desktop file (dynamically configured with paths)
+ configure_file (${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/rstudio.desktop.in
+ ${RSTUDIO_FREEDESKTOP_BINARY_DIR}/rstudio.desktop)
+ install(FILES ${RSTUDIO_FREEDESKTOP_BINARY_DIR}/rstudio.desktop
+ DESTINATION /usr/share/applications)
+
+ # mime types
+ install(FILES ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/rstudio.xml
+ DESTINATION /usr/share/mime/packages)
+
+ # define icon dirs
+ set(RSTUDIO_ICONS_16 ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/icons/16x16)
+ set(RSTUDIO_ICONS_24 ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/icons/24x24)
+ set(RSTUDIO_ICONS_32 ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/icons/32x32)
+ set(RSTUDIO_ICONS_48 ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/icons/48x48)
+ set(RSTUDIO_ICONS_256 ${RSTUDIO_FREEDESKTOP_SOURCE_DIR}/icons/256x256)
+
+ # application icon
+ install(FILES ${RSTUDIO_ICONS_32}/rstudio.png
+ DESTINATION /usr/share/pixmaps)
+ install(FILES ${RSTUDIO_ICONS_16}/rstudio.png
+ DESTINATION /usr/share/icons/hicolor/16x16/apps)
+ install(FILES ${RSTUDIO_ICONS_24}/rstudio.png
+ DESTINATION /usr/share/icons/hicolor/24x24/apps)
+ install(FILES ${RSTUDIO_ICONS_32}/rstudio.png
+ DESTINATION /usr/share/icons/hicolor/32x32/apps)
+ install(FILES ${RSTUDIO_ICONS_48}/rstudio.png
+ DESTINATION /usr/share/icons/hicolor/48x48/apps)
+ install(FILES ${RSTUDIO_ICONS_256}/rstudio.png
+ DESTINATION /usr/share/icons/hicolor/256x256/apps)
+
+ # .RData icon
+ install(FILES ${RSTUDIO_ICONS_16}/application-x-r-data.png
+ DESTINATION /usr/share/icons/hicolor/16x16/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_24}/application-x-r-data.png
+ DESTINATION /usr/share/icons/hicolor/24x24/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_32}/application-x-r-data.png
+ DESTINATION /usr/share/icons/hicolor/32x32/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_48}/application-x-r-data.png
+ DESTINATION /usr/share/icons/hicolor/48x48/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_256}/application-x-r-data.png
+ DESTINATION /usr/share/icons/hicolor/256x256/mimetypes)
+
+ # .Rproj icon
+ install(FILES ${RSTUDIO_ICONS_16}/application-x-r-project.png
+ DESTINATION /usr/share/icons/hicolor/16x16/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_24}/application-x-r-project.png
+ DESTINATION /usr/share/icons/hicolor/24x24/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_32}/application-x-r-project.png
+ DESTINATION /usr/share/icons/hicolor/32x32/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_48}/application-x-r-project.png
+ DESTINATION /usr/share/icons/hicolor/48x48/mimetypes)
+ install(FILES ${RSTUDIO_ICONS_256}/application-x-r-project.png
+ DESTINATION /usr/share/icons/hicolor/256x256/mimetypes)
+
+endif()
diff --git a/src/cpp/desktop/DesktopAboutDialog.cpp b/src/cpp/desktop/DesktopAboutDialog.cpp
new file mode 100644
index 0000000..d72c95a
--- /dev/null
+++ b/src/cpp/desktop/DesktopAboutDialog.cpp
@@ -0,0 +1,64 @@
+/*
+ * DesktopAboutDialog.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopAboutDialog.hpp"
+#include "ui_DesktopAboutDialog.h"
+
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+
+#include <QPushButton>
+
+#include "DesktopOptions.hpp"
+#include "desktop-config.h"
+
+using namespace core;
+
+AboutDialog::AboutDialog(QWidget *parent) :
+ QDialog(parent, Qt::Dialog),
+ ui(new Ui::AboutDialog())
+{
+ ui->setupUi(this);
+
+ ui->buttonBox->addButton(new QPushButton(QString::fromAscii("OK")),
+ QDialogButtonBox::AcceptRole);
+ ui->lblIcon->setPixmap(QPixmap(QString::fromAscii(":/icons/resources/freedesktop/icons/64x64/rstudio.png")));
+ ui->lblVersion->setText(QString::fromAscii(
+ "Version " RSTUDIO_VERSION " - © 2009-2012 RStudio, Inc."));
+
+ setWindowModality(Qt::ApplicationModal);
+
+ // read notice file
+ FilePath supportingFilePath = desktop::options().supportingFilePath();
+ FilePath noticePath = supportingFilePath.complete("NOTICE");
+ std::string notice;
+ Error error = readStringFromFile(noticePath, ¬ice);
+ if (!error)
+ {
+ ui->textBrowser->setFontFamily(desktop::options().fixedWidthFont());
+#ifdef Q_WS_MACX
+ ui->textBrowser->setFontPointSize(11);
+#else
+ ui->textBrowser->setFontPointSize(9);
+#endif
+ ui->textBrowser->setText(QString::fromUtf8(notice.c_str()));
+ }
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
diff --git a/src/cpp/desktop/DesktopAboutDialog.hpp b/src/cpp/desktop/DesktopAboutDialog.hpp
new file mode 100644
index 0000000..326cf99
--- /dev/null
+++ b/src/cpp/desktop/DesktopAboutDialog.hpp
@@ -0,0 +1,37 @@
+/*
+ * DesktopAboutDialog.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOPABOUTDIALOG_HPP
+#define DESKTOPABOUTDIALOG_HPP
+
+#include <QDialog>
+
+namespace Ui {
+ class AboutDialog;
+}
+
+class AboutDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AboutDialog(QWidget *parent = 0);
+ ~AboutDialog();
+
+private:
+ Ui::AboutDialog *ui;
+};
+
+#endif // DESKTOPABOUTDIALOG_HPP
diff --git a/src/cpp/desktop/DesktopAboutDialog.ui b/src/cpp/desktop/DesktopAboutDialog.ui
new file mode 100644
index 0000000..efcd7c7
--- /dev/null
+++ b/src/cpp/desktop/DesktopAboutDialog.ui
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>575</width>
+ <height>570</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>About RStudio</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="lblIcon">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap>resources/freedesktop/icons/64x64/rstudio.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <pointsize>16</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>RStudio</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblVersion">
+ <property name="font">
+ <font>
+ <pointsize>11</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Version 1.0.0 - © 2009-2012 RStudio, Inc.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>3</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="font">
+ <font>
+ <pointsize>11</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Lucida Grande';">Unless you have received this program directly from RStudio pursuant to the terms of a commercial license agreement with RStudio, then this program is licensed to you under the terms of version 3 of the GNU </span><a href="http://www.gnu.org/licenses/agpl-3.0.txt"><span s [...]
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>12</width>
+ <height>12</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="openLinks">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::NoButton</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/cpp/desktop/DesktopApplicationLaunch.hpp b/src/cpp/desktop/DesktopApplicationLaunch.hpp
new file mode 100644
index 0000000..8c1bdd3
--- /dev/null
+++ b/src/cpp/desktop/DesktopApplicationLaunch.hpp
@@ -0,0 +1,62 @@
+/*
+ * DesktopApplicationLaunch.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOPAPPLICATIONLAUNCH_HPP
+#define DESKTOPAPPLICATIONLAUNCH_HPP
+
+#include <QObject>
+#include <QWidget>
+#include <QApplication>
+#include <boost/scoped_ptr.hpp>
+
+namespace desktop {
+
+class ApplicationLaunch : public QWidget
+{
+ Q_OBJECT
+public:
+ static void init(QString appname,
+ int& argc,
+ char* argv[],
+ boost::scoped_ptr<QApplication>* ppApp,
+ boost::scoped_ptr<ApplicationLaunch>* ppAppLaunch);
+
+ void setActivationWindow(QWidget* pWindow);
+
+ void activateWindow();
+
+ void attemptToRegisterPeer();
+
+ QString startupOpenFileRequest() const;
+
+protected:
+ explicit ApplicationLaunch();
+#ifdef _WIN32
+ bool winEvent(MSG *message, long *result);
+#endif
+
+signals:
+ void openFileRequest(QString filename);
+
+public slots:
+ bool sendMessage(QString filename);
+
+private:
+ QWidget* pMainWindow_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOPAPPLICATIONLAUNCH_HPP
diff --git a/src/cpp/desktop/DesktopBrowserWindow.cpp b/src/cpp/desktop/DesktopBrowserWindow.cpp
new file mode 100644
index 0000000..c367c60
--- /dev/null
+++ b/src/cpp/desktop/DesktopBrowserWindow.cpp
@@ -0,0 +1,133 @@
+/*
+ * DesktopBrowserWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopBrowserWindow.hpp"
+#include <QWebFrame>
+#include "DesktopWebView.hpp"
+
+#include "DesktopUtils.hpp"
+#include "DesktopOptions.hpp"
+
+namespace desktop {
+
+BrowserWindow::BrowserWindow(bool showToolbar,
+ bool adjustTitle,
+ QUrl baseUrl,
+ QWidget* pParent) :
+ QMainWindow(pParent)
+{
+ adjustTitle_ = adjustTitle;
+ progress_ = 0;
+
+ pView_ = new WebView(baseUrl, this);
+ QWebFrame* mainFrame = pView_->page()->mainFrame();
+ connect(mainFrame, SIGNAL(javaScriptWindowObjectCleared()),
+ this, SLOT(onJavaScriptWindowObjectCleared()));
+ connect(pView_, SIGNAL(titleChanged(QString)), SLOT(adjustTitle()));
+ connect(pView_, SIGNAL(loadProgress(int)), SLOT(setProgress(int)));
+ connect(pView_, SIGNAL(loadFinished(bool)), SLOT(finishLoading(bool)));
+ connect(pView_->page(), SIGNAL(printRequested(QWebFrame*)),
+ this, SLOT(printRequested(QWebFrame*)));
+
+ // set zoom factor
+ double zoomLevel = options().zoomLevel();
+ if (zoomLevel != pView_->dpiAwareZoomFactor())
+ pView_->setDpiAwareZoomFactor(options().zoomLevel());
+
+ // Kind of a hack to new up a toolbar and not attach it to anything.
+ // Once it is clear what secondary browser windows look like we can
+ // decide whether to keep this.
+ pToolbar_ = showToolbar ? addToolBar(tr("Navigation")) : new QToolBar();
+ pToolbar_->setMovable(false);
+
+ setCentralWidget(pView_);
+ setUnifiedTitleAndToolBarOnMac(true);
+
+ desktop::enableFullscreenMode(this, false);
+
+ QShortcut* copyShortcut = new QShortcut(QKeySequence::Copy, this);
+ connect(copyShortcut, SIGNAL(activated()),
+ pView_->pageAction(QWebPage::Copy), SLOT(trigger()));
+}
+
+void BrowserWindow::printRequested(QWebFrame* frame)
+{
+ QPrintPreviewDialog dialog(window());
+ QSize size = printDialogMinimumSize();
+ if (!size.isNull())
+ dialog.setMinimumSize(size);
+ dialog.setWindowModality(Qt::WindowModal);
+ connect(&dialog, SIGNAL(paintRequested(QPrinter*)),
+ frame, SLOT(print(QPrinter*)));
+ dialog.exec();
+}
+
+void BrowserWindow::onCloseRequested()
+{
+ close();
+}
+
+void BrowserWindow::adjustTitle()
+{
+ if (adjustTitle_)
+ setWindowTitle(pView_->title());
+}
+
+void BrowserWindow::setProgress(int p)
+{
+ progress_ = p;
+ adjustTitle();
+}
+
+void BrowserWindow::finishLoading(bool)
+{
+ progress_ = 100;
+ adjustTitle();
+}
+
+WebView* BrowserWindow::webView()
+{
+ return pView_;
+}
+
+void BrowserWindow::avoidMoveCursorIfNecessary()
+{
+#ifdef Q_WS_MACX
+ webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("document.body.className = document.body.className + ' avoid-move-cursor'"));
+#endif
+}
+
+QWidget* BrowserWindow::asWidget()
+{
+ return this;
+}
+
+WebPage* BrowserWindow::webPage()
+{
+ return webView()->webPage();
+}
+
+void BrowserWindow::postWebViewEvent(QEvent *keyEvent)
+{
+ QCoreApplication::postEvent(webView(), keyEvent);
+}
+
+void BrowserWindow::triggerPageAction(QWebPage::WebAction action)
+{
+ webView()->triggerPageAction(action);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopBrowserWindow.hpp b/src/cpp/desktop/DesktopBrowserWindow.hpp
new file mode 100644
index 0000000..d91e9bf
--- /dev/null
+++ b/src/cpp/desktop/DesktopBrowserWindow.hpp
@@ -0,0 +1,75 @@
+/*
+ * DesktopBrowserWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_BROWSER_WINDOW_HPP
+#define DESKTOP_BROWSER_WINDOW_HPP
+
+#include <QAction>
+#include <QMainWindow>
+#include <QWebView>
+#include <QLineEdit>
+
+#include "DesktopWebView.hpp"
+#include "DesktopGwtCallbackOwner.hpp"
+
+namespace desktop {
+
+class BrowserWindow : public QMainWindow, public GwtCallbackOwner
+{
+ Q_OBJECT
+public:
+ explicit BrowserWindow(bool showToolbar,
+ bool adjustTitle,
+ QUrl baseUrl = QUrl(),
+ QWidget *parent = NULL);
+ WebView* webView();
+
+protected slots:
+
+ void adjustTitle();
+ void setProgress(int p);
+ virtual void finishLoading(bool);
+ virtual void onJavaScriptWindowObjectCleared() {}
+ void printRequested(QWebFrame* frame);
+
+ void onCloseRequested();
+
+protected:
+ void avoidMoveCursorIfNecessary();
+
+ // implement GwtCallbackOwner
+ virtual QWidget* asWidget();
+ virtual WebPage* webPage();
+ virtual void postWebViewEvent(QEvent *event);
+ virtual void triggerPageAction(QWebPage::WebAction action);
+
+ // hooks for subclasses
+ virtual QSize printDialogMinimumSize()
+ {
+ return QSize(0,0);
+ }
+
+protected:
+ WebView* pView_;
+ QToolBar* pToolbar_;
+
+private:
+ int progress_;
+ bool adjustTitle_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_BROWSER_WINDOW_HPP
diff --git a/src/cpp/desktop/DesktopChooseRHome.cpp b/src/cpp/desktop/DesktopChooseRHome.cpp
new file mode 100644
index 0000000..a25e422
--- /dev/null
+++ b/src/cpp/desktop/DesktopChooseRHome.cpp
@@ -0,0 +1,303 @@
+/*
+ * DesktopChooseRHome.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "DesktopChooseRHome.hpp"
+#include "ui_DesktopChooseRHome.h"
+
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QListWidgetItem>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include "DesktopDetectRHome.hpp"
+#include "DesktopRVersion.hpp"
+#include "DesktopUtils.hpp"
+
+using namespace desktop;
+
+namespace {
+
+QListWidgetItem* toItem(const RVersion& version)
+{
+ QListWidgetItem* pItem = new QListWidgetItem();
+ pItem->setText(version.description());
+ pItem->setData(Qt::UserRole, version.binDir());
+ return pItem;
+}
+
+RVersion toVersion(QListWidgetItem* pItem)
+{
+ if (!pItem)
+ return RVersion();
+ return RVersion(pItem->data(Qt::UserRole).toString());
+}
+
+} // anonymous namespace
+
+ChooseRHome::ChooseRHome(QList<RVersion> list, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ChooseRHome()),
+ pOK_(NULL)
+{
+ ui->setupUi(this);
+ if (!core::system::isWin64())
+ ui->radioDefault64->setVisible(false);
+
+ setWindowIcon(QIcon(QString::fromAscii(":/icons/RStudio.ico")));
+
+ setWindowFlags(
+ (windowFlags() | Qt::Dialog)
+ & ~Qt::WindowContextHelpButtonHint
+ );
+
+ pOK_ = new QPushButton(QString::fromUtf8("OK"));
+ ui->buttonBox->addButton(pOK_, QDialogButtonBox::AcceptRole);
+
+ QPushButton* pCancel = new QPushButton(QString::fromUtf8("Cancel"));
+ ui->buttonBox->addButton(pCancel, QDialogButtonBox::RejectRole);
+
+ for (int i = 0; i < list.size(); i++)
+ {
+ ui->listHomeDir->addItem(toItem(list.at(i)));
+ }
+
+ connect(ui->btnOther, SIGNAL(clicked()),
+ this, SLOT(chooseOther()));
+ connect(ui->listHomeDir, SIGNAL(itemSelectionChanged()),
+ this, SLOT(validateSelection()));
+ validateSelection();
+
+ ui->radioDefault->setChecked(true);
+ connect(ui->radioDefault, SIGNAL(toggled(bool)),
+ this, SLOT(onModeChanged()));
+ connect(ui->radioDefault64, SIGNAL(toggled(bool)),
+ this, SLOT(onModeChanged()));
+ onModeChanged();
+}
+
+ChooseRHome::~ChooseRHome()
+{
+ delete ui;
+}
+
+void ChooseRHome::chooseOther()
+{
+ if (lastDir_.isEmpty())
+ {
+ lastDir_ = QString::fromLocal8Bit(core::system::getenv("ProgramFiles").c_str());
+ }
+
+ QString dir = QFileDialog::getExistingDirectory(
+ this,
+ QString::fromUtf8("Choose R Directory"),
+ lastDir_,
+ QFileDialog::ShowDirsOnly | standardFileDialogOptions());
+
+ if (dir.isEmpty())
+ return;
+
+ lastDir_ = dir;
+
+ QList<RVersion> versions = detectVersionsInDir(dir);
+
+ RVersion rVer;
+
+ if (versions.size() == 0)
+ {
+ showWarning(
+ this,
+ QString::fromUtf8("Invalid R Directory"),
+ QString::fromUtf8("This directory does not appear to contain a "
+ "valid R installation.\n\nPlease try again."));
+ return;
+ }
+ else if (versions.size() > 1)
+ {
+ QStringList items;
+ for (int i = 0; i < versions.size(); i++)
+ items << versions.at(i).description();
+
+ QInputDialog inputDialog(this);
+ inputDialog.setOptions(QInputDialog::UseListViewForComboBoxItems);
+ inputDialog.setComboBoxItems(items);
+ inputDialog.setComboBoxEditable(false);
+ inputDialog.setWindowTitle(QString::fromUtf8("Choose Version"));
+ inputDialog.setLabelText(QString::fromUtf8("Please choose the version to use:"));
+
+ if (inputDialog.exec() != QDialog::Accepted)
+ return;
+
+ int idx = items.indexOf(QRegExp(inputDialog.textValue(),
+ Qt::CaseSensitive,
+ QRegExp::FixedString));
+ if (idx < 0)
+ return;
+ rVer = versions.at(idx);
+ }
+ else // versions.size() == 1
+ {
+ rVer = versions.at(0);
+ }
+
+ switch (rVer.validate())
+ {
+ case desktop::ValidateSuccess:
+ break;
+ case desktop::ValidateNotFound:
+ showWarning(
+ this,
+ QString::fromUtf8("Invalid R Directory"),
+ QString::fromUtf8("This directory does not appear to contain a "
+ "valid R installation.\n\nPlease try again."));
+ return;
+ case desktop::ValidateBadArchitecture:
+ showWarning(
+ this,
+ QString::fromUtf8("Incompatible R Build"),
+ QString::fromUtf8("The version of R you've selected was built "
+ "for a different CPU architecture and cannot "
+ "be used with this version of RStudio."));
+ return;
+ case desktop::ValidateVersionTooOld:
+ default:
+ showWarning(
+ this,
+ QString::fromUtf8("Incompatible R Build"),
+ QString::fromUtf8("The version of R you've selected is not "
+ "compatible with RStudio. Please install a "
+ "newer version of R."));
+ return;
+ }
+
+ QList<QListWidgetItem*> items = ui->listHomeDir->findItems(
+ rVer.description(), Qt::MatchExactly);
+ if (!items.isEmpty())
+ {
+ ui->listHomeDir->setCurrentItem(items[0]);
+ }
+ else
+ {
+ QListWidgetItem* pItem = toItem(rVer);
+ ui->listHomeDir->addItem(pItem);
+ pItem->setSelected(true);
+ }
+}
+
+void ChooseRHome::done(int r)
+{
+ if (r == QDialog::Accepted)
+ {
+ if (!ui->radioCustom->isChecked())
+ {
+ Architecture arch = preferR64() ? ArchX64 : ArchX86;
+ if (desktop::autoDetect(arch).isEmpty())
+ {
+ if (desktop::allRVersions().length() > 0)
+ {
+ QString name = QString::fromUtf8(preferR64() ? "R64" : "R");
+
+ showWarning(
+ this,
+ QString::fromUtf8("No %1 Installation Detected").arg(name),
+ QString::fromUtf8("No compatible %1 version was found. If you "
+ "have a compatible version of %1 installed, "
+ "please choose it manually."
+ ).arg(name)
+ );
+ ui->radioCustom->setChecked(true);
+ return;
+ }
+ else
+ {
+ if (showYesNoDialog(
+ QMessageBox::Warning,
+ this,
+ QString::fromUtf8("R Not Installed"),
+ QString::fromUtf8("R does not appear to be installed. Please "
+ "install R before using RStudio.\n\n"
+ "You can download R from the official R Project "
+ "website. Would you like to go there now?")))
+ {
+ desktop::openUrl(QUrl(QString::fromAscii("http://www.rstudio.org/links/r-project")));
+ }
+ }
+ }
+ }
+ }
+
+ this->QDialog::done(r);
+}
+
+void ChooseRHome::validateSelection()
+{
+ ui->listHomeDir->setEnabled(ui->radioCustom->isChecked());
+ ui->btnOther->setEnabled(ui->radioCustom->isChecked());
+
+ if (!ui->radioCustom->isChecked())
+ ui->listHomeDir->setCurrentRow(-1);
+
+ if (ui->radioCustom->isChecked())
+ {
+ pOK_->setEnabled(this->value().isValid());
+ }
+ else
+ {
+ pOK_->setEnabled(true);
+ }
+}
+
+void ChooseRHome::onModeChanged()
+{
+ validateSelection();
+}
+
+RVersion ChooseRHome::value()
+{
+ if (!ui->radioCustom->isChecked())
+ return QString();
+
+ QList<QListWidgetItem*> selectedItems
+ = ui->listHomeDir->selectedItems();
+ return selectedItems.isEmpty()
+ ? RVersion()
+ : toVersion(selectedItems.at(0));
+}
+
+void ChooseRHome::setValue(const RVersion& value, bool preferR64)
+{
+ if (value.isEmpty())
+ {
+ if (preferR64)
+ ui->radioDefault64->setChecked(true);
+ else
+ ui->radioDefault->setChecked(true);
+ }
+ else
+ {
+ ui->radioCustom->setChecked(true);
+ QList<QListWidgetItem*> matches =
+ ui->listHomeDir->findItems(value.description(), Qt::MatchExactly);
+ if (matches.size() > 0)
+ matches.first()->setSelected(true);
+ ui->listHomeDir->setFocus();
+ }
+}
+
+bool ChooseRHome::preferR64()
+{
+ return ui->radioDefault64->isChecked();
+}
diff --git a/src/cpp/desktop/DesktopChooseRHome.hpp b/src/cpp/desktop/DesktopChooseRHome.hpp
new file mode 100644
index 0000000..3864146
--- /dev/null
+++ b/src/cpp/desktop/DesktopChooseRHome.hpp
@@ -0,0 +1,56 @@
+/*
+ * DesktopChooseRHome.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef DESKTOPCHOOSERHOME_HPP
+#define DESKTOPCHOOSERHOME_HPP
+
+#include <QDialog>
+#include <QStringListModel>
+#include <QCloseEvent>
+
+#include "DesktopRVersion.hpp"
+#include "DesktopOptions.hpp"
+
+namespace Ui {
+ class ChooseRHome;
+}
+
+class ChooseRHome : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit ChooseRHome(QList<desktop::RVersion> list, QWidget *parent = 0);
+ ~ChooseRHome();
+
+ // "" means auto-detect
+ desktop::RVersion value();
+ bool preferR64();
+ void setValue(const desktop::RVersion& value, bool preferR64);
+
+protected slots:
+ void chooseOther();
+ void validateSelection();
+ void onModeChanged();
+
+protected:
+ void done(int r);
+
+private:
+ Ui::ChooseRHome *ui;
+ QPushButton* pOK_;
+ QString lastDir_;
+};
+
+#endif // DESKTOPCHOOSERHOME_HPP
diff --git a/src/cpp/desktop/DesktopChooseRHome.ui b/src/cpp/desktop/DesktopChooseRHome.ui
new file mode 100644
index 0000000..277f1a9
--- /dev/null
+++ b/src/cpp/desktop/DesktopChooseRHome.ui
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChooseRHome</class>
+ <widget class="QDialog" name="ChooseRHome">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>391</width>
+ <height>320</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Choose R Installation</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>RStudio requires an existing installation of R in order to work. Please select the version of R to use.</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioDefault64">
+ <property name="text">
+ <string>Use your machine's default version of R64 (64-bit)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioDefault">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Use your machine's default version of R (32-bit)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioCustom">
+ <property name="text">
+ <string>Choose a specific version of R:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>22</number>
+ </property>
+ <item>
+ <widget class="QListWidget" name="listHomeDir">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>350</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnOther">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>&Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::NoButton</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ChooseRHome</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ChooseRHome</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/cpp/desktop/DesktopCommandInvoker.cpp b/src/cpp/desktop/DesktopCommandInvoker.cpp
new file mode 100644
index 0000000..d37272c
--- /dev/null
+++ b/src/cpp/desktop/DesktopCommandInvoker.cpp
@@ -0,0 +1,30 @@
+/*
+ * DesktopCommandInvoker.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopCommandInvoker.hpp"
+
+namespace desktop {
+
+CommandInvoker::CommandInvoker(QString commandId, QObject *parent) :
+ QObject(parent), commandId_(commandId)
+{
+}
+
+void CommandInvoker::invoke()
+{
+ commandInvoked(commandId_);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopCommandInvoker.hpp b/src/cpp/desktop/DesktopCommandInvoker.hpp
new file mode 100644
index 0000000..8770c71
--- /dev/null
+++ b/src/cpp/desktop/DesktopCommandInvoker.hpp
@@ -0,0 +1,41 @@
+/*
+ * DesktopCommandInvoker.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_COMMAND_INVOKER_HPP
+#define DESKTOP_COMMAND_INVOKER_HPP
+
+#include <QObject>
+
+namespace desktop {
+
+class CommandInvoker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit CommandInvoker(QString commandId, QObject *parent = 0);
+
+signals:
+ void commandInvoked(QString commandId);
+
+public slots:
+ void invoke();
+
+private:
+ QString commandId_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_COMMAND_INVOKER_HPP
diff --git a/src/cpp/desktop/DesktopDataUriNetworkReply.cpp b/src/cpp/desktop/DesktopDataUriNetworkReply.cpp
new file mode 100644
index 0000000..c9690ef
--- /dev/null
+++ b/src/cpp/desktop/DesktopDataUriNetworkReply.cpp
@@ -0,0 +1,91 @@
+/*
+ * DesktopDataUriNetworkReply.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopDataUriNetworkReply.hpp"
+
+#include <QtGlobal>
+#include <QTimer>
+#include <QDebug>
+
+#include "DesktopUtils.hpp"
+
+namespace desktop {
+
+// NOTE: this does not yet actually work. PNGs served back through this
+// class end up unreadable by the client (but it appears as if at least
+// one GIF works). Note we actually tried returning just raw bytes
+// without the headers and it worked. We should therefore do more
+// research on what the expected behavior for data uris
+//
+// NOTE: if we get this work we still need to do some work around:
+//
+// (1) Optimizing/eliminating data copies
+// (2) Return error conditions for malformed data
+// (3) Other assorted cleanup
+//
+
+void DataUriNetworkReply::createReply()
+{
+ // get the url as a byte array
+ QByteArray urlBytes = url().toEncoded(QUrl::None);
+
+ // get the delimiter locations
+ int colonLoc = urlBytes.indexOf(':');
+ int semicolonLoc = urlBytes.indexOf(';', colonLoc);
+ int commaLoc = urlBytes.indexOf(',', semicolonLoc);
+
+ // get the content type
+ QByteArray contentTypeBytes = urlBytes.mid(colonLoc + 1,
+ semicolonLoc - colonLoc - 1);
+ QString contentType = QString::fromAscii(contentTypeBytes.data(),
+ contentTypeBytes.size());
+ setHeader(QNetworkRequest::ContentTypeHeader, contentType);
+
+ // get the base64 encoded data
+ QByteArray base64Content = urlBytes.right(urlBytes.size() - commaLoc - 1);
+ content_ = QByteArray::fromBase64(base64Content);
+ offset_ = 0;
+
+ // set status OK
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute,
+ QString::fromAscii("OK"));
+
+ open(ReadOnly | Unbuffered);
+ setHeader(QNetworkRequest::ContentLengthHeader, QVariant(content_.size()));
+
+ QTimer::singleShot(0, this, SIGNAL(readyRead()) );
+ QTimer::singleShot(0, this, SIGNAL(finished()) );
+}
+
+qint64 DataUriNetworkReply::bytesAvailable() const
+{
+ return content_.size() - offset_;
+}
+
+qint64 DataUriNetworkReply::readData(char *data, qint64 maxSize)
+{
+ if (offset_ >= content_.size())
+ return -1;
+
+ qint64 bytes = qMin(maxSize, (qint64)(content_.size() - offset_));
+ ::memcpy(data, content_.constData() + offset_, bytes);
+ offset_ += bytes;
+
+ return bytes;
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopDataUriNetworkReply.hpp b/src/cpp/desktop/DesktopDataUriNetworkReply.hpp
new file mode 100644
index 0000000..91df075
--- /dev/null
+++ b/src/cpp/desktop/DesktopDataUriNetworkReply.hpp
@@ -0,0 +1,67 @@
+/*
+ * DesktopDataUriNetworkReply.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_DATA_URI_NETWORK_REPLY_HPP
+#define DESKTOP_DATA_URI_NETWORK_REPLY_HPP
+
+#include <QNetworkReply>
+#include <QNetworkRequest>
+
+namespace desktop {
+
+class DataUriNetworkReply : public QNetworkReply
+{
+ Q_OBJECT
+
+public:
+ DataUriNetworkReply(QObject* parent, const QNetworkRequest &req)
+ : QNetworkReply(parent), offset_(0)
+ {
+ setRequest(req);
+ setUrl(req.url());
+ setOperation(QNetworkAccessManager::GetOperation);
+ }
+
+public:
+ void createReply();
+
+public:
+ void abort()
+ {
+ close();
+ }
+
+ qint64 bytesAvailable() const;
+
+ bool isSequential() const
+ {
+ return true;
+ }
+
+protected:
+ qint64 readData(char *data, qint64 maxSize);
+
+private:
+ void setContent(const QByteArray& content);
+
+private:
+ QByteArray content_;
+ int offset_;
+};
+
+
+} // namespace desktop
+
+#endif // DESKTOP_DATA_URI_NETWORK_REPLY_HPP
diff --git a/src/cpp/desktop/DesktopDetectRHome.hpp b/src/cpp/desktop/DesktopDetectRHome.hpp
new file mode 100644
index 0000000..a282251
--- /dev/null
+++ b/src/cpp/desktop/DesktopDetectRHome.hpp
@@ -0,0 +1,31 @@
+/*
+ * DesktopDetectRHome.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef DESKTOP_DETECT_R_HOME_HPP
+#define DESKTOP_DETECT_R_HOME_HPP
+
+#include <QString>
+#include <QSettings>
+
+#include "DesktopOptions.hpp"
+#include "DesktopRVersion.hpp"
+
+namespace desktop {
+
+bool prepareEnvironment(Options& settings);
+
+} // namespace desktop
+
+
+#endif // DESKTOP_DETECT_R_HOME_HPP
diff --git a/src/cpp/desktop/DesktopDownloadHelper.cpp b/src/cpp/desktop/DesktopDownloadHelper.cpp
new file mode 100644
index 0000000..a4a39d9
--- /dev/null
+++ b/src/cpp/desktop/DesktopDownloadHelper.cpp
@@ -0,0 +1,90 @@
+/*
+ * DesktopDownloadHelper.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopDownloadHelper.hpp"
+#include <QFile>
+#include <core/Log.hpp>
+
+#include "DesktopUtils.hpp"
+
+namespace desktop {
+
+namespace {
+
+bool handleReplyError(QNetworkReply* pReply)
+{
+ if (pReply->error() != QNetworkReply::NoError)
+ {
+ showWarning(NULL,
+ QString::fromUtf8("Download Failed"),
+ QString::fromUtf8("An error occurred during download:\n\n")
+ + pReply->errorString());
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void writeReply(QNetworkReply* pReply, QString fileName)
+{
+ QFile file(fileName);
+ if (file.open(QFile::ReadWrite))
+ {
+ file.write(pReply->readAll());
+ file.close();
+ }
+}
+
+} // anonymous namespace
+
+DownloadHelper::DownloadHelper(QNetworkReply* pReply,
+ QString fileName) :
+ QObject(pReply),
+ fileName_(fileName)
+{
+ connect(pReply, SIGNAL(finished()), this, SLOT(onDownloadFinished()));
+}
+
+void DownloadHelper::handleDownload(QNetworkReply* pReply, QString fileName)
+{
+ if (!handleReplyError(pReply))
+ return;
+
+ writeReply(pReply, fileName);
+
+ pReply->close();
+ pReply->deleteLater();
+}
+
+
+void DownloadHelper::onDownloadFinished()
+{
+ QNetworkReply* pReply = static_cast<QNetworkReply*>(sender());
+
+ if (!handleReplyError(pReply))
+ return;
+
+ writeReply(pReply, fileName_);
+
+ downloadFinished(fileName_);
+
+ pReply->close();
+ pReply->deleteLater();
+ deleteLater();
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopDownloadHelper.hpp b/src/cpp/desktop/DesktopDownloadHelper.hpp
new file mode 100644
index 0000000..90d8b8e
--- /dev/null
+++ b/src/cpp/desktop/DesktopDownloadHelper.hpp
@@ -0,0 +1,46 @@
+/*
+ * DesktopDownloadHelper.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_DOWNLOAD_HELPER_HPP
+#define DESKTOP_DOWNLOAD_HELPER_HPP
+
+#include <QObject>
+#include <QNetworkReply>
+
+namespace desktop {
+
+// DownloadHelper is self-freeing
+class DownloadHelper : public QObject
+{
+ Q_OBJECT
+public:
+ DownloadHelper(QNetworkReply* pReply,
+ QString fileName);
+
+ static void handleDownload(QNetworkReply* pReply, QString fileName);
+
+protected slots:
+ void onDownloadFinished();
+
+signals:
+ void downloadFinished(QString fileName);
+
+private:
+ QString fileName_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_DOWNLOAD_HELPER_HPP
diff --git a/src/cpp/desktop/DesktopGwtCallback.cpp b/src/cpp/desktop/DesktopGwtCallback.cpp
new file mode 100644
index 0000000..24edfc1
--- /dev/null
+++ b/src/cpp/desktop/DesktopGwtCallback.cpp
@@ -0,0 +1,977 @@
+/*
+ * DesktopGwtCallback.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopGwtCallback.hpp"
+
+#include <algorithm>
+
+#ifdef _WIN32
+#include <shlobj.h>
+#endif
+
+#include <boost/foreach.hpp>
+
+#include <QtGui/QFileDialog>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include <core/FilePath.hpp>
+#include <core/DateTime.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include "DesktopAboutDialog.hpp"
+#include "DesktopOptions.hpp"
+#include "DesktopBrowserWindow.hpp"
+#include "DesktopWindowTracker.hpp"
+#include "DesktopInputDialog.hpp"
+#include "DesktopSecondaryWindow.hpp"
+#include "DesktopRVersion.hpp"
+#include "DesktopMainWindow.hpp"
+#include "DesktopUtils.hpp"
+#include "DesktopSynctex.hpp"
+
+#ifdef __APPLE__
+#include <Carbon/Carbon.h>
+#endif
+
+using namespace core;
+
+namespace desktop {
+
+extern QString scratchPath;
+
+GwtCallback::GwtCallback(MainWindow* pMainWindow, GwtCallbackOwner* pOwner)
+ : pMainWindow_(pMainWindow),
+ pOwner_(pOwner),
+ pSynctex_(NULL),
+ pendingQuit_(PendingQuitNone)
+{
+}
+
+Synctex& GwtCallback::synctex()
+{
+ if (pSynctex_ == NULL)
+ pSynctex_ = Synctex::create(pMainWindow_);
+
+ return *pSynctex_;
+}
+
+bool GwtCallback::isCocoa()
+{
+ return false;
+}
+
+void GwtCallback::browseUrl(QString url)
+{
+ QUrl qurl = QUrl::fromEncoded(url.toAscii());
+
+#ifdef Q_WS_MAC
+ if (qurl.scheme() == QString::fromAscii("file"))
+ {
+ QProcess open;
+ QStringList args;
+ // force use of Preview for PDFs (Adobe Reader 10.01 crashes)
+ if (url.toLower().endsWith(QString::fromAscii(".pdf")))
+ {
+ args.append(QString::fromAscii("-a"));
+ args.append(QString::fromAscii("Preview"));
+ args.append(url);
+ }
+ else
+ {
+ args.append(url);
+ }
+ open.start(QString::fromAscii("open"), args);
+ open.waitForFinished(5000);
+ if (open.exitCode() != 0)
+ {
+ // Probably means that the file doesn't have a registered
+ // application or something.
+ QProcess reveal;
+ reveal.startDetached(QString::fromAscii("open"), QStringList() << QString::fromAscii("-R") << url);
+ }
+ return;
+ }
+#endif
+
+ desktop::openUrl(qurl);
+}
+
+namespace {
+
+FilePath userHomePath()
+{
+ return core::system::userHomePath("R_USER|HOME");
+}
+
+QString createAliasedPath(const QString& path)
+{
+ std::string aliased = FilePath::createAliasedPath(
+ FilePath(path.toUtf8().constData()), userHomePath());
+ return QString::fromUtf8(aliased.c_str());
+}
+
+QString resolveAliasedPath(const QString& path)
+{
+ FilePath resolved(FilePath::resolveAliasedPath(path.toUtf8().constData(),
+ userHomePath()));
+ return QString::fromUtf8(resolved.absolutePath().c_str());
+}
+
+} // anonymous namespace
+
+QString GwtCallback::getOpenFileName(const QString& caption,
+ const QString& dir,
+ const QString& filter)
+{
+ QString resolvedDir = resolveAliasedPath(dir);
+ QString result = QFileDialog::getOpenFileName(pOwner_->asWidget(),
+ caption,
+ resolvedDir,
+ filter,
+ 0,
+ standardFileDialogOptions());
+
+ activateAndFocusOwner();
+ return createAliasedPath(result);
+}
+
+QString GwtCallback::getSaveFileName(const QString& caption,
+ const QString& dir,
+ const QString& defaultExtension,
+ bool forceDefaultExtension)
+{
+ QString resolvedDir = resolveAliasedPath(dir);
+
+ while (true)
+ {
+ QString result = QFileDialog::getSaveFileName(pOwner_->asWidget(), caption, resolvedDir,
+ QString(), 0, standardFileDialogOptions());
+ activateAndFocusOwner();
+ if (result.isEmpty())
+ return result;
+
+ if (!defaultExtension.isEmpty())
+ {
+ FilePath fp(result.toUtf8().constData());
+ if (fp.extension().empty() ||
+ (forceDefaultExtension &&
+ (fp.extension() != defaultExtension.toStdString())))
+ {
+ result += defaultExtension;
+ FilePath newExtPath(result.toUtf8().constData());
+ if (newExtPath.exists())
+ {
+ std::string message = "\"" + newExtPath.filename() + "\" already "
+ "exists. Do you want to overwrite it?";
+ if (QMessageBox::Cancel == QMessageBox::warning(
+ pOwner_->asWidget(),
+ QString::fromUtf8("Save File"),
+ QString::fromUtf8(message.c_str()),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Ok))
+ {
+ resolvedDir = result;
+ continue;
+ }
+ }
+ }
+ }
+
+ return createAliasedPath(result);
+ }
+}
+
+QString GwtCallback::getExistingDirectory(const QString& caption,
+ const QString& dir)
+{
+ QString resolvedDir = resolveAliasedPath(dir);
+
+ QString result;
+#ifdef _WIN32
+ if (dir.isNull())
+ {
+ // Bug
+ wchar_t szDir[MAX_PATH];
+ BROWSEINFOW bi;
+ bi.hwndOwner = pOwner_->asWidget()->winId();
+ bi.pidlRoot = NULL;
+ bi.pszDisplayName = szDir;
+ bi.lpszTitle = L"Select a folder:";
+ bi.ulFlags = BIF_RETURNONLYFSDIRS;
+ bi.lpfn = NULL;
+ bi.lpfn = 0;
+ bi.iImage = -1;
+ LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
+ if (!pidl || !SHGetPathFromIDListW(pidl, szDir))
+ result = QString();
+ else
+ result = QString::fromWCharArray(szDir);
+ }
+ else
+ {
+ result = QFileDialog::getExistingDirectory(pOwner_->asWidget(), caption, resolvedDir,
+ QFileDialog::ShowDirsOnly | standardFileDialogOptions());
+ }
+#else
+ result = QFileDialog::getExistingDirectory(pOwner_->asWidget(), caption, resolvedDir,
+ QFileDialog::ShowDirsOnly | standardFileDialogOptions());
+#endif
+
+ activateAndFocusOwner();
+ return createAliasedPath(result);
+}
+
+void GwtCallback::doAction(QKeySequence::StandardKey key)
+{
+ QList<QKeySequence> bindings = QKeySequence::keyBindings(key);
+ if (bindings.size() == 0)
+ return;
+
+ QKeySequence seq = bindings.first();
+
+ int keyCode = seq[0];
+ Qt::KeyboardModifier modifiers = static_cast<Qt::KeyboardModifier>(keyCode & Qt::KeyboardModifierMask);
+ keyCode &= ~Qt::KeyboardModifierMask;
+
+ QKeyEvent* keyEvent = new QKeyEvent(QKeyEvent::KeyPress, keyCode, modifiers);
+ pOwner_->postWebViewEvent(keyEvent);
+}
+
+void GwtCallback::undo()
+{
+ doAction(QKeySequence::Undo);
+}
+
+void GwtCallback::redo()
+{
+ doAction(QKeySequence::Redo);
+}
+
+void GwtCallback::clipboardCut()
+{
+ doAction(QKeySequence::Cut);
+}
+
+void GwtCallback::clipboardCopy()
+{
+ doAction(QKeySequence::Copy);
+}
+
+void GwtCallback::clipboardPaste()
+{
+ doAction(QKeySequence::Paste);
+}
+
+QString GwtCallback::proportionalFont()
+{
+ return options().proportionalFont();
+}
+
+QString GwtCallback::fixedWidthFont()
+{
+ return options().fixedWidthFont();
+}
+
+QString GwtCallback::getUriForPath(QString path)
+{
+ return QUrl::fromLocalFile(resolveAliasedPath(path)).toString();
+}
+
+void GwtCallback::onWorkbenchInitialized(QString scratchPath)
+{
+ workbenchInitialized();
+ desktop::scratchPath = scratchPath;
+}
+
+void GwtCallback::showFolder(QString path)
+{
+ if (path.isNull() || path.isEmpty())
+ return;
+
+ path = resolveAliasedPath(path);
+
+ QDir dir(path);
+ if (dir.exists())
+ {
+ desktop::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
+ }
+}
+
+void GwtCallback::showFile(QString path)
+{
+ if (path.isNull() || path.isEmpty())
+ return;
+
+ path = resolveAliasedPath(path);
+
+ desktop::openUrl(QUrl::fromLocalFile(path));
+}
+
+
+QString GwtCallback::getRVersion()
+{
+#ifdef Q_OS_WIN32
+ bool defaulted = options().rBinDir().isEmpty();
+ QString rDesc = defaulted
+ ? QString::fromUtf8("[Default] ") + autoDetect().description()
+ : RVersion(options().rBinDir()).description();
+ return rDesc;
+#else
+ return QString();
+#endif
+}
+
+QString GwtCallback::chooseRVersion()
+{
+#ifdef Q_OS_WIN32
+ RVersion rVersion = desktop::detectRVersion(true, pOwner_->asWidget());
+ if (rVersion.isValid())
+ return getRVersion();
+ else
+ return QString();
+#else
+ return QString();
+#endif
+}
+
+bool GwtCallback::canChooseRVersion()
+{
+#ifdef Q_OS_WIN32
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool GwtCallback::isRetina()
+{
+ return desktop::isRetina(pMainWindow_);
+}
+
+void GwtCallback::openMinimalWindow(QString name,
+ QString url,
+ int width,
+ int height)
+{
+ static WindowTracker windowTracker;
+
+ bool named = !name.isEmpty() && name != QString::fromAscii("_blank");
+
+ BrowserWindow* browser = NULL;
+ if (named)
+ browser = windowTracker.getWindow(name);
+
+ if (!browser)
+ {
+ browser = new BrowserWindow(false, true);
+ browser->setAttribute(Qt::WA_DeleteOnClose);
+ browser->setAttribute(Qt::WA_QuitOnClose, false);
+ browser->connect(browser->webView(), SIGNAL(onCloseWindowShortcut()),
+ browser, SLOT(onCloseRequested()));
+ if (named)
+ windowTracker.addWindow(name, browser);
+ }
+
+ browser->webView()->load(QUrl(url));
+ browser->resize(width, height);
+ browser->show();
+ browser->activateWindow();
+}
+
+void GwtCallback::prepareForSatelliteWindow(QString name,
+ int width,
+ int height)
+{
+ pOwner_->webPage()->prepareForSatelliteWindow(
+ PendingSatelliteWindow(name, pMainWindow_, width, height));
+}
+
+void GwtCallback::activateSatelliteWindow(QString name)
+{
+ pOwner_->webPage()->activateSatelliteWindow(name);
+}
+
+void GwtCallback::copyImageToClipboard(int left, int top, int width, int height)
+{
+ pOwner_->webPage()->updatePositionDependentActions(
+ QPoint(left + (width/2), top + (height/2)));
+ pOwner_->triggerPageAction(QWebPage::CopyImageToClipboard);
+}
+
+bool GwtCallback::supportsClipboardMetafile()
+{
+#ifdef Q_OS_WIN32
+ return true;
+#else
+ return false;
+#endif
+}
+
+namespace {
+ QMessageBox::ButtonRole captionToRole(QString caption)
+ {
+ if (caption == QString::fromAscii("OK"))
+ return QMessageBox::AcceptRole;
+ else if (caption == QString::fromAscii("Cancel"))
+ return QMessageBox::RejectRole;
+ else if (caption == QString::fromAscii("Yes"))
+ return QMessageBox::YesRole;
+ else if (caption == QString::fromAscii("No"))
+ return QMessageBox::NoRole;
+ else if (caption == QString::fromAscii("Save"))
+ return QMessageBox::AcceptRole;
+ else if (caption == QString::fromAscii("Don't Save"))
+ return QMessageBox::DestructiveRole;
+ else
+ return QMessageBox::ActionRole;
+ }
+} // anonymous namespace
+
+int GwtCallback::showMessageBox(int type,
+ QString caption,
+ QString message,
+ QString buttons,
+ int defaultButton,
+ int cancelButton)
+{
+ // cancel other message box if it's visible
+ QMessageBox* pMsgBox = qobject_cast<QMessageBox*>(
+ QApplication::activeModalWidget());
+ if (pMsgBox != NULL)
+ pMsgBox->close();
+
+ QMessageBox msgBox(safeMessageBoxIcon(static_cast<QMessageBox::Icon>(type)),
+ caption,
+ message,
+ QMessageBox::NoButton,
+ pOwner_->asWidget(),
+ Qt::Dialog | Qt::Sheet);
+ msgBox.setWindowModality(Qt::WindowModal);
+ msgBox.setTextFormat(Qt::PlainText);
+
+ QStringList buttonList = buttons.split(QChar::fromAscii('|'));
+
+ for (int i = 0; i != buttonList.size(); i++)
+ {
+ QPushButton* pBtn = msgBox.addButton(buttonList.at(i),
+ captionToRole(buttonList.at(i)));
+ if (defaultButton == i)
+ msgBox.setDefaultButton(pBtn);
+ }
+
+ msgBox.exec();
+
+ QAbstractButton* button = msgBox.clickedButton();
+ if (!button)
+ return cancelButton;
+
+ for (int i = 0; i < buttonList.size(); i++)
+ if (buttonList.at(i) == button->text())
+ return i;
+
+ return cancelButton;
+}
+
+QString GwtCallback::promptForText(QString title,
+ QString caption,
+ QString defaultValue,
+ bool usePasswordMask,
+ QString extraOptionPrompt,
+ bool extraOptionByDefault,
+ bool numbersOnly,
+ int selectionStart,
+ int selectionLength)
+{
+ InputDialog dialog(pOwner_->asWidget());
+ dialog.setWindowTitle(title);
+ dialog.setCaption(caption);
+
+ if (usePasswordMask)
+ dialog.setEchoMode(QLineEdit::Password);
+
+ if (!extraOptionPrompt.isEmpty())
+ {
+ dialog.setExtraOptionPrompt(extraOptionPrompt);
+ dialog.setExtraOption(extraOptionByDefault);
+ }
+
+ if (usePasswordMask)
+ {
+ // password prompts are shown higher up (because they relate to
+ // console progress dialogs which are at the top of the screen)
+ QRect parentGeom = pOwner_->asWidget()->geometry();
+ int x = parentGeom.left() + (parentGeom.width() / 2) - (dialog.width() / 2);
+ dialog.move(x, parentGeom.top() + 75);
+ }
+
+ if (numbersOnly)
+ dialog.setNumbersOnly(true);
+ if (!defaultValue.isEmpty())
+ {
+ dialog.setTextValue(defaultValue);
+ if (selectionStart >= 0 && selectionLength >= 0)
+ {
+ dialog.setSelection(selectionStart, selectionLength);
+ }
+ else
+ {
+ dialog.setSelection(0, defaultValue.size());
+ }
+ }
+
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ QString value = dialog.textValue();
+ bool extraOption = dialog.extraOption();
+ QString values;
+ values += value;
+ values += QString::fromAscii("\n");
+ values += extraOption ? QString::fromAscii("1") : QString::fromAscii("0");
+ return values;
+ }
+ else
+ return QString();
+}
+
+bool GwtCallback::supportsFullscreenMode()
+{
+ return desktop::supportsFullscreenMode(pMainWindow_);
+}
+
+void GwtCallback::toggleFullscreenMode()
+{
+ desktop::toggleFullscreenMode(pMainWindow_);
+}
+
+void GwtCallback::showKeyboardShortcutHelp()
+{
+ FilePath keyboardHelpPath = options().wwwDocsPath().complete("keyboard.htm");
+ QString file = QString::fromUtf8(keyboardHelpPath.absolutePath().c_str());
+ QUrl url = QUrl::fromLocalFile(file);
+ desktop::openUrl(url);
+}
+
+void GwtCallback::showAboutDialog()
+{
+ // WA_DeleteOnClose
+ AboutDialog* about = new AboutDialog(pOwner_->asWidget());
+ about->setAttribute(Qt::WA_DeleteOnClose);
+ about->show();
+}
+
+void GwtCallback::bringMainFrameToFront()
+{
+ desktop::raiseAndActivateWindow(pMainWindow_);
+}
+
+QString GwtCallback::filterText(QString text)
+{
+ // Ace doesn't do well with NFD Unicode text. To repro on
+ // Mac OS X, create a folder on disk with accented characters
+ // in the name, then create a file in that folder. Do a
+ // Get Info on the file and copy the path. Now you'll have
+ // an NFD string on the clipboard.
+ return text.normalized(QString::NormalizationForm_C);
+}
+
+#ifdef __APPLE__
+
+namespace {
+
+template <typename TValue>
+class CFReleaseHandle
+{
+public:
+ CFReleaseHandle(TValue value=NULL)
+ {
+ value_ = value;
+ }
+
+ ~CFReleaseHandle()
+ {
+ if (value_)
+ CFRelease(value_);
+ }
+
+ TValue& value()
+ {
+ return value_;
+ }
+
+ operator TValue () const
+ {
+ return value_;
+ }
+
+ TValue* operator& ()
+ {
+ return &value_;
+ }
+
+private:
+ TValue value_;
+};
+
+OSStatus addToPasteboard(PasteboardRef pasteboard,
+ int slot,
+ CFStringRef flavor,
+ const QByteArray& data)
+{
+ CFReleaseHandle<CFDataRef> dataRef = CFDataCreate(
+ NULL,
+ reinterpret_cast<const UInt8*>(data.constData()),
+ data.length());
+
+ if (!dataRef)
+ return memFullErr;
+
+ return ::PasteboardPutItemFlavor(pasteboard,
+ reinterpret_cast<PasteboardItemID>(slot),
+ flavor, dataRef, 0);
+}
+
+} // anonymous namespace
+
+void GwtCallback::cleanClipboard(bool stripHtml)
+{
+ CFReleaseHandle<PasteboardRef> clipboard;
+ if (::PasteboardCreate(kPasteboardClipboard, &clipboard))
+ return;
+
+ ::PasteboardSynchronize(clipboard);
+
+ ItemCount itemCount;
+ if (::PasteboardGetItemCount(clipboard, &itemCount) || itemCount < 1)
+ return;
+
+ PasteboardItemID itemId;
+ if (::PasteboardGetItemIdentifier(clipboard, 1, &itemId))
+ return;
+
+
+ /*
+ CFReleaseHandle<CFArrayRef> flavorTypes;
+ if (::PasteboardCopyItemFlavors(clipboard, itemId, &flavorTypes))
+ return;
+ for (int i = 0; i < CFArrayGetCount(flavorTypes); i++)
+ {
+ CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypes, i);
+ char buffer[1000];
+ if (!CFStringGetCString(flavorType, buffer, 1000, kCFStringEncodingMacRoman))
+ return;
+ qDebug() << buffer;
+ }
+ */
+
+ CFReleaseHandle<CFDataRef> data;
+ if (::PasteboardCopyItemFlavorData(clipboard,
+ itemId,
+ CFSTR("public.utf16-plain-text"),
+ &data))
+ {
+ return;
+ }
+
+ CFReleaseHandle<CFDataRef> htmlData;
+ OSStatus err;
+ if (!stripHtml && (err = ::PasteboardCopyItemFlavorData(clipboard, itemId, CFSTR("public.html"), &htmlData)))
+ {
+ if (err != badPasteboardFlavorErr)
+ return;
+ }
+
+ CFIndex len = ::CFDataGetLength(data);
+ QByteArray buffer;
+ buffer.resize(len);
+ ::CFDataGetBytes(data, CFRangeMake(0, len), reinterpret_cast<UInt8*>(buffer.data()));
+ QString str = QString::fromUtf16(reinterpret_cast<const ushort*>(buffer.constData()), buffer.length()/2);
+
+ if (::PasteboardClear(clipboard))
+ return;
+ if (addToPasteboard(clipboard, 1, CFSTR("public.utf8-plain-text"), str.toUtf8()))
+ return;
+
+ if (htmlData.value())
+ {
+ ::PasteboardPutItemFlavor(clipboard,
+ (PasteboardItemID)1,
+ CFSTR("public.html"),
+ htmlData,
+ 0);
+ }
+}
+#else
+
+void GwtCallback::cleanClipboard(bool stripHtml)
+{
+}
+
+#endif
+
+void GwtCallback::setPendingQuit(int pendingQuit)
+{
+ pendingQuit_ = pendingQuit;
+}
+
+int GwtCallback::collectPendingQuitRequest()
+{
+ if (pendingQuit_ != PendingQuitNone)
+ {
+ int pendingQuit = pendingQuit_;
+ pendingQuit_ = PendingQuitNone;
+ return pendingQuit;
+ }
+ else
+ {
+ return PendingQuitNone;
+ }
+}
+
+void GwtCallback::openProjectInNewWindow(QString projectFilePath)
+{
+ launchProjectInNewInstance(resolveAliasedPath(projectFilePath));
+}
+
+void GwtCallback::openTerminal(QString terminalPath,
+ QString workingDirectory,
+ QString extraPathEntries)
+{
+ // append extra path entries to our path before launching
+ std::string path = core::system::getenv("PATH");
+ std::string previousPath = path;
+ core::system::addToPath(&path, extraPathEntries.toStdString());
+ core::system::setenv("PATH", path);
+
+#if defined(Q_WS_MACX)
+
+ // call Terminal.app with an applescript that navigates it
+ // to the specified directory. note we don't reference the
+ // passed terminalPath because this setting isn't respected
+ // on the Mac (we always use Terminal.app)
+ FilePath macTermScriptFilePath =
+ desktop::options().scriptsPath().complete("mac-terminal");
+ QString macTermScriptPath = QString::fromUtf8(
+ macTermScriptFilePath.absolutePath().c_str());
+ QStringList args;
+ args.append(resolveAliasedPath(workingDirectory));
+ QProcess::startDetached(macTermScriptPath, args);
+
+#elif defined(Q_WS_WIN)
+
+ // git bash
+ if (terminalPath.length() > 0)
+ {
+ QStringList args;
+ args.append(QString::fromAscii("--login"));
+ args.append(QString::fromAscii("-i"));
+ QProcess::startDetached(terminalPath,
+ args,
+ resolveAliasedPath(workingDirectory));
+ }
+ else
+ {
+ // set HOME to USERPROFILE so msys ssh can find our keys
+ std::string previousHome = core::system::getenv("HOME");
+ std::string userProfile = core::system::getenv("USERPROFILE");
+ core::system::setenv("HOME", userProfile);
+
+ // run the process
+ QProcess::startDetached(QString::fromAscii("cmd.exe"),
+ QStringList(),
+ resolveAliasedPath(workingDirectory));
+
+ // revert to previous home
+ core::system::setenv("HOME", previousHome);
+ }
+
+
+#elif defined(Q_WS_X11)
+
+ // start the auto-detected terminal (or user-specified override)
+ if (!terminalPath.length() == 0)
+ {
+ QStringList args;
+ QProcess::startDetached(terminalPath,
+ args,
+ resolveAliasedPath(workingDirectory));
+ }
+ else
+ {
+ desktop::showWarning(
+ NULL,
+ QString::fromAscii("Terminal Not Found"),
+ QString::fromAscii(
+ "Unable to find a compatible terminal program to launch"));
+ }
+
+#endif
+
+ // restore previous path
+ core::system::setenv("PATH", previousPath);
+}
+
+bool isProportionalFont(QString fontFamily)
+{
+ QFont font(fontFamily, 12);
+ return !isFixedWidthFont(font);
+}
+
+QString GwtCallback::getFixedWidthFontList()
+{
+ QFontDatabase db;
+ QStringList families = db.families();
+
+ QStringList::iterator it = std::remove_if(
+ families.begin(), families.end(), isProportionalFont);
+ families.erase(it, families.end());
+
+ return families.join(QString::fromAscii("\n"));
+}
+
+QString GwtCallback::getFixedWidthFont()
+{
+ return options().fixedWidthFont();
+}
+
+void GwtCallback::setFixedWidthFont(QString font)
+{
+ options().setFixedWidthFont(font);
+}
+
+QString GwtCallback::getZoomLevels()
+{
+ QStringList zoomLevels;
+ BOOST_FOREACH(double zoomLevel, pMainWindow_->zoomLevels())
+ {
+ zoomLevels.append(QString::fromStdString(
+ safe_convert::numberToString(zoomLevel)));
+ }
+ return zoomLevels.join(QString::fromAscii("\n"));
+}
+
+double GwtCallback::getZoomLevel()
+{
+ return options().zoomLevel();
+}
+
+void GwtCallback::setZoomLevel(double zoomLevel)
+{
+ options().setZoomLevel(zoomLevel);
+}
+
+void GwtCallback::macZoomActualSize()
+{
+}
+
+void GwtCallback::macZoomIn()
+{
+}
+
+void GwtCallback::macZoomOut()
+{
+}
+
+
+QString GwtCallback::getDesktopSynctexViewer()
+{
+ return Synctex::desktopViewerInfo().name;
+}
+
+void GwtCallback::externalSynctexPreview(QString pdfPath, int page)
+{
+ synctex().syncView(resolveAliasedPath(pdfPath), page);
+}
+
+void GwtCallback::externalSynctexView(const QString& pdfFile,
+ const QString& srcFile,
+ int line,
+ int column)
+{
+ synctex().syncView(resolveAliasedPath(pdfFile),
+ resolveAliasedPath(srcFile),
+ QPoint(line, column));
+}
+
+void GwtCallback::launchSession(bool reload)
+{
+ pMainWindow_->launchSession(reload);
+}
+
+
+void GwtCallback::activateAndFocusOwner()
+{
+ desktop::raiseAndActivateWindow(pOwner_->asWidget());
+ pOwner_->webPage()->mainFrame()->setFocus();
+}
+
+void GwtCallback::reloadZoomWindow()
+{
+ QWidgetList topLevels = QApplication::topLevelWidgets();
+ for (int i = 0; i < topLevels.size(); i++)
+ {
+ QWidget* pWindow = topLevels.at(i);
+ if (!pWindow->isVisible())
+ continue;
+
+ if (pWindow->windowTitle() == QString::fromAscii("Plot Zoom"))
+ {
+ // do the reload
+ BrowserWindow* pBrowserWindow = (BrowserWindow*)pWindow;
+ pBrowserWindow->webView()->reload();
+
+ break;
+ }
+ }
+}
+
+void GwtCallback::setViewerUrl(QString url)
+{
+ pOwner_->webPage()->setViewerUrl(url);
+}
+
+bool GwtCallback::isOSXMavericks()
+{
+ return desktop::isOSXMavericks();
+}
+
+QString GwtCallback::getScrollingCompensationType()
+{
+#if defined(Q_WS_MACX)
+ return QString::fromAscii("Mac");
+#elif defined(Q_WS_WIN)
+ return QString::fromAscii("Win");
+#else
+ return QString::fromAscii("None");
+#endif
+}
+
+void GwtCallback::setBusy(bool)
+{
+#if defined(Q_WS_MACX)
+ // call AppNap apis for Mac (we use Cocoa on the Mac though so
+ // this codepath will never be hit)
+#endif
+}
+
+void GwtCallback::setWindowTitle(QString title)
+{
+ pMainWindow_->setWindowTitle(title + QString::fromUtf8(" - RStudio"));
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopGwtCallback.hpp b/src/cpp/desktop/DesktopGwtCallback.hpp
new file mode 100644
index 0000000..55a1c5b
--- /dev/null
+++ b/src/cpp/desktop/DesktopGwtCallback.hpp
@@ -0,0 +1,175 @@
+/*
+ * DesktopGwtCallback.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_GWT_CALLBACK_HPP
+#define DESKTOP_GWT_CALLBACK_HPP
+
+#include <QObject>
+#include <QtWebKit>
+
+#include "DesktopGwtCallbackOwner.hpp"
+
+namespace desktop {
+
+class MainWindow;
+class BrowserWindow;
+class Synctex;
+
+enum PendingQuit {
+ PendingQuitNone = 0,
+ PendingQuitAndExit = 1,
+ PendingQuitAndRestart = 2,
+ PendingQuitRestartAndReload = 3
+};
+
+class GwtCallback : public QObject
+{
+ Q_OBJECT
+
+public:
+ GwtCallback(MainWindow* pMainWindow, GwtCallbackOwner* pOwner);
+
+ int collectPendingQuitRequest();
+
+signals:
+ void workbenchInitialized();
+
+public slots:
+ QString proportionalFont();
+ QString fixedWidthFont();
+ bool isCocoa();
+ void browseUrl(QString url);
+ QString getOpenFileName(const QString& caption,
+ const QString& dir,
+ const QString& filter);
+ QString getSaveFileName(const QString& caption,
+ const QString& dir,
+ const QString& defaultExtension,
+ bool forceDefaultExtension);
+ QString getExistingDirectory(const QString& caption,
+ const QString& dir);
+ void undo();
+ void redo();
+ void clipboardCut();
+ void clipboardCopy();
+ void clipboardPaste();
+ QString getUriForPath(QString path);
+ void onWorkbenchInitialized(QString scratchPath);
+ void showFolder(QString path);
+ void showFile(QString path);
+
+ QString getRVersion();
+ QString chooseRVersion();
+ bool canChooseRVersion();
+
+ bool isRetina();
+
+ void openMinimalWindow(QString name, QString url, int width, int height);
+ void activateSatelliteWindow(QString name);
+ void prepareForSatelliteWindow(QString name, int width, int height);
+
+
+ // Image coordinates are relative to the window contents
+ void copyImageToClipboard(int left, int top, int width, int height);
+
+ bool supportsClipboardMetafile();
+
+ int showMessageBox(int type,
+ QString caption,
+ QString message,
+ QString buttons,
+ int defaultButton,
+ int cancelButton);
+
+ QString promptForText(QString title,
+ QString caption,
+ QString defaultValue,
+ bool usePasswordMask,
+ QString rememberPasswordPrompt,
+ bool rememberByDefault,
+ bool numbersOnly,
+ int selectionStart,
+ int selectionLength);
+
+ void showAboutDialog();
+ void bringMainFrameToFront();
+
+ QString filterText(QString text);
+
+ void cleanClipboard(bool stripHtml);
+
+ void setPendingQuit(int pendingQuit);
+
+ void openProjectInNewWindow(QString projectFilePath);
+
+ void openTerminal(QString terminalPath,
+ QString workingDirectory,
+ QString extraPathEntries);
+
+ QString getFixedWidthFontList();
+ QString getFixedWidthFont();
+ void setFixedWidthFont(QString font);
+
+ QString getZoomLevels();
+ double getZoomLevel();
+ void setZoomLevel(double zoomLevel);
+
+ void macZoomActualSize();
+ void macZoomIn();
+ void macZoomOut();
+
+ QString getDesktopSynctexViewer();
+
+ void externalSynctexPreview(QString pdfPath, int page);
+
+ void externalSynctexView(const QString& pdfFile,
+ const QString& srcFile,
+ int line,
+ int column);
+
+ bool supportsFullscreenMode();
+ void toggleFullscreenMode();
+ void showKeyboardShortcutHelp();
+
+ void launchSession(bool reload);
+
+ void reloadZoomWindow();
+
+ void setViewerUrl(QString url);
+ QString getScrollingCompensationType();
+
+ bool isOSXMavericks();
+
+ void setBusy(bool busy);
+
+ void setWindowTitle(QString title);
+
+private:
+ Synctex& synctex();
+
+ void activateAndFocusOwner();
+
+private:
+ void doAction(QKeySequence::StandardKey key);
+ MainWindow* pMainWindow_;
+ GwtCallbackOwner* pOwner_;
+ Synctex* pSynctex_;
+ int pendingQuit_;
+
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_GWT_CALLBACK_HPP
diff --git a/src/cpp/desktop/DesktopGwtCallbackOwner.hpp b/src/cpp/desktop/DesktopGwtCallbackOwner.hpp
new file mode 100644
index 0000000..2460b31
--- /dev/null
+++ b/src/cpp/desktop/DesktopGwtCallbackOwner.hpp
@@ -0,0 +1,40 @@
+/*
+ * DesktopGwtCallbackOwner.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_GWT_CALLBACK_OWNER_HPP
+#define DESKTOP_GWT_CALLBACK_OWNER_HPP
+
+#include <QWidget>
+#include <QString>
+#include <QWebPage>
+
+namespace desktop {
+
+class WebPage;
+
+class GwtCallbackOwner
+{
+public:
+ virtual ~GwtCallbackOwner() {}
+
+ virtual QWidget* asWidget() = 0;
+ virtual WebPage* webPage() = 0;
+ virtual void postWebViewEvent(QEvent *event) = 0;
+ virtual void triggerPageAction(QWebPage::WebAction action) = 0;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_GWT_CALLBACK_OWNER_HPP
diff --git a/src/cpp/desktop/DesktopGwtWindow.cpp b/src/cpp/desktop/DesktopGwtWindow.cpp
new file mode 100644
index 0000000..d95ddda
--- /dev/null
+++ b/src/cpp/desktop/DesktopGwtWindow.cpp
@@ -0,0 +1,39 @@
+/*
+ * DesktopGwtWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopGwtWindow.hpp"
+
+
+
+namespace desktop {
+
+GwtWindow::GwtWindow(bool showToolbar,
+ bool adjustTitle,
+ QUrl baseUrl,
+ QWidget* pParent) :
+ BrowserWindow(showToolbar, adjustTitle, baseUrl, pParent)
+{
+}
+
+bool GwtWindow::event(QEvent* pEvent)
+{
+ if (pEvent->type() == QEvent::WindowActivate)
+ onActivated();
+
+ return BrowserWindow::event(pEvent);
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopGwtWindow.hpp b/src/cpp/desktop/DesktopGwtWindow.hpp
new file mode 100644
index 0000000..2bf37db
--- /dev/null
+++ b/src/cpp/desktop/DesktopGwtWindow.hpp
@@ -0,0 +1,44 @@
+/*
+ * DesktopGwtWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_GWT_WINDOW_HPP
+#define DESKTOP_GWT_WINDOW_HPP
+
+#include "DesktopBrowserWindow.hpp"
+
+namespace desktop {
+
+class GwtWindow : public BrowserWindow
+{
+ Q_OBJECT
+public:
+ explicit GwtWindow(bool showToolbar,
+ bool adjustTitle,
+ QUrl baseUrl = QUrl(),
+ QWidget *parent = NULL);
+
+protected:
+ virtual bool event(QEvent* pEvent);
+
+private:
+ virtual void onActivated()
+ {
+ }
+
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_GWT_WINDOW_HPP
diff --git a/src/cpp/desktop/DesktopInputDialog.cpp b/src/cpp/desktop/DesktopInputDialog.cpp
new file mode 100644
index 0000000..3ee48ad
--- /dev/null
+++ b/src/cpp/desktop/DesktopInputDialog.cpp
@@ -0,0 +1,104 @@
+/*
+ * DesktopInputDialog.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopInputDialog.hpp"
+#include "ui_DesktopInputDialog.h"
+
+#include <QPushButton>
+
+InputDialog::InputDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::InputDialog()),
+ pOK_(NULL)
+{
+ ui->setupUi(this);
+ setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
+
+ pOK_ = new QPushButton(QString::fromAscii("OK"));
+ ui->buttonBox->addButton(pOK_, QDialogButtonBox::AcceptRole);
+
+ QPushButton* pCancel = new QPushButton(QString::fromAscii("Cancel"));
+ ui->buttonBox->addButton(pCancel, QDialogButtonBox::RejectRole);
+
+ ui->extraOption->setVisible(false);
+}
+
+InputDialog::~InputDialog()
+{
+ delete ui;
+}
+
+QString InputDialog::caption()
+{
+ return ui->label->text();
+}
+
+void InputDialog::setCaption(const QString& caption)
+{
+ ui->label->setText(caption);
+}
+
+QString InputDialog::textValue()
+{
+ return ui->lineEdit->text();
+}
+
+void InputDialog::setTextValue(const QString& value)
+{
+ ui->lineEdit->setText(value);
+}
+
+void InputDialog::setSelection(int offset, int length)
+{
+ offset = std::min(offset, textValue().size());
+ length = std::min(length,
+ textValue().size() - offset);
+
+ ui->lineEdit->setSelection(offset, length);
+}
+
+void InputDialog::setOkButtonLabel(const QString& label)
+{
+ pOK_->setText(label);
+}
+
+void InputDialog::setEchoMode(QLineEdit::EchoMode mode)
+{
+ ui->lineEdit->setEchoMode(mode);
+}
+
+void InputDialog::setNumbersOnly(bool numbersOnly)
+{
+ if (numbersOnly)
+ ui->lineEdit->setInputMask(QString::fromAscii("D99999999"));
+ else
+ ui->lineEdit->setInputMask(QString());
+}
+
+void InputDialog::setExtraOptionPrompt(const QString& prompt)
+{
+ ui->extraOption->setVisible(!prompt.isEmpty());
+ ui->extraOption->setText(prompt);
+}
+
+void InputDialog::setExtraOption(bool extraOption)
+{
+ ui->extraOption->setCheckState(Qt::Checked);
+}
+
+bool InputDialog::extraOption()
+{
+ return ui->extraOption->checkState() == Qt::Checked;
+}
diff --git a/src/cpp/desktop/DesktopInputDialog.hpp b/src/cpp/desktop/DesktopInputDialog.hpp
new file mode 100644
index 0000000..341308a
--- /dev/null
+++ b/src/cpp/desktop/DesktopInputDialog.hpp
@@ -0,0 +1,52 @@
+/*
+ * DesktopInputDialog.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOPINPUTDIALOG_HPP
+#define DESKTOPINPUTDIALOG_HPP
+
+#include <QDialog>
+#include <QLineEdit>
+
+namespace Ui {
+ class InputDialog;
+}
+
+class InputDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit InputDialog(QWidget *parent = 0);
+ ~InputDialog();
+
+ QString caption();
+ void setCaption(const QString& caption);
+ QString textValue();
+ void setTextValue(const QString& value);
+ void setSelection(int offset, int length);
+ void setOkButtonLabel(const QString& label);
+ void setEchoMode(QLineEdit::EchoMode mode);
+ void setNumbersOnly(bool numbersOnly);
+ void setExtraOptionPrompt(const QString& prompt);
+
+ void setExtraOption(bool extraOption);
+ bool extraOption();
+
+private:
+ Ui::InputDialog *ui;
+ QPushButton* pOK_;
+};
+
+#endif // DESKTOPINPUTDIALOG_HPP
diff --git a/src/cpp/desktop/DesktopInputDialog.ui b/src/cpp/desktop/DesktopInputDialog.ui
new file mode 100644
index 0000000..ba38b73
--- /dev/null
+++ b/src/cpp/desktop/DesktopInputDialog.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InputDialog</class>
+ <widget class="QDialog" name="InputDialog">
+ <property name="windowModality">
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>126</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>8</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="extraOption">
+ <property name="text">
+ <string>Extra Option</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::NoButton</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>InputDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>InputDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/cpp/desktop/DesktopMain.cpp b/src/cpp/desktop/DesktopMain.cpp
new file mode 100644
index 0000000..6843ab1
--- /dev/null
+++ b/src/cpp/desktop/DesktopMain.cpp
@@ -0,0 +1,374 @@
+/*
+ * DesktopMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <QtGui>
+#include <QtWebKit>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <core/Log.hpp>
+
+#include <core/system/FileScanner.hpp>
+#include <core/Error.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/StringUtils.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include <core/r_util/RProjectFile.hpp>
+
+#include "DesktopApplicationLaunch.hpp"
+#include "DesktopSlotBinders.hpp"
+#include "DesktopDetectRHome.hpp"
+#include "DesktopOptions.hpp"
+#include "DesktopUtils.hpp"
+#include "DesktopSessionLauncher.hpp"
+
+QProcess* pRSessionProcess;
+QString sharedSecret;
+
+using namespace core;
+using namespace desktop;
+
+namespace {
+
+void initializeSharedSecret()
+{
+ sharedSecret = QString::number(rand())
+ + QString::number(rand())
+ + QString::number(rand());
+ std::string value = sharedSecret.toUtf8().constData();
+ core::system::setenv("RS_SHARED_SECRET", value);
+}
+
+void initializeWorkingDirectory(int argc,
+ char* argv[],
+ const QString& filename)
+{
+ // calculate what our initial working directory should be
+ std::string workingDir;
+
+ // if there is a filename passed to us then use it's path
+ if (filename != QString())
+ {
+ FilePath filePath(filename.toUtf8().constData());
+ if (filePath.exists())
+ {
+ if (filePath.isDirectory())
+ workingDir = filePath.absolutePath();
+ else
+ workingDir = filePath.parent().absolutePath();
+ }
+ }
+
+ // do additinal detection if necessary
+ if (workingDir.empty())
+ {
+ // get current path
+ FilePath currentPath = FilePath::safeCurrentPath(
+ core::system::userHomePath());
+
+#if defined(_WIN32) || defined(__APPLE__)
+
+ // detect whether we were launched from the system application menu
+ // (e.g. Dock, Program File icon, etc.). we do this by checking
+ // whether the executable path is within the current path. if we
+ // weren't launched from the system app menu that set the initial
+ // wd to the current path
+
+ FilePath exePath;
+ Error error = core::system::executablePath(argv[0], &exePath);
+ if (!error)
+ {
+ if (!exePath.isWithin(currentPath))
+ workingDir = currentPath.absolutePath();
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+
+#else
+
+ // on linux we take the current working dir if we were launched
+ // from within a terminal
+ if (core::system::stdoutIsTerminal() &&
+ (currentPath != core::system::userHomePath()))
+ {
+ workingDir = currentPath.absolutePath();
+ }
+
+#endif
+
+ }
+
+ // set the working dir if we have one
+ if (!workingDir.empty())
+ core::system::setenv("RS_INITIAL_WD", workingDir);
+}
+
+void setInitialProject(const FilePath& projectFile, QString* pFilename)
+{
+ core::system::setenv("RS_INITIAL_PROJECT", projectFile.absolutePath());
+ pFilename->clear();
+}
+
+void initializeStartupEnvironment(QString* pFilename)
+{
+ // if the filename ends with .RData or .rda then this is an
+ // environment file. if it ends with .Rproj then it is
+ // a project file. we handle both cases by setting an environment
+ // var and then resetting the pFilename so it isn't processed
+ // using the standard open file logic
+ FilePath filePath(pFilename->toUtf8().constData());
+ if (filePath.exists())
+ {
+ std::string ext = filePath.extensionLowerCase();
+
+ // if it is a directory or just an .rdata file then we can see
+ // whether there is a project file we can automatically attach to
+ if (filePath.isDirectory())
+ {
+ FilePath projectFile = r_util::projectFromDirectory(filePath);
+ if (!projectFile.empty())
+ {
+ setInitialProject(projectFile, pFilename);
+ }
+ }
+ else if (ext == ".rproj")
+ {
+ setInitialProject(filePath, pFilename);
+ }
+ else if (ext == ".rdata" || ext == ".rda")
+ {
+ core::system::setenv("RS_INITIAL_ENV", filePath.absolutePath());
+ pFilename->clear();
+ }
+
+ }
+}
+
+QString verifyAndNormalizeFilename(QString filename)
+{
+ if (filename.isNull() || filename.isEmpty())
+ return QString();
+
+ QFileInfo fileInfo(filename);
+ if (fileInfo.exists())
+ return fileInfo.absoluteFilePath();
+ else
+ return QString();
+}
+
+bool isNonProjectFilename(QString filename)
+{
+ if (filename.isNull() || filename.isEmpty())
+ return false;
+
+ FilePath filePath(filename.toUtf8().constData());
+ return filePath.exists() && filePath.extensionLowerCase() != ".rproj";
+}
+
+} // anonymous namespace
+
+int main(int argc, char* argv[])
+{
+ core::system::initHook();
+
+ try
+ {
+ initializeLang();
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
+
+ // initialize log
+ core::system::initializeLog("rdesktop",
+ core::system::kLogLevelWarning,
+ desktop::userLogPath());
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+#ifdef __APPLE__
+ // font substituion for OSX Mavericks
+ // see: https://bugreports.qt-project.org/browse/QTBUG-32789
+ QFont::insertSubstitution(QString::fromUtf8(".Lucida Grande UI"),
+ QString::fromUtf8("Lucida Grande"));
+#endif
+
+ boost::scoped_ptr<QApplication> pApp;
+ boost::scoped_ptr<ApplicationLaunch> pAppLaunch;
+ ApplicationLaunch::init(QString::fromAscii("RStudio"),
+ argc,
+ argv,
+ &pApp,
+ &pAppLaunch);
+
+ // determine the filename that was passed to us
+ QString filename;
+#ifdef __APPLE__
+ // get filename from OpenFile apple-event (pump to ensure delivery)
+ pApp->processEvents();
+ filename = verifyAndNormalizeFilename(
+ pAppLaunch->startupOpenFileRequest());
+#endif
+ // allow all platforms (including OSX) to check the command line.
+ // we include OSX because the way Qt handles apple events is to
+ // re-route them to the first instance to register for events. in
+ // this case (for projects) we use this to initiate a launch
+ // of the application with the project filename on the command line
+ if (filename.isEmpty())
+ {
+ // get filename from command line arguments
+ if (pApp->arguments().size() > 1)
+ {
+ QString arg = pApp->arguments().last();
+ if (arg != QString::fromAscii(kRunDiagnosticsOption))
+ filename = verifyAndNormalizeFilename(arg);
+ }
+ }
+
+ // if we have a filename and it is NOT a project file then see
+ // if we can open it within an existing instance
+ if (isNonProjectFilename(filename))
+ {
+ if (pAppLaunch->sendMessage(filename))
+ return 0;
+ }
+ else
+ {
+ // try to register ourselves as a peer for others
+ pAppLaunch->attemptToRegisterPeer();
+ }
+
+ // init options from command line
+ desktop::options().initFromCommandLine(pApp->arguments());
+
+ // reset log if we are in run-diagnostics mode
+ if (desktop::options().runDiagnostics())
+ {
+ desktop::reattachConsoleIfNecessary();
+ initializeStderrLog("rdesktop", core::system::kLogLevelWarning);
+ }
+
+ pApp->setAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
+
+ initializeSharedSecret();
+ initializeWorkingDirectory(argc, argv, filename);
+ initializeStartupEnvironment(&filename);
+
+ Options& options = desktop::options();
+ if (!prepareEnvironment(options))
+ return 1;
+
+ // get install path
+ FilePath installPath;
+ error = core::system::installPath("..", argv[0], &installPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return EXIT_FAILURE;
+ }
+
+#ifdef _WIN32
+ RVersion version = detectRVersion(false);
+#endif
+
+ // calculate paths to config file, rsession, and desktop scripts
+ FilePath confPath, sessionPath, scriptsPath;
+
+ // check for debug configuration
+#ifndef NDEBUG
+ FilePath currentPath = FilePath::safeCurrentPath(installPath);
+ if (currentPath.complete("conf/rdesktop-dev.conf").exists())
+ {
+ confPath = currentPath.complete("conf/rdesktop-dev.conf");
+ sessionPath = currentPath.complete("session/rsession");
+ scriptsPath = currentPath.complete("desktop");
+#ifdef _WIN32
+ if (version.architecture() == ArchX64)
+ sessionPath = installPath.complete("x64/rsession");
+#endif
+ }
+#endif
+
+ // if there is no conf path then release mode
+ if (confPath.empty())
+ {
+ // default paths (then tweak)
+ sessionPath = installPath.complete("bin/rsession");
+ scriptsPath = installPath.complete("bin");
+
+ // check for win64 binary on windows
+#ifdef _WIN32
+ if (version.architecture() == ArchX64)
+ sessionPath = installPath.complete("bin/x64/rsession");
+#endif
+
+ // check for running in a bundle on OSX
+#ifdef __APPLE__
+ if (installPath.complete("Info.plist").exists())
+ {
+ sessionPath = installPath.complete("MacOS/rsession");
+ scriptsPath = installPath.complete("MacOS");
+ }
+#endif
+ }
+ core::system::fixupExecutablePath(&sessionPath);
+
+ // set the scripts path in options
+ desktop::options().setScriptsPath(scriptsPath);
+
+ // launch session
+ SessionLauncher sessionLauncher(sessionPath, confPath);
+ error = sessionLauncher.launchFirstSession(filename, pAppLaunch.get());
+ if (!error)
+ {
+ int result = pApp->exec();
+
+ sessionLauncher.cleanupAtExit();
+
+ options.cleanUpScratchTempDir();
+
+ return result;
+ }
+ else
+ {
+ LOG_ERROR(error);
+
+ // These calls to processEvents() seem to be necessary to get
+ // readAllStandardError to work.
+ pApp->processEvents();
+ pApp->processEvents();
+ pApp->processEvents();
+
+ QMessageBox errorMsg(safeMessageBoxIcon(QMessageBox::Critical),
+ QString::fromUtf8("RStudio"),
+ sessionLauncher.launchFailedErrorMessage());
+ errorMsg.addButton(new QPushButton(QString::fromUtf8("OK")),
+ QMessageBox::AcceptRole);
+ errorMsg.show();
+
+ pApp->exec();
+
+ return EXIT_FAILURE;
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
diff --git a/src/cpp/desktop/DesktopMainWindow.cpp b/src/cpp/desktop/DesktopMainWindow.cpp
new file mode 100644
index 0000000..5ab2bbb
--- /dev/null
+++ b/src/cpp/desktop/DesktopMainWindow.cpp
@@ -0,0 +1,372 @@
+/*
+ * DesktopMainWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopMainWindow.hpp"
+
+#include <algorithm>
+
+#include <QtGui>
+#include <QtWebKit>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+
+#include "DesktopGwtCallback.hpp"
+#include "DesktopMenuCallback.hpp"
+#include "DesktopWebView.hpp"
+#include "DesktopOptions.hpp"
+#include "DesktopSlotBinders.hpp"
+#include "DesktopUtils.hpp"
+#include "DesktopSessionLauncher.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+MainWindow::MainWindow(QUrl url) :
+ GwtWindow(false, false, url, NULL),
+ menuCallback_(this),
+ gwtCallback_(this, this),
+ pSessionLauncher_(NULL),
+ pCurrentSessionProcess_(NULL)
+{
+ quitConfirmed_ = false;
+ pToolbar_->setVisible(false);
+
+ // initialize zoom levels
+ zoomLevels_.push_back(1.0);
+ zoomLevels_.push_back(1.1);
+ zoomLevels_.push_back(1.20);
+ zoomLevels_.push_back(1.30);
+ zoomLevels_.push_back(1.40);
+ zoomLevels_.push_back(1.50);
+ zoomLevels_.push_back(1.75);
+ zoomLevels_.push_back(2.00);
+
+ // Dummy menu bar to deal with the fact that
+ // the real menu bar isn't ready until well
+ // after startup.
+ QMenuBar* pMainMenuStub = new QMenuBar(this);
+ pMainMenuStub->addMenu(QString::fromUtf8("File"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Edit"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Code"));
+ pMainMenuStub->addMenu(QString::fromUtf8("View"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Plots"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Session"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Build"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Debug"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Tools"));
+ pMainMenuStub->addMenu(QString::fromUtf8("Help"));
+ setMenuBar(pMainMenuStub);
+
+ connect(&menuCallback_, SIGNAL(menuBarCompleted(QMenuBar*)),
+ this, SLOT(setMenuBar(QMenuBar*)));
+ connect(&menuCallback_, SIGNAL(commandInvoked(QString)),
+ this, SLOT(invokeCommand(QString)));
+ connect(&menuCallback_, SIGNAL(manageCommand(QString,QAction*)),
+ this, SLOT(manageCommand(QString,QAction*)));
+ connect(&menuCallback_, SIGNAL(manageCommandVisibility(QString,QAction*)),
+ this, SLOT(manageCommandVisibility(QString,QAction*)));
+
+ connect(&menuCallback_, SIGNAL(zoomIn()), this, SLOT(zoomIn()));
+ connect(&menuCallback_, SIGNAL(zoomOut()), this, SLOT(zoomOut()));
+
+ connect(&gwtCallback_, SIGNAL(workbenchInitialized()),
+ this, SIGNAL(firstWorkbenchInitialized()));
+ connect(&gwtCallback_, SIGNAL(workbenchInitialized()),
+ this, SLOT(onWorkbenchInitialized()));
+
+ connect(webView(), SIGNAL(onCloseWindowShortcut()),
+ this, SLOT(onCloseWindowShortcut()));
+
+ connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)),
+ this, SLOT(commitDataRequest(QSessionManager&)),
+ Qt::DirectConnection);
+
+ setWindowIcon(QIcon(QString::fromAscii(":/icons/RStudio.ico")));
+
+ setWindowTitle(QString::fromAscii("RStudio"));
+
+#ifdef Q_OS_MAC
+ QMenuBar* pDefaultMenu = new QMenuBar();
+ pDefaultMenu->addMenu(new WindowMenu());
+#endif
+
+ desktop::enableFullscreenMode(this, true);
+
+ //setContentsMargins(10000, 0, -10000, 0);
+ setStyleSheet(QString::fromAscii("QMainWindow { background: #e1e2e5; }"));
+}
+
+QString MainWindow::getSumatraPdfExePath()
+{
+ QWebFrame* pMainFrame = webView()->page()->mainFrame();
+ QString sumatraPath = pMainFrame->evaluateJavaScript(QString::fromAscii(
+ "window.desktopHooks.getSumatraPdfExePath()")).toString();
+ return sumatraPath;
+}
+
+void MainWindow::launchSession(bool reload)
+{
+ Error error = pSessionLauncher_->launchNextSession(reload);
+ if (error)
+ {
+ LOG_ERROR(error);
+
+ showMessageBox(QMessageBox::Critical,
+ this,
+ QString::fromUtf8("RStudio"),
+ QString::fromUtf8("The R session failed to start."));
+
+ quit();
+ }
+}
+
+void MainWindow::onCloseWindowShortcut()
+{
+ QWebFrame* pMainFrame = webView()->page()->mainFrame();
+
+ bool closeSourceDocEnabled = pMainFrame->evaluateJavaScript(
+ QString::fromAscii(
+ "window.desktopHooks.isCommandEnabled('closeSourceDoc')")).toBool();
+
+ if (!closeSourceDocEnabled)
+ close();
+}
+
+
+void MainWindow::onWorkbenchInitialized()
+{
+ //QTimer::singleShot(300, this, SLOT(resetMargins()));
+
+ // reset state (in case this occurred in response to a manual reload
+ // or reload for a new project context)
+ quitConfirmed_ = false;
+
+ // see if there is a project dir to display in the titlebar
+ // if there are unsaved changes then resolve them before exiting
+ QVariant vProjectDir = webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.getActiveProjectDir()"));
+ QString projectDir = vProjectDir.toString();
+ if (projectDir.length() > 0)
+ setWindowTitle(projectDir + QString::fromAscii(" - RStudio"));
+ else
+ setWindowTitle(QString::fromAscii("RStudio"));
+
+ avoidMoveCursorIfNecessary();
+}
+
+void MainWindow::resetMargins()
+{
+ setContentsMargins(0, 0, 0, 0);
+}
+
+// this notification occurs when windows or X11 is shutting
+// down -- in this case we want to be a good citizen and just
+// exit right away so we notify the gwt callback that a legit
+// quit and exit is on the way and we set the quitConfirmed_
+// flag so no prompting occurs (note that source documents
+// have already been auto-saved so will be restored next time
+// the current project context is opened)
+void MainWindow::commitDataRequest(QSessionManager &manager)
+{
+ gwtCallback_.setPendingQuit(PendingQuitAndExit);
+ quitConfirmed_ = true;
+}
+
+void MainWindow::loadUrl(const QUrl& url)
+{
+ webView()->setBaseUrl(url);
+ webView()->load(url);
+}
+
+void MainWindow::quit()
+{
+ quitConfirmed_ = true;
+ close();
+}
+
+void MainWindow::onJavaScriptWindowObjectCleared()
+{
+ double zoomLevel = options().zoomLevel();
+ if (zoomLevel != webView()->dpiAwareZoomFactor())
+ webView()->setDpiAwareZoomFactor(zoomLevel);
+
+ webView()->page()->mainFrame()->addToJavaScriptWindowObject(
+ QString::fromAscii("desktop"),
+ &gwtCallback_,
+ QScriptEngine::QtOwnership);
+ webView()->page()->mainFrame()->addToJavaScriptWindowObject(
+ QString::fromAscii("desktopMenuCallback"),
+ &menuCallback_,
+ QScriptEngine::QtOwnership);
+}
+
+void MainWindow::invokeCommand(QString commandId)
+{
+ webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.invokeCommand('") + commandId + QString::fromAscii("');"));
+}
+
+void MainWindow::zoomIn()
+{
+ // get next greatest value
+ std::vector<double>::const_iterator it = std::upper_bound(
+ zoomLevels_.begin(), zoomLevels_.end(), options().zoomLevel());
+ if (it != zoomLevels_.end())
+ {
+ options().setZoomLevel(*it);
+ webView()->reload();
+ }
+}
+
+void MainWindow::zoomOut()
+{
+ // get next smallest value
+ std::vector<double>::const_iterator it = std::lower_bound(
+ zoomLevels_.begin(), zoomLevels_.end(), options().zoomLevel());
+ if (it != zoomLevels_.begin() && it != zoomLevels_.end())
+ {
+ options().setZoomLevel(*(it-1));
+ webView()->reload();
+ }
+}
+
+void MainWindow::manageCommand(QString cmdId, QAction* action)
+{
+ QWebFrame* pMainFrame = webView()->page()->mainFrame();
+ action->setVisible(pMainFrame->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.isCommandVisible('") + cmdId + QString::fromAscii("')")).toBool());
+ action->setEnabled(pMainFrame->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.isCommandEnabled('") + cmdId + QString::fromAscii("')")).toBool());
+ action->setText(pMainFrame->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.getCommandLabel('") + cmdId + QString::fromAscii("')")).toString());
+ if (action->isCheckable())
+ {
+ action->setChecked(pMainFrame->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.isCommandChecked('") + cmdId + QString::fromAscii("')")).toBool());
+ }
+}
+
+// a faster version of the above that just checks and sets the command's
+// visibility state (to trigger visibility of menus containing the command)
+void MainWindow::manageCommandVisibility(QString cmdId, QAction* action)
+{
+ QWebFrame* pMainFrame = webView()->page()->mainFrame();
+ action->setVisible(pMainFrame->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.isCommandVisible('") + cmdId + QString::fromAscii("')")).toBool());
+}
+
+void MainWindow::evaluateJavaScript(QString jsCode)
+{
+ QWebFrame* pMainFrame = webView()->page()->mainFrame();
+ pMainFrame->evaluateJavaScript(jsCode);
+}
+
+void MainWindow::closeEvent(QCloseEvent* pEvent)
+{
+ QWebFrame* pFrame = webView()->page()->mainFrame();
+ if (!pFrame)
+ {
+ pEvent->accept();
+ return;
+ }
+
+ QVariant hasQuitR = pFrame->evaluateJavaScript(QString::fromAscii("!!window.desktopHooks"));
+
+ if (quitConfirmed_
+ || !hasQuitR.toBool()
+ || pCurrentSessionProcess_ == NULL
+ || pCurrentSessionProcess_->state() != QProcess::Running)
+ {
+ pEvent->accept();
+ }
+ else
+ {
+ pFrame->evaluateJavaScript(QString::fromAscii("window.desktopHooks.quitR()"));
+ pEvent->ignore();
+ }
+}
+
+void MainWindow::setMenuBar(QMenuBar *pMenubar)
+{
+ delete menuBar();
+ this->QMainWindow::setMenuBar(pMenubar);
+}
+
+void MainWindow::openFileInRStudio(QString path)
+{
+ QFileInfo fileInfo(path);
+ if (!fileInfo.isAbsolute() || !fileInfo.exists() || !fileInfo.isFile())
+ return;
+
+ path = path.replace(QString::fromAscii("\\"), QString::fromAscii("\\\\"))
+ .replace(QString::fromAscii("\""), QString::fromAscii("\\\""))
+ .replace(QString::fromAscii("\n"), QString::fromAscii("\\n"));
+
+ webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.openFile(\"") + path + QString::fromAscii("\")"));
+}
+
+void MainWindow::onPdfViewerClosed(QString pdfPath)
+{
+ webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("window.synctexNotifyPdfViewerClosed(\"") +
+ pdfPath + QString::fromAscii("\")"));
+}
+
+void MainWindow::onPdfViewerSyncSource(QString srcFile, int line, int column)
+{
+ boost::format fmt("window.desktopSynctexInverseSearch(\"%1%\", %2%, %3%)");
+ std::string js = boost::str(fmt % srcFile.toStdString() % line % column);
+
+ webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromStdString(js));
+}
+
+// private interface for SessionLauncher
+
+void MainWindow::setSessionLauncher(SessionLauncher* pSessionLauncher)
+{
+ pSessionLauncher_ = pSessionLauncher;
+}
+
+void MainWindow::setSessionProcess(QProcess* pSessionProcess)
+{
+ pCurrentSessionProcess_ = pSessionProcess;
+}
+
+// allow SessionLauncher to collect restart requests from GwtCallback
+int MainWindow::collectPendingQuitRequest()
+{
+ return gwtCallback_.collectPendingQuitRequest();
+}
+
+bool MainWindow::desktopHooksAvailable()
+{
+ return webView()->page()->mainFrame()->evaluateJavaScript(
+ QString::fromAscii("!!window.desktopHooks")).toBool();
+}
+
+void MainWindow::onActivated()
+{
+ if (desktopHooksAvailable())
+ invokeCommand(QString::fromAscii("vcsRefreshNoError"));
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopMainWindow.hpp b/src/cpp/desktop/DesktopMainWindow.hpp
new file mode 100644
index 0000000..81e4665
--- /dev/null
+++ b/src/cpp/desktop/DesktopMainWindow.hpp
@@ -0,0 +1,103 @@
+/*
+ * DesktopMainWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_MAIN_WINDOW_HPP
+#define DESKTOP_MAIN_WINDOW_HPP
+
+#include <vector>
+
+#include <QProcess>
+#include <QtGui>
+#include <QSessionManager>
+
+#include "DesktopGwtCallback.hpp"
+#include "DesktopGwtWindow.hpp"
+#include "DesktopMenuCallback.hpp"
+
+namespace desktop {
+
+class SessionLauncher;
+
+class MainWindow : public GwtWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QUrl url=QUrl());
+
+public:
+ QString getSumatraPdfExePath();
+ void evaluateJavaScript(QString jsCode);
+ void launchSession(bool reload);
+
+ const std::vector<double>& zoomLevels() const { return zoomLevels_; }
+
+public slots:
+ void quit();
+ void loadUrl(const QUrl& url);
+ void setMenuBar(QMenuBar *pMenuBar);
+ void invokeCommand(QString commandId);
+ void zoomIn();
+ void zoomOut();
+ void manageCommand(QString cmdId, QAction* pAction);
+ void manageCommandVisibility(QString cmdId, QAction* pAction);
+ void openFileInRStudio(QString path);
+ void onPdfViewerClosed(QString pdfPath);
+ void onPdfViewerSyncSource(QString srcFile, int line, int column);
+
+signals:
+ void firstWorkbenchInitialized();
+
+protected slots:
+ void onCloseWindowShortcut();
+ void onJavaScriptWindowObjectCleared();
+ void onWorkbenchInitialized();
+ void resetMargins();
+ void commitDataRequest(QSessionManager &manager);
+
+protected:
+ virtual void closeEvent(QCloseEvent*);
+
+// private interface for SessionLauncher
+private:
+ friend class SessionLauncher;
+
+ // allow SessionLauncher to give us a reference to itself (so we can
+ // call launchProcess back on it)
+ void setSessionLauncher(SessionLauncher* pSessionLauncher);
+
+ // allow SessionLauncher to give us a reference to the currently
+ // active rsession process so that we can use it in closeEvent handling
+ void setSessionProcess(QProcess* pSessionProcess);
+
+ // allow SessionLauncher to collect restart requests from GwtCallback
+ int collectPendingQuitRequest();
+
+ bool desktopHooksAvailable();
+
+ virtual void onActivated();
+
+private:
+ std::vector<double> zoomLevels_;
+ bool quitConfirmed_;
+ MenuCallback menuCallback_;
+ GwtCallback gwtCallback_;
+ SessionLauncher* pSessionLauncher_;
+ QProcess* pCurrentSessionProcess_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_MAIN_WINDOW_HPP
diff --git a/src/cpp/desktop/DesktopMenuCallback.cpp b/src/cpp/desktop/DesktopMenuCallback.cpp
new file mode 100644
index 0000000..089bb90
--- /dev/null
+++ b/src/cpp/desktop/DesktopMenuCallback.cpp
@@ -0,0 +1,311 @@
+/*
+ * DesktopMenuCallback.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopMenuCallback.hpp"
+#include <QDebug>
+#include <QApplication>
+#include "DesktopCommandInvoker.hpp"
+#include "DesktopSubMenu.hpp"
+
+#ifdef Q_OS_MAC
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+namespace desktop {
+
+MenuCallback::MenuCallback(QObject *parent) :
+ QObject(parent)
+{
+}
+
+void MenuCallback::beginMainMenu()
+{
+ pMainMenu_ = new QMenuBar();
+}
+
+void MenuCallback::beginMenu(QString label)
+{
+#ifdef Q_OS_MAC
+ if (label == QString::fromUtf8("&Help"))
+ {
+ pMainMenu_->addMenu(new WindowMenu(pMainMenu_));
+ }
+#endif
+
+ SubMenu* pMenu = new SubMenu(label, pMainMenu_);
+
+ connect(pMenu, SIGNAL(manageCommandVisibility(QString,QAction*)),
+ this, SIGNAL(manageCommandVisibility(QString,QAction*)));
+
+ if (menuStack_.count() == 0)
+ pMainMenu_->addMenu(pMenu);
+ else
+ menuStack_.top()->addMenu(pMenu);
+
+ menuStack_.push(pMenu);
+}
+
+QAction* MenuCallback::addCustomAction(QString commandId,
+ QString label,
+ QString tooltip)
+{
+
+ QAction* pAction = NULL;
+ if (commandId == QString::fromAscii("zoomIn"))
+ {
+ pAction = menuStack_.top()->addAction(QIcon(),
+ label,
+ this,
+ SIGNAL(zoomIn()),
+ QKeySequence::ZoomIn);
+ }
+ else if (commandId == QString::fromAscii("zoomOut"))
+ {
+ pAction = menuStack_.top()->addAction(QIcon(),
+ label,
+ this,
+ SIGNAL(zoomOut()),
+ QKeySequence::ZoomOut);
+ }
+#ifdef Q_OS_LINUX
+ else if (commandId == QString::fromAscii("nextTab"))
+ {
+ pAction = menuStack_.top()->addAction(QIcon(),
+ label,
+ this,
+ SLOT(actionInvoked()),
+ QKeySequence(Qt::CTRL +
+ Qt::Key_PageDown));
+ }
+ else if (commandId == QString::fromAscii("previousTab"))
+ {
+ pAction = menuStack_.top()->addAction(QIcon(),
+ label,
+ this,
+ SLOT(actionInvoked()),
+ QKeySequence(Qt::CTRL +
+ Qt::Key_PageUp));
+ }
+#endif
+
+ if (pAction != NULL)
+ {
+ pAction->setData(commandId);
+ pAction->setToolTip(tooltip);
+ return pAction;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+void MenuCallback::addCommand(QString commandId,
+ QString label,
+ QString tooltip,
+ QString shortcut,
+ bool checkable)
+{
+ shortcut = shortcut.replace(QString::fromUtf8("Enter"), QString::fromAscii("\n"));
+
+ QKeySequence keySequence(shortcut);
+#ifndef Q_WS_MAC
+ if (shortcut.contains(QString::fromAscii("\n")))
+ {
+ int value = (keySequence[0] & Qt::MODIFIER_MASK) + Qt::Key_Enter;
+ keySequence = QKeySequence(value);
+ }
+#endif
+
+ // allow custom action handlers first shot
+ QAction* pAction = addCustomAction(commandId, label, tooltip);
+
+ // if there was no custom handler then do stock command-id processing
+ if (pAction == NULL)
+ {
+ pAction = menuStack_.top()->addAction(QIcon(),
+ label,
+ this,
+ SLOT(actionInvoked()),
+ keySequence);
+ pAction->setData(commandId);
+ pAction->setToolTip(tooltip);
+ if (checkable)
+ pAction->setCheckable(true);
+
+ MenuActionBinder* pBinder = new MenuActionBinder(menuStack_.top(), pAction);
+ connect(pBinder, SIGNAL(manageCommand(QString,QAction*)),
+ this, SIGNAL(manageCommand(QString,QAction*)));
+ }
+}
+
+void MenuCallback::actionInvoked()
+{
+ QAction* action = qobject_cast<QAction*>(sender());
+ QString commandId = action->data().toString();
+ commandInvoked(commandId);
+}
+
+void MenuCallback::addSeparator()
+{
+ if (menuStack_.count() > 0)
+ menuStack_.top()->addSeparator();
+}
+
+void MenuCallback::endMenu()
+{
+ menuStack_.pop();
+}
+
+void MenuCallback::endMainMenu()
+{
+ menuBarCompleted(pMainMenu_);
+}
+
+MenuActionBinder::MenuActionBinder(QMenu* pMenu, QAction* pAction) : QObject(pAction)
+{
+ connect(pMenu, SIGNAL(aboutToShow()), this, SLOT(onShowMenu()));
+ connect(pMenu, SIGNAL(aboutToHide()), this, SLOT(onHideMenu()));
+ pAction_ = pAction;
+ keySequence_ = pAction->shortcut();
+ pAction->setShortcut(QKeySequence());
+}
+
+void MenuActionBinder::onShowMenu()
+{
+ QString commandId = pAction_->data().toString();
+ manageCommand(commandId, pAction_);
+ pAction_->setShortcut(keySequence_);
+}
+
+void MenuActionBinder::onHideMenu()
+{
+ pAction_->setShortcut(QKeySequence());
+}
+
+WindowMenu::WindowMenu(QWidget *parent) : QMenu(QString::fromUtf8("&Window"), parent)
+{
+ pMinimize_ = addAction(QString::fromUtf8("Minimize"));
+ pMinimize_->setShortcut(QKeySequence(QString::fromAscii("Meta+M")));
+ connect(pMinimize_, SIGNAL(triggered()),
+ this, SLOT(onMinimize()));
+
+ pZoom_ = addAction(QString::fromUtf8("Zoom"));
+ connect(pZoom_, SIGNAL(triggered()),
+ this, SLOT(onZoom()));
+
+ addSeparator();
+
+ pWindowPlaceholder_ = addAction(QString::fromAscii("__PLACEHOLDER__"));
+ pWindowPlaceholder_->setVisible(false);
+
+ addSeparator();
+
+ pBringAllToFront_ = addAction(QString::fromUtf8("Bring All to Front"));
+ connect(pBringAllToFront_, SIGNAL(triggered()),
+ this, SLOT(onBringAllToFront()));
+
+ connect(this, SIGNAL(aboutToShow()),
+ this, SLOT(onAboutToShow()));
+ connect(this, SIGNAL(aboutToHide()),
+ this, SLOT(onAboutToHide()));
+}
+
+void WindowMenu::onMinimize()
+{
+ QWidget* pWin = QApplication::activeWindow();
+ if (pWin)
+ {
+ pWin->setWindowState(Qt::WindowMinimized);
+ }
+}
+
+void WindowMenu::onZoom()
+{
+ QWidget* pWin = QApplication::activeWindow();
+ if (pWin)
+ {
+ pWin->setWindowState(pWin->windowState() ^ Qt::WindowMaximized);
+ }
+}
+
+void WindowMenu::onBringAllToFront()
+{
+#ifdef Q_WS_MAC
+ CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
+ if (appUrlRef)
+ {
+ LSOpenCFURLRef(appUrlRef, NULL);
+ CFRelease(appUrlRef);
+ }
+#endif
+}
+
+void WindowMenu::onAboutToShow()
+{
+ QWidget* win = QApplication::activeWindow();
+ pMinimize_->setEnabled(win);
+ pZoom_->setEnabled(win && win->maximumSize() != win->minimumSize());
+ pBringAllToFront_->setEnabled(win);
+
+
+ for (int i = windows_.size() - 1; i >= 0; i--)
+ {
+ QAction* pAction = windows_[i];
+ removeAction(pAction);
+ windows_.removeAt(i);
+ pAction->deleteLater();
+ }
+
+ QWidgetList topLevels = QApplication::topLevelWidgets();
+ for (int i = 0; i < topLevels.size(); i++)
+ {
+ QWidget* pWindow = topLevels.at(i);
+ if (!pWindow->isVisible())
+ continue;
+
+ // construct with no parent (we free it manually)
+ QAction* pAction = new QAction(pWindow->windowTitle(), NULL);
+ pAction->setData(QVariant::fromValue(pWindow));
+ pAction->setCheckable(true);
+ if (pWindow->isActiveWindow())
+ pAction->setChecked(true);
+ insertAction(pWindowPlaceholder_, pAction);
+ connect(pAction, SIGNAL(triggered()),
+ this, SLOT(showWindow()));
+
+ windows_.append(pAction);
+ }
+}
+
+void WindowMenu::onAboutToHide()
+{
+}
+
+void WindowMenu::showWindow()
+{
+ QAction* pAction = qobject_cast<QAction*>(sender());
+ if (!pAction)
+ return;
+ QWidget* pWidget = pAction->data().value<QWidget*>();
+ if (!pWidget)
+ return;
+ if (pWidget->isMinimized())
+ pWidget->setWindowState(pWidget->windowState() & ~Qt::WindowMinimized);
+ pWidget->activateWindow();
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopMenuCallback.hpp b/src/cpp/desktop/DesktopMenuCallback.hpp
new file mode 100644
index 0000000..d849f13
--- /dev/null
+++ b/src/cpp/desktop/DesktopMenuCallback.hpp
@@ -0,0 +1,121 @@
+/*
+ * DesktopMenuCallback.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_MENU_CALLBACK_HPP
+#define DESKTOP_MENU_CALLBACK_HPP
+
+#include <QObject>
+#include <QHash>
+#include <QList>
+#include <QMenu>
+#include <QMenuBar>
+#include <QStack>
+#include <QKeyEvent>
+#include <DesktopSubMenu.hpp>
+
+namespace desktop {
+
+class MenuCallback : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MenuCallback(QObject *parent = 0);
+
+public slots:
+ void beginMainMenu();
+ void beginMenu(QString label);
+ void addCommand(QString commandId,
+ QString label,
+ QString tooltip,
+ QString shortcut,
+ bool isCheckable);
+ void addSeparator();
+ void endMenu();
+ void endMainMenu();
+ void actionInvoked();
+
+signals:
+ void menuBarCompleted(QMenuBar* menuBar);
+ void manageCommand(QString commandId, QAction* action);
+ void manageCommandVisibility(QString commandId, QAction* action);
+ void commandInvoked(QString commandId);
+
+ void zoomIn();
+ void zoomOut();
+
+private:
+ QAction* addCustomAction(QString commandId,
+ QString label,
+ QString tooltip);
+
+private:
+ QMenuBar* pMainMenu_;
+ QStack<SubMenu*> menuStack_;
+};
+
+/* Previously, in desktop mode, many keyboard shortcuts were handled by Qt,
+ * by way of the keyboard shortcuts being added to QAction objects which
+ * appear on menus. This is problematic because the keyboard shortcut handling
+ * is thus not managed using the same code as web mode; most seriously, when
+ * modal dialogs are shown, the keyboard shortcuts still work.
+ *
+ * Rather than try to keep the QActions in sync with commands, which seems
+ * like it would be very chatty, this class ensures the QActions don't have
+ * shortcuts assigned to them--except while their menus are showing. It also
+ * ensures that commands are "managed" right before they are shown.
+ */
+class MenuActionBinder : public QObject
+{
+ Q_OBJECT
+public:
+ MenuActionBinder(QMenu* pMenu, QAction* action);
+
+public slots:
+ void onShowMenu();
+ void onHideMenu();
+
+signals:
+ void manageCommand(QString commandId, QAction* action);
+
+private:
+ QAction* pAction_;
+ QKeySequence keySequence_;
+};
+
+class WindowMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ explicit WindowMenu(QWidget *parent = 0);
+
+protected slots:
+ void onMinimize();
+ void onZoom();
+ void onBringAllToFront();
+ void onAboutToShow();
+ void onAboutToHide();
+ void showWindow();
+
+private:
+ QAction* pMinimize_;
+ QAction* pZoom_;
+ QAction* pBringAllToFront_;
+ QAction* pWindowPlaceholder_;
+ QList<QAction*> windows_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_MENU_CALLBACK_HPP
diff --git a/src/cpp/desktop/DesktopNetworkAccessManager.cpp b/src/cpp/desktop/DesktopNetworkAccessManager.cpp
new file mode 100644
index 0000000..baa3455
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkAccessManager.cpp
@@ -0,0 +1,68 @@
+/*
+ * DesktopNetworkAccessManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopNetworkAccessManager.hpp"
+
+#include <core/FilePath.hpp>
+
+#include <QTimer>
+
+#include "DesktopNetworkReply.hpp"
+#include "DesktopNetworkIOService.hpp"
+#include "DesktopOptions.hpp"
+
+using namespace core;
+
+using namespace desktop;
+
+NetworkAccessManager::NetworkAccessManager(QString secret, QObject *parent) :
+ QNetworkAccessManager(parent), secret_(secret)
+{
+ setProxy(QNetworkProxy::NoProxy);
+
+ QTimer* pTimer = new QTimer(this);
+ connect(pTimer, SIGNAL(timeout()), SLOT(pollForIO()));
+ pTimer->start(25);
+}
+
+QNetworkReply* NetworkAccessManager::createRequest(
+ Operation op,
+ const QNetworkRequest& req,
+ QIODevice* outgoingData)
+{
+ if (req.url().scheme() == QString::fromAscii("http") &&
+ (req.url().host() == QString::fromAscii("127.0.0.1") ||
+ req.url().host() == QString::fromAscii("localhost")) &&
+ req.url().port() == desktop::options().portNumber().toInt())
+ {
+ return new NetworkReply(
+ desktop::options().localPeer(),
+ secret_,
+ op,
+ req,
+ outgoingData,
+ this);
+ }
+ else
+ {
+ return QNetworkAccessManager::createRequest(op, req, outgoingData);
+ }
+}
+
+
+void NetworkAccessManager::pollForIO()
+{
+ ioServicePoll();
+}
diff --git a/src/cpp/desktop/DesktopNetworkAccessManager.hpp b/src/cpp/desktop/DesktopNetworkAccessManager.hpp
new file mode 100644
index 0000000..a24cde7
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkAccessManager.hpp
@@ -0,0 +1,41 @@
+/*
+ * DesktopNetworkAccessManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_NETWORK_ACCESS_MANAGER_HPP
+#define DESKTOP_NETWORK_ACCESS_MANAGER_HPP
+
+#include <QObject>
+#include <QtNetwork>
+
+class NetworkAccessManager : public QNetworkAccessManager
+{
+ Q_OBJECT
+public:
+ explicit NetworkAccessManager(QString secret,
+ QObject *parent = 0);
+
+private slots:
+ void pollForIO();
+
+protected:
+ QNetworkReply* createRequest(Operation op,
+ const QNetworkRequest& req,
+ QIODevice* outgoingData = 0);
+
+private:
+ QString secret_;
+};
+
+#endif // DESKTOP_NETWORK_ACCESS_MANAGER_HPP
diff --git a/src/cpp/desktop/DesktopNetworkIOService.cpp b/src/cpp/desktop/DesktopNetworkIOService.cpp
new file mode 100644
index 0000000..5f4c707
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkIOService.cpp
@@ -0,0 +1,40 @@
+/*
+ * DesktopNetworkIOService.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopNetworkIOService.hpp"
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+using namespace core;
+
+namespace desktop {
+
+boost::asio::io_service& ioService()
+{
+ static boost::asio::io_service instance;
+ return instance;
+}
+
+void ioServicePoll()
+{
+ boost::system::error_code ec;
+ ioService().poll(ec);
+ if (ec)
+ LOG_ERROR(Error(ec, ERROR_LOCATION));
+
+ ioService().reset();
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopNetworkIOService.hpp b/src/cpp/desktop/DesktopNetworkIOService.hpp
new file mode 100644
index 0000000..92b7c06
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkIOService.hpp
@@ -0,0 +1,27 @@
+/*
+ * DesktopNetworkIOService.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_NETWORK_IO_SERVICE_HPP
+#define DESKTOP_NETWORK_IO_SERVICE_HPP
+
+#include <boost/asio/io_service.hpp>
+
+namespace desktop {
+
+boost::asio::io_service& ioService();
+
+void ioServicePoll();
+
+} // namespace desktop
+
+#endif // DESKTOP_NETWORK_IO_SERVICE_HPP
diff --git a/src/cpp/desktop/DesktopNetworkProxyFactory.cpp b/src/cpp/desktop/DesktopNetworkProxyFactory.cpp
new file mode 100644
index 0000000..5bdb9a2
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkProxyFactory.cpp
@@ -0,0 +1,37 @@
+/*
+ * DesktopNetworkProxyFactory.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopNetworkProxyFactory.hpp"
+
+NetworkProxyFactory::NetworkProxyFactory()
+{
+}
+
+QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery& query)
+{
+ QList<QNetworkProxy> results;
+
+ if (query.peerHostName() == QString::fromAscii("127.0.0.1")
+ || query.peerHostName().toLower() == QString::fromAscii("localhost"))
+ {
+ results.append(QNetworkProxy::NoProxy);
+ }
+ else
+ {
+ results = systemProxyForQuery(query);
+ }
+
+ return results;
+}
diff --git a/src/cpp/desktop/DesktopNetworkProxyFactory.hpp b/src/cpp/desktop/DesktopNetworkProxyFactory.hpp
new file mode 100644
index 0000000..410543c
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkProxyFactory.hpp
@@ -0,0 +1,30 @@
+/*
+ * DesktopNetworkProxyFactory.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOPNETWORKPROXYFACTORY_HPP
+#define DESKTOPNETWORKPROXYFACTORY_HPP
+
+#include <QtCore>
+#include <QtNetwork>
+
+class NetworkProxyFactory : public QNetworkProxyFactory
+{
+public:
+ NetworkProxyFactory();
+
+ virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery& query = QNetworkProxyQuery());
+};
+
+#endif // DESKTOPNETWORKPROXYFACTORY_HPP
diff --git a/src/cpp/desktop/DesktopNetworkReply.cpp b/src/cpp/desktop/DesktopNetworkReply.cpp
new file mode 100644
index 0000000..9c861c4
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkReply.cpp
@@ -0,0 +1,315 @@
+/*
+ * DesktopNetworkReply.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopNetworkReply.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/asio/io_service.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <core/http/Response.hpp>
+
+#ifdef _WIN32
+#include <core/http/NamedPipeAsyncClient.hpp>
+#else
+#include <core/http/LocalStreamAsyncClient.hpp>
+#endif
+
+#include <QTimer>
+#include <QUrl>
+
+#include "DesktopNetworkIOService.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+struct NetworkReply::Impl
+{
+ Impl(const std::string& localPeer)
+ #ifdef _WIN32
+ : pClient(new http::NamedPipeAsyncClient(ioService(),
+ localPeer,
+ retryProfile())),
+ #else
+ : pClient(new http::LocalStreamAsyncClient(ioService(),
+ FilePath(localPeer),
+ false,
+ retryProfile())),
+ #endif
+ replyReadOffset(0)
+ {
+ }
+ #ifdef _WIN32
+ boost::shared_ptr<http::NamedPipeAsyncClient> pClient;
+ #else
+ boost::shared_ptr<http::LocalStreamAsyncClient> pClient;
+ #endif
+ QByteArray replyData;
+ qint64 replyReadOffset;
+
+private:
+ static http::ConnectionRetryProfile retryProfile()
+ {
+ return http::ConnectionRetryProfile(
+ boost::posix_time::seconds(10),
+ boost::posix_time::milliseconds(50));
+ }
+};
+
+NetworkReply::NetworkReply(const std::string& localPeer,
+ const QString& secret,
+ QNetworkAccessManager::Operation op,
+ const QNetworkRequest& req,
+ QIODevice* outgoingData,
+ QObject *parent)
+ : QNetworkReply(parent),
+ pImpl_(new Impl(localPeer)),
+ localPeer_(localPeer),
+ secret_(secret),
+ redirects_(0)
+{
+ // set our attributes
+ setOperation(op);
+ setRequest(req);
+ setUrl(req.url());
+
+ // build http request
+ http::Request request;
+ switch(op)
+ {
+ case QNetworkAccessManager::HeadOperation:
+ request.setMethod("HEAD");
+ break;
+ case QNetworkAccessManager::GetOperation:
+ request.setMethod("GET");
+ break;
+ case QNetworkAccessManager::PutOperation:
+ request.setMethod("PUT");
+ break;
+ case QNetworkAccessManager::PostOperation:
+ request.setMethod("POST");
+ break;
+ case QNetworkAccessManager::DeleteOperation:
+ request.setMethod("DELETE");
+ break;
+ case QNetworkAccessManager::CustomOperation:
+ {
+ QVariant custom = req.attribute(QNetworkRequest::CustomVerbAttribute);
+ if (!custom.isNull())
+ request.setMethod(custom.toString().toStdString());
+ break;
+ }
+ case QNetworkAccessManager::UnknownOperation:
+ LOG_WARNING_MESSAGE("Unknown operation passed to createRequest");
+ break;
+ }
+
+ // uri
+ std::string uri = req.url().path().toStdString();
+ if (req.url().hasQuery())
+ {
+ uri.append("?");
+ QByteArray queryString = req.url().encodedQuery();
+ uri.append(queryString.begin(), queryString.end());
+ }
+ request.setUri(uri);
+
+ // host
+ request.setHost("127.0.0.1");
+
+ // headers
+ QList<QByteArray> headers = req.rawHeaderList();
+ for (int i=0; i<headers.size(); i++)
+ {
+ QByteArray name = headers.at(i);
+ QByteArray value = req.rawHeader(name);
+ request.setHeader(std::string(name.begin(), name.end()),
+ std::string(value.begin(), value.end()));
+ }
+
+ // shared secret header
+ request.setHeader("X-Shared-Secret", secret.toStdString());
+
+ // body
+ if (outgoingData != NULL)
+ {
+ QByteArray postData = outgoingData->readAll();
+ request.setBody(std::string(postData.begin(), postData.end()));
+ }
+
+ // execute
+ executeRequest(request);
+}
+
+void NetworkReply::executeRequest(const http::Request& request)
+{
+ // set the request
+ pImpl_->pClient->request().assign(request);
+
+ // execute and bind to response handlers
+ pImpl_->pClient->execute(boost::bind(&NetworkReply::onResponse, this, _1),
+ boost::bind(&NetworkReply::onError, this, _1));
+}
+
+NetworkReply::~NetworkReply()
+{
+ try
+ {
+ pImpl_->pClient->disableHandlers();
+ pImpl_->pClient->close();
+ }
+ catch(...)
+ {
+ }
+}
+
+
+qint64 NetworkReply::bytesAvailable() const
+{
+ // check for bytes available
+ qint64 avail = pImpl_->replyData.size() - pImpl_->replyReadOffset;
+
+ // Qt will never call readData unless you tell it that at least
+ // 512 bytes are available
+ return std::max(avail, (qint64)512);
+}
+
+bool NetworkReply::isSequential() const
+{
+ return true;
+}
+
+void NetworkReply::abort()
+{
+}
+
+qint64 NetworkReply::readData(char *data, qint64 maxSize)
+{
+ if (pImpl_->replyReadOffset >= pImpl_->replyData.size())
+ return -1;
+
+ qint64 bytesToRead = qMin(maxSize, pImpl_->replyData.size() -
+ pImpl_->replyReadOffset);
+
+ ::memcpy(data,
+ pImpl_->replyData.constData() + pImpl_->replyReadOffset,
+ bytesToRead);
+
+ pImpl_->replyReadOffset += bytesToRead;
+
+ if (pImpl_->replyReadOffset >= pImpl_->replyData.size())
+ QTimer::singleShot(0, this, SIGNAL(finished()));
+ else
+ QTimer::singleShot(0, this, SIGNAL(readyRead()));
+
+ return bytesToRead;
+}
+
+void NetworkReply::handleRedirect(QString location)
+{
+ // calculate the redirected url
+ QUrl newUrl = request().url().resolved(location);
+
+ // perform the redirect
+ http::Request request;
+ request.setMethod("GET");
+ request.setUri(newUrl.path().toStdString());
+ request.setHeader("X-Shared-Secret", secret_.toStdString());
+ request.setHost("127.0.0.1");
+
+ // reset the connection
+ pImpl_.reset(new Impl(localPeer_));
+
+ // execute the request
+ executeRequest(request);
+}
+
+void NetworkReply::onResponse(const http::Response& response)
+{
+ // check for a redirect
+ if (response.statusCode() == http::status::MovedTemporarily ||
+ response.statusCode() == http::status::MovedPermanently)
+ {
+ // check for max redirects
+ if (++redirects_ > 5)
+ {
+ http::Response tooManyRedirectsResponse;
+ tooManyRedirectsResponse.setError(http::status::TooManyRedirects,
+ "Too many redirects");
+ onResponse(tooManyRedirectsResponse);
+ }
+ // perform redirect
+ else
+ {
+ std::string location = response.headerValue("Location");
+ handleRedirect(QString::fromStdString(location));
+ }
+
+ // done
+ return;
+ }
+
+ // call open on the QIODevice
+ open(ReadOnly | Unbuffered);
+
+ // set http status and reason codes
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute,
+ response.statusCode());
+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute,
+ QString::fromStdString(response.statusMessage()));
+
+ // set headers
+ BOOST_FOREACH(const http::Header& header, response.headers())
+ {
+ QByteArray name = QByteArray(header.name.c_str());
+ QByteArray value = QByteArray(header.value.c_str());
+ setRawHeader(name, value);
+ }
+
+ // set body / content-length
+ const std::string& body = response.body();
+ if (!body.empty())
+ {
+ setHeader(QNetworkRequest::ContentLengthHeader, (uint)body.length());
+ pImpl_->replyData.append(body.data(), body.length());
+ pImpl_->replyReadOffset = 0;
+ }
+
+ // notify listeners that data is ready
+ QTimer::singleShot(0, this, SIGNAL(readyRead()));
+
+ // set finished flag
+ setFinished(true);
+}
+
+void NetworkReply::onError(const Error& networkError)
+{
+ if ((networkError.code() != boost::asio::error::operation_aborted) &&
+ (networkError.code() != boost::asio::error::broken_pipe) &&
+ (networkError.code() != boost::asio::error::eof) &&
+ !core::isPathNotFoundError(networkError) )
+ {
+ LOG_ERROR(networkError);
+ }
+
+ error(QNetworkReply::UnknownNetworkError);
+ finished();
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopNetworkReply.hpp b/src/cpp/desktop/DesktopNetworkReply.hpp
new file mode 100644
index 0000000..baec518
--- /dev/null
+++ b/src/cpp/desktop/DesktopNetworkReply.hpp
@@ -0,0 +1,76 @@
+/*
+ * DesktopNetworkReply.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOPNETWORKREPLY_HPP
+#define DESKTOPNETWORKREPLY_HPP
+
+#include <boost/scoped_ptr.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <QNetworkReply>
+
+namespace core {
+ class Error;
+ class FilePath;
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+namespace desktop {
+
+class NetworkReply : public QNetworkReply
+{
+ Q_OBJECT
+public:
+ NetworkReply(const std::string& localPeer,
+ const QString& secret,
+ QNetworkAccessManager::Operation op,
+ const QNetworkRequest& req,
+ QIODevice* outgoingData,
+ QObject *parent = 0);
+ virtual ~NetworkReply();
+
+signals:
+
+public slots:
+
+public:
+
+ qint64 bytesAvailable() const;
+ bool isSequential() const;
+ void abort();
+
+protected:
+ qint64 readData(char *data, qint64 maxSize);
+
+private:
+ void onResponse(const core::http::Response& response);
+ void onError(const core::Error& error);
+
+ void handleRedirect(QString location);
+ void executeRequest(const core::http::Request& request);
+
+private:
+ struct Impl;
+ boost::scoped_ptr<Impl> pImpl_;
+ std::string localPeer_;
+ QString secret_;
+ int redirects_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOPNETWORKREPLY_HPP
diff --git a/src/cpp/desktop/DesktopOptions.cpp b/src/cpp/desktop/DesktopOptions.cpp
new file mode 100644
index 0000000..a7949d2
--- /dev/null
+++ b/src/cpp/desktop/DesktopOptions.cpp
@@ -0,0 +1,380 @@
+/*
+ * DesktopOptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopOptions.hpp"
+
+#include <QtGui>
+
+#include <core/Error.hpp>
+#include <core/Random.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include "DesktopUtils.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+#ifdef _WIN32
+// Defined in DesktopRVersion.cpp
+QString binDirToHomeDir(QString binDir);
+#endif
+
+QString scratchPath;
+
+Options& options()
+{
+ static Options singleton;
+ return singleton;
+}
+
+void Options::initFromCommandLine(const QStringList& arguments)
+{
+ for (int i=1; i<arguments.size(); i++)
+ {
+ QString arg = arguments.at(i);
+ if (arg == QString::fromAscii(kRunDiagnosticsOption))
+ runDiagnostics_ = true;
+ }
+}
+
+void Options::restoreMainWindowBounds(QMainWindow* win)
+{
+ QString key = QString::fromAscii("mainwindow/geometry");
+ if (settings_.contains(key))
+ win->restoreGeometry(settings_.value(key).toByteArray());
+ else
+ {
+ QSize size = QSize(1024, 768).boundedTo(
+ QApplication::desktop()->availableGeometry().size());
+ if (size.width() > 800 && size.height() > 500)
+ {
+ // Only use default size if it seems sane; otherwise let Qt set it
+ win->resize(size);
+ }
+ }
+}
+
+void Options::saveMainWindowBounds(QMainWindow* win)
+{
+ settings_.setValue(QString::fromAscii("mainwindow/geometry"),
+ win->saveGeometry());
+}
+
+QString Options::portNumber() const
+{
+ // lookup / generate on demand
+ if (portNumber_.length() == 0)
+ {
+ // Use a random-ish port number to avoid collisions between different
+ // instances of rdesktop-launched rsessions
+ int base = std::abs(core::random::uniformRandomInteger<int>());
+ portNumber_ = QString::number((base % 40000) + 8080);
+
+ // recalculate the local peer and set RS_LOCAL_PEER so that
+ // rsession and it's children can use it
+#ifdef _WIN32
+ QString localPeer = QString::fromAscii("\\\\.\\pipe\\") +
+ portNumber_ + QString::fromAscii("-rsession");
+#else
+ QString localPeer = QDir(QDir::tempPath()).absolutePath() +
+ QString::fromAscii("/") + portNumber_ +
+ QString::fromAscii("-rsession");
+#endif
+ localPeer_ = localPeer.toUtf8().constData();
+ core::system::setenv("RS_LOCAL_PEER", localPeer_);
+ }
+
+ return portNumber_;
+}
+
+QString Options::newPortNumber()
+{
+ portNumber_.clear();
+ return portNumber();
+}
+
+std::string Options::localPeer() const
+{
+ return localPeer_;
+}
+
+
+namespace {
+QString findFirstMatchingFont(const QStringList& fonts,
+ QString defaultFont,
+ bool fixedWidthOnly)
+{
+ for (int i = 0; i < fonts.size(); i++)
+ {
+ QFont font(fonts.at(i));
+ if (font.exactMatch())
+ if (!fixedWidthOnly || isFixedWidthFont(QFont(fonts.at(i))))
+ return fonts.at(i);
+ }
+ return defaultFont;
+}
+} // anonymous namespace
+
+QString Options::proportionalFont() const
+{
+ static QString detectedFont;
+
+ QString font =
+ settings_.value(QString::fromAscii("font.proportional")).toString();
+ if (!font.isEmpty())
+ {
+ return font;
+ }
+
+ if (!detectedFont.isEmpty())
+ return detectedFont;
+
+ QStringList fontList;
+#if defined(_WIN32)
+ fontList <<
+ QString::fromAscii("Segoe UI") << QString::fromAscii("Verdana") << // Windows
+ QString::fromAscii("Lucida Sans") << QString::fromAscii("DejaVu Sans") << // Linux
+ QString::fromAscii("Lucida Grande") << // Mac
+ QString::fromAscii("Helvetica");
+#elif defined(__APPLE__)
+ fontList <<
+ QString::fromAscii("Lucida Grande") << // Mac
+ QString::fromAscii("Lucida Sans") << QString::fromAscii("DejaVu Sans") << // Linux
+ QString::fromAscii("Segoe UI") << QString::fromAscii("Verdana") << // Windows
+ QString::fromAscii("Helvetica");
+#else
+ fontList <<
+ QString::fromAscii("Ubuntu") << // Ubuntu
+ QString::fromAscii("Lucida Sans") << QString::fromAscii("DejaVu Sans") << // Linux
+ QString::fromAscii("Lucida Grande") << // Mac
+ QString::fromAscii("Segoe UI") << QString::fromAscii("Verdana") << // Windows
+ QString::fromAscii("Helvetica");
+#endif
+ return QString::fromAscii("\"") +
+ findFirstMatchingFont(fontList, QString::fromAscii("sans-serif"), false) +
+ QString::fromAscii("\"");
+}
+
+void Options::setFixedWidthFont(QString font)
+{
+ if (font.isEmpty())
+ settings_.remove(QString::fromAscii("font.fixedWidth"));
+ else
+ settings_.setValue(QString::fromAscii("font.fixedWidth"),
+ font);
+}
+
+QString Options::fixedWidthFont() const
+{
+ static QString detectedFont;
+
+ QString font =
+ settings_.value(QString::fromAscii("font.fixedWidth")).toString();
+ if (!font.isEmpty())
+ {
+ return font;
+ }
+
+ if (!detectedFont.isEmpty())
+ return detectedFont;
+
+ QStringList fontList;
+ fontList <<
+#if defined(Q_WS_MACX)
+ QString::fromAscii("Monaco")
+#elif defined (Q_WS_X11)
+ QString::fromAscii("Ubuntu Mono") << QString::fromAscii("Droid Sans Mono") << QString::fromAscii("DejaVu Sans Mono") << QString::fromAscii("Monospace")
+#else
+ QString::fromAscii("Lucida Console") << QString::fromAscii("Consolas") // Windows;
+#endif
+ ;
+
+ // The fallback font is Courier, not monospace, because QtWebKit doesn't
+ // actually provide a monospace font (appears to use Helvetica)
+
+ return detectedFont = QString::fromAscii("\"") +
+ findFirstMatchingFont(fontList, QString::fromAscii("Courier"), true) +
+ QString::fromAscii("\"");
+}
+
+
+double Options::zoomLevel() const
+{
+ QVariant zoom = settings_.value(QString::fromAscii("view.zoomLevel"), 1.0);
+ return zoom.toDouble();
+}
+
+void Options::setZoomLevel(double zoomLevel)
+{
+ settings_.setValue(QString::fromAscii("view.zoomLevel"), zoomLevel);
+}
+
+
+
+#ifdef _WIN32
+QString Options::rBinDir() const
+{
+ // HACK: If RBinDir doesn't appear at all, that means the user has never
+ // specified a preference for R64 vs. 32-bit R. In this situation we should
+ // accept either. We'll distinguish between this case (where preferR64
+ // should be ignored) and the other case by using null for this case and
+ // empty string for the other.
+ if (!settings_.contains(QString::fromAscii("RBinDir")))
+ return QString::null;
+
+ QString value = settings_.value(QString::fromAscii("RBinDir")).toString();
+ return value.isNull() ? QString() : value;
+}
+
+void Options::setRBinDir(QString path)
+{
+ settings_.setValue(QString::fromAscii("RBinDir"), path);
+}
+
+bool Options::preferR64() const
+{
+ if (!core::system::isWin64())
+ return false;
+
+ if (!settings_.contains(QString::fromAscii("PreferR64")))
+ return true;
+ return settings_.value(QString::fromAscii("PreferR64")).toBool();
+}
+
+void Options::setPreferR64(bool preferR64)
+{
+ settings_.setValue(QString::fromAscii("PreferR64"), preferR64);
+}
+#endif
+
+FilePath Options::scriptsPath() const
+{
+ return scriptsPath_;
+}
+
+
+void Options::setScriptsPath(const FilePath& scriptsPath)
+{
+ scriptsPath_ = scriptsPath;
+}
+
+FilePath Options::executablePath() const
+{
+ if (executablePath_.empty())
+ {
+ Error error = core::system::executablePath(QApplication::arguments().at(0).toUtf8(),
+ &executablePath_);
+ if (error)
+ LOG_ERROR(error);
+ }
+ return executablePath_;
+}
+
+FilePath Options::supportingFilePath() const
+{
+ if (supportingFilePath_.empty())
+ {
+ // default to install path
+ core::system::installPath("..",
+ QApplication::arguments().at(0).toUtf8(),
+ &supportingFilePath_);
+
+ // adapt for OSX resource bundles
+#ifdef __APPLE__
+ if (supportingFilePath_.complete("Info.plist").exists())
+ supportingFilePath_ = supportingFilePath_.complete("Resources");
+#endif
+ }
+ return supportingFilePath_;
+}
+
+FilePath Options::wwwDocsPath() const
+{
+ FilePath supportingFilePath = desktop::options().supportingFilePath();
+ FilePath wwwDocsPath = supportingFilePath.complete("www/docs");
+ if (!wwwDocsPath.exists())
+ wwwDocsPath = supportingFilePath.complete("../gwt/www/docs");
+#ifdef __APPLE__
+ if (!wwwDocsPath.exists())
+ wwwDocsPath = supportingFilePath.complete("../../../../../gwt/www/docs");
+#endif
+ return wwwDocsPath;
+}
+
+#ifdef _WIN32
+
+FilePath Options::urlopenerPath() const
+{
+ FilePath parentDir = scriptsPath();
+
+ // detect dev configuration
+ if (parentDir.filename() == "desktop")
+ parentDir = parentDir.complete("urlopener");
+
+ return parentDir.complete("urlopener.exe");
+}
+
+FilePath Options::rsinversePath() const
+{
+ FilePath parentDir = scriptsPath();
+
+ // detect dev configuration
+ if (parentDir.filename() == "desktop")
+ parentDir = parentDir.complete("synctex/rsinverse");
+
+ return parentDir.complete("rsinverse.exe");
+}
+
+#endif
+
+QStringList Options::ignoredUpdateVersions() const
+{
+ return settings_.value(QString::fromAscii("ignoredUpdateVersions"), QStringList()).toStringList();
+}
+
+void Options::setIgnoredUpdateVersions(const QStringList& ignoredVersions)
+{
+ settings_.setValue(QString::fromAscii("ignoredUpdateVersions"), ignoredVersions);
+}
+
+core::FilePath Options::scratchTempDir(core::FilePath defaultPath)
+{
+ core::FilePath dir(scratchPath.toUtf8().constData());
+
+ if (!dir.empty() && dir.exists())
+ {
+ dir = dir.childPath("tmp");
+ core::Error error = dir.ensureDirectory();
+ if (!error)
+ return dir;
+ }
+ return defaultPath;
+}
+
+void Options::cleanUpScratchTempDir()
+{
+ core::FilePath temp = scratchTempDir(core::FilePath());
+ if (!temp.empty())
+ temp.removeIfExists();
+}
+
+bool Options::webkitDevTools()
+{
+ return settings_.value(QString::fromAscii("webkitDevTools"), false).toBool();
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopOptions.hpp b/src/cpp/desktop/DesktopOptions.hpp
new file mode 100644
index 0000000..7a4019e
--- /dev/null
+++ b/src/cpp/desktop/DesktopOptions.hpp
@@ -0,0 +1,111 @@
+/*
+ * DesktopOptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_OPTIONS_HPP
+#define DESKTOP_OPTIONS_HPP
+
+#include <boost/noncopyable.hpp>
+
+#include <QDir>
+#include <QMainWindow>
+#include <QSettings>
+#include <QStringList>
+
+#include <core/FilePath.hpp>
+
+#define kRunDiagnosticsOption "--run-diagnostics"
+
+#if defined(__APPLE__)
+#define FORMAT QSettings::NativeFormat
+#else
+#define FORMAT QSettings::IniFormat
+#endif
+
+namespace desktop {
+
+class Options;
+Options& options();
+
+class Options : boost::noncopyable
+{
+public:
+ void initFromCommandLine(const QStringList& arguments);
+
+ void restoreMainWindowBounds(QMainWindow* window);
+ void saveMainWindowBounds(QMainWindow* window);
+ QString portNumber() const;
+ QString newPortNumber();
+ std::string localPeer() const; // derived from portNumber
+
+ QString proportionalFont() const;
+ QString fixedWidthFont() const;
+ void setFixedWidthFont(QString font);
+
+ double zoomLevel() const;
+ void setZoomLevel(double zoomLevel);
+
+#ifdef _WIN32
+ // If "", then use automatic detection
+ QString rBinDir() const;
+ void setRBinDir(QString path);
+
+ bool preferR64() const;
+ void setPreferR64(bool preferR64);
+#endif
+
+ core::FilePath scriptsPath() const;
+ void setScriptsPath(const core::FilePath& scriptsPath);
+
+ core::FilePath executablePath() const;
+ core::FilePath supportingFilePath() const;
+
+ core::FilePath wwwDocsPath() const;
+
+#ifdef _WIN32
+ core::FilePath urlopenerPath() const;
+ core::FilePath rsinversePath() const;
+#endif
+
+ QStringList ignoredUpdateVersions() const;
+ void setIgnoredUpdateVersions(const QStringList& ignoredVersions);
+
+ core::FilePath scratchTempDir(core::FilePath defaultPath=core::FilePath());
+ void cleanUpScratchTempDir();
+
+ bool webkitDevTools();
+
+ bool runDiagnostics() { return runDiagnostics_; }
+
+private:
+ Options() : settings_(FORMAT, QSettings::UserScope,
+ QString::fromAscii("RStudio"),
+ QString::fromAscii("desktop")),
+ runDiagnostics_(false)
+ {
+ }
+ friend Options& options();
+
+ QSettings settings_;
+ core::FilePath scriptsPath_;
+ mutable core::FilePath executablePath_;
+ mutable core::FilePath supportingFilePath_;
+ mutable QString portNumber_;
+ mutable std::string localPeer_;
+ bool runDiagnostics_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_OPTIONS_HPP
diff --git a/src/cpp/desktop/DesktopPosixApplication.cpp b/src/cpp/desktop/DesktopPosixApplication.cpp
new file mode 100644
index 0000000..5179a94
--- /dev/null
+++ b/src/cpp/desktop/DesktopPosixApplication.cpp
@@ -0,0 +1,77 @@
+/*
+ * DesktopPosixApplication.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopPosixApplication.hpp"
+
+#include <core/FilePath.hpp>
+
+#include <QProcess>
+#include <QFileOpenEvent>
+
+#include "DesktopOptions.hpp"
+#include "DesktopUtils.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+bool PosixApplication::event(QEvent* pEvent)
+{
+ switch(pEvent->type())
+ {
+ case QEvent::FileOpen:
+ {
+ // get filename
+ QString filename = static_cast<QFileOpenEvent*>(pEvent)->file();
+
+ if (activationWindow() == NULL)
+ {
+ // if we don't yet have an activation window then this is a startup
+ // request -- save it so DesktopMain can pull it out later
+ startupOpenFileRequest_ = filename;
+ }
+ else
+ {
+ // otherwise we are already running so this is an apple event
+ // targeted at opening a file in an existing instance
+
+ // if this is a project then re-post the request back to
+ // another instance using the command line (this is to
+ // circumvent the fact that the first RStudio application
+ // launched on OSX gets all of the apple events). note that
+ // we don't make this code conditional for __APPLE__ because
+ // we'd need the same logic if other platforms started posting
+ // FileOpen back to existing instances (e.g. via DDE)
+
+ FilePath filePath(filename.toUtf8().constData());
+ if (filePath.exists() && filePath.extensionLowerCase() == ".rproj")
+ {
+ launchProjectInNewInstance(filename);
+ }
+ else
+ {
+ openFileRequest(filename);
+ }
+ }
+
+ return true;
+ }
+
+ default:
+ return QtSingleApplication::event(pEvent);
+ }
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopPosixApplication.hpp b/src/cpp/desktop/DesktopPosixApplication.hpp
new file mode 100644
index 0000000..a4d348b
--- /dev/null
+++ b/src/cpp/desktop/DesktopPosixApplication.hpp
@@ -0,0 +1,54 @@
+/*
+ * DesktopPosixApplication.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_POSIX_APPLICATION_HPP
+#define DESKTOP_POSIX_APPLICATION_HPP
+
+#include "3rdparty/qtsingleapplication/QtSingleApplication"
+
+namespace desktop {
+
+class PosixApplication : public QtSingleApplication
+{
+ Q_OBJECT
+public:
+
+ PosixApplication(QString appName, int& argc, char* argv[])
+ : QtSingleApplication(appName, argc, argv)
+ {
+ setApplicationName(appName);
+ }
+
+ QString startupOpenFileRequest() const
+ {
+ return startupOpenFileRequest_;
+ }
+
+signals:
+ void openFileRequest(QString filename);
+
+protected:
+ virtual bool event(QEvent* pEvent);
+
+private:
+ QString startupOpenFileRequest_;
+
+};
+
+
+
+} // namespace desktop
+
+#endif // DESKTOP_POSIX_APPLICATION_HPP
diff --git a/src/cpp/desktop/DesktopPosixApplicationLaunch.cpp b/src/cpp/desktop/DesktopPosixApplicationLaunch.cpp
new file mode 100644
index 0000000..04edb12
--- /dev/null
+++ b/src/cpp/desktop/DesktopPosixApplicationLaunch.cpp
@@ -0,0 +1,90 @@
+/*
+ * DesktopPosixApplicationLaunch.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopApplicationLaunch.hpp"
+
+#include "DesktopPosixApplication.hpp"
+
+
+namespace desktop {
+
+namespace {
+
+
+PosixApplication* app()
+{
+ return qobject_cast<PosixApplication*>(qApp);
+}
+
+} // anonymous namespace
+
+ApplicationLaunch::ApplicationLaunch() :
+ QWidget(NULL),
+ pMainWindow_(NULL)
+{
+ connect(app(), SIGNAL(messageReceived(QString)),
+ this, SIGNAL(openFileRequest(QString)));
+}
+
+void ApplicationLaunch::init(QString appName,
+ int& argc,
+ char* argv[],
+ boost::scoped_ptr<QApplication>* ppApp,
+ boost::scoped_ptr<ApplicationLaunch>* ppAppLaunch)
+{
+ // Immediately stuffed into scoped_ptr
+ PosixApplication* pSingleApplication = new PosixApplication(appName,
+ argc,
+ argv);
+ pSingleApplication->setApplicationName(appName);
+ ppApp->reset(pSingleApplication);
+
+ ppAppLaunch->reset(new ApplicationLaunch());
+
+ // connect app open file signal to app launch
+ connect(app(), SIGNAL(openFileRequest(QString)),
+ ppAppLaunch->get(), SIGNAL(openFileRequest(QString)));
+}
+
+void ApplicationLaunch::setActivationWindow(QWidget* pWindow)
+{
+ pMainWindow_ = pWindow;
+ app()->setActivationWindow(pWindow, true);
+}
+
+
+void ApplicationLaunch::activateWindow()
+{
+ app()->activateWindow();
+}
+
+void ApplicationLaunch::attemptToRegisterPeer()
+{
+ // side-effect of is running is to try to register ourselves as a peer
+ app()->isRunning();
+}
+
+
+bool ApplicationLaunch::sendMessage(QString filename)
+{
+ return app()->sendMessage(filename);
+}
+
+QString ApplicationLaunch::startupOpenFileRequest() const
+{
+ return app()->startupOpenFileRequest();
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopPosixDetectRHome.cpp b/src/cpp/desktop/DesktopPosixDetectRHome.cpp
new file mode 100644
index 0000000..f5f355b
--- /dev/null
+++ b/src/cpp/desktop/DesktopPosixDetectRHome.cpp
@@ -0,0 +1,91 @@
+/*
+ * DesktopPosixDetectRHome.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopDetectRHome.hpp"
+
+#include <vector>
+
+#include <boost/algorithm/string/trim.hpp>
+
+#include <QtCore>
+#include <QMessageBox>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include <core/r_util/REnvironment.hpp>
+
+#include "DesktopUtils.hpp"
+#include "DesktopOptions.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+namespace {
+
+void showRNotFoundError(const std::string& msg)
+{
+ showMessageBox(QMessageBox::Critical,
+ NULL,
+ QString::fromUtf8("R Not Found"),
+ QString::fromUtf8(msg.c_str()));
+}
+
+} // anonymous namespace
+
+bool prepareEnvironment(Options& options)
+{
+ // check for which R override
+ FilePath rWhichRPath;
+ std::string whichROverride = core::system::getenv("RSTUDIO_WHICH_R");
+ if (!whichROverride.empty())
+ rWhichRPath = FilePath(whichROverride);
+
+ // determine rLdPaths script location
+ FilePath supportingFilePath = options.supportingFilePath();
+ FilePath rLdScriptPath = supportingFilePath.complete("bin/r-ldpath");
+ if (!rLdScriptPath.exists())
+ rLdScriptPath = supportingFilePath.complete("session/r-ldpath");
+
+ // attempt to detect R environment
+ std::string rScriptPath, errMsg;
+ r_util::EnvironmentVars rEnvVars;
+ bool success = r_util::detectREnvironment(rWhichRPath,
+ rLdScriptPath,
+ std::string(),
+ &rScriptPath,
+ &rEnvVars,
+ &errMsg);
+ if (!success)
+ {
+ showRNotFoundError(errMsg);
+ return false;
+ }
+
+ if (desktop::options().runDiagnostics())
+ {
+ std::cout << std::endl << "Using R script: " << rScriptPath
+ << std::endl;
+ }
+
+ // set environment and return true
+ r_util::setREnvironmentVars(rEnvVars);
+ return true;
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopRVersion.cpp b/src/cpp/desktop/DesktopRVersion.cpp
new file mode 100644
index 0000000..df4845e
--- /dev/null
+++ b/src/cpp/desktop/DesktopRVersion.cpp
@@ -0,0 +1,585 @@
+/*
+ * DesktopRVersion.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "DesktopRVersion.hpp"
+
+#include <windows.h>
+
+#include <QtAlgorithms>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/RegistryKey.hpp>
+
+#include "DesktopChooseRHome.hpp"
+
+#ifndef KEY_WOW64_32KEY
+#define KEY_WOW64_32KEY 0x0200
+#endif
+#ifndef KEY_WOW64_64KEY
+#define KEY_WOW64_64KEY 0x0100
+#endif
+
+using namespace core;
+
+namespace desktop {
+
+namespace {
+
+template <typename T>
+T read(std::ifstream& stream)
+{
+ T val;
+ stream.read(reinterpret_cast<char*>(&val), sizeof(val));
+ return val;
+}
+
+DWORD getVersion(QString path)
+{
+ if (!QFile::exists(path))
+ return 0;
+
+ DWORD bytesNeeded = ::GetFileVersionInfoSize(path.toLocal8Bit(), NULL);
+ if (bytesNeeded == 0)
+ return 0;
+
+ std::vector<BYTE> buffer(bytesNeeded);
+ LPVOID pBlock = reinterpret_cast<LPVOID>(&(buffer[0]));
+
+ if (!::GetFileVersionInfo(
+ path.toLocal8Bit(),
+ 0,
+ buffer.size(),
+ pBlock))
+ {
+ return 0;
+ }
+
+ char root[] = "\\";
+ VS_FIXEDFILEINFO* pFixedFileInfo;
+ UINT len;
+ if (!::VerQueryValue(pBlock,
+ root,
+ reinterpret_cast<LPVOID*>(&pFixedFileInfo),
+ &len))
+ {
+ return 0;
+ }
+
+ return pFixedFileInfo->dwFileVersionMS;
+}
+
+bool confirmMinVersion(
+ DWORD version,
+ int major=RSTUDIO_R_MAJOR_VERSION_REQUIRED,
+ int minor=RSTUDIO_R_MINOR_VERSION_REQUIRED+RSTUDIO_R_PATCH_VERSION_REQUIRED)
+{
+ WORD fileMajor = HIWORD(version);
+ if (fileMajor > major)
+ return true;
+ else if (fileMajor < major)
+ return false;
+ return LOWORD(version) >= minor;
+}
+
+Architecture getArch(QString path)
+{
+ // http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
+ using namespace std;
+
+ if (!QFile::exists(path))
+ return ArchNone;
+
+ ifstream stream(path.toLocal8Bit().data(), ifstream::in | ifstream::binary);
+ stream.seekg(0x3C, ios::beg);
+ uint32_t headerOffset = read<uint32_t>(stream);
+ stream.seekg(headerOffset, ios::beg);
+ uint32_t header = read<uint32_t>(stream);
+ if (header != 0x4550)
+ return ArchNone;
+ uint16_t arch = read<uint16_t>(stream);
+
+ if (!stream.good())
+ return ArchNone;
+
+ switch (arch)
+ {
+ case IMAGE_FILE_MACHINE_I386:
+ return ArchX86;
+ case IMAGE_FILE_MACHINE_AMD64:
+ return ArchX64;
+ default:
+ return ArchUnknown;
+ }
+}
+
+} // anonymous namespace
+
+
+// Given an R home directory, add candidates for child bin directories
+// to the given version list. The versions may not be valid.
+void versionsFromRHome(QDir rHome, QList<RVersion>* pResults)
+{
+ QStringList dirs = QStringList() <<
+ QString::fromUtf8("bin") <<
+ QString::fromUtf8("bin/i386") <<
+ QString::fromUtf8("bin/x64");
+ for (int i = 0; i < dirs.size(); i++)
+ {
+ QDir tmp = rHome;
+ if (tmp.cd(dirs.at(i)) && tmp.exists(QString::fromUtf8("R.dll")))
+ *pResults << RVersion(tmp.absolutePath());
+ }
+}
+
+// Given an R bin directory, return our best guess at its home dir.
+// It will try even if the bin dir doesn't exist.
+QString binDirToHomeDir(QString binDir)
+{
+ if (binDir.isEmpty())
+ return QString();
+
+ QDir dir = binDir;
+ if (!dir.isAbsolute())
+ return QString();
+
+ // For R-2.12 and later (bin/i386 and bin/x64)
+ if (dir.dirName() != QString::fromUtf8("bin"))
+ dir.setPath(QDir::cleanPath(dir.filePath(QString::fromAscii(".."))));
+
+ // The parent of the bin dir is the home dir
+ if (dir.dirName() == QString::fromUtf8("bin"))
+ return QDir::cleanPath(dir.filePath(QString::fromAscii("..")));
+
+ return QString();
+}
+
+QList<RVersion> detectVersionsInDir(QString dir)
+{
+ QDir qdir(dir);
+ if (qdir.exists(QString::fromUtf8("R.dll")))
+ return QList<RVersion>() << RVersion(qdir.absolutePath());
+
+ if (qdir.dirName() == QString::fromUtf8("bin"))
+ qdir.setPath(binDirToHomeDir(qdir.absolutePath()));
+
+ QList<RVersion> results;
+ versionsFromRHome(qdir, &results);
+ return results;
+}
+
+// Grovel the given program files dir for R versions. They might not be valid.
+void enumProgramFiles(QString progFiles, QList<RVersion>* pResults)
+{
+ QDir programFiles(progFiles);
+ if (programFiles.isAbsolute() && programFiles.exists())
+ {
+ if (programFiles.cd(QString::fromUtf8("R")))
+ {
+ QStringList rDirs = programFiles.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (int i = 0; i < rDirs.size(); i++)
+ {
+ versionsFromRHome(programFiles.absoluteFilePath(rDirs.at(i)),
+ pResults);
+ }
+ }
+ }
+}
+
+// Grovel all program files dirs for R versions. They might not be valid.
+void enumProgramFiles(QList<RVersion>* pResults)
+{
+ QStringList progFiles;
+ progFiles << QString::fromStdString(system::getenv("ProgramFiles"));
+ progFiles << QString::fromStdString(system::getenv("ProgramW6432"));
+ progFiles << QString::fromStdString(system::getenv("ProgramFiles(x86)"));
+ progFiles.removeAll(QString());
+ progFiles.removeDuplicates();
+
+ for (int i = 0; i < progFiles.size(); i++)
+ enumProgramFiles(progFiles.at(i), pResults);
+}
+
+void enumRegistry(Architecture architecture, QList<RVersion>* pResults)
+{
+ using namespace core::system;
+
+ REGSAM flags;
+ switch (architecture)
+ {
+ case ArchX86:
+ flags = KEY_WOW64_32KEY;
+ break;
+ case ArchX64:
+ flags = KEY_WOW64_64KEY;
+ break;
+ default:
+ return;
+ }
+
+ RegistryKey regKey;
+ Error error = regKey.open(HKEY_LOCAL_MACHINE,
+ "Software\\R-core\\R",
+ KEY_READ | flags);
+ if (error)
+ {
+ if (error.code() != boost::system::errc::no_such_file_or_directory)
+ LOG_ERROR(error);
+ return;
+ }
+
+ std::vector<std::string> keys = regKey.keyNames();
+ for (int i = 0; i < keys.size(); i++)
+ {
+ RegistryKey verKey;
+ error = verKey.open(regKey.handle(),
+ keys.at(i),
+ KEY_READ | flags);
+ if (error)
+ {
+ LOG_ERROR(error);
+ continue;
+ }
+
+ std::string installPath = verKey.getStringValue("InstallPath", "");
+ if (!installPath.empty())
+ versionsFromRHome(QString::fromStdString(installPath), pResults);
+ }
+}
+
+void enumRegistry(QList<RVersion>* pResults)
+{
+ enumRegistry(ArchX86, pResults);
+ if (core::system::isWin64())
+ enumRegistry(ArchX64, pResults);
+}
+
+// Return all valid versions of R we can find, nicely sorted and de-duped.
+// You can explicitly pass in versions that you know about (that may or
+// may not be valid) using the versions param.
+QList<RVersion> allRVersions(QList<RVersion> versions)
+{
+ versionsFromRHome(QString::fromStdString(system::getenv("R_HOME")),
+ &versions);
+ enumRegistry(&versions);
+ enumProgramFiles(&versions);
+
+ // Remove any invalid versions
+ for (int i = 0; i < versions.size(); i++)
+ {
+ if (!versions.at(i).isValid())
+ versions.removeAt(i--);
+ }
+
+ // Sort and de-duplicate
+ qSort(versions);
+ for (int i = 1; i < versions.size(); i++)
+ {
+ if (versions.at(i) == versions.at(i-1))
+ versions.removeAt(i--);
+ }
+
+ return versions;
+}
+
+RVersion detectPreferredFromRegistry(Architecture architecture)
+{
+ using namespace core::system;
+
+ REGSAM flags;
+ switch (architecture)
+ {
+ case ArchX86:
+ flags = KEY_WOW64_32KEY;
+ break;
+ case ArchX64:
+ flags = KEY_WOW64_64KEY;
+ break;
+ default:
+ return RVersion();
+ }
+
+ RegistryKey regKey;
+ Error error = regKey.open(HKEY_LOCAL_MACHINE,
+ "Software\\R-core\\R",
+ KEY_READ | flags);
+ if (error)
+ {
+ if (error.code() != boost::system::errc::no_such_file_or_directory)
+ LOG_ERROR(error);
+ return RVersion();
+ }
+
+ QList<RVersion> versions;
+ versionsFromRHome(
+ QString::fromStdString(regKey.getStringValue("InstallPath", "")),
+ &versions);
+ for (int i = 0; i < versions.size(); i++)
+ {
+ if (versions.at(i).isValid()
+ && versions.at(i).architecture() == architecture)
+ {
+ return versions.at(i);
+ }
+ }
+
+ return RVersion();
+}
+
+RVersion autoDetect(Architecture architecture, bool preferredOnly)
+{
+ RVersion preferred = detectPreferredFromRegistry(architecture);
+ if (preferred.isValid())
+ return preferred;
+ if (preferredOnly)
+ return RVersion();
+
+ QList<RVersion> versions = allRVersions();
+ for (int i = 0; i < versions.size(); i++)
+ if (versions.at(i).architecture() == architecture)
+ return versions.at(i);
+
+ return RVersion();
+}
+
+RVersion autoDetect()
+{
+ Options& options = desktop::options();
+
+ if (options.rBinDir().isNull() && system::isWin64())
+ {
+ // Special case where user has never specified a preference
+ // for R64 vs. R.
+
+ RVersion ver;
+
+ // rBinDir is getting set to null whenever it is empty
+ // (perhaps a change in Qt behavior?). we therefore
+ // need to look for 32bit first when preferR64 is false
+ if (!options.preferR64())
+ {
+ // Preferred R
+ ver = autoDetect(ArchX86, true);
+ if (ver.isValid())
+ return ver;
+ }
+
+ // Preferred R64
+ ver = autoDetect(ArchX64, true);
+ if (ver.isValid())
+ return ver;
+
+ // Preferred R
+ ver = autoDetect(ArchX86, true);
+ if (ver.isValid())
+ return ver;
+
+ // Any R64
+ ver = autoDetect(ArchX64);
+ if (ver.isValid())
+ return ver;
+
+ // Any R
+ ver = autoDetect(ArchX86);
+ if (ver.isValid())
+ return ver;
+
+ return RVersion();
+ }
+ else
+ {
+ return autoDetect(options.preferR64() ? ArchX64 : ArchX86);
+ }
+}
+
+/*
+Looks for a valid R directory in the following places:
+- Value of %R_HOME%
+- Value of HKEY_LOCAL_MACHINE\Software\R-core\R at InstallPath
+ (both 32-bit and 64-bit keys)
+- Values under HKEY_LOCAL_MACHINE\Software\R-core\R\*@InstallPath
+ (both 32-bit and 64-bit keys)
+- Enumerate %ProgramFiles% directory (both 32-bit and 64-bit dirs)
+
+If forceUi is true, we always show the picker dialog.
+Otherwise, we try to do our best to match the user's specified wishes,
+and if we can't succeed then we show the picker dialog.
+*/
+RVersion detectRVersion(bool forceUi, QWidget* parent)
+{
+ Options& options = desktop::options();
+
+ RVersion rVersion(options.rBinDir());
+
+ if (!forceUi)
+ {
+ if (!rVersion.isEmpty())
+ {
+ // User manually chose an R version. Use it if it's valid.
+ if (rVersion.isValid())
+ return rVersion;
+ }
+ else
+ {
+ // User wants us to autodetect (or didn't specify--autodetect
+ // is the default).
+ RVersion autoDetected = autoDetect();
+ if (autoDetected.isValid())
+ return autoDetected;
+ }
+ }
+
+ // Either forceUi was true, xor the manually specified R version is
+ // no longer valid, xor we tried to autodetect and failed.
+ // Now we show the dialog and make the user choose.
+
+ ChooseRHome dialog(allRVersions(QList<RVersion>() << rVersion), parent);
+ dialog.setValue(rVersion, options.preferR64());
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ // Keep in mind this value might be "", if the user indicated
+ // they want to use the system default. The dialog won't let
+ // itself be accepted unless a valid installation is detected.
+ rVersion = dialog.value();
+ options.setRBinDir(rVersion.binDir());
+ if (rVersion.isEmpty())
+ options.setPreferR64(dialog.preferR64());
+
+ // Recurse. The ChooseRHome dialog should've validated that
+ // the values are acceptable, so this recursion will never
+ // go more than one level deep (i.e. this call should never
+ // result in the dialog being shown again).
+ return detectRVersion(false, parent);
+ }
+
+ // We couldn't autodetect a string and the user bailed on the
+ // dialog. No R_HOME is available.
+ return QString();
+}
+
+RVersion::RVersion(QString binDir) :
+ binDir_(binDir),
+ homeDir_(binDirToHomeDir(binDir)),
+ version_(0),
+ arch_(ArchNone)
+{
+ if (!binDir.isEmpty())
+ {
+ QString dllPath = QDir(binDir_).absoluteFilePath(QString::fromUtf8("R.dll"));
+ version_ = getVersion(dllPath);
+ arch_ = getArch(dllPath);
+ }
+}
+
+QString RVersion::binDir() const
+{
+ return binDir_;
+}
+
+QString RVersion::homeDir() const
+{
+ return homeDir_;
+}
+
+QString RVersion::description() const
+{
+ QString result;
+
+ if (core::system::isWin64())
+ {
+ if (architecture() == ArchX64)
+ result.append(QString::fromUtf8("[64-bit] "));
+ else if (architecture() == ArchX86)
+ result.append(QString::fromUtf8("[32-bit] "));
+ }
+
+ result.append(QDir::toNativeSeparators(homeDir_));
+
+ return result;
+}
+
+bool RVersion::isEmpty() const
+{
+ return binDir_.isEmpty();
+}
+
+bool RVersion::isValid() const
+{
+ return validate() == ValidateSuccess;
+}
+
+ValidateResult RVersion::validate() const
+{
+ if (isEmpty() || homeDir_.isEmpty())
+ return ValidateNotFound;
+
+ QDir binDir(binDir_);
+ if (!binDir.exists(QString::fromUtf8("R.dll")))
+ return ValidateNotFound;
+
+ if (!confirmMinVersion(version()))
+ return ValidateVersionTooOld;
+
+ return ValidateSuccess;
+}
+
+quint32 RVersion::version() const
+{
+ return version_;
+}
+
+Architecture RVersion::architecture() const
+{
+ return arch_;
+}
+
+int RVersion::compareTo(const RVersion& other) const
+{
+ int c;
+
+ // First order by version, descending
+ c = -(version() - other.version());
+ if (c != 0)
+ return c;
+
+ // Then by homedir
+ c = QString::compare(homeDir(), other.homeDir(), Qt::CaseInsensitive);
+ if (c != 0)
+ return c;
+
+ // Then put 64-bit first
+ c = -(architecture() - other.architecture());
+ if (c != 0)
+ return c;
+
+ // Then order by bindir
+ c = QString::compare(binDir(), other.binDir(), Qt::CaseInsensitive);
+ if (c != 0)
+ return c;
+
+ return 0;
+}
+
+bool RVersion::operator<(const RVersion& other) const
+{
+ return compareTo(other) < 0;
+}
+
+bool RVersion::operator==(const RVersion& other) const
+{
+ return QString::compare(binDir_, other.binDir_, Qt::CaseInsensitive) == 0;
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopRVersion.hpp b/src/cpp/desktop/DesktopRVersion.hpp
new file mode 100644
index 0000000..c045b62
--- /dev/null
+++ b/src/cpp/desktop/DesktopRVersion.hpp
@@ -0,0 +1,91 @@
+/*
+ * DesktopRVersion.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef DESKTOPRVERSION_HPP
+#define DESKTOPRVERSION_HPP
+
+#include <iostream>
+#include <fstream>
+
+#include <QtCore>
+#include <QWidget>
+
+#include "desktop-config.h"
+
+namespace desktop {
+
+enum Architecture
+{
+ ArchNone = 1,
+ ArchX86 = 2,
+ ArchX64 = 4,
+ ArchUnknown = 0x100
+};
+typedef int Architectures;
+
+enum ValidateResult
+{
+ ValidateSuccess = 1,
+ ValidateNotFound = 2,
+ ValidateBadArchitecture = 4,
+ ValidateVersionTooOld = 8
+};
+
+class RVersion
+{
+public:
+ RVersion(QString binDir=QString());
+
+ QString binDir() const;
+ QString homeDir() const;
+ QString description() const;
+ bool isEmpty() const;
+ bool isValid() const;
+ ValidateResult validate() const;
+ quint32 version() const;
+ Architecture architecture() const;
+ int compareTo(const RVersion& other) const;
+ bool operator<(const RVersion& other) const;
+ bool operator==(const RVersion& other) const;
+
+private:
+ void stat();
+
+ QString binDir_;
+ QString homeDir_;
+ bool loaded_;
+ quint32 version_;
+ Architecture arch_;
+};
+
+QString binDirToHomeDir(QString binDir);
+
+// Detect versions that might be implied by a user-specified dir.
+// The versions that are returned might not be valid.
+QList<RVersion> detectVersionsInDir(QString dir);
+
+// Enumerates all valid versions of R that are detected on the system.
+// The versions parameter can be used to explicitly provide one or more
+// R versions that may not be detected.
+QList<RVersion> allRVersions(QList<RVersion> versions=QList<RVersion>());
+
+RVersion autoDetect(Architecture architecture, bool preferredOnly=false);
+RVersion autoDetect();
+
+RVersion detectRVersion(bool forceUi,
+ QWidget* parent = NULL);
+
+} // namespace desktop
+
+#endif // DESKTOPRVERSION_HPP
diff --git a/src/cpp/desktop/DesktopSatelliteWindow.cpp b/src/cpp/desktop/DesktopSatelliteWindow.cpp
new file mode 100644
index 0000000..2a18c00
--- /dev/null
+++ b/src/cpp/desktop/DesktopSatelliteWindow.cpp
@@ -0,0 +1,72 @@
+/*
+ * DesktopSatelliteWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopSatelliteWindow.hpp"
+
+#include "DesktopGwtCallback.hpp"
+
+namespace desktop {
+
+
+SatelliteWindow::SatelliteWindow(MainWindow* pMainWindow) :
+ GwtWindow(false, true),
+ gwtCallback_(pMainWindow, this)
+{
+ setAttribute(Qt::WA_QuitOnClose, false);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+
+ setWindowIcon(QIcon(QString::fromAscii(":/icons/RStudio.ico")));
+}
+
+void SatelliteWindow::onJavaScriptWindowObjectCleared()
+{
+ webView()->page()->mainFrame()->addToJavaScriptWindowObject(
+ QString::fromAscii("desktop"),
+ &gwtCallback_,
+ QScriptEngine::QtOwnership);
+
+ connect(webView(), SIGNAL(onCloseWindowShortcut()),
+ this, SLOT(onCloseWindowShortcut()));
+}
+
+void SatelliteWindow::onCloseWindowShortcut()
+{
+ close();
+}
+
+void SatelliteWindow::finishLoading(bool ok)
+{
+ BrowserWindow::finishLoading(ok);
+
+ if (ok)
+ avoidMoveCursorIfNecessary();
+}
+
+void SatelliteWindow::closeEvent(QCloseEvent *)
+{
+ webView()->page()->mainFrame()->evaluateJavaScript(QString::fromAscii(
+ "if (window.notifyRStudioSatelliteClosing) "
+ " window.notifyRStudioSatelliteClosing();"));
+}
+
+void SatelliteWindow::onActivated()
+{
+ webView()->page()->mainFrame()->evaluateJavaScript(QString::fromAscii(
+ "if (window.notifyRStudioSatelliteReactivated) "
+ " window.notifyRStudioSatelliteReactivated(null);"));
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSatelliteWindow.hpp b/src/cpp/desktop/DesktopSatelliteWindow.hpp
new file mode 100644
index 0000000..7072f22
--- /dev/null
+++ b/src/cpp/desktop/DesktopSatelliteWindow.hpp
@@ -0,0 +1,63 @@
+/*
+ * DesktopSatelliteWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SATELLITE_WINDOW_HPP
+#define DESKTOP_SATELLITE_WINDOW_HPP
+
+#include <QMainWindow>
+#include <QtWebKit>
+#include "DesktopGwtWindow.hpp"
+
+#include "DesktopGwtCallback.hpp"
+
+namespace desktop {
+
+class MainWindow;
+
+class SatelliteWindow : public GwtWindow
+{
+ Q_OBJECT
+public:
+ SatelliteWindow(MainWindow* pMainWindow);
+
+signals:
+
+public slots:
+
+
+protected slots:
+ void onCloseWindowShortcut();
+ void finishLoading(bool ok);
+ void onJavaScriptWindowObjectCleared();
+
+protected:
+ virtual void closeEvent(QCloseEvent *event);
+
+ virtual QSize printDialogMinimumSize()
+ {
+ return this->size();
+ }
+
+private:
+ virtual void onActivated();
+
+
+private:
+ GwtCallback gwtCallback_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_SATELLITE_WINDOW_HPP
diff --git a/src/cpp/desktop/DesktopSecondaryWindow.cpp b/src/cpp/desktop/DesktopSecondaryWindow.cpp
new file mode 100644
index 0000000..8c53476
--- /dev/null
+++ b/src/cpp/desktop/DesktopSecondaryWindow.cpp
@@ -0,0 +1,97 @@
+/*
+ * DesktopSecondaryWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopSecondaryWindow.hpp"
+#include "DesktopWebView.hpp"
+
+namespace desktop {
+
+namespace {
+
+QIcon icon(const char* name)
+{
+#ifdef Q_WS_MACX
+ static QString suffix(QString::fromAscii("_mac"));
+#else
+ static QString suffix(QString::fromAscii(""));
+#endif
+ return QIcon(QString::fromAscii(":/icons/") + QString::fromAscii(name) + suffix + QString::fromAscii(".png"));
+}
+
+}
+
+SecondaryWindow::SecondaryWindow(QUrl baseUrl, QWidget* pParent) :
+ BrowserWindow(true, true, baseUrl, pParent)
+{
+ setAttribute(Qt::WA_QuitOnClose, false);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+
+#ifdef Q_WS_MACX
+ setIconSize(QSize(26, 22));
+#else
+ setIconSize(QSize(26, 20));
+#endif
+
+ back_ = pToolbar_->addAction(icon("back"), QString::fromUtf8("Back"));
+ back_->setToolTip(QString::fromUtf8("Back"));
+ connect(back_, SIGNAL(triggered()),
+ webView(), SLOT(back()));
+
+ forward_ = pToolbar_->addAction(icon("forward"), QString::fromUtf8("Forward"));
+ forward_->setToolTip(QString::fromUtf8("Forward"));
+ connect(forward_, SIGNAL(triggered()),
+ webView(), SLOT(forward()));
+
+ reload_ = pToolbar_->addAction(icon("reload"), QString::fromUtf8("Reload"));
+ reload_->setToolTip(QString::fromUtf8("Reload"));
+ connect(reload_, SIGNAL(triggered()),
+ webView(), SLOT(reload()));
+
+ print_ = pToolbar_->addAction(icon("print"), QString::fromUtf8("Print"));
+ print_->setToolTip(QString::fromUtf8("Print"));
+ connect(print_, SIGNAL(triggered()),
+ this, SLOT(print()));
+
+ history_ = webView()->history();
+
+ connect(webView(), SIGNAL(loadStarted()),
+ this, SLOT(manageCommandState()));
+ connect(webView(), SIGNAL(urlChanged(QUrl)),
+ this, SLOT(manageCommandState()));
+
+ manageCommandState();
+
+ // Size it (use computed size if it seems sane; otherwise let Qt set it)
+ QSize size = QSize(850, 1100).boundedTo(
+ QApplication::desktop()->availableGeometry().size());
+ if (size.width() > 500 && size.height() > 500)
+ {
+ size.setHeight(size.height()-75);
+ resize(size);
+ }
+}
+
+void SecondaryWindow::print()
+{
+ printRequested(webView()->page()->mainFrame());
+}
+
+void SecondaryWindow::manageCommandState()
+{
+ back_->setEnabled(history_->canGoBack());
+ forward_->setEnabled(history_->canGoForward());
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSecondaryWindow.hpp b/src/cpp/desktop/DesktopSecondaryWindow.hpp
new file mode 100644
index 0000000..f75f247
--- /dev/null
+++ b/src/cpp/desktop/DesktopSecondaryWindow.hpp
@@ -0,0 +1,50 @@
+/*
+ * DesktopSecondaryWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SECONDARY_WINDOW_HPP
+#define DESKTOP_SECONDARY_WINDOW_HPP
+
+#include <QMainWindow>
+#include <QtWebKit>
+#include "DesktopBrowserWindow.hpp"
+
+namespace desktop {
+
+class SecondaryWindow : public BrowserWindow
+{
+ Q_OBJECT
+public:
+ explicit SecondaryWindow(QUrl baseUrl, QWidget* pParent = NULL);
+
+signals:
+
+public slots:
+ void print();
+
+ protected slots:
+ virtual void manageCommandState();
+
+private:
+ QAction* back_;
+ QAction* forward_;
+ QAction* reload_;
+ QAction* print_;
+
+ QWebHistory* history_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_SECONDARY_WINDOW_HPP
diff --git a/src/cpp/desktop/DesktopSessionLauncher.cpp b/src/cpp/desktop/DesktopSessionLauncher.cpp
new file mode 100644
index 0000000..19d2470
--- /dev/null
+++ b/src/cpp/desktop/DesktopSessionLauncher.cpp
@@ -0,0 +1,398 @@
+/*
+ * DesktopSessionLauncher.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopSessionLauncher.hpp"
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+
+#include <core/WaitUtils.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/ParentProcessMonitor.hpp>
+
+#include <QProcess>
+#include <QtNetwork/QTcpSocket>
+
+#include "DesktopUtils.hpp"
+#include "DesktopOptions.hpp"
+#include "DesktopSlotBinders.hpp"
+#include "DesktopGwtCallback.hpp"
+
+#define RUN_DIAGNOSTICS_LOG(message) if (desktop::options().runDiagnostics()) \
+ std::cout << (message) << std::endl;
+
+using namespace core;
+
+namespace desktop {
+
+namespace {
+
+void launchProcess(std::string absPath,
+ QStringList argList,
+ QProcess** ppProc)
+{
+ QProcess* pProcess = new QProcess();
+ if (options().runDiagnostics())
+ pProcess->setProcessChannelMode(QProcess::ForwardedChannels);
+ else
+ pProcess->setProcessChannelMode(QProcess::SeparateChannels);
+ pProcess->start(QString::fromUtf8(absPath.c_str()), argList);
+ *ppProc = pProcess;
+}
+
+FilePath abendLogPath()
+{
+ return desktop::userLogPath().complete("rsession_abort_msg.log");
+}
+
+void logEnvVar(const std::string& name)
+{
+ std::string value = core::system::getenv(name);
+ if (!value.empty())
+ RUN_DIAGNOSTICS_LOG(" " + name + "=" + value);
+}
+
+} // anonymous namespace
+
+
+Error SessionLauncher::launchFirstSession(const QString& filename,
+ ApplicationLaunch* pAppLaunch)
+{
+ // save reference to app launch
+ pAppLaunch_ = pAppLaunch;
+
+ // build a new new launch context
+ QString host, port;
+ QStringList argList;
+ QUrl url;
+ buildLaunchContext(&host, &port, &argList, &url);
+
+ RUN_DIAGNOSTICS_LOG("\nAttempting to launch R session...");
+ logEnvVar("RSTUDIO_WHICH_R");
+ logEnvVar("R_HOME");
+ logEnvVar("R_DOC_DIR");
+ logEnvVar("R_INCLUDE_DIR");
+ logEnvVar("R_SHARE_DIR");
+ logEnvVar("R_LIBS");
+ logEnvVar("R_LIBS_USER");
+ logEnvVar("DYLD_LIBRARY_PATH");
+ logEnvVar("LD_LIBRARY_PATH");
+ logEnvVar("PATH");
+ logEnvVar("HOME");
+ logEnvVar("R_USER");
+
+ // launch the process
+ Error error = launchSession(argList, &pRSessionProcess_);
+ if (error)
+ return error;
+
+ RUN_DIAGNOSTICS_LOG("\nR session launched, "
+ "attempting to connect on port "
+ + port.toStdString() +
+ "...");
+
+ // jcheng 03/16/2011: Due to crashing caused by authenticating
+ // proxies, bypass all proxies from Qt until we can get the problem
+ // completely solved. This is only expected to affect CRAN mirror
+ // selection (which falls back to local mirror list) and update
+ // checking.
+ //NetworkProxyFactory* pProxyFactory = new NetworkProxyFactory();
+ //QNetworkProxyFactory::setApplicationProxyFactory(pProxyFactory);
+
+ pMainWindow_ = new MainWindow(url);
+ pMainWindow_->setSessionLauncher(this);
+ pMainWindow_->setSessionProcess(pRSessionProcess_);
+ pAppLaunch->setActivationWindow(pMainWindow_);
+
+ desktop::options().restoreMainWindowBounds(pMainWindow_);
+
+ RUN_DIAGNOSTICS_LOG("\nConnected to R session, attempting to initialize...\n");
+
+ // one-time workbench intiailized hook for startup file association
+ if (!filename.isNull() && !filename.isEmpty())
+ {
+ StringSlotBinder* filenameBinder = new StringSlotBinder(filename);
+ pMainWindow_->connect(pMainWindow_,
+ SIGNAL(firstWorkbenchInitialized()),
+ filenameBinder,
+ SLOT(trigger()));
+ pMainWindow_->connect(filenameBinder,
+ SIGNAL(triggered(QString)),
+ pMainWindow_,
+ SLOT(openFileInRStudio(QString)));
+ }
+
+ pMainWindow_->connect(pAppLaunch_,
+ SIGNAL(openFileRequest(QString)),
+ pMainWindow_,
+ SLOT(openFileInRStudio(QString)));
+
+ pMainWindow_->connect(pRSessionProcess_,
+ SIGNAL(finished(int,QProcess::ExitStatus)),
+ this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));
+
+
+ // show the window (but don't if we are doing a --run-diagnostics)
+ if (!options().runDiagnostics())
+ {
+ pMainWindow_->show();
+ pAppLaunch->activateWindow();
+ pMainWindow_->loadUrl(url);
+ }
+
+ return Success();
+}
+
+void SessionLauncher::closeAllSatillites()
+{
+ QWidgetList topLevels = QApplication::topLevelWidgets();
+ for (int i = 0; i < topLevels.size(); i++)
+ {
+ QWidget* pWindow = topLevels.at(i);
+ if (pWindow != pMainWindow_)
+ pWindow->close();
+ }
+}
+
+
+void SessionLauncher::onRSessionExited(int, QProcess::ExitStatus)
+{
+ // if this is a verify-installation session then just quit
+ if (options().runDiagnostics())
+ {
+ pMainWindow_->quit();
+ return;
+ }
+
+ int pendingQuit = pMainWindow_->collectPendingQuitRequest();
+
+ // if there was no pending quit set then this is a crash
+ if (pendingQuit == PendingQuitNone)
+ {
+ closeAllSatillites();
+
+ pMainWindow_->evaluateJavaScript(
+ QString::fromAscii("window.desktopHooks.notifyRCrashed()"));
+
+ if (abendLogPath().exists())
+ {
+ showMessageBox(QMessageBox::Critical,
+ pMainWindow_,
+ QString::fromUtf8("RStudio"),
+ launchFailedErrorMessage());
+ }
+ }
+
+ // quit and exit means close the main window
+ else if (pendingQuit == PendingQuitAndExit)
+ {
+ pMainWindow_->quit();
+ }
+
+ // otherwise this is a restart so we need to launch the next session
+ else
+ {
+ // close all satellite windows if we are reloading
+ bool reload = (pendingQuit == PendingQuitRestartAndReload);
+ if (reload)
+ closeAllSatillites();
+
+ // launch next session
+ Error error = launchNextSession(reload);
+ if (error)
+ {
+ LOG_ERROR(error);
+
+ showMessageBox(QMessageBox::Critical,
+ pMainWindow_,
+ QString::fromUtf8("RStudio"),
+ launchFailedErrorMessage());
+
+ pMainWindow_->quit();
+ }
+ }
+}
+
+Error SessionLauncher::launchNextSession(bool reload)
+{
+ // disconnect the firstWorkbenchInitialized event so it doesn't occur
+ // again when we launch the next session
+ pMainWindow_->disconnect(SIGNAL(firstWorkbenchInitialized()));
+
+ // delete the old process object
+ pMainWindow_->setSessionProcess(NULL);
+ if (pRSessionProcess_)
+ {
+ delete pRSessionProcess_;
+ pRSessionProcess_ = NULL;
+ }
+
+ // build a new launch context -- re-use the same port if we aren't reloading
+ QString port = !reload ? options().portNumber() : QString::fromAscii("");
+ QString host;
+ QStringList argList;
+ QUrl url;
+ buildLaunchContext(&host, &port, &argList, &url);
+
+ // launch the process
+ Error error = launchSession(argList, &pRSessionProcess_);
+ if (error)
+ return error;
+
+ // update the main window's reference to the process object
+ pMainWindow_->setSessionProcess(pRSessionProcess_);
+
+ // connect to quit event
+ pMainWindow_->connect(pRSessionProcess_,
+ SIGNAL(finished(int,QProcess::ExitStatus)),
+ this, SLOT(onRSessionExited(int,QProcess::ExitStatus)));
+
+ if (reload)
+ {
+ // load url -- use a delay because on occation we've seen the
+ // mac client crash during switching of projects and this could
+ // be some type of timing related issue
+ nextSessionUrl_ = url;
+ QTimer::singleShot(100, this, SLOT(onReloadFrameForNextSession()));
+ }
+
+ return Success();
+}
+
+void SessionLauncher::onReloadFrameForNextSession()
+{
+ pMainWindow_->loadUrl(nextSessionUrl_);
+ nextSessionUrl_.clear();
+}
+
+
+
+Error SessionLauncher::launchSession(const QStringList& argList,
+ QProcess** ppRSessionProcess)
+{
+ // always remove the abend log path before launching
+ Error error = abendLogPath().removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ return parent_process_monitor::wrapFork(
+ boost::bind(launchProcess,
+ sessionPath_.absolutePath(),
+ argList,
+ ppRSessionProcess));
+}
+
+QString SessionLauncher::collectAbendLogMessage() const
+{
+ std::string contents;
+ FilePath abendLog = abendLogPath();
+ if (abendLog.exists())
+ {
+ Error error = core::readStringFromFile(abendLog, &contents);
+ if (error)
+ LOG_ERROR(error);
+
+ error = abendLog.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return QString::fromStdString(contents);
+}
+
+QString SessionLauncher::launchFailedErrorMessage() const
+{
+ QString errMsg = QString::fromUtf8("The R session had a fatal error.");
+
+ // check for abend log
+ QString abendLogMessage = collectAbendLogMessage();
+
+ // check for R version mismatch
+ if (abendLogMessage.contains(
+ QString::fromUtf8("arguments passed to .Internal")))
+ {
+ errMsg.append(QString::fromUtf8("\n\nThis error was very likely caused "
+ "by R attempting to load packages from a different "
+ "incompatible version of R on your system. Please remove "
+ "other versions of R and/or remove environment variables "
+ "that reference libraries from other versions of R "
+ "before proceeding."));
+ }
+
+ if (!abendLogMessage.isEmpty())
+ errMsg.append(QString::fromAscii("\n\n").append(abendLogMessage));
+
+ // check for stderr
+ if (pRSessionProcess_)
+ {
+ QString errmsgs = QString::fromLocal8Bit(
+ pRSessionProcess_->readAllStandardError());
+ if (errmsgs.size())
+ {
+ errMsg = errMsg.append(
+ QString::fromAscii("\n\n")).append(errmsgs);
+ }
+ }
+
+ return errMsg;
+}
+
+
+void SessionLauncher::cleanupAtExit()
+{
+ if (pMainWindow_)
+ desktop::options().saveMainWindowBounds(pMainWindow_);
+}
+
+
+void SessionLauncher::buildLaunchContext(QString* pHost,
+ QString* pPort,
+ QStringList* pArgList,
+ QUrl* pUrl) const
+{
+ *pHost = QString::fromAscii("127.0.0.1");
+ if (pPort->isEmpty())
+ *pPort = desktop::options().newPortNumber();
+ *pUrl = QUrl(QString::fromAscii("http://") + *pHost +
+ QString::fromAscii(":") + *pPort + QString::fromAscii("/"));
+
+ if (!confPath_.empty())
+ {
+ *pArgList << QString::fromAscii("--config-file") <<
+ QString::fromUtf8(confPath_.absolutePath().c_str());
+ }
+ else
+ {
+ // explicitly pass "none" so that rsession doesn't read an
+ // /etc/rstudio/rsession.conf file which may be sitting around
+ // from a previous configuratin or install
+ *pArgList << QString::fromAscii("--config-file") <<
+ QString::fromAscii("none");
+ }
+
+ *pArgList << QString::fromAscii("--program-mode") <<
+ QString::fromAscii("desktop");
+
+ *pArgList << QString::fromAscii("--www-port") << *pPort;
+
+ if (options().runDiagnostics())
+ *pArgList << QString::fromAscii("--verify-installation") <<
+ QString::fromAscii("1");
+}
+
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSessionLauncher.hpp b/src/cpp/desktop/DesktopSessionLauncher.hpp
new file mode 100644
index 0000000..82001b9
--- /dev/null
+++ b/src/cpp/desktop/DesktopSessionLauncher.hpp
@@ -0,0 +1,84 @@
+/*
+ * DesktopSessionLauncher.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SESSION_LAUNCHER_HPP
+#define DESKTOP_SESSION_LAUNCHER_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include "DesktopApplicationLaunch.hpp"
+#include "DesktopMainWindow.hpp"
+
+namespace desktop {
+
+class SessionLauncher : public QObject
+{
+ Q_OBJECT
+public:
+ SessionLauncher(const core::FilePath& sessionPath,
+ const core::FilePath& confPath)
+ : confPath_(confPath),
+ sessionPath_(sessionPath),
+ pAppLaunch_(NULL),
+ pMainWindow_(NULL),
+ pRSessionProcess_(NULL)
+ {
+ }
+
+ core::Error launchFirstSession(const QString& filename,
+ ApplicationLaunch* pAppLaunch);
+
+ core::Error launchNextSession(bool reload);
+
+ QString launchFailedErrorMessage() const;
+
+ void cleanupAtExit();
+
+public slots:
+ void onRSessionExited(int exitCode, QProcess::ExitStatus exitStatus);
+ void onReloadFrameForNextSession();
+
+private:
+
+ QString collectAbendLogMessage() const;
+
+ void closeAllSatillites();
+
+ core::Error launchSession(const QStringList& argList,
+ QProcess** ppRSessionProcess);
+
+ void buildLaunchContext(QString* pHost,
+ QString* pPort,
+ QStringList* pArgList,
+ QUrl* pUrl) const;
+
+
+private:
+ core::FilePath confPath_;
+ core::FilePath sessionPath_;
+ ApplicationLaunch* pAppLaunch_;
+ MainWindow* pMainWindow_;
+ QProcess* pRSessionProcess_;
+ QUrl nextSessionUrl_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_SESSION_LAUNCHER_HPP
diff --git a/src/cpp/desktop/DesktopSlotBinders.cpp b/src/cpp/desktop/DesktopSlotBinders.cpp
new file mode 100644
index 0000000..7e78b63
--- /dev/null
+++ b/src/cpp/desktop/DesktopSlotBinders.cpp
@@ -0,0 +1,32 @@
+/*
+ * DesktopSlotBinders.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopSlotBinders.hpp"
+
+#include <QtCore>
+
+namespace desktop {
+
+StringSlotBinder::StringSlotBinder(QString arg, QObject *parent) :
+ QObject(parent), arg_(arg)
+{
+}
+
+void StringSlotBinder::trigger()
+{
+ triggered(arg_);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSlotBinders.hpp b/src/cpp/desktop/DesktopSlotBinders.hpp
new file mode 100644
index 0000000..5e9113f
--- /dev/null
+++ b/src/cpp/desktop/DesktopSlotBinders.hpp
@@ -0,0 +1,67 @@
+/*
+ * DesktopSlotBinders.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_STRING_SLOT_BINDER_HPP
+#define DESKTOP_STRING_SLOT_BINDER_HPP
+
+#include <QObject>
+#include <QWidget>
+
+#include <boost/function.hpp>
+
+namespace desktop {
+
+class StringSlotBinder : public QObject
+{
+ Q_OBJECT
+public:
+ explicit StringSlotBinder(QString arg,
+ QObject *parent = 0);
+
+signals:
+ void triggered(QString arg);
+
+public slots:
+ void trigger();
+
+private:
+ QString arg_;
+};
+
+// Makes an arbitrary function available as a slot
+class FunctionSlotBinder : public QObject
+{
+ Q_OBJECT
+public:
+ explicit FunctionSlotBinder(boost::function<void()> func,
+ QObject* parent = 0) :
+ QObject(parent),
+ func_(func)
+ {
+ }
+
+public slots:
+ void execute()
+ {
+ func_();
+ }
+
+private:
+ boost::function<void()> func_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_STRING_SLOT_BINDER_HPP
diff --git a/src/cpp/desktop/DesktopSubMenu.cpp b/src/cpp/desktop/DesktopSubMenu.cpp
new file mode 100644
index 0000000..31ca074
--- /dev/null
+++ b/src/cpp/desktop/DesktopSubMenu.cpp
@@ -0,0 +1,79 @@
+/*
+ * DesktopSubMenu.cpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <QAction>
+#include <QDebug>
+#include <QList>
+
+#include "DesktopSubMenu.hpp"
+#include "DesktopCommandInvoker.hpp"
+
+namespace desktop {
+
+SubMenu::SubMenu(const QString& title, QWidget* parent):
+ QMenu(title, parent)
+{
+ connect(this, SIGNAL(aboutToShow()),
+ this, SLOT(onAboutToShow()));
+}
+
+// This algorithm checks each action in the menu to see whether it is a
+// submenu that contains only invisible commands; if so, it hides the submenu.
+void SubMenu::onAboutToShow()
+{
+ QList<QAction*> actionList = actions();
+ for (QList<QAction*>::const_iterator pAction = actionList.begin();
+ pAction != actionList.end();
+ pAction++)
+ {
+ QMenu* menu = (*pAction)->menu();
+ if (menu != NULL)
+ {
+ // Found a submenu; presume that it needs to be hidden until we
+ // discover either a non-command or a visible command
+ bool hide = true;
+ QList<QAction*> subActionList = menu->actions();
+ for (QList<QAction*>::const_iterator pSubAction = subActionList.begin();
+ pSubAction != subActionList.end();
+ pSubAction++)
+ {
+ QAction* subAction = *pSubAction;
+
+ // Ignore separators
+ if (subAction->isSeparator())
+ continue;
+
+ // If it's not a command or a separator, stop checking this menu
+ QString cmdId = subAction->data().toString();
+ if (cmdId.length() == 0)
+ {
+ hide = false;
+ break;
+ }
+
+ // It's a command, check visibility state
+ manageCommandVisibility(cmdId, subAction);
+ if (subAction->isVisible())
+ {
+ hide = false;
+ break;
+ }
+ }
+ (*pAction)->setVisible(!hide);
+ }
+ }
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSubMenu.hpp b/src/cpp/desktop/DesktopSubMenu.hpp
new file mode 100644
index 0000000..3e8d712
--- /dev/null
+++ b/src/cpp/desktop/DesktopSubMenu.hpp
@@ -0,0 +1,42 @@
+/*
+ * DesktopSubMenu.hpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SUBMENU_HPP
+#define DESKTOP_SUBMENU_HPP
+
+#include <QObject>
+#include <QHash>
+#include <QList>
+#include <QMenu>
+#include <QKeyEvent>
+
+namespace desktop {
+
+class SubMenu : public QMenu
+{
+ Q_OBJECT
+public:
+ SubMenu(const QString& title, QWidget* parent = 0);
+
+signals:
+ void manageCommandVisibility(QString,QAction*);
+
+protected slots:
+ void onAboutToShow();
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_SUBMENU_HPP
diff --git a/src/cpp/desktop/DesktopSynctex.cpp b/src/cpp/desktop/DesktopSynctex.cpp
new file mode 100644
index 0000000..bcb6d5a
--- /dev/null
+++ b/src/cpp/desktop/DesktopSynctex.cpp
@@ -0,0 +1,175 @@
+/*
+ * DesktopSynctex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopSynctex.hpp"
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/Error.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/Process.hpp>
+
+#include "DesktopUtils.hpp"
+
+// per-platform synctex implemetnations
+#if defined(Q_WS_MACX)
+
+#elif defined(Q_OS_WIN)
+#include "synctex/sumatra/SumatraSynctex.hpp"
+#elif defined(Q_OS_LINUX)
+#include "synctex/evince/EvinceSynctex.hpp"
+#endif
+
+using namespace core;
+
+namespace desktop {
+
+namespace {
+
+#if defined(Q_OS_WIN)
+
+SynctexViewerInfo discoverViewer()
+{
+ SynctexViewerInfo sv;
+ sv.name = QString::fromAscii("Sumatra");
+ sv.versionMajor = 2;
+ sv.versionMinor = 0;
+ sv.versionPatch = 1;
+ return sv;
+}
+
+#elif defined(Q_OS_LINUX)
+
+SynctexViewerInfo discoverViewer()
+{
+ // probe for evince version
+ core::system::ProcessResult result;
+ Error error = core::system::runCommand("evince --version",
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return SynctexViewerInfo();
+ }
+ else if (result.exitStatus != EXIT_SUCCESS)
+ {
+ return SynctexViewerInfo();
+ }
+
+ // trim output
+ std::string stdOut = boost::algorithm::trim_copy(result.stdOut);
+
+ // extract version
+ boost::smatch match;
+ boost::regex re("^.*(\\d+)\\.(\\d+)\\.(\\d)+$");
+ if (boost::regex_match(stdOut, match, re))
+ {
+ SynctexViewerInfo sv;
+ sv.name = QString::fromAscii("Evince");
+ sv.versionMajor = safe_convert::stringTo<int>(match[1], 2);
+ sv.versionMinor = safe_convert::stringTo<int>(match[2], 0);
+ sv.versionPatch = safe_convert::stringTo<int>(match[3], 0);
+
+ // the synctex dBus interface changed to include a timestamp parameter
+ // in evince 2.91.3 -- we therefore require this version or greater
+ // in order to work with evince
+ if (sv.version() >= QT_VERSION_CHECK(2, 91, 3))
+ {
+ return sv;
+ }
+ else
+ {
+ return SynctexViewerInfo();
+ }
+ }
+ else
+ {
+ return SynctexViewerInfo();
+ }
+}
+
+#else
+
+SynctexViewerInfo discoverViewer()
+{
+ return SynctexViewerInfo();
+}
+
+#endif
+
+// static cache for discoverd desktop viewer
+SynctexViewerInfo s_viewer;
+
+} // anonymous namespace
+
+SynctexViewerInfo Synctex::desktopViewerInfo()
+{
+ if (s_viewer.empty())
+ s_viewer = discoverViewer();
+
+ return s_viewer;
+}
+
+Synctex* Synctex::create(MainWindow* pMainWindow)
+{
+ // per-platform synctex implemetnations
+#if defined(Q_WS_MACX)
+ return new Synctex(pMainWindow);
+#elif defined(Q_OS_WIN)
+ return new synctex::SumatraSynctex(pMainWindow);
+#elif defined(Q_OS_LINUX)
+ return new synctex::EvinceSynctex(desktopViewerInfo(), pMainWindow);
+#else
+ return new Synctex(pMainWindow);
+#endif
+}
+
+void Synctex::onClosed(const QString& pdfFile)
+{
+ // don't notify the main window that the viewer closed -- this mechanism
+ // was originallly designed for the RStudio Viewer because after it closes
+ // there is no way for synctex to work. With desktop viewers there are
+ // two other considerations:
+ //
+ // (1) there might be multiple viewers active and we don't want to
+ // disable the command for the other viewers
+ //
+ // (2) they can actually bring back the pdf viewer to do the sync
+ // without triggering a compile
+ //
+ // if we want to take this all they way we could:
+ //
+ // - Enable/disable the forward search on a per-editor basis (note
+ // below that we pass the PDF file to the onClosed handler so
+ // we have the context to do this
+ //
+ // - Allow the RStudio Viewer to be opened without kicking off a compile
+ //
+
+ /*
+ pMainWindow_->onPdfViewerClosed(pdfFile);
+ */
+}
+
+void Synctex::onSyncSource(const QString &srcFile, const QPoint &srcLoc)
+{
+ desktop::raiseAndActivateWindow(pMainWindow_);
+
+ pMainWindow_->onPdfViewerSyncSource(srcFile, srcLoc.x(), srcLoc.y());
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopSynctex.hpp b/src/cpp/desktop/DesktopSynctex.hpp
new file mode 100644
index 0000000..b6ea242
--- /dev/null
+++ b/src/cpp/desktop/DesktopSynctex.hpp
@@ -0,0 +1,91 @@
+/*
+ * DesktopSynctex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SYNCTEX_HPP
+#define DESKTOP_SYNCTEX_HPP
+
+#include <QObject>
+
+#include "DesktopMainWindow.hpp"
+namespace desktop {
+
+struct SynctexViewerInfo
+{
+ SynctexViewerInfo()
+ : versionMajor(0), versionMinor(0), versionPatch(0)
+ {
+ }
+
+ QString name;
+
+ bool empty() const { return name.isEmpty(); }
+
+ int version() const
+ {
+ return QT_VERSION_CHECK(versionMajor, versionMinor, versionPatch);
+ }
+
+ int versionMajor;
+ int versionMinor;
+ int versionPatch;
+};
+
+
+class Synctex : public QObject
+{
+public:
+ // return the desktop viewer if there is one for this platform/environment
+ static SynctexViewerInfo desktopViewerInfo();
+
+ static Synctex* create(MainWindow* pMainWindow);
+
+ Q_OBJECT
+public:
+ explicit Synctex(MainWindow* pMainWindow)
+ : QObject(pMainWindow), pMainWindow_(pMainWindow)
+ {
+ }
+
+ // the base Synctex class does nothing -- subclasses provide an
+ // implementation that does something by overriding syncView and
+ // calling onClosed and onSyncSource at the appropriate times
+
+ virtual void syncView(const QString& pdfFile,
+ const QString& srcFile,
+ const QPoint& srcLoc)
+ {
+ }
+
+ virtual void syncView(const QString& pdfFile, int pdfPage)
+ {
+ }
+
+protected:
+ WId mainWindowId() const { return pMainWindow_->effectiveWinId(); }
+
+protected:
+ void onClosed(const QString& pdfFile);
+ void onSyncSource(const QString& srcFile, const QPoint& sourceLoc);
+
+public slots:
+
+
+private:
+ MainWindow* pMainWindow_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_SYNCTEX_HPP
diff --git a/src/cpp/desktop/DesktopURLDownloader.cpp b/src/cpp/desktop/DesktopURLDownloader.cpp
new file mode 100644
index 0000000..f39fc9b
--- /dev/null
+++ b/src/cpp/desktop/DesktopURLDownloader.cpp
@@ -0,0 +1,92 @@
+/*
+ * DesktopURLDownloader.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopURLDownloader.hpp"
+
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+
+namespace desktop {
+
+
+URLDownloader::URLDownloader(const QUrl& url,
+ int timeoutMs,
+ bool manuallyInvoked,
+ QObject* pParent)
+ : QObject(pParent),
+ pRequestTimeoutTimer_(new QTimer(this)),
+ manuallyInvoked_(manuallyInvoked)
+{
+ QNetworkAccessManager* pNetman = new QNetworkAccessManager(this);
+ pReply_ = pNetman->get(QNetworkRequest(url));
+ connect(pReply_, SIGNAL(finished()),
+ this, SLOT(complete()));
+ connect(pReply_, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(error(QNetworkReply::NetworkError)));
+
+ if (timeoutMs != -1)
+ {
+ pRequestTimeoutTimer_->setInterval(timeoutMs);
+ pRequestTimeoutTimer_->setSingleShot(true);
+ connect(pRequestTimeoutTimer_, SIGNAL(timeout()),
+ this, SLOT(timeout()));
+ pRequestTimeoutTimer_->start();
+ }
+}
+
+void URLDownloader::complete()
+{
+ if (pRequestTimeoutTimer_->isActive())
+ pRequestTimeoutTimer_->stop();
+
+ QNetworkReply* pReply = qobject_cast<QNetworkReply*>(sender());
+
+ if (pReply->error() == QNetworkReply::NoError)
+ {
+ QByteArray data = pReply->readAll();
+ downloadComplete(data);
+ }
+
+ deleteLater();
+}
+
+void URLDownloader::error(QNetworkReply::NetworkError error)
+{
+ // ignore cancelled error (since it came from timeout)
+ if (error == QNetworkReply::OperationCanceledError)
+ return;
+
+ if (pRequestTimeoutTimer_->isActive())
+ pRequestTimeoutTimer_->stop();
+
+ QNetworkReply* pReply = qobject_cast<QNetworkReply*>(sender());
+
+ downloadError(pReply->errorString());
+
+ deleteLater();
+}
+
+void URLDownloader::timeout()
+{
+ pReply_->abort();
+
+ downloadTimeout();
+
+ deleteLater();
+}
+
+
+}
diff --git a/src/cpp/desktop/DesktopURLDownloader.hpp b/src/cpp/desktop/DesktopURLDownloader.hpp
new file mode 100644
index 0000000..cc6cbed
--- /dev/null
+++ b/src/cpp/desktop/DesktopURLDownloader.hpp
@@ -0,0 +1,60 @@
+/*
+ * DesktopURLDownloader.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_URL_DOWNLOADER_HPP
+#define DESKTOP_URL_DOWNLOADER_HPP
+
+#include <string>
+#include <vector>
+
+#include <QUrl>
+#include <QNetworkReply>
+#include <QTimer>
+
+namespace desktop {
+
+// URLDownloader is self-freeing
+class URLDownloader : public QObject
+{
+ Q_OBJECT
+
+ public:
+ URLDownloader(const QUrl& url,
+ int timeoutMs,
+ bool manuallyInvoked,
+ QObject* pParent);
+ ~URLDownloader() {}
+
+ bool manuallyInvoked() { return manuallyInvoked_; }
+
+ signals:
+ void downloadComplete(const QByteArray& data);
+ void downloadError(const QString& message);
+ void downloadTimeout();
+
+ protected slots:
+ void complete();
+ void error(QNetworkReply::NetworkError);
+ void timeout();
+
+private:
+ QNetworkReply* pReply_;
+ QTimer* pRequestTimeoutTimer_;
+ bool manuallyInvoked_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_URL_DOWNLOADER_HPP
diff --git a/src/cpp/desktop/DesktopUtils.cpp b/src/cpp/desktop/DesktopUtils.cpp
new file mode 100644
index 0000000..7ca5182
--- /dev/null
+++ b/src/cpp/desktop/DesktopUtils.cpp
@@ -0,0 +1,283 @@
+/*
+ * DesktopUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopUtils.hpp"
+
+#include <QProcess>
+#include <QPushButton>
+#include <QDesktopServices>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/System.hpp>
+
+#include "DesktopOptions.hpp"
+
+#ifdef Q_WS_WIN
+#include <windows.h>
+#endif
+
+using namespace core;
+
+namespace desktop {
+
+#ifdef Q_WS_WIN
+
+void reattachConsoleIfNecessary()
+{
+ if (::AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ freopen("CONOUT$","wb",stdout);
+ freopen("CONOUT$","wb",stderr);
+ freopen("CONIN$","rb",stdin);
+ std::ios::sync_with_stdio();
+ }
+}
+
+#else
+
+void reattachConsoleIfNecessary()
+{
+
+}
+
+#endif
+
+// NOTE: this code is duplicated in diagnostics as well (and also in
+// SessionOptions.hpp although the code path isn't exactly the same)
+FilePath userLogPath()
+{
+ FilePath userHomePath = core::system::userHomePath("R_USER|HOME");
+ FilePath logPath = core::system::userSettingsPath(
+ userHomePath,
+ "RStudio-Desktop").childPath("log");
+ return logPath;
+}
+
+#ifndef Q_OS_MAC
+bool isRetina(QMainWindow* pMainWindow)
+{
+ return false;
+}
+
+bool isOSXMavericks()
+{
+ return false;
+}
+
+void enableFullscreenMode(QMainWindow* pMainWindow, bool primary)
+{
+
+}
+
+void toggleFullscreenMode(QMainWindow* pMainWindow)
+{
+
+}
+
+bool supportsFullscreenMode(QMainWindow* pMainWindow)
+{
+ return false;
+}
+
+void initializeLang()
+{
+}
+
+#endif
+
+void raiseAndActivateWindow(QWidget* pWindow)
+{
+ // WId wid = pWindow->effectiveWinId(); -- gets X11 window id
+ // gtk_window_present_with_time(GTK_WINDOW, timestamp)
+
+ if (pWindow->isMinimized())
+ {
+ pWindow->setWindowState(
+ pWindow->windowState() & ~Qt::WindowMinimized);
+ }
+
+ pWindow->raise();
+ pWindow->activateWindow();
+}
+
+QMessageBox::Icon safeMessageBoxIcon(QMessageBox::Icon icon)
+{
+ // if a gtk theme has a missing or corrupt icon for one of the stock
+ // dialog images, qt crashes when attempting to show the dialog
+#ifdef Q_WS_X11
+ return QMessageBox::NoIcon;
+#else
+ return icon;
+#endif
+}
+
+
+bool showYesNoDialog(QMessageBox::Icon icon,
+ QWidget *parent,
+ const QString &title,
+ const QString& text)
+{
+ // basic message box attributes
+ QMessageBox messageBox(safeMessageBoxIcon(icon),
+ title,
+ text,
+ QMessageBox::NoButton,
+ parent);
+ messageBox.setWindowModality(Qt::WindowModal);
+
+ // initialize buttons
+ QPushButton* pYes = new QPushButton(QString::fromUtf8("Yes"));
+ messageBox.addButton(pYes, QMessageBox::YesRole);
+ messageBox.addButton(new QPushButton(QString::fromUtf8("No")), QMessageBox::NoRole);
+ messageBox.setDefaultButton(pYes);
+
+ // show the dialog modally
+ messageBox.exec();
+
+ // return true if the user clicked yes
+ return messageBox.clickedButton() == pYes;
+}
+
+void showMessageBox(QMessageBox::Icon icon,
+ QWidget *parent,
+ const QString &title,
+ const QString& text)
+{
+ // basic message box attributes
+ QMessageBox messageBox(safeMessageBoxIcon(icon),
+ title,
+ text,
+ QMessageBox::NoButton,
+ parent);
+ messageBox.setWindowModality(Qt::WindowModal);
+ messageBox.addButton(new QPushButton(QString::fromUtf8("OK")), QMessageBox::AcceptRole);
+ messageBox.exec();
+}
+
+void showWarning(QWidget *parent, const QString &title, const QString& text)
+{
+ showMessageBox(QMessageBox::Warning, parent, title, text);
+}
+
+void showInfo(QWidget* parent, const QString& title, const QString& text)
+{
+ showMessageBox(QMessageBox::Information, parent, title, text);
+}
+
+void showFileError(const QString& action,
+ const QString& file,
+ const QString& error)
+{
+ QString msg = QString::fromUtf8("Error ") + action +
+ QString::fromUtf8(" ") + file +
+ QString::fromUtf8(" - ") + error;
+ showMessageBox(QMessageBox::Critical,
+ NULL,
+ QString::fromUtf8("File Error"),
+ msg);
+}
+
+void launchProjectInNewInstance(QString projectFilename)
+{
+ // launch the new instance
+ QStringList args;
+ args.append(projectFilename);
+ QString exePath = QString::fromUtf8(
+ desktop::options().executablePath().absolutePath().c_str());
+ QProcess::startDetached(exePath, args);
+}
+
+
+bool isFixedWidthFont(const QFont& font)
+{
+ QFontMetrics metrics(font);
+ int width = metrics.width(QChar::fromAscii(' '));
+ char chars[] = {'m', 'i', 'A', '/', '-', '1', 'l', '!', 'x', 'X', 'y', 'Y'};
+ for (size_t i = 0; i < sizeof(chars); i++)
+ {
+ if (metrics.width(QChar::fromAscii(chars[i])) != width)
+ return false;
+ }
+ return true;
+}
+
+#ifdef _WIN32
+
+// on Win32 open urls using our special urlopener.exe -- this is
+// so that the shell exec is made out from under our windows "job"
+void openUrl(const QUrl& url)
+{
+ // we allow default handling for mailto and file schemes because qt
+ // does custom handling for them and they aren't affected by the chrome
+ //job object issue noted above
+ if (url.scheme() == QString::fromAscii("mailto") ||
+ url.scheme() == QString::fromAscii("file"))
+ {
+ QDesktopServices::openUrl(url);
+ }
+ else
+ {
+ core::system::ProcessOptions options;
+ options.breakawayFromJob = true;
+ options.detachProcess = true;
+
+ std::vector<std::string> args;
+ args.push_back(url.toString().toStdString());
+
+ core::system::ProcessResult result;
+ Error error = core::system::runProgram(
+ desktop::options().urlopenerPath().absolutePath(),
+ args,
+ "",
+ options,
+ &result);
+
+ if (error)
+ LOG_ERROR(error);
+ else if (result.exitStatus != EXIT_SUCCESS)
+ LOG_ERROR_MESSAGE(result.stdErr);
+ }
+}
+
+// Qt 4.8.3 on Win7 (32-bit) has problems with opening the ~ directory
+// (it attempts to navigate to the "Documents library" and then hangs)
+// So we use the Qt file dialog implementations when we are running
+// on Win32
+QFileDialog::Options standardFileDialogOptions()
+{
+ bool isWindowsXP = QSysInfo::windowsVersion() == QSysInfo::WV_XP;
+ if (isWindowsXP || core::system::isWin64())
+ return 0;
+ else
+ return QFileDialog::DontUseNativeDialog;
+}
+
+#else
+
+void openUrl(const QUrl& url)
+{
+ QDesktopServices::openUrl(url);
+}
+
+QFileDialog::Options standardFileDialogOptions()
+{
+ return 0;
+}
+
+#endif
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopUtils.hpp b/src/cpp/desktop/DesktopUtils.hpp
new file mode 100644
index 0000000..c47080c
--- /dev/null
+++ b/src/cpp/desktop/DesktopUtils.hpp
@@ -0,0 +1,76 @@
+/*
+ * DesktopUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_UTILS_HPP
+#define DESKTOP_UTILS_HPP
+
+#include <QUrl>
+#include <QMessageBox>
+#include <QMainWindow>
+#include <QFileDialog>
+
+namespace core {
+ class FilePath;
+}
+
+namespace desktop {
+
+void reattachConsoleIfNecessary();
+
+core::FilePath userLogPath();
+
+bool isRetina(QMainWindow* pMainWindow);
+
+bool isOSXMavericks();
+
+void raiseAndActivateWindow(QWidget* pWindow);
+
+QMessageBox::Icon safeMessageBoxIcon(QMessageBox::Icon icon);
+
+bool showYesNoDialog(QMessageBox::Icon icon,
+ QWidget *parent,
+ const QString &title,
+ const QString& text);
+
+void showMessageBox(QMessageBox::Icon icon,
+ QWidget *parent,
+ const QString &title,
+ const QString& text);
+
+void showWarning(QWidget *parent, const QString &title, const QString& text);
+
+void showInfo(QWidget* parent, const QString& title, const QString& text);
+
+void showFileError(const QString& action,
+ const QString& file,
+ const QString& error);
+
+void launchProjectInNewInstance(QString projectFilename);
+
+bool isFixedWidthFont(const QFont& font);
+
+void openUrl(const QUrl& url);
+
+void enableFullscreenMode(QMainWindow* pMainWindow, bool primary);
+void toggleFullscreenMode(QMainWindow* pMainWindow);
+bool supportsFullscreenMode(QMainWindow* pMainWindow);
+
+void initializeLang();
+
+QFileDialog::Options standardFileDialogOptions();
+
+} // namespace desktop
+
+#endif // DESKTOP_UTILS_HPP
diff --git a/src/cpp/desktop/DesktopUtilsMac.mm b/src/cpp/desktop/DesktopUtilsMac.mm
new file mode 100644
index 0000000..b009a89
--- /dev/null
+++ b/src/cpp/desktop/DesktopUtilsMac.mm
@@ -0,0 +1,174 @@
+/*
+ * DesktopUtilsMac.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopUtils.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <QWidget>
+
+#include <core/system/Environment.hpp>
+
+namespace desktop {
+
+bool isRetina(QMainWindow* pMainWindow)
+{
+ OSWindowRef macWindow = qt_mac_window_for(pMainWindow);
+ NSWindow* pWindow = (NSWindow*)macWindow;
+
+ if ([pWindow respondsToSelector:@selector(backingScaleFactor)])
+ {
+ double scaleFactor = [pWindow backingScaleFactor];
+ return scaleFactor == 2.0;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool isOSXMavericks()
+{
+ NSDictionary *systemVersionDictionary =
+ [NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"];
+
+ NSString *systemVersion =
+ [systemVersionDictionary objectForKey:@"ProductVersion"];
+
+ std::string version(
+ [systemVersion cStringUsingEncoding:NSASCIIStringEncoding]);
+
+ return boost::algorithm::starts_with(version, "10.9");
+}
+
+
+namespace {
+
+NSWindow* nsWindowForMainWindow(QMainWindow* pMainWindow)
+{
+ OSWindowRef macWindow = qt_mac_window_for(pMainWindow);
+ NSWindow* pWindow = (NSWindow*)macWindow;
+ return pWindow;
+}
+
+bool supportsFullscreenMode(NSWindow* pWindow)
+{
+ return [pWindow respondsToSelector:@selector(toggleFullScreen:)];
+}
+
+} // anonymous namespace
+
+
+bool supportsFullscreenMode(QMainWindow* pMainWindow)
+{
+ NSWindow* pWindow = nsWindowForMainWindow(pMainWindow);
+ return supportsFullscreenMode(pWindow);
+}
+
+// see: https://bugreports.qt-project.org/browse/QTBUG-21607
+// see: https://developer.apple.com/library/mac/#documentation/General/Conceptual/MOSXAppProgrammingGuide/FullScreenApp/FullScreenApp.html
+void enableFullscreenMode(QMainWindow* pMainWindow, bool primary)
+{
+ NSWindow* pWindow = nsWindowForMainWindow(pMainWindow);
+
+ if (supportsFullscreenMode(pWindow))
+ {
+ NSWindowCollectionBehavior behavior = [pWindow collectionBehavior];
+ behavior = behavior | (primary ?
+ NSWindowCollectionBehaviorFullScreenPrimary :
+ NSWindowCollectionBehaviorFullScreenAuxiliary);
+ [pWindow setCollectionBehavior:behavior];
+ }
+}
+
+void toggleFullscreenMode(QMainWindow* pMainWindow)
+{
+ NSWindow* pWindow = nsWindowForMainWindow(pMainWindow);
+ if (supportsFullscreenMode(pWindow))
+ [pWindow toggleFullScreen:nil];
+}
+
+void initializeLang()
+{
+ // Not sure what the memory management rules are here, i.e. whether an
+ // autorelease pool is active. Just let it leak, since we're only calling
+ // this once (at the time of this writing).
+
+ // We try to simulate the behavior of R.app.
+
+ NSString* lang = nil;
+
+ // Highest precedence: force.LANG. If it has a value, use it.
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ [defaults addSuiteNamed:@"org.R-project.R"];
+ lang = [defaults stringForKey:@"force.LANG"];
+ if (lang && ![lang length])
+ {
+ // If force.LANG is present but empty, don't touch LANG at all.
+ return;
+ }
+
+ // Next highest precedence: ignore.system.locale. If it has a value,
+ // hardcode to en_US.UTF-8.
+ if (!lang && [defaults boolForKey:@"ignore.system.locale"])
+ {
+ lang = @"en_US.UTF-8";
+ }
+
+ // Next highest precedence: LANG environment variable.
+ if (!lang)
+ {
+ std::string envLang = core::system::getenv("LANG");
+ if (!envLang.empty())
+ {
+ lang = [NSString stringWithCString:envLang.c_str()
+ encoding:NSASCIIStringEncoding];
+ }
+ }
+
+ // Next highest precedence: Try to figure out language from the current
+ // locale.
+ if (!lang)
+ {
+ NSString* lcid = [[NSLocale currentLocale] localeIdentifier];
+ if (lcid)
+ {
+ // Eliminate trailing @ components (OS X uses the @ suffix to append
+ // locale overrides like alternate currency formats)
+ std::string localeId = std::string([lcid UTF8String]);
+ std::size_t atLoc = localeId.find('@');
+ if (atLoc != std::string::npos)
+ {
+ localeId = localeId.substr(0, atLoc);
+ lcid = [NSString stringWithUTF8String: localeId.c_str()];
+ }
+
+ lang = [lcid stringByAppendingString:@".UTF-8"];
+ }
+ }
+
+ // None of the above worked. Just hard code it.
+ if (!lang)
+ {
+ lang = @"en_US.UTF-8";
+ }
+
+ const char* clang = [lang cStringUsingEncoding:NSASCIIStringEncoding];
+ core::system::setenv("LANG", clang);
+ core::system::setenv("LC_CTYPE", clang);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopWebPage.cpp b/src/cpp/desktop/DesktopWebPage.cpp
new file mode 100644
index 0000000..2bec37e
--- /dev/null
+++ b/src/cpp/desktop/DesktopWebPage.cpp
@@ -0,0 +1,301 @@
+/*
+ * DesktopWebPage.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopWebPage.hpp"
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <QWidget>
+#include <QtDebug>
+#include "DesktopNetworkAccessManager.hpp"
+#include "DesktopWindowTracker.hpp"
+#include "DesktopSatelliteWindow.hpp"
+#include "DesktopSecondaryWindow.hpp"
+#include "DesktopMainWindow.hpp"
+
+#include "DesktopUtils.hpp"
+
+using namespace core;
+
+extern QString sharedSecret;
+
+namespace desktop {
+
+namespace {
+
+WindowTracker s_windowTracker;
+
+} // anonymous namespace
+
+WebPage::WebPage(QUrl baseUrl, QWidget *parent) :
+ QWebPage(parent),
+ baseUrl_(baseUrl),
+ navigated_(false)
+{
+ settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
+ settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true);
+ setNetworkAccessManager(new NetworkAccessManager(sharedSecret, parent));
+ defaultSaveDir_ = QDir::home();
+}
+
+void WebPage::setBaseUrl(const QUrl& baseUrl)
+{
+ baseUrl_ = baseUrl;
+}
+
+void WebPage::activateSatelliteWindow(QString name)
+{
+ BrowserWindow* pSatellite = s_windowTracker.getWindow(name);
+ if (pSatellite)
+ desktop::raiseAndActivateWindow(pSatellite);
+}
+
+void WebPage::prepareForSatelliteWindow(
+ const PendingSatelliteWindow& pendingWnd)
+{
+ pendingSatelliteWindow_ = pendingWnd;
+}
+
+QWebPage* WebPage::createWindow(QWebPage::WebWindowType)
+{
+ // check if this is a satellite window
+ if (!pendingSatelliteWindow_.isEmpty())
+ {
+ // capture pending window params then clear them (one time only)
+ QString name = pendingSatelliteWindow_.name;
+ MainWindow* pMainWindow = pendingSatelliteWindow_.pMainWindow;
+ int width = pendingSatelliteWindow_.width;
+ int height = pendingSatelliteWindow_.height;
+ pendingSatelliteWindow_ = PendingSatelliteWindow();
+
+ // check for an existing window of this name
+ BrowserWindow* pSatellite = s_windowTracker.getWindow(name);
+ if (pSatellite)
+ {
+ // activate the browser then return NULL to indicate
+ // we didn't create a new WebView
+ desktop::raiseAndActivateWindow(pSatellite);
+ return NULL;
+ }
+ // create a new window if we didn't find one
+ else
+ {
+ // create and size
+ pSatellite = new SatelliteWindow(pMainWindow);
+ pSatellite->resize(width, height);
+
+ // try to tile the window (but leave pdf window alone
+ // since it is so large)
+ if (name != QString::fromAscii("pdf"))
+ {
+ // calculate location to move to
+
+ // y always attempts to be 25 pixels above then faults back
+ // to 25 pixels below if that would be offscreen
+ const int OVERLAP = 25;
+ int moveY = pMainWindow->y() - OVERLAP;
+ if (moveY < 0)
+ moveY = pMainWindow->y() + OVERLAP;
+
+ // x is based on centering over main window
+ int moveX = pMainWindow->x() +
+ (pMainWindow->width() / 2) -
+ (width / 2);
+
+ // perform movve
+ pSatellite->move(moveX, moveY);
+ }
+
+ // add to tracker
+ s_windowTracker.addWindow(name, pSatellite);
+
+ // show and return the browser
+ pSatellite->show();
+ return pSatellite->webView()->webPage();
+ }
+ }
+ else
+ {
+ SecondaryWindow* pWindow = new SecondaryWindow(baseUrl_);
+ pWindow->show();
+ return pWindow->webView()->webPage();
+ }
+}
+
+
+bool WebPage::shouldInterruptJavaScript()
+{
+ return false;
+}
+
+void WebPage::javaScriptConsoleMessage(const QString& message, int /*lineNumber*/, const QString& /*sourceID*/)
+{
+ qDebug() << message;
+}
+
+QString WebPage::userAgentForUrl(const QUrl &url) const
+{
+ return this->QWebPage::userAgentForUrl(url) + QString::fromAscii(" Qt/") + QString::fromAscii(qVersion());
+}
+
+bool WebPage::acceptNavigationRequest(QWebFrame* pWebFrame,
+ const QNetworkRequest& request,
+ NavigationType navType)
+{
+ QUrl url = request.url();
+
+ if (url.toString() == QString::fromAscii("about:blank"))
+ return true;
+
+ if (url.scheme() != QString::fromAscii("http")
+ && url.scheme() != QString::fromAscii("https")
+ && url.scheme() != QString::fromAscii("mailto")
+ && url.scheme() != QString::fromAscii("data"))
+ {
+ qDebug() << url.toString();
+ return false;
+ }
+
+ // determine if this is a local request (handle internally only if local)
+ std::string host = url.host().toStdString();
+ bool isLocal = host == "localhost" || host == "127.0.0.1";
+
+ if ((baseUrl_.isEmpty() && isLocal) ||
+ (url.scheme() == baseUrl_.scheme()
+ && url.host() == baseUrl_.host()
+ && url.port() == baseUrl_.port()))
+ {
+ navigated_ = true;
+ return true;
+ }
+ // allow local viewer urls to be handled internally by Qt
+ else if (isLocal && !viewerUrl_.isEmpty() &&
+ url.toString().startsWith(viewerUrl_))
+ {
+ navigated_ = true;
+ return true;
+ }
+ else
+ {
+ if (url.scheme() == QString::fromUtf8("data") &&
+ (navType == QWebPage::NavigationTypeLinkClicked ||
+ navType == QWebPage::NavigationTypeFormSubmitted))
+ {
+ handleBase64Download(pWebFrame, url);
+ }
+ else if (navType == QWebPage::NavigationTypeLinkClicked)
+ {
+ desktop::openUrl(url);
+ }
+
+ if (!navigated_)
+ this->view()->window()->deleteLater();
+
+ return false;
+ }
+}
+
+void WebPage::handleBase64Download(QWebFrame* pWebFrame, QUrl url)
+{
+ // look for beginning of base64 data
+ QString base64 = QString::fromUtf8("base64,");
+ int pos = url.path().indexOf(base64);
+ if (pos == -1)
+ {
+ LOG_ERROR_MESSAGE("Base64 designator not found in data url");
+ return;
+ }
+
+ // extract the base64 data from the uri
+ QString base64Data = url.path();
+ base64Data.remove(0, pos + base64.length());
+ QByteArray base64ByteArray(base64Data.toStdString().c_str());
+ QByteArray byteArray = QByteArray::fromBase64(base64ByteArray);
+
+ // find the a tag in the page with this href
+ QWebElement aTag;
+ QString urlString = url.toString(QUrl::None);
+ QWebElementCollection aElements = pWebFrame->findAllElements(
+ QString::fromUtf8("a"));
+ for (int i=0; i<aElements.count(); i++)
+ {
+ QWebElement a = aElements.at(i);
+ QString href = a.attribute(QString::fromUtf8("href"));
+ href.replace(QChar::fromAscii('\n'), QString::fromUtf8(""));
+ if (href == urlString)
+ {
+ aTag = a;
+ break;
+ }
+ }
+
+ // if no a tag was found then bail
+ if (aTag.isNull())
+ {
+ LOG_ERROR_MESSAGE("Unable to finding matching a tag for data url");
+ return;
+ }
+
+ // get the download attribute (default filename)
+ QString download = aTag.attribute(QString::fromUtf8("download"));
+ QString defaultFilename = defaultSaveDir_.absoluteFilePath(download);
+
+ // prompt for filename
+ QString filename = QFileDialog::getSaveFileName(
+ NULL,
+ tr("Download File"),
+ defaultFilename,
+ defaultSaveDir_.absolutePath(),
+ 0,
+ standardFileDialogOptions());
+ if (!filename.isEmpty())
+ {
+ // see if we need to force the extension
+ FilePath defaultFilePath(defaultFilename.toUtf8().constData());
+ FilePath chosenFilePath(filename.toUtf8().constData());
+ if (chosenFilePath.extension().empty() &&
+ !defaultFilePath.extension().empty())
+ {
+ filename += QString::fromStdString(defaultFilePath.extension());
+ }
+
+ // write the file
+ QFile file(filename);
+ if (file.open(QIODevice::WriteOnly))
+ {
+ if (file.write(byteArray) == -1)
+ {
+ showFileError(QString::fromUtf8("writing"),
+ filename,
+ file.errorString());
+ }
+
+ file.close();
+ }
+ else
+ {
+ showFileError(QString::fromUtf8("writing"),
+ filename,
+ file.errorString());
+ }
+
+ // persist the defaultSaveDir
+ defaultSaveDir_ = QFileInfo(file).dir();
+ }
+}
+
+}
diff --git a/src/cpp/desktop/DesktopWebPage.hpp b/src/cpp/desktop/DesktopWebPage.hpp
new file mode 100644
index 0000000..a50fde5
--- /dev/null
+++ b/src/cpp/desktop/DesktopWebPage.hpp
@@ -0,0 +1,89 @@
+/*
+ * DesktopWebPage.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_WEB_PAGE_HPP
+#define DESKTOP_WEB_PAGE_HPP
+
+#include <QtGui>
+#include <QtWebKit>
+
+namespace desktop {
+
+class MainWindow;
+
+struct PendingSatelliteWindow
+{
+ PendingSatelliteWindow()
+ : name(), pMainWindow(NULL), width(-1), height(-1)
+ {
+ }
+
+ PendingSatelliteWindow(QString name,
+ MainWindow* pMainWindow,
+ int width,
+ int height)
+ : name(name), pMainWindow(pMainWindow), width(width), height(height)
+ {
+ }
+
+ bool isEmpty() const { return name.isEmpty(); }
+
+ QString name;
+
+ MainWindow* pMainWindow;
+
+ int width;
+ int height;
+};
+
+
+class WebPage : public QWebPage
+{
+ Q_OBJECT
+
+public:
+ explicit WebPage(QUrl baseUrl = QUrl(), QWidget *parent = NULL);
+
+ void setBaseUrl(const QUrl& baseUrl);
+ void setViewerUrl(const QString& viewerUrl) { viewerUrl_ = viewerUrl; }
+
+ void activateSatelliteWindow(QString name);
+ void prepareForSatelliteWindow(const PendingSatelliteWindow& pendingWnd);
+
+public slots:
+ bool shouldInterruptJavaScript();
+
+protected:
+ QWebPage* createWindow(QWebPage::WebWindowType type);
+ void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID);
+ QString userAgentForUrl(const QUrl &url) const;
+ bool acceptNavigationRequest(QWebFrame* frame,
+ const QNetworkRequest& request,
+ NavigationType type);
+
+private:
+ void handleBase64Download(QWebFrame* pWebFrame, QUrl url);
+
+private:
+ QUrl baseUrl_;
+ QString viewerUrl_;
+ bool navigated_;
+ PendingSatelliteWindow pendingSatelliteWindow_;
+ QDir defaultSaveDir_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_WEB_PAGE_HPP
diff --git a/src/cpp/desktop/DesktopWebView.cpp b/src/cpp/desktop/DesktopWebView.cpp
new file mode 100644
index 0000000..a9c9f71
--- /dev/null
+++ b/src/cpp/desktop/DesktopWebView.cpp
@@ -0,0 +1,278 @@
+/*
+ * DesktopWebView.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopWebView.hpp"
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QTemporaryFile>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include "DesktopDownloadHelper.hpp"
+#include "DesktopOptions.hpp"
+#include "DesktopWebPage.hpp"
+#include "DesktopUtils.hpp"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <wingdi.h>
+#endif
+
+namespace desktop {
+
+WebView::WebView(QUrl baseUrl, QWidget *parent) :
+ QWebView(parent),
+ baseUrl_(baseUrl),
+ dpiZoomScaling_(1.0)
+{
+#ifdef Q_WS_X11
+ if (!core::system::getenv("KDE_FULL_SESSION").empty())
+ setStyle(new QPlastiqueStyle());
+#endif
+ pWebPage_ = new WebPage(baseUrl, this);
+ setPage(pWebPage_);
+
+ page()->setForwardUnsupportedContent(true);
+ if (desktop::options().webkitDevTools())
+ page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
+
+ connect(page(), SIGNAL(downloadRequested(QNetworkRequest)),
+ this, SLOT(downloadRequested(QNetworkRequest)));
+ connect(page(), SIGNAL(unsupportedContent(QNetworkReply*)),
+ this, SLOT(unsupportedContent(QNetworkReply*)));
+
+#ifdef _WIN32
+ // On Windows, check for high DPI; if present, scale the zoom factors
+ // accordingly.
+ HDC defaultDC = GetDC(NULL);
+ int dpi = GetDeviceCaps(defaultDC, LOGPIXELSX);
+ if (dpi >= 192)
+ {
+ // Corresponds to 200% scaling (introduced in Windows 8.1)
+ dpiZoomScaling_ = 1.5;
+ }
+ else if (dpi >= 144)
+ {
+ // Corresponds to 150% scaling
+ dpiZoomScaling_ = 1.2;
+ }
+ ReleaseDC(NULL, defaultDC);
+#endif
+}
+
+void WebView::setBaseUrl(const QUrl& baseUrl)
+{
+ baseUrl_ = baseUrl;
+ pWebPage_->setBaseUrl(baseUrl_);
+}
+
+void WebView::activateSatelliteWindow(QString name)
+{
+ pWebPage_->activateSatelliteWindow(name);
+}
+
+void WebView::prepareForSatelliteWindow(
+ const PendingSatelliteWindow& pendingWnd)
+{
+ pWebPage_->prepareForSatelliteWindow(pendingWnd);
+}
+
+QString WebView::promptForFilename(const QNetworkRequest& request,
+ QNetworkReply* pReply = NULL)
+{
+ QString defaultFileName = QFileInfo(request.url().path()).fileName();
+
+ // Content-Disposition's filename parameter should be used as the
+ // default, if present.
+ if (pReply && pReply->hasRawHeader("content-disposition"))
+ {
+ QString headerValue = QString::fromAscii(pReply->rawHeader("content-disposition"));
+ QRegExp regexp(QString::fromAscii("filename=(.+)"), Qt::CaseInsensitive);
+ if (regexp.indexIn(headerValue) >= 0)
+ {
+ defaultFileName = regexp.cap(1);
+ }
+ }
+
+ QString fileName = QFileDialog::getSaveFileName(this,
+ tr("Download File"),
+ defaultFileName,
+ QString(),
+ 0,
+ standardFileDialogOptions());
+ return fileName;
+}
+
+void WebView::keyPressEvent(QKeyEvent* pEv)
+{
+ // emit close window shortcut signal if appropriate
+#ifndef _WIN32
+ if (pEv->key() == 'W')
+ {
+#ifdef Q_WS_MAC
+ Qt::KeyboardModifier modifier = Qt::MetaModifier;
+#else
+ Qt::KeyboardModifier modifier = Qt::ControlModifier;
+#endif
+
+ // check modifier and emit signal
+ if (pEv->modifiers() & modifier)
+ onCloseWindowShortcut();
+ }
+#endif
+
+ // Work around bugs in QtWebKit that result in numpad key
+ // presses resulting in keyCode=0 in the DOM's keydown events.
+ // This is due to some missing switch cases in the case
+ // where the keypad modifier bit is on, so we turn it off.
+
+ Qt::KeyboardModifiers modifiers;
+
+#ifdef Q_WS_MAC
+ if ((pEv->nativeModifiers() & 0x40101) == 0x40101) {
+ modifiers &= ~Qt::MetaModifier;
+ modifiers |= Qt::ControlModifier;
+ } else if ((pEv->nativeModifiers() & 0x100108) == 0x100108) {
+ modifiers &= ~Qt::ControlModifier;
+ modifiers |= Qt::MetaModifier;
+ } else {
+#else
+ {
+#endif
+ modifiers = pEv->modifiers();
+ }
+
+ QKeyEvent newEv(pEv->type(),
+ pEv->key(),
+ modifiers & ~Qt::KeypadModifier,
+ pEv->text(),
+ pEv->isAutoRepeat(),
+ pEv->count());
+
+ this->QWebView::keyPressEvent(&newEv);
+}
+
+void WebView::downloadRequested(const QNetworkRequest& request)
+{
+ QString fileName = promptForFilename(request);
+ if (fileName.isEmpty())
+ return;
+
+ // Ask the network manager to download
+ // the file and connect to the progress
+ // and finished signals.
+ QNetworkRequest newRequest = request;
+
+ QNetworkAccessManager* pNetworkManager = page()->networkAccessManager();
+ QNetworkReply* pReply = pNetworkManager->get(newRequest);
+ // DownloadHelper frees itself when downloading is done
+ new DownloadHelper(pReply, fileName);
+}
+
+void WebView::unsupportedContent(QNetworkReply* pReply)
+{
+ bool closeAfterDownload = false;
+ if (this->page()->history()->count() == 0)
+ {
+ /* This is for the case where a new browser window was launched just
+ to show a PDF or save a file. Otherwise we would have an empty
+ browser window with no history hanging around. */
+ window()->hide();
+ closeAfterDownload = true;
+ }
+
+ DownloadHelper* pDownloadHelper = NULL;
+
+ QString contentType =
+ pReply->header(QNetworkRequest::ContentTypeHeader).toString();
+ if (contentType.contains(QRegExp(QString::fromAscii("^\\s*application/pdf($|;)"),
+ Qt::CaseInsensitive)))
+ {
+ core::FilePath dir(options().scratchTempDir());
+
+ QTemporaryFile pdfFile(QString::fromUtf8(
+ dir.childPath("rstudio-XXXXXX.pdf").absolutePath().c_str()));
+ pdfFile.setAutoRemove(false);
+ pdfFile.open();
+ pdfFile.close();
+
+ if (pReply->isFinished())
+ {
+ DownloadHelper::handleDownload(pReply, pdfFile.fileName());
+ openFile(pdfFile.fileName());
+ }
+ else
+ {
+ // DownloadHelper frees itself when downloading is done
+ pDownloadHelper = new DownloadHelper(pReply, pdfFile.fileName());
+ connect(pDownloadHelper, SIGNAL(downloadFinished(QString)),
+ this, SLOT(openFile(QString)));
+ }
+ }
+ else
+ {
+ QString fileName = promptForFilename(pReply->request(), pReply);
+ if (fileName.isEmpty())
+ {
+ pReply->abort();
+ if (closeAfterDownload)
+ window()->close();
+ }
+ else
+ {
+ // DownloadHelper frees itself when downloading is done
+ pDownloadHelper = new DownloadHelper(pReply, fileName);
+ }
+ }
+
+ if (closeAfterDownload && pDownloadHelper)
+ {
+ connect(pDownloadHelper, SIGNAL(downloadFinished(QString)),
+ window(), SLOT(close()));
+ }
+}
+
+void WebView::openFile(QString fileName)
+{
+ // force use of Preview for PDFs on the Mac (Adobe Reader 10.01 crashes)
+#ifdef Q_WS_MAC
+ if (fileName.toLower().endsWith(QString::fromAscii(".pdf")))
+ {
+ QStringList args;
+ args.append(QString::fromAscii("-a"));
+ args.append(QString::fromAscii("Preview"));
+ args.append(fileName);
+ QProcess::startDetached(QString::fromAscii("open"), args);
+ return;
+ }
+#endif
+
+ QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
+}
+
+// QWebView doesn't respect the system DPI and always renders as though
+// it were at 96dpi. To work around this, we take the user-specified zoom level
+// and scale it by a DPI-determined constant before applying it to the view.
+// See: https://bugreports.qt-project.org/browse/QTBUG-29571
+void WebView::setDpiAwareZoomFactor(qreal factor)
+{
+ setZoomFactor(factor * dpiZoomScaling_);
+}
+
+qreal WebView::dpiAwareZoomFactor()
+{
+ return zoomFactor() / dpiZoomScaling_;
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopWebView.hpp b/src/cpp/desktop/DesktopWebView.hpp
new file mode 100644
index 0000000..b31ecb1
--- /dev/null
+++ b/src/cpp/desktop/DesktopWebView.hpp
@@ -0,0 +1,68 @@
+/*
+ * DesktopWebView.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_WEB_VIEW_HPP
+#define DESKTOP_WEB_VIEW_HPP
+
+#include <QtGui>
+#include <QWebView>
+
+#include "DesktopWebPage.hpp"
+
+namespace desktop {
+
+class MainWindow;
+
+class WebView : public ::QWebView
+{
+ Q_OBJECT
+
+public:
+ explicit WebView(QUrl baseUrl = QUrl(),
+ QWidget *parent = NULL);
+
+ void setBaseUrl(const QUrl& baseUrl);
+
+ void activateSatelliteWindow(QString name);
+ void prepareForSatelliteWindow(const PendingSatelliteWindow& pendingWnd);
+ void setDpiAwareZoomFactor(qreal factor);
+ qreal dpiAwareZoomFactor();
+
+ WebPage* webPage() const { return pWebPage_; }
+
+signals:
+ void onCloseWindowShortcut();
+
+public slots:
+
+protected:
+ QString promptForFilename(const QNetworkRequest& request,
+ QNetworkReply* pReply);
+ void keyPressEvent(QKeyEvent* pEv);
+
+protected slots:
+ void downloadRequested(const QNetworkRequest&);
+ void unsupportedContent(QNetworkReply*);
+ void openFile(QString file);
+
+private:
+ QUrl baseUrl_;
+ WebPage* pWebPage_;
+ double dpiZoomScaling_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_WEB_VIEW_HPP
diff --git a/src/cpp/desktop/DesktopWin32ApplicationLaunch.cpp b/src/cpp/desktop/DesktopWin32ApplicationLaunch.cpp
new file mode 100644
index 0000000..b272c6a
--- /dev/null
+++ b/src/cpp/desktop/DesktopWin32ApplicationLaunch.cpp
@@ -0,0 +1,204 @@
+/*
+ * DesktopWin32ApplicationLaunch.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "DesktopApplicationLaunch.hpp"
+
+#include <windows.h>
+
+#include <QWidget>
+
+#include "DesktopOptions.hpp"
+
+/*
+ This class is implemented using a message-only Win32 window with
+ a well-known window title. The message-only window can respond
+ to a "get main window handle" message, which will return the HWND
+ of the application's main window. It also responds to WM_COPYDATA
+ with OPENFILE as dwData, which causes openFileRequest(QString) to
+ be signaled with the copied data.
+
+ It also uses a lockfile (%TEMP%\rstudio.lock) to try to ensure two
+ copies of the app don't run concurrently even if something goes
+ wrong with the message-only window.
+ */
+
+namespace desktop {
+
+namespace {
+
+const ULONG_PTR OPENFILE = 1;
+const char* WINDOW_TITLE = "RStudio_LaunchWindow_6dd82276-ccc3-4324-839e-4e6bcd5145bd";
+
+UINT wmGetMainWindowHandle()
+{
+ static UINT msg = 0;
+ if (!msg)
+ msg = ::RegisterWindowMessage("3dd567f8-7c07-4d9d-934b-dcb922cd737e");
+ return msg;
+}
+
+void activate(HWND hWnd)
+{
+ HWND hwndPopup = ::GetLastActivePopup(hWnd);
+ if (::IsWindow(hwndPopup))
+ hWnd = hwndPopup;
+ ::SetForegroundWindow(hWnd);
+ if (::IsIconic(hWnd))
+ ::ShowWindow(hWnd, SW_RESTORE);
+}
+
+} // anonymous namespace
+
+ApplicationLaunch::ApplicationLaunch() :
+ QWidget(NULL),
+ pMainWindow_(NULL)
+{
+ setAttribute(Qt::WA_NativeWindow);
+ setWindowTitle(QString::fromAscii(WINDOW_TITLE));
+ ::SetParent(winId(), HWND_MESSAGE);
+}
+
+void ApplicationLaunch::init(QString,
+ int& argc,
+ char* argv[],
+ boost::scoped_ptr<QApplication>* ppApp,
+ boost::scoped_ptr<ApplicationLaunch>* ppAppLaunch)
+{
+ ppApp->reset(new QApplication(argc, argv));
+ ppAppLaunch->reset(new ApplicationLaunch());
+}
+
+void ApplicationLaunch::setActivationWindow(QWidget* pWindow)
+{
+ pMainWindow_ = pWindow;
+}
+
+void ApplicationLaunch::activateWindow()
+{
+ activate(winId());
+}
+
+QString ApplicationLaunch::startupOpenFileRequest() const
+{
+ return QString();
+}
+
+namespace {
+
+bool acquireLock()
+{
+ // The file is implicitly released/deleted when the process exits
+
+ QString lockFilePath = QDir::temp().absoluteFilePath(QString::fromAscii("rstudio.lock"));
+ HANDLE hFile = ::CreateFileW(lockFilePath.toStdWString().c_str(),
+ GENERIC_WRITE,
+ 0, // exclusive access
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ if (::GetLastError() == ERROR_SHARING_VIOLATION)
+ return false;
+ }
+
+ return true;
+}
+
+} // anonymous namespace
+
+
+void ApplicationLaunch::attemptToRegisterPeer()
+{
+ acquireLock();
+}
+
+
+bool ApplicationLaunch::sendMessage(QString filename)
+{
+ if (acquireLock())
+ return false;
+
+ HWND hwndAppLaunch = NULL;
+ do
+ {
+ hwndAppLaunch = ::FindWindowEx(HWND_MESSAGE, hwndAppLaunch, NULL, WINDOW_TITLE);
+ } while (hwndAppLaunch == winId()); // Ignore ourselves
+
+ if (::IsWindow(hwndAppLaunch))
+ {
+ HWND hwnd = reinterpret_cast<HWND>(::SendMessage(hwndAppLaunch,
+ wmGetMainWindowHandle(),
+ NULL,
+ NULL));
+ if (::IsWindow(hwnd))
+ {
+ HWND hwndPopup = ::GetLastActivePopup(hwnd);
+ if (::IsWindow(hwndPopup))
+ hwnd = hwndPopup;
+ ::SetForegroundWindow(hwnd);
+ if (::IsIconic(hwnd))
+ ::ShowWindow(hwnd, SW_RESTORE);
+
+ if (!filename.isEmpty())
+ {
+ QByteArray data = filename.toUtf8();
+
+ COPYDATASTRUCT copydata;
+ copydata.dwData = OPENFILE;
+ copydata.lpData = data.data();
+ copydata.cbData = data.size();
+
+ HWND sender = winId();
+
+ ::SendMessage(hwndAppLaunch,
+ WM_COPYDATA,
+ reinterpret_cast<WPARAM>(sender),
+ reinterpret_cast<LPARAM>(©data));
+ }
+ }
+ }
+
+ return true;
+}
+
+bool ApplicationLaunch::winEvent(MSG *message, long *result)
+{
+ if (message->message == WM_COPYDATA)
+ {
+ COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(message->lParam);
+ if (cds->dwData == OPENFILE)
+ {
+ QString fileName = QString::fromUtf8(
+ reinterpret_cast<char*>(cds->lpData),
+ cds->cbData);
+ openFileRequest(fileName);
+ *result = 1;
+ return true;
+ }
+ }
+ else if (message->message == wmGetMainWindowHandle())
+ {
+ if (pMainWindow_)
+ *result = reinterpret_cast<LRESULT>(pMainWindow_->winId());
+ else
+ *result = NULL;
+ return true;
+ }
+ return QWidget::winEvent(message, result);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopWin32DetectRHome.cpp b/src/cpp/desktop/DesktopWin32DetectRHome.cpp
new file mode 100644
index 0000000..31a8a17
--- /dev/null
+++ b/src/cpp/desktop/DesktopWin32DetectRHome.cpp
@@ -0,0 +1,73 @@
+/*
+ * DesktopWin32DetectRHome.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef _WIN32
+#error DesktopDetectRHome.cpp is Windows-specific
+#endif
+
+#include "DesktopDetectRHome.hpp"
+
+#include <windows.h>
+
+#include <boost/bind.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include "DesktopRVersion.hpp"
+
+using namespace core;
+
+namespace desktop {
+
+bool prepareEnvironment(Options &options)
+{
+ bool forceUi = ::GetAsyncKeyState(VK_CONTROL) & ~1;
+
+ RVersion rVersion = detectRVersion(forceUi);
+ if (!rVersion.isValid())
+ return false;
+
+
+ // get the short path version of the home dir
+ std::string homePath =
+ QDir::toNativeSeparators(rVersion.homeDir()).toStdString();
+ DWORD len = ::GetShortPathName(homePath.c_str(), NULL, 0);
+ std::vector<TCHAR> buffer(len, 0);
+ if (::GetShortPathName(homePath.c_str(), &(buffer[0]), len) != 0)
+ {
+ // copy path to string and assign it we got one
+ std::string shortHomePath(&(buffer[0]));
+ if (!shortHomePath.empty())
+ homePath = shortHomePath;
+ }
+ else
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ }
+
+
+ // set R_HOME
+ system::setenv("R_HOME", homePath);
+
+ std::string path =
+ QDir::toNativeSeparators(rVersion.binDir()).toStdString() + ";" +
+ system::getenv("PATH");
+ system::setenv("PATH", path);
+
+ return true;
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopWindowTracker.cpp b/src/cpp/desktop/DesktopWindowTracker.cpp
new file mode 100644
index 0000000..9a7361a
--- /dev/null
+++ b/src/cpp/desktop/DesktopWindowTracker.cpp
@@ -0,0 +1,53 @@
+/*
+ * DesktopWindowTracker.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DesktopWindowTracker.hpp"
+
+#include "DesktopSlotBinders.hpp"
+
+namespace desktop {
+
+WindowTracker::WindowTracker(QObject *parent) :
+ QObject(parent)
+{
+}
+
+BrowserWindow* WindowTracker::getWindow(QString key)
+{
+ return map_.value(key, NULL);
+}
+
+void WindowTracker::addWindow(QString key, BrowserWindow* window)
+{
+ map_.insert(key, window);
+
+ // Freed by signal
+ StringSlotBinder* stringSlotBinder = new StringSlotBinder(key, this);
+ connect(stringSlotBinder, SIGNAL(triggered(QString)),
+ stringSlotBinder, SLOT(deleteLater()));
+
+ connect(window, SIGNAL(destroyed()),
+ stringSlotBinder, SLOT(trigger()));
+
+ connect(stringSlotBinder, SIGNAL(triggered(QString)),
+ this, SLOT(onWindowDestroyed(QString)));
+}
+
+void WindowTracker::onWindowDestroyed(QString key)
+{
+ map_.remove(key);
+}
+
+} // namespace desktop
diff --git a/src/cpp/desktop/DesktopWindowTracker.hpp b/src/cpp/desktop/DesktopWindowTracker.hpp
new file mode 100644
index 0000000..8709618
--- /dev/null
+++ b/src/cpp/desktop/DesktopWindowTracker.hpp
@@ -0,0 +1,45 @@
+/*
+ * DesktopWindowTracker.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_WINDOW_TRACKER_HPP
+#define DESKTOP_WINDOW_TRACKER_HPP
+
+#include <QtCore>
+#include <QMainWindow>
+#include <QMap>
+
+#include "DesktopBrowserWindow.hpp"
+
+namespace desktop {
+
+class WindowTracker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit WindowTracker(QObject *parent = 0);
+
+ BrowserWindow* getWindow(QString key);
+ void addWindow(QString key, BrowserWindow* window);
+
+protected slots:
+ void onWindowDestroyed(QString key);
+
+private:
+ QMap<QString, BrowserWindow*> map_;
+};
+
+} // namespace desktop
+
+#endif // DESKTOP_WINDOW_TRACKER_HPP
diff --git a/src/cpp/desktop/FixBundle.cmake.in b/src/cpp/desktop/FixBundle.cmake.in
new file mode 100644
index 0000000..b9f406a
--- /dev/null
+++ b/src/cpp/desktop/FixBundle.cmake.in
@@ -0,0 +1,8 @@
+
+include(BundleUtilities)
+
+set(BUNDLE "${CMAKE_INSTALL_PREFIX}/bin/rdesktop at CMAKE_EXECUTABLE_SUFFIX@")
+set(OTHER_LIBS "")
+set(DIRS "")
+
+fixup_bundle("${BUNDLE}" "${OTHER_LIBS}" "${DIRS}")
diff --git a/src/cpp/desktop/Info.plist.in b/src/cpp/desktop/Info.plist.in
new file mode 100644
index 0000000..9023bf2
--- /dev/null
+++ b/src/cpp/desktop/Info.plist.in
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>fold</string>
+ </array>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>RData</string>
+ <string>Rdata</string>
+ <string>rdata</string>
+ <string>rda</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RData.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Data File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rproj</string>
+ <string>RProj</string>
+ <string>rproj</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RProject.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Project</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>R</string>
+ <string>r</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RSource.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Source File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rd</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RDoc.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Doc File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rnw</string>
+ <string>rnw</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RSweave.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Sweave File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rmd</string>
+ <string>rmd</string>
+ <string>Rmarkdown</string>
+ <string>rmarkdown</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RMarkdown.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Markdown File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rhtml</string>
+ <string>rhtml</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RHTML.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R HTML File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>Rpres</string>
+ <string>rpres</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RPresentation.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>R Presentation File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSIsAppleDefaultForType</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>tex</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>RTex.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>TeX File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>md</string>
+ <string>mdtxt</string>
+ <string>markdown</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>Markdown.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Markdown File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>htm</string>
+ <string>html</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>HTML.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>HTML File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>css</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>CSS.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>CSS File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>js</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>JS.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Javascript File</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ </array>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>RStudio</string>
+ <key>CFBundleGetInfoString</key>
+ <string>RStudio ${CPACK_PACKAGE_VERSION}, © 2009-2012 RStudio, Inc.</string>
+ <key>CFBundleIconFile</key>
+ <string>RStudio.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.rstudio.RStudio</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CFBundleName</key>
+ <string>RStudio</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CFBundleSignature</key>
+ <string>Rstd</string>
+ <key>CFBundleVersion</key>
+ <string>${CPACK_PACKAGE_VERSION}</string>
+ <key>CSResourcesFileMapped</key>
+ <true/>
+ <key>LSRequiresCarbon</key>
+ <true/>
+ <key>NSHumanReadableCopyright</key>
+ <string>RStudio ${CPACK_PACKAGE_VERSION}, © 2009-2012 RStudio, Inc.</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+</dict>
+</plist>
diff --git a/src/cpp/desktop/RProject.ico b/src/cpp/desktop/RProject.ico
new file mode 100644
index 0000000..c86785f
Binary files /dev/null and b/src/cpp/desktop/RProject.ico differ
diff --git a/src/cpp/desktop/RStudio.ico b/src/cpp/desktop/RStudio.ico
new file mode 100644
index 0000000..8644e72
Binary files /dev/null and b/src/cpp/desktop/RStudio.ico differ
diff --git a/src/cpp/desktop/assets.qrc b/src/cpp/desktop/assets.qrc
new file mode 100644
index 0000000..5ae076b
--- /dev/null
+++ b/src/cpp/desktop/assets.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>popout.png</file>
+ </qresource>
+</RCC>
diff --git a/src/cpp/desktop/back.png b/src/cpp/desktop/back.png
new file mode 100644
index 0000000..e6d5578
Binary files /dev/null and b/src/cpp/desktop/back.png differ
diff --git a/src/cpp/desktop/back_mac.png b/src/cpp/desktop/back_mac.png
new file mode 100644
index 0000000..4aab3f3
Binary files /dev/null and b/src/cpp/desktop/back_mac.png differ
diff --git a/src/cpp/desktop/desktop-config.h.in b/src/cpp/desktop/desktop-config.h.in
new file mode 100644
index 0000000..956c09a
--- /dev/null
+++ b/src/cpp/desktop/desktop-config.h.in
@@ -0,0 +1,23 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define RSTUDIO_VERSION "${CPACK_PACKAGE_VERSION}"
+
+#define RSTUDIO_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
+
+#define RSTUDIO_R_MAJOR_VERSION_REQUIRED ${RSTUDIO_R_MAJOR_VERSION_REQUIRED}
+#define RSTUDIO_R_MINOR_VERSION_REQUIRED ${RSTUDIO_R_MINOR_VERSION_REQUIRED}
+#define RSTUDIO_R_PATCH_VERSION_REQUIRED ${RSTUDIO_R_PATCH_VERSION_REQUIRED}
+
diff --git a/src/cpp/desktop/desktop.qrc b/src/cpp/desktop/desktop.qrc
new file mode 100644
index 0000000..9e47285
--- /dev/null
+++ b/src/cpp/desktop/desktop.qrc
@@ -0,0 +1,14 @@
+<RCC>
+ <qresource prefix="/icons">
+ <file>RStudio.ico</file>
+ <file>back.png</file>
+ <file>forward.png</file>
+ <file>print.png</file>
+ <file>reload.png</file>
+ <file>back_mac.png</file>
+ <file>forward_mac.png</file>
+ <file>print_mac.png</file>
+ <file>reload_mac.png</file>
+ <file>resources/freedesktop/icons/64x64/rstudio.png</file>
+ </qresource>
+</RCC>
diff --git a/src/cpp/desktop/fancybrowser.pro b/src/cpp/desktop/fancybrowser.pro
new file mode 100644
index 0000000..438c0ad
--- /dev/null
+++ b/src/cpp/desktop/fancybrowser.pro
@@ -0,0 +1,27 @@
+QT += webkit network
+HEADERS = mainwindow.h \
+ rswebview.h \
+ secondarywindow.h \
+ browserwindow.h \
+ downloadhelper.h
+SOURCES = main.cpp \
+ mainwindow.cpp \
+ rswebview.cpp \
+ secondarywindow.cpp \
+ browserwindow.cpp \
+ downloadhelper.cpp
+RESOURCES = \
+ assets.qrc
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/webkit/fancybrowser
+sources.files = $$SOURCES $$HEADERS $$RESOURCES *.pro
+sources.path = $$[QT_INSTALL_EXAMPLES]/webkit/fancybrowser
+INSTALLS += target sources
+
+symbian {
+ TARGET.UID3 = 0xA000CF6C
+ include($$QT_SOURCE_TREE/examples/symbianpkgrules.pri)
+}
+
+FORMS +=
diff --git a/src/cpp/desktop/forward.png b/src/cpp/desktop/forward.png
new file mode 100644
index 0000000..7537fa7
Binary files /dev/null and b/src/cpp/desktop/forward.png differ
diff --git a/src/cpp/desktop/forward_mac.png b/src/cpp/desktop/forward_mac.png
new file mode 100644
index 0000000..dc73cbd
Binary files /dev/null and b/src/cpp/desktop/forward_mac.png differ
diff --git a/src/cpp/desktop/mac-terminal.in b/src/cpp/desktop/mac-terminal.in
new file mode 100755
index 0000000..7f95797
--- /dev/null
+++ b/src/cpp/desktop/mac-terminal.in
@@ -0,0 +1,9 @@
+#!/usr/bin/osascript
+on run argv
+ set dir to quoted form of (first item of argv)
+ tell app "Terminal"
+ activate
+ do script "cd " & dir
+ end tell
+end run
+
diff --git a/src/cpp/desktop/print.png b/src/cpp/desktop/print.png
new file mode 100644
index 0000000..7081298
Binary files /dev/null and b/src/cpp/desktop/print.png differ
diff --git a/src/cpp/desktop/print_mac.png b/src/cpp/desktop/print_mac.png
new file mode 100644
index 0000000..1b1331d
Binary files /dev/null and b/src/cpp/desktop/print_mac.png differ
diff --git a/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac.cpp b/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac.cpp
new file mode 100644
index 0000000..48ec2a1
--- /dev/null
+++ b/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac.cpp
@@ -0,0 +1,1755 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qbitmap.h>
+#include <qpaintdevice.h>
+#include <private/qpaintengine_mac_p.h>
+#include <qpainterpath.h>
+#include <qpixmapcache.h>
+#include <private/qpaintengine_raster_p.h>
+#include <private/qprintengine_mac_p.h>
+#include <qprinter.h>
+#include <qstack.h>
+#include <qtextcodec.h>
+#include <qwidget.h>
+#include <qvarlengtharray.h>
+#include <qdebug.h>
+#include <qcoreapplication.h>
+#include <qmath.h>
+
+#include <private/qfont_p.h>
+#include <private/qfontengine_p.h>
+#include <private/qfontengine_coretext_p.h>
+#include <private/qfontengine_mac_p.h>
+#include <private/qnumeric_p.h>
+#include <private/qpainter_p.h>
+#include <private/qpainterpath_p.h>
+#include <private/qpixmap_mac_p.h>
+#include <private/qt_mac_p.h>
+#include <private/qtextengine_p.h>
+#include <private/qwidget_p.h>
+#include <private/qt_cocoa_helpers_mac_p.h>
+
+#include <string.h>
+
+QT_BEGIN_NAMESPACE
+
+extern int qt_antialiasing_threshold; // QApplication.cpp
+
+/*****************************************************************************
+ External functions
+ *****************************************************************************/
+extern CGImageRef qt_mac_create_imagemask(const QPixmap &px, const QRectF &sr); //qpixmap_mac.cpp
+extern QPoint qt_mac_posInWindow(const QWidget *w); //qwidget_mac.cpp
+extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp
+extern CGContextRef qt_mac_cg_context(const QPaintDevice *); //qpaintdevice_mac.cpp
+extern void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp
+extern QPixmap qt_pixmapForBrush(int, bool); //qbrush.cpp
+
+void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform);
+
+
+//Implemented for qt_mac_p.h
+QMacCGContext::QMacCGContext(QPainter *p)
+{
+ QPaintEngine *pe = p->paintEngine();
+ if (pe->type() == QPaintEngine::MacPrinter)
+ pe = static_cast<QMacPrintEngine*>(pe)->paintEngine();
+ pe->syncState();
+ context = 0;
+ if(pe->type() == QPaintEngine::CoreGraphics)
+ context = static_cast<QCoreGraphicsPaintEngine*>(pe)->handle();
+
+ int devType = p->device()->devType();
+ if (pe->type() == QPaintEngine::Raster
+ && (devType == QInternal::Widget || devType == QInternal::Pixmap || devType == QInternal::Image)) {
+
+ extern CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *paintDevice);
+ CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pe->paintDevice());
+ uint flags = kCGImageAlphaPremultipliedFirst;
+#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version
+ flags |= kCGBitmapByteOrder32Host;
+#endif
+ const QImage *image = (const QImage *) pe->paintDevice();
+
+ context = CGBitmapContextCreate((void *) image->bits(), image->width(), image->height(),
+ 8, image->bytesPerLine(), colorspace, flags);
+
+ CGContextTranslateCTM(context, 0, image->height());
+ CGContextScaleCTM(context, 1, -1);
+
+ if (devType == QInternal::Widget) {
+ QRegion clip = p->paintEngine()->systemClip();
+ QTransform native = p->deviceTransform();
+ QTransform logical = p->combinedTransform();
+
+ if (p->hasClipping()) {
+ QRegion r = p->clipRegion();
+ r.translate(native.dx(), native.dy());
+ if (clip.isEmpty())
+ clip = r;
+ else
+ clip &= r;
+ }
+ qt_mac_clip_cg(context, clip, 0);
+
+ CGContextTranslateCTM(context, native.dx(), native.dy());
+ }
+ } else {
+ CGContextRetain(context);
+ }
+}
+
+
+/*****************************************************************************
+ QCoreGraphicsPaintEngine utility functions
+ *****************************************************************************/
+
+//conversion
+inline static float qt_mac_convert_color_to_cg(int c) { return ((float)c * 1000 / 255) / 1000; }
+inline static int qt_mac_convert_color_from_cg(float c) { return qRound(c * 255); }
+CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &t) {
+ return CGAffineTransformMake(t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy());
+}
+
+CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *paintDevice)
+{
+ bool isWidget = (paintDevice->devType() == QInternal::Widget);
+ return QCoreGraphicsPaintEngine::macDisplayColorSpace(isWidget ? static_cast<const QWidget *>(paintDevice)
+ : 0);
+}
+
+inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col, QPaintDevice *pdev)
+{
+ CGFloat components[] = {
+ qt_mac_convert_color_to_cg(col.red()),
+ qt_mac_convert_color_to_cg(col.green()),
+ qt_mac_convert_color_to_cg(col.blue()),
+ qt_mac_convert_color_to_cg(col.alpha())
+ };
+ return CGColorCreate(qt_mac_colorSpaceForDeviceType(pdev), components);
+}
+
+// There's architectural problems with using native gradients
+// on the Mac at the moment, so disable them.
+// #define QT_MAC_USE_NATIVE_GRADIENTS
+
+#ifdef QT_MAC_USE_NATIVE_GRADIENTS
+static bool drawGradientNatively(const QGradient *gradient)
+{
+ return gradient->spread() == QGradient::PadSpread;
+}
+
+// gradiant callback
+static void qt_mac_color_gradient_function(void *info, const CGFloat *in, CGFloat *out)
+{
+ QBrush *brush = static_cast<QBrush *>(info);
+ Q_ASSERT(brush && brush->gradient());
+
+ const QGradientStops stops = brush->gradient()->stops();
+ const int n = stops.count();
+ Q_ASSERT(n >= 1);
+ const QGradientStop *begin = stops.constBegin();
+ const QGradientStop *end = begin + n;
+
+ qreal p = in[0];
+ const QGradientStop *i = begin;
+ while (i != end && i->first < p)
+ ++i;
+
+ QRgb c;
+ if (i == begin) {
+ c = begin->second.rgba();
+ } else if (i == end) {
+ c = (end - 1)->second.rgba();
+ } else {
+ const QGradientStop &s1 = *(i - 1);
+ const QGradientStop &s2 = *i;
+ qreal p1 = s1.first;
+ qreal p2 = s2.first;
+ QRgb c1 = s1.second.rgba();
+ QRgb c2 = s2.second.rgba();
+ int idist = 256 * (p - p1) / (p2 - p1);
+ int dist = 256 - idist;
+ c = qRgba(INTERPOLATE_PIXEL_256(qRed(c1), dist, qRed(c2), idist),
+ INTERPOLATE_PIXEL_256(qGreen(c1), dist, qGreen(c2), idist),
+ INTERPOLATE_PIXEL_256(qBlue(c1), dist, qBlue(c2), idist),
+ INTERPOLATE_PIXEL_256(qAlpha(c1), dist, qAlpha(c2), idist));
+ }
+
+ out[0] = qt_mac_convert_color_to_cg(qRed(c));
+ out[1] = qt_mac_convert_color_to_cg(qGreen(c));
+ out[2] = qt_mac_convert_color_to_cg(qBlue(c));
+ out[3] = qt_mac_convert_color_to_cg(qAlpha(c));
+}
+#endif
+
+//clipping handling
+void QCoreGraphicsPaintEnginePrivate::resetClip()
+{
+ static bool inReset = false;
+ if (inReset)
+ return;
+ inReset = true;
+
+ CGAffineTransform old_xform = CGContextGetCTM(hd);
+
+ //setup xforms
+ CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform));
+ while (stackCount > 0) {
+ restoreGraphicsState();
+ }
+ saveGraphicsState();
+ inReset = false;
+ //reset xforms
+ CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd)));
+ CGContextConcatCTM(hd, old_xform);
+}
+
+static CGRect qt_mac_compose_rect(const QRectF &r, float off=0)
+{
+ return CGRectMake(r.x()+off, r.y()+off, r.width(), r.height());
+}
+
+static CGMutablePathRef qt_mac_compose_path(const QPainterPath &p, float off=0)
+{
+ CGMutablePathRef ret = CGPathCreateMutable();
+ QPointF startPt;
+ for (int i=0; i<p.elementCount(); ++i) {
+ const QPainterPath::Element &elm = p.elementAt(i);
+ switch (elm.type) {
+ case QPainterPath::MoveToElement:
+ if(i > 0
+ && p.elementAt(i - 1).x == startPt.x()
+ && p.elementAt(i - 1).y == startPt.y())
+ CGPathCloseSubpath(ret);
+ startPt = QPointF(elm.x, elm.y);
+ CGPathMoveToPoint(ret, 0, elm.x+off, elm.y+off);
+ break;
+ case QPainterPath::LineToElement:
+ CGPathAddLineToPoint(ret, 0, elm.x+off, elm.y+off);
+ break;
+ case QPainterPath::CurveToElement:
+ Q_ASSERT(p.elementAt(i+1).type == QPainterPath::CurveToDataElement);
+ Q_ASSERT(p.elementAt(i+2).type == QPainterPath::CurveToDataElement);
+ CGPathAddCurveToPoint(ret, 0,
+ elm.x+off, elm.y+off,
+ p.elementAt(i+1).x+off, p.elementAt(i+1).y+off,
+ p.elementAt(i+2).x+off, p.elementAt(i+2).y+off);
+ i+=2;
+ break;
+ default:
+ qFatal("QCoreGraphicsPaintEngine::drawPath(), unhandled type: %d", elm.type);
+ break;
+ }
+ }
+ if(!p.isEmpty()
+ && p.elementAt(p.elementCount() - 1).x == startPt.x()
+ && p.elementAt(p.elementCount() - 1).y == startPt.y())
+ CGPathCloseSubpath(ret);
+ return ret;
+}
+
+CGColorSpaceRef QCoreGraphicsPaintEngine::m_genericColorSpace = 0;
+QHash<QWidget*, CGColorSpaceRef> QCoreGraphicsPaintEngine::m_displayColorSpaceHash; // window -> color space
+bool QCoreGraphicsPaintEngine::m_postRoutineRegistered = false;
+
+CGColorSpaceRef QCoreGraphicsPaintEngine::macGenericColorSpace()
+{
+#if 0
+ if (!m_genericColorSpace) {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) {
+ m_genericColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ } else
+#endif
+ {
+ m_genericColorSpace = CGColorSpaceCreateDeviceRGB();
+ }
+ if (!m_postRoutineRegistered) {
+ m_postRoutineRegistered = true;
+ qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces);
+ }
+ }
+ return m_genericColorSpace;
+#else
+ // Just return the main display colorspace for the moment.
+ return macDisplayColorSpace();
+#endif
+}
+
+CGColorSpaceRef QCoreGraphicsPaintEngine::macDisplayColorSpace(const QWidget *widget)
+{
+ // The color space depends on which screen the widget's window is on.
+ // widget == 0 is a spacial case where we use the main display.
+ QWidget *window = widget ? widget->window() : 0;
+
+ // Check for cached color space and return if found.
+ if (m_displayColorSpaceHash.contains(window))
+ return m_displayColorSpaceHash.value(window);
+
+ // Find which display the window is on.
+ CGDirectDisplayID displayID;
+ if (window == 0) {
+ displayID = CGMainDisplayID();
+ } else {
+ const QRect &qrect = window->geometry();
+ CGRect rect = CGRectMake(qrect.x(), qrect.y(), qrect.width(), qrect.height());
+ CGDisplayCount throwAway;
+ CGDisplayErr dErr = CGGetDisplaysWithRect(rect, 1, &displayID, &throwAway);
+ if (dErr != kCGErrorSuccess)
+ displayID = CGMainDisplayID();
+ }
+
+ // Get the color space from the display profile.
+ CGColorSpaceRef colorSpace = 0;
+ CMProfileRef displayProfile = 0;
+ CMError err = CMGetProfileByAVID((CMDisplayIDType)displayID, &displayProfile);
+ if (err == noErr) {
+ colorSpace = CGColorSpaceCreateWithPlatformColorSpace(displayProfile);
+ CMCloseProfile(displayProfile);
+ }
+
+ // Fallback: use generic DeviceRGB
+ if (colorSpace == 0)
+ colorSpace = CGColorSpaceCreateDeviceRGB();
+
+ // Install cleanup routines
+ if (!m_postRoutineRegistered) {
+ m_postRoutineRegistered = true;
+ qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces);
+ }
+
+ // Cache and return.
+ m_displayColorSpaceHash.insert(window, colorSpace);
+ return colorSpace;
+}
+
+void QCoreGraphicsPaintEngine::cleanUpMacColorSpaces()
+{
+ if (m_genericColorSpace) {
+ CFRelease(m_genericColorSpace);
+ m_genericColorSpace = 0;
+ }
+ QHash<QWidget*, CGColorSpaceRef>::const_iterator it = m_displayColorSpaceHash.constBegin();
+ while (it != m_displayColorSpaceHash.constEnd()) {
+ if (it.value())
+ CFRelease(it.value());
+ ++it;
+ }
+ m_displayColorSpaceHash.clear();
+}
+
+void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform)
+{
+ CGAffineTransform old_xform = CGAffineTransformIdentity;
+ if(orig_xform) { //setup xforms
+ old_xform = CGContextGetCTM(hd);
+ CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform));
+ CGContextConcatCTM(hd, *orig_xform);
+ }
+
+ //do the clipping
+ CGContextBeginPath(hd);
+ if(rgn.isEmpty()) {
+ CGContextAddRect(hd, CGRectMake(0, 0, 0, 0));
+ } else {
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) {
+ QCFType<HIMutableShapeRef> shape = rgn.toHIMutableShape();
+ Q_ASSERT(!HIShapeIsEmpty(shape));
+ HIShapeReplacePathInCGContext(shape, hd);
+ } else
+#endif
+ {
+ QVector<QRect> rects = rgn.rects();
+ const int count = rects.size();
+ for(int i = 0; i < count; i++) {
+ const QRect &r = rects[i];
+ CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height());
+ CGContextAddRect(hd, mac_r);
+ }
+ }
+
+ }
+ CGContextClip(hd);
+
+ if(orig_xform) {//reset xforms
+ CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd)));
+ CGContextConcatCTM(hd, old_xform);
+ }
+}
+
+
+//pattern handling (tiling)
+#if 1
+# define QMACPATTERN_MASK_MULTIPLIER 32
+#else
+# define QMACPATTERN_MASK_MULTIPLIER 1
+#endif
+class QMacPattern
+{
+public:
+ QMacPattern() : as_mask(false), pdev(0), image(0) { data.bytes = 0; }
+ ~QMacPattern() { CGImageRelease(image); }
+ int width() {
+ if(image)
+ return CGImageGetWidth(image);
+ if(data.bytes)
+ return 8*QMACPATTERN_MASK_MULTIPLIER;
+ return data.pixmap.width();
+ }
+ int height() {
+ if(image)
+ return CGImageGetHeight(image);
+ if(data.bytes)
+ return 8*QMACPATTERN_MASK_MULTIPLIER;
+ return data.pixmap.height();
+ }
+
+ //input
+ QColor foreground;
+ bool as_mask;
+ struct {
+ QPixmap pixmap;
+ const uchar *bytes;
+ } data;
+ QPaintDevice *pdev;
+ //output
+ CGImageRef image;
+};
+static void qt_mac_draw_pattern(void *info, CGContextRef c)
+{
+ QMacPattern *pat = (QMacPattern*)info;
+ int w = 0, h = 0;
+ bool isBitmap = (pat->data.pixmap.depth() == 1);
+ if(!pat->image) { //lazy cache
+ if(pat->as_mask) {
+ Q_ASSERT(pat->data.bytes);
+ w = h = 8;
+#if (QMACPATTERN_MASK_MULTIPLIER == 1)
+ CGDataProviderRef provider = CGDataProviderCreateWithData(0, pat->data.bytes, w*h, 0);
+ pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false);
+ CGDataProviderRelease(provider);
+#else
+ const int numBytes = (w*h)/sizeof(uchar);
+ uchar xor_bytes[numBytes];
+ for(int i = 0; i < numBytes; ++i)
+ xor_bytes[i] = pat->data.bytes[i] ^ 0xFF;
+ CGDataProviderRef provider = CGDataProviderCreateWithData(0, xor_bytes, w*h, 0);
+ CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false);
+ CGDataProviderRelease(provider);
+
+ const QColor c0(0, 0, 0, 0), c1(255, 255, 255, 255);
+ QPixmap pm(w*QMACPATTERN_MASK_MULTIPLIER, h*QMACPATTERN_MASK_MULTIPLIER);
+ pm.fill(c0);
+ CGContextRef pm_ctx = qt_mac_cg_context(&pm);
+ CGContextSetFillColorWithColor(c, cgColorForQColor(c1, pat->pdev));
+ CGRect rect = CGRectMake(0, 0, w, h);
+ for(int x = 0; x < QMACPATTERN_MASK_MULTIPLIER; ++x) {
+ rect.origin.x = x * w;
+ for(int y = 0; y < QMACPATTERN_MASK_MULTIPLIER; ++y) {
+ rect.origin.y = y * h;
+ qt_mac_drawCGImage(pm_ctx, &rect, swatch);
+ }
+ }
+ pat->image = qt_mac_create_imagemask(pm, pm.rect());
+ CGImageRelease(swatch);
+ CGContextRelease(pm_ctx);
+ w *= QMACPATTERN_MASK_MULTIPLIER;
+ h *= QMACPATTERN_MASK_MULTIPLIER;
+#endif
+ } else {
+ w = pat->data.pixmap.width();
+ h = pat->data.pixmap.height();
+ if (isBitmap)
+ pat->image = qt_mac_create_imagemask(pat->data.pixmap, pat->data.pixmap.rect());
+ else
+ pat->image = (CGImageRef)pat->data.pixmap.macCGHandle();
+ }
+ } else {
+ w = CGImageGetWidth(pat->image);
+ h = CGImageGetHeight(pat->image);
+ }
+
+ //draw
+ bool needRestore = false;
+ if (CGImageIsMask(pat->image)) {
+ CGContextSaveGState(c);
+ CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground, pat->pdev));
+ }
+ CGRect rect = CGRectMake(0, 0, w, h);
+ qt_mac_drawCGImage(c, &rect, pat->image);
+ if(needRestore)
+ CGContextRestoreGState(c);
+}
+static void qt_mac_dispose_pattern(void *info)
+{
+ QMacPattern *pat = (QMacPattern*)info;
+ delete pat;
+}
+
+/*****************************************************************************
+ QCoreGraphicsPaintEngine member functions
+ *****************************************************************************/
+
+inline static QPaintEngine::PaintEngineFeatures qt_mac_cg_features()
+{
+ return QPaintEngine::PaintEngineFeatures(QPaintEngine::AllFeatures & ~QPaintEngine::PaintOutsidePaintEvent
+ & ~QPaintEngine::PerspectiveTransform
+ & ~QPaintEngine::ConicalGradientFill
+ & ~QPaintEngine::LinearGradientFill
+ & ~QPaintEngine::RadialGradientFill
+ & ~QPaintEngine::BrushStroke);
+}
+
+QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine()
+: QPaintEngine(*(new QCoreGraphicsPaintEnginePrivate), qt_mac_cg_features())
+{
+}
+
+QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr)
+: QPaintEngine(dptr, qt_mac_cg_features())
+{
+}
+
+QCoreGraphicsPaintEngine::~QCoreGraphicsPaintEngine()
+{
+}
+
+bool
+QCoreGraphicsPaintEngine::begin(QPaintDevice *pdev)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ if(isActive()) { // already active painting
+ qWarning("QCoreGraphicsPaintEngine::begin: Painter already active");
+ return false;
+ }
+
+ //initialization
+ d->pdev = pdev;
+ d->complexXForm = false;
+ d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth;
+ d->cosmeticPenSize = 1;
+ d->current.clipEnabled = false;
+ d->pixelSize = QPoint(1,1);
+ d->hd = qt_mac_cg_context(pdev);
+ if(d->hd) {
+ d->saveGraphicsState();
+ d->orig_xform = CGContextGetCTM(d->hd);
+ if (d->shading) {
+ CGShadingRelease(d->shading);
+ d->shading = 0;
+ }
+ d->setClip(0); //clear the context's clipping
+ }
+
+ setActive(true);
+
+ if(d->pdev->devType() == QInternal::Widget) { // device is a widget
+ QWidget *w = (QWidget*)d->pdev;
+ bool unclipped = w->testAttribute(Qt::WA_PaintUnclipped);
+
+ if((w->windowType() == Qt::Desktop)) {
+ if(!unclipped)
+ qWarning("QCoreGraphicsPaintEngine::begin: Does not support clipped desktop on Mac OS X");
+ // ## need to do [qt_mac_window_for(w) makeKeyAndOrderFront]; (need to rename the file)
+ } else if(unclipped) {
+ qWarning("QCoreGraphicsPaintEngine::begin: Does not support unclipped painting");
+ }
+ } else if(d->pdev->devType() == QInternal::Pixmap) { // device is a pixmap
+ QPixmap *pm = (QPixmap*)d->pdev;
+ if(pm->isNull()) {
+ qWarning("QCoreGraphicsPaintEngine::begin: Cannot paint null pixmap");
+ end();
+ return false;
+ }
+ }
+
+ setDirty(QPaintEngine::DirtyPen);
+ setDirty(QPaintEngine::DirtyBrush);
+ setDirty(QPaintEngine::DirtyBackground);
+ setDirty(QPaintEngine::DirtyHints);
+ return true;
+}
+
+bool
+QCoreGraphicsPaintEngine::end()
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ setActive(false);
+ if(d->pdev->devType() == QInternal::Widget && static_cast<QWidget*>(d->pdev)->windowType() == Qt::Desktop) {
+#ifndef QT_MAC_USE_COCOA
+ HideWindow(qt_mac_window_for(static_cast<QWidget*>(d->pdev)));
+#else
+// // ### need to do [qt_mac_window_for(static_cast<QWidget *>(d->pdev)) orderOut]; (need to rename)
+#endif
+
+ }
+ if(d->shading) {
+ CGShadingRelease(d->shading);
+ d->shading = 0;
+ }
+ d->pdev = 0;
+ if(d->hd) {
+ d->restoreGraphicsState();
+ CGContextSynchronize(d->hd);
+ CGContextRelease(d->hd);
+ d->hd = 0;
+ }
+ return true;
+}
+
+void
+QCoreGraphicsPaintEngine::updateState(const QPaintEngineState &state)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ QPaintEngine::DirtyFlags flags = state.state();
+
+ if (flags & DirtyTransform)
+ updateMatrix(state.transform());
+
+ if (flags & DirtyClipEnabled) {
+ if (state.isClipEnabled())
+ updateClipPath(painter()->clipPath(), Qt::ReplaceClip);
+ else
+ updateClipPath(QPainterPath(), Qt::NoClip);
+ }
+
+ if (flags & DirtyClipPath) {
+ updateClipPath(state.clipPath(), state.clipOperation());
+ } else if (flags & DirtyClipRegion) {
+ updateClipRegion(state.clipRegion(), state.clipOperation());
+ }
+
+ // If the clip has changed we need to update all other states
+ // too, since they are included in the system context on OSX,
+ // and changing the clip resets that context back to scratch.
+ if (flags & (DirtyClipPath | DirtyClipRegion | DirtyClipEnabled))
+ flags |= AllDirty;
+
+ if (flags & DirtyPen)
+ updatePen(state.pen());
+ if (flags & (DirtyBrush|DirtyBrushOrigin))
+ updateBrush(state.brush(), state.brushOrigin());
+ if (flags & DirtyFont)
+ updateFont(state.font());
+ if (flags & DirtyOpacity)
+ updateOpacity(state.opacity());
+ if (flags & DirtyHints)
+ updateRenderHints(state.renderHints());
+ if (flags & DirtyCompositionMode)
+ updateCompositionMode(state.compositionMode());
+
+ if (flags & (DirtyPen | DirtyTransform)) {
+ if (!d->current.pen.isCosmetic()) {
+ d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticNone;
+ } else if (d->current.transform.m11() < d->current.transform.m22()-1.0 ||
+ d->current.transform.m11() > d->current.transform.m22()+1.0) {
+ d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath;
+ d->cosmeticPenSize = d->adjustPenWidth(d->current.pen.widthF());
+ if (!d->cosmeticPenSize)
+ d->cosmeticPenSize = 1.0;
+ } else {
+ d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth;
+ static const float sqrt2 = sqrt(2);
+ qreal width = d->current.pen.widthF();
+ if (!width)
+ width = 1;
+ d->cosmeticPenSize = sqrt(pow(d->pixelSize.y(), 2) + pow(d->pixelSize.x(), 2)) / sqrt2 * width;
+ }
+ }
+}
+
+void
+QCoreGraphicsPaintEngine::updatePen(const QPen &pen)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+ d->current.pen = pen;
+ d->setStrokePen(pen);
+}
+
+void
+QCoreGraphicsPaintEngine::updateBrush(const QBrush &brush, const QPointF &brushOrigin)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+ d->current.brush = brush;
+
+#ifdef QT_MAC_USE_NATIVE_GRADIENTS
+ // Quartz supports only pad spread
+ if (const QGradient *gradient = brush.gradient()) {
+ if (drawGradientNatively(gradient)) {
+ gccaps |= QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill;
+ } else {
+ gccaps &= ~(QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill);
+ }
+ }
+#endif
+
+ if (d->shading) {
+ CGShadingRelease(d->shading);
+ d->shading = 0;
+ }
+ d->setFillBrush(brushOrigin);
+}
+
+void
+QCoreGraphicsPaintEngine::updateOpacity(qreal opacity)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ CGContextSetAlpha(d->hd, opacity);
+}
+
+void
+QCoreGraphicsPaintEngine::updateFont(const QFont &)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+ updatePen(d->current.pen);
+}
+
+void
+QCoreGraphicsPaintEngine::updateMatrix(const QTransform &transform)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (qt_is_nan(transform.m11()) || qt_is_nan(transform.m12()) || qt_is_nan(transform.m13())
+ || qt_is_nan(transform.m21()) || qt_is_nan(transform.m22()) || qt_is_nan(transform.m23())
+ || qt_is_nan(transform.m31()) || qt_is_nan(transform.m32()) || qt_is_nan(transform.m33()))
+ return;
+
+ d->current.transform = transform;
+ d->setTransform(transform.isIdentity() ? 0 : &transform);
+ d->complexXForm = (transform.m11() != 1 || transform.m22() != 1
+ || transform.m12() != 0 || transform.m21() != 0);
+ d->pixelSize = d->devicePixelSize(d->hd);
+}
+
+void
+QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+ if(op == Qt::NoClip) {
+ if(d->current.clipEnabled) {
+ d->current.clipEnabled = false;
+ d->current.clip = QRegion();
+ d->setClip(0);
+ }
+ } else {
+ if(!d->current.clipEnabled)
+ op = Qt::ReplaceClip;
+ d->current.clipEnabled = true;
+ QRegion clipRegion(p.toFillPolygon().toPolygon(), p.fillRule());
+ if(op == Qt::ReplaceClip) {
+ d->current.clip = clipRegion;
+ d->setClip(0);
+ if(p.isEmpty()) {
+ CGRect rect = CGRectMake(0, 0, 0, 0);
+ CGContextClipToRect(d->hd, rect);
+ } else {
+ CGMutablePathRef path = qt_mac_compose_path(p);
+ CGContextBeginPath(d->hd);
+ CGContextAddPath(d->hd, path);
+ if(p.fillRule() == Qt::WindingFill)
+ CGContextClip(d->hd);
+ else
+ CGContextEOClip(d->hd);
+ CGPathRelease(path);
+ }
+ } else if(op == Qt::IntersectClip) {
+ d->current.clip = d->current.clip.intersected(clipRegion);
+ d->setClip(&d->current.clip);
+ } else if(op == Qt::UniteClip) {
+ d->current.clip = d->current.clip.united(clipRegion);
+ d->setClip(&d->current.clip);
+ }
+ }
+}
+
+void
+QCoreGraphicsPaintEngine::updateClipRegion(const QRegion &clipRegion, Qt::ClipOperation op)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+ if(op == Qt::NoClip) {
+ d->current.clipEnabled = false;
+ d->current.clip = QRegion();
+ d->setClip(0);
+ } else {
+ if(!d->current.clipEnabled)
+ op = Qt::ReplaceClip;
+ d->current.clipEnabled = true;
+ if(op == Qt::IntersectClip)
+ d->current.clip = d->current.clip.intersected(clipRegion);
+ else if(op == Qt::ReplaceClip)
+ d->current.clip = clipRegion;
+ else if(op == Qt::UniteClip)
+ d->current.clip = d->current.clip.united(clipRegion);
+ d->setClip(&d->current.clip);
+ }
+}
+
+void
+QCoreGraphicsPaintEngine::drawPath(const QPainterPath &p)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ CGMutablePathRef path = qt_mac_compose_path(p);
+ uchar ops = QCoreGraphicsPaintEnginePrivate::CGStroke;
+ if(p.fillRule() == Qt::WindingFill)
+ ops |= QCoreGraphicsPaintEnginePrivate::CGFill;
+ else
+ ops |= QCoreGraphicsPaintEnginePrivate::CGEOFill;
+ CGContextBeginPath(d->hd);
+ d->drawPath(ops, path);
+ CGPathRelease(path);
+}
+
+void
+QCoreGraphicsPaintEngine::drawRects(const QRectF *rects, int rectCount)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ for (int i=0; i<rectCount; ++i) {
+ QRectF r = rects[i];
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ CGPathAddRect(path, 0, qt_mac_compose_rect(r));
+ d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill|QCoreGraphicsPaintEnginePrivate::CGStroke,
+ path);
+ CGPathRelease(path);
+ }
+}
+
+void
+QCoreGraphicsPaintEngine::drawPoints(const QPointF *points, int pointCount)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ if (d->current.pen.capStyle() == Qt::FlatCap)
+ CGContextSetLineCap(d->hd, kCGLineCapSquare);
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ for(int i=0; i < pointCount; i++) {
+ float x = points[i].x(), y = points[i].y();
+ CGPathMoveToPoint(path, 0, x, y);
+ CGPathAddLineToPoint(path, 0, x+0.001, y);
+ }
+
+ bool doRestore = false;
+ if(d->cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticNone && !(state->renderHints() & QPainter::Antialiasing)) {
+ //we don't want adjusted pens for point rendering
+ doRestore = true;
+ d->saveGraphicsState();
+ CGContextSetLineWidth(d->hd, d->current.pen.widthF());
+ }
+ d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path);
+ if (doRestore)
+ d->restoreGraphicsState();
+ CGPathRelease(path);
+ if (d->current.pen.capStyle() == Qt::FlatCap)
+ CGContextSetLineCap(d->hd, kCGLineCapButt);
+}
+
+void
+QCoreGraphicsPaintEngine::drawEllipse(const QRectF &r)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ CGAffineTransform transform = CGAffineTransformMakeScale(r.width() / r.height(), 1);
+ CGPathAddArc(path, &transform,(r.x() + (r.width() / 2)) / (r.width() / r.height()),
+ r.y() + (r.height() / 2), r.height() / 2, 0, (2 * M_PI), false);
+ d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill | QCoreGraphicsPaintEnginePrivate::CGStroke,
+ path);
+ CGPathRelease(path);
+}
+
+void
+QCoreGraphicsPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ CGPathMoveToPoint(path, 0, points[0].x(), points[0].y());
+ for(int x = 1; x < pointCount; ++x)
+ CGPathAddLineToPoint(path, 0, points[x].x(), points[x].y());
+ if(mode != PolylineMode && points[0] != points[pointCount-1])
+ CGPathAddLineToPoint(path, 0, points[0].x(), points[0].y());
+ uint op = QCoreGraphicsPaintEnginePrivate::CGStroke;
+ if (mode != PolylineMode)
+ op |= mode == OddEvenMode ? QCoreGraphicsPaintEnginePrivate::CGEOFill
+ : QCoreGraphicsPaintEnginePrivate::CGFill;
+ d->drawPath(op, path);
+ CGPathRelease(path);
+}
+
+void
+QCoreGraphicsPaintEngine::drawLines(const QLineF *lines, int lineCount)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ for(int i = 0; i < lineCount; i++) {
+ const QPointF start = lines[i].p1(), end = lines[i].p2();
+ CGPathMoveToPoint(path, 0, start.x(), start.y());
+ CGPathAddLineToPoint(path, 0, end.x(), end.y());
+ }
+ d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path);
+ CGPathRelease(path);
+}
+
+void QCoreGraphicsPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ if(pm.isNull())
+ return;
+
+ bool differentSize = (QRectF(0, 0, pm.width(), pm.height()) != sr), doRestore = false;
+ CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height());
+ QCFType<CGImageRef> image;
+ bool isBitmap = (pm.depth() == 1);
+ if (isBitmap) {
+ doRestore = true;
+ d->saveGraphicsState();
+
+ const QColor &col = d->current.pen.color();
+ CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col, d->pdev));
+ image = qt_mac_create_imagemask(pm, sr);
+ } else if (differentSize) {
+ QCFType<CGImageRef> img = pm.toMacCGImageRef();
+ image = CGImageCreateWithImageInRect(img, CGRectMake(qRound(sr.x()), qRound(sr.y()), qRound(sr.width()), qRound(sr.height())));
+ } else {
+ image = (CGImageRef)pm.macCGHandle();
+ }
+ qt_mac_drawCGImage(d->hd, &rect, image);
+ if (doRestore)
+ d->restoreGraphicsState();
+}
+
+static void drawImageReleaseData (void *info, const void *, size_t)
+{
+ delete static_cast<QImage *>(info);
+}
+
+CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0)
+{
+ QImage *image;
+ if (img.depth() != 32)
+ image = new QImage(img.convertToFormat(QImage::Format_ARGB32_Premultiplied));
+ else
+ image = new QImage(img);
+
+ uint cgflags = kCGImageAlphaNone;
+ switch (image->format()) {
+ case QImage::Format_ARGB32_Premultiplied:
+ cgflags = kCGImageAlphaPremultipliedFirst;
+ break;
+ case QImage::Format_ARGB32:
+ cgflags = kCGImageAlphaFirst;
+ break;
+ case QImage::Format_RGB32:
+ cgflags = kCGImageAlphaNoneSkipFirst;
+ default:
+ break;
+ }
+#if defined(kCGBitmapByteOrder32Host) //only needed because CGImage.h added symbols in the minor version
+ cgflags |= kCGBitmapByteOrder32Host;
+#endif
+ QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(image,
+ static_cast<const QImage *>(image)->bits(),
+ image->byteCount(),
+ drawImageReleaseData);
+ if (imagePtr)
+ *imagePtr = image;
+ return CGImageCreate(image->width(), image->height(), 8, 32,
+ image->bytesPerLine(),
+ QCoreGraphicsPaintEngine::macGenericColorSpace(),
+ cgflags, dataProvider, 0, false, kCGRenderingIntentDefault);
+
+}
+
+void QCoreGraphicsPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr,
+ Qt::ImageConversionFlags flags)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_UNUSED(flags);
+ Q_ASSERT(isActive());
+
+ if (img.isNull() || state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ const QImage *image;
+ QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(img, &image);
+ CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height());
+ if (QRectF(0, 0, img.width(), img.height()) != sr)
+ cgimage = CGImageCreateWithImageInRect(cgimage, CGRectMake(sr.x(), sr.y(),
+ sr.width(), sr.height()));
+ qt_mac_drawCGImage(d->hd, &rect, cgimage);
+}
+
+void QCoreGraphicsPaintEngine::initialize()
+{
+}
+
+void QCoreGraphicsPaintEngine::cleanup()
+{
+}
+
+CGContextRef
+QCoreGraphicsPaintEngine::handle() const
+{
+ return d_func()->hd;
+}
+
+void
+QCoreGraphicsPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap,
+ const QPointF &p)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ Q_ASSERT(isActive());
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ //save the old state
+ d->saveGraphicsState();
+
+ //setup the pattern
+ QMacPattern *qpattern = new QMacPattern;
+ qpattern->data.pixmap = pixmap;
+ qpattern->foreground = d->current.pen.color();
+ qpattern->pdev = d->pdev;
+ CGPatternCallbacks callbks;
+ callbks.version = 0;
+ callbks.drawPattern = qt_mac_draw_pattern;
+ callbks.releaseInfo = qt_mac_dispose_pattern;
+ const int width = qpattern->width(), height = qpattern->height();
+ CGAffineTransform trans = CGContextGetCTM(d->hd);
+ CGPatternRef pat = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height),
+ trans, width, height,
+ kCGPatternTilingNoDistortion, true, &callbks);
+ CGColorSpaceRef cs = CGColorSpaceCreatePattern(0);
+ CGContextSetFillColorSpace(d->hd, cs);
+ CGFloat component = 1.0; //just one
+ CGContextSetFillPattern(d->hd, pat, &component);
+ CGSize phase = CGSizeApplyAffineTransform(CGSizeMake(-(p.x()-r.x()), -(p.y()-r.y())), trans);
+ CGContextSetPatternPhase(d->hd, phase);
+
+ //fill the rectangle
+ CGRect mac_rect = CGRectMake(r.x(), r.y(), r.width(), r.height());
+ CGContextFillRect(d->hd, mac_rect);
+
+ //restore the state
+ d->restoreGraphicsState();
+ //cleanup
+ CGColorSpaceRelease(cs);
+ CGPatternRelease(pat);
+}
+
+void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem &item)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ if (d->current.transform.type() == QTransform::TxProject
+#ifndef QMAC_NATIVE_GRADIENTS
+ || painter()->pen().brush().gradient() //Just let the base engine "emulate" the gradient
+#endif
+ ) {
+ QPaintEngine::drawTextItem(pos, item);
+ return;
+ }
+
+ if (state->compositionMode() == QPainter::CompositionMode_Destination)
+ return;
+
+ const QTextItemInt &ti = static_cast<const QTextItemInt &>(item);
+
+ QPen oldPen = painter()->pen();
+ QBrush oldBrush = painter()->brush();
+ QPointF oldBrushOrigin = painter()->brushOrigin();
+ updatePen(Qt::NoPen);
+ updateBrush(oldPen.brush(), QPointF(0, 0));
+
+ Q_ASSERT(type() == QPaintEngine::CoreGraphics);
+
+ QFontEngine *fe = ti.fontEngine;
+
+ const bool textAA = state->renderHints() & QPainter::TextAntialiasing && fe->fontDef.pointSize > qt_antialiasing_threshold && !(fe->fontDef.styleStrategy & QFont::NoAntialias);
+ const bool lineAA = state->renderHints() & QPainter::Antialiasing;
+ if(textAA != lineAA)
+ CGContextSetShouldAntialias(d->hd, textAA);
+
+ if (ti.glyphs.numGlyphs) {
+ switch (fe->type()) {
+ case QFontEngine::Mac:
+#ifdef QT_MAC_USE_COCOA
+ static_cast<QCoreTextFontEngine *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height());
+#else
+ static_cast<QFontEngineMac *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height());
+#endif
+ break;
+ case QFontEngine::Box:
+ d->drawBoxTextItem(pos, ti);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if(textAA != lineAA)
+ CGContextSetShouldAntialias(d->hd, !textAA);
+
+ updatePen(oldPen);
+ updateBrush(oldBrush, oldBrushOrigin);
+}
+
+QPainter::RenderHints
+QCoreGraphicsPaintEngine::supportedRenderHints() const
+{
+ return QPainter::RenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
+}
+enum CGCompositeMode {
+ kCGCompositeModeClear = 0,
+ kCGCompositeModeCopy = 1,
+ kCGCompositeModeSourceOver = 2,
+ kCGCompositeModeSourceIn = 3,
+ kCGCompositeModeSourceOut = 4,
+ kCGCompositeModeSourceAtop = 5,
+ kCGCompositeModeDestinationOver = 6,
+ kCGCompositeModeDestinationIn = 7,
+ kCGCompositeModeDestinationOut = 8,
+ kCGCompositeModeDestinationAtop = 9,
+ kCGCompositeModeXOR = 10,
+ kCGCompositeModePlusDarker = 11, // (max (0, (1-d) + (1-s)))
+ kCGCompositeModePlusLighter = 12, // (min (1, s + d))
+ };
+extern "C" {
+ extern void CGContextSetCompositeOperation(CGContextRef, int);
+} // private function, but is in all versions of OS X.
+void
+QCoreGraphicsPaintEngine::updateCompositionMode(QPainter::CompositionMode mode)
+{
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) {
+ int cg_mode = kCGBlendModeNormal;
+ switch(mode) {
+ case QPainter::CompositionMode_Multiply:
+ cg_mode = kCGBlendModeMultiply;
+ break;
+ case QPainter::CompositionMode_Screen:
+ cg_mode = kCGBlendModeScreen;
+ break;
+ case QPainter::CompositionMode_Overlay:
+ cg_mode = kCGBlendModeOverlay;
+ break;
+ case QPainter::CompositionMode_Darken:
+ cg_mode = kCGBlendModeDarken;
+ break;
+ case QPainter::CompositionMode_Lighten:
+ cg_mode = kCGBlendModeLighten;
+ break;
+ case QPainter::CompositionMode_ColorDodge:
+ cg_mode = kCGBlendModeColorDodge;
+ break;
+ case QPainter::CompositionMode_ColorBurn:
+ cg_mode = kCGBlendModeColorBurn;
+ break;
+ case QPainter::CompositionMode_HardLight:
+ cg_mode = kCGBlendModeHardLight;
+ break;
+ case QPainter::CompositionMode_SoftLight:
+ cg_mode = kCGBlendModeSoftLight;
+ break;
+ case QPainter::CompositionMode_Difference:
+ cg_mode = kCGBlendModeDifference;
+ break;
+ case QPainter::CompositionMode_Exclusion:
+ cg_mode = kCGBlendModeExclusion;
+ break;
+ case QPainter::CompositionMode_Plus:
+ cg_mode = kCGBlendModePlusLighter;
+ break;
+ case QPainter::CompositionMode_SourceOver:
+ cg_mode = kCGBlendModeNormal;
+ break;
+ case QPainter::CompositionMode_DestinationOver:
+ cg_mode = kCGBlendModeDestinationOver;
+ break;
+ case QPainter::CompositionMode_Clear:
+ cg_mode = kCGBlendModeClear;
+ break;
+ case QPainter::CompositionMode_Source:
+ cg_mode = kCGBlendModeCopy;
+ break;
+ case QPainter::CompositionMode_Destination:
+ cg_mode = -1;
+ break;
+ case QPainter::CompositionMode_SourceIn:
+ cg_mode = kCGBlendModeSourceIn;
+ break;
+ case QPainter::CompositionMode_DestinationIn:
+ cg_mode = kCGCompositeModeDestinationIn;
+ break;
+ case QPainter::CompositionMode_SourceOut:
+ cg_mode = kCGBlendModeSourceOut;
+ break;
+ case QPainter::CompositionMode_DestinationOut:
+ cg_mode = kCGBlendModeDestinationOver;
+ break;
+ case QPainter::CompositionMode_SourceAtop:
+ cg_mode = kCGBlendModeSourceAtop;
+ break;
+ case QPainter::CompositionMode_DestinationAtop:
+ cg_mode = kCGBlendModeDestinationAtop;
+ break;
+ case QPainter::CompositionMode_Xor:
+ cg_mode = kCGBlendModeXOR;
+ break;
+ default:
+ break;
+ }
+ if (cg_mode > -1) {
+ CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode));
+ }
+ } else
+#endif
+ // The standard porter duff ops.
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_3
+ && mode <= QPainter::CompositionMode_Xor) {
+ int cg_mode = kCGCompositeModeCopy;
+ switch (mode) {
+ case QPainter::CompositionMode_SourceOver:
+ cg_mode = kCGCompositeModeSourceOver;
+ break;
+ case QPainter::CompositionMode_DestinationOver:
+ cg_mode = kCGCompositeModeDestinationOver;
+ break;
+ case QPainter::CompositionMode_Clear:
+ cg_mode = kCGCompositeModeClear;
+ break;
+ default:
+ qWarning("QCoreGraphicsPaintEngine: Unhandled composition mode %d", (int)mode);
+ break;
+ case QPainter::CompositionMode_Source:
+ cg_mode = kCGCompositeModeCopy;
+ break;
+ case QPainter::CompositionMode_Destination:
+ cg_mode = CGCompositeMode(-1);
+ break;
+ case QPainter::CompositionMode_SourceIn:
+ cg_mode = kCGCompositeModeSourceIn;
+ break;
+ case QPainter::CompositionMode_DestinationIn:
+ cg_mode = kCGCompositeModeDestinationIn;
+ break;
+ case QPainter::CompositionMode_SourceOut:
+ cg_mode = kCGCompositeModeSourceOut;
+ break;
+ case QPainter::CompositionMode_DestinationOut:
+ cg_mode = kCGCompositeModeDestinationOut;
+ break;
+ case QPainter::CompositionMode_SourceAtop:
+ cg_mode = kCGCompositeModeSourceAtop;
+ break;
+ case QPainter::CompositionMode_DestinationAtop:
+ cg_mode = kCGCompositeModeDestinationAtop;
+ break;
+ case QPainter::CompositionMode_Xor:
+ cg_mode = kCGCompositeModeXOR;
+ break;
+ }
+ if (cg_mode > -1)
+ CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode));
+ } else {
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
+ bool needPrivateAPI = false;
+ if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) {
+ int cg_mode = kCGBlendModeNormal;
+ switch (mode) {
+ case QPainter::CompositionMode_Multiply:
+ cg_mode = kCGBlendModeMultiply;
+ break;
+ case QPainter::CompositionMode_Screen:
+ cg_mode = kCGBlendModeScreen;
+ break;
+ case QPainter::CompositionMode_Overlay:
+ cg_mode = kCGBlendModeOverlay;
+ break;
+ case QPainter::CompositionMode_Darken:
+ cg_mode = kCGBlendModeDarken;
+ break;
+ case QPainter::CompositionMode_Lighten:
+ cg_mode = kCGBlendModeLighten;
+ break;
+ case QPainter::CompositionMode_ColorDodge:
+ cg_mode = kCGBlendModeColorDodge;
+ break;
+ case QPainter::CompositionMode_ColorBurn:
+ cg_mode = kCGBlendModeColorBurn;
+ break;
+ case QPainter::CompositionMode_HardLight:
+ cg_mode = kCGBlendModeHardLight;
+ break;
+ case QPainter::CompositionMode_SoftLight:
+ cg_mode = kCGBlendModeSoftLight;
+ break;
+ case QPainter::CompositionMode_Difference:
+ cg_mode = kCGBlendModeDifference;
+ break;
+ case QPainter::CompositionMode_Exclusion:
+ cg_mode = kCGBlendModeExclusion;
+ break;
+ case QPainter::CompositionMode_Plus:
+ needPrivateAPI = true;
+ cg_mode = kCGCompositeModePlusLighter;
+ break;
+ default:
+ break;
+ }
+ if (!needPrivateAPI)
+ CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode));
+ else
+ CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode));
+ }
+#endif
+ }
+}
+
+void
+QCoreGraphicsPaintEngine::updateRenderHints(QPainter::RenderHints hints)
+{
+ Q_D(QCoreGraphicsPaintEngine);
+ CGContextSetShouldAntialias(d->hd, hints & QPainter::Antialiasing);
+ static const CGFloat ScaleFactor = qt_mac_get_scalefactor();
+ if (ScaleFactor > 1.) {
+ CGContextSetInterpolationQuality(d->hd, kCGInterpolationHigh);
+ } else {
+ CGContextSetInterpolationQuality(d->hd, (hints & QPainter::SmoothPixmapTransform) ?
+ kCGInterpolationHigh : kCGInterpolationNone);
+ }
+ bool textAntialiasing = (hints & QPainter::TextAntialiasing) == QPainter::TextAntialiasing;
+ if (!textAntialiasing || d->disabledSmoothFonts) {
+ d->disabledSmoothFonts = !textAntialiasing;
+ CGContextSetShouldSmoothFonts(d->hd, textAntialiasing);
+ }
+}
+
+/*
+ Returns the size of one device pixel in user-space coordinates.
+*/
+QPointF QCoreGraphicsPaintEnginePrivate::devicePixelSize(CGContextRef)
+{
+ QPointF p1 = current.transform.inverted().map(QPointF(0, 0));
+ QPointF p2 = current.transform.inverted().map(QPointF(1, 1));
+ return QPointF(qAbs(p2.x() - p1.x()), qAbs(p2.y() - p1.y()));
+}
+
+/*
+ Adjusts the pen width so we get correct line widths in the
+ non-transformed, aliased case.
+*/
+float QCoreGraphicsPaintEnginePrivate::adjustPenWidth(float penWidth)
+{
+ Q_Q(QCoreGraphicsPaintEngine);
+ float ret = penWidth;
+ if (!complexXForm && !(q->state->renderHints() & QPainter::Antialiasing)) {
+ if (penWidth < 2)
+ ret = 1;
+ else if (penWidth < 3)
+ ret = 1.5;
+ else
+ ret = penWidth -1;
+ }
+ return ret;
+}
+
+void
+QCoreGraphicsPaintEnginePrivate::setStrokePen(const QPen &pen)
+{
+ //pencap
+ CGLineCap cglinecap = kCGLineCapButt;
+ if(pen.capStyle() == Qt::SquareCap)
+ cglinecap = kCGLineCapSquare;
+ else if(pen.capStyle() == Qt::RoundCap)
+ cglinecap = kCGLineCapRound;
+ CGContextSetLineCap(hd, cglinecap);
+ CGContextSetLineWidth(hd, adjustPenWidth(pen.widthF()));
+
+ //join
+ CGLineJoin cglinejoin = kCGLineJoinMiter;
+ if(pen.joinStyle() == Qt::BevelJoin)
+ cglinejoin = kCGLineJoinBevel;
+ else if(pen.joinStyle() == Qt::RoundJoin)
+ cglinejoin = kCGLineJoinRound;
+ CGContextSetLineJoin(hd, cglinejoin);
+// CGContextSetMiterLimit(hd, pen.miterLimit());
+
+ //pen style
+ QVector<CGFloat> linedashes;
+ if(pen.style() == Qt::CustomDashLine) {
+ QVector<qreal> customs = pen.dashPattern();
+ for(int i = 0; i < customs.size(); ++i)
+ linedashes.append(customs.at(i));
+ } else if(pen.style() == Qt::DashLine) {
+ linedashes.append(4);
+ linedashes.append(2);
+ } else if(pen.style() == Qt::DotLine) {
+ linedashes.append(1);
+ linedashes.append(2);
+ } else if(pen.style() == Qt::DashDotLine) {
+ linedashes.append(4);
+ linedashes.append(2);
+ linedashes.append(1);
+ linedashes.append(2);
+ } else if(pen.style() == Qt::DashDotDotLine) {
+ linedashes.append(4);
+ linedashes.append(2);
+ linedashes.append(1);
+ linedashes.append(2);
+ linedashes.append(1);
+ linedashes.append(2);
+ }
+ const CGFloat cglinewidth = pen.widthF() <= 0.0f ? 1.0f : float(pen.widthF());
+ for(int i = 0; i < linedashes.size(); ++i) {
+ linedashes[i] *= cglinewidth;
+ if(cglinewidth < 3 && (cglinecap == kCGLineCapSquare || cglinecap == kCGLineCapRound)) {
+ if((i%2))
+ linedashes[i] += cglinewidth/2;
+ else
+ linedashes[i] -= cglinewidth/2;
+ }
+ }
+ CGContextSetLineDash(hd, pen.dashOffset() * cglinewidth, linedashes.data(), linedashes.size());
+
+ // color
+ CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color(), pdev));
+}
+
+// Add our own patterns here to deal with the fact that the coordinate system
+// is flipped vertically with Quartz2D.
+static const uchar *qt_mac_patternForBrush(int brushStyle)
+{
+ Q_ASSERT(brushStyle > Qt::SolidPattern && brushStyle < Qt::LinearGradientPattern);
+ static const uchar dense1_pat[] = { 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00 };
+ static const uchar dense2_pat[] = { 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88 };
+ static const uchar dense3_pat[] = { 0x11, 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa };
+ static const uchar dense4_pat[] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 };
+ static const uchar dense5_pat[] = { 0xee, 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55 };
+ static const uchar dense6_pat[] = { 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77 };
+ static const uchar dense7_pat[] = { 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff };
+ static const uchar hor_pat[] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff };
+ static const uchar ver_pat[] = { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef };
+ static const uchar cross_pat[] = { 0xef, 0xef, 0xef, 0xef, 0x00, 0xef, 0xef, 0xef };
+ static const uchar fdiag_pat[] = { 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe };
+ static const uchar bdiag_pat[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f };
+ static const uchar dcross_pat[] = { 0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e };
+ static const uchar *const pat_tbl[] = {
+ dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat,
+ dense6_pat, dense7_pat,
+ hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat };
+ return pat_tbl[brushStyle - Qt::Dense1Pattern];
+}
+
+void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset)
+{
+ // pattern
+ Qt::BrushStyle bs = current.brush.style();
+#ifdef QT_MAC_USE_NATIVE_GRADIENTS
+ if (bs == Qt::LinearGradientPattern || bs == Qt::RadialGradientPattern) {
+ const QGradient *grad = static_cast<const QGradient*>(current.brush.gradient());
+ if (drawGradientNatively(grad)) {
+ Q_ASSERT(grad->spread() == QGradient::PadSpread);
+
+ static const CGFloat domain[] = { 0.0f, +1.0f };
+ static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, 0 };
+ CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast<void *>(¤t.brush),
+ 1, domain, 4, 0, &callbacks);
+
+ CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev);
+ if (bs == Qt::LinearGradientPattern) {
+ const QLinearGradient *linearGrad = static_cast<const QLinearGradient *>(grad);
+ const QPointF start(linearGrad->start());
+ const QPointF stop(linearGrad->finalStop());
+ shading = CGShadingCreateAxial(colorspace, CGPointMake(start.x(), start.y()),
+ CGPointMake(stop.x(), stop.y()), fill_func, true, true);
+ } else {
+ Q_ASSERT(bs == Qt::RadialGradientPattern);
+ const QRadialGradient *radialGrad = static_cast<const QRadialGradient *>(grad);
+ QPointF center(radialGrad->center());
+ QPointF focal(radialGrad->focalPoint());
+ qreal radius = radialGrad->radius();
+ qreal focalRadius = radialGrad->focalRadius();
+ shading = CGShadingCreateRadial(colorspace, CGPointMake(focal.x(), focal.y()),
+ focalRadius, CGPointMake(center.x(), center.y()), radius, fill_func, false, true);
+ }
+
+ CGFunctionRelease(fill_func);
+ }
+ } else
+#endif
+ if(bs != Qt::SolidPattern && bs != Qt::NoBrush
+#ifndef QT_MAC_USE_NATIVE_GRADIENTS
+ && (bs < Qt::LinearGradientPattern || bs > Qt::ConicalGradientPattern)
+#endif
+ )
+ {
+ QMacPattern *qpattern = new QMacPattern;
+ qpattern->pdev = pdev;
+ CGFloat components[4] = { 1.0, 1.0, 1.0, 1.0 };
+ CGColorSpaceRef base_colorspace = 0;
+ if(bs == Qt::TexturePattern) {
+ qpattern->data.pixmap = current.brush.texture();
+ if(qpattern->data.pixmap.isQBitmap()) {
+ const QColor &col = current.brush.color();
+ components[0] = qt_mac_convert_color_to_cg(col.red());
+ components[1] = qt_mac_convert_color_to_cg(col.green());
+ components[2] = qt_mac_convert_color_to_cg(col.blue());
+ base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace();
+ }
+ } else {
+ qpattern->as_mask = true;
+
+ qpattern->data.bytes = qt_mac_patternForBrush(bs);
+ const QColor &col = current.brush.color();
+ components[0] = qt_mac_convert_color_to_cg(col.red());
+ components[1] = qt_mac_convert_color_to_cg(col.green());
+ components[2] = qt_mac_convert_color_to_cg(col.blue());
+ base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace();
+ }
+ int width = qpattern->width(), height = qpattern->height();
+ qpattern->foreground = current.brush.color();
+
+ CGColorSpaceRef fill_colorspace = CGColorSpaceCreatePattern(base_colorspace);
+ CGContextSetFillColorSpace(hd, fill_colorspace);
+
+ CGAffineTransform xform = CGContextGetCTM(hd);
+ xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(current.brush.transform()), xform);
+ xform = CGAffineTransformTranslate(xform, offset.x(), offset.y());
+
+ CGPatternCallbacks callbks;
+ callbks.version = 0;
+ callbks.drawPattern = qt_mac_draw_pattern;
+ callbks.releaseInfo = qt_mac_dispose_pattern;
+ CGPatternRef fill_pattern = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height),
+ xform, width, height, kCGPatternTilingNoDistortion,
+ !base_colorspace, &callbks);
+ CGContextSetFillPattern(hd, fill_pattern, components);
+
+ CGPatternRelease(fill_pattern);
+ CGColorSpaceRelease(fill_colorspace);
+ } else if(bs != Qt::NoBrush) {
+ CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color(), pdev));
+ }
+}
+
+void
+QCoreGraphicsPaintEnginePrivate::setClip(const QRegion *rgn)
+{
+ Q_Q(QCoreGraphicsPaintEngine);
+ if(hd) {
+ resetClip();
+ QRegion sysClip = q->systemClip();
+ if(!sysClip.isEmpty())
+ qt_mac_clip_cg(hd, sysClip, &orig_xform);
+ if(rgn)
+ qt_mac_clip_cg(hd, *rgn, 0);
+ }
+}
+
+struct qt_mac_cg_transform_path {
+ CGMutablePathRef path;
+ CGAffineTransform transform;
+};
+
+void qt_mac_cg_transform_path_apply(void *info, const CGPathElement *element)
+{
+ Q_ASSERT(info && element);
+ qt_mac_cg_transform_path *t = (qt_mac_cg_transform_path*)info;
+ switch(element->type) {
+ case kCGPathElementMoveToPoint:
+ CGPathMoveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y);
+ break;
+ case kCGPathElementAddLineToPoint:
+ CGPathAddLineToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y);
+ break;
+ case kCGPathElementAddQuadCurveToPoint:
+ CGPathAddQuadCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y,
+ element->points[1].x, element->points[1].y);
+ break;
+ case kCGPathElementAddCurveToPoint:
+ CGPathAddCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y,
+ element->points[1].x, element->points[1].y,
+ element->points[2].x, element->points[2].y);
+ break;
+ case kCGPathElementCloseSubpath:
+ CGPathCloseSubpath(t->path);
+ break;
+ default:
+ qDebug() << "Unhandled path transform type: " << element->type;
+ }
+}
+
+void QCoreGraphicsPaintEnginePrivate::drawPath(uchar ops, CGMutablePathRef path)
+{
+ Q_Q(QCoreGraphicsPaintEngine);
+ Q_ASSERT((ops & (CGFill | CGEOFill)) != (CGFill | CGEOFill)); //can't really happen
+ if((ops & (CGFill | CGEOFill))) {
+ if (shading) {
+ Q_ASSERT(path);
+ CGContextBeginPath(hd);
+ CGContextAddPath(hd, path);
+ saveGraphicsState();
+ if (ops & CGFill)
+ CGContextClip(hd);
+ else if (ops & CGEOFill)
+ CGContextEOClip(hd);
+ if (current.brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) {
+ CGRect boundingBox = CGPathGetBoundingBox(path);
+ CGContextConcatCTM(hd,
+ CGAffineTransformMake(boundingBox.size.width, 0,
+ 0, boundingBox.size.height,
+ boundingBox.origin.x, boundingBox.origin.y));
+ }
+ CGContextDrawShading(hd, shading);
+ restoreGraphicsState();
+ ops &= ~CGFill;
+ ops &= ~CGEOFill;
+ } else if (current.brush.style() == Qt::NoBrush) {
+ ops &= ~CGFill;
+ ops &= ~CGEOFill;
+ }
+ }
+ if((ops & CGStroke) && current.pen.style() == Qt::NoPen)
+ ops &= ~CGStroke;
+
+ if(ops & (CGEOFill | CGFill)) {
+ CGContextBeginPath(hd);
+ CGContextAddPath(hd, path);
+ if (ops & CGEOFill) {
+ CGContextEOFillPath(hd);
+ } else {
+ CGContextFillPath(hd);
+ }
+ }
+
+ // Avoid saving and restoring the context if we can.
+ const bool needContextSave = (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone ||
+ !(q->state->renderHints() & QPainter::Antialiasing));
+ if(ops & CGStroke) {
+ if (needContextSave)
+ saveGraphicsState();
+ CGContextBeginPath(hd);
+
+ // Translate a fraction of a pixel size in the y direction
+ // to make sure that primitives painted at pixel borders
+ // fills the right pixel. This is needed since the y xais
+ // in the Quartz coordinate system is inverted compared to Qt.
+ if (!(q->state->renderHints() & QPainter::Antialiasing)) {
+ if (current.pen.style() == Qt::SolidLine || current.pen.width() >= 3)
+ CGContextTranslateCTM(hd, double(pixelSize.x()) * 0.25, double(pixelSize.y()) * 0.25);
+ else if (current.pen.style() == Qt::DotLine && QSysInfo::MacintoshVersion == QSysInfo::MV_10_3)
+ ; // Do nothing.
+ else
+ CGContextTranslateCTM(hd, 0, double(pixelSize.y()) * 0.1);
+ }
+
+ if (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone) {
+ // If antialiazing is enabled, use the cosmetic pen size directly.
+ if (q->state->renderHints() & QPainter::Antialiasing)
+ CGContextSetLineWidth(hd, cosmeticPenSize);
+ else if (current.pen.widthF() <= 1)
+ CGContextSetLineWidth(hd, cosmeticPenSize * 0.9f);
+ else
+ CGContextSetLineWidth(hd, cosmeticPenSize);
+ }
+ if(cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath) {
+ qt_mac_cg_transform_path t;
+ t.transform = qt_mac_convert_transform_to_cg(current.transform);
+ t.path = CGPathCreateMutable();
+ CGPathApply(path, &t, qt_mac_cg_transform_path_apply); //transform the path
+ setTransform(0); //unset the context transform
+ CGContextSetLineWidth(hd, cosmeticPenSize);
+ CGContextAddPath(hd, t.path);
+ CGPathRelease(t.path);
+ } else {
+ CGContextAddPath(hd, path);
+ }
+
+ CGContextStrokePath(hd);
+ if (needContextSave)
+ restoreGraphicsState();
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac_p.h b/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac_p.h
new file mode 100644
index 0000000..9d33e9f
--- /dev/null
+++ b/src/cpp/desktop/qt-patch/mac-colorspace/qpaintengine_mac_p.h
@@ -0,0 +1,256 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info at nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QPAINTENGINE_MAC_P_H
+#define QPAINTENGINE_MAC_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtGui/qpaintengine.h"
+#include "private/qt_mac_p.h"
+#include "private/qpaintengine_p.h"
+#include "private/qpolygonclipper_p.h"
+#include "private/qfont_p.h"
+#include "QtCore/qhash.h"
+
+typedef struct CGColorSpace *CGColorSpaceRef;
+QT_BEGIN_NAMESPACE
+
+class QCoreGraphicsPaintEnginePrivate;
+class QCoreGraphicsPaintEngine : public QPaintEngine
+{
+ Q_DECLARE_PRIVATE(QCoreGraphicsPaintEngine)
+
+public:
+ QCoreGraphicsPaintEngine();
+ ~QCoreGraphicsPaintEngine();
+
+ bool begin(QPaintDevice *pdev);
+ bool end();
+ static CGColorSpaceRef macGenericColorSpace();
+ static CGColorSpaceRef macDisplayColorSpace(const QWidget *widget = 0);
+
+ void updateState(const QPaintEngineState &state);
+
+ void updatePen(const QPen &pen);
+ void updateBrush(const QBrush &brush, const QPointF &pt);
+ void updateFont(const QFont &font);
+ void updateOpacity(qreal opacity);
+ void updateMatrix(const QTransform &matrix);
+ void updateTransform(const QTransform &matrix);
+ void updateClipRegion(const QRegion ®ion, Qt::ClipOperation op);
+ void updateClipPath(const QPainterPath &path, Qt::ClipOperation op);
+ void updateCompositionMode(QPainter::CompositionMode mode);
+ void updateRenderHints(QPainter::RenderHints hints);
+
+ void drawLines(const QLineF *lines, int lineCount);
+ void drawRects(const QRectF *rects, int rectCount);
+ void drawPoints(const QPointF *p, int pointCount);
+ void drawEllipse(const QRectF &r);
+ void drawPath(const QPainterPath &path);
+
+ void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode);
+ void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr);
+ void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s);
+
+ void drawTextItem(const QPointF &pos, const QTextItem &item);
+ void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
+ Qt::ImageConversionFlags flags = Qt::AutoColor);
+
+ Type type() const { return QPaintEngine::CoreGraphics; }
+
+ CGContextRef handle() const;
+
+ static void initialize();
+ static void cleanup();
+
+ QPainter::RenderHints supportedRenderHints() const;
+
+ //avoid partial shadowed overload warnings...
+ void drawLines(const QLine *lines, int lineCount) { QPaintEngine::drawLines(lines, lineCount); }
+ void drawRects(const QRect *rects, int rectCount) { QPaintEngine::drawRects(rects, rectCount); }
+ void drawPoints(const QPoint *p, int pointCount) { QPaintEngine::drawPoints(p, pointCount); }
+ void drawEllipse(const QRect &r) { QPaintEngine::drawEllipse(r); }
+ void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode)
+ { QPaintEngine::drawPolygon(points, pointCount, mode); }
+
+ bool supportsTransformations(qreal, const QTransform &) const { return true; };
+
+protected:
+ friend class QMacPrintEngine;
+ friend class QMacPrintEnginePrivate;
+ friend void qt_mac_display_change_callbk(CGDirectDisplayID, CGDisplayChangeSummaryFlags, void *);
+ friend void qt_color_profile_changed(CFNotificationCenterRef center, void *,
+ CFStringRef , const void *, CFDictionaryRef);
+ QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr);
+
+private:
+ static bool m_postRoutineRegistered;
+ static CGColorSpaceRef m_genericColorSpace;
+ static QHash<QWidget*, CGColorSpaceRef> m_displayColorSpaceHash; // window -> color space
+ static void cleanUpMacColorSpaces();
+ Q_DISABLE_COPY(QCoreGraphicsPaintEngine)
+};
+
+/*****************************************************************************
+ Private data
+ *****************************************************************************/
+class QCoreGraphicsPaintEnginePrivate : public QPaintEnginePrivate
+{
+ Q_DECLARE_PUBLIC(QCoreGraphicsPaintEngine)
+public:
+ QCoreGraphicsPaintEnginePrivate()
+ : hd(0), shading(0), stackCount(0), complexXForm(false), disabledSmoothFonts(false)
+ {
+ }
+
+ struct {
+ QPen pen;
+ QBrush brush;
+ uint clipEnabled : 1;
+ QRegion clip;
+ QTransform transform;
+ } current;
+
+ //state info (shared with QD)
+ CGAffineTransform orig_xform;
+
+ //cg structures
+ CGContextRef hd;
+ CGShadingRef shading;
+ int stackCount;
+ bool complexXForm;
+ bool disabledSmoothFonts;
+ enum { CosmeticNone, CosmeticTransformPath, CosmeticSetPenWidth } cosmeticPen;
+
+ // pixel and cosmetic pen size in user coordinates.
+ QPointF pixelSize;
+ float cosmeticPenSize;
+
+ //internal functions
+ enum { CGStroke=0x01, CGEOFill=0x02, CGFill=0x04 };
+ void drawPath(uchar ops, CGMutablePathRef path = 0);
+ void setClip(const QRegion *rgn=0);
+ void resetClip();
+ void setFillBrush(const QPointF &origin=QPoint());
+ void setStrokePen(const QPen &pen);
+ inline void saveGraphicsState();
+ inline void restoreGraphicsState();
+ float penOffset();
+ QPointF devicePixelSize(CGContextRef context);
+ float adjustPenWidth(float penWidth);
+ inline void setTransform(const QTransform *matrix=0)
+ {
+ CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd)));
+ CGAffineTransform xform = orig_xform;
+ if(matrix) {
+ extern CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &);
+ xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(*matrix), xform);
+ }
+ CGContextConcatCTM(hd, xform);
+ CGContextSetTextMatrix(hd, xform);
+ }
+};
+
+inline void QCoreGraphicsPaintEnginePrivate::saveGraphicsState()
+{
+ ++stackCount;
+ CGContextSaveGState(hd);
+}
+
+inline void QCoreGraphicsPaintEnginePrivate::restoreGraphicsState()
+{
+ --stackCount;
+ Q_ASSERT(stackCount >= 0);
+ CGContextRestoreGState(hd);
+}
+
+class QMacQuartzPaintDevice : public QPaintDevice
+{
+public:
+ QMacQuartzPaintDevice(CGContextRef cg, int width, int height, int bytesPerLine)
+ : mCG(cg), mWidth(width), mHeight(height), mBytesPerLine(bytesPerLine)
+ { }
+ int devType() const { return QInternal::MacQuartz; }
+ CGContextRef cgContext() const { return mCG; }
+ int metric(PaintDeviceMetric metric) const {
+ switch (metric) {
+ case PdmWidth:
+ return mWidth;
+ case PdmHeight:
+ return mHeight;
+ case PdmWidthMM:
+ return (qt_defaultDpiX() * mWidth) / 2.54;
+ case PdmHeightMM:
+ return (qt_defaultDpiY() * mHeight) / 2.54;
+ case PdmNumColors:
+ return 0;
+ case PdmDepth:
+ return 32;
+ case PdmDpiX:
+ case PdmPhysicalDpiX:
+ return qt_defaultDpiX();
+ case PdmDpiY:
+ case PdmPhysicalDpiY:
+ return qt_defaultDpiY();
+ }
+ return 0;
+ }
+ QPaintEngine *paintEngine() const { qWarning("This function should never be called."); return 0; }
+private:
+ CGContextRef mCG;
+ int mWidth;
+ int mHeight;
+ int mBytesPerLine;
+};
+
+QT_END_NAMESPACE
+
+#endif // QPAINTENGINE_MAC_P_H
diff --git a/src/cpp/desktop/qt.conf b/src/cpp/desktop/qt.conf
new file mode 100644
index 0000000..e69de29
diff --git a/src/cpp/desktop/reload.png b/src/cpp/desktop/reload.png
new file mode 100644
index 0000000..518b0f7
Binary files /dev/null and b/src/cpp/desktop/reload.png differ
diff --git a/src/cpp/desktop/reload_mac.png b/src/cpp/desktop/reload_mac.png
new file mode 100644
index 0000000..5b7f0dd
Binary files /dev/null and b/src/cpp/desktop/reload_mac.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-data.png
new file mode 100755
index 0000000..e89f00e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-project.png
new file mode 100644
index 0000000..99bc533
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/rstudio.png
new file mode 100644
index 0000000..9f5c478
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-css.png
new file mode 100644
index 0000000..ac2cb98
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-html.png
new file mode 100644
index 0000000..914c53e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-javascript.png
new file mode 100644
index 0000000..e2d0b05
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-markdown.png
new file mode 100644
index 0000000..72a9f29
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-doc.png
new file mode 100644
index 0000000..34e7d64
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-html.png
new file mode 100644
index 0000000..50f21cb
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-markdown.png
new file mode 100644
index 0000000..63d812a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-presentation.png
new file mode 100644
index 0000000..4e53fdf
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-source.png
new file mode 100644
index 0000000..5d75bbc
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-sweave.png
new file mode 100755
index 0000000..9e8a567
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-tex.png
new file mode 100644
index 0000000..06a0a0d
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/128x128/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-data.png
new file mode 100755
index 0000000..d3ac97a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-project.png
new file mode 100644
index 0000000..b939f2a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/rstudio.png
new file mode 100644
index 0000000..4b3b041
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-css.png
new file mode 100644
index 0000000..ad013ac
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-html.png
new file mode 100644
index 0000000..6d4cfc9
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-javascript.png
new file mode 100644
index 0000000..f496444
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-markdown.png
new file mode 100644
index 0000000..85b17c4
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-doc.png
new file mode 100644
index 0000000..957d47e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-html.png
new file mode 100644
index 0000000..1fabb60
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-markdown.png
new file mode 100644
index 0000000..2ba2caf
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-presentation.png
new file mode 100644
index 0000000..70b79c8
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-source.png
new file mode 100644
index 0000000..84701d7
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-sweave.png
new file mode 100755
index 0000000..c07c2be
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-tex.png
new file mode 100644
index 0000000..ced8750
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/16x16/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-data.png
new file mode 100755
index 0000000..091f96b
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-project.png
new file mode 100644
index 0000000..29706e5
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/rstudio.png
new file mode 100644
index 0000000..a0ea726
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-css.png
new file mode 100644
index 0000000..0efc286
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-html.png
new file mode 100644
index 0000000..d2f5909
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-javascript.png
new file mode 100644
index 0000000..10554cd
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-markdown.png
new file mode 100644
index 0000000..1269a33
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-doc.png
new file mode 100644
index 0000000..806f81f
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-html.png
new file mode 100644
index 0000000..1929f0e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-markdown.png
new file mode 100644
index 0000000..8b33246
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-presentation.png
new file mode 100644
index 0000000..06ae736
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-source.png
new file mode 100644
index 0000000..e46ac29
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-sweave.png
new file mode 100755
index 0000000..7be7798
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-tex.png
new file mode 100644
index 0000000..ef2834a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/24x24/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-data.png
new file mode 100755
index 0000000..bade904
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-project.png
new file mode 100644
index 0000000..3e8c8ff
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/rstudio.png
new file mode 100644
index 0000000..62cc3e0
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-css.png
new file mode 100644
index 0000000..3b6a5f9
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-html.png
new file mode 100644
index 0000000..2b3b0cf
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-javascript.png
new file mode 100644
index 0000000..5f59ba5
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-markdown.png
new file mode 100644
index 0000000..acbe4a1
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-doc.png
new file mode 100644
index 0000000..6f6282a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-html.png
new file mode 100644
index 0000000..ba28b01
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-markdown.png
new file mode 100644
index 0000000..35835c2
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-presentation.png
new file mode 100644
index 0000000..863d3da
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-source.png
new file mode 100644
index 0000000..c575f27
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-sweave.png
new file mode 100755
index 0000000..0ef63cd
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-tex.png
new file mode 100644
index 0000000..f51184b
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/256x256/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-data.png
new file mode 100755
index 0000000..98088b2
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-project.png
new file mode 100644
index 0000000..7b5a435
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/rstudio.png
new file mode 100644
index 0000000..c4f5c2a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-css.png
new file mode 100644
index 0000000..855f08a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-html.png
new file mode 100644
index 0000000..333f712
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-javascript.png
new file mode 100644
index 0000000..d542789
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-markdown.png
new file mode 100644
index 0000000..cb3d89e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-doc.png
new file mode 100644
index 0000000..9c05c21
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-html.png
new file mode 100644
index 0000000..488557e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-markdown.png
new file mode 100644
index 0000000..ea65eee
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-presentation.png
new file mode 100644
index 0000000..8f67171
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-source.png
new file mode 100644
index 0000000..2314444
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-sweave.png
new file mode 100755
index 0000000..c384e8f
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-tex.png
new file mode 100644
index 0000000..ede8d44
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/32x32/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-data.png
new file mode 100755
index 0000000..1c02191
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-project.png
new file mode 100644
index 0000000..18e225a
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/rstudio.png
new file mode 100644
index 0000000..1cd2ff3
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-css.png
new file mode 100644
index 0000000..bc346d8
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-html.png
new file mode 100644
index 0000000..fd17845
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-javascript.png
new file mode 100644
index 0000000..082a864
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-markdown.png
new file mode 100644
index 0000000..28a73e4
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-doc.png
new file mode 100644
index 0000000..29688e1
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-html.png
new file mode 100644
index 0000000..7b56aa8
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-markdown.png
new file mode 100644
index 0000000..cdb9895
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-source.png
new file mode 100644
index 0000000..e0df50d
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-sweave.png
new file mode 100755
index 0000000..02230d1
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-tex.png
new file mode 100644
index 0000000..e59aa33
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/48x48/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-data.png
new file mode 100755
index 0000000..a4c02e9
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-project.png
new file mode 100644
index 0000000..ba6c98b
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/rstudio.png
new file mode 100644
index 0000000..6e2ea64
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-css.png
new file mode 100644
index 0000000..e4549a6
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-html.png
new file mode 100644
index 0000000..a088ffa
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-javascript.png
new file mode 100644
index 0000000..dfaf843
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-markdown.png
new file mode 100644
index 0000000..9339545
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-doc.png
new file mode 100644
index 0000000..ec8d3f4
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-html.png
new file mode 100644
index 0000000..5966dcb
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-markdown.png
new file mode 100644
index 0000000..1b8e9c9
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-presentation.png
new file mode 100644
index 0000000..bce436e
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-source.png
new file mode 100644
index 0000000..ce03150
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-sweave.png
new file mode 100755
index 0000000..0b398b5
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-tex.png
new file mode 100644
index 0000000..cc42b57
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/512x512/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-data.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-data.png
new file mode 100755
index 0000000..9bb83e0
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-data.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-project.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-project.png
new file mode 100644
index 0000000..e28291b
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/application-x-r-project.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/rstudio.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/rstudio.png
new file mode 100644
index 0000000..85c047c
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/rstudio.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-css.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-css.png
new file mode 100644
index 0000000..887a65c
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-css.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-html.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-html.png
new file mode 100644
index 0000000..1e23d62
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-javascript.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-javascript.png
new file mode 100644
index 0000000..4a2c728
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-javascript.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-markdown.png
new file mode 100644
index 0000000..5ab71c6
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-doc.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-doc.png
new file mode 100644
index 0000000..cf03c28
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-doc.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-html.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-html.png
new file mode 100644
index 0000000..ef0791d
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-html.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-markdown.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-markdown.png
new file mode 100644
index 0000000..c9ad4fc
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-markdown.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-presentation.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-presentation.png
new file mode 100644
index 0000000..6a3366b
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-presentation.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-source.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-source.png
new file mode 100644
index 0000000..4e0f118
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-source.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-sweave.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-sweave.png
new file mode 100755
index 0000000..0ae6497
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-r-sweave.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-tex.png b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-tex.png
new file mode 100644
index 0000000..de3dad7
Binary files /dev/null and b/src/cpp/desktop/resources/freedesktop/icons/64x64/text-x-tex.png differ
diff --git a/src/cpp/desktop/resources/freedesktop/rstudio.desktop.in b/src/cpp/desktop/resources/freedesktop/rstudio.desktop.in
new file mode 100644
index 0000000..6d54b97
--- /dev/null
+++ b/src/cpp/desktop/resources/freedesktop/rstudio.desktop.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Exec=${CMAKE_INSTALL_PREFIX}/${RSTUDIO_INSTALL_BIN}/rstudio %F
+Icon=rstudio
+Type=Application
+Terminal=false
+Name=RStudio
+Categories=Development;
+MimeType=text/x-r-source;text/x-r;text/x-R;text/x-r-doc;text/x-r-sweave;text/x-r-markdown;text/x-r-html;text/x-r-presentation;application/x-r-data;application/x-r-project;text/x-r-history;text/x-r-profile;text/x-tex;text/x-markdown;text/html;text/css;text/javascript;
diff --git a/src/cpp/desktop/resources/freedesktop/rstudio.xml b/src/cpp/desktop/resources/freedesktop/rstudio.xml
new file mode 100644
index 0000000..8169c33
--- /dev/null
+++ b/src/cpp/desktop/resources/freedesktop/rstudio.xml
@@ -0,0 +1,76 @@
+<?xml version='1.0' encoding='utf-8'?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+
+ <mime-type type="text/x-r-source">
+ <sub-class-of type="text/plain"/>
+ <comment>R Source File</comment>
+ <glob pattern="*.R"/>
+ <glob pattern="*.r"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-doc">
+ <sub-class-of type="text/plain"/>
+ <comment>R Documentation File</comment>
+ <glob pattern="*.Rd"/>
+ <glob pattern="*.rd"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-sweave">
+ <sub-class-of type="text/plain"/>
+ <comment>R Sweave File</comment>
+ <glob pattern="*.Rnw"/>
+ <glob pattern="*.rnw"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-markdown">
+ <sub-class-of type="text/plain"/>
+ <comment>R Markdown File</comment>
+ <glob pattern="*.Rmd"/>
+ <glob pattern="*.rmd"/>
+ <glob pattern="*.Rmarkdown"/>
+ <glob pattern="*.rmarkdown"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-html">
+ <sub-class-of type="text/plain"/>
+ <comment>R HTML File</comment>
+ <glob pattern="*.Rhtml"/>
+ <glob pattern="*.rhtml"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-presentation">
+ <sub-class-of type="text/plain"/>
+ <comment>R Presentation File</comment>
+ <glob pattern="*.Rpres"/>
+ <glob pattern="*.rpres"/>
+ </mime-type>
+
+ <mime-type type="application/x-r-data">
+ <comment>R Data File</comment>
+ <glob pattern="*.RData"/>
+ <glob pattern="*.Rdata"/>
+ <glob pattern="*.rdata"/>
+ <glob pattern="*.rda"/>
+ </mime-type>
+
+ <mime-type type="application/x-r-project">
+ <comment>R Project</comment>
+ <glob pattern="*.Rproj"/>
+ <glob pattern="*.RProj"/>
+ <glob pattern="*.rproj"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-history">
+ <sub-class-of type="text/plain"/>
+ <comment>R History File</comment>
+ <glob pattern="*.Rhistory"/>
+ </mime-type>
+
+ <mime-type type="text/x-r-profile">
+ <sub-class-of type="text/plain"/>
+ <comment>R Profile File</comment>
+ <glob pattern="*.Rprofile"/>
+ </mime-type>
+
+</mime-info>
+
diff --git a/src/cpp/desktop/resources/icns/CSS.icns b/src/cpp/desktop/resources/icns/CSS.icns
new file mode 100644
index 0000000..0190563
Binary files /dev/null and b/src/cpp/desktop/resources/icns/CSS.icns differ
diff --git a/src/cpp/desktop/resources/icns/HTML.icns b/src/cpp/desktop/resources/icns/HTML.icns
new file mode 100644
index 0000000..3b9bbc8
Binary files /dev/null and b/src/cpp/desktop/resources/icns/HTML.icns differ
diff --git a/src/cpp/desktop/resources/icns/JS.icns b/src/cpp/desktop/resources/icns/JS.icns
new file mode 100644
index 0000000..90c78a9
Binary files /dev/null and b/src/cpp/desktop/resources/icns/JS.icns differ
diff --git a/src/cpp/desktop/resources/icns/Markdown.icns b/src/cpp/desktop/resources/icns/Markdown.icns
new file mode 100644
index 0000000..ad1d220
Binary files /dev/null and b/src/cpp/desktop/resources/icns/Markdown.icns differ
diff --git a/src/cpp/desktop/resources/icns/RData.icns b/src/cpp/desktop/resources/icns/RData.icns
new file mode 100644
index 0000000..b75ff91
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RData.icns differ
diff --git a/src/cpp/desktop/resources/icns/RDoc.icns b/src/cpp/desktop/resources/icns/RDoc.icns
new file mode 100644
index 0000000..e8cf6f4
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RDoc.icns differ
diff --git a/src/cpp/desktop/resources/icns/RHTML.icns b/src/cpp/desktop/resources/icns/RHTML.icns
new file mode 100644
index 0000000..e0273a7
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RHTML.icns differ
diff --git a/src/cpp/desktop/resources/icns/RMarkdown.icns b/src/cpp/desktop/resources/icns/RMarkdown.icns
new file mode 100644
index 0000000..4267626
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RMarkdown.icns differ
diff --git a/src/cpp/desktop/resources/icns/RPresentation.icns b/src/cpp/desktop/resources/icns/RPresentation.icns
new file mode 100644
index 0000000..4b26c7d
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RPresentation.icns differ
diff --git a/src/cpp/desktop/resources/icns/RProject.icns b/src/cpp/desktop/resources/icns/RProject.icns
new file mode 100644
index 0000000..8a38727
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RProject.icns differ
diff --git a/src/cpp/desktop/resources/icns/RSource.icns b/src/cpp/desktop/resources/icns/RSource.icns
new file mode 100644
index 0000000..303ba2b
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RSource.icns differ
diff --git a/src/cpp/desktop/resources/icns/RStudio.icns b/src/cpp/desktop/resources/icns/RStudio.icns
new file mode 100644
index 0000000..c7b14c7
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RStudio.icns differ
diff --git a/src/cpp/desktop/resources/icns/RSweave.icns b/src/cpp/desktop/resources/icns/RSweave.icns
new file mode 100644
index 0000000..51194ac
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RSweave.icns differ
diff --git a/src/cpp/desktop/resources/icns/RTex.icns b/src/cpp/desktop/resources/icns/RTex.icns
new file mode 100644
index 0000000..da031f7
Binary files /dev/null and b/src/cpp/desktop/resources/icns/RTex.icns differ
diff --git a/src/cpp/desktop/rstudio-backtrace.sh.in b/src/cpp/desktop/rstudio-backtrace.sh.in
new file mode 100644
index 0000000..a86e198
--- /dev/null
+++ b/src/cpp/desktop/rstudio-backtrace.sh.in
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# check if rstudio is already running
+PIDOF_RSTUDIO=`pidof rstudio`
+if ! test -z $PIDOF_RSTUDIO
+then
+ echo ""
+ echo "RStudio already running (quit existing sessions before running this utility)"
+ echo ""
+ exit 1
+fi
+
+# record current working dir
+CURRENT_DIR=`pwd`
+
+# make sure gdb is installed
+sudo apt-get install gdb
+
+# allow core dumps of unlimited size
+ulimit -c unlimited
+
+# set core pattern to custom value
+OLD_CORE_PATTERN=`cat /proc/sys/kernel/core_pattern`
+CORE_DIR=/tmp/rstudio/coredumps
+rm -rf $CORE_DIR
+mkdir -p $CORE_DIR
+chmod 777 $CORE_DIR
+sudo sh -c "echo $CORE_DIR/%e.core > /proc/sys/kernel/core_pattern"
+
+# outfile
+RSTUDIO_BACKTRACE_OUT=$CURRENT_DIR/rstudio-backtrace.txt
+sudo rm -f $RSTUDIO_BACKTRACE_OUT
+touch $RSTUDIO_BACKTRACE_OUT
+
+# write RStudio and R version info
+echo "RStudio Version: ${CPACK_PACKAGE_VERSION}" >> $RSTUDIO_BACKTRACE_OUT
+echo "" >> $RSTUDIO_BACKTRACE_OUT
+R --vanilla --quiet -e "print(sessionInfo())" >> $RSTUDIO_BACKTRACE_OUT
+echo $RSTUDIO_WHICH_R >> $RSTUDIO_BACKTRACE_OUT
+which R >> $RSTUDIO_BACKTRACE_OUT
+uname -a >> $RSTUDIO_BACKTRACE_OUT
+echo "" >> $RSTUDIO_BACKTRACE_OUT
+
+
+# run rstudio
+${CMAKE_INSTALL_PREFIX}/bin/rstudio
+
+# create gdb command file for backtraces
+RSTUDIO_BACKTRACE_GDB=/tmp/rstudio-backtrace.gdb
+sudo rm -f $RSTUDIO_BACKTRACE_GDB
+cat <<'EOF' > $RSTUDIO_BACKTRACE_GDB
+thread apply all bt full
+quit
+EOF
+
+# write any backtraces we found
+if test -e $CORE_DIR/rstudio.core
+then
+ # write backtraces for rstudio
+ gdb --command=$RSTUDIO_BACKTRACE_GDB \
+ ${CMAKE_INSTALL_PREFIX}/bin/rstudio $CORE_DIR/rstudio.core \
+ >> $RSTUDIO_BACKTRACE_OUT 2>/dev/null
+fi
+
+if test -e $CORE_DIR/rsession.core
+then
+ # write backtraces for rsession
+ gdb --command=$RSTUDIO_BACKTRACE_GDB \
+ ${CMAKE_INSTALL_PREFIX}/bin/rsession $CORE_DIR/rsession.core \
+ >> $RSTUDIO_BACKTRACE_OUT 2>/dev/null
+fi
+
+
+# remove tmp backtrace command file
+rm $RSTUDIO_BACKTRACE_GDB
+
+# restore old core pattern
+sudo sh -c "echo $OLD_CORE_PATTERN > /proc/sys/kernel/core_pattern"
+
+
+
diff --git a/src/cpp/desktop/rstudio.exe.manifest b/src/cpp/desktop/rstudio.exe.manifest
new file mode 100644
index 0000000..b7547d0
--- /dev/null
+++ b/src/cpp/desktop/rstudio.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="rstudio"
+ type="win32"/>
+ <description>rstudio</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/desktop/rstudio.rc.in b/src/cpp/desktop/rstudio.rc.in
new file mode 100644
index 0000000..deb7f3f
--- /dev/null
+++ b/src/cpp/desktop/rstudio.rc.in
@@ -0,0 +1,32 @@
+#include "winuser.h"
+
+1 ICON RStudio.ico
+2 ICON RProject.ico
+
+1 RT_MANIFEST "rstudio.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "RStudio\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "rstudio.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/desktop/synctex/evince/EvinceDaemon.cpp b/src/cpp/desktop/synctex/evince/EvinceDaemon.cpp
new file mode 100644
index 0000000..47150ac
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceDaemon.cpp
@@ -0,0 +1,36 @@
+/*
+ * EvinceDaemon.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "EvinceDaemon.hpp"
+
+namespace desktop {
+namespace synctex {
+
+EvinceDaemon::EvinceDaemon(QObject *parent)
+ : QDBusAbstractInterface(QString::fromAscii("org.gnome.evince.Daemon"),
+ QString::fromAscii("/org/gnome/evince/Daemon"),
+ staticInterfaceName(),
+ QDBusConnection::sessionBus(),
+ parent)
+{
+}
+
+EvinceDaemon::~EvinceDaemon()
+{
+}
+
+} // namespace synctex
+} // namespace desktop
+
diff --git a/src/cpp/desktop/synctex/evince/EvinceDaemon.hpp b/src/cpp/desktop/synctex/evince/EvinceDaemon.hpp
new file mode 100644
index 0000000..0b07f18
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceDaemon.hpp
@@ -0,0 +1,65 @@
+/*
+ * EvinceDaemon.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SYNCTEX_EVINCEDAEMON_HPP
+#define DESKTOP_SYNCTEX_EVINCEDAEMON_HPP
+
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+#include <QtDBus/QtDBus>
+
+namespace desktop {
+namespace synctex {
+
+class EvinceDaemon : public QDBusAbstractInterface
+{
+ Q_OBJECT
+public:
+ static inline const char *staticInterfaceName()
+ { return "org.gnome.evince.Daemon"; }
+
+public:
+ EvinceDaemon(QObject *parent = 0);
+
+ ~EvinceDaemon();
+
+public Q_SLOTS: // METHODS
+ inline QDBusPendingReply<QString> FindDocument(const QString &uri, bool spawn)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(uri) << QVariant::fromValue(spawn);
+ return asyncCallWithArgumentList(QLatin1String("FindDocument"), argumentList);
+ }
+
+Q_SIGNALS: // SIGNALS
+};
+
+} // namespace synctex
+} // namespace desktop
+
+namespace org {
+ namespace gnome {
+ namespace evince {
+ typedef desktop::synctex::EvinceDaemon Daemon;
+ }
+ }
+}
+
+#endif // DESKTOP_SYNCTEX_EVINCEDAEMON_HPP
diff --git a/src/cpp/desktop/synctex/evince/EvinceSynctex.cpp b/src/cpp/desktop/synctex/evince/EvinceSynctex.cpp
new file mode 100644
index 0000000..c435bfb
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceSynctex.cpp
@@ -0,0 +1,206 @@
+/*
+ * EvinceSynctex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "EvinceSynctex.hpp"
+
+#include <boost/format.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/DateTime.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <DesktopMainWindow.hpp>
+#include <DesktopUtils.hpp>
+
+#include "EvinceDaemon.hpp"
+#include "EvinceWindow.hpp"
+
+using namespace core;
+
+namespace desktop {
+namespace synctex {
+
+namespace {
+
+void logDBusError(const QDBusError& error, const ErrorLocation& location)
+{
+ boost::format fmt("Error %1% (%2%): %3%");
+ std::string msg = boost::str(fmt % error.type() %
+ error.name().toStdString() %
+ error.message().toStdString());
+ core::log::logErrorMessage(msg, location);
+}
+
+} // anonymous namespace
+
+EvinceSynctex::EvinceSynctex(const SynctexViewerInfo& viewerInfo,
+ MainWindow* pMainWindow)
+ : Synctex(pMainWindow), viewerInfo_(viewerInfo)
+{
+ pEvince_ = new EvinceDaemon(this);
+}
+
+void EvinceSynctex::syncView(const QString& pdfFile,
+ const QString& srcFile,
+ const QPoint& srcLoc)
+{
+ syncView(SyncRequest(pdfFile, srcFile, srcLoc));
+}
+
+void EvinceSynctex::syncView(const QString& pdfFile, int page)
+{
+ syncView(SyncRequest(pdfFile, page));
+}
+
+void EvinceSynctex::syncView(const SyncRequest& syncRequest)
+{
+ QString pdfFile = syncRequest.pdfFile;
+ if (windows_.contains(pdfFile))
+ {
+ syncView(windows_.value(pdfFile), syncRequest);
+ }
+ else
+ {
+ // find the window
+ QDBusPendingReply<QString> reply = pEvince_->FindDocument(
+ QUrl::fromLocalFile(pdfFile).toString(),
+ true);
+
+ // wait for the results asynchronously
+ QDBusPendingCallWatcher* pWatcher = new QDBusPendingCallWatcher(reply,
+ this);
+ pendingSyncRequests_.insert(pWatcher, syncRequest);
+
+ QObject::connect(pWatcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(onFindWindowFinished(QDBusPendingCallWatcher*)));
+ }
+}
+
+void EvinceSynctex::onFindWindowFinished(QDBusPendingCallWatcher* pWatcher)
+{
+ // get the reply and the sync request params
+ QDBusPendingReply<QString> reply = *pWatcher;
+ SyncRequest req = pendingSyncRequests_.value(pWatcher);
+ pendingSyncRequests_.remove(pWatcher);
+
+ if (reply.isError())
+ {
+ logDBusError(reply.error(), ERROR_LOCATION);
+ }
+ else
+ {
+ // initialize a connection to it
+ EvinceWindow* pWindow = new EvinceWindow(viewerInfo_, reply.value());
+ if (!pWindow->isValid())
+ {
+ logDBusError(pWindow->lastError(), ERROR_LOCATION);
+ return;
+ }
+
+ // put it in our map
+ windows_.insert(req.pdfFile, pWindow);
+
+ // sign up for events
+ QObject::connect(pWindow,
+ SIGNAL(Closed()),
+ this,
+ SLOT(onClosed()));
+ QObject::connect(pWindow,
+ SIGNAL(SyncSource(const QString&,const QPoint&,uint)),
+ this,
+ SLOT(onSyncSource(const QString&,const QPoint&,uint)));
+
+ // perform sync
+ syncView(pWindow, req);
+ }
+
+ // delete the watcher
+ pWatcher->deleteLater();
+}
+
+void EvinceSynctex::syncView(EvinceWindow* pWindow, const SyncRequest& req)
+{
+ if (req.page != -1)
+ {
+ QStringList args;
+ args.append(QString::fromAscii("-i"));
+ args.append(QString::fromStdString(
+ safe_convert::numberToString(req.page)));
+ args.append(req.pdfFile);
+ QProcess::startDetached(QString::fromAscii("evince"), args);
+ }
+ else
+ {
+ syncView(pWindow, req.srcFile, req.srcLoc);
+ }
+
+}
+
+void EvinceSynctex::syncView(EvinceWindow* pWindow,
+ const QString& srcFile,
+ const QPoint& srcLoc)
+{
+ QDBusPendingReply<> reply = pWindow->SyncView(
+ srcFile,
+ srcLoc,
+ core::date_time::secondsSinceEpoch());
+
+ // wait for the results asynchronously
+ QDBusPendingCallWatcher* pWatcher = new QDBusPendingCallWatcher(reply,
+ this);
+ QObject::connect(pWatcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(onSyncViewFinished(QDBusPendingCallWatcher*)));
+}
+
+void EvinceSynctex::onSyncViewFinished(QDBusPendingCallWatcher* pWatcher)
+{
+ QDBusPendingReply<QString> reply = *pWatcher;
+ if (reply.isError())
+ logDBusError(reply.error(), ERROR_LOCATION);
+
+ pWatcher->deleteLater();
+}
+
+void EvinceSynctex::onClosed()
+{
+ // get the window that closed and determine the associated pdf
+ EvinceWindow* pWindow = static_cast<EvinceWindow*>(sender());
+ QString pdfFile = windows_.key(pWindow);
+
+ // notify base
+ Synctex::onClosed(pdfFile);
+
+ // remove window
+ windows_.remove(pdfFile);
+ pWindow->deleteLater();
+}
+
+
+void EvinceSynctex::onSyncSource(const QString& srcFile,
+ const QPoint& srcLoc,
+ uint)
+{
+ QUrl fileUrl(srcFile);
+ Synctex::onSyncSource(fileUrl.toLocalFile(), srcLoc);
+}
+
+
+} // namesapce synctex
+} // namespace desktop
diff --git a/src/cpp/desktop/synctex/evince/EvinceSynctex.hpp b/src/cpp/desktop/synctex/evince/EvinceSynctex.hpp
new file mode 100644
index 0000000..0bd3939
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceSynctex.hpp
@@ -0,0 +1,101 @@
+/*
+ * EvinceSynctex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SYNCTEX_EVINCESYNCTEX_HPP
+#define DESKTOP_SYNCTEX_EVINCESYNCTEX_HPP
+
+#include <QObject>
+#include <QMap>
+#include <QPoint>
+#include <QDBusPendingCallWatcher>
+
+#include <DesktopSynctex.hpp>
+
+namespace desktop {
+
+class MainWindow;
+
+namespace synctex {
+
+class EvinceDaemon;
+class EvinceWindow;
+
+class EvinceSynctex : public Synctex
+{
+ Q_OBJECT
+
+public:
+ explicit EvinceSynctex(const SynctexViewerInfo& viewerInfo,
+ MainWindow* pMainWindow);
+
+ virtual void syncView(const QString& pdfFile,
+ const QString& srcFile,
+ const QPoint& srcLoc);
+
+ virtual void syncView(const QString& pdfFile, int pdfPage);
+
+private slots:
+ void onFindWindowFinished(QDBusPendingCallWatcher *pCall);
+ void onSyncViewFinished(QDBusPendingCallWatcher *pCall);
+ void onClosed();
+ void onSyncSource(const QString &source_file,
+ const QPoint &source_point,
+ uint timestamp);
+
+
+private:
+ struct SyncRequest
+ {
+ SyncRequest()
+ : page(-1)
+ {
+ }
+
+ SyncRequest(QString pdfFile, int page)
+ : pdfFile(pdfFile), page(page)
+ {
+ }
+
+ SyncRequest(QString pdfFile, QString srcFile, QPoint srcLoc)
+ : pdfFile(pdfFile), page(-1), srcFile(srcFile), srcLoc(srcLoc)
+ {
+ }
+
+ QString pdfFile;
+ int page;
+ QString srcFile;
+ QPoint srcLoc;
+ };
+
+ void syncView(const SyncRequest& syncRequest);
+
+ void syncView(EvinceWindow* pWindow, const SyncRequest& syncRequest);
+ void syncView(EvinceWindow* pWindow,
+ const QString& srcFile,
+ const QPoint& srcLoc);
+
+private:
+ SynctexViewerInfo viewerInfo_;
+ EvinceDaemon* pEvince_;
+ QMap<QString, EvinceWindow*> windows_;
+
+ QMap<QDBusPendingCallWatcher*, SyncRequest> pendingSyncRequests_;
+};
+
+
+} // namespace synctex
+} // namespace desktop
+
+#endif // DESKTOP_SYNCTEX_EVINCESYNCTEX_HPP
diff --git a/src/cpp/desktop/synctex/evince/EvinceWindow.cpp b/src/cpp/desktop/synctex/evince/EvinceWindow.cpp
new file mode 100644
index 0000000..d7ff9e3
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceWindow.cpp
@@ -0,0 +1,39 @@
+/*
+ * EvinceWindow.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "EvinceWindow.hpp"
+
+namespace desktop {
+namespace synctex {
+
+EvinceWindow::EvinceWindow(const SynctexViewerInfo& viewerInfo,
+ const QString &service,
+ QObject *parent)
+ : QDBusAbstractInterface(service,
+ QString::fromAscii("/org/gnome/evince/Window/0"),
+ staticInterfaceName(),
+ QDBusConnection::sessionBus(),
+ parent),
+ viewerInfo_(viewerInfo)
+{
+}
+
+EvinceWindow::~EvinceWindow()
+{
+}
+
+} // namespace synctex
+} // namespace desktop
+
diff --git a/src/cpp/desktop/synctex/evince/EvinceWindow.hpp b/src/cpp/desktop/synctex/evince/EvinceWindow.hpp
new file mode 100644
index 0000000..b2bef15
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/EvinceWindow.hpp
@@ -0,0 +1,90 @@
+/*
+ * EvinceWindow.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SYNCTEX_EVINCEWINDOW_HPP
+#define DESKTOP_SYNCTEX_EVINCEWINDOW_HPP
+
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+#include <QtDBus/QtDBus>
+
+#include <DesktopSynctex.hpp>
+
+namespace desktop {
+namespace synctex {
+
+class EvinceWindow : public QDBusAbstractInterface
+{
+ Q_OBJECT
+public:
+ static inline const char *staticInterfaceName()
+ { return "org.gnome.evince.Window"; }
+
+public:
+ EvinceWindow(const SynctexViewerInfo& viewerInfo,
+ const QString &service,
+ QObject *parent = 0);
+
+ ~EvinceWindow();
+
+public Q_SLOTS: // METHODS
+ inline QDBusPendingReply<> SyncView(const QString &source_file, const QPoint &source_point, uint timestamp)
+ {
+ // get source file path
+ QString srcFilePath = source_file;
+
+ // fixup the source file to have a "/./" (required by Evince < 3.3.2
+ // because it hadn't yet updated to version 1.17 of the synctex parser
+ // which is much more liberal in path parsing)
+ if (viewerInfo_.version() < QT_VERSION_CHECK(3,3,20))
+ {
+ QFileInfo srcFileInfo(srcFilePath);
+ srcFilePath = srcFileInfo.canonicalPath() +
+ QString::fromAscii("/./") +
+ srcFileInfo.fileName();
+ }
+
+ // invoke SyncView
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(srcFilePath) << QVariant::fromValue(source_point) << QVariant::fromValue(timestamp);
+ return asyncCallWithArgumentList(QLatin1String("SyncView"), argumentList);
+ }
+
+Q_SIGNALS: // SIGNALS
+ void Closed();
+ void DocumentLoaded(const QString &uri);
+ void SyncSource(const QString &source_file, const QPoint &source_point, uint timestamp);
+
+private:
+ SynctexViewerInfo viewerInfo_;
+};
+
+} // namespace synctex
+} // namespace desktop
+
+namespace org {
+ namespace gnome {
+ namespace evince {
+ typedef desktop::synctex::EvinceWindow Window;
+ }
+ }
+}
+
+#endif // DESKTOP_SYNCTEX_EVINCEWINDOW_HPP
diff --git a/src/cpp/desktop/synctex/evince/README b/src/cpp/desktop/synctex/evince/README
new file mode 100644
index 0000000..db1d73a
--- /dev/null
+++ b/src/cpp/desktop/synctex/evince/README
@@ -0,0 +1,110 @@
+
+Overview
+------------------------------------------------------------------------
+
+This directory includes QtDBus Proxies for Evince. They were initially
+generated by using the qdbusxml2cpp utility on the evince Daemon and
+Window intefaces. The Daemon interface was obtained using this command:
+
+dbus-send --session --type=method_call --print-reply \
+ --dest=org.gnome.evince.Daemon /org/gnome/evince/Daemon \
+ org.freedesktop.DBus.Introspectable.Introspect
+
+The Window interface was obtained from the introspection_xml[] static
+in ev_window.c (from the evince source code).
+
+We are maintaining these interfaces manually now because (a) there
+was a problem marshalling the point structure which we couldn't
+work around; and (b) we need to modify the Window interface to
+deal with the fact that pre 2.9.1 versions of evince don't include
+a timestamp parameter.
+
+Synctex Version History
+-----------------------------------------------------------------------
+
+2.31.5 -- introduced
+2.31.6 -- updated synctex parser
+2.91.0 -- add timestamp to SyncView & fix crash when reverse search fails
+2.91.3 -- add timestamp to SyncSource signal
+3.1.90 -- update parser to 1.16
+3.3.20 -- update parser to 1.17 (more liberal path parsing)
+
+Notes
+-----------------------------------------------------------------------
+
+- The addition of the timestamp parameter in 2.91.* creates a requirement
+that this parameter be excluded from dBus calls in these versions and prior
+(see: https://bugzilla.gnome.org/show_bug.cgi?id=632313).
+
+ NOTE: We attempted to modify our Qt DBus bindings to call the earlier
+ version of the API however initial testing of this against Evince 2.32.0
+ did not work correctly. This attempt is on the gnome2-synctex branch if
+ we want to experiment further.
+
+ NOTE: We also attempted to do a lightweight version of syncing based
+ on passing the -i parameter to evince. This worked only for when the
+ document wasn't open yet so didn't really buy us much. This attempt is
+ on the gnome2-pagesync branch if we want to experiment further.
+
+- It has been noted that in Synctex parser versions < 1.17 that there is a
+requirement of including ./ in paths. For example see:
+http://jlebl.wordpress.com/2011/01/13/vim-evince-and-forward-and-backward-latex-synctex-search/
+We pass full paths in to evince and in our own testing on Evince 3.2.0 we
+have not observed this problem (perhaps addressed in 1.16?). There are
+some notes about TexLive 2011 -- perhaps the issue only occurs when running
+against synctex files created by TexLive 2011? (not yet in Ubuntu 12.04)
+
+- If for some reason the 1.16 parser is enough to overcome the issue then
+we should be OK, as mainstream distros (Debian, Ubuntu, and Fedora) all had
+evince 3.2 as the first appearance in their distros (none had 2.91.*). See
+distro notes below.
+
+- Here are some additional notes on the changes made to the synctex parser
+regarding path handling:
+http://ftp.tug.org/svn/texlive/trunk/Build/source/texk/web2c/synctexdir/synctex_parser_readme.txt?view=markup
+
+- We've also discovered some other differences across versions highlighted in this thread: http://comments.gmane.org/gmane.emacs.auctex.general/4434. See particularly this comment:
+
+Mandar, I also checked when the needed Evince Daemon DBUS service was
+introduced. It was around Evince version 2.30, but the API changed
+several times before 3.0.0. Most importantly, before 3.0 you couldn't
+spawn a new evince instance via DBUS, which means AUCTeX would need to
+check if evince is running via DBUS, spawn a new instance via the
+command line if not, and then sync the view again via DBUS. Another
+major API incompatibility is that until 2.91.3 (an unstable release) you
+needed to provide the local path to the document, and now evince wants a
+URI. All in all, I think it's not worth the complexity to support the
+unstable pre-3.0 evince DBUS API.
+
+
+Evince Versions in Distributions
+-----------------------------------------------------------------------
+
+NOTE: Neither Ubuntu nor Fedora are shipping TexLive 2011 as of their
+most current versions (12.04 and 16 respectively). This means that if
+there is a problem confined concurrent use of Evince < 3.3.20 and
+TexLive >= 2011 that no user would see it in the default configuration
+(they'd have to have installed TexLive 2011 directly)
+
+Ubuntu
+
+- 10.04 -- 2.30.3 (no synctex)
+- 10.10 -- 2.32.0 (first synctex)
+- 11.04 -- 2.32.0
+- 11.10 -- 3.2.0 (timestamp params)
+- 12.04 -- 3.4.0 (new 1.17 file parsing)
+
+Debian
+
+- Debian 6 -- 2.30.3 (no synctex)
+- Wheezy -- 3.2.1
+
+Fedora
+
+- Fedora 13 -- 2.30.3 (no synctex)
+- Fedora 14 -- 2.32 (first synctex)
+- Fedora 15 -- 2.91.90 (timestamp for SyncView but NOT for SyncSource)
+- Fedora 16 -- 3.20 (timestamp also in SyncSource)
+- Fedora 17 -- 3.4.0 (new 1.17 file parsing)
+
+
diff --git a/src/cpp/desktop/synctex/rsinverse/CMakeLists.txt b/src/cpp/desktop/synctex/rsinverse/CMakeLists.txt
new file mode 100644
index 0000000..484a0ca
--- /dev/null
+++ b/src/cpp/desktop/synctex/rsinverse/CMakeLists.txt
@@ -0,0 +1,65 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(RSINVERSE)
+
+# include files
+file(GLOB_RECURSE RSINVERSE_HEADER_FILES "*.h*")
+
+# set include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+)
+
+set(RSINVERSE_SOURCE_FILES
+ RsInverseMain.cpp
+)
+
+
+# configure rsinverse.rc
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rsinverse.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rsinverse.rc)
+
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rsinverse.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/rsinverse.exe.manifest COPYONLY)
+
+set(LINK_FLAGS -Wl,-subsystem,windows -lversion)
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "rsinverse.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/rsinverse.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/rsinverse.exe.manifest")
+set(RSINVERSE_SOURCE_FILES
+ ${RSINVERSE_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows")
+
+add_executable(rsinverse
+ ${RSINVERSE_SOURCE_FILES}
+)
+
+# set link dependencies
+target_link_libraries(rsinverse
+ rstudio-core
+)
+
+install(TARGETS rsinverse DESTINATION ${RSTUDIO_INSTALL_BIN})
diff --git a/src/cpp/desktop/synctex/rsinverse/RsInverseMain.cpp b/src/cpp/desktop/synctex/rsinverse/RsInverseMain.cpp
new file mode 100644
index 0000000..595a3c9
--- /dev/null
+++ b/src/cpp/desktop/synctex/rsinverse/RsInverseMain.cpp
@@ -0,0 +1,145 @@
+/*
+ * RsInverseMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <windows.h>
+
+#include <string>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/StringUtils.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/ProgramOptions.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <core/http/Util.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/NamedPipeBlockingClient.hpp>
+
+// NOTE: this is a cut and paste job from SessionConstants.hpp so these
+// should not be changed unless also changed there
+#define kLocalUriLocationPrefix "/rsession-local/"
+#define kPostbackUriScope "postback/"
+#define kPostbackExitCodeHeader "X-Postback-ExitCode"
+
+using namespace core;
+
+int main(int argc, char** argv)
+{
+ try
+ {
+ // initialize log
+ initializeSystemLog("rsinverse", core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ // read options
+ using namespace boost::program_options ;
+ options_description rsinverseOptions("rsinverse");
+ unsigned int windowHandle;
+ std::string port, sharedSecret, sourceFile;
+ int line;
+ rsinverseOptions.add_options()
+ ("hwnd",
+ value<unsigned int>(&windowHandle),
+ "hwnd of rstudio instance")
+ ("port",
+ value<std::string>(&port),
+ "port of rstudio instance")
+ ("secret",
+ value<std::string>(&sharedSecret),
+ "rstudio shared secret")
+ ("source-file",
+ value<std::string>(&sourceFile),
+ "source file to navigate to")
+ ("line",
+ value<int>(&line),
+ "line of code to navigate to");
+
+ // define program options (allow positional specification)
+ core::program_options::OptionsDescription optDesc("rsinverse");
+ optDesc.commandLine.add(rsinverseOptions);
+ optDesc.positionalOptions.add("hwnd", 1);
+ optDesc.positionalOptions.add("port", 1);
+ optDesc.positionalOptions.add("secret", 1);
+ optDesc.positionalOptions.add("source-file", 1);
+ optDesc.positionalOptions.add("line", 1);
+
+ // read options
+ ProgramStatus status = core::program_options::read(optDesc, argc, argv);
+ if (status.exit())
+ return status.exitCode();
+
+ // activate the window
+ HWND hRStudioWnd = reinterpret_cast<HWND>(windowHandle);
+ if (::IsWindow(hRStudioWnd))
+ {
+ HWND hwndPopup = ::GetLastActivePopup(hRStudioWnd);
+ if (::IsWindow(hwndPopup))
+ hRStudioWnd = hwndPopup;
+ ::SetForegroundWindow(hRStudioWnd);
+ if (::IsIconic(hRStudioWnd))
+ ::ShowWindow(hRStudioWnd, SW_RESTORE);
+ }
+
+ // we presume that the path is passed to us in the system encoding
+ sourceFile = string_utils::systemToUtf8(sourceFile);
+
+ // enocde the source file and line as a query string
+ std::string requestBody;
+ core::http::Fields args;
+ args.push_back(std::make_pair("source-file", sourceFile));
+ args.push_back(std::make_pair("line",
+ safe_convert::numberToString(line)));
+ http::util::buildQueryString(args, &requestBody);
+
+
+ // determine postback uri
+ std::string uri = std::string(kLocalUriLocationPrefix kPostbackUriScope) +
+ "rsinverse";
+
+ // build postback request
+ http::Request request;
+ request.setMethod("POST");
+ request.setUri(uri);
+ request.setHeader("Accept", "*/*");
+ request.setHeader("X-Shared-Secret", sharedSecret);
+ request.setHeader("Connection", "close");
+ request.setBody(requestBody);
+
+ // send it
+ http::Response response;
+ std::string pipeName = core::system::getenv("RS_LOCAL_PEER");
+ error = http::sendRequest(pipeName,
+ request,
+ http::ConnectionRetryProfile(
+ boost::posix_time::seconds(10),
+ boost::posix_time::milliseconds(50)),
+ &response);
+
+ std::string exitCode = response.headerValue(kPostbackExitCodeHeader);
+ return safe_convert::stringTo<int>(exitCode, EXIT_FAILURE);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
diff --git a/src/cpp/desktop/synctex/rsinverse/rsinverse.exe.manifest b/src/cpp/desktop/synctex/rsinverse/rsinverse.exe.manifest
new file mode 100644
index 0000000..0008d01
--- /dev/null
+++ b/src/cpp/desktop/synctex/rsinverse/rsinverse.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="rsinverse"
+ type="win32"/>
+ <description>rsinverse</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/desktop/synctex/rsinverse/rsinverse.rc.in b/src/cpp/desktop/synctex/rsinverse/rsinverse.rc.in
new file mode 100644
index 0000000..c41e7fe
--- /dev/null
+++ b/src/cpp/desktop/synctex/rsinverse/rsinverse.rc.in
@@ -0,0 +1,29 @@
+#include "winuser.h"
+
+1 RT_MANIFEST "rsinverse.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio Inverse Search\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "rsinverse\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "rsinverse.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/desktop/synctex/sumatra/SumatraSynctex.cpp b/src/cpp/desktop/synctex/sumatra/SumatraSynctex.cpp
new file mode 100644
index 0000000..9c3fa11
--- /dev/null
+++ b/src/cpp/desktop/synctex/sumatra/SumatraSynctex.cpp
@@ -0,0 +1,114 @@
+/*
+ * SumatraSynctex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SumatraSynctex.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/Environment.hpp>
+
+#include "DesktopUtils.hpp"
+#include "DesktopOptions.hpp"
+
+using namespace core;
+
+namespace desktop {
+namespace synctex {
+
+namespace {
+
+
+QStringList standardSumatraArgs()
+{
+ QStringList args;
+ args.append(QString::fromAscii("-bg-color"));
+ args.append(QString::fromAscii("#ffffff"));
+ args.append(QString::fromAscii("-reuse-instance"));
+ return args;
+}
+
+QStringList inverseSearchArgs(WId mainWindowId)
+{
+ QStringList args;
+ args.append(QString::fromAscii("-inverse-search"));
+
+ QString cmdFormat;
+ QString quote = QString::fromAscii("\"");
+ QString space = QString::fromAscii(" ");
+
+ // path to rsinverse binary
+ std::string rsinverse = desktop::options().rsinversePath().absolutePath();
+ cmdFormat.append(quote + QString::fromStdString(rsinverse) + quote);
+ cmdFormat.append(space);
+
+ // main window handle
+ unsigned int hwnd = reinterpret_cast<unsigned int>(mainWindowId);
+ std::string hwndStr = safe_convert::numberToString(hwnd);
+ cmdFormat.append(QString::fromStdString(hwndStr));
+ cmdFormat.append(space);
+
+ // port
+ cmdFormat.append(desktop::options().portNumber());
+ cmdFormat.append(space);
+
+ // shared secret
+ cmdFormat.append(
+ QString::fromStdString(core::system::getenv("RS_SHARED_SECRET")));
+ cmdFormat.append(space);
+
+ // file and line placeholders
+ cmdFormat.append(QString::fromAscii("\"%f\" %l"));
+ args.append(cmdFormat);
+
+ return args;
+}
+
+} // anonymous namespace
+
+SumatraSynctex::SumatraSynctex(MainWindow* pMainWindow)
+ : Synctex(pMainWindow)
+{
+ sumatraExePath_ = pMainWindow->getSumatraPdfExePath();
+}
+
+void SumatraSynctex::syncView(const QString& pdfFile,
+ const QString& srcFile,
+ const QPoint& srcLoc)
+{
+ QStringList args = standardSumatraArgs();
+ args.append(QString::fromAscii("-forward-search"));
+ args.append(srcFile);
+ args.append(
+ QString::fromStdString(safe_convert::numberToString(srcLoc.x())));
+ args.append(inverseSearchArgs(mainWindowId()));
+ args.append(pdfFile);
+ QProcess::startDetached(sumatraExePath_, args);
+}
+
+void SumatraSynctex::syncView(const QString& pdfFile, int page)
+{
+ QStringList args = standardSumatraArgs();
+ args.append(QString::fromAscii("-page"));
+ args.append(QString::fromStdString(safe_convert::numberToString(page)));
+ args.append(inverseSearchArgs(mainWindowId()));
+ args.append(pdfFile);
+ QProcess::startDetached(sumatraExePath_, args);
+}
+
+} // namesapce synctex
+} // namespace desktop
diff --git a/src/cpp/desktop/synctex/sumatra/SumatraSynctex.hpp b/src/cpp/desktop/synctex/sumatra/SumatraSynctex.hpp
new file mode 100644
index 0000000..bdbb627
--- /dev/null
+++ b/src/cpp/desktop/synctex/sumatra/SumatraSynctex.hpp
@@ -0,0 +1,52 @@
+/*
+ * SumatraSynctex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef DESKTOP_SYNCTEX_SUMATRASYNCTEX_HPP
+#define DESKTOP_SYNCTEX_SUMATRASYNCTEX_HPP
+
+#include <QObject>
+#include <QMap>
+#include <QPoint>
+
+#include <DesktopSynctex.hpp>
+
+namespace desktop {
+
+class MainWindow;
+
+namespace synctex {
+
+class SumatraSynctex : public Synctex
+{
+ Q_OBJECT
+
+public:
+ explicit SumatraSynctex(MainWindow* pMainWindow);
+
+ virtual void syncView(const QString& pdfFile,
+ const QString& srcFile,
+ const QPoint& srcLoc);
+
+ virtual void syncView(const QString& pdfFile, int pdfPage);
+
+private:
+ QString sumatraExePath_;
+};
+
+
+} // namespace synctex
+} // namespace desktop
+
+#endif // DESKTOP_SYNCTEX_SUMATRASYNCTEX_HPP
diff --git a/src/cpp/desktop/urlopener/CMakeLists.txt b/src/cpp/desktop/urlopener/CMakeLists.txt
new file mode 100644
index 0000000..9b2ebcc
--- /dev/null
+++ b/src/cpp/desktop/urlopener/CMakeLists.txt
@@ -0,0 +1,63 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(URLOPENER)
+
+# include files
+file(GLOB_RECURSE URLOPENER_HEADER_FILES "*.h*")
+
+# set include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+)
+
+set(URLOPENER_SOURCE_FILES
+ UrlOpenerMain.cpp
+)
+
+
+# configure urlopener.rc
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/urlopener.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/urlopener.rc)
+
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/urlopener.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/urlopener.exe.manifest COPYONLY)
+
+set(LINK_FLAGS -Wl,-subsystem,windows -lversion)
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "urlopener.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/urlopener.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/urlopener.exe.manifest")
+set(URLOPENER_SOURCE_FILES
+ ${URLOPENER_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+
+add_executable(urlopener
+ ${URLOPENER_SOURCE_FILES}
+)
+
+# set link dependencies
+target_link_libraries(urlopener
+ rstudio-core
+)
+
+install(TARGETS urlopener DESTINATION ${RSTUDIO_INSTALL_BIN})
diff --git a/src/cpp/desktop/urlopener/UrlOpenerMain.cpp b/src/cpp/desktop/urlopener/UrlOpenerMain.cpp
new file mode 100644
index 0000000..0d9524f
--- /dev/null
+++ b/src/cpp/desktop/urlopener/UrlOpenerMain.cpp
@@ -0,0 +1,82 @@
+/*
+ * UrlOpenerMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <windows.h>
+#include <shlwapi.h>
+
+#include <iostream>
+
+#include <core/Log.hpp>
+
+#include <core/system/System.hpp>
+
+int main(int argc, char** argv)
+{
+ try
+ {
+ // initialize log
+ initializeSystemLog("urlopener", core::system::kLogLevelWarning);
+
+ // check arguments
+ if (argc < 2)
+ {
+ std::cerr << "Error: Not enough arguments" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ // shell execute
+ DWORD ret = (DWORD) ::ShellExecute(NULL,
+ "open",
+ argv[1],
+ NULL,
+ NULL,
+ SW_SHOW);
+
+ // check for error
+ if(ret <= 32)
+ {
+ if(ret == ERROR_FILE_NOT_FOUND ||
+ ret == ERROR_PATH_NOT_FOUND ||
+ ret == SE_ERR_FNF ||
+ ret == SE_ERR_PNF)
+ {
+ std::cerr << argv[1] << " not found" << std::endl;
+ }
+ else if(ret == SE_ERR_ASSOCINCOMPLETE || ret == SE_ERR_NOASSOC)
+ {
+ std::cerr << "file association for " << argv[1] <<
+ " not available or invalid" << std::endl;
+ }
+ else if(ret == SE_ERR_ACCESSDENIED || ret == SE_ERR_SHARE)
+ {
+ std::cerr << "access to " << argv[1] << " denied" << std::endl;
+ }
+ else
+ {
+ std::cerr << "problem in displaying " << argv[1] << std::endl;
+ }
+
+ return ret;
+ }
+ else
+ {
+ return EXIT_SUCCESS;
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
diff --git a/src/cpp/desktop/urlopener/urlopener.exe.manifest b/src/cpp/desktop/urlopener/urlopener.exe.manifest
new file mode 100644
index 0000000..dc4f349
--- /dev/null
+++ b/src/cpp/desktop/urlopener/urlopener.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="urlopener"
+ type="win32"/>
+ <description>urlopener</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/desktop/urlopener/urlopener.rc.in b/src/cpp/desktop/urlopener/urlopener.rc.in
new file mode 100644
index 0000000..e8e0c47
--- /dev/null
+++ b/src/cpp/desktop/urlopener/urlopener.rc.in
@@ -0,0 +1,29 @@
+#include "winuser.h"
+
+1 RT_MANIFEST "urlopener.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio URL Opener\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "urlopener\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "urlopener.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/diagnostics/CMakeLists.txt b/src/cpp/diagnostics/CMakeLists.txt
new file mode 100644
index 0000000..976f385
--- /dev/null
+++ b/src/cpp/diagnostics/CMakeLists.txt
@@ -0,0 +1,69 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(DIAGNOSTICS)
+
+# include files
+file(GLOB_RECURSE DIAGNOSTICS_HEADER_FILES "*.h*")
+
+# set include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+set(DIAGNOSTICS_SOURCE_FILES
+ DiagnosticsMain.cpp
+)
+
+# config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+if(WIN32)
+ # configure diagnostics.rc
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/diagnostics.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/diagnostics.rc)
+
+
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/diagnostics.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/diagnostics.exe.manifest COPYONLY)
+
+ add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "diagnostics.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/diagnostics.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/diagnostics.exe.manifest")
+ set(DIAGNOSTICS_SOURCE_FILES
+ ${DIAGNOSTICS_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+endif()
+
+add_executable(diagnostics
+ ${DIAGNOSTICS_SOURCE_FILES}
+)
+
+# set link dependencies
+target_link_libraries(diagnostics
+ rstudio-core
+)
+if(NOT RSTUDIO_SESSION_WIN64)
+ install(TARGETS diagnostics DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
diff --git a/src/cpp/diagnostics/DiagnosticsMain.cpp b/src/cpp/diagnostics/DiagnosticsMain.cpp
new file mode 100644
index 0000000..11221d0
--- /dev/null
+++ b/src/cpp/diagnostics/DiagnosticsMain.cpp
@@ -0,0 +1,91 @@
+/*
+ * DiagnosticsMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+
+#include "config.h"
+
+using namespace core;
+
+namespace {
+
+FilePath homePath()
+{
+ return core::system::userHomePath("R_USER|HOME");
+}
+
+// NOTE: this code is duplicated in diagnostics as well (and also in
+// SessionOptions.hpp although the code path isn't exactly the same)
+FilePath userLogPath()
+{
+ FilePath logPath = core::system::userSettingsPath(
+ homePath(),
+ "RStudio-Desktop"
+ ).childPath("log");
+ return logPath;
+}
+
+void writeLogFile(const std::string& logFileName, std::ostream& ostr)
+{
+ ostr << "Log file: " << logFileName << std::endl;
+ ostr << "--------------------------------------------------" << std::endl;
+ ostr << std::endl;
+
+ FilePath logFilePath = userLogPath().childPath(logFileName);
+ if (logFilePath.exists())
+ {
+ std::string contents;
+ Error error = core::readStringFromFile(logFilePath, &contents);
+ if (error)
+ LOG_ERROR(error);
+ if (contents.empty())
+ ostr << "(Empty)" << std::endl << std::endl;
+ else
+ ostr << contents << std::endl;
+ }
+ else
+ {
+ ostr << "(Not Found)" << std::endl << std::endl;
+ }
+}
+
+
+} // anonymous namespace
+
+
+int main(int argc, char** argv)
+{
+ core::system::initializeStderrLog("rstudio-diagnostics",
+ core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ writeLogFile("rdesktop.log", std::cout);
+ writeLogFile("rsession-" + core::system::username() + ".log", std::cout);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/cpp/diagnostics/config.h.in b/src/cpp/diagnostics/config.h.in
new file mode 100644
index 0000000..7ad37ee
--- /dev/null
+++ b/src/cpp/diagnostics/config.h.in
@@ -0,0 +1,16 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#cmakedefine RSTUDIO_SERVER
diff --git a/src/cpp/diagnostics/diagnostics.exe.manifest b/src/cpp/diagnostics/diagnostics.exe.manifest
new file mode 100644
index 0000000..d4dedb6
--- /dev/null
+++ b/src/cpp/diagnostics/diagnostics.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="diagnostics"
+ type="win32"/>
+ <description>diagnostics</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/diagnostics/diagnostics.rc.in b/src/cpp/diagnostics/diagnostics.rc.in
new file mode 100644
index 0000000..5f9b6f1
--- /dev/null
+++ b/src/cpp/diagnostics/diagnostics.rc.in
@@ -0,0 +1,29 @@
+#include "winuser.h"
+
+1 RT_MANIFEST "diagnostics.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio Diagnostics\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "diagnostics\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "diagnostics.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/monitor/CMakeLists.txt b/src/cpp/monitor/CMakeLists.txt
new file mode 100644
index 0000000..eb846fd
--- /dev/null
+++ b/src/cpp/monitor/CMakeLists.txt
@@ -0,0 +1,44 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (MONITOR)
+
+# include files
+file(GLOB_RECURSE MONITOR_HEADER_FILES "*.h*")
+
+# source files
+set (MONITOR_SOURCE_FILES
+ events/Event.cpp
+ metrics/Metric.cpp
+ MonitorClient.cpp
+ MonitorClientOverlay.cpp
+)
+
+# include directories
+include_directories(
+ include
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CORE_SOURCE_DIR}/include
+)
+
+# define library
+add_library(rstudio-monitor STATIC ${MONITOR_SOURCE_FILES}
+ ${MONITOR_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(rstudio-monitor
+ rstudio-core
+)
+
diff --git a/src/cpp/monitor/MonitorClient.cpp b/src/cpp/monitor/MonitorClient.cpp
new file mode 100644
index 0000000..8728f26
--- /dev/null
+++ b/src/cpp/monitor/MonitorClient.cpp
@@ -0,0 +1,85 @@
+/*
+ * MonitorClient.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <monitor/MonitorClient.hpp>
+
+#include "MonitorClientImpl.hpp"
+
+namespace monitor {
+
+namespace {
+
+class MonitorLogWriter : public core::LogWriter
+{
+public:
+ MonitorLogWriter(const std::string& programIdentity)
+ : programIdentity_(programIdentity)
+ {
+ }
+
+ virtual void log(core::system::LogLevel level, const std::string& message)
+ {
+ log(programIdentity_, level, message);
+ }
+
+ virtual void log(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message)
+ {
+ client().logMessage(programIdentity, level, message);
+ }
+
+private:
+ std::string programIdentity_;
+};
+
+// single global instance of the monitor client (allocate it on the heap
+// and never free it so that there are no order of destruction surprises)
+Client* s_pClient = NULL;
+
+} // anonymous namespace
+
+boost::shared_ptr<core::LogWriter> Client::createLogWriter(
+ const std::string& programIdentity)
+{
+ return boost::shared_ptr<core::LogWriter>(
+ new MonitorLogWriter(programIdentity));
+}
+
+
+void initializeMonitorClient(const std::string& metricsSocket,
+ const std::string& sharedSecret)
+{
+ BOOST_ASSERT(s_pClient == NULL);
+ s_pClient = new SyncClient(metricsSocket, sharedSecret);
+}
+
+void initializeMonitorClient(const std::string& metricsSocket,
+ const std::string& sharedSecret,
+ boost::asio::io_service& ioService)
+{
+ BOOST_ASSERT(s_pClient == NULL);
+ s_pClient = new AsyncClient(metricsSocket, sharedSecret, ioService);
+}
+
+Client& client()
+{
+ BOOST_ASSERT(s_pClient != NULL);
+ return *s_pClient;
+}
+
+} // namespace monitor
+
+
diff --git a/src/cpp/monitor/MonitorClientImpl.hpp b/src/cpp/monitor/MonitorClientImpl.hpp
new file mode 100644
index 0000000..bec9c5e
--- /dev/null
+++ b/src/cpp/monitor/MonitorClientImpl.hpp
@@ -0,0 +1,73 @@
+/*
+ * MonitorClientImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef MONITOR_MONITOR_CLIENT_IMPL_HPP
+#define MONITOR_MONITOR_CLIENT_IMPL_HPP
+
+#include <monitor/MonitorClient.hpp>
+
+namespace monitor {
+
+class SyncClient : public Client
+{
+public:
+ SyncClient(const std::string& metricsSocket,
+ const std::string& sharedSecret)
+ : Client(metricsSocket, sharedSecret)
+ {
+ }
+
+ void logMessage(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message);
+
+ void sendMetrics(const std::vector<metrics::Metric>& metrics);
+
+ void sendMultiMetrics(const std::vector<metrics::MultiMetric>& metrics);
+
+ void logEvent(const Event& event);
+};
+
+class AsyncClient : public Client
+{
+public:
+ AsyncClient(const std::string& metricsSocket,
+ const std::string& sharedSecret,
+ boost::asio::io_service& ioService)
+ : Client(metricsSocket, sharedSecret),
+ ioService_(ioService)
+ {
+ }
+
+ void logMessage(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message);
+
+ void sendMetrics(const std::vector<metrics::Metric>& metrics);
+
+ void sendMultiMetrics(const std::vector<metrics::MultiMetric>& metrics);
+
+ void logEvent(const Event& event);
+
+protected:
+ boost::asio::io_service& ioService() { return ioService_; }
+
+private:
+ boost::asio::io_service& ioService_;
+};
+
+} // namespace monitor
+
+#endif // MONITOR_MONITOR_CLIENT_IMPL_HPP
diff --git a/src/cpp/monitor/MonitorClientOverlay.cpp b/src/cpp/monitor/MonitorClientOverlay.cpp
new file mode 100644
index 0000000..98eb5ee
--- /dev/null
+++ b/src/cpp/monitor/MonitorClientOverlay.cpp
@@ -0,0 +1,62 @@
+/*
+ * MonitorClientOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <monitor/MonitorClient.hpp>
+#include "MonitorClientImpl.hpp"
+
+namespace monitor {
+
+void SyncClient::logMessage(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message)
+{
+}
+
+void SyncClient::sendMetrics(const std::vector<metrics::Metric>& metrics)
+{
+}
+
+void SyncClient::sendMultiMetrics(
+ const std::vector<metrics::MultiMetric>& metrics)
+{
+}
+
+void AsyncClient::logMessage(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message)
+{
+}
+
+void AsyncClient::sendMetrics(const std::vector<metrics::Metric>& metrics)
+{
+}
+
+void AsyncClient::sendMultiMetrics(
+ const std::vector<metrics::MultiMetric>& metrics)
+{
+}
+
+void SyncClient::logEvent(const Event& event)
+{
+}
+
+void AsyncClient::logEvent(const Event& event)
+{
+}
+
+
+} // namespace monitor
+
+
diff --git a/src/cpp/monitor/events/Event.cpp b/src/cpp/monitor/events/Event.cpp
new file mode 100644
index 0000000..e31c8d4
--- /dev/null
+++ b/src/cpp/monitor/events/Event.cpp
@@ -0,0 +1,82 @@
+/*
+ * Event.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <monitor/events/Event.hpp>
+
+#include <iostream>
+
+#include <core/StringUtils.hpp>
+
+#include <core/http/Util.hpp>
+
+namespace monitor {
+
+std::ostream& operator<<(std::ostream& ostr, const Event& event)
+{
+ switch(event.scope())
+ {
+ case kAuthScope:
+ ostr << "auth";
+ break;
+ case kSessionScope:
+ ostr << "session";
+ break;
+ default:
+ ostr << "<unknown>";
+ break;
+ }
+ ostr << " ";
+
+ switch(event.id())
+ {
+ case kAuthLoginEvent:
+ ostr << "login";
+ break;
+ case kAuthLogoutEvent:
+ ostr << "logout";
+ break;
+ case kSessionStartEvent:
+ ostr << "start";
+ break;
+ case kSessionSuicideEvent:
+ ostr << "suicide";
+ break;
+ case kSessionSuspendEvent:
+ ostr << "suspend";
+ break;
+ case kSessionQuitEvent:
+ ostr << "quit";
+ break;
+ case kSessionExitEvent:
+ ostr << "exit";
+ break;
+ default:
+ ostr << "<unknown>";
+ break;
+ }
+
+ ostr << " - ";
+ ostr << event.username() << " [" << event.pid() << "] - ";
+ ostr << core::http::util::httpDate(event.timestamp());
+ if (!event.data().empty())
+ {
+ ostr << " - " << event.data();
+ }
+
+ return ostr;
+}
+
+} // namespace monitor
+
diff --git a/src/cpp/monitor/include/monitor/MonitorClient.hpp b/src/cpp/monitor/include/monitor/MonitorClient.hpp
new file mode 100644
index 0000000..ff87058
--- /dev/null
+++ b/src/cpp/monitor/include/monitor/MonitorClient.hpp
@@ -0,0 +1,81 @@
+/*
+ * MonitorClient.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef MONITOR_MONITOR_CLIENT_HPP
+#define MONITOR_MONITOR_CLIENT_HPP
+
+#include <string>
+
+#include <boost/asio/io_service.hpp>
+
+#include <core/system/System.hpp>
+#include <core/LogWriter.hpp>
+
+#include <monitor/events/Event.hpp>
+#include <monitor/metrics/Metric.hpp>
+
+#include "MonitorConstants.hpp"
+
+namespace monitor {
+
+class Client : boost::noncopyable
+{
+protected:
+ Client(const std::string& metricsSocket,
+ const std::string& sharedSecret)
+ : metricsSocket_(metricsSocket),
+ sharedSecret_(sharedSecret)
+ {
+ }
+
+public:
+ virtual ~Client() {}
+
+ virtual void logMessage(const std::string& programIdentity,
+ core::system::LogLevel level,
+ const std::string& message) = 0;
+
+ boost::shared_ptr<core::LogWriter> createLogWriter(
+ const std::string& programIdentity);
+
+ virtual void sendMetrics(const std::vector<metrics::Metric>& metrics) = 0;
+
+ virtual void sendMultiMetrics(
+ const std::vector<metrics::MultiMetric>& metrics) = 0;
+
+ virtual void logEvent(const Event& event) = 0;
+
+protected:
+ const std::string& metricsSocket() const { return metricsSocket_; }
+ const std::string& sharedSecret() const { return sharedSecret_; }
+
+private:
+ std::string metricsSocket_;
+ std::string sharedSecret_;
+};
+
+void initializeMonitorClient(const std::string& metricsSocket,
+ const std::string& sharedSecret);
+
+void initializeMonitorClient(const std::string& metricsSocket,
+ const std::string& sharedSecret,
+ boost::asio::io_service& ioService);
+
+Client& client();
+
+} // namespace monitor
+
+#endif // MONITOR_MONITOR_CLIENT_HPP
+
diff --git a/src/cpp/monitor/include/monitor/MonitorConstants.hpp b/src/cpp/monitor/include/monitor/MonitorConstants.hpp
new file mode 100644
index 0000000..4043462
--- /dev/null
+++ b/src/cpp/monitor/include/monitor/MonitorConstants.hpp
@@ -0,0 +1,24 @@
+/*
+ * MonitorConstants.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef MONITOR_CONSTANTS_HPP
+#define MONITOR_CONSTANTS_HPP
+
+#define kMonitorSocketPath "/tmp/rstudio-rserver/rserver-monitor.socket"
+#define kMonitorSharedSecretEnvVar "RS_MONITOR_SHARED_SECRET"
+#define kMonitorIntervalSeconds "monitor-interval-seconds"
+
+#endif // MONITOR_CONSTANTS_HPP
+
diff --git a/src/cpp/monitor/include/monitor/events/Event.hpp b/src/cpp/monitor/include/monitor/events/Event.hpp
new file mode 100644
index 0000000..2f7a290
--- /dev/null
+++ b/src/cpp/monitor/include/monitor/events/Event.hpp
@@ -0,0 +1,91 @@
+/*
+ * Event.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef MONITOR_EVENTS_EVENT_HPP
+#define MONITOR_EVENTS_EVENT_HPP
+
+#include <iosfwd>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/system/System.hpp>
+
+namespace monitor {
+
+enum EventScope
+{
+ kAuthScope = 0,
+ kSessionScope = 1
+};
+
+#define kAuthLoginEvent 1001
+#define kAuthLogoutEvent 1002
+
+#define kSessionStartEvent 2001
+#define kSessionSuicideEvent 2002
+#define kSessionSuspendEvent 2003
+#define kSessionQuitEvent 2004
+#define kSessionExitEvent 2005
+
+class Event
+{
+public:
+ Event()
+ : empty_(true)
+ {
+ }
+
+ Event(EventScope scope,
+ int id,
+ const std::string& data = std::string(),
+ const std::string& username = core::system::username(),
+ PidType pid = core::system::currentProcessId(),
+ boost::posix_time::ptime timestamp =
+ boost::posix_time::microsec_clock::universal_time())
+ : empty_(false),
+ scope_(scope),
+ id_(id),
+ username_(username),
+ pid_(pid),
+ timestamp_(timestamp),
+ data_(data)
+ {
+ }
+
+public:
+ bool empty() const { return empty_; }
+ EventScope scope() const { return scope_; }
+ int id() const { return id_; }
+ const std::string& username() const { return username_; }
+ PidType pid() const { return pid_; }
+ boost::posix_time::ptime timestamp() const { return timestamp_; }
+ const std::string& data() const { return data_; }
+
+private:
+ bool empty_;
+ EventScope scope_;
+ int id_;
+ std::string username_;
+ PidType pid_;
+ boost::posix_time::ptime timestamp_;
+ std::string data_;
+};
+
+std::ostream& operator<<(std::ostream& ostr, const Event& event);
+
+} // namespace monitor
+
+#endif // MONITOR_EVENTS_EVENT_HPP
+
diff --git a/src/cpp/monitor/include/monitor/metrics/Metric.hpp b/src/cpp/monitor/include/monitor/metrics/Metric.hpp
new file mode 100644
index 0000000..e077fff
--- /dev/null
+++ b/src/cpp/monitor/include/monitor/metrics/Metric.hpp
@@ -0,0 +1,162 @@
+/*
+ * Metric.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef MONITOR_METRIC_METRIC_HPP
+#define MONITOR_METRIC_METRIC_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace monitor {
+namespace metrics {
+
+// convenience base class for Metric and MultiMetric
+class MetricBase
+{
+protected:
+ MetricBase() {}
+
+ MetricBase(const std::string& scope,
+ int intervalSeconds,
+ const std::string& type,
+ const std::string& unit,
+ boost::posix_time::ptime timestamp)
+ : scope_(scope),
+ intervalSeconds_(intervalSeconds),
+ type_(type),
+ unit_(unit),
+ timestamp_(timestamp)
+ {
+ }
+
+public:
+ // check for empty
+ bool isEmpty() const { return scope_.empty(); }
+
+ // property accessors
+ const std::string& scope() const { return scope_; }
+ int intervalSeconds() const { return intervalSeconds_; }
+ const std::string& type() const { return type_; }
+ const std::string& unit() const { return unit_; }
+ const boost::posix_time::ptime& timestamp() const { return timestamp_; }
+
+private:
+ std::string scope_;
+ int intervalSeconds_;
+ std::string type_;
+ std::string unit_;
+ boost::posix_time::ptime timestamp_;
+};
+
+struct MetricData
+{
+ MetricData()
+ : value(0)
+ {
+ }
+
+ MetricData(const MetricData& data)
+ : name(data.name), value(data.value)
+ {
+ }
+
+ MetricData(const std::string& name, double value)
+ : name(name), value(value)
+ {
+ }
+
+ bool isEmpty() const { return name.empty(); }
+
+ std::string name;
+ double value;
+};
+
+class Metric : public MetricBase
+{
+public:
+ Metric() : MetricBase() {}
+
+ Metric(const std::string& scope,
+ int intervalSeconds,
+ const MetricData& data,
+ const std::string& type = "gauge",
+ const std::string& unit = std::string(),
+ boost::posix_time::ptime timestamp =
+ boost::posix_time::microsec_clock::universal_time())
+ : MetricBase(scope, intervalSeconds, type, unit, timestamp),
+ data_(data)
+ {
+ }
+
+public:
+ const MetricData& data() const { return data_; }
+
+private:
+ MetricData data_;
+};
+
+
+class MultiMetric : public MetricBase
+{
+public:
+ MultiMetric() : MetricBase() {}
+
+ MultiMetric(const std::string& scope,
+ int intervalSeconds,
+ const std::vector<MetricData>& data,
+ const std::string& type = "gauge",
+ const std::string& unit = std::string(),
+ boost::posix_time::ptime timestamp =
+ boost::posix_time::microsec_clock::universal_time())
+ : MetricBase(scope, intervalSeconds, type, unit, timestamp),
+ data_(data)
+ {
+ }
+
+public:
+ const std::vector<MetricData>& data() const { return data_; }
+
+private:
+ std::vector<MetricData> data_;
+};
+
+// metric handlers
+typedef boost::function<void(const Metric&)> MetricHandler;
+typedef boost::function<void(const MultiMetric&)> MultiMetricHandler;
+
+// json serialization
+core::json::Object metricToJson(const Metric& metric);
+core::Error metricFromJson(const core::json::Object& metricJson,
+ Metric* pMetric);
+
+core::json::Object metricToJson(const MultiMetric& multiMetric);
+core::Error metricFromJson(const core::json::Object& multiMetricJson,
+ MultiMetric* pMultiMetric);
+
+
+} // namespace metrics
+} // namespace monitor
+
+#endif // MONITOR_METRIC_METRIC_HPP
+
diff --git a/src/cpp/monitor/metrics/Metric.cpp b/src/cpp/monitor/metrics/Metric.cpp
new file mode 100644
index 0000000..b645136
--- /dev/null
+++ b/src/cpp/monitor/metrics/Metric.cpp
@@ -0,0 +1,177 @@
+/*
+ * Metric.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <monitor/metrics/Metric.hpp>
+
+#include <ostream>
+
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/DateTime.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+using namespace core;
+
+namespace monitor {
+namespace metrics {
+
+namespace {
+
+json::Object metricBaseToJson(const MetricBase& metric)
+{
+ json::Object metricJson;
+ metricJson["scope"] = metric.scope();
+ metricJson["interval"] = metric.intervalSeconds();
+ metricJson["type"] = metric.type();
+ metricJson["unit"] = metric.unit();
+ metricJson["ts"] = date_time::secondsSinceEpoch(metric.timestamp());
+ return metricJson;
+}
+
+Error metricBaseFromJson(const json::Object& metricJson,
+ std::string* pScope,
+ int* pIntervalSeconds,
+ std::string* pType,
+ std::string* pUnit,
+ double* pTimestamp)
+{
+ return json::readObject(metricJson,
+ "scope", pScope,
+ "interval", pIntervalSeconds,
+ "type", pType,
+ "unit", pUnit,
+ "ts", pTimestamp);
+}
+
+json::Value toMetricDataJson(const MetricData& data)
+{
+ json::Object dataJson;
+ dataJson["name"] = data.name;
+ dataJson["value"] = data.value;
+ return dataJson;
+}
+
+
+} // anonymous namespace
+
+json::Object metricToJson(const Metric& metric)
+{
+ json::Object metricJson = metricBaseToJson(metric);
+ metricJson["name"] = metric.data().name;
+ metricJson["value"] = metric.data().value;
+ return metricJson;
+}
+
+Error metricFromJson(const json::Object& metricJson, Metric* pMetric)
+{
+ // read the fields
+ std::string scope, name, type, unit;
+ double value, ts;
+ int intervalSeconds;
+ Error error = metricBaseFromJson(metricJson,
+ &scope,
+ &intervalSeconds,
+ &type,
+ &unit,
+ &ts);
+ if (error)
+ return error;
+
+ error = json::readObject(metricJson,
+ "name", &name,
+ "value", &value);
+ if (error)
+ return error;
+
+ *pMetric = Metric(scope,
+ intervalSeconds,
+ MetricData(name, value),
+ type,
+ unit,
+ date_time::timeFromSecondsSinceEpoch(ts));
+
+ return Success();
+}
+
+json::Object metricToJson(const MultiMetric& multiMetric)
+{
+ json::Object multiMetricJson = metricBaseToJson(multiMetric);
+
+ json::Array dataJson;
+ std::transform(multiMetric.data().begin(),
+ multiMetric.data().end(),
+ std::back_inserter(dataJson),
+ toMetricDataJson);
+ multiMetricJson["data"] = dataJson;
+
+ return multiMetricJson;
+}
+
+Error metricFromJson(const json::Object& multiMetricJson,
+ MultiMetric* pMultiMetric)
+{
+ // read the fields
+ std::string scope, type, unit;
+ double ts;
+ int intervalSeconds;
+ Error error = metricBaseFromJson(multiMetricJson,
+ &scope,
+ &intervalSeconds,
+ &type,
+ &unit,
+ &ts);
+ if (error)
+ return error;
+
+ // read the data array
+ json::Array dataJson;
+ error = json::readObject(multiMetricJson, "data", &dataJson);
+ if (error)
+ return error;
+
+ // create vector of metric data
+ std::vector<MetricData> data;
+ BOOST_FOREACH(const json::Value& value, dataJson)
+ {
+ if (!json::isType<json::Object>(value))
+ return Error(json::errc::ParamTypeMismatch, ERROR_LOCATION);
+
+ MetricData dataItem;
+ json::Object valueObj = value.get_obj();
+ Error error = json::readObject(valueObj,
+ "name", &dataItem.name,
+ "value", &dataItem.value);
+ if (error)
+ return error;
+
+ data.push_back(dataItem);
+ }
+
+ *pMultiMetric = MultiMetric(scope,
+ intervalSeconds,
+ data,
+ type,
+ unit,
+ date_time::timeFromSecondsSinceEpoch(ts));
+
+ return Success();
+}
+
+
+} // namespace metrics
+} // namespace monitor
+
diff --git a/src/cpp/r/CMakeLists.txt b/src/cpp/r/CMakeLists.txt
new file mode 100644
index 0000000..52debe0
--- /dev/null
+++ b/src/cpp/r/CMakeLists.txt
@@ -0,0 +1,99 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (R)
+
+# custom R packages for RStudio
+add_subdirectory(R/packages)
+
+# include files
+file(GLOB_RECURSE R_HEADER_FILES "*.h*")
+
+# source files
+set (R_SOURCE_FILES
+ RErrorCategory.cpp
+ RExec.cpp
+ RFunctionHook.cpp
+ RJson.cpp
+ RJsonRpc.cpp
+ ROptions.cpp
+ RRoutines.cpp
+ RSexp.cpp
+ RSourceManager.cpp
+ RUtil.cpp
+ session/RClientMetrics.cpp
+ session/RClientState.cpp
+ session/RConsoleActions.cpp
+ session/RConsoleHistory.cpp
+ session/RDiscovery.cpp
+ session/RRestartContext.cpp
+ session/RSearchPath.cpp
+ session/RSessionState.cpp
+ session/RSession.cpp
+ session/graphics/RGraphicsDevice.cpp
+ session/graphics/RGraphicsErrorCategory.cpp
+ session/graphics/RGraphicsPlot.cpp
+ session/graphics/RGraphicsPlotManipulator.cpp
+ session/graphics/RGraphicsPlotManipulatorManager.cpp
+ session/graphics/RGraphicsPlotManager.cpp
+ session/graphics/RGraphicsUtils.cpp
+ session/graphics/RGraphicsDevDesc.cpp
+ session/graphics/RGraphicsHandler.cpp
+ session/graphics/RShadowPngGraphicsHandler.cpp
+)
+
+
+# UNIX specific
+if (UNIX)
+
+ set(R_SOURCE_FILES ${R_SOURCE_FILES}
+ session/REmbeddedPosix.cpp
+ )
+
+# Win32 specific
+else()
+
+ set(R_SOURCE_FILES ${R_SOURCE_FILES}
+ session/REmbeddedWin32.cpp
+ )
+
+endif()
+
+# create a config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+# include directories
+include_directories(
+ include
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CORE_SOURCE_DIR}/include
+ ${LIBR_INCLUDE_DIRS}
+)
+
+# define library
+add_library(rstudio-r STATIC ${R_SOURCE_FILES} ${R_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(rstudio-r
+ ${LIBR_LIBRARIES}
+ rstudio-core
+)
+
+# install rules
+if (NOT RSTUDIO_SESSION_WIN64)
+ file(GLOB R_SRC_FILES "R/*.R")
+ install(FILES ${R_SRC_FILES} DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R)
+endif()
diff --git a/src/cpp/r/R/Diagnostics.R b/src/cpp/r/R/Diagnostics.R
new file mode 100644
index 0000000..1668a3e
--- /dev/null
+++ b/src/cpp/r/R/Diagnostics.R
@@ -0,0 +1,69 @@
+#
+# Diagnostics.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# capture output into a file (note this path is in module_context::sourceDiagnostics
+# so changes to the path should be synchronized there)
+library(utils)
+dir.create("~/rstudio-diagnostics", showWarnings=FALSE)
+diagnosticsFile <- suppressWarnings(normalizePath("~/rstudio-diagnostics/diagnostics-report.txt"))
+
+capture.output({
+
+ # version
+ versionFile <- "../VERSION"
+ if (file.exists(versionFile)) {
+ print(readLines(versionFile))
+ cat("\n")
+ }
+
+ # basic info
+ print(as.list(Sys.which(c("R",
+ "pdflatex",
+ "bibtex",
+ "gcc",
+ "git",
+ "svn"))))
+ print(sessionInfo())
+ cat("\nSysInfo:\n")
+ print(Sys.info())
+ cat("\nR Version:\n")
+ print(version)
+ print(as.list(Sys.getenv()))
+ print(search())
+
+ # locate diagonstics binary and run it
+ sysName <- Sys.info()[['sysname']]
+ ext <- ifelse(identical(sysName, "Windows"), ".exe", "")
+
+ # first look for debug version
+ cppDiag <- paste("../../../qtcreator-build/diagnostics/diagnostics",
+ ext, sep="")
+ if (!file.exists(cppDiag)) {
+ if (identical(sysName, "Darwin"))
+ cppDiag <- "../../MacOS/diagnostics"
+ else
+ cppDiag <- paste("../bin/diagnostics", ext, sep="")
+ }
+
+ if (file.exists(cppDiag))
+ diag <- system(cppDiag, intern=TRUE)
+ cat(diag, sep="\n")
+
+
+}, file=diagnosticsFile)
+
+cat("Diagnostics report written to:", diagnosticsFile, "\n")
+
+
diff --git a/src/cpp/r/R/Options.R b/src/cpp/r/R/Options.R
new file mode 100644
index 0000000..798966f
--- /dev/null
+++ b/src/cpp/r/R/Options.R
@@ -0,0 +1,77 @@
+#
+# Options.R
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# custom browseURL implementation
+options(browser = function(url)
+{
+ .Call("rs_browseURL", url) ;
+})
+
+# default viewer option if not already set
+if (is.null(getOption("viewer"))) {
+ options(viewer = function(url, height = NULL)
+ {
+ if (!is.character(url) || (length(url) != 1))
+ stop("url must be a single element character vector.", call. = FALSE)
+
+ if (!is.null(height) && (!is.numeric(height) || (length(height) != 1)))
+ stop("height must be a single element integer vector.", call. = FALSE)
+
+ invisible(.Call("rs_viewer", url, height))
+ })
+}
+
+# custom pager implementation
+options(pager = .rs.pager)
+
+# never allow graphical menus
+options(menu.graphics = FALSE)
+
+# set max print so that the DOM won't go haywire showing large datasets
+options(max.print = 10000)
+
+# set RStudio as the GUI
+local({
+ platform = .Platform
+ if (platform$GUI != "RStudio") {
+ platform$GUI = "RStudio"
+ unlockBinding(".Platform", asNamespace("base"))
+ assign(".Platform", platform, inherits=TRUE)
+ lockBinding(".Platform", asNamespace("base"))
+ }
+})
+
+# set default x display (see below for comment on why we need to do this)
+if (is.na(Sys.getenv("DISPLAY", NA)))
+ Sys.setenv(DISPLAY = ":0")
+
+# the above two display oriented command affect the behavior of edit.data.frame
+# and edit.matrix as follows: these methods will use .Internal(edit, ...) rather
+# than .Internal(dataentry, ...) if DISPLAY == "" or if the .Platform$GUI is
+# "unknown". since we plan on running on a server without X available we need
+# to manually make sure that the DISPLAY environment variable exists and that
+# the .Platform$GUI is not "unknown"
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cpp/r/R/ServerOptions.R b/src/cpp/r/R/ServerOptions.R
new file mode 100644
index 0000000..b90e910
--- /dev/null
+++ b/src/cpp/r/R/ServerOptions.R
@@ -0,0 +1,30 @@
+#
+# ServerOptions.R
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# set save defaults for high-performance (only if the administrator
+# or user hasn't set them explicitly already)
+
+if (is.null(getOption("save.defaults")))
+ options(save.defaults=list(ascii=FALSE, compress=FALSE))
+
+if (is.null(getOption("save.image.defaults")))
+ options(save.image.defaults=list(ascii=FALSE, safe=TRUE, compress=FALSE))
+
+# no support for email
+options(mailer = "none")
+
+# use internal unzip
+options(unzip = "internal")
+
diff --git a/src/cpp/r/R/Tools.R b/src/cpp/r/R/Tools.R
new file mode 100644
index 0000000..8ff42f4
--- /dev/null
+++ b/src/cpp/r/R/Tools.R
@@ -0,0 +1,577 @@
+#
+# Tools.R
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# target environment for rstudio supplemental tools
+.rs.Env <- attach(NULL,name="tools:rstudio")
+
+# add a function to the tools:rstudio environment
+assign( envir = .rs.Env, ".rs.addFunction", function(
+ name, FN, attrs = list())
+{
+ fullName = paste(".rs.", name, sep="")
+ for (attrib in names(attrs))
+ attr(FN, attrib) <- attrs[[attrib]]
+ assign(fullName, FN, .rs.Env)
+ environment(.rs.Env[[fullName]]) <- .rs.Env
+})
+
+# add a global (non-scoped) function to the tools:rstudio environment
+assign( envir = .rs.Env, ".rs.addGlobalFunction", function(name, FN)
+{
+ assign(name, FN, .rs.Env)
+ environment(.rs.Env[[name]]) <- .rs.Env
+})
+
+assign( envir = .rs.Env, ".rs.setVar", function(name, var)
+{
+ fullName = paste(".rs.", name, sep="")
+ assign(fullName, var, .rs.Env)
+ environment(.rs.Env[[fullName]]) <- .rs.Env
+})
+
+assign( envir = .rs.Env, ".rs.clearVar", function(name)
+{
+ fullName = paste(".rs.", name, sep="")
+ remove(list=fullName, pos=.rs.Env)
+})
+
+.rs.addFunction( "evalInGlobalEnv", function(code)
+{
+ eval(parse(text=code), envir=globalenv())
+})
+
+# save current state of options() to file
+.rs.addFunction( "saveOptions", function(filename)
+{
+ opt = options();
+ save(opt, file=filename)
+})
+
+# restore options() from file
+.rs.addFunction( "restoreOptions", function(filename)
+{
+ load(filename)
+ options(opt)
+})
+
+# save current state of .libPaths() to file
+.rs.addFunction( "saveLibPaths", function(filename)
+{
+ libPaths = .libPaths();
+ save(libPaths, file=filename)
+})
+
+# restore .libPaths() from file
+.rs.addFunction( "restoreLibPaths", function(filename)
+{
+ load(filename)
+ .libPaths(libPaths)
+})
+
+
+# try to determine if devtools::dev_mode is on
+.rs.addFunction( "devModeOn", function(){
+
+ # determine devmode path (devtools <= 0.6 hard-coded it)
+ devToolsPath <- getOption("devtools.path")
+ if (is.null(devToolsPath))
+ if ("devtools" %in% .packages())
+ devToolsPath <- "~/R-dev"
+
+ # no devtools path
+ if (is.null(devToolsPath))
+ return (FALSE)
+
+ # is the devtools path active?
+ devToolsPath <- .rs.normalizePath(devToolsPath, winslash = "/", mustWork = FALSE)
+ devToolsPath %in% .libPaths()
+})
+
+# load a package by name
+.rs.addFunction( "loadPackage", function(packageName, lib)
+{
+ if (nzchar(lib))
+ library(packageName, lib.loc = lib, character.only = TRUE)
+ else
+ library(packageName, character.only = TRUE)
+})
+
+# unload a package by name
+.rs.addFunction( "unloadPackage", function(packageName)
+{
+ pkg = paste("package:", packageName, sep="")
+ detach(pos = match(pkg, search()))
+})
+
+.rs.addFunction("getPackageVersion", function(packageName)
+{
+ package_version(utils:::packageDescription(packageName,
+ fields="Version"))
+})
+
+# save an environment to a file
+.rs.addFunction( "saveEnvironment", function(env, filename)
+{
+ save(list = ls(envir = env, all.names = TRUE),
+ file = filename,
+ envir = env)
+
+ invisible (NULL)
+})
+
+.rs.addFunction( "disableSaveCompression", function()
+{
+ options(save.defaults=list(ascii=FALSE, compress=FALSE))
+ options(save.image.defaults=list(ascii=FALSE, safe=TRUE, compress=FALSE))
+})
+
+.rs.addFunction( "attachDataFile", function(filename, name, pos = 2)
+{
+ if (!file.exists(filename))
+ stop(gettextf("file '%s' not found", filename), domain = NA)
+
+ .Internal(attach(NULL, pos, name))
+ load(filename, envir = as.environment(pos))
+
+ invisible (NULL)
+})
+
+.rs.addGlobalFunction( "RStudioGD", function()
+{
+ .Call("rs_createGD")
+})
+
+# set our graphics device as the default and cause it to be created/set
+.rs.addFunction( "initGraphicsDevice", function()
+{
+ options(device="RStudioGD")
+ grDevices::deviceIsInteractive("RStudioGD")
+})
+
+.rs.addFunction( "activateGraphicsDevice", function()
+{
+ invisible(.Call("rs_activateGD"))
+})
+
+# record an object to a file
+.rs.addFunction( "saveGraphicsSnapshot", function(snapshot, filename)
+{
+ # make a copy of the snapshot into plot and set its metadata in a way
+ # that is compatible with recordPlot
+ plot = snapshot
+ attr(plot, "version") <- as.character(getRversion())
+ class(plot) <- "recordedplot"
+
+ save(plot, file=filename)
+})
+
+# record an object to a file
+.rs.addFunction( "saveGraphics", function(filename)
+{
+ plot = grDevices::recordPlot()
+ save(plot, file=filename)
+})
+
+# restore an object from a file
+.rs.addFunction( "restoreGraphics", function(filename)
+{
+ load(filename)
+
+ # restore native symbols for R >= 3.0
+ rVersion <- getRversion()
+ if (rVersion >= "3.0")
+ {
+ for(i in 1:length(plot[[1]]))
+ {
+ # get the symbol then test if it's a native symbol
+ symbol <- plot[[1]][[i]][[2]][[1]]
+ if("NativeSymbolInfo" %in% class(symbol))
+ {
+ # determine the dll that the symbol lives in
+ if (!is.null(symbol$package))
+ name = symbol$package[["name"]]
+ else
+ name = symbol$dll[["name"]]
+ pkgDLL <- getLoadedDLLs()[[name]]
+
+ # reconstruct the native symbol and assign it into the plot
+ nativeSymbol <-getNativeSymbolInfo(name = symbol$name,
+ PACKAGE = pkgDLL,
+ withRegistrationInfo = TRUE);
+ plot[[1]][[i]][[2]][[1]] <- nativeSymbol;
+ }
+ }
+ }
+ # restore native symbols for R >= 2.14
+ else if (rVersion >= "2.14")
+ {
+ try({
+ for(i in 1:length(plot[[1]]))
+ {
+ if("NativeSymbolInfo" %in% class(plot[[1]][[i]][[2]][[1]]))
+ {
+ nativeSymbol <-getNativeSymbolInfo(plot[[1]][[i]][[2]][[1]]$name);
+ plot[[1]][[i]][[2]][[1]] <- nativeSymbol;
+ }
+ }
+ },
+ silent = TRUE);
+ }
+
+ # set the pid attribute to the current pid if necessary
+ if (rVersion >= "3.0.2")
+ {
+ plotPid <- attr(plot, "pid")
+ if (is.null(plotPid) || (plotPid != Sys.getpid()))
+ attr(plot, "pid") <- Sys.getpid()
+ }
+
+ # we suppressWarnings so that R doesnt print a warning if we restore
+ # a plot saved from a previous version of R (which will occur if we
+ # do a resume after upgrading the version of R on the server)
+ suppressWarnings(grDevices::replayPlot(plot))
+})
+
+# generate a uuid
+.rs.addFunction( "createUUID", function()
+{
+ .Call("rs_createUUID")
+})
+
+# check the current R architecture
+.rs.addFunction( "getRArch", function()
+{
+ .Platform$r_arch
+})
+
+# pager
+.rs.addFunction( "pager", function(files, header, title, delete.file)
+{
+ for (i in 1:length(files)) {
+ if ((i > length(header)) || !nzchar(header[[i]]))
+ fileTitle <- title
+ else
+ fileTitle <- header[[i]]
+
+ .Call("rs_showFile", fileTitle, files[[i]], delete.file)
+ }
+})
+
+# indirection for normalizePath function
+.rs.addFunction("normalizePath",
+ if(getRversion() < "2.13.0")
+ utils::normalizePath
+ else
+ normalizePath
+)
+
+# indirection for path.package function
+.rs.addFunction("pathPackage",
+ if(getRversion() < "2.13.0")
+ .path.package
+ else
+ path.package
+)
+
+# handle viewing a pdf differently on each platform:
+# - windows: shell.exec
+# - mac: Preview
+# - linux: getOption("pdfviewer")
+.rs.addFunction( "shellViewPdf", function(path)
+{
+ sysName <- Sys.info()[['sysname']]
+
+ if (identical(sysName, "Windows"))
+ {
+ shell.exec(path)
+ }
+ else
+ {
+ # force preview on osx to workaround acrobat reader crashing bug
+ if (identical(sysName, "Darwin"))
+ cmd <- paste("open", "-a", "Preview")
+ else
+ cmd <- shQuote(getOption("pdfviewer"))
+ system(paste(cmd, shQuote(path)), wait = FALSE)
+ }
+})
+
+
+# hook an internal R function
+.rs.addFunction( "registerHook", function(name, package, hookFactory)
+{
+ # get the original function
+ packageName = paste("package:", package, sep="")
+ original <- get(name, packageName, mode="function")
+
+ # install the hook
+ if (!is.null(original))
+ {
+ # new function definition
+ new <- hookFactory(original)
+
+ # re-map function
+ packageEnv = as.environment(packageName)
+ unlockBinding(name, packageEnv)
+ assign(name, new, packageName)
+ lockBinding(name, packageEnv)
+ }
+ else
+ {
+ stop(cat("function", name, "not found\n"))
+ }
+})
+
+.rs.addFunction( "callAs", function(name, f, ...)
+{
+ # TODO: figure out how to print the args (...) as part of the message
+
+ # run the original function (f). setup condition handlers soley so that
+ # we can correctly print the name of the function called in error
+ # and warning messages -- otherwise R prints "original(...)"
+ withCallingHandlers(tryCatch(f(...),
+ error=function(e)
+ {
+ cat("Error in ", name, " : ", e$message,
+ "\n", sep="")
+ }),
+ warning=function(w)
+ {
+ cat("Warning in ", name, " :\n ", w$message,
+ "\n", sep="")
+ invokeRestart("muffleWarning")
+ })
+})
+
+# replacing an internal R function
+.rs.addFunction( "registerReplaceHook", function(name, package, hook)
+{
+ hookFactory <- function(original) function(...) .rs.callAs(name,
+ hook,
+ original,
+ ...);
+ .rs.registerHook(name, package, hookFactory);
+})
+
+# notification that an internal R function was called
+.rs.addFunction( "registerNotifyHook", function(name, package, hook)
+{
+ hookFactory <- function(original) function(...)
+ {
+ # call hook after original is executed
+ on.exit(hook(...))
+
+ # call original
+ .rs.callAs(name, original, ...)
+ }
+ .rs.registerHook(name, package, hookFactory);
+})
+
+# marking functions in R packages as unsupported
+.rs.addFunction( "registerUnsupported", function(name, package, alternative = "")
+{
+ unsupported <- function(...)
+ {
+ msg <- "function not supported in RStudio"
+ if (nzchar(alternative))
+ msg <- paste(msg, "(try", alternative, "instead)")
+ msg <- paste(msg, "\n", sep="")
+ stop(msg)
+ }
+
+ .rs.registerReplaceHook(name, package, unsupported)
+})
+
+.rs.addFunction( "setCRANRepos", function(reposUrl)
+{
+ local({
+ r <- getOption("repos");
+ # attribute indicating the repos was set from rstudio prefs
+ attr(r, "RStudio") <- TRUE
+ r["CRAN"] <- reposUrl;
+ options(repos=r)
+ })
+})
+
+.rs.addFunction( "setCRANReposAtStartup", function(reposUrl)
+{
+ # check whether the user has already set a CRAN repository
+ # in their .Rprofile
+ repos = getOption("repos")
+ cranMirrorConfigured <- !is.null(repos) && repos != "@CRAN@"
+
+ if (!cranMirrorConfigured)
+ .rs.setCRANRepos(reposUrl)
+})
+
+.rs.addFunction( "setCRANReposFromSettings", function(reposUrl)
+{
+ # only set the repository if the repository was set by us
+ # in the first place (it wouldn't be if the user defined a
+ # repository in .Rprofile or called setRepositories directly)
+ if (!is.null(attr(getOption("repos"), "RStudio")))
+ .rs.setCRANRepos(reposUrl)
+})
+
+
+.rs.addFunction( "setMemoryLimit", function(limit)
+{
+ suppressWarnings(utils::memory.limit(limit))
+})
+
+
+.rs.addFunction( "libPathsAppend", function(path)
+{
+ # remove it if it already exists
+ .libPaths(.libPaths()[.libPaths() != path])
+
+ # append it
+ .libPaths(append(.libPaths(), path))
+})
+
+
+.rs.addFunction( "isLibraryWriteable", function(lib)
+{
+ file.exists(lib) && (file.access(lib, 2) == 0)
+})
+
+.rs.addFunction( "defaultLibPathIsWriteable", function()
+{
+ .rs.isLibraryWriteable(.libPaths()[1L])
+})
+
+.rs.addFunction( "disableQuartz", function()
+{
+ .rs.registerReplaceHook("quartz", "grDevices", function(...) {
+ stop(paste("RStudio does not support the quartz device in R <= 2.11.1.",
+ "Please upgrade to a newer version of R to use quartz."))
+ })
+})
+
+
+# Support for implementing json-rpc methods directly in R:
+#
+# - json-rpc method endpoints can be installed by calling the
+# .rs.addJsonRpcHandler function (all R files within the handlers directory
+# are sourced so that they can install handlers)
+#
+# - these endpoints are installed within the tools:rstudio environment,
+# therefore if common helper methods are required they should be added to
+# the same environment using .rs.addFunction
+#
+# - Json <-> R marshalling is implemented within RJsonRpc.cpp
+# details on how this works can be found in the comments therin
+#
+
+# add an rpc handler to the tools:rstudio environment
+.rs.addFunction( "addJsonRpcHandler", function(name, FN)
+{
+ fullName = paste("rpc.", name, sep="")
+ .rs.addFunction(fullName, FN, TRUE)
+})
+
+# list all rpc handlers in the tools:rstudio environment
+.rs.addFunction( "listJsonRpcHandlers", function()
+{
+ rpcHandlers <- objects("tools:rstudio",
+ all.names=TRUE,
+ pattern=utils:::glob2rx(".rs.rpc.*"))
+ return (rpcHandlers)
+})
+
+
+.rs.addFunction("showDiagnostics", function()
+{
+ diagPath <- shQuote(.rs.normalizePath("~/rstudio-diagnostics"))
+ sysName <- Sys.info()[['sysname']]
+ if (identical(sysName, "Windows"))
+ shell.exec(diagPath)
+ else if (identical(sysName, "Darwin"))
+ system(paste("open", diagPath))
+ else if (nzchar(Sys.which("nautilus")))
+ system(paste("nautilus", diagPath))
+})
+
+
+.rs.registerReplaceHook("history", "utils", function(original, ...) {
+ invisible(.Call("rs_activatePane", "history"))
+})
+
+.rs.addFunction("registerHistoryFunctions", function() {
+
+ # loadhistory
+ .rs.registerReplaceHook("loadhistory", "utils", function(original,
+ file = ".Rhistory")
+ {
+ invisible(.Call("rs_loadHistory", file))
+ })
+
+ # savehistory
+ .rs.registerReplaceHook("savehistory", "utils", function(original,
+ file = ".Rhistory")
+ {
+ invisible(.Call("rs_saveHistory", file))
+ })
+
+ # timestamp
+ .rs.registerReplaceHook("timestamp", "utils", function(original, ...)
+ {
+ invisible(.Call("rs_timestamp", date()))
+ })
+})
+
+
+.rs.addFunction("parseQuitArguments", function(command) {
+
+ # parse the command
+ expr <- parse(text=command)
+ if (length(expr) == 0)
+ stop("Not a fully formed command: ", command)
+
+ # match args
+ call <- as.call(expr[[1]])
+ call <- match.call(quit, call)
+
+ # return as list without the function name
+ as.list(call)[-1]
+})
+
+.rs.addFunction("isTraced", function(fun) {
+ isS4(fun) && class(fun) == "functionWithTrace"
+})
+
+# when a function is traced, some data about the function (such as its original
+# body and source references) exist only on the untraced copy
+.rs.addFunction("untraced", function(fun) {
+ if (.rs.isTraced(fun))
+ fun at original
+ else
+ fun
+})
+
+.rs.addFunction("getSrcref", function(fun) {
+ attr(.rs.untraced(fun), "srcref")
+})
+
+# returns a list containing line data from the given source reference,
+# formatted for output to the client
+.rs.addFunction("lineDataList", function(srcref) {
+ list(
+ line_number = .rs.scalar(srcref[1]),
+ end_line_number = .rs.scalar(srcref[3]),
+ character_number = .rs.scalar(srcref[5]),
+ end_character_number = .rs.scalar(srcref[6]))
+})
+
diff --git a/src/cpp/r/R/packages/CMakeLists.txt b/src/cpp/r/R/packages/CMakeLists.txt
new file mode 100644
index 0000000..ba93f70
--- /dev/null
+++ b/src/cpp/r/R/packages/CMakeLists.txt
@@ -0,0 +1,70 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(PACKAGES)
+
+# set library dir and ensure it exsits
+set(PACKAGES_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/library)
+file(MAKE_DIRECTORY ${PACKAGES_LIBRARY_DIR})
+
+# manipulate package
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/manipulate/DESCRIPTION.in
+ ${CMAKE_CURRENT_SOURCE_DIR}/manipulate/DESCRIPTION)
+
+set(MANIPULATE_OUTPUT ${PACKAGES_LIBRARY_DIR}/manipulate/DESCRIPTION)
+file(GLOB_RECURSE MANIPULATE_DEPENDENCIES manipulate/*.*)
+set_source_files_properties(${MANIPULATE_DEPENDENCIES} PROPERTIES
+ SYMBOLIC TRUE)
+add_custom_command(OUTPUT ${MANIPULATE_OUTPUT}
+ DEPENDS ${MANIPULATE_DEPENDENCIES}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND ${LIBR_EXECUTABLE}
+ ARGS CMD INSTALL -l ${PACKAGES_LIBRARY_DIR} manipulate)
+
+# rstudio package
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rstudio/DESCRIPTION.in
+ ${CMAKE_CURRENT_SOURCE_DIR}/rstudio/DESCRIPTION)
+
+set(RSTUDIO_OUTPUT ${PACKAGES_LIBRARY_DIR}/rstudio/DESCRIPTION)
+file(GLOB_RECURSE RSTUDIO_DEPENDENCIES rstudio/*.*)
+set_source_files_properties(${RSTUDIO_DEPENDENCIES} PROPERTIES
+ SYMBOLIC TRUE)
+add_custom_command(OUTPUT ${RSTUDIO_OUTPUT}
+ DEPENDS ${RSTUDIO_DEPENDENCIES}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND ${LIBR_EXECUTABLE}
+ ARGS CMD INSTALL -l ${PACKAGES_LIBRARY_DIR} rstudio)
+
+
+# add target for building packages
+add_custom_target(build_packages ALL
+ DEPENDS ${MANIPULATE_OUTPUT} ${RSTUDIO_OUTPUT})
+
+# install manipulate package and it's source (for installing in R 3.0)
+install(DIRECTORY ${PACKAGES_LIBRARY_DIR}/manipulate
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R/library)
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/manipulate
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R/packages)
+
+# install rstudio package and it's source (for installing in R 3.0)
+install(DIRECTORY ${PACKAGES_LIBRARY_DIR}/rstudio
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R/library)
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/rstudio
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R/packages)
+
+
+
+
+
diff --git a/src/cpp/r/R/packages/manipulate/.gitignore b/src/cpp/r/R/packages/manipulate/.gitignore
new file mode 100644
index 0000000..af40a38
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/.gitignore
@@ -0,0 +1,3 @@
+DESCRIPTION
+
+
diff --git a/src/cpp/r/R/packages/manipulate/DESCRIPTION.in b/src/cpp/r/R/packages/manipulate/DESCRIPTION.in
new file mode 100644
index 0000000..f27f58b
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/DESCRIPTION.in
@@ -0,0 +1,13 @@
+Package: manipulate
+Type: Package
+Title: Interactive Plots for RStudio
+Version: ${CPACK_PACKAGE_VERSION}
+Date: 2011-04-11
+Author: RStudio
+Maintainer: RStudio <info at rstudio.com>
+Description: Interactive Plots for RStudio
+Depends: R (>= 2.10.0)
+Suggests: graphics, grid, lattice, ggplot2
+License:
+LazyLoad: yes
+
diff --git a/src/cpp/r/R/packages/manipulate/NAMESPACE b/src/cpp/r/R/packages/manipulate/NAMESPACE
new file mode 100644
index 0000000..ee90a60
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/NAMESPACE
@@ -0,0 +1,11 @@
+
+export(slider,
+ picker,
+ checkbox,
+ button,
+ manipulatorSetState,
+ manipulatorGetState,
+ manipulatorMouseClick,
+ manipulate)
+
+
diff --git a/src/cpp/r/R/packages/manipulate/R/manipulate-internal.R b/src/cpp/r/R/packages/manipulate/R/manipulate-internal.R
new file mode 100644
index 0000000..de9a114
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/R/manipulate-internal.R
@@ -0,0 +1,150 @@
+#
+# manipulate-internal.R
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+manipulatorExecute <- function(manipulator)
+{
+ # evaulate the expression
+ result <- withVisible(eval(manipulator$.code, envir = manipulator))
+
+ # emulate the behavior of the console by printing the result if it
+ # is visible. this will allow objects returned from e.g. lattice or
+ # ggplot plots to be displayed without requiring an explicit print
+ # statement, whereas plotting functions like plot (or custom user
+ # functions will not print anything assuming they return invisibly.
+ if (result$visible)
+ {
+ # evaluate print in the context of the manipulator's parent
+ # environment (typically the global environment if manipulate was
+ # entered directly at the consle). this allows the dispatch of the
+ # print generic method to find the appropriate class method
+ eval(print(result$value), enclos=parent.env(manipulator))
+ }
+}
+
+manipulatorSave <- function(manipulator, filename)
+{
+ suppressWarnings(save(manipulator, file=filename))
+}
+
+manipulatorLoad <- function(filename)
+{
+ load(filename)
+ return (manipulator)
+}
+
+hasActiveManipulator <- function()
+{
+ .Call(getNativeSymbolInfo("rs_hasActiveManipulator", PACKAGE=""))
+}
+
+activeManipulator <- function()
+{
+ .Call(getNativeSymbolInfo("rs_activeManipulator", PACKAGE=""))
+}
+
+ensureManipulatorSaved <- function()
+{
+ .Call(getNativeSymbolInfo("rs_ensureManipulatorSaved", PACKAGE=""))
+}
+
+createUUID <- function()
+{
+ .Call(getNativeSymbolInfo("rs_createUUID", PACKAGE=""))
+}
+
+executeAndAttachManipulator <- function(manipulator)
+{
+ .Call(getNativeSymbolInfo("rs_executeAndAttachManipulator", PACKAGE=""),
+ manipulator)
+}
+
+setManipulatorValue <- function(manipulator, name, value)
+{
+ # assign the user visible value
+ assign(name, value, envir = get(".userVisibleValues", envir = manipulator))
+
+ # calculate the underlying value. if this was a picker then lookup the
+ # underlying value otherwise use the value passed as-is
+ underlyingValue <- value
+ controls <- get(".controls", envir = manipulator)
+ control <- controls[[name]]
+ if (inherits(control, "manipulator.picker"))
+ underlyingValue <- (control$values[[value]])
+
+ # assign the value
+ assign(name, underlyingValue, envir = manipulator)
+}
+
+userVisibleValues <- function(manipulator, variables)
+{
+ mget(variables, envir = get(".userVisibleValues", envir = manipulator))
+}
+
+buttonNames <- function(manipulator)
+{
+ if (exists(".buttonNames", envir = manipulator))
+ get(".buttonNames", envir = manipulator)
+ else
+ character()
+}
+
+trackingMouseClicks <- function(manipulator)
+{
+ exists(".mouseClick", envir = manipulator)
+}
+
+setMouseClick <- function(manipulator,
+ deviceX,
+ deviceY,
+ userX,
+ userY,
+ ndcX,
+ ndcY)
+{
+ mouseClick <- list(deviceX = deviceX,
+ deviceY = deviceY,
+ userX = userX,
+ userY = userY,
+ ndcX = ndcX,
+ ndcY = ndcY)
+ assign(".mouseClick", mouseClick, envir = manipulator)
+}
+
+clearMouseClick <- function(manipulator)
+{
+ assign(".mouseClick", NULL, envir = manipulator)
+}
+
+resolveVariableArguments <- function(args)
+{
+ # if the first argument is an unnamed list then just use this list
+ if ( (length(args) == 1L) &&
+ is.list(args[[1L]]) &&
+ (is.null(names(args)) || (names(args)[[1L]] == "")) )
+ {
+ return (args[[1L]])
+ }
+ else
+ {
+ return (args)
+ }
+}
+
+
+
+
+
+
+
diff --git a/src/cpp/r/R/packages/manipulate/R/manipulate.R b/src/cpp/r/R/packages/manipulate/R/manipulate.R
new file mode 100644
index 0000000..f196f05
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/R/manipulate.R
@@ -0,0 +1,253 @@
+#
+# manipulate.R
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+
+slider <- function(min, max, initial = min, label = NULL, step = NULL, ticks = TRUE)
+{
+ # validate inputs
+ if (!is.numeric(initial) || !is.numeric(min) || !is.numeric(max))
+ stop("min, max, amd initial must all be numeric values")
+ else if (initial < min)
+ stop(paste("slider initial value", initial, "is less than the specified minimum"))
+ else if (initial > max)
+ stop(paste("slider initial value", initial, "is greater than the specified maximum"))
+ else if (min > max)
+ stop(paste("slider maximum is greater than minimum"))
+ else if ( !is.null(step) )
+ {
+ if ( !is.numeric(step) )
+ stop("step is not a numeric value")
+ if ( step > (max - min) )
+ stop("step is greater than range")
+ }
+ else if ( !is.logical(ticks) )
+ stop("ticks is not a logical value")
+ else if ( !is.null(label) && !is.character(label) )
+ stop("label is not a character value")
+
+ # serialize "default" step as -1
+ if ( is.null(step) )
+ step <- -1
+
+ # create slider and return it
+ slider <- list(type = 0,
+ min = min,
+ max = max,
+ initialValue = initial,
+ label = label,
+ step = step,
+ ticks = ticks)
+ class(slider) <- "manipulator.slider"
+ return (slider)
+}
+
+picker <- function(..., initial = NULL, label = NULL)
+{
+ # get values
+ values <- resolveVariableArguments(list(...))
+
+ # get value names
+ valueNames <- names(values)
+ if (is.null(valueNames))
+ valueNames <- character(length(values))
+
+ # default missing names to choice values
+ missingNames <- valueNames == ""
+ valueNames[missingNames] <- paste(values)[missingNames]
+ names(values) <- valueNames
+
+ # validate inputs
+ if ( length(values) < 1 )
+ {
+ stop("picker choices must contain at least one value")
+ }
+ else if ( length(valueNames) != length(unique(valueNames)) )
+ {
+ stop("picker choices must have unique names (duplicate detected)")
+ }
+ else if ( !is.null(initial) )
+ {
+ if (length(initial) != 1)
+ stop("initial must be a single object")
+ else if ( !(as.character(initial) %in% valueNames) )
+ stop("initial doesn't match one of the supplied choices")
+ }
+ else if ( !is.null(label) && !is.character(label) )
+ {
+ stop("label is not a character value")
+ }
+
+ # provide default value if necessary
+ if ( is.null(initial) )
+ initial <- valueNames[1]
+
+ # create picker
+ picker <- list(type = 1,
+ choices = valueNames,
+ values = values,
+ initialValue = initial,
+ label = label)
+ class(picker) <- "manipulator.picker"
+ return (picker)
+}
+
+checkbox <- function(initial = FALSE, label = NULL)
+{
+ # validate inputs
+ if ( !is.logical(initial) )
+ stop("initial must be a logical")
+ else if ( !is.null(label) && !is.character(label) )
+ stop("label is not a character value")
+
+ # create checkbox and return it
+ checkbox <- list(type = 2,
+ initialValue = initial,
+ label = label)
+ class(checkbox) <- "manipulator.checkbox"
+ return (checkbox)
+}
+
+button <- function(label)
+{
+ # validate inputs
+ if ( !is.null(label) && !is.character(label) )
+ stop("label is not a character value")
+
+ # create button and return it
+ button <- list(type = 3,
+ initialValue = FALSE,
+ label = label)
+ class(button) <- "manipulator.button"
+ return (button)
+}
+
+manipulatorGetState <- function(name)
+{
+ if ( hasActiveManipulator() )
+ {
+ value <- NULL
+ try(silent = TRUE,
+ value <- get(name,
+ envir = get(".state", envir = activeManipulator()))
+ )
+ return (value)
+ }
+ else
+ {
+ stop("no plot manipulator currently active")
+ }
+}
+
+manipulatorSetState <- function(name, value)
+{
+ if ( hasActiveManipulator() )
+ {
+ assign(name, value, envir = get(".state", envir = activeManipulator()))
+ ensureManipulatorSaved()
+ invisible(NULL)
+ }
+ else
+ {
+ stop("no plot manipulator currently active")
+ }
+}
+
+manipulatorMouseClick <- function()
+{
+ if ( hasActiveManipulator() )
+ {
+ # if there is no .mouseClick then create a NULL one (the existence of
+ # the .mouseClick object is a sentinel indicating that we should
+ # callback the manipulate function every time the plot is clicked)
+ if (!exists(".mouseClick", envir = activeManipulator()))
+ assign(".mouseClick", NULL, envir = activeManipulator())
+
+ # return the .mouseClick
+ get(".mouseClick", envir = activeManipulator())
+ }
+ else
+ {
+ stop("no plot manipulator currently active")
+ }
+}
+
+manipulate <- function(`_expr`, ...)
+{
+ # create new list container for the manipulator
+ manipulator <- new.env(parent = parent.frame())
+ manipulator$.id <- createUUID()
+
+ # save the unevaluated expression as the code
+ manipulator$.code <- substitute(`_expr`)
+
+ # save a human readable version of the code (specify control = NULL
+ # to make the display as close to the original text as possible)
+ manipulator$.codeAsText <- deparse(substitute(`_expr`), control = NULL)
+
+ # get the controls and control names
+ controls <- resolveVariableArguments(list(...))
+ controlNames <- names(controls)
+
+ # validate that all controls have unique names
+ duplicatedIndex <- anyDuplicated(controlNames)
+ if (duplicatedIndex > 0)
+ stop(paste("duplicated control name:", controlNames[[duplicatedIndex]]))
+
+ # save the controls and their names into the manipulator
+ manipulator$.controls <- controls
+ manipulator$.variables <- controlNames
+
+ # establish state
+ manipulator$.state <- new.env(parent = globalenv())
+
+ # establish 'user visible' values (indirection btw e.g. picker choice & value)
+ manipulator$.userVisibleValues <- new.env(parent = globalenv())
+
+ # iterate over the names and controls, adding the default values to the env
+ manipulator$.buttonNames <- character()
+ for (name in names(controls))
+ {
+ # check the name
+ if (name == "")
+ stop("all controls passed to manipulate must be named")
+
+ # confirm that this is in fact a control
+ control <- controls[[name]]
+ if ( ! (class(control) %in% c("manipulator.slider",
+ "manipulator.picker",
+ "manipulator.checkbox",
+ "manipulator.button")) )
+ {
+ stop(paste("argument", name, "is not a control"))
+ }
+
+ # keep a special side list of button controls (so we can
+ # always set them to FALSE after execution)
+ if (inherits(control, "manipulator.button"))
+ manipulator$.buttonNames <- append(manipulator$.buttonNames, name)
+
+ # assign the control's default into the list
+ setManipulatorValue(manipulator, name, control$initialValue)
+ }
+
+ # execute the manipulator -- will execute the code and attach it
+ # to the first plot (if any) generated by the execution
+ executeAndAttachManipulator(manipulator)
+
+ # return invisibly
+ invisible(NULL)
+}
+
+
diff --git a/src/cpp/r/R/packages/manipulate/man/button.Rd b/src/cpp/r/R/packages/manipulate/man/button.Rd
new file mode 100644
index 0000000..3704fe8
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/button.Rd
@@ -0,0 +1,44 @@
+\name{button}
+\alias{button}
+\title{Create a button control}
+\description{
+ Create a button control to enable triggering of conditional actions within manipulate expressions. When the user presses the button the manipulate expression will be executed with its associated value set to TRUE (in all other cases the value will be set to FALSE).
+}
+\usage{
+button(label)
+}
+
+
+\arguments{
+ \item{label}{
+ Label for button.
+}
+}
+
+\value{
+ An object of class "manipulator.button" which can be passed to the \code{\link{manipulate}} function.
+}
+
+
+\seealso{
+ \code{\link{manipulate}}, \code{\link{slider}}, \code{\link{picker}}, \code{\link{checkbox}}
+}
+
+\examples{
+\dontrun{
+
+## use a button to reset a random seed
+manipulate(
+ {
+ if(resetSeed)
+ set.seed(sample(1:1000))
+
+ hist(rnorm(n=100, mean=0, sd=3), breaks=bins)
+ },
+ bins = slider(1, 20, step=1, initial =5, label="Bins"),
+ resetSeed = button("Reset Seed")
+)
+
+}
+}
+
diff --git a/src/cpp/r/R/packages/manipulate/man/checkbox.Rd b/src/cpp/r/R/packages/manipulate/man/checkbox.Rd
new file mode 100644
index 0000000..0d483de
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/checkbox.Rd
@@ -0,0 +1,46 @@
+\name{checkbox}
+\alias{checkbox}
+\title{Create a checkbox control}
+\description{
+ Create a checkbox control to enable manipulation of logical plot variables.
+}
+\usage{
+checkbox(initial = FALSE, label = NULL)
+}
+
+
+\arguments{
+ \item{initial}{
+ Initial value for checkbox. Must be logical (defaults to \code{FALSE}).
+}
+ \item{label}{
+ Display label for checkbox. Defaults to the variable name if not specified.
+}
+}
+
+\value{
+ An object of class "manipulator.checkbox" which can be passed to the \code{\link{manipulate}} function.
+}
+
+
+\seealso{
+ \code{\link{manipulate}}, \code{\link{slider}}, \code{\link{picker}}, \code{\link{button}}
+}
+
+\examples{
+\dontrun{
+
+## Using checkboxes for boolean parameters
+manipulate(
+ plot(cars, axes = axes, ann = label),
+ axes = checkbox(TRUE, "Draw Axes"),
+ label = checkbox(FALSE, "Draw Labels"))
+
+## Toggle boxplot outlier display using checkbox
+manipulate(
+ boxplot(Freq ~ Class, data = Titanic, outline = outline),
+ outline = checkbox(FALSE, "Show outliers"))
+
+}
+}
+
diff --git a/src/cpp/r/R/packages/manipulate/man/manipulate-package.Rd b/src/cpp/r/R/packages/manipulate/man/manipulate-package.Rd
new file mode 100644
index 0000000..0ac0633
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/manipulate-package.Rd
@@ -0,0 +1,71 @@
+\name{manipulate-package}
+\alias{manipulate-package}
+\docType{package}
+\title{Interactive Plots for RStudio}
+\description{
+Interactive plotting functions for use within RStudio.
+}
+\details{
+
+The \code{\link{manipulate}} function accepts a plotting expression and a set of controls (e.g. \code{\link{slider}}, \code{\link{picker}}, \code{\link{checkbox}}, or \code{\link{button}}) which are used to dynamically change values within the expression. When a value is changed using its corresponding control the expression is automatically re-executed and the plot is redrawn.
+
+For example, to create a plot that enables manipulation of a parameter using a slider control you could use syntax like this:
+
+ \code{manipulate(plot(1:x), x = slider(1, 10))}
+
+After this code is executed the plot is drawn using an initial value of 1 for \code{x}. A manipulator panel is also opened adjacent to the plot which contains a slider control used to change the value of \code{x} from 1 to 10.
+
+}
+
+\author{
+RStudio
+
+Maintainer: RStudio <info at rstudio.com>
+}
+
+
+\keyword{ package }
+\keyword{ dynamic }
+\keyword{ iplot }
+
+\examples{
+\dontrun{
+
+## Create a plot with a manipulator
+manipulate(plot(1:x), x = slider(5, 10))
+
+## Using more than one slider
+manipulate(
+ plot(cars, xlim=c(x.min,x.max)),
+ x.min=slider(0,15),
+ x.max=slider(15,30))
+
+## Filtering data with a picker
+manipulate(
+ barplot(as.matrix(longley[,factor]),
+ beside = TRUE, main = factor),
+ factor = picker("GNP", "Unemployed", "Employed"))
+
+## Create a picker with labels
+manipulate(
+ plot(pressure, type = type),
+ type = picker("points" = "p", "line" = "l", "step" = "s"))
+
+## Toggle boxplot outlier display using checkbox
+manipulate(
+ boxplot(Freq ~ Class, data = Titanic, outline = outline),
+ outline = checkbox(FALSE, "Show outliers"))
+
+## Combining controls
+manipulate(
+ plot(cars, xlim = c(x.min, x.max), type = type,
+ axes = axes, ann = label),
+ x.min = slider(0,15),
+ x.max = slider(15,30, initial = 25),
+ type = picker("p", "l", "b", "c", "o", "h", "s", "S", "n"),
+ axes = checkbox(TRUE, "Draw Axes"),
+ label = checkbox(FALSE, "Draw Labels"))
+}
+}
+
+
diff --git a/src/cpp/r/R/packages/manipulate/man/manipulate.Rd b/src/cpp/r/R/packages/manipulate/man/manipulate.Rd
new file mode 100644
index 0000000..eef6615
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/manipulate.Rd
@@ -0,0 +1,74 @@
+\name{manipulate}
+\alias{manipulate}
+\title{Create an interactive plot}
+\description{
+The \code{\link{manipulate}} function accepts a plotting expression and a set of controls (e.g. \code{\link{slider}}, \code{\link{picker}}, \code{\link{checkbox}}, or \code{\link{button}}) which are used to dynamically change values within the expression. When a value is changed using its corresponding control the expression is automatically re-executed and the plot is redrawn.
+}
+\usage{
+manipulate(`_expr`, ...)
+}
+
+
+\arguments{
+ \item{_expr}{
+ Expression to evalulate. The expression should result in the creation of a plot (e.g. \code{plot} or \code{qplot}). Note that the expression need not be a top-level plotting function, it could also be a custom function that creates a plot as part of its implementation. This expression will be re-evaluated with appropriate parameter substitution each time one of the manipulator control values is changed.
+}
+ \item{\dots}{
+ One or more named control arguments (i.e. \code{\link{slider}}, \code{\link{picker}}, \code{\link{checkbox}}, or \code{\link{button}}), or a list containing named controls.
+}
+}
+
+\details{
+ Once a set of manipulator controls are attached to a plot they remain attached and can be recalled whenever viewing the plot (a gear button is added to the top-left of the plot to indicate that it has a manipulator).
+
+ The \code{_expr} argument is evaluated using \code{\link{withVisible}}. If it's return value is visible then \code{\link{print}} is called. This enables manipulate expressions to behave simillarly to their being executed directly at the console.
+
+ The \code{_expr} argument uses a syntactially invalid (but backtick quoted) name to avoid clashes with named control arguments.
+
+ The \code{\link{manipulatorSetState}} and \code{\link{manipulatorGetState}} functions can be used to associate custom state with a manipulator (for example, to track the values used for previous plot executions). These values are stored in a custom environment which is stored along with the rest of the manipulator context.
+}
+
+\author{
+RStudio <info at rstudio.com>
+}
+
+\examples{
+\dontrun{
+
+## Create a plot with a manipulator
+manipulate(plot(1:x), x = slider(5, 10))
+
+## Using more than one slider
+manipulate(
+ plot(cars, xlim=c(x.min,x.max)),
+ x.min=slider(0,15),
+ x.max=slider(15,30))
+
+## Filtering data with a picker
+manipulate(
+ barplot(as.matrix(longley[,factor]),
+ beside = TRUE, main = factor),
+ factor = picker("GNP", "Unemployed", "Employed"))
+
+## Create a picker with labels
+manipulate(
+ plot(pressure, type = type),
+ type = picker("points" = "p", "line" = "l", "step" = "s"))
+
+## Toggle boxplot outlier display using checkbox
+manipulate(
+ boxplot(Freq ~ Class, data = Titanic, outline = outline),
+ outline = checkbox(FALSE, "Show outliers"))
+
+## Combining controls
+manipulate(
+ plot(cars, xlim = c(x.min, x.max), type = type,
+ axes = axes, ann = label),
+ x.min = slider(0,15),
+ x.max = slider(15,30, initial = 25),
+ type = picker("p", "l", "b", "c", "o", "h", "s", "S", "n"),
+ axes = checkbox(TRUE, "Draw Axes"),
+ label = checkbox(FALSE, "Draw Labels"))
+}
+}
+
diff --git a/src/cpp/r/R/packages/manipulate/man/mouseclick.Rd b/src/cpp/r/R/packages/manipulate/man/mouseclick.Rd
new file mode 100644
index 0000000..0b85291
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/mouseclick.Rd
@@ -0,0 +1,45 @@
+\name{manipulatorMouseClick}
+\alias{manipulatorMouseClick}
+\title{Receive notification of mouse clicks on a manipulator plot}
+\description{
+ This function can be called to determine if a mouse click on the plot was what caused the current invocation of the manipulate expression, and to determine the coordinates which were clicked.
+}
+\usage{
+manipulatorMouseClick()
+}
+
+\details{
+ If a mouse click did occur, then the function returns a list with the coordinates which the user clicked on.
+
+ If a mouse click did not cause the current invocation of the manipulate expression (e.g. if it was caused by the user changing the value of a control) then the function returns NULL.
+
+ The mouse click coordinates are provided in device, user, and ndc coordinates. To convert these coordinates into other coordinate systems (e.g. cm or npc) you can use the \code{\link{grconvertX}} and \code{\link{grconvertY}} functions.
+
+ Note that the userX and userY coordinates are only applicable for base graphics plots (they are not applicable for grid, lattice, ggplot, etc). Therefore, for non-base graphics the userX and userY values will not contain valid coordinates.
+}
+
+\value{
+Returns a list containing the coordinates that user clicked (or NULL if a mouse click didn't occur):
+\tabular{ll}{
+ \code{deviceX} \tab Device X coordinate (expressed in pixels)\cr
+ \code{deviceY} \tab Device Y coordinate (expressed in pixels)\cr
+ \code{userX} \tab User X coordinate (expressed in plot x units). Note that this value is only valid for base graphics.\cr
+ \code{userY} \tab User Y coordinate (expressed in plot y units). Note that this value is only valid for base graphics.\cr
+ \code{ndcX} \tab NDC X coordinate (0 to 1 from left to right)\cr
+ \code{ndcY} \tab NDC Y coordinate (0 to 1 from bottom to top)\cr
+}
+}
+
+\seealso{
+ \code{\link{manipulate}}, \code{\link{grconvertX}}, \code{\link{grconvertY}}
+}
+
+\examples{
+\dontrun{
+
+
+
+}
+}
+
+
diff --git a/src/cpp/r/R/packages/manipulate/man/picker.Rd b/src/cpp/r/R/packages/manipulate/man/picker.Rd
new file mode 100644
index 0000000..1666479
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/picker.Rd
@@ -0,0 +1,64 @@
+\name{picker}
+\alias{picker}
+\title{Create a picker control}
+\description{
+ Create a picker control to enable manipulation of plot variables based on a set of fixed choices.
+}
+
+\usage{
+picker(..., initial = NULL, label = NULL)
+}
+
+
+\arguments{
+ \item{\dots}{
+ Arguments containing objects to be presented as choices for the picker (or a list containing the choices). If an element is named then the name is used to display it within the picker. If an element is not named then it is displayed within the picker using \code{\link{as.character}}.
+}
+ \item{initial}{
+ Initial value for picker. Value must be present in the list of choices specified. If not specified defaults to the first choice.
+}
+ \item{label}{
+ Display label for picker. Defaults to the variable name if not specified.
+}
+}
+
+\value{
+ An object of class "manipulator.picker" which can be passed to the \code{\link{manipulate}} function.
+}
+
+\seealso{
+\code{\link{manipulate}}, \code{\link{slider}}, \code{\link{checkbox}}, \code{\link{button}}
+}
+
+
+\examples{
+\dontrun{
+
+## Filtering data with a picker
+manipulate(
+ barplot(as.matrix(longley[,factor]),
+ beside = TRUE, main = factor),
+ factor = picker("GNP", "Unemployed", "Employed"))
+
+## Create a picker with labels
+manipulate(
+ plot(pressure, type = type),
+ type = picker("points" = "p", "line" = "l", "step" = "s"))
+
+## Picker with groups
+manipulate(
+ barplot(as.matrix(mtcars[group,"mpg"]), beside=TRUE),
+ group = picker("Group 1" = 1:11,
+ "Group 2" = 12:22,
+ "Group 3" = 23:32))
+
+## Histogram w/ picker to select type
+require(lattice)
+require(stats)
+manipulate(
+ histogram(~ height | voice.part,
+ data = singer, type = type),
+ type = picker("percent", "count", "density"))
+
+}
+}
\ No newline at end of file
diff --git a/src/cpp/r/R/packages/manipulate/man/slider.Rd b/src/cpp/r/R/packages/manipulate/man/slider.Rd
new file mode 100644
index 0000000..dfac6c9
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/slider.Rd
@@ -0,0 +1,70 @@
+\name{slider}
+\alias{slider}
+\title{Create a slider control}
+\description{
+ Create a slider control to allow manipulation of a plot variable along a numeric range.
+}
+\usage{
+slider(min, max, initial = min,
+ label = NULL, step = NULL, ticks = TRUE)
+}
+
+\arguments{
+ \item{min}{
+ Minimum value for slider.
+}
+ \item{max}{
+ Maximum value for slider.
+}
+ \item{value}{
+ Initial value for slider. Defaults to \code{min} if not specified.
+}
+ \item{label}{
+ Display label for slider. Defaults to the variable name if not specified.
+}
+ \item{step}{
+ Step value for slider. If not specified then defaults to 1 for integer ranges and single pixel granularity for floating point ranges (\code{max} - \code{min} divided by the number of pixels in the slider).
+}
+ \item{ticks}{
+ Show tick marks on the slider. Note that if the granularity of the step value is very low (more than 25 ticks would be shown) then ticks are automatically turned off.
+}
+
+}
+
+\value{
+ An object of class "manipulator.slider" which can be passed to the \code{\link{manipulate}} function.
+}
+
+\seealso{
+\code{\link{manipulate}}, \code{\link{picker}}, \code{\link{checkbox}}, \code{\link{button}}
+}
+
+\examples{
+\dontrun{
+
+## Create a plot with a slider
+manipulate(plot(1:x), x = slider(5, 10))
+
+## Use multiple sliders
+manipulate(
+ plot(cars, xlim = c(x.min, x.max)),
+ x.min = slider(0,15),
+ x.max = slider(15,30))
+
+## Specify a custom initial value for a slider
+manipulate(
+ barplot(1:x),
+ x = slider(5, 25, initial = 10))
+
+## Specify a custom label for a slider
+manipulate(
+ barplot(1:x),
+ x = slider(5, 25, label = "Limit"))
+
+## Specify a step value for a slider
+manipulate(
+ barplot(1:x),
+ x = slider(5, 25, step = 5))
+}
+}
+
diff --git a/src/cpp/r/R/packages/manipulate/man/state.Rd b/src/cpp/r/R/packages/manipulate/man/state.Rd
new file mode 100644
index 0000000..a36ac24
--- /dev/null
+++ b/src/cpp/r/R/packages/manipulate/man/state.Rd
@@ -0,0 +1,45 @@
+\name{Manipulator Custom State}
+\alias{manipulatorSetState}
+\alias{manipulatorGetState}
+\title{Modify manipulator state}
+\description{
+ These functions allow the storage of custom state variables across multiple evaluations of manipulator expressions. These functions are useful if the manipulate expression is a custom function (rather than a high level plotting function like \code{\link{plot}}) which requires reading and writing of persistent values.
+}
+\usage{
+manipulatorSetState(name, value)
+manipulatorGetState(name)
+}
+
+\arguments{
+ \item{name}{
+ A chraracter string holding a state variable name.
+}
+ \item{value}{
+ An object holding a state value.
+}
+}
+
+\value{
+ \code{manipulatorGetState} returns a custom state value which was previously set by \code{manipulatorSetState} (or \code{NULL} if the specified name is not found).
+}
+
+\seealso{
+ \code{\link{manipulate}}
+}
+
+\examples{
+\dontrun{
+
+## set custom state variable
+manipulatorSetState("last", x)
+
+## get custom state variable
+last <- manipulatorGetState("last")
+if ( !is.null(last) ) {
+ # do something interesting
+}
+
+}
+}
+
+
diff --git a/src/cpp/r/R/packages/rstudio/.Rbuildignore b/src/cpp/r/R/packages/rstudio/.Rbuildignore
new file mode 100644
index 0000000..91114bf
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/.Rbuildignore
@@ -0,0 +1,2 @@
+^.*\.Rproj$
+^\.Rproj\.user$
diff --git a/src/cpp/r/R/packages/rstudio/.gitignore b/src/cpp/r/R/packages/rstudio/.gitignore
new file mode 100644
index 0000000..458b226
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/.gitignore
@@ -0,0 +1 @@
+DESCRIPTION
diff --git a/src/cpp/r/R/packages/rstudio/DESCRIPTION.in b/src/cpp/r/R/packages/rstudio/DESCRIPTION.in
new file mode 100644
index 0000000..065f017
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/DESCRIPTION.in
@@ -0,0 +1,13 @@
+Package: rstudio
+Type: Package
+Title: Tools and Utilities for RStudio
+Version: ${CPACK_PACKAGE_VERSION}
+Date: 2012-09-17
+Author: RStudio
+Maintainer: RStudio <info at rstudio.com>
+Description: Interactive Plots for RStudio
+Depends: R (>= 2.10.0)
+Imports: utils
+License:
+LazyLoad: yes
+
diff --git a/src/cpp/r/R/packages/rstudio/NAMESPACE b/src/cpp/r/R/packages/rstudio/NAMESPACE
new file mode 100644
index 0000000..bdee3ed
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/NAMESPACE
@@ -0,0 +1,6 @@
+export(versionInfo)
+export(diagnosticsReport)
+export(previewRd)
+export(viewer)
+export(shinyViewer)
+
diff --git a/src/cpp/r/R/packages/rstudio/R/rstudio.R b/src/cpp/r/R/packages/rstudio/R/rstudio.R
new file mode 100644
index 0000000..e9c4e4a
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/R/rstudio.R
@@ -0,0 +1,40 @@
+
+
+versionInfo <- function() {
+ info <- list()
+ info$version <- package_version(utils:::packageDescription("rstudio",
+ fields="Version"))
+ info$mode <- .Call(getNativeSymbolInfo("rs_rstudioProgramMode",
+ PACKAGE=""))
+ info
+}
+
+diagnosticsReport <- function() {
+ invisible(.Call(getNativeSymbolInfo("rs_sourceDiagnostics", PACKAGE="")))
+}
+
+previewRd <- function(rdFile) {
+
+ if (!is.character(rdFile) || (length(rdFile) != 1))
+ stop("rdFile must be a single element character vector.")
+ if (!file.exists(rdFile))
+ stop("The specified rdFile ' ", rdFile, "' does not exist.")
+
+ invisible(.Call(getNativeSymbolInfo("rs_previewRd", PACKAGE=""), rdFile))
+}
+
+viewer <- function(url, height = NULL) {
+
+ if (!is.character(url) || (length(url) != 1))
+ stop("url must be a single element character vector.")
+
+ if (!is.null(height) && (!is.numeric(height) || (length(height) != 1)))
+ stop("height must be a single element numeric vector.")
+
+ invisible(.Call(getNativeSymbolInfo("rs_viewer", PACKAGE=""), url, height))
+}
+
+shinyViewer <- function(url, path) {
+
+ invisible(.Call(getNativeSymbolInfo("rs_shinyviewer", PACKAGE=""), url, path))
+}
diff --git a/src/cpp/r/R/packages/rstudio/inst/CITATION b/src/cpp/r/R/packages/rstudio/inst/CITATION
new file mode 100644
index 0000000..30cb0f9
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/inst/CITATION
@@ -0,0 +1,19 @@
+bibentry("Manual",
+ title = "RStudio: Integrated Development Environment for R",
+ author = person("RStudio Team"),
+ organization = "RStudio, Inc.",
+ address = "Boston, MA",
+ year = "2012",
+ url = "http://www.rstudio.com/",
+
+ textVersion =
+ paste("RStudio Team (2012). ",
+ "RStudio: Integrated Development for R. ",
+ "RStudio, Inc., Boston, MA ",
+ "URL http://www.rstudio.com/.",
+ sep=""),
+
+ mheader = "To cite RStudio in publications use:",
+
+ mfooter = ""
+ )
diff --git a/src/cpp/r/R/packages/rstudio/man/diagnosticsReport.Rd b/src/cpp/r/R/packages/rstudio/man/diagnosticsReport.Rd
new file mode 100644
index 0000000..a0709c5
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/man/diagnosticsReport.Rd
@@ -0,0 +1,17 @@
+\name{diagnosticsReport}
+\alias{diagnosticsReport}
+\title{
+Write an RStudio Diagnostics Report
+}
+\description{
+Write a diagnostics report to assist in troubleshooting problems with RStudio. The report is written to ~/rstudio-diagnostics and contains information on the R session and environment as well as copies of the RStudio log files.
+}
+\usage{
+diagnosticsReport()
+}
+
+\examples{
+\dontrun{
+rstudio::diagnosticsReport()
+}
+}
\ No newline at end of file
diff --git a/src/cpp/r/R/packages/rstudio/man/previewRd.Rd b/src/cpp/r/R/packages/rstudio/man/previewRd.Rd
new file mode 100644
index 0000000..0d4906c
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/man/previewRd.Rd
@@ -0,0 +1,21 @@
+\name{previewRd}
+\alias{previewRd}
+
+\title{
+Preview an Rd topic in the Help pane
+}
+\description{
+Preview an Rd topic in the Help pane
+}
+\usage{
+previewRd(rdFile)
+}
+\arguments{
+ \item{rdFile}{Single element character vector containing the name of the Rd file to be displayed}
+}
+
+\examples{
+\dontrun{
+rstudio::previewRd("~/MyPackage/man/foo.Rd")
+}
+}
\ No newline at end of file
diff --git a/src/cpp/r/R/packages/rstudio/man/rstudio-package.Rd b/src/cpp/r/R/packages/rstudio/man/rstudio-package.Rd
new file mode 100644
index 0000000..7545851
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/man/rstudio-package.Rd
@@ -0,0 +1,52 @@
+\name{rstudio-package}
+\alias{rstudio-package}
+\alias{rstudio}
+\docType{package}
+\title{
+Tools and Utilities for RStudio
+}
+\description{
+Tools, utility functions, and APIs for use within RStudio.
+}
+
+\author{
+RStudio
+
+Maintainer: RStudio <info at rstudio.com>
+}
+
+\keyword{ package }
+
+\examples{
+\dontrun{
+
+# Test whether running under RStudio
+isRStudio <- Sys.getenv("RSTUDIO") == "1"
+
+# Use RStudio version information
+if (isRStudio) {
+
+ # Get version info
+ require(rstudio)
+ rstudioVer <- versionInfo()
+
+ # Test specific version constraint
+ if (rstudioVer$version >= "0.97") {
+ # do some 0.97 dependent stuff
+ }
+
+ # Check current mode
+ desktopMode <- rstudioVer$mode == "desktop"
+ serverMode <- rstudioVer$mode == "server"
+
+ # Write diagnostics to ~/rstudio-diagnostics
+ rstudio::diagnosticsReport()
+
+ # Get citation information for RStudio
+ utils::citation("rstudio")
+}
+
+}
+
+}
+
diff --git a/src/cpp/r/R/packages/rstudio/man/versionInfo.Rd b/src/cpp/r/R/packages/rstudio/man/versionInfo.Rd
new file mode 100644
index 0000000..e16d478
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/man/versionInfo.Rd
@@ -0,0 +1,40 @@
+\name{versionInfo}
+\alias{versionInfo}
+\title{
+RStudio Version Information
+}
+\description{
+Provides information about the currently running version of RStudio, including it's specific version number and whether it is running in desktop or server mode.
+}
+\usage{
+versionInfo()
+}
+
+\value{
+
+Returns a list with the following elements:
+
+\tabular{ll}{
+ \code{version} \tab A package version object that can be used in comparisons. This is the same value which would be returned from \code{packageVersion("rstudio")} \cr
+ \code{mode} \tab Current program mode (either "desktop" or "server")\cr
+}
+
+
+}
+
+\examples{
+\dontrun{
+require(rstudio)
+rstudioVer <- versionInfo()
+
+# Test specific version constraint
+if (rstudioVer$version >= "0.97") {
+ # do some 0.97 dependent stuff
+}
+
+# Check current mode
+desktopMode <- rstudioVer$mode == "desktop"
+serverMode <- rstudioVer$mode == "server"
+}
+}
+
diff --git a/src/cpp/r/R/packages/rstudio/man/viewer.Rd b/src/cpp/r/R/packages/rstudio/man/viewer.Rd
new file mode 100644
index 0000000..9e1b8e5
--- /dev/null
+++ b/src/cpp/r/R/packages/rstudio/man/viewer.Rd
@@ -0,0 +1,69 @@
+\name{viewer}
+\alias{viewer}
+
+\title{
+View local web content within RStudio
+}
+\description{
+View local web content within RStudio. Content can be served from static files in the R session temporary directory or can be a \link[shiny:shiny-package]{Shiny}, \link[Rook:Rook-package]{Rook}, \link[opencpu:opencpu]{OpenCPU}, or any other type of localhost web application.
+}
+\usage{
+rstudio::viewer(url, height = NULL)
+}
+\arguments{
+ \item{url}{Application URL. This can be either a localhost URL or a path to a file within the R session temporary directory (i.e. a path returned by \code{\link[base:tempfile]{tempfile}}).
+ }
+ \item{height}{Desired height. Specifies a desired height for the Viewer pane (the default is \code{NULL} which makes no change to the height of the pane). See details below for a discussion of constraints imposed on the height.
+ }
+}
+
+\details{
+RStudio also sets the global \code{viewer} option to the \code{rstudio::viewer} function so that it can be invoked in a front-end independent manner.
+
+Applications are displayed within the Viewer pane. The application URL must either be served from localhost or be a path to a file within the R session temporary directory. If the URL doesn't conform to these requirements it is displayed within a standard browser window.
+
+The \code{height} parameter specifies a desired height, however it's possible the Viewer pane will end up smaller if the request can't be fulfilled (RStudio ensures that the pane paired with the Viewer maintains a minimum height). A height of 400 pixels or lower is likely to succeed in a large proportion of configurations. A very large height (e.g. 2000 pixels) will allocate the maximum allowable space for the Viewer.
+
+}
+
+\section{Viewer Detection}{
+
+When a page is displayed within the Viewer it's possible that the user will choose to pop it out into a standalone browser window. When rendering inside a standard browser you may want to make different choices about how content is laid out or scaled. Web pages can detect that they are running inside the Viewer pane by looking for the \code{viewer_pane} query parameter, which is automatically injected into URLs when they are shown in the Viewer. For example, the following URL:
+
+\preformatted{
+http://localhost:8100
+}
+
+When rendered in the Viewer pane is transformed to:
+
+\preformatted{
+http://localhost:8100?viewer_pane=1
+}
+
+To provide a good user experience it's strongly recommended that callers take advantage of this to automatically scale their content to the current size of the Viewer pane. For example, re-rendering a JavaScript plot with new dimensions when the size of the pane changes.
+
+}
+
+\examples{
+\dontrun{
+
+# run an application inside the IDE
+rstudio::viewer("http://localhost:8100")
+
+# run an application and request a height of 500 pixels
+rstudio::viewer("http://localhost:8100", height = 500)
+
+# probe for viewer option then fall back to browseURL
+viewer <- getOption("viewer")
+if (!is.null(viewer))
+ viewer("http://localhost:8100")
+else
+ utils::browseURL("http://localhost:8100")
+
+# generate a temporary html file and display it
+htmlFile <- tempfile(fileext=".html")
+# (code to write some content to the file)
+rstudio::viewer(htmlFile)
+}
+
+}
diff --git a/src/cpp/r/RErrorCategory.cpp b/src/cpp/r/RErrorCategory.cpp
new file mode 100644
index 0000000..4214cd8
--- /dev/null
+++ b/src/cpp/r/RErrorCategory.cpp
@@ -0,0 +1,117 @@
+/*
+ * RErrorCategory.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RErrorCategory.hpp>
+
+namespace r {
+
+class RErrorCategory : public boost::system::error_category
+{
+public:
+ virtual const char * name() const;
+ virtual std::string message( int ev ) const;
+};
+
+const boost::system::error_category& rCategory()
+{
+ static RErrorCategory rErrorCategoryConst ;
+ return rErrorCategoryConst ;
+}
+
+const char * RErrorCategory::name() const
+{
+ return "r" ;
+}
+
+std::string RErrorCategory::message( int ev ) const
+{
+ std::string message ;
+ switch (ev)
+ {
+ case errc::RHomeNotFound:
+ message = "Unable to find R home directory";
+ break;
+
+ case errc::UnsupportedLocale:
+ message = "Unsupported locale (UTF-8 required)";
+ break;
+
+ case errc::ExpressionParsingError:
+ message = "Expression parsing error";
+ break;
+
+ case errc::CodeExecutionError:
+ message = "R code execution error";
+ break;
+
+ case errc::SymbolNotFoundError:
+ message = "R symbol not found";
+ break;
+
+ case errc::ListElementNotFoundError:
+ message = "List element not found";
+ break;
+
+ case errc::UnexpectedDataTypeError:
+ message = "Unexpected data type";
+ break;
+
+ case errc::NoDataAvailableError:
+ message = "No data available from R";
+ break;
+
+ default:
+ message = "Unknown error" ;
+ break;
+ }
+
+ return message ;
+}
+
+core::Error rCodeExecutionError(const std::string& errMsg,
+ const core::ErrorLocation& location)
+{
+ core::Error error(errc::CodeExecutionError, location);
+ error.addProperty("errormsg", errMsg);
+ return error;
+}
+
+
+bool isCodeExecutionError(const core::Error& error, std::string* pErrMsg)
+{
+ if (error.code() == r::errc::CodeExecutionError)
+ {
+ if (pErrMsg != NULL)
+ *pErrMsg = error.getProperty("errormsg");
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+std::string endUserErrorMessage(const core::Error& error)
+{
+ std::string errMsg;
+ if (isCodeExecutionError(error, &errMsg))
+ return errMsg;
+ else
+ return error.code().message();
+}
+
+
+
+} // namespace r
diff --git a/src/cpp/r/RExec.cpp b/src/cpp/r/RExec.cpp
new file mode 100644
index 0000000..1fe2138
--- /dev/null
+++ b/src/cpp/r/RExec.cpp
@@ -0,0 +1,505 @@
+/*
+ * RExec.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RExec.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/Log.hpp>
+
+#include <r/RErrorCategory.hpp>
+#include <r/RSourceManager.hpp>
+#include <r/RInterface.hpp>
+#include <r/ROptions.hpp>
+
+#include <R_ext/Parse.h>
+
+#include <R_ext/libextern.h>
+LibExtern Rboolean R_interrupts_suspended;
+LibExtern int R_interrupts_pending;
+#ifdef _WIN32
+LibExtern int UserBreak;
+#endif
+
+using namespace core ;
+
+namespace r {
+
+namespace exec {
+
+namespace {
+
+// create a scope for disabling any installed error handlers (e.g. recover)
+// we need to do this so that recover isn't invoked while we are running
+// R code within an r::exec scope -- when the user presses 0 to exit
+// from recover and jump_to_top it gets eaten by the R_ToplevelExecute
+// context so the console becomes unresponsive
+class DisableErrorHandlerScope : boost::noncopyable
+{
+public:
+ DisableErrorHandlerScope()
+ : didDisable_(false),
+ previousErrorHandlerSEXP_(R_NilValue)
+ {
+ previousErrorHandlerSEXP_ = r::options::setErrorOption(R_NilValue);
+ if (previousErrorHandlerSEXP_ != R_NilValue)
+ {
+ rProtect_.add(previousErrorHandlerSEXP_);
+ didDisable_ = true;
+ }
+ }
+ virtual ~DisableErrorHandlerScope()
+ {
+ try
+ {
+ if (didDisable_)
+ r::options::setErrorOption(previousErrorHandlerSEXP_);
+ }
+ catch(...)
+ {
+ }
+ }
+
+private:
+ bool didDisable_;
+ r::sexp::Protect rProtect_;
+ SEXP previousErrorHandlerSEXP_;
+};
+
+
+Error parseString(const std::string& str, SEXP* pSEXP, sexp::Protect* pProtect)
+{
+ // string to parse
+ SEXP cv = sexp::create(str, pProtect);
+
+ // do the parse and protect the result
+ ParseStatus ps;
+ *pSEXP=R_ParseVector(cv, 1, &ps, R_NilValue);
+ pProtect->add(*pSEXP);
+
+ // check error/success
+ if (ps != PARSE_OK)
+ {
+ Error error(errc::ExpressionParsingError, ERROR_LOCATION);
+ error.addProperty("code", str);
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+// evaluate expressions without altering the error handler (use with caution--
+// a user-supplied error handler may be invoked if the expression raises
+// an error!)
+Error evaluateExpressionsUnsafe(SEXP expr,
+ SEXP env,
+ SEXP* pSEXP,
+ sexp::Protect* pProtect)
+{
+ int er=0;
+ int i=0,l;
+
+ // if we have an entire expression list, evaluate its contents one-by-one
+ // and return only the last one
+ if (TYPEOF(expr)==EXPRSXP)
+ {
+ l = LENGTH(expr);
+ while (i<l)
+ {
+ *pSEXP = R_tryEval(VECTOR_ELT(expr, i), env, &er);
+ i++;
+ }
+ }
+ // evaluate single expression
+ else
+ {
+ *pSEXP = R_tryEval(expr, R_GlobalEnv, &er);
+ }
+
+ // protect the result
+ pProtect->add(*pSEXP);
+
+ if (er)
+ {
+ // get error message -- note this results in a recursive call to
+ // evaluate expressions during the fetching of the error. if this
+ // call yielded an error then this could infinitely recurse. it doesn't
+ // appears as if geterrmessage will ever return an error state so
+ // this is likely not an issue. still, if we were concerned about it
+ // then we could simply read the error buffer directly from the module
+ // where do_geterrmessage is defined (errors.c)
+ return rCodeExecutionError(getErrorMessage(), ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error evaluateExpressions(SEXP expr,
+ SEXP env,
+ SEXP* pSEXP,
+ sexp::Protect* pProtect)
+{
+ // disable custom error handlers while we execute code
+ DisableErrorHandlerScope disableErrorHandler;
+
+ return evaluateExpressionsUnsafe(expr, env, pSEXP, pProtect);
+}
+
+Error evaluateExpressions(SEXP expr, SEXP* pSEXP, sexp::Protect* pProtect)
+{
+ return evaluateExpressions(expr, R_GlobalEnv, pSEXP, pProtect);
+}
+
+void topLevelExec(void *data)
+{
+ boost::function<void()>* pFunction = (boost::function<void()>*)data;
+ pFunction->operator()();
+}
+
+struct SEXPTopLevelExecContext
+{
+ boost::function<SEXP()> function;
+ SEXP* pReturnSEXP ;
+};
+
+void SEXPTopLevelExec(void *data)
+{
+ SEXPTopLevelExecContext* pContext = (SEXPTopLevelExecContext*)data;
+ *(pContext->pReturnSEXP) = pContext->function();
+}
+
+} // anonymous namespace
+
+Error executeSafely(boost::function<void()> function)
+{
+ // disable custom error handlers while we execute code
+ DisableErrorHandlerScope disableErrorHandler;
+
+ Rboolean success = R_ToplevelExec(topLevelExec, (void*)&function);
+ if (!success)
+ {
+ return rCodeExecutionError(getErrorMessage(), ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+core::Error executeSafely(boost::function<SEXP()> function, SEXP* pSEXP)
+{
+ // disable custom error handlers while we execute code
+ DisableErrorHandlerScope disableErrorHandler;
+
+ SEXPTopLevelExecContext context ;
+ context.function = function ;
+ context.pReturnSEXP = pSEXP ;
+ Rboolean success = R_ToplevelExec(SEXPTopLevelExec, (void*)&context);
+ if (!success)
+ {
+ return rCodeExecutionError(getErrorMessage(), ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error executeString(const std::string& str)
+{
+ sexp::Protect rProtect;
+ SEXP ignoredSEXP ;
+ return evaluateString(str, &ignoredSEXP, &rProtect);
+}
+
+Error evaluateString(const std::string& str,
+ SEXP* pSEXP,
+ sexp::Protect* pProtect)
+{
+ // refresh source if necessary (no-op in production)
+ r::sourceManager().reloadIfNecessary();
+
+ // surrond the string with try in silent mode so we can capture error text
+ std::string rCode = "try(" + str + ", TRUE)";
+
+ // parse expression
+ SEXP ps;
+ Error parseError = parseString(rCode, &ps, pProtect);
+ if (parseError)
+ return parseError;
+
+ // evaluate the expression
+ Error evalError = evaluateExpressions(ps, pSEXP, pProtect);
+ if (evalError)
+ {
+ evalError.addProperty("code", str);
+ return evalError;
+ }
+
+ // check for try-error
+ if (Rf_inherits(*pSEXP, "try-error"))
+ {
+ // get error message (merely log on failure so we can continue
+ // and return the real error)
+ std::string errorMsg ;
+ Error extractError = sexp::extract(*pSEXP, &errorMsg);
+ if (extractError)
+ LOG_ERROR(extractError);
+
+ // add it to the error
+ return rCodeExecutionError(errorMsg, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+RFunction::RFunction(SEXP functionSEXP)
+{
+ functionSEXP_ = functionSEXP;
+ rProtect_.add(functionSEXP_);
+}
+
+RFunction::~RFunction()
+{
+}
+
+void RFunction::commonInit(const std::string& functionName)
+{
+ // refresh source if necessary (no-op in production)
+ r::sourceManager().reloadIfNecessary();
+
+ // record functionName (used later for diagnostics)
+ functionName_ = functionName;
+
+ // get name & ns
+ std::string name, ns;
+
+ // check for namespace qualifier
+ std::string nsQual(":::");
+ size_t pos = functionName_.find(nsQual);
+ if (pos != std::string::npos)
+ {
+ ns = functionName_.substr(0, pos);
+ name = functionName_.substr(pos + nsQual.size());
+
+ }
+ else
+ {
+ name = functionName_;
+ }
+
+ // lookup function
+ functionSEXP_ = sexp::findFunction(name, ns);
+ if (functionSEXP_ != R_UnboundValue)
+ rProtect_.add(functionSEXP_);
+}
+
+Error RFunction::callUnsafe()
+{
+ return call(R_GlobalEnv, false);
+}
+
+Error RFunction::call(SEXP evalNS, bool safely)
+{
+ sexp::Protect rProtect;
+ SEXP ignoredResultSEXP ;
+ return call(evalNS, safely, &ignoredResultSEXP, &rProtect);
+}
+
+Error RFunction::call(SEXP* pResultSEXP, sexp::Protect* pProtect)
+{
+ return call(R_GlobalEnv, pResultSEXP, pProtect);
+}
+
+Error RFunction::call(SEXP evalNS, SEXP* pResultSEXP, sexp::Protect* pProtect)
+{
+ return call(evalNS, true, pResultSEXP, pProtect);
+}
+
+Error RFunction::call(SEXP evalNS, bool safely, SEXP* pResultSEXP,
+ sexp::Protect* pProtect)
+{
+ // verify the function
+ if (functionSEXP_ == R_UnboundValue)
+ {
+ Error error(errc::SymbolNotFoundError, ERROR_LOCATION);
+ if (!functionName_.empty())
+ error.addProperty("symbol", functionName_);
+ return error;
+ }
+
+ // create the call object (LANGSXP) with the correct number of elements
+ SEXP callSEXP ;
+ pProtect->add(callSEXP = Rf_allocVector(LANGSXP, 1 + params_.size()));
+ SET_TAG(callSEXP, R_NilValue); // just like do_ascall() does
+
+ // assign the function to the first element of the call
+ SETCAR(callSEXP, functionSEXP_);
+
+ // assign parameters to the subseqent elements of the call
+ SEXP nextSlotSEXP = CDR(callSEXP);
+ for (std::vector<Param>::const_iterator
+ it = params_.begin(); it != params_.end(); ++it)
+ {
+ SETCAR(nextSlotSEXP, it->valueSEXP);
+ // parameters can optionally be named
+ if (!(it->name.empty()))
+ SET_TAG(nextSlotSEXP, Rf_install(it->name.c_str()));
+ nextSlotSEXP = CDR(nextSlotSEXP);
+ }
+
+ // call the function
+ Error error = safely ?
+ evaluateExpressions(callSEXP, evalNS, pResultSEXP, pProtect) :
+ evaluateExpressionsUnsafe(callSEXP, evalNS, pResultSEXP, pProtect);
+ if (error)
+ return error;
+
+ // return success
+ return Success();
+}
+
+FilePath rBinaryPath()
+{
+ FilePath binPath = FilePath(R_HomeDir()).complete("bin");
+#ifdef _WIN32
+ return binPath.complete("Rterm.exe");
+#else
+ return binPath.complete("R");
+#endif
+}
+
+Error system(const std::string& command, std::string* pOutput)
+{
+ r::exec::RFunction system("system", command);
+ system.addParam("intern", true);
+ system.addParam("ignore.stderr", true);
+
+ // call it
+ Error error = system.call(pOutput);
+ if (error)
+ {
+ // if it is NoDataAvailable this means empty output
+ if (error.code() == r::errc::NoDataAvailableError)
+ {
+ pOutput->clear();
+ return Success();
+ }
+ else
+ {
+ return error;
+ }
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+
+void error(const std::string& message)
+{
+ Rf_error(message.c_str());
+}
+
+void errorCall(SEXP call, const std::string& message)
+{
+ Rf_errorcall(call, message.c_str());
+}
+
+std::string getErrorMessage()
+{
+ std::string errMessage ;
+ Error callError = RFunction("geterrmessage").call(&errMessage);
+ if (callError)
+ LOG_ERROR(callError);
+ return errMessage;
+}
+
+
+void warning(const std::string& warning)
+{
+ Rf_warning(warning.c_str());
+}
+
+
+bool interruptsPending()
+{
+#ifdef _WIN32
+ return UserBreak == 1 ? true : false;
+#else
+ return R_interrupts_pending == 1 ? true : false;
+#endif
+}
+
+void setInterruptsPending(bool pending)
+{
+#ifdef _WIN32
+ UserBreak = pending ? 1 : 0;
+#else
+ R_interrupts_pending = pending ? 1 : 0;
+#endif
+}
+
+void checkUserInterrupt()
+{
+ R_CheckUserInterrupt();
+}
+
+IgnoreInterruptsScope::IgnoreInterruptsScope()
+ : pSignalBlocker_(new core::system::SignalBlocker())
+{
+ // save suspend state and set suspend flag
+ previousInterruptsSuspended_ = (R_interrupts_suspended == TRUE);
+ R_interrupts_suspended = TRUE;
+
+ // clear existing
+ setInterruptsPending(false);
+
+ // enable signal blocker
+ Error error = pSignalBlocker_->block(core::system::SigInt);
+ if (error)
+ LOG_ERROR(error);
+}
+
+IgnoreInterruptsScope::~IgnoreInterruptsScope()
+{
+ try
+ {
+ // delete signal blocker (may cause delivery of one of the blocked
+ // interrupts, but we restore the previous interrupt state below
+ // so this is no problem)
+ pSignalBlocker_.reset();
+
+ // restore suspended state
+ R_interrupts_suspended = previousInterruptsSuspended_ ? TRUE : FALSE;
+
+ // clear state
+ setInterruptsPending(false);
+ }
+ catch(...)
+ {
+ }
+}
+
+} // namespace exec
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RFunctionHook.cpp b/src/cpp/r/RFunctionHook.cpp
new file mode 100644
index 0000000..02f79cf
--- /dev/null
+++ b/src/cpp/r/RFunctionHook.cpp
@@ -0,0 +1,50 @@
+/*
+ * RFunctionHook.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RFunctionHook.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <r/RExec.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace function_hook {
+
+namespace {
+
+} // anonymous namespace
+
+
+Error registerUnsupported(const std::string& name, const std::string& package)
+{
+ return r::exec::RFunction(".rs.registerUnsupported", name, package).call();
+}
+
+Error registerUnsupportedWithAlternative(const std::string& name,
+ const std::string& package,
+ const std::string& alternative)
+{
+ return r::exec::RFunction(".rs.registerUnsupported",
+ name, package, alternative).call();
+}
+
+} // namespace function_hook
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RJson.cpp b/src/cpp/r/RJson.cpp
new file mode 100644
index 0000000..0d0b684
--- /dev/null
+++ b/src/cpp/r/RJson.cpp
@@ -0,0 +1,417 @@
+/*
+ * RJson.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/* Convert R objects to json, conversions are performed as follows:
+
+1) R nil values are converted to null. you can explicity return nil
+ from a function using "return ()"
+
+2) R vectors of primitive types are returned to json & GWT as follows:
+
+ string - array of string values (JsArrayString)
+ logical - array of true/false values (JsArrayBoolean)
+ real - array of number values (JsArrayNumber)
+ integer - array of number values (JsArrayInteger)
+ complex - array of complex objects (JsArray<Complex>)
+
+3) R new style lists (VECSXP) with named elements are returned to json & GWT as
+ json objects (with each named list element constituting an object field)
+
+4) R new style lists (VECSXP) without named elements (or with only some elements
+ named) are returned as a json array. this array may be heterogenous and
+ therefore not readily mappable to a JS overlay type so in general this
+ scenario should be avoided.
+
+5) R data frame (class="data.frame") are returned as arrays of json objects
+ JsArray<Object>. Note that when creating a data frame to be marshalled
+ back to javascript that check.rows = TRUE & stringsAsFactors = FALSE
+ should be specified.
+
+4) R old style lists (LISTSXP) are not currently supported.
+
+5) There is as of yet no explicit support for arrays or matrixes so they
+ will be returned as plain flattened vectors of primitive types
+
+*/
+
+#include <iostream>
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RJson.hpp>
+
+#include <core/Error.hpp>
+#include <core/StringUtils.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RErrorCategory.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace json {
+
+namespace {
+
+Error jsonValueFromVectorElement(SEXP vectorSEXP,
+ int i,
+ core::json::Value* pValue)
+{
+ // NOTE: currently NaN is represented in json as null. this is problematic
+ // as parsing routines (such as JS overlay types in GWT) won't handle
+ // this properly. we need to either have a higher level representation
+ // of r values (which would be quite verbose) or we need to represent
+ // r numbers as strings which can contain embedded NaN (other other special
+ // values like infinity) or we need to use a lower level JSON interface
+ // (not JS overlay types for accessing R data
+
+ // NOTE: we currently don't use NA_REAL (rather we use ISNAN). these
+ // are different concepts (no value and NaN). distinguish these cases
+ // and also make sure they are distinguished for other types
+
+ // default to null
+ *pValue = core::json::Value();
+
+ // check for underlying value
+ switch(TYPEOF(vectorSEXP))
+ {
+ case NILSXP:
+ {
+ *pValue = core::json::Value();
+ break;
+ }
+ case STRSXP:
+ {
+ SEXP stringSEXP = STRING_ELT(vectorSEXP, i);
+ if (stringSEXP != NA_STRING)
+ {
+ std::string value(Rf_translateCharUTF8(stringSEXP));
+ *pValue = value;
+ }
+ break;
+ }
+ case INTSXP:
+ {
+ int value = INTEGER(vectorSEXP)[i] ;
+ if (value != NA_INTEGER)
+ *pValue = value;
+ break;
+ }
+ case REALSXP:
+ {
+ double value = REAL(vectorSEXP)[i] ;
+ if (!ISNAN(value))
+ *pValue = value;
+ break;
+ }
+ case LGLSXP:
+ {
+ int value = LOGICAL(vectorSEXP)[i];
+ if (value != NA_LOGICAL)
+ {
+ bool boolValue = (value == TRUE);
+ *pValue = boolValue;
+ }
+ break;
+ }
+ case CPLXSXP:
+ {
+ double real = COMPLEX(vectorSEXP)[i].r;
+ double imaginary = COMPLEX(vectorSEXP)[i].i;
+ if ( !ISNAN(real) && !ISNAN(imaginary))
+ {
+ core::json::Object jsonComplex ;
+ jsonComplex["r"] = real;
+ jsonComplex["i"] = imaginary;
+ *pValue = jsonComplex;
+ }
+ break;
+ }
+ case ENVSXP:
+ {
+ *pValue = std::string("<environment>");
+ break;
+ }
+ default:
+ {
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+ }
+ }
+
+ return Success();
+}
+
+
+Error jsonValueArrayFromList(SEXP listSEXP, core::json::Value* pValue)
+{
+ // value array to return
+ core::json::Array jsonValueArray;
+
+ // return a value for each list item
+ int listLength = Rf_length(listSEXP);
+ for (int i=0; i<listLength; i++)
+ {
+ // get the value
+ SEXP valueSEXP = VECTOR_ELT(listSEXP, i);
+
+ // extract the value
+ core::json::Value jsonValue ;
+ Error error = jsonValueFromObject(valueSEXP, &jsonValue);
+ if (error)
+ return error;
+
+ // add it to our result array
+ jsonValueArray.push_back(jsonValue);
+ }
+
+ // set value then return success
+ *pValue = jsonValueArray;
+ return Success();
+}
+
+bool isNamedList(SEXP listSEXP)
+{
+ // must have the same number of names as elements
+ int listLength = Rf_length(listSEXP);
+ SEXP namesSEXP = Rf_getAttrib(listSEXP, R_NamesSymbol);
+ if (namesSEXP == R_NilValue)
+ return false;
+ if (Rf_length(namesSEXP) != listLength)
+ return false;
+
+ // must have a name for each element
+ std::vector<std::string> fieldNames ;
+ Error error = sexp::getNames(listSEXP, &fieldNames);
+ if (error)
+ return false ;
+ int nameCount = std::count_if(fieldNames.begin(),
+ fieldNames.end(),
+ &core::string_utils::stringNotEmpty);
+ if (nameCount != listLength)
+ return false;
+
+ // passed all the tests!
+ return true;
+}
+
+Error jsonObjectFromListElement(SEXP listSEXP,
+ const std::vector<std::string>& fieldNames,
+ int index,
+ core::json::Value* pValue)
+{
+ // note list length
+ int listLength = Rf_length(listSEXP);
+
+ // compose an object by iterating through the fields
+ core::json::Object jsonObject ;
+ for (int f=0; f<listLength; f++)
+ {
+ // get the field
+ SEXP fieldSEXP = VECTOR_ELT(listSEXP, f);
+
+ // extract the value
+ core::json::Value fieldValue ;
+ switch(TYPEOF(fieldSEXP))
+ {
+ case VECSXP:
+ {
+ SEXP valueSEXP = VECTOR_ELT(fieldSEXP, index);
+ Error error = jsonValueFromObject(valueSEXP, &fieldValue);
+ if (error)
+ return error;
+
+ break;
+ }
+ default:
+ {
+ Error error = jsonValueFromVectorElement(fieldSEXP,
+ index,
+ &fieldValue);
+ if (error)
+ return error;
+
+ break;
+ }
+ }
+
+ // add it to the json object
+ jsonObject[fieldNames[f]] = fieldValue;
+ }
+
+ // set value and return success
+ *pValue = jsonObject;
+ return Success();
+}
+
+//
+// NOTE: this function assumes that isNamedList has been called
+// and returned true for this list (validates a name for each element)
+//
+Error jsonObjectFromList(SEXP listSEXP, core::json::Value* pValue)
+{
+ // get the names of the list elements
+ std::vector<std::string> fieldNames ;
+ Error error = sexp::getNames(listSEXP, &fieldNames);
+ if (error)
+ return error;
+
+ // compose object
+ core::json::Object object ;
+ int fields = Rf_length(listSEXP);
+ for (int i=0; i<fields; i++)
+ {
+ SEXP fieldSEXP = VECTOR_ELT(listSEXP, i);
+
+ core::json::Value objectValue ;
+ error = jsonValueFromObject(fieldSEXP, &objectValue);
+ if (error)
+ return error ;
+
+ object[fieldNames[i]] = objectValue;
+ }
+
+ // set object as return value
+ *pValue = object;
+ return Success();
+}
+
+//
+// NOTE: this function assumes that isNamedList has been called
+// and returned true for this list (validates a name for each element)
+//
+Error jsonObjectArrayFromDataFrame(SEXP listSEXP, core::json::Value* pValue)
+{
+ // get the names of the list elements
+ std::vector<std::string> fieldNames ;
+ Error error = sexp::getNames(listSEXP, &fieldNames);
+ if (error)
+ return error;
+
+ // object array to return
+ core::json::Array jsonObjectArray ;
+
+ // iterate through the values
+ int values = Rf_length(VECTOR_ELT(listSEXP, 0));
+ for (int v=0; v<values; v++)
+ {
+ core::json::Value objectValue ;
+ error = jsonObjectFromListElement(listSEXP, fieldNames, v, &objectValue);
+ if (error)
+ return error ;
+
+ jsonObjectArray.push_back(objectValue);
+ }
+
+ // return array and success
+ *pValue = jsonObjectArray;
+ return Success();
+}
+
+} // anonymous namespace
+
+Error jsonValueFromScalar(SEXP scalarSEXP, core::json::Value* pValue)
+{
+ // verify length
+ if (sexp::length(scalarSEXP) != 1)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ // get element
+ return jsonValueFromVectorElement(scalarSEXP, 0, pValue);
+}
+
+
+Error jsonValueFromVector(SEXP vectorSEXP, core::json::Value* pValue)
+{
+ int vectorLength = Rf_length(vectorSEXP);
+
+ if (Rf_inherits(vectorSEXP, "rs.scalar"))
+ {
+ if (vectorLength > 0)
+ return jsonValueFromVectorElement(vectorSEXP, 0, pValue);
+ else
+ {
+ // return null
+ *pValue = core::json::Value();
+ return Success();
+ }
+ }
+
+ core::json::Array vectorValues ;
+ for (int i=0; i<vectorLength; i++)
+ {
+ core::json::Value elementValue ;
+ Error error = jsonValueFromVectorElement(vectorSEXP, i, &elementValue);
+ if (error)
+ return error;
+
+ vectorValues.push_back(elementValue);
+ }
+
+ *pValue = vectorValues;
+ return Success();
+}
+
+
+Error jsonValueFromList(SEXP listSEXP, core::json::Value* pValue)
+{
+ if (isNamedList(listSEXP))
+ {
+ if (Rf_inherits(listSEXP, "data.frame"))
+ return jsonObjectArrayFromDataFrame(listSEXP, pValue);
+ else
+ return jsonObjectFromList(listSEXP, pValue);
+ }
+ else
+ {
+ return jsonValueArrayFromList(listSEXP, pValue);
+ }
+}
+
+
+Error jsonValueFromObject(SEXP objectSEXP, core::json::Value* pValue)
+{
+ // NOTE: a few additional types/scenarios we could support are:
+ // - special handling for array
+ // - special handling for matrix
+ // - special handling for S3 and S4 style objects
+ // - support for LISTSXP? (old style lists)
+ // - isVectorAtomic should replace default: label
+
+ switch(TYPEOF(objectSEXP))
+ {
+ case NILSXP:
+ {
+ *pValue = core::json::Value();
+ return Success();
+ }
+ case VECSXP:
+ {
+ return jsonValueFromList(objectSEXP, pValue);
+ }
+ case SYMSXP:
+ case LANGSXP:
+ {
+ *pValue = sexp::asString(objectSEXP);
+ return Success();
+ }
+ default:
+ {
+ return jsonValueFromVector(objectSEXP, pValue);
+ }
+ }
+}
+
+} // namespace json
+} // namesapce r
+
diff --git a/src/cpp/r/RJsonRpc.cpp b/src/cpp/r/RJsonRpc.cpp
new file mode 100644
index 0000000..6405e1c
--- /dev/null
+++ b/src/cpp/r/RJsonRpc.cpp
@@ -0,0 +1,155 @@
+/*
+ * RJsonRpc.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/* Support for implementing json-rpc methods directly in R:
+
+- Inbound json parameters are converted to R objects as follows:
+
+ 1) Both params and kwparams are supported (params are pushed onto the
+ call stack first followed by kwparams)
+
+ 2) Json null values are converted to R_NilValue
+
+ 3) Json values of primitive types are converted to scalar vectors:
+
+ string - Scalar String (STRSXP)
+ integer - Scalar Integer (INTSXP)
+ number - Scalar Real (REALSXP)
+ true/false - Scalar Logical (LGLSXP)
+
+ 4) Json arrays are converted to lists (VECSXP)
+
+ 5) Json objects are converted to lists (VECSXP) with field names
+ assigned to list elements
+
+- Outbound R objects are converted to json via rules described in RJson.cpp
+
+- Json-rpc functions can signal errors by calling the stop function. note
+ that these and any other errors which occur during json rpc calls are
+ both reported as errors to the rpc caller as well as printed to the
+ console (this is because we use r::engine::evaluateExpressions for calling
+ the method and have yet to figure out how to supress it from printing
+ messages to the console).
+*/
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RJsonRpc.hpp>
+
+#include <boost/bind.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RSourceManager.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RJson.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace json {
+
+namespace {
+
+Error setJsonResult(SEXP resultSEXP, core::json::JsonRpcResponse* pResponse)
+{
+ // get the result
+ core::json::Value resultValue ;
+ Error error = jsonValueFromObject(resultSEXP, &resultValue);
+ if (error)
+ return error ;
+
+ // set the result and return success
+ pResponse->setResult(resultValue);
+ return Success();
+}
+
+Error callRHandler(const std::string& functionName,
+ const core::json::JsonRpcRequest& request,
+ SEXP* pResult,
+ sexp::Protect* pProtect)
+{
+ // intialize the function
+ r::exec::RFunction rFunction(functionName);
+
+ // add params
+ const core::json::Array& params = request.params;
+ for (core::json::Array::size_type i=0; i<params.size(); i++)
+ rFunction.addParam(params[i]);
+
+ // add kwparams
+ const core::json::Object& kwparams = request.kwparams;
+ for (core::json::Object::const_iterator
+ it = kwparams.begin();
+ it != kwparams.end();
+ ++it)
+ {
+ rFunction.addParam(it->first, it->second);
+ }
+
+ // call the function
+ return rFunction.call(pResult, pProtect);
+}
+
+Error handleRequest(const std::string& rFunctionName,
+ const core::json::JsonRpcRequest& request,
+ core::json::JsonRpcResponse* pResponse)
+{
+ // call the function
+ sexp::Protect rProtect;
+ SEXP resultSEXP;
+ Error error = callRHandler(rFunctionName, request, &resultSEXP, &rProtect);
+ if (error)
+ return error;
+
+ // convert from R SEXP to json result
+ return setJsonResult(resultSEXP, pResponse);
+}
+
+} // anonymous namespace
+
+
+Error getRpcMethods(core::json::JsonRpcMethods* pMethods)
+{
+ // find all of the rpc handlers
+ std::vector<std::string> rpcHandlers;
+ Error error = r::exec::RFunction(".rs.listJsonRpcHandlers").call(
+ &rpcHandlers);
+ if (error)
+ return error;
+
+ // populate function map
+ pMethods->clear();
+ std::string rpcPrefix(".rs.rpc.");
+ for (std::vector<std::string>::const_iterator
+ it = rpcHandlers.begin();
+ it != rpcHandlers.end();
+ ++it)
+ {
+ std::string methodName = it->substr(rpcPrefix.size());
+ pMethods->insert(std::make_pair(methodName,
+ boost::bind(handleRequest,
+ *it,
+ _1,
+ _2)));
+ }
+
+ return Success();
+}
+
+} // namespace json
+} // namesapce r
+
diff --git a/src/cpp/r/ROptions.cpp b/src/cpp/r/ROptions.cpp
new file mode 100644
index 0000000..23173ea
--- /dev/null
+++ b/src/cpp/r/ROptions.cpp
@@ -0,0 +1,87 @@
+/*
+ * ROptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/ROptions.hpp>
+
+#include <boost/format.hpp>
+
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+
+#include <r/RExec.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace options {
+
+Error saveOptions(const FilePath& filePath)
+{
+ return exec::RFunction(".rs.saveOptions", filePath.absolutePath()).call();
+}
+
+Error restoreOptions(const FilePath& filePath)
+{
+ return exec::RFunction(".rs.restoreOptions", filePath.absolutePath()).call();
+}
+
+const int kDefaultWidth = 80;
+
+void setOptionWidth(int width)
+{
+ boost::format fmt("options(width=%1%)");
+ Error error = r::exec::executeString(boost::str(fmt % width));
+ if (error)
+ LOG_ERROR(error);
+}
+
+int getOptionWidth()
+{
+ return getOption<int>("width", kDefaultWidth);
+}
+
+SEXP getOption(const std::string& name)
+{
+ return Rf_GetOption(Rf_install(name.c_str()), R_BaseEnv);
+}
+
+SEXP setErrorOption(SEXP value)
+{
+ SEXP errorTag = Rf_install("error");
+ SEXP option = SYMVALUE(Rf_install(".Options"));
+ while (option != R_NilValue)
+ {
+ // is this the error option?
+ if (TAG(option) == errorTag)
+ {
+ // set and return previous value
+ SEXP previous = CAR(option);
+ SETCAR(option, value);
+ return previous;
+ }
+
+ // next option
+ option = CDR(option);
+ }
+
+ return R_NilValue;
+}
+
+} // namespace options
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RRoutines.cpp b/src/cpp/r/RRoutines.cpp
new file mode 100644
index 0000000..2f483bf
--- /dev/null
+++ b/src/cpp/r/RRoutines.cpp
@@ -0,0 +1,75 @@
+/*
+ * RRoutines.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RRoutines.hpp>
+
+#include <algorithm>
+
+namespace r {
+namespace routines {
+
+namespace {
+ std::vector<R_CMethodDef> s_cMethods;
+ std::vector<R_CallMethodDef> s_callMethods;
+}
+
+void addCMethod(const R_CMethodDef method)
+{
+ s_cMethods.push_back(method);
+}
+
+void addCallMethod(const R_CallMethodDef method)
+{
+ s_callMethods.push_back(method);
+}
+
+void registerAll()
+{
+ // c methods
+ R_CMethodDef* pCMethods = NULL;
+ if (s_cMethods.size() > 0)
+ {
+ R_CMethodDef nullMethodDef ;
+ nullMethodDef.name = NULL ;
+ nullMethodDef.fun = NULL ;
+ nullMethodDef.numArgs = 0 ;
+ nullMethodDef.types = NULL;
+ nullMethodDef.styles = NULL;
+ s_cMethods.push_back(nullMethodDef);
+ pCMethods = &s_cMethods[0];
+ }
+
+ // call methods
+ R_CallMethodDef* pCallMethods = NULL;
+ if (s_callMethods.size() > 0)
+ {
+ R_CallMethodDef nullMethodDef ;
+ nullMethodDef.name = NULL ;
+ nullMethodDef.fun = NULL ;
+ nullMethodDef.numArgs = 0 ;
+ s_callMethods.push_back(nullMethodDef);
+ pCallMethods = &s_callMethods[0];
+ }
+
+ DllInfo *info = R_getEmbeddingDllInfo() ;
+ R_registerRoutines(info, pCMethods, pCallMethods, NULL, NULL) ;
+}
+
+
+} // namespace routines
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RSexp.cpp b/src/cpp/r/RSexp.cpp
new file mode 100644
index 0000000..deaf585
--- /dev/null
+++ b/src/cpp/r/RSexp.cpp
@@ -0,0 +1,649 @@
+/*
+ * RSexp.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RSexp.hpp>
+#include <r/RInternal.hpp>
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <core/Log.hpp>
+#include <core/DateTime.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RErrorCategory.hpp>
+
+// clean out global definitions of TRUE and FALSE so we can
+// use the Rboolean variations of them
+#undef TRUE
+#undef FALSE
+
+using namespace core ;
+
+namespace r {
+
+using namespace exec ;
+
+namespace sexp {
+
+std::string asString(SEXP object)
+{
+ return std::string(Rf_translateChar(Rf_asChar(object)));
+}
+
+std::string safeAsString(SEXP object, const std::string& defValue)
+{
+ if (object != R_NilValue)
+ return asString(object);
+ else
+ return defValue;
+}
+
+int asInteger(SEXP object)
+{
+ return Rf_asInteger(object);
+}
+
+double asReal(SEXP object)
+{
+ return Rf_asReal(object);
+}
+
+bool asLogical(SEXP object)
+{
+ return Rf_asLogical(object) ? true : false;
+}
+
+SEXP findNamespace(const std::string& name)
+{
+ if (name.empty())
+ return R_UnboundValue;
+
+ return R_FindNamespace(Rf_mkString(name.c_str()));
+}
+
+void listEnvironment(SEXP env,
+ bool includeAll,
+ Protect* pProtect,
+ std::vector<Variable>* pVariables)
+{
+ // reset passed vars
+ pVariables->clear();
+
+ // get the list of environment vars (protect locally because we
+ // we don't acutally return this list to the caller
+ SEXP envVarsSEXP;
+ Protect rProtect(envVarsSEXP = R_lsInternal(env, includeAll ? TRUE : FALSE));
+
+ // get variables
+ std::vector<std::string> vars;
+ Error error = r::sexp::extract(envVarsSEXP, &vars);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // populate pVariables
+ BOOST_FOREACH(const std::string& var, vars)
+ {
+ SEXP varSEXP = R_NilValue;
+ // Merely calling Rf_findVar on an active binding will fire the binding.
+ // Don't try to get the SEXP for the variable in this case; leave the
+ // value as nil.
+ if (!isActiveBinding(var, env))
+ varSEXP = Rf_findVar(Rf_install(var.c_str()), env);
+
+ if (varSEXP != R_UnboundValue) // should never be unbound
+ {
+ pProtect->add(varSEXP);
+ pVariables->push_back(std::make_pair(var, varSEXP));
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE(
+ "Unexpected R_UnboundValue returned from R_lsInternal");
+ }
+ }
+}
+
+bool isActiveBinding(const std::string& name, const SEXP env)
+{
+ return R_BindingIsActive(Rf_install(name.c_str()), env);
+}
+
+SEXP findVar(const std::string &name, const SEXP env)
+{
+ return Rf_findVar(Rf_install(name.c_str()), env);
+}
+
+SEXP findVar(const std::string& name, const std::string& ns)
+{
+ if (name.empty())
+ return R_UnboundValue;
+
+ SEXP env = ns.empty() ? R_GlobalEnv : findNamespace(ns);
+
+ return findVar(name, env);
+}
+
+SEXP findFunction(const std::string& name, const std::string& ns)
+{
+ if (name.empty())
+ return R_UnboundValue;
+
+ SEXP env = ns.empty() ? R_GlobalEnv : findNamespace(ns);
+
+ SEXP functionSEXP;
+ Error error = executeSafely<SEXP>(
+ boost::bind(Rf_findFun, Rf_install(name.c_str()), env),
+ &functionSEXP
+ );
+
+ if (error)
+ return R_UnboundValue;
+ else
+ return functionSEXP ;
+}
+
+std::string typeAsString(SEXP object)
+{
+ return Rf_type2char(TYPEOF(object));
+}
+
+std::string classOf(SEXP objectSEXP)
+{
+ return asString(Rf_getAttrib(objectSEXP, Rf_install("class")));
+}
+
+int length(SEXP object)
+{
+ return Rf_length(object);
+}
+
+
+bool isLanguage(SEXP object)
+{
+ return Rf_isLanguage(object);
+}
+
+bool isString(SEXP object)
+{
+ return Rf_isString(object);
+}
+
+bool isMatrix(SEXP object)
+{
+ return Rf_isMatrix(object);
+}
+
+bool isDataFrame(SEXP object)
+{
+ return Rf_isFrame(object);
+}
+
+bool isNull(SEXP object)
+{
+ return Rf_isNull(object) == TRUE;
+}
+
+
+SEXP getNames(SEXP sexp)
+{
+ return Rf_getAttrib(sexp, R_NamesSymbol);
+}
+
+Error getNames(SEXP sexp, std::vector<std::string>* pNames)
+{
+ // attempt to get the field names
+ SEXP namesSEXP = getNames(sexp);
+
+ if (namesSEXP == R_NilValue || TYPEOF(namesSEXP) != STRSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+ else if (Rf_length(namesSEXP) != Rf_length(sexp))
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ // copy them into the vector
+ for (int i=0; i<Rf_length(namesSEXP); i++)
+ pNames->push_back(Rf_translateChar(STRING_ELT(namesSEXP, i)) );
+
+ return Success();
+}
+
+SEXP getAttrib(SEXP object, SEXP attrib)
+{
+ return Rf_getAttrib(object, attrib);
+}
+
+SEXP getAttrib(SEXP object, const std::string& attrib)
+{
+ return getAttrib(object, Rf_install(attrib.c_str()));
+}
+
+SEXP setAttrib(SEXP object, const std::string& attrib, SEXP val)
+{
+ return Rf_setAttrib(object, Rf_install(attrib.c_str()), val);
+}
+
+SEXP makeWeakRef(SEXP key, SEXP val, R_CFinalizer_t fun, Rboolean onexit)
+{
+ return R_MakeWeakRefC(key, val, fun, onexit);
+}
+
+void registerFinalizer(SEXP s, R_CFinalizer_t fun)
+{
+ R_RegisterCFinalizer(s, fun);
+}
+
+SEXP makeExternalPtr(void* ptr, R_CFinalizer_t fun, Protect* pProtect)
+{
+ SEXP s = R_MakeExternalPtr(ptr, R_NilValue, R_NilValue);
+ if (pProtect)
+ pProtect->add(s);
+ registerFinalizer(s, fun);
+ return s;
+}
+
+void* getExternalPtrAddr(SEXP extptr)
+{
+ return R_ExternalPtrAddr(extptr);
+}
+
+void clearExternalPtr(SEXP extptr)
+{
+ R_ClearExternalPtr(extptr);
+}
+
+Error extract(SEXP valueSEXP, int* pInt)
+{
+ if (TYPEOF(valueSEXP) != INTSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ if (Rf_length(valueSEXP) < 1)
+ return Error(errc::NoDataAvailableError, ERROR_LOCATION);
+
+ *pInt = INTEGER(valueSEXP)[0] ;
+ return Success();
+}
+
+Error extract(SEXP valueSEXP, bool* pBool)
+{
+ if (TYPEOF(valueSEXP) != LGLSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ if (Rf_length(valueSEXP) < 1)
+ return Error(errc::NoDataAvailableError, ERROR_LOCATION);
+
+ *pBool = LOGICAL(valueSEXP)[0] == TRUE ? true : false ;
+ return Success();
+
+}
+
+Error extract(SEXP valueSEXP, double* pDouble)
+{
+ if (TYPEOF(valueSEXP) != REALSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ if (Rf_length(valueSEXP) < 1)
+ return Error(errc::NoDataAvailableError, ERROR_LOCATION);
+
+ *pDouble = REAL(valueSEXP)[0];
+ return Success();
+}
+
+Error extract(SEXP valueSEXP, std::vector<int>* pVector)
+{
+ if (TYPEOF(valueSEXP) != INTSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ pVector->clear();
+ for (int i=0; i<Rf_length(valueSEXP); i++)
+ pVector->push_back(INTEGER(valueSEXP)[i]);
+
+ return Success();
+}
+
+
+Error extract(SEXP valueSEXP, std::string* pString)
+{
+ if (TYPEOF(valueSEXP) != STRSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ if (Rf_length(valueSEXP) < 1)
+ return Error(errc::NoDataAvailableError, ERROR_LOCATION);
+
+ *pString = std::string(Rf_translateChar(STRING_ELT(valueSEXP, 0)));
+
+ return Success();
+}
+
+
+Error extract(SEXP valueSEXP, std::vector<std::string>* pVector)
+{
+ if (TYPEOF(valueSEXP) != STRSXP)
+ return Error(errc::UnexpectedDataTypeError, ERROR_LOCATION);
+
+ pVector->clear();
+ for (int i=0; i<Rf_length(valueSEXP); i++)
+ pVector->push_back(Rf_translateChar(STRING_ELT(valueSEXP, i)));
+
+ return Success();
+}
+
+
+SEXP create(const json::Value& value, Protect* pProtect)
+{
+ // call embedded create function based on type
+ if (value.type() == json::StringType)
+ {
+ return create(value.get_str(), pProtect);
+ }
+ else if (value.type() == json::IntegerType)
+ {
+ return create(value.get_int(), pProtect);
+ }
+ else if (value.type() == json::RealType)
+ {
+ return create(value.get_real(), pProtect);
+ }
+ else if (value.type() == json::BooleanType)
+ {
+ return create(value.get_bool(), pProtect);
+ }
+ else if (value.type() == json::ArrayType)
+ {
+ return create(value.get_array(), pProtect);
+ }
+ else if (value.type() == json::ObjectType)
+ {
+ return create(value.get_obj(), pProtect);
+ }
+ else if (value.is_null())
+ {
+ return R_NilValue;
+ }
+ else
+ {
+ return R_NilValue;
+ }
+}
+
+SEXP create(const char* value, Protect* pProtect)
+{
+ return create(std::string(value), pProtect);
+}
+
+SEXP create(const std::string& value, Protect* pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(STRSXP, 1));
+ SET_STRING_ELT(valueSEXP, 0, Rf_mkChar(value.c_str()));
+ return valueSEXP;
+}
+
+SEXP create(int value, Protect* pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(INTSXP, 1));
+ INTEGER(valueSEXP)[0] = value ;
+ return valueSEXP;
+}
+
+SEXP create(double value, Protect* pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(REALSXP, 1));
+ REAL(valueSEXP)[0] = value ;
+ return valueSEXP;
+}
+
+SEXP create(bool value, Protect* pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(LGLSXP, 1));
+ LOGICAL(valueSEXP)[0] = value ;
+ return valueSEXP;
+}
+
+SEXP create(const json::Array& value, Protect* pProtect)
+{
+ // create the list
+ SEXP listSEXP;
+ pProtect->add(listSEXP = Rf_allocVector(VECSXP, value.size()));
+
+ // add each array element to it
+ for (json::Array::size_type i=0; i<value.size(); i++)
+ {
+ SEXP valueSEXP = create(value[i], pProtect);
+ SET_VECTOR_ELT(listSEXP, i, valueSEXP);
+ }
+ return listSEXP;
+}
+
+SEXP create(const json::Object& value, Protect* pProtect)
+{
+ // create the list
+ SEXP listSEXP ;
+ pProtect->add(listSEXP = Rf_allocVector(VECSXP, value.size()));
+
+ // build list of names
+ SEXP namesSEXP ;
+ pProtect->add(namesSEXP = Rf_allocVector(STRSXP, value.size()));
+
+ // add each object field to it
+ int index = 0;
+ for (json::Object::const_iterator
+ it = value.begin();
+ it != value.end();
+ ++it)
+ {
+ // set name
+ SET_STRING_ELT(namesSEXP, index, Rf_mkChar(it->first.c_str()));
+
+ // set value
+ SEXP valueSEXP = create(it->second, pProtect);
+ SET_VECTOR_ELT(listSEXP, index, valueSEXP);
+
+ // increment element index
+ index++;
+ }
+
+ // attach names
+ Rf_setAttrib(listSEXP, R_NamesSymbol, namesSEXP);
+
+ // return the list
+ return listSEXP;
+}
+
+SEXP create(const std::vector<std::string>& value, Protect* pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(STRSXP, value.size()));
+
+ int index = 0;
+ for (std::vector<std::string>::const_iterator
+ it = value.begin(); it != value.end(); ++it)
+ {
+ SET_STRING_ELT(valueSEXP, index++, Rf_mkChar(it->c_str()));
+ }
+
+ return valueSEXP;
+}
+
+SEXP create(const std::vector<int>& value, Protect *pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(INTSXP, value.size()));
+
+ for (std::size_t i = 0; i < value.size(); ++i)
+ INTEGER(valueSEXP)[i] = value[i] ;
+
+ return valueSEXP;
+}
+
+SEXP create(const std::vector<double>& value, Protect *pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(REALSXP, value.size()));
+
+ for (std::size_t i = 0; i < value.size(); ++i)
+ REAL(valueSEXP)[i] = value[i] ;
+
+ return valueSEXP;
+}
+
+SEXP create(const std::vector<bool>& value, Protect *pProtect)
+{
+ SEXP valueSEXP;
+ pProtect->add(valueSEXP = Rf_allocVector(LGLSXP, value.size()));
+
+ for (std::size_t i = 0; i < value.size(); ++i)
+ LOGICAL(valueSEXP)[i] = value[i] ;
+
+ return valueSEXP;
+}
+
+namespace {
+int secondsSinceEpoch(boost::posix_time::ptime date)
+{
+ return boost::numeric_cast<int>(date_time::secondsSinceEpoch(date));
+}}
+
+SEXP create(const std::vector<boost::posix_time::ptime>& value,
+ Protect* pProtect)
+{
+ // first create a vector of doubles containing seconds since epoch
+ std::vector<int> seconds ;
+ std::transform(value.begin(),
+ value.end(),
+ std::back_inserter(seconds),
+ secondsSinceEpoch);
+
+ // now turn this into an R vector and call as.POSIXct
+ SEXP secondsSEXP = create(seconds, pProtect);
+ SEXP posixCtSEXP = R_NilValue;
+ r::exec::RFunction asPOSIXct("as.POSIXct", secondsSEXP);
+ asPOSIXct.addParam("tz", "GMT");
+ asPOSIXct.addParam("origin", "1970-01-01");
+ Error error = asPOSIXct.call(&posixCtSEXP, pProtect);
+ if (error)
+ LOG_ERROR(error);
+
+ // return it
+ return posixCtSEXP;
+}
+
+SEXP create(const std::vector<std::pair<std::string,std::string> >& value,
+ Protect* pProtect)
+{
+ // create the character vector and the names vector
+ SEXP charSEXP, namesSEXP;
+ pProtect->add(charSEXP = Rf_allocVector(STRSXP, value.size()));
+ pProtect->add(namesSEXP = Rf_allocVector(STRSXP, value.size()));
+
+ int index = 0;
+ for (std::vector<std::pair<std::string,std::string> >::const_iterator
+ it = value.begin(); it != value.end(); ++it)
+ {
+ // set name and value
+ SET_STRING_ELT(namesSEXP, index, Rf_mkChar(it->first.c_str()));
+ SET_STRING_ELT(charSEXP, index, Rf_mkChar(it->second.c_str()));
+
+ // increment element index
+ index++;
+ }
+
+ // attach names
+ Rf_setAttrib(charSEXP, R_NamesSymbol, namesSEXP);
+
+ // return the vector
+ return charSEXP;
+}
+
+Protect::~Protect()
+{
+ try
+ {
+ unprotectAll();
+ }
+ catch(...)
+ {
+ }
+}
+
+void Protect::add(SEXP sexp)
+{
+ PROTECT(sexp);
+ protectCount_++;
+}
+
+void Protect::unprotectAll()
+{
+ if (protectCount_ > 0)
+ UNPROTECT(protectCount_);
+ protectCount_ = 0;
+}
+
+
+PreservedSEXP::PreservedSEXP()
+ : sexp_(R_NilValue)
+{
+}
+
+PreservedSEXP::PreservedSEXP(SEXP sexp)
+ : sexp_(R_NilValue)
+{
+ set(sexp);
+}
+
+void PreservedSEXP::set(SEXP sexp)
+{
+ releaseNow();
+ sexp_ = sexp ;
+ if (sexp_ != R_NilValue)
+ ::R_PreserveObject(sexp_);
+}
+
+PreservedSEXP::~PreservedSEXP()
+{
+ try
+ {
+ releaseNow();
+ }
+ catch(...)
+ {
+ }
+}
+
+void PreservedSEXP::releaseNow()
+{
+ if (sexp_ != R_NilValue)
+ {
+ ::R_ReleaseObject(sexp_);
+ sexp_ = R_NilValue;
+ }
+}
+
+
+} // namespace sexp
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RSourceManager.cpp b/src/cpp/r/RSourceManager.cpp
new file mode 100644
index 0000000..53e8fbe
--- /dev/null
+++ b/src/cpp/r/RSourceManager.cpp
@@ -0,0 +1,154 @@
+/*
+ * RSourceManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RSourceManager.hpp>
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Log.hpp>
+
+#include <r/RExec.hpp>
+
+using namespace core ;
+
+namespace r {
+
+
+SourceManager& sourceManager()
+{
+ static SourceManager instance ;
+ return instance ;
+}
+
+Error SourceManager::sourceTools(const core::FilePath& filePath)
+{
+ Error error = sourceLocal(filePath);
+ if (error)
+ return error;
+
+ toolsFilePaths_.push_back(filePath);
+
+ return Success();
+}
+
+
+Error SourceManager::sourceLocal(const FilePath& filePath)
+{
+ return source(filePath, true);
+}
+
+void SourceManager::ensureToolsLoaded()
+{
+ // check whether tools:rstudio is still in the search patch
+ bool toolsRStudioLoaded = true;
+ Error error = r::exec::evaluateString("\"tools:rstudio\" %in% search()",
+ &toolsRStudioLoaded);
+ if (error)
+ LOG_ERROR(error);
+
+ // if not then source all of the tools files back in
+ if (!toolsRStudioLoaded)
+ {
+ std::for_each(toolsFilePaths_.begin(),
+ toolsFilePaths_.end(),
+ boost::bind(&SourceManager::reSourceTools, this, _1));
+ }
+}
+
+void SourceManager::reloadIfNecessary()
+{
+ if (autoReload_)
+ {
+ std::for_each(sourcedFiles_.begin(),
+ sourcedFiles_.end(),
+ boost::bind(&SourceManager::reloadSourceIfNecessary,
+ this,
+ _1));
+ }
+}
+
+
+void SourceManager::reSourceTools(const core::FilePath& filePath)
+{
+ Error error = source(filePath, true);
+ if (error)
+ LOG_ERROR(error);
+}
+
+Error SourceManager::source(const FilePath& filePath, bool local)
+{
+ std::string localPrefix = local ? "local(" : "";
+ std::string localParam = local ? "TRUE" : "FALSE" ;
+ std::string localSuffix = local ? ")" : "";
+
+ // do \ escaping (for windows)
+ std::string path = filePath.absolutePath();
+ boost::algorithm::replace_all(path, "\\", "\\\\");
+
+ // Build the code. If this build is targeted for debugging, keep the source
+ // code around; otherwise, turn it off to conserve memory and expose fewer
+ // internals to the user.
+ std::string rCode = localPrefix + "source(\""
+ + path + "\", " +
+ "local=" + localParam + ", " +
+ "echo=FALSE, " +
+ "verbose=FALSE, " +
+#ifdef NDEBUG
+ "keep.source=FALSE, " +
+#else
+ "keep.source=TRUE, " +
+#endif
+ "encoding='UTF-8')" + localSuffix;
+
+ // record that we sourced the file.
+ recordSourcedFile(filePath, local);
+
+ // source the file
+ return r::exec::executeString(rCode);
+}
+
+void SourceManager::recordSourcedFile(const FilePath& filePath, bool local)
+{
+ SourcedFileInfo fileInfo(filePath.lastWriteTime(), local);
+ sourcedFiles_[filePath.absolutePath()] = fileInfo ;
+}
+
+void SourceManager::reloadSourceIfNecessary(
+ const SourcedFileMap::value_type& value)
+{
+ // extract values
+ FilePath sourcedFilePath(value.first);
+ SourcedFileInfo fileInfo = value.second;
+
+ // compare last write times and source again if necessary
+ double diffTime = std::difftime(sourcedFilePath.lastWriteTime(),
+ fileInfo.lastWriteTime);
+ if (diffTime > 0)
+ {
+ Error error = source(sourcedFilePath, fileInfo.local);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+} // namespace r
+
+
+
diff --git a/src/cpp/r/RUtil.cpp b/src/cpp/r/RUtil.cpp
new file mode 100644
index 0000000..7737521
--- /dev/null
+++ b/src/cpp/r/RUtil.cpp
@@ -0,0 +1,165 @@
+/*
+ * RUtil.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <r/RUtil.hpp>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/regex.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+#include <core/Error.hpp>
+
+#include <r/RExec.hpp>
+
+#include <R_ext/Riconv.h>
+
+using namespace core;
+
+namespace r {
+namespace util {
+
+std::string expandFileName(const std::string& name)
+{
+ return std::string(R_ExpandFileName(name.c_str()));
+}
+
+std::string fixPath(const std::string& path)
+{
+ // R sometimes gives us a path a double slashes in it ("//"). Eliminate them.
+ std::string fixedPath(path);
+ boost::algorithm::replace_all(fixedPath, "//", "/");
+ return fixedPath;
+}
+
+bool hasRequiredVersion(const std::string& version)
+{
+ std::string versionTest("getRversion() >= \"" + version + "\"");
+ bool hasRequired = false;
+ Error error = r::exec::evaluateString(versionTest, &hasRequired);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ else
+ {
+ return hasRequired;
+ }
+}
+
+bool hasCapability(const std::string& capability)
+{
+ bool hasCap = false;
+ Error error = r::exec::RFunction("capabilities", capability).call(&hasCap);
+ if (error)
+ LOG_ERROR(error);
+ return hasCap;
+}
+
+std::string rconsole2utf8(const std::string& encoded)
+{
+ boost::regex utf8("\x02\xFF\xFE(.*?)(\x03\xFF\xFE|\\')");
+
+ std::string output;
+ std::string::const_iterator pos = encoded.begin();
+ boost::smatch m;
+ while (pos != encoded.end() && boost::regex_search(pos, encoded.end(), m, utf8))
+ {
+ if (pos < m[0].first)
+ output.append(string_utils::systemToUtf8(std::string(pos, m[0].first)));
+ output.append(m[1].first, m[1].second);
+ pos = m[0].second;
+ }
+ if (pos != encoded.end())
+ output.append(string_utils::systemToUtf8(std::string(pos, encoded.end())));
+
+ return output;
+}
+
+core::Error iconvstr(const std::string& value,
+ const std::string& from,
+ const std::string& to,
+ bool allowSubstitution,
+ std::string* pResult)
+{
+ std::string effectiveFrom = from;
+ if (effectiveFrom.empty())
+ effectiveFrom = "UTF-8";
+ std::string effectiveTo = to;
+ if (effectiveTo.empty())
+ effectiveTo = "UTF-8";
+
+ if (effectiveFrom == effectiveTo)
+ {
+ *pResult = value;
+ return Success();
+ }
+
+ std::vector<char> output;
+ output.reserve(value.length());
+
+ void* handle = ::Riconv_open(to.c_str(), from.c_str());
+ if (handle == (void*)(-1))
+ return systemError(errno, ERROR_LOCATION);
+
+ const char* pIn = value.data();
+ size_t inBytes = value.size();
+
+ char buffer[256];
+ while (inBytes > 0)
+ {
+ const char* pInOrig = pIn;
+ char* pOut = buffer;
+ size_t outBytes = sizeof(buffer);
+
+ size_t result = ::Riconv(handle, &pIn, &inBytes, &pOut, &outBytes);
+ if (buffer != pOut)
+ output.insert(output.end(), buffer, pOut);
+
+ if (result == (size_t)(-1))
+ {
+ if ((errno == EILSEQ || errno == EINVAL) && allowSubstitution)
+ {
+ output.push_back('?');
+ pIn++;
+ inBytes--;
+ }
+ else if (errno == E2BIG && pInOrig != pIn)
+ {
+ continue;
+ }
+ else
+ {
+ ::Riconv_close(handle);
+ Error error = systemError(errno, ERROR_LOCATION);
+ error.addProperty("str", value);
+ error.addProperty("len", value.length());
+ return error;
+ }
+ }
+ }
+ ::Riconv_close(handle);
+
+ *pResult = std::string(output.begin(), output.end());
+ return Success();
+}
+
+} // namespace util
+} // namespace r
+
+
+
diff --git a/src/cpp/r/VERSION b/src/cpp/r/VERSION
new file mode 100644
index 0000000..22648cb
--- /dev/null
+++ b/src/cpp/r/VERSION
@@ -0,0 +1 @@
+${CPACK_PACKAGE_VERSION}
diff --git a/src/cpp/r/config.h.in b/src/cpp/r/config.h.in
new file mode 100644
index 0000000..6ba67c8
--- /dev/null
+++ b/src/cpp/r/config.h.in
@@ -0,0 +1,19 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#cmakedefine PANGO_CAIRO_FOUND
+
+
diff --git a/src/cpp/r/include/r/RErrorCategory.hpp b/src/cpp/r/include/r/RErrorCategory.hpp
new file mode 100644
index 0000000..bd64499
--- /dev/null
+++ b/src/cpp/r/include/r/RErrorCategory.hpp
@@ -0,0 +1,85 @@
+/*
+ * RErrorCategory.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_ERROR_CATEGORY_HPP
+#define R_R_ERROR_CATEGORY_HPP
+
+#include <boost/system/error_code.hpp>
+
+namespace r {
+namespace errc {
+
+enum errc_t {
+ Success = 0,
+ RHomeNotFound,
+ UnsupportedLocale,
+ ExpressionParsingError,
+ CodeExecutionError,
+ SymbolNotFoundError,
+ ListElementNotFoundError,
+ UnexpectedDataTypeError,
+ NoDataAvailableError
+};
+
+} // namespace errc
+} // namespace r
+
+
+namespace boost {
+namespace system {
+template <>
+struct is_error_code_enum<r::errc::errc_t>
+ { static const bool value = true; };
+} // namespace system
+} // namespace boost
+
+
+
+#include <core/Error.hpp>
+
+namespace r {
+
+const boost::system::error_category& rCategory() ;
+
+namespace errc {
+
+inline boost::system::error_code make_error_code( errc_t e )
+{
+ return boost::system::error_code( e, rCategory() ); }
+
+inline boost::system::error_condition make_error_condition( errc_t e )
+{
+ return boost::system::error_condition( e, rCategory() );
+}
+
+} // namespace errc
+
+
+core::Error rCodeExecutionError(const std::string& errMsg,
+ const core::ErrorLocation& location);
+
+bool isCodeExecutionError(const core::Error& error,
+ std::string* pErrMsg = NULL);
+
+// use the error message generated by R for code execution errors,
+// otherwise use error.message()
+std::string endUserErrorMessage(const core::Error& error);
+
+
+} // namespace r
+
+
+#endif // R_R_ERROR_CATEGORY_HPP
+
diff --git a/src/cpp/r/include/r/RExec.hpp b/src/cpp/r/include/r/RExec.hpp
new file mode 100644
index 0000000..ecaa050
--- /dev/null
+++ b/src/cpp/r/include/r/RExec.hpp
@@ -0,0 +1,284 @@
+/*
+ * RExec.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_EXEC_HPP
+#define R_R_EXEC_HPP
+
+#include <string>
+#include <vector>
+#include <stdexcept>
+
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/function.hpp>
+
+#include <core/Error.hpp>
+#include <core/system/System.hpp>
+
+#include <r/RSexp.hpp>
+
+
+namespace core {
+ class FilePath;
+}
+
+// IMPORTANT NOTE: all code in r::exec must provide "no jump" guarantee.
+// See comment in RInternal.hpp for more info on this
+
+namespace r {
+namespace exec {
+
+// safe (no r error longjump) execution of abritrary nullary function
+core::Error executeSafely(boost::function<void()> function);
+
+// helper class for variation of executeSafely w/ return value (impl below)
+template <typename T>
+class ExecuteTargetWithReturn
+{
+public:
+ ExecuteTargetWithReturn(const boost::function<T()>& function, T* pReturn)
+ : function_(function), pReturn_(pReturn) {}
+
+ // COPYING: via compiler (copyable members)
+
+ void operator()() { *pReturn_ = function_(); }
+
+private:
+ boost::function<T()> function_ ;
+ T* pReturn_ ;
+};
+
+// safe (no r error longjump) execution of abritrary nullary function w/ return
+template <typename T>
+core::Error executeSafely(boost::function<T()> function, T* pReturn)
+{
+ ExecuteTargetWithReturn<T> target(function, pReturn);
+ return executeSafely(target);
+}
+
+
+// parse and evaluate expressions
+core::Error executeString(const std::string& str);
+core::Error evaluateString(const std::string& str,
+ SEXP* pSEXP,
+ sexp::Protect* pProtect);
+template <typename T>
+core::Error evaluateString(const std::string& str, T* pValue)
+{
+ sexp::Protect rProtect;
+ SEXP valueSEXP ;
+ core::Error error = evaluateString(str, &valueSEXP, &rProtect);
+ if (error)
+ return error ;
+
+ return sexp::extract(valueSEXP, pValue);
+}
+
+// call R functions
+class RFunction : boost::noncopyable
+{
+public:
+ explicit RFunction(const std::string& name)
+ : functionSEXP_(R_UnboundValue)
+ {
+ commonInit(name);
+ }
+
+ template <typename ParamType>
+ RFunction(const std::string& name, const ParamType& param)
+ : functionSEXP_(R_UnboundValue)
+ {
+ commonInit(name);
+ addParam(param);
+ }
+
+ template <typename Param1Type, typename Param2Type>
+ RFunction(const std::string& name,
+ const Param1Type& param1,
+ const Param2Type& param2)
+ : functionSEXP_(R_UnboundValue)
+ {
+ commonInit(name);
+ addParam(param1);
+ addParam(param2);
+ }
+
+ template <typename Param1Type, typename Param2Type, typename Param3Type>
+ RFunction(const std::string& name,
+ const Param1Type& param1,
+ const Param2Type& param2,
+ const Param3Type& param3)
+ : functionSEXP_(R_UnboundValue)
+ {
+ commonInit(name);
+ addParam(param1);
+ addParam(param2);
+ addParam(param3);
+ }
+
+ template <typename Param1Type, typename Param2Type,
+ typename Param3Type, typename Param4Type>
+ RFunction(const std::string& name,
+ const Param1Type& param1,
+ const Param2Type& param2,
+ const Param3Type& param3,
+ const Param4Type& param4)
+ : functionSEXP_(R_UnboundValue)
+ {
+ commonInit(name);
+ addParam(param1);
+ addParam(param2);
+ addParam(param3);
+ addParam(param4);
+ }
+
+ explicit RFunction(SEXP functionSEXP);
+
+ virtual ~RFunction() ;
+
+ // COPYING: boost::noncopyable
+
+ void addParam(SEXP param)
+ {
+ addParam(std::string(), param);
+ }
+
+ template <typename T>
+ void addParam(const T& param)
+ {
+ addParam(std::string(), param);
+ }
+
+ void addParam(const std::string& name, SEXP param)
+ {
+ params_.push_back(Param(name, param));
+ }
+
+ template <typename T>
+ void addParam(const std::string& name, const T& param)
+ {
+ SEXP paramSEXP = sexp::create(param, &rProtect_);
+ params_.push_back(Param(name, paramSEXP));
+ }
+
+ core::Error call(SEXP evalNS = R_GlobalEnv, bool safely = true);
+ core::Error callUnsafe();
+
+ core::Error call(SEXP* pResultSEXP, sexp::Protect* pProtect);
+ core::Error call(SEXP evalNS, SEXP* pResultSEXP, sexp::Protect* pProtect);
+ core::Error call(SEXP evalNS, bool safely, SEXP* pResultSEXP,
+ sexp::Protect* pProtect);
+
+ template <typename T>
+ core::Error call(T* pValue)
+ {
+ return call(R_GlobalEnv, pValue);
+ }
+
+ template <typename T>
+ core::Error call(SEXP evalNS, T* pValue)
+ {
+ // call the function
+ sexp::Protect rProtect;
+ SEXP resultSEXP ;
+ core::Error error = call(evalNS, &resultSEXP, &rProtect);
+ if (error)
+ return error ;
+
+ // convert result to c++ accessible type
+ return sexp::extract(resultSEXP, pValue) ;
+ }
+
+private:
+ void commonInit(const std::string& functionName);
+
+private:
+ // protect included SEXPs
+ sexp::Protect rProtect_ ;
+
+ // function
+ SEXP functionSEXP_;
+
+ // function name (optional)
+ std::string functionName_;
+
+ // params
+ struct Param
+ {
+ Param(const std::string& name, SEXP valueSEXP)
+ : name(name), valueSEXP(valueSEXP)
+ {
+ }
+ std::string name ;
+ SEXP valueSEXP ;
+ };
+ std::vector<Param> params_ ;
+};
+
+void warning(const std::string& warning);
+
+// special exception type used to raise R errors. allows for correct
+// exiting from c++ context (with destructors called, etc.) while still
+// propagating the error to a point where it will be re-raised to r
+// using Rf_error
+#define R_ERROR_BUFF_SIZE 256
+class RErrorException : public std::exception
+{
+public:
+ RErrorException(const std::string& message)
+ {
+ size_t length = message.copy(msgBuff_, R_ERROR_BUFF_SIZE);
+ msgBuff_[length] = '\0';
+ }
+
+ const char* message() const { return msgBuff_; }
+
+private:
+ char msgBuff_[R_ERROR_BUFF_SIZE+1];
+};
+
+
+core::FilePath rBinaryPath();
+
+core::Error system(const std::string& command, std::string* pOutput);
+
+// raise error and get last error message
+void error(const std::string& message);
+void errorCall(SEXP call, const std::string& message);
+std::string getErrorMessage();
+
+bool interruptsPending();
+void setInterruptsPending(bool pending);
+void checkUserInterrupt();
+
+
+class IgnoreInterruptsScope : boost::noncopyable
+{
+public:
+ IgnoreInterruptsScope();
+ virtual ~IgnoreInterruptsScope();
+private:
+ bool previousInterruptsSuspended_ ;
+ boost::scoped_ptr<core::system::SignalBlocker> pSignalBlocker_;
+};
+
+class InterruptException {};
+
+} // namespace exec
+} // namespace r
+
+
+#endif // R_R_EXEC_HPP
+
diff --git a/src/cpp/r/include/r/RFunctionHook.hpp b/src/cpp/r/include/r/RFunctionHook.hpp
new file mode 100644
index 0000000..3a439f3
--- /dev/null
+++ b/src/cpp/r/include/r/RFunctionHook.hpp
@@ -0,0 +1,41 @@
+/*
+ * RFunctionHook.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_FUNCTION_HOOK_HPP
+#define R_FUNCTION_HOOK_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace r {
+namespace function_hook {
+
+core::Error registerUnsupported(const std::string& name,
+ const std::string& package);
+
+
+core::Error registerUnsupportedWithAlternative(const std::string& name,
+ const std::string& package,
+ const std::string& alternative);
+
+} // namespace function_hook
+} // namespace r
+
+
+#endif // R_FUNCTION_HOOK_HPP
+
diff --git a/src/cpp/r/include/r/RInterface.hpp b/src/cpp/r/include/r/RInterface.hpp
new file mode 100644
index 0000000..dfb8b27
--- /dev/null
+++ b/src/cpp/r/include/r/RInterface.hpp
@@ -0,0 +1,112 @@
+/*
+ * RInterface.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_INTERFACE_HPP
+#define R_INTERFACE_HPP
+
+#include <string>
+#include <setjmp.h>
+
+#ifdef _WIN32
+
+#include <R_ext/Boolean.h>
+#include <R_ext/RStartup.h>
+
+extern "C" void R_RestoreGlobalEnvFromFile(const char *, Rboolean);
+extern "C" void R_SaveGlobalEnvToFile(const char *);
+extern "C" void R_Suicide(const char *);
+extern "C" char *R_HomeDir(void);
+extern "C" void Rf_jump_to_toplevel(void);
+extern "C" void Rf_onintr(void);
+#define R_ClearerrConsole void
+extern "C" void R_FlushConsole();
+extern "C" int R_SignalHandlers;
+extern "C" void run_Rmainloop();
+extern "C" void Rf_mainloop(void);
+extern "C" void* R_GlobalContext;
+
+typedef struct SEXPREC *SEXP;
+
+#else
+
+#define R_INTERFACE_PTRS 1
+#include <Rinterface.h>
+
+#endif
+
+#ifdef _WIN32
+// on Windows platforms, use a manual definition of sigjmp_buf that corresponds
+// to how R lays out the structure in memory
+
+typedef struct
+{
+ jmp_buf buf;
+ int sigmask;
+ int savedmask;
+}
+sigjmp_buf[1];
+#endif
+
+typedef struct RCNTXT {
+ struct RCNTXT *nextcontext;
+ int callflag;
+ sigjmp_buf cjmpbuf;
+ int cstacktop;
+ int evaldepth;
+ SEXP promargs;
+ SEXP callfun;
+ SEXP sysparent;
+ SEXP call;
+ SEXP cloenv;
+ SEXP conexit;
+ void (*cend)(void *);
+ void *cenddata;
+ void *vmax;
+ int intsusp;
+ SEXP handlerstack;
+ SEXP restartstack;
+ struct RPRSTACK *prstack;
+ SEXP *nodestack;
+#ifdef BC_INT_STACK
+ IStackval *intstack;
+#endif
+ SEXP srcref;
+} RCNTXT, *context;
+
+enum {
+ CTXT_TOPLEVEL = 0,
+ CTXT_NEXT = 1,
+ CTXT_BREAK = 2,
+ CTXT_LOOP = 3,
+ CTXT_FUNCTION = 4,
+ CTXT_CCODE = 8,
+ CTXT_RETURN = 12,
+ CTXT_BROWSER = 16,
+ CTXT_GENERIC = 20,
+ CTXT_RESTART = 32,
+ CTXT_BUILTIN = 64
+};
+
+namespace r {
+
+inline RCNTXT* getGlobalContext()
+{
+ return static_cast<RCNTXT*>(R_GlobalContext);
+}
+
+} // namespace r
+
+#endif // R_INTERFACE_HPP
+
diff --git a/src/cpp/r/include/r/RInternal.hpp b/src/cpp/r/include/r/RInternal.hpp
new file mode 100644
index 0000000..b10c7fa
--- /dev/null
+++ b/src/cpp/r/include/r/RInternal.hpp
@@ -0,0 +1,247 @@
+/*
+ * RInternal.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_INTERNAL_HPP
+#define R_R_INTERNAL_HPP
+
+// IMPORTANT NOTE: If R internal functions (functions prefixed with R_ or Rf_,
+// as distinct from simple SEXP accessor functions) are called from within a
+// c++ execution context they must either:
+//
+// (a) be inspected to make sure they won't ever call error (longjump); or
+//
+// (b) be called from within a safe block (see r::exec::executeSafely)
+//
+// This is critical to prevent error (longjump) from being called. If it is
+// called then execution will pop back to the top of the R stack (repl loop)
+// and c++ stack unwinding will not occur!
+//
+// To prevent inadvertent use of R_ and Rf_ internal functions by clients
+// they are only included if the R_INTERNAL_FUNCTIONS macro is defined.
+//
+// Note also that public functions and classes exposed from within the r
+// namespace have an implicit guarantee not to allow an error/longjump to
+// escape from them (they adhere to guideline a or b above). Therefore,
+// if client code from outside the r namespace feels the need to call R
+// internal functions a sounder approach might be to add a suitable function
+// to the r namespace which performs the intended operation safely.
+//
+
+#define R_NO_REMAP
+#include <Rinternals.h>
+
+#ifndef R_INTERNAL_FUNCTIONS
+
+// force compiler error if the client tries to call an R internal function
+#define Rf_asChar INTERNAL_R_FUNCTION
+#define Rf_coerceVector INTERNAL_R_FUNCTION
+#define Rf_PairToVectorList INTERNAL_R_FUNCTION
+#define Rf_VectorToPairList INTERNAL_R_FUNCTION
+#define Rf_asLogical INTERNAL_R_FUNCTION
+#define Rf_asInteger INTERNAL_R_FUNCTION
+#define Rf_asReal INTERNAL_R_FUNCTION
+#define Rf_asComplex INTERNAL_R_FUNCTION
+#define Rf_acopy_string INTERNAL_R_FUNCTION
+#define Rf_alloc3DArray INTERNAL_R_FUNCTION
+#define Rf_allocArray INTERNAL_R_FUNCTION
+#define Rf_allocMatrix INTERNAL_R_FUNCTION
+#define Rf_allocList INTERNAL_R_FUNCTION
+#define Rf_allocS4Object INTERNAL_R_FUNCTION
+#define Rf_allocSExp INTERNAL_R_FUNCTION
+#define Rf_allocVector INTERNAL_R_FUNCTION
+#define Rf_applyClosure INTERNAL_R_FUNCTION
+#define Rf_arraySubscript INTERNAL_R_FUNCTION
+#define Rf_asComplex INTERNAL_R_FUNCTION
+#define Rf_asInteger INTERNAL_R_FUNCTION
+#define Rf_asLogical INTERNAL_R_FUNCTION
+#define Rf_asReal INTERNAL_R_FUNCTION
+#define Rf_classgets INTERNAL_R_FUNCTION
+#define Rf_cons INTERNAL_R_FUNCTION
+#define Rf_copyMatrix INTERNAL_R_FUNCTION
+#define Rf_copyMostAttrib INTERNAL_R_FUNCTION
+#define Rf_copyVector INTERNAL_R_FUNCTION
+#define Rf_CreateTag INTERNAL_R_FUNCTION
+#define Rf_defineVar INTERNAL_R_FUNCTION
+#define Rf_dimgets INTERNAL_R_FUNCTION
+#define Rf_dimnamesgets INTERNAL_R_FUNCTION
+#define Rf_DropDims INTERNAL_R_FUNCTION
+#define Rf_duplicate INTERNAL_R_FUNCTION
+#define Rf_duplicated INTERNAL_R_FUNCTION
+#define Rf_eval INTERNAL_R_FUNCTION
+#define Rf_findFun INTERNAL_R_FUNCTION
+#define Rf_findVar INTERNAL_R_FUNCTION
+#define Rf_findVarInFrame INTERNAL_R_FUNCTION
+#define Rf_findVarInFrame3 INTERNAL_R_FUNCTION
+#define Rf_getAttrib INTERNAL_R_FUNCTION
+#define Rf_GetArrayDimnames INTERNAL_R_FUNCTION
+#define Rf_GetColNames INTERNAL_R_FUNCTION
+#define Rf_GetMatrixDimnames INTERNAL_R_FUNCTION
+#define Rf_GetOption INTERNAL_R_FUNCTION
+#define Rf_GetOptionDigits INTERNAL_R_FUNCTION
+#define Rf_GetOptionWidth INTERNAL_R_FUNCTION
+#define Rf_GetRowNames INTERNAL_R_FUNCTION
+#define Rf_gsetVar INTERNAL_R_FUNCTION
+#define Rf_install INTERNAL_R_FUNCTION
+#define Rf_isFree INTERNAL_R_FUNCTION
+#define Rf_isOrdered INTERNAL_R_FUNCTION
+#define Rf_isUnordered INTERNAL_R_FUNCTION
+#define Rf_isUnsorted INTERNAL_R_FUNCTION
+#define Rf_lengthgets INTERNAL_R_FUNCTION
+#define R_lsInternal INTERNAL_R_FUNCTION
+#define Rf_match INTERNAL_R_FUNCTION
+#define Rf_namesgets INTERNAL_R_FUNCTION
+#define Rf_mkChar INTERNAL_R_FUNCTION
+#define Rf_mkCharLen INTERNAL_R_FUNCTION
+#define Rf_NonNullStringMatch INTERNAL_R_FUNCTION
+#define Rf_ncols INTERNAL_R_FUNCTION
+#define Rf_nrows INTERNAL_R_FUNCTION
+#define Rf_nthcdr INTERNAL_R_FUNCTION
+#define Rf_pmatch INTERNAL_R_FUNCTION
+#define Rf_psmatch INTERNAL_R_FUNCTION
+#define Rf_PrintValue INTERNAL_R_FUNCTION
+#define Rf_protect INTERNAL_R_FUNCTION
+#define Rf_setAttrib INTERNAL_R_FUNCTION
+#define Rf_setSVector INTERNAL_R_FUNCTION
+#define Rf_setVar INTERNAL_R_FUNCTION
+#define Rf_str2type INTERNAL_R_FUNCTION
+#define Rf_StringBlank INTERNAL_R_FUNCTION
+#define Rf_substitute INTERNAL_R_FUNCTION
+#define Rf_translateChar INTERNAL_R_FUNCTION
+#define Rf_translateCharUTF8 INTERNAL_R_FUNCTION
+#define Rf_type2char INTERNAL_R_FUNCTION
+#define Rf_type2str INTERNAL_R_FUNCTION
+#define Rf_unprotect INTERNAL_R_FUNCTION
+#define Rf_unprotect_ptr INTERNAL_R_FUNCTION
+#define R_ProtectWithIndex INTERNAL_R_FUNCTION
+#define R_Reprotect INTERNAL_R_FUNCTION
+#define R_tryEval INTERNAL_R_FUNCTION
+#define Rf_asS4 INTERNAL_R_FUNCTION
+#define Rf_getCharCE INTERNAL_R_FUNCTION
+#define Rf_mkCharCE INTERNAL_R_FUNCTION
+#define Rf_mkCharLenCE INTERNAL_R_FUNCTION
+#define Rf_reEnc INTERNAL_R_FUNCTION
+#define R_MakeExternalPtr INTERNAL_R_FUNCTION
+#define R_ExternalPtrAddr INTERNAL_R_FUNCTION
+#define R_ExternalPtrTag INTERNAL_R_FUNCTION
+#define R_ExternalPtrProtected INTERNAL_R_FUNCTION
+#define R_ClearExternalPtr INTERNAL_R_FUNCTION
+#define R_SetExternalPtrAddr INTERNAL_R_FUNCTION
+#define R_SetExternalPtrTag INTERNAL_R_FUNCTION
+#define R_SetExternalPtrProtected INTERNAL_R_FUNCTION
+#define R_RegisterFinalizer INTERNAL_R_FUNCTION
+#define R_RegisterCFinalizer INTERNAL_R_FUNCTION
+#define R_RegisterFinalizerEx INTERNAL_R_FUNCTION
+#define R_RegisterCFinalizerEx INTERNAL_R_FUNCTION
+#define R_MakeWeakRef INTERNAL_R_FUNCTION
+#define R_MakeWeakRefC INTERNAL_R_FUNCTION
+#define R_WeakRefKey INTERNAL_R_FUNCTION
+#define R_WeakRefValue INTERNAL_R_FUNCTION
+#define R_RunWeakRefFinalizer INTERNAL_R_FUNCTION
+#define R_PromiseExpr INTERNAL_R_FUNCTION
+#define R_ClosureExpr INTERNAL_R_FUNCTION
+#define R_initialize_bcode INTERNAL_R_FUNCTION
+#define R_bcEncode INTERNAL_R_FUNCTION
+#define R_bcDecode INTERNAL_R_FUNCTION
+#define R_ToplevelExec INTERNAL_R_FUNCTION
+#define R_RestoreHashCount INTERNAL_R_FUNCTION
+#define R_IsPackageEnv INTERNAL_R_FUNCTION
+#define R_PackageEnvName INTERNAL_R_FUNCTION
+#define R_FindPackageEnv INTERNAL_R_FUNCTION
+#define R_IsNamespaceEnv INTERNAL_R_FUNCTION
+#define R_NamespaceEnvSpec INTERNAL_R_FUNCTION
+#define R_FindNamespace INTERNAL_R_FUNCTION
+#define R_LockEnvironment INTERNAL_R_FUNCTION
+#define R_EnvironmentIsLocked INTERNAL_R_FUNCTION
+#define R_LockBinding INTERNAL_R_FUNCTION
+#define R_unLockBinding INTERNAL_R_FUNCTION
+#define R_MakeActiveBinding INTERNAL_R_FUNCTION
+#define R_BindingIsLocked INTERNAL_R_FUNCTION
+#define R_BindingIsActive INTERNAL_R_FUNCTION
+#define R_HasFancyBindings INTERNAL_R_FUNCTION
+#define Rf_errorcall INTERNAL_R_FUNCTION
+#define Rf_warningcall INTERNAL_R_FUNCTION
+#define Rf_warningcall_immediate INTERNAL_R_FUNCTION
+#define R_XDREncodeDouble INTERNAL_R_FUNCTION
+#define R_XDRDecodeDouble INTERNAL_R_FUNCTION
+#define R_XDREncodeInteger INTERNAL_R_FUNCTION
+#define R_XDRDecodeInteger INTERNAL_R_FUNCTION
+#define R_InitInPStream INTERNAL_R_FUNCTION
+#define R_InitOutPStream INTERNAL_R_FUNCTION
+#define R_InitFileInPStream INTERNAL_R_FUNCTION
+#define R_InitFileOutPStream INTERNAL_R_FUNCTION
+#define R_InitConnOutPStream INTERNAL_R_FUNCTION
+#define R_InitConnInPStream INTERNAL_R_FUNCTION
+#define R_Serialize INTERNAL_R_FUNCTION
+#define R_Unserialize INTERNAL_R_FUNCTION
+#define R_do_slot INTERNAL_R_FUNCTION
+#define R_do_slot_assign INTERNAL_R_FUNCTION
+#define R_has_slot INTERNAL_R_FUNCTION
+#define R_do_MAKE_CLASS INTERNAL_R_FUNCTION
+#define R_getClassDef INTERNAL_R_FUNCTION
+#define R_do_new_object INTERNAL_R_FUNCTION
+#define R_PreserveObject INTERNAL_R_FUNCTION
+#define R_ReleaseObject INTERNAL_R_FUNCTION
+#define R_dot_Last INTERNAL_R_FUNCTION
+#define R_RunExitFinalizers INTERNAL_R_FUNCTION
+#define R_popen INTERNAL_R_FUNCTION
+#define R_system INTERNAL_R_FUNCTION
+#define Rf_conformable INTERNAL_R_FUNCTION
+#define Rf_elt INTERNAL_R_FUNCTION
+#define Rf_inherits INTERNAL_R_FUNCTION
+#define Rf_isArray INTERNAL_R_FUNCTION
+#define Rf_isFactor INTERNAL_R_FUNCTION
+#define Rf_isFrame INTERNAL_R_FUNCTION
+#define Rf_isFunction INTERNAL_R_FUNCTION
+#define Rf_isInteger INTERNAL_R_FUNCTION
+#define Rf_isLanguage INTERNAL_R_FUNCTION
+#define Rf_isList INTERNAL_R_FUNCTION
+#define Rf_isMatrix INTERNAL_R_FUNCTION
+#define Rf_isNewList INTERNAL_R_FUNCTION
+#define Rf_isNumeric INTERNAL_R_FUNCTION
+#define Rf_isPairList INTERNAL_R_FUNCTION
+#define Rf_isPrimitive INTERNAL_R_FUNCTION
+#define Rf_isTs INTERNAL_R_FUNCTION
+#define Rf_isUserBinop INTERNAL_R_FUNCTION
+#define Rf_isValidString INTERNAL_R_FUNCTION
+#define Rf_isValidStringF INTERNAL_R_FUNCTION
+#define Rf_isVector INTERNAL_R_FUNCTION
+#define Rf_isVectorAtomic INTERNAL_R_FUNCTION
+#define Rf_isVectorList INTERNAL_R_FUNCTION
+#define Rf_isVectorizable INTERNAL_R_FUNCTION
+#define Rf_lang1 INTERNAL_R_FUNCTION
+#define Rf_lang2 INTERNAL_R_FUNCTION
+#define Rf_lang3 INTERNAL_R_FUNCTION
+#define Rf_lang4 INTERNAL_R_FUNCTION
+#define Rf_lastElt INTERNAL_R_FUNCTION
+#define Rf_lcons INTERNAL_R_FUNCTION
+#define Rf_length INTERNAL_R_FUNCTION
+#define Rf_list1 INTERNAL_R_FUNCTION
+#define Rf_list2 INTERNAL_R_FUNCTION
+#define Rf_list3 INTERNAL_R_FUNCTION
+#define Rf_list4 INTERNAL_R_FUNCTION
+#define Rf_listAppend INTERNAL_R_FUNCTION
+#define Rf_mkString INTERNAL_R_FUNCTION
+#define Rf_nlevels INTERNAL_R_FUNCTION
+#define Rf_ScalarComplex INTERNAL_R_FUNCTION
+#define Rf_ScalarInteger INTERNAL_R_FUNCTION
+#define Rf_ScalarLogical INTERNAL_R_FUNCTION
+#define Rf_ScalarRaw INTERNAL_R_FUNCTION
+#define Rf_ScalarReal INTERNAL_R_FUNCTION
+#define Rf_ScalarString INTERNAL_R_FUNCTION
+
+#endif // R_INTERNAL_FUNCTIONS
+
+#endif // R_R_INTERNAL_HPP
+
diff --git a/src/cpp/r/include/r/RJson.hpp b/src/cpp/r/include/r/RJson.hpp
new file mode 100644
index 0000000..7987eae
--- /dev/null
+++ b/src/cpp/r/include/r/RJson.hpp
@@ -0,0 +1,42 @@
+/*
+ * RJson.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_JSON_HPP
+#define R_JSON_HPP
+
+#include <core/json/Json.hpp>
+
+typedef struct SEXPREC *SEXP;
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+// IMPORTANT NOTE: all code in r::json must provide "no jump" guarantee.
+// See comment in RInternal.hpp for more info on this
+
+namespace r {
+namespace json {
+
+core::Error jsonValueFromScalar(SEXP scalarSEXP, core::json::Value* pValue);
+core::Error jsonValueFromVector(SEXP vectorSEXP, core::json::Value* pValue);
+core::Error jsonValueFromList(SEXP listSEXP, core::json::Value* pValue);
+core::Error jsonValueFromObject(SEXP objectSEXP, core::json::Value* pValue);
+
+} // namespace json
+} // namesapce r
+
+#endif // R_JSON_HPP
diff --git a/src/cpp/r/include/r/RJsonRpc.hpp b/src/cpp/r/include/r/RJsonRpc.hpp
new file mode 100644
index 0000000..77d52b8
--- /dev/null
+++ b/src/cpp/r/include/r/RJsonRpc.hpp
@@ -0,0 +1,38 @@
+/*
+ * RJsonRpc.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_JSON_RPC_HPP
+#define R_R_JSON_RPC_HPP
+
+#include <string>
+
+#include <core/json/JsonRpc.hpp>
+
+typedef struct SEXPREC *SEXP;
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace json {
+
+core::Error getRpcMethods(core::json::JsonRpcMethods* pMethods);
+
+} // namespace json
+} // namesapce r
+
+#endif // R_R_JSON_RPC_HPP
diff --git a/src/cpp/r/include/r/ROptions.hpp b/src/cpp/r/include/r/ROptions.hpp
new file mode 100644
index 0000000..839b77e
--- /dev/null
+++ b/src/cpp/r/include/r/ROptions.hpp
@@ -0,0 +1,108 @@
+/*
+ * ROptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_OPTIONS_HPP
+#define R_R_OPTIONS_HPP
+
+#include <string>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RErrorCategory.hpp>
+
+// IMPORTANT NOTE: all code in r::options must provide "no jump" guarantee.
+// See comment in RInternal.hpp for more info on this
+
+namespace core {
+ class FilePath;
+}
+
+namespace r {
+namespace options {
+
+core::Error saveOptions(const core::FilePath& filePath);
+core::Error restoreOptions(const core::FilePath& filePath);
+
+// console width
+extern const int kDefaultWidth;
+void setOptionWidth(int width);
+int getOptionWidth();
+
+// generic get and set
+
+SEXP getOption(const std::string& name);
+
+template <typename T>
+T getOption(const std::string& name,
+ const T& defaultValue = T(),
+ bool logNotFound = true)
+{
+ // prevent Rf_install from encountering error/longjmp condition
+ if (name.empty())
+ return defaultValue;
+
+ SEXP valueSEXP = getOption(name);
+ if (valueSEXP != R_NilValue)
+ {
+ T value;
+ core::Error error = sexp::extract(valueSEXP, &value);
+ if (error)
+ {
+ error.addProperty("symbol (option)", name);
+ LOG_ERROR(error);
+ return defaultValue;
+ }
+
+ return value;
+ }
+ else
+ {
+ core::Error error(errc::SymbolNotFoundError, ERROR_LOCATION);
+ error.addProperty("symbol (option)", name);
+ if (logNotFound)
+ LOG_ERROR(error);
+ return defaultValue;
+ }
+}
+
+template <typename T>
+core::Error setOption(const std::string& name, const T& value)
+{
+ r::exec::RFunction optionsFunction("options");
+ optionsFunction.addParam(name, value);
+ core::Error error = optionsFunction.call();
+ if (error)
+ {
+ error.addProperty("option-name", name);
+ return error;
+ }
+ else
+ {
+ return core::Success();
+ }
+}
+
+SEXP setErrorOption(SEXP value);
+
+
+} // namespace options
+} // namespace r
+
+
+#endif // R_R_OPTIONS_HPP
+
diff --git a/src/cpp/r/include/r/RRoutines.hpp b/src/cpp/r/include/r/RRoutines.hpp
new file mode 100644
index 0000000..0bee1ca
--- /dev/null
+++ b/src/cpp/r/include/r/RRoutines.hpp
@@ -0,0 +1,36 @@
+/*
+ * RRoutines.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_ROUTINES_HPP
+#define R_ROUTINES_HPP
+
+#include <vector>
+
+#include <R_ext/Rdynload.h>
+
+namespace r {
+namespace routines {
+
+void addCMethod(const R_CMethodDef method);
+void addCallMethod(const R_CallMethodDef method);
+
+void registerAll();
+
+} // namespace routines
+} // namespace r
+
+
+#endif // R_ROUTINES_HPP
+
diff --git a/src/cpp/r/include/r/RSexp.hpp b/src/cpp/r/include/r/RSexp.hpp
new file mode 100644
index 0000000..76d70a1
--- /dev/null
+++ b/src/cpp/r/include/r/RSexp.hpp
@@ -0,0 +1,287 @@
+/*
+ * RSexp.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_SEXP_HPP
+#define R_R_SEXP_HPP
+
+#include <string>
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/any.hpp>
+#include <boost/utility.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/Error.hpp>
+#include <core/json/Json.hpp>
+
+#include <r/RErrorCategory.hpp>
+#include <r/RInternal.hpp>
+
+
+// IMPORTANT NOTE: all code in r::sexp must provide "no jump" guarantee.
+// See comment in RInternal.hpp for more info on this
+
+
+namespace r {
+namespace sexp {
+
+class Protect;
+
+// environments and namespaces
+SEXP findNamespace(const std::string& name);
+
+// variables within an environment
+typedef std::pair<std::string,SEXP> Variable ;
+void listEnvironment(SEXP env,
+ bool includeAll,
+ Protect* pProtect,
+ std::vector<Variable>* pVariables);
+
+// object info
+SEXP findVar(const std::string& name,
+ const std::string& ns = std::string());
+SEXP findVar(const std::string& name,
+ const SEXP env);
+SEXP findFunction(const std::string& name,
+ const std::string& ns = std::string());
+std::string typeAsString(SEXP object);
+std::string classOf(SEXP objectSEXP);
+int length(SEXP object);
+
+SEXP getNames(SEXP sexp);
+core::Error getNames(SEXP sexp, std::vector<std::string>* pNames);
+bool isActiveBinding(const std::string&, const SEXP);
+
+// type checking
+bool isString(SEXP object);
+bool isLanguage(SEXP object);
+bool isMatrix(SEXP object);
+bool isDataFrame(SEXP object);
+
+bool isNull(SEXP object);
+
+// type coercions
+std::string asString(SEXP object);
+std::string safeAsString(SEXP object,
+ const std::string& defValue = std::string());
+int asInteger(SEXP object);
+double asReal(SEXP object);
+bool asLogical(SEXP object);
+
+SEXP getAttrib(SEXP object, SEXP attrib);
+SEXP getAttrib(SEXP object, const std::string& attrib);
+SEXP setAttrib(SEXP object, const std::string& attrib, SEXP val);
+
+// weak/external pointers and finalizers
+SEXP makeWeakRef(SEXP key, SEXP val, R_CFinalizer_t fun, Rboolean onexit);
+void registerFinalizer(SEXP s, R_CFinalizer_t fun);
+SEXP makeExternalPtr(void* ptr, R_CFinalizer_t fun, Protect* protect);
+void* getExternalPtrAddr(SEXP extptr);
+void clearExternalPtr(SEXP extptr);
+
+// extract c++ type from R SEXP
+core::Error extract(SEXP valueSEXP, int* pInt);
+core::Error extract(SEXP valueSEXP, bool* pBool);
+core::Error extract(SEXP valueSEXP, double* pDouble);
+core::Error extract(SEXP valueSEXP, std::vector<int>* pVector);
+core::Error extract(SEXP valueSEXP, std::string* pString);
+core::Error extract(SEXP valueSEXP, std::vector<std::string>* pVector);
+
+// create SEXP from c++ type
+SEXP create(const core::json::Value& value, Protect* pProtect);
+SEXP create(const char* value, Protect* pProtect);
+SEXP create(const std::string& value, Protect* pProtect);
+SEXP create(int value, Protect* pProtect);
+SEXP create(double value, Protect* pProtect);
+SEXP create(bool value, Protect* pProtect);
+SEXP create(const std::vector<std::string>& value, Protect* pProtect);
+SEXP create(const std::vector<int>& value, Protect*pProtect);
+SEXP create(const std::vector<double>& value, Protect*pProtect);
+SEXP create(const std::vector<bool>& value, Protect*pProtect);
+SEXP create(const std::vector<boost::posix_time::ptime>& value,
+ Protect* pProtect);
+
+SEXP create(const std::vector<std::pair<std::string,std::string> >& value,
+ Protect* pProtect);
+SEXP create(const core::json::Array& value, Protect* pProtect);
+SEXP create(const core::json::Object& value, Protect* pProtect);
+
+
+inline int indexOfElementNamed(SEXP listSEXP, const std::string& name)
+{
+ // get the names so we can determine which slot the element is in are in
+ std::vector<std::string> names;
+ core::Error error = r::sexp::getNames(listSEXP, &names);
+ if (error)
+ return -1;
+
+ // find the index
+ int valueIndex = -1;
+ for (int i = 0; i<(int)names.size(); i++)
+ {
+ if (names[i] == name)
+ {
+ valueIndex = i;
+ break;
+ }
+ }
+
+ // return
+ return valueIndex;
+
+}
+
+template <typename T>
+core::Error getNamedListElement(SEXP listSEXP,
+ const std::string& name,
+ T* pValue)
+{
+ // find the element
+ int valueIndex = indexOfElementNamed(listSEXP, name);
+
+ if (valueIndex != -1)
+ {
+ // get the appropriate value
+ SEXP valueSEXP = VECTOR_ELT(listSEXP, valueIndex);
+ return sexp::extract(valueSEXP, pValue);
+ }
+ else
+ {
+ // otherwise an error
+ core::Error error(r::errc::ListElementNotFoundError, ERROR_LOCATION);
+ error.addProperty("element", name);
+ return error;
+ }
+}
+
+template <typename T>
+core::Error getNamedListElement(SEXP listSEXP,
+ const std::string& name,
+ T* pValue,
+ const T& defaultValue)
+{
+ core:: Error error = getNamedListElement(listSEXP, name, pValue);
+ if (error)
+ {
+ if (error.code() == r::errc::ListElementNotFoundError)
+ {
+ *pValue = defaultValue;
+ return core::Success();
+ }
+ else
+ {
+ return error;
+ }
+ }
+ else
+ {
+ return core::Success();
+ }
+}
+
+
+// protect R expressions
+class Protect : boost::noncopyable
+{
+public:
+ Protect()
+ : protectCount_(0)
+ {
+ }
+
+ explicit Protect(SEXP sexp)
+ : protectCount_(0)
+ {
+ add(sexp);
+ }
+
+ virtual ~Protect();
+
+ // COPYING: boost::noncopyable
+
+ void add(SEXP sexp);
+ void unprotectAll();
+
+private:
+ int protectCount_ ;
+};
+
+// set list element by name. note that the specified element MUST already
+// exist before the call
+template <typename T>
+core::Error setNamedListElement(SEXP listSEXP,
+ const std::string& name,
+ const T& value)
+{
+ // convert to SEXP
+ r::sexp::Protect rProtect;
+ SEXP valueSEXP = create(value, &rProtect);
+
+ // find the element
+ int valueIndex = indexOfElementNamed(listSEXP, name);
+
+ if (valueIndex != -1)
+ {
+ // set the appropriate value and return success
+ SET_VECTOR_ELT(listSEXP, valueIndex, valueSEXP);
+ return core::Success();
+ }
+ else
+ {
+ // otherwise an error
+ core::Error error(r::errc::ListElementNotFoundError, ERROR_LOCATION);
+ error.addProperty("element", name);
+ return error;
+ }
+}
+
+
+class PreservedSEXP : boost::noncopyable
+{
+public:
+ PreservedSEXP();
+ explicit PreservedSEXP(SEXP sexp);
+ virtual ~PreservedSEXP();
+
+ void set(SEXP sexp);
+ SEXP get() const { return sexp_; }
+ bool isNil() const { return sexp_ == R_NilValue; }
+
+ typedef void (*unspecified_bool_type)();
+ static void unspecified_bool_true() {}
+ operator unspecified_bool_type() const
+ {
+ return isNil() ? 0 : unspecified_bool_true;
+ }
+ bool operator!() const
+ {
+ return isNil();
+ }
+
+ void releaseNow();
+
+private:
+ SEXP sexp_;
+};
+
+} // namespace sexp
+} // namespace r
+
+
+#endif // R_R_SEXP_HPP
+
diff --git a/src/cpp/r/include/r/RSourceManager.hpp b/src/cpp/r/include/r/RSourceManager.hpp
new file mode 100644
index 0000000..16a94c7
--- /dev/null
+++ b/src/cpp/r/include/r/RSourceManager.hpp
@@ -0,0 +1,89 @@
+/*
+ * RSourceManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SOURCE_MANAGER_HPP
+#define R_SOURCE_MANAGER_HPP
+
+#include <time.h>
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/unordered_map.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace core {
+ class Error ;
+}
+
+namespace r {
+
+// singleton
+class SourceManager ;
+SourceManager& sourceManager();
+
+class SourceManager : boost::noncopyable
+{
+private:
+ SourceManager() : autoReload_(false) {}
+ friend SourceManager& sourceManager();
+ // COPYING: boost::noncopyable
+
+public:
+
+ bool autoReload() const { return autoReload_; }
+ void setAutoReload(bool autoReload) { autoReload_ = autoReload; }
+
+ core::Error sourceTools(const core::FilePath& filePath);
+ void ensureToolsLoaded();
+
+ core::Error sourceLocal(const core::FilePath& filePath);
+
+ void reloadIfNecessary();
+
+private:
+ // data types
+ struct SourcedFileInfo
+ {
+ SourcedFileInfo() : lastWriteTime((time_t)-1), local(true) {}
+ SourcedFileInfo(time_t lastWriteTime, bool local)
+ : lastWriteTime(lastWriteTime),
+ local(local)
+ {
+ }
+ time_t lastWriteTime;
+ bool local;
+ };
+ typedef boost::unordered_map<std::string, SourcedFileInfo> SourcedFileMap;
+
+ // helper functions
+ core::Error source(const core::FilePath& filePath, bool local);
+ void reSourceTools(const core::FilePath& filePath);
+ void recordSourcedFile(const core::FilePath& filePath, bool local);
+ void reloadSourceIfNecessary(const SourcedFileMap::value_type& value);
+
+ // members
+ bool autoReload_ ;
+ SourcedFileMap sourcedFiles_ ;
+ std::vector<core::FilePath> toolsFilePaths_;
+};
+
+} // namespace r
+
+
+#endif // R_SOURCE_MANAGER_HPP
+
diff --git a/src/cpp/r/include/r/RUtil.hpp b/src/cpp/r/include/r/RUtil.hpp
new file mode 100644
index 0000000..a804211
--- /dev/null
+++ b/src/cpp/r/include/r/RUtil.hpp
@@ -0,0 +1,50 @@
+/*
+ * RUtil.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_UTIL_HPP
+#define R_UTIL_HPP
+
+#include <string>
+
+namespace core {
+ class FilePath;
+ class Error;
+}
+
+namespace r {
+namespace util {
+
+std::string expandFileName(const std::string& name);
+
+std::string fixPath(const std::string& path);
+
+bool hasRequiredVersion(const std::string& version);
+
+bool hasCapability(const std::string& capability);
+
+std::string rconsole2utf8(const std::string& encoded);
+
+core::Error iconvstr(const std::string& value,
+ const std::string& from,
+ const std::string& to,
+ bool allowSubstitution,
+ std::string* result);
+
+} // namespace util
+} // namespace r
+
+
+#endif // R_UTIL_HPP
+
diff --git a/src/cpp/r/include/r/session/RClientState.hpp b/src/cpp/r/include/r/session/RClientState.hpp
new file mode 100644
index 0000000..6ae5687
--- /dev/null
+++ b/src/cpp/r/include/r/session/RClientState.hpp
@@ -0,0 +1,96 @@
+/*
+ * RClientState.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_CLIENT_STATE_HPP
+#define R_SESSION_CLIENT_STATE_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace r {
+namespace session {
+
+// singleton
+class ClientState ;
+ClientState& clientState();
+
+enum ClientStateCommitType
+{
+ ClientStateCommitAll,
+ ClientStateCommitPersistentOnly,
+};
+
+class ClientState : boost::noncopyable
+{
+private:
+ ClientState();
+ friend ClientState& clientState();
+
+public:
+
+ void clear();
+
+ void putTemporary(const std::string& scope,
+ const std::string& name,
+ const core::json::Value& value);
+
+ void putTemporary(const core::json::Object& temporaryState);
+
+ void putPersistent(const std::string& scope,
+ const std::string& name,
+ const core::json::Value& value);
+
+ void putPersistent(const core::json::Object& persistentState);
+
+ void putProjectPersistent(const std::string& scope,
+ const std::string& name,
+ const core::json::Value& value);
+
+ void putProjectPersistent(const core::json::Object& projectPersistentState);
+ core::json::Value getProjectPersistent(std::string scope,
+ std::string name);
+
+ core::Error commit(ClientStateCommitType commitType,
+ const core::FilePath& stateDir,
+ const core::FilePath& projectStateDir);
+
+ core::Error restore(const core::FilePath& stateDir,
+ const core::FilePath& projectStateDir);
+
+ void currentState(core::json::Object* pCurrentState) const;
+
+private:
+ void restoreGlobalState(const core::FilePath& stateFile);
+ void restoreProjectState(const core::FilePath& stateFile);
+
+private:
+ core::json::Object temporaryState_ ;
+ core::json::Object persistentState_ ;
+ core::json::Object projectPersistentState_;
+};
+
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_CLIENT_STATE_HPP
+
diff --git a/src/cpp/r/include/r/session/RConsoleActions.hpp b/src/cpp/r/include/r/session/RConsoleActions.hpp
new file mode 100644
index 0000000..3f6aee0
--- /dev/null
+++ b/src/cpp/r/include/r/session/RConsoleActions.hpp
@@ -0,0 +1,82 @@
+/*
+ * RConsoleActions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_CONSOLE_ACTIONS_HPP
+#define R_SESSION_CONSOLE_ACTIONS_HPP
+
+#include <boost/utility.hpp>
+#include <boost/circular_buffer.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+
+// singleton
+class ConsoleActions;
+ConsoleActions& consoleActions();
+
+#define kConsoleActionPrompt 0
+#define kConsoleActionInput 1
+#define kConsoleActionOutput 2
+#define kConsoleActionOutputError 3
+
+class ConsoleActions : boost::noncopyable
+{
+private:
+ ConsoleActions();
+ friend ConsoleActions& consoleActions();
+
+public:
+ int capacity() const ;
+ void setCapacity(int capacity);
+
+ void add(int type, const std::string& data);
+ void notifyInterrupt();
+
+ std::vector<std::string> pendingInput() const { return pendingInput_; }
+
+ // reset to all but the last prompt
+ void reset();
+
+ // get actions in their wire-representation (two identically sized arrays,
+ // one for type and one for data)
+ void asJson(core::json::Object* pActions) const;
+
+ core::Error loadFromFile(const core::FilePath& filePath);
+ core::Error saveToFile(const core::FilePath& filePath) const;
+
+private:
+ // protect data using a mutex because background threads (e.g.
+ // console output capture threads) can interact with console actions
+ mutable boost::mutex mutex_;
+ boost::circular_buffer<core::json::Value> actionsType_;
+ boost::circular_buffer<core::json::Value> actionsData_;
+ std::vector<std::string> pendingInput_;
+};
+
+
+
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_CONSOLE_ACTIONS_HPP
+
diff --git a/src/cpp/r/include/r/session/RConsoleHistory.hpp b/src/cpp/r/include/r/session/RConsoleHistory.hpp
new file mode 100644
index 0000000..89ffce3
--- /dev/null
+++ b/src/cpp/r/include/r/session/RConsoleHistory.hpp
@@ -0,0 +1,105 @@
+/*
+ * RConsoleHistory.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_CONSOLE_HISTORY_HPP
+#define R_SESSION_CONSOLE_HISTORY_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/circular_buffer.hpp>
+#include <boost/signal.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error ;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+
+// singleton
+class ConsoleHistory ;
+ConsoleHistory& consoleHistory();
+
+class ConsoleHistory : boost::noncopyable
+{
+public:
+ typedef boost::circular_buffer<std::string>::value_type value_type;
+ typedef boost::circular_buffer<std::string>::const_iterator const_iterator;
+ typedef boost::signal<void (const std::string&)> AddSignal;
+
+private:
+ ConsoleHistory();
+ friend ConsoleHistory& consoleHistory();
+ // COPYING: boost::noncopyable
+
+public:
+ void setCapacity(int capacity);
+
+ void setCapacityFromRHistsize();
+
+ int capacity() const
+ {
+ return historyBuffer_.capacity();
+ }
+
+ void setRemoveDuplicates(bool removeDuplicates);
+
+ void add(const std::string& command);
+
+ const_iterator begin() const { return historyBuffer_.begin(); }
+ const_iterator end() const { return historyBuffer_.end(); }
+
+ int size() const
+ {
+ return historyBuffer_.size();
+ }
+
+ void clear();
+
+ void remove(const std::vector<int>& indexes);
+
+ void subset(int beginIndex, // inclusive
+ int endIndex, // exclusive,
+ std::vector<std::string>* pEntries) const;
+
+ void asJson(core::json::Array* pHistoryArray) const;
+
+ core::Error loadFromFile(const core::FilePath& filePath, bool verifyFile);
+ core::Error saveToFile(const core::FilePath& filePath) const;
+
+ boost::signals::connection connectOnAdd(
+ const AddSignal::slot_function_type& slot)
+ {
+ return onAdd_.connect(slot);
+ }
+
+private:
+ void safeRemove(int index);
+
+private:
+ bool removeDuplicates_;
+ boost::circular_buffer<std::string> historyBuffer_ ;
+ AddSignal onAdd_;
+};
+
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_CONSOLE_HISTORY_HPP
+
diff --git a/src/cpp/r/include/r/session/RDiscovery.hpp b/src/cpp/r/include/r/session/RDiscovery.hpp
new file mode 100644
index 0000000..4d460a2
--- /dev/null
+++ b/src/cpp/r/include/r/session/RDiscovery.hpp
@@ -0,0 +1,40 @@
+/*
+ * RDiscovery.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_DISCOVERY_HPP
+#define R_SESSION_DISCOVERY_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace r {
+namespace session {
+
+struct RLocations
+{
+ std::string homePath;
+ std::string docPath;
+};
+
+core::Error discoverR(RLocations* pLocations);
+
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_DISCOVERY_HPP
+
diff --git a/src/cpp/r/include/r/session/REventLoop.hpp b/src/cpp/r/include/r/session/REventLoop.hpp
new file mode 100644
index 0000000..31791ce
--- /dev/null
+++ b/src/cpp/r/include/r/session/REventLoop.hpp
@@ -0,0 +1,67 @@
+/*
+ * REventLoop.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_EVENT_LOOP_HPP
+#define R_EVENT_LOOP_HPP
+
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+
+namespace r {
+namespace session {
+namespace event_loop {
+
+//
+// R calls the polled event handler via R_PolledEvents. this occurs at
+// the following times:
+//
+// - During Rstd_ReadConsole (via R_runHandlers). Note that front-ends
+// which install a custom ptr_R_ReadConsole will not be called in
+// this context.
+//
+// - During do_syssleep (via R_runHandlers)
+//
+// - During R_CheckUserInterrupt
+//
+// - During R_SocketWait
+//
+// - During RxmlNanoHTTPConnectAttempt and RxmlNanoHTTPRecv
+//
+// The fact that the handler is called from so many contexts make it
+// suitable for implementing the event-polling required for a responsive
+// GUI. This could take the form of pumping GUI events or checking for
+// inbound network requests. The point is that this function will continue
+// to be called even while R is busy doing other things like processing
+// network requests, waiting for input, sleeping, or performing computations
+//
+void initializePolledEventHandler(void (*newPolledEventHandler)(void));
+
+void permanentlyDisablePolledEventHandler();
+
+// check whether the polled event handler has already been initialized
+bool polledEventHandlerInitialized();
+
+// event processing (allowing R gui components like GraphApp or the quartz
+// device to remain responsive)
+void processEvents();
+
+
+
+} // namespace event_loop
+} // namespace session
+} // namespace r
+
+
+#endif // R_EVENT_LOOP_HPP
+
diff --git a/src/cpp/r/include/r/session/RGraphics.hpp b/src/cpp/r/include/r/session/RGraphics.hpp
new file mode 100644
index 0000000..8ce0e8f
--- /dev/null
+++ b/src/cpp/r/include/r/session/RGraphics.hpp
@@ -0,0 +1,180 @@
+/*
+ * RGraphics.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_HPP
+#define R_SESSION_GRAPHICS_HPP
+
+#include <boost/system/error_code.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace errc {
+
+enum errc_t
+{
+ Success = 0,
+ IncompatibleGraphicsEngine,
+ DeviceNotAvailable,
+ NoActivePlot,
+ InvalidPlotIndex,
+ InvalidPlotImageType,
+ PlotRenderingError,
+ PlotFileError
+};
+
+} // namespace errc
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+namespace boost {
+namespace system {
+template <>
+struct is_error_code_enum<r::session::graphics::errc::errc_t>
+ { static const bool value = true; };
+} // namespace system
+} // namespace boost
+
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+#include <boost/signal.hpp>
+
+#include <core/Error.hpp>
+#include <core/json/Json.hpp>
+
+namespace core {
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+struct DisplayState
+{
+ DisplayState(const std::string& imageFilename,
+ const core::json::Value& manipulatorJson,
+ int width,
+ int height,
+ int activePlotIndex,
+ int plotCount)
+ : imageFilename(imageFilename),
+ manipulatorJson(manipulatorJson),
+ width(width),
+ height(height),
+ activePlotIndex(activePlotIndex),
+ plotCount(plotCount)
+ {
+ }
+
+ std::string imageFilename;
+ core::json::Value manipulatorJson;
+ int width;
+ int height;
+ int activePlotIndex;
+ int plotCount;
+};
+
+extern const char * const kPngFormat;
+extern const char * const kJpegFormat;
+extern const char * const kTiffFormat;
+extern const char * const kBmpFormat;
+extern const char * const kMetafileFormat;
+extern const char * const kSvgFormat;
+extern const char * const kPostscriptFormat;
+
+class Display
+{
+public:
+ virtual ~Display() {}
+
+ // plot list
+ virtual int plotCount() const = 0 ;
+ virtual core::Error plotImageFilename(int index,
+ std::string* pImageFilename) const = 0;
+ virtual int activePlotIndex() const = 0;
+ virtual core::Error setActivePlot(int index) = 0;
+ virtual core::Error removePlot(int index) = 0;
+
+ // actions on active plot
+ virtual core::Error savePlotAsImage(const core::FilePath& filePath,
+ const std::string& format,
+ int widthPx,
+ int heightPx) = 0;
+
+ virtual core::Error savePlotAsPdf(const core::FilePath& filePath,
+ double widthInches,
+ double heightInches,
+ bool useCairoPdf) = 0;
+
+ virtual core::Error savePlotAsMetafile(const core::FilePath& filePath,
+ int widthPx,
+ int heightPx) = 0;
+
+ // display
+ virtual bool hasOutput() const = 0 ;
+ virtual bool hasChanges() const = 0 ;
+ virtual bool isActiveDevice() const = 0;
+ virtual boost::posix_time::ptime lastChange() const = 0;
+ virtual void render(boost::function<void(DisplayState)> outputFunction)=0;
+ virtual std::string imageFilename() const = 0 ;
+ virtual void refresh() = 0;
+
+ // retrieve image path based on filename
+ virtual core::FilePath imagePath(const std::string& imageFilename) const = 0;
+
+ // clear the display (closes the device)
+ virtual void clear() = 0;
+
+ // subscribe to showManipulator event
+ virtual boost::signal<void ()>& onShowManipulator() = 0;
+
+ // set manipulator values
+ virtual void setPlotManipulatorValues(const core::json::Object& values) = 0;
+ virtual void manipulatorPlotClicked(int x, int y) = 0;
+
+ // notify that we are about to execute code
+ virtual void onBeforeExecute() = 0;
+};
+
+// singleton
+Display& display();
+
+const boost::system::error_category& rGraphicsCategory() ;
+
+namespace errc {
+
+inline boost::system::error_code make_error_code( errc_t e )
+{
+ return boost::system::error_code( e, rGraphicsCategory() ); }
+
+inline boost::system::error_condition make_error_condition( errc_t e )
+{
+ return boost::system::error_condition( e, rGraphicsCategory() );
+}
+
+} // namespace errc
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_GRAPHICS_HPP
+
diff --git a/src/cpp/r/include/r/session/RSession.hpp b/src/cpp/r/include/r/session/RSession.hpp
new file mode 100644
index 0000000..083e1d3
--- /dev/null
+++ b/src/cpp/r/include/r/session/RSession.hpp
@@ -0,0 +1,188 @@
+/*
+ * RSession.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_RSESSION_HPP
+#define R_RSESSION_HPP
+
+#include <string>
+
+#include <boost/function.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <R_ext/RStartup.h>
+#include <r/session/RSessionUtils.hpp>
+
+namespace core {
+ class Error ;
+ class Settings;
+}
+
+namespace r {
+namespace session {
+
+struct RClientMetrics
+{
+ RClientMetrics()
+ : consoleWidth(0), graphicsWidth(0), graphicsHeight(0)
+ {
+ }
+ int consoleWidth ;
+ int graphicsWidth ;
+ int graphicsHeight;
+};
+
+struct ROptions
+{
+ ROptions() :
+ useInternet2(true),
+ rCompatibleGraphicsEngineVersion(9),
+ serverMode(false),
+ autoReloadSource(false),
+ restoreWorkspace(true),
+ saveWorkspace(SA_SAVEASK),
+ rProfileOnResume(false)
+ {
+ }
+ core::FilePath userHomePath;
+ core::FilePath userScratchPath;
+ core::FilePath scopedScratchPath;
+ core::FilePath logPath;
+ core::FilePath startupEnvironmentFilePath;
+ std::string sessionPort;
+ boost::function<core::Settings&()> persistentState;
+ boost::function<core::FilePath()> rEnvironmentDir;
+ boost::function<core::FilePath()> rHistoryDir;
+ boost::function<bool()> alwaysSaveHistory;
+ core::FilePath rSourcePath;
+ std::string rLibsUser;
+ std::string rCRANRepos;
+ bool useInternet2;
+ int rCompatibleGraphicsEngineVersion;
+ bool serverMode;
+ bool autoReloadSource ;
+ bool restoreWorkspace;
+ SA_TYPE saveWorkspace;
+ bool rProfileOnResume;
+};
+
+struct RInitInfo
+{
+ RInitInfo()
+ : resumed(false)
+ {
+ }
+ RInitInfo(bool resumed)
+ : resumed(resumed)
+ {
+ }
+ bool resumed;
+};
+
+struct RConsoleInput
+{
+ RConsoleInput() : cancel(true) {}
+ RConsoleInput(const std::string& text) : cancel(false), text(text) {}
+ bool cancel ;
+ std::string text;
+};
+
+// forward declare DisplayState
+namespace graphics {
+ struct DisplayState;
+}
+
+// serialization actions
+extern const int kSerializationActionSaveDefaultWorkspace;
+extern const int kSerializationActionLoadDefaultWorkspace;
+extern const int kSerializationActionSuspendSession;
+extern const int kSerializationActionResumeSession;
+extern const int kSerializationActionCompleted;
+
+struct RSuspendOptions;
+struct RCallbacks
+{
+ boost::function<core::Error(const RInitInfo&)> init ;
+ boost::function<bool(const std::string&,bool,RConsoleInput*)> consoleRead;
+ boost::function<void(const std::string&)> browseURL;
+ boost::function<void(const core::FilePath&)> browseFile;
+ boost::function<void(const std::string&)> showHelp;
+ boost::function<void(const std::string&, core::FilePath&, bool)> showFile;
+ boost::function<void(const std::string&, int)> consoleWrite;
+ boost::function<void()> consoleHistoryReset;
+ boost::function<bool(double*,double*)> locator;
+ boost::function<core::FilePath(bool)> chooseFile;
+ boost::function<int(const std::string&)> editFile;
+ boost::function<void(const std::string&)> showMessage ;
+ boost::function<void(bool)> busy;
+ boost::function<void(bool)> deferredInit;
+ boost::function<void(const r::session::RSuspendOptions& options)> suspended;
+ boost::function<void()> resumed;
+ boost::function<bool()> handleUnsavedChanges;
+ boost::function<void()> quit;
+ boost::function<void(const std::string&)> suicide;
+ boost::function<void(bool)> cleanup;
+ boost::function<void(int,const core::FilePath&)> serialization;
+};
+
+// run the session
+core::Error run(const ROptions& options, const RCallbacks& callbacks);
+
+// deferred deserialization of the session
+void ensureDeserialized();
+
+// set client metrics
+void setClientMetrics(const RClientMetrics& metrics);
+
+// report a warning to the user and also log it
+void reportAndLogWarning(const std::string& warning);
+
+// suspend/resume
+bool isSuspendable(const std::string& prompt);
+bool suspend(bool force);
+
+struct RSuspendOptions
+{
+ RSuspendOptions()
+ : saveMinimal(false), saveWorkspace(false), excludePackages(false)
+ {
+ }
+ bool saveMinimal;
+ bool saveWorkspace;
+ bool excludePackages;
+};
+void suspendForRestart(const RSuspendOptions& options);
+
+// set save action
+extern const int kSaveActionNoSave;
+extern const int kSaveActionSave;
+extern const int kSaveActionAsk;
+void setSaveAction(int saveAction);
+
+// image dirty state
+void setImageDirty(bool imageDirty);
+bool imageIsDirty();
+
+// check whether there is a browser context active
+bool browserContextActive();
+
+// quit
+void quit(bool saveWorkspace);
+
+} // namespace session
+} // namespace r
+
+#endif // R_RSESSION_HPP
+
diff --git a/src/cpp/r/include/r/session/RSessionUtils.hpp b/src/cpp/r/include/r/session/RSessionUtils.hpp
new file mode 100644
index 0000000..0552823
--- /dev/null
+++ b/src/cpp/r/include/r/session/RSessionUtils.hpp
@@ -0,0 +1,59 @@
+/*
+ * RSessionUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_UTILS_HPP
+#define R_SESSION_UTILS_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace utils {
+
+// check for R 3.0
+bool isR3();
+
+bool isDefaultPrompt(const std::string& prompt);
+
+// user home path
+const core::FilePath& userHomePath();
+
+core::FilePath safeCurrentPath();
+
+core::FilePath tempFile(const std::string& prefix,
+ const std::string& extension);
+
+core::FilePath tempDir();
+
+
+// suppress output in scope
+class SuppressOutputInScope
+{
+public:
+ SuppressOutputInScope();
+ ~SuppressOutputInScope();
+};
+
+} // namespace utils
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_UTILS_HPP
+
diff --git a/src/cpp/r/session/RClientMetrics.cpp b/src/cpp/r/session/RClientMetrics.cpp
new file mode 100644
index 0000000..020bbb1
--- /dev/null
+++ b/src/cpp/r/session/RClientMetrics.cpp
@@ -0,0 +1,115 @@
+/*
+ * RClientMetrics.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RClientMetrics.hpp"
+
+#include <iostream>
+
+#include <core/Settings.hpp>
+
+#include <r/ROptions.hpp>
+#include <r/session/RSession.hpp>
+
+#include "graphics/RGraphicsDevice.hpp"
+
+using namespace core ;
+
+namespace r {
+namespace session {
+namespace client_metrics {
+
+namespace {
+const char * const kConsoleWidth = "r.session.client_metrics.console-width";
+const char * const kGraphicsWidth = "r.session.client_metrics.graphics-width";
+const char * const kGraphicsHeight = "r.session.client_metrics.graphics-height";
+
+}
+
+RClientMetrics get()
+{
+ RClientMetrics metrics ;
+ metrics.consoleWidth = r::options::getOptionWidth();
+ metrics.graphicsWidth = graphics::device::getWidth();
+ metrics.graphicsHeight = graphics::device::getHeight();
+ return metrics;
+}
+
+void set(const RClientMetrics& metrics)
+{
+ // set console width (if it's within the valid range -- if it's not
+ // then an error will be thrown)
+ if (metrics.consoleWidth >= 10 && metrics.consoleWidth <= 10000)
+ r::options::setOptionWidth(metrics.consoleWidth);
+
+ // set graphics size, however don't do anything if width or height is less
+ // than or equal to 0)
+ // (means the graphics window is minimized)
+ if (metrics.graphicsWidth > 0 && metrics.graphicsHeight > 0)
+ {
+ // enforce a minimum graphics size so we don't get display
+ // list redraw errors -- note that setting the device to a size
+ // which diverges from the actual client size will break locator
+ // so we need to set the size small enough that there is no way
+ // it can reasonably be used for locator
+ int width = std::max(metrics.graphicsWidth, 100);
+ int height = std::max(metrics.graphicsHeight, 100);
+
+ // enforce a maximum graphics size so we don't create a device
+ // that is so large that it exhausts available memory
+ width = std::min(width, 10000);
+ height = std::min(height, 10000);
+
+ // set device size
+ graphics::device::setSize(width, height);
+ }
+}
+
+void save(Settings* pSettings)
+{
+ // get the client metrics
+ RClientMetrics metrics = client_metrics::get();
+
+ // save them
+ pSettings->beginUpdate();
+ pSettings->set(kConsoleWidth, metrics.consoleWidth);
+ pSettings->set(kGraphicsWidth, metrics.graphicsWidth);
+ pSettings->set(kGraphicsHeight, metrics.graphicsHeight);
+ pSettings->endUpdate();
+}
+
+void restore(const Settings& settings)
+{
+ // read the client metrics (specify defaults to be defensive)
+ RClientMetrics metrics ;
+ metrics.consoleWidth = settings.getInt(kConsoleWidth,
+ r::options::kDefaultWidth);
+
+ metrics.graphicsWidth = settings.getInt(kGraphicsWidth,
+ graphics::device::kDefaultWidth);
+
+ metrics.graphicsHeight = settings.getInt(kGraphicsHeight,
+ graphics::device::kDefaultHeight);
+
+ // set them
+ set(metrics);
+}
+
+
+} // namespace client_metrics
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RClientMetrics.hpp b/src/cpp/r/session/RClientMetrics.hpp
new file mode 100644
index 0000000..c929fe5
--- /dev/null
+++ b/src/cpp/r/session/RClientMetrics.hpp
@@ -0,0 +1,40 @@
+/*
+ * RClientMetrics.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_CLIENT_METRICS_HPP
+#define R_SESSION_CLIENT_METRICS_HPP
+
+namespace core {
+ class Settings;
+}
+
+namespace r {
+namespace session {
+
+struct RClientMetrics;
+
+namespace client_metrics {
+
+RClientMetrics get();
+void set(const RClientMetrics& metrics);
+void save(core::Settings* pSettings);
+void restore(const core::Settings& settings);
+
+} // namespace client_metrics
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_CLIENT_METRICS_HPP
+
diff --git a/src/cpp/r/session/RClientState.cpp b/src/cpp/r/session/RClientState.cpp
new file mode 100644
index 0000000..5ae04bd
--- /dev/null
+++ b/src/cpp/r/session/RClientState.cpp
@@ -0,0 +1,323 @@
+/*
+ * RClientState.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/session/RClientState.hpp>
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+using namespace core;
+
+namespace r {
+namespace session {
+
+namespace {
+
+const char * const kTemporaryExt = ".temporary";
+const char * const kPersistentExt = ".persistent";
+const char * const kProjPersistentExt = ".pper";
+
+void putState(const std::string& scope,
+ const json::Object::value_type& entry,
+ json::Object* pStateContainer)
+{
+ // get the scope object (create if it doesn't exist)
+ json::Object::iterator pos = pStateContainer->find(scope);
+ if (pos == pStateContainer->end())
+ {
+ json::Object newScopeObject;
+ pStateContainer->insert(std::make_pair(scope, newScopeObject));
+ }
+ json::Value& scopeValue = pStateContainer->operator[](scope);
+ json::Object& scopeObject = scopeValue.get_obj();
+
+ // insert the value into the scope
+ scopeObject[entry.first] = entry.second;
+}
+
+void mergeStateScope(const json::Object::value_type& scopePair,
+ json::Object* pTargetState)
+{
+ const std::string& scope = scopePair.first;
+ const json::Value& value = scopePair.second;
+ if ( value.type() == json::ObjectType )
+ {
+ const json::Object& stateObject = value.get_obj();
+ std::for_each(stateObject.begin(),
+ stateObject.end(),
+ boost::bind(putState, scope, _1, pTargetState));
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("set_client_state call sent non json object data");
+ }
+}
+
+
+void mergeState(const json::Object& sourceState,
+ json::Object* pTargetState)
+{
+ std::for_each(sourceState.begin(),
+ sourceState.end(),
+ boost::bind(mergeStateScope, _1, pTargetState));
+}
+
+void commitState(const json::Object& stateContainer,
+ const std::string& fileExt,
+ const core::FilePath& stateDir)
+{
+ for (json::Object::const_iterator
+ it = stateContainer.begin(); it != stateContainer.end(); ++it)
+ {
+ // generate json
+ std::ostringstream ostr ;
+ json::writeFormatted(it->second, ostr);
+
+ // write to file
+ FilePath stateFile = stateDir.complete(it->first + fileExt);
+ Error error = writeStringToFile(stateFile, ostr.str());
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void restoreState(const core::FilePath& stateFilePath,
+ json::Object* pStateContainer)
+{
+ // read the contents of the file
+ std::string contents ;
+ Error error = readStringFromFile(stateFilePath, &contents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // parse the json
+ json::Value value;
+ if ( !json::parse(contents, &value) )
+ {
+ LOG_ERROR_MESSAGE("Error parsing client state");
+ return;
+ }
+
+ // write to the container
+ pStateContainer->insert(std::make_pair(stateFilePath.stem(), value));
+}
+
+Error removeAndRecreateStateDir(const FilePath& stateDir)
+{
+ Error error = stateDir.removeIfExists();
+ if (error)
+ return error;
+ return stateDir.ensureDirectory();
+}
+
+Error restoreStateFiles(const FilePath& sourceDir,
+ boost::function<void(const FilePath&)> restoreFunc)
+{
+ // ignore if the directory doesn't exist
+ if (!sourceDir.exists())
+ return Success();
+
+ // list the files
+ std::vector<FilePath> childPaths ;
+ Error error = sourceDir.children(&childPaths);
+ if (error)
+ return error ;
+
+ // restore files
+ std::for_each(childPaths.begin(), childPaths.end(), restoreFunc);
+ return Success();
+}
+
+void appendAndValidateState(const json::Object& sourceState,
+ json::Object* pTargetState)
+{
+ // append (log warning if there are dupes)
+ for (json::Object::const_iterator it = sourceState.begin();
+ it != sourceState.end();
+ ++it)
+ {
+ if (pTargetState->find(it->first) != pTargetState->end())
+ LOG_WARNING_MESSAGE("duplicate state key: " + it->first);
+ else
+ pTargetState->insert(*it);
+ }
+}
+
+
+} // anonymous namespace
+
+// singleton
+ClientState& clientState()
+{
+ static ClientState instance;
+ return instance;
+}
+
+
+ClientState::ClientState()
+{
+}
+
+void ClientState::restoreGlobalState(const FilePath& stateFile)
+{
+ if (stateFile.extension() == kTemporaryExt)
+ restoreState(stateFile, &temporaryState_);
+ else if (stateFile.extension() == kPersistentExt)
+ restoreState(stateFile, &persistentState_);
+}
+
+void ClientState::restoreProjectState(const FilePath& stateFile)
+{
+ if (stateFile.extension() == kProjPersistentExt)
+ restoreState(stateFile, &projectPersistentState_);
+}
+
+void ClientState::clear()
+{
+ temporaryState_.clear();
+ persistentState_.clear();
+ projectPersistentState_.clear();
+}
+
+void ClientState::putTemporary(const std::string& scope,
+ const std::string& name,
+ const json::Value& value)
+{
+ json::Object stateContainer ;
+ putState(scope, std::make_pair(name, value), &stateContainer);
+ putTemporary(stateContainer);
+}
+
+void ClientState::putTemporary(const json::Object& temporaryState)
+{
+ mergeState(temporaryState, &temporaryState_);
+}
+
+void ClientState::putPersistent(const std::string& scope,
+ const std::string& name,
+ const json::Value& value)
+{
+ json::Object stateContainer;
+ putState(scope, std::make_pair(name, value), &stateContainer);
+ putPersistent(stateContainer);
+}
+
+void ClientState::putPersistent(const json::Object& persistentState)
+{
+ mergeState(persistentState, &persistentState_);
+}
+
+void ClientState::putProjectPersistent(const std::string& scope,
+ const std::string& name,
+ const json::Value& value)
+{
+ json::Object stateContainer;
+ putState(scope, std::make_pair(name, value), &stateContainer);
+ putProjectPersistent(stateContainer);
+}
+
+json::Value ClientState::getProjectPersistent(std::string scope,
+ std::string name)
+{
+ json::Object::iterator i = projectPersistentState_.find(scope);
+ if (i == projectPersistentState_.end())
+ {
+ return json::Value();
+ }
+ else
+ {
+ if (!json::isType<core::json::Object>(i->second))
+ return json::Value();
+ json::Object& scopeObject = (i->second).get_obj();
+ return scopeObject[name];
+ }
+}
+
+void ClientState::putProjectPersistent(
+ const json::Object& projectPersistentState)
+{
+ mergeState(projectPersistentState, &projectPersistentState_);
+}
+
+
+Error ClientState::commit(ClientStateCommitType commitType,
+ const core::FilePath& stateDir,
+ const core::FilePath& projectStateDir)
+{
+ // remove and re-create the stateDirs
+ Error error = removeAndRecreateStateDir(stateDir);
+ if (error)
+ return error;
+ error = removeAndRecreateStateDir(projectStateDir);
+ if (error)
+ return error;
+
+ // always commit persistent state
+ commitState(persistentState_, kPersistentExt, stateDir);
+ commitState(projectPersistentState_, kProjPersistentExt, projectStateDir);
+
+ // commit all state if requested
+ if (commitType == ClientStateCommitAll)
+ commitState(temporaryState_, kTemporaryExt, stateDir);
+ else
+ temporaryState_.clear();
+
+ return Success();
+}
+
+Error ClientState::restore(const FilePath& stateDir,
+ const FilePath& projectStateDir)
+{
+ // clear existing values
+ clear();
+
+ // restore global state
+ Error error = restoreStateFiles(
+ stateDir,
+ boost::bind(&ClientState::restoreGlobalState, this, _1));
+ if (error)
+ return error;
+
+ // restore project state
+ return restoreStateFiles(
+ projectStateDir,
+ boost::bind(&ClientState::restoreProjectState, this, _1));
+}
+
+// generate current state by merging temporary and persistent states
+void ClientState::currentState(json::Object* pCurrentState) const
+{
+ // start with copy of persistent state
+ pCurrentState->clear();
+ pCurrentState->insert(persistentState_.begin(), persistentState_.end());
+
+ // add and validate other state collections
+ appendAndValidateState(projectPersistentState_, pCurrentState);
+ appendAndValidateState(temporaryState_, pCurrentState);
+}
+
+} // namespace session
+} // namespace r
+
diff --git a/src/cpp/r/session/RConsoleActions.cpp b/src/cpp/r/session/RConsoleActions.cpp
new file mode 100644
index 0000000..efc6406
--- /dev/null
+++ b/src/cpp/r/session/RConsoleActions.cpp
@@ -0,0 +1,233 @@
+/*
+ * RConsoleActions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/session/RConsoleActions.hpp>
+
+#include <algorithm>
+
+#include <boost/algorithm/string/split.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/Thread.hpp>
+
+#include <r/ROptions.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace session {
+
+namespace {
+const char * const kActionType = "type";
+const char * const kActionData = "data";
+}
+
+ConsoleActions& consoleActions()
+{
+ static ConsoleActions instance;
+ return instance;
+}
+
+ConsoleActions::ConsoleActions()
+{
+ setCapacity(1000);
+}
+
+int ConsoleActions::capacity() const
+{
+ LOCK_MUTEX(mutex_)
+ {
+ return actionsType_.capacity();
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return 0;
+}
+
+void ConsoleActions::setCapacity(int capacity)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ actionsType_.set_capacity(capacity);
+ actionsData_.set_capacity(capacity);
+ }
+ END_LOCK_MUTEX
+}
+
+void ConsoleActions::add(int type, const std::string& data)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ // manage pending input buffer
+ if (type == kConsoleActionPrompt &&
+ data == r::options::getOption<std::string>("prompt"))
+ {
+ pendingInput_.clear();
+ }
+ else if (type == kConsoleActionInput)
+ {
+ std::vector<std::string> input;
+ boost::algorithm::split(input,
+ data,
+ boost::algorithm::is_any_of("\n"));
+ pendingInput_.insert(pendingInput_.end(), input.begin(), input.end());
+ }
+
+
+ // automatically combine consecutive output actions (up to 512 bytes)
+ // we enforce a limit so that the limit defined for our circular buffer
+ // (see setCapacity above) implies a content size limit as well (if we
+ // didn't cap the size of combined output then the output actions could
+ // grow to arbitrary size)
+ if (type == kConsoleActionOutput &&
+ actionsType_.size() > 0 &&
+ actionsType_.back().get_value<int>() == kConsoleActionOutput &&
+ actionsData_.back().get_str().size() < 512)
+ {
+ actionsData_.back() = actionsData_.back().get_str() + data;
+ }
+ else
+ {
+ actionsType_.push_back(type);
+ actionsData_.push_back(data);
+ }
+ }
+ END_LOCK_MUTEX
+}
+
+void ConsoleActions::notifyInterrupt()
+{
+ LOCK_MUTEX(mutex_)
+ {
+ pendingInput_.clear();
+ }
+ END_LOCK_MUTEX
+}
+
+void ConsoleActions::reset()
+{
+ LOCK_MUTEX(mutex_)
+ {
+ // clear the existing actions
+ actionsType_.clear();
+ actionsData_.clear();
+ }
+ END_LOCK_MUTEX
+}
+
+void ConsoleActions::asJson(json::Object* pActions) const
+{
+ LOCK_MUTEX(mutex_)
+ {
+ // clear inbound
+ pActions->clear();
+
+ // copy actions and insert into destination
+ json::Array actionsType;
+ std::copy(actionsType_.begin(),
+ actionsType_.end(),
+ std::back_inserter(actionsType));
+ pActions->operator[](kActionType) = actionsType;
+
+ // copy data and insert into destination
+ json::Array actionsData;
+ std::copy(actionsData_.begin(),
+ actionsData_.end(),
+ std::back_inserter(actionsData));
+ pActions->operator[](kActionData) = actionsData;
+ }
+ END_LOCK_MUTEX
+}
+
+Error ConsoleActions::loadFromFile(const FilePath& filePath)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ actionsType_.clear();
+ actionsData_.clear();
+
+ if (filePath.exists())
+ {
+ // read from file
+ std::string actionsJson ;
+ Error error = readStringFromFile(filePath, &actionsJson);
+ if (error)
+ return error ;
+
+ // parse json and confirm it contains an object
+ json::Value value;
+ if ( json::parse(actionsJson, &value) &&
+ (value.type() == json::ObjectType) )
+ {
+ json::Object& actions = value.get_obj();
+
+ const json::Value& typeValue = actions[kActionType] ;
+ if (typeValue.type() == json::ArrayType)
+ {
+ const json::Array& actionsType = typeValue.get_array();
+ std::copy(actionsType.begin(),
+ actionsType.end(),
+ std::back_inserter(actionsType_));
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
+ }
+
+ json::Value& dataValue = actions[kActionData] ;
+ if ( dataValue.type() == json::ArrayType )
+ {
+ const json::Array& actionsData = dataValue.get_array();
+ std::copy(actionsData.begin(),
+ actionsData.end(),
+ std::back_inserter(actionsData_));
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
+ }
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
+ }
+ }
+ }
+ END_LOCK_MUTEX
+
+ return Success();
+}
+
+Error ConsoleActions::saveToFile(const core::FilePath& filePath) const
+{
+ // write actions
+ json::Object actionsObject;
+ asJson(&actionsObject);
+ std::ostringstream ostr ;
+ json::writeFormatted(actionsObject, ostr);
+
+ // write to file
+ return writeStringToFile(filePath, ostr.str());
+}
+
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RConsoleHistory.cpp b/src/cpp/r/session/RConsoleHistory.cpp
new file mode 100644
index 0000000..1e06b6e
--- /dev/null
+++ b/src/cpp/r/session/RConsoleHistory.cpp
@@ -0,0 +1,191 @@
+/*
+ * RConsoleHistory.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/session/RConsoleHistory.hpp>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/tokenizer.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include <core/SafeConvert.hpp>
+
+using namespace core;
+
+namespace r {
+namespace session {
+
+ConsoleHistory& consoleHistory()
+{
+ static ConsoleHistory instance ;
+ return instance ;
+}
+
+ConsoleHistory::ConsoleHistory()
+ : removeDuplicates_(true)
+{
+ setCapacity(512);
+}
+
+void ConsoleHistory::setCapacity(int capacity)
+{
+ historyBuffer_.set_capacity(capacity);
+}
+
+void ConsoleHistory::setCapacityFromRHistsize()
+{
+ std::string histSize = core::system::getenv("R_HISTSIZE");
+ if (!histSize.empty())
+ {
+ setCapacity(
+ safe_convert::stringTo<std::size_t>(histSize, capacity()));
+ }
+}
+
+void ConsoleHistory::setRemoveDuplicates(bool removeDuplicates)
+{
+ removeDuplicates_ = removeDuplicates;
+}
+
+void ConsoleHistory::add(const std::string& command)
+{
+ if (!command.empty())
+ {
+ // split input into list of commands
+ boost::char_separator<char> lineSep("\n");
+ boost::tokenizer<boost::char_separator<char> > lines(command, lineSep);
+ for (boost::tokenizer<boost::char_separator<char> >::iterator
+ lineIter = lines.begin();
+ lineIter != lines.end();
+ ++lineIter)
+ {
+ // get line (skip empty lines)
+ std::string line(*lineIter);
+ boost::algorithm::trim(line);
+ if (line.empty())
+ continue;
+
+ // add this line if its not a duplciate
+ if (!removeDuplicates_ ||
+ historyBuffer_.empty() ||
+ (line != historyBuffer_.back()))
+ {
+ // add to buffer
+ historyBuffer_.push_back(line);
+
+ // notify listeners
+ onAdd_(line);
+ }
+ }
+ }
+}
+
+void ConsoleHistory::clear()
+{
+ historyBuffer_.clear();
+}
+
+
+void ConsoleHistory::remove(const std::vector<int>& indexes)
+{
+ // make a copy and sort descending
+ std::vector<int> sortedIndexes;
+ std::copy(indexes.begin(),
+ indexes.end(),
+ std::back_inserter(sortedIndexes));
+ std::sort(sortedIndexes.begin(), sortedIndexes.end(),std::greater<int>());
+
+ // remove them all
+ std::for_each(sortedIndexes.begin(),
+ sortedIndexes.end(),
+ boost::bind(&ConsoleHistory::safeRemove, this, _1));
+}
+
+void ConsoleHistory::subset(int beginIndex, // inclusive
+ int endIndex, // exclusive,
+ std::vector<std::string>* pEntries) const
+{
+ // clear existing
+ pEntries->clear();
+
+ // bail if begin index exceeds our number of entries
+ if (beginIndex >= size())
+ return;
+
+ // cap end index at our size
+ endIndex = std::min(endIndex, size());
+
+ // copy
+ std::copy(historyBuffer_.begin() + beginIndex,
+ historyBuffer_.begin() + endIndex,
+ std::back_inserter(*pEntries));
+}
+
+
+
+void ConsoleHistory::asJson(json::Array* pHistoryArray) const
+{
+ const_iterator it = begin();
+ while(it != end())
+ pHistoryArray->push_back(*it++);
+}
+
+Error ConsoleHistory::loadFromFile(const FilePath& filePath,
+ bool verifyFile)
+{
+ historyBuffer_.clear();
+
+ // tolerate file not found -- the user may not have any prior history
+ if (filePath.exists())
+ {
+ return core::readCollectionFromFile<boost::circular_buffer<std::string> >(
+ filePath,
+ &historyBuffer_,
+ core::parseString);
+ }
+ else if (verifyFile)
+ {
+ return systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error ConsoleHistory::saveToFile(const FilePath& filePath) const
+{
+ return core::writeCollectionToFile<boost::circular_buffer<std::string> >(
+ filePath,
+ historyBuffer_,
+ core::stringifyString);
+}
+
+void ConsoleHistory::safeRemove(int index)
+{
+ if (index >= 0 && index < (int)historyBuffer_.size())
+ historyBuffer_.erase(historyBuffer_.begin() + index);
+}
+
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RDiscovery.cpp b/src/cpp/r/session/RDiscovery.cpp
new file mode 100644
index 0000000..2f66f1d
--- /dev/null
+++ b/src/cpp/r/session/RDiscovery.cpp
@@ -0,0 +1,84 @@
+/*
+ * RDiscovery.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/session/RDiscovery.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <r/RErrorCategory.hpp>
+
+#ifdef _WIN32
+#define Win32
+#endif
+#include <Rembedded.h>
+
+using namespace core;
+
+namespace r {
+namespace session {
+
+// probe registry for windows
+#ifdef _WIN32
+
+Error discoverR(RLocations* pLocations)
+{
+ // query R for the home path
+ const char* lpszRHome = ::get_R_HOME();
+ if (lpszRHome == NULL)
+ return Error(errc::RHomeNotFound, ERROR_LOCATION);
+
+ // set paths
+ FilePath rHome(lpszRHome);
+ pLocations->homePath = rHome.absolutePath();
+ pLocations->docPath = rHome.complete("doc").absolutePath();
+
+ return Success();
+}
+
+// Use environment variables for unix & osx
+#else
+
+Error discoverR(RLocations* pLocations)
+{
+ // rhome
+ std::string rHome = core::system::getenv("R_HOME");
+ if (rHome.empty() || !FilePath(rHome).exists())
+ return core::pathNotFoundError(rHome, ERROR_LOCATION);
+ else
+ pLocations->homePath = rHome;
+
+ // rdocdir
+ std::string rDocDir = core::system::getenv("R_DOC_DIR");
+ if (rDocDir.empty() || !FilePath(rDocDir).exists())
+ return core::pathNotFoundError(rDocDir, ERROR_LOCATION);
+ else
+ pLocations->docPath = rDocDir;
+
+ return Success();
+}
+
+#endif
+
+} // namespace session
+} // namespace r
+
+
+
+
+
+
diff --git a/src/cpp/r/session/REmbedded.hpp b/src/cpp/r/session/REmbedded.hpp
new file mode 100644
index 0000000..21c629d
--- /dev/null
+++ b/src/cpp/r/session/REmbedded.hpp
@@ -0,0 +1,79 @@
+/*
+ * REmbedded.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_EMBEDDED_HPP
+#define R_EMBEDDED_HPP
+
+#include <string>
+
+typedef struct SEXPREC *SEXP;
+
+#include <R_ext/Boolean.h>
+#include <R_ext/RStartup.h>
+
+#ifdef _WIN32
+typedef char CONSOLE_BUFFER_CHAR;
+#else
+typedef unsigned char CONSOLE_BUFFER_CHAR;
+#endif
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+
+struct Callbacks
+{
+ void (*showMessage)(const char*);
+ int (*readConsole)(const char *, CONSOLE_BUFFER_CHAR*, int, int);
+ void (*writeConsoleEx)(const char *, int, int);
+ int (*editFile)(const char*);
+ void (*busy)(int);
+ int (*chooseFile)(int, char *, int);
+ int (*showFiles)(int, const char **, const char **, const char *,
+ Rboolean, const char *);
+ void (*loadhistory)(SEXP, SEXP, SEXP, SEXP);
+ void (*savehistory)(SEXP, SEXP, SEXP, SEXP);
+ void (*addhistory)(SEXP, SEXP, SEXP, SEXP);
+ void (*suicide)(const char*);
+ void (*cleanUp)(SA_TYPE, int, int);
+};
+
+struct InternalCallbacks
+{
+ InternalCallbacks() : suicide(NULL), cleanUp(NULL) {}
+ void (*suicide)(const char*);
+ void (*cleanUp)(SA_TYPE, int, int);
+};
+
+void runEmbeddedR(const core::FilePath& rHome,
+ const core::FilePath& userHome,
+ bool quiet,
+ bool loadInitFile,
+ SA_TYPE defaultSaveAction,
+ const Callbacks& callbacks,
+ InternalCallbacks* pInternal);
+
+core::Error completeEmbeddedRInitialization(bool useInternet2);
+
+} // namespace session
+} // namespace r
+
+
+#endif // R_EMBEDDED_HPP
+
diff --git a/src/cpp/r/session/REmbeddedPosix.cpp b/src/cpp/r/session/REmbeddedPosix.cpp
new file mode 100644
index 0000000..4afd5dc
--- /dev/null
+++ b/src/cpp/r/session/REmbeddedPosix.cpp
@@ -0,0 +1,374 @@
+/*
+ * REmbeddedPosix.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RExec.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+
+// after boost stuff to prevent length (Rf_length) symbol conflict issues
+#include "REmbedded.hpp"
+#include <r/RInterface.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RUtil.hpp>
+
+#include <R_ext/eventloop.h>
+
+#include <Rembedded.h>
+
+#ifdef __APPLE__
+#include <dlfcn.h>
+extern "C" void R_ProcessEvents(void);
+extern "C" void (*ptr_R_ProcessEvents)(void);
+#define QCF_SET_PEPTR 1 /* set ProcessEvents function pointer */
+#define QCF_SET_FRONT 2 /* set application mode to front */
+extern "C" typedef void (*ptr_QuartzCocoa_SetupEventLoop)(int, unsigned long);
+#endif
+
+extern int R_running_as_main_program; // from unix/system.c
+
+using namespace core;
+
+namespace r {
+namespace session {
+
+void runEmbeddedR(const core::FilePath& /*rHome*/, // ignored on posix
+ const core::FilePath& /*userHome*/, // ignored on posix
+ bool quiet,
+ bool loadInitFile,
+ SA_TYPE defaultSaveAction,
+ const Callbacks& callbacks,
+ InternalCallbacks* pInternal)
+{
+ // disable R signal handlers. see src/main/main.c for the default
+ // implementations. in our case ignore them for the following reasons:
+ //
+ // INT - no concept of Ctrl-C based interruption (use flag directly)
+ //
+ // SEGV, ILL, & BUS: unsupported due to prompt invoking networking
+ // code (unsupported from within a signal handler)
+ //
+ // USR1 & USR2: same as above SEGV, etc. + we use them for other purposes
+ //
+ // PIPE: we ignore this globally in SessionMain. before doing this we
+ // confirmed that asio wasn't in some way manipulating it -- on linux
+ // boost passes MSG_NOSIGNAL to sendmsg and on OSX sets the SO_NOSIGPIPE
+ // option on all sockets created. note that on other platforms including
+ // solaris, hpux, etc. boost uses detail/signal_init to ignore SIGPIPE
+
+ // globally (this is done in io_service.hpp).
+ R_SignalHandlers = 0;
+
+ // set message callback early so we can see initialization error messages
+ ptr_R_ShowMessage = callbacks.showMessage ;
+
+ // running as main program (affects location of R_CStackStart on platforms
+ // without HAVE_LIBC_STACK_END or HAVE_KERN_USRSTACK). see also discussion
+ // on R_CStackStart in 8.1.5 Threading issues
+ R_running_as_main_program = 1;
+
+ // initialize R
+ const char *args[]= {"RStudio", "--interactive"};
+ Rf_initialize_R(sizeof(args)/sizeof(args[0]), (char**)args);
+
+ // For newSession = false we need to do a few things:
+ //
+ // 1) set R_Quiet so we startup without a banner
+ //
+ // 2) set LoadInitFile to supress execution of .Rprofile
+ //
+ // 3) we also need to make sure that .First is not executed. this is
+ // taken care of via the fact that we set RestoreAction to SA_NORESTORE
+ // which means that when setup_Rmainloop there is no .First function
+ // available to it because we haven't restored the environment yet.
+ // Note that .First is executed in the case of new sessions because
+ // it is read from .Rprofile as part of setup_Rmainloop. This implies
+ // that in our version of R the .First function must be defined in
+ // .Rprofile rather than simply saved into the global environment
+ // of the default workspace
+ //
+ structRstart rp;
+ Rstart Rp = &rp;
+ R_DefParams(Rp) ;
+ Rp->R_Slave = FALSE ;
+ Rp->R_Quiet = quiet ? TRUE : FALSE;
+ Rp->R_Interactive = TRUE ;
+ Rp->SaveAction = defaultSaveAction ;
+ Rp->RestoreAction = SA_NORESTORE; // handled within initialize()
+ Rp->LoadInitFile = loadInitFile ? TRUE : FALSE;
+ R_SetParams(Rp) ;
+
+ // redirect console
+ R_Interactive = TRUE; // should have also been set by call to Rf_initialize_R
+ R_Consolefile = NULL;
+ R_Outputfile = NULL;
+ ptr_R_ReadConsole = callbacks.readConsole ;
+ ptr_R_WriteConsole = NULL; // must set this to NULL for Ex to be called
+ ptr_R_WriteConsoleEx = callbacks.writeConsoleEx ;
+ ptr_R_EditFile = callbacks.editFile ;
+ ptr_R_Busy = callbacks.busy;
+
+ // hook messages (in case Rf_initialize_R overwrites previously set hook)
+ ptr_R_ShowMessage = callbacks.showMessage ;
+
+ // hook file handling
+ ptr_R_ChooseFile = callbacks.chooseFile ;
+ ptr_R_ShowFiles = callbacks.showFiles ;
+
+ // hook history
+ ptr_R_loadhistory = callbacks.loadhistory;
+ ptr_R_savehistory = callbacks.savehistory;
+ ptr_R_addhistory = callbacks.addhistory;
+
+ // hook suicide, but save reference to internal suicide so we can forward
+ pInternal->suicide = ptr_R_Suicide;
+ ptr_R_Suicide = callbacks.suicide;
+
+ // hook clean up, but save reference to internal clean up so can forward
+ pInternal->cleanUp = ptr_R_CleanUp;
+ ptr_R_CleanUp = callbacks.cleanUp ;
+
+ // NOTE: we do not hook the following callbacks because they are targeted
+ // at clients that have a stdio-based console
+ // ptr_R_ResetConsole
+ // ptr_R_FlushConsole
+ // ptr_R_ClearerrConsole
+
+ // run main loop (does not return)
+ Rf_mainloop();
+}
+
+Error completeEmbeddedRInitialization(bool useInternet2)
+{
+ return Success();
+}
+
+namespace event_loop {
+
+namespace {
+
+// currently installed polled event handler
+void (*s_polledEventHandler)(void) = NULL;
+
+// previously existing polled event handler
+void (*s_oldPolledEventHandler)(void) = NULL;
+
+// function we register with R to implement polled event handler
+void polledEventHandler()
+{
+ if (s_polledEventHandler != NULL)
+ s_polledEventHandler();
+
+ if (s_oldPolledEventHandler != NULL)
+ s_oldPolledEventHandler();
+}
+
+
+#ifdef __APPLE__
+
+void logDLError(const std::string& message, const ErrorLocation& location)
+{
+ std::string errmsg(message);
+ char* dlError = ::dlerror();
+ if (dlError)
+ errmsg += ": " + std::string(dlError);
+ core::log::logErrorMessage(errmsg, location);
+}
+
+// Note that when we passed QCF_SET_FRONT to QuartzCocoa_SetupEventLoop
+// sometimes this resulted in our application having a "bouncing"
+// state which we couldn't rid ourselves of.
+//
+// Note that in researching the way R implements QCF_SET_FRONT I discovered
+// that a depricated API is called AND an explicit call to SetFront. Another
+// way to go would be to call the TransformProcessType API:
+//
+// http://www.cocoadev.com/index.pl?TransformProcessType
+// http://developer.apple.com/library/mac/#documentation/Carbon/Reference/Process_Manager/Reference/reference.html%23//apple_ref/c/func/TransformProcessType
+//
+// Note this would look something like (cmake and includes for completeness):
+/*
+ find_library(CARBON_LIBRARY NAMES Carbon)
+ set(LINK_FLAGS ${CARBON_LIBRARY})
+
+ #include <Carbon/Carbon.h>
+ #undef TRUE
+ #undef FALSE
+
+ static const ProcessSerialNumber thePSN = { 0, kCurrentProcess };
+ ::TransformProcessType(&thePSN, kProcessTransformToForegroundApplication);
+*/
+
+// attempt to setup quartz event loop, if this fails then log and
+// return false (as a result we'll have to disable the quartz R
+// function so the user doesn't get in trouble)
+bool setupQuartzEventLoop()
+{
+ // first make sure that the gdDevices pacakage is loaded
+ Error error = r::exec::executeString("library(grDevices)");
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // get a reference to the grDevices library
+ void* pGrDevices = ::dlopen("grDevices.so",
+ RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+ if (pGrDevices)
+ {
+ ptr_QuartzCocoa_SetupEventLoop pSetupEventLoop =
+ (ptr_QuartzCocoa_SetupEventLoop)::dlsym(
+ pGrDevices,
+ "QuartzCocoa_SetupEventLoop");
+ if (pSetupEventLoop)
+ {
+ // attempt to setup event loop
+ pSetupEventLoop(QCF_SET_PEPTR, 100);
+
+ // check that we got the ptr_R_ProcessEvents initialized
+ if (ptr_R_ProcessEvents != NULL)
+ {
+ return true;
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("ptr_R_ProcessEvents not initialized");
+ return false;
+ }
+ }
+ else
+ {
+ logDLError("Error looking up QuartzCocoa_SetupEventLoop",
+ ERROR_LOCATION);
+ return false;
+ }
+ }
+ else
+ {
+ logDLError("Error loading grDevices.so", ERROR_LOCATION);
+ return false;
+ }
+}
+
+// On versions prior to R 2.12 the event pump is handled by R_ProcessEvents
+// rather than by the expected R_PolledEvents mechanism. On the Mac
+// R_ProcessEvents includes a hook (ptr_R_ProcessEvents) but this is
+// taken by the quartz module. We therefore need a way to hook it but
+// still delegate to quartz so the quartz device works. do this by
+// ensuring quartz is loaded then calling QuartzCocoa_SetupEventLoop
+void installAppleR_2_11_Workaround(void (*newPolledEventHandler)(void))
+{
+ // attempt to initialize the quartz event loop (init ptr_R_ProcessEvents
+ // so that we can delegate to it after we override it)
+ if (!setupQuartzEventLoop())
+ {
+ Error error = r::exec::RFunction(".rs.disableQuartz").call();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // copy handler function
+ s_polledEventHandler = newPolledEventHandler;
+
+ // preserve old handler and set new one (note that ptr_R_ProcessEvents
+ // might be NULL if we didn't succeed in setting up the quartz
+ // event loop above. in this case the polled event handler will
+ // ignore it
+ s_oldPolledEventHandler = ptr_R_ProcessEvents;
+ ptr_R_ProcessEvents = polledEventHandler;
+}
+
+#endif
+
+} // anonymous namespace
+
+
+void initializePolledEventHandler(void (*newPolledEventHandler)(void))
+{
+ // can only call this once
+ BOOST_ASSERT(!s_polledEventHandler);
+
+ // special hack for R 2.11.1 on OSX
+#ifdef __APPLE__
+ if (!r::util::hasRequiredVersion("2.12"))
+ {
+ installAppleR_2_11_Workaround(newPolledEventHandler);
+ return;
+ }
+#endif
+
+ // implementation based on addTcl() in tcltk_unix.c
+
+ // copy handler function
+ s_polledEventHandler = newPolledEventHandler;
+
+ // preserve old handler and set new one
+ s_oldPolledEventHandler = R_PolledEvents;
+ R_PolledEvents = polledEventHandler;
+
+ // set R_wait_usec
+ if (R_wait_usec > 10000 || R_wait_usec == 0)
+ R_wait_usec = 10000;
+}
+
+// NOTE: this call is used in child process after multicore forks
+// to make sure all subsequent R code is executed without any
+// event handlers (appropriate since the forked child is headless).
+// the prefix "permanently" is used because we explicitly don't
+// handle the abilty to restore event handling by calling
+// initializePolledEventHandler -- this is because we overwrite
+// s_oldPolledEventHandler with NULL, thus losing any reference
+// we have to a R_PolledEvents value that existed before our
+// initialization (it would be possible to implement a temporary
+// disable with a bit more complex control flow)
+void permanentlyDisablePolledEventHandler()
+{
+ s_polledEventHandler = NULL;
+ s_oldPolledEventHandler = NULL;
+}
+
+bool polledEventHandlerInitialized()
+{
+ return s_polledEventHandler != NULL;
+}
+
+void processEvents()
+{
+#ifdef __APPLE__
+ R_ProcessEvents();
+
+ // pickup X11 graphics device events (if any) via X11 input handler
+ fd_set* what = R_checkActivity(0,1);
+ if (what != NULL)
+ R_runHandlers(R_InputHandlers, what);
+#else
+ // check for activity on standard input handlers (but ignore stdin).
+ // return immediately if there is no input currently available
+ fd_set* what = R_checkActivity(0,1);
+
+ // run handlers on the input (or run the polled event handler if there
+ // is no input currently available)
+ R_runHandlers(R_InputHandlers, what);
+#endif
+}
+
+} // namespace event_loop
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/REmbeddedWin32.cpp b/src/cpp/r/session/REmbeddedWin32.cpp
new file mode 100644
index 0000000..28ce63b
--- /dev/null
+++ b/src/cpp/r/session/REmbeddedWin32.cpp
@@ -0,0 +1,269 @@
+/*
+ * REmbeddedWin32.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <windows.h>
+#undef TRUE
+#undef FALSE
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RInternal.hpp>
+
+#define Win32
+#include "REmbedded.hpp"
+
+#include <stdio.h>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+
+#include <r/RInterface.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/RExec.hpp>
+#include <r/session/REventLoop.hpp>
+
+#include <Rembedded.h>
+#include <graphapp.h>
+
+extern "C" void R_ProcessEvents(void);
+extern "C" void R_CleanUp(SA_TYPE, int, int);
+extern "C" UImode CharacterMode;
+
+using namespace core;
+
+namespace r {
+namespace session {
+
+namespace {
+
+void (*s_polledEventHandler)(void) = NULL;
+void rPolledEventCallback()
+{
+ if (s_polledEventHandler != NULL)
+ s_polledEventHandler();
+}
+
+void showMessage(const char *info)
+{
+ ::MessageBoxA(::GetForegroundWindow(),
+ info,
+ "R Message",
+ MB_ICONEXCLAMATION | MB_OK);
+}
+
+#define YES 1
+#define NO -1
+#define CANCEL 0
+int askYesNoCancel(const char* question)
+{
+ if (!question)
+ question = "";
+
+ int result = ::MessageBoxA(::GetForegroundWindow(),
+ question,
+ "Question",
+ MB_ICONQUESTION | MB_YESNOCANCEL);
+
+ switch(result)
+ {
+ case IDYES:
+ return YES;
+ case IDNO:
+ return NO;
+ case IDCANCEL:
+ return CANCEL;
+ }
+}
+
+void setMemoryLimit()
+{
+ // set defaults for R_max_memory. this code is based on similar code
+ // in cmdlineoptions in system.c (but calls memory.limit directly rather
+ // than setting R_max_memory directly, which we can't do because it
+ // isn't exported from the R.dll
+
+ // some constants
+ const DWORDLONG MB_TO_BYTES = 1024 * 1024;
+ const DWORDLONG VIRTUAL_OFFSET = 512 * MB_TO_BYTES;
+
+ // interograte physical and virtual memory
+ MEMORYSTATUSEX memoryStatus;
+ memoryStatus.dwLength = sizeof(memoryStatus);
+ ::GlobalMemoryStatusEx(&memoryStatus);
+ DWORDLONG virtualMemory = memoryStatus.ullTotalVirtual - VIRTUAL_OFFSET;
+ DWORDLONG physicalMem = memoryStatus.ullTotalPhys;
+
+ // use physical memory on win64. on win32 further constrain by
+ // virtual memory minus an offset (for the os and other programs)
+ #ifdef WIN64
+ DWORDLONG maxMemory = physicalMem;
+ #else
+ DWORDLONG maxMemory = std::min(virtualMemory, physicalMem);
+ #endif
+
+ // call the memory.limit function
+ maxMemory = maxMemory / MB_TO_BYTES;
+ r::exec::RFunction memoryLimit(".rs.setMemoryLimit");
+ memoryLimit.addParam((double)maxMemory);
+ Error error = memoryLimit.call();
+ if (error)
+ LOG_ERROR(error);
+}
+
+}
+
+void runEmbeddedR(const core::FilePath& rHome,
+ const core::FilePath& userHome,
+ bool quiet,
+ bool loadInitFile,
+ SA_TYPE defaultSaveAction,
+ const Callbacks& callbacks,
+ InternalCallbacks* pInternal)
+{
+ // no signal handlers (see comment in REmbeddedPosix.cpp for rationale)
+ R_SignalHandlers = 0;
+
+ // set start time
+ ::R_setStartTime();
+
+ // setup params structure
+ structRstart rp;
+ Rstart pRP = &rp;
+ ::R_DefParams(pRP);
+
+ // set paths (copy to new string so we can provide char*)
+ std::string* pRHome = new std::string(rHome.absolutePath());
+ std::string* pUserHome = new std::string(userHome.absolutePath());
+ pRP->rhome = const_cast<char*>(pRHome->c_str());
+ pRP->home = const_cast<char*>(pUserHome->c_str());
+
+ // more configuration
+ pRP->CharacterMode = RGui;
+ pRP->R_Slave = FALSE;
+ pRP->R_Quiet = quiet ? TRUE : FALSE;
+ pRP->R_Interactive = TRUE;
+ pRP->SaveAction = defaultSaveAction;
+ pRP->RestoreAction = SA_NORESTORE;
+ pRP->LoadInitFile = loadInitFile ? TRUE : FALSE;
+
+ // hooks
+ pRP->ReadConsole = callbacks.readConsole;
+ pRP->WriteConsole = NULL;
+ pRP->WriteConsoleEx = callbacks.writeConsoleEx;
+ pRP->CallBack = rPolledEventCallback;
+ pRP->ShowMessage = showMessage;
+ pRP->YesNoCancel = askYesNoCancel;
+ pRP->Busy = callbacks.busy;
+
+ // set internal callbacks
+ pInternal->cleanUp = R_CleanUp;
+ pInternal->suicide = R_Suicide;
+
+ // set command line
+ const char *args[]= {"RStudio", "--interactive"};
+ int argc = sizeof(args)/sizeof(args[0]);
+ ::R_set_command_line_arguments(argc, (char**)args);
+
+ // set params
+ ::R_SetParams(pRP);
+
+ // clear console input buffer
+ ::FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));
+
+ // R global ui initialization
+ ::GA_initapp(0, 0);
+ ::readconsolecfg();
+
+ // Set CharacterMode to LinkDLL during main loop setup. The mode can't be
+ // RGui during setup_Rmainloop or calls to history functions (e.g. timestamp)
+ // which occur during .Rprofile execution will crash when R attempts to
+ // interact with the (non-existent) R gui console data structures. Note that
+ // there are no references to CharacterMode within setup_Rmainloop that we
+ // can see so the only side effect of this should be the disabling of the
+ // console history mechanism.
+ CharacterMode = LinkDLL;
+
+ // setup main loop
+ ::setup_Rmainloop();
+
+ // reset character mode to RGui
+ CharacterMode = RGui;
+
+ // run main loop
+ ::run_Rmainloop();
+}
+
+Error completeEmbeddedRInitialization(bool useInternet2)
+{
+ // set memory limit
+ setMemoryLimit();
+
+ // use IE proxy settings if requested
+ boost::format fmt("suppressWarnings(utils::setInternet2(%1%))");
+ Error error = r::exec::executeString(boost::str(fmt % useInternet2));
+ if (error)
+ LOG_ERROR(error);
+
+
+ // register history functions
+ error = r::exec::RFunction(".rs.registerHistoryFunctions").call();
+ if (error)
+ LOG_ERROR(error);
+
+ using boost::bind;
+ using namespace r::function_hook ;
+ ExecBlock block ;
+ block.addFunctions()
+ (bind(registerUnsupported, "winMenuAdd", "utils"))
+ (bind(registerUnsupported, "winMenuAddItem", "utils"))
+ (bind(registerUnsupported, "winMenuDel", "utils"))
+ (bind(registerUnsupported, "winMenuDelItem", "utils"))
+ (bind(registerUnsupported, "winMenuNames", "utils"))
+ (bind(registerUnsupported, "winMenuItems", "utils"));
+ return block.execute();
+}
+
+namespace event_loop {
+
+void initializePolledEventHandler(void (*newPolledEventHandler)(void))
+{
+ s_polledEventHandler = newPolledEventHandler;
+}
+
+void permanentlyDisablePolledEventHandler()
+{
+ s_polledEventHandler = NULL;
+}
+
+
+bool polledEventHandlerInitialized()
+{
+ return s_polledEventHandler != NULL;
+}
+
+void processEvents()
+{
+ R_ProcessEvents();
+}
+
+} // namespace event_loop
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RRestartContext.cpp b/src/cpp/r/session/RRestartContext.cpp
new file mode 100644
index 0000000..7150b8b
--- /dev/null
+++ b/src/cpp/r/session/RRestartContext.cpp
@@ -0,0 +1,110 @@
+/*
+ * RRestartContext.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RRestartContext.hpp"
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FileUtils.hpp>
+#include <core/system/System.hpp>
+
+#include "RSessionState.hpp"
+
+using namespace core ;
+
+namespace r {
+namespace session {
+
+namespace {
+
+const char * const kContext = "ctx-";
+
+FilePath restartContextsPath(const FilePath& scopePath)
+{
+ FilePath contextsPath = scopePath.complete("ctx");
+ Error error = contextsPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ return contextsPath;
+}
+
+} // anonymous namespace
+
+// singleton
+RestartContext& restartContext()
+{
+ static RestartContext instance;
+ return instance;
+}
+
+RestartContext::RestartContext()
+{
+}
+
+void RestartContext::initialize(const FilePath& scopePath,
+ const std::string& contextId)
+{
+ FilePath contextsPath = restartContextsPath(scopePath);
+ FilePath statePath = contextsPath.complete(kContext + contextId);
+ if (statePath.exists())
+ sessionStatePath_ = statePath;
+}
+
+bool RestartContext::hasSessionState() const
+{
+ return !sessionStatePath().empty();
+}
+
+bool RestartContext::rProfileOnRestore() const
+{
+ // if we don't have any session state then this check shouldn't
+ // trigger loading of the profile (allow other checks like whether
+ // we are coming back from a server suspend to run)
+ if (!hasSessionState())
+ return false;
+
+ return r::session::state::rProfileOnRestore(sessionStatePath());
+}
+
+FilePath RestartContext::sessionStatePath() const
+{
+ return sessionStatePath_;
+}
+
+void RestartContext::removeSessionState()
+{
+ r::session::state::destroy(sessionStatePath_);
+}
+
+FilePath RestartContext::createSessionStatePath(const FilePath& scopePath,
+ const std::string& contextId)
+{
+ FilePath contextsPath = restartContextsPath(scopePath);
+ FilePath statePath = contextsPath.complete(kContext + contextId);
+
+ Error error = statePath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ return statePath;
+}
+
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RRestartContext.hpp b/src/cpp/r/session/RRestartContext.hpp
new file mode 100644
index 0000000..e50430e
--- /dev/null
+++ b/src/cpp/r/session/RRestartContext.hpp
@@ -0,0 +1,62 @@
+/*
+ * RRestartContext.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_RESTART_CONTEXT_HPP
+#define R_SESSION_RESTART_CONTEXT_HPP
+
+#include <boost/noncopyable.hpp>
+
+#include <core/FilePath.hpp>
+
+namespace r {
+namespace session {
+
+// singleton
+class RestartContext ;
+RestartContext& restartContext();
+
+class RestartContext : boost::noncopyable
+{
+private:
+ RestartContext();
+ friend RestartContext& restartContext();
+
+public:
+
+ void initialize(const core::FilePath& scopePath,
+ const std::string& contextId);
+
+ bool hasSessionState() const;
+
+ bool rProfileOnRestore() const;
+
+ core::FilePath sessionStatePath() const;
+
+ void removeSessionState();
+
+ static core::FilePath createSessionStatePath(
+ const core::FilePath& scopePath,
+ const std::string& contextId);
+
+private:
+ core::FilePath sessionStatePath_;
+};
+
+
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_RESTART_CONTEXT_HPP
+
diff --git a/src/cpp/r/session/RSearchPath.cpp b/src/cpp/r/session/RSearchPath.cpp
new file mode 100644
index 0000000..490c1a4
--- /dev/null
+++ b/src/cpp/r/session/RSearchPath.cpp
@@ -0,0 +1,411 @@
+/*
+ * RSearchPath.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// NOTE: known "holes" in search path persistence include:
+//
+// - package internal state
+// - contents of UserDefinedDatabase objects installed by packages
+//
+
+#include "RSearchPath.hpp"
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RInternal.hpp>
+#include <r/RExec.hpp>
+#include <r/RInterface.hpp>
+
+using namespace core ;
+
+namespace r {
+
+using namespace exec ;
+
+namespace session {
+namespace search_path {
+
+namespace {
+
+const char * const kEnvironmentFile = "environment";
+const char * const kSearchPathDir = "search_path";
+
+const char * const kSearchPathElementsDir = "search_path_elements";
+const char * const kPackagePaths = "package_paths";
+const char * const kEnvDataDir = "environment_data";
+
+void reportRestoreError(const std::string& context,
+ const Error& error,
+ const ErrorLocation& location)
+{
+ // build the message
+ std::string message = "Error restoring session data";
+ if (!context.empty())
+ message += std::string(" (" + context + ")");
+
+ // add context to error and log it
+ Error restoreError = error ;
+ restoreError.addProperty("context", message);
+ core::log::logError(restoreError, location);
+
+ // notify end-user
+ std::string report = message + ": " + error.code().message() + "\n";
+ REprintf(report.c_str());
+}
+
+Error saveGlobalEnvironmentToFile(const FilePath& environmentFile)
+{
+ std::string envPath =
+ string_utils::utf8ToSystem(environmentFile.absolutePath());
+ return executeSafely(boost::bind(R_SaveGlobalEnvToFile, envPath.c_str()));
+}
+
+Error restoreGlobalEnvironment(const core::FilePath& environmentFile)
+{
+ // tolerate no environment saved
+ if (!environmentFile.exists())
+ return Success();
+
+ return RFunction("load", environmentFile.absolutePath()).call();
+}
+
+bool isPackage(const std::string& elementName, std::string* pPackageName)
+{
+ std::string packagePrefix("package:");
+ if ( boost::algorithm::starts_with(elementName, packagePrefix) &&
+ (elementName.size() > packagePrefix.size()) )
+ {
+ *pPackageName = elementName.substr(packagePrefix.size());
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool hasEnvironmentData(const std::string& elementName)
+{
+ if (boost::algorithm::starts_with(elementName, "package:"))
+ return false;
+ else if (elementName == "Autoloads")
+ return false;
+ else if (elementName == "tools:rstudio")
+ return false;
+ else if (elementName == ".GlobalEnv") // saved and restored separately
+ return false;
+ else
+ return true;
+}
+
+// detach any search path elements which are not contained in the passed list
+Error detachSearchPathElementsNotInList(
+ const std::vector<std::string>& searchPathList,
+ const std::vector<std::string>& currentSearchPathList)
+{
+ // make a copy of the list and sort it so we can use std::set_difference
+ std::vector<std::string> sortedSearchPathList = searchPathList;
+ std::sort(sortedSearchPathList.begin(), sortedSearchPathList.end());
+
+ // make a copy of the current list and sort it as well
+ std::vector<std::string> sortedCurrentSearchPathList = currentSearchPathList;
+ std::sort(sortedCurrentSearchPathList.begin(),
+ sortedCurrentSearchPathList.end());
+
+ // find items in the current list which are NOT in the saved list
+ std::vector<std::string> detachSearchPathList;
+ std::set_difference(sortedCurrentSearchPathList.begin(),
+ sortedCurrentSearchPathList.end(),
+ sortedSearchPathList.begin(),
+ sortedSearchPathList.end(),
+ std::back_inserter(detachSearchPathList));
+
+ // detach these items
+ for (std::vector<std::string>::const_iterator
+ it = detachSearchPathList.begin();
+ it != detachSearchPathList.end();
+ ++it)
+ {
+ // get name of item to detach
+ std::string detachItem = *it;
+
+ // don't allow detach of core packages
+ if ( detachItem == "tools:rstudio" ||
+ detachItem == "package:utils" )
+ {
+ continue;
+ }
+
+ // do the detach
+ boost::format fmt("detach(pos = match(\"%1%\", search()))");
+ std::string detach = boost::str(fmt % detachItem);
+ Error error = r::exec::executeString(detach);
+ if (error)
+ reportRestoreError("detaching " + detachItem, error, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+bool packageIsLoaded(const std::string& packageElementName,
+ const std::vector<std::string>& searchPathList)
+{
+ return std::find(searchPathList.begin(),
+ searchPathList.end(),
+ packageElementName) != searchPathList.end();
+
+}
+
+void loadPackage(const std::string& packageName, const std::string& path)
+{
+ // calculate the lib
+ std::string lib;
+ if (!path.empty())
+ lib = string_utils::utf8ToSystem(FilePath(path).parent().absolutePath());
+
+ Error error = r::exec::RFunction(".rs.loadPackage", packageName, lib).call();
+ if (error)
+ {
+ reportRestoreError("loading package " + packageName,
+ error,
+ ERROR_LOCATION);
+ }
+}
+
+void attachEnvironmentData(const FilePath& dataFilePath,
+ const std::string& name)
+{
+ if (dataFilePath.exists())
+ {
+ Error error = r::exec::RFunction(".rs.attachDataFile",
+ dataFilePath.absolutePath(),
+ name).call();
+
+ if (error)
+ {
+ reportRestoreError("attaching search path element "+ name,
+ error,
+ ERROR_LOCATION);
+ }
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("environment data file not found: " +
+ dataFilePath.absolutePath());
+ }
+}
+
+
+} // anonymous namespace
+
+
+Error save(const FilePath& statePath)
+{
+ // save the global environment
+ FilePath environmentFile = statePath.complete(kEnvironmentFile);
+ Error error = saveGlobalEnvironmentToFile(environmentFile);
+ if (error)
+ return error;
+
+ // reset the contents of the search path dir
+ FilePath searchPathDir = statePath.complete(kSearchPathDir);
+ error = searchPathDir.resetDirectory();
+ if (error)
+ return error ;
+
+ // create environment data subdirectory
+ FilePath environmentDataPath = searchPathDir.complete(kEnvDataDir);
+ error = environmentDataPath.ensureDirectory();
+ if (error)
+ return error;
+
+ // iterate throught the search path (build a list as we go). set
+ // .GlobalEnv and package:base as bookends of the list (note this code
+ // is based on the implementation of do_search)
+ std::vector<std::string> searchPathElements;
+ searchPathElements.push_back(".GlobalEnv");
+ std::map<std::string,std::string> packagePaths;
+ for (SEXP envSEXP = ENCLOS(R_GlobalEnv);
+ envSEXP != R_BaseEnv ;
+ envSEXP = ENCLOS(envSEXP))
+ {
+ // screen out UserDefinedDatabase elements (attempting to perisist
+ // a UserDefinedDatabase caused mischief in at least one case (e.g. see
+ // RProtoBuf:DescriptorPool) so we exclude it globally.
+ if (r::sexp::classOf(envSEXP) == "UserDefinedDatabase")
+ continue;
+
+ // get the name of the search path element and add it to our list
+ SEXP nameSEXP = Rf_getAttrib(envSEXP, Rf_install("name"));
+ std::string elementName;
+ if (!Rf_isString(nameSEXP) || Rf_length(nameSEXP) < 1)
+ elementName = "(unknown)";
+ else
+ elementName = r::sexp::asString(nameSEXP);
+
+ // if this is a package also save it's path
+ if (boost::algorithm::starts_with(elementName, "package:"))
+ {
+ std::string name = boost::algorithm::replace_first_copy(elementName,
+ "package:",
+ "");
+ std::string path;
+ Error error = r::exec::RFunction(".rs.pathPackage", name).call(&path);
+ if (error)
+ LOG_ERROR(error);
+
+ if (!path.empty())
+ {
+ path = core::string_utils::systemToUtf8(path);
+ packagePaths[name] = path;
+ }
+ }
+
+ searchPathElements.push_back(elementName);
+
+ // save the environment's data if necessary
+ if (hasEnvironmentData(elementName))
+ {
+ // determine file path (index of item within list)
+ std::string itemIndex = safe_convert::numberToString(
+ searchPathElements.size()-1);
+ FilePath dataFilePath = environmentDataPath.complete(itemIndex);
+
+ // save the environment
+ Error error = r::exec::RFunction(".rs.saveEnvironment",
+ envSEXP,
+ dataFilePath.absolutePath()).call();
+ if (error)
+ return error;
+ }
+ }
+ searchPathElements.push_back("package:base");
+
+ // save the search path list
+ FilePath elementsPath = searchPathDir.complete(kSearchPathElementsDir);
+ error = writeStringVectorToFile(elementsPath, searchPathElements);
+ if (error)
+ return error;
+
+ // save the package paths list
+ FilePath packagePathsFile = searchPathDir.complete(kPackagePaths);
+ return writeStringMapToFile(packagePathsFile, packagePaths);
+}
+
+
+Error saveGlobalEnvironment(const FilePath& statePath)
+{
+ FilePath environmentFile = statePath.complete(kEnvironmentFile);
+ return saveGlobalEnvironmentToFile(environmentFile);
+}
+
+Error restore(const FilePath& statePath)
+{
+ // restore global environment
+ FilePath environmentFile = statePath.complete(kEnvironmentFile);
+ Error error = restoreGlobalEnvironment(environmentFile);
+ if (error)
+ return error;
+
+ // attempt to restore the search path if one has been saved
+ FilePath searchPathDir = statePath.complete(kSearchPathDir);
+ if (!searchPathDir.exists())
+ return Success();
+
+ // read the saved list
+ std::vector<std::string> savedSearchPathList;
+ FilePath elementsPath = searchPathDir.complete(kSearchPathElementsDir);
+ error = readStringVectorFromFile(elementsPath, &savedSearchPathList);
+ if (error)
+ return error;
+
+ // read the package paths list
+ std::map<std::string,std::string> packagePaths;
+ FilePath packagePathsFile = searchPathDir.complete(kPackagePaths);
+ if (packagePathsFile.exists())
+ {
+ error = readStringMapFromFile(packagePathsFile, &packagePaths);
+ if (error)
+ return error;
+ }
+
+ // get the current search path
+ std::vector<std::string> currentSearchPathList;
+ error = r::exec::RFunction("search").call(¤tSearchPathList);
+ if (error)
+ return error;
+
+ // detach any items in the current list which aren't in the saved list
+ error = detachSearchPathElementsNotInList(savedSearchPathList,
+ currentSearchPathList);
+ if (error)
+ return error;
+
+ // iterate though the saved list in reverse, loading packages and
+ // environments saved in external data files as necessary. note that
+ // this excludes the first and last entries in the list (.GlobalEnv and
+ // package:base respectively)
+ FilePath environmentDataPath = searchPathDir.complete(kEnvDataDir);
+ for (int i = (savedSearchPathList.size() - 2); i > 0; i--)
+ {
+ // get the path element
+ std::string pathElement = savedSearchPathList[i];
+
+ // if it is a package then load it if it is not already loaded
+ std::string packageName;
+ if ( isPackage(pathElement, &packageName) )
+ {
+ if ( !packageIsLoaded(packageName, currentSearchPathList) )
+ loadPackage(packageName, packagePaths[packageName]);
+ }
+
+ // else if it has external environment data then load it
+ else if (hasEnvironmentData(pathElement))
+ {
+ std::string itemIndex = safe_convert::numberToString(i);
+ FilePath dataFilePath = environmentDataPath.complete(itemIndex);
+ attachEnvironmentData(dataFilePath, pathElement);
+ }
+
+ else
+ {
+ // it must be "tools:rstudio" or "Autoloads", do nothing
+ }
+ }
+
+ return Success();
+}
+
+
+} // namespace search_path
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/RSearchPath.hpp b/src/cpp/r/session/RSearchPath.hpp
new file mode 100644
index 0000000..cc4fc43
--- /dev/null
+++ b/src/cpp/r/session/RSearchPath.hpp
@@ -0,0 +1,37 @@
+/*
+ * RSearchPath.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_SEARCH_PATH_HPP
+#define R_SESSION_SEARCH_PATH_HPP
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace search_path {
+
+core::Error save(const core::FilePath& statePath);
+core::Error saveGlobalEnvironment(const core::FilePath& statePath);
+core::Error restore(const core::FilePath& statePath);
+
+} // namespace search_path
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_SEARCH_PATH_HPP
+
diff --git a/src/cpp/r/session/RSession.cpp b/src/cpp/r/session/RSession.cpp
new file mode 100644
index 0000000..c612aee
--- /dev/null
+++ b/src/cpp/r/session/RSession.cpp
@@ -0,0 +1,1666 @@
+/*
+ * RSession.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/session/RSession.hpp>
+
+#include <iostream>
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Settings.hpp>
+#include <core/Scope.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/http/Util.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RUtil.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/ROptions.hpp>
+#include <r/RSourceManager.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RInterface.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/session/RConsoleActions.hpp>
+#include <r/session/RConsoleHistory.hpp>
+#include <r/session/RClientState.hpp>
+#include <r/session/RGraphics.hpp>
+#include <r/session/RDiscovery.hpp>
+
+#include "RClientMetrics.hpp"
+#include "RSessionState.hpp"
+#include "RRestartContext.hpp"
+#include "REmbedded.hpp"
+
+#include "graphics/RGraphicsUtils.hpp"
+#include "graphics/RGraphicsDevice.hpp"
+#include "graphics/RGraphicsPlotManager.hpp"
+
+#include <Rembedded.h>
+#include <R_ext/Utils.h>
+#include <R_ext/Rdynload.h>
+#include <R_ext/RStartup.h>
+extern "C" SA_TYPE SaveAction;
+
+#define CTXT_BROWSER 16
+
+// get rid of windows TRUE and FALSE definitions
+#undef TRUE
+#undef FALSE
+
+// constants for graphics scratch subdirectory
+#define kGraphicsPath "graphics"
+
+using namespace core ;
+
+namespace r {
+namespace session {
+
+namespace {
+
+// is this R 3.0 or greator
+bool s_isR3 = false;
+
+// options
+ROptions s_options;
+
+// callbacks
+RCallbacks s_callbacks;
+
+// client-state paths
+FilePath s_clientStatePath;
+FilePath s_projectClientStatePath;
+
+// session state path
+FilePath s_suspendedSessionPath ;
+
+// function for deferred deserialization actions. this encapsulates parts of
+// the initialization process that are potentially highly latent. this allows
+// clients to bring their UI up and then receive an event indicating that the
+// latent deserialization actions are taking place
+boost::function<void()> s_deferredDeserializationAction;
+
+// have we completed our one-time initialization yet?
+bool s_initialized = false;
+
+// are in the middle of servicing a suspend request?
+bool s_suspended = false;
+
+// temporarily suppress output
+bool s_suppressOuput = false;
+
+FilePath rHistoryFilePath()
+{
+ std::string histFile = core::system::getenv("R_HISTFILE");
+ boost::algorithm::trim(histFile);
+ if (histFile.empty())
+ histFile = ".Rhistory";
+
+ return s_options.rHistoryDir().complete(histFile);
+}
+
+
+FilePath rSaveGlobalEnvironmentFilePath()
+{
+ FilePath rEnvironmentDir = s_options.rEnvironmentDir();
+ return rEnvironmentDir.complete(".RData");
+}
+
+std::string createAliasedPath(const FilePath& filePath)
+{
+ return FilePath::createAliasedPath(filePath, s_options.userHomePath);
+}
+
+class SerializationCallbackScope : boost::noncopyable
+{
+public:
+ SerializationCallbackScope(int action,
+ const FilePath& targetPath = FilePath())
+ {
+ s_callbacks.serialization(action, targetPath);
+ }
+
+ ~SerializationCallbackScope()
+ {
+ try {
+ s_callbacks.serialization(kSerializationActionCompleted,
+ FilePath());
+ } catch(...) {}
+ }
+};
+
+
+void reportDeferredDeserializationError(const Error& error)
+{
+ // log error
+ LOG_ERROR(error);
+
+ // report to user
+ std::string errMsg = r::endUserErrorMessage(error);
+ REprintf((errMsg + "\n").c_str());
+}
+
+void completeDeferredSessionInit(bool newSession)
+{
+ // always cleanup any restart context here
+ restartContext().removeSessionState();
+
+ // call external hook
+ if (s_callbacks.deferredInit)
+ s_callbacks.deferredInit(newSession);
+}
+
+void saveClientState(ClientStateCommitType commitType)
+{
+ using namespace r::session;
+
+ // save client state (note we don't explicitly restore this
+ // in restoreWorkingState, rather it is restored during
+ // initialize() so that the client always has access to it when
+ // for client_init)
+ r::session::clientState().commit(commitType,
+ s_clientStatePath,
+ s_projectClientStatePath);
+}
+
+
+
+bool saveSessionState(const RSuspendOptions& options,
+ const FilePath& suspendedSessionPath,
+ bool disableSaveCompression)
+{
+ // notify client of serialization status
+ SerializationCallbackScope cb(kSerializationActionSuspendSession);
+
+ // suppress interrupts which occur during saving
+ r::exec::IgnoreInterruptsScope ignoreInterrupts;
+
+ // save
+ if (options.saveMinimal)
+ {
+ // save minimal
+ return r::session::state::saveMinimal(suspendedSessionPath,
+ options.saveWorkspace);
+
+ }
+ else
+ {
+ return r::session::state::save(suspendedSessionPath,
+ s_options.serverMode,
+ options.excludePackages,
+ disableSaveCompression);
+ }
+}
+
+void deferredRestoreSuspendedSession(
+ const boost::function<Error()>& deferredRestoreAction)
+{
+ // notify client of serialization status
+ SerializationCallbackScope cb(kSerializationActionResumeSession);
+
+ // suppress interrupts which occur during restore
+ r::exec::IgnoreInterruptsScope ignoreInterrupts;
+
+ // suppress output which occurs during restore (packages can sometimes
+ // print messages to the console indicating they have conflicts -- the
+ // has already seen these messages and doesn't expect them now so
+ // we suppress them
+ utils::SuppressOutputInScope suppressOutput;
+
+ // restore action
+ Error error = deferredRestoreAction();
+ if (error)
+ reportDeferredDeserializationError(error);
+
+ // complete deferred init
+ completeDeferredSessionInit(false);
+
+}
+
+Error saveDefaultGlobalEnvironment()
+{
+ // path to save to
+ FilePath globalEnvPath = rSaveGlobalEnvironmentFilePath();
+
+ // notify client of serialization status
+ SerializationCallbackScope cb(kSerializationActionSaveDefaultWorkspace,
+ globalEnvPath);
+
+ // suppress interrupts which occur during saving
+ r::exec::IgnoreInterruptsScope ignoreInterrupts;
+
+ // save global environment
+ std::string path = string_utils::utf8ToSystem(globalEnvPath.absolutePath());
+ Error error = r::exec::executeSafely(
+ boost::bind(R_SaveGlobalEnvToFile, path.c_str()));
+
+ if (error)
+ {
+ return error;
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+void deferredRestoreNewSession()
+{
+ // restore the default global environment if there is one
+ FilePath globalEnvPath = s_options.startupEnvironmentFilePath;
+ if (s_options.restoreWorkspace && globalEnvPath.exists())
+ {
+ // notify client of serialization status
+ SerializationCallbackScope cb(kSerializationActionLoadDefaultWorkspace,
+ globalEnvPath);
+
+ // ignore interrupts which occur during restoring of the global env
+ // the restoration will run to completion in any case and then the
+ // next thing the user does will be "interrupted" -- clearly not
+ // what they intended
+ r::exec::IgnoreInterruptsScope ignoreInterrupts;
+
+ std::string path = string_utils::utf8ToSystem(globalEnvPath.absolutePath());
+ Error error = r::exec::executeSafely(boost::bind(
+ R_RestoreGlobalEnvFromFile,
+ path.c_str(),
+ TRUE));
+ if (error)
+ {
+ reportDeferredDeserializationError(error);
+ }
+ else
+ {
+ // print path to console
+ std::string aliasedPath = createAliasedPath(globalEnvPath);
+ Rprintf(("[Workspace loaded from " + aliasedPath + "]\n\n").c_str());
+ }
+ }
+
+ // mark image clean (we need to do this due to our delayed handling
+ // of workspace restoration)
+ setImageDirty(false);
+
+ // complete deferred init
+ completeDeferredSessionInit(true);
+}
+
+void reportHistoryAccessError(const std::string& context,
+ const FilePath& historyFilePath,
+ const Error& error)
+{
+ // always log
+ LOG_ERROR(error);
+
+ // default summary
+ std::string summary = error.summary();
+
+ // if the file exists and we still got no such file or directory
+ // then it is almost always permission denied. this seems to happen
+ // somewhat frequently on linux systems where the user was root for
+ // an operation and ended up writing a .Rhistory
+ if (historyFilePath.exists() &&
+ (error.code() == boost::system::errc::no_such_file_or_directory))
+ {
+ summary = "permission denied (is the .Rhistory file owned by root?)";
+ }
+
+ // notify the user
+ std::string path = createAliasedPath(historyFilePath);
+ std::string errmsg = context + " " + path + ": " + summary;
+ REprintf(("Error attempting to " + errmsg + "\n").c_str());
+}
+
+} // anonymous namespace
+
+
+const int kSerializationActionSaveDefaultWorkspace = 1;
+const int kSerializationActionLoadDefaultWorkspace = 2;
+const int kSerializationActionSuspendSession = 3;
+const int kSerializationActionResumeSession = 4;
+const int kSerializationActionCompleted = 5;
+
+void restoreSession(const FilePath& suspendedSessionPath,
+ std::string* pErrorMessages)
+{
+ // don't show output during deserialization (packages loaded
+ // during deserialization sometimes print messages)
+ utils::SuppressOutputInScope suppressOutput;
+
+ // deserialize session. if any part of this fails then the errors
+ // will be logged and error messages will be returned in the passed
+ // errorMessages buffer (this mechanism is used because we generally
+ // suppress output during restore but we need a way for the error
+ // messages to make their way back to the user)
+ boost::function<Error()> deferredRestoreAction;
+ r::session::state::restore(suspendedSessionPath,
+ s_options.serverMode,
+ &deferredRestoreAction,
+ pErrorMessages);
+
+ if (deferredRestoreAction)
+ {
+ s_deferredDeserializationAction = boost::bind(
+ deferredRestoreSuspendedSession,
+ deferredRestoreAction);
+ }
+}
+
+// one-time per session initialization
+Error initialize()
+{
+ // ensure that the utils package is loaded (it might not be loaded
+ // if R is attempting to recover from a library loading error which
+ // occurs during .Rprofile)
+ Error libError = r::exec::RFunction("library", "utils").call();
+ if (libError)
+ LOG_ERROR(libError);
+
+ // check whether this is R 3.0 or greater
+ Error r3Error = r::exec::evaluateString("getRversion() >= '3.0.0'", &s_isR3);
+ if (r3Error)
+ LOG_ERROR(r3Error);
+
+ // initialize console history capacity
+ r::session::consoleHistory().setCapacityFromRHistsize();
+
+ // install R tools
+ FilePath toolsFilePath = s_options.rSourcePath.complete("Tools.R");
+ Error error = r::sourceManager().sourceTools(toolsFilePath);
+ if (error)
+ return error ;
+
+ // initialize graphics device -- use a stable directory for server mode
+ // and temp directory for desktop mode (so that we can support multiple
+ // concurrent processes using the same project)
+ FilePath graphicsPath;
+ if (s_options.serverMode)
+ {
+ std::string path = kGraphicsPath;
+ if (utils::isR3())
+ path += "-r3";
+ graphicsPath = s_options.scopedScratchPath.complete(path);
+ }
+ else
+ {
+ graphicsPath = r::session::utils::tempDir().complete(
+ "rs-graphics-" + core::system::generateUuid());
+ }
+
+ error = graphics::device::initialize(graphicsPath,
+ s_callbacks.locator);
+ if (error)
+ return error;
+
+ // restore client state
+ session::clientState().restore(s_clientStatePath,
+ s_projectClientStatePath);
+
+ // restore suspended session if we have one
+ bool wasResumed = false;
+
+ // first check for a pending restart
+ if (restartContext().hasSessionState())
+ {
+ // restore session
+ std::string errorMessages ;
+ restoreSession(restartContext().sessionStatePath(), &errorMessages);
+
+ // show any error messages
+ if (!errorMessages.empty())
+ REprintf(errorMessages.c_str());
+
+ // note we were resumed
+ wasResumed = true;
+ }
+ else if (s_suspendedSessionPath.exists())
+ {
+ // restore session
+ std::string errorMessages ;
+ restoreSession(s_suspendedSessionPath, &errorMessages);
+
+ // show any error messages
+ if (!errorMessages.empty())
+ REprintf(errorMessages.c_str());
+
+ // note we were resumed
+ wasResumed = true;
+ }
+ // new session
+ else
+ {
+ // restore console history
+ FilePath historyPath = rHistoryFilePath();
+ error = consoleHistory().loadFromFile(historyPath, false);
+ if (error)
+ reportHistoryAccessError("read history from", historyPath, error);
+
+ // defer loading of global environment
+ s_deferredDeserializationAction = deferredRestoreNewSession;
+ }
+
+ // initialize client
+ RInitInfo rInitInfo(wasResumed);
+ error = s_callbacks.init(rInitInfo);
+ if (error)
+ return error;
+
+ // call resume hook if we were resumed
+ if (wasResumed)
+ s_callbacks.resumed();
+
+ // now that all initialization code has had a chance to run we
+ // can register all external routines which were added to r::routines
+ // during the init sequence
+ r::routines::registerAll();
+
+ // set default repository if requested
+ if (!s_options.rCRANRepos.empty())
+ {
+ error = r::exec::RFunction(".rs.setCRANReposAtStartup",
+ s_options.rCRANRepos).call();
+ if (error)
+ return error;
+ }
+
+ // complete embedded r initialization
+ error = r::session::completeEmbeddedRInitialization(s_options.useInternet2);
+ if (error)
+ return error;
+
+ // set global R options
+ FilePath optionsFilePath = s_options.rSourcePath.complete("Options.R");
+ error = r::sourceManager().sourceLocal(optionsFilePath);
+ if (error)
+ return error;
+
+ // server specific R options options
+ if (s_options.serverMode)
+ {
+#ifndef __APPLE__
+ FilePath serverOptionsFilePath = s_options.rSourcePath.complete(
+ "ServerOptions.R");
+ return r::sourceManager().sourceLocal(serverOptionsFilePath);
+#else
+ return Success();
+#endif
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+void rSuicide(const std::string& msg)
+{
+ // log abort message if we are in desktop mode
+ if (!s_options.serverMode)
+ {
+ FilePath abendLogPath = s_options.logPath.complete(
+ "rsession_abort_msg.log");
+ Error error = core::writeStringToFile(abendLogPath, msg);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+
+ R_Suicide(msg.c_str());
+}
+
+void rSuicide(const Error& error)
+{
+ rSuicide(core::log::errorAsLogEntry(error));
+}
+
+// forward declare win32 quit handler and provide string based quit
+// handler that parses the quit command from the console
+#ifdef _WIN32
+bool win32Quit(const std::string& saveAction,
+ int status,
+ bool runLast,
+ std::string* pErrMsg);
+
+bool win32Quit(const std::string& command, std::string* pErrMsg)
+{
+ // default values
+ std::string saveAction = "default";
+ double status = 0;
+ bool runLast = true;
+
+ // parse quit arguments
+ SEXP argsSEXP;
+ r::sexp::Protect rProtect;
+ Error error = r::exec::RFunction(".rs.parseQuitArguments", command).call(
+ &argsSEXP,
+ &rProtect);
+ if (!error)
+ {
+ error = r::sexp::getNamedListElement(argsSEXP,
+ "save",
+ &saveAction,
+ saveAction);
+ if (error)
+ LOG_ERROR(error);
+
+ error = r::sexp::getNamedListElement(argsSEXP,
+ "status",
+ &status,
+ status);
+ if (error)
+ LOG_ERROR(error);
+
+ error = r::sexp::getNamedListElement(argsSEXP,
+ "runLast",
+ &runLast,
+ runLast);
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ *pErrMsg = r::endUserErrorMessage(error);
+ return false;
+ }
+
+ return win32Quit(saveAction, static_cast<int>(status), runLast, pErrMsg);
+}
+
+#endif
+
+bool consoleInputHook(const std::string& prompt,
+ const std::string& input)
+{
+ // only check for quit when we're at the default prompt
+ if (!r::session::utils::isDefaultPrompt(prompt))
+ return true;
+
+ // check for user quit invocation
+ boost::regex re("^\\s*(q|quit)\\s*\\(.*$");
+ boost::smatch match;
+ if (boost::regex_match(input, match, re))
+ {
+ if (!s_callbacks.handleUnsavedChanges())
+ {
+ REprintf("User cancelled quit operation\n");
+ return false;
+ }
+
+ // on win32 we will actually assume responsibility for the
+ // quit function entirely (so we can call our internal cleanup
+ // handler code)
+#ifdef _WIN32
+ std::string quitErr;
+ bool didQuit = win32Quit(input, &quitErr);
+ if (!didQuit)
+ REprintf((quitErr + "\n").c_str());
+
+ // always return false (since we take over the command fully)
+ return false;
+#else
+ return true;
+#endif
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool isInjectedBrowserCommand(const std::string& cmd)
+{
+ return browserContextActive() &&
+ (cmd == "c" || cmd == "Q" || cmd == "n" || cmd == "s" || cmd == "f");
+}
+
+
+int RReadConsole (const char *pmt,
+ CONSOLE_BUFFER_CHAR* buf,
+ int buflen,
+ int hist)
+{
+ try
+ {
+ // capture the prompt for later manipulation
+ std::string prompt(pmt);
+
+ // invoke one time initialization
+ if (!s_initialized)
+ {
+ // ignore interrupts which occur during initialization. any
+ // interrupt will cause the initialization to fail which will
+ // then require the user to start over from the beginning. if the
+ // user has to wait in any case we might as well help them out by
+ // never having to start from scratch
+ r::exec::IgnoreInterruptsScope ignoreInterrupts;
+
+ // attempt to initialize
+ Error initError;
+ Error error = r::exec::executeSafely<Error>(initialize, &initError);
+ if (error || initError)
+ {
+ if (initError)
+ error = initError;
+
+ // log the error
+ LOG_ERROR(error);
+
+ // terminate the session (use suicide so that no special
+ // termination code runs -- i.e. call to setAbnormalEnd(false)
+ // or call to client::quitSession)
+ rSuicide(error);
+ }
+
+ // reset the prompt to whatever the default is after we've
+ // fully initialized (and restored suspended options)
+ prompt = r::options::getOption<std::string>("prompt");
+
+ // ensure only one initialization
+ s_initialized = true;
+ }
+
+ std::string promptString(prompt);
+ promptString = util::rconsole2utf8(promptString);
+
+ // get the next input
+ bool addToHistory = (hist == 1);
+ RConsoleInput consoleInput;
+ if ( s_callbacks.consoleRead(promptString, addToHistory, &consoleInput) )
+ {
+ // add prompt to console actions (we do this after consoleRead
+ // completes so that we don't send both a console prompt event
+ // AND include the same prompt in the actions history)
+ consoleActions().add(kConsoleActionPrompt, prompt);
+
+ if (consoleInput.cancel)
+ {
+ // notify of interrupt
+ consoleActions().notifyInterrupt();
+
+ // escape out using exception so that we can allow normal
+ // c++ stack unwinding to occur before jumping
+ throw r::exec::InterruptException();
+ }
+ else
+ {
+ // determine the input to return to R
+ std::string rInput = consoleInput.text;
+
+ // refresh source if necessary (no-op in production)
+ r::sourceManager().reloadIfNecessary();
+
+ // ensure that our input fits within the buffer
+ std::string::size_type maxLen = buflen - 2; // for \n\0
+ rInput = string_utils::utf8ToSystem(rInput, true);
+ if (rInput.length() > maxLen)
+ rInput.resize(maxLen);
+ std::string::size_type inputLen = rInput.length();
+
+ // add to console actions and history (if requested). note that
+ // we add the user's input rather than any tranformed input we
+ // created as a result of a shell escape
+ consoleActions().add(kConsoleActionInput, consoleInput.text);
+ if (addToHistory && !isInjectedBrowserCommand(consoleInput.text))
+ consoleHistory().add(consoleInput.text);
+
+ // call console input hook and interrupt if the hook tells us to
+ if (!consoleInputHook(prompt, consoleInput.text))
+ throw r::exec::InterruptException();
+
+ // copy to buffer and add terminators
+ rInput.copy( (char*)buf, maxLen);
+ buf[inputLen] = '\n';
+ buf[inputLen+1] = '\0';
+ }
+
+ return 1 ;
+ }
+ else
+ {
+ return 0; // terminate
+ }
+ }
+ catch(r::exec::InterruptException)
+ {
+ // this will result in a longjmp
+ r::exec::setInterruptsPending(true);
+ r::exec::checkUserInterrupt();
+ }
+ catch(const std::exception& e)
+ {
+ std::string msg = std::string("Unexpected exception: ") + e.what();
+ LOG_ERROR_MESSAGE(msg);
+ rSuicide(msg);
+ }
+ catch(...)
+ {
+ std::string msg = "Unknown exception";
+ LOG_ERROR_MESSAGE(msg);
+ rSuicide(msg);
+ }
+
+ return 0 ; // keep compiler happy
+}
+
+void RWriteConsoleEx (const char *buf, int buflen, int otype)
+{
+ try
+ {
+ if (!s_suppressOuput)
+ {
+ // get output
+ std::string output = std::string(buf,buflen);
+ output = util::rconsole2utf8(output);
+
+ // add to console actions
+ int type = otype == 1 ? kConsoleActionOutputError :
+ kConsoleActionOutput;
+ consoleActions().add(type, output);
+
+ // write
+ s_callbacks.consoleWrite(output, otype) ;
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+void RShowMessage(const char* msg)
+{
+ try
+ {
+ s_callbacks.showMessage(msg) ;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+
+// NOTE: Win32 doesn't receive this callback
+int REditFile(const char* file)
+{
+ try
+ {
+ return s_callbacks.editFile(r::util::fixPath(file));
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // error if we got this far
+ return 1 ;
+}
+
+SEXP rs_editFile(SEXP fileSEXP)
+{
+ try
+ {
+ std::string file = r::sexp::asString(fileSEXP);
+ bool success = REditFile(file.c_str()) == 0;
+ r::sexp::Protect rProtect;
+ return r::sexp::create(success, &rProtect);
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // keep compiler happy (this code is unreachable)
+ return R_NilValue;
+}
+
+SEXP rs_showFile(SEXP titleSEXP, SEXP fileSEXP, SEXP delSEXP)
+{
+ try
+ {
+ std::string file = r::util::fixPath(r::sexp::asString(fileSEXP));
+ FilePath filePath = utils::safeCurrentPath().complete(file);
+ if (!filePath.exists())
+ {
+ throw r::exec::RErrorException(
+ "File " + file + " does not exist.");
+ }
+
+ s_callbacks.showFile(r::sexp::asString(titleSEXP),
+ filePath,
+ r::sexp::asLogical(delSEXP));
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+// NOTE: Win32 doesn't receive this callback
+int RShowFiles (int nfile,
+ const char **file,
+ const char **headers,
+ const char *wtitle,
+ Rboolean del,
+ const char *pager)
+{
+ try
+ {
+ for (int i=0; i<nfile; i++)
+ {
+ // determine file path and title
+ std::string fixedPath = r::util::fixPath(file[i]);
+ FilePath filePath = utils::safeCurrentPath().complete(fixedPath);
+ if (filePath.exists())
+ {
+ std::string title(headers[i]);
+ if (title.empty())
+ title = wtitle;
+
+ // show file
+ s_callbacks.showFile(title, filePath, del);
+ }
+ else
+ {
+ throw r::exec::RErrorException(
+ "File " + fixedPath + " does not exist.");
+ }
+ }
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // NOTE: the documentation doesn't indicate what to return and do_fileshow
+ // in platform.c doesn't check the return value s
+ return 0;
+}
+
+// NOTE: Win32 doesn't receive this callback
+int RChooseFile (int newFile, char *buf, int len)
+{
+ try
+ {
+ FilePath filePath = s_callbacks.chooseFile(newFile == TRUE);
+ if (!filePath.empty())
+ {
+ // get absolute path
+ std::string absolutePath = filePath.absolutePath();
+
+ // trunate file if it is too long
+ std::string::size_type maxLen = len - 1;
+ if (absolutePath.length() > maxLen)
+ absolutePath.resize(maxLen);
+
+ // copy the file to the buffer
+ absolutePath.copy(buf, maxLen);
+ buf[absolutePath.length()] = '\0';
+
+ // return the length of the filepath buffer
+ return absolutePath.length();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // error if we got this far
+ return 0 ;
+}
+
+// method called from browseUrl
+SEXP rs_browseURL(SEXP urlSEXP)
+{
+ try
+ {
+ std::string URL = r::sexp::asString(urlSEXP);
+
+ // file urls require special dispatching
+ std::string filePrefix("file://");
+ if (URL.find(filePrefix) == 0)
+ {
+ // also look for file:///c: style urls on windows
+#ifdef _WIN32
+ if (URL.find(filePrefix + "/") == 0)
+ filePrefix = filePrefix + "/";
+#endif
+
+ // transform into FilePath
+ std::string path = URL.substr(filePrefix.length());
+ path = core::http::util::urlDecode(path);
+ FilePath filePath(r::util::fixPath(path));
+
+ // sometimes R passes short paths (like for files within the
+ // R home directory). Convert these to long paths
+#ifdef _WIN32
+ core::system::ensureLongPath(&filePath);
+#endif
+
+ // fire browseFile
+ s_callbacks.browseFile(filePath);
+ }
+ // urls with no protocol are assumed to be file references
+ else if (URL.find("://") == std::string::npos)
+ {
+ std::string file = r::util::expandFileName(URL);
+ FilePath filePath = utils::safeCurrentPath().complete(
+ r::util::fixPath(file));
+ s_callbacks.browseFile(filePath);
+ }
+ else
+ {
+ s_callbacks.browseURL(URL) ;
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+SEXP rs_createUUID()
+{
+ r::sexp::Protect rProtect;
+ return r::sexp::create(core::system::generateUuid(false), &rProtect);
+}
+
+SEXP rs_loadHistory(SEXP sFile)
+{
+ std::string file = R_ExpandFileName(r::sexp::asString(sFile).c_str());
+ Error error = consoleHistory().loadFromFile(FilePath(file), true);
+ if (error)
+ LOG_ERROR(error);
+ else
+ s_callbacks.consoleHistoryReset();
+ return R_NilValue;
+}
+
+SEXP rs_saveHistory(SEXP sFile)
+{
+ std::string file = R_ExpandFileName(r::sexp::asString(sFile).c_str());
+ consoleHistory().saveToFile(FilePath(file));
+ return R_NilValue;
+}
+
+void doHistoryFileOperation(SEXP args,
+ boost::function<Error(const FilePath&)> fileOp)
+{
+ // validate filename argument
+ SEXP file = CAR(args);
+ if (!sexp::isString(file) || sexp::length(file) < 1)
+ throw r::exec::RErrorException("invalid 'file' argument");
+
+ // calculate full path to history file
+ FilePath historyFilePath(R_ExpandFileName(Rf_translateChar(STRING_ELT(file,
+ 0))));
+ // perform operation
+ Error error = fileOp(historyFilePath);
+ if (error)
+ throw r::exec::RErrorException(error.code().message());
+}
+
+void Rloadhistory(SEXP call, SEXP op, SEXP args, SEXP env)
+{
+ try
+ {
+ doHistoryFileOperation(args, boost::bind(&ConsoleHistory::loadFromFile,
+ &consoleHistory(), _1, true));
+
+ s_callbacks.consoleHistoryReset();
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::errorCall(call, e.message());
+ }
+}
+
+void Rsavehistory(SEXP call, SEXP op, SEXP args, SEXP env)
+{
+ try
+ {
+ doHistoryFileOperation(args, boost::bind(&ConsoleHistory::saveToFile,
+ &consoleHistory(), _1));
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::errorCall(call, e.message());
+ }
+}
+
+void Raddhistory(SEXP call, SEXP op, SEXP args, SEXP env)
+{
+ try
+ {
+ // get commands
+ std::vector<std::string> commands ;
+ Error error = sexp::extract(CAR(args), &commands);
+ if (error)
+ throw r::exec::RErrorException(error.code().message());
+
+ // append them
+ ConsoleHistory& history = consoleHistory();
+ std::for_each(commands.begin(),
+ commands.end(),
+ boost::bind(&ConsoleHistory::add, &history, _1));
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::errorCall(call, e.message());
+ }
+}
+
+void RBusy(int which)
+{
+ try
+ {
+ s_callbacks.busy(which == 1) ;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+
+// track internal callbacks for delgation
+r::session::InternalCallbacks s_internalCallbacks;
+
+// NOTE: Win32 doesn't receive this callback
+void RSuicide(const char* s)
+{
+ s_callbacks.suicide(s);
+ s_internalCallbacks.suicide(s);
+}
+
+class JumpToTopException
+{
+};
+
+SA_TYPE saveAsk()
+{
+ try
+ {
+ // end user prompt
+ std::string wsPath = createAliasedPath(rSaveGlobalEnvironmentFilePath());
+ std::string prompt = "Save workspace image to " + wsPath + "? [y/n/c]: ";
+
+ // input buffer
+ std::vector<CONSOLE_BUFFER_CHAR> inputBuffer(512, 0);
+
+ while(true)
+ {
+ // read input
+ RReadConsole(prompt.c_str(), &(inputBuffer[0]), inputBuffer.size(), 0);
+ std::string input(1, inputBuffer[0]);
+ boost::algorithm::to_lower(input);
+
+ // look for yes, no, or cancel
+ if (input == "y")
+ return SA_SAVE;
+ else if (input == "n")
+ return SA_NOSAVE;
+ else if (input == "c")
+ throw JumpToTopException();
+ }
+ }
+ catch(JumpToTopException)
+ {
+ Rf_jump_to_toplevel();
+ }
+
+ // keep compiler happy
+ return SA_SAVE;
+}
+
+// NOTE: Win32 doesn't receive this function. As a result we only use
+// it in server mode (where it is used to support suspending as well as
+// notifying the browser of quit). In desktop mode we allow standard
+// R_CleanUp processing to take place. There are two important implications
+// of this:
+//
+// (1) In desktop mode we override do_quit and use it as an indicator
+// that we should save history and persistent client state (an
+// alternative would be to more eagerly persist this data)
+//
+// (2) In desktop mode we don't receive termination oriented events such
+// as quit, suicide, and cleanup. This means that the desktop process
+// must simply detect that we have exited and terminate itself. It also
+// means that cleanup of our http damons, file monitoring, etc. never
+// occurs (not an issue b/c our process is going away but worth noting)
+//
+void RCleanUp(SA_TYPE saveact, int status, int runLast)
+{
+ // perform cleanup that is coupled to our internal history,
+ // environment-saving, client-state, and graphics implementations
+ // and then delegate to internal R_CleanUp for the remainder
+ // of processing
+ try
+ {
+ // set to default if requested
+ if (saveact == SA_DEFAULT)
+ saveact = SaveAction ;
+
+ // prompt user to resolve SA_SAVEASK into SA_SAVE or SA_NOSAVE
+ if (saveact == SA_SAVEASK)
+ {
+ if (imageIsDirty() || !s_options.alwaysSaveHistory())
+ saveact = saveAsk(); // can Rf_jump_to_toplevel()
+ else
+ saveact = SA_NOSAVE; // auto-resolve to no save when not dirty
+ }
+
+ // if the session was quit by the user then run our termination code
+ bool sessionQuitByUser = (saveact != SA_SUICIDE) && !s_suspended ;
+ if (sessionQuitByUser)
+ {
+ // run last if requested (can throw error)
+ if (runLast)
+ R_dot_Last();
+
+ // save history if we either always save history or saveact == SA_SAVE
+ if (s_options.alwaysSaveHistory() || saveact == SA_SAVE)
+ {
+ FilePath historyPath = rHistoryFilePath();
+ Error error = consoleHistory().saveToFile(historyPath);
+ if (error)
+ reportHistoryAccessError("write history to", historyPath, error);
+ }
+
+ // save environment and history
+ if (saveact == SA_SAVE)
+ {
+ // attempt save if the image is dirty
+ if (imageIsDirty())
+ {
+ // attempt to save global environment. raise error (longjmp
+ // back to REPL) if there was a problem saving
+ Error error = saveDefaultGlobalEnvironment();
+ if (error)
+ r::exec::error(r::endUserErrorMessage(error));
+ }
+
+ // update state
+ saveact = SA_NOSAVE; // prevent R from saving
+ }
+
+ // since we've successfully completed the session we can safely
+ // remove any serialized session remaining on disk. note that if
+ // we do not successfully destroy the session then this would cause
+ // data loss when the previous session is read rather than the
+ // contents of .RData. therefore, we refuse to quit if we can't
+ // successfully destroy the suspended session
+ if (!r::session::state::destroy(s_suspendedSessionPath))
+ {
+ // this will cause us to jump back to the REPL loop
+ r::exec::error("Unable to quit (session cleanup failure)\n");
+ }
+
+ // commit client state
+ saveClientState(ClientStateCommitPersistentOnly);
+
+ // clear display
+ r::session::graphics::display().clear();
+
+ // notify client that the session has been quit
+ s_callbacks.quit();
+ }
+
+ // allow client to cleanup
+ bool terminatedNormally = saveact != SA_SUICIDE;
+ s_callbacks.cleanup(terminatedNormally);
+
+ // call internal cleanup (never .runLast because we do it above)
+ // NOTE: may want to replace RCleanUp entirely so that the client
+ // can see any errors which occur during cleanup in the console
+ // (they aren't seen now because the handling of quit obstructs them)
+ s_internalCallbacks.cleanUp(saveact, status, FALSE);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+// Replace the quit function so we can call our R_CleanUp hook. Note
+// that we need to take special measures to code this safely visa-vi
+// Rf_error long jmps and C++ exceptions. Currently, the RCleanUp
+// function can still long jump if runLast encounters an error. We
+// should re-write the combination of this function and RCleanUp to
+// be fully "error-safe" (not doing this now due to regression risk)
+#ifdef _WIN32
+bool win32Quit(const std::string& saveAction,
+ int status,
+ bool runLast,
+ std::string* pErrMsg)
+{
+ if (r::session::browserContextActive())
+ {
+ *pErrMsg = "unable to quit when browser is active";
+ return false;
+ }
+
+ // determine save action
+ SA_TYPE action = SA_DEFAULT;
+ if (saveAction == "ask")
+ action = SA_SAVEASK;
+ else if (saveAction == "no")
+ action = SA_NOSAVE;
+ else if (saveAction == "yes")
+ action = SA_SAVE;
+ else if (saveAction == "default")
+ action = SA_DEFAULT;
+ else
+ {
+ *pErrMsg = "Unknown save action: " + saveAction;
+ return false;
+ }
+
+ // clean up
+ Error error = r::exec::executeSafely(
+ boost::bind(&RCleanUp, action, status, runLast));
+ if (error)
+ {
+ *pErrMsg = r::endUserErrorMessage(error);
+ return false;
+ }
+
+ // failsafe in case we don't actually quit as a result of cleanup
+ ::exit(0);
+
+ // keep compiler happy
+ return true;
+}
+#endif
+
+
+Error run(const ROptions& options, const RCallbacks& callbacks)
+{
+ // copy options and callbacks
+ s_options = options ;
+ s_callbacks = callbacks ;
+
+ // set to default "C" numeric locale as-per R embedding docs
+ setlocale(LC_NUMERIC, "C") ;
+
+ // perform R discovery
+ r::session::RLocations rLocations;
+ Error error = r::session::discoverR(&rLocations);
+ if (error)
+ return error;
+
+ // R_HOME
+ core::system::setenv("R_HOME", rLocations.homePath);
+
+ // R_DOC_DIR (required by help-links.sh)
+ core::system::setenv("R_DOC_DIR", rLocations.docPath);
+
+ // R_LIBS_USER
+ if (!s_options.rLibsUser.empty())
+ core::system::setenv("R_LIBS_USER", s_options.rLibsUser);
+
+ // set compatible graphics engine version
+ int engineVersion = s_options.rCompatibleGraphicsEngineVersion;
+ graphics::setCompatibleEngineVersion(engineVersion);
+
+ // set client state paths
+ s_clientStatePath = s_options.userScratchPath.complete("client-state");
+ s_projectClientStatePath = s_options.scopedScratchPath.complete("pcs");
+
+ // set source reloading behavior
+ sourceManager().setAutoReload(options.autoReloadSource);
+
+ // initialize suspended session path
+ FilePath userScratchPath = s_options.userScratchPath;
+ s_suspendedSessionPath = userScratchPath.complete("suspended-session");
+
+ // initialize restart context
+ restartContext().initialize(s_options.scopedScratchPath,
+ s_options.sessionPort);
+
+ // register browseURL method
+ R_CallMethodDef browseURLMethod ;
+ browseURLMethod.name = "rs_browseURL";
+ browseURLMethod.fun = (DL_FUNC)rs_browseURL;
+ browseURLMethod.numArgs = 1;
+ r::routines::addCallMethod(browseURLMethod);
+
+ // register editFile method
+ R_CallMethodDef editFileMethod;
+ editFileMethod.name = "rs_editFile";
+ editFileMethod.fun = (DL_FUNC)rs_editFile;
+ editFileMethod.numArgs = 1;
+ r::routines::addCallMethod(editFileMethod);
+
+ // register showFile method
+ R_CallMethodDef showFileMethod;
+ showFileMethod.name = "rs_showFile";
+ showFileMethod.fun = (DL_FUNC)rs_showFile;
+ showFileMethod.numArgs = 3;
+ r::routines::addCallMethod(showFileMethod);
+
+ // register createUUID method
+ R_CallMethodDef createUUIDMethodDef ;
+ createUUIDMethodDef.name = "rs_createUUID" ;
+ createUUIDMethodDef.fun = (DL_FUNC) rs_createUUID ;
+ createUUIDMethodDef.numArgs = 0;
+ r::routines::addCallMethod(createUUIDMethodDef);
+
+ // register loadHistory method
+ R_CallMethodDef loadHistoryMethodDef ;
+ loadHistoryMethodDef.name = "rs_loadHistory" ;
+ loadHistoryMethodDef.fun = (DL_FUNC) rs_loadHistory ;
+ loadHistoryMethodDef.numArgs = 1;
+ r::routines::addCallMethod(loadHistoryMethodDef);
+
+ // register saveHistory method
+ R_CallMethodDef saveHistoryMethodDef ;
+ saveHistoryMethodDef.name = "rs_saveHistory" ;
+ saveHistoryMethodDef.fun = (DL_FUNC) rs_saveHistory ;
+ saveHistoryMethodDef.numArgs = 1;
+ r::routines::addCallMethod(saveHistoryMethodDef);
+
+
+ // run R
+
+ // should we run .Rprofile?
+ bool loadInitFile = false;
+ if (restartContext().hasSessionState())
+ {
+ loadInitFile = restartContext().rProfileOnRestore();
+ }
+ else
+ {
+ loadInitFile = !s_suspendedSessionPath.exists()
+ || options.rProfileOnResume;
+ }
+
+ // quiet for resume cases
+ bool quiet = restartContext().hasSessionState() ||
+ s_suspendedSessionPath.exists();
+
+ r::session::Callbacks cb;
+ cb.showMessage = RShowMessage;
+ cb.readConsole = RReadConsole;
+ cb.writeConsoleEx = RWriteConsoleEx;
+ cb.editFile = REditFile;
+ cb.busy = RBusy;
+ cb.chooseFile = RChooseFile;
+ cb.showFiles = RShowFiles;
+ cb.loadhistory = Rloadhistory;
+ cb.savehistory = Rsavehistory;
+ cb.addhistory = Raddhistory;
+ cb.suicide = RSuicide;
+ cb.cleanUp = RCleanUp;
+ r::session::runEmbeddedR(FilePath(rLocations.homePath),
+ options.userHomePath,
+ quiet,
+ loadInitFile,
+ s_options.saveWorkspace,
+ cb,
+ &s_internalCallbacks);
+
+ // keep compiler happy
+ return Success() ;
+}
+
+void ensureDeserialized()
+{
+ if (s_deferredDeserializationAction)
+ {
+ // do the deferred action
+ s_deferredDeserializationAction();
+ s_deferredDeserializationAction.clear();
+ }
+}
+
+namespace {
+
+void doSetClientMetrics(const RClientMetrics& metrics)
+{
+ // set the metrics
+ client_metrics::set(metrics);
+}
+
+} // anonymous namespace
+
+void setClientMetrics(const RClientMetrics& metrics)
+{
+ // get existing values in case this results in an error
+ RClientMetrics previousMetrics = client_metrics::get();
+
+ // attempt to set the metrics
+ Error error = r::exec::executeSafely(boost::bind(doSetClientMetrics,
+ metrics));
+
+ if (error)
+ {
+ // report to user
+ std::string errMsg = r::endUserErrorMessage(error);
+ REprintf((errMsg + "\n").c_str());
+
+ // restore previous values (but don't fire plotsChanged b/c
+ // the reset doesn't result in a change in graphics state)
+ r::exec::executeSafely(boost::bind(doSetClientMetrics, previousMetrics));
+ }
+}
+
+void reportAndLogWarning(const std::string& warning)
+{
+ std::string msg = "WARNING: " + warning + "\n";
+ RWriteConsoleEx(msg.c_str(), msg.length(), 1);
+ LOG_WARNING_MESSAGE("(Reported to User) " + warning);
+}
+
+bool isSuspendable(const std::string& currentPrompt)
+{
+ // NOTE: active file graphics devices (e.g. png or pdf) are wiped out
+ // during a suspend as are open connections. there may or may not be a
+ // way to make this more robust.
+
+ // are we not at the default prompt?
+ std::string defaultPrompt = r::options::getOption<std::string>("prompt");
+ if (currentPrompt != defaultPrompt)
+ return false;
+ else
+ return true;
+}
+
+
+bool suspend(const RSuspendOptions& options,
+ const FilePath& suspendedSessionPath,
+ bool disableSaveCompression,
+ bool force)
+{
+ // validate that force == true if disableSaveCompression is specified
+ // this is because save compression is disabled and the previous options
+ // are not restored, so it is only suitable to use this when we know
+ // the process is going to go away completely
+ if (disableSaveCompression)
+ BOOST_ASSERT(force == true);
+
+ // commit all client state
+ saveClientState(ClientStateCommitAll);
+
+ // if we are saving minimal then clear the graphics device
+ if (options.saveMinimal)
+ {
+ r::session::graphics::display().clear();
+ }
+
+ // save the session state. errors are handled internally and reported
+ // directly to the end user and written to the server log.
+ bool suspend = saveSessionState(options,
+ suspendedSessionPath,
+ disableSaveCompression);
+
+ // if we failed to save the data and are being forced then warn user
+ if (!suspend && force)
+ {
+ reportAndLogWarning("Forcing suspend of process in spite of all session "
+ "data not being fully saved.");
+ suspend = true;
+ }
+
+ // only continue with exiting the process if we actually succeed in saving
+ if(suspend)
+ {
+ // set suspended flag so cleanup code can act accordingly
+ s_suspended = true;
+
+ // call suspend hook
+ s_callbacks.suspended(options);
+
+ // clean up but don't save workspace or runLast because we have
+ // been suspended
+ RCleanUp(SA_NOSAVE, 0, FALSE);
+
+ // keep compiler happy (this line will never execute)
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool suspend(bool force)
+{
+ return suspend(RSuspendOptions(), s_suspendedSessionPath, false, force);
+}
+
+void suspendForRestart(const RSuspendOptions& options)
+{
+ suspend(options,
+ RestartContext::createSessionStatePath(s_options.scopedScratchPath,
+ s_options.sessionPort),
+ true, // disable save compression
+ true); // force suspend
+}
+
+// set save action
+const int kSaveActionNoSave = 0;
+const int kSaveActionSave = 1;
+const int kSaveActionAsk = -1;
+
+void setSaveAction(int saveAction)
+{
+ switch(saveAction)
+ {
+ case kSaveActionNoSave:
+ SaveAction = SA_NOSAVE;
+ break;
+ case kSaveActionSave:
+ SaveAction = SA_SAVE;
+ break;
+ case kSaveActionAsk:
+ default:
+ SaveAction = SA_SAVEASK;
+ break;
+ }
+
+}
+
+void setImageDirty(bool imageDirty)
+{
+ R_DirtyImage = imageDirty ? 1 : 0;
+}
+
+bool imageIsDirty()
+{
+ return R_DirtyImage != 0;
+}
+
+bool browserContextActive()
+{
+ return Rf_countContexts(CTXT_BROWSER, 1) > 0;
+}
+
+void quit(bool saveWorkspace)
+{
+ // invoke quit
+ std::string save = saveWorkspace ? "yes" : "no";
+ #ifdef _WIN32
+ std::string quitErr;
+ bool didQuit = win32Quit(save, 0, true, &quitErr);
+ if (!didQuit)
+ {
+ REprintf((quitErr + "\n").c_str());
+ LOG_ERROR_MESSAGE(quitErr);
+ }
+ #else
+ Error error = r::exec::RFunction("q", save, 0, true).call();
+ if (error)
+ {
+ REprintf((r::endUserErrorMessage(error) + "\n").c_str());
+ LOG_ERROR(error);
+ }
+ #endif
+}
+
+namespace utils {
+
+bool isR3()
+{
+ return s_isR3;
+}
+
+bool isDefaultPrompt(const std::string& prompt)
+{
+ return prompt == r::options::getOption<std::string>("prompt");
+}
+
+const FilePath& userHomePath()
+{
+ return s_options.userHomePath;
+}
+
+FilePath safeCurrentPath()
+{
+ return FilePath::safeCurrentPath(userHomePath());
+}
+
+FilePath tempFile(const std::string& prefix, const std::string& extension)
+{
+ std::string filename;
+ Error error = r::exec::RFunction("tempfile", prefix).call(&filename);
+ if (error)
+ LOG_ERROR(error);
+ FilePath filePath(string_utils::systemToUtf8(r::util::fixPath(filename)) + "." + extension);
+ return filePath;
+}
+
+FilePath tempDir()
+{
+ std::string tempDir;
+ Error error = r::exec::RFunction("tempdir").call(&tempDir);
+ if (error)
+ LOG_ERROR(error);
+ FilePath filePath(r::util::fixPath(tempDir));
+ return filePath;
+}
+
+SuppressOutputInScope::SuppressOutputInScope()
+{
+ s_suppressOuput = true;
+}
+
+SuppressOutputInScope::~SuppressOutputInScope()
+{
+ s_suppressOuput = false;
+}
+
+} // namespace utils
+
+} // namespace session
+} // namespace r
diff --git a/src/cpp/r/session/RSessionState.cpp b/src/cpp/r/session/RSessionState.cpp
new file mode 100644
index 0000000..ce6b836
--- /dev/null
+++ b/src/cpp/r/session/RSessionState.cpp
@@ -0,0 +1,535 @@
+/*
+ * RSessionState.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RSessionState.hpp"
+
+#include <algorithm>
+
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Settings.hpp>
+#include <core/Log.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/Environment.hpp>
+
+#include <r/RExec.hpp>
+#include <r/ROptions.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RConsoleActions.hpp>
+#include <r/session/RConsoleHistory.hpp>
+#include <r/session/RGraphics.hpp>
+
+#include "RClientMetrics.hpp"
+#include "RSearchPath.hpp"
+#include "graphics/RGraphicsPlotManager.hpp"
+
+using namespace core ;
+
+namespace r {
+
+using namespace exec ;
+
+namespace session {
+namespace state {
+
+namespace {
+
+// file names
+const char * const kSettingsFile = "settings";
+const char * const kConsoleActionsFile = "console_actions";
+const char * const kOptionsFile = "options";
+const char * const kEnvironmentVars = "environment_vars";
+const char * const kLibPathsFile = "libpaths";
+const char * const kHistoryFile = "history";
+const char * const kPlotsFile = "plots";
+const char * const kPlotsDir = "plots_dir";
+const char * const kSearchPath = "search_path";
+const char * const kGlobalEnvironment = "global_environment";
+
+// settings
+const char * const kWorkingDirectory = "working_directory";
+const char * const kDevModeOn = "dev_mode_on";
+const char * const kRProfileOnRestore = "r_profile_on_restore";
+
+
+Error saveLibPaths(const FilePath& libPathsFile)
+{
+ std::string file = string_utils::utf8ToSystem(libPathsFile.absolutePath());
+ return r::exec::RFunction(".rs.saveLibPaths", file).call();
+}
+
+Error restoreLibPaths(const FilePath& libPathsFile)
+{
+ if (!libPathsFile.exists())
+ return Success();
+
+ std::string file = string_utils::utf8ToSystem(libPathsFile.absolutePath());
+ return r::exec::RFunction(".rs.restoreLibPaths", file).call();
+}
+
+Error saveEnvironmentVars(const FilePath& envFile)
+{
+ // remove then create settings file
+ Error error = envFile.removeIfExists();
+ if (error)
+ return error;
+ core::Settings envSettings;
+ error = envSettings.initialize(envFile);
+ if (error)
+ return error;
+
+ // get environment and write it to the file
+ core::system::Options env;
+ core::system::environment(&env);
+ envSettings.beginUpdate();
+ BOOST_FOREACH(const core::system::Option& var, env)
+ {
+ envSettings.set(var.first, var.second);
+ }
+ envSettings.endUpdate();
+
+ return Success();
+}
+
+void setEnvVar(const std::string& name, const std::string& value)
+{
+ core::system::setenv(name, value);
+}
+
+Error restoreEnvironmentVars(const FilePath& envFile)
+{
+ if (!envFile.exists())
+ return Success();
+
+ // read settings file
+ core::Settings envSettings;
+ Error error = envSettings.initialize(envFile);
+ if (error)
+ return error;
+
+ // set the environment vars
+ envSettings.forEach(setEnvVar);
+
+ return Success();
+}
+
+Error restoreWorkingDirectory(const FilePath& userHomePath,
+ const std::string& workingDirectory)
+{
+ // resolve working dir
+ FilePath workingDirPath = FilePath::resolveAliasedPath(workingDirectory,
+ userHomePath);
+
+ // restore working path if it exists (else revert to home)
+ if (workingDirPath.exists())
+ return workingDirPath.makeCurrentPath() ;
+ else
+ return utils::userHomePath().makeCurrentPath();
+}
+
+const char * const kSaving = "saving";
+const char * const kRestoring = "restoring";
+const char * const kCleaningUp = "cleaning up";
+
+void reportError(const std::string& action,
+ const std::string& context,
+ const Error& error,
+ const ErrorLocation& location,
+ const boost::function<void(const char*)>& reportFunction =
+ boost::function<void(const char*)>())
+{
+ // build the message
+ std::string message = "Error " + action + " session";
+ if (!context.empty())
+ message += std::string(" (" + context + ")");
+
+ // add context to error and log it
+ Error serializationError = error ;
+ serializationError.addProperty("context", message);
+ core::log::logError(serializationError, location);
+
+ // notify end-user
+ std::string report = message + ": " + error.code().message() + "\n";
+ if (reportFunction)
+ reportFunction(report.c_str());
+ else
+ REprintf(report.c_str());
+}
+
+struct ErrorRecorder
+{
+ ErrorRecorder(std::string* pMessages)
+ : pMessages_(pMessages)
+ {
+ }
+
+ void operator()(const char* message)
+ {
+ pMessages_->operator+=(message);
+ }
+
+ std::string* pMessages_ ;
+};
+
+void saveDevMode(Settings* pSettings)
+{
+ // check if dev-mode is on -- if it is then note this and turn it off
+ // (so that at restore time we can explicitly re-enable it)
+ bool devModeOn = false;
+ Error error = r::exec::RFunction(".rs.devModeOn").call(&devModeOn);
+ if (error)
+ LOG_ERROR(error);
+ if (devModeOn)
+ {
+ // set devmode bit in suspended settings
+ pSettings->set(kDevModeOn, true);
+
+ // turn dev mode off -- this is important so that dev mode undoes
+ // its manipulations of the prompt and libpaths before they are saved
+ // suppress output to eliminate dev_mode OFF message
+ // ignore error on purpose -- will happen if devtools isn't intalled
+ r::session::utils::SuppressOutputInScope suppressOutput;
+ error = r::exec::RFunction("devtools:::dev_mode", false).call();
+ }
+
+}
+
+void initSaveContext(const FilePath& statePath,
+ Settings* pSettings,
+ bool* pSaved)
+{
+ // ensure the context exists
+ Error error = statePath.ensureDirectory();
+ if (error)
+ {
+ reportError(kSaving, "creating directory", error, ERROR_LOCATION);
+ *pSaved = false;
+ }
+
+ // init session settings
+ error = pSettings->initialize(statePath.complete(kSettingsFile));
+ if (error)
+ {
+ reportError(kSaving, kSettingsFile, error, ERROR_LOCATION);
+ *pSaved = false;
+ }
+}
+
+void saveWorkingContext(const FilePath& statePath,
+ Settings* pSettings,
+ bool* pSaved)
+{
+ // save history
+ FilePath historyPath = statePath.complete(kHistoryFile);
+ Error error = consoleHistory().saveToFile(historyPath);
+ if (error)
+ {
+ reportError(kSaving, kHistoryFile, error, ERROR_LOCATION);
+ *pSaved = false;
+ }
+
+ // save client metrics
+ client_metrics::save(pSettings);
+
+ // save aliased path to current working directory
+ std::string workingDirectory = FilePath::createAliasedPath(
+ utils::safeCurrentPath(),
+ r::session::utils::userHomePath());
+ pSettings->set(kWorkingDirectory, workingDirectory);
+
+ // save console actions
+ FilePath consoleActionsPath = statePath.complete(kConsoleActionsFile);
+ error = consoleActions().saveToFile(consoleActionsPath);
+ if (error)
+ {
+ reportError(kSaving, kConsoleActionsFile, error, ERROR_LOCATION);
+ *pSaved = false;
+ }
+}
+
+} // anonymous namespace
+
+
+
+bool save(const FilePath& statePath,
+ bool serverMode,
+ bool excludePackages,
+ bool disableSaveCompression)
+{
+ // initialize context
+ Settings settings;
+ bool saved = true;
+ initSaveContext(statePath, &settings, &saved);
+
+ // set r profile on restore
+ settings.set(kRProfileOnRestore, !excludePackages);
+
+ // save environment variables
+ Error error = saveEnvironmentVars(statePath.complete(kEnvironmentVars));
+ if (error)
+ {
+ reportError(kSaving, kEnvironmentVars, error, ERROR_LOCATION);
+ saved = false;
+ }
+
+ // if we are in server mode then we just need to write the plot
+ // state index (because the location of the graphics directory is stable)
+ if (serverMode)
+ {
+ error = graphics::plotManager().savePlotsState();
+ if (error)
+ {
+ reportError(kSaving, kPlotsFile, error, ERROR_LOCATION);
+ saved = false;
+ }
+ }
+ else
+ {
+ error = graphics::plotManager().serialize(statePath.complete(kPlotsDir));
+ if (error)
+ {
+ reportError(kSaving, kPlotsDir, error, ERROR_LOCATION);
+ saved = false;
+ }
+ }
+
+ // handle dev mode -- note that this MUST be executed before
+ // save libpaths and save options because it manipulates them
+ // (by disabling devmode)
+ saveDevMode(&settings);
+
+ // save libpaths
+ error = saveLibPaths(statePath.complete(kLibPathsFile));
+ if (error)
+ {
+ reportError(kSaving, kLibPathsFile, error, ERROR_LOCATION);
+ saved = false;
+ }
+
+ // save options
+ error = r::options::saveOptions(statePath.complete(kOptionsFile));
+ if (error)
+ {
+ reportError(kSaving, kOptionsFile, error, ERROR_LOCATION);
+ saved = false;
+ }
+
+ // save working context
+ saveWorkingContext(statePath, &settings, &saved);
+
+ // save search path (disable save compression if requested)
+ if (disableSaveCompression)
+ {
+ error = r::exec::RFunction(".rs.disableSaveCompression").call();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ if (!excludePackages)
+ {
+ error = search_path::save(statePath);
+ if (error)
+ {
+ reportError(kSaving, kSearchPath, error, ERROR_LOCATION);
+ saved = false;
+ }
+ }
+ else
+ {
+ error = search_path::saveGlobalEnvironment(statePath);
+ if (error)
+ {
+ reportError(kSaving, kGlobalEnvironment, error, ERROR_LOCATION);
+ saved = false;
+ }
+ }
+
+ // return status
+ return saved;
+}
+
+
+bool saveMinimal(const core::FilePath& statePath,
+ bool saveGlobalEnvironment)
+{
+ // initialize context
+ Settings settings;
+ bool saved = true;
+ initSaveContext(statePath, &settings, &saved);
+
+ // set r profile on restore
+ settings.set(kRProfileOnRestore, true);
+
+ // handle dev mode
+ saveDevMode(&settings);
+
+ // save working context
+ saveWorkingContext(statePath, &settings, &saved);
+
+ // save global environment if requested
+ if (saveGlobalEnvironment)
+ {
+ // disable save compression
+ Error error = r::exec::RFunction(".rs.disableSaveCompression").call();
+ if (error)
+ LOG_ERROR(error);
+
+ error = search_path::saveGlobalEnvironment(statePath);
+ if (error)
+ {
+ reportError(kSaving, kGlobalEnvironment, error, ERROR_LOCATION);
+ saved = false;
+ }
+ }
+
+
+
+ // return status
+ return saved;
+}
+
+bool rProfileOnRestore(const core::FilePath& statePath)
+{
+ Settings settings ;
+ Error error = settings.initialize(statePath.complete(kSettingsFile));
+ if (error)
+ {
+ LOG_ERROR(error);
+ return true;
+ }
+
+ return settings.getBool(kRProfileOnRestore, true);
+}
+
+Error deferredRestore(const FilePath& statePath, bool serverMode)
+{
+ // search path
+ Error error = search_path::restore(statePath);
+ if (error)
+ return error;
+
+ // if we are in server mode we just need to read the plots state
+ // file (because the location of the graphics directory is stable)
+ if (serverMode)
+ {
+ return graphics::plotManager().restorePlotsState();
+ }
+ else
+ {
+ FilePath plotsDir = statePath.complete(kPlotsDir);
+ if (plotsDir.exists())
+ return graphics::plotManager().deserialize(plotsDir);
+ else
+ return Success();
+ }
+}
+
+bool restore(const FilePath& statePath,
+ bool serverMode,
+ boost::function<Error()>* pDeferredRestoreAction,
+ std::string* pErrorMessages)
+{
+ // setup error buffer
+ ErrorRecorder er(pErrorMessages);
+
+ // init session settings (used below)
+ Settings settings ;
+ Error error = settings.initialize(statePath.complete(kSettingsFile));
+ if (error)
+ reportError(kRestoring, kSettingsFile, error, ERROR_LOCATION, er);
+
+ // restore console actions
+ FilePath consoleActionsPath = statePath.complete(kConsoleActionsFile);
+ error = consoleActions().loadFromFile(consoleActionsPath);
+ if (error)
+ reportError(kRestoring, kConsoleActionsFile, error, ERROR_LOCATION, er);
+
+ // restore working directory
+ std::string workingDir = settings.get(kWorkingDirectory);
+ error = restoreWorkingDirectory(r::session::utils::userHomePath(),
+ workingDir);
+ if (error)
+ reportError(kRestoring, kWorkingDirectory, error, ERROR_LOCATION, er);
+
+ // restore options
+ FilePath optionsPath = statePath.complete(kOptionsFile);
+ if (optionsPath.exists())
+ {
+ error = r::options::restoreOptions(optionsPath);
+ if (error)
+ reportError(kRestoring, kOptionsFile, error, ERROR_LOCATION, er);
+ }
+
+ // restore libpaths
+ error = restoreLibPaths(statePath.complete(kLibPathsFile));
+ if (error)
+ reportError(kRestoring, kLibPathsFile, error, ERROR_LOCATION, er);
+
+ // restore devmode
+ if (settings.getBool(kDevModeOn, false))
+ {
+ // ignore error -- will occur if devtools isn't installed
+ error = r::exec::RFunction("devtools:::dev_mode", true).call();
+ }
+
+ // restore client_metrics (must execute after restore of options for
+ // console width but prior to graphics::device for device size)
+ client_metrics::restore(settings);
+
+ // restore history
+ FilePath historyFilePath = statePath.complete(kHistoryFile);
+ error = consoleHistory().loadFromFile(historyFilePath, false);
+ if (error)
+ reportError(kRestoring, kHistoryFile, error, ERROR_LOCATION, er);
+
+ // restore environment vars
+ error = restoreEnvironmentVars(statePath.complete(kEnvironmentVars));
+ if (error)
+ reportError(kRestoring, kEnvironmentVars, error, ERROR_LOCATION, er);
+
+ // set deferred restore action. this encapsulates parts of the restore
+ // process that are potentially highly latent. this allows clients
+ // to bring their UI up and then receive an event indicating that the
+ // latent deserialization actions are taking place
+ *pDeferredRestoreAction = boost::bind(deferredRestore,
+ statePath, serverMode);
+
+ // return true if there were no error messages
+ return pErrorMessages->empty();
+}
+
+bool destroy(const FilePath& statePath)
+{
+ Error error = statePath.removeIfExists();
+ if (error)
+ {
+ reportError(kCleaningUp, "", error, ERROR_LOCATION);
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+
+
+} // namespace state
+} // namespace session
+} // namespace r
diff --git a/src/cpp/r/session/RSessionState.hpp b/src/cpp/r/session/RSessionState.hpp
new file mode 100644
index 0000000..afa7b08
--- /dev/null
+++ b/src/cpp/r/session/RSessionState.hpp
@@ -0,0 +1,56 @@
+/*
+ * RSessionState.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_R_SESSION_STATE_HPP
+#define R_R_SESSION_STATE_HPP
+
+#include <string>
+
+#include <boost/function.hpp>
+
+#include <core/Error.hpp>
+
+namespace core {
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace state {
+
+bool save(const core::FilePath& statePath,
+ bool serverMode,
+ bool excludePackages,
+ bool disableSaveCompression);
+
+bool saveMinimal(const core::FilePath& statePath,
+ bool saveGlobalEnvironment);
+
+
+bool rProfileOnRestore(const core::FilePath& statePath);
+
+bool restore(const core::FilePath& statePath,
+ bool serverMode,
+ boost::function<core::Error()>* pDeferredRestoreAction,
+ std::string* pErrorMessages);
+
+bool destroy(const core::FilePath& statePath);
+
+} // namespace state
+} // namespace session
+} // namespace r
+
+#endif // R_R_SESSION_STATE_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsDevDesc.cpp b/src/cpp/r/session/graphics/RGraphicsDevDesc.cpp
new file mode 100644
index 0000000..7d4fa1d
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsDevDesc.cpp
@@ -0,0 +1,675 @@
+/*
+ * RGraphicsDevDesc.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsDevDesc.hpp"
+
+#include <cstdlib>
+
+#include <R_ext/RS.h>
+
+// compatability structs for previous graphics engine versions
+extern "C" {
+
+struct DevDescVersion5
+{
+ double left;
+ double right;
+ double bottom;
+ double top;
+ double clipLeft;
+ double clipRight;
+ double clipBottom;
+ double clipTop;
+ double xCharOffset;
+ double yCharOffset;
+ double yLineBias;
+ double ipr[2];
+ double cra[2];
+ double gamma;
+ Rboolean canClip;
+ Rboolean canChangeGamma;
+ int canHAdj;
+ double startps;
+ int startcol;
+ int startfill;
+ int startlty;
+ int startfont;
+ double startgamma;
+ void *deviceSpecific;
+ Rboolean displayListOn;
+ Rboolean canGenMouseDown;
+ Rboolean canGenMouseMove;
+ Rboolean canGenMouseUp;
+ Rboolean canGenKeybd;
+ Rboolean gettingEvent;
+
+ void (*activate)(const pDevDesc );
+ void (*circle)(double x, double y, double r, const pGEcontext gc, pDevDesc dd);
+ void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dd);
+ void (*close)(pDevDesc dd);
+ void (*deactivate)(pDevDesc );
+ Rboolean (*locator)(double *x, double *y, pDevDesc dd);
+ void (*line)(double x1, double y1, double x2, double y2,
+ const pGEcontext gc, pDevDesc dd);
+ void (*metricInfo)(int c, const pGEcontext gc,
+ double* ascent, double* descent, double* width,
+ pDevDesc dd);
+ void (*mode)(int mode, pDevDesc dd);
+ void (*newPage)(const pGEcontext gc, pDevDesc dd);
+ void (*polygon)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*polyline)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*rect)(double x0, double y0, double x1, double y1,
+ const pGEcontext gc, pDevDesc dd);
+
+ // end of devDescUniversal
+
+ void (*size)(double *left, double *right, double *bottom, double *top,
+ pDevDesc dd);
+ double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dd);
+ void (*text)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ void (*onExit)(pDevDesc dd);
+ SEXP (*getEvent)(SEXP, const char *);
+ Rboolean (*newFrameConfirm)(pDevDesc dd);
+
+ Rboolean hasTextUTF8; /* and strWidthUTF8 */
+ void (*textUTF8)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ double (*strWidthUTF8)(const char *str, const pGEcontext gc, pDevDesc dd);
+ Rboolean wantSymbolUTF8;
+ Rboolean useRotatedTextInContour;
+ char reserved[64];
+};
+
+struct DevDescVersion6
+{
+ double left;
+ double right;
+ double bottom;
+ double top;
+ double clipLeft;
+ double clipRight;
+ double clipBottom;
+ double clipTop;
+ double xCharOffset;
+ double yCharOffset;
+ double yLineBias;
+ double ipr[2];
+ double cra[2];
+ double gamma;
+ Rboolean canClip;
+ Rboolean canChangeGamma;
+ int canHAdj;
+ double startps;
+ int startcol;
+ int startfill;
+ int startlty;
+ int startfont;
+ double startgamma;
+ void *deviceSpecific;
+ Rboolean displayListOn;
+ Rboolean canGenMouseDown;
+ Rboolean canGenMouseMove;
+ Rboolean canGenMouseUp;
+ Rboolean canGenKeybd;
+ Rboolean gettingEvent;
+
+ void (*activate)(const pDevDesc );
+ void (*circle)(double x, double y, double r, const pGEcontext gc, pDevDesc dd);
+ void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dd);
+ void (*close)(pDevDesc dd);
+ void (*deactivate)(pDevDesc );
+ Rboolean (*locator)(double *x, double *y, pDevDesc dd);
+ void (*line)(double x1, double y1, double x2, double y2,
+ const pGEcontext gc, pDevDesc dd);
+ void (*metricInfo)(int c, const pGEcontext gc,
+ double* ascent, double* descent, double* width,
+ pDevDesc dd);
+ void (*mode)(int mode, pDevDesc dd);
+ void (*newPage)(const pGEcontext gc, pDevDesc dd);
+ void (*polygon)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*polyline)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*rect)(double x0, double y0, double x1, double y1,
+ const pGEcontext gc, pDevDesc dd);
+
+
+ // dev_Raster and dev_Cap added in version 6
+ void (*raster)(unsigned int *raster, int w, int h,
+ double x, double y,
+ double width, double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc, pDevDesc dd);
+ SEXP (*cap)(pDevDesc dd);
+
+ void (*size)(double *left, double *right, double *bottom, double *top,
+ pDevDesc dd);
+ double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dd);
+ void (*text)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ void (*onExit)(pDevDesc dd);
+ SEXP (*getEvent)(SEXP, const char *);
+ Rboolean (*newFrameConfirm)(pDevDesc dd);
+
+ Rboolean hasTextUTF8; /* and strWidthUTF8 */
+ void (*textUTF8)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ double (*strWidthUTF8)(const char *str, const pGEcontext gc, pDevDesc dd);
+ Rboolean wantSymbolUTF8;
+ Rboolean useRotatedTextInContour;
+ char reserved[64];
+};
+
+struct DevDescVersion7
+{
+ double left;
+ double right;
+ double bottom;
+ double top;
+ double clipLeft;
+ double clipRight;
+ double clipBottom;
+ double clipTop;
+ double xCharOffset;
+ double yCharOffset;
+ double yLineBias;
+ double ipr[2];
+ double cra[2];
+ double gamma;
+ Rboolean canClip;
+ Rboolean canChangeGamma;
+ int canHAdj;
+ double startps;
+ int startcol;
+ int startfill;
+ int startlty;
+ int startfont;
+ double startgamma;
+ void *deviceSpecific;
+ Rboolean displayListOn;
+ Rboolean canGenMouseDown;
+ Rboolean canGenMouseMove;
+ Rboolean canGenMouseUp;
+ Rboolean canGenKeybd;
+ Rboolean gettingEvent;
+
+ void (*activate)(const pDevDesc );
+ void (*circle)(double x, double y, double r, const pGEcontext gc, pDevDesc dd);
+ void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dd);
+ void (*close)(pDevDesc dd);
+ void (*deactivate)(pDevDesc );
+ Rboolean (*locator)(double *x, double *y, pDevDesc dd);
+ void (*line)(double x1, double y1, double x2, double y2,
+ const pGEcontext gc, pDevDesc dd);
+ void (*metricInfo)(int c, const pGEcontext gc,
+ double* ascent, double* descent, double* width,
+ pDevDesc dd);
+ void (*mode)(int mode, pDevDesc dd);
+ void (*newPage)(const pGEcontext gc, pDevDesc dd);
+ void (*polygon)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*polyline)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*rect)(double x0, double y0, double x1, double y1,
+ const pGEcontext gc, pDevDesc dd);
+
+
+ // dev_Raster and dev_Cap added in version 6
+ void (*raster)(unsigned int *raster, int w, int h,
+ double x, double y,
+ double width, double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc, pDevDesc dd);
+ SEXP (*cap)(pDevDesc dd);
+
+ void (*size)(double *left, double *right, double *bottom, double *top,
+ pDevDesc dd);
+ double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dd);
+ void (*text)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ void (*onExit)(pDevDesc dd);
+ SEXP (*getEvent)(SEXP, const char *);
+ Rboolean (*newFrameConfirm)(pDevDesc dd);
+
+ Rboolean hasTextUTF8; /* and strWidthUTF8 */
+ void (*textUTF8)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ double (*strWidthUTF8)(const char *str, const pGEcontext gc, pDevDesc dd);
+ Rboolean wantSymbolUTF8;
+ Rboolean useRotatedTextInContour;
+
+ // eventEnv and eventHelper added in version 7
+ SEXP eventEnv;
+ void (*eventHelper)(pDevDesc dd, int code);
+
+ char reserved[64];
+};
+
+struct DevDescVersion8
+{
+ double left;
+ double right;
+ double bottom;
+ double top;
+ double clipLeft;
+ double clipRight;
+ double clipBottom;
+ double clipTop;
+ double xCharOffset;
+ double yCharOffset;
+ double yLineBias;
+ double ipr[2];
+ double cra[2];
+ double gamma;
+ Rboolean canClip;
+ Rboolean canChangeGamma;
+ int canHAdj;
+ double startps;
+ int startcol;
+ int startfill;
+ int startlty;
+ int startfont;
+ double startgamma;
+ void *deviceSpecific;
+ Rboolean displayListOn;
+ Rboolean canGenMouseDown;
+ Rboolean canGenMouseMove;
+ Rboolean canGenMouseUp;
+ Rboolean canGenKeybd;
+ Rboolean gettingEvent;
+
+ void (*activate)(const pDevDesc );
+ void (*circle)(double x, double y, double r, const pGEcontext gc, pDevDesc dd);
+ void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dd);
+ void (*close)(pDevDesc dd);
+ void (*deactivate)(pDevDesc );
+ Rboolean (*locator)(double *x, double *y, pDevDesc dd);
+ void (*line)(double x1, double y1, double x2, double y2,
+ const pGEcontext gc, pDevDesc dd);
+ void (*metricInfo)(int c, const pGEcontext gc,
+ double* ascent, double* descent, double* width,
+ pDevDesc dd);
+ void (*mode)(int mode, pDevDesc dd);
+ void (*newPage)(const pGEcontext gc, pDevDesc dd);
+ void (*polygon)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*polyline)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*rect)(double x0, double y0, double x1, double y1,
+ const pGEcontext gc, pDevDesc dd);
+
+ // dev_Path added in version 8
+ void (*path)(double *x, double *y,
+ int npoly, int *nper,
+ Rboolean winding,
+ const pGEcontext gc, pDevDesc dd);
+
+ // dev_Raster and dev_Cap added in version 6
+ void (*raster)(unsigned int *raster, int w, int h,
+ double x, double y,
+ double width, double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc, pDevDesc dd);
+ SEXP (*cap)(pDevDesc dd);
+
+ void (*size)(double *left, double *right, double *bottom, double *top,
+ pDevDesc dd);
+ double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dd);
+ void (*text)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ void (*onExit)(pDevDesc dd);
+ SEXP (*getEvent)(SEXP, const char *);
+ Rboolean (*newFrameConfirm)(pDevDesc dd);
+
+ Rboolean hasTextUTF8; /* and strWidthUTF8 */
+ void (*textUTF8)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ double (*strWidthUTF8)(const char *str, const pGEcontext gc, pDevDesc dd);
+ Rboolean wantSymbolUTF8;
+ Rboolean useRotatedTextInContour;
+
+ // eventEnv and eventHelper added in version 7
+ SEXP eventEnv;
+ void (*eventHelper)(pDevDesc dd, int code);
+
+ char reserved[64];
+};
+
+} // extern C
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace handler {
+namespace dev_desc {
+
+namespace {
+
+template <typename T>
+void copyCommonMembers(const DevDescVersion9& sourceDevDesc,
+ T* pTargetDevDesc)
+{
+ pTargetDevDesc->left = sourceDevDesc.left;
+ pTargetDevDesc->right = sourceDevDesc.right;
+ pTargetDevDesc->bottom = sourceDevDesc.bottom;
+ pTargetDevDesc->top = sourceDevDesc.top;
+ pTargetDevDesc->clipLeft = sourceDevDesc.clipLeft;
+ pTargetDevDesc->clipRight = sourceDevDesc.clipRight;
+ pTargetDevDesc->clipBottom = sourceDevDesc.clipBottom;
+ pTargetDevDesc->clipTop = sourceDevDesc.clipTop;
+ pTargetDevDesc->xCharOffset = sourceDevDesc.xCharOffset;
+ pTargetDevDesc->yCharOffset = sourceDevDesc.yCharOffset;
+ pTargetDevDesc->yLineBias = sourceDevDesc.yLineBias;
+ pTargetDevDesc->ipr[0] = sourceDevDesc.ipr[0];
+ pTargetDevDesc->ipr[1] = sourceDevDesc.ipr[1];
+ pTargetDevDesc->cra[0] = sourceDevDesc.cra[0];
+ pTargetDevDesc->cra[1] = sourceDevDesc.cra[1];
+ pTargetDevDesc->gamma = sourceDevDesc.gamma;
+ pTargetDevDesc->canClip = sourceDevDesc.canClip;
+ pTargetDevDesc->canChangeGamma = sourceDevDesc.canChangeGamma;
+ pTargetDevDesc->canHAdj = sourceDevDesc.canHAdj;
+ pTargetDevDesc->startps = sourceDevDesc.startps;
+ pTargetDevDesc->startcol = sourceDevDesc.startcol;
+ pTargetDevDesc->startfill = sourceDevDesc.startfill;
+ pTargetDevDesc->startlty = sourceDevDesc.startlty;
+ pTargetDevDesc->startfont = sourceDevDesc.startfont;
+ pTargetDevDesc->startgamma = sourceDevDesc.startgamma;
+ pTargetDevDesc->deviceSpecific = sourceDevDesc.deviceSpecific;
+ pTargetDevDesc->displayListOn = sourceDevDesc.displayListOn;
+ pTargetDevDesc->canGenMouseDown = sourceDevDesc.canGenMouseDown;
+ pTargetDevDesc->canGenMouseMove = sourceDevDesc.canGenMouseMove;
+ pTargetDevDesc->canGenMouseUp = sourceDevDesc.canGenMouseUp;
+ pTargetDevDesc->canGenKeybd = sourceDevDesc.canGenKeybd;
+ pTargetDevDesc->gettingEvent = sourceDevDesc.gettingEvent;
+ pTargetDevDesc->activate = sourceDevDesc.activate;
+ pTargetDevDesc->circle = sourceDevDesc.circle;
+ pTargetDevDesc->clip = sourceDevDesc.clip;
+ pTargetDevDesc->close = sourceDevDesc.close;
+ pTargetDevDesc->deactivate = sourceDevDesc.deactivate;
+ pTargetDevDesc->locator = sourceDevDesc.locator;
+ pTargetDevDesc->line = sourceDevDesc.line;
+ pTargetDevDesc->metricInfo = sourceDevDesc.metricInfo;
+ pTargetDevDesc->mode = sourceDevDesc.mode;
+ pTargetDevDesc->newPage = sourceDevDesc.newPage;
+ pTargetDevDesc->polygon = sourceDevDesc.polygon;
+ pTargetDevDesc->polyline = sourceDevDesc.polyline;
+ pTargetDevDesc->rect = sourceDevDesc.rect;
+ pTargetDevDesc->size = sourceDevDesc.size;
+ pTargetDevDesc->strWidth = sourceDevDesc.strWidth;
+ pTargetDevDesc->text = sourceDevDesc.text;
+ pTargetDevDesc->onExit = sourceDevDesc.onExit;
+ pTargetDevDesc->getEvent = sourceDevDesc.getEvent;
+ pTargetDevDesc->newFrameConfirm = sourceDevDesc.newFrameConfirm;
+ pTargetDevDesc->hasTextUTF8 = sourceDevDesc.hasTextUTF8;
+ pTargetDevDesc->textUTF8 = sourceDevDesc.textUTF8;
+ pTargetDevDesc->strWidthUTF8 = sourceDevDesc.strWidthUTF8;
+ pTargetDevDesc->wantSymbolUTF8 = sourceDevDesc.wantSymbolUTF8;
+ pTargetDevDesc->useRotatedTextInContour = sourceDevDesc.useRotatedTextInContour;
+
+ // zero out reserved
+ ::memset(pTargetDevDesc->reserved, 0, 64);
+}
+
+template <typename T>
+T* allocAndInitCommonMembers(const DevDescVersion9& devDescVersion9)
+{
+ T* pDevDesc = (T*) std::calloc(1, sizeof(T));
+ copyCommonMembers(devDescVersion9, pDevDesc);
+ return pDevDesc;
+}
+
+} // anonymous namespace
+
+pDevDesc allocate(const DevDescVersion9& devDescVersion9)
+{
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 5:
+ {
+ DevDescVersion5* pDD = allocAndInitCommonMembers<DevDescVersion5>(
+ devDescVersion9);
+ return (pDevDesc)pDD;
+ }
+
+ case 6:
+ {
+ DevDescVersion6* pDD = allocAndInitCommonMembers<DevDescVersion6>(
+ devDescVersion9);
+
+ pDD->raster = devDescVersion9.raster;
+ pDD->cap = devDescVersion9.cap;
+
+ return (pDevDesc)pDD;
+ }
+
+ case 7:
+ {
+ DevDescVersion7* pDD = allocAndInitCommonMembers<DevDescVersion7>(
+ devDescVersion9);
+
+ pDD->raster = devDescVersion9.raster;
+ pDD->cap = devDescVersion9.cap;
+ pDD->eventEnv = devDescVersion9.eventEnv;
+ pDD->eventHelper = devDescVersion9.eventHelper;
+
+ return (pDevDesc)pDD;
+ }
+
+
+ case 8:
+ {
+ DevDescVersion8* pDD = allocAndInitCommonMembers<DevDescVersion8>(
+ devDescVersion9);
+
+ pDD->path = devDescVersion9.path;
+ pDD->raster = devDescVersion9.raster;
+ pDD->cap = devDescVersion9.cap;
+ pDD->eventEnv = devDescVersion9.eventEnv;
+ pDD->eventHelper = devDescVersion9.eventHelper;
+
+ return (pDevDesc)pDD;
+ }
+
+ // NOTE: graphics device won't be initialized unless we confirm
+ // that the current graphics engine version is v9 compatible
+ case 9:
+ default:
+ {
+ DevDescVersion9* pDD = (DevDescVersion9*) std::calloc(
+ 1, sizeof(DevDescVersion9));
+ *pDD = devDescVersion9;
+
+ return (pDevDesc)pDD;
+ }
+
+ }
+}
+
+void setSize(pDevDesc pDD)
+{
+ // get pointer to size function
+ void (*pSizeFn)(double*, double*, double*, double*, pDevDesc);
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 5:
+ pSizeFn = ((DevDescVersion5*)pDD)->size;
+ break;
+ case 6:
+ pSizeFn = ((DevDescVersion6*)pDD)->size;
+ break;
+ case 7:
+ pSizeFn = ((DevDescVersion7*)pDD)->size;
+ break;
+ case 8:
+ pSizeFn = ((DevDescVersion8*)pDD)->size;
+ break;
+ case 9:
+ default:
+ pSizeFn = ((DevDescVersion9*)pDD)->size;
+ break;
+ }
+
+ // set size
+ pSizeFn(&(pDD->left),
+ &(pDD->right),
+ &(pDD->bottom),
+ &(pDD->top),
+ pDD);
+
+ // set clip region
+ pSizeFn(&(pDD->clipLeft),
+ &(pDD->clipRight),
+ &(pDD->clipBottom),
+ &(pDD->clipTop),
+ pDD);
+}
+
+void path(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ // get pointer to path function
+ void (*pPathFn)(double*, double*, int, int*, Rboolean, const pGEcontext,
+ pDevDesc);
+
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 8:
+ pPathFn = ((DevDescVersion8*)dd)->path;
+ break;
+ case 9:
+ default:
+ pPathFn = ((DevDescVersion9*)dd)->path;
+ break;
+ }
+
+ // call it
+ pPathFn(x, y, npoly, nper, winding, gc, dd);
+}
+
+void raster(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ // get pointer to raster function
+ void (*pRasterFn)(unsigned int*, int, int, double, double, double,
+ double, double, Rboolean, const pGEcontext, pDevDesc);
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 6:
+ pRasterFn = ((DevDescVersion6*)dd)->raster;
+ break;
+ case 7:
+ pRasterFn = ((DevDescVersion7*)dd)->raster;
+ break;
+ case 8:
+ pRasterFn = ((DevDescVersion8*)dd)->raster;
+ break;
+ case 9:
+ default:
+ pRasterFn = ((DevDescVersion9*)dd)->raster;
+ break;
+ }
+
+ // call it
+ pRasterFn(raster, w, h, x, y, width, height, rot, interpolate, gc, dd);
+}
+
+double strWidth(const char *str, const pGEcontext gc, pDevDesc dev)
+{
+ // get pointer to strWidth function
+ double (*pStrWidthFn)(const char*, const pGEcontext, pDevDesc);
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 5:
+ pStrWidthFn = ((DevDescVersion5*)dev)->strWidth;
+ break;
+ case 6:
+ pStrWidthFn = ((DevDescVersion6*)dev)->strWidth;
+ break;
+ case 7:
+ pStrWidthFn = ((DevDescVersion7*)dev)->strWidth;
+ break;
+ case 8:
+ pStrWidthFn = ((DevDescVersion8*)dev)->strWidth;
+ break;
+ case 9:
+ default:
+ pStrWidthFn = ((DevDescVersion9*)dev)->strWidth;
+ break;
+ }
+
+ // call it
+ return pStrWidthFn(str, gc, dev);
+}
+
+void text(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ // get pointer to text function
+ void (*pTextFn)(double, double, const char*, double, double,
+ const pGEcontext, pDevDesc);
+ int engineVersion = ::R_GE_getVersion();
+ switch(engineVersion)
+ {
+ case 5:
+ pTextFn = ((DevDescVersion5*)dev)->text;
+ break;
+ case 6:
+ pTextFn = ((DevDescVersion6*)dev)->text;
+ break;
+ case 7:
+ pTextFn = ((DevDescVersion7*)dev)->text;
+ break;
+ case 8:
+ pTextFn = ((DevDescVersion8*)dev)->text;
+ break;
+ case 9:
+ default:
+ pTextFn = ((DevDescVersion9*)dev)->text;
+ break;
+ }
+
+ // call it
+ pTextFn(x, y, str, rot, hadj, gc, dev);
+}
+
+
+} // namespace dev_desc
+} // namespace handler
+} // namespace graphics
+} // namespace session
+} // namespace r
+
diff --git a/src/cpp/r/session/graphics/RGraphicsDevDesc.hpp b/src/cpp/r/session/graphics/RGraphicsDevDesc.hpp
new file mode 100644
index 0000000..8c68f6a
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsDevDesc.hpp
@@ -0,0 +1,176 @@
+/*
+ * RGraphicsDevDesc.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_DEV_DESC_HPP
+#define R_SESSION_GRAPHICS_DEV_DESC_HPP
+
+#include <Rinternals.h>
+
+#include <R_ext/Boolean.h>
+#define R_USE_PROTOTYPES 1
+#include <R_ext/GraphicsEngine.h>
+
+extern "C" {
+
+struct DevDescVersion9
+{
+ double left;
+ double right;
+ double bottom;
+ double top;
+ double clipLeft;
+ double clipRight;
+ double clipBottom;
+ double clipTop;
+ double xCharOffset;
+ double yCharOffset;
+ double yLineBias;
+ double ipr[2];
+ double cra[2];
+ double gamma;
+ Rboolean canClip;
+ Rboolean canChangeGamma;
+ int canHAdj;
+ double startps;
+ int startcol;
+ int startfill;
+ int startlty;
+ int startfont;
+ double startgamma;
+ void *deviceSpecific;
+ Rboolean displayListOn;
+ Rboolean canGenMouseDown;
+ Rboolean canGenMouseMove;
+ Rboolean canGenMouseUp;
+ Rboolean canGenKeybd;
+ Rboolean gettingEvent;
+
+ void (*activate)(const pDevDesc );
+ void (*circle)(double x, double y, double r, const pGEcontext gc, pDevDesc dd);
+ void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dd);
+ void (*close)(pDevDesc dd);
+ void (*deactivate)(pDevDesc );
+ Rboolean (*locator)(double *x, double *y, pDevDesc dd);
+ void (*line)(double x1, double y1, double x2, double y2,
+ const pGEcontext gc, pDevDesc dd);
+ void (*metricInfo)(int c, const pGEcontext gc,
+ double* ascent, double* descent, double* width,
+ pDevDesc dd);
+ void (*mode)(int mode, pDevDesc dd);
+ void (*newPage)(const pGEcontext gc, pDevDesc dd);
+ void (*polygon)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*polyline)(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd);
+ void (*rect)(double x0, double y0, double x1, double y1,
+ const pGEcontext gc, pDevDesc dd);
+
+
+ // dev_Path added in version 8
+ void (*path)(double *x, double *y,
+ int npoly, int *nper,
+ Rboolean winding,
+ const pGEcontext gc, pDevDesc dd);
+
+ // dev_Raster and dev_Cap added in version 6
+ void (*raster)(unsigned int *raster, int w, int h,
+ double x, double y,
+ double width, double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc, pDevDesc dd);
+ SEXP (*cap)(pDevDesc dd);
+
+ void (*size)(double *left, double *right, double *bottom, double *top,
+ pDevDesc dd);
+ double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dd);
+ void (*text)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ void (*onExit)(pDevDesc dd);
+ SEXP (*getEvent)(SEXP, const char *);
+ Rboolean (*newFrameConfirm)(pDevDesc dd);
+
+ Rboolean hasTextUTF8; /* and strWidthUTF8 */
+ void (*textUTF8)(double x, double y, const char *str, double rot,
+ double hadj, const pGEcontext gc, pDevDesc dd);
+ double (*strWidthUTF8)(const char *str, const pGEcontext gc, pDevDesc dd);
+ Rboolean wantSymbolUTF8;
+ Rboolean useRotatedTextInContour;
+
+ // eventEnv and eventHelper added in version 7
+ SEXP eventEnv;
+ void (*eventHelper)(pDevDesc dd, int code);
+
+ // holdFlush and have* added in version 9 (R 2.14)
+ int (*holdflush)(pDevDesc dd, int level);
+ int haveTransparency;
+ int haveTransparentBg;
+ int haveRaster;
+ int haveCapture, haveLocator;
+
+ char reserved[64];
+};
+
+} // extern "C"
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace handler {
+namespace dev_desc {
+
+pDevDesc allocate(const DevDescVersion9& devDescVersion9);
+
+void setSize(pDevDesc pDevDesc);
+
+void path(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+void raster(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+double strWidth(const char *str, const pGEcontext gc, pDevDesc dev);
+
+void text(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+
+} // namespace dev_desc
+} // namespace handler
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+
+#endif // R_SESSION_GRAPHICS_DEV_DESC_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsDevice.cpp b/src/cpp/r/session/graphics/RGraphicsDevice.cpp
new file mode 100644
index 0000000..d5a9d46
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsDevice.cpp
@@ -0,0 +1,777 @@
+/*
+ * RGraphicsDevice.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsDevice.hpp"
+
+#include <cstdlib>
+
+#include <boost/bind.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RUtil.hpp>
+
+#include "RGraphicsUtils.hpp"
+#include "RGraphicsPlotManager.hpp"
+#include "RGraphicsHandler.hpp"
+
+#include "config.h"
+
+// nix windows definitions
+#undef TRUE
+#undef FALSE
+#undef ERROR
+
+#ifdef TRACE_GD_CALLS
+#define TRACE_GD_CALL std::cerr << \
+ std::string(BOOST_CURRENT_FUNCTION).substr( \
+ std::string(BOOST_CURRENT_FUNCTION).find_last_of("::") + 1) \
+ << std::endl;
+#else
+#define TRACE_GD_CALL
+#endif
+
+
+using namespace core ;
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace device {
+
+namespace {
+
+// name of our graphics device
+const char * const kRStudioDevice = "RStudioGD";
+
+// GE device description
+pGEDevDesc s_pGEDevDesc = NULL;
+
+// externally provided locator function
+boost::function<bool(double*,double*)> s_locatorFunction;
+
+// global size attributes (used to initialize new devices)
+int s_width = 0;
+int s_height = 0;
+
+// provide GraphicsDeviceEvents for plot manager
+GraphicsDeviceEvents s_graphicsDeviceEvents;
+
+using namespace handler;
+
+
+void GD_NewPage(const pGEcontext gc, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ // delegate
+ handler::newPage(gc, dev);
+
+ // fire event (pass previousPageSnapshot)
+ SEXP previousPageSnapshot = s_pGEDevDesc->savedSnapshot;
+ s_graphicsDeviceEvents.onNewPage(previousPageSnapshot);
+}
+
+Rboolean GD_NewFrameConfirm(pDevDesc dd)
+{
+ TRACE_GD_CALL
+
+ // returning false causes the default implementation (printing a prompt
+ // of "Hit <Return> to see next plot:" to the console) to be used. this
+ // seems ideal compared to any custom UI we could produce so we leave it be
+ return FALSE;
+}
+
+
+void GD_Mode(int mode, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ // 0 = stop drawing
+ // 1 = start drawing
+ // 2 = input active
+
+ handler::mode(mode, dev);
+
+ s_graphicsDeviceEvents.onDrawing();
+}
+
+void GD_Size(double *left,
+ double *right,
+ double *bottom,
+ double *top,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ *left = 0.0;
+ *right = s_width;
+ *bottom = s_height;
+ *top = 0.0;
+}
+
+void GD_Clip(double x0, double x1, double y0, double y1, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::clip(x0, x1, y0, y1, dev);
+}
+
+
+void GD_Rect(double x0,
+ double y0,
+ double x1,
+ double y1,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::rect(x0, y0, x1, y1, gc, dev);
+}
+
+void GD_Path(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ TRACE_GD_CALL
+
+ handler::path(x, y, npoly, nper, winding, gc, dd);
+}
+
+void GD_Raster(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ TRACE_GD_CALL
+
+ handler::raster(raster, w, h, x, y, width, height, rot, interpolate, gc, dd);
+}
+
+SEXP GD_Cap(pDevDesc dd)
+{
+ TRACE_GD_CALL
+
+ return handler::cap(dd);
+}
+
+void GD_Circle(double x,
+ double y,
+ double r,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::circle(x, y, r, gc, dev);
+}
+
+void GD_Line(double x1,
+ double y1,
+ double x2,
+ double y2,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::line(x1, y1, x2, y2, gc, dev);
+}
+
+void GD_Polyline(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::polyline(n, x, y, gc, dev);
+}
+
+void GD_Polygon(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::polygon(n, x, y, gc, dev);
+}
+
+void GD_MetricInfo(int c,
+ const pGEcontext gc,
+ double* ascent,
+ double* descent,
+ double* width,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::metricInfo(c, gc, ascent, descent, width, dev);
+}
+
+double GD_StrWidth(const char *str, const pGEcontext gc, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ return handler::strWidth(str, gc, dev);
+}
+
+double GD_StrWidthUTF8(const char *str, const pGEcontext gc, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ return handler::strWidth(str, gc, dev);
+}
+
+void GD_Text(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::text(x, y, str, rot, hadj, gc, dev);
+}
+
+void GD_TextUTF8(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ handler::text(x, y, str, rot, hadj, gc, dev);
+}
+
+
+Rboolean GD_Locator(double *x, double *y, pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ if (s_locatorFunction)
+ {
+ s_graphicsDeviceEvents.onDrawing();
+
+ if(s_locatorFunction(x,y))
+ {
+ // if our graphics device went away while we were waiting
+ // for locator input then we need to return false
+
+ if (s_pGEDevDesc != NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+ else
+ {
+ s_graphicsDeviceEvents.onDrawing();
+ return FALSE;
+ }
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void GD_Activate(pDevDesc dev)
+{
+ TRACE_GD_CALL
+}
+
+void GD_Deactivate(pDevDesc dev)
+{
+ TRACE_GD_CALL
+}
+
+void GD_Close(pDevDesc dev)
+{
+ TRACE_GD_CALL
+
+ if (s_pGEDevDesc != NULL)
+ {
+ // destroy device specific struct
+ DeviceContext* pDC = (DeviceContext*)s_pGEDevDesc->dev->deviceSpecific;
+ handler::destroy(pDC);
+
+ // explicitly free and then null out the dev pointer of the GEDevDesc
+ // This is to avoid incompatabilities between the heap we are compiled with
+ // and the heap R is compiled with (we observed this to a problem with
+ // 64-bit R)
+ std::free(s_pGEDevDesc->dev);
+ s_pGEDevDesc->dev = NULL;
+
+ // set GDDevDesc to NULL so we don't reference it again
+ s_pGEDevDesc = NULL;
+ }
+
+ s_graphicsDeviceEvents.onClosed();
+}
+
+void GD_OnExit(pDevDesc dd)
+{
+ TRACE_GD_CALL
+
+ // NOTE: this may be called at various times including during error
+ // handling (jump_to_top_ex). therefore, do not place any process or device
+ // final termination code here (even though the name of the function
+ // suggests you might want to do this!)
+}
+
+int GD_HoldFlush(pDevDesc dd, int level)
+{
+ TRACE_GD_CALL
+
+ // NOTE: holdflush does not apply to bitmap devices since they are
+ // already "buffered" via the fact that they only do expensive operations
+ // (write to file) on dev.off. We could in theory use dev.flush as
+ // an indicator that we should detectChanges (e.g. when in a long
+ // running piece of code which doesn't yield to the REPL -- in practice
+ // however there are way too many flushes yielding lots of extra disk io
+ // and http round trips. If anything perhaps we could introduce a
+ // time-buffered variation where flush could set a flag that is checked
+ // every e.g. 1-second during background processing.
+
+ return 0;
+}
+
+void resyncDisplayList()
+{
+ // get pointers to device desc and cairo data
+ pDevDesc pDev = s_pGEDevDesc->dev;
+ DeviceContext* pDC = (DeviceContext*)pDev->deviceSpecific;
+
+ // destroy existing device context
+ handler::destroy(pDC);
+
+ // allocate a new one and set it to be the device specific ptr
+ pDC = handler::allocate(pDev);
+ pDev->deviceSpecific = pDC;
+
+ // re-create with the correct size (don't set a file path)
+ if (!handler::initialize(s_width, s_height, pDC))
+ {
+ // if this fails we are dead so close the device
+ close();
+ return;
+ }
+
+ // now update the device structure
+ handler::setSize(pDev);
+
+ // replay the display list onto the resized surface
+ {
+ SuppressDeviceEventsScope scope(plotManager());
+ Error error = r::exec::executeSafely(
+ boost::bind(GEplayDisplayList,s_pGEDevDesc));
+ if (error)
+ {
+ std::string errMsg;
+ if (r::isCodeExecutionError(error, &errMsg))
+ Rprintf(errMsg.c_str());
+ else
+ LOG_ERROR(error);
+ }
+
+ }
+}
+
+void resizeGraphicsDevice()
+{
+ // resync display list
+ resyncDisplayList();
+
+ // notify listeners of resize
+ s_graphicsDeviceEvents.onResized();
+}
+
+// routine which creates device
+SEXP createGD()
+{
+ // error if there is already an RStudio device
+ if (s_pGEDevDesc != NULL)
+ {
+ Rf_warning("Only one RStudio graphics device is permitted");
+ return R_NilValue;
+ }
+
+
+ R_CheckDeviceAvailable();
+
+ BEGIN_SUSPEND_INTERRUPTS
+ {
+ // initialize v9 structure
+ DevDescVersion9 devDesc;
+
+ // device functions
+ devDesc.activate = GD_Activate;
+ devDesc.deactivate = GD_Deactivate;
+ devDesc.size = GD_Size;
+ devDesc.clip = GD_Clip;
+ devDesc.rect = GD_Rect;
+ devDesc.path = GD_Path;
+ devDesc.raster = GD_Raster;
+ devDesc.cap = GD_Cap;
+ devDesc.circle = GD_Circle;
+ devDesc.line = GD_Line;
+ devDesc.polyline = GD_Polyline;
+ devDesc.polygon = GD_Polygon;
+ devDesc.locator = GD_Locator;
+ devDesc.mode = GD_Mode;
+ devDesc.metricInfo = GD_MetricInfo;
+ devDesc.strWidth = GD_StrWidth;
+ devDesc.strWidthUTF8 = GD_StrWidthUTF8;
+ devDesc.text = GD_Text;
+ devDesc.textUTF8 = GD_TextUTF8;
+ devDesc.hasTextUTF8 = TRUE;
+ devDesc.wantSymbolUTF8 = TRUE;
+ devDesc.useRotatedTextInContour = FALSE;
+ devDesc.newPage = GD_NewPage;
+ devDesc.close = GD_Close;
+ devDesc.newFrameConfirm = GD_NewFrameConfirm;
+ devDesc.onExit = GD_OnExit;
+ devDesc.eventEnv = R_NilValue;
+ devDesc.eventHelper = NULL;
+ devDesc.holdflush = GD_HoldFlush;
+
+ // capabilities flags
+ devDesc.haveTransparency = 2;
+ devDesc.haveTransparentBg = 2;
+ devDesc.haveRaster = 2;
+ devDesc.haveCapture = 1;
+ devDesc.haveLocator = 2;
+
+ // allocate device
+ pDevDesc pDev = handler::dev_desc::allocate(devDesc);
+
+ // allocate and initialize context
+ DeviceContext* pDC = handler::allocate(pDev);
+ if (!handler::initialize(s_width, s_height, pDC))
+ {
+ handler::destroy(pDC);
+
+ // leak the pDev on purpose because we don't have
+ // access to R's heap/free function
+
+ Rf_error("Unable to start RStudio device");
+ }
+
+ // set device specific context
+ pDev->deviceSpecific = pDC;
+
+ // device attributes
+ handler::setSize(pDev);
+ handler::setDeviceAttributes(pDev);
+
+ // notify handler we are about to add (enables shadow device
+ // to close itself so it doesn't show up in the dev.list()
+ // in front of us
+ handler::onBeforeAddDevice(pDC);
+
+ // associate with device description and add it
+ s_pGEDevDesc = GEcreateDevDesc(pDev);
+ GEaddDevice2(s_pGEDevDesc, kRStudioDevice);
+
+ // notify handler we have added (so it can regenerate its context)
+ handler::onAfterAddDevice(pDC);
+
+ // make us active
+ Rf_selectDevice(Rf_ndevNumber(s_pGEDevDesc->dev));
+ }
+ END_SUSPEND_INTERRUPTS;
+
+ return R_NilValue;
+}
+
+// ensure that our device is created and active (required for snapshot
+// creation/restoration)
+Error makeActive()
+{
+ // don't make active if our graphics version is incompatible
+ if (!graphics::validateRequirements())
+ return Error(graphics::errc::IncompatibleGraphicsEngine, ERROR_LOCATION);
+
+ // make sure we have been created
+ if (s_pGEDevDesc == NULL)
+ {
+ SEXP ignoredSEXP;
+ Error error = r::exec::executeSafely<SEXP>(boost::bind(createGD),
+ &ignoredSEXP);
+ if (error)
+ return error;
+ }
+
+ // select us
+ Rf_selectDevice(Rf_ndevNumber(s_pGEDevDesc->dev));
+
+ return Success();
+}
+
+bool isActive()
+{
+ return s_pGEDevDesc != NULL &&
+ Rf_ndevNumber(s_pGEDevDesc->dev) == Rf_curDevice();
+}
+
+SEXP rs_activateGD()
+{
+ Error error = makeActive();
+ if (error)
+ LOG_ERROR(error);
+ return R_NilValue;
+}
+
+
+DisplaySize displaySize()
+{
+ return DisplaySize(s_width, s_height);
+}
+
+double grconvert(double val,
+ const std::string& type,
+ const std::string& from,
+ const std::string& to)
+{
+ r::exec::RFunction grconvFunc("graphics:::grconvert" + type, val, from, to);
+ double convertedVal = val; // default in case of error
+ Error error = grconvFunc.call(&convertedVal);
+ if (error)
+ LOG_ERROR(error);
+ return convertedVal;
+}
+
+double grconvertX(double x, const std::string& from, const std::string& to)
+{
+ return grconvert(x, "X", from, to);
+}
+
+double grconvertY(double y, const std::string& from, const std::string& to)
+{
+ return grconvert(y, "Y", from, to);
+}
+
+void deviceToUser(double* x, double* y)
+{
+ *x = grconvertX(*x, "device", "user");
+ *y = grconvertY(*y, "device", "user");
+}
+
+void deviceToNDC(double* x, double* y)
+{
+ *x = grconvertX(*x, "device", "ndc");
+ *y = grconvertY(*y, "device", "ndc");
+}
+
+Error saveSnapshot(const core::FilePath& snapshotFile,
+ const core::FilePath& imageFile)
+{
+ // ensure we are active
+ Error error = makeActive();
+ if (error)
+ return error ;
+
+ // save snaphot file
+ error = r::exec::RFunction(".rs.saveGraphics",
+ string_utils::utf8ToSystem(snapshotFile.absolutePath())).call();
+ if (error)
+ return error;
+
+ // save png file
+ DeviceContext* pDC = (DeviceContext*)s_pGEDevDesc->dev->deviceSpecific;
+ return handler::writeToPNG(imageFile, pDC);
+}
+
+Error restoreSnapshot(const core::FilePath& snapshotFile)
+{
+ // ensure we are active
+ Error error = makeActive();
+ if (error)
+ return error ;
+
+ // restore
+ return r::exec::RFunction(".rs.restoreGraphics",
+ string_utils::utf8ToSystem(snapshotFile.absolutePath())).call();
+}
+
+void copyToActiveDevice()
+{
+ int rsDeviceNumber = GEdeviceNumber(s_pGEDevDesc);
+ GEcopyDisplayList(rsDeviceNumber);
+}
+
+std::string imageFileExtension()
+{
+ return "png";
+}
+
+void onBeforeExecute()
+{
+ if (s_pGEDevDesc != NULL)
+ {
+ DeviceContext* pDC = (DeviceContext*)s_pGEDevDesc->dev->deviceSpecific;
+ if (pDC != NULL)
+ handler::onBeforeExecute(pDC);
+ }
+}
+
+} // anonymous namespace
+
+const int kDefaultWidth = 500;
+const int kDefaultHeight = 500;
+
+Error initialize(
+ const FilePath& graphicsPath,
+ const boost::function<bool(double*,double*)>& locatorFunction)
+{
+ // initialize shadow handler
+ r::session::graphics::handler::installShadowHandler();
+
+ // save reference to locator function
+ s_locatorFunction = locatorFunction;
+
+ // device conversion functions
+ UnitConversionFunctions convert;
+ convert.deviceToUser = deviceToUser;
+ convert.deviceToNDC = deviceToNDC;
+
+ // create plot manager (provide functions & events)
+ GraphicsDeviceFunctions graphicsDevice;
+ graphicsDevice.isActive = isActive;
+ graphicsDevice.displaySize = displaySize;
+ graphicsDevice.convert = convert;
+ graphicsDevice.saveSnapshot = saveSnapshot;
+ graphicsDevice.restoreSnapshot = restoreSnapshot;
+ graphicsDevice.copyToActiveDevice = copyToActiveDevice;
+ graphicsDevice.imageFileExtension = imageFileExtension;
+ graphicsDevice.close = close;
+ graphicsDevice.onBeforeExecute = onBeforeExecute;
+ Error error = plotManager().initialize(graphicsPath,
+ graphicsDevice,
+ &s_graphicsDeviceEvents);
+ if (error)
+ return error;
+
+ // set size
+ setSize(kDefaultWidth, kDefaultHeight);
+
+ // check for an incompatible graphics version before fully initializing.
+ std::string message;
+ if (graphics::validateRequirements(&message))
+ {
+ // register device creation routine
+ R_CallMethodDef createGDMethodDef ;
+ createGDMethodDef.name = "rs_createGD" ;
+ createGDMethodDef.fun = (DL_FUNC) createGD ;
+ createGDMethodDef.numArgs = 0;
+ r::routines::addCallMethod(createGDMethodDef);
+
+ // regsiter device activiation routine
+ R_CallMethodDef activateGDMethodDef ;
+ activateGDMethodDef.name = "rs_activateGD" ;
+ activateGDMethodDef.fun = (DL_FUNC) rs_activateGD ;
+ activateGDMethodDef.numArgs = 0;
+ r::routines::addCallMethod(activateGDMethodDef);
+
+ // initialize
+ return r::exec::RFunction(".rs.initGraphicsDevice").call();
+ }
+ else
+ {
+ // if there is one then print a warning and return Success. This allows
+ // users to continue using the product while still being made aware of the
+ // fact that their graphics engine is incompatible
+ r::exec::warning(message);
+
+ // success with warning
+ return Success();
+ }
+}
+
+
+void setSize(int width, int height)
+{
+ // only set if the values have changed (prevents unnecessary plot
+ // invalidations from occuring)
+ if ( width != s_width || height != s_height )
+ {
+ s_width = width;
+ s_height = height;
+
+ // if there is a device active sync its size
+ if (s_pGEDevDesc != NULL)
+ resizeGraphicsDevice();
+ }
+}
+
+int getWidth()
+{
+ return s_width;
+}
+
+int getHeight()
+{
+ return s_height;
+}
+
+void close()
+{
+ if (s_pGEDevDesc != NULL)
+ Rf_killDevice(Rf_ndevNumber(s_pGEDevDesc->dev));
+}
+
+
+
+} // namespace device
+
+// if we don't have pango cairo then provide a null definition
+// for the pango cairo init routine (for linking on other platforms)
+#ifndef PANGO_CAIRO_FOUND
+namespace handler {
+void installCairoHandler() {}
+}
+#endif
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/graphics/RGraphicsDevice.hpp b/src/cpp/r/session/graphics/RGraphicsDevice.hpp
new file mode 100644
index 0000000..358eb23
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsDevice.hpp
@@ -0,0 +1,55 @@
+/*
+ * RGraphicsDevice.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_DEVICE_HPP
+#define R_SESSION_GRAPHICS_DEVICE_HPP
+
+#include <boost/function.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace device {
+
+extern const int kDefaultWidth;
+extern const int kDefaultHeight;
+
+// initialize
+core::Error initialize(
+ const core::FilePath& graphicsPath,
+ const boost::function<bool(double*,double*)>& locatorFunction);
+
+// device size
+void setSize(int width, int height);
+int getWidth();
+int getHeight();
+
+// reset
+void close();
+
+
+} // namespace device
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_SESSION_GRAPHICS_DEVICE_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsErrorCategory.cpp b/src/cpp/r/session/graphics/RGraphicsErrorCategory.cpp
new file mode 100644
index 0000000..2b9b060
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsErrorCategory.cpp
@@ -0,0 +1,85 @@
+/*
+ * RGraphicsErrorCategory.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/session/RGraphics.hpp>
+
+using namespace core;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+class RGraphicsErrorCategory : public boost::system::error_category
+{
+public:
+ virtual const char * name() const;
+ virtual std::string message( int ev ) const;
+};
+
+const boost::system::error_category& rGraphicsCategory()
+{
+ static RGraphicsErrorCategory rGraphicsErrorCategoryConst ;
+ return rGraphicsErrorCategoryConst ;
+}
+
+const char * RGraphicsErrorCategory::name() const
+{
+ return "r-graphics" ;
+}
+
+std::string RGraphicsErrorCategory::message( int ev ) const
+{
+ std::string message ;
+ switch (ev)
+ {
+ case errc::IncompatibleGraphicsEngine:
+ message = "Incompatible graphics engine";
+ break;
+
+ case errc::DeviceNotAvailable:
+ message = "Device not available";
+ break;
+
+ case errc::NoActivePlot:
+ message = "No active plot";
+ break;
+
+ case errc::InvalidPlotIndex:
+ message = "Invalid plot index";
+ break;
+
+ case errc::InvalidPlotImageType:
+ message = "Invalid plot image type";
+ break;
+
+ case errc::PlotRenderingError:
+ message = "Plot rendering error";
+ break;
+
+ case errc::PlotFileError:
+ message = "Plot file error";
+ break;
+
+ default:
+ message = "Unknown error" ;
+ break;
+ }
+
+ return message ;
+}
+
+} // namespace graphics
+} // namespace session
+} // namespace r
diff --git a/src/cpp/r/session/graphics/RGraphicsHandler.cpp b/src/cpp/r/session/graphics/RGraphicsHandler.cpp
new file mode 100644
index 0000000..9de8ee5
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsHandler.cpp
@@ -0,0 +1,121 @@
+/*
+ * RGraphicsHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsHandler.hpp"
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace handler {
+
+DeviceContext* (*allocate)(pDevDesc dev);
+void (*destroy)(DeviceContext* pDC);
+
+bool (*initialize)(int width, int height, DeviceContext* pDC);
+
+void (*setSize)(pDevDesc pDev);
+void (*setDeviceAttributes)(pDevDesc pDev);
+
+void (*onBeforeAddDevice)(DeviceContext* pDC);
+void (*onAfterAddDevice)(DeviceContext* pDC);
+
+core::Error (*writeToPNG)(const core::FilePath& targetPath,
+ DeviceContext* pDC);
+
+void (*circle)(double x,
+ double y,
+ double r,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*line)(double x1,
+ double y1,
+ double x2,
+ double y2,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*polygon)(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*polyline)(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*rect)(double x0,
+ double y0,
+ double x1,
+ double y1,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*path)(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+void (*raster)(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+SEXP (*cap)(pDevDesc dd);
+
+void (*metricInfo)(int c,
+ const pGEcontext gc,
+ double* ascent,
+ double* descent,
+ double* width,
+ pDevDesc dev);
+
+double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dev);
+
+void (*text)(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dev);
+
+void (*newPage)(const pGEcontext gc, pDevDesc dev);
+
+void (*mode)(int mode, pDevDesc dev);
+
+void (*onBeforeExecute)(DeviceContext* pDC);
+
+} // namespace handler
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
diff --git a/src/cpp/r/session/graphics/RGraphicsHandler.hpp b/src/cpp/r/session/graphics/RGraphicsHandler.hpp
new file mode 100644
index 0000000..ecd76d5
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsHandler.hpp
@@ -0,0 +1,159 @@
+/*
+ * RGraphicsHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_GRAPHICS_HANDLER_HPP
+#define R_GRAPHICS_HANDLER_HPP
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include "RGraphicsDevDesc.hpp"
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace handler {
+
+
+void installShadowHandler();
+void installCairoHandler();
+
+struct DeviceContext
+{
+ DeviceContext(pDevDesc ownerDev) :
+ pDeviceSpecific(NULL),
+ width(0),
+ height(0),
+ dev(ownerDev) {}
+
+ // platform specific device info
+ void* pDeviceSpecific;
+
+ // file info
+ core::FilePath targetPath;
+ int width;
+ int height;
+
+ // back pointer to owning device
+ pDevDesc dev;
+};
+
+extern DeviceContext* (*allocate)(pDevDesc dev);
+extern void (*destroy)(DeviceContext* pDC);
+
+extern bool (*initialize)(int width, int height, DeviceContext* pDC);
+
+
+extern void (*setSize)(pDevDesc pDev);
+extern void (*setDeviceAttributes)(pDevDesc pDev);
+
+extern void (*onBeforeAddDevice)(DeviceContext* pDC);
+extern void (*onAfterAddDevice)(DeviceContext* pDC);
+
+extern core::Error (*writeToPNG)(const core::FilePath& targetPath,
+ DeviceContext* pDC);
+
+extern void (*circle)(double x,
+ double y,
+ double r,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*line)(double x1,
+ double y1,
+ double x2,
+ double y2,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*polygon)(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*polyline)(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*rect)(double x0,
+ double y0,
+ double x1,
+ double y1,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*path)(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+extern void (*raster)(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd);
+
+extern SEXP (*cap)(pDevDesc dd);
+
+extern void (*metricInfo)(int c,
+ const pGEcontext gc,
+ double* ascent,
+ double* descent,
+ double* width,
+ pDevDesc dev);
+
+extern double (*strWidth)(const char *str, const pGEcontext gc, pDevDesc dev);
+
+extern void (*text)(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev);
+
+extern void (*clip)(double x0, double x1, double y0, double y1, pDevDesc dev);
+
+extern void (*newPage)(const pGEcontext gc, pDevDesc dev);
+
+extern void (*mode)(int mode, pDevDesc dev);
+
+extern void (*onBeforeExecute)(DeviceContext* pDC);
+
+} // namespace handler
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_GRAPHICS_HANDLER_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlot.cpp b/src/cpp/r/session/graphics/RGraphicsPlot.cpp
new file mode 100644
index 0000000..e7da2d1
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlot.cpp
@@ -0,0 +1,300 @@
+/*
+ * RGraphicsPlot.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsPlot.hpp"
+
+#include <iostream>
+
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <core/system/System.hpp>
+#include <core/StringUtils.hpp>
+
+#include <r/RExec.hpp>
+#include <r/session/RGraphics.hpp>
+
+using namespace core ;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+Plot::Plot(const GraphicsDeviceFunctions& graphicsDevice,
+ const FilePath& baseDirPath,
+ SEXP manipulatorSEXP)
+ : graphicsDevice_(graphicsDevice),
+ baseDirPath_(baseDirPath),
+ needsUpdate_(false),
+ manipulator_(manipulatorSEXP)
+{
+}
+
+Plot::Plot(const GraphicsDeviceFunctions& graphicsDevice,
+ const FilePath& baseDirPath,
+ const std::string& storageUuid,
+ const DisplaySize& renderedSize)
+ : graphicsDevice_(graphicsDevice),
+ baseDirPath_(baseDirPath),
+ storageUuid_(storageUuid),
+ renderedSize_(renderedSize),
+ needsUpdate_(false),
+ manipulator_()
+{
+ // invalidate if the image file doesn't exist (allows the server
+ // to migrate between different image backends e.g. png, jpeg, etc)
+ if (!imageFilePath(storageUuid_).exists())
+ invalidate();
+}
+
+std::string Plot::storageUuid() const
+{
+ return storageUuid_;
+}
+
+bool Plot::hasValidStorage() const
+{
+ return hasStorage() && snapshotFilePath().exists();
+}
+
+void Plot::invalidate()
+{
+ needsUpdate_ = true;
+}
+
+bool Plot::hasManipulator() const
+{
+ // check is a bit complicated because defer loading the manipulator
+ // until it is asked for
+
+ return !manipulator_.empty() || hasManipulatorFile();
+}
+
+SEXP Plot::manipulatorSEXP() const
+{
+ if (hasManipulator())
+ {
+ loadManipulatorIfNecessary();
+ return manipulator_.sexp();
+ }
+ else
+ {
+ return R_NilValue;
+ }
+
+ return manipulator_.sexp();
+}
+
+void Plot::manipulatorAsJson(json::Value* pValue) const
+{
+ if (hasManipulator())
+ {
+ loadManipulatorIfNecessary();
+ manipulator_.asJson(pValue);
+ }
+ else
+ {
+ *pValue = json::Value();
+ }
+}
+
+void Plot::saveManipulator() const
+{
+ if (hasManipulator() && !storageUuid_.empty())
+ saveManipulator(storageUuid_);
+}
+
+Error Plot::renderFromDisplay()
+{
+ // we can use our cached representation if we don't need an update and our
+ // rendered size is the same as the current graphics device size
+ if ( !needsUpdate_ &&
+ (renderedSize() == graphicsDevice_.displaySize()) )
+ {
+ return Success();
+ }
+
+ // generate a new storage uuid
+ std::string storageUuid = core::system::generateUuid();
+
+ // generate snapshot and image files
+ Error error = graphicsDevice_.saveSnapshot(snapshotFilePath(storageUuid),
+ imageFilePath(storageUuid));
+ if (error)
+ return Error(errc::PlotRenderingError, error, ERROR_LOCATION);
+
+ // save rendered size
+ renderedSize_ = graphicsDevice_.displaySize();
+
+ // save manipulator (if any)
+ saveManipulator(storageUuid);
+
+ // delete existing files (if any)
+ Error removeError = removeFiles();
+
+ // update state
+ storageUuid_ = storageUuid;
+ needsUpdate_ = false;
+
+ // return error status
+ return removeError;
+}
+
+Error Plot::renderFromDisplaySnapshot(SEXP snapshot)
+{
+ // generate a new storage uuid
+ std::string storageUuid = core::system::generateUuid();
+
+ // generate snapshot file
+ FilePath snapshotFile = snapshotFilePath(storageUuid);
+ Error error = r::exec::RFunction(".rs.saveGraphicsSnapshot",
+ snapshot,
+ string_utils::utf8ToSystem(snapshotFile.absolutePath())).call();
+ if (error)
+ return error ;
+
+ //
+ // we can't generate an image file at this point in the processing
+ // because the GraphicsDevice has already moved on to the next page. this is
+ // OK though because we simply set needsUpdate_ = true below and the next
+ // time renderFromDisplay is called it will be rendered
+ //
+
+ // save rendered size
+ renderedSize_ = graphicsDevice_.displaySize();
+
+ // save manipulator (if any)
+ saveManipulator(storageUuid);
+
+ // delete existing files (if any)
+ Error removeError = removeFiles();
+
+ // update state
+ storageUuid_ = storageUuid;
+ needsUpdate_ = true;
+
+ // return error status
+ return removeError;
+}
+
+
+std::string Plot::imageFilename() const
+{
+ return imageFilePath(storageUuid()).filename();
+}
+
+Error Plot::renderToDisplay()
+{
+ Error error = graphicsDevice_.restoreSnapshot(snapshotFilePath());
+ if (error)
+ {
+ Error graphicsError(errc::PlotRenderingError, error, ERROR_LOCATION);
+ DisplaySize deviceSize = graphicsDevice_.displaySize();
+ graphicsError.addProperty("device-width", deviceSize.width);
+ graphicsError.addProperty("device-height", deviceSize.height);
+ return graphicsError;
+ }
+ else
+ return Success();
+}
+
+Error Plot::removeFiles()
+{
+ // bail if we don't have any storage
+ if (storageUuid_.empty())
+ return Success();
+
+ Error snapshotError = snapshotFilePath(storageUuid_).removeIfExists();
+ Error imageError = imageFilePath(storageUuid_).removeIfExists();
+ Error manipulatorError = manipulatorFilePath(storageUuid_).removeIfExists();
+
+ if (snapshotError)
+ return Error(errc::PlotFileError, snapshotError, ERROR_LOCATION);
+ else if (imageError)
+ return Error(errc::PlotFileError, imageError, ERROR_LOCATION);
+ else if (manipulatorError)
+ return Error(errc::PlotFileError, manipulatorError, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+void Plot::purgeInMemoryResources()
+{
+ manipulator_.clear();
+}
+
+bool Plot::hasStorage() const
+{
+ return !storageUuid_.empty();
+}
+
+FilePath Plot::snapshotFilePath() const
+{
+ return snapshotFilePath(storageUuid());
+}
+
+
+FilePath Plot::snapshotFilePath(const std::string& storageUuid) const
+{
+ return baseDirPath_.complete(storageUuid + ".snapshot");
+}
+
+FilePath Plot::imageFilePath(const std::string& storageUuid) const
+{
+ std::string extension = graphicsDevice_.imageFileExtension();
+ return baseDirPath_.complete(storageUuid + "." + extension);
+}
+
+bool Plot::hasManipulatorFile() const
+{
+ return hasStorage() && manipulatorFilePath(storageUuid()).exists();
+}
+
+FilePath Plot::manipulatorFilePath(const std::string& storageUuid) const
+{
+ return baseDirPath_.complete(storageUuid + ".manip");
+}
+
+void Plot::loadManipulatorIfNecessary() const
+{
+ if (manipulator_.empty() && hasManipulatorFile())
+ {
+ FilePath manipPath = manipulatorFilePath(storageUuid());
+ Error error = manipulator_.load(manipPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void Plot::saveManipulator(const std::string& storageUuid) const
+{
+ loadManipulatorIfNecessary();
+ if (!manipulator_.empty())
+ {
+ Error error = manipulator_.save(manipulatorFilePath(storageUuid));
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlot.hpp b/src/cpp/r/session/graphics/RGraphicsPlot.hpp
new file mode 100644
index 0000000..81132c3
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlot.hpp
@@ -0,0 +1,104 @@
+/*
+ * RGraphicsPlot.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_PLOT_HPP
+#define R_SESSION_GRAPHICS_PLOT_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/FilePath.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <r/RSexp.hpp>
+
+#include "RGraphicsTypes.hpp"
+#include "RGraphicsPlotManipulator.hpp"
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+// plot storage data structure
+class Plot : boost::noncopyable
+{
+public:
+ Plot(const GraphicsDeviceFunctions& graphicsDevice,
+ const core::FilePath& baseDirPath,
+ SEXP manipulatorSEXP);
+
+ Plot(const GraphicsDeviceFunctions& graphicsDevice,
+ const core::FilePath& baseDirPath,
+ const std::string& storageUuid,
+ const DisplaySize& renderedSize);
+
+ std::string storageUuid() const;
+ bool hasValidStorage() const;
+ const DisplaySize& renderedSize() const { return renderedSize_; }
+
+ bool hasManipulator() const;
+ SEXP manipulatorSEXP() const;
+ void manipulatorAsJson(core::json::Value* pValue) const;
+ void saveManipulator() const;
+
+ void invalidate();
+
+ core::Error renderFromDisplay();
+ core::Error renderFromDisplaySnapshot(SEXP snapshot);
+ std::string imageFilename() const;
+
+ core::Error renderToDisplay();
+
+ core::Error removeFiles();
+
+ void purgeInMemoryResources();
+
+private:
+ bool hasStorage() const;
+
+ core::FilePath snapshotFilePath() const ;
+ core::FilePath snapshotFilePath(const std::string& storageUuid) const;
+ core::FilePath imageFilePath(const std::string& storageUuid) const;
+
+ bool hasManipulatorFile() const;
+ core::FilePath manipulatorFilePath(const std::string& storageUuid) const;
+ void loadManipulatorIfNecessary() const;
+ void saveManipulator(const std::string& storageUuid) const;
+
+private:
+ GraphicsDeviceFunctions graphicsDevice_;
+ core::FilePath baseDirPath_;
+ std::string storageUuid_ ;
+ DisplaySize renderedSize_ ;
+ bool needsUpdate_;
+
+ // manipulator and protection scope for it
+ mutable PlotManipulator manipulator_;
+};
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_SESSION_GRAPHICS_PLOT_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManager.cpp b/src/cpp/r/session/graphics/RGraphicsPlotManager.cpp
new file mode 100644
index 0000000..f89fe26
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManager.cpp
@@ -0,0 +1,872 @@
+/*
+ * RGraphicsPlotManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsPlotManager.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RUtil.hpp>
+#include <r/ROptions.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#include "RGraphicsUtils.hpp"
+#include "RGraphicsDevice.hpp"
+#include "RGraphicsPlotManipulatorManager.hpp"
+
+using namespace core;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+namespace {
+
+double pixelsToInches(int pixels)
+{
+ return (double)pixels / 96.0;
+}
+
+} // anonymous namespace
+
+const char * const kPngFormat = "png";
+const char * const kJpegFormat = "jpeg";
+const char * const kBmpFormat = "bmp";
+const char * const kTiffFormat = "tiff";
+const char * const kMetafileFormat = "emf";
+const char * const kSvgFormat = "svg";
+const char * const kPostscriptFormat = "eps";
+
+// satisfy r::session::graphics::Display singleton
+Display& display()
+{
+ return graphics::plotManager();
+}
+
+PlotManager& plotManager()
+{
+ static PlotManager instance;
+ return instance;
+}
+
+PlotManager::PlotManager()
+ : displayHasChanges_(false),
+ lastChange_(boost::posix_time::not_a_date_time),
+ suppressDeviceEvents_(false),
+ activePlot_(-1),
+ plotInfoRegex_("([A-Za-z0-9\\-]+):([0-9]+),([0-9]+)")
+{
+ plots_.set_capacity(30);
+}
+
+Error PlotManager::initialize(const FilePath& graphicsPath,
+ const GraphicsDeviceFunctions& graphicsDevice,
+ GraphicsDeviceEvents* pEvents)
+{
+ // initialize plot manipulator manager
+ Error error = plotManipulatorManager().initialize(graphicsDevice.convert);
+ if (error)
+ return error;
+
+ // save reference to graphics path and make sure it exists
+ graphicsPath_ = graphicsPath ;
+ error = graphicsPath_.ensureDirectory();
+ if (error)
+ return error;
+
+ // save reference to plots state file
+ plotsStateFile_ = graphicsPath_.complete("INDEX");
+
+ // save reference to graphics device functions
+ graphicsDevice_ = graphicsDevice;
+
+ // sign up for graphics device events
+ using boost::bind;
+ pEvents->onNewPage.connect(bind(&PlotManager::onDeviceNewPage, this, _1));
+ pEvents->onDrawing.connect(bind(&PlotManager::onDeviceDrawing, this));
+ pEvents->onResized.connect(bind(&PlotManager::onDeviceResized, this));
+ pEvents->onClosed.connect(bind(&PlotManager::onDeviceClosed, this));
+
+ return Success();
+}
+
+
+int PlotManager::plotCount() const
+{
+ return plots_.size();
+}
+
+Error PlotManager::plotImageFilename(int index,
+ std::string* pImageFilename) const
+{
+ if (!isValidPlotIndex(index))
+ {
+ return plotIndexError(index, ERROR_LOCATION);
+ }
+ else
+ {
+ *pImageFilename = plots_[index]->imageFilename();
+ return Success();
+ }
+}
+
+int PlotManager::activePlotIndex() const
+{
+ return activePlot_;
+}
+
+// NOTE: returns an error if the plot index is invalid. Otherwise will always
+// successfully update the active plot state. If any file or rendering errors
+// occur while setting the active plot they will be reported but will not
+// cause the method to return an error.
+Error PlotManager::setActivePlot(int index)
+{
+ if (!isValidPlotIndex(index))
+ return plotIndexError(index, ERROR_LOCATION);
+
+ if (activePlot_ != index)
+ {
+ // if there is already a plot active then release its
+ // in-memory resources
+ if (hasPlot())
+ activePlot().purgeInMemoryResources();
+
+ // set index
+ activePlot_ = index;
+
+ // render it
+ renderActivePlotToDisplay();
+
+ // trip changes flag
+ setDisplayHasChanges(true);
+ }
+
+ // return success
+ return Success();
+}
+
+
+// NOTE: returns an error if the plot index is invalid. Otherwise it
+// is guaranteed to have removed the plot. Rendering or file errors which
+// occur during the removal or transformation to a new graphics state are
+// reported to the user and logged but are not returned (because in these
+// cases the actual removal succeeded)
+Error PlotManager::removePlot(int index)
+{
+ if (!isValidPlotIndex(index))
+ return plotIndexError(index, ERROR_LOCATION);
+
+ // remove the plot files
+ Error removeError = plots_[index]->removeFiles();
+ if (removeError)
+ logAndReportError(removeError, ERROR_LOCATION);
+
+ // erase the plot from the internal list
+ plots_.erase(plots_.begin() + index);
+
+ // trip changes flag (removing a plot will affect the number of plots
+ // and the active plot index so we need a new changed event)
+ setDisplayHasChanges(true);
+
+ // fixup active plot as necessary
+
+ // case: we just removed the active plot
+ if (index == activePlot_)
+ {
+ // clear active plot
+ activePlot_ = -1;
+
+ // try to select the plot after the one removed
+ if (isValidPlotIndex(index))
+ {
+ Error error = setActivePlot(index);
+ if (error)
+ logAndReportError(error, ERROR_LOCATION);
+ }
+ // try to select the plot prior to the one removed
+ else if (isValidPlotIndex(index-1))
+ {
+ Error error = setActivePlot(index-1);
+ if (error)
+ logAndReportError(error, ERROR_LOCATION);
+ }
+ }
+ // case: we removed a plot *prior to* the active plot. this means
+ // that the list shrunk by 1 so the active plot's index needs to
+ // shrink by 1 as well
+ else if (index < activePlot_)
+ {
+ --activePlot_;
+ }
+
+
+ return Success();
+}
+
+Error PlotManager::savePlotAsFile(const boost::function<Error()>&
+ deviceCreationFunction)
+{
+ if (!hasPlot())
+ return Error(errc::NoActivePlot, ERROR_LOCATION);
+
+ // restore previous device after invoking file device
+ RestorePreviousGraphicsDeviceScope restoreScope;
+
+ // create the target device
+ Error error = deviceCreationFunction();
+ if (error)
+ return error ;
+
+ // copy the current contents of the graphics device to the target device
+ graphicsDevice_.copyToActiveDevice();
+
+ // close the target device to save the file
+ return r::exec::RFunction("dev.off").call();
+}
+
+Error PlotManager::savePlotAsFile(const std::string& deviceCreationCode)
+{
+ return savePlotAsFile(
+ boost::bind(r::exec::executeString, deviceCreationCode));
+}
+
+Error PlotManager::savePlotAsImage(const FilePath& filePath,
+ const std::string& format,
+ int widthPx,
+ int heightPx)
+{
+ if (format == kPngFormat ||
+ format == kBmpFormat ||
+ format == kJpegFormat ||
+ format == kTiffFormat)
+ {
+ return savePlotAsBitmapFile(filePath, format, widthPx, heightPx);
+ }
+ else if (format == kSvgFormat)
+ {
+ return savePlotAsSvg(filePath, widthPx, heightPx);
+ }
+ else if (format == kMetafileFormat)
+ {
+ return savePlotAsMetafile(filePath, widthPx, heightPx);
+ }
+ else if (format == kPostscriptFormat)
+ {
+ return savePlotAsPostscript(filePath, widthPx, heightPx);
+ }
+ else
+ {
+ return systemError(boost::system::errc::invalid_argument, ERROR_LOCATION);
+ }
+}
+
+Error PlotManager::savePlotAsBitmapFile(const FilePath& targetPath,
+ const std::string& bitmapFileType,
+ int width,
+ int height)
+{
+ // optional format specific extra params
+ std::string extraParams;
+
+ // add extra quality parameter for jpegs
+ if (bitmapFileType == kJpegFormat)
+ extraParams = ", quality = 100";
+
+ // add extra bitmap params
+ extraParams += r::session::graphics::extraBitmapParams();
+
+ // generate code for creating bitmap file device
+ boost::format fmt(
+ "{ require(grDevices, quietly=TRUE); "
+ " %1%(filename=\"%2%\", width=%3%, height=%4%, pointsize = 16 %5%); }");
+ std::string deviceCreationCode = boost::str(fmt % bitmapFileType %
+ string_utils::utf8ToSystem(targetPath.absolutePath()) %
+ width %
+ height %
+ extraParams);
+
+ // save the file
+ return savePlotAsFile(deviceCreationCode);
+}
+
+Error PlotManager::savePlotAsPdf(const FilePath& filePath,
+ double widthInches,
+ double heightInches,
+ bool useCairoPdf)
+{
+ // generate code for creating pdf file device
+ std::string code("{ require(grDevices, quietly=TRUE); ");
+ if (useCairoPdf)
+ code += "cairo_pdf(file=\"%1%\", width=%2%, height=%3%); }";
+ else
+ code += " pdf(file=\"%1%\", width=%2%, height=%3%, "
+ " useDingbats=FALSE); }";
+ boost::format fmt(code);
+ std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(filePath.absolutePath()) %
+ widthInches %
+ heightInches);
+
+ // save the file
+ return savePlotAsFile(deviceCreationCode);
+}
+
+Error PlotManager::savePlotAsSvg(const FilePath& targetPath,
+ int width,
+ int height)
+{
+ // calculate size in inches
+ double widthInches = pixelsToInches(width);
+ double heightInches = pixelsToInches(height);
+
+ // generate code for creating svg device
+ boost::format fmt("{ require(grDevices, quietly=TRUE); "
+ " svg(filename=\"%1%\", width=%2%, height=%3%, "
+ " antialias = \"subpixel\"); }");
+ std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(targetPath.absolutePath()) %
+ widthInches %
+ heightInches);
+
+ return savePlotAsFile(deviceCreationCode);
+}
+
+Error PlotManager::savePlotAsPostscript(const FilePath& targetPath,
+ int width,
+ int height)
+{
+ // calculate size in inches
+ double widthInches = pixelsToInches(width);
+ double heightInches = pixelsToInches(height);
+
+ // generate code for creating postscript device
+ boost::format fmt("{ require(grDevices, quietly=TRUE); "
+ " postscript(file=\"%1%\", width=%2%, height=%3%, "
+ " onefile = FALSE, "
+ " paper = \"special\", "
+ " horizontal = FALSE); }");
+ std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(targetPath.absolutePath()) %
+ widthInches %
+ heightInches);
+
+ return savePlotAsFile(deviceCreationCode);
+}
+
+
+Error PlotManager::savePlotAsMetafile(const core::FilePath& filePath,
+ int widthPx,
+ int heightPx)
+{
+#ifdef _WIN32
+ // calculate size in inches
+ double widthInches = pixelsToInches(widthPx);
+ double heightInches = pixelsToInches(heightPx);
+
+ // generate code for creating metafile device
+ boost::format fmt("{ require(grDevices, quietly=TRUE); "
+ " win.metafile(filename=\"%1%\", width=%2%, height=%3%, "
+ " restoreConsole=FALSE); }");
+ std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(filePath.absolutePath()) %
+ widthInches %
+ heightInches);
+
+ return savePlotAsFile(deviceCreationCode);
+
+#else
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+#endif
+}
+
+
+bool PlotManager::hasOutput() const
+{
+ return hasPlot();
+}
+
+bool PlotManager::hasChanges() const
+{
+ return displayHasChanges_ ;
+}
+
+bool PlotManager::isActiveDevice() const
+{
+ return graphicsDevice_.isActive();
+}
+
+boost::posix_time::ptime PlotManager::lastChange() const
+{
+ return lastChange_;
+}
+
+void PlotManager::render(boost::function<void(DisplayState)> outputFunction)
+{
+ // make sure the graphics path exists (may have been blown away
+ // by call to dev.off or other call to removeAllPlots)
+ Error error = graphicsPath_.ensureDirectory();
+ if (error)
+ {
+ Error graphicsError(errc::PlotFileError, error, ERROR_LOCATION);
+ logAndReportError(graphicsError, ERROR_LOCATION);
+ return;
+ }
+
+ // clear changes flag
+ setDisplayHasChanges(false);
+
+ // optional manipulator structure
+ json::Value plotManipulatorJson;
+
+ if (hasPlot()) // write image for active plot
+ {
+ // copy current contents of the display to the active plot files
+ Error error = activePlot().renderFromDisplay();
+ if (error)
+ {
+ // no such file error expected in the case of an invalid graphics
+ // context (because generation of the PNG would have failed)
+ bool pngNotFound = error.cause() && isPathNotFoundError(error.cause());
+
+ // only log if this wasn't png not found
+ if (!pngNotFound)
+ {
+ // for r code execution errors we just report them
+ if (r::isCodeExecutionError(error))
+ reportError(error);
+ else
+ logAndReportError(error, ERROR_LOCATION);
+ }
+
+ return;
+ }
+
+ // get manipulator
+ activePlot().manipulatorAsJson(&plotManipulatorJson);
+ }
+ else // write "empty" image
+ {
+ // create an empty file
+ FilePath emptyImageFilePath = graphicsPath_.complete(emptyImageFilename());
+ error = writeStringToFile(emptyImageFilePath, std::string());
+ if (error)
+ {
+ Error graphicsError(errc::PlotRenderingError, error, ERROR_LOCATION);
+ logAndReportError(graphicsError, ERROR_LOCATION);
+ return;
+ }
+ }
+
+ // call output function
+ DisplayState currentState(imageFilename(),
+ plotManipulatorJson,
+ r::session::graphics::device::getWidth(),
+ r::session::graphics::device::getHeight(),
+ activePlotIndex(),
+ plotCount());
+ outputFunction(currentState);
+}
+
+std::string PlotManager::imageFilename() const
+{
+ if (hasPlot())
+ {
+ return plots_[activePlot_]->imageFilename();
+ }
+ else
+ {
+ return emptyImageFilename();
+ }
+}
+
+void PlotManager::refresh()
+{
+ invalidateActivePlot();
+}
+
+FilePath PlotManager::imagePath(const std::string& imageFilename) const
+{
+ return graphicsPath_.complete(imageFilename);
+}
+
+void PlotManager::clear()
+{
+ graphicsDevice_.close();
+}
+
+
+
+boost::signal<void ()>& PlotManager::onShowManipulator()
+{
+ return plotManipulatorManager().onShowManipulator();
+}
+
+
+
+void PlotManager::setPlotManipulatorValues(const json::Object& values)
+{
+ return plotManipulatorManager().setPlotManipulatorValues(values);
+}
+
+void PlotManager::manipulatorPlotClicked(int x, int y)
+{
+ plotManipulatorManager().manipulatorPlotClicked(x, y);
+}
+
+
+void PlotManager::onBeforeExecute()
+{
+ graphicsDevice_.onBeforeExecute();
+}
+
+Error PlotManager::savePlotsState()
+{
+ // list to write
+ std::vector<std::string> plots ;
+
+ // write the storage id of the active plot
+ if (hasPlot())
+ plots.push_back(activePlot().storageUuid());
+
+ // build sequence of plot info (id:width,height)
+ for (boost::circular_buffer<PtrPlot>::const_iterator it = plots_.begin();
+ it != plots_.end();
+ ++it)
+ {
+ const Plot& plot = *(it->get());
+
+ boost::format fmt("%1%:%2%,%3%");
+ std::string plotInfo = boost::str(fmt % plot.storageUuid() %
+ plot.renderedSize().width %
+ plot.renderedSize().height);
+ plots.push_back(plotInfo);
+ }
+
+ // suppres all device events after suspend
+ suppressDeviceEvents_ = true ;
+
+ // write plot list
+ return writeStringVectorToFile(plotsStateFile_, plots);
+}
+
+Error PlotManager::restorePlotsState()
+{
+ // exit if we don't have a plot list
+ if (!plotsStateFile_.exists())
+ return Success() ;
+
+ // read plot list from file
+ std::vector<std::string> plots;
+ Error error = readStringVectorFromFile(plotsStateFile_, &plots);
+ if (error)
+ return error;
+
+ // if it is empty then return succes
+ if (plots.empty())
+ return Success();
+
+ // read the storage id of the active plot them remove it from the list
+ std::string activePlotStorageId ;
+ if (!plots.empty())
+ {
+ activePlotStorageId = plots[0];
+ plots.erase(plots.begin());
+ }
+
+ // initialize plot list
+ std::string plotInfo;
+ for (int i=0; i<(int)plots.size(); ++i)
+ {
+ std::string plotStorageId ;
+ DisplaySize renderedSize(0,0);
+
+ // extract the id, width, and height
+ plotInfo = plots[i];
+ boost::cmatch matches ;
+ if (boost::regex_match(plotInfo.c_str(), matches, plotInfoRegex_) &&
+ (matches.size() > 3) )
+ {
+ plotStorageId = matches[1];
+ renderedSize.width = boost::lexical_cast<int>(matches[2]);
+ renderedSize.height = boost::lexical_cast<int>(matches[3]);
+ }
+
+ // create next plot
+ PtrPlot ptrPlot(new Plot(graphicsDevice_,
+ graphicsPath_,
+ plotStorageId,
+ renderedSize));
+
+ // ensure it actually exists on disk before we add it
+ if (ptrPlot->hasValidStorage())
+ {
+ plots_.push_back(ptrPlot);
+
+ // set it as active if necessary
+ if (plotStorageId == activePlotStorageId)
+ activePlot_ = i;
+ }
+ }
+
+ // if we didn't find the active plot or if it exceeds the size
+ // of the circular buffer (would happen when migrating from a
+ // suspended session that allowed more plots)
+ if ((activePlot_ == -1) ||
+ (activePlot_ > (static_cast<int>(plots_.size()) - 1)))
+ {
+ activePlot_ = plots_.size() - 1;
+ }
+
+ // restore snapshot for the active plot
+ if (hasPlot())
+ renderActivePlotToDisplay();
+
+ return Success();
+}
+
+namespace {
+
+Error copyDirectory(const FilePath& srcDir, const FilePath& targetDir)
+{
+ Error error = targetDir.removeIfExists();
+ if (error)
+ return error;
+
+ error = targetDir.ensureDirectory();
+ if (error)
+ return error;
+
+ std::vector<FilePath> srcFiles;
+ error = srcDir.children(&srcFiles);
+ if (error)
+ return error;
+ BOOST_FOREACH(const FilePath& srcFile, srcFiles)
+ {
+ FilePath targetFile = targetDir.complete(srcFile.filename());
+ Error error = srcFile.copy(targetFile);
+ if (error)
+ return error;
+ }
+
+ return Success();
+}
+
+} // anonymous namespace
+
+Error PlotManager::serialize(const FilePath& saveToPath)
+{
+ // save plots state
+ Error error = savePlotsState();
+ if (error)
+ return error;
+
+ // copy the plots dir to the save to path
+ return copyDirectory(graphicsPath_, saveToPath);
+}
+
+Error PlotManager::deserialize(const FilePath& restoreFromPath)
+{
+ // copy the restoreFromPath to the graphics path
+ Error error = copyDirectory(restoreFromPath, graphicsPath_);
+ if (error)
+ return error;
+
+ // restore plots state
+ return restorePlotsState();
+}
+
+
+void PlotManager::onDeviceNewPage(SEXP previousPageSnapshot)
+{
+ if (suppressDeviceEvents_)
+ return;
+
+ // if we have a plot with unrendered changes then save the previous snapshot
+ if (hasPlot() && hasChanges())
+ {
+ if (previousPageSnapshot != R_NilValue)
+ {
+ r::sexp::Protect protectSnapshot(previousPageSnapshot);
+ Error error = activePlot().renderFromDisplaySnapshot(
+ previousPageSnapshot);
+ if (error)
+ logAndReportError(error, ERROR_LOCATION);
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE(
+ "onDeviceNewPage was not passed a previousPageSnapshot");
+ }
+ }
+
+ // if we are replaying a manipulator then this plot replaces the
+ // existing plot
+ if (hasPlot() &&
+ plotManipulatorManager().replayingManipulator())
+ {
+ // create plot using the active plot's manipulator
+ PtrPlot ptrPlot(new Plot(graphicsDevice_,
+ graphicsPath_,
+ activePlot().manipulatorSEXP()));
+
+ // replace active plot
+ plots_[activePlotIndex()] = ptrPlot;
+ }
+ else
+ {
+ // if there is already a plot active then release its
+ // in-memory resources
+ if (hasPlot())
+ activePlot().purgeInMemoryResources();
+
+ // create new plot (use pending manipulator, if any)
+ PtrPlot ptrPlot(new Plot(graphicsDevice_,
+ graphicsPath_,
+ plotManipulatorManager().pendingManipulatorSEXP()));
+
+ // if we're full then remove the first plot's files before adding a new one
+ if (plots_.full())
+ {
+ Error error = plots_.front()->removeFiles();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // add the plot
+ plots_.push_back(ptrPlot);
+ activePlot_ = plots_.size() - 1 ;
+ }
+
+ // once we render the new plot we always reset pending manipulator state
+ plotManipulatorManager().clearPendingManipulatorState();
+
+ // ensure updates
+ invalidateActivePlot();
+}
+
+void PlotManager::onDeviceDrawing()
+{
+ if (suppressDeviceEvents_)
+ return;
+
+ invalidateActivePlot();
+}
+
+void PlotManager::onDeviceResized()
+{
+ if (suppressDeviceEvents_)
+ return;
+
+ invalidateActivePlot();
+}
+
+void PlotManager::onDeviceClosed()
+{
+ if (suppressDeviceEvents_)
+ return ;
+
+ // clear plots
+ activePlot_ = -1;
+ plots_.clear();
+
+ // trip changes flag to ensure repaint
+ setDisplayHasChanges(true);
+
+ // remove all files
+ Error error = plotsStateFile_.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ error = graphicsPath_.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+}
+
+Plot& PlotManager::activePlot() const
+{
+ return *(plots_[activePlot_]);
+}
+
+bool PlotManager::isValidPlotIndex(int index) const
+{
+ return (index >= 0) && (index < (int)plots_.size());
+}
+
+bool PlotManager::hasPlot() const
+{
+ return activePlot_ >= 0;
+}
+
+void PlotManager::setDisplayHasChanges(bool hasChanges)
+{
+ displayHasChanges_ = hasChanges;
+
+ if (hasChanges)
+ lastChange_ = boost::posix_time::microsec_clock::universal_time();
+ else
+ lastChange_ = boost::posix_time::not_a_date_time;
+}
+
+
+void PlotManager::invalidateActivePlot()
+{
+ setDisplayHasChanges(true);
+
+ if (hasPlot())
+ activePlot().invalidate();
+}
+
+// render active plot to display (used in setActivePlot and onSessionResume)
+void PlotManager::renderActivePlotToDisplay()
+{
+ suppressDeviceEvents_ = true;
+
+ // attempt to render the active plot -- notify end user if there is an error
+ Error error = activePlot().renderToDisplay();
+ if (error)
+ reportError(error);
+
+ suppressDeviceEvents_ = false;
+
+}
+
+
+Error PlotManager::plotIndexError(int index, const ErrorLocation& location)
+ const
+{
+ Error error(errc::InvalidPlotIndex, location);
+ error.addProperty("index", index);
+ return error;
+}
+
+
+std::string PlotManager::emptyImageFilename() const
+{
+ return "empty." + graphicsDevice_.imageFileExtension();
+}
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManager.hpp b/src/cpp/r/session/graphics/RGraphicsPlotManager.hpp
new file mode 100644
index 0000000..925cb41
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManager.hpp
@@ -0,0 +1,216 @@
+/*
+ * RGraphicsPlotManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_PLOT_MANAGER_HPP
+#define R_SESSION_GRAPHICS_PLOT_MANAGER_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/signal.hpp>
+#include <boost/regex.hpp>
+#include <boost/circular_buffer.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <r/session/RGraphics.hpp>
+
+#include "RGraphicsTypes.hpp"
+#include "RGraphicsPlot.hpp"
+
+namespace r {
+namespace session {
+namespace graphics {
+
+// singleton
+class PlotManager;
+PlotManager& plotManager();
+
+struct GraphicsDeviceEvents
+{
+ boost::signal<void (SEXP)> onNewPage;
+ boost::signal<void ()> onDrawing;
+ boost::signal<void ()> onResized;
+ boost::signal<void ()> onClosed;
+};
+
+class PlotManipulatorManager;
+
+class PlotManager : boost::noncopyable, public r::session::graphics::Display
+{
+private:
+ PlotManager();
+ friend PlotManager& plotManager();
+
+public:
+ virtual ~PlotManager() {}
+
+ core::Error initialize(const core::FilePath& graphicsPath,
+ const GraphicsDeviceFunctions& graphicsDevice,
+ GraphicsDeviceEvents* pEvents);
+
+ // plot list
+ virtual int plotCount() const;
+ virtual core::Error plotImageFilename(int index,
+ std::string* pImageFilename) const;
+ virtual int activePlotIndex() const;
+ virtual core::Error setActivePlot(int index) ;
+ virtual core::Error removePlot(int index);
+
+ // actions on active plot
+ virtual core::Error savePlotAsImage(const core::FilePath& filePath,
+ const std::string& format,
+ int widthPx,
+ int heightPx);
+
+ virtual core::Error savePlotAsPdf(const core::FilePath& filePath,
+ double widthInches,
+ double heightInches,
+ bool useCairoPdf);
+
+ virtual core::Error savePlotAsMetafile(const core::FilePath& filePath,
+ int widthPx,
+ int heightPx);
+
+ // display
+ virtual bool hasOutput() const;
+ virtual bool hasChanges() const;
+ virtual bool isActiveDevice() const;
+ virtual boost::posix_time::ptime lastChange() const;
+ virtual void render(boost::function<void(DisplayState)> outputFunction);
+ virtual std::string imageFilename() const ;
+ virtual void refresh() ;
+
+ // retrieve image path based on filename
+ virtual core::FilePath imagePath(const std::string& imageFilename) const;
+
+ virtual void clear();
+
+ virtual boost::signal<void ()>& onShowManipulator() ;
+ virtual void setPlotManipulatorValues(const core::json::Object& values);
+ virtual void manipulatorPlotClicked(int x, int y);
+
+ virtual void onBeforeExecute();
+
+ // manipulate persistent state
+ core::Error savePlotsState();
+ core::Error restorePlotsState();
+
+ // fully serialize and deserialize to an external directory
+ core::Error serialize(const core::FilePath& saveToPath);
+ core::Error deserialize(const core::FilePath& restoreFromPath);
+
+private:
+
+ // make plot manipulator manager a friend
+ friend class PlotManipulatorManager;
+
+ // typedefs
+ typedef boost::shared_ptr<Plot> PtrPlot ;
+
+ // device events
+ void onDeviceNewPage(SEXP previousPageSnapshot);
+ void onDeviceDrawing();
+ void onDeviceResized();
+ void onDeviceClosed();
+
+ // active plot
+ Plot& activePlot() const;
+ bool isValidPlotIndex(int index) const;
+ bool hasPlot() const;
+
+ // set change flag
+ void setDisplayHasChanges(bool hasChanges);
+
+ // invalidate the active plot
+ void invalidateActivePlot();
+
+ // render active plot to display (used in setActivePlot and onSessionResume)
+ void renderActivePlotToDisplay();
+
+ // render active plot file file
+ core::Error savePlotAsFile(const boost::function<core::Error()>&
+ deviceCreationFunction);
+ core::Error savePlotAsFile(const std::string& fileDeviceCreationCode);
+
+ core::Error savePlotAsBitmapFile(const core::FilePath& targetPath,
+ const std::string& bitmapFileType,
+ int width,
+ int height);
+
+ core::Error savePlotAsSvg(const core::FilePath& targetPath,
+ int width,
+ int height);
+
+ core::Error savePlotAsPostscript(const core::FilePath& targetPath,
+ int width,
+ int height);
+
+
+ // error helpers
+ core::Error plotIndexError(int index, const core::ErrorLocation& location)
+ const;
+
+ std::string emptyImageFilename() const ;
+
+private:
+ friend class SuppressDeviceEventsScope;
+
+ // storage paths
+ core::FilePath plotsStateFile_;
+ core::FilePath graphicsPath_;
+
+ // interface to graphics device
+ GraphicsDeviceFunctions graphicsDevice_ ;
+
+ // state
+ bool displayHasChanges_;
+ boost::posix_time::ptime lastChange_;
+ bool suppressDeviceEvents_;
+
+ int activePlot_;
+ boost::circular_buffer<PtrPlot> plots_ ;
+
+ boost::regex plotInfoRegex_;
+};
+
+class SuppressDeviceEventsScope
+{
+public:
+ SuppressDeviceEventsScope(PlotManager& plotManager)
+ : plotManager_(plotManager)
+ {
+ plotManager_.suppressDeviceEvents_ = true;
+ }
+
+ virtual ~SuppressDeviceEventsScope()
+ {
+ plotManager_.suppressDeviceEvents_ = false;
+ }
+private:
+ PlotManager& plotManager_;
+};
+
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_GRAPHICS_PLOT_MANAGER_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManipulator.cpp b/src/cpp/r/session/graphics/RGraphicsPlotManipulator.cpp
new file mode 100644
index 0000000..c8bdbcc
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManipulator.cpp
@@ -0,0 +1,271 @@
+/*
+ * RGraphicsPlotManipulator.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsPlotManipulator.hpp"
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RJson.hpp>
+
+using namespace core;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+
+PlotManipulator::PlotManipulator()
+ : sexp_()
+{
+}
+
+PlotManipulator::PlotManipulator(SEXP sexp)
+ : sexp_(sexp)
+{
+}
+
+PlotManipulator::~PlotManipulator()
+{
+ try
+ {
+ }
+ catch(...)
+ {
+ }
+}
+
+void PlotManipulator::clear()
+{
+ sexp_.releaseNow();
+}
+
+Error PlotManipulator::save(const FilePath& filePath) const
+{
+ // call manipulator save
+ r::exec::RFunction manipSave("manipulate:::manipulatorSave");
+ manipSave.addParam(sexp_.get());
+ manipSave.addParam(filePath.absolutePath());
+ return manipSave.call();
+}
+
+Error PlotManipulator::load(const FilePath& filePath)
+{
+ // call manipulator load
+ r::exec::RFunction manipLoad("manipulate:::manipulatorLoad");
+ manipLoad.addParam(filePath.absolutePath());
+ r::sexp::Protect rProtect;
+ SEXP manipSEXP;
+ Error error = manipLoad.call(&manipSEXP, &rProtect);
+ if (error)
+ return error;
+
+ // set it
+ sexp_.set(manipSEXP);
+ return Success();
+}
+
+void PlotManipulator::asJson(core::json::Value* pValue) const
+{
+ if (!empty())
+ {
+ // build manipulator json
+ core::json::Object manipulator;
+
+ // meta-info
+ manipulator["id"] = getAsJson(".id");
+ manipulator["controls"] = getControlsAsJson();
+ manipulator["variables"] = getAsJson(".variables");
+
+ // variable values
+ core::json::Value valuesJson;
+ SEXP valuesSEXP = getUserVisibleValuesList();
+ Error error = r::json::jsonValueFromObject(valuesSEXP, &valuesJson);
+ if (error)
+ LOG_ERROR(error);
+ manipulator["values"] = valuesJson;
+
+ // return manipualtor
+ *pValue = manipulator;
+ }
+ else
+ {
+ *pValue = core::json::Value();
+ }
+}
+
+SEXP PlotManipulator::sexp() const
+{
+ return sexp_.get();
+}
+
+SEXP PlotManipulator::get(const std::string& name) const
+{
+ if (!empty())
+ {
+ r::exec::RFunction getFunction("get");
+ getFunction.addParam(name);
+ getFunction.addParam("envir", sexp());
+ r::sexp::Protect rProtect;
+ SEXP valueSEXP;
+ Error error = getFunction.call(&valueSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return R_NilValue;
+ }
+ else
+ {
+ return valueSEXP;
+ }
+ }
+ else
+ {
+ return R_NilValue;
+ }
+}
+
+core::json::Value PlotManipulator::getAsJson(const std::string& name) const
+{
+ core::json::Value value;
+ Error error = r::json::jsonValueFromObject(get(name), &value);
+ if (error)
+ LOG_ERROR(error);
+ return value;
+}
+
+core::json::Object PlotManipulator::getControlAsJson(SEXP controlSEXP) const
+{
+ // field names
+ std::vector<std::string> names ;
+ Error error = sexp::getNames(controlSEXP, &names);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return core::json::Object();
+ }
+
+ // json object to return
+ core::json::Object control;
+
+ int length = r::sexp::length(controlSEXP);
+ for (int i=0; i<length; i++)
+ {
+ // get name and control
+ std::string name = names[i];
+
+ // screen out values field
+ if (name == "values")
+ continue;
+
+ // get json for field
+ SEXP fieldSEXP = VECTOR_ELT(controlSEXP, i);
+ core::json::Value fieldValue;
+ Error error = r::json::jsonValueFromObject(fieldSEXP, &fieldValue);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return core::json::Object();
+ }
+
+ // set the field
+ control[name] = fieldValue;
+ }
+
+ // return the control
+ return control;
+}
+
+core::json::Object PlotManipulator::getControlsAsJson() const
+{
+ // extract controls
+ r::sexp::Protect rProtect;
+ SEXP controlsSEXP = get(".controls");
+ if (controlsSEXP != R_NilValue)
+ {
+ rProtect.add(controlsSEXP);
+
+ // are there any controls contained in the list?
+ if (r::sexp::length(controlsSEXP) > 0)
+ {
+
+ // control names
+ std::vector<std::string> controlNames ;
+ Error error = sexp::getNames(controlsSEXP, &controlNames);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return core::json::Object();
+ }
+
+ // json object to return
+ core::json::Object controls;
+
+ int length = r::sexp::length(controlsSEXP);
+ for (int i=0; i<length; i++)
+ {
+ // get name and control
+ std::string name = controlNames[i];
+ SEXP controlSEXP = VECTOR_ELT(controlsSEXP, i);
+ controls[name] = getControlAsJson(controlSEXP);
+ }
+
+ // return controls
+ return controls;
+ }
+ else
+ {
+ return core::json::Object();
+ }
+ }
+ else
+ {
+ return core::json::Object();
+ }
+}
+
+SEXP PlotManipulator::getUserVisibleValuesList() const
+{
+ SEXP variablesSEXP = get(".variables");
+ if (variablesSEXP != R_NilValue)
+ {
+ r::exec::RFunction userValuesFunction("manipulate:::userVisibleValues");
+ userValuesFunction.addParam(sexp());
+ userValuesFunction.addParam(variablesSEXP);
+ r::sexp::Protect rProtect;
+ SEXP valuesSEXP;
+ Error error = userValuesFunction.call(&valuesSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return R_NilValue;
+ }
+ else
+ {
+ return valuesSEXP;
+ }
+ }
+ else
+ {
+ return R_NilValue;
+ }
+}
+
+
+} // namespace graphics
+} // namespace session
+} // namesapce r
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManipulator.hpp b/src/cpp/r/session/graphics/RGraphicsPlotManipulator.hpp
new file mode 100644
index 0000000..9bce1f7
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManipulator.hpp
@@ -0,0 +1,69 @@
+/*
+ * RGraphicsPlotManipulator.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_PLOT_MANIPULATOR_HPP
+#define R_SESSION_GRAPHICS_PLOT_MANIPULATOR_HPP
+
+#include <boost/utility.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <r/RSexp.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+class PlotManipulator : boost::noncopyable
+{
+public:
+ PlotManipulator();
+ explicit PlotManipulator(SEXP sexp);
+ virtual ~PlotManipulator();
+
+ bool empty() const { return !sexp_; }
+
+ void clear();
+
+ core::Error save(const core::FilePath& filePath) const;
+ core::Error load(const core::FilePath& filePath);
+
+ void asJson(core::json::Value* pValue) const;
+
+ SEXP sexp() const;
+
+private:
+ SEXP get(const std::string& name) const;
+ core::json::Value getAsJson(const std::string& name) const;
+ core::json::Object getControlAsJson(SEXP controlSEXP) const;
+ core::json::Object getControlsAsJson() const;
+ SEXP getUserVisibleValuesList() const;
+
+private:
+ r::sexp::PreservedSEXP sexp_;
+};
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_SESSION_GRAPHICS_PLOT_MANIPULATOR_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.cpp b/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.cpp
new file mode 100644
index 0000000..7c6afb4
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.cpp
@@ -0,0 +1,353 @@
+/*
+ * RGraphicsPlotManipulatorManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsPlotManipulatorManager.hpp"
+
+#include <string>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RRoutines.hpp>
+
+#include "RGraphicsUtils.hpp"
+#include "RGraphicsPlotManager.hpp"
+
+using namespace core;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+namespace {
+
+void setManipulatorJsonValue(SEXP manipulatorSEXP,
+ const std::pair<std::string,json::Value>& object)
+{
+ // get the actual value to assign
+ r::exec::RFunction setFunction("manipulate:::setManipulatorValue");
+ setFunction.addParam(manipulatorSEXP);
+ setFunction.addParam(object.first);
+ setFunction.addParam(object.second);
+ Error error = setFunction.call();
+ if (error)
+ LOG_ERROR(error);
+}
+
+void setManipulatorValueToFalse(SEXP manipulatorSEXP, const std::string& name)
+{
+ setManipulatorJsonValue(manipulatorSEXP,
+ std::make_pair(name, json::toJsonValue(false)));
+}
+
+
+void safeExecuteManipulator(SEXP manipulatorSEXP)
+{
+ try
+ {
+ // execute the code within the manipulator.
+ Error error = r::exec::RFunction("manipulate:::manipulatorExecute",
+ manipulatorSEXP).call();
+
+ // r code execution errors are expected (e.g. for invalid manipulate
+ // code or incorrectly specified controls). if we get something that
+ // isn't an r code execution error then report and log it
+ if (error)
+ {
+ if (!r::isCodeExecutionError(error))
+ logAndReportError(error, ERROR_LOCATION);
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+void setManipulatorButtonsToFalse(SEXP manipulatorSEXP)
+{
+ // get the list of buttons
+ std::vector<std::string> buttonNames;
+ Error error = r::exec::RFunction("manipulate:::buttonNames",
+ manipulatorSEXP).call(&buttonNames);
+ if (error)
+ {
+ logAndReportError(error, ERROR_LOCATION);
+ return;
+ }
+
+ // set the values
+ std::for_each(buttonNames.begin(),
+ buttonNames.end(),
+ boost::bind(setManipulatorValueToFalse,
+ manipulatorSEXP,
+ _1));
+}
+
+SEXP rs_executeAndAttachManipulator(SEXP manipulatorSEXP)
+{
+ plotManipulatorManager().executeAndAttachManipulator(manipulatorSEXP);
+ return R_NilValue;
+}
+
+SEXP rs_hasActiveManipulator()
+{
+ r::sexp::Protect rProtect;
+ return r::sexp::create(plotManipulatorManager().hasActiveManipulator(),
+ &rProtect);
+}
+
+SEXP rs_activeManipulator()
+{
+ return plotManipulatorManager().activeManipulator();
+}
+
+SEXP rs_ensureManipulatorSaved()
+{
+ try
+ {
+ plotManipulatorManager().ensureManipulatorSaved();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+
+} // anonymous namespace
+
+
+PlotManipulatorManager& plotManipulatorManager()
+{
+ static PlotManipulatorManager instance;
+ return instance;
+}
+
+PlotManipulatorManager::PlotManipulatorManager()
+ : pendingManipulatorSEXP_(R_NilValue),
+ replayingManipulator_(false)
+{
+}
+
+
+Error PlotManipulatorManager::initialize(
+ const UnitConversionFunctions& convert)
+{
+ // save reference to device conversion function
+ convert_ = convert;
+
+ // register R entry points for this class
+ R_CallMethodDef execManipulatorMethodDef ;
+ execManipulatorMethodDef.name = "rs_executeAndAttachManipulator" ;
+ execManipulatorMethodDef.fun = (DL_FUNC) rs_executeAndAttachManipulator;
+ execManipulatorMethodDef.numArgs = 1;
+ r::routines::addCallMethod(execManipulatorMethodDef);
+
+ // register has active manipulator routine
+ R_CallMethodDef hasActiveManipulatorMethodDef ;
+ hasActiveManipulatorMethodDef.name = "rs_hasActiveManipulator" ;
+ hasActiveManipulatorMethodDef.fun = (DL_FUNC) rs_hasActiveManipulator;
+ hasActiveManipulatorMethodDef.numArgs = 0;
+ r::routines::addCallMethod(hasActiveManipulatorMethodDef);
+
+ // register active manipulator routine
+ R_CallMethodDef activeManipulatorMethodDef ;
+ activeManipulatorMethodDef.name = "rs_activeManipulator" ;
+ activeManipulatorMethodDef.fun = (DL_FUNC) rs_activeManipulator;
+ activeManipulatorMethodDef.numArgs = 0;
+ r::routines::addCallMethod(activeManipulatorMethodDef);
+
+ // register ensure manipulator saved routine
+ R_CallMethodDef ensureManipulatorSavedMethodDef ;
+ ensureManipulatorSavedMethodDef.name = "rs_ensureManipulatorSaved" ;
+ ensureManipulatorSavedMethodDef.fun = (DL_FUNC) rs_ensureManipulatorSaved;
+ ensureManipulatorSavedMethodDef.numArgs = 0;
+ r::routines::addCallMethod(ensureManipulatorSavedMethodDef);
+
+ return Success();
+}
+
+
+boost::signal<void ()>& PlotManipulatorManager::onShowManipulator()
+{
+ return onShowManipulator_;
+}
+
+
+// execute a manipulator
+void PlotManipulatorManager::executeAndAttachManipulator(SEXP manipulatorSEXP)
+{
+ // keep the pending manipulator set for the duration of this call.
+ // this allows the plot manager to "collect" it on a new plot
+ // but to ignore and let it die if the manipulator code block
+ // doesn't create a plot
+ pendingManipulatorSEXP_ = manipulatorSEXP;
+
+ // execute it
+ safeExecuteManipulator(pendingManipulatorSEXP_);
+
+ // did the code create a new manipulator that is still active?
+ bool showNewManipulator = (pendingManipulatorSEXP_ == R_NilValue) &&
+ hasActiveManipulator();
+
+ // always clear the pending manipulator state so it is only attached
+ // to plots which are generated by the code above
+ pendingManipulatorSEXP_ = R_NilValue;
+
+ // if the active plot has a manipulator after this call then
+ // notify listeners that we need to show the manipulator
+ if (showNewManipulator)
+ onShowManipulator_();
+}
+
+bool PlotManipulatorManager::hasActiveManipulator() const
+{
+ return activeManipulator() != R_NilValue;
+}
+
+// the "active" manipultor is either the currently pending manipulator
+// or if there is none pending then the the one associated with the
+// currently active plot
+SEXP PlotManipulatorManager::activeManipulator() const
+{
+ if (pendingManipulatorSEXP_ != R_NilValue)
+ {
+ return pendingManipulatorSEXP_;
+ }
+ else if (plotManager().hasPlot())
+ {
+ return plotManager().activePlot().manipulatorSEXP();
+ }
+ else
+ {
+ return R_NilValue;
+ }
+}
+
+bool PlotManipulatorManager::trackingMouseClicks(SEXP manipulatorSEXP) const
+{
+ r::exec::RFunction trackingMouseClicks("manipulate:::trackingMouseClicks");
+ trackingMouseClicks.addParam(manipulatorSEXP);
+ bool tracking = false;
+ Error error = trackingMouseClicks.call(&tracking);
+ if (error)
+ logAndReportError(error, ERROR_LOCATION);
+ return tracking;
+}
+
+bool PlotManipulatorManager::manipulatorIsActive() const
+{
+ return plotManager().hasPlot() &&
+ plotManager().activePlot().hasManipulator();
+}
+
+void PlotManipulatorManager::replayManipulator(SEXP manipulatorSEXP)
+{
+ replayingManipulator_ = true;
+ safeExecuteManipulator(manipulatorSEXP);
+ replayingManipulator_ = false;
+}
+
+void PlotManipulatorManager::setPlotManipulatorValues(const json::Object& values)
+{
+ if (manipulatorIsActive())
+ {
+ // get the manipulator
+ SEXP manipulatorSEXP = plotManager().activePlot().manipulatorSEXP();
+
+ // set the underlying values
+ std::for_each(values.begin(),
+ values.end(),
+ boost::bind(setManipulatorJsonValue, manipulatorSEXP, _1));
+
+ // replay the manipulator
+ replayManipulator(manipulatorSEXP);
+
+ // set all of the buttons to false
+ setManipulatorButtonsToFalse(manipulatorSEXP);
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("called setPlotManipulatorValues but active plot "
+ "has no manipulator");
+ }
+}
+
+void PlotManipulatorManager::manipulatorPlotClicked(int x, int y)
+{
+ if (manipulatorIsActive())
+ {
+ // get the manipulator
+ SEXP manipulatorSEXP = plotManager().activePlot().manipulatorSEXP();
+
+ // check if we are tracking mouse clicks
+ if (trackingMouseClicks(manipulatorSEXP))
+ {
+ // transform the coordinates to user
+ double deviceX = x;
+ double deviceY = y;
+ double userX = x;
+ double userY = y;
+ convert_.deviceToUser(&userX, &userY);
+ double ndcX = x;
+ double ndcY = y;
+ convert_.deviceToNDC(&ndcX,&ndcY);
+
+ // set the mouse click state
+ r::exec::RFunction setMouseClick("manipulate:::setMouseClick");
+ setMouseClick.addParam(manipulatorSEXP);
+ setMouseClick.addParam(deviceX);
+ setMouseClick.addParam(deviceY);
+ setMouseClick.addParam(userX);
+ setMouseClick.addParam(userY);
+ setMouseClick.addParam(ndcX);
+ setMouseClick.addParam(ndcY);
+ Error error = setMouseClick.call();
+ if (error)
+ {
+ logAndReportError(error, ERROR_LOCATION);
+ return;
+ }
+
+ // replay the manipulator
+ replayManipulator(manipulatorSEXP);
+
+ // unset the mouse click state
+ r::exec::RFunction clearMouseClick("manipulate:::clearMouseClick");
+ clearMouseClick.addParam(manipulatorSEXP);
+ error = clearMouseClick.call();
+ if (error)
+ logAndReportError(error, ERROR_LOCATION);
+ }
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("called manipulatorPlotClicked but active plot "
+ "has no manipulator");
+ }
+}
+
+void PlotManipulatorManager::ensureManipulatorSaved()
+{
+ if (plotManager().hasPlot() && plotManager().activePlot().hasManipulator())
+ plotManager().activePlot().saveManipulator();
+}
+
+} // namespace graphics
+} // namespace session
+} // namespace r
diff --git a/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.hpp b/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.hpp
new file mode 100644
index 0000000..224fd58
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsPlotManipulatorManager.hpp
@@ -0,0 +1,97 @@
+/*
+ * RGraphicsPlotManipulatorManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_PLOT_MANIPULATOR_MANAGER_HPP
+#define R_SESSION_GRAPHICS_PLOT_MANIPULATOR_MANAGER_HPP
+
+#include <boost/signal.hpp>
+
+#include <core/Error.hpp>
+#include <core/json/Json.hpp>
+
+#include <r/RSexp.hpp>
+
+#include "RGraphicsTypes.hpp"
+
+namespace core {
+ class Error;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+// singleton
+class PlotManipulatorManager;
+PlotManipulatorManager& plotManipulatorManager();
+
+
+class PlotManipulatorManager : boost::noncopyable
+{
+private:
+ friend PlotManipulatorManager& plotManipulatorManager();
+ PlotManipulatorManager();
+
+public:
+ virtual ~PlotManipulatorManager() {}
+
+public:
+ core::Error initialize(const UnitConversionFunctions& convert);
+
+ boost::signal<void ()>& onShowManipulator() ;
+ void setPlotManipulatorValues(const core::json::Object& values);
+ void manipulatorPlotClicked(int x, int y);
+
+ void executeAndAttachManipulator(SEXP manipulatorSEXP);
+ bool hasActiveManipulator() const;
+ SEXP activeManipulator() const;
+
+ bool replayingManipulator() const { return replayingManipulator_; }
+ SEXP pendingManipulatorSEXP() const { return pendingManipulatorSEXP_; }
+
+ void clearPendingManipulatorState()
+ {
+ pendingManipulatorSEXP_ = R_NilValue;
+ replayingManipulator_ = false;
+ }
+
+ void ensureManipulatorSaved();
+
+private:
+ bool manipulatorIsActive() const;
+ bool trackingMouseClicks(SEXP manipulatorSEXP) const;
+ void replayManipulator(SEXP manipulatorSEXP);
+
+private:
+ // pending manipulator
+ SEXP pendingManipulatorSEXP_;
+
+ // are we currently replaying a manipulator call?
+ bool replayingManipulator_;
+
+ // manipulator event hook
+ boost::signal<void ()> onShowManipulator_;
+
+ // unit conversion function
+ UnitConversionFunctions convert_;
+
+};
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+#endif // R_SESSION_GRAPHICS_PLOT_MANIPULATOR_MANAGER_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsTypes.hpp b/src/cpp/r/session/graphics/RGraphicsTypes.hpp
new file mode 100644
index 0000000..862ca18
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsTypes.hpp
@@ -0,0 +1,82 @@
+/*
+ * RGraphicsTypes.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_TYPES_HPP
+#define R_SESSION_GRAPHICS_TYPES_HPP
+
+#include <boost/utility.hpp>
+#include <boost/format.hpp>
+#include <boost/function.hpp>
+
+typedef struct SEXPREC *SEXP;
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+struct DisplaySize
+{
+ DisplaySize(int width, int height) : width(width), height(height) {}
+ DisplaySize() : width(0), height(0) {}
+ int width;
+ int height;
+
+ bool operator==(const DisplaySize& other) const
+ {
+ return width == other.width &&
+ height == other.height;
+ }
+
+ bool operator!=(const DisplaySize& other) const
+ {
+ return !(*this == other);
+ }
+};
+
+typedef boost::function<void(double*,double*)> UnitConversionFunction;
+
+struct UnitConversionFunctions
+{
+ UnitConversionFunction deviceToUser;
+ UnitConversionFunction deviceToNDC;
+};
+
+struct GraphicsDeviceFunctions
+{
+ boost::function<bool()> isActive;
+ boost::function<DisplaySize()> displaySize;
+ UnitConversionFunctions convert;
+ boost::function<core::Error(const core::FilePath&,
+ const core::FilePath&)> saveSnapshot;
+ boost::function<core::Error(const core::FilePath&)> restoreSnapshot;
+ boost::function<void()> copyToActiveDevice;
+ boost::function<std::string()> imageFileExtension;
+ boost::function<void()> close;
+ boost::function<void()> onBeforeExecute;
+};
+
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_SESSION_GRAPHICS_TYPES_HPP
+
diff --git a/src/cpp/r/session/graphics/RGraphicsUtils.cpp b/src/cpp/r/session/graphics/RGraphicsUtils.cpp
new file mode 100644
index 0000000..a03424d
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsUtils.cpp
@@ -0,0 +1,224 @@
+/*
+ * RGraphicsUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "RGraphicsUtils.hpp"
+
+#include <boost/format.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+
+#include <r/RExec.hpp>
+#include <r/ROptions.hpp>
+#include <r/RUtil.hpp>
+
+#include <Rinternals.h>
+#define R_USE_PROTOTYPES 1
+#include <R_ext/GraphicsEngine.h>
+#include <R_ext/GraphicsDevice.h>
+
+#include <r/RErrorCategory.hpp>
+
+using namespace core;
+
+namespace r {
+namespace session {
+namespace graphics {
+
+namespace {
+
+int s_compatibleEngineVersion = 8;
+
+#ifdef __APPLE__
+class QuartzStatus : boost::noncopyable
+{
+public:
+ QuartzStatus() : checked_(false), installed_(false) {}
+
+ bool isInstalled()
+ {
+ if (!checked_)
+ {
+ checked_ = true;
+
+ Error error = r::exec::RFunction("capabilities",
+ "aqua").call(&installed_);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return installed_;
+ }
+
+private:
+ bool checked_;
+ bool installed_;
+};
+
+bool hasRequiredGraphicsDevices(std::string* pMessage)
+{
+ static QuartzStatus s_quartzStatus;
+ if (!s_quartzStatus.isInstalled())
+ {
+ if (pMessage != NULL)
+ {
+ *pMessage = "\nWARNING: The version of R you are running against "
+ "does not support the quartz graphics device (which is "
+ "required by RStudio for graphics). The Plots tab will "
+ "be disabled until a version of R that supports quartz "
+ "is installed.";
+ }
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+#else
+
+bool hasRequiredGraphicsDevices(std::string* pMessage)
+{
+ return true;
+}
+
+#endif
+
+} // anonymous namespace
+
+void setCompatibleEngineVersion(int version)
+{
+ s_compatibleEngineVersion = version;
+}
+
+bool validateRequirements(std::string* pMessage)
+{
+ // get engineVersion
+ int engineVersion = R_GE_getVersion();
+
+ // version too old
+ if (engineVersion < 5)
+ {
+ if (pMessage != NULL)
+ {
+ boost::format fmt(
+ "R graphics engine version %1% is not supported by RStudio. "
+ "The Plots tab will be disabled until a newer version of "
+ "R is installed.");
+ *pMessage = boost::str(fmt % engineVersion);
+ }
+
+ return false;
+ }
+
+ // version too new
+ else if (engineVersion > s_compatibleEngineVersion)
+ {
+ if (pMessage != NULL)
+ {
+ boost::format fmt(
+ "R graphics engine version %1% is not supported by this "
+ "version of RStudio. The Plots tab will be disabled until "
+ "a newer version of RStudio is installed.");
+ *pMessage = boost::str(fmt % engineVersion);
+ }
+
+ return false;
+ }
+
+
+ // check for required devices
+ else
+ {
+ return hasRequiredGraphicsDevices(pMessage);
+ }
+}
+
+std::string extraBitmapParams()
+{
+#if defined(_WIN32)
+
+ // no extra params for windows
+
+#elif defined(__APPLE__)
+
+ return ", type = \"quartz\", antialias=\"default\"";
+
+#else
+
+ // if bitmapType is Xlib then force cairo if we can
+ if (r::options::getOption<std::string>("bitmapType") == "Xlib")
+ {
+ if (r::util::hasRequiredVersion("2.14") &&
+ r::util::hasCapability("cairo"))
+ {
+ return ", type = \"cairo\"";
+ }
+ }
+
+#endif
+
+ return "";
+}
+
+struct RestorePreviousGraphicsDeviceScope::Impl
+{
+ Impl() : pPreviousDevice(NULL) {}
+ pGEDevDesc pPreviousDevice;
+};
+
+
+RestorePreviousGraphicsDeviceScope::RestorePreviousGraphicsDeviceScope()
+ : pImpl_(new Impl())
+{
+ // save ptr to previously selected device (if there is one)
+ pImpl_->pPreviousDevice = Rf_NoDevices() ? NULL : GEcurrentDevice();
+}
+
+RestorePreviousGraphicsDeviceScope::~RestorePreviousGraphicsDeviceScope()
+{
+ try
+ {
+ // reslect the previously selected device if we had one
+ if (pImpl_->pPreviousDevice != NULL)
+ Rf_selectDevice(Rf_ndevNumber(pImpl_->pPreviousDevice->dev));
+ }
+ catch(...)
+ {
+ }
+}
+
+void reportError(const core::Error& error)
+{
+ std::string endUserMessage = r::endUserErrorMessage(error);
+ std::string errmsg = ("Graphics error: " + endUserMessage + "\n");
+ REprintf(errmsg.c_str());
+}
+
+void logAndReportError(const Error& error, const ErrorLocation& location)
+{
+ // log
+ core::log::logError(error, location);
+
+ // report to user
+ reportError(error);
+}
+
+
+} // namespace graphics
+} // namespace session
+} // namesapce r
+
diff --git a/src/cpp/r/session/graphics/RGraphicsUtils.hpp b/src/cpp/r/session/graphics/RGraphicsUtils.hpp
new file mode 100644
index 0000000..8346dac
--- /dev/null
+++ b/src/cpp/r/session/graphics/RGraphicsUtils.hpp
@@ -0,0 +1,58 @@
+/*
+ * RGraphicsUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef R_SESSION_GRAPHICS_UTILS_HPP
+#define R_SESSION_GRAPHICS_UTILS_HPP
+
+#include <boost/shared_ptr.hpp>
+
+namespace core {
+ class Error;
+ class ErrorLocation;
+ class FilePath;
+}
+
+namespace r {
+namespace session {
+namespace graphics {
+
+void setCompatibleEngineVersion(int version);
+bool validateRequirements(std::string* pMessage = NULL);
+
+std::string extraBitmapParams();
+
+class RestorePreviousGraphicsDeviceScope
+{
+public:
+ RestorePreviousGraphicsDeviceScope();
+ virtual ~RestorePreviousGraphicsDeviceScope();
+
+private:
+ struct Impl;
+ boost::shared_ptr<Impl> pImpl_;
+};
+
+void reportError(const core::Error& error);
+
+void logAndReportError(const core::Error& error,
+ const core::ErrorLocation& location);
+
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+#endif // R_SESSION_GRAPHICS_UTILS_HPP
+
diff --git a/src/cpp/r/session/graphics/RShadowPngGraphicsHandler.cpp b/src/cpp/r/session/graphics/RShadowPngGraphicsHandler.cpp
new file mode 100644
index 0000000..528b368
--- /dev/null
+++ b/src/cpp/r/session/graphics/RShadowPngGraphicsHandler.cpp
@@ -0,0 +1,565 @@
+/*
+ * RShadowPngGraphicsHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include <core/system/System.hpp>
+#include <core/StringUtils.hpp>
+
+#include <r/RExec.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#undef TRUE
+#undef FALSE
+
+#include "RGraphicsHandler.hpp"
+#include "RGraphicsUtils.hpp"
+
+#include <Rembedded.h>
+
+using namespace core ;
+
+namespace r {
+namespace session {
+namespace graphics {
+namespace handler {
+namespace shadow {
+
+namespace {
+
+class PreserveCurrentDeviceScope
+{
+public:
+ PreserveCurrentDeviceScope() : previousDevice_(NULL)
+ {
+ if (!NoDevices())
+ previousDevice_ = GEcurrentDevice();
+ }
+ ~PreserveCurrentDeviceScope()
+ {
+ try
+ {
+ // always restore previous device
+ if (previousDevice_ != NULL)
+ selectDevice(ndevNumber(previousDevice_->dev));
+ }
+ catch(...)
+ {
+ }
+ }
+private:
+ pGEDevDesc previousDevice_;
+};
+
+struct ShadowDeviceData
+{
+ ShadowDeviceData() : pShadowPngDevice(NULL) {}
+ pDevDesc pShadowPngDevice;
+};
+
+void shadowDevOff(DeviceContext* pDC)
+{
+ ShadowDeviceData* pDevData = (ShadowDeviceData*)pDC->pDeviceSpecific;
+ if (pDevData->pShadowPngDevice != NULL)
+ {
+ // kill the deviceF
+ pGEDevDesc geDev = desc2GEDesc(pDevData->pShadowPngDevice);
+
+ // only kill it is if is still alive
+ if (ndevNumber(pDevData->pShadowPngDevice) > 0)
+ {
+ // close the device -- don't log R errors because they can happen
+ // in the ordinary course of things for invalid graphics staes
+ Error error = r::exec::executeSafely(boost::bind(GEkillDevice, geDev));
+ if (error && !r::isCodeExecutionError(error))
+ LOG_ERROR(error);
+ }
+ // set to null
+ pDevData->pShadowPngDevice = NULL;
+ }
+}
+
+Error shadowDevDesc(DeviceContext* pDC, pDevDesc* pDev)
+{
+ ShadowDeviceData* pDevData = (ShadowDeviceData*)pDC->pDeviceSpecific;
+
+ // generate on demand
+ if (pDevData->pShadowPngDevice == NULL ||
+ ndevNumber(pDevData->pShadowPngDevice) == 0)
+ {
+ pDevData->pShadowPngDevice = NULL;
+
+ PreserveCurrentDeviceScope preserveCurrentDeviceScope;
+
+ // create PNG device (completely bail on error)
+ boost::format fmt("grDevices:::png(\"%1%\", %2%, %3% %4%, pointsize = 16)");
+ std::string code = boost::str(fmt %
+ string_utils::utf8ToSystem(pDC->targetPath.absolutePath()) %
+ pDC->width %
+ pDC->height %
+ r::session::graphics::extraBitmapParams());
+ Error err = r::exec::executeString(code);
+ if (err)
+ return err;
+
+ // save reference to shadow device
+ pDevData->pShadowPngDevice = GEcurrentDevice()->dev;
+ }
+
+ // return shadow device
+ *pDev = pDevData->pShadowPngDevice;
+ return Success();
+}
+
+// this version of the function is called from R graphics primitives
+// so can (and should) throw errors in R longjmp style
+pDevDesc shadowDevDesc(pDevDesc dev)
+{
+ try
+ {
+ DeviceContext* pDC = (DeviceContext*)dev->deviceSpecific;
+
+ pDevDesc shadowDev = NULL;
+ Error error = shadowDevDesc(pDC, &shadowDev);
+ if (error)
+ {
+ LOG_ERROR(error);
+ throw r::exec::RErrorException(error.summary());
+ }
+
+ return shadowDev;
+ }
+ catch(const r::exec::RErrorException& e)
+ {
+ r::exec::error("Shadow graphics device error: " +
+ std::string(e.message()));
+ }
+
+ // keep compiler happy
+ return NULL;
+}
+
+FilePath tempFile(const std::string& extension)
+{
+ FilePath tempFileDir(string_utils::systemToUtf8(R_TempDir));
+ FilePath tempFilePath = tempFileDir.complete(core::system::generateUuid(false) +
+ "." + extension);
+ return tempFilePath;
+}
+
+
+void shadowDevSync(DeviceContext* pDC)
+{
+ // get the rstudio device number
+ pGEDevDesc rsGEDevDesc = desc2GEDesc(pDC->dev);
+ int rsDeviceNumber = GEdeviceNumber(rsGEDevDesc);
+
+ // copy the rstudio device's display list onto the shadow device
+ PreserveCurrentDeviceScope preserveCurrentDevice;
+
+ pDevDesc dev = NULL;
+ Error error = shadowDevDesc(pDC, &dev);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+ selectDevice(ndevNumber(dev));
+
+ // copy display list (ignore R errors because they can happen in the normal
+ // course of things for invalid graphics states). also suppress output
+ // in scope because R 3.0 seems to sneak out error messages from within
+ // the invalid name warning in checkValidSymbolId in dotcode.c
+ {
+ r::session::utils::SuppressOutputInScope scope;
+ error = r::exec::executeSafely(boost::bind(GEcopyDisplayList,
+ rsDeviceNumber));
+ if (error && !r::isCodeExecutionError(error))
+ LOG_ERROR(error);
+ }
+}
+
+} // anonymous namespace
+
+
+bool initialize(int width, int height, DeviceContext* pDC)
+{
+ pDC->targetPath = tempFile("png");
+ pDC->width = width;
+ pDC->height = height;
+
+ return true;
+}
+
+DeviceContext* allocate(pDevDesc dev)
+{
+ // create device context
+ DeviceContext* pDC = new DeviceContext(dev);
+
+ // create device specific
+ pDC->pDeviceSpecific = new ShadowDeviceData();
+ return pDC;
+}
+
+void destroy(DeviceContext* pDC)
+{
+ // nix the shadow device
+ shadowDevOff(pDC);
+
+ // delete pointers
+ ShadowDeviceData* pDevData = (ShadowDeviceData*)pDC->pDeviceSpecific;
+ delete pDevData;
+ delete pDC;
+}
+
+void setSize(pDevDesc pDev)
+{
+ dev_desc::setSize(pDev);
+ dev_desc::setSize(shadowDevDesc(pDev));
+}
+
+void setDeviceAttributes(pDevDesc pDev)
+{
+ pDevDesc shadowDev = shadowDevDesc(pDev);
+ if (shadowDev == NULL)
+ return;
+
+ pDev->cra[0] = shadowDev->cra[0];
+ pDev->cra[1] = shadowDev->cra[1];
+ pDev->startps = shadowDev->startps;
+ pDev->ipr[0] = shadowDev->ipr[0];
+ pDev->ipr[1] = shadowDev->ipr[1];
+ pDev->xCharOffset = shadowDev->xCharOffset;
+ pDev->yCharOffset = shadowDev->yCharOffset;
+ pDev->yLineBias = shadowDev->yLineBias;
+
+ pDev->canClip = shadowDev->canClip;
+ pDev->canHAdj = shadowDev->canHAdj;
+ pDev->canChangeGamma = shadowDev->canChangeGamma;
+ pDev->startcol = shadowDev->startcol;
+ pDev->startfill = shadowDev->startfill;
+ pDev->startlty = shadowDev->startlty;
+ pDev->startfont = shadowDev->startfont;
+ pDev->startps = shadowDev->startps;
+ pDev->startgamma = shadowDev->startgamma;
+ pDev->displayListOn = TRUE;
+
+ // no support for events yet
+ pDev->canGenMouseDown = FALSE;
+ pDev->canGenMouseMove = FALSE;
+ pDev->canGenMouseUp = FALSE;
+ pDev->canGenKeybd = FALSE;
+ pDev->gettingEvent = FALSE;
+}
+
+// the shadow device is created during creation of the main RStudio
+// interactive graphics device (so we can copy its underlying device
+// attributes into the RStudio device) however if we don't turn the
+// shadow device off before adding the RStudio device then it shows
+// up in the display list AHEAD of the RStudio device. This is a very
+// bad state because it leaves the shadow device as the default
+// device whenever another device (e.g. png, pdf, x11, etc.) is closed
+void onBeforeAddDevice(DeviceContext* pDC)
+{
+ shadowDevOff(pDC);
+}
+void onAfterAddDevice(DeviceContext* pDC)
+{
+ pDevDesc dev;
+ Error error = shadowDevDesc(pDC, &dev);
+ if (error)
+ LOG_ERROR(error);
+}
+
+Error writeToPNG(const FilePath& targetPath, DeviceContext* pDC)
+{
+ // sync the shadow device to ensure we have the full playlist,
+ shadowDevSync(pDC);
+
+ // turn the shadow device off to write the file
+ shadowDevOff(pDC);
+
+ // if the targetPath != the bitmap path then copy it
+ Error error;
+ if (targetPath != pDC->targetPath)
+ {
+ // the target path would not exist if R failed to write the PNG
+ // (e.g. because the graphics device was too small for the content)
+ if (!pDC->targetPath.exists())
+ {
+ error = pathNotFoundError(ERROR_LOCATION);
+ }
+ else
+ {
+ error = pDC->targetPath.copy(targetPath);
+
+ Error deleteError = pDC->targetPath.remove();
+ if (deleteError)
+ LOG_ERROR(error);
+ }
+ }
+
+ // regenerate the shadow device
+ pDevDesc dev = pDC->dev;
+ int width = pDC->width;
+ int height = pDC->height;
+ handler::destroy(pDC);
+ pDC = handler::allocate(dev);
+ dev->deviceSpecific = pDC;
+
+ // re-create with the correct size
+ if (!handler::initialize(width, height, pDC))
+ return systemError(boost::system::errc::not_connected, ERROR_LOCATION);
+
+ // now update the device structure
+ handler::setSize(dev);
+
+ // replay the rstudio graphics device context onto the png
+ shadowDevSync(pDC);
+
+ // return status
+ return error;
+}
+
+
+void circle(double x,
+ double y,
+ double r,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->circle(x, y, r, gc, pngDevDesc);
+}
+
+void line(double x1,
+ double y1,
+ double x2,
+ double y2,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->line(x1, y1, x2, y2, gc, pngDevDesc);
+}
+
+void polygon(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->polygon(n, x, y, gc, pngDevDesc);
+}
+
+void polyline(int n,
+ double *x,
+ double *y,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->polyline(n, x, y, gc, pngDevDesc);
+}
+
+void rect(double x0,
+ double y0,
+ double x1,
+ double y1,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->rect(x0, y0, x1, y1, gc, pngDevDesc);
+}
+
+void path(double *x,
+ double *y,
+ int npoly,
+ int *nper,
+ Rboolean winding,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dd);
+ dev_desc::path(x, y, npoly, nper, winding, gc, pngDevDesc);
+}
+
+void raster(unsigned int *raster,
+ int w,
+ int h,
+ double x,
+ double y,
+ double width,
+ double height,
+ double rot,
+ Rboolean interpolate,
+ const pGEcontext gc,
+ pDevDesc dd)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dd);
+ dev_desc::raster(raster,
+ w,
+ h,
+ x,
+ y,
+ width,
+ height,
+ rot,
+ interpolate,
+ gc,
+ pngDevDesc);
+}
+
+SEXP cap(pDevDesc dd)
+{
+ return R_NilValue;
+}
+
+
+
+void metricInfo(int c,
+ const pGEcontext gc,
+ double* ascent,
+ double* descent,
+ double* width,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->metricInfo(c, gc, ascent, descent, width, pngDevDesc);
+}
+
+double strWidth(const char *str, const pGEcontext gc, pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ return dev_desc::strWidth(str, gc, pngDevDesc);
+}
+
+void text(double x,
+ double y,
+ const char *str,
+ double rot,
+ double hadj,
+ const pGEcontext gc,
+ pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ dev_desc::text(x, y, str, rot, hadj, gc, pngDevDesc);
+}
+
+void clip(double x0, double x1, double y0, double y1, pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->clip(x0, x1, y0, y1, pngDevDesc);
+}
+
+void newPage(const pGEcontext gc, pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ pngDevDesc->newPage(gc, pngDevDesc);
+}
+
+void mode(int mode, pDevDesc dev)
+{
+ pDevDesc pngDevDesc = shadowDevDesc(dev);
+ if (pngDevDesc == NULL)
+ return;
+
+ if (pngDevDesc->mode != NULL)
+ pngDevDesc->mode(mode, pngDevDesc);
+}
+
+void onBeforeExecute(DeviceContext* pDC)
+{
+ // if the shadow device has somehow become the active device
+ // then switch to the rstudio device. note this can occur if the
+ // user creates another device such as windows() or postscript() and
+ // then does a dev.off
+ pGEDevDesc pCurrentDevice = NoDevices() ? NULL : GEcurrentDevice();
+ ShadowDeviceData* pShadowDevData = (ShadowDeviceData*)pDC->pDeviceSpecific;
+ if (pCurrentDevice != NULL && pShadowDevData != NULL)
+ {
+ if (pCurrentDevice->dev == pShadowDevData->pShadowPngDevice)
+ {
+ // select the rstudio device
+ selectDevice(ndevNumber(pDC->dev));
+ }
+ }
+}
+
+} // namespace shadow
+
+void installShadowHandler()
+{
+ handler::allocate = shadow::allocate;
+ handler::destroy = shadow::destroy;
+ handler::initialize = shadow::initialize;
+ handler::setSize = shadow::setSize;
+ handler::setDeviceAttributes = shadow::setDeviceAttributes;
+ handler::onBeforeAddDevice = shadow::onBeforeAddDevice;
+ handler::onAfterAddDevice = shadow::onAfterAddDevice;
+ handler::writeToPNG = shadow::writeToPNG;
+ handler::circle = shadow::circle;
+ handler::line = shadow::line;
+ handler::polygon = shadow::polygon;
+ handler::polyline = shadow::polyline;
+ handler::rect = shadow::rect;
+ handler::path = shadow::path;
+ handler::raster = shadow::raster;
+ handler::cap = shadow::cap;
+ handler::metricInfo = shadow::metricInfo;
+ handler::strWidth = shadow::strWidth;
+ handler::text = shadow::text;
+ handler::clip = shadow::clip;
+ handler::newPage = shadow::newPage;
+ handler::mode = shadow::mode;
+ handler::onBeforeExecute = shadow::onBeforeExecute;
+}
+
+} // namespace handler
+} // namespace graphics
+} // namespace session
+} // namespace r
+
+
+
diff --git a/src/cpp/rdesktop-dev.in b/src/cpp/rdesktop-dev.in
new file mode 100755
index 0000000..7a71908
--- /dev/null
+++ b/src/cpp/rdesktop-dev.in
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+#
+# rdesktop-dev
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+session/rsession --config-file conf/rdesktop-dev.conf \
+ $1 $2 $3 $4 $5 $6 $7 $8 $9
+
+
+
diff --git a/src/cpp/rserver-dev.in b/src/cpp/rserver-dev.in
new file mode 100755
index 0000000..9b4ab3b
--- /dev/null
+++ b/src/cpp/rserver-dev.in
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+#
+# rserver-dev
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# remove stream files/dirs
+cleanupStreams()
+{
+ rm -rf /tmp/rstudio-rsession
+ rm -rf /tmp/rstudio-rserver
+}
+cleanupStreams
+
+# generic cleanup routine
+cleanup()
+{
+ cleanupStreams
+ exit $?
+}
+trap cleanup SIGINT
+
+server/rserver --config-file conf/rserver-dev.conf "$@"
+
diff --git a/src/cpp/rserver-test.in b/src/cpp/rserver-test.in
new file mode 100644
index 0000000..d853583
--- /dev/null
+++ b/src/cpp/rserver-test.in
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+#
+# rserver-test
+#
+# Copyright (C) 2009-14 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+./rserver-dev --www-port=4011 --auth-none=1 "$@"
+
diff --git a/src/cpp/server/CMakeLists.txt b/src/cpp/server/CMakeLists.txt
new file mode 100644
index 0000000..184e514
--- /dev/null
+++ b/src/cpp/server/CMakeLists.txt
@@ -0,0 +1,200 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (SERVER)
+
+add_subdirectory(pam)
+
+# include files
+file(GLOB_RECURSE SERVER_HEADER_FILES "*.h*")
+
+# source files
+set(SERVER_SOURCE_FILES
+ ServerAppArmor.cpp
+ ServerBrowser.cpp
+ ServerEval.cpp
+ ServerInit.cpp
+ ServerMain.cpp
+ ServerMainOverlay.cpp
+ ServerMeta.cpp
+ ServerOffline.cpp
+ ServerOptions.cpp
+ ServerOptionsOverlay.cpp
+ ServerPAMAuth.cpp
+ ServerREnvironment.cpp
+ ServerSessionProxy.cpp
+ ServerSessionManager.cpp
+ auth/ServerAuthHandler.cpp
+ auth/ServerSecureCookie.cpp
+ auth/ServerSecureUriHandler.cpp
+ auth/ServerValidateUser.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/ServerAddins.cpp
+)
+
+# define core include dirs
+set(CORE_INCLUDE_DIRS ${CORE_SOURCE_DIR}/include)
+
+# include addins
+if(RSTUDIO_ADDINS_PATH)
+ # search for addins (then remove special core directory)
+ file(GLOB RSTUDIO_ADDINS ${RSTUDIO_ADDINS_PATH}/*)
+ list(REMOVE_ITEM RSTUDIO_ADDINS "core")
+
+ # incorporate all addins found
+ foreach(RSTUDIO_ADDIN ${RSTUDIO_ADDINS})
+ set(SERVER_ADDIN_PATH ${RSTUDIO_ADDIN}/server)
+ if(EXISTS ${SERVER_ADDIN_PATH})
+ # glob the addin header, source, and template files
+ file(GLOB_RECURSE ADDIN_HEADER_FILES "${SERVER_ADDIN_PATH}/*.h*")
+ list(APPEND SERVER_HEADER_FILES ${ADDIN_HEADER_FILES})
+ file(GLOB_RECURSE ADDIN_SOURCE_FILES "${SERVER_ADDIN_PATH}/*.c*")
+ list(APPEND SERVER_SOURCE_FILES ${ADDIN_SOURCE_FILES})
+ file(GLOB_RECURSE ADDIN_TEMPLATE_FILES "${SERVER_ADDIN_PATH}/templates/*")
+ list(APPEND SERVER_ADDIN_TEMPLATE_FILES ${ADDIN_TEMPLATE_FILES})
+ # generate an initialize call for the addin
+ get_filename_component(ADDIN_NAME ${RSTUDIO_ADDIN} NAME)
+ set(SERVER_ADDIN_DECLARATIONS
+ "${SERVER_ADDIN_DECLARATIONS}namespace ${ADDIN_NAME} { Error initialize(); }\n" )
+ set(SERVER_ADDIN_INITIALIZATIONS
+ "${SERVER_ADDIN_INITIALIZATIONS}(${ADDIN_NAME}::initialize) ")
+ endif()
+ endforeach()
+
+ # add to core include dirs if appropriate
+ set(CORE_ADDINS_INCLUDE_DIR ${RSTUDIO_ADDINS_PATH}/core/include)
+ if(EXISTS ${CORE_ADDINS_INCLUDE_DIR})
+ list(APPEND CORE_INCLUDE_DIRS ${CORE_ADDINS_INCLUDE_DIR})
+ endif()
+
+endif()
+
+# always configure the addins bootstrap file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ServerAddins.cpp.in
+ ${CMAKE_CURRENT_BINARY_DIR}/ServerAddins.cpp)
+
+# configure template files into the www directory
+foreach(SERVER_ADDIN_TEMPLATE_FILE ${SERVER_ADDIN_TEMPLATE_FILES})
+ get_filename_component(TEMPLATE_FILE_NAME ${SERVER_ADDIN_TEMPLATE_FILE} NAME)
+ configure_file(${SERVER_ADDIN_TEMPLATE_FILE}
+ "${CMAKE_CURRENT_SOURCE_DIR}/../../gwt/www/templates/addins/${TEMPLATE_FILE_NAME}"
+ COPYONLY)
+endforeach()
+
+
+# generate config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/server-config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/server-config.h)
+
+# required on Fedora for AppArmor-related code to compile
+if(NOT APPLE)
+ find_library(DL_LIBRARIES dl)
+endif()
+
+# include directories and libraries
+set(SERVER_SYSTEM_LIBRARIES ${DL_LIBRARIES})
+
+
+# set include directories
+include_directories(
+ include
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${Boost_INCLUDE_DIRS}
+ ${SERVER_SYSTEM_INCLUDE_DIRS}
+ ${CORE_INCLUDE_DIRS}
+ ${MONITOR_SOURCE_DIR}/include
+ ${SESSION_SOURCE_DIR}/include
+)
+
+# define executable
+add_executable(rserver ${SERVER_SOURCE_FILES} ${SERVER_HEADER_FILES})
+
+# set link dependencies
+target_link_libraries(rserver
+ rstudio-core
+ rstudio-monitor
+ ${SERVER_SYSTEM_LIBRARIES}
+)
+
+# install binary
+install(TARGETS rserver DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+if (UNIX AND NOT APPLE)
+
+ # install configured admin script
+ set(RSERVER_ADMIN_SCRIPT "extras/admin/rstudio-server")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_ADMIN_SCRIPT}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_ADMIN_SCRIPT})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_ADMIN_SCRIPT}
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+ # install configured debian init.d script
+ set(RSERVER_INITD_DEBIAN_DIR "extras/init.d/debian")
+ set(RSERVER_INITD_DEBIAN_SCRIPT "${RSERVER_INITD_DEBIAN_DIR}/rstudio-server")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_INITD_DEBIAN_SCRIPT}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_DEBIAN_SCRIPT})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_DEBIAN_SCRIPT}
+ DESTINATION ${RSERVER_INITD_DEBIAN_DIR})
+
+ # install configured redhat init.d script
+ set(RSERVER_INITD_REDHAT_DIR "extras/init.d/redhat")
+ set(RSERVER_INITD_REDHAT_SCRIPT "${RSERVER_INITD_REDHAT_DIR}/rstudio-server")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_INITD_REDHAT_SCRIPT}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_REDHAT_SCRIPT})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_REDHAT_SCRIPT}
+ DESTINATION ${RSERVER_INITD_REDHAT_DIR})
+
+ # install configured suse init.d script
+ set(RSERVER_INITD_SUSE_DIR "extras/init.d/suse")
+ set(RSERVER_INITD_SUSE_SCRIPT "${RSERVER_INITD_SUSE_DIR}/rstudio-server")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_INITD_SUSE_SCRIPT}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_SUSE_SCRIPT})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_INITD_SUSE_SCRIPT}
+ DESTINATION ${RSERVER_INITD_SUSE_DIR})
+
+ # install pam profile
+ set(RSERVER_PAM_DIR "extras/pam")
+ set(RSERVER_PAM_PROFILE "${RSERVER_PAM_DIR}/rstudio")
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_PAM_PROFILE}
+ DESTINATION ${RSERVER_PAM_DIR})
+
+ # install configured apparmor profile
+ set(RSERVER_APPARMOR_DIR "extras/apparmor")
+ set(RSERVER_APPARMOR_PROFILE "${RSERVER_APPARMOR_DIR}/rstudio-server")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_APPARMOR_PROFILE}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_APPARMOR_PROFILE})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_APPARMOR_PROFILE}
+ DESTINATION ${RSERVER_APPARMOR_DIR})
+ install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_APPARMOR_DIR}/apparmor-profile-load
+ DESTINATION ${RSERVER_APPARMOR_DIR})
+
+ # install configured upstart profile
+ set(RSERVER_UPSTART_DIR "extras/upstart")
+ set(RSERVER_UPSTART_PROFILE "${RSERVER_UPSTART_DIR}/rstudio-server.conf")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_UPSTART_PROFILE}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_UPSTART_PROFILE})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_UPSTART_PROFILE}
+ DESTINATION ${RSERVER_UPSTART_DIR})
+ set(RSERVER_UPSTART_PROFILE_REDHAT "${RSERVER_UPSTART_DIR}/rstudio-server.redhat.conf")
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${RSERVER_UPSTART_PROFILE_REDHAT}.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_UPSTART_PROFILE_REDHAT})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${RSERVER_UPSTART_PROFILE_REDHAT}
+ DESTINATION ${RSERVER_UPSTART_DIR})
+
+endif()
+
+# add overlay if it exists
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+endif()
diff --git a/src/cpp/server/Main.cpp b/src/cpp/server/Main.cpp
new file mode 100644
index 0000000..29cd41e
--- /dev/null
+++ b/src/cpp/server/Main.cpp
@@ -0,0 +1,27 @@
+/*
+ * Main.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+
+int main(int argc, char * const argv[])
+{
+ core::system::initializeLog("rserver", 2);
+
+ LOG_WARNING_MESSAGE("hello cmake!");
+
+ return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/cpp/server/ServerAddins.cpp.in b/src/cpp/server/ServerAddins.cpp.in
new file mode 100644
index 0000000..9f96a96
--- /dev/null
+++ b/src/cpp/server/ServerAddins.cpp.in
@@ -0,0 +1,48 @@
+/*
+ * ServerAddins.cpp.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+using namespace core;
+
+namespace server {
+namespace addins {
+
+${SERVER_ADDIN_DECLARATIONS}
+
+namespace {
+
+Error dummyInit()
+{
+ return Success();
+}
+
+}
+
+Error initialize()
+{
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ ${SERVER_ADDIN_INITIALIZATIONS}
+ (dummyInit);
+
+ return initBlock.execute();
+}
+
+
+} // namespace addins
+} // namespace server
+
diff --git a/src/cpp/server/ServerAddins.hpp b/src/cpp/server/ServerAddins.hpp
new file mode 100644
index 0000000..83ac40c
--- /dev/null
+++ b/src/cpp/server/ServerAddins.hpp
@@ -0,0 +1,32 @@
+/*
+ * ServerAddins.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_ADDINS_HPP
+#define SERVER_ADDINS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace addins {
+
+core::Error initialize();
+
+} // namespace addins
+} // namespace server
+
+#endif // SERVER_ADDINS_HPP
+
diff --git a/src/cpp/server/ServerAppArmor.cpp b/src/cpp/server/ServerAppArmor.cpp
new file mode 100644
index 0000000..449d013
--- /dev/null
+++ b/src/cpp/server/ServerAppArmor.cpp
@@ -0,0 +1,104 @@
+/*
+ * ServerAppArmor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerAppArmor.hpp"
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/Random.hpp>
+
+#ifndef __APPLE__
+#include <dlfcn.h>
+#endif
+
+using namespace core;
+
+namespace server {
+namespace app_armor {
+
+#ifdef __APPLE__
+
+Error enforceRestricted()
+{
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+}
+
+#else
+
+
+namespace {
+
+void addLastDLErrorMessage(Error* pError)
+{
+ const char* msg = ::dlerror();
+ if (msg != NULL)
+ pError->addProperty("dlerror", std::string(msg));
+}
+
+} // anonymous namespace
+
+Error enforceRestricted()
+{
+ // dynamically load libapparmor
+ void* pLibAA = ::dlopen("libapparmor.so.1", RTLD_NOW);
+ if (pLibAA == NULL)
+ {
+ Error error = systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ addLastDLErrorMessage(&error);
+ return error;
+ }
+
+ // lookup the change hat function
+ typedef int (*PtrAAChangeHat)(const char*, unsigned long);
+ PtrAAChangeHat pChangeHat = (PtrAAChangeHat)::dlsym(pLibAA,
+ "aa_change_hat");
+ if (pChangeHat == NULL)
+ {
+ Error error = systemError(boost::system::errc::not_supported,
+ ERROR_LOCATION);
+ addLastDLErrorMessage(&error);
+ return error;
+ }
+
+ // change to restricted
+ if (pChangeHat("restricted", 0) == -1)
+ {
+ // if this is operation not permitted then simply log a warning
+ // (this occurs when the app armor profile is disabled)
+ if (errno == EPERM)
+ {
+ LOG_WARNING_MESSAGE("Unable to change rserver into app armor "
+ "restricted hat (profile may be disabled)");
+ return Success();
+ }
+ else
+ {
+ return systemError(errno, ERROR_LOCATION);
+ }
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+#endif
+
+
+} // namespace app_aprmor
+} // namespace server
+
diff --git a/src/cpp/server/ServerAppArmor.hpp b/src/cpp/server/ServerAppArmor.hpp
new file mode 100644
index 0000000..1bd06f1
--- /dev/null
+++ b/src/cpp/server/ServerAppArmor.hpp
@@ -0,0 +1,34 @@
+/*
+ * ServerAppArmor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_APP_ARMOR_HPP
+#define SERVER_APP_ARMOR_HPP
+
+#include <boost/utility.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace app_armor {
+
+core::Error enforceRestricted();
+
+} // namespace app_armor
+} // namespace server
+
+#endif // SERVER_APP_ARMOR_HPP
+
diff --git a/src/cpp/server/ServerBrowser.cpp b/src/cpp/server/ServerBrowser.cpp
new file mode 100644
index 0000000..06cbeff
--- /dev/null
+++ b/src/cpp/server/ServerBrowser.cpp
@@ -0,0 +1,73 @@
+/*
+ * ServerBrowser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerBrowser.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Log.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <server/ServerOptions.hpp>
+
+using namespace core;
+
+namespace server {
+namespace browser {
+
+const char * const kBrowserUnsupported = "/unsupported_browser.htm";
+
+bool supportedBrowserFilter(const http::Request& request,
+ http::Response* pResponse)
+{
+ using namespace boost::algorithm;
+
+ std::string userAgent = request.headerValue("User-Agent");
+
+ if (contains(userAgent, "Chrome") ||
+ contains(userAgent, "chromeframe") ||
+ contains(userAgent, "Firefox") ||
+ contains(userAgent, "Safari") ||
+ contains(userAgent, "AppleWebKit"))
+ {
+ return true;
+ }
+ else // unknown browser
+ {
+ pResponse->setMovedTemporarily(request, kBrowserUnsupported);
+ return false;
+ }
+}
+
+
+void handleBrowserUnsupportedRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // get the path to the browser file
+ Options& options = server::options();
+ FilePath wwwPath(options.wwwLocalPath());
+ FilePath browserFilePath = wwwPath.complete(std::string(".") + kBrowserUnsupported);
+
+ // return browser page
+ pResponse->setNoCacheHeaders();
+ pResponse->setFile(browserFilePath, request);
+ pResponse->setContentType("text/html");
+}
+
+} // namespace browser
+} // namespace server
+
diff --git a/src/cpp/server/ServerBrowser.hpp b/src/cpp/server/ServerBrowser.hpp
new file mode 100644
index 0000000..d26c2db
--- /dev/null
+++ b/src/cpp/server/ServerBrowser.hpp
@@ -0,0 +1,43 @@
+/*
+ * ServerBrowser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_BROWSER_HPP
+#define SERVER_BROWSER_HPP
+
+#include <string>
+
+namespace core {
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+namespace server {
+namespace browser {
+
+extern const char * const kBrowserUnsupported;
+
+bool supportedBrowserFilter(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+void handleBrowserUnsupportedRequest(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+} // namespace browser
+} // namespace server
+
+#endif // SERVER_BROWSER_HPP
+
diff --git a/src/cpp/server/ServerEval.cpp b/src/cpp/server/ServerEval.cpp
new file mode 100644
index 0000000..bdffc6d
--- /dev/null
+++ b/src/cpp/server/ServerEval.cpp
@@ -0,0 +1,65 @@
+/*
+ * ServerEval.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerEval.hpp"
+
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/DateTime.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <server/ServerOptions.hpp>
+
+using namespace core;
+
+namespace server {
+namespace eval {
+
+bool expirationFilter(const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ // read the expiration date
+ std::string expires;
+ FilePath expiresPath = server::options().wwwSymbolMapsPath().childPath(
+ "17493e044be34dc589712565d9902700.symbolMapOffset");
+ Error error = readStringFromFile(expiresPath, &expires);
+ boost::algorithm::trim(expires);
+ if (error || expires.empty())
+ return true;
+
+ // convert to seconds
+ double expiresSeconds = safe_convert::stringTo<double>(expires, 0);
+
+ // check if that time is greater than the current time, if it is then
+ // serve back the expired page
+ if (expiresSeconds > date_time::secondsSinceEpoch())
+ {
+ pResponse->setMovedTemporarily(request, "/expired.htm");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+} // namespace eval
+} // namespace server
+
diff --git a/src/cpp/server/ServerEval.hpp b/src/cpp/server/ServerEval.hpp
new file mode 100644
index 0000000..e7d0d74
--- /dev/null
+++ b/src/cpp/server/ServerEval.hpp
@@ -0,0 +1,36 @@
+/*
+ * ServerEval.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_EVAL_HPP
+#define SERVER_EVAL_HPP
+
+namespace core {
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+namespace server {
+namespace eval {
+
+bool expirationFilter(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+} // namespace eval
+} // namespace server
+
+#endif // SERVER_EVAL_HPP
+
diff --git a/src/cpp/server/ServerInit.cpp b/src/cpp/server/ServerInit.cpp
new file mode 100644
index 0000000..de4e8a9
--- /dev/null
+++ b/src/cpp/server/ServerInit.cpp
@@ -0,0 +1,41 @@
+/*
+ * ServerInit.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+
+#include <core/Error.hpp>
+
+#include <core/http/TcpIpAsyncServer.hpp>
+
+#include <server/ServerOptions.hpp>
+
+using namespace core;
+
+namespace server {
+
+http::AsyncServer* httpServerCreate()
+{
+ return new http::TcpIpAsyncServer("RStudio");
+}
+
+Error httpServerInit(http::AsyncServer* pAsyncServer)
+{
+ Options& options = server::options();
+ return dynamic_cast<http::TcpIpAsyncServer*>(pAsyncServer)->init(
+ options.wwwAddress(), options.wwwPort());
+}
+
+} // namespace server
+
diff --git a/src/cpp/server/ServerInit.hpp b/src/cpp/server/ServerInit.hpp
new file mode 100644
index 0000000..095e375
--- /dev/null
+++ b/src/cpp/server/ServerInit.hpp
@@ -0,0 +1,37 @@
+/*
+ * ServerInit.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_INIT_HPP
+#define SERVER_INIT_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ namespace http {
+ class AsyncServer;
+ }
+}
+
+namespace server {
+
+core::http::AsyncServer* httpServerCreate();
+core::Error httpServerInit(core::http::AsyncServer* pAsyncServer);
+
+
+} // namespace server
+
+#endif // SERVER_INIT_HPP
+
diff --git a/src/cpp/server/ServerMain.cpp b/src/cpp/server/ServerMain.cpp
new file mode 100644
index 0000000..1ba7293
--- /dev/null
+++ b/src/cpp/server/ServerMain.cpp
@@ -0,0 +1,519 @@
+/*
+ * ServerMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <pthread.h>
+#include <signal.h>
+
+#include <core/Error.hpp>
+#include <core/LogWriter.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/ProgramOptions.hpp>
+
+#include <core/text/TemplateFilter.hpp>
+
+#include <core/system/PosixSystem.hpp>
+#include <core/system/Crypto.hpp>
+
+#include <core/http/URL.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+#include <core/http/TcpIpAsyncServer.hpp>
+
+#include <core/gwt/GwtLogHandler.hpp>
+#include <core/gwt/GwtFileHandler.hpp>
+
+#include <monitor/MonitorClient.hpp>
+
+#include <session/SessionConstants.hpp>
+
+
+#include <server/auth/ServerAuthHandler.hpp>
+#include <server/auth/ServerValidateUser.hpp>
+#include <server/auth/ServerSecureCookie.hpp>
+#include <server/auth/ServerSecureUriHandler.hpp>
+
+#include <server/ServerOptions.hpp>
+#include <server/ServerUriHandlers.hpp>
+#include <server/ServerScheduler.hpp>
+
+#include "ServerAddins.hpp"
+#include "ServerAppArmor.hpp"
+#include "ServerBrowser.hpp"
+#include "ServerEval.hpp"
+#include "ServerInit.hpp"
+#include "ServerMeta.hpp"
+#include "ServerOffline.hpp"
+#include "ServerPAMAuth.hpp"
+#include "ServerSessionProxy.hpp"
+#include "ServerREnvironment.hpp"
+#include "ServerSessionManager.hpp"
+
+using namespace core ;
+using namespace server;
+
+// forward-declare overlay methods
+namespace server {
+namespace overlay {
+
+Error initialize();
+Error startup();
+void shutdown();
+
+} // namespace overlay
+} // namespace server
+
+namespace {
+
+bool mainPageFilter(const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ return server::eval::expirationFilter(request, pResponse) &&
+ server::browser::supportedBrowserFilter(request, pResponse) &&
+ auth::handler::mainPageFilter(request, pResponse);
+}
+
+
+http::UriHandlerFunction blockingFileHandler()
+{
+ Options& options = server::options();
+
+ // determine initJs (none for now)
+ std::string initJs;
+
+ // return file
+ return gwt::fileHandlerFunction(options.wwwLocalPath(),
+ "/",
+ mainPageFilter,
+ initJs,
+ options.wwwUseEmulatedStack());
+}
+
+//
+// some fancy footwork is required to take the standand blocking file handler
+// and make it work within a secure async context.
+//
+auth::SecureAsyncUriHandlerFunction secureAsyncFileHandler()
+{
+ // create a functor which can adapt a synchronous file handler into
+ // an asynchronous handler
+ class FileRequestHandler {
+ public:
+ static void handleRequest(
+ const http::UriHandlerFunction& fileHandlerFunction,
+ boost::shared_ptr<http::AsyncConnection> pConnection)
+ {
+ fileHandlerFunction(pConnection->request(), &(pConnection->response()));
+ pConnection->writeResponse();
+ }
+ };
+
+ // use this functor to generate an async uri handler function from the
+ // stock blockingFileHandler (defined above)
+ http::AsyncUriHandlerFunction asyncFileHandler =
+ boost::bind(FileRequestHandler::handleRequest, blockingFileHandler(), _1);
+
+
+ // finally, adapt this to be a secure async uri handler by binding out the
+ // first parameter (username, which the gwt file handler knows nothing of)
+ return boost::bind(asyncFileHandler, _2);
+}
+
+// http server
+boost::scoped_ptr<http::AsyncServer> s_pHttpServer;
+
+Error httpServerInit()
+{
+ s_pHttpServer.reset(server::httpServerCreate());
+
+ // set server options
+ s_pHttpServer->setAbortOnResourceError(true);
+
+ // initialize
+ return server::httpServerInit(s_pHttpServer.get());
+}
+
+void httpServerAddHandlers()
+{
+ // establish json-rpc handlers
+ using namespace server::auth;
+ using namespace server::session_proxy;
+ uri_handlers::add("/rpc", secureAsyncJsonRpcHandler(proxyRpcRequest));
+ uri_handlers::add("/events", secureAsyncJsonRpcHandler(proxyEventsRequest));
+
+ // establish content handlers
+ uri_handlers::add("/graphics", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/upload", secureAsyncUploadHandler(proxyContentRequest));
+ uri_handlers::add("/export", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/source", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/content", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/diff", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/file_show", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/view_pdf", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/agreement", secureAsyncHttpHandler(proxyContentRequest));
+ uri_handlers::add("/presentation", secureAsyncHttpHandler(proxyContentRequest));
+
+ // content handlers which might be accessed outside the context of the
+ // workbench get secure + authentication when required
+ uri_handlers::add("/help", secureAsyncHttpHandler(proxyContentRequest, true));
+ uri_handlers::add("/files", secureAsyncHttpHandler(proxyContentRequest, true));
+ uri_handlers::add("/custom", secureAsyncHttpHandler(proxyContentRequest, true));
+ uri_handlers::add("/session", secureAsyncHttpHandler(proxyContentRequest, true));
+ uri_handlers::add("/docs", secureAsyncHttpHandler(secureAsyncFileHandler(), true));
+ uri_handlers::add("/html_preview", secureAsyncHttpHandler(proxyContentRequest, true));
+
+ // proxy localhost if requested
+ if (server::options().wwwProxyLocalhost())
+ uri_handlers::add("/p/", secureAsyncHttpHandler(proxyLocalhostRequest, true));
+
+ // establish logging handler
+ uri_handlers::addBlocking("/log", secureJsonRpcHandler(gwt::handleLogRequest));
+
+ // establish meta
+ uri_handlers::addBlocking("/meta", secureJsonRpcHandler(meta::handleMetaRequest));
+
+ // establish progress handler
+ FilePath wwwPath(server::options().wwwLocalPath());
+ FilePath progressPagePath = wwwPath.complete("progress.htm");
+ uri_handlers::addBlocking("/progress",
+ secureHttpHandler(boost::bind(
+ core::text::handleSecureTemplateRequest,
+ _1, progressPagePath, _2, _3)));
+
+ // establish browser unsupported handler
+ using namespace server::browser;
+ uri_handlers::addBlocking(kBrowserUnsupported,
+ handleBrowserUnsupportedRequest);
+
+ // restrct access to templates directory
+ uri_handlers::addBlocking("/templates", http::notFoundHandler);
+
+ // initialize gwt symbol maps
+ gwt::initializeSymbolMaps(server::options().wwwSymbolMapsPath());
+
+ // add default handler for gwt app
+ uri_handlers::setBlockingDefault(blockingFileHandler());
+}
+
+
+// bogus SIGCHLD handler (never called)
+void handleSIGCHLD(int)
+{
+}
+
+// wait for and handle signals
+Error waitForSignals()
+{
+ // setup bogus handler for SIGCHLD (if we don't do this then
+ // we can't successfully block/wait for the signal). This also
+ // allows us to specify SA_NOCLDSTOP
+ struct sigaction sa;
+ ::memset(&sa, 0, sizeof sa);
+ sa.sa_handler = handleSIGCHLD;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_NOCLDSTOP;
+ int result = ::sigaction(SIGCHLD, &sa, NULL);
+ if (result != 0)
+ return systemError(errno, ERROR_LOCATION);
+
+ // block signals that we want to sigwait on
+ sigset_t wait_mask;
+ sigemptyset(&wait_mask);
+ sigaddset(&wait_mask, SIGCHLD);
+ sigaddset(&wait_mask, SIGINT);
+ sigaddset(&wait_mask, SIGQUIT);
+ sigaddset(&wait_mask, SIGTERM);
+ result = ::pthread_sigmask(SIG_BLOCK, &wait_mask, NULL);
+ if (result != 0)
+ return systemError(result, ERROR_LOCATION);
+
+ // wait for child exits
+ for(;;)
+ {
+ // perform wait
+ int sig = 0;
+ int result = ::sigwait(&wait_mask, &sig);
+ if (result != 0)
+ return systemError(result, ERROR_LOCATION);
+
+ // SIGCHLD
+ if (sig == SIGCHLD)
+ {
+ sessionManager().notifySIGCHLD();
+ }
+
+ // SIGINT, SIGQUIT, SIGTERM
+ else if (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM)
+ {
+ //
+ // Here is where we can perform server cleanup e.g.
+ // closing pam sessions
+ //
+
+ // call overlay shutdown
+ overlay::shutdown();
+
+ // clear the signal mask
+ Error error = core::system::clearSignalMask();
+ if (error)
+ LOG_ERROR(error);
+
+ // reset the signal to its default
+ struct sigaction sa;
+ ::memset(&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_DFL;
+ int result = ::sigaction(sig, &sa, NULL);
+ if (result != 0)
+ LOG_ERROR(systemError(result, ERROR_LOCATION));
+
+ // re-raise the signal
+ ::kill(::getpid(), sig);
+ }
+
+ // Unexpected signal
+ else
+ {
+ LOG_WARNING_MESSAGE("Unexpected signal returned from sigwait: " +
+ safe_convert::numberToString(sig));
+ }
+ }
+
+ // keep compiler happy (we never get here)
+ return Success();
+}
+
+} // anonymous namespace
+
+// provide global access to handlers
+namespace server {
+namespace uri_handlers {
+
+void add(const std::string& prefix,
+ const http::AsyncUriHandlerFunction& handler)
+{
+ s_pHttpServer->addHandler(prefix, handler);
+}
+
+void addBlocking(const std::string& prefix,
+ const http::UriHandlerFunction& handler)
+{
+ s_pHttpServer->addBlockingHandler(prefix, handler);
+}
+
+void setDefault(const http::AsyncUriHandlerFunction& handler)
+{
+ s_pHttpServer->setDefaultHandler(handler);
+}
+
+// set blocking default handler
+void setBlockingDefault(const http::UriHandlerFunction& handler)
+{
+ s_pHttpServer->setBlockingDefaultHandler(handler);
+}
+
+} // namespace uri_handlers
+
+namespace scheduler {
+
+void addCommand(boost::shared_ptr<ScheduledCommand> pCmd)
+{
+ s_pHttpServer->addScheduledCommand(pCmd);
+}
+
+} // namespace scheduler
+} // namespace server
+
+
+int main(int argc, char * const argv[])
+{
+ try
+ {
+ // initialize log
+ const char * const kProgramIdentity = "rserver";
+ initializeSystemLog(kProgramIdentity, core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE (don't log error because we should never call
+ // syslog prior to daemonizing)
+ core::system::ignoreSignal(core::system::SigPipe);
+
+ // read program options
+ std::ostringstream osWarnings;
+ Options& options = server::options();
+ ProgramStatus status = options.read(argc, argv, osWarnings);
+ std::string optionsWarnings = osWarnings.str();
+ if ( status.exit() )
+ {
+ if (!optionsWarnings.empty())
+ program_options::reportWarnings(optionsWarnings, ERROR_LOCATION);
+
+ return status.exitCode() ;
+ }
+
+ // daemonize if requested
+ if (options.serverDaemonize())
+ {
+ Error error = core::system::daemonize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ error = core::system::ignoreTerminalSignals();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // set file creation mask to 022 (might have inherted 0 from init)
+ setUMask(core::system::OthersNoWriteMask);
+ }
+
+ // wait until now to output options warnings (we need to wait for our
+ // first call to logging functions until after daemonization)
+ if (!optionsWarnings.empty())
+ program_options::reportWarnings(optionsWarnings, ERROR_LOCATION);
+
+ // detect R environment variables (calls R (and this forks) so must
+ // happen after daemonize so that upstart script can correctly track us
+ std::string errMsg;
+ bool detected = r_environment::initialize(&errMsg);
+ if (!detected)
+ {
+ program_options::reportError(errMsg, ERROR_LOCATION);
+ return EXIT_FAILURE;
+ }
+
+ // increase the number of open files allowed (need more files
+ // so we can supports lots of concurrent connectins)
+ if (core::system::realUserIsRoot())
+ {
+ Error error = setResourceLimit(core::system::FilesLimit, 4096);
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+ }
+
+ // set working directory
+ Error error = FilePath(options.serverWorkingDir()).makeCurrentPath();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // initialize crypto utils
+ core::system::crypto::initialize();
+
+ // initialize secure cookie module
+ error = auth::secure_cookie::initialize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // initialize the session proxy
+ error = session_proxy::initialize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // initialize http server
+ error = httpServerInit();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // initialize monitor (needs to happen post http server init for access
+ // to the server's io service)
+ monitor::initializeMonitorClient(kMonitorSocketPath,
+ server::options().monitorSharedSecret(),
+ s_pHttpServer->ioService());
+
+ // add a monitor log writer
+ core::system::addLogWriter(
+ monitor::client().createLogWriter(kProgramIdentity));
+
+ // call overlay initialize
+ error = overlay::initialize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // add handlers and initiliaze addins (offline has distinct behavior)
+ if (server::options().serverOffline())
+ {
+ offline::httpServerAddHandlers();
+ }
+ else
+ {
+ // add handlers
+ httpServerAddHandlers();
+
+ // initialize addins
+ error = addins::initialize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // initialize pam auth if we don't already have an auth handler
+ if (!auth::handler::isRegistered())
+ {
+ error = pam_auth::initialize();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+ }
+ }
+
+ // enforce restricted mode if we are running under app armor
+ // note that failure to do this (for whatever unanticipated reason)
+ // is not considered fatal however it is logged as an error
+ // so the sys-admin is informed
+ if (options.serverAppArmorEnabled())
+ {
+ error = app_armor::enforceRestricted();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // give up root privilige if requested
+ std::string runAsUser = options.serverUser();
+ if (!runAsUser.empty())
+ {
+ // drop root priv
+ Error error = core::system::temporarilyDropPriv(runAsUser);
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+ }
+
+ // run special verify installation mode if requested
+ if (options.verifyInstallation())
+ {
+ Error error = session_proxy::runVerifyInstallationSession();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ return EXIT_SUCCESS;
+ }
+
+ // call overlay startup
+ error = overlay::startup();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // run http server
+ error = s_pHttpServer->run(options.wwwThreadPoolSize());
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // wait for signals
+ error = waitForSignals();
+ if (error)
+ return core::system::exitFailure(error, ERROR_LOCATION);
+
+ // NOTE: we never get here because waitForSignals waits forever
+ return EXIT_SUCCESS;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
+
+
diff --git a/src/cpp/server/ServerMainOverlay.cpp b/src/cpp/server/ServerMainOverlay.cpp
new file mode 100644
index 0000000..75a3222
--- /dev/null
+++ b/src/cpp/server/ServerMainOverlay.cpp
@@ -0,0 +1,38 @@
+/*
+ * ServerMainOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+
+using namespace core ;
+
+namespace server {
+namespace overlay {
+
+Error initialize()
+{
+ return Success();
+}
+
+Error startup()
+{
+ return Success();
+}
+
+void shutdown()
+{
+}
+
+} // namespace overlay
+} // namespace server
diff --git a/src/cpp/server/ServerMeta.cpp b/src/cpp/server/ServerMeta.cpp
new file mode 100644
index 0000000..7bece93
--- /dev/null
+++ b/src/cpp/server/ServerMeta.cpp
@@ -0,0 +1,69 @@
+/*
+ * ServerMeta.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerMeta.hpp"
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+using namespace core;
+
+namespace server {
+namespace meta {
+
+namespace {
+
+void handleInitMessagesRequest(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(json::Value());
+}
+
+} // anonymous namespace
+
+void handleMetaRequest(const std::string& username,
+ const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ // parse request
+ json::JsonRpcRequest jsonRpcRequest;
+ Error parseError = parseJsonRpcRequest(request.body(), &jsonRpcRequest) ;
+ if (parseError)
+ {
+ LOG_ERROR(parseError);
+ json::setJsonRpcError(parseError, pResponse);
+ return;
+ }
+
+ // check for supported methods
+ if (jsonRpcRequest.method == "get_init_messages")
+ {
+ json::JsonRpcResponse jsonResponse;
+ handleInitMessagesRequest(jsonRpcRequest, &jsonResponse);
+ json::setJsonRpcResponse(jsonResponse, pResponse);
+ }
+ else
+ {
+ Error methodError = Error(json::errc::MethodNotFound, ERROR_LOCATION);
+ methodError.addProperty("method", jsonRpcRequest.method);
+ LOG_ERROR(methodError);
+ json::setJsonRpcError(methodError, pResponse);
+ }
+}
+
+} // namespace meta
+} // namespace server
diff --git a/src/cpp/server/ServerMeta.hpp b/src/cpp/server/ServerMeta.hpp
new file mode 100644
index 0000000..772d849
--- /dev/null
+++ b/src/cpp/server/ServerMeta.hpp
@@ -0,0 +1,39 @@
+/*
+ * ServerMeta.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_META_HPP
+#define SERVER_META_HPP
+
+#include <string>
+
+namespace core {
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+namespace server {
+namespace meta {
+
+void handleMetaRequest(const std::string& username,
+ const core::http::Request& request,
+ core::http::Response* pResponse);
+
+} // namespace meta
+} // namespace server
+
+#endif // SERVER_META_HPP
+
diff --git a/src/cpp/server/ServerOffline.cpp b/src/cpp/server/ServerOffline.cpp
new file mode 100644
index 0000000..c0165d4
--- /dev/null
+++ b/src/cpp/server/ServerOffline.cpp
@@ -0,0 +1,85 @@
+/*
+ * ServerOffline.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerOffline.hpp"
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <core/gwt/GwtFileHandler.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <server/ServerOptions.hpp>
+#include <server/ServerUriHandlers.hpp>
+
+using namespace core;
+
+namespace server {
+namespace offline {
+
+namespace {
+
+void handleOfflineRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // send error code for json responses
+ if (request.acceptsContentType(json::kJsonContentType))
+ {
+ json::setJsonRpcError(json::errc::ServerOffline, pResponse);
+ }
+
+ // send error page for html responses
+ else if (request.acceptsContentType("text/html"))
+ {
+ pResponse->setStatusCode(http::status::ServiceUnavailable);
+ pResponse->setNoCacheHeaders();
+ FilePath wwwPath(server::options().wwwLocalPath());
+ pResponse->setFile(wwwPath.complete("offline.htm"), request);
+ }
+
+ // other content types just get a plain 503 with no content
+ else
+ {
+ pResponse->setStatusCode(http::status::ServiceUnavailable);
+ }
+}
+
+}
+
+Error httpServerAddHandlers()
+{
+ // alias options
+ Options& options = server::options();
+
+ // use default gwt handling for image urls (required to render
+ // embedded images in offline page)
+ uri_handlers::addBlocking("/images",
+ gwt::fileHandlerFunction(options.wwwLocalPath(),
+ "/"));
+
+ // default handler sends back offline page or json error as appropriate
+ uri_handlers::setBlockingDefault(handleOfflineRequest);
+
+ // success
+ return Success();
+}
+
+} // namespace offline
+} // namespace server
+
diff --git a/src/cpp/server/ServerOffline.hpp b/src/cpp/server/ServerOffline.hpp
new file mode 100644
index 0000000..67c103b
--- /dev/null
+++ b/src/cpp/server/ServerOffline.hpp
@@ -0,0 +1,32 @@
+/*
+ * ServerOffline.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_OFFLINE_HPP
+#define SERVER_OFFLINE_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace offline {
+
+core::Error httpServerAddHandlers();
+
+} // namespace offline
+} // namespace server
+
+#endif // SERVER_OFFLINE_HPP
+
diff --git a/src/cpp/server/ServerOptions.cpp b/src/cpp/server/ServerOptions.cpp
new file mode 100644
index 0000000..1eaf7c0
--- /dev/null
+++ b/src/cpp/server/ServerOptions.cpp
@@ -0,0 +1,328 @@
+/*
+ * ServerOptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <server/ServerOptions.hpp>
+
+#include <core/ProgramStatus.hpp>
+#include <core/ProgramOptions.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/system/PosixUser.hpp>
+#include <core/system/PosixSystem.hpp>
+
+#include <monitor/MonitorConstants.hpp>
+
+#include "ServerAppArmor.hpp"
+
+using namespace core ;
+
+namespace server {
+
+namespace {
+
+const char * const kDefaultProgramUser = "rstudio-server";
+
+struct Deprecated
+{
+ Deprecated()
+ : memoryLimitMb(0),
+ stackLimitMb(0),
+ userProcessLimit(0),
+ authPamRequiresPriv(true)
+ {
+ }
+
+ int memoryLimitMb;
+ int stackLimitMb;
+ int userProcessLimit;
+ bool authPamRequiresPriv;
+};
+
+void reportDeprecationWarning(const std::string& option, std::ostream& os)
+{
+ os << "The option '" << option << "' is deprecated and will be discarded."
+ << std::endl;
+}
+
+void reportDeprecationWarnings(const Deprecated& userOptions,
+ std::ostream& os)
+{
+ Deprecated defaultOptions;
+
+ if (userOptions.memoryLimitMb != defaultOptions.memoryLimitMb)
+ reportDeprecationWarning("rsession-memory-limit-mb", os);
+
+ if (userOptions.stackLimitMb != defaultOptions.stackLimitMb)
+ reportDeprecationWarning("rsession-stack-limit-mb", os);
+
+ if (userOptions.userProcessLimit != defaultOptions.userProcessLimit)
+ reportDeprecationWarning("rsession-process-limit", os);
+
+ if (userOptions.authPamRequiresPriv != defaultOptions.authPamRequiresPriv)
+ reportDeprecationWarning("auth-pam-requires-priv", os);
+}
+
+} // anonymous namespace
+
+Options& options()
+{
+ static Options instance ;
+ return instance ;
+}
+
+
+ProgramStatus Options::read(int argc,
+ char * const argv[],
+ std::ostream& osWarnings)
+{
+ using namespace boost::program_options ;
+
+ // compute install path
+ Error error = core::system::installPath("..", argv[0], &installPath_);
+ if (error)
+ {
+ LOG_ERROR_MESSAGE("Unable to determine install path: "+error.summary());
+ return ProgramStatus::exitFailure();
+ }
+
+ // compute the resource and binary paths
+ FilePath resourcePath = installPath_;
+ FilePath binaryPath = installPath_.childPath("bin");
+
+ // detect running in OSX bundle and tweak paths
+#ifdef __APPLE__
+ if (installPath_.complete("Info.plist").exists())
+ {
+ resourcePath = installPath_.complete("Resources");
+ binaryPath = installPath_.complete("MacOS");
+ }
+#endif
+
+ // verify installation flag
+ options_description verify("verify");
+ verify.add_options()
+ ("verify-installation",
+ value<bool>(&verifyInstallation_)->default_value(false),
+ "verify the current installation");
+
+ // special program offline option (based on file existence at
+ // startup for easy bash script enable/disable of offline state)
+ serverOffline_ = FilePath("/var/lib/rstudio-server/offline").exists();
+
+ // generate monitor shared secret
+ monitorSharedSecret_ = core::system::generateUuid();
+
+ // program - name and execution
+ options_description server("server");
+ server.add_options()
+ ("server-working-dir",
+ value<std::string>(&serverWorkingDir_)->default_value("/"),
+ "program working directory")
+ ("server-user",
+ value<std::string>(&serverUser_)->default_value(kDefaultProgramUser),
+ "program user")
+ ("server-daemonize",
+ value<bool>(&serverDaemonize_)->default_value(
+ core::system::effectiveUserIsRoot()),
+ "run program as daemon")
+ ("server-app-armor-enabled",
+ value<bool>(&serverAppArmorEnabled_)->default_value(1),
+ "is app armor enabled for this session");
+
+ // www - web server options
+ options_description www("www") ;
+ www.add_options()
+ ("www-address",
+ value<std::string>(&wwwAddress_)->default_value("0.0.0.0"),
+ "server address")
+ ("www-port",
+ value<std::string>(&wwwPort_)->default_value(""),
+ "port to listen on")
+ ("www-local-path",
+ value<std::string>(&wwwLocalPath_)->default_value("www"),
+ "www files path")
+ ("www-symbol-maps-path",
+ value<std::string>(&wwwSymbolMapsPath_)->default_value(
+ "www-symbolmaps"),
+ "www symbol maps path")
+ ("www-use-emulated-stack",
+ value<bool>(&wwwUseEmulatedStack_)->default_value(false),
+ "use gwt emulated stack")
+ ("www-thread-pool-size",
+ value<int>(&wwwThreadPoolSize_)->default_value(2),
+ "thread pool size")
+ ("www-proxy-localhost",
+ value<bool>(&wwwProxyLocalhost_)->default_value(true),
+ "proxy requests to localhost ports over main server port");
+
+ // rsession
+ Deprecated dep;
+ options_description rsession("rsession");
+ rsession.add_options()
+ ("rsession-which-r",
+ value<std::string>(&rsessionWhichR_)->default_value(""),
+ "path to main R program (e.g. /usr/bin/R)")
+ ("rsession-path",
+ value<std::string>(&rsessionPath_)->default_value("rsession"),
+ "path to rsession executable")
+ ("rldpath-path",
+ value<std::string>(&rldpathPath_)->default_value("r-ldpath"),
+ "path to r-ldpath script")
+ ("rsession-ld-library-path",
+ value<std::string>(&rsessionLdLibraryPath_)->default_value(""),
+ "default LD_LIBRARY_PATH for rsession")
+ ("rsession-config-file",
+ value<std::string>(&rsessionConfigFile_)->default_value(""),
+ "path to rsession config file")
+ ("rsession-memory-limit-mb",
+ value<int>(&dep.memoryLimitMb)->default_value(dep.memoryLimitMb),
+ "rsession memory limit (mb) - DEPRECATED")
+ ("rsession-stack-limit-mb",
+ value<int>(&dep.stackLimitMb)->default_value(dep.stackLimitMb),
+ "rsession stack limit (mb) - DEPRECATED")
+ ("rsession-process-limit",
+ value<int>(&dep.userProcessLimit)->default_value(dep.userProcessLimit),
+ "rsession user process limit - DEPRECATED");
+
+ // still read depracated options (so we don't break config files)
+ options_description auth("auth");
+ auth.add_options()
+ ("auth-none",
+ value<bool>(&authNone_)->default_value(
+ !core::system::effectiveUserIsRoot()),
+ "don't do any authentication")
+ ("auth-validate-users",
+ value<bool>(&authValidateUsers_)->default_value(
+ core::system::effectiveUserIsRoot()),
+ "validate that authenticated users exist on the target system")
+ ("auth-required-user-group",
+ value<std::string>(&authRequiredUserGroup_)->default_value(""),
+ "limit to users belonging to the specified group")
+ ("auth-pam-helper-path",
+ value<std::string>(&authPamHelperPath_)->default_value("rserver-pam"),
+ "path to PAM helper binary")
+ ("auth-pam-requires-priv",
+ value<bool>(&dep.authPamRequiresPriv)->default_value(
+ dep.authPamRequiresPriv),
+ "deprecated: will always be true");
+
+ options_description monitor("monitor");
+ monitor.add_options()
+ (kMonitorIntervalSeconds,
+ value<int>(&monitorIntervalSeconds_)->default_value(300),
+ "monitoring interval");
+
+ // define program options
+ FilePath defaultConfigPath("/etc/rstudio/rserver.conf");
+ std::string configFile = defaultConfigPath.exists() ?
+ defaultConfigPath.absolutePath() : "";
+ program_options::OptionsDescription optionsDesc("rserver", configFile);
+
+ // overlay hook
+ addOverlayOptions(&server, &www, &rsession, &auth, &monitor);
+
+ optionsDesc.commandLine.add(verify).add(server).add(www).add(rsession).add(auth).add(monitor);
+ optionsDesc.configFile.add(server).add(www).add(rsession).add(auth).add(monitor);
+
+ // read options
+ bool help = false;
+ ProgramStatus status = core::program_options::read(optionsDesc,
+ argc,
+ argv,
+ &help);
+
+ // terminate if this was a help request
+ if (help)
+ return ProgramStatus::exitSuccess();
+
+ // report deprecation warnings
+ reportDeprecationWarnings(dep, osWarnings);
+
+ // call overlay hooks
+ resolveOverlayOptions();
+ std::string errMsg;
+ if (!validateOverlayOptions(&errMsg, osWarnings))
+ {
+ program_options::reportError(errMsg, ERROR_LOCATION);
+ return ProgramStatus::exitFailure();
+ }
+
+ // exit if the call to read indicated we should -- note we don't do this
+ // immediately so that we can allow overlay validation to occur (otherwise
+ // a --test-config wouldn't test overlay options)
+ if (status.exit())
+ return status;
+
+ // rationalize auth settings
+ if (authNone_)
+ authValidateUsers_ = false;
+
+ // if specified, confirm that the program user exists. however, if the
+ // program user is the default and it doesn't exist then allow that to pass,
+ // this just means that the user did a simple make install and hasn't setup
+ // an rserver user yet. in this case the program will run as root
+ if (!serverUser_.empty())
+ {
+ // if we aren't running as root then forget the programUser
+ if (!core::system::realUserIsRoot())
+ {
+ serverUser_ = "";
+ }
+ // if there is a program user specified and it doesn't exist....
+ else if (!core::system::user::exists(serverUser_))
+ {
+ if (serverUser_ == kDefaultProgramUser)
+ {
+ // administrator hasn't created an rserver system account yet
+ // so we'll end up running as root
+ serverUser_ = "";
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("Server user "+ serverUser_ +" does not exist");
+ return ProgramStatus::exitFailure();
+ }
+ }
+ }
+
+ // if app armor is enabled do a further check to see whether
+ // the profile exists. if it doesn't then disable it
+ if (serverAppArmorEnabled_)
+ {
+ if (!FilePath("/etc/apparmor.d/rstudio-server").exists())
+ serverAppArmorEnabled_ = false;
+ }
+
+ // convert relative paths by completing from the system installation
+ // path (this allows us to be relocatable)
+ resolvePath(resourcePath, &wwwLocalPath_);
+ resolvePath(resourcePath, &wwwSymbolMapsPath_);
+ resolvePath(binaryPath, &authPamHelperPath_);
+ resolvePath(binaryPath, &rsessionPath_);
+ resolvePath(binaryPath, &rldpathPath_);
+ resolvePath(resourcePath, &rsessionConfigFile_);
+
+ // return status
+ return status;
+}
+
+void Options::resolvePath(const FilePath& basePath,
+ std::string* pPath) const
+{
+ if (!pPath->empty())
+ *pPath = basePath.complete(*pPath).absolutePath();
+}
+
+} // namespace server
diff --git a/src/cpp/server/ServerOptionsOverlay.cpp b/src/cpp/server/ServerOptionsOverlay.cpp
new file mode 100644
index 0000000..1ac26a2
--- /dev/null
+++ b/src/cpp/server/ServerOptionsOverlay.cpp
@@ -0,0 +1,41 @@
+/*
+ * ServerOptionsOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <server/ServerOptions.hpp>
+
+using namespace core ;
+
+namespace server {
+
+void Options::addOverlayOptions(
+ boost::program_options::options_description* pServer,
+ boost::program_options::options_description* pWWW,
+ boost::program_options::options_description* pRSession,
+ boost::program_options::options_description* pAuth,
+ boost::program_options::options_description* pMonitor)
+{
+}
+
+bool Options::validateOverlayOptions(std::string* pErrMsg,
+ std::ostream& osWarnings)
+{
+ return true;
+}
+
+void Options::resolveOverlayOptions()
+{
+}
+
+} // namespace server
diff --git a/src/cpp/server/ServerPAMAuth.cpp b/src/cpp/server/ServerPAMAuth.cpp
new file mode 100644
index 0000000..b3c96c4
--- /dev/null
+++ b/src/cpp/server/ServerPAMAuth.cpp
@@ -0,0 +1,369 @@
+/*
+ * ServerPAMAuth.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "ServerPAMAuth.hpp"
+
+
+#include <core/Error.hpp>
+#include <core/PeriodicCommand.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Crypto.hpp>
+#include <core/system/PosixSystem.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/URL.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+#include <core/text/TemplateFilter.hpp>
+
+#include <monitor/MonitorClient.hpp>
+
+#include <server/auth/ServerValidateUser.hpp>
+#include <server/auth/ServerSecureUriHandler.hpp>
+#include <server/auth/ServerAuthHandler.hpp>
+#include <server/auth/ServerSecureCookie.hpp>
+
+#include <server/ServerOptions.hpp>
+#include <server/ServerUriHandlers.hpp>
+
+#include "ServerSessionProxy.hpp"
+
+namespace server {
+namespace pam_auth {
+
+namespace {
+
+void assumeRootPriv()
+{
+ // RedHat 5 returns PAM_SYSTEM_ERR from pam_authenticate if we're
+ // running with geteuid != getuid (as is the case when we temporarily
+ // drop privileges). We've also seen kerberos on Ubuntu require
+ // priv to work correctly -- so, restore privilliges in the child
+ if (core::system::realUserIsRoot())
+ {
+ Error error = core::system::restorePriv();
+ if (error)
+ {
+ LOG_ERROR(error);
+ // intentionally fail forward (see note above)
+ }
+ }
+}
+
+bool pamLogin(const std::string& username, const std::string& password)
+{
+ // get path to pam helper
+ FilePath pamHelperPath(server::options().authPamHelperPath());
+ if (!pamHelperPath.exists())
+ {
+ LOG_ERROR_MESSAGE("PAM helper binary does not exist at " +
+ pamHelperPath.absolutePath());
+ return false;
+ }
+
+ // form args
+ std::vector<std::string> args;
+ args.push_back(username);
+
+ // options (assume priv after fork)
+ core::system::ProcessOptions options;
+ options.onAfterFork = assumeRootPriv;
+
+ // run pam helper
+ core::system::ProcessResult result;
+ Error error = core::system::runProgram(pamHelperPath.absolutePath(),
+ args,
+ password,
+ options,
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // check for success
+ return result.exitStatus == 0;
+}
+
+
+
+const char * const kUserId = "user-id";
+
+// It's important that URIs be in the root directory, so the cookie
+// gets set/unset at the correct scope!
+const char * const kDoSignIn = "/auth-do-sign-in";
+const char * const kPublicKey = "/auth-public-key";
+
+const char * const kAppUri = "appUri";
+
+const char * const kErrorParam = "error";
+const char * const kErrorDisplay = "errorDisplay";
+const char * const kErrorMessage = "errorMessage";
+
+
+std::string applicationURL(const http::Request& request,
+ const std::string& path = std::string())
+{
+ return http::URL::uncomplete(
+ request.uri(),
+ path);
+}
+
+std::string applicationSignInURL(const http::Request& request,
+ const std::string& appUri,
+ const std::string& errorMessage=std::string())
+{
+ // build fields
+ http::Fields fields ;
+ if (appUri != "/")
+ fields.push_back(std::make_pair(kAppUri, appUri));
+ if (!errorMessage.empty())
+ fields.push_back(std::make_pair(kErrorParam, errorMessage));
+
+ // build query string
+ std::string queryString ;
+ if (!fields.empty())
+ http::util::buildQueryString(fields, &queryString);
+
+ // generate url
+ std::string signInURL = applicationURL(request, auth::handler::kSignIn);
+ if (!queryString.empty())
+ signInURL += ("?" + queryString);
+ return signInURL;
+}
+
+std::string getUserIdentifier(const core::http::Request& request)
+{
+ if (server::options().authNone())
+ return core::system::username();
+ else
+ return auth::secure_cookie::readSecureCookie(request, kUserId);
+}
+
+std::string userIdentifierToLocalUsername(const std::string& userIdentifier)
+{
+ return userIdentifier;
+}
+
+bool mainPageFilter(const http::Request& request,
+ http::Response* pResponse)
+{
+ // check for user identity, if we have one then allow the request to proceed
+ std::string userIdentifier = getUserIdentifier(request);
+ if (userIdentifier.empty())
+ {
+ // otherwise redirect to sign-in
+ pResponse->setMovedTemporarily(request, applicationSignInURL(request, request.uri()));
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void signInThenContinue(const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ pResponse->setMovedTemporarily(request, applicationSignInURL(request, request.uri()));
+}
+
+void refreshCredentialsThenContinue(
+ boost::shared_ptr<core::http::AsyncConnection> pConnection)
+{
+ // no silent refresh possible so delegate to sign-in and continue
+ signInThenContinue(pConnection->request(),
+ &(pConnection->response()));
+
+ // write response
+ pConnection->writeResponse();
+}
+
+void signIn(const http::Request& request,
+ http::Response* pResponse)
+{
+ auth::secure_cookie::remove(request, kUserId, "", pResponse);
+
+ std::map<std::string,std::string> variables;
+ variables["action"] = applicationURL(request, kDoSignIn);
+ variables["publicKeyUrl"] = applicationURL(request, kPublicKey);
+
+ // setup template variables
+ std::string error = request.queryParamValue(kErrorParam);
+ variables[kErrorMessage] = error;
+ variables[kErrorDisplay] = error.empty() ? "none" : "block";
+
+ variables[kAppUri] = request.queryParamValue(kAppUri);
+
+ // get the path to the JS file
+ Options& options = server::options();
+ FilePath wwwPath(options.wwwLocalPath());
+ FilePath signInPath = wwwPath.complete("templates/encrypted-sign-in.htm");
+
+ text::TemplateFilter filter(variables);
+
+ pResponse->setFile(signInPath, request, filter);
+ pResponse->setContentType("text/html");
+}
+
+void publicKey(const http::Request&,
+ http::Response* pResponse)
+{
+ std::string exp, mod;
+ core::system::crypto::rsaPublicKey(&exp, &mod);
+ pResponse->setNoCacheHeaders();
+ pResponse->setBody(exp + ":" + mod);
+ pResponse->setContentType("text/plain");
+}
+
+void setSignInCookies(const core::http::Request& request,
+ const std::string& username,
+ bool persist,
+ core::http::Response* pResponse)
+{
+ boost::optional<boost::gregorian::days> expiry;
+ if (persist)
+ expiry = boost::gregorian::days(3652);
+ else
+ expiry = boost::none;
+
+ auth::secure_cookie::set(kUserId,
+ username,
+ request,
+ boost::posix_time::time_duration(24*3652,
+ 0,
+ 0,
+ 0),
+ expiry,
+ std::string(),
+ pResponse);
+}
+
+void doSignIn(const http::Request& request,
+ http::Response* pResponse)
+{
+ std::string appUri = request.formFieldValue(kAppUri);
+ if (appUri.empty())
+ appUri = "/";
+
+ std::string encryptedValue = request.formFieldValue("v");
+ bool persist = request.formFieldValue("persist") == "1";
+ std::string plainText;
+ Error error = core::system::crypto::rsaPrivateDecrypt(encryptedValue,
+ &plainText);
+ if (error)
+ {
+ LOG_ERROR(error);
+ pResponse->setMovedTemporarily(
+ request,
+ applicationSignInURL(request,
+ appUri,
+ "Temporary server error,"
+ " please try again"));
+ return;
+ }
+
+ size_t splitAt = plainText.find('\n');
+ if (splitAt == std::string::npos)
+ {
+ LOG_ERROR_MESSAGE("Didn't find newline in plaintext");
+ pResponse->setMovedTemporarily(
+ request,
+ applicationSignInURL(request,
+ appUri,
+ "Temporary server error,"
+ " please try again"));
+ return;
+ }
+
+ std::string username = plainText.substr(0, splitAt);
+ std::string password = plainText.substr(splitAt + 1, plainText.size());
+
+ // tranform to local username
+ username = auth::handler::userIdentifierToLocalUsername(username);
+
+ if ( pamLogin(username, password) && server::auth::validateUser(username))
+ {
+ if (appUri.size() > 0 && appUri[0] != '/')
+ appUri = "/" + appUri;
+
+ setSignInCookies(request, username, persist, pResponse);
+ pResponse->setMovedTemporarily(request, appUri);
+
+ // register login with monitor
+ using namespace monitor;
+ client().logEvent(Event(kAuthScope,
+ kAuthLoginEvent,
+ "",
+ username));
+ }
+ else
+ {
+ pResponse->setMovedTemporarily(
+ request,
+ applicationSignInURL(request,
+ appUri,
+ "Incorrect or invalid username/password"));
+ }
+}
+
+void signOut(const http::Request& request,
+ http::Response* pResponse)
+{
+ // register logout with monitor if we have the username
+ std::string userIdentifier = getUserIdentifier(request);
+ if (!userIdentifier.empty())
+ {
+ std::string username = userIdentifierToLocalUsername(userIdentifier);
+
+ using namespace monitor;
+ client().logEvent(Event(kAuthScope,
+ kAuthLogoutEvent,
+ "",
+ username));
+ }
+
+ auth::secure_cookie::remove(request, kUserId, "", pResponse);
+ pResponse->setMovedTemporarily(request, auth::handler::kSignIn);
+}
+
+} // anonymous namespace
+
+
+Error initialize()
+{
+ // register ourselves as the auth handler
+ server::auth::handler::Handler pamHandler;
+ pamHandler.getUserIdentifier = getUserIdentifier;
+ pamHandler.userIdentifierToLocalUsername = userIdentifierToLocalUsername;
+ pamHandler.mainPageFilter = mainPageFilter;
+ pamHandler.signInThenContinue = signInThenContinue;
+ pamHandler.refreshCredentialsThenContinue = refreshCredentialsThenContinue;
+ pamHandler.signIn = signIn;
+ pamHandler.signOut = signOut;
+ pamHandler.setSignInCookies = setSignInCookies;
+ auth::handler::registerHandler(pamHandler);
+
+ // add pam-specific auth handlers
+ uri_handlers::addBlocking(kDoSignIn, doSignIn);
+ uri_handlers::addBlocking(kPublicKey, publicKey);
+
+ // initialize crypto
+ return core::system::crypto::rsaInit();
+}
+
+
+} // namespace pam_auth
+} // namespace server
diff --git a/src/cpp/server/ServerPAMAuth.hpp b/src/cpp/server/ServerPAMAuth.hpp
new file mode 100644
index 0000000..8f9eaad
--- /dev/null
+++ b/src/cpp/server/ServerPAMAuth.hpp
@@ -0,0 +1,32 @@
+/*
+ * ServerPAMAuth.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_PAM_AUTH_HPP
+#define SERVER_PAM_AUTH_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace pam_auth {
+
+core::Error initialize();
+
+} // namespace pam_auth
+} // namespace server
+
+#endif // SERVER_PAM_AUTH_HPP
+
diff --git a/src/cpp/server/ServerREnvironment.cpp b/src/cpp/server/ServerREnvironment.cpp
new file mode 100644
index 0000000..9bee9ac
--- /dev/null
+++ b/src/cpp/server/ServerREnvironment.cpp
@@ -0,0 +1,79 @@
+/*
+ * ServerREnvironment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerREnvironment.hpp"
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Thread.hpp>
+#include <core/r_util/REnvironment.hpp>
+
+#include <server/ServerOptions.hpp>
+#include <server/ServerUriHandlers.hpp>
+
+using namespace core;
+
+namespace server {
+namespace r_environment {
+
+namespace {
+
+// static R environment vars detected during initialization
+r_util::EnvironmentVars s_rEnvironmentVars;
+
+}
+
+bool initialize(std::string* pErrMsg)
+{
+ // check for which R override
+ FilePath rWhichRPath;
+ std::string whichROverride = server::options().rsessionWhichR();
+ if (!whichROverride.empty())
+ rWhichRPath = FilePath(whichROverride);
+
+ // determine rLdPaths script location
+ FilePath rLdScriptPath(server::options().rldpathPath());
+ std::string ldLibraryPath = server::options().rsessionLdLibraryPath();
+
+ // attempt to detect R environment
+ std::string rScriptPath;
+ return r_util::detectREnvironment(rWhichRPath,
+ rLdScriptPath,
+ ldLibraryPath,
+ &rScriptPath,
+ &s_rEnvironmentVars,
+ pErrMsg);
+}
+
+std::vector<std::pair<std::string,std::string> > variables()
+{
+ // make a copy protected by a mutex just to be on the safest
+ // possible side (the copy is cheap and we're not sure what
+ // universal guarantees about multi-threaded read access to
+ // std::vector are)
+ static boost::mutex s_variablesMutex ;
+ LOCK_MUTEX(s_variablesMutex)
+ {
+ return s_rEnvironmentVars;
+ }
+ END_LOCK_MUTEX
+
+ // mutex related error
+ return r_util::EnvironmentVars();
+}
+
+} // namespace r_environment
+} // namespace server
+
diff --git a/src/cpp/server/ServerREnvironment.hpp b/src/cpp/server/ServerREnvironment.hpp
new file mode 100644
index 0000000..43a8a2e
--- /dev/null
+++ b/src/cpp/server/ServerREnvironment.hpp
@@ -0,0 +1,37 @@
+/*
+ * ServerREnvironment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_R_ENVIRONMENT_HPP
+#define SERVER_R_ENVIRONMENT_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace r_environment {
+
+bool initialize(std::string* pErrMsg);
+
+std::vector<std::pair<std::string,std::string> > variables();
+
+} // namespace r_environment
+} // namespace server
+
+#endif // SERVER_R_ENVIRONMENT_HPP
+
diff --git a/src/cpp/server/ServerSessionManager.cpp b/src/cpp/server/ServerSessionManager.cpp
new file mode 100644
index 0000000..ffa5e0e
--- /dev/null
+++ b/src/cpp/server/ServerSessionManager.cpp
@@ -0,0 +1,252 @@
+/*
+ * ServerSessionManager.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerSessionManager.hpp"
+
+#include <vector>
+
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/PosixSystem.hpp>
+#include <core/system/PosixUser.hpp>
+#include <core/system/Environment.hpp>
+
+#include <monitor/MonitorClient.hpp>
+#include <session/SessionConstants.hpp>
+
+#include <server/ServerOptions.hpp>
+
+
+#include <server/auth/ServerValidateUser.hpp>
+
+#include "ServerREnvironment.hpp"
+
+
+using namespace core;
+
+namespace server {
+
+namespace {
+
+core::system::ProcessConfig sessionProcessConfig(
+ const std::string& username,
+ const core::system::Options& extraArgs = core::system::Options())
+{
+ // prepare command line arguments
+ server::Options& options = server::options();
+ core::system::Options args ;
+
+ // check for options-specified config file and add to command
+ // line if specified
+ std::string rsessionConfigFile(options.rsessionConfigFile());
+ if (!rsessionConfigFile.empty())
+ args.push_back(std::make_pair("--config-file", rsessionConfigFile));
+
+ // pass the user-identity
+ args.push_back(std::make_pair("-" kUserIdentitySessionOptionShort,
+ username));
+
+ // allow session timeout to be overridden via environment variable
+ std::string timeout = core::system::getenv("RSTUDIO_SESSION_TIMEOUT");
+ if (!timeout.empty())
+ args.push_back(std::make_pair("--" kTimeoutSessionOption, timeout));
+
+ // pass our uid to instruct rsession to limit rpc clients to us and itself
+ core::system::Options environment;
+ uid_t uid = core::system::user::currentUserIdentity().userId;
+ environment.push_back(std::make_pair(
+ kRStudioLimitRpcClientUid,
+ safe_convert::numberToString(uid)));
+
+ // pass extra params
+ std::copy(extraArgs.begin(), extraArgs.end(), std::back_inserter(args));
+
+ // append R environment variables
+ core::system::Options rEnvVars = r_environment::variables();
+ environment.insert(environment.end(), rEnvVars.begin(), rEnvVars.end());
+
+ // add monitor shared secret
+ environment.push_back(std::make_pair(kMonitorSharedSecretEnvVar,
+ options.monitorSharedSecret()));
+
+ // build the config object and return it
+ core::system::ProcessConfig config;
+ config.args = args;
+ config.environment = environment;
+ config.stdStreamBehavior = core::system::StdStreamInherit;
+ return config;
+}
+
+void onProcessExit(const std::string& username, PidType pid)
+{
+}
+
+} // anonymous namespace
+
+SessionManager& sessionManager()
+{
+ static SessionManager instance;
+ return instance;
+}
+
+SessionManager::SessionManager()
+{
+ // set default session launcher
+ sessionLaunchFunction_ = boost::bind(&SessionManager::launchAndTrackSession,
+ this, _1);
+}
+
+Error SessionManager::launchSession(const std::string& username)
+{
+ using namespace boost::posix_time;
+
+ // last ditch user validation -- an invalid user should very rarely
+ // get to this point since we pre-emptively validate on client_init
+ if (!server::auth::validateUser(username))
+ {
+ Error error = systemError(boost::system::errc::permission_denied,
+ ERROR_LOCATION);
+ error.addProperty("username", username);
+ return error;
+ }
+
+ LOCK_MUTEX(launchesMutex_)
+ {
+ // check whether we already have a launch pending
+ LaunchMap::const_iterator pos = pendingLaunches_.find(username);
+ if (pos != pendingLaunches_.end())
+ {
+ // if the launch is less than one minute old then return success
+ if ( (pos->second + boost::posix_time::minutes(1))
+ > microsec_clock::universal_time() )
+ {
+ return Success();
+ }
+ // otherwise erase it from pending launches and then
+ // re-launch (immediately below)
+ else
+ {
+ // very unexpected condition
+ LOG_WARNING_MESSAGE("Very long session launch delay for "
+ "user " + username + " (aborting wait)");
+
+ pendingLaunches_.erase(username);
+ }
+ }
+
+ // record the launch
+ pendingLaunches_[username] = microsec_clock::universal_time();
+ }
+ END_LOCK_MUTEX
+
+ // determine launch options
+ r_util::SessionLaunchProfile profile;
+ profile.username = username;
+ profile.executablePath = server::options().rsessionPath();
+ profile.config = sessionProcessConfig(username);
+
+ // pass the profile to the filter if we have one
+ if (sessionLaunchProfileFilter_)
+ sessionLaunchProfileFilter_(&profile);
+
+ // launch the session
+ Error error = sessionLaunchFunction_(profile);
+ if (error)
+ {
+ removePendingLaunch(username);
+ return error;
+ }
+
+ return Success();
+}
+
+// default session launcher -- does the launch then tracks the pid
+// for later reaping
+Error SessionManager::launchAndTrackSession(
+ const core::r_util::SessionLaunchProfile& profile)
+{
+ // if we are root then assume the identity of the user
+ using namespace core::system;
+ std::string runAsUser = realUserIsRoot() ? profile.username : "";
+
+ // launch the session
+ PidType pid = 0;
+ Error error = launchChildProcess(profile.executablePath,
+ runAsUser,
+ profile.config,
+ &pid);
+ if (error)
+ return error;
+
+ // track it for subsequent reaping
+ processTracker_.addProcess(pid, boost::bind(onProcessExit,
+ profile.username,
+ pid));
+
+ // return success
+ return Success();
+}
+
+void SessionManager::setSessionLaunchFunction(
+ const SessionLaunchFunction& launchFunction)
+{
+ sessionLaunchFunction_ = launchFunction;
+}
+
+void SessionManager::setSessionLaunchProfileFilter(
+ const SessionLaunchProfileFilter& filter)
+{
+ sessionLaunchProfileFilter_ = filter;
+}
+
+void SessionManager::removePendingLaunch(const std::string& username)
+{
+ LOCK_MUTEX(launchesMutex_)
+ {
+ pendingLaunches_.erase(username);
+ }
+ END_LOCK_MUTEX
+}
+
+void SessionManager::notifySIGCHLD()
+{
+ processTracker_.notifySIGCHILD();
+}
+
+// helper function for verify-installation
+Error launchSession(const std::string& username,
+ const core::system::Options& extraArgs,
+ PidType* pPid)
+{
+ // launch the session
+ std::string rsessionPath = server::options().rsessionPath();
+ std::string runAsUser = core::system::realUserIsRoot() ? username : "";
+ core::system::ProcessConfig config = sessionProcessConfig(username,
+ extraArgs);
+
+ *pPid = -1;
+ return core::system::launchChildProcess(rsessionPath,
+ runAsUser,
+ config,
+ pPid);
+}
+
+
+} // namespace server
+
diff --git a/src/cpp/server/ServerSessionManager.hpp b/src/cpp/server/ServerSessionManager.hpp
new file mode 100644
index 0000000..dfceb89
--- /dev/null
+++ b/src/cpp/server/ServerSessionManager.hpp
@@ -0,0 +1,103 @@
+/*
+ * ServerSessionManager.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_SESSION_MANAGER_HPP
+#define SERVER_SESSION_MANAGER_HPP
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <boost/signals.hpp>
+
+#include <core/Thread.hpp>
+
+#include <core/system/PosixSystem.hpp>
+#include <core/system/PosixChildProcessTracker.hpp>
+
+#include <core/r_util/RSessionLaunchProfile.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+
+// singleton
+class SessionManager;
+SessionManager& sessionManager();
+
+// Session manager for launching managed sessions. This includes
+// automatically waiting for other pending launches (rather than
+// attempting to launch the same session twice) as well as reaping
+// of session child processes
+class SessionManager
+{
+private:
+ // singleton
+ SessionManager();
+ friend SessionManager& sessionManager();
+
+public:
+ // launching
+ core::Error launchSession(const std::string& username);
+ void removePendingLaunch(const std::string& username);
+
+ // set a custom session launcher
+ typedef boost::function<core::Error(
+ const core::r_util::SessionLaunchProfile&)>
+ SessionLaunchFunction;
+ void setSessionLaunchFunction(const SessionLaunchFunction& launchFunction);
+
+ // set a launch profile filter
+ typedef boost::function<void(
+ core::r_util::SessionLaunchProfile*)>
+ SessionLaunchProfileFilter;
+ void setSessionLaunchProfileFilter(const SessionLaunchProfileFilter& filter);
+
+ // notification that a SIGCHLD was received
+ void notifySIGCHLD();
+
+private:
+ // default session launcher -- runs the process then uses the
+ // ChildProcessTracker to track it's pid for later reaping
+ core::Error launchAndTrackSession(
+ const core::r_util::SessionLaunchProfile& profile);
+
+private:
+ // pending launches
+ boost::mutex launchesMutex_;
+ typedef std::map<std::string,boost::posix_time::ptime> LaunchMap;
+ LaunchMap pendingLaunches_;
+
+ // session launch function and profile filter
+ SessionLaunchFunction sessionLaunchFunction_;
+ SessionLaunchProfileFilter sessionLaunchProfileFilter_;
+
+ // child process tracker
+ core::system::ChildProcessTracker processTracker_;
+};
+
+// Lower-level global functions for launching sessions. These are used
+// internally by the SessionManager as well as for verify-installation
+core::Error launchSession(const std::string& username,
+ const core::system::Options& extraArgs,
+ PidType* pPid);
+
+
+} // namespace server
+
+#endif // SERVER_SESSION_MANAGER_HPP
+
diff --git a/src/cpp/server/ServerSessionProxy.cpp b/src/cpp/server/ServerSessionProxy.cpp
new file mode 100644
index 0000000..9f44854
--- /dev/null
+++ b/src/cpp/server/ServerSessionProxy.cpp
@@ -0,0 +1,501 @@
+/*
+ * ServerSessionProxy.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "ServerSessionProxy.hpp"
+
+#include <vector>
+#include <sstream>
+#include <map>
+
+#include <boost/regex.hpp>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/thread/thread_time.hpp>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/Log.hpp>
+#include <core/Thread.hpp>
+#include <core/WaitUtils.hpp>
+
+#include <core/http/SocketUtils.hpp>
+#include <core/http/SocketProxy.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/LocalStreamAsyncClient.hpp>
+#include <core/http/TcpIpAsyncClient.hpp>
+#include <core/http/Util.hpp>
+#include <core/system/PosixSystem.hpp>
+#include <core/system/PosixUser.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <session/SessionConstants.hpp>
+#include <session/SessionLocalStreams.hpp>
+
+#include <server/auth/ServerValidateUser.hpp>
+#include <server/auth/ServerAuthHandler.hpp>
+
+#include <server/ServerOptions.hpp>
+
+#include "ServerSessionManager.hpp"
+
+using namespace core ;
+
+namespace server {
+namespace session_proxy {
+
+namespace {
+
+void launchSessionRecovery(const std::string& username)
+{
+ // recreate streams dir if necessary
+ Error error = session::local_streams::ensureStreamsDir();
+ if (error)
+ LOG_ERROR(error);
+
+ error = sessionManager().launchSession(username);
+ if (error)
+ LOG_ERROR(error);
+}
+
+http::ConnectionRetryProfile sessionRetryProfile(const std::string& username)
+{
+ http::ConnectionRetryProfile retryProfile;
+ retryProfile.retryInterval = boost::posix_time::milliseconds(25);
+ retryProfile.maxWait = boost::posix_time::seconds(10);
+ retryProfile.recoveryFunction = boost::bind(launchSessionRecovery, username);
+ return retryProfile;
+}
+
+
+void handleProxyResponse(
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ std::string username,
+ const http::Response& response)
+{
+ // if there was a launch pending then remove it
+ sessionManager().removePendingLaunch(username);
+
+ // write the response
+ ptrConnection->writeResponse(response);
+}
+
+class LocalhostAsyncClient : public http::TcpIpAsyncClient
+{
+public:
+ LocalhostAsyncClient(boost::asio::io_service& ioService,
+ const std::string& address,
+ const std::string& port)
+ : http::TcpIpAsyncClient(ioService, address, port)
+ {
+ }
+
+private:
+ // detect when we've got the whole response and force a response and a
+ // close of the socket (this is because the current version of httpuv
+ // expects a close from the client end of the socket)
+ virtual bool stopReadingAndRespond()
+ {
+ return response_.body().length() >= response_.contentLength();
+ }
+
+ // ensure that we don't close the connection when a websockets
+ // upgrade is taking place
+ virtual bool keepConnectionAlive()
+ {
+ return response_.statusCode() == http::status::SwitchingProtocols;
+ }
+};
+
+
+void rewriteLocalhostAddressHeader(const std::string& headerName,
+ const http::Request& originalRequest,
+ const std::string& port,
+ http::Response* pResponse)
+{
+ // get the address and the proxied address
+ std::string address = pResponse->headerValue(headerName);
+ std::string proxiedAddress = "http://localhost:" + port;
+ std::string portPath = "/p/" + port;
+
+ // relative address, just prepend port
+ if (boost::algorithm::starts_with(address, "/"))
+ {
+ address = portPath + address;
+ }
+ // proxied address, substitute base url
+ else if (boost::algorithm::starts_with(address, proxiedAddress))
+ {
+ // find the base url from the original request
+ std::string originalUri = originalRequest.absoluteUri();
+ std::string::size_type pos = originalUri.find(portPath);
+ if (pos != std::string::npos) // precaution, should always be true
+ {
+ // substitute the base url for the proxied address
+ std::string baseUrl = originalUri.substr(0, pos + portPath.length());
+ address = baseUrl + address.substr(proxiedAddress.length());
+ }
+ }
+
+ // replace the header (no-op if both of the above tests fail)
+ pResponse->replaceHeader(headerName, address);
+}
+
+void handleLocalhostResponse(
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ boost::shared_ptr<LocalhostAsyncClient> ptrLocalhost,
+ const std::string& port,
+ const http::Response& response)
+{
+ // check for upgrade to websockets
+ if (response.statusCode() == http::status::SwitchingProtocols)
+ {
+ // write the response but don't close the connection
+ ptrConnection->writeResponse(response, false);
+
+ // cast to generic socket types
+ boost::shared_ptr<http::Socket> ptrClient =
+ boost::static_pointer_cast<http::Socket>(ptrConnection);
+ boost::shared_ptr<http::Socket> ptrServer =
+ boost::static_pointer_cast<http::Socket>(ptrLocalhost);
+
+ // connect the sockets
+ http::SocketProxy::create(ptrClient, ptrServer);
+ }
+ // normal response, write and close (handle redirects if necessary)
+ else
+ {
+ // re-write location headers if necessary
+ const char * const kLocation = "Location";
+ const char * const kRefresh = "Refresh";
+ std::string location = response.headerValue(kLocation);
+ std::string refresh = response.headerValue(kRefresh);
+ if (!location.empty() || !refresh.empty())
+ {
+ // make a copy of the response to rewrite the headers into
+ http::Response redirectResponse;
+ redirectResponse.assign(response);
+
+ // handle Location
+ if (!location.empty())
+ {
+ rewriteLocalhostAddressHeader(kLocation,
+ ptrConnection->request(),
+ port,
+ &redirectResponse);
+ }
+
+ // handle Refresh
+ if (!refresh.empty())
+ {
+ rewriteLocalhostAddressHeader(kRefresh,
+ ptrConnection->request(),
+ port,
+ &redirectResponse);
+ }
+
+ // write the copy
+ ptrConnection->writeResponse(redirectResponse);
+ }
+ else
+ {
+ ptrConnection->writeResponse(response);
+ }
+ }
+}
+
+
+void logIfNotConnectionTerminated(const Error& error,
+ const http::Request& request)
+{
+ if (!http::isConnectionTerminatedError(error))
+ {
+ Error logError(error);
+ logError.addProperty("request-uri", request.uri());
+ LOG_ERROR(logError);
+ }
+}
+
+void handleContentError(
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ std::string username,
+ const Error& error)
+{
+ // if there was a launch pending then remove it
+ sessionManager().removePendingLaunch(username);
+
+ // log if not connection terminated
+ logIfNotConnectionTerminated(error, ptrConnection->request());
+
+ // handle connection unavailable with sign out if session launches
+ // require authentication, otherwise just return service unavailable
+ if (http::isConnectionUnavailableError(error))
+ {
+ // write service unavailable
+ http::Response& response = ptrConnection->response();
+ response.setStatusCode(http::status::ServiceUnavailable);
+
+ ptrConnection->writeResponse();
+ }
+ // otherwise just forward the error
+ else
+ {
+ ptrConnection->writeError(error);
+ }
+}
+
+void handleRpcError(
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ std::string username,
+ const Error& error)
+{
+ // if there was a launch pending then remove it
+ sessionManager().removePendingLaunch(username);
+
+ // log if not connection terminated
+ logIfNotConnectionTerminated(error, ptrConnection->request());
+
+ // distinguish between connection and other error types
+ if (http::isConnectionUnavailableError(error))
+ {
+ json::setJsonRpcError(json::errc::ConnectionError,
+ &(ptrConnection->response()));
+ }
+ else
+ {
+ json::setJsonRpcError(json::errc::TransmissionError,
+ &(ptrConnection->response())) ;
+ }
+
+ // write the response
+ ptrConnection->writeResponse();
+}
+
+void handleEventsError(
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ const Error& error)
+{
+ // NOTE: events requests don't initiate session launches so
+ // we don't call removePendingLaunch here
+
+ // distinguish connection error as (expected) "Unavailable" error state
+ if (http::isConnectionUnavailableError(error))
+ {
+ json::setJsonRpcError(json::errc::Unavailable,
+ &(ptrConnection->response())) ;
+ }
+ else
+ {
+ // log if not connection terminated
+ logIfNotConnectionTerminated(error, ptrConnection->request());
+
+ json::setJsonRpcError(json::errc::TransmissionError,
+ &(ptrConnection->response())) ;
+ }
+
+ // write the response
+ ptrConnection->writeResponse();
+}
+
+void proxyRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection,
+ const http::ErrorHandler& errorHandler,
+ const http::ConnectionRetryProfile& connectionRetryProfile =
+ http::ConnectionRetryProfile())
+{
+ // calculate stream path
+ FilePath streamPath = session::local_streams::streamPath(username);
+
+ // create async client
+ boost::shared_ptr<http::LocalStreamAsyncClient> pClient(
+ new http::LocalStreamAsyncClient(ptrConnection->ioService(), streamPath));
+
+ // setup retry context
+ if (!connectionRetryProfile.empty())
+ pClient->setConnectionRetryProfile(connectionRetryProfile);
+
+ // assign request
+ pClient->request().assign(ptrConnection->request());
+
+ // execute
+ pClient->execute(
+ boost::bind(handleProxyResponse, ptrConnection, username, _1),
+ errorHandler);
+}
+
+// function used to periodically validate that the user is valid (has an
+// account on the system and belongs to the required group if specified)
+// we used to do this on every request but now do it on client_init and
+// get_events. this will introduce less overhead and provide the same
+// level of both usability (redirect the user to the login page) and
+// and assurance that the user is legit (albeit every 50 seconds rather than
+// continuously) note that this check is placed here mostly to provide good
+// feedback to the user that their credentials are no longer valid. the
+// user is also validated when they sign in as well as right before
+// a session is launched (however the usability factor will be much lower
+// if they fail before during session launch since there isn't adequate
+// http connection context at that level of the system to return
+// json::errc::Unauthorized)
+bool validateUser(boost::shared_ptr<http::AsyncConnection> ptrConnection,
+ const std::string& username)
+{
+ if (server::auth::validateUser(username))
+ {
+ return true;
+ }
+ else
+ {
+ json::setJsonRpcError(json::errc::Unauthorized,
+ &(ptrConnection->response()));
+ ptrConnection->writeResponse();
+ return false;
+ }
+}
+
+} // anonymous namespace
+
+
+Error initialize()
+{
+ return session::local_streams::ensureStreamsDir();
+}
+
+Error runVerifyInstallationSession()
+{
+ // get current user
+ core::system::user::User user;
+ Error error = currentUser(&user);
+ if (error)
+ return error;
+
+ // launch verify installation session
+ core::system::Options args;
+ args.push_back(core::system::Option("--" kVerifyInstallationSessionOption, "1"));
+ PidType sessionPid;
+ error = server::launchSession(user.username, args, &sessionPid);
+ if (error)
+ return error;
+
+ // wait for exit
+ return core::system::waitForProcessExit(sessionPid);
+}
+
+void proxyContentRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection)
+{
+ proxyRequest(username,
+ ptrConnection,
+ boost::bind(handleContentError, ptrConnection, username, _1),
+ sessionRetryProfile(username));
+}
+
+void proxyRpcRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection)
+{
+ // validate the user if this is client_init
+ if (boost::algorithm::ends_with(ptrConnection->request().uri(),
+ "client_init"))
+ {
+ if (!validateUser(ptrConnection, username))
+ return;
+ }
+
+ proxyRequest(username,
+ ptrConnection,
+ boost::bind(handleRpcError, ptrConnection, username, _1),
+ sessionRetryProfile(username));
+}
+
+void proxyEventsRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection)
+{
+ // validate the user
+ if (!validateUser(ptrConnection, username))
+ return;
+
+ proxyRequest(username,
+ ptrConnection,
+ boost::bind(handleEventsError, ptrConnection, _1));
+}
+
+void proxyLocalhostRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection)
+{
+ // make a copy of the request for forwarding
+ http::Request request;
+ request.assign(ptrConnection->request());
+
+ // extract the port
+ boost::regex re("/p/(\\d+)/");
+ boost::smatch match;
+ if (!boost::regex_search(request.uri(), match, re))
+ {
+ ptrConnection->response().setError(http::status::NotFound,
+ request.uri() + " not found");
+ return;
+ }
+ std::string port = match[1];
+
+ // strip the port part of the uri
+ using namespace boost::algorithm;
+ std::string portPath = match[0];
+ std::string uri = replace_first_copy(request.uri(), portPath, "/");
+ request.setUri(uri);
+
+ // set the host
+ request.setHost("localhost:" + port);
+
+ // remove headers to be a correctly behaving proxy
+ request.removeHeader("Keep-Alive");
+ request.removeHeader("Proxy-Authenticate");
+ request.removeHeader("Proxy-Authorization");
+ request.removeHeader("Trailers");
+ // spec says we should drop these but we're not sure if that's
+ // true for our use case
+ //request.removeHeader("TE");
+ //request.removeHeader("Transfer-Encoding");
+
+ // specify closing of the connection after the request unless this is
+ // an attempt to upgrade to websockets
+ if (!boost::algorithm::iequals(request.headerValue("Connection"), "Upgrade"))
+ request.setHeader("Connection", "close");
+
+ // create async tcp/ip client and assign request
+ boost::shared_ptr<LocalhostAsyncClient> pClient(
+ new LocalhostAsyncClient(ptrConnection->ioService(), "localhost", port));
+ pClient->request().assign(request);
+
+ // execute request
+ pClient->execute(
+ boost::bind(handleLocalhostResponse, ptrConnection, pClient, port, _1),
+ boost::bind(&core::http::AsyncConnection::writeError,
+ ptrConnection, _1));
+}
+
+} // namespace session_proxy
+} // namespace server
+
+
diff --git a/src/cpp/server/ServerSessionProxy.hpp b/src/cpp/server/ServerSessionProxy.hpp
new file mode 100644
index 0000000..2cb23dc
--- /dev/null
+++ b/src/cpp/server/ServerSessionProxy.hpp
@@ -0,0 +1,54 @@
+/*
+ * ServerSessionProxy.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_SESSION_PROXY_HPP
+#define SERVER_SESSION_PROXY_HPP
+
+#include <string>
+
+#include <core/http/AsyncConnection.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace server {
+namespace session_proxy {
+
+core::Error initialize();
+
+core::Error runVerifyInstallationSession();
+
+void proxyContentRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection) ;
+
+void proxyRpcRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection) ;
+
+void proxyEventsRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection);
+
+void proxyLocalhostRequest(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection> ptrConnection);
+
+} // namespace session_proxy
+} // namespace server
+
+#endif // SERVER_SESSION_PROXY_HPP
+
diff --git a/src/cpp/server/auth/ServerAuthHandler.cpp b/src/cpp/server/auth/ServerAuthHandler.cpp
new file mode 100644
index 0000000..75de1df
--- /dev/null
+++ b/src/cpp/server/auth/ServerAuthHandler.cpp
@@ -0,0 +1,144 @@
+/*
+ * ServerAuthHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_AUTH_HANDLER_CPP
+#define SERVER_AUTH_HANDLER_CPP
+
+#include <server/auth/ServerAuthHandler.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <server/ServerUriHandlers.hpp>
+
+#include <server/auth/ServerSecureUriHandler.hpp>
+
+using namespace core;
+
+namespace server {
+namespace auth {
+namespace handler {
+
+namespace {
+
+// global auth handler
+Handler s_handler;
+
+void updateCredentialsNotSupported(
+ boost::shared_ptr<core::http::AsyncConnection> pConnection)
+{
+ // alias response
+ http::Response* pResponse = &(pConnection->response());
+
+ // gwt form panel requires text/html content type
+ pResponse->setContentType("text/html");
+
+ // set method not found error
+ Error methodNotFoundError(json::errc::MethodNotFound, ERROR_LOCATION);
+ json::setJsonRpcError(methodNotFoundError, pResponse);
+
+ // write response
+ pConnection->writeResponse();
+}
+
+} // anonymous namespace
+
+// uri constants
+const char * const kSignIn = "/auth-sign-in";
+const char * const kSignOut = "/auth-sign-out";
+const char * const kRefreshCredentialsAndContinue = "/auth-refresh-credentials";
+
+
+std::string getUserIdentifier(const core::http::Request& request)
+{
+ return s_handler.getUserIdentifier(request);
+}
+
+std::string userIdentifierToLocalUsername(const std::string& userIdentifier)
+{
+ return s_handler.userIdentifierToLocalUsername(userIdentifier);
+}
+
+bool mainPageFilter(const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ return s_handler.mainPageFilter(request, pResponse);
+}
+
+void signInThenContinue(const core::http::Request& request,
+ core::http::Response* pResponse)
+{
+ s_handler.signInThenContinue(request, pResponse);
+}
+
+void refreshCredentialsThenContinue(
+ boost::shared_ptr<core::http::AsyncConnection> pConnection)
+{
+ s_handler.refreshCredentialsThenContinue(pConnection);
+}
+
+
+// register the auth handler
+void registerHandler(const Handler& handler)
+{
+ // set handler functions
+ s_handler = handler;
+
+ // register uri handlers
+ uri_handlers::addBlocking(kSignIn, s_handler.signIn);
+
+ uri_handlers::addBlocking(kSignOut,
+ auth::secureHttpHandler(
+ boost::bind(s_handler.signOut, _2, _3)));
+
+ uri_handlers::add(kRefreshCredentialsAndContinue,
+ s_handler.refreshCredentialsThenContinue);
+
+ uri_handlers::add("/auth-update-credentials",
+ s_handler.updateCredentials ?
+ s_handler.updateCredentials :
+ updateCredentialsNotSupported);
+}
+
+// is there a handler already registered?
+bool isRegistered()
+{
+ return ! s_handler.getUserIdentifier.empty();
+}
+
+bool canSetSignInCookies()
+{
+ return !s_handler.setSignInCookies.empty();
+}
+
+void setSignInCookies(const core::http::Request& request,
+ const std::string& username,
+ bool persist,
+ core::http::Response* pResponse)
+{
+ s_handler.setSignInCookies(request, username, persist, pResponse);
+}
+
+void signOut(const http::Request& request, http::Response* pResponse)
+{
+ s_handler.signOut(request, pResponse);
+}
+
+} // namespace handler
+} // namespace auth
+} // namespace server
+
+#endif // SERVER_AUTH_HANDLER_CPP
+
+
diff --git a/src/cpp/server/auth/ServerSecureCookie.cpp b/src/cpp/server/auth/ServerSecureCookie.cpp
new file mode 100644
index 0000000..c5cb01c
--- /dev/null
+++ b/src/cpp/server/auth/ServerSecureCookie.cpp
@@ -0,0 +1,300 @@
+/*
+ * ServerSecureCookie.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <server/auth/ServerSecureCookie.hpp>
+
+#include <sys/stat.h>
+
+#include <boost/optional.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+
+#include <core/Log.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/http/URL.hpp>
+#include <core/http/Cookie.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/Util.hpp>
+
+#include <core/system/Crypto.hpp>
+#include <core/system/PosixSystem.hpp>
+#include <core/system/FileMode.hpp>
+
+#include <server/ServerOptions.hpp>
+
+namespace server {
+namespace auth {
+namespace secure_cookie {
+
+namespace {
+
+// cookie field delimiter
+const char * const kDelim = "|";
+
+// secure cookie key
+std::string s_secureCookieKey ;
+
+
+Error base64HMAC(const std::string& value,
+ const std::string& expires,
+ std::string* pHMAC)
+{
+ // compute message to apply hmac to
+ std::string message = value + expires;
+
+ // get cookie key
+ // NOTE: threadsafe because we never modify s_secureCookieKey and
+ // the c_str accessor just returns a pointer to the internal data
+ const char * cookieKey = s_secureCookieKey.c_str();
+
+ // compute hmac for the message
+ std::vector<unsigned char> hmac;
+ Error error = core::system::crypto::HMAC_SHA1(message, cookieKey, &hmac);
+ if (error)
+ return error;
+
+ // base 64 encode it
+ return core::system::crypto::base64Encode(hmac, pHMAC);
+}
+
+http::Cookie createSecureCookie(
+ const std::string& name,
+ const std::string& value,
+ const core::http::Request& request,
+ const boost::posix_time::time_duration& validDuration,
+ const std::string& path)
+{
+ // generate expires string
+ std::string expires = http::util::httpDate(
+ boost::posix_time::second_clock::universal_time() + validDuration);
+
+ // form signed cookie value (will simply be an empty string if an
+ // error occurs during encoding. this will cause the application to
+ // fail downstream which is what we want given that we couldn't properly
+ // secure the cookie)
+ std::string signedCookieValue ;
+ std::string hmac;
+ Error error = base64HMAC(value, expires, &hmac);
+ if (error)
+ {
+ LOG_ERROR(error);
+ signedCookieValue = "";
+ }
+ else
+ {
+ signedCookieValue = http::util::urlEncode(value) +
+ kDelim +
+ http::util::urlEncode(expires) +
+ kDelim +
+ http::util::urlEncode(hmac);
+ }
+
+ // return the cookie
+ return http::Cookie(request,
+ name,
+ signedCookieValue,
+ path,
+ true);
+}
+
+} // anonymous namespace
+
+std::string readSecureCookie(const core::http::Request& request,
+ const std::string& name)
+{
+ // get the signed cookie value
+ std::string signedCookieValue = request.cookieValue(name);
+ if (signedCookieValue.empty())
+ return std::string();
+
+ // split it into its parts (url decode them as well)
+ std::string value, expires, hmac;
+ using namespace boost;
+ char_separator<char> separator(kDelim);
+ tokenizer<char_separator<char> > cookieTokens(signedCookieValue, separator);
+ tokenizer<char_separator<char> >::iterator cookieIter = cookieTokens.begin();
+ if (cookieIter != cookieTokens.end())
+ value = http::util::urlDecode(*cookieIter++);
+ if (cookieIter != cookieTokens.end())
+ expires = http::util::urlDecode(*cookieIter++);
+ if (cookieIter != cookieTokens.end())
+ hmac = http::util::urlDecode(*cookieIter++);
+
+ // validate we got all the parts
+ if ((cookieIter != cookieTokens.end()) ||
+ value.empty() ||
+ expires.empty() ||
+ hmac.empty())
+ {
+ LOG_WARNING_MESSAGE("Invalid secure cookie (wrong number of fields): " +
+ signedCookieValue);
+ return std::string();
+ }
+
+ // compute the hmac of the value + expires
+ std::string computedHmac;
+ Error error = base64HMAC(value, expires, &computedHmac);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+
+ // compare hmac to the one in the cookie
+ if (hmac != computedHmac)
+ {
+ // will occur in normal course of operations if the user upgrades
+ // their browser (and the User-Agent changes). could also occur
+ // in the case of an attempted forgery
+ return std::string();
+ }
+
+ // check the expiration
+ using namespace boost::posix_time;
+ ptime expiresTime = http::util::parseHttpDate(expires);
+ if (expiresTime.is_not_a_date_time())
+ return std::string();
+ else if (expiresTime <= second_clock::universal_time())
+ return std::string();
+
+ // ok to return the value
+ return value;
+}
+
+void set(const std::string& name,
+ const std::string& value,
+ const http::Request& request,
+ const boost::posix_time::time_duration& validDuration,
+ const std::string& path,
+ http::Response* pResponse)
+{
+ secure_cookie::set(name,
+ value,
+ request,
+ validDuration,
+ boost::none,
+ path,
+ pResponse);
+}
+
+void set(const std::string& name,
+ const std::string& value,
+ const http::Request& request,
+ const boost::posix_time::time_duration& validDuration,
+ const boost::optional<boost::gregorian::days>& cookieExpiresDays,
+ const std::string& path,
+ http::Response* pResponse)
+{
+ // create secure cookie
+ http::Cookie cookie = createSecureCookie(name,
+ value,
+ request,
+ validDuration,
+ path);
+
+ // expire from browser as requested
+ if (cookieExpiresDays.is_initialized())
+ cookie.setExpires(*cookieExpiresDays);
+
+ // add to response
+ pResponse->addCookie(cookie);
+}
+
+void remove(const http::Request& request,
+ const std::string& name,
+ const std::string& path,
+ core::http::Response* pResponse)
+{
+ // create vanilla cookie (no need for secure cookie since we are removing)
+ http::Cookie cookie(request, name, std::string(), path);
+
+ // expire delete
+ cookie.setExpiresDelete();
+
+ // add to response
+ pResponse->addCookie(cookie);
+}
+
+Error initialize()
+{
+ // determine path to use for secure cookie key file
+ FilePath secureCookieKeyPath;
+ if (core::system::effectiveUserIsRoot())
+ secureCookieKeyPath = FilePath("/var/lib/rstudio-server/secure-cookie-key");
+ else
+ secureCookieKeyPath = FilePath("/tmp/rstudio-server/secure-cookie-key");
+
+ // read file if it already exists
+ if (secureCookieKeyPath.exists())
+ {
+ // read the key
+ std::string secureCookieKey;
+ Error error = readStringFromFile(secureCookieKeyPath, &secureCookieKey);
+ if (error)
+ return error;
+
+ // check for non-empty key
+ if (secureCookieKey.empty())
+ {
+ return systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ }
+
+ // save the key and return success
+ s_secureCookieKey = secureCookieKey;
+ return Success();
+ }
+
+ // otherwise generate a new key and write it to the file
+ else
+ {
+ // generate a new key
+ std::string secureCookieKey = core::system::generateUuid(false);
+
+ // ensure the parent directory
+ Error error = secureCookieKeyPath.parent().ensureDirectory();
+ if (error)
+ return error;
+
+ // attempt to write it
+ error = writeStringToFile(secureCookieKeyPath, secureCookieKey);
+ if (error)
+ return error;
+
+ // change mode it so it is only readable and writeable by this user
+ if (changeFileMode(secureCookieKeyPath,
+ core::system::UserReadWriteMode) < 0)
+ {
+ return systemError(errno, ERROR_LOCATION);
+ }
+
+ // successfully generated the cookie key, set it
+ s_secureCookieKey = secureCookieKey;
+
+ // reutrn success
+ return Success();
+ }
+
+}
+
+
+} // namespace secure_cookie
+} // namespace auth
+} // namespace server
+
diff --git a/src/cpp/server/auth/ServerSecureUriHandler.cpp b/src/cpp/server/auth/ServerSecureUriHandler.cpp
new file mode 100644
index 0000000..ea72999
--- /dev/null
+++ b/src/cpp/server/auth/ServerSecureUriHandler.cpp
@@ -0,0 +1,195 @@
+/*
+ * ServerSecureUriHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <server/auth/ServerSecureUriHandler.hpp>
+
+#include <boost/function.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/URL.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/gwt/GwtFileHandler.hpp>
+
+#include <server/auth/ServerAuthHandler.hpp>
+#include <server/auth/ServerValidateUser.hpp>
+
+using namespace core;
+
+namespace server {
+namespace auth {
+
+namespace {
+
+class UriHandler
+{
+public:
+ UriHandler(SecureUriHandlerFunction handlerFunction,
+ http::UriHandlerFunction unauthorizedResponseFunction)
+ : handlerFunction_(handlerFunction),
+ unauthorizedResponseFunction_(unauthorizedResponseFunction)
+ {
+ }
+
+ // COPYING: via compiler (copyable members)
+
+ // provide http::UriHandlerFunction concept
+ void operator()(const http::Request& request, http::Response *pResponse)
+ {
+ std::string userIdentifier = handler::getUserIdentifier(request);
+ if (userIdentifier.empty())
+ {
+ unauthorizedResponseFunction_(request, pResponse);
+ return;
+ }
+
+ // convert to local username
+ std::string username = handler::userIdentifierToLocalUsername(
+ userIdentifier);
+
+ // call the handler
+ handlerFunction_(username, request, pResponse);
+ }
+
+private:
+ SecureUriHandlerFunction handlerFunction_;
+ http::UriHandlerFunction unauthorizedResponseFunction_;
+};
+
+class AsyncUriHandler
+{
+public:
+ AsyncUriHandler(
+ SecureAsyncUriHandlerFunction handlerFunction,
+ http::AsyncUriHandlerFunction unauthorizedResponseFunction)
+ : handlerFunction_(handlerFunction),
+ unauthorizedResponseFunction_(unauthorizedResponseFunction)
+ {
+ }
+
+ // COPYING: via compiler (copyable members)
+
+ // provide http::AsyncUriHandlerFunction concept
+ void operator()(boost::shared_ptr<http::AsyncConnection> pConnection)
+ {
+ std::string userIdentifier = handler::getUserIdentifier(
+ pConnection->request());
+ if (userIdentifier.empty())
+ {
+ unauthorizedResponseFunction_(pConnection);
+ return;
+ }
+
+ // convert to local username
+ std::string username = handler::userIdentifierToLocalUsername(
+ userIdentifier);
+
+ // call the handler
+ handlerFunction_(username, pConnection);
+ }
+
+private:
+ SecureAsyncUriHandlerFunction handlerFunction_;
+ http::AsyncUriHandlerFunction unauthorizedResponseFunction_;
+};
+
+
+void setHttpError(const http::Request& request, http::Response* pResponse)
+{
+ pResponse->setError(http::status::Unauthorized, "Unauthorized");
+}
+
+void setJsonRpcError(const http::Request&, http::Response* pResponse)
+{
+ json::setJsonRpcError(json::errc::Unauthorized, pResponse);
+}
+
+void setFileUploadError(const http::Request& request, http::Response* pResponse)
+{
+ // response content type must always be text/html to be handled
+ // properly by the browser/gwt on the client side
+
+ pResponse->setContentType("text/html");
+ setJsonRpcError(request, pResponse);
+}
+
+void asyncSetError(const http::UriHandlerFunction& syncSetErrorFunction,
+ boost::shared_ptr<http::AsyncConnection> pConnection)
+{
+ syncSetErrorFunction(pConnection->request(), &(pConnection->response()));
+ pConnection->writeResponse();
+}
+
+} // anonymous namespace
+
+http::UriHandlerFunction secureHttpHandler(SecureUriHandlerFunction handler,
+ bool authenticate)
+{
+ if (authenticate)
+ return UriHandler(handler, auth::handler::signInThenContinue);
+ else
+ return UriHandler(handler, setHttpError);
+}
+
+http::UriHandlerFunction secureJsonRpcHandler(
+ SecureUriHandlerFunction handler)
+{
+ return UriHandler(handler, setJsonRpcError);
+}
+
+http::UriHandlerFunction secureUploadHandler(SecureUriHandlerFunction handler)
+{
+ return UriHandler(handler, setFileUploadError);
+}
+
+http::AsyncUriHandlerFunction secureAsyncHttpHandler(
+ SecureAsyncUriHandlerFunction handler,
+ bool authenticate)
+{
+ if (authenticate)
+ {
+ // try to recover from auth failure by silently refreshing credentials
+ return AsyncUriHandler(handler,
+ auth::handler::refreshCredentialsThenContinue);
+ }
+ else
+ {
+ return AsyncUriHandler(handler,
+ boost::bind(asyncSetError, setHttpError, _1));
+ }
+}
+
+http::AsyncUriHandlerFunction secureAsyncJsonRpcHandler(
+ SecureAsyncUriHandlerFunction handler)
+{
+ return AsyncUriHandler(handler,
+ boost::bind(asyncSetError, setJsonRpcError, _1));
+}
+
+http::AsyncUriHandlerFunction secureAsyncUploadHandler(
+ SecureAsyncUriHandlerFunction handler)
+{
+ return AsyncUriHandler(handler,
+ boost::bind(asyncSetError, setFileUploadError, _1));
+}
+
+} // namespace auth
+} // namespace server
+
+
+
diff --git a/src/cpp/server/auth/ServerValidateUser.cpp b/src/cpp/server/auth/ServerValidateUser.cpp
new file mode 100644
index 0000000..5c878c1
--- /dev/null
+++ b/src/cpp/server/auth/ServerValidateUser.cpp
@@ -0,0 +1,93 @@
+/*
+ * ServerValidateUser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <server/auth/ServerValidateUser.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/StringUtils.hpp>
+
+#include <core/system/PosixSystem.hpp>
+#include <core/system/PosixUser.hpp>
+
+#include <server/ServerOptions.hpp>
+
+using namespace core;
+
+namespace server {
+namespace auth {
+
+bool validateUser(const std::string& username,
+ const std::string& requiredGroup,
+ bool groupFailureWarning)
+{
+ // short circuit if we aren't validating users
+ if (!server::options().authValidateUsers())
+ return true;
+
+ // get the user
+ core::system::user::User user;
+ Error error = userFromUsername(username, &user);
+ if (error)
+ {
+ // log the error only if it is unexpected
+ if (!core::system::isUserNotFoundError(error))
+ LOG_ERROR(error);
+
+ // not found either due to non-existence or an unexpected error
+ return false;
+ }
+
+ // validate user if necessary
+ if (!requiredGroup.empty())
+ {
+ // see if they are a member of the required group
+ bool belongsToGroup ;
+ error = core::system::userBelongsToGroup(user,
+ requiredGroup,
+ &belongsToGroup);
+ if (error)
+ {
+ // log and return false
+ LOG_ERROR(error);
+ return false;
+ }
+ else
+ {
+ // log a warning whenever a user doesn't belong to a required group
+ if (!belongsToGroup && groupFailureWarning)
+ {
+ LOG_WARNING_MESSAGE(
+ "User " + username + " could not be authenticated because they "
+ "do not belong to the required group (" + requiredGroup + ")");
+ }
+
+ // return belongs status
+ return belongsToGroup;
+ }
+ }
+ else
+ {
+ // not validating (running in some type of dev mode where we
+ // don't have a system account for every login)
+ return true;
+ }
+}
+
+} // namespace auth
+} // namespace server
+
+
+
diff --git a/src/cpp/server/extras/admin/rstudio-server.in b/src/cpp/server/extras/admin/rstudio-server.in
new file mode 100644
index 0000000..14f1807
--- /dev/null
+++ b/src/cpp/server/extras/admin/rstudio-server.in
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+daemonCmd() {
+ if test -e /etc/init/rstudio-server.conf
+ then
+ eval "initctl $1 rstudio-server"
+ else
+ eval "/etc/init.d/rstudio-server $1"
+ fi
+ return $?
+}
+
+testConfig() {
+ `${CMAKE_INSTALL_PREFIX}/bin/rserver --test-config`
+}
+
+verifyInstallation() {
+ `${CMAKE_INSTALL_PREFIX}/bin/rserver --verify-installation=1 --server-daemonize=0`
+}
+
+
+case "$1" in
+ start)
+ testConfig || return $?
+ daemonCmd "start"
+ ;;
+
+ stop)
+ daemonCmd "stop"
+ ;;
+
+ restart)
+ testConfig || return $?
+ daemonCmd "restart"
+ ;;
+
+ test-config)
+ testConfig
+ ;;
+
+ verify-installation)
+ daemonCmd "stop" 2>/dev/null
+ verifyInstallation
+ daemonCmd "start"
+ ;;
+
+ suspend-session)
+ if test -n $2
+ then
+ kill -s USR1 $3 $4 $5 $6 $7 $8 $9 $2
+ else
+ echo "Must specify PID of session to suspend"
+ exit 1
+ fi
+ ;;
+
+ suspend-all)
+ killall -s USR1 $2 $3 $4 $5 $6 $7 $8 $9 rsession
+ ;;
+
+ force-suspend-session)
+ if test -n $2
+ then
+ kill -s USR2 $3 $4 $5 $6 $7 $8 $9 $2
+ else
+ echo "Must specify PID of session to force suspend"
+ exit 1
+ fi
+ ;;
+
+ force-suspend-all)
+ killall -s USR2 $2 $3 $4 $5 $6 $7 $8 $9 rsession
+ ;;
+
+ offline)
+ mkdir -p /var/lib/rstudio-server
+ touch /var/lib/rstudio-server/offline
+ daemonCmd "restart"
+ ;;
+
+ online)
+ mkdir -p /var/lib/rstudio-server
+ rm -f /var/lib/rstudio-server/offline
+ daemonCmd "restart"
+ ;;
+
+ active-sessions)
+ ps opid,cputime,args -C "rsession"
+ ;;
+
+ version)
+ echo "${CPACK_PACKAGE_VERSION}"
+ ;;
+
+ *)
+ echo $"Usage: rstudio-server {start|stop|restart|test-config|verify-installation|suspend-session|suspend-all|force-suspend-session|force-suspend-all|offline|online|active-sessions|version}"
+ exit 2
+esac
+
+
+
diff --git a/src/cpp/server/extras/apparmor/apparmor-profile-load b/src/cpp/server/extras/apparmor/apparmor-profile-load
new file mode 100644
index 0000000..c746c0c
--- /dev/null
+++ b/src/cpp/server/extras/apparmor/apparmor-profile-load
@@ -0,0 +1,44 @@
+#!/bin/sh
+# apparmor-profile-load
+#
+# Helper for loading an AppArmor profile in pre-start scripts.
+#
+# NOTE: This is a snapshot of the /lib/init/apparmor-profile-load script
+# which is included in Ubuntu 11.04. We did this so that we could take
+# advantage of this in 10.04 and 10.10. The issue it is designed to
+# workaround is the fact that AppArmor doesn't use upstart and therefore
+# isn't available to rstudio-server after a restart. The bug is described
+# and discussed here: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/577445
+#
+# If porting this script somehow doesn't work then the alternate (obstensibly
+# less robust) workaround would be to include this directly in our
+# rstudio-server.conf upstart script
+#
+# pre-start script
+# if [ -d /sys/module/apparmor ] && [ -x /sbin/apparmor_parser ]; then
+# /sbin/apparmor_parser -r -W /etc/apparmor.d/rstudio-server || true
+# fi
+# end script
+#
+
+[ -z "$1" ] && exit 1 # require a profile name
+
+[ -d /rofs/etc/apparmor.d ] && exit 0 # do not load if running liveCD
+
+profile=/etc/apparmor.d/"$1"
+[ -e "$profile" ] || exit 0 # skip when missing profile
+
+module=/sys/module/apparmor
+[ -d $module ] || exit 0 # do not load without AppArmor in kernel
+
+[ -x /sbin/apparmor_parser ] || exit 0 # do not load without parser
+
+aafs=/sys/kernel/security/apparmor
+[ -d $aafs ] || exit 0 # do not load if unmounted
+[ -w $aafs/.load ] || exit 1 # fail if cannot load profiles
+
+params=$module/parameters
+[ -r $params/enabled ] || exit 0 # do not load if missing
+read enabled < $params/enabled || exit 1 # if this fails, something went wrong
+[ "$enabled" = "Y" ] || exit 0 # do not load if disabled
+
diff --git a/src/cpp/server/extras/apparmor/rstudio-server.in b/src/cpp/server/extras/apparmor/rstudio-server.in
new file mode 100644
index 0000000..b3b0a40
--- /dev/null
+++ b/src/cpp/server/extras/apparmor/rstudio-server.in
@@ -0,0 +1,55 @@
+#include <tunables/global>
+
+${CMAKE_INSTALL_PREFIX}/bin/rserver {
+
+
+ # #################################################################
+ # startup mode
+ # #################################################################
+
+ # Allow everything during startup
+ #include <abstractions/nameservice>
+ capability setgid,
+ capability setuid,
+ capability sys_resource,
+ capability kill,
+ capability chown,
+ capability fowner,
+ capability fsetid,
+ capability dac_override,
+ capability dac_read_search,
+ /** rwixmkl,
+
+
+ # #################################################################
+ # restricted mode (transitioned into at the end of startup)
+ # #################################################################
+
+ ^restricted {
+
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+ #include <abstractions/ssl_certs>
+
+ capability setgid,
+ capability setuid,
+ capability sys_resource,
+ capability kill,
+
+ owner @{HOME}/** rw,
+ owner /tmp/** rw,
+ /tmp/rstudio-rsession/** rw,
+ /tmp/rstudio-rserver/** rw,
+ /etc/rstudio/** r,
+
+ ${CMAKE_INSTALL_PREFIX}/metrics/rserver-* Ux,
+
+ ${CMAKE_INSTALL_PREFIX}/bin/rserver-* Ux,
+
+ ${CMAKE_INSTALL_PREFIX}/bin/rsession* ux,
+ ${CMAKE_INSTALL_PREFIX}/www/** r,
+ ${CMAKE_INSTALL_PREFIX}/www-symbolmaps/** r,
+ }
+}
+
+
diff --git a/src/cpp/server/extras/init.d/debian/rstudio-server.in b/src/cpp/server/extras/init.d/debian/rstudio-server.in
new file mode 100644
index 0000000..f5f63d3
--- /dev/null
+++ b/src/cpp/server/extras/init.d/debian/rstudio-server.in
@@ -0,0 +1,106 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: rstudio-server
+# Required-Start: $remote_fs
+# Required-Stop: $remote_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: RStudio Server
+# Description: RStudio Server provides a web interface to R
+### END INIT INFO
+
+# Author: RStudio <info at rstudio.com>
+#
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="RStudio Server"
+NAME=rserver
+DAEMON=${CMAKE_INSTALL_PREFIX}/bin/rserver
+SCRIPTNAME=/etc/init.d/rstudio-server
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --exec $DAEMON \
+ || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ return "$RETVAL"
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/src/cpp/server/extras/init.d/redhat/rstudio-server.in b/src/cpp/server/extras/init.d/redhat/rstudio-server.in
new file mode 100755
index 0000000..ccc7703
--- /dev/null
+++ b/src/cpp/server/extras/init.d/redhat/rstudio-server.in
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# chkconfig: 2345 80 20
+# description: RStudio server provides a web interface to R
+# processname: rserver
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+rserver="${CMAKE_INSTALL_PREFIX}/bin/rserver"
+prog=$(basename $rserver)
+lockfile=/var/lock/subsys/rstudio-server
+
+# Exit if the package is not installed
+[ -x "$rserver" ] || exit 0
+
+start() {
+ echo -n $"Starting rstudio-server: "
+ daemon $rserver
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && touch $lockfile
+ return $retval
+}
+
+stop() {
+ echo -n $"Stopping rstudio-server: "
+ killproc $prog
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && rm -f $lockfile
+ return $retval
+}
+
+restart() {
+ stop
+ sleep 1
+ start
+}
+
+rh_status() {
+ status $prog
+}
+
+rh_status_q() {
+ rh_status >/dev/null 2>&1
+}
+
+case "$1" in
+ start)
+ rh_status_q && exit 0
+ $1
+ ;;
+ stop)
+ rh_status_q || exit 0
+ $1
+ ;;
+ restart)
+ $1
+ ;;
+ status)
+ rh_status
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart}"
+ exit 2
+esac
+
diff --git a/src/cpp/server/extras/init.d/suse/rstudio-server.in b/src/cpp/server/extras/init.d/suse/rstudio-server.in
new file mode 100644
index 0000000..b4be13f
--- /dev/null
+++ b/src/cpp/server/extras/init.d/suse/rstudio-server.in
@@ -0,0 +1,75 @@
+#!/bin/sh
+#
+### BEGIN INIT INFO
+# Provides: rstudio-server
+# Required-Start: $local_fs $remote_fs $syslog $network $named
+# Required-Stop: $local_fs $remote_fs $syslog $network $named
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: RStudio server provides a web interface to R
+# Description: RStudio server provides a web interface to R
+### END INIT INFO
+
+# Check for missing binaries
+RSERVER_BIN="${CMAKE_INSTALL_PREFIX}/bin/rserver"
+test -x $RSERVER_BIN || { echo "$RSERVER_BIN not installed";
+ if [ "$1" = "stop" ]; then exit 0;
+ else exit 5; fi; }
+
+
+# Source LSB init functions
+. /etc/rc.status
+
+# Reset status of this service
+rc_reset
+
+case "$1" in
+ start)
+ echo -n "Starting rstudio-server "
+
+ ## Start daemon with startproc(8). If this fails
+ ## the return value is set appropriately by startproc.
+ /sbin/startproc $RSERVER_BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+ stop)
+ echo -n "Shutting down rstudio-server "
+
+ ## Stop daemon with killproc(8) and if this fails
+ ## killproc sets the return value according to LSB.
+ /sbin/killproc $RSERVER_BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+ restart)
+ ## Stop the service and regardless of whether it was
+ ## running or not, start it again.
+ $0 stop
+ $0 start
+
+ # Remember status and be quiet
+ rc_status
+ ;;
+ reload)
+ ## we don't support reload
+ rc_failed 3
+ rc_status -v
+ ;;
+ status)
+ echo -n "Checking for service rstudio-server"
+
+ ## Check status with checkproc(8), if process is running
+ ## checkproc will return with exit status 0.
+ /sbin/checkproc $RSERVER_BIN
+
+ # Remember status and be verbose
+ rc_status -v
+ ;;
+esac
+rc_exit
+
+
+
diff --git a/src/cpp/server/extras/pam/rstudio b/src/cpp/server/extras/pam/rstudio
new file mode 100644
index 0000000..1cc96a8
--- /dev/null
+++ b/src/cpp/server/extras/pam/rstudio
@@ -0,0 +1,5 @@
+#%PAM-1.0
+auth requisite pam_succeed_if.so uid >= 500 quiet
+auth required pam_unix.so nodelay
+
+account required pam_unix.so
diff --git a/src/cpp/server/extras/upstart/rstudio-server.conf.in b/src/cpp/server/extras/upstart/rstudio-server.conf.in
new file mode 100644
index 0000000..976a685
--- /dev/null
+++ b/src/cpp/server/extras/upstart/rstudio-server.conf.in
@@ -0,0 +1,27 @@
+# rserver - RStudio main gateway process
+#
+#
+# upstart docs: http://upstart.ubuntu.com/getting-started.html
+# http://manpages.ubuntu.com/manpages/karmic/man5/init.5.html
+#
+# (note that embedding a script and pre-start and post-start actions are supported)
+#
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+expect fork
+
+respawn
+
+pre-start script
+ # Make sure our AppArmor profile is loaded on boot
+ # (see: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/577445)
+ ${CMAKE_INSTALL_PREFIX}/extras/apparmor/apparmor-profile-load rstudio-server
+end script
+
+# run the server
+exec ${CMAKE_INSTALL_PREFIX}/bin/rserver
+
+
+
diff --git a/src/cpp/server/extras/upstart/rstudio-server.redhat.conf.in b/src/cpp/server/extras/upstart/rstudio-server.redhat.conf.in
new file mode 100644
index 0000000..3b869cb
--- /dev/null
+++ b/src/cpp/server/extras/upstart/rstudio-server.redhat.conf.in
@@ -0,0 +1,21 @@
+# rserver - RStudio main gateway process
+#
+#
+# upstart docs: http://upstart.ubuntu.com/getting-started.html
+# http://manpages.ubuntu.com/manpages/karmic/man5/init.5.html
+#
+# (note that embedding a script and pre-start and post-start actions are supported)
+#
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+expect fork
+
+respawn
+
+# run the server
+exec ${CMAKE_INSTALL_PREFIX}/bin/rserver
+
+
+
diff --git a/src/cpp/server/include/server/ServerOptions.hpp b/src/cpp/server/include/server/ServerOptions.hpp
new file mode 100644
index 0000000..331ed96
--- /dev/null
+++ b/src/cpp/server/include/server/ServerOptions.hpp
@@ -0,0 +1,247 @@
+/*
+ * ServerOptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_SERVER_OPTIONS_HPP
+#define SERVER_SERVER_OPTIONS_HPP
+
+#include <string>
+#include <map>
+#include <iosfwd>
+
+#include <boost/utility.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/ProgramOptions.hpp>
+#include <core/SafeConvert.hpp>
+
+namespace core {
+ class ProgramStatus;
+}
+
+namespace server {
+
+// singleton
+class Options ;
+Options& options();
+
+class Options : boost::noncopyable
+{
+private:
+ Options() {}
+ friend Options& options();
+ // COPYING: boost::noncopyable
+
+public:
+ virtual ~Options() {}
+ core::ProgramStatus read(int argc,
+ char * const argv[],
+ std::ostream& osWarnings);
+
+ bool verifyInstallation() const
+ {
+ return verifyInstallation_;
+ }
+
+ std::string serverWorkingDir() const
+ {
+ return std::string(serverWorkingDir_.c_str());
+ }
+
+ bool serverOffline() const
+ {
+ return serverOffline_;
+ }
+
+ std::string serverUser() const
+ {
+ return std::string(serverUser_.c_str());
+ }
+
+ bool serverDaemonize() const { return serverDaemonize_; }
+
+ bool serverAppArmorEnabled() const { return serverAppArmorEnabled_; }
+
+ // www
+ std::string wwwAddress() const
+ {
+ return std::string(wwwAddress_.c_str()) ;
+ }
+
+ std::string wwwPort(bool secure = false) const
+ {
+ if (!wwwPort_.empty())
+ {
+ return std::string(wwwPort_.c_str());
+ }
+ else
+ {
+ if (secure)
+ return std::string("443");
+ else
+ return std::string("8787");
+ }
+ }
+
+ std::string wwwLocalPath() const
+ {
+ return std::string(wwwLocalPath_.c_str());
+ }
+
+ core::FilePath wwwSymbolMapsPath() const
+ {
+ return core::FilePath(wwwSymbolMapsPath_.c_str());
+ }
+
+ bool wwwUseEmulatedStack() const
+ {
+ return wwwUseEmulatedStack_;
+ }
+
+ int wwwThreadPoolSize() const
+ {
+ return wwwThreadPoolSize_;
+ }
+
+ bool wwwProxyLocalhost() const
+ {
+ return wwwProxyLocalhost_;
+ }
+
+ // auth
+ bool authNone()
+ {
+ return authNone_;
+ }
+
+ bool authValidateUsers()
+ {
+ return authValidateUsers_;
+ }
+
+ std::string authRequiredUserGroup()
+ {
+ return std::string(authRequiredUserGroup_.c_str());
+ }
+
+ std::string authPamHelperPath() const
+ {
+ return std::string(authPamHelperPath_.c_str());
+ }
+
+ // rsession
+ std::string rsessionWhichR() const
+ {
+ return std::string(rsessionWhichR_.c_str());
+ }
+
+ std::string rsessionPath() const
+ {
+ return std::string(rsessionPath_.c_str());
+ }
+
+ std::string rldpathPath() const
+ {
+ return std::string(rldpathPath_.c_str());
+ }
+
+ std::string rsessionLdLibraryPath() const
+ {
+ return std::string(rsessionLdLibraryPath_.c_str());
+ }
+
+ std::string rsessionConfigFile() const
+ {
+ return std::string(rsessionConfigFile_.c_str());
+ }
+
+ std::string monitorSharedSecret() const
+ {
+ return std::string(monitorSharedSecret_.c_str());
+ }
+
+ int monitorIntervalSeconds() const
+ {
+ return monitorIntervalSeconds_;
+ }
+
+ std::string getOverlayOption(const std::string& name)
+ {
+ return overlayOptions_[name];
+ }
+
+private:
+
+ void resolvePath(const core::FilePath& basePath,
+ std::string* pPath) const;
+
+ void addOverlayOptions(boost::program_options::options_description* pServer,
+ boost::program_options::options_description* pWWW,
+ boost::program_options::options_description* pRSession,
+ boost::program_options::options_description* pAuth,
+ boost::program_options::options_description* pMonitor);
+
+ bool validateOverlayOptions(std::string* pErrMsg, std::ostream& osWarnings);
+
+ void resolveOverlayOptions();
+
+ void setOverlayOption(const std::string& name, const std::string& value)
+ {
+ overlayOptions_[name] = value;
+ }
+
+ void setOverlayOption(const std::string& name, bool value)
+ {
+ setOverlayOption(name, value ? std::string("1") : std::string("0"));
+ }
+
+ void setOverlayOption(const std::string& name, int value)
+ {
+ setOverlayOption(name, core::safe_convert::numberToString(value));
+ }
+
+
+private:
+ core::FilePath installPath_;
+ bool verifyInstallation_;
+ std::string serverWorkingDir_;
+ std::string serverUser_;
+ bool serverDaemonize_;
+ bool serverAppArmorEnabled_;
+ bool serverOffline_;
+ std::string wwwAddress_ ;
+ std::string wwwPort_ ;
+ std::string wwwLocalPath_ ;
+ std::string wwwSymbolMapsPath_;
+ bool wwwUseEmulatedStack_;
+ int wwwThreadPoolSize_;
+ bool wwwProxyLocalhost_;
+ bool authNone_;
+ bool authValidateUsers_;
+ std::string authRequiredUserGroup_;
+ std::string authPamHelperPath_;
+ std::string rsessionWhichR_;
+ std::string rsessionPath_;
+ std::string rldpathPath_;
+ std::string rsessionConfigFile_;
+ std::string rsessionLdLibraryPath_;
+ std::string monitorSharedSecret_;
+ int monitorIntervalSeconds_;
+ std::map<std::string,std::string> overlayOptions_;
+};
+
+} // namespace server
+
+#endif // SERVER_SERVER_OPTIONS_HPP
+
diff --git a/src/cpp/server/include/server/ServerScheduler.hpp b/src/cpp/server/include/server/ServerScheduler.hpp
new file mode 100644
index 0000000..d8f26df
--- /dev/null
+++ b/src/cpp/server/include/server/ServerScheduler.hpp
@@ -0,0 +1,36 @@
+/*
+ * ServerScheduler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_SCHEDULER_HPP
+#define SERVER_SCHEDULER_HPP
+
+#include <string>
+
+#include <core/ScheduledCommand.hpp>
+
+namespace server {
+namespace scheduler {
+
+// add a scheduled command to the server
+//
+// note that this function does not synchronize access to the list of
+// scheduled commands so it should ONLY be called during server init
+void addCommand(boost::shared_ptr<core::ScheduledCommand> pCmd);
+
+} // namespace scheduler
+} // namespace server
+
+#endif // SERVER_SCHEDULER_HPP
+
diff --git a/src/cpp/server/include/server/ServerUriHandlers.hpp b/src/cpp/server/include/server/ServerUriHandlers.hpp
new file mode 100644
index 0000000..35ef4b8
--- /dev/null
+++ b/src/cpp/server/include/server/ServerUriHandlers.hpp
@@ -0,0 +1,45 @@
+/*
+ * ServerUriHandlers.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_URI_HANDLERS_HPP
+#define SERVER_URI_HANDLERS_HPP
+
+#include <string>
+
+#include <core/http/UriHandler.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+
+namespace server {
+namespace uri_handlers {
+
+// add async uri handler
+void add(const std::string& prefix,
+ const core::http::AsyncUriHandlerFunction& handler);
+
+// add blocking uri handler
+void addBlocking(const std::string& prefix,
+ const core::http::UriHandlerFunction& handler);
+
+// set async default handler
+void setDefault(const core::http::AsyncUriHandlerFunction& handler);
+
+// set blocking default handler
+void setBlockingDefault(const core::http::UriHandlerFunction& handler);
+
+} // namespace uri_handlers
+} // namespace server
+
+#endif // SERVER_URI_HANDLERS_HPP
+
diff --git a/src/cpp/server/include/server/auth/ServerAuthHandler.hpp b/src/cpp/server/include/server/auth/ServerAuthHandler.hpp
new file mode 100644
index 0000000..0ea0d16
--- /dev/null
+++ b/src/cpp/server/include/server/auth/ServerAuthHandler.hpp
@@ -0,0 +1,99 @@
+/*
+ * ServerAuthHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_AUTH_HANDLER_HPP
+#define SERVER_AUTH_HANDLER_HPP
+
+#include <string>
+
+#include <boost/function.hpp>
+
+#include <core/http/UriHandler.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+
+#include <server/auth/ServerSecureUriHandler.hpp>
+
+namespace server {
+namespace auth {
+namespace handler {
+
+// uri constants
+extern const char * const kSignIn;
+extern const char * const kSignOut;
+extern const char * const kRefreshCredentialsAndContinue;
+
+// functions which can be called on the handler directly
+std::string getUserIdentifier(const core::http::Request& request);
+
+std::string userIdentifierToLocalUsername(const std::string& userIdentifier);
+
+bool mainPageFilter(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+void signInThenContinue(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+// Special uri handler which attempts to refresh the user's
+// credentials then continues on to the originally requested
+// URI (or to a special override URI if specified). if the
+// auth back-end doesn't support this behavior then it should
+// redirect to the sign-in page
+void refreshCredentialsThenContinue(
+ boost::shared_ptr<core::http::AsyncConnection> pConnection);
+
+
+// functions which must be provided by an auth handler
+struct Handler
+{
+ boost::function<std::string(const core::http::Request&)> getUserIdentifier;
+ boost::function<std::string(const std::string&)>
+ userIdentifierToLocalUsername;
+ core::http::UriFilterFunction mainPageFilter;
+ core::http::UriHandlerFunction signInThenContinue;
+ core::http::AsyncUriHandlerFunction refreshCredentialsThenContinue;
+ core::http::AsyncUriHandlerFunction updateCredentials;
+ core::http::UriHandlerFunction signIn;
+ core::http::UriHandlerFunction signOut;
+
+ boost::function<void(const core::http::Request&,
+ const std::string&,
+ bool,
+ core::http::Response*)> setSignInCookies;
+};
+
+// register the auth handler
+void registerHandler(const Handler& handler);
+
+// is there a handler already registered?
+bool isRegistered();
+
+// set sign in cookies
+bool canSetSignInCookies();
+void setSignInCookies(const core::http::Request& request,
+ const std::string& username,
+ bool persist,
+ core::http::Response* pResponse);
+
+// sign out
+void signOut(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+} // namespace handler
+} // namespace auth
+} // namespace server
+
+#endif // SERVER_AUTH_HANDLER_HPP
+
+
diff --git a/src/cpp/server/include/server/auth/ServerSecureCookie.hpp b/src/cpp/server/include/server/auth/ServerSecureCookie.hpp
new file mode 100644
index 0000000..a29e4fc
--- /dev/null
+++ b/src/cpp/server/include/server/auth/ServerSecureCookie.hpp
@@ -0,0 +1,66 @@
+/*
+ * ServerSecureCookie.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_AUTH_SECURE_COOKIE_HPP
+#define SERVER_AUTH_SECURE_COOKIE_HPP
+
+#include <string>
+#include <boost/optional.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace core {
+ class Error;
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+using namespace core;
+
+namespace server {
+namespace auth {
+namespace secure_cookie {
+
+std::string readSecureCookie(const core::http::Request& request,
+ const std::string& name);
+
+void set(const std::string& name,
+ const std::string& value,
+ const http::Request& request,
+ const boost::posix_time::time_duration& validDuration,
+ const std::string& path,
+ http::Response* pResponse);
+
+void set(const std::string& name,
+ const std::string& value,
+ const http::Request& request,
+ const boost::posix_time::time_duration& validDuration,
+ const boost::optional<boost::gregorian::days>& cookieExpiresDays,
+ const std::string& path,
+ http::Response* pResponse);
+
+void remove(const http::Request& request,
+ const std::string& name,
+ const std::string& path,
+ core::http::Response* pResponse);
+
+core::Error initialize();
+
+} // namespace secure_cookie
+} // namespace auth
+} // namespace server
+
+#endif // SERVER_AUTH_SECURE_COOKIE_HPP
diff --git a/src/cpp/server/include/server/auth/ServerSecureUriHandler.hpp b/src/cpp/server/include/server/auth/ServerSecureUriHandler.hpp
new file mode 100644
index 0000000..6bfddbb
--- /dev/null
+++ b/src/cpp/server/include/server/auth/ServerSecureUriHandler.hpp
@@ -0,0 +1,69 @@
+/*
+ * ServerSecureUriHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_AUTH_SECURE_URI_HANDLER_HPP
+#define SERVER_AUTH_SECURE_URI_HANDLER_HPP
+
+#include <boost/function.hpp>
+
+#include <core/http/UriHandler.hpp>
+#include <core/http/AsyncUriHandler.hpp>
+
+namespace core {
+namespace http {
+ class Request;
+ class Response;
+} // namespace http
+} // namespace core
+
+namespace server {
+namespace auth {
+
+typedef boost::function<void(
+ const std::string& username,
+ const core::http::Request&,
+ core::http::Response*)> SecureUriHandlerFunction ;
+
+typedef boost::function<void(
+ const std::string& username,
+ boost::shared_ptr<core::http::AsyncConnection>)>
+ SecureAsyncUriHandlerFunction;
+
+
+core::http::UriHandlerFunction secureHttpHandler(
+ SecureUriHandlerFunction handler,
+ bool authenticate = false);
+
+core::http::UriHandlerFunction secureJsonRpcHandler(
+ SecureUriHandlerFunction handler);
+
+core::http::UriHandlerFunction secureUploadHandler(
+ SecureUriHandlerFunction handler);
+
+core::http::AsyncUriHandlerFunction secureAsyncHttpHandler(
+ SecureAsyncUriHandlerFunction handler,
+ bool authenticate = false);
+
+core::http::AsyncUriHandlerFunction secureAsyncJsonRpcHandler(
+ SecureAsyncUriHandlerFunction handler);
+
+core::http::AsyncUriHandlerFunction secureAsyncUploadHandler(
+ SecureAsyncUriHandlerFunction handler);
+
+} // namespace auth
+} // namespace server
+
+#endif // SERVER_AUTH_SECURE_URI_HANDLER_HPP
+
diff --git a/src/cpp/server/include/server/auth/ServerValidateUser.hpp b/src/cpp/server/include/server/auth/ServerValidateUser.hpp
new file mode 100644
index 0000000..1065b2c
--- /dev/null
+++ b/src/cpp/server/include/server/auth/ServerValidateUser.hpp
@@ -0,0 +1,43 @@
+/*
+ * ServerValidateUser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SERVER_AUTH_VALIDATE_USER_HPP
+#define SERVER_AUTH_VALIDATE_USER_HPP
+
+#include <string>
+
+#include <server/ServerOptions.hpp>
+
+namespace server {
+namespace auth {
+
+bool validateUser(
+ const std::string& username,
+ const std::string& requiredGroup,
+ bool groupFailureWarning);
+
+inline bool validateUser(const std::string& username)
+{
+ return validateUser(username,
+ server::options().authRequiredUserGroup(),
+ true);
+}
+
+
+} // namespace auth
+} // namespace server
+
+#endif // SERVER_AUTH_VALIDATE_USER_HPP
+
diff --git a/src/cpp/server/pam/CMakeLists.txt b/src/cpp/server/pam/CMakeLists.txt
new file mode 100644
index 0000000..a951955
--- /dev/null
+++ b/src/cpp/server/pam/CMakeLists.txt
@@ -0,0 +1,51 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (PAM)
+
+# include files
+file(GLOB_RECURSE PAM_HEADER_FILES "*.h*")
+
+# find PAM
+find_package(PAM REQUIRED)
+
+# source files
+set(PAM_SOURCE_FILES
+ PamMain.cpp
+)
+
+# set include directories
+include_directories(
+ ${Boost_INCLUDE_DIRS}
+ ${CORE_SOURCE_DIR}/include
+ ${PAM_INCLUDE_DIR}
+)
+
+
+# define executable
+add_executable(rserver-pam ${PAM_SOURCE_FILES} ${PAM_HEADER_FILES})
+
+# set link dependencies
+target_link_libraries(rserver-pam
+ rstudio-core
+ ${PAM_LIBRARIES}
+)
+
+# installation rules
+install(TARGETS rserver-pam DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+
+
+
diff --git a/src/cpp/server/pam/PamMain.cpp b/src/cpp/server/pam/PamMain.cpp
new file mode 100644
index 0000000..85d2a7d
--- /dev/null
+++ b/src/cpp/server/pam/PamMain.cpp
@@ -0,0 +1,107 @@
+/*
+ * PamMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <iostream>
+#include <stdio.h>
+
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/system/System.hpp>
+#include <core/system/PosixUser.hpp>
+#include <core/system/Pam.hpp>
+
+using namespace core ;
+
+namespace {
+
+int inappropriateUsage(const ErrorLocation& location)
+{
+ // log warning
+ boost::format fmt("Inappropriate use of pam helper binary (user=%1%)");
+ std::string msg = boost::str(
+ fmt % core::system::user::currentUserIdentity().userId);
+ core::log::logWarningMessage(msg, location);
+
+ // additional notification to the user
+ std::cerr << "\nThis binary is not designed for running this way\n"
+ "-- the system administrator has been informed\n\n";
+
+ // cause further annoyance
+ ::sleep(10);
+
+ return EXIT_FAILURE;
+}
+
+} // anonymous namespace
+
+
+int main(int argc, char * const argv[])
+{
+ try
+ {
+ // initialize log
+ initializeSystemLog("rserver-pam", core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ // ensure that we aren't being called inappropriately
+ if (::isatty(STDIN_FILENO))
+ return inappropriateUsage(ERROR_LOCATION);
+ else if (::isatty(STDOUT_FILENO))
+ return inappropriateUsage(ERROR_LOCATION);
+ else if (argc != 2)
+ return inappropriateUsage(ERROR_LOCATION);
+
+ // read username from command line
+ std::string username(argv[1]);
+
+ // read password (up to 200 chars in length)
+ std::string password;
+ const int MAXPASS = 200;
+ int ch = 0;
+ int count = 0;
+ while((ch = ::fgetc(stdin)) != EOF)
+ {
+ if (++count <= MAXPASS)
+ {
+ password.push_back(static_cast<char>(ch));
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("Password exceeded maximum length for "
+ "user " + username);
+ return EXIT_FAILURE;
+ }
+ }
+
+ // verify password
+ if (core::system::PAM("rstudio", false).login(username,
+ password) == PAM_SUCCESS)
+ return EXIT_SUCCESS;
+ else
+ return EXIT_FAILURE;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
+
diff --git a/src/cpp/server/server-config.h.in b/src/cpp/server/server-config.h.in
new file mode 100644
index 0000000..1f8456a
--- /dev/null
+++ b/src/cpp/server/server-config.h.in
@@ -0,0 +1,21 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#cmakedefine RSTUDIO_DEVELOPMENT
+
+
+
+
+
diff --git a/src/cpp/session/CMakeLists.txt b/src/cpp/session/CMakeLists.txt
new file mode 100644
index 0000000..d938ae2
--- /dev/null
+++ b/src/cpp/session/CMakeLists.txt
@@ -0,0 +1,377 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (SESSION)
+
+add_subdirectory(workers)
+
+# verify that install-dictionaries, install-mathjax, and install-pandoc have been run
+if(NOT EXISTS "${RSTUDIO_DEPENDENCIES_DIR}/common/dictionaries")
+ message(FATAL_ERROR "Dictionaries not found (re-run install-dependencies script to install)")
+endif()
+if(NOT EXISTS "${RSTUDIO_DEPENDENCIES_DIR}/common/mathjax")
+ message(FATAL_ERROR "Mathjax not found (re-run install-dependencies script to install)")
+endif()
+if(NOT EXISTS "${RSTUDIO_DEPENDENCIES_DIR}/common/pandoc")
+ message(FATAL_ERROR "pandoc not found (re-run install-dependencies script to install)")
+endif()
+
+# include files
+file(GLOB_RECURSE SESSION_HEADER_FILES "*.h*")
+
+
+# source files
+set (SESSION_SOURCE_FILES
+ SessionClientEvent.cpp
+ SessionClientEventQueue.cpp
+ SessionClientEventService.cpp
+ SessionContentUrls.cpp
+ SessionSSH.cpp
+ SessionMain.cpp
+ SessionModuleContext.cpp
+ SessionOptions.cpp
+ SessionOptionsOverlay.cpp
+ SessionPersistentState.cpp
+ SessionPostback.cpp
+ SessionSourceDatabase.cpp
+ SessionSourceDatabaseSupervisor.cpp
+ SessionUserSettings.cpp
+ SessionWorkerContext.cpp
+ http/SessionHttpConnectionQueue.cpp
+ http/SessionHttpConnectionUtils.cpp
+ modules/SessionAbout.cpp
+ modules/SessionAgreement.cpp
+ modules/SessionAskPass.cpp
+ modules/SessionAuthoring.cpp
+ modules/SessionBreakpoints.cpp
+ modules/SessionCodeSearch.cpp
+ modules/SessionConsole.cpp
+ modules/SessionConsoleProcess.cpp
+ modules/SessionDirty.cpp
+ modules/SessionErrors.cpp
+ modules/SessionFiles.cpp
+ modules/SessionFilesListingMonitor.cpp
+ modules/SessionFilesQuotas.cpp
+ modules/SessionFind.cpp
+ modules/SessionGit.cpp
+ modules/SessionHelp.cpp
+ modules/SessionHistory.cpp
+ modules/SessionHistoryArchive.cpp
+ modules/SessionHTMLPreview.cpp
+ modules/SessionLimits.cpp
+ modules/SessionLists.cpp
+ modules/SessionPackages.cpp
+ modules/SessionPath.cpp
+ modules/SessionPlots.cpp
+ modules/SessionProfiler.cpp
+ modules/SessionRMarkdown.cpp
+ modules/SessionRPubs.cpp
+ modules/SessionShinyApps.cpp
+ modules/SessionShinyViewer.cpp
+ modules/SessionSource.cpp
+ modules/SessionSpelling.cpp
+ modules/SessionSVN.cpp
+ modules/SessionUpdates.cpp
+ modules/SessionVCS.cpp
+ modules/SessionViewer.cpp
+ modules/SessionWorkbench.cpp
+ modules/build/SessionBuild.cpp
+ modules/build/SessionBuildEnvironment.cpp
+ modules/build/SessionBuildErrors.cpp
+ modules/build/SessionBuildUtils.cpp
+ modules/build/SessionSourceCpp.cpp
+ modules/data/SessionData.cpp
+ modules/data/DataViewer.cpp
+ modules/environment/EnvironmentMonitor.cpp
+ modules/environment/EnvironmentUtils.cpp
+ modules/environment/SessionEnvironment.cpp
+ modules/overlay/SessionOverlay.cpp
+ modules/presentation/SessionPresentation.cpp
+ modules/presentation/PresentationLog.cpp
+ modules/presentation/PresentationState.cpp
+ modules/presentation/PresentationOverlay.cpp
+ modules/presentation/SlideMediaRenderer.cpp
+ modules/presentation/SlideNavigationList.cpp
+ modules/presentation/SlideParser.cpp
+ modules/presentation/SlideQuizRenderer.cpp
+ modules/presentation/SlideRenderer.cpp
+ modules/presentation/SlideRequestHandler.cpp
+ modules/shiny/SessionShiny.cpp
+ modules/tex/SessionCompilePdf.cpp
+ modules/tex/SessionCompilePdfSupervisor.cpp
+ modules/tex/SessionPdfLatex.cpp
+ modules/tex/SessionRnwConcordance.cpp
+ modules/tex/SessionRnwWeave.cpp
+ modules/tex/SessionSynctex.cpp
+ modules/tex/SessionTexUtils.cpp
+ modules/tex/SessionViewPdf.cpp
+ modules/vcs/SessionVCSCore.cpp
+ modules/vcs/SessionVCSUtils.cpp
+ projects/SessionProjects.cpp
+ projects/SessionProjectContext.cpp
+ projects/SessionProjectFirstRun.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/SessionAddins.cpp
+)
+
+# platform specific source files
+if(UNIX)
+ set(SESSION_SOURCE_FILES ${SESSION_SOURCE_FILES}
+ http/SessionPosixHttpConnectionListener.cpp
+ )
+ if(RSTUDIO_SERVER)
+ set(SESSION_SOURCE_FILES ${SESSION_SOURCE_FILES}
+ modules/SessionCrypto.cpp
+ )
+ endif()
+ if(APPLE)
+ set(SESSION_SOURCE_FILES ${SESSION_SOURCE_FILES}
+ SessionModuleContext.mm
+ )
+ endif()
+else()
+ set(SESSION_SOURCE_FILES ${SESSION_SOURCE_FILES}
+ http/SessionWin32HttpConnectionListener.cpp
+ )
+endif()
+
+# R files
+file(GLOB_RECURSE SESSION_R_FILES "modules/*.R")
+
+# define core include dirs
+set(CORE_INCLUDE_DIRS ${CORE_SOURCE_DIR}/include)
+
+# include addins
+if(RSTUDIO_ADDINS_PATH)
+ # search for addins (then remove special core directory)
+ file(GLOB RSTUDIO_ADDINS ${RSTUDIO_ADDINS_PATH}/*)
+ list(REMOVE_ITEM RSTUDIO_ADDINS "core")
+
+ # incorporate all addins found
+ foreach(RSTUDIO_ADDIN ${RSTUDIO_ADDINS})
+ set(SESSION_ADDIN_PATH ${RSTUDIO_ADDIN}/session)
+ if(EXISTS ${SESSION_ADDIN_PATH})
+ # glob the hpp, cpp, and R files
+ file(GLOB_RECURSE ADDIN_HEADER_FILES "${SESSION_ADDIN_PATH}/*.h*")
+ list(APPEND SESSION_HEADER_FILES ${ADDIN_HEADER_FILES})
+ file(GLOB_RECURSE ADDIN_SOURCE_FILES "${SESSION_ADDIN_PATH}/*.c*")
+ list(APPEND SESSION_SOURCE_FILES ${ADDIN_SOURCE_FILES})
+ file(GLOB_RECURSE ADDIN_R_FILES "${SESSION_ADDIN_PATH}/*.R")
+ list(APPEND SESSION_R_FILES ${ADDIN_R_FILES})
+
+ # generate an initialize call
+ get_filename_component(ADDIN_NAME ${RSTUDIO_ADDIN} NAME)
+ set(SESSION_ADDIN_DECLARATIONS
+ "${SESSION_ADDIN_DECLARATIONS}namespace ${ADDIN_NAME} { Error initialize(); }\n" )
+ set(SESSION_ADDIN_INITIALIZATIONS
+ "${SESSION_ADDIN_INITIALIZATIONS}(${ADDIN_NAME}::initialize) ")
+ endif()
+ endforeach()
+
+ # add to core include dirs if appropriate
+ set(CORE_ADDINS_INCLUDE_DIR ${RSTUDIO_ADDINS_PATH}/core/include)
+ if(EXISTS ${CORE_ADDINS_INCLUDE_DIR})
+ list(APPEND CORE_INCLUDE_DIRS ${CORE_ADDINS_INCLUDE_DIR})
+ endif()
+
+endif()
+
+# config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/session-config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/session-config.h)
+
+# always configure the addins bootstrap file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/SessionAddins.cpp.in
+ ${CMAKE_CURRENT_BINARY_DIR}/SessionAddins.cpp)
+
+# configure R files into the binary directory
+foreach(SESSION_R_FILE ${SESSION_R_FILES})
+ get_filename_component(R_FILE_NAME ${SESSION_R_FILE} NAME)
+ configure_file(${SESSION_R_FILE}
+ "${CMAKE_CURRENT_BINARY_DIR}/modules/R/${R_FILE_NAME}"
+ COPYONLY)
+endforeach()
+
+# configure the NOTICE file into the resources directory
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../../../NOTICE
+ ${CMAKE_CURRENT_SOURCE_DIR}/resources COPYONLY)
+
+# set include directories
+include_directories(
+ include
+ ${LIBR_INCLUDE_DIRS}
+ ${CORE_INCLUDE_DIRS}
+ ${MONITOR_SOURCE_DIR}/include
+ ${R_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+# link directories
+link_directories(${R_GRAPHICS_HANDLER_SYSTEM_LIBRARY_DIRS})
+
+if(WIN32)
+ # configure rsession.rc
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rsession.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/rsession.rc)
+
+
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/rsession.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/rsession.exe.manifest COPYONLY)
+
+ add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "rsession.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/rsession.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/rsession.exe.manifest")
+ set(SESSION_SOURCE_FILES
+ ${SESSION_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+ if(NOT RSTUDIO_SESSION_WIN64)
+ add_subdirectory(consoleio)
+ endif()
+endif()
+
+# define executable
+add_executable(rsession ${SESSION_SOURCE_FILES} ${SESSION_HEADER_FILES})
+
+# set link dependencies
+if(WIN32)
+ set(RSTUDIO_CORE_ZLIB rstudio-core-zlib)
+ set(SESSION_SYSTEM_LIBRARIES ${SESSION_SYSTEM_LIBRARIES} -ladvapi32)
+endif()
+if(APPLE)
+ find_library(MAC_APPKIT_LIBRARY NAMES AppKit)
+ set (SESSION_SYSTEM_LIBRARIES
+ ${SESSION_SYSTEM_LIBRARIES}
+ ${MAC_APPKIT_LIBRARY})
+endif()
+target_link_libraries(rsession
+ rstudio-core
+ rstudio-core-hunspell
+ rstudio-core-synctex
+ ${RSTUDIO_CORE_ZLIB}
+ rstudio-monitor
+ rstudio-r
+ rstudio-session-workers
+ ${SESSION_SYSTEM_LIBRARIES}
+ ${LIBR_LIBRARIES}
+)
+
+# configure and install r-ldpaths script
+if(UNIX AND NOT APPLE)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/r-ldpath.in
+ ${CMAKE_CURRENT_BINARY_DIR}/r-ldpath)
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/r-ldpath
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
+
+# install binary
+install(TARGETS rsession DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+# include resources, R scripts and 64bit binaries if this isn't a session 64bit build
+if (NOT RSTUDIO_SESSION_WIN64)
+
+ # postback
+ add_subdirectory(postback)
+
+ # HTML resources
+ file(GLOB HTML_RESOURCE_FILES "resources/*.html")
+ install(FILES ${HTML_RESOURCE_FILES}
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+ # CSS resources
+ file(GLOB CSS_RESOURCE_FILES "resources/*.css")
+ install(FILES ${CSS_RESOURCE_FILES}
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+ # templates
+ install(DIRECTORY "resources/templates"
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+ # JS resources
+ file(GLOB JS_RESOURCE_FILES "resources/*.js")
+ install(FILES ${JS_RESOURCE_FILES}
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+ # presentation
+ install(DIRECTORY "resources/presentation"
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+ # notice
+ install(FILES "resources/NOTICE"
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/resources)
+
+ # R scripts
+ file(GLOB R_MODULE_SRC_FILES "${CMAKE_CURRENT_BINARY_DIR}/modules/R/*.R")
+ install(FILES ${R_MODULE_SRC_FILES}
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/R/modules)
+
+
+ # install hunspell dictionaries
+ install(DIRECTORY "${RSTUDIO_DEPENDENCIES_DIR}/common/dictionaries"
+ DESTINATION "${RSTUDIO_INSTALL_SUPPORTING}/resources")
+
+ # install mathjax for local html preview
+ install(DIRECTORY "${RSTUDIO_DEPENDENCIES_DIR}/common/mathjax"
+ DESTINATION "${RSTUDIO_INSTALL_SUPPORTING}/resources")
+
+ # install pandoc
+ set(PANDOC_BIN "${RSTUDIO_DEPENDENCIES_DIR}/common/pandoc/1.12.3")
+ file(GLOB PANDOC_FILES "${PANDOC_BIN}/pandoc*")
+ install(PROGRAMS ${PANDOC_FILES}
+ DESTINATION ${RSTUDIO_INSTALL_BIN}/pandoc)
+
+ # install 64 bit binaries if we are on win64
+ if(WIN32)
+ if(NOT ("$ENV{PROGRAMW6432}" STREQUAL ""))
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x64")
+ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x64"
+ USE_SOURCE_PERMISSIONS
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+ endif()
+ endif()
+
+ # install gnudiff, mysys_ssh, and sumatra-pdf on windows
+ if(WIN32)
+ install(DIRECTORY "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/gnudiff"
+ USE_SOURCE_PERMISSIONS
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+ install(DIRECTORY "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/gnugrep"
+ USE_SOURCE_PERMISSIONS
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+ install(DIRECTORY "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/msys_ssh"
+ USE_SOURCE_PERMISSIONS
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+
+
+ install(PROGRAMS "${RSTUDIO_WINDOWS_DEPENDENCIES_DIR}/sumatra/2.4/SumatraPDF.exe"
+ DESTINATION "${RSTUDIO_INSTALL_BIN}/sumatra")
+ install(FILES resources/sumatrapdfrestrict.ini
+ DESTINATION "${RSTUDIO_INSTALL_BIN}/sumatra")
+ endif()
+endif()
+
+ # install 64-bit gcc runtime for session win64
+if(RSTUDIO_SESSION_WIN64)
+ get_filename_component(GCC_PATH ${CMAKE_C_COMPILER} PATH CACHE)
+ install(PROGRAMS ${GCC_PATH}/libgcc_s_sjlj-1.dll
+ DESTINATION ${RSTUDIO_INSTALL_BIN})
+endif()
+
+# add overlay if it exists
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+ include(CMakeOverlay.txt)
+endif()
+
+
diff --git a/src/cpp/session/SessionAddins.cpp.in b/src/cpp/session/SessionAddins.cpp.in
new file mode 100644
index 0000000..6a08c3c
--- /dev/null
+++ b/src/cpp/session/SessionAddins.cpp.in
@@ -0,0 +1,47 @@
+/*
+ * SessionAddins.cpp.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+using namespace core;
+
+namespace session {
+namespace addins {
+
+${SESSION_ADDIN_DECLARATIONS}
+
+namespace {
+
+Error dummyInit()
+{
+ return Success();
+}
+
+}
+
+Error initialize()
+{
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ ${SESSION_ADDIN_INITIALIZATIONS}
+ (dummyInit);
+
+ return initBlock.execute();
+}
+
+} // namespace addins
+} // namespace session
+
diff --git a/src/cpp/session/SessionAddins.hpp b/src/cpp/session/SessionAddins.hpp
new file mode 100644
index 0000000..621e407
--- /dev/null
+++ b/src/cpp/session/SessionAddins.hpp
@@ -0,0 +1,32 @@
+/*
+ * SessionAddins.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_ADDINS_HPP
+#define SESSION_ADDINS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace addins {
+
+core::Error initialize();
+
+} // namespace addins
+} // namespace session
+
+#endif // SESSION_ADDINS_HPP
+
diff --git a/src/cpp/session/SessionClientEvent.cpp b/src/cpp/session/SessionClientEvent.cpp
new file mode 100644
index 0000000..cf3aa47
--- /dev/null
+++ b/src/cpp/session/SessionClientEvent.cpp
@@ -0,0 +1,328 @@
+/*
+ * SessionClientEvent.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionClientEvent.hpp>
+
+#include <boost/lexical_cast.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+
+using namespace core ;
+
+namespace session {
+
+namespace client_events {
+
+const int kBusy = 1;
+const int kConsolePrompt = 2;
+const int kConsoleWriteOutput = 3;
+const int kConsoleWriteError = 4;
+const int kShowErrorMessage = 5;
+const int kShowHelp = 6;
+const int kBrowseUrl = 7;
+const int kShowEditor = 11;
+const int kChooseFile = 13;
+const int kAbendWarning = 14;
+const int kQuit = 15;
+const int kSuicide = 16;
+const int kFileChanged = 17;
+const int kWorkingDirChanged = 18;
+const int kPlotsStateChanged = 19;
+const int kViewData = 20;
+const int kPackageStatusChanged = 21;
+const int kInstalledPackagesChanged = 22;
+const int kLocator = 23;
+const int kConsoleResetHistory = 25;
+const int kSessionSerialization = 26;
+const int kHistoryEntriesAdded = 27;
+const int kQuotaStatus = 29;
+const int kFileEdit = 32;
+const int kShowContent = 33;
+const int kShowData = 34;
+const int kAsyncCompletion = 35;
+const int kSaveActionChanged = 36;
+const int kConsoleWritePrompt = 37;
+const int kConsoleWriteInput = 38;
+const int kShowWarningBar = 39;
+const int kOpenProjectError = 40;
+const int kVcsRefresh = 41;
+const int kAskPass = 42;
+const int kConsoleProcessOutput = 43;
+const int kConsoleProcessExit = 44;
+const int kListChanged = 45;
+const int kConsoleProcessCreated = 46;
+const int kUiPrefsChanged = 47;
+const int kHandleUnsavedChanges = 48;
+const int kConsoleProcessPrompt = 49;
+const int kShowConsoleProcessDialog = 50;
+const int kHTMLPreviewStartedEvent = 51;
+const int kHTMLPreviewOutputEvent = 52;
+const int kHTMLPreviewCompletedEvent = 53;
+const int kCompilePdfStartedEvent = 54;
+const int kCompilePdfOutputEvent = 55;
+const int kCompilePdfErrorsEvent = 56;
+const int kCompilePdfCompletedEvent = 57;
+const int kSynctexEditFile = 58;
+const int kFindResult = 59;
+const int kFindOperationEnded = 60;
+const int kRPubsUploadStatus = 61;
+const int kBuildStarted = 62;
+const int kBuildOutput = 63;
+const int kBuildCompleted = 64;
+const int kBuildErrors = 65;
+const int kDirectoryNavigate = 66;
+const int kDeferredInitCompleted = 67;
+const int kPlotsZoomSizeChanged = 68;
+const int kSourceCppStarted = 69;
+const int kSourceCppCompleted = 70;
+const int kLoadedPackageUpdates = 71;
+const int kActivatePane = 72;
+const int kShowPresentationPane = 73;
+const int kEnvironmentRefresh = 74;
+const int kContextDepthChanged = 75;
+const int kEnvironmentAssigned = 76;
+const int kEnvironmentRemoved = 77;
+const int kBrowserLineChanged = 78;
+const int kPackageLoaded = 79;
+const int kPackageUnloaded = 80;
+const int kPresentationPaneRequestCompleted = 81;
+const int kUnhandledError = 82;
+const int kErrorHandlerChanged = 83;
+const int kViewerNavigate = 84;
+const int kSourceExtendedTypeDetected = 85;
+const int kShinyViewer = 86;
+const int kDebugSourceCompleted = 87;
+}
+
+void ClientEvent::init(int type, const json::Value& data)
+{
+ type_ = type;
+ data_ = data;
+ id_ = core::system::generateUuid();
+}
+
+void ClientEvent::asJsonObject(int id, json::Object* pObject) const
+{
+ json::Object& object = *pObject;
+ object["id"] = id;
+ object["type"] = typeName();
+ object["data"] = data();
+}
+
+std::string ClientEvent::typeName() const
+{
+ switch(type_)
+ {
+ case client_events::kBusy:
+ return "busy";
+ case client_events::kConsolePrompt:
+ return "console_prompt";
+ case client_events::kConsoleWriteOutput:
+ return "console_output";
+ case client_events::kConsoleWriteError:
+ return "console_error";
+ case client_events::kShowErrorMessage:
+ return "show_error_message";
+ case client_events::kShowHelp:
+ return "show_help";
+ case client_events::kBrowseUrl:
+ return "browse_url";
+ case client_events::kShowEditor:
+ return "show_editor";
+ case client_events::kChooseFile:
+ return "choose_file";
+ case client_events::kAbendWarning:
+ return "abend_warning";
+ case client_events::kQuit:
+ return "quit";
+ case client_events::kSuicide:
+ return "suicide";
+ case client_events::kFileChanged:
+ return "file_changed";
+ case client_events::kWorkingDirChanged:
+ return "working_dir_changed";
+ case client_events::kPlotsStateChanged:
+ return "plots_state_changed";
+ case client_events::kViewData:
+ return "view_data";
+ case client_events::kPackageStatusChanged:
+ return "package_status_changed";
+ case client_events::kInstalledPackagesChanged:
+ return "installed_packages_changed";
+ case client_events::kLocator:
+ return "locator";
+ case client_events::kConsoleResetHistory:
+ return "console_reset_history";
+ case client_events::kSessionSerialization:
+ return "session_serialization";
+ case client_events::kHistoryEntriesAdded:
+ return "history_entries_added";
+ case client_events::kQuotaStatus:
+ return "quota_status";
+ case client_events::kFileEdit:
+ return "file_edit";
+ case client_events::kShowContent:
+ return "show_content";
+ case client_events::kShowData:
+ return "show_data";
+ case client_events::kAsyncCompletion:
+ return "async_completion";
+ case client_events::kSaveActionChanged:
+ return "save_action_changed";
+ case client_events::kConsoleWritePrompt:
+ return "console_write_prompt";
+ case client_events::kConsoleWriteInput:
+ return "console_write_input";
+ case client_events::kShowWarningBar:
+ return "show_warning_bar";
+ case client_events::kOpenProjectError:
+ return "open_project_error";
+ case client_events::kVcsRefresh:
+ return "vcs_refresh";
+ case client_events::kAskPass:
+ return "ask_pass";
+ case client_events::kConsoleProcessOutput:
+ return "console_process_output";
+ case client_events::kConsoleProcessExit:
+ return "console_process_exit";
+ case client_events::kListChanged:
+ return "list_changed";
+ case client_events::kUiPrefsChanged:
+ return "ui_prefs_changed";
+ case client_events::kHandleUnsavedChanges:
+ return "handle_unsaved_changes";
+ case client_events::kConsoleProcessPrompt:
+ return "console_process_prompt";
+ case client_events::kConsoleProcessCreated:
+ return "console_process_created";
+ case client_events::kHTMLPreviewStartedEvent:
+ return "html_preview_started_event";
+ case client_events::kHTMLPreviewOutputEvent:
+ return "html_preview_output_event";
+ case client_events::kHTMLPreviewCompletedEvent:
+ return "html_preview_completed_event";
+ case client_events::kCompilePdfStartedEvent:
+ return "compile_pdf_started_event";
+ case client_events::kCompilePdfOutputEvent:
+ return "compile_pdf_output_event";
+ case client_events::kCompilePdfErrorsEvent:
+ return "compile_pdf_errors_event";
+ case client_events::kCompilePdfCompletedEvent:
+ return "compile_pdf_completed_event";
+ case client_events::kSynctexEditFile:
+ return "synctex_edit_file";
+ case client_events::kFindResult:
+ return "find_result";
+ case client_events::kFindOperationEnded:
+ return "find_operation_ended";
+ case client_events::kRPubsUploadStatus:
+ return "rpubs_upload_status";
+ case client_events::kBuildStarted:
+ return "build_started";
+ case client_events::kBuildOutput:
+ return "build_output";
+ case client_events::kBuildCompleted:
+ return "build_completed";
+ case client_events::kBuildErrors:
+ return "build_errors";
+ case client_events::kDirectoryNavigate:
+ return "directory_navigate";
+ case client_events::kDeferredInitCompleted:
+ return "deferred_init_completed";
+ case client_events::kPlotsZoomSizeChanged:
+ return "plots_zoom_size_changed";
+ case client_events::kSourceCppStarted:
+ return "source_cpp_started";
+ case client_events::kSourceCppCompleted:
+ return "source_cpp_completed";
+ case client_events::kLoadedPackageUpdates:
+ return "loaded_package_updates";
+ case client_events::kActivatePane:
+ return "activate_pane";
+ case client_events::kShowPresentationPane:
+ return "show_presentation_pane";
+ case client_events::kEnvironmentRefresh:
+ return "environment_refresh";
+ case client_events::kContextDepthChanged:
+ return "context_depth_changed";
+ case client_events::kEnvironmentAssigned:
+ return "environment_assigned";
+ case client_events::kEnvironmentRemoved:
+ return "environment_removed";
+ case client_events::kBrowserLineChanged:
+ return "browser_line_changed";
+ case client_events::kPackageLoaded:
+ return "package_loaded";
+ case client_events::kPackageUnloaded:
+ return "package_unloaded";
+ case client_events::kPresentationPaneRequestCompleted:
+ return "presentation_pane_request_completed";
+ case client_events::kUnhandledError:
+ return "unhandled_error";
+ case client_events::kErrorHandlerChanged:
+ return "error_handler_changed";
+ case client_events::kViewerNavigate:
+ return "viewer_navigate";
+ case client_events::kSourceExtendedTypeDetected:
+ return "source_extended_type_detected";
+ case client_events::kShinyViewer:
+ return "shiny_viewer";
+ case client_events::kDebugSourceCompleted:
+ return "debug_source_completed";
+ default:
+ LOG_WARNING_MESSAGE("unexpected event type: " +
+ safe_convert::numberToString(type_));
+ return "";
+ }
+}
+
+ClientEvent showEditorEvent(const std::string& content,
+ bool isRCode,
+ bool lineWrapping)
+{
+ json::Object data;
+ data["content"] = content;
+ data["is_r_code"] = isRCode;
+ data["line_wrapping"] = lineWrapping;
+ return ClientEvent(client_events::kShowEditor, data);
+}
+
+ClientEvent browseUrlEvent(const std::string& url, const std::string& window)
+{
+ json::Object browseURLInfo;
+ browseURLInfo["url"] = url;
+ browseURLInfo["window"] = window;
+ return ClientEvent(client_events::kBrowseUrl, browseURLInfo);
+}
+
+
+ClientEvent showErrorMessageEvent(const std::string& title,
+ const std::string& message)
+{
+ json::Object errorMessage ;
+ errorMessage["title"] = title;
+ errorMessage["message"] = message;
+ return ClientEvent(client_events::kShowErrorMessage, errorMessage);
+}
+
+
+
+
+} // namespace session
diff --git a/src/cpp/session/SessionClientEventQueue.cpp b/src/cpp/session/SessionClientEventQueue.cpp
new file mode 100644
index 0000000..737de28
--- /dev/null
+++ b/src/cpp/session/SessionClientEventQueue.cpp
@@ -0,0 +1,177 @@
+/*
+ * SessionClientEventQueue.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionClientEventQueue.hpp"
+
+#include <boost/foreach.hpp>
+
+
+#include <core/BoostThread.hpp>
+#include <core/Thread.hpp>
+#include <core/json/Json.hpp>
+#include <core/StringUtils.hpp>
+
+#include <r/session/RConsoleActions.hpp>
+
+using namespace core ;
+
+namespace session {
+
+namespace {
+ClientEventQueue* s_pClientEventQueue = NULL;
+}
+
+void initializeClientEventQueue()
+{
+ BOOST_ASSERT(s_pClientEventQueue == NULL);
+ s_pClientEventQueue = new ClientEventQueue();
+}
+
+ClientEventQueue& clientEventQueue()
+{
+ return *s_pClientEventQueue;
+}
+
+ClientEventQueue::ClientEventQueue()
+ : pMutex_(new boost::mutex()),
+ pWaitForEventCondition_(new boost::condition()),
+ lastEventAddTime_(boost::posix_time::not_a_date_time)
+{
+}
+
+void ClientEventQueue::add(const ClientEvent& event)
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ // console output is batched up for compactness/efficiency.
+ if (event.type() == client_events::kConsoleWriteOutput)
+ {
+ if (event.data().type() == json::StringType)
+ pendingConsoleOutput_ += event.data().get_str();
+ }
+ else
+ {
+ // flush existing console output prior to adding an
+ // action of another type
+ flushPendingConsoleOutput() ;
+
+ // add event to queue
+ pendingEvents_.push_back(event) ;
+ }
+
+ lastEventAddTime_ = boost::posix_time::microsec_clock::universal_time();
+ }
+ END_LOCK_MUTEX
+
+ // notify listeners that an event has been added
+ pWaitForEventCondition_->notify_all();
+}
+
+bool ClientEventQueue::hasEvents()
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ return pendingEvents_.size() > 0 || pendingConsoleOutput_.length() > 0;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false ;
+}
+
+void ClientEventQueue::remove(std::vector<ClientEvent>* pEvents)
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ // flush any pending output
+ flushPendingConsoleOutput();
+
+ // copy the events to the caller
+ pEvents->insert(pEvents->begin(),
+ pendingEvents_.begin(),
+ pendingEvents_.end());
+
+ // clear pending events
+ pendingEvents_.clear();
+ }
+ END_LOCK_MUTEX
+}
+
+void ClientEventQueue::clear()
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ pendingConsoleOutput_.clear();
+ pendingEvents_.clear();
+ }
+ END_LOCK_MUTEX
+}
+
+
+bool ClientEventQueue::waitForEvent(
+ const boost::posix_time::time_duration& waitDuration)
+{
+ using namespace boost;
+ try
+ {
+ unique_lock<mutex> lock(*pMutex_);
+ system_time timeoutTime = get_system_time() + waitDuration;
+ return pWaitForEventCondition_->timed_wait(lock, timeoutTime);
+ }
+ catch(const thread_resource_error& e)
+ {
+ Error waitError(boost::thread_error::ec_from_exception(e),
+ ERROR_LOCATION) ;
+ LOG_ERROR(waitError);
+ return false ;
+ }
+}
+
+
+bool ClientEventQueue::eventAddedSince(const boost::posix_time::ptime& time)
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ if (lastEventAddTime_.is_not_a_date_time())
+ return false;
+ else
+ return lastEventAddTime_ >= time;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false;
+}
+
+
+void ClientEventQueue::flushPendingConsoleOutput()
+{
+ // NOTE: private helper so no lock required (mutex is not recursive)
+
+ if ( !pendingConsoleOutput_.empty() )
+ {
+ // If there's more console output than the client can even show, then
+ // truncate it to the amount that the client can show. Too much output
+ // can overwhelm the client, causing it to become unresponsive.
+ int limit = r::session::consoleActions().capacity() + 1;
+ string_utils::trimLeadingLines(limit, &pendingConsoleOutput_);
+
+ pendingEvents_.push_back(ClientEvent(client_events::kConsoleWriteOutput,
+ pendingConsoleOutput_));
+ pendingConsoleOutput_.clear() ;
+ }
+}
+
+} // namespace session
diff --git a/src/cpp/session/SessionClientEventQueue.hpp b/src/cpp/session/SessionClientEventQueue.hpp
new file mode 100644
index 0000000..5ddd77a
--- /dev/null
+++ b/src/cpp/session/SessionClientEventQueue.hpp
@@ -0,0 +1,88 @@
+/*
+ * SessionClientEventQueue.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_CLIENT_EVENT_QUEUE_HPP
+#define SESSION_SESSION_CLIENT_EVENT_QUEUE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+#include <boost/utility.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/BoostThread.hpp>
+
+#include <session/SessionClientEvent.hpp>
+
+namespace session {
+
+// initialization
+void initializeClientEventQueue();
+
+// singleton
+class ClientEventQueue;
+ClientEventQueue& clientEventQueue();
+
+class ClientEventQueue : boost::noncopyable
+{
+private:
+ ClientEventQueue() ;
+ friend void initializeClientEventQueue();
+
+public:
+ // COPYING: boost::noncopyable
+
+ // add an event
+ void add(const ClientEvent& event);
+
+ // remove all available events
+ void remove(std::vector<ClientEvent>* pEvents);
+
+ // are there any events pending?
+ bool hasEvents();
+
+ // clear the event queue
+ void clear();
+
+ // wait for a new event
+ bool waitForEvent(const boost::posix_time::time_duration& waitDuration);
+
+ // has an event been added since the specified time
+ bool eventAddedSince(const boost::posix_time::ptime& time);
+
+private:
+ void flushPendingConsoleOutput();
+
+private:
+ // synchronization objects. heap based so they are never destructed
+ // we don't want them destructed because in desktop mode we don't
+ // explicitly stop the queue and this sometimes results in mutex
+ // destroy assertions if someone is waiting on the queue while
+ // it is being destroyed
+ boost::mutex* pMutex_ ;
+ boost::condition* pWaitForEventCondition_ ;
+
+ // instance data
+ std::string pendingConsoleOutput_ ;
+ std::vector<ClientEvent> pendingEvents_ ;
+ boost::posix_time::ptime lastEventAddTime_;
+
+
+};
+
+} // namespace session
+
+#endif // SESSION_SESSION_CLIENT_EVENT_QUEUE_HPP
diff --git a/src/cpp/session/SessionClientEventService.cpp b/src/cpp/session/SessionClientEventService.cpp
new file mode 100644
index 0000000..5c4d451
--- /dev/null
+++ b/src/cpp/session/SessionClientEventService.cpp
@@ -0,0 +1,353 @@
+/*
+ * SessionClientEventService.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionClientEventService.hpp"
+
+#include <algorithm>
+
+#include <boost/function.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/Thread.hpp>
+#include <core/system/System.hpp>
+
+
+#include <core/http/Request.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionHttpConnectionListener.hpp>
+
+#include "SessionClientEventQueue.hpp"
+
+using namespace core;
+
+namespace session {
+
+namespace {
+
+const int kLastChanceWaitSeconds = 4;
+
+bool hasEventIdLessThanOrEqualTo(const json::Value& event, int targetId)
+{
+ const json::Object& eventJSON = event.get_obj();
+ int eventId = eventJSON.find("id")->second.get_int();
+ return eventId <= targetId;
+}
+
+} // anonymous namespace
+
+ClientEventService& clientEventService()
+{
+ static ClientEventService instance ;
+ return instance ;
+}
+
+Error ClientEventService::start(const std::string& clientId)
+{
+ // set our clientid
+ setClientId(clientId, false);
+
+ // block all signals for launch of background thread (will cause it
+ // to never receive signals)
+ core::system::SignalBlocker signalBlocker;
+ Error error = signalBlocker.blockAll();
+ if (error)
+ return error ;
+
+ // launch the service thread
+ try
+ {
+ using boost::bind;
+ boost::thread serviceThread(bind(&ClientEventService::run, this));
+ serviceThread_ = serviceThread.move();
+
+ return Success();
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ return Error(boost::thread_error::ec_from_exception(e), ERROR_LOCATION);
+ }
+}
+
+void ClientEventService::stop()
+{
+ try
+ {
+ if (serviceThread_.joinable())
+ {
+ serviceThread_.interrupt();
+
+ // wait for for the service thread to stop
+ if (!serviceThread_.timed_join(
+ boost::posix_time::seconds(kLastChanceWaitSeconds + 1)))
+ {
+ LOG_WARNING_MESSAGE("ClientEventService didn't stop on its own");
+ }
+
+ serviceThread_.detach();
+ }
+ }
+ catch(const boost::thread_interrupted& e)
+ {
+ // the main thread is the one who calls stop() and it should
+ // NEVER be interrupted for any reason
+ LOG_WARNING_MESSAGE("thread interrupted during stop");
+ }
+}
+
+void ClientEventService::setClientId(const std::string& clientId, bool clearEvents)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ clientId_ = clientId.c_str(); // avoid ref count
+ if (clearEvents)
+ clientEvents_.clear();
+ }
+ END_LOCK_MUTEX
+
+ if (clearEvents)
+ clientEventQueue().clear();
+}
+
+std::string ClientEventService::clientId()
+{
+ LOCK_MUTEX(mutex_)
+ {
+ return std::string(clientId_.c_str()) ; // avoid ref-count
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return std::string();
+}
+
+void ClientEventService::erasePreviouslyDeliveredEvents(int lastClientEventIdSeen)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ clientEvents_.erase(
+ std::remove_if(clientEvents_.begin(),
+ clientEvents_.end(),
+ boost::bind(hasEventIdLessThanOrEqualTo,
+ _1,
+ lastClientEventIdSeen)),
+ clientEvents_.end());
+ }
+ END_LOCK_MUTEX
+}
+
+bool ClientEventService::havePendingClientEvents()
+{
+ LOCK_MUTEX(mutex_)
+ {
+ return !clientEvents_.empty();
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false;
+}
+
+void ClientEventService::addClientEvent(const json::Object& eventObject)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ clientEvents_.push_back(eventObject);
+ }
+ END_LOCK_MUTEX
+}
+
+void ClientEventService::setClientEventResult(
+ core::json::JsonRpcResponse* pResponse)
+{
+ LOCK_MUTEX(mutex_)
+ {
+ pResponse->setResult(clientEvents_);
+ }
+ END_LOCK_MUTEX
+}
+
+
+void ClientEventService::run()
+{
+ try
+ {
+ // default time durations
+ using namespace boost::posix_time;
+ time_duration maxRequestSec = seconds(50);
+ time_duration batchDelay = milliseconds(20);
+ time_duration maxTotalBatchDelay = seconds(2);
+
+ // make much shorter for desktop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ batchDelay = milliseconds(2);
+ maxTotalBatchDelay = milliseconds(10);
+ }
+
+ // get alias to client event queue
+ ClientEventQueue& clientEventQueue = session::clientEventQueue();
+
+ // initialize state
+ int nextEventId = 0;
+
+ // accept loop
+ bool stopServer = false ;
+ while (!stopServer || clientEventQueue.hasEvents())
+ {
+ boost::shared_ptr<HttpConnection> ptrConnection ;
+ try
+ {
+ // wait for up to 1 second for a connection
+ long secondsToWait = stopServer ? kLastChanceWaitSeconds : 1;
+ ptrConnection =
+ httpConnectionListener().eventsConnectionQueue().dequeConnection(
+ boost::posix_time::seconds(secondsToWait));
+
+ // if we didn't get one then check for interruption requested
+ // and then continue waiting
+ if (!ptrConnection)
+ {
+ if (stopServer)
+ {
+ // This was our last chance. There are still some events
+ // left in the queue, but we waited and nobody came.
+ break;
+ }
+
+ // check for interruption and set stopServer flag if we were
+ if (boost::this_thread::interruption_requested())
+ throw boost::thread_interrupted();
+
+ // accept next request (assuming we weren't interrupted)
+ continue;
+ }
+ }
+ catch(const boost::thread_interrupted&)
+ {
+ stopServer = true;
+ continue;
+ }
+
+ // parse the json rpc request
+ json::JsonRpcRequest request;
+ Error error = json::parseJsonRpcRequest(ptrConnection->request().body(),
+ &request);
+ if (error)
+ {
+ ptrConnection->sendJsonRpcError(error);
+ continue;
+ }
+
+ // send an error back if this request came from the wrong client
+ if (request.clientId != clientId())
+ {
+ Error error = Error(json::errc::InvalidClientId,
+ ERROR_LOCATION);
+ ptrConnection->sendJsonRpcError(error);
+ continue;
+ }
+
+ // get the last event id seen by the client
+ int lastClientEventIdSeen = -1;
+ Error paramError = json::readParam(request.params,
+ 0,
+ &lastClientEventIdSeen);
+ if (paramError)
+ {
+ ptrConnection->sendJsonRpcError(paramError);
+ continue;
+ }
+
+ // remove all events already seen by the client from our internal list
+ erasePreviouslyDeliveredEvents(lastClientEventIdSeen);
+
+ // sync next event id to client (required so that when we resume
+ // from a suspend we provide client event ids in line with the
+ // client's expectations -- if we started with zero then the client
+ // would never see any events!)
+ nextEventId = std::max(nextEventId, lastClientEventIdSeen + 1);
+
+ // check for events (and wait a specified internal if there are none)
+ try
+ {
+ // wait for the specified maximum time
+ if (havePendingClientEvents() || clientEventQueue.hasEvents() ||
+ clientEventQueue.waitForEvent(maxRequestSec))
+ {
+ // ...got at least one event
+
+ // wait for additional events that occur in rapid succession
+ // but don't wait for more than the specified maximum seconds
+ boost::system_time maxBatchDelayTime =
+ boost::get_system_time() + maxTotalBatchDelay;
+
+ while ( clientEventQueue.waitForEvent(batchDelay) &&
+ (boost::get_system_time() < maxBatchDelayTime) )
+ {
+ }
+ }
+ }
+ catch(const boost::thread_interrupted& e)
+ {
+ // set flag so we terminate on the next accept loop iteration
+ stopServer = true ;
+
+ // NOTE: even if we are interrupted we still want to allow the
+ // response to be sent (e.g. need to send the client either
+ // an empty list of events back or perhaps even the quit event!)
+ }
+
+ // if this is the correct client then remove events from the
+ // queue and send them. otherwise, send an InvalidClientId error
+ // to this client. the currently active client will then pickup the
+ // events on the next iteration of the accept loop
+ if (request.clientId == clientId())
+ {
+ // deque the events
+ std::vector<ClientEvent> events;
+ clientEventQueue.remove(&events);
+
+ // convert to json and add event id
+ for (std::vector<ClientEvent>::const_iterator
+ it = events.begin(); it != events.end(); ++it)
+ {
+ json::Object event ;
+ it->asJsonObject(nextEventId++, &event);
+ addClientEvent(event);
+ }
+
+ // send them (pass false for kEventsPending b/c responses from the
+ // event service shouldn't interact with automatic event service
+ // starting/re-starting)
+ json::JsonRpcResponse response;
+ setClientEventResult(&response);
+ response.setField(kEventsPending, "false");
+ ptrConnection->sendJsonRpcResponse(response);
+ }
+ else
+ {
+ Error error(json::errc::InvalidClientId, ERROR_LOCATION);
+ ptrConnection->sendJsonRpcError(error);
+ }
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+} // namespace session
diff --git a/src/cpp/session/SessionClientEventService.hpp b/src/cpp/session/SessionClientEventService.hpp
new file mode 100644
index 0000000..91b15e0
--- /dev/null
+++ b/src/cpp/session/SessionClientEventService.hpp
@@ -0,0 +1,75 @@
+/*
+ * SessionClientEventService.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_CLIENT_EVENT_SERVICE_HPP
+#define SESSION_CLIENT_EVENT_SERVICE_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/BoostThread.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+
+// singleton
+class ClientEventService;
+ClientEventService& clientEventService();
+
+class ClientEventService : boost::noncopyable
+{
+private:
+ ClientEventService() {}
+ friend ClientEventService& clientEventService();
+
+public:
+ // COPYING: boost::noncopyable
+
+ core::Error start(const std::string& clientId);
+ void stop();
+
+ void setClientId(const std::string& clientId, bool clearEvents);
+
+
+private:
+ std::string clientId();
+
+ void run();
+
+ void erasePreviouslyDeliveredEvents(int lastClientEventIdSeen);
+ bool havePendingClientEvents();
+ void addClientEvent(const core::json::Object& eventObject);
+ void setClientEventResult(core::json::JsonRpcResponse* pResponse);
+
+
+private:
+ boost::mutex mutex_ ;
+ boost::thread serviceThread_ ;
+
+ std::string clientId_ ;
+ core::json::Array clientEvents_ ;
+};
+
+
+} // namespace session
+
+#endif // SESSION_CLIENT_EVENT_SERVICE_HPP
diff --git a/src/cpp/session/SessionContentUrls.cpp b/src/cpp/session/SessionContentUrls.cpp
new file mode 100644
index 0000000..3f356bb
--- /dev/null
+++ b/src/cpp/session/SessionContentUrls.cpp
@@ -0,0 +1,220 @@
+/*
+ * SessionContentUrls.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionContentUrls.hpp>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/Exec.hpp>
+
+#include <core/system/System.hpp>
+
+#include <core/http/Util.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/Request.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+extern "C" const char *locale2charset(const char *);
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace content_urls {
+
+namespace {
+
+FilePath contentUrlPath()
+{
+ FilePath filePath = module_context::userScratchPath().complete("content_urls");
+ Error error = filePath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ return filePath;
+}
+
+std::string buildContentUrl(const std::string& title,
+ const std::string& contentFile)
+{
+ // calculate and return content url
+ std::string url = std::string("content") +
+ "?title=" + http::util::urlEncode(title, true) +
+ "&file=" + http::util::urlEncode(contentFile, true);
+ return url;
+}
+
+Error contentFileInfo(const std::string& contentUrl,
+ std::string* pTitle,
+ core::FilePath* pFilePath)
+{
+ // extract and parse query string
+ std::string queryString;
+ std::string::size_type pos = contentUrl.find('?');
+ if (pos != std::string::npos)
+ {
+ if ((pos+1) < contentUrl.length())
+ queryString = contentUrl.substr(pos+1);
+ else
+ return systemError(ENOENT, ERROR_LOCATION);
+ }
+ else
+ {
+ return systemError(ENOENT, ERROR_LOCATION);
+ }
+ http::Fields fields;
+ http::util::parseQueryString(queryString, &fields);
+
+ // get title parameter
+ *pTitle = http::util::fieldValue<std::string>(fields, "title", "(Untitled)");
+
+ // get file parameter
+ std::string contentFile = http::util::fieldValue(fields, "file");
+ if (contentFile.empty())
+ return systemError(ENOENT, ERROR_LOCATION);
+ *pFilePath = contentUrlPath().complete(contentFile);
+
+ // return success
+ return Success();
+}
+
+
+} // anonymous namespace
+
+
+std::string provision(const std::string& title, const FilePath& filePath)
+{
+ // calculate content path
+ std::string contentFile = core::system::generateUuid(false) +
+ filePath.extension();
+ FilePath contentPath = contentUrlPath().complete(contentFile);
+
+ // copy the file
+ Error error = filePath.copy(contentPath);
+ if (error)
+ LOG_ERROR(error);
+
+ // calculate and return content url
+ return buildContentUrl(title, contentFile);
+}
+
+std::string provision(const std::string& title,
+ const std::string& content,
+ const std::string& extension)
+{
+ // calculate content path
+ std::string contentFile = core::system::generateUuid(false) + extension;
+ FilePath contentPath = contentUrlPath().complete(contentFile);
+
+ // write the file
+ Error error = writeStringToFile(contentPath, content);
+ if (error)
+ LOG_ERROR(error);
+
+ // calculate and return content url
+ return buildContentUrl(title, contentFile);
+}
+
+
+
+void handleContentRequest(const http::Request& request, http::Response* pResponse)
+{
+ // get content file info
+ std::string title;
+ FilePath contentFilePath;
+ Error error = contentFileInfo(request.uri(), &title, &contentFilePath);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // set private cache forever headers
+ pResponse->setPrivateCacheForeverHeaders();
+
+ // set file
+ pResponse->setFile(contentFilePath, request);
+
+ bool isUtf8 = true;
+ if (boost::algorithm::starts_with(contentFilePath.mimeContentType(), "text/"))
+ {
+ // If the content looks like valid UTF-8, assume it is. Otherwise, assume
+ // it's the system encoding.
+ std::string contents;
+ error = core::readStringFromFile(contentFilePath, &contents);
+ if (!error)
+ {
+ for (std::string::iterator pos = contents.begin(); pos != contents.end(); )
+ {
+ error = string_utils::utf8Advance(pos, 1, contents.end(), &pos);
+ if (error)
+ {
+ isUtf8 = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // reset content-type with charset
+ pResponse->setContentType(contentFilePath.mimeContentType() +
+ std::string("; charset=") +
+ (isUtf8 ? "UTF-8" : ::locale2charset(NULL)));
+
+ // set title header
+ pResponse->setHeader("Title", title);
+}
+
+Error removeContentUrl(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get content url
+ std::string contentUrl;
+ Error error = json::readParams(request.params, &contentUrl);
+ if (error)
+ return error;
+
+ // get content file info
+ std::string title;
+ FilePath contentFilePath;
+ error = contentFileInfo(contentUrl, &title, &contentFilePath);
+ if (error)
+ return error;
+
+ // remove it
+ return contentFilePath.removeIfExists();
+}
+
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace session::module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerUriHandler, "/content", handleContentRequest))
+ (bind(registerRpcMethod, "remove_content_url", removeContentUrl));
+ return initBlock.execute();
+}
+
+
+} // namespace content_urls
+} // namesapce session
+
diff --git a/src/cpp/session/SessionMain.cpp b/src/cpp/session/SessionMain.cpp
new file mode 100644
index 0000000..bc0d52d
--- /dev/null
+++ b/src/cpp/session/SessionMain.cpp
@@ -0,0 +1,2936 @@
+/*
+ * SessionMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionMain.hpp>
+
+// required to avoid Win64 winsock order of include
+// compilation problem
+#include <boost/asio/io_service.hpp>
+
+#include <string>
+#include <vector>
+#include <queue>
+#include <map>
+#include <algorithm>
+#include <cstdlib>
+#include <csignal>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+#include <boost/signals.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+#include <core/Error.hpp>
+#include <core/BoostThread.hpp>
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+#include <core/Scope.hpp>
+#include <core/Settings.hpp>
+#include <core/Thread.hpp>
+#include <core/Log.hpp>
+#include <core/LogWriter.hpp>
+#include <core/system/System.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/system/System.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/http/URL.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/UriHandler.hpp>
+#include <core/json/JsonRpc.hpp>
+#include <core/gwt/GwtLogHandler.hpp>
+#include <core/gwt/GwtFileHandler.hpp>
+#include <core/system/Crypto.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/ParentProcessMonitor.hpp>
+#include <core/system/FileMonitor.hpp>
+#include <core/text/TemplateFilter.hpp>
+
+#include <r/RJsonRpc.hpp>
+#include <r/RExec.hpp>
+#include <r/ROptions.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/RInterface.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RClientState.hpp>
+#include <r/session/RConsoleActions.hpp>
+#include <r/session/RConsoleHistory.hpp>
+#include <r/session/RGraphics.hpp>
+#include <r/session/REventLoop.hpp>
+
+extern "C" const char *locale2charset(const char *);
+
+#include <monitor/MonitorClient.hpp>
+
+#include <session/SessionConstants.hpp>
+#include <session/SessionOptions.hpp>
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionSourceDatabase.hpp>
+#include <session/SessionPersistentState.hpp>
+#include <session/SessionContentUrls.hpp>
+
+#include "SessionAddins.hpp"
+
+#include "SessionModuleContextInternal.hpp"
+
+#include "SessionClientEventQueue.hpp"
+#include "SessionClientEventService.hpp"
+
+#include "modules/SessionAbout.hpp"
+#include "modules/SessionAgreement.hpp"
+#include "modules/SessionAskPass.hpp"
+#include "modules/SessionAuthoring.hpp"
+#include "modules/SessionBreakpoints.hpp"
+#include "modules/SessionHTMLPreview.hpp"
+#include "modules/SessionCodeSearch.hpp"
+#include "modules/SessionConsole.hpp"
+#include "modules/SessionConsoleProcess.hpp"
+#include "modules/SessionCrypto.hpp"
+#include "modules/SessionErrors.hpp"
+#include "modules/SessionFiles.hpp"
+#include "modules/SessionFind.hpp"
+#include "modules/SessionDirty.hpp"
+#include "modules/SessionWorkbench.hpp"
+#include "modules/SessionHelp.hpp"
+#include "modules/SessionPlots.hpp"
+#include "modules/SessionPath.hpp"
+#include "modules/SessionPackages.hpp"
+#include "modules/SessionProfiler.hpp"
+#include "modules/SessionRMarkdown.hpp"
+#include "modules/SessionRPubs.hpp"
+#include "modules/SessionShinyApps.hpp"
+#include "modules/SessionShinyViewer.hpp"
+#include "modules/SessionSpelling.hpp"
+#include "modules/SessionSource.hpp"
+#include "modules/SessionUpdates.hpp"
+#include "modules/SessionVCS.hpp"
+#include "modules/SessionViewer.hpp"
+#include "modules/SessionHistory.hpp"
+#include "modules/SessionLimits.hpp"
+#include "modules/SessionLists.hpp"
+#include "modules/build/SessionBuild.hpp"
+#include "modules/data/SessionData.hpp"
+#include "modules/environment/SessionEnvironment.hpp"
+#include "modules/overlay/SessionOverlay.hpp"
+#include "modules/presentation/SessionPresentation.hpp"
+#include "modules/shiny/SessionShiny.hpp"
+
+#include "modules/SessionGit.hpp"
+#include "modules/SessionSVN.hpp"
+
+#include <session/projects/SessionProjects.hpp>
+#include "projects/SessionProjectsInternal.hpp"
+
+#include "workers/SessionWebRequestWorker.hpp"
+
+#include <session/SessionHttpConnectionListener.hpp>
+
+#include "session-config.h"
+
+using namespace core;
+using namespace session;
+using namespace session::client_events;
+
+namespace {
+
+// uri handlers
+http::UriHandlers s_uriHandlers;
+http::UriHandlerFunction s_defaultUriHandler;
+
+// json rpc methods
+core::json::JsonRpcAsyncMethods s_jsonRpcMethods;
+
+// R browseUrl handlers
+std::vector<module_context::RBrowseUrlHandler> s_rBrowseUrlHandlers;
+
+// R browseFile handlers
+std::vector<module_context::RBrowseFileHandler> s_rBrowseFileHandlers;
+
+// names of waitForMethod handlers (used to screen out of bkgnd processing)
+std::vector<std::string> s_waitForMethodNames;
+
+// last prompt we issued
+std::string s_lastPrompt;
+
+// have we fully initialized? used by rConsoleRead and clientInit to
+// tweak their behavior when the process is first starting
+bool s_sessionInitialized = false;
+
+// was the underlying r session resumed
+bool s_rSessionResumed = false;
+
+// manage global state indicating whether R is processing input
+volatile sig_atomic_t s_rProcessingInput = 0;
+
+// did we fail to coerce the charset to UTF-8
+bool s_printCharsetWarning = false;
+
+std::queue<r::session::RConsoleInput> s_consoleInputBuffer;
+
+// json rpc methods we handle (the rest are delegated to the HttpServer)
+const char * const kClientInit = "client_init" ;
+const char * const kConsoleInput = "console_input" ;
+const char * const kEditCompleted = "edit_completed";
+const char * const kChooseFileCompleted = "choose_file_completed";
+const char * const kLocatorCompleted = "locator_completed";
+const char * const kHandleUnsavedChangesCompleted = "handle_unsaved_changes_completed";
+const char * const kQuitSession = "quit_session" ;
+const char * const kSuspendSession = "suspend_session";
+const char * const kInterrupt = "interrupt";
+
+// convenience function for disallowing suspend (note still doesn't override
+// the presence of s_forceSuspend = 1)
+bool disallowSuspend() { return false; }
+
+// request suspends (cooperative and forced) using interrupts
+volatile sig_atomic_t s_suspendRequested = 0;
+volatile sig_atomic_t s_forceSuspend = 0;
+volatile sig_atomic_t s_forceSuspendInterruptedR = 0;
+bool s_suspendedFromTimeout = false;
+
+// cooperative suspend -- the http server is forced to timeout and a flag
+// indicating that we should suspend at ourfirst valid opportunity is set
+void handleUSR1(int unused)
+{
+ // note that a suspend has been requested. the process will suspend
+ // at the first instance that it is valid for it to do so
+ s_suspendRequested = 1;
+}
+
+// forced suspend -- R is interrupted, the http server is forced to timeout,
+// and the 'force' flag is set
+void handleUSR2(int unused)
+{
+ // note whether R was interrupted
+ if (s_rProcessingInput)
+ s_forceSuspendInterruptedR = 1;
+
+ // set the r interrupt flag (always)
+ r::exec::setInterruptsPending(true);
+
+ // note that a suspend is being forced.
+ s_forceSuspend = 1;
+}
+
+// version of the executable
+double s_version = 0;
+
+// installed version (computed as the time in seconds since epoch that
+// the running/served code was installed) can be distinct from the version
+// of the currently running executable if a deployment occured after this
+// executable started running.
+double installedVersion()
+{
+ // never return a version in desktop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ return 0;
+
+ // read installation time (as string) from file (return 0 if not found)
+ FilePath installedPath("/var/lib/rstudio-server/installed");
+ if (!installedPath.exists())
+ return 0;
+
+ // attempt to get the value (return 0 if we have any trouble)
+ std::string installedStr;
+ Error error = readStringFromFile(installedPath, &installedStr);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return 0;
+ }
+
+ // empty string means 0
+ if (installedStr.empty())
+ {
+ LOG_ERROR_MESSAGE("No value within /var/lib/rstudio-server/installed");
+ return 0;
+ }
+
+ // attempt to parse the value into a double
+ double installedSeconds = 0.0;
+ try
+ {
+ std::istringstream istr(installedStr);
+ istr >> installedSeconds;
+ if (istr.fail())
+ LOG_ERROR(systemError(boost::system::errc::io_error, ERROR_LOCATION));
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // return installed seconds as version
+ return installedSeconds ;
+}
+
+
+void detectChanges(module_context::ChangeSource source)
+{
+ module_context::events().onDetectChanges(source);
+}
+
+
+// certain things are deferred until after we have sent our first response
+// take care of these things here
+void ensureSessionInitialized()
+{
+ // note that we are now fully initialized. we defer setting this
+ // flag so that consoleRead and handleClientInit know that we have just
+ // started up and can act accordingly
+ s_sessionInitialized = true;
+
+ // ensure the session is fully deserialized (deferred deserialization
+ // is supported so that the workbench UI can load without having to wait
+ // for the potentially very lengthy deserialization of the environment)
+ r::session::ensureDeserialized();
+}
+
+FilePath getDefaultWorkingDirectory()
+{
+ // calculate using user settings
+ FilePath defaultWorkingDir = userSettings().initialWorkingDirectory();
+
+ // return it if it exists, otherwise use the default user home path
+ if (defaultWorkingDir.exists() && defaultWorkingDir.isDirectory())
+ return defaultWorkingDir;
+ else
+ return session::options().userHomePath();
+}
+
+FilePath getInitialWorkingDirectory()
+{
+ // check for a project
+ if (projects::projectContext().hasProject())
+ return projects::projectContext().directory();
+
+ // see if there is an override from the environment (perhaps based
+ // on a folder drag and drop or other file association)
+ FilePath workingDirPath = session::options().initialWorkingDirOverride();
+ if (workingDirPath.exists() && workingDirPath.isDirectory())
+ {
+ return workingDirPath;
+ }
+ else
+ {
+ // if not then just return default working dir
+ return getDefaultWorkingDirectory();
+ }
+}
+
+std::string switchToProject(const http::Request& request)
+{
+ std::string referrer = request.headerValue("referer");
+ std::string baseURL, queryString;
+ http::URL(referrer).split(&baseURL, &queryString);
+ http::Fields fields;
+ http::util::parseQueryString(queryString, &fields);
+ std::string project = http::util::fieldValue(fields, "project");
+
+ if (!project.empty())
+ {
+ // resolve project
+ FilePath projectPath = module_context::resolveAliasedPath(project);
+ if ((projectPath.extensionLowerCase() != ".rproj") &&
+ projectPath.isDirectory())
+ {
+ FilePath discoveredPath = r_util::projectFromDirectory(projectPath);
+ if (!discoveredPath.empty())
+ projectPath = discoveredPath;
+ }
+ project = module_context::createAliasedPath(projectPath);
+
+ // check if we're already in this project
+ if (projects::projectContext().hasProject())
+ {
+ std::string currentProject = module_context::createAliasedPath(
+ projects::projectContext().file());
+ if (project != currentProject)
+ return project;
+ else
+ return std::string();
+ }
+ // no project active so need to switch
+ else
+ {
+ return project;
+ }
+ }
+ // no project in the query string
+ else
+ {
+ return std::string();
+ }
+}
+
+
+void handleClientInit(const boost::function<void()>& initFunction,
+ boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ // alias options
+ Options& options = session::options();
+
+ // calculate initialization parameters
+ std::string clientId = session::persistentState().newActiveClientId();
+ bool resumed = s_rSessionResumed || s_sessionInitialized;
+
+ // if we are resuming then we don't need to worry about events queued up
+ // by R during startup (e.g. printing of the banner) being sent to the
+ // client. so, clear out the events which might be pending in the
+ // client event service and/or queue
+ bool clearEvents = resumed;
+
+ // reset the client event service for the new client (will cause
+ // outstanding http requests from old clients to fail with
+ // InvalidClientId). note that we can't simply stop() the
+ // ClientEventService and start() a new one because in that case the
+ // old client will never get disconnected because it won't get
+ // the InvalidClientId error.
+ clientEventService().setClientId(clientId, clearEvents);
+
+ // set RSTUDIO_HTTP_REFERER environment variable based on Referer
+ if (options.programMode() == kSessionProgramModeServer)
+ {
+ std::string referer = ptrConnection->request().headerValue("referer");
+ core::system::setenv("RSTUDIO_HTTP_REFERER", referer);
+ }
+
+ // prepare session info
+ json::Object sessionInfo ;
+ sessionInfo["clientId"] = clientId;
+ sessionInfo["mode"] = options.programMode();
+
+ sessionInfo["userIdentity"] = options.userIdentity();
+
+ // only send log_dir and scratch_dir if we are in desktop mode
+ if (options.programMode() == kSessionProgramModeDesktop)
+ {
+ sessionInfo["log_dir"] = options.userLogPath().absolutePath();
+ sessionInfo["scratch_dir"] = options.userScratchPath().absolutePath();
+ }
+
+ // temp dir
+ FilePath tempDir = r::session::utils::tempDir();
+ Error error = tempDir.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ sessionInfo["temp_dir"] = tempDir.absolutePath();
+
+ // installed version
+ sessionInfo["version"] = installedVersion();
+
+ // default prompt
+ sessionInfo["prompt"] = r::options::getOption<std::string>("prompt");
+
+ // client state
+ json::Object clientStateObject;
+ r::session::clientState().currentState(&clientStateObject);
+ sessionInfo["client_state"] = clientStateObject;
+
+ // source documents
+ json::Array jsonDocs;
+ error = modules::source::clientInitDocuments(&jsonDocs);
+ if (error)
+ LOG_ERROR(error);
+ sessionInfo["source_documents"] = jsonDocs;
+
+ // agreement
+ sessionInfo["hasAgreement"] = modules::agreement::hasAgreement();
+ sessionInfo["pendingAgreement"] = modules::agreement::pendingAgreement();
+
+ // docs url
+ sessionInfo["docsURL"] = session::options().docsURL();
+
+ // get alias to console_actions and get limit
+ r::session::ConsoleActions& consoleActions = r::session::consoleActions();
+ sessionInfo["console_actions_limit"] = consoleActions.capacity();
+
+ // resumed
+ sessionInfo["resumed"] = resumed;
+ if (resumed)
+ {
+ // console actions
+ json::Object actionsObject;
+ consoleActions.asJson(&actionsObject);
+ sessionInfo["console_actions"] = actionsObject;
+ }
+
+ sessionInfo["rnw_weave_types"] = modules::authoring::supportedRnwWeaveTypes();
+ sessionInfo["latex_program_types"] = modules::authoring::supportedLatexProgramTypes();
+ sessionInfo["tex_capabilities"] = modules::authoring::texCapabilitiesAsJson();
+ sessionInfo["compile_pdf_state"] = modules::authoring::compilePdfStateAsJson();
+
+ sessionInfo["html_capabilities"] = modules::html_preview::capabilitiesAsJson();
+
+ sessionInfo["find_in_files_state"] = modules::find::findInFilesStateAsJson();
+
+ sessionInfo["rstudio_version"] = std::string(RSTUDIO_VERSION);
+
+ sessionInfo["ui_prefs"] = userSettings().uiPrefs();
+
+ sessionInfo["have_advanced_step_commands"] =
+ modules::breakpoints::haveAdvancedStepCommands();
+
+ // initial working directory
+ std::string initialWorkingDir = module_context::createAliasedPath(
+ getInitialWorkingDirectory());
+ sessionInfo["initial_working_dir"] = initialWorkingDir;
+
+ // active project file
+ if (projects::projectContext().hasProject())
+ {
+ sessionInfo["active_project_file"] = module_context::createAliasedPath(
+ projects::projectContext().file());
+ sessionInfo["project_ui_prefs"] = projects::projectContext().uiPrefs();
+ sessionInfo["project_open_docs"] = projects::projectContext().openDocs();
+ }
+ else
+ {
+ sessionInfo["active_project_file"] = json::Value();
+ sessionInfo["project_ui_prefs"] = json::Value();
+ sessionInfo["project_open_docs"] = json::Value();
+ }
+
+ sessionInfo["system_encoding"] = std::string(::locale2charset(NULL));
+
+ std::vector<std::string> vcsAvailable;
+ if (modules::source_control::isGitInstalled())
+ vcsAvailable.push_back(modules::git::kVcsId);
+ if (modules::source_control::isSvnInstalled())
+ vcsAvailable.push_back(modules::svn::kVcsId);
+ sessionInfo["vcs_available"] = boost::algorithm::join(vcsAvailable, ",");
+ sessionInfo["vcs"] = modules::source_control::activeVCSName();
+ sessionInfo["default_ssh_key_dir"] =module_context::createAliasedPath(
+ modules::source_control::defaultSshKeyDir());
+ sessionInfo["is_github_repo"] = modules::git::isGithubRepository();
+
+ // contents of all lists
+ sessionInfo["lists"] = modules::lists::allListsAsJson();
+
+ sessionInfo["console_processes"] =
+ session::modules::console_process::processesAsJson();
+
+ // is internal preview supported by the client browser
+ std::string userAgent = ptrConnection->request().userAgent();
+ sessionInfo["internal_pdf_preview_enabled"] =
+ modules::authoring::isPdfViewerSupported(userAgent);
+
+ // send sumatra pdf exe path if we are on windows
+#ifdef _WIN32
+ sessionInfo["sumatra_pdf_exe_path"] =
+ options.sumatraPath().complete("SumatraPDF.exe").absolutePath();
+#endif
+
+ // are build tools enabled
+ if (projects::projectContext().hasProject())
+ {
+ std::string type = projects::projectContext().config().buildType;
+ sessionInfo["build_tools_type"] = type;
+
+ FilePath buildTargetDir = projects::projectContext().buildTargetPath();
+ if (!buildTargetDir.empty())
+ {
+ sessionInfo["build_target_dir"] = module_context::createAliasedPath(
+ buildTargetDir);
+ sessionInfo["has_pkg_src"] = (type == r_util::kBuildTypePackage) &&
+ buildTargetDir.childPath("src").exists();
+ sessionInfo["has_pkg_vig"] =
+ (type == r_util::kBuildTypePackage) &&
+ buildTargetDir.childPath("vignettes").exists();
+ }
+ else
+ {
+ sessionInfo["build_target_dir"] = json::Value();
+ sessionInfo["has_pkg_src"] = false;
+ sessionInfo["has_pkg_vig"] = false;
+ }
+
+ }
+ else
+ {
+ sessionInfo["build_tools_type"] = r_util::kBuildTypeNone;
+ sessionInfo["build_target_dir"] = json::Value();
+ sessionInfo["has_pkg_src"] = false;
+ sessionInfo["has_pkg_vig"] = false;
+ }
+
+ sessionInfo["presentation_state"] = modules::presentation::presentationStateAsJson();
+
+ sessionInfo["build_state"] = modules::build::buildStateAsJson();
+ sessionInfo["devtools_installed"] = module_context::isPackageInstalled(
+ "devtools");
+ sessionInfo["have_cairo_pdf"] = modules::plots::haveCairoPdf();
+
+ sessionInfo["have_srcref_attribute"] =
+ modules::breakpoints::haveSrcrefAttribute();
+
+ // console history -- we do this at the end because
+ // restoreBuildRestartContext may have reset it
+ json::Array historyArray;
+ r::session::consoleHistory().asJson(&historyArray);
+ sessionInfo["console_history"] = historyArray;
+ sessionInfo["console_history_capacity"] =
+ r::session::consoleHistory().capacity();
+
+ sessionInfo["disable_packages"] =
+ !core::system::getenv("RSTUDIO_DISABLE_PACKAGES").empty();
+
+ sessionInfo["disable_check_for_updates"] =
+ !core::system::getenv("RSTUDIO_DISABLE_CHECK_FOR_UPDATES").empty();
+
+ sessionInfo["allow_vcs_exe_edit"] = options.allowVcsExecutableEdit();
+ sessionInfo["allow_cran_repos_edit"] = options.allowCRANReposEdit();
+ sessionInfo["allow_vcs"] = options.allowVcs();
+ sessionInfo["allow_pkg_install"] = options.allowPackageInstallation();
+ sessionInfo["allow_shell"] = options.allowShell();
+ sessionInfo["allow_file_download"] = options.allowFileDownloads();
+ sessionInfo["allow_remove_public_folder"] = options.allowRemovePublicFolder();
+ sessionInfo["allow_rpubs_publish"] = options.allowRpubsPublish();
+
+ // check whether a switch project is required
+ sessionInfo["switch_to_project"] = switchToProject(ptrConnection->request());
+
+ sessionInfo["environment_state"] = modules::environment::environmentStateAsJson();
+ sessionInfo["error_state"] = modules::errors::errorStateAsJson();
+
+ // send whether we should show the user identity
+ sessionInfo["show_identity"] =
+ (options.programMode() == kSessionProgramModeServer) &&
+ options.showUserIdentity();
+
+ // light up shinyapps-related UI features if shinyapps is installed
+ sessionInfo["shinyapps_installed"] =
+ module_context::isPackageVersionInstalled("shinyapps", "0.2.1");
+
+ // send response (we always set kEventsPending to false so that the client
+ // won't poll for events until it is ready)
+ json::JsonRpcResponse jsonRpcResponse ;
+ jsonRpcResponse.setField(kEventsPending, "false");
+ jsonRpcResponse.setResult(sessionInfo) ;
+ ptrConnection->sendJsonRpcResponse(jsonRpcResponse);
+
+ // complete initialization of session
+ ensureSessionInitialized();
+
+ // notify modules of the client init
+ module_context::events().onClientInit();
+
+ // call the init function
+ initFunction();
+}
+
+enum ConnectionType
+{
+ ForegroundConnection,
+ BackgroundConnection
+};
+
+void endHandleRpcRequestDirect(boost::shared_ptr<HttpConnection> ptrConnection,
+ boost::posix_time::ptime executeStartTime,
+ const core::Error& executeError,
+ json::JsonRpcResponse* pJsonRpcResponse)
+{
+ // return error or result then continue waiting for requests
+ if (executeError)
+ {
+ ptrConnection->sendJsonRpcError(executeError);
+ }
+ else
+ {
+ // allow modules to detect changes after rpc calls
+ if (!pJsonRpcResponse->suppressDetectChanges())
+ detectChanges(module_context::ChangeSourceRPC);
+
+ // are there (or will there likely be) events pending?
+ // (if not then notify the client)
+ if ( !clientEventQueue().eventAddedSince(executeStartTime) &&
+ !pJsonRpcResponse->hasAfterResponse() )
+ {
+ pJsonRpcResponse->setField(kEventsPending, "false");
+ }
+
+ // send the response
+ ptrConnection->sendJsonRpcResponse(*pJsonRpcResponse);
+
+ // run after response if we have one (then detect changes again)
+ if (pJsonRpcResponse->hasAfterResponse())
+ {
+ pJsonRpcResponse->runAfterResponse();
+ if (!pJsonRpcResponse->suppressDetectChanges())
+ detectChanges(module_context::ChangeSourceRPC);
+ }
+ }
+}
+
+void endHandleRpcRequestIndirect(
+ const std::string& asyncHandle,
+ const core::Error& executeError,
+ json::JsonRpcResponse* pJsonRpcResponse)
+{
+ json::JsonRpcResponse temp;
+ json::JsonRpcResponse& jsonRpcResponse =
+ pJsonRpcResponse ? *pJsonRpcResponse : temp;
+
+ BOOST_ASSERT(!jsonRpcResponse.hasAfterResponse());
+ if (executeError)
+ {
+ jsonRpcResponse.setError(executeError);
+ }
+ json::Object value;
+ value["handle"] = asyncHandle;
+ value["response"] = jsonRpcResponse.getRawResponse();
+ ClientEvent evt(client_events::kAsyncCompletion, value);
+ module_context::enqueClientEvent(evt);
+}
+
+void handleRpcRequest(const core::json::JsonRpcRequest& request,
+ boost::shared_ptr<HttpConnection> ptrConnection,
+ ConnectionType connectionType)
+{
+ // record the time just prior to execution of the event
+ // (so we can determine if any events were added during execution)
+ using namespace boost::posix_time;
+ ptime executeStartTime = microsec_clock::universal_time();
+
+ // execute the method
+ json::JsonRpcAsyncMethods::const_iterator it =
+ s_jsonRpcMethods.find(request.method);
+ if (it != s_jsonRpcMethods.end())
+ {
+ std::pair<bool, json::JsonRpcAsyncFunction> reg = it->second;
+ json::JsonRpcAsyncFunction handlerFunction = reg.second;
+
+ if (reg.first)
+ {
+ // direct return
+ handlerFunction(request,
+ boost::bind(endHandleRpcRequestDirect,
+ ptrConnection,
+ executeStartTime,
+ _1,
+ _2));
+ }
+ else
+ {
+ // indirect return (asyncHandle style)
+ std::string handle = core::system::generateUuid(true);
+ json::JsonRpcResponse response;
+ response.setAsyncHandle(handle);
+ response.setField(kEventsPending, "false");
+ ptrConnection->sendJsonRpcResponse(response);
+
+ handlerFunction(request,
+ boost::bind(endHandleRpcRequestIndirect,
+ handle,
+ _1,
+ _2));
+ }
+ }
+ else
+ {
+ Error executeError = Error(json::errc::MethodNotFound, ERROR_LOCATION);
+ executeError.addProperty("method", request.method);
+
+ // we need to know about these because they represent unexpected
+ // application states
+ LOG_ERROR(executeError);
+
+ endHandleRpcRequestDirect(ptrConnection, executeStartTime, executeError, NULL);
+ }
+
+
+}
+
+bool isMethod(const std::string& uri, const std::string& method)
+{
+ return boost::algorithm::ends_with(uri, method);
+}
+
+bool isMethod(boost::shared_ptr<HttpConnection> ptrConnection,
+ const std::string& method)
+{
+ return isMethod(ptrConnection->request().uri(), method);
+}
+
+bool isJsonRpcRequest(boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ return boost::algorithm::starts_with(ptrConnection->request().uri(),
+ "/rpc/");
+}
+
+bool isWaitForMethodUri(const std::string& uri)
+{
+ BOOST_FOREACH(const std::string& methodName, s_waitForMethodNames)
+ {
+ if (isMethod(uri, methodName))
+ return true;
+ }
+
+ return false;
+}
+
+bool parseAndValidateJsonRpcConnection(
+ boost::shared_ptr<HttpConnection> ptrConnection,
+ json::JsonRpcRequest* pJsonRpcRequest)
+{
+ // attempt to parse the request into a json-rpc request
+ Error error = json::parseJsonRpcRequest(ptrConnection->request().body(),
+ pJsonRpcRequest);
+ if (error)
+ {
+ ptrConnection->sendJsonRpcError(error);
+ return false;
+ }
+
+ // check for invalid client id
+ if (pJsonRpcRequest->clientId != session::persistentState().activeClientId())
+ {
+ Error error(json::errc::InvalidClientId, ERROR_LOCATION);
+ ptrConnection->sendJsonRpcError(error);
+ return false;
+ }
+
+ // check for old client version
+ if ( (pJsonRpcRequest->version > 0) &&
+ (s_version > pJsonRpcRequest->version) )
+ {
+ Error error(json::errc::InvalidClientVersion, ERROR_LOCATION);
+ ptrConnection->sendJsonRpcError(error);
+ return false;
+ }
+
+ // got through all of the validation, return true
+ return true;
+}
+
+void endHandleConnection(boost::shared_ptr<HttpConnection> ptrConnection,
+ ConnectionType connectionType,
+ http::Response* pResponse)
+{
+ ptrConnection->sendResponse(*pResponse);
+ if (!s_rProcessingInput)
+ detectChanges(module_context::ChangeSourceURI);
+}
+
+void handleConnection(boost::shared_ptr<HttpConnection> ptrConnection,
+ ConnectionType connectionType)
+{
+ // check for a uri handler registered by a module
+ const http::Request& request = ptrConnection->request();
+ std::string uri = request.uri();
+ http::UriAsyncHandlerFunction uriHandler = s_uriHandlers.handlerFor(uri);
+
+ if (uriHandler) // uri handler
+ {
+ // r code may execute - ensure session is initialized
+ ensureSessionInitialized();
+
+ uriHandler(request, boost::bind(endHandleConnection,
+ ptrConnection,
+ connectionType,
+ _1));
+ }
+ else if (isJsonRpcRequest(ptrConnection)) // check for json-rpc
+ {
+ // r code may execute - ensure session is initialized
+ ensureSessionInitialized();
+
+ // attempt to parse & validate
+ json::JsonRpcRequest jsonRpcRequest;
+ if (parseAndValidateJsonRpcConnection(ptrConnection, &jsonRpcRequest))
+ {
+ // quit_session: exit process
+ if (jsonRpcRequest.method == kQuitSession)
+ {
+ // see whether we should save the workspace
+ bool saveWorkspace = true;
+ std::string switchToProject;
+ Error error = json::readParams(jsonRpcRequest.params,
+ &saveWorkspace,
+ &switchToProject) ;
+ if (error)
+ LOG_ERROR(error);
+
+ // note switch to project
+ if (!switchToProject.empty())
+ {
+ session::projects::projectContext().setNextSessionProject(
+ switchToProject);
+ }
+
+ // acknowledge request & quit session
+ ptrConnection->sendJsonRpcResponse();
+ r::session::quit(saveWorkspace); // does not return
+ }
+ else if (jsonRpcRequest.method == kSuspendSession)
+ {
+ // check for force
+ bool force = true;
+ Error error = json::readParams(jsonRpcRequest.params, &force);
+ if (error)
+ LOG_ERROR(error);
+
+ // acknowledge request and set flags to suspend session
+ ptrConnection->sendJsonRpcResponse();
+ if (force)
+ handleUSR2(0);
+ else
+ handleUSR1(0);
+ }
+
+ // interrupt
+ else if ( jsonRpcRequest.method == kInterrupt )
+ {
+ // Discard any buffered input
+ while (!s_consoleInputBuffer.empty())
+ s_consoleInputBuffer.pop();
+
+ // aknowledge request
+ ptrConnection->sendJsonRpcResponse();
+
+ // only accept interrupts while R is processing input
+ if ( s_rProcessingInput )
+ r::exec::setInterruptsPending(true);
+ }
+
+ // other rpc method, handle it
+ else
+ {
+ jsonRpcRequest.isBackgroundConnection =
+ (connectionType == BackgroundConnection);
+ handleRpcRequest(jsonRpcRequest, ptrConnection, connectionType);
+ }
+ }
+ }
+ else if (s_defaultUriHandler)
+ {
+ http::Response response;
+ s_defaultUriHandler(request, &response);
+ ptrConnection->sendResponse(response);
+ }
+ else
+ {
+ http::Response response;
+ response.setError(http::status::NotFound, request.uri() + " not found");
+ ptrConnection->sendResponse(response);
+ }
+}
+
+// fork state
+boost::thread::id s_mainThreadId;
+bool s_wasForked = false;
+
+// fork handlers (only applicatable to Unix platforms)
+#ifndef _WIN32
+
+void prepareFork()
+{
+ // only detect forks from the main thread (since we are going to be
+ // calling into non-threadsafe code). this is ok because fork
+ // detection is meant to handle forks that don't exec (and thus
+ // continue running R code). only the main thread will ever do this
+ if (boost::this_thread::get_id() != s_mainThreadId)
+ return;
+
+}
+
+void atForkParent()
+{
+ if (boost::this_thread::get_id() != s_mainThreadId)
+ return;
+
+}
+
+void atForkChild()
+{
+ s_wasForked = true;
+}
+
+void setupForkHandlers()
+{
+ int rc = ::pthread_atfork(prepareFork, atForkParent, atForkChild);
+ if (rc != 0)
+ LOG_ERROR(systemError(errno, ERROR_LOCATION));
+}
+#else
+void setupForkHandlers()
+{
+
+}
+#endif
+
+void polledEventHandler()
+{
+ // if R is getting called after a fork this is likely multicore or
+ // some other parallel computing package that uses fork. in this
+ // case be defensive by shutting down as many things as we can
+ // which might cause mischief in the child process
+ if (s_wasForked)
+ {
+ // no more polled events
+ r::session::event_loop::permanentlyDisablePolledEventHandler();
+
+ // done
+ return;
+ }
+
+ // static lastPerformed value used for throttling
+ using namespace boost::posix_time;
+ static ptime s_lastPerformed;
+ if (s_lastPerformed.is_not_a_date_time())
+ s_lastPerformed = microsec_clock::universal_time();
+
+ // throttle to no more than once every 50ms
+ static time_duration s_intervalMs = milliseconds(50);
+ if (microsec_clock::universal_time() <= (s_lastPerformed + s_intervalMs))
+ return;
+
+ // notify modules
+ module_context::onBackgroundProcessing(false);
+
+ // set last performed (should be set after calling onBackgroundProcessing so
+ // that long running background processing handlers can't overflow the 50ms
+ // interval between background processing invocations)
+ s_lastPerformed = microsec_clock::universal_time();
+
+ // check for a pending connections only while R is processing
+ // (otherwise we'll handle them directly in waitForMethod)
+ if (s_rProcessingInput)
+ {
+ // check the uri of the next connection
+ std::string nextConnectionUri =
+ httpConnectionListener().mainConnectionQueue().peekNextConnectionUri();
+
+ // if the uri is empty or if it one of our special waitForMethod calls
+ // then bails so that the waitForMethod logic can handle it
+ if (nextConnectionUri.empty() || isWaitForMethodUri(nextConnectionUri))
+ return;
+
+ // attempt to deque a connection and handle it. for now we just handle
+ // a single connection at a time (we'll be called back again if processing
+ // continues)
+ boost::shared_ptr<HttpConnection> ptrConnection =
+ httpConnectionListener().mainConnectionQueue().dequeConnection();
+ if (ptrConnection)
+ {
+ if ( isMethod(ptrConnection, kClientInit) )
+ {
+ // client_init means the user is attempting to reload the browser
+ // in the middle of a computation. process client_init and post
+ // a busy event as our initFunction
+ using namespace session::module_context;
+ ClientEvent busyEvent(client_events::kBusy, true);
+ handleClientInit(boost::bind(enqueClientEvent, busyEvent),
+ ptrConnection);
+ }
+ else
+ {
+ handleConnection(ptrConnection, BackgroundConnection);
+ }
+ }
+ }
+}
+
+bool suspendSession(bool force)
+{
+ // need to make sure the global environment is loaded before we
+ // attemmpt to save it!
+ r::session::ensureDeserialized();
+
+ // perform the suspend (does not return if successful)
+ return r::session::suspend(force);
+}
+
+void suspendIfRequested(const boost::function<bool()>& allowSuspend)
+{
+ // never suspend in desktop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ return;
+
+ // check for forced suspend request
+ if (s_forceSuspend)
+ {
+ // reset flag (if for any reason we fail we don't want to keep
+ // hammering away on the failure case)
+ s_forceSuspend = false;
+
+ // did this force suspend interrupt R?
+ if (s_forceSuspendInterruptedR)
+ {
+ // reset flag
+ s_forceSuspendInterruptedR = false;
+
+ // notify user
+ r::session::reportAndLogWarning(
+ "Session forced to suspend due to system upgrade, restart, maintenance, "
+ "or other issue. Your session data was saved however running "
+ "computations may have been interrupted.");
+ }
+
+ // execute the forced suspend (does not return)
+ suspendSession(true);
+ }
+
+ // cooperative suspend request
+ else if (s_suspendRequested && allowSuspend())
+ {
+ // reset flag (if for any reason we fail we don't want to keep
+ // hammering away on the failure case)
+ s_suspendRequested = false;
+
+ // attempt suspend -- if this succeeds it doesn't return; if it fails
+ // errors will be logged/reported internally and we will move on
+ suspendSession(false);
+ }
+}
+
+bool haveRunningChildren()
+{
+ return module_context::processSupervisor().hasRunningChildren() ||
+ modules::authoring::hasRunningChildren();
+}
+
+bool canSuspend(const std::string& prompt)
+{
+ return !haveRunningChildren() && r::session::isSuspendable(prompt);
+}
+
+
+bool isTimedOut(const boost::posix_time::ptime& timeoutTime)
+{
+ using namespace boost::posix_time;
+
+ // never time out in desktop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ return false;
+
+ // check for an client disconnection based timeout
+ int disconnectedTimeoutMinutes = options().disconnectedTimeoutMinutes();
+ if (disconnectedTimeoutMinutes > 0)
+ {
+ ptime lastEventConnection =
+ httpConnectionListener().eventsConnectionQueue().lastConnectionTime();
+ if (!lastEventConnection.is_not_a_date_time())
+ {
+ if ( (lastEventConnection + minutes(disconnectedTimeoutMinutes)
+ < second_clock::universal_time()) )
+ {
+ return true;
+ }
+ }
+ }
+
+ // check for a foreground inactivity based timeout
+ if (timeoutTime.is_not_a_date_time())
+ return false;
+ else
+ return second_clock::universal_time() > timeoutTime;
+}
+
+boost::posix_time::ptime timeoutTimeFromNow()
+{
+ int timeoutMinutes = session::options().timeoutMinutes();
+ if (timeoutMinutes > 0)
+ {
+ return boost::posix_time::second_clock::universal_time() +
+ boost::posix_time::minutes(session::options().timeoutMinutes());
+ }
+ else
+ {
+ return boost::posix_time::ptime(boost::posix_time::not_a_date_time);
+ }
+}
+
+void processDesktopGuiEvents()
+{
+ // keep R gui alive when we are in destkop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ // execute safely since this can call arbitrary R code (and
+ // (can also cause jump_to_top if an interrupt is pending)
+ Error error = r::exec::executeSafely(
+ r::session::event_loop::processEvents);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+
+// wait for the specified method. will either:
+// - return true and the method request in pRequest
+// - return false indicating failure (e.g. called after fork in child)
+// - suspend or quit the process
+// exit the process as a result of suspend or quit)
+bool waitForMethod(const std::string& method,
+ const boost::function<void()>& initFunction,
+ const boost::function<bool()>& allowSuspend,
+ core::json::JsonRpcRequest* pRequest)
+{
+ if (s_wasForked)
+ {
+ LOG_ERROR_MESSAGE("Waiting for method " + method + " after fork");
+ return false;
+ }
+
+ // establish timeouts
+ boost::posix_time::ptime timeoutTime = timeoutTimeFromNow();
+ boost::posix_time::time_duration connectionQueueTimeout =
+ boost::posix_time::milliseconds(50);
+
+ // wait until we get the method we are looking for
+ while(true)
+ {
+ // suspend if necessary (does not return if a suspend occurs)
+ suspendIfRequested(allowSuspend);
+
+ // check for timeout
+ if ( isTimedOut(timeoutTime) )
+ {
+ if (allowSuspend())
+ {
+ // note that we timed out
+ s_suspendedFromTimeout = true;
+
+ // attempt to suspend (does not return if it succeeds)
+ if ( !suspendSession(false) )
+ {
+ // reset timeout flag
+ s_suspendedFromTimeout = false;
+
+ // if it fails then reset the timeout timer so we don't keep
+ // hammering away on the failure case
+ timeoutTime = timeoutTimeFromNow();
+ }
+ }
+ }
+
+ // if we have at least one async process running then this counts
+ // as "activity" and resets the timeout timer
+ if(haveRunningChildren())
+ timeoutTime = timeoutTimeFromNow();
+
+ // look for a connection (waiting for the specified interval)
+ boost::shared_ptr<HttpConnection> ptrConnection =
+ httpConnectionListener().mainConnectionQueue().dequeConnection(
+ connectionQueueTimeout);
+
+
+ // perform background processing (true for isIdle)
+ module_context::onBackgroundProcessing(true);
+
+ // process pending events in desktop mode
+ processDesktopGuiEvents();
+
+ if (ptrConnection)
+ {
+ // check for client_init
+ if ( isMethod(ptrConnection, kClientInit) )
+ {
+ handleClientInit(initFunction, ptrConnection);
+ }
+
+ // check for the method we are waiting on
+ else if ( isMethod(ptrConnection, method) )
+ {
+ // parse and validate request then proceed
+ if (parseAndValidateJsonRpcConnection(ptrConnection, pRequest))
+ {
+ // respond to the method
+ ptrConnection->sendJsonRpcResponse();
+
+ // ensure initialized
+ ensureSessionInitialized();
+
+ break; // got the method, we are out of here!
+ }
+ }
+
+ // another connection type, dispatch it
+ else
+ {
+ handleConnection(ptrConnection, ForegroundConnection);
+ }
+
+ // since we got a connection we can reset the timeout time
+ timeoutTime = timeoutTimeFromNow();
+
+ // after we've processed at least one waitForMethod it is now safe to
+ // initialize the polledEventHandler (which is used to maintain rsession
+ // responsiveness even when R is executing code received at the console).
+ // we defer this to make sure that the FIRST request is always handled
+ // by the logic above. if we didn't do this then client_init or
+ // console_input (often the first request) could go directly to
+ // handleConnection which wouldn't know what to do with them
+ if (!r::session::event_loop::polledEventHandlerInitialized())
+ r::session::event_loop::initializePolledEventHandler(
+ polledEventHandler);
+ }
+ }
+
+ // satisfied the request
+ return true;
+}
+
+
+// wait for the specified method (will either return the method or
+// exit the process as a result of suspend or quit)
+bool waitForMethod(const std::string& method,
+ const ClientEvent& initEvent,
+ const boost::function<bool()>& allowSuspend,
+ core::json::JsonRpcRequest* pRequest)
+{
+ return waitForMethod(method,
+ boost::bind(module_context::enqueClientEvent,
+ initEvent),
+ allowSuspend,
+ pRequest);
+}
+
+// forward declare convenience wait for method init function which
+// enques the specified event and then issues either the last console
+// prompt or a busy event
+void waitForMethodInitFunction(const ClientEvent& initEvent);
+
+void addToConsoleInputBuffer(const r::session::RConsoleInput& consoleInput)
+{
+ if (consoleInput.cancel || consoleInput.text.find('\n') == std::string::npos)
+ {
+ s_consoleInputBuffer.push(consoleInput);
+ return;
+ }
+
+ // split input into list of commands
+ boost::char_separator<char> lineSep("\n", "", boost::keep_empty_tokens);
+ boost::tokenizer<boost::char_separator<char> > lines(consoleInput.text, lineSep);
+ for (boost::tokenizer<boost::char_separator<char> >::iterator
+ lineIter = lines.begin();
+ lineIter != lines.end();
+ ++lineIter)
+ {
+ // get line
+ std::string line(*lineIter);
+
+ // add to buffer
+ s_consoleInputBuffer.push(line);
+ }
+}
+
+// extract console input -- can be either null (user hit escape) or a string
+Error extractConsoleInput(const json::JsonRpcRequest& request)
+{
+ if (request.params.size() == 1)
+ {
+ if (request.params[0].is_null())
+ {
+ addToConsoleInputBuffer(r::session::RConsoleInput());
+ return Success();
+ }
+ else if (request.params[0].type() == json::StringType)
+ {
+ // get console input to return to R
+ std::string text = request.params[0].get_str();
+ addToConsoleInputBuffer(r::session::RConsoleInput(text));
+
+ // return success
+ return Success();
+ }
+ else
+ {
+ return Error(json::errc::ParamTypeMismatch, ERROR_LOCATION);
+ }
+ }
+ else
+ {
+ return Error(json::errc::ParamMissing, ERROR_LOCATION);
+ }
+}
+
+// allow console_input requests to come in when we aren't explicitly waiting
+// on them (i.e. waitForMethod("console_input")). place them into into a buffer
+// which is then checked by rConsoleRead prior to it calling waitForMethod
+Error bufferConsoleInput(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // extract the input
+ return extractConsoleInput(request);
+}
+
+
+void doSuspendForRestart(const r::session::RSuspendOptions& options)
+{
+ module_context::consoleWriteOutput("\nRestarting R session...\n\n");
+
+ r::session::suspendForRestart(options);
+}
+
+Error suspendForRestart(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::RSuspendOptions options;
+ Error error = json::readObjectParam(
+ request.params, 0,
+ "save_minimal", &(options.saveMinimal),
+ "save_workspace", &(options.saveWorkspace),
+ "exclude_packages", &(options.excludePackages));
+ if (error)
+ return error;
+
+ pResponse->setAfterResponse(boost::bind(doSuspendForRestart, options));
+ return Success();
+}
+
+
+Error ping(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ return Success();
+}
+
+
+Error startHttpConnectionListener()
+{
+ initializeHttpConnectionListener();
+ return httpConnectionListener().start();
+}
+
+Error startClientEventService()
+{
+ return clientEventService().start(session::persistentState().activeClientId());
+}
+
+void registerGwtHandlers()
+{
+ // alias options
+ session::Options& options = session::options();
+
+ // establish logging handler
+ module_context::registerUriHandler(
+ "/log",
+ boost::bind(gwt::handleLogRequest, options.userIdentity(), _1, _2));
+
+ // establish progress handler
+ FilePath wwwPath(options.wwwLocalPath());
+ FilePath progressPagePath = wwwPath.complete("progress.htm");
+ module_context::registerUriHandler(
+ "/progress",
+ boost::bind(text::handleTemplateRequest, progressPagePath, _1, _2));
+
+ // initialize gwt symbol maps
+ gwt::initializeSymbolMaps(options.wwwSymbolMapsPath());
+
+ // set default handler
+ s_defaultUriHandler = gwt::fileHandlerFunction(options.wwwLocalPath(), "/");
+}
+
+Error registerSignalHandlers()
+{
+ using boost::bind;
+ using namespace core::system;
+
+ // USR1 and USR2: perform suspend in server mode
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ ExecBlock registerBlock ;
+ registerBlock.addFunctions()
+ (bind(handleSignal, SigUsr1, handleUSR1))
+ (bind(handleSignal, SigUsr2, handleUSR2));
+ return registerBlock.execute();
+ }
+ // USR1 and USR2: ignore in desktop mode
+ else
+ {
+ ExecBlock registerBlock ;
+ registerBlock.addFunctions()
+ (bind(ignoreSignal, SigUsr1))
+ (bind(ignoreSignal, SigUsr2));
+ return registerBlock.execute();
+ }
+}
+
+
+Error runPreflightScript()
+{
+ // alias options
+ Options& options = session::options();
+
+ // run the preflight script (if specified)
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ FilePath preflightScriptPath = options.preflightScriptPath();
+ if (!preflightScriptPath.empty())
+ {
+ if (preflightScriptPath.exists())
+ {
+ // run the script (ignore errors and continue no matter what
+ // the outcome of the script is)
+ std::string script = preflightScriptPath.absolutePath();
+ core::system::ProcessResult result;
+ Error error = runCommand(script,
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ {
+ error.addProperty("preflight-script", script);
+ LOG_ERROR(error);
+ }
+ }
+ else
+ {
+ LOG_WARNING_MESSAGE("preflight script does not exist: " +
+ preflightScriptPath.absolutePath());
+ }
+ }
+ }
+
+ // always return success
+ return Success();
+}
+
+Error rInit(const r::session::RInitInfo& rInitInfo)
+{
+ // save state we need to reference later
+ s_rSessionResumed = rInitInfo.resumed;
+
+ // record built-in waitForMethod handlers
+ s_waitForMethodNames.push_back(kLocatorCompleted);
+ s_waitForMethodNames.push_back(kEditCompleted);
+ s_waitForMethodNames.push_back(kChooseFileCompleted);
+ s_waitForMethodNames.push_back(kHandleUnsavedChangesCompleted);
+
+ // execute core initialization functions
+ using boost::bind;
+ using namespace core::system;
+ using namespace session::module_context;
+ ExecBlock initialize ;
+ initialize.addFunctions()
+
+ // client event service
+ (startClientEventService)
+
+ // json-rpc listeners
+ (bind(registerRpcMethod, kConsoleInput, bufferConsoleInput))
+ (bind(registerRpcMethod, "suspend_for_restart", suspendForRestart))
+ (bind(registerRpcMethod, "ping", ping))
+
+ // signal handlers
+ (registerSignalHandlers)
+
+ // main module context
+ (module_context::initialize)
+
+ // projects (early project init required -- module inits below
+ // can then depend on e.g. computed defaultEncoding)
+ (projects::initialize)
+
+ // source database
+ (source_database::initialize)
+
+ // content urls
+ (content_urls::initialize)
+
+ // overlay R
+ (bind(sourceModuleRFile, "SessionOverlay.R"))
+
+ // addins
+ (addins::initialize)
+
+ // modules with c++ implementations
+ (modules::spelling::initialize)
+ (modules::lists::initialize)
+ (modules::path::initialize)
+ (modules::limits::initialize)
+ (modules::ask_pass::initialize)
+ (modules::agreement::initialize)
+ (modules::console::initialize)
+ (modules::console_process::initialize)
+#ifdef RSTUDIO_SERVER
+ (modules::crypto::initialize)
+#endif
+ (modules::files::initialize)
+ (modules::find::initialize)
+ (modules::environment::initialize)
+ (modules::dirty::initialize)
+ (modules::workbench::initialize)
+ (modules::data::initialize)
+ (modules::help::initialize)
+ (modules::presentation::initialize)
+ (modules::plots::initialize)
+ (modules::packages::initialize)
+ (modules::profiler::initialize)
+ (modules::viewer::initialize)
+ (modules::rmarkdown::initialize)
+ (modules::rpubs::initialize)
+ (modules::shiny::initialize)
+ (modules::source::initialize)
+ (modules::source_control::initialize)
+ (modules::authoring::initialize)
+ (modules::html_preview::initialize)
+ (modules::history::initialize)
+ (modules::code_search::initialize)
+ (modules::build::initialize)
+ (modules::overlay::initialize)
+ (modules::breakpoints::initialize)
+ (modules::errors::initialize)
+ (modules::updates::initialize)
+ (modules::about::initialize)
+ (modules::shiny_viewer::initialize)
+ (modules::shiny_apps::initialize)
+
+ // workers
+ (workers::web_request::initialize)
+
+ // R code
+ (bind(sourceModuleRFile, "SessionCodeTools.R"))
+
+ // unsupported functions
+ (bind(r::function_hook::registerUnsupported, "bug.report", "utils"))
+ (bind(r::function_hook::registerUnsupported, "help.request", "utils"))
+ ;
+
+ Error error = initialize.execute();
+ if (error)
+ return error;
+
+ // if we are in verify installation mode then we should exit (successfully) now
+ if (session::options().verifyInstallation())
+ {
+ // in desktop mode we write a success message and execute diagnostics
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ std::cout << "Successfully initialized R session."
+ << std::endl << std::endl;
+ FilePath diagFile = module_context::sourceDiagnostics();
+ if (!diagFile.empty())
+ {
+ std::cout << "Diagnostics report written to: "
+ << diagFile << std::endl << std::endl;
+
+ Error error = r::exec::RFunction(".rs.showDiagnostics").call();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ session::options().verifyInstallationHomeDir().removeIfExists();
+ ::exit(EXIT_SUCCESS);
+ }
+
+ // register all of the json rpc methods implemented in R
+ json::JsonRpcMethods rMethods ;
+ error = r::json::getRpcMethods(&rMethods);
+ if (error)
+ return error ;
+ BOOST_FOREACH(const json::JsonRpcMethod& method, rMethods)
+ {
+ s_jsonRpcMethods.insert(json::adaptMethodToAsync(method));
+ }
+
+ // add gwt handlers if we are running desktop mode
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ registerGwtHandlers();
+
+ // enque abend warning event if necessary.
+ using namespace session::client_events;
+ if (session::persistentState().hadAbend())
+ {
+ LOG_ERROR_MESSAGE("session hadabend");
+
+ ClientEvent abendWarningEvent(kAbendWarning);
+ session::clientEventQueue().add(abendWarningEvent);
+ }
+
+ if (s_printCharsetWarning)
+ r::exec::warning("Character set is not UTF-8; please change your locale");
+
+ // propagate console history options
+ r::session::consoleHistory().setRemoveDuplicates(
+ userSettings().removeHistoryDuplicates());
+
+
+ // register function editor on windows
+#ifdef _WIN32
+ error = r::exec::RFunction(".rs.registerFunctionEditor").call();
+ if (error)
+ LOG_ERROR(error);
+#endif
+
+ // set flag indicating we had an abnormal end (if this doesn't get
+ // unset by the time we launch again then we didn't terminate normally
+ // i.e. either the process dying unexpectedly or a call to R_Suicide)
+ session::persistentState().setAbend(true);
+
+ // setup fork handlers
+ setupForkHandlers();
+
+ // success!
+ return Success();
+}
+
+void rDeferredInit(bool newSession)
+{
+ module_context::events().onDeferredInit(newSession);
+
+ // fire an event to the client
+ ClientEvent event(client_events::kDeferredInitCompleted);
+ module_context::enqueClientEvent(event);
+}
+
+void consolePrompt(const std::string& prompt, bool addToHistory)
+{
+ // save the last prompt (for re-issuing)
+ s_lastPrompt = prompt;
+
+ // enque the event
+ json::Object data ;
+ data["prompt"] = prompt ;
+ data["history"] = addToHistory;
+ bool isDefaultPrompt = prompt == r::options::getOption<std::string>("prompt");
+ data["default"] = isDefaultPrompt;
+ ClientEvent consolePromptEvent(client_events::kConsolePrompt, data);
+ session::clientEventQueue().add(consolePromptEvent);
+
+ // allow modules to detect changes after execution of previous REPL
+ detectChanges(module_context::ChangeSourceREPL);
+
+ // call prompt hook
+ module_context::events().onConsolePrompt(prompt);
+}
+
+void reissueLastConsolePrompt()
+{
+ consolePrompt(s_lastPrompt, false);
+}
+
+bool rConsoleRead(const std::string& prompt,
+ bool addToHistory,
+ r::session::RConsoleInput* pConsoleInput)
+{
+ // this is an invalid state in a forked (multicore) process
+ if (s_wasForked)
+ {
+ LOG_WARNING_MESSAGE("rConsoleRead called in forked processs");
+ return false;
+ }
+
+ // r is not processing input
+ s_rProcessingInput = false;
+
+ if (!s_consoleInputBuffer.empty())
+ {
+ *pConsoleInput = s_consoleInputBuffer.front();
+ s_consoleInputBuffer.pop();
+ }
+ // otherwise prompt and wait for console_input from the client
+ else
+ {
+ // fire console_prompt event (unless we are just starting up, in which
+ // case we will either prompt as part of the response to client_init or
+ // we shouldn't prompt at all because we are resuming a suspended session)
+ if (s_sessionInitialized)
+ consolePrompt(prompt, addToHistory);
+
+ // wait for console_input
+ json::JsonRpcRequest request ;
+ bool succeeded = waitForMethod(
+ kConsoleInput,
+ boost::bind(consolePrompt, prompt, addToHistory),
+ boost::bind(canSuspend, prompt),
+ &request);
+
+ // exit process if we failed
+ if (!succeeded)
+ return false;
+
+ // extract console input. if there is an error during extraction we log it
+ // but still return and empty string and true (returning false will cause R
+ // to abort)
+ Error error = extractConsoleInput(request);
+ if (error)
+ {
+ LOG_ERROR(error);
+ *pConsoleInput = r::session::RConsoleInput("");
+ }
+ *pConsoleInput = s_consoleInputBuffer.front();
+ s_consoleInputBuffer.pop();
+ }
+
+ // fire onBeforeExecute and onConsoleInput events if this isn't a cancel
+ if (!pConsoleInput->cancel)
+ {
+ module_context::events().onBeforeExecute();
+ module_context::events().onConsoleInput(pConsoleInput->text);
+ }
+
+ // we are about to return input to r so set the flag indicating that state
+ s_rProcessingInput = true;
+
+ ClientEvent promptEvent(kConsoleWritePrompt, prompt);
+ session::clientEventQueue().add(promptEvent);
+ ClientEvent inputEvent(kConsoleWriteInput, pConsoleInput->text + "\n");
+ session::clientEventQueue().add(inputEvent);
+
+ // always return true (returning false causes the process to exit)
+ return true;
+}
+
+
+int rEditFile(const std::string& file)
+{
+ // read file contents
+ FilePath filePath(file);
+ std::string fileContents;
+ Error readError = core::readStringFromFile(filePath, &fileContents);
+ if (readError)
+ {
+ LOG_ERROR(readError);
+ return 1; // r will raise/report an error indicating edit failed
+ }
+
+ // fire edit event
+ ClientEvent editEvent = session::showEditorEvent(fileContents, true, false);
+ session::clientEventQueue().add(editEvent);
+
+ // wait for edit_completed
+ json::JsonRpcRequest request ;
+ bool succeeded = waitForMethod(kEditCompleted,
+ editEvent,
+ disallowSuspend,
+ &request);
+
+ if (!succeeded)
+ return false;
+
+ // user cancelled edit
+ if (request.params[0].is_null())
+ {
+ return 0; // no-op, object will be re-parsed from original content
+ }
+
+ // user confirmed edit
+ else
+ {
+ // extract the content
+ std::string editedFileContents;
+ Error error = json::readParam(request.params, 0, &editedFileContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return 1; // error (r will notify user via the console)
+ }
+
+ // write the content back to the file (append newline expected by R)
+ editedFileContents += "\n";
+ Error writeError = core::writeStringToFile(filePath, editedFileContents);
+ if (writeError)
+ {
+ LOG_ERROR(writeError);
+ return 1 ; // error (r will notify user via the console)
+ }
+
+ // success!
+ return 0 ;
+ }
+}
+
+
+FilePath rChooseFile(bool newFile)
+{
+ // fire choose file event
+ ClientEvent chooseFileEvent(kChooseFile, newFile);
+ session::clientEventQueue().add(chooseFileEvent);
+
+ // wait for choose_file_completed
+ json::JsonRpcRequest request ;
+ bool succeeded = waitForMethod(kChooseFileCompleted,
+ chooseFileEvent,
+ disallowSuspend,
+ &request);
+
+ if (!succeeded)
+ return FilePath();
+
+ // extract the file name
+ std::string fileName;
+ if (!request.params[0].is_null())
+ {
+ Error error = json::readParam(request.params, 0, &fileName);
+ if (error)
+ LOG_ERROR(error);
+
+ // resolve aliases and return it
+ return module_context::resolveAliasedPath(fileName);
+ }
+ else
+ {
+ return FilePath();
+ }
+}
+
+
+void rBusy(bool busy)
+{
+ if (s_wasForked)
+ return;
+
+ // screen out busy = true events that occur when R isn't busy
+ if (busy && !s_rProcessingInput)
+ return;
+
+ ClientEvent busyEvent(kBusy, busy);
+ session::clientEventQueue().add(busyEvent);
+}
+
+void rConsoleWrite(const std::string& output, int otype)
+{
+ if (s_wasForked)
+ return;
+
+ int event = otype == 1 ? kConsoleWriteError : kConsoleWriteOutput;
+ ClientEvent writeEvent(event, output);
+ session::clientEventQueue().add(writeEvent);
+
+ // fire event
+ module_context::events().onConsoleOutput(
+ otype == 1 ? module_context::ConsoleOutputError :
+ module_context::ConsoleOutputNormal,
+ output);
+
+}
+
+void rConsoleHistoryReset()
+{
+ json::Array historyJson;
+ r::session::consoleHistory().asJson(&historyJson);
+ json::Object resetJson;
+ resetJson["history"] = historyJson;
+ resetJson["preserve_ui_context"] = false;
+ ClientEvent event(kConsoleResetHistory, resetJson);
+ session::clientEventQueue().add(event);
+}
+
+bool rLocator(double* x, double* y)
+{
+ // since locator can be called in a loop we need to checkForChanges
+ // here (because we'll never get back to the REPL). this enables
+ // identify() to correctly update the plot after each click
+ detectChanges(module_context::ChangeSourceREPL);
+
+ // fire locator event
+ ClientEvent locatorEvent(kLocator);
+ session::clientEventQueue().add(locatorEvent);
+
+ // wait for locator_completed
+ json::JsonRpcRequest request ;
+ bool succeeded = waitForMethod(kLocatorCompleted,
+ locatorEvent,
+ disallowSuspend,
+ &request);
+
+ if (!succeeded)
+ return false;
+
+ // see if we got a point
+ if ((request.params.size() > 0) && !request.params[0].is_null())
+ {
+ // read the x and y
+ Error error = json::readObjectParam(request.params, 0,
+ "x", x,
+ "y", y);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // return true
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void rShowFile(const std::string& title, const FilePath& filePath, bool del)
+{
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ // for files in the user's home directory and pdfs use an external browser
+ if (module_context::isVisibleUserFile(filePath) ||
+ (filePath.extensionLowerCase() == ".pdf"))
+ {
+ module_context::showFile(filePath);
+ }
+ else
+ {
+ module_context::showContent(title, filePath);
+ }
+ }
+ else // (session::options().programMode() == kSessionProgramModeDesktop
+ {
+#ifdef _WIN32
+ if (!filePath.extension().empty())
+ {
+ module_context::showFile(filePath);
+ del = false;
+ }
+ else
+ {
+ module_context::showContent(title, filePath);
+ }
+#else
+ module_context::showContent(title, filePath);
+#endif
+ }
+
+ if (del)
+ {
+ Error error = filePath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void rBrowseURL(const std::string& url)
+{
+ // first see if any of our handlers want to take it
+ for (std::vector<module_context::RBrowseUrlHandler>::const_iterator
+ it = s_rBrowseUrlHandlers.begin();
+ it != s_rBrowseUrlHandlers.end();
+ ++it)
+ {
+ if ((*it)(url))
+ return;
+ }
+
+ // raise event to client
+ session::clientEventQueue().add(browseUrlEvent(url));
+}
+
+void rBrowseFile(const core::FilePath& filePath)
+{
+ // see if any of our handlers want to take it
+ for (std::vector<module_context::RBrowseFileHandler>::const_iterator
+ it = s_rBrowseFileHandlers.begin();
+ it != s_rBrowseFileHandlers.end();
+ ++it)
+ {
+ if ((*it)(filePath))
+ return;
+ }
+
+ // no handlers took it, send along to default
+ module_context::showFile(filePath);
+}
+
+void rShowHelp(const std::string& helpURL)
+{
+ ClientEvent showHelpEvent(kShowHelp, helpURL);
+ session::clientEventQueue().add(showHelpEvent);
+}
+
+void rShowMessage(const std::string& message)
+{
+ ClientEvent event = showErrorMessageEvent("R Error", message);
+ session::clientEventQueue().add(event);
+}
+
+void logExitEvent(const monitor::Event& precipitatingEvent)
+{
+ using namespace monitor;
+ client().logEvent(precipitatingEvent);
+ client().logEvent(Event(kSessionScope, kSessionExitEvent));
+}
+
+void rSuspended(const r::session::RSuspendOptions& options)
+{
+ // log to monitor
+ using namespace monitor;
+ std::string data;
+ if (s_suspendedFromTimeout)
+ data = safe_convert::numberToString(session::options().timeoutMinutes());
+ logExitEvent(Event(kSessionScope, kSessionSuspendEvent, data));
+
+ // fire event
+ module_context::onSuspended(options, &(persistentState().settings()));
+}
+
+void rResumed()
+{
+ module_context::onResumed(persistentState().settings());
+}
+
+bool rHandleUnsavedChanges()
+{
+ // enque the event
+ ClientEvent event(client_events::kHandleUnsavedChanges);
+ module_context::enqueClientEvent(event);
+
+ // wait for method
+ json::JsonRpcRequest request;
+ bool succeeded = waitForMethod(
+ kHandleUnsavedChangesCompleted,
+ boost::bind(waitForMethodInitFunction, event),
+ disallowSuspend,
+ &request);
+
+ if (!succeeded)
+ return false;
+
+ // read response and return it
+ bool handled = false;
+ Error error = json::readParam(request.params, 0, &handled);
+ if (error)
+ LOG_ERROR(error);
+ return handled;
+}
+
+void rQuit()
+{
+ if (s_wasForked)
+ return;
+
+ // log to monitor
+ using namespace monitor;
+ logExitEvent(Event(kSessionScope, kSessionQuitEvent));
+
+ // notify modules
+ module_context::events().onQuit();
+
+ // enque a quit event
+ bool switchProjects =
+ !session::projects::projectContext().nextSessionProject().empty();
+ ClientEvent quitEvent(kQuit, switchProjects);
+ session::clientEventQueue().add(quitEvent);
+}
+
+// NOTE: this event is never received on windows (because we can't
+// override suicide on windows)
+void rSuicide(const std::string& message)
+{
+ if (s_wasForked)
+ return;
+
+ // log to monitor
+ using namespace monitor;
+ logExitEvent(Event(kSessionScope, kSessionSuicideEvent));
+
+ // log the error
+ LOG_ERROR_MESSAGE("R SUICIDE: " + message);
+
+ // enque suicide event so the client knows
+ ClientEvent suicideEvent(kSuicide, message);
+ session::clientEventQueue().add(suicideEvent);
+}
+
+// terminate all children of the provided process supervisor
+// and then wait a brief period to attempt to reap the child
+void terminateAllChildren(core::system::ProcessSupervisor* pSupervisor,
+ const ErrorLocation& location)
+{
+ // send kill signal
+ pSupervisor->terminateAll();
+
+ // wait and reap children (but for no longer than 1 second)
+ if (!pSupervisor->wait(boost::posix_time::milliseconds(10),
+ boost::posix_time::milliseconds(1000)))
+ {
+ core::log::logWarningMessage(
+ "Process supervisor did not terminate within 1 second",
+ location);
+ }
+}
+
+void rCleanup(bool terminatedNormally)
+{
+ try
+ {
+ // bail completely if we were forked
+ if (s_wasForked)
+ return;
+
+ // note that we didn't abend
+ if (terminatedNormally)
+ session::persistentState().setAbend(false);
+
+ // fire shutdown event to modules
+ module_context::events().onShutdown(terminatedNormally);
+
+ // cause graceful exit of clientEventService (ensures delivery
+ // of any pending events prior to process termination). wait a
+ // very brief interval first to allow the quit or other termination
+ // related events to get into the queue
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ // only stop the http services if we are in server mode. in desktop
+ // mode we had issues with both OSX crashing and with Windows taking
+ // the full 3 seconds to terminate. the cleanup is kind of a nice
+ // to have and most important on the server where we delete the
+ // unix domain socket file so it is no big deal to bypass it
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ clientEventService().stop();
+ httpConnectionListener().stop();
+ }
+
+ // terminate known child processes
+ terminateAllChildren(&module_context::processSupervisor(),
+ ERROR_LOCATION);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+}
+
+void rSerialization(int action, const FilePath& targetPath)
+{
+ json::Object serializationActionObject ;
+ serializationActionObject["type"] = action;
+ if (!targetPath.empty())
+ {
+ serializationActionObject["targetPath"] =
+ module_context::createAliasedPath(targetPath);
+ }
+
+ ClientEvent event(kSessionSerialization, serializationActionObject);
+ session::clientEventQueue().add(event);
+}
+
+
+void ensureRProfile()
+{
+ Options& options = session::options();
+ FilePath rProfilePath = options.userHomePath().complete(".Rprofile");
+ if (!rProfilePath.exists() && !userSettings().autoCreatedProfile())
+ {
+ userSettings().setAutoCreatedProfile(true);
+
+ std::string p;
+ p = "# .Rprofile -- commands to execute at the beginning of each R session\n"
+ "#\n"
+ "# You can use this file to load packages, set options, etc.\n"
+ "#\n"
+ "# NOTE: changes in this file won't be reflected until after you quit\n"
+ "# and start a new session\n"
+ "#\n\n";
+
+ Error error = writeStringToFile(rProfilePath, p);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void ensurePublicFolder()
+{
+ // check if we need to create the public folder (bail if we don't)
+ Options& options = session::options();
+ if (!options.createPublicFolder())
+ return;
+
+ FilePath publicPath = options.userHomePath().complete("Public");
+ if (!publicPath.exists())
+ {
+ // create directory
+ Error error = publicPath.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // write notice
+ boost::format fmt(
+ "\n"
+ "Files within your public folder are readable (but not writeable)\n"
+ "by others. The path for other users to access your public folder is:\n"
+ "\n"
+ " /shared/%1%/\n"
+ "\n"
+ "For example, to source a file named \"Utils.R\" from your public\n"
+ "folder another user would enter the command:\n"
+ "\n"
+ " source(\"/shared/%1%/Utils.R\")\n"
+ "\n"
+ "To load a dataset named \"Data.csv\" they would enter the command:\n"
+ "\n"
+ " read.csv(\"/shared/%1%/Data.csv\")\n"
+ "\n"
+ "Other users can also browse and open the files available in your\n"
+ "public folder by:\n"
+ "\n"
+ " 1) Selecting the Open File... menu item\n"
+ " 2) Entering /shared/%1%/ as the file name\n"
+ " 3) Clicking the Open button (or pressing the Enter key)\n"
+ "\n"
+ );
+ std::string notice = boost::str(fmt % options.userIdentity());
+
+ FilePath noticePath = publicPath.complete("AboutPublic.txt");
+ error = writeStringToFile(noticePath, notice);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void ensureRLibsUser(const core::FilePath& userHomePath,
+ const std::string& rLibsUser)
+{
+ FilePath rLibsUserPath = FilePath::resolveAliasedPath(rLibsUser,
+ userHomePath);
+ Error error = rLibsUserPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+}
+
+#ifdef __APPLE__
+// we now launch our child processes from the desktop using our standard
+// process management code which closes all file descriptors thereby
+// breaking parent_process_monitor. So on the Mac we use the more simplistic
+// approach of polling for ppid == 1. This is fine because we expect that
+// the Desktop will _always_ outlive us (it waits for us to exit before
+// closing) so anytime it exits before we do it must be a crash). we don't
+// call abort() however because we don't want a crash report to occur
+void detectParentTermination()
+{
+ while(true)
+ {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(500));
+ if (::getppid() == 1)
+ {
+ ::exit(EXIT_FAILURE);
+ }
+ }
+}
+#else
+void detectParentTermination()
+{
+ using namespace parent_process_monitor;
+ ParentTermination result = waitForParentTermination();
+ if (result == ParentTerminationAbnormal)
+ {
+ LOG_ERROR_MESSAGE("Parent terminated");
+ core::system::abort();
+ }
+ else if (result == ParentTerminationNormal)
+ {
+ //LOG_ERROR_MESSAGE("Normal terminate");
+ }
+ else if (result == ParentTerminationWaitFailure)
+ {
+ LOG_ERROR_MESSAGE("waitForParentTermination failed");
+ }
+}
+#endif
+
+// NOTE: mirrors behavior of WorkbenchContext.getREnvironmentPath on the client
+FilePath rEnvironmentDir()
+{
+ // for projects we always use the project directory
+ if (projects::projectContext().hasProject())
+ {
+ return projects::projectContext().directory();
+ }
+
+ // for desktop the current path
+ else if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ return FilePath::safeCurrentPath(session::options().userHomePath());
+ }
+
+ // for server the initial working dir
+ else
+ {
+ return getInitialWorkingDirectory();
+ }
+}
+
+SA_TYPE saveWorkspaceOption()
+{
+ // convert from internal type to R type
+ int saveAction = module_context::saveWorkspaceAction();
+ if (saveAction == r::session::kSaveActionSave)
+ return SA_SAVE;
+ else if (saveAction == r::session::kSaveActionNoSave)
+ return SA_NOSAVE;
+ else
+ return SA_SAVEASK;
+}
+
+bool restoreWorkspaceOption()
+{
+ // allow project override
+ const projects::ProjectContext& projContext = projects::projectContext();
+ if (projContext.hasProject())
+ {
+ switch(projContext.config().restoreWorkspace)
+ {
+ case r_util::YesValue:
+ return true;
+ case r_util::NoValue:
+ return false;
+ default:
+ // fall through
+ break;
+ }
+ }
+
+ // no project override
+ return userSettings().loadRData() ||
+ !session::options().initialEnvironmentFileOverride().empty();
+}
+
+FilePath rHistoryDir()
+{
+ // for projects we always use the project directory
+ if (projects::projectContext().hasProject())
+ {
+ return projects::projectContext().directory();
+ }
+
+ // for server we use the default working directory
+ else if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ return getDefaultWorkingDirectory();
+ }
+
+ // for desktop we take the current path
+ else
+ {
+ return FilePath::safeCurrentPath(session::options().userHomePath());
+ }
+}
+
+bool alwaysSaveHistoryOption()
+{
+ // allow project override
+ const projects::ProjectContext& projContext = projects::projectContext();
+ if (projContext.hasProject())
+ {
+ switch(projContext.config().alwaysSaveHistory)
+ {
+ case r_util::YesValue:
+ return true;
+ case r_util::NoValue:
+ return false;
+ default:
+ // fall through
+ break;
+ }
+ }
+
+ return userSettings().alwaysSaveHistory();
+}
+
+FilePath getStartupEnvironmentFilePath()
+{
+ FilePath envFile = session::options().initialEnvironmentFileOverride();
+ if (!envFile.empty())
+ return envFile;
+ else
+ return rEnvironmentDir().complete(".RData");
+}
+
+void waitForMethodInitFunction(const ClientEvent& initEvent)
+{
+ module_context::enqueClientEvent(initEvent);
+
+ if (s_rProcessingInput)
+ {
+ ClientEvent busyEvent(client_events::kBusy, true);
+ module_context::enqueClientEvent(busyEvent);
+ }
+ else
+ {
+ reissueLastConsolePrompt();
+ }
+}
+
+
+} // anonymous namespace
+
+
+// provide definition methods for session::module_context
+namespace session {
+namespace module_context {
+
+Error registerRBrowseUrlHandler(const RBrowseUrlHandler& handler)
+{
+ s_rBrowseUrlHandlers.push_back(handler);
+ return Success();
+}
+
+Error registerRBrowseFileHandler(const RBrowseFileHandler& handler)
+{
+ s_rBrowseFileHandlers.push_back(handler);
+ return Success();
+}
+
+Error registerAsyncUriHandler(
+ const std::string& name,
+ const http::UriAsyncHandlerFunction& handlerFunction)
+{
+
+ s_uriHandlers.add(http::UriHandler(name,
+ handlerFunction));
+ return Success();
+}
+
+Error registerUriHandler(const std::string& name,
+ const http::UriHandlerFunction& handlerFunction)
+{
+
+ s_uriHandlers.add(http::UriHandler(name,
+ handlerFunction));
+ return Success();
+}
+
+
+Error registerAsyncLocalUriHandler(
+ const std::string& name,
+ const http::UriAsyncHandlerFunction& handlerFunction)
+{
+ s_uriHandlers.add(http::UriHandler(kLocalUriLocationPrefix + name,
+ handlerFunction));
+ return Success();
+}
+
+Error registerLocalUriHandler(const std::string& name,
+ const http::UriHandlerFunction& handlerFunction)
+{
+ s_uriHandlers.add(http::UriHandler(kLocalUriLocationPrefix + name,
+ handlerFunction));
+ return Success();
+}
+
+
+Error registerAsyncRpcMethod(const std::string& name,
+ const core::json::JsonRpcAsyncFunction& function)
+{
+ s_jsonRpcMethods.insert(
+ std::make_pair(name, std::make_pair(false, function)));
+ return Success();
+}
+
+Error registerRpcMethod(const std::string& name,
+ const core::json::JsonRpcFunction& function)
+{
+ s_jsonRpcMethods.insert(
+ std::make_pair(name,
+ std::make_pair(true, json::adaptToAsync(function))));
+ return Success();
+}
+
+bool rSessionResumed()
+{
+ return s_rSessionResumed;
+}
+
+int saveWorkspaceAction()
+{
+ // allow project override
+ const projects::ProjectContext& projContext = projects::projectContext();
+ if (projContext.hasProject())
+ {
+ switch(projContext.config().saveWorkspace)
+ {
+ case r_util::YesValue:
+ return r::session::kSaveActionSave;
+ case r_util::NoValue:
+ return r::session::kSaveActionNoSave;
+ case r_util::AskValue:
+ return r::session::kSaveActionAsk;
+ default:
+ // fall through
+ break;
+ }
+ }
+
+ // no project override, read from settings
+ return userSettings().saveAction();
+}
+
+void syncRSaveAction()
+{
+ r::session::setSaveAction(saveWorkspaceOption());
+}
+
+
+namespace {
+
+bool registeredWaitForMethod(const std::string& method,
+ const ClientEvent& event,
+ core::json::JsonRpcRequest* pRequest)
+{
+ // enque the event which notifies the client we want input
+ module_context::enqueClientEvent(event);
+
+ // wait for method
+ return waitForMethod(method,
+ boost::bind(waitForMethodInitFunction, event),
+ disallowSuspend,
+ pRequest);
+}
+
+} // anonymous namepace
+
+WaitForMethodFunction registerWaitForMethod(const std::string& methodName)
+{
+ s_waitForMethodNames.push_back(methodName);
+ return boost::bind(registeredWaitForMethod, methodName, _2, _1);
+}
+
+} // namespace module_context
+} // namespace session
+
+
+namespace {
+
+int sessionExitFailure(const core::Error& cause,
+ const core::ErrorLocation& location)
+{
+ core::log::logError(cause, location);
+ return EXIT_FAILURE;
+}
+
+std::string ctypeEnvName()
+{
+ if (!core::system::getenv("LC_ALL").empty())
+ return "LC_ALL";
+ if (!core::system::getenv("LC_CTYPE").empty())
+ return "LC_CTYPE";
+ if (!core::system::getenv("LANG").empty())
+ return "LANG";
+ return "LC_CTYPE";
+}
+
+/*
+If possible, we want to coerce the character set to UTF-8.
+We can't do this by directly calling setlocale because R
+will override those settings when it starts up. Instead we
+set the corresponding environment variables, which R will
+use.
+
+The exception is Win32, which doesn't allow UTF-8 to be used
+as an ANSI codepage.
+
+Returns false if we tried and failed to set the charset to
+UTF-8, either because we didn't recognize the locale string
+format or because the system didn't accept our new locale
+string.
+*/
+bool ensureUtf8Charset()
+{
+#if _WIN32
+ return true;
+#else
+ std::string name = ctypeEnvName();
+ std::string ctype = core::system::getenv(name);
+
+ if (boost::regex_search(ctype, boost::regex("UTF-8$")))
+ return true;
+
+#if __APPLE__
+ // For Mac, we attempt to do the fixup in DesktopMain.cpp. If it didn't
+ // work, let's not do anything rash here--just let the UTF-8 warning show.
+ return false;
+#else
+
+ std::string newCType;
+ if (ctype.empty() || ctype == "C" || ctype == "POSIX")
+ {
+ newCType = "en_US.UTF-8";
+ }
+ else
+ {
+ using namespace boost;
+
+ smatch match;
+ if (regex_match(ctype, match, regex("(\\w+_\\w+)(\\.[^@]+)?(@.+)?")))
+ {
+ // Try to replace the charset while keeping everything else the same.
+ newCType = match[1] + ".UTF-8" + match[3];
+ }
+ }
+
+ if (!newCType.empty())
+ {
+ if (setlocale(LC_CTYPE, newCType.c_str()))
+ {
+ core::system::setenv(name, newCType);
+ setlocale(LC_CTYPE, "");
+ return true;
+ }
+ }
+
+ return false;
+#endif
+#endif
+}
+
+
+} // anonymous namespace
+
+// run session
+int main (int argc, char * const argv[])
+{
+ try
+ {
+ // initialize log so we capture all errors including ones which occur
+ // reading the config file (if we are in desktop mode then the log
+ // will get re-initialized below)
+ initializeSystemLog("rsession-" + core::system::username(),
+ core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ // get main thread id (used to distinguish forks which occur
+ // from the main thread vs. child threads)
+ s_mainThreadId = boost::this_thread::get_id();
+
+ // determine character set
+ s_printCharsetWarning = !ensureUtf8Charset();
+
+ // read program options
+ Options& options = session::options();
+ ProgramStatus status = options.read(argc, argv) ;
+ if (status.exit())
+ return status.exitCode() ;
+
+ // initialize monitor
+ monitor::initializeMonitorClient(kMonitorSocketPath,
+ options.monitorSharedSecret());
+
+ // register monitor log writer
+ core::system::addLogWriter(monitor::client().createLogWriter(
+ options.programIdentity()));
+
+ // convenience flags for server and desktop mode
+ bool desktopMode = options.programMode() == kSessionProgramModeDesktop;
+ bool serverMode = options.programMode() == kSessionProgramModeServer;
+
+ // re-initialize log for desktop mode
+ if (desktopMode)
+ {
+ if (options.verifyInstallation())
+ {
+ initializeStderrLog(options.programIdentity(),
+ core::system::kLogLevelWarning);
+ }
+ else
+ {
+ initializeLog(options.programIdentity(),
+ core::system::kLogLevelWarning,
+ options.userLogPath());
+ }
+ }
+
+ // set version
+ s_version = installedVersion();
+
+ // set the rstudio environment variable so code can check for
+ // whether rstudio is running
+ core::system::setenv("RSTUDIO", "1");
+
+ // set the rstudio user identity environment variable (can differ from
+ // username in debug configurations). this is provided so that
+ // rpostback knows what local stream to connect back to
+ core::system::setenv(kRStudioUserIdentity, options.userIdentity());
+ if (desktopMode)
+ {
+ // do the same for port number, for rpostback in rdesktop configs
+ core::system::setenv(kRSessionPortNumber, options.wwwPort());
+ }
+
+ // ensure we aren't being started as a low (priviliged) account
+ if (serverMode &&
+ core::system::currentUserIsPrivilleged(options.minimumUserId()))
+ {
+ Error error = systemError(boost::system::errc::permission_denied,
+ ERROR_LOCATION);
+ return sessionExitFailure(error, ERROR_LOCATION);
+ }
+
+#ifdef RSTUDIO_SERVER
+ if (serverMode)
+ {
+ Error error = core::system::crypto::rsaInit();
+ if (error)
+ LOG_ERROR(error);
+ }
+#endif
+
+ // start the file monitor
+ core::system::file_monitor::initialize();
+
+ // initialize client event queue. this must be done very early
+ // in main so that any other code which needs to enque an event
+ // has access to the queue
+ session::initializeClientEventQueue();
+
+ // detect parent termination
+ if (desktopMode)
+ core::thread::safeLaunchThread(detectParentTermination);
+
+ // set the rpostback absolute path
+ FilePath rpostback = options.rpostbackPath()
+ .parent().parent()
+ .childPath("rpostback");
+ core::system::setenv(
+ "RS_RPOSTBACK_PATH",
+ string_utils::utf8ToSystem(rpostback.absolutePath()));
+
+ // ensure that the user scratch path exists
+ FilePath userScratchPath = options.userScratchPath();
+ error = userScratchPath.ensureDirectory();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION);
+
+ // initialize user settings
+ error = userSettings().initialize();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION) ;
+
+ // startup projects -- must be after userSettings is initialized
+ // but before persistentState and setting working directory
+ projects::startup();
+
+ // initialize persistent state
+ error = session::persistentState().initialize();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION) ;
+
+ // set working directory
+ FilePath workingDir = getInitialWorkingDirectory();
+ error = workingDir.makeCurrentPath();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION);
+
+ // start http connection listener
+ error = startHttpConnectionListener();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION);
+
+ // run optional preflight script -- needs to be after the http listeners
+ // so the proxy server sees that we have startup up
+ error = runPreflightScript();
+ if (error)
+ return sessionExitFailure(error, ERROR_LOCATION);
+
+ // server-only user file/directory initialization
+ if (serverMode)
+ {
+ // r profile file
+ ensureRProfile();
+
+ // public folder
+ ensurePublicFolder();
+
+ // ensure the user has an R library directory
+ if (!options.rLibsUser().empty())
+ ensureRLibsUser(options.userHomePath(), options.rLibsUser());
+ }
+
+ // we've gotten through startup so let's log a start event
+ using namespace monitor;
+ client().logEvent(Event(kSessionScope, kSessionStartEvent));
+
+
+ // install home and doc dir overrides if requested (for debugger mode)
+ if (!options.rHomeDirOverride().empty())
+ core::system::setenv("R_HOME", options.rHomeDirOverride());
+ if (!options.rDocDirOverride().empty())
+ core::system::setenv("R_DOC_DIR", options.rDocDirOverride());
+
+ // r options
+ r::session::ROptions rOptions ;
+ rOptions.userHomePath = options.userHomePath();
+ rOptions.userScratchPath = userScratchPath;
+ rOptions.scopedScratchPath = module_context::scopedScratchPath();
+ rOptions.logPath = options.userLogPath();
+ rOptions.sessionPort = options.wwwPort();
+ rOptions.startupEnvironmentFilePath = getStartupEnvironmentFilePath();
+ rOptions.persistentState = boost::bind(&PersistentState::settings,
+ &(persistentState()));
+ rOptions.rEnvironmentDir = boost::bind(rEnvironmentDir);
+ rOptions.rHistoryDir = boost::bind(rHistoryDir);
+ rOptions.alwaysSaveHistory = boost::bind(alwaysSaveHistoryOption);
+ rOptions.rSourcePath = options.coreRSourcePath();
+ if (!desktopMode) // ignore r-libs-user in desktop mode
+ rOptions.rLibsUser = options.rLibsUser();
+ // CRAN repos: user setting first then global server option
+ rOptions.rCRANRepos = userSettings().cranMirror().url;
+ if (rOptions.rCRANRepos.empty())
+ rOptions.rCRANRepos = options.rCRANRepos();
+ rOptions.useInternet2 = userSettings().useInternet2();
+ rOptions.rCompatibleGraphicsEngineVersion =
+ options.rCompatibleGraphicsEngineVersion();
+ rOptions.serverMode = serverMode;
+ rOptions.autoReloadSource = options.autoReloadSource();
+ rOptions.restoreWorkspace = restoreWorkspaceOption();
+ rOptions.saveWorkspace = saveWorkspaceOption();
+ rOptions.rProfileOnResume = serverMode &&
+ userSettings().rProfileOnResume();
+
+ // r callbacks
+ r::session::RCallbacks rCallbacks;
+ rCallbacks.init = rInit;
+ rCallbacks.consoleRead = rConsoleRead;
+ rCallbacks.editFile = rEditFile;
+ rCallbacks.showFile = rShowFile;
+ rCallbacks.chooseFile = rChooseFile;
+ rCallbacks.busy = rBusy;
+ rCallbacks.consoleWrite = rConsoleWrite;
+ rCallbacks.consoleHistoryReset = rConsoleHistoryReset;
+ rCallbacks.locator = rLocator;
+ rCallbacks.deferredInit = rDeferredInit;
+ rCallbacks.suspended = rSuspended;
+ rCallbacks.resumed = rResumed;
+ rCallbacks.handleUnsavedChanges = rHandleUnsavedChanges;
+ rCallbacks.quit = rQuit;
+ rCallbacks.suicide = rSuicide;
+ rCallbacks.cleanup = rCleanup ;
+ rCallbacks.browseURL = rBrowseURL;
+ rCallbacks.browseFile = rBrowseFile;
+ rCallbacks.showHelp = rShowHelp;
+ rCallbacks.showMessage = rShowMessage;
+ rCallbacks.serialization = rSerialization;
+
+ // run r (does not return, terminates process using exit)
+ error = r::session::run(rOptions, rCallbacks) ;
+ if (error)
+ {
+ // this is logically equivilant to R_Suicide
+ logExitEvent(Event(kSessionScope, kSessionSuicideEvent));
+
+ // return failure
+ return sessionExitFailure(error, ERROR_LOCATION);
+ }
+
+ // return success for good form
+ return EXIT_SUCCESS;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
+
+
+
diff --git a/src/cpp/session/SessionModuleContext.cpp b/src/cpp/session/SessionModuleContext.cpp
new file mode 100644
index 0000000..f26674a
--- /dev/null
+++ b/src/cpp/session/SessionModuleContext.cpp
@@ -0,0 +1,1449 @@
+/*
+ * SessionModuleContext.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionModuleContextInternal.hpp"
+
+#include <vector>
+
+#include <boost/assert.hpp>
+#include <boost/utility.hpp>
+#include <boost/signal.hpp>
+#include <boost/format.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileInfo.hpp>
+#include <core/Log.hpp>
+#include <core/Hash.hpp>
+#include <core/Settings.hpp>
+#include <core/DateTime.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/FileScanner.hpp>
+#include <core/IncrementalCommand.hpp>
+#include <core/PeriodicCommand.hpp>
+#include <core/collection/Tree.hpp>
+
+#include <core/http/Util.hpp>
+
+#include <core/system/Process.hpp>
+#include <core/system/FileMonitor.hpp>
+#include <core/system/FileChangeEvent.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <core/r_util/RPackageInfo.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RUtil.hpp>
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RJson.hpp>
+#include <r/RJsonRpc.hpp>
+#include <r/RSourceManager.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RConsoleActions.hpp>
+
+#include <session/SessionOptions.hpp>
+#include "SessionClientEventQueue.hpp"
+
+#include <session/projects/SessionProjects.hpp>
+
+#include <session/SessionContentUrls.hpp>
+
+#include "modules/SessionBreakpoints.hpp"
+#include "modules/SessionVCS.hpp"
+#include "modules/SessionFiles.hpp"
+
+#include "session-config.h"
+
+using namespace core ;
+
+namespace session {
+namespace module_context {
+
+namespace {
+
+// enqueClientEvent from R
+SEXP rs_enqueClientEvent(SEXP nameSEXP, SEXP dataSEXP)
+{
+ try
+ {
+ // extract name
+ std::string name = r::sexp::asString(nameSEXP);
+
+ // extract json value (for primitive types we only support scalars
+ // since this is the most common type of event data). to return an
+ // array of primitives you need to wrap them in a list/object
+ Error extractError ;
+ json::Value data ;
+ switch(TYPEOF(dataSEXP))
+ {
+ case NILSXP:
+ {
+ // do nothing, data will be a null json value
+ break;
+ }
+ case VECSXP:
+ {
+ extractError = r::json::jsonValueFromList(dataSEXP, &data);
+ break;
+ }
+ default:
+ {
+ extractError = r::json::jsonValueFromScalar(dataSEXP, &data);
+ break;
+ }
+ }
+
+ // check for error
+ if (extractError)
+ {
+ LOG_ERROR(extractError);
+ throw r::exec::RErrorException(
+ "Couldn't extract json value from event data");
+ }
+
+ // determine the event type from the event name
+ int type = -1 ;
+ if (name == "package_status_changed")
+ type = session::client_events::kPackageStatusChanged;
+ else if (name == "installed_packages_changed")
+ type = session::client_events::kInstalledPackagesChanged;
+ else if (name == "unhandled_error")
+ type = session::client_events::kUnhandledError;
+
+ if (type != -1)
+ {
+ ClientEvent event(type, data);
+ session::clientEventQueue().add(event);
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("Unexpected event name from R: " + name);
+ }
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue ;
+}
+
+SEXP rs_activatePane(SEXP paneSEXP)
+{
+ module_context::activatePane(r::sexp::safeAsString(paneSEXP));
+ return R_NilValue;
+}
+
+// show error message from R
+SEXP rs_showErrorMessage(SEXP titleSEXP, SEXP messageSEXP)
+{
+ std::string title = r::sexp::asString(titleSEXP);
+ std::string message = r::sexp::asString(messageSEXP);
+ module_context::showErrorMessage(title, message);
+ return R_NilValue;
+}
+
+// log error message from R
+SEXP rs_logErrorMessage(SEXP messageSEXP)
+{
+ std::string message = r::sexp::asString(messageSEXP);
+ LOG_ERROR_MESSAGE(message);
+ return R_NilValue;
+}
+
+// log warning message from R
+SEXP rs_logWarningMessage(SEXP messageSEXP)
+{
+ std::string message = r::sexp::asString(messageSEXP);
+ LOG_WARNING_MESSAGE(message);
+ return R_NilValue;
+}
+
+// sleep the main thread (debugging function used to test rpc/abort)
+SEXP rs_threadSleep(SEXP secondsSEXP)
+{
+ int seconds = r::sexp::asInteger(secondsSEXP);
+ boost::this_thread::sleep(boost::posix_time::seconds(seconds));
+ return R_NilValue;
+}
+
+// get rstudio mode
+SEXP rs_rstudioProgramMode()
+{
+ r::sexp::Protect rProtect;
+ return r::sexp::create(session::options().programMode(), &rProtect);
+}
+
+// ensure file hidden
+SEXP rs_ensureFileHidden(SEXP fileSEXP)
+{
+#ifdef _WIN32
+ std::string file = r::sexp::asString(fileSEXP);
+ if (!file.empty())
+ {
+ FilePath filePath = module_context::resolveAliasedPath(file);
+ Error error = core::system::makeFileHidden(filePath);
+ if (error)
+ LOG_ERROR(error);
+ }
+#endif
+
+ return R_NilValue;
+}
+
+SEXP rs_sourceDiagnostics()
+{
+ module_context::sourceDiagnostics();
+ return R_NilValue;
+}
+
+SEXP rs_packageLoaded(SEXP pkgnameSEXP)
+{
+ std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
+
+ // fire server event
+ events().onPackageLoaded(pkgname);
+
+ // fire client event
+ ClientEvent packageLoadedEvent(
+ client_events::kPackageLoaded,
+ json::Value(pkgname));
+ enqueClientEvent(packageLoadedEvent);
+
+ return R_NilValue;
+}
+
+SEXP rs_packageUnloaded(SEXP pkgnameSEXP)
+{
+ std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
+ ClientEvent packageUnloadedEvent(
+ client_events::kPackageUnloaded,
+ json::Value(pkgname));
+ enqueClientEvent(packageUnloadedEvent);
+ return R_NilValue;
+}
+
+} // anonymous namespace
+
+
+// register a scratch path which is monitored
+
+namespace {
+
+typedef std::map<FilePath,OnFileChange> MonitoredScratchPaths;
+MonitoredScratchPaths s_monitoredScratchPaths;
+bool s_monitorByScanning = false;
+
+FilePath monitoredParentPath()
+{
+ FilePath monitoredPath = userScratchPath().complete("monitored");
+ Error error = monitoredPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ return monitoredPath;
+}
+
+bool monitoredScratchFilter(const FileInfo& fileInfo)
+{
+ return true;
+}
+
+
+void onFilesChanged(const std::vector<core::system::FileChangeEvent>& changes)
+{
+ BOOST_FOREACH(const core::system::FileChangeEvent& fileChange, changes)
+ {
+ FilePath changedFilePath(fileChange.fileInfo().absolutePath());
+ for (MonitoredScratchPaths::const_iterator
+ it = s_monitoredScratchPaths.begin();
+ it != s_monitoredScratchPaths.end();
+ ++it)
+ {
+ if (changedFilePath.isWithin(it->first))
+ {
+ it->second(fileChange);
+ break;
+ }
+ }
+
+ }
+}
+
+boost::shared_ptr<tree<FileInfo> > monitoredPathTree()
+{
+ boost::shared_ptr<tree<FileInfo> > pMonitoredTree(new tree<FileInfo>());
+ core::system::FileScannerOptions options;
+ options.recursive = true;
+ options.filter = monitoredScratchFilter;
+ Error scanError = scanFiles(FileInfo(monitoredParentPath()),
+ options,
+ pMonitoredTree.get());
+ if (scanError)
+ LOG_ERROR(scanError);
+
+ return pMonitoredTree;
+}
+
+bool scanForMonitoredPathChanges(boost::shared_ptr<tree<FileInfo> > pPrevTree)
+{
+ // check for changes
+ std::vector<core::system::FileChangeEvent> changes;
+ boost::shared_ptr<tree<FileInfo> > pCurrentTree = monitoredPathTree();
+ core::system::collectFileChangeEvents(pPrevTree->begin(),
+ pPrevTree->end(),
+ pCurrentTree->begin(),
+ pCurrentTree->end(),
+ &changes);
+
+ // fire events
+ onFilesChanged(changes);
+
+ // reset the tree
+ *pPrevTree = *pCurrentTree;
+
+ // scan again after interval
+ return true;
+}
+
+void onMonitoringError(const Error& error)
+{
+ // log the error
+ LOG_ERROR(error);
+
+ // fallback to periodically scanning for changes
+ if (!s_monitorByScanning)
+ {
+ s_monitorByScanning = true;
+ module_context::schedulePeriodicWork(
+ boost::posix_time::seconds(3),
+ boost::bind(scanForMonitoredPathChanges, monitoredPathTree()),
+ true);
+ }
+}
+
+void initializeMonitoredUserScratchDir()
+{
+ // setup callbacks and register
+ core::system::file_monitor::Callbacks cb;
+ cb.onRegistrationError = onMonitoringError;
+ cb.onMonitoringError = onMonitoringError;
+ cb.onFilesChanged = onFilesChanged;
+ core::system::file_monitor::registerMonitor(
+ monitoredParentPath(),
+ true,
+ monitoredScratchFilter,
+ cb);
+}
+
+
+} // anonymous namespace
+
+FilePath registerMonitoredUserScratchDir(const std::string& dirName,
+ const OnFileChange& onFileChange)
+{
+ // create the subdir
+ FilePath dirPath = monitoredParentPath().complete(dirName);
+ Error error = dirPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+
+ // register the path
+ s_monitoredScratchPaths[dirPath] = onFileChange;
+
+ // return it
+ return dirPath;
+}
+
+
+
+Error initialize()
+{
+ // register rs_enqueClientEvent with R
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_enqueClientEvent" ;
+ methodDef.fun = (DL_FUNC) rs_enqueClientEvent ;
+ methodDef.numArgs = 2;
+ r::routines::addCallMethod(methodDef);
+
+ // register rs_showErrorMessage with R
+ R_CallMethodDef methodDef3 ;
+ methodDef3.name = "rs_showErrorMessage" ;
+ methodDef3.fun = (DL_FUNC) rs_showErrorMessage ;
+ methodDef3.numArgs = 2;
+ r::routines::addCallMethod(methodDef3);
+
+ // register rs_logErrorMessage with R
+ R_CallMethodDef methodDef4 ;
+ methodDef4.name = "rs_logErrorMessage" ;
+ methodDef4.fun = (DL_FUNC) rs_logErrorMessage ;
+ methodDef4.numArgs = 1;
+ r::routines::addCallMethod(methodDef4);
+
+ // register rs_logWarningMessage with R
+ R_CallMethodDef methodDef5 ;
+ methodDef5.name = "rs_logWarningMessage" ;
+ methodDef5.fun = (DL_FUNC) rs_logWarningMessage ;
+ methodDef5.numArgs = 1;
+ r::routines::addCallMethod(methodDef5);
+
+ // register rs_threadSleep with R (debugging function used to test rpc/abort)
+ R_CallMethodDef methodDef6 ;
+ methodDef6.name = "rs_threadSleep" ;
+ methodDef6.fun = (DL_FUNC) rs_threadSleep ;
+ methodDef6.numArgs = 1;
+ r::routines::addCallMethod(methodDef6);
+
+ // register rs_rstudioProgramMode with R
+ R_CallMethodDef methodDef7 ;
+ methodDef7.name = "rs_rstudioProgramMode" ;
+ methodDef7.fun = (DL_FUNC) rs_rstudioProgramMode ;
+ methodDef7.numArgs = 0;
+ r::routines::addCallMethod(methodDef7);
+
+ // register rs_ensureFileHidden with R
+ R_CallMethodDef methodDef8;
+ methodDef8.name = "rs_ensureFileHidden" ;
+ methodDef8.fun = (DL_FUNC) rs_ensureFileHidden ;
+ methodDef8.numArgs = 1;
+ r::routines::addCallMethod(methodDef8);
+
+ // register rs_sourceDiagnostics
+ R_CallMethodDef methodDef9;
+ methodDef9.name = "rs_sourceDiagnostics" ;
+ methodDef9.fun = (DL_FUNC) rs_sourceDiagnostics;
+ methodDef9.numArgs = 0;
+ r::routines::addCallMethod(methodDef9);
+
+ // register rs_activatePane
+ R_CallMethodDef methodDef10;
+ methodDef10.name = "rs_activatePane" ;
+ methodDef10.fun = (DL_FUNC) rs_activatePane;
+ methodDef10.numArgs = 1;
+ r::routines::addCallMethod(methodDef10);
+
+ // register rs_packageLoaded
+ R_CallMethodDef methodDef11;
+ methodDef11.name = "rs_packageLoaded" ;
+ methodDef11.fun = (DL_FUNC) rs_packageLoaded;
+ methodDef11.numArgs = 1;
+ r::routines::addCallMethod(methodDef11);
+
+ // register rs_packageUnloaded
+ R_CallMethodDef methodDef12;
+ methodDef12.name = "rs_packageUnloaded" ;
+ methodDef12.fun = (DL_FUNC) rs_packageUnloaded;
+ methodDef12.numArgs = 1;
+ r::routines::addCallMethod(methodDef12);
+
+ // initialize monitored scratch dir
+ initializeMonitoredUserScratchDir();
+
+ // source the ModuleTools.R file
+ FilePath modulesPath = session::options().modulesRSourcePath();
+ return r::sourceManager().sourceTools(modulesPath.complete("ModuleTools.R"));
+}
+
+namespace {
+
+// manage signals used for custom save and restore
+class SuspendHandlers : boost::noncopyable
+{
+public:
+ SuspendHandlers() : nextGroup_(0) {}
+
+public:
+ void add(const SuspendHandler& handler)
+ {
+ int group = nextGroup_++;
+ suspendSignal_.connect(group, handler.suspend());
+ resumeSignal_.connect(group, handler.resume());
+ }
+
+ void suspend(const r::session::RSuspendOptions& options,
+ Settings* pSettings)
+ {
+ suspendSignal_(options, pSettings);
+ }
+
+ void resume(const Settings& settings)
+ {
+ resumeSignal_(settings);
+ }
+
+private:
+
+ // use groups to ensure signal order. call suspend handlers in order
+ // of subscription and call resume handlers in reverse order of
+ // subscription.
+
+ int nextGroup_;
+
+ boost::signal<void(const r::session::RSuspendOptions&,Settings*),
+ boost::last_value<void>,
+ int,
+ std::less<int> > suspendSignal_;
+
+ boost::signal<void(const Settings&),
+ boost::last_value<void>,
+ int,
+ std::greater<int> > resumeSignal_;
+};
+
+// handlers instance
+SuspendHandlers s_suspendHandlers ;
+
+} // anonymous namespace
+
+void addSuspendHandler(const SuspendHandler& handler)
+{
+ s_suspendHandlers.add(handler);
+}
+
+void onSuspended(const r::session::RSuspendOptions& options,
+ Settings* pPersistentState)
+{
+ pPersistentState->beginUpdate();
+ s_suspendHandlers.suspend(options, pPersistentState);
+ pPersistentState->endUpdate();
+
+}
+
+void onResumed(const Settings& persistentState)
+{
+ s_suspendHandlers.resume(persistentState);
+}
+
+// idle work
+
+namespace {
+
+typedef std::vector<boost::shared_ptr<ScheduledCommand> >
+ ScheduledCommands;
+ScheduledCommands s_scheduledCommands;
+ScheduledCommands s_idleScheduledCommands;
+
+void addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCommand,
+ bool idleOnly)
+{
+ if (idleOnly)
+ s_idleScheduledCommands.push_back(pCommand);
+ else
+ s_scheduledCommands.push_back(pCommand);
+}
+
+void executeScheduledCommands(ScheduledCommands* pCommands)
+{
+ // execute all commands
+ std::for_each(pCommands->begin(),
+ pCommands->end(),
+ boost::bind(&ScheduledCommand::execute, _1));
+
+ // remove any commands which are finished
+ pCommands->erase(
+ std::remove_if(
+ pCommands->begin(),
+ pCommands->end(),
+ boost::bind(&ScheduledCommand::finished, _1)),
+ pCommands->end());
+}
+
+
+} // anonymous namespace
+
+void scheduleIncrementalWork(
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute,
+ bool idleOnly)
+{
+ addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
+ new IncrementalCommand(incrementalDuration,
+ execute)),
+ idleOnly);
+}
+
+void scheduleIncrementalWork(
+ const boost::posix_time::time_duration& initialDuration,
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute,
+ bool idleOnly)
+{
+ addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
+ new IncrementalCommand(initialDuration,
+ incrementalDuration,
+ execute)),
+ idleOnly);
+}
+
+
+void schedulePeriodicWork(const boost::posix_time::time_duration& period,
+ const boost::function<bool()> &execute,
+ bool idleOnly,
+ bool immediate)
+{
+ addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
+ new PeriodicCommand(period, execute, immediate)),
+ idleOnly);
+}
+
+
+namespace {
+
+bool performDelayedWork(const boost::function<void()> &execute)
+{
+ execute();
+ return false;
+}
+
+} // anonymous namespeace
+
+void scheduleDelayedWork(const boost::posix_time::time_duration& period,
+ const boost::function<void()> &execute,
+ bool idleOnly)
+{
+ schedulePeriodicWork(period,
+ boost::bind(performDelayedWork, execute),
+ idleOnly,
+ false);
+}
+
+
+void onBackgroundProcessing(bool isIdle)
+{
+ // allow process supervisor to poll for events
+ processSupervisor().poll();
+
+ // check for file monitor changes
+ core::system::file_monitor::checkForChanges();
+
+ // fire event
+ events().onBackgroundProcessing(isIdle);
+
+ // execute incremental commands
+ executeScheduledCommands(&s_scheduledCommands);
+ if (isIdle)
+ executeScheduledCommands(&s_idleScheduledCommands);
+}
+
+Error readAndDecodeFile(const FilePath& filePath,
+ const std::string& encoding,
+ bool allowSubstChars,
+ std::string* pContents)
+{
+ // read contents
+ std::string encodedContents;
+ Error error = readStringFromFile(filePath, &encodedContents,
+ options().sourceLineEnding());
+
+ if (error)
+ return error ;
+
+ // convert to UTF-8
+ return convertToUtf8(encodedContents,
+ encoding,
+ allowSubstChars,
+ pContents);
+}
+
+Error convertToUtf8(const std::string& encodedContents,
+ const std::string& encoding,
+ bool allowSubstChars,
+ std::string* pContents)
+{
+ Error error;
+ error = r::util::iconvstr(encodedContents, encoding, "UTF-8",
+ allowSubstChars, pContents);
+ if (error)
+ return error;
+
+ stripBOM(pContents);
+
+ // Detect invalid UTF-8 sequences and recover
+ error = string_utils::utf8Clean(pContents->begin(),
+ pContents->end(),
+ '?');
+ return error ;
+}
+
+FilePath userHomePath()
+{
+ return session::options().userHomePath();
+}
+
+std::string createAliasedPath(const FileInfo& fileInfo)
+{
+ return createAliasedPath(FilePath(fileInfo.absolutePath()));
+}
+
+std::string createAliasedPath(const FilePath& path)
+{
+ return FilePath::createAliasedPath(path, userHomePath());
+}
+
+FilePath resolveAliasedPath(const std::string& aliasedPath)
+{
+ return FilePath::resolveAliasedPath(aliasedPath, userHomePath());
+}
+
+FilePath userScratchPath()
+{
+ return session::options().userScratchPath();
+}
+
+FilePath scopedScratchPath()
+{
+ if (projects::projectContext().hasProject())
+ return projects::projectContext().scratchPath();
+ else
+ return userScratchPath();
+}
+
+FilePath oldScopedScratchPath()
+{
+ if (projects::projectContext().hasProject())
+ return projects::projectContext().oldScratchPath();
+ else
+ return userScratchPath();
+}
+
+bool isVisibleUserFile(const FilePath& filePath)
+{
+ return (filePath.isWithin(module_context::userHomePath()) &&
+ !filePath.isWithin(module_context::userScratchPath()));
+}
+
+FilePath safeCurrentPath()
+{
+ return FilePath::safeCurrentPath(userHomePath());
+}
+
+FilePath tempFile(const std::string& prefix, const std::string& extension)
+{
+ return r::session::utils::tempFile(prefix, extension);
+}
+
+FilePath tempDir()
+{
+ return r::session::utils::tempDir();
+}
+
+FilePath findProgram(const std::string& name)
+{
+ std::string which;
+ Error error = r::exec::RFunction("Sys.which", name).call(&which);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+ else
+ {
+ return FilePath(which);
+ }
+}
+
+namespace {
+
+bool hasTextMimeType(const FilePath& filePath)
+{
+ std::string mimeType = filePath.mimeContentType("");
+ if (mimeType.empty())
+ return false;
+
+ return boost::algorithm::starts_with(mimeType, "text/") ||
+ boost::algorithm::ends_with(mimeType, "+xml");
+}
+
+bool hasBinaryMimeType(const FilePath& filePath)
+{
+ // screen known text types
+ if (hasTextMimeType(filePath))
+ return false;
+
+ std::string mimeType = filePath.mimeContentType("");
+ if (mimeType.empty())
+ return false;
+
+ return boost::algorithm::starts_with(mimeType, "application/") ||
+ boost::algorithm::starts_with(mimeType, "image/") ||
+ boost::algorithm::starts_with(mimeType, "audio/") ||
+ boost::algorithm::starts_with(mimeType, "video/");
+}
+
+} // anonymous namespace
+
+bool isTextFile(const FilePath& targetPath)
+{
+ if (hasTextMimeType(targetPath))
+ return true;
+
+ if (hasBinaryMimeType(targetPath))
+ return false;
+
+ if (targetPath.size() == 0)
+ return true;
+
+#ifndef _WIN32
+ core::shell_utils::ShellCommand cmd("file");
+ cmd << "--dereference";
+ cmd << "--mime";
+ cmd << "--brief";
+ cmd << targetPath;
+ core::system::ProcessResult result;
+ Error error = core::system::runCommand(cmd,
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return error;
+ }
+
+ // strip encoding
+ std::string fileType = boost::algorithm::trim_copy(result.stdOut);
+ fileType = fileType.substr(0, fileType.find(';'));
+
+ // check value
+ return boost::algorithm::starts_with(fileType, "text/") ||
+ boost::algorithm::ends_with(fileType, "+xml") ||
+ boost::algorithm::ends_with(fileType, "x-empty") ||
+ boost::algorithm::equals(fileType, "application/postscript");
+#else
+
+ // read contents of file
+ std::string contents;
+ Error error = core::readStringFromFile(targetPath, &contents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return error;
+ }
+
+ // does it have null bytes?
+ std::string nullBytes;
+ nullBytes.push_back('\0');
+ nullBytes.push_back('\0');
+ return !boost::algorithm::contains(contents, nullBytes);
+
+#endif
+
+}
+
+Error rBinDir(core::FilePath* pRBinDirPath)
+{
+ std::string rHomeBin;
+ r::exec::RFunction rHomeBinFunc("R.home", "bin");
+ Error error = rHomeBinFunc.call(&rHomeBin);
+ if (error)
+ return error;
+
+ *pRBinDirPath = FilePath(rHomeBin);
+ return Success();
+}
+
+
+Error rScriptPath(FilePath* pRScriptPath)
+{
+ FilePath rHomeBinPath;
+ Error error = rBinDir(&rHomeBinPath);
+ if (error)
+ return error;
+
+#ifdef _WIN32
+*pRScriptPath = rHomeBinPath.complete("Rterm.exe");
+#else
+*pRScriptPath = rHomeBinPath.complete("R");
+#endif
+ return Success();
+}
+
+shell_utils::ShellCommand rCmd(const core::FilePath& rBinDir)
+{
+#ifdef _WIN32
+ return shell_utils::ShellCommand(rBinDir.childPath("Rcmd.exe"));
+#else
+ shell_utils::ShellCommand rCmd(rBinDir.childPath("R"));
+ rCmd << "CMD";
+ return rCmd;
+#endif
+}
+
+// get the R local help port
+std::string rLocalHelpPort()
+{
+ std::string port;
+ Error error = r::exec::RFunction(".rs.httpdPort").call(&port);
+ if (error)
+ LOG_ERROR(error);
+ return port;
+}
+
+// check if a package is installed
+bool isPackageInstalled(const std::string& packageName)
+{
+ r::session::utils::SuppressOutputInScope suppressOutput;
+
+ bool installed;
+ r::exec::RFunction func(".rs.isPackageInstalled", packageName);
+ Error error = func.call(&installed);
+ return !error ? installed : false;
+}
+
+bool isPackageVersionInstalled(const std::string& packageName,
+ const std::string& version)
+{
+ r::session::utils::SuppressOutputInScope suppressOutput;
+
+ bool installed;
+ r::exec::RFunction func(".rs.isPackageVersionInstalled",
+ packageName, version);
+ Error error = func.call(&installed);
+ return !error ? installed : false;
+}
+
+std::string packageNameForSourceFile(const core::FilePath& sourceFilePath)
+{
+ // check whether we are in a package
+ FilePath sourceDir = sourceFilePath.parent();
+ if (sourceDir.filename() == "R" &&
+ r_util::isPackageDirectory(sourceDir.parent()))
+ {
+ r_util::RPackageInfo pkgInfo;
+ Error error = pkgInfo.read(sourceDir.parent());
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+
+ return pkgInfo.name();
+ }
+ else
+ {
+ return std::string();
+ }
+}
+
+json::Object createFileSystemItem(const FileInfo& fileInfo)
+{
+ json::Object entry ;
+
+ std::string aliasedPath = module_context::createAliasedPath(fileInfo);
+ std::string rawPath =
+ module_context::resolveAliasedPath(aliasedPath).absolutePath();
+
+ entry["path"] = aliasedPath;
+ if (aliasedPath != rawPath)
+ entry["raw_path"] = rawPath;
+ entry["dir"] = fileInfo.isDirectory();
+
+ // length requires cast
+ try
+ {
+ entry["length"] = boost::numeric_cast<uint64_t>(fileInfo.size());
+ }
+ catch (const boost::bad_numeric_cast& e)
+ {
+ LOG_ERROR_MESSAGE(std::string("Error converting file size: ") +
+ e.what());
+ entry["length"] = 0;
+ }
+
+ entry["lastModified"] = date_time::millisecondsSinceEpoch(
+ fileInfo.lastWriteTime());
+ return entry;
+}
+
+json::Object createFileSystemItem(const FilePath& filePath)
+{
+ return createFileSystemItem(FileInfo(filePath));
+}
+
+std::string libPathsString()
+{
+ // call into R to get the string
+ std::string libPaths;
+ Error error = r::exec::RFunction(".rs.libPathsString").call(&libPaths);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+
+ // this is presumably system-encoded, so convert this to utf8 before return
+ return string_utils::systemToUtf8(libPaths);
+}
+
+Error sourceModuleRFile(const std::string& rSourceFile)
+{
+ FilePath modulesPath = session::options().modulesRSourcePath();
+ FilePath srcPath = modulesPath.complete(rSourceFile);
+ return r::sourceManager().sourceTools(srcPath);
+}
+
+Error sourceModuleRFileWithResult(const std::string& rSourceFile,
+ const FilePath& workingDir,
+ core::system::ProcessResult* pResult)
+{
+ // R binary
+ FilePath rProgramPath;
+ Error error = module_context::rScriptPath(&rProgramPath);
+ if (error)
+ return error;
+ std::string rBin = string_utils::utf8ToSystem(rProgramPath.absolutePath());
+
+ // vanilla execution of a single expression
+ std::vector<std::string> args;
+ args.push_back("--slave");
+ args.push_back("--vanilla");
+ args.push_back("-e");
+
+ // build source command
+ boost::format fmt("source('%1%')");
+ FilePath modulesPath = session::options().modulesRSourcePath();
+ FilePath srcFilePath = modulesPath.complete(rSourceFile);
+ std::string srcPath = core::string_utils::utf8ToSystem(
+ srcFilePath.absolutePath());
+ std::string escapedSrcPath = string_utils::jsLiteralEscape(srcPath);
+ std::string cmd = boost::str(fmt % escapedSrcPath);
+ args.push_back(cmd);
+
+ // options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+ options.workingDir = workingDir;
+
+ // allow child process to inherit our R_LIBS
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+ std::string libPaths = libPathsString();
+ if (!libPaths.empty())
+ core::system::setenv(&childEnv, "R_LIBS", libPaths);
+ options.environment = childEnv;
+
+ // run the child
+ return core::system::runProgram(rBin, args, "", options, pResult);
+}
+
+
+void enqueClientEvent(const ClientEvent& event)
+{
+ session::clientEventQueue().add(event);
+}
+
+bool isDirectoryMonitored(const FilePath& directory)
+{
+ return session::projects::projectContext().isMonitoringDirectory(directory) ||
+ session::modules::files::isMonitoringDirectory(directory);
+}
+
+bool isRScriptInPackageBuildTarget(const FilePath &filePath)
+{
+ using namespace session::projects;
+
+ if (projectContext().config().buildType == r_util::kBuildTypePackage)
+ {
+ FilePath pkgPath = projects::projectContext().buildTargetPath();
+ std::string pkgRelative = filePath.relativePath(pkgPath);
+ return boost::algorithm::starts_with(pkgRelative, "R/");
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool fileListingFilter(const core::FileInfo& fileInfo)
+{
+ // check extension for special file types which are always visible
+ core::FilePath filePath(fileInfo.absolutePath());
+ std::string ext = filePath.extensionLowerCase();
+ std::string name = filePath.filename();
+ if (ext == ".rprofile" ||
+ ext == ".rbuildignore" ||
+ ext == ".rdata" ||
+ ext == ".rhistory" ||
+ ext == ".renviron" ||
+ ext == ".gitignore")
+ {
+ return true;
+ }
+ else if (name == ".travis.yml")
+ {
+ return true;
+ }
+ else if (userSettings().hideObjectFiles() &&
+ (ext == ".o" || ext == ".so" || ext == ".dll"))
+ {
+ return false;
+ }
+ else
+ {
+ return !filePath.isHidden();
+ }
+}
+
+namespace {
+// enque file changed event
+void enqueFileChangedEvent(
+ const core::system::FileChangeEvent& event,
+ boost::shared_ptr<modules::source_control::FileDecorationContext> pCtx)
+{
+ // create file change object
+ json::Object fileChange ;
+ fileChange["type"] = event.type();
+ json::Object fileSystemItem = createFileSystemItem(event.fileInfo());
+
+ pCtx->decorateFile(FilePath(event.fileInfo().absolutePath()),
+ &fileSystemItem);
+
+ fileChange["file"] = fileSystemItem;
+
+ // enque it
+ ClientEvent clientEvent(client_events::kFileChanged, fileChange);
+ module_context::enqueClientEvent(clientEvent);
+}
+} // namespace
+
+void enqueFileChangedEvent(const core::system::FileChangeEvent &event)
+{
+ FilePath filePath = FilePath(event.fileInfo().absolutePath());
+
+ using namespace session::modules::source_control;
+ boost::shared_ptr<FileDecorationContext> pCtx =
+ fileDecorationContext(filePath);
+
+ enqueFileChangedEvent(event, pCtx);
+}
+
+void enqueFileChangedEvents(const core::FilePath& vcsStatusRoot,
+ const std::vector<core::system::FileChangeEvent>& events)
+{
+ using namespace modules::source_control;
+
+ if (events.empty())
+ return;
+
+ // try to find the common parent of the events
+ FilePath commonParentPath = FilePath(events.front().fileInfo().absolutePath()).parent();
+ BOOST_FOREACH(const core::system::FileChangeEvent& event, events)
+ {
+ // if not within the common parent then revert to the vcs status root
+ if (!FilePath(event.fileInfo().absolutePath()).isWithin(commonParentPath))
+ {
+ commonParentPath = vcsStatusRoot;
+ break;
+ }
+ }
+
+ using namespace session::modules::source_control;
+ boost::shared_ptr<FileDecorationContext> pCtx =
+ fileDecorationContext(commonParentPath);
+
+ // fire client events as necessary
+ BOOST_FOREACH(const core::system::FileChangeEvent& event, events)
+ {
+ enqueFileChangedEvent(event, pCtx);
+ }
+}
+
+
+// NOTE: we used to call explicitly back into r::session to write output
+// and errors however the fact that these functions are called from
+// background threads during std stream capture means that they must
+// provide thread safety guarantees. since the r::session module generally
+// assumes single-threaded operation it is more straightforward to have
+// the code here ape the actions of RWriteConsoleEx with a more explicit
+// thread safety constraint
+
+void consoleWriteOutput(const std::string& output)
+{
+ // NOTE: all actions herein must be threadsafe! (see comment above)
+
+ // add console action
+ r::session::consoleActions().add(kConsoleActionOutput, output);
+
+ // enque write output (same as session::rConsoleWrite)
+ ClientEvent event(client_events::kConsoleWriteOutput, output);
+ enqueClientEvent(event);
+
+ // fire event
+ module_context::events().onConsoleOutput(
+ module_context::ConsoleOutputNormal,
+ output);
+}
+
+void consoleWriteError(const std::string& message)
+{
+ // NOTE: all actions herein must be threadsafe! (see comment above)
+
+ // add console action
+ r::session::consoleActions().add(kConsoleActionOutputError, message);
+
+ // enque write error (same as session::rConsoleWrite)
+ ClientEvent event(client_events::kConsoleWriteError, message);
+ enqueClientEvent(event);
+
+ // fire event
+ module_context::events().onConsoleOutput(
+ module_context::ConsoleOutputError,
+ message);
+}
+
+void showErrorMessage(const std::string& title, const std::string& message)
+{
+ session::clientEventQueue().add(showErrorMessageEvent(title, message));
+}
+
+void showFile(const FilePath& filePath, const std::string& window)
+{
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ // for pdfs handle specially for each platform
+ if (filePath.extensionLowerCase() == ".pdf")
+ {
+ std::string path = filePath.absolutePath();
+ Error error = r::exec::RFunction(".rs.shellViewPdf", path).call();
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ ClientEvent event = browseUrlEvent("file:///" + filePath.absolutePath());
+ module_context::enqueClientEvent(event);
+ }
+ }
+ else if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ if (session::options().allowFileDownloads())
+ {
+ std::string url = createFileUrl(filePath);
+ ClientEvent event = browseUrlEvent(url);
+ module_context::enqueClientEvent(event);
+ }
+ else
+ {
+ module_context::showErrorMessage(
+ "File Download Error",
+ "Unable to show file because file downloads are restricted "
+ "on this server.\n");
+ }
+ }
+}
+
+std::string createFileUrl(const core::FilePath& filePath)
+{
+ // determine url based on whether this is in ~ or not
+ std::string url ;
+ if (isVisibleUserFile(filePath))
+ {
+ std::string relPath = filePath.relativePath(
+ module_context::userHomePath());
+ url = "files/" + relPath;
+ }
+ else
+ {
+ url = "file_show?path=" + http::util::urlEncode(
+ filePath.absolutePath(), true);
+ }
+ return url;
+}
+
+
+void showContent(const std::string& title, const core::FilePath& filePath)
+{
+ // first provision a content url
+ std::string contentUrl = content_urls::provision(title, filePath);
+
+ // fire event
+ json::Object contentItem;
+ contentItem["title"] = title;
+ contentItem["contentUrl"] = contentUrl;
+ ClientEvent event(client_events::kShowContent, contentItem);
+ module_context::enqueClientEvent(event);
+}
+
+
+std::string resourceFileAsString(const std::string& fileName)
+{
+ FilePath resPath = session::options().rResourcesPath();
+ FilePath filePath = resPath.complete(fileName);
+ std::string fileContents;
+ Error error = readStringFromFile(filePath, &fileContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+
+ return fileContents;
+}
+
+bool portmapPathForLocalhostUrl(const std::string& url, std::string* pPath)
+{
+ // extract the port
+ boost::regex re("http[s]?://(?:localhost|127\\.0\\.0\\.1):([0-9]+)(/.*)?");
+ boost::smatch match;
+ if (boost::regex_search(url, match, re))
+ {
+ // calculate the path
+ std::string path = match[2];
+ if (path.empty())
+ path = "/";
+ path = "p/" + match[1] + path;
+ *pPath = path;
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// given a url, return a portmap path if applicable (i.e. we're in server
+// mode and the path needs port mapping), and the unmodified url otherwise
+std::string mapUrlPorts(const std::string& url)
+{
+ // if we are in server mode then we need to do port mapping
+ if (session::options().programMode() != kSessionProgramModeServer)
+ return url;
+
+ // see if we can form a portmap path for this url
+ std::string path;
+ if (portmapPathForLocalhostUrl(url, &path))
+ return path;
+
+ return url;
+}
+
+// given a pair of paths, return the second in the context of the first
+std::string pathRelativeTo(const FilePath& sourcePath,
+ const FilePath& targetPath)
+{
+ std::string relative;
+ if (targetPath == sourcePath)
+ {
+ relative = ".";
+ }
+ else if (targetPath.isWithin(sourcePath))
+ {
+ relative = targetPath.relativePath(sourcePath);
+ }
+ else
+ {
+ relative = createAliasedPath(targetPath);
+ }
+ return relative;
+}
+
+void activatePane(const std::string& pane)
+{
+ ClientEvent event(client_events::kActivatePane, pane);
+ module_context::enqueClientEvent(event);
+}
+
+FilePath shellWorkingDirectory()
+{
+ if (projects::projectContext().hasProject())
+ return projects::projectContext().directory();
+ else
+ return module_context::safeCurrentPath();
+}
+
+Events& events()
+{
+ static Events instance;
+ return instance;
+}
+
+
+core::system::ProcessSupervisor& processSupervisor()
+{
+ static core::system::ProcessSupervisor instance;
+ return instance;
+}
+
+FilePath sourceDiagnostics()
+{
+ r::exec::RFunction sourceFx("source");
+ sourceFx.addParam(string_utils::utf8ToSystem(
+ options().coreRSourcePath().childPath("Diagnostics.R").absolutePath()));
+ sourceFx.addParam("chdir", true);
+ Error error = sourceFx.call();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+ else
+ {
+ // note this path is also in Diagnostics.R so changes to the path
+ // need to be synchronized there
+ return module_context::resolveAliasedPath(
+ "~/rstudio-diagnostics/diagnostics-report.txt");
+ }
+}
+
+namespace {
+void beginRpcHandler(json::JsonRpcFunction function,
+ json::JsonRpcRequest request,
+ std::string asyncHandle)
+{
+ try
+ {
+ json::JsonRpcResponse response;
+ Error error = function(request, &response);
+ BOOST_ASSERT(!response.hasAfterResponse());
+ if (error)
+ {
+ response.setError(error);
+ }
+ json::Object value;
+ value["handle"] = asyncHandle;
+ value["response"] = response.getRawResponse();
+ ClientEvent evt(client_events::kAsyncCompletion, value);
+ enqueClientEvent(evt);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+}
+} // anonymous namespace
+
+core::Error executeAsync(const json::JsonRpcFunction& function,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // Immediately return a response to the server with a handle that
+ // identifies this invocation. In the meantime, kick off the actual
+ // operation on a new thread.
+
+ std::string handle = core::system::generateUuid(true);
+ core::thread::safeLaunchThread(bind(beginRpcHandler,
+ function,
+ request,
+ handle));
+ pResponse->setAsyncHandle(handle);
+ return Success();
+}
+
+} // namespace module_context
+} // namespace session
diff --git a/src/cpp/session/SessionModuleContext.mm b/src/cpp/session/SessionModuleContext.mm
new file mode 100644
index 0000000..417d9ba
--- /dev/null
+++ b/src/cpp/session/SessionModuleContext.mm
@@ -0,0 +1,70 @@
+/*
+ * SessionModuleContext.mm
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionModuleContext.hpp>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/system/Process.hpp>
+
+#include <Foundation/NSString.h>
+#include <Foundation/NSDictionary.h>
+#include <Foundation/NSAutoreleasePool.h>
+
+using namespace core;
+
+namespace session {
+namespace module_context {
+
+bool isOSXMavericks()
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ NSDictionary *systemVersionDictionary =
+ [NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"];
+
+ NSString *systemVersion =
+ [systemVersionDictionary objectForKey:@"ProductVersion"];
+
+ std::string version(
+ [systemVersion cStringUsingEncoding:NSASCIIStringEncoding]);
+
+ [pool release];
+
+ return boost::algorithm::starts_with(version, "10.9");
+}
+
+bool hasOSXMavericksDeveloperTools()
+{
+ if (isOSXMavericks())
+ {
+ core::system::ProcessResult result;
+ Error error = core::system::runCommand("xcode-select -p",
+ core::system::ProcessOptions(),
+ &result);
+ if (!error && (result.exitStatus == EXIT_SUCCESS))
+ return true;
+ else
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+} // namespace module_context
+} // namespace session
diff --git a/src/cpp/session/SessionModuleContextInternal.hpp b/src/cpp/session/SessionModuleContextInternal.hpp
new file mode 100644
index 0000000..1a0e68d
--- /dev/null
+++ b/src/cpp/session/SessionModuleContextInternal.hpp
@@ -0,0 +1,55 @@
+/*
+ * SessionModuleContextInternal.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULE_CONTEXT_INTERNAL_HPP
+#define SESSION_MODULE_CONTEXT_INTERNAL_HPP
+
+#include <session/SessionModuleContext.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+ class Settings;
+}
+
+namespace r {
+namespace session {
+ struct RSuspendOptions;
+}
+}
+
+namespace session {
+namespace module_context {
+
+// initialize
+core::Error initialize();
+
+// suspend and resume
+
+void onSuspended(const r::session::RSuspendOptions& options,
+ core::Settings* pPersistentState);
+void onResumed(const core::Settings& persistentState);
+
+// notify of backgound processing
+void onBackgroundProcessing(bool isIdle);
+
+// source diagnostics
+core::FilePath sourceDiagnostics();
+
+} // namespace module_context
+} // namespace session
+
+#endif // SESSION_MODULE_CONTEXT_INTERNAL_HPP
+
diff --git a/src/cpp/session/SessionOptions.cpp b/src/cpp/session/SessionOptions.cpp
new file mode 100644
index 0000000..d2aec41
--- /dev/null
+++ b/src/cpp/session/SessionOptions.cpp
@@ -0,0 +1,507 @@
+/*
+ * SessionOptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionOptions.hpp>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+
+#include <monitor/MonitorConstants.hpp>
+
+#include <r/session/RSession.hpp>
+
+#include <session/SessionConstants.hpp>
+
+using namespace core ;
+
+namespace session {
+
+namespace {
+const char* const kDefaultPandocPath = "bin/pandoc";
+const char* const kDefaultPostbackPath = "bin/postback/rpostback";
+} // anonymous namespace
+
+Options& options()
+{
+ static Options instance ;
+ return instance ;
+}
+
+core::ProgramStatus Options::read(int argc, char * const argv[])
+{
+ using namespace boost::program_options ;
+
+ // get the shared secret
+ monitorSharedSecret_ = core::system::getenv(kMonitorSharedSecretEnvVar);
+ core::system::unsetenv(kMonitorSharedSecretEnvVar);
+
+ // compute the resource path
+ FilePath resourcePath;
+ Error error = core::system::installPath("..", argv[0], &resourcePath);
+ if (error)
+ {
+ LOG_ERROR_MESSAGE("Unable to determine install path: "+error.summary());
+ return ProgramStatus::exitFailure();
+ }
+
+ // detect running in OSX bundle and tweak resource path
+#ifdef __APPLE__
+ if (resourcePath.complete("Info.plist").exists())
+ resourcePath = resourcePath.complete("Resources");
+#endif
+
+ // detect running in x64 directory and tweak resource path
+#ifdef _WIN32
+ if (resourcePath.complete("x64").exists())
+ resourcePath = resourcePath.parent();
+#endif
+
+ // verify installation flag
+ options_description verify("verify");
+ verify.add_options()
+ (kVerifyInstallationSessionOption,
+ value<bool>(&verifyInstallation_)->default_value(false),
+ "verify the current installation");
+
+ // program - name and execution
+ options_description program("program");
+ program.add_options()
+ (kProgramModeSessionOption,
+ value<std::string>(&programMode_)->default_value("server"),
+ "program mode (desktop or server");
+
+ // agreement
+ options_description agreement("agreement");
+ agreement.add_options()
+ ("agreement-file",
+ value<std::string>(&agreementFilePath_)->default_value(""),
+ "agreement file");
+
+ // docs url
+ options_description docs("docs");
+ docs.add_options()
+ ("docs-url",
+ value<std::string>(&docsURL_)->default_value(""),
+ "custom docs url");
+
+ // www options
+ options_description www("www") ;
+ www.add_options()
+ ("www-local-path",
+ value<std::string>(&wwwLocalPath_)->default_value("www"),
+ "www local path")
+ ("www-symbol-maps-path",
+ value<std::string>(&wwwSymbolMapsPath_)->default_value(
+ "www-symbolmaps"),
+ "www symbol maps path")
+ ("www-port",
+ value<std::string>(&wwwPort_)->default_value("8787"),
+ "port to listen on");
+
+ // session options
+ std::string saveActionDefault;
+ options_description session("session") ;
+ session.add_options()
+ (kTimeoutSessionOption,
+ value<int>(&timeoutMinutes_)->default_value(120),
+ "session timeout (minutes)" )
+ (kDisconnectedTimeoutSessionOption,
+ value<int>(&disconnectedTimeoutMinutes_)->default_value(0),
+ "session disconnected timeout (minutes)" )
+ ("session-preflight-script",
+ value<std::string>(&preflightScript_)->default_value(""),
+ "session preflight script")
+ ("session-create-public-folder",
+ value<bool>(&createPublicFolder_)->default_value(false),
+ "automatically create public folder")
+ ("session-rprofile-on-resume-default",
+ value<bool>(&rProfileOnResumeDefault_)->default_value(false),
+ "default user setting for running Rprofile on resume")
+ ("session-save-action-default",
+ value<std::string>(&saveActionDefault)->default_value(""),
+ "default save action (yes, no, or ask)");
+
+ // allow options
+ options_description allow("allow");
+ allow.add_options()
+ ("allow-vcs-executable-edit",
+ value<bool>(&allowVcsExecutableEdit_)->default_value(true),
+ "allow editing of vcs executables")
+ ("allow-r-cran-repos-edit",
+ value<bool>(&allowCRANReposEdit_)->default_value(true),
+ "Allow editing of CRAN repository")
+ ("allow-vcs",
+ value<bool>(&allowVcs_)->default_value(true),
+ "allow use of version control features")
+ ("allow-package-installation",
+ value<bool>(&allowPackageInstallation_)->default_value(true),
+ "allow installation of packages from the packages pane")
+ ("allow-shell",
+ value<bool>(&allowShell_)->default_value(true),
+ "allow access to shell dialog")
+ ("allow-file-downloads",
+ value<bool>(&allowFileDownloads_)->default_value(true),
+ "allow file downloads from the files pane")
+ ("allow-remove-public-folder",
+ value<bool>(&allowRemovePublicFolder_)->default_value(true),
+ "allow removal of the user public folder")
+ ("allow-rpubs-publish",
+ value<bool>(&allowRpubsPublish_)->default_value(true),
+ "allow publishing to rpubs");
+
+ // r options
+ bool rShellEscape; // no longer works but don't want to break any
+ // config files which formerly used it
+ // TODO: eliminate this option entirely
+ options_description r("r") ;
+ r.add_options()
+ ("r-core-source",
+ value<std::string>(&coreRSourcePath_)->default_value("R"),
+ "Core R source path")
+ ("r-modules-source",
+ value<std::string>(&modulesRSourcePath_)->default_value("R/modules"),
+ "Modules R source path")
+ ("r-session-library",
+ value<std::string>(&sessionLibraryPath_)->default_value("R/library"),
+ "R library path")
+ ("r-session-packages",
+ value<std::string>(&sessionPackagesPath_)->default_value("R/packages"),
+ "R library path")
+ ("r-libs-user",
+ value<std::string>(&rLibsUser_)->default_value(""),
+ "R user library path")
+ ("r-cran-repos",
+ value<std::string>(&rCRANRepos_)->default_value(""),
+ "Default CRAN repository")
+ ("r-auto-reload-source",
+ value<bool>(&autoReloadSource_)->default_value(false),
+ "Reload R source if it changes during the session")
+ ("r-compatible-graphics-engine-version",
+ value<int>(&rCompatibleGraphicsEngineVersion_)->default_value(10),
+ "Maximum graphics engine version we are compatible with")
+ ("r-resources-path",
+ value<std::string>(&rResourcesPath_)->default_value("resources"),
+ "Directory containing external resources")
+ ("r-shell-escape",
+ value<bool>(&rShellEscape)->default_value(false),
+ "Support shell escape (deprecated, no longer works)")
+ ("r-home-dir-override",
+ value<std::string>(&rHomeDirOverride_)->default_value(""),
+ "Override for R_HOME (used for debug configurations)")
+ ("r-doc-dir-override",
+ value<std::string>(&rDocDirOverride_)->default_value(""),
+ "Override for R_DOC_DIR (used for debug configurations)");
+
+ // limits options
+ options_description limits("limits");
+ limits.add_options()
+ ("limit-file-upload-size-mb",
+ value<int>(&limitFileUploadSizeMb_)->default_value(0),
+ "limit of file upload size")
+ ("limit-cpu-time-minutes",
+ value<int>(&limitCpuTimeMinutes_)->default_value(0),
+ "limit on time of top level computations")
+ ("limit-xfs-disk-quota",
+ value<bool>(&limitXfsDiskQuota_)->default_value(false),
+ "limit xfs disk quota");
+
+ // external options
+ options_description external("external");
+ external.add_options()
+ ("external-rpostback-path",
+ value<std::string>(&rpostbackPath_)->default_value(kDefaultPostbackPath),
+ "Path to rpostback executable")
+ ("external-consoleio-path",
+ value<std::string>(&consoleIoPath_)->default_value("bin/consoleio.exe"),
+ "Path to consoleio executable")
+ ("external-gnudiff-path",
+ value<std::string>(&gnudiffPath_)->default_value("bin/gnudiff"),
+ "Path to gnudiff utilities (windows-only)")
+ ("external-gnugrep-path",
+ value<std::string>(&gnugrepPath_)->default_value("bin/gnugrep"),
+ "Path to gnugrep utilities (windows-only)")
+ ("external-msysssh-path",
+ value<std::string>(&msysSshPath_)->default_value("bin/msys_ssh"),
+ "Path to msys_ssh utilities (windows-only)")
+ ("external-sumatra-path",
+ value<std::string>(&sumatraPath_)->default_value("bin/sumatra"),
+ "Path to SumatraPDF (windows-only)")
+ ("external-hunspell-dictionaries-path",
+ value<std::string>(&hunspellDictionariesPath_)->default_value("resources/dictionaries"),
+ "Path to hunspell dictionaries")
+ ("external-mathjax-path",
+ value<std::string>(&mathjaxPath_)->default_value("resources/mathjax"),
+ "Path to mathjax library")
+ ("external-pandoc-path",
+ value<std::string>(&pandocPath_)->default_value(kDefaultPandocPath),
+ "Path to pandoc binaries");
+
+ // user options (default user identity to current username)
+ std::string currentUsername = core::system::username();
+ options_description user("user") ;
+ user.add_options()
+ (kUserIdentitySessionOption "," kUserIdentitySessionOptionShort,
+ value<std::string>(&userIdentity_)->default_value(currentUsername),
+ "user identity" )
+ (kShowUserIdentitySessionOption,
+ value<bool>(&showUserIdentity_)->default_value(true),
+ "show the user identity");
+
+ // overlay options
+ options_description overlay("overlay");
+ addOverlayOptions(&overlay);
+
+ // define program options
+ FilePath defaultConfigPath("/etc/rstudio/rsession.conf");
+ std::string configFile = defaultConfigPath.exists() ?
+ defaultConfigPath.absolutePath() : "";
+ core::program_options::OptionsDescription optionsDesc("rsession",
+ configFile);
+
+ optionsDesc.commandLine.add(verify);
+ optionsDesc.commandLine.add(program);
+ optionsDesc.commandLine.add(agreement);
+ optionsDesc.commandLine.add(docs);
+ optionsDesc.commandLine.add(www);
+ optionsDesc.commandLine.add(session);
+ optionsDesc.commandLine.add(allow);
+ optionsDesc.commandLine.add(r);
+ optionsDesc.commandLine.add(limits);
+ optionsDesc.commandLine.add(external);
+ optionsDesc.commandLine.add(user);
+
+ // define groups included in config-file processing
+ optionsDesc.configFile.add(program);
+ optionsDesc.configFile.add(agreement);
+ optionsDesc.configFile.add(docs);
+ optionsDesc.configFile.add(www);
+ optionsDesc.configFile.add(session);
+ optionsDesc.configFile.add(allow);
+ optionsDesc.configFile.add(r);
+ optionsDesc.configFile.add(limits);
+ optionsDesc.configFile.add(external);
+ optionsDesc.configFile.add(user);
+ optionsDesc.configFile.add(overlay);
+
+ // read configuration
+ ProgramStatus status = core::program_options::read(optionsDesc, argc,argv);
+ if (status.exit())
+ return status;
+
+ // make sure the program mode is valid
+ if (programMode_ != kSessionProgramModeDesktop &&
+ programMode_ != kSessionProgramModeServer)
+ {
+ LOG_ERROR_MESSAGE("invalid program mode: " + programMode_);
+ return ProgramStatus::exitFailure();
+ }
+
+ // call overlay hooks
+ resolveOverlayOptions();
+ std::string errMsg;
+ if (!validateOverlayOptions(&errMsg))
+ {
+ program_options::reportError(errMsg, ERROR_LOCATION);
+ return ProgramStatus::exitFailure();
+ }
+
+ // compute program identity
+ programIdentity_ = "rsession-" + userIdentity_;
+
+ // provide special home path in temp directory if we are verifying
+ if (verifyInstallation_)
+ {
+ // we create a special home directory in server mode (since the
+ // user we are running under might not have a home directory)
+ if (programMode_ == kSessionProgramModeServer)
+ {
+ verifyInstallationHomeDir_ = "/tmp/rstudio-verify-installation";
+ Error error = FilePath(verifyInstallationHomeDir_).ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return ProgramStatus::exitFailure();
+ }
+ core::system::setenv("R_USER", verifyInstallationHomeDir_);
+ }
+ }
+
+ // compute user home path
+ FilePath userHomePath = core::system::userHomePath("R_USER|HOME");
+
+ userHomePath_ = userHomePath.absolutePath();
+
+ // compute user scratch path
+ std::string scratchPathName;
+ if (programMode_ == kSessionProgramModeDesktop)
+ scratchPathName = "RStudio-Desktop";
+ else
+ scratchPathName = "RStudio";
+ userScratchPath_ = core::system::userSettingsPath(
+ userHomePath,
+ scratchPathName).absolutePath();
+
+ // session timeout seconds is always -1 in desktop mode
+ if (programMode_ == kSessionProgramModeDesktop)
+ timeoutMinutes_ = 0;
+
+ // convert string save action default to intenger
+ if (saveActionDefault == "yes")
+ saveActionDefault_ = r::session::kSaveActionSave;
+ else if (saveActionDefault == "no")
+ saveActionDefault_ = r::session::kSaveActionNoSave;
+ else if (saveActionDefault == "ask" || saveActionDefault.empty())
+ saveActionDefault_ = r::session::kSaveActionAsk;
+ else
+ {
+ program_options::reportWarnings(
+ "Invalid value '" + saveActionDefault + "' for "
+ "session-save-action-default. Valid values are yes, no, and ask.",
+ ERROR_LOCATION);
+ saveActionDefault_ = r::session::kSaveActionAsk;
+ }
+
+ // convert relative paths by completing from the app resource path
+ resolvePath(resourcePath, &rResourcesPath_);
+ resolvePath(resourcePath, &agreementFilePath_);
+ resolvePath(resourcePath, &wwwLocalPath_);
+ resolvePath(resourcePath, &wwwSymbolMapsPath_);
+ resolvePath(resourcePath, &coreRSourcePath_);
+ resolvePath(resourcePath, &modulesRSourcePath_);
+ resolvePath(resourcePath, &sessionLibraryPath_);
+ resolvePath(resourcePath, &sessionPackagesPath_);
+ resolvePostbackPath(resourcePath, &rpostbackPath_);
+#ifdef _WIN32
+ resolvePath(resourcePath, &consoleIoPath_);
+ resolvePath(resourcePath, &gnudiffPath_);
+ resolvePath(resourcePath, &gnugrepPath_);
+ resolvePath(resourcePath, &msysSshPath_);
+ resolvePath(resourcePath, &sumatraPath_);
+#endif
+ resolvePath(resourcePath, &hunspellDictionariesPath_);
+ resolvePath(resourcePath, &mathjaxPath_);
+ resolvePandocPath(resourcePath, &pandocPath_);
+
+ // shared secret with parent
+ secret_ = core::system::getenv("RS_SHARED_SECRET");
+ /* SECURITY: Need RS_SHARED_SECRET to be available to
+ rpostback. However, we really ought to communicate
+ it in a more secure manner than this, at least on
+ Windows where even within the same user session some
+ processes can have different priviliges (integrity
+ levels) than others. For example, using a named pipe
+ with proper SACL to retrieve the shared secret, where
+ the name of the pipe is in an environment variable. */
+ //core::system::unsetenv("RS_SHARED_SECRET");
+
+ // initial working dir override
+ initialWorkingDirOverride_ = core::system::getenv("RS_INITIAL_WD");
+ core::system::unsetenv("RS_INITIAL_WD");
+
+ // initial environment file override
+ initialEnvironmentFileOverride_ = core::system::getenv("RS_INITIAL_ENV");
+ core::system::unsetenv("RS_INITIAL_ENV");
+
+ // initial project
+ initialProjectPath_ = core::system::getenv("RS_INITIAL_PROJECT");
+ core::system::unsetenv("RS_INITIAL_PROJECT");
+
+ // limit rpc client uid
+ limitRpcClientUid_ = -1;
+ std::string limitUid = core::system::getenv(kRStudioLimitRpcClientUid);
+ if (!limitUid.empty())
+ {
+ limitRpcClientUid_ = core::safe_convert::stringTo<int>(limitUid, -1);
+ core::system::unsetenv(kRStudioLimitRpcClientUid);
+ }
+
+ // return status
+ return status;
+}
+
+bool Options::getBoolOverlayOption(const std::string& name)
+{
+ std::string optionValue = getOverlayOption(name);
+ return boost::algorithm::trim_copy(optionValue) == "1";
+}
+
+void Options::resolvePath(const FilePath& resourcePath,
+ std::string* pPath)
+{
+ if (!pPath->empty())
+ *pPath = resourcePath.complete(*pPath).absolutePath();
+}
+
+#ifdef __APPLE__
+
+void Options::resolvePostbackPath(const FilePath& resourcePath,
+ std::string* pPath)
+{
+ // On OSX we keep the postback scripts over in the MacOS directory
+ // rather than in the Resources directory -- make this adjustment
+ // when the default postback path has been passed
+ if (*pPath == kDefaultPostbackPath)
+ {
+ FilePath path = resourcePath.parent().complete("MacOS/postback/rpostback");
+ *pPath = path.absolutePath();
+ }
+ else
+ {
+ resolvePath(resourcePath, pPath);
+ }
+}
+
+void Options::resolvePandocPath(const FilePath& resourcePath,
+ std::string* pPath)
+{
+ if (*pPath == kDefaultPandocPath)
+ {
+ FilePath path = resourcePath.parent().complete("MacOS/pandoc");
+ *pPath = path.absolutePath();
+ }
+ else
+ {
+ resolvePath(resourcePath, pPath);
+ }
+}
+
+#else
+
+void Options::resolvePostbackPath(const FilePath& resourcePath,
+ std::string* pPath)
+{
+ resolvePath(resourcePath, pPath);
+}
+
+void Options::resolvePandocPath(const FilePath& resourcePath,
+ std::string* pPath)
+{
+ resolvePath(resourcePath, pPath);
+}
+
+
+
+#endif
+
+} // namespace session
diff --git a/src/cpp/session/SessionOptionsOverlay.cpp b/src/cpp/session/SessionOptionsOverlay.cpp
new file mode 100644
index 0000000..968236c
--- /dev/null
+++ b/src/cpp/session/SessionOptionsOverlay.cpp
@@ -0,0 +1,36 @@
+/*
+ * SessionOptionsOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionOptions.hpp>
+
+using namespace core ;
+
+namespace session {
+
+void Options::addOverlayOptions(
+ boost::program_options::options_description* pOpt)
+{
+}
+
+bool Options::validateOverlayOptions(std::string* pErrMsg)
+{
+ return true;
+}
+
+void Options::resolveOverlayOptions()
+{
+}
+
+} // namespace session
diff --git a/src/cpp/session/SessionPersistentState.cpp b/src/cpp/session/SessionPersistentState.cpp
new file mode 100644
index 0000000..14010c6
--- /dev/null
+++ b/src/cpp/session/SessionPersistentState.cpp
@@ -0,0 +1,117 @@
+/*
+ * SessionPersistentState.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionPersistentState.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+
+namespace {
+const char * const kActiveClientId = "active-client-id";
+const char * const kAbend = "abend";
+}
+
+PersistentState& persistentState()
+{
+ static PersistentState instance ;
+ return instance;
+}
+
+Error PersistentState::initialize()
+{
+ serverMode_ = (session::options().programMode() ==
+ kSessionProgramModeServer);
+
+ // always the same so that we can supporrt a restart of
+ // the session without reloading the client page
+ desktopClientId_ = "33e600bb-c1b1-46bf-b562-ab5cba070b0e";
+
+ FilePath scratchPath = module_context::scopedScratchPath();
+ FilePath statePath = scratchPath.complete("persistent-state");
+ return settings_.initialize(statePath);
+}
+
+std::string PersistentState::activeClientId()
+{
+ if (serverMode_)
+ {
+ std::string activeClientId = settings_.get(kActiveClientId);
+ if (!activeClientId.empty())
+ return activeClientId;
+ else
+ return newActiveClientId();
+ }
+ else
+ {
+ return desktopClientId_;
+ }
+}
+
+std::string PersistentState::newActiveClientId()
+{
+ if (serverMode_)
+ {
+ std::string newId = core::system::generateUuid();
+ settings_.set(kActiveClientId, newId);
+ return newId;
+ }
+ else
+ {
+ return desktopClientId_;
+ }
+}
+
+// abend tracking only applies to server mode
+
+bool PersistentState::hadAbend()
+{
+ if (serverMode_)
+ {
+ return settings_.getInt(kAbend, false);
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void PersistentState::setAbend(bool abend)
+{
+ if (serverMode_)
+ {
+ settings_.set(kAbend, abend);
+ }
+}
+
+std::string PersistentState::activeEnvironmentName() const
+{
+ return settings_.get("activeEnvironmentName", "R_GlobalEnv");
+}
+
+void PersistentState::setActiveEnvironmentName(std::string environmentName)
+{
+ settings_.set("activeEnvironmentName", environmentName);
+}
+
+} // namespace session
diff --git a/src/cpp/session/SessionPostback.cpp b/src/cpp/session/SessionPostback.cpp
new file mode 100644
index 0000000..fa5dbfb
--- /dev/null
+++ b/src/cpp/session/SessionPostback.cpp
@@ -0,0 +1,116 @@
+/*
+ * SessionPostback.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/* Postback Handlers
+
+Postback handlers are generally used for situations where R options call for
+an external executable rather than a function. For example, the R action
+for viewing PDFs, options("pdfviewer"), is handle using a postback handler
+named 'pdfviewer'.
+
+To create a new postback handler for an action 'foo' do the following:
+
+1) Create a shell script named 'rpostback-foo' (based on rpostback-pdfviewer)
+
+2) Ensure that the shell script is included in the installation and registered
+ in the rsession apparmor profile
+
+3) Call module_context::registerPostbackHandler with 'foo' as the name param
+ and the function you want called during the postback. The function will
+ be passed a single parameter corresponding to the first command line
+ argument passed by R to the shell script.
+
+4) The registration function uses the pShellCommand out param to provide you
+ with the shell command which you in turn provide to R.
+
+*/
+
+#include <string>
+#include <map>
+
+#include <boost/function.hpp>
+
+#include <core/Error.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include <session/SessionOptions.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace module_context {
+
+namespace {
+
+std::map<std::string,
+ module_context::PostbackHandlerFunction> s_postbackHandlers;
+
+void endHandlePostback(const http::UriHandlerFunctionContinuation& cont,
+ int exitCode,
+ const std::string& output)
+{
+ http::Response response;
+ // send basic response
+ response.setStatusCode(http::status::Ok);
+ response.setContentType("text/plain");
+ response.setHeader(kPostbackExitCodeHeader,
+ safe_convert::numberToString(exitCode));
+ response.setBody(output);
+ cont(&response);
+}
+
+// UriHandlerFunction wrapper for simple postbacks
+void handlePostback(const PostbackHandlerFunction& handlerFunction,
+ const http::Request& request,
+ const http::UriHandlerFunctionContinuation& cont)
+{
+ // pass the body to the postback function
+ handlerFunction(request.body(), boost::bind(endHandlePostback, cont, _1, _2));
+}
+
+} // anonymous namespace
+
+
+Error registerPostbackHandler(const std::string& name,
+ const PostbackHandlerFunction& handlerFunction,
+ std::string* pShellCommand)
+{
+ // form postback uri fragment
+ std::string postback = kPostbackUriScope + name;
+
+ // register a uri handler for this prefix
+ Error error = module_context::registerAsyncLocalUriHandler(
+ postback,
+ boost::bind(handlePostback, handlerFunction, _1, _2));
+ if (error)
+ return error ;
+
+ // compute the shell command required to invoke this handler and return it
+ Options& options = session::options();
+ *pShellCommand = options.rpostbackPath().absolutePath() + "-" + name ;
+
+ // return success
+ return Success();
+}
+
+
+
+} // namespace module_context
+} // namespace session
diff --git a/src/cpp/session/SessionSSH.cpp b/src/cpp/session/SessionSSH.cpp
new file mode 100644
index 0000000..8721bf1
--- /dev/null
+++ b/src/cpp/session/SessionSSH.cpp
@@ -0,0 +1,93 @@
+/*
+ * SessionSSH.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <session/SessionSSH.hpp>
+
+#include <boost/foreach.hpp>
+
+#include <core/system/Environment.hpp>
+
+// TODO: Implement ProcessOptions.workingDir for Windows
+
+using namespace core;
+
+namespace session {
+namespace ssh {
+
+void ProcessOptionsCreator::addEnv(const std::string& name, const std::string& value)
+{
+ env_[name] = value;
+}
+
+void ProcessOptionsCreator::rmEnv(const std::string& name)
+{
+ env_.erase(name);
+}
+
+void ProcessOptionsCreator::addToPath(const core::FilePath& dir)
+{
+ pathDirs_.push_back(dir);
+}
+
+void ProcessOptionsCreator::setWorkingDirectory(const core::FilePath& dir)
+{
+ workingDir_ = dir;
+}
+
+void ProcessOptionsCreator::clearWorkingDirectory()
+{
+ workingDir_ = FilePath();
+}
+
+core::system::ProcessOptions ProcessOptionsCreator::processOptions() const
+{
+ core::system::ProcessOptions options = baseOptions_;
+
+ // Set up environment
+ core::system::Options envOpts;
+ core::system::environment(&envOpts);
+ typedef std::pair<std::string, std::string> StringPair;
+ BOOST_FOREACH(StringPair var, env_)
+ {
+ if (var.second.empty())
+ core::system::unsetenv(&envOpts, var.first);
+ else
+ core::system::setenv(&envOpts, var.first, var.second);
+ }
+
+ if (!pathDirs_.empty())
+ {
+ std::string path = core::system::getenv(envOpts, "PATH");
+ BOOST_FOREACH(FilePath pathDir, pathDirs_)
+ {
+#ifdef _WIN32
+ path += ";";
+#else
+ path += ":";
+#endif
+ path += pathDir.absolutePathNative();
+ }
+ core::system::setenv(&envOpts, "PATH", path);
+ }
+
+ if (!workingDir_.empty())
+ {
+ options.workingDir = workingDir_;
+ }
+
+ return options;
+}
+
+} // namespace ssh
+} // namespace session
diff --git a/src/cpp/session/SessionSourceDatabase.cpp b/src/cpp/session/SessionSourceDatabase.cpp
new file mode 100644
index 0000000..67a9229
--- /dev/null
+++ b/src/cpp/session/SessionSourceDatabase.cpp
@@ -0,0 +1,673 @@
+/*
+ * SessionSourceDatabase.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionSourceDatabase.hpp>
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Hash.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/FileUtils.hpp>
+#include <core/DateTime.hpp>
+
+#include <core/system/System.hpp>
+
+#include <core/http/Util.hpp>
+
+#include <r/RUtil.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionSourceDatabaseSupervisor.hpp"
+
+// NOTE: if a file is deleted then its properties database entry is not
+// deleted. this has two implications:
+//
+// - storage is not reclaimed
+// - the properties can be "resurreced" and re-attached to another
+// file with the same path
+//
+// One way to overcome this might be to use filesystem metadata to store
+// properties rather than a side-database
+
+using namespace core;
+
+namespace session {
+namespace source_database {
+
+namespace {
+
+struct PropertiesDatabase
+{
+ FilePath path;
+ FilePath indexFile;
+ std::map<std::string,std::string> index;
+};
+
+Error getPropertiesDatabase(PropertiesDatabase* pDatabase)
+{
+ pDatabase->path = module_context::scopedScratchPath().complete("sdb/prop");
+ Error error = pDatabase->path.ensureDirectory();
+ if (error)
+ return error;
+
+ pDatabase->indexFile = pDatabase->path.complete("INDEX");
+
+ if (pDatabase->indexFile.exists())
+ return readStringMapFromFile(pDatabase->indexFile, &(pDatabase->index));
+ else
+ return Success();
+}
+
+Error putProperties(const std::string& path, const json::Object& properties)
+{
+ // url escape path (so we can use key=value persistence)
+ std::string escapedPath = http::util::urlEncode(path);
+
+ // get properties database
+ PropertiesDatabase propertiesDB;
+ Error error = getPropertiesDatabase(&propertiesDB);
+ if (error)
+ return error;
+
+ // use existing properties file if it exists, otherwise create new
+ bool updateIndex = false;
+ std::string propertiesFile = propertiesDB.index[escapedPath];
+ if (propertiesFile.empty())
+ {
+ FilePath propFile = file_utils::uniqueFilePath(propertiesDB.path);
+ propertiesFile = propFile.filename();
+ propertiesDB.index[escapedPath] = propertiesFile;
+ updateIndex = true;
+ }
+
+ // write the file
+ std::ostringstream ostr ;
+ json::writeFormatted(properties, ostr);
+ FilePath propertiesFilePath = propertiesDB.path.complete(propertiesFile);
+ error = writeStringToFile(propertiesFilePath, ostr.str());
+ if (error)
+ return error;
+
+ // update the index if necessary
+ if (updateIndex)
+ return writeStringMapToFile(propertiesDB.indexFile, propertiesDB.index);
+ else
+ return Success();
+}
+
+Error getProperties(const std::string& path, json::Object* pProperties)
+{
+ // url escape path (so we can use key=value persistence)
+ std::string escapedPath = http::util::urlEncode(path);
+
+ // get properties database
+ PropertiesDatabase propertiesDB;
+ Error error = getPropertiesDatabase(&propertiesDB);
+ if (error)
+ return error;
+
+ // check for properties file
+ std::string propertiesFile = propertiesDB.index[escapedPath];
+ if (propertiesFile.empty())
+ {
+ // return empty object if there is none
+ *pProperties = json::Object();
+ return Success();
+ }
+
+ // read the properties file
+ std::string contents ;
+ FilePath propertiesFilePath = propertiesDB.path.complete(propertiesFile);
+ error = readStringFromFile(propertiesFilePath, &contents,
+ options().sourceLineEnding());
+ if (error)
+ return error;
+
+ // parse the json
+ json::Value value;
+ if ( !json::parse(contents, &value) )
+ return systemError(boost::system::errc::bad_message, ERROR_LOCATION);
+
+ // return it
+ if (json::isType<json::Object>(value))
+ *pProperties = value.get_obj();
+ return Success();
+}
+
+json::Value pathToProjectPath(const std::string& path)
+{
+ // no project
+ projects::ProjectContext& projectContext = projects::projectContext();
+ if (!projectContext.hasProject())
+ return json::Value();
+
+ // no path
+ if (path.empty())
+ return json::Value();
+
+ // return relative path if we are within the project directory
+ FilePath filePath = module_context::resolveAliasedPath(path);
+ if (filePath.isWithin(projectContext.directory()))
+ return filePath.relativePath(projectContext.directory());
+ else
+ return json::Value();
+}
+
+std::string pathFromProjectPath(json::Value projPathJson)
+{
+ // no project
+ projects::ProjectContext& projectContext = projects::projectContext();
+ if (!projectContext.hasProject())
+ return std::string();
+
+ // no proj path
+ std::string projPath = !projPathJson.is_null() ? projPathJson.get_str() :
+ std::string();
+ if (projPath.empty())
+ return std::string();
+
+ // interpret path relative to project directory
+ FilePath filePath = projectContext.directory().childPath(projPath);
+ if (filePath.exists())
+ return module_context::createAliasedPath(filePath);
+ else
+ return std::string();
+}
+
+} // anonymous namespace
+
+SourceDocument::SourceDocument(const std::string& type)
+{
+ FilePath srcDBPath = source_database::path();
+ FilePath docPath = file_utils::uniqueFilePath(srcDBPath);
+ id_ = docPath.filename();
+ type_ = type;
+ setContents("");
+ dirty_ = false;
+ created_ = date_time::millisecondsSinceEpoch();
+ sourceOnSave_ = false;
+}
+
+
+std::string SourceDocument::getProperty(const std::string& name) const
+{
+ json::Object::const_iterator it = properties_.find(name);
+ if (it != properties_.end())
+ {
+ json::Value valueJson = it->second;
+ if (json::isType<std::string>(valueJson))
+ return valueJson.get_str();
+ else
+ return "";
+ }
+ else
+ {
+ return "";
+ }
+}
+
+bool SourceDocument::isUntitled() const
+{
+ return path().empty() && !getProperty("tempName").empty();
+}
+
+// set contents from string
+void SourceDocument::setContents(const std::string& contents)
+{
+ contents_ = contents;
+ hash_ = hash::crc32Hash(contents_);
+}
+
+// set contents from file
+Error SourceDocument::setPathAndContents(const std::string& path,
+ bool allowSubstChars)
+{
+ // resolve aliased path
+ FilePath docPath = module_context::resolveAliasedPath(path);
+
+ std::string contents;
+ Error error = module_context::readAndDecodeFile(docPath,
+ encoding(),
+ allowSubstChars,
+ &contents);
+ if (error)
+ return error ;
+
+ // update path and contents
+ path_ = path;
+ setContents(contents);
+ lastKnownWriteTime_ = docPath.lastWriteTime();
+
+ return Success();
+}
+
+Error SourceDocument::updateDirty()
+{
+ if (path().empty())
+ {
+ dirty_ = !contents_.empty();
+ }
+ else if (dirty_)
+ {
+ // This doesn't actually guarantee that dirty state is correct. All
+ // it does, at the most, is take a dirty document and mark it clean
+ // if the contents are the same as on disk. This is important because
+ // the client now has logic to detect when undo/redo causes a document
+ // to be reverted to its previous state (i.e. a dirty document can
+ // become clean through undo/redo), but that state doesn't get sent
+ // back to the server.
+
+ // We don't make a clean document dirty here, even if the contents
+ // on disk are different, because we will do that on the client side
+ // and the UI logic is a little complicated.
+
+ FilePath docPath = module_context::resolveAliasedPath(path());
+ if (docPath.exists() && docPath.size() <= (1024*1024))
+ {
+ std::string contents;
+ Error error = module_context::readAndDecodeFile(docPath,
+ encoding(),
+ true,
+ &contents);
+ if (error)
+ return error;
+
+ if (contents_.length() == contents.length() && hash_ == hash::crc32Hash(contents))
+ dirty_ = false;
+ }
+ }
+ return Success();
+}
+
+void SourceDocument::editProperties(json::Object& properties)
+{
+ std::for_each(properties.begin(),
+ properties.end(),
+ boost::bind(&SourceDocument::editProperty, this, _1));
+}
+
+void SourceDocument::checkForExternalEdit(std::time_t* pTime)
+{
+ *pTime = 0;
+
+ if (path_.empty())
+ return;
+
+ if (lastKnownWriteTime_ == 0)
+ return;
+
+ core::FilePath filePath = module_context::resolveAliasedPath(path_);
+ if (!filePath.exists())
+ return;
+
+ std::time_t newTime = filePath.lastWriteTime();
+ if (newTime != lastKnownWriteTime_)
+ *pTime = newTime;
+}
+
+void SourceDocument::updateLastKnownWriteTime()
+{
+ lastKnownWriteTime_ = 0;
+ if (path_.empty())
+ return;
+
+ core::FilePath filePath = module_context::resolveAliasedPath(path_);
+ if (!filePath.exists())
+ return;
+
+ lastKnownWriteTime_ = filePath.lastWriteTime();
+}
+
+Error SourceDocument::readFromJson(json::Object* pDocJson)
+{
+ // NOTE: since this class is the one who presumably persisted the
+ // json values in the first place we don't do "checked" access to
+ // the json data elements. if the persistence format differs from
+ // what we expect things will blow up. therefore if we change the
+ // persistence format we need to make sure this code is robust
+ // in the presence of the old format
+
+ try
+ {
+ json::Object& docJson = *pDocJson;
+
+ id_ = docJson["id"].get_str();
+ json::Value path = docJson["path"];
+ path_ = !path.is_null() ? path.get_str() : std::string();
+
+ // if we have a project_path field then it supercedes the path field
+ // (since it would correctly survive a moved project folder)
+ std::string projPath = pathFromProjectPath(docJson["project_path"]);
+ if (!projPath.empty())
+ path_ = projPath;
+
+ json::Value type = docJson["type"];
+ type_ = !type.is_null() ? type.get_str() : std::string();
+
+ setContents(docJson["contents"].get_str());
+ dirty_ = docJson["dirty"].get_bool();
+ created_ = docJson["created"].get_real();
+ sourceOnSave_ = docJson["source_on_save"].get_bool();
+
+ // read safely (migration)
+ json::Value properties = docJson["properties"];
+ properties_ = !properties.is_null() ? properties.get_obj() : json::Object();
+
+ json::Value lastKnownWriteTime = docJson["lastKnownWriteTime"];
+ lastKnownWriteTime_ = !lastKnownWriteTime.is_null()
+ ? lastKnownWriteTime.get_int64()
+ : 0;
+
+ json::Value encoding = docJson["encoding"];
+ encoding_ = !encoding.is_null() ? encoding.get_str() : std::string();
+
+ json::Value folds = docJson["folds"];
+ folds_ = !folds.is_null() ? folds.get_str() : std::string();
+
+ return Success();
+ }
+ catch(const std::exception& e)
+ {
+ return systemError(boost::system::errc::protocol_error,
+ e.what(),
+ ERROR_LOCATION);
+ }
+}
+
+void SourceDocument::writeToJson(json::Object* pDocJson) const
+{
+ json::Object& jsonDoc = *pDocJson;
+ jsonDoc["id"] = id();
+ jsonDoc["path"] = !path().empty() ? path_ : json::Value();
+ jsonDoc["project_path"] = pathToProjectPath(path_);
+ jsonDoc["type"] = !type().empty() ? type_ : json::Value();
+ jsonDoc["hash"] = hash();
+ jsonDoc["contents"] = contents();
+ jsonDoc["dirty"] = dirty();
+ jsonDoc["created"] = created();
+ jsonDoc["source_on_save"] = sourceOnSave();
+ jsonDoc["properties"] = properties();
+ jsonDoc["folds"] = folds();
+ jsonDoc["lastKnownWriteTime"] = json::Value(
+ static_cast<boost::int64_t>(lastKnownWriteTime_));
+ jsonDoc["encoding"] = encoding_;
+}
+
+Error SourceDocument::writeToFile(const FilePath& filePath) const
+{
+ // get json representation
+ json::Object jsonDoc ;
+ writeToJson(&jsonDoc);
+ std::ostringstream ostr ;
+ json::writeFormatted(jsonDoc, ostr);
+
+ // write to file
+ return writeStringToFile(filePath, ostr.str());
+}
+
+void SourceDocument::editProperty(const json::Object::value_type& property)
+{
+ if (property.second.is_null())
+ {
+ properties_.erase(property.first);
+ }
+ else
+ {
+ properties_[property.first] = property.second;
+ }
+}
+
+bool sortByCreated(const boost::shared_ptr<SourceDocument>& pDoc1,
+ const boost::shared_ptr<SourceDocument>& pDoc2)
+{
+ return pDoc1->created() < pDoc2->created();
+}
+
+namespace {
+
+FilePath s_sourceDBPath;
+
+} // anonymous namespace
+
+FilePath path()
+{
+ return s_sourceDBPath;
+}
+
+Error get(const std::string& id, boost::shared_ptr<SourceDocument> pDoc)
+{
+ FilePath filePath = source_database::path().complete(id);
+ if (filePath.exists())
+ {
+ // read the contents of the file
+ std::string contents ;
+ Error error = readStringFromFile(filePath, &contents,
+ options().sourceLineEnding());
+ if (error)
+ return error;
+
+ // parse the json
+ json::Value value;
+ if ( !json::parse(contents, &value) )
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+
+ // initialize doc from json
+ json::Object jsonDoc = value.get_obj();
+ return pDoc->readFromJson(&jsonDoc);
+ }
+ else
+ {
+ return systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ }
+}
+
+Error getDurableProperties(const std::string& path, json::Object* pProperties)
+{
+ return getProperties(path, pProperties);
+}
+
+bool isSourceDocument(const FilePath& filePath)
+{
+ if (filePath.isDirectory())
+ return false;
+ else if (filePath.filename() == ".DS_Store")
+ return false;
+ else if (filePath.filename() == "lock_file")
+ return false;
+ else
+ return true;
+}
+
+void logUnsafeSourceDocument(const FilePath& filePath,
+ const std::string& reason)
+{
+ std::string msg = "Excluded unsafe source document";
+ if (!filePath.empty())
+ msg += " (" + filePath.absolutePath() + ")";
+ msg += ": " + reason;
+ LOG_WARNING_MESSAGE(msg);
+}
+
+bool hasNullByteSequence(const std::string& contents)
+{
+ std::string nullBytes;
+ nullBytes.push_back('\0');
+ nullBytes.push_back('\0');
+ return boost::algorithm::contains(contents, nullBytes);
+}
+
+bool isSafeSourceDocument(const FilePath& docDbPath,
+ boost::shared_ptr<SourceDocument> pDoc)
+{
+ // get a filepath and use it for filtering if we can
+ FilePath filePath;
+ if (!pDoc->path().empty())
+ {
+ filePath = FilePath(pDoc->path());
+ if (filePath.extensionLowerCase() == ".rdata")
+ {
+ logUnsafeSourceDocument(filePath, ".RData file");
+ return false;
+ }
+ }
+
+ // get the size of the file in KB
+ uintmax_t docSizeKb = docDbPath.size() / 1024;
+ std::string kbStr = safe_convert::numberToString(docSizeKb);
+
+ // if it's larger than 2MB then always drop it (that's the limit
+ // enforced by the editor)
+ if (docSizeKb > (2 * 1024))
+ {
+ logUnsafeSourceDocument(filePath, "File too large (" + kbStr + ")");
+ return false;
+ }
+
+ // if it's larger then 500K and not dirty then drop it as well
+ // (that's the file size considered "large" on the client)
+ else if (!pDoc->dirty() && (docSizeKb > 512))
+ {
+ logUnsafeSourceDocument(filePath, "File too large (" + kbStr + ")");
+ return false;
+ }
+
+ // if it has a sequence of 2 null bytes then drop it
+ else if (hasNullByteSequence(pDoc->contents()))
+ {
+ logUnsafeSourceDocument(filePath,
+ "File is binary (has null byte sequence)");
+ return false;
+ }
+
+ else
+ {
+ return true;
+ }
+}
+
+
+Error list(std::vector<boost::shared_ptr<SourceDocument> >* pDocs)
+{
+ std::vector<FilePath> files ;
+ Error error = source_database::path().children(&files);
+ if (error)
+ return error ;
+
+ BOOST_FOREACH( FilePath& filePath, files )
+ {
+ if (isSourceDocument(filePath))
+ {
+ // get the source doc
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument()) ;
+ Error error = source_database::get(filePath.filename(), pDoc);
+ if (!error)
+ {
+ // safety filter
+ if (isSafeSourceDocument(filePath, pDoc))
+ pDocs->push_back(pDoc);
+ }
+ else
+ LOG_ERROR(error);
+ }
+ }
+
+ return Success();
+}
+
+Error put(boost::shared_ptr<SourceDocument> pDoc)
+{
+ // write to file
+ FilePath filePath = source_database::path().complete(pDoc->id());
+ Error error = pDoc->writeToFile(filePath);
+ if (error)
+ return error ;
+
+ // write properties to durable storage (if there is a path)
+ if (!pDoc->path().empty())
+ {
+ error = putProperties(pDoc->path(), pDoc->properties());
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return Success();
+}
+
+Error remove(const std::string& id)
+{
+ return source_database::path().complete(id).removeIfExists();
+}
+
+Error removeAll()
+{
+ std::vector<FilePath> files ;
+ Error error = source_database::path().children(&files);
+ if (error)
+ return error ;
+
+ BOOST_FOREACH( FilePath& filePath, files )
+ {
+ Error error = filePath.remove();
+ if (error)
+ return error ;
+ }
+
+ return Success();
+}
+
+namespace {
+
+void onShutdown(bool)
+{
+ Error error = supervisor::detachFromSourceDatabase();
+ if (error)
+ LOG_ERROR(error);
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // provision a source database directory
+ Error error = supervisor::attachToSourceDatabase(&s_sourceDBPath);
+ if (error)
+ return error;
+
+ // signup for the shutdown event
+ module_context::events().onShutdown.connect(onShutdown);
+
+ return Success();
+}
+
+} // namespace source_database
+} // namesapce session
+
diff --git a/src/cpp/session/SessionSourceDatabaseSupervisor.cpp b/src/cpp/session/SessionSourceDatabaseSupervisor.cpp
new file mode 100644
index 0000000..60df239
--- /dev/null
+++ b/src/cpp/session/SessionSourceDatabaseSupervisor.cpp
@@ -0,0 +1,407 @@
+/*
+ * SessionSourceDatabaseSupervisor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSourceDatabaseSupervisor.hpp"
+
+#include <vector>
+
+#include <boost/foreach.hpp>
+#include <boost/scope_exit.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/FileLock.hpp>
+#include <core/FileUtils.hpp>
+#include <core/BoostErrors.hpp>
+
+
+#include <core/system/System.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include "session/SessionSourceDatabase.hpp"
+
+using namespace core;
+
+namespace session {
+namespace source_database {
+namespace supervisor {
+
+namespace {
+
+const char * const kSessionDirPrefix = "s-";
+
+FilePath oldSourceDatabaseRoot()
+{
+ return
+ module_context::scopedScratchPath().complete("source_database");
+}
+
+FilePath sourceDatabaseRoot()
+{
+ return module_context::scopedScratchPath().complete("sdb");
+}
+
+FilePath persistentTitledDir()
+{
+ return sourceDatabaseRoot().complete("per/t");
+}
+
+FilePath oldPersistentTitledDir()
+{
+ FilePath oldPath = module_context::oldScopedScratchPath();
+ if (oldPath.exists())
+ return oldPath.complete("source_database_v2/persistent/titled");
+ else
+ return FilePath();
+}
+
+FilePath persistentUntitledDir()
+{
+ return sourceDatabaseRoot().complete("per/u");
+}
+
+FilePath oldPersistentUntitledDir()
+{
+ FilePath oldPath = module_context::oldScopedScratchPath();
+ if (oldPath.exists())
+ return oldPath.complete("source_database_v2/persistent/untitled");
+ else
+ return FilePath();
+}
+
+FilePath sessionLockFilePath(const FilePath& sessionDir)
+{
+ return sessionDir.complete("lock_file");
+}
+
+// session dir lock (initialized by attachToSourceDatabase)
+FileLock s_sessionDirLock;
+
+Error removeSessionDir(const FilePath& sessionDir)
+{
+ // first remove children
+ std::vector<FilePath> children;
+ Error error = sessionDir.children(&children);
+ if (error)
+ LOG_ERROR(error);
+ BOOST_FOREACH(const FilePath& filePath, children)
+ {
+ error = filePath.remove();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // then remove dir
+ return sessionDir.remove();
+}
+
+FilePath generateSessionDirPath()
+{
+ return file_utils::uniqueFilePath(sourceDatabaseRoot(),
+ kSessionDirPrefix);
+}
+
+bool isNotSessionDir(const FilePath& filePath)
+{
+ return !filePath.isDirectory() || !boost::algorithm::starts_with(
+ filePath.filename(),
+ kSessionDirPrefix);
+}
+
+Error enumerateSessionDirs(std::vector<FilePath>* pSessionDirs)
+{
+ // get the directories
+ Error error = sourceDatabaseRoot().children(pSessionDirs);
+ if (error)
+ return error;
+
+ // clean out non session dirs
+ pSessionDirs->erase(std::remove_if(pSessionDirs->begin(),
+ pSessionDirs->end(),
+ isNotSessionDir),
+ pSessionDirs->end());
+
+ // return success
+ return Success();
+}
+
+void attemptToMoveSourceDbFiles(const FilePath& fromPath,
+ const FilePath& toPath)
+{
+ // enumerate the from path
+ std::vector<FilePath> children;
+ Error error = fromPath.children(&children);
+ if (error)
+ LOG_ERROR(error);
+
+ // move the files
+ BOOST_FOREACH(const FilePath& filePath, children)
+ {
+ // if the target path already exists then skip it and log
+ // (we used to generate a new uniqueFilePath however this
+ // caused the filename and id (stored in the source doc)
+ // to get out of sync, making documents unclosable. The
+ // chance of file with the same name already existing is
+ // close to zero (collision probability of uniqueFilePath)
+ // so it's no big deal to punt here.
+ FilePath targetPath = toPath.complete(filePath.filename());
+ if (targetPath.exists())
+ {
+ LOG_WARNING_MESSAGE("Skipping source db move for: " +
+ filePath.absolutePath());
+
+ Error error = filePath.remove();
+ if (error)
+ LOG_ERROR(error);
+
+ continue;
+ }
+
+ Error error = filePath.move(targetPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+// NOTE: the supervisor needs to return a session dir in order for the process
+// to start. therefore, in the createSessionDir family of functions below
+// once we successfully create and lock the session dir other errors (such
+// as trying to move files into the session dir) are simply logged
+
+Error createSessionDir(FilePath* pSessionDir)
+{
+ *pSessionDir = generateSessionDirPath();
+
+ Error error = pSessionDir->ensureDirectory();
+ if (error)
+ return error;
+
+ // attempt to acquire the lock. if we can't then we still continue
+ // so we can support filesystems that don't have file locks.
+ error = s_sessionDirLock.acquire(sessionLockFilePath(*pSessionDir));
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+}
+
+Error createSessionDirFromOldSourceDatabase(FilePath* pSessionDir)
+{
+ // move properties (if any) into new source database root
+ FilePath propsPath = oldSourceDatabaseRoot().complete("properties");
+ if (propsPath.exists())
+ {
+ FilePath newPropsPath = sourceDatabaseRoot().complete("prop");
+ Error error = propsPath.move(newPropsPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // move the old source database into a new dir
+ *pSessionDir = generateSessionDirPath();
+ Error error = oldSourceDatabaseRoot().move(*pSessionDir);
+ if (error)
+ LOG_ERROR(error);
+
+ // if that failed we might still need to call ensureDirectory
+ error = pSessionDir->ensureDirectory();
+ if (error)
+ return error;
+
+ // attempt to acquire the lock. if we can't then we still continue
+ // so we can support filesystems that don't have file locks.
+ error = s_sessionDirLock.acquire(sessionLockFilePath(*pSessionDir));
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+}
+
+Error createSessionDirFromPersistent(FilePath* pSessionDir)
+{
+ // create new session dir
+ Error error = createSessionDir(pSessionDir);
+ if (error)
+ return error;
+
+ // move persistent titled files
+ if (persistentTitledDir().exists())
+ attemptToMoveSourceDbFiles(persistentTitledDir(), *pSessionDir);
+
+ // get legacy titled docs if they exist
+ if (oldPersistentTitledDir().exists())
+ attemptToMoveSourceDbFiles(oldPersistentTitledDir(), *pSessionDir);
+
+ // move persistent untitled files
+ if (persistentUntitledDir().exists())
+ attemptToMoveSourceDbFiles(persistentUntitledDir(), *pSessionDir);
+
+ // get legacy untitled docs if they exist
+ if (oldPersistentUntitledDir().exists())
+ attemptToMoveSourceDbFiles(oldPersistentUntitledDir(), *pSessionDir);
+
+ // return success
+ return Success();
+}
+
+bool reclaimOrphanedSession(const std::vector<FilePath>& sessionDirs,
+ FilePath* pSessionDir)
+{
+ BOOST_FOREACH(const FilePath& sessionDir, sessionDirs)
+ {
+ FilePath lockFilePath = sessionLockFilePath(sessionDir);
+ if (!FileLock::isLocked(lockFilePath))
+ {
+ Error error = s_sessionDirLock.acquire(lockFilePath);
+ if (!error)
+ {
+ *pSessionDir = sessionDir;
+ return true;
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+
+// NOTE: we attempt to use file locks to coordinate between disperate
+// processes all attempting to open a session in the same context (project
+// or global). Locks are used to implement recovery of crashed sessions
+// as follows: if there is an existing source-db directory on disk that
+// is NOT locked then it's presumed to be an orphan (resulting from a crash)
+// and we should initialize with this directory to "recover" it
+//
+// Unfortunately, some file systems (mostly remote network volumes) don't
+// support file-locking. In these cases we need to gracefully fall back
+// to some sane behavior. To implement this we use the following scheme:
+//
+// (1) Always attempt to call FileLock::acquire to create an advisory lock
+// but if it fails we still allow the process to start up.
+//
+// (2) When checking for "orphan" source-db directories we try to acquire
+// a lock on them -- for volumes that don't support locks this will
+// always be an error so we'll never be able to recover an orphan dir
+//
+
+Error attachToSourceDatabase(FilePath* pSessionDir)
+{
+ // check whether we will need to migrate -- ensure we do this only
+ // one time so that if for whatever reason we can't migrate the
+ // old source database we don't get stuck trying to do it every
+ // time we start up
+ bool needToMigrate = !sourceDatabaseRoot().exists() &&
+ oldSourceDatabaseRoot().exists();
+
+ // ensure the root path exists
+ Error error = sourceDatabaseRoot().ensureDirectory();
+ if (error)
+ return error;
+
+ // check for existing sessions (use this to decide how to startup below)
+ std::vector<FilePath> sessionDirs;
+ error = enumerateSessionDirs(&sessionDirs);
+ if (error)
+ LOG_ERROR(error);
+
+ // attempt to migrate if necessary
+ if (needToMigrate)
+ return createSessionDirFromOldSourceDatabase(pSessionDir);
+
+ // if there is an orphan (crash) then reclaim it
+ else if (reclaimOrphanedSession(sessionDirs, pSessionDir))
+ return Success();
+
+ // attempt to create from persistent
+ else
+ return createSessionDirFromPersistent(pSessionDir);
+}
+
+Error detachFromSourceDatabase()
+{
+ // list all current source docs
+ std::vector<boost::shared_ptr<SourceDocument> > sourceDocs;
+ Error error = source_database::list(&sourceDocs);
+ if (error)
+ return error;
+
+ // get references to persistent subdirs
+ FilePath titledDir = persistentTitledDir();
+ FilePath untitledDir = persistentUntitledDir();
+
+ // first blow away the existing persistent titled dir
+ error = titledDir.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ // ensure both directories exist -- if they don't it is a fatal error
+ error = titledDir.ensureDirectory();
+ if (error)
+ return error;
+ error = untitledDir.ensureDirectory();
+ if (error)
+ return error;
+
+ // now write the source database entries to the appropriate places
+ BOOST_FOREACH(boost::shared_ptr<SourceDocument> pDoc, sourceDocs)
+ {
+ if (pDoc->isUntitled())
+ {
+ // compute the target path (manage uniqueness since this
+ // directory is appended to from multiple processes who
+ // could have created docs with the same id)
+ FilePath targetPath = untitledDir.complete(pDoc->id());
+ if (targetPath.exists())
+ targetPath = file_utils::uniqueFilePath(untitledDir);
+
+ error = pDoc->writeToFile(targetPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ error = pDoc->writeToFile(titledDir.complete(pDoc->id()));
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ // record session dir (parent of lock file)
+ FilePath sessionDir = s_sessionDirLock.lockFilePath().parent();
+
+ // give up our lock
+ error = s_sessionDirLock.release();
+ if (error)
+ LOG_ERROR(error);
+
+ // remove the session directory
+ return removeSessionDir(sessionDir);
+}
+
+
+} // namespace supervisor
+} // namespace source_database
+} // namespace session
+
+
+
diff --git a/src/cpp/session/SessionSourceDatabaseSupervisor.hpp b/src/cpp/session/SessionSourceDatabaseSupervisor.hpp
new file mode 100644
index 0000000..ec21dbc
--- /dev/null
+++ b/src/cpp/session/SessionSourceDatabaseSupervisor.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionSourceDatabaseSupervisor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SOURCE_DATABASE_SUPERVISOR_HPP
+#define SESSION_SOURCE_DATABASE_SUPERVISOR_HPP
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace source_database {
+namespace supervisor {
+
+core::Error attachToSourceDatabase(core::FilePath* pSessionDir);
+
+core::Error detachFromSourceDatabase();
+
+} // namespace supervisor
+} // namespace source_database
+} // namespace session
+
+
+#endif // SESSION_SOURCE_DATABASE_SUPERVISOR_HPP
diff --git a/src/cpp/session/SessionUserSettings.cpp b/src/cpp/session/SessionUserSettings.cpp
new file mode 100644
index 0000000..08f7637
--- /dev/null
+++ b/src/cpp/session/SessionUserSettings.cpp
@@ -0,0 +1,636 @@
+/*
+ * SessionUserSettings.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionUserSettings.hpp>
+
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionOptions.hpp>
+#include "modules/SessionErrors.hpp"
+#include "modules/SessionShinyViewer.hpp"
+
+#include <r/RExec.hpp>
+#include <r/ROptions.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RConsoleHistory.hpp>
+
+using namespace core ;
+
+namespace session {
+
+#define kAgreementPrefix "agreement."
+
+namespace {
+const char * const kContextId ="contextIdentifier";
+const char * const kAgreementHash = kAgreementPrefix "agreedToHash";
+const char * const kAutoCreatedProfile = "autoCreatedProfile";
+const char * const kUiPrefs = "uiPrefs";
+const char * const kAlwaysRestoreLastProject = "restoreLastProject";
+const char * const kRProfileOnResume = "rprofileOnResume";
+const char * const kSaveAction = "saveAction";
+const char * const kLoadRData = "loadRData";
+const char * const kInitialWorkingDirectory = "initialWorkingDirectory";
+const char * const kCRANMirrorName = "cranMirrorName";
+const char * const kCRANMirrorHost = "cranMirrorHost";
+const char * const kCRANMirrorUrl = "cranMirrorUrl";
+const char * const kCRANMirrorCountry = "cranMirrorCountry";
+const char * const kBioconductorMirrorName = "bioconductorMirrorName";
+const char * const kBioconductorMirrorUrl = "bioconductorMirrorUrl";
+const char * const kAlwaysSaveHistory = "alwaysSaveHistory";
+const char * const kRemoveHistoryDuplicates = "removeHistoryDuplicates";
+
+void setCRANReposOption(const std::string& url)
+{
+ if (!url.empty())
+ {
+ Error error = r::exec::RFunction(".rs.setCRANReposFromSettings",
+ url).call();
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+void setBioconductorReposOption(const std::string& mirror)
+{
+ if (!mirror.empty())
+ {
+ Error error = r::options::setOption("BioC_mirror", mirror);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+
+
+} // anonymous namespace
+
+UserSettings& userSettings()
+{
+ static UserSettings instance ;
+ return instance;
+}
+
+Error UserSettings::initialize()
+{
+ // calculate settings file path
+ FilePath settingsDir = module_context::registerMonitoredUserScratchDir(
+ "user-settings",
+ boost::bind(&UserSettings::onSettingsFileChanged, this, _1));
+ settingsFilePath_ = settingsDir.complete("user-settings");
+
+ // if it doesn't exist see if we can migrate an old user settings
+ if (!settingsFilePath_.exists())
+ {
+ FilePath oldSettingsPath =
+ module_context::userScratchPath().complete("user-settings");
+ if (oldSettingsPath.exists())
+ oldSettingsPath.move(settingsFilePath_);
+ }
+
+ // read the settings
+ Error error = settings_.initialize(settingsFilePath_);
+ if (error)
+ return error;
+
+ // make sure we have a context id
+ if (contextId().empty())
+ setContextId(core::system::generateShortenedUuid());
+
+ return Success();
+}
+
+void UserSettings::onSettingsFileChanged(
+ const core::system::FileChangeEvent& changeEvent)
+{
+ // ensure this is for our target file
+ if (settingsFilePath_.absolutePath() !=
+ changeEvent.fileInfo().absolutePath())
+ {
+ return;
+ }
+
+ // re-read the settings from disk
+ Error error = settings_.initialize(settingsFilePath_);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // update prefs cache
+ updatePrefsCache(uiPrefs());
+
+ // set underlying R repos options
+ std::string cranMirrorURL = cranMirror().url;
+ if (!cranMirrorURL.empty())
+ setCRANReposOption(cranMirrorURL);
+ std::string bioconductorMirrorURL = settings_.get(kBioconductorMirrorUrl);
+ if (!bioconductorMirrorURL.empty())
+ setBioconductorReposOption(bioconductorMirrorURL);
+
+ // update remove dups in underlying R session
+ using namespace r::session;
+ consoleHistory().setRemoveDuplicates(removeHistoryDuplicates());
+
+ // fire event so others can react appropriately
+ onChanged();
+}
+
+
+std::string UserSettings::contextId() const
+{
+ return settings_.get(kContextId);
+}
+
+std::string UserSettings::oldContextId() const
+{
+ return settings_.get("contextId");
+}
+
+void UserSettings::setContextId(const std::string& contextId)
+{
+ settings_.set(kContextId, contextId);
+}
+
+std::string UserSettings::agreementHash() const
+{
+ return settings_.get(kAgreementHash, "");
+}
+
+void UserSettings::setAgreementHash(const std::string& hash)
+{
+ settings_.set(kAgreementHash, hash);
+}
+
+
+bool UserSettings::autoCreatedProfile() const
+{
+ return settings_.getBool(kAutoCreatedProfile, false);
+}
+
+void UserSettings::setAutoCreatedProfile(bool autoCreated)
+{
+ settings_.set(kAutoCreatedProfile, autoCreated);
+}
+
+core::json::Object UserSettings::uiPrefs() const
+{
+ std::string value = settings_.get(kUiPrefs, "{}");
+ json::Value jsonValue;
+ bool success = core::json::parse(value, &jsonValue);
+ if (success)
+ return jsonValue.get_obj();
+ else
+ return json::Object();
+}
+
+void UserSettings::setUiPrefs(const core::json::Object& prefsObject)
+{
+ std::ostringstream output;
+ json::writeFormatted(prefsObject, output);
+ settings_.set(kUiPrefs, output.str());
+
+ updatePrefsCache(prefsObject);
+}
+
+namespace {
+
+template <typename T>
+T readPref(const json::Object& prefs,
+ const std::string& name,
+ const T& defaultValue)
+{
+ T value = defaultValue;
+ Error error = json::readObject(prefs,
+ name,
+ defaultValue,
+ &value);
+ if (error)
+ {
+ value = defaultValue;
+ error.addProperty("pref", name);
+ LOG_ERROR(error);
+ }
+
+ return value;
+}
+
+} // anonymous namespace
+
+void UserSettings::updatePrefsCache(const json::Object& prefs) const
+{
+ bool useSpacesForTab = readPref<bool>(prefs, "use_spaces_for_tab", true);
+ pUseSpacesForTab_.reset(new bool(useSpacesForTab));
+
+ int numSpacesForTab = readPref<int>(prefs, "num_spaces_for_tab", 2);
+ pNumSpacesForTab_.reset(new int(numSpacesForTab));
+
+ bool autoAppendNewline = readPref<bool>(prefs, "auto_append_newline", false);
+ pAutoAppendNewline_.reset(new bool(autoAppendNewline));
+
+ bool stripTrailingWhitespace = readPref<bool>(prefs, "strip_trailing_whitespace", false);
+ pStripTrailingWhitespace_.reset(new bool(stripTrailingWhitespace));
+
+ std::string enc = readPref<std::string>(prefs, "default_encoding", "");
+ pDefaultEncoding_.reset(new std::string(enc));
+
+ std::string sweave = readPref<std::string>(prefs, "default_sweave_engine", "Sweave");
+ pDefaultSweaveEngine_.reset(new std::string(sweave));
+
+ std::string latex = readPref<std::string>(prefs, "default_latex_program", "pdfLaTeX");
+ pDefaultLatexProgram_.reset(new std::string(latex));
+
+ bool alwaysEnableRnwConcordance = readPref<bool>(prefs, "always_enable_concordance", true);
+ pAlwaysEnableRnwConcordance_.reset(new bool(alwaysEnableRnwConcordance));
+
+ std::string spellingLanguage = readPref<std::string>(prefs, "spelling_dictionary_language", "en_US");
+ pSpellingLanguage_.reset(new std::string(spellingLanguage));
+
+ json::Array spellingCustomDicts = readPref<core::json::Array>(prefs, "spelling_custom_dictionaries", core::json::Array());
+ pSpellingCustomDicts_.reset(new json::Array(spellingCustomDicts));
+
+ bool handleErrorsInUserCodeOnly = readPref<bool>(prefs, "handle_errors_in_user_code_only", true);
+ pHandleErrorsInUserCodeOnly_.reset(new bool(handleErrorsInUserCodeOnly));
+
+ int shinyViewerType = readPref<int>(prefs, "shiny_viewer_type", modules::shiny_viewer::SHINY_VIEWER_WINDOW);
+ pShinyViewerType_.reset(new int(shinyViewerType));
+}
+
+
+// readers for ui prefs
+
+bool UserSettings::useSpacesForTab() const
+{
+ return readUiPref<bool>(pUseSpacesForTab_);
+}
+
+int UserSettings::numSpacesForTab() const
+{
+ return readUiPref<int>(pNumSpacesForTab_);
+}
+
+bool UserSettings::autoAppendNewline() const
+{
+ return readUiPref<bool>(pAutoAppendNewline_);
+}
+
+bool UserSettings::stripTrailingWhitespace() const
+{
+ return readUiPref<bool>(pStripTrailingWhitespace_);
+}
+
+std::string UserSettings::defaultEncoding() const
+{
+ return readUiPref<std::string>(pDefaultEncoding_);
+}
+
+std::string UserSettings::defaultSweaveEngine() const
+{
+ return readUiPref<std::string>(pDefaultSweaveEngine_);
+}
+
+std::string UserSettings::defaultLatexProgram() const
+{
+ return readUiPref<std::string>(pDefaultLatexProgram_);
+}
+
+bool UserSettings::alwaysEnableRnwCorcordance() const
+{
+ return readUiPref<bool>(pAlwaysEnableRnwConcordance_);
+}
+
+std::string UserSettings::spellingLanguage() const
+{
+ return readUiPref<std::string>(pSpellingLanguage_);
+}
+
+bool UserSettings::handleErrorsInUserCodeOnly() const
+{
+ return readUiPref<bool>(pHandleErrorsInUserCodeOnly_);
+}
+
+int UserSettings::shinyViewerType() const
+{
+ return readUiPref<int>(pShinyViewerType_);
+}
+
+std::vector<std::string> UserSettings::spellingCustomDictionaries() const
+{
+ json::Array dictsJson = readUiPref<json::Array>(pSpellingCustomDicts_);
+ std::vector<std::string> dicts;
+ BOOST_FOREACH(const json::Value& dictJson, dictsJson)
+ {
+ if (json::isType<std::string>(dictJson))
+ dicts.push_back(dictJson.get_str());
+ }
+ return dicts;
+}
+
+
+bool UserSettings::alwaysRestoreLastProject() const
+{
+ return settings_.getBool(kAlwaysRestoreLastProject, true);
+}
+
+void UserSettings::setAlwaysRestoreLastProject(bool alwaysRestore)
+{
+ settings_.set(kAlwaysRestoreLastProject, alwaysRestore);
+}
+
+bool UserSettings::rProfileOnResume() const
+{
+ return settings_.getBool(kRProfileOnResume,
+ session::options().rProfileOnResumeDefault());
+}
+
+void UserSettings::setRprofileOnResume(bool rProfileOnResume)
+{
+ settings_.set(kRProfileOnResume, rProfileOnResume);
+}
+
+int UserSettings::saveAction() const
+{
+ return settings_.getInt(kSaveAction,
+ session::options().saveActionDefault());
+}
+
+void UserSettings::setSaveAction(int saveAction)
+{
+ settings_.set(kSaveAction, saveAction);
+}
+
+
+bool UserSettings::loadRData() const
+{
+ return settings_.getBool(kLoadRData, true);
+}
+
+void UserSettings::setLoadRData(bool loadRData)
+{
+ settings_.set(kLoadRData, loadRData);
+}
+
+core::FilePath UserSettings::initialWorkingDirectory() const
+{
+ return getWorkingDirectoryValue(kInitialWorkingDirectory);
+}
+
+CRANMirror UserSettings::cranMirror() const
+{
+ // get the settings
+ CRANMirror mirror;
+ mirror.name = settings_.get(kCRANMirrorName);
+ mirror.host = settings_.get(kCRANMirrorHost);
+ mirror.url = settings_.get(kCRANMirrorUrl);
+
+ // re-map cran.rstudio.org to cran.rstudio.com
+ if (mirror.url == "http://cran.rstudio.org")
+ mirror.url = "http://cran.rstudio.com";
+
+ mirror.country = settings_.get(kCRANMirrorCountry);
+
+ // if there is no URL then return the default RStudio mirror
+ if (mirror.url.empty())
+ {
+ mirror.name = "Global (CDN)";
+ mirror.host = "RStudio";
+ mirror.url = "http://cran.rstudio.com";
+ mirror.country = "us";
+ }
+
+ return mirror;
+}
+
+void UserSettings::setCRANMirror(const CRANMirror& mirror)
+{
+ settings_.set(kCRANMirrorName, mirror.name);
+ settings_.set(kCRANMirrorHost, mirror.host);
+ settings_.set(kCRANMirrorUrl, mirror.url);
+ settings_.set(kCRANMirrorCountry, mirror.country);
+
+ setCRANReposOption(mirror.url);
+}
+
+BioconductorMirror UserSettings::bioconductorMirror() const
+{
+ BioconductorMirror mirror ;
+
+ mirror.name = settings_.get(kBioconductorMirrorName);
+ if (!mirror.name.empty())
+ {
+ mirror.url = settings_.get(kBioconductorMirrorUrl);
+ }
+ else
+ {
+ mirror.name = "Seattle (USA)";
+ mirror.url = "http://www.bioconductor.org";
+ }
+
+ return mirror;
+}
+
+void UserSettings::setBioconductorMirror(
+ const BioconductorMirror& bioconductorMirror)
+{
+ settings_.set(kBioconductorMirrorName, bioconductorMirror.name);
+ settings_.set(kBioconductorMirrorUrl, bioconductorMirror.url);
+
+ setBioconductorReposOption(bioconductorMirror.url);
+}
+
+bool UserSettings::vcsEnabled() const
+{
+ return settings_.getBool("vcsEnabled", true);
+}
+
+void UserSettings::setVcsEnabled(bool enabled)
+{
+ settings_.set("vcsEnabled", enabled);
+}
+
+FilePath UserSettings::gitExePath() const
+{
+ std::string dir = settings_.get("vcsGitExePath");
+ if (!dir.empty())
+ return FilePath(dir);
+ else
+ return FilePath();
+}
+
+void UserSettings::setGitExePath(const FilePath& gitExePath)
+{
+ settings_.set("vcsGitExePath", gitExePath.absolutePath());
+}
+
+FilePath UserSettings::svnExePath() const
+{
+ std::string dir = settings_.get("vcsSvnExePath");
+ if (!dir.empty())
+ return FilePath(dir);
+ else
+ return FilePath();
+}
+
+void UserSettings::setSvnExePath(const FilePath& svnExePath)
+{
+ settings_.set("vcsSvnExePath", svnExePath.absolutePath());
+}
+
+FilePath UserSettings::vcsTerminalPath() const
+{
+ std::string dir = settings_.get("vcsTerminalPath");
+ if (!dir.empty())
+ return FilePath(dir);
+ else
+ return FilePath();
+}
+
+void UserSettings::setVcsTerminalPath(const FilePath& terminalPath)
+{
+ settings_.set("vcsTerminalPath", terminalPath.absolutePath());
+}
+
+bool UserSettings::vcsUseGitBash() const
+{
+ return settings_.getBool("vcsUseGitBash", true);
+}
+
+void UserSettings::setVcsUseGitBash(bool useGitBash)
+{
+ settings_.set("vcsUseGitBash", useGitBash);
+}
+
+bool UserSettings::cleanTexi2DviOutput() const
+{
+ return settings_.getBool("cleanTexi2DviOutput", true);
+}
+
+void UserSettings::setCleanTexi2DviOutput(bool cleanTexi2DviOutput)
+{
+ settings_.set("cleanTexi2DviOutput", cleanTexi2DviOutput);
+}
+
+bool UserSettings::enableLaTeXShellEscape() const
+{
+ return settings_.getBool("enableLaTeXShellEscape", false);
+}
+
+void UserSettings::setEnableLaTeXShellEscape(bool enableShellEscape)
+{
+ settings_.set("enableLaTeXShellEscape", enableShellEscape);
+}
+
+bool UserSettings::useInternet2() const
+{
+ return settings_.getBool("useInternet2", true);
+}
+
+void UserSettings::setUseInternet2(bool useInternet2)
+{
+ settings_.set("useInternet2", useInternet2);
+}
+
+bool UserSettings::cleanupAfterRCmdCheck() const
+{
+ return settings_.getBool("cleanupAfterRCmdCheck", true);
+}
+
+void UserSettings::setCleanupAfterRCmdCheck(bool cleanup)
+{
+ settings_.set("cleanupAfterRCmdCheck", cleanup);
+}
+
+bool UserSettings::viewDirAfterRCmdCheck() const
+{
+ return settings_.getBool("viewDirAfterRCmdCheck", false);
+}
+
+void UserSettings::setViewDirAfterRCmdCheck(bool viewDir)
+{
+ settings_.set("viewDirAfterRCmdCheck", viewDir);
+}
+
+bool UserSettings::hideObjectFiles() const
+{
+ return settings_.getBool("hideObjectFiles", true);
+}
+
+void UserSettings::setHideObjectFiles(bool hide)
+{
+ settings_.set("hideObjectFiles", hide);
+}
+
+bool UserSettings::alwaysSaveHistory() const
+{
+ return settings_.getBool(kAlwaysSaveHistory, true);
+}
+
+void UserSettings::setAlwaysSaveHistory(bool alwaysSave)
+{
+ settings_.set(kAlwaysSaveHistory, alwaysSave);
+}
+
+bool UserSettings::removeHistoryDuplicates() const
+{
+ return settings_.getBool(kRemoveHistoryDuplicates, false);
+}
+
+void UserSettings::setRemoveHistoryDuplicates(bool removeDuplicates)
+{
+ settings_.set(kRemoveHistoryDuplicates, removeDuplicates);
+
+ // update in underlying R session
+ r::session::consoleHistory().setRemoveDuplicates(removeDuplicates);
+}
+
+void UserSettings::setInitialWorkingDirectory(const FilePath& filePath)
+{
+ setWorkingDirectoryValue(kInitialWorkingDirectory, filePath);
+}
+
+FilePath UserSettings::getWorkingDirectoryValue(const std::string& key) const
+{
+ return module_context::resolveAliasedPath(settings_.get(key, "~"));
+}
+
+
+void UserSettings::setWorkingDirectoryValue(const std::string& key,
+ const FilePath& filePath)
+{
+ if (module_context::createAliasedPath(filePath) == "~")
+ settings_.set(key, std::string("~"));
+ else
+ settings_.set(key, filePath.absolutePath());
+}
+
+int UserSettings::errorHandlerType() const
+{
+ return settings_.getInt("errorHandlerType",
+ modules::errors::ERRORS_TRACEBACK);
+}
+
+void UserSettings::setErrorHandlerType(int type)
+{
+ settings_.set("errorHandlerType", type);
+}
+
+}// namespace session
diff --git a/src/cpp/session/SessionWorkerContext.cpp b/src/cpp/session/SessionWorkerContext.cpp
new file mode 100644
index 0000000..d2b0a7d
--- /dev/null
+++ b/src/cpp/session/SessionWorkerContext.cpp
@@ -0,0 +1,44 @@
+/*
+ * SessionWorkerContext.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <string>
+
+#include <core/Error.hpp>
+#include <core/Thread.hpp>
+#include <core/json/JsonRpc.hpp>
+
+#include <session/SessionClientEvent.hpp>
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace worker_context {
+
+// Worker RPC methods don't hold up an HTTP connection while the operation
+// executes. Instead, they return immediately and provide the results later,
+// using the client event queue.
+Error registerWorkerRpcMethod(const std::string& name,
+ const json::JsonRpcFunction& function)
+{
+ return module_context::registerRpcMethod(name,
+ boost::bind(module_context::executeAsync,
+ function,
+ _1,
+ _2));
+}
+
+} // namespace worker_context
+} // namespace session
diff --git a/src/cpp/session/consoleio/CMakeLists.txt b/src/cpp/session/consoleio/CMakeLists.txt
new file mode 100644
index 0000000..67aa6c6
--- /dev/null
+++ b/src/cpp/session/consoleio/CMakeLists.txt
@@ -0,0 +1,62 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project(CONSOLEIO)
+
+# include files
+file(GLOB_RECURSE CONSOLEIO_HEADER_FILES "*.h*")
+
+# set include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+)
+
+set(CONSOLEIO_SOURCE_FILES
+ ConsoleIOMain.cpp
+)
+
+
+# configure consoleio.rc
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/consoleio.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/consoleio.rc)
+
+
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/consoleio.exe.manifest
+ ${CMAKE_CURRENT_BINARY_DIR}/consoleio.exe.manifest COPYONLY)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ COMMAND windres.exe
+ -I "."
+ -i "consoleio.rc"
+ -o "${CMAKE_CURRENT_BINARY_DIR}/res.o"
+ -Ocoff
+ DEPENDS
+ "${CMAKE_CURRENT_BINARY_DIR}/consoleio.rc"
+ "${CMAKE_CURRENT_SOURCE_DIR}/consoleio.exe.manifest")
+set(CONSOLEIO_SOURCE_FILES
+ ${CONSOLEIO_SOURCE_FILES}
+ "${CMAKE_CURRENT_BINARY_DIR}/res.o")
+
+add_executable(consoleio
+ ${CONSOLEIO_SOURCE_FILES}
+)
+
+# set link dependencies
+target_link_libraries(consoleio
+ rstudio-core
+)
+
+install(TARGETS consoleio DESTINATION ${RSTUDIO_INSTALL_BIN})
diff --git a/src/cpp/session/consoleio/ConsoleIOMain.cpp b/src/cpp/session/consoleio/ConsoleIOMain.cpp
new file mode 100644
index 0000000..e8237cc
--- /dev/null
+++ b/src/cpp/session/consoleio/ConsoleIOMain.cpp
@@ -0,0 +1,473 @@
+/*
+ * ConsoleIOMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <windows.h>
+
+#include <boost/algorithm/string.hpp>
+
+#define BOOST_THREAD_USE_LIB
+#include <core/BoostThread.hpp>
+#include <core/Error.hpp>
+
+using namespace core;
+
+HANDLE hSnapshotOutput;
+// Use this event to ensure that the transferConsoleOutToStdErr thread gets a
+// chance to dump the console one last time before exiting
+HANDLE hReadyForExitEvent;
+
+/*
+ * ConsoleIO is an Win32 program that allows a console program that uses
+ * low-level console input (_getch()) to be fed via stdin redirection.
+ *
+ * The program that needs to be executed and its arguments, should be
+ * passed as arguments to consoleio.
+ */
+
+void print_usage()
+{
+ std::cerr << "Usage: consoleio.exe <program> [program-arguments]"
+ << std::endl;
+}
+
+inline void print_error(const std::string& label = std::string())
+{
+ DWORD err = ::GetLastError();
+ if (label.empty())
+ std::cerr << "Error " << err << std::endl;
+ else
+ std::cerr << "Error calling " << label << ": " << err << std::endl;
+}
+
+std::string removeCommandFromCommandLine(const std::string& cmd,
+ const std::string& cmdLine)
+{
+ size_t pos = cmdLine.find(cmd);
+ if (pos != 0 && pos != 1)
+ return "";
+ pos += cmd.size();
+
+ for ( ; cmdLine[pos] != ' ' && pos < cmdLine.size(); pos++)
+ {
+ }
+
+ for ( ; cmdLine[pos] == ' ' && pos < cmdLine.size(); pos++)
+ {
+ }
+
+ return cmdLine.substr(pos);
+}
+
+bool send_console_input_char(HANDLE hConsoleIn, char c)
+{
+ WORD keyCode = 0;
+ char keyChar = c;
+
+ switch (c)
+ {
+ case '\r':
+ keyCode = VK_RETURN;
+ break;
+ case '\n':
+ // Skip newlines because Enters come in as \r\n, even though we need to
+ // send them to the console as \r lest we get two returns.
+ // Note that this introduces a bug if Enter is ever sent as \n, so,
+ // don't do that.
+ return true;
+ }
+
+ INPUT_RECORD inputRecords[2];
+ ZeroMemory(inputRecords, sizeof(inputRecords));
+
+ inputRecords[0].EventType = KEY_EVENT;
+ inputRecords[0].Event.KeyEvent.bKeyDown = TRUE;
+ inputRecords[0].Event.KeyEvent.wRepeatCount = 1;
+ inputRecords[0].Event.KeyEvent.wVirtualKeyCode = keyCode;
+ inputRecords[0].Event.KeyEvent.wVirtualScanCode = 0;
+ inputRecords[0].Event.KeyEvent.uChar.AsciiChar = keyChar;
+
+ inputRecords[1].EventType = KEY_EVENT;
+ inputRecords[1].Event.KeyEvent.bKeyDown = FALSE;
+ inputRecords[1].Event.KeyEvent.wRepeatCount = 1;
+ inputRecords[1].Event.KeyEvent.wVirtualKeyCode = keyCode;
+ inputRecords[1].Event.KeyEvent.wVirtualScanCode = 0;
+ inputRecords[1].Event.KeyEvent.uChar.AsciiChar = keyChar;
+
+ DWORD written;
+ return ::WriteConsoleInput(hConsoleIn,
+ inputRecords,
+ 2,
+ &written);
+}
+
+template <class InputIterator>
+bool send_console_input(HANDLE hConsoleIn,
+ InputIterator begin,
+ InputIterator end)
+{
+ for (; begin != end; begin++)
+ if (!send_console_input_char(hConsoleIn, *begin))
+ return false;
+ return true;
+}
+
+// Grab one row of console output, with up to the specified number of columns
+// (or exactly that number of columns if padToColumnWidth is set). The output
+// will be appended to pOutput.
+BOOL capture_console_output_row(HANDLE hConsoleOut,
+ SHORT row,
+ SHORT columns,
+ bool padToColumnWidth,
+ std::string* pOutput)
+{
+ static std::vector<CHAR_INFO> buffer;
+
+ if (columns == 0)
+ return true;
+
+ COORD targetSize = {columns, 1};
+ buffer.resize(targetSize.X * targetSize.Y);
+
+ COORD from = {};
+
+ // left, top, right, bottom
+ SMALL_RECT rect = {0, row, columns-1, row};
+
+ if (!::ReadConsoleOutput(hConsoleOut,
+ &(buffer[0]),
+ targetSize,
+ from,
+ &rect))
+ {
+ return false;
+ }
+
+ for (SHORT col = 0; col <= rect.Right; col++)
+ {
+ CHAR c = buffer[col].Char.AsciiChar;
+ pOutput->push_back(c);
+ }
+ if (padToColumnWidth)
+ {
+ SHORT extraPadding = columns - rect.Right - 1;
+ for (SHORT i = 0; i < extraPadding; i++)
+ pOutput->push_back(' ');
+ }
+ return true;
+}
+
+BOOL capture_console_output(HANDLE hConsoleOut, std::string* pOutput)
+{
+ static std::vector<CHAR_INFO> buffer;
+
+ CONSOLE_SCREEN_BUFFER_INFO csbInfo;
+ if (!::GetConsoleScreenBufferInfo(hConsoleOut, &csbInfo))
+ return false;
+
+ COORD& cursor = csbInfo.dwCursorPosition;
+ COORD& consoleSize = csbInfo.dwSize;
+
+ if (cursor.X == 0 && cursor.Y == 0)
+ return true;
+
+ for (SHORT row = 0; row < cursor.Y; row++)
+ {
+ if (!capture_console_output_row(hConsoleOut, row, consoleSize.Y,
+ false, pOutput))
+ {
+ return false;
+ }
+
+ // Remove trailing spaces
+ std::string::iterator trimPos = pOutput->end();
+ while (trimPos != pOutput->begin())
+ {
+ if (*(trimPos-1) != ' ')
+ break;
+ trimPos--;
+ }
+ pOutput->erase(trimPos, pOutput->end());
+
+ pOutput->push_back('\r');
+ pOutput->push_back('\n');
+ }
+
+ // Now grab the line that contains the cursor
+ if (!capture_console_output_row(hConsoleOut, cursor.Y, cursor.X, true, pOutput))
+ return false;
+
+ return true;
+}
+
+// Dump the entire console buffer (up to the cursor) of hConsole
+// and write it to hOutput
+BOOL write_to_handle(const std::string& output, HANDLE hOutput)
+{
+ const CHAR* pData = output.c_str();
+ DWORD bytesToWrite = output.size();
+ while (bytesToWrite > 0)
+ {
+ DWORD bytesWritten;
+ if (!::WriteFile(hOutput,
+ pData,
+ bytesToWrite,
+ &bytesWritten,
+ NULL))
+ {
+ return false;
+ }
+ bytesToWrite -= bytesWritten;
+ pData += bytesWritten;
+ }
+ return true;
+}
+
+bool isNotSpace(char c)
+{
+ return c != ' ';
+}
+
+std::string calcDifference(const std::string& current,
+ const std::string& prev)
+{
+ std::string::const_iterator itCur = current.begin(), itPrev = prev.begin();
+ for (;
+ itCur != current.end() && itPrev != prev.end() && *itCur == *itPrev;
+ itCur++, itPrev++)
+ {
+ }
+
+ if (itPrev == prev.end())
+ {
+ // Entire prefix matched--good!
+ return std::string(itCur, current.end());
+ }
+
+ if (std::find_if(itPrev, prev.end(), isNotSpace) != prev.end())
+ {
+ // Significant (non-space) part of prev was not found in current.
+ // Send \f which causes the screen to clear, then the entire current
+ // snapshot.
+ return "\f" + current;
+ }
+
+ // If we got here, current starts with prev except for the end of prev which
+ // consists only of spaces.
+
+ if (itCur != current.end() && (*itCur == '\r' || *itCur == '\n'))
+ {
+ // We'll accept newline as a substitute for those spaces, this is common
+ // due to trimming which occurs on all lines but the last one when
+ // capturing console output.
+ return std::string(itCur, current.end());
+ }
+
+ return "\f" + current;
+}
+
+void transferStdInToConsole(HANDLE hConIn)
+{
+ HANDLE hStdIn = ::GetStdHandle(STD_INPUT_HANDLE);
+ std::vector<char> buf(1024);
+ DWORD bytesRead;
+
+ while (true)
+ {
+ if (!::ReadFile(hStdIn, &(buf[0]), buf.size(), &bytesRead, NULL))
+ break;
+
+ send_console_input(hConIn, buf.begin(), buf.begin() + bytesRead);
+ }
+}
+
+void transferConsoleOutToStdErr(HANDLE hConOut)
+{
+ std::string lastKnownConsoleContents;
+ std::string output;
+ while (true)
+ {
+ if (!::SetEvent(hReadyForExitEvent))
+ {
+ print_error("SetEvent");
+ }
+
+ ::Sleep(500);
+
+ output.clear();
+ if (!capture_console_output(hConOut, &output))
+ {
+ print_error("capture_console_output");
+ continue;
+ }
+
+ std::string valueToWrite = calcDifference(output,
+ lastKnownConsoleContents);
+
+ if (valueToWrite.empty())
+ continue;
+
+ lastKnownConsoleContents = output;
+
+ if (!write_to_handle(valueToWrite, hSnapshotOutput))
+ {
+ print_error("dump_console_output");
+ continue;
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ if (argc < 2)
+ {
+ std::cerr << "Error: Not enough arguments" << std::endl;
+ print_usage();
+ return 1;
+ }
+
+ std::string cmd = removeCommandFromCommandLine(argv[0],
+ ::GetCommandLine());
+
+ // Use cmd.exe to allow shell commands like "dir" to work properly
+ cmd = "cmd.exe /s /c \"" + cmd + "\"";
+ std::vector<char> cmdBuf(cmd.size() + 1, '\0');
+ cmd.copy(&(cmdBuf[0]), cmd.size());
+
+ SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
+ sa.bInheritHandle = true;
+
+ HANDLE hConIn = ::CreateFile("CONIN$",
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ &sa,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (hConIn == INVALID_HANDLE_VALUE)
+ {
+ print_error("CreateFile");
+ return 1;
+ }
+
+ HANDLE hConOut = ::CreateFile("CONOUT$",
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ &sa,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (hConOut == INVALID_HANDLE_VALUE)
+ {
+ print_error("CreateFile");
+ return 1;
+ }
+
+ hSnapshotOutput = ::GetStdHandle(STD_ERROR_HANDLE);
+ if (!::SetStdHandle(STD_ERROR_HANDLE, hConOut))
+ {
+ print_error("SetStdHandle");
+ return 1;
+ }
+
+ SMALL_RECT screenSize = { 1, 1, 3, 3 };
+ if (!::SetConsoleWindowInfo(hConOut, TRUE, &screenSize))
+ {
+ print_error("SetConsoleWindowInfo");
+ return 1;
+ }
+
+ COORD newSize = {80, 160};
+ if (!::SetConsoleScreenBufferSize(hConOut, newSize))
+ {
+ print_error("SetConsoleScreenBufferSize");
+ return 1;
+ }
+
+ STARTUPINFO si = {0};
+ si.cb = sizeof(STARTUPINFO);
+ si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+ si.wShowWindow = SW_HIDE;
+ si.hStdInput = hConIn;
+ si.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
+ si.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
+
+ PROCESS_INFORMATION pi = {0};
+
+ if (!::CreateProcess(NULL,
+ &(cmdBuf[0]),
+ NULL,
+ NULL,
+ TRUE,
+ 0,
+ NULL,
+ NULL,
+ &si,
+ &pi))
+ {
+ print_error("CreateProcess");
+ return 1;
+ }
+
+ hReadyForExitEvent = ::CreateEvent(NULL, true, true, NULL);
+ if (hReadyForExitEvent == INVALID_HANDLE_VALUE)
+ {
+ print_error("CreateEvent");
+ return 1;
+ }
+
+ boost::thread(&transferStdInToConsole, hConIn);
+ boost::thread(&transferConsoleOutToStdErr, hConOut);
+
+ while (true)
+ {
+ DWORD waitResult = ::WaitForSingleObject(pi.hProcess,
+ INFINITE);
+ if (waitResult == WAIT_OBJECT_0)
+ {
+ // Process has exited
+ DWORD exitCode;
+ if (::GetExitCodeProcess(pi.hProcess, &exitCode))
+ {
+ if (::ResetEvent(hReadyForExitEvent))
+ {
+ ::WaitForSingleObject(hReadyForExitEvent, 2000);
+ }
+
+ return exitCode;
+ }
+ else
+ {
+ print_error("GetExitCodeProcess");
+ return 1;
+ }
+ }
+ else if (waitResult == WAIT_FAILED)
+ {
+ print_error("WaitForMultipleObjects");
+ return 1;
+ }
+ else
+ {
+ std::cerr << "Unexpected result from WaitForMultipleObjects: "
+ << waitResult
+ << std::endl;
+ return 1;
+ }
+ }
+}
diff --git a/src/cpp/session/consoleio/consoleio.exe.manifest b/src/cpp/session/consoleio/consoleio.exe.manifest
new file mode 100644
index 0000000..0ea9d96
--- /dev/null
+++ b/src/cpp/session/consoleio/consoleio.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="consoleio"
+ type="win32"/>
+ <description>consoleio</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/session/consoleio/consoleio.rc.in b/src/cpp/session/consoleio/consoleio.rc.in
new file mode 100644
index 0000000..c121e68
--- /dev/null
+++ b/src/cpp/session/consoleio/consoleio.rc.in
@@ -0,0 +1,29 @@
+#include "winuser.h"
+
+1 RT_MANIFEST "consoleio.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio ConsoleIO\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "consoleio\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "consoleio.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/session/http/.gitignore b/src/cpp/session/http/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/src/cpp/session/http/SessionHttpConnectionImpl.hpp b/src/cpp/session/http/SessionHttpConnectionImpl.hpp
new file mode 100644
index 0000000..159262e
--- /dev/null
+++ b/src/cpp/session/http/SessionHttpConnectionImpl.hpp
@@ -0,0 +1,227 @@
+/*
+ * SessionHttpConnectionImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_IMPL_HPP
+#define SESSION_HTTP_CONNECTION_IMPL_HPP
+
+
+#include <boost/array.hpp>
+
+#include <boost/utility.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/write.hpp>
+#include <boost/asio/placeholders.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/RequestParser.hpp>
+#include <core/http/SocketUtils.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <session/SessionHttpConnection.hpp>
+
+#include "SessionHttpConnectionUtils.hpp"
+
+namespace session {
+
+template <typename ProtocolType>
+class HttpConnectionImpl :
+ public HttpConnection,
+ public boost::enable_shared_from_this<HttpConnectionImpl<ProtocolType> >,
+ boost::noncopyable
+{
+public:
+ typedef boost::function<void(
+ boost::shared_ptr<HttpConnectionImpl<ProtocolType> >)> Handler;
+
+
+public:
+ HttpConnectionImpl(boost::asio::io_service& ioService,
+ const Handler& handler)
+ : socket_(ioService), handler_(handler)
+ {
+ }
+
+ virtual ~HttpConnectionImpl()
+ {
+ // close here as a precaution
+ try
+ {
+ close();
+ }
+ catch(...)
+ {
+ }
+ }
+
+public:
+
+ // request/resposne (used by Handler)
+ virtual const core::http::Request& request() { return request_; }
+
+ virtual void sendResponse(const core::http::Response &response)
+ {
+ try
+ {
+ // write the response
+ boost::asio::write(socket_,
+ response.toBuffers(
+ core::http::Header::connectionClose()));
+ }
+ catch(const boost::system::system_error& e)
+ {
+ // establish error
+ core::Error error = core::Error(e.code(), ERROR_LOCATION);
+ error.addProperty("request-uri", request_.uri());
+
+ // log the error if it wasn't connection terminated
+ if (!core::http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // always close connection
+ try
+ {
+ close();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ // close (occurs automatically after writeResponse, here in case it
+ // need to be closed in other circumstances
+ virtual void close()
+ {
+ // always close connection
+ core::Error error = core::http::closeSocket(socket_);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // other useful introspection methods
+ virtual std::string requestId() const { return requestId_; }
+
+ // start reading the request from the connection. once a request
+ // is successfully read the Connection is passed to the Handler
+ void startReading()
+ {
+ readSome();
+ }
+
+ // get the socket
+ typename ProtocolType::socket& socket() { return socket_; }
+
+
+private:
+
+ // async request reading interface
+ void readSome()
+ {
+ // NOTE: the call to HttpConnection::shared_from_this() is what
+ // continues to keep this object alive during processing. when we
+ // are finished processing the connection will go out of scope
+ // (unless the handler chooses to retain a copy of it e.g. to perform
+ // processing in a background thread)
+
+ socket_.async_read_some(
+ boost::asio::buffer(buffer_),
+ boost::bind(
+ &HttpConnectionImpl<ProtocolType>::handleRead,
+ HttpConnectionImpl<ProtocolType>::shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+
+ void handleRead(const boost::system::error_code& e,
+ std::size_t bytesTransferred)
+ {
+ try
+ {
+ if (!e)
+ {
+ // parse next chunk
+ core::http::RequestParser::status status = requestParser_.parse(
+ request_,
+ buffer_.data(),
+ buffer_.data() + bytesTransferred);
+
+ // error - return bad request
+ if (status == core::http::RequestParser::error)
+ {
+ core::http::Response response;
+ response.setStatusCode(core::http::status::BadRequest);
+ sendResponse(response);
+
+ // no more async operations w/ shared_from_this() initiated so this
+ // object has no more references to it and will be destroyed
+ }
+
+ // incomplete -- keep reading
+ else if (status == core::http::RequestParser::incomplete)
+ {
+ readSome();
+ }
+
+ // got valid request -- handle it
+ else
+ {
+ // establish request id
+ requestId_ = connection::rstudioRequestIdFromRequest(request_);
+
+ // call handler
+ handler_(HttpConnectionImpl<ProtocolType>::shared_from_this());
+
+ // no more async operations w/ shared_from_this() initiated so this
+ // object has no more references to it and will be destroyed. note
+ // though that the handler may choose to retain a reference
+ // (e.g. if it handles the connection in a background thread)
+ }
+ }
+ else // error reading
+ {
+ // log the error if it wasn't connection terminated
+ core::Error error(e, ERROR_LOCATION);
+ if (!core::http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+
+ // close the connection
+ close();
+
+ // no more async operations w/ shared_from_this() initiated so this
+ // object has no more references to it and will be destroyed
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+private:
+ typename ProtocolType::socket socket_;
+ boost::array<char, 8192> buffer_ ;
+ core::http::RequestParser requestParser_ ;
+ core::http::Request request_;
+ std::string requestId_;
+ Handler handler_;
+};
+
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_HPP
+
diff --git a/src/cpp/session/http/SessionHttpConnectionListenerImpl.hpp b/src/cpp/session/http/SessionHttpConnectionListenerImpl.hpp
new file mode 100644
index 0000000..eff1fbf
--- /dev/null
+++ b/src/cpp/session/http/SessionHttpConnectionListenerImpl.hpp
@@ -0,0 +1,319 @@
+/*
+ * SessionHttpConnectionListenerImpl.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_LISTENER_IMPL_HPP
+#define SESSION_HTTP_CONNECTION_LISTENER_IMPL_HPP
+
+#include <queue>
+
+#include <boost/shared_ptr.hpp>
+
+#include <boost/utility.hpp>
+#include <boost/asio/placeholders.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/FilePath.hpp>
+#include <core/Error.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/system/System.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/http/SocketAcceptorService.hpp>
+
+#include <core/FileSerializer.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionConstants.hpp>
+
+#include <session/SessionHttpConnection.hpp>
+#include <session/SessionHttpConnectionQueue.hpp>
+#include <session/SessionHttpConnectionListener.hpp>
+
+#include "SessionHttpConnectionImpl.hpp"
+
+
+namespace session {
+
+namespace {
+
+bool isShutdownError(const boost::system::error_code& ec)
+{
+ // for windows check explicitly for is not a socket error
+#ifdef _WIN32
+ if (ec.value() == WSAENOTSOCK)
+ return true;
+#endif
+
+ // - operation cancelled (happens while shutting down the server)
+ // - invalid argument (happens if socket is closed before we
+ // can actually peform the handleAccept)
+ // - bad file descriptor (simillar to above)
+ return (ec == boost::asio::error::operation_aborted ||
+ ec == boost::asio::error::invalid_argument ||
+ ec == boost::system::errc::bad_file_descriptor);
+}
+
+}
+
+
+template <typename ProtocolType>
+class HttpConnectionListenerImpl : public HttpConnectionListener,
+ boost::noncopyable
+{
+protected:
+ HttpConnectionListenerImpl() : started_(false) {}
+
+ // COPYING: boost::noncopyable
+
+public:
+ virtual core::Error start()
+ {
+ // cleanup any existing networking state
+ core::Error error = cleanup();
+ if (error)
+ return error ;
+
+ // initialize acceptor
+ error = initializeAcceptor(&acceptorService_);
+ if (error)
+ return error;
+
+ // accept next connection (asynchronously)
+ acceptNextConnection();
+
+ // block all signals for launch of listener thread (will cause it
+ // to never receive signals)
+ core::system::SignalBlocker signalBlocker;
+ error = signalBlocker.blockAll();
+ if (error)
+ return error ;
+
+ // launch the listener thread
+ try
+ {
+ using boost::bind;
+ boost::thread listenerThread(bind(&boost::asio::io_service::run,
+ &(acceptorService_.ioService())));
+ listenerThread_ = listenerThread.move();
+
+ // set started flag
+ started_ = true;
+
+ return core::Success();
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ return core::Error(boost::thread_error::ec_from_exception(e),
+ ERROR_LOCATION);
+ }
+ }
+
+ virtual void stop()
+ {
+ // don't stop if we never started
+ if (!started_)
+ {
+ LOG_WARNING_MESSAGE("Stopping HttpConnectionListener "
+ "which wasn't started");
+ return;
+ }
+
+ // close acceptor
+ boost::system::error_code ec ;
+ acceptorService_.closeAcceptor(ec);
+ if (ec)
+ LOG_ERROR(core::Error(ec, ERROR_LOCATION));
+
+ // stop the server
+ ioService().stop();
+
+ // join the thread and wait for it complete
+ if (listenerThread_.joinable())
+ {
+ if (!listenerThread_.timed_join(boost::posix_time::seconds(3)))
+ {
+ LOG_WARNING_MESSAGE(
+ "HttpConnectionListener didn't stop within 3 sec");
+ }
+
+ listenerThread_.detach();
+ }
+
+ // allow subclass specific cleanup
+ core::Error error = cleanup();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // connection queues
+ virtual HttpConnectionQueue& mainConnectionQueue()
+ {
+ return mainConnectionQueue_;
+ }
+
+ virtual HttpConnectionQueue& eventsConnectionQueue()
+ {
+ return eventsConnectionQueue_;
+ }
+
+protected:
+
+ virtual bool authenticate(boost::shared_ptr<HttpConnection>)
+ {
+ return true;
+ }
+
+private:
+ // required subclass hooks
+ virtual core::Error initializeAcceptor(
+ core::http::SocketAcceptorService<ProtocolType>* pAcceptor) = 0;
+
+ virtual bool validateConnection(
+ boost::shared_ptr<HttpConnectionImpl<ProtocolType> > ptrConnection) = 0;
+
+ virtual core::Error cleanup() = 0 ;
+
+private:
+ boost::asio::io_service& ioService() { return acceptorService_.ioService(); }
+
+ void acceptNextConnection()
+ {
+ // create the connection
+ ptrNextConnection_.reset( new HttpConnectionImpl<ProtocolType>(
+ ioService(),
+ boost::bind(
+ &HttpConnectionListenerImpl<ProtocolType>::enqueConnection,
+ this,
+ _1))
+ );
+
+ // wait for next connection
+ acceptorService_.asyncAccept(
+ ptrNextConnection_->socket(),
+ boost::bind(&HttpConnectionListenerImpl<ProtocolType>::handleAccept,
+ this,
+ boost::asio::placeholders::error)
+ );
+ }
+
+
+ void handleAccept(const boost::system::error_code& ec)
+ {
+ try
+ {
+ if (!ec)
+ {
+ // validate the connection
+ if (validateConnection(ptrNextConnection_))
+ {
+ // start reading from the connection
+ ptrNextConnection_->startReading();
+ }
+ else
+ {
+ // invalid client: close the connection immediately
+ ptrNextConnection_->close();
+ }
+ }
+ else
+ {
+ // for errors, log and continue,but don't log errors caused
+ // by normal course of socket shutdown
+ if (!isShutdownError(ec))
+ LOG_ERROR(core::Error(ec, ERROR_LOCATION)) ;
+ }
+ }
+ catch(const boost::system::system_error& e)
+ {
+ LOG_ERROR_MESSAGE(std::string("Unexpected exception: ") + e.what());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // ALWAYS accept next connection
+ try
+ {
+ acceptNextConnection() ;
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ // NOTE: this logic is duplicated btw here and NamedPipeConnectionListener
+
+ void enqueConnection(
+ boost::shared_ptr<HttpConnectionImpl<ProtocolType> > ptrConnection)
+ {
+ // convert to cannonical HttpConnection
+ boost::shared_ptr<HttpConnection> ptrHttpConnection =
+ boost::static_pointer_cast<HttpConnection>(ptrConnection);
+
+ if (!authenticate(ptrHttpConnection))
+ {
+ core::http::Response response;
+ response.setStatusCode(403);
+ response.setStatusMessage("Forbidden");
+ ptrConnection->sendResponse(response);
+ return;
+ }
+
+ // check for the special rpc/abort endpoint and abort if requested
+ // we do this in the background listener thread so it can always
+ // be processed even if the foreground thread is deadlocked or otherwise
+ // unresponsive
+ if (connection::checkForAbort(
+ ptrHttpConnection,
+ boost::bind(&HttpConnectionListenerImpl<ProtocolType>::cleanup,
+ this)))
+ {
+ return;
+ }
+
+ // check for a suspend_session. done here as well as in foreground to
+ // allow clients without the requisite client-id and/or version header
+ // to also initiate a suspend (e.g. an admin/supervisor process)
+ if (connection::checkForSuspend(ptrHttpConnection))
+ return;
+
+ // place the connection on the correct queue
+ if (connection::isGetEvents(ptrHttpConnection))
+ eventsConnectionQueue_.enqueConnection(ptrHttpConnection);
+ else
+ mainConnectionQueue_.enqueConnection(ptrHttpConnection);
+ }
+
+private:
+
+ // acceptor service (includes io service)
+ core::http::SocketAcceptorService<ProtocolType> acceptorService_;
+
+ // next connection
+ boost::shared_ptr<HttpConnectionImpl<ProtocolType> > ptrNextConnection_;
+
+ // connection queues
+ HttpConnectionQueue mainConnectionQueue_;
+ HttpConnectionQueue eventsConnectionQueue_;
+
+ // listener thread
+ boost::thread listenerThread_ ;
+
+ // flag indicating we've started
+ bool started_;
+};
+
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_LISTENER_IMPL_HPP
+
diff --git a/src/cpp/session/http/SessionHttpConnectionQueue.cpp b/src/cpp/session/http/SessionHttpConnectionQueue.cpp
new file mode 100644
index 0000000..ae088c0
--- /dev/null
+++ b/src/cpp/session/http/SessionHttpConnectionQueue.cpp
@@ -0,0 +1,139 @@
+/*
+ * SessionHttpConnectionQueue.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionHttpConnectionQueue.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/Thread.hpp>
+
+#include <core/http/Request.hpp>
+
+using namespace core ;
+
+namespace session {
+
+void HttpConnectionQueue::enqueConnection(
+ boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ // enque
+ queue_.push(ptrConnection);
+ }
+ END_LOCK_MUTEX
+
+ pWaitCondition_->notify_all();
+}
+
+
+boost::shared_ptr<HttpConnection> HttpConnectionQueue::doDequeConnection()
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ if (!queue_.empty())
+ {
+ // remove it
+ boost::shared_ptr<HttpConnection> next = queue_.front();
+ queue_.pop();
+
+ // note last connection time
+ lastConnectionTime_ =
+ boost::posix_time::second_clock::universal_time();
+
+ // return it
+ return next;
+ }
+ else
+ {
+ return boost::shared_ptr<HttpConnection>();
+ }
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return boost::shared_ptr<HttpConnection>();
+}
+
+boost::shared_ptr<HttpConnection> HttpConnectionQueue::dequeConnection()
+{
+ // perform the deque
+ boost::shared_ptr<HttpConnection> connection = doDequeConnection();
+
+ // return the connection
+ return connection;
+}
+
+boost::shared_ptr<HttpConnection> HttpConnectionQueue::dequeConnection(
+ const boost::posix_time::time_duration& waitDuration)
+{
+ // first see if we already have one
+ boost::shared_ptr<HttpConnection> ptrConnection = dequeConnection();
+ if (ptrConnection)
+ return ptrConnection;
+
+ // now wait the specified interval for one to materialize
+ if (waitForConnection(waitDuration))
+ return dequeConnection();
+ else
+ return boost::shared_ptr<HttpConnection>();
+}
+
+std::string HttpConnectionQueue::peekNextConnectionUri()
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ if (!queue_.empty())
+ return queue_.front()->request().uri();
+ else
+ return std::string();
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return std::string();
+}
+
+bool HttpConnectionQueue::waitForConnection(
+ const boost::posix_time::time_duration& waitDuration)
+{
+ using namespace boost;
+ try
+ {
+ unique_lock<mutex> lock(*pMutex_);
+ system_time timeoutTime = get_system_time() + waitDuration;
+ return pWaitCondition_->timed_wait(lock, timeoutTime);
+ }
+ catch(const thread_resource_error& e)
+ {
+ Error waitError(boost::thread_error::ec_from_exception(e), ERROR_LOCATION) ;
+ LOG_ERROR(waitError);
+ return false ;
+ }
+}
+
+boost::posix_time::ptime HttpConnectionQueue::lastConnectionTime()
+{
+ LOCK_MUTEX(*pMutex_)
+ {
+ return lastConnectionTime_;
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return boost::posix_time::ptime();
+}
+
+} // namespace session
diff --git a/src/cpp/session/http/SessionHttpConnectionUtils.cpp b/src/cpp/session/http/SessionHttpConnectionUtils.cpp
new file mode 100644
index 0000000..61dc8ba
--- /dev/null
+++ b/src/cpp/session/http/SessionHttpConnectionUtils.cpp
@@ -0,0 +1,236 @@
+/*
+ * SessionHttpConnectionUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionHttpConnectionUtils.hpp"
+
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FileSerializer.hpp>
+
+
+#include <core/http/Response.hpp>
+#include <core/http/Request.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionConstants.hpp>
+
+
+
+namespace session {
+
+void HttpConnection::sendJsonRpcError(const core::Error& error)
+{
+ core::json::JsonRpcResponse jsonRpcResponse;
+ jsonRpcResponse.setError(error);
+ sendJsonRpcResponse(jsonRpcResponse);
+}
+
+void HttpConnection::sendJsonRpcResponse()
+{
+ core::json::JsonRpcResponse jsonRpcResponse ;
+ sendJsonRpcResponse(jsonRpcResponse);
+}
+
+void HttpConnection::sendJsonRpcResponse(
+ const core::json::JsonRpcResponse& jsonRpcResponse)
+{
+ // setup response
+ core::http::Response response ;
+
+ // automagic gzip support
+ if (request().acceptsEncoding(core::http::kGzipEncoding))
+ response.setContentEncoding(core::http::kGzipEncoding);
+
+ // set response
+ core::json::setJsonRpcResponse(jsonRpcResponse, &response);
+
+ // send the response
+ sendResponse(response);
+}
+
+
+
+namespace connection {
+
+std::string rstudioRequestIdFromRequest(const core::http::Request& request)
+{
+ return request.headerValue("X-RS-RID");
+}
+
+
+bool isMethod(boost::shared_ptr<HttpConnection> ptrConnection,
+ const std::string& method)
+{
+ return boost::algorithm::ends_with(ptrConnection->request().uri(),
+ "rpc/" + method);
+}
+
+bool isGetEvents(boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ return boost::algorithm::ends_with(ptrConnection->request().uri(),
+ "events/get_events");
+}
+
+void handleAbortNextProjParam(
+ boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ std::string nextProj;
+ core::json::JsonRpcRequest jsonRpcRequest;
+ core::Error error = core::json::parseJsonRpcRequest(
+ ptrConnection->request().body(),
+ &jsonRpcRequest);
+ if (!error)
+ {
+ error = core::json::readParam(jsonRpcRequest.params, 0, &nextProj);
+ if (error)
+ LOG_ERROR(error);
+
+ if (!nextProj.empty())
+ {
+ // NOTE: this must be synchronized with the implementation of
+ // ProjectContext::setNextSessionProject -- we do this using
+ // constants rather than code so that this code (which runs in
+ // a background thread) don't call into the projects module (which
+ // is designed to be foreground and single-threaded)
+ core::FilePath userScratch = session::options().userScratchPath();
+ core::FilePath settings = userScratch.complete(kProjectsSettings);
+ error = settings.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ core::FilePath writePath = settings.complete(kNextSessionProject);
+ core::Error error = core::writeStringToFile(writePath, nextProj);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+}
+
+bool checkForAbort(boost::shared_ptr<HttpConnection> ptrConnection,
+ const boost::function<void()> cleanupHandler)
+{
+ if (isMethod(ptrConnection, "abort"))
+ {
+ // respond and log (try/catch so we are ALWAYS guaranteed to abort)
+ try
+ {
+ // handle the nextProj param if it's specified
+ handleAbortNextProjParam(ptrConnection);
+
+ // respond
+ ptrConnection->sendJsonRpcResponse();
+
+ // log
+ LOG_WARNING_MESSAGE("Abort requested");
+ }
+ catch(...)
+ {
+ }
+
+ // cleanup (if we don't do this then the user may be locked out of
+ // future requests). note that this should occur in the normal
+ // course of a graceful shutdown but we do it here anyway just
+ // to be paranoid
+ try
+ {
+ if (cleanupHandler)
+ cleanupHandler();
+ }
+ catch(...)
+ {
+ }
+
+ // abort
+ ::abort();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// on windows we allow suspend_session to be handled on the foreground
+// thread since we don't have a way to ::kill on that that platform
+#ifdef _WIN32
+
+bool checkForSuspend(boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ return false;
+}
+
+#else
+
+bool checkForSuspend(boost::shared_ptr<HttpConnection> ptrConnection)
+{
+ using namespace core::json;
+ if (isMethod(ptrConnection, "suspend_session"))
+ {
+ bool force = false;
+ JsonRpcRequest jsonRpcRequest;
+ core::Error error = parseJsonRpcRequest(ptrConnection->request().body(),
+ &jsonRpcRequest);
+ if (error)
+ {
+ ptrConnection->sendJsonRpcError(error);
+ }
+ else if ((error = readParam(jsonRpcRequest.params, 0, &force)))
+ {
+ ptrConnection->sendJsonRpcError(error);
+ }
+ else
+ {
+ // send a signal to this process to suspend
+ using namespace core::system;
+ sendSignalToSelf(force ? SigUsr2 : SigUsr1);
+
+ // send response
+ ptrConnection->sendJsonRpcResponse();
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+#endif
+
+bool authenticate(boost::shared_ptr<HttpConnection> ptrConnection,
+ const std::string& secret)
+{
+ // allow all requests if no secret
+ if (secret.empty())
+ return true;
+
+ // validate against shared secret
+ return secret == ptrConnection->request().headerValue("X-Shared-Secret");
+}
+
+} // namespace connection
+} // namespace session
+
+
diff --git a/src/cpp/session/http/SessionHttpConnectionUtils.hpp b/src/cpp/session/http/SessionHttpConnectionUtils.hpp
new file mode 100644
index 0000000..1f5185b
--- /dev/null
+++ b/src/cpp/session/http/SessionHttpConnectionUtils.hpp
@@ -0,0 +1,58 @@
+/*
+ * SessionHttpConnectionUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_UTILS_HPP
+#define SESSION_HTTP_CONNECTION_UTILS_HPP
+
+#include <session/SessionHttpConnection.hpp>
+
+#include <string>
+
+#include <boost/function.hpp>
+
+namespace core {
+namespace http {
+ class Request;
+}
+}
+
+namespace session {
+namespace connection {
+
+std::string rstudioRequestIdFromRequest(const core::http::Request& request);
+
+bool isMethod(boost::shared_ptr<HttpConnection> ptrConnection,
+ const std::string& method);
+
+
+bool isGetEvents(boost::shared_ptr<HttpConnection> ptrConnection);
+
+void handleAbortNextProjParam(
+ boost::shared_ptr<HttpConnection> ptrConnection);
+
+bool checkForAbort(boost::shared_ptr<HttpConnection> ptrConnection,
+ const boost::function<void()> cleanupHandler);
+
+bool checkForSuspend(boost::shared_ptr<HttpConnection> ptrConnection);
+
+bool authenticate(boost::shared_ptr<HttpConnection> ptrConnection,
+ const std::string& secret);
+
+
+} // namespace connection
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_HPP
+
diff --git a/src/cpp/session/http/SessionLocalStreamHttpConnectionListener.hpp b/src/cpp/session/http/SessionLocalStreamHttpConnectionListener.hpp
new file mode 100644
index 0000000..0569fbf
--- /dev/null
+++ b/src/cpp/session/http/SessionLocalStreamHttpConnectionListener.hpp
@@ -0,0 +1,133 @@
+/*
+ * SessionLocalStreamHttpConnectionListener.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include <vector>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/system/PosixUser.hpp>
+
+#include <core/http/LocalStreamSocketUtils.hpp>
+
+#include "SessionHttpConnectionListenerImpl.hpp"
+
+using namespace core ;
+
+namespace session {
+
+// implementation of local stream http connection listener
+class LocalStreamHttpConnectionListener :
+ public HttpConnectionListenerImpl<boost::asio::local::stream_protocol>
+{
+public:
+ LocalStreamHttpConnectionListener(const FilePath& streamPath,
+ core::system::FileMode streamFileMode,
+ const std::string& secret,
+ int limitRpcClientUid)
+ : localStreamPath_(streamPath),
+ streamFileMode_(streamFileMode),
+ secret_(secret)
+ {
+ if (limitRpcClientUid != -1)
+ {
+ // always add current user
+ using namespace core::system::user;
+ permittedClients_.push_back(currentUserIdentity().userId);
+
+ // also add rpc client
+ permittedClients_.push_back(limitRpcClientUid);
+ }
+ }
+
+private:
+
+ virtual Error initializeAcceptor(
+ http::SocketAcceptorService<boost::asio::local::stream_protocol>*
+ pAcceptor)
+ {
+ return http::initLocalStreamAcceptor(*pAcceptor,
+ localStreamPath_,
+ streamFileMode_);
+ }
+
+ virtual bool validateConnection(
+ boost::shared_ptr<HttpConnectionImpl<boost::asio::local::stream_protocol> > ptrConnection)
+ {
+ // only validate if we have a set of permitted clients
+ if (permittedClients_.size() > 0)
+ {
+ // get socket
+ int socket = ptrConnection->socket().native();
+
+ // get client identity
+ core::system::user::UserIdentity userIdentity;
+ core::Error error = socketPeerIdentity(socket,&userIdentity);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // got it
+ uid_t clientUid = userIdentity.userId;
+
+ // check against list
+ for (std::vector<uid_t>::const_iterator it = permittedClients_.begin();
+ it != permittedClients_.end();
+ ++it)
+ {
+ if (clientUid == *it)
+ return true;
+ }
+
+ // didn't find it in the list
+ LOG_WARNING_MESSAGE("Connection attempted by invalid user-id: " +
+ safe_convert::numberToString(clientUid));
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+
+ virtual Error cleanup()
+ {
+ return localStreamPath_.removeIfExists();
+ }
+
+
+protected:
+
+ virtual bool authenticate(boost::shared_ptr<HttpConnection> ptrConnection)
+ {
+ return connection::authenticate(ptrConnection, secret_);
+ }
+
+private:
+ core::FilePath localStreamPath_;
+ core::system::FileMode streamFileMode_;
+
+ // desktop shared secret
+ std::string secret_;
+
+ // user-ids we will accept connections from
+ std::vector<uid_t> permittedClients_;
+};
+
+} // namespace session
diff --git a/src/cpp/session/http/SessionNamedPipeHttpConnectionListener.hpp b/src/cpp/session/http/SessionNamedPipeHttpConnectionListener.hpp
new file mode 100644
index 0000000..8c2c930
--- /dev/null
+++ b/src/cpp/session/http/SessionNamedPipeHttpConnectionListener.hpp
@@ -0,0 +1,487 @@
+/*
+ * SessionNamedPipeHttpConnectionListener.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionHttpConnectionListener.hpp>
+
+// Necessary to avoid compile error on Win x64
+#include <winsock2.h>
+
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio/buffer.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/Thread.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/RequestParser.hpp>
+#include <core/http/SocketUtils.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <windows.h>
+#include <sddl.h>
+
+// Vista+
+#ifndef PIPE_REJECT_REMOTE_CLIENTS
+#define PIPE_REJECT_REMOTE_CLIENTS 0x00000008
+#endif
+
+// Mingw doesn't have this declaration
+#ifndef _WIN64
+#define SDDL_REVISION_1 1
+extern "C" BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorA(
+ LPCSTR, DWORD, LPVOID *, PULONG);
+#endif
+
+#include <session/SessionOptions.hpp>
+
+#include "SessionHttpConnectionUtils.hpp"
+
+using namespace core ;
+
+#define kReadBufferSize 4096
+
+namespace session {
+
+class NamedPipeHttpConnection : public HttpConnection,
+ boost::noncopyable
+{
+public:
+ explicit NamedPipeHttpConnection(HANDLE hPipe)
+ : hPipe_(hPipe)
+ {
+ }
+
+ virtual ~NamedPipeHttpConnection()
+ {
+ try
+ {
+ close();
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ bool readRequest()
+ {
+ core::http::RequestParser parser;
+ CHAR buff[kReadBufferSize];
+ DWORD bytesRead;
+
+ while(TRUE)
+ {
+ // read from pipe
+ BOOL result = ::ReadFile(hPipe_, buff, kReadBufferSize, &bytesRead, NULL);
+
+ // check for error
+ if (!result)
+ {
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ if (!core::http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+
+ close();
+
+ return false;
+ }
+
+ // end of file - we should never get this far (request parser
+ // should signal that we have the full request bfore we get here)
+ else if (bytesRead == 0)
+ {
+ LOG_WARNING_MESSAGE("ReadFile returned 0 bytes");
+
+ core::http::Response response;
+ response.setStatusCode(core::http::status::BadRequest);
+ sendResponse(response);
+
+ return false;
+ }
+
+ // got input
+ else
+ {
+ // parse next chunk
+ http::RequestParser::status status = parser.parse(
+ request_,
+ buff,
+ buff + bytesRead);
+
+ // error - return bad request
+ if (status == core::http::RequestParser::error)
+ {
+ core::http::Response response;
+ response.setStatusCode(core::http::status::BadRequest);
+ sendResponse(response);
+
+ return false;
+ }
+
+ // incomplete -- keep reading
+ else if (status == core::http::RequestParser::incomplete)
+ {
+ continue;
+ }
+
+ // got valid request -- handle it
+ else
+ {
+ requestId_ = request_.headerValue("X-RS-RID");
+ return true;
+ }
+ }
+ }
+
+ // keep compiler happy (we should never get here
+ return false;
+ }
+
+ virtual const core::http::Request& request() { return request_; }
+
+ virtual void sendResponse(const core::http::Response &response)
+ {
+ // get the buffers
+ std::vector<boost::asio::const_buffer> buffers =response.toBuffers(
+ core::http::Header::connectionClose());
+
+ // write them
+ DWORD bytesWritten;
+ for (std::size_t i=0; i<buffers.size(); i++)
+ {
+ DWORD bytesToWrite = boost::asio::buffer_size(buffers[i]);
+ BOOL success = ::WriteFile(
+ hPipe_,
+ boost::asio::buffer_cast<const unsigned char*>(buffers[i]),
+ bytesToWrite,
+ &bytesWritten,
+ NULL);
+
+ if (!success || (bytesWritten != bytesToWrite))
+ {
+ // establish error
+ Error error = systemError(::GetLastError(), ERROR_LOCATION);
+ error.addProperty("request-uri", request_.uri());
+
+ // log the error if it wasn't connection terminated
+ if (!core::http::isConnectionTerminatedError(error))
+ LOG_ERROR(error);
+
+ // close and terminate
+ close();
+ break;
+ }
+ }
+ }
+
+ // close (occurs automatically after writeResponse, here in case it
+ // need to be closed in other circumstances
+ virtual void close()
+ {
+ if (hPipe_ != INVALID_HANDLE_VALUE)
+ {
+ if (!::FlushFileBuffers(hPipe_))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+
+ if (!::DisconnectNamedPipe(hPipe_))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+
+ if (!::CloseHandle(hPipe_))
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+
+ hPipe_ = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ // other useful introspection methods
+ virtual std::string requestId() const { return requestId_; }
+
+
+private:
+ HANDLE hPipe_;
+ core::http::Request request_;
+ std::string requestId_;
+};
+
+
+class NamedPipeHttpConnectionListener : public HttpConnectionListener,
+ boost::noncopyable
+{
+public:
+ explicit NamedPipeHttpConnectionListener(const std::string& pipeName,
+ const std::string& secret)
+ : pipeName_(pipeName), secret_(secret)
+ {
+ }
+
+
+ virtual Error start()
+ {
+ core::thread::safeLaunchThread(
+ boost::bind(&NamedPipeHttpConnectionListener::listenerThread,
+ this));
+
+ return Success();
+ }
+
+ virtual void stop()
+ {
+ // we don't support stop because it is never called in desktop mode
+
+ }
+
+ // connection queues
+ virtual HttpConnectionQueue& mainConnectionQueue()
+ {
+ return mainConnectionQueue_;
+ }
+
+ virtual HttpConnectionQueue& eventsConnectionQueue()
+ {
+ return eventsConnectionQueue_;
+ }
+
+
+private:
+ void listenerThread()
+ {
+ try
+ {
+ while (true)
+ {
+ // create security attributes
+ PSECURITY_ATTRIBUTES pSA = NULL;
+ SECURITY_ATTRIBUTES sa;
+ ZeroMemory(&sa, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = FALSE;
+
+ // get login session only descriptor -- proceed without one
+ // if we fail since we don't have 100% assurance this will
+ // work in all configurations and the world ends if we don't
+ // proceed with creating the pipe
+ sa.lpSecurityDescriptor = pipeServerSecurityDescriptor();
+ if (sa.lpSecurityDescriptor)
+ pSA = &sa;
+
+ // set pipe mode, specify rejection of remote clients if >= vista
+ DWORD dwPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
+ if (core::system::isVistaOrLater())
+ dwPipeMode |= PIPE_REJECT_REMOTE_CLIENTS;
+
+ // create pipe
+ HANDLE hPipe = ::CreateNamedPipeA(pipeName_.c_str(),
+ PIPE_ACCESS_DUPLEX,
+ dwPipeMode,
+ PIPE_UNLIMITED_INSTANCES,
+ kReadBufferSize,
+ kReadBufferSize,
+ 0,
+ pSA);
+ DWORD lastError = ::GetLastError(); // capture err before LocalFree
+
+ // free security descriptor if we used one
+ if (pSA)
+ ::LocalFree(pSA->lpSecurityDescriptor);
+
+ // check for error
+ if (hPipe == INVALID_HANDLE_VALUE)
+ {
+ LOG_ERROR(systemError(lastError, ERROR_LOCATION));
+ continue;
+ }
+
+ // attempt to connect
+ BOOL connected = ::ConnectNamedPipe(hPipe, NULL) ?
+ TRUE : (::GetLastError() == ERROR_PIPE_CONNECTED);
+
+ if (connected)
+ {
+ // create connection
+ boost::shared_ptr<NamedPipeHttpConnection> ptrPipeConnection(
+ new NamedPipeHttpConnection(hPipe));
+
+ // if we can successfully read a request then enque it
+ if (ptrPipeConnection->readRequest())
+ enqueConnection(ptrPipeConnection);
+ }
+ else
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ }
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+ }
+
+ // NOTE: this logic is duplicated btw here and HttpConnectionListenerImpl
+
+ void enqueConnection(
+ boost::shared_ptr<NamedPipeHttpConnection> ptrConnection)
+ {
+ // convert to cannonical HttpConnection
+ boost::shared_ptr<HttpConnection> ptrHttpConnection =
+ boost::static_pointer_cast<HttpConnection>(ptrConnection);
+
+ if (!authenticate(ptrHttpConnection))
+ {
+ core::http::Response response;
+ response.setStatusCode(403);
+ response.setStatusMessage("Forbidden");
+ ptrConnection->sendResponse(response);
+ return;
+ }
+
+ // check for the special rpc/abort endpoint and abort if requested
+ // we do this in the background listener thread so it can always
+ // be processed even if the foreground thread is deadlocked or otherwise
+ // unresponsive
+ if (connection::checkForAbort(
+ ptrHttpConnection,
+ boost::bind(&NamedPipeHttpConnectionListener::cleanup,
+ this)))
+ {
+ return;
+ }
+
+ // check for a suspend_session. done here as well as in foreground to
+ // allow clients without the requisite client-id and/or version header
+ // to also initiate a suspend (e.g. an admin/supervisor process)
+ if (connection::checkForSuspend(ptrHttpConnection))
+ return;
+
+ // place the connection on the correct queue
+ if (connection::isGetEvents(ptrHttpConnection))
+ eventsConnectionQueue_.enqueConnection(ptrHttpConnection);
+ else
+ mainConnectionQueue_.enqueConnection(ptrHttpConnection);
+ }
+
+ virtual bool authenticate(boost::shared_ptr<HttpConnection> ptrConnection)
+ {
+ return connection::authenticate(ptrConnection, secret_);
+ }
+
+ core::Error cleanup()
+ {
+ return Success();
+ }
+
+ static LPVOID pipeServerSecurityDescriptor()
+ {
+ // NOTE: if this doesn't work for whatever reason we could consider
+ // falling back to this: "D:(D;;GA;;;AN)(A;;GA;;;AU)"
+ // (which would be deny access to anonymous users and grant access
+ // to authenticated users)
+
+ std::string securityDescriptor;
+ Error error = logonSessionOnlyDescriptor(&securityDescriptor);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return NULL;
+ }
+
+ ULONG sdSize;
+ LPVOID pSA;
+ if (::ConvertStringSecurityDescriptorToSecurityDescriptorA(
+ securityDescriptor.c_str(),
+ SDDL_REVISION_1,
+ &pSA,
+ &sdSize))
+ {
+ return pSA;
+ }
+ else
+ {
+ LOG_ERROR(systemError(::GetLastError(), ERROR_LOCATION));
+ return NULL;
+ }
+ }
+
+ static core::Error logonSessionOnlyDescriptor(std::string* pDescriptor)
+ {
+ // token for current process
+ HANDLE hToken = NULL;
+ if (!OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+ core::system::CloseHandleOnExitScope tokenScope(&hToken, ERROR_LOCATION);
+
+ // size of token groups structure (note that we exepct the error
+ // since we pass NULL for the token information buffer)
+ DWORD tgSize = 0;
+ BOOL res = ::GetTokenInformation(hToken, TokenGroups, NULL, 0, &tgSize);
+ if (res != FALSE && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // get the token groups structure
+ std::vector<char> tg(tgSize);
+ TOKEN_GROUPS* pTG = reinterpret_cast<TOKEN_GROUPS*>(&tg[0]);
+ if (!::GetTokenInformation(hToken, TokenGroups, pTG, tgSize, &tgSize))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // find login sid
+ SID* pSid = NULL;
+ for (DWORD i = 0; i < pTG->GroupCount ; ++i)
+ {
+ if ((pTG->Groups[i].Attributes & SE_GROUP_LOGON_ID)
+ == SE_GROUP_LOGON_ID)
+ {
+ pSid = reinterpret_cast<SID*>(pTG->Groups[i].Sid);
+ break;
+ }
+ }
+
+ // ensure we found it
+ if (pSid == NULL)
+ {
+ return systemError(boost::system::windows_error::file_not_found,
+ "Failed to find SE_GROUP_LOGON_ID",
+ ERROR_LOCATION);
+ }
+
+ // convert to a string
+ char* pSidString = NULL;
+ if (!::ConvertSidToStringSid(pSid, &pSidString))
+ return systemError(::GetLastError(), ERROR_LOCATION);
+
+ // format string for caller
+ boost::format fmt("D:(A;OICI;GA;;;%1%)");
+ *pDescriptor = boost::str(fmt % pSidString);
+
+ // free sid string
+ ::LocalFree(pSidString);
+
+ // return success
+ return Success();
+ }
+
+
+private:
+ std::string pipeName_;
+ std::string secret_;
+ HttpConnectionQueue mainConnectionQueue_;
+ HttpConnectionQueue eventsConnectionQueue_;
+};
+
+} // namespace session
diff --git a/src/cpp/session/http/SessionPosixHttpConnectionListener.cpp b/src/cpp/session/http/SessionPosixHttpConnectionListener.cpp
new file mode 100644
index 0000000..9f065c6
--- /dev/null
+++ b/src/cpp/session/http/SessionPosixHttpConnectionListener.cpp
@@ -0,0 +1,84 @@
+/*
+ * SessionPosixHttpConnectionListener.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/SessionHttpConnectionListener.hpp>
+
+#include <core/system/Environment.hpp>
+#include <core/system/FileMode.hpp>
+
+#include <session/SessionConstants.hpp>
+#include <session/SessionOptions.hpp>
+#include <session/SessionLocalStreams.hpp>
+
+#include "SessionTcpIpHttpConnectionListener.hpp"
+#include "SessionLocalStreamHttpConnectionListener.hpp"
+
+using namespace core ;
+
+namespace session {
+
+namespace {
+
+// pointer to global connection listener singleton
+HttpConnectionListener* s_pHttpConnectionListener = NULL;
+
+} // anonymouys namespace
+
+
+void initializeHttpConnectionListener()
+{
+ // alias options
+ session::Options& options = session::options();
+
+ if (options.programMode() == kSessionProgramModeDesktop)
+ {
+ std::string localPeer = core::system::getenv("RS_LOCAL_PEER");
+ if (!localPeer.empty())
+ {
+ FilePath streamPath(localPeer);
+ s_pHttpConnectionListener = new LocalStreamHttpConnectionListener(
+ streamPath,
+ core::system::UserReadWriteMode,
+ options.sharedSecret(),
+ -1);
+ }
+ else
+ {
+ s_pHttpConnectionListener = new TcpIpHttpConnectionListener(
+ "127.0.0.1",
+ options.wwwPort(),
+ options.sharedSecret());
+ }
+ }
+ else // mode == "server"
+ {
+ // create listener based on options
+ std::string userIdentity = options.userIdentity();
+ FilePath localStreamPath = local_streams::streamPath(userIdentity);
+ s_pHttpConnectionListener = new LocalStreamHttpConnectionListener(
+ localStreamPath,
+ core::system::EveryoneReadWriteMode,
+ "", // no shared secret
+ options.limitRpcClientUid());
+ }
+}
+
+HttpConnectionListener& httpConnectionListener()
+{
+ return *s_pHttpConnectionListener;
+}
+
+
+} // namespace session
diff --git a/src/cpp/session/http/SessionTcpIpHttpConnectionListener.hpp b/src/cpp/session/http/SessionTcpIpHttpConnectionListener.hpp
new file mode 100644
index 0000000..f57a34f
--- /dev/null
+++ b/src/cpp/session/http/SessionTcpIpHttpConnectionListener.hpp
@@ -0,0 +1,74 @@
+/*
+ * SessionTcpIpHttpConnectionListener.hpp
+ *
+ * Copyright (C) 2009-11 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+
+#include <core/http/TcpIpSocketUtils.hpp>
+
+#include "SessionHttpConnectionUtils.hpp"
+#include "SessionHttpConnectionListenerImpl.hpp"
+
+using namespace core ;
+
+namespace session {
+
+// implementation of local stream http connection listener
+class TcpIpHttpConnectionListener :
+ public HttpConnectionListenerImpl<boost::asio::ip::tcp>
+{
+public:
+ TcpIpHttpConnectionListener(const std::string& address,
+ const std::string& port,
+ const std::string& sharedSecret)
+
+ : address_(address), port_(port), secret_(sharedSecret)
+ {
+ }
+
+protected:
+
+ bool authenticate(boost::shared_ptr<HttpConnection> ptrConnection)
+ {
+ return connection::authenticate(ptrConnection, secret_);
+ }
+
+private:
+
+ virtual Error initializeAcceptor(
+ http::SocketAcceptorService<boost::asio::ip::tcp>* pAcceptor)
+ {
+ return http::initTcpIpAcceptor(*pAcceptor, address_, port_);
+ }
+
+ virtual bool validateConnection(
+ boost::shared_ptr<HttpConnectionImpl<boost::asio::ip::tcp> > ptrConnection)
+ {
+ return true;
+ }
+
+
+ virtual Error cleanup()
+ {
+ return Success();
+ }
+
+
+private:
+ std::string address_;
+ std::string port_;
+ std::string secret_;
+};
+
+} // namespace session
diff --git a/src/cpp/session/http/SessionWin32HttpConnectionListener.cpp b/src/cpp/session/http/SessionWin32HttpConnectionListener.cpp
new file mode 100644
index 0000000..88b4598
--- /dev/null
+++ b/src/cpp/session/http/SessionWin32HttpConnectionListener.cpp
@@ -0,0 +1,54 @@
+/*
+ * SessionWin32HttpConnectionListener.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// Necessary to avoid compile error on Win x64
+#include <winsock2.h>
+
+#include <session/SessionHttpConnectionListener.hpp>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <core/system/System.hpp>
+
+#include "SessionNamedPipeHttpConnectionListener.hpp"
+
+using namespace core ;
+
+namespace session {
+
+namespace {
+
+// pointer to global connection listener singleton
+HttpConnectionListener* s_pHttpConnectionListener = NULL ;
+
+} // anonymouys namespace
+
+
+void initializeHttpConnectionListener()
+{
+ session::Options& options = session::options();
+ std::string pipeName = core::system::getenv("RS_LOCAL_PEER");
+ std::string secret = options.sharedSecret();
+ s_pHttpConnectionListener = new NamedPipeHttpConnectionListener(pipeName,
+ secret);
+}
+
+HttpConnectionListener& httpConnectionListener()
+{
+ return *s_pHttpConnectionListener;
+}
+
+
+} // namespace session
diff --git a/src/cpp/session/include/session/SessionClientEvent.hpp b/src/cpp/session/include/session/SessionClientEvent.hpp
new file mode 100644
index 0000000..0fa33af
--- /dev/null
+++ b/src/cpp/session/include/session/SessionClientEvent.hpp
@@ -0,0 +1,17 @@
+/*
+ * SessionClientEvent.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "worker_safe/session/SessionClientEvent.hpp"
diff --git a/src/cpp/session/include/session/SessionConstants.hpp b/src/cpp/session/include/session/SessionConstants.hpp
new file mode 100644
index 0000000..f0e29c1
--- /dev/null
+++ b/src/cpp/session/include/session/SessionConstants.hpp
@@ -0,0 +1,51 @@
+/*
+ * SessionConstants.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_CONSTANTS_HPP
+#define SESSION_CONSTANTS_HPP
+
+#define kEventsPending "ep"
+
+#define kRStudioUserIdentity "RSTUDIO_USER_IDENTITY"
+#define kRStudioLimitRpcClientUid "RSTUDIO_LIMIT_RPC_CLIENT_UID"
+#define kRSessionPortNumber "RSTUDIO_SESSION_PORT"
+
+#define kProgramModeSessionOption "program-mode"
+#define kSessionProgramModeDesktop "desktop"
+#define kSessionProgramModeServer "server"
+
+#define kShowUserIdentitySessionOption "show-user-identity"
+#define kUserIdentitySessionOption "user-identity"
+#define kUserIdentitySessionOptionShort "u"
+
+#define kVerifyInstallationSessionOption "verify-installation"
+
+#define kTimeoutSessionOption "session-timeout-minutes"
+#define kDisconnectedTimeoutSessionOption "session-disconnected-timeout-minutes"
+
+// NOTE: literal versions of these are depended upon by the desktop/rsinverse
+// project so they should be updated there as well if they are changed
+#define kLocalUriLocationPrefix "/rsession-local/"
+#define kPostbackUriScope "postback/"
+#define kPostbackExitCodeHeader "X-Postback-ExitCode"
+
+// These constants are here so that the HttpConnectionListener::checkForAbort
+// method can write the next session project (so that aborts don't require
+// a full IDE reload)
+#define kProjectsSettings "projects_settings"
+#define kNextSessionProject "next-session-project"
+
+#endif // SESSION_CONSTANTS_HPP
+
diff --git a/src/cpp/session/include/session/SessionContentUrls.hpp b/src/cpp/session/include/session/SessionContentUrls.hpp
new file mode 100644
index 0000000..309c5a2
--- /dev/null
+++ b/src/cpp/session/include/session/SessionContentUrls.hpp
@@ -0,0 +1,40 @@
+/*
+ * SessionContentUrls.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_CONTENT_URLS_HPP
+#define SESSION_SESSION_CONTENT_URLS_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace content_urls {
+
+std::string provision(const std::string& title, const core::FilePath& filePath);
+
+std::string provision(const std::string& title,
+ const std::string& content,
+ const std::string& extension);
+
+core::Error initialize();
+
+} // namespace content_urls
+} // namesapce session
+
+#endif // SESSION_SESSION_CONTENT_URLS_HPP
diff --git a/src/cpp/session/include/session/SessionHttpConnection.hpp b/src/cpp/session/include/session/SessionHttpConnection.hpp
new file mode 100644
index 0000000..266a191
--- /dev/null
+++ b/src/cpp/session/include/session/SessionHttpConnection.hpp
@@ -0,0 +1,80 @@
+/*
+ * SessionHttpConnection.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_HPP
+#define SESSION_HTTP_CONNECTION_HPP
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+
+/*
+ HttpConnection plays two related roles in the system:
+
+ 1) It is a sink for asynchronous reading of http requests by
+ HttpConnectionListener (this interface is private to HttpConnectionListener
+ via friendship). This role occurs on a background thread.
+
+ 2) It provides an interface for HttpConnection::Handler to respond to
+ requests. Responses are sent synchronously and are threfore disconnected
+ from the HttpConnectionListener io_service and (most importantly) can
+ therefore be sent on background threads.
+*/
+
+namespace core {
+
+ class Error;
+
+ namespace http {
+ class Request;
+ class Response;
+ }
+
+ namespace json {
+ class JsonRpcResponse;
+ }
+}
+
+namespace session {
+
+// abstract base (insulate clients from knowledge of protocol-specifics)
+class HttpConnection
+{
+public:
+ virtual ~HttpConnection() {}
+
+ virtual const core::http::Request& request() = 0;
+ virtual void sendResponse(const core::http::Response& response) = 0;
+
+ void sendJsonRpcError(const core::Error& error);
+ void sendJsonRpcResponse();
+ void sendJsonRpcResponse(
+ const core::json::JsonRpcResponse& jsonRpcResponse);
+
+
+ // close (occurs automatically after writeResponse, here in case it
+ // need to be closed in other circumstances
+ virtual void close() = 0;
+
+ // other useful introspection methods
+ virtual std::string requestId() const = 0;
+};
+
+
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_HPP
+
diff --git a/src/cpp/session/include/session/SessionHttpConnectionListener.hpp b/src/cpp/session/include/session/SessionHttpConnectionListener.hpp
new file mode 100644
index 0000000..815afee
--- /dev/null
+++ b/src/cpp/session/include/session/SessionHttpConnectionListener.hpp
@@ -0,0 +1,104 @@
+/*
+ * SessionHttpConnectionListener.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_LISTENER_HPP
+#define SESSION_HTTP_CONNECTION_LISTENER_HPP
+
+
+/*
+ The HttpConnectionListener is an IO service which runs in a background thread
+ and accepts http requests. Once a request has been its accepted its
+ HttpConnection object is placed on a threadsafe queue for retrieval by
+ the foreground R thread.
+
+ The foreground R thread will typically pull connections off the queue
+ at two specific junctures:
+
+ 1) When it is waiting for a specific method from the client it will pull
+ connections off the queue in hopes that the connection has the expected
+ method. If the connection is not the expected method then it will still
+ be handled and foreground R thread will continue waiting in a loop.
+
+ 2) Periodically during R_PolledEvents. This allows the client to remain
+ responsive even while computations are being peformed.
+
+ If a request pulled off the queue by the main thread can potentially be
+ executed in a background thread (e.g. file or source operation) then it
+ may (optionally) do so. Note that these request handlers should NEVER
+ execute R code since it must all be called from the main thread.
+
+ Note that since R_PolledEvents can occur during the processing of R code
+ it is possible that requests which execute R code can execute in a nested fashion
+ This is analogous to what occurs when GUIs pump events (during R_PolledEvents)
+ that result in handling user gestures that call R code.
+
+ R code which is running a computation can therefore be impacted by user
+ gestures which occur during the computation (e.g. the value of an object
+ in the global environment could be changed from under a computation or a
+ required package could be unloaded during a computation). This sort of
+ interaction is currently permitted by the OSX client and is considered OK
+ presumably because the user directly manipulated the environment and therefore
+ won't be suprised if his computation changes. The tradeoff is that operations
+ that are read-only and highly useful to peform during computations (e.g.
+ requesting completions or syntax checking in source mode, browsing objects,
+ searching and viewing help, etc.) are allowed to execute thus maintaining
+ a high level of interactivity in the client even when long computations
+ are running.
+
+ If we become uncomfortable with this behavior we could mark certain rpc or
+ http handlers as requiring more stringent serialization. For example, they
+ could be queued and executed only when the REPL loop comes back to the top.
+ Note however that if too many of these requests are queued then browser
+ request throttling may come into play and start queing ALL requests which
+ exceed the browser limit. For this reason we will start with the position
+ that nested execution of R handlers during computation is OK and back off
+ only as necessary.
+
+*/
+
+#include "SessionHttpConnectionQueue.hpp"
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+
+// global initialization (allows instantation of listener which
+// implements the protocol appropriate for our current configuration)
+void initializeHttpConnectionListener();
+
+// singleton
+class HttpConnectionListener;
+HttpConnectionListener& httpConnectionListener();
+
+class HttpConnectionListener
+{
+public:
+ virtual ~HttpConnectionListener() {}
+
+ // start and stop
+ virtual core::Error start() = 0;
+ virtual void stop() = 0;
+
+ // connection queues
+ virtual HttpConnectionQueue& mainConnectionQueue() = 0;
+ virtual HttpConnectionQueue& eventsConnectionQueue() = 0;
+};
+
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_LISTENER_HPP
+
diff --git a/src/cpp/session/include/session/SessionHttpConnectionQueue.hpp b/src/cpp/session/include/session/SessionHttpConnectionQueue.hpp
new file mode 100644
index 0000000..e1d8cea
--- /dev/null
+++ b/src/cpp/session/include/session/SessionHttpConnectionQueue.hpp
@@ -0,0 +1,76 @@
+/*
+ * SessionHttpConnectionQueue.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTTP_CONNECTION_QUEUE_HPP
+#define SESSION_HTTP_CONNECTION_QUEUE_HPP
+
+#include <queue>
+
+#include <boost/shared_ptr.hpp>
+
+#include <boost/utility.hpp>
+
+#include <core/BoostThread.hpp>
+
+#include <session/SessionHttpConnection.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+
+class HttpConnectionQueue : boost::noncopyable
+{
+public:
+ HttpConnectionQueue()
+ : pMutex_(new boost::mutex()),
+ pWaitCondition_(new boost::condition())
+ {
+ }
+
+ void enqueConnection(boost::shared_ptr<HttpConnection> ptrConnection);
+
+ boost::shared_ptr<HttpConnection> dequeConnection();
+
+ boost::shared_ptr<HttpConnection> dequeConnection(
+ const boost::posix_time::time_duration& waitDuration);
+
+ std::string peekNextConnectionUri();
+
+ boost::posix_time::ptime lastConnectionTime();
+
+private:
+ boost::shared_ptr<HttpConnection> doDequeConnection();
+ bool waitForConnection(const boost::posix_time::time_duration& waitDuration);
+
+private:
+ // synchronization objects. heap based so they are never destructed
+ // we don't want them destructed because in desktop mode we don't
+ // explicitly stop the queue and this sometimes results in mutex
+ // destroy assertions if someone is waiting on the queue while
+ // it is being destroyed
+ boost::mutex* pMutex_ ;
+ boost::condition* pWaitCondition_ ;
+
+ // instance data
+ boost::posix_time::ptime lastConnectionTime_;
+ std::queue<boost::shared_ptr<HttpConnection> > queue_;
+};
+
+} // namespace session
+
+#endif // SESSION_HTTP_CONNECTION_QUEUE_HPP
+
diff --git a/src/cpp/session/include/session/SessionLocalStreams.hpp b/src/cpp/session/include/session/SessionLocalStreams.hpp
new file mode 100644
index 0000000..cd0fc6a
--- /dev/null
+++ b/src/cpp/session/include/session/SessionLocalStreams.hpp
@@ -0,0 +1,54 @@
+/*
+ * SessionLocalStreams.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_LOCAL_STREAMS_HPP
+#define SESSION_SESSION_LOCAL_STREAMS_HPP
+
+#include <string>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+
+#include <core/http/LocalStreamSocketUtils.hpp>
+
+#define kSessionLocalStreamsDir "/tmp/rstudio-rsession"
+
+namespace session {
+namespace local_streams {
+
+inline core::Error ensureStreamsDir()
+{
+ core::FilePath sessionStreamsPath(kSessionLocalStreamsDir);
+ return core::http::initializeStreamDir(sessionStreamsPath);
+}
+
+inline core::FilePath streamPath(const std::string& user)
+{
+ return core::FilePath(kSessionLocalStreamsDir).complete(user);
+}
+
+inline void removeStreams(const std::string& user)
+{
+ core::Error error = streamPath(user).removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+}
+
+} // namepspace local_streams
+} // namespace session
+
+#endif // SESSION_SESSION_LOCAL_STREAMS_HPP
+
diff --git a/src/cpp/session/include/session/SessionMain.hpp b/src/cpp/session/include/session/SessionMain.hpp
new file mode 100644
index 0000000..cd008c0
--- /dev/null
+++ b/src/cpp/session/include/session/SessionMain.hpp
@@ -0,0 +1,25 @@
+/*
+ * SessionMain.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MAIN_HPP
+#define SESSION_MAIN_HPP
+
+namespace session {
+
+
+}
+
+#endif // SESSION_MAIN_HPP
+
diff --git a/src/cpp/session/include/session/SessionModuleContext.hpp b/src/cpp/session/include/session/SessionModuleContext.hpp
new file mode 100644
index 0000000..04f9a9e
--- /dev/null
+++ b/src/cpp/session/include/session/SessionModuleContext.hpp
@@ -0,0 +1,450 @@
+/*
+ * SessionModuleContext.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULE_CONTEXT_HPP
+#define SESSION_MODULE_CONTEXT_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/function.hpp>
+#include <boost/signals.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/FileChangeEvent.hpp>
+#include <core/http/UriHandler.hpp>
+#include <core/json/JsonRpc.hpp>
+#include <core/Thread.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionClientEvent.hpp>
+#include <session/SessionSourceDatabase.hpp>
+
+namespace core {
+ class Error;
+ class Success;
+ class FilePath;
+ class FileInfo;
+ class Settings;
+ namespace system {
+ class ProcessSupervisor;
+ struct ProcessResult;
+ }
+ namespace shell_utils {
+ class ShellCommand;
+ }
+}
+
+namespace r {
+namespace session {
+ struct RSuspendOptions;
+}
+}
+
+namespace session {
+namespace module_context {
+
+// paths
+core::FilePath userHomePath();
+std::string createAliasedPath(const core::FileInfo& fileInfo);
+std::string createAliasedPath(const core::FilePath& path);
+std::string createFileUrl(const core::FilePath& path);
+core::FilePath resolveAliasedPath(const std::string& aliasedPath);
+core::FilePath userScratchPath();
+core::FilePath scopedScratchPath();
+core::FilePath oldScopedScratchPath();
+bool isVisibleUserFile(const core::FilePath& filePath);
+
+core::FilePath safeCurrentPath();
+
+core::json::Object createFileSystemItem(const core::FileInfo& fileInfo);
+core::json::Object createFileSystemItem(const core::FilePath& filePath);
+
+// get a temp file
+core::FilePath tempFile(const std::string& prefix,
+ const std::string& extension);
+
+core::FilePath tempDir();
+
+// find out the location of a binary
+core::FilePath findProgram(const std::string& name);
+
+// is the file a text file
+bool isTextFile(const core::FilePath& targetPath);
+
+// find the location of the R script
+core::Error rBinDir(core::FilePath* pRBinDirPath);
+core::Error rScriptPath(core::FilePath* pRScriptPath);
+core::shell_utils::ShellCommand rCmd(const core::FilePath& rBinDir);
+
+// get the R local help port
+std::string rLocalHelpPort();
+
+// check if a package is installed
+bool isPackageInstalled(const std::string& packageName);
+
+// check if a package is installed with a specific version
+bool isPackageVersionInstalled(const std::string& packageName,
+ const std::string& version);
+
+// find the package name for a source file
+std::string packageNameForSourceFile(const core::FilePath& sourceFilePath);
+
+// register a handler for rBrowseUrl
+typedef boost::function<bool(const std::string&)> RBrowseUrlHandler;
+core::Error registerRBrowseUrlHandler(const RBrowseUrlHandler& handler);
+
+// register a handler for rBrowseFile
+typedef boost::function<bool(const core::FilePath&)> RBrowseFileHandler;
+core::Error registerRBrowseFileHandler(const RBrowseFileHandler& handler);
+
+// register an inbound uri handler (include a leading slash)
+core::Error registerAsyncUriHandler(
+ const std::string& name,
+ const core::http::UriAsyncHandlerFunction& handlerFunction);
+
+// register an inbound uri handler (include a leading slash)
+core::Error registerUriHandler(
+ const std::string& name,
+ const core::http::UriHandlerFunction& handlerFunction);
+
+// register a local uri handler (scoped by a special prefix which indicates
+// a local scope)
+core::Error registerAsyncLocalUriHandler(
+ const std::string& name,
+ const core::http::UriAsyncHandlerFunction& handlerFunction);
+
+// register a local uri handler (scoped by a special prefix which indicates
+// a local scope)
+core::Error registerLocalUriHandler(
+ const std::string& name,
+ const core::http::UriHandlerFunction& handlerFunction);
+
+typedef boost::function<void(int, const std::string&)> PostbackHandlerContinuation;
+
+// register a postback handler. see docs in SessionPostback.cpp for
+// details on the requirements of postback handlers
+typedef boost::function<void(const std::string&, const PostbackHandlerContinuation&)>
+ PostbackHandlerFunction;
+core::Error registerPostbackHandler(
+ const std::string& name,
+ const PostbackHandlerFunction& handlerFunction,
+ std::string* pShellCommand);
+
+// register an rpc method
+core::Error registerAsyncRpcMethod(
+ const std::string& name,
+ const core::json::JsonRpcAsyncFunction& function);
+
+// register an rpc method
+core::Error registerRpcMethod(const std::string& name,
+ const core::json::JsonRpcFunction& function);
+
+
+core::Error executeAsync(const core::json::JsonRpcFunction& function,
+ const core::json::JsonRpcRequest& request,
+ core::json::JsonRpcResponse* pResponse);
+
+
+// create a waitForMethod function -- when called this function will:
+//
+// (a) enque the passed event
+// (b) wait for the specified methodName to be returned from the client
+// (c) automatically re-issue the event after a client-init
+//
+typedef boost::function<bool(core::json::JsonRpcRequest*, const ClientEvent&)> WaitForMethodFunction;
+WaitForMethodFunction registerWaitForMethod(const std::string& methodName);
+
+namespace {
+
+template <typename T>
+core::Error rpcAsyncCoupleRunner(
+ boost::function<core::Error(const core::json::JsonRpcRequest&, T*)> initFunc,
+ boost::function<core::Error(const core::json::JsonRpcRequest&, const T&, core::json::JsonRpcResponse*)> workerFunc,
+ const core::json::JsonRpcRequest& request,
+ core::json::JsonRpcResponse* pResponse)
+{
+ T state;
+ core::Error error = initFunc(request, &state);
+ if (error)
+ return error;
+
+ return executeAsync(boost::bind(workerFunc, _1, state, _2),
+ request,
+ pResponse);
+}
+
+} // anonymous namespace
+
+// Registers a two-part request handler, where "initFunc" runs on the main
+// thread (and has access to everything a normal handler does, like R) and
+// "workerFunc" runs on a background thread (and must not touch anything
+// that isn't threadsafe). It is a Good Idea to only use workerFunc functions
+// that are declared in the "workers" sub-project.
+//
+// The T type parameter represents the type of a value that initFunc produces
+// and workerFunc consumes. This can be used to pass context between the two.
+template <typename T>
+core::Error registerRpcAsyncCoupleMethod(
+ const std::string& name,
+ boost::function<core::Error(const core::json::JsonRpcRequest&, T*)> initFunc,
+ boost::function<core::Error(const core::json::JsonRpcRequest&, const T&, core::json::JsonRpcResponse*)> workerFunc)
+{
+ return registerRpcMethod(name, boost::bind(rpcAsyncCoupleRunner<T>,
+ initFunc,
+ workerFunc,
+ _1,
+ _2));
+}
+
+enum ConsoleOutputType
+{
+ ConsoleOutputNormal,
+ ConsoleOutputError
+};
+
+enum ChangeSource
+{
+ ChangeSourceREPL,
+ ChangeSourceRPC,
+ ChangeSourceURI
+};
+
+
+// custom slot combiner that takes the first non empty value
+template<typename T>
+struct firstNonEmpty
+{
+ typedef T result_type;
+
+ template<typename InputIterator>
+ T operator()(InputIterator first, InputIterator last) const
+ {
+ for (InputIterator it = first; it != last; ++it)
+ {
+ if (!it->empty())
+ return *it;
+ }
+ return T();
+ }
+};
+
+
+// session events
+struct Events : boost::noncopyable
+{
+ boost::signal<void ()> onClientInit;
+ boost::signal<void ()> onBeforeExecute;
+ boost::signal<void(const std::string&)> onConsolePrompt;
+ boost::signal<void(const std::string&)> onConsoleInput;
+ boost::signal<void (ConsoleOutputType, const std::string&)>
+ onConsoleOutput;
+ boost::signal<void (ChangeSource)> onDetectChanges;
+ boost::signal<void (core::FilePath)> onSourceEditorFileSaved;
+ boost::signal<void(bool)> onDeferredInit;
+ boost::signal<void(bool)> onBackgroundProcessing;
+ boost::signal<void(bool)> onShutdown;
+ boost::signal<void ()> onQuit;
+ boost::signal<void (const std::string&)> onPackageLoaded;
+
+ // signal for detecting extended type of documents
+ boost::signal<std::string(boost::shared_ptr<source_database::SourceDocument>),
+ firstNonEmpty<std::string> > onDetectSourceExtendedType;
+};
+
+Events& events();
+
+// ProcessSupervisor
+core::system::ProcessSupervisor& processSupervisor();
+
+// schedule incremental work. execute will be called back periodically
+// (up to every 25ms if the process is completely idle). if execute
+// returns true then it will be called back again, if it returns false
+// then it won't ever be called again. in a given period of work the
+// execute method will be called multiple times (consecutively) for up
+// to the specified incrementalDuration. if you want to implement a
+// stateful worker simply create a shared_ptr to your worker object
+// and then bind one of its members as the execute parameter. passing
+// true as the idleOnly parameter (the default) means that the execute
+// function will only be called back during idle time (when the session
+// is waiting for user input)
+void scheduleIncrementalWork(
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute,
+ bool idleOnly = true);
+
+// variation of scheduleIncrementalWork which performs a configurable
+// amount of work immediately. this work occurs synchronously with the
+// call and will consist of execute being called back repeatedly for
+// up to the specified initialDuration
+void scheduleIncrementalWork(
+ const boost::posix_time::time_duration& initialDuration,
+ const boost::posix_time::time_duration& incrementalDuration,
+ const boost::function<bool()>& execute,
+ bool idleOnly = true);
+
+
+// schedule work to done every time the specified period elapses.
+// if the execute function returns true then the worker will be called
+// again after the specified period. pass idleOnly = true to restrict
+// periodic work to idle time.
+void schedulePeriodicWork(const boost::posix_time::time_duration& period,
+ const boost::function<bool()> &execute,
+ bool idleOnly = true,
+ bool immediate = true);
+
+
+// schedule work to be done after a fixed delay
+void scheduleDelayedWork(const boost::posix_time::time_duration& period,
+ const boost::function<void()> &execute,
+ bool idleOnly = true);
+
+
+core::Error readAndDecodeFile(const core::FilePath& filePath,
+ const std::string& encoding,
+ bool allowSubstChars,
+ std::string* pContents);
+
+core::Error convertToUtf8(const std::string& encodedContent,
+ const std::string& encoding,
+ bool allowSubstChars,
+ std::string* pDecodedContent);
+
+// source R files
+core::Error sourceModuleRFile(const std::string& rSourceFile);
+core::Error sourceModuleRFileWithResult(const std::string& rSourceFile,
+ const core::FilePath& workingDir,
+ core::system::ProcessResult* pResult);
+
+// enque client events (note R methods can do this via .rs.enqueClientEvent)
+void enqueClientEvent(const ClientEvent& event);
+
+// check whether a directory is currently being monitored by one of our subsystems
+bool isDirectoryMonitored(const core::FilePath& directory);
+
+// check whether an R source file belongs to the package under development
+bool isRScriptInPackageBuildTarget(const core::FilePath& filePath);
+
+// convenience method for filtering out file listing and changes
+bool fileListingFilter(const core::FileInfo& fileInfo);
+
+// enque file changed events
+void enqueFileChangedEvent(const core::system::FileChangeEvent& event);
+void enqueFileChangedEvents(const core::FilePath& vcsStatusRoot,
+ const std::vector<core::system::FileChangeEvent>& events);
+
+
+// register a scratch path which is monitored.
+typedef boost::function<void(const core::system::FileChangeEvent&)> OnFileChange;
+core::FilePath registerMonitoredUserScratchDir(const std::string& dirName,
+ const OnFileChange& onFileChange);
+
+// write output to the console (convenience wrapper for enquing a
+// kConsoleWriteOutput event)
+void consoleWriteOutput(const std::string& output);
+
+// write an error to the console (convenience wrapper for enquing a
+// kConsoleWriteOutput event)
+void consoleWriteError(const std::string& message);
+
+// show an error dialog (convenience wrapper for enquing kShowErrorMessage)
+void showErrorMessage(const std::string& title, const std::string& message);
+
+void showFile(const core::FilePath& filePath,
+ const std::string& window = "_blank");
+
+
+void showContent(const std::string& title, const core::FilePath& filePath);
+
+std::string resourceFileAsString(const std::string& fileName);
+
+bool portmapPathForLocalhostUrl(const std::string& url, std::string* pPath);
+
+std::string mapUrlPorts(const std::string& url);
+
+std::string pathRelativeTo(const core::FilePath& sourcePath,
+ const core::FilePath& targetPath);
+
+void activatePane(const std::string& pane);
+
+int saveWorkspaceAction();
+void syncRSaveAction();
+
+std::string libPathsString();
+bool canBuildCpp();
+bool haveRcppAttributes();
+
+#ifdef __APPLE__
+bool isOSXMavericks();
+bool hasOSXMavericksDeveloperTools();
+#else
+inline bool isOSXMavericks()
+{
+ return false;
+}
+inline bool hasOSXMavericksDeveloperTools()
+{
+ return false;
+}
+#endif
+
+struct VcsContext
+{
+ std::string detectedVcs;
+ std::vector<std::string> applicableVcs;
+ std::string svnRepositoryRoot;
+ std::string gitRemoteOriginUrl;
+};
+VcsContext vcsContext(const core::FilePath& workingDir);
+
+std::string normalizeVcsOverride(const std::string& vcsOverride);
+
+core::FilePath shellWorkingDirectory();
+
+// persist state accross suspend and resume
+
+typedef boost::function<void (const r::session::RSuspendOptions&,
+ core::Settings*)> SuspendFunction;
+typedef boost::function<void(const core::Settings&)> ResumeFunction;
+
+class SuspendHandler
+{
+public:
+ SuspendHandler(const SuspendFunction& suspend,
+ const ResumeFunction& resume)
+ : suspend_(suspend), resume_(resume)
+ {
+ }
+
+ // COPYING: via compiler
+
+ const SuspendFunction& suspend() const { return suspend_; }
+ const ResumeFunction& resume() const { return resume_; }
+
+private:
+ SuspendFunction suspend_;
+ ResumeFunction resume_;
+};
+
+void addSuspendHandler(const SuspendHandler& handler);
+
+bool rSessionResumed();
+
+} // namespace module_context
+} // namespace session
+
+#endif // SESSION_MODULE_CONTEXT_HPP
+
diff --git a/src/cpp/session/include/session/SessionOptions.hpp b/src/cpp/session/include/session/SessionOptions.hpp
new file mode 100644
index 0000000..fae86a8
--- /dev/null
+++ b/src/cpp/session/include/session/SessionOptions.hpp
@@ -0,0 +1,469 @@
+/*
+ * SessionOptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_OPTIONS_HPP
+#define SESSION_SESSION_OPTIONS_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/SafeConvert.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/System.hpp>
+#include <core/StringUtils.hpp>
+#include <core/ProgramOptions.hpp>
+
+#include <R_ext/RStartup.h>
+
+#include <session/SessionConstants.hpp>
+
+namespace core {
+ class ProgramStatus;
+}
+
+namespace session {
+
+
+// singleton
+class Options;
+Options& options();
+
+class Options : boost::noncopyable
+{
+private:
+ Options()
+ {
+ }
+ friend Options& options() ;
+
+ // COPYING: boost::noncopyable
+
+public:
+ // read options
+ core::ProgramStatus read(int argc, char * const argv[]);
+ virtual ~Options() {}
+
+ bool verifyInstallation() const
+ {
+ return verifyInstallation_;
+ }
+
+ core::FilePath verifyInstallationHomeDir() const
+ {
+ if (!verifyInstallationHomeDir_.empty())
+ return core::FilePath(verifyInstallationHomeDir_.c_str());
+ else
+ return core::FilePath();
+ }
+
+ std::string programIdentity() const
+ {
+ return std::string(programIdentity_.c_str());
+ }
+
+ std::string programMode() const
+ {
+ return std::string(programMode_.c_str());
+ }
+
+
+
+ // agreement
+ core::FilePath agreementFilePath() const
+ {
+ if (!agreementFilePath_.empty())
+ return core::FilePath(agreementFilePath_.c_str());
+ else
+ return core::FilePath();
+ }
+
+ // docs
+ std::string docsURL() const
+ {
+ return std::string(docsURL_.c_str());
+ }
+
+ // www
+ std::string wwwLocalPath() const
+ {
+ return std::string(wwwLocalPath_.c_str());
+ }
+
+ core::FilePath wwwSymbolMapsPath() const
+ {
+ return core::FilePath(wwwSymbolMapsPath_.c_str());
+ }
+
+ std::string wwwPort() const
+ {
+ return std::string(wwwPort_.c_str());
+ }
+
+ std::string sharedSecret() const
+ {
+ return std::string(secret_.c_str());
+ }
+
+ core::FilePath preflightScriptPath() const
+ {
+ return core::FilePath(preflightScript_.c_str());
+ }
+
+ int timeoutMinutes() const { return timeoutMinutes_; }
+
+ int disconnectedTimeoutMinutes() { return disconnectedTimeoutMinutes_; }
+
+ bool createPublicFolder() const { return createPublicFolder_; }
+
+ bool rProfileOnResumeDefault() const { return rProfileOnResumeDefault_; }
+
+ int saveActionDefault() const { return saveActionDefault_; }
+
+ unsigned int minimumUserId() const { return 100; }
+
+ core::FilePath coreRSourcePath() const
+ {
+ return core::FilePath(coreRSourcePath_.c_str());
+ }
+
+ core::FilePath modulesRSourcePath() const
+ {
+ return core::FilePath(modulesRSourcePath_.c_str());
+ }
+
+ core::FilePath sessionLibraryPath() const
+ {
+ return core::FilePath(sessionLibraryPath_.c_str());
+ }
+
+ core::FilePath sessionPackagesPath() const
+ {
+ return core::FilePath(sessionPackagesPath_.c_str());
+ }
+
+ std::string rLibsUser() const
+ {
+ return std::string(rLibsUser_.c_str());
+ }
+
+ std::string rCRANRepos() const
+ {
+ return std::string(rCRANRepos_.c_str());
+ }
+
+ int rCompatibleGraphicsEngineVersion() const
+ {
+ return rCompatibleGraphicsEngineVersion_;
+ }
+
+ core::FilePath rResourcesPath() const
+ {
+ return core::FilePath(rResourcesPath_.c_str());
+ }
+
+ std::string rHomeDirOverride()
+ {
+ return std::string(rHomeDirOverride_.c_str());
+ }
+
+ std::string rDocDirOverride()
+ {
+ return std::string(rDocDirOverride_.c_str());
+ }
+
+ bool autoReloadSource() const { return autoReloadSource_; }
+
+ // limits
+ int limitFileUploadSizeMb() const { return limitFileUploadSizeMb_; }
+ int limitCpuTimeMinutes() const { return limitCpuTimeMinutes_; }
+
+ int limitRpcClientUid() const { return limitRpcClientUid_; }
+
+ bool limitXfsDiskQuota() const { return limitXfsDiskQuota_; }
+
+ // external
+ core::FilePath rpostbackPath() const
+ {
+ return core::FilePath(rpostbackPath_.c_str());
+ }
+
+ core::FilePath consoleIoPath() const
+ {
+ return core::FilePath(consoleIoPath_.c_str());
+ }
+
+ core::FilePath gnudiffPath() const
+ {
+ return core::FilePath(gnudiffPath_.c_str());
+ }
+
+ core::FilePath gnugrepPath() const
+ {
+ return core::FilePath(gnugrepPath_.c_str());
+ }
+
+ core::FilePath msysSshPath() const
+ {
+ return core::FilePath(msysSshPath_.c_str());
+ }
+
+ core::FilePath sumatraPath() const
+ {
+ return core::FilePath(sumatraPath_.c_str());
+ }
+
+ core::FilePath hunspellDictionariesPath() const
+ {
+ return core::FilePath(hunspellDictionariesPath_.c_str());
+ }
+
+ core::FilePath mathjaxPath() const
+ {
+ return core::FilePath(mathjaxPath_.c_str());
+ }
+
+ core::FilePath pandocPath() const
+ {
+ return core::FilePath(pandocPath_.c_str());
+ }
+
+ bool allowFileDownloads() const
+ {
+ return allowFileDownloads_;
+ }
+
+ bool allowShell() const
+ {
+ return allowShell_;
+ }
+
+ bool allowPackageInstallation() const
+ {
+ return allowPackageInstallation_;
+ }
+
+ bool allowVcs() const
+ {
+ return allowVcs_;
+ }
+
+ bool allowCRANReposEdit() const
+ {
+ return allowCRANReposEdit_;
+ }
+
+ bool allowVcsExecutableEdit() const
+ {
+ return allowVcsExecutableEdit_;
+ }
+
+ bool allowRemovePublicFolder() const
+ {
+ return allowRemovePublicFolder_;
+ }
+
+ bool allowRpubsPublish() const
+ {
+ return allowRpubsPublish_;
+ }
+
+ // user info
+ std::string userIdentity() const
+ {
+ return std::string(userIdentity_.c_str());
+ }
+
+ bool showUserIdentity() const
+ {
+ return showUserIdentity_;
+ }
+
+ core::FilePath userHomePath() const
+ {
+ return core::FilePath(userHomePath_.c_str());
+ }
+
+ core::FilePath userScratchPath() const
+ {
+ return core::FilePath(userScratchPath_.c_str());
+ }
+
+ core::FilePath userLogPath() const
+ {
+ return userScratchPath().childPath("log");
+ }
+
+ core::FilePath initialWorkingDirOverride()
+ {
+ if (!initialWorkingDirOverride_.empty())
+ return core::FilePath(initialWorkingDirOverride_.c_str());
+ else
+ return core::FilePath();
+ }
+
+ core::FilePath initialEnvironmentFileOverride()
+ {
+ if (!initialEnvironmentFileOverride_.empty())
+ return core::FilePath(initialEnvironmentFileOverride_.c_str());
+ else
+ return core::FilePath();
+ }
+
+ core::FilePath initialProjectPath()
+ {
+ if (!initialProjectPath_.empty())
+ return core::FilePath(initialProjectPath_.c_str());
+ else
+ return core::FilePath();
+ }
+
+ void clearInitialContextSettings()
+ {
+ initialWorkingDirOverride_.clear();
+ initialEnvironmentFileOverride_.clear();
+ initialProjectPath_.clear();
+ }
+
+ // The line ending we use when working with source documents
+ // in memory. This doesn't really make sense for the user to
+ // change.
+ core::string_utils::LineEnding sourceLineEnding() const
+ {
+ return core::string_utils::LineEndingPosix;
+ }
+
+ // The line ending we persist to disk with. This could potentially
+ // be a per-user or even per-file option.
+ core::string_utils::LineEnding sourcePersistLineEnding() const
+ {
+ return core::string_utils::LineEndingNative;
+ }
+
+ std::string monitorSharedSecret() const
+ {
+ return monitorSharedSecret_.c_str();
+ }
+
+ std::string getOverlayOption(const std::string& name)
+ {
+ return overlayOptions_[name];
+ }
+
+ bool getBoolOverlayOption(const std::string& name);
+
+private:
+ void resolvePath(const core::FilePath& resourcePath,
+ std::string* pPath);
+ void resolvePostbackPath(const core::FilePath& resourcePath,
+ std::string* pPath);
+ void resolvePandocPath(const core::FilePath& resourcePath, std::string* pPath);
+
+ void addOverlayOptions(boost::program_options::options_description* pOpt);
+ bool validateOverlayOptions(std::string* pErrMsg);
+ void resolveOverlayOptions();
+
+private:
+ // verify
+ bool verifyInstallation_;
+ std::string verifyInstallationHomeDir_;
+
+ // program
+ std::string programIdentity_;
+ std::string programMode_;
+
+ // agreement
+ std::string agreementFilePath_;
+
+ // docs
+ std::string docsURL_;
+
+ // www
+ std::string wwwLocalPath_;
+ std::string wwwSymbolMapsPath_;
+ std::string wwwPort_;
+
+ // session
+ std::string secret_;
+ std::string preflightScript_;
+ int timeoutMinutes_;
+ int disconnectedTimeoutMinutes_;
+ bool createPublicFolder_;
+ bool rProfileOnResumeDefault_;
+ int saveActionDefault_;
+
+ // r
+ std::string coreRSourcePath_;
+ std::string modulesRSourcePath_;
+ std::string sessionLibraryPath_;
+ std::string sessionPackagesPath_;
+ std::string rLibsUser_;
+ std::string rCRANRepos_;
+ bool autoReloadSource_ ;
+ int rCompatibleGraphicsEngineVersion_;
+ std::string rResourcesPath_;
+ std::string rHomeDirOverride_;
+ std::string rDocDirOverride_;
+
+ // limits
+ int limitFileUploadSizeMb_;
+ int limitCpuTimeMinutes_;
+ int limitRpcClientUid_;
+ bool limitXfsDiskQuota_;
+
+ // external
+ std::string rpostbackPath_;
+ std::string consoleIoPath_;
+ std::string gnudiffPath_;
+ std::string gnugrepPath_;
+ std::string msysSshPath_;
+ std::string sumatraPath_;
+ std::string hunspellDictionariesPath_;
+ std::string mathjaxPath_;
+ std::string pandocPath_;
+
+ bool allowFileDownloads_;
+ bool allowShell_;
+ bool allowPackageInstallation_;
+ bool allowVcs_;
+ bool allowCRANReposEdit_;
+ bool allowVcsExecutableEdit_;
+ bool allowRemovePublicFolder_;
+ bool allowRpubsPublish_;
+
+ // user info
+ bool showUserIdentity_;
+ std::string userIdentity_;
+ std::string userHomePath_;
+ std::string userScratchPath_;
+
+ // overrides
+ std::string initialWorkingDirOverride_;
+ std::string initialEnvironmentFileOverride_;
+
+ // initial project
+ std::string initialProjectPath_;
+
+ // monitor
+ std::string monitorSharedSecret_;
+
+ // overlay options
+ std::map<std::string,std::string> overlayOptions_;
+};
+
+} // namespace session
+
+#endif // SESSION_SESSION_OPTIONS_HPP
+
diff --git a/src/cpp/session/include/session/SessionPersistentState.hpp b/src/cpp/session/include/session/SessionPersistentState.hpp
new file mode 100644
index 0000000..fc8f991
--- /dev/null
+++ b/src/cpp/session/include/session/SessionPersistentState.hpp
@@ -0,0 +1,66 @@
+/*
+ * SessionPersistentState.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PERSISTENT_STATE_HPP
+#define SESSION_PERSISTENT_STATE_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/Settings.hpp>
+
+namespace session {
+
+// singleton
+class PersistentState;
+PersistentState& persistentState();
+
+class PersistentState : boost::noncopyable
+{
+private:
+ PersistentState() : serverMode_(false) {}
+ friend PersistentState& persistentState();
+
+public:
+ // COPYING: boost::noncopyable
+
+ core::Error initialize();
+
+ // active-client-id
+ std::string activeClientId();
+ std::string newActiveClientId();
+
+ // abend
+ bool hadAbend();
+ void setAbend(bool abend);
+
+ // active environment
+ std::string activeEnvironmentName() const;
+ void setActiveEnvironmentName(std::string environmentName);
+
+ // get underlying settings
+ core::Settings& settings() { return settings_; }
+
+private:
+ bool serverMode_;
+ std::string desktopClientId_;
+ core::Settings settings_;
+};
+
+} // namespace session
+
+#endif // SESSION_PERSISTENT_STATE_HPP
+
diff --git a/src/cpp/session/include/session/SessionSSH.hpp b/src/cpp/session/include/session/SessionSSH.hpp
new file mode 100644
index 0000000..752c913
--- /dev/null
+++ b/src/cpp/session/include/session/SessionSSH.hpp
@@ -0,0 +1,72 @@
+/*
+ * SessionSSH.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SSH_HPP
+#define SESSION_SSH_HPP
+
+#include <vector>
+#include <map>
+#include <string>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/system/Process.hpp>
+
+namespace session {
+namespace ssh {
+
+// Holds a common set of options that need to be applied to child
+// processes when they are executed.
+class ProcessOptionsCreator
+{
+public:
+ ProcessOptionsCreator(core::system::ProcessOptions baseOptions)
+ : baseOptions_(baseOptions)
+ {
+ }
+
+ // COPYING: Via compiler (copyable members)
+
+ // Add an environment variable that should be added every time.
+ // To explicitly unset an existing environment variable every
+ // time, pass std::string() for value.
+ void addEnv(const std::string& name, const std::string& value);
+
+ // Remove an environment variable from the list of ones that should
+ // be added every time. Note that this will not *unset* this var
+ // if it's in the current process's environment.
+ void rmEnv(const std::string& name);
+
+ // Add a directory to the path of the child process
+ void addToPath(const core::FilePath& dir);
+
+ void setWorkingDirectory(const core::FilePath& dir);
+ void clearWorkingDirectory();
+
+ // Create the actual ProcessOptions object from the state of this
+ // object.
+ core::system::ProcessOptions processOptions() const;
+
+private:
+ core::system::ProcessOptions baseOptions_;
+ std::map<std::string, std::string> env_;
+ std::vector<core::FilePath> pathDirs_;
+ core::FilePath workingDir_;
+};
+
+} // namespace ssh
+} // namespace session
+
+#endif // SESSION_SSH_HPP
diff --git a/src/cpp/session/include/session/SessionSourceDatabase.hpp b/src/cpp/session/include/session/SessionSourceDatabase.hpp
new file mode 100644
index 0000000..9ce739f
--- /dev/null
+++ b/src/cpp/session/include/session/SessionSourceDatabase.hpp
@@ -0,0 +1,147 @@
+/*
+ * SessionSourceDatabase.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SOURCE_DATABASE_HPP
+#define SESSION_SOURCE_DATABASE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace source_database {
+
+class SourceDocument : boost::noncopyable
+{
+public:
+ SourceDocument(const std::string& type = std::string());
+ virtual ~SourceDocument() {}
+ // COPYING: via compiler
+
+ // accessors
+ const std::string& id() const { return id_; }
+ const std::string& path() const { return path_; }
+ const std::string& type() const { return type_; }
+ const std::string& contents() const { return contents_; }
+ const std::string& hash() const { return hash_; }
+ const std::string& encoding() const { return encoding_; }
+ bool dirty() const { return dirty_; }
+ double created() const { return created_; }
+ bool sourceOnSave() const { return sourceOnSave_; }
+ const core::json::Object& properties() const { return properties_; }
+ const std::string& folds() const { return folds_; }
+ std::string getProperty(const std::string& name) const;
+
+ // is this an untitled document?
+ bool isUntitled() const;
+
+ // set contents from string
+ void setContents(const std::string& contents);
+
+ // set contents from file
+ core::Error setPathAndContents(const std::string& path,
+ bool allowSubstChars = true);
+
+ core::Error updateDirty();
+
+ // set dirty
+ void setDirty(bool dirty)
+ {
+ dirty_ = dirty;
+ }
+
+ // set source on save
+ void setSourceOnSave(bool sourceOnSave)
+ {
+ sourceOnSave_ = sourceOnSave;
+ }
+
+ void setEncoding(const std::string& encoding)
+ {
+ encoding_ = encoding;
+ }
+
+ void setFolds(const std::string& folds)
+ {
+ folds_ = folds;
+ }
+
+ void checkForExternalEdit(std::time_t* pTime);
+
+ void updateLastKnownWriteTime();
+
+ // applies the values in the given properties object to the document's property
+ // bag. this does NOT replace all of the doc's properties on the server; any
+ // properties that already exist but are not present in the given object are
+ // left unchanged. if an entry in the given object has a null value, that
+ // property should be removed.
+ void editProperties(core::json::Object& properties);
+
+ void setType(const std::string& type)
+ {
+ type_ = type;
+ }
+
+ core::Error readFromJson(core::json::Object* pDocJson);
+ void writeToJson(core::json::Object* pDocJson) const;
+
+ core::Error writeToFile(const core::FilePath& filePath) const;
+
+private:
+ void editProperty(const core::json::Object::value_type& property);
+
+private:
+ std::string id_;
+ std::string path_;
+ std::string type_;
+ std::string contents_;
+ std::string hash_;
+ std::string encoding_;
+ std::string folds_;
+ std::time_t lastKnownWriteTime_;
+ bool dirty_;
+ double created_;
+ bool sourceOnSave_;
+ core::json::Object properties_;
+};
+
+bool sortByCreated(const boost::shared_ptr<SourceDocument>& pDoc1,
+ const boost::shared_ptr<SourceDocument>& pDoc2);
+
+core::FilePath path();
+core::Error get(const std::string& id, boost::shared_ptr<SourceDocument> pDoc);
+core::Error getDurableProperties(const std::string& path,
+ core::json::Object* pProperties);
+core::Error list(std::vector<boost::shared_ptr<SourceDocument> >* pDocs);
+core::Error put(boost::shared_ptr<SourceDocument> pDoc);
+core::Error remove(const std::string& id);
+core::Error removeAll();
+
+core::Error initialize();
+
+} // namespace source_database
+} // namesapce session
+
+#endif // SESSION_SOURCE_DATABASE_HPP
diff --git a/src/cpp/session/include/session/SessionUserSettings.hpp b/src/cpp/session/include/session/SessionUserSettings.hpp
new file mode 100644
index 0000000..4280a0c
--- /dev/null
+++ b/src/cpp/session/include/session/SessionUserSettings.hpp
@@ -0,0 +1,209 @@
+/*
+ * SessionUserSettings.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_USER_SETTINGS_HPP
+#define SESSION_USER_SETTINGS_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/signal.hpp>
+
+#include <core/Settings.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <core/system/FileChangeEvent.hpp>
+
+namespace session {
+
+// singleton
+class UserSettings;
+UserSettings& userSettings();
+
+struct CRANMirror
+{
+ std::string name;
+ std::string host;
+ std::string url;
+ std::string country;
+};
+
+struct BioconductorMirror
+{
+ std::string name;
+ std::string url;
+};
+
+class UserSettings : boost::noncopyable
+{
+private:
+ UserSettings() {}
+ friend UserSettings& userSettings();
+
+public:
+ boost::signal<void()> onChanged;
+
+public:
+ // COPYING: boost::noncopyable
+
+ // intialize
+ core::Error initialize();
+
+ // enable batch updates
+ void beginUpdate() { settings_.beginUpdate(); }
+ void endUpdate() { settings_.endUpdate(); }
+
+ // context id
+ std::string contextId() const;
+ void setContextId(const std::string& contextId);
+
+ // old context-id (for migrating untitled files)
+ std::string oldContextId() const;
+
+ // agreement hash code
+ std::string agreementHash() const;
+ void setAgreementHash(const std::string& hash) ;
+
+ // did we already auto-create the profile?
+ bool autoCreatedProfile() const;
+ void setAutoCreatedProfile(bool autoCreated) ;
+
+ core::json::Object uiPrefs() const;
+ void setUiPrefs(const core::json::Object& prefsObject);
+
+ // readers for ui prefs
+ bool useSpacesForTab() const;
+ int numSpacesForTab() const;
+ bool autoAppendNewline() const;
+ bool stripTrailingWhitespace() const;
+ std::string defaultEncoding() const;
+ std::string defaultSweaveEngine() const;
+ std::string defaultLatexProgram() const;
+ bool alwaysEnableRnwCorcordance() const;
+ bool handleErrorsInUserCodeOnly() const;
+ int shinyViewerType() const;
+
+ bool rProfileOnResume() const;
+ void setRprofileOnResume(bool rProfileOnResume);
+
+ bool alwaysRestoreLastProject() const;
+ void setAlwaysRestoreLastProject(bool alwaysRestore);
+
+ int saveAction() const;
+ void setSaveAction(int saveAction);
+
+ bool loadRData() const;
+ void setLoadRData(bool loadRData);
+
+ core::FilePath initialWorkingDirectory() const;
+ void setInitialWorkingDirectory(const core::FilePath& filePath);
+
+ bool alwaysSaveHistory() const;
+ void setAlwaysSaveHistory(bool alwaysSave);
+
+ bool removeHistoryDuplicates() const;
+ void setRemoveHistoryDuplicates(bool removeDuplicates);
+
+ CRANMirror cranMirror() const;
+ void setCRANMirror(const CRANMirror& cranMirror);
+
+ BioconductorMirror bioconductorMirror() const;
+ void setBioconductorMirror(const BioconductorMirror& bioconductorMirror);
+
+ bool vcsEnabled() const;
+ void setVcsEnabled(bool enabled);
+
+ core::FilePath gitExePath() const;
+ void setGitExePath(const core::FilePath& gitExePath);
+
+ core::FilePath svnExePath() const;
+ void setSvnExePath(const core::FilePath& svnExePath);
+
+ core::FilePath vcsTerminalPath() const;
+ void setVcsTerminalPath(const core::FilePath& terminalPath);
+
+ bool vcsUseGitBash() const;
+ void setVcsUseGitBash(bool useGitBash);
+
+ bool cleanTexi2DviOutput() const;
+ void setCleanTexi2DviOutput(bool cleanTexi2DviOutput);
+
+ bool enableLaTeXShellEscape() const;
+ void setEnableLaTeXShellEscape(bool enableShellEscape);
+
+ std::string spellingLanguage() const;
+ std::vector<std::string> spellingCustomDictionaries() const;
+
+ bool useInternet2() const;
+ void setUseInternet2(bool useInternet2);
+
+ bool cleanupAfterRCmdCheck() const;
+ void setCleanupAfterRCmdCheck(bool cleanup);
+
+ bool hideObjectFiles() const;
+ void setHideObjectFiles(bool hide);
+
+ bool viewDirAfterRCmdCheck() const;
+ void setViewDirAfterRCmdCheck(bool viewDir);
+
+ int errorHandlerType() const;
+ void setErrorHandlerType(int type);
+
+private:
+
+ void onSettingsFileChanged(
+ const core::system::FileChangeEvent& changeEvent);
+
+ core::FilePath getWorkingDirectoryValue(const std::string& key) const;
+ void setWorkingDirectoryValue(const std::string& key,
+ const core::FilePath& filePath) ;
+
+ void updatePrefsCache(const core::json::Object& uiPrefs) const;
+
+ template <typename T>
+ T readUiPref(const boost::scoped_ptr<T>& pPref) const
+ {
+ if (!pPref)
+ updatePrefsCache(uiPrefs());
+
+ return *pPref;
+ }
+
+private:
+ core::FilePath settingsFilePath_;
+ core::Settings settings_;
+
+ // cached prefs values
+ mutable boost::scoped_ptr<bool> pUseSpacesForTab_;
+ mutable boost::scoped_ptr<int> pNumSpacesForTab_;
+ mutable boost::scoped_ptr<bool> pAutoAppendNewline_;
+ mutable boost::scoped_ptr<bool> pStripTrailingWhitespace_;
+ mutable boost::scoped_ptr<std::string> pDefaultEncoding_;
+ mutable boost::scoped_ptr<std::string> pDefaultSweaveEngine_;
+ mutable boost::scoped_ptr<std::string> pDefaultLatexProgram_;
+ mutable boost::scoped_ptr<bool> pAlwaysEnableRnwConcordance_;
+ mutable boost::scoped_ptr<std::string> pSpellingLanguage_;
+ mutable boost::scoped_ptr<core::json::Array> pSpellingCustomDicts_;
+ mutable boost::scoped_ptr<bool> pHandleErrorsInUserCodeOnly_;
+ mutable boost::scoped_ptr<int> pShinyViewerType_;
+};
+
+} // namespace session
+
+#endif // SESSION_USER_SETTINGS_HPP
+
diff --git a/src/cpp/session/include/session/projects/SessionProjects.hpp b/src/cpp/session/include/session/projects/SessionProjects.hpp
new file mode 100644
index 0000000..b0e9e63
--- /dev/null
+++ b/src/cpp/session/include/session/projects/SessionProjects.hpp
@@ -0,0 +1,220 @@
+/*
+ * SessionProjects.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PROJECTS_PROJECTS_HPP
+#define SESSION_PROJECTS_PROJECTS_HPP
+
+#include <vector>
+#include <map>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <boost/signals.hpp>
+
+#include <core/FileInfo.hpp>
+#include <core/FilePath.hpp>
+#include <core/Settings.hpp>
+
+#include <core/system/FileMonitor.hpp>
+#include <core/system/FileChangeEvent.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <core/collection/Tree.hpp>
+
+#include <core/r_util/RProjectFile.hpp>
+#include <core/r_util/RSourceIndex.hpp>
+#include <core/r_util/RPackageInfo.hpp>
+
+namespace session {
+namespace projects {
+
+
+// file monitoring callbacks (all callbacks are optional)
+struct FileMonitorCallbacks
+{
+ boost::function<void(const tree<core::FileInfo>&)> onMonitoringEnabled;
+ boost::function<void(
+ const std::vector<core::system::FileChangeEvent>&)> onFilesChanged;
+ boost::function<void()> onMonitoringDisabled;
+};
+
+// vcs options
+struct RProjectVcsOptions
+{
+ std::string vcsOverride;
+};
+
+// build options
+struct RProjectBuildOptions
+{
+ RProjectBuildOptions() :
+ autoRoxygenizeForCheck(true),
+ autoRoxygenizeForBuildPackage(true),
+ autoRoxygenizeForBuildAndReload(false)
+ {
+ }
+
+ std::string makefileArgs;
+ bool autoRoxygenizeForCheck;
+ bool autoRoxygenizeForBuildPackage;
+ bool autoRoxygenizeForBuildAndReload;
+};
+
+class ProjectContext : boost::noncopyable
+{
+public:
+ ProjectContext()
+ : hasFileMonitor_(false)
+ {
+ }
+ virtual ~ProjectContext() {}
+
+ core::Error startup(const core::FilePath& projectFile,
+ std::string* pUserErrMsg);
+
+ core::Error initialize();
+
+public:
+ // these functions can be called even when there is no project
+ bool hasProject() const { return !file_.empty(); }
+
+ // next session project path -- low level value used by suspend
+ // and switch-to-project
+ std::string nextSessionProject() const;
+ void setNextSessionProject(const std::string& nextSessionProject);
+
+ // last project path -- used to implement restore last project user setting
+ core::FilePath lastProjectPath() const;
+ void setLastProjectPath(const core::FilePath& lastProjectPath);
+
+ const core::FilePath& file() const { return file_; }
+ const core::FilePath& directory() const { return directory_; }
+ const core::FilePath& scratchPath() const { return scratchPath_; }
+
+ core::FilePath oldScratchPath() const;
+
+ const core::r_util::RProjectConfig& config() const { return config_; }
+ void setConfig(const core::r_util::RProjectConfig& config)
+ {
+ config_ = config;
+ updateDefaultEncoding();
+ updateBuildTargetPath();
+ updatePackageInfo();
+ }
+
+ core::Error readVcsOptions(RProjectVcsOptions* pOptions) const;
+ core::Error writeVcsOptions(const RProjectVcsOptions& options) const;
+
+ core::Error readBuildOptions(RProjectBuildOptions* pOptions);
+ core::Error writeBuildOptions(const RProjectBuildOptions& options);
+
+ // code which needs to rely on the encoding should call this method
+ // rather than getting the encoding off of the config (because the
+ // config could have been created on another system with an encoding
+ // not available here -- defaultEncoding reflects (if possible) a
+ // local mapping of an unknown encoding and a fallback to UTF-8
+ // if necessary
+ std::string defaultEncoding() const;
+
+ // computed absolute path to project build target directory
+ const core::FilePath& buildTargetPath() const
+ {
+ return buildTargetPath_;
+ }
+
+ core::json::Object uiPrefs() const;
+
+ core::json::Array openDocs() const;
+
+ // current build options (note that these are not synchronized
+ // accross processes!)
+ const RProjectBuildOptions& buildOptions() const
+ {
+ return buildOptions_;
+ }
+
+ // current package info (if this is a package)
+ const core::r_util::RPackageInfo& packageInfo() const
+ {
+ return packageInfo_;
+ }
+
+ // does this project context have a file monitor? (might not have one
+ // if the user has disabled code indexing or if file monitoring failed
+ // for this path)
+ bool hasFileMonitor() const { return hasFileMonitor_; }
+
+ // are we monitoring the specified directory? (used by other modules to
+ // suppress file monitoring if the project already has it covered)
+ bool isMonitoringDirectory(const core::FilePath& directory) const;
+
+ // subscribe to file monitor notifications -- note that to ensure
+ // receipt of the onMonitoringEnabled callback subscription should
+ // occur during module initialization
+ void subscribeToFileMonitor(const std::string& featureName,
+ const FileMonitorCallbacks& cb);
+
+public:
+ static core::r_util::RProjectConfig defaultConfig();
+
+private:
+ // deferred init handler (this allows other modules to reliably subscribe
+ // to our file monitoring events with no concern that they'll miss
+ // onMonitoringEnabled)
+ void onDeferredInit(bool newSession);
+
+ // file monitor event handlers
+ void fileMonitorRegistered(core::system::file_monitor::Handle handle,
+ const tree<core::FileInfo>& files);
+ void fileMonitorFilesChanged(
+ const std::vector<core::system::FileChangeEvent>& events);
+ void fileMonitorTermination(const core::Error& error);
+
+ core::FilePath vcsOptionsFilePath() const;
+ core::Error buildOptionsFile(core::Settings* pOptionsFile) const;
+
+ void updateDefaultEncoding();
+ void updateBuildTargetPath();
+ void updatePackageInfo();
+
+ void augmentRbuildignore();
+
+private:
+ core::FilePath file_;
+ core::FilePath directory_;
+ core::FilePath scratchPath_;
+ core::r_util::RProjectConfig config_;
+ std::string defaultEncoding_;
+ core::FilePath buildTargetPath_;
+ RProjectBuildOptions buildOptions_;
+ core::r_util::RPackageInfo packageInfo_;
+
+ bool hasFileMonitor_;
+ std::vector<std::string> monitorSubscribers_;
+ boost::signal<void(const tree<core::FileInfo>&)> onMonitoringEnabled_;
+ boost::signal<void(const std::vector<core::system::FileChangeEvent>&)>
+ onFilesChanged_;
+ boost::signal<void()> onMonitoringDisabled_;
+};
+
+ProjectContext& projectContext();
+
+
+} // namespace projects
+} // namesapce session
+
+#endif // SESSION_PROJECTS_PROJECTS_HPP
diff --git a/src/cpp/session/include/session/worker_safe/session/SessionClientEvent.hpp b/src/cpp/session/include/session/worker_safe/session/SessionClientEvent.hpp
new file mode 100644
index 0000000..a40e4b4
--- /dev/null
+++ b/src/cpp/session/include/session/worker_safe/session/SessionClientEvent.hpp
@@ -0,0 +1,174 @@
+/*
+ * SessionClientEvent.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_CLIENT_EVENT_HPP
+#define SESSION_SESSION_CLIENT_EVENT_HPP
+
+#include <string>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class FilePath;
+}
+
+namespace session {
+
+namespace client_events {
+
+extern const int kConsolePrompt;
+extern const int kConsoleWriteOutput;
+extern const int kConsoleWriteError ;
+extern const int kShowErrorMessage;
+extern const int kShowHelp;
+extern const int kBrowseUrl;
+extern const int kShowEditor;
+extern const int kChooseFile;
+extern const int kQuit;
+extern const int kSuicide;
+extern const int kAbendWarning;
+extern const int kBusy;
+extern const int kFileChanged;
+extern const int kWorkingDirChanged;
+extern const int kPlotsStateChanged;
+extern const int kViewData;
+extern const int kPackageStatusChanged;
+extern const int kInstalledPackagesChanged;
+extern const int kLocator;
+extern const int kConsoleResetHistory;
+extern const int kSessionSerialization;
+extern const int kHistoryEntriesAdded;
+extern const int kQuotaStatus;
+extern const int kFileEdit;
+extern const int kShowContent;
+extern const int kShowData;
+extern const int kAsyncCompletion;
+extern const int kSaveActionChanged;
+extern const int kConsoleWritePrompt;
+extern const int kConsoleWriteInput;
+extern const int kShowWarningBar;
+extern const int kOpenProjectError;
+extern const int kVcsRefresh;
+extern const int kAskPass;
+extern const int kConsoleProcessOutput;
+extern const int kConsoleProcessExit;
+extern const int kListChanged;
+extern const int kConsoleProcessCreated;
+extern const int kUiPrefsChanged;
+extern const int kHandleUnsavedChanges;
+extern const int kConsoleProcessPrompt;
+extern const int kConsoleProcessCreated;
+extern const int kHTMLPreviewStartedEvent;
+extern const int kHTMLPreviewOutputEvent;
+extern const int kHTMLPreviewCompletedEvent;
+extern const int kCompilePdfStartedEvent;
+extern const int kCompilePdfOutputEvent;
+extern const int kCompilePdfErrorsEvent;
+extern const int kCompilePdfCompletedEvent;
+extern const int kSynctexEditFile;
+extern const int kFindResult;
+extern const int kFindOperationEnded;
+extern const int kRPubsUploadStatus;
+extern const int kBuildStarted;
+extern const int kBuildOutput;
+extern const int kBuildCompleted;
+extern const int kBuildErrors;
+extern const int kDirectoryNavigate;
+extern const int kDeferredInitCompleted;
+extern const int kPlotsZoomSizeChanged;
+extern const int kSourceCppStarted;
+extern const int kSourceCppCompleted;
+extern const int kLoadedPackageUpdates;
+extern const int kActivatePane;
+extern const int kShowPresentationPane;
+extern const int kEnvironmentRefresh;
+extern const int kContextDepthChanged;
+extern const int kEnvironmentAssigned;
+extern const int kEnvironmentRemoved;
+extern const int kBrowserLineChanged;
+extern const int kPackageLoaded;
+extern const int kPackageUnloaded;
+extern const int kPresentationPaneRequestCompleted;
+extern const int kUnhandledError;
+extern const int kErrorHandlerChanged;
+extern const int kViewerNavigate;
+extern const int kSourceExtendedTypeDetected;
+extern const int kShinyViewer;
+extern const int kDebugSourceCompleted;
+}
+
+class ClientEvent
+{
+public:
+ explicit ClientEvent(int type)
+ {
+ init(type, core::json::Value());
+ }
+
+ ClientEvent(int type, const core::json::Value& data)
+ {
+ init(type, data);
+ }
+
+ ClientEvent(int type, const char* data)
+ {
+ init(type, core::json::Value(std::string(data)));
+ }
+
+ ClientEvent(int type, const std::string& data)
+ {
+ init(type, core::json::Value(data));
+ }
+
+ ClientEvent(int type, bool data)
+ {
+ core::json::Object boolObject ;
+ boolObject["value"] = data;
+ init(type, boolObject);
+ }
+
+ // COPYING: via compiler (copyable members)
+
+public:
+ int type() const { return type_; }
+ std::string typeName() const;
+ const core::json::Value& data() const { return data_; }
+ const std::string& id() const { return id_; }
+
+ void asJsonObject(int id, core::json::Object* pObject) const;
+
+private:
+ void init(int type, const core::json::Value& data);
+
+private:
+ int type_ ;
+ core::json::Value data_ ;
+ std::string id_;
+};
+
+ClientEvent showEditorEvent(const std::string& content,
+ bool isRCode,
+ bool lineWrapping);
+
+ClientEvent browseUrlEvent(const std::string& url,
+ const std::string& window = "_blank");
+
+ClientEvent showErrorMessageEvent(const std::string& title,
+ const std::string& message);
+
+} // namespace session
+
+#endif // SESSION_SESSION_CLIENT_EVENT_HPP
+
diff --git a/src/cpp/session/include/session/worker_safe/session/SessionWorkerContext.hpp b/src/cpp/session/include/session/worker_safe/session/SessionWorkerContext.hpp
new file mode 100644
index 0000000..0dd0f14
--- /dev/null
+++ b/src/cpp/session/include/session/worker_safe/session/SessionWorkerContext.hpp
@@ -0,0 +1,41 @@
+/*
+ * SessionWorkerContext.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_WORKER_CONTEXT_HPP
+#define SESSION_WORKER_CONTEXT_HPP
+
+#include <string>
+
+#include <core/json/JsonRpc.hpp>
+
+#include "SessionClientEvent.hpp"
+
+namespace session {
+namespace worker_context {
+
+// register a worker method
+core::Error registerWorkerRpcMethod(const std::string& name,
+ const core::json::JsonRpcFunction& function);
+
+
+// enque client event
+void enqueClientEvent(const ClientEvent& event);
+
+
+} // namespace worker_context
+} // namespace session
+
+#endif // SESSION_WORKER_CONTEXT_HPP
+
diff --git a/src/cpp/session/modules/ModuleTools.R b/src/cpp/session/modules/ModuleTools.R
new file mode 100644
index 0000000..46b8bf6
--- /dev/null
+++ b/src/cpp/session/modules/ModuleTools.R
@@ -0,0 +1,97 @@
+#
+# ModuleTools.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("enqueClientEvent", function(type, data = NULL)
+{
+ .Call("rs_enqueClientEvent", type, data)
+})
+
+.rs.addFunction("showErrorMessage", function(title, message)
+{
+ .Call("rs_showErrorMessage", title, message)
+})
+
+.rs.addFunction("logErrorMessage", function(message)
+{
+ .Call("rs_logErrorMessage", message)
+})
+
+.rs.addFunction("logWarningMessage", function(message)
+{
+ .Call("rs_logWarningMessage", message)
+})
+
+.rs.addFunction("getSignature", function(obj)
+{
+ sig = capture.output(print(args(obj)))
+ sig = sig[1:length(sig)-1]
+ sig = gsub('^\\s+', '', sig)
+ paste(sig, collapse='')
+})
+
+# Wrap a return value in this to give a hint to the
+# JSON serializer that one-element vectors should be
+# marshalled as scalar types instead of arrays
+.rs.addFunction("scalar", function(obj)
+{
+ class(obj) <- 'rs.scalar'
+ return(obj)
+})
+
+.rs.addFunction("validateAndNormalizeEncoding", function(encoding)
+{
+ iconvList <- toupper(iconvlist())
+ encodingUpper <- toupper(encoding)
+ if (encodingUpper %in% iconvList)
+ {
+ return (encodingUpper)
+ }
+ else
+ {
+ encodingUpper <- gsub("[_]", "-", encodingUpper)
+ if (encodingUpper %in% iconvList)
+ return (encodingUpper)
+ else
+ return ("")
+ }
+})
+
+.rs.addFunction("usingUtf8Charset", function()
+{
+ l10n_info()$`UTF-8` || identical(utils::localeToCharset(), "UTF-8")
+})
+
+
+.rs.addFunction("isRtoolsOnPath", function()
+{
+ return (nzchar(Sys.which("ls.exe")) && nzchar(Sys.which("gcc.exe")))
+})
+
+.rs.addFunction("getPackageFunction", function(name, packageName)
+{
+ tryCatch(eval(parse(text=paste(packageName, ":::", name, sep=""))),
+ error = function(e) NULL)
+})
+
+.rs.addFunction("isPackageInstalled", function(name, libLoc = NULL)
+{
+ name %in% .packages(all.available = TRUE, lib.loc = libLoc)
+})
+
+.rs.addFunction("isPackageVersionInstalled", function(name, version) {
+ .rs.isPackageInstalled(name) && (.rs.getPackageVersion(name) >= version)
+})
+
+
diff --git a/src/cpp/session/modules/SessionAbout.cpp b/src/cpp/session/modules/SessionAbout.cpp
new file mode 100644
index 0000000..1767c37
--- /dev/null
+++ b/src/cpp/session/modules/SessionAbout.cpp
@@ -0,0 +1,61 @@
+/*
+ * SessionAbout.cpp
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionAbout.hpp"
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/json/JsonRpc.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include <string>
+
+#include "session-config.h"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace about {
+namespace {
+
+Error productInfo(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object result;
+ result["version"] = RSTUDIO_VERSION;
+ result["notice"] = module_context::resourceFileAsString("NOTICE");
+ pResponse->setResult(result);
+ return Success();
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "get_product_info", productInfo))
+ ;
+ return initBlock.execute();
+}
+
+} // namespace about
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionAbout.hpp b/src/cpp/session/modules/SessionAbout.hpp
new file mode 100644
index 0000000..c6c9077
--- /dev/null
+++ b/src/cpp/session/modules/SessionAbout.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionAbout.hpp
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_ABOUT_HPP
+#define SESSION_ABOUT_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace about {
+
+core::Error initialize();
+
+} // namespace about
+} // namespace modules
+} // namespace session
+
+#endif
\ No newline at end of file
diff --git a/src/cpp/session/modules/SessionAgreement.cpp b/src/cpp/session/modules/SessionAgreement.cpp
new file mode 100644
index 0000000..8f250c4
--- /dev/null
+++ b/src/cpp/session/modules/SessionAgreement.cpp
@@ -0,0 +1,191 @@
+/*
+ * SessionAgreement.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionAgreement.hpp"
+
+#include <string>
+
+#include <boost/function.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Hash.hpp>
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace agreement {
+
+namespace {
+
+struct Agreement
+{
+ Agreement() : updated(false) {}
+ Agreement(const std::string& title,
+ const std::string& contents,
+ const std::string& hash,
+ bool updated)
+ : title(title), contents(contents), hash(hash), updated(updated)
+ {
+ }
+
+ bool empty() const { return hash.empty(); }
+
+ const std::string title;
+ const std::string contents;
+ const std::string hash;
+ const bool updated ;
+};
+
+Error agreementFileContents(std::string* pContents, std::string* pContentType)
+{
+ FilePath agreementFilePath = session::options().agreementFilePath();
+ *pContentType = agreementFilePath.mimeContentType();
+ return readStringFromFile(agreementFilePath, pContents);
+}
+
+Agreement checkForPendingAgreement()
+{
+ // get hash of any previously agreed to agreement
+ std::string agreedToHash = userSettings().agreementHash();
+
+ // read agreement file contents
+ std::string contents, contentType ;
+ Error error = agreementFileContents(&contents, &contentType);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return Agreement();
+ }
+
+ // hash: filename + crc32 checksum of contents
+ std::string hash = session::options().agreementFilePath().filename() +
+ hash::crc32Hash(contents);
+
+ // see if we have not yet agreed to this agreement
+ if (hash != agreedToHash)
+ {
+ // set updated flag based on whether there was a previous agreement
+ bool updated = !agreedToHash.empty();
+
+ // return the pending agreement
+ return Agreement("RStudio Agreement",
+ contents,
+ hash,
+ updated);
+ }
+ else
+ {
+ // no agreement pending, return empty Agreement
+ return Agreement();
+ }
+}
+
+Error handleAcceptAgreement(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read hash param
+ std::string hash ;
+ Error paramError = json::readParam(request.params, 0, &hash);
+ if (paramError)
+ return paramError;
+
+ // set it
+ userSettings().setAgreementHash(hash);
+
+ // return success
+ return Success();
+}
+
+void handleAgreementRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // attempt to read the agreement
+ std::string agreementContents, contentType;
+ Error error = agreementFileContents(&agreementContents, &contentType);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // set it as our response
+ pResponse->setNoCacheHeaders();
+ pResponse->setContentType(contentType);
+ pResponse->setBody(agreementContents);
+}
+
+
+} // anonymous namespace
+
+bool hasAgreement()
+{
+ return !session::options().agreementFilePath().empty();
+}
+
+json::Value pendingAgreement()
+{
+ if ( hasAgreement() &&
+ (session::options().programMode() == kSessionProgramModeServer) )
+ {
+ Agreement agreement = checkForPendingAgreement();
+ if (!agreement.empty())
+ {
+ json::Object jsonAgreement;
+ jsonAgreement["title"] = agreement.title;
+ jsonAgreement["contents"] = agreement.contents;
+ jsonAgreement["hash"] = agreement.hash;
+ jsonAgreement["updated"] = agreement.updated;
+ return jsonAgreement;
+ }
+ else
+ {
+ return json::Value();
+ }
+ }
+ else
+ {
+ return json::Value();
+ }
+}
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "accept_agreement", handleAcceptAgreement))
+ (bind(registerUriHandler, "/agreement", handleAgreementRequest))
+ ;
+
+ return initBlock.execute();
+}
+
+
+} // namespace agreement
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionAgreement.hpp b/src/cpp/session/modules/SessionAgreement.hpp
new file mode 100644
index 0000000..94ea5b9
--- /dev/null
+++ b/src/cpp/session/modules/SessionAgreement.hpp
@@ -0,0 +1,40 @@
+/*
+ * SessionAgreement.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_AGREEMENT_HPP
+#define SESSION_AGREEMENT_HPP
+
+#include <core/json/JsonRpc.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace agreement {
+
+bool hasAgreement();
+
+core::json::Value pendingAgreement();
+
+core::Error initialize();
+
+} // namespace agreeement
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_AGREEMENT_HPP
+
diff --git a/src/cpp/session/modules/SessionAskPass.R b/src/cpp/session/modules/SessionAskPass.R
new file mode 100644
index 0000000..b0b1a21
--- /dev/null
+++ b/src/cpp/session/modules/SessionAskPass.R
@@ -0,0 +1,20 @@
+#
+# SessionAskPass.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction( "askForPassword", function(prompt)
+{
+ .Call("rs_askForPassword", prompt)
+})
+
diff --git a/src/cpp/session/modules/SessionAskPass.cpp b/src/cpp/session/modules/SessionAskPass.cpp
new file mode 100644
index 0000000..b3b04ec
--- /dev/null
+++ b/src/cpp/session/modules/SessionAskPass.cpp
@@ -0,0 +1,175 @@
+/*
+ * SessionAskPass.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionAskPass.hpp"
+
+#include <boost/bind.hpp>
+
+#include <core/Exec.hpp>
+#include <core/Log.hpp>
+#include <core/json/Json.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "session-config.h"
+
+#ifdef RSTUDIO_SERVER
+#include <core/system/Crypto.hpp>
+#endif
+
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace ask_pass {
+
+namespace {
+
+std::string s_askPassWindow;
+module_context::WaitForMethodFunction s_waitForAskPass;
+
+void onClientInit()
+{
+ s_askPassWindow = "";
+}
+
+
+// show error message from R
+SEXP rs_askForPassword(SEXP promptSEXP)
+{
+ try
+ {
+ std::string prompt = r::sexp::asString(promptSEXP);
+
+ PasswordInput input;
+ Error error = askForPassword(prompt, "", &input);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return R_NilValue;
+ }
+ else if (input.cancelled || input.password.empty())
+ {
+ return R_NilValue;
+ }
+ else
+ {
+ r::sexp::Protect rProtect;
+ return r::sexp::create(input.password, &rProtect);
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+} // anonymous namespace
+
+std::string activeWindow()
+{
+ return s_askPassWindow;
+}
+
+void setActiveWindow(const std::string& window)
+{
+ s_askPassWindow = window;
+}
+
+Error askForPassword(const std::string& prompt,
+ const std::string& rememberPrompt,
+ PasswordInput* pInput)
+{
+ json::Object payload;
+ payload["prompt"] = prompt;
+ payload["remember_prompt"] = rememberPrompt;
+ payload["window"] = s_askPassWindow;
+ ClientEvent askPassEvent(client_events::kAskPass, payload);
+
+ // wait for method
+ core::json::JsonRpcRequest request;
+ if (!s_waitForAskPass(&request, askPassEvent))
+ {
+ return systemError(boost::system::errc::operation_canceled,
+ ERROR_LOCATION);
+ }
+
+ // read params
+ json::Value value;
+ bool remember = false;
+ Error error = json::readParams(request.params, &value, &remember);
+ if (error)
+ return error;
+
+ // null passphrase means dialog was cancelled
+ if (!json::isType<std::string>(value))
+ {
+ pInput->cancelled = true;
+ return Success();
+ }
+
+ // read inputs
+ pInput->remember = remember;
+ pInput->password = value.get_value<std::string>();
+
+ // decrypt if necessary
+#ifdef RSTUDIO_SERVER
+ if (options().programMode() == kSessionProgramModeServer)
+ {
+ // In server mode, passphrases are encrypted
+ error = core::system::crypto::rsaPrivateDecrypt(
+ pInput->password,
+ &pInput->password);
+ if (error)
+ return error;
+ }
+#endif
+
+ return Success();
+}
+
+
+Error initialize()
+{
+ module_context::events().onClientInit.connect(onClientInit);
+
+ // register waitForMethod handler
+ s_waitForAskPass = module_context::registerWaitForMethod(
+ "askpass_completed");
+
+ // register rs_askForPassword with R
+ R_CallMethodDef methodDefAskPass ;
+ methodDefAskPass.name = "rs_askForPassword" ;
+ methodDefAskPass.fun = (DL_FUNC) rs_askForPassword ;
+ methodDefAskPass.numArgs = 1;
+ r::routines::addCallMethod(methodDefAskPass);
+
+ // complete initialization
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (boost::bind(module_context::sourceModuleRFile, "SessionAskPass.R"));
+ return initBlock.execute();
+
+}
+
+
+} // namespace content_urls
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionAskPass.hpp b/src/cpp/session/modules/SessionAskPass.hpp
new file mode 100644
index 0000000..c4e12ab
--- /dev/null
+++ b/src/cpp/session/modules/SessionAskPass.hpp
@@ -0,0 +1,51 @@
+/*
+ * SessionAskPass.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_ASK_PASS_HPP
+#define SESSION_SESSION_ASK_PASS_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace ask_pass {
+
+
+std::string activeWindow();
+void setActiveWindow(const std::string& windowName);
+
+struct PasswordInput
+{
+ PasswordInput() : cancelled(false), remember(false) {}
+ bool cancelled;
+ std::string password;
+ bool remember;
+};
+
+core::Error askForPassword(const std::string& prompt,
+ const std::string& rememberPrompt,
+ PasswordInput* pInput);
+
+core::Error initialize();
+
+} // namespace ask_pass
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_ASK_PASS_HPP
diff --git a/src/cpp/session/modules/SessionAuthoring.R b/src/cpp/session/modules/SessionAuthoring.R
new file mode 100644
index 0000000..057d7a8
--- /dev/null
+++ b/src/cpp/session/modules/SessionAuthoring.R
@@ -0,0 +1,72 @@
+#
+# SessionAuthoring.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+
+.rs.addFunction( "knitrChunkOptions", function()
+{
+ # starting with v0.4.2 this is avavilable directly from knitr
+ if (.rs.getPackageVersion("knitr") >= "0.4.2")
+ return(knitr:::opts_chunk_attr)
+
+ # still try to return correct results for previous versions
+ knitrOptions <- knitr:::opts_chunk$get()
+ knitrOptions <- as.list(sapply(knitrOptions, class))
+ knitrOptions[knitrOptions == "NULL"] <- "character"
+ knitrOptions$results <- list("markup", "asis", "hide")
+ knitrOptions$fig.show <- list("asis", "hold", "animate")
+ knitrOptions$fig.keep <- list("high", "none", "all", "first", "last")
+ knitrOptions$fig.align <- list("left", "right", "center")
+ if (.rs.getPackageVersion("knitr") >= "0.4")
+ knitrOptions$dev <- as.list(names(knitr:::auto_exts))
+
+ return (knitrOptions)
+})
+
+.rs.addFunction( "sweaveChunkOptions", function()
+{
+ sweaveOptions <- list()
+
+ sweaveOptions$label <- "character"
+ sweaveOptions$engine <- list("R", "S")
+ sweaveOptions$echo <- "logical"
+ sweaveOptions$keep.source <- "logical"
+ sweaveOptions$eval <- "logical"
+ sweaveOptions$results <- list("verbatim", "tex", "hide")
+ sweaveOptions$print <- "logical"
+ sweaveOptions$term <- "logical"
+ sweaveOptions$split <- "logical"
+ sweaveOptions$strip.white <- list("true", "all", "false")
+ sweaveOptions$prefix <- "logical"
+ sweaveOptions$prefix.string <- "character"
+ sweaveOptions$include <- "logical"
+ sweaveOptions$fig <- "logical"
+ sweaveOptions$eps <- "logical"
+ sweaveOptions$pdf <- "logical"
+ sweaveOptions$pdf.version <- "character"
+ sweaveOptions$pdf.encoding <- "character"
+ sweaveOptions$pdf.compress <-"logical"
+ if (getRversion() >= "2.13.0") {
+ sweaveOptions$png <- "logical"
+ sweaveOptions$jpeg <- "logical"
+ sweaveOptions$grdevice <- "character"
+ }
+ sweaveOptions$width <- "numeric"
+ sweaveOptions$height <- "numeric"
+ sweaveOptions$resolution <- "numeric"
+ sweaveOptions$figs.only <- "logical"
+
+ return (sweaveOptions)
+})
+
diff --git a/src/cpp/session/modules/SessionAuthoring.cpp b/src/cpp/session/modules/SessionAuthoring.cpp
new file mode 100644
index 0000000..b1cc93b
--- /dev/null
+++ b/src/cpp/session/modules/SessionAuthoring.cpp
@@ -0,0 +1,279 @@
+/*
+ * SessionAuthoring.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionAuthoring.hpp"
+
+#include <string>
+
+#include <boost/regex.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "tex/SessionCompilePdf.hpp"
+#include "tex/SessionRnwWeave.hpp"
+#include "tex/SessionPdfLatex.hpp"
+#include "tex/SessionCompilePdf.hpp"
+#include "tex/SessionCompilePdfSupervisor.hpp"
+#include "tex/SessionSynctex.hpp"
+#include "tex/SessionViewPdf.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace authoring {
+
+namespace {
+
+FilePath pdfFilePath(const FilePath& texFilePath)
+{
+ return texFilePath.parent().complete(texFilePath.stem() + ".pdf");
+}
+
+void viewPdfExternal(const FilePath& texPath)
+{
+ module_context::showFile(pdfFilePath(texPath),
+ "_rstudio_compile_pdf");
+}
+
+Error getTexCapabilities(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(authoring::texCapabilitiesAsJson());
+ return Success();
+}
+
+Error getChunkOptions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string weaveType;
+ Error error = json::readParams(request.params, &weaveType);
+ if (error)
+ return error;
+
+ pResponse->setResult(tex::rnw_weave::chunkOptions(weaveType));
+ return Success();
+}
+
+Error isTexInstalled(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(tex::pdflatex::isInstalled());
+ return Success();
+}
+
+Error compilePdf(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string targetFile, encoding, completedAction;
+ json::Object sourceLocation;
+ Error error = json::readParams(request.params,
+ &targetFile,
+ &encoding,
+ &sourceLocation,
+ &completedAction);
+ if (error)
+ return error;
+
+ // determine compilation target
+ FilePath targetFilePath = module_context::resolveAliasedPath(targetFile);
+
+ // initialize the completed function
+ boost::function<void()> completedFunction;
+ if (completedAction == "view_external")
+ completedFunction = boost::bind(viewPdfExternal, targetFilePath);
+
+ // attempt to kickoff the compile
+ bool started = tex::compile_pdf::startCompile(targetFilePath,
+ encoding,
+ sourceLocation,
+ completedFunction);
+
+ // return true
+ pResponse->setResult(started);
+ return Success();
+}
+
+Error isCompilePdfRunning(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+
+ pResponse->setResult(tex::compile_pdf::compileIsRunning());
+
+ return Success();
+}
+
+
+Error terminateCompilePdf(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+
+ pResponse->setResult(tex::compile_pdf::terminateCompile());
+
+ return Success();
+}
+
+
+Error compilePdfClosed(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+
+ tex::compile_pdf::notifyTabClosed();
+ return Success();
+}
+
+bool isBrowserSupported(const std::string& userAgent,
+ const boost::regex& versionRegEx,
+ double requiredVersion)
+{
+ double detectedVersion = requiredVersion;
+ boost::smatch match;
+ if (boost::regex_search(userAgent, match, versionRegEx))
+ {
+ std::string versionString = match[1];
+ detectedVersion = safe_convert::stringTo<double>(versionString,
+ detectedVersion);
+ if (detectedVersion < requiredVersion)
+ return false;
+ }
+
+ return true;
+}
+
+SEXP rs_rnwTangle(SEXP filePathSEXP, SEXP rnwWeaveSEXP)
+{
+ try
+ {
+ modules::tex::rnw_weave::runTangle(r::sexp::asString(filePathSEXP),
+ r::sexp::asString(rnwWeaveSEXP));
+ }
+ catch(const r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+
+
+} // anonymous namespace
+
+
+bool isPdfViewerSupported(const std::string& userAgent)
+{
+ // Qt 4.7 not supported
+ bool isQt47 = userAgent.find("Qt/4.7") != std::string::npos;
+ if (isQt47)
+ return false;
+
+ // Firefox >= 6 required
+ boost::regex ffRegEx("Firefox/(\\d{1,4})");
+ if (!isBrowserSupported(userAgent, ffRegEx, 6.0))
+ return false;
+
+ // Safari >= 5.1 required (look for both Safari and not Chrome in the UA
+ // since Chrome also includes Safari in its UA)
+ if ( userAgent.find("Safari") != std::string::npos &&
+ userAgent.find("Chrome") == std::string::npos)
+ {
+ boost::regex safariRegEx("Version/(\\d{1,4}\\.\\d)");
+ if (!isBrowserSupported(userAgent, safariRegEx, 5.1))
+ return false;
+ }
+
+ return true;
+}
+
+json::Array supportedRnwWeaveTypes()
+{
+ return tex::rnw_weave::supportedTypes();
+}
+
+json::Array supportedLatexProgramTypes()
+{
+ return tex::pdflatex::supportedTypes();
+}
+
+json::Object texCapabilitiesAsJson()
+{
+ json::Object obj;
+
+ obj["tex_installed"] = tex::pdflatex::isInstalled();
+
+ tex::rnw_weave::getTypesInstalledStatus(&obj);
+
+ return obj;
+}
+
+bool hasRunningChildren()
+{
+ return tex::compile_pdf_supervisor::hasRunningChildren();
+}
+
+json::Object compilePdfStateAsJson()
+{
+ return tex::compile_pdf::currentStateAsJson();
+}
+
+
+Error initialize()
+{
+ // register tanble function
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_rnwTangle" ;
+ methodDef.fun = (DL_FUNC) rs_rnwTangle ;
+ methodDef.numArgs = 2;
+ r::routines::addCallMethod(methodDef);
+
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionAuthoring.R"))
+ (tex::compile_pdf::initialize)
+ (tex::compile_pdf_supervisor::initialize)
+ (tex::synctex::initialize)
+ (tex::view_pdf::initialize)
+ (bind(registerRpcMethod, "is_tex_installed", isTexInstalled))
+ (bind(registerRpcMethod, "get_tex_capabilities", getTexCapabilities))
+ (bind(registerRpcMethod, "get_chunk_options", getChunkOptions))
+ (bind(registerRpcMethod, "compile_pdf", compilePdf))
+ (bind(registerRpcMethod, "is_compile_pdf_running", isCompilePdfRunning))
+ (bind(registerRpcMethod, "terminate_compile_pdf", terminateCompilePdf))
+ (bind(registerRpcMethod, "compile_pdf_closed", compilePdfClosed))
+ ;
+ return initBlock.execute();
+}
+
+} // namespace authoring
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionAuthoring.hpp b/src/cpp/session/modules/SessionAuthoring.hpp
new file mode 100644
index 0000000..c59a44a
--- /dev/null
+++ b/src/cpp/session/modules/SessionAuthoring.hpp
@@ -0,0 +1,46 @@
+/*
+ * SessionAuthoring.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_AUTHORING_HPP
+#define SESSION_MODULES_AUTHORING_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace authoring {
+
+// accessors used by SessionMain
+bool isPdfViewerSupported(const std::string& userAgent);
+core::json::Array supportedRnwWeaveTypes();
+core::json::Array supportedLatexProgramTypes();
+core::json::Object texCapabilitiesAsJson();
+core::json::Object compilePdfStateAsJson();
+bool hasRunningChildren();
+
+// accessors used by workbench
+std::string desktopSynctexViewer();
+
+core::Error initialize();
+
+} // namespace authoring
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_AUTHORING_HPP
diff --git a/src/cpp/session/modules/SessionBreakpoints.R b/src/cpp/session/modules/SessionBreakpoints.R
new file mode 100644
index 0000000..34e0b79
--- /dev/null
+++ b/src/cpp/session/modules/SessionBreakpoints.R
@@ -0,0 +1,505 @@
+#
+# SessionBreakpoints.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# given a function name and filename, find the environment that contains a
+# function with the given name that originated from the given file.
+.rs.addFunction("getEnvironmentOfFunction", function(
+ objName, fileName, packageName)
+{
+ isPackage <- nchar(packageName) > 0
+
+ # when searching specifically for a function in a package, search from the
+ # package namespace to the global environment (considers package imports and
+ # non-exported functions); otherwise, search from the global environment to
+ # the empty namespace
+ lastEnvir <- if (isPackage) "R_GlobalEnv" else "R_EmptyEnv"
+ env <- if (isPackage)
+ asNamespace(packageName)
+ else
+ globalenv()
+ while (environmentName(env) != lastEnvir)
+ {
+ # if the function with the given name exists in this environment...
+ if (!is.null(env) &&
+ exists(objName, env, mode = "function", inherits = FALSE))
+ {
+ # we need the source reference to look up the filename; we may need to
+ # access the original copy of a traced function to get this
+ srcref <- .rs.getSrcref(get(objName, env))
+ if (!is.null(srcref))
+ {
+ # get the name of the file from which the function originated, and
+ # trim off the trailing space; see if it matches the filename
+ fileattr <- attr(srcref, "srcfile")
+ if (normalizePath(fileattr$filename) == normalizePath(fileName))
+ {
+ return (env)
+ }
+ }
+ }
+ env <- parent.env(env)
+ }
+ return(NULL)
+})
+
+# given the body of a function, search recursively through its parsed
+# representation for a step with a given source reference line number.
+.rs.addFunction("stepsAtLine", function(funBody, line)
+{
+ if (typeof(funBody) != "language")
+ {
+ return(NULL)
+ }
+
+ refs <- attr(funBody, "srcref")
+ for (idx in 1:length(funBody))
+ {
+ # if there's a source ref on this line, check it against the line number
+ # provided by the caller
+ ref <- refs[[idx]]
+ if (length(ref) > 0)
+ {
+ if (line > ref[3] || line < ref[1])
+ {
+ next
+ }
+ }
+
+ # check for sub-steps--these exist when there's a nested function
+ nestedFunSteps <- .rs.stepsAtLine(funBody[[idx]], line)
+ if (!is.null(nestedFunSteps))
+ {
+ return(c(idx, nestedFunSteps))
+ }
+
+ # match functions rather than opening brackets
+ if (length(ref) > 0 &&
+ !(typeof(funBody[[idx]]) == "symbol" &&
+ identical(as.character(funBody[[idx]]), "{")))
+ {
+ return(idx)
+ }
+ }
+ return(NULL)
+})
+
+# Given the body of the function, return the substeps of the function on which
+# a breakpoint was set (not the substep of the breakpoint itself).
+.rs.addFunction("findBreakpointSteps", function(funBody)
+{
+ if (typeof(funBody) != "language")
+ {
+ return(NULL)
+ }
+ for (idx in 1:length(funBody))
+ {
+ # if this is a doTrace call, we found a breakpoint; stop recursion here
+ if (is.call(funBody[[idx]]) &&
+ as.character(funBody[[idx]][[1]]) == ".doTrace")
+ {
+ return(idx + 1)
+ }
+ nestedSteps <- .rs.findBreakpointSteps(funBody[[idx]])
+ if (!is.null(nestedSteps))
+ {
+ return(c(idx, nestedSteps))
+ }
+ }
+})
+
+# Given a function, return the function with all of the breakpoints removed.
+.rs.addFunction("removeBreakpoints", function(fun)
+{
+ repeat
+ {
+ # Find the step on which a breakpoint was set
+ inner <- .rs.findBreakpointSteps(body(fun))
+ if (length(inner) < 2)
+ break;
+
+ # Replace the outer expression (which contains the breakpoint) with the
+ # inner one (which contains the expression formerly wrapped by the
+ # breakpoint)
+ outer <- inner[1:(length(inner)-1)]
+ body(fun)[[outer]] <- body(fun)[[inner]]
+ }
+ fun
+})
+
+# given a traced function body and the original function body, recursively copy
+# the source references from the original body to the traced body, adding
+# source references to the injected trace code from the line being traced
+.rs.addFunction("tracedSourceRefs",function(funBody, originalFunBody)
+{
+ if (is.symbol(funBody) || is.symbol(originalFunBody))
+ {
+ return (funBody)
+ }
+
+ # start with a copy of the original source references
+ attr(funBody, "srcref") <- attr(originalFunBody, "srcref")
+
+ for (idx in 1:length(funBody))
+ {
+ # Check to see if this is one of the several types of objects we can't do
+ # equality testing for. Note that these object types are all leaf nodes in
+ # the parse tree, so it's safe to stop recursion here. Also note that we
+ # can't use the helpful is.na() here since that function emits warnings
+ # for some types of objects in the parse tree.
+ if (is.null(funBody[[idx]]) ||
+ identical(funBody[[idx]], NA) ||
+ identical(funBody[[idx]], NA_character_) ||
+ identical(funBody[[idx]], NA_complex_) ||
+ identical(funBody[[idx]], NA_integer_) ||
+ identical(funBody[[idx]], NA_real_) ||
+ is.pairlist(funBody[[idx]]))
+ next
+
+ # if this expression was replaced by trace(), copy the source references
+ # from the original expression over each expression injected by trace()
+ if (length(funBody[[idx]]) != length(originalFunBody[[idx]]) ||
+ sum(funBody[[idx]] != originalFunBody[[idx]]) > 0)
+ {
+ attr(funBody[[idx]], "srcref") <-
+ rep(list(attr(originalFunBody, "srcref")[[idx]]), length(funBody[[idx]]))
+ }
+
+ # recurse to symbol level
+ else if (is.language(funBody[[idx]]))
+ {
+ funBody[[idx]] <- .rs.tracedSourceRefs(
+ funBody[[idx]],
+ originalFunBody[[idx]])
+ }
+ }
+ return(funBody)
+})
+
+.rs.addFunction("getFunctionSteps", function(fun, functionName, lineNumbers)
+{
+ funBody <- body(fun)
+ lineNumbers <- unique(lineNumbers)
+
+ # attempt to find the end line of the function
+ funStartLine <- 0
+ funEndLine <- 0
+ funSrcRef <- attr(fun, "srcref")
+ if (!is.null(funSrcRef) && length(funSrcRef) > 3)
+ {
+ funStartLine <- funSrcRef[1]
+ funEndLine <- funSrcRef[3]
+ }
+ else
+ {
+ return(list())
+ }
+
+ # process each line on which a breakpoint was requested
+ lapply(lineNumbers, function(lineNumber)
+ {
+ # don't try to process lines that aren't inside the body of the function
+ steps <- integer()
+ if (lineNumber >= funStartLine &&
+ lineNumber <= funEndLine)
+ {
+ # if we don't find any function steps associated with the given line
+ # number, keep trying the next one until we do, up to the end of the
+ # function (as marked by its source references)
+ repeat
+ {
+ steps <- .rs.stepsAtLine(funBody, lineNumber)
+ if (length(steps) > 0 ||
+ lineNumber >= funEndLine)
+ {
+ break
+ }
+ lineNumber <- lineNumber + 1
+ }
+ }
+
+ list(
+ name=.rs.scalar(functionName),
+ line=.rs.scalar(lineNumber),
+ at=.rs.scalar(paste(steps, collapse=",")))
+ })
+
+})
+
+# this function is used to get the steps in the given function that are
+# associated with the given line number, using the function's source
+# references.
+.rs.addFunction("getSteps", function(
+ functionName,
+ fileName,
+ packageName,
+ lineNumbers)
+{
+ .rs.getFunctionSteps(
+ .rs.getUntracedFunction(functionName, fileName, packageName),
+ functionName,
+ lineNumbers)
+})
+
+.rs.addFunction("setFunctionBreakpoints", function(
+ functionName,
+ envir,
+ steps)
+{
+ if (length(steps) == 0 || nchar(steps) == 0)
+ {
+ # Restore the function to its original state. Note that trace/untrace
+ # emit messages when they act on a function in a package environment; hide
+ # those messages since they're just noise to the user.
+ fun <- get(functionName, envir = envir)
+ if (.rs.isTraced(fun))
+ {
+ suppressMessages(untrace(
+ what = functionName,
+ where = envir))
+ }
+ }
+ else
+ {
+ # inject the browser calls
+ suppressMessages(trace(
+ what = functionName,
+ where = envir,
+ at = lapply(strsplit(as.character(steps), ","), as.numeric),
+ tracer = browser,
+ print = FALSE))
+
+ # unlock the binding if necessary to inject the source references;
+ # bindings are often locked in package environments
+ lockedBinding <- FALSE
+
+ # remap the source references so that the code injected by trace() is
+ # mapped back to the line on which the breakpoint was set. We need to
+ # assign directly to the @.Data internal slot since assignment to the
+ # public-facing body of the function will remove the tracing information.
+ tryCatch({
+ if (bindingIsLocked(functionName, envir))
+ {
+ unlockBinding(functionName, envir)
+ lockedBinding <- TRUE
+ }
+ body(envir[[functionName]]@.Data) <- .rs.tracedSourceRefs(
+ body(envir[[functionName]]@.Data),
+ body(envir[[functionName]]@original))
+ },
+ finally =
+ {
+ # restore the lock
+ if (lockedBinding)
+ {
+ lockBinding(functionName, envir)
+ }
+ }
+ )
+ }
+ return(functionName)
+})
+
+.rs.addFunction("getUntracedFunction", function(
+ functionName, fileName, packageName)
+{
+ envir <- .rs.getEnvironmentOfFunction(functionName, fileName, packageName)
+ if (is.null(envir))
+ {
+ return(NULL)
+ }
+ .rs.untraced(get(functionName, mode="function", envir=envir))
+})
+
+.rs.addFunction("getFunctionSourceRefs", function(
+ functionName, fileName, packageName)
+{
+ fun <- .rs.getUntracedFunction(functionName, fileName, packageName)
+ if (is.null(fun))
+ {
+ return(NULL)
+ }
+ attr(fun, "srcref")
+})
+
+.rs.addFunction("getFunctionSourceCode", function(
+ functionName, fileName, packageName)
+{
+ paste(capture.output(
+ .rs.getFunctionSourceRefs(functionName, fileName, packageName)),
+ collapse="\n")
+})
+
+# Parses and executes a file for debugging
+.rs.addFunction("executeDebugSource", function(fileName, encoding, breaklines)
+{
+ # Create a function containing the parsed contents of the file
+ env <- new.env(parent = emptyenv())
+ env$fun <- .rs.makeSourceEquivFunction(fileName, encoding)
+ breakSteps <- character()
+
+ # Inject the breakpoints
+ if (length(breaklines) > 0)
+ {
+ steps <- .rs.getFunctionSteps(env$fun, "fun", breaklines)
+ breakSteps <- unlist(lapply(steps, function(step) step$at))
+ suppressWarnings(.rs.setFunctionBreakpoints(
+ "fun", env, lapply(steps, function(step) { step$at } )))
+ }
+
+ # Run it!
+ env$fun()
+
+ # We injected function breakpoints above, but we don't want to leave them
+ # in the in-memory copies of the functions. Replay the assignment that
+ # created the original copy of the function. The client will set proper
+ # breakpoints on the functions later.
+ env$fun <- .rs.removeBreakpoints(env$fun)
+ breakSteps <- breakSteps[nchar(breakSteps) > 6]
+ for (steps in breakSteps) {
+ step <- as.numeric(strsplit(breakSteps, ",")[[1]][3])
+ op <- deparse(body(env$fun)[[2]][[2]][[step]][[1]])
+ if (op == "<-" || op == "=")
+ eval(body(env$fun)[[2]][[2]][[step]], envir = globalenv())
+ }
+
+ return(NULL)
+
+}, attrs = list(hideFromDebugger = TRUE))
+
+.rs.addFunction("getShinyFunction", function(name, where)
+{
+ if (is(where, "refClass"))
+ where$field(name)
+ else
+ get(name, where)
+})
+
+.rs.addFunction("setShinyFunction", function(name, where, val)
+{
+ if (is(where, "refClass"))
+ where$field(name, val)
+ else
+ assign(name, val, where)
+})
+
+.rs.addFunction("setShinyBreakpoints", function(name, where, lines)
+{
+ # Create a blank environment and load the function into it
+ env <- new.env(parent = emptyenv())
+ env$fun <- .rs.getShinyFunction(name, where)
+
+ # Get the steps of the function corresponding to the lines on
+ # which breakpoints are to be set, and set breakpoints there
+ steps <- .rs.getFunctionSteps(env$fun, "fun", lines)
+
+ suppressWarnings(.rs.setFunctionBreakpoints(
+ "fun", env, lapply(steps, function(step) { step$at } )))
+
+ # Store the updated copy of the function back into Shiny
+ .rs.setShinyFunction(name, where, env$fun)
+})
+
+# Given a filename, creates a source-equivalent function: a function that,
+# when executed, has the same effect as sourcing the file.
+.rs.addFunction("makeSourceEquivFunction", function(filename, encoding)
+{
+ content <- suppressWarnings(parse(filename, encoding))
+
+ # Create an empty function to host the expressions in the file
+ fun <- function()
+ {
+ evalq({ 1 }, envir = globalenv())
+ }
+
+ # Copy each statement from the file into the eval body of the function
+ for (i in 1:length(content)) {
+ body(fun)[[2]][[2]][[i + 1]] <- content[[i]]
+ }
+
+ # Set up the source references
+ refs <- attr(content, "srcref")
+ lastref <- length(refs)
+ attr(body(fun), "srcfile") <- attr(content, "srcfile")
+
+ # Simulate a source reference that contains the whole function by
+ # combining the first and last source references of each statement in
+ # the function
+ ref <- structure(c(refs[[1]][1], refs[[1]][2],
+ refs[[lastref]][3], refs[[lastref]][[4]],
+ refs[[1]][5], refs[[lastref]][6],
+ refs[[1]][1], refs[[lastref]][3]),
+ srcfile = attr(content, "srcfile"),
+ class = "srcref")
+ attr(body(fun), "srcref")[[2]] <- ref
+ linerefs <- list(attr(content, "srcref")[[1]])
+ for (i in 1:length(content)) {
+ linerefs[[i + 1]] <- attr(content, "srcref")[[i]]
+ }
+ attr(body(fun)[[2]][[2]], "srcref") <- linerefs
+ attr(fun, "srcref") <- ref
+ return(fun)
+})
+
+.rs.addGlobalFunction("debugSource", function(fileName, echo=FALSE,
+ encoding="unknown")
+{
+ # NYI: Consider whether we need to implement source with echo for debugging.
+ # This would likely involve injecting print statements into the generated
+ # source-equivalent function.
+ invisible(.Call("rs_debugSourceFile", fileName, encoding))
+})
+
+# Parameters expected to be in environment:
+# where - environment or reference object
+# name - name of function or reference field name
+# label - friendly label for function to show in callstack
+.rs.addGlobalFunction("registerShinyDebugHook", function(params)
+{
+ # Get the function from storage in Shiny, and remove any breakpoints it may
+ # already contain
+ fun <- .rs.getShinyFunction(params$name, params$where)
+ fun <- .rs.removeBreakpoints(fun)
+ params$expr <- body(fun)
+
+ # Copy source refs to the body of the function
+ attr(fun, "srcref") <- attr(body(fun), "wholeSrcref")
+ params$fun <- fun
+
+ # Register the function with RStudio (may set breakpoints)
+ .Call("rs_registerShinyFunction", params)
+})
+
+.rs.addJsonRpcHandler("get_function_steps", function(
+ functionName,
+ fileName,
+ packageName,
+ lineNumbers)
+{
+ .rs.getSteps(functionName, fileName, packageName, lineNumbers)
+})
+
+.rs.addFunction("haveAdvancedSteppingCommands", function() {
+ if (getRversion() >= "3.1") {
+ svnRev <- R.version$`svn rev`
+ if (!is.null(svnRev)) {
+ svnRevNumeric <- suppressWarnings(as.numeric(svnRev))
+ if (!is.na(svnRevNumeric) && length(svnRevNumeric) == 1)
+ return (svnRevNumeric >= 63400)
+ }
+ }
+ # fallthrough
+ return (FALSE)
+})
+
diff --git a/src/cpp/session/modules/SessionBreakpoints.cpp b/src/cpp/session/modules/SessionBreakpoints.cpp
new file mode 100644
index 0000000..93555f9
--- /dev/null
+++ b/src/cpp/session/modules/SessionBreakpoints.cpp
@@ -0,0 +1,627 @@
+/*
+ * SessionBreakpoints.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "environment/EnvironmentUtils.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/utility.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/RUtil.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RClientState.hpp>
+#include <r/RInternal.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core;
+using namespace r::sexp;
+using namespace r::exec;
+
+namespace session {
+namespace modules {
+namespace breakpoints {
+namespace
+{
+
+int s_maxShinyFunctionId = 0;
+
+// Represents a currently running Shiny function.
+class ShinyFunction : boost::noncopyable
+{
+public:
+ ShinyFunction(SEXP expr, const std::string& name, SEXP where):
+ id_(s_maxShinyFunctionId++),
+ firstLine_(0),
+ lastLine_(0),
+ name_(name),
+ where_(where)
+ {
+ // If the srcref attribute is present on the expression, use it to
+ // compute the first and last lines of the function in the original
+ // source file.
+ SEXP srcref = r::sexp::getAttrib(expr, "srcref");
+ if (srcref != NULL && TYPEOF(srcref) != NILSXP)
+ {
+ SEXP firstRef = VECTOR_ELT(srcref, 0);
+ firstLine_ = INTEGER(firstRef)[0];
+ SEXP lastRef = VECTOR_ELT(srcref, r::sexp::length(srcref) - 1);
+ lastLine_ = INTEGER(lastRef)[2];
+ }
+ // If the srcfile attribute is present, extract it
+ SEXP srcfile = r::sexp::getAttrib(expr, "srcfile");
+ if (srcfile != NULL && TYPEOF(srcfile) != NILSXP)
+ {
+ SEXP file = r::sexp::findVar("filename", srcfile);
+ r::sexp::extract(file, &srcfilename_);
+ }
+ }
+
+ bool contains(std::string filename, int line) const
+ {
+ if (!(line >= firstLine_ && line <= lastLine_))
+ return false;
+
+ return module_context::resolveAliasedPath(srcfilename_) ==
+ module_context::resolveAliasedPath(filename);
+ }
+
+ int getId()
+ {
+ return id_;
+ }
+
+ int getSize()
+ {
+ return lastLine_ - firstLine_;
+ }
+
+ std::string getName()
+ {
+ return name_;
+ }
+
+ SEXP getWhere()
+ {
+ return where_;
+ }
+
+private:
+ int id_;
+ int firstLine_;
+ int lastLine_;
+ std::string name_;
+ std::string srcfilename_;
+ SEXP where_;
+};
+
+// A list of the Shiny functions we know about (see notes in
+// rs_registerShinyFunction for an explanation of how this memory is managed)
+std::vector<boost::shared_ptr<ShinyFunction> > s_shinyFunctions;
+
+// Breakpoint data known by the server (subset of fields known by the client)
+#define TYPE_FUNCTION 0
+#define TYPE_TOPLEVEL 1
+
+class Breakpoint : boost::noncopyable
+{
+public:
+ int type;
+ int lineNumber;
+ int id;
+ std::string path;
+ Breakpoint(int typeIn, int lineNumberIn, int idIn, std::string pathIn):
+ type(typeIn),
+ lineNumber(lineNumberIn),
+ id(idIn),
+ path(pathIn)
+ {}
+};
+
+// A list of the breakpoints we know about. Note that this is a slave list;
+// the client maintains the master copy and is responsible for synchronizing
+// with this list. This list is maintained so we can inject breakpoints
+// synchronously.
+std::vector<boost::shared_ptr<Breakpoint> > s_breakpoints;
+
+// Returns the Shiny function that contains the given line, if any.
+// Finds the smallest (innermost) function in the case where more than one
+// expression encloses the line.
+boost::shared_ptr<ShinyFunction> findShinyFunction(std::string filename,
+ int line)
+{
+ boost::shared_ptr<ShinyFunction> bestPsf;
+ int bestSize = INT_MAX;
+ BOOST_FOREACH(boost::shared_ptr<ShinyFunction> psf, s_shinyFunctions)
+ {
+ if (psf->contains(filename, line) &&
+ psf->getSize() < bestSize)
+ {
+ bestSize = psf->getSize();
+ bestPsf = psf;
+ }
+ }
+
+ return bestPsf;
+}
+
+boost::shared_ptr<Breakpoint> breakpointFromJson(json::Object& obj)
+{
+ return boost::make_shared<Breakpoint>(obj["type"].get_int(),
+ obj["line_number"].get_int(),
+ obj["id"].get_int(),
+ obj["path"].get_str());
+
+}
+
+std::vector<int> getShinyBreakpointLines(const ShinyFunction& sf)
+{
+ std::vector<int> lines;
+ BOOST_FOREACH(boost::shared_ptr<Breakpoint> pbp, s_breakpoints)
+ {
+ if (sf.contains(pbp->path, pbp->lineNumber) &&
+ pbp->type == TYPE_TOPLEVEL)
+ lines.push_back(pbp->lineNumber);
+ }
+ return lines;
+}
+
+// Runs a series of pre-flight checks to determine whether we can set a
+// breakpoint at the given location, and, if we can, what kind of breakpoint
+// we should set.
+Error getFunctionState(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object response;
+ std::string functionName, fileName, packageName;
+ int lineNumber = 0;
+ bool inSync = false;
+ Error error = json::readParams(
+ request.params, &functionName, &fileName, &lineNumber);
+ if (error)
+ {
+ return error;
+ }
+
+ // check whether the function is in a package
+ packageName = module_context::packageNameForSourceFile(
+ module_context::resolveAliasedPath(fileName));
+
+ // get the source refs and code for the function
+ SEXP srcRefs = NULL;
+ Protect protect;
+ std::string functionCode;
+ error = r::exec::RFunction(".rs.getFunctionSourceRefs",
+ functionName,
+ fileName,
+ packageName)
+ .call(&srcRefs, &protect);
+ if (!error)
+ {
+ error = r::exec::RFunction(".rs.getFunctionSourceCode",
+ functionName,
+ fileName,
+ packageName)
+ .call(&functionCode);
+ }
+ // compare with the disk if we were able to get the source code;
+ // otherwise, assume it's out of sync
+ if (!error)
+ inSync = !environment::functionDiffersFromSource(srcRefs, functionCode);
+
+ response["sync_state"] = inSync;
+ response["package_name"] = packageName;
+ response["is_package_function"] = packageName.length() > 0;
+ pResponse->setResult(response);
+
+ return Success();
+}
+
+// Sets a breakpoint on a single copy of a function. Invoked several times to
+// look for function copies in alternate environemnts. Returns true if a
+// breakpoint was set; false otherwise.
+bool setBreakpoint(const std::string& functionName,
+ const std::string& fileName,
+ const std::string& packageName,
+ const json::Array& steps)
+{
+ SEXP env = NULL;
+ Protect protect;
+ Error error = r::exec::RFunction(".rs.getEnvironmentOfFunction",
+ functionName,
+ fileName,
+ packageName)
+ .call(&env, &protect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // if we found a function in the requested environment, set the breakpoint
+ if (TYPEOF(env) == ENVSXP)
+ {
+ error = r::exec::RFunction(".rs.setFunctionBreakpoints",
+ functionName,
+ env,
+ steps).call();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ // successfully set a breakpoint
+ return true;
+ }
+
+ // did not find the function in the environment
+ return false;
+}
+
+// Set a breakpoint on a function, potentially on multiple copies:
+// 1. The private copy of the function inside the package under development
+// (even if from another package; it may be an imported copy)
+// 2. The private copy of the function inside its own package (if from a
+// package)
+// 3. The copy of the function on the global search path
+//
+// Note that this is not guaranteed to find ALL copies of the function in ANY
+// environment--at most, three breakpoints are set.
+Error setBreakpoints(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string functionName, fileName, packageName, projPackageName;
+ json::Array steps;
+ bool set = false;
+ Error error = json::readParams(request.params,
+ &functionName,
+ &fileName,
+ &packageName,
+ &steps);
+ if (error)
+ return error;
+
+ // Always search the global namespace (and attached namespaces)
+ // first. Manipulating the source references for the copy in a searchable
+ // namespace has (inexplicable) side effects in package namespaces, but the
+ // reverse is not true (see case 3795).
+ set |= setBreakpoint(functionName, fileName, "", steps);
+
+ // If we're in package development mode, try to set a breakpoint in the
+ // package's namespace.
+ const projects::ProjectContext& projectContext = projects::projectContext();
+ if (projectContext.config().buildType == r_util::kBuildTypePackage)
+ {
+ projPackageName = projectContext.packageInfo().name();
+ set |= setBreakpoint(
+ functionName, fileName, projPackageName, steps);
+ }
+
+ // If a package name was specified, try to set a breakpoint in that package's
+ // namespace, too (unless we did already).
+ if (packageName.length() > 0 &&
+ packageName != projPackageName)
+ {
+ set |= setBreakpoint(functionName, fileName, packageName, steps);
+ }
+
+ // Couldn't find a function to set a breakpoint on--maybe a bad parameter?
+ if (!set)
+ {
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+ }
+
+ return Success();
+}
+
+std::vector<boost::shared_ptr<Breakpoint> >::iterator posOfBreakpointId(int id)
+{
+ std::vector<boost::shared_ptr<Breakpoint> >::iterator psbi;
+ for (psbi = s_breakpoints.begin();
+ psbi != s_breakpoints.end();
+ psbi++)
+ {
+ if ((*psbi)->id == id)
+ break;
+ }
+ return psbi;
+}
+
+// Called by the R garbage collector when a Shiny function is cleaned up;
+// we use this as a trigger to clean up our own references to the function.
+void unregisterShinyFunction(SEXP ptr)
+{
+ // Extract the cached pointer
+ ShinyFunction* psf = static_cast<ShinyFunction*>
+ (r::sexp::getExternalPtrAddr(ptr));
+ if (psf == NULL)
+ return;
+
+ // Look over each Shiny function we know about; if this was a function
+ // we were tracking, release it.
+ for (std::vector<boost::shared_ptr<ShinyFunction> >::iterator psfi =
+ s_shinyFunctions.begin();
+ psfi != s_shinyFunctions.end();
+ psfi++)
+ {
+ if (psfi->get() == psf)
+ {
+ s_shinyFunctions.erase(psfi);
+ break;
+ }
+ }
+ r::sexp::clearExternalPtr(ptr);
+}
+
+// Called by Shiny (through a debug hook set up in tools:rstudio) to register
+// a Shiny function for debugging.
+//
+// 'params' is an ENVSXP expected to contain the following contents:
+// expr - The original expression from which the Shiny function was generated
+// fun - The function generated from that expression
+// name - The name of the variable or field containing the object
+// where - The environment or reference object containing the object
+// label - The name to be shown for the object in the debugger
+//
+// Sets up a data structure and attaches it to the function as an EXTPTRSXP
+// attribute; unregistration is performed when R garbage-collects this pointer.
+void rs_registerShinyFunction(SEXP params)
+{
+ Protect protect;
+ SEXP expr = r::sexp::findVar("expr", params);
+ SEXP fun = r::sexp::findVar("fun", params);
+ SEXP name = r::sexp::findVar("name", params);
+ SEXP where = r::sexp::findVar("where", params);
+
+ // Get the name of the object we're about to attach.
+ std::string objName;
+ Error error = r::sexp::extract(name, &objName);
+ if (error)
+ return;
+
+ boost::shared_ptr<ShinyFunction> psf =
+ boost::make_shared<ShinyFunction>(expr, objName, where);
+
+ // The Shiny server function itself is always the first one registered when
+ // a Shiny session starts. If we had other functions "running", they
+ // likely simply haven't been GC'ed yet--forcefully clean them up.
+ SEXP isShinyServer = r::sexp::getAttrib(fun, "shinyServerFunction");
+ if (isShinyServer != NULL &&
+ TYPEOF(isShinyServer) != NILSXP)
+ {
+ s_shinyFunctions.clear();
+ }
+
+ s_shinyFunctions.push_back(psf);
+
+ // Attach the information we just created to the Shiny function.
+ SEXP sid = r::sexp::create(psf->getId(), &protect);
+ r::sexp::setAttrib(fun, "_rs_shinyDebugPtr",
+ r::sexp::makeExternalPtr(psf.get(),
+ unregisterShinyFunction,
+ &protect));
+ r::sexp::setAttrib(fun, "_rs_shinyDebugId", sid);
+ r::sexp::setAttrib(fun, "_rs_shinyDebugLabel",
+ r::sexp::findVar("label", params));
+
+ r::exec::RFunction(".rs.setShinyFunction", name, where, fun).call();
+
+ // If we found breakpoint lines in this Shiny function, set breakpoints
+ // on it.
+ std::vector<int> lines = getShinyBreakpointLines(*psf);
+ if (lines.size() > 0)
+ {
+ // Copy the function into the Shiny object first
+ r::exec::RFunction(".rs.setShinyBreakpoints", name, where, lines).call();
+ }
+}
+
+// Executes the contents of the given file under the debugger
+SEXP rs_debugSourceFile(SEXP filename, SEXP encoding)
+{
+ // Get the file that was sourced
+ std::string path;
+ Error error = r::sexp::extract(filename, &path);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return R_NilValue;
+ }
+ FilePath filePath = module_context::resolveAliasedPath(path);
+
+ // Find all the lines in the file that have breakpoints
+ std::vector<int> lines;
+ BOOST_FOREACH(boost::shared_ptr<Breakpoint> pbp, s_breakpoints)
+ {
+ if (module_context::resolveAliasedPath(pbp->path) == filePath)
+ {
+ lines.push_back(pbp->lineNumber);
+ }
+ }
+
+ // Execute the contents with breakpoints. Don't log errors here, since it's
+ // acceptable for errors to be raised from the code in the file, and don't
+ // disable the user's error handlers.
+ Protect protect;
+ SEXP lineSEXP = lines.size() > 0 ?
+ r::sexp::create(lines, &protect) :
+ R_NilValue;
+ error = r::exec::RFunction(".rs.executeDebugSource", filename, encoding,
+ lineSEXP)
+ .callUnsafe();
+
+ // Let the client know we're done; this is the client's cue to re-inject
+ // breakpoints.
+ json::Object result;
+ result["path"] = path;
+ result["succeeded"] = error ? false : true;
+ ClientEvent debugSourceCompleted(client_events::kDebugSourceCompleted,
+ result);
+ module_context::enqueClientEvent(debugSourceCompleted);
+
+ return R_NilValue;
+}
+
+Error initBreakpoints()
+{
+ // Register rs_debugSourceFile; called from the console (as debugSource)
+ R_CallMethodDef debugSource;
+ debugSource.name = "rs_debugSourceFile";
+ debugSource.fun = (DL_FUNC)rs_debugSourceFile;
+ debugSource.numArgs = 2;
+ r::routines::addCallMethod(debugSource);
+
+ // Register rs_registerShinyFunction; called from registerShinyDebugHook
+ R_CallMethodDef registerShiny;
+ registerShiny.name = "rs_registerShinyFunction";
+ registerShiny.fun = (DL_FUNC)rs_registerShinyFunction;
+ registerShiny.numArgs = 1;
+ r::routines::addCallMethod(registerShiny);
+
+ // Initializes the set of breakpoints the server knows about by populating
+ // it from client state. This set is used for synchronous breakpoint
+ // injection when a Shiny function is registered or debugSource is run.
+ json::Value breakpointStateValue =
+ r::session::clientState().getProjectPersistent("debug-breakpoints",
+ "debugBreakpointsState");
+ if (!breakpointStateValue.is_null() &&
+ json::isType<core::json::Object>(breakpointStateValue))
+ {
+ json::Object breakpointState = breakpointStateValue.get_obj();
+ json::Array breakpointArray = breakpointState["breakpoints"].get_array();
+ s_breakpoints.clear();
+ BOOST_FOREACH(json::Value bp, breakpointArray)
+ {
+ if (json::isType<core::json::Object>(bp))
+ {
+ s_breakpoints.push_back(breakpointFromJson(bp.get_obj()));
+ }
+ }
+ }
+
+ return Success();
+}
+
+Error updateBreakpoints(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse*)
+{
+ json::Array breakpointArr;
+ bool set = false, arm = false;
+ Error error = json::readParams(request.params, &breakpointArr, &set, &arm);
+ if (error)
+ return error;
+
+ BOOST_FOREACH(json::Value bp, breakpointArr)
+ {
+ boost::shared_ptr<Breakpoint> breakpoint
+ (breakpointFromJson(bp.get_obj()));
+ std::vector<boost::shared_ptr<Breakpoint> >::iterator psbi =
+ posOfBreakpointId(breakpoint->id);
+
+ // Erase anything we already know about this breakpoint
+ if (psbi != s_breakpoints.end())
+ s_breakpoints.erase(psbi);
+
+ // If setting or updating the brekapoint, reintroduce it
+ if (set)
+ s_breakpoints.push_back(breakpoint);
+
+ // Is this breakpoint associated with a running Shiny function? If it is,
+ // and the caller wants the changes armed immediately, reflect them
+ if (arm && breakpoint->type == TYPE_TOPLEVEL) {
+ boost::shared_ptr<ShinyFunction> psf =
+ findShinyFunction(breakpoint->path, breakpoint->lineNumber);
+ if (psf)
+ {
+ // Collect all the breakpoints associated with this function and
+ // update the function's state
+ std::vector<int> lines = getShinyBreakpointLines(*psf);
+ r::exec::RFunction(".rs.setShinyBreakpoints", psf->getName(),
+ psf->getWhere(),
+ lines).call();
+ }
+ }
+ }
+
+ return Success();
+}
+
+Error removeAllBreakpoints(const json::JsonRpcRequest&,
+ json::JsonRpcResponse*)
+{
+ s_breakpoints.clear();
+ return Success();
+}
+
+} // anonymous namespace
+
+bool haveSrcrefAttribute()
+{
+ // check whether this is R 2.14 or greater
+ bool haveSrcref = false;
+ Error error = r::exec::evaluateString("getRversion() >= '2.14.0'", &haveSrcref);
+ if (error)
+ LOG_ERROR(error);
+ return haveSrcref;
+}
+
+bool haveAdvancedStepCommands()
+{
+ bool haveCommands = false;
+ Error error = r::exec::RFunction(".rs.haveAdvancedSteppingCommands")
+ .call(&haveCommands);
+ if (error)
+ LOG_ERROR(error);
+ return haveCommands;
+}
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "get_function_state", getFunctionState))
+ (bind(registerRpcMethod, "set_function_breakpoints", setBreakpoints))
+ (bind(registerRpcMethod, "remove_all_breakpoints", removeAllBreakpoints))
+ (bind(registerRpcMethod, "update_breakpoints", updateBreakpoints))
+ (bind(sourceModuleRFile, "SessionBreakpoints.R"))
+ (bind(initBreakpoints));
+
+ return initBlock.execute();
+}
+
+
+} // namepsace breakpoints
+} // namespace modules
+} // namesapce session
+
+
diff --git a/src/cpp/session/modules/SessionBreakpoints.hpp b/src/cpp/session/modules/SessionBreakpoints.hpp
new file mode 100644
index 0000000..ff6e21b
--- /dev/null
+++ b/src/cpp/session/modules/SessionBreakpoints.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionBreakpoints.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSIONBREAKPOINTS_HPP
+#define SESSIONBREAKPOINTS_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace breakpoints {
+
+core::Error initialize();
+bool haveSrcrefAttribute();
+bool haveAdvancedStepCommands();
+
+} // namespace breakpoints
+} // namespace modules
+} // namespace session
+
+#endif // SESSIONBREAKPOINTS_HPP
diff --git a/src/cpp/session/modules/SessionBuild.R b/src/cpp/session/modules/SessionBuild.R
new file mode 100644
index 0000000..0a706b2
--- /dev/null
+++ b/src/cpp/session/modules/SessionBuild.R
@@ -0,0 +1,26 @@
+#
+# SessionBuild.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+setHook("sourceCpp.onBuild", function(file, fromCode, showOutput) {
+ .Call("rs_sourceCppOnBuild", file, fromCode, showOutput)
+})
+
+setHook("sourceCpp.onBuildComplete", function(succeeded, output) {
+ .Call("rs_sourceCppOnBuildComplete", succeeded, output)
+})
+
+
+
+
diff --git a/src/cpp/session/modules/SessionCodeSearch.cpp b/src/cpp/session/modules/SessionCodeSearch.cpp
new file mode 100644
index 0000000..1cd3337
--- /dev/null
+++ b/src/cpp/session/modules/SessionCodeSearch.cpp
@@ -0,0 +1,1444 @@
+/*
+ * SessionCodeSearch.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionCodeSearch.hpp"
+
+#include <iostream>
+#include <vector>
+#include <set>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/r_util/RSourceIndex.hpp>
+
+#include <core/system/FileChangeEvent.hpp>
+#include <core/system/FileMonitor.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionSource.hpp"
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace code_search {
+
+namespace {
+
+bool isGlobalFunctionNamed(const r_util::RSourceItem& sourceItem,
+ const std::string& name)
+{
+ return sourceItem.braceLevel() == 0 &&
+ (sourceItem.type() == r_util::RSourceItem::Function ||
+ sourceItem.type() == r_util::RSourceItem::Method) &&
+ sourceItem.name() == name;
+}
+
+boost::regex regexFromTerm(const std::string& term)
+{
+ // create wildcard pattern if the search has a '*'
+ bool hasWildcard = term.find('*') != std::string::npos;
+ boost::regex pattern;
+ if (hasWildcard)
+ pattern = regex_utils::wildcardPatternToRegex(term);
+ return pattern;
+}
+
+// return if we are past max results
+bool enforceMaxResults(std::size_t maxResults,
+ json::Array* pNames,
+ json::Array* pPaths,
+ bool* pMoreAvailable)
+{
+ if (pNames->size() > maxResults)
+ {
+ *pMoreAvailable = true;
+ pNames->resize(maxResults);
+ pPaths->resize(maxResults);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+class SourceFileIndex : boost::noncopyable
+{
+public:
+ SourceFileIndex()
+ : indexing_(false)
+ {
+ }
+
+ virtual ~SourceFileIndex()
+ {
+ }
+
+ // COPYING: prohibited
+
+ template <typename ForwardIterator>
+ void enqueFiles(ForwardIterator begin, ForwardIterator end)
+ {
+ // add all source files to the indexing queue
+ using namespace core::system;
+ for ( ; begin != end; ++begin)
+ {
+ if (isSourceFile(*begin))
+ {
+ FileChangeEvent addEvent(FileChangeEvent::FileAdded, *begin);
+ indexingQueue_.push(addEvent);
+ }
+ }
+
+ // schedule indexing if necessary. perform up to 200ms of work
+ // immediately and then continue in periodic 20ms chunks until
+ // we are completed.
+ if (!indexingQueue_.empty() && !indexing_)
+ {
+ indexing_ = true;
+
+ module_context::scheduleIncrementalWork(
+ boost::posix_time::milliseconds(200),
+ boost::posix_time::milliseconds(20),
+ boost::bind(&SourceFileIndex::dequeAndIndex, this),
+ false /* allow indexing even when non-idle */);
+ }
+ }
+
+ void enqueFileChange(const core::system::FileChangeEvent& event)
+ {
+ // screen out files which aren't source files
+ if (!isSourceFile(event.fileInfo()))
+ return;
+
+ // add to the queue
+ indexingQueue_.push(event);
+
+ // schedule indexing if necessary. don't index anything immediately
+ // (this is to defend against large numbers of files being enqued
+ // at once and typing up the main thread). rather, schedule indexing
+ // to occur during idle time in 20ms chunks
+ if (!indexing_)
+ {
+ indexing_ = true;
+
+ module_context::scheduleIncrementalWork(
+ boost::posix_time::milliseconds(20),
+ boost::bind(&SourceFileIndex::dequeAndIndex, this),
+ false /* allow indexing even when non-idle */);
+ }
+ }
+
+ bool findGlobalFunction(const std::string& functionName,
+ const std::set<std::string>& excludeContexts,
+ r_util::RSourceItem* pFunctionItem)
+ {
+ std::vector<r_util::RSourceItem> sourceItems;
+ BOOST_FOREACH(const Entry& entry, entries_)
+ {
+ // bail if there is no index
+ if (!entry.hasIndex())
+ continue;
+
+ // bail if this is an exluded context
+ if (excludeContexts.find(entry.pIndex->context()) !=
+ excludeContexts.end())
+ {
+ continue;
+ }
+
+ // scan the next index
+ sourceItems.clear();
+ entry.pIndex->search(
+ boost::bind(isGlobalFunctionNamed, _1, functionName),
+ std::back_inserter(sourceItems));
+
+ // return if we got a hit
+ if (sourceItems.size() > 0)
+ {
+ *pFunctionItem = sourceItems[0];
+ return true;
+ }
+ }
+
+ // none found
+ return false;
+ }
+
+ void searchSource(const std::string& term,
+ std::size_t maxResults,
+ bool prefixOnly,
+ const std::set<std::string>& excludeContexts,
+ std::vector<r_util::RSourceItem>* pItems)
+ {
+ BOOST_FOREACH(const Entry& entry, entries_)
+ {
+ // skip if it has no index
+ if (!entry.hasIndex())
+ continue;
+
+ // bail if this is an exluded context
+ if (excludeContexts.find(entry.pIndex->context()) !=
+ excludeContexts.end())
+ {
+ continue;
+ }
+
+ // scan the next index
+ entry.pIndex->search(term,
+ prefixOnly,
+ false,
+ std::back_inserter(*pItems));
+
+ // return if we are past maxResults
+ if (pItems->size() >= maxResults)
+ {
+ pItems->resize(maxResults);
+ return;
+ }
+ }
+ }
+
+ void searchFiles(const std::string& term,
+ std::size_t maxResults,
+ bool prefixOnly,
+ json::Array* pNames,
+ json::Array* pPaths,
+ bool* pMoreAvailable)
+ {
+ // default to no more available
+ *pMoreAvailable = false;
+
+ // create wildcard pattern if the search has a '*'
+ boost::regex pattern = regexFromTerm(term);
+
+ // iterate over the files
+ BOOST_FOREACH(const Entry& entry, entries_)
+ {
+ // get file and name
+ FilePath filePath(entry.fileInfo.absolutePath());
+ std::string name = filePath.filename();
+
+ // compare for match (wildcard or standard)
+ bool matches = false;
+ if (!pattern.empty())
+ {
+ matches = regex_utils::textMatches(name,
+ pattern,
+ prefixOnly,
+ false);
+ }
+ else
+ {
+ if (prefixOnly)
+ matches = boost::algorithm::istarts_with(name, term);
+ else
+ matches = boost::algorithm::icontains(name, term);
+ }
+
+ // add the file if we found a match
+ if (matches)
+ {
+ // name and aliased path
+ pNames->push_back(filePath.filename());
+ pPaths->push_back(module_context::createAliasedPath(filePath));
+
+ // return if we are past max results
+ if (enforceMaxResults(maxResults, pNames, pPaths, pMoreAvailable))
+ return;
+ }
+ }
+ }
+
+ void clear()
+ {
+ indexing_ = false;
+ indexingQueue_ = std::queue<core::system::FileChangeEvent>();
+ entries_.clear();
+ }
+
+private:
+
+ // index entries we are managing
+ struct Entry
+ {
+ explicit Entry(const FileInfo& fileInfo)
+ : fileInfo(fileInfo)
+ {
+ }
+
+ Entry(const FileInfo& fileInfo,
+ boost::shared_ptr<core::r_util::RSourceIndex> pIndex)
+ : fileInfo(fileInfo), pIndex(pIndex)
+ {
+ }
+
+ FileInfo fileInfo;
+
+ boost::shared_ptr<core::r_util::RSourceIndex> pIndex;
+
+ bool hasIndex() const { return pIndex.get() != NULL; }
+
+ bool operator < (const Entry& other) const
+ {
+ return core::fileInfoPathLessThan(fileInfo, other.fileInfo);
+ }
+ };
+
+private:
+
+ bool dequeAndIndex()
+ {
+ using namespace core::system;
+
+ if (!indexingQueue_.empty())
+ {
+ // remove the event from the queue
+ FileChangeEvent event = indexingQueue_.front();
+ indexingQueue_.pop();
+
+ // process the change
+ const FileInfo& fileInfo = event.fileInfo();
+ switch(event.type())
+ {
+ case FileChangeEvent::FileAdded:
+ case FileChangeEvent::FileModified:
+ {
+ updateIndexEntry(fileInfo);
+ break;
+ }
+
+ case FileChangeEvent::FileRemoved:
+ {
+ removeIndexEntry(fileInfo);
+ break;
+ }
+
+ case FileChangeEvent::None:
+ break;
+ }
+ }
+
+ // return status
+ indexing_ = !indexingQueue_.empty();
+ return indexing_;
+ }
+
+ void updateIndexEntry(const FileInfo& fileInfo)
+ {
+ // index the source if necessary
+ boost::shared_ptr<r_util::RSourceIndex> pIndex;
+ if (isIndexableSourceFile(fileInfo))
+ {
+ // read the file
+ FilePath filePath(fileInfo.absolutePath());
+ std::string code;
+ Error error = module_context::readAndDecodeFile(
+ filePath,
+ projects::projectContext().defaultEncoding(),
+ true,
+ &code);
+ if (error)
+ {
+ // log if not path not found error (this can happen if the
+ // file was removed after entering the indexing queue)
+ if (!core::isPathNotFoundError(error))
+ {
+ error.addProperty("src-file", filePath.absolutePath());
+ LOG_ERROR(error);
+ }
+ return;
+ }
+
+ // add index entry
+ std::string context = module_context::createAliasedPath(filePath);
+ pIndex.reset(new r_util::RSourceIndex(context, code));
+ }
+
+ // attempt to add the entry
+ Entry entry(fileInfo, pIndex);
+ std::pair<std::set<Entry>::iterator,bool> result = entries_.insert(entry);
+
+ // insert failed, remove then re-add
+ if (result.second == false)
+ {
+ // was the first item, erase and re-insert without a hint
+ if (result.first == entries_.begin())
+ {
+ entries_.erase(result.first);
+ entries_.insert(entry);
+ }
+ // can derive a valid hint
+ else
+ {
+ // derive hint iterator
+ std::set<Entry>::iterator hintIter = result.first;
+ hintIter--;
+
+ // erase and re-insert with hint
+ entries_.erase(result.first);
+ entries_.insert(hintIter, entry);
+ }
+ }
+ }
+
+ void removeIndexEntry(const FileInfo& fileInfo)
+ {
+ // create a fake entry with a null source index to pass to find
+ Entry entry(fileInfo, boost::shared_ptr<r_util::RSourceIndex>());
+
+ // do the find (will use Entry::operator< for equivilance test)
+ std::set<Entry>::iterator it = entries_.find(entry);
+ if (it != entries_.end())
+ entries_.erase(it);
+ }
+
+ static bool isSourceFile(const FileInfo& fileInfo)
+ {
+ FilePath filePath(fileInfo.absolutePath());
+
+ // if we are in a package project then screen our src- files
+ if (projects::projectContext().hasProject())
+ {
+ if (projects::projectContext().config().buildType ==
+ r_util::kBuildTypePackage)
+ {
+ FilePath pkgPath = projects::projectContext().buildTargetPath();
+ std::string pkgRelative = filePath.relativePath(pkgPath);
+ if (boost::algorithm::starts_with(pkgRelative, "src-"))
+ return false;
+ }
+ }
+
+ // filter files by name and extension
+ std::string ext = filePath.extensionLowerCase();
+ std::string filename = filePath.filename();
+ return !filePath.isDirectory() &&
+ (ext == ".r" || ext == ".rnw" ||
+ ext == ".rmd" || ext == ".rmarkdown" ||
+ ext == ".rhtml" || ext == ".rd" ||
+ ext == ".h" || ext == ".hpp" ||
+ ext == ".c" || ext == ".cpp" ||
+ filename == "DESCRIPTION" ||
+ filename == "NAMESPACE" ||
+ filename == "README" ||
+ filename == "NEWS" ||
+ filename == "Makefile" ||
+ filePath.hasTextMimeType());
+ }
+
+ static bool isIndexableSourceFile(const FileInfo& fileInfo)
+ {
+ FilePath filePath(fileInfo.absolutePath());
+ return !filePath.isDirectory() &&
+ filePath.extensionLowerCase() == ".r";
+ }
+
+private:
+ // index entries
+ std::set<Entry> entries_;
+
+ // indexing queue
+ bool indexing_;
+ std::queue<core::system::FileChangeEvent> indexingQueue_;
+};
+
+// global source file index
+SourceFileIndex s_projectIndex;
+
+
+// if we have a project active then restrict results to the project
+bool sourceDatabaseFilter(const r_util::RSourceIndex& index)
+{
+ if (projects::projectContext().hasProject())
+ {
+ // get file path
+ FilePath docPath = module_context::resolveAliasedPath(index.context());
+ return docPath.isWithin(projects::projectContext().directory());
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool findGlobalFunctionInSourceDatabase(
+ const std::string& functionName,
+ r_util::RSourceItem* pFunctionItem,
+ std::set<std::string>* pContextsSearched)
+{
+ // get all of the source indexes
+ std::vector<boost::shared_ptr<r_util::RSourceIndex> > rIndexes =
+ modules::source::rIndexes();
+
+ std::vector<r_util::RSourceItem> sourceItems;
+ BOOST_FOREACH(boost::shared_ptr<r_util::RSourceIndex>& pIndex, rIndexes)
+ {
+ // apply the filter
+ if (!sourceDatabaseFilter(*pIndex))
+ continue;
+
+ // record this context
+ pContextsSearched->insert(pIndex->context());
+
+ // scan the next index
+ sourceItems.clear();
+ pIndex->search(
+ boost::bind(isGlobalFunctionNamed, _1, functionName),
+ std::back_inserter(sourceItems));
+
+ // return if we got a hit
+ if (sourceItems.size() > 0)
+ {
+ *pFunctionItem = sourceItems[0];
+ return true;
+ }
+ }
+
+ // none found
+ return false;
+}
+
+void searchSourceDatabase(const std::string& term,
+ std::size_t maxResults,
+ bool prefixOnly,
+ std::vector<r_util::RSourceItem>* pItems,
+ std::set<std::string>* pContextsSearched)
+{
+ // get all of the source indexes
+ std::vector<boost::shared_ptr<r_util::RSourceIndex> > rIndexes =
+ modules::source::rIndexes();
+
+ BOOST_FOREACH(boost::shared_ptr<r_util::RSourceIndex>& pIndex, rIndexes)
+ {
+ // apply the filter
+ if (!sourceDatabaseFilter(*pIndex))
+ continue;
+
+ // record this context
+ pContextsSearched->insert(pIndex->context());
+
+ // scan the source index
+ pIndex->search(term,
+ prefixOnly,
+ false,
+ std::back_inserter(*pItems));
+
+ // return if we are past maxResults
+ if (pItems->size() >= maxResults)
+ {
+ pItems->resize(maxResults);
+ return;
+ }
+ }
+}
+
+void searchSource(const std::string& term,
+ std::size_t maxResults,
+ bool prefixOnly,
+ std::vector<r_util::RSourceItem>* pItems,
+ bool* pMoreAvailable)
+{
+ // default to no more available
+ *pMoreAvailable = false;
+
+ // first search the source database
+ std::set<std::string> srcDBContexts;
+ searchSourceDatabase(term, maxResults, prefixOnly, pItems, &srcDBContexts);
+
+ // we are done if we had >= maxResults
+ if (pItems->size() > maxResults)
+ {
+ *pMoreAvailable = true;
+ pItems->resize(maxResults);
+ return;
+ }
+
+ // compute project max results based on existing results
+ std::size_t maxProjResults = maxResults - pItems->size();
+
+ // now search the project (excluding contexts already searched in the source db)
+ std::vector<r_util::RSourceItem> projItems;
+ s_projectIndex.searchSource(term,
+ maxProjResults,
+ prefixOnly,
+ srcDBContexts,
+ &projItems);
+
+ // add project items to the list
+ BOOST_FOREACH(const r_util::RSourceItem& sourceItem, projItems)
+ {
+ // add the item
+ pItems->push_back(sourceItem);
+
+ // bail if we've hit the max
+ if (pItems->size() > maxResults)
+ {
+ *pMoreAvailable = true;
+ pItems->resize(maxResults);
+ break;
+ }
+ }
+}
+
+void searchSourceDatabaseFiles(const std::string& term,
+ std::size_t maxResults,
+ json::Array* pNames,
+ json::Array* pPaths,
+ bool* pMoreAvailable)
+{
+ // default to no more available
+ *pMoreAvailable = false;
+
+ // create wildcard pattern if the search has a '*'
+ boost::regex pattern = regexFromTerm(term);
+
+ // get all of the source indexes
+ std::vector<boost::shared_ptr<r_util::RSourceIndex> > rIndexes =
+ modules::source::rIndexes();
+
+ BOOST_FOREACH(boost::shared_ptr<r_util::RSourceIndex>& pIndex, rIndexes)
+ {
+ // bail if there is no path
+ std::string context = pIndex->context();
+ if (context.empty())
+ continue;
+
+ // get filename
+ FilePath filePath = module_context::resolveAliasedPath(context);
+ std::string filename = filePath.filename();
+
+ // compare for match (wildcard or standard)
+ bool matches = false;
+ if (!pattern.empty())
+ {
+ matches = regex_utils::textMatches(filename,
+ pattern,
+ true,
+ false);
+ }
+ else
+ {
+ matches = boost::algorithm::istarts_with(filename, term);
+ }
+
+ // add the file if we found a match
+ if (matches)
+ {
+ // name and aliased path
+ pNames->push_back(filename);
+ pPaths->push_back(pIndex->context());
+
+ // return if we are past max results
+ if (enforceMaxResults(maxResults, pNames, pPaths, pMoreAvailable))
+ return;
+ }
+
+ }
+}
+
+void searchFiles(const std::string& term,
+ std::size_t maxResults,
+ json::Array* pNames,
+ json::Array* pPaths,
+ bool* pMoreAvailable)
+{
+ // if we have a file monitor then search the project index
+ if (session::projects::projectContext().hasFileMonitor())
+ {
+ s_projectIndex.searchFiles(term,
+ maxResults,
+ true,
+ pNames,
+ pPaths,
+ pMoreAvailable);
+ }
+ else
+ {
+ searchSourceDatabaseFiles(term,
+ maxResults,
+ pNames,
+ pPaths,
+ pMoreAvailable);
+ }
+}
+
+
+template <typename TValue, typename TFunc>
+json::Array toJsonArray(
+ const std::vector<r_util::RSourceItem> &items,
+ TFunc memberFunc)
+{
+ json::Array col;
+ std::transform(items.begin(),
+ items.end(),
+ std::back_inserter(col),
+ boost::bind(json::toJsonValue<TValue>,
+ boost::bind(memberFunc, _1)));
+ return col;
+}
+
+
+json::Array signatureToJson(const std::vector<r_util::RS4MethodParam>& sig)
+{
+ json::Array sigJson;
+ BOOST_FOREACH(const r_util::RS4MethodParam& param, sig)
+ {
+ json::Object paramJson;
+ paramJson["name"] = param.name();
+ paramJson["type"] = param.type();
+ sigJson.push_back(paramJson);
+ }
+ return sigJson;
+}
+
+json::Array signaturesToJsonArray(
+ const std::vector<r_util::RSourceItem> &items)
+{
+ json::Array sigCol;
+ std::transform(items.begin(),
+ items.end(),
+ std::back_inserter(sigCol),
+ boost::bind(
+ signatureToJson,
+ boost::bind(&r_util::RSourceItem::signature, _1)));
+ return sigCol;
+}
+
+bool compareItems(const r_util::RSourceItem& i1, const r_util::RSourceItem& i2)
+{
+ return i1.name() < i2.name();
+}
+
+
+Error searchCode(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get params
+ std::string term;
+ int maxResultsInt = 20;
+ Error error = json::readParams(request.params, &term, &maxResultsInt);
+ if (error)
+ return error;
+ std::size_t maxResults = safe_convert::numberTo<std::size_t>(maxResultsInt,
+ 20);
+
+ // object to return
+ json::Object result;
+
+ // search files
+ json::Array names;
+ json::Array paths;
+ bool moreFilesAvailable = false;
+ searchFiles(term, maxResults, &names, &paths, &moreFilesAvailable);
+ json::Object files;
+ files["filename"] = names;
+ files["path"] = paths;
+ result["file_items"] = files;
+
+ // search source (sort results by name)
+ std::vector<r_util::RSourceItem> items;
+ bool moreSourceItemsAvailable = false;
+ searchSource(term, maxResults, true, &items, &moreSourceItemsAvailable);
+ std::sort(items.begin(), items.end(), compareItems);
+
+ // see if we need to do src truncation
+ bool truncated = false;
+ if ( (names.size() + items.size()) > maxResults )
+ {
+ // truncate source items
+ std::size_t srcItems = maxResults - names.size();
+ items.resize(srcItems);
+ truncated = true;
+ }
+
+ // return rpc array list (wire efficiency)
+ json::Object src;
+ src["type"] = toJsonArray<int>(items, &r_util::RSourceItem::type);
+ src["name"] = toJsonArray<std::string>(items, &r_util::RSourceItem::name);
+ src["signature"] = signaturesToJsonArray(items);
+ src["context"] = toJsonArray<std::string>(items, &r_util::RSourceItem::context);
+ src["line"] = toJsonArray<int>(items, &r_util::RSourceItem::line);
+ src["column"] = toJsonArray<int>(items, &r_util::RSourceItem::column);
+ result["source_items"] = src;
+
+ // set more available bit
+ result["more_available"] =
+ moreFilesAvailable || moreSourceItemsAvailable || truncated;
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+
+bool namespaceIsPackage(const std::string& namespaceName,
+ std::string* pPackage)
+{
+ if (namespaceName.empty())
+ return false;
+
+ std::string pkgPrefix("package:");
+ std::string::size_type pkgPrefixPos = namespaceName.find(pkgPrefix);
+ if (pkgPrefixPos == 0 && namespaceName.length() > pkgPrefix.length())
+ {
+ *pPackage = namespaceName.substr(pkgPrefix.length());
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool findFunction(const std::string& name,
+ const std::string& fromWhere,
+ std::string* pNamespaceName)
+{
+ // if fromWhere is a package name then we should first directly
+ // search that package (so that we can find hidden functions)
+ Error error;
+ std::string pkgName;
+ pNamespaceName->clear();
+ if (namespaceIsPackage(fromWhere, &pkgName))
+ {
+ r::sexp::Protect rProtect;
+ SEXP functionSEXP = R_NilValue;
+ r::exec::RFunction func(".rs.getPackageFunction", name, pkgName);
+ error = func.call(&functionSEXP, &rProtect);
+ if (!error && !r::sexp::isNull(functionSEXP))
+ *pNamespaceName = fromWhere;
+ }
+
+ // if we haven't found it yet
+ if (pNamespaceName->empty())
+ {
+ r::exec::RFunction func(".rs.findFunctionNamespace",
+ name,
+ fromWhere);
+ error = func.call(pNamespaceName);
+ }
+
+ // log error and return appropriate status
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ else if (pNamespaceName->empty())
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+
+void getFunctionSource(SEXP functionSEXP,
+ std::vector<std::string>* pLines,
+ bool* pFromSrcAttrib)
+{
+ // check if the function has a "srcref" attribute
+ *pFromSrcAttrib = false;
+ r::exec::RFunction getSrcRefFunc(".rs.functionHasSrcRef", functionSEXP);
+ Error error = getSrcRefFunc.call(pFromSrcAttrib);
+ if (error)
+ LOG_ERROR(error);
+
+ // deparse
+ r::exec::RFunction deparseFunc(".rs.deparseFunction");
+ deparseFunc.addParam(functionSEXP);
+ deparseFunc.addParam(*pFromSrcAttrib);
+ error = deparseFunc.call(pLines);
+ if (error)
+ LOG_ERROR(error);
+}
+
+void getFunctionS3Methods(const std::string& methodName, json::Array* pMethods)
+{
+ // lookup S3 methods for that base name
+ std::vector<std::string> methods;
+ r::exec::RFunction rfunc(".rs.getS3MethodsForFunction", methodName);
+ Error error = rfunc.call(&methods);
+ if (error)
+ LOG_ERROR(error);
+
+ // provide them to the caller
+ std::transform(methods.begin(),
+ methods.end(),
+ std::back_inserter(*pMethods),
+ boost::bind(json::toJsonString, _1));
+
+}
+
+
+std::string trimToken(const std::string& token)
+{
+ return boost::algorithm::trim_copy(token);
+}
+
+class FunctionInfo
+{
+public:
+ explicit FunctionInfo(const std::string& name)
+ : name_(name)
+ {
+ boost::regex pattern("^([^{]+)\\{([^}]+)\\}$");
+ boost::smatch match;
+ if (boost::regex_search(name_, match, pattern))
+ {
+ // read method name
+ methodName_ = match[1];
+ boost::algorithm::trim(methodName_);
+
+ // read , separated fields
+ std::string types = match[2];
+ using namespace boost ;
+ char_separator<char> comma(",");
+ tokenizer<char_separator<char> > typeTokens(types, comma);
+ std::transform(typeTokens.begin(),
+ typeTokens.end(),
+ std::back_inserter(paramTypes_),
+ trimToken);
+ }
+ }
+
+ bool isS4Method() const { return !methodName_.empty(); }
+
+ const std::string& name() const { return name_; }
+ const std::string& methodName() const { return methodName_; }
+ const std::vector<std::string>& paramTypes() const { return paramTypes_; }
+
+private:
+ std::string name_;
+ std::string methodName_;
+ std::vector<std::string> paramTypes_;
+};
+
+
+void getFunctionS4Methods(const std::string& methodName, json::Array* pMethods)
+{
+ // check if the function isGeneric
+ bool generic = false;
+ if (methodName != "class")
+ {
+ Error error = r::exec::RFunction("methods:::isGeneric", methodName).call(
+ &generic);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ if (generic)
+ {
+ std::vector<std::string> methods;
+ r::exec::RFunction rFunc(".rs.getS4MethodsForFunction", methodName);
+ Error error = rFunc.call(&methods);
+ if (error)
+ LOG_ERROR(error);
+
+ // provide them to the caller
+ std::transform(methods.begin(),
+ methods.end(),
+ std::back_inserter(*pMethods),
+ boost::bind(json::toJsonString, _1));
+ }
+}
+
+
+json::Object createErrorFunctionDefinition(const std::string& name,
+ const std::string& namespaceName)
+{
+ json::Object funDef;
+ funDef["name"] = name;
+ funDef["namespace"] = namespaceName;
+ funDef["methods"] = json::Array();
+ boost::format fmt("\n# ERROR: Defintion of function '%1%' not found\n"
+ "# in namespace '%2%'");
+ funDef["code"] = boost::str(fmt % name % namespaceName);
+ funDef["from_src_attrib"] = false;
+
+ return funDef;
+}
+
+std::string baseMethodName(const std::string& name)
+{
+ // strip type qualifiers for S4 methods
+ FunctionInfo functionInfo(name);
+ if (functionInfo.isS4Method())
+ {
+ return functionInfo.methodName();
+ }
+ // strip content after the '.' for S3 methods
+ else
+ {
+ // first find the base name of the function
+ std::string baseName = name;
+ std::size_t periodLoc = baseName.find('.');
+ if (periodLoc != std::string::npos && periodLoc > 0)
+ baseName = baseName.substr(0, periodLoc);
+ return baseName;
+ }
+}
+
+json::Object createFunctionDefinition(const std::string& name,
+ const std::string& namespaceName,
+ SEXP functionSEXP)
+{
+ // basic metadata
+ json::Object funDef;
+ funDef["name"] = name;
+ funDef["namespace"] = namespaceName;
+
+ // function source code
+ bool fromSrcAttrib = false;
+ std::vector<std::string> lines;
+ getFunctionSource(functionSEXP, &lines, &fromSrcAttrib);
+
+ // did we get some lines back?
+ if (lines.size() > 0)
+ {
+ // append the lines to the code and set it
+ std::string code;
+ BOOST_FOREACH(const std::string& line, lines)
+ {
+ code.append(line);
+ code.append("\n");
+ }
+ funDef["code"] = code;
+ funDef["from_src_attrib"] = fromSrcAttrib;
+
+ // methods
+ std::string methodName = baseMethodName(name);
+ json::Array methodsJson;
+ getFunctionS4Methods(methodName, &methodsJson);
+ getFunctionS3Methods(methodName, &methodsJson);
+ funDef["methods"] = methodsJson;
+
+ return funDef;
+ }
+ else
+ {
+ return createErrorFunctionDefinition(name, namespaceName);
+ }
+}
+
+Error getS4Method(const FunctionInfo& functionInfo,
+ std::string* pNamespaceName,
+ SEXP* pFunctionSEXP,
+ r::sexp::Protect* pProtect)
+{
+ // get the method
+ r::exec::RFunction rFunc("methods:::getMethod");
+ rFunc.addParam(functionInfo.methodName());
+ rFunc.addParam(functionInfo.paramTypes());
+ Error error = rFunc.call(pFunctionSEXP, pProtect);
+ if (error)
+ return error;
+
+ // get the namespace
+ r::exec::RFunction rNsFunc(".rs.getS4MethodNamespaceName", *pFunctionSEXP);
+ return rNsFunc.call(pNamespaceName);
+}
+
+json::Object createFunctionDefinition(const std::string& name,
+ const std::string& namespaceName)
+{
+ // stuff we are trying to find
+ std::string functionNamespace = namespaceName;
+ r::sexp::Protect rProtect;
+ SEXP functionSEXP = R_NilValue;
+ Error error;
+
+ // what type of function are we looking for?
+ FunctionInfo functionInfo(name);
+ if (functionInfo.isS4Method())
+ {
+ // check for S4 method definition
+ error = getS4Method(functionInfo,
+ &functionNamespace,
+ &functionSEXP,
+ &rProtect);
+ }
+ else
+ {
+ // get the function -- if it within a package namespace then do special
+ // handling to make sure we can find hidden functions as well
+ std::string pkgName;
+ if (namespaceIsPackage(functionNamespace, &pkgName))
+ {
+ r::exec::RFunction getFunc(".rs.getPackageFunction", name, pkgName);
+ error = getFunc.call(&functionSEXP, &rProtect);
+ }
+ else
+ {
+ r::exec::RFunction getFunc(".rs.getFunction", name, functionNamespace);
+ error = getFunc.call(&functionSEXP, &rProtect);
+ }
+ }
+
+ // check find status and return appropriate definiton
+ if (!error)
+ {
+ if (!r::sexp::isNull(functionSEXP))
+ return createFunctionDefinition(name, functionNamespace, functionSEXP);
+ else
+ return createErrorFunctionDefinition(name, functionNamespace);
+ }
+ else
+ {
+ LOG_ERROR(error);
+ return createErrorFunctionDefinition(name, functionNamespace);
+ }
+
+}
+
+json::Value createS3MethodDefinition(const std::string& name)
+{
+ // first call getAnywhere to see if we can find a definition
+ r::sexp::Protect rProtect;
+ SEXP getAnywhereSEXP;
+ r::exec::RFunction getAnywhereFunc("utils:::getAnywhere", name);
+ Error error = getAnywhereFunc.call(&getAnywhereSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Value();
+ }
+
+ // access the "where" element
+ std::vector<std::string> whereList;
+ error = r::sexp::getNamedListElement(getAnywhereSEXP, "where", &whereList);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Value();
+ }
+
+ // find an element beginning with "package:" or "namespace:"
+ std::string packagePrefix = "package:";
+ std::string namespacePrefix = "namespace:";
+ std::string namespaceName;
+ BOOST_FOREACH(const std::string& where, whereList)
+ {
+ if (boost::algorithm::starts_with(where, packagePrefix))
+ {
+ namespaceName = where;
+ break;
+ }
+
+ if (boost::algorithm::starts_with(where, namespacePrefix) &&
+ (where.length() > namespacePrefix.length()))
+ {
+ namespaceName = "package:" +
+ where.substr(namespacePrefix.length());
+ break;
+ }
+ }
+
+ // if we found one then go through standard route, else return null
+ if (!namespaceName.empty())
+ return createFunctionDefinition(name, namespaceName);
+ else
+ return json::Value();
+}
+
+
+json::Value createS4MethodDefinition(const FunctionInfo& functionInfo)
+{
+ // lookup the method
+ std::string functionNamespace ;
+ r::sexp::Protect rProtect;
+ SEXP functionSEXP = R_NilValue;
+ Error error = getS4Method(functionInfo,
+ &functionNamespace,
+ &functionSEXP,
+ &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Value();
+ }
+ else
+ {
+ return createFunctionDefinition(functionInfo.name(),
+ functionNamespace,
+ functionSEXP);
+ }
+}
+
+
+struct FunctionToken
+{
+ std::string package;
+ std::string name;
+};
+
+
+json::Object createFunctionDefinition(const FunctionToken& token)
+{
+ return createFunctionDefinition(token.name, "package:" + token.package);
+}
+
+Error guessFunctionToken(const std::string& line,
+ int pos,
+ FunctionToken* pToken)
+{
+ // call into R to determine the token
+ std::string token;
+ Error error = r::exec::RFunction(".rs.guessToken", line, pos).call(&token);
+ if (error)
+ return error;
+
+ // see if it has a namespace qualifier
+ boost::regex pattern("^([^:]+):{2,3}([^:]+)$");
+ boost::smatch match;
+ if (boost::regex_search(token, match, pattern))
+ {
+ pToken->package = match[1];
+ pToken->name = match[2];
+ }
+ else
+ {
+ pToken->name = token;
+ }
+
+ return Success();
+}
+
+Error getFunctionDefinition(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string line;
+ int pos;
+ Error error = json::readParams(request.params, &line, &pos);
+ if (error)
+ return error;
+
+ // call into R to determine the token
+ FunctionToken token;
+ error = guessFunctionToken(line, pos, &token);
+ if (error)
+ return error;
+
+ // default return value is null function name (indicating no results)
+ json::Object defJson;
+ defJson["function_name"] = json::Value();
+
+ // if there was a package then we go straight to the search path
+ if (!token.package.empty())
+ {
+ defJson["function_name"] = token.name;
+ defJson["search_path_definition"] = createFunctionDefinition(token);
+ }
+
+ // if we got a name token then search the code
+ else if (!token.name.empty())
+ {
+ // discovered a token so we have at least a function name to return
+ defJson["function_name"] = token.name;
+
+ // find in source database then in project index
+ std::set<std::string> contexts;
+ r_util::RSourceItem sourceItem;
+ bool found =
+ findGlobalFunctionInSourceDatabase(token.name, &sourceItem, &contexts) ||
+ s_projectIndex.findGlobalFunction(token.name, contexts, &sourceItem);
+
+ // found the file
+ if (found)
+ {
+ // return full path to file
+ FilePath srcFilePath = module_context::resolveAliasedPath(
+ sourceItem.context());
+ defJson["file"] = module_context::createFileSystemItem(srcFilePath);
+
+ // return location in file
+ json::Object posJson;
+ posJson["line"] = sourceItem.line();
+ posJson["column"] = sourceItem.column();
+ defJson["position"] = posJson;
+ }
+ // didn't find the file, check the search path
+ else
+ {
+ // find the function
+ std::string namespaceName;
+ if (findFunction(token.name, "", &namespaceName))
+ {
+ defJson["search_path_definition"] =
+ createFunctionDefinition(token.name,
+ namespaceName);
+ }
+ }
+ }
+
+ // set result
+ pResponse->setResult(defJson);
+
+ return Success();
+}
+
+
+Error getSearchPathFunctionDefinition(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string name;
+ std::string namespaceName;
+ Error error = json::readParams(request.params, &name, &namespaceName);
+ if (error)
+ return error;
+
+ // return result
+ pResponse->setResult(createFunctionDefinition(name, namespaceName));
+ return Success();
+}
+
+Error getMethodDefinition(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string name;
+ Error error = json::readParam(request.params, 0, &name);
+ if (error)
+ return error;
+
+ // return result (distinguish between S3 and S4 methods)
+ FunctionInfo functionInfo(name);
+ if (functionInfo.isS4Method())
+ pResponse->setResult(createS4MethodDefinition(functionInfo));
+ else
+ pResponse->setResult(createS3MethodDefinition(name));
+
+ return Success();
+}
+
+Error findFunctionInSearchPath(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string line;
+ int pos;
+ json::Value fromWhereJSON;
+ Error error = json::readParams(request.params, &line, &pos, &fromWhereJSON);
+ if (error)
+ return error;
+
+ // handle fromWhere NULL case
+ std::string fromWhere = fromWhereJSON.is_null() ? "" :
+ fromWhereJSON.get_str();
+
+
+ // call into R to determine the token
+ FunctionToken token;
+ error = guessFunctionToken(line, pos, &token);
+ if (error)
+ return error;
+
+ // lookup the namespace if we need to
+ std::string namespaceName;
+ if (!token.package.empty())
+ namespaceName = "package:" + token.package;
+ else
+ findFunction(token.name, fromWhere, &namespaceName);
+
+ // return either just the name or the full function
+ if (!namespaceName.empty())
+ {
+ pResponse->setResult(createFunctionDefinition(token.name,
+ namespaceName));
+ }
+ else
+ {
+ json::Object funDefName;
+ funDefName["name"] = token.name;
+ pResponse->setResult(funDefName);
+ }
+
+ return Success();
+}
+
+void onFileMonitorEnabled(const tree<core::FileInfo>& files)
+{
+ s_projectIndex.enqueFiles(files.begin_leaf(), files.end_leaf());
+}
+
+void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events)
+{
+ std::for_each(
+ events.begin(),
+ events.end(),
+ boost::bind(&SourceFileIndex::enqueFileChange, &s_projectIndex, _1));
+}
+
+void onFileMonitorDisabled()
+{
+ // clear the index so we don't ever get stale results
+ s_projectIndex.clear();
+}
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // subscribe to project context file monitoring state changes
+ // (note that if there is no project this will no-op)
+ session::projects::FileMonitorCallbacks cb;
+ cb.onMonitoringEnabled = onFileMonitorEnabled;
+ cb.onFilesChanged = onFilesChanged;
+ cb.onMonitoringDisabled = onFileMonitorDisabled;
+ projects::projectContext().subscribeToFileMonitor("R source file indexing",
+ cb);
+
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "search_code", searchCode))
+ (bind(registerRpcMethod, "get_function_definition", getFunctionDefinition))
+ (bind(registerRpcMethod, "get_search_path_function_definition", getSearchPathFunctionDefinition))
+ (bind(registerRpcMethod, "get_method_definition", getMethodDefinition))
+ (bind(registerRpcMethod, "find_function_in_search_path", findFunctionInSearchPath));
+
+ return initBlock.execute();
+}
+
+
+} // namespace code_search
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionCodeSearch.hpp b/src/cpp/session/modules/SessionCodeSearch.hpp
new file mode 100644
index 0000000..a32b2a9
--- /dev/null
+++ b/src/cpp/session/modules/SessionCodeSearch.hpp
@@ -0,0 +1,34 @@
+/*
+ * SessionCodeSearch.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_CODE_SEARCH_HPP
+#define SESSION_CODE_SEARCH_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace code_search {
+
+core::Error initialize();
+
+} // namespace code_search
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_CODE_SEARCH_HPP
+
diff --git a/src/cpp/session/modules/SessionCodeTools.R b/src/cpp/session/modules/SessionCodeTools.R
new file mode 100644
index 0000000..40daee8
--- /dev/null
+++ b/src/cpp/session/modules/SessionCodeTools.R
@@ -0,0 +1,247 @@
+#
+# SessionCodeTools.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# Return the scope names in which the given names exist
+.rs.addFunction("which", function(names) {
+ scopes = search()
+ sapply(names, function(name) {
+ for (scope in scopes) {
+ if (exists(name, where=scope, inherits=F))
+ return(scope)
+ }
+ return("")
+ })
+})
+
+.rs.addFunction("guessToken", function(line, cursorPos)
+{
+ utils:::.assignLinebuffer(line)
+ utils:::.assignEnd(cursorPos)
+ utils:::.guessTokenFromLine()
+})
+
+.rs.addFunction("findFunctionNamespace", function(name, fromWhere)
+{
+ if (!identical(fromWhere, ""))
+ {
+ if ( ! (fromWhere %in% search()) )
+ return ("")
+
+ where = as.environment(fromWhere)
+ }
+ else
+ {
+ where = globalenv()
+ }
+
+ envList <- methods:::findFunction(name, where = where)
+ if (length(envList) > 0)
+ {
+ env <- envList[[1]]
+ if (identical(env, baseenv()))
+ {
+ return ("package:base")
+ }
+ else if (identical(env, globalenv()))
+ {
+ return(".GlobalEnv")
+ }
+ else
+ {
+ envName = attr(envList[[1]], "name")
+ if (!is.null(envName))
+ return (envName)
+ else
+ return ("")
+ }
+ }
+ else
+ {
+ return ("")
+ }
+})
+
+.rs.addFunction("getFunction", function(name, namespaceName)
+{
+ tryCatch(eval(parse(text = name),
+ envir = as.environment(namespaceName),
+ enclos = NULL),
+ error = function(e) NULL)
+})
+
+
+.rs.addFunction("functionHasSrcRef", function(func)
+{
+ return (!is.null(attr(func, "srcref")))
+})
+
+.rs.addFunction("deparseFunction", function(func, useSource)
+{
+ control <- c("keepInteger", "keepNA")
+ if (useSource)
+ control <- append(control, "useSource")
+
+ deparse(func, width.cutoff = 59, control = control)
+})
+
+.rs.addFunction("getS3MethodsForFunction", function(func)
+{
+ tryCatch(as.character(suppressWarnings(methods(func))),
+ error = function(e) character())
+})
+
+
+# Return a list of S4 methods formatted as functionName {className, className}
+# NOTE: should call isGeneric prior to calling this (it will yield an error
+# for functions that aren't generic)
+.rs.addFunction("getS4MethodsForFunction", function(func)
+{
+ sigs <- findMethodSignatures(methods = findMethods(func))
+ apply(sigs,
+ 1,
+ function(sig)
+ {
+ paste(func,
+ " {",
+ paste(sig, collapse=", "),
+ "}",
+ sep="",
+ collapse = "")
+ })
+})
+
+.rs.addFunction("getS4MethodNamespaceName", function(method)
+{
+ env <- environment(method)
+ if (identical(env, baseenv()))
+ return ("package:base")
+ else if (identical(env, globalenv()))
+ return (".GlobalEnv")
+ else
+ {
+ envName <- environmentName(env)
+ if (envName %in% search())
+ return (envName)
+ else
+ paste("package:", envName, sep="")
+ }
+})
+
+.rs.addFunction("attemptRoxygenTagCompletion", function(line, cursorPos)
+{
+ line <- substr(line, 0, cursorPos)
+ match <- grepl("^\\s*#+'\\s*@[a-zA-Z0-9]*$", line, perl=T)
+ if (!match)
+ return(NULL)
+
+ tag <- sub(".*(?=@)", '', line, perl=T)
+
+ # All known Roxygen2 tags, in alphabetical order
+ tags <- c(
+ "@aliases",
+ "@author",
+ "@concepts",
+ "@description",
+ "@details",
+ "@docType",
+ "@example",
+ "@examples",
+ "@export",
+ "@exportClass",
+ "@exportMethod",
+ "@family",
+ "@format",
+ "@import",
+ "@importClassesFrom",
+ "@importFrom",
+ "@importMethodsFrom",
+ "@include",
+ "@inheritParams",
+ "@keywords",
+ "@method",
+ "@name",
+ "@note",
+ "@param",
+ "@rdname",
+ "@references",
+ "@return",
+ "@S3method",
+ "@section",
+ "@seealso",
+ "@source",
+ "@template",
+ "@templateVar",
+ "@title",
+ "@usage",
+ "@useDynLib"
+ );
+
+ matchingTags <- grep(paste("^", tag, sep=""), tags, value=T)
+
+ list(token=tag,
+ results=matchingTags,
+ packages=vector(mode='character', length=length(matchingTags)),
+ fguess=c())
+})
+
+.rs.addFunction("getPendingInput", function()
+{
+ .Call("rs_getPendingInput")
+})
+
+utils:::rc.settings(files=T)
+.rs.addJsonRpcHandler("get_completions", function(line, cursorPos)
+{
+ roxygen <- .rs.attemptRoxygenTagCompletion(line, cursorPos)
+ if (!is.null(roxygen))
+ return(roxygen);
+
+ utils:::.assignLinebuffer(line)
+ utils:::.assignEnd(cursorPos)
+ token = utils:::.guessTokenFromLine()
+ utils:::.completeToken()
+ results = utils:::.retrieveCompletions()
+ status = utils:::rc.status()
+
+ packages = sub('^package:', '', .rs.which(results))
+
+ # ensure spaces around =
+ results <- sub("=$", " = ", results)
+
+ choose = packages == '.GlobalEnv'
+ results.sorted = c(results[choose], results[!choose])
+ packages.sorted = c(packages[choose], packages[!choose])
+
+ packages.sorted = sub('^\\.GlobalEnv$', '', packages.sorted)
+
+ list(token=token,
+ results=results.sorted,
+ packages=packages.sorted,
+ fguess=status$fguess)
+})
+
+.rs.addJsonRpcHandler("get_help_at_cursor", function(line, cursorPos)
+{
+ token <- .rs.guessToken(line, cursorPos)
+ if (token == '')
+ return()
+
+ pieces <- strsplit(token, ':{2,3}')[[1]]
+
+ if (length(pieces) > 1)
+ print(help(pieces[2], package=pieces[1], help_type='html'))
+ else
+ print(help(pieces[1], help_type='html', try.all.packages=T))
+})
diff --git a/src/cpp/session/modules/SessionCompileAttributes.R b/src/cpp/session/modules/SessionCompileAttributes.R
new file mode 100644
index 0000000..6294479
--- /dev/null
+++ b/src/cpp/session/modules/SessionCompileAttributes.R
@@ -0,0 +1,8 @@
+
+updated <- Rcpp::compileAttributes()
+wd <- normalizePath(".", winslash = "/")
+for (file in updated) {
+ file <- substr(file, nchar(wd)+2, nchar(file))
+ cat("* Updated ", file, "\n", sep="")
+}
+
diff --git a/src/cpp/session/modules/SessionConsole.R b/src/cpp/session/modules/SessionConsole.R
new file mode 100644
index 0000000..8c159fd
--- /dev/null
+++ b/src/cpp/session/modules/SessionConsole.R
@@ -0,0 +1,16 @@
+
+#
+# SessionConsole.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
diff --git a/src/cpp/session/modules/SessionConsole.cpp b/src/cpp/session/modules/SessionConsole.cpp
new file mode 100644
index 0000000..bad60e0
--- /dev/null
+++ b/src/cpp/session/modules/SessionConsole.cpp
@@ -0,0 +1,181 @@
+/*
+ * SessionConsole.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionConsole.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/system/OutputCapture.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RConsoleActions.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace console {
+
+namespace {
+
+bool suppressOutput(const std::string& output)
+{
+ // tokens to suppress
+ const char * const kGlibWarningToken = "GLib-WARNING **:";
+ const char * const kGlibCriticalToken = "GLib-CRITICAL **:";
+ const char * const kGlibGObjectWarningToken = "GLib-GObject-WARNING **:";
+ const char * const kAutoreleaseNoPool = "utoreleaseNoPool";
+ const char * const kSelectInterrupted = "select: Interrupted system call";
+ const char * const kNotAGitRepo = "Not a git repository";
+ const char * const kIsOutsideRepo = "is outside repository";
+ const char * const kCGContextError = "<Error>: CGContext";
+
+ // check tokens
+ if (boost::algorithm::contains(output, kGlibWarningToken) ||
+ boost::algorithm::contains(output, kGlibCriticalToken) ||
+ boost::algorithm::contains(output, kGlibGObjectWarningToken) ||
+ boost::algorithm::contains(output, kAutoreleaseNoPool) ||
+ boost::algorithm::contains(output, kSelectInterrupted) ||
+ boost::algorithm::contains(output, kNotAGitRepo) ||
+ boost::algorithm::contains(output, kIsOutsideRepo) ||
+ boost::algorithm::contains(output, kCGContextError))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void writeStandardOutput(const std::string& output)
+{
+ module_context::consoleWriteOutput(output);
+}
+
+void writeStandardError(const std::string& output)
+{
+ if (!suppressOutput(output))
+ module_context::consoleWriteError(output);
+}
+
+
+Error initializeOutputCapture()
+{
+ // only capture stderr if it isn't connected to a terminal
+ boost::function<void(const std::string&)> stderrHandler;
+ if (!core::system::stderrIsTerminal())
+ stderrHandler = writeStandardError;
+
+ // initialize
+ return core::system::captureStandardStreams(writeStandardOutput,
+ stderrHandler);
+}
+
+FilePath s_lastWorkingDirectory;
+
+void detectWorkingDirectoryChanged()
+{
+ FilePath currentWorkingDirectory = module_context::safeCurrentPath();
+ if ( s_lastWorkingDirectory.empty() ||
+ (currentWorkingDirectory != s_lastWorkingDirectory) )
+ {
+ // fire event
+ std::string path = module_context::createAliasedPath(currentWorkingDirectory);
+ ClientEvent event(client_events::kWorkingDirChanged, path);
+ module_context::enqueClientEvent(event);
+
+ // update state
+ s_lastWorkingDirectory = currentWorkingDirectory;
+ }
+}
+
+void onClientInit()
+{
+ // reset state to force wd changed event
+ s_lastWorkingDirectory = FilePath();
+ detectWorkingDirectoryChanged();
+}
+
+void onDetectChanges(module_context::ChangeSource source)
+{
+ // check for working directory changed
+ detectWorkingDirectoryChanged();
+}
+
+Error resetConsoleActions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::consoleActions().reset();
+
+ return Success();
+}
+
+SEXP rs_getPendingInput()
+{
+ r::sexp::Protect rProtect;
+ return r::sexp::create(r::session::consoleActions().pendingInput(),
+ &rProtect);
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ if (!session::options().verifyInstallation())
+ {
+ // capture standard streams
+ Error error = initializeOutputCapture();
+ if (error)
+ return error;
+ }
+
+ // register routines
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_getPendingInput" ;
+ methodDef.fun = (DL_FUNC) rs_getPendingInput ;
+ methodDef.numArgs = 0;
+ r::routines::addCallMethod(methodDef);
+
+
+ // subscribe to events
+ using boost::bind;
+ using namespace module_context;
+ events().onClientInit.connect(bind(onClientInit));
+ events().onDetectChanges.connect(bind(onDetectChanges, _1));
+
+ // more initialization
+ using boost::bind;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionConsole.R"))
+ (bind(registerRpcMethod, "reset_console_actions", resetConsoleActions));
+
+ return initBlock.execute();
+}
+
+
+} // namespace console
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionConsole.hpp b/src/cpp/session/modules/SessionConsole.hpp
new file mode 100644
index 0000000..e8d9fe3
--- /dev/null
+++ b/src/cpp/session/modules/SessionConsole.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionConsole.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_CONSOLE_HPP
+#define SESSION_CONSOLE_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace console {
+
+core::Error initialize();
+
+} // namespace console
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_CONSOLE_HPP
diff --git a/src/cpp/session/modules/SessionConsoleProcess.cpp b/src/cpp/session/modules/SessionConsoleProcess.cpp
new file mode 100644
index 0000000..8ca33cd
--- /dev/null
+++ b/src/cpp/session/modules/SessionConsoleProcess.cpp
@@ -0,0 +1,765 @@
+/*
+ * SessionConsoleProcess.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "SessionConsoleProcess.hpp"
+
+#include <boost/regex.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/json/JsonRpc.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/Exec.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/Settings.hpp>
+
+#include <core/system/Environment.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "session-config.h"
+
+#ifdef RSTUDIO_SERVER
+#include <core/system/Crypto.hpp>
+#endif
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace console_process {
+
+namespace {
+ const size_t OUTPUT_BUFFER_SIZE = 8192;
+ typedef std::map<std::string, boost::shared_ptr<ConsoleProcess> > ProcTable;
+ ProcTable s_procs;
+} // anonymous namespace
+
+const int kDefaultMaxOutputLines = 500;
+
+ConsoleProcess::ConsoleProcess()
+ : dialog_(false), showOnOutput_(false), interactionMode_(InteractionNever),
+ maxOutputLines_(kDefaultMaxOutputLines), started_(true),
+ interrupt_(false), outputBuffer_(OUTPUT_BUFFER_SIZE)
+{
+ regexInit();
+
+ // When we retrieve from outputBuffer, we only want complete lines. Add a
+ // dummy \n so we can tell the first line is a complete line.
+ outputBuffer_.push_back('\n');
+}
+
+ConsoleProcess::ConsoleProcess(const std::string& command,
+ const core::system::ProcessOptions& options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode interactionMode,
+ int maxOutputLines)
+ : command_(command), options_(options), caption_(caption), dialog_(dialog),
+ showOnOutput_(false),
+ interactionMode_(interactionMode), maxOutputLines_(maxOutputLines),
+ started_(false), interrupt_(false),
+ outputBuffer_(OUTPUT_BUFFER_SIZE)
+{
+ commonInit();
+}
+
+ConsoleProcess::ConsoleProcess(const std::string& program,
+ const std::vector<std::string>& args,
+ const core::system::ProcessOptions& options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode interactionMode,
+ int maxOutputLines)
+ : program_(program), args_(args), options_(options), caption_(caption), dialog_(dialog),
+ showOnOutput_(false),
+ interactionMode_(interactionMode), maxOutputLines_(maxOutputLines),
+ started_(false), interrupt_(false),
+ outputBuffer_(OUTPUT_BUFFER_SIZE)
+{
+ commonInit();
+}
+
+void ConsoleProcess::regexInit()
+{
+ controlCharsPattern_ = boost::regex("[\\r\\b]");
+ promptPattern_ = boost::regex("^(.+)[\\W_]( +)$");
+}
+
+void ConsoleProcess::commonInit()
+{
+ regexInit();
+
+ handle_ = core::system::generateUuid(false);
+
+ // always redirect stderr to stdout so output is interleaved
+ options_.redirectStdErrToStdOut = true;
+
+ if (interactionMode() != InteractionNever)
+ {
+#ifdef _WIN32
+ // NOTE: We use consoleio.exe here in order to make sure svn.exe password
+ // prompting works properly
+ options_.createNewConsole = true;
+
+ FilePath consoleIoPath = session::options().consoleIoPath();
+
+ // if this is as runProgram then fixup the program and args
+ if (!program_.empty())
+ {
+ // build new args
+ shell_utils::ShellArgs args;
+ args << program_;
+ args << args_;
+
+ // fixup program_ and args_ so we run the consoleio.exe proxy
+ program_ = consoleIoPath.absolutePathNative();
+ args_ = args;
+ }
+ // if this is a runCommand then prepend consoleio.exe to the command
+ else
+ {
+ command_ = shell_utils::escape(consoleIoPath) + " " + command_;
+ }
+#else
+ // request a pseudoterminal if this is an interactive console process
+ options_.pseudoterminal = core::system::Pseudoterminal(80, 1);
+
+ // define TERM to dumb (but first make sure we have an environment
+ // block to modify)
+ if (!options_.environment)
+ {
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+ options_.environment = childEnv;
+ }
+ core::system::setenv(&(options_.environment.get()), "TERM", "dumb");
+#endif
+ }
+
+
+ // When we retrieve from outputBuffer, we only want complete lines. Add a
+ // dummy \n so we can tell the first line is a complete line.
+ outputBuffer_.push_back('\n');
+}
+
+std::string ConsoleProcess::bufferedOutput() const
+{
+ boost::circular_buffer<char>::const_iterator pos =
+ std::find(outputBuffer_.begin(), outputBuffer_.end(), '\n');
+
+ std::string result;
+ if (pos != outputBuffer_.end())
+ pos++;
+ std::copy(pos, outputBuffer_.end(), std::back_inserter(result));
+ // Will be empty if the buffer was overflowed by a single line
+ return result;
+}
+
+void ConsoleProcess::setPromptHandler(
+ const boost::function<bool(const std::string&, Input*)>& onPrompt)
+{
+ onPrompt_ = onPrompt;
+}
+
+Error ConsoleProcess::start()
+{
+ if (started_)
+ return Success();
+
+ Error error;
+ if (!command_.empty())
+ {
+ error = module_context::processSupervisor().runCommand(
+ command_, options_, createProcessCallbacks());
+ }
+ else
+ {
+ error = module_context::processSupervisor().runProgram(
+ program_, args_, options_, createProcessCallbacks());
+ }
+ if (!error)
+ started_ = true;
+ return error;
+}
+
+void ConsoleProcess::enqueInput(const Input& input)
+{
+ inputQueue_.push(input);
+}
+
+void ConsoleProcess::interrupt()
+{
+ interrupt_ = true;
+}
+
+bool ConsoleProcess::onContinue(core::system::ProcessOperations& ops)
+{
+ // full stop interrupt if requested
+ if (interrupt_)
+ return false;
+
+ // process input queue
+ while (!inputQueue_.empty())
+ {
+ // pop input
+ Input input = inputQueue_.front();
+ inputQueue_.pop();
+
+ // pty interrupt
+ if (input.interrupt)
+ {
+ Error error = ops.ptyInterrupt();
+ if (error)
+ LOG_ERROR(error);
+
+ if (input.echoInput)
+ appendToOutputBuffer("^C");
+ }
+
+ // text input
+ else
+ {
+ std::string inputText = input.text;
+#ifdef _WIN32
+ string_utils::convertLineEndings(&inputText, string_utils::LineEndingWindows);
+#endif
+ Error error = ops.writeToStdin(inputText, false);
+ if (error)
+ LOG_ERROR(error);
+
+ if (input.echoInput)
+ appendToOutputBuffer(inputText);
+ else
+ appendToOutputBuffer("\n");
+ }
+ }
+
+ // continue
+ return true;
+}
+
+void ConsoleProcess::appendToOutputBuffer(const std::string &str)
+{
+ std::copy(str.begin(), str.end(), std::back_inserter(outputBuffer_));
+}
+
+void ConsoleProcess::enqueOutputEvent(const std::string &output, bool error)
+{
+ // copy to output buffer
+ appendToOutputBuffer(output);
+
+ // If there's more output than the client can even show, then
+ // truncate it to the amount that the client can show. Too much
+ // output can overwhelm the client, making it unresponsive.
+ std::string trimmedOutput = output;
+ string_utils::trimLeadingLines(maxOutputLines_, &trimmedOutput);
+
+ json::Object data;
+ data["handle"] = handle_;
+ data["error"] = error;
+ data["output"] = trimmedOutput;
+ module_context::enqueClientEvent(
+ ClientEvent(client_events::kConsoleProcessOutput, data));
+}
+
+void ConsoleProcess::onStdout(core::system::ProcessOperations& ops,
+ const std::string& output)
+{
+ // convert line endings to posix
+ std::string posixOutput = output;
+ string_utils::convertLineEndings(&posixOutput,
+ string_utils::LineEndingPosix);
+
+ // process as normal output or detect a prompt if there is one
+ if (boost::algorithm::ends_with(posixOutput, "\n"))
+ {
+ enqueOutputEvent(posixOutput, false);
+ }
+ else
+ {
+ // look for the last newline and take the content after
+ // that as the prompt
+ std::size_t lastLoc = posixOutput.find_last_of("\n\f");
+ if (lastLoc != std::string::npos)
+ {
+ enqueOutputEvent(posixOutput.substr(0, lastLoc), false);
+ maybeConsolePrompt(ops, posixOutput.substr(lastLoc + 1));
+ }
+ else
+ {
+ maybeConsolePrompt(ops, posixOutput);
+ }
+ }
+}
+
+void ConsoleProcess::maybeConsolePrompt(core::system::ProcessOperations& ops,
+ const std::string& output)
+{
+ boost::smatch smatch;
+
+ // treat special control characters as output rather than a prompt
+ if (boost::regex_search(output, smatch, controlCharsPattern_))
+ enqueOutputEvent(output, false);
+
+ // make sure the output matches our prompt pattern
+ if (!boost::regex_match(output, smatch, promptPattern_))
+ enqueOutputEvent(output, false);
+
+ // it is a prompt
+ else
+ handleConsolePrompt(ops, output);
+}
+
+void ConsoleProcess::handleConsolePrompt(core::system::ProcessOperations& ops,
+ const std::string& prompt)
+{
+ // if there is a custom prmopt handler then give it a chance to
+ // handle the prompt first
+ if (onPrompt_)
+ {
+ Input input;
+ if (onPrompt_(prompt, &input))
+ {
+ if (!input.empty())
+ {
+ enqueInput(input);
+ }
+ else
+ {
+ Error error = ops.terminate();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return;
+ }
+
+ }
+
+ // enque a prompt event
+ json::Object data;
+ data["handle"] = handle_;
+ data["prompt"] = prompt;
+ module_context::enqueClientEvent(
+ ClientEvent(client_events::kConsoleProcessPrompt, data));
+}
+
+void ConsoleProcess::onExit(int exitCode)
+{
+ exitCode_.reset(exitCode);
+
+ json::Object data;
+ data["handle"] = handle_;
+ data["exitCode"] = exitCode;
+ module_context::enqueClientEvent(
+ ClientEvent(client_events::kConsoleProcessExit, data));
+
+ onExit_(exitCode);
+}
+
+core::json::Object ConsoleProcess::toJson() const
+{
+ json::Object result;
+ result["handle"] = handle_;
+ result["caption"] = caption_;
+ result["dialog"] = dialog_;
+ result["show_on_output"] = showOnOutput_;
+ result["interaction_mode"] = static_cast<int>(interactionMode_);
+ result["max_output_lines"] = maxOutputLines_;
+ result["buffered_output"] = bufferedOutput();
+ if (exitCode_)
+ result["exit_code"] = *exitCode_;
+ else
+ result["exit_code"] = json::Value();
+ return result;
+}
+
+boost::shared_ptr<ConsoleProcess> ConsoleProcess::fromJson(
+ core::json::Object &obj)
+{
+ boost::shared_ptr<ConsoleProcess> pProc(new ConsoleProcess());
+ pProc->handle_ = obj["handle"].get_str();
+ pProc->caption_ = obj["caption"].get_str();
+ pProc->dialog_ = obj["dialog"].get_bool();
+
+ json::Value showOnOutput = obj["show_on_output"];
+ if (!showOnOutput.is_null())
+ pProc->showOnOutput_ = showOnOutput.get_bool();
+ else
+ pProc->showOnOutput_ = false;
+
+ json::Value mode = obj["interaction_mode"];
+ if (!mode.is_null())
+ pProc->interactionMode_ = static_cast<InteractionMode>(mode.get_int());
+ else
+ pProc->interactionMode_ = InteractionNever;
+
+ json::Value maxLines = obj["max_output_lines"];
+ if (!maxLines.is_null())
+ pProc->maxOutputLines_ = maxLines.get_int();
+ else
+ pProc->maxOutputLines_ = kDefaultMaxOutputLines;
+
+ std::string bufferedOutput = obj["buffered_output"].get_str();
+ std::copy(bufferedOutput.begin(), bufferedOutput.end(),
+ std::back_inserter(pProc->outputBuffer_));
+ json::Value exitCode = obj["exit_code"];
+ if (exitCode.is_null())
+ pProc->exitCode_.reset();
+ else
+ pProc->exitCode_.reset(exitCode.get_int());
+
+ return pProc;
+}
+
+core::system::ProcessCallbacks ConsoleProcess::createProcessCallbacks()
+{
+ core::system::ProcessCallbacks cb;
+ cb.onContinue = boost::bind(&ConsoleProcess::onContinue, ConsoleProcess::shared_from_this(), _1);
+ cb.onStdout = boost::bind(&ConsoleProcess::onStdout, ConsoleProcess::shared_from_this(), _1, _2);
+ cb.onExit = boost::bind(&ConsoleProcess::onExit, ConsoleProcess::shared_from_this(), _1);
+ return cb;
+}
+
+Error procStart(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string handle;
+ Error error = json::readParams(request.params, &handle);
+ if (error)
+ return error;
+ ProcTable::const_iterator pos = s_procs.find(handle);
+ if (pos != s_procs.end())
+ {
+ return pos->second->start();
+ }
+ else
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+}
+
+Error procInterrupt(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string handle;
+ Error error = json::readParams(request.params, &handle);
+ if (error)
+ return error;
+ ProcTable::const_iterator pos = s_procs.find(handle);
+ if (pos != s_procs.end())
+ {
+ pos->second->interrupt();
+ return Success();
+ }
+ else
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+}
+
+Error procReap(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string handle;
+ Error error = json::readParams(request.params, &handle);
+ if (error)
+ return error;
+
+ if (!s_procs.erase(handle))
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error procWriteStdin(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string handle;
+ Error error = json::readParam(request.params, 0, &handle);
+ if (error)
+ return error;
+
+ ConsoleProcess::Input input;
+ error = json::readObjectParam(request.params, 1,
+ "interrupt", &input.interrupt,
+ "text", &input.text,
+ "echo_input", &input.echoInput);
+ if (error)
+ return error;
+
+ ProcTable::const_iterator pos = s_procs.find(handle);
+ if (pos != s_procs.end())
+ {
+#ifdef RSTUDIO_SERVER
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ if (!input.interrupt)
+ {
+ error = core::system::crypto::rsaPrivateDecrypt(input.text,
+ &input.text);
+ if (error)
+ return error;
+ }
+ }
+#endif
+
+ pos->second->enqueInput(input);
+
+ return Success();
+ }
+ else
+ {
+ return systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ }
+}
+
+boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
+ const std::string& command,
+ core::system::ProcessOptions options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode interactionMode,
+ int maxOutputLines)
+{
+ options.terminateChildren = true;
+ boost::shared_ptr<ConsoleProcess> ptrProc(
+ new ConsoleProcess(command,
+ options,
+ caption,
+ dialog,
+ interactionMode,
+ maxOutputLines));
+ s_procs[ptrProc->handle()] = ptrProc;
+ return ptrProc;
+}
+
+boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
+ const std::string& program,
+ const std::vector<std::string>& args,
+ core::system::ProcessOptions options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode interactionMode,
+ int maxOutputLines)
+{
+ options.terminateChildren = true;
+ boost::shared_ptr<ConsoleProcess> ptrProc(
+ new ConsoleProcess(program,
+ args,
+ options,
+ caption,
+ dialog,
+ interactionMode,
+ maxOutputLines));
+ s_procs[ptrProc->handle()] = ptrProc;
+ return ptrProc;
+}
+
+void PasswordManager::attach(
+ boost::shared_ptr<console_process::ConsoleProcess> pCP,
+ bool showRememberOption)
+{
+ pCP->setPromptHandler(boost::bind(&PasswordManager::handlePrompt,
+ this,
+ pCP->handle(),
+ _1,
+ showRememberOption,
+ _2));
+
+ pCP->onExit().connect(boost::bind(&PasswordManager::onExit,
+ this,
+ pCP->handle(),
+ _1));
+}
+
+bool PasswordManager::handlePrompt(const std::string& cpHandle,
+ const std::string& prompt,
+ bool showRememberOption,
+ ConsoleProcess::Input* pInput)
+{
+ // is this a password prompt?
+ boost::smatch match;
+ if (boost::regex_match(prompt, match, promptPattern_))
+ {
+ // see if it matches any of our existing cached passwords
+ std::vector<CachedPassword>::const_iterator it =
+ std::find_if(passwords_.begin(),
+ passwords_.end(),
+ boost::bind(&hasPrompt, _1, prompt));
+ if (it != passwords_.end())
+ {
+ // cached password
+ *pInput = ConsoleProcess::Input(it->password + "\n", false);
+ }
+ else
+ {
+ // prompt for password
+ std::string password;
+ bool remember;
+ if (promptHandler_(prompt, showRememberOption, &password, &remember))
+ {
+
+ // cache the password (but also set the remember flag so it
+ // will be removed from the cache when the console process
+ // exits if the user chose not to remember).
+ CachedPassword cachedPassword;
+ cachedPassword.cpHandle = cpHandle;
+ cachedPassword.prompt = prompt;
+ cachedPassword.password = password;
+ cachedPassword.remember = remember;
+ passwords_.push_back(cachedPassword);
+
+ // interactively entered password
+ *pInput = ConsoleProcess::Input(password + "\n", false);
+ }
+ else
+ {
+ // user cancelled
+ *pInput = ConsoleProcess::Input();
+ }
+ }
+
+ return true;
+ }
+ // not a password prompt so ignore
+ else
+ {
+ return false;
+ }
+}
+
+void PasswordManager::onExit(const std::string& cpHandle,
+ int exitCode)
+{
+ // if a process exits with an error then remove any cached
+ // passwords which originated from that process
+ if (exitCode != EXIT_SUCCESS)
+ {
+ passwords_.erase(std::remove_if(passwords_.begin(),
+ passwords_.end(),
+ boost::bind(&hasHandle, _1, cpHandle)),
+ passwords_.end());
+ }
+
+ // otherwise remove any cached password for this process which doesn't
+ // have its remember flag set
+ else
+ {
+ passwords_.erase(std::remove_if(passwords_.begin(),
+ passwords_.end(),
+ boost::bind(&forgetOnExit, _1, cpHandle)),
+ passwords_.end());
+ }
+}
+
+
+bool PasswordManager::hasPrompt(const CachedPassword& cachedPassword,
+ const std::string& prompt)
+{
+ return cachedPassword.prompt == prompt;
+}
+
+bool PasswordManager::hasHandle(const CachedPassword& cachedPassword,
+ const std::string& cpHandle)
+{
+ return cachedPassword.cpHandle == cpHandle;
+}
+
+bool PasswordManager::forgetOnExit(const CachedPassword& cachedPassword,
+ const std::string& cpHandle)
+{
+ return hasHandle(cachedPassword, cpHandle) && !cachedPassword.remember;
+}
+
+core::json::Array processesAsJson()
+{
+ json::Array procInfos;
+ for (ProcTable::const_iterator it = s_procs.begin();
+ it != s_procs.end();
+ it++)
+ {
+ procInfos.push_back(it->second->toJson());
+ }
+ return procInfos;
+}
+
+void onSuspend(core::Settings* pSettings)
+{
+ json::Array array;
+ for (ProcTable::const_iterator it = s_procs.begin();
+ it != s_procs.end();
+ it++)
+ {
+ array.push_back(it->second->toJson());
+ }
+
+ std::ostringstream ostr;
+ json::write(array, ostr);
+ pSettings->set("console_procs", ostr.str());
+}
+
+void onResume(const core::Settings& settings)
+{
+ std::string strVal = settings.get("console_procs");
+ if (strVal.empty())
+ return;
+
+ json::Value value;
+ if (!json::parse(strVal, &value))
+ return;
+
+ json::Array procs = value.get_array();
+ for (json::Array::iterator it = procs.begin();
+ it != procs.end();
+ it++)
+ {
+ boost::shared_ptr<ConsoleProcess> proc =
+ ConsoleProcess::fromJson(it->get_obj());
+ s_procs[proc->handle()] = proc;
+ }
+}
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ // add suspend/resume handler
+ addSuspendHandler(SuspendHandler(boost::bind(onSuspend, _2), onResume));
+
+ // install rpc methods
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "process_start", procStart))
+ (bind(registerRpcMethod, "process_interrupt", procInterrupt))
+ (bind(registerRpcMethod, "process_reap", procReap))
+ (bind(registerRpcMethod, "process_write_stdin", procWriteStdin));
+
+ return initBlock.execute();
+}
+
+} // namespace console_process
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionConsoleProcess.hpp b/src/cpp/session/modules/SessionConsoleProcess.hpp
new file mode 100644
index 0000000..86e7d8c
--- /dev/null
+++ b/src/cpp/session/modules/SessionConsoleProcess.hpp
@@ -0,0 +1,256 @@
+/*
+ * SessionConsoleProcess.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef SESSION_CONSOLE_PROCESS_HPP
+#define SESSION_CONSOLE_PROCESS_HPP
+
+#include <queue>
+
+#include <boost/regex.hpp>
+#include <boost/signals.hpp>
+#include <boost/circular_buffer.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/system/Process.hpp>
+#include <core/Log.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace console_process {
+
+enum InteractionMode
+{
+ InteractionNever = 0,
+ InteractionPossible = 1,
+ InteractionAlways = 2
+};
+
+extern const int kDefaultMaxOutputLines;
+
+class ConsoleProcess : boost::noncopyable,
+ public boost::enable_shared_from_this<ConsoleProcess>
+{
+private:
+ // This constructor is only for resurrecting orphaned processes (i.e. for
+ // suspend/resume scenarios)
+ ConsoleProcess();
+
+ ConsoleProcess(
+ const std::string& command,
+ const core::system::ProcessOptions& options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode mode,
+ int maxOutputLines);
+
+ ConsoleProcess(
+ const std::string& program,
+ const std::vector<std::string>& args,
+ const core::system::ProcessOptions& options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode mode,
+ int maxOutputLines);
+
+ void regexInit();
+ void commonInit();
+
+public:
+ struct Input
+ {
+ explicit Input(const std::string& text, bool echoInput = true)
+ : interrupt(false), text(text), echoInput(echoInput)
+ {
+ }
+
+ Input() : interrupt(false), echoInput(false) {}
+
+ bool empty() { return !interrupt && text.empty(); }
+
+ bool interrupt ;
+ std::string text;
+ bool echoInput;
+ };
+
+public:
+ // creating console processes with a command string is not supported on
+ // Win32 because in order to implement the InteractionPossible/Always
+ // modes we use the consoleio.exe proxy, which can only be invoked from
+ // the runProgram codepath
+ static boost::shared_ptr<ConsoleProcess> create(
+ const std::string& command,
+ core::system::ProcessOptions options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode mode,
+ int maxOutputLines = kDefaultMaxOutputLines);
+
+ static boost::shared_ptr<ConsoleProcess> create(
+ const std::string& program,
+ const std::vector<std::string>& args,
+ core::system::ProcessOptions options,
+ const std::string& caption,
+ bool dialog,
+ InteractionMode mode,
+ int maxOutputLines = kDefaultMaxOutputLines);
+
+ virtual ~ConsoleProcess() {}
+
+ // set a custom prompt handler -- return true to indicate the prompt
+ // was handled and false to let it pass. return empty input to
+ // indicate the user cancelled out of the prompt (in this case the
+ // process will be terminated)
+ void setPromptHandler(
+ const boost::function<bool(const std::string&, Input*)>& onPrompt);
+
+ boost::signal<void(int)>& onExit() { return onExit_; }
+
+ std::string handle() const { return handle_; }
+ InteractionMode interactionMode() const { return interactionMode_; }
+
+ core::Error start();
+ void enqueInput(const Input& input);
+ void interrupt();
+
+ void setShowOnOutput(bool showOnOutput) { showOnOutput_ = showOnOutput; }
+
+ core::json::Object toJson() const;
+ static boost::shared_ptr<ConsoleProcess> fromJson(
+ core::json::Object& obj);
+
+private:
+ core::system::ProcessCallbacks createProcessCallbacks();
+ bool onContinue(core::system::ProcessOperations& ops);
+ void onStdout(core::system::ProcessOperations& ops,
+ const std::string& output);
+ void onExit(int exitCode);
+
+ std::string bufferedOutput() const;
+ void appendToOutputBuffer(const std::string& str);
+ void enqueOutputEvent(const std::string& output, bool error);
+ void handleConsolePrompt(core::system::ProcessOperations& ops,
+ const std::string& prompt);
+ void maybeConsolePrompt(core::system::ProcessOperations& ops,
+ const std::string& output);
+
+private:
+ // Command and options that will be used when start() is called
+ std::string command_;
+ std::string program_;
+ std::vector<std::string> args_;
+ core::system::ProcessOptions options_;
+
+ std::string caption_;
+ bool dialog_;
+ bool showOnOutput_;
+ InteractionMode interactionMode_;
+ int maxOutputLines_;
+
+ // The handle that the client can use to refer to this process
+ std::string handle_;
+
+ // Whether the process has been successfully started
+ bool started_;
+
+ // Whether the process should be stopped
+ bool interrupt_;
+
+ // Pending input (writes or ptyInterrupts)
+ std::queue<Input> inputQueue_;
+
+ // Buffer output in case client disconnects/reconnects and needs
+ // to recover some history
+ boost::circular_buffer<char> outputBuffer_;
+
+ boost::optional<int> exitCode_;
+
+ boost::function<bool(const std::string&, Input*)> onPrompt_;
+ boost::signal<void(int)> onExit_;
+
+
+ // regex for prompt detection
+ boost::regex controlCharsPattern_;
+ boost::regex promptPattern_;
+};
+
+
+class PasswordManager : boost::noncopyable
+{
+public:
+ typedef boost::function<bool(const std::string&, bool, std::string*, bool*)>
+ PromptHandler;
+
+ explicit PasswordManager(const boost::regex& promptPattern,
+ const PromptHandler& promptHandler)
+ : promptPattern_(promptPattern), promptHandler_(promptHandler)
+ {
+ }
+ virtual ~PasswordManager() {}
+
+ // COPYING: boost::noncopyable
+
+public:
+ // NOTE: if you don't showRememberOption then passwords from that
+ // interaction will NOT be remembered after the parent console
+ // process exits
+ void attach(boost::shared_ptr<ConsoleProcess> pCP,
+ bool showRememberOption = true);
+
+
+private:
+ bool handlePrompt(const std::string& cpHandle,
+ const std::string& prompt,
+ bool showRememberOption,
+ ConsoleProcess::Input* pInput);
+
+ void onExit(const std::string& cpHandle, int exitCode);
+
+ struct CachedPassword
+ {
+ CachedPassword() : remember(false) {}
+ std::string cpHandle;
+ std::string prompt;
+ std::string password;
+ bool remember;
+ };
+
+ static bool hasPrompt(const CachedPassword& cachedPassword,
+ const std::string& prompt);
+
+ static bool hasHandle(const CachedPassword& cachedPassword,
+ const std::string& cpHandle);
+
+ static bool forgetOnExit(const CachedPassword& cachedPassword,
+ const std::string& cpHandle);
+
+private:
+ boost::regex promptPattern_;
+ PromptHandler promptHandler_;
+ std::vector<CachedPassword> passwords_;
+};
+
+core::json::Array processesAsJson();
+core::Error initialize();
+
+} // namespace console_process
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_CONSOLE_PROCESS_HPP
diff --git a/src/cpp/session/modules/SessionCrypto.cpp b/src/cpp/session/modules/SessionCrypto.cpp
new file mode 100644
index 0000000..fe8c2d8
--- /dev/null
+++ b/src/cpp/session/modules/SessionCrypto.cpp
@@ -0,0 +1,78 @@
+/*
+ * SessionCrypto.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionCrypto.hpp"
+
+#include <string>
+
+#include <boost/bind.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/system/Crypto.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace crypto {
+
+namespace {
+
+Error getPublicKey(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(publicKeyInfoJson());
+
+ return Success();
+}
+
+} // anonymous namespace
+
+
+json::Object publicKeyInfoJson()
+{
+ std::string exponent;
+ std::string modulo;
+ core::system::crypto::rsaPublicKey(&exponent, &modulo);
+
+ json::Object result;
+ result["exponent"] = exponent;
+ result["modulo"] = modulo;
+ return result;
+}
+
+Error initialize()
+{
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "get_public_key", getPublicKey));
+ return initBlock.execute();
+}
+
+
+} // namespace crypto
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionCrypto.hpp b/src/cpp/session/modules/SessionCrypto.hpp
new file mode 100644
index 0000000..dffc87c
--- /dev/null
+++ b/src/cpp/session/modules/SessionCrypto.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionCrypto.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_CRYPTO_HPP
+#define SESSION_CRYPTO_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace crypto {
+
+core::json::Object publicKeyInfoJson();
+
+core::Error initialize();
+
+} // namespace crypto
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_CRYPTO_HPP
diff --git a/src/cpp/session/modules/SessionDataImport.R b/src/cpp/session/modules/SessionDataImport.R
new file mode 100644
index 0000000..0309dc3
--- /dev/null
+++ b/src/cpp/session/modules/SessionDataImport.R
@@ -0,0 +1,125 @@
+#
+# SessionDataImport.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("parseDataFile", function(path, header, sep, dec, quote, nrows) {
+ data <- tryCatch(
+ # try to use read.csv directly if possible (since this is a common case
+ # and since LibreOffice spreadsheet exports produce files unparsable
+ # by read.table). check Workspace.makeCommand if we want to deduce
+ # other more concrete read calls.
+ if (identical(sep,",") && identical(dec,".") && identical(quote,"\""))
+ read.csv(path, header=header, nrows=nrows)
+ else
+ read.table(path, header=header, sep=sep, dec=dec, quote=quote, nrows=nrows),
+ error=function(e) {
+ data.frame(Error=e$message)
+ })
+
+ oldWidth <- options('width')$width
+ options(width=1000)
+ output <- format(data)
+ options(width=oldWidth)
+ return(output)
+})
+
+.rs.addJsonRpcHandler("download_data_file", function(url)
+{
+ # download the file
+ downloadPath <- tempfile("data")
+ download.file(url, downloadPath)
+
+ # return the path
+ downloadInfo <- list()
+ downloadInfo$path = downloadPath
+
+ # also return a suggested variable name
+ downloadInfo$varname <- "dataset"
+ urlBasename <- basename(url)
+ if (length(urlBasename) > 0)
+ {
+ fileComponents <- unlist(strsplit(urlBasename, ".", fixed = TRUE))
+ components <- length(fileComponents)
+ if (components >= 1)
+ downloadInfo$varname <- paste(fileComponents[1:components-1],
+ collapse=".")
+ }
+
+ return (downloadInfo)
+})
+
+.rs.addJsonRpcHandler("get_data_preview", function(path)
+{
+ nrows <- 20
+
+ lines <- readLines(path, n=nrows, warn=F)
+
+ # Drop comment lines, leaving the significant ones
+ siglines <- grep("^[^#].*", lines, value=TRUE)
+ firstline <- siglines[1]
+
+ dataline <- siglines[2]
+ if (is.na(dataline) || length(grep("[^\\s]+", dataline)) == 0)
+ dataline <- firstline
+
+ sep <- ''
+ if (length(grep("\\t", firstline)) > 0) {
+ sep <- "\t"
+ } else if (length(grep(";", firstline)) > 0) {
+ sep <- ";"
+ } else if (length(grep(",", firstline)) > 0) {
+ sep <- ","
+ }
+
+ dec <- '.'
+ if (length(grep("\\.", dataline)) == 0
+ && length(grep(",", dataline)) > 0
+ && sep != ",")
+ {
+ dec <- ','
+ }
+
+ header <- length(grep("[0-9]", firstline)) == 0
+
+ quote <- "\""
+
+ output <- .rs.parseDataFile(path,
+ header=header,
+ sep=sep,
+ dec=dec,
+ quote=quote,
+ nrows=nrows)
+
+ list(inputLines=paste(lines, collapse="\n"),
+ output=output,
+ outputNames=names(output),
+ header=header,
+ separator=sep,
+ decimal=dec,
+ quote=quote,
+ defaultStringsAsFactors=default.stringsAsFactors())
+})
+
+.rs.addJsonRpcHandler("get_output_preview", function(path, header, sep, decimal, quote)
+{
+ nrows <- 20
+ output <- .rs.parseDataFile(path, header=header, sep=sep, dec=decimal, quote=quote, nrows=nrows)
+
+ list(output=output,
+ outputNames=names(output),
+ header=header,
+ separator=sep,
+ quote=quote,
+ defaultStringsAsFactors=default.stringsAsFactors())
+})
diff --git a/src/cpp/session/modules/SessionDataViewer.R b/src/cpp/session/modules/SessionDataViewer.R
new file mode 100644
index 0000000..c899cc9
--- /dev/null
+++ b/src/cpp/session/modules/SessionDataViewer.R
@@ -0,0 +1,57 @@
+#
+# SessionData.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction( "formatDataColumn", function(x, len, ...)
+{
+ # first truncate if necessary
+ if ( length(x) > len )
+ length(x) <- len
+
+ # now format
+ format(x, trim = TRUE, justify = "none", ...)
+})
+
+.rs.registerReplaceHook("View", "utils", function(original, x, title) {
+
+ # generate title if necessary
+ if (missing(title))
+ title <- deparse(substitute(x))[1]
+
+ # make sure we are dealing with a data frame (cast explicity both
+ # for the case of it not being a data frame or for the case of
+ # more than one class)
+ if (!is.data.frame(x) || (length(class(x)) > 1))
+ x <- as.data.frame(x)
+
+ # add a column for custom row names if necessary
+ rowNames <- row.names(x)
+ if (!identical(rowNames,as.character(1:length(rowNames)))) {
+ colNames <- names(x)
+ x$row.names <- rowNames
+ x <- x[c("row.names", colNames, recursive=TRUE)]
+ }
+
+ # call viewData (prepare columns so they are either double or character)
+ invisible(.Call("rs_viewData",
+ lapply(x, function(col) {
+ if (is.numeric(col)) {
+ storage.mode(col) <- "double"
+ col
+ }
+ else
+ as.character(col)
+ }),
+ title))
+})
diff --git a/src/cpp/session/modules/SessionDirty.cpp b/src/cpp/session/modules/SessionDirty.cpp
new file mode 100644
index 0000000..372ac95
--- /dev/null
+++ b/src/cpp/session/modules/SessionDirty.cpp
@@ -0,0 +1,132 @@
+/*
+ * SessionDirty.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionDirty.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/utility.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RErrorCategory.hpp>
+#include <r/session/RSession.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+
+using namespace core ;
+using namespace r::sexp;
+using namespace r::exec;
+
+namespace session {
+namespace modules {
+namespace dirty {
+
+namespace {
+
+// last save action.
+// NOTE: we don't persist this (or the workspace dirty state) during suspends in
+// server mode. this means that if you are ever suspended then you will always
+// end up with a 'dirty' workspace. not a big deal considering how infrequently
+// quit occurs in server mode.
+// TODO: this now affects switching projects after a suspend. we should try
+// to figure out how to preserve dirty state of the workspace accross suspend
+int s_lastSaveAction = r::session::kSaveActionAsk;
+
+const char * const kSaveActionState = "saveActionState";
+const char * const kImageDirtyState = "imageDirtyState";
+
+void enqueSaveActionChanged()
+{
+ json::Object saveAction;
+ saveAction["action"] = s_lastSaveAction;
+ ClientEvent event(client_events::kSaveActionChanged, saveAction);
+ module_context::enqueClientEvent(event);
+}
+
+void checkForSaveActionChanged()
+{
+ // compute current save action
+ int currentSaveAction = r::session::imageIsDirty() ?
+ module_context::saveWorkspaceAction() :
+ r::session::kSaveActionNoSave;
+
+ // compare and fire event if necessary
+ if (s_lastSaveAction != currentSaveAction)
+ {
+ s_lastSaveAction = currentSaveAction;
+ enqueSaveActionChanged();
+ }
+}
+
+void onSuspend(const r::session::RSuspendOptions&, Settings* pSettings)
+{
+ pSettings->set(kSaveActionState, s_lastSaveAction);
+ pSettings->set(kImageDirtyState, r::session::imageIsDirty());
+}
+
+void onResume(const Settings& settings)
+{
+ s_lastSaveAction = settings.getInt(kSaveActionState,
+ r::session::kSaveActionAsk);
+
+ r::session::setImageDirty(settings.getBool(kImageDirtyState, true));
+
+ enqueSaveActionChanged();
+}
+
+void onClientInit()
+{
+ // enque save action changed
+ enqueSaveActionChanged();
+}
+
+void onDetectChanges(module_context::ChangeSource source)
+{
+ // check for save action changed
+ checkForSaveActionChanged();
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // add suspend handler
+ using namespace session::module_context;
+ addSuspendHandler(SuspendHandler(onSuspend, onResume));
+
+ // subscribe to events
+ using boost::bind;
+ events().onClientInit.connect(bind(onClientInit));
+ events().onDetectChanges.connect(bind(onDetectChanges, _1));
+
+ return Success();
+}
+
+
+} // namepsace dirty
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionDirty.hpp b/src/cpp/session/modules/SessionDirty.hpp
new file mode 100644
index 0000000..03554ff
--- /dev/null
+++ b/src/cpp/session/modules/SessionDirty.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionDirty.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_DIRTY_HPP
+#define SESSION_SESSION_DIRTY_HPP
+
+namespace core {
+ class Error ;
+}
+
+namespace session {
+namespace modules {
+namespace dirty {
+
+core::Error initialize();
+
+} // namespace dirty
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SESSION_DIRTY_HPP
diff --git a/src/cpp/session/modules/SessionEnvironment.R b/src/cpp/session/modules/SessionEnvironment.R
new file mode 100644
index 0000000..c21aaf0
--- /dev/null
+++ b/src/cpp/session/modules/SessionEnvironment.R
@@ -0,0 +1,578 @@
+#
+# SessionEnvironment.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+.rs.addFunction("valueAsString", function(val)
+{
+ tryCatch(
+ {
+ is.scalarOrVector <- function (x) {
+ if (is.null(attributes(x)))
+ {
+ !is.na(c(NULL=TRUE,
+ logical=TRUE,
+ double=TRUE,
+ integer=TRUE,
+ complex=TRUE,
+ character=TRUE)[typeof(x)])
+ }
+ else
+ {
+ FALSE
+ }
+ }
+
+ if (is.scalarOrVector(val))
+ {
+ if (length(val) == 0)
+ return (paste(.rs.getSingleClass(val), " (empty)"))
+ if (length(val) == 1)
+ {
+ if (nchar(val) < 1024)
+ return (deparse(val))
+ else
+ return (paste(substr(val, 1, 1024), " ..."))
+ }
+ else if (length(val) > 1)
+ return (capture.output(str(val)))
+ else
+ return ("NO_VALUE")
+ }
+ else if (.rs.isFunction(val))
+ return (.rs.getSignature(val))
+ else if (is(val, "Date"))
+ return (format(val))
+ else
+ return ("NO_VALUE")
+ },
+ error = function(e)
+ {
+ # Don't print errors--they'll appear in the R console. Instead, let the
+ # client deal with errors by handling the special value NO_VALUE.
+ })
+
+ return ("NO_VALUE")
+})
+
+.rs.addFunction("valueContents", function(val)
+{
+ tryCatch(
+ {
+ # only return the first 100 lines of detail (generally columns)--any more
+ # won't be very presentable in the environment pane. the first line
+ # generally contains descriptive text, so don't return that.
+ output <- capture.output(str(val))
+ return (output[min(length(output), 2):min(length(output),100)])
+ },
+ error = function(e) { })
+
+ return ("NO_VALUE")
+})
+
+.rs.addFunction("isFunction", function(val)
+{
+ is.function(val) || identical(.rs.getSingleClass(val), "C++Function")
+})
+
+# used to create description for promises
+.rs.addFunction("promiseDescription", function(obj)
+{
+ # by default, the description should be the expression associated with the
+ # object
+ description <- paste(deparse(substitute(obj)), collapse="")
+
+ # create a more friendly description for delay-loaded data
+ if (substr(description, 1, 16) == "lazyLoadDBfetch(")
+ {
+ description <- "<Promise>"
+ }
+ return (description)
+})
+
+# used to create descriptions for language objects and symbols
+.rs.addFunction("languageDescription", function(env, objectName)
+{
+ desc <- "Missing object"
+ tryCatch(
+ {
+ desc <- capture.output(print(get(objectName, env)))
+ },
+ error = function(e)
+ {
+ # silently ignore; if the object can't be retrieved from the
+ # environment, treat it as missing
+ },
+ finally =
+ {
+ return(desc)
+ })
+})
+
+.rs.addFunction("sourceFileFromRef", function(srcref)
+{
+ if (!is.null(srcref))
+ {
+ fileattr <- attr(srcref, "srcfile")
+ fileattr$filename
+ }
+ else
+ ""
+})
+
+.rs.addFunction("sourceCodeFromFunction", function(fun)
+{
+ if (is.null(attr(fun, "srcref")))
+ {
+ # The function does not have source refs, so deparse it to get a
+ # formatted representation.
+ paste(deparse(fun), collapse="\n")
+ }
+ else
+ {
+ # The function has source refs; use them to get exactly the code that
+ # was used to create the function.
+ paste(capture.output(attr(fun, "srcref")), collapse="\n")
+ }
+})
+
+# Given a function and some content inside that function, returns a vector
+# in the standard R source reference format that represents the location of
+# the content in the deparsed representation of the function.
+.rs.addFunction("simulateSourceRefs", function(var)
+{
+ # Read arguments from attached attributes to the input (these can't be passed
+ # naked from RStudio)
+ fun <- attr(var, "_rs_callfun")
+ call <- attr(var, "_rs_callobj")
+ calltext <- attr(var, "_rs_calltext")
+ linepref <- attr(var, "_rs_lastline")
+
+ # To proceed, we need the function to look in, and either the raw call
+ # object (which we will deparse later) or the text to look for.
+ if (is.null(fun) ||
+ (is.null(call) && is.null(calltext)) )
+ return(c(0L, 0L, 0L, 0L, 0L, 0L))
+
+ lines <- deparse(fun)
+
+ # Remember the indentation level on each line (added by deparse), and remove
+ # it along with any other leading or trailing whitespace.
+ indents <- nchar(sub("\\S.*", "", lines))
+ slines <- sub("\\s+$", "", sub("^\\s+", "", lines))
+
+ # Compute the character position of the start of each line, and collapse the
+ # lines to a character vector of length 1.
+ nchars <- 0
+ offsets <- integer(length(slines))
+ for (i in 1:length(slines)) {
+ nchars <- nchars + nchar(slines[i]) + 1
+ offsets[i] <- nchars
+ }
+ singleline <- paste(slines, collapse=" ")
+
+ if (is.null(calltext))
+ {
+ # No call text specified; deparse into a list of lines
+ calltext <- deparse(call)
+ }
+ else
+ {
+ # Call text specified as a single character vector; split into a list
+ # of lines
+ calltext <- unlist(strsplit(calltext, "\n", fixed = TRUE))
+ }
+
+ calltext <- sub("\\s+$", "", sub("^\\s+", "", calltext))
+ calltext <- paste(calltext, collapse=" ")
+
+ # NULL is output by R when it doesn't have an expression to output; don't
+ # try to match it to code
+ if (identical(calltext, "NULL"))
+ return(c(0L, 0L, 0L, 0L, 0L, 0L))
+
+ pos <- gregexpr(calltext, singleline, fixed = TRUE)[[1]]
+ if (length(pos) > 1)
+ {
+ # There is more than one instance of the call text in the function; try
+ # to pick the first match past the preferred line.
+ best <- which(pos > offsets[linepref])
+ if (length(best) == 0)
+ {
+ # No match past the preferred line, just pick the match closest
+ best <- which.min(abs(linepref - pos))
+ }
+ else
+ best <- best[1]
+ endpos <- pos[best] + attr(pos, "match.length")[best]
+ pos <- pos[best]
+ }
+ else
+ {
+ endpos <- pos + attr(pos, "match.length")
+ }
+
+
+ # Return an empty source ref if we couldn't find a match
+ if (length(pos) == 0 || pos < 0)
+ return(c(0L, 0L, 0L, 0L, 0L, 0L))
+
+ # Compute the starting and ending lines
+ firstline <- which(offsets >= pos, arr.ind = TRUE)[1]
+ lastline <- which(offsets >= endpos, arr.ind = TRUE)[1]
+ if (is.na(lastline))
+ lastline <- length(offsets)
+
+ # Compute the starting and ending character positions within the line,
+ # taking into account the indents we removed earlier.
+ firstchar <- pos - (if (firstline == 1) 0 else offsets[firstline - 1])
+ firstchar <- firstchar + indents[firstline]
+
+ # If the match is a block ({ ... }) and contains more than a few lines,
+ # match the first line instead of the whole block; having the entire contents
+ # of the code browser highlighted is not useful.
+ if (substr(calltext, 1, 1) == "{" &&
+ substr(calltext, nchar(calltext), nchar(calltext)) == "}" &&
+ lastline - firstline > 5)
+ {
+ lastline <- firstline
+ lastchar <- offsets[firstline] - pos
+ }
+ else
+ {
+ lastchar <- endpos - (if (lastline == 1) 0 else offsets[lastline - 1])
+ lastchar <- lastchar + indents[lastline]
+ }
+
+ result <- as.integer(c(firstline, firstchar, lastline,
+ lastchar, firstchar, lastchar))
+ return(result)
+})
+
+.rs.addFunction("functionNameFromCall", function(val)
+{
+ call <- attr(val, "_rs_call")
+ if (is.function(call[[1]]))
+ "[Anonymous function]"
+ else
+ as.character(substitute(call))
+})
+
+.rs.addFunction("argumentListSummary", function(args)
+{
+ return(paste(lapply(args, function(arg) {
+ if (is.language(arg))
+ capture.output(print(arg))
+ else if (is.environment(arg) ||
+ is.function(arg))
+ deparse(substitute(arg))
+ else
+ as.character(arg)
+ }), collapse = ", "))
+})
+
+.rs.addFunction("valueDescription", function(obj)
+{
+ tryCatch(
+ {
+ if (missing(obj))
+ {
+ return("Missing argument")
+ }
+ else if (is(obj, "ore.frame"))
+ {
+ return(paste(ncol(obj),"columns"))
+ }
+ else if (is(obj, "externalptr"))
+ {
+ return("External pointer")
+ }
+ else if (is.data.frame(obj))
+ {
+ return(paste(dim(obj)[1],
+ "obs. of",
+ dim(obj)[2],
+ "variables",
+ sep=" "))
+ }
+ else if (is.environment(obj))
+ {
+ return("Environment")
+ }
+ else if (isS4(obj))
+ {
+ return(paste("Formal class ", is(obj)))
+ }
+ else if (is.list(obj))
+ {
+ return(paste("List of ", length(obj)))
+ }
+ else if (is.matrix(obj)
+ || is.numeric(obj)
+ || is.factor(obj)
+ || is.raw(obj))
+ {
+ return(capture.output(str(obj)))
+ }
+ else
+ return("")
+ },
+ error = function(e) print(e))
+
+ return ("")
+})
+
+
+.rs.addFunction("registerFunctionEditor", function() {
+
+ # save default editor
+ defaultEditor <- getOption("editor")
+
+ # ensure we have a scratch file
+ scratchFile <- tempfile()
+ cat("", file = scratchFile)
+
+ options(editor = function(name, file, title) {
+
+ # use internal editor for files and functions, otherwise
+ # delegate to the default editor
+ if (is.null(name) || is.function(name)) {
+
+ # if no name then use file
+ if (is.null(name)) {
+ if (!is.null(file) && nzchar(file))
+ targetFile <- file
+ else
+ targetFile <- scratchFile
+ }
+ # otherwise it's a function, write it to a file for editing
+ else {
+ functionSrc <- .rs.deparseFunction(name, TRUE)
+ targetFile <- scratchFile
+ writeLines(functionSrc, targetFile)
+ }
+
+ # invoke the RStudio editor on the file
+ if (.Call("rs_editFile", targetFile)) {
+
+ # try to parse it back in
+ newFunc <- try(eval.parent(parse(targetFile)),
+ silent = TRUE)
+ if (inherits(newFunc, "try-error")) {
+ stop(newFunc, "You can attempt to correct the error using ",
+ title, " = edit()")
+ }
+
+ return(newFunc)
+ }
+ else {
+ stop("Error occurred while editing function '", name, "'")
+ }
+ }
+ else
+ edit(name, file, title, editor=defaultEditor)
+ })
+})
+
+
+.rs.addFunction("getSingleClass", function(obj)
+{
+ className <- "(unknown)"
+ tryCatch(className <- class(obj)[1],
+ error = function(e) print(e))
+ return (className)
+})
+
+.rs.addFunction("describeObject", function(env, objName)
+{
+ obj <- get(objName, env)
+ val <- "(unknown)"
+ desc <- ""
+ size <- object.size(obj)
+ len <- length(obj)
+ class <- .rs.getSingleClass(obj)
+ contents <- list()
+ contents_deferred <- FALSE
+ # for language objects, don't evaluate, just show the expression
+ if (is.language(obj) || is.symbol(obj))
+ {
+ val <- deparse(obj)
+ }
+ else
+ {
+ # for large objects (> half MB), don't try to get the value, just show
+ # the size. Some functions (e.g. str()) can cause the object to be
+ # copied, which is slow for large objects.
+ if (size > 524288)
+ {
+ len <- if (len > 1)
+ paste(len, " elements, ", sep="")
+ else
+ ""
+ # data frames are likely to be large, but a summary is still helpful
+ if (is.data.frame(obj))
+ {
+ val <- "NO_VALUE"
+ desc <- .rs.valueDescription(obj)
+ }
+ else
+ {
+ val <- paste("Large ", class, " (", len,
+ capture.output(print(size, units="auto")), ")", sep="")
+ }
+ contents_deferred <- TRUE
+ }
+ else
+ {
+ val <- .rs.valueAsString(obj)
+ desc <- .rs.valueDescription(obj)
+
+ # expandable object--supply contents
+ if (class == "data.table" ||
+ class == "cast_df" ||
+ class == "xts" ||
+ is.list(obj) ||
+ is.data.frame(obj) ||
+ isS4(obj))
+ {
+ contents <- .rs.valueContents(obj)
+ }
+ }
+ }
+ list (
+ name = .rs.scalar(objName),
+ type = .rs.scalar(class),
+ is_data = .rs.scalar(is.data.frame(obj)),
+ value = .rs.scalar(val),
+ description = .rs.scalar(desc),
+ size = .rs.scalar(size),
+ length = .rs.scalar(length(obj)),
+ contents = contents,
+ contents_deferred = .rs.scalar(contents_deferred))
+})
+
+# returns the name and frame number of an environment from a call frame
+.rs.addFunction("environmentCallFrameName", function(env)
+{
+ numCalls <- length(sys.calls())
+ result <- list()
+ for (i in 1:numCalls)
+ {
+ if (identical(sys.frame(i), env))
+ {
+ calldesc <- paste(deparse(sys.call(i)[[1]]), "()", sep="")
+ result <- list(name = .rs.scalar(calldesc),
+ frame = .rs.scalar(i),
+ local = .rs.scalar(TRUE))
+ break
+ }
+ }
+ if (identical(result, list()))
+ list(name = .rs.scalar("unknown"), frame = .rs.scalar(0L))
+ else
+ result
+})
+
+# indicate whether the given environment is local (i.e. it comes before the
+# global environment in the search path)
+.rs.addFunction("environmentIsLocal", function(env)
+{
+ while (!identical(env, emptyenv()))
+ {
+ # if one of this environment's parents is the global environment, it's
+ # local
+ env = parent.env(env)
+ if (identical(env, globalenv()))
+ return(TRUE)
+ }
+ return(FALSE)
+})
+
+.rs.addFunction("environmentName", function(env)
+{
+ # look for the environment's given name; if it doesn't have a name, check
+ # the callstack to see if it matches the environment in one of the call
+ # frames.
+ result <- environmentName(env)
+ if (nchar(result) == 0)
+ .rs.environmentCallFrameName(env)$name
+ else
+ result
+})
+
+.rs.addFunction("environmentList", function(startEnv)
+{
+ env <- startEnv
+ envs <- list()
+ local <- TRUE
+ # if starting above the global environment, the environments will be
+ # unnamed. to provide sensible names for them, look for a matching frame in
+ # the callstack.
+ if (!identical(env, globalenv()))
+ {
+ while (!identical(env, globalenv()) &&
+ !identical(env, emptyenv()))
+ {
+ frame <- .rs.environmentCallFrameName(env)
+ # if this frame is from the callstack, store it and proceed
+ if (frame$frame > 0)
+ {
+ envs[[length(envs)+1]] <- frame
+ env <- parent.env(env)
+ }
+ # otherwise, stop here and get names normally
+ else
+ break
+ }
+ }
+ # we're now past the call-frame portion of the stack; proceed normally
+ # through the rest of the search path.
+ while (!identical(env, emptyenv()))
+ {
+ # mark all environments as local until we reach the global
+ # environment
+ if (identical(env, globalenv()))
+ local <- FALSE
+
+ envName <- environmentName(env)
+
+ # hide the RStudio internal tools environment and the autoloads
+ # environment, and any environment that doesn't have a name
+ if (nchar(envName) > 0 &&
+ envName != "tools:rstudio" &&
+ envName != "Autoloads")
+ {
+ envs[[length(envs)+1]] <-
+ list (name = .rs.scalar(envName),
+ frame = .rs.scalar(0L),
+ local = .rs.scalar(local))
+ }
+ env <- parent.env(env)
+ }
+ envs
+})
+
+.rs.addFunction("removeObjects", function(objNames, env)
+{
+ remove(list=unlist(objNames), envir=env)
+})
+
+.rs.addFunction("removeAllObjects", function(includeHidden, env)
+{
+ rm(list=ls(envir=env, all.names=includeHidden), envir=env)
+})
+
+.rs.addFunction("getObjectContents", function(objName, env)
+{
+ .rs.valueContents(get(objName, env));
+})
+
diff --git a/src/cpp/session/modules/SessionErrors.R b/src/cpp/session/modules/SessionErrors.R
new file mode 100644
index 0000000..78afd84
--- /dev/null
+++ b/src/cpp/session/modules/SessionErrors.R
@@ -0,0 +1,189 @@
+#
+# SessionErrors.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("isSourceCall", function(call)
+{
+ fun <- deparse(call[[1]])
+ return (fun == "source" ||
+ fun == "debugSource" ||
+ fun == "source.with.encoding")
+})
+
+.rs.addFunction("recordTraceback", function(userOnly)
+{
+ calls <- sys.calls()
+ foundUserCode <- FALSE
+ inSource <- FALSE
+
+ # When this handler is invoked for an unhandled error happening at
+ # the top level, there are four calls on the stack:
+ # 1. This function
+ # 2. The anonymous error handler (set via options below)
+ # 3. The error invoker (e.g. stop)
+ # 4. The function from which the error was raised
+ # So we want there to be at least 5 calls on the stack--otherwise the error
+ # is likely to be top-level.
+ if (length(calls) < 5)
+ return()
+
+ # create the traceback for the client
+ stack <- lapply(calls[1:(length(calls) - 2)], function(call)
+ {
+ # don't show debugger-hidden functions
+ if (isTRUE(attr(call[[1]], "hideFromDebugger")))
+ return(NULL)
+
+ # we want to ignore the first user code entry after a call to source(),
+ # since that call happens at the top level
+ isSourceCall <- FALSE
+ if (.rs.isSourceCall(call))
+ {
+ isSourceCall <- TRUE
+ inSource <<- TRUE
+ }
+ srcref <- attr(call, "srcref")
+ srcfile <- ""
+ if (!is.null(srcref))
+ {
+ fileattr <- attr(srcref, "srcfile")
+ srcfile <- fileattr$filename
+ if (!is.null(srcfile))
+ {
+ if (inSource && !isSourceCall)
+ inSource <<- FALSE
+ else
+ foundUserCode <<- TRUE
+ }
+ }
+ else
+ srcref <- rep(0L, 8)
+
+ # don't display more than 4 lines of a long expression
+ lines <- deparse(call)
+ if (length(lines) > 4)
+ {
+ lines <- lines[1:4]
+ lines[4] <- paste(lines[4], "...")
+ }
+
+ c (list(func = .rs.scalar(paste(lines, collapse="\n")),
+ file = .rs.scalar(srcfile)),
+ .rs.lineDataList(srcref))
+ })
+
+ # remove hidden entries from the stack
+ stack <- stack[!sapply(stack, is.null)]
+
+ # if we found user code (or weren't looking for it), tell the client
+ if (foundUserCode || !userOnly)
+ {
+ event <- list(
+ frames = stack,
+ message = .rs.scalar(geterrmessage()))
+ .rs.enqueClientEvent("unhandled_error", event)
+ }
+})
+
+.rs.addFunction("breakOnError", function(userOnly)
+{
+ calls <- sys.calls()
+ if (length(calls) < 5)
+ return()
+
+ foundUserCode <- FALSE
+ inSource <- FALSE
+ if (userOnly)
+ {
+ for (n in 1:(length(calls) - 1))
+ {
+ isSourceCall <- FALSE
+ if (.rs.isSourceCall(sys.call(n)))
+ {
+ isSourceCall <- TRUE
+ inSource <- TRUE
+ }
+ func <- .rs.untraced(sys.function(n))
+ srcref <- attr(func, "srcref")
+ if (!is.null(srcref) &&
+ !is.null(attr(srcref, "srcfile")))
+ {
+ if (inSource && !isSourceCall)
+ {
+ inSource <- FALSE
+ }
+ else
+ {
+ # looks like non-top-level user code--invoke the browser below
+ foundUserCode <- TRUE
+ break
+ }
+ }
+ }
+ }
+ if (foundUserCode || !userOnly)
+ {
+ # The magic values 3 and 9 here are derived from the position in the
+ # stack where this error handler resides relative to where we expect
+ # the user code that raised the error to be. These will need to be
+ # adjusted if evaluation layers are added or removed between the
+ # root error handler (set in options(error=...)) and this function.
+ frame <- length(sys.frames()) - 3
+ eval(substitute(browser(skipCalls = pos), list(pos = 9 - frame)),
+ envir = sys.frame(frame))
+ }
+},
+attrs = list(hideFromDebugger = TRUE))
+
+.rs.addFunction("recordAnyTraceback", function()
+{
+ .rs.recordTraceback(FALSE)
+},
+attrs = list(hideFromDebugger = TRUE,
+ errorHandlerType = 1L))
+
+.rs.addFunction("recordUserTraceback", function()
+{
+ .rs.recordTraceback(TRUE)
+},
+attrs = list(hideFromDebugger = TRUE,
+ errorHandlerType = 1L))
+
+.rs.addFunction("breakOnAnyError", function()
+{
+ .rs.breakOnError(FALSE)
+},
+attrs = list(hideFromDebugger = TRUE,
+ errorHandlerType = 2L))
+
+.rs.addFunction("breakOnUserError", function()
+{
+ .rs.breakOnError(TRUE)
+},
+attrs = list(hideFromDebugger = TRUE,
+ errorHandlerType = 2L))
+
+.rs.addFunction("setErrorManagementType", function(type, userOnly)
+{
+ if (type == 0)
+ options(error = NULL)
+ else if (type == 1 && userOnly)
+ options(error = .rs.recordUserTraceback)
+ else if (type == 1 && !userOnly)
+ options(error = .rs.recordAnyTraceback)
+ else if (type == 2 && userOnly)
+ options(error = .rs.breakOnUserError)
+ else if (type == 2 && !userOnly)
+ options(error = .rs.breakOnAnyError)
+})
diff --git a/src/cpp/session/modules/SessionErrors.cpp b/src/cpp/session/modules/SessionErrors.cpp
new file mode 100644
index 0000000..5d8db71
--- /dev/null
+++ b/src/cpp/session/modules/SessionErrors.cpp
@@ -0,0 +1,223 @@
+/*
+ * SessionErrors.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <algorithm>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/json/JsonRpc.hpp>
+
+#include <r/RExec.hpp>
+#include <r/ROptions.hpp>
+
+#include <boost/bind.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+#include "SessionErrors.hpp"
+#include "SessionBreakpoints.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace errors {
+namespace {
+
+void enqueErrorHandlerChanged(int type)
+{
+ json::Object errorHandlerType;
+ errorHandlerType["type"] = type;
+ ClientEvent errorHandlerChanged(
+ client_events::kErrorHandlerChanged, errorHandlerType);
+ module_context::enqueClientEvent(errorHandlerChanged);
+}
+
+Error setErrHandler(int type, bool inMyCode,
+ boost::shared_ptr<SEXP> pErrorHandler)
+{
+ // when setting the error handler to "custom", just leave it as it was
+ if (type == ERRORS_CUSTOM)
+ return Success();
+
+ // this feature requires the source reference attribute; don't try to set
+ // the error handler if we don't have that.
+ if (!breakpoints::haveSrcrefAttribute())
+ return Success();
+
+ Error error = r::exec::RFunction(
+ ".rs.setErrorManagementType", type, inMyCode)
+ .callUnsafe();
+ if (error)
+ return error;
+
+ *pErrorHandler = r::options::getOption("error");
+ return Success();
+}
+
+Error setErrHandlerType(int type,
+ boost::shared_ptr<SEXP> pErrorHandler)
+{
+ Error error = setErrHandler(type,
+ userSettings().handleErrorsInUserCodeOnly(),
+ pErrorHandler);
+ if (error)
+ return error;
+
+ userSettings().setErrorHandlerType(type);
+ enqueErrorHandlerChanged(type);
+ return Success();
+}
+
+Error setErrHandlerType(boost::shared_ptr<SEXP> pErrorHandler,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ int type = 0;
+ Error error = json::readParams(request.params, &type);
+ if (error)
+ return error;
+
+ return setErrHandlerType(type, pErrorHandler);
+}
+
+Error initializeErrManagement(boost::shared_ptr<SEXP> pErrorHandler,
+ boost::shared_ptr<bool> pHandleUserErrorsOnly)
+{
+ SEXP currentHandler = r::options::getOption("error");
+ *pHandleUserErrorsOnly = userSettings().handleErrorsInUserCodeOnly();
+ // This runs after ~/.RProfile, so don't change the error handler if
+ // there's already one assigned, or if we're aware of a custom error
+ // handler.
+ if (currentHandler == R_NilValue &&
+ userSettings().errorHandlerType() != ERRORS_CUSTOM)
+ setErrHandlerType(userSettings().errorHandlerType(), pErrorHandler);
+ else
+ userSettings().setErrorHandlerType(ERRORS_CUSTOM);
+ return Success();
+}
+
+// Detect whether the error handler has changed, and optionally record the
+// change as a permanent. (We don't make changes permanent when they're
+// detected during a session.)
+void detectHandlerChange(boost::shared_ptr<SEXP> pErrorHandler,
+ bool recordSetting)
+{
+ // check to see if the error option has been changed from beneath us; if
+ // it has, emit a client event so the client doesn't show an incorrect
+ // error handler
+ SEXP currentHandler = r::options::getOption("error");
+ if (currentHandler != *pErrorHandler)
+ {
+ int handlerType = -1;
+ if (currentHandler != R_NilValue &&
+ r::sexp::isLanguage(currentHandler))
+ {
+ // it's possible for the SEXP to change (it's a pointer) even though
+ // the handler is correct; check the attribute of the function invoked
+ // by the handler and compare to the user preference.
+ SEXP fun = CAR(currentHandler);
+ SEXP typeSEXP = r::sexp::getAttrib(fun, "errorHandlerType");
+ if (typeSEXP != NULL && !r::sexp::isNull(typeSEXP))
+ {
+ Error error = r::sexp::extract(typeSEXP, &handlerType);
+ if (!error && handlerType == userSettings().errorHandlerType())
+ {
+ // the SEXP is different but the attribute matches; update our
+ // SEXP so we don't keep detecting a change
+ *pErrorHandler = currentHandler;
+ return;
+ }
+ }
+ }
+ *pErrorHandler = currentHandler;
+
+ // attempt to figure out what error handler type is in use, if any
+ if (handlerType < 0)
+ {
+ handlerType = (currentHandler == R_NilValue) ?
+ ERRORS_MESSAGE :
+ ERRORS_CUSTOM;
+ }
+ if (recordSetting)
+ userSettings().setErrorHandlerType(handlerType);
+ enqueErrorHandlerChanged(handlerType);
+ }
+}
+
+void onUserSettingsChanged(boost::shared_ptr<SEXP> pErrorHandler,
+ boost::shared_ptr<bool> pHandleUserErrorsOnly)
+{
+ // check to see if the setting for 'handle errors in user code only'
+ // has been changed since we last looked at it; if it has, switch the
+ // error handler in a corresponding way
+ bool handleUserErrorsOnly = userSettings().handleErrorsInUserCodeOnly();
+ if (handleUserErrorsOnly != *pHandleUserErrorsOnly)
+ {
+ Error error = setErrHandler(userSettings().errorHandlerType(),
+ handleUserErrorsOnly,
+ pErrorHandler);
+ if (error)
+ LOG_ERROR(error);
+
+ *pHandleUserErrorsOnly = handleUserErrorsOnly;
+ }
+}
+
+} // anonymous namespace
+
+json::Value errorStateAsJson()
+{
+ json::Object state;
+ state["error_handler_type"] = userSettings().errorHandlerType();
+ return state;
+}
+
+Error initialize()
+{
+ boost::shared_ptr<SEXP> pErrorHandler =
+ boost::make_shared<SEXP>(R_NilValue);
+ boost::shared_ptr<bool> pHandleUserErrorsOnly =
+ boost::make_shared<bool>(true);
+
+ using boost::bind;
+ using namespace module_context;
+
+ // Check to see whether the error handler has changed immediately after init
+ // (to find changes from e.g. .Rprofile) and after every console prompt.
+ events().onConsolePrompt.connect(bind(detectHandlerChange,
+ pErrorHandler, false));
+ events().onDeferredInit.connect(bind(detectHandlerChange,
+ pErrorHandler, true));
+ userSettings().onChanged.connect(bind(onUserSettingsChanged,
+ pErrorHandler,
+ pHandleUserErrorsOnly));
+
+ json::JsonRpcFunction setErrMgmt =
+ bind(setErrHandlerType, pErrorHandler, _1, _2);
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "set_error_management_type", setErrMgmt))
+ (bind(sourceModuleRFile, "SessionErrors.R"))
+ (bind(initializeErrManagement, pErrorHandler, pHandleUserErrorsOnly));
+
+ return initBlock.execute();
+}
+
+} // namespace errors
+} // namespace modules
+} // namespace session
+
+
diff --git a/src/cpp/session/modules/SessionErrors.hpp b/src/cpp/session/modules/SessionErrors.hpp
new file mode 100644
index 0000000..51997fd
--- /dev/null
+++ b/src/cpp/session/modules/SessionErrors.hpp
@@ -0,0 +1,44 @@
+/*
+ * SessionErrors.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSIONERRORS_HPP
+#define SESSIONERRORS_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace errors {
+
+// Error handler types understood by the client. The client has its own copy
+// of this enum, and its values are persisted in user settings, so the meaning
+// of these values must be preserved.
+const int ERRORS_MESSAGE = 0;
+const int ERRORS_TRACEBACK = 1;
+const int ERRORS_BREAK = 2;
+const int ERRORS_CUSTOM = 3;
+
+core::Error initialize();
+core::json::Value errorStateAsJson();
+
+} // namespace errors
+} // namepace modules
+} // namesapce session
+
+#endif // SESSIONERRORS_HPP
diff --git a/src/cpp/session/modules/SessionFiles.R b/src/cpp/session/modules/SessionFiles.R
new file mode 100644
index 0000000..056ff27
--- /dev/null
+++ b/src/cpp/session/modules/SessionFiles.R
@@ -0,0 +1,39 @@
+#
+# SessionFiles.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("listZipFile", function(zipfile)
+{
+ as.character(utils::unzip(zipfile, list=TRUE)$Name)
+})
+
+
+.rs.addFunction("createZipFile", function(zipfile, parent, files)
+{
+ # set working dir to parent (and restore on exit)
+ previous_wd = getwd()
+ setwd(parent)
+ on.exit(setwd(previous_wd))
+
+ # build zip command
+ quotedFiles = paste(shQuote(files), collapse = " ")
+ createZipCommand = paste("zip", "-r", shQuote(zipfile), quotedFiles, "2>&1")
+
+ # execute the command (return output of command)
+ system(createZipCommand, intern = TRUE)
+})
+
+.rs.addJsonRpcHandler("list_all_files", function(path, pattern) {
+ list.files(path, pattern=pattern, recursive=T)
+})
diff --git a/src/cpp/session/modules/SessionFiles.cpp b/src/cpp/session/modules/SessionFiles.cpp
new file mode 100644
index 0000000..2937312
--- /dev/null
+++ b/src/cpp/session/modules/SessionFiles.cpp
@@ -0,0 +1,919 @@
+/*
+ * SessionFiles.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionFiles.hpp"
+
+#include <csignal>
+
+#include <vector>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <algorithm>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileInfo.hpp>
+#include <core/Settings.hpp>
+#include <core/Exec.hpp>
+#include <core/DateTime.hpp>
+
+#include <core/http/Util.hpp>
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <core/system/ShellUtils.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/RecycleBin.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RErrorCategory.hpp>
+
+#include <session/SessionClientEvent.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionOptions.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionFilesQuotas.hpp"
+#include "SessionFilesListingMonitor.hpp"
+
+using namespace core ;
+
+namespace session {
+
+namespace modules {
+namespace files {
+
+namespace {
+
+// monitor for file listings
+FilesListingMonitor s_filesListingMonitor;
+
+// make sure that monitoring persists accross suspended sessions
+const char * const kMonitoredPath = "files.monitored-path";
+
+void onSuspend(Settings* pSettings)
+{
+ // get monitored path and alias it
+ std::string monitoredPath = s_filesListingMonitor.currentMonitoredPath().absolutePath();
+ if (!monitoredPath.empty())
+ {
+ monitoredPath = FilePath::createAliasedPath(FilePath(monitoredPath),
+ module_context::userHomePath());
+ }
+
+ // set it
+ pSettings->set(kMonitoredPath, monitoredPath);
+}
+
+void onResume(const Settings& settings)
+{
+ // get the monitored path
+ std::string monitoredPath = settings.get(kMonitoredPath);
+ if (!monitoredPath.empty())
+ {
+ // resolve aliases
+ FilePath resolvedPath = FilePath::resolveAliasedPath(
+ monitoredPath,
+ module_context::userHomePath());
+
+ // start monitoriing
+ json::Array jsonFiles;
+ s_filesListingMonitor.start(resolvedPath, &jsonFiles);
+ }
+
+ quotas::checkQuotaStatus();
+}
+
+void onClientInit()
+{
+ quotas::checkQuotaStatus();
+}
+
+
+// extract a set of FilePath object from a list of home path relative strings
+Error extractFilePaths(const json::Array& files,
+ std::vector<FilePath>* pFilePaths)
+{
+ for(json::Array::const_iterator
+ it = files.begin();
+ it != files.end();
+ ++it)
+ {
+ if (it->type() != json::StringType)
+ return Error(json::errc::ParamTypeMismatch, ERROR_LOCATION);
+
+ std::string file = it->get_str() ;
+ pFilePaths->push_back(module_context::resolveAliasedPath(file)) ;
+ }
+
+ return Success() ;
+}
+
+core::Error stat(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ Error error = json::readParams(request.params, &path);
+ if (error)
+ return error;
+
+ FilePath targetPath = module_context::resolveAliasedPath(path);
+
+ pResponse->setResult(module_context::createFileSystemItem(targetPath));
+ return Success();
+}
+
+core::Error isTextFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ Error error = json::readParams(request.params, &path);
+ if (error)
+ return error;
+
+ FilePath targetPath = module_context::resolveAliasedPath(path);
+
+ pResponse->setResult(module_context::isTextFile(targetPath));
+
+ return Success();
+}
+
+
+Error listFiles(const json::JsonRpcRequest& request, json::JsonRpcResponse* pResponse)
+{
+ // get args
+ std::string path;
+ bool monitor;
+ Error error = json::readParams(request.params, &path, &monitor);
+ if (error)
+ return error;
+ FilePath targetPath = module_context::resolveAliasedPath(path) ;
+
+ // if this includes a request for monitoring
+ core::json::Array jsonFiles;
+ if (monitor)
+ {
+ // always stop existing if we have one
+ s_filesListingMonitor.stop();
+
+ // install a monitor only if we aren't already covered by the project monitor
+ if (!session::projects::projectContext().isMonitoringDirectory(targetPath))
+ {
+ error = s_filesListingMonitor.start(targetPath, &jsonFiles);
+ if (error)
+ return error;
+ }
+ else
+ {
+ error = FilesListingMonitor::listFiles(targetPath, &jsonFiles);
+ if (error)
+ return error;
+ }
+ }
+ else
+ {
+ error = FilesListingMonitor::listFiles(targetPath, &jsonFiles);
+ if (error)
+ return error;
+ }
+
+ pResponse->setResult(jsonFiles);
+ return Success();
+}
+
+
+// IN: String path
+core::Error createFolder(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error ;
+
+ // create the directory
+ FilePath folderPath = module_context::resolveAliasedPath(path) ;
+ if (folderPath.exists())
+ {
+ return fileExistsError(ERROR_LOCATION);
+ }
+ else
+ {
+ Error createError = folderPath.ensureDirectory() ;
+ if (createError)
+ return createError ;
+ }
+
+ return Success() ;
+}
+
+
+core::Error deleteFile(const FilePath& filePath)
+{
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ Error error = core::system::recycle_bin::sendTo(filePath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return filePath.remove();
+ }
+ else
+ {
+ return Success();
+ }
+ }
+ else
+ {
+ return filePath.remove();
+ }
+}
+
+// IN: Array<String> paths
+core::Error deleteFiles(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array files;
+ Error error = json::readParam(request.params, 0, &files);
+ if (error)
+ return error ;
+
+ // extract vector of FilePath
+ std::vector<FilePath> filePaths ;
+ Error extractError = extractFilePaths(files, &filePaths) ;
+ if (extractError)
+ return extractError ;
+
+ // delete each file
+ Error deleteError ;
+ for (std::vector<FilePath>::const_iterator
+ it = filePaths.begin();
+ it != filePaths.end();
+ ++it)
+ {
+ // attempt to send the file to the recycle bin
+ deleteError = deleteFile(*it);
+ if (deleteError)
+ return deleteError ;
+ }
+
+ return Success() ;
+}
+
+
+void copySourceFile(const FilePath& sourceDir,
+ const FilePath& destDir,
+ int level,
+ const FilePath& sourceFilePath)
+{
+ // compute the target path
+ std::string relativePath = sourceFilePath.relativePath(sourceDir);
+ FilePath targetPath = destDir.complete(relativePath);
+
+ // if the copy item is a directory just create it
+ if (sourceFilePath.isDirectory())
+ {
+ Error error = targetPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ }
+ // otherwise copy it
+ else
+ {
+ Error error = sourceFilePath.copy(targetPath);
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+// IN: String sourcePath, String targetPath
+Error copyFile(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string sourcePath, targetPath;
+ bool overwrite;
+ Error error = json::readParams(request.params,
+ &sourcePath,
+ &targetPath,
+ &overwrite);
+ if (error)
+ return error;
+ FilePath targetFilePath = module_context::resolveAliasedPath(targetPath);
+
+ // make sure the target path doesn't exist
+ if (targetFilePath.exists())
+ {
+ if (overwrite)
+ {
+ Error error = targetFilePath.remove();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return fileExistsError(ERROR_LOCATION);
+ }
+ }
+ else
+ {
+ return fileExistsError(ERROR_LOCATION);
+ }
+ }
+
+ // compute the source file path
+ FilePath sourceFilePath = module_context::resolveAliasedPath(sourcePath);
+
+ // copy directories recursively
+ Error copyError ;
+ if (sourceFilePath.isDirectory())
+ {
+ // create the target directory
+ Error error = targetFilePath.ensureDirectory();
+ if (error)
+ return error ;
+
+ // iterate over the source
+ copyError = sourceFilePath.childrenRecursive(
+ boost::bind(copySourceFile, sourceFilePath, targetFilePath, _1, _2));
+ }
+ else
+ {
+ copyError = sourceFilePath.copy(targetFilePath);
+ }
+
+ // check quota after copies
+ quotas::checkQuotaStatus();
+
+ // return error status
+ return copyError;
+}
+
+
+// IN: Array<String> paths, String targetPath
+Error moveFiles(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array files;
+ std::string targetPath;
+ Error error = json::readParams(request.params, &files, &targetPath);
+ if (error)
+ return error ;
+
+ // extract vector of FilePath
+ std::vector<FilePath> filePaths ;
+ Error extractError = extractFilePaths(files, &filePaths) ;
+ if (extractError)
+ return extractError ;
+
+ // create FilePath for target directory
+ FilePath targetDirPath = module_context::resolveAliasedPath(targetPath);
+ if (!targetDirPath.isDirectory())
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+
+ // move the files
+ for (std::vector<FilePath>::const_iterator
+ it = filePaths.begin();
+ it != filePaths.end();
+ ++it)
+ {
+ // move the file
+ FilePath targetPath = targetDirPath.childPath(it->filename()) ;
+ Error moveError = it->move(targetPath) ;
+ if (moveError)
+ return moveError ;
+ }
+
+ return Success() ;
+}
+
+// IN: String path, String targetPath
+core::Error renameFile(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string path, targetPath;
+ Error error = json::readParams(request.params, &path, &targetPath);
+ if (error)
+ return error ;
+
+ // if the destination already exists then send back file exists
+ FilePath destPath = module_context::resolveAliasedPath(targetPath) ;
+ if (destPath.exists())
+ return fileExistsError(ERROR_LOCATION);
+
+ // move the file
+ FilePath sourcePath = module_context::resolveAliasedPath(path);
+ Error renameError = sourcePath.move(destPath);
+ if (renameError)
+ return renameError ;
+
+ return Success() ;
+}
+
+void handleFilesRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ Options& options = session::options();
+ if (options.programMode() != kSessionProgramModeServer)
+ {
+ pResponse->setError(http::status::NotFound,
+ request.uri() + " not found");
+ return;
+ }
+
+ // get prefix and uri (strip query string)
+ std::string prefix = "/files/";
+ std::string uri = request.uri();
+ std::size_t pos = uri.find("?");
+ if (pos != std::string::npos)
+ uri.erase(pos);
+
+ // validate the uri
+ if (prefix.length() >= uri.length() || // prefix longer than uri
+ uri.find(prefix) != 0 || // uri doesn't start with prefix
+ uri.find("..") != std::string::npos) // uri has inavlid char sequence
+ {
+ pResponse->setError(http::status::NotFound,
+ request.uri() + " not found");
+ return;
+ }
+
+ // compute path to file
+ int prefixLen = prefix.length();
+ std::string relativePath = http::util::urlDecode(uri.substr(prefixLen));
+ if (relativePath.empty())
+ {
+ pResponse->setError(http::status::NotFound, request.uri() + " not found");
+ return;
+ }
+
+ // complete path to file
+ FilePath filePath = module_context::userHomePath().complete(relativePath);
+
+ // no directory listing available
+ if (filePath.isDirectory())
+ {
+ pResponse->setError(http::status::NotFound,
+ "No listing available for " + request.uri());
+ return;
+ }
+
+
+ pResponse->setNoCacheHeaders();
+ pResponse->setFile(filePath, request);
+}
+
+const char * const kUploadFilename = "filename";
+const char * const kUploadedTempFile = "uploadedTempFile";
+const char * const kUploadTargetDirectory = "targetDirectory";
+
+Error completeUpload(const core::json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ json::Object token;
+ bool commit ;
+ Error error = json::readParams(request.params, &token, &commit);
+ if (error)
+ return error ;
+
+ // parse fields out of token object
+ std::string filename, uploadedTempFile, targetDirectory;
+ error = json::readObject(token,
+ kUploadFilename, &filename,
+ kUploadedTempFile, &uploadedTempFile,
+ kUploadTargetDirectory, &targetDirectory);
+ if (error)
+ return error;
+
+ // get path to temp file
+ FilePath uploadedTempFilePath(uploadedTempFile);
+
+ // commit or cancel
+ if (commit)
+ {
+ FilePath targetDirectoryPath(targetDirectory);
+ if (uploadedTempFilePath.extensionLowerCase() == ".zip")
+ {
+ // expand the archive
+ r::exec::RFunction unzip("unzip");
+ unzip.addParam("zipfile", uploadedTempFilePath.absolutePath());
+ unzip.addParam("exdir", targetDirectoryPath.absolutePath());
+ Error unzipError = unzip.call();
+ if (unzipError)
+ return unzipError;
+
+ // remove the __MACOSX folder if it exists
+ const std::string kMacOSXFolder("__MACOSX");
+ FilePath macOSXPath = targetDirectoryPath.complete(kMacOSXFolder);
+ Error removeError = macOSXPath.removeIfExists();
+ if (removeError)
+ LOG_ERROR(removeError);
+ }
+ else
+ {
+ // calculate target path
+ FilePath targetPath = targetDirectoryPath.childPath(filename);
+
+ // remove existing target path
+ Error removeError = targetPath.removeIfExists();
+ if (removeError)
+ return removeError;
+
+ // copy the source to the destination
+ Error copyError = uploadedTempFilePath.copy(targetPath);
+ if (copyError)
+ return copyError;
+ }
+
+ // remove the uploaded temp file
+ error = uploadedTempFilePath.remove();
+ if (error)
+ LOG_ERROR(error);
+
+ // check quota after uploads
+ quotas::checkQuotaStatus();
+
+ return Success();
+ }
+ else
+ {
+ // merely log failures to remove the temp file (not critical to the user)
+ Error error = uploadedTempFilePath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+ }
+}
+
+
+Error detectZipFileOverwrites(const FilePath& uploadedZipFile,
+ const FilePath& destDir,
+ json::Array* pOverwritesJson)
+{
+ // query for all of the paths in the zip file
+ std::vector<std::string> zipFileListing;
+ r::exec::RFunction listZipFile(".rs.listZipFile",
+ uploadedZipFile.absolutePath());
+ Error unzipError = listZipFile.call(&zipFileListing);
+ if (unzipError)
+ return unzipError;
+
+ // check for overwrites
+ for (std::vector<std::string>::const_iterator
+ it = zipFileListing.begin();
+ it != zipFileListing.end();
+ ++it)
+ {
+ FilePath filePath = destDir.complete(*it);
+ if (filePath.exists())
+ pOverwritesJson->push_back(module_context::createFileSystemItem(filePath));
+ }
+
+ return Success();
+}
+
+bool validateUploadedFile(const http::File& file, http::Response* pResponse)
+{
+ // get limit
+ size_t mbLimit = session::options().limitFileUploadSizeMb();
+
+ // don't enforce if no limit specified
+ if (mbLimit <= 0)
+ return true;
+
+ // convert limit to bytes
+ size_t byteLimit = mbLimit * 1024 * 1024;
+
+ // compare to file size
+ if (file.contents.size() > byteLimit)
+ {
+ Error fileTooLargeError = systemError(boost::system::errc::file_too_large,
+ ERROR_LOCATION);
+
+ json::setJsonRpcError(fileTooLargeError, pResponse);
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void handleFileUploadRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // response content type must always be text/html to be handled
+ // properly by the browser/gwt on the client side
+ pResponse->setContentType("text/html");
+
+ // get fields
+ const http::File& file = request.uploadedFile("file");
+ std::string targetDirectory = request.formFieldValue("targetDirectory");
+
+ // first validate that we got the required fields
+ if (file.name.empty() || targetDirectory.empty())
+ {
+ json::setJsonRpcError(json::errc::ParamInvalid, pResponse);
+ return;
+ }
+
+ // now validate the file
+ if ( !validateUploadedFile(file, pResponse) )
+ return ;
+
+ // form destination path
+ FilePath destDir = module_context::resolveAliasedPath(targetDirectory);
+ FilePath destPath = destDir.childPath(file.name);
+
+ // establish whether this is a zip file and create appropriate temp file path
+ bool isZip = destPath.extensionLowerCase() == ".zip";
+ FilePath tempFilePath = module_context::tempFile("upload",
+ isZip ? "zip" : "bin");
+
+ // attempt to write the temp file
+ Error saveError = core::writeStringToFile(tempFilePath, file.contents);
+ if (saveError)
+ {
+ LOG_ERROR(saveError);
+ json::setJsonRpcError(saveError, pResponse);
+ return;
+ }
+
+ // detect any potential overwrites
+ json::Array overwritesJson;
+ if (isZip)
+ {
+ Error error = detectZipFileOverwrites(tempFilePath,
+ destDir,
+ &overwritesJson);
+ if (error)
+ {
+ LOG_ERROR(error);
+ json::setJsonRpcError(error, pResponse);
+ return;
+ }
+ }
+ else
+ {
+ if (destPath.exists())
+ overwritesJson.push_back(module_context::createFileSystemItem(destPath));
+ }
+
+ // set the upload information as the result
+ json::Object uploadTokenJson;
+ uploadTokenJson[kUploadFilename] = file.name;
+ uploadTokenJson[kUploadedTempFile] = tempFilePath.absolutePath();
+ uploadTokenJson[kUploadTargetDirectory] = destDir.absolutePath();
+ json::Object uploadJson;
+ uploadJson["token"] = uploadTokenJson;
+ uploadJson["overwrites"] = overwritesJson;
+ json::setJsonRpcResult(uploadJson, pResponse);
+}
+
+void setAttachmentResponse(const http::Request& request,
+ const std::string& filename,
+ const FilePath& attachmentPath,
+ http::Response* pResponse)
+{
+ if (request.headerValue("User-Agent").find("MSIE") == std::string::npos)
+ {
+ pResponse->setNoCacheHeaders();
+ }
+ else
+ {
+ // Can't set full no-cache headers because this breaks downloads in IE
+ pResponse->setHeader("Expires", "Fri, 01 Jan 1990 00:00:00 GMT");
+ pResponse->setHeader("Cache-Control", "private");
+ }
+ // Can't rely on "filename*" in Content-Disposition header because not all
+ // browsers support non-ASCII characters here (e.g. Safari 5.0.5). If
+ // possible, make the requesting URL contain the UTF-8 byte escaped filename
+ // as the last path element.
+ pResponse->setHeader("Content-Disposition",
+ "attachment; filename*=UTF-8''"
+ + http::util::urlEncode(filename, false));
+ pResponse->setHeader("Content-Type", "application/octet-stream");
+ pResponse->setBody(attachmentPath);
+}
+
+void handleMultipleFileExportRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // name parameter
+ std::string name = request.queryParamValue("name");
+ if (name.empty())
+ {
+ pResponse->setError(http::status::BadRequest, "name not specified");
+ return;
+ }
+
+ // parent parameter
+ std::string parent = request.queryParamValue("parent");
+ if (parent.empty())
+ {
+ pResponse->setError(http::status::BadRequest, "parent not specified");
+ return;
+ }
+ FilePath parentPath = module_context::resolveAliasedPath(parent);
+ if (!parentPath.exists())
+ {
+ pResponse->setError(http::status::BadRequest, "parent doesn't exist");
+ return;
+ }
+
+ // files parameters (paths relative to parent)
+ std::vector<std::string> files;
+ for (int i=0; ;i++)
+ {
+ // get next file (terminate when we stop finding files)
+ std::string fileParam = "file" + safe_convert::numberToString(i);
+ std::string file = request.queryParamValue(fileParam);
+ if (file.empty())
+ break;
+
+ // verify that the file exists
+ FilePath filePath = parentPath.complete(file);
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::BadRequest,
+ "file " + file + " doesn't exist");
+ return;
+ }
+
+ // add it
+ files.push_back(file);
+ }
+
+ // create the zip file
+ FilePath tempZipFilePath = module_context::tempFile("export", "zip");
+ Error error = r::exec::RFunction(".rs.createZipFile",
+ tempZipFilePath.absolutePath(),
+ parentPath.absolutePath(),
+ files).call();
+ if (error)
+ {
+ LOG_ERROR(error);
+ pResponse->setError(error);
+ return;
+ }
+
+ // return attachment
+ setAttachmentResponse(request, name, tempZipFilePath, pResponse);
+}
+
+void handleFileExportRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // see if this is a single or multiple file request
+ std::string file = request.queryParamValue("file");
+ if (!file.empty())
+ {
+ // resolve alias and ensure that it exists
+ FilePath filePath = module_context::resolveAliasedPath(file);
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::NotFound, "file doesn't exist");
+ return;
+ }
+
+ // get the name
+ std::string name = request.queryParamValue("name");
+ if (name.empty())
+ {
+ pResponse->setError(http::status::BadRequest, "name not specified");
+ return;
+ }
+
+ // download as attachment
+ setAttachmentResponse(request, name, filePath, pResponse);
+ }
+ else
+ {
+ handleMultipleFileExportRequest(request, pResponse);
+ }
+}
+
+SEXP rs_pathInfo(SEXP pathSEXP)
+{
+ try
+ {
+ // validate
+ if (r::sexp::length(pathSEXP) != 1)
+ {
+ throw r::exec::RErrorException(
+ "must pass a single file to get path info for");
+ }
+
+ std::string path;
+ Error error = r::sexp::extract(pathSEXP, &path);
+ if (error)
+ throw r::exec::RErrorException(r::endUserErrorMessage(error));
+
+ // resolve aliased path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+ if (filePath.empty())
+ throw r::exec::RErrorException("invalid path: " + path);
+
+ // create path info vector (use json repsesentation to force convertion
+ // to VECSXP rather than STRSXP)
+ json::Object pathInfo;
+ pathInfo["path"] = filePath.absolutePath();
+ std::string parent = filePath.absolutePath();
+ FilePath parentPath = filePath.parent();
+ if (!parentPath.empty())
+ parent = parentPath.absolutePath();
+ pathInfo["directory"] = parent;
+ pathInfo["name"] = filePath.filename();
+ pathInfo["stem"] = filePath.stem();
+ pathInfo["extension"] = filePath.extension();
+
+ // return it
+ r::sexp::Protect rProtect;
+ return r::sexp::create(pathInfo, &rProtect);
+ }
+ catch(r::exec::RErrorException e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+} // anonymous namespace
+
+bool isMonitoringDirectory(const FilePath& directory)
+{
+ FilePath monitoredPath = s_filesListingMonitor.currentMonitoredPath();
+ return !monitoredPath.empty() && (directory == monitoredPath);
+}
+
+Error initialize()
+{
+ // register suspend handler
+ using boost::bind;
+ using namespace module_context;
+ addSuspendHandler(SuspendHandler(bind(onSuspend, _2), onResume));
+
+ // subscribe to events
+ events().onClientInit.connect(bind(onClientInit));
+
+ // register path info function
+ R_CallMethodDef pathInfoMethodDef ;
+ pathInfoMethodDef.name = "rs_pathInfo" ;
+ pathInfoMethodDef.fun = (DL_FUNC) rs_pathInfo ;
+ pathInfoMethodDef.numArgs = 1;
+ r::routines::addCallMethod(pathInfoMethodDef);
+
+ // install handlers
+ using boost::bind;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "stat", stat))
+ (bind(registerRpcMethod, "is_text_file", isTextFile))
+ (bind(registerRpcMethod, "list_files", listFiles))
+ (bind(registerRpcMethod, "create_folder", createFolder))
+ (bind(registerRpcMethod, "delete_files", deleteFiles))
+ (bind(registerRpcMethod, "copy_file", copyFile))
+ (bind(registerRpcMethod, "move_files", moveFiles))
+ (bind(registerRpcMethod, "rename_file", renameFile))
+ (bind(registerUriHandler, "/files", handleFilesRequest))
+ (bind(registerUriHandler, "/upload", handleFileUploadRequest))
+ (bind(registerUriHandler, "/export", handleFileExportRequest))
+ (bind(registerRpcMethod, "complete_upload", completeUpload))
+ (bind(sourceModuleRFile, "SessionFiles.R"))
+ (bind(quotas::initialize));
+ return initBlock.execute();
+}
+
+
+} // namepsace files
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionFiles.hpp b/src/cpp/session/modules/SessionFiles.hpp
new file mode 100644
index 0000000..264b0ac
--- /dev/null
+++ b/src/cpp/session/modules/SessionFiles.hpp
@@ -0,0 +1,36 @@
+/*
+ * SessionFiles.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_FILES_HPP
+#define SESSION_SESSION_FILES_HPP
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace files {
+
+bool isMonitoringDirectory(const core::FilePath& directory);
+
+core::Error initialize();
+
+} // namespace files
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_FILES_HPP
diff --git a/src/cpp/session/modules/SessionFilesListingMonitor.cpp b/src/cpp/session/modules/SessionFilesListingMonitor.cpp
new file mode 100644
index 0000000..07b8a8b
--- /dev/null
+++ b/src/cpp/session/modules/SessionFilesListingMonitor.cpp
@@ -0,0 +1,208 @@
+/*
+ * SessionFilesListingMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionFilesListingMonitor.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/system/FileMonitor.hpp>
+#include <core/system/FileChangeEvent.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionVCS.hpp"
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace files {
+
+Error FilesListingMonitor::start(const FilePath& filePath, json::Array* pJsonFiles)
+{
+ // always stop existing
+ stop();
+
+ // scan the directory (populates pJsonFiles out parameter)
+ std::vector<FilePath> files;
+ Error error = listFiles(filePath, &files, pJsonFiles);
+ if (error)
+ return error;
+
+ // copy the file listing into a vector of FileInfo which we will order so that it can
+ // be compared with the initial scan of the file montor for changes
+ std::vector<FileInfo> prevFiles;
+ std::transform(files.begin(),
+ files.end(),
+ std::back_inserter(prevFiles),
+ core::toFileInfo);
+
+ // kickoff new monitor
+ core::system::file_monitor::Callbacks cb;
+ cb.onRegistered = boost::bind(&FilesListingMonitor::onRegistered,
+ this, _1, filePath, prevFiles, _2);
+ cb.onRegistrationError = boost::bind(core::log::logError, _1, ERROR_LOCATION);
+ cb.onFilesChanged = boost::bind(module_context::enqueFileChangedEvents, filePath, _1);
+ cb.onMonitoringError = boost::bind(core::log::logError, _1, ERROR_LOCATION);
+ cb.onUnregistered = boost::bind(&FilesListingMonitor::onUnregistered, this, _1);
+ core::system::file_monitor::registerMonitor(filePath,
+ false,
+ module_context::fileListingFilter,
+ cb);
+
+ return Success();
+}
+
+void FilesListingMonitor::stop()
+{
+ // reset monitored path and unregister any existing handle
+ currentPath_ = FilePath();
+ if (!currentHandle_.empty())
+ {
+ core::system::file_monitor::unregisterMonitor(currentHandle_);
+ currentHandle_ = core::system::file_monitor::Handle();
+ }
+}
+
+const FilePath& FilesListingMonitor::currentMonitoredPath() const
+{
+ return currentPath_;
+}
+
+namespace {
+
+// Convert fileInfo returned from file monitor into a normalized path which
+// will traverse a symlink if necessary. this addresses the following concern:
+//
+// - Our core file listing code calls FilePath::children which traverses
+// symblinks to list the actual underlying file or directory linked to
+//
+// - Our file monitoring code however treats symlinks literally (to avoid
+// recursive or otherwise very long traversals)
+//
+// - The above two behaviors intersect to cause a pair of add/remove events
+// for symliniks within onRegistered (because the initial snapshot
+// was taken with FilePath::children and the file monitor enumeration
+// is taken using core::scanFiles). When propagated to the client this
+// results in symlinked directories appearing as documents and not
+// being traversable in the files pane
+//
+// - We could fix this by changing the behavior of core::scanFiles and/or
+// another layer in the file listing / monitoring code however we
+// are making the fix late in the cycle and therefore want to treat
+// only the symptom (it's not clear that this isn't the best fix anyway,
+// but just want to note that other fixes were not considered and
+// might be superior)
+//
+FileInfo normalizeFileScannerPath(const FileInfo& fileInfo)
+{
+ FilePath filePath(fileInfo.absolutePath());
+ return FileInfo(filePath);
+}
+
+} // anonymous namespace
+
+void FilesListingMonitor::onRegistered(core::system::file_monitor::Handle handle,
+ const FilePath& filePath,
+ const std::vector<FileInfo>& prevFiles,
+ const tree<core::FileInfo>& files)
+{
+ // set path and current handle
+ currentPath_ = filePath;
+ currentHandle_ = handle;
+
+ // normalize scanned file paths (see comment above for explanation)
+ std::vector<FileInfo> currFiles;
+ std::transform(files.begin(files.begin()),
+ files.end(files.begin()),
+ std::back_inserter(currFiles),
+ normalizeFileScannerPath);
+
+ // compare the previously returned listing with the initial scan to see if any
+ // file changes occurred between listings
+ std::vector<core::system::FileChangeEvent> events;
+ core::system::collectFileChangeEvents(prevFiles.begin(),
+ prevFiles.end(),
+ currFiles.begin(),
+ currFiles.end(),
+ module_context::fileListingFilter,
+ &events);
+
+ // enque any events we discovered
+ if (!events.empty())
+ module_context::enqueFileChangedEvents(filePath, events);
+}
+
+void FilesListingMonitor::onUnregistered(core::system::file_monitor::Handle handle)
+{
+ // typically we clear our internal state explicitly when a new registration
+ // comes in. however, it is possible that our monitor could be unregistered
+ // as a result of an error which occurs during monitoring. in this case
+ // we clear our state explicitly here as well
+ if (currentHandle_ == handle)
+ {
+ currentPath_ = FilePath();
+ currentHandle_ = core::system::file_monitor::Handle();
+ }
+}
+
+Error FilesListingMonitor::listFiles(const FilePath& rootPath,
+ std::vector<FilePath>* pFiles,
+ json::Array* pJsonFiles)
+{
+ // enumerate the files
+ pFiles->clear();
+ core::Error error = rootPath.children(pFiles) ;
+ if (error)
+ return error;
+
+ using namespace source_control;
+ boost::shared_ptr<FileDecorationContext> pCtx =
+ source_control::fileDecorationContext(rootPath);
+
+ // sort the files by name
+ std::sort(pFiles->begin(), pFiles->end(), core::compareAbsolutePathNoCase);
+
+ // produce json listing
+ BOOST_FOREACH( core::FilePath& filePath, *pFiles)
+ {
+ // files which may have been deleted after the listing or which
+ // are not end-user visible
+ if (filePath.exists() && module_context::fileListingFilter(core::FileInfo(filePath)))
+ {
+ core::json::Object fileObject = module_context::createFileSystemItem(filePath);
+ pCtx->decorateFile(filePath, &fileObject);
+ pJsonFiles->push_back(fileObject) ;
+ }
+ }
+
+ return Success();
+}
+
+
+} // namepsace files
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionFilesListingMonitor.hpp b/src/cpp/session/modules/SessionFilesListingMonitor.hpp
new file mode 100644
index 0000000..92f3796
--- /dev/null
+++ b/src/cpp/session/modules/SessionFilesListingMonitor.hpp
@@ -0,0 +1,93 @@
+/*
+ * SessionFilesListingMonitor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_FILES_LISTING_MONITOR_HPP
+#define SESSION_SESSION_FILES_LISTING_MONITOR_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <core/collection/Tree.hpp>
+
+#include <core/json/Json.hpp>
+#include <core/system/FileMonitor.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+ class FileInfo;
+ namespace system {
+ class FileChangeEvent;
+ }
+}
+
+namespace session {
+namespace modules {
+
+ namespace git {
+ class StatusResult;
+ }
+
+namespace files {
+
+class FilesListingMonitor : boost::noncopyable
+{
+public:
+ // kickoff monitoring
+ core::Error start(const core::FilePath& filePath, core::json::Array* pJsonFiles);
+
+ void stop();
+
+ // what path are we currently monitoring?
+ const core::FilePath& currentMonitoredPath() const;
+
+ // convenience method which is also called by listFiles for requests that
+ // don't specify monitoring (e.g. file dialog listing)
+ static core::Error listFiles(const core::FilePath& rootPath,
+ core::json::Array* pJsonFiles)
+ {
+ std::vector<core::FilePath> files;
+ return listFiles(rootPath, &files, pJsonFiles);
+ }
+
+private:
+ // stateful handlers for registration and unregistration
+ void onRegistered(core::system::file_monitor::Handle handle,
+ const core::FilePath& filePath,
+ const std::vector<core::FileInfo>& prevFiles,
+ const tree<core::FileInfo>& files);
+
+ void onUnregistered(core::system::file_monitor::Handle handle);
+
+ // helpers
+ static core::Error listFiles(const core::FilePath& rootPath,
+ std::vector<core::FilePath>* pFiles,
+ core::json::Array* pJsonFiles);
+
+private:
+ core::FilePath currentPath_;
+ core::system::file_monitor::Handle currentHandle_;
+};
+
+
+
+
+} // namespace files
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_FILES_LISTING_MONITOR_HPP
diff --git a/src/cpp/session/modules/SessionFilesQuotas.cpp b/src/cpp/session/modules/SessionFilesQuotas.cpp
new file mode 100644
index 0000000..c317981
--- /dev/null
+++ b/src/cpp/session/modules/SessionFilesQuotas.cpp
@@ -0,0 +1,244 @@
+/*
+ * SessionFilesQuotas.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionFilesQuotas.hpp"
+
+#include <iostream>
+
+#include <boost/tokenizer.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <core/BoostThread.hpp>
+#include <core/Error.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/Log.hpp>
+
+#include <core/system/Process.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace files {
+namespace quotas {
+
+namespace {
+
+// does the system have quotas?
+bool s_systemHasQuotas = false;
+
+struct QuotaInfo
+{
+ typedef long long size_type;
+
+ QuotaInfo()
+ : hasQuota(false), used(0), quota(0), limit(0)
+ {
+ }
+
+ bool hasQuota;
+ size_type used;
+ size_type quota;
+ size_type limit;
+};
+
+void quotaInfoToJson(const QuotaInfo& quotaInfo,
+ json::Object* pQuotaInfoJson)
+{
+ // first get the data into json serializable types
+ double used = 0;
+ double quota = 0;
+ double limit = 0;
+ try
+ {
+ used = boost::numeric_cast<double>(quotaInfo.used);
+ quota = boost::numeric_cast<double>(quotaInfo.quota);
+ limit = boost::numeric_cast<double>(quotaInfo.limit);
+ }
+ catch(boost::numeric::bad_numeric_cast& e)
+ {
+ LOG_ERROR_MESSAGE(std::string("Error converting quota info to double: ") +
+ e.what());
+ }
+
+ // write to json
+ json::Object& quotaInfoJson = *pQuotaInfoJson;
+ quotaInfoJson["used"] = used;
+ quotaInfoJson["quota"] = quota;
+ quotaInfoJson["limit"] = limit;
+}
+
+QuotaInfo::size_type quotaBytes(const std::string& quotaKb)
+{
+ QuotaInfo::size_type kb = boost::lexical_cast<QuotaInfo::size_type>(quotaKb);
+ return kb * 1024L;
+}
+
+Error parseQuotaInfo(const std::string& quotaInfo, QuotaInfo* pInfo)
+{
+ // if there was no quota info returned then there is no quota on this box
+ if (quotaInfo.empty())
+ {
+ pInfo->hasQuota = false;
+ return Success();
+ }
+
+ // tokenzie results and seek quota info
+ int resultIndex = 0;
+ boost::char_separator<char> sep(" \t");
+ typedef boost::tokenizer<boost::char_separator<char> > QuotaInfoTokenizer;
+ QuotaInfoTokenizer tok(quotaInfo, sep);
+ for (QuotaInfoTokenizer::const_iterator it = tok.begin();
+ it != tok.end();
+ ++it)
+ {
+ try
+ {
+ switch(resultIndex++)
+ {
+ // ingore first entry (device)
+ case 0:
+ break;
+
+ // Used
+ case 1:
+ pInfo->used = quotaBytes(*it);
+ break;
+
+ // Quota
+ case 2:
+ pInfo->quota = quotaBytes(*it);
+ break;
+
+ // Limit
+ case 3:
+ pInfo->limit = quotaBytes(*it);
+ pInfo->hasQuota = true;
+ break;
+ }
+ }
+ catch(boost::bad_lexical_cast&)
+ {
+ return systemError(boost::system::errc::result_out_of_range,
+ ERROR_LOCATION);
+ }
+
+ // out of here....
+ if (pInfo->hasQuota)
+ break;
+ }
+
+ // make sure we got all of the results
+ if (!pInfo->hasQuota)
+ {
+ return systemError(boost::system::errc::result_out_of_range,
+ ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+void checkQuotaThread()
+{
+ try
+ {
+ // run the command
+ core::system::ProcessResult result;
+ Error error = runCommand("xfs_quota -c 'quota -N'",
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // parse output
+ QuotaInfo quotaInfo;
+ error = parseQuotaInfo(result.stdOut, "aInfo);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // send event only if there are quotas established
+ if (quotaInfo.hasQuota)
+ {
+ json::Object quotaInfoJson;
+ quotaInfoToJson(quotaInfo, "aInfoJson);
+ ClientEvent event(client_events::kQuotaStatus, quotaInfoJson);
+ module_context::enqueClientEvent(event);
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // one time initialization of s_systemHasQuotas
+ if ( (session::options().programMode() == kSessionProgramModeServer) &&
+ session::options().limitXfsDiskQuota() )
+ {
+ std::string out;
+ Error error = r::exec::system("which xfs_quota", &out);
+ s_systemHasQuotas = !out.empty();
+ }
+ else
+ {
+ s_systemHasQuotas = false;
+ }
+
+ return Success();
+}
+
+
+void checkQuotaStatus()
+{
+ if (s_systemHasQuotas)
+ {
+ try
+ {
+ // block all signals for launch of background thread (will cause it
+ // to never receive signals)
+ core::system::SignalBlocker signalBlocker;
+ Error error = signalBlocker.blockAll();
+ if (error)
+ LOG_ERROR(error);
+
+ boost::thread t(checkQuotaThread);
+ }
+ catch(const boost::thread_resource_error& e)
+ {
+ LOG_ERROR(Error(boost::thread_error::ec_from_exception(e),
+ ERROR_LOCATION));
+ }
+ }
+}
+
+} // namespace quotas
+} // namepsace files
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionFilesQuotas.hpp b/src/cpp/session/modules/SessionFilesQuotas.hpp
new file mode 100644
index 0000000..ec44a0c
--- /dev/null
+++ b/src/cpp/session/modules/SessionFilesQuotas.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionFilesQuotas.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_FILES_QUOTAS_HPP
+#define SESSION_SESSION_FILES_QUOTAS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace files {
+namespace quotas {
+
+core::Error initialize();
+
+void checkQuotaStatus();
+
+} // namespace quotas
+} // namespace files
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_FILES_QUOTAS_HPP
diff --git a/src/cpp/session/modules/SessionFind.cpp b/src/cpp/session/modules/SessionFind.cpp
new file mode 100644
index 0000000..953fbd3
--- /dev/null
+++ b/src/cpp/session/modules/SessionFind.cpp
@@ -0,0 +1,587 @@
+/*
+ * SessionFind.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionFind.hpp"
+
+#include <algorithm>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/Exec.hpp>
+#include <core/StringUtils.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <r/RUtil.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace find {
+
+namespace {
+
+// This must be the same as MAX_COUNT in FindOutputPane.java
+const size_t MAX_COUNT = 1000;
+
+// Reflects the current set of Find results that are being
+// displayed, in case they need to be re-fetched (i.e. browser
+// refresh)
+class FindInFilesState : public boost::noncopyable
+{
+public:
+
+ explicit FindInFilesState() : running_(false)
+ {
+ }
+
+ std::string handle() const
+ {
+ return handle_;
+ }
+
+ int resultCount() const
+ {
+ return files_.size();
+ }
+
+ bool isRunning() const
+ {
+ return running_;
+ }
+
+ bool addResult(const std::string& handle,
+ const json::Array& files,
+ const json::Array& lineNums,
+ const json::Array& contents,
+ const json::Array& matchOns,
+ const json::Array& matchOffs)
+ {
+ if (handle_.empty())
+ handle_ = handle;
+ else if (handle_ != handle)
+ return false;
+
+ std::copy(files.begin(), files.end(), std::back_inserter(files_));
+ std::copy(lineNums.begin(), lineNums.end(), std::back_inserter(lineNums_));
+ std::copy(contents.begin(), contents.end(), std::back_inserter(contents_));
+ std::copy(matchOns.begin(), matchOns.end(), std::back_inserter(matchOns_));
+ std::copy(matchOffs.begin(), matchOffs.end(), std::back_inserter(matchOffs_));
+ return true;
+ }
+
+ void onFindBegin(const std::string& handle,
+ const std::string& input,
+ const std::string& path,
+ bool asRegex)
+ {
+ handle_ = handle;
+ input_ = input;
+ path_ = path;
+ regex_ = asRegex;
+ running_ = true;
+ }
+
+ void onFindEnd(const std::string& handle)
+ {
+ if (handle_ == handle)
+ running_ = false;
+ }
+
+ void clear()
+ {
+ handle_ = std::string();
+ files_.clear();
+ lineNums_.clear();
+ contents_.clear();
+ matchOns_.clear();
+ matchOffs_.clear();
+ }
+
+ Error readFromJson(const json::Object& asJson)
+ {
+ json::Object results;
+ Error error = json::readObject(asJson,
+ "handle", &handle_,
+ "input", &input_,
+ "path", &path_,
+ "regex", ®ex_,
+ "results", &results,
+ "running", &running_);
+ if (error)
+ return error;
+
+ error = json::readObject(results,
+ "file", &files_,
+ "line", &lineNums_,
+ "lineValue", &contents_,
+ "matchOn", &matchOns_,
+ "matchOff", &matchOffs_);
+ if (error)
+ return error;
+
+ if (files_.size() != lineNums_.size() || files_.size() != contents_.size())
+ {
+ files_.clear();
+ lineNums_.clear();
+ contents_.clear();
+ }
+
+ return Success();
+ }
+
+ json::Object asJson()
+ {
+ json::Object obj;
+ obj["handle"] = handle_;
+ obj["input"] = input_;
+ obj["path"] = path_;
+ obj["regex"] = regex_;
+
+ json::Object results;
+ results["file"] = files_;
+ results["line"] = lineNums_;
+ results["lineValue"] = contents_;
+ results["matchOn"] = matchOns_;
+ results["matchOff"] = matchOffs_;
+ obj["results"] = results;
+
+ obj["running"] = running_;
+
+ return obj;
+ }
+
+private:
+ std::string handle_;
+ std::string input_;
+ std::string path_;
+ bool regex_;
+ json::Array files_;
+ json::Array lineNums_;
+ json::Array contents_;
+ json::Array matchOns_;
+ json::Array matchOffs_;
+ bool running_;
+};
+
+FindInFilesState& findResults()
+{
+ static FindInFilesState* s_pFindResults = NULL;
+ if (s_pFindResults == NULL)
+ s_pFindResults = new FindInFilesState();
+ return *s_pFindResults;
+}
+
+class GrepOperation : public boost::enable_shared_from_this<GrepOperation>
+{
+public:
+ static boost::shared_ptr<GrepOperation> create(const std::string& encoding,
+ const FilePath& tempFile)
+ {
+ return boost::shared_ptr<GrepOperation>(new GrepOperation(encoding,
+ tempFile));
+ }
+
+private:
+ GrepOperation(const std::string& encoding,
+ const FilePath& tempFile)
+ : firstDecodeError_(true), encoding_(encoding), tempFile_(tempFile)
+ {
+ handle_ = core::system::generateUuid(false);
+ }
+
+public:
+ std::string handle() const
+ {
+ return handle_;
+ }
+
+ core::system::ProcessCallbacks createProcessCallbacks()
+ {
+ core::system::ProcessCallbacks callbacks;
+ callbacks.onContinue = boost::bind(&GrepOperation::onContinue,
+ shared_from_this(),
+ _1);
+ callbacks.onStdout = boost::bind(&GrepOperation::onStdout,
+ shared_from_this(),
+ _1, _2);
+ callbacks.onStderr = boost::bind(&GrepOperation::onStderr,
+ shared_from_this(),
+ _1, _2);
+ callbacks.onExit = boost::bind(&GrepOperation::onExit,
+ shared_from_this(),
+ _1);
+ return callbacks;
+ }
+
+private:
+ bool onContinue(const core::system::ProcessOperations& ops) const
+ {
+ return findResults().isRunning() && findResults().handle() == handle();
+ }
+
+ std::string decode(const std::string& encoded)
+ {
+ if (encoded.empty())
+ return encoded;
+
+ std::string decoded;
+ Error error = r::util::iconvstr(encoded, encoding_, "UTF-8", true,
+ &decoded);
+
+ // Log error, but only once per grep operation
+ if (error && firstDecodeError_)
+ {
+ firstDecodeError_ = false;
+ LOG_ERROR(error);
+ }
+
+ return decoded;
+ }
+
+ void processContents(std::string* pContent,
+ json::Array* pMatchOn,
+ json::Array* pMatchOff)
+ {
+ using namespace boost;
+
+ std::string decodedLine;
+
+ std::string::iterator inputPos = pContent->begin();
+
+ smatch match;
+ while (regex_search(std::string(inputPos, pContent->end()), match,
+ regex("\x1B\\[(\\d\\d)?m(\x1B\\[K)?")))
+ {
+ std::string match1 = match[1];
+
+ decodedLine.append(decode(
+ std::string(inputPos, inputPos + match.position())));
+
+ inputPos += match.position() + match.length();
+
+ size_t charSize;
+ Error error = string_utils::utf8Distance(decodedLine.begin(),
+ decodedLine.end(),
+ &charSize);
+ if (error)
+ charSize = decodedLine.size();
+
+ if (match1 == "01")
+ pMatchOn->push_back(static_cast<int>(charSize));
+ else
+ pMatchOff->push_back(static_cast<int>(charSize));
+ }
+ if (inputPos != pContent->end())
+ decodedLine.append(decode(std::string(inputPos, pContent->end())));
+
+ if (decodedLine.size() > 300)
+ {
+ decodedLine = decodedLine.erase(300);
+ decodedLine.append("...");
+ }
+
+ *pContent = decodedLine;
+ }
+
+ void onStdout(const core::system::ProcessOperations& ops, const std::string& data)
+ {
+ json::Array files;
+ json::Array lineNums;
+ json::Array contents;
+ json::Array matchOns;
+ json::Array matchOffs;
+
+ int recordsToProcess = MAX_COUNT + 1 - findResults().resultCount();
+ if (recordsToProcess < 0)
+ recordsToProcess = 0;
+
+ stdOutBuf_.append(data);
+ size_t nextLineStart = 0;
+ size_t pos = -1;
+ while (recordsToProcess &&
+ std::string::npos != (pos = stdOutBuf_.find('\n', pos + 1)))
+ {
+ std::string line = stdOutBuf_.substr(nextLineStart, pos - nextLineStart);
+ nextLineStart = pos + 1;
+
+ boost::smatch match;
+ if (boost::regex_match(line, match, boost::regex("^((?:[a-zA-Z]:)?[^:]+):(\\d+):(.*)")))
+ {
+ std::string file = module_context::createAliasedPath(
+ FilePath(string_utils::systemToUtf8(match[1])));
+
+ if (file.find("/.Rproj.user/") != std::string::npos)
+ continue;
+ if (file.find("/.git/") != std::string::npos)
+ continue;
+ if (file.find("/.svn/") != std::string::npos)
+ continue;
+
+ int lineNum = safe_convert::stringTo<int>(std::string(match[2]), -1);
+ std::string lineContents = match[3];
+ boost::algorithm::trim(lineContents);
+ json::Array matchOn, matchOff;
+ processContents(&lineContents, &matchOn, &matchOff);
+
+ files.push_back(file);
+ lineNums.push_back(lineNum);
+ contents.push_back(lineContents);
+ matchOns.push_back(matchOn);
+ matchOffs.push_back(matchOff);
+
+ recordsToProcess--;
+ }
+ }
+
+ if (nextLineStart)
+ {
+ stdOutBuf_.erase(0, nextLineStart);
+ }
+
+ if (files.size() > 0)
+ {
+ json::Object result;
+ result["handle"] = handle();
+ json::Object results;
+ results["file"] = files;
+ results["line"] = lineNums;
+ results["lineValue"] = contents;
+ results["matchOn"] = matchOns;
+ results["matchOff"] = matchOffs;
+ result["results"] = results;
+
+ findResults().addResult(handle(),
+ files,
+ lineNums,
+ contents,
+ matchOns,
+ matchOffs);
+
+ module_context::enqueClientEvent(
+ ClientEvent(client_events::kFindResult, result));
+ }
+
+ if (recordsToProcess <= 0)
+ findResults().onFindEnd(handle());
+ }
+
+ void onStderr(const core::system::ProcessOperations& ops, const std::string& data)
+ {
+ LOG_ERROR_MESSAGE("grep: " + data);
+ }
+
+ void onExit(int exitCode)
+ {
+ findResults().onFindEnd(handle());
+ module_context::enqueClientEvent(
+ ClientEvent(client_events::kFindOperationEnded, handle()));
+ if (!tempFile_.empty())
+ tempFile_.removeIfExists();
+ }
+
+ bool firstDecodeError_;
+ std::string encoding_;
+ FilePath tempFile_;
+ std::string stdOutBuf_;
+ std::string handle_;
+};
+
+} // namespace
+
+core::Error beginFind(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string searchString;
+ bool asRegex, ignoreCase;
+ std::string directory;
+ json::Array filePatterns;
+
+ Error error = json::readParams(request.params,
+ &searchString,
+ &asRegex,
+ &ignoreCase,
+ &directory,
+ &filePatterns);
+ if (error)
+ return error;
+
+ core::system::ProcessOptions options;
+
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+ core::system::setenv(&childEnv, "GREP_COLOR", "01");
+ core::system::setenv(&childEnv, "GREP_COLORS", "ne:fn=:ln=:se=:mt=01");
+#ifdef _WIN32
+ FilePath gnuGrepPath = session::options().gnugrepPath();
+ core::system::addToPath(
+ &childEnv,
+ string_utils::utf8ToSystem(gnuGrepPath.absolutePath()));
+#endif
+ options.environment = childEnv;
+
+ // Put the grep pattern in a file
+ FilePath tempFile = module_context::tempFile("rs_grep", "txt");
+ boost::shared_ptr<std::ostream> pStream;
+ error = tempFile.open_w(&pStream);
+ if (error)
+ return error;
+ std::string encoding = projects::projectContext().hasProject() ?
+ projects::projectContext().defaultEncoding() :
+ userSettings().defaultEncoding();
+ std::string encodedString;
+ error = r::util::iconvstr(searchString,
+ "UTF-8",
+ encoding,
+ false,
+ &encodedString);
+ if (error)
+ {
+ LOG_ERROR(error);
+ encodedString = searchString;
+ }
+
+ *pStream << encodedString << std::endl;
+ pStream.reset(); // release file handle
+
+ boost::shared_ptr<GrepOperation> ptrGrepOp = GrepOperation::create(encoding,
+ tempFile);
+ core::system::ProcessCallbacks callbacks =
+ ptrGrepOp->createProcessCallbacks();
+
+#ifdef _WIN32
+ shell_utils::ShellCommand cmd(gnuGrepPath.complete("grep"));
+#else
+ shell_utils::ShellCommand cmd("grep");
+#endif
+ cmd << "-rHn" << "--binary-files=without-match" << "--color=always";
+#ifndef _WIN32
+ cmd << "--devices=skip";
+#endif
+
+ if (ignoreCase)
+ cmd << "-i";
+
+ // Use -f to pass pattern via file, so we don't have to worry about
+ // escaping double quotes, etc.
+ cmd << "-f";
+ cmd << tempFile;
+ if (!asRegex)
+ cmd << "-F";
+
+ BOOST_FOREACH(json::Value filePattern, filePatterns)
+ {
+ cmd << "--include=" + filePattern.get_str();
+ }
+
+ cmd << shell_utils::EscapeFilesOnly << "--" << shell_utils::EscapeAll;
+ cmd << module_context::resolveAliasedPath(directory);
+
+ // Clear existing results
+ findResults().clear();
+
+ error = module_context::processSupervisor().runCommand(cmd,
+ options,
+ callbacks);
+ if (error)
+ return error;
+
+ findResults().onFindBegin(ptrGrepOp->handle(),
+ searchString,
+ directory,
+ asRegex);
+ pResponse->setResult(ptrGrepOp->handle());
+
+ return Success();
+}
+
+core::Error stopFind(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string handle;
+ Error error = json::readParams(request.params, &handle);
+ if (error)
+ return error;
+
+ findResults().onFindEnd(handle);
+
+ return Success();
+}
+
+core::Error clearFindResults(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ findResults().clear();
+ return Success();
+}
+
+void onSuspend(core::Settings* pSettings)
+{
+ std::ostringstream os;
+ json::write(findResults().asJson(), os);
+ pSettings->set("find-in-files-state", os.str());
+}
+
+void onResume(const core::Settings& settings)
+{
+ std::string state = settings.get("find-in-files-state");
+ if (!state.empty())
+ {
+ json::Value stateJson;
+ if (!json::parse(state, &stateJson))
+ {
+ LOG_WARNING_MESSAGE("invalid find results state json");
+ return;
+ }
+
+ Error error = findResults().readFromJson(stateJson.get_obj());
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+json::Object findInFilesStateAsJson()
+{
+ return findResults().asJson();
+}
+
+core::Error initialize()
+{
+ using boost::bind;
+ using namespace session::module_context;
+
+ // register suspend handler
+ addSuspendHandler(SuspendHandler(bind(onSuspend, _2), onResume));
+
+ // install handlers
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "begin_find", beginFind))
+ (bind(registerRpcMethod, "stop_find", stopFind))
+ (bind(registerRpcMethod, "clear_find_results", clearFindResults));
+ return initBlock.execute();
+}
+
+} // namespace find
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionFind.hpp b/src/cpp/session/modules/SessionFind.hpp
new file mode 100644
index 0000000..0e9a18e
--- /dev/null
+++ b/src/cpp/session/modules/SessionFind.hpp
@@ -0,0 +1,34 @@
+/*
+ * SessionFind.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_FIND_HPP
+#define SESSION_FIND_HPP
+
+#include <core/Error.hpp>
+#include <core/json/Json.hpp>
+
+namespace session {
+namespace modules {
+namespace find {
+
+core::json::Object findInFilesStateAsJson();
+
+core::Error initialize();
+
+} // namespace find
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_FIND_HPP
diff --git a/src/cpp/session/modules/SessionGit.cpp b/src/cpp/session/modules/SessionGit.cpp
new file mode 100644
index 0000000..5ea7ea1
--- /dev/null
+++ b/src/cpp/session/modules/SessionGit.cpp
@@ -0,0 +1,2857 @@
+/*
+ * SessionGit.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "SessionGit.hpp"
+
+#include <signal.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/function.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/optional.hpp>
+#include <boost/regex.hpp>
+#include <core/BoostLamda.hpp>
+
+#include <core/json/JsonRpc.hpp>
+#include <core/system/Crypto.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/system/System.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/GitGraph.hpp>
+#include <core/Scope.hpp>
+#include <core/StringUtils.hpp>
+
+
+#include <r/RExec.hpp>
+#include <r/RUtil.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionConsoleProcess.hpp"
+#include "SessionAskPass.hpp"
+
+#include "SessionVCS.hpp"
+
+#include "vcs/SessionVCSCore.hpp"
+#include "vcs/SessionVCSUtils.hpp"
+
+#include "session-config.h"
+
+using namespace core;
+using namespace core::shell_utils;
+using session::modules::console_process::ConsoleProcess;
+using namespace session::modules::vcs_utils;
+using session::modules::source_control::FileWithStatus;
+using session::modules::source_control::VCSStatus;
+using session::modules::source_control::StatusResult;
+
+namespace session {
+namespace modules {
+namespace git {
+
+const char * const kVcsId = "Git";
+
+namespace {
+
+// git bin dir which we detect at startup. note that if the git bin
+// is already in the path then this will be empty
+std::string s_gitExePath;
+uint64_t s_gitVersion;
+const uint64_t GIT_1_7_2 = ((uint64_t)1 << 48) |
+ ((uint64_t)7 << 32) |
+ ((uint64_t)2 << 16);
+
+core::system::ProcessOptions procOptions()
+{
+ core::system::ProcessOptions options;
+
+ // detach the session so there is no terminal
+#ifndef _WIN32
+ options.detachSession = true;
+#endif
+
+ // get current environment for modification prior to passing to child
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+
+ // add git bin dir to PATH if necessary
+ std::string nonPathGitBinDir = git::nonPathGitBinDir();
+ if (!nonPathGitBinDir.empty())
+ core::system::addToPath(&childEnv, nonPathGitBinDir);
+
+ // add postback directory to PATH
+ FilePath postbackDir = session::options().rpostbackPath().parent();
+ core::system::addToPath(&childEnv, postbackDir.absolutePath());
+
+ options.workingDir = projects::projectContext().directory();
+
+ // on windows set HOME to USERPROFILE
+#ifdef _WIN32
+ std::string userProfile = core::system::getenv(childEnv, "USERPROFILE");
+ core::system::setenv(&childEnv, "HOME", userProfile);
+#endif
+
+ // set custom environment
+ options.environment = childEnv;
+
+ return options;
+}
+
+enum PatchMode
+{
+ PatchModeWorking = 0,
+ PatchModeStage = 1
+};
+
+struct CommitInfo
+{
+ std::string id;
+ std::string author;
+ std::string subject;
+ std::string description;
+ std::string parent;
+ boost::int64_t date; // millis since epoch, UTC
+ std::vector<std::string> refs;
+ std::vector<std::string> tags;
+ std::string graph;
+};
+
+struct RemoteBranchInfo
+{
+ RemoteBranchInfo() : commitsBehind(0) {}
+
+ RemoteBranchInfo(const std::string& name, int commitsBehind)
+ : name(name), commitsBehind(commitsBehind)
+ {
+ }
+
+ bool empty() const { return name.empty(); }
+
+ std::string name;
+ int commitsBehind;
+
+ json::Value toJson() const
+ {
+ if (!empty())
+ {
+ json::Object remoteInfoJson;
+ remoteInfoJson["name"] = name;
+ remoteInfoJson["commits_behind"] = commitsBehind;
+ return remoteInfoJson;
+ }
+ else
+ {
+ return json::Value();
+ }
+ }
+};
+
+class Git;
+
+std::vector<PidType> s_pidsToTerminate_;
+
+ShellCommand git()
+{
+ if (!s_gitExePath.empty())
+ {
+ FilePath fullPath(s_gitExePath);
+ return ShellCommand(fullPath);
+ }
+ else
+ return ShellCommand("git");
+}
+
+
+#ifdef _WIN32
+std::string gitBin()
+{
+ if (!s_gitExePath.empty())
+ {
+ return FilePath(s_gitExePath).absolutePathNative();
+ }
+ else
+ return "git.exe";
+}
+#endif
+
+Error gitExec(const ShellArgs& args,
+ const core::FilePath& workingDir,
+ core::system::ProcessResult* pResult)
+{
+ core::system::ProcessOptions options = procOptions();
+ options.workingDir = workingDir;
+ // Important to ensure SSH_ASKPASS works
+#ifdef _WIN32
+ options.detachProcess = true;
+#endif
+
+#ifdef _WIN32
+ return runProgram(gitBin(),
+ args.args(),
+ "",
+ options,
+ pResult);
+#else
+ return runCommand(git() << args.args(),
+ "",
+ options,
+ pResult);
+#endif
+}
+
+bool commitIsMatch(const std::vector<std::string>& patterns,
+ const CommitInfo& commit)
+{
+ BOOST_FOREACH(std::string pattern, patterns)
+ {
+ if (!boost::algorithm::ifind_first(commit.author, pattern)
+ && !boost::algorithm::ifind_first(commit.description, pattern)
+ && !boost::algorithm::ifind_first(commit.id, pattern))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+boost::function<bool(CommitInfo)> createSearchTextPredicate(
+ const std::string& searchText)
+{
+ if (searchText.empty())
+ return boost::lambda::constant(true);
+
+ std::vector<std::string> results;
+ boost::algorithm::split(results, searchText,
+ boost::algorithm::is_any_of(" \t\r\n"));
+ return boost::bind(commitIsMatch, results, _1);
+}
+
+bool isUntracked(const source_control::StatusResult& statusResult,
+ const FilePath& filePath)
+{
+ return statusResult.getStatus(filePath).status() == "??";
+}
+
+class Git : public boost::noncopyable
+{
+private:
+ FilePath root_;
+
+protected:
+ core::Error runGit(const ShellArgs& args,
+ std::string* pStdOut=NULL,
+ std::string* pStdErr=NULL,
+ int* pExitCode=NULL)
+ {
+ using namespace core::system;
+
+ ProcessResult result;
+ Error error = gitExec(args, root_, &result);
+ if (error)
+ return error;
+
+ if (pStdOut)
+ *pStdOut = result.stdOut;
+ if (pStdErr)
+ *pStdErr = result.stdErr;
+ if (pExitCode)
+ *pExitCode = result.exitStatus;
+
+ if (result.exitStatus != EXIT_SUCCESS)
+ {
+ LOG_DEBUG_MESSAGE(result.stdErr);
+ }
+
+ return Success();
+ }
+
+ core::Error createConsoleProc(const ShellArgs& args,
+ const std::string& caption,
+ bool dialog,
+ boost::shared_ptr<ConsoleProcess>* ppCP,
+ const boost::optional<FilePath>& workingDir=boost::optional<FilePath>())
+ {
+ using namespace session::modules::console_process;
+
+ core::system::ProcessOptions options = procOptions();
+#ifdef _WIN32
+ options.detachProcess = true;
+#endif
+ if (!workingDir)
+ options.workingDir = root_;
+ else if (!workingDir.get().empty())
+ options.workingDir = workingDir.get();
+
+#ifdef _WIN32
+ *ppCP = ConsoleProcess::create(gitBin(),
+ args.args(),
+ options,
+ caption,
+ dialog,
+ console_process::InteractionNever,
+ console_process::kDefaultMaxOutputLines);
+#else
+ *ppCP = ConsoleProcess::create(git() << args.args(),
+ options,
+ caption,
+ dialog,
+ console_process::InteractionNever,
+ console_process::kDefaultMaxOutputLines);
+#endif
+
+ (*ppCP)->onExit().connect(boost::bind(&enqueueRefreshEvent));
+
+ return Success();
+ }
+
+ std::vector<std::string> split(const std::string& str)
+ {
+ std::vector<std::string> output;
+ boost::algorithm::split(output, str,
+ boost::algorithm::is_any_of("\r\n"));
+ return output;
+ }
+
+
+ void appendPathArgs(const std::vector<FilePath>& filePaths,
+ ShellArgs* pArgs)
+ {
+ // On OSX we observed that staging and unstaging operations involving
+ // directories that didn't exist would fail with an "unable to switch
+ // to directory" message from git. We discovered this at the very
+ // end of the v0.95 cycle so wanted to make as targeted a fix as
+ // we could, so below we use git root relative paths whenever we can
+ // on OSX, but on other platforms continue to use full absolute paths
+#ifdef __APPLE__
+ BOOST_FOREACH(const FilePath& filePath, filePaths)
+ {
+ if (filePath.isWithin(root_))
+ *pArgs << filePath.relativePath(root_);
+ else
+ *pArgs << filePath;
+ }
+#else
+ *pArgs << filePaths;
+#endif
+ }
+
+public:
+
+ Git() : root_(FilePath())
+ {
+ }
+
+ Git(const FilePath& root) : root_(root)
+ {
+ }
+
+ std::string name() { return kVcsId; }
+
+ FilePath root() const
+ {
+ return root_;
+ }
+
+ void setRoot(const FilePath& path)
+ {
+ root_ = path;
+ }
+
+ core::Error status(const FilePath& dir,
+ StatusResult* pStatusResult)
+ {
+ using namespace boost;
+
+ std::vector<FileWithStatus> files;
+
+ std::vector<std::string> lines;
+ std::string output;
+ Error error = runGit(ShellArgs() << "status" << "--porcelain" << "--" << dir,
+ &output);
+ if (error)
+ return error;
+ lines = split(output);
+
+ for (std::vector<std::string>::iterator it = lines.begin();
+ it != lines.end();
+ it++)
+ {
+ std::string line = *it;
+ if (line.length() < 4)
+ continue;
+ FileWithStatus file;
+
+ file.status = line.substr(0, 2);
+
+ std::string filePath = line.substr(3);
+ if (filePath.length() > 1 && filePath[filePath.length() - 1] == '/')
+ filePath = filePath.substr(0, filePath.size() - 1);
+ file.path = root_.childPath(string_utils::systemToUtf8(filePath));
+
+ files.push_back(file);
+ }
+
+ *pStatusResult = StatusResult(files);
+
+ return Success();
+ }
+
+ core::Error add(const std::vector<FilePath>& filePaths)
+ {
+ return runGit(ShellArgs() << "add" << "--" << filePaths);
+ }
+
+ core::Error remove(const std::vector<FilePath>& filePaths)
+ {
+ ShellArgs args;
+ args << "rm" << "--";
+ appendPathArgs(filePaths, &args);
+ return runGit(args);
+ }
+
+ core::Error discard(const std::vector<FilePath>& filePaths)
+ {
+ source_control::StatusResult statusResult;
+ Error error = status(root_, &statusResult);
+ if (error)
+ return error;
+
+ std::vector<FilePath> trackedPaths;
+ std::remove_copy_if(filePaths.begin(),
+ filePaths.end(),
+ std::back_inserter(trackedPaths),
+ boost::bind(isUntracked, statusResult, _1));
+
+ if (!trackedPaths.empty())
+ {
+ // -f means don't fail on unmerged entries
+ return runGit(ShellArgs() << "checkout" << "-f" << "--" << trackedPaths);
+ }
+ else
+ {
+ return Success();
+ }
+ }
+
+ core::Error stage(const std::vector<FilePath> &filePaths)
+ {
+ StatusResult statusResult;
+ this->status(root_, &statusResult);
+
+ std::vector<FilePath> filesToAdd;
+ std::vector<FilePath> filesToRm;
+
+ BOOST_FOREACH(const FilePath& path, filePaths)
+ {
+ std::string status = statusResult.getStatus(path).status();
+ if (status.size() < 2)
+ {
+ // In the case of renames, getStatus(path) might not return
+ // anything
+ StatusResult individualStatusResult;
+ this->status(path, &individualStatusResult);
+ status = individualStatusResult.getStatus(path).status();
+ if (status.size() < 2)
+ continue;
+ }
+ if (status[1] == 'D')
+ filesToRm.push_back(path);
+ else if (status[1] != ' ')
+ filesToAdd.push_back(path);
+ }
+
+ Error error;
+
+ if (!filesToAdd.empty())
+ {
+ error = this->add(filesToAdd);
+ if (error)
+ return error;
+ }
+
+ if (!filesToRm.empty())
+ {
+ error = this->remove(filesToRm);
+ if (error)
+ return error;
+ }
+
+ return Success();
+ }
+
+ core::Error unstage(const std::vector<FilePath>& filePaths)
+ {
+ source_control::StatusResult statusResult;
+ Error error = status(root_, &statusResult);
+ if (error)
+ return error;
+
+ std::vector<FilePath> trackedPaths;
+ std::remove_copy_if(filePaths.begin(),
+ filePaths.end(),
+ std::back_inserter(trackedPaths),
+ boost::bind(isUntracked, statusResult, _1));
+
+ // Detect if HEAD does not exist (i.e. no commits in repo yet)
+ int exitCode;
+ error = runGit(ShellArgs() << "rev-parse" << "HEAD", NULL, NULL,
+ &exitCode);
+ if (error)
+ return error;
+
+ ShellArgs args;
+ if (exitCode == 0)
+ args << "reset" << "HEAD" << "--" ;
+ else
+ args << "rm" << "--cached" << "--";
+
+ if (!trackedPaths.empty())
+ {
+ appendPathArgs(trackedPaths, &args);
+ return runGit(args);
+ }
+ else
+ {
+ return Success();
+ }
+ }
+
+ core::Error listBranches(std::vector<std::string>* pBranches,
+ boost::optional<size_t>* pActiveBranchIndex)
+ {
+ std::vector<std::string> lines;
+
+ std::string output;
+ Error error = runGit(ShellArgs() << "branch" << "-a", &output);
+ if (error)
+ return error;
+ lines = split(output);
+
+ for (size_t i = 0; i < lines.size(); i++)
+ {
+ const std::string line = lines.at(i);
+ if (line.size() < 2)
+ break;
+
+ if (boost::algorithm::starts_with(line, "* "))
+ *pActiveBranchIndex = i;
+ pBranches->push_back(line.substr(2));
+ }
+
+ return Success();
+ }
+
+ core::Error checkout(const std::string& id,
+ boost::shared_ptr<ConsoleProcess>* ppCP)
+ {
+ return createConsoleProc(ShellArgs() << "checkout" << id << "--",
+ "Git Checkout " + id,
+ true,
+ ppCP);
+ }
+
+ core::Error commit(std::string message, bool amend, bool signOff,
+ boost::shared_ptr<ConsoleProcess>* ppCP)
+ {
+ bool alwaysUseUtf8 = s_gitVersion >= GIT_1_7_2;
+
+ if (!alwaysUseUtf8)
+ {
+ std::string encoding;
+ int exitCode;
+ Error error = runGit(ShellArgs() << "config" << "i18n.commitencoding",
+ &encoding,
+ NULL,
+ &exitCode);
+ if (!error)
+ {
+ boost::algorithm::trim_right(encoding);
+ if (!encoding.empty() && encoding != "UTF-8")
+ {
+ error = r::util::iconvstr(message,
+ "UTF-8",
+ encoding,
+ false,
+ &message);
+ if (error)
+ {
+ return systemError(boost::system::errc::illegal_byte_sequence,
+ "The commit message could not be encoded to " + encoding + ".\n\nYou can correct this by calling 'git config i18n.commitencoding UTF-8' and committing again.",
+ ERROR_LOCATION);
+ }
+ }
+ }
+ }
+
+ FilePath tempFile = module_context::tempFile("gitmsg", "txt");
+ boost::shared_ptr<std::ostream> pStream;
+
+ Error error = tempFile.open_w(&pStream);
+ if (error)
+ return error;
+
+ *pStream << message;
+
+ FilePath gitDir = root_.childPath(".git");
+ if (gitDir.childPath("MERGE_HEAD").exists())
+ {
+ FilePath mergeMsg = gitDir.childPath("MERGE_MSG");
+ if (mergeMsg.exists())
+ {
+ std::string mergeMsgStr;
+ error = core::readStringFromFile(mergeMsg, &mergeMsgStr);
+ if (!error)
+ {
+ if (!message.empty())
+ *pStream << std::endl << std::endl;
+ *pStream << mergeMsgStr;
+ }
+ }
+ }
+
+ pStream->flush();
+ pStream.reset(); // release file handle
+
+ // Make sure we override i18n settings that may cause the commit message
+ // to be marked as using an encoding other than utf-8.
+ ShellArgs args;
+ if (alwaysUseUtf8)
+ args << "-c" << "i18n.commitencoding=utf-8";
+ args << "commit" << "-F" << tempFile;
+
+ if (amend)
+ args << "--amend";
+ if (signOff)
+ args << "--signoff";
+
+ return createConsoleProc(args,
+ "Git Commit",
+ true,
+ ppCP);
+ }
+
+ core::Error clone(const std::string& url,
+ const std::string dirName,
+ const FilePath& parentPath,
+ boost::shared_ptr<ConsoleProcess>* ppCP)
+ {
+ // SPECIAL: happens in different working directory than usual
+
+ return
+ createConsoleProc(ShellArgs() << "clone" << "--progress" << url << dirName,
+ "Clone Repository",
+ true,
+ ppCP,
+ boost::optional<FilePath>(parentPath));
+ }
+
+ Error currentBranch(std::string* pBranch)
+ {
+ std::vector<std::string> branches;
+ boost::optional<size_t> index;
+ Error error = listBranches(&branches, &index);
+ if (error)
+ return error;
+ if (!index)
+ {
+ pBranch->clear();
+ return Success();
+ }
+
+ *pBranch = branches.at(*index);
+ if (*pBranch == "(no branch)")
+ {
+ pBranch->clear();
+ }
+ return Success();
+ }
+
+ bool remoteMerge(const std::string& branch,
+ std::string* pRemote,
+ std::string* pMerge)
+ {
+ int exitStatus;
+ Error error = runGit(ShellArgs() << "config" << "--get" << "branch." + branch + ".remote",
+ pRemote,
+ NULL,
+ &exitStatus);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ if (exitStatus != EXIT_SUCCESS)
+ return false;
+ boost::algorithm::trim(*pRemote);
+
+ error = runGit(ShellArgs() << "config" << "--get" << "branch." + branch + ".merge",
+ pMerge,
+ NULL,
+ &exitStatus);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ if (exitStatus != EXIT_SUCCESS)
+ return false;
+ boost::algorithm::trim(*pMerge);
+
+ return true;
+ }
+
+ core::Error push(boost::shared_ptr<ConsoleProcess>* ppCP)
+ {
+ std::string branch;
+ Error error = currentBranch(&branch);
+ if (error)
+ return error;
+
+ ShellArgs args = ShellArgs() << "push";
+
+ std::string remote, merge;
+ if (remoteMerge(branch, &remote, &merge))
+ {
+ args << remote << merge;
+ }
+
+ return createConsoleProc(args, "Git Push", true, ppCP);
+ }
+
+ core::Error pull(boost::shared_ptr<ConsoleProcess>* ppCP)
+ {
+ return createConsoleProc(ShellArgs() << "pull",
+ "Git Pull", true, ppCP);
+ }
+
+ core::Error doDiffFile(const FilePath& filePath,
+ const FilePath* pCompareTo,
+ PatchMode mode,
+ int contextLines,
+ std::string* pOutput)
+ {
+ ShellArgs args = ShellArgs() << "diff";
+ args << "-U" + safe_convert::numberToString(contextLines);
+ if (mode == PatchModeStage)
+ args << "--cached";
+ args << "--";
+ if (pCompareTo)
+ args << *pCompareTo;
+ args << filePath;
+
+ return runGit(args, pOutput, NULL, NULL);
+ }
+
+ core::Error diffFile(const FilePath& filePath,
+ PatchMode mode,
+ int contextLines,
+ std::string* pOutput)
+ {
+ Error error = doDiffFile(filePath, NULL, mode, contextLines, pOutput);
+ if (error)
+ return error;
+
+ if (pOutput->empty())
+ {
+ // detect add case
+ VCSStatus status;
+ error = git::fileStatus(filePath, &status);
+ if (error)
+ return error;
+ if (status.status() == "??" && mode == PatchModeWorking)
+ {
+ error = doDiffFile(filePath,
+ &(shell_utils::devnull()),
+ mode,
+ contextLines,
+ pOutput);
+ if (error)
+ return error;
+ }
+ }
+
+ return Success();
+ }
+
+ boost::int64_t convertGitRawDate(const std::string& time,
+ const std::string& timeZone)
+ {
+ boost::int64_t secs = safe_convert::stringTo<boost::int64_t>(time, 0);
+
+ int offset = safe_convert::stringTo<int>(timeZone, 0);
+
+ // Positive timezone offset means we have to SUBTRACT
+ // the offset to get UTC time, and vice versa
+ int factor = offset > 0 ? -1 : 1;
+
+ offset = abs(offset);
+ int hours = offset / 100;
+ int minutes = offset % 100;
+
+ secs += factor * (hours * 60*60);
+ secs += factor * (minutes * 60);
+
+ return secs;
+ }
+
+ core::Error applyPatch(const FilePath& patchFile,
+ PatchMode patchMode)
+ {
+ ShellArgs args = ShellArgs() << "apply";
+ if (patchMode == PatchModeStage)
+ args << "--cached";
+ args << "--";
+ args << patchFile;
+
+ return runGit(args);
+ }
+
+ void parseCommitValue(const std::string& value,
+ CommitInfo* pCommitInfo)
+ {
+ static boost::regex commitRegex("^([a-z0-9]+)(\\s+\\((.*)\\))?");
+ boost::smatch smatch;
+ if (boost::regex_match(value, smatch, commitRegex))
+ {
+ pCommitInfo->id = smatch[1];
+ std::vector<std::string> refs;
+ if (smatch[3].matched)
+ {
+ boost::algorithm::split(refs,
+ static_cast<const std::string>(smatch[3]),
+ boost::algorithm::is_any_of(","));
+ BOOST_FOREACH(std::string ref, refs)
+ {
+ boost::algorithm::trim(ref);
+ if (boost::algorithm::starts_with(ref, "tag: "))
+ pCommitInfo->tags.push_back(ref.substr(5));
+ else if (boost::algorithm::starts_with(ref, "refs/tags/"))
+ {
+ // Sometimes with git 1.7.0 tags appear without the "tags: "
+ // prefix, e.g. plyr-1.6
+ pCommitInfo->tags.push_back(ref);
+ }
+ else if (!boost::algorithm::starts_with(ref, "refs/bisect/"))
+ pCommitInfo->refs.push_back(ref);
+ }
+ }
+ }
+ else
+ {
+ pCommitInfo->id = value;
+ }
+ }
+
+ core::Error logLength(const std::string &rev,
+ const FilePath& fileFilter,
+ const std::string &searchText,
+ int *pLength)
+ {
+ if (searchText.empty())
+ {
+ ShellArgs args = ShellArgs() << "log";
+ args << "--pretty=oneline";
+ if (!rev.empty())
+ args << rev;
+
+ if (!fileFilter.empty())
+ args << "--" << fileFilter;
+
+ std::string output;
+ Error error = runGit(args, &output);
+ if (error)
+ return error;
+
+ *pLength = static_cast<int>(std::count(output.begin(), output.end(), '\n'));
+ return Success();
+ }
+ else
+ {
+ std::vector<CommitInfo> output;
+ Error error = log(rev, fileFilter, 0, -1, searchText, &output);
+ if (error)
+ return error;
+ *pLength = output.size();
+ return Success();
+ }
+ }
+
+ core::Error log(const std::string& rev,
+ const FilePath& fileFilter,
+ int skip,
+ int maxentries,
+ const std::string& searchText,
+ std::vector<CommitInfo>* pOutput)
+ {
+ ShellArgs args = ShellArgs() << "log" << "--encoding=UTF-8"
+ << "--pretty=raw" << "--decorate=full"
+ << "--date-order";
+
+ ShellArgs revListArgs = ShellArgs() << "rev-list" << "--date-order" << "--parents";
+ int revListSkip = skip;
+
+ if (!fileFilter.empty())
+ {
+ args << "--" << fileFilter;
+ revListArgs << "--" << fileFilter;
+ }
+
+ if (searchText.empty() && fileFilter.empty())
+ {
+ // This is a way more efficient way to implement skip and maxentries
+ // if we know that all commits are included.
+ if (skip > 0)
+ {
+ args << "--skip=" + safe_convert::numberToString(skip);
+ skip = 0;
+ }
+ if (maxentries >= 0)
+ {
+ args << "--max-count=" + safe_convert::numberToString(maxentries);
+ maxentries = -1;
+
+ revListArgs << "--max-count=" + safe_convert::numberToString(
+ (skip < 0 ? 0 : skip) + maxentries);
+ }
+ }
+
+ if (!rev.empty())
+ {
+ args << rev;
+ revListArgs << rev;
+ }
+ else
+ {
+ revListArgs << "HEAD";
+ }
+
+ if (maxentries < 0)
+ maxentries = std::numeric_limits<int>::max();
+
+ std::vector<std::string> outLines;
+ std::string output;
+ Error error = runGit(args, &output);
+ if (error)
+ return error;
+ outLines = split(output);
+ output.clear();
+
+ std::vector<std::string> graphLines;
+ if (searchText.empty() && fileFilter.empty())
+ {
+ std::vector<std::string> revOutLines;
+ std::string revOutput;
+ error = runGit(revListArgs, &revOutput);
+ if (error)
+ return error;
+ revOutLines = split(revOutput);
+ revOutput.clear();
+
+ gitgraph::GitGraph graph;
+ for (size_t i = 0; i < revOutLines.size(); i++)
+ {
+ typedef std::vector<std::string> find_vector_type;
+ find_vector_type parents;
+ boost::algorithm::split(parents, revOutLines[i],
+ boost::algorithm::is_any_of(" "));
+ if (parents.size() < 1)
+ break;
+
+ std::string commit = parents.front();
+ parents.erase(parents.begin());
+
+ gitgraph::Line line = graph.addCommit(commit, parents);
+ if (revListSkip <= 0 || static_cast<int>(i) >= revListSkip)
+ graphLines.push_back(line.string());
+ }
+ }
+
+ boost::function<bool(CommitInfo)> filter = createSearchTextPredicate(searchText);
+
+ boost::regex kvregex("^(\\w+) (.*)$");
+ boost::regex authTimeRegex("^(.*?) (\\d+) ([+\\-]?\\d+)$");
+
+ size_t graphLineIndex = 0;
+ int skipped = 0;
+ CommitInfo currentCommit;
+
+ for (std::vector<std::string>::const_iterator it = outLines.begin();
+ it != outLines.end() && pOutput->size() < static_cast<size_t>(maxentries);
+ it++)
+ {
+ boost::smatch smatch;
+ if (boost::regex_search(*it, smatch, kvregex))
+ {
+ std::string key = smatch[1];
+ std::string value = smatch[2];
+ if (key == "commit")
+ {
+ if (!currentCommit.id.empty() && filter(currentCommit))
+ {
+ if (skipped < skip)
+ skipped++;
+ else
+ {
+ if (graphLineIndex < graphLines.size())
+ currentCommit.graph = graphLines[graphLineIndex];
+ pOutput->push_back(currentCommit);
+ }
+
+ graphLineIndex++;
+ }
+
+ currentCommit = CommitInfo();
+ parseCommitValue(value, ¤tCommit);
+ }
+ else if (key == "author" || key == "committer")
+ {
+ boost::smatch authTimeMatch;
+ if (boost::regex_search(value, authTimeMatch, authTimeRegex))
+ {
+ std::string author = authTimeMatch[1];
+ std::string time = authTimeMatch[2];
+ std::string tz = authTimeMatch[3];
+
+ if (key == "author")
+ currentCommit.author = author;
+ else // if (key == "committer")
+ currentCommit.date = convertGitRawDate(time, tz);
+ }
+ }
+ else if (key == "parent")
+ {
+ if (!currentCommit.parent.empty())
+ currentCommit.parent.push_back(' ');
+ currentCommit.parent.append(value, 0, 8);
+ }
+ }
+ else if (boost::starts_with(*it, " "))
+ {
+ if (currentCommit.subject.empty())
+ currentCommit.subject = it->substr(4);
+
+ if (!currentCommit.description.empty())
+ currentCommit.description.append("\n");
+ currentCommit.description.append(it->substr(4));
+ }
+ else if (it->length() == 0)
+ {
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("Unexpected git-log output");
+ }
+ }
+
+ if (pOutput->size() < static_cast<size_t>(maxentries)
+ && !currentCommit.id.empty()
+ && filter(currentCommit))
+ {
+ if (skipped < skip)
+ skipped++;
+ else
+ {
+ if (graphLineIndex < graphLines.size())
+ currentCommit.graph = graphLines[graphLineIndex];
+ pOutput->push_back(currentCommit);
+ }
+ graphLineIndex++;
+ }
+
+ return Success();
+ }
+
+ virtual core::Error show(const std::string& rev,
+ std::string* pOutput)
+ {
+ ShellArgs args = ShellArgs() << "show" << "--pretty=oneline" << "-M";
+ if (s_gitVersion >= GIT_1_7_2)
+ args << "-c";
+ args << rev;
+
+ return runGit(args, pOutput);
+ }
+
+ virtual core::Error showFile(const std::string& rev,
+ const std::string& filename,
+ std::string* pOutput)
+ {
+ boost::format fmt("%1%:%2%");
+ ShellArgs args =
+ ShellArgs() << "show" << boost::str(fmt % rev % filename);
+
+ return runGit(args, pOutput);
+ }
+
+ virtual core::Error remoteBranchInfo(RemoteBranchInfo* pRemoteBranchInfo)
+ {
+ // default to none
+ *pRemoteBranchInfo = RemoteBranchInfo();
+
+ std::string branch;
+ Error error = currentBranch(&branch);
+ if (error)
+ return error;
+
+ if (branch.empty())
+ return Success();
+
+ std::string remote, merge;
+ if (remoteMerge(branch, &remote, &merge))
+ {
+ // branch name is simple concatenation
+ std::string name = remote + "/" + branch;
+
+ // list the commits between the current upstream and HEAD
+ ShellArgs args = ShellArgs() << "log" << "@{u}..HEAD" << "--oneline";
+ std::string output;
+ Error error = runGit(args, &output);
+ if (error)
+ return error;
+
+ // commits == number of lines (since we used --oneline mode)
+ int commitsBehind = safe_convert::numberTo<int>(
+ std::count(output.begin(), output.end(), '\n'), 0);
+
+ *pRemoteBranchInfo = RemoteBranchInfo(name, commitsBehind);
+ }
+
+ return Success();
+ }
+};
+
+Git s_git_;
+
+FilePath resolveAliasedPath(const std::string& path)
+{
+ if (boost::algorithm::starts_with(path, "~/"))
+ return module_context::resolveAliasedPath(path);
+ else
+ return s_git_.root().childPath(path);
+}
+
+bool splitRename(const std::string& path, std::string* pOld, std::string* pNew)
+{
+ const std::string RENAME(" -> ");
+
+ size_t index = path.find(RENAME);
+ if (index == path.npos)
+ return false;
+
+ if (pOld)
+ *pOld = std::string(path.begin(), path.begin() + index);
+ if (pNew)
+ *pNew = std::string(path.begin() + index + RENAME.size(), path.end());
+
+ return true;
+}
+
+std::vector<FilePath> resolveAliasedPaths(const json::Array& paths,
+ bool includeRenameOld = false,
+ bool includeRenameNew = true)
+{
+ std::vector<FilePath> results;
+ for (json::Array::const_iterator it = paths.begin();
+ it != paths.end();
+ it++)
+ {
+ std::string oldPath, newPath;
+ if (splitRename(it->get_str(), &oldPath, &newPath))
+ {
+ if (includeRenameOld)
+ results.push_back(resolveAliasedPath(oldPath));
+ if (includeRenameNew)
+ results.push_back(resolveAliasedPath(newPath));
+ }
+ else
+ {
+ results.push_back(resolveAliasedPath(it->get_str()));
+ }
+ }
+ return results;
+}
+
+FilePath detectGitDir(const FilePath& workingDir)
+{
+ core::system::ProcessOptions options = procOptions();
+ options.workingDir = workingDir;
+#ifndef _WIN32
+ options.detachSession = true;
+#endif
+
+ core::system::ProcessResult result;
+ Error error = core::system::runCommand(
+ git() << "rev-parse" << "--show-toplevel",
+ "",
+ options,
+ &result);
+
+ if (error)
+ return FilePath();
+
+ if (result.exitStatus != 0)
+ return FilePath();
+
+ return FilePath(boost::algorithm::trim_copy(
+ string_utils::systemToUtf8(result.stdOut)));
+}
+
+} // anonymous namespace
+
+GitFileDecorationContext::GitFileDecorationContext(const FilePath& rootDir)
+ : fullRefreshRequired_(false)
+{
+ // get source control status (merely log errors doing this)
+ Error error = git::status(rootDir, &vcsStatus_);
+ if (error)
+ LOG_ERROR(error);
+}
+
+GitFileDecorationContext::~GitFileDecorationContext()
+{
+ if (fullRefreshRequired_)
+ enqueueRefreshEvent();
+}
+
+void GitFileDecorationContext::decorateFile(const FilePath &filePath,
+ json::Object *pFileObject)
+{
+ VCSStatus status = vcsStatus_.getStatus(filePath);
+
+ if (status.status().empty() && !fullRefreshRequired_)
+ {
+ // Special edge case when file is inside an untracked directory
+ // that may or may not be known to the client. (It wouldn't be
+ // known if the directory was empty until this file event.)
+
+ FilePath parent = filePath;
+ while (true)
+ {
+ if (parent == parent.parent())
+ break;
+
+ parent = parent.parent();
+ if (vcsStatus_.getStatus(parent).status() == "??")
+ {
+ fullRefreshRequired_ = true;
+ break;
+ }
+ }
+ }
+
+ json::Object vcsObj;
+ Error error = statusToJson(filePath, status, &vcsObj);
+ if (error)
+ LOG_ERROR(error);
+ (*pFileObject)["git_status"] = vcsObj;
+}
+
+core::Error status(const FilePath& dir, StatusResult* pStatusResult)
+{
+ if (s_git_.root().empty())
+ return Success();
+
+ return s_git_.status(dir, pStatusResult);
+}
+
+Error fileStatus(const FilePath& filePath, VCSStatus* pStatus)
+{
+ StatusResult statusResult;
+ Error error = git::status(filePath.parent(), &statusResult);
+ if (error)
+ return error;
+
+ *pStatus = statusResult.getStatus(filePath);
+
+ return Success();
+}
+
+namespace {
+
+Error vcsAdd(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ return s_git_.add(resolveAliasedPaths(paths));
+}
+
+Error vcsRemove(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ return s_git_.remove(resolveAliasedPaths(paths));
+}
+
+Error vcsDiscard(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ return s_git_.discard(resolveAliasedPaths(paths));
+}
+
+Error vcsRevert(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ error = s_git_.unstage(resolveAliasedPaths(paths, true, true));
+ if (error)
+ LOG_ERROR(error);
+ error = s_git_.discard(resolveAliasedPaths(paths, true, false));
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+}
+
+Error vcsStage(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ return s_git_.stage(resolveAliasedPaths(paths));
+}
+
+Error vcsUnstage(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array paths;
+ Error error = json::readParam(request.params, 0, &paths);
+ if (error)
+ return error ;
+
+ return s_git_.unstage(resolveAliasedPaths(paths, true, true));
+}
+
+Error vcsListBranches(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::vector<std::string> branches;
+ boost::optional<size_t> activeIndex;
+ Error error = s_git_.listBranches(&branches, &activeIndex);
+ if (error)
+ return error;
+
+ json::Array jsonBranches;
+ std::transform(branches.begin(), branches.end(),
+ std::back_inserter(jsonBranches),
+ json::toJsonString);
+
+ json::Object result;
+ result["branches"] = jsonBranches;
+ result["activeIndex"] =
+ activeIndex
+ ? json::Value(static_cast<boost::uint64_t>(activeIndex.get()))
+ : json::Value();
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+Error vcsCheckout(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ std::string id;
+ Error error = json::readParams(request.params, &id);
+ if (error)
+ return error;
+
+ boost::shared_ptr<ConsoleProcess> pCP;
+ error = s_git_.checkout(id, &pCP);
+ if (error)
+ return error;
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error vcsFullStatus(const json::JsonRpcRequest&,
+ json::JsonRpcResponse* pResponse)
+{
+ StatusResult statusResult;
+ Error error = s_git_.status(s_git_.root(), &statusResult);
+ if (error)
+ return error;
+
+ std::vector<FileWithStatus> files = statusResult.files();
+ json::Array result;
+ for (std::vector<FileWithStatus>::const_iterator it = files.begin();
+ it != files.end();
+ it++)
+ {
+ VCSStatus status = it->status;
+ FilePath path = it->path;
+ json::Object obj;
+ error = statusToJson(path, status, &obj);
+ if (error)
+ return error;
+ result.push_back(obj);
+ }
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+Error vcsAllStatus(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object result;
+ json::JsonRpcResponse tmp;
+
+ Error error = vcsFullStatus(request, &tmp);
+ if (error)
+ return error;
+ result["status"] = tmp.result();
+
+ error = vcsListBranches(request, &tmp);
+ if (error)
+ return error;
+ result["branches"] = tmp.result();
+
+ RemoteBranchInfo remoteBranchInfo;
+ error = s_git_.remoteBranchInfo(&remoteBranchInfo);
+ if (error)
+ return error;
+ result["remote_branch_info"] = remoteBranchInfo.toJson();
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+Error vcsCommit(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ std::string commitMsg;
+ bool amend, signOff;
+ Error error = json::readParams(request.params, &commitMsg, &amend, &signOff);
+ if (error)
+ return error;
+
+ boost::shared_ptr<ConsoleProcess> pCP;
+ error = s_git_.commit(commitMsg, amend, signOff, &pCP);
+ if (error)
+ {
+ if (error.code() == boost::system::errc::illegal_byte_sequence)
+ {
+ pResponse->setError(error, error.getProperty("description"));
+ return Success();
+ }
+
+ return error;
+ }
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error vcsPush(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ boost::shared_ptr<ConsoleProcess> pCP;
+ Error error = s_git_.push(&pCP);
+ if (error)
+ return error;
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error vcsPull(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ boost::shared_ptr<ConsoleProcess> pCP;
+ Error error = s_git_.pull(&pCP);
+ if (error)
+ return error;
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error vcsDiffFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ int mode;
+ int contextLines;
+ bool noSizeWarning;
+ Error error = json::readParams(request.params,
+ &path,
+ &mode,
+ &contextLines,
+ &noSizeWarning);
+ if (error)
+ return error;
+
+ if (contextLines < 0)
+ contextLines = 999999999;
+
+ splitRename(path, NULL, &path);
+
+ std::string output;
+ error = s_git_.diffFile(resolveAliasedPath(path),
+ static_cast<PatchMode>(mode),
+ contextLines,
+ &output);
+ if (error)
+ return error;
+
+ std::string sourceEncoding = projects::projectContext().defaultEncoding();
+ bool usedSourceEncoding;
+ output = convertDiff(output, sourceEncoding, "UTF-8", false,
+ &usedSourceEncoding);
+ if (!usedSourceEncoding)
+ sourceEncoding = "";
+
+ if (!noSizeWarning && output.size() > source_control::WARN_SIZE)
+ {
+ error = systemError(boost::system::errc::file_too_large,
+ ERROR_LOCATION);
+ pResponse->setError(error,
+ json::Value(static_cast<uint64_t>(output.size())));
+ }
+ else
+ {
+ json::Object result;
+ result["source_encoding"] = sourceEncoding;
+ result["decoded_value"] = output;
+ pResponse->setResult(result);
+ }
+ return Success();
+}
+
+Error vcsApplyPatch(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ std::string patch;
+ int mode;
+ std::string sourceEncoding;
+ Error error = json::readParams(request.params, &patch, &mode, &sourceEncoding);
+ if (error)
+ return error;
+
+ bool converted;
+ patch = convertDiff(patch, "UTF-8", sourceEncoding, false, &converted);
+ if (!converted)
+ return systemError(boost::system::errc::illegal_byte_sequence, ERROR_LOCATION);
+
+ FilePath patchFile = module_context::tempFile("rstudiovcs", "patch");
+ error = writeStringToFile(patchFile, patch);
+ if (error)
+ return error;
+
+ error = s_git_.applyPatch(patchFile, static_cast<PatchMode>(mode));
+
+ Error error2 = patchFile.remove();
+ if (error2)
+ LOG_ERROR(error2);
+
+ if (error)
+ return error;
+
+ return Success();
+}
+
+Error vcsGetIgnores(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+ FilePath gitIgnorePath = filePath.complete(".gitignore");
+
+ // setup result (default to empty)
+ core::system::ProcessResult result;
+ result.exitStatus = EXIT_SUCCESS;
+ result.stdOut = "";
+
+ // read the file if it exists
+ if (gitIgnorePath.exists())
+ {
+ Error error = core::readStringFromFile(gitIgnorePath,
+ &result.stdOut,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+ }
+
+ // return contents
+ pResponse->setResult(processResultToJson(result));
+ return Success();
+}
+
+Error vcsSetIgnores(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ // get the params
+ std::string path, ignores;
+ Error error = json::readParams(request.params, &path, &ignores);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+ FilePath gitIgnorePath = filePath.complete(".gitignore");
+
+ // write the .gitignore file
+ error = core::writeStringToFile(gitIgnorePath,
+ ignores,
+ string_utils::LineEndingNative);
+ if (error)
+ return error;
+
+ // always return an empty (successful) ProcessResult
+ core::system::ProcessResult result;
+ result.exitStatus = EXIT_SUCCESS;
+ pResponse->setResult(processResultToJson(result));
+ return Success();
+}
+
+
+std::string getUpstream(const std::string& branch = std::string())
+{
+ // determine the query (no explicit branch means current branch)
+ std::string query = "@{upstream}";
+ if (!branch.empty())
+ query = branch + query;
+
+ // get the upstream
+ std::string upstream;
+ core::system::ProcessResult result;
+ Error error = gitExec(ShellArgs() <<
+ "rev-parse" << "--abbrev-ref" << query,
+ s_git_.root(),
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+ else if (result.exitStatus == EXIT_SUCCESS)
+ {
+ upstream = boost::algorithm::trim_copy(result.stdOut);
+ }
+
+ return upstream;
+}
+
+std::string githubUrl(const std::string& view,
+ const FilePath& filePath = FilePath())
+{
+ if (!isGitEnabled())
+ return std::string();
+
+ // get the upstream for the current branch
+ std::string upstream = getUpstream();
+
+ // if there is none then get the upstream for master
+ if (upstream.empty())
+ upstream = getUpstream("master");
+
+ // if there still isn't one then fall back to origin/master
+ if (upstream.empty())
+ upstream = "origin/master";
+
+ // parse out the upstream name and branch
+ std::string::size_type pos = upstream.find_first_of('/');
+ if (pos == std::string::npos)
+ {
+ LOG_ERROR_MESSAGE("No / in upstream name: " + upstream);
+ return std::string();
+ }
+ std::string upstreamName = upstream.substr(0, pos);
+ std::string upstreamBranch = upstream.substr(pos + 1);
+
+ // now get the remote url
+ core::system::ProcessResult result;
+ Error error = gitExec(ShellArgs() <<
+ "config" << "--get" << ("remote." + upstreamName + ".url"),
+ s_git_.root(),
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+ else if (result.exitStatus != 0)
+ {
+ LOG_ERROR_MESSAGE(result.stdErr);
+ return std::string();
+ }
+
+ // get the url
+ std::string remoteUrl = boost::algorithm::trim_copy(result.stdOut);
+
+ // check for github
+
+ // check for ssh url
+ std::string repo;
+ const std::string kSSHPrefix = "git at github.com:";
+ if (boost::algorithm::starts_with(remoteUrl, kSSHPrefix))
+ repo = remoteUrl.substr(kSSHPrefix.length());
+
+ // check for https url
+ const std::string kHTTPSPrefix = "https://github.com/";
+ if (boost::algorithm::starts_with(remoteUrl, kHTTPSPrefix))
+ repo = remoteUrl.substr(kHTTPSPrefix.length());
+
+ // bail if we didn't get a repo
+ if (repo.empty())
+ return std::string();
+
+ // if the repo starts with / then remove it
+ if (repo[0] == '/')
+ repo = repo.substr(1);
+
+ // strip the .git off the end and form the github url from repo and branch
+ boost::algorithm::replace_last(repo, ".git", "");
+ std::string url = "https://github.com/" +
+ repo + "/" + view + "/" +
+ upstreamBranch;
+
+ if (!filePath.empty())
+ {
+ std::string relative = filePath.relativePath(s_git_.root());
+ if (relative.empty())
+ return std::string();
+
+ url = url + "/" + relative;
+ }
+
+ return url;
+}
+
+
+Error vcsGithubRemoteUrl(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the params
+ std::string view, path;
+ Error error = json::readParams(request.params, &view, &path);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+
+ // return the github url
+ pResponse->setResult(githubUrl(view, filePath));
+ return Success();
+}
+
+
+Error vcsHistoryCount(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string rev, searchText;
+ json::Value fileFilterJson;
+ Error error = json::readParams(request.params,
+ &rev,
+ &fileFilterJson,
+ &searchText);
+ if (error)
+ return error;
+
+ FilePath fileFilter = fileFilterPath(fileFilterJson);
+
+ boost::algorithm::trim(searchText);
+
+ int count = 0;
+ error = s_git_.logLength(rev, fileFilter, searchText, &count);
+ if (error)
+ return error;
+
+ json::Object result;
+ result["count"] = count;
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+Error vcsHistory(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string rev, searchText;
+ json::Value fileFilterJson;
+ int skip, maxentries;
+ Error error = json::readParams(request.params,
+ &rev,
+ &fileFilterJson,
+ &skip,
+ &maxentries,
+ &searchText);
+ if (error)
+ return error;
+
+ FilePath fileFilter = fileFilterPath(fileFilterJson);
+
+ boost::algorithm::trim(searchText);
+
+ std::vector<CommitInfo> commits;
+ error = s_git_.log(rev, fileFilter, skip, maxentries, searchText, &commits);
+ if (error)
+ return error;
+
+ json::Array ids;
+ json::Array authors;
+ json::Array parents;
+ json::Array subjects;
+ json::Array dates;
+ json::Array descriptions;
+ json::Array refs;
+ json::Array tags;
+ json::Array graphs;
+
+ for (std::vector<CommitInfo>::const_iterator it = commits.begin();
+ it != commits.end();
+ it++)
+ {
+ ids.push_back(it->id.substr(0, 8));
+ authors.push_back(string_utils::filterControlChars(it->author));
+ parents.push_back(string_utils::filterControlChars(it->parent));
+ subjects.push_back(string_utils::filterControlChars(it->subject));
+ descriptions.push_back(string_utils::filterControlChars(it->description));
+ dates.push_back(static_cast<double>(it->date));
+ graphs.push_back(it->graph);
+
+ json::Array theseRefs;
+ std::copy(it->refs.begin(), it->refs.end(), std::back_inserter(theseRefs));
+ refs.push_back(theseRefs);
+
+ json::Array theseTags;
+ std::copy(it->tags.begin(), it->tags.end(), std::back_inserter(theseTags));
+ tags.push_back(theseTags);
+ }
+
+ json::Object result;
+ result["id"] = ids;
+ result["author"] = authors;
+ result["parent"] = parents;
+ result["subject"] = subjects;
+ result["description"] = descriptions;
+ result["date"] = dates;
+ result["refs"] = refs;
+ result["tags"] = tags;
+ result["graph"] = graphs;
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+Error vcsShow(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string rev;
+ bool noSizeWarning;
+ Error error = json::readParams(request.params, &rev, &noSizeWarning);
+ if (error)
+ return error;
+
+ std::string output;
+ s_git_.show(rev, &output);
+ output = convertDiff(output, projects::projectContext().defaultEncoding(),
+ "UTF-8", true);
+ output = string_utils::filterControlChars(output);
+
+ if (!noSizeWarning && output.size() > source_control::WARN_SIZE)
+ {
+ error = systemError(boost::system::errc::file_too_large,
+ ERROR_LOCATION);
+ pResponse->setError(error,
+ json::Value(static_cast<uint64_t>(output.size())));
+ }
+ else
+ {
+ pResponse->setResult(output);
+ }
+ return Success();
+}
+
+
+Error vcsShowFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string rev,filename;
+ Error error = json::readParams(request.params, &rev, &filename);
+ if (error)
+ return error;
+
+ std::string output;
+ error = s_git_.showFile(rev, filename, &output);
+ if (error)
+ return error;
+
+ // convert to utf8
+ output = convertToUtf8(output, false);
+
+ output = string_utils::filterControlChars(output);
+
+ pResponse->setResult(output);
+
+ return Success();
+}
+
+
+Error vcsExportFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read parameters
+ std::string rev, filename, targetPath;
+ Error error = json::readParams(request.params,
+ &rev,
+ &filename,
+ &targetPath);
+ if (error)
+ return error;
+
+ // get the contents of the file
+ std::string output;
+ error = s_git_.showFile(rev, filename, &output);
+ if (error)
+ return error;
+
+ // write it
+ return core::writeStringToFile(
+ module_context::resolveAliasedPath(targetPath),
+ output);
+}
+
+
+
+Error vcsSshPublicKey(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get public key path
+ std::string aliasedPath;
+ Error error = json::readParam(request.params, 0, &aliasedPath);
+ if (error)
+ return error;
+
+ // unalias it and check for existence
+ FilePath publicKeyPath = module_context::resolveAliasedPath(aliasedPath);
+ if (!publicKeyPath.exists())
+ {
+ return core::fileNotFoundError(publicKeyPath.absolutePath(),
+ ERROR_LOCATION);
+ }
+
+ // read the key
+ std::string publicKeyContents;
+ error = core::readStringFromFile(publicKeyPath, &publicKeyContents);
+ if (error)
+ return error;
+
+ pResponse->setResult(publicKeyContents);
+
+ return Success();
+}
+
+Error vcsHasRepo(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get directory
+ std::string directory;
+ Error error = json::readParam(request.params, 0, &directory);
+ if (error)
+ return error;
+ FilePath dirPath = module_context::resolveAliasedPath(directory);
+
+ FilePath gitDir = detectGitDir(dirPath);
+
+ pResponse->setResult(!gitDir.empty());
+
+ return Success();
+}
+
+
+Error vcsInitRepo(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get directory
+ std::string directory;
+ Error error = json::readParam(request.params, 0, &directory);
+ if (error)
+ return error;
+ FilePath dirPath = module_context::resolveAliasedPath(directory);
+
+ core::system::ProcessOptions options = procOptions();
+ options.workingDir = dirPath;
+
+ // run it
+ core::system::ProcessResult result;
+ error = runCommand(git() << "init", options, &result);
+ if (error)
+ return error;
+
+ // verify success
+ if (result.exitStatus != 0)
+ {
+ LOG_ERROR_MESSAGE("Error creating git repo: " + result.stdErr);
+ return systemError(boost::system::errc::operation_not_permitted,
+ ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+bool ensureSSHAgentIsRunning()
+{
+ // Use "ssh-add -l" to see if ssh-agent is running
+ core::system::ProcessResult result;
+ Error error = runCommand(shell_utils::sendStdErrToNull("ssh-add -l"),
+ procOptions(), &result);
+ if (error)
+ {
+ // We couldn't even launch ssh-add.
+ return false;
+ }
+
+ if (result.exitStatus == 1)
+ {
+ // exitStatus == 1 means ssh-agent was running but no identities were
+ // present.
+ return true;
+ }
+ else if (result.exitStatus == EXIT_SUCCESS)
+ {
+ return true;
+ }
+
+ // Start ssh-agent using bash-style output
+ error = runCommand("ssh-agent -s", procOptions(), &result);
+ if (error)
+ {
+ // Failed to start ssh-agent, give up.
+ LOG_ERROR(error);
+ return false;
+ }
+ if (result.exitStatus != EXIT_SUCCESS)
+ {
+ return false;
+ }
+
+ // In addition to dumping the ssh-agent output, we also need to parse
+ // it so we can modify rsession's environment to use the new ssh-agent
+ // as well.
+ boost::sregex_iterator it(result.stdOut.begin(), result.stdOut.end(),
+ boost::regex("^([A-Za-z0-9_]+)=([^;]+);"));
+ boost::sregex_iterator end;
+ for (; it != end; it++)
+ {
+ std::string name = (*it).str(1);
+ std::string value = (*it).str(2);
+ core::system::setenv(name, value);
+
+ if (name == "SSH_AGENT_PID")
+ {
+ int pid = safe_convert::stringTo<int>(value, 0);
+ if (pid)
+ s_pidsToTerminate_.push_back(pid);
+ }
+ }
+
+ return true;
+}
+
+void addKeyToSSHAgent_onCompleted(const core::system::ProcessResult& result)
+{
+ if (result.exitStatus != EXIT_SUCCESS)
+ LOG_ERROR_MESSAGE(result.stdErr);
+}
+
+void addKeyToSSHAgent(const FilePath& keyFile,
+ const std::string& passphrase)
+{
+ core::system::ProcessOptions options = procOptions();
+ core::system::setenv(options.environment.get_ptr(),
+ "__ASKPASS_PASSTHROUGH_RESULT",
+ passphrase);
+ core::system::setenv(options.environment.get_ptr(),
+ "SSH_ASKPASS",
+ "askpass-passthrough");
+
+ ShellCommand cmd("ssh-add");
+ cmd << "--" << keyFile;
+
+ // Fire and forget. We don't care about the outcome.
+ // But we want to run it async in case it does somehow end up blocking;
+ // if we were running it synchronously, this would block the main thread.
+ module_context::processSupervisor().runCommand(
+ shell_utils::sendNullToStdIn(cmd),
+ options,
+ &addKeyToSSHAgent_onCompleted);
+}
+
+
+std::string transformKeyPath(const std::string& path)
+{
+#ifdef _WIN32
+ boost::smatch match;
+ if (boost::regex_match(path, match, boost::regex("/([a-zA-Z])/.*")))
+ {
+ return match[1] + std::string(":") + path.substr(2);
+ }
+#endif
+ return path;
+}
+
+void postbackSSHAskPass(const std::string& prompt,
+ const module_context::PostbackHandlerContinuation& cont)
+{
+ // default to failure unless we successfully receive a passphrase
+ int retcode = EXIT_FAILURE;
+ std::string passphrase;
+
+ bool promptToRemember;
+ boost::smatch match;
+ FilePath keyFile;
+
+ // This is what the prompt looks like on OpenSSH_4.6p1 (Windows)
+ if (boost::regex_match(prompt, match, boost::regex("Enter passphrase for key '(.+)': ")))
+ {
+ promptToRemember = true;
+ keyFile = FilePath(transformKeyPath(match[1]));
+ }
+ // This is what the prompt looks like on OpenSSH_5.8p1 Debian-7ubuntu1 (Ubuntu 11.10)
+ else if (boost::regex_match(prompt, match, boost::regex("Enter passphrase for (.+): ")))
+ {
+ promptToRemember = true;
+ keyFile = FilePath(transformKeyPath(match[1]));
+ }
+ else
+ promptToRemember = false;
+
+ promptToRemember = promptToRemember && keyFile.exists();
+
+ std::string rememberPrompt;
+ if (promptToRemember)
+ rememberPrompt = "Remember passphrase for this session";
+
+ std::string askPrompt = !prompt.empty() ? prompt :
+ std::string("Enter passphrase:");
+
+ // prompt
+ ask_pass::PasswordInput input;
+ Error error = ask_pass::askForPassword(askPrompt,
+ rememberPrompt,
+ &input);
+ if (!error)
+ {
+ if (!input.cancelled)
+ {
+ retcode = EXIT_SUCCESS;
+ passphrase = input.password;
+
+ if (input.remember)
+ {
+ ensureSSHAgentIsRunning();
+ addKeyToSSHAgent(keyFile, passphrase);
+ }
+ }
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+
+ // satisfy continuation
+ cont(retcode, passphrase);
+}
+
+#ifdef _WIN32
+
+template <typename T>
+class AutoRelease
+{
+public:
+ AutoRelease(T* pUnk) : pUnk_(pUnk)
+ {
+ }
+
+ ~AutoRelease()
+ {
+ if (pUnk_)
+ pUnk_->Release();
+ }
+
+private:
+ T* pUnk_;
+};
+
+
+bool detectGitExeDirOnPath(FilePath* pPath)
+{
+ std::vector<wchar_t> path(MAX_PATH+2);
+ wcscpy(&(path[0]), L"git.exe");
+ if (::PathFindOnPathW(&(path[0]), NULL))
+ {
+ // As of version 20120710 of msysgit, the cmd directory contains a
+ // git.exe wrapper that, if used by us, causes console windows to
+ // flash
+ FilePath filePath(&(path[0]));
+ if (filePath.parent().filename() == "cmd")
+ return false;
+
+ *pPath = filePath.parent();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool isGitExeOnPath()
+{
+ FilePath gitExeDir;
+ return detectGitExeDirOnPath(&gitExeDir);
+}
+
+
+bool detectGitBinDirFromPath(FilePath* pPath)
+{
+ std::vector<wchar_t> path(MAX_PATH+2);
+ wcscpy(&(path[0]), L"git.cmd");
+
+ if (::PathFindOnPathW(&(path[0]), NULL))
+ {
+ *pPath = FilePath(&(path[0])).parent().parent().childPath("bin");
+ return true;
+ }
+
+ // Look for cmd/git.exe and redirect to bin/
+ wcscpy(&(path[0]), L"git.exe");
+
+ if (::PathFindOnPathW(&(path[0]), NULL))
+ {
+ *pPath = FilePath(&(path[0])).parent().parent().childPath("bin");
+ return true;
+ }
+
+ return false;
+}
+
+HRESULT detectGitBinDirFromShortcut(FilePath* pPath)
+{
+ using namespace boost;
+
+ CoInitialize(NULL);
+
+ // Step 1. Find the Git Bash shortcut on the Start menu
+ std::vector<wchar_t> data(MAX_PATH+2);
+ HRESULT hr = ::SHGetFolderPathW(NULL,
+ CSIDL_COMMON_PROGRAMS,
+ NULL,
+ SHGFP_TYPE_CURRENT,
+ &(data[0]));
+ if (FAILED(hr))
+ return hr;
+
+ std::wstring path(&(data[0]));
+ path.append(L"\\Git\\Git Bash.lnk");
+ if (::GetFileAttributesW(path.c_str()) == INVALID_FILE_ATTRIBUTES)
+ return E_FAIL;
+
+
+ // Step 2. Extract the argument from the Git Bash shortcut
+ IShellLinkW* pShellLink;
+ hr = CoCreateInstance(CLSID_ShellLink,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW,
+ (void**)&pShellLink);
+ if (FAILED(hr))
+ return hr;
+ AutoRelease<IShellLinkW> arShellLink(pShellLink);
+
+ IPersistFile* pPersistFile;
+ hr = pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile);
+ if (FAILED(hr))
+ return hr;
+ AutoRelease<IPersistFile> arPersistFile(pPersistFile);
+
+ pPersistFile->Load(path.c_str(), STGM_READ);
+ if (FAILED(hr))
+ return hr;
+
+ hr = pShellLink->Resolve(NULL, SLR_NO_UI | 0x10000);
+ if (FAILED(hr))
+ return hr;
+ std::vector<wchar_t> argbuff(1024);
+ hr = pShellLink->GetArguments(&(argbuff[0]), argbuff.capacity() - 1);
+ if (FAILED(hr))
+ return hr;
+
+
+ // Step 3. Extract the git/bin directory from the arguments.
+ // Example: /c ""C:\Program Files\Git\bin\sh.exe" --login -i"
+ wcmatch match;
+ if (!regex_search(&(argbuff[0]), match, wregex(L"\"\"([^\"]*)\"")))
+ {
+ LOG_ERROR_MESSAGE("Unexpected git bash argument format: " +
+ string_utils::wideToUtf8(&(argbuff[0])));
+ return E_FAIL;
+ }
+
+ *pPath = FilePath(match[1]);
+ if (!pPath->exists())
+ return E_FAIL;
+ // The path we have is to sh.exe, we want the parent
+ *pPath = pPath->parent();
+ if (!pPath->exists())
+ return E_FAIL;
+
+ return S_OK;
+}
+
+Error discoverGitBinDir(FilePath* pPath)
+{
+ if (detectGitBinDirFromPath(pPath))
+ return Success();
+
+ HRESULT hr = detectGitBinDirFromShortcut(pPath);
+ if (SUCCEEDED(hr))
+ return Success();
+
+ return systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+}
+
+Error detectAndSaveGitExePath()
+{
+ if (isGitExeOnPath())
+ return Success();
+
+ FilePath path;
+ Error error = discoverGitBinDir(&path);
+ if (error)
+ return error;
+
+ // save it
+ s_gitExePath = path.complete("git.exe").absolutePath();
+
+ return Success();
+}
+
+
+#endif
+
+void onShutdown(bool)
+{
+ std::for_each(s_pidsToTerminate_.begin(), s_pidsToTerminate_.end(),
+ &core::system::terminateProcess);
+ s_pidsToTerminate_.clear();
+}
+
+Error addFilesToGitIgnore(const FilePath& gitIgnoreFile,
+ const std::vector<std::string>& filesToIgnore,
+ bool addExtraNewline)
+{
+#ifdef _WIN32
+ const char * const kNewline = "\r\n";
+#else
+ const char * const kNewline = "\n";
+#endif
+
+ if (filesToIgnore.empty())
+ return Success();
+
+ boost::shared_ptr<std::ostream> ptrOs;
+ Error error = gitIgnoreFile.open_w(&ptrOs, false);
+ if (error)
+ return error;
+
+ ptrOs->seekp(0, std::ios::end);
+ if (ptrOs->good())
+ {
+ if (addExtraNewline)
+ *ptrOs << kNewline;
+
+ BOOST_FOREACH(const std::string& line, filesToIgnore)
+ {
+ *ptrOs << line << kNewline;
+ }
+
+ ptrOs->flush();
+ }
+
+ return Success();
+}
+
+Error augmentGitIgnore(const FilePath& gitIgnoreFile)
+{
+ // Add stuff to .gitignore
+ std::vector<std::string> filesToIgnore;
+ if (!gitIgnoreFile.exists())
+ {
+ // If no .gitignore exists, add this stuff
+
+ // standard R and RStudio files
+ filesToIgnore.push_back(".Rproj.user");
+ filesToIgnore.push_back(".Rhistory");
+ filesToIgnore.push_back(".RData");
+
+ // if this is a package dir with a src directory then
+ // also ignore native code build artifacts
+ FilePath gitIgnoreParent = gitIgnoreFile.parent();
+ if (gitIgnoreParent.childPath("DESCRIPTION").exists() &&
+ gitIgnoreParent.childPath("src").exists())
+ {
+ filesToIgnore.push_back("src/*.o");
+ filesToIgnore.push_back("src/*.so");
+ filesToIgnore.push_back("src/*.dll");
+ }
+
+ return addFilesToGitIgnore(gitIgnoreFile, filesToIgnore, false);
+ }
+ else
+ {
+ // If .gitignore exists, add .Rproj.user unless it's already there
+
+ std::string strIgnore;
+ Error error = core::readStringFromFile(gitIgnoreFile, &strIgnore);
+ if (error)
+ return error;
+
+ if (boost::regex_search(strIgnore, boost::regex("^\\.Rproj\\.user$")))
+ return Success();
+
+ bool addExtraNewline = strIgnore.size() > 0
+ && strIgnore[strIgnore.size() - 1] != '\n';
+
+ std::vector<std::string> filesToIgnore;
+ filesToIgnore.push_back(".Rproj.user");
+ return addFilesToGitIgnore(gitIgnoreFile,
+ filesToIgnore,
+ addExtraNewline);
+ }
+}
+
+FilePath whichGitExe()
+{
+ std::string whichGit;
+ Error error = r::exec::RFunction("Sys.which", "git").call(&whichGit);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+ else
+ {
+ // if we are on osx mavericks we need to do a further check to make
+ // sure this isn't the fake version of git installed by default
+ if (module_context::isOSXMavericks())
+ {
+ if (module_context::hasOSXMavericksDeveloperTools())
+ return FilePath(whichGit);
+ else
+ return FilePath();
+ }
+ else
+ {
+ return FilePath(whichGit);
+ }
+ }
+}
+
+} // anonymous namespace
+
+bool isGitInstalled()
+{
+ if (!userSettings().vcsEnabled())
+ return false;
+
+ // special handling for mavericks for case where there is /usr/bin/git
+ // but it's the fake on installed by osx
+ if (module_context::isOSXMavericks() &&
+ !module_context::hasOSXMavericksDeveloperTools() &&
+ whichGitExe().empty())
+ {
+ return false;
+ }
+
+ core::system::ProcessResult result;
+ Error error = core::system::runCommand(git() << "--version",
+ procOptions(),
+ &result);
+ if (error)
+ return false;
+ return result.exitStatus == EXIT_SUCCESS;
+}
+
+bool isGitEnabled()
+{
+ return !s_git_.root().empty();
+}
+
+FilePath detectedGitExePath()
+{
+#ifdef _WIN32
+ FilePath path;
+ if (detectGitExeDirOnPath(&path))
+ {
+ return path.complete("git.exe");
+ }
+ else
+ {
+ Error error = discoverGitBinDir(&path);
+ if (!error)
+ {
+ return path.complete("git.exe");
+ }
+ else
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+ }
+#else
+ FilePath gitExeFilePath = whichGitExe();
+ if (!gitExeFilePath.empty())
+ return FilePath(gitExeFilePath);
+ else
+ return FilePath();
+#endif
+}
+
+
+std::string nonPathGitBinDir()
+{
+ if (!s_gitExePath.empty())
+ return FilePath(s_gitExePath).parent().absolutePath();
+ else
+ return std::string();
+}
+
+void onUserSettingsChanged()
+{
+ FilePath gitExePath = userSettings().gitExePath();
+ if (session::options().allowVcsExecutableEdit() && !gitExePath.empty())
+ {
+ // if there is an explicit value then set it
+ s_gitExePath = gitExePath.absolutePath();
+ }
+ else
+ {
+ // if we are relying on an auto-detected value then scan on windows
+ // and reset to empty on posix
+#ifdef _WIN32
+ Error error = detectAndSaveGitExePath();
+ if (error)
+ LOG_ERROR(error);
+#else
+ s_gitExePath = "";
+#endif
+ }
+}
+
+Error statusToJson(const core::FilePath &path,
+ const VCSStatus &status,
+ core::json::Object *pObject)
+{
+ json::Object& obj = *pObject;
+ obj["status"] = status.status();
+ obj["path"] = path.relativePath(s_git_.root());
+ obj["raw_path"] = module_context::createAliasedPath(path);
+ obj["discardable"] = status.status()[1] != ' ' && status.status()[1] != '?';
+ obj["is_directory"] = path.isDirectory();
+ return Success();
+}
+
+void onSuspend(core::Settings*)
+{
+}
+
+void onResume(const core::Settings&)
+{
+ enqueueRefreshEvent();
+}
+
+bool initGitBin()
+{
+ Error error;
+
+ // get the git bin dir from settings if it is there
+ if (session::options().allowVcsExecutableEdit())
+ s_gitExePath = userSettings().gitExePath().absolutePath();
+
+ // if it wasn't provided in settings then make sure we can detect it
+ if (s_gitExePath.empty())
+ {
+#ifdef _WIN32
+ error = detectAndSaveGitExePath();
+ if (error)
+ return false; // no Git install detected
+#else
+ FilePath gitExeFilePath = whichGitExe();
+ if (gitExeFilePath.empty())
+ return false; // no Git install detected
+#endif
+ }
+
+ // Save version
+ s_gitVersion = GIT_1_7_2;
+ core::system::ProcessResult result;
+ error = core::system::runCommand(git() << "--version",
+ procOptions(),
+ &result);
+ if (error)
+ LOG_ERROR(error);
+ else
+ {
+ if (result.exitStatus == 0)
+ {
+ boost::smatch matches;
+ if (boost::regex_search(result.stdOut,
+ matches,
+ boost::regex("\\d+(\\.\\d+)+")))
+ {
+ string_utils::parseVersion(matches[0], &s_gitVersion);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool isGitDirectory(const core::FilePath& workingDir)
+{
+ return !detectGitDir(workingDir).empty();
+}
+
+
+std::string remoteOriginUrl(const FilePath& workingDir)
+{
+ // default to none
+ std::string remoteOriginUrl;
+
+ core::system::ProcessResult result;
+ Error error = gitExec(ShellArgs() <<
+ "config" << "--get" << "remote.origin.url",
+ workingDir,
+ &result);
+
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ else if (result.exitStatus == 0)
+ {
+ remoteOriginUrl = boost::algorithm::trim_copy(result.stdOut);
+ }
+
+ // return any url we discovered
+ return remoteOriginUrl;
+}
+
+
+bool isGithubRepository()
+{
+ return !githubUrl("blob").empty();
+}
+
+
+core::Error initializeGit(const core::FilePath& workingDir)
+{
+ s_git_.setRoot(detectGitDir(workingDir));
+
+ if (!s_git_.root().empty())
+ {
+ FilePath gitIgnore = s_git_.root().childPath(".gitignore");
+ Error error = augmentGitIgnore(gitIgnore);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ return Success();
+}
+
+
+Error clone(const std::string& url,
+ const std::string dirName,
+ const FilePath& parentPath,
+ boost::shared_ptr<console_process::ConsoleProcess>* ppCP)
+{
+ Git git(options().userHomePath());
+ Error error = git.clone(url, dirName, parentPath, ppCP);
+ if (error)
+ return error;
+
+ return Success();
+}
+
+core::Error initialize()
+{
+ using namespace session::module_context;
+
+ Error error;
+
+ module_context::events().onShutdown.connect(onShutdown);
+
+ initGitBin();
+
+ bool interceptAskPass;
+
+ if (options().programMode() == kSessionProgramModeServer)
+ {
+ interceptAskPass = true;
+ }
+ else
+ {
+#ifdef _WIN32
+ // Windows probably unlikely to have either ssh-agent or askpass
+ interceptAskPass = true;
+#else
+ // Everything fine on Mac and Linux
+ interceptAskPass = false;
+#endif
+ }
+
+ // register postback handler
+ std::string sshAskCmd;
+ error = module_context::registerPostbackHandler("askpass",
+ postbackSSHAskPass,
+ &sshAskCmd);
+ if (error)
+ return error;
+
+ // setup environment
+ BOOST_ASSERT(boost::algorithm::ends_with(sshAskCmd, "rpostback-askpass"));
+ core::system::setenv("GIT_ASKPASS", "rpostback-askpass");
+
+ if (interceptAskPass)
+ {
+ core::system::setenv("SSH_ASKPASS", "rpostback-askpass");
+ }
+
+ // add suspend/resume handler
+ addSuspendHandler(SuspendHandler(boost::bind(onSuspend, _2), onResume));
+
+ // add settings changed handler
+ userSettings().onChanged.connect(onUserSettingsChanged);
+
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "git_add", vcsAdd))
+ (bind(registerRpcMethod, "git_remove", vcsRemove))
+ (bind(registerRpcMethod, "git_discard", vcsDiscard))
+ (bind(registerRpcMethod, "git_revert", vcsRevert))
+ (bind(registerRpcMethod, "git_stage", vcsStage))
+ (bind(registerRpcMethod, "git_unstage", vcsUnstage))
+ (bind(registerRpcMethod, "git_list_branches", vcsListBranches))
+ (bind(registerRpcMethod, "git_checkout", vcsCheckout))
+ (bind(registerRpcMethod, "git_full_status", vcsFullStatus))
+ (bind(registerRpcMethod, "git_all_status", vcsAllStatus))
+ (bind(registerRpcMethod, "git_commit", vcsCommit))
+ (bind(registerRpcMethod, "git_push", vcsPush))
+ (bind(registerRpcMethod, "git_pull", vcsPull))
+ (bind(registerRpcMethod, "git_diff_file", vcsDiffFile))
+ (bind(registerRpcMethod, "git_apply_patch", vcsApplyPatch))
+ (bind(registerRpcMethod, "git_history_count", vcsHistoryCount))
+ (bind(registerRpcMethod, "git_history", vcsHistory))
+ (bind(registerRpcMethod, "git_show", vcsShow))
+ (bind(registerRpcMethod, "git_show_file", vcsShowFile))
+ (bind(registerRpcMethod, "git_export_file", vcsExportFile))
+ (bind(registerRpcMethod, "git_ssh_public_key", vcsSshPublicKey))
+ (bind(registerRpcMethod, "git_has_repo", vcsHasRepo))
+ (bind(registerRpcMethod, "git_init_repo", vcsInitRepo))
+ (bind(registerRpcMethod, "git_get_ignores", vcsGetIgnores))
+ (bind(registerRpcMethod, "git_set_ignores", vcsSetIgnores))
+ (bind(registerRpcMethod, "git_github_remote_url", vcsGithubRemoteUrl));
+ error = initBlock.execute();
+ if (error)
+ return error;
+
+ return Success();
+}
+
+} // namespace git
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionGit.hpp b/src/cpp/session/modules/SessionGit.hpp
new file mode 100644
index 0000000..998c420
--- /dev/null
+++ b/src/cpp/session/modules/SessionGit.hpp
@@ -0,0 +1,86 @@
+/*
+ * SessionGit.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_GIT_HPP
+#define SESSION_GIT_HPP
+
+#include <map>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/json/Json.hpp>
+
+#include "vcs/SessionVCSCore.hpp"
+
+namespace session {
+namespace modules {
+namespace console_process {
+
+class ConsoleProcess;
+
+} // namespace console_process
+
+namespace git {
+
+extern const char * const kVcsId;
+
+class GitFileDecorationContext : public source_control::FileDecorationContext
+{
+public:
+ GitFileDecorationContext(const core::FilePath& rootDir);
+ virtual ~GitFileDecorationContext();
+ virtual void decorateFile(const core::FilePath &filePath,
+ core::json::Object *pFileObject);
+
+private:
+ source_control::StatusResult vcsStatus_;
+ bool fullRefreshRequired_;
+};
+
+bool isGitInstalled();
+bool isGitEnabled();
+
+bool isGitDirectory(const core::FilePath& workingDir);
+
+std::string remoteOriginUrl(const core::FilePath& workingDir);
+
+bool isGithubRepository();
+
+core::Error initializeGit(const core::FilePath& workingDir);
+
+core::FilePath detectedGitExePath();
+
+std::string nonPathGitBinDir();
+
+core::Error status(const core::FilePath& dir,
+ source_control::StatusResult* pStatusResult);
+core::Error fileStatus(const core::FilePath& filePath,
+ source_control::VCSStatus* pStatus);
+core::Error statusToJson(const core::FilePath& path,
+ const source_control::VCSStatus& status,
+ core::json::Object* pObject);
+
+core::Error clone(const std::string& url,
+ const std::string dirName,
+ const core::FilePath& parentPath,
+ boost::shared_ptr<console_process::ConsoleProcess>* ppCP);
+
+core::Error initialize();
+
+} // namespace git
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_GIT_HPP
diff --git a/src/cpp/session/modules/SessionHTMLPreview.R b/src/cpp/session/modules/SessionHTMLPreview.R
new file mode 100644
index 0000000..a70e6b0
--- /dev/null
+++ b/src/cpp/session/modules/SessionHTMLPreview.R
@@ -0,0 +1,40 @@
+#
+# SessionHTMLPreview.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction( "getHTMLCapabilities", function(markdownVersion,
+ stitchVersion)
+{
+ caps <- list()
+ caps$r_markdown_supported = .rs.scalar(FALSE)
+ caps$stitch_supported = .rs.scalar(FALSE)
+ if (.rs.isPackageInstalled("knitr"))
+ {
+ knitrVersion <- .rs.getPackageVersion("knitr")
+ caps$r_markdown_supported = .rs.scalar(knitrVersion >= markdownVersion)
+ caps$stitch_supported = .rs.scalar(knitrVersion >= stitchVersion)
+ }
+ return (caps)
+})
+
+
+.rs.addFunction( "spinScript", function(script, signature)
+{
+ # do the spin
+ rmd <- knitr::spin(script, knit = FALSE, format = "Rmd")
+
+ # append the signature (for overwrite protection)
+ cat(signature, file = rmd, append = TRUE)
+})
+
diff --git a/src/cpp/session/modules/SessionHTMLPreview.cpp b/src/cpp/session/modules/SessionHTMLPreview.cpp
new file mode 100644
index 0000000..d754e67
--- /dev/null
+++ b/src/cpp/session/modules/SessionHTMLPreview.cpp
@@ -0,0 +1,1048 @@
+/*
+ * SessionHTMLPreview.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionHTMLPreview.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/filter/regex.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/HtmlUtils.hpp>
+#include <core/http/Util.hpp>
+#include <core/PerformanceTimer.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/text/TemplateFilter.hpp>
+#include <core/system/Process.hpp>
+#include <core/StringUtils.hpp>
+#include <core/HtmlUtils.hpp>
+
+#include <core/markdown/Markdown.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RJson.hpp>
+#include <r/RUtil.hpp>
+#include <r/ROptions.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionSourceDatabase.hpp>
+
+#include "SessionRPubs.hpp"
+
+#define kHTMLPreview "html_preview"
+#define kHTMLPreviewLocation "/" kHTMLPreview "/"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace html_preview {
+
+namespace {
+
+class HTMLPreview : boost::noncopyable,
+ public boost::enable_shared_from_this<HTMLPreview>
+{
+public:
+ static boost::shared_ptr<HTMLPreview> create(const FilePath& targetFile,
+ const std::string& encoding,
+ bool isMarkdown,
+ bool isNotebook,
+ bool knit)
+ {
+ boost::shared_ptr<HTMLPreview> pPreview(new HTMLPreview(targetFile));
+ pPreview->start(encoding, isMarkdown, isNotebook, knit);
+ return pPreview;
+ }
+
+private:
+ HTMLPreview(const FilePath& targetFile)
+ : targetFile_(targetFile),
+ isMarkdown_(false),
+ isInternalMarkdown_(false),
+ isNotebook_(false),
+ requiresKnit_(false),
+ isRunning_(false),
+ terminationRequested_(false)
+ {
+ }
+
+ void start(const std::string& encoding,
+ bool isMarkdown,
+ bool isNotebook,
+ bool requiresKnit)
+ {
+ enqueHTMLPreviewStarted(targetFile_);
+
+ isMarkdown_ = isMarkdown;
+ isNotebook_ = isNotebook;
+ requiresKnit_ = requiresKnit;
+
+ // determine whether we need to knit the file
+ if (requiresKnit)
+ {
+ performKnit(encoding);
+ }
+
+ // otherwise we can just either copy or generate the html inline
+ // (for markdown) and return with success
+ else
+ {
+ terminateWithContent(targetFile_, encoding);
+ }
+ }
+
+public:
+ ~HTMLPreview()
+ {
+ }
+
+ // COPYING: prohibited
+
+public:
+
+ bool isRunning() const { return isRunning_; }
+
+ void terminate() { terminationRequested_ = true; }
+
+ bool isMarkdown() { return isMarkdown_; }
+
+ bool isInternalMarkdown() { return isInternalMarkdown_; }
+
+ bool isNotebook() { return isNotebook_; }
+
+ bool requiresKnit() { return requiresKnit_; }
+
+ FilePath targetFile() const
+ {
+ return targetFile_;
+ }
+
+ FilePath targetDirectory() const
+ {
+ return targetFile_.parent();
+ }
+
+ FilePath knitrOutputFile() const
+ {
+ return knitrOutputFile_;
+ }
+
+ std::string readOutput() const
+ {
+ if (outputFile_.empty())
+ return std::string();
+
+ std::string output;
+ Error error = core::readStringFromFile(outputFile_, &output);
+ if (error)
+ LOG_ERROR(error);
+
+ return output;
+ }
+
+ FilePath htmlPreviewFile()
+ {
+ FilePath baseFile = requiresKnit() ? knitrOutputFile() : targetFile();
+ if (isMarkdown())
+ return baseFile.parent().childPath(baseFile.stem() + ".html");
+ else
+ return baseFile;
+ }
+
+private:
+
+ void performKnit(const std::string& encoding)
+ {
+ // set running flag
+ isRunning_ = true;
+
+ // predict the name of the output file -- if we can't do this then
+ // we instrument our call to knitr to return it in a temp file
+ FilePath outputFileTempFile;
+ if (isMarkdown() && targetIsRMarkdown())
+ knitrOutputFile_ = outputFileForTarget(".md");
+ else if (targetFile_.extensionLowerCase() == ".rhtml")
+ knitrOutputFile_= outputFileForTarget(".html");
+ else
+ outputFileTempFile = module_context::tempFile("knitr-output", "out");
+
+ // R binary
+ FilePath rProgramPath;
+ Error error = module_context::rScriptPath(&rProgramPath);
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // args
+ std::vector<std::string> args;
+ args.push_back("--silent");
+ args.push_back("--no-save");
+ args.push_back("--no-restore");
+ args.push_back("-e");
+ if (!knitrOutputFile_.empty())
+ {
+ boost::format fmt;
+
+ fmt = boost::format("require(knitr); "
+ "knit('%2%', encoding='%1%');");
+
+ std::string cmd = boost::str(fmt % encoding % targetFile_.filename());
+ args.push_back(cmd);
+ }
+ else
+ {
+ std::string tempFilePath = string_utils::utf8ToSystem(
+ outputFileTempFile.absolutePath());
+ boost::format fmt;
+ fmt = boost::format("require(knitr); "
+ "knit('%2%', encoding='%1%'); "
+ "cat(o, file='%3%');");
+ std::string cmd = boost::str(fmt % encoding
+ % targetFile_.filename()
+ % tempFilePath);
+ args.push_back(cmd);
+ }
+
+ // options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+ options.redirectStdErrToStdOut = true;
+ options.workingDir = targetFile_.parent();
+
+ // callbacks
+ core::system::ProcessCallbacks cb;
+ cb.onContinue = boost::bind(&HTMLPreview::onKnitContinue,
+ HTMLPreview::shared_from_this());
+ cb.onStdout = boost::bind(&HTMLPreview::onKnitOutput,
+ HTMLPreview::shared_from_this(), _2);
+ cb.onStderr = boost::bind(&HTMLPreview::onKnitOutput,
+ HTMLPreview::shared_from_this(), _2);
+ cb.onExit = boost::bind(&HTMLPreview::onKnitCompleted,
+ HTMLPreview::shared_from_this(),
+ _1, outputFileTempFile, encoding);
+
+ // execute knitr
+ module_context::processSupervisor().runProgram(rProgramPath.absolutePath(),
+ args,
+ options,
+ cb);
+ }
+
+ bool targetIsRMarkdown()
+ {
+ std::string ext = targetFile_.extensionLowerCase();
+ return ext == ".rmd" || ext == ".rmarkdown";
+ }
+
+ bool onKnitContinue()
+ {
+ return !terminationRequested_;
+ }
+
+ void onKnitOutput(const std::string& output)
+ {
+ enqueHTMLPreviewOutput(output);
+ }
+
+ void onKnitCompleted(int exitStatus,
+ const FilePath& outputPathTempFile,
+ const std::string& encoding)
+ {
+ if (exitStatus == EXIT_SUCCESS)
+ {
+ // determine the path of the knitr output file if necessary
+ if (knitrOutputFile_.empty())
+ {
+ std::string outputFile;
+ Error error = core::readStringFromFile(outputPathTempFile,
+ &outputFile);
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+ boost::algorithm::trim(outputFile);
+ knitrOutputFile_ = targetFile_.parent().complete(outputFile);
+ }
+
+ // terminate with content
+ terminateWithContent(knitrOutputFile_, encoding);
+ }
+ else
+ {
+ boost::format fmt("\nknitr terminated with status %1%\n");
+ terminateWithError(boost::str(fmt % exitStatus));
+ }
+ }
+
+ FilePath outputFileForTarget(const std::string& ext)
+ {
+ return targetFile_.parent().childPath(targetFile_.stem() + ext);
+ }
+
+ void terminateWithContent(const FilePath& filePath,
+ const std::string& encoding)
+ {
+ // lookup the custom renderer function and calculate
+ // whether we are going to use it
+ SEXP renderMarkdownSEXP = r::options::getOption(
+ "rstudio.markdownToHTML");
+ r::sexp::Protect rProtect(renderMarkdownSEXP);
+ bool usingCustomMarkdownRenderer =
+ isMarkdown() && !isNotebook() &&
+ !r::sexp::isNull(renderMarkdownSEXP);
+
+ // call custom renderer if necessary
+ if (usingCustomMarkdownRenderer)
+ {
+ // fulfill semantics of calling the custom function
+ // from the directory of the input file
+ RestoreCurrentPathScope restorePathScope(
+ module_context::safeCurrentPath());
+ Error error = filePath.parent().makeCurrentPath();
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // call the function
+ r::exec::RFunction renderMarkdownFunc(renderMarkdownSEXP);
+ renderMarkdownFunc.addParam(
+ string_utils::utf8ToSystem(filePath.filename()));
+ renderMarkdownFunc.addParam(
+ string_utils::utf8ToSystem(htmlPreviewFile().filename()));
+ error = renderMarkdownFunc.call();
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // terminate with the target file
+ terminateWithSuccess(htmlPreviewFile());
+ }
+ else
+ {
+ // read file contents
+ std::string content;
+ Error error = module_context::readAndDecodeFile(filePath,
+ encoding,
+ true,
+ &content);
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // if this is markdown then convert it
+ if (isMarkdown())
+ {
+ // set flag indicating that we used the internal
+ // markdown renderer (so we know to do mathjax,
+ // highlighting, base64 encoding, etc.)
+ isInternalMarkdown_ = true;
+
+ // run markdownToHTML
+ std::string htmlContent;
+ Error error = markdown::markdownToHTML(
+ content,
+ markdown::Extensions(),
+ markdown::HTMLOptions(),
+ &htmlContent);
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // substitute html version
+ content = htmlContent;
+ }
+
+ // create an output file and write to it
+ FilePath outputFile = createOutputFile();
+ error = core::writeStringToFile(outputFile, content);
+ if (error)
+ terminateWithError(error);
+ else
+ terminateWithSuccess(outputFile);
+ }
+ }
+
+
+ void terminateWithError(const Error& error)
+ {
+ std::string message =
+ "Error generating HTML preview for " +
+ module_context::createAliasedPath(targetFile_) + " " +
+ error.summary();
+ terminateWithError(message);
+ }
+
+ void terminateWithError(const std::string& message)
+ {
+ isRunning_ = false;
+ enqueHTMLPreviewOutput(message);
+ enqueHTMLPreviewFailed();
+ }
+
+ void terminateWithSuccess(const FilePath& outputFile)
+ {
+ isRunning_ = false;
+ outputFile_ = outputFile;
+
+ enqueHTMLPreviewSucceeded(kHTMLPreview "/",
+ targetFile(),
+ htmlPreviewFile(),
+ isMarkdown(),
+ !isNotebook());
+ }
+
+ static void enqueHTMLPreviewStarted(const FilePath& targetFile)
+ {
+ json::Object dataJson;
+ dataJson["target_file"] = module_context::createAliasedPath(targetFile);
+ ClientEvent event(client_events::kHTMLPreviewStartedEvent, dataJson);
+ module_context::enqueClientEvent(event);
+ }
+
+ static void enqueHTMLPreviewOutput(const std::string& output)
+ {
+ ClientEvent event(client_events::kHTMLPreviewOutputEvent, output);
+ module_context::enqueClientEvent(event);
+ }
+
+ static void enqueHTMLPreviewFailed()
+ {
+ json::Object resultJson;
+ resultJson["succeeded"] = false;
+ ClientEvent event(client_events::kHTMLPreviewCompletedEvent, resultJson);
+ module_context::enqueClientEvent(event);
+ }
+
+ static void enqueHTMLPreviewSucceeded(const std::string& previewUrl,
+ const FilePath& sourceFile,
+ const FilePath& htmlFile,
+ bool enableSaveAs,
+ bool enableRefresh)
+ {
+ json::Object resultJson;
+ resultJson["succeeded"] = true;
+ resultJson["source_file"] = module_context::createAliasedPath(sourceFile);
+ resultJson["html_file"] = module_context::createAliasedPath(htmlFile);
+ resultJson["preview_url"] = previewUrl;
+ resultJson["enable_saveas"] = enableSaveAs;
+ resultJson["enable_refresh"] = enableRefresh;
+ resultJson["previously_published"] = !rpubs::previousUploadId(htmlFile).empty();
+ ClientEvent event(client_events::kHTMLPreviewCompletedEvent, resultJson);
+ module_context::enqueClientEvent(event);
+ }
+
+ static FilePath createOutputFile()
+ {
+ return module_context::tempFile("html_preview", "htm");
+ }
+
+private:
+ FilePath targetFile_;
+ bool isMarkdown_;
+ bool isInternalMarkdown_;
+ bool isNotebook_;
+ bool requiresKnit_;
+
+ FilePath knitrOutputFile_;
+ FilePath outputFile_;
+ bool isRunning_;
+ bool terminationRequested_;
+};
+
+std::string deriveNotebookPath(const std::string& path)
+{
+ if (path.size() > 2
+ && (boost::algorithm::ends_with(path, ".r") ||
+ boost::algorithm::ends_with(path, ".R")))
+ {
+ return path.substr(0, path.size()-2) + ".Rmd";
+ }
+ return path + ".Rmd";
+}
+
+// current preview (stays around after the preview executes so it can
+// serve the web content back)
+boost::shared_ptr<HTMLPreview> s_pCurrentPreview_;
+
+bool isPreviewRunning()
+{
+ return s_pCurrentPreview_ && s_pCurrentPreview_->isRunning();
+}
+
+Error previewHTML(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string file, encoding;
+ bool isMarkdown, knit, isNotebook;
+ Error error = json::readObjectParam(request.params, 0,
+ "path", &file,
+ "encoding", &encoding,
+ "is_markdown", &isMarkdown,
+ "requires_knit", &knit,
+ "is_notebook", &isNotebook);
+
+ if (isNotebook)
+ file = deriveNotebookPath(file);
+
+ if (error)
+ return error;
+ FilePath filePath = module_context::resolveAliasedPath(file);
+
+ // if we have a preview already running then just return false
+ if (isPreviewRunning())
+ {
+ pResponse->setResult(false);
+ }
+ else
+ {
+ s_pCurrentPreview_ = HTMLPreview::create(filePath,
+ encoding,
+ isMarkdown,
+ isNotebook,
+ knit);
+ pResponse->setResult(true);
+ }
+
+ return Success();
+}
+
+
+Error terminatePreviewHTML(const json::JsonRpcRequest&,
+ json::JsonRpcResponse*)
+{
+ if (isPreviewRunning())
+ s_pCurrentPreview_->terminate();
+
+ return Success();
+}
+
+Error getHTMLCapabilities(const json::JsonRpcRequest&,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(html_preview::capabilitiesAsJson());
+ return Success();
+}
+
+
+const char* const MAGIC_GUID = "12861c30b10411e1afa60800200c9a66";
+const char* const FIGURE_DIR = "figure-compile-notebook-12861c30b";
+
+bool okToGenerateFile(const FilePath& rmdPath,
+ const std::string& extension,
+ std::string* pErrMsg)
+{
+ FilePath filePath = rmdPath.parent().complete(
+ rmdPath.stem() + extension);
+
+ if (filePath.exists())
+ {
+ boost::shared_ptr<std::istream> pStr;
+ Error error = filePath.open_r(&pStr);
+ if (error)
+ {
+ *pErrMsg = "Error opening file: " + error.summary();
+ return false;
+ }
+
+ std::string magicGuid(MAGIC_GUID);
+ std::istreambuf_iterator<char> eod;
+ if (eod == std::search(std::istreambuf_iterator<char>(*pStr),
+ eod,
+ magicGuid.begin(),
+ magicGuid.end()))
+ {
+ *pErrMsg = "Unable to generate the file '" +
+ filePath.filename() + "' because it already "
+ "exists.\n\n"
+ "You need to move or delete this file prior to "
+ "compiling a notebook for this R script.";
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool okToGenerateFiles(const FilePath& rmdPath, std::string* pErrMsg)
+{
+ return okToGenerateFile(rmdPath, ".Rmd", pErrMsg) &&
+ okToGenerateFile(rmdPath, ".md", pErrMsg) &&
+ okToGenerateFile(rmdPath, ".html", pErrMsg);
+}
+
+Error createNotebook(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string id, prefix, suffix, type;
+ bool sessionInfo;
+ Error error = json::readObjectParam(request.params, 0,
+ "id", &id,
+ "prefix", &prefix,
+ "suffix", &suffix,
+ "session_info", &sessionInfo,
+ "notebook_type", &type);
+ if (error)
+ return error;
+
+ boost::shared_ptr<source_database::SourceDocument> pDoc(
+ new source_database::SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error;
+
+ std::string path = pDoc->path();
+ path = deriveNotebookPath(path);
+
+ FilePath realPath = module_context::resolveAliasedPath(path);
+
+ std::string signature =
+ "\n<!-- Automatically generated by RStudio [" +
+ std::string(MAGIC_GUID) + "] -->\n";
+
+ // make sure we can overwrite any existing files
+ std::string errMsg;
+ if (!okToGenerateFiles(realPath, &errMsg))
+ {
+ json::Object resultJson;
+ resultJson["succeeded"] = false;
+ resultJson["failure_message"] = errMsg;
+ pResponse->setResult(resultJson);
+ return Success();
+ }
+
+ if (type == "default")
+ {
+ // Add the signature to the prefix
+ prefix = signature + prefix;
+
+ error = r::util::iconvstr(prefix,
+ "UTF-8",
+ pDoc->encoding(),
+ true,
+ &prefix);
+ if (error)
+ return error;
+ error = r::util::iconvstr(suffix,
+ "UTF-8",
+ pDoc->encoding(),
+ true,
+ &suffix);
+ if (error)
+ return error;
+
+ std::string contents;
+ contents.append(prefix);
+ contents.append("`r opts_chunk$set(tidy=FALSE, comment=NA, "
+ "fig.path='" +std::string(FIGURE_DIR) + "/')`");
+ contents.append("\n");
+ contents.append("```{r}\n");
+ contents.append(pDoc->contents());
+ contents.append("\n```\n");
+ contents.append(suffix);
+
+ // write the file
+ error = writeStringToFile(realPath, contents);
+ if (error)
+ return error;
+ }
+ else
+ {
+ // switch to the source document's directory for this operation
+ RestoreCurrentPathScope restorePathScope(
+ module_context::safeCurrentPath());
+
+ FilePath scriptPath = module_context::resolveAliasedPath(
+ pDoc->path());
+ Error error = scriptPath.parent().makeCurrentPath();
+ if (error)
+ return error;
+
+ // determine the function to be used to convert .R to .Rmd
+ std::string rmdFunction;
+ if (type == "spin")
+ rmdFunction = ".rs.spinScript";
+ else
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+
+ // call the function
+ std::string scriptFileName =
+ string_utils::utf8ToSystem(scriptPath.filename());
+ error = r::exec::RFunction(rmdFunction,
+ scriptFileName,
+ signature).call();
+ if (error)
+ {
+ json::Object result;
+ result["succeeded"] = false;
+ result["failure_message"] = r::endUserErrorMessage(error);
+ pResponse->setResult(result);
+ return Success();
+ }
+ }
+
+ // return success
+ json::Object resultJson;
+ resultJson["succeeded"] = true;
+ pResponse->setResult(resultJson);
+ return Success();
+}
+
+
+bool requiresHighlighting(const std::string& htmlOutput)
+{
+ boost::regex hlRegex("<pre><code class=\"(r|cpp)\"");
+ return boost::regex_search(htmlOutput, hlRegex);
+}
+
+
+// for whatever reason when we host an iFrame in a Qt WebKit instance
+// it only looks at the very first font listed in the font-family
+// attribute. if the font isn't found then it displays a non-monospace
+// font by default. so to make preview mode always work we need to
+// order the font-family list according to the current platform.
+std::string preFontFamily()
+{
+ std::vector<std::string> linuxFonts;
+ linuxFonts.push_back("'DejaVu Sans Mono'");
+ linuxFonts.push_back("'Droid Sans Mono'");
+
+ std::vector<std::string> windowsFonts;
+ windowsFonts.push_back("'Lucida Console'");
+ windowsFonts.push_back("Consolas");
+
+ std::vector<std::string> macFonts;
+ macFonts.push_back("Monaco");
+
+ std::vector<std::string> universalFonts;
+ universalFonts.push_back("monospace");
+
+ std::vector<std::string> fonts;
+#if defined(__APPLE__)
+ fonts.insert(fonts.end(), macFonts.begin(), macFonts.end());
+ fonts.insert(fonts.end(), linuxFonts.begin(), linuxFonts.end());
+ fonts.insert(fonts.end(), windowsFonts.begin(), windowsFonts.end());
+#elif defined(_WIN32)
+ fonts.insert(fonts.end(), windowsFonts.begin(), windowsFonts.end());
+ fonts.insert(fonts.end(), linuxFonts.begin(), linuxFonts.end());
+ fonts.insert(fonts.end(), macFonts.begin(), macFonts.end());
+#else
+ fonts.insert(fonts.end(), linuxFonts.begin(), linuxFonts.end());
+ fonts.insert(fonts.end(), windowsFonts.begin(), windowsFonts.end());
+ fonts.insert(fonts.end(), macFonts.begin(), macFonts.end());
+#endif
+ fonts.insert(fonts.end(), universalFonts.begin(), universalFonts.end());
+
+ return boost::algorithm::join(fonts, ", ");
+}
+
+
+void modifyOutputForPreview(std::string* pOutput)
+{
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ // use correct font ordering for this platform
+ *pOutput = boost::regex_replace(
+ *pOutput,
+ boost::regex("tt, code, pre \\{\\n\\s+font-family:[^\n]+;"),
+ "tt, code, pre {\n font-family: " + preFontFamily() + ";");
+
+#ifdef __APPLE__
+ // use SVG fonts on MacOS (because HTML-CSS fonts crash QtWebKit)
+ boost::algorithm::replace_first(
+ *pOutput,
+ "config=TeX-AMS-MML_HTMLorMML",
+ "config=TeX-AMS-MML_SVG");
+#else
+ // add HTML-CSS options required for correct qtwebkit rendering
+ std::string target = "<!-- MathJax scripts -->";
+ boost::algorithm::replace_first(
+ *pOutput,
+ target,
+ target + "\n"
+ "<script type=\"text/x-mathjax-config\">"
+ "MathJax.Hub.Config({"
+ " \"HTML-CSS\": { minScaleAdjust: 125, availableFonts: [] } "
+ " });"
+ "</script>");
+#endif
+ }
+
+ // serve mathjax locally
+ std::string previewMathjax = "mathjax";
+ boost::algorithm::replace_first(
+ *pOutput,
+ "https://c328740.ssl.cf1.rackcdn.com/mathjax/2.0-latest",
+ previewMathjax);
+}
+
+
+Error readPreviewTemplate(const FilePath& resPath,
+ std::string* pPreviewTemplate)
+{
+
+ FilePath htmlPreviewFile = resPath.childPath("markdown.html");
+ return core::readStringFromFile(htmlPreviewFile, pPreviewTemplate);
+}
+
+void setVarFromHtmlResourceFile(const std::string& name,
+ const std::string& fileName,
+ std::map<std::string,std::string>* pVars)
+{
+ std::string fileContents = module_context::resourceFileAsString(fileName);
+ (*pVars)[name] = fileContents;
+}
+
+void setVarFromHtmlResourceFile(const std::string& name,
+ std::map<std::string,std::string>* pVars)
+{
+ setVarFromHtmlResourceFile(name, name + ".html", pVars);
+}
+
+void handleInternalMarkdownPreviewRequest(
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ try
+ {
+ // read input template
+ FilePath resPath = session::options().rResourcesPath();
+ std::string previewTemplate;
+ Error error = readPreviewTemplate(resPath, &previewTemplate);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // read output
+ std::string htmlOutput = s_pCurrentPreview_->readOutput();
+
+ // define template filter
+ std::map<std::string,std::string> vars;
+ vars["title"] = html_utils::defaultTitle(htmlOutput);
+ setVarFromHtmlResourceFile("markdown_css", "markdown.css", &vars);
+ if (requiresHighlighting(htmlOutput))
+ setVarFromHtmlResourceFile("r_highlight", &vars);
+ else
+ vars["r_highlight"] = "";
+ if (markdown::isMathJaxRequired(htmlOutput))
+ setVarFromHtmlResourceFile("mathjax", &vars);
+ else
+ vars["mathjax"] = "";
+ vars["html_output"] = htmlOutput;
+ text::TemplateFilter templateFilter(vars);
+
+ // define base64 image filter
+ html_utils::Base64ImageFilter imageFilter(
+ s_pCurrentPreview_->targetDirectory());
+
+ // write into in-memory string
+ std::istringstream previewInputStream(previewTemplate);
+ std::stringstream previewStrStream;
+ previewStrStream.exceptions(std::istream::failbit | std::istream::badbit);
+ boost::iostreams::filtering_ostream previewOutputStream ;
+ previewOutputStream.push(templateFilter);
+ previewOutputStream.push(imageFilter);
+ previewOutputStream.push(previewStrStream);
+ boost::iostreams::copy(previewInputStream,
+ previewOutputStream,
+ 128);
+
+
+ // write to output file
+ std::string previewHtml = previewStrStream.str();
+ error = core::writeStringToFile(s_pCurrentPreview_->htmlPreviewFile(),
+ previewHtml);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // remove generated files if this was a notebook
+ if (s_pCurrentPreview_->isNotebook())
+ {
+ error = s_pCurrentPreview_->targetFile().removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ error = s_pCurrentPreview_->knitrOutputFile()
+ .removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ error = s_pCurrentPreview_->targetDirectory()
+ .complete(FIGURE_DIR).removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // modify outpout then write back to client
+ modifyOutputForPreview(&previewHtml);
+ pResponse->setDynamicHtml(previewHtml, request);
+ }
+ catch(const std::exception& e)
+ {
+ Error error = systemError(boost::system::errc::io_error,
+ ERROR_LOCATION);
+ error.addProperty("what", e.what());
+ LOG_ERROR(error);
+ pResponse->setError(error);
+ }
+}
+
+void handlePreviewRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // if there isn't a current preview this is an error
+ if (!s_pCurrentPreview_)
+ {
+ pResponse->setError(http::status::NotFound, "No preview available");
+ return;
+ }
+
+ // disable caching entirely
+ pResponse->setNoCacheHeaders();
+
+ // get the requested path
+ std::string path = http::util::pathAfterPrefix(request,
+ kHTMLPreviewLocation);
+
+ // if it is empty then this is the main request
+ if (path.empty())
+ {
+ if (s_pCurrentPreview_->isMarkdown())
+ {
+ if (s_pCurrentPreview_->isInternalMarkdown())
+ handleInternalMarkdownPreviewRequest(request, pResponse);
+ else
+ pResponse->setFile(s_pCurrentPreview_->htmlPreviewFile(),
+ request);
+ }
+ else if (s_pCurrentPreview_->requiresKnit())
+ {
+ pResponse->setFile(s_pCurrentPreview_->knitrOutputFile(), request);
+ }
+ else
+ {
+ pResponse->setFile(s_pCurrentPreview_->targetFile(), request);
+ }
+ }
+
+ // request for mathjax file
+ else if (boost::algorithm::starts_with(path, "mathjax"))
+ {
+ FilePath filePath =
+ session::options().mathjaxPath().parent().childPath(path);
+ pResponse->setFile(filePath, request);
+ }
+
+ // request for dependent file
+ else
+ {
+ FilePath filePath = s_pCurrentPreview_->targetDirectory().childPath(path);
+ pResponse->setFile(filePath, request);
+ }
+}
+
+
+} // anonymous namespace
+
+core::json::Object capabilitiesAsJson()
+{
+ // default to unsupported
+ json::Object capsJson;
+ capsJson["r_markdown_supported"] = false;
+ capsJson["stitch_supported"] = false;
+
+ // query for required versions
+ std::string markdownVersion = "1.2";
+ std::string stitchVersion = "1.2";
+ r::sexp::Protect rProtect;
+ SEXP capsSEXP;
+ r::exec::RFunction func(".rs.getHTMLCapabilities");
+ func.addParam(markdownVersion);
+ func.addParam(stitchVersion);
+ Error error = func.call(&capsSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ else
+ {
+ json::Value valJson;
+ error = r::json::jsonValueFromList(capsSEXP, &valJson);
+ if (error)
+ LOG_ERROR(error);
+ else if (core::json::isType<core::json::Object>(valJson))
+ capsJson = valJson.get_obj();
+ }
+
+ return capsJson;
+}
+
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionHTMLPreview.R"))
+ (bind(registerRpcMethod, "preview_html", previewHTML))
+ (bind(registerRpcMethod, "terminate_preview_html", terminatePreviewHTML))
+ (bind(registerRpcMethod, "get_html_capabilities", getHTMLCapabilities))
+ (bind(registerRpcMethod, "create_notebook", createNotebook))
+ (bind(registerUriHandler, kHTMLPreviewLocation, handlePreviewRequest))
+ ;
+ return initBlock.execute();
+}
+
+
+
+} // namespace html_preview
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionHTMLPreview.hpp b/src/cpp/session/modules/SessionHTMLPreview.hpp
new file mode 100644
index 0000000..cdd47e9
--- /dev/null
+++ b/src/cpp/session/modules/SessionHTMLPreview.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionHTMLPreview.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HTML_PREVIEW_HPP
+#define SESSION_HTML_PREVIEW_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace html_preview {
+
+core::json::Object capabilitiesAsJson();
+
+core::Error initialize();
+
+} // namespace html_preview
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_HTML_PREVIEW_HPP
diff --git a/src/cpp/session/modules/SessionHelp.R b/src/cpp/session/modules/SessionHelp.R
new file mode 100644
index 0000000..0bb6a28
--- /dev/null
+++ b/src/cpp/session/modules/SessionHelp.R
@@ -0,0 +1,163 @@
+#
+# SessionHelp.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# use html help
+options(help_type = "html")
+
+.rs.addFunction( "httpdPort", function()
+{
+ as.character(tools:::httpdPort)
+})
+
+.rs.addFunction("initHelp", function(port, isDesktop)
+{
+ # function to set the help port directly
+ setHelpPort <- function() {
+ env <- environment(tools::startDynamicHelp)
+ unlockBinding("httpdPort", env)
+ assign("httpdPort", port, envir = env)
+ lockBinding("httpdPort", env)
+ }
+
+ # for desktop mode see if R can successfully initialize the httpd
+ # server -- if it can't then perhaps localhost ports are blocked,
+ # in this case we take over help entirely
+ if (isDesktop)
+ {
+ # start the help server if it hasn't previously been started
+ # (suppress warnings and messages because if there is a problem
+ # binding to a local port we are going to patch this up by
+ # redirecting all traffic to our local peer)
+ if (tools:::httpdPort <= 0L)
+ suppressWarnings(suppressMessages(tools::startDynamicHelp()))
+
+ # if couldn't start it then set the help port directly so that
+ # help requests still flow through our local peer connection
+ if (tools:::httpdPort <= 0L)
+ {
+ setHelpPort()
+ return (TRUE)
+ }
+ else
+ {
+ return (FALSE)
+ }
+ }
+ # always take over help in server mode
+ else
+ {
+ # stop the help server if it was previously started e.g. by .Rprofile
+ if (tools:::httpdPort > 0L)
+ suppressMessages(tools::startDynamicHelp(start=FALSE))
+
+ # set the help port
+ setHelpPort()
+
+ # indicate we should handle custom internally
+ return (TRUE)
+ }
+})
+
+.rs.addFunction( "handlerLookupError", function(path, query=NULL, ...)
+{
+ payload = paste(
+ "<h3>R Custom HTTP Handler Not Found</h3>",
+ "<p>Unable to locate custom HTTP handler for",
+ "<i>", path, "</i>",
+ "<p>Is the package which implements this HTTP handler loaded?</p>")
+
+ list(payload, "text/html", character(), 404)
+});
+
+.rs.addJsonRpcHandler("suggest_topics", function(prefix)
+{
+ if (getRversion() >= "3.0.0")
+ sort(utils:::matchAvailableTopics("", prefix))
+ else
+ sort(utils:::matchAvailableTopics(prefix))
+});
+
+.rs.addJsonRpcHandler("get_help", function(topic, package, options)
+{
+ helpfiles = help(topic, help_type="html")
+ if (length(helpfiles) <= 0)
+ return ()
+
+ file = helpfiles[[1]]
+ path <- dirname(file)
+ dirpath <- dirname(path)
+ pkgname <- basename(dirpath)
+
+ html = tools:::httpd(paste("/library/",
+ pkgname,
+ "/html/",
+ basename(file),
+ ".html", sep=""),
+ NULL,
+ NULL)$payload
+
+ match = suppressWarnings(regexpr('<body>.*</body>', html))
+ if (match < 0)
+ {
+ html = NULL
+ }
+ else
+ {
+ html = substring(html, match + 6, match + attr(match, 'match.length') - 1 - 7)
+
+ match = suppressWarnings(regexpr('<h3>Details</h3>', html))
+ if (match >= 0)
+ html = substring(html, 1, match - 1)
+ }
+
+ obj = tryCatch(get(topic, pos=globalenv()),
+ error = function(e) NULL)
+
+ if (is.function(obj))
+ {
+ sig = .rs.getSignature(obj)
+ sig = gsub('^function ', topic, sig)
+ }
+ else
+ {
+ sig = NULL
+ }
+
+ list('html' = html, 'signature' = sig, 'pkgname' = pkgname)
+});
+
+.rs.addJsonRpcHandler("show_help_topic", function(topic, package)
+{
+ if (!is.null(package))
+ require(package, character.only = TRUE)
+ print(help(topic, help_type="html"))
+})
+
+.rs.addJsonRpcHandler("search", function(query)
+{
+ exactMatch = help(query, help_type="html")
+ if (length(exactMatch) == 1)
+ {
+ print(exactMatch)
+ return ()
+ }
+ else
+ {
+ paste("help/doc/html/Search?pattern=",
+ utils::URLencode(query, reserved = TRUE),
+ "&title=1&keyword=1&alias=1",
+ sep = "")
+ }
+})
diff --git a/src/cpp/session/modules/SessionHelp.cpp b/src/cpp/session/modules/SessionHelp.cpp
new file mode 100644
index 0000000..524eaa1
--- /dev/null
+++ b/src/cpp/session/modules/SessionHelp.cpp
@@ -0,0 +1,917 @@
+/*
+ * SessionHelp.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionHelp.hpp"
+
+#include <algorithm>
+
+#include <boost/regex.hpp>
+#include <boost/function.hpp>
+#include <boost/format.hpp>
+#include <boost/range/iterator_range.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/iostreams/filter/aggregate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/Log.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#include <core/http/URL.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/r_util/RPackageInfo.hpp>
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RInternal.hpp>
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/ROptions.hpp>
+#include <r/RUtil.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "presentation/SlideRequestHandler.hpp"
+
+// protect R against windows TRUE/FALSE defines
+#undef TRUE
+#undef FALSE
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace help {
+
+namespace {
+
+// save computed help url prefix for comparison in rHelpUrlHandler
+const char * const kHelpLocation = "/help";
+const char * const kCustomLocation = "/custom";
+const char * const kSessionLocation = "/session";
+
+// flag indicating whether we should send headers to custom handlers
+// (only do this for 2.13 or higher)
+bool s_provideHeaders = false;
+
+// are we handling custom urls internally or allowing them to
+// show in an external browser
+bool s_handleCustom = false;
+
+std::string localURL(const std::string& address, const std::string& port)
+{
+ return "http://" + address + ":" + port + "/";
+}
+
+std::string replaceRPort(const std::string& url, const std::string& rPort)
+{
+ std::string newUrl = url;
+ boost::algorithm::replace_last(newUrl, rPort, session::options().wwwPort());
+ return newUrl;
+}
+
+bool isLocalURL(const std::string& url,
+ const std::string& scope,
+ std::string* pLocalURLPath = NULL)
+{
+ // first look for local ip prefix
+ std::string rPort = module_context::rLocalHelpPort();
+ std::string urlPrefix = localURL("127.0.0.1", rPort);
+ size_t pos = url.find(urlPrefix + scope);
+ if (pos != std::string::npos)
+ {
+ std::string relativeUrl = url.substr(urlPrefix.length());
+ if (pLocalURLPath)
+ *pLocalURLPath = replaceRPort(relativeUrl, rPort);
+ return true;
+ }
+
+ // next look for localhost
+ urlPrefix = localURL("localhost", rPort);
+ pos = url.find(urlPrefix + scope);
+ if (pos != std::string::npos)
+ {
+ std::string relativeUrl = url.substr(urlPrefix.length());
+ if (pLocalURLPath)
+ *pLocalURLPath = replaceRPort(relativeUrl, rPort);
+ return true;
+ }
+
+ // none found
+ return false;
+}
+
+
+// hook the browseURL function to look for calls to the R internal http
+// server. for custom URLs remap the address to remote and then fire
+// the browse_url event. for help URLs fire the appropraite show_help event
+bool handleLocalHttpUrl(const std::string& url)
+{
+ // check for custom
+ std::string customPath;
+ if (isLocalURL(url, "custom", &customPath))
+ {
+ if (s_handleCustom)
+ {
+ ClientEvent event = browseUrlEvent(customPath);
+ module_context::enqueClientEvent(event);
+ return true;
+ }
+ else // leave alone (show in external browser)
+ {
+ return false;
+ }
+ }
+
+ // check for session
+ std::string sessionPath;
+ if (isLocalURL(url, "session", &sessionPath))
+ {
+ if (s_handleCustom)
+ {
+ ClientEvent event = browseUrlEvent(sessionPath);
+ module_context::enqueClientEvent(event);
+ return true;
+ }
+ else // leave alone (show in external browser)
+ {
+ return false;
+ }
+ }
+
+ // leave portmapped urls alone
+ if (isLocalURL(url, "p/"))
+ {
+ return false;
+ }
+
+ // otherwise look for help (which would be all other localhost urls)
+ std::string helpPath;
+ if (isLocalURL(url, "", &helpPath))
+ {
+ helpPath = "help/" + helpPath;
+ ClientEvent helpEvent(client_events::kShowHelp, helpPath);
+ module_context::enqueClientEvent(helpEvent);
+ return true;
+ }
+
+ // other localhost URLs can benefit from port mapping -- we map them
+ // all since if we don't do any mapping they'll just fail hard
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ // see if we can form a portmap path for this url
+ std::string path;
+ if (module_context::portmapPathForLocalhostUrl(url, &path))
+ {
+ module_context::enqueClientEvent(browseUrlEvent(path));
+ return true;
+ }
+ }
+
+ // wasn't a url of interest
+ return false;
+}
+
+// As of R 2.10 RShowDoc still uses the legacy file::// mechanism for
+// displaying the manual. Redirect these to the appropriate help event
+bool handleRShowDocFile(const core::FilePath& filePath)
+{
+ boost::regex manualRegx(".*/lib/R/(doc/manual/[A-Za-z0-9_\\-]*\\.html)");
+ boost::smatch match;
+ if (regex_match(filePath.absolutePath(), match, manualRegx))
+ {
+ ClientEvent helpEvent(client_events::kShowHelp, match[1]);
+ module_context::enqueClientEvent(helpEvent);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// javascript callbacks to inject into page
+const char * const kJsCallbacks =
+ "<script type=\"text/javascript\">\n"
+ "if (window.parent.helpNavigated)\n"
+ " window.parent.helpNavigated(document, window);\n"
+ "if (window.parent.helpKeydown)\n"
+ " window.onkeydown = function(e) {window.parent.helpKeydown(e);}\n"
+ "</script>\n";
+
+
+
+class HelpContentsFilter : public boost::iostreams::aggregate_filter<char>
+{
+public:
+ typedef std::vector<char> Characters ;
+
+ HelpContentsFilter(const http::Request& request)
+ {
+ requestUri_ = request.uri();
+ }
+
+ void do_filter(const Characters& src, Characters& dest)
+ {
+ std::string baseUrl = http::URL::uncomplete(
+ requestUri_,
+ kHelpLocation);
+
+ // fixup hard-coded hrefs
+ Characters tempDest1;
+ boost::algorithm::replace_all_copy(
+ std::back_inserter(tempDest1),
+ boost::make_iterator_range(src.begin(), src.end()),
+ "href=\"/",
+ "href=\"" + baseUrl + "/");
+ Characters tempDest2;
+ boost::algorithm::replace_all_copy(
+ std::back_inserter(tempDest2),
+ boost::make_iterator_range(tempDest1.begin(), tempDest1.end()),
+ "href='/",
+ "href='" + baseUrl + "/");
+
+ // fixup hard-coded src=
+ Characters tempDest3;
+ boost::algorithm::replace_all_copy(
+ std::back_inserter(tempDest3),
+ boost::make_iterator_range(tempDest2.begin(), tempDest2.end()),
+ "src=\"/",
+ "src=\"" + baseUrl + "/");
+ boost::algorithm::replace_all_copy(
+ std::back_inserter(dest),
+ boost::make_iterator_range(tempDest3.begin(), tempDest3.end()),
+ "src='/",
+ "src='" + baseUrl + "/");
+
+ // append javascript callbacks
+ std::string js(kJsCallbacks);
+ std::copy(js.begin(), js.end(), std::back_inserter(dest));
+ }
+private:
+ std::string requestUri_;
+};
+
+
+template <typename Filter>
+void setDynamicContentResponse(const std::string& content,
+ const http::Request& request,
+ const Filter& filter,
+ http::Response* pResponse)
+{
+ // always attempt gzip
+ if (request.acceptsEncoding(http::kGzipEncoding))
+ pResponse->setContentEncoding(http::kGzipEncoding);
+
+ // if the response doesn't already have Cache-Control then
+ // send an eTag back and force revalidation
+ if (!pResponse->containsHeader("Cache-Control"))
+ {
+ // force cache revalidation since this is dynamic content
+ pResponse->setCacheWithRevalidationHeaders();
+
+ // set as cacheable content (uses eTag/If-None-Match)
+ Error error = pResponse->setCacheableBody(content, request, filter);
+ if (error)
+ {
+ pResponse->setError(http::status::InternalServerError,
+ error.code().message());
+ }
+ }
+ // otherwise just leave it alone
+ else
+ {
+ pResponse->setBody(content, filter);
+ }
+}
+
+
+void setDynamicContentResponse(const std::string& content,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ http::NullOutputFilter nullFilter;
+ setDynamicContentResponse(content, request, nullFilter, pResponse);
+}
+
+
+template <typename Filter>
+void handleHttpdResult(SEXP httpdSEXP,
+ const http::Request& request,
+ const Filter& htmlFilter,
+ http::Response* pResponse)
+{
+ // NOTE: this function is a port of process_request in Rhttpd.c
+ // (that function is coupled to sending its results via the R http daemon,
+ // since we need to send the results via our daemon we need our own
+ // implemetnation of the function). The port was completed 10/28/2009 so
+ // diffs in this function subsequent to that should be accounted for
+
+ // defaults
+ int code = 200;
+ const char * const kTextHtml = "text/html";
+ std::string contentType(kTextHtml);
+ std::vector<std::string> headers;
+
+ // if present, second element is content type
+ if (LENGTH(httpdSEXP) > 1)
+ {
+ SEXP ctSEXP = VECTOR_ELT(httpdSEXP, 1);
+ if (TYPEOF(ctSEXP) == STRSXP && LENGTH(ctSEXP) > 0)
+ contentType = CHAR(STRING_ELT(ctSEXP, 0));
+ }
+
+ // if present, third element is headers vector
+ if (LENGTH(httpdSEXP) > 2)
+ {
+ SEXP headersSEXP = VECTOR_ELT(httpdSEXP, 2);
+ if (TYPEOF(headersSEXP) == STRSXP)
+ r::sexp::extract(headersSEXP, &headers);
+ }
+
+ // if present, fourth element is HTTP code
+ if (LENGTH(httpdSEXP) > 3)
+ {
+ code = r::sexp::asInteger(VECTOR_ELT(httpdSEXP, 3));
+ }
+
+ // setup response
+ pResponse->setStatusCode(code);
+ pResponse->setContentType(contentType);
+
+ // set headers
+ std::for_each(headers.begin(),
+ headers.end(),
+ boost::bind(&http::Response::setHeaderLine, pResponse, _1));
+
+ // check payload
+ SEXP payloadSEXP = VECTOR_ELT(httpdSEXP, 0);
+
+ // payload = string
+ if ((TYPEOF(payloadSEXP) == STRSXP || TYPEOF(payloadSEXP) == VECSXP) &&
+ LENGTH(payloadSEXP) > 0)
+ {
+ // get the names and the content string
+ SEXP namesSEXP = r::sexp::getNames(httpdSEXP);
+ std::string content;
+ if (TYPEOF(payloadSEXP) == STRSXP)
+ content = r::sexp::asString(STRING_ELT(payloadSEXP, 0));
+ else if (TYPEOF(payloadSEXP) == VECSXP)
+ content = r::sexp::asString(VECTOR_ELT(payloadSEXP, 0));
+
+ // check for special file returns
+ std::string fileName ;
+ if (TYPEOF(namesSEXP) == STRSXP && LENGTH(namesSEXP) > 0 &&
+ !std::strcmp(CHAR(STRING_ELT(namesSEXP, 0)), "file"))
+ {
+ fileName = content;
+ }
+ else if (LENGTH(payloadSEXP) > 1 && content == "*FILE*")
+ {
+ fileName = CHAR(STRING_ELT(payloadSEXP, 1));
+ }
+
+ // set the body
+ if (!fileName.empty()) // from file
+ {
+ // get file path
+ FilePath filePath(fileName);
+
+ // cache with revalidation
+ pResponse->setCacheWithRevalidationHeaders();
+
+ // read file contents
+ std::string contents;
+ Error error = readStringFromFile(filePath, &contents);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // set body (apply filter to html)
+ if (pResponse->contentType() == kTextHtml)
+ {
+ pResponse->setCacheableBody(contents, request, htmlFilter);
+ }
+ else
+ {
+ pResponse->setCacheableBody(contents, request);
+ }
+ }
+ else // from dynamic content
+ {
+ if (code == http::status::Ok)
+ {
+ // set body (apply filter to html)
+ if (pResponse->contentType() == kTextHtml)
+ {
+ setDynamicContentResponse(content,
+ request,
+ htmlFilter,
+ pResponse);
+ }
+ else
+ {
+ setDynamicContentResponse(content, request, pResponse);
+ }
+ }
+ else // could be a redirect or something else, don't interfere
+ {
+ pResponse->setBodyUnencoded(content);
+ }
+ }
+ }
+
+ // payload = raw buffer
+ else if (TYPEOF(payloadSEXP) == RAWSXP)
+ {
+ std::string bytes((char*)(RAW(payloadSEXP)), LENGTH(payloadSEXP));
+ setDynamicContentResponse(bytes, request, pResponse);
+ }
+
+ // payload = unexpected type
+ else
+ {
+ pResponse->setError(http::status::InternalServerError,
+ "Invalid response from R");
+ }
+}
+
+
+// mirrors parse_query in Rhttpd.c
+SEXP parseQuery(const http::Fields& fields, r::sexp::Protect* pProtect)
+{
+ if (fields.empty())
+ return R_NilValue;
+ else
+ return r::sexp::create(fields, pProtect);
+}
+
+// mirrors parse_request_body in Rhttpd.c
+SEXP parseRequestBody(const http::Request& request, r::sexp::Protect* pProtect)
+{
+ if (request.body().empty())
+ {
+ return R_NilValue;
+ }
+ else if (request.contentType() == "application/x-www-form-urlencoded")
+ {
+ return parseQuery(request.formFields(), pProtect);
+ }
+ else
+ {
+ // body bytes
+ int contentLength = request.body().length();
+ SEXP bodySEXP;
+ pProtect->add(bodySEXP = Rf_allocVector(RAWSXP, contentLength));
+ if (contentLength > 0)
+ ::memcpy(RAW(bodySEXP), request.body().c_str(), contentLength);
+
+ // content type
+ if (!request.contentType().empty())
+ {
+ Rf_setAttrib(bodySEXP,
+ Rf_install("content-type"),
+ Rf_mkString(request.contentType().c_str()));
+ }
+
+ return bodySEXP;
+ }
+}
+
+// mirrors collect_buffers in Rhttpd.c
+SEXP headersBuffer(const http::Request& request, r::sexp::Protect* pProtect)
+{
+ // get headers
+ std::string headers;
+ for(http::Headers::const_iterator it = request.headers().begin();
+ it != request.headers().end();
+ ++it)
+ {
+ headers.append(it->name);
+ headers.append(": ");
+ headers.append(it->value);
+ headers.append("\n");
+ }
+
+ // append Request-Method
+ headers.append("Request-Method: " + request.method() + "\n");
+
+ // allocate RAWSXP and copy headers to it
+ SEXP headersSEXP = Rf_allocVector(RAWSXP, headers.length());
+ pProtect->add(headersSEXP);
+ char* headersBuffer = (char*) RAW(headersSEXP);
+ headers.copy(headersBuffer, headers.length());
+
+ // return
+ return headersSEXP;
+}
+
+typedef boost::function<SEXP(const std::string&)> HandlerSource;
+
+
+// NOTE: this emulates the calling portion of process_request in Rhttpd.c,
+// to do this it uses low-level R functions and therefore must be wrapped
+// in executeSafely
+SEXP callHandler(const std::string& path,
+ const http::Request& request,
+ const HandlerSource& handlerSource,
+ r::sexp::Protect* pProtect)
+{
+ // uri decode the path
+ std::string decodedPath = http::util::urlDecode(path, false);
+
+ // construct "try(httpd(url, query, body, headers), silent=TRUE)"
+
+ SEXP trueSEXP;
+ pProtect->add(trueSEXP = Rf_ScalarLogical(TRUE));
+ SEXP queryStringSEXP = parseQuery(request.queryParams(), pProtect);
+ SEXP requestBodySEXP = parseRequestBody(request, pProtect);
+ SEXP headersSEXP = headersBuffer(request, pProtect);
+
+ // only provide headers if appropriate
+ SEXP argsSEXP;
+ if (s_provideHeaders)
+ {
+ argsSEXP = Rf_list4(Rf_mkString(path.c_str()),
+ queryStringSEXP,
+ requestBodySEXP,
+ headersSEXP);
+ }
+ else
+ {
+ argsSEXP = Rf_list3(Rf_mkString(path.c_str()),
+ queryStringSEXP,
+ requestBodySEXP);
+ }
+ pProtect->add(argsSEXP);
+
+ // form the call expression
+ SEXP callSEXP;
+ pProtect->add(callSEXP = Rf_lang3(
+ Rf_install("try"),
+ Rf_lcons( (handlerSource(path)), argsSEXP),
+ trueSEXP));
+ SET_TAG(CDR(CDR(callSEXP)), Rf_install("silent"));
+
+ // execute and return
+ SEXP resultSEXP;
+ pProtect->add(resultSEXP = Rf_eval(callSEXP,
+ R_FindNamespace(Rf_mkString("tools"))));
+ return resultSEXP;
+}
+
+r_util::RPackageInfo packageInfoForRd(const FilePath& rdFilePath)
+{
+ FilePath packageDir = rdFilePath.parent().parent();
+
+ FilePath descFilePath = packageDir.childPath("DESCRIPTION");
+ if (!descFilePath.exists())
+ return r_util::RPackageInfo();
+
+ r_util::RPackageInfo pkgInfo;
+ Error error = pkgInfo.read(packageDir);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return r_util::RPackageInfo();
+ }
+ else
+ {
+ return pkgInfo;
+ }
+}
+
+template <typename Filter>
+void handleRdPreviewRequest(const http::Request& request,
+ const Filter& filter,
+ http::Response* pResponse)
+{
+ // read parmaeters
+ std::string file = request.queryParamValue("file");
+ if (file.empty())
+ {
+ pResponse->setError(http::status::BadRequest, "No file parameter");
+ return;
+ }
+
+ // ensure file exists
+ FilePath filePath = module_context::resolveAliasedPath(file);
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::NotFound, request.uri());
+ return;
+ }
+
+ // build command used to convert to HTML
+ FilePath rHomeBinDir;
+ Error error = module_context::rBinDir(&rHomeBinDir);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+ shell_utils::ShellCommand rCmd = module_context::rCmd(rHomeBinDir);
+ rCmd << "Rdconv";
+ rCmd << "--type=html";
+ r_util::RPackageInfo pkgInfo = packageInfoForRd(filePath);
+ if (!pkgInfo.empty())
+ rCmd << "--package=" + pkgInfo.name();
+
+ rCmd << filePath;
+
+ // run the converstion and return it
+ core::system::ProcessOptions options;
+ core::system::ProcessResult result;
+ error = core::system::runCommand(rCmd, options, &result);
+ if (error)
+ {
+ pResponse->setError(error);
+ }
+ else if (result.exitStatus != EXIT_SUCCESS)
+ {
+ pResponse->setError(http::status::InternalServerError, result.stdErr);
+ }
+ else
+ {
+ pResponse->setContentType("text/html");
+ pResponse->setNoCacheHeaders();
+ std::istringstream istr(result.stdOut);
+ pResponse->setBody(istr, filter);
+ }
+}
+
+template <typename Filter>
+void handleHttpdRequest(const std::string& location,
+ const HandlerSource& handlerSource,
+ const http::Request& request,
+ const Filter& filter,
+ http::Response* pResponse)
+{
+ // get the requested path
+ std::string path = http::util::pathAfterPrefix(request, location);
+
+ // server custom css file if necessary
+ if (boost::algorithm::ends_with(path, "/R.css"))
+ {
+ core::FilePath cssFile = options().rResourcesPath().childPath("R.css");
+ if (cssFile.exists())
+ {
+ pResponse->setFile(cssFile, request, filter);
+ return;
+ }
+ }
+
+ // handle presentation url
+ if (boost::algorithm::starts_with(path, "/presentation"))
+ {
+ presentation::handlePresentationHelpRequest(request, kJsCallbacks, pResponse);
+ return;
+ }
+
+ // handle Rd file preview
+ if (boost::algorithm::starts_with(path, "/preview"))
+ {
+ handleRdPreviewRequest(request, filter, pResponse);
+ return;
+ }
+
+ // markdown help is also a special case
+ if (path == "/doc/markdown_help.html")
+ {
+ core::FilePath helpFile = options().rResourcesPath().childPath(
+ "markdown_help.html");
+ if (helpFile.exists())
+ {
+ pResponse->setFile(helpFile, request, filter);
+ return;
+ }
+ }
+
+ // evalute the handler
+ r::sexp::Protect rp;
+ SEXP httpdSEXP;
+ Error error = r::exec::executeSafely<SEXP>(
+ boost::bind(callHandler,
+ path,
+ boost::cref(request),
+ handlerSource,
+ &rp),
+ &httpdSEXP);
+
+ // error calling the function
+ if (error)
+ {
+ pResponse->setError(http::status::InternalServerError,
+ error.code().message());
+ }
+
+ // error returned explicitly by httpd
+ else if (TYPEOF(httpdSEXP) == STRSXP && LENGTH(httpdSEXP) > 0)
+ {
+ pResponse->setError(http::status::InternalServerError,
+ r::sexp::asString(httpdSEXP));
+ }
+
+ // content returned from httpd
+ else if (TYPEOF(httpdSEXP) == VECSXP && LENGTH(httpdSEXP) > 0)
+ {
+ handleHttpdResult(httpdSEXP, request, filter, pResponse);
+ }
+
+ // unexpected SEXP type returned from httpd
+ else
+ {
+ pResponse->setError(http::status::InternalServerError,
+ "Invalid response from R");
+ }
+}
+
+// this mirrors handler_for_path in Rhttpd.c. They cache the custom handlers
+// env (not sure why). do the same for consistency
+SEXP s_customHandlersEnv = NULL;
+SEXP lookupCustomHandler(const std::string& uri)
+{
+ // pick name of handler out of uri
+ boost::regex customRegx(".*/custom/([A-Za-z0-9_\\-]*).*");
+ boost::smatch match;
+ if (regex_match(uri, match, customRegx))
+ {
+ std::string handler = match[1];
+
+ // load .httpd.handlers.env
+ if (!s_customHandlersEnv)
+ {
+ s_customHandlersEnv = Rf_eval(Rf_install(".httpd.handlers.env"),
+ R_FindNamespace(Rf_mkString("tools")));
+ }
+
+ // we only proceed if .httpd.handlers.env really exists
+ if (TYPEOF(s_customHandlersEnv) == ENVSXP)
+ {
+ SEXP cl = Rf_findVarInFrame3(s_customHandlersEnv,
+ Rf_install(handler.c_str()),
+ TRUE);
+ if (cl != R_UnboundValue && TYPEOF(cl) == CLOSXP) // need a closure
+ return cl;
+ }
+ }
+
+ // if we didn't find a handler then return handler lookup error
+ return r::sexp::findFunction(".rs.handlerLookupError");
+}
+
+
+// .httpd.handlers.env
+void handleCustomRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ handleHttpdRequest("",
+ lookupCustomHandler,
+ request,
+ http::NullOutputFilter(),
+ pResponse);
+}
+
+// handle requests for session temporary directory
+void handleSessionRequest(const http::Request& request, http::Response* pResponse)
+{
+ // get the raw uri & strip its location prefix
+ std::string sessionPrefix = std::string(kSessionLocation) + "/";
+ std::string uri = request.uri();
+ if (!uri.compare(0, sessionPrefix.length(), sessionPrefix))
+ uri = uri.substr(sessionPrefix.length());
+
+ // remove query parameters and anchor
+ std::size_t pos = uri.find("?");
+ if (pos != std::string::npos)
+ uri.erase(pos);
+ pos = uri.find("#");
+ if (pos != std::string::npos)
+ uri.erase(pos);
+
+ // ensure that this path does not contain ..
+ if (uri.find("..") != std::string::npos)
+ {
+ pResponse->setError(http::status::NotFound, uri + " not found");
+ return;
+ }
+
+ // form a path to the temporary file
+ FilePath tempFilePath = r::session::utils::tempDir().childPath(uri);
+
+ // return the file
+ pResponse->setCacheWithRevalidationHeaders();
+ if (tempFilePath.mimeContentType() == "text/html")
+ {
+ pResponse->setCacheableFile(tempFilePath,
+ request,
+ HelpContentsFilter(request));
+ }
+ else
+ {
+ pResponse->setCacheableFile(tempFilePath, request);
+ }
+}
+
+// the ShowHelp event will result in the Help pane requesting the specified
+// help url. we handle this request directly by calling the R httpd function
+// to dynamically form the correct http response
+void handleHelpRequest(const http::Request& request, http::Response* pResponse)
+{
+ handleHttpdRequest(kHelpLocation,
+ boost::bind(r::sexp::findFunction, "httpd", "tools"),
+ request,
+ HelpContentsFilter(request),
+ pResponse);
+}
+
+SEXP rs_previewRd(SEXP rdFileSEXP)
+{
+ std::string rdFile = r::sexp::safeAsString(rdFileSEXP);
+ boost::format fmt("help/preview?file=%1%");
+ std::string url = boost::str(fmt % http::util::urlEncode(rdFile));
+ ClientEvent event(client_events::kShowHelp, url);
+ module_context::enqueClientEvent(event);
+ return R_NilValue;
+}
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // determine whether we should provide headers to custom handlers
+ s_provideHeaders = r::util::hasRequiredVersion("2.13");
+
+
+ // register previewRd function
+ R_CallMethodDef previewRdMethodDef ;
+ previewRdMethodDef.name = "rs_previewRd" ;
+ previewRdMethodDef.fun = (DL_FUNC)rs_previewRd ;
+ previewRdMethodDef.numArgs = 1;
+ r::routines::addCallMethod(previewRdMethodDef);
+
+ using boost::bind;
+ using core::http::UriHandler;
+ using namespace module_context;
+ using namespace r::function_hook ;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRBrowseUrlHandler, handleLocalHttpUrl))
+ (bind(registerRBrowseFileHandler, handleRShowDocFile))
+ (bind(registerUriHandler, kHelpLocation, handleHelpRequest))
+ (bind(sourceModuleRFile, "SessionHelp.R"));
+ Error error = initBlock.execute();
+ if (error)
+ return error;
+
+ // init help
+ bool isDesktop = options().programMode() == kSessionProgramModeDesktop;
+ int port = safe_convert::stringTo<int>(session::options().wwwPort(), 0);
+ error = r::exec::RFunction(".rs.initHelp", port, isDesktop).call(
+ &s_handleCustom);
+ if (error)
+ LOG_ERROR(error);
+
+ // handle /custom and /session urls internally if necessary (always in
+ // server mode, in desktop mode if the internal http server can't
+ // bind to a port)
+ if (s_handleCustom)
+ {
+ ExecBlock serverInitBlock;
+ serverInitBlock.addFunctions()
+ (bind(registerUriHandler, kCustomLocation, handleCustomRequest))
+ (bind(registerUriHandler, kSessionLocation, handleSessionRequest));
+ error = serverInitBlock.execute();
+ if (error)
+ return error;
+ }
+
+ return Success();
+}
+
+
+} // namepsace help
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionHelp.hpp b/src/cpp/session/modules/SessionHelp.hpp
new file mode 100644
index 0000000..c8a6a30
--- /dev/null
+++ b/src/cpp/session/modules/SessionHelp.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionHelp.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HELP_HPP
+#define SESSION_HELP_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace help {
+
+core::Error initialize();
+
+} // namespace help
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_HELP_HPP
diff --git a/src/cpp/session/modules/SessionHistory.cpp b/src/cpp/session/modules/SessionHistory.cpp
new file mode 100644
index 0000000..400f7fc
--- /dev/null
+++ b/src/cpp/session/modules/SessionHistory.cpp
@@ -0,0 +1,409 @@
+/*
+ * SessionHistory.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionHistory.hpp"
+
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+
+#include <boost/utility.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/format.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/FilePath.hpp>
+#include <core/DateTime.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RConsoleHistory.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionHistoryArchive.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace history {
+
+namespace {
+
+
+void historyEntriesAsJson(const std::vector<HistoryEntry>& entries,
+ json::Object* pEntriesJson)
+{
+ // clear inbound
+ pEntriesJson->clear();
+
+ // populate arrays
+ json::Array indexArray, timestampArray, commandArray;
+ for (std::size_t i=0; i<entries.size(); i++)
+ {
+ indexArray.push_back(entries[i].index);
+ timestampArray.push_back(entries[i].timestamp);
+ commandArray.push_back(entries[i].command);
+ }
+
+ // set arrays into result object
+ pEntriesJson->operator[]("index") = indexArray;
+ pEntriesJson->operator[]("timestamp") = timestampArray;
+ pEntriesJson->operator[]("command") = commandArray;
+}
+
+Error setJsonResultFromHistory(int startIndex,
+ int endIndex,
+ json::JsonRpcResponse* pResponse)
+{
+ // get all entries
+ const std::vector<HistoryEntry>& allEntries = historyArchive().entries();
+
+ // validate indexes
+ int historySize = allEntries.size();
+ if ( (startIndex < 0) ||
+ (startIndex > historySize) ||
+ (endIndex < 0) ||
+ (endIndex > historySize) )
+ {
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+ }
+
+ // return the entries
+ std::vector<HistoryEntry> entries;
+ std::copy(allEntries.begin() + startIndex,
+ allEntries.begin() + endIndex,
+ std::back_inserter(entries));
+ json::Object entriesJson;
+ historyEntriesAsJson(entries, &entriesJson);
+ pResponse->setResult(entriesJson);
+ return Success();
+}
+
+bool matches(const HistoryEntry& entry,
+ const std::vector<std::string>& searchTerms)
+{
+ // look for each search term in the input
+ for (std::vector<std::string>::const_iterator it = searchTerms.begin();
+ it != searchTerms.end();
+ ++it)
+ {
+ if (!boost::algorithm::contains(entry.command, *it))
+ return false;
+ }
+
+ // had all of the search terms, return true
+ return true;
+}
+
+
+void historyRangeAsJson(int startIndex,
+ int endIndex,
+ json::Object* pHistoryJson)
+{
+ // get the subset of entries
+ std::vector<HistoryEntry> historyEntries;
+ std::vector<std::string> entries;
+ r::session::consoleHistory().subset(startIndex, endIndex, &entries);
+ for (std::vector<std::string>::const_iterator it = entries.begin();
+ it != entries.end();
+ ++it)
+ {
+ historyEntries.push_back(HistoryEntry(startIndex++, 0, *it));
+ }
+
+ // convert to json
+ historyEntriesAsJson(historyEntries, pHistoryJson);
+}
+
+Error getRecentHistory(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get params
+ int maxItems;
+ Error error = json::readParam(request.params, 0, &maxItems);
+ if (error)
+ return error;
+
+ // alias console history
+ using namespace r::session;
+ ConsoleHistory& consoleHistory = r::session::consoleHistory();
+
+ // validate
+ if (maxItems <= 0)
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+
+ // compute start and end indexes
+ int startIndex = std::max(0, consoleHistory.size() - maxItems);
+ int endIndex = consoleHistory.size();
+
+ // get json and set it
+ json::Object historyJson;
+ historyRangeAsJson(startIndex, endIndex, &historyJson);
+ pResponse->setResult(historyJson);
+ return Success();
+}
+
+Error getHistoryItems(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get start and end index
+ int startIndex; // inclusive
+ int endIndex; // exclusive
+ Error error = json::readParams(request.params, &startIndex, &endIndex);
+ if (error)
+ return error;
+
+ // get the range and return it
+ json::Object historyJson;
+ historyRangeAsJson(startIndex, endIndex, &historyJson);
+ pResponse->setResult(historyJson);
+ return Success();
+}
+
+void enqueConsoleResetHistoryEvent(bool preserveUIContext)
+{
+ json::Array historyJson;
+ r::session::consoleHistory().asJson(&historyJson);
+ json::Object resetJson;
+ resetJson["history"] = historyJson;
+ resetJson["preserve_ui_context"] = preserveUIContext;
+ ClientEvent event(client_events::kConsoleResetHistory, resetJson);
+ module_context::enqueClientEvent(event);
+}
+
+Error removeHistoryItems(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get indexes
+ json::Array bottomIndexesJson;
+ Error error = json::readParam(request.params, 0, &bottomIndexesJson);
+ if (error)
+ return error;
+
+ // convert to top indexes
+ int historySize = r::session::consoleHistory().size();
+ std::vector<int> indexes;
+ for (std::size_t i=0; i<bottomIndexesJson.size(); i++)
+ {
+ const json::Value& value = bottomIndexesJson[i];
+ if (json::isType<int>(value))
+ {
+ int bottomIndex = value.get_int();
+ int topIndex = historySize - 1 - bottomIndex;
+ indexes.push_back(topIndex);
+ }
+ }
+
+ // remove them
+ r::session::consoleHistory().remove(indexes);
+
+ // enque event
+ enqueConsoleResetHistoryEvent(true);
+
+ return Success();
+}
+
+Error clearHistory(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::consoleHistory().clear();
+
+ enqueConsoleResetHistoryEvent(false);
+
+ return Success();
+}
+
+Error getHistoryArchiveItems(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get start and end index
+ int startIndex; // inclusive
+ int endIndex; // exclusive
+ Error error = json::readParams(request.params, &startIndex, &endIndex);
+ if (error)
+ return error;
+
+ // truncate indexes if necessary
+ int historySize = historyArchive().entries().size();
+ startIndex = std::min(startIndex, historySize);
+ endIndex = std::min(endIndex, historySize);
+
+ // return json for the appropriate range
+ return setJsonResultFromHistory(startIndex, endIndex, pResponse);
+}
+
+Error searchHistoryArchive(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the query
+ std::string query;
+ int maxEntries;
+ Error error = json::readParams(request.params, &query, &maxEntries);
+ if (error)
+ return error;
+
+ // convert the query into a list of search terms
+ std::vector<std::string> searchTerms;
+ boost::char_separator<char> sep;
+ boost::tokenizer<boost::char_separator<char> > tok(query, sep);
+ std::copy(tok.begin(), tok.end(), std::back_inserter(searchTerms));
+
+ // examine the items in the history for matches
+ const std::vector<HistoryEntry>& allEntries = historyArchive().entries();
+ std::vector<HistoryEntry> matchingEntries;
+ for (std::vector<HistoryEntry>::const_reverse_iterator
+ it = allEntries.rbegin();
+ it != allEntries.rend();
+ ++it)
+ {
+ // check limit
+ if (matchingEntries.size() >= static_cast<std::size_t>(maxEntries))
+ break;
+
+ // look for match
+ if (matches(*it, searchTerms))
+ {
+ // add entry
+ matchingEntries.push_back(*it);
+ }
+ }
+
+ // return json
+ json::Object entriesJson;
+ historyEntriesAsJson(matchingEntries, &entriesJson);
+ pResponse->setResult(entriesJson);
+ return Success();
+}
+
+Error searchHistoryArchiveByPrefix(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the query
+ std::string prefix;
+ int maxEntries;
+ bool uniqueOnly;
+ Error error = json::readParams(request.params,
+ &prefix, &maxEntries, &uniqueOnly);
+ if (error)
+ return error;
+
+ // trim the prefix
+ boost::algorithm::trim(prefix);
+
+ // examine the items in the history for matches
+ const std::vector<HistoryEntry>& allEntries = historyArchive().entries();
+ std::set<std::string> matchedCommands;
+ std::vector<HistoryEntry> matchingEntries;
+ for (std::vector<HistoryEntry>::const_reverse_iterator
+ it = allEntries.rbegin();
+ it != allEntries.rend();
+ ++it)
+ {
+ // check limit
+ if (matchingEntries.size() >= static_cast<std::size_t>(maxEntries))
+ break;
+
+ // look for match
+ if (boost::algorithm::starts_with(it->command, prefix))
+ {
+ if (!uniqueOnly || (matchedCommands.count(it->command) == 0))
+ {
+ matchingEntries.push_back(*it);
+ matchedCommands.insert(it->command);
+ }
+ }
+ }
+
+ // return json
+ json::Object entriesJson;
+ historyEntriesAsJson(matchingEntries, &entriesJson);
+ pResponse->setResult(entriesJson);
+ return Success();
+}
+
+void onHistoryAdd(const std::string& command)
+{
+ // add command to history archive
+ Error error = historyArchive().add(command);
+ if (error)
+ LOG_ERROR(error);
+
+ // fire event
+ int entryIndex = r::session::consoleHistory().size() - 1;
+ std::vector<HistoryEntry> entries;
+ entries.push_back(HistoryEntry(entryIndex, 0, command));
+ json::Object entriesJson;
+ historyEntriesAsJson(entries, &entriesJson);
+ ClientEvent event(client_events::kHistoryEntriesAdded, entriesJson);
+ module_context::enqueClientEvent(event);
+}
+
+SEXP rs_timestamp(SEXP dateSEXP)
+{
+ boost::format fmt("##------ %1% ------##");
+ std::string ts = boost::str(fmt % r::sexp::safeAsString(dateSEXP));
+ r::session::consoleHistory().add(ts);
+ module_context::consoleWriteOutput(ts + "\n");
+ return R_NilValue;
+}
+
+} // anonymous namespace
+
+
+Error initialize()
+{
+ // migrate .Rhistory if necessary
+ HistoryArchive::migrateRhistoryIfNecessary();
+
+ // connect to console history add event
+ r::session::consoleHistory().connectOnAdd(onHistoryAdd);
+
+ // register timestamp function
+ R_CallMethodDef methodDef;
+ methodDef.name = "rs_timestamp" ;
+ methodDef.fun = (DL_FUNC) rs_timestamp;
+ methodDef.numArgs = 1;
+ r::routines::addCallMethod(methodDef);
+
+ // install handlers
+ using boost::bind;
+ using namespace session::module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "get_recent_history", getRecentHistory))
+ (bind(registerRpcMethod, "get_history_items", getHistoryItems))
+ (bind(registerRpcMethod, "remove_history_items", removeHistoryItems))
+ (bind(registerRpcMethod, "clear_history", clearHistory))
+ (bind(registerRpcMethod, "get_history_archive_items", getHistoryArchiveItems))
+ (bind(registerRpcMethod, "search_history_archive", searchHistoryArchive))
+ (bind(registerRpcMethod, "search_history_archive_by_prefix", searchHistoryArchiveByPrefix));
+ return initBlock.execute();
+}
+
+
+} // namespace history
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionHistory.hpp b/src/cpp/session/modules/SessionHistory.hpp
new file mode 100644
index 0000000..440174f
--- /dev/null
+++ b/src/cpp/session/modules/SessionHistory.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionHistory.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HISTORY_HPP
+#define SESSION_HISTORY_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace history {
+
+core::Error initialize();
+
+} // namespace history
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_HISTORY_HPP
diff --git a/src/cpp/session/modules/SessionHistoryArchive.cpp b/src/cpp/session/modules/SessionHistoryArchive.cpp
new file mode 100644
index 0000000..b86a767
--- /dev/null
+++ b/src/cpp/session/modules/SessionHistoryArchive.cpp
@@ -0,0 +1,217 @@
+/*
+ * SessionHistoryArchive.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionHistoryArchive.hpp"
+
+#include <string>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/DateTime.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <r/session/RConsoleHistory.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+
+#define kHistoryDatabase "history_database"
+#define kHistoryMaxBytes (750*1024) // rotate/remove every 750K
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace history {
+
+namespace {
+
+FilePath historyDatabaseFilePath()
+{
+ return module_context::userScratchPath().complete(kHistoryDatabase);
+}
+
+FilePath historyDatabaseRotatedFilePath()
+{
+ return module_context::userScratchPath().complete(kHistoryDatabase ".1");
+}
+
+void rotateHistoryDatabase()
+{
+ FilePath historyDB = historyDatabaseFilePath();
+ if (historyDB.exists() && (historyDB.size() > kHistoryMaxBytes))
+ {
+ // first remove the rotated file if it exists (ignore errors because
+ // there's nothing we can do with them at this level)
+ FilePath rotatedHistoryDB = historyDatabaseRotatedFilePath();
+ rotatedHistoryDB.removeIfExists();
+
+ // now rotate the file
+ historyDB.move(rotatedHistoryDB);
+ }
+}
+
+void writeEntry(double timestamp, const std::string& command, std::ostream* pOS)
+{
+ *pOS << std::fixed << std::setprecision(0)
+ << timestamp << ":" << command;
+}
+
+std::string migratedHistoryEntry(const std::string& command)
+{
+ std::ostringstream ostr ;
+ writeEntry(0, command, &ostr);
+ return ostr.str();
+}
+
+void attemptRhistoryMigration()
+{
+ Error error = writeCollectionToFile<r::session::ConsoleHistory>(
+ historyDatabaseFilePath(),
+ r::session::consoleHistory(),
+ migratedHistoryEntry);
+
+ // log any error which occurs
+ if (error)
+ LOG_ERROR(error);
+}
+
+// simple reader for parsing lines of history file
+ReadCollectionAction readHistoryEntry(const std::string& line,
+ HistoryEntry* pEntry,
+ int* pNextIndex)
+{
+ // if the line doesn't have a ':' then ignore it
+ if (line.find(':') == std::string::npos)
+ return ReadCollectionIgnoreLine;
+
+ pEntry->index = (*pNextIndex)++;
+ std::istringstream istr(line);
+ istr >> pEntry->timestamp ;
+ istr.ignore(1, ':');
+ std::getline(istr, pEntry->command);
+
+ // if we had a read failure log it and return ignore state
+ if (!istr.fail())
+ {
+ return ReadCollectionAddLine;
+ }
+ else
+ {
+ LOG_ERROR_MESSAGE("unexpected io error reading history line: " +
+ line);
+ return ReadCollectionIgnoreLine;
+ }
+}
+
+} // anonymous namespace
+
+HistoryArchive& historyArchive()
+{
+ static HistoryArchive instance;
+ return instance;
+}
+
+Error HistoryArchive::add(const std::string& command)
+{
+ // reset the cache (since this write will invalidate the current one,
+ // no sense in keeping our cache around in memory)
+ entries_.clear();
+ entryCacheLastWriteTime_ = -1;
+
+ // rotate if necessary
+ rotateHistoryDatabase();
+
+ // write the entry to the file
+ std::ostringstream ostrEntry ;
+ double currentTime = core::date_time::millisecondsSinceEpoch();
+ writeEntry(currentTime, command, &ostrEntry);
+ ostrEntry << std::endl;
+ return appendToFile(historyDatabaseFilePath(), ostrEntry.str());
+}
+
+const std::vector<HistoryEntry>& HistoryArchive::entries() const
+{
+ // calculate path to history db
+ FilePath historyDBPath = historyDatabaseFilePath();
+
+ // if the file doesn't exist then clear the collection
+ if (!historyDBPath.exists())
+ {
+ entries_.clear();
+ }
+
+ // otherwise check for divergent lastWriteTime and read the file
+ // if our internal list isn't up to date
+ else if (historyDBPath.lastWriteTime() != entryCacheLastWriteTime_)
+ {
+ entries_.clear();
+
+ // establish a next index counter
+ int nextIndex = 0;
+
+ // first read from rotated file if it exists
+ FilePath rotatedHistoryDBPath = historyDatabaseRotatedFilePath();
+ if (rotatedHistoryDBPath.exists())
+ {
+ Error error = readCollectionFromFile<std::vector<HistoryEntry> >(
+ rotatedHistoryDBPath,
+ &entries_,
+ boost::bind(readHistoryEntry, _1, _2, &nextIndex));
+ if (error)
+ LOG_ERROR(error);
+ }
+
+
+ // now read from main history db
+ std::vector<HistoryEntry> entries;
+ Error error = readCollectionFromFile<std::vector<HistoryEntry> >(
+ historyDBPath,
+ &entries,
+ boost::bind(readHistoryEntry, _1, _2, &nextIndex));
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ else
+ {
+ std::copy(entries.begin(),
+ entries.end(),
+ std::back_inserter(entries_));
+
+ entryCacheLastWriteTime_ = historyDBPath.lastWriteTime();
+ }
+
+ }
+
+ // return entries
+ return entries_;
+}
+
+void HistoryArchive::migrateRhistoryIfNecessary()
+{
+ // if the history database doesn't exist see if we can migrate the
+ // old .Rhistory file
+ FilePath historyDBPath = historyDatabaseFilePath();
+ if (!historyDBPath.exists())
+ attemptRhistoryMigration() ;
+}
+
+
+} // namespace history
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionHistoryArchive.hpp b/src/cpp/session/modules/SessionHistoryArchive.hpp
new file mode 100644
index 0000000..881cd74
--- /dev/null
+++ b/src/cpp/session/modules/SessionHistoryArchive.hpp
@@ -0,0 +1,70 @@
+/*
+ * SessionHistoryArchive.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_HISTORY_ARCHIVE_HPP
+#define SESSION_HISTORY_ARCHIVE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace history {
+
+struct HistoryEntry
+{
+ HistoryEntry() : index(0), timestamp(0) {}
+ HistoryEntry(int index, double timestamp, const std::string& command)
+ : index(index), timestamp(timestamp), command(command)
+ {
+ }
+ int index;
+ double timestamp;
+ std::string command;
+};
+
+class HistoryArchive;
+HistoryArchive& historyArchive();
+
+class HistoryArchive : boost::noncopyable
+{
+private:
+ HistoryArchive() : entryCacheLastWriteTime_(-1) {}
+ friend HistoryArchive& historyArchive();
+
+public:
+ static void migrateRhistoryIfNecessary();
+
+public:
+ core::Error add(const std::string& command);
+ const std::vector<HistoryEntry>& entries() const;
+
+private:
+ mutable time_t entryCacheLastWriteTime_;
+ mutable std::vector<HistoryEntry> entries_;
+};
+
+} // namespace history
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_HISTORY_ARCHIVE_HPP
diff --git a/src/cpp/session/modules/SessionLimits.cpp b/src/cpp/session/modules/SessionLimits.cpp
new file mode 100644
index 0000000..b8d4237
--- /dev/null
+++ b/src/cpp/session/modules/SessionLimits.cpp
@@ -0,0 +1,73 @@
+/*
+ * SessionLimits.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionLimits.hpp"
+
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/system/System.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace limits {
+
+namespace {
+
+void onBeforeExecute()
+{
+ // enforce a limit if there is one
+ if (session::options().limitCpuTimeMinutes() > 0)
+ {
+ // calculate seconds
+ int seconds = session::options().limitCpuTimeMinutes() * 60;
+
+ // call setTimeLimit
+ r::exec::RFunction setTimeLimit("setTimeLimit");
+ setTimeLimit.addParam("cpu", seconds);
+ Error error = setTimeLimit.call();
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // subscribe to onBeforeExecute so we can set a cpu time limit for
+ // top level computations
+ module_context::events().onBeforeExecute.connect(boost::bind(
+ onBeforeExecute));
+
+ return Success();
+}
+
+
+
+} // namespace limits
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionLimits.hpp b/src/cpp/session/modules/SessionLimits.hpp
new file mode 100644
index 0000000..515f269
--- /dev/null
+++ b/src/cpp/session/modules/SessionLimits.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionLimits.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_LIMITS_HPP
+#define SESSION_LIMITS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace limits {
+
+core::Error initialize();
+
+} // namespace limits
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_LIMITS_HPP
diff --git a/src/cpp/session/modules/SessionLists.cpp b/src/cpp/session/modules/SessionLists.cpp
new file mode 100644
index 0000000..65f6b50
--- /dev/null
+++ b/src/cpp/session/modules/SessionLists.cpp
@@ -0,0 +1,354 @@
+/*
+ * SessionLists.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionLists.hpp"
+
+#include <map>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/utility.hpp>
+#include <boost/circular_buffer.hpp>
+
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace lists {
+
+namespace {
+
+// list names
+const char * const kFileMru = "file_mru";
+const char * const kProjectMru = "project_mru";
+const char * const kHelpHistory = "help_history_links";
+const char * const kUserDictioanry = "user_dictionary";
+
+// path to lists dir
+FilePath s_listsPath;
+
+// registered lists
+typedef std::map<std::string, std::size_t> Lists;
+Lists s_lists;
+
+// lookup list size
+std::size_t listSize(const char* const name)
+{
+ Lists::const_iterator pos = s_lists.find(name);
+ if (pos != s_lists.end())
+ return pos->second;
+ else
+ return -1;
+}
+
+
+FilePath listPath(const std::string& name)
+{
+ return s_listsPath.complete(name);
+}
+
+template <typename T>
+Error readList(const std::string& name, T* pList)
+{
+ // lookup list size (also serves as a validation of list name)
+ std::size_t size = listSize(name.c_str());
+ if (size <= 0)
+ {
+ Error error = systemError(boost::system::errc::invalid_argument,
+ ERROR_LOCATION);
+ error.addProperty("name", name);
+ return error;
+ }
+
+ // read the list from disk
+ pList->clear();
+ FilePath listFilePath = listPath(name);
+ if (listFilePath.exists())
+ {
+ Error error = readCollectionFromFile<T>(listFilePath,
+ pList,
+ parseString);
+ if (error)
+ return error;
+
+ if (pList->size() > size)
+ pList->resize(size);
+ }
+
+ // return success
+ return Success();
+}
+
+
+template <typename T>
+Error writeList(const std::string& name, const T& list)
+{
+ return writeCollectionToFile<T>(listPath(name), list, stringifyString);
+}
+
+
+json::Array listToJson(const std::list<std::string>& list)
+{
+ json::Array jsonArray;
+ BOOST_FOREACH(const std::string& val, list)
+ {
+ jsonArray.push_back(val);
+ }
+ return jsonArray;
+}
+
+void onListsFileChanged(const core::system::FileChangeEvent& fileChange)
+{
+ // ignore if deleted
+ if (fileChange.type() == core::system::FileChangeEvent::FileRemoved)
+ return;
+
+ // ignore if it is the lists directory
+ if (fileChange.fileInfo().absolutePath() == s_listsPath.absolutePath())
+ return;
+
+ // get the name of the list
+ FilePath filePath(fileChange.fileInfo().absolutePath());
+ std::string name = filePath.filename();
+
+ // read it
+ std::list<std::string> list;
+ Error error = readList(name, &list);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ json::Object eventJson;
+ eventJson["name"] = name;
+ eventJson["list"] = listToJson(list);
+
+ ClientEvent event(client_events::kListChanged, eventJson);
+ module_context::enqueClientEvent(event);
+}
+
+bool isListNameValid(const std::string& name)
+{
+ return listSize(name.c_str()) > 0;
+}
+
+Error getListName(const json::JsonRpcRequest& request, std::string* pName)
+{
+ Error error = json::readParam(request.params, 0, pName);
+ if (error)
+ return error;
+
+ if (!isListNameValid(*pName))
+ return Error(json::errc::ParamInvalid, ERROR_LOCATION);
+ else
+ return Success();
+}
+
+
+Error getListNameAndContents(const json::JsonRpcRequest& request,
+ std::string* pName,
+ std::list<std::string>* pList)
+{
+ Error error = getListName(request, pName);
+ if (error)
+ return error;
+
+ return readList(*pName, pList);
+}
+
+
+Error listGet(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string name;
+ std::list<std::string> list;
+ Error error = getListNameAndContents(request, &name, &list);
+ if (error)
+ return error;
+
+ pResponse->setResult(listToJson(list));
+
+ return Success();
+}
+
+Error listSetContents(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string name;
+ json::Array jsonList;
+ Error error = json::readParams(request.params, &name, &jsonList);
+ if (error)
+ return error;
+
+ std::list<std::string> list;
+ BOOST_FOREACH(const json::Value& val, jsonList)
+ {
+ if (!json::isType<std::string>(val))
+ {
+ BOOST_ASSERT(false);
+ continue;
+ }
+
+ list.push_back(val.get_str());
+ }
+
+ return writeList(name, list);
+}
+
+Error listInsertItem(bool prepend,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get params and other context
+ std::string name, value;
+ std::list<std::string> list;
+ std::size_t maxSize;
+ Error error = getListNameAndContents(request, &name, &list);
+ if (error)
+ return error;
+ error = json::readParam(request.params, 1, &value);
+ if (error)
+ return error;
+ maxSize = listSize(name.c_str());
+
+ // remove any existing item with this value
+ list.remove(value);
+
+ // enforce size constraints
+ while (list.size() >= maxSize)
+ {
+ if (prepend)
+ list.pop_back();
+ else
+ list.pop_front();
+ }
+
+ // do the insert
+ if (prepend)
+ list.push_front(value);
+ else
+ list.push_back(value);
+
+ // update the list
+ return writeList(name, list);
+}
+
+
+Error listPrependItem(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ return listInsertItem(true, request, pResponse);
+}
+
+
+Error listAppendItem(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ return listInsertItem(false, request, pResponse);
+}
+
+
+Error listRemoveItem(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get list name and contents
+ std::string name;
+ std::list<std::string> list;
+ Error error = getListNameAndContents(request, &name, &list);
+ if (error)
+ return error;
+
+ // get value to remove
+ std::string value;
+ error = json::readParam(request.params, 1, &value);
+
+ // remove it
+ list.remove(value);
+
+ // update the list
+ return writeList(name, list);
+}
+
+Error listClear(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // which list
+ std::string name;
+ Error error = getListName(request, &name);
+ if (error)
+ return error;
+
+ // write empty list
+ return writeList(name, std::list<std::string>());
+}
+
+} // anonymous namespace
+
+
+json::Object allListsAsJson()
+{
+ json::Object allListsJson;
+ for (Lists::const_iterator it = s_lists.begin(); it != s_lists.end(); ++it)
+ {
+ std::list<std::string> list;
+ Error error = readList(it->first, &list);
+ if (error)
+ LOG_ERROR(error);
+
+ allListsJson[it->first] = listToJson(list);
+ }
+
+ return allListsJson;
+}
+
+Error initialize()
+{
+ // register lists / max sizes
+ s_lists[kFileMru] = 10;
+ s_lists[kProjectMru] = 10;
+ s_lists[kHelpHistory] = 15;
+ s_lists[kUserDictioanry] = 10000;
+
+ // monitor the lists directory
+ s_listsPath = module_context::registerMonitoredUserScratchDir(
+ "lists",
+ onListsFileChanged);
+
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "list_get", listGet))
+ (bind(registerRpcMethod, "list_set_contents", listSetContents))
+ (bind(registerRpcMethod, "list_prepend_item", listPrependItem))
+ (bind(registerRpcMethod, "list_append_item", listAppendItem))
+ (bind(registerRpcMethod, "list_remove_item", listRemoveItem))
+ (bind(registerRpcMethod, "list_clear", listClear));
+ return initBlock.execute();
+}
+
+
+
+} // namespace lists
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionLists.hpp b/src/cpp/session/modules/SessionLists.hpp
new file mode 100644
index 0000000..be4be2d
--- /dev/null
+++ b/src/cpp/session/modules/SessionLists.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionLists.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_LISTS_HPP
+#define SESSION_LISTS_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace lists {
+
+core::json::Object allListsAsJson();
+
+core::Error initialize();
+
+} // namespace lists
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_LISTS_HPP
diff --git a/src/cpp/session/modules/SessionOverlay.R b/src/cpp/session/modules/SessionOverlay.R
new file mode 100644
index 0000000..f17a6cd
--- /dev/null
+++ b/src/cpp/session/modules/SessionOverlay.R
@@ -0,0 +1,14 @@
+#
+# SessionOverlay.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
diff --git a/src/cpp/session/modules/SessionPackages.R b/src/cpp/session/modules/SessionPackages.R
new file mode 100644
index 0000000..ae4d4c4
--- /dev/null
+++ b/src/cpp/session/modules/SessionPackages.R
@@ -0,0 +1,439 @@
+#
+# SessionPackages.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction( "updatePackageEvents", function()
+{
+ reportPackageStatus <- function(status)
+ function(pkgname, ...)
+ {
+ packageStatus = list(name=pkgname,
+ path=.rs.pathPackage(pkgname, quiet=TRUE),
+ loaded=status)
+ .rs.enqueClientEvent("package_status_changed", packageStatus)
+ }
+
+ notifyPackageLoaded <- function(pkgname, ...)
+ {
+ .Call("rs_packageLoaded", pkgname)
+ }
+
+ notifyPackageUnloaded <- function(pkgname, ...)
+ {
+ .Call("rs_packageUnloaded", pkgname)
+ }
+
+ sapply(.packages(TRUE), function(packageName)
+ {
+ if ( !(packageName %in% .rs.hookedPackages) )
+ {
+ attachEventName = packageEvent(packageName, "attach")
+ setHook(attachEventName, reportPackageStatus(TRUE), action="append")
+
+ loadEventName = packageEvent(packageName, "onLoad")
+ setHook(loadEventName, notifyPackageLoaded, action="append")
+
+ unloadEventName = packageEvent(packageName, "onUnload")
+ setHook(unloadEventName, notifyPackageUnloaded, action="append")
+
+ detachEventName = packageEvent(packageName, "detach")
+ setHook(detachEventName, reportPackageStatus(FALSE), action="append")
+
+ .rs.setVar("hookedPackages", append(.rs.hookedPackages, packageName))
+ }
+ })
+})
+
+.rs.addFunction( "packages.initialize", function()
+{
+ # list of packages we have hooked attach/detach for
+ .rs.setVar( "hookedPackages", character() )
+
+ # set flag indicating we should not ignore loadedPackageUpdates checks
+ .rs.setVar("ignoreNextLoadedPackageCheck", FALSE)
+
+ # ensure we are subscribed to package attach/detach events
+ .rs.updatePackageEvents()
+
+ # whenever a package is installed notify the client and make sure
+ # we are subscribed to its attach/detach events
+ .rs.registerReplaceHook("install.packages", "utils", function(original,
+ pkgs,
+ lib,
+ repos = getOption("repos"),
+ ...)
+ {
+ if (!is.null(repos) && .rs.loadedPackageUpdates(pkgs)) {
+
+ # attempt to determine the install command
+ if (length(sys.calls()) > 7) {
+ installCall <- sys.call(-7)
+ installCmd <- format(installCall)
+ } else {
+ installCmd <- NULL
+ }
+
+ # call back into rsession to send an event to the client
+ .rs.enqueLoadedPackageUpdates(installCmd)
+
+ # throw error
+ stop("Updating loaded packages")
+ }
+
+ # fixup path as necessary
+ .rs.addRToolsToPath()
+
+ # do housekeeping after we execute the original
+ on.exit({
+ .rs.updatePackageEvents()
+ .rs.enqueClientEvent("installed_packages_changed")
+ .rs.restorePreviousPath()
+ })
+
+ # call original
+ original(pkgs, lib, repos, ...)
+ })
+
+ # whenever a package is removed notify the client (leave attach/detach
+ # alone because the dangling event is harmless and removing it would
+ # requrie somewhat involved code
+ .rs.registerReplaceHook("remove.packages", "utils", function(original,
+ pkgs,
+ lib,
+ ...)
+ {
+ # do housekeeping after we execute the original
+ on.exit(.rs.enqueClientEvent("installed_packages_changed"))
+
+ # call original
+ original(pkgs, lib, ...)
+ })
+})
+
+.rs.addFunction( "addRToolsToPath", function()
+{
+ .Call("rs_addRToolsToPath")
+})
+
+.rs.addFunction( "restorePreviousPath", function()
+{
+ .Call("rs_restorePreviousPath")
+})
+
+.rs.addFunction( "uniqueLibraryPaths", function()
+{
+ # get library paths (normalize on unix to get rid of duplicate symlinks)
+ libPaths <- .libPaths()
+ if (!identical(.Platform$OS.type, "windows"))
+ libPaths <- .rs.normalizePath(libPaths)
+
+ uniqueLibPaths <- subset(libPaths, !duplicated(libPaths))
+ return (uniqueLibPaths)
+})
+
+.rs.addFunction( "writeableLibraryPaths", function()
+{
+ uniqueLibraryPaths <- .rs.uniqueLibraryPaths()
+ writeableLibraryPaths <- character()
+ for (libPath in uniqueLibraryPaths)
+ if (.rs.isLibraryWriteable(libPath))
+ writeableLibraryPaths <- append(writeableLibraryPaths, libPath)
+ return (writeableLibraryPaths)
+})
+
+.rs.addFunction("defaultUserLibraryPath", function()
+{
+ unlist(strsplit(Sys.getenv("R_LIBS_USER"),
+ .Platform$path.sep))[1L]
+})
+
+.rs.addFunction("defaultLibraryPath", function()
+{
+ .libPaths()[1]
+})
+
+.rs.addJsonRpcHandler( "is_package_loaded", function(packageName, libName)
+{
+ .rs.scalar( (packageName %in% .packages()) &&
+ identical(.rs.pathPackage(packageName, quiet=TRUE),
+ paste(libName, packageName, sep="/"))
+ )
+})
+
+.rs.addFunction("forceUnloadPackage", function(name)
+{
+ if (name %in% .packages())
+ {
+ fullName <- paste("package:", name, sep="")
+ suppressWarnings(detach(fullName,
+ character.only=TRUE,
+ unload=TRUE,
+ force=TRUE))
+
+ pkgDLL <- getLoadedDLLs()[[name]]
+ if (!is.null(pkgDLL)) {
+ suppressWarnings(library.dynam.unload(name,
+ system.file(package=name)))
+ }
+ }
+})
+
+.rs.addFunction("libPathsString", function()
+{
+ paste(.libPaths(), collapse = .Platform$path.sep)
+})
+
+.rs.addFunction("packageVersion", function(name, libPath, pkgs)
+{
+ pkgs <- subset(pkgs, Package == name & LibPath == libPath)
+ if (nrow(pkgs) == 1)
+ pkgs$Version
+ else
+ ""
+})
+
+.rs.addFunction( "initDefaultUserLibrary", function()
+{
+ userdir <- .rs.defaultUserLibraryPath()
+ dir.create(userdir, showWarnings = FALSE, recursive = TRUE)
+ .libPaths(c(userdir, .libPaths()))
+})
+
+.rs.addFunction( "initializeRStudioPackages", function(libDir,
+ pkgSrcDir,
+ rsVersion,
+ force) {
+
+ if (getRversion() >= "3.0.0") {
+
+ # make sure the default library is writeable
+ if (!.rs.defaultLibPathIsWriteable())
+ .rs.initDefaultUserLibrary()
+
+ # function to update a package if necessary
+ updateIfNecessary <- function(pkgName) {
+ isInstalled <- .rs.isPackageInstalled(pkgName, .rs.defaultLibraryPath())
+ if (force || !isInstalled || (.rs.getPackageVersion(pkgName) != rsVersion)) {
+
+ # remove if necessary
+ if (isInstalled)
+ utils::remove.packages(pkgName, .rs.defaultLibraryPath())
+
+ # call back into rstudio to install
+ .Call("rs_installPackage",
+ file.path(pkgSrcDir, pkgName),
+ .rs.defaultLibraryPath())
+ }
+ }
+
+ updateIfNecessary("rstudio")
+ updateIfNecessary("manipulate")
+
+ } else {
+ .rs.libPathsAppend(libDir)
+ }
+
+})
+
+.rs.addJsonRpcHandler( "list_packages", function()
+{
+ # calculate unique libpaths
+ uniqueLibPaths <- .rs.uniqueLibraryPaths()
+
+ # get packages
+ x <- suppressWarnings(library(lib.loc=uniqueLibPaths))
+ x <- x$results[x$results[, 1] != "base", ]
+
+ # extract/compute required fields
+ pkgs.name <- x[, 1]
+ pkgs.library <- x[, 2]
+ pkgs.desc <- x[, 3]
+ pkgs.url <- file.path("help/library",
+ pkgs.name,
+ "html",
+ "00Index.html")
+ loaded.pkgs <- .rs.pathPackage()
+ pkgs.loaded <- !is.na(match(paste(pkgs.library,pkgs.name, sep="/"),
+ loaded.pkgs))
+
+
+ # build up vector of package versions
+ instPkgs <- as.data.frame(installed.packages(), stringsAsFactors=F)
+ pkgs.version <- character(length=length(pkgs.name))
+ for (i in 1:length(pkgs.name)) {
+ pkgs.version[[i]] <- .rs.packageVersion(pkgs.name[[i]],
+ pkgs.library[[i]],
+ instPkgs)
+ }
+
+ # return data frame sorted by name
+ packages = data.frame(name=pkgs.name,
+ library=pkgs.library,
+ version=pkgs.version,
+ desc=pkgs.desc,
+ url=pkgs.url,
+ loaded=pkgs.loaded,
+ check.rows = TRUE,
+ stringsAsFactors = FALSE)
+
+ # sort and return
+ packages[order(packages$name),]
+})
+
+.rs.addJsonRpcHandler( "get_package_install_context", function()
+{
+ # cran mirror configured
+ repos = getOption("repos")
+ cranMirrorConfigured <- !is.null(repos) && repos != "@CRAN@"
+
+ # selected repository names
+ selectedRepositoryNames <- names(repos)
+
+ # package archive extension
+ if (identical(.Platform$OS.type, "windows"))
+ packageArchiveExtension <- ".zip; .tar.gz"
+ else if (identical(substr(.Platform$pkgType, 1L, 10L), "mac.binary"))
+ packageArchiveExtension <- ".tgz; .tar.gz"
+ else
+ packageArchiveExtension <- ".tar.gz"
+
+ # default library path (normalize on unix)
+ defaultLibraryPath = .libPaths()[1L]
+ if (!identical(.Platform$OS.type, "windows"))
+ defaultLibraryPath <- .rs.normalizePath(defaultLibraryPath)
+
+ # return context
+ list(cranMirrorConfigured = cranMirrorConfigured,
+ selectedRepositoryNames = selectedRepositoryNames,
+ packageArchiveExtension = packageArchiveExtension,
+ defaultLibraryPath = defaultLibraryPath,
+ defaultLibraryWriteable = .rs.defaultLibPathIsWriteable(),
+ writeableLibraryPaths = .rs.writeableLibraryPaths(),
+ defaultUserLibraryPath = .rs.defaultUserLibraryPath(),
+ devModeOn = .rs.devModeOn())
+})
+
+.rs.addJsonRpcHandler( "get_cran_mirrors", function()
+{
+ # RStudio mirror
+ rstudioDF <- data.frame(name = "Global (CDN)",
+ host = "RStudio",
+ url = "http://cran.rstudio.com",
+ country = "us",
+ stringsAsFactors = FALSE)
+
+ # CRAN mirrors
+ cranMirrors <- utils::getCRANmirrors()
+ cranDF <- data.frame(name = cranMirrors$Name,
+ host = cranMirrors$Host,
+ url = cranMirrors$URL,
+ country = cranMirrors$CountryCode,
+ stringsAsFactors = FALSE)
+
+ # return mirrors
+ rbind(rstudioDF, cranDF)
+})
+
+.rs.addJsonRpcHandler( "init_default_user_library", function()
+{
+ .rs.initDefaultUserLibrary()
+})
+
+
+.rs.addJsonRpcHandler( "check_for_package_updates", function()
+{
+ # get updates writeable libraries and convert to a data frame
+ updates <- as.data.frame(utils::old.packages(lib.loc =
+ .rs.writeableLibraryPaths()),
+ stringsAsFactors = FALSE)
+ row.names(updates) <- NULL
+
+ # see which ones are from CRAN and add a news column for them
+ cranRep <- getOption("repos")["CRAN"]
+ cranRepLen <- nchar(cranRep)
+ isFromCRAN <- cranRep == substr(updates$Repository, 1, cranRepLen)
+ newsURL <- character(nrow(updates))
+ if (substr(cranRep, cranRepLen, cranRepLen) != "/")
+ cranRep <- paste(cranRep, "/", sep="")
+
+ newsURL[isFromCRAN] <- paste(cranRep,
+ "web/packages/",
+ updates$Package,
+ "/NEWS", sep = "")[isFromCRAN]
+
+ updates <- data.frame(packageName = updates$Package,
+ libPath = updates$LibPath,
+ installed = updates$Installed,
+ available = updates$ReposVer,
+ newsUrl = newsURL,
+ stringsAsFactors = FALSE)
+
+
+ return (updates)
+})
+
+.rs.addFunction("packagesLoaded", function(pkgs) {
+ # first check loaded namespaces
+ if (any(pkgs %in% loadedNamespaces()))
+ return(TRUE)
+
+ # now check if there are libraries still loaded in spite of the
+ # namespace being unloaded
+ libs <- .dynLibs()
+ libnames <- vapply(libs, "[[", character(1), "name")
+ return(any(pkgs %in% libnames))
+})
+
+.rs.addFunction("loadedPackageUpdates", function(pkgs)
+{
+ # are we ignoring?
+ ignore <- .rs.ignoreNextLoadedPackageCheck
+ .rs.setVar("ignoreNextLoadedPackageCheck", FALSE)
+ if (ignore)
+ return(FALSE)
+
+ # if the default set of namespaces in rstudio are loaded
+ # then skip the check
+ defaultNamespaces <- c("base", "datasets", "graphics", "grDevices",
+ "methods", "stats", "tools", "utils")
+ if (identical(defaultNamespaces, loadedNamespaces()) &&
+ length(.dynLibs()) == 4)
+ return(FALSE)
+
+ if (.rs.packagesLoaded(pkgs)) {
+ return(TRUE)
+ }
+ else {
+ avail <- available.packages()
+ deps <- suppressMessages(suppressWarnings(
+ utils:::getDependencies(pkgs, available=avail)))
+ return(.rs.packagesLoaded(deps))
+ }
+})
+
+.rs.addFunction("enqueLoadedPackageUpdates", function(installCmd)
+{
+ .Call("rs_enqueLoadedPackageUpdates", installCmd)
+})
+
+.rs.addJsonRpcHandler("loaded_package_updates_required", function(pkgs)
+{
+ .rs.scalar(.rs.loadedPackageUpdates(as.character(pkgs)))
+})
+
+.rs.addJsonRpcHandler("ignore_next_loaded_package_check", function() {
+ .rs.setVar("ignoreNextLoadedPackageCheck", TRUE)
+ return(NULL)
+})
+
diff --git a/src/cpp/session/modules/SessionPackages.cpp b/src/cpp/session/modules/SessionPackages.cpp
new file mode 100644
index 0000000..8a96877
--- /dev/null
+++ b/src/cpp/session/modules/SessionPackages.cpp
@@ -0,0 +1,251 @@
+/*
+ * SessionPackages.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+// boost requires that winsock2.h must be included before windows.h
+#ifdef _WIN32
+#include <winsock2.h>
+#endif
+
+#include "SessionPackages.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/regex.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/http/URL.hpp>
+#include <core/http/TcpIpBlockingClient.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "session-config.h"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace packages {
+
+namespace {
+
+Error availablePackagesBegin(const core::json::JsonRpcRequest& request,
+ std::vector<std::string>* pContribUrls)
+{
+ return r::exec::evaluateString<std::vector<std::string> >(
+ "contrib.url(getOption('repos'), getOption('pkgType'))",
+ pContribUrls);
+}
+
+class AvailablePackagesCache
+{
+public:
+ AvailablePackagesCache()
+ : pMutex_(new boost::mutex())
+ {
+ }
+
+ void insert(const std::string& contribUrl,
+ const std::vector<std::string>& availablePackages)
+ {
+ LOCK_MUTEX(*pMutex_)
+ {
+ cache_[contribUrl] = availablePackages;
+ }
+ END_LOCK_MUTEX
+ }
+
+
+ bool lookup(const std::string& contribUrl,
+ std::vector<std::string>* pAvailablePackages)
+ {
+ LOCK_MUTEX(*pMutex_)
+ {
+ std::map<std::string, std::vector<std::string> >::const_iterator it =
+ cache_.find(contribUrl);
+ if (it != cache_.end())
+ {
+ *pAvailablePackages = it->second;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ END_LOCK_MUTEX
+
+ // keep compiler happy
+ return false;
+ }
+
+private:
+ // make mutex heap based to avoid boost mutex assertions when
+ // it is destructucted in a multicore forked child
+ boost::mutex* pMutex_;
+ std::map<std::string, std::vector<std::string> > cache_;
+};
+
+void downloadAvailablePackages(const std::string& contribUrl,
+ std::vector<std::string>* pAvailablePackages)
+{
+ // cache available packages to minimize http round trips
+ static AvailablePackagesCache s_availablePackagesCache;
+
+ // check cache first
+ std::vector<std::string> availablePackages;
+ if (s_availablePackagesCache.lookup(contribUrl, &availablePackages))
+ {
+ std::copy(availablePackages.begin(),
+ availablePackages.end(),
+ std::back_inserter(*pAvailablePackages));
+ return;
+ }
+
+ http::URL url(contribUrl + "/PACKAGES");
+ http::Request pkgRequest;
+ pkgRequest.setMethod("GET");
+ pkgRequest.setHost(url.hostname());
+ pkgRequest.setUri(url.path());
+ pkgRequest.setHeader("Accept", "*/*");
+ pkgRequest.setHeader("Connection", "close");
+ http::Response pkgResponse;
+
+ Error error = http::sendRequest(url.hostname(),
+ safe_convert::numberToString(url.port()),
+ pkgRequest,
+ &pkgResponse);
+
+ // we don't log errors or bad http status codes because we expect these
+ // requests will fail frequently due to either being offline or unable to
+ // navigate a proxy server
+ if (!error && (pkgResponse.statusCode() == 200))
+ {
+ std::string body = pkgResponse.body();
+ boost::regex re("^Package:\\s*([^\\s]+?)\\s*$");
+
+ boost::sregex_iterator matchBegin(body.begin(), body.end(), re);
+ boost::sregex_iterator matchEnd;
+ std::vector<std::string> results;
+ for (; matchBegin != matchEnd; matchBegin++)
+ {
+ // copy to temporary list for insertion into cache
+ results.push_back((*matchBegin)[1]);
+
+ // append to out param
+ pAvailablePackages->push_back((*matchBegin)[1]);
+ }
+
+ // add to cache
+ s_availablePackagesCache.insert(contribUrl, results);
+ }
+}
+
+Error availablePackagesEnd(const core::json::JsonRpcRequest& request,
+ const std::vector<std::string>& contribUrls,
+ core::json::JsonRpcResponse* pResponse)
+{
+ // download available packages
+ std::vector<std::string> availablePackages;
+ std::for_each(contribUrls.begin(),
+ contribUrls.end(),
+ boost::bind(&downloadAvailablePackages, _1, &availablePackages));
+
+ // order and remove duplicates
+ std::stable_sort(availablePackages.begin(), availablePackages.end());
+ std::unique(availablePackages.begin(), availablePackages.end());
+
+ // return as json
+ json::Array jsonResults;
+ for (size_t i = 0; i < availablePackages.size(); i++)
+ jsonResults.push_back(availablePackages.at(i));
+ pResponse->setResult(jsonResults);
+ return Success();
+}
+
+SEXP rs_enqueLoadedPackageUpdates(SEXP installCmdSEXP)
+{
+ std::string installCmd;
+ if (installCmdSEXP != R_NilValue)
+ installCmd = r::sexp::asString(installCmdSEXP);
+
+ ClientEvent event(client_events::kLoadedPackageUpdates, installCmd);
+ module_context::enqueClientEvent(event);
+
+ return R_NilValue;
+}
+
+void initializeRStudioPackages(bool newSession)
+{
+#ifdef RSTUDIO_UNVERSIONED_BUILD
+ bool force = true;
+#else
+ bool force = false;
+#endif
+
+ if (newSession || (options().programMode() == kSessionProgramModeServer))
+ {
+ std::string libDir = core::string_utils::utf8ToSystem(
+ options().sessionLibraryPath().absolutePath());
+ std::string pkgSrcDir = core::string_utils::utf8ToSystem(
+ options().sessionPackagesPath().absolutePath());
+ std::string rsVersion = RSTUDIO_VERSION;
+ Error error = r::exec::RFunction(".rs.initializeRStudioPackages",
+ libDir,
+ pkgSrcDir,
+ rsVersion,
+ force)
+ .call();
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // register deferred init
+ module_context::events().onDeferredInit.connect(initializeRStudioPackages);
+
+ // register routines
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_enqueLoadedPackageUpdates" ;
+ methodDef.fun = (DL_FUNC) rs_enqueLoadedPackageUpdates ;
+ methodDef.numArgs = 1;
+ r::routines::addCallMethod(methodDef);
+
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcAsyncCoupleMethod<std::vector<std::string> >,
+ "available_packages",
+ availablePackagesBegin,
+ availablePackagesEnd))
+ (bind(sourceModuleRFile, "SessionPackages.R"))
+ (bind(r::exec::executeString, ".rs.packages.initialize()"));
+ return initBlock.execute();
+}
+
+
+} // namespace packages
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionPackages.hpp b/src/cpp/session/modules/SessionPackages.hpp
new file mode 100644
index 0000000..fb62fa7
--- /dev/null
+++ b/src/cpp/session/modules/SessionPackages.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionPackages.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PACKAGES_HPP
+#define SESSION_PACKAGES_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace packages {
+
+core::Error initialize();
+
+} // namespace packages
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PACKAGES_HPP
diff --git a/src/cpp/session/modules/SessionPath.cpp b/src/cpp/session/modules/SessionPath.cpp
new file mode 100644
index 0000000..faa6688
--- /dev/null
+++ b/src/cpp/session/modules/SessionPath.cpp
@@ -0,0 +1,133 @@
+/*
+ * SessionPath.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionPath.hpp"
+
+#include <string>
+#include <vector>
+
+#include <boost/regex.hpp>
+#include <boost/bind.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace path {
+
+namespace {
+
+Error readPathsFromFile(const FilePath& filePath,
+ std::vector<std::string>* pPaths)
+{
+ std::vector<std::string> paths;
+ Error error = core::readStringVectorFromFile(filePath, &paths);
+ if (error)
+ {
+ error.addProperty("path-source", filePath.absolutePath());
+ return error;
+ }
+
+ std::copy(paths.begin(), paths.end(), std::back_inserter(*pPaths));
+
+ return Success();
+}
+
+void safeReadPathsFromFile(const FilePath& filePath,
+ std::vector<std::string>* pPaths)
+{
+ Error error = readPathsFromFile(filePath, pPaths);
+ if (error)
+ LOG_ERROR(error);
+}
+
+void addToPathIfNecessary(const std::string& entry, std::string* pPath)
+{
+ if (!regex_search(*pPath, boost::regex("(^|:)" + entry + "/?($|:)")))
+ {
+ if (!pPath->empty())
+ pPath->push_back(':');
+ pPath->append(entry);
+ }
+}
+
+} // anonymous namespace
+
+
+Error initialize()
+{
+#ifdef __APPLE__
+ // read /etc/paths
+ std::vector<std::string> paths;
+ safeReadPathsFromFile(FilePath("/etc/paths"), &paths);
+
+ // read /etc/paths.d/* (once again failure is not fatal as we
+ // can fall back to the previous setting)
+ FilePath pathsD("/etc/paths.d");
+ if (pathsD.exists())
+ {
+ // enumerate the children
+ std::vector<FilePath> pathsDChildren;
+ Error error = pathsD.children(&pathsDChildren);
+ if (error)
+ LOG_ERROR(error);
+
+ // collect their paths
+ std::for_each(pathsDChildren.begin(),
+ pathsDChildren.end(),
+ boost::bind(safeReadPathsFromFile, _1, &paths));
+
+ }
+
+ // build the PATH
+ std::string path = core::system::getenv("PATH");
+ std::for_each(paths.begin(),
+ paths.end(),
+ boost::bind(addToPathIfNecessary, _1, &path));
+
+ // do we need to add /usr/texbin (sometimes texlive doesn't get this
+ // written into /etc/paths.d)
+ FilePath texbinPath("/usr/texbin");
+ if (texbinPath.exists())
+ addToPathIfNecessary(texbinPath.absolutePath(), &path);
+
+ // add /opt/local/bin if necessary
+ FilePath optLocalBinPath("/opt/local/bin");
+ if (optLocalBinPath.exists())
+ addToPathIfNecessary(optLocalBinPath.absolutePath(), &path);
+
+ // set the path
+ core::system::setenv("PATH", path);
+#endif
+
+ return Success();
+
+}
+
+
+} // namespace path
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionPath.hpp b/src/cpp/session/modules/SessionPath.hpp
new file mode 100644
index 0000000..4f16ae6
--- /dev/null
+++ b/src/cpp/session/modules/SessionPath.hpp
@@ -0,0 +1,35 @@
+/*
+ * SessionPath.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_PATH_HPP
+#define SESSION_SESSION_PATH_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace path {
+
+core::Error initialize();
+
+} // namespace path
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_PATH_HPP
diff --git a/src/cpp/session/modules/SessionPlots.cpp b/src/cpp/session/modules/SessionPlots.cpp
new file mode 100644
index 0000000..62defdb
--- /dev/null
+++ b/src/cpp/session/modules/SessionPlots.cpp
@@ -0,0 +1,811 @@
+/*
+ * SessionPlots.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionPlots.hpp"
+
+#include <boost/format.hpp>
+#include <boost/iostreams/filter/regex.hpp>
+
+#include <core/Error.hpp>
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/Predicate.hpp>
+#include <core/FilePath.hpp>
+#include <core/BoostErrors.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/system/Environment.hpp>
+
+#include <core/text/TemplateFilter.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RGraphics.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace plots {
+
+namespace {
+
+// locations
+#define kGraphics "/graphics"
+
+Error nextPlot(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::graphics::Display& display = r::session::graphics::display();
+ return display.setActivePlot(display.activePlotIndex() + 1);
+}
+
+Error previousPlot(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::graphics::Display& display = r::session::graphics::display();
+ return display.setActivePlot(display.activePlotIndex() - 1);
+}
+
+
+Error removePlot(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::graphics::Display& display = r::session::graphics::display();
+
+ if (display.plotCount() < 1)
+ {
+ return Error(core::json::errc::ParamInvalid, ERROR_LOCATION);
+ }
+ else if (display.plotCount() == 1)
+ {
+ r::session::graphics::display().clear();
+ return Success();
+ }
+ else
+ {
+ int activePlot = display.activePlotIndex();
+ return display.removePlot(activePlot);
+ }
+}
+
+
+Error clearPlots(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::graphics::display().clear();
+ return Success();
+}
+
+Error refreshPlot(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ r::session::graphics::display().refresh();
+ return Success();
+}
+
+json::Object boolObject(bool value)
+{
+ json::Object boolObject ;
+ boolObject["value"] = value;
+ return boolObject;
+}
+
+Error savePlotAs(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get args
+ std::string path, format;
+ int width, height;
+ bool overwrite;
+ Error error = json::readParams(request.params,
+ &path,
+ &format,
+ &width,
+ &height,
+ &overwrite);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath plotPath = module_context::resolveAliasedPath(path);
+
+ // if it already exists and we aren't ovewriting then return false
+ if (plotPath.exists() && !overwrite)
+ {
+ pResponse->setResult(boolObject(false));
+ return Success();
+ }
+
+ // save plot
+ using namespace r::session::graphics;
+ Display& display = r::session::graphics::display();
+ error = display.savePlotAsImage(plotPath, format, width, height);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return error;
+ }
+
+
+ // set success result
+ pResponse->setResult(boolObject(true));
+ return Success();
+}
+
+
+Error savePlotAsPdf(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get args
+ std::string path;
+ double width, height;
+ bool useCairoPdf, overwrite;
+ Error error = json::readParams(request.params,
+ &path,
+ &width,
+ &height,
+ &useCairoPdf,
+ &overwrite);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath plotPath = module_context::resolveAliasedPath(path);
+
+ // if it already exists and we aren't ovewriting then return false
+ if (plotPath.exists() && !overwrite)
+ {
+ pResponse->setResult(boolObject(false));
+ return Success();
+ }
+
+ // save plot
+ using namespace r::session::graphics;
+ Display& display = r::session::graphics::display();
+ error = display.savePlotAsPdf(plotPath, width, height, useCairoPdf);
+ if (error)
+ {
+ LOG_ERROR_MESSAGE(r::endUserErrorMessage(error));
+ return error;
+ }
+
+ // set success result
+ pResponse->setResult(boolObject(true));
+ return Success();
+}
+
+Error copyPlotToClipboardMetafile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get args
+ int width, height;
+ Error error = json::readParams(request.params, &width, &height);
+ if (error)
+ return error;
+
+#if _WIN32
+
+ // create temp file to write to
+ FilePath targetFile = module_context::tempFile("clipboard", "emf");
+
+ // save as metafile
+ using namespace r::session::graphics;
+ Display& display = r::session::graphics::display();
+ error = display.savePlotAsMetafile(targetFile, width, height);
+ if (error)
+ return error;
+
+ // copy to clipboard
+ error = system::copyMetafileToClipboard(targetFile);
+ if (error)
+ return error;
+
+ // remove temp file
+ error = targetFile.remove();
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+
+#else
+ return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
+#endif
+}
+
+
+
+bool hasStem(const FilePath& filePath, const std::string& stem)
+{
+ return filePath.stem() == stem;
+}
+
+json::Object plotExportFormat(const std::string& name,
+ const std::string& extension)
+{
+ json::Object formatJson;
+ formatJson["name"] = name;
+ formatJson["extension"] = extension;
+ return formatJson;
+}
+
+Error uniqueSavePlotStem(const FilePath& directoryPath, std::string* pStem)
+{
+ // determine unique file name
+ std::vector<FilePath> children;
+ Error error = directoryPath.children(&children);
+ if (error)
+ return error;
+
+ // search for unique stem
+ int i = 0;
+ *pStem = "Rplot";
+ while(true)
+ {
+ // seek stem
+ std::vector<FilePath>::const_iterator it = std::find_if(
+ children.begin(),
+ children.end(),
+ boost::bind(hasStem, _1, *pStem));
+ // break if not found
+ if (it == children.end())
+ break;
+
+ // update stem and search again
+ boost::format fmt("Rplot%1%");
+ *pStem = boost::str(fmt % boost::io::group(std::setfill('0'),
+ std::setw(2),
+ ++i));
+ }
+
+ return Success();
+}
+
+Error getUniqueSavePlotStem(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get directory arg and convert to path
+ std::string directory;
+ Error error = json::readParam(request.params, 0, &directory);
+ if (error)
+ return error;
+ FilePath directoryPath = module_context::resolveAliasedPath(directory);
+
+ // get stem
+ std::string stem;
+ error = uniqueSavePlotStem(directoryPath, &stem);
+ if (error)
+ return error;
+
+ // set resposne
+ pResponse->setResult(stem);
+ return Success();
+}
+
+bool supportsSvg()
+{
+ bool supportsSvg = false;
+ Error error = r::exec::RFunction("capabilities", "cairo").call(&supportsSvg);
+ if (error)
+ LOG_ERROR(error);
+ return supportsSvg;
+}
+
+Error getSavePlotContext(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get directory arg
+ std::string directory;
+ Error error = json::readParam(request.params, 0, &directory);
+ if (error)
+ return error;
+
+ // context
+ json::Object contextJson;
+
+ // get supported formats
+ using namespace r::session::graphics;
+ json::Array formats;
+ formats.push_back(plotExportFormat("PNG", kPngFormat));
+ formats.push_back(plotExportFormat("JPEG", kJpegFormat));
+ formats.push_back(plotExportFormat("TIFF", kTiffFormat));
+ formats.push_back(plotExportFormat("BMP", kBmpFormat));
+#if _WIN32
+ formats.push_back(plotExportFormat("Metafile", kMetafileFormat));
+#endif
+ if(supportsSvg())
+ formats.push_back(plotExportFormat("SVG", kSvgFormat));
+ formats.push_back(plotExportFormat("EPS", kPostscriptFormat));
+ contextJson["formats"] = formats;
+
+ // get directory path -- if it doesn't exist revert to the current
+ // working directory
+ FilePath directoryPath = module_context::resolveAliasedPath(directory);
+ if (!directoryPath.exists())
+ directoryPath = module_context::safeCurrentPath();
+
+ // reflect directory back to caller
+ contextJson["directory"] = module_context::createFileSystemItem(directoryPath);
+
+ // get unique stem
+ std::string stem;
+ error = uniqueSavePlotStem(directoryPath, &stem);
+ if (error)
+ return error;
+ contextJson["uniqueFileStem"] = stem;
+
+ pResponse->setResult(contextJson);
+
+ return Success();
+}
+
+template <typename T>
+bool extractSizeParams(const http::Request& request,
+ T min,
+ T max,
+ T* pWidth,
+ T* pHeight,
+ http::Response* pResponse)
+{
+ // get the width and height parameters
+ if (!request.queryParamValue("width",
+ predicate::range(min, max),
+ pWidth))
+ {
+ pResponse->setError(http::status::BadRequest, "invalid width");
+ return false;
+ }
+ if (!request.queryParamValue("height",
+ predicate::range(min, max),
+ pHeight))
+ {
+ pResponse->setError(http::status::BadRequest, "invalid height");
+ return false;
+ }
+
+ // got two valid params
+ return true;
+}
+
+void setImageFileResponse(const FilePath& imageFilePath,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // set content type
+ pResponse->setContentType(imageFilePath.mimeContentType());
+
+ // attempt gzip
+ if (request.acceptsEncoding(http::kGzipEncoding))
+ pResponse->setContentEncoding(http::kGzipEncoding);
+
+ // set file
+ Error error = pResponse->setBody(imageFilePath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ pResponse->setError(http::status::InternalServerError,
+ error.code().message());
+ }
+}
+
+void setTemporaryFileResponse(const FilePath& filePath,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // no cache (dynamic content)
+ pResponse->setNoCacheHeaders();
+
+ // return the file
+ pResponse->setFile(filePath, request);
+
+ // delete the file
+ Error error = filePath.remove();
+ if (error)
+ LOG_ERROR(error);
+}
+
+void handleZoomRequest(const http::Request& request, http::Response* pResponse)
+{
+ using namespace r::session;
+
+ // get the width and height parameters
+ int width, height;
+ if (!extractSizeParams(request, 100, 3000, &width, &height, pResponse))
+ return ;
+
+ // fire off the plot zoom size changed event to notify the client
+ // that a new default size should be established
+ json::Object dataJson;
+ dataJson["width"] = width;
+ dataJson["height"] = height;
+ ClientEvent event(client_events::kPlotsZoomSizeChanged, dataJson);
+ module_context::enqueClientEvent(event);
+
+ // get the scale parameter
+ int scale = request.queryParamValue("scale", 1);
+
+ // define template
+ std::stringstream templateStream;
+ templateStream <<
+ "<html>"
+ "<head>"
+ "<title>Plot Zoom</title>"
+ "<script type=\"text/javascript\">"
+
+ "window.onresize = function() {"
+
+ "var plotEl = document.getElementById('plot');"
+ "if (plotEl && (#scale#==1) ) {"
+ "plotEl.style.width='100%';"
+ "plotEl.style.height='100%';"
+ "}"
+
+ "if(window.activeTimer)"
+ "clearTimeout(window.activeTimer);"
+
+ "window.activeTimer = setTimeout( function() { "
+
+ "window.location.href = "
+ "\"plot_zoom?width=\" + document.body.clientWidth "
+ " + \"&height=\" + document.body.clientHeight "
+ " + \"&scale=\" + #scale#;"
+ "}, 300);"
+ "}"
+ "</script>"
+ "</head>"
+ "<body style=\"margin: 0; overflow: hidden\">"
+ "<img id=\"plot\" src=\"plot_zoom_png?width=#width#&height=#height#\"/>"
+ "</body>"
+ "</html>";
+
+ // define variables
+ std::map<std::string,std::string> variables;
+ variables["width"] = safe_convert::numberToString(width);
+ variables["height"] = safe_convert::numberToString(height);
+ variables["scale"] = safe_convert::numberToString(scale);;
+ text::TemplateFilter filter(variables);
+
+ pResponse->setNoCacheHeaders();
+ pResponse->setBody(templateStream, filter);
+ pResponse->setContentType("text/html");
+}
+
+void handleZoomPngRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ using namespace r::session;
+
+ // get the width and height parameters
+ int width, height;
+ if (!extractSizeParams(request, 100, 5000, &width, &height, pResponse))
+ return ;
+
+ // generate the file
+ using namespace r::session::graphics;
+ FilePath imagePath = module_context::tempFile("plot", "png");
+ Error saveError = graphics::display().savePlotAsImage(imagePath,
+ kPngFormat,
+ width,
+ height);
+ if (saveError)
+ {
+ pResponse->setError(http::status::InternalServerError,
+ saveError.code().message());
+ return;
+ }
+
+ // send it back
+ setImageFileResponse(imagePath, request, pResponse);
+
+ // delete the temp file
+ Error error = imagePath.remove();
+ if (error)
+ LOG_ERROR(error);
+}
+
+void handlePngRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // get the width and height parameters
+ int width, height;
+ if (!extractSizeParams(request, 100, 5000, &width, &height, pResponse))
+ return ;
+
+ // generate the image
+ using namespace r::session;
+ FilePath imagePath = module_context::tempFile("plot", "png");
+ Error error = graphics::display().savePlotAsImage(imagePath,
+ graphics::kPngFormat,
+ width,
+ height);
+ if (error)
+ {
+ pResponse->setError(http::status::InternalServerError,
+ error.code().message());
+ return;
+ }
+
+ // check for attachment flag and set content-disposition
+ bool attachment = request.queryParamValue("attachment") == "1";
+ if (attachment)
+ {
+ pResponse->setHeader("Content-Disposition",
+ "attachment; filename=rstudio-plot" +
+ imagePath.extension());
+ }
+
+ // return it
+ setTemporaryFileResponse(imagePath, request, pResponse);
+}
+
+
+// NOTE: this function assumes it is retreiving the image for the currently
+// active plot (the assumption is implied by the fact that file not found
+// on the requested png results in a redirect to the currently active
+// plot's png). to handle this redirection we should always maintain an
+// entry point with these semantics. if we wish to have an entry point
+// for obtaining arbitrary pngs then it should be separate from this.
+
+void handleGraphicsRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // extract plot key from request (take everything after the last /)
+ std::string uri = request.uri();
+ std::size_t lastSlashPos = uri.find_last_of('/');
+ if (lastSlashPos == std::string::npos ||
+ lastSlashPos == (uri.length() - 1))
+ {
+ std::string errmsg = "invalid graphics uri: " + uri;
+ LOG_ERROR_MESSAGE(errmsg);
+ pResponse->setError(http::status::NotFound, errmsg);
+ return ;
+ }
+ std::string filename = uri.substr(lastSlashPos+1);
+
+ // calculate the path to the png
+ using namespace r::session;
+ FilePath imagePath = graphics::display().imagePath(filename);
+
+ // if it exists then return it
+ if (imagePath.exists())
+ {
+ // strong named - cache permanently (in user's browser only)
+ pResponse->setPrivateCacheForeverHeaders();
+
+ // set the file
+ setImageFileResponse(imagePath, request, pResponse);
+ }
+ else
+ {
+ // redirect to png for currently active plot
+ if (graphics::display().hasOutput())
+ {
+ // calculate location of current image
+ std::string imageFilename = graphics::display().imageFilename();
+ std::string imageLocation = std::string(kGraphics "/") +
+ imageFilename;
+
+ // redirect to it
+ pResponse->setMovedTemporarily(request, imageLocation);
+ }
+ else
+ {
+ // not found error
+ pResponse->setError(http::status::NotFound,
+ request.uri() + " not found");
+ }
+ }
+}
+
+
+void enquePlotsChanged(const r::session::graphics::DisplayState& displayState,
+ bool activatePlots, bool showManipulator)
+{
+ // build graphics output event
+ json::Object jsonPlotsState;
+ jsonPlotsState["filename"] = displayState.imageFilename;
+ jsonPlotsState["manipulator"] = displayState.manipulatorJson;
+ jsonPlotsState["width"] = displayState.width;
+ jsonPlotsState["height"] = displayState.height;
+ jsonPlotsState["plotIndex"] = displayState.activePlotIndex;
+ jsonPlotsState["plotCount"] = displayState.plotCount;
+ jsonPlotsState["activatePlots"] = activatePlots &&
+ (displayState.plotCount > 0);
+ jsonPlotsState["showManipulator"] = showManipulator;
+ ClientEvent plotsStateChangedEvent(client_events::kPlotsStateChanged,
+ jsonPlotsState);
+
+ // fire it
+ module_context::enqueClientEvent(plotsStateChangedEvent);
+
+}
+
+void renderGraphicsOutput(bool activatePlots, bool showManipulator)
+{
+ using namespace r::session;
+ if (graphics::display().hasOutput())
+ {
+ graphics::display().render(
+ boost::bind(enquePlotsChanged, _1, activatePlots, showManipulator));
+ }
+}
+
+
+void onClientInit()
+{
+ // if we have output make sure the client knows about it
+ renderGraphicsOutput(false, false);
+}
+
+void detectChanges(bool activatePlots)
+{
+ // check for changes
+ using namespace r::session;
+ if (graphics::display().hasChanges())
+ {
+ graphics::display().render(boost::bind(enquePlotsChanged,
+ _1,
+ activatePlots,
+ false));
+ }
+}
+
+void onDetectChanges(module_context::ChangeSource source)
+{
+ bool activatePlots = source == module_context::ChangeSourceREPL;
+ detectChanges(activatePlots);
+}
+
+void onBackgroundProcessing(bool)
+{
+ using namespace r::session;
+ if (graphics::display().isActiveDevice() && graphics::display().hasChanges())
+ {
+ // verify that the last change is more than 50ms old. the reason
+ // we check for changes in the background is so we can respect
+ // plot animations (typically implemented using Sys.sleep). however,
+ // we don't want this to spill over inot incrementally rendering all
+ // plots as this will slow down overall plotting performance
+ // considerably.
+ using namespace boost::posix_time;
+ const int kChangeWindowMs = 50;
+ if ((graphics::display().lastChange() + milliseconds(kChangeWindowMs)) <
+ boost::posix_time::microsec_clock::universal_time())
+ {
+ detectChanges(true);
+ }
+ }
+}
+
+void onBeforeExecute()
+{
+ r::session::graphics::display().onBeforeExecute();
+}
+
+void onShowManipulator()
+{
+ // render changes and show manipulator
+ renderGraphicsOutput(true, true);
+}
+
+Error setManipulatorValues(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read the params
+ json::Object jsObject;
+ Error error = json::readParam(request.params, 0, &jsObject);
+ if (error)
+ return error;
+
+ // set them
+ using namespace r::session;
+ graphics::display().setPlotManipulatorValues(jsObject);
+
+ // render
+ renderGraphicsOutput(true, false);
+
+ return Success();
+}
+
+Error manipulatorPlotClicked(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read the params
+ int x, y;
+ Error error = json::readParams(request.params, &x, &y);
+ if (error)
+ return error;
+
+ // notify the device
+ using namespace r::session;
+ graphics::display().manipulatorPlotClicked(x, y);
+
+ // render
+ renderGraphicsOutput(true, false);
+
+ return Success();
+}
+
+
+
+} // anonymous namespace
+
+bool haveCairoPdf()
+{
+ // make sure there is a real x server running on osx
+#ifdef __APPLE__
+ std::string display = core::system::getenv("DISPLAY");
+ if (display.empty() || (display == ":0"))
+ return false;
+#endif
+
+ SEXP functionSEXP = R_NilValue;
+ r::sexp::Protect rProtect;
+ r::exec::RFunction f(".rs.getPackageFunction", "cairo_pdf", "grDevices");
+ Error error = f.call(&functionSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ return functionSEXP != R_NilValue;
+}
+
+Error initialize()
+{
+ // subscribe to events
+ using boost::bind;
+ module_context::events().onClientInit.connect(bind(onClientInit));
+ module_context::events().onDetectChanges.connect(bind(onDetectChanges, _1));
+ module_context::events().onBeforeExecute.connect(bind(onBeforeExecute));
+ module_context::events().onBackgroundProcessing.connect(onBackgroundProcessing);
+
+ // connect to onShowManipulator
+ using namespace r::session;
+ graphics::display().onShowManipulator().connect(bind(onShowManipulator));
+
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "next_plot", nextPlot))
+ (bind(registerRpcMethod, "previous_plot", previousPlot))
+ (bind(registerRpcMethod, "remove_plot", removePlot))
+ (bind(registerRpcMethod, "clear_plots", clearPlots))
+ (bind(registerRpcMethod, "refresh_plot", refreshPlot))
+ (bind(registerRpcMethod, "save_plot_as", savePlotAs))
+ (bind(registerRpcMethod, "save_plot_as_pdf", savePlotAsPdf))
+ (bind(registerRpcMethod, "copy_plot_to_clipboard_metafile", copyPlotToClipboardMetafile))
+ (bind(registerRpcMethod, "get_unique_save_plot_stem", getUniqueSavePlotStem))
+ (bind(registerRpcMethod, "get_save_plot_context", getSavePlotContext))
+ (bind(registerRpcMethod, "set_manipulator_values", setManipulatorValues))
+ (bind(registerRpcMethod, "manipulator_plot_clicked", manipulatorPlotClicked))
+ (bind(registerUriHandler, kGraphics "/plot_zoom_png", handleZoomPngRequest))
+ (bind(registerUriHandler, kGraphics "/plot_zoom", handleZoomRequest))
+ (bind(registerUriHandler, kGraphics "/plot.png", handlePngRequest))
+ (bind(registerUriHandler, kGraphics, handleGraphicsRequest));
+ return initBlock.execute();
+}
+
+} // namespace plots
+} // namespace modules
+} // namespace session
+
diff --git a/src/cpp/session/modules/SessionPlots.hpp b/src/cpp/session/modules/SessionPlots.hpp
new file mode 100644
index 0000000..a73ff28
--- /dev/null
+++ b/src/cpp/session/modules/SessionPlots.hpp
@@ -0,0 +1,35 @@
+/*
+ * SessionPlots.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PLOTS_HPP
+#define SESSION_PLOTS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace plots {
+
+bool haveCairoPdf();
+
+core::Error initialize();
+
+} // namespace plots
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_PLOTS_HPP
diff --git a/src/cpp/session/modules/SessionPresentation.R b/src/cpp/session/modules/SessionPresentation.R
new file mode 100644
index 0000000..d5ecd3a
--- /dev/null
+++ b/src/cpp/session/modules/SessionPresentation.R
@@ -0,0 +1,35 @@
+#
+# SessionPresentation.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction( "showPresentation", function(file = ".") {
+
+ if (!is.character(file))
+ stop("file must be of type character")
+
+ invisible(.Call(getNativeSymbolInfo("rs_showPresentation", PACKAGE=""),
+ .rs.normalizePath(path.expand(file))))
+})
+
+.rs.addFunction( "showPresentationHelpDoc", function(doc) {
+
+ if (!is.character(doc))
+ stop("doc must be of type character")
+
+ invisible(.Call(getNativeSymbolInfo("rs_showPresentationHelpDoc", PACKAGE=""),
+ doc))
+})
+
+
+
diff --git a/src/cpp/session/modules/SessionProfiler.R b/src/cpp/session/modules/SessionProfiler.R
new file mode 100644
index 0000000..4b4d225
--- /dev/null
+++ b/src/cpp/session/modules/SessionProfiler.R
@@ -0,0 +1,16 @@
+#
+# SessionProfiler.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+
diff --git a/src/cpp/session/modules/SessionProfiler.cpp b/src/cpp/session/modules/SessionProfiler.cpp
new file mode 100644
index 0000000..168e424
--- /dev/null
+++ b/src/cpp/session/modules/SessionProfiler.cpp
@@ -0,0 +1,51 @@
+/*
+ * SessionProfiler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionProfiler.hpp"
+
+#include <core/Exec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace profiler {
+
+namespace {
+
+
+
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (boost::bind(module_context::sourceModuleRFile, "SessionProfiler.R"));
+ return initBlock.execute();
+
+}
+
+
+
+} // namespace profiler
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionProfiler.hpp b/src/cpp/session/modules/SessionProfiler.hpp
new file mode 100644
index 0000000..ef41b8e
--- /dev/null
+++ b/src/cpp/session/modules/SessionProfiler.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionProfiler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PROFILER_HPP
+#define SESSION_PROFILER_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace profiler {
+
+core::Error initialize();
+
+} // namespace profiler
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PROFILER_HPP
diff --git a/src/cpp/session/modules/SessionRMarkdown.cpp b/src/cpp/session/modules/SessionRMarkdown.cpp
new file mode 100644
index 0000000..5a1c448
--- /dev/null
+++ b/src/cpp/session/modules/SessionRMarkdown.cpp
@@ -0,0 +1,57 @@
+/*
+ * SessionRMarkdown.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionRMarkdown.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/FileSerializer.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace rmarkdown {
+
+namespace {
+
+void initPandocPath()
+{
+ r::exec::RFunction sysSetenv("Sys.setenv");
+ sysSetenv.addParam("RSTUDIO_PANDOC",
+ session::options().pandocPath().absolutePath());
+ Error error = sysSetenv.call();
+ if (error)
+ LOG_ERROR(error);
+}
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ initPandocPath();
+
+ return Success();
+}
+
+} // namepsace rmarkdown
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionRMarkdown.hpp b/src/cpp/session/modules/SessionRMarkdown.hpp
new file mode 100644
index 0000000..5b5f347
--- /dev/null
+++ b/src/cpp/session/modules/SessionRMarkdown.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionRMarkdown.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_RMARKDOWN_HPP
+#define SESSION_SESSION_RMARKDOWN_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace rmarkdown {
+
+core::Error initialize();
+
+} // namespace rmarkdown
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_RMARKDOWN_HPP
diff --git a/src/cpp/session/modules/SessionRPubs.R b/src/cpp/session/modules/SessionRPubs.R
new file mode 100644
index 0000000..42c7496
--- /dev/null
+++ b/src/cpp/session/modules/SessionRPubs.R
@@ -0,0 +1,422 @@
+#
+# SessionRPubs.R
+#
+# Copyright (C) 2009-1012 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU General Public License. This program is distributed WITHOUT ANY
+# EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# GPL (http://www.gnu.org/licenses/gpl-3.0.txt) for more details.
+#
+#
+
+#' Upload an HTML file to RPubs
+#'
+#' This function uploads an HTML file to rpubs.com. If the upload succeeds a
+#' list that includes an \code{id} and \code{continueUrl} is returned. A browser
+#' should be opened to the \code{continueUrl} to complete publishing of the
+#' document. If an error occurs then a diagnostic message is returned in the
+#' \code{error} element of the list.
+#'
+#' @param title The title of the document.
+#' @param htmlFile The path to the HTML file to upload.
+#' @param id If this upload is an update of an existing document then the id
+#' parameter should specify the document id to update. Note that the id is
+#' provided as an element of the list returned by successful calls to
+#' \code{rpubsUpload}.
+#' @param properties A named list containing additional document properties
+#' (RPubs doesn't currently expect any additional properties, this parameter
+#' is reserved for future use).
+#' @param method Method to be used for uploading. "internal" uses a plain http
+#' socket connection; "curl" uses the curl binary to do an https upload;
+#' "rcurl" uses the RCurl package to do an https upload; and "auto" uses
+#' the best available method searched for in the following order: "curl",
+#' "rcurl", and then "internal". The global default behavior can be
+#' configured by setting the \code{rpubs.upload.method} option (the default
+#' is "auto").
+#'
+#' @return A named list. If the upload was successful then the list contains a
+#' \code{id} element that can be used to subsequently update the document as
+#' well as a \code{continueUrl} element that provides a URL that a browser
+#' should be opened to in order to complete publishing of the document. If the
+#' upload fails then the list contains an \code{error} element which contains
+#' an explanation of the error that occurred.
+#'
+#' @examples
+#' \dontrun{
+#' # upload a document
+#' result <- rpubsUpload("My document title", "Document.html")
+#' if (!is.null(result$continueUrl))
+#' browseURL(result$continueUrl)
+#' else
+#' stop(result$error)
+#'
+#' # update the same document with a new title
+#' updateResult <- rpubsUpload("My updated title", "Document.html", result$id)
+#' }
+rpubsUpload <- function(title,
+ htmlFile,
+ id = NULL,
+ properties = list(),
+ method = getOption("rpubs.upload.method")) {
+
+ # validate inputs
+ if (!is.character(title))
+ stop("title must be specified")
+ if (nzchar(title) == FALSE)
+ stop("title pmust be a non-empty string")
+ if (!is.character(htmlFile))
+ stop("htmlFile parameter must be specified")
+ if (!file.exists(htmlFile))
+ stop("specified htmlFile does not exist")
+ if (!is.list(properties))
+ stop("properties paramater must be a named list")
+
+ # resolve method to auto if necessary
+ if (is.null(method))
+ method <- "auto"
+
+
+ parseHeader <- function(header) {
+ split <- strsplit(header, ": ")[[1]]
+ if (length(split) == 2)
+ return (list(name = split[1], value = split[2]))
+ else
+ return (NULL)
+ }
+
+ jsonEscapeString <- function(value) {
+ chars <- strsplit(value, "")[[1]]
+ chars <- vapply(chars, function(x) {
+ if (x %in% c('"', '\\', '/'))
+ paste('\\', x, sep='')
+ else if (charToRaw(x) < 20)
+ paste('\\u', toupper(format(as.hexmode(as.integer(charToRaw(x))),
+ width=4)),
+ sep='')
+ else
+ x
+ }, character(1))
+ paste(chars, sep="", collapse="")
+ }
+
+ jsonProperty <- function(name, value) {
+ paste("\"",
+ jsonEscapeString(enc2utf8(name)),
+ "\" : \"",
+ jsonEscapeString(enc2utf8(value)),
+ "\"",
+ sep="")
+ }
+
+ regexExtract <- function(re, input) {
+ match <- regexec(re, input)
+ matchLoc <- match[1][[1]]
+ if (length(matchLoc) > 1) {
+ matchLen <-attributes(matchLoc)$match.length
+ url <- substr(input, matchLoc[2], matchLoc[2] + matchLen[2]-1)
+ return (url)
+ }
+ else {
+ return (NULL)
+ }
+ }
+
+ # NOTE: we parse the json naively using a regex because:
+ # - We don't want to take a dependency on a json library for just this case
+ # - We know the payload is an ascii url so we don't need a robust parser
+ parseContinueUrl <- function(continueUrl) {
+ regexExtract("\\{\\s*\"continueUrl\"\\s*:\\s*\"([^\"]+)\"\\s*\\}",
+ continueUrl)
+ }
+
+ parseHttpStatusCode <- function(statusLine) {
+ statusCode <- regexExtract("HTTP/[0-9]+\\.[0-9]+ ([0-9]+).*", statusLine)
+ if (is.null(statusCode))
+ return (-1)
+ else
+ return (as.integer(statusCode))
+ }
+
+ pathFromId <- function(id) {
+ split <- strsplit(id, "^https?://[^/]+")[[1]]
+ if (length(split) == 2)
+ return (split[2])
+ else
+ return (NULL)
+ }
+
+ buildPackage <- function(title,
+ htmlFile,
+ properties = list()) {
+
+ # build package.json
+ packageJson <- "{"
+ packageJson <- paste(packageJson, jsonProperty("title", title), ",")
+ for (name in names(properties)) {
+ if (nzchar(name) == FALSE)
+ stop("all properties must be named")
+ value <- properties[[name]]
+ packageJson <- paste(packageJson, jsonProperty(name, value), ",")
+ }
+ packageJson <- substr(packageJson, 1, nchar(packageJson)-1)
+ packageJson <- paste(packageJson,"}")
+
+ # create a tempdir to build the package in and copy the files to it
+ fileSep <- .Platform$file.sep
+ packageDir <- tempfile()
+ dir.create(packageDir)
+ packageFile <- function(fileName) {
+ paste(packageDir,fileName,sep=fileSep)
+ }
+ writeLines(packageJson, packageFile("package.json"))
+ file.copy(htmlFile, packageFile("index.html"))
+
+ # switch to the package dir for building
+ oldWd <- getwd()
+ setwd(packageDir)
+ on.exit(setwd(oldWd))
+
+ # create the tarball
+ tarfile <- tempfile("package", fileext = ".tar.gz")
+ utils::tar(tarfile, files = ".", compression = "gzip")
+
+ # return the full path to the tarball
+ return (tarfile)
+ }
+
+ readResponse <- function(conn) {
+ # read status code
+ resp <- readLines(conn, 1)
+ statusCode <- parseHttpStatusCode(resp[1])
+
+ # read response headers
+ contentLength <- NULL
+ location <- NULL
+ repeat {
+ resp <- readLines(conn, 1)
+ if (nzchar(resp) == 0)
+ break()
+
+ header <- parseHeader(resp)
+ if (!is.null(header))
+ {
+ if (identical(header$name, "Content-Type"))
+ contentType <- header$value
+ if (identical(header$name, "Content-Length"))
+ contentLength <- as.integer(header$value)
+ if (identical(header$name, "Location"))
+ location <- header$value
+ }
+ }
+
+ # read the response content
+ content <- rawToChar(readBin(conn, what = 'raw', n=contentLength))
+
+ # return list
+ list(status = statusCode,
+ location = location,
+ contentType = contentType,
+ content = content)
+ }
+
+ # internal sockets implementation of upload (supports http-only)
+ internalUpload <- function(path,
+ contentType,
+ headers,
+ packageFile) {
+
+ # read file in binary mode
+ fileLength <- file.info(packageFile)$size
+ fileContents <- readBin(packageFile, what="raw", n=fileLength)
+
+ # build http request
+ request <- NULL
+ request <- c(request, paste("POST ", path, " HTTP/1.1\r\n", sep=""))
+ request <- c(request, "User-Agent: RStudio\r\n")
+ request <- c(request, "Host: api.rpubs.com\r\n")
+ request <- c(request, "Accept: */*\r\n")
+ request <- c(request, paste("Content-Type: ",
+ contentType,
+ "\r\n",
+ sep=""))
+ request <- c(request, paste("Content-Length: ",
+ fileLength,
+ "\r\n",
+ sep=""))
+ for (name in names(headers))
+ {
+ request <- c(request,
+ paste(name, ": ", headers[[name]], "\r\n", sep=""))
+ }
+ request <- c(request, "\r\n")
+
+ # open socket connection
+ conn <- socketConnection(host="api.rpubs.com",
+ port=80,
+ open="w+b",
+ blocking=TRUE)
+ on.exit(close(conn))
+
+ # write the request header and file payload
+ writeBin(charToRaw(paste(request,collapse="")), conn, size=1)
+ writeBin(fileContents, conn, size=1)
+
+ # read the response
+ readResponse(conn)
+ }
+
+
+ rcurlUpload <- function(path,
+ contentType,
+ headers,
+ packageFile) {
+
+ require(RCurl)
+
+ # url to post to
+ url <- paste("https://api.rpubs.com", path, sep="")
+
+ # upload package file
+ params <- list(file = RCurl::fileUpload(filename = packageFile,
+ contentType = contentType))
+
+ # use custom header and text gatherers
+ options <- RCurl::curlOptions(url)
+ headerGatherer <- RCurl::basicHeaderGatherer()
+ options$headerfunction <- headerGatherer$update
+ textGatherer <- RCurl::basicTextGatherer()
+ options$writefunction <- textGatherer$update
+
+ # add extra headers
+ extraHeaders <- as.character(headers)
+ names(extraHeaders) <- names(headers)
+ options$httpheader <- extraHeaders
+
+ # post the form
+ RCurl::postForm(paste("https://api.rpubs.com", path, sep=""),
+ .params = params,
+ .opts = options,
+ useragent = "RStudio")
+
+ # return list
+ headers <- headerGatherer$value()
+ if ("Location" %in% names(headers))
+ location <- headers[["Location"]]
+ else
+ location <- NULL
+ list(status = as.integer(headers[["status"]]),
+ location = location,
+ contentType <- headers[["Content-Type"]],
+ content = textGatherer$value())
+
+ }
+
+ curlUpload <- function(path,
+ contentType,
+ headers,
+ packageFile) {
+
+ fileLength <- file.info(packageFile)$size
+
+ extraHeaders <- character()
+ for (header in names(headers))
+ {
+ extraHeaders <- paste(extraHeaders, "--header")
+ extraHeaders <- paste(extraHeaders,
+ paste(header,":",headers[[header]], sep=""))
+ }
+
+ outputFile <- tempfile()
+
+ command <- paste("curl",
+ "-X",
+ "POST",
+ "--data-binary",
+ shQuote(paste("@", packageFile, sep="")),
+ "-i",
+ "--header", paste("Content-Type:",contentType, sep=""),
+ "--header", paste("Content-Length:", fileLength, sep=""),
+ extraHeaders,
+ "--header", "Expect:",
+ "--silent",
+ "--show-error",
+ "-o", shQuote(outputFile),
+ paste("https://api.rpubs.com", path, sep=""))
+
+ result <- system(command)
+
+ if (result == 0) {
+ fileConn <- file(outputFile, "rb")
+ on.exit(close(fileConn))
+ readResponse(fileConn)
+ } else {
+ stop(paste("Upload failed (curl error", result, "occurred)"))
+ }
+ }
+
+ uploadFunction <- NULL
+ if (is.function(method)) {
+ uploadFunction <- method
+ } else if (identical("auto", method)) {
+ if (nzchar(Sys.which("curl")))
+ uploadFunction <- curlUpload
+ else if (suppressWarnings(require("RCurl", quietly=TRUE)))
+ uploadFunction <- rcurlUpload
+ else
+ uploadFunction <- internalUpload
+ } else if (identical("internal", method)) {
+ uploadFunction <- internalUpload
+ } else if (identical("curl", method)) {
+ uploadFunction <- curlUpload
+ } else if (identical("rcurl", method)) {
+ uploadFunction <- rcurlUpload
+ } else {
+ stop(paste("Invalid upload method specified:",method))
+ }
+
+ # build the package
+ packageFile <- buildPackage(title, htmlFile, properties)
+
+ # determine whether this is a new doc or an update
+ isUpdate <- FALSE
+ path <- "/api/v1/document"
+ headers <- list()
+ headers$Connection <- "close"
+ if (!is.null(id)) {
+ isUpdate <- TRUE
+ path <- pathFromId(id)
+ headers$`X-HTTP-Method-Override` <- "PUT"
+ }
+
+
+ # send the request
+ result <- uploadFunction(path,
+ "application/x-compressed",
+ headers,
+ packageFile)
+
+ # check for success
+ succeeded <- FALSE
+ if (isUpdate && (result$status == 200))
+ succeeded <- TRUE
+ else if (result$status == 201)
+ succeeded <- TRUE
+
+ # mark content as UTF-8
+ content <- result$content
+ Encoding(content) <- "UTF-8"
+
+ # return either id & continueUrl or error
+ if (succeeded) {
+ return (list(id = ifelse(isUpdate, id, result$location),
+ continueUrl = parseContinueUrl(content)))
+ }
+ else {
+ return (list(error = content))
+ }
+}
+
diff --git a/src/cpp/session/modules/SessionRPubs.cpp b/src/cpp/session/modules/SessionRPubs.cpp
new file mode 100644
index 0000000..da23173
--- /dev/null
+++ b/src/cpp/session/modules/SessionRPubs.cpp
@@ -0,0 +1,432 @@
+/*
+ * SessionRPubs.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionRPubs.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/utility.hpp>
+#include <boost/format.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/Log.hpp>
+#include <core/Settings.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/text/CsvParser.hpp>
+#include <core/http/Util.hpp>
+#include <core/system/Process.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace rpubs {
+
+namespace {
+
+// we get a fresh settings object for each read or write so multiple
+// processes can all read and write without cache issues
+void getUploadIdSettings(Settings* pSettings)
+{
+ FilePath rpubsUploadIds =
+ module_context::scopedScratchPath().complete("rpubs_upload_ids");
+ Error error = pSettings->initialize(rpubsUploadIds);
+ if (error)
+ LOG_ERROR(error);
+}
+
+std::string pathIdentifier(const FilePath& filePath)
+{
+ // use a relative path if we are in a project
+ std::string path;
+ projects::ProjectContext& projectContext = projects::projectContext();
+ if (projectContext.hasProject() &&
+ filePath.isWithin(projectContext.directory()))
+ {
+ path = filePath.relativePath(projectContext.directory());
+ }
+ else
+ {
+ path = filePath.absolutePath();
+ }
+
+ // urlencode so we can use it as a key
+ return http::util::urlEncode(path);
+}
+
+void saveUploadId(const FilePath& filePath, const std::string& uploadId)
+{
+ Settings settings;
+ getUploadIdSettings(&settings);
+ settings.set(pathIdentifier(filePath), uploadId);
+}
+
+
+class RPubsUpload : boost::noncopyable,
+ public boost::enable_shared_from_this<RPubsUpload>
+{
+public:
+ static boost::shared_ptr<RPubsUpload> create(const std::string& contextId,
+ const std::string& title,
+ const FilePath& htmlFile,
+ bool allowUpdate)
+ {
+ boost::shared_ptr<RPubsUpload> pUpload(new RPubsUpload(contextId));
+ pUpload->start(title, htmlFile, allowUpdate);
+ return pUpload;
+ }
+
+ virtual ~RPubsUpload()
+ {
+ }
+
+ bool isRunning() const { return isRunning_; }
+
+ void terminate()
+ {
+ terminationRequested_ = true;
+ }
+
+private:
+ explicit RPubsUpload(const std::string& contextId)
+ : contextId_(contextId), terminationRequested_(false), isRunning_(false)
+ {
+ }
+
+ void start(const std::string& title, const FilePath& htmlFile, bool allowUpdate)
+ {
+ using namespace core::string_utils;
+ using namespace module_context;
+
+ htmlFile_ = htmlFile;
+ csvOutputFile_ = module_context::tempFile("rpubsupload", "csv");
+ isRunning_ = true;
+
+ // see if we already know of an upload id for this file
+ std::string id = allowUpdate ? previousUploadId(htmlFile_) : std::string();
+
+ // R binary
+ FilePath rProgramPath;
+ Error error = rScriptPath(&rProgramPath);
+ if (error)
+ {
+ terminateWithError(error);
+ return;
+ }
+
+ // args
+ std::vector<std::string> args;
+ args.push_back("--slave");
+ args.push_back("--no-save");
+ args.push_back("--no-restore");
+ args.push_back("-e");
+
+ boost::format fmt(
+ "source('%1%'); "
+ "result <- rpubsUpload('%2%', '%3%', %4%); "
+ "utils::write.csv(as.data.frame(result), "
+ " file='%5%', "
+ " row.names=FALSE);");
+
+ FilePath modulesPath = session::options().modulesRSourcePath();;
+ std::string scriptPath = utf8ToSystem(
+ modulesPath.complete("SessionRPubs.R").absolutePath());
+ std::string htmlPath = utf8ToSystem(htmlFile.absolutePath());
+ std::string outputPath = utf8ToSystem(csvOutputFile_.absolutePath());
+
+ std::string escapedScriptPath = string_utils::jsLiteralEscape(scriptPath);
+ std::string escapedTitle = string_utils::jsLiteralEscape(title);
+ std::string escapedHtmlPath = string_utils::jsLiteralEscape(htmlPath);
+ std::string escapedId = string_utils::jsLiteralEscape(id);
+ std::string escapedOutputPath = string_utils::jsLiteralEscape(outputPath);
+
+ std::string cmd = boost::str(fmt %
+ escapedScriptPath % escapedTitle % escapedHtmlPath %
+ (!escapedId.empty() ? "'" + escapedId + "'" : "NULL") %
+ escapedOutputPath);
+ args.push_back(cmd);
+
+ // options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+ options.workingDir = htmlFile.parent();
+
+ // callbacks
+ core::system::ProcessCallbacks cb;
+ cb.onContinue = boost::bind(&RPubsUpload::onContinue,
+ RPubsUpload::shared_from_this());
+ cb.onStdout = boost::bind(&RPubsUpload::onStdOut,
+ RPubsUpload::shared_from_this(), _2);
+ cb.onStderr = boost::bind(&RPubsUpload::onStdErr,
+ RPubsUpload::shared_from_this(), _2);
+ cb.onExit = boost::bind(&RPubsUpload::onCompleted,
+ RPubsUpload::shared_from_this(), _1);
+
+ // execute
+ processSupervisor().runProgram(rProgramPath.absolutePath(),
+ args,
+ options,
+ cb);
+ }
+
+ bool onContinue()
+ {
+ return !terminationRequested_;
+ }
+
+ void onStdOut(const std::string& output)
+ {
+ output_.append(output);
+ }
+
+ void onStdErr(const std::string& error)
+ {
+ error_.append(error);
+ }
+
+ void onCompleted(int exitStatus)
+ {
+ if (exitStatus == EXIT_SUCCESS)
+ {
+ if(csvOutputFile_.exists())
+ {
+ std::string csvOutput;
+ Error error = core::readStringFromFile(
+ csvOutputFile_,
+ &csvOutput,
+ string_utils::LineEndingPosix);
+ if (error)
+ {
+ terminateWithError(error);
+ }
+ else
+ {
+ // parse output
+ Result result = parseOutput(csvOutput);
+ if (!result.empty())
+ terminateWithResult(result);
+ else
+ terminateWithError(
+ "Unexpected output from upload: " + csvOutput);
+ }
+ }
+ else
+ {
+ terminateWithError("Unexpected output from upload: " + output_);
+ }
+ }
+ else
+ {
+ terminateWithError(error_);
+ }
+ }
+
+ void terminateWithError(const Error& error)
+ {
+ terminateWithError(error.summary());
+ }
+
+ void terminateWithError(const std::string& error)
+ {
+ terminateWithResult(Result(error));
+ }
+
+ struct Result
+ {
+ Result()
+ {
+ }
+
+ Result(const std::string& error)
+ : error(error)
+ {
+ }
+
+ Result(const std::string& id, const std::string& continueUrl)
+ : id(id), continueUrl(continueUrl)
+ {
+ }
+
+ bool empty() const { return id.empty() && error.empty(); }
+
+ std::string id;
+ std::string continueUrl;
+ std::string error;
+ };
+
+ void terminateWithResult(const Result& result)
+ {
+ isRunning_ = false;
+
+ if (!result.id.empty())
+ saveUploadId(htmlFile_, result.id);
+
+ json::Object statusJson;
+ statusJson["contextId"] = contextId_;
+ statusJson["id"] = result.id;
+ statusJson["continueUrl"] = result.continueUrl;
+ statusJson["error"] = result.error;
+ ClientEvent event(client_events::kRPubsUploadStatus, statusJson);
+ module_context::enqueClientEvent(event);
+ }
+
+ Result parseOutput(const std::string& output)
+ {
+ std::pair<std::vector<std::string>, std::string::const_iterator>
+ line = text::parseCsvLine(output.begin(), output.end());
+ if (!line.first.empty())
+ {
+ std::vector<std::string> headers = line.first;
+
+ line = text::parseCsvLine(line.second, output.end());
+ if (!line.first.empty())
+ {
+ std::vector<std::string> data = line.first;
+
+ if (headers.size() == 1 &&
+ data.size() == 1 &&
+ headers[0] == "error")
+ {
+ return Result(data[0]);
+ }
+ else if (headers.size() == 2 &&
+ data.size() == 2 &&
+ headers[0] == "id" &&
+ headers[1] == "continueUrl")
+ {
+ return Result(data[0], data[1]);
+ }
+ }
+ }
+
+ return Result();
+ }
+
+private:
+ std::string contextId_;
+ FilePath htmlFile_;
+ bool terminationRequested_;
+ bool isRunning_;
+ std::string output_;
+ std::string error_;
+ FilePath csvOutputFile_;
+};
+
+std::map<std::string, boost::shared_ptr<RPubsUpload> > s_pCurrentUploads;
+
+bool isUploadRunning(const std::string& contextId)
+{
+ boost::shared_ptr<RPubsUpload> pCurrentUpload = s_pCurrentUploads[contextId];
+ return pCurrentUpload && pCurrentUpload->isRunning();
+}
+
+Error rpubsIsPublished(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string htmlFile;
+ Error error = json::readParams(request.params, &htmlFile);
+ if (error)
+ return error;
+
+ FilePath filePath = module_context::resolveAliasedPath(htmlFile);
+
+ pResponse->setResult(!previousUploadId(filePath).empty());
+
+ return Success();
+}
+
+Error rpubsUpload(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string contextId, title, htmlFile;
+ bool isUpdate;
+ Error error = json::readParams(request.params,
+ &contextId,
+ &title,
+ &htmlFile,
+ &isUpdate);
+ if (error)
+ return error;
+
+ if (isUploadRunning(contextId))
+ {
+ pResponse->setResult(false);
+ }
+ else
+ {
+ FilePath filePath = module_context::resolveAliasedPath(htmlFile);
+ s_pCurrentUploads[contextId] = RPubsUpload::create(contextId,
+ title,
+ filePath,
+ isUpdate);
+ pResponse->setResult(true);
+ }
+
+ return Success();
+}
+
+Error terminateRpubsUpload(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string contextId;
+ Error error = json::readParam(request.params, 0, &contextId);
+ if (error)
+ return error;
+
+
+ if (isUploadRunning(contextId))
+ s_pCurrentUploads[contextId]->terminate();
+
+ return Success();
+}
+
+} // anonymous namespace
+
+std::string previousUploadId(const FilePath& filePath)
+{
+ Settings settings;
+ getUploadIdSettings(&settings);
+ return settings.get(pathIdentifier(filePath));
+}
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "rpubs_is_published", rpubsIsPublished))
+ (bind(registerRpcMethod, "rpubs_upload", rpubsUpload))
+ (bind(registerRpcMethod, "terminate_rpubs_upload", terminateRpubsUpload))
+ ;
+ return initBlock.execute();
+}
+
+
+} // namespace rpubs
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionRPubs.hpp b/src/cpp/session/modules/SessionRPubs.hpp
new file mode 100644
index 0000000..5c31227
--- /dev/null
+++ b/src/cpp/session/modules/SessionRPubs.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionRPubs.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_RPUBS_HPP
+#define SESSION_SESSION_RPUBS_HPP
+
+#include <string>
+#include <core/FilePath.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace rpubs {
+
+core::Error initialize();
+std::string previousUploadId(const core::FilePath& filePath);
+
+} // namespace rpubs
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_RPUBS_HPP
diff --git a/src/cpp/session/modules/SessionSVN.cpp b/src/cpp/session/modules/SessionSVN.cpp
new file mode 100644
index 0000000..90278fc
--- /dev/null
+++ b/src/cpp/session/modules/SessionSVN.cpp
@@ -0,0 +1,1849 @@
+/*
+ * SessionSVN.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSVN.hpp"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time.hpp>
+#include <boost/regex.hpp>
+#include <core/BoostLamda.hpp>
+
+#include <core/FileSerializer.hpp>
+#include <core/rapidxml/rapidxml.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/Exec.hpp>
+#include <core/http/Header.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionOptions.hpp>
+#include <session/SessionUserSettings.hpp>
+
+#include <r/RExec.hpp>
+
+#include "SessionVCS.hpp"
+#include "vcs/SessionVCSUtils.hpp"
+#include "SessionConsoleProcess.hpp"
+#include "SessionAskPass.hpp"
+#include "SessionWorkbench.hpp"
+#include "SessionGit.hpp"
+
+using namespace core;
+using namespace core::shell_utils;
+using namespace session::modules::vcs_utils;
+using namespace session::modules::console_process;
+
+namespace session {
+namespace modules {
+namespace svn {
+
+const char * const kVcsId = "SVN";
+
+namespace {
+
+// password manager for caching svn+ssh credentials
+boost::scoped_ptr<PasswordManager> s_pPasswordManager;
+
+// svn exe which we detect at startup. note that if the svn exe
+// is already in the path then this will be empty
+std::string s_svnExePath;
+
+// is the current repository svn+ssh
+bool s_isSvnSshRepository = false;
+
+
+
+/** GLOBAL STATE **/
+FilePath s_workingDir;
+
+FilePath resolveAliasedPath(const std::string& path)
+{
+ if (boost::algorithm::starts_with(path, "~/"))
+ return module_context::resolveAliasedPath(path);
+ else
+ return s_workingDir.childPath(path);
+}
+
+
+std::vector<FilePath> resolveAliasedPaths(const json::Array& paths,
+ bool includeRenameOld = false,
+ bool includeRenameNew = true)
+{
+ std::vector<FilePath> results;
+ for (json::Array::const_iterator it = paths.begin();
+ it != paths.end();
+ it++)
+ {
+ results.push_back(resolveAliasedPath(it->get_str()));
+ }
+ return results;
+}
+
+core::system::ProcessOptions procOptions(bool requiresSsh)
+{
+ core::system::ProcessOptions options;
+
+ // detach the session so there is no terminal
+#ifndef _WIN32
+ options.detachSession = true;
+#endif
+
+ // get current environment for modification prior to passing to child
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+
+ // add postback directory to PATH
+ FilePath postbackDir = session::options().rpostbackPath().parent();
+ core::system::addToPath(&childEnv, postbackDir.absolutePath());
+
+ // on windows add gnudiff directory to the path
+#ifdef _WIN32
+ core::system::addToPath(&childEnv,
+ session::options().gnudiffPath().absolutePath());
+#endif
+
+ // on windows add msys_ssh to the path if we need ssh
+#ifdef _WIN32
+ if (requiresSsh)
+ {
+ core::system::addToPath(&childEnv,
+ session::options().msysSshPath().absolutePath());
+ }
+#endif
+
+ if (!s_workingDir.empty())
+ options.workingDir = s_workingDir;
+ else
+ options.workingDir = projects::projectContext().directory();
+
+ // on windows set HOME to USERPROFILE
+#ifdef _WIN32
+ std::string userProfile = core::system::getenv(childEnv, "USERPROFILE");
+ core::system::setenv(&childEnv, "HOME", userProfile);
+#endif
+
+ // set the SVN_EDITOR if it is available
+ std::string editFileCommand = workbench::editFileCommand();
+ if (!editFileCommand.empty())
+ core::system::setenv(&childEnv, "SVN_EDITOR", editFileCommand);
+
+ // set custom environment
+ options.environment = childEnv;
+
+ return options;
+}
+
+core::system::ProcessOptions procOptions()
+{
+ return procOptions(s_isSvnSshRepository);
+}
+
+void maybeAttachPasswordManager(boost::shared_ptr<ConsoleProcess> pCP)
+{
+ if (s_isSvnSshRepository)
+ s_pPasswordManager->attach(pCP);
+}
+
+ShellCommand svn()
+{
+ FilePath exePath(s_svnExePath);
+ return ShellCommand(exePath);
+}
+
+Error runSvn(const ShellArgs& args,
+ const FilePath& workingDir,
+ bool redirectStdErrToStdOut,
+ core::system::ProcessResult* pResult)
+{
+ core::system::ProcessOptions options = procOptions();
+ if (!workingDir.empty())
+ options.workingDir = workingDir;
+ options.redirectStdErrToStdOut = redirectStdErrToStdOut;
+ Error error = core::system::runCommand(svn() << args.args(),
+ options,
+ pResult);
+ return error;
+}
+
+Error runSvn(const ShellArgs& args,
+ bool redirectStdErrToStdOut,
+ core::system::ProcessResult* pResult)
+{
+ FilePath workingDir;
+ if (!s_workingDir.empty())
+ workingDir = s_workingDir;
+
+ return runSvn(args, workingDir, redirectStdErrToStdOut, pResult);
+}
+
+Error runSvn(const ShellArgs& args,
+ std::string* pStdOut=NULL,
+ std::string* pStdErr=NULL,
+ int* pExitCode=NULL)
+{
+ core::system::ProcessResult result;
+ Error error = runSvn(args, false, &result);
+ if (error)
+ return error;
+
+ if (pStdOut)
+ *pStdOut = result.stdOut;
+ if (pStdErr)
+ *pStdErr = result.stdErr;
+ if (pExitCode)
+ *pExitCode = result.exitStatus;
+
+ return Success();
+}
+
+std::vector<std::string> globalArgs()
+{
+ std::vector<std::string> args;
+ return args;
+}
+
+
+core::Error createConsoleProc(const ShellArgs& args,
+ const FilePath& outputFile,
+ const boost::optional<FilePath>& workingDir,
+ const std::string& caption,
+ bool requiresSsh,
+ bool dialog,
+ bool enqueueRefreshOnExit,
+ boost::shared_ptr<ConsoleProcess>* ppCP)
+{
+ core::system::ProcessOptions options = procOptions(requiresSsh);
+ if (!workingDir)
+ options.workingDir = s_workingDir;
+ else if (!workingDir.get().empty())
+ options.workingDir = workingDir.get();
+
+ // NOTE: we use runCommand style process creation on both windows and posix
+ // so that we can redirect standard output to a file -- this works on
+ // windows because we are not specifying options.detachProcess (not
+ // necessary because ConsoleProcess specifies options.createNewConsole
+ // which overrides options.detachProcess)
+
+ // build command
+ std::string command = svn() << args.args();
+
+ // redirect stdout to a file
+ if (!outputFile.empty())
+ options.stdOutFile = outputFile;
+
+ // create the process
+ *ppCP = ConsoleProcess::create(command,
+ options,
+ caption,
+ dialog,
+ console_process::InteractionPossible,
+ console_process::kDefaultMaxOutputLines);
+
+ if (enqueueRefreshOnExit)
+ (*ppCP)->onExit().connect(boost::bind(&enqueueRefreshEvent));
+
+ return Success();
+}
+
+core::Error createConsoleProc(const ShellArgs& args,
+ const std::string& caption,
+ bool requiresSsh,
+ bool dialog,
+ bool enqueueRefreshOnExit,
+ boost::shared_ptr<ConsoleProcess>* ppCP)
+{
+ return createConsoleProc(args,
+ FilePath(),
+ boost::optional<FilePath>(),
+ caption,
+ requiresSsh,
+ dialog,
+ enqueueRefreshOnExit,
+ ppCP);
+}
+
+typedef boost::function<void(const core::Error&,
+ const core::system::ProcessResult&)>
+ ProcResultCallback;
+
+void onAsyncSvnExit(int exitCode,
+ const FilePath& outputFile,
+ ProcResultCallback completionCallback)
+{
+ if (exitCode == EXIT_SUCCESS)
+ {
+ // read the file
+ std::string contents;
+ Error error = core::readStringFromFile(outputFile, &contents);
+ if (error)
+ {
+ completionCallback(error, core::system::ProcessResult());
+ return;
+ }
+
+ core::system::ProcessResult result;
+ result.exitStatus = exitCode;
+ result.stdOut = contents;
+ completionCallback(Success(), result);
+ }
+ else
+ {
+ completionCallback(
+ systemError(boost::system::errc::operation_canceled,
+ ERROR_LOCATION),
+ core::system::ProcessResult());
+ }
+}
+
+void runSvnAsync(const ShellArgs& args,
+ const std::string& caption,
+ bool enqueueRefreshOnExit,
+ ProcResultCallback completionCallback)
+{
+ // allocate a temporary file for holding the output
+ FilePath outputFile = module_context::tempFile("svn", "out");
+
+ // create a console process so that we can either do terminal based
+ // auth prompting or do PasswordManager based prompting if necessary
+ boost::shared_ptr<ConsoleProcess> pCP;
+ Error error = createConsoleProc(args,
+ outputFile,
+ boost::optional<FilePath>(),
+ caption,
+ s_isSvnSshRepository,
+ true,
+ enqueueRefreshOnExit,
+ &pCP);
+ if (error)
+ completionCallback(error, core::system::ProcessResult());
+
+ // set showOnOutput
+ pCP->setShowOnOutput(true);
+
+ // attach a password manager if this is svn+ssh
+ maybeAttachPasswordManager(pCP);
+
+ // add an exitHandler for returning the file contents to the completion
+ pCP->onExit().connect(
+ boost::bind(onAsyncSvnExit, _1, outputFile, completionCallback));
+
+ // notify the client about the console process
+ json::Object data;
+ data["process_info"] = pCP->toJson();
+ data["target_window"] = ask_pass::activeWindow();
+ ClientEvent event(client_events::kConsoleProcessCreated, data);
+ module_context::enqueClientEvent(event);
+}
+
+#ifdef _WIN32
+bool detectSvnExeOnPath(FilePath* pPath)
+{
+ std::vector<wchar_t> path(MAX_PATH+2);
+ wcscpy(&(path[0]), L"svn.exe");
+ if (::PathFindOnPathW(&(path[0]), NULL))
+ {
+ *pPath = FilePath(&(path[0]));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+#endif
+
+FilePath whichSvnExe()
+{
+ std::string whichSvn;
+ Error error = r::exec::RFunction("Sys.which", "svn").call(&whichSvn);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+ else
+ {
+ return FilePath(whichSvn);
+ }
+}
+
+void initSvnBin()
+{
+ // get the svn exe from user settings if it is there
+ if (session::options().allowVcsExecutableEdit())
+ s_svnExePath = userSettings().svnExePath().absolutePath();
+
+ // if it wasn't provided in settings try to detect it
+ if (s_svnExePath.empty())
+ s_svnExePath = svn::detectedSvnExePath().absolutePath();
+}
+
+Error parseXml(const std::string strData,
+ std::vector<char>* pDataBuffer,
+ rapidxml::xml_document<>* pDoc)
+{
+ pDataBuffer->reserve(strData.size() + 1);
+ std::copy(strData.begin(),
+ strData.end(),
+ std::back_inserter(*pDataBuffer));
+ pDataBuffer->push_back('\0'); // null terminator
+
+ try
+ {
+ pDoc->parse<0>(&((*pDataBuffer)[0]));
+ return Success();
+ }
+ catch (rapidxml::parse_error)
+ {
+ return systemError(boost::system::errc::protocol_error,
+ "Could not parse XML",
+ ERROR_LOCATION);
+ }
+}
+
+
+} // namespace
+
+
+bool isSvnInstalled()
+{
+ // special check on osx mavericks to make sure we don't run the fake svn
+ if (module_context::isOSXMavericks() &&
+ !module_context::hasOSXMavericksDeveloperTools() &&
+ whichSvnExe().empty())
+ {
+ return false;
+ }
+
+ int exitCode;
+ Error error = runSvn(ShellArgs() << "help", NULL, NULL, &exitCode);
+
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ return exitCode == EXIT_SUCCESS;
+}
+
+struct SvnInfo
+{
+ bool empty() const { return repositoryRoot.empty(); }
+
+ std::string repositoryRoot;
+};
+
+
+Error runSvnInfo(const core::FilePath& workingDir, SvnInfo* pSvnInfo)
+{
+ if (workingDir.empty())
+ return Success();
+
+ core::system::ProcessResult result;
+ Error error = runSvn(ShellArgs() << "info" << "--xml",
+ workingDir,
+ true,
+ &result);
+ if (error)
+ return error;
+
+ if (result.exitStatus == EXIT_SUCCESS)
+ {
+ // parse the xml
+ std::vector<char> xmlData;
+ using namespace rapidxml;
+ xml_document<> doc;
+ Error error = parseXml(result.stdOut, &xmlData, &doc);
+ if (error)
+ return error;
+
+ // traverse to repository root
+ xml_node<>* pInfo = doc.first_node("info");
+ if (!pInfo)
+ return systemError(boost::system::errc::invalid_seek, ERROR_LOCATION);
+ xml_node<>* pEntry = pInfo->first_node("entry");
+ if (!pEntry)
+ return systemError(boost::system::errc::invalid_seek, ERROR_LOCATION);
+ xml_node<>* pRepository = pEntry->first_node("repository");
+ if (!pRepository)
+ return systemError(boost::system::errc::invalid_seek, ERROR_LOCATION);
+ xml_node<>* pRoot = pRepository->first_node("root");
+ if (!pRoot)
+ return systemError(boost::system::errc::invalid_seek, ERROR_LOCATION);
+
+ // get the value
+ pSvnInfo->repositoryRoot = pRoot->value();
+ }
+
+ return Success();
+}
+
+bool isSvnDirectory(const core::FilePath& workingDir)
+{
+ return !repositoryRoot(workingDir).empty();
+}
+
+std::string repositoryRoot(const FilePath& workingDir)
+{
+ SvnInfo svnInfo;
+ Error error = runSvnInfo(workingDir, &svnInfo);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+
+ return svnInfo.repositoryRoot;
+}
+
+bool isSvnEnabled()
+{
+ return !s_workingDir.empty();
+}
+
+FilePath detectedSvnExePath()
+{
+#ifdef _WIN32
+ FilePath path;
+ if (detectSvnExeOnPath(&path))
+ {
+ return path;
+ }
+ else
+ {
+ return FilePath();
+ }
+#else
+ FilePath svnExeFilePath = whichSvnExe();
+ if (!svnExeFilePath.empty())
+ {
+ // extra check on mavericks to make sure it's not the fake svn
+ if (module_context::isOSXMavericks())
+ {
+ if (module_context::hasOSXMavericksDeveloperTools())
+ return FilePath(svnExeFilePath);
+ else
+ return FilePath();
+ }
+ else
+ {
+ return FilePath(svnExeFilePath);
+ }
+ }
+ else
+ return FilePath();
+#endif
+}
+
+std::string nonPathSvnBinDir()
+{
+ if (s_svnExePath != svn::detectedSvnExePath().absolutePath())
+ return FilePath(s_svnExePath).parent().absolutePath();
+ else
+ return std::string();
+}
+
+void onUserSettingsChanged()
+{
+ initSvnBin();
+}
+
+std::string translateItemStatus(const std::string& status)
+{
+ if (status == "added")
+ return "A";
+ if (status == "conflicted")
+ return "C";
+ if (status == "deleted")
+ return "D";
+ if (status == "external")
+ return "X";
+ if (status == "ignored")
+ return "I";
+ if (status == "incomplete")
+ return "!";
+ if (status == "merged") // ??
+ return "G";
+ if (status == "missing")
+ return "!";
+ if (status == "modified")
+ return "M";
+ if (status == "none")
+ return " ";
+ if (status == "normal") // ??
+ return " ";
+ if (status == "obstructed")
+ return "~";
+ if (status == "replaced")
+ return "~";
+ if (status == "unversioned")
+ return "?";
+
+ return " ";
+}
+
+int rankItemStatus(const std::string& status)
+{
+ if (status == " " || status.empty())
+ return 10;
+
+ if (status == "I")
+ return 7;
+
+ if (status == "M")
+ return 1;
+
+ if (status == "C")
+ return 0;
+
+ return 5;
+}
+
+std::string topStatus(const std::string& a, const std::string& b)
+{
+ if (rankItemStatus(a) <= rankItemStatus(b))
+ return a;
+ return b;
+}
+
+#define FOREACH_NODE(parent, varname, name) \
+ for (rapidxml::xml_node<>* varname = parent->first_node(name); \
+ parent && varname; \
+ varname = varname->next_sibling(name))
+
+std::string attr_value(rapidxml::xml_node<>* pNode, const std::string& attrName)
+{
+ if (!pNode)
+ return std::string();
+ rapidxml::xml_attribute<>* pAttr = pNode->first_attribute(attrName.c_str());
+ if (!pAttr)
+ return std::string();
+ return std::string(pAttr->value());
+}
+
+std::string node_value(rapidxml::xml_node<>* pNode, const std::string& nodeName)
+{
+ using namespace rapidxml;
+
+ xml_node<>* pChild = pNode->first_node(nodeName.c_str());
+ if (!pChild)
+ return std::string();
+
+ return pChild->value();
+}
+
+FilePath resolveAliasedJsonPath(const json::Value& value)
+{
+ std::string path = value.get_str();
+ if (boost::algorithm::starts_with(path, "~/"))
+ return module_context::resolveAliasedPath(path);
+ else
+ return s_workingDir.childPath(path);
+}
+
+Error svnAdd(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array files;
+ Error error = json::readParams(request.params, &files);
+ if (error)
+ return error;
+
+ std::vector<FilePath> paths;
+ std::transform(files.begin(), files.end(), std::back_inserter(paths),
+ &resolveAliasedJsonPath);
+
+ core::system::ProcessResult result;
+ error = runSvn(ShellArgs() << "add" << globalArgs() << "-q" << "--" << paths,
+ true, &result);
+ if (error)
+ return error;
+
+ pResponse->setResult(processResultToJson(result));
+
+ return Success();
+}
+
+Error svnDelete(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array files;
+ Error error = json::readParams(request.params, &files);
+ if (error)
+ return error;
+
+ std::vector<FilePath> paths;
+ std::transform(files.begin(), files.end(), std::back_inserter(paths),
+ &resolveAliasedJsonPath);
+
+ core::system::ProcessResult result;
+ error = runSvn(ShellArgs() << "delete" << globalArgs() << "-q" << "--" << paths,
+ true, &result);
+ if (error)
+ return error;
+
+ pResponse->setResult(processResultToJson(result));
+
+ return Success();
+}
+
+Error svnRevert(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ json::Array files;
+ Error error = json::readParams(request.params, &files);
+ if (error)
+ return error;
+
+ std::vector<FilePath> paths;
+ std::transform(files.begin(), files.end(), std::back_inserter(paths),
+ &resolveAliasedJsonPath);
+
+ core::system::ProcessResult result;
+ error = runSvn(ShellArgs() << "revert" << globalArgs() << "-q" <<
+ "--depth" << "infinity" <<
+ "--" << paths,
+ true, &result);
+ if (error)
+ return error;
+
+ pResponse->setResult(processResultToJson(result));
+
+ return Success();
+}
+
+Error svnResolve(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+ {
+ RefreshOnExit refreshOnExit;
+
+ std::string accept;
+ json::Array files;
+ Error error = json::readParams(request.params, &accept, &files);
+ if (error)
+ return error;
+
+ std::vector<FilePath> paths;
+ std::transform(files.begin(), files.end(), std::back_inserter(paths),
+ &resolveAliasedJsonPath);
+
+ core::system::ProcessResult result;
+ error = runSvn(ShellArgs() << "resolve" << globalArgs() << "-q" <<
+ "--accept" << accept <<
+ "--" << paths,
+ true, &result);
+ if (error)
+ return error;
+
+ pResponse->setResult(processResultToJson(result));
+
+ return Success();
+ }
+
+Error statusToJson(const core::FilePath &path,
+ const source_control::VCSStatus &status,
+ core::json::Object *pObject)
+{
+ json::Object& obj = *pObject;
+ obj["status"] = status.status();
+ obj["path"] = path.relativePath(s_workingDir);
+ obj["raw_path"] = module_context::createAliasedPath(path);
+ obj["is_directory"] = path.isDirectory();
+ if (!status.changelist().empty())
+ obj["changelist"] = status.changelist();
+ return Success();
+}
+
+Error status(const FilePath& filePath,
+ std::vector<source_control::FileWithStatus>* pFiles)
+{
+ using namespace source_control;
+
+ ShellArgs args;
+ args << "status" << globalArgs() << "--xml" << "--ignore-externals";
+ if (!filePath.empty())
+ args << "--" << filePath;
+
+ std::string stdOut, stdErr;
+ int exitCode;
+ Error error = runSvn(
+ args,
+ &stdOut,
+ &stdErr,
+ &exitCode);
+ if (error)
+ return error;
+
+ if (exitCode != EXIT_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE(stdErr);
+ return Success();
+ }
+
+ std::vector<char> xmlData;
+ using namespace rapidxml;
+ xml_document<> doc;
+ error = parseXml(stdOut, &xmlData, &doc);
+ if (error)
+ return error;
+
+ const std::string CHANGELIST_NAME("changelist");
+
+ json::Array results;
+
+ xml_node<>* pStatus = doc.first_node("status");
+ if (pStatus)
+ {
+ FOREACH_NODE(pStatus, pList,)
+ {
+ std::string changelist;
+ if (pList->name() == CHANGELIST_NAME)
+ {
+ changelist = attr_value(pList, "name");
+ }
+
+ FOREACH_NODE(pList, pEntry, "entry")
+ {
+ std::string path = attr_value(pEntry, "path");
+ if (path.empty())
+ {
+ LOG_ERROR_MESSAGE("Path attribute not found");
+ continue;
+ }
+
+ xml_node<>* pStatus = pEntry->first_node("wc-status");
+ if (!pStatus)
+ {
+ LOG_ERROR_MESSAGE("Status node not found");
+ continue;
+ }
+
+ std::string item = attr_value(pStatus, "item");
+ if (item.empty())
+ {
+ LOG_ERROR_MESSAGE("Item attribute not found");
+ continue;
+ }
+ item = translateItemStatus(item);
+
+ std::string props = attr_value(pStatus, "props");
+ if (props.empty())
+ {
+ LOG_ERROR_MESSAGE("Item properties not found");
+ continue;
+ }
+ props = translateItemStatus(props);
+
+ std::string treeConf = attr_value(pStatus, "tree-conflicted");
+ if (treeConf == "true")
+ item = topStatus(item, "C");
+
+ std::string status = topStatus(item, props);
+
+ if (status.empty() || status == " ")
+ continue;
+
+ VCSStatus vcsStatus(status);
+ vcsStatus.changelist() = changelist;
+ FileWithStatus fileWithStatus;
+ fileWithStatus.status = status;
+ fileWithStatus.path = s_workingDir.complete(path);
+
+ pFiles->push_back(fileWithStatus);
+ }
+ }
+ }
+
+ return Success();
+}
+
+Error status(const FilePath& filePath,
+ json::Array* pResults)
+{
+ std::vector<source_control::FileWithStatus> files;
+ Error error = status(filePath, &files);
+ if (error)
+ return error;
+
+ BOOST_FOREACH(source_control::FileWithStatus file, files)
+ {
+ json::Object fileObj;
+ error = statusToJson(file.path, file.status, &fileObj);
+ if (error)
+ return error;
+ pResults->push_back(fileObj);
+ }
+ return Success();
+}
+
+Error svnStatus(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array results;
+ Error error = status(FilePath(), &results);
+ if (error)
+ return error;
+
+ pResponse->setResult(results);
+
+ return Success();
+}
+
+Error svnUpdate(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ boost::shared_ptr<ConsoleProcess> pCP;
+ Error error = createConsoleProc(ShellArgs() << "update" << globalArgs(),
+ "SVN Update",
+ s_isSvnSshRepository,
+ true,
+ true,
+ &pCP);
+ if (error)
+ return error;
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ maybeAttachPasswordManager(pCP);
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error svnCleanup(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ core::system::ProcessResult result;
+ Error error = runSvn(ShellArgs() << "cleanup" << globalArgs(),
+ true,
+ &result);
+ if (error)
+ return error;
+
+ pResponse->setResult(processResultToJson(result));
+
+ return Success();
+}
+
+
+Error svnCommit(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array paths;
+ std::string message;
+
+ Error error = json::readParams(request.params, &paths, &message);
+ if (error)
+ return error;
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ FilePath tempFile = module_context::tempFile("svnmsg", "txt");
+ boost::shared_ptr<std::ostream> pStream;
+
+ error = tempFile.open_w(&pStream);
+ if (error)
+ return error;
+
+ *pStream << message;
+
+ pStream->flush();
+ pStream.reset(); // release file handle
+
+
+ ShellArgs args;
+ args << "commit" << globalArgs();
+ args << "-F" << tempFile;
+
+ args << "--";
+ if (!paths.empty())
+ args << resolveAliasedPaths(paths);
+
+ // TODO: ensure tempFile is deleted when the commit process exits
+
+ boost::shared_ptr<ConsoleProcess> pCP;
+ error = createConsoleProc(args,
+ "SVN Commit",
+ s_isSvnSshRepository,
+ true,
+ true,
+ &pCP);
+ if (error)
+ return error;
+
+ maybeAttachPasswordManager(pCP);
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+Error svnDiffFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ int contextLines;
+ bool noSizeWarning;
+ Error error = json::readParams(request.params,
+ &path,
+ &contextLines,
+ &noSizeWarning);
+ if (error)
+ return error;
+
+ FilePath filePath = resolveAliasedPath(path);
+
+ if (contextLines < 0)
+ contextLines = 999999999;
+
+ std::string extArgs = "-U " + safe_convert::numberToString(contextLines);
+
+ std::string stdOut, stdErr;
+ int exitCode;
+ error = runSvn(ShellArgs() << "diff" <<
+ "--depth" << "empty" <<
+ "--diff-cmd" << "diff" <<
+ "-x" << extArgs <<
+ "--" << filePath,
+ &stdOut, &stdErr, &exitCode);
+ if (error)
+ return error;
+
+ if (exitCode != EXIT_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE(stdErr);
+ }
+
+ std::string sourceEncoding = projects::projectContext().defaultEncoding();
+ bool usedSourceEncoding;
+ stdOut = convertDiff(stdOut, sourceEncoding, "UTF-8", false,
+ &usedSourceEncoding);
+ if (!usedSourceEncoding)
+ sourceEncoding = "";
+
+ if (!noSizeWarning && stdOut.size() > source_control::WARN_SIZE)
+ {
+ error = systemError(boost::system::errc::file_too_large,
+ ERROR_LOCATION);
+ pResponse->setError(error,
+ json::Value(static_cast<uint64_t>(stdOut.size())));
+ }
+ else
+ {
+ json::Object result;
+ result["source_encoding"] = sourceEncoding;
+ result["decoded_value"] = stdOut;
+ pResponse->setResult(result);
+ }
+
+ return Success();
+}
+
+Error svnApplyPatch(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ std::string path, patch, sourceEncoding;
+ Error error = json::readParams(request.params,
+ &path,
+ &patch,
+ &sourceEncoding);
+ if (error)
+ return error;
+
+ bool converted;
+ patch = convertDiff(patch, "UTF-8", sourceEncoding, false, &converted);
+ if (!converted)
+ return systemError(boost::system::errc::illegal_byte_sequence, ERROR_LOCATION);
+
+ FilePath filePath = resolveAliasedPath(path);
+
+ FilePath tempFile = module_context::tempFile("svnpatch", "txt");
+ boost::shared_ptr<std::ostream> pStream;
+
+ error = tempFile.open_w(&pStream);
+ if (error)
+ return error;
+
+ *pStream << patch;
+
+ pStream->flush();
+ pStream.reset(); // release file handle
+
+ ShellCommand cmd("patch");
+ cmd << "-i" << tempFile;
+ cmd << filePath;
+
+ core::system::ProcessOptions options = procOptions();
+
+ core::system::ProcessResult result;
+ error = core::system::runCommand(cmd,
+ options,
+ &result);
+ if (error)
+ return error;
+
+ if (result.exitStatus != EXIT_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE(result.stdErr);
+ }
+
+ return Success();
+}
+
+struct CommitInfo
+{
+ std::string id;
+ std::string author;
+ std::string subject;
+ std::string description;
+ boost::posix_time::time_duration::sec_type date;
+};
+
+bool commitIsMatch(const std::vector<std::string>& patterns,
+ const CommitInfo& commit)
+{
+ BOOST_FOREACH(std::string pattern, patterns)
+ {
+ if (!boost::algorithm::ifind_first(commit.author, pattern)
+ && !boost::algorithm::ifind_first(commit.description, pattern)
+ && !boost::algorithm::ifind_first(commit.id, pattern)
+ && !boost::algorithm::ifind_first(commit.subject, pattern))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+boost::function<bool(const CommitInfo&)> createSearchTextPredicate(
+ const std::string& searchText)
+{
+ if (searchText.empty())
+ return boost::lambda::constant(true);
+
+ std::vector<std::string> results;
+ boost::algorithm::split(results, searchText,
+ boost::algorithm::is_any_of(" \t\r\n"));
+ return boost::bind(commitIsMatch, results, _1);
+}
+
+Error parseHistoryXml(int skip,
+ int maxentries,
+ const std::string& searchText,
+ const std::string& output,
+ const boost::function<Error(const CommitInfo&)>& callback)
+{
+ using namespace rapidxml;
+ using namespace boost::posix_time;
+
+ std::vector<char> buffer;
+ xml_document<> doc;
+ Error error = parseXml(output, &buffer, &doc);
+ if (error)
+ return error;
+
+ xml_node<>* pLog = doc.first_node("log");
+
+ boost::function<bool(const CommitInfo&)> filter =
+ createSearchTextPredicate(searchText);
+
+ const std::string NAME_REVISION = "revision";
+ const std::string NAME_AUTHOR = "author";
+ const std::string NAME_MSG = "msg";
+ const std::string NAME_DATE = "date";
+ const ptime epoch(boost::gregorian::date(1970,1,1));
+
+ CommitInfo commit;
+
+ int count = 0;
+ FOREACH_NODE(pLog, pEntry, "logentry")
+ {
+ if (count > maxentries)
+ break;
+
+ // This is not strictly necessary as "skip > 0" below would catch
+ // this case. But this saves us from doing some work for the common
+ // case of not searching.
+ if (searchText.empty() && skip > 0)
+ {
+ skip--;
+ continue;
+ }
+
+ commit.id = attr_value(pEntry, NAME_REVISION);
+ commit.author = string_utils::filterControlChars(node_value(pEntry, NAME_AUTHOR));
+ std::string message = string_utils::filterControlChars(node_value(pEntry, NAME_MSG));
+ splitMessage(message, &commit.subject, &commit.description);
+
+ ptime date = boost::date_time::parse_delimited_time<ptime>(
+ node_value(pEntry, NAME_DATE), 'T');
+ commit.date = (date - epoch).total_seconds();
+
+ // If we're searching and this doesn't match, skip it and don't decrement
+ // the skip--it's as if this one doesn't count
+ if (!filter(commit))
+ {
+ continue;
+ }
+
+ if (skip > 0)
+ {
+ skip--;
+ continue;
+ }
+
+ error = callback(commit);
+ if (error)
+ return error;
+
+ count++;
+ }
+
+ return Success();
+}
+
+void historyEnd(boost::function<void(Error, const std::string&)> callback,
+ const Error& error,
+ const core::system::ProcessResult& result)
+{
+ if (!error && result.exitStatus != EXIT_SUCCESS && !result.stdErr.empty())
+ LOG_ERROR_MESSAGE(result.stdErr);
+
+ if (error)
+ LOG_ERROR(error);
+
+ callback(error, result.stdOut);
+}
+
+void history(int rev,
+ FilePath fileFilter,
+ ShellArgs options,
+ boost::function<void(Error, const std::string&)> callback)
+{
+ ShellArgs args;
+ args << "log";
+ args << "--xml";
+
+ args << options.args();
+
+ if (rev > 0)
+ args << "-r" << safe_convert::numberToString(rev) + ":1";
+ else
+ args << "-r" << "HEAD:1";
+
+ if (!fileFilter.empty())
+ args << fileFilter;
+
+ runSvnAsync(args,
+ "SVN History",
+ false,
+ boost::bind(historyEnd, callback, _1, _2));
+}
+
+Error svnHistoryCountEnd_CommitCallback(int* pCount, const CommitInfo&)
+{
+ (*pCount)++;
+ return Success();
+}
+
+void svnHistoryCountEnd(const std::string& searchText,
+ const json::JsonRpcFunctionContinuation& cont,
+ Error error, const std::string& output)
+{
+ using namespace rapidxml;
+
+ json::JsonRpcResponse response;
+ if (error)
+ {
+ cont(error, &response);
+ return;
+ }
+
+ int count = 0;
+
+ if (searchText.empty())
+ {
+ std::vector<char> buffer;
+ xml_document<> doc;
+ error = parseXml(output, &buffer, &doc);
+ if (error)
+ {
+ cont(error, &response);
+ return;
+ }
+
+ xml_node<>* pLog = doc.first_node("log");
+ if (pLog)
+ {
+ FOREACH_NODE(pLog, pEntry, "logentry")
+ {
+ count++;
+ }
+ }
+ }
+ else
+ {
+ error = parseHistoryXml(0, 999999999, searchText, output,
+ boost::bind(svnHistoryCountEnd_CommitCallback,
+ &count,
+ _1));
+ if (error)
+ {
+ cont(error, &response);
+ return;
+ }
+ }
+
+ json::Object result;
+ result["count"] = count;
+ response.setResult(result);
+
+ cont(Success(), &response);
+}
+
+void svnHistoryCount(const json::JsonRpcRequest& request,
+ const json::JsonRpcFunctionContinuation& cont)
+{
+ int rev;
+ json::Value fileFilterJson;
+ std::string searchText;
+ Error error = json::readParams(request.params,
+ &rev,
+ &fileFilterJson,
+ &searchText);
+ if (error)
+ {
+ json::JsonRpcResponse response;
+ cont(error, &response);
+ return;
+ }
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ ShellArgs options;
+ options << "-q";
+ FilePath fileFilter = fileFilterPath(fileFilterJson);
+ history(rev, fileFilter, options,
+ boost::bind(svnHistoryCountEnd, searchText, cont, _1, _2));
+}
+
+Error svnHistoryEnd_CommitCallback(json::Array *pIds,
+ json::Array *pAuthors,
+ json::Array *pSubjects,
+ json::Array *pDescriptions,
+ json::Array *pDates,
+ const CommitInfo& commit)
+{
+ pIds->push_back(commit.id);
+ pAuthors->push_back(commit.author);
+ pSubjects->push_back(commit.subject);
+ pDescriptions->push_back(commit.description);
+ pDates->push_back(commit.date);
+ return Success();
+}
+
+void svnHistoryEnd(int skip,
+ int maxentries,
+ const std::string& searchText,
+ const json::JsonRpcFunctionContinuation& cont,
+ Error error,
+ const std::string& output)
+{
+ using namespace boost::posix_time;
+ using namespace rapidxml;
+
+ json::JsonRpcResponse response;
+
+ if (error)
+ {
+ cont(error, &response);
+ return;
+ }
+
+ json::Array ids;
+ json::Array authors;
+ json::Array subjects;
+ json::Array descriptions;
+ json::Array dates;
+
+ error = parseHistoryXml(skip, maxentries, searchText, output,
+ boost::bind(svnHistoryEnd_CommitCallback,
+ &ids,
+ &authors,
+ &subjects,
+ &descriptions,
+ &dates,
+ _1));
+ if (error)
+ {
+ cont(error, &response);
+ return;
+ }
+
+ json::Object result;
+ result["id"] = ids;
+ result["author"] = authors;
+ result["subject"] = subjects;
+ result["description"] = descriptions;
+ result["date"] = dates;
+
+ response.setResult(result);
+ cont(Success(), &response);
+}
+
+void svnHistory(const json::JsonRpcRequest& request,
+ const json::JsonRpcFunctionContinuation& cont)
+{
+ int rev;
+ json::Value fileFilterJson;
+ std::string searchText;
+ int skip, maxentries;
+ Error error = json::readParams(request.params,
+ &rev,
+ &fileFilterJson,
+ &skip,
+ &maxentries,
+ &searchText);
+ if (error)
+ {
+ cont(error, NULL);
+ return;
+ }
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ ShellArgs options;
+ if (searchText.empty())
+ {
+ int limit = skip + maxentries;
+ options << "--limit" << safe_convert::numberToString(limit);
+ }
+
+ FilePath fileFilter = fileFilterPath(fileFilterJson);
+ history(rev, fileFilter, options,
+ boost::bind(svnHistoryEnd,
+ skip,
+ maxentries,
+ searchText,
+ cont,
+ _1,
+ _2));
+}
+
+void svnShowEnd(bool noSizeWarning,
+ const json::JsonRpcFunctionContinuation& cont,
+ Error error,
+ const core::system::ProcessResult& result)
+{
+ json::JsonRpcResponse response;
+
+ if (error)
+ {
+ LOG_ERROR(error);
+ cont(error, &response);
+ return;
+ }
+
+ if (!noSizeWarning && result.stdOut.size() > source_control::WARN_SIZE)
+ {
+ response.setError(
+ systemError(boost::system::errc::file_too_large, ERROR_LOCATION),
+ json::Value(static_cast<uint64_t>(result.stdOut.size())));
+ }
+ else
+ {
+ response.setResult(
+ convertDiff(result.stdOut,
+ projects::projectContext().defaultEncoding(),
+ "UTF-8",
+ true));
+ }
+
+ cont(error, &response);
+}
+
+void svnShow(const json::JsonRpcRequest& request,
+ const json::JsonRpcFunctionContinuation& cont)
+{
+ int revision;
+ bool noSizeWarning;
+ Error error = json::readParams(request.params, &revision, &noSizeWarning);
+ if (error)
+ {
+ json::JsonRpcResponse response;
+ cont(error, &response);
+ return;
+ }
+
+ ShellArgs args;
+ args << "diff" << "-c" << revision;
+
+ runSvnAsync(args,
+ "SVN History",
+ false,
+ boost::bind(svnShowEnd, noSizeWarning, cont, _1, _2));
+}
+
+void svnShowFileEnd(const json::JsonRpcFunctionContinuation& cont,
+ Error error,
+ const core::system::ProcessResult& result)
+{
+ json::JsonRpcResponse response;
+
+ if (error)
+ {
+ LOG_ERROR(error);
+ cont(error, &response);
+ return;
+ }
+
+ response.setResult(convertToUtf8(result.stdOut, false));
+ cont(error, &response);
+}
+
+void svnShowFile(const json::JsonRpcRequest& request,
+ const json::JsonRpcFunctionContinuation& cont)
+{
+ int rev;
+ std::string filename;
+ Error error = json::readParams(request.params, &rev, &filename);
+ if (error)
+ {
+ json::JsonRpcResponse response;
+ cont(error, &response);
+ return;
+ }
+
+ ShellArgs args;
+ args << "cat" << "-r" << safe_convert::numberToString(rev)
+ << "--" << filename;
+ runSvnAsync(args,
+ "SVN Show File",
+ false,
+ boost::bind(svnShowFileEnd, cont, _1, _2));
+}
+
+Error getIgnores(const FilePath& filePath,
+ core::system::ProcessResult* pResult)
+{
+ return runSvn(ShellArgs() << "propget" << "svn:ignore"
+ << filePath << globalArgs(),
+ true,
+ pResult);
+}
+
+Error svnGetIgnores(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+
+ core::system::ProcessResult result;
+ error = getIgnores(filePath, &result);
+ if (error)
+ return error;
+
+ // success
+ pResponse->setResult(processResultToJson(result));
+ return Success();
+}
+
+Error setIgnores(const FilePath& filePath,
+ const std::string& ignores,
+ core::system::ProcessResult* pResult)
+{
+ // write the ignores to a temporary file
+ FilePath ignoresFile = module_context::tempFile("svn-ignore", "txt");
+ Error error = core::writeStringToFile(ignoresFile, ignores);
+ if (error)
+ return error;
+
+ // set them
+ error = runSvn(ShellArgs() << "propset" << "svn:ignore"
+ << filePath << "-F" << ignoresFile
+ << globalArgs(),
+ true,
+ pResult);
+
+ // always remove the temporary file
+ Error removeError = ignoresFile.remove();
+ if (removeError)
+ LOG_ERROR(error);
+
+ // return svn error status
+ return error;
+}
+
+Error svnSetIgnores(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ RefreshOnExit refreshOnExit;
+
+ // get the params
+ std::string path, ignores;
+ Error error = json::readParams(request.params, &path, &ignores);
+ if (error)
+ return error;
+
+ // resolve path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+
+ core::system::ProcessResult result;
+ error = setIgnores(filePath, ignores, &result);
+ if (error)
+ return error;
+
+ // success
+ pResponse->setResult(processResultToJson(result));
+ return Success();
+}
+
+Error checkout(const std::string& url,
+ const std::string& username,
+ const std::string dirName,
+ const core::FilePath& parentPath,
+ boost::shared_ptr<console_process::ConsoleProcess>* ppCP)
+{
+ // optional username arg
+ ShellArgs args;
+ if (!username.empty())
+ args << "--username" << username;
+
+ // checkout command
+ args << "checkout" << url;
+
+ // optional target directory arg
+ if (!dirName.empty())
+ args << dirName;
+
+ bool requiresSsh = boost::algorithm::starts_with(url, "svn+ssh");
+
+ Error error = createConsoleProc(args,
+ FilePath(),
+ parentPath,
+ "SVN Checkout",
+ requiresSsh,
+ true,
+ true,
+ ppCP);
+
+ if (error)
+ return error;
+
+ // attach the password manager if this is an svn+ssh url
+ if (requiresSsh)
+ s_pPasswordManager->attach(*ppCP, false);
+
+ return Success();
+}
+
+bool promptForPassword(const std::string& prompt,
+ bool showRememberOption,
+ std::string* pPassword,
+ bool* pRemember)
+{
+ std::string rememberPrompt = showRememberOption ?
+ "Remember for this session" : "";
+ ask_pass::PasswordInput input;
+ Error error = ask_pass::askForPassword(prompt,
+ rememberPrompt,
+ &input);
+ if (!error)
+ {
+ if (!input.cancelled)
+ {
+ *pPassword = input.password;
+ *pRemember = input.remember;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+}
+
+SvnFileDecorationContext::SvnFileDecorationContext(
+ const core::FilePath& rootDir)
+{
+ using namespace source_control;
+
+ std::vector<FileWithStatus> results;
+ Error error = status(rootDir, &results);
+ if (error)
+ return;
+
+ vcsResult_ = StatusResult(results);
+}
+
+SvnFileDecorationContext::~SvnFileDecorationContext()
+{
+}
+
+void SvnFileDecorationContext::decorateFile(const core::FilePath& filePath,
+ core::json::Object* pFileObject)
+{
+ using namespace source_control;
+
+ VCSStatus status = vcsResult_.getStatus(filePath);
+
+ json::Object jsonStatus;
+ Error error = statusToJson(filePath, status, &jsonStatus);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ (*pFileObject)["svn_status"] = jsonStatus;
+}
+
+Error augmentSvnIgnore()
+{
+ // check for existing svn:ignore
+ core::system::ProcessResult result;
+ Error error = getIgnores(s_workingDir, &result);
+ if (error)
+ return error;
+ if (result.exitStatus != EXIT_SUCCESS)
+ {
+ LOG_ERROR_MESSAGE(result.stdErr);
+ return Success();
+ }
+ std::string svnIgnore = boost::algorithm::trim_copy(result.stdOut);
+
+ // if it's empty then set our default
+ if (svnIgnore.empty())
+ {
+ // If no svn:ignore exists, add this stuff
+ svnIgnore += ".Rproj.user\n";
+ svnIgnore += ".Rhistory\n";
+ svnIgnore += ".RData\n";
+ }
+ else
+ {
+ // If svn:ignore exists, add .Rproj.user unless it's already there
+ if (boost::regex_search(svnIgnore, boost::regex("^\\.Rproj\\.user$")))
+ return Success();
+
+ bool addExtraNewline = svnIgnore.size() > 0
+ && svnIgnore[svnIgnore.size() - 1] != '\n';
+ if (addExtraNewline)
+ svnIgnore += "\n";
+
+ svnIgnore += ".Rproj.user\n";
+ }
+
+ // write back svn:ignore
+ core::system::ProcessResult setResult;
+ error = setIgnores(s_workingDir, svnIgnore, &setResult);
+ if (error)
+ return error;
+
+ if (result.exitStatus != EXIT_SUCCESS)
+ LOG_ERROR_MESSAGE(result.stdErr);
+
+ return Success();
+}
+
+Error initialize()
+{
+ initSvnBin();
+
+ // initialize password manager
+ s_pPasswordManager.reset(new PasswordManager(
+ boost::regex("^(.+): $"),
+ boost::bind(promptForPassword, _1, _2, _3, _4)));
+
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "svn_add", svnAdd))
+ (bind(registerRpcMethod, "svn_delete", svnDelete))
+ (bind(registerRpcMethod, "svn_revert", svnRevert))
+ (bind(registerRpcMethod, "svn_resolve", svnResolve))
+ (bind(registerRpcMethod, "svn_status", svnStatus))
+ (bind(registerRpcMethod, "svn_update", svnUpdate))
+ (bind(registerRpcMethod, "svn_cleanup", svnCleanup))
+ (bind(registerRpcMethod, "svn_commit", svnCommit))
+ (bind(registerRpcMethod, "svn_diff_file", svnDiffFile))
+ (bind(registerRpcMethod, "svn_apply_patch", svnApplyPatch))
+ (bind(registerAsyncRpcMethod, "svn_history_count", svnHistoryCount))
+ (bind(registerAsyncRpcMethod, "svn_history", svnHistory))
+ (bind(registerAsyncRpcMethod, "svn_show", svnShow))
+ (bind(registerAsyncRpcMethod, "svn_show_file", svnShowFile))
+ (bind(registerRpcMethod, "svn_get_ignores", svnGetIgnores))
+ (bind(registerRpcMethod, "svn_set_ignores", svnSetIgnores))
+ ;
+ Error error = initBlock.execute();
+ if (error)
+ return error;
+
+ return Success();
+}
+
+Error initializeSvn(const core::FilePath& workingDir)
+{
+ s_workingDir = workingDir;
+
+ Error error = augmentSvnIgnore();
+ if (error)
+ LOG_ERROR(error);
+
+ std::string repoURL = repositoryRoot(s_workingDir);
+ s_isSvnSshRepository = boost::algorithm::starts_with(repoURL, "svn+ssh");
+
+ userSettings().onChanged.connect(onUserSettingsChanged);
+
+ return Success();
+}
+
+} // namespace svn
+} // namespace modules
+} //namespace session
diff --git a/src/cpp/session/modules/SessionSVN.hpp b/src/cpp/session/modules/SessionSVN.hpp
new file mode 100644
index 0000000..78ac5b0
--- /dev/null
+++ b/src/cpp/session/modules/SessionSVN.hpp
@@ -0,0 +1,76 @@
+/*
+ * SessionSVN.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SVN_HPP
+#define SESSION_SVN_HPP
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include "vcs/SessionVCSCore.hpp"
+
+namespace session {
+namespace modules {
+namespace console_process {
+
+class ConsoleProcess;
+
+} // namespace console_process
+
+namespace svn {
+
+extern const char * const kVcsId;
+
+class SvnFileDecorationContext : public source_control::FileDecorationContext
+{
+public:
+ SvnFileDecorationContext(const core::FilePath& rootDir);
+ virtual ~SvnFileDecorationContext();
+ void decorateFile(const core::FilePath& filePath,
+ core::json::Object* pFileObject);
+private:
+ source_control::StatusResult vcsResult_;
+};
+
+// Returns true if Subversion install is detected
+bool isSvnInstalled();
+
+// Returns true if the working directory is in a Subversion tree
+bool isSvnDirectory(const core::FilePath& workingDir);
+
+std::string repositoryRoot(const core::FilePath& workingDir);
+
+bool isSvnEnabled();
+
+core::FilePath detectedSvnExePath();
+
+std::string nonPathSvnBinDir();
+
+core::Error checkout(const std::string& url,
+ const std::string& username,
+ const std::string dirName,
+ const core::FilePath& parentPath,
+ boost::shared_ptr<console_process::ConsoleProcess>* ppCP);
+
+core::Error initialize();
+
+// Initialize SVN with the given working directory
+core::Error initializeSvn(const core::FilePath& workingDir);
+
+} // namespace svn
+} // namespace modules
+} // namespace session
+
+#endif
diff --git a/src/cpp/session/modules/SessionShinyApps.R b/src/cpp/session/modules/SessionShinyApps.R
new file mode 100644
index 0000000..cb3b9ec
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyApps.R
@@ -0,0 +1,53 @@
+#
+# SessionShinyApps
+#
+# Copyright (C) 2009-14 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("scalarListFromFrame", function(frame)
+{
+ ret <- list()
+ cols <- names(frame)
+
+ # take apart the frame and compose a list of scalars from each row
+ for (i in seq_len(nrow(frame))) {
+ row <- lapply(cols, function(col) { .rs.scalar(unlist(frame[i,col])) })
+ names(row) <- cols
+ ret[[i]] <- row
+ }
+ return(ret)
+})
+
+.rs.addJsonRpcHandler("get_shinyapps_account_list", function() {
+ shinyapps::accounts()
+})
+
+.rs.addJsonRpcHandler("remove_shinyapps_account", function(account) {
+ shinyapps::removeAccount(account)
+})
+
+.rs.addJsonRpcHandler("get_shinyapps_app_list", function(account) {
+ .rs.scalarListFromFrame(shinyapps::applications(account))
+})
+
+.rs.addJsonRpcHandler("get_shinyapps_deployments", function(dir) {
+ .rs.scalarListFromFrame(shinyapps::deployments(dir))
+})
+
+# The parameter to this function is a string containing the R command from
+# the ShinyApps service; we just need to parse and execute it directly.
+# The client is responsible for verifying that the statement corresponds to
+# a valid ::setAccountInfo command.
+.rs.addJsonRpcHandler("connect_shinyapps_account", function(accountCmd) {
+ cmd <- parse(text=accountCmd)
+ eval(cmd, envir = globalenv())
+})
diff --git a/src/cpp/session/modules/SessionShinyApps.cpp b/src/cpp/session/modules/SessionShinyApps.cpp
new file mode 100644
index 0000000..df483e4
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyApps.cpp
@@ -0,0 +1,51 @@
+/*
+ * SessionShinyApps.cpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionShinyApps.hpp"
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <r/RSexp.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace shiny_apps {
+
+namespace {
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionShinyApps.R"));
+
+ return initBlock.execute();
+}
+
+} // namespace shiny_apps
+} // namespace modules
+} // namespace session
+
diff --git a/src/cpp/session/modules/SessionShinyApps.hpp b/src/cpp/session/modules/SessionShinyApps.hpp
new file mode 100644
index 0000000..aebc340
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyApps.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionShinyApps.hpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SHINY_APPS_HPP
+#define SESSION_SHINY_APPS_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace shiny_apps {
+
+core::Error initialize();
+
+} // namespace shiny_apps
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_SHINY_APPS_HPP
diff --git a/src/cpp/session/modules/SessionShinyViewer.R b/src/cpp/session/modules/SessionShinyViewer.R
new file mode 100644
index 0000000..8836b46
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyViewer.R
@@ -0,0 +1,49 @@
+#
+# SessionShinyViewer.R
+#
+# Copyright (C) 2009-14 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("invokeShinyPaneViewer", function(url) {
+ invisible(.Call("rs_shinyviewer", url, getwd(), 2))
+}, attrs = list(shinyViewerType = 2))
+
+.rs.addFunction("invokeShinyWindowViewer", function(url) {
+ invisible(.Call("rs_shinyviewer", url, getwd(), 3))
+}, attrs = list(shinyViewerType = 3))
+
+.rs.addFunction("setShinyViewerType", function(type) {
+ if (type == 1)
+ options(shiny.launch.browser = FALSE)
+ else if (type == 2)
+ options(shiny.launch.browser = .rs.invokeShinyPaneViewer)
+ else if (type == 3)
+ options(shiny.launch.browser = .rs.invokeShinyWindowViewer)
+ else if (type == 4)
+ options(shiny.launch.browser = TRUE)
+})
+
+.rs.addFunction("getShinyViewerType", function() {
+ viewer <- getOption("shiny.launch.browser")
+ if (identical(viewer, FALSE))
+ return(1)
+ else if (identical(viewer, TRUE))
+ return(4)
+ else if (is.function(viewer) && is.numeric(attr(viewer, "shinyViewerType")))
+ return(attr(viewer, "shinyViewerType"))
+ return(0)
+})
+
+.rs.addJsonRpcHandler("get_shiny_viewer_type", function() {
+ list(viewerType = .rs.scalar(.rs.getShinyViewerType()))
+})
+
diff --git a/src/cpp/session/modules/SessionShinyViewer.cpp b/src/cpp/session/modules/SessionShinyViewer.cpp
new file mode 100644
index 0000000..ae445b9
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyViewer.cpp
@@ -0,0 +1,228 @@
+/*
+ * SessionShinyViewer.cpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionShinyViewer.hpp"
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RUtil.hpp>
+#include <r/ROptions.hpp>
+
+#include <r/session/RSessionUtils.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace shiny_viewer {
+
+namespace {
+
+// track the current viewed url and path
+std::string s_currentAppUrl;
+std::string s_currentAppPath;
+
+void enqueStartEvent(const std::string& url, const std::string& path,
+ int viewerType)
+{
+ // record the url and path
+ s_currentAppUrl = module_context::mapUrlPorts(url);
+ s_currentAppPath = path;
+
+ // enque the event
+ json::Object dataJson;
+ dataJson["url"] = s_currentAppUrl;
+ dataJson["path"] =
+ module_context::createAliasedPath(FilePath(s_currentAppPath));
+ dataJson["state"] = "started";
+ dataJson["viewer"] = viewerType;
+ ClientEvent event(client_events::kShinyViewer, dataJson);
+ module_context::enqueClientEvent(event);
+}
+
+SEXP rs_shinyviewer(SEXP urlSEXP, SEXP pathSEXP, SEXP viewerSEXP)
+{
+ try
+ {
+ if (!r::sexp::isString(urlSEXP) || (r::sexp::length(urlSEXP) != 1))
+ {
+ throw r::exec::RErrorException(
+ "url must be a single element character vector.");
+ }
+
+ if (!r::sexp::isString(pathSEXP) || (r::sexp::length(pathSEXP) != 1))
+ {
+ throw r::exec::RErrorException(
+ "path must be a single element character vector.");
+ }
+ int viewertype = r::sexp::asInteger(viewerSEXP);
+
+ // in desktop mode make sure we have the right version of httpuv
+ if (options().programMode() == kSessionProgramModeDesktop)
+ {
+ if (!module_context::isPackageVersionInstalled("httpuv", "1.2"))
+ {
+ module_context::consoleWriteError("\nWARNING: To view Shiny "
+ "applications inside RStudio, you need to "
+ "install the latest version of the httpuv package from "
+ "CRAN (version 1.2 or higher is required).\n\n");
+ }
+ }
+
+ enqueStartEvent(r::sexp::safeAsString(urlSEXP),
+ r::sexp::safeAsString(pathSEXP),
+ viewertype);
+ }
+ catch(const r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+
+ return R_NilValue;
+}
+
+void setShinyViewerType(int viewerType)
+{
+ Error error =
+ r::exec::RFunction(".rs.setShinyViewerType",
+ viewerType).call();
+ if (error)
+ LOG_ERROR(error);
+}
+
+void onUserSettingsChanged(boost::shared_ptr<int> pShinyViewerType)
+{
+ int shinyViewerType = userSettings().shinyViewerType();
+ if (shinyViewerType != *pShinyViewerType)
+ {
+ setShinyViewerType(shinyViewerType);
+ *pShinyViewerType = shinyViewerType;
+ }
+}
+
+Error setShinyViewer(boost::shared_ptr<int> pShinyViewerType,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse*)
+{
+ int viewerType;
+ Error error = json::readParams(request.params, &viewerType);
+ if (error)
+ return error;
+
+ if (viewerType != *pShinyViewerType)
+ {
+ setShinyViewerType(viewerType);
+ *pShinyViewerType = viewerType;
+ }
+
+ return Success();
+}
+
+Error getShinyRunCmd(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string targetPath;
+ Error error = json::readParams(request.params, &targetPath);
+ if (error)
+ return error;
+
+ // Consider: if the shiny namespace is attached to the search path, we
+ // don't need to emit "shiny::".
+ std::string runCmd = "shiny::runApp(";
+ std::string dir = module_context::pathRelativeTo(
+ module_context::safeCurrentPath(),
+ module_context::resolveAliasedPath(targetPath));
+ if (dir != ".")
+ {
+ // runApp defaults to the current working directory, so don't specify
+ // it unless we need to.
+ runCmd.append("'");
+ runCmd.append(dir);
+ runCmd.append("'");
+ }
+ runCmd.append(")");
+
+ json::Object dataJson;
+ dataJson["run_cmd"] = runCmd;
+ pResponse->setResult(dataJson);
+
+ return Success();
+}
+
+Error initShinyViewerPref(boost::shared_ptr<int> pShinyViewerType)
+{
+ SEXP shinyBrowser = r::options::getOption("shiny.launch.browser");
+ *pShinyViewerType = userSettings().shinyViewerType();
+
+ // If the user hasn't specified a value for the shiny.launch.browser
+ // preference, set it to the one specified in UI prefs. Note we only
+ // do this for shiny >= 0.8 since that is the version which supports
+ // passing a function to shiny.launch.browser
+ if (module_context::isPackageVersionInstalled("shiny", "0.8"))
+ {
+ if (shinyBrowser == R_NilValue)
+ {
+ setShinyViewerType(*pShinyViewerType);
+ }
+ }
+
+ return Success();
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ boost::shared_ptr<int> pShinyViewerType =
+ boost::make_shared<int>(SHINY_VIEWER_NONE);
+
+ json::JsonRpcFunction setShinyViewerTypeRpc =
+ boost::bind(setShinyViewer, pShinyViewerType, _1, _2);
+
+ R_CallMethodDef methodDefViewer;
+ methodDefViewer.name = "rs_shinyviewer";
+ methodDefViewer.fun = (DL_FUNC) rs_shinyviewer;
+ methodDefViewer.numArgs = 3;
+ r::routines::addCallMethod(methodDefViewer);
+
+ userSettings().onChanged.connect(bind(onUserSettingsChanged,
+ pShinyViewerType));
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionShinyViewer.R"))
+ (bind(registerRpcMethod, "get_shiny_run_cmd", getShinyRunCmd))
+ (bind(registerRpcMethod, "set_shiny_viewer_type", setShinyViewerTypeRpc))
+ (bind(initShinyViewerPref, pShinyViewerType));
+
+ return initBlock.execute();
+}
+
+} // namespace shiny_viewer
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionShinyViewer.hpp b/src/cpp/session/modules/SessionShinyViewer.hpp
new file mode 100644
index 0000000..85eda78
--- /dev/null
+++ b/src/cpp/session/modules/SessionShinyViewer.hpp
@@ -0,0 +1,39 @@
+/*
+ * SessionShinyViewer.hpp
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SHINY_VIEWER_HPP
+#define SESSION_SHINY_VIEWER_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace shiny_viewer {
+
+const int SHINY_VIEWER_USER = 0;
+const int SHINY_VIEWER_NONE = 1;
+const int SHINY_VIEWER_PANE = 2;
+const int SHINY_VIEWER_WINDOW = 3;
+const int SHINY_VIEWER_BROWSER = 4;
+
+core::Error initialize();
+
+} // namespace shiny_viewer
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SHINY_VIEWER_HPP
diff --git a/src/cpp/session/modules/SessionSource.R b/src/cpp/session/modules/SessionSource.R
new file mode 100644
index 0000000..38928d4
--- /dev/null
+++ b/src/cpp/session/modules/SessionSource.R
@@ -0,0 +1,300 @@
+#
+# SessionSource.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addJsonRpcHandler("save_active_document", function(contents,
+ sweave,
+ rnwWeave)
+{
+ # manage working directory
+ previousWd = getwd()
+ setwd("~/")
+ on.exit(setwd(previousWd))
+
+ activeRStudioDoc <- "~/.active-rstudio-document"
+ if (file.exists(activeRStudioDoc))
+ file.remove(activeRStudioDoc)
+
+ writeChar(contents, activeRStudioDoc, eos=NULL)
+
+ if (sweave)
+ {
+ op <- function() {
+ .Call("rs_rnwTangle", activeRStudioDoc, rnwWeave)
+ file.remove(activeRStudioDoc)
+ file.rename(paste(activeRStudioDoc, ".R", sep=""), activeRStudioDoc)
+ }
+ capture.output(op())
+ }
+
+ .Call("rs_ensureFileHidden", activeRStudioDoc)
+
+ return()
+})
+
+.rs.addFunction("iconvcommon", function()
+{
+ # NOTE: we originally included MacRoman and HZ-GB-2312 in our list of
+ # common encodings however MacRoman isn't available on Windows or Linux
+ # and HZ-GB-2312 isn't available on Linux so we removed them from the
+ # common list (in the interest of providing a list of portable encodings
+ # so that projects could reliably use common encodings and not run
+ # into issues moving from system to system
+ #
+
+ common <- c(
+ 'ASCII', 'UTF-8',
+ # Western
+ 'ISO-8859-1', 'Windows-1252', # 'MacRoman',
+ # Japanese
+ 'Shift-JIS', 'ISO-2022-JP', #'EUC-JP', 'Shift-JISX0213',
+ # Trad Chinese
+ 'Big5', #'Big5-HKSCS',
+ # Korean
+ 'ISO-2022-KR',
+ # Arabic
+ #'ISO-8859-6', #'Windows-1256',
+ # Hebrew
+ #'ISO-8859-8', #'Windows-1255',
+ # Greek
+ 'ISO-8859-7', #'Windows-1253',
+ # Cyrillic
+ #'ISO-8859-5', 'MacCyrillic', 'KOI8-R', 'Windows-1251',
+ # Ukranian
+ #'KOI8-U',
+ # Simplified Chinese
+ 'GB2312', #'HZ-GB-2312',
+ # Chinese
+ 'GB18030',
+ # Central European
+ 'ISO-8859-2' #'ISO-8859-4', 'MacCentralEurope', 'Windows-1250'
+ # Vietnamese
+ #'Windows-1258',
+ # Turkish
+ #'ISO-8859-5', 'Windows-1254',
+ # Baltic
+ #'Windows-1257'
+ )
+
+ toupper(common)
+})
+
+.rs.addJsonRpcHandler("iconvlist", function()
+{
+ list(common=sort(intersect(.rs.iconvcommon(), toupper(iconvlist()))),
+ all=sort(iconvlist()))
+})
+
+.rs.addGlobalFunction('source.with.encoding',
+ function(path, encoding,
+ echo=getOption('verbose'),
+ print.eval=echo,
+ max.deparse.length=150,
+ chdir=FALSE)
+{
+ con = file(path, open='r', encoding=encoding)
+ on.exit(close(con))
+ source(con,
+ echo=echo,
+ print.eval=print.eval,
+ max.deparse.length=max.deparse.length,
+ chdir=chdir)
+})
+
+### Detect free variables ###
+
+# Callback when code walker encounters function call.
+# It's mostly looking for variable assignment--if it sees
+# a symbol being assigned, it sets the symbol equal to true
+# in w$assigned. Otherwise, it recurses.
+#
+# Functions are handled specially, they redefine w$assigned
+# to be their sub-environment, with a parent reference to
+# the containing environment.
+.rs.addFunction("detectFreeVars_Call", function(e, w)
+{
+ freeVars <- character(0)
+
+ func <- e[[1]]
+ funcName <- as.character(func)
+ args <- as.list(e[-1])
+
+ if (typeof(func) == 'language')
+ {
+ freeVars <- c(freeVars, codetools:::walkCode(func, w))
+ }
+ else if (funcName %in% c('<-', '<<-', '=', 'for')
+ && length(args) > 1
+ && typeof(args[[1]]) != 'language')
+ {
+ lvalue <- as.character(args[[1]])
+
+ # Need to walk the right side of an assignment, before
+ # considering the lvalue (e.g.: x <- x + 1)
+ args <- args[-1]
+ if (length(args) > 0)
+ {
+ for (ee in args)
+ freeVars <- c(freeVars, codetools:::walkCode(ee, w))
+ }
+ args <- c() # Clear out `args` so they aren't walked later
+
+ if (funcName == '<<-')
+ assign(lvalue, T, envir=w$assignedGlobals)
+ else
+ assign(lvalue, T, envir=w$assigned)
+ }
+ else if (funcName == '$')
+ {
+ # In foo$bar, ignore bar
+ args <- args[-2]
+ }
+ else if (funcName == 'function')
+ {
+ params <- args[[1]]
+ w$assigned <- new.env(parent=w$assigned)
+
+ for (param in names(params))
+ {
+ assign(param, T, envir=w$assigned)
+ freeVars <- c(freeVars, codetools:::walkCode(params[[param]], w))
+ }
+ args <- args[-1]
+ }
+
+ if (length(args) > 0)
+ {
+ for (ee in args)
+ freeVars <- c(freeVars, codetools:::walkCode(ee, w))
+ }
+ return(unique(freeVars))
+})
+
+# Lets us know when we've seen a symbol. If the symbol hasn't
+# been assigned yet (i.e. it doesn't exist in w$assigned) then
+# we can assume it's a free variable.
+.rs.addFunction("detectFreeVars_Leaf", function(e, w)
+{
+ if (typeof(e) == 'symbol' && nchar(as.character(e)) > 0 && !exists(as.character(e), envir=w$assigned))
+ return(as.character(e))
+ else
+ return(character(0))
+})
+
+.rs.addJsonRpcHandler("detect_free_vars", function(code)
+{
+ globals <- new.env(parent=emptyenv())
+
+ # Ignore predefined symbols like T and F
+ assign('T', T, envir=globals)
+ assign('F', T, envir=globals)
+
+ w <- codetools:::makeCodeWalker(assigned=globals,
+ assignedGlobals=globals,
+ call=.rs.detectFreeVars_Call,
+ leaf=.rs.detectFreeVars_Leaf)
+ freeVars <- character(0)
+ for (e in parse(text=code))
+ freeVars <- c(freeVars, codetools:::walkCode(e, w))
+ return(unique(freeVars))
+})
+
+
+.rs.addFunction("createDefaultShellRd", function(`_name`, `_type`)
+{
+ # create a tempdir and switch to it for the duration of the function
+ dirName <- tempfile("RdShell")
+ dir.create(dirName)
+ previousWd <- getwd()
+ on.exit(setwd(previousWd))
+ setwd(dirName)
+
+ if (identical(`_type`, "function"))
+ {
+ assign(`_name`, function(x) {})
+ return (.rs.normalizePath(paste(getwd(),
+ utils::prompt(name = `_name`),
+ sep="/")))
+ }
+ else if (identical(`_type`, "data"))
+ {
+ assign(`_name`, data.frame(x=integer(), y=integer()))
+ return (.rs.normalizePath(paste(getwd(),
+ utils::promptData(name = `_name`),
+ sep="/")))
+ }
+ else
+ {
+ return ("")
+ }
+})
+
+.rs.addFunction("createShellRd", function(name, type, package)
+{
+ # create a tempdir and switch to it for the duration of the function
+ dirName <- tempfile("RdShell")
+ dir.create(dirName)
+ previousWd <- getwd()
+ on.exit(setwd(previousWd))
+ setwd(dirName)
+
+ if (identical(type, "function"))
+ {
+ func <- .rs.getPackageFunction(name, package)
+ if (!is.null(func))
+ {
+ funcRd <- utils::prompt(func, name = name)
+ return (.rs.normalizePath(paste(getwd(),funcRd,sep="/")))
+ }
+ else
+ {
+ return ("")
+ }
+ }
+ else if (identical(type, "data"))
+ {
+ dataRd <- tryCatch(suppressWarnings({
+ library(package,character.only=TRUE)
+ eval(parse(text = paste("data(", name, "); ",
+ "utils::promptData(", name, ");", sep="")),
+ envir = globalenv())
+ }), error = function(e) {print(e); ""})
+
+ if (nzchar(dataRd) == TRUE)
+ return (.rs.normalizePath(paste(getwd(), dataRd, sep="/")))
+ else
+ return ("")
+ }
+ else
+ {
+ return ("")
+ }
+})
+
+.rs.addFunction("initSource", function()
+{
+ .rs.registerReplaceHook("file.edit", "utils", function(original, ...)
+ {
+ # just take unnamed arguments (those are the files)
+ args <- c(...)
+ names <- names(args)
+ if (!is.null(names))
+ args <- args[names(args) == ""]
+
+ # call rstudio fileEdit function
+ files <- path.expand(args)
+ invisible(.Call("rs_fileEdit", files))
+ })
+})
+
diff --git a/src/cpp/session/modules/SessionSource.cpp b/src/cpp/session/modules/SessionSource.cpp
new file mode 100644
index 0000000..707aac8
--- /dev/null
+++ b/src/cpp/session/modules/SessionSource.cpp
@@ -0,0 +1,1072 @@
+/*
+ * SessionSource.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSource.hpp"
+
+#include <string>
+#include <map>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/utility.hpp>
+
+#include <core/r_util/RSourceIndex.hpp>
+
+#include <core/Log.hpp>
+#include <core/Exec.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileInfo.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/StringUtils.hpp>
+#include <core/text/TemplateFilter.hpp>
+#include <core/r_util/RPackageInfo.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/system/FileChangeEvent.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RInternal.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/RUtil.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+extern "C" const char *locale2charset(const char *);
+
+#include <session/SessionSourceDatabase.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace source {
+
+using namespace session::source_database;
+
+namespace {
+
+// maintain an in-memory list of R source document indexes (for fast
+// code searching)
+class RSourceIndexes : boost::noncopyable
+{
+private:
+ friend RSourceIndexes& rSourceIndexes();
+ RSourceIndexes() {}
+
+public:
+ virtual ~RSourceIndexes() {}
+
+ // COPYING: boost::noncopyable
+
+ void update(boost::shared_ptr<SourceDocument> pDoc)
+ {
+ // is this indexable? if not then bail
+ if ( pDoc->path().empty() ||
+ (FilePath(pDoc->path()).extensionLowerCase() != ".r") )
+ {
+ return;
+ }
+
+ // index the source
+ boost::shared_ptr<r_util::RSourceIndex> pIndex(
+ new r_util::RSourceIndex(pDoc->path(), pDoc->contents()));
+
+ // insert it
+ indexes_[pDoc->id()] = pIndex;
+ }
+
+ void remove(const std::string& id)
+ {
+ indexes_.erase(id);
+ }
+
+ void removeAll()
+ {
+ indexes_.clear();
+ }
+
+ std::vector<boost::shared_ptr<r_util::RSourceIndex> > indexes()
+ {
+ std::vector<boost::shared_ptr<r_util::RSourceIndex> > indexes;
+ BOOST_FOREACH(const IndexMap::value_type& index, indexes_)
+ {
+ indexes.push_back(index.second);
+ }
+ return indexes;
+ }
+
+private:
+ typedef std::map<std::string, boost::shared_ptr<r_util::RSourceIndex> >
+ IndexMap;
+ IndexMap indexes_;
+};
+
+RSourceIndexes& rSourceIndexes()
+{
+ static RSourceIndexes instance;
+ return instance;
+}
+
+
+void writeDocToJson(boost::shared_ptr<SourceDocument> pDoc,
+ core::json::Object* pDocJson)
+{
+ // write the doc
+ pDoc->writeToJson(pDocJson);
+
+ // derive the extended type property
+ (*pDocJson)["extended_type"] = module_context::events()
+ .onDetectSourceExtendedType(pDoc);
+}
+
+void detectExtendedType(boost::shared_ptr<SourceDocument> pDoc)
+{
+ // detect the extended type of the document by calling any registered
+ // extended type detection handlers
+ std::string extendedType =
+ module_context::events().onDetectSourceExtendedType(pDoc);
+
+ // notify the client
+ json::Object jsonData;
+ jsonData["doc_id"] = pDoc->id();
+ jsonData["extended_type"] = extendedType;
+ ClientEvent event(client_events::kSourceExtendedTypeDetected, jsonData);
+ module_context::enqueClientEvent(event);
+}
+
+// wrap source_database::put for situations where there are new contents
+// (so we can index the contents)
+Error sourceDatabasePutWithUpdatedContents(
+ boost::shared_ptr<SourceDocument> pDoc)
+{
+ // write the file to the database
+ Error error = source_database::put(pDoc);
+ if (error)
+ return error ;
+
+ // update index
+ rSourceIndexes().update(pDoc);
+
+ return Success();
+}
+
+Error newDocument(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string type;
+ json::Value jsonContents;
+ json::Object properties;
+ Error error = json::readParams(request.params,
+ &type,
+ &jsonContents,
+ &properties);
+ if (error)
+ return error ;
+
+ // create the new doc and write it to the database
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument(type)) ;
+
+ if (json::isType<std::string>(jsonContents))
+ pDoc->setContents(jsonContents.get_str());
+
+ pDoc->editProperties(properties);
+
+ error = source_database::put(pDoc);
+ if (error)
+ return error;
+
+ // return the doc
+ json::Object jsonDoc;
+ writeDocToJson(pDoc, &jsonDoc);
+ pResponse->setResult(jsonDoc);
+ return Success();
+}
+
+Error openDocument(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error ;
+
+ std::string type;
+ error = json::readParam(request.params, 1, &type);
+ if (error)
+ return error ;
+
+ std::string encoding;
+ error = json::readParam(request.params, 2, &encoding);
+ if (error && error.code() != core::json::errc::ParamTypeMismatch)
+ return error ;
+ if (encoding.empty())
+ encoding = ::locale2charset(NULL);
+
+ // ensure the file exists
+ FilePath documentPath = module_context::resolveAliasedPath(path);
+ if (!documentPath.exists())
+ {
+ return systemError(boost::system::errc::no_such_file_or_directory,
+ ERROR_LOCATION);
+ }
+
+ // ensure the file is not binary
+ if (!module_context::isTextFile(documentPath))
+ {
+ Error error = systemError(boost::system::errc::illegal_byte_sequence,
+ ERROR_LOCATION);
+ pResponse->setError(error, "File is binary rather than text so cannot "
+ "be opened by the source editor.");
+ return Success();
+ }
+
+ // set the doc contents to the specified file
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument(type)) ;
+ pDoc->setEncoding(encoding);
+ error = pDoc->setPathAndContents(path, false);
+ if (error)
+ {
+ error = pDoc->setPathAndContents(path, true);
+ if (error)
+ return error ;
+
+ module_context::consoleWriteError(
+ "Not all characters in " + documentPath.absolutePath() +
+ " could be decoded using " + encoding + ". To try a "
+ "different encoding, choose \"File | Reopen with "
+ "Encoding...\" from the main menu.");
+ }
+
+ // recover durable properties if they are available
+ json::Object properties;
+ error = source_database::getDurableProperties(path, &properties);
+ if (!error)
+ pDoc->editProperties(properties);
+ else
+ LOG_ERROR(error);
+
+ // write to the source_database
+ error = sourceDatabasePutWithUpdatedContents(pDoc);
+ if (error)
+ return error;
+
+ // return the doc
+ json::Object jsonDoc;
+ writeDocToJson(pDoc, &jsonDoc);
+ pResponse->setResult(jsonDoc);
+ return Success();
+}
+
+Error saveDocumentCore(const std::string& contents,
+ const json::Value& jsonPath,
+ const json::Value& jsonType,
+ const json::Value& jsonEncoding,
+ const json::Value& jsonFoldSpec,
+ boost::shared_ptr<SourceDocument> pDoc)
+{
+ // check whether we have a path and if we do get/resolve its value
+ std::string path;
+ FilePath fullDocPath;
+ bool hasPath = json::isType<std::string>(jsonPath);
+ if (hasPath)
+ {
+ path = jsonPath.get_str();
+ fullDocPath = module_context::resolveAliasedPath(path);
+ }
+
+ // update dirty state: dirty if there was no path AND the new contents
+ // are different from the old contents (and was thus a content autosave
+ // as distinct from a fold-spec or scroll-position/selection autosave)
+ pDoc->setDirty(!hasPath && (contents != pDoc->contents()));
+
+ bool hasType = json::isType<std::string>(jsonType);
+ if (hasType)
+ {
+ pDoc->setType(jsonType.get_str());
+ }
+
+ Error error;
+
+ bool hasEncoding = json::isType<std::string>(jsonEncoding);
+ if (hasEncoding)
+ {
+ pDoc->setEncoding(jsonEncoding.get_str());
+ }
+
+ bool hasFoldSpec = json::isType<std::string>(jsonFoldSpec);
+ if (hasFoldSpec)
+ {
+ pDoc->setFolds(jsonFoldSpec.get_str());
+ }
+
+ // handle document (varies depending upon whether we have a path)
+ if (hasPath)
+ {
+ std::string encoded;
+ error = r::util::iconvstr(contents,
+ "UTF-8",
+ pDoc->encoding(),
+ false,
+ &encoded);
+ if (error)
+ {
+ error = r::util::iconvstr(contents,
+ "UTF-8",
+ pDoc->encoding(),
+ true,
+ &encoded);
+ if (error)
+ return error;
+
+
+ module_context::consoleWriteError(
+ "Not all of the characters in " + path +
+ " could be encoded using " + pDoc->encoding() +
+ ". To save using a different encoding, choose \"File | "
+ "Save with Encoding...\" from the main menu.");
+ }
+
+ // note whether the file existed prior to writing
+ bool newFile = !fullDocPath.exists();
+
+ // write the contents to the file
+ error = writeStringToFile(fullDocPath, encoded,
+ options().sourcePersistLineEnding());
+ if (error)
+ return error ;
+
+ // set the new path and contents for the document
+ error = pDoc->setPathAndContents(path);
+ if (error)
+ return error ;
+
+ // enque file changed event if we need to
+ if (!module_context::isDirectoryMonitored(fullDocPath.parent()))
+ {
+ using core::system::FileChangeEvent;
+ FileChangeEvent changeEvent(newFile ? FileChangeEvent::FileAdded :
+ FileChangeEvent::FileModified,
+ FileInfo(fullDocPath));
+ module_context::enqueFileChangedEvent(changeEvent);
+ }
+
+ // notify other server modules of the file save
+ module_context::events().onSourceEditorFileSaved(fullDocPath);
+
+ // save could change the extended type of the file so check it
+ detectExtendedType(pDoc);
+ }
+
+ // always update the contents so it holds the original UTF-8 data
+ pDoc->setContents(contents);
+
+ return Success();
+}
+
+Error saveDocument(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+
+{
+ // params
+ std::string id, contents;
+ json::Value jsonPath, jsonType, jsonEncoding, jsonFoldSpec;
+ Error error = json::readParams(request.params,
+ &id,
+ &jsonPath,
+ &jsonType,
+ &jsonEncoding,
+ &jsonFoldSpec,
+ &contents);
+ if (error)
+ return error ;
+
+ // get the doc
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ error = saveDocumentCore(contents, jsonPath, jsonType, jsonEncoding,
+ jsonFoldSpec, pDoc);
+ if (error)
+ return error;
+
+ // write to the source_database
+ error = sourceDatabasePutWithUpdatedContents(pDoc);
+ if (error)
+ return error;
+
+ // return the hash
+ pResponse->setResult(pDoc->hash());
+ return Success();
+}
+
+Error saveDocumentDiff(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ using namespace core::string_utils;
+
+ // unique id and jsonPath (can be null for auto-save)
+ std::string id;
+ json::Value jsonPath, jsonType, jsonEncoding, jsonFoldSpec;
+
+ // This is a chunk of text that should be inserted into the
+ // current document. It replaces the subrange [offset, offset+length).
+ std::string replacement;
+ int offset, length;
+
+ // This is the expected hash of the current document. If the
+ // current hash value is different than this value, then the
+ // document cannot be patched and the request should be discarded.
+ std::string hash;
+
+ // read params
+ Error error = json::readParams(request.params,
+ &id,
+ &jsonPath,
+ &jsonType,
+ &jsonEncoding,
+ &jsonFoldSpec,
+ &replacement,
+ &offset,
+ &length,
+ &hash);
+ if (error)
+ return error ;
+
+ // if this has no path then it is an autosave, in this case
+ // suppress change detection
+ bool hasPath = json::isType<std::string>(jsonPath);
+ if (!hasPath)
+ pResponse->setSuppressDetectChanges(true);
+
+ // get the doc
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ // Don't even attempt anything if we're not working off the same original
+ if (pDoc->hash() == hash)
+ {
+ std::string contents(pDoc->contents());
+
+ // Offset and length are specified in characters, but contents
+ // is in UTF8 bytes. Convert before using.
+ std::string::iterator rangeBegin = contents.begin();
+ error = utf8Advance(rangeBegin, offset, contents.end(), &rangeBegin);
+ if (error)
+ return Success(); // UTF8 decoding failed. Abort differential save.
+
+ std::string::iterator rangeEnd = rangeBegin;
+ error = utf8Advance(rangeEnd, length, contents.end(), &rangeEnd);
+ if (error)
+ return Success(); // UTF8 decoding failed. Abort differential save.
+
+ contents.erase(rangeBegin, rangeEnd);
+ contents.insert(rangeBegin, replacement.begin(), replacement.end());
+
+ error = saveDocumentCore(contents, jsonPath, jsonType, jsonEncoding,
+ jsonFoldSpec, pDoc);
+ if (error)
+ return error;
+
+ // write to the source_database
+ error = sourceDatabasePutWithUpdatedContents(pDoc);
+ if (error)
+ return error;
+
+ pResponse->setResult(pDoc->hash());
+ }
+
+ return Success();
+}
+
+Error checkForExternalEdit(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setSuppressDetectChanges(true);
+
+ // params
+ std::string id ;
+ Error error = json::readParams(request.params, &id);
+ if (error)
+ return error;
+
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument()) ;
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ json::Object result;
+ result["modified"] = false;
+ result["deleted"] = false;
+
+ // Only check if this document has ever been saved
+ if (!pDoc->path().empty())
+ {
+ FilePath docFile = module_context::resolveAliasedPath(pDoc->path());
+ if (!docFile.exists() || docFile.isDirectory())
+ {
+ result["deleted"] = true;
+
+ pDoc->setDirty(true);
+ error = source_database::put(pDoc);
+ if (error)
+ return error;
+ }
+ else
+ {
+ std::time_t lastWriteTime ;
+ pDoc->checkForExternalEdit(&lastWriteTime);
+
+ if (lastWriteTime)
+ {
+ FilePath filePath = module_context::resolveAliasedPath(pDoc->path()) ;
+ json::Object fsItem = module_context::createFileSystemItem(filePath);
+ result["item"] = fsItem;
+ result["modified"] = true;
+ }
+ }
+ }
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+namespace {
+
+Error reopen(std::string id, std::string fileType, std::string encoding,
+ json::JsonRpcResponse* pResponse)
+{
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument()) ;
+ Error error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ if (!encoding.empty())
+ pDoc->setEncoding(encoding);
+
+ if (!fileType.empty())
+ pDoc->setType(fileType);
+
+ error = pDoc->setPathAndContents(pDoc->path(), false);
+ if (error)
+ {
+ error = pDoc->setPathAndContents(pDoc->path(), true);
+ if (error)
+ return error ;
+
+ module_context::consoleWriteError(
+ "Not all characters in " + pDoc->path() +
+ " could be decoded using " + encoding + ".");
+ }
+ pDoc->setDirty(false);
+
+ // write to the source_database
+ error = sourceDatabasePutWithUpdatedContents(pDoc);
+ if (error)
+ return error;
+
+ json::Object resultObj;
+ writeDocToJson(pDoc, &resultObj);
+ pResponse->setResult(resultObj);
+
+ return Success();
+}
+
+} // anonymous namespace
+
+Error revertDocument(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string id, fileType ;
+ Error error = json::readParams(request.params, &id, &fileType) ;
+ if (error)
+ return error;
+
+ return reopen(id, fileType, std::string(), pResponse);
+}
+
+Error reopenWithEncoding(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string id, encoding;
+ Error error = json::readParams(request.params, &id, &encoding);
+ if (error)
+ return error;
+
+ return reopen(id, std::string(), encoding, pResponse);
+}
+
+Error ignoreExternalEdit(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string id ;
+ Error error = json::readParams(request.params, &id) ;
+ if (error)
+ return error;
+
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error;
+
+ pDoc->updateLastKnownWriteTime();
+
+ error = source_database::put(pDoc);
+ if (error)
+ return error;
+
+ return Success();
+}
+
+Error setSourceDocumentOnSave(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string id ;
+ bool value = false;
+ Error error = json::readParams(request.params, &id, &value);
+ if (error)
+ return error ;
+
+ // get the doc
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ // set source on save and then write it
+ pDoc->setSourceOnSave(value);
+ return source_database::put(pDoc);
+}
+
+
+Error modifyDocumentProperties(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string id ;
+ json::Object properties;
+ Error error = json::readParams(request.params, &id, &properties);
+ if (error)
+ return error ;
+
+ // get the doc
+ boost::shared_ptr<SourceDocument> pDoc(new SourceDocument());
+ error = source_database::get(id, pDoc);
+ if (error)
+ return error ;
+
+ // edit properties and write the document
+ pDoc->editProperties(properties);
+ return source_database::put(pDoc);
+}
+
+Error closeDocument(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string id ;
+ Error error = json::readParam(request.params, 0, &id);
+ if (error)
+ return error ;
+
+ error = source_database::remove(id);
+ if (error)
+ return error;
+
+ rSourceIndexes().remove(id);
+
+ return Success();
+}
+
+Error closeAllDocuments(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ Error error = source_database::removeAll();
+ if (error)
+ return error;
+
+ rSourceIndexes().removeAll();
+
+ return Success();
+}
+
+Error processSourceTemplate(const std::string& name,
+ const std::string& templateName,
+ std::string* pContents)
+{
+ // setup template filter
+ std::map<std::string,std::string> vars;
+ vars["name"] = name;
+ core::text::TemplateFilter filter(vars);
+
+ // read file with template filter
+ FilePath templatePath = session::options().rResourcesPath().complete(
+ "templates/" + templateName);
+ return core::readStringFromFile(templatePath,
+ filter,
+ pContents,
+ string_utils::LineEndingPosix);
+}
+
+Error getSourceTemplate(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string name, templateName;
+ Error error = json::readParams(request.params, &name, &templateName);
+ if (error)
+ return error;
+
+ std::string contents;
+ error = processSourceTemplate(name, templateName, &contents);
+ if (error)
+ return error;
+
+ pResponse->setResult(contents);
+ return Success();
+}
+
+Error defaultRdResponse(const std::string& name,
+ const std::string& type,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string filePath;
+ Error error = r::exec::RFunction(".rs.createDefaultShellRd", name, type)
+ .call(&filePath);
+ if (error)
+ return error;
+
+ std::string contents;
+ error = core::readStringFromFile(
+ FilePath(string_utils::systemToUtf8(filePath)),
+ &contents,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+
+ json::Object resultJson;
+ resultJson["path"] = json::Value();
+ resultJson["contents"] = contents;
+ pResponse->setResult(resultJson);
+ return Success();
+}
+
+
+Error createRdShell(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string name, type;
+ Error error = json::readParams(request.params, &name, &type);
+ if (error)
+ return error;
+
+ // suppress output so that R doesn't write the Rd message to the console
+ r::session::utils::SuppressOutputInScope suppressOutputScope;
+
+ // if we are within a package development environment then use that
+ // as the basis for the new document
+ if (projects::projectContext().config().buildType ==
+ r_util::kBuildTypePackage)
+ {
+ // read package info
+ FilePath packageDir = projects::projectContext().buildTargetPath();
+ r_util::RPackageInfo pkgInfo;
+ Error error = pkgInfo.read(packageDir);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return defaultRdResponse(name, type, pResponse);
+ }
+
+ // lookup the object in the package first
+ std::string filePath;
+ error = r::exec::RFunction(".rs.createShellRd",
+ name, type, pkgInfo.name()).call(&filePath);
+ if (error)
+ return error;
+
+ // if it was found then read it
+ if (!filePath.empty())
+ {
+ FilePath rdFilePath(string_utils::systemToUtf8(filePath));
+ FilePath manFilePath = packageDir.childPath("man").childPath(
+ rdFilePath.filename());
+ if (!manFilePath.exists())
+ {
+ Error error = rdFilePath.copy(manFilePath);
+ if (error)
+ return error;
+
+ json::Object resultJson;
+ resultJson["path"] = module_context::createAliasedPath(manFilePath);
+ resultJson["contents"] = json::Value();
+ pResponse->setResult(resultJson);
+ }
+ else
+ {
+ std::string contents;
+ error = core::readStringFromFile(rdFilePath,
+ &contents,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+ json::Object resultJson;
+ resultJson["path"] = json::Value();
+ resultJson["contents"] = contents;
+ pResponse->setResult(resultJson);
+ }
+
+ return Success();
+ }
+ else
+ {
+ return defaultRdResponse(name, type, pResponse);
+ }
+
+ }
+ else
+ {
+ return defaultRdResponse(name, type, pResponse);
+ }
+}
+
+Error isReadOnlyFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // params
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error ;
+ FilePath filePath = module_context::resolveAliasedPath(path);
+
+ pResponse->setResult(filePath.exists() &&
+ core::system::isReadOnly(filePath));
+
+ return Success();
+}
+
+void enqueFileEditEvent(const std::string& file)
+{
+ // ignore if no file passed
+ if (file.empty())
+ return;
+
+ // calculate full path
+ FilePath filePath = module_context::safeCurrentPath().complete(file);
+
+ // if it doesn't exist then create it
+ if (!filePath.exists())
+ {
+ Error error = core::writeStringToFile(filePath, "",
+ options().sourcePersistLineEnding());
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+ }
+
+ // fire event
+ json::Object fileJson = module_context::createFileSystemItem(filePath);
+ ClientEvent event(client_events::kFileEdit, fileJson);
+ module_context::enqueClientEvent(event);
+}
+
+void onSuspend(Settings*)
+{
+}
+
+// update the source database index on resume
+
+// TODO: a resume followed by a client_init will cause us to call
+// source_database::list twice (which will cause us to read all of
+// the files twice). find a way to prevent this.
+
+void onResume(const Settings&)
+{
+ rSourceIndexes().removeAll();
+
+ // get the docs and sort them by created
+ std::vector<boost::shared_ptr<SourceDocument> > docs ;
+ Error error = source_database::list(&docs);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+ std::sort(docs.begin(), docs.end(), sortByCreated);
+
+ // update the indexes
+ std::for_each(docs.begin(),
+ docs.end(),
+ boost::bind(&RSourceIndexes::update, &rSourceIndexes(), _1));
+}
+
+void onShutdown(bool terminatedNormally)
+{
+ FilePath activeDocumentFile =
+ module_context::resolveAliasedPath("~/.active-rstudio-document");
+ Error error = activeDocumentFile.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+}
+
+SEXP rs_fileEdit(SEXP fileSEXP)
+{
+ try
+ {
+ // read and validate file name (we ignore all other parameters)
+ if (!r::sexp::isString(fileSEXP))
+ throw r::exec::RErrorException("invalid filename specification");
+
+ // extract string vector
+ std::vector<std::string> filenames;
+ Error error = r::sexp::extract(fileSEXP, &filenames);
+ if (error)
+ throw r::exec::RErrorException(error.summary());
+
+ // fire events
+ std::for_each(filenames.begin(), filenames.end(), enqueFileEditEvent);
+
+ // done
+ return R_NilValue;
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // keep compiler happy
+ return R_NilValue;
+}
+
+} // anonymous namespace
+
+Error clientInitDocuments(core::json::Array* pJsonDocs)
+{
+ // remove all items from the source index database
+ rSourceIndexes().removeAll();
+
+ // get the docs and sort them by created
+ std::vector<boost::shared_ptr<SourceDocument> > docs ;
+ Error error = source_database::list(&docs);
+ if (error)
+ return error ;
+ std::sort(docs.begin(), docs.end(), sortByCreated);
+
+ // populate the array
+ pJsonDocs->clear();
+ BOOST_FOREACH( boost::shared_ptr<SourceDocument>& pDoc, docs )
+ {
+ // Force dirty state to be checked.
+ // Client and server dirty state can get out of sync because
+ // undo/redo on the client side can make dirty documents
+ // become clean again. I tried pushing the client dirty state
+ // back to the server but couldn't convince myself that I
+ // got all the edge cases. This approach is simpler--just
+ // compare the contents in the doc database to the contents
+ // on disk, and only do it when listing documents. However
+ // it does mean that reloading the client may cause a dirty
+ // document to become clean (if the contents are identical
+ // to what's on disk).
+ error = pDoc->updateDirty();
+ if (error)
+ LOG_ERROR(error);
+
+ json::Object jsonDoc ;
+ writeDocToJson(pDoc, &jsonDoc);
+ pJsonDocs->push_back(jsonDoc);
+
+ // update the source index
+ rSourceIndexes().update(pDoc);
+ }
+
+ return Success();
+}
+
+std::vector<boost::shared_ptr<core::r_util::RSourceIndex> > rIndexes()
+{
+ return rSourceIndexes().indexes();
+}
+
+Error initialize()
+{
+ // connect to events
+ using namespace module_context;
+ events().onShutdown.connect(onShutdown);
+
+ // add suspend/resume handler
+ addSuspendHandler(SuspendHandler(boost::bind(onSuspend, _2), onResume));
+
+ // register fileEdit method
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_fileEdit" ;
+ methodDef.fun = (DL_FUNC) rs_fileEdit ;
+ methodDef.numArgs = 1;
+ r::routines::addCallMethod(methodDef);
+
+ // install rpc methods
+ using boost::bind;
+ using namespace r::function_hook;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "new_document", newDocument))
+ (bind(registerRpcMethod, "open_document", openDocument))
+ (bind(registerRpcMethod, "save_document", saveDocument))
+ (bind(registerRpcMethod, "save_document_diff", saveDocumentDiff))
+ (bind(registerRpcMethod, "check_for_external_edit", checkForExternalEdit))
+ (bind(registerRpcMethod, "ignore_external_edit", ignoreExternalEdit))
+ (bind(registerRpcMethod, "set_source_document_on_save", setSourceDocumentOnSave))
+ (bind(registerRpcMethod, "modify_document_properties", modifyDocumentProperties))
+ (bind(registerRpcMethod, "revert_document", revertDocument))
+ (bind(registerRpcMethod, "reopen_with_encoding", reopenWithEncoding))
+ (bind(registerRpcMethod, "close_document", closeDocument))
+ (bind(registerRpcMethod, "close_all_documents", closeAllDocuments))
+ (bind(registerRpcMethod, "get_source_template", getSourceTemplate))
+ (bind(registerRpcMethod, "create_rd_shell", createRdShell))
+ (bind(registerRpcMethod, "is_read_only_file", isReadOnlyFile))
+ (bind(sourceModuleRFile, "SessionSource.R"));
+ Error error = initBlock.execute();
+ if (error)
+ return error;
+
+ // init source
+ error = r::exec::RFunction(".rs.initSource").call();
+ if (error)
+ LOG_ERROR(error);
+ return Success();
+}
+
+
+} // namespace source
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionSource.hpp b/src/cpp/session/modules/SessionSource.hpp
new file mode 100644
index 0000000..e22719b
--- /dev/null
+++ b/src/cpp/session/modules/SessionSource.hpp
@@ -0,0 +1,46 @@
+/*
+ * SessionSource.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SOURCE_HPP
+#define SESSION_SOURCE_HPP
+
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ namespace r_util {
+ class RSourceIndex;
+ }
+}
+
+namespace session {
+namespace modules {
+namespace source {
+
+core::Error clientInitDocuments(core::json::Array* pJsonDocs) ;
+
+std::vector<boost::shared_ptr<core::r_util::RSourceIndex> > rIndexes();
+
+core::Error initialize();
+
+} // namespace source
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SOURCE_HPP
diff --git a/src/cpp/session/modules/SessionSpelling.R b/src/cpp/session/modules/SessionSpelling.R
new file mode 100644
index 0000000..aac905c
--- /dev/null
+++ b/src/cpp/session/modules/SessionSpelling.R
@@ -0,0 +1,64 @@
+#
+# SessionSpelling.R
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+.rs.addFunction("downloadAllDictionaries", function(targetDir)
+{
+ # archive we are downloading
+ allDicts <- "all-dictionaries.zip"
+
+ # remove existing archive if necessary
+ allDictsTemp <- paste(tempdir(), allDicts, sep="/")
+ if (file.exists(allDictsTemp))
+ file.remove(allDictsTemp)
+
+ # download function
+ download <- function(protocol, method) {
+ download.file(paste(protocol,
+ "://s3.amazonaws.com/rstudio-dictionaries/",
+ allDicts, sep=""),
+ destfile=allDictsTemp,
+ method=method,
+ cacheOK = FALSE,
+ quiet = TRUE)
+ }
+
+ # system-specific download methods (to try to get https)
+ switch(Sys.info()[['sysname']],
+ Windows = { method <- "internal"},
+ Linux = { method <- "wget"},
+ Darwin = { method <- "curl"})
+
+ # try downloading using https, fallback to http if it fails for any reason
+ tryCatch(download("https", method),
+ error = function(e) download("http", "internal"))
+
+ # define function to remove the existing dictionaries then call it
+ removeExisting <- function() {
+ suppressWarnings({
+ file.remove(paste(targetDir, list.files(targetDir), sep="/"))
+ unlink(targetDir)
+ })
+ }
+ removeExisting()
+
+ # unzip downloaded dictionaires into target -- if this fails for any
+ # reason then remove any files that were unpacked (because we don't
+ # know if the partially unzipped archive is valid)
+ tryCatch(unzip(allDictsTemp, exdir=targetDir),
+ error = function(e) { removeExisting(); stop(e); })
+
+ # remove the archive
+ file.remove(allDictsTemp)
+})
diff --git a/src/cpp/session/modules/SessionSpelling.cpp b/src/cpp/session/modules/SessionSpelling.cpp
new file mode 100644
index 0000000..1fbe576
--- /dev/null
+++ b/src/cpp/session/modules/SessionSpelling.cpp
@@ -0,0 +1,329 @@
+/*
+ * SessionSpelling.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSpelling.hpp"
+
+#include <boost/shared_ptr.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <core/spelling/HunspellSpellingEngine.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RUtil.hpp>
+#include <r/RExec.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace spelling {
+
+namespace {
+
+// underlying spelling engine
+boost::scoped_ptr<core::spelling::SpellingEngine> s_pSpellingEngine;
+
+// R function for testing & debugging
+SEXP rs_checkSpelling(SEXP wordSEXP)
+{
+ bool isCorrect;
+ std::string word = r::sexp::asString(wordSEXP);
+
+ Error error = s_pSpellingEngine->checkSpelling(word, &isCorrect);
+
+ // We'll return true here so as not to tie up the front end.
+ if (error)
+ {
+ LOG_ERROR(error);
+ isCorrect = true;
+ }
+
+ r::sexp::Protect rProtect;
+ return r::sexp::create(isCorrect, &rProtect);
+}
+
+
+json::Object dictionaryAsJson(const core::spelling::HunspellDictionary& dict)
+{
+ json::Object dictJson;
+ dictJson["id"] = dict.id();
+ dictJson["name"] = dict.name();
+ return dictJson;
+}
+
+FilePath userDictionariesDir()
+{
+ return module_context::userScratchPath().childPath("dictionaries");
+}
+
+
+void syncSpellingEngineDictionaries()
+{
+ s_pSpellingEngine->useDictionary(userSettings().spellingLanguage());
+}
+
+
+core::spelling::HunspellDictionaryManager hunspellDictionaryManager()
+{
+ core::spelling::HunspellDictionaryManager dictManager(
+ options().hunspellDictionariesPath(),
+ userDictionariesDir());
+ return dictManager;
+}
+
+FilePath allLanguagesDir()
+{
+ return module_context::userScratchPath().childPath(
+ "dictionaries/languages-system");
+}
+
+
+Error checkSpelling(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array words;
+ Error error = json::readParams(request.params, &words);
+ if (error)
+ return error;
+
+ json::Array misspelledIndexes;
+ for (std::size_t i=0; i<words.size(); i++)
+ {
+ if (!json::isType<std::string>(words[i]))
+ {
+ BOOST_ASSERT(false);
+ continue;
+ }
+
+ std::string word = words[i].get_str();
+ bool isCorrect = true;
+ error = s_pSpellingEngine->checkSpelling(word, &isCorrect);
+ if (error)
+ return error;
+
+ if (!isCorrect)
+ misspelledIndexes.push_back(static_cast<int>(i));
+ }
+
+ pResponse->setResult(misspelledIndexes);
+
+ return Success();
+}
+
+Error suggestionList(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string word;
+ Error error = json::readParams(request.params, &word);
+ if (error)
+ return error;
+
+ std::vector<std::string> sugs;
+ error = s_pSpellingEngine->suggestionList(word, &sugs);
+ if (error)
+ return error;
+
+ json::Array sugsJson;
+ std::transform(sugs.begin(),
+ sugs.end(),
+ std::back_inserter(sugsJson),
+ json::toJsonString);
+ pResponse->setResult(sugsJson);
+
+ return Success();
+}
+
+Error getWordChars(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::wstring wordChars;
+ Error error = s_pSpellingEngine->wordChars(&wordChars);
+ if (error)
+ return error;
+
+ pResponse->setResult(string_utils::wideToUtf8(wordChars));
+
+ return Success();
+}
+
+
+
+Error addCustomDictionary(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the argument
+ std::string dict;
+ Error error = json::readParams(request.params, &dict);
+ if (error)
+ return error;
+ FilePath dictPath = module_context::resolveAliasedPath(dict);
+
+ // verify .dic extension
+ if (!dictPath.hasExtensionLowerCase(".dic"))
+ {
+ std::string msg = "Dictionary files must have a .dic extension";
+ Error error(json::errc::ParamInvalid, ERROR_LOCATION);
+ pResponse->setError(error, json::Value(msg));
+ return Success();
+ }
+
+ // perform the add
+ using namespace core::spelling;
+ HunspellDictionaryManager dictManager = hunspellDictionaryManager();
+ error = dictManager.custom().add(dictPath);
+ if (error)
+ return error;
+
+ // sync spelling engine
+ syncSpellingEngineDictionaries();
+
+ // return
+ pResponse->setResult(json::toJsonArray(dictManager.custom().dictionaries()));
+ return Success();
+}
+
+Error removeCustomDictionary(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the argument
+ std::string name;
+ Error error = json::readParams(request.params, &name);
+ if (error)
+ return error;
+
+ // perform the remove
+ using namespace core::spelling;
+ HunspellDictionaryManager dictManager = hunspellDictionaryManager();
+ error = dictManager.custom().remove(name);
+ if (error)
+ return error;
+
+ // sync spelling engine
+ syncSpellingEngineDictionaries();
+
+ // return
+ pResponse->setResult(json::toJsonArray(dictManager.custom().dictionaries()));
+ return Success();
+}
+
+Error installAllDictionaries(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // form system path to all languages dir
+ std::string targetDir = string_utils::utf8ToSystem(
+ allLanguagesDir().absolutePath());
+
+ // perform the download
+ r::exec::RFunction dlFunc(".rs.downloadAllDictionaries", targetDir);
+ Error error = dlFunc.call();
+ if (error)
+ {
+ std::string userMessage = r::endUserErrorMessage(error);
+ pResponse->setError(error, json::Value(userMessage));
+ return Success();
+ }
+ else
+ {
+ pResponse->setResult(spelling::spellingPrefsContextAsJson());
+ return Success();
+ }
+}
+
+// reset dictionary on user settings changed
+void onUserSettingsChanged()
+{
+ syncSpellingEngineDictionaries();
+}
+
+} // anonymous namespace
+
+
+core::json::Object spellingPrefsContextAsJson()
+{
+ using namespace core::spelling;
+
+ core::json::Object contextJson;
+
+ HunspellDictionaryManager dictManager = hunspellDictionaryManager();
+ std::vector<HunspellDictionary> dictionaries;
+ Error error = dictManager.availableLanguages(&dictionaries);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return core::json::Object();
+ }
+
+ core::json::Array dictionariesJson;
+ std::transform(dictionaries.begin(),
+ dictionaries.end(),
+ std::back_inserter(dictionariesJson),
+ dictionaryAsJson);
+
+
+ std::vector<std::string> customDicts = dictManager.custom().dictionaries();
+ core::json::Array customDictsJson = json::toJsonArray(customDicts);
+
+ // return json
+ contextJson["all_languages_installed"] = dictManager.allLanguagesInstalled();
+ contextJson["available_languages"] = dictionariesJson;
+ contextJson["custom_dictionaries"] = customDictsJson;
+ return contextJson;
+}
+
+Error initialize()
+{
+ R_CallMethodDef methodDef;
+ methodDef.name = "rs_checkSpelling" ;
+ methodDef.fun = (DL_FUNC) rs_checkSpelling ;
+ methodDef.numArgs = 1;
+ r::routines::addCallMethod(methodDef);
+
+ // initialize spelling engine
+ using namespace core::spelling;
+ HunspellSpellingEngine* pHunspell = new HunspellSpellingEngine(
+ userSettings().spellingLanguage(),
+ hunspellDictionaryManager(),
+ &r::util::iconvstr);
+ s_pSpellingEngine.reset(pHunspell);
+
+ // connect to user settings changed
+ userSettings().onChanged.connect(onUserSettingsChanged);
+
+ // register rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "check_spelling", checkSpelling))
+ (bind(registerRpcMethod, "suggestion_list", suggestionList))
+ (bind(registerRpcMethod, "get_word_chars", getWordChars))
+ (bind(registerRpcMethod, "add_custom_dictionary", addCustomDictionary))
+ (bind(registerRpcMethod, "remove_custom_dictionary", removeCustomDictionary))
+ (bind(registerRpcMethod, "install_all_dictionaries", installAllDictionaries))
+ (bind(sourceModuleRFile, "SessionSpelling.R"));
+ return initBlock.execute();
+}
+
+
+} // namespace spelling
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionSpelling.hpp b/src/cpp/session/modules/SessionSpelling.hpp
new file mode 100644
index 0000000..32da7ae
--- /dev/null
+++ b/src/cpp/session/modules/SessionSpelling.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionSpelling.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SPELLING_HPP
+#define SESSION_SPELLING_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace spelling {
+
+core::json::Object spellingPrefsContextAsJson();
+
+core::Error initialize();
+
+} // namespace spelling
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SPELLING_HPP
diff --git a/src/cpp/session/modules/SessionUpdates.R b/src/cpp/session/modules/SessionUpdates.R
new file mode 100644
index 0000000..9e2ddfc
--- /dev/null
+++ b/src/cpp/session/modules/SessionUpdates.R
@@ -0,0 +1,22 @@
+downloadUpdateInfo <- function(version, os, manual) {
+ updateUrl <- paste("http://www.rstudio.org/links/check_for_update",
+ "?version=", version,
+ "&os=", os,
+ "&format=kvp", sep = "")
+ if (isTRUE(manual))
+ {
+ updateUrl <- paste(updateUrl, "&manual=true", sep = "")
+ }
+
+ # Open the URL and read the result
+ conn <- url(updateUrl, open = "rt")
+ on.exit(close(conn), add = TRUE)
+ result <- readLines(conn, warn = FALSE)
+
+ # Print one key-value pair per line:
+ # key1=value1
+ # key2=value2
+ # ..
+ cat(sapply(unlist(strsplit(result, "&")), URLdecode), sep = "\n")
+}
+
diff --git a/src/cpp/session/modules/SessionUpdates.cpp b/src/cpp/session/modules/SessionUpdates.cpp
new file mode 100644
index 0000000..2e348d8
--- /dev/null
+++ b/src/cpp/session/modules/SessionUpdates.cpp
@@ -0,0 +1,155 @@
+/*
+ * SessionUpdates.cpp
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionUpdates.hpp"
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+
+#include <boost/bind.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include <string>
+
+#include "session-config.h"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace updates {
+namespace {
+
+json::Object jsonFromProcessResult(const core::system::ProcessResult& result)
+{
+ json::Object obj;
+ std::stringstream output(result.stdOut);
+ // The output looks like:
+ // key1=value1
+ // key2=value2
+ // ...
+ for (std::string line; std::getline(output, line); )
+ {
+ size_t pos = line.find('=');
+ if (pos > 0)
+ {
+ obj[line.substr(0, pos)] = line.substr(pos + 1,
+ line.length() - (pos + 1));
+ }
+ }
+ return obj;
+}
+
+void beginUpdateCheck(bool manual,
+ const boost::function<void(const core::system::ProcessResult&)>& onCompleted)
+{
+ // Find the path to R
+ FilePath rProgramPath;
+ Error error = module_context::rScriptPath(&rProgramPath);
+ if (error)
+ {
+ return;
+ }
+
+ // Find the path to the script we need to source
+ FilePath modulesPath = session::options().modulesRSourcePath();;
+ std::string scriptPath = core::string_utils::utf8ToSystem(
+ modulesPath.complete("SessionUpdates.R").absolutePath());
+
+ // Arguments
+ std::vector<std::string> args;
+ args.push_back("--slave");
+ args.push_back("--vanilla");
+#if defined(_WIN32)
+ if (userSettings().useInternet2())
+ {
+ args.push_back("--internet2");
+ }
+#endif
+ args.push_back("-e");
+
+ // Build the command to send to R
+ std::string cmd;
+ cmd.append("source('");
+ cmd.append(string_utils::jsLiteralEscape(scriptPath));
+ cmd.append("'); downloadUpdateInfo('");
+ cmd.append(RSTUDIO_VERSION);
+ cmd.append("', '");
+#if defined(_WIN32)
+ cmd.append("windows");
+#elif defined(__APPLE__)
+ cmd.append("mac");
+#else
+ cmd.append("linux");
+#endif
+ cmd.append("', ");
+ cmd.append(manual ? "TRUE" : "FALSE");
+ cmd.append(")");
+ args.push_back(cmd);
+
+ // Set options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+
+ module_context::processSupervisor().runProgram(rProgramPath.absolutePath(),
+ args,
+ std::string(),
+ options,
+ onCompleted);
+}
+
+void endRPCUpdateCheck(const json::JsonRpcFunctionContinuation& cont,
+ const core::system::ProcessResult& result)
+{
+ json::JsonRpcResponse response;
+ response.setResult(jsonFromProcessResult(result));
+ cont(Success(), &response);
+}
+
+void checkForUpdates(const json::JsonRpcRequest& request,
+ const json::JsonRpcFunctionContinuation& cont)
+{
+ bool manual = false;
+ Error error = json::readParam(request.params, 0, &manual);
+ if (error)
+ {
+ json::JsonRpcResponse response;
+ cont(error, &response);
+ return;
+ }
+ beginUpdateCheck(manual, boost::bind(endRPCUpdateCheck, cont, _1));
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace module_context;
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (bind(registerAsyncRpcMethod, "check_for_updates", checkForUpdates))
+ ;
+ return initBlock.execute();
+}
+
+} // namespace updates
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/SessionUpdates.hpp b/src/cpp/session/modules/SessionUpdates.hpp
new file mode 100644
index 0000000..15a0beb
--- /dev/null
+++ b/src/cpp/session/modules/SessionUpdates.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionUpdates.hpp
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_UPDATES_HPP
+#define SESSION_UPDATES_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace updates {
+
+core::Error initialize();
+
+} // namespace updates
+} // namespace modules
+} // namespace session
+
+#endif
\ No newline at end of file
diff --git a/src/cpp/session/modules/SessionVCS.cpp b/src/cpp/session/modules/SessionVCS.cpp
new file mode 100644
index 0000000..50abbb4
--- /dev/null
+++ b/src/cpp/session/modules/SessionVCS.cpp
@@ -0,0 +1,311 @@
+/*
+ * SessionVCS.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionVCS.hpp"
+
+#include <boost/foreach.hpp>
+
+#include <core/Exec.hpp>
+#include <core/StringUtils.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "vcs/SessionVCSUtils.hpp"
+
+#include "SessionSVN.hpp"
+#include "SessionGit.hpp"
+
+#include "SessionAskPass.hpp"
+#include "SessionConsoleProcess.hpp"
+
+#include "session-config.h"
+
+#ifdef RSTUDIO_SERVER
+#include <core/system/Crypto.hpp>
+#endif
+
+using namespace core;
+
+namespace session {
+
+namespace {
+ const char * const kVcsIdNone = "none";
+} // anonymous namespace
+
+namespace module_context {
+
+// if we change the name of one of the VCS systems then there will
+// be persisted versions of the name on disk we need to deal with
+// migrating. This function can do that migration -- note the initial
+// default implementation is to return "none" for unrecognized options
+std::string normalizeVcsOverride(const std::string& vcsOverride)
+{
+ if (vcsOverride == modules::git::kVcsId)
+ return vcsOverride;
+ else if (vcsOverride == modules::svn::kVcsId)
+ return vcsOverride;
+ else if (vcsOverride == kVcsIdNone)
+ return vcsOverride;
+ else
+ return "";
+}
+
+} // namespace module_context
+
+namespace modules {
+namespace source_control {
+
+namespace {
+
+Error vcsClone(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string vcsName;
+ std::string url;
+ std::string username;
+ std::string dirName;
+ std::string parentDir;
+ Error error = json::readObjectParam(request.params, 0,
+ "vcs_name", &vcsName,
+ "repo_url", &url,
+ "username", &username,
+ "directory_name", &dirName,
+ "parent_path", &parentDir);
+ if (error)
+ return error;
+
+ ask_pass::setActiveWindow(request.sourceWindow);
+
+ FilePath parentPath = module_context::resolveAliasedPath(parentDir);
+
+ boost::shared_ptr<console_process::ConsoleProcess> pCP;
+ if (vcsName == git::kVcsId)
+ {
+ Error error = git::clone(url,
+ dirName,
+ parentPath,
+ &pCP);
+ if (error)
+ return error;
+ }
+ else if (vcsName == svn::kVcsId)
+ {
+ Error error = svn::checkout(url,
+ username,
+ dirName,
+ parentPath,
+ &pCP);
+ if (error)
+ return error;
+ }
+ else
+ {
+ return systemError(json::errc::ParamInvalid, ERROR_LOCATION);
+ }
+
+ pResponse->setResult(pCP->toJson());
+
+ return Success();
+}
+
+class NullFileDecorationContext : public FileDecorationContext
+{
+ void decorateFile(const FilePath&, json::Object*)
+ {
+ }
+};
+
+} // anonymous namespace
+
+boost::shared_ptr<FileDecorationContext> fileDecorationContext(
+ const core::FilePath& rootDir)
+{
+ if (git::isGitEnabled())
+ {
+ return boost::shared_ptr<FileDecorationContext>(
+ new git::GitFileDecorationContext(rootDir));
+ }
+ else if (svn::isSvnEnabled())
+ {
+ return boost::shared_ptr<FileDecorationContext>(
+ new svn::SvnFileDecorationContext(rootDir));
+ }
+ else
+ {
+ return boost::shared_ptr<FileDecorationContext>(
+ new NullFileDecorationContext());
+ }
+}
+
+VCS activeVCS()
+{
+ return git::isGitEnabled() ? VCSGit : VCSNone;
+}
+
+std::string activeVCSName()
+{
+ if (git::isGitEnabled())
+ return git::kVcsId;
+ else if (svn::isSvnEnabled())
+ return svn::kVcsId;
+ else
+ return std::string();
+}
+
+bool isGitInstalled()
+{
+ return git::isGitInstalled();
+}
+
+bool isSvnInstalled()
+{
+ return svn::isSvnInstalled();
+}
+
+FilePath getTrueHomeDir()
+{
+#if _WIN32
+ // On Windows, R's idea of "$HOME" is not, by default, the same as
+ // $USERPROFILE, which is what we want for ssh purposes
+ return FilePath(string_utils::systemToUtf8(core::system::getenv("USERPROFILE")));
+#else
+ return FilePath(string_utils::systemToUtf8(core::system::getenv("HOME")));
+#endif
+}
+
+FilePath defaultSshKeyDir()
+{
+ return getTrueHomeDir().childPath(".ssh");
+}
+
+void enqueueRefreshEvent()
+{
+ vcs_utils::enqueueRefreshEvent();
+}
+
+
+
+core::Error initialize()
+{
+ git::initialize();
+ svn::initialize();
+
+ // http endpoints
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "vcs_clone", vcsClone));
+ Error error = initBlock.execute();
+ if (error)
+ return error;
+
+ // If VCS is disabled, or we're not in a project, do nothing
+ const projects::ProjectContext& projContext = projects::projectContext();
+ FilePath workingDir = projContext.directory();
+
+ if (!session::options().allowVcs() || !userSettings().vcsEnabled() || workingDir.empty())
+ return Success();
+
+
+ // If Git or SVN was explicitly specified, choose it if valid
+ projects::RProjectVcsOptions vcsOptions;
+ if (projContext.hasProject())
+ {
+ Error vcsError = projContext.readVcsOptions(&vcsOptions);
+ if (vcsError)
+ LOG_ERROR(vcsError);
+ }
+
+ if (vcsOptions.vcsOverride == kVcsIdNone)
+ {
+ return Success();
+ }
+ else if (vcsOptions.vcsOverride == git::kVcsId)
+ {
+ if (git::isGitInstalled() && git::isGitDirectory(workingDir))
+ return git::initializeGit(workingDir);
+ return Success();
+ }
+ else if (vcsOptions.vcsOverride == svn::kVcsId)
+ {
+ if (svn::isSvnInstalled() && svn::isSvnDirectory(workingDir))
+ return svn::initializeSvn(workingDir);
+ return Success();
+ }
+
+ if (git::isGitInstalled() && git::isGitDirectory(workingDir))
+ {
+ return git::initializeGit(workingDir);
+ }
+ else if (svn::isSvnInstalled() && svn::isSvnDirectory(workingDir))
+ {
+ return svn::initializeSvn(workingDir);
+ }
+ else
+ {
+ return Success(); // none specified or detected
+ }
+}
+
+} // namespace source_control
+} // namespace modules
+} // namespace session
+
+namespace session {
+namespace module_context {
+
+VcsContext vcsContext(const FilePath& workingDir)
+{
+ using namespace session::modules;
+ using namespace session::modules::source_control;
+
+ // inspect current vcs state (underlying functions execute child
+ // processes so we want to be sure to only call them once)
+ bool gitInstalled = isGitInstalled();
+ bool isGitDirectory = gitInstalled && git::isGitDirectory(workingDir);
+ bool svnInstalled = isSvnInstalled();
+ bool isSvnDirectory = svnInstalled && svn::isSvnDirectory(workingDir);
+
+ // detected vcs
+ VcsContext context;
+ if (isGitDirectory)
+ context.detectedVcs = git::kVcsId;
+ else if (isSvnDirectory)
+ context.detectedVcs = svn::kVcsId;
+ else
+ context.detectedVcs = kVcsIdNone;
+
+ // applicable vcs
+ if (gitInstalled)
+ context.applicableVcs.push_back(git::kVcsId);
+ if (isSvnDirectory)
+ context.applicableVcs.push_back(svn::kVcsId);
+
+ // remote urls
+ if (isGitDirectory)
+ context.gitRemoteOriginUrl = git::remoteOriginUrl(workingDir);
+ if (isSvnDirectory)
+ context.svnRepositoryRoot = svn::repositoryRoot(workingDir);
+
+ return context;
+}
+
+} // namespace module_context
+} // namespace session
diff --git a/src/cpp/session/modules/SessionVCS.hpp b/src/cpp/session/modules/SessionVCS.hpp
new file mode 100644
index 0000000..0dab9be
--- /dev/null
+++ b/src/cpp/session/modules/SessionVCS.hpp
@@ -0,0 +1,64 @@
+/*
+ * SessionVCS.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_VCS_HPP
+#define SESSION_VCS_HPP
+
+#include <boost/shared_ptr.hpp>
+
+#include <core/json/Json.hpp>
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <session/SessionUserSettings.hpp>
+
+#include "vcs/SessionVCSCore.hpp"
+
+#include "SessionGit.hpp"
+
+namespace session {
+namespace modules {
+namespace source_control {
+
+enum VCS
+{
+ VCSNone,
+ VCSGit,
+ VCSSubversion
+};
+
+boost::shared_ptr<FileDecorationContext> fileDecorationContext(
+ const core::FilePath& rootDir);
+
+VCS activeVCS();
+std::string activeVCSName();
+bool isGitInstalled();
+bool isSvnInstalled();
+
+// default directory for reading/writing ssh keys
+core::FilePath defaultSshKeyDir();
+
+void enqueueRefreshEvent();
+
+core::Error fileStatus(const core::FilePath& filePath,
+ source_control::VCSStatus* pStatus);
+
+core::Error initialize();
+
+} // namespace source_control
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_VCS_HPP
diff --git a/src/cpp/session/modules/SessionViewer.cpp b/src/cpp/session/modules/SessionViewer.cpp
new file mode 100644
index 0000000..7ca0f92
--- /dev/null
+++ b/src/cpp/session/modules/SessionViewer.cpp
@@ -0,0 +1,178 @@
+/*
+ * SessionViewer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionViewer.hpp"
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RUtil.hpp>
+#include <r/ROptions.hpp>
+
+#include <r/session/RSessionUtils.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace viewer {
+
+namespace {
+
+// track the current viewed url
+std::string s_currentUrl;
+
+// viewer stopped means clear the url
+Error viewerStopped(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ s_currentUrl.clear();
+ return Success();
+}
+
+void viewerNavigate(const std::string& url, int height = 0)
+{
+ // record the url (for reloads)
+ s_currentUrl = module_context::mapUrlPorts(url);
+
+ // enque the event
+ json::Object dataJson;
+ dataJson["url"] = s_currentUrl;
+ dataJson["height"] = height;
+ ClientEvent event(client_events::kViewerNavigate, dataJson);
+ module_context::enqueClientEvent(event);
+}
+
+SEXP rs_viewer(SEXP urlSEXP, SEXP heightSEXP)
+{
+ try
+ {
+ // get the height parameter (0 if null)
+ int height = 0;
+ if (!r::sexp::isNull(heightSEXP))
+ height = r::sexp::asInteger(heightSEXP);
+
+ // transform the url to a localhost:<port>/session one if it's
+ // a path to a file within the R session temporary directory
+ std::string url = r::sexp::safeAsString(urlSEXP);
+ if (!boost::algorithm::starts_with(url, "http"))
+ {
+ // get the path to the tempdir and the file
+ FilePath tempDir = r::session::utils::tempDir();
+ FilePath filePath = module_context::resolveAliasedPath(url);
+
+ // if it's in the temp dir and we're running R >= 2.14 then
+ // we can serve it via the help server, otherwise we need
+ // to show it in an external browser
+ if (filePath.isWithin(tempDir) && r::util::hasRequiredVersion("2.14"))
+ {
+ std::string path = filePath.relativePath(tempDir);
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ boost::format fmt("http://localhost:%1%/session/%2%");
+ url = boost::str(fmt % module_context::rLocalHelpPort() % path);
+ }
+ else
+ {
+ boost::format fmt("session/%1%");
+ url = boost::str(fmt % path);
+ }
+ viewerNavigate(url, height);
+ }
+ else
+ {
+ module_context::showFile(filePath);
+ }
+ }
+ else
+ {
+ // in desktop mode make sure we have the right version of httpuv
+ if (options().programMode() == kSessionProgramModeDesktop)
+ {
+ if (!module_context::isPackageVersionInstalled("httpuv", "1.2"))
+ {
+ module_context::consoleWriteError("\nWARNING: To run "
+ "applications within the RStudio Viewer pane you need to "
+ "install the latest version of the httpuv package from "
+ "CRAN (version 1.2 or higher is required).\n\n");
+ }
+ }
+
+ // navigate the viewer
+ viewerNavigate(url, height);
+ }
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ return R_NilValue;
+}
+
+void onSuspend(const r::session::RSuspendOptions&, Settings*)
+{
+}
+
+void onResume(const Settings&)
+{
+ viewerNavigate("", false);
+}
+
+void onClientInit()
+{
+ if (!s_currentUrl.empty())
+ viewerNavigate(s_currentUrl);
+}
+
+} // anonymous namespace
+
+Error initialize()
+{
+ R_CallMethodDef methodDefViewer ;
+ methodDefViewer.name = "rs_viewer" ;
+ methodDefViewer.fun = (DL_FUNC) rs_viewer ;
+ methodDefViewer.numArgs = 2;
+ r::routines::addCallMethod(methodDefViewer);
+
+ // install event handlers
+ using namespace module_context;
+ events().onClientInit.connect(onClientInit);
+ addSuspendHandler(SuspendHandler(onSuspend, onResume));
+
+ // set ggvis.renderer to svg in desktop mode
+ if ((session::options().programMode() == kSessionProgramModeDesktop) &&
+ r::options::getOption<std::string>("ggvis.renderer", "", false).empty())
+ {
+ r::options::setOption("ggvis.renderer", "svg");
+ }
+
+ // install rpc methods
+ using boost::bind;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "viewer_stopped", viewerStopped));
+ return initBlock.execute();
+}
+
+
+} // namespace viewer
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionViewer.hpp b/src/cpp/session/modules/SessionViewer.hpp
new file mode 100644
index 0000000..342f4ba
--- /dev/null
+++ b/src/cpp/session/modules/SessionViewer.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionViewer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_VIEWER_HPP
+#define SESSION_VIEWER_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace viewer {
+
+core::Error initialize();
+
+} // namespace viewer
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_VIEWER_HPP
diff --git a/src/cpp/session/modules/SessionWorkbench.cpp b/src/cpp/session/modules/SessionWorkbench.cpp
new file mode 100644
index 0000000..7320a3e
--- /dev/null
+++ b/src/cpp/session/modules/SessionWorkbench.cpp
@@ -0,0 +1,832 @@
+/*
+ * SessionWorkbench.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SessionWorkbench.hpp"
+
+#include <algorithm>
+
+#include <boost/function.hpp>
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/StringUtils.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/system/Environment.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <r/ROptions.hpp>
+#include <r/session/RSession.hpp>
+#include <r/session/RClientState.hpp>
+#include <r/RFunctionHook.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+
+#include "SessionVCS.hpp"
+#include "SessionGit.hpp"
+#include "SessionSVN.hpp"
+
+#include "SessionConsoleProcess.hpp"
+#include "SessionSpelling.hpp"
+
+#include <R_ext/RStartup.h>
+extern "C" SA_TYPE SaveAction;
+
+#include "session-config.h"
+#ifdef RSTUDIO_SERVER
+#include <core/system/Crypto.hpp>
+#endif
+
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace workbench {
+
+namespace {
+
+Error setClientState(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setSuppressDetectChanges(true);
+
+ // extract params
+ json::Object temporaryState, persistentState, projPersistentState;
+ Error error = json::readParams(request.params,
+ &temporaryState,
+ &persistentState,
+ &projPersistentState);
+ if (error)
+ return error ;
+
+ // set state
+ r::session::ClientState& clientState = r::session::clientState();
+ clientState.putTemporary(temporaryState);
+ clientState.putPersistent(persistentState);
+ clientState.putProjectPersistent(projPersistentState);
+
+ return Success();
+}
+
+
+// IN: WorkbenchMetrics object
+// OUT: Void
+Error setWorkbenchMetrics(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // extract fields
+ r::session::RClientMetrics metrics ;
+ Error error = json::readObjectParam(request.params, 0,
+ "consoleWidth", &(metrics.consoleWidth),
+ "graphicsWidth", &(metrics.graphicsWidth),
+ "graphicsHeight", &(metrics.graphicsHeight));
+ if (error)
+ return error;
+
+ // set the metrics
+ r::session::setClientMetrics(metrics);
+
+ return Success();
+}
+
+CRANMirror toCRANMirror(const json::Object& cranMirrorJson)
+{
+ CRANMirror cranMirror;
+ json::readObject(cranMirrorJson,
+ "name", &cranMirror.name,
+ "host", &cranMirror.host,
+ "url", &cranMirror.url,
+ "country", &cranMirror.country);
+ return cranMirror;
+}
+
+/* Call to this is commented out below
+BioconductorMirror toBioconductorMirror(const json::Object& mirrorJson)
+{
+ BioconductorMirror mirror;
+ json::readObject(mirrorJson,
+ "name", &mirror.name,
+ "url", &mirror.url);
+ return mirror;
+}
+*/
+
+// try to detect a terminal on linux desktop
+FilePath detectedTerminalPath()
+{
+#if defined(_WIN32) || defined(__APPLE__)
+ return FilePath();
+#else
+ if (session::options().programMode() == kSessionProgramModeDesktop)
+ {
+ std::vector<FilePath> terminalPaths;
+ terminalPaths.push_back(FilePath("/usr/bin/gnome-terminal"));
+ terminalPaths.push_back(FilePath("/usr/bin/konsole"));
+ terminalPaths.push_back(FilePath("/usr/bin/xfce4-terminal"));
+ terminalPaths.push_back(FilePath("/usr/bin/xterm"));
+
+ BOOST_FOREACH(const FilePath& terminalPath, terminalPaths)
+ {
+ if (terminalPath.exists())
+ return terminalPath;
+ }
+
+ return FilePath();
+ }
+ else
+ {
+ return FilePath();
+ }
+#endif
+}
+
+Error setPrefs(const json::JsonRpcRequest& request, json::JsonRpcResponse*)
+{
+ // read params
+ json::Object generalPrefs, historyPrefs, packagesPrefs, projectsPrefs,
+ sourceControlPrefs, compilePdfPrefs;
+ Error error = json::readObjectParam(request.params, 0,
+ "general_prefs", &generalPrefs,
+ "history_prefs", &historyPrefs,
+ "packages_prefs", &packagesPrefs,
+ "projects_prefs", &projectsPrefs,
+ "source_control_prefs", &sourceControlPrefs,
+ "compile_pdf_prefs", &compilePdfPrefs);
+ if (error)
+ return error;
+ json::Object uiPrefs;
+ error = json::readParam(request.params, 1, &uiPrefs);
+ if (error)
+ return error;
+
+
+ // read and set general prefs
+ int saveAction;
+ bool loadRData, rProfileOnResume;
+ std::string initialWorkingDir;
+ error = json::readObject(generalPrefs,
+ "save_action", &saveAction,
+ "load_rdata", &loadRData,
+ "rprofile_on_resume", &rProfileOnResume,
+ "initial_working_dir", &initialWorkingDir);
+ if (error)
+ return error;
+
+ userSettings().beginUpdate();
+ userSettings().setSaveAction(saveAction);
+ userSettings().setLoadRData(loadRData);
+ userSettings().setRprofileOnResume(rProfileOnResume);
+ userSettings().setInitialWorkingDirectory(FilePath(initialWorkingDir));
+ userSettings().endUpdate();
+
+ // sync underlying R save action
+ module_context::syncRSaveAction();
+
+ // read and set history prefs
+ bool alwaysSave, removeDuplicates;
+ error = json::readObject(historyPrefs,
+ "always_save", &alwaysSave,
+ "remove_duplicates", &removeDuplicates);
+ if (error)
+ return error;
+ userSettings().beginUpdate();
+ userSettings().setAlwaysSaveHistory(alwaysSave);
+ userSettings().setRemoveHistoryDuplicates(removeDuplicates);
+ userSettings().endUpdate();
+
+ // read and set packages prefs
+ bool useInternet2, cleanupAfterCheckSuccess, viewDirAfterCheckFailure;
+ bool hideObjectFiles;
+ json::Object cranMirrorJson;
+ error = json::readObject(packagesPrefs,
+ "cran_mirror", &cranMirrorJson,
+ "use_internet2", &useInternet2,
+/* see note on bioconductor below
+ "bioconductor_mirror", &bioconductorMirrorJson);
+*/
+ "cleanup_after_check_success", &cleanupAfterCheckSuccess,
+ "viewdir_after_check_failure", &viewDirAfterCheckFailure,
+ "hide_object_files", &hideObjectFiles);
+
+ if (error)
+ return error;
+ userSettings().beginUpdate();
+ userSettings().setCRANMirror(toCRANMirror(cranMirrorJson));
+ userSettings().setUseInternet2(useInternet2);
+ userSettings().setCleanupAfterRCmdCheck(cleanupAfterCheckSuccess);
+ userSettings().setHideObjectFiles(hideObjectFiles);
+ userSettings().setViewDirAfterRCmdCheck(viewDirAfterCheckFailure);
+
+ // NOTE: currently there is no UI for bioconductor mirror so we
+ // don't want to set it (would have side effect of overwriting
+ // user-specified BioC_Mirror option)
+ /*
+ userSettings().setBioconductorMirror(toBioconductorMirror(
+ bioconductorMirrorJson));
+ */
+ userSettings().endUpdate();
+
+
+ // read and set projects prefs
+ bool restoreLastProject;
+ error = json::readObject(projectsPrefs,
+ "restore_last_project", &restoreLastProject);
+ if (error)
+ return error;
+ userSettings().beginUpdate();
+ userSettings().setAlwaysRestoreLastProject(restoreLastProject);
+ userSettings().endUpdate();
+
+ // read and set source control prefs
+ bool vcsEnabled, useGitBash;
+ std::string gitExe, svnExe, terminalPath;
+ error = json::readObject(sourceControlPrefs,
+ "vcs_enabled", &vcsEnabled,
+ "git_exe_path", &gitExe,
+ "svn_exe_path", &svnExe,
+ "terminal_path", &terminalPath,
+ "use_git_bash", &useGitBash);
+ if (error)
+ return error;
+ userSettings().beginUpdate();
+ userSettings().setVcsEnabled(vcsEnabled);
+
+ FilePath gitExePath(gitExe);
+ if (gitExePath == git::detectedGitExePath())
+ userSettings().setGitExePath(FilePath());
+ else
+ userSettings().setGitExePath(gitExePath);
+
+ FilePath svnExePath(svnExe);
+ if (svnExePath == svn::detectedSvnExePath())
+ userSettings().setSvnExePath(FilePath());
+ else
+ userSettings().setSvnExePath(svnExePath);
+
+ FilePath terminalFilePath(terminalPath);
+ if (terminalFilePath == detectedTerminalPath())
+ userSettings().setVcsTerminalPath(FilePath());
+ else
+ userSettings().setVcsTerminalPath(terminalFilePath);
+
+ userSettings().setVcsUseGitBash(useGitBash);
+
+ userSettings().endUpdate();
+
+
+ // read and update compile pdf prefs
+ bool cleanOutput, enableShellEscape;
+ error = json::readObject(compilePdfPrefs,
+ "clean_output", &cleanOutput,
+ "enable_shell_escape", &enableShellEscape);
+ if (error)
+ return error;
+ userSettings().beginUpdate();
+ userSettings().setCleanTexi2DviOutput(cleanOutput);
+ userSettings().setEnableLaTeXShellEscape(enableShellEscape);
+ userSettings().endUpdate();
+
+ // set ui prefs
+ userSettings().setUiPrefs(uiPrefs);
+
+ return Success();
+}
+
+
+Error setUiPrefs(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object uiPrefs;
+ Error error = json::readParams(request.params, &uiPrefs);
+ if (error)
+ return error;
+
+ userSettings().setUiPrefs(uiPrefs);
+
+ return Success();
+}
+
+
+json::Object toCRANMirrorJson(const CRANMirror& cranMirror)
+{
+ json::Object cranMirrorJson;
+ cranMirrorJson["name"] = cranMirror.name;
+ cranMirrorJson["host"] = cranMirror.host;
+ cranMirrorJson["url"] = cranMirror.url;
+ cranMirrorJson["country"] = cranMirror.country;
+ return cranMirrorJson;
+}
+
+json::Object toBioconductorMirrorJson(
+ const BioconductorMirror& bioconductorMirror)
+{
+ json::Object bioconductorMirrorJson;
+ bioconductorMirrorJson["name"] = bioconductorMirror.name;
+ bioconductorMirrorJson["url"] = bioconductorMirror.url;
+ return bioconductorMirrorJson;
+}
+
+
+Error getRPrefs(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get general prefs
+ json::Object generalPrefs;
+ generalPrefs["save_action"] = userSettings().saveAction();
+ generalPrefs["load_rdata"] = userSettings().loadRData();
+ generalPrefs["rprofile_on_resume"] = userSettings().rProfileOnResume();
+ generalPrefs["initial_working_dir"] = module_context::createAliasedPath(
+ userSettings().initialWorkingDirectory());
+
+ // get history prefs
+ json::Object historyPrefs;
+ historyPrefs["always_save"] = userSettings().alwaysSaveHistory();
+ historyPrefs["remove_duplicates"] = userSettings().removeHistoryDuplicates();
+
+ // get packages prefs
+ json::Object packagesPrefs;
+ packagesPrefs["cran_mirror"] = toCRANMirrorJson(
+ userSettings().cranMirror());
+ packagesPrefs["use_internet2"] = userSettings().useInternet2();
+ packagesPrefs["bioconductor_mirror"] = toBioconductorMirrorJson(
+ userSettings().bioconductorMirror());
+ packagesPrefs["cleanup_after_check_success"] = userSettings().cleanupAfterRCmdCheck();
+ packagesPrefs["viewdir_after_check_failure"] = userSettings().viewDirAfterRCmdCheck();
+ packagesPrefs["hide_object_files"] = userSettings().hideObjectFiles();
+
+ // get projects prefs
+ json::Object projectsPrefs;
+ projectsPrefs["restore_last_project"] = userSettings().alwaysRestoreLastProject();
+
+ // get source control prefs
+ json::Object sourceControlPrefs;
+ sourceControlPrefs["vcs_enabled"] = userSettings().vcsEnabled();
+ FilePath gitExePath = userSettings().gitExePath();
+ if (gitExePath.empty())
+ gitExePath = git::detectedGitExePath();
+ sourceControlPrefs["git_exe_path"] = gitExePath.absolutePath();
+
+ FilePath svnExePath = userSettings().svnExePath();
+ if (svnExePath.empty())
+ svnExePath = svn::detectedSvnExePath();
+ sourceControlPrefs["svn_exe_path"] = svnExePath.absolutePath();
+
+ FilePath terminalPath = userSettings().vcsTerminalPath();
+ if (terminalPath.empty())
+ terminalPath = detectedTerminalPath();
+ sourceControlPrefs["terminal_path"] = terminalPath.absolutePath();
+
+ sourceControlPrefs["use_git_bash"] = userSettings().vcsUseGitBash();
+
+ FilePath sshKeyDir = modules::source_control::defaultSshKeyDir();
+ FilePath rsaSshKeyPath = sshKeyDir.childPath("id_rsa");
+ sourceControlPrefs["rsa_key_path"] =
+ module_context::createAliasedPath(rsaSshKeyPath);
+ sourceControlPrefs["have_rsa_key"] = rsaSshKeyPath.exists();
+
+
+ // get compile pdf prefs
+ json::Object compilePdfPrefs;
+ compilePdfPrefs["clean_output"] = userSettings().cleanTexi2DviOutput();
+ compilePdfPrefs["enable_shell_escape"] = userSettings().enableLaTeXShellEscape();
+
+ // initialize and set result object
+ json::Object result;
+ result["general_prefs"] = generalPrefs;
+ result["history_prefs"] = historyPrefs;
+ result["packages_prefs"] = packagesPrefs;
+ result["projects_prefs"] = projectsPrefs;
+ result["source_control_prefs"] = sourceControlPrefs;
+ result["compile_pdf_prefs"] = compilePdfPrefs;
+ result["spelling_prefs_context"] =
+ session::modules::spelling::spellingPrefsContextAsJson();
+
+ pResponse->setResult(result);
+
+ return Success();
+}
+
+template <typename T>
+void ammendShellPaths(T* pTarget)
+{
+ // non-path git bin dir
+ std::string gitBinDir = git::nonPathGitBinDir();
+ if (!gitBinDir.empty())
+ core::system::addToPath(pTarget, gitBinDir);
+
+ // non-path svn bin dir
+ std::string svnBinDir = svn::nonPathSvnBinDir();
+ if (!svnBinDir.empty())
+ core::system::addToPath(pTarget, svnBinDir);
+
+ // msys_ssh path
+ core::system::addToPath(pTarget,
+ session::options().msysSshPath().absolutePath());
+}
+
+Error getTerminalOptions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object optionsJson;
+
+ FilePath terminalPath;
+
+#if defined(_WIN32)
+
+ // if we are using git bash then return its path
+ if (git::isGitEnabled() && userSettings().vcsUseGitBash())
+ {
+ FilePath gitExePath = git::detectedGitExePath();
+ if (!gitExePath.empty())
+ terminalPath = gitExePath.parent().childPath("sh.exe");
+ }
+
+#elif defined(__APPLE__)
+
+ // do nothing (we always launch Terminal.app)
+
+#else
+
+ // auto-detection (+ overridable by a setting)
+ terminalPath = userSettings().vcsTerminalPath();
+ if (terminalPath.empty())
+ terminalPath = detectedTerminalPath();
+
+#endif
+
+ // append shell paths as appropriate
+ std::string extraPathEntries;
+ ammendShellPaths(&extraPathEntries);
+
+ optionsJson["terminal_path"] = terminalPath.absolutePath();
+ optionsJson["working_directory"] =
+ module_context::shellWorkingDirectory().absolutePath();
+ optionsJson["extra_path_entries"] = extraPathEntries;
+ pResponse->setResult(optionsJson);
+
+ return Success();
+}
+
+Error createSshKey(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string path, type, passphrase;
+ bool overwrite;
+ Error error = json::readObjectParam(request.params, 0,
+ "path", &path,
+ "type", &type,
+ "passphrase", &passphrase,
+ "overwrite", &overwrite);
+ if (error)
+ return error;
+
+#ifdef RSTUDIO_SERVER
+ // In server mode, passphrases are encrypted
+ using namespace core::system::crypto;
+ error = rsaPrivateDecrypt(passphrase, &passphrase);
+ if (error)
+ return error;
+#endif
+
+ // resolve key path
+ FilePath sshKeyPath = module_context::resolveAliasedPath(path);
+ FilePath sshPublicKeyPath = sshKeyPath.parent().complete(
+ sshKeyPath.stem() + ".pub");
+ if (sshKeyPath.exists() || sshPublicKeyPath.exists())
+ {
+ if (!overwrite)
+ {
+ json::Object resultJson;
+ resultJson["failed_key_exists"] = true;
+ pResponse->setResult(resultJson);
+ return Success();
+ }
+ else
+ {
+ Error error = sshKeyPath.removeIfExists();
+ if (error)
+ return error;
+ error = sshPublicKeyPath.removeIfExists();
+ if (error)
+ return error;
+ }
+ }
+
+ // compose a shell command to create the key
+ shell_utils::ShellCommand cmd("ssh-keygen");
+
+ // type
+ cmd << "-t" << type;
+
+ // passphrase (optional)
+ cmd << "-N";
+ if (!passphrase.empty())
+ cmd << passphrase;
+ else
+ cmd << std::string("");
+
+ // path
+ cmd << "-f" << sshKeyPath;
+
+ // process options
+ core::system::ProcessOptions options;
+
+ // detach the session so there is no terminal
+#ifndef _WIN32
+ options.detachSession = true;
+#endif
+
+ // customize the environment on Win32
+#ifdef _WIN32
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+
+ // set HOME to USERPROFILE
+ std::string userProfile = core::system::getenv(childEnv, "USERPROFILE");
+ core::system::setenv(&childEnv, "HOME", userProfile);
+
+ // add msys_ssh to path
+ core::system::addToPath(&childEnv,
+ session::options().msysSshPath().absolutePath());
+
+ options.environment = childEnv;
+#endif
+
+ // run it
+ core::system::ProcessResult result;
+ error = runCommand(shell_utils::sendStdErrToStdOut(cmd),
+ options,
+ &result);
+ if (error)
+ return error;
+
+ // return exit code and output
+ json::Object resultJson;
+ resultJson["failed_key_exists"] = false;
+ resultJson["exit_status"] = result.exitStatus;
+ resultJson["output"] = result.stdOut;
+ pResponse->setResult(resultJson);
+ return Success();
+}
+
+
+
+// path edit file postback script (provided as GIT_EDITOR and SVN_EDITOR)
+std::string s_editFileCommand;
+
+// function we can call to wait for edit_completed
+module_context::WaitForMethodFunction s_waitForEditCompleted;
+
+// edit file postback handler
+void editFilePostback(const std::string& file,
+ const module_context::PostbackHandlerContinuation& cont)
+{
+ // read file contents
+ FilePath filePath(file);
+ std::string fileContents;
+ Error error = core::readStringFromFile(filePath, &fileContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ cont(EXIT_FAILURE, "");
+ return;
+ }
+
+ // prepare edit event
+ ClientEvent editEvent = session::showEditorEvent(fileContents, false, true);
+
+ // wait for edit_completed
+ json::JsonRpcRequest request ;
+ bool succeeded = s_waitForEditCompleted(&request, editEvent);
+
+ // cancelled or otherwise didn't succeed
+ if (!succeeded || request.params[0].is_null())
+ {
+ cont(EXIT_FAILURE, "");
+ return;
+ }
+
+ // extract the content
+ std::string editedFileContents;
+ error = json::readParam(request.params, 0, &editedFileContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ cont(EXIT_FAILURE, "");
+ return;
+ }
+
+ // write the content back to the file
+ error = core::writeStringToFile(filePath, editedFileContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ cont(EXIT_FAILURE, "");
+ return;
+ }
+
+ // success
+ cont(EXIT_SUCCESS, "");
+}
+
+Error startShellDialog(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+#ifndef _WIN32
+ using namespace session::module_context;
+ using namespace session::modules::console_process;
+
+ // configure environment for shell
+ core::system::Options shellEnv;
+ core::system::environment(&shellEnv);
+
+ // set dumb terminal
+ core::system::setenv(&shellEnv, "TERM", "dumb");
+
+ // set prompt
+ std::string path = module_context::createAliasedPath(
+ module_context::safeCurrentPath());
+ std::string prompt = (path.length() > 30) ? "\\W$ " : "\\w$ ";
+ core::system::setenv(&shellEnv, "PS1", prompt);
+
+ // disable screen oriented facillites
+ core::system::unsetenv(&shellEnv, "EDITOR");
+ core::system::unsetenv(&shellEnv, "VISUAL");
+ core::system::setenv(&shellEnv, "PAGER", "/bin/cat");
+
+ core::system::setenv(&shellEnv, "GIT_EDITOR", s_editFileCommand);
+ core::system::setenv(&shellEnv, "SVN_EDITOR", s_editFileCommand);
+
+ // ammend shell paths as appropriate
+ ammendShellPaths(&shellEnv);
+
+ // set options
+ core::system::ProcessOptions options;
+ options.workingDir = module_context::shellWorkingDirectory();
+ options.environment = shellEnv;
+
+ // configure bash command
+ core::shell_utils::ShellCommand bashCommand("/bin/bash");
+ bashCommand << "--norc";
+
+ // run process
+ boost::shared_ptr<ConsoleProcess> ptrProc =
+ ConsoleProcess::create(bashCommand,
+ options,
+ "Shell",
+ true,
+ InteractionAlways,
+ console_process::kDefaultMaxOutputLines);
+
+ ptrProc->onExit().connect(boost::bind(
+ &source_control::enqueueRefreshEvent));
+
+ pResponse->setResult(ptrProc->toJson());
+
+ return Success();
+#else // not supported on Win32
+ return Error(json::errc::InvalidRequest, ERROR_LOCATION);
+#endif
+}
+
+Error setCRANMirror(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object cranMirrorJson;
+ Error error = json::readParam(request.params, 0, &cranMirrorJson);
+ if (error)
+ return error;
+ CRANMirror cranMirror = toCRANMirror(cranMirrorJson);
+
+ userSettings().beginUpdate();
+ userSettings().setCRANMirror(cranMirror);
+ userSettings().endUpdate();
+
+ return Success();
+}
+
+
+// options("pdfviewer")
+void viewPdfPostback(const std::string& pdfPath,
+ const module_context::PostbackHandlerContinuation& cont)
+{
+ module_context::showFile(FilePath(pdfPath));
+ cont(EXIT_SUCCESS, "");
+}
+
+
+void handleFileShow(const http::Request& request, http::Response* pResponse)
+{
+ // get the file path
+ FilePath filePath(request.queryParamValue("path"));
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::NotFound, "File not found");
+ return;
+ }
+
+ // send it back
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableFile(filePath, request);
+}
+
+void onUserSettingsChanged()
+{
+ // sync underlying R save action
+ module_context::syncRSaveAction();
+
+ // fire event notifying the client that uiPrefs changed
+ json::Object dataJson;
+ dataJson["type"] = "global";
+ dataJson["prefs"] = userSettings().uiPrefs();
+ ClientEvent event(client_events::kUiPrefsChanged, dataJson);
+ module_context::enqueClientEvent(event);
+}
+
+} // anonymous namespace
+
+std::string editFileCommand()
+{
+ // NOTE: only registered for server mode
+ return s_editFileCommand;
+}
+
+Error initialize()
+{
+ // register for change notifications on user settings
+ userSettings().onChanged.connect(onUserSettingsChanged);
+
+ // register postback handler for viewPDF (server-only)
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ std::string pdfShellCommand ;
+ Error error = module_context::registerPostbackHandler("pdfviewer",
+ viewPdfPostback,
+ &pdfShellCommand);
+ if (error)
+ return error ;
+
+ // set pdfviewer option
+ error = r::options::setOption("pdfviewer", pdfShellCommand);
+ if (error)
+ return error ;
+
+
+ // register editfile handler and save its path
+ error = module_context::registerPostbackHandler("editfile",
+ editFilePostback,
+ &s_editFileCommand);
+ if (error)
+ return error;
+
+ // register edit_completed waitForMethod handler
+ s_waitForEditCompleted = module_context::registerWaitForMethod(
+ "edit_completed");
+ }
+
+ // complete initialization
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerUriHandler, "/file_show", handleFileShow))
+ (bind(registerRpcMethod, "set_client_state", setClientState))
+ (bind(registerRpcMethod, "set_workbench_metrics", setWorkbenchMetrics))
+ (bind(registerRpcMethod, "set_prefs", setPrefs))
+ (bind(registerRpcMethod, "set_ui_prefs", setUiPrefs))
+ (bind(registerRpcMethod, "get_r_prefs", getRPrefs))
+ (bind(registerRpcMethod, "set_cran_mirror", setCRANMirror))
+ (bind(registerRpcMethod, "get_terminal_options", getTerminalOptions))
+ (bind(registerRpcMethod, "create_ssh_key", createSshKey))
+ (bind(registerRpcMethod, "start_shell_dialog", startShellDialog));
+ return initBlock.execute();
+}
+
+
+} // namepsace workbench
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/SessionWorkbench.hpp b/src/cpp/session/modules/SessionWorkbench.hpp
new file mode 100644
index 0000000..1497d4f
--- /dev/null
+++ b/src/cpp/session/modules/SessionWorkbench.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionWorkbench.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_WORKBENCH_HPP
+#define SESSION_SESSION_WORKBENCH_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace workbench {
+
+std::string editFileCommand();
+
+core::Error initialize();
+
+} // namespace workbench
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SESSION_WORKBENCH_HPP
diff --git a/src/cpp/session/modules/build/SessionBuild.cpp b/src/cpp/session/modules/build/SessionBuild.cpp
new file mode 100644
index 0000000..efe4975
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuild.cpp
@@ -0,0 +1,1665 @@
+/*
+ * SessionBuild.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionBuild.hpp"
+
+#include <vector>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/format.hpp>
+#include <boost/scope_exit.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/text/DcfParser.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+#include <core/system/ShellUtils.hpp>
+#include <core/r_util/RPackageInfo.hpp>
+
+
+#ifdef _WIN32
+#include <core/r_util/RToolsInfo.hpp>
+#endif
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/session/RSessionUtils.hpp>
+#include <r/session/RConsoleHistory.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionBuildEnvironment.hpp"
+#include "SessionBuildErrors.hpp"
+#include "SessionBuildUtils.hpp"
+#include "SessionSourceCpp.hpp"
+
+using namespace core;
+
+namespace session {
+
+namespace {
+
+std::string quoteString(const std::string& str)
+{
+ return "'" + str + "'";
+}
+
+std::string packageArgsVector(std::string args)
+{
+ // spilt the string
+ boost::algorithm::trim(args);
+ std::vector<std::string> argList;
+ boost::algorithm::split(argList,
+ args,
+ boost::is_space(),
+ boost::algorithm::token_compress_on);
+
+ // quote the args
+ std::vector<std::string> quotedArgs;
+ std::transform(argList.begin(),
+ argList.end(),
+ std::back_inserter(quotedArgs),
+ quoteString);
+
+ std::ostringstream ostr;
+ ostr << "c(" << boost::algorithm::join(quotedArgs, ",") << ")";
+ return ostr.str();
+}
+
+shell_utils::ShellCommand buildRCmd(const core::FilePath& rBinDir)
+{
+#if defined(_WIN32)
+ shell_utils::ShellCommand rCmd(rBinDir.childPath("Rcmd.exe"));
+#else
+ shell_utils::ShellCommand rCmd(rBinDir.childPath("R"));
+ rCmd << "CMD";
+#endif
+ return rCmd;
+}
+
+
+bool isPackageBuildError(const std::string& output)
+{
+ std::string input = boost::algorithm::trim_copy(output);
+ return boost::algorithm::istarts_with(input, "warning: ") ||
+ boost::algorithm::istarts_with(input, "error: ") ||
+ boost::algorithm::ends_with(input, "WARNING");
+}
+
+// R command invocation -- has two representations, one to be submitted
+// (shellCmd_) and one to show the user (cmdString_)
+class RCommand
+{
+public:
+ explicit RCommand(const FilePath& rBinDir)
+ : shellCmd_(buildRCmd(rBinDir))
+ {
+#ifdef _WIN32
+ cmdString_ = "Rcmd.exe";
+#else
+ cmdString_ = "R CMD";
+#endif
+
+ // set escape mode to files-only. this is so that when we
+ // add the group of extra arguments from the user that we
+ // don't put quotes around it.
+ shellCmd_ << shell_utils::EscapeFilesOnly;
+ }
+
+ RCommand& operator<<(const std::string& arg)
+ {
+ if (!arg.empty())
+ {
+ cmdString_ += " " + arg;
+ shellCmd_ << arg;
+ }
+ return *this;
+ }
+
+ RCommand& operator<<(const FilePath& arg)
+ {
+ cmdString_ += " " + arg.absolutePath();
+ shellCmd_ << arg;
+ return *this;
+ }
+
+
+ const std::string& commandString() const
+ {
+ return cmdString_;
+ }
+
+ const shell_utils::ShellCommand& shellCommand() const
+ {
+ return shellCmd_;
+ }
+
+private:
+ std::string cmdString_;
+ shell_utils::ShellCommand shellCmd_;
+};
+
+} // anonymous namespace
+
+namespace modules {
+namespace build {
+
+namespace {
+
+// track whether to force a package rebuild. we do this if the user
+// saves a header file (since the R CMD INSTALL makefile doesn't
+// force a rebuild for those changes)
+bool s_forcePackageRebuild = false;
+
+bool isPackageHeaderFile(const FilePath& filePath)
+{
+ if (projects::projectContext().hasProject() &&
+ (projects::projectContext().config().buildType ==
+ r_util::kBuildTypePackage) &&
+ boost::algorithm::starts_with(filePath.extensionLowerCase(), ".h"))
+ {
+ FilePath pkgPath = projects::projectContext().buildTargetPath();
+ std::string pkgRelative = filePath.relativePath(pkgPath);
+ if (boost::algorithm::starts_with(pkgRelative, "src"))
+ return true;
+ else if (boost::algorithm::starts_with(pkgRelative, "inst/include"))
+ return true;
+ }
+
+ return false;
+}
+
+void onFileChanged(FilePath sourceFilePath)
+{
+ if (!s_forcePackageRebuild)
+ {
+ if (isPackageHeaderFile(sourceFilePath))
+ s_forcePackageRebuild = true;
+ }
+}
+
+void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events)
+{
+ if (!s_forcePackageRebuild)
+ {
+ for (size_t i=0; i<events.size(); i++)
+ {
+ FilePath filePath(events[i].fileInfo().absolutePath());
+ onFileChanged(filePath);
+ }
+ }
+}
+
+bool collectForcePackageRebuild()
+{
+ if (s_forcePackageRebuild)
+ {
+ s_forcePackageRebuild = false;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+const char * const kRoxygenizePackage = "roxygenize-package";
+const char * const kBuildSourcePackage = "build-source-package";
+const char * const kBuildBinaryPackage = "build-binary-package";
+const char * const kTestPackage = "test-package";
+const char * const kCheckPackage = "check-package";
+const char * const kBuildAndReload = "build-all";
+const char * const kRebuildAll = "rebuild-all";
+
+class Build : boost::noncopyable,
+ public boost::enable_shared_from_this<Build>
+{
+public:
+ static boost::shared_ptr<Build> create(const std::string& type)
+ {
+ boost::shared_ptr<Build> pBuild(new Build());
+ pBuild->start(type);
+ return pBuild;
+ }
+
+private:
+ Build()
+ : isRunning_(false), terminationRequested_(false), restartR_(false)
+ {
+ }
+
+ void start(const std::string& type)
+ {
+ ClientEvent event(client_events::kBuildStarted);
+ module_context::enqueClientEvent(event);
+
+ isRunning_ = true;
+
+ // read build options
+ Error error = projects::projectContext().readBuildOptions(&options_);
+ if (error)
+ {
+ terminateWithError("reading build options file", error);
+ return;
+ }
+
+ // callbacks
+ core::system::ProcessCallbacks cb;
+ cb.onContinue = boost::bind(&Build::onContinue,
+ Build::shared_from_this());
+ cb.onStdout = boost::bind(&Build::onStandardOutput,
+ Build::shared_from_this(), _2);
+ cb.onStderr = boost::bind(&Build::onStandardError,
+ Build::shared_from_this(), _2);
+ cb.onExit = boost::bind(&Build::onCompleted,
+ Build::shared_from_this(),
+ _1);
+
+ // execute build
+ executeBuild(type, cb);
+ }
+
+
+ void executeBuild(const std::string& type,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+
+ FilePath buildTargetPath = projects::projectContext().buildTargetPath();
+ const core::r_util::RProjectConfig& config = projectConfig();
+ if (config.buildType == r_util::kBuildTypePackage)
+ {
+ options.workingDir = buildTargetPath.parent();
+ executePackageBuild(type, buildTargetPath, options, cb);
+ }
+ else if (config.buildType == r_util::kBuildTypeMakefile)
+ {
+ options.workingDir = buildTargetPath;
+ executeMakefileBuild(type, buildTargetPath, options, cb);
+ }
+ else if (config.buildType == r_util::kBuildTypeCustom)
+ {
+ options.workingDir = buildTargetPath.parent();
+ executeCustomBuild(type, buildTargetPath, options, cb);
+ }
+ else
+ {
+ terminateWithError("Unrecognized build type: " + config.buildType);
+ }
+ }
+
+ void executePackageBuild(const std::string& type,
+ const FilePath& packagePath,
+ const core::system::ProcessOptions& options,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // validate that this is a package
+ if (!r_util::isPackageDirectory(packagePath))
+ {
+ boost::format fmt ("ERROR: The build directory does "
+ "not contain a DESCRIPTION\n"
+ "file so cannot be built as a package.\n\n"
+ "Build directory: %1%\n");
+ terminateWithError(boost::str(
+ fmt % module_context::createAliasedPath(packagePath)));
+ return;
+ }
+
+ // get package info
+ Error error = pkgInfo_.read(packagePath);
+ if (error)
+ {
+ terminateWithError("Reading package DESCRIPTION", error);
+ return;
+ }
+
+ // if this package links to Rcpp then we run compileAttributes
+ if (pkgInfo_.linkingTo().find("Rcpp") != std::string::npos)
+ if (!compileRcppAttributes(packagePath))
+ return;
+
+ if (type == kRoxygenizePackage)
+ {
+ successMessage_ = "Documentation completed";
+ roxygenize(packagePath, options, cb);
+ }
+ else
+ {
+ // bind a function that can be used to build the package
+ boost::function<void()> buildFunction = boost::bind(
+ &Build::buildPackage, Build::shared_from_this(),
+ type, packagePath, options, cb);
+
+ if (roxygenizeRequired(type))
+ {
+ // special callback for roxygenize result
+ core::system::ProcessCallbacks roxygenizeCb = cb;
+ roxygenizeCb.onExit = boost::bind(&Build::onRoxygenizeCompleted,
+ Build::shared_from_this(),
+ _1,
+ buildFunction);
+
+ // run it
+ roxygenize(packagePath, options, roxygenizeCb);
+ }
+ else
+ {
+ buildFunction();
+ }
+ }
+ }
+
+ bool roxygenizeRequired(const std::string& type)
+ {
+ if (!projectConfig().packageRoxygenize.empty())
+ {
+ if ((type == kBuildAndReload || type == kRebuildAll) &&
+ options_.autoRoxygenizeForBuildAndReload)
+ {
+ return true;
+ }
+ else if ( (type == kBuildSourcePackage ||
+ type == kBuildBinaryPackage) &&
+ options_.autoRoxygenizeForBuildPackage)
+ {
+ return true;
+ }
+ else if ( (type == kCheckPackage) &&
+ options_.autoRoxygenizeForCheck &&
+ !useDevtools())
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ std::string buildRoxygenizeCall()
+ {
+ // build the call to roxygenize
+ std::vector<std::string> roclets;
+ boost::algorithm::split(roclets,
+ projectConfig().packageRoxygenize,
+ boost::algorithm::is_any_of(","));
+ BOOST_FOREACH(std::string& roclet, roclets)
+ {
+ roclet = "'" + roclet + "'";
+ }
+
+ boost::format fmt;
+ if (useDevtools())
+ fmt = boost::format("devtools::document(roclets=c(%1%))");
+ else
+ fmt = boost::format("roxygen2::roxygenize('.', roclets=c(%1%))");
+ std::string roxygenizeCall = boost::str(
+ fmt % boost::algorithm::join(roclets, ", "));
+
+ // show the user the call to roxygenize
+ enqueCommandString(roxygenizeCall);
+
+ // format the command to send to R
+ boost::format cmdFmt(
+ "suppressPackageStartupMessages("
+ "{oldLC <- Sys.getlocale(category = 'LC_COLLATE'); "
+ " Sys.setlocale(category = 'LC_COLLATE', locale = 'C'); "
+ " on.exit(Sys.setlocale(category = 'LC_COLLATE', locale = oldLC));"
+ " %1%; }"
+ ")");
+ return boost::str(cmdFmt % roxygenizeCall);
+ }
+
+ void onRoxygenizeCompleted(int exitStatus,
+ const boost::function<void()>& buildFunction)
+ {
+ if (exitStatus == EXIT_SUCCESS)
+ {
+ std::string msg = "Documentation completed\n\n";
+ enqueBuildOutput(kBuildOutputNormal, msg);
+ buildFunction();
+ }
+ else
+ {
+ terminateWithErrorStatus(exitStatus);
+ }
+ }
+
+
+ void roxygenize(const FilePath& packagePath,
+ core::system::ProcessOptions options,
+ const core::system::ProcessCallbacks& cb)
+ {
+ FilePath rScriptPath;
+ Error error = module_context::rScriptPath(&rScriptPath);
+ if (error)
+ {
+ terminateWithError("Locating R script", error);
+ return;
+ }
+
+ // build the roxygenize command
+ shell_utils::ShellCommand cmd(rScriptPath);
+ cmd << "--slave";
+ cmd << "--vanilla";
+ cmd << "-e";
+ cmd << buildRoxygenizeCall();
+
+ // use the package working dir
+ options.workingDir = packagePath;
+
+ // run it
+ module_context::processSupervisor().runCommand(cmd,
+ options,
+ cb);
+ }
+
+ bool compileRcppAttributes(const FilePath& packagePath)
+ {
+ if (module_context::haveRcppAttributes())
+ {
+ core::system::ProcessResult result;
+ Error error = module_context::sourceModuleRFileWithResult(
+ "SessionCompileAttributes.R",
+ packagePath,
+ &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ enqueCommandString("Rcpp::compileAttributes()");
+ terminateWithError(r::endUserErrorMessage(error));
+ return false;
+ }
+ else if (!result.stdOut.empty() || !result.stdErr.empty())
+ {
+ enqueCommandString("Rcpp::compileAttributes()");
+ enqueBuildOutput(kBuildOutputNormal, result.stdOut);
+ if (!result.stdErr.empty())
+ enqueBuildOutput(kBuildOutputError, result.stdErr);
+ enqueBuildOutput(kBuildOutputNormal, "\n");
+ return result.exitStatus == EXIT_SUCCESS;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ void buildPackage(const std::string& type,
+ const FilePath& packagePath,
+ const core::system::ProcessOptions& options,
+ const core::system::ProcessCallbacks& cb)
+ {
+
+ // if this action is going to INSTALL the package then on
+ // windows we need to unload the library first
+#ifdef _WIN32
+ if (packagePath.childPath("src").exists() &&
+ (type == kBuildAndReload || type == kRebuildAll ||
+ type == kBuildBinaryPackage))
+ {
+ std::string pkg = pkgInfo_.name();
+ Error error = r::exec::RFunction(".rs.forceUnloadPackage", pkg).call();
+ if (error)
+ LOG_ERROR(error);
+ }
+#endif
+
+ // use both the R and gcc error parsers
+ CompileErrorParsers parsers;
+ parsers.add(rErrorParser(packagePath.complete("R")));
+ parsers.add(gccErrorParser(packagePath.complete("src")));
+ initErrorParser(packagePath, parsers);
+
+ // make a copy of options so we can customize the environment
+ core::system::ProcessOptions pkgOptions(options);
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+
+ // allow child process to inherit our R_LIBS
+ std::string libPaths = module_context::libPathsString();
+ if (!libPaths.empty())
+ core::system::setenv(&childEnv, "R_LIBS", libPaths);
+
+ // prevent spurious cygwin warnings on windows
+#ifdef _WIN32
+ core::system::setenv(&childEnv, "CYGWIN", "nodosfilewarning");
+#endif
+
+ // set the not cran env var
+ core::system::setenv(&childEnv, "NOT_CRAN", "true");
+
+ // add r tools to path if necessary
+ addRtoolsToPathIfNecessary(&childEnv, &postBuildWarning_);
+
+ pkgOptions.environment = childEnv;
+
+ // get R bin directory
+ FilePath rBinDir;
+ Error error = module_context::rBinDir(&rBinDir);
+ if (error)
+ {
+ terminateWithError("attempting to locate R binary", error);
+ return;
+ }
+
+ // install an error filter (because R package builds produce much
+ // of their output on stderr)
+ errorOutputFilterFunction_ = isPackageBuildError;
+
+ // build command
+ if (type == kBuildAndReload || type == kRebuildAll)
+ {
+ // restart R after build is completed
+ restartR_ = true;
+
+ // build command
+ RCommand rCmd(rBinDir);
+ rCmd << "INSTALL";
+
+ // get extra args
+ std::string extraArgs = projectConfig().packageInstallArgs;
+
+ // add --preclean if this is a rebuild all
+ if (collectForcePackageRebuild() || (type == kRebuildAll))
+ {
+ if (!boost::algorithm::contains(extraArgs, "--preclean"))
+ rCmd << "--preclean";
+ }
+
+ // add extra args if provided
+ rCmd << extraArgs;
+
+ // add filename as a FilePath so it is escaped
+ rCmd << FilePath(packagePath.filename());
+
+ // show the user the command
+ enqueCommandString(rCmd.commandString());
+
+ // run R CMD INSTALL <package-dir>
+ module_context::processSupervisor().runCommand(rCmd.shellCommand(),
+ pkgOptions,
+ cb);
+ }
+
+ else if (type == kBuildSourcePackage)
+ {
+ if (useDevtools())
+ devtoolsBuildPackage(packagePath, false, pkgOptions, cb);
+ else
+ buildSourcePackage(rBinDir, packagePath, pkgOptions, cb);
+ }
+
+ else if (type == kBuildBinaryPackage)
+ {
+ if (useDevtools())
+ devtoolsBuildPackage(packagePath, true, pkgOptions, cb);
+ else
+ buildBinaryPackage(rBinDir, packagePath, pkgOptions, cb);
+ }
+
+ else if (type == kCheckPackage)
+ {
+ if (useDevtools())
+ devtoolsCheckPackage(packagePath, pkgOptions, cb);
+ else
+ checkPackage(rBinDir, packagePath, pkgOptions, cb);
+ }
+
+ else if (type == kTestPackage)
+ {
+ devtoolsTestPackage(packagePath, pkgOptions, cb);
+ }
+ }
+
+
+ void buildSourcePackage(const FilePath& rBinDir,
+ const FilePath& packagePath,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // compose the build command
+ RCommand rCmd(rBinDir);
+ rCmd << "build";
+
+ // add extra args if provided
+ std::string extraArgs = projectConfig().packageBuildArgs;
+ rCmd << extraArgs;
+
+ // add filename as a FilePath so it is escaped
+ rCmd << FilePath(packagePath.filename());
+
+ // show the user the command
+ enqueCommandString(rCmd.commandString());
+
+ // set a success message
+ successMessage_ = buildPackageSuccessMsg("Source");
+
+ // run R CMD build <package-dir>
+ module_context::processSupervisor().runCommand(rCmd.shellCommand(),
+ pkgOptions,
+ cb);
+
+ }
+
+
+ void buildBinaryPackage(const FilePath& rBinDir,
+ const FilePath& packagePath,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // compose the INSTALL --binary
+ RCommand rCmd(rBinDir);
+ rCmd << "INSTALL";
+ rCmd << "--build";
+ rCmd << "--preclean";
+
+ // add extra args if provided
+ std::string extraArgs = projectConfig().packageBuildBinaryArgs;
+ rCmd << extraArgs;
+
+ // add filename as a FilePath so it is escaped
+ rCmd << FilePath(packagePath.filename());
+
+ // show the user the command
+ enqueCommandString(rCmd.commandString());
+
+ // set a success message
+ successMessage_ = "\n" + buildPackageSuccessMsg("Binary");
+
+ // run R CMD INSTALL --build <package-dir>
+ module_context::processSupervisor().runCommand(rCmd.shellCommand(),
+ pkgOptions,
+ cb);
+ }
+
+ void checkPackage(const FilePath& rBinDir,
+ const FilePath& packagePath,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // first build then check
+
+ // compose the build command
+ RCommand rCmd(rBinDir);
+ rCmd << "build";
+
+ // add extra args if provided
+ rCmd << projectConfig().packageBuildArgs;
+
+ // add --no-manual and --no-build-vignettes if they are in the check options
+ std::string checkArgs = projectConfig().packageCheckArgs;
+ if (checkArgs.find("--no-manual") != std::string::npos)
+ rCmd << "--no-manual";
+ if (checkArgs.find("--no-build-vignettes") != std::string::npos)
+ rCmd << "--no-build-vignettes";
+
+ // add filename as a FilePath so it is escaped
+ rCmd << FilePath(packagePath.filename());
+
+ // compose the check command (will be executed by the onExit
+ // handler of the build cmd)
+ RCommand rCheckCmd(rBinDir);
+ rCheckCmd << "check";
+
+ // add extra args if provided
+ std::string extraArgs = projectConfig().packageCheckArgs;
+ rCheckCmd << extraArgs;
+
+ // add filename as a FilePath so it is escaped
+ rCheckCmd << FilePath(pkgInfo_.sourcePackageFilename());
+
+ // special callback for build result
+ core::system::ProcessCallbacks buildCb = cb;
+ buildCb.onExit = boost::bind(&Build::onBuildForCheckCompleted,
+ Build::shared_from_this(),
+ _1,
+ rCheckCmd,
+ pkgOptions,
+ buildCb);
+
+ // show the user the command
+ enqueCommandString(rCmd.commandString());
+
+ // set a success message
+ successMessage_ = "R CMD check succeeded\n";
+
+ // bind a success function if appropriate
+ if (userSettings().cleanupAfterRCmdCheck())
+ {
+ successFunction_ = boost::bind(&Build::cleanupAfterCheck,
+ Build::shared_from_this(),
+ pkgInfo_);
+ }
+
+ if (userSettings().viewDirAfterRCmdCheck())
+ {
+ failureFunction_ = boost::bind(
+ &Build::viewDirAfterFailedCheck,
+ Build::shared_from_this(),
+ pkgInfo_);
+ }
+
+ // run the source build
+ module_context::processSupervisor().runCommand(rCmd.shellCommand(),
+ pkgOptions,
+ buildCb);
+ }
+
+ bool devtoolsExecute(const std::string& command,
+ const FilePath& packagePath,
+ core::system::ProcessOptions pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // Find the path to R
+ FilePath rProgramPath;
+ Error error = module_context::rScriptPath(&rProgramPath);
+ if (error)
+ {
+ terminateWithError("attempting to locate R binary", error);
+ return false;
+ }
+
+ // execute within the package directory
+ pkgOptions.workingDir = packagePath;
+
+ // build args
+ std::vector<std::string> args;
+ args.push_back("--slave");
+ args.push_back("--no-save");
+ args.push_back("--no-restore");
+ args.push_back("-e");
+ args.push_back(command);
+
+ // run it
+ module_context::processSupervisor().runProgram(
+ string_utils::utf8ToSystem(rProgramPath.absolutePath()),
+ args,
+ pkgOptions,
+ cb);
+
+ return true;
+ }
+
+ void devtoolsCheckPackage(const FilePath& packagePath,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // build the call to check
+ std::ostringstream ostr;
+ ostr << "devtools::check(";
+
+ std::vector<std::string> args;
+
+ if (projectConfig().packageRoxygenize.empty() ||
+ !options_.autoRoxygenizeForCheck)
+ args.push_back("document = FALSE");
+
+ if (!userSettings().cleanupAfterRCmdCheck())
+ args.push_back("cleanup = FALSE");
+
+ // optional extra check args
+ if (!projectConfig().packageCheckArgs.empty())
+ {
+ args.push_back("args = " +
+ packageArgsVector(projectConfig().packageCheckArgs));
+ }
+
+ // optional extra build args
+ if (!projectConfig().packageBuildArgs.empty())
+ {
+ // propagate check vignette args
+ // add --no-manual and --no-build-vignettes if they are specified
+ std::string buildArgs = projectConfig().packageBuildArgs;
+ std::string checkArgs = projectConfig().packageCheckArgs;
+ if (checkArgs.find("--no-manual") != std::string::npos)
+ buildArgs.append(" --no-manual");
+ if (checkArgs.find("--no-build-vignettes") != std::string::npos)
+ buildArgs.append(" --no-build-vignettes");
+
+ args.push_back("build_args = " + packageArgsVector(buildArgs));
+ }
+
+ // add the args
+ ostr << boost::algorithm::join(args, ", ");
+
+ // enque the command string without the check_dir
+ enqueCommandString(ostr.str() + ")");
+
+ // now complete the command
+ ostr << ", check_dir = dirname(getwd()))";
+ std::string command = ostr.str();
+
+ // set a success message
+ successMessage_ = "\nR CMD check succeeded\n";
+
+ // bind a success function if appropriate
+ if (userSettings().cleanupAfterRCmdCheck())
+ {
+ successFunction_ = boost::bind(&Build::cleanupAfterCheck,
+ Build::shared_from_this(),
+ pkgInfo_);
+ }
+
+ if (userSettings().viewDirAfterRCmdCheck())
+ {
+ failureFunction_ = boost::bind(&Build::viewDirAfterFailedCheck,
+ Build::shared_from_this(),
+ pkgInfo_);
+ }
+
+ // run it
+ devtoolsExecute(command, packagePath, pkgOptions, cb);
+ }
+
+ void devtoolsTestPackage(const FilePath& packagePath,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ std::string command = "devtools::test()";
+ enqueCommandString(command);
+ devtoolsExecute(command, packagePath, pkgOptions, cb);
+ }
+
+ void devtoolsBuildPackage(const FilePath& packagePath,
+ bool binary,
+ const core::system::ProcessOptions& pkgOptions,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // create the call to build
+ std::ostringstream ostr;
+ ostr << "devtools::build(";
+
+ // args
+ std::vector<std::string> args;
+
+ // binary package?
+ if (binary)
+ args.push_back("binary = TRUE");
+
+ // add R args
+ std::string rArgs = binary ? projectConfig().packageBuildBinaryArgs :
+ projectConfig().packageBuildArgs;
+ if (binary)
+ rArgs.append(" --preclean");
+ if (!rArgs.empty())
+ args.push_back("args = " + packageArgsVector(rArgs));
+
+ ostr << boost::algorithm::join(args, ", ");
+ ostr << ")";
+
+ // set a success message
+ std::string type = binary ? "Binary" : "Source";
+ successMessage_ = "\n" + buildPackageSuccessMsg(type);
+
+ // execute it
+ std::string command = ostr.str();
+ enqueCommandString(command);
+ devtoolsExecute(command, packagePath, pkgOptions, cb);
+ }
+
+
+ void onBuildForCheckCompleted(
+ int exitStatus,
+ const RCommand& checkCmd,
+ const core::system::ProcessOptions& checkOptions,
+ const core::system::ProcessCallbacks& checkCb)
+ {
+ if (exitStatus == EXIT_SUCCESS)
+ {
+ // show the user the buld command
+ enqueCommandString(checkCmd.commandString());
+
+ // run the check
+ module_context::processSupervisor().runCommand(checkCmd.shellCommand(),
+ checkOptions,
+ checkCb);
+ }
+ else
+ {
+ terminateWithErrorStatus(exitStatus);
+ }
+ }
+
+
+ void cleanupAfterCheck(const r_util::RPackageInfo& pkgInfo)
+ {
+ // compute paths
+ FilePath buildPath = projects::projectContext().buildTargetPath().parent();
+ FilePath srcPkgPath = buildPath.childPath(pkgInfo.sourcePackageFilename());
+ FilePath chkDirPath = buildPath.childPath(pkgInfo.name() + ".Rcheck");
+
+ // cleanup
+ Error error = srcPkgPath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ error = chkDirPath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ void viewDirAfterFailedCheck(const r_util::RPackageInfo& pkgInfo)
+ {
+ if (!terminationRequested_)
+ {
+ FilePath buildPath = projects::projectContext()
+ .buildTargetPath().parent();
+ FilePath chkDirPath = buildPath.childPath(pkgInfo.name() + ".Rcheck");
+
+ json::Object dataJson;
+ dataJson["directory"] = module_context::createAliasedPath(chkDirPath);
+ dataJson["activate"] = true;
+ ClientEvent event(client_events::kDirectoryNavigate, dataJson);
+
+ module_context::enqueClientEvent(event);
+ }
+ }
+
+ void executeMakefileBuild(const std::string& type,
+ const FilePath& targetPath,
+ const core::system::ProcessOptions& options,
+ const core::system::ProcessCallbacks& cb)
+ {
+ // validate that there is a Makefile file
+ FilePath makefilePath = targetPath.childPath("Makefile");
+ if (!makefilePath.exists())
+ {
+ boost::format fmt ("ERROR: The build directory does "
+ "not contain a Makefile\n"
+ "so the target cannot be built.\n\n"
+ "Build directory: %1%\n");
+ terminateWithError(boost::str(
+ fmt % module_context::createAliasedPath(targetPath)));
+ return;
+ }
+
+ // install the gcc error parser
+ initErrorParser(targetPath, gccErrorParser(targetPath));
+
+ std::string make = "make";
+ if (!options_.makefileArgs.empty())
+ make += " " + options_.makefileArgs;
+
+ std::string makeClean = make + " clean";
+
+ std::string cmd;
+ if (type == "build-all")
+ {
+ cmd = make;
+ }
+ else if (type == "clean-all")
+ {
+ cmd = makeClean;
+ }
+ else if (type == "rebuild-all")
+ {
+ cmd = shell_utils::join_and(makeClean, make);
+ }
+
+ module_context::processSupervisor().runCommand(cmd,
+ options,
+ cb);
+ }
+
+ void executeCustomBuild(const std::string& type,
+ const FilePath& customScriptPath,
+ const core::system::ProcessOptions& options,
+ const core::system::ProcessCallbacks& cb)
+ {
+ module_context::processSupervisor().runCommand(
+ shell_utils::ShellCommand(customScriptPath),
+ options,
+ cb);
+ }
+
+
+ void terminateWithErrorStatus(int exitStatus)
+ {
+ boost::format fmt("\nExited with status %1%.\n\n");
+ enqueBuildOutput(kBuildOutputError, boost::str(fmt % exitStatus));
+ enqueBuildCompleted();
+ }
+
+ void terminateWithError(const std::string& context,
+ const Error& error)
+ {
+ std::string msg = "Error " + context + ": " + error.summary();
+ terminateWithError(msg);
+ }
+
+ void terminateWithError(const std::string& msg)
+ {
+ enqueBuildOutput(kBuildOutputError, msg);
+ enqueBuildCompleted();
+ }
+
+ bool useDevtools()
+ {
+ return projectConfig().packageUseDevtools &&
+ module_context::isPackageVersionInstalled("devtools", "1.4.1");
+ }
+
+public:
+ virtual ~Build()
+ {
+ }
+
+ bool isRunning() const { return isRunning_; }
+
+ const std::string& errorsBaseDir() const { return errorsBaseDir_; }
+ const json::Array& errorsAsJson() const { return errorsJson_; }
+ json::Array outputAsJson() const
+ {
+ json::Array outputJson;
+ std::transform(output_.begin(),
+ output_.end(),
+ std::back_inserter(outputJson),
+ buildOutputAsJson);
+ return outputJson;
+ }
+
+ std::string outputAsText()
+ {
+ std::string output;
+ BOOST_FOREACH(const BuildOutput& buildOutput, output_)
+ {
+ output.append(buildOutput.output);
+ }
+ return output;
+ }
+
+ void terminate()
+ {
+ enqueBuildOutput(kBuildOutputNormal, "\n");
+ terminationRequested_ = true;
+ }
+
+private:
+ bool onContinue()
+ {
+ return !terminationRequested_;
+ }
+
+ void outputWithFilter(const std::string& output)
+ {
+ // split into lines
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines, output, boost::algorithm::is_any_of("\n"));
+
+ // apply filter to each line
+ int size = lines.size();
+ for (int i=0; i<size; i++)
+ {
+ // apply filter
+ std::string line = lines.at(i);
+ int type = errorOutputFilterFunction_(line) ?
+ kBuildOutputError : kBuildOutputNormal;
+
+ // add newline if this wasn't the last line
+ if (i != (size-1))
+ line.append("\n");
+
+ // enque the output
+ enqueBuildOutput(type, line);
+ }
+ }
+
+ void onStandardOutput(const std::string& output)
+ {
+ if (errorOutputFilterFunction_)
+ outputWithFilter(output);
+ else
+ enqueBuildOutput(kBuildOutputNormal, output);
+ }
+
+ void onStandardError(const std::string& output)
+ {
+ if (errorOutputFilterFunction_)
+ outputWithFilter(output);
+ else
+ enqueBuildOutput(kBuildOutputError, output);
+ }
+
+ void onCompleted(int exitStatus)
+ {
+ // call the error parser if one has been specified
+ if (errorParser_)
+ {
+ std::vector<CompileError> errors = errorParser_(outputAsText());
+ if (!errors.empty())
+ {
+ errorsJson_ = compileErrorsAsJson(errors);
+ enqueBuildErrors(errorsJson_);
+ }
+ }
+
+ if (exitStatus != EXIT_SUCCESS)
+ {
+ boost::format fmt("\nExited with status %1%.\n\n");
+ enqueBuildOutput(kBuildOutputError, boost::str(fmt % exitStatus));
+
+ // if this is a package build then check if we can build
+ // C++ code at all
+ if (!pkgInfo_.empty() && postBuildWarning_.empty())
+ {
+ if (!module_context::canBuildCpp())
+ {
+ postBuildWarning_ =
+ "WARNING: The tools required to build R packages "
+ "are not currently installed. Additional information on "
+ "installing the required tools for your platform can be "
+ "found here:\n\n"
+ "http://www.rstudio.com/ide/docs/packages/prerequisites";
+ }
+ }
+
+ // never restart R after a failed build
+ restartR_ = false;
+
+ // take other actions
+ if (failureFunction_)
+ failureFunction_();
+ }
+ else
+ {
+ if (!successMessage_.empty())
+ enqueBuildOutput(kBuildOutputNormal, successMessage_ + "\n");
+
+ if (successFunction_)
+ successFunction_();
+ }
+
+ enqueBuildCompleted();
+ }
+
+ void enqueBuildOutput(int type, const std::string& output)
+ {
+ BuildOutput buildOutput(type, output);
+
+ output_.push_back(buildOutput);
+
+ ClientEvent event(client_events::kBuildOutput,
+ buildOutputAsJson(buildOutput));
+
+ module_context::enqueClientEvent(event);
+ }
+
+ void enqueCommandString(const std::string& cmd)
+ {
+ enqueBuildOutput(kBuildOutputCommand, "==> " + cmd + "\n\n");
+ }
+
+ void enqueBuildErrors(const json::Array& errors)
+ {
+ json::Object jsonData;
+ jsonData["base_dir"] = errorsBaseDir_;
+ jsonData["errors"] = errors;
+
+ ClientEvent event(client_events::kBuildErrors, jsonData);
+ module_context::enqueClientEvent(event);
+ }
+
+ void enqueBuildCompleted()
+ {
+ isRunning_ = false;
+
+ if (!postBuildWarning_.empty())
+ enqueBuildOutput(kBuildOutputError, postBuildWarning_ + "\n\n");
+
+ // enque event
+ std::string afterRestartCommand;
+ if (restartR_)
+ afterRestartCommand = "library(" + pkgInfo_.name() + ")";
+ json::Object dataJson;
+ dataJson["restart_r"] = restartR_;
+ dataJson["after_restart_command"] = afterRestartCommand;
+ ClientEvent event(client_events::kBuildCompleted, dataJson);
+ module_context::enqueClientEvent(event);
+ }
+
+ const r_util::RProjectConfig& projectConfig()
+ {
+ return projects::projectContext().config();
+ }
+
+ std::string buildPackageSuccessMsg(const std::string& type)
+ {
+ FilePath writtenPath = projects::projectContext().buildTargetPath().parent();
+ std::string written = module_context::createAliasedPath(writtenPath);
+ if (written == "~")
+ written = writtenPath.absolutePath();
+
+ return type + " package written to " + written;
+ }
+
+ void initErrorParser(const FilePath& baseDir, CompileErrorParser parser)
+ {
+ // set base dir -- make sure it ends with a / so the slash is
+ // excluded from error display
+ errorsBaseDir_ = module_context::createAliasedPath(baseDir);
+ if (!errorsBaseDir_.empty() &&
+ !boost::algorithm::ends_with(errorsBaseDir_, "/"))
+ {
+ errorsBaseDir_.append("/");
+ }
+
+ errorParser_ = parser;
+ }
+
+private:
+ bool isRunning_;
+ bool terminationRequested_;
+ std::vector<BuildOutput> output_;
+ CompileErrorParser errorParser_;
+ std::string errorsBaseDir_;
+ json::Array errorsJson_;
+ r_util::RPackageInfo pkgInfo_;
+ projects::RProjectBuildOptions options_;
+ std::string successMessage_;
+ std::string postBuildWarning_;
+ boost::function<void()> successFunction_;
+ boost::function<void()> failureFunction_;
+ boost::function<bool(const std::string&)> errorOutputFilterFunction_;
+ bool restartR_;
+};
+
+boost::shared_ptr<Build> s_pBuild;
+
+
+bool isBuildRunning()
+{
+ return s_pBuild && s_pBuild->isRunning();
+}
+
+Error startBuild(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get type
+ std::string type;
+ Error error = json::readParam(request.params, 0, &type);
+ if (error)
+ return error;
+
+ // if we have a build already running then just return false
+ if (isBuildRunning())
+ {
+ pResponse->setResult(false);
+ }
+ else
+ {
+ s_pBuild = Build::create(type);
+ pResponse->setResult(true);
+ }
+
+ return Success();
+}
+
+
+
+Error terminateBuild(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ if (isBuildRunning())
+ s_pBuild->terminate();
+
+ pResponse->setResult(true);
+
+ return Success();
+}
+
+Error getCppCapabilities(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object capsJson;
+ capsJson["can_build"] = module_context::canBuildCpp();
+ capsJson["can_source_cpp"] = module_context::haveRcppAttributes();
+ pResponse->setResult(capsJson);
+
+ return Success();
+}
+
+Error devtoolsLoadAllPath(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(module_context::pathRelativeTo(
+ module_context::safeCurrentPath(),
+ projects::projectContext().buildTargetPath()));
+
+ return Success();
+}
+
+
+struct BuildContext
+{
+ bool empty() const { return errors.empty() && outputs.empty(); }
+ std::string errorsBaseDir;
+ json::Array errors;
+ json::Array outputs;
+};
+
+BuildContext s_suspendBuildContext;
+
+
+void writeBuildContext(const BuildContext& buildContext,
+ core::Settings* pSettings)
+{
+ std::ostringstream ostr;
+ json::write(buildContext.outputs, ostr);
+ pSettings->set("build-last-outputs", ostr.str());
+
+ std::ostringstream ostrErrors;
+ json::write(buildContext.errors, ostrErrors);
+ pSettings->set("build-last-errors", ostrErrors.str());
+
+ pSettings->set("build-last-errors-base-dir", buildContext.errorsBaseDir);
+}
+
+void onSuspend(core::Settings* pSettings)
+{
+ if (s_pBuild)
+ {
+ BuildContext buildContext;
+ buildContext.outputs = s_pBuild->outputAsJson();
+ buildContext.errors = s_pBuild->errorsAsJson();
+ buildContext.errorsBaseDir = s_pBuild->errorsBaseDir();
+ writeBuildContext(buildContext, pSettings);
+ }
+ else if (!s_suspendBuildContext.empty())
+ {
+ writeBuildContext(s_suspendBuildContext, pSettings);
+ }
+ else
+ {
+ BuildContext emptyBuildContext;
+ writeBuildContext(emptyBuildContext, pSettings);
+ }
+}
+
+void onResume(const core::Settings& settings)
+{
+ std::string buildLastOutputs = settings.get("build-last-outputs");
+ if (!buildLastOutputs.empty())
+ {
+ json::Value outputsJson;
+ if (json::parse(buildLastOutputs, &outputsJson) &&
+ json::isType<json::Array>(outputsJson))
+ {
+ s_suspendBuildContext.outputs = outputsJson.get_array();
+ }
+ }
+
+ s_suspendBuildContext.errorsBaseDir = settings.get("build-last-errors-base-dir");
+ std::string buildLastErrors = settings.get("build-last-errors");
+ if (!buildLastErrors.empty())
+ {
+ json::Value errorsJson;
+ if (json::parse(buildLastErrors, &errorsJson) &&
+ json::isType<json::Array>(errorsJson))
+ {
+ s_suspendBuildContext.errors = errorsJson.get_array();
+ }
+ }
+}
+
+
+SEXP rs_canBuildCpp()
+{
+ r::sexp::Protect rProtect;
+ return r::sexp::create(module_context::canBuildCpp(), &rProtect);
+}
+
+std::string s_previousPath;
+SEXP rs_restorePreviousPath()
+{
+#ifdef _WIN32
+ if (!s_previousPath.empty())
+ core::system::setenv("PATH", s_previousPath);
+ s_previousPath.clear();
+#endif
+ return R_NilValue;
+}
+
+SEXP rs_addRToolsToPath()
+{
+#ifdef _WIN32
+ s_previousPath = core::system::getenv("PATH");
+ std::string newPath = s_previousPath;
+ std::string warningMsg;
+ build::addRtoolsToPathIfNecessary(&newPath, &warningMsg);
+ core::system::setenv("PATH", newPath);
+
+#endif
+ return R_NilValue;
+}
+
+SEXP rs_installPackage(SEXP pkgPathSEXP, SEXP libPathSEXP)
+{
+ // get R bin directory
+ FilePath rBinDir;
+ Error error = module_context::rBinDir(&rBinDir);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return R_NilValue;
+ }
+
+ // run command
+ RCommand installCommand(rBinDir);
+ installCommand << "INSTALL";
+ installCommand << "-l";
+ installCommand << "\"" + r::sexp::asString(libPathSEXP) + "\"";
+ installCommand << "\"" + r::sexp::asString(pkgPathSEXP) + "\"";
+ core::system::ProcessResult result;
+ error = core::system::runCommand(installCommand.commandString(),
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ LOG_ERROR(error);
+ if ((result.exitStatus != EXIT_SUCCESS) && !result.stdErr.empty())
+ LOG_ERROR_MESSAGE("Error install package: " + result.stdErr);
+
+ return R_NilValue;
+}
+
+
+} // anonymous namespace
+
+json::Value buildStateAsJson()
+{
+ if (s_pBuild)
+ {
+ json::Object stateJson;
+ stateJson["running"] = s_pBuild->isRunning();
+ stateJson["outputs"] = s_pBuild->outputAsJson();
+ stateJson["errors_base_dir"] = s_pBuild->errorsBaseDir();
+ stateJson["errors"] = s_pBuild->errorsAsJson();
+ return stateJson;
+ }
+ else if (!s_suspendBuildContext.empty())
+ {
+ json::Object stateJson;
+ stateJson["running"] = false;
+ stateJson["outputs"] = s_suspendBuildContext.outputs;
+ stateJson["errors_base_dir"] = s_suspendBuildContext.errorsBaseDir;
+ stateJson["errors"] = s_suspendBuildContext.errors;
+ return stateJson;
+ }
+ else
+ {
+ return json::Value();
+ }
+}
+
+void onDeferredInit(bool newSession)
+{
+ if (newSession)
+ {
+ // if we are on mavericks then provide an .R/Makevars that points
+ // to clang if necessary
+ using namespace module_context;
+ FilePath makevarsPath = userHomePath().childPath(".R/Makevars");
+ if (isOSXMavericks() && !makevarsPath.exists() && !canBuildCpp())
+ {
+ Error error = makevarsPath.parent().ensureDirectory();
+ if (!error)
+ {
+ std::string makevars = "CC=clang\nCXX=clang++\n";
+ error = core::writeStringToFile(makevarsPath, makevars);
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+ }
+}
+
+Error initialize()
+{
+ R_CallMethodDef canBuildMethodDef ;
+ canBuildMethodDef.name = "rs_canBuildCpp" ;
+ canBuildMethodDef.fun = (DL_FUNC) rs_canBuildCpp ;
+ canBuildMethodDef.numArgs = 0;
+ r::routines::addCallMethod(canBuildMethodDef);
+
+ R_CallMethodDef addRToolsToPathMethodDef ;
+ addRToolsToPathMethodDef.name = "rs_addRToolsToPath" ;
+ addRToolsToPathMethodDef.fun = (DL_FUNC) rs_addRToolsToPath ;
+ addRToolsToPathMethodDef.numArgs = 0;
+ r::routines::addCallMethod(addRToolsToPathMethodDef);
+
+ R_CallMethodDef restorePreviousPathMethodDef ;
+ restorePreviousPathMethodDef.name = "rs_restorePreviousPath" ;
+ restorePreviousPathMethodDef.fun = (DL_FUNC) rs_restorePreviousPath ;
+ restorePreviousPathMethodDef.numArgs = 0;
+ r::routines::addCallMethod(restorePreviousPathMethodDef);
+
+ R_CallMethodDef installPackageMethodDef ;
+ installPackageMethodDef.name = "rs_installPackage" ;
+ installPackageMethodDef.fun = (DL_FUNC) rs_installPackage;
+ installPackageMethodDef.numArgs = 2;
+ r::routines::addCallMethod(installPackageMethodDef);
+
+ // subscribe to deferredInit for build tools fixup
+ module_context::events().onDeferredInit.connect(onDeferredInit);
+
+ // subscribe to file monitor and source editor file saved so we
+ // can tickle a flag to indicates when we should force an R
+ // package rebuild
+ session::projects::FileMonitorCallbacks cb;
+ cb.onFilesChanged = onFilesChanged;
+ projects::projectContext().subscribeToFileMonitor("", cb);
+ module_context::events().onSourceEditorFileSaved.connect(onFileChanged);
+
+ // add suspend handler
+ addSuspendHandler(module_context::SuspendHandler(boost::bind(onSuspend, _2),
+ onResume));
+
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "start_build", startBuild))
+ (bind(registerRpcMethod, "terminate_build", terminateBuild))
+ (bind(registerRpcMethod, "get_cpp_capabilities", getCppCapabilities))
+ (bind(registerRpcMethod, "devtools_load_all_path", devtoolsLoadAllPath))
+ (bind(sourceModuleRFile, "SessionBuild.R"))
+ (bind(source_cpp::initialize));
+ return initBlock.execute();
+}
+
+
+} // namespace build
+} // namespace modules
+
+namespace module_context {
+
+bool haveRcppAttributes()
+{
+ return module_context::isPackageVersionInstalled("Rcpp", "0.10.1");
+}
+
+bool canBuildCpp()
+{
+ // try to build a simple c file to test whether we have build tools available
+ FilePath cppPath = module_context::tempFile("test", "c");
+ Error error = core::writeStringToFile(cppPath, "void test() {}\n");
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // get R bin directory
+ FilePath rBinDir;
+ error = module_context::rBinDir(&rBinDir);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // try to run build tools
+ RCommand rCmd(rBinDir);
+ rCmd << "SHLIB";
+ rCmd << cppPath.filename();
+
+ core::system::ProcessOptions options;
+ options.workingDir = cppPath.parent();
+ core::system::Options childEnv;
+ core::system::environment(&childEnv);
+ std::string warningMsg;
+ modules::build::addRtoolsToPathIfNecessary(&childEnv, &warningMsg);
+ options.environment = childEnv;
+
+ core::system::ProcessResult result;
+ error = core::system::runCommand(rCmd.commandString(), options, &result);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ return result.exitStatus == EXIT_SUCCESS;
+}
+
+}
+
+} // namesapce session
+
diff --git a/src/cpp/session/modules/build/SessionBuild.hpp b/src/cpp/session/modules/build/SessionBuild.hpp
new file mode 100644
index 0000000..c19a71f
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuild.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionBuild.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_BUILD_HPP
+#define SESSION_BUILD_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace build {
+
+core::json::Value buildStateAsJson();
+
+core::Error initialize();
+
+} // namespace build
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_BUILD_HPP
diff --git a/src/cpp/session/modules/build/SessionBuildEnvironment.cpp b/src/cpp/session/modules/build/SessionBuildEnvironment.cpp
new file mode 100644
index 0000000..66e8787
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildEnvironment.cpp
@@ -0,0 +1,237 @@
+/*
+ * SessionBuildEnvironment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionBuildEnvironment.hpp"
+
+#include <string>
+#include <vector>
+
+#include <boost/regex.hpp>
+#include <boost/format.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+#include <core/r_util/RToolsInfo.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace build {
+
+#ifdef _WIN32
+
+namespace {
+
+bool isRtoolsCompatible(const r_util::RToolsInfo& rTools)
+{
+ bool isCompatible = false;
+ Error error = r::exec::evaluateString(rTools.versionPredicate(),
+ &isCompatible);
+ if (error)
+ LOG_ERROR(error);
+ return isCompatible;
+}
+
+r_util::RToolsInfo scanPathForRTools()
+{
+ // first confirm ls.exe is in Rtools
+ r_util::RToolsInfo noToolsFound;
+ FilePath lsPath = module_context::findProgram("ls.exe");
+ if (lsPath.empty())
+ return noToolsFound;
+
+ // we have a candidate installPath
+ FilePath installPath = lsPath.parent().parent();
+ core::system::ensureLongPath(&installPath);
+ if (!installPath.childPath("Rtools.txt").exists())
+ return noToolsFound;
+
+ // find the version path
+ FilePath versionPath = installPath.childPath("VERSION.txt");
+ if (!versionPath.exists())
+ return noToolsFound;
+
+ // further verify that gcc is in Rtools
+ FilePath gccPath = module_context::findProgram("gcc.exe");
+ if (!gccPath.exists())
+ return noToolsFound;
+ if (!gccPath.parent().parent().parent().childPath("Rtools.txt").exists())
+ return noToolsFound;
+
+ // Rtools is in the path -- now crack the VERSION file
+ std::string contents;
+ Error error = core::readStringFromFile(versionPath, &contents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return noToolsFound;
+ }
+
+ // extract the version
+ boost::algorithm::trim(contents);
+ boost::regex pattern("Rtools version (\\d\\.\\d\\d)[\\d\\.]+$");
+ boost::smatch match;
+ if (boost::regex_search(contents, match, pattern))
+ return r_util::RToolsInfo(match[1], installPath);
+ else
+ return noToolsFound;
+}
+
+std::string formatPath(const FilePath& filePath)
+{
+ FilePath displayPath = filePath;
+ core::system::ensureLongPath(&displayPath);
+ return boost::algorithm::replace_all_copy(
+ displayPath.absolutePath(), "/", "\\");
+}
+
+template <typename T>
+bool doAddRtoolsToPathIfNecessary(T* pTarget, std::string* pWarningMessage)
+{
+ // can we find ls.exe and gcc.exe on the path? if so then
+ // we assume Rtools are already there (this is the same test
+ // used by devtools)
+ bool rToolsOnPath = false;
+ Error error = r::exec::RFunction(".rs.isRtoolsOnPath").call(&rToolsOnPath);
+ if (error)
+ LOG_ERROR(error);
+ if (rToolsOnPath)
+ {
+ // perform an extra check to see if the version on the path is not
+ // compatible with the currenly running version of R
+ r_util::RToolsInfo rTools = scanPathForRTools();
+ if (!rTools.empty())
+ {
+ if (!isRtoolsCompatible(rTools))
+ {
+ boost::format fmt(
+ "WARNING: Rtools version %1% is on the PATH (intalled at %2%) "
+ "but is "
+ "not compatible with the currently running version of R."
+ "\n\nPlease download and install the appropriate version of "
+ "Rtools to ensure that packages are built correctly:"
+ "\n\nhttp://cran.rstudio.com/bin/windows/Rtools/"
+ "\n\nNote that in addition to installing a compatible verison you "
+ "also need to remove the incompatible version from your PATH");
+ *pWarningMessage = boost::str(
+ fmt % rTools.name() % formatPath(rTools.installPath()));
+ }
+ }
+
+ return false;
+ }
+
+ // ok so scan for R tools
+ std::vector<r_util::RToolsInfo> rTools;
+ error = core::r_util::scanRegistryForRTools(&rTools);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ // enumerate them to see if we have a compatible version
+ // (go in reverse order for most recent first)
+ std::vector<r_util::RToolsInfo>::const_reverse_iterator it = rTools.rbegin();
+ for ( ; it != rTools.rend(); ++it)
+ {
+ if (isRtoolsCompatible(*it))
+ {
+ r_util::prependToSystemPath(*it, pTarget);
+ return true;
+ }
+ }
+
+ // if we found no version of rtools whatsoever then print warning and return
+ if (rTools.empty())
+ {
+ *pWarningMessage =
+ "WARNING: Rtools is required to build R packages but is not "
+ "currently installed. "
+ "Please download and install the appropriate "
+ "version of Rtools before proceeding:\n\n"
+ "http://cran.rstudio.com/bin/windows/Rtools/";
+ }
+ else
+ {
+ // Rtools installed but no compatible version, print a suitable warning
+ pWarningMessage->append(
+ "WARNING: Rtools is required to build R packages but no version "
+ "of Rtools compatible with the currently running version of R "
+ "was found. Note that the following incompatible version(s) "
+ "of Rtools were found:\n\n");
+
+ std::vector<r_util::RToolsInfo>::const_iterator fwdIt = rTools.begin();
+ for (; fwdIt != rTools.end(); ++fwdIt)
+ {
+ std::string path = formatPath(fwdIt->installPath());
+ boost::format fmt(" - Rtools %1% (installed at %2%)\n");
+ pWarningMessage->append(boost::str(fmt % fwdIt->name() % path));
+ }
+
+ pWarningMessage->append(
+ "\nPlease download and install the appropriate "
+ "version of Rtools before proceeding:\n\n"
+ "http://cran.rstudio.com/bin/windows/Rtools/");
+ }
+
+ return false;
+}
+
+
+} // anonymous namespace
+
+
+bool addRtoolsToPathIfNecessary(std::string* pPath,
+ std::string* pWarningMessage)
+{
+ return doAddRtoolsToPathIfNecessary(pPath, pWarningMessage);
+}
+
+bool addRtoolsToPathIfNecessary(core::system::Options* pEnvironment,
+ std::string* pWarningMessage)
+{
+ return doAddRtoolsToPathIfNecessary(pEnvironment, pWarningMessage);
+}
+
+
+#else
+
+bool addRtoolsToPathIfNecessary(std::string* pPath,
+ std::string* pWarningMessage)
+{
+ return false;
+}
+
+bool addRtoolsToPathIfNecessary(core::system::Options* pEnvironment,
+ std::string* pWarningMessage)
+{
+ return false;
+}
+
+#endif
+
+
+} // namespace build
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/build/SessionBuildEnvironment.hpp b/src/cpp/session/modules/build/SessionBuildEnvironment.hpp
new file mode 100644
index 0000000..29bf88a
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildEnvironment.hpp
@@ -0,0 +1,38 @@
+/*
+ * SessionBuildEnvironment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_BUILD_ENVIRONMENT_HPP
+#define SESSION_BUILD_ENVIRONMENT_HPP
+
+#include <string>
+
+#include <core/system/Environment.hpp>
+
+namespace session {
+namespace modules {
+namespace build {
+
+bool addRtoolsToPathIfNecessary(std::string* pPath,
+ std::string* pWarningMessage);
+
+bool addRtoolsToPathIfNecessary(core::system::Options* pEnvironment,
+ std::string* pWarningMessage);
+
+} // namespace build
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_BUILD_ENVIRONMENT_HPP
+
diff --git a/src/cpp/session/modules/build/SessionBuildErrors.cpp b/src/cpp/session/modules/build/SessionBuildErrors.cpp
new file mode 100644
index 0000000..0049a04
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildErrors.cpp
@@ -0,0 +1,254 @@
+/*
+ * SessionBuildErrors.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionBuildErrors.hpp"
+
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/regex.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace build {
+
+namespace {
+
+
+bool isRSourceFile(const FilePath& filePath)
+{
+ return (filePath.extensionLowerCase() == ".q" ||
+ filePath.extensionLowerCase() == ".s" ||
+ filePath.extensionLowerCase() == ".r");
+}
+
+bool isMatchingFile(const std::vector<std::string>& lines,
+ std::size_t diagLine,
+ const std::string& lineContents,
+ const std::string& nextLineContents)
+{
+ // first verify the file has enough lines to match
+ if (lines.size() < (diagLine+1))
+ return false;
+
+ return boost::algorithm::equals(lines[diagLine-1],lineContents) &&
+ boost::algorithm::starts_with(lines[diagLine], nextLineContents);
+}
+
+FilePath scanForRSourceFile(const FilePath& basePath,
+ std::size_t diagLine,
+ const std::string& lineContents,
+ const std::string& nextLineContents)
+{
+ std::vector<FilePath> children;
+ Error error = basePath.children(&children);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return FilePath();
+ }
+
+ BOOST_FOREACH(const FilePath& child, children)
+ {
+ if (isRSourceFile(child))
+ {
+ std::vector<std::string> lines;
+ Error error = core::readStringVectorFromFile(child, &lines, false);
+ if (error)
+ {
+ LOG_ERROR(error);
+ continue;
+ }
+
+ if (isMatchingFile(lines, diagLine, lineContents, nextLineContents))
+ return child;
+ }
+ }
+
+ return FilePath();
+}
+
+std::vector<CompileError> parseRErrors(const FilePath& basePath,
+ const std::string& output)
+{
+ std::vector<CompileError> errors;
+
+ boost::regex re("^Error in parse\\(outFile\\) : ([0-9]+?):([0-9]+?): (.+?)\\n"
+ "([0-9]+?): (.*?)\\n([0-9]+?): (.+?)$");
+ boost::sregex_iterator iter(output.begin(), output.end(), re,
+ boost::regex_constants::match_not_dot_newline);
+ boost::sregex_iterator end;
+ for (; iter != end; iter++)
+ {
+ boost::smatch match = *iter;
+ BOOST_ASSERT(match.size() == 8);
+
+ // first part is straightforward
+ std::string line = match[1];
+ std::string column = match[2];
+ std::string message = match[3];
+
+ // we need to guess the file based on the contextual information
+ // provided in the error message
+ int diagLine = core::safe_convert::stringTo<int>(match[4], -1);
+ if (diagLine != -1)
+ {
+ FilePath rSrcFile = scanForRSourceFile(basePath,
+ diagLine,
+ match[5],
+ match[7]);
+ if (!rSrcFile.empty())
+ {
+ // create error and add it
+ CompileError err(CompileError::Error,
+ rSrcFile,
+ core::safe_convert::stringTo<int>(line, 1),
+ core::safe_convert::stringTo<int>(column, 1),
+ message,
+ false);
+ errors.push_back(err);
+ }
+ }
+
+ }
+
+ return errors;
+
+}
+
+
+std::vector<CompileError> parseGccErrors(const FilePath& basePath,
+ const std::string& output)
+{
+ std::vector<CompileError> errors;
+
+ // parse standard gcc errors and warning lines but also pickup "from"
+ // prefixed errors and substitute the from file for the error/warning file
+ boost::regex re("(?:from (.+?):([0-9]+?).+?\\n)?"
+ "^(.+?):([0-9]+?):(?:([0-9]+?):)? (error|warning): (.+)$");
+ boost::sregex_iterator iter(output.begin(), output.end(), re,
+ boost::regex_constants::match_not_dot_newline);
+ boost::sregex_iterator end;
+ for (; iter != end; iter++)
+ {
+ boost::smatch match = *iter;
+ BOOST_ASSERT(match.size() == 8);
+
+ std::string file, line, column, type, message;
+ std::string match1 = match[1];
+ if (!match1.empty() && FilePath::isRootPath(match[1]))
+ {
+ file = match[1];
+ line = match[2];
+ column = "1";
+ }
+ else
+ {
+ file = match[3];
+ line = match[4];
+ column = match[5];
+ if (column.empty())
+ column = "1";
+ }
+ type = match[6];
+ message = match[7];
+
+ // resolve file path
+ FilePath filePath;
+ if (FilePath::isRootPath(file))
+ filePath = FilePath(file);
+ else
+ filePath = basePath.complete(file);
+ FilePath realPath;
+ Error error = core::system::realPath(filePath, &realPath);
+ if (error)
+ LOG_ERROR(error);
+ else
+ filePath = realPath;
+
+ // don't show warnings from Makeconf
+ if (filePath.filename() == "Makeconf")
+ continue;
+
+ // resolve type
+ CompileError::Type errType = (type == "warning") ? CompileError::Warning :
+ CompileError::Error;
+
+ // create error and add it
+ CompileError err(errType,
+ filePath,
+ core::safe_convert::stringTo<int>(line, 1),
+ core::safe_convert::stringTo<int>(column, 1),
+ message,
+ true);
+ errors.push_back(err);
+ }
+
+ return errors;
+}
+
+// NOTE: sync changes with SessionCompilePdf.cpp logEntryJson
+json::Value compileErrorJson(const CompileError& compileError)
+{
+ json::Object obj;
+ obj["type"] = static_cast<int>(compileError.type);
+ obj["path"] = module_context::createAliasedPath(compileError.path);
+ obj["line"] = compileError.line;
+ obj["column"] = compileError.column;
+ obj["message"] = compileError.message;
+ obj["log_path"] = "";
+ obj["log_line"] = -1;
+ obj["show_error_list"] = compileError.showErrorList;
+ return obj;
+}
+
+
+} // anonymous namespace
+
+json::Array compileErrorsAsJson(const std::vector<CompileError>& errors)
+{
+ json::Array errorsJson;
+ std::transform(errors.begin(),
+ errors.end(),
+ std::back_inserter(errorsJson),
+ compileErrorJson);
+ return errorsJson;
+}
+
+CompileErrorParser gccErrorParser(const FilePath& basePath)
+{
+ return boost::bind(parseGccErrors, basePath, _1);
+}
+
+CompileErrorParser rErrorParser(const FilePath& basePath)
+{
+ return boost::bind(parseRErrors, basePath, _1);
+}
+
+
+} // namespace build
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/build/SessionBuildErrors.hpp b/src/cpp/session/modules/build/SessionBuildErrors.hpp
new file mode 100644
index 0000000..313550a
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildErrors.hpp
@@ -0,0 +1,104 @@
+/*
+ * SessionBuildErrors.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_BUILD_ERRORS_HPP
+#define SESSION_BUILD_ERRORS_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/json/Json.hpp>
+
+namespace session {
+namespace modules {
+namespace build {
+
+struct CompileError
+{
+ // NOTE: error types are shared accross all client code that uses
+ // the CompileError type. therefore if we want to add more types
+ // we need to do so beyond the 'Box' value
+ enum Type {
+ Error = 0, Warning = 1 /*, Box = 2 */
+ };
+
+ CompileError(Type type,
+ const core::FilePath& path,
+ int line,
+ int column,
+ const std::string& message,
+ bool showErrorList)
+ : type(type), path(path), line(line), column(column), message(message),
+ showErrorList(showErrorList)
+ {
+ }
+
+ Type type;
+ core::FilePath path;
+ int line;
+ int column;
+ std::string message;
+ bool showErrorList;
+};
+
+core::json::Array compileErrorsAsJson(const std::vector<CompileError>& errors);
+
+typedef boost::function<std::vector<CompileError>(const std::string&)>
+ CompileErrorParser;
+
+class CompileErrorParsers
+{
+public:
+ CompileErrorParsers()
+ {
+ }
+
+ void add(CompileErrorParser parser)
+ {
+ parsers_.push_back(parser);
+ }
+
+public:
+ std::vector<CompileError> operator()(const std::string& output)
+ {
+ std::vector<CompileError> allErrors;
+ BOOST_FOREACH(const CompileErrorParser& parser, parsers_)
+ {
+ std::vector<CompileError> errors = parser(output);
+ std::copy(errors.begin(), errors.end(), std::back_inserter(allErrors));
+ }
+
+ return allErrors;
+ }
+
+private:
+ std::vector<CompileErrorParser> parsers_;
+};
+
+CompileErrorParser gccErrorParser(const core::FilePath& basePath);
+
+CompileErrorParser rErrorParser(const core::FilePath& basePath);
+
+
+} // namespace build
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_BUILD_ERRORS_HPP
+
diff --git a/src/cpp/session/modules/build/SessionBuildUtils.cpp b/src/cpp/session/modules/build/SessionBuildUtils.cpp
new file mode 100644
index 0000000..306a81f
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildUtils.cpp
@@ -0,0 +1,34 @@
+/*
+ * SessionBuildUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionBuildUtils.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace build {
+
+core::json::Object buildOutputAsJson(const BuildOutput& buildOutput)
+{
+ json::Object buildOutputJson;
+ buildOutputJson["type"] = buildOutput.type;
+ buildOutputJson["output"] = buildOutput.output;
+ return buildOutputJson;
+}
+
+} // namespace build
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/build/SessionBuildUtils.hpp b/src/cpp/session/modules/build/SessionBuildUtils.hpp
new file mode 100644
index 0000000..3f3246f
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionBuildUtils.hpp
@@ -0,0 +1,51 @@
+/*
+ * SessionBuildUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_BUILD_UTILS_HPP
+#define SESSION_BUILD_UTILS_HPP
+
+#include <string>
+
+#include <core/json/Json.hpp>
+
+namespace session {
+namespace modules {
+namespace build {
+
+
+const int kBuildOutputCommand = 0;
+const int kBuildOutputNormal = 1;
+const int kBuildOutputError = 2;
+
+struct BuildOutput
+{
+ BuildOutput(int type, const std::string& output)
+ : type(type), output(output)
+ {
+ }
+
+ int type;
+ std::string output;
+};
+
+core::json::Object buildOutputAsJson(const BuildOutput& buildOutput);
+
+
+} // namespace build
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_BUILD_UTILS_HPP
+
diff --git a/src/cpp/session/modules/build/SessionSourceCpp.cpp b/src/cpp/session/modules/build/SessionSourceCpp.cpp
new file mode 100644
index 0000000..a75138f
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionSourceCpp.cpp
@@ -0,0 +1,288 @@
+/*
+ * SessionSourceCpp.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSourceCpp.hpp"
+
+#include <boost/signal.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionBuildUtils.hpp"
+#include "SessionBuildErrors.hpp"
+#include "SessionBuildEnvironment.hpp"
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace build {
+namespace source_cpp {
+
+namespace {
+
+struct SourceCppState
+{
+ bool empty() const { return errors.empty() && outputs.empty(); }
+
+ void clear()
+ {
+ targetFile.clear();
+ errors.clear();
+ outputs.clear();
+ }
+
+ void addOutput(int type, const std::string& output)
+ {
+ outputs.push_back(buildOutputAsJson(BuildOutput(type,output)));
+ }
+
+ json::Value asJson() const
+ {
+ json::Object stateJson;
+ stateJson["target_file"] = targetFile;
+ stateJson["outputs"] = outputs;
+ stateJson["errors"] = errors;
+ return stateJson;
+ }
+
+ std::string targetFile;
+ json::Array errors;
+ json::Array outputs;
+};
+
+void enqueSourceCppStarted()
+{
+ ClientEvent event(client_events::kSourceCppStarted);
+ module_context::enqueClientEvent(event);
+}
+
+void enqueSourceCppCompleted(const FilePath& sourceFile,
+ const std::string& output,
+ const std::string& errorOutput)
+{
+ // reset last sourceCpp state with new data
+ SourceCppState sourceCppState;
+ sourceCppState.targetFile = module_context::createAliasedPath(sourceFile);
+ sourceCppState.addOutput(kBuildOutputNormal, output);
+ sourceCppState.addOutput(kBuildOutputError, errorOutput);
+
+ // parse errors
+ std::string allOutput = output + "\n" + errorOutput;
+ CompileErrorParser errorParser = gccErrorParser(sourceFile.parent());
+ std::vector<CompileError> errors = errorParser(allOutput);
+ sourceCppState.errors = compileErrorsAsJson(errors);
+
+ // enque event
+ ClientEvent event(client_events::kSourceCppCompleted,
+ sourceCppState.asJson());
+ module_context::enqueClientEvent(event);
+}
+
+
+class SourceCppContext : boost::noncopyable
+{
+private:
+ SourceCppContext() {}
+ friend SourceCppContext& sourceCppContext();
+
+public:
+ bool onBuild(const FilePath& sourceFile, bool fromCode, bool showOutput)
+ {
+ // always clear state before starting a new build
+ reset();
+
+ // capture params
+ sourceFile_ = sourceFile;
+ fromCode_ = fromCode;
+ showOutput_ = showOutput;
+
+ // fixup path if necessary
+ std::string path = core::system::getenv("PATH");
+ std::string newPath = path;
+ if (build::addRtoolsToPathIfNecessary(&newPath, &rToolsWarning_))
+ {
+ previousPath_ = path;
+ core::system::setenv("PATH", newPath);
+ }
+
+ // capture all output that goes to the console
+ module_context::events().onConsoleOutput.connect(
+ boost::bind(&SourceCppContext::onConsoleOutput, this, _1, _2));
+
+ // enque build started
+ enqueSourceCppStarted();
+
+ // return true to indicate it's okay to build
+ return true;
+ }
+
+ void onBuildComplete(bool succeeded, const std::string& output)
+ {
+ // defer handling of build complete so we make sure to get all of the
+ // stderr output from console std stream capture
+ module_context::scheduleDelayedWork(
+ boost::posix_time::milliseconds(200),
+ boost::bind(&SourceCppContext::handleBuildComplete,
+ this, succeeded, output),
+ false);
+ }
+
+private:
+
+ void handleBuildComplete(bool succeeded, const std::string& output)
+ {
+ // restore previous path
+ if (!previousPath_.empty())
+ core::system::setenv("PATH", previousPath_);
+
+ // collect all build output (do this before r tools warning so
+ // it's output doesn't end up in consoleErrorBuffer_)
+ std::string buildOutput;
+ if (!succeeded || showOutput_)
+ buildOutput = consoleOutputBuffer_;
+ else
+ buildOutput = output;
+
+ // if we failed and there was an R tools warning then show it
+ if (!succeeded && !rToolsWarning_.empty())
+ module_context::consoleWriteError(rToolsWarning_);
+
+ // parse for gcc errors for sourceCpp
+ if (!fromCode_)
+ enqueSourceCppCompleted(sourceFile_, buildOutput, consoleErrorBuffer_);
+
+ // reset state
+ reset();
+ }
+
+
+ void onConsoleOutput(module_context::ConsoleOutputType type,
+ std::string output)
+ {
+#ifdef _WIN32
+ // on windows make sure that output ends with a newline (because
+ // standard output and error both come in on the same channel not
+ // separated by newlines which prevents us from parsing errors)
+ if (!boost::algorithm::ends_with(output, "\n"))
+ output += "\n";
+#endif
+
+ if (type == module_context::ConsoleOutputNormal)
+ consoleOutputBuffer_.append(output);
+ else
+ consoleErrorBuffer_.append(output);
+ }
+
+ void reset()
+ {
+ sourceFile_ = FilePath();
+ showOutput_ = false;
+ fromCode_ = false;
+ consoleOutputBuffer_.clear();
+ consoleErrorBuffer_.clear();
+ module_context::events().onConsoleOutput.disconnect(
+ boost::bind(&SourceCppContext::onConsoleOutput, this, _1, _2));
+ previousPath_.clear();
+ rToolsWarning_.clear();
+ }
+
+private:
+ FilePath sourceFile_;
+ bool showOutput_;
+ bool fromCode_;
+ std::string consoleOutputBuffer_;
+ std::string consoleErrorBuffer_;
+ std::string previousPath_;
+ std::string rToolsWarning_;
+};
+
+SourceCppContext& sourceCppContext()
+{
+ static SourceCppContext instance;
+ return instance;
+}
+
+
+
+SEXP rs_sourceCppOnBuild(SEXP sFile, SEXP sFromCode, SEXP sShowOutput)
+{
+ std::string file = r::sexp::asString(sFile);
+ FilePath filePath(string_utils::systemToUtf8(file));
+ bool fromCode = r::sexp::asLogical(sFromCode);
+ bool showOutput = r::sexp::asLogical(sShowOutput);
+
+ bool doBuild = sourceCppContext().onBuild(filePath, fromCode, showOutput);
+
+ r::sexp::Protect rProtect;
+ return r::sexp::create(doBuild, &rProtect);
+}
+
+SEXP rs_sourceCppOnBuildComplete(SEXP sSucceeded, SEXP sOutput)
+{
+ bool succeeded = r::sexp::asLogical(sSucceeded);
+
+ std::string output;
+ if (sOutput != R_NilValue)
+ {
+ std::vector<std::string> outputLines;
+ Error error = r::sexp::extract(sOutput, &outputLines);
+ if (error)
+ LOG_ERROR(error);
+ output = boost::algorithm::join(outputLines, "\n");
+ }
+
+ sourceCppContext().onBuildComplete(succeeded, output);
+
+ return R_NilValue;
+}
+
+
+} // anonymous namespace
+
+
+Error initialize()
+{
+
+ // onBuild hook
+ R_CallMethodDef sourceCppOnBuildMethodDef ;
+ sourceCppOnBuildMethodDef.name = "rs_sourceCppOnBuild" ;
+ sourceCppOnBuildMethodDef.fun = (DL_FUNC)rs_sourceCppOnBuild ;
+ sourceCppOnBuildMethodDef.numArgs = 3;
+ r::routines::addCallMethod(sourceCppOnBuildMethodDef);
+
+ // onBuildCompleted hook
+ R_CallMethodDef sourceCppOnBuildCompleteMethodDef ;
+ sourceCppOnBuildCompleteMethodDef.name = "rs_sourceCppOnBuildComplete";
+ sourceCppOnBuildCompleteMethodDef.fun = (DL_FUNC)rs_sourceCppOnBuildComplete;
+ sourceCppOnBuildCompleteMethodDef.numArgs = 2;
+ r::routines::addCallMethod(sourceCppOnBuildCompleteMethodDef);
+
+ return Success();
+}
+
+} // namespace source_cpp
+} // namespace build
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/build/SessionSourceCpp.hpp b/src/cpp/session/modules/build/SessionSourceCpp.hpp
new file mode 100644
index 0000000..a036386
--- /dev/null
+++ b/src/cpp/session/modules/build/SessionSourceCpp.hpp
@@ -0,0 +1,36 @@
+/*
+ * SessionSourceCpp.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_BUILD_SOURCE_CPP_HPP
+#define SESSION_BUILD_SOURCE_CPP_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace build {
+namespace source_cpp {
+
+core::Error initialize();
+
+} // namespace source_cpp
+} // namespace build
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_BUILD_SOURCE_CPP_HPP
+
diff --git a/src/cpp/session/modules/data/DataViewer.cpp b/src/cpp/session/modules/data/DataViewer.cpp
new file mode 100644
index 0000000..f0cdbf2
--- /dev/null
+++ b/src/cpp/session/modules/data/DataViewer.cpp
@@ -0,0 +1,276 @@
+/*
+ * DataViewer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "DataViewer.hpp"
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/StringUtils.hpp>
+
+#define R_INTERNAL_FUNCTIONS
+#include <r/RInternal.hpp>
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RJson.hpp>
+#include <r/ROptions.hpp>
+#include <r/RFunctionHook.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include <session/SessionContentUrls.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace data {
+namespace viewer {
+
+namespace {
+
+void appendTag(std::string* pHTML,
+ const std::string& text,
+ const std::string& tag,
+ const std::string& className = std::string())
+{
+ std::string classAttrib;
+ if (!className.empty())
+ classAttrib = " class=\"" + className + "\"";
+
+ boost::format fmt("<%1%%2%>%3%</%1%>");
+ pHTML->append(boost::str(fmt % tag %
+ classAttrib %
+ string_utils::textToHtml(text)));
+}
+
+void appendTD(std::string* pHTML,
+ const std::string& text,
+ const std::string& className = std::string())
+{
+ appendTag(pHTML, text, "td", className);
+}
+
+void appendTH(std::string* pHTML, const std::string& text)
+{
+ appendTag(pHTML, text, "th");
+}
+
+
+SEXP rs_viewData(SEXP dataSEXP, SEXP captionSEXP)
+{
+ try
+ {
+ // validate title
+ if (!Rf_isString(captionSEXP) || Rf_length(captionSEXP) != 1)
+ throw r::exec::RErrorException("invalid caption argument");
+
+ // validate data
+ if (TYPEOF(dataSEXP) != VECSXP)
+ throw r::exec::RErrorException("invalid data argument (not a list)");
+
+ // validate names (View ensures that length(names) == length(data))
+ SEXP namesSEXP = Rf_getAttrib(dataSEXP, R_NamesSymbol);
+ if (TYPEOF(namesSEXP) != STRSXP ||
+ Rf_length(namesSEXP) != Rf_length(dataSEXP))
+ {
+ throw r::exec::RErrorException(
+ "invalid data argument (names not specified)");
+ }
+
+ // get column count
+ int columnCount = r::sexp::length(dataSEXP);
+
+ // calculate columns to display
+ const int kMaxColumns = 100;
+ int displayedColumns = std::min(columnCount, kMaxColumns);
+
+ // extract caption and column names
+ std::string caption = r::sexp::asString(captionSEXP);
+ std::vector<std::string> columnNames;
+ Error error = r::sexp::extract(namesSEXP, &columnNames);
+ if (error)
+ throw r::exec::RErrorException("invalid names: " +
+ error.code().message());
+
+ // truncate columns names to displayedColumns
+ columnNames.resize(displayedColumns);
+
+ // get column lenghts and then calculate # of rows based on the maximum #
+ // of elements in single column (technically R can pass columns which have
+ // a disparate # of rows to this method)
+ int rowCount = 0;
+ std::vector<int> columnLengths;
+ for (int i=0; i<displayedColumns; i++)
+ {
+ // get the column and record its length (updating rowCount)
+ SEXP columnSEXP = VECTOR_ELT(dataSEXP, i);
+ int columnLength = r::sexp::length(columnSEXP);
+ columnLengths.push_back(columnLength);
+ rowCount = std::max(columnLength, rowCount);
+ }
+
+ // calculate rows to display
+ const int kMaxRows = 1000;
+ int displayedRows = std::min(rowCount, kMaxRows);
+
+ // format the data for presentation
+ r::sexp::Protect rProtect;
+ SEXP formattedDataSEXP = Rf_allocVector(VECSXP, displayedColumns);
+ rProtect.add(formattedDataSEXP);
+ for (int i=0; i<displayedColumns; i++)
+ {
+ SEXP columnSEXP = VECTOR_ELT(dataSEXP, i);
+ SEXP formattedColumnSEXP;
+ r::exec::RFunction formatFx(".rs.formatDataColumn");
+ formatFx.addParam(columnSEXP);
+ formatFx.addParam(displayedRows);
+ Error error = formatFx.call(&formattedColumnSEXP, &rProtect);
+ if (error)
+ throw r::exec::RErrorException(error.summary());
+ SET_VECTOR_ELT(formattedDataSEXP, i, formattedColumnSEXP);
+ }
+
+ // write html header
+ boost::format headerFmt(
+ "<html>\n"
+ " <head>\n"
+ " <title>%1%</title>\n"
+ " <meta charset=\"utf-8\"/>\n"
+ " <link rel=\"stylesheet\" type=\"text/css\" href=\"css/data.css\"/>\n"
+ " </head>\n"
+ " <body>\n");
+ std::string html = boost::str(headerFmt % caption);
+
+ // output begin table & header
+ html += "<table>\n";
+ html += "<thead><tr>\n";
+ html += "<td id=\"origin\"> </td>"; // above row numbers
+ std::for_each(columnNames.begin(),
+ columnNames.end(),
+ boost::bind(appendTH, &html, _1));
+ html += "\n</tr></thead>\n";
+
+ html += "<tbody>\n";
+ // output rows
+ for (int row=0; row<displayedRows; row++)
+ {
+ html += "<tr>\n";
+
+ // row number
+ appendTD(&html, safe_convert::numberToString(row+1), "rn");
+
+ // output a data element from each column where this row is available
+ for (int col=0; col<Rf_length(formattedDataSEXP); col++)
+ {
+ if (columnLengths[col] > row)
+ {
+ SEXP columnSEXP = VECTOR_ELT(formattedDataSEXP, col);
+ SEXP stringSEXP = STRING_ELT(columnSEXP, row);
+ if (stringSEXP != NULL &&
+ stringSEXP != NA_STRING &&
+ r::sexp::length(stringSEXP) > 0)
+ {
+ std::string text(Rf_translateChar(stringSEXP));
+ appendTD(&html, text);
+ }
+ else
+ {
+ html += "<td> </td>";
+ }
+ }
+ else
+ {
+ html += "<td> </td>";
+ }
+ }
+
+ html += "\n</tr>\n";
+ }
+ html += "</tbody>\n";
+
+ // append table footer
+ html += "\n</table>\n";
+
+ // append document footer
+ html += "</body></html>\n";
+
+
+ // compute variables based on presence of row.names
+ int variables = columnCount;
+ if (columnNames.size() > 0 && columnNames[0] == "row.names")
+ variables--;
+
+ // fire show data event
+ json::Object dataItem;
+ dataItem["caption"] = caption;
+ dataItem["totalObservations"] = rowCount;
+ dataItem["displayedObservations"] = displayedRows;
+ dataItem["variables"] = variables;
+ dataItem["displayedVariables"] = displayedColumns;
+ dataItem["contentUrl"] = content_urls::provision(caption, html, ".htm");
+ ClientEvent event(client_events::kShowData, dataItem);
+ module_context::enqueClientEvent(event);
+
+ // done
+ return R_NilValue;
+ }
+ catch(r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // keep compiler happy
+ return R_NilValue;
+}
+
+
+} // anonymous namespace
+
+Error initialize()
+{
+ // register viewData method
+ R_CallMethodDef methodDef ;
+ methodDef.name = "rs_viewData" ;
+ methodDef.fun = (DL_FUNC) rs_viewData ;
+ methodDef.numArgs = 2;
+ r::routines::addCallMethod(methodDef);
+
+ using boost::bind;
+ using namespace r::function_hook ;
+ using namespace session::module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(sourceModuleRFile, "SessionDataViewer.R"));
+
+ return initBlock.execute();
+}
+
+
+} // namespace viewer
+} // namespace data
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/data/DataViewer.hpp b/src/cpp/session/modules/data/DataViewer.hpp
new file mode 100644
index 0000000..d0d988f
--- /dev/null
+++ b/src/cpp/session/modules/data/DataViewer.hpp
@@ -0,0 +1,35 @@
+/*
+ * DataViewer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_DATA_VIEWER_HPP
+#define SESSION_DATA_VIEWER_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace data {
+namespace viewer {
+
+core::Error initialize();
+
+} // namespace viewer
+} // namespace data
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_DATA_VIEWER_HPP
diff --git a/src/cpp/session/modules/data/SessionData.cpp b/src/cpp/session/modules/data/SessionData.cpp
new file mode 100644
index 0000000..d0dd31d
--- /dev/null
+++ b/src/cpp/session/modules/data/SessionData.cpp
@@ -0,0 +1,45 @@
+/*
+ * SessionData.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionData.hpp"
+
+#include <core/Exec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "DataViewer.hpp"
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace data {
+
+Error initialize()
+{
+ using boost::bind;
+ using namespace session::module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (data::viewer::initialize)
+ (bind(sourceModuleRFile, "SessionDataImport.R"));
+
+ return initBlock.execute();
+}
+
+} // namespace data
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/data/SessionData.hpp b/src/cpp/session/modules/data/SessionData.hpp
new file mode 100644
index 0000000..04d9898
--- /dev/null
+++ b/src/cpp/session/modules/data/SessionData.hpp
@@ -0,0 +1,35 @@
+/*
+ * SessionData.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_DATA_HPP
+#define SESSION_SESSION_DATA_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace data {
+
+core::Error initialize();
+
+} // namespace data
+} // namepace handlers
+} // namesapce session
+
+#endif // SESSION_SESSION_DATA_HPP
diff --git a/src/cpp/session/modules/environment/EnvironmentMonitor.cpp b/src/cpp/session/modules/environment/EnvironmentMonitor.cpp
new file mode 100644
index 0000000..8ecfb6e
--- /dev/null
+++ b/src/cpp/session/modules/environment/EnvironmentMonitor.cpp
@@ -0,0 +1,236 @@
+/*
+ * EnvironmentMonitor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "EnvironmentMonitor.hpp"
+
+#include <r/RSexp.hpp>
+#include <r/RInterface.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "EnvironmentUtils.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace environment {
+namespace {
+
+bool compareVarName(const r::sexp::Variable& var1,
+ const r::sexp::Variable& var2)
+{
+ return var1.first < var2.first;
+}
+
+void enqueRefreshEvent()
+{
+ ClientEvent refreshEvent(client_events::kEnvironmentRefresh);
+ module_context::enqueClientEvent(refreshEvent);
+}
+
+// if the given variable is an unevaluated promise, add it to the given
+// list of variables
+void addUnevaledPromise(std::vector<r::sexp::Variable>* pEnv,
+ const r::sexp::Variable& var)
+{
+ if (isUnevaluatedPromise(var.second))
+ {
+ pEnv->push_back(var);
+ }
+}
+
+// If the given variable exists in the given list, remove it. Compares on name
+// only.
+void removeVarFromList(std::vector<r::sexp::Variable>* pEnv,
+ const r::sexp::Variable& var)
+{
+ for (std::vector<r::sexp::Variable>::iterator iter = pEnv->begin();
+ iter != pEnv->end(); iter++)
+ {
+ if (iter->first == var.first)
+ {
+ pEnv->erase(iter);
+ break;
+ }
+ }
+}
+
+} // anonymous namespace
+
+EnvironmentMonitor::EnvironmentMonitor() :
+ initialized_(false),
+ refreshOnInit_(false)
+{}
+
+void EnvironmentMonitor::enqueRemovedEvent(const r::sexp::Variable& variable)
+{
+ ClientEvent removedEvent(client_events::kEnvironmentRemoved, variable.first);
+ module_context::enqueClientEvent(removedEvent);
+}
+
+void EnvironmentMonitor::enqueAssignedEvent(const r::sexp::Variable& variable)
+{
+ // get object info
+ json::Value objInfo = varToJson(getMonitoredEnvironment(), variable);
+
+ // enque event
+ ClientEvent assignedEvent(client_events::kEnvironmentAssigned, objInfo);
+ module_context::enqueClientEvent(assignedEvent);
+}
+
+void EnvironmentMonitor::setMonitoredEnvironment(SEXP pEnvironment,
+ bool refresh)
+{
+ // ignore if we're already monitoring this environment
+ if (getMonitoredEnvironment() == pEnvironment)
+ return;
+
+ environment_.set(pEnvironment);
+
+ // init the environment by doing an initial check for changes
+ initialized_ = false;
+ refreshOnInit_ = refresh;
+ checkForChanges();
+}
+
+SEXP EnvironmentMonitor::getMonitoredEnvironment()
+{
+ return environment_.get();
+}
+
+bool EnvironmentMonitor::hasEnvironment()
+{
+ return getMonitoredEnvironment() != NULL;
+}
+
+void EnvironmentMonitor::listEnv(std::vector<r::sexp::Variable>* pEnv)
+{
+ r::sexp::Protect rProtect;
+ r::sexp::listEnvironment(getMonitoredEnvironment(), false, &rProtect, pEnv);
+}
+
+void EnvironmentMonitor::checkForChanges()
+{
+ // information about the current environment
+ std::vector<r::sexp::Variable> currentEnv ;
+ std::vector<r::sexp::Variable> currentPromises;
+
+ // list of assigns/removes (includes both value changes and promise
+ // evaluations)
+ std::vector<r::sexp::Variable> addedVars;
+ std::vector<r::sexp::Variable> removedVars;
+
+ // get the set of variables and promises in the current environment
+ listEnv(¤tEnv);
+
+ // R returns an environment list sorted in dictionary order. Since the
+ // set difference algorithms below use simple string comparisons to
+ // establish order, we need to re-sort the list into canonical order
+ // to avoid the algorithms detecting superfluous insertions.
+ std::sort(currentEnv.begin(), currentEnv.end(), compareVarName);
+
+ std::for_each(currentEnv.begin(), currentEnv.end(),
+ boost::bind(addUnevaledPromise, ¤tPromises, _1));
+
+ bool refreshEnqueued = false;
+ if (!initialized_)
+ {
+ if (refreshOnInit_ ||
+ getMonitoredEnvironment() == R_GlobalEnv)
+ {
+ enqueRefreshEvent();
+ refreshEnqueued = true;
+ }
+ initialized_ = true;
+ refreshOnInit_ = false;
+ }
+ else
+ {
+ if (currentEnv != lastEnv_)
+ {
+ // optimize for empty currentEnv (user reset workspace) or empty
+ // lastEnv_ (startup) by just sending a single refresh event
+ // only do this for the global environment--while debugging local
+ // environments, the environment object list is sent down as part of
+ // the context depth event.
+ if ((currentEnv.empty() || lastEnv_.empty())
+ && getMonitoredEnvironment() == R_GlobalEnv)
+ {
+ enqueRefreshEvent();
+ refreshEnqueued = true;
+ }
+ else
+ {
+ std::set_difference(lastEnv_.begin(), lastEnv_.end(),
+ currentEnv.begin(), currentEnv.end(),
+ std::back_inserter(removedVars),
+ compareVarName);
+
+ // fire removed event for deletes
+ std::for_each(removedVars.begin(),
+ removedVars.end(),
+ boost::bind(&EnvironmentMonitor::enqueRemovedEvent,
+ this, _1));
+
+ // remove deleted objects from the list of uneval'ed promises
+ // so we'll stop monitoring them for evaluation
+ std::for_each(removedVars.begin(),
+ removedVars.end(),
+ boost::bind(removeVarFromList, &unevaledPromises_, _1));
+
+ // find adds & assigns (all variable name/value combinations in the
+ // current environment but NOT in the previous environment)
+ std::set_difference(currentEnv.begin(), currentEnv.end(),
+ lastEnv_.begin(), lastEnv_.end(),
+ std::back_inserter(addedVars));
+
+ // remove assigned objects from the list of uneval'ed promises
+ // (otherwise, we double-assign in the case where a promise SEXP
+ // is simultaneously forced/evaluated and assigned a new value)
+ std::for_each(addedVars.begin(),
+ addedVars.end(),
+ boost::bind(removeVarFromList, &unevaledPromises_, _1));
+
+ }
+ }
+ // if a refresh is scheduled there's no need to emit add events one by one
+ if (!refreshEnqueued)
+ {
+ // have any promises been evaluated since we last checked?
+ if (currentPromises != unevaledPromises_)
+ {
+ // for each promise that is in the set of promises we are monitoring
+ // for evaluation but not in the set of currently tracked promises,
+ // we assume this to be an eval--process as an assign
+ std::set_difference(unevaledPromises_.begin(), unevaledPromises_.end(),
+ currentPromises.begin(), currentPromises.end(),
+ std::back_inserter(addedVars));
+ }
+
+ // fire assigned event for adds, assigns, and promise evaluations
+ std::for_each(addedVars.begin(),
+ addedVars.end(),
+ boost::bind(&EnvironmentMonitor::enqueAssignedEvent,
+ this, _1));
+ }
+ }
+
+ unevaledPromises_ = currentPromises;
+ lastEnv_ = currentEnv;
+}
+
+} // namespace environment
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/environment/EnvironmentMonitor.hpp b/src/cpp/session/modules/environment/EnvironmentMonitor.hpp
new file mode 100644
index 0000000..0c11325
--- /dev/null
+++ b/src/cpp/session/modules/environment/EnvironmentMonitor.hpp
@@ -0,0 +1,47 @@
+/*
+ * EnvironmentMonitor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <r/RSexp.hpp>
+#include <r/RInterface.hpp>
+
+namespace session {
+namespace modules {
+namespace environment {
+
+// EnvironmentMonitor listens for changes to objects in the given environment
+// context, and emits object add/remove events.
+class EnvironmentMonitor : boost::noncopyable
+{
+public:
+ EnvironmentMonitor();
+ void setMonitoredEnvironment(SEXP pEnvironment, bool refresh = false);
+ SEXP getMonitoredEnvironment();
+ bool hasEnvironment();
+ void checkForChanges();
+private:
+ void listEnv(std::vector<r::sexp::Variable>* pEnvironment);
+ void enqueRemovedEvent(const r::sexp::Variable& variable);
+ void enqueAssignedEvent(const r::sexp::Variable& variable);
+
+ std::vector<r::sexp::Variable> lastEnv_;
+ std::vector<r::sexp::Variable> unevaledPromises_;
+ r::sexp::PreservedSEXP environment_;
+ bool initialized_;
+ bool refreshOnInit_;
+};
+
+} // namespace environment
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/environment/EnvironmentUtils.cpp b/src/cpp/session/modules/environment/EnvironmentUtils.cpp
new file mode 100644
index 0000000..46c923c
--- /dev/null
+++ b/src/cpp/session/modules/environment/EnvironmentUtils.cpp
@@ -0,0 +1,223 @@
+/*
+ * EnvironmentUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "EnvironmentUtils.hpp"
+
+#include <algorithm>
+
+#include <r/RExec.hpp>
+#include <r/RJson.hpp>
+#include <core/FileSerializer.hpp>
+#include <session/SessionModuleContext.hpp>
+
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace environment {
+namespace {
+
+// the string sent to the client when we're unable to get the display value
+// of a variable
+const char UNKNOWN_VALUE[] = "<unknown>";
+
+json::Value descriptionOfVar(SEXP var)
+{
+ std::string value;
+ Error error = r::exec::RFunction(
+ isUnevaluatedPromise(var) ?
+ ".rs.promiseDescription" :
+ ".rs.valueDescription",
+ var).call(&value);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Value(); // return null
+ }
+ else
+ {
+ return value;
+ }
+}
+
+} // anonymous namespace
+
+// a variable is an unevaluated promise if its promise value is still unbound
+bool isUnevaluatedPromise (SEXP var)
+{
+ return (TYPEOF(var) == PROMSXP) && (PRVALUE(var) == R_UnboundValue);
+}
+
+// convert a language variable to a value. language variables are special in
+// that we can't allow them to be evaluated (doing so may e.g. trigger early
+// evaluation of a call), so instead we pass the name of the variable and a
+// reference to its environment so the lookup only happens in the context of
+// the R session.
+json::Value languageVarToJson(SEXP env, std::string objectName)
+{
+ std::string value;
+ Error error = r::exec::RFunction(".rs.languageDescription",
+ env, objectName)
+ .call(&value);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return UNKNOWN_VALUE;
+ }
+ else
+ {
+ return value;
+ }
+}
+
+json::Value varToJson(SEXP env, const r::sexp::Variable& var)
+{
+ json::Object varJson;
+ SEXP varSEXP = var.second;
+
+ // We can get a value from almost any object type from R, but there are
+ // a few cases in which attempting to inspect the object will lead to
+ // undesirable behavior. For these special value types, construct the
+ // object definition manually.
+ if ((varSEXP == R_UnboundValue) ||
+ (varSEXP == R_MissingArg) ||
+ isUnevaluatedPromise(varSEXP) ||
+ r::sexp::isActiveBinding(var.first, env))
+ {
+ varJson["name"] = var.first;
+ if (isUnevaluatedPromise(varSEXP))
+ {
+ varJson["type"] = std::string("promise");
+ varJson["value"] = descriptionOfVar(varSEXP);
+ }
+ else if (r::sexp::isActiveBinding(var.first, env))
+ {
+ varJson["type"] = std::string("active binding");
+ varJson["value"] = std::string("<Active binding>");
+ }
+ else
+ {
+ varJson["type"] = std::string("unknown");
+ varJson["value"] = (varSEXP == R_MissingArg) ?
+ descriptionOfVar(varSEXP) :
+ UNKNOWN_VALUE;
+ }
+ varJson["description"] = std::string("");
+ varJson["contents"] = json::Array();
+ varJson["length"] = 0;
+ varJson["size"] = 0;
+ varJson["contents_deferred"] = false;
+ }
+ // For all other value types, construct the definition normally.
+ else
+ {
+ SEXP description;
+ json::Value val;
+ r::sexp::Protect protect;
+ Error error = r::exec::RFunction(".rs.describeObject",
+ env, var.first)
+ .call(&description, &protect);
+ if (error)
+ LOG_ERROR(error);
+ else
+ {
+ error = r::json::jsonValueFromObject(description, &val);
+ if (error)
+ LOG_ERROR(error);
+ else
+ return val;
+ }
+ }
+ return varJson;
+}
+
+bool functionDiffersFromSource(
+ SEXP srcRef,
+ const std::string& functionCode)
+{
+ std::string fileName;
+ Error error = r::exec::RFunction(".rs.sourceFileFromRef", srcRef)
+ .call(&fileName);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return true;
+ }
+
+ // check for ~/.active-rstudio-document -- we never want to match sources
+ // in this file, as it's used to source unsaved changes from RStudio
+ // editor buffers. don't match sources to an empty filename, either
+ // (this will resolve to the user's home directory below).
+ boost::algorithm::trim(fileName);
+ if (fileName == "~/.active-rstudio-document" ||
+ fileName.length() == 0)
+ {
+ return true;
+ }
+
+ // make sure the file exists and isn't a directory
+ FilePath sourceFilePath = module_context::resolveAliasedPath(fileName);
+ if (!sourceFilePath.exists() ||
+ sourceFilePath.isDirectory())
+ {
+ return true;
+ }
+
+ // read the portion of the file pointed to by the source refs from disk
+ // the sourceref structure (including the array offsets used below)
+ // is documented here:
+ // http://journal.r-project.org/archive/2010-2/RJournal_2010-2_Murdoch.pdf
+ std::string fileContent;
+ error = readStringFromFile(
+ sourceFilePath,
+ &fileContent,
+ string_utils::LineEndingPosix,
+ INTEGER(srcRef)[0], // the first line
+ INTEGER(srcRef)[2], // the last line
+ INTEGER(srcRef)[4], // character position on the first line
+ INTEGER(srcRef)[5] // character position on the last line
+ );
+ if (error)
+ {
+ LOG_ERROR(error);
+ return true;
+ }
+ return functionCode != fileContent;
+}
+
+// given a source reference and a JSON object, add the line and character data
+// from the source reference to the JSON object.
+void sourceRefToJson(const SEXP srcref, json::Object* pObject)
+{
+ if (srcref == NULL || r::sexp::isNull(srcref))
+ {
+ (*pObject)["line_number"] = 0;
+ (*pObject)["end_line_number"] = 0;
+ (*pObject)["character_number"] = 0;
+ (*pObject)["end_character_number"] = 0;
+ }
+ else
+ {
+ (*pObject)["line_number"] = INTEGER(srcref)[0];
+ (*pObject)["end_line_number"] = INTEGER(srcref)[2];
+ (*pObject)["character_number"] = INTEGER(srcref)[4];
+ (*pObject)["end_character_number"] = INTEGER(srcref)[5];
+ }
+}
+
+} // namespace environment
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/environment/EnvironmentUtils.hpp b/src/cpp/session/modules/environment/EnvironmentUtils.hpp
new file mode 100644
index 0000000..5e80ae3
--- /dev/null
+++ b/src/cpp/session/modules/environment/EnvironmentUtils.hpp
@@ -0,0 +1,30 @@
+/*
+ * EnvironmentUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/json/Json.hpp>
+#include <r/RSexp.hpp>
+
+namespace session {
+namespace modules {
+namespace environment {
+
+core::json::Value varToJson(SEXP env, const r::sexp::Variable& var);
+bool isUnevaluatedPromise(SEXP var);
+bool functionDiffersFromSource(SEXP srcRef, const std::string& functionCode);
+void sourceRefToJson(const SEXP srcref, core::json::Object* pObject);
+
+} // namespace environment
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/environment/SessionEnvironment.cpp b/src/cpp/session/modules/environment/SessionEnvironment.cpp
new file mode 100644
index 0000000..579d361
--- /dev/null
+++ b/src/cpp/session/modules/environment/SessionEnvironment.cpp
@@ -0,0 +1,1170 @@
+/*
+ * SessionEnvironment.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionEnvironment.hpp"
+#include "EnvironmentMonitor.hpp"
+
+#include <algorithm>
+
+#include <core/Exec.hpp>
+
+#define INTERNAL_R_FUNCTIONS
+#include <r/RJson.hpp>
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/session/RSession.hpp>
+#include <r/RInterface.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionSourceDatabase.hpp>
+#include <session/SessionPersistentState.hpp>
+#include <boost/foreach.hpp>
+
+#include "EnvironmentUtils.hpp"
+
+#define TOP_FUNCTION 1
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace environment {
+
+// allocate on the heap so we control timing of destruction (if we leave it
+// to the destructor we might release the underlying environment SEXP after
+// R has already shut down)
+EnvironmentMonitor* s_pEnvironmentMonitor = NULL;
+
+namespace {
+
+// Keeps track of the data related to the most recent debugging event
+class LineDebugState
+{
+ public:
+ LineDebugState()
+ {
+ reset();
+ }
+ void reset()
+ {
+ lastDebugText = "";
+ lastDebugLine = 0;
+ }
+ std::string lastDebugText;
+ int lastDebugLine;
+};
+
+// The environment monitor and friends do work in reponse to events in R.
+// In rare cases, this work can trigger the same events in R that are
+// being responded to, leading to unwanted recursion. This simple guard
+// increments the given counter on construction (and decrements on destruction)
+// so vulnerable event handlers below can prevent reentrancy.
+class EventRecursionGuard
+{
+public:
+ EventRecursionGuard(int& counter): counter_(counter) { counter_++; }
+ ~EventRecursionGuard() { counter_--; }
+private:
+ int& counter_;
+};
+
+bool isValidSrcref(SEXP srcref)
+{
+ return srcref && TYPEOF(srcref) != NILSXP;
+}
+
+bool handleRBrowseEnv(const core::FilePath& filePath)
+{
+ if (filePath.filename() == "wsbrowser.html")
+ {
+ module_context::showContent("R objects", filePath);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+RCNTXT* firstFunctionContext(RCNTXT* start)
+{
+ RCNTXT* firstFunContext = start;
+ while ((firstFunContext->callfun == NULL ||
+ firstFunContext->callfun == R_NilValue) &&
+ firstFunContext->callflag)
+ firstFunContext = firstFunContext->nextcontext;
+ return firstFunContext;
+}
+
+SEXP getOriginalFunctionCallObject(const RCNTXT* pContext)
+{
+ SEXP callObject = pContext->callfun;
+ // enabling tracing on a function turns it into an S4 object with an
+ // 'original' slot that includes the function's original contents. use
+ // this instead if it's set up. (consider: is it safe to assume that S4
+ // objects here are always traced functions, or do we need to compare classes
+ // to be safe?)
+ if (Rf_isS4(callObject))
+ {
+ callObject = r::sexp::getAttrib(callObject, "original");
+ }
+ return callObject;
+}
+
+Error getFileNameFromContext(const RCNTXT* pContext,
+ std::string* pFileName)
+{
+ SEXP srcref = pContext->srcref;
+ if (isValidSrcref(srcref))
+ {
+ return r::exec::RFunction(".rs.sourceFileFromRef", srcref)
+ .call(pFileName);
+ }
+ else
+ {
+ // If no source references, that's OK--just set an empty filename.
+ pFileName->clear();
+ return Success();
+ }
+}
+
+// Construct a simulated source reference from a context containing a
+// function being debugged, and either the context containing the current
+// invocation or a string containing the last debug ouput from R.
+// We use this to highlight portions of deparsed functions when visually
+// stepping through code for which source references are unvailable.
+SEXP simulatedSourceRefsOfContext(const RCNTXT* pContext,
+ const RCNTXT* pLineContext,
+ const LineDebugState* pLineDebugState)
+{
+ SEXP simulatedSrcref = R_NilValue;
+ r::sexp::Protect protect;
+ // The objects we will later transmit to .rs.simulateSourceRefs below
+ // include language objects that we need to protect from early evaluation.
+ // Attach them to a carrier SEXP as attributes rather than passing directly.
+ SEXP info = r::sexp::create("_rs_sourceinfo", &protect);
+ r::sexp::setAttrib(info, "_rs_callfun", pContext->callfun);
+ if (pLineContext != NULL)
+ {
+ r::sexp::setAttrib(info, "_rs_callobj", pLineContext->call);
+ }
+ else if (pLineDebugState != NULL)
+ {
+ SEXP lastDebugSEXP = r::sexp::create(
+ pLineDebugState->lastDebugText, &protect);
+ r::sexp::setAttrib(info, "_rs_calltext", lastDebugSEXP);
+ SEXP lastLineSEXP = r::sexp::create(
+ pLineDebugState->lastDebugLine, &protect);
+ r::sexp::setAttrib(info, "_rs_lastline", lastLineSEXP);
+ }
+ Error error = r::exec::RFunction(".rs.simulateSourceRefs", info)
+ .call(&simulatedSrcref, &protect);
+ if (error)
+ LOG_ERROR(error);
+ return simulatedSrcref;
+}
+
+SEXP sourceRefsOfContext(const RCNTXT* pContext)
+{
+ return r::sexp::getAttrib(getOriginalFunctionCallObject(pContext), "srcref");
+}
+
+void getShinyFunctionLabel(const RCNTXT* pContext, std::string* label)
+{
+ SEXP s = r::sexp::getAttrib(
+ getOriginalFunctionCallObject(pContext), "_rs_shinyDebugLabel");
+ if (s != NULL && TYPEOF(s) != NILSXP)
+ {
+ r::sexp::extract(s, label);
+ }
+}
+
+bool hasSourceRefs(const RCNTXT* pContext)
+{
+ return isValidSrcref(sourceRefsOfContext(pContext));
+}
+
+bool isDebugHiddenContext(RCNTXT* pContext)
+{
+ SEXP hideFlag = r::sexp::getAttrib(pContext->callfun, "hideFromDebugger");
+ return TYPEOF(hideFlag) != NILSXP && r::sexp::asLogical(hideFlag);
+}
+
+bool isErrorHandlerContext(RCNTXT* pContext)
+{
+ SEXP errFlag = r::sexp::getAttrib(pContext->callfun, "errorHandlerType");
+ return TYPEOF(errFlag) == INTSXP;
+}
+
+// return the function context at the given depth
+RCNTXT* getFunctionContext(const int depth,
+ bool findUserCode = false,
+ int* pFoundDepth = NULL,
+ SEXP* pEnvironment = NULL)
+{
+ RCNTXT* pRContext = r::getGlobalContext();
+ RCNTXT* pSrcContext = pRContext;
+ int currentDepth = 0;
+ bool foundUserCode = false;
+ RCNTXT* pErrContext = NULL;
+ int errorDepth = 0;
+ while (pRContext->callflag)
+ {
+ if (pRContext->callflag & CTXT_FUNCTION)
+ {
+ // If the caller asked us to find user code, don't stop unless the
+ // context we're examining meets the following criteria:
+ // 1) has a valid source ref (i.e. we have the user code associated
+ // with the context
+ // 2) source ref is not a duplicate of the source ref from the
+ // previous frame. R <= 2.15.0 appears to have a bug wherein error
+ // handlers have a srcref that points not to the handler itself but
+ // to the error, so the error source reference appears twice
+ // consecutively on the stack. This duplicate reference should not
+ // be considered real user code since we don't want to break into
+ // the error handler.
+ if (++currentDepth >= depth &&
+ !isDebugHiddenContext(pRContext) &&
+ !(findUserCode && (!isValidSrcref(pSrcContext->srcref) ||
+ (pRContext != pSrcContext &&
+ pRContext->srcref == pSrcContext->srcref))))
+ {
+ foundUserCode = true;
+ break;
+ }
+ // Record the depth at which the error handler was found (if at all);
+ // we will default to reporting code at the function that invoked
+ // the handler, which is two functions down.
+ if (findUserCode && isErrorHandlerContext(pRContext))
+ {
+ pErrContext = getFunctionContext(currentDepth + 2, false,
+ &errorDepth);
+ }
+ }
+ pRContext = pRContext->nextcontext;
+ }
+
+ // indicate the depth at which we stopped and the environment we found at
+ // that depth, if requested
+ if (pFoundDepth)
+ {
+ *pFoundDepth = currentDepth;
+ }
+ if (pEnvironment)
+ {
+ *pEnvironment = currentDepth == 0 ? R_GlobalEnv : pRContext->cloenv;
+ }
+ if (depth == TOP_FUNCTION && findUserCode && !foundUserCode)
+ {
+ if (pErrContext != NULL)
+ {
+ // if there's an error handler on the stack, report the "user" code to
+ // be the function that invoked the handler.
+ pRContext = pErrContext;
+ *pFoundDepth = errorDepth;
+ if (pEnvironment)
+ *pEnvironment = pErrContext->cloenv;
+ }
+ else
+ {
+ // if we were looking for the top user-mode function on the stack but
+ // found nothing, return the top of the stack rather than the bottom.
+ if (pEnvironment)
+ *pEnvironment = r::getGlobalContext()->cloenv;
+ if (pFoundDepth)
+ *pFoundDepth = 1;
+ }
+ pRContext = r::getGlobalContext();
+ }
+ return pRContext;
+}
+
+// Return whether we're in browse context--meaning that there's a browser on
+// the context stack and at least one function (i.e. we're not browsing at the
+// top level).
+bool inBrowseContext()
+{
+ RCNTXT* pRContext = r::getGlobalContext();
+ bool foundBrowser = false;
+ bool foundFunction = false;
+ while (pRContext->callflag)
+ {
+ if ((pRContext->callflag & CTXT_BROWSER) &&
+ !(pRContext->callflag & CTXT_FUNCTION))
+ {
+ foundBrowser = true;
+ }
+ else if (pRContext->callflag & CTXT_FUNCTION)
+ {
+ foundFunction = true;
+ }
+ if (foundBrowser && foundFunction)
+ {
+ return true;
+ }
+ pRContext = pRContext->nextcontext;
+ }
+ return false;
+}
+
+// Return whether the current context is being evaluated inside a hidden
+// (debugger internal) function at the top level.
+bool insideDebugHiddenFunction()
+{
+ RCNTXT* pRContext = r::getGlobalContext();
+ while (pRContext->callflag)
+ {
+ if (pRContext->callflag & CTXT_FUNCTION)
+ {
+ // If we find a debugger internal function before any user function,
+ // hide it from the user callstack.
+ if (isDebugHiddenContext(pRContext))
+ return true;
+
+ // If we find a user function before we encounter a debugger internal
+ // function, don't hide the user code it invokes.
+ if (hasSourceRefs(pRContext))
+ return false;
+ }
+ pRContext = pRContext->nextcontext;
+ }
+ return false;
+}
+
+Error functionNameFromContext(const RCNTXT* pContext,
+ std::string* pFunctionName)
+{
+ SEXP functionName;
+ r::sexp::Protect protect;
+ SEXP val = r::sexp::create("_rs_callval", &protect);
+ r::sexp::setAttrib(val, "_rs_call", pContext->call);
+ Error error = r::exec::RFunction(".rs.functionNameFromCall", val)
+ .call(&functionName, &protect);
+ if (!error && r::sexp::length(functionName) > 0)
+ {
+ error = r::sexp::extract(functionName, pFunctionName);
+ }
+ else
+ {
+ pFunctionName->clear();
+ }
+ return error;
+}
+
+// Return the call frames and debug information as a JSON object.
+json::Array callFramesAsJson(LineDebugState* pLineDebugState)
+{
+ RCNTXT* pRContext = r::getGlobalContext();
+ RCNTXT* pPrevContext = pRContext;
+ RCNTXT* pSrcContext = pRContext;
+ json::Array listFrames;
+ int contextDepth = 0;
+ Error error;
+
+ while (pRContext->callflag)
+ {
+ if (pRContext->callflag & CTXT_FUNCTION)
+ {
+ json::Object varFrame;
+ std::string functionName;
+ varFrame["context_depth"] = ++contextDepth;
+
+ error = functionNameFromContext(pRContext, &functionName);
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ varFrame["function_name"] = functionName;
+ varFrame["is_error_handler"] = isErrorHandlerContext(pRContext);
+ varFrame["is_hidden"] = isDebugHiddenContext(pRContext);
+
+ // in the linked list of R contexts, the srcref associated with each
+ // context points to the place from which the context was invoked.
+ // however, for traditional debugging, we want the call frame to show
+ // where control *left* the frame to go to the next frame. pSrcContext
+ // keeps track of the previous invocation.
+ std::string filename;
+ error = getFileNameFromContext(pSrcContext, &filename);
+ if (error)
+ LOG_ERROR(error);
+ varFrame["file_name"] = filename;
+
+ SEXP srcref = pSrcContext->srcref;
+ if (isValidSrcref(srcref))
+ {
+ varFrame["real_sourceref"] = true;
+ sourceRefToJson(srcref, &varFrame);
+ }
+ else
+ {
+ varFrame["real_sourceref"] = false;
+ // if this is the top frame, we simulate the sourceref using R
+ // output of the last debugged statement; if it isn't, we
+ // construct it by deparsing calls in the context stack.
+ SEXP simulatedSrcref;
+ if (contextDepth == 1 &&
+ pLineDebugState != NULL &&
+ pLineDebugState->lastDebugText.length() > 0)
+ simulatedSrcref =
+ simulatedSourceRefsOfContext(pRContext, NULL,
+ pLineDebugState);
+ else
+ simulatedSrcref =
+ simulatedSourceRefsOfContext(pRContext, pPrevContext,
+ NULL);
+
+ // store the line stepped over in the top frame, so we can infer
+ // that the next line stepped over will be near this one
+ if (contextDepth == 1 &&
+ pLineDebugState != NULL &&
+ isValidSrcref(simulatedSrcref))
+ {
+ int stepLine = INTEGER(simulatedSrcref)[0];
+ if (stepLine > 0)
+ pLineDebugState->lastDebugLine = stepLine;
+ }
+
+ sourceRefToJson(simulatedSrcref, &varFrame);
+ }
+ pSrcContext = pRContext;
+
+ // extract the first line of the function. the client can optionally
+ // use this to compute the source location as an offset into the
+ // function rather than as an absolute file position (useful when
+ // we need to debug a copy of the function rather than the real deal).
+ SEXP srcRef = sourceRefsOfContext(pSrcContext);
+ if (isValidSrcref(srcRef))
+ {
+ varFrame["function_line_number"] = INTEGER(srcRef)[0];
+ }
+ else
+ {
+ // if we don't have a source ref, we'll debug using a deparsed
+ // version of the function that starts on line 1
+ varFrame["function_line_number"] = 1;
+ }
+
+ std::string argList;
+ SEXP args = CDR(pRContext->call);
+ switch (TYPEOF(args))
+ {
+ case LISTSXP:
+ error = r::exec::RFunction(".rs.argumentListSummary", args)
+ .call(&argList);
+ break;
+ case LANGSXP:
+ error = r::exec::RFunction(".rs.promiseDescription", args)
+ .call(&argList);
+ break;
+ }
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+ varFrame["argument_list"] = error ? "" : argList;
+
+ // If this is a Shiny function, provide its label
+ std::string shinyLabel;
+ getShinyFunctionLabel(pRContext, &shinyLabel);
+ varFrame["shiny_function_label"] = shinyLabel;
+
+ listFrames.push_back(varFrame);
+ }
+ pPrevContext = pRContext;
+ pRContext = pRContext->nextcontext;
+ }
+ return listFrames;
+}
+
+json::Array environmentListAsJson()
+{
+ using namespace r::sexp;
+ Protect rProtect;
+ std::vector<Variable> vars;
+ json::Array listJson;
+
+ if (s_pEnvironmentMonitor->hasEnvironment())
+ {
+ SEXP env = s_pEnvironmentMonitor->getMonitoredEnvironment();
+ if (env != NULL)
+ listEnvironment(env, false, &rProtect, &vars);
+
+ // get object details and transform to json
+ std::transform(vars.begin(),
+ vars.end(),
+ std::back_inserter(listJson),
+ boost::bind(varToJson, env, _1));
+ }
+
+ return listJson;
+}
+
+Error listEnvironment(boost::shared_ptr<int> pContextDepth,
+ const json::JsonRpcRequest&,
+ json::JsonRpcResponse* pResponse)
+{
+ // return list
+ pResponse->setResult(environmentListAsJson());
+ return Success();
+}
+
+// Sets an environment by name. Used when the environment can be reliably
+// identified by its name (e.g. package environments).
+Error setEnvironmentName(int contextDepth,
+ RCNTXT* pContext,
+ std::string environmentName)
+{
+ SEXP environment;
+ if (environmentName == "R_GlobalEnv")
+ {
+ environment = R_GlobalEnv;
+ }
+ else if (environmentName == "base")
+ {
+ environment = R_BaseEnv;
+ }
+ else
+ {
+ r::sexp::Protect protect;
+ // We need to traverse the search path manually looking for an environment
+ // whose name matches the one the caller requested, because R's
+ // as.environment() function only searches the global search path, and
+ // we may wish to set an environment whose name only exists in a private
+ // environment chain.
+ //
+ // This would be better wrapped in an R function, but this code may
+ // run during session init when tools:rstudio isn't yet attached to the
+ // search path.
+ SEXP env = contextDepth > 0 ?
+ pContext->cloenv :
+ R_GlobalEnv;
+ std::string candidateEnv;
+ Error error;
+ while (env != R_EmptyEnv)
+ {
+ error = r::exec::RFunction("environmentName", env).call(&candidateEnv);
+ if (error)
+ break;
+ if (candidateEnv == environmentName)
+ {
+ environment = env;
+ break;
+ }
+ // Proceed to the parent of the environment
+ env = ENCLOS(env);
+ }
+ if (error || env == R_EmptyEnv)
+ {
+ s_pEnvironmentMonitor->setMonitoredEnvironment(R_GlobalEnv, true);
+ return error;
+ }
+ }
+
+ s_pEnvironmentMonitor->setMonitoredEnvironment(environment, true);
+ return Success();
+}
+
+Error setEnvironment(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<RCNTXT*> pCurrentContext,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string environmentName;
+ Error error = json::readParam(request.params, 0, &environmentName);
+ if (error)
+ return error;
+
+ error = setEnvironmentName(*pContextDepth,
+ *pCurrentContext,
+ environmentName);
+ if (error)
+ return error;
+
+ persistentState().setActiveEnvironmentName(environmentName);
+ return Success();
+}
+
+// Sets an environment by its frame number. Used for unnamed, transient
+// function environments.
+Error setEnvironmentFrame(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ int frameNumber = 0;
+ Error error = json::readParam(request.params, 0, &frameNumber);
+ if (error)
+ return error;
+
+ SEXP environment;
+ r::sexp::Protect protect;
+ error = r::exec::RFunction("sys.frame", frameNumber)
+ .call(&environment, &protect);
+ if (error)
+ return error;
+
+ s_pEnvironmentMonitor->setMonitoredEnvironment(environment, true);
+ return Success();
+}
+
+// given a function context, indicate whether the copy of the source code
+// for the function is different than the source code on disk.
+bool functionIsOutOfSync(const RCNTXT *pContext,
+ std::string *pFunctionCode)
+{
+ Error error;
+
+ // start by extracting the source code from the call site
+ error = r::exec::RFunction(".rs.sourceCodeFromFunction",
+ getOriginalFunctionCallObject(pContext))
+ .call(pFunctionCode);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return true;
+ }
+
+ // make sure the function has source references
+ if (!hasSourceRefs(pContext))
+ {
+ return true;
+ }
+
+ return functionDiffersFromSource(
+ sourceRefsOfContext(pContext), *pFunctionCode);
+}
+
+// Returns a JSON array containing the names and associated call frame numbers
+// of the current environment stack.
+json::Value environmentNames(SEXP env)
+{
+ SEXP environments;
+ r::sexp::Protect protect;
+ Error error = r::exec::RFunction(".rs.environmentList", env)
+ .call(&environments, &protect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Array();
+ }
+ else
+ {
+ json::Value namesJson;
+ error = r::json::jsonValueFromObject(environments, &namesJson);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Array();
+ }
+ return namesJson;
+ }
+}
+
+// create a JSON object that contains information about the current environment;
+// used both to initialize the environment state on first load and to send
+// information about the new environment on a context change
+json::Object commonEnvironmentStateData(
+ int depth,
+ LineDebugState* pLineDebugState)
+{
+ json::Object varJson;
+ bool useProvidedSource = false;
+ std::string functionCode;
+ bool inFunctionEnvironment = false;
+
+ varJson["context_depth"] = depth;
+ varJson["environment_list"] = environmentListAsJson();
+ varJson["call_frames"] = callFramesAsJson(pLineDebugState);
+
+ // if we're in a debug context, add information about the function currently
+ // being debugged
+ if (depth > 0)
+ {
+ RCNTXT* pContext = getFunctionContext(depth);
+ std::string functionName;
+ Error error = functionNameFromContext(pContext, &functionName);
+ if (error)
+ {
+ LOG_ERROR(error);
+ }
+
+ // If the environment currently monitored is the function's environment,
+ // return that environment, unless the environment is the global
+ // environment (which happens for source-equivalent functions).
+ SEXP env = s_pEnvironmentMonitor->getMonitoredEnvironment();
+ if (env != R_GlobalEnv && env == pContext->cloenv)
+ {
+ varJson["environment_name"] = functionName + "()";
+ varJson["environment_is_local"] = true;
+ inFunctionEnvironment = true;
+ }
+
+ if (pContext && functionName != "eval")
+ {
+ // see if the function to be debugged is out of sync with its saved
+ // sources (if available).
+ useProvidedSource =
+ functionIsOutOfSync(pContext, &functionCode) &&
+ functionCode != "NULL";
+ }
+ varJson["function_name"] = functionName;
+ }
+ else
+ {
+ varJson["function_name"] = "";
+ }
+
+ if (!inFunctionEnvironment)
+ {
+ // emit the name of the environment we're currently working with
+ std::string environmentName;
+ bool local = false;
+ if (s_pEnvironmentMonitor->hasEnvironment())
+ {
+ Error error = r::exec::RFunction(".rs.environmentName",
+ s_pEnvironmentMonitor->getMonitoredEnvironment())
+ .call(&environmentName);
+ if (error)
+ LOG_ERROR(error);
+
+ error = r::exec::RFunction(".rs.environmentIsLocal",
+ s_pEnvironmentMonitor->getMonitoredEnvironment())
+ .call(&local);
+ if (error)
+ LOG_ERROR(error);
+ }
+ varJson["environment_name"] = environmentName;
+ varJson["environment_is_local"] = local;
+ }
+
+ // always emit the code for the function, even if we don't think that the
+ // client's going to need it. we only checked the saved copy of the function
+ // above; the client may be aware of local/unsaved changes to the function,
+ // in which case it will need to fall back on a server-provided copy.
+ varJson["use_provided_source"] = useProvidedSource;
+ varJson["function_code"] = functionCode;
+
+ return varJson;
+}
+
+void enqueContextDepthChangedEvent(int depth,
+ LineDebugState* pLineDebugState)
+{
+ // emit an event to the client indicating the new call frame and the
+ // current state of the environment
+ ClientEvent event (client_events::kContextDepthChanged,
+ commonEnvironmentStateData(depth, pLineDebugState));
+ module_context::enqueClientEvent(event);
+}
+
+void enqueBrowserLineChangedEvent(const SEXP srcref)
+{
+ json::Object varJson;
+ sourceRefToJson(srcref, &varJson);
+ ClientEvent event (client_events::kBrowserLineChanged, varJson);
+ module_context::enqueClientEvent(event);
+}
+
+Error setContextDepth(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<LineDebugState> pLineDebugState,
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse*)
+{
+ // get the requested depth
+ int requestedDepth;
+ Error error = json::readParam(request.params, 0, &requestedDepth);
+ if (error)
+ return error;
+
+ // set state for the new depth
+ *pContextDepth = requestedDepth;
+ SEXP env = NULL;
+ getFunctionContext(requestedDepth, false, NULL, &env);
+ s_pEnvironmentMonitor->setMonitoredEnvironment(env);
+
+ // populate the new state on the client
+ enqueContextDepthChangedEvent(*pContextDepth, pLineDebugState.get());
+
+ return Success();
+}
+
+Error getEnvironmentState(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<LineDebugState> pLineDebugState,
+ const json::JsonRpcRequest&,
+ json::JsonRpcResponse* pResponse)
+{
+ pResponse->setResult(commonEnvironmentStateData(*pContextDepth,
+ pLineDebugState.get()));
+ return Success();
+}
+
+void onDetectChanges(module_context::ChangeSource source)
+{
+ // Prevent recursive calls to this function (see notes in
+ // EventRecursionGuard)
+ static int inDetectChanges = 0;
+ if (inDetectChanges > 0)
+ return;
+
+ EventRecursionGuard guard(inDetectChanges);
+
+ s_pEnvironmentMonitor->checkForChanges();
+}
+
+void onConsolePrompt(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<LineDebugState> pLineDebugState,
+ boost::shared_ptr<bool> pCapturingDebugOutput,
+ boost::shared_ptr<RCNTXT*> pCurrentContext)
+{
+ // Prevent recursive calls to this function (see notes in
+ // EventRecursionGuard)
+ static int inConsolePrompt = 0;
+ if (inConsolePrompt > 0)
+ return;
+
+ EventRecursionGuard guard(inConsolePrompt);
+
+ int depth = 0;
+ SEXP environmentTop = NULL;
+ RCNTXT* pRContext = NULL;
+
+ // End debug output capture every time a console prompt occurs
+ *pCapturingDebugOutput = false;
+
+ // If we were debugging but there's no longer a browser on the context stack,
+ // switch back to the top level; otherwise, examine the stack and find the
+ // first function there running user code.
+ if (*pContextDepth > 0 && !inBrowseContext())
+ {
+ pRContext = r::getGlobalContext();
+ environmentTop = R_GlobalEnv;
+ }
+ else
+ {
+ // If we're not currently debugging, look for user code (we prefer to
+ // show the user their own code on entering debug), but once debugging,
+ // allow the user to explore other code.
+ pRContext =
+ getFunctionContext(TOP_FUNCTION, *pContextDepth == 0,
+ &depth, &environmentTop);
+ }
+
+ if (environmentTop != s_pEnvironmentMonitor->getMonitoredEnvironment() ||
+ depth != *pContextDepth ||
+ pRContext != *pCurrentContext)
+ {
+ // if we appear to be switching into debug mode, make sure there's a
+ // browser call somewhere on the stack. if there isn't, then we're
+ // probably just waiting for user input inside a function (e.g. scan());
+ // assume the user isn't interested in seeing the function's internals.
+ if (*pContextDepth == 0 &&
+ !inBrowseContext())
+ {
+ return;
+ }
+
+ // if we're leaving debug mode, clear out the debug state to prepare
+ // for the next debug session
+ if (*pContextDepth > 0 && depth == 0)
+ {
+ pLineDebugState->reset();
+ }
+
+ // start monitoring the enviroment at the new depth
+ s_pEnvironmentMonitor->setMonitoredEnvironment(environmentTop);
+ *pContextDepth = depth;
+ *pCurrentContext = pRContext;
+ enqueContextDepthChangedEvent(depth, pLineDebugState.get());
+ }
+ // if we're debugging and stayed in the same frame, update the line number
+ else if (depth > 0)
+ {
+ // we don't want to send linenumber updates if the current depth is inside
+ // a debug-hidden function
+ if (!insideDebugHiddenFunction())
+ {
+ // check to see if we have real source references for the currently
+ // executing context
+ SEXP srcref = r::getGlobalContext()->srcref;
+ if (!isValidSrcref(srcref))
+ {
+ // we don't, so reconstruct them from R output
+ RCNTXT *firstFunContext = firstFunctionContext(
+ r::getGlobalContext());
+ srcref = simulatedSourceRefsOfContext(firstFunContext, NULL,
+ pLineDebugState.get());
+ }
+ enqueBrowserLineChangedEvent(srcref);
+ }
+ }
+}
+
+void onBeforeExecute()
+{
+ // The client tracks busy state based on whether a console prompt has
+ // been issued (because R doesn't reliably deliver non-busy state) --
+ // i.e. when a console prompt occurs the client leaves busy state.
+ // During debugging the busy state is therefore exited as soon as a
+ // Browse> prompt is hit. This is often not a problem as the debug
+ // stop command will interrupt R if necessary. However, in the case
+ // where the Next or Continue command results in R running without
+ // hitting another breakpoint we've essentially lost the busy state.
+ //
+ // This handler (which executes right before console input is returned
+ // to R) checks whether we are in the Browser and if so re-raises the
+ // busy event to indicate that R is now back in a busy state. The busy
+ // state will be immediately cleared if another Browse> prompt is hit
+ // however if R continues running then the client will properly restore
+ // the state of the interruptR command
+
+ if (inBrowseContext())
+ {
+ ClientEvent event(client_events::kBusy, true);
+ module_context::enqueClientEvent(event);
+ }
+}
+
+Error getEnvironmentNames(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<RCNTXT*> pCurrentContext,
+ const json::JsonRpcRequest&,
+ json::JsonRpcResponse* pResponse)
+{
+ // If looking at a non-toplevel context, start from there; otherwise, start
+ // from the global environment.
+ SEXP env = *pContextDepth > 0 ?
+ (*pCurrentContext)->cloenv :
+ R_GlobalEnv;
+ pResponse->setResult(environmentNames(env));
+ return Success();
+}
+
+void initEnvironmentMonitoring()
+{
+ // Check to see whether we're actively debugging. If we are, the debug
+ // environment trumps whatever the user wants to browse in at the top level.
+ int contextDepth = 0;
+ RCNTXT* pContext = getFunctionContext(TOP_FUNCTION, false, &contextDepth);
+ if (contextDepth == 0 ||
+ !inBrowseContext())
+ {
+ // Not actively debugging; see if we have a stored environment name to
+ // begin monitoring.
+ std::string envName = persistentState().activeEnvironmentName();
+ if (!envName.empty())
+ {
+ // It's possible for this to fail if the environment we were
+ // monitoring doesn't exist any more. If this is the case, reset
+ // the monitor to the global environment.
+ Error error = setEnvironmentName(contextDepth, pContext, envName);
+ if (error)
+ {
+ persistentState().setActiveEnvironmentName("R_GlobalEnv");
+ }
+ }
+ }
+}
+
+// Remove the given objects from the currently monitored environment.
+Error removeObjects(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Array objectNames;
+ Error error = json::readParam(request.params, 0, &objectNames);
+ if (error)
+ return error;
+
+ error = r::exec::RFunction(".rs.removeObjects",
+ objectNames,
+ s_pEnvironmentMonitor->getMonitoredEnvironment()).call();
+ if (error)
+ return error;
+
+ return Success();
+}
+
+// Remove all the objects from the currently monitored environment.
+Error removeAllObjects(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ bool includeHidden;
+ Error error = json::readParam(request.params, 0, &includeHidden);
+ if (error)
+ return error;
+
+ error = r::exec::RFunction(".rs.removeAllObjects",
+ includeHidden,
+ s_pEnvironmentMonitor->getMonitoredEnvironment()).call();
+ if (error)
+ return error;
+
+ return Success();
+}
+
+// Return the contents of the given object. Called on-demand by the client when
+// the object is large enough that we don't want to get its contents
+// immediately (i.e. as part of environmentListAsJson)
+Error getObjectContents(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+
+{
+ std::string objectName;
+ r::sexp::Protect protect;
+ SEXP objContents;
+ json::Value contents;
+ Error error = json::readParam(request.params, 0, &objectName);
+ if (error)
+ return error;
+ error = r::exec::RFunction(".rs.getObjectContents",
+ objectName,
+ s_pEnvironmentMonitor->getMonitoredEnvironment())
+ .call(&objContents, &protect);
+ if (error)
+ return error;
+
+ error = r::json::jsonValueFromObject(objContents, &contents);
+ if (error)
+ return error;
+
+ json::Object result;
+ result["contents"] = contents;
+ pResponse->setResult(result);
+ return Success();
+}
+
+// Called by the client to force a re-query of the currently monitored
+// context depth and environment.
+Error requeryContext(boost::shared_ptr<int> pContextDepth,
+ boost::shared_ptr<LineDebugState> pLineDebugState,
+ boost::shared_ptr<RCNTXT*> pCurrentContext,
+ const json::JsonRpcRequest&,
+ json::JsonRpcResponse*)
+{
+ onConsolePrompt(pContextDepth, pLineDebugState,
+ boost::make_shared<bool>(false), pCurrentContext);
+ return Success();
+}
+
+// Stores the last "debug: " R output. Used to reconstruct source references
+// when unavailable (see simulatedSourceRefsOfContext).
+void onConsoleOutput(boost::shared_ptr<LineDebugState> pLineDebugState,
+ boost::shared_ptr<bool> pCapturingDebugOutput,
+ module_context::ConsoleOutputType type,
+ const std::string& output)
+{
+ if (*pCapturingDebugOutput)
+ {
+ // stop capturing output if non-normal output occurs
+ if (type != module_context::ConsoleOutputNormal)
+ {
+ *pCapturingDebugOutput = false;
+ return;
+ }
+ pLineDebugState->lastDebugText.append(output);
+ }
+ else if (type == module_context::ConsoleOutputNormal &&
+ output == "debug: ")
+ {
+ // start capturing debug output when R outputs "debug: "
+ pLineDebugState->lastDebugText = "";
+ *pCapturingDebugOutput = true;
+ }
+}
+
+} // anonymous namespace
+
+json::Value environmentStateAsJson()
+{
+ int contextDepth = 0;
+ getFunctionContext(TOP_FUNCTION, true, &contextDepth);
+ // If there's no browser on the stack, stay at the top level even if
+ // there are functions on the stack--this is not a user debug session.
+ if (!inBrowseContext())
+ contextDepth = 0;
+ return commonEnvironmentStateData(contextDepth, NULL);
+}
+
+Error initialize()
+{
+ // store on the heap so that the destructor is never called (so we
+ // don't end up releasing the underlying environment SEXP after
+ // R has already shut down / deinitialized)
+ s_pEnvironmentMonitor = new EnvironmentMonitor();
+
+ boost::shared_ptr<int> pContextDepth =
+ boost::make_shared<int>(0);
+ boost::shared_ptr<RCNTXT*> pCurrentContext =
+ boost::make_shared<RCNTXT*>(r::getGlobalContext());
+
+ // functions that emit call frames also emit source references; these
+ // values capture and supply the currently executing expression emitted by R
+ // for the purpose of reconstructing references when none are present.
+ boost::shared_ptr<LineDebugState> pLineDebugState =
+ boost::make_shared<LineDebugState>();
+ boost::shared_ptr<bool> pCapturingDebugOutput =
+ boost::make_shared<bool>(false);
+
+ // subscribe to events
+ using boost::bind;
+ using namespace session::module_context;
+ events().onDetectChanges.connect(bind(onDetectChanges, _1));
+ events().onConsolePrompt.connect(bind(onConsolePrompt,
+ pContextDepth,
+ pLineDebugState,
+ pCapturingDebugOutput,
+ pCurrentContext));
+ events().onBeforeExecute.connect(onBeforeExecute);
+ events().onConsoleOutput.connect(bind(onConsoleOutput,
+ pLineDebugState,
+ pCapturingDebugOutput, _1, _2));
+
+ json::JsonRpcFunction listEnv =
+ boost::bind(listEnvironment, pContextDepth, _1, _2);
+ json::JsonRpcFunction setCtxDepth =
+ boost::bind(setContextDepth, pContextDepth, pLineDebugState,
+ _1, _2);
+ json::JsonRpcFunction getEnv =
+ boost::bind(getEnvironmentState, pContextDepth, pLineDebugState,
+ _1, _2);
+ json::JsonRpcFunction getEnvNames =
+ boost::bind(getEnvironmentNames, pContextDepth, pCurrentContext,
+ _1, _2);
+ json::JsonRpcFunction setEnvName =
+ boost::bind(setEnvironment, pContextDepth, pCurrentContext,
+ _1, _2);
+ json::JsonRpcFunction requeryCtx =
+ boost::bind(requeryContext, pContextDepth, pLineDebugState,
+ pCurrentContext, _1, _2);
+
+ initEnvironmentMonitoring();
+
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRBrowseFileHandler, handleRBrowseEnv))
+ (bind(registerRpcMethod, "list_environment", listEnv))
+ (bind(registerRpcMethod, "set_context_depth", setCtxDepth))
+ (bind(registerRpcMethod, "set_environment", setEnvName))
+ (bind(registerRpcMethod, "set_environment_frame", setEnvironmentFrame))
+ (bind(registerRpcMethod, "get_environment_names", getEnvNames))
+ (bind(registerRpcMethod, "remove_objects", removeObjects))
+ (bind(registerRpcMethod, "remove_all_objects", removeAllObjects))
+ (bind(registerRpcMethod, "get_environment_state", getEnv))
+ (bind(registerRpcMethod, "get_object_contents", getObjectContents))
+ (bind(registerRpcMethod, "requery_context", requeryCtx))
+ (bind(sourceModuleRFile, "SessionEnvironment.R"));
+
+ return initBlock.execute();
+}
+
+} // namespace environment
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/environment/SessionEnvironment.hpp b/src/cpp/session/modules/environment/SessionEnvironment.hpp
new file mode 100644
index 0000000..0a22331
--- /dev/null
+++ b/src/cpp/session/modules/environment/SessionEnvironment.hpp
@@ -0,0 +1,37 @@
+/*
+ * SessionEnvironment.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SESSION_ENVIRONMENT_HPP
+#define SESSION_SESSION_ENVIRONMENT_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace environment {
+
+core::json::Value environmentStateAsJson();
+
+core::Error initialize();
+
+} // namespace environment
+} // namepace modules
+} // namesapce session
+
+#endif // SESSION_SESSION_ENVIRONMENT_HPP
diff --git a/src/cpp/session/modules/overlay/SessionOverlay.cpp b/src/cpp/session/modules/overlay/SessionOverlay.cpp
new file mode 100644
index 0000000..3501527
--- /dev/null
+++ b/src/cpp/session/modules/overlay/SessionOverlay.cpp
@@ -0,0 +1,33 @@
+/*
+ * SessionOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionOverlay.hpp"
+
+#include <core/Error.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace modules {
+namespace overlay {
+
+Error initialize()
+{
+ return Success();
+}
+
+} // namespace overlay
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/overlay/SessionOverlay.hpp b/src/cpp/session/modules/overlay/SessionOverlay.hpp
new file mode 100644
index 0000000..a60f3b1
--- /dev/null
+++ b/src/cpp/session/modules/overlay/SessionOverlay.hpp
@@ -0,0 +1,34 @@
+/*
+ * SessionOverlay.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_OVERLAY_HPP
+#define SESSION_OVERLAY_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace overlay {
+
+core::Error initialize();
+
+} // namespace overlay
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_OVERLAY_HPP
+
diff --git a/src/cpp/session/modules/presentation/PresentationLog.cpp b/src/cpp/session/modules/presentation/PresentationLog.cpp
new file mode 100644
index 0000000..033f2ea
--- /dev/null
+++ b/src/cpp/session/modules/presentation/PresentationLog.cpp
@@ -0,0 +1,369 @@
+/*
+ * PresentationLog.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "PresentationLog.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/Error.hpp>
+#include <core/DateTime.hpp>
+#include <core/StringUtils.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#include "PresentationState.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+Log& log()
+{
+ static Log instance;
+ return instance;
+}
+
+
+Error Log::initialize()
+{
+
+ // connect to console events
+ using namespace boost;
+ using namespace session::module_context;
+
+ events().onConsolePrompt.connect(boost::bind(&Log::onConsolePrompt,
+ this, _1));
+ events().onConsoleInput.connect(boost::bind(&Log::onConsoleInput,
+ this, _1));
+ events().onConsoleOutput.connect(boost::bind(&Log::onConsoleOutput,
+ this, _1, _2));
+
+ return Success();
+}
+
+void Log::recordCommand(int slideIndex, const Command& command)
+{
+ if (command.name() == "console-input")
+ {
+ std::string params = boost::algorithm::trim_copy(command.params());
+ slideDeckInputCommands_[slideIndex].insert(params);
+ }
+ else if (command.name() == "help-topic")
+ {
+ slideHelpTopics_[slideIndex] = command.params();
+ }
+ else if (command.name() == "help-doc")
+ {
+ slideHelpDocs_[slideIndex] = command.params();
+ }
+}
+
+void Log::onSlideDeckChanged(const SlideDeck& slideDeck)
+{
+ slideDeckInputCommands_.clear();
+ slideTypes_.clear();
+
+ slideHelpTopics_ = std::vector<std::string>(slideDeck.slides().size());
+ slideHelpDocs_ = std::vector<std::string>(slideDeck.slides().size());
+
+ const std::vector<Slide>& slides = slideDeck.slides();
+ for (std::size_t i = 0; i<slides.size(); i++)
+ {
+ slideTypes_.push_back(slides[i].type());
+
+ const std::vector<Command>& commands = slides[i].commands();
+ BOOST_FOREACH(const Command& command, commands)
+ {
+ recordCommand(i, command);
+ }
+
+ const std::vector<AtCommand>& atCommands = slides[i].atCommands();
+ BOOST_FOREACH(const AtCommand& atCommand, atCommands)
+ {
+ recordCommand(i, atCommand.command());
+ }
+ }
+}
+
+void Log::onSlideIndexChanged(int index)
+{
+ currentSlideIndex_ = index;
+
+ append(NavigationEntry,
+ currentSlideIndex_,
+ currentSlideType(),
+ currentSlideHelpTopic(),
+ currentSlideHelpDoc(),
+ "",
+ "");
+}
+
+void Log::onConsolePrompt(const std::string& prompt)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ // ignore if this isn't the default prompt
+ if (!r::session::utils::isDefaultPrompt(prompt))
+ return;
+
+ if (!consoleInputBuffer_.empty())
+ {
+ using namespace boost::algorithm;
+ std::string input = trim_copy(join(consoleInputBuffer_, "\n"));
+ std::string errors = trim_copy(join(errorOutputBuffer_, "\n"));
+
+ // check to see if this command was one of the ones instrumented
+ // by the current slide
+ if (slideDeckInputCommands_[currentSlideIndex_].count(input) == 0)
+ {
+ append(InputEntry,
+ currentSlideIndex_,
+ currentSlideType(),
+ currentSlideHelpTopic(),
+ currentSlideHelpDoc(),
+ input,
+ errors);
+ }
+ }
+
+ consoleInputBuffer_.clear();
+ errorOutputBuffer_.clear();
+}
+
+
+void Log::onConsoleInput(const std::string& text)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ consoleInputBuffer_.push_back(text);
+}
+
+
+void Log::onConsoleOutput(module_context::ConsoleOutputType type,
+ const std::string& output)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ if (type == module_context::ConsoleOutputError)
+ errorOutputBuffer_.push_back(output);
+
+}
+
+namespace {
+
+std::string csvString(std::string str)
+{
+ boost::algorithm::replace_all(str, "\n", "\\n");
+ boost::algorithm::replace_all(str, "\"", "\"\"");
+ return "\"" + str + "\"";
+}
+
+std::string timestamp()
+{
+ // generate timestamp
+ using namespace boost::posix_time;
+ ptime time = microsec_clock::universal_time();
+ std::string dateTime = date_time::format(time, "%Y-%m-%dT%H:%M:%SZ");
+ return dateTime;
+}
+
+std::string csvPresentationPath()
+{
+ return csvString(module_context::createAliasedPath(
+ presentation::state::filePath()));
+}
+
+Error ensureTargetFile(const std::string& filename,
+ const std::string& header,
+ FilePath* pTargetFile)
+{
+ using namespace module_context;
+ FilePath presDir = userScratchPath().childPath("presentation");
+ Error error = presDir.ensureDirectory();
+ if (error)
+ return error;
+
+ *pTargetFile = presDir.childPath(filename);
+ if (!pTargetFile->exists())
+ {
+ Error error = core::writeStringToFile(*pTargetFile, header + "\n");
+ if (error)
+ return error;
+ }
+
+ return Success();
+}
+
+
+} // anonymous namespace
+
+std::string Log::currentSlideType() const
+{
+ if (currentSlideIndex_ < slideTypes_.size())
+ return slideTypes_[currentSlideIndex_];
+ else
+ return "default";
+}
+
+std::string Log::currentSlideHelpTopic() const
+{
+ if (currentSlideIndex_ < slideHelpTopics_.size())
+ return slideHelpTopics_[currentSlideIndex_];
+ else
+ return "";
+}
+
+std::string Log::currentSlideHelpDoc() const
+{
+ if (currentSlideIndex_ < slideHelpDocs_.size())
+ return slideHelpDocs_[currentSlideIndex_];
+ else
+ return "";
+}
+
+void Log::append(EntryType type,
+ int slideIndex,
+ const std::string& slideType,
+ const std::string& helpTopic,
+ const std::string& helpDoc,
+ const std::string& input,
+ const std::string& errors)
+{
+ // bail if this isn't a tutorial
+ if (!presentation::state::isTutorial())
+ return;
+
+ // ensure target file
+ FilePath logFilePath;
+ Error error = ensureTargetFile(
+ "presentation-log-v2.csv",
+ "type, timestamp, username, presentation, slide, slide-type, "
+ "help-topic, help-doc, input, errors\n",
+ &logFilePath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // generate entry
+ std::vector<std::string> fields;
+ fields.push_back((type == NavigationEntry) ? "Navigation" : "Input");
+ fields.push_back(timestamp());
+ fields.push_back(csvString(core::system::username()));
+ fields.push_back(csvPresentationPath());
+ fields.push_back(safe_convert::numberToString(slideIndex));
+ fields.push_back(slideType);
+ fields.push_back(csvString(helpTopic));
+ fields.push_back(csvString(helpDoc));
+ fields.push_back(csvString(input));
+ fields.push_back(csvString(errors));
+ std::string entry = boost::algorithm::join(fields, ",");
+
+ // append entry
+ error = core::appendToFile(logFilePath, entry + "\n");
+ if (error)
+ LOG_ERROR(error);
+}
+
+void Log::recordFeedback(const std::string& feedback)
+{
+ // bail if this isn't a tutorial
+ if (!presentation::state::isTutorial())
+ return;
+
+ // ensure target file
+ FilePath feedbackFilePath;
+ Error error = ensureTargetFile("feedback-v2.csv",
+ "timestamp, username, presentation, "
+ "slide, feedback\n",
+ &feedbackFilePath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // generate entry
+ std::vector<std::string> fields;
+ fields.push_back(timestamp());
+ fields.push_back(csvString(core::system::username()));
+ fields.push_back(csvPresentationPath());
+ fields.push_back(safe_convert::numberToString(currentSlideIndex_));
+ fields.push_back(csvString(feedback));
+ std::string entry = boost::algorithm::join(fields, ",");
+
+ // append entry
+ error = core::appendToFile(feedbackFilePath, entry + "\n");
+ if (error)
+ LOG_ERROR(error);
+}
+
+void Log::recordQuizResponse(int index, int answer, bool correct)
+{
+ // bail if this isn't a tutorial
+ if (!presentation::state::isTutorial())
+ return;
+
+ // ensure target file
+ FilePath quizResponseFilePath;
+ Error error = ensureTargetFile(
+ "quiz-responses-v2.csv",
+ "timestamp, username, presentation, slide, "
+ "answer, correct",
+ &quizResponseFilePath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // generate entry
+ std::vector<std::string> fields;
+ fields.push_back(timestamp());
+ fields.push_back(csvString(core::system::username()));
+ fields.push_back(csvPresentationPath());
+ fields.push_back(safe_convert::numberToString(index));
+ fields.push_back(safe_convert::numberToString(answer));
+ fields.push_back(safe_convert::numberToString(correct));
+ std::string entry = boost::algorithm::join(fields, ",");
+
+ // append entry
+ error = core::appendToFile(quizResponseFilePath, entry + "\n");
+ if (error)
+ LOG_ERROR(error);
+}
+
+
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/PresentationLog.hpp b/src/cpp/session/modules/presentation/PresentationLog.hpp
new file mode 100644
index 0000000..4f84c70
--- /dev/null
+++ b/src/cpp/session/modules/presentation/PresentationLog.hpp
@@ -0,0 +1,100 @@
+/*
+ * PresentationLog.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_LOG_HPP
+#define SESSION_PRESENTATION_LOG_HPP
+
+#include <string>
+#include <set>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SlideParser.hpp"
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+class Log;
+Log& log();
+
+class Log : boost::noncopyable
+{
+private:
+ Log() : currentSlideIndex_(0) {}
+ friend Log& log();
+
+public:
+ core::Error initialize();
+
+ void onSlideDeckChanged(const SlideDeck& slideDeck);
+ void onSlideIndexChanged(int index);
+
+ void recordFeedback(const std::string& feedback);
+ void recordQuizResponse(int index, int answer, bool correct);
+
+private:
+ void onConsolePrompt(const std::string& prompt);
+ void onConsoleInput(const std::string& text);
+ void onConsoleOutput(module_context::ConsoleOutputType type,
+ const std::string& output);
+
+ enum EntryType { NavigationEntry, InputEntry };
+
+ static void append(EntryType type,
+ int slideIndex,
+ const std::string& slideType,
+ const std::string& helpTopic,
+ const std::string& helpDoc,
+ const std::string& input,
+ const std::string& errors);
+
+ std::string currentSlideType() const;
+ std::string currentSlideHelpTopic() const;
+ std::string currentSlideHelpDoc() const;
+
+ void recordCommand(int slideIndex, const Command& command);
+
+private:
+ std::size_t currentSlideIndex_;
+ SlideDeck currentSlideDeck_;
+
+ std::map<int, std::set<std::string> > slideDeckInputCommands_;
+
+ std::vector<std::string> slideHelpTopics_;
+ std::vector<std::string> slideHelpDocs_;
+
+ std::vector<std::string> slideTypes_;
+
+ std::vector<std::string> consoleInputBuffer_;
+ std::vector<std::string> errorOutputBuffer_;
+
+
+
+};
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_LOG_HPP
diff --git a/src/cpp/session/modules/presentation/PresentationOverlay.cpp b/src/cpp/session/modules/presentation/PresentationOverlay.cpp
new file mode 100644
index 0000000..73dbd3f
--- /dev/null
+++ b/src/cpp/session/modules/presentation/PresentationOverlay.cpp
@@ -0,0 +1,44 @@
+/*
+ * PresentationOverlay.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "PresentationState.hpp"
+
+#include <core/Error.hpp>
+
+#include "SlideParser.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+void onSlideDeckChangedOverlay(const SlideDeck& slideDeck)
+{
+}
+
+namespace state {
+
+Error initializeOverlay()
+{
+ return Success();
+}
+
+} // namespace state
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/PresentationState.cpp b/src/cpp/session/modules/presentation/PresentationState.cpp
new file mode 100644
index 0000000..d66af38
--- /dev/null
+++ b/src/cpp/session/modules/presentation/PresentationState.cpp
@@ -0,0 +1,232 @@
+/*
+ * PresentationState.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "PresentationState.hpp"
+
+#include <core/FilePath.hpp>
+#include <core/Settings.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+namespace state {
+
+namespace {
+
+struct PresentationState
+{
+ PresentationState()
+ : active(false), isTutorial(false), slideIndex(0)
+ {
+ }
+
+ bool active;
+ std::string paneCaption;
+ bool isTutorial;
+ FilePath filePath;
+ int slideIndex;
+ FilePath viewInBrowserPath; // not saved, is created on demand
+};
+
+// write-through cache of presentation state
+PresentationState s_presentationState;
+
+FilePath presentationStatePath()
+{
+ FilePath path = module_context::scopedScratchPath().childPath("presentation");
+ Error error = path.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+ return path.childPath("presentation-state-v2");
+}
+
+std::string toPersistentPath(const FilePath& filePath)
+{
+ projects::ProjectContext& projectContext = projects::projectContext();
+
+ if (projectContext.hasProject() &&
+ filePath.isWithin(projectContext.directory()))
+ {
+ return filePath.relativePath(projectContext.directory());
+ }
+ else
+ {
+ return filePath.absolutePath();
+ }
+}
+
+FilePath fromPersistentPath(const std::string& path)
+{
+ projects::ProjectContext& projectContext = projects::projectContext();
+ if (projectContext.hasProject())
+ {
+ return projectContext.directory().complete(path);
+ }
+ else
+ {
+ return FilePath(path);
+ }
+}
+
+
+void savePresentationState(const PresentationState& state)
+{
+ // update write-through cache
+ s_presentationState = state;
+
+ // save to disk
+ Settings settings;
+ Error error = settings.initialize(presentationStatePath());
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+ settings.beginUpdate();
+ settings.set("active", state.active);
+ settings.set("pane-caption", state.paneCaption);
+ settings.set("is-tutorial", state.isTutorial);
+ settings.set("file-path", toPersistentPath(state.filePath));
+ settings.set("slide-index", state.slideIndex);
+ settings.endUpdate();
+}
+
+void loadPresentationState()
+{
+ FilePath statePath = presentationStatePath();
+ if (statePath.exists())
+ {
+ Settings settings;
+ Error error = settings.initialize(presentationStatePath());
+ if (error)
+ LOG_ERROR(error);
+
+ s_presentationState.active = settings.getBool("active", false);
+ s_presentationState.paneCaption = settings.get("pane-caption", "Presentation");
+ s_presentationState.isTutorial = settings.getBool("is-tutorial");
+ s_presentationState.filePath = fromPersistentPath(settings.get("file-path"));
+ s_presentationState.slideIndex = settings.getInt("slide-index", 0);
+ }
+ else
+ {
+ s_presentationState = PresentationState();
+ }
+}
+
+} // anonymous namespace
+
+
+void init(const FilePath& filePath,
+ const std::string& caption,
+ bool isTutorial)
+{
+ PresentationState state;
+ state.active = true;
+ state.paneCaption = caption;
+ state.isTutorial = isTutorial;
+ state.filePath = filePath;
+ state.slideIndex = 0;
+ savePresentationState(state);
+}
+
+void setSlideIndex(int index)
+{
+ s_presentationState.slideIndex = index;
+ savePresentationState(s_presentationState);
+}
+
+void setCaption(const std::string& caption)
+{
+ s_presentationState.paneCaption = caption;
+ savePresentationState(s_presentationState);
+}
+
+bool isActive()
+{
+ return s_presentationState.active;
+}
+
+bool isTutorial()
+{
+ return s_presentationState.isTutorial;
+}
+
+FilePath filePath()
+{
+ return s_presentationState.filePath;
+}
+
+FilePath directory()
+{
+ return s_presentationState.filePath.parent();
+}
+
+FilePath viewInBrowserPath()
+{
+ if (s_presentationState.viewInBrowserPath.empty())
+ {
+ FilePath viewDir = module_context::tempFile("view", "dir");
+ Error error = viewDir.ensureDirectory();
+ if (!error)
+ {
+ s_presentationState.viewInBrowserPath =
+ viewDir.childPath("presentation.html");
+ }
+ else
+ {
+ LOG_ERROR(error);
+ }
+ }
+
+ return s_presentationState.viewInBrowserPath;
+}
+
+void clear()
+{
+ savePresentationState(PresentationState());
+}
+
+json::Value asJson()
+{
+ json::Object stateJson;
+ stateJson["active"] = s_presentationState.active;
+ stateJson["pane_caption"] = s_presentationState.paneCaption;
+ stateJson["is_tutorial"] = s_presentationState.isTutorial;
+ stateJson["file_path"] = module_context::createAliasedPath(
+ s_presentationState.filePath);
+ stateJson["slide_index"] = s_presentationState.slideIndex;
+ return stateJson;
+}
+
+Error initialize()
+{
+ // attempt to load any cached state
+ loadPresentationState();
+
+ // call overlay hook
+ return initializeOverlay();
+}
+
+} // namespace state
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/PresentationState.hpp b/src/cpp/session/modules/presentation/PresentationState.hpp
new file mode 100644
index 0000000..e32e279
--- /dev/null
+++ b/src/cpp/session/modules/presentation/PresentationState.hpp
@@ -0,0 +1,65 @@
+/*
+ * PresentationState.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_STATE_HPP
+#define SESSION_PRESENTATION_STATE_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace presentation {
+namespace state {
+
+
+void init(const core::FilePath& filePath,
+ const std::string& caption = "Presentation",
+ bool isTutorial = false);
+void setSlideIndex(int index);
+void setCaption(const std::string& caption);
+
+
+bool isActive();
+
+bool isTutorial();
+
+core::FilePath filePath();
+
+core::FilePath directory();
+
+core::FilePath viewInBrowserPath();
+
+void clear();
+
+
+core::json::Value asJson();
+
+
+
+core::Error initialize();
+core::Error initializeOverlay();
+
+
+} // namespace state
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_STATE_HPP
diff --git a/src/cpp/session/modules/presentation/SessionPresentation.cpp b/src/cpp/session/modules/presentation/SessionPresentation.cpp
new file mode 100644
index 0000000..7d2f996
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SessionPresentation.cpp
@@ -0,0 +1,511 @@
+/*
+ * SessionPresentation.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+// TODO: custom code navigator for presentations
+
+#include "SessionPresentation.hpp"
+
+
+#include <boost/bind.hpp>
+
+#include <core/Exec.hpp>
+#include <core/http/Util.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/text/TemplateFilter.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "../SessionRPubs.hpp"
+
+#include "PresentationLog.hpp"
+#include "PresentationState.hpp"
+#include "SlideRequestHandler.hpp"
+#include "SlideNavigationList.hpp"
+
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+namespace {
+
+
+void showPresentation(const FilePath& filePath)
+{
+ // initialize state
+ presentation::state::init(filePath);
+
+ // notify the client
+ ClientEvent event(client_events::kShowPresentationPane,
+ presentation::state::asJson());
+ module_context::enqueClientEvent(event);
+}
+
+SEXP rs_showPresentation(SEXP fileSEXP)
+{
+ try
+ {
+ // validate path
+ FilePath filePath(r::sexp::asString(fileSEXP));
+ if (!filePath.exists())
+ throw r::exec::RErrorException("File path " + filePath.absolutePath() +
+ " does not exist.");
+
+ showPresentation(filePath);
+ }
+ catch(const r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+
+ return R_NilValue;
+}
+
+SEXP rs_showPresentationHelpDoc(SEXP helpDocSEXP)
+{
+ try
+ {
+ // verify a presentation is active
+ if (!presentation::state::isActive())
+ {
+ throw r::exec::RErrorException(
+ "No presentation is currently active");
+ }
+
+ // resolve against presentation directory
+ std::string helpDoc = r::sexp::asString(helpDocSEXP);
+ FilePath helpDocPath = presentation::state::directory().childPath(
+ helpDoc);
+ if (!helpDocPath.exists())
+ {
+ throw r::exec::RErrorException("Path " + helpDocPath.absolutePath()
+ + " not found.");
+ }
+
+ // build url and fire event
+ std::string url = "help/presentation/?file=";
+ std::string file = module_context::createAliasedPath(helpDocPath);
+ url += http::util::urlEncode(file, true);
+
+ ClientEvent event(client_events::kShowHelp, url);
+ module_context::enqueClientEvent(event);
+ }
+ catch(const r::exec::RErrorException& e)
+ {
+ r::exec::error(e.message());
+ }
+
+ return R_NilValue;
+}
+
+Error setPresentationSlideIndex(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse*)
+{
+ int index = 0;
+ Error error = json::readParam(request.params, 0, &index);
+ if (error)
+ return error;
+
+ presentation::state::setSlideIndex(index);
+
+ presentation::log().onSlideIndexChanged(index);
+
+ return Success();
+}
+
+Error createNewPresentation(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get file path
+ std::string file;
+ Error error = json::readParam(request.params, 0, &file);
+ if (error)
+ return error;
+ FilePath filePath = module_context::resolveAliasedPath(file);
+
+ // process template
+ std::map<std::string,std::string> vars;
+ vars["name"] = filePath.stem();
+ core::text::TemplateFilter filter(vars);
+
+ // read file with template filter
+ FilePath templatePath = session::options().rResourcesPath().complete(
+ "templates/r_presentation.Rpres");
+ std::string presContents;
+ error = core::readStringFromFile(templatePath, filter, &presContents);
+ if (error)
+ return error;
+
+
+ // write file
+ return core::writeStringToFile(filePath,
+ presContents,
+ string_utils::LineEndingNative);
+}
+
+Error showPresentationPane(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string file;
+ Error error = json::readParam(request.params, 0, &file);
+ if (error)
+ return error;
+
+ FilePath filePath = module_context::resolveAliasedPath(file);
+ if (!filePath.exists())
+ return core::fileNotFoundError(filePath, ERROR_LOCATION);
+
+ showPresentation(filePath);
+
+ return Success();
+}
+
+Error closePresentationPane(const json::JsonRpcRequest&,
+ json::JsonRpcResponse*)
+{
+ presentation::state::clear();
+
+ return Success();
+}
+
+Error presentationExecuteCode(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the code
+ std::string code;
+ Error error = json::readParam(request.params, 0, &code);
+ if (error)
+ return error;
+
+ // confirm we are active
+ if (!presentation::state::isActive())
+ {
+ pResponse->setError(json::errc::MethodUnexpected);
+ return Success();
+ }
+
+ // execute within the context of either the tutorial project directory
+ // or presentation directory
+ RestoreCurrentPathScope restorePathScope(
+ module_context::safeCurrentPath());
+ if (presentation::state::isTutorial() &&
+ projects::projectContext().hasProject())
+ {
+ error = projects::projectContext().directory().makeCurrentPath();
+ }
+ else
+ {
+ error = presentation::state::directory().makeCurrentPath();
+ }
+ if (error)
+ return error;
+
+
+ // actually execute the code (show error in the console)
+ error = r::exec::executeString(code);
+ if (error)
+ {
+ std::string errMsg = "Error executing code: " + code + "\n";
+ errMsg += r::endUserErrorMessage(error);
+ module_context::consoleWriteError(errMsg + "\n");
+ }
+
+ return Success();
+}
+
+Error setWorkingDirectory(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse*)
+{
+ // get the path
+ std::string path;
+ Error error = json::readParam(request.params, 0, &path);
+ if (error)
+ return error;
+
+ // set current path
+ FilePath filePath = module_context::resolveAliasedPath(path);
+ return filePath.makeCurrentPath();
+}
+
+Error tutorialFeedback(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the feedback
+ std::string feedback;
+ Error error = json::readParam(request.params, 0, &feedback);
+ if (error)
+ return error;
+
+ // confirm we are active
+ if (!presentation::state::isActive())
+ {
+ pResponse->setError(json::errc::MethodUnexpected);
+ return Success();
+ }
+
+ // record the feedback
+ presentation::log().recordFeedback(feedback);
+
+ return Success();
+}
+
+Error tutorialQuizResponse(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the params
+ int slideIndex, answer;
+ bool correct;
+ Error error = json::readParams(request.params,
+ &slideIndex,
+ &answer,
+ &correct);
+ if (error)
+ return error;
+
+ // confirm we are active
+ if (!presentation::state::isActive())
+ {
+ pResponse->setError(json::errc::MethodUnexpected);
+ return Success();
+ }
+
+ // record the feedback
+ presentation::log().recordQuizResponse(slideIndex, answer, correct);
+
+ return Success();
+}
+
+
+Error getSlideNavigation(const std::string& code,
+ const FilePath& baseDir,
+ json::Object* pSlideNavigationJson)
+{
+ SlideDeck slideDeck;
+ Error error = slideDeck.readSlides(code, baseDir);
+ if (error)
+ return error;
+
+ SlideNavigationList navigationList("slide");
+ BOOST_FOREACH(const Slide& slide, slideDeck.slides())
+ {
+ navigationList.add(slide);
+ }
+
+ *pSlideNavigationJson = navigationList.asJson();
+
+ return Success();
+}
+
+Error getSlideNavigationForFile(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get param
+ std::string file;
+ Error error = json::readParam(request.params, 0, &file);
+ if (error)
+ return error;
+ FilePath filePath = module_context::resolveAliasedPath(file);
+
+ // read code
+ std::string code;
+ error = core::readStringFromFile(filePath,
+ &code,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+ // get slide navigation
+ json::Object slideNavigationJson;
+ error = getSlideNavigation(code, filePath.parent(), &slideNavigationJson);
+ if (error)
+ return error;
+ pResponse->setResult(slideNavigationJson);
+
+ return Success();
+}
+
+Error getSlideNavigationForCode(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get params
+ std::string code, parentDir;
+ Error error = json::readParams(request.params, &code, &parentDir);
+ if (error)
+ return error;
+ FilePath parentDirPath = module_context::resolveAliasedPath(parentDir);
+
+ // get slide navigation
+ json::Object slideNavigationJson;
+ error = getSlideNavigation(code, parentDirPath, &slideNavigationJson);
+ if (error)
+ return error;
+ pResponse->setResult(slideNavigationJson);
+
+ return Success();
+}
+
+Error clearPresentationCache(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+
+ ErrorResponse errorResponse;
+ if (!clearKnitrCache(&errorResponse))
+ {
+ pResponse->setError(systemError(boost::system::errc::io_error,
+ ERROR_LOCATION),
+ json::toJsonString(errorResponse.message));
+ }
+
+ return Success();
+}
+
+
+
+Error createStandalonePresentation(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string pathParam;
+ Error error = json::readParam(request.params, 0, &pathParam);
+ if (error)
+ return error;
+ FilePath targetPath = module_context::resolveAliasedPath(pathParam);
+
+ ErrorResponse errorResponse;
+ if (!savePresentationAsStandalone(targetPath, &errorResponse))
+ {
+ pResponse->setError(systemError(boost::system::errc::io_error,
+ ERROR_LOCATION),
+ json::toJsonString(errorResponse.message));
+ }
+
+ return Success();
+}
+
+Error createDesktopViewInBrowserPresentation(
+ const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // save to view in browser path
+ FilePath targetPath = presentation::state::viewInBrowserPath();
+ ErrorResponse errorResponse;
+ if (savePresentationAsStandalone(targetPath, &errorResponse))
+ {
+ pResponse->setResult(module_context::createAliasedPath(targetPath));
+ }
+ else
+ {
+ pResponse->setError(systemError(boost::system::errc::io_error,
+ ERROR_LOCATION),
+ json::toJsonString(errorResponse.message));
+ }
+
+ return Success();
+}
+
+Error createPresentationRpubsSource(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // use a stable location in the presentation directory for the Rpubs
+ // source file so that update works across sessions
+ std::string stem = presentation::state::filePath().stem();
+ FilePath filePath = presentation::state::directory().childPath(
+ stem + "-rpubs.html");
+
+ ErrorResponse errorResponse;
+ if (savePresentationAsRpubsSource(filePath, &errorResponse))
+ {
+ json::Object resultJson;
+ resultJson["published"] = !rpubs::previousUploadId(filePath).empty();
+ resultJson["source_file_path"] = module_context::createAliasedPath(
+ filePath);
+ pResponse->setResult(resultJson);
+ }
+ else
+ {
+ pResponse->setError(systemError(boost::system::errc::io_error,
+ ERROR_LOCATION),
+ json::toJsonString(errorResponse.message));
+ }
+
+ return Success();
+}
+
+} // anonymous namespace
+
+
+json::Value presentationStateAsJson()
+{
+ return presentation::state::asJson();
+}
+
+Error initialize()
+{
+ // register rs_showPresentation
+ R_CallMethodDef methodDefShowPresentation;
+ methodDefShowPresentation.name = "rs_showPresentation" ;
+ methodDefShowPresentation.fun = (DL_FUNC) rs_showPresentation;
+ methodDefShowPresentation.numArgs = 1;
+ r::routines::addCallMethod(methodDefShowPresentation);
+
+ // register rs_showPresentationHelpDoc
+ R_CallMethodDef methodDefShowHelpDoc;
+ methodDefShowHelpDoc.name = "rs_showPresentationHelpDoc" ;
+ methodDefShowHelpDoc.fun = (DL_FUNC) rs_showPresentationHelpDoc;
+ methodDefShowHelpDoc.numArgs = 1;
+ r::routines::addCallMethod(methodDefShowHelpDoc);
+
+ // initialize presentation log
+ Error error = log().initialize();
+ if (error)
+ return error;
+
+ using boost::bind;
+ using namespace session::module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerUriHandler, "/presentation", handlePresentationPaneRequest))
+ (bind(registerRpcMethod, "create_standalone_presentation", createStandalonePresentation))
+ (bind(registerRpcMethod, "create_desktop_view_in_browser_presentation",
+ createDesktopViewInBrowserPresentation))
+ (bind(registerRpcMethod, "create_presentation_rpubs_source", createPresentationRpubsSource))
+ (bind(registerRpcMethod, "set_presentation_slide_index", setPresentationSlideIndex))
+ (bind(registerRpcMethod, "create_new_presentation", createNewPresentation))
+ (bind(registerRpcMethod, "show_presentation_pane", showPresentationPane))
+ (bind(registerRpcMethod, "close_presentation_pane", closePresentationPane))
+ (bind(registerRpcMethod, "presentation_execute_code", presentationExecuteCode))
+ (bind(registerRpcMethod, "set_working_directory", setWorkingDirectory))
+ (bind(registerRpcMethod, "tutorial_feedback", tutorialFeedback))
+ (bind(registerRpcMethod, "tutorial_quiz_response", tutorialQuizResponse))
+ (bind(registerRpcMethod, "get_slide_navigation_for_file", getSlideNavigationForFile))
+ (bind(registerRpcMethod, "get_slide_navigation_for_code", getSlideNavigationForCode))
+ (bind(registerRpcMethod, "clear_presentation_cache", clearPresentationCache))
+ (bind(presentation::state::initialize))
+ (bind(sourceModuleRFile, "SessionPresentation.R"));
+
+ return initBlock.execute();
+}
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SessionPresentation.hpp b/src/cpp/session/modules/presentation/SessionPresentation.hpp
new file mode 100644
index 0000000..e0d95ce
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SessionPresentation.hpp
@@ -0,0 +1,44 @@
+/*
+ * SessionPresentation.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_HPP
+#define SESSION_PRESENTATION_HPP
+
+#include <string>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+ namespace http {
+ class Request;
+ class Response;
+ }
+}
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+core::json::Value presentationStateAsJson();
+
+core::Error initialize();
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_HPP
diff --git a/src/cpp/session/modules/presentation/SlideMediaRenderer.cpp b/src/cpp/session/modules/presentation/SlideMediaRenderer.cpp
new file mode 100644
index 0000000..5af397a
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideMediaRenderer.cpp
@@ -0,0 +1,40 @@
+/*
+ * SlideMediaRenderer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SlideMediaRenderer.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+void renderMedia(const std::string& type,
+ int slideNumber,
+ const FilePath& baseDir,
+ const std::string& fileName,
+ const std::vector<AtCommand>& atCommands,
+ std::ostream& os,
+ std::vector<std::string>* pInitActions,
+ std::vector<std::string>* pSlideActions)
+{
+}
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideMediaRenderer.hpp b/src/cpp/session/modules/presentation/SlideMediaRenderer.hpp
new file mode 100644
index 0000000..db5ec0f
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideMediaRenderer.hpp
@@ -0,0 +1,48 @@
+/*
+ * SlideMediaRenderer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_MEDIA_RENDERER_HPP
+#define SESSION_PRESENTATION_SLIDE_MEDIA_RENDERER_HPP
+
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+namespace core {
+ class FilePath;
+}
+
+#include "SlideParser.hpp"
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+void renderMedia(const std::string& type,
+ int slideNumber,
+ const core::FilePath& baseDir,
+ const std::string& fileName,
+ const std::vector<AtCommand>& atCommands,
+ std::ostream& os,
+ std::vector<std::string>* pInitActions,
+ std::vector<std::string>* pSlideActions);
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_MEDIA_RENDERER_HPP
diff --git a/src/cpp/session/modules/presentation/SlideNavigationList.cpp b/src/cpp/session/modules/presentation/SlideNavigationList.cpp
new file mode 100644
index 0000000..78cb33e
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideNavigationList.cpp
@@ -0,0 +1,145 @@
+/*
+ * SlideNavigationList.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SlideNavigationList.hpp"
+
+#include <sstream>
+
+#include <boost/foreach.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+SlideNavigationList::SlideNavigationList(const std::string& type)
+ : allowNavigation_(true),
+ allowSlideNavigation_(true),
+ index_(0),
+ inSubSection_(false),
+ hasSections_(false)
+{
+ if (type == "slide")
+ {
+ allowNavigation_ = true;
+ allowSlideNavigation_ = true;
+ }
+ else if (type == "section")
+ {
+ allowNavigation_ = true;
+ allowSlideNavigation_ = false;
+ }
+ else if (type == "none")
+ {
+ allowNavigation_ = false;
+ allowSlideNavigation_ = false;
+ }
+}
+
+void SlideNavigationList::add(const Slide& slide)
+{
+ // if there is no navigation then we only add the first slide
+ if (!allowNavigation_)
+ {
+ if (slides_.empty())
+ addSlide(slide.title(), 0, 0, slide.line());
+ }
+ else if (!allowSlideNavigation_)
+ {
+ if (slide.type() == "section")
+ addSlide(slide.title(), 0, index_, slide.line());
+ }
+ else
+ {
+ int indent = 0;
+ if (slides_.empty())
+ {
+ inSubSection_ = false;
+ indent = 0;
+ }
+ else if (slide.type() == "section")
+ {
+ inSubSection_ = false;
+ indent = 0;
+ hasSections_ = true;
+ }
+ else if (slide.type() == "sub-section")
+ {
+ inSubSection_ = true;
+ indent = 1;
+ hasSections_ = true;
+ }
+ else
+ {
+ indent = inSubSection_ ? 2 : 1;
+ }
+
+ addSlide(slide.title(), indent, index_, slide.line());
+ }
+
+ index_++;
+}
+
+void SlideNavigationList::complete()
+{
+ // if we don't have any sections then flatted the indents
+ if (!hasSections_)
+ {
+ BOOST_FOREACH(json::Value& slide, slides_)
+ {
+ slide.get_obj()["indent"] = 0;
+ }
+ }
+}
+
+std::string SlideNavigationList::asCall() const
+{
+ std::ostringstream ostr;
+ ostr << "window.parent.initPresentationNavigator(";
+ json::write(asJson(), ostr);
+ ostr << ");";
+ return ostr.str();
+}
+
+json::Object SlideNavigationList::asJson() const
+{
+ json::Object slideNavigationJson;
+ slideNavigationJson["total_slides"] = index_;
+ slideNavigationJson["items"] = slides_;
+ return slideNavigationJson;
+}
+
+
+void SlideNavigationList::addSlide(const std::string& title,
+ int indent,
+ int index,
+ int line)
+{
+ json::Object slideJson;
+ slideJson["title"] = title;
+ slideJson["indent"] = indent;
+ slideJson["index"] = index;
+ slideJson["line"] = line;
+ slides_.push_back(slideJson);
+}
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideNavigationList.hpp b/src/cpp/session/modules/presentation/SlideNavigationList.hpp
new file mode 100644
index 0000000..0c1bbfc
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideNavigationList.hpp
@@ -0,0 +1,57 @@
+/*
+ * SlideNavigationList.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_NAVIGATION_LIST_HPP
+#define SESSION_PRESENTATION_SLIDE_NAVIGATION_LIST_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+#include <core/json/Json.hpp>
+
+#include "SlideParser.hpp"
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+class SlideNavigationList : public boost::noncopyable
+{
+public:
+ SlideNavigationList(const std::string& type);
+ void add(const Slide& slide);
+ void complete();
+
+ std::string asCall() const;
+
+ core::json::Object asJson() const;
+
+private:
+ void addSlide(const std::string& title, int indent, int index, int line);
+ core::json::Array slides_;
+ bool allowNavigation_;
+ bool allowSlideNavigation_;
+ int index_;
+ bool inSubSection_;
+ bool hasSections_;
+};
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_NAVIGATION_LIST_HPP
diff --git a/src/cpp/session/modules/presentation/SlideParser.cpp b/src/cpp/session/modules/presentation/SlideParser.cpp
new file mode 100644
index 0000000..8aa87ed
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideParser.cpp
@@ -0,0 +1,504 @@
+/*
+ * SlideParser.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SlideParser.hpp"
+
+#include <iostream>
+
+#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/StringUtils.hpp>
+#include <core/text/DcfParser.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+namespace {
+
+struct CompareName
+{
+ CompareName(const std::string& name) : name_(name) {}
+ bool operator()(const Slide::Field& field) const {
+ return boost::iequals(name_, field.first);
+ }
+ private:
+ std::string name_;
+};
+
+bool isCommandField(const std::string& name)
+{
+ return boost::iequals(name, "help-doc") ||
+ boost::iequals(name, "help-topic") ||
+ boost::iequals(name, "source") ||
+ boost::iequals(name, "console") ||
+ boost::iequals(name, "console-input") ||
+ boost::iequals(name, "execute") ||
+ boost::iequals(name, "pause");
+}
+
+bool isAtCommandField(const std::string& name)
+{
+ return isCommandField(name) ||
+ boost::iequals(name, "pause");
+}
+
+
+bool isValidField(const std::string& name)
+{
+ return isCommandField(name) ||
+ boost::iequals(name, "title") ||
+ boost::iequals(name, "author") ||
+ boost::iequals(name, "date") ||
+ boost::iequals(name, "autosize") ||
+ boost::iequals(name, "width") ||
+ boost::iequals(name, "height") ||
+ boost::iequals(name, "rtl") ||
+ boost::iequals(name, "depends") ||
+ boost::iequals(name, "transition") ||
+ boost::iequals(name, "transition-speed") ||
+ boost::iequals(name, "font-family") ||
+ boost::iequals(name, "font-import") ||
+ boost::iequals(name, "css") ||
+ boost::iequals(name, "class") ||
+ boost::iequals(name, "navigation") ||
+ boost::iequals(name, "incremental") ||
+ boost::iequals(name, "left") ||
+ boost::iequals(name, "right") ||
+ boost::iequals(name, "id") ||
+ boost::iequals(name, "audio") ||
+ boost::iequals(name, "video") ||
+ boost::iequals(name, "type") ||
+ boost::iequals(name, "at");
+}
+
+std::string normalizeFieldValue(const std::string& value)
+{
+ std::string normalized = text::dcfMultilineAsFolded(value);
+ return boost::algorithm::trim_copy(normalized);
+}
+
+} // anonymous namespace
+
+
+json::Object Command::asJson() const
+{
+ json::Object commandJson;
+ commandJson["name"] = name();
+ commandJson["params"] = params();
+ return commandJson;
+}
+
+json::Object AtCommand::asJson() const
+{
+ json::Object atCommandJson;
+ atCommandJson["at"] = seconds();
+ atCommandJson["command"] = command().asJson();
+ return atCommandJson;
+}
+
+// default title to true if there is a title provided and we
+// aren't in a video slide
+bool Slide::showTitle() const
+{
+ std::string defaultTitle = (!title().empty() && video().empty()) ? "true" :
+ "false";
+ return boost::iequals(fieldValue("title", defaultTitle), "true");
+}
+
+std::vector<Command> Slide::commands() const
+{
+ std::vector<Command> commands;
+ BOOST_FOREACH(const Slide::Field& field, fields_)
+ {
+ if (isCommandField(field.first))
+ commands.push_back(Command(field.first, field.second));
+ }
+ return commands;
+}
+
+std::vector<AtCommand> Slide::atCommands() const
+{
+ std::vector<AtCommand> atCommands;
+ boost::regex re("^([0-9]+)\\:([0-9]{2})\\s+([^\\:]+)(?:\\:\\s+(.*))?$");
+
+
+ std::vector<std::string> atFields = fieldValues("at");
+ BOOST_FOREACH(const std::string& atField, atFields)
+ {
+ boost::smatch match;
+ if (boost::regex_match(atField, match, re))
+ {
+ std::string cmd = match[3];
+ if (isAtCommandField(cmd))
+ {
+ int mins = safe_convert::stringTo<int>(match[1], 0);
+ int secs = (mins*60) + safe_convert::stringTo<int>(match[2], 0);
+ Command command(cmd, match[4]);
+ atCommands.push_back(AtCommand(secs, command));
+ }
+ else
+ {
+ module_context::consoleWriteError("Unrecognized command '" +
+ cmd + "'\n");
+ }
+ }
+ else
+ {
+ module_context::consoleWriteError(
+ "Skipping at command with invalid syntax:\n at: " +
+ atField + "\n");
+ }
+ }
+
+ return atCommands;
+}
+
+
+std::vector<std::string> Slide::fields() const
+{
+ std::vector<std::string> fields;
+ BOOST_FOREACH(const Field& field, fields_)
+ {
+ fields.push_back(field.first);
+ }
+ return fields;
+}
+
+std::string Slide::fieldValue(const std::string& name,
+ const std::string& defaultValue) const
+{
+ std::vector<Field>::const_iterator it =
+ std::find_if(fields_.begin(), fields_.end(), CompareName(name));
+ if (it != fields_.end())
+ return normalizeFieldValue(it->second);
+ else
+ return defaultValue;
+}
+
+
+std::vector<std::string> Slide::fieldValues(const std::string& name) const
+{
+ std::vector<std::string> values;
+ BOOST_FOREACH(const Field& field, fields_)
+ {
+ if (boost::iequals(name, field.first))
+ values.push_back(normalizeFieldValue(field.second));
+ }
+ return values;
+}
+
+std::vector<std::string> Slide::invalidFields() const
+{
+ return invalidFields_;
+}
+
+namespace {
+
+void insertField(std::vector<Slide::Field>* pFields, const Slide::Field& field)
+{
+ pFields->push_back(field);
+}
+
+} // anonymous namespace
+
+std::string Slide::transition() const
+{
+ std::string value = fieldValue("transition");
+ if (value == "rotate")
+ value = "default";
+ return value;
+}
+
+std::string Slide::rtl() const
+{
+ std::string value = fieldValue("rtl");
+ if (value == "true")
+ return value;
+ else
+ return "false";
+}
+
+bool Slide::autosize() const
+{
+ std::string value = fieldValue("autosize");
+ return value == "true";
+}
+
+std::string SlideDeck::title() const
+{
+ if (!slides_.empty())
+ return slides_[0].title();
+ else
+ return std::string();
+}
+
+std::string SlideDeck::rtl() const
+{
+ if (!slides_.empty())
+ return slides_[0].rtl();
+ else
+ return "false";
+}
+
+bool SlideDeck::autosize() const
+{
+ if (!slides_.empty())
+ return slides_[0].autosize();
+ else
+ return false;
+}
+
+int SlideDeck::width() const
+{
+ const int kDefaultWidth = 960;
+ if (!slides_.empty() && !slides_[0].width().empty())
+ return safe_convert::stringTo<int>(slides_[0].width(), kDefaultWidth);
+ else
+ return kDefaultWidth;
+}
+
+
+int SlideDeck::height() const
+{
+ const int kDefaultHeight = 700;
+ if (!slides_.empty() && !slides_[0].height().empty())
+ return safe_convert::stringTo<int>(slides_[0].height(), kDefaultHeight);
+ else
+ return kDefaultHeight;
+}
+
+std::string SlideDeck::fontFamily() const
+{
+ if (!slides_.empty())
+ return slides_[0].fontFamily();
+ else
+ return std::string();
+}
+
+std::string SlideDeck::css() const
+{
+ if (!slides_.empty())
+ return slides_[0].css();
+ else
+ return std::string();
+}
+
+std::string SlideDeck::transition() const
+{
+ std::string transition;
+ if (!slides_.empty())
+ transition = slides_[0].transition();
+
+ if (transition.empty())
+ transition = "linear";
+
+ return transition;
+}
+
+std::string SlideDeck::transitionSpeed() const
+{
+ std::string speed;
+ if (!slides_.empty())
+ speed = slides_[0].transitionSpeed();
+
+ if (!speed.empty())
+ return speed;
+ else
+ return "default";
+}
+
+std::string SlideDeck::navigation() const
+{
+ if (!slides_.empty())
+ return slides_[0].navigation();
+ else
+ return "slide";
+}
+
+std::string SlideDeck::incremental() const
+{
+ std::string val = !slides_.empty() ? slides_[0].incremental() : "";
+ if (!val.empty())
+ return val;
+ else
+ return "false";
+}
+
+std::string SlideDeck::depends() const
+{
+ if (!slides_.empty())
+ return slides_[0].depends();
+ else
+ return "slide";
+}
+
+
+Error SlideDeck::readSlides(const FilePath& filePath)
+{
+ // read the file
+ std::string slides;
+ Error error = readStringFromFile(filePath,
+ &slides,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+
+ // read the slides
+ return readSlides(slides, filePath.parent());
+}
+
+Error SlideDeck::readSlides(const std::string& slides, const FilePath& baseDir)
+{
+ // clear existing
+ slides_.clear();
+
+ // capture base dir
+ baseDir_ = baseDir;
+
+ // split into lines
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines, slides, boost::algorithm::is_any_of("\n"));
+
+ // find indexes of lines with 3 or more consecutive equals
+ boost::regex re("^\\={3,}\\s*$");
+ std::vector<std::size_t> headerLines;
+ for (std::size_t i = 0; i<lines.size(); i++)
+ {
+ boost::smatch m;
+ if (boost::regex_match(lines[i], m, re))
+ headerLines.push_back(i);
+ }
+
+ // capture the preamble (if any)
+ preamble_.clear();
+ if (!headerLines.empty())
+ {
+ if (headerLines[0] > 1)
+ {
+ for (std::size_t i = 0; i<(headerLines[0] - 1); i++)
+ preamble_.append(lines[i]);
+ }
+ }
+
+ // loop through the header lines to capture the slides
+ boost::regex dcfFieldRegex(core::text::kDcfFieldRegex);
+ for (std::size_t i = 0; i<headerLines.size(); i++)
+ {
+ // line index
+ std::size_t lineIndex = headerLines[i];
+
+ // title is the line before (if there is one)
+ std::string title = lineIndex > 0 ? lines[lineIndex-1] : "";
+
+ // line of code the slide is on
+ int line = !title.empty() ? lineIndex - 1 : lineIndex;
+
+ // find the begin index (line after)
+ std::size_t beginIndex = lineIndex + 1;
+
+ // find the end index (next section or end of file)
+ std::size_t endIndex;
+ if (i < (headerLines.size()-1))
+ endIndex = headerLines[i+1] - 1;
+ else
+ endIndex = lines.size();
+
+ // now iterate through from begin to end and break into fields and content
+ bool inFields = true;
+ std::string fields, content;
+ for (std::size_t l = beginIndex; l<endIndex; l++)
+ {
+ if (inFields)
+ {
+ if (boost::regex_match(lines[l], dcfFieldRegex))
+ {
+ fields += lines[l] + "\n";
+ }
+ else
+ {
+ content += lines[l] + "\n";
+ inFields = false;
+ }
+ }
+ else
+ {
+ content += lines[l] + "\n";
+ }
+ }
+
+ // now parse the fields
+ std::string errMsg;
+ std::vector<Slide::Field> slideFields;
+ Error error = text::parseDcfFile(fields,
+ false,
+ boost::bind(insertField,
+ &slideFields, _1),
+ &errMsg);
+ if (error)
+ {
+ std::string badLine = error.getProperty("line-contents");
+ if (!badLine.empty())
+ module_context::consoleWriteError("Invalid DCF field:\n "
+ + badLine + "\n");
+ return error;
+ }
+
+ // validate all of the fields
+ std::vector<std::string> invalidFields;
+ BOOST_FOREACH(const Slide::Field& field, slideFields)
+ {
+ if (!isValidField(field.first))
+ invalidFields.push_back(field.first);
+ }
+
+ // create the slide
+ slides_.push_back(Slide(title,
+ slideFields,
+ invalidFields,
+ content,
+ line));
+ }
+
+ // if the deck is empty then insert a placeholder first slide
+ if (slides_.empty())
+ {
+ slides_.push_back(Slide(baseDir.filename(),
+ std::vector<Slide::Field>(),
+ std::vector<std::string>(),
+ std::string(),
+ 0));
+ }
+
+ return Success();
+}
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideParser.hpp b/src/cpp/session/modules/presentation/SlideParser.hpp
new file mode 100644
index 0000000..494cdd0
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideParser.hpp
@@ -0,0 +1,189 @@
+/*
+ * SlideParser.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_PARSER_HPP
+#define SESSION_PRESENTATION_SLIDE_PARSER_HPP
+
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+class Command {
+public:
+ Command(const std::string& name, const std::string& params)
+ : name_(name), params_(params)
+ {
+ }
+
+ const std::string& name() const { return name_; }
+ const std::string& params() const { return params_; }
+
+ core::json::Object asJson() const;
+
+private:
+ std::string name_;
+ std::string params_;
+};
+
+class AtCommand {
+public:
+ AtCommand(int seconds, const Command& command)
+ : seconds_(seconds), command_(command)
+ {
+ }
+
+ int seconds() const { return seconds_; }
+ const Command& command() const { return command_; }
+
+ core::json::Object asJson() const;
+
+private:
+ int seconds_;
+ Command command_;
+};
+
+class Slide
+{
+public:
+ typedef std::pair<std::string,std::string> Field;
+
+public:
+ Slide(const std::string& title,
+ const std::vector<Field>& fields,
+ const std::vector<std::string>& invalidFields,
+ const std::string& content,
+ std::size_t line)
+ : title_(title),
+ fields_(fields),
+ invalidFields_(invalidFields),
+ content_(content),
+ line_(line)
+ {
+ }
+
+public:
+ // title
+ std::string title() const { return title_; }
+ bool showTitle() const;
+
+ // line
+ int line() const { return line_; }
+
+ std::string author() const { return fieldValue("author"); }
+ std::string date() const { return fieldValue("date"); }
+ std::string rtl() const;
+ bool autosize() const;
+ std::string width() const { return fieldValue("width"); }
+ std::string height() const { return fieldValue("height"); }
+ std::string depends() const { return fieldValue("depends"); }
+ std::string fontFamily() const { return fieldValue("font-family"); }
+ std::string css() const { return fieldValue("css"); }
+ std::string transition() const;
+ std::string transitionSpeed() const
+ {
+ return fieldValue("transition-speed");
+ }
+ std::string navigation() const { return fieldValue("navigation", "slide"); }
+
+public:
+ // global/local fields
+ std::string incremental() const { return fieldValue("incremental"); }
+
+ // local fields
+ std::string id() const { return fieldValue("id"); }
+ std::string type() const { return fieldValue("type"); }
+ std::string video() const { return fieldValue("video"); }
+ std::string audio() const { return fieldValue("audio"); }
+ std::string cssClass() const { return fieldValue("class"); }
+ std::string left() const { return fieldValue("left"); }
+ std::string right() const { return fieldValue("right"); }
+
+ std::vector<Command> commands() const;
+
+ std::vector<AtCommand> atCommands() const;
+
+ std::vector<std::string> fields() const;
+ std::string fieldValue(const std::string& name,
+ const std::string& defaultValue="") const;
+ std::vector<std::string> fieldValues(const std::string& name) const;
+
+ std::vector<std::string> invalidFields() const;
+
+ const std::string& content() const { return content_; }
+
+private:
+ std::string title_;
+ std::vector<Field> fields_;
+ std::vector<std::string> invalidFields_;
+ std::string content_;
+ int line_;
+};
+
+class SlideDeck
+{
+public:
+ SlideDeck()
+ {
+ }
+
+ core::Error readSlides(const core::FilePath& filePath);
+ core::Error readSlides(const std::string& slides, const core::
+ FilePath& baseDir);
+
+ std::string title() const;
+ std::string rtl() const;
+ bool autosize() const;
+ int width() const;
+ int height() const;
+
+ std::string fontFamily() const;
+
+ std::string css() const;
+
+ std::string transition() const;
+ std::string transitionSpeed() const;
+ std::string navigation() const;
+ std::string incremental() const;
+
+ std::string depends() const;
+
+ std::string preamble() const { return preamble_; }
+
+ const std::vector<Slide>& slides() const { return slides_; }
+
+ core::FilePath baseDir() const { return baseDir_; }
+
+private:
+ core::FilePath baseDir_;
+ std::string preamble_;
+ std::vector<Slide> slides_;
+};
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_PARSER_HPP
diff --git a/src/cpp/session/modules/presentation/SlideQuizRenderer.cpp b/src/cpp/session/modules/presentation/SlideQuizRenderer.cpp
new file mode 100644
index 0000000..488261d
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideQuizRenderer.cpp
@@ -0,0 +1,159 @@
+/*
+ * SlideQuizRenderer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SlideQuizRenderer.hpp"
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/SafeConvert.hpp>
+#include <core/RegexUtils.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+namespace {
+
+std::string handleClickFunction(const std::string& formId)
+{
+ return "handle" + formId + "Click";
+}
+
+std::string asFormInput(const boost::cmatch& match,
+ const std::string& formId,
+ int* pItemIndex)
+{
+ *pItemIndex = *pItemIndex + 1;
+
+ // parse out any answer/feedback content
+ bool isAnswer = false;
+ std::string feedback;
+ boost::smatch m;
+ std::string itemText = match[1];
+ boost::regex re("\\[(.*?)\\]");
+ if (boost::regex_search(itemText, m, re))
+ {
+ // trim the content
+ feedback = m[1];
+ boost::algorithm::trim(feedback);
+
+ // is this the answer?
+ std::size_t ansLoc = feedback.find('*');
+ if (ansLoc == 0)
+ {
+ isAnswer = true;
+ feedback = feedback.substr(ansLoc + 1);
+ boost::algorithm::trim(feedback);
+ }
+
+ // extract item text
+ itemText = itemText.substr(0, itemText.find_first_of('['));
+ }
+
+ // if there is no feedback then create defaults
+ if (feedback.empty())
+ feedback = isAnswer ? "Correct!" : "Incorrect";
+
+ boost::format fmt("<li>"
+ "<input type=\"radio\" name=\"%1%\" value=\"%2%\""
+ " onclick=\"%3%(this.value,%4%,'%5%');\"/>%6%"
+ "</li>");
+
+ std::string input = boost::str(fmt % (formId + "_input")
+ % *pItemIndex
+ % handleClickFunction(formId)
+ % (isAnswer ? "true" : "false")
+ % feedback
+ % itemText);
+
+ return input;
+}
+
+
+} // anonymous namespace
+
+void renderQuiz(int slideIndex, std::string* pHead, std::string* pHTML)
+{
+ // tweak the radio button size
+ pHead->append(
+ "<style>.quiz-multichoice .reveal input { zoom: 2.5; }</style>\n");
+
+ // build form id
+ std::string suffix = safe_convert::numberToString<int>(slideIndex);
+ std::string formId = "quizForm" + suffix;
+
+ // build validation script
+ boost::format fmtScript(
+ "<script>\n"
+ "function %1%(answer, correct, feedback) {\n"
+ " document.getElementById('%2%_correctFeedback').innerText = feedback;\n"
+ " document.getElementById('%2%_incorrectFeedback').innerText = feedback;\n"
+ " document.getElementById('%2%_correct').style.display ="
+ " correct ? \"block\" : \"none\";\n"
+ " document.getElementById('%2%_incorrect').style.display ="
+ " correct ? \"none\" : \"block\";\n"
+ " if (window.parent.recordPresentationQuizAnswer)\n"
+ " window.parent.recordPresentationQuizAnswer("
+ "%3%, answer, correct);\n "
+ "}\n"
+ "</script>\n\n");
+ pHead->append(boost::str(fmtScript
+ % handleClickFunction(formId)
+ % formId
+ % slideIndex));
+
+ // correct and incorrect divs
+ std::string cssAttribs = "class=\"quizFeedback\" style=\"display:none\"";
+ boost::format fmtFeedback(
+ "<div id=\"%1%_correct\" %2%>\n"
+ "<img src=\"slides-images/correct.png\"/>"
+ "<span id=\"%1%_correctFeedback\">Correct!</span>\n"
+ "</div>\n"
+ "<div id=\"%1%_incorrect\" %2%>\n"
+ "<img src=\"slides-images/incorrect.png\"/>"
+ "<span id=\"%1%_incorrectFeedback\">Incorrect</span>\n"
+ "</div>\n");
+ std::string feedbackHTML = boost::str(fmtFeedback % formId % cssAttribs);
+
+ // enclose in form
+ boost::format fmt("<form id=\"%1%\">\n\n<ul>");
+ boost::algorithm::replace_first(*pHTML, "<ul>", boost::str(fmt % formId));
+ boost::format suffixFmt("</ul>\n\n%1%\n</form>\n");
+ boost::algorithm::replace_last(*pHTML, "</ul>", boost::str(suffixFmt %
+ feedbackHTML));
+
+ // create input elements
+ int itemIndex = 0;
+ boost::iostreams::regex_filter filter(boost::regex("<li>(.+?)<\\/li>"),
+ boost::bind(asFormInput,
+ _1, formId, &itemIndex));
+
+ // inputs html
+ Error error = regex_utils::filterString(*pHTML, filter, pHTML);
+ if (error)
+ LOG_ERROR(error);
+}
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideQuizRenderer.hpp b/src/cpp/session/modules/presentation/SlideQuizRenderer.hpp
new file mode 100644
index 0000000..fc6000f
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideQuizRenderer.hpp
@@ -0,0 +1,31 @@
+/*
+ * SlideQuizRenderer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_QUIZ_RENDERER_HPP
+#define SESSION_PRESENTATION_SLIDE_QUIZ_RENDERER_HPP
+
+#include <string>
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+void renderQuiz(int slideIndex, std::string* pHead, std::string* pHTML);
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_QUIZ_RENDERER_HPP
diff --git a/src/cpp/session/modules/presentation/SlideRenderer.cpp b/src/cpp/session/modules/presentation/SlideRenderer.cpp
new file mode 100644
index 0000000..8f978f7
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideRenderer.cpp
@@ -0,0 +1,555 @@
+/*
+ * SlideRenderer.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SlideRenderer.hpp"
+
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/StringUtils.hpp>
+#include <core/json/Json.hpp>
+
+#include <core/markdown/Markdown.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SlideParser.hpp"
+#include "SlideMediaRenderer.hpp"
+#include "SlideNavigationList.hpp"
+#include "SlideQuizRenderer.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+namespace {
+
+std::string commandsAsJsonArray(const Slide& slide)
+{
+ json::Array commandsJsonArray;
+
+ std::vector<Command> commands = slide.commands();
+ BOOST_FOREACH(const Command& command, commands)
+ {
+ commandsJsonArray.push_back(command.asJson());
+ }
+
+ std::ostringstream ostr;
+ json::write(commandsJsonArray, ostr);
+ return ostr.str();
+}
+
+Error renderMarkdown(const std::string& content, std::string* pHTML)
+{
+ markdown::Extensions extensions;
+ markdown::HTMLOptions htmlOptions;
+ return markdown::markdownToHTML(content,
+ extensions,
+ htmlOptions,
+ pHTML);
+}
+
+
+std::string divWrap(const std::string& classNames,
+ const std::string& width,
+ const std::string& contents)
+{
+ std::string styleAttribute;
+ if (!width.empty())
+ styleAttribute = "style=\"width: " + width + ";\" ";
+
+ boost::format fmt("\n<div class=\"%1%\" %2%>\n%3%\n</div>\n");
+ return boost::str(fmt % classNames % styleAttribute % contents);
+}
+
+std::string divWrap(const std::string& classNames,
+ const std::string& contents)
+{
+ return divWrap(classNames, "", contents);
+}
+
+std::string mediaClass(const std::string& html)
+{
+ boost::regex imageRegex(
+ "\\s*<p>\\s*<img src=\"([^\"]+)\"(?: [\\w]+=\"[^\"]*\")*/>\\s*</p>\\s*");
+
+ boost::smatch match;
+ if (boost::regex_search(html, match, imageRegex))
+ {
+ std::string::const_iterator begin = match[0].first;
+ std::string::const_iterator end = match[0].second;
+
+ if (begin == html.begin() && end == html.end())
+ return "mediaOnly";
+ else
+ return "mediaInline";
+ }
+ // look for videos
+ else
+ {
+ // trim for comparison
+ std::string trimmedHtml = boost::algorithm::trim_copy(html);
+ if (boost::algorithm::contains(trimmedHtml, "<video id="))
+ {
+ bool starts = boost::algorithm::starts_with(trimmedHtml, "<video id=");
+ bool ends = boost::algorithm::ends_with(trimmedHtml, "</video>");
+ if (starts && ends)
+ return "mediaOnly";
+ else
+ return "mediaInline";
+ }
+ }
+
+ return std::string();
+}
+
+void addFragmentClass(const std::string& fragmentClass,
+ std::string* pHTML)
+{
+ // add fragment class to elements eligible for incremental build
+ *pHTML = boost::regex_replace(*pHTML,
+ boost::regex("<(h[1-6]|p|blockquote|pre|li)>"),
+ "<$1 class=\"" + fragmentClass + "\">");
+
+ // remove class from first paragraph element
+ std::string pWithClass = "<p class=\"" + fragmentClass + "\">";
+ boost::algorithm::replace_first(*pHTML, pWithClass, "<p>");
+
+ // remove class from paragraph inside blockquote
+ std::string bqWithClass = "<blockquote class=\"" + fragmentClass + "\">";
+ boost::algorithm::replace_first(*pHTML, bqWithClass + "\n" + pWithClass,
+ bqWithClass + "\n<p>");
+}
+
+void validateTransitionType(const std::string& type)
+{
+ bool isValid = boost::iequals(type, "none") ||
+ boost::iequals(type, "default") ||
+ boost::iequals(type, "linear") ||
+ boost::iequals(type, "fade") ||
+ boost::iequals(type, "zoom") ||
+ boost::iequals(type, "concave");
+
+ if (!isValid)
+ {
+ module_context::consoleWriteError("Invalid value for transition field: "
+ + type + "\n");
+ }
+}
+
+void validateTransitionSpeedType(const std::string& speed)
+{
+ bool isValid = speed.empty() ||
+ boost::iequals(speed, "default") ||
+ boost::iequals(speed, "fast") ||
+ boost::iequals(speed, "slow");
+ if (!isValid)
+ {
+ module_context::consoleWriteError("Invalid value for transition-speed "
+ "field: " + speed + "\n");
+ }
+}
+
+void validateNavigationType(const std::string& type)
+{
+ bool isValid = boost::iequals(type, "slide") ||
+ boost::iequals(type, "section") ||
+ boost::iequals(type, "none");
+
+ if (!isValid)
+ {
+ module_context::consoleWriteError("Invalid value for navigation field: "
+ + type + "\n");
+ }
+}
+
+void validateIncrementalType(const std::string& type)
+{
+ bool isValid = boost::iequals(type, "false") ||
+ boost::iequals(type, "true");
+
+ if (!isValid)
+ {
+ module_context::consoleWriteError("Invalid value for incremental field: "
+ + type + "\n");
+ }
+}
+
+void validateSlideDeckFields(const SlideDeck& slideDeck)
+{
+ validateTransitionType(slideDeck.transition());
+ validateTransitionSpeedType(slideDeck.transitionSpeed());
+ validateNavigationType(slideDeck.navigation());
+ validateIncrementalType(slideDeck.incremental());
+}
+
+void computeColumnWidths(const std::string& width,
+ std::string* pSpecifiedWidth,
+ std::string* pOtherWidth)
+{
+ int w = core::safe_convert::stringTo<int>(width, 50);
+ *pSpecifiedWidth = safe_convert::numberToString(w - 2) + "%";
+ *pOtherWidth = safe_convert::numberToString(100 - w - 2) + "%";
+}
+
+void computeColumnWidths(const Slide& slide,
+ std::string* pLeftWidth,
+ std::string* pRightWidth)
+{
+ boost::regex re("([0-9]+)\\s*%?");
+ boost::smatch match;
+
+ if (boost::regex_match(slide.left(), match, re))
+ computeColumnWidths(match[1], pLeftWidth, pRightWidth);
+ else if (boost::regex_match(slide.right(), match, re))
+ computeColumnWidths(match[1], pRightWidth, pLeftWidth);
+}
+
+Error slideToHtml(const Slide& slide,
+ int slideNumber,
+ const std::string& extraContent,
+ const std::string& incremental,
+ std::string* pHead,
+ std::string* pHTML)
+{
+ // invalid fields
+ if (slide.invalidFields().size() > 0)
+ {
+ std::ostringstream ostr;
+ ostr << "<div class=\"fieldError\">";
+ BOOST_FOREACH(const std::string& field, slide.invalidFields())
+ {
+ ostr << "<span>Unrecognized slide field:</span> "
+ << "<code>" << field << "</code><br/>";
+ }
+ ostr << "</div>";
+ pHTML->append(ostr.str());
+ }
+
+ // render the markdown
+ std::string markdownHTML;
+ Error error = renderMarkdown(slide.content(), &markdownHTML);
+ if (error)
+ return error;
+
+ // see if we need to render a quiz
+ if (slide.type() == "quiz-multichoice")
+ {
+ std::string head;
+ renderQuiz(slideNumber, &head, &markdownHTML);
+ pHead->append(head);
+ }
+
+ // append the html
+ pHTML->append(markdownHTML);
+
+ // add the extra content
+ pHTML->append(extraContent);
+
+ // slide content classes
+ std::string slideClasses = "slideContent";
+ if (!slide.showTitle())
+ slideClasses += " noTitle";
+ if (!slide.cssClass().empty())
+ slideClasses += " " + slide.cssClass();
+
+ // look for an <hr/> splitting the html into columns
+ const std::string kHRTag = "<hr/>";
+ std::size_t hrLoc = pHTML->find(kHRTag);
+ if (hrLoc != std::string::npos)
+ {
+ // get the columns
+ std::string column1 = pHTML->substr(0, hrLoc);
+ std::string column2;
+ if (pHTML->length() > (column1.length() + kHRTag.length()))
+ column2 = pHTML->substr(hrLoc + kHRTag.length());
+
+ // compute the column widths
+ std::string leftWidth;
+ std::string rightWidth;
+ computeColumnWidths(slide, &leftWidth, &rightWidth);
+
+ // now render two divs with the columns
+ pHTML->clear();
+ std::ostringstream ostr;
+ ostr << divWrap("column column1 " + slideClasses, leftWidth, column1);
+ ostr << divWrap("column column2 " + slideClasses, rightWidth, column2);
+ *pHTML = ostr.str();
+ }
+
+ // apply standard (and optional media) classes
+ else
+ {
+ std::string extraMediaClass = mediaClass(*pHTML);
+ if (!extraMediaClass.empty())
+ slideClasses = extraMediaClass + " " + slideClasses;
+
+ *pHTML = divWrap(slideClasses, *pHTML);
+ }
+
+ // check whether we need to apply the fragment style (create
+ // fragmentClass string so we can support other fragment
+ // reveal visual styles in the future if we want)
+ std::string fragmentClass;
+ if (incremental == "true")
+ fragmentClass = "fragment";
+
+ // apply fragmentClass if necessary
+ if (!fragmentClass.empty())
+ addFragmentClass(fragmentClass, pHTML);
+
+ return Success();
+}
+
+} // anonymous namespace
+
+
+Error renderSlides(const SlideDeck& slideDeck,
+ std::string* pSlidesHead,
+ std::string* pSlides,
+ std::string* pRevealConfig,
+ std::string* pInitActions,
+ std::string* pSlideActions)
+{
+ // validate global slide deck fields (will just print warnings)
+ validateSlideDeckFields(slideDeck);
+
+ // render the slides to HTML and slide commands to case statements
+ std::ostringstream ostr, ostrRevealConfig, ostrInitActions, ostrSlideActions;
+
+ // track json version of slide list
+ SlideNavigationList navigationList(slideDeck.navigation());
+
+ // now the slides
+ std::string cmdPad(8, ' ');
+ int slideNumber = 0;
+ for (size_t i=0; i<slideDeck.slides().size(); i++)
+ {
+ // slide
+ const Slide& slide = slideDeck.slides().at(i);
+
+ // is this the first slide?
+ bool isFirstSlide = (i == 0);
+
+ // track slide in list
+ navigationList.add(slide);
+
+ ostr << "<section";
+ if (!slide.id().empty())
+ ostr << " id=\"" << slide.id() << "\"";
+
+ // get the slide type
+ std::string type = isFirstSlide ? "section" : slide.type();
+
+ // add the state if there is a type
+ if (!type.empty())
+ ostr << " data-state=\"" << type << "\"";
+
+ // add the transition
+ std::string transition;
+ if (!slide.transition().empty())
+ {
+ validateTransitionType(slide.transition());
+ transition = slide.transition();
+ }
+ else
+ {
+ transition = slideDeck.transition();
+ }
+ ostr << " data-transition=\"" << transition << "\"";
+
+ // add the transition speed
+ std::string transitionSpeed;
+ if (!slide.transitionSpeed().empty())
+ {
+ validateTransitionSpeedType(slide.transitionSpeed());
+ transitionSpeed = slide.transitionSpeed();
+ }
+ else
+ {
+ transitionSpeed = slideDeck.transitionSpeed();
+ }
+ ostr << " data-transition-speed=\"" << transitionSpeed << "\"";
+
+ // end section tag
+ ostr << ">\n";
+
+ // show the title with the appropriate header. also track whether
+ // this slide is eligible for incremental display (first slide
+ // and section slides are not)
+ bool canShowIncremental = true;
+ if (isFirstSlide || slide.showTitle())
+ {
+ std::string hTag;
+ if (isFirstSlide)
+ {
+ hTag = "h1";
+ canShowIncremental = false;
+ }
+ else if (type == "section" || type == "sub-section")
+ {
+ hTag = "h2";
+ canShowIncremental = false;
+ }
+ else
+ {
+ hTag = "h3";
+ }
+
+ ostr << "<" << hTag << ">"
+ << string_utils::htmlEscape(slide.title())
+ << "</" << hTag << ">";
+ }
+
+ // if this is slide one then render author and date if they are included
+ if (isFirstSlide)
+ {
+ ostr << "<p>";
+ if (!slide.author().empty())
+ {
+ ostr << string_utils::htmlEscape(slide.author());
+ if (!slide.date().empty())
+ ostr << "<br/>";
+ }
+ if (!slide.date().empty())
+ ostr << string_utils::htmlEscape(slide.date());
+
+ ostr << "</p>" << "\n";
+ }
+
+ // determine incremental property
+ std::string incremental = "false";
+ if (canShowIncremental)
+ {
+ if (!slide.incremental().empty())
+ {
+ validateIncrementalType(slide.incremental());
+ incremental = slide.incremental();
+ }
+ else
+ {
+ incremental = slideDeck.incremental();
+ }
+ }
+
+ // setup vectors for reveal config and init actions
+ std::vector<std::string> revealConfig;
+ std::vector<std::string> initActions;
+
+ // setup a vector of js actions to take when the slide loads
+ // (we always take the action of adding any embedded commands)
+ std::vector<std::string> slideActions;
+ slideActions.push_back("cmds = " + commandsAsJsonArray(slide));
+
+ // get at commands
+ std::vector<AtCommand> atCommands = slide.atCommands();
+
+ // render video if specified
+ std::ostringstream ostrMedia;
+ std::string video = slide.video();
+ if (!video.empty())
+ {
+ renderMedia("video",
+ slideNumber,
+ slideDeck.baseDir(),
+ video,
+ atCommands,
+ ostrMedia,
+ &initActions,
+ &slideActions);
+ }
+
+ // render audio if specified
+ std::string audio = slide.audio();
+ if (!audio.empty())
+ {
+ renderMedia("audio",
+ slideNumber,
+ slideDeck.baseDir(),
+ audio,
+ atCommands,
+ ostrMedia,
+ &initActions,
+ &slideActions);
+ }
+
+
+ // render markdown
+ std::string headContent, htmlContent;
+ Error error = slideToHtml(slide,
+ slideNumber,
+ ostrMedia.str(),
+ incremental,
+ &headContent,
+ &htmlContent);
+ if (error)
+ return error;
+
+ // record head
+ pSlidesHead->append(headContent);
+
+ // record html
+ ostr << htmlContent << "\n";
+
+ // render end section
+ ostr << "</section>" << "\n";
+
+ // reveal config actions
+ BOOST_FOREACH(const std::string& config, revealConfig)
+ {
+ ostrRevealConfig << config << "," << "\n";
+ }
+
+ // javascript actions to take on slide deck init
+ BOOST_FOREACH(const std::string& jsAction, initActions)
+ {
+ ostrInitActions << jsAction << ";" << "\n";
+ }
+
+ // javascript actions to take on slide load
+ ostrSlideActions << cmdPad << "case " << slideNumber << ":" << "\n";
+ BOOST_FOREACH(const std::string& jsAction, slideActions)
+ {
+ ostrSlideActions << cmdPad << " " << jsAction << ";" << "\n";
+ }
+ ostrSlideActions << "\n" << cmdPad << " break;" << "\n";
+
+ // increment slide number
+ slideNumber++;
+ }
+
+ // init slide list as part of actions
+ navigationList.complete();
+ ostrInitActions << navigationList.asCall() << "\n";
+
+ *pSlides = ostr.str();
+ *pRevealConfig = ostrRevealConfig.str();
+ *pInitActions = ostrInitActions.str();
+ *pSlideActions = ostrSlideActions.str();
+ return Success();
+}
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideRenderer.hpp b/src/cpp/session/modules/presentation/SlideRenderer.hpp
new file mode 100644
index 0000000..644f2c4
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideRenderer.hpp
@@ -0,0 +1,44 @@
+/*
+ * SlideRenderer.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_RENDERER_HPP
+#define SESSION_PRESENTATION_SLIDE_RENDERER_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ class FilePath;
+} // anonymous namespace
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+class SlideDeck;
+
+core::Error renderSlides(const SlideDeck& slideDeck,
+ std::string* pSlidesHead,
+ std::string* pSlides,
+ std::string* pRevealConfig,
+ std::string* pInitActions,
+ std::string* pSlideActions);
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_RENDERER_HPP
diff --git a/src/cpp/session/modules/presentation/SlideRequestHandler.cpp b/src/cpp/session/modules/presentation/SlideRequestHandler.cpp
new file mode 100644
index 0000000..1f9cfb8
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideRequestHandler.cpp
@@ -0,0 +1,1255 @@
+/*
+ * SlideRequestHandler.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "SlideRequestHandler.hpp"
+
+#include <iostream>
+
+#include <boost/utility.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <boost/regex.hpp>
+#include <boost/iostreams/filter/regex.hpp>
+
+#include <core/FileSerializer.hpp>
+#include <core/HtmlUtils.hpp>
+#include <core/markdown/Markdown.hpp>
+#include <core/text/TemplateFilter.hpp>
+#include <core/system/Process.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "PresentationState.hpp"
+#include "PresentationLog.hpp"
+#include "SlideParser.hpp"
+#include "SlideRenderer.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+void onSlideDeckChangedOverlay(const SlideDeck& slideDeck);
+
+namespace {
+
+const char * const kDefaultRevealFont = "\"Lato\"";
+const char * const kDefaultRevealHeadingFont = "\"News Cycle\"";
+const char * const kMediaPrint = "media=\"print\"";
+
+class ResourceFiles : boost::noncopyable
+{
+private:
+ ResourceFiles() {}
+
+public:
+ std::string get(const std::string& path)
+ {
+ if (cache_.find(path) == cache_.end())
+ cache_[path] = module_context::resourceFileAsString(path);
+ return cache_[path];
+ }
+
+private:
+ friend ResourceFiles& resourceFiles();
+ std::map<std::string,std::string> cache_;
+};
+
+ResourceFiles& resourceFiles()
+{
+ static ResourceFiles instance;
+ return instance;
+}
+
+
+std::string revealResource(const std::string& path,
+ bool embed,
+ const std::string& extraAttribs)
+{
+ // determine type
+ bool isCss = boost::algorithm::ends_with(path, "css");
+
+ // generate code for link vs. embed
+ std::string code;
+ if (embed)
+ {
+ if (isCss)
+ {
+ code = "<style type=\"text/css\" " + extraAttribs + " >\n" +
+ resourceFiles().get("presentation/" + path) + "\n"
+ + "</style>";
+ }
+ else
+ {
+ code = "<script type=\"text/javascript\" " + extraAttribs + " >\n" +
+ resourceFiles().get("presentation/" + path) + "\n"
+ + "</script>";
+ }
+ }
+ else
+ {
+ if (isCss)
+ {
+ code = "<link rel=\"stylesheet\" type=\"text/css\" href=\""
+ + path + "\" " + extraAttribs + " >";
+ }
+ else
+ {
+ code = "<script src=\"" + path + "\" " + extraAttribs + " ></script>";
+ }
+ }
+
+ return code;
+}
+
+std::string revealEmbed(const std::string& path,
+ const std::string& extraAttribs = std::string())
+{
+ return revealResource(path, true, extraAttribs);
+}
+
+std::string revealLink(const std::string& path,
+ const std::string& extraAttribs = std::string())
+{
+ return revealResource(path, false, extraAttribs);
+}
+
+
+std::string remoteMathjax()
+{
+ return resourceFiles().get("presentation/mathjax.html");
+}
+
+std::string alternateMathjax(const std::string& prefix)
+{
+ return boost::algorithm::replace_first_copy(
+ remoteMathjax(),
+ "https://c328740.ssl.cf1.rackcdn.com/mathjax/2.0-latest",
+ prefix);
+}
+
+
+std::string localMathjax()
+{
+ return alternateMathjax("mathjax");
+}
+
+std::string copiedMathjax(const FilePath& targetFile)
+{
+ // determine target files dir and create it if necessary
+ std::string presFilesDir = targetFile.stem() + "_files";
+ FilePath filesTargetDir = targetFile.parent().complete(presFilesDir);
+ Error error = filesTargetDir.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return remoteMathjax();
+ }
+
+ // copy the mathjax directory
+ r::exec::RFunction fileCopy("file.copy");
+ fileCopy.addParam("from", string_utils::utf8ToSystem(
+ session::options().mathjaxPath().absolutePath()));
+ fileCopy.addParam("to", string_utils::utf8ToSystem(
+ filesTargetDir.absolutePath()));
+ fileCopy.addParam("recursive", true);
+ error = fileCopy.call();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return remoteMathjax();
+ }
+
+ // return fixed up html
+ return alternateMathjax(presFilesDir + "/mathjax");
+}
+
+std::string localWebFonts()
+{
+ return "@import url('revealjs/fonts/NewsCycle.css');\n"
+ "@import url('revealjs/fonts/Lato.css');";
+}
+
+std::string remoteWebFonts()
+{
+ return "@import url('https://fonts.googleapis.com/css?family=News+Cycle:400,700');\n"
+ "@import url('https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic');";
+}
+
+std::string embeddedWebFonts()
+{
+ std::string fonts = "presentation/revealjs/fonts";
+ std::string css = resourceFiles().get(fonts + "/Lato.css") +
+ resourceFiles().get(fonts + "/NewsCycle.css");
+
+ try
+ {
+ // input stream
+ std::istringstream cssStream(css);
+
+ // filtered output stream
+ boost::iostreams::filtering_ostream filteredStream;
+
+ // base64 encoder
+ FilePath fontPath = session::options().rResourcesPath().complete(fonts);
+ filteredStream.push(html_utils::CssUrlFilter(fontPath));
+
+ // target stream
+ std::ostringstream os;
+ os.exceptions(std::istream::failbit | std::istream::badbit);
+ filteredStream.push(os);
+
+ // copy and return
+ boost::iostreams::copy(cssStream, filteredStream, 128);
+ return os.str();
+ }
+ catch(const std::exception& e)
+ {
+ LOG_ERROR_MESSAGE(e.what());
+ return remoteWebFonts();
+ }
+
+}
+
+bool hasKnitrVersion_1_2()
+{
+ return module_context::isPackageVersionInstalled("knitr", "1.2");
+}
+
+std::string extractKnitrError(const std::string& stdError)
+{
+ std::string knitrError = stdError;
+
+ // strip everything before "Error in"
+ size_t errorInPos = knitrError.find("Error in");
+ if (errorInPos != std::string::npos)
+ knitrError = knitrError.substr(errorInPos);
+
+ // strip everything (inclusive) after "Calls: "
+ size_t callsPos = knitrError.find("Calls: ");
+ if (callsPos != std::string::npos)
+ knitrError = knitrError.substr(0, callsPos);
+
+ return boost::algorithm::trim_copy(knitrError);
+
+}
+
+bool performKnit(const FilePath& rmdPath,
+ bool clearCache,
+ ErrorResponse* pErrorResponse)
+{
+ // calculate the target md path
+ FilePath mdPath = rmdPath.parent().childPath(rmdPath.stem() + ".md");
+
+ // remove the md if we are clearing the cache
+ if (clearCache)
+ {
+ Error error = mdPath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // Now detect whether we even need to knit -- if there is an .md
+ // file with timestamp the same as or later than the .Rmd then skip it
+ if (mdPath.exists() && (mdPath.lastWriteTime() > rmdPath.lastWriteTime()))
+ return true;
+
+ // R binary
+ FilePath rProgramPath;
+ Error error = module_context::rScriptPath(&rProgramPath);
+ if (error)
+ {
+ *pErrorResponse = ErrorResponse(error.summary());
+ return false;
+ }
+
+ // confirm correct version of knitr
+ if (!hasKnitrVersion_1_2())
+ {
+ *pErrorResponse = ErrorResponse("knitr version 1.2 or greater is "
+ "required for presentations");
+ return false;
+ }
+
+ // removet the target file
+ error = mdPath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ // remove the cache if requested
+ if (clearCache)
+ {
+ FilePath cachePath = rmdPath.parent().childPath(rmdPath.stem()+"-cache");
+ if (cachePath.exists())
+ {
+ Error error = cachePath.remove();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ // args
+ std::vector<std::string> args;
+ args.push_back("--silent");
+ args.push_back("--no-save");
+ args.push_back("--no-restore");
+ args.push_back("-e");
+ boost::format fmt("library(knitr); "
+ "opts_chunk$set(cache.path='%1%-cache/', "
+ "fig.path='%1%-figure/', "
+ "tidy=FALSE, "
+ "warning=FALSE, "
+ "error=FALSE, "
+ "message=FALSE, "
+ "comment=NA); "
+ "render_markdown(); "
+ "knit('%2%', output = '%3%', encoding='%4%');");
+ std::string encoding = projects::projectContext().defaultEncoding();
+ std::string cmd = boost::str(
+ fmt % string_utils::utf8ToSystem(rmdPath.stem())
+ % string_utils::utf8ToSystem(rmdPath.filename())
+ % string_utils::utf8ToSystem(mdPath.filename())
+ % encoding);
+ args.push_back(cmd);
+
+ // options
+ core::system::ProcessOptions options;
+ core::system::ProcessResult result;
+ options.workingDir = rmdPath.parent();
+
+ // run knit
+ error = core::system::runProgram(
+ core::string_utils::utf8ToSystem(rProgramPath.absolutePath()),
+ args,
+ "",
+ options,
+ &result);
+ if (error)
+ {
+ *pErrorResponse = ErrorResponse(error.summary());
+ return false;
+ }
+ else if (result.exitStatus != EXIT_SUCCESS)
+ {
+ // if the markdown file doesn't exist then create one to
+ // play the error text back into
+ if (!mdPath.exists())
+ {
+ Error error = core::writeStringToFile(mdPath,
+ mdPath.stem() +
+ "\n=======================\n");
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // append the knitr error message to the file
+ std::ostringstream ostr;
+ ostr << std::endl
+ << "```" << std::endl
+ << extractKnitrError(result.stdErr) << std::endl
+ << "```" << std::endl;
+
+ Error error = core::appendToFile(mdPath, ostr.str());
+ if (error)
+ LOG_ERROR(error);
+
+ return true;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+std::string presentationCommandClickHandler(const std::string& name,
+ const std::string& params)
+{
+ using namespace boost::algorithm;
+ std::ostringstream ostr;
+ ostr << "onclick='";
+ ostr << "window.parent.dispatchPresentationCommand(";
+ json::Object cmdObj;
+ using namespace boost::algorithm;
+ cmdObj["name"] = name;
+ cmdObj["params"] = params;
+ json::write(cmdObj, ostr);
+ ostr << "); return false;'";
+ return ostr.str();
+}
+
+std::string fixupLink(const boost::cmatch& match)
+{
+ std::string href = http::util::urlDecode(match[1]);
+
+ if (boost::algorithm::starts_with(href, "#"))
+ {
+ // leave internal links alone
+ return match[0];
+ }
+ else if (href.find("://") != std::string::npos)
+ {
+ // open external links in a new window
+ return match[0] + " target=\"_blank\"";
+ }
+ else if (boost::algorithm::starts_with(href, "help-topic:") ||
+ boost::algorithm::starts_with(href, "help-doc:"))
+ {
+ // convert help commands to javascript calls
+ std::string onClick;
+ std::size_t colonLoc = href.find_first_of(':');
+ if (href.size() > colonLoc+2)
+ {
+ using namespace boost::algorithm;
+ std::string name = trim_copy(href.substr(0, colonLoc));
+ std::string params = trim_copy(href.substr(colonLoc+1));
+ onClick = presentationCommandClickHandler(name, params);
+ }
+
+ return match[0] + " " + onClick;
+ }
+ else if (boost::algorithm::starts_with(href, "tutorial:"))
+ {
+ // paths are relative to the parent dir of the presenentation dir
+ using namespace boost::algorithm;
+ std::size_t colonLoc = href.find_first_of(':');
+ std::string path = trim_copy(href.substr(colonLoc+1));
+ path = core::http::util::urlDecode(path);
+ if (boost::algorithm::starts_with(path, "~/"))
+ path = module_context::resolveAliasedPath(path).absolutePath();
+ FilePath filePath = presentation::state::directory()
+ .parent().complete(path);
+
+ Error error = core::system::realPath(filePath, &filePath);
+ if (error)
+ {
+ if (!core::isPathNotFoundError(error))
+ LOG_ERROR(error);
+ return match[0];
+ }
+
+ // bulid the call
+ std::string onClick;
+ if (href.size() > colonLoc+2)
+ {
+ std::string name = "tutorial";
+ std::string params = module_context::createAliasedPath(filePath);
+ onClick = presentationCommandClickHandler(name, params);
+ }
+
+ return match[0] + " " + onClick;
+ }
+ else
+ {
+ return match[0];
+ }
+}
+
+boost::iostreams::regex_filter linkFilter()
+{
+ return boost::iostreams::regex_filter(
+ boost::regex("<a href=\"([^\"]+)\""),
+ fixupLink);
+}
+
+std::string userSlidesCss(const SlideDeck& slideDeck)
+{
+ // first determine the path to the css file -- check for a css field
+ // first and if that doesn't exist form one from the basename of
+ // the presentation
+ std::string cssFile = slideDeck.css();
+ if (cssFile.empty())
+ cssFile = presentation::state::filePath().stem() + ".css";
+ FilePath cssPath = presentation::state::directory().complete(cssFile);
+
+ // read user css if it exists
+ std::string userSlidesCss;
+ if (cssPath.exists())
+ {
+ Error error = core::readStringFromFile(cssPath, &userSlidesCss);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // return it
+ return userSlidesCss;
+}
+
+
+bool readPresentation(SlideDeck* pSlideDeck,
+ std::string* pSlides,
+ std::string* pInitActions,
+ std::string* pSlideActions,
+ std::map<std::string,std::string>* pVars,
+ ErrorResponse* pErrorResponse)
+{
+ // look for slides and knit if we need to
+ FilePath rmdFile = presentation::state::filePath();
+ std::string ext = rmdFile.extensionLowerCase();
+ if (rmdFile.exists() && (ext != ".md"))
+ {
+ if (!performKnit(rmdFile, false, pErrorResponse))
+ return false;
+ }
+
+ // look for slides markdown
+ FilePath slidesFile = rmdFile.parent().childPath(rmdFile.stem() + ".md");
+ if (!slidesFile.exists())
+ {
+ *pErrorResponse = ErrorResponse(slidesFile.absolutePath() +
+ " not found");
+ return false;
+ }
+
+ // parse the slides
+ Error error = pSlideDeck->readSlides(slidesFile);
+ if (error)
+ {
+ LOG_ERROR(error);
+ *pErrorResponse = ErrorResponse(error.summary());
+ return false;
+ }
+
+ // render the slides
+ std::string slidesHead;
+ std::string revealConfig;
+ error = presentation::renderSlides(*pSlideDeck,
+ &slidesHead,
+ pSlides,
+ &revealConfig,
+ pInitActions,
+ pSlideActions);
+ if (error)
+ {
+ LOG_ERROR(error);
+ *pErrorResponse = ErrorResponse(error.summary());
+ return false;
+ }
+
+ // build template variables
+ std::map<std::string,std::string>& vars = *pVars;
+ vars["title"] = pSlideDeck->title();
+ vars["slides_head"] = slidesHead;
+ vars["slides"] = *pSlides;
+ vars["slides_css"] = resourceFiles().get("presentation/slides.css");
+ vars["user_slides_css"] = userSlidesCss(*pSlideDeck);
+ vars["r_highlight"] = resourceFiles().get("r_highlight.html");
+ vars["reveal_config"] = revealConfig;
+ vars["preamble"] = pSlideDeck->preamble();
+
+ return true;
+}
+
+bool renderPresentation(
+ const std::map<std::string,std::string>& vars,
+ const std::vector<boost::iostreams::regex_filter>& filters,
+ std::ostream& os,
+ ErrorResponse* pErrorResponse)
+{
+ std::string presentationTemplate =
+ resourceFiles().get("presentation/slides.html");
+ std::istringstream templateStream(presentationTemplate);
+
+ try
+ {
+ os.exceptions(std::istream::failbit | std::istream::badbit);
+ boost::iostreams::filtering_ostream filteredStream ;
+
+ // template filter
+ text::TemplateFilter templateFilter(vars);
+ filteredStream.push(templateFilter);
+
+ // custom filters
+ for (std::size_t i=0; i<filters.size(); i++)
+ filteredStream.push(filters[i]);
+
+ // target stream
+ filteredStream.push(os);
+
+ boost::iostreams::copy(templateStream, filteredStream, 128);
+ }
+ catch(const std::exception& e)
+ {
+ *pErrorResponse = ErrorResponse(e.what());
+ return false;
+ }
+
+ return true;
+}
+
+typedef boost::function<void(const FilePath&,
+ const std::string&,
+ std::map<std::string,std::string>*)> VarSource;
+
+void publishToRPubsVars(const FilePath&,
+ const std::string& slides,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ // webfonts w/ remote url
+ vars["google_webfonts"] = remoteWebFonts();
+
+ // mathjax w/ remote url
+ if (markdown::isMathJaxRequired(slides))
+ vars["mathjax"] = remoteMathjax();
+ else
+ vars["mathjax"] = "";
+}
+
+void saveAsStandaloneVars(const FilePath& targetFile,
+ const std::string& slides,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ // embedded web fonts
+ vars["google_webfonts"] = embeddedWebFonts();
+
+ // mathjax w/ remote url
+ if (markdown::isMathJaxRequired(slides))
+ vars["mathjax"] = copiedMathjax(targetFile);
+ else
+ vars["mathjax"] = "";
+}
+
+
+void viewInBrowserVars(const std::string& slides,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ vars["google_webfonts"] = localWebFonts();
+
+ // mathjax w/ local
+ if (markdown::isMathJaxRequired(slides))
+ vars["mathjax"] = localMathjax();
+ else
+ vars["mathjax"] = "";
+}
+
+void fontVars(const SlideDeck& slideDeck,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ // font imports
+ vars["user_font_imports"] = std::string();
+ if (slideDeck.slides().size() > 0)
+ {
+ std::ostringstream ostr;
+ std::vector<std::string> fontImports =
+ slideDeck.slides().at(0).fieldValues("font-import");
+
+ BOOST_FOREACH(const std::string& fontImport, fontImports)
+ {
+ ostr << "@import url('" << fontImport << "');" << std::endl;
+ }
+
+ vars["user_font_imports"] = ostr.str();
+ }
+
+ // fonts
+ if (!slideDeck.fontFamily().empty())
+ {
+ vars["reveal_font"] = slideDeck.fontFamily();
+ vars["reveal_heading_font"] = slideDeck.fontFamily();
+ }
+ else
+ {
+ vars["reveal_font"] = kDefaultRevealFont;
+ vars["reveal_heading_font"] = kDefaultRevealHeadingFont;
+ }
+}
+
+
+void localRevealVars(std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ vars["reveal_css"] = revealLink("revealjs/css/reveal.css");
+ vars["reveal_theme_css"] = revealLink("revealjs/css/theme/simple.css");
+ vars["reveal_head_js"] = revealLink("revealjs/lib/js/head.min.js");
+ vars["reveal_js"] = revealLink("revealjs/js/reveal.js");
+}
+
+
+void revealSizeVars(const SlideDeck& slideDeck,
+ bool zoom,
+ bool allowAutosize,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ bool autosize = allowAutosize && slideDeck.autosize();
+ vars["reveal_autosize"] = autosize ? "true" : "false";
+ if (autosize)
+ {
+ std::string zoomStr = zoom ? "true" : "false";
+ vars["reveal_width"] = "revealDetectWidth(" + zoomStr + ")";
+ vars["reveal_height"] = "revealDetectHeight(" + zoomStr + ")";
+ }
+ else
+ {
+ vars["reveal_width"] = safe_convert::numberToString(slideDeck.width());
+ vars["reveal_height"] = safe_convert::numberToString(slideDeck.height());
+ }
+}
+
+void externalBrowserVars(const SlideDeck& slideDeck,
+ std::map<std::string,std::string>* pVars)
+{
+ std::map<std::string,std::string>& vars = *pVars;
+
+ vars["slide_commands"] = "";
+ vars["slides_js"] = "";
+ vars["init_commands"] = "";
+
+ // width and height
+ revealSizeVars(slideDeck, false, false, pVars);
+
+ // use transitions for standalone
+ vars["reveal_transition"] = slideDeck.transition();
+ vars["reveal_transition_speed"] = slideDeck.transitionSpeed();
+
+ // rtl
+ vars["reveal_rtl"] = slideDeck.rtl();
+}
+
+bool createStandalonePresentation(const FilePath& targetFile,
+ const VarSource& varSource,
+ ErrorResponse* pErrorResponse)
+{
+ // read presentation
+ presentation::SlideDeck slideDeck;
+ std::string slides, initCommands, slideCommands;
+ std::map<std::string,std::string> vars;
+ if (!readPresentation(&slideDeck,
+ &slides,
+ &initCommands,
+ &slideCommands,
+ &vars,
+ pErrorResponse))
+ {
+ return false;
+ }
+
+ // embedded versions of reveal assets
+ vars["reveal_print_css"] = revealEmbed("revealjs/css/print/pdf.css",
+ kMediaPrint);
+ vars["reveal_css"] = revealEmbed("revealjs/css/reveal.min.css");
+ vars["reveal_theme_css"] = revealEmbed("revealjs/css/theme/simple.css");
+ vars["reveal_head_js"] = revealEmbed("revealjs/lib/js/head.min.js");
+ vars["reveal_js"] = revealEmbed("revealjs/js/reveal.min.js");
+
+ // font vars
+ fontVars(slideDeck, &vars);
+
+ // call var source hook function
+ varSource(targetFile, slides, &vars);
+
+ // no IDE interaction
+ externalBrowserVars(slideDeck, &vars);
+
+ // target file stream
+ boost::shared_ptr<std::ostream> pOfs;
+ Error error = targetFile.open_w(&pOfs);
+ if (error)
+ {
+ LOG_ERROR(error);
+ *pErrorResponse = ErrorResponse(error.summary());
+ return false;
+ }
+
+ // create image filter
+ FilePath dirPath = presentation::state::directory();
+ std::vector<boost::iostreams::regex_filter> filters;
+ filters.push_back(html_utils::Base64ImageFilter(dirPath));
+
+ // render presentation
+ return renderPresentation(vars, filters, *pOfs, pErrorResponse);
+}
+
+
+void loadSlideDeckDependencies(const SlideDeck& slideDeck)
+{
+ // see if there is a depends field
+ std::string dependsField = slideDeck.depends();
+ std::vector<std::string> depends;
+ boost::algorithm::split(depends,
+ dependsField,
+ boost::algorithm::is_any_of(","));
+
+ // load any dependencies
+ BOOST_FOREACH(std::string pkg, depends)
+ {
+ boost::algorithm::trim(pkg);
+
+ if (module_context::isPackageInstalled(pkg))
+ {
+ Error error = r::exec::RFunction(".rs.loadPackage", pkg, "").call();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+}
+
+void handlePresentationRootRequest(const std::string& path,
+ http::Response* pResponse)
+{
+ // read presentation
+ presentation::SlideDeck slideDeck;
+ std::string slides, initCommands, slideCommands;
+ std::map<std::string,std::string> vars;
+ ErrorResponse errorResponse;
+ if (!readPresentation(&slideDeck,
+ &slides,
+ &initCommands,
+ &slideCommands,
+ &vars,
+ &errorResponse))
+ {
+ pResponse->setError(errorResponse.statusCode, errorResponse.message);
+ return;
+ }
+
+ // load any dependencies
+ loadSlideDeckDependencies(slideDeck);
+
+ // notify slide deck changed
+ log().onSlideDeckChanged(slideDeck);
+ onSlideDeckChangedOverlay(slideDeck);
+
+ // set preload to none for media
+ vars["slides"] = boost::algorithm::replace_all_copy(
+ vars["slides"],
+ "controls preload=\"auto\"",
+ "controls preload=\"none\"");
+
+ // linked versions of reveal assets
+ localRevealVars(&vars);
+
+ // font vars
+ fontVars(slideDeck, &vars);
+
+ // no print css for qtwebkit
+ vars["reveal_print_css"] = "";
+
+ // webfonts local
+ vars["google_webfonts"] = localWebFonts();
+
+ // mathjax local
+ if (markdown::isMathJaxRequired(slides))
+ vars["mathjax"] = localMathjax();
+ else
+ vars["mathjax"] = "";
+
+ // javascript supporting IDE interaction
+ vars["slide_commands"] = slideCommands;
+ vars["slides_js"] = resourceFiles().get("presentation/slides.js");
+ vars["init_commands"] = initCommands;
+
+ // width and height
+ bool zoom = path == "zoom";
+ revealSizeVars(slideDeck, zoom, true, &vars);
+
+ // no transition in desktop mode (qtwebkit can't keep up)
+ bool isDesktop = options().programMode() == kSessionProgramModeDesktop;
+ vars["reveal_transition"] = isDesktop? "none" : slideDeck.transition();
+ vars["reveal_transition_speed"] = isDesktop ? "default" :
+ slideDeck.transitionSpeed();
+
+ // rtl
+ vars["reveal_rtl"] = slideDeck.rtl();
+
+ // render to output stream
+ std::stringstream previewOutputStream;
+ std::vector<boost::iostreams::regex_filter> filters;
+ filters.push_back(linkFilter());
+ if (renderPresentation(vars, filters, previewOutputStream, &errorResponse))
+ {
+ // set response
+ pResponse->setNoCacheHeaders();
+ pResponse->setContentType("text/html");
+ pResponse->setBody(previewOutputStream);
+
+ // also save a view in browser version if that path already exists
+ // (allows the user to do a simple browser refresh to see changes)
+ FilePath viewInBrowserPath = presentation::state::viewInBrowserPath();
+ if (viewInBrowserPath.exists())
+ {
+ ErrorResponse errorResponse;
+ if (!savePresentationAsStandalone(viewInBrowserPath, &errorResponse))
+ LOG_ERROR_MESSAGE(errorResponse.message);
+ }
+ }
+ else
+ {
+ pResponse->setError(errorResponse.statusCode, errorResponse.message);
+ }
+
+ if (!zoom)
+ {
+ ClientEvent event(client_events::kPresentationPaneRequestCompleted);
+ module_context::enqueClientEvent(event);
+ }
+}
+
+
+
+
+void handlePresentationHelpMarkdownRequest(const FilePath& filePath,
+ const std::string& jsCallbacks,
+ core::http::Response* pResponse)
+{
+ // indirection on the actual md file (related to processing R markdown)
+ FilePath mdFilePath;
+
+ // knit if required
+ if (filePath.mimeContentType() == "text/x-r-markdown")
+ {
+ // actual file path will be the md file
+ mdFilePath = filePath.parent().complete(filePath.stem() + ".md");
+
+ // do the knit if we need to
+ ErrorResponse errorResponse;
+ if (!performKnit(filePath, false, &errorResponse))
+ {
+ pResponse->setError(errorResponse.statusCode, errorResponse.message);
+ return;
+ }
+ }
+ else
+ {
+ mdFilePath = filePath;
+ }
+
+ // read in the file (process markdown)
+ std::string helpDoc;
+ Error error = markdown::markdownToHTML(mdFilePath,
+ markdown::Extensions(),
+ markdown::HTMLOptions(),
+ &helpDoc);
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // process the template
+ std::map<std::string,std::string> vars;
+ vars["title"] = html_utils::defaultTitle(helpDoc);
+ vars["styles"] = resourceFiles().get("presentation/helpdoc.css");
+ vars["r_highlight"] = resourceFiles().get("r_highlight.html");
+ if (markdown::isMathJaxRequired(helpDoc))
+ vars["mathjax"] = localMathjax();
+ else
+ vars["mathjax"] = "";
+ vars["content"] = helpDoc;
+ vars["js_callbacks"] = jsCallbacks;
+ pResponse->setNoCacheHeaders();
+ pResponse->setContentType("text/html");
+ pResponse->setBody(resourceFiles().get("presentation/helpdoc.html"),
+ text::TemplateFilter(vars));
+}
+
+void handleRangeRequest(const FilePath& targetFile,
+ const http::Request& request,
+ http::Response* pResponse)
+{
+ // cache the last file
+ struct RangeFileCache
+ {
+ FileInfo file;
+ std::string contentType;
+ std::string contents;
+
+ void clear()
+ {
+ file = FileInfo();
+ contentType.clear();
+ contents.clear();
+ }
+ };
+ static RangeFileCache s_cache;
+
+ // see if we need to do a fresh read
+ if (targetFile.absolutePath() != s_cache.file.absolutePath() ||
+ targetFile.lastWriteTime() != s_cache.file.lastWriteTime())
+ {
+ // clear the cache
+ s_cache.clear();
+
+ // read the file in from disk
+ Error error = core::readStringFromFile(targetFile, &(s_cache.contents));
+ if (error)
+ {
+ pResponse->setError(error);
+ return;
+ }
+
+ // update the cache
+ s_cache.file = FileInfo(targetFile);
+ s_cache.contentType = targetFile.mimeContentType();
+ }
+
+ // always serve from the cache
+ pResponse->setRangeableFile(s_cache.contents,
+ s_cache.contentType,
+ request);
+
+
+}
+
+void handlePresentationViewInBrowserRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // read presentation
+ presentation::SlideDeck slideDeck;
+ std::string slides, initCommands, slideCommands;
+ std::map<std::string,std::string> vars;
+ ErrorResponse errorResponse;
+ if (!readPresentation(&slideDeck,
+ &slides,
+ &initCommands,
+ &slideCommands,
+ &vars,
+ &errorResponse))
+ {
+ pResponse->setError(errorResponse.statusCode, errorResponse.message);
+ return;
+ }
+
+ // linked versions of reveal assets
+ localRevealVars(&vars);
+
+ // font vars
+ fontVars(slideDeck, &vars);
+
+ // link to reveal print css
+ vars["reveal_print_css"] = revealLink("revealjs/css/print/pdf.css",
+ kMediaPrint);
+
+ // webfonts local
+ viewInBrowserVars(slides, &vars);
+
+ // external browser vars
+ externalBrowserVars(slideDeck, &vars);
+
+ // render to output stream
+ std::stringstream previewOutputStream;
+ std::vector<boost::iostreams::regex_filter> filters;
+ filters.push_back(linkFilter());
+ if (renderPresentation(vars, filters, previewOutputStream, &errorResponse))
+ {
+ pResponse->setNoCacheHeaders();
+ pResponse->setContentType("text/html");
+ pResponse->setBody(previewOutputStream);
+ }
+ else
+ {
+ pResponse->setError(errorResponse.statusCode, errorResponse.message);
+ }
+}
+
+void handlePresentationFileRequest(const http::Request& request,
+ const std::string& dir,
+ http::Response* pResponse)
+{
+ std::string path = http::util::pathAfterPrefix(request,
+ "/presentation/" + dir + "/");
+ FilePath resPath = options().rResourcesPath().complete("presentation");
+ FilePath filePath = resPath.complete(dir + "/" + path);
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableBody(filePath, request);
+}
+
+} // anonymous namespace
+
+bool clearKnitrCache(ErrorResponse* pErrorResponse)
+{
+ FilePath rmdFile = presentation::state::filePath();
+ std::string ext = rmdFile.extensionLowerCase();
+ if (rmdFile.exists() && (ext != ".md"))
+ return performKnit(rmdFile, true, pErrorResponse);
+ else
+ return true;
+}
+
+
+void handlePresentationPaneRequest(const http::Request& request,
+ http::Response* pResponse)
+{
+ // return not found if presentation isn't active
+ if (!presentation::state::isActive())
+ {
+ pResponse->setError(http::status::NotFound, request.uri() + " not found");
+ return;
+ }
+
+ // get the requested path
+ std::string path = http::util::pathAfterPrefix(request, "/presentation/");
+
+ // special handling for the root
+ if (path.empty() || (path == "zoom"))
+ {
+ handlePresentationRootRequest(path, pResponse);
+ }
+
+ // special handling for view in browser
+ else if (boost::algorithm::starts_with(path, "view"))
+ {
+ handlePresentationViewInBrowserRequest(request, pResponse);
+ }
+
+ // special handling for reveal.js assets
+ else if (boost::algorithm::starts_with(path, "revealjs/"))
+ {
+ handlePresentationFileRequest(request, "revealjs", pResponse);
+ }
+
+ // special handling for images
+ else if (boost::algorithm::starts_with(path, "slides-images/"))
+ {
+ handlePresentationFileRequest(request, "slides-images", pResponse);
+ }
+
+ // special handling for mathjax assets
+ else if (boost::algorithm::starts_with(path, "mathjax/"))
+ {
+ FilePath filePath =
+ session::options().mathjaxPath().parent().childPath(path);
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableBody(filePath, request);
+ }
+
+
+ // serve the file back
+ else
+ {
+ FilePath targetFile = presentation::state::directory().childPath(path);
+ if (!request.headerValue("Range").empty())
+ {
+ handleRangeRequest(targetFile, request, pResponse);
+ }
+ else
+ {
+ // indicate that we accept byte range requests
+ pResponse->addHeader("Accept-Ranges", "bytes");
+
+ // return the file
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableBody(targetFile, request);
+ }
+ }
+}
+
+void handlePresentationHelpRequest(const core::http::Request& request,
+ const std::string& jsCallbacks,
+ core::http::Response* pResponse)
+{
+ // we save the most recent /help/presentation/&file=parameter so we
+ // can resolve relative file references against it. we do this
+ // separately from presentation::state::directory so that the help
+ // urls can be available within the help pane (and history)
+ // independent of the duration of the presentation tab
+ static FilePath s_presentationHelpDir;
+
+ // check if this is a root request
+ std::string file = request.queryParamValue("file");
+ if (!file.empty())
+ {
+ // ensure file exists
+ FilePath filePath = module_context::resolveAliasedPath(file);
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::NotFound, request.uri());
+ return;
+ }
+
+ // save the help dir
+ s_presentationHelpDir = filePath.parent();
+
+ // check for markdown
+ if (filePath.mimeContentType() == "text/x-markdown" ||
+ filePath.mimeContentType() == "text/x-r-markdown")
+ {
+ handlePresentationHelpMarkdownRequest(filePath,
+ jsCallbacks,
+ pResponse);
+ }
+
+ // just a stock file
+ else
+ {
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableBody(filePath, request);
+ }
+ }
+
+ // it's a relative file reference
+ else
+ {
+ // make sure the directory exists
+ if (!s_presentationHelpDir.exists())
+ {
+ pResponse->setError(http::status::NotFound,
+ "Directory not found: " +
+ s_presentationHelpDir.absolutePath());
+ return;
+ }
+
+ // resolve the file reference
+ std::string path = http::util::pathAfterPrefix(request,
+ "/help/presentation/");
+
+ // serve the file back
+ pResponse->setCacheWithRevalidationHeaders();
+ pResponse->setCacheableBody(s_presentationHelpDir.complete(path),
+ request);
+ }
+}
+
+bool savePresentationAsStandalone(const core::FilePath& filePath,
+ ErrorResponse* pErrorResponse)
+{
+ return createStandalonePresentation(filePath,
+ saveAsStandaloneVars,
+ pErrorResponse);
+}
+
+bool savePresentationAsRpubsSource(const core::FilePath& filePath,
+ ErrorResponse* pErrorResponse)
+{
+ return createStandalonePresentation(filePath,
+ publishToRPubsVars,
+ pErrorResponse);
+}
+
+
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/presentation/SlideRequestHandler.hpp b/src/cpp/session/modules/presentation/SlideRequestHandler.hpp
new file mode 100644
index 0000000..b6f8fe5
--- /dev/null
+++ b/src/cpp/session/modules/presentation/SlideRequestHandler.hpp
@@ -0,0 +1,67 @@
+/*
+ * SlideRequestHandler.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_SLIDE_REQUEST_HANDLER_HPP
+#define SESSION_PRESENTATION_SLIDE_REQUEST_HANDLER_HPP
+
+#include <string>
+#include <core/http/Response.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+ namespace http {
+ class Request;
+ }
+}
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+struct ErrorResponse
+{
+ explicit ErrorResponse(const std::string& message = std::string(),
+ core::http::status::Code statusCode
+ = core::http::status::InternalServerError)
+ : message(message), statusCode(statusCode)
+ {
+ }
+
+ std::string message;
+ core::http::status::Code statusCode;
+};
+
+bool clearKnitrCache(ErrorResponse* pErrorResponse);
+
+void handlePresentationPaneRequest(const core::http::Request& request,
+ core::http::Response* pResponse);
+
+
+void handlePresentationHelpRequest(const core::http::Request& request,
+ const std::string& jsCallbacks,
+ core::http::Response* pResponse);
+
+bool savePresentationAsStandalone(const core::FilePath& filePath,
+ ErrorResponse* pErrorResponse);
+
+bool savePresentationAsRpubsSource(const core::FilePath& filePath,
+ ErrorResponse* pErrorResponse);
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_SLIDE_REQUEST_HANDLER_HPP
diff --git a/src/cpp/session/modules/shiny/SessionShiny.cpp b/src/cpp/session/modules/shiny/SessionShiny.cpp
new file mode 100644
index 0000000..82075dc
--- /dev/null
+++ b/src/cpp/session/modules/shiny/SessionShiny.cpp
@@ -0,0 +1,140 @@
+/*
+ * SessionShiny.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionShiny.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <core/Error.hpp>
+#include <core/Exec.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionOptions.hpp>
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace shiny {
+
+namespace {
+
+void onPackageLoaded(const std::string& pkgname)
+{
+ // we need an up to date version of shiny when running in server mode
+ // to get the websocket protocol/path and port randomizing changes
+ if (session::options().programMode() == kSessionProgramModeServer)
+ {
+ if (pkgname == "shiny")
+ {
+ if (!module_context::isPackageVersionInstalled("shiny", "0.8"))
+ {
+ module_context::consoleWriteError("\nWARNING: To run Shiny "
+ "applications with RStudio you need to install the "
+ "latest version of the Shiny package from CRAN (version 0.8 "
+ "or higher is required).\n\n");
+ }
+ }
+ }
+}
+
+
+
+bool isShinyAppDir(const FilePath& filePath)
+{
+ bool hasServer = filePath.childPath("server.R").exists() ||
+ filePath.childPath("server.r").exists();
+ if (hasServer)
+ {
+ bool hasUI = filePath.childPath("ui.R").exists() ||
+ filePath.childPath("ui.r").exists() ||
+ filePath.childPath("www").exists();
+
+ return hasUI;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+std::string onDetectShinySourceType(
+ boost::shared_ptr<source_database::SourceDocument> pDoc)
+{
+ const char * const kShinyType = "shiny";
+
+ if (!pDoc->path().empty())
+ {
+ FilePath filePath = module_context::resolveAliasedPath(pDoc->path());
+ std::string filename = filePath.filename();
+
+ if (boost::algorithm::iequals(filename, "ui.r") &&
+ boost::algorithm::icontains(pDoc->contents(), "shinyUI"))
+ {
+ return kShinyType;
+ }
+ else if (boost::algorithm::iequals(filename, "server.r") &&
+ boost::algorithm::icontains(pDoc->contents(), "shinyServer"))
+ {
+ return kShinyType;
+ }
+ else if (filePath.extensionLowerCase() == ".r" &&
+ isShinyAppDir(filePath.parent()))
+ {
+ return kShinyType;
+ }
+ }
+
+ return std::string();
+}
+
+Error getShinyCapabilities(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object capsJson;
+ capsJson["installed"] = module_context::isPackageInstalled("shiny");
+ pResponse->setResult(capsJson);
+
+ return Success();
+}
+
+} // anonymous namespace
+
+
+
+Error initialize()
+{
+ using namespace module_context;
+ events().onPackageLoaded.connect(onPackageLoaded);
+
+ // run app features require shiny v0.8 (the version where the
+ // shiny.launch.browser option can be a function)
+ if (module_context::isPackageVersionInstalled("shiny", "0.8"))
+ events().onDetectSourceExtendedType.connect(onDetectShinySourceType);
+
+ ExecBlock initBlock;
+ initBlock.addFunctions()
+ (boost::bind(registerRpcMethod, "get_shiny_capabilities", getShinyCapabilities));
+
+ return initBlock.execute();
+}
+
+
+} // namespace crypto
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/shiny/SessionShiny.hpp b/src/cpp/session/modules/shiny/SessionShiny.hpp
new file mode 100644
index 0000000..56680ba
--- /dev/null
+++ b/src/cpp/session/modules/shiny/SessionShiny.hpp
@@ -0,0 +1,35 @@
+/*
+ * SessionShiny.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_SHINY_HPP
+#define SESSION_SHINY_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace shiny {
+
+core::Error initialize();
+
+} // namespace shiny
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_SHINY_HPP
diff --git a/src/cpp/session/modules/tex/SessionCompilePdf.cpp b/src/cpp/session/modules/tex/SessionCompilePdf.cpp
new file mode 100644
index 0000000..00dbec7
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionCompilePdf.cpp
@@ -0,0 +1,883 @@
+/*
+ * SessionCompilePdf.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionCompilePdf.hpp"
+
+#include <set>
+
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+#include <core/Settings.hpp>
+#include <core/Algorithm.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/ShellUtils.hpp>
+
+#include <core/tex/TexLogParser.hpp>
+#include <core/tex/TexMagicComment.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionPdfLatex.hpp"
+#include "SessionRnwWeave.hpp"
+#include "SessionRnwConcordance.hpp"
+#include "SessionSynctex.hpp"
+#include "SessionCompilePdfSupervisor.hpp"
+#include "SessionViewPdf.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace compile_pdf {
+
+namespace {
+
+// track compile pdf state so we can send it to the client on client init
+class CompilePdfState : boost::noncopyable
+{
+public:
+ CompilePdfState()
+ : tabVisible_(false), running_(false)
+ {
+ }
+
+ virtual ~CompilePdfState() {}
+ // COPYING: noncoypable
+
+ Error readFromJson(const json::Object& asJson)
+ {
+ clear();
+ return json::readObject(asJson,
+ "tab_visible", &tabVisible_,
+ "running", &running_,
+ "target_file", &targetFile_,
+ "output", &output_,
+ "errors", &errors_);
+ }
+
+
+ void onStarted(const std::string& targetFile)
+ {
+ clear();
+ running_ = true;
+ tabVisible_ = true;
+ targetFile_ = targetFile;
+ }
+
+ void addOutput(const std::string& output)
+ {
+ output_ += output;
+ }
+
+ void setErrors(const json::Array& errors)
+ {
+ errors_ = errors;
+ }
+
+ void onCompleted()
+ {
+ running_ = false;
+ }
+
+ void clear()
+ {
+ tabVisible_ = false;
+ running_ = false;
+ targetFile_.clear();
+ output_.clear();
+ errors_.clear();
+ }
+
+ json::Object asJson() const
+ {
+ json::Object obj;
+ obj["tab_visible"] = tabVisible_;
+ obj["running"] = running_;
+ obj["target_file"] = targetFile_;
+ obj["output"] = output_;
+ obj["errors"] = errors_;
+ return obj;
+ }
+
+private:
+ bool tabVisible_;
+ bool running_;
+ std::string targetFile_;
+ std::string output_;
+ json::Array errors_;
+};
+
+CompilePdfState s_compilePdfState;
+
+void onSuspend(Settings* pSettings)
+{
+ std::ostringstream os;
+ json::write(s_compilePdfState.asJson(), os);
+ pSettings->set("compile_pdf_state", os.str());
+}
+
+
+void onResume(const Settings& settings)
+{
+ std::string state = settings.get("compile_pdf_state");
+ if (!state.empty())
+ {
+ json::Value stateJson;
+ if (!json::parse(state, &stateJson))
+ {
+ LOG_WARNING_MESSAGE("invalid compile pdf state json");
+ return;
+ }
+
+ Error error = s_compilePdfState.readFromJson(stateJson.get_obj());
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+FilePath ancillaryFilePath(const FilePath& texFilePath, const std::string& ext)
+{
+ return texFilePath.parent().childPath(texFilePath.stem() + ext);
+}
+
+bool isSynctexAvailable(const FilePath& texFilePath)
+{
+ return ancillaryFilePath(texFilePath, ".synctex.gz").exists() ||
+ ancillaryFilePath(texFilePath, ".synctex").exists();
+}
+
+void enqueOutputEvent(const std::string& output)
+{
+ s_compilePdfState.addOutput(output);
+
+ ClientEvent event(client_events::kCompilePdfOutputEvent, output);
+ module_context::enqueClientEvent(event);
+}
+
+void enqueStartedEvent(const FilePath& texFilePath)
+{
+ std::string targetFile =
+ module_context::createAliasedPath(texFilePath);
+
+ s_compilePdfState.onStarted(targetFile);
+
+ json::Object dataJson;
+ dataJson["target_file"] = targetFile;
+ FilePath pdfPath = ancillaryFilePath(texFilePath, ".pdf");
+ dataJson["pdf_path"] = module_context::createAliasedPath(pdfPath);
+
+ ClientEvent event(client_events::kCompilePdfStartedEvent,
+ dataJson);
+
+ module_context::enqueClientEvent(event);
+}
+
+void enqueCompletedEvent(bool succeeded,
+ const json::Object& sourceLocation,
+ const FilePath& texFilePath)
+{
+ s_compilePdfState.onCompleted();
+
+ json::Object dataJson;
+ dataJson["succeeded"] = succeeded;
+ dataJson["target_file"] =
+ module_context::createAliasedPath(texFilePath);
+
+ FilePath pdfPath = ancillaryFilePath(texFilePath, ".pdf");
+ dataJson["pdf_path"] = module_context::createAliasedPath(pdfPath);
+ dataJson["view_pdf_url"] = tex::view_pdf::createViewPdfUrl(pdfPath);
+ bool synctexAvailable = isSynctexAvailable(texFilePath);
+ dataJson["synctex_available"] = synctexAvailable;
+ if (synctexAvailable)
+ {
+ json::Value pdfLocation;
+ Error error = modules::tex::synctex::forwardSearch(texFilePath,
+ sourceLocation,
+ &pdfLocation);
+ if (error)
+ LOG_ERROR(error);
+ dataJson["pdf_location"] = pdfLocation;
+ }
+
+ ClientEvent event(client_events::kCompilePdfCompletedEvent,
+ dataJson);
+ module_context::enqueClientEvent(event);
+}
+
+void enqueCompletedWithFailureEvent(const FilePath& texFilePath,
+ json::Object& sourceLocation)
+{
+ enqueCompletedEvent(false, sourceLocation, texFilePath);
+}
+
+void enqueCompletedWithSuccessEvent(const FilePath& texFilePath,
+ const json::Object& sourceLocation)
+{
+ enqueCompletedEvent(true, sourceLocation, texFilePath);
+}
+
+void enqueErrorsEvent(const json::Array& logEntriesJson)
+{
+ s_compilePdfState.setErrors(logEntriesJson);
+
+ ClientEvent event(client_events::kCompilePdfErrorsEvent, logEntriesJson);
+ module_context::enqueClientEvent(event);
+}
+
+// NOTE: sync changes with SessionBuildErrors.cpp compileErrorJson
+json::Object logEntryJson(const core::tex::LogEntry& logEntry)
+{
+ json::Object obj;
+ obj["type"] = static_cast<int>(logEntry.type());
+ obj["path"] = module_context::createAliasedPath(logEntry.filePath());
+ obj["line"] = logEntry.line();
+ obj["column"] = 1;
+ obj["message"] = logEntry.message();
+ obj["log_path"] = module_context::createAliasedPath(logEntry.logFilePath());
+ obj["log_line"] = logEntry.logLine();
+ obj["show_error_list"] = true;
+ return obj;
+}
+
+void showLogEntries(const core::tex::LogEntries& logEntries,
+ const rnw_concordance::Concordances& rnwConcordances =
+ rnw_concordance::Concordances())
+{
+ json::Array logEntriesJson;
+ BOOST_FOREACH(const core::tex::LogEntry& logEntry, logEntries)
+ {
+ using namespace tex::rnw_concordance;
+ core::tex::LogEntry rnwEntry = rnwConcordances.fixup(logEntry);
+ logEntriesJson.push_back(logEntryJson(rnwEntry));
+ }
+
+ enqueErrorsEvent(logEntriesJson);
+}
+
+void writeLogEntriesOutput(const core::tex::LogEntries& logEntries)
+{
+ if (logEntries.empty())
+ return;
+
+ std::string output = "\n";
+ BOOST_FOREACH(const core::tex::LogEntry& logEntry, logEntries)
+ {
+ switch(logEntry.type())
+ {
+ case core::tex::LogEntry::Error:
+ output += "Error: ";
+ break;
+ case core::tex::LogEntry::Warning:
+ output += "Warning: ";
+ break;
+ case core::tex::LogEntry::Box:
+ output += "Bad Box: ";
+ break;
+ }
+
+ output += logEntry.filePath().filename();
+ int line = logEntry.line();
+ if (line >= 0)
+ output += ":" + safe_convert::numberToString(line);
+
+ output += ": " + logEntry.message() + "\n";
+ }
+ output += "\n";
+
+ enqueOutputEvent(output);
+
+}
+
+
+bool includeLogEntry(const core::tex::LogEntry& logEntry)
+{
+ return true;
+}
+
+// filter out log entries which we view as superflous or distracting
+void filterLatexLog(const core::tex::LogEntries& logEntries,
+ core::tex::LogEntries* pFilteredLogEntries)
+{
+ core::algorithm::copy_if(logEntries.begin(),
+ logEntries.end(),
+ std::back_inserter(*pFilteredLogEntries),
+ includeLogEntry);
+}
+
+bool isLogEntryFromTargetFile(const core::tex::LogEntry& logEntry,
+ const FilePath& texPath)
+{
+ return logEntry.filePath() == texPath;
+}
+
+void getLogEntries(const FilePath& texPath,
+ core::tex::LogEntries* pLogEntries)
+{
+ // latex log file
+ FilePath logPath = ancillaryFilePath(texPath, ".log");
+ if (logPath.exists())
+ {
+ core::tex::LogEntries logEntries;
+ Error error = core::tex::parseLatexLog(logPath, &logEntries);
+ if (error)
+ LOG_ERROR(error);
+
+ filterLatexLog(logEntries, pLogEntries);
+
+ // re-arrange so that issues in the target file always end up at the top
+ // of the error display
+ std::partition(pLogEntries->begin(),
+ pLogEntries->end(),
+ boost::bind(isLogEntryFromTargetFile, _1, texPath));
+ }
+
+ // bibtex log file
+ core::tex::LogEntries bibtexLogEntries;
+ logPath = ancillaryFilePath(texPath, ".blg");
+ if (logPath.exists())
+ {
+ Error error = core::tex::parseBibtexLog(logPath, &bibtexLogEntries);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // concatenate them together
+ std::copy(bibtexLogEntries.begin(),
+ bibtexLogEntries.end(),
+ std::back_inserter(*pLogEntries));
+}
+
+void removeExistingAncillary(const FilePath& texFilePath,
+ const std::string& extension)
+{
+ Error error = ancillaryFilePath(texFilePath, extension).removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+}
+
+void removeExistingLatexAncillaryFiles(const FilePath& texFilePath)
+{
+ removeExistingAncillary(texFilePath, ".log");
+ removeExistingAncillary(texFilePath, ".blg");
+ removeExistingAncillary(texFilePath, ".synctex");
+ removeExistingAncillary(texFilePath, ".synctex.gz");
+ }
+
+std::string buildIssuesMessage(const core::tex::LogEntries& logEntries)
+{
+ if (logEntries.empty())
+ return std::string();
+
+ // count error types
+ int errors = 0, warnings = 0, badBoxes = 0;
+ BOOST_FOREACH(const core::tex::LogEntry& logEntry, logEntries)
+ {
+ if (logEntry.type() == core::tex::LogEntry::Error)
+ errors++;
+ else if (logEntry.type() == core::tex::LogEntry::Warning)
+ warnings++;
+ else if (logEntry.type() == core::tex::LogEntry::Box)
+ badBoxes++;
+ }
+
+ std::string issues;
+ boost::format fmt("%1% %2%");
+ if (errors > 0)
+ {
+ issues += boost::str(fmt % errors % "error");
+ if (errors > 1)
+ issues += "s";
+ }
+ if (warnings > 0)
+ {
+ if (!issues.empty())
+ issues += ", ";
+ issues += boost::str(fmt % warnings % "warning");
+ if (warnings > 1)
+ issues += "s";
+ }
+ if (badBoxes > 0)
+ {
+ if (!issues.empty())
+ issues += ", ";
+ issues += boost::str(fmt % badBoxes % "bad");
+ if (badBoxes > 1)
+ issues += "boxes";
+ else
+ issues += "box";
+ }
+
+ if (!issues.empty())
+ return "Issues: " + issues;
+ else
+ return std::string();
+}
+
+class AuxillaryFileCleanupContext : boost::noncopyable
+{
+public:
+ AuxillaryFileCleanupContext()
+ : cleanLog_(true)
+ {
+ }
+
+ virtual ~AuxillaryFileCleanupContext()
+ {
+ try
+ {
+ cleanup();
+ }
+ catch(...)
+ {
+ }
+ }
+
+ void init(const FilePath& targetFilePath)
+ {
+ basePath_ = targetFilePath.parent().childPath(
+ targetFilePath.stem()).absolutePath();
+ }
+
+ void preserveLog()
+ {
+ cleanLog_ = false;
+ }
+
+ void preserveLogReferencedFiles(
+ const core::tex::LogEntries& logEntries)
+ {
+ BOOST_FOREACH(const core::tex::LogEntry& logEntry, logEntries)
+ {
+ logRefFiles_.insert(logEntry.filePath());
+ }
+ }
+
+ void cleanup()
+ {
+ if (!basePath_.empty())
+ {
+ // remove known auxillary files
+ remove(".out");
+ remove(".aux");
+
+ // only clean bbl if .bib exists
+ if (exists(".bib"))
+ remove(".bbl");
+
+ // clean anciallary logs if requested (never clean latex log)
+ if (cleanLog_)
+ {
+ remove(".blg");
+ }
+
+ // reset base path so we only do this once
+ basePath_.clear();
+ }
+ }
+
+private:
+ bool exists(const std::string& extension)
+ {
+ return FilePath(basePath_ + extension).exists();
+ }
+
+ // remove the specified file (but don't if it's referenced
+ // from the log)
+ void remove(const std::string& extension)
+ {
+ FilePath filePath(basePath_ + extension);
+ if (logRefFiles_.find(filePath) == logRefFiles_.end())
+ {
+ Error error = filePath.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+private:
+ std::string basePath_;
+ bool cleanLog_;
+ std::set<FilePath> logRefFiles_;
+};
+
+// implement pdf compilation within a class so we can maintain state
+// accross the various async callbacks the compile is composed of
+class AsyncPdfCompiler : boost::noncopyable,
+ public boost::enable_shared_from_this<AsyncPdfCompiler>
+{
+public:
+ static void start(const FilePath& targetFilePath,
+ const std::string& encoding,
+ const json::Object& sourceLocation,
+ const boost::function<void()>& onCompleted)
+ {
+ boost::shared_ptr<AsyncPdfCompiler> pCompiler(
+ new AsyncPdfCompiler(targetFilePath,
+ encoding,
+ sourceLocation,
+ onCompleted));
+
+ pCompiler->start();
+ }
+
+ virtual ~AsyncPdfCompiler() {}
+
+private:
+ AsyncPdfCompiler(const FilePath& targetFilePath,
+ const std::string& encoding,
+ const json::Object& sourceLocation,
+ const boost::function<void()>& onCompleted)
+ : targetFilePath_(targetFilePath),
+ encoding_(encoding),
+ sourceLocation_(sourceLocation),
+ onCompleted_(onCompleted)
+ {
+ if (targetFilePath_.exists())
+ {
+ Error error = core::system::realPath(targetFilePath_, &targetFilePath_);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+
+ void start()
+ {
+ // enque started event
+ enqueStartedEvent(targetFilePath_);
+
+ // terminate if the file doesn't exist
+ if (!targetFilePath_.exists())
+ {
+ terminateWithError("Target document not found: '" +
+ targetFilePath_.absolutePath() + "'");
+ return;
+ }
+
+ // ensure no spaces in path
+ std::string filename = targetFilePath_.filename();
+ if (filename.find(' ') != std::string::npos)
+ {
+ terminateWithError("Invalid filename: '" + filename +
+ "' (TeX does not understand paths with spaces)");
+ return;
+ }
+
+ // parse magic comments
+ Error error = core::tex::parseMagicComments(targetFilePath_,
+ &magicComments_);
+ if (error)
+ LOG_ERROR(error);
+
+ // determine tex program path
+ std::string userErrMsg;
+ if (!pdflatex::latexProgramForFile(magicComments_,
+ &texProgramPath_,
+ &userErrMsg))
+ {
+ terminateWithError(userErrMsg);
+ return;
+ }
+
+ // see if we need to weave
+ std::string ext = targetFilePath_.extensionLowerCase();
+ bool isRnw = ext == ".rnw" || ext == ".snw" || ext == ".nw";
+ if (isRnw)
+ {
+ // remove existing ancillary files + concordance
+ removeExistingLatexAncillaryFiles(targetFilePath_);
+ removeExistingAncillary(targetFilePath_, "-concordance.tex");
+
+ // attempt to weave the rnw
+ rnw_weave::runWeave(targetFilePath_,
+ encoding_,
+ magicComments_,
+ enqueOutputEvent,
+ boost::bind(
+ &AsyncPdfCompiler::onWeaveCompleted,
+ AsyncPdfCompiler::shared_from_this(), _1));
+ }
+ else
+ {
+ runLatexCompiler(false);
+ }
+
+ }
+
+private:
+
+ void onWeaveCompleted(const rnw_weave::Result& result)
+ {
+ if (result.succeeded)
+ runLatexCompiler(true, result.concordances);
+ else if (!result.errorLogEntries.empty())
+ terminateWithErrorLogEntries(result.errorLogEntries);
+ else
+ terminateWithError(result.errorMessage);
+ }
+
+ void runLatexCompiler(bool targetWeaved,
+ const rnw_concordance::Concordances& concordances =
+ rnw_concordance::Concordances())
+ {
+ // configure pdflatex options
+ pdflatex::PdfLatexOptions options;
+ options.fileLineError = false;
+ options.syncTex = !isTargetRnw() || !concordances.empty();
+ options.shellEscape = userSettings().enableLaTeXShellEscape();
+
+ // get back-end version info
+ core::system::ProcessResult result;
+ Error error = core::system::runProgram(
+ string_utils::utf8ToSystem(texProgramPath_.absolutePath()),
+ core::shell_utils::ShellArgs() << "--version",
+ "",
+ core::system::ProcessOptions(),
+ &result);
+ if (error)
+ LOG_ERROR(error);
+ else if (result.exitStatus != EXIT_SUCCESS)
+ LOG_ERROR_MESSAGE("Error probing for latex version: "+ result.stdErr);
+ else
+ options.versionInfo = result.stdOut;
+
+ // compute tex file path
+ FilePath texFilePath;
+ if (targetWeaved)
+ {
+ texFilePath = targetFilePath_.parent().complete(
+ targetFilePath_.stem() + ".tex");
+ }
+ else
+ {
+ texFilePath = targetFilePath_;
+ }
+
+ // remove log files if they exist (avoids confusion created by parsing
+ // old log files for errors)
+ removeExistingLatexAncillaryFiles(texFilePath);
+
+ // setup cleanup context if clean was specified
+ if (userSettings().cleanTexi2DviOutput())
+ auxillaryFileCleanupContext_.init(texFilePath);
+
+ // run latex compile
+
+ // this is our "simulated" texi2dvi -- this was originally
+ // coded as a sequence of sync calls to pdflatex, bibtex, and
+ // makeindex. re-coding it as async is going to be a bit
+ // involved so considering that this is not the default
+ // codepath we'll leave it sync for now (and then just call
+ // the (typically) async callback function onLatexCompileCompleted
+ // directly after the function returns
+
+ enqueOutputEvent("Running " + texProgramPath_.filename() +
+ " on " + texFilePath.filename() + "...");
+
+ error = tex::pdflatex::texToPdf(texProgramPath_,
+ texFilePath,
+ options,
+ &result);
+
+ if (error)
+ {
+ terminateWithError("Unable to compile pdf: " + error.summary());
+ }
+ else
+ {
+ onLatexCompileCompleted(result.exitStatus,
+ texFilePath,
+ concordances);
+ }
+
+ }
+
+ void onLatexCompileCompleted(int exitStatus,
+ const FilePath& texFilePath,
+ const rnw_concordance::Concordances& concords)
+ {
+ // collect errors from the log
+ core::tex::LogEntries logEntries;
+ getLogEntries(texFilePath, &logEntries);
+
+ // determine whether they will be shown in the list
+ // list or within the console
+ bool showIssuesList = !isTargetRnw() || !concords.empty();
+
+ // notify the cleanp context of log entries (so it can
+ // preserve any referenced files)
+ auxillaryFileCleanupContext_.preserveLogReferencedFiles(
+ logEntries);
+
+ // show log entries and build issues message
+ std::string issuesMsg;
+ if (showIssuesList && !logEntries.empty())
+ {
+ showLogEntries(logEntries, concords);
+ issuesMsg = buildIssuesMessage(logEntries);
+ }
+
+ if (exitStatus == EXIT_SUCCESS)
+ {
+ FilePath pdfPath = ancillaryFilePath(texFilePath, ".pdf");
+ std::string pdfFile = module_context::createAliasedPath(
+ pdfPath);
+ std::string completed = "completed\n\nCreated PDF: " + pdfFile + "\n";
+ if (!issuesMsg.empty())
+ completed += "\n" + issuesMsg;
+ enqueOutputEvent(completed);
+
+ // show issues in console if necessary
+ if (!showIssuesList)
+ writeLogEntriesOutput(logEntries);
+
+ if (onCompleted_)
+ onCompleted_();
+
+ enqueCompletedWithSuccessEvent(targetFilePath_, sourceLocation_);
+ }
+ else
+ {
+ std::string failedMsg = "failed\n";
+ if (!issuesMsg.empty())
+ failedMsg += "\n" + issuesMsg;
+ enqueOutputEvent(failedMsg);
+
+ // don't remove the log
+ auxillaryFileCleanupContext_.preserveLog();
+
+ // if there were no error found in the log file then just
+ // print the error and exit code
+ if (logEntries.empty())
+ {
+ boost::format fmt("Error running %1% (exit code %2%)");
+ std::string msg(boost::str(fmt % texProgramPath_.absolutePath()
+ % exitStatus));
+ enqueOutputEvent(msg + "\n");
+ }
+
+ // show issues in console if necessary
+ if (!showIssuesList)
+ writeLogEntriesOutput(logEntries);
+
+ enqueCompletedWithFailureEvent(targetFilePath_, sourceLocation_);
+ }
+ }
+
+ void terminateWithError(const std::string& message)
+ {
+ enqueOutputEvent(message + "\n");
+ enqueCompletedWithFailureEvent(targetFilePath_, sourceLocation_);
+ }
+
+ void terminateWithErrorLogEntries(const core::tex::LogEntries& logEntries)
+ {
+ showLogEntries(logEntries);
+ enqueCompletedWithFailureEvent(targetFilePath_, sourceLocation_);
+ }
+
+ bool isTargetRnw() const
+ {
+ return targetFilePath_.extensionLowerCase() == ".rnw";
+ }
+
+private:
+ FilePath targetFilePath_;
+ std::string encoding_;
+ json::Object sourceLocation_;
+ const boost::function<void()> onCompleted_;
+ core::tex::TexMagicComments magicComments_;
+ FilePath texProgramPath_;
+ AuxillaryFileCleanupContext auxillaryFileCleanupContext_;
+};
+
+
+} // anonymous namespace
+
+
+bool startCompile(const core::FilePath& targetFilePath,
+ const std::string& encoding,
+ const json::Object& sourceLocation,
+ const boost::function<void()>& onCompleted)
+{
+ if (!compile_pdf_supervisor::hasRunningChildren())
+ {
+ AsyncPdfCompiler::start(targetFilePath,
+ encoding,
+ sourceLocation,
+ onCompleted);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool compileIsRunning()
+{
+ return compile_pdf_supervisor::hasRunningChildren();
+}
+
+bool terminateCompile()
+{
+ Error error = compile_pdf_supervisor::terminateAll(
+ boost::posix_time::seconds(1));
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+ else
+ {
+ enqueOutputEvent("\n[Compile PDF Stopped]\n");
+ return true;
+ }
+}
+
+void notifyTabClosed()
+{
+ s_compilePdfState.clear();
+}
+
+json::Object currentStateAsJson()
+{
+ return s_compilePdfState.asJson();
+}
+
+Error initialize()
+{
+ // register suspend handler
+ using namespace module_context;
+ addSuspendHandler(SuspendHandler(boost::bind(onSuspend, _2), onResume));
+
+ return Success();
+}
+
+} // namespace compile_pdf
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionCompilePdf.hpp b/src/cpp/session/modules/tex/SessionCompilePdf.hpp
new file mode 100644
index 0000000..563c48d
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionCompilePdf.hpp
@@ -0,0 +1,53 @@
+/*
+ * SessionCompilePdf.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_TEX_COMPILE_PDF_HPP
+#define SESSION_MODULES_TEX_COMPILE_PDF_HPP
+
+#include <boost/function.hpp>
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace compile_pdf {
+
+bool startCompile(const core::FilePath& targetFilePath,
+ const std::string& encoding,
+ const core::json::Object& sourceLocation,
+ const boost::function<void()>& onCompleted);
+
+bool compileIsRunning();
+
+bool terminateCompile();
+
+void notifyTabClosed();
+
+core::json::Object currentStateAsJson();
+
+core::Error initialize();
+
+} // namespace compile_pdf
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_TEX_COMPILE_PDF_HPP
diff --git a/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.cpp b/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.cpp
new file mode 100644
index 0000000..419901c
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.cpp
@@ -0,0 +1,157 @@
+/*
+ * SessionCompilePdfSupervisor.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionCompilePdfSupervisor.hpp"
+
+#include <core/Log.hpp>
+#include <core/Error.hpp>
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace compile_pdf_supervisor {
+
+namespace {
+
+// supervisor is a module level static so that we can terminateChildren
+// upon exit of the session (otherwise we could leave a long running
+// operation still hogging cpu after we exit)
+core::system::ProcessSupervisor s_processSupervisor;
+
+void onBackgroundProcessing(bool)
+{
+ s_processSupervisor.poll();
+}
+
+void onShutdown(bool)
+{
+ // send kill signal
+ s_processSupervisor.terminateAll();
+
+ // wait and reap children (but for no longer than 1 second)
+ if (!s_processSupervisor.wait(boost::posix_time::milliseconds(10),
+ boost::posix_time::milliseconds(1000)))
+ {
+ LOG_WARNING_MESSAGE("Compile PDF supervisor didn't terminate in <1 sec");
+ }
+}
+
+// define class which accumulates output and passes it to onExited
+class CB : boost::noncopyable
+{
+public:
+ CB(const boost::function<void(const std::string&)>& onOutput,
+ const boost::function<void(int,const std::string&)>& onExited)
+ : onOutput_(onOutput), onExited_(onExited)
+ {
+ }
+ virtual ~CB() {}
+
+public:
+ void onOutput(const std::string& output)
+ {
+ onOutput_(output);
+ output_ += output;
+ }
+
+ void onExit(int exitStatus)
+ {
+ onExited_(exitStatus, output_);
+ }
+
+private:
+ std::string output_;
+ boost::function<void(const std::string&)> onOutput_;
+ boost::function<void(int,const std::string&)> onExited_;
+};
+
+
+} // anonymous namespace
+
+bool hasRunningChildren()
+{
+ return s_processSupervisor.hasRunningChildren();
+}
+
+Error terminateAll(const boost::posix_time::time_duration& waitDuration)
+{
+ // send the kill signals
+ s_processSupervisor.terminateAll();
+
+ // wait for the processes to exit
+ if (!s_processSupervisor.wait(boost::posix_time::milliseconds(100),
+ waitDuration))
+ {
+ return systemError(boost::system::errc::timed_out,
+ "CompilePDF didn't terminate within timeout interval",
+ ERROR_LOCATION);
+ }
+ else
+ {
+ return Success();
+ }
+}
+
+Error runProgram(const core::FilePath& programFilePath,
+ const std::vector<std::string>& args,
+ const core::system::Options& extraEnvVars,
+ const core::FilePath& workingDir,
+ const boost::function<void(const std::string&)>& onOutput,
+ const boost::function<void(int,const std::string&)>& onExited)
+{
+ // get system program file path
+ std::string programPath = string_utils::utf8ToSystem(
+ programFilePath.absolutePath());
+
+ // setup options
+ core::system::ProcessOptions options;
+ options.terminateChildren = true;
+ options.redirectStdErrToStdOut = true;
+ core::system::Options env;
+ core::system::getModifiedEnv(extraEnvVars, &env);
+ options.environment = env;
+ options.workingDir = workingDir;
+
+ // setup callbacks
+ boost::shared_ptr<CB> pCB(new CB(onOutput, onExited));
+ core::system::ProcessCallbacks cb;
+ cb.onStdout = cb.onStderr = boost::bind(&CB::onOutput, pCB, _2);
+ cb.onExit = boost::bind(&CB::onExit, pCB, _1);
+
+ // run process using supervisor
+ return s_processSupervisor.runProgram(programPath, args, options, cb);
+}
+
+Error initialize()
+{
+ // subscribe to events
+ module_context::Events& events = module_context::events();
+ events.onBackgroundProcessing.connect(onBackgroundProcessing);
+ events.onShutdown.connect(onShutdown);
+
+ return Success();
+}
+
+} // namespace compile_pdf_supervisor
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.hpp b/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.hpp
new file mode 100644
index 0000000..2566ae1
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionCompilePdfSupervisor.hpp
@@ -0,0 +1,58 @@
+/*
+ * SessionCompilePdfSupervisor.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_TEX_COMPILE_PDF_SUPERVISOR_HPP
+#define SESSION_MODULES_TEX_COMPILE_PDF_SUPERVISOR_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+
+#include <core/system/Types.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+ namespace system {
+ struct ProcessOptions;
+ }
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace compile_pdf_supervisor {
+
+
+bool hasRunningChildren();
+core::Error terminateAll(const boost::posix_time::time_duration& waitDuration);
+
+core::Error runProgram(const core::FilePath& programFilePath,
+ const std::vector<std::string>& args,
+ const core::system::Options& extraEnvVars,
+ const core::FilePath& workingDir,
+ const boost::function<void(const std::string&)>& onOutput,
+ const boost::function<void(int,const std::string&)>& onExited);
+
+core::Error initialize();
+
+} // namespace compile_pdf_supervisor
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_TEX_COMPILE_PDF_SUPERVISOR_HPP
diff --git a/src/cpp/session/modules/tex/SessionPdfLatex.cpp b/src/cpp/session/modules/tex/SessionPdfLatex.cpp
new file mode 100644
index 0000000..cfd9708
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionPdfLatex.cpp
@@ -0,0 +1,449 @@
+/*
+ * SessionPdfLatex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionPdfLatex.hpp"
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/system/Environment.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <session/projects/SessionProjects.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionTexUtils.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace pdflatex {
+
+namespace {
+
+class LatexProgramTypes : boost::noncopyable
+{
+public:
+ LatexProgramTypes()
+ {
+ types_.push_back("pdfLaTeX");
+ types_.push_back("XeLaTeX");
+ }
+
+ const std::vector<std::string>& allTypes() const
+ {
+ return types_;
+ }
+
+ json::Array allTypesAsJson() const
+ {
+ json::Array typesJson;
+ std::transform(types_.begin(),
+ types_.end(),
+ std::back_inserter(typesJson),
+ json::toJsonString);
+ return typesJson;
+ }
+
+ bool isValidTypeName(const std::string& name) const
+ {
+ BOOST_FOREACH(const std::string& type, types_)
+ {
+ if (boost::algorithm::iequals(name, type))
+ return true;
+ }
+
+ return false;
+ }
+
+ std::string printableTypeNames() const
+ {
+ if (types_.size() == 1)
+ return types_[0];
+ else if (types_.size() == 2)
+ return types_[0] + " and " + types_[1];
+ else
+ {
+ std::string str;
+ for (std::size_t i=0; i<types_.size(); i++)
+ {
+ str.append(types_[i]);
+ if (i != (types_.size() - 1))
+ str.append(", ");
+ if (i == (types_.size() - 2))
+ str.append("and ");
+ }
+ return str;
+ }
+ }
+
+private:
+ std::vector<std::string> types_;
+};
+
+const LatexProgramTypes& programTypes()
+{
+ static LatexProgramTypes instance;
+ return instance;
+}
+
+std::string latexProgramMagicComment(
+ const core::tex::TexMagicComments& magicComments)
+{
+ BOOST_FOREACH(const core::tex::TexMagicComment& mc, magicComments)
+ {
+ if (boost::algorithm::iequals(mc.scope(), "tex") &&
+ (boost::algorithm::iequals(mc.variable(), "program") ||
+ boost::algorithm::iequals(mc.variable(), "ts-program")))
+ {
+ return mc.value();
+ }
+ }
+
+ return std::string();
+}
+
+void setInvalidProgramTypeMessage(const std::string& program,
+ std::string* pUserErrMsg)
+{
+ *pUserErrMsg = "Unknown LaTeX program type '" + program +
+ "' specified (valid types are " +
+ programTypes().printableTypeNames() + ")";
+}
+
+
+bool validateLatexProgram(const std::string& program,
+ FilePath* pTexProgramPath,
+ std::string* pUserErrMsg)
+{
+ // convert to lower case for finding
+ std::string programName = string_utils::toLower(program);
+
+ // try to find on the path
+ *pTexProgramPath = module_context::findProgram(programName);
+ if (pTexProgramPath->empty())
+ {
+ *pUserErrMsg = "Unabled to find specified LaTeX program '" +
+ program + "' on the system path";
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+
+bool validateLatexProgramType(const std::string& programType,
+ std::string* pUserErrMsg)
+{
+ if (!programTypes().isValidTypeName(programType))
+ {
+ setInvalidProgramTypeMessage(programType, pUserErrMsg);
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void appendEnvVarNotice(std::string* pUserErrMsg)
+{
+ pUserErrMsg->append(" (the program was specified using the "
+ "RSTUDIO_PDFLATEX environment variable)");
+}
+
+shell_utils::ShellArgs shellArgs(const PdfLatexOptions& options)
+{
+ shell_utils::ShellArgs args;
+
+ if (options.fileLineError)
+ {
+ if (options.isMikTeX())
+ args << kCStyleErrorsOption;
+ else
+ args << kFileLineErrorOption;
+ }
+ if (options.syncTex)
+ {
+ args << kSynctexOption;
+ }
+ if (options.shellEscape)
+ {
+ if (options.isMikTeX())
+ args << kEnableWrite18Option;
+ else
+ args << kShellEscapeOption;
+ }
+ args << "-interaction=nonstopmode";
+
+ return args;
+}
+
+FilePath programPath(const std::string& name, const std::string& envOverride)
+{
+ std::string envProgram = core::system::getenv(envOverride);
+ std::string program = envProgram.empty() ? name : envProgram;
+ return module_context::findProgram(program);
+}
+
+
+
+bool lineIncludes(const std::string& line, const boost::regex& regex)
+{
+ boost::smatch match;
+ return boost::regex_search(line, match, regex);
+}
+
+int countCitationMisses(const FilePath& logFilePath)
+{
+ // read the log file
+ std::vector<std::string> lines;
+ Error error = core::readStringVectorFromFile(logFilePath, &lines);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return 0;
+ }
+
+ // look for misses
+ boost::regex missRegex("Warning:.*Citation.*undefined");
+ int misses = std::count_if(lines.begin(),
+ lines.end(),
+ boost::bind(lineIncludes, _1, missRegex));
+ return misses;
+}
+
+bool logIncludesRerun(const FilePath& logFilePath)
+{
+ std::string logContents;
+ Error error = core::readStringFromFile(logFilePath, &logContents);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return false;
+ }
+
+ return logContents.find("Rerun to get") != std::string::npos;
+}
+
+} // anonymous namespace
+
+const char * const kFileLineErrorOption = "-file-line-error";
+const char * const kCStyleErrorsOption = "-c-style-errors";
+const char * const kShellEscapeOption = "-shell-escape";
+const char * const kEnableWrite18Option = "-enable-write18";
+const char * const kSynctexOption = "-synctex=1";
+
+bool isInstalled()
+{
+ return !module_context::findProgram("pdflatex").empty();
+}
+
+
+core::json::Array supportedTypes()
+{
+ return programTypes().allTypesAsJson();
+}
+
+
+bool latexProgramForFile(const core::tex::TexMagicComments& magicComments,
+ FilePath* pTexProgramPath,
+ std::string* pUserErrMsg)
+{
+ // get (optional) magic comments and environment variable override
+ std::string latexProgramMC = latexProgramMagicComment(magicComments);
+ std::string pdflatexEnv = core::system::getenv("RSTUDIO_PDFLATEX");
+
+ // magic comment always takes highest priority
+ if (!latexProgramMC.empty())
+ {
+ // validate magic comment
+ if (!validateLatexProgramType(latexProgramMC, pUserErrMsg))
+ {
+ return false;
+ }
+ else
+ {
+ return validateLatexProgram(latexProgramMC,
+ pTexProgramPath,
+ pUserErrMsg);
+ }
+ }
+
+ // next is environment variable
+ else if (!pdflatexEnv.empty())
+ {
+ if (FilePath::isRootPath(pdflatexEnv))
+ {
+ FilePath texProgramPath(pdflatexEnv);
+ if (texProgramPath.exists())
+ {
+ *pTexProgramPath = texProgramPath;
+ return true;
+ }
+ else
+ {
+ *pUserErrMsg = "Unabled to find specified LaTeX program " +
+ pdflatexEnv;
+ appendEnvVarNotice(pUserErrMsg);
+ return false;
+ }
+ }
+ else
+ {
+ bool validated = validateLatexProgram(pdflatexEnv,
+ pTexProgramPath,
+ pUserErrMsg);
+
+ if (!validated)
+ appendEnvVarNotice(pUserErrMsg);
+
+ return validated;
+ }
+ }
+
+ // project or global default setting
+ else
+ {
+ std::string defaultProgram = projects::projectContext().hasProject() ?
+ projects::projectContext().config().defaultLatexProgram :
+ userSettings().defaultLatexProgram();
+
+ if (!validateLatexProgramType(defaultProgram, pUserErrMsg))
+ {
+ return false;
+ }
+ else
+ {
+ return validateLatexProgram(defaultProgram,
+ pTexProgramPath,
+ pUserErrMsg);
+ }
+ }
+}
+
+// this function provides an "emulated" version of texi2dvi for when the
+// user has texi2dvi disabled. For example to workaround this bug:
+//
+// http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=534458
+//
+// this code is a port of the simillar logic which exists in the
+// tools::texi2dvi function (but the regex for detecting citation
+// warnings was made a bit more liberal)
+//
+core::Error texToPdf(const core::FilePath& texProgramPath,
+ const core::FilePath& texFilePath,
+ const tex::pdflatex::PdfLatexOptions& options,
+ core::system::ProcessResult* pResult)
+{
+ // input file paths
+ FilePath baseFilePath = texFilePath.parent().complete(texFilePath.stem());
+ FilePath idxFilePath(baseFilePath.absolutePath() + ".idx");
+ FilePath logFilePath(baseFilePath.absolutePath() + ".log");
+
+ // bibtex and makeindex program paths
+ FilePath bibtexProgramPath = programPath("bibtex", "BIBTEX");
+ FilePath makeindexProgramPath = programPath("makeindex", "MAKEINDEX");
+
+ // args and process options for running bibtex and makeindex
+ core::shell_utils::ShellArgs bibtexArgs;
+ bibtexArgs << string_utils::utf8ToSystem(baseFilePath.filename());
+ core::shell_utils::ShellArgs makeindexArgs;
+ makeindexArgs << string_utils::utf8ToSystem(idxFilePath.filename());
+ core::system::ProcessOptions procOptions;
+ procOptions.environment = utils::rTexInputsEnvVars();
+ procOptions.workingDir = texFilePath.parent();
+
+ // run the initial compile
+ Error error = utils::runTexCompile(texProgramPath,
+ utils::rTexInputsEnvVars(),
+ shellArgs(options),
+ texFilePath,
+ pResult);
+ if (error)
+ return error;
+
+ // count misses
+ int misses = countCitationMisses(logFilePath);
+ int previousMisses = 0;
+
+ // resolve citation misses and index
+ for (int i=0; i<10; i++)
+ {
+ // run bibtex if necessary
+ if (misses > 0 && !bibtexProgramPath.empty())
+ {
+ core::system::ProcessResult result;
+ Error error = core::system::runProgram(
+ string_utils::utf8ToSystem(bibtexProgramPath.absolutePath()),
+ bibtexArgs,
+ "",
+ procOptions,
+ pResult);
+ if (error)
+ LOG_ERROR(error);
+ else if (pResult->exitStatus != EXIT_SUCCESS)
+ return Success(); // pass error state on to caller
+ }
+ previousMisses = misses;
+
+ // run makeindex if necessary
+ if (idxFilePath.exists() && !makeindexProgramPath.empty())
+ {
+ Error error = core::system::runProgram(
+ string_utils::utf8ToSystem(makeindexProgramPath.absolutePath()),
+ makeindexArgs,
+ "",
+ procOptions,
+ pResult);
+ if (error)
+ LOG_ERROR(error);
+ else if (pResult->exitStatus != EXIT_SUCCESS)
+ return Success(); // pass error state on to caller
+ }
+
+ // re-run latex
+ Error error = utils::runTexCompile(texProgramPath,
+ utils::rTexInputsEnvVars(),
+ shellArgs(options),
+ texFilePath,
+ pResult);
+ if (error)
+ return error;
+
+ // count misses
+ misses = countCitationMisses(logFilePath);
+
+ // if there is no change in misses and there is no "Rerun to get"
+ // in the log file then break
+ if ((misses == previousMisses) && !logIncludesRerun(logFilePath))
+ break;
+ }
+
+ return Success();
+}
+
+
+} // namespace pdflatex
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionPdfLatex.hpp b/src/cpp/session/modules/tex/SessionPdfLatex.hpp
new file mode 100644
index 0000000..9f50c73
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionPdfLatex.hpp
@@ -0,0 +1,89 @@
+/*
+ * SessionPdfLatex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_TEX_PDFLATEX_HPP
+#define SESSION_MODULES_TEX_PDFLATEX_HPP
+
+#include <core/FilePath.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <core/tex/TexMagicComment.hpp>
+
+#include <core/system/Types.hpp>
+#include <core/system/Process.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace pdflatex {
+
+// NOTE: other potential command line parameters to support:
+// -interaction=batch (but will that work with texi2dvi?)
+// -halt-on-error (same question, does it work with texi2dvi)
+//
+
+// NOTE: synctex can be called from the command line as in:
+// synctex view –i 25:15:filename.tex –o filename.pdf
+//
+
+extern const char * const kFileLineErrorOption;
+extern const char * const kCStyleErrorsOption;
+extern const char * const kShellEscapeOption;
+extern const char * const kEnableWrite18Option;
+extern const char * const kSynctexOption;
+
+struct PdfLatexOptions
+{
+ PdfLatexOptions()
+ : fileLineError(false), syncTex(false), shellEscape(false)
+ {
+ }
+
+ bool isMikTeX() const
+ {
+ return versionInfo.find("MiKTeX") != std::string::npos;
+ }
+
+ bool fileLineError;
+ bool syncTex;
+ bool shellEscape;
+ std::string versionInfo;
+};
+
+core::Error texToPdf(const core::FilePath& texProgramPath,
+ const core::FilePath& texFilePath,
+ const tex::pdflatex::PdfLatexOptions& options,
+ core::system::ProcessResult* pResult);
+
+bool isInstalled();
+
+core::json::Array supportedTypes();
+
+bool latexProgramForFile(const core::tex::TexMagicComments& magicComments,
+ core::FilePath* pTexProgramPath,
+ std::string* pUserErrMsg);
+
+} // namespace pdflatex
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_TEX_PDFLATEX_HPP
diff --git a/src/cpp/session/modules/tex/SessionRnwConcordance.cpp b/src/cpp/session/modules/tex/SessionRnwConcordance.cpp
new file mode 100644
index 0000000..ff8c6ce
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionRnwConcordance.cpp
@@ -0,0 +1,413 @@
+/*
+ * SessionRnwConcordance.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionRnwConcordance.hpp"
+
+#include <iostream>
+
+#include <boost/foreach.hpp>
+#include <boost/regex.hpp>
+
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/regex.hpp>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/Algorithm.hpp>
+
+#include <core/tex/TexSynctex.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace rnw_concordance {
+
+namespace {
+
+FilePath concordanceFilePath(const FilePath& rnwFilePath)
+{
+ FilePath parentDir = rnwFilePath.parent();
+ return parentDir.complete(rnwFilePath.stem() + "-concordance.tex");
+}
+
+Error badFormatError(const FilePath& concordanceFile,
+ const std::string& context,
+ const ErrorLocation& location)
+{
+ return systemError(boost::system::errc::protocol_error,
+ "Unexpected concordance file format (" + context + ")",
+ location);
+}
+
+inline int strToInt(const std::string& str)
+{
+ return boost::lexical_cast<int>(str);
+}
+
+template<typename InputIterator, typename OutputIterator>
+OutputIterator rleDecodeValues(InputIterator begin,
+ InputIterator end,
+ OutputIterator destBegin)
+{
+ while (begin != end)
+ {
+ int count = *begin++;
+
+ if (begin == end)
+ break;
+
+ int val = *begin++;
+
+ for (int i=0;i<count; i++)
+ *destBegin++ = val;
+ }
+ return destBegin;
+}
+
+} // anonymous namespace
+
+Error Concordance::parse(const FilePath& sourceFile,
+ const std::string& input,
+ const FilePath& baseDir)
+{
+ // split into lines
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines, input, boost::algorithm::is_any_of("\n"));
+
+ // paste them back together (removing trailing %)
+ using namespace boost::algorithm;
+ std::string concordance;
+ BOOST_FOREACH(const std::string& line, lines)
+ {
+ concordance.append(trim_right_copy_if(line, is_any_of("%")));
+ }
+
+ // extract concordance structure
+ boost::regex re("\\\\Sconcordance\\{([^\\}]+)\\}");
+ boost::smatch match;
+ if (!boost::regex_match(concordance, match, re))
+ return badFormatError(sourceFile, "body", ERROR_LOCATION);
+
+ // split into sections
+ std::vector<std::string> sections;
+ boost::algorithm::split(sections,
+ static_cast<const std::string>(match[1]),
+ boost::algorithm::is_any_of(":"));
+
+ // validate the number of sections
+ if (sections.size() < 4 || sections.size() > 5)
+ return badFormatError(sourceFile, "sections", ERROR_LOCATION);
+
+ // get input and output file names
+ outputFile_ = baseDir.complete(core::tex::normalizeSynctexName(sections[1]));
+ inputFile_ = baseDir.complete(core::tex::normalizeSynctexName(sections[2]));
+
+ // get offset and values
+ std::string valuesSection;
+ if (sections.size() == 5)
+ {
+ boost::regex re("^ofs ([0-9]+)");
+ boost::smatch match;
+ if (!boost::regex_match(sections[3], match, re))
+ return badFormatError(sourceFile, "offset", ERROR_LOCATION);
+
+ offset_ = safe_convert::stringTo<std::size_t>(match[1], 0);
+ valuesSection = sections[4];
+ }
+ else
+ {
+ offset_ = 0;
+ valuesSection = sections[3];
+ }
+
+ // convert values to integer array
+ std::vector<std::string> strValues;
+ boost::algorithm::split(strValues,
+ valuesSection,
+ boost::algorithm::is_space(),
+ boost::algorithm::token_compress_on);
+ std::vector<int> rleValues;
+ try
+ {
+ std::transform(strValues.begin(),
+ strValues.end(),
+ std::back_inserter(rleValues),
+ &strToInt);
+ }
+ catch(const boost::bad_lexical_cast&)
+ {
+ return badFormatError(sourceFile, "values", ERROR_LOCATION);
+ }
+
+ // confirm we have at least one element and extract it as the start line
+ if (rleValues.size() < 1)
+ return badFormatError(sourceFile, "no-values", ERROR_LOCATION);
+ int startLine = rleValues[0];
+
+ // unroll the RLE encoded values
+ std::vector<int> diffs;
+ rleDecodeValues(rleValues.begin() + 1,
+ rleValues.end(),
+ std::back_inserter(diffs));
+
+ // use these values to create the mapping
+ mapping_.resize(diffs.size());
+ int pos = startLine;
+ for (std::size_t i = 0; i<diffs.size(); i++)
+ {
+ mapping_[i] = pos;
+ pos += diffs[i];
+ }
+
+ return Success();
+}
+
+void Concordance::append(const Concordance& concordance)
+{
+ // if we don't yet have an input and output file then initialize
+ // from this concordance -- otherwise verify that the inbound
+ // concordances match
+ if (inputFile_.empty())
+ inputFile_ = concordance.inputFile();
+ if (outputFile_.empty())
+ outputFile_ = concordance.outputFile();
+
+ if (inputFile_ != concordance.inputFile())
+ {
+ LOG_WARNING_MESSAGE("Non matching concordance: " +
+ inputFile_.absolutePath() + ", " +
+ concordance.inputFile().absolutePath());
+ return;
+ }
+
+ else if (outputFile_ != concordance.outputFile())
+ {
+ LOG_WARNING_MESSAGE("Non matching concordance: " +
+ outputFile_.absolutePath() + ", " +
+ concordance.outputFile().absolutePath());
+ return;
+ }
+
+ // if the concordance being added has an offset greater than our
+ // number of lines then we need to pad (so that we have a line for
+ // each line in the output file even if our concordances aren't
+ // responsible for the output)
+ int rnwLine = mapping_.size() > 0 ? mapping_.back() : 1;
+ while (mapping_.size() < concordance.offset())
+ mapping_.push_back(rnwLine);
+
+ // append the inbound concordance
+ std::copy(concordance.mapping_.begin(),
+ concordance.mapping_.end(),
+ std::back_inserter(mapping_));
+}
+
+std::ostream& operator << (std::ostream& stream, const FileAndLine& fileLine)
+{
+ stream << fileLine.filePath() << ":" << fileLine.line();
+ return stream;
+}
+
+
+namespace {
+
+bool hasOutputFile(const Concordance& concord, const FilePath& outputFile)
+{
+ return concord.outputFile().isEquivalentTo(outputFile);
+}
+
+bool hasInputFile(const Concordance& concord, const FilePath& inputFile)
+{
+ return concord.inputFile().isEquivalentTo(inputFile);
+}
+
+} // anonymous namespace
+
+FileAndLine Concordances::rnwLine(const FileAndLine& texLine) const
+{
+ if (texLine.filePath().empty())
+ return FileAndLine();
+
+ // inspect concordance where output file is equivliant to tex file
+ std::vector<Concordance> texFileConcords;
+ algorithm::copy_if(
+ concordances_.begin(),
+ concordances_.end(),
+ std::back_inserter(texFileConcords),
+ boost::bind(hasOutputFile, _1, texLine.filePath()));
+
+ // reverse search for the first concordances whose offset is less than
+ // the text line we are seeking concordance for
+ for (std::vector<Concordance>::const_reverse_iterator it =
+ texFileConcords.rbegin(); it != texFileConcords.rend(); ++it)
+ {
+ if (texLine.line() > static_cast<int>(it->offset()))
+ {
+ return FileAndLine(it->inputFile(),
+ it->rnwLine(texLine.line()));
+ }
+ }
+
+ return FileAndLine();
+}
+
+
+FileAndLine Concordances::texLine(const FileAndLine& rnwLine) const
+{
+ if (rnwLine.filePath().empty())
+ return FileAndLine();
+
+ // inspect concordance where input file is equivilant to rnw file
+ std::vector<Concordance> rnwFileConcords;
+ algorithm::copy_if(
+ concordances_.begin(),
+ concordances_.end(),
+ std::back_inserter(rnwFileConcords),
+ boost::bind(hasInputFile, _1, rnwLine.filePath()));
+ if (rnwFileConcords.size() == 0)
+ return FileAndLine();
+
+ // build a single concordance structure for seeking
+ Concordance rnwFileConcord;
+ for (std::vector<Concordance>::const_iterator
+ it = rnwFileConcords.begin(); it != rnwFileConcords.end(); ++it)
+ {
+ rnwFileConcord.append(*it);
+ }
+
+ // seek
+ FileAndLine texLine(rnwFileConcord.outputFile(),
+ rnwFileConcord.texLine(rnwLine.line()));
+ return texLine;
+}
+
+std::string fixup_formatter(const Concordances& concordances,
+ const FilePath& sourceFile,
+ const boost::smatch& what)
+{
+ std::string result = what[0];
+
+ for (unsigned int i = what.size()-1; i > 0; i--)
+ {
+ if (what[i].matched)
+ {
+ int inputLine = core::safe_convert::stringTo<int>(what[i], 1);
+ FileAndLine dest = concordances.rnwLine(
+ FileAndLine(sourceFile, inputLine));
+ if (!dest.empty())
+ {
+ result.replace(what.position(i) - what.position(),
+ what.length(i),
+ safe_convert::numberToString(dest.line()));
+ }
+ }
+ }
+
+ return result;
+}
+
+core::tex::LogEntry Concordances::fixup(const core::tex::LogEntry &entry,
+ bool *pSuccess) const
+{
+ // Error messages themselves can (and usually do) contain line numbers.
+ // It looks silly when we show the Rnw line numbers juxtaposed with the
+ // TeX line numbers (e.g. "Line 102: Error at line 192")
+ static boost::regex regexLines("\\blines? (\\d+)(?:-{1,3}(\\d+))?\\b");
+
+ FileAndLine mapped = rnwLine(FileAndLine(entry.filePath(), entry.line()));
+ if (!mapped.empty())
+ {
+ boost::function<std::string(boost::smatch)> formatter =
+ boost::bind(fixup_formatter, *this, entry.filePath(), _1);
+ std::string mappedMsg =
+ boost::regex_replace(entry.message(), regexLines, formatter);
+
+ if (pSuccess)
+ *pSuccess = true;
+
+ return core::tex::LogEntry(entry.logFilePath(),
+ entry.logLine(),
+ entry.type(),
+ mapped.filePath(),
+ mapped.line(),
+ mappedMsg);
+ }
+ else
+ {
+ if (pSuccess)
+ *pSuccess = false;
+ return entry;
+ }
+}
+
+void removePrevious(const core::FilePath& rnwFile)
+{
+ Error error = concordanceFilePath(rnwFile).removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+}
+
+
+Error readIfExists(const core::FilePath& srcFile, Concordances* pConcordances)
+{
+ // return success if the file doesn't exist
+ FilePath concordanceFile = concordanceFilePath(srcFile);
+ if (!concordanceFile.exists())
+ return Success();
+
+ // read the file
+ std::string contents;
+ Error error = core::readStringFromFile(concordanceFile,
+ &contents,
+ string_utils::LineEndingPosix);
+ if (error)
+ return error;
+
+ // split on concordance
+ const char * const kConcordance = "\\Sconcordance";
+ boost::regex re("\\" + std::string(kConcordance));
+ std::vector<std::string> concordances;
+ boost::algorithm::split_regex(concordances, contents, re);
+ BOOST_FOREACH(const std::string& concordance, concordances)
+ {
+ std::string entry = boost::algorithm::trim_copy(concordance);
+ if (!entry.empty())
+ {
+ Concordance concord;
+ Error error = concord.parse(concordanceFile,
+ kConcordance + entry,
+ srcFile.parent());
+ if (error)
+ LOG_ERROR(error);
+ else
+ pConcordances->add(concord);
+ }
+ }
+
+ return Success();
+}
+
+} // namespace rnw_concordance
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionRnwConcordance.hpp b/src/cpp/session/modules/tex/SessionRnwConcordance.hpp
new file mode 100644
index 0000000..123038e
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionRnwConcordance.hpp
@@ -0,0 +1,182 @@
+/*
+ * SessionRnwConcordance.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_RNW_CONCORDANCE_HPP
+#define SESSION_MODULES_RNW_CONCORDANCE_HPP
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/tex/TexLogParser.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace rnw_concordance {
+
+class Concordance
+{
+public:
+ Concordance()
+ : offset_(0)
+ {
+ }
+
+ // COPYING: via compiler
+
+ // create by parsing a concordance file
+ core::Error parse(const core::FilePath& sourceFile,
+ const std::string& input,
+ const core::FilePath& baseDir);
+
+ // append another concordance to this concordance (assumes they have
+ // the same input and output file and they originate from a common
+ // concordance generation sequences -- i.e. offsets line up)
+ void append(const Concordance& concordance);
+
+ bool empty() const { return mapping_.empty(); }
+
+ const core::FilePath& outputFile() const { return outputFile_; }
+
+ const core::FilePath& inputFile() const { return inputFile_; }
+
+ std::size_t offset() const { return offset_; }
+
+ // checked access to rnw lines from tex lines
+ int rnwLine(int texLine) const
+ {
+ // subtract 1 to normalize lines to C array indexes
+ texLine--;
+
+ // then substract the offset (which was the starting line
+ // number of the output generated by the input file)
+ texLine -= offset_;
+
+ // return the mapping (but return -1 if it is out of range)
+ int mappingSize = core::safe_convert::numberTo<int>(mapping_.size(), 0);
+ if (texLine >= 0 && texLine < mappingSize)
+ return mapping_[texLine];
+ else
+ return -1;
+ }
+
+ // checked access to tex lines from rnw lines. note that this returns
+ // the tex line which is closest to the specified rnw line (since some
+ // rnw lines don't result in tex output e.g. ones in hidden sweave chunks)
+ int texLine(int rnwLine) const
+ {
+ int texLine = -1;
+ int smallestDistance = -1;
+ for (std::size_t i = 0; i<mapping_.size(); i++)
+ {
+ if (texLine == -1)
+ {
+ texLine = i + 1 + offset_;
+ smallestDistance = std::abs(mapping_[i] - rnwLine);
+ }
+ else
+ {
+ int thisDistance = std::abs(mapping_[i] - rnwLine);
+ if (thisDistance < smallestDistance)
+ {
+ texLine = i + 1 + offset_;
+ smallestDistance = thisDistance;
+
+ if (smallestDistance == 0)
+ break;
+ }
+ }
+ }
+
+ return texLine;
+ }
+
+private:
+ core::FilePath outputFile_;
+ core::FilePath inputFile_;
+ std::size_t offset_;
+ std::vector<int> mapping_;
+};
+
+class FileAndLine
+{
+public:
+ FileAndLine()
+ : line_(-1)
+ {
+ }
+
+ FileAndLine(const core::FilePath& filePath, int line)
+ : filePath_(filePath), line_(line)
+ {
+ }
+ ~FileAndLine() {}
+ // COPYING: via compiler
+
+ bool empty() const { return filePath_.empty(); }
+
+ const core::FilePath& filePath() const { return filePath_; }
+ int line() const { return line_; }
+
+private:
+ core::FilePath filePath_;
+ int line_;
+};
+
+std::ostream& operator << (std::ostream& stream, const FileAndLine& fileLine);
+
+class Concordances
+{
+public:
+ Concordances() {}
+ ~Concordances() {}
+ // COPYING: via compiler
+
+ bool empty() const { return concordances_.empty(); }
+
+ void add(Concordance& concordance)
+ {
+ concordances_.push_back(concordance);
+ }
+
+ FileAndLine rnwLine(const FileAndLine& texLine) const;
+ FileAndLine texLine(const FileAndLine& rnwLine) const;
+
+ core::tex::LogEntry fixup(const core::tex::LogEntry& entry,
+ bool* pSuccess=NULL) const;
+
+private:
+ std::vector<Concordance> concordances_;
+};
+
+void removePrevious(const core::FilePath& rnwFile);
+
+core::Error readIfExists(const core::FilePath& srcFile,
+ Concordances* pConcordances);
+
+} // namespace rnw_concordance
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_RNW_CONCORDANCE_HPP
diff --git a/src/cpp/session/modules/tex/SessionRnwWeave.cpp b/src/cpp/session/modules/tex/SessionRnwWeave.cpp
new file mode 100644
index 0000000..007b794
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionRnwWeave.cpp
@@ -0,0 +1,580 @@
+/*
+ * SessionRnwWeave.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionRnwWeave.hpp"
+
+#include <boost/utility.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+
+#include <boost/algorithm/string/split.hpp>
+
+#include <core/FileSerializer.hpp>
+
+#include <core/tex/TexLogParser.hpp>
+#include <core/tex/TexMagicComment.hpp>
+
+#include <r/RExec.hpp>
+#include <r/RRoutines.hpp>
+#include <r/RJson.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/projects/SessionProjects.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionRnwConcordance.hpp"
+#include "SessionCompilePdfSupervisor.hpp"
+
+using namespace core;
+using namespace session::modules::tex::rnw_concordance;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace rnw_weave {
+
+namespace {
+
+Error rBinDir(FilePath* pRBinDir)
+{
+ std::string rHomeBin;
+ r::exec::RFunction rHomeBinFunc("R.home", "bin");
+ Error error = rHomeBinFunc.call(&rHomeBin);
+ if (error)
+ return error;
+
+ *pRBinDir = FilePath(rHomeBin);
+ return Success();
+}
+
+
+class RnwWeave : boost::noncopyable
+{
+public:
+ explicit RnwWeave(const std::string& name,
+ const std::string& packageName = "")
+ {
+ name_ = name;
+ packageName_ = !packageName.empty() ? packageName : name;
+ }
+
+ virtual ~RnwWeave()
+ {
+ }
+
+ // COPYING: noncopyable (to prevent slicing)
+
+ const std::string& name() const { return name_; }
+ const std::string& packageName() const { return packageName_; }
+
+ virtual bool injectConcordance() const = 0;
+
+ virtual bool usesCodeForOptions() const = 0;
+
+ virtual bool forceEchoOnExec() const = 0;
+
+ virtual bool isInstalled() const = 0;
+
+ virtual core::json::Value chunkOptions() const = 0;
+
+ // tangle the passed file (note that the implementation can assume
+ // that the working directory is already set to that of the file)
+ virtual core::Error tangle(const std::string& file) = 0;
+
+ virtual std::vector<std::string> commandArgs(
+ const std::string& file,
+ const std::string& encoding) const
+ {
+ std::vector<std::string> args;
+ args.push_back("--silent");
+ args.push_back("--no-save");
+ args.push_back("--no-restore");
+ args.push_back("-e");
+ std::string cmd = "grDevices::pdf.options(useDingbats = FALSE); "
+ + weaveCommand(file, encoding);
+ args.push_back(cmd);
+ return args;
+ }
+
+ virtual std::string weaveCommand(const std::string& file,
+ const std::string& encoding) const = 0;
+
+ virtual core::Error parseOutputForErrors(
+ const std::string& output,
+ const core::FilePath& rnwFilePath,
+ core::tex::LogEntries* pLogEntries) const
+ {
+ // split into lines so we can determine the line numbers for the chunks
+ // NOTE: will need to read this using global/project encoding if we
+ // want to look for text outside of theh orignal error parsing
+ // scenario (which only required ascii)
+ std::string rnwContents;
+ Error error = core::readStringFromFile(rnwFilePath, &rnwContents);
+ if (error)
+ return error;
+ std::vector<std::string> lines;
+ boost::algorithm::split(lines, rnwContents, boost::is_any_of("\n"));
+
+ // determine line numbers
+ boost::regex re("^<<(.*)>>=.*");
+ boost::smatch match;
+ std::vector<int> chunkLineNumbers;
+ for (std::size_t i=0; i<lines.size(); i++)
+ {
+ if (boost::regex_match(lines[i], match, re))
+ chunkLineNumbers.push_back(i+1);
+ }
+
+ // determine chunk number and error message
+ boost::regex cre(
+ "[\\w]+:[\\s]+chunk[\\s]+(\\d+)[^\n]+\n[^:]+:[\\s]*[\n]?([^\n]+)\n");
+ if (boost::regex_search(output, match, cre))
+ {
+ std::string match1(match[1]);
+ std::string match2(match[2]);
+ std::size_t chunk = core::safe_convert::stringTo<int>(match1, 0);
+ std::string msg = boost::algorithm::trim_copy(match2);
+ if (chunk > 0 && chunk <= chunkLineNumbers.size())
+ {
+ boost::format fmt("(chunk %1%) %2%");
+ core::tex::LogEntry logEntry(FilePath(),
+ -1,
+ core::tex::LogEntry::Error,
+ rnwFilePath,
+ chunkLineNumbers[chunk-1],
+ boost::str(fmt % chunk % msg));
+ pLogEntries->push_back(logEntry);
+ }
+ }
+
+ return Success();
+ }
+
+protected:
+ core::json::Value chunkOptions(const std::string& chunkFunction) const
+ {
+ SEXP optionsSEXP;
+ r::sexp::Protect rProtect;
+ r::exec::RFunction optionsFunc(chunkFunction);
+ Error error = optionsFunc.call(&optionsSEXP, &rProtect);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return json::Value();
+ }
+
+ core::json::Value optionsJson;
+ error = r::json::jsonValueFromList(optionsSEXP, &optionsJson);
+ if (error)
+ LOG_ERROR(error);
+
+ return optionsJson;
+ }
+
+private:
+ std::string name_;
+ std::string packageName_;
+};
+
+class RnwSweave : public RnwWeave
+{
+public:
+ RnwSweave()
+ : RnwWeave("Sweave")
+ {
+ }
+
+ virtual bool isInstalled() const { return true; }
+
+ virtual bool injectConcordance() const { return true; }
+
+ virtual bool usesCodeForOptions() const { return false; }
+
+ virtual bool forceEchoOnExec() const { return false; }
+
+ virtual core::json::Value chunkOptions() const
+ {
+ return RnwWeave::chunkOptions(".rs.sweaveChunkOptions");
+ }
+
+ virtual core::Error tangle(const std::string& file)
+ {
+ return r::exec::RFunction("utils:::Stangle", file).call();
+ }
+
+ virtual std::string weaveCommand(const std::string& file,
+ const std::string& encoding) const
+ {
+ std::string cmd = "utils::Sweave('" + file + "'";
+
+ if (!encoding.empty())
+ cmd += (", encoding='" + encoding + "'");
+
+ cmd += ")";
+
+ return cmd;
+ }
+};
+
+
+class RnwKnitr : public RnwWeave
+{
+public:
+ RnwKnitr()
+ : RnwWeave("knitr", "knitr")
+ {
+ }
+
+ virtual bool injectConcordance() const { return false; }
+
+ virtual bool usesCodeForOptions() const { return true; }
+
+ virtual bool forceEchoOnExec() const { return true; }
+
+ virtual bool isInstalled() const
+ {
+ return module_context::isPackageInstalled(packageName());
+ }
+
+ virtual std::string weaveCommand(const std::string& file,
+ const std::string& encoding) const
+ {
+ std::string format = "require(knitr); ";
+ if (userSettings().alwaysEnableRnwCorcordance())
+ format += "opts_knit$set(concordance = TRUE); ";
+ format += "knit('%1%'";
+ std::string cmd = boost::str(boost::format(format) % file);
+
+ if (!encoding.empty())
+ cmd += (", encoding='" + encoding + "'");
+
+ cmd += ")";
+
+ return cmd;
+ }
+
+ virtual core::Error parseOutputForErrors(
+ const std::string& output,
+ const core::FilePath& rnwFilePath,
+ core::tex::LogEntries* pLogEntries) const
+ {
+ // older error style
+ boost::regex errRe("^\\s*Quitting from lines ([0-9]+)-([0-9]+): "
+ "(?:Error in [a-z]+\\([a-z=, ]+\\) : \n?)?"
+ "([^\n]+)$");
+
+ // new error style
+ boost::regex newErrRe("^\\s*Quitting from lines ([0-9]+)-([0-9]+) "
+ "\\((.*?)\\)\\s*\\n(.*?)\\n");
+
+ boost::smatch match;
+ if (boost::regex_search(output, match, errRe))
+ {
+ // extract error info
+ int lineBegin = safe_convert::stringTo<int>(match[1], -1);
+ std::string message = match[3];
+
+ // check to see if there is a parse error which provides more
+ // precise pinpointing of the line
+ boost::regex parseRe("^\\s*<text>:([0-9]+):[0-9]+: (.+)$");
+ if (boost::regex_match(message, match, parseRe))
+ {
+ lineBegin += safe_convert::stringTo<int>(match[1], -1);
+ message = match[2];
+ }
+
+ core::tex::LogEntry logEntry(FilePath(),
+ -1,
+ core::tex::LogEntry::Error,
+ rnwFilePath,
+ lineBegin,
+ message);
+ pLogEntries->push_back(logEntry);
+ }
+
+ else if (boost::regex_search(output, match, newErrRe))
+ {
+ // extract error info
+ int lineBegin = safe_convert::stringTo<int>(match[1], -1);
+ std::string message = match[4];
+
+ core::tex::LogEntry logEntry(FilePath(),
+ -1,
+ core::tex::LogEntry::Error,
+ rnwFilePath,
+ lineBegin,
+ message);
+ pLogEntries->push_back(logEntry);
+ }
+
+ return Success();
+ }
+
+ virtual core::json::Value chunkOptions() const
+ {
+ if (isInstalled())
+ return RnwWeave::chunkOptions(".rs.knitrChunkOptions");
+ else
+ return json::Value();
+ }
+
+ virtual core::Error tangle(const std::string& file)
+ {
+ r::session::utils::SuppressOutputInScope suppressOutput;
+ r::exec::RFunction purlFunc("knitr:::purl");
+ purlFunc.addParam("input", file);
+ purlFunc.addParam("output", file + ".R");
+ return purlFunc.call();
+ }
+};
+
+
+class RnwWeaveRegistry : boost::noncopyable
+{
+private:
+ RnwWeaveRegistry()
+ {
+ weaveTypes_.push_back(boost::shared_ptr<RnwWeave>(new RnwSweave()));
+ weaveTypes_.push_back(boost::shared_ptr<RnwWeave>(new RnwKnitr()));
+ }
+ friend const RnwWeaveRegistry& weaveRegistry();
+
+public:
+ typedef std::vector<boost::shared_ptr<RnwWeave> > RnwWeaveTypes;
+
+
+public:
+ std::string printableTypeNames() const
+ {
+ std::string str;
+ for (std::size_t i=0; i<weaveTypes_.size(); i++)
+ {
+ str.append(weaveTypes_[i]->name());
+ if (i != (weaveTypes_.size() - 1))
+ str.append(", ");
+ if (i == (weaveTypes_.size() - 2))
+ str.append("and ");
+ }
+ return str;
+ }
+
+ RnwWeaveTypes weaveTypes() const { return weaveTypes_; }
+
+ boost::shared_ptr<RnwWeave> findTypeIgnoreCase(const std::string& name)
+ const
+ {
+ BOOST_FOREACH(boost::shared_ptr<RnwWeave> weaveType, weaveTypes_)
+ {
+ if (boost::algorithm::iequals(weaveType->name(), name))
+ return weaveType;
+ }
+
+ return boost::shared_ptr<RnwWeave>();
+ }
+
+private:
+ RnwWeaveTypes weaveTypes_;
+};
+
+
+const RnwWeaveRegistry& weaveRegistry()
+{
+ static RnwWeaveRegistry instance;
+ return instance;
+}
+
+std::string weaveTypeForFile(const core::tex::TexMagicComments& magicComments)
+{
+ // first see if the file contains an rnw weave magic comment
+ BOOST_FOREACH(const core::tex::TexMagicComment& mc, magicComments)
+ {
+ if (boost::algorithm::iequals(mc.scope(), "rnw") &&
+ boost::algorithm::iequals(mc.variable(), "weave"))
+ {
+ return mc.value();
+ }
+ }
+
+ // if we didn't find a directive then inspect project & global config
+ if (projects::projectContext().hasProject())
+ return projects::projectContext().config().defaultSweaveEngine;
+ else
+ return userSettings().defaultSweaveEngine();
+}
+
+
+void onWeaveProcessExit(boost::shared_ptr<RnwWeave> pRnwWeave,
+ int exitCode,
+ const std::string& output,
+ const FilePath& rnwPath,
+ const CompletedFunction& onCompleted)
+{
+ if (exitCode == EXIT_SUCCESS)
+ {
+ // pickup concordance if there is any
+ rnw_concordance::Concordances concordances;
+ Error error = rnw_concordance::readIfExists(rnwPath, &concordances);
+ if (error)
+ LOG_ERROR(error);
+
+ // return success
+ onCompleted(Result::success(concordances));
+ }
+ else
+ {
+ // parse for errors
+ core::tex::LogEntries entries;
+ Error error = pRnwWeave->parseOutputForErrors(output, rnwPath, &entries);
+ if (error)
+ LOG_ERROR(error);
+
+ if (!entries.empty())
+ {
+ onCompleted(Result::error(entries));
+ }
+ else
+ {
+ // don't return an error message because the process almost
+ // certainly already printed something to stderr
+ onCompleted(Result::error(std::string()));
+ }
+ }
+}
+
+} // anonymous namespace
+
+void runTangle(const std::string& filePath, const std::string& rnwWeave)
+{
+ using namespace module_context;
+ boost::shared_ptr<RnwWeave> pWeave =
+ weaveRegistry().findTypeIgnoreCase(rnwWeave);
+ if (!pWeave)
+ {
+ consoleWriteError("Unknown Rnw weave type: " + rnwWeave + "\n");
+ }
+ else
+ {
+ Error error = pWeave->tangle(filePath);
+ if (error)
+ consoleWriteError(r::endUserErrorMessage(error) + "\n");
+ }
+}
+
+void runWeave(const core::FilePath& rnwPath,
+ const std::string& encoding,
+ const core::tex::TexMagicComments& magicComments,
+ const boost::function<void(const std::string&)>& onOutput,
+ const CompletedFunction& onCompleted)
+{
+ // remove existing concordance file (if any)
+ rnw_concordance::removePrevious(rnwPath);
+
+ // get the R bin dir
+ FilePath rBin;
+ Error error = rBinDir(&rBin);
+ if (error)
+ {
+ LOG_ERROR(error);
+ onCompleted(Result::error(error.summary()));
+ return;
+ }
+
+ // R exe path differs by platform
+#ifdef _WIN32
+ FilePath rBinPath = rBin.complete("Rterm.exe");
+#else
+ FilePath rBinPath = rBin.complete("R");
+#endif
+
+ // determine the active sweave engine
+ std::string weaveType = weaveTypeForFile(magicComments);
+ boost::shared_ptr<RnwWeave> pRnwWeave = weaveRegistry()
+ .findTypeIgnoreCase(weaveType);
+
+ // run the weave
+ if (pRnwWeave)
+ {
+ std::vector<std::string> args = pRnwWeave->commandArgs(
+ rnwPath.filename(),
+ encoding);
+
+ // call back-end
+ Error error = compile_pdf_supervisor::runProgram(
+ rBinPath,
+ args,
+ core::system::Options(),
+ rnwPath.parent(),
+ onOutput,
+ boost::bind(onWeaveProcessExit,
+ pRnwWeave, _1, _2, rnwPath, onCompleted));
+ if (error)
+ {
+ LOG_ERROR(error);
+ onCompleted(Result::error(error.summary()));
+ }
+ }
+ else
+ {
+ onCompleted(Result::error(
+ "Unknown Rnw weave method '" + weaveType + "' specified (valid " +
+ "values are " + weaveRegistry().printableTypeNames() + ")"));
+ }
+}
+
+core::json::Value chunkOptions(const std::string& weaveType)
+{
+ boost::shared_ptr<RnwWeave> pRnwWeave = weaveRegistry()
+ .findTypeIgnoreCase(weaveType);
+ if (pRnwWeave)
+ return pRnwWeave->chunkOptions();
+ else
+ return core::json::Value();
+}
+
+core::json::Array supportedTypes()
+{
+ // query for list of supported types
+ json::Array array;
+ BOOST_FOREACH(boost::shared_ptr<RnwWeave> pRnwWeave,
+ weaveRegistry().weaveTypes())
+ {
+ json::Object object;
+ object["name"] = pRnwWeave->name();
+ object["package_name"] = pRnwWeave->packageName();
+ object["inject_concordance"] = pRnwWeave->injectConcordance();
+ object["uses_code_for_options"] = pRnwWeave->usesCodeForOptions();
+ object["force_echo_on_exec"] = pRnwWeave->forceEchoOnExec();
+ array.push_back(object);
+ }
+ return array;
+}
+
+void getTypesInstalledStatus(json::Object* pObj)
+{
+ // query for status of all rnw weave types
+ BOOST_FOREACH(boost::shared_ptr<RnwWeave> pRnwWeave,
+ weaveRegistry().weaveTypes())
+ {
+ std::string n = string_utils::toLower(pRnwWeave->name() + "_installed");
+ (*pObj)[n] = pRnwWeave->isInstalled();
+ }
+}
+
+} // namespace rnw_weave
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionRnwWeave.hpp b/src/cpp/session/modules/tex/SessionRnwWeave.hpp
new file mode 100644
index 0000000..5e564d3
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionRnwWeave.hpp
@@ -0,0 +1,93 @@
+/*
+ * SessionRnwWeave.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_RNW_WEAVE_HPP
+#define SESSION_MODULES_RNW_WEAVE_HPP
+
+#include <boost/function.hpp>
+
+#include <core/tex/TexLogParser.hpp>
+#include <core/tex/TexMagicComment.hpp>
+
+#include <core/json/Json.hpp>
+
+#include "SessionRnwConcordance.hpp"
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+
+namespace rnw_weave {
+
+core::json::Array supportedTypes();
+void getTypesInstalledStatus(core::json::Object* pObj);
+
+core::json::Value chunkOptions(const std::string& weaveType);
+
+struct Result
+{
+ static Result error(const std::string& errorMessage)
+ {
+ Result result;
+ result.succeeded = false;
+ result.errorMessage = errorMessage;
+ return result;
+ }
+
+ static Result error(const core::tex::LogEntries& logEntries)
+ {
+ Result result;
+ result.succeeded = false;
+ result.errorLogEntries = logEntries;
+ return result;
+ }
+
+ static Result success(
+ const tex::rnw_concordance::Concordances& concordances)
+ {
+ Result result;
+ result.succeeded = true;
+ result.concordances = concordances;
+ return result;
+ }
+
+ bool succeeded;
+ std::string errorMessage;
+ core::tex::LogEntries errorLogEntries;
+ tex::rnw_concordance::Concordances concordances;
+};
+
+typedef boost::function<void(const Result&)> CompletedFunction;
+
+void runTangle(const std::string& filePath, const std::string& rnwWeave);
+
+void runWeave(const core::FilePath& filePath,
+ const std::string& encoding,
+ const core::tex::TexMagicComments& magicComments,
+ const boost::function<void(const std::string&)>& onOutput,
+ const CompletedFunction& onCompleted);
+
+
+} // namespace rnw_weave
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_RNW_WEAVE_HPP
diff --git a/src/cpp/session/modules/tex/SessionSynctex.cpp b/src/cpp/session/modules/tex/SessionSynctex.cpp
new file mode 100644
index 0000000..e010296
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionSynctex.cpp
@@ -0,0 +1,374 @@
+/*
+ * SessionSynctex.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionSynctex.hpp"
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+
+#include <core/json/JsonRpc.hpp>
+
+#include <core/tex/TexSynctex.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+#include "SessionRnwConcordance.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace synctex {
+
+namespace {
+
+json::Value toJson(const FilePath& pdfFile,
+ const core::tex::PdfLocation& pdfLoc,
+ bool fromClick)
+{
+ if (!pdfLoc.empty())
+ {
+ json::Object pdfJson;
+ pdfJson["file"] = module_context::createAliasedPath(pdfFile);
+ pdfJson["page"] = pdfLoc.page();
+ pdfJson["x"] = pdfLoc.x();
+ pdfJson["y"] = pdfLoc.y();
+ pdfJson["width"] = pdfLoc.width();
+ pdfJson["height"] = pdfLoc.height();
+ pdfJson["from_click"] = fromClick;
+ return pdfJson;
+ }
+ else
+ {
+ return json::Value();
+ }
+}
+
+json::Value toJson(const core::tex::SourceLocation& srcLoc)
+{
+ if (!srcLoc.empty())
+ {
+ json::Object srcJson;
+ srcJson["file"] = module_context::createAliasedPath(srcLoc.file());
+ srcJson["line"] = srcLoc.line();
+ srcJson["column"] = srcLoc.column();
+ return srcJson;
+ }
+ else
+ {
+ return json::Value();
+ }
+}
+
+void applyForwardConcordance(const FilePath& mainFile,
+ core::tex::SourceLocation* pLoc)
+{
+ // skip if this isn't an Rnw
+ if (pLoc->file().extensionLowerCase() != ".rnw")
+ return;
+
+ // try to read concordance
+ using namespace tex::rnw_concordance;
+ Concordances concordances;
+ Error error = readIfExists(mainFile, &concordances);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // try to find a match
+ FileAndLine texLine = concordances.texLine(FileAndLine(pLoc->file(),
+ pLoc->line()));
+ if (!texLine.empty())
+ {
+ *pLoc = core::tex::SourceLocation(texLine.filePath(),
+ texLine.line(),
+ pLoc->column());
+ }
+}
+
+
+json::Object sourceLocationAsJson(const core::tex::SourceLocation& srcLoc,
+ bool fromClick)
+{
+ json::Object sourceLocation;
+ sourceLocation["file"] = module_context::createAliasedPath(srcLoc.file());
+ sourceLocation["line"] = srcLoc.line();
+ sourceLocation["column"] = srcLoc.column();
+ sourceLocation["from_click"] = fromClick;
+ return sourceLocation;
+}
+
+Error synctexForwardSearch(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string rootDoc;
+ json::Object sourceLocation;
+ Error error = json::readParams(request.params, &rootDoc, &sourceLocation);
+ if (error)
+ return error;
+ FilePath rootDocPath = module_context::resolveAliasedPath(rootDoc);
+
+
+ // do the search
+ json::Value pdfLocation;
+ error = forwardSearch(rootDocPath, sourceLocation, &pdfLocation);
+ if (error)
+ return error;
+
+ // return the results
+ pResponse->setResult(pdfLocation);
+
+ return Success();
+}
+
+
+void applyInverseConcordance(core::tex::SourceLocation* pLoc)
+{
+ // try to read concordance
+ using namespace tex::rnw_concordance;
+ Concordances concordances;
+ Error error = readIfExists(pLoc->file(), &concordances);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // try to find a match
+ FileAndLine rnwLine = concordances.rnwLine(FileAndLine(pLoc->file(),
+ pLoc->line()));
+ if (!rnwLine.empty())
+ {
+ *pLoc = core::tex::SourceLocation(rnwLine.filePath(),
+ rnwLine.line(),
+ pLoc->column());
+ }
+}
+
+Error rpcApplyForwardConcordance(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string rootDoc;
+ json::Object sourceLocation;
+ Error error = json::readParams(request.params, &rootDoc, &sourceLocation);
+ if (error)
+ return error;
+ FilePath rootDocPath = module_context::resolveAliasedPath(rootDoc);
+
+ // read source location
+ std::string file;
+ int line, column;
+ bool fromClick;
+ error = json::readObject(sourceLocation,
+ "file", &file,
+ "line", &line,
+ "column", &column,
+ "from_click", &fromClick);
+ if (error)
+ return error;
+
+
+ FilePath srcPath = module_context::resolveAliasedPath(file);
+
+ core::tex::SourceLocation srcLoc(srcPath, line, column);
+
+ applyForwardConcordance(rootDocPath, &srcLoc);
+
+ pResponse->setResult(sourceLocationAsJson(srcLoc, fromClick));
+
+ return Success();
+}
+
+Error rpcApplyInverseConcordance(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read source location
+ std::string file;
+ int line, column;
+ bool fromClick;
+ Error error = json::readObjectParam(request.params,
+ 0,
+ "file", &file,
+ "line", &line,
+ "column", &column,
+ "from_click", &fromClick);
+ if (error)
+ return error;
+ FilePath srcPath = module_context::resolveAliasedPath(file);
+
+ core::tex::SourceLocation srcLoc(srcPath, line, column);
+
+ applyInverseConcordance(&srcLoc);
+
+ pResponse->setResult(sourceLocationAsJson(srcLoc, fromClick));
+
+ return Success();
+}
+
+
+Error synctexInverseSearch(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ std::string file;
+ int page;
+ double x, y, width, height;
+ bool fromClick;
+ Error error = json::readObjectParam(request.params, 0,
+ "file", &file,
+ "page", &page,
+ "x", &x,
+ "y", &y,
+ "width", &width,
+ "height", &height,
+ "from_click", &fromClick);
+ if (error)
+ return error;
+ FilePath pdfPath = module_context::resolveAliasedPath(file);
+
+ core::tex::Synctex synctex;
+ if (synctex.parse(pdfPath))
+ {
+ if (!fromClick)
+ {
+ // find the top of the page content, however override it with
+ // the passed x and y coordinates since they represent the
+ // top of the user-visible content (in case the page is
+ // scrolled down from the top)
+ core::tex::PdfLocation contLoc = synctex.topOfPageContent(page);
+ x = std::max((float)x, contLoc.x());
+ y = std::max((float)y, contLoc.y());
+ }
+
+ core::tex::PdfLocation pdfLocation(page, x, y, width, height);
+
+ core::tex::SourceLocation srcLoc = synctex.inverseSearch(pdfLocation);
+ applyInverseConcordance(&srcLoc);
+
+ pResponse->setResult(toJson(srcLoc));
+ }
+ else
+ {
+ pResponse->setResult(json::Value());
+ }
+
+ return Success();
+}
+
+#ifdef _WIN32
+void rsinversePostback(const std::string& arguments,
+ const module_context::PostbackHandlerContinuation& cont)
+{
+ // crack the arguments and bind to them positionally
+ http::Fields args;
+ http::util::parseQueryString(arguments, &args);
+ if (args.size() != 2)
+ cont(EXIT_FAILURE, "Invalid number of arguments");
+ std::string sourceFile = args[0].second;
+ int line = safe_convert::stringTo<int>(args[1].second, 1);
+
+ // apply inverse concordance
+ core::tex::SourceLocation srcLoc(FilePath(sourceFile), line, 1);
+ applyInverseConcordance(&srcLoc);
+
+ // edit the file
+ ClientEvent event(client_events::kSynctexEditFile,
+ sourceLocationAsJson(srcLoc, true));
+ module_context::enqueClientEvent(event);
+
+ cont(EXIT_SUCCESS, "");
+}
+#endif
+
+} // anonymous namespace
+
+
+Error forwardSearch(const FilePath& rootFile,
+ const json::Object& sourceLocation,
+ json::Value* pPdfLocation)
+{
+ // read params
+ std::string file;
+ int line, column;
+ bool fromClick;
+ Error error = json::readObject(sourceLocation,
+ "file", &file,
+ "line", &line,
+ "column", &column,
+ "from_click", &fromClick);
+ if (error)
+ return error;
+
+ // determine input file
+ FilePath inputFile = module_context::resolveAliasedPath(file);
+
+ // determine pdf
+ FilePath pdfFile = rootFile.parent().complete(rootFile.stem() + ".pdf");
+
+ core::tex::Synctex synctex;
+ if (synctex.parse(pdfFile))
+ {
+ core::tex::SourceLocation srcLoc(inputFile, line, column);
+ applyForwardConcordance(rootFile, &srcLoc);
+
+ core::tex::PdfLocation pdfLoc = synctex.forwardSearch(srcLoc);
+ *pPdfLocation = toJson(pdfFile, pdfLoc, fromClick);
+ }
+ else
+ {
+ *pPdfLocation = json::Value();
+ }
+
+ return Success();
+}
+
+Error initialize()
+{
+ // register postback handler for sumatra pdf
+#ifdef _WIN32
+ std::string ignoredCommand; // assumes bash script invocation, we
+ // don't/can't use that for rsinverse
+ Error error = module_context::registerPostbackHandler("rsinverse",
+ rsinversePostback,
+ &ignoredCommand);
+ if (error)
+ return error ;
+
+#endif
+
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "apply_forward_concordance", rpcApplyForwardConcordance))
+ (bind(registerRpcMethod, "apply_inverse_concordance", rpcApplyInverseConcordance))
+ (bind(registerRpcMethod, "synctex_forward_search", synctexForwardSearch))
+ (bind(registerRpcMethod, "synctex_inverse_search", synctexInverseSearch))
+ ;
+ return initBlock.execute();
+}
+
+} // namespace synctex
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionSynctex.hpp b/src/cpp/session/modules/tex/SessionSynctex.hpp
new file mode 100644
index 0000000..fa674e7
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionSynctex.hpp
@@ -0,0 +1,44 @@
+/*
+ * SessionSynctex.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_TEX_SYNCTEX_HPP
+#define SESSION_MODULES_TEX_SYNCTEX_HPP
+
+#include <core/json/Json.hpp>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace synctex {
+
+// returns an object suitable for jnsi binding back into a PdfLocation
+// (or null if the search didn't succeed)
+core::Error forwardSearch(const core::FilePath& rootDocument,
+ const core::json::Object& sourceLocation,
+ core::json::Value* pPdfLocation);
+
+core::Error initialize();
+
+} // namespace synctex
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_TEX_SYNCTEX_HPP
diff --git a/src/cpp/session/modules/tex/SessionTexUtils.cpp b/src/cpp/session/modules/tex/SessionTexUtils.cpp
new file mode 100644
index 0000000..97b834b
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionTexUtils.cpp
@@ -0,0 +1,186 @@
+/*
+ * SessionTexUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionTexUtils.hpp"
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <core/system/Process.hpp>
+#include <core/system/Environment.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionCompilePdfSupervisor.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace utils {
+
+namespace {
+
+// this function attempts to emulate the behavior of tools::texi2dvi
+// in appending extra paths to TEXINPUTS, BIBINPUTS, & BSTINPUTS
+core::system::Option inputsEnvVar(const std::string& name,
+ const FilePath& extraPath,
+ bool ensureForwardSlashes)
+{
+ std::string value = core::system::getenv(name);
+ if (value.empty())
+ value = ".";
+
+ // on windows tools::texi2dvi replaces \ with / when defining the TEXINPUTS
+ // environment variable (but for BIBINPUTS and BSTINPUTS)
+#ifdef _WIN32
+ if (ensureForwardSlashes)
+ boost::algorithm::replace_all(value, "\\", "/");
+#endif
+
+ std::string sysPath = string_utils::utf8ToSystem(extraPath.absolutePath());
+ core::system::addToPath(&value, sysPath);
+ core::system::addToPath(&value, ""); // trailing : required by tex
+
+ return std::make_pair(name, value);
+}
+
+shell_utils::ShellArgs buildArgs(const shell_utils::ShellArgs& args,
+ const FilePath& texFilePath)
+{
+ shell_utils::ShellArgs procArgs;
+ procArgs << args;
+ procArgs << texFilePath.filename();
+ return procArgs;
+}
+
+void ignoreOutput(const std::string& output)
+{
+}
+
+} // anonymous namespace
+
+RTexmfPaths rTexmfPaths()
+{
+ // first determine the R share directory
+ std::string rHomeShare;
+ r::exec::RFunction rHomeShareFunc("R.home", "share");
+ Error error = rHomeShareFunc.call(&rHomeShare);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return RTexmfPaths();
+ }
+ FilePath rHomeSharePath(rHomeShare);
+ if (!rHomeSharePath.exists())
+ {
+ LOG_ERROR(core::pathNotFoundError(rHomeShare, ERROR_LOCATION));
+ return RTexmfPaths();
+ }
+
+ // R texmf path
+ FilePath rTexmfPath(rHomeSharePath.complete("texmf"));
+ if (!rTexmfPath.exists())
+ {
+ LOG_ERROR(core::pathNotFoundError(rTexmfPath.absolutePath(),
+ ERROR_LOCATION));
+ return RTexmfPaths();
+ }
+
+ // populate and return struct
+ RTexmfPaths texmfPaths;
+ texmfPaths.texInputsPath = rTexmfPath.childPath("tex/latex");
+ texmfPaths.bibInputsPath = rTexmfPath.childPath("bibtex/bib");
+ texmfPaths.bstInputsPath = rTexmfPath.childPath("bibtex/bst");
+ return texmfPaths;
+}
+
+
+// build TEXINPUTS, BIBINPUTS etc. by composing any existing value in
+// the environment (or . if none) with the R dirs in share/texmf
+core::system::Options rTexInputsEnvVars()
+{
+ core::system::Options envVars;
+ RTexmfPaths texmfPaths = rTexmfPaths();
+ if (!texmfPaths.empty())
+ {
+ envVars.push_back(inputsEnvVar("TEXINPUTS",
+ texmfPaths.texInputsPath,
+ true));
+ envVars.push_back(inputsEnvVar("BIBINPUTS",
+ texmfPaths.bibInputsPath,
+ false));
+ envVars.push_back(inputsEnvVar("BSTINPUTS",
+ texmfPaths.bstInputsPath,
+ false));
+ }
+ return envVars;
+}
+
+Error runTexCompile(const FilePath& texProgramPath,
+ const core::system::Options& envVars,
+ const shell_utils::ShellArgs& args,
+ const FilePath& texFilePath,
+ core::system::ProcessResult* pResult)
+{
+ // copy extra environment variables
+ core::system::Options env;
+ core::system::environment(&env);
+ BOOST_FOREACH(const core::system::Option& var, envVars)
+ {
+ core::system::setenv(&env, var.first, var.second);
+ }
+
+ // set options
+ core::system::ProcessOptions procOptions;
+ procOptions.terminateChildren = true;
+ procOptions.redirectStdErrToStdOut = true;
+ procOptions.environment = env;
+ procOptions.workingDir = texFilePath.parent();
+
+ // run the program
+ return core::system::runProgram(
+ string_utils::utf8ToSystem(texProgramPath.absolutePath()),
+ buildArgs(args, texFilePath),
+ "",
+ procOptions,
+ pResult);
+}
+
+core::Error runTexCompile(
+ const core::FilePath& texProgramPath,
+ const core::system::Options& envVars,
+ const core::shell_utils::ShellArgs& args,
+ const core::FilePath& texFilePath,
+ const boost::function<void(int,const std::string&)>& onExited)
+{
+ return compile_pdf_supervisor::runProgram(
+ texProgramPath,
+ buildArgs(args, texFilePath),
+ envVars,
+ texFilePath.parent(),
+ ignoreOutput,
+ onExited);
+
+}
+
+} // namespace utils
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionTexUtils.hpp b/src/cpp/session/modules/tex/SessionTexUtils.hpp
new file mode 100644
index 0000000..0871e77
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionTexUtils.hpp
@@ -0,0 +1,65 @@
+/*
+ * SessionTexUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_TEX_UTILS_HPP
+#define SESSION_MODULES_TEX_UTILS_HPP
+
+#include <core/FilePath.hpp>
+
+#include <core/system/ShellUtils.hpp>
+#include <core/system/Types.hpp>
+#include <core/system/Process.hpp>
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace utils {
+
+struct RTexmfPaths
+{
+ bool empty() const { return texInputsPath.empty(); }
+
+ core::FilePath texInputsPath;
+ core::FilePath bibInputsPath;
+ core::FilePath bstInputsPath;
+};
+
+RTexmfPaths rTexmfPaths();
+
+core::system::Options rTexInputsEnvVars();
+
+core::Error runTexCompile(const core::FilePath& texProgramPath,
+ const core::system::Options& envVars,
+ const core::shell_utils::ShellArgs& args,
+ const core::FilePath& texFilePath,
+ core::system::ProcessResult* pResult);
+
+core::Error runTexCompile(
+ const core::FilePath& texProgramPath,
+ const core::system::Options& envVars,
+ const core::shell_utils::ShellArgs& args,
+ const core::FilePath& texFilePath,
+ const boost::function<void(int,const std::string&)>& onExited);
+
+} // namespace utils
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_TEX_UTILS_HPP
diff --git a/src/cpp/session/modules/tex/SessionViewPdf.cpp b/src/cpp/session/modules/tex/SessionViewPdf.cpp
new file mode 100644
index 0000000..1d0ec0b
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionViewPdf.cpp
@@ -0,0 +1,75 @@
+/*
+ * SessionViewPdf.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionViewPdf.hpp"
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Exec.hpp>
+
+#include <core/http/Util.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace view_pdf {
+
+namespace {
+
+void handleViewPdf(const http::Request& request, http::Response* pResponse)
+{
+ // get the file path
+ FilePath filePath(request.queryParamValue("path"));
+ if (!filePath.exists())
+ {
+ pResponse->setError(http::status::NotFound, "File not found");
+ return;
+ }
+
+ // send it back
+ pResponse->setNoCacheHeaders();
+ pResponse->setFile(filePath, request);
+ pResponse->setContentType("application/pdf");
+}
+
+} // anonymous namespace
+
+std::string createViewPdfUrl(const core::FilePath& filePath)
+{
+ return "view_pdf?path=" + http::util::urlEncode(filePath.absolutePath(),
+ true);
+}
+
+Error initialize()
+{
+ // install rpc methods
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerUriHandler, "/view_pdf", handleViewPdf))
+ ;
+ return initBlock.execute();
+}
+
+} // namespace view_pdf
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
diff --git a/src/cpp/session/modules/tex/SessionViewPdf.hpp b/src/cpp/session/modules/tex/SessionViewPdf.hpp
new file mode 100644
index 0000000..9c9431e
--- /dev/null
+++ b/src/cpp/session/modules/tex/SessionViewPdf.hpp
@@ -0,0 +1,40 @@
+/*
+ * SessionViewPdf.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_MODULES_VIEW_PDF_HPP
+#define SESSION_MODULES_VIEW_PDF_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace tex {
+namespace view_pdf {
+
+std::string createViewPdfUrl(const core::FilePath& filePath);
+
+core::Error initialize();
+
+} // namespace view_pdf
+} // namespace tex
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_MODULES_VIEW_PDF_HPP
diff --git a/src/cpp/session/modules/vcs/SessionVCSCore.cpp b/src/cpp/session/modules/vcs/SessionVCSCore.cpp
new file mode 100644
index 0000000..ff91b79
--- /dev/null
+++ b/src/cpp/session/modules/vcs/SessionVCSCore.cpp
@@ -0,0 +1,37 @@
+/*
+ * SessionVCSCore.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "SessionVCSCore.hpp"
+
+#include <core/FilePath.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace source_control {
+
+VCSStatus StatusResult::getStatus(const FilePath& fileOrDirectory) const
+{
+ std::map<std::string, VCSStatus>::const_iterator found =
+ this->filesByPath_.find(fileOrDirectory.absolutePath());
+ if (found != this->filesByPath_.end())
+ return found->second;
+
+ return VCSStatus();
+}
+
+} // namespace source_control
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/vcs/SessionVCSCore.hpp b/src/cpp/session/modules/vcs/SessionVCSCore.hpp
new file mode 100644
index 0000000..984186c
--- /dev/null
+++ b/src/cpp/session/modules/vcs/SessionVCSCore.hpp
@@ -0,0 +1,95 @@
+/*
+ * SessionVCSCore.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#ifndef SESSION_VCS_CORE_HPP
+#define SESSION_VCS_CORE_HPP
+
+#include <vector>
+#include <string>
+#include <map>
+
+#include <boost/noncopyable.hpp>
+
+#include <core/json/Json.hpp>
+#include <core/FilePath.hpp>
+
+namespace session {
+namespace modules {
+namespace source_control {
+
+// The size threshold at which we warn the user that the thing they are
+// requesting might slow down the app and are they sure they want to proceed?
+const size_t WARN_SIZE = 200 * 1024;
+
+class VCSStatus
+{
+public:
+ VCSStatus(const std::string& status=std::string())
+ {
+ status_ = status;
+ }
+
+ std::string status() const { return status_; }
+ // SVN-specific
+ std::string changelist() const { return changelist_; }
+private:
+ std::string status_;
+ std::string changelist_;
+};
+
+struct FileWithStatus
+{
+ VCSStatus status;
+ core::FilePath path;
+};
+
+class StatusResult
+{
+public:
+ StatusResult(const std::vector<FileWithStatus>& files =
+ std::vector<FileWithStatus>())
+ {
+ files_ = files;
+ for (std::vector<FileWithStatus>::iterator it = files_.begin();
+ it != files_.end();
+ it++)
+ {
+ filesByPath_[it->path.absolutePath()] = it->status;
+ }
+ }
+
+ VCSStatus getStatus(const core::FilePath& fileOrDirectory) const;
+ std::vector<FileWithStatus> files() const { return files_; }
+
+private:
+ std::vector<FileWithStatus> files_;
+ std::map<std::string, VCSStatus> filesByPath_;
+};
+
+
+class FileDecorationContext : boost::noncopyable
+{
+public:
+ FileDecorationContext() {}
+ virtual ~FileDecorationContext() {}
+
+ virtual void decorateFile(const core::FilePath& filePath,
+ core::json::Object* pFileObject) = 0;
+};
+
+} // namespace source_control
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_VCS_CORE_HPP
diff --git a/src/cpp/session/modules/vcs/SessionVCSUtils.cpp b/src/cpp/session/modules/vcs/SessionVCSUtils.cpp
new file mode 100644
index 0000000..b01f64b
--- /dev/null
+++ b/src/cpp/session/modules/vcs/SessionVCSUtils.cpp
@@ -0,0 +1,206 @@
+/*
+ * SessionVCSUtils.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include "SessionVCSUtils.hpp"
+
+#include <boost/regex.hpp>
+
+#include <core/json/Json.hpp>
+
+#include <r/RUtil.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/projects/SessionProjects.hpp>
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace vcs_utils {
+
+void enqueRefreshEventWithDelay(int delay)
+{
+ // Sometimes on commit, the subsequent request contains outdated
+ // status (i.e. as if the commit had not happened yet). No idea
+ // right now what is causing this. Add a delay for commits to make
+ // sure the correct state is shown.
+
+ json::Object data;
+ data["delay"] = delay;
+ module_context::enqueClientEvent(ClientEvent(client_events::kVcsRefresh,
+ data));
+}
+
+void enqueueRefreshEvent()
+{
+ enqueRefreshEventWithDelay(0);
+}
+
+core::json::Object processResultToJson(
+ const core::system::ProcessResult& result)
+{
+ core::json::Object obj;
+ obj["output"] = result.stdOut;
+ obj["exit_code"] = result.exitStatus;
+ return obj;
+}
+
+FilePath fileFilterPath(const json::Value& fileFilterJson)
+{
+ if (json::isType<std::string>(fileFilterJson))
+ {
+ // get the underlying file path
+ std::string aliasedPath= fileFilterJson.get_str();
+ return module_context::resolveAliasedPath(aliasedPath);
+ }
+ else
+ {
+ return FilePath();
+ }
+}
+
+void splitMessage(const std::string message,
+ std::string* pSubject,
+ std::string* pDescription)
+{
+ boost::smatch match;
+ if (!boost::regex_match(message,
+ match,
+ boost::regex("(.*?)(\r?\n)+(.*)")))
+ {
+ *pSubject = message;
+ *pDescription = std::string();
+ }
+ else
+ {
+ *pSubject = match[1];
+ *pDescription = match[3];
+ }
+}
+
+std::string convertToUtf8(const std::string& content, bool allowSubst)
+{
+ std::string output;
+ Error error = module_context::convertToUtf8(
+ content,
+ projects::projectContext().defaultEncoding(),
+ allowSubst,
+ &output);
+ if (error)
+ {
+ return content;
+ }
+ else
+ {
+ return output;
+ }
+}
+
+// Transcode the contents of a diff while leaving the structure of the diff
+// untouched (including filenames and other non-diff content).
+// This is implemented using a quick and dirty regex instead of with a proper
+// diff parser, so there might be edge cases where stuff gets transcoded that
+// should not be.
+std::string convertDiff(const std::string& diff,
+ const std::string& fromEncoding,
+ const std::string& toEncoding,
+ bool allowSubst,
+ bool* pSuccess)
+{
+ if (pSuccess)
+ *pSuccess = true;
+
+ if (fromEncoding == toEncoding)
+ return diff;
+
+ if ((fromEncoding.empty() || fromEncoding == "UTF-8") &&
+ (toEncoding.empty() || toEncoding == "UTF-8"))
+ {
+ return diff;
+ }
+
+ if (pSuccess)
+ *pSuccess = false;
+
+ std::string result;
+
+ std::string transcoded;
+ Error error;
+
+ std::string::const_iterator lastMatchEnd = diff.begin();
+
+ boost::regex contentLine("^(?:[+\\- ]|(?:@@[+\\-,\\d ]+@@))(.+?)$");
+ boost::sregex_iterator iter(diff.begin(), diff.end(), contentLine,
+ boost::regex_constants::match_not_dot_newline);
+ boost::sregex_iterator end;
+ for (; iter != end; iter++)
+ {
+ const boost::smatch m = *iter;
+
+ // Copy any lines we skipped over in getting here
+ std::copy(m.prefix().first, m.prefix().second,
+ std::back_inserter(result));
+
+ std::string line = std::string(m[0].first, m[0].second);
+ if (boost::algorithm::starts_with(line, "+++ ") ||
+ boost::algorithm::starts_with(line, "--- "))
+ {
+ // This is a +++ or --- line, leave it alone
+ std::copy(m[0].first, m[0].second, std::back_inserter(result));
+ }
+ else
+ {
+ // This is a content line, replace it!
+
+ // Copy the leading part of the match verbatim
+ std::copy(m[0].first, m[1].first, std::back_inserter(result));
+
+ transcoded.clear();
+ error = r::util::iconvstr(std::string(m[1].first, m[1].second),
+ fromEncoding,
+ toEncoding,
+ allowSubst,
+ &transcoded);
+ if (error)
+ return diff;
+
+ // Don't allow transcoding to break diff semantics, which would happen if
+ // new lines were introduced
+ if (transcoded.find('\n') != std::string::npos)
+ return diff;
+
+ std::copy(transcoded.begin(), transcoded.end(),
+ std::back_inserter(result));
+
+ // This should never copy any characters with the regex as it is
+ // written today, but keeping it for symmetry.
+ std::copy(m[1].second, m[0].second, std::back_inserter(result));
+ }
+
+ lastMatchEnd = m[0].second;
+ }
+
+ // Copy the last set of lines we skipped over (or if there were no matches,
+ // then we're actually copying the entire diff)
+ std::copy(lastMatchEnd, diff.end(), std::back_inserter(result));
+
+ if (pSuccess)
+ *pSuccess = true;
+
+ return result;
+}
+
+} // namespace vcs_utils
+} // namespace modules
+} // namespace session
diff --git a/src/cpp/session/modules/vcs/SessionVCSUtils.hpp b/src/cpp/session/modules/vcs/SessionVCSUtils.hpp
new file mode 100644
index 0000000..51ae15c
--- /dev/null
+++ b/src/cpp/session/modules/vcs/SessionVCSUtils.hpp
@@ -0,0 +1,76 @@
+/*
+ * SessionVCSUtils.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_VCS_UTILS_HPP
+#define SESSION_VCS_UTILS_HPP
+
+#include <boost/noncopyable.hpp>
+
+#include <core/json/Json.hpp>
+#include <core/system/Process.hpp>
+
+namespace session {
+namespace modules {
+namespace vcs_utils {
+
+void enqueRefreshEventWithDelay(int delay);
+void enqueueRefreshEvent();
+
+core::json::Object processResultToJson(
+ const core::system::ProcessResult& result);
+
+core::FilePath fileFilterPath(const core::json::Value& fileFilterJson);
+
+void splitMessage(const std::string message,
+ std::string* pSubject,
+ std::string* pDescription);
+
+// If no invalid byte ranges are encountered, then everything will be converted
+// from project encoding to UTF-8, regardless of allowSubst value.
+//
+// If allowSubst is true, and invalid byte ranges are encountered, they will
+// be replaced with ? and any valid byte ranges will be converted from project
+// encoding to UTF-8.
+//
+// If allowSubst is false, and invalid byte ranges are encountered, the entire
+// string is returned unchanged.
+std::string convertToUtf8(const std::string& content, bool allowSubst);
+
+std::string convertDiff(const std::string& diff,
+ const std::string& fromEncoding,
+ const std::string& toEncoding,
+ bool allowSubst,
+ bool* pSuccess=NULL);
+
+struct RefreshOnExit : public boost::noncopyable
+{
+ ~RefreshOnExit()
+ {
+ try
+ {
+ enqueueRefreshEvent();
+ }
+ catch(...)
+ {
+ }
+ }
+};
+
+} // namespace vcs_utils
+} // namespace modules
+} // namespace session
+
+#endif // SESSION_VCS_UTILS_HPP
+
diff --git a/src/cpp/session/postback/CMakeLists.txt b/src/cpp/session/postback/CMakeLists.txt
new file mode 100644
index 0000000..3ceb3cc
--- /dev/null
+++ b/src/cpp/session/postback/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (POSTBACK)
+
+# include files
+file(GLOB_RECURSE POSTBACK_HEADER_FILES "*.h*")
+
+# source files
+set(POSTBACK_SOURCE_FILES
+ PostbackMain.cpp
+ PostbackOptions.cpp
+)
+
+# set include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+ ${SESSION_SOURCE_DIR}/include
+)
+
+# define executable
+add_executable(rpostback ${POSTBACK_SOURCE_FILES} ${POSTBACK_HEADER_FILES})
+
+# set link dependencies
+target_link_libraries(rpostback
+ rstudio-core
+)
+
+# configure postback scripts for development mode
+set(POSTBACK_SCRIPT_DIR ${CMAKE_CURRENT_BINARY_DIR}/postback)
+file(MAKE_DIRECTORY ${POSTBACK_SCRIPT_DIR})
+configure_file(rpostback-editfile ${POSTBACK_SCRIPT_DIR}/rpostback-editfile)
+configure_file(rpostback-pdfviewer ${POSTBACK_SCRIPT_DIR}/rpostback-pdfviewer)
+configure_file(rpostback-gitssh ${POSTBACK_SCRIPT_DIR}/rpostback-gitssh)
+configure_file(rpostback-askpass ${POSTBACK_SCRIPT_DIR}/rpostback-askpass)
+configure_file(askpass-passthrough ${POSTBACK_SCRIPT_DIR}/askpass-passthrough)
+
+# installation rules
+if(NOT RSTUDIO_SESSION_WIN64)
+ install(TARGETS rpostback DESTINATION ${RSTUDIO_INSTALL_BIN})
+ file(GLOB POSTBACK_SCRIPTS "rpostback-*")
+ set(POSTBACK_SCRIPTS ${POSTBACK_SCRIPTS} "askpass-passthrough")
+ install(PROGRAMS ${POSTBACK_SCRIPTS}
+ DESTINATION ${RSTUDIO_INSTALL_BIN}/postback)
+endif()
+
+
+
diff --git a/src/cpp/session/postback/PostbackMain.cpp b/src/cpp/session/postback/PostbackMain.cpp
new file mode 100644
index 0000000..5c3df15
--- /dev/null
+++ b/src/cpp/session/postback/PostbackMain.cpp
@@ -0,0 +1,124 @@
+/*
+ * PostbackMain.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#if defined(_WIN32)
+// Necessary to avoid compile error on Win x64
+#include <winsock2.h>
+#endif
+
+#include <iostream>
+
+#include <core/Error.hpp>
+#include <core/FilePath.hpp>
+#include <core/Log.hpp>
+#include <core/ProgramStatus.hpp>
+#include <core/SafeConvert.hpp>
+
+#include <core/system/System.hpp>
+#include <core/system/Environment.hpp>
+
+#include <core/http/Request.hpp>
+#include <core/http/Response.hpp>
+#if !defined(_WIN32)
+#include <core/http/LocalStreamBlockingClient.hpp>
+#else
+#include <core/http/NamedPipeBlockingClient.hpp>
+#endif
+
+#include <session/SessionConstants.hpp>
+#if !defined(_WIN32)
+#include <session/SessionLocalStreams.hpp>
+#endif
+
+#include "PostbackOptions.hpp"
+
+using namespace core ;
+using namespace session::postback ;
+
+int exitFailure(const Error& error)
+{
+ LOG_ERROR(error);
+ return EXIT_FAILURE;
+}
+
+Error sendRequest(http::Request* pRequest, http::Response* pResponse)
+{
+#ifdef _WIN32
+ // get local peer
+ std::string pipeName = core::system::getenv("RS_LOCAL_PEER");
+ pRequest->setHeader("X-Shared-Secret",
+ core::system::getenv("RS_SHARED_SECRET"));
+ return http::sendRequest(pipeName,
+ *pRequest,
+ http::ConnectionRetryProfile(
+ boost::posix_time::seconds(10),
+ boost::posix_time::milliseconds(50)),
+ pResponse);
+#else
+ // determine stream path
+ std::string userIdentity = core::system::getenv(kRStudioUserIdentity);
+ FilePath streamPath = session::local_streams::streamPath(userIdentity);
+
+ return http::sendRequest(streamPath, *pRequest, pResponse);
+#endif
+
+}
+
+int main(int argc, char * const argv[])
+{
+ try
+ {
+ // initialize log
+ initializeSystemLog("rpostback", core::system::kLogLevelWarning);
+
+ // ignore SIGPIPE
+ Error error = core::system::ignoreSignal(core::system::SigPipe);
+ if (error)
+ LOG_ERROR(error);
+
+ // read program options
+ Options& options = session::postback::options();
+ ProgramStatus status = options.read(argc, argv);
+ if ( status.exit() )
+ return status.exitCode() ;
+
+ // determine postback uri
+ std::string uri = std::string(kLocalUriLocationPrefix kPostbackUriScope) +
+ options.command();
+
+ // build postback request
+ http::Request request;
+ request.setMethod("POST");
+ request.setUri(uri);
+ request.setHeader("Accept", "*/*");
+ request.setHeader("Connection", "close");
+ request.setBody(options.argument());
+
+ // send it
+ http::Response response;
+ error = sendRequest(&request, &response);
+ if (error)
+ return exitFailure(error);
+
+ std::string exitCode = response.headerValue(kPostbackExitCodeHeader);
+ std::cout << response.body();
+ return safe_convert::stringTo<int>(exitCode, EXIT_FAILURE);
+ }
+ CATCH_UNEXPECTED_EXCEPTION
+
+ // if we got this far we had an unexpected exception
+ return EXIT_FAILURE ;
+}
+
diff --git a/src/cpp/session/postback/PostbackOptions.cpp b/src/cpp/session/postback/PostbackOptions.cpp
new file mode 100644
index 0000000..6e00911
--- /dev/null
+++ b/src/cpp/session/postback/PostbackOptions.cpp
@@ -0,0 +1,62 @@
+/*
+ * PostbackOptions.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "PostbackOptions.hpp"
+
+#include <core/ProgramStatus.hpp>
+#include <core/ProgramOptions.hpp>
+
+using namespace core ;
+
+namespace session {
+namespace postback {
+
+Options& options()
+{
+ static Options instance ;
+ return instance ;
+}
+
+ProgramStatus Options::read(int argc, char * const argv[])
+{
+ using namespace boost::program_options ;
+
+ // postback includes command and optional argument
+ options_description postback("postback");
+ postback.add_options()
+ ("command",
+ value<std::string>(&command_),
+ "command to postback")
+ ("argument",
+ value<std::string>(&argument_)->default_value(""),
+ "argument to postback");
+
+ // define program options (allow positional specification)
+ program_options::OptionsDescription optionsDesc(programName_);
+ optionsDesc.commandLine.add(postback);
+ optionsDesc.positionalOptions.add("command", 1);
+ optionsDesc.positionalOptions.add("argument", 1);
+
+ // read options
+ ProgramStatus status = core::program_options::read(optionsDesc, argc, argv);
+ if (status.exit())
+ return status;
+
+ // return status
+ return ProgramStatus::run();
+}
+
+} // namespace postback
+} // namespace session
diff --git a/src/cpp/session/postback/PostbackOptions.hpp b/src/cpp/session/postback/PostbackOptions.hpp
new file mode 100644
index 0000000..b1ae552
--- /dev/null
+++ b/src/cpp/session/postback/PostbackOptions.hpp
@@ -0,0 +1,64 @@
+/*
+ * PostbackOptions.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef POSTBACK_OPTIONS_HPP
+#define POSTBACK_OPTIONS_HPP
+
+#include <string>
+
+#include <boost/utility.hpp>
+
+namespace core {
+ class ProgramStatus;
+}
+
+namespace session {
+namespace postback {
+
+// singleton
+class Options ;
+Options& options();
+
+class Options : boost::noncopyable
+{
+private:
+ Options() {} ;
+ friend Options& options();
+ // COPYING: boost::noncopyable
+
+public:
+ core::ProgramStatus read(int argc, char * const argv[]);
+
+ std::string command() const
+ {
+ return std::string(command_.c_str());
+ }
+
+ std::string argument() const
+ {
+ return std::string(argument_.c_str());
+ }
+
+private:
+ std::string programName_;
+ std::string command_;
+ std::string argument_;
+};
+
+} // namespace postback
+} // namespace session
+
+#endif // POSTBACK_OPTIONS_HPP
+
diff --git a/src/cpp/session/postback/askpass-passthrough b/src/cpp/session/postback/askpass-passthrough
new file mode 100755
index 0000000..007c5ee
--- /dev/null
+++ b/src/cpp/session/postback/askpass-passthrough
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+#
+# askpass-passthrough
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+echo $__ASKPASS_PASSTHROUGH_RESULT
diff --git a/src/cpp/session/postback/rpostback-askpass b/src/cpp/session/postback/rpostback-askpass
new file mode 100755
index 0000000..944f55a
--- /dev/null
+++ b/src/cpp/session/postback/rpostback-askpass
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+#
+# rpostback-askpass
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+set -e
+
+"$RS_RPOSTBACK_PATH" askpass "$@"
diff --git a/src/cpp/session/postback/rpostback-editfile b/src/cpp/session/postback/rpostback-editfile
new file mode 100755
index 0000000..63fcf88
--- /dev/null
+++ b/src/cpp/session/postback/rpostback-editfile
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+#
+# rpostback-editfile
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+set -e
+
+"$RS_RPOSTBACK_PATH" editfile $1
diff --git a/src/cpp/session/postback/rpostback-gitssh b/src/cpp/session/postback/rpostback-gitssh
new file mode 100755
index 0000000..8f3751e
--- /dev/null
+++ b/src/cpp/session/postback/rpostback-gitssh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+#
+# rpostback-gitssh
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+set -e
+
+SSH_AGENT_SCRIPT=$("$RS_RPOSTBACK_PATH" gitssh 2> /dev/null)
+eval "$SSH_AGENT_SCRIPT" &> /dev/null
+ssh -o StrictHostKeyChecking=no $*
diff --git a/src/cpp/session/postback/rpostback-pdfviewer b/src/cpp/session/postback/rpostback-pdfviewer
new file mode 100755
index 0000000..b320e81
--- /dev/null
+++ b/src/cpp/session/postback/rpostback-pdfviewer
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+#
+# rpostback-pdfviewer
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+set -e
+
+"$RS_RPOSTBACK_PATH" pdfviewer $1
diff --git a/src/cpp/session/projects/SessionProjectContext.cpp b/src/cpp/session/projects/SessionProjectContext.cpp
new file mode 100644
index 0000000..678f607
--- /dev/null
+++ b/src/cpp/session/projects/SessionProjectContext.cpp
@@ -0,0 +1,702 @@
+/*
+ * SessionProjectContext.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/projects/SessionProjects.hpp>
+
+#include <map>
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include <core/FileSerializer.hpp>
+#include <core/r_util/RProjectFile.hpp>
+
+#include <core/system/FileMonitor.hpp>
+
+#include <r/RExec.hpp>
+
+#include <session/SessionUserSettings.hpp>
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionProjectFirstRun.hpp"
+
+using namespace core;
+
+namespace session {
+namespace projects {
+
+namespace {
+
+bool canWriteToProjectDir(const FilePath& projectDirPath)
+{
+ FilePath testFile = projectDirPath.complete(core::system::generateUuid());
+ Error error = core::writeStringToFile(testFile, "test");
+ if (error)
+ {
+ return false;
+ }
+ else
+ {
+ error = testFile.removeIfExists();
+ if (error)
+ LOG_ERROR(error);
+
+ return true;
+ }
+}
+
+// access to project settings
+std::string readSetting(const char * const settingName);
+void writeSetting(const char * const settingName, const std::string& value);
+
+} // anonymous namespace
+
+
+Error computeScratchPath(const FilePath& projectFile, FilePath* pScratchPath)
+{
+ // ensure project user dir
+ FilePath projectUserDir = projectFile.parent().complete(".Rproj.user");
+ if (!projectUserDir.exists())
+ {
+ // create
+ Error error = projectUserDir.ensureDirectory();
+ if (error)
+ return error;
+
+ // mark hidden if we are on win32
+#ifdef _WIN32
+ error = core::system::makeFileHidden(projectUserDir);
+ if (error)
+ return error;
+#endif
+ }
+
+ // now add context id to form scratch path
+ FilePath scratchPath = projectUserDir.complete(userSettings().contextId());
+ Error error = scratchPath.ensureDirectory();
+ if (error)
+ return error;
+
+ // return the path
+ *pScratchPath = scratchPath;
+ return Success();
+}
+
+FilePath ProjectContext::oldScratchPath() const
+{
+ // start from the standard .Rproj.user dir
+ FilePath projectUserDir = directory().complete(".Rproj.user");
+ if (!projectUserDir.exists())
+ return FilePath();
+
+ // add username if we can get one
+ std::string username = core::system::username();
+ if (!username.empty())
+ projectUserDir = projectUserDir.complete(username);
+
+ // if this path doesn't exist then bail
+ if (!projectUserDir.exists())
+ return FilePath();
+
+ // see if an old scratch path using the old contextId is present
+ // and if so return it
+ FilePath oldPath = projectUserDir.complete(userSettings().oldContextId());
+ if (oldPath.exists())
+ return oldPath;
+ else
+ return FilePath();
+}
+
+// NOTE: this function is called very early in the process lifetime (from
+// session::projects::startup) so can only have limited dependencies.
+// specifically, it can rely on userSettings() being available, but can
+// definitely NOT rely on calling into R. For initialization related tasks
+// that need to run after R is available use the implementation of the
+// initialize method (below)
+Error ProjectContext::startup(const FilePath& projectFile,
+ std::string* pUserErrMsg)
+{
+ // test for project file existence
+ if (!projectFile.exists())
+ {
+ *pUserErrMsg = "the project file does not exist";
+ return pathNotFoundError(projectFile.absolutePath(), ERROR_LOCATION);
+ }
+
+ // test for writeabilty of parent
+ if (!canWriteToProjectDir(projectFile.parent()))
+ {
+ *pUserErrMsg = "the project directory is not writeable";
+ return systemError(boost::system::errc::permission_denied,
+ ERROR_LOCATION);
+ }
+
+ // calculate project scratch path
+ FilePath scratchPath;
+ Error error = computeScratchPath(projectFile, &scratchPath);
+ if (error)
+ {
+ *pUserErrMsg = "unable to initialize project - " + error.summary();
+ return error;
+ }
+
+ // read project file config
+ bool providedDefaults;
+ r_util::RProjectConfig config;
+ error = r_util::readProjectFile(projectFile,
+ defaultConfig(),
+ &config,
+ &providedDefaults,
+ pUserErrMsg);
+ if (error)
+ return error;
+
+ // update package install args with new defaults (one time only)
+ const char * kUpdatePackageInstallDefault = "update-pkg-install-default";
+ if (readSetting(kUpdatePackageInstallDefault).empty())
+ {
+ writeSetting(kUpdatePackageInstallDefault, "1");
+ if (r_util::updateSetPackageInstallArgsDefault(&config))
+ providedDefaults = true;
+ }
+
+ // if we provided defaults then re-write the project file
+ // with the defaults
+ if (providedDefaults)
+ {
+ error = r_util::writeProjectFile(projectFile, config);
+ if (error)
+ LOG_ERROR(error);
+ }
+
+ // initialize members
+ file_ = projectFile;
+ directory_ = file_.parent();
+ scratchPath_ = scratchPath;
+ config_ = config;
+
+ // assume true so that the initial files pane listing doesn't register
+ // a duplicate monitor. if it turns out to be false then this can be
+ // repaired by a single refresh of the files pane
+ hasFileMonitor_ = config_.enableCodeIndexing;
+
+ // return success
+ return Success();
+
+}
+
+void ProjectContext::augmentRbuildignore()
+{
+ if (r_util::isPackageDirectory(directory()))
+ {
+ // constants
+ const char * const kIgnoreRproj = "^.*\\.Rproj$";
+ const char * const kIgnoreRprojUser = "^\\.Rproj\\.user$";
+
+ // create the file if it doesn't exists
+ FilePath rbuildIgnorePath = directory().childPath(".Rbuildignore");
+ if (!rbuildIgnorePath.exists())
+ {
+ Error error = writeStringToFile(rbuildIgnorePath,
+ kIgnoreRproj + std::string("\n") +
+ kIgnoreRprojUser + std::string("\n"),
+ string_utils::LineEndingNative);
+ if (error)
+ LOG_ERROR(error);
+ }
+ else
+ {
+ // if .Rbuildignore exists, add *.Rproj and .Rproj.user unless
+ // they are already there
+
+ std::string strIgnore;
+ Error error = core::readStringFromFile(
+ rbuildIgnorePath,
+ &strIgnore,
+ string_utils::LineEndingPosix);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // NOTE: we don't search for the full kIgnoreRproj to account
+ // for previous less precisely specified .Rproj entries
+ bool hasRProj = strIgnore.find("\\.Rproj$") != std::string::npos;
+ bool hasRProjUser = strIgnore.find(kIgnoreRprojUser) != std::string::npos;
+
+ if (hasRProj && hasRProjUser)
+ return;
+
+ bool addExtraNewline = strIgnore.size() > 0
+ && strIgnore[strIgnore.size() - 1] != '\n';
+
+ if (addExtraNewline)
+ strIgnore += "\n";
+ if (!hasRProj)
+ strIgnore += kIgnoreRproj + std::string("\n");
+ if (!hasRProjUser)
+ strIgnore += kIgnoreRprojUser + std::string("\n");
+ error = core::writeStringToFile(rbuildIgnorePath,
+ strIgnore,
+ string_utils::LineEndingNative);
+ if (error)
+ LOG_ERROR(error);
+ }
+ }
+}
+
+
+Error ProjectContext::initialize()
+{
+ if (hasProject())
+ {
+ // read build options for the side effect of updating buildOptions_
+ RProjectBuildOptions buildOptions;
+ Error error = readBuildOptions(&buildOptions);
+ if (error)
+ LOG_ERROR(error);
+
+ // compute the build target path
+ updateBuildTargetPath();
+
+ // update package info
+ updatePackageInfo();
+
+ // compute the default encoding
+ updateDefaultEncoding();
+
+ // augmewnt .Rbuildignore if this is a package
+ augmentRbuildignore();
+
+ // subscribe to deferred init (for initializing our file monitor)
+ if (config().enableCodeIndexing)
+ {
+ module_context::events().onDeferredInit.connect(
+ boost::bind(&ProjectContext::onDeferredInit, this, _1));
+ }
+ }
+
+ return Success();
+}
+
+
+namespace {
+const char * const kLastProjectPath = "last-project-path";
+
+
+// NOTE: the HttpConnectionListener relies on this path as well as the
+// kNextSessionProject constant in order to write the next session project
+// in the case of a forced abort (the two implementations are synchronized
+// using constants so that the connection listener doesn't call into modules
+// that are single threaded by convention
+FilePath settingsPath()
+{
+ FilePath settingsPath = session::options().userScratchPath().complete(
+ kProjectsSettings);
+ Error error = settingsPath.ensureDirectory();
+ if (error)
+ LOG_ERROR(error);
+
+ return settingsPath;
+}
+
+std::string readSetting(const char * const settingName)
+{
+ FilePath readPath = settingsPath().complete(settingName);
+ if (readPath.exists())
+ {
+ std::string value;
+ Error error = core::readStringFromFile(readPath, &value);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return std::string();
+ }
+ boost::algorithm::trim(value);
+ return value;
+ }
+ else
+ {
+ return std::string();
+ }
+}
+
+void writeSetting(const char * const settingName, const std::string& value)
+{
+ FilePath writePath = settingsPath().complete(settingName);
+ Error error = core::writeStringToFile(writePath, value);
+ if (error)
+ LOG_ERROR(error);
+}
+
+} // anonymous namespace
+
+std::string ProjectContext::nextSessionProject() const
+{
+ return readSetting(kNextSessionProject);
+}
+
+void ProjectContext::setNextSessionProject(
+ const std::string& nextSessionProject)
+{
+ writeSetting(kNextSessionProject, nextSessionProject);
+}
+
+
+FilePath ProjectContext::lastProjectPath() const
+{
+ std::string path = readSetting(kLastProjectPath);
+ if (!path.empty())
+ return FilePath(path);
+ else
+ return FilePath();
+}
+
+void ProjectContext::setLastProjectPath(const FilePath& lastProjectPath)
+{
+ if (!lastProjectPath.empty())
+ writeSetting(kLastProjectPath, lastProjectPath.absolutePath());
+ else
+ writeSetting(kLastProjectPath, "");
+}
+
+
+void ProjectContext::onDeferredInit(bool newSession)
+{
+ // kickoff file monitoring for this directory
+ using namespace boost;
+ core::system::file_monitor::Callbacks cb;
+ cb.onRegistered = bind(&ProjectContext::fileMonitorRegistered,
+ this, _1, _2);
+ cb.onRegistrationError = bind(&ProjectContext::fileMonitorTermination,
+ this, _1);
+ cb.onMonitoringError = bind(&ProjectContext::fileMonitorTermination,
+ this, _1);
+ cb.onFilesChanged = bind(&ProjectContext::fileMonitorFilesChanged,
+ this, _1);
+ cb.onUnregistered = bind(&ProjectContext::fileMonitorTermination,
+ this, Success());
+ core::system::file_monitor::registerMonitor(
+ directory(),
+ true,
+ module_context::fileListingFilter,
+ cb);
+}
+
+void ProjectContext::fileMonitorRegistered(
+ core::system::file_monitor::Handle handle,
+ const tree<core::FileInfo>& files)
+{
+ // update state
+ hasFileMonitor_ = true;
+
+ // notify subscribers
+ onMonitoringEnabled_(files);
+}
+
+void ProjectContext::fileMonitorFilesChanged(
+ const std::vector<core::system::FileChangeEvent>& events)
+{
+ // notify client (gwt)
+ module_context::enqueFileChangedEvents(directory(), events);
+
+ // notify subscribers
+ onFilesChanged_(events);
+}
+
+void ProjectContext::fileMonitorTermination(const Error& error)
+{
+ // always log error
+ if (error)
+ LOG_ERROR(error);
+
+ // if we have a file monitor then unwind it
+ if (hasFileMonitor_)
+ {
+ // do this only once
+ hasFileMonitor_ = false;
+
+ // notify end-user if this was an error condition
+ if (error)
+ {
+ // base error message
+ boost::system::error_code ec = error.code();
+ std::string dir = module_context::createAliasedPath(directory());
+ boost::format fmt(
+ "\nWarning message:\n"
+ "File monitoring failed for project at \"%1%\"\n"
+ "Error %2% (%3%)");
+ std::string msg = boost::str(fmt % dir % ec.value() % ec.message());
+
+ // enumeration of affected features
+ if (!monitorSubscribers_.empty())
+ msg.append("\nFeatures disabled:");
+ for(std::size_t i=0; i<monitorSubscribers_.size(); ++i)
+ {
+ if (i > 0)
+ msg.append(",");
+ msg.append(" ");
+ msg.append(monitorSubscribers_[i]);
+ }
+
+ // write to console
+ module_context::consoleWriteError(msg);
+ }
+
+ // notify subscribers
+ onMonitoringDisabled_();
+ }
+}
+
+bool ProjectContext::isMonitoringDirectory(const FilePath& dir) const
+{
+ return hasProject() && hasFileMonitor() && dir.isWithin(directory());
+}
+
+void ProjectContext::subscribeToFileMonitor(const std::string& featureName,
+ const FileMonitorCallbacks& cb)
+{
+ if (!featureName.empty())
+ monitorSubscribers_.push_back(featureName);
+
+ if (cb.onMonitoringEnabled)
+ onMonitoringEnabled_.connect(cb.onMonitoringEnabled);
+ if (cb.onFilesChanged)
+ onFilesChanged_.connect(cb.onFilesChanged);
+ if (cb.onMonitoringDisabled)
+ onMonitoringDisabled_.connect(cb.onMonitoringDisabled);
+}
+
+std::string ProjectContext::defaultEncoding() const
+{
+ return defaultEncoding_;
+}
+
+void ProjectContext::updateDefaultEncoding()
+{
+ defaultEncoding_.clear();
+ Error error = r::exec::RFunction(
+ ".rs.validateAndNormalizeEncoding",
+ config().encoding).call(&defaultEncoding_);
+ if (error)
+ LOG_ERROR(error);
+
+ // if the default encoding is empty then change to UTF-8 and
+ // and enque a warning
+ if (defaultEncoding_.empty())
+ {
+ // fallback
+ defaultEncoding_ = "UTF-8";
+
+ // enque a warning
+ json::Object msgJson;
+ msgJson["severe"] = false;
+ boost::format fmt(
+ "Project text encoding '%1%' not available (using UTF-8). "
+ "You can specify an alternate text encoding via Project Options.");
+ msgJson["message"] = boost::str(fmt % config().encoding);
+ ClientEvent event(client_events::kShowWarningBar, msgJson);
+ module_context::enqueClientEvent(event);
+ }
+}
+
+void ProjectContext::updateBuildTargetPath()
+{
+ if (config().buildType == r_util::kBuildTypeNone)
+ {
+ buildTargetPath_ = FilePath();
+ }
+ else
+ {
+ // determine the relative build target
+ std::string buildTarget;
+ if (config().buildType == r_util::kBuildTypePackage)
+ buildTarget = config().packagePath;
+ else if (config().buildType == r_util::kBuildTypeMakefile)
+ buildTarget = config().makefilePath;
+ else if (config().buildType == r_util::kBuildTypeCustom)
+ buildTarget = config().customScriptPath;
+
+ // determine the path
+ if (boost::algorithm::starts_with(buildTarget, "~/") ||
+ FilePath::isRootPath(buildTarget))
+ {
+ buildTargetPath_ = module_context::resolveAliasedPath(buildTarget);
+ }
+ else
+ {
+ buildTargetPath_= projects::projectContext().directory().childPath(
+ buildTarget);
+ }
+ }
+}
+
+void ProjectContext::updatePackageInfo()
+{
+ if (config().buildType == r_util::kBuildTypePackage)
+ {
+ Error error = packageInfo_.read(buildTargetPath());
+ if (error)
+ LOG_ERROR(error);
+ }
+}
+
+json::Object ProjectContext::uiPrefs() const
+{
+ json::Object uiPrefs;
+ uiPrefs["use_spaces_for_tab"] = config_.useSpacesForTab;
+ uiPrefs["num_spaces_for_tab"] = config_.numSpacesForTab;
+ uiPrefs["auto_append_newline"] = config_.autoAppendNewline;
+ uiPrefs["strip_trailing_whitespace"] = config_.stripTrailingWhitespace;
+ uiPrefs["default_encoding"] = defaultEncoding();
+ uiPrefs["default_sweave_engine"] = config_.defaultSweaveEngine;
+ uiPrefs["default_latex_program"] = config_.defaultLatexProgram;
+ uiPrefs["root_document"] = config_.rootDocument;
+ uiPrefs["use_roxygen"] = !config_.packageRoxygenize.empty();
+ return uiPrefs;
+}
+
+json::Array ProjectContext::openDocs() const
+{
+ json::Array openDocsJson;
+ std::vector<std::string> docs = projects::collectFirstRunDocs(file());
+ BOOST_FOREACH(const std::string& doc, docs)
+ {
+ FilePath docPath = directory().childPath(doc);
+ openDocsJson.push_back(module_context::createAliasedPath(docPath));
+ }
+ return openDocsJson;
+}
+
+r_util::RProjectConfig ProjectContext::defaultConfig()
+{
+ // setup defaults for project file
+ r_util::RProjectConfig defaultConfig;
+ defaultConfig.useSpacesForTab = userSettings().useSpacesForTab();
+ defaultConfig.numSpacesForTab = userSettings().numSpacesForTab();
+ defaultConfig.autoAppendNewline = userSettings().autoAppendNewline();
+ defaultConfig.stripTrailingWhitespace =
+ userSettings().stripTrailingWhitespace();
+ if (!userSettings().defaultEncoding().empty())
+ defaultConfig.encoding = userSettings().defaultEncoding();
+ else
+ defaultConfig.encoding = "UTF-8";
+ defaultConfig.defaultSweaveEngine = userSettings().defaultSweaveEngine();
+ defaultConfig.defaultLatexProgram = userSettings().defaultLatexProgram();
+ defaultConfig.rootDocument = std::string();
+ defaultConfig.buildType = std::string();
+ defaultConfig.tutorialPath = std::string();
+ return defaultConfig;
+}
+
+
+namespace {
+
+const char * const kVcsOverride = "activeVcsOverride";
+const char * const kSshKeyPathOverride = "sshKeyPathOverride";
+
+} // anonymous namespace
+
+FilePath ProjectContext::vcsOptionsFilePath() const
+{
+ return scratchPath().childPath("vcs_options");
+}
+
+Error ProjectContext::buildOptionsFile(Settings* pOptionsFile) const
+{
+ return pOptionsFile->initialize(scratchPath().childPath("build_options"));
+}
+
+
+Error ProjectContext::readVcsOptions(RProjectVcsOptions* pOptions) const
+{
+ core::Settings settings;
+ Error error = settings.initialize(vcsOptionsFilePath());
+ if (error)
+ return error;
+
+ std::string vcsOverride = settings.get(kVcsOverride);
+
+ pOptions->vcsOverride = module_context::normalizeVcsOverride(vcsOverride);
+
+ return Success();
+}
+
+Error ProjectContext::writeVcsOptions(const RProjectVcsOptions& options) const
+{
+ core::Settings settings;
+ Error error = settings.initialize(vcsOptionsFilePath());
+ if (error)
+ return error;
+
+ settings.beginUpdate();
+ settings.set(kVcsOverride, options.vcsOverride);
+ settings.endUpdate();
+
+ return Success();
+}
+
+Error ProjectContext::readBuildOptions(RProjectBuildOptions* pOptions)
+{
+ core::Settings optionsFile;
+ Error error = buildOptionsFile(&optionsFile);
+ if (error)
+ return error;
+
+ pOptions->makefileArgs = optionsFile.get("makefile_args");
+ pOptions->autoRoxygenizeForCheck = optionsFile.getBool(
+ "auto_roxygenize_for_check",
+ true);
+ pOptions->autoRoxygenizeForBuildPackage = optionsFile.getBool(
+ "auto_roxygenize_for_build_package",
+ true);
+ pOptions->autoRoxygenizeForBuildAndReload = optionsFile.getBool(
+ "auto_roxygenize_for_build_and_reload",
+ false);
+
+ // opportunistically sync in-memory representation to what we read from disk
+ buildOptions_ = *pOptions;
+
+ return Success();
+}
+
+Error ProjectContext::writeBuildOptions(const RProjectBuildOptions& options)
+{
+ core::Settings optionsFile;
+ Error error = buildOptionsFile(&optionsFile);
+ if (error)
+ return error;
+
+ optionsFile.beginUpdate();
+ optionsFile.set("makefile_args", options.makefileArgs);
+ optionsFile.set("auto_roxygenize_for_check",
+ options.autoRoxygenizeForCheck);
+ optionsFile.set("auto_roxygenize_for_build_package",
+ options.autoRoxygenizeForBuildPackage);
+ optionsFile.set("auto_roxygenize_for_build_and_reload",
+ options.autoRoxygenizeForBuildAndReload);
+ optionsFile.endUpdate();
+
+ // opportunistically sync in-memory representation to what we wrote to disk
+ buildOptions_ = options;
+
+ return Success();
+}
+
+
+} // namespace projects
+} // namesapce session
+
diff --git a/src/cpp/session/projects/SessionProjectFirstRun.cpp b/src/cpp/session/projects/SessionProjectFirstRun.cpp
new file mode 100644
index 0000000..5d1e81e
--- /dev/null
+++ b/src/cpp/session/projects/SessionProjectFirstRun.cpp
@@ -0,0 +1,86 @@
+/*
+ * SessionProjectFirstRun.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include "SessionProjectFirstRun.hpp"
+
+#include <boost/foreach.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SessionProjectsInternal.hpp"
+
+using namespace core;
+
+namespace session {
+namespace projects {
+
+namespace {
+
+const char* const kFirstRunDocs = "first_run_docs";
+
+} // anonymous namespace
+
+void addFirstRunDoc(const FilePath& projectFile, const std::string& doc)
+{
+ FilePath scratchPath;
+ Error error = computeScratchPath(projectFile, &scratchPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ std::ostringstream ostr;
+ ostr << doc << std::endl;
+ error = core::appendToFile(scratchPath.childPath(kFirstRunDocs), ostr.str());
+ if (error)
+ LOG_ERROR(error);
+}
+
+std::vector<std::string> collectFirstRunDocs(const FilePath& projectFile)
+{
+ // docs to return
+ std::vector<std::string> docs;
+
+ // get the scratch path
+ FilePath scratchPath;
+ Error error = computeScratchPath(projectFile, &scratchPath);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return docs;
+ }
+
+ // check for first run file
+ FilePath firstRunDocsPath = scratchPath.childPath(kFirstRunDocs);
+ if (firstRunDocsPath.exists())
+ {
+ Error error = core::readStringVectorFromFile(firstRunDocsPath, &docs);
+ if (error)
+ LOG_ERROR(error);
+
+ // remove since this is a one-time only thing
+ firstRunDocsPath.remove();
+ }
+
+ return docs;
+}
+
+} // namespace projects
+} // namesapce session
+
diff --git a/src/cpp/session/projects/SessionProjectFirstRun.hpp b/src/cpp/session/projects/SessionProjectFirstRun.hpp
new file mode 100644
index 0000000..c05e5b4
--- /dev/null
+++ b/src/cpp/session/projects/SessionProjectFirstRun.hpp
@@ -0,0 +1,35 @@
+/*
+ * SessionProjectFirstRun.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PROJECTS_PROJECT_FIRST_RUN_HPP
+#define SESSION_PROJECTS_PROJECT_FIRST_RUN_HPP
+
+#include <string>
+#include <vector>
+
+namespace core {
+ class FilePath;
+}
+
+namespace session {
+namespace projects {
+
+void addFirstRunDoc(const core::FilePath& projectFile, const std::string& doc);
+std::vector<std::string> collectFirstRunDocs(const core::FilePath& projectFile);
+
+} // namespace projects
+} // namesapce session
+
+#endif // SESSION_PROJECTS_PROJECT_FIRST_RUN_HPP
diff --git a/src/cpp/session/projects/SessionProjects.cpp b/src/cpp/session/projects/SessionProjects.cpp
new file mode 100644
index 0000000..64ab3e4
--- /dev/null
+++ b/src/cpp/session/projects/SessionProjects.cpp
@@ -0,0 +1,680 @@
+/*
+ * SessionProjects.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <session/projects/SessionProjects.hpp>
+
+#include <core/FilePath.hpp>
+#include <core/Settings.hpp>
+#include <core/Exec.hpp>
+#include <core/FileSerializer.hpp>
+#include <core/system/System.hpp>
+#include <core/r_util/RProjectFile.hpp>
+
+#include <session/SessionModuleContext.hpp>
+#include <session/SessionUserSettings.hpp>
+
+#include <r/RExec.hpp>
+#include <r/session/RSessionUtils.hpp>
+
+#include "SessionProjectFirstRun.hpp"
+#include "SessionProjectsInternal.hpp"
+
+using namespace core;
+
+namespace session {
+namespace projects {
+
+namespace {
+
+ProjectContext s_projectContext;
+
+
+void onSuspend(Settings*)
+{
+ // on suspend write out current project path as the one to use
+ // on resume. we read this back in initalize (rather than in
+ // the onResume handler) becuase we need it very early in the
+ // processes lifetime and onResume happens too late
+ s_projectContext.setNextSessionProject(
+ s_projectContext.file().absolutePath());
+}
+
+void onResume(const Settings&) {}
+
+Error getNewProjectContext(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ json::Object contextJson;
+
+ contextJson["rcpp_available"] = module_context::isPackageInstalled("Rcpp");
+
+ pResponse->setResult(contextJson);
+
+ return Success();
+}
+
+Error createProject(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read params
+ std::string projectFile;
+ json::Value newPackageJson, newShinyAppJson;
+ Error error = json::readParams(request.params,
+ &projectFile,
+ &newPackageJson,
+ &newShinyAppJson);
+ if (error)
+ return error;
+ FilePath projectFilePath = module_context::resolveAliasedPath(projectFile);
+
+ // package project
+ if (!newPackageJson.is_null())
+ {
+ // build list of code files
+ bool usingRcpp;
+ json::Array codeFilesJson;
+ Error error = json::readObject(newPackageJson.get_obj(),
+ "using_rcpp", &usingRcpp,
+ "code_files", &codeFilesJson);
+ if (error)
+ return error;
+ std::vector<FilePath> codeFiles;
+ BOOST_FOREACH(const json::Value codeFile, codeFilesJson)
+ {
+ if (!json::isType<std::string>(codeFile))
+ {
+ BOOST_ASSERT(false);
+ continue;
+ }
+
+ FilePath codeFilePath =
+ module_context::resolveAliasedPath(codeFile.get_str());
+ codeFiles.push_back(codeFilePath);
+ }
+
+ // error if the package dir already exists
+ FilePath packageDir = projectFilePath.parent();
+ if (packageDir.exists())
+ return core::fileExistsError(ERROR_LOCATION);
+
+ // create a temp dir (so we can import the list of code files)
+ FilePath tempDir = module_context::tempFile("newpkg", "dir");
+ error = tempDir.ensureDirectory();
+ if (error)
+ return error;
+
+ // copy the code files into the tempDir and build up a
+ // list of the filenames for passing to package.skeleton
+ std::vector<std::string> rFileNames, cppFileNames;
+ BOOST_FOREACH(const FilePath& codeFilePath, codeFiles)
+ {
+ FilePath targetPath = tempDir.complete(codeFilePath.filename());
+ Error error = codeFilePath.copy(targetPath);
+ if (error)
+ return error;
+
+ std::string ext = targetPath.extensionLowerCase();
+ std::string file = string_utils::utf8ToSystem(targetPath.filename());
+ if (boost::algorithm::starts_with(ext,".c"))
+ cppFileNames.push_back(file);
+ else
+ rFileNames.push_back(file);
+ }
+
+
+ // if the list of code files is empty then add an empty file
+ // with the same name as the package (but don't do this for
+ // Rcpp since it generates a hello world file)
+ if (codeFiles.empty() && !usingRcpp)
+ {
+ std::string srcFileName = packageDir.filename() + ".R";
+ FilePath srcFilePath = tempDir.complete(srcFileName);
+ Error error = core::writeStringToFile(srcFilePath, "");
+ if (error)
+ return error;
+ rFileNames.push_back(string_utils::utf8ToSystem(srcFileName));
+ }
+
+ // temporarily switch to the tempDir for package creation
+ RestoreCurrentPathScope pathScope(module_context::safeCurrentPath());
+ tempDir.makeCurrentPath();
+
+ // call package.skeleton
+
+ r::exec::RFunction pkgSkeleton(usingRcpp ?
+ "Rcpp:::Rcpp.package.skeleton" :
+ "utils:::package.skeleton");
+ pkgSkeleton.addParam("name",
+ string_utils::utf8ToSystem(packageDir.filename()));
+ pkgSkeleton.addParam("path",
+ string_utils::utf8ToSystem(packageDir.parent().absolutePath()));
+ pkgSkeleton.addParam("code_files", rFileNames);
+ if (usingRcpp && module_context::haveRcppAttributes())
+ {
+ if (!cppFileNames.empty())
+ {
+ pkgSkeleton.addParam("example_code", false);
+ pkgSkeleton.addParam("cpp_files", cppFileNames);
+ }
+ else
+ {
+ pkgSkeleton.addParam("attributes", true);
+ }
+ }
+ error = pkgSkeleton.call();
+ if (error)
+ return error;
+
+ // create the project file (allow auto-detection of the package
+ // to setup the package build type & default options)
+ r_util::RProjectConfig projConfig = ProjectContext::defaultConfig();
+ return r_util::writeProjectFile(projectFilePath, projConfig);
+ }
+
+ else if (!newShinyAppJson.is_null())
+ {
+ // error if the shiny app dir already exists
+ FilePath appDir = projectFilePath.parent();
+ if (appDir.exists())
+ return core::fileExistsError(ERROR_LOCATION);
+
+ // now create it
+ Error error = appDir.ensureDirectory();
+ if (error)
+ return error;
+
+ // copy ui.R and server.R into the project
+ const char * const kUI = "ui.R";
+ const char * const kServer = "server.R";
+ FilePath shinyDir = session::options().rResourcesPath().childPath(
+ "templates/shiny");
+ error = shinyDir.childPath(kUI).copy(appDir.childPath(kUI));
+ if (error)
+ LOG_ERROR(error);
+ error = shinyDir.childPath(kServer).copy(appDir.childPath(kServer));
+ if (error)
+ LOG_ERROR(error);
+
+ // add first run actions for the source files
+ addFirstRunDoc(projectFilePath, kUI);
+ addFirstRunDoc(projectFilePath, kServer);
+
+ // create the project file
+ return r_util::writeProjectFile(projectFilePath,
+ ProjectContext::defaultConfig());
+ }
+
+ // default project
+ else
+ {
+ // create the project directory if necessary
+ error = projectFilePath.parent().ensureDirectory();
+ if (error)
+ return error;
+
+ // create the project file
+ if (!projectFilePath.exists())
+ {
+ return r_util::writeProjectFile(projectFilePath,
+ ProjectContext::defaultConfig());
+ }
+ else
+ {
+ return Success();
+ }
+ }
+}
+
+json::Object projectConfigJson(const r_util::RProjectConfig& config)
+{
+ json::Object configJson;
+ configJson["version"] = config.version;
+ configJson["restore_workspace"] = config.restoreWorkspace;
+ configJson["save_workspace"] = config.saveWorkspace;
+ configJson["always_save_history"] = config.alwaysSaveHistory;
+ configJson["enable_code_indexing"] = config.enableCodeIndexing;
+ configJson["use_spaces_for_tab"] = config.useSpacesForTab;
+ configJson["num_spaces_for_tab"] = config.numSpacesForTab;
+ configJson["auto_append_newline"] = config.autoAppendNewline;
+ configJson["strip_trailing_whitespace"] = config.stripTrailingWhitespace;
+ configJson["default_encoding"] = config.encoding;
+ configJson["default_sweave_engine"] = config.defaultSweaveEngine;
+ configJson["default_latex_program"] = config.defaultLatexProgram;
+ configJson["root_document"] = config.rootDocument;
+ configJson["build_type"] = config.buildType;
+ configJson["package_use_devtools"] = config.packageUseDevtools;
+ configJson["package_path"] = config.packagePath;
+ configJson["package_install_args"] = config.packageInstallArgs;
+ configJson["package_build_args"] = config.packageBuildArgs;
+ configJson["package_build_binary_args"] = config.packageBuildBinaryArgs;
+ configJson["package_check_args"] = config.packageCheckArgs;
+ configJson["package_roxygenize"] = config.packageRoxygenize;
+ configJson["makefile_path"] = config.makefilePath;
+ configJson["custom_script_path"] = config.customScriptPath;
+ configJson["tutorial_path"] = config.tutorialPath;
+ return configJson;
+}
+
+json::Object projectBuildOptionsJson()
+{
+ RProjectBuildOptions buildOptions;
+ Error error = s_projectContext.readBuildOptions(&buildOptions);
+ if (error)
+ LOG_ERROR(error);
+ json::Object buildOptionsJson;
+ buildOptionsJson["makefile_args"] = buildOptions.makefileArgs;
+
+ json::Object autoRoxJson;
+ autoRoxJson["run_on_check"] = buildOptions.autoRoxygenizeForCheck;
+ autoRoxJson["run_on_package_builds"] =
+ buildOptions.autoRoxygenizeForBuildPackage;
+ autoRoxJson["run_on_build_and_reload"] =
+ buildOptions.autoRoxygenizeForBuildAndReload;
+ buildOptionsJson["auto_roxygenize_options"] = autoRoxJson;
+ return buildOptionsJson;
+}
+
+json::Object projectVcsOptionsJson()
+{
+ RProjectVcsOptions vcsOptions;
+ Error error = s_projectContext.readVcsOptions(&vcsOptions);
+ if (error)
+ LOG_ERROR(error);
+ json::Object vcsOptionsJson;
+ vcsOptionsJson["active_vcs_override"] = vcsOptions.vcsOverride;
+ return vcsOptionsJson;
+}
+
+json::Object projectVcsContextJson()
+{
+ module_context::VcsContext vcsContext = module_context::vcsContext(
+ s_projectContext.directory());
+
+ json::Object contextJson;
+ contextJson["detected_vcs"] = vcsContext.detectedVcs;
+ json::Array applicableJson;
+ BOOST_FOREACH(const std::string& vcs, vcsContext.applicableVcs)
+ {
+ applicableJson.push_back(vcs);
+ }
+ contextJson["applicable_vcs"] = applicableJson;
+
+ contextJson["svn_repository_root"] = vcsContext.svnRepositoryRoot;
+ contextJson["git_remote_origin_url"] = vcsContext.gitRemoteOriginUrl;
+
+ return contextJson;
+}
+
+json::Object projectBuildContextJson()
+{
+ json::Object contextJson;
+ contextJson["roxygen2_installed"] =
+ module_context::isPackageInstalled("roxygen2");
+ contextJson["devtools_installed"] =
+ module_context::isPackageInstalled("devtools");
+ return contextJson;
+}
+
+Error readProjectOptions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get project config json
+ json::Object configJson = projectConfigJson(s_projectContext.config());
+
+ // create project options json
+ json::Object optionsJson;
+ optionsJson["config"] = configJson;
+ optionsJson["vcs_options"] = projectVcsOptionsJson();
+ optionsJson["vcs_context"] = projectVcsContextJson();
+ optionsJson["build_options"] = projectBuildOptionsJson();
+ optionsJson["build_context"] = projectBuildContextJson();
+
+ pResponse->setResult(optionsJson);
+ return Success();
+}
+
+void setProjectConfig(const r_util::RProjectConfig& config)
+{
+ // set it
+ s_projectContext.setConfig(config);
+
+ // sync underlying R setting
+ module_context::syncRSaveAction();
+}
+
+Error rProjectBuildOptionsFromJson(const json::Object& optionsJson,
+ RProjectBuildOptions* pOptions)
+{
+ json::Object autoRoxJson;
+ Error error = json::readObject(
+ optionsJson,
+ "makefile_args", &(pOptions->makefileArgs),
+ "auto_roxygenize_options", &autoRoxJson);
+ if (error)
+ return error;
+
+ return json::readObject(
+ autoRoxJson,
+ "run_on_check", &(pOptions->autoRoxygenizeForCheck),
+ "run_on_package_builds", &(pOptions->autoRoxygenizeForBuildPackage),
+ "run_on_build_and_reload", &(pOptions->autoRoxygenizeForBuildAndReload));
+}
+
+Error rProjectVcsOptionsFromJson(const json::Object& optionsJson,
+ RProjectVcsOptions* pOptions)
+{
+ return json::readObject(
+ optionsJson,
+ "active_vcs_override", &(pOptions->vcsOverride));
+}
+
+Error writeProjectOptions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // get the project config, vcs options, and build options
+ json::Object configJson, vcsOptionsJson, buildOptionsJson;
+ Error error = json::readObjectParam(request.params, 0,
+ "config", &configJson,
+ "vcs_options", &vcsOptionsJson,
+ "build_options", &buildOptionsJson);
+ if (error)
+ return error;
+
+ // read the config
+ r_util::RProjectConfig config;
+ error = json::readObject(
+ configJson,
+ "version", &(config.version),
+ "restore_workspace", &(config.restoreWorkspace),
+ "save_workspace", &(config.saveWorkspace),
+ "always_save_history", &(config.alwaysSaveHistory),
+ "enable_code_indexing", &(config.enableCodeIndexing),
+ "use_spaces_for_tab", &(config.useSpacesForTab),
+ "num_spaces_for_tab", &(config.numSpacesForTab),
+ "default_encoding", &(config.encoding),
+ "default_sweave_engine", &(config.defaultSweaveEngine),
+ "default_latex_program", &(config.defaultLatexProgram),
+ "root_document", &(config.rootDocument));
+ if (error)
+ return error;
+
+ error = json::readObject(
+ configJson,
+ "auto_append_newline", &(config.autoAppendNewline),
+ "strip_trailing_whitespace", &(config.stripTrailingWhitespace));
+ if (error)
+ return error;
+
+ error = json::readObject(
+ configJson,
+ "build_type", &(config.buildType),
+ "package_use_devtools", &(config.packageUseDevtools),
+ "package_path", &(config.packagePath),
+ "package_install_args", &(config.packageInstallArgs),
+ "package_build_args", &(config.packageBuildArgs),
+ "package_build_binary_args", &(config.packageBuildBinaryArgs),
+ "package_check_args", &(config.packageCheckArgs),
+ "package_roxygenize", &(config.packageRoxygenize),
+ "makefile_path", &(config.makefilePath),
+ "custom_script_path", &(config.customScriptPath),
+ "tutorial_path", &(config.tutorialPath));
+ if (error)
+ return error;
+
+ // read the vcs options
+ RProjectVcsOptions vcsOptions;
+ error = rProjectVcsOptionsFromJson(vcsOptionsJson, &vcsOptions);
+ if (error)
+ return error;
+
+ // read the buld options
+ RProjectBuildOptions buildOptions;
+ error = rProjectBuildOptionsFromJson(buildOptionsJson, &buildOptions);
+ if (error)
+ return error;
+
+ // write the config
+ error = r_util::writeProjectFile(s_projectContext.file(), config);
+ if (error)
+ return error;
+
+ // set the config
+ setProjectConfig(config);
+
+ // write the vcs options
+ error = s_projectContext.writeVcsOptions(vcsOptions);
+ if (error)
+ LOG_ERROR(error);
+
+ // write the build options
+ error = s_projectContext.writeBuildOptions(buildOptions);
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+}
+
+Error writeProjectVcsOptions(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+ // read the vcs options
+ json::Object vcsOptionsJson;
+ Error error = json::readParam(request.params, 0, &vcsOptionsJson);
+ if (error)
+ return error;
+ RProjectVcsOptions vcsOptions;
+ error = rProjectVcsOptionsFromJson(vcsOptionsJson, &vcsOptions);
+ if (error)
+ return error;
+
+ // write the vcs options
+ error = s_projectContext.writeVcsOptions(vcsOptions);
+ if (error)
+ LOG_ERROR(error);
+
+ return Success();
+}
+
+
+void onQuit()
+{
+ s_projectContext.setLastProjectPath(s_projectContext.file());
+}
+
+void syncProjectFileChanges()
+{
+ // read project file config
+ bool providedDefaults;
+ std::string userErrMsg;
+ r_util::RProjectConfig config;
+ Error error = r_util::readProjectFile(s_projectContext.file(),
+ ProjectContext::defaultConfig(),
+ &config,
+ &providedDefaults,
+ &userErrMsg);
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ // set config
+ setProjectConfig(config);
+
+ // fire event to client
+ json::Object dataJson;
+ dataJson["type"] = "project";
+ dataJson["prefs"] = s_projectContext.uiPrefs();
+ ClientEvent event(client_events::kUiPrefsChanged, dataJson);
+ module_context::enqueClientEvent(event);
+}
+
+void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events)
+{
+ BOOST_FOREACH(const core::system::FileChangeEvent& event, events)
+ {
+ // if the project file changed then sync its changes
+ if (event.fileInfo().absolutePath() ==
+ s_projectContext.file().absolutePath())
+ {
+ syncProjectFileChanges();
+ break;
+ }
+ }
+}
+
+void onMonitoringDisabled()
+{
+ // NOTE: if monitoring is disabled then we can't sync changes to the
+ // project file -- we could poll for this however since it is only
+ // a conveninece to have these synced we don't do this
+}
+
+
+} // anonymous namespace
+
+
+void startup()
+{
+ // register suspend handler
+ using namespace module_context;
+ addSuspendHandler(SuspendHandler(boost::bind(onSuspend, _2), onResume));
+
+ // determine project file path
+ FilePath projectFilePath;
+
+ // see if there is a project path hard-wired for the next session
+ // (this would be used for a switch to project or for the resuming of
+ // a suspended session)
+ std::string nextSessionProject = s_projectContext.nextSessionProject();
+ FilePath lastProjectPath = s_projectContext.lastProjectPath();
+
+ // check for next session project path (see above for comment)
+ if (!nextSessionProject.empty())
+ {
+ // reset next session project path so its a one shot deal
+ s_projectContext.setNextSessionProject("");
+
+ // clear any initial context settings which may be leftover
+ // by a re-instatiation of rsession by desktop
+ session::options().clearInitialContextSettings();
+
+ // check for special "none" value (used for close project)
+ if (nextSessionProject == "none")
+ {
+ projectFilePath = FilePath();
+ }
+ else
+ {
+ projectFilePath = module_context::resolveAliasedPath(
+ nextSessionProject);
+ }
+ }
+
+ // check for envrionment variable (file association)
+ else if (!session::options().initialProjectPath().empty())
+ {
+ projectFilePath = session::options().initialProjectPath();
+ }
+
+ // check for other working dir override (implies a launch of a file
+ // but not of a project). this code path is here to prevent
+ // the next code path from executing
+ else if (!session::options().initialWorkingDirOverride().empty())
+ {
+ projectFilePath = FilePath();
+ }
+
+ // check for restore last project
+ else if (userSettings().alwaysRestoreLastProject() &&
+ !lastProjectPath.empty())
+ {
+
+ // get last project path
+ projectFilePath = lastProjectPath;
+
+ // reset it to empty so that we only attempt to load the "lastProject"
+ // a single time (this will be reset to the path below after we
+ // clear the s_projectContext.initialize)
+ s_projectContext.setLastProjectPath(FilePath());
+ }
+
+ // else no active project for this session
+ else
+ {
+ projectFilePath = FilePath();
+ }
+
+ // if we have a project file path then try to initialize the
+ // project context (show a warning to the user if we can't)
+ if (!projectFilePath.empty())
+ {
+ std::string userErrMsg;
+ Error error = s_projectContext.startup(projectFilePath, &userErrMsg);
+ if (error)
+ {
+ // log the error
+ error.addProperty("project-file", projectFilePath.absolutePath());
+ error.addProperty("user-msg", userErrMsg);
+ LOG_ERROR(error);
+
+ // enque the error
+ json::Object openProjError;
+ openProjError["project"] = module_context::createAliasedPath(
+ projectFilePath);
+ openProjError["message"] = userErrMsg;
+ ClientEvent event(client_events::kOpenProjectError, openProjError);
+ module_context::enqueClientEvent(event);
+ }
+ }
+}
+
+Error initialize()
+{
+ // call project-context initialize
+ Error error = s_projectContext.initialize();
+ if (error)
+ return error;
+
+ // subscribe to file_monitor for project file changes
+ projects::FileMonitorCallbacks cb;
+ cb.onFilesChanged = onFilesChanged;
+ cb.onMonitoringDisabled = onMonitoringDisabled;
+ s_projectContext.subscribeToFileMonitor("", cb);
+
+ // subscribe to quit for setting last project path
+ module_context::events().onQuit.connect(onQuit);
+
+ using boost::bind;
+ using namespace module_context;
+ ExecBlock initBlock ;
+ initBlock.addFunctions()
+ (bind(registerRpcMethod, "get_new_project_context", getNewProjectContext))
+ (bind(registerRpcMethod, "create_project", createProject))
+ (bind(registerRpcMethod, "read_project_options", readProjectOptions))
+ (bind(registerRpcMethod, "write_project_options", writeProjectOptions))
+ (bind(registerRpcMethod, "write_project_vcs_options", writeProjectVcsOptions))
+ ;
+ return initBlock.execute();
+}
+
+ProjectContext& projectContext()
+{
+ return s_projectContext;
+}
+
+} // namespace projects
+} // namesapce session
+
diff --git a/src/cpp/session/projects/SessionProjectsInternal.hpp b/src/cpp/session/projects/SessionProjectsInternal.hpp
new file mode 100644
index 0000000..f2ad9ba
--- /dev/null
+++ b/src/cpp/session/projects/SessionProjectsInternal.hpp
@@ -0,0 +1,39 @@
+/*
+ * SessionProjectsInternal.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PROJECTS_PROJECTS_INTERNAL_HPP
+#define SESSION_PROJECTS_PROJECTS_INTERNAL_HPP
+
+#include <string>
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace projects {
+
+void startup();
+
+core::Error initialize();
+
+core::Error computeScratchPath(const core::FilePath& projectFile,
+ core::FilePath* pScratchPath);
+
+} // namespace projects
+} // namesapce session
+
+#endif // SESSION_PROJECTS_PROJECTS_INTERNAL_HPP
diff --git a/src/cpp/session/r-ldpath.in b/src/cpp/session/r-ldpath.in
new file mode 100755
index 0000000..6639786
--- /dev/null
+++ b/src/cpp/session/r-ldpath.in
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+#
+# r-ldpath
+#
+# Copyright (C) 2009-11 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+RHOME=$1
+. $RHOME/etc/ldpaths
+echo -n $LD_LIBRARY_PATH
+
diff --git a/src/cpp/session/resources/.gitignore b/src/cpp/session/resources/.gitignore
new file mode 100644
index 0000000..6d5ee1d
--- /dev/null
+++ b/src/cpp/session/resources/.gitignore
@@ -0,0 +1 @@
+NOTICE
diff --git a/src/cpp/session/resources/R.css b/src/cpp/session/resources/R.css
new file mode 100644
index 0000000..4551f56
--- /dev/null
+++ b/src/cpp/session/resources/R.css
@@ -0,0 +1,97 @@
+/*
+ * R.css
+ *
+ * Copyright (C) 2009-11 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+body, td {
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+::selection {
+ background: rgb(181, 213, 255);
+}
+
+::-moz-selection{
+ background: rgb(181, 213, 255);
+}
+
+a:visited {
+ color: rgb(50%, 0%, 50%);
+}
+
+h1 {
+ font-size: x-large;
+}
+
+h2 {
+ font-size: x-large;
+ font-weight: normal;
+}
+
+h3 {
+ color: rgb(35%, 35%, 35%);
+}
+
+h4 {
+ color: rgb(35%, 35%, 35%);
+ font-style: italic;
+}
+
+h5 {
+ color: rgb(35%, 35%, 35%);
+}
+
+h6 {
+ color: rgb(35%, 35%, 35%);
+ font-style: italic;
+}
+
+img.toplogo {
+ vertical-align: middle;
+}
+
+img.arrow {
+ width: 30px;
+ height: 30px;
+ border: 0;
+}
+
+span.acronym {
+ font-size: small;
+}
+
+span.env {
+ font-family: monospace;
+}
+
+span.file {
+ font-family: monospace;
+}
+
+span.option {
+ font-family: monospace;
+}
+
+span.pkg {
+ font-weight: bold;
+}
+
+span.samp {
+ font-family: monospace;
+}
+
+div.vignettes a:hover {
+ background: rgb(85%, 85%, 85%);
+}
+
diff --git a/src/cpp/session/resources/markdown.css b/src/cpp/session/resources/markdown.css
new file mode 100644
index 0000000..0a90ddb
--- /dev/null
+++ b/src/cpp/session/resources/markdown.css
@@ -0,0 +1,124 @@
+body, td {
+ font-family: sans-serif;
+ background-color: white;
+ font-size: 12px;
+ margin: 8px;
+}
+
+tt, code, pre {
+ font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace;
+}
+
+h1 {
+ font-size:2.2em;
+}
+
+h2 {
+ font-size:1.8em;
+}
+
+h3 {
+ font-size:1.4em;
+}
+
+h4 {
+ font-size:1.0em;
+}
+
+h5 {
+ font-size:0.9em;
+}
+
+h6 {
+ font-size:0.8em;
+}
+
+a:visited {
+ color: rgb(50%, 0%, 50%);
+}
+
+pre {
+ margin-top: 0;
+ max-width: 95%;
+ border: 1px solid #ccc;
+ white-space: pre-wrap;
+}
+
+pre code {
+ display: block; padding: 0.5em;
+}
+
+code.r, code.cpp {
+ background-color: #F8F8F8;
+}
+
+table, td, th {
+ border: none;
+}
+
+blockquote {
+ color:#666666;
+ margin:0;
+ padding-left: 1em;
+ border-left: 0.5em #EEE solid;
+}
+
+hr {
+ height: 0px;
+ border-bottom: none;
+ border-top-width: thin;
+ border-top-style: dotted;
+ border-top-color: #999999;
+}
+
+ at media print {
+ * {
+ background: transparent !important;
+ color: black !important;
+ filter:none !important;
+ -ms-filter: none !important;
+ }
+
+ body {
+ font-size:12pt;
+ max-width:100%;
+ }
+
+ a, a:visited {
+ text-decoration: underline;
+ }
+
+ hr {
+ visibility: hidden;
+ page-break-before: always;
+ }
+
+ pre, blockquote {
+ padding-right: 1em;
+ page-break-inside: avoid;
+ }
+
+ tr, img {
+ page-break-inside: avoid;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page :left {
+ margin: 15mm 20mm 15mm 10mm;
+ }
+
+ @page :right {
+ margin: 15mm 10mm 15mm 20mm;
+ }
+
+ p, h2, h3 {
+ orphans: 3; widows: 3;
+ }
+
+ h2, h3 {
+ page-break-after: avoid;
+ }
+}
diff --git a/src/cpp/session/resources/markdown.html b/src/cpp/session/resources/markdown.html
new file mode 100644
index 0000000..9b4f3db
--- /dev/null
+++ b/src/cpp/session/resources/markdown.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!-- saved from url=(0014)about:internet -->
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta http-equiv="x-ua-compatible" content="IE=9" >
+
+<title>#!title#</title>
+
+<style type="text/css">
+#!markdown_css#
+</style>
+
+#!r_highlight#
+
+#!mathjax#
+
+</head>
+
+<body>
+#!html_output#
+</body>
+
+</html>
+
diff --git a/src/cpp/session/resources/markdown_help.html b/src/cpp/session/resources/markdown_help.html
new file mode 100644
index 0000000..25cfd9a
--- /dev/null
+++ b/src/cpp/session/resources/markdown_help.html
@@ -0,0 +1,243 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+<head>
+<title>Markdown Quick Reference</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel="stylesheet" type="text/css" href="R.css">
+
+<style type="text/css">
+
+p {
+ font-size: 0.9em;
+}
+
+h4 {
+ color: rgb(20%, 20%, 20%);
+}
+
+#md_examples p {
+ margin-top: 3px;
+ margin-bottom: 0px;
+}
+
+#md_examples h4 {
+ margin-bottom: 8px;
+}
+
+#md_examples h5 {
+ margin-top: 5px;
+ margin-bottom: 2px;
+}
+
+#md_examples code {
+ font-family: Monaco, "DejaVu Sans Mono", Consolas, "Lucida Console", Courier, monospace;
+ font-size: 11px;
+}
+
+#md_examples pre {
+ line-height: 12px;
+ margin-top: 3px;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ padding: 4px;
+}
+
+#md_examples ul {
+ margin-top: 3px;
+ padding-left: 2em;
+}
+
+</style>
+
+</head>
+
+<body>
+
+<h3>Markdown Quick Reference</h3>
+
+<p>Markdown is an easy-to-write plain text format for creating web content. See <a href="http://www.rstudio.org/links/using_markdown" target="_blank">Using Markdown with RStudio</a> to learn more.</p>
+
+<div id="md_examples">
+<h4>Emphasis</h4>
+
+<pre><code>*italic* **bold**
+
+_italic_ __bold__
+</code></pre>
+
+<h4>Headers</h4>
+<pre><code>Header 1
+=========================
+
+Header 2
+-------------------------
+
+### Header 3
+
+#### Header 4
+</code></pre>
+
+<h4>Lists</h4>
+<h5>Unordered List</h5>
+<pre><code>* Item 1
+* Item 2
+ * Item 2a
+ * Item 2b
+</code></pre>
+
+<h5>Ordered List</h5>
+<pre><code>1. Item 1
+2. Item 2
+3. Item 3
+ * Item 3a
+ * Item 3b
+</code></pre>
+
+<h4>Manual Line Breaks</h4>
+<p>End a line with two or more spaces:</p>
+<pre><code>Roses are red,
+Violets are blue.
+</code></pre>
+
+<h4>Links</h4>
+<p>Use a plain http address or add a link to a phrase:</p>
+<pre><code>http://example.com
+
+[linked phrase](http://example.com)
+</code></pre>
+
+<h4>Images</h4>
+<p>Images on the web or local files in the same directory:</p>
+<pre><code>
+
+
+</code></pre>
+
+<h4>Blockquotes</h4>
+<pre><code>A friend once said:
+
+> It's always better to give
+> than to receive.
+</code></pre>
+
+<h4>R Code Blocks</h4>
+<p>R code will be evaluated and printed</p>
+<pre><code>```{r}
+summary(cars$dist)
+summary(cars$speed)
+```
+</code></pre>
+
+<h4>Inline R Code</h4>
+<pre><code>There were `r nrow(cars)` cars studied
+</code></pre>
+
+<h4>Plain Code Blocks</h4>
+<p>Plain code blocks are displayed in a fixed-width font but not evaulated</p>
+<pre><code>```
+This text is displayed verbatim / preformatted
+```
+</code></pre>
+
+<h4>Inline Code</h4>
+<pre><code>We defined the `add` function to
+compute the sum of two numbers.
+</code></pre>
+
+<h4>LaTeX Equations</h4>
+<p>See also: <a href="http://www.rstudio.org/links/mathjax_help" target="_blank">Using Equations with Markdown</a></p>
+
+<h5>Inline Equation</h5>
+<pre><code>$<i>equation</i>$
+
+$latex <i>equation</i>$
+
+\( <i>equation</i> \)
+</code></pre>
+
+<h5>Display Equation</h5>
+<pre><code>$$ <i>equation</i> $$
+
+$$latex <i>equation</i> $$
+
+\[ <i>equation</i> \]
+</code></pre>
+
+<h4>Horizontal Rule / Page Break</h4>
+<p>Three or more asterisks or dashes:</p>
+<pre><code>******
+
+------
+</code></pre>
+
+<h4>Tables</h4>
+<pre><code>First Header | Second Header
+------------- | -------------
+Content Cell | Content Cell
+Content Cell | Content Cell
+</code></pre>
+
+<h4>Reference Style Links and Images</h4>
+<h5>Links</h5>
+<pre><code>A [linked phrase][id].
+
+At the bottom of the document:
+
+[id]: http://example.com/ "Title"
+</code></pre>
+
+<h5>Images</h5>
+<pre><code>![alt text][id]
+
+At the bottom of the document:
+
+[id]: figures/img.png "Title"
+</code></pre>
+
+<h4>Miscellaneous</h4>
+<pre><code>superscript^2
+
+~~strikethrough~~
+</code></pre>
+
+<h4>Typographic Entities</h4>
+<p>
+ASCII characters are transformed into
+typographic HTML entities:
+</p>
+
+<ul>
+<li>
+Straight quotes ( " and ' ) into “curly” quotes
+</li>
+
+<li>
+Backtick quotes (<code>``like this''</code>) into “curly” quotes
+</li>
+
+<li>
+Dashes (“<code>--</code>” and “<code>---</code>”) into en- and em-dash entities
+</li>
+
+<li>
+Three consecutive dots (“<code>...</code>”) into an ellipsis entity
+</li>
+
+<li>
+Fractions <code>1/4</code>, <code>1/2</code>, and <code>3/4</code> into ¼, ½, and ¾.
+</li>
+
+<li>
+Symbols <code>(c)</code>, <code>(tm)</code>, and <code>(r)</code> into ©, ™, and ®
+</li>
+
+</ul>
+
+
+</div>
+
+</body>
+
+</html>
diff --git a/src/cpp/session/resources/mathjax.html b/src/cpp/session/resources/mathjax.html
new file mode 100644
index 0000000..166a6ab
--- /dev/null
+++ b/src/cpp/session/resources/mathjax.html
@@ -0,0 +1,4 @@
+<!-- MathJax scripts -->
+<script type="text/javascript" src="https://c328740.ssl.cf1.rackcdn.com/mathjax/2.0-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
+</script>
+
diff --git a/src/cpp/session/resources/presentation/helpdoc.css b/src/cpp/session/resources/presentation/helpdoc.css
new file mode 100644
index 0000000..55584ca
--- /dev/null
+++ b/src/cpp/session/resources/presentation/helpdoc.css
@@ -0,0 +1,16 @@
+
+
+pre {
+ margin-top: 0;
+ max-width: 90%;
+ border: 1px solid #ccc;
+ white-space: pre-wrap;
+}
+
+pre code {
+ display: block; padding: 0.5em;
+}
+
+code.r, code.cpp {
+ background-color: #F8F8F8;
+}
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/helpdoc.html b/src/cpp/session/resources/presentation/helpdoc.html
new file mode 100644
index 0000000..1630b37
--- /dev/null
+++ b/src/cpp/session/resources/presentation/helpdoc.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="utf-8">
+ <title>#!title#</title>
+
+ <link rel="stylesheet" type="text/css" href="R.css">
+
+ <style type="text/css">
+ #!styles#
+ </style>
+
+ #!r_highlight#
+
+ #!mathjax#
+
+</head>
+
+<body>
+
+ #!content#
+
+ #!js_callbacks#
+
+</body>
+
+</html>
diff --git a/src/cpp/session/resources/presentation/mathjax.html b/src/cpp/session/resources/presentation/mathjax.html
new file mode 100644
index 0000000..8409633
--- /dev/null
+++ b/src/cpp/session/resources/presentation/mathjax.html
@@ -0,0 +1,10 @@
+<script type="text/x-mathjax-config">
+ MathJax.Hub.Config({
+ showProcessingMessages: false,
+ messageStyle: "none"
+ });
+</script>
+
+<script type="text/javascript" src="https://c328740.ssl.cf1.rackcdn.com/mathjax/2.0-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
+</script>
+
diff --git a/src/cpp/session/resources/presentation/revealjs/LICENSE b/src/cpp/session/resources/presentation/revealjs/LICENSE
new file mode 100644
index 0000000..e1e8bf7
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2013 Hakim El Hattab, http://hakim.se
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/css/print/paper.css b/src/cpp/session/resources/presentation/revealjs/css/print/paper.css
new file mode 100644
index 0000000..f902dea
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/css/print/paper.css
@@ -0,0 +1,176 @@
+/* Default Print Stylesheet Template
+ by Rob Glazebrook of CSSnewbie.com
+ Last Updated: June 4, 2008
+
+ Feel free (nay, compelled) to edit, append, and
+ manipulate this file as you see fit. */
+
+
+/* SECTION 1: Set default width, margin, float, and
+ background. This prevents elements from extending
+ beyond the edge of the printed page, and prevents
+ unnecessary background images from printing */
+body {
+ background: #fff;
+ font-size: 13pt;
+ width: auto;
+ height: auto;
+ border: 0;
+ margin: 0 5%;
+ padding: 0;
+ float: none !important;
+ overflow: visible;
+}
+html {
+ background: #fff;
+ width: auto;
+ height: auto;
+ overflow: visible;
+}
+
+/* SECTION 2: Remove any elements not needed in print.
+ This would include navigation, ads, sidebars, etc. */
+.nestedarrow,
+.controls,
+.reveal .progress,
+.reveal.overview,
+.fork-reveal,
+.share-reveal,
+.state-background {
+ display: none !important;
+}
+
+/* SECTION 3: Set body font face, size, and color.
+ Consider using a serif font for readability. */
+body, p, td, li, div, a {
+ font-size: 16pt!important;
+ font-family: Georgia, "Times New Roman", Times, serif !important;
+ color: #000;
+}
+
+/* SECTION 4: Set heading font face, sizes, and color.
+ Diffrentiate your headings from your body text.
+ Perhaps use a large sans-serif for distinction. */
+h1,h2,h3,h4,h5,h6 {
+ color: #000!important;
+ height: auto;
+ line-height: normal;
+ font-family: Georgia, "Times New Roman", Times, serif !important;
+ text-shadow: 0 0 0 #000 !important;
+ text-align: left;
+ letter-spacing: normal;
+}
+/* Need to reduce the size of the fonts for printing */
+h1 { font-size: 26pt !important; }
+h2 { font-size: 22pt !important; }
+h3 { font-size: 20pt !important; }
+h4 { font-size: 20pt !important; font-variant: small-caps; }
+h5 { font-size: 19pt !important; }
+h6 { font-size: 18pt !important; font-style: italic; }
+
+/* SECTION 5: Make hyperlinks more usable.
+ Ensure links are underlined, and consider appending
+ the URL to the end of the link for usability. */
+a:link,
+a:visited {
+ color: #000 !important;
+ font-weight: bold;
+ text-decoration: underline;
+}
+/*
+.reveal a:link:after,
+.reveal a:visited:after {
+ content: " (" attr(href) ") ";
+ color: #222 !important;
+ font-size: 90%;
+}
+*/
+
+
+/* SECTION 6: more reveal.js specific additions by @skypanther */
+ul, ol, div, p {
+ visibility: visible;
+ position: static;
+ width: auto;
+ height: auto;
+ display: block;
+ overflow: visible;
+ margin: auto;
+ text-align: left !important;
+}
+.reveal .slides {
+ position: static;
+ width: auto;
+ height: auto;
+
+ left: auto;
+ top: auto;
+ margin-left: auto;
+ margin-top: auto;
+ padding: auto;
+
+ overflow: visible;
+ display: block;
+
+ text-align: center;
+ -webkit-perspective: none;
+ -moz-perspective: none;
+ -ms-perspective: none;
+ perspective: none;
+
+ -webkit-perspective-origin: 50% 50%; /* there isn't a none/auto value but 50-50 is the default */
+ -moz-perspective-origin: 50% 50%;
+ -ms-perspective-origin: 50% 50%;
+ perspective-origin: 50% 50%;
+}
+.reveal .slides>section,
+.reveal .slides>section>section {
+
+ visibility: visible !important;
+ position: static !important;
+ width: 90% !important;
+ height: auto !important;
+ display: block !important;
+ overflow: visible !important;
+
+ left: 0% !important;
+ top: 0% !important;
+ margin-left: 0px !important;
+ margin-top: 0px !important;
+ padding: 20px 0px !important;
+
+ opacity: 1 !important;
+
+ -webkit-transform-style: flat !important;
+ -moz-transform-style: flat !important;
+ -ms-transform-style: flat !important;
+ transform-style: flat !important;
+
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ transform: none !important;
+}
+.reveal section {
+ page-break-after: always !important;
+ display: block !important;
+}
+.reveal section .fragment {
+ opacity: 1 !important;
+ visibility: visible !important;
+
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ transform: none !important;
+}
+.reveal section:last-of-type {
+ page-break-after: avoid !important;
+}
+.reveal section img {
+ display: block;
+ margin: 15px 0px;
+ background: rgba(255,255,255,1);
+ border: 1px solid #666;
+ box-shadow: none;
+}
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/css/print/pdf.css b/src/cpp/session/resources/presentation/revealjs/css/print/pdf.css
new file mode 100644
index 0000000..9811096
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/css/print/pdf.css
@@ -0,0 +1,164 @@
+/* Default Print Stylesheet Template
+ by Rob Glazebrook of CSSnewbie.com
+ Last Updated: June 4, 2008
+
+ Feel free (nay, compelled) to edit, append, and
+ manipulate this file as you see fit. */
+
+
+/* SECTION 1: Set default width, margin, float, and
+ background. This prevents elements from extending
+ beyond the edge of the printed page, and prevents
+ unnecessary background images from printing */
+
+* {
+ -webkit-print-color-adjust: exact;
+}
+
+body {
+ font-size: 18pt;
+ width: auto;
+ height: auto;
+ border: 0;
+ padding: 0;
+ float: none !important;
+ overflow: visible;
+}
+
+html {
+ width: 100%;
+ height: 100%;
+ overflow: visible;
+}
+
+ at page {
+ size: letter landscape;
+ margin: 0;
+}
+
+/* SECTION 2: Remove any elements not needed in print.
+ This would include navigation, ads, sidebars, etc. */
+.nestedarrow,
+.controls,
+.reveal .progress,
+.reveal.overview,
+.fork-reveal,
+.share-reveal,
+.state-background {
+ display: none !important;
+}
+
+/* SECTION 3: Set body font face, size, and color.
+ Consider using a serif font for readability. */
+body, p, td, li, div {
+ font-size: 18pt;
+}
+
+/* SECTION 4: Set heading font face, sizes, and color.
+ Diffrentiate your headings from your body text.
+ Perhaps use a large sans-serif for distinction. */
+h1,h2,h3,h4,h5,h6 {
+ text-shadow: 0 0 0 #000 !important;
+}
+
+/* SECTION 5: Make hyperlinks more usable.
+ Ensure links are underlined, and consider appending
+ the URL to the end of the link for usability. */
+a:link,
+a:visited {
+ font-weight: bold;
+ text-decoration: underline;
+}
+
+
+/* SECTION 6: more reveal.js specific additions by @skypanther */
+ul, ol, div, p {
+ visibility: visible;
+ position: static;
+ width: auto;
+ height: auto;
+ display: block;
+ overflow: visible;
+ margin: auto;
+}
+.reveal .slides {
+ position: static;
+ width: 100%;
+ height: auto;
+
+ left: auto;
+ top: auto;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: auto;
+ padding: auto;
+
+ overflow: visible;
+ display: block;
+
+ text-align: center;
+
+ -webkit-perspective: none;
+ -moz-perspective: none;
+ -ms-perspective: none;
+ perspective: none;
+
+ -webkit-perspective-origin: 50% 50%; /* there isn't a none/auto value but 50-50 is the default */
+ -moz-perspective-origin: 50% 50%;
+ -ms-perspective-origin: 50% 50%;
+ perspective-origin: 50% 50%;
+}
+.reveal .slides section {
+
+ page-break-after: always !important;
+
+ visibility: visible !important;
+ position: static !important;
+ width: 100% !important;
+ height: auto !important;
+ min-height: initial !important;
+ display: block !important;
+ overflow: visible !important;
+
+ left: 0 !important;
+ top: 0 !important;
+ margin-left: 0px !important;
+ margin-top: 50px !important;
+ padding: 20px 0px !important;
+
+ opacity: 1 !important;
+
+ -webkit-transform-style: flat !important;
+ -moz-transform-style: flat !important;
+ -ms-transform-style: flat !important;
+ transform-style: flat !important;
+
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ transform: none !important;
+}
+.reveal section.stack {
+ margin: 0px !important;
+ padding: 0px !important;
+ page-break-after: avoid !important;
+}
+.reveal section .fragment {
+ opacity: 1 !important;
+ visibility: visible !important;
+
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ transform: none !important;
+}
+.reveal img {
+ box-shadow: none;
+}
+.reveal .roll {
+ overflow: visible;
+ line-height: 1em;
+}
+.reveal small a {
+ font-size: 16pt !important;
+}
diff --git a/src/cpp/session/resources/presentation/revealjs/css/reveal.css b/src/cpp/session/resources/presentation/revealjs/css/reveal.css
new file mode 100644
index 0000000..394cc9f
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/css/reveal.css
@@ -0,0 +1,1370 @@
+ at charset "UTF-8";
+
+/*!
+ * reveal.js
+ * http://lab.hakim.se/reveal-js
+ * MIT licensed
+ *
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
+ */
+
+
+/*********************************************
+ * RESET STYLES
+ *********************************************/
+
+html, body, .reveal div, .reveal span, .reveal applet, .reveal object, .reveal iframe,
+.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal p, .reveal blockquote, .reveal pre,
+.reveal a, .reveal abbr, .reveal acronym, .reveal address, .reveal big, .reveal cite, .reveal code,
+.reveal del, .reveal dfn, .reveal em, .reveal img, .reveal ins, .reveal kbd, .reveal q, .reveal s, .reveal samp,
+.reveal small, .reveal strike, .reveal strong, .reveal sub, .reveal sup, .reveal tt, .reveal var,
+.reveal b, .reveal u, .reveal i, .reveal center,
+.reveal dl, .reveal dt, .reveal dd, .reveal ol, .reveal ul, .reveal li,
+.reveal fieldset, .reveal form, .reveal label, .reveal legend,
+.reveal table, .reveal caption, .reveal tbody, .reveal tfoot, .reveal thead, .reveal tr, .reveal th, .reveal td,
+.reveal article, .reveal aside, .reveal canvas, .reveal details, .reveal embed,
+.reveal figure, .reveal figcaption, .reveal footer, .reveal header, .reveal hgroup,
+.reveal menu, .reveal nav, .reveal output, .reveal ruby, .reveal section, .reveal summary,
+.reveal time, .reveal mark, .reveal audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+.reveal article, .reveal aside, .reveal details, .reveal figcaption, .reveal figure,
+.reveal footer, .reveal header, .reveal hgroup, .reveal menu, .reveal nav, .reveal section {
+ display: block;
+}
+
+
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+
+html,
+body {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ position: relative;
+ line-height: 1;
+}
+
+::selection {
+ background: #FF5E99;
+ color: #fff;
+ text-shadow: none;
+}
+
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+ -webkit-hyphens: auto;
+ -moz-hyphens: auto;
+ hyphens: auto;
+
+ word-wrap: break-word;
+}
+
+.reveal h1 { font-size: 3.77em; }
+.reveal h2 { font-size: 2.11em; }
+.reveal h3 { font-size: 1.55em; }
+.reveal h4 { font-size: 1em; }
+
+
+/*********************************************
+ * VIEW FRAGMENTS
+ *********************************************/
+
+.reveal .slides section .fragment {
+ opacity: 0;
+
+ -webkit-transition: all .2s ease;
+ -moz-transition: all .2s ease;
+ -ms-transition: all .2s ease;
+ -o-transition: all .2s ease;
+ transition: all .2s ease;
+}
+ .reveal .slides section .fragment.visible {
+ opacity: 1;
+ }
+
+.reveal .slides section .fragment.grow {
+ opacity: 1;
+}
+ .reveal .slides section .fragment.grow.visible {
+ -webkit-transform: scale( 1.3 );
+ -moz-transform: scale( 1.3 );
+ -ms-transform: scale( 1.3 );
+ -o-transform: scale( 1.3 );
+ transform: scale( 1.3 );
+ }
+
+.reveal .slides section .fragment.shrink {
+ opacity: 1;
+}
+ .reveal .slides section .fragment.shrink.visible {
+ -webkit-transform: scale( 0.7 );
+ -moz-transform: scale( 0.7 );
+ -ms-transform: scale( 0.7 );
+ -o-transform: scale( 0.7 );
+ transform: scale( 0.7 );
+ }
+
+.reveal .slides section .fragment.zoom-in {
+ opacity: 0;
+
+ -webkit-transform: scale( 0.1 );
+ -moz-transform: scale( 0.1 );
+ -ms-transform: scale( 0.1 );
+ -o-transform: scale( 0.1 );
+ transform: scale( 0.1 );
+}
+
+ .reveal .slides section .fragment.zoom-in.visible {
+ opacity: 1;
+
+ -webkit-transform: scale( 1 );
+ -moz-transform: scale( 1 );
+ -ms-transform: scale( 1 );
+ -o-transform: scale( 1 );
+ transform: scale( 1 );
+ }
+
+.reveal .slides section .fragment.roll-in {
+ opacity: 0;
+
+ -webkit-transform: rotateX( 90deg );
+ -moz-transform: rotateX( 90deg );
+ -ms-transform: rotateX( 90deg );
+ -o-transform: rotateX( 90deg );
+ transform: rotateX( 90deg );
+}
+ .reveal .slides section .fragment.roll-in.visible {
+ opacity: 1;
+
+ -webkit-transform: rotateX( 0 );
+ -moz-transform: rotateX( 0 );
+ -ms-transform: rotateX( 0 );
+ -o-transform: rotateX( 0 );
+ transform: rotateX( 0 );
+ }
+
+.reveal .slides section .fragment.fade-out {
+ opacity: 1;
+}
+ .reveal .slides section .fragment.fade-out.visible {
+ opacity: 0;
+ }
+
+.reveal .slides section .fragment.highlight-red,
+.reveal .slides section .fragment.highlight-green,
+.reveal .slides section .fragment.highlight-blue {
+ opacity: 1;
+}
+ .reveal .slides section .fragment.highlight-red.visible {
+ color: #ff2c2d
+ }
+ .reveal .slides section .fragment.highlight-green.visible {
+ color: #17ff2e;
+ }
+ .reveal .slides section .fragment.highlight-blue.visible {
+ color: #1b91ff;
+ }
+
+
+/*********************************************
+ * DEFAULT ELEMENT STYLES
+ *********************************************/
+
+/* Fixes issue in Chrome where italic fonts did not appear when printing to PDF */
+.reveal:after {
+ content: '';
+ font-style: italic;
+}
+
+/* Ensure certain elements are never larger than the slide itself */
+.reveal img,
+.reveal video,
+.reveal iframe {
+ max-width: 95%;
+ max-height: 95%;
+}
+
+/** Prevents layering issues in certain browser/transition combinations */
+.reveal a {
+ position: relative;
+}
+
+.reveal strong,
+.reveal b {
+ font-weight: bold;
+}
+
+.reveal em,
+.reveal i {
+ font-style: italic;
+}
+
+.reveal ol,
+.reveal ul {
+ display: inline-block;
+
+ text-align: left;
+ margin: 0 0 0 1em;
+}
+
+.reveal ol {
+ list-style-type: decimal;
+}
+
+.reveal ul {
+ list-style-type: disc;
+}
+
+.reveal ul ul {
+ list-style-type: square;
+}
+
+.reveal ul ul ul {
+ list-style-type: circle;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+ display: block;
+ margin-left: 40px;
+}
+
+.reveal p {
+ margin-bottom: 10px;
+ line-height: 1.2em;
+}
+
+.reveal q,
+.reveal blockquote {
+ quotes: none;
+}
+
+.reveal blockquote {
+ display: block;
+ position: relative;
+ width: 70%;
+ margin: 5px auto;
+ padding: 5px;
+
+ font-style: italic;
+ background: rgba(255, 255, 255, 0.05);
+ box-shadow: 0px 0px 2px rgba(0,0,0,0.2);
+}
+ .reveal blockquote p:first-child,
+ .reveal blockquote p:last-child {
+ display: inline-block;
+ }
+
+.reveal q {
+ font-style: italic;
+}
+
+.reveal pre {
+ display: block;
+ position: relative;
+ width: 90%;
+ margin: 15px auto;
+
+ text-align: left;
+ font-size: 0.55em;
+ font-family: monospace;
+ line-height: 1.2em;
+
+ word-wrap: break-word;
+
+ box-shadow: 0px 0px 6px rgba(0,0,0,0.3);
+}
+.reveal code {
+ font-family: monospace;
+}
+.reveal pre code {
+ padding: 5px;
+ overflow: auto;
+ max-height: 400px;
+ word-wrap: normal;
+}
+
+.reveal table th,
+.reveal table td {
+ text-align: left;
+ padding-right: .3em;
+}
+
+.reveal table th {
+ text-shadow: rgb(255,255,255) 1px 1px 2px;
+}
+
+.reveal sup {
+ vertical-align: super;
+}
+.reveal sub {
+ vertical-align: sub;
+}
+
+.reveal small {
+ display: inline-block;
+ font-size: 0.6em;
+ line-height: 1.2em;
+ vertical-align: top;
+}
+
+.reveal small * {
+ vertical-align: top;
+}
+
+
+/*********************************************
+ * CONTROLS
+ *********************************************/
+
+.reveal .controls {
+ display: none;
+ position: fixed;
+ width: 110px;
+ height: 110px;
+ z-index: 30;
+ right: 10px;
+ bottom: 10px;
+}
+
+.reveal .controls div {
+ position: absolute;
+ opacity: 0.05;
+ width: 0;
+ height: 0;
+ border: 12px solid transparent;
+
+ -moz-transform: scale(.9999);
+
+ -webkit-transition: all 0.2s ease;
+ -moz-transition: all 0.2s ease;
+ -ms-transition: all 0.2s ease;
+ -o-transition: all 0.2s ease;
+ transition: all 0.2s ease;
+}
+
+.reveal .controls div.enabled {
+ opacity: 0.7;
+ cursor: pointer;
+}
+
+.reveal .controls div.enabled:active {
+ margin-top: 1px;
+}
+
+ .reveal .controls div.navigate-left {
+ top: 42px;
+
+ border-right-width: 22px;
+ border-right-color: #eee;
+ }
+ .reveal .controls div.navigate-left.fragmented {
+ opacity: 0.3;
+ }
+
+ .reveal .controls div.navigate-right {
+ left: 74px;
+ top: 42px;
+
+ border-left-width: 22px;
+ border-left-color: #eee;
+ }
+ .reveal .controls div.navigate-right.fragmented {
+ opacity: 0.3;
+ }
+
+ .reveal .controls div.navigate-up {
+ left: 42px;
+
+ border-bottom-width: 22px;
+ border-bottom-color: #eee;
+ }
+ .reveal .controls div.navigate-up.fragmented {
+ opacity: 0.3;
+ }
+
+ .reveal .controls div.navigate-down {
+ left: 42px;
+ top: 74px;
+
+ border-top-width: 22px;
+ border-top-color: #eee;
+ }
+ .reveal .controls div.navigate-down.fragmented {
+ opacity: 0.3;
+ }
+
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+
+.reveal .progress {
+ position: fixed;
+ display: none;
+ height: 3px;
+ width: 100%;
+ bottom: 0;
+ left: 0;
+ z-index: 10;
+}
+ .reveal .progress:after {
+ content: '';
+ display: 'block';
+ position: absolute;
+ height: 20px;
+ width: 100%;
+ top: -20px;
+ }
+ .reveal .progress span {
+ display: block;
+ height: 100%;
+ width: 0px;
+
+ -webkit-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -moz-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -ms-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -o-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ }
+
+
+/*********************************************
+ * ROLLING LINKS
+ *********************************************/
+
+.reveal .roll {
+ display: inline-block;
+ line-height: 1.2;
+ overflow: hidden;
+
+ vertical-align: top;
+
+ -webkit-perspective: 400px;
+ -moz-perspective: 400px;
+ -ms-perspective: 400px;
+ perspective: 400px;
+
+ -webkit-perspective-origin: 50% 50%;
+ -moz-perspective-origin: 50% 50%;
+ -ms-perspective-origin: 50% 50%;
+ perspective-origin: 50% 50%;
+}
+ .reveal .roll:hover {
+ background: none;
+ text-shadow: none;
+ }
+.reveal .roll span {
+ display: block;
+ position: relative;
+ padding: 0 2px;
+
+ pointer-events: none;
+
+ -webkit-transition: all 400ms ease;
+ -moz-transition: all 400ms ease;
+ -ms-transition: all 400ms ease;
+ transition: all 400ms ease;
+
+ -webkit-transform-origin: 50% 0%;
+ -moz-transform-origin: 50% 0%;
+ -ms-transform-origin: 50% 0%;
+ transform-origin: 50% 0%;
+
+ -webkit-transform-style: preserve-3d;
+ -moz-transform-style: preserve-3d;
+ -ms-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ backface-visibility: hidden;
+}
+ .reveal .roll:hover span {
+ background: rgba(0,0,0,0.5);
+
+ -webkit-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
+ -moz-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
+ -ms-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
+ transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
+ }
+.reveal .roll span:after {
+ content: attr(data-title);
+
+ display: block;
+ position: absolute;
+ left: 0;
+ top: 0;
+ padding: 0 2px;
+
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ backface-visibility: hidden;
+
+ -webkit-transform-origin: 50% 0%;
+ -moz-transform-origin: 50% 0%;
+ -ms-transform-origin: 50% 0%;
+ transform-origin: 50% 0%;
+
+ -webkit-transform: translate3d( 0px, 110%, 0px ) rotateX( -90deg );
+ -moz-transform: translate3d( 0px, 110%, 0px ) rotateX( -90deg );
+ -ms-transform: translate3d( 0px, 110%, 0px ) rotateX( -90deg );
+ transform: translate3d( 0px, 110%, 0px ) rotateX( -90deg );
+}
+
+
+/*********************************************
+ * SLIDES
+ *********************************************/
+
+.reveal {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.reveal .slides {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 50%;
+ top: 50%;
+
+ overflow: visible;
+ z-index: 1;
+ text-align: center;
+
+ -webkit-transition: -webkit-perspective .4s ease;
+ -moz-transition: -moz-perspective .4s ease;
+ -ms-transition: -ms-perspective .4s ease;
+ -o-transition: -o-perspective .4s ease;
+ transition: perspective .4s ease;
+
+ -webkit-perspective: 600px;
+ -moz-perspective: 600px;
+ -ms-perspective: 600px;
+ perspective: 600px;
+
+ -webkit-perspective-origin: 0px -100px;
+ -moz-perspective-origin: 0px -100px;
+ -ms-perspective-origin: 0px -100px;
+ perspective-origin: 0px -100px;
+}
+
+.reveal .slides>section,
+.reveal .slides>section>section {
+ display: none;
+ position: absolute;
+ width: 100%;
+ padding: 20px 0px;
+
+ z-index: 10;
+ line-height: 1.2em;
+ font-weight: normal;
+
+ -webkit-transform-style: preserve-3d;
+ -moz-transform-style: preserve-3d;
+ -ms-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+
+ -webkit-transition: -webkit-transform-origin 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ -webkit-transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -moz-transition: -moz-transform-origin 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ -moz-transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -ms-transition: -ms-transform-origin 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ -ms-transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ -o-transition: -o-transform-origin 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ -o-transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+ transition: transform-origin 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ transform 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ visibility 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985),
+ opacity 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
+}
+
+/* Global transition speed settings */
+.reveal[data-transition-speed="fast"] .slides section {
+ -webkit-transition-duration: 400ms;
+ -moz-transition-duration: 400ms;
+ -ms-transition-duration: 400ms;
+ transition-duration: 400ms;
+}
+.reveal[data-transition-speed="slow"] .slides section {
+ -webkit-transition-duration: 1200ms;
+ -moz-transition-duration: 1200ms;
+ -ms-transition-duration: 1200ms;
+ transition-duration: 1200ms;
+}
+
+/* Slide-specific transition speed overrides */
+.reveal .slides section[data-transition-speed="fast"] {
+ -webkit-transition-duration: 400ms;
+ -moz-transition-duration: 400ms;
+ -ms-transition-duration: 400ms;
+ transition-duration: 400ms;
+}
+.reveal .slides section[data-transition-speed="slow"] {
+ -webkit-transition-duration: 1200ms;
+ -moz-transition-duration: 1200ms;
+ -ms-transition-duration: 1200ms;
+ transition-duration: 1200ms;
+}
+
+.reveal .slides>section {
+ left: -50%;
+ top: -50%;
+}
+
+.reveal .slides>section.stack {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.reveal .slides>section.present,
+.reveal .slides>section>section.present {
+ display: block;
+ z-index: 11;
+ opacity: 1;
+}
+
+.reveal.center,
+.reveal.center .slides,
+.reveal.center .slides section {
+ min-height: auto !important;
+}
+
+
+
+/*********************************************
+ * DEFAULT TRANSITION
+ *********************************************/
+
+.reveal .slides>section[data-transition=default].past,
+.reveal .slides>section.past {
+ display: block;
+ opacity: 0;
+
+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
+ -moz-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
+ -ms-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
+ transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
+}
+.reveal .slides>section[data-transition=default].future,
+.reveal .slides>section.future {
+ display: block;
+ opacity: 0;
+
+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
+ -moz-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
+ -ms-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
+}
+
+.reveal .slides>section>section[data-transition=default].past,
+.reveal .slides>section>section.past {
+ display: block;
+ opacity: 0;
+
+ -webkit-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
+ -moz-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
+ -ms-transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
+ transform: translate3d(0, -300px, 0) rotateX(70deg) translate3d(0, -300px, 0);
+}
+.reveal .slides>section>section[data-transition=default].future,
+.reveal .slides>section>section.future {
+ display: block;
+ opacity: 0;
+
+ -webkit-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
+ -moz-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
+ -ms-transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
+ transform: translate3d(0, 300px, 0) rotateX(-70deg) translate3d(0, 300px, 0);
+}
+
+
+/*********************************************
+ * CONCAVE TRANSITION
+ *********************************************/
+
+.reveal .slides>section[data-transition=concave].past,
+.reveal.concave .slides>section.past {
+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
+ -moz-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
+ -ms-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
+ transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
+}
+.reveal .slides>section[data-transition=concave].future,
+.reveal.concave .slides>section.future {
+ -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
+ -moz-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
+ -ms-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
+}
+
+.reveal .slides>section>section[data-transition=concave].past,
+.reveal.concave .slides>section>section.past {
+ -webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
+ -moz-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
+ -ms-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
+ transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
+}
+.reveal .slides>section>section[data-transition=concave].future,
+.reveal.concave .slides>section>section.future {
+ -webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
+ -moz-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
+ -ms-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
+ transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
+}
+
+
+/*********************************************
+ * ZOOM TRANSITION
+ *********************************************/
+
+.reveal .slides>section[data-transition=zoom].past,
+.reveal.zoom .slides>section.past {
+ opacity: 0;
+ visibility: hidden;
+
+ -webkit-transform: scale(16);
+ -moz-transform: scale(16);
+ -ms-transform: scale(16);
+ -o-transform: scale(16);
+ transform: scale(16);
+}
+.reveal .slides>section[data-transition=zoom].future,
+.reveal.zoom .slides>section.future {
+ opacity: 0;
+ visibility: hidden;
+
+ -webkit-transform: scale(0.2);
+ -moz-transform: scale(0.2);
+ -ms-transform: scale(0.2);
+ -o-transform: scale(0.2);
+ transform: scale(0.2);
+}
+
+.reveal .slides>section>section[data-transition=zoom].past,
+.reveal.zoom .slides>section>section.past {
+ -webkit-transform: translate(0, -150%);
+ -moz-transform: translate(0, -150%);
+ -ms-transform: translate(0, -150%);
+ -o-transform: translate(0, -150%);
+ transform: translate(0, -150%);
+}
+.reveal .slides>section>section[data-transition=zoom].future,
+.reveal.zoom .slides>section>section.future {
+ -webkit-transform: translate(0, 150%);
+ -moz-transform: translate(0, 150%);
+ -ms-transform: translate(0, 150%);
+ -o-transform: translate(0, 150%);
+ transform: translate(0, 150%);
+}
+
+
+/*********************************************
+ * LINEAR TRANSITION
+ *********************************************/
+
+.reveal.linear section {
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ backface-visibility: hidden;
+}
+
+.reveal .slides>section[data-transition=linear].past,
+.reveal.linear .slides>section.past {
+ -webkit-transform: translate(-150%, 0);
+ -moz-transform: translate(-150%, 0);
+ -ms-transform: translate(-150%, 0);
+ -o-transform: translate(-150%, 0);
+ transform: translate(-150%, 0);
+}
+.reveal .slides>section[data-transition=linear].future,
+.reveal.linear .slides>section.future {
+ -webkit-transform: translate(150%, 0);
+ -moz-transform: translate(150%, 0);
+ -ms-transform: translate(150%, 0);
+ -o-transform: translate(150%, 0);
+ transform: translate(150%, 0);
+}
+
+.reveal .slides>section>section[data-transition=linear].past,
+.reveal.linear .slides>section>section.past {
+ -webkit-transform: translate(0, -150%);
+ -moz-transform: translate(0, -150%);
+ -ms-transform: translate(0, -150%);
+ -o-transform: translate(0, -150%);
+ transform: translate(0, -150%);
+}
+.reveal .slides>section>section[data-transition=linear].future,
+.reveal.linear .slides>section>section.future {
+ -webkit-transform: translate(0, 150%);
+ -moz-transform: translate(0, 150%);
+ -ms-transform: translate(0, 150%);
+ -o-transform: translate(0, 150%);
+ transform: translate(0, 150%);
+}
+
+
+/*********************************************
+ * CUBE TRANSITION
+ *********************************************/
+
+.reveal.cube .slides {
+ -webkit-perspective: 1300px;
+ -moz-perspective: 1300px;
+ -ms-perspective: 1300px;
+ perspective: 1300px;
+}
+
+.reveal.cube .slides section {
+ padding: 30px;
+ min-height: 700px;
+
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ backface-visibility: hidden;
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+ .reveal.center.cube .slides section {
+ min-height: auto;
+ }
+ .reveal.cube .slides section:not(.stack):before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ background: rgba(0,0,0,0.1);
+ border-radius: 4px;
+
+ -webkit-transform: translateZ( -20px );
+ -moz-transform: translateZ( -20px );
+ -ms-transform: translateZ( -20px );
+ -o-transform: translateZ( -20px );
+ transform: translateZ( -20px );
+ }
+ .reveal.cube .slides section:not(.stack):after {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 90%;
+ height: 30px;
+ left: 5%;
+ bottom: 0;
+ background: none;
+ z-index: 1;
+
+ border-radius: 4px;
+ box-shadow: 0px 95px 25px rgba(0,0,0,0.2);
+
+ -webkit-transform: translateZ(-90px) rotateX( 65deg );
+ -moz-transform: translateZ(-90px) rotateX( 65deg );
+ -ms-transform: translateZ(-90px) rotateX( 65deg );
+ -o-transform: translateZ(-90px) rotateX( 65deg );
+ transform: translateZ(-90px) rotateX( 65deg );
+ }
+
+.reveal.cube .slides>section.stack {
+ padding: 0;
+ background: none;
+}
+
+.reveal.cube .slides>section.past {
+ -webkit-transform-origin: 100% 0%;
+ -moz-transform-origin: 100% 0%;
+ -ms-transform-origin: 100% 0%;
+ transform-origin: 100% 0%;
+
+ -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
+ -moz-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
+ -ms-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
+ transform: translate3d(-100%, 0, 0) rotateY(-90deg);
+}
+
+.reveal.cube .slides>section.future {
+ -webkit-transform-origin: 0% 0%;
+ -moz-transform-origin: 0% 0%;
+ -ms-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+
+ -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
+ -moz-transform: translate3d(100%, 0, 0) rotateY(90deg);
+ -ms-transform: translate3d(100%, 0, 0) rotateY(90deg);
+ transform: translate3d(100%, 0, 0) rotateY(90deg);
+}
+
+.reveal.cube .slides>section>section.past {
+ -webkit-transform-origin: 0% 100%;
+ -moz-transform-origin: 0% 100%;
+ -ms-transform-origin: 0% 100%;
+ transform-origin: 0% 100%;
+
+ -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
+ -moz-transform: translate3d(0, -100%, 0) rotateX(90deg);
+ -ms-transform: translate3d(0, -100%, 0) rotateX(90deg);
+ transform: translate3d(0, -100%, 0) rotateX(90deg);
+}
+
+.reveal.cube .slides>section>section.future {
+ -webkit-transform-origin: 0% 0%;
+ -moz-transform-origin: 0% 0%;
+ -ms-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+
+ -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
+ -moz-transform: translate3d(0, 100%, 0) rotateX(-90deg);
+ -ms-transform: translate3d(0, 100%, 0) rotateX(-90deg);
+ transform: translate3d(0, 100%, 0) rotateX(-90deg);
+}
+
+
+/*********************************************
+ * PAGE TRANSITION
+ *********************************************/
+
+.reveal.page .slides {
+ -webkit-perspective-origin: 0% 50%;
+ -moz-perspective-origin: 0% 50%;
+ -ms-perspective-origin: 0% 50%;
+ perspective-origin: 0% 50%;
+
+ -webkit-perspective: 3000px;
+ -moz-perspective: 3000px;
+ -ms-perspective: 3000px;
+ perspective: 3000px;
+}
+
+.reveal.page .slides section {
+ padding: 30px;
+ min-height: 700px;
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+ .reveal.page .slides section.past {
+ z-index: 12;
+ }
+ .reveal.page .slides section:not(.stack):before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ background: rgba(0,0,0,0.1);
+
+ -webkit-transform: translateZ( -20px );
+ -moz-transform: translateZ( -20px );
+ -ms-transform: translateZ( -20px );
+ -o-transform: translateZ( -20px );
+ transform: translateZ( -20px );
+ }
+ .reveal.page .slides section:not(.stack):after {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 90%;
+ height: 30px;
+ left: 5%;
+ bottom: 0;
+ background: none;
+ z-index: 1;
+
+ border-radius: 4px;
+ box-shadow: 0px 95px 25px rgba(0,0,0,0.2);
+
+ -webkit-transform: translateZ(-90px) rotateX( 65deg );
+ }
+
+.reveal.page .slides>section.stack {
+ padding: 0;
+ background: none;
+}
+
+.reveal.page .slides>section.past {
+ -webkit-transform-origin: 0% 0%;
+ -moz-transform-origin: 0% 0%;
+ -ms-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+
+ -webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
+ -moz-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
+ -ms-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
+ transform: translate3d(-40%, 0, 0) rotateY(-80deg);
+}
+
+.reveal.page .slides>section.future {
+ -webkit-transform-origin: 100% 0%;
+ -moz-transform-origin: 100% 0%;
+ -ms-transform-origin: 100% 0%;
+ transform-origin: 100% 0%;
+
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
+ -ms-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+}
+
+.reveal.page .slides>section>section.past {
+ -webkit-transform-origin: 0% 0%;
+ -moz-transform-origin: 0% 0%;
+ -ms-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+
+ -webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
+ -moz-transform: translate3d(0, -40%, 0) rotateX(80deg);
+ -ms-transform: translate3d(0, -40%, 0) rotateX(80deg);
+ transform: translate3d(0, -40%, 0) rotateX(80deg);
+}
+
+.reveal.page .slides>section>section.future {
+ -webkit-transform-origin: 0% 100%;
+ -moz-transform-origin: 0% 100%;
+ -ms-transform-origin: 0% 100%;
+ transform-origin: 0% 100%;
+
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate3d(0, 0, 0);
+ -ms-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+}
+
+
+/*********************************************
+ * FADE TRANSITION
+ *********************************************/
+
+.reveal .slides section[data-transition=fade],
+.reveal.fade .slides section,
+.reveal.fade .slides>section>section {
+ -webkit-transform: none;
+ -moz-transform: none;
+ -ms-transform: none;
+ -o-transform: none;
+ transform: none;
+
+ -webkit-transition: opacity 0.5s;
+ -moz-transition: opacity 0.5s;
+ -ms-transition: opacity 0.5s;
+ -o-transition: opacity 0.5s;
+ transition: opacity 0.5s;
+}
+
+
+.reveal.fade.overview .slides section,
+.reveal.fade.overview .slides>section>section,
+.reveal.fade.exit-overview .slides section,
+.reveal.fade.exit-overview .slides>section>section {
+ -webkit-transition: none;
+ -moz-transition: none;
+ -ms-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+
+/*********************************************
+ * NO TRANSITION
+ *********************************************/
+
+.reveal .slides section[data-transition=none],
+.reveal.none .slides section {
+ -webkit-transform: none;
+ -moz-transform: none;
+ -ms-transform: none;
+ -o-transform: none;
+ transform: none;
+
+ -webkit-transition: none;
+ -moz-transition: none;
+ -ms-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+
+/*********************************************
+ * OVERVIEW
+ *********************************************/
+
+.reveal.overview .slides {
+ -webkit-perspective-origin: 0% 0%;
+ -moz-perspective-origin: 0% 0%;
+ -ms-perspective-origin: 0% 0%;
+ perspective-origin: 0% 0%;
+
+ -webkit-perspective: 700px;
+ -moz-perspective: 700px;
+ -ms-perspective: 700px;
+ perspective: 700px;
+}
+
+.reveal.overview .slides section {
+ height: 600px;
+ overflow: hidden;
+ opacity: 1!important;
+ visibility: visible!important;
+ cursor: pointer;
+ background: rgba(0,0,0,0.1);
+}
+.reveal.overview .slides section .fragment {
+ opacity: 1;
+}
+.reveal.overview .slides section:after,
+.reveal.overview .slides section:before {
+ display: none !important;
+}
+.reveal.overview .slides section>section {
+ opacity: 1;
+ cursor: pointer;
+}
+ .reveal.overview .slides section:hover {
+ background: rgba(0,0,0,0.3);
+ }
+ .reveal.overview .slides section.present {
+ background: rgba(0,0,0,0.3);
+ }
+.reveal.overview .slides>section.stack {
+ padding: 0;
+ background: none;
+ overflow: visible;
+}
+
+
+/*********************************************
+ * PAUSED MODE
+ *********************************************/
+
+.reveal .pause-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: black;
+ visibility: hidden;
+ opacity: 0;
+ z-index: 100;
+
+ -webkit-transition: all 1s ease;
+ -moz-transition: all 1s ease;
+ -ms-transition: all 1s ease;
+ -o-transition: all 1s ease;
+ transition: all 1s ease;
+}
+.reveal.paused .pause-overlay {
+ visibility: visible;
+ opacity: 1;
+}
+
+
+/*********************************************
+ * FALLBACK
+ *********************************************/
+
+.no-transforms {
+ overflow-y: auto;
+}
+
+.no-transforms .reveal .slides {
+ position: relative;
+ width: 80%;
+ height: auto !important;
+ top: 0;
+ left: 50%;
+ margin: 0;
+ text-align: center;
+}
+
+.no-transforms .reveal .controls,
+.no-transforms .reveal .progress {
+ display: none !important;
+}
+
+.no-transforms .reveal .slides section {
+ display: block !important;
+ opacity: 1 !important;
+ position: relative !important;
+ height: auto;
+ min-height: auto;
+ top: 0;
+ left: -50%;
+ margin: 70px 0;
+
+ -webkit-transform: none;
+ -moz-transform: none;
+ -ms-transform: none;
+ -o-transform: none;
+ transform: none;
+}
+
+.no-transforms .reveal .slides section section {
+ left: 0;
+}
+
+.no-transition {
+ -webkit-transition: none;
+ -moz-transition: none;
+ -ms-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+
+/*********************************************
+ * BACKGROUND STATES
+ *********************************************/
+
+.reveal .state-background {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: rgba( 0, 0, 0, 0 );
+
+ -webkit-transition: background 800ms ease;
+ -moz-transition: background 800ms ease;
+ -ms-transition: background 800ms ease;
+ -o-transition: background 800ms ease;
+ transition: background 800ms ease;
+}
+.alert .reveal .state-background {
+ background: rgba( 200, 50, 30, 0.6 );
+}
+.soothe .reveal .state-background {
+ background: rgba( 50, 200, 90, 0.4 );
+}
+.blackout .reveal .state-background {
+ background: rgba( 0, 0, 0, 0.6 );
+}
+.whiteout .reveal .state-background {
+ background: rgba( 255, 255, 255, 0.6 );
+}
+.cobalt .reveal .state-background {
+ background: rgba( 22, 152, 213, 0.6 );
+}
+.mint .reveal .state-background {
+ background: rgba( 22, 213, 75, 0.6 );
+}
+.submerge .reveal .state-background {
+ background: rgba( 12, 25, 77, 0.6);
+}
+.lila .reveal .state-background {
+ background: rgba( 180, 50, 140, 0.6 );
+}
+.sunset .reveal .state-background {
+ background: rgba( 255, 122, 0, 0.6 );
+}
+
+
+/*********************************************
+ * RTL SUPPORT
+ *********************************************/
+
+.reveal.rtl .slides,
+.reveal.rtl .slides h1,
+.reveal.rtl .slides h2,
+.reveal.rtl .slides h3,
+.reveal.rtl .slides h4,
+.reveal.rtl .slides h5,
+.reveal.rtl .slides h6 {
+ direction: rtl;
+ font-family: sans-serif;
+}
+
+.reveal.rtl pre,
+.reveal.rtl code {
+ direction: ltr;
+}
+
+.reveal.rtl ol,
+.reveal.rtl ul {
+ text-align: right;
+}
+
+.reveal.rtl .progress span {
+ float: right
+}
+
+
+/*********************************************
+ * SPEAKER NOTES
+ *********************************************/
+
+.reveal aside.notes {
+ display: none;
+}
+
+
+/*********************************************
+ * ZOOM PLUGIN
+ *********************************************/
+
+.zoomed .reveal *,
+.zoomed .reveal *:before,
+.zoomed .reveal *:after {
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ transform: none !important;
+
+ -webkit-backface-visibility: visible !important;
+ -moz-backface-visibility: visible !important;
+ -ms-backface-visibility: visible !important;
+ backface-visibility: visible !important;
+}
+
+.zoomed .reveal .progress,
+.zoomed .reveal .controls {
+ opacity: 0;
+}
+
+.zoomed .reveal .roll span {
+ background: none;
+}
+
+.zoomed .reveal .roll span:after {
+ visibility: hidden;
+}
+
+
diff --git a/src/cpp/session/resources/presentation/revealjs/css/reveal.min.css b/src/cpp/session/resources/presentation/revealjs/css/reveal.min.css
new file mode 100644
index 0000000..467f9d4
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/css/reveal.min.css
@@ -0,0 +1,7 @@
+ at charset "UTF-8";/*!
+ * reveal.js
+ * http://lab.hakim.se/reveal-js
+ * MIT licensed
+ *
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
+ */ html,body,.reveal div,.reveal span,.reveal applet,.reveal object,.reveal iframe,.reveal h1,.reveal h2,.reveal h3,.reveal h4,.reveal h5,.reveal h6,.reveal p,.reveal blockquote,.reveal pre,.reveal a,.reveal abbr,.reveal acronym,.reveal address,.reveal big,.reveal cite,.reveal code,.reveal del,.reveal dfn,.reveal em,.reveal img,.reveal ins,.reveal kbd,.reveal q,.reveal s,.reveal samp,.reveal small,.reveal strike,.reveal strong,.reveal sub,.reveal sup,.reveal tt,.reveal var,.reveal b,.re [...]
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/css/theme/simple.css b/src/cpp/session/resources/presentation/revealjs/css/theme/simple.css
new file mode 100644
index 0000000..9db0fc6
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/css/theme/simple.css
@@ -0,0 +1,130 @@
+/**
+ * A simple theme for reveal.js presentations, similar
+ * to the default theme. The accent color is darkblue.
+ *
+ * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed.
+ * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
+ */
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+body {
+ background: white;
+ background-color: white; }
+
+.reveal {
+ font-family: "Lato", sans-serif;
+ font-size: 36px;
+ font-weight: 200;
+ letter-spacing: -0.02em;
+ color: black; }
+
+::selection {
+ color: white;
+ background: rgba(0, 0, 0, 0.99);
+ text-shadow: none; }
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+ margin: 0 0 20px 0;
+ color: black;
+ font-family: "News Cycle", Impact, sans-serif;
+ line-height: 0.9em;
+ letter-spacing: 0.02em;
+ text-transform: none;
+ text-shadow: none; }
+
+.reveal h1 {
+ text-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2); }
+
+/*********************************************
+ * LINKS
+ *********************************************/
+.reveal a:not(.image) {
+ color: darkblue;
+ text-decoration: none;
+ -webkit-transition: color .15s ease;
+ -moz-transition: color .15s ease;
+ -ms-transition: color .15s ease;
+ -o-transition: color .15s ease;
+ transition: color .15s ease; }
+
+.reveal a:not(.image):hover {
+ color: #0000f1;
+ text-shadow: none;
+ border: none; }
+
+.reveal .roll span:after {
+ color: #fff;
+ background: #00003f; }
+
+/*********************************************
+ * IMAGES
+ *********************************************/
+.reveal section img {
+ margin: 15px 0px;
+ background: rgba(255, 255, 255, 0.12);
+ border: 4px solid black;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+ -webkit-transition: all .2s linear;
+ -moz-transition: all .2s linear;
+ -ms-transition: all .2s linear;
+ -o-transition: all .2s linear;
+ transition: all .2s linear; }
+
+.reveal a:hover img {
+ background: rgba(255, 255, 255, 0.2);
+ border-color: darkblue;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); }
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+.reveal .controls div.navigate-left,
+.reveal .controls div.navigate-left.enabled {
+ border-right-color: darkblue; }
+
+.reveal .controls div.navigate-right,
+.reveal .controls div.navigate-right.enabled {
+ border-left-color: darkblue; }
+
+.reveal .controls div.navigate-up,
+.reveal .controls div.navigate-up.enabled {
+ border-bottom-color: darkblue; }
+
+.reveal .controls div.navigate-down,
+.reveal .controls div.navigate-down.enabled {
+ border-top-color: darkblue; }
+
+.reveal .controls div.navigate-left.enabled:hover {
+ border-right-color: #0000f1; }
+
+.reveal .controls div.navigate-right.enabled:hover {
+ border-left-color: #0000f1; }
+
+.reveal .controls div.navigate-up.enabled:hover {
+ border-bottom-color: #0000f1; }
+
+.reveal .controls div.navigate-down.enabled:hover {
+ border-top-color: #0000f1; }
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+.reveal .progress {
+ background: rgba(0, 0, 0, 0.2); }
+
+.reveal .progress span {
+ background: darkblue;
+ -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
+ -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
+ -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
+ -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
+ transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Bold.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Bold.ttf
new file mode 100644
index 0000000..7434369
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Bold.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/Lato-BoldItalic.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-BoldItalic.ttf
new file mode 100644
index 0000000..684aacf
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-BoldItalic.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Italic.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Italic.ttf
new file mode 100644
index 0000000..3d3b7a2
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Italic.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Regular.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Regular.ttf
new file mode 100644
index 0000000..04ea8ef
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/Lato-Regular.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/Lato.css b/src/cpp/session/resources/presentation/revealjs/fonts/Lato.css
new file mode 100644
index 0000000..f82a319
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/fonts/Lato.css
@@ -0,0 +1,24 @@
+ at font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Lato Regular'), local('Lato-Regular'), url('Lato-Regular.ttf') format('truetype');
+}
+ at font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Lato Bold'), local('Lato-Bold'), url('Lato-Bold.ttf') format('truetype');
+}
+ at font-face {
+ font-family: 'Lato';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Lato Italic'), local('Lato-Italic'), url('Lato-Italic.ttf') format('truetype');
+}
+ at font-face {
+ font-family: 'Lato';
+ font-style: italic;
+ font-weight: 700;
+ src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('Lato-BoldItalic.ttf') format('truetype');
+}
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Bold.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Bold.ttf
new file mode 100644
index 0000000..59d6a2c
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Bold.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Regular.ttf b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Regular.ttf
new file mode 100644
index 0000000..d3b74da
Binary files /dev/null and b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle-Regular.ttf differ
diff --git a/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle.css b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle.css
new file mode 100644
index 0000000..0ad8f80
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/fonts/NewsCycle.css
@@ -0,0 +1,12 @@
+ at font-face {
+ font-family: 'News Cycle';
+ font-style: normal;
+ font-weight: 400;
+ src: local('News Cycle Regular'), local('NewsCycle-Regular'), url('NewsCycle-Regular.ttf') format('truetype');
+}
+ at font-face {
+ font-family: 'News Cycle';
+ font-style: normal;
+ font-weight: 700;
+ src: local('News Cycle Bold'), local('NewsCycle-Bold'), url('NewsCycle-Bold.ttf') format('truetype');
+}
diff --git a/src/cpp/session/resources/presentation/revealjs/js/reveal.js b/src/cpp/session/resources/presentation/revealjs/js/reveal.js
new file mode 100644
index 0000000..a4a71db
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/js/reveal.js
@@ -0,0 +1,2229 @@
+/*!
+ * reveal.js
+ * http://lab.hakim.se/reveal-js
+ * MIT licensed
+ *
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
+ */
+var Reveal = (function(){
+
+ 'use strict';
+
+ var SLIDES_SELECTOR = '.reveal .slides section',
+ HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
+ VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
+ HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-child',
+
+ // Configurations defaults, can be overridden at initialization time
+ config = {
+
+ // The "normal" size of the presentation, aspect ratio will be preserved
+ // when the presentation is scaled to fit different resolutions
+ width: 960,
+ height: 700,
+
+ // Factor of the display size that should remain empty around the content
+ margin: 0.1,
+
+ // Bounds for smallest/largest possible scale to apply to content
+ minScale: 0.2,
+ maxScale: 1.0,
+
+ // Display controls in the bottom right corner
+ controls: true,
+
+ // Display a presentation progress bar
+ progress: true,
+
+ // Push each slide change to the browser history
+ history: false,
+
+ // Enable keyboard shortcuts for navigation
+ keyboard: true,
+
+ // Enable the slide overview mode
+ overview: true,
+
+ // Vertical centring of slides
+ center: true,
+
+ // Enables touch navigation on devices with touch input
+ touch: true,
+
+ // Loop the presentation
+ loop: false,
+
+ // Change the presentation direction to be RTL
+ rtl: false,
+
+ // Turns fragments on and off globally
+ fragments: true,
+
+ // Number of milliseconds between automatically proceeding to the
+ // next slide, disabled when set to 0, this value can be overwritten
+ // by using a data-autoslide attribute on your slides
+ autoSlide: 0,
+
+ // Enable slide navigation via mouse wheel
+ mouseWheel: false,
+
+ // Apply a 3D roll to links on hover
+ rollingLinks: true,
+
+ // Theme (see /css/theme)
+ theme: null,
+
+ // Transition style
+ transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
+
+ // Transition speed
+ transitionSpeed: 'default', // default/fast/slow
+
+ // Script dependencies to load
+ dependencies: []
+ },
+
+ // The current auto-slide duration
+ autoSlide = 0,
+
+ // The horizontal and vertical index of the currently active slide
+ indexh = 0,
+ indexv = 0,
+
+ // The previous and current slide HTML elements
+ previousSlide,
+ currentSlide,
+
+ // Slides may hold a data-state attribute which we pick up and apply
+ // as a class to the body. This list contains the combined state of
+ // all current slides.
+ state = [],
+
+ // The current scale of the presentation (see width/height config)
+ scale = 1,
+
+ // Cached references to DOM elements
+ dom = {},
+
+ // Detect support for CSS 3D transforms
+ supports3DTransforms = 'WebkitPerspective' in document.body.style ||
+ 'MozPerspective' in document.body.style ||
+ 'msPerspective' in document.body.style ||
+ 'OPerspective' in document.body.style ||
+ 'perspective' in document.body.style,
+
+ // Detect support for CSS 2D transforms
+ supports2DTransforms = 'WebkitTransform' in document.body.style ||
+ 'MozTransform' in document.body.style ||
+ 'msTransform' in document.body.style ||
+ 'OTransform' in document.body.style ||
+ 'transform' in document.body.style,
+
+ // Throttles mouse wheel navigation
+ mouseWheelTimeout = 0,
+
+ // An interval used to automatically move on to the next slide
+ autoSlideTimeout = 0,
+
+ // Delays updates to the URL due to a Chrome thumbnailer bug
+ writeURLTimeout = 0,
+
+ // A delay used to activate the overview mode
+ activateOverviewTimeout = 0,
+
+ // A delay used to deactivate the overview mode
+ deactivateOverviewTimeout = 0,
+
+ // Flags if the interaction event listeners are bound
+ eventsAreBound = false,
+
+ // Holds information about the currently ongoing touch input
+ touch = {
+ startX: 0,
+ startY: 0,
+ startSpan: 0,
+ startCount: 0,
+ handled: false,
+ threshold: 80
+ };
+
+ /**
+ * Starts up the presentation if the client is capable.
+ */
+ function initialize( options ) {
+
+ if( !supports2DTransforms && !supports3DTransforms ) {
+ document.body.setAttribute( 'class', 'no-transforms' );
+
+ // If the browser doesn't support core features we won't be
+ // using JavaScript to control the presentation
+ return;
+ }
+
+ // Force a layout when the whole page, incl fonts, has loaded
+ window.addEventListener( 'load', layout, false );
+
+ // Copy options over to our config object
+ extend( config, options );
+
+ // Hide the address bar in mobile browsers
+ hideAddressBar();
+
+ // Loads the dependencies and continues to #start() once done
+ load();
+
+ }
+
+ /**
+ * Finds and stores references to DOM elements which are
+ * required by the presentation. If a required element is
+ * not found, it is created.
+ */
+ function setupDOM() {
+
+ // Cache references to key DOM elements
+ dom.theme = document.querySelector( '#theme' );
+ dom.wrapper = document.querySelector( '.reveal' );
+ dom.slides = document.querySelector( '.reveal .slides' );
+
+ // Progress bar
+ if( !dom.wrapper.querySelector( '.progress' ) ) {
+ var progressElement = document.createElement( 'div' );
+ progressElement.classList.add( 'progress' );
+ progressElement.innerHTML = '<span></span>';
+ dom.wrapper.appendChild( progressElement );
+ }
+
+ // Arrow controls
+ if( !dom.wrapper.querySelector( '.controls' ) ) {
+ var controlsElement = document.createElement( 'aside' );
+ controlsElement.classList.add( 'controls' );
+ controlsElement.innerHTML = '<div class="navigate-left"></div>' +
+ '<div class="navigate-right"></div>' +
+ '<div class="navigate-up"></div>' +
+ '<div class="navigate-down"></div>';
+ dom.wrapper.appendChild( controlsElement );
+ }
+
+ // Presentation background element
+ if( !dom.wrapper.querySelector( '.state-background' ) ) {
+ var backgroundElement = document.createElement( 'div' );
+ backgroundElement.classList.add( 'state-background' );
+ dom.wrapper.appendChild( backgroundElement );
+ }
+
+ // Overlay graphic which is displayed during the paused mode
+ if( !dom.wrapper.querySelector( '.pause-overlay' ) ) {
+ var pausedElement = document.createElement( 'div' );
+ pausedElement.classList.add( 'pause-overlay' );
+ dom.wrapper.appendChild( pausedElement );
+ }
+
+ // Cache references to elements
+ dom.progress = document.querySelector( '.reveal .progress' );
+ dom.progressbar = document.querySelector( '.reveal .progress span' );
+
+ if ( config.controls ) {
+ dom.controls = document.querySelector( '.reveal .controls' );
+
+ // There can be multiple instances of controls throughout the page
+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
+ }
+
+ }
+
+ /**
+ * Hides the address bar if we're on a mobile device.
+ */
+ function hideAddressBar() {
+
+ if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
+ // Events that should trigger the address bar to hide
+ window.addEventListener( 'load', removeAddressBar, false );
+ window.addEventListener( 'orientationchange', removeAddressBar, false );
+ }
+
+ }
+
+ /**
+ * Loads the dependencies of reveal.js. Dependencies are
+ * defined via the configuration option 'dependencies'
+ * and will be loaded prior to starting/binding reveal.js.
+ * Some dependencies may have an 'async' flag, if so they
+ * will load after reveal.js has been started up.
+ */
+ function load() {
+
+ var scripts = [],
+ scriptsAsync = [];
+
+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
+ var s = config.dependencies[i];
+
+ // Load if there's no condition or the condition is truthy
+ if( !s.condition || s.condition() ) {
+ if( s.async ) {
+ scriptsAsync.push( s.src );
+ }
+ else {
+ scripts.push( s.src );
+ }
+
+ // Extension may contain callback functions
+ if( typeof s.callback === 'function' ) {
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
+ }
+ }
+ }
+
+ // Called once synchronous scripts finish loading
+ function proceed() {
+ if( scriptsAsync.length ) {
+ // Load asynchronous scripts
+ head.js.apply( null, scriptsAsync );
+ }
+
+ start();
+ }
+
+ if( scripts.length ) {
+ head.ready( proceed );
+
+ // Load synchronous scripts
+ head.js.apply( null, scripts );
+ }
+ else {
+ proceed();
+ }
+
+ }
+
+ /**
+ * Starts up reveal.js by binding input events and navigating
+ * to the current URL deeplink if there is one.
+ */
+ function start() {
+
+ // Make sure we've got all the DOM elements we need
+ setupDOM();
+
+ // Updates the presentation to match the current configuration values
+ configure();
+
+ // Read the initial hash
+ readURL();
+
+ // Notify listeners that the presentation is ready but use a 1ms
+ // timeout to ensure it's not fired synchronously after #initialize()
+ setTimeout( function() {
+ dispatchEvent( 'ready', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'currentSlide': currentSlide
+ } );
+ }, 1 );
+
+ }
+
+ /**
+ * Applies the configuration settings from the config
+ * object. May be called multiple times.
+ */
+ function configure( options ) {
+
+ dom.wrapper.classList.remove( config.transition );
+
+ // New config options may be passed when this method
+ // is invoked through the API after initialization
+ if( typeof options === 'object' ) extend( config, options );
+
+ // Force linear transition based on browser capabilities
+ if( supports3DTransforms === false ) config.transition = 'linear';
+
+ dom.wrapper.classList.add( config.transition );
+
+ dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
+
+ if( dom.controls ) {
+ dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
+ }
+
+ if( dom.progress ) {
+ dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
+ }
+
+ if( config.rtl ) {
+ dom.wrapper.classList.add( 'rtl' );
+ }
+ else {
+ dom.wrapper.classList.remove( 'rtl' );
+ }
+
+ if( config.center ) {
+ dom.wrapper.classList.add( 'center' );
+ }
+ else {
+ dom.wrapper.classList.remove( 'center' );
+ }
+
+ if( config.mouseWheel ) {
+ document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
+ document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
+ }
+ else {
+ document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
+ document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
+ }
+
+ // 3D links
+ if( config.rollingLinks ) {
+ enable3DLinks();
+ }
+ else {
+ disable3DLinks();
+ }
+
+ // Load the theme in the config, if it's not already loaded
+ if( config.theme && dom.theme ) {
+ var themeURL = dom.theme.getAttribute( 'href' );
+ var themeFinder = /[^\/]*?(?=\.css)/;
+ var themeName = themeURL.match(themeFinder)[0];
+
+ if( config.theme !== themeName ) {
+ themeURL = themeURL.replace(themeFinder, config.theme);
+ dom.theme.setAttribute( 'href', themeURL );
+ }
+ }
+
+ sync();
+
+ }
+
+ /**
+ * Binds all event listeners.
+ */
+ function addEventListeners() {
+
+ eventsAreBound = true;
+
+ window.addEventListener( 'hashchange', onWindowHashChange, false );
+ window.addEventListener( 'resize', onWindowResize, false );
+
+ if( config.touch ) {
+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
+
+ // Support pointer-style touch interaction as well
+ if( window.navigator.msPointerEnabled ) {
+ dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
+ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
+ dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
+ }
+ }
+
+ if( config.keyboard ) {
+ document.addEventListener( 'keydown', onDocumentKeyDown, false );
+ }
+
+ if ( config.progress && dom.progress ) {
+ dom.progress.addEventListener( 'click', onProgressClicked, false );
+ }
+
+ if ( config.controls && dom.controls ) {
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
+ } );
+ }
+
+ }
+
+ /**
+ * Unbinds all event listeners.
+ */
+ function removeEventListeners() {
+
+ eventsAreBound = false;
+
+ document.removeEventListener( 'keydown', onDocumentKeyDown, false );
+ window.removeEventListener( 'hashchange', onWindowHashChange, false );
+ window.removeEventListener( 'resize', onWindowResize, false );
+
+ dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
+ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
+ dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
+
+ if( window.navigator.msPointerEnabled ) {
+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
+ }
+
+ if ( config.progress && dom.progress ) {
+ dom.progress.removeEventListener( 'click', onProgressClicked, false );
+ }
+
+ if ( config.controls && dom.controls ) {
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
+ } );
+ }
+
+ }
+
+ /**
+ * Extend object a with the properties of object b.
+ * If there's a conflict, object b takes precedence.
+ */
+ function extend( a, b ) {
+
+ for( var i in b ) {
+ a[ i ] = b[ i ];
+ }
+
+ }
+
+ /**
+ * Converts the target object to an array.
+ */
+ function toArray( o ) {
+
+ return Array.prototype.slice.call( o );
+
+ }
+
+ /**
+ * Measures the distance in pixels between point a
+ * and point b.
+ *
+ * @param {Object} a point with x/y properties
+ * @param {Object} b point with x/y properties
+ */
+ function distanceBetween( a, b ) {
+
+ var dx = a.x - b.x,
+ dy = a.y - b.y;
+
+ return Math.sqrt( dx*dx + dy*dy );
+
+ }
+
+ /**
+ * Causes the address bar to hide on mobile devices,
+ * more vertical space ftw.
+ */
+ function removeAddressBar() {
+
+ if( window.orientation === 0 ) {
+ document.documentElement.style.overflow = 'scroll';
+ document.body.style.height = '120%';
+ }
+ else {
+ document.documentElement.style.overflow = '';
+ document.body.style.height = '100%';
+ }
+
+ setTimeout( function() {
+ window.scrollTo( 0, 1 );
+ }, 10 );
+
+ }
+
+ /**
+ * Dispatches an event of the specified type from the
+ * reveal DOM element.
+ */
+ function dispatchEvent( type, properties ) {
+
+ var event = document.createEvent( "HTMLEvents", 1, 2 );
+ event.initEvent( type, true, true );
+ extend( event, properties );
+ dom.wrapper.dispatchEvent( event );
+
+ }
+
+ /**
+ * Wrap all links in 3D goodness.
+ */
+ function enable3DLinks() {
+
+ if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
+
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
+ var anchor = anchors[i];
+
+ if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
+ var span = document.createElement('span');
+ span.setAttribute('data-title', anchor.text);
+ span.innerHTML = anchor.innerHTML;
+
+ anchor.classList.add( 'roll' );
+ anchor.innerHTML = '';
+ anchor.appendChild(span);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Unwrap all 3D links.
+ */
+ function disable3DLinks() {
+
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
+
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
+ var anchor = anchors[i];
+ var span = anchor.querySelector( 'span' );
+
+ if( span ) {
+ anchor.classList.remove( 'roll' );
+ anchor.innerHTML = span.innerHTML;
+ }
+ }
+
+ }
+
+ /**
+ * Return a sorted fragments list, ordered by an increasing
+ * "data-fragment-index" attribute.
+ *
+ * Fragments will be revealed in the order that they are returned by
+ * this function, so you can use the index attributes to control the
+ * order of fragment appearance.
+ *
+ * To maintain a sensible default fragment order, fragments are presumed
+ * to be passed in document order. This function adds a "fragment-index"
+ * attribute to each node if such an attribute is not already present,
+ * and sets that attribute to an integer value which is the position of
+ * the fragment within the fragments list.
+ */
+ function sortFragments( fragments ) {
+
+ var a = toArray( fragments );
+
+ a.forEach( function( el, idx ) {
+ if( !el.hasAttribute( 'data-fragment-index' ) ) {
+ el.setAttribute( 'data-fragment-index', idx );
+ }
+ } );
+
+ a.sort( function( l, r ) {
+ return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
+ } );
+
+ return a;
+
+ }
+
+ /**
+ * Applies JavaScript-controlled layout rules to the
+ * presentation.
+ */
+ function layout() {
+
+ if( dom.wrapper ) {
+
+ // Available space to scale within
+ var availableWidth = dom.wrapper.offsetWidth,
+ availableHeight = dom.wrapper.offsetHeight;
+
+ // Reduce available space by margin
+ availableWidth -= ( availableHeight * config.margin );
+ availableHeight -= ( availableHeight * config.margin );
+
+ // Dimensions of the content
+ var slideWidth = config.width,
+ slideHeight = config.height;
+
+ // Slide width may be a percentage of available width
+ if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
+ slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
+ }
+
+ // Slide height may be a percentage of available height
+ if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
+ slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
+ }
+
+ dom.slides.style.width = slideWidth + 'px';
+ dom.slides.style.height = slideHeight + 'px';
+
+ // Determine scale of content to fit within available space
+ scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
+
+ // Respect max/min scale settings
+ scale = Math.max( scale, config.minScale );
+ scale = Math.min( scale, config.maxScale );
+
+ // Prefer applying scale via zoom since Chrome blurs scaled content
+ // with nested transforms
+ if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
+ dom.slides.style.zoom = scale;
+ }
+ // Apply scale transform as a fallback
+ else {
+ var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)';
+
+ dom.slides.style.WebkitTransform = transform;
+ dom.slides.style.MozTransform = transform;
+ dom.slides.style.msTransform = transform;
+ dom.slides.style.OTransform = transform;
+ dom.slides.style.transform = transform;
+ }
+
+ // Select all slides, vertical and horizontal
+ var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
+
+ for( var i = 0, len = slides.length; i < len; i++ ) {
+ var slide = slides[ i ];
+
+ // Don't bother updating invisible slides
+ if( slide.style.display === 'none' ) {
+ continue;
+ }
+
+ if( config.center ) {
+ // Vertical stacks are not centred since their section
+ // children will be
+ if( slide.classList.contains( 'stack' ) ) {
+ slide.style.top = 0;
+ }
+ else {
+ slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, -slideHeight / 2 ) + 'px';
+ }
+ }
+ else {
+ slide.style.top = '';
+ }
+
+ }
+
+ updateProgress();
+
+ }
+
+ }
+
+ /**
+ * Stores the vertical index of a stack so that the same
+ * vertical slide can be selected when navigating to and
+ * from the stack.
+ *
+ * @param {HTMLElement} stack The vertical stack element
+ * @param {int} v Index to memorize
+ */
+ function setPreviousVerticalIndex( stack, v ) {
+
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
+ stack.setAttribute( 'data-previous-indexv', v || 0 );
+ }
+
+ }
+
+ /**
+ * Retrieves the vertical index which was stored using
+ * #setPreviousVerticalIndex() or 0 if no previous index
+ * exists.
+ *
+ * @param {HTMLElement} stack The vertical stack element
+ */
+ function getPreviousVerticalIndex( stack ) {
+
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
+ return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
+ }
+
+ return 0;
+
+ }
+
+ /**
+ * Displays the overview of slides (quick nav) by
+ * scaling down and arranging all slide elements.
+ *
+ * Experimental feature, might be dropped if perf
+ * can't be improved.
+ */
+ function activateOverview() {
+
+ // Only proceed if enabled in config
+ if( config.overview ) {
+
+ // Don't auto-slide while in overview mode
+ cancelAutoSlide();
+
+ var wasActive = dom.wrapper.classList.contains( 'overview' );
+
+ dom.wrapper.classList.add( 'overview' );
+ dom.wrapper.classList.remove( 'exit-overview' );
+
+ clearTimeout( activateOverviewTimeout );
+ clearTimeout( deactivateOverviewTimeout );
+
+ // Not the pretties solution, but need to let the overview
+ // class apply first so that slides are measured accurately
+ // before we can position them
+ activateOverviewTimeout = setTimeout( function(){
+
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
+ var hslide = horizontalSlides[i],
+ hoffset = config.rtl ? -105 : 105,
+ htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)';
+
+ hslide.setAttribute( 'data-index-h', i );
+ hslide.style.display = 'block';
+ hslide.style.WebkitTransform = htransform;
+ hslide.style.MozTransform = htransform;
+ hslide.style.msTransform = htransform;
+ hslide.style.OTransform = htransform;
+ hslide.style.transform = htransform;
+
+ if( hslide.classList.contains( 'stack' ) ) {
+
+ var verticalSlides = hslide.querySelectorAll( 'section' );
+
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
+
+ var vslide = verticalSlides[j],
+ vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)';
+
+ vslide.setAttribute( 'data-index-h', i );
+ vslide.setAttribute( 'data-index-v', j );
+ vslide.style.display = 'block';
+ vslide.style.WebkitTransform = vtransform;
+ vslide.style.MozTransform = vtransform;
+ vslide.style.msTransform = vtransform;
+ vslide.style.OTransform = vtransform;
+ vslide.style.transform = vtransform;
+
+ // Navigate to this slide on click
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
+ }
+
+ }
+ else {
+
+ // Navigate to this slide on click
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
+
+ }
+ }
+
+ layout();
+
+ if( !wasActive ) {
+ // Notify observers of the overview showing
+ dispatchEvent( 'overviewshown', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'currentSlide': currentSlide
+ } );
+ }
+
+ }, 10 );
+
+ }
+
+ }
+
+ /**
+ * Exits the slide overview and enters the currently
+ * active slide.
+ */
+ function deactivateOverview() {
+
+ // Only proceed if enabled in config
+ if( config.overview ) {
+
+ clearTimeout( activateOverviewTimeout );
+ clearTimeout( deactivateOverviewTimeout );
+
+ dom.wrapper.classList.remove( 'overview' );
+
+ // Temporarily add a class so that transitions can do different things
+ // depending on whether they are exiting/entering overview, or just
+ // moving from slide to slide
+ dom.wrapper.classList.add( 'exit-overview' );
+
+ deactivateOverviewTimeout = setTimeout( function () {
+ dom.wrapper.classList.remove( 'exit-overview' );
+ }, 10);
+
+ // Select all slides
+ var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
+
+ for( var i = 0, len = slides.length; i < len; i++ ) {
+ var element = slides[i];
+
+ element.style.display = '';
+
+ // Resets all transforms to use the external styles
+ element.style.WebkitTransform = '';
+ element.style.MozTransform = '';
+ element.style.msTransform = '';
+ element.style.OTransform = '';
+ element.style.transform = '';
+
+ element.removeEventListener( 'click', onOverviewSlideClicked, true );
+ }
+
+ slide( indexh, indexv );
+
+ cueAutoSlide();
+
+ // Notify observers of the overview hiding
+ dispatchEvent( 'overviewhidden', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'currentSlide': currentSlide
+ } );
+
+ }
+ }
+
+ /**
+ * Toggles the slide overview mode on and off.
+ *
+ * @param {Boolean} override Optional flag which overrides the
+ * toggle logic and forcibly sets the desired state. True means
+ * overview is open, false means it's closed.
+ */
+ function toggleOverview( override ) {
+
+ if( typeof override === 'boolean' ) {
+ override ? activateOverview() : deactivateOverview();
+ }
+ else {
+ isOverview() ? deactivateOverview() : activateOverview();
+ }
+
+ }
+
+ /**
+ * Checks if the overview is currently active.
+ *
+ * @return {Boolean} true if the overview is active,
+ * false otherwise
+ */
+ function isOverview() {
+
+ return dom.wrapper.classList.contains( 'overview' );
+
+ }
+
+ /**
+ * Checks if the current or specified slide is vertical
+ * (nested within another slide).
+ *
+ * @param {HTMLElement} slide [optional] The slide to check
+ * orientation of
+ */
+ function isVerticalSlide( slide ) {
+
+ // Prefer slide argument, otherwise use current slide
+ slide = slide ? slide : currentSlide;
+
+ return slide && !!slide.parentNode.nodeName.match( /section/i );
+
+ }
+
+ /**
+ * Handling the fullscreen functionality via the fullscreen API
+ *
+ * @see http://fullscreen.spec.whatwg.org/
+ * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
+ */
+ function enterFullscreen() {
+
+ var element = document.body;
+
+ // Check which implementation is available
+ var requestMethod = element.requestFullScreen ||
+ element.webkitRequestFullscreen ||
+ element.webkitRequestFullScreen ||
+ element.mozRequestFullScreen ||
+ element.msRequestFullScreen;
+
+ if( requestMethod ) {
+ requestMethod.apply( element );
+ }
+
+ }
+
+ /**
+ * Enters the paused mode which fades everything on screen to
+ * black.
+ */
+ function pause() {
+
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
+
+ cancelAutoSlide();
+ dom.wrapper.classList.add( 'paused' );
+
+ if( wasPaused === false ) {
+ dispatchEvent( 'paused' );
+ }
+
+ }
+
+ /**
+ * Exits from the paused mode.
+ */
+ function resume() {
+
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
+ dom.wrapper.classList.remove( 'paused' );
+
+ cueAutoSlide();
+
+ if( wasPaused ) {
+ dispatchEvent( 'resumed' );
+ }
+
+ }
+
+ /**
+ * Toggles the paused mode on and off.
+ */
+ function togglePause() {
+
+ if( isPaused() ) {
+ resume();
+ }
+ else {
+ pause();
+ }
+
+ }
+
+ /**
+ * Checks if we are currently in the paused mode.
+ */
+ function isPaused() {
+
+ return dom.wrapper.classList.contains( 'paused' );
+
+ }
+
+ /**
+ * Steps from the current point in the presentation to the
+ * slide which matches the specified horizontal and vertical
+ * indices.
+ *
+ * @param {int} h Horizontal index of the target slide
+ * @param {int} v Vertical index of the target slide
+ * @param {int} f Optional index of a fragment within the
+ * target slide to activate
+ * @param {int} o Optional origin for use in multimaster environments
+ */
+ function slide( h, v, f, o ) {
+
+ // Remember where we were at before
+ previousSlide = currentSlide;
+
+ // Query all horizontal slides in the deck
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+
+ // If no vertical index is specified and the upcoming slide is a
+ // stack, resume at its previous vertical index
+ if( v === undefined ) {
+ v = getPreviousVerticalIndex( horizontalSlides[ h ] );
+ }
+
+ // If we were on a vertical stack, remember what vertical index
+ // it was on so we can resume at the same position when returning
+ if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
+ setPreviousVerticalIndex( previousSlide.parentNode, indexv );
+ }
+
+ // Remember the state before this slide
+ var stateBefore = state.concat();
+
+ // Reset the state array
+ state.length = 0;
+
+ var indexhBefore = indexh,
+ indexvBefore = indexv;
+
+ // Activate and transition to the new slide
+ indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
+ indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
+
+ layout();
+
+ // Apply the new state
+ stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
+ // Check if this state existed on the previous slide. If it
+ // did, we will avoid adding it repeatedly
+ for( var j = 0; j < stateBefore.length; j++ ) {
+ if( stateBefore[j] === state[i] ) {
+ stateBefore.splice( j, 1 );
+ continue stateLoop;
+ }
+ }
+
+ document.documentElement.classList.add( state[i] );
+
+ // Dispatch custom event matching the state's name
+ dispatchEvent( state[i] );
+ }
+
+ // Clean up the remains of the previous state
+ while( stateBefore.length ) {
+ document.documentElement.classList.remove( stateBefore.pop() );
+ }
+
+ // If the overview is active, re-activate it to update positions
+ if( isOverview() ) {
+ activateOverview();
+ }
+
+ // Update the URL hash after a delay since updating it mid-transition
+ // is likely to cause visual lag
+ writeURL( 1500 );
+
+ // Find the current horizontal slide and any possible vertical slides
+ // within it
+ var currentHorizontalSlide = horizontalSlides[ indexh ],
+ currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
+
+ // Store references to the previous and current slides
+ currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
+
+
+ // Show fragment, if specified
+ if( typeof f !== 'undefined' ) {
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
+
+ toArray( fragments ).forEach( function( fragment, indexf ) {
+ if( indexf < f ) {
+ fragment.classList.add( 'visible' );
+ }
+ else {
+ fragment.classList.remove( 'visible' );
+ }
+ } );
+ }
+
+ // Dispatch an event if the slide changed
+ if( indexh !== indexhBefore || indexv !== indexvBefore ) {
+ dispatchEvent( 'slidechanged', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'previousSlide': previousSlide,
+ 'currentSlide': currentSlide,
+ 'origin': o
+ } );
+ }
+ else {
+ // Ensure that the previous slide is never the same as the current
+ previousSlide = null;
+ }
+
+ // Solves an edge case where the previous slide maintains the
+ // 'present' class when navigating between adjacent vertical
+ // stacks
+ if( previousSlide ) {
+ previousSlide.classList.remove( 'present' );
+
+ // Reset all slides upon navigate to home
+ // Issue: #285
+ if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
+ // Launch async task
+ setTimeout( function () {
+ var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
+ for( i in slides ) {
+ if( slides[i] ) {
+ // Reset stack
+ setPreviousVerticalIndex( slides[i], 0 );
+ }
+ }
+ }, 0 );
+ }
+ }
+
+ // Handle embedded content
+ stopEmbeddedContent( previousSlide );
+ startEmbeddedContent( currentSlide );
+
+ updateControls();
+ updateProgress();
+
+ }
+
+ /**
+ * Syncs the presentation with the current DOM. Useful
+ * when new slides or control elements are added or when
+ * the configuration has changed.
+ */
+ function sync() {
+
+ // Subscribe to input
+ removeEventListeners();
+ addEventListeners();
+
+ // Force a layout to make sure the current config is accounted for
+ layout();
+
+ // Reflect the current autoSlide value
+ autoSlide = config.autoSlide;
+
+ // Start auto-sliding if it's enabled
+ cueAutoSlide();
+
+ updateControls();
+ updateProgress();
+
+ }
+
+ /**
+ * Updates one dimension of slides by showing the slide
+ * with the specified index.
+ *
+ * @param {String} selector A CSS selector that will fetch
+ * the group of slides we are working with
+ * @param {Number} index The index of the slide that should be
+ * shown
+ *
+ * @return {Number} The index of the slide that is now shown,
+ * might differ from the passed in index if it was out of
+ * bounds.
+ */
+ function updateSlides( selector, index ) {
+
+ // Select all slides and convert the NodeList result to
+ // an array
+ var slides = toArray( document.querySelectorAll( selector ) ),
+ slidesLength = slides.length;
+
+ if( slidesLength ) {
+
+ // Should the index loop?
+ if( config.loop ) {
+ index %= slidesLength;
+
+ if( index < 0 ) {
+ index = slidesLength + index;
+ }
+ }
+
+ // Enforce max and minimum index bounds
+ index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
+
+ for( var i = 0; i < slidesLength; i++ ) {
+ var element = slides[i];
+
+ // Optimization; hide all slides that are three or more steps
+ // away from the present slide
+ if( isOverview() === false ) {
+ // The distance loops so that it measures 1 between the first
+ // and last slides
+ var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
+
+ element.style.display = distance > 3 ? 'none' : 'block';
+ }
+
+ var reverse = config.rtl && !isVerticalSlide( element );
+
+ element.classList.remove( 'past' );
+ element.classList.remove( 'present' );
+ element.classList.remove( 'future' );
+
+ if( i < index ) {
+ // Any element previous to index is given the 'past' class
+ element.classList.add( reverse ? 'future' : 'past' );
+ }
+ else if( i > index ) {
+ // Any element subsequent to index is given the 'future' class
+ element.classList.add( reverse ? 'past' : 'future' );
+ }
+
+ // If this element contains vertical slides
+ if( element.querySelector( 'section' ) ) {
+ element.classList.add( 'stack' );
+ }
+ }
+
+ // Mark the current slide as present
+ slides[index].classList.add( 'present' );
+
+ // If this slide has a state associated with it, add it
+ // onto the current state of the deck
+ var slideState = slides[index].getAttribute( 'data-state' );
+ if( slideState ) {
+ state = state.concat( slideState.split( ' ' ) );
+ }
+
+ // If this slide has a data-autoslide attribtue associated use this as
+ // autoSlide value otherwise use the global configured time
+ var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
+ if( slideAutoSlide ) {
+ autoSlide = parseInt( slideAutoSlide, 10 );
+ }
+ else {
+ autoSlide = config.autoSlide;
+ }
+
+ }
+ else {
+ // Since there are no slides we can't be anywhere beyond the
+ // zeroth index
+ index = 0;
+ }
+
+ return index;
+
+ }
+
+ /**
+ * Updates the progress bar to reflect the current slide.
+ */
+ function updateProgress() {
+
+ // Update progress if enabled
+ if( config.progress && dom.progress ) {
+
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+
+ // The number of past and total slides
+ var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
+ var pastCount = 0;
+
+ // Step through all slides and count the past ones
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
+
+ var horizontalSlide = horizontalSlides[i];
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
+
+ for( var j = 0; j < verticalSlides.length; j++ ) {
+
+ // Stop as soon as we arrive at the present
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
+ break mainLoop;
+ }
+
+ pastCount++;
+
+ }
+
+ // Stop as soon as we arrive at the present
+ if( horizontalSlide.classList.contains( 'present' ) ) {
+ break;
+ }
+
+ // Don't count the wrapping section for vertical slides
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
+ pastCount++;
+ }
+
+ }
+
+ dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
+
+ }
+
+ }
+
+ /**
+ * Updates the state of all control/navigation arrows.
+ */
+ function updateControls() {
+
+ if ( config.controls && dom.controls ) {
+
+ var routes = availableRoutes();
+ var fragments = availableFragments();
+
+ // Remove the 'enabled' class from all directions
+ dom.controlsLeft.concat( dom.controlsRight )
+ .concat( dom.controlsUp )
+ .concat( dom.controlsDown )
+ .concat( dom.controlsPrev )
+ .concat( dom.controlsNext ).forEach( function( node ) {
+ node.classList.remove( 'enabled' );
+ node.classList.remove( 'fragmented' );
+ } );
+
+ // Add the 'enabled' class to the available routes
+ if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+ if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+ if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+ if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+
+ // Prev/next buttons
+ if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+ if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
+
+ // Highlight fragment directions
+ if( currentSlide ) {
+
+ // Always apply fragment decorator to prev/next buttons
+ if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+ if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+
+ // Apply fragment decorators to directional buttons based on
+ // what slide axis they are in
+ if( isVerticalSlide( currentSlide ) ) {
+ if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+ if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+ }
+ else {
+ if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+ if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * Determine what available routes there are for navigation.
+ *
+ * @return {Object} containing four booleans: left/right/up/down
+ */
+ function availableRoutes() {
+
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
+
+ var routes = {
+ left: indexh > 0 || config.loop,
+ right: indexh < horizontalSlides.length - 1 || config.loop,
+ up: indexv > 0,
+ down: indexv < verticalSlides.length - 1
+ };
+
+ // reverse horizontal controls for rtl
+ if( config.rtl ) {
+ var left = routes.left;
+ routes.left = routes.right;
+ routes.right = left;
+ }
+
+ return routes;
+
+ }
+
+ /**
+ * Returns an object describing the available fragment
+ * directions.
+ *
+ * @return {Object} two boolean properties: prev/next
+ */
+ function availableFragments() {
+
+ if( currentSlide && config.fragments ) {
+ var fragments = currentSlide.querySelectorAll( '.fragment' );
+ var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
+
+ return {
+ prev: fragments.length - hiddenFragments.length > 0,
+ next: !!hiddenFragments.length
+ };
+ }
+ else {
+ return { prev: false, next: false };
+ }
+
+ }
+
+ /**
+ * Start playback of any embedded content inside of
+ * the targeted slide.
+ */
+ function startEmbeddedContent( slide ) {
+
+ if( slide ) {
+ // HTML5 media elements
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
+ if( el.hasAttribute( 'data-autoplay' ) ) {
+ el.play();
+ }
+ } );
+
+ // YouTube embeds
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
+ if( el.hasAttribute( 'data-autoplay' ) ) {
+ el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
+ }
+ });
+ }
+
+ }
+
+ /**
+ * Stop playback of any embedded content inside of
+ * the targeted slide.
+ */
+ function stopEmbeddedContent( slide ) {
+
+ if( slide ) {
+ // HTML5 media elements
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
+ if( !el.hasAttribute( 'data-ignore' ) ) {
+ el.pause();
+ }
+ } );
+
+ // YouTube embeds
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
+ el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
+ }
+ });
+ }
+
+ }
+
+ /**
+ * Reads the current URL (hash) and navigates accordingly.
+ */
+ function readURL() {
+
+ var hash = window.location.hash;
+
+ // Attempt to parse the hash as either an index or name
+ var bits = hash.slice( 2 ).split( '/' ),
+ name = hash.replace( /#|\//gi, '' );
+
+ // If the first bit is invalid and there is a name we can
+ // assume that this is a named link
+ if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
+ // Find the slide with the specified name
+ var element = document.querySelector( '#' + name );
+
+ if( element ) {
+ // Find the position of the named slide and navigate to it
+ var indices = Reveal.getIndices( element );
+ slide( indices.h, indices.v );
+ }
+ // If the slide doesn't exist, navigate to the current slide
+ else {
+ slide( indexh, indexv );
+ }
+ }
+ else {
+ // Read the index components of the hash
+ var h = parseInt( bits[0], 10 ) || 0,
+ v = parseInt( bits[1], 10 ) || 0;
+
+ slide( h, v );
+ }
+
+ }
+
+ /**
+ * Updates the page URL (hash) to reflect the current
+ * state.
+ *
+ * @param {Number} delay The time in ms to wait before
+ * writing the hash
+ */
+ function writeURL( delay ) {
+
+ if( config.history ) {
+
+ // Make sure there's never more than one timeout running
+ clearTimeout( writeURLTimeout );
+
+ // If a delay is specified, timeout this call
+ if( typeof delay === 'number' ) {
+ writeURLTimeout = setTimeout( writeURL, delay );
+ }
+ else {
+ var url = '/';
+
+ // If the current slide has an ID, use that as a named link
+ if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
+ url = '/' + currentSlide.getAttribute( 'id' );
+ }
+ // Otherwise use the /h/v index
+ else {
+ if( indexh > 0 || indexv > 0 ) url += indexh;
+ if( indexv > 0 ) url += '/' + indexv;
+ }
+
+ window.location.hash = url;
+ }
+ }
+
+ }
+
+ /**
+ * Retrieves the h/v location of the current, or specified,
+ * slide.
+ *
+ * @param {HTMLElement} slide If specified, the returned
+ * index will be for this slide rather than the currently
+ * active one
+ *
+ * @return {Object} { h: <int>, v: <int>, f: <int> }
+ */
+ function getIndices( slide ) {
+
+ // By default, return the current indices
+ var h = indexh,
+ v = indexv,
+ f;
+
+ // If a slide is specified, return the indices of that slide
+ if( slide ) {
+ var isVertical = isVerticalSlide( slide );
+ var slideh = isVertical ? slide.parentNode : slide;
+
+ // Select all horizontal slides
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+
+ // Now that we know which the horizontal slide is, get its index
+ h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
+
+ // If this is a vertical slide, grab the vertical index
+ if( isVertical ) {
+ v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
+ }
+ }
+
+ if( !slide && currentSlide ) {
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
+ if( visibleFragments.length ) {
+ f = visibleFragments.length;
+ }
+ }
+
+ return { h: h, v: v, f: f };
+
+ }
+
+ /**
+ * Navigate to the next slide fragment.
+ *
+ * @return {Boolean} true if there was a next fragment,
+ * false otherwise
+ */
+ function nextFragment() {
+
+ if( currentSlide && config.fragments ) {
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
+
+ if( fragments.length ) {
+ fragments[0].classList.add( 'visible' );
+
+ // Notify subscribers of the change
+ dispatchEvent( 'fragmentshown', { fragment: fragments[0] } );
+
+ updateControls();
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Navigate to the previous slide fragment.
+ *
+ * @return {Boolean} true if there was a previous fragment,
+ * false otherwise
+ */
+ function previousFragment() {
+
+ if( currentSlide && config.fragments ) {
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
+
+ if( fragments.length ) {
+ fragments[ fragments.length - 1 ].classList.remove( 'visible' );
+
+ // Notify subscribers of the change
+ dispatchEvent( 'fragmenthidden', { fragment: fragments[ fragments.length - 1 ] } );
+
+ updateControls();
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Cues a new automated slide if enabled in the config.
+ */
+ function cueAutoSlide() {
+
+ clearTimeout( autoSlideTimeout );
+
+ // Cue the next auto-slide if enabled
+ if( autoSlide && !isPaused() && !isOverview() ) {
+ autoSlideTimeout = setTimeout( navigateNext, autoSlide );
+ }
+
+ }
+
+ /**
+ * Cancels any ongoing request to auto-slide.
+ */
+ function cancelAutoSlide() {
+
+ clearTimeout( autoSlideTimeout );
+
+ }
+
+ function navigateLeft() {
+
+ // Reverse for RTL
+ if( config.rtl ) {
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
+ slide( indexh + 1 );
+ }
+ }
+ // Normal navigation
+ else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
+ slide( indexh - 1 );
+ }
+
+ }
+
+ function navigateRight() {
+
+ // Reverse for RTL
+ if( config.rtl ) {
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
+ slide( indexh - 1 );
+ }
+ }
+ // Normal navigation
+ else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
+ slide( indexh + 1 );
+ }
+
+ }
+
+ function navigateUp() {
+
+ // Prioritize hiding fragments
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
+ slide( indexh, indexv - 1 );
+ }
+
+ }
+
+ function navigateDown() {
+
+ // Prioritize revealing fragments
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
+ slide( indexh, indexv + 1 );
+ }
+
+ }
+
+ /**
+ * Navigates backwards, prioritized in the following order:
+ * 1) Previous fragment
+ * 2) Previous vertical slide
+ * 3) Previous horizontal slide
+ */
+ function navigatePrev() {
+
+ // Prioritize revealing fragments
+ if( previousFragment() === false ) {
+ if( availableRoutes().up ) {
+ navigateUp();
+ }
+ else {
+ // Fetch the previous horizontal slide, if there is one
+ var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
+
+ if( previousSlide ) {
+ indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
+ indexh --;
+ slide( indexh, indexv );
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Same as #navigatePrev() but navigates forwards.
+ */
+ function navigateNext() {
+
+ // Prioritize revealing fragments
+ if( nextFragment() === false ) {
+ availableRoutes().down ? navigateDown() : navigateRight();
+ }
+
+ // If auto-sliding is enabled we need to cue up
+ // another timeout
+ cueAutoSlide();
+
+ }
+
+
+ // --------------------------------------------------------------------//
+ // ----------------------------- EVENTS -------------------------------//
+ // --------------------------------------------------------------------//
+
+
+ /**
+ * Handler for the document level 'keydown' event.
+ *
+ * @param {Object} event
+ */
+ function onDocumentKeyDown( event ) {
+
+ // Check if there's a focused element that could be using
+ // the keyboard
+ var activeElement = document.activeElement;
+ var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
+
+ // Disregard the event if there's a focused element or a
+ // keyboard modifier key is present
+ if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
+
+ var triggered = true;
+
+ // while paused only allow "unpausing" keyboard events (b and .)
+ if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
+ return false;
+ }
+
+ switch( event.keyCode ) {
+ // p, page up
+ case 80: case 33: navigatePrev(); break;
+ // n, page down
+ case 78: case 34: navigateNext(); break;
+ // h, left
+ case 72: case 37: navigateLeft(); break;
+ // l, right
+ case 76: case 39: navigateRight(); break;
+ // k, up
+ case 75: case 38: navigateUp(); break;
+ // j, down
+ case 74: case 40: navigateDown(); break;
+ // home
+ case 36: slide( 0 ); break;
+ // end
+ case 35: slide( Number.MAX_VALUE ); break;
+ // space
+ case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
+ // return
+ case 13: isOverview() ? deactivateOverview() : triggered = false; break;
+ // b, period, Logitech presenter tools "black screen" button
+ case 66: case 190: case 191: togglePause(); break;
+ // f
+ case 70: enterFullscreen(); break;
+ default:
+ triggered = false;
+ }
+
+ // If the input resulted in a triggered action we should prevent
+ // the browsers default behavior
+ if( triggered ) {
+ event.preventDefault();
+ }
+ else if ( event.keyCode === 27 && supports3DTransforms ) {
+ toggleOverview();
+
+ event.preventDefault();
+ }
+
+ // If auto-sliding is enabled we need to cue up
+ // another timeout
+ cueAutoSlide();
+
+ }
+
+ /**
+ * Handler for the 'touchstart' event, enables support for
+ * swipe and pinch gestures.
+ */
+ function onTouchStart( event ) {
+
+ touch.startX = event.touches[0].clientX;
+ touch.startY = event.touches[0].clientY;
+ touch.startCount = event.touches.length;
+
+ // If there's two touches we need to memorize the distance
+ // between those two points to detect pinching
+ if( event.touches.length === 2 && config.overview ) {
+ touch.startSpan = distanceBetween( {
+ x: event.touches[1].clientX,
+ y: event.touches[1].clientY
+ }, {
+ x: touch.startX,
+ y: touch.startY
+ } );
+ }
+
+ }
+
+ /**
+ * Handler for the 'touchmove' event.
+ */
+ function onTouchMove( event ) {
+
+ // Each touch should only trigger one action
+ if( !touch.handled ) {
+ var currentX = event.touches[0].clientX;
+ var currentY = event.touches[0].clientY;
+
+ // If the touch started off with two points and still has
+ // two active touches; test for the pinch gesture
+ if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
+
+ // The current distance in pixels between the two touch points
+ var currentSpan = distanceBetween( {
+ x: event.touches[1].clientX,
+ y: event.touches[1].clientY
+ }, {
+ x: touch.startX,
+ y: touch.startY
+ } );
+
+ // If the span is larger than the desire amount we've got
+ // ourselves a pinch
+ if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
+ touch.handled = true;
+
+ if( currentSpan < touch.startSpan ) {
+ activateOverview();
+ }
+ else {
+ deactivateOverview();
+ }
+ }
+
+ event.preventDefault();
+
+ }
+ // There was only one touch point, look for a swipe
+ else if( event.touches.length === 1 && touch.startCount !== 2 ) {
+
+ var deltaX = currentX - touch.startX,
+ deltaY = currentY - touch.startY;
+
+ if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
+ touch.handled = true;
+ navigateLeft();
+ }
+ else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
+ touch.handled = true;
+ navigateRight();
+ }
+ else if( deltaY > touch.threshold ) {
+ touch.handled = true;
+ navigateUp();
+ }
+ else if( deltaY < -touch.threshold ) {
+ touch.handled = true;
+ navigateDown();
+ }
+
+ event.preventDefault();
+
+ }
+ }
+ // There's a bug with swiping on some Android devices unless
+ // the default action is always prevented
+ else if( navigator.userAgent.match( /android/gi ) ) {
+ event.preventDefault();
+ }
+
+ }
+
+ /**
+ * Handler for the 'touchend' event.
+ */
+ function onTouchEnd( event ) {
+
+ touch.handled = false;
+
+ }
+
+ /**
+ * Convert pointer down to touch start.
+ */
+ function onPointerDown( event ) {
+
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+ onTouchStart( event );
+ }
+
+ }
+
+ /**
+ * Convert pointer move to touch move.
+ */
+ function onPointerMove( event ) {
+
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+ onTouchMove( event );
+ }
+
+ }
+
+ /**
+ * Convert pointer up to touch end.
+ */
+ function onPointerUp( event ) {
+
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+ onTouchEnd( event );
+ }
+
+ }
+
+ /**
+ * Handles mouse wheel scrolling, throttled to avoid skipping
+ * multiple slides.
+ */
+ function onDocumentMouseScroll( event ) {
+
+ clearTimeout( mouseWheelTimeout );
+
+ mouseWheelTimeout = setTimeout( function() {
+ var delta = event.detail || -event.wheelDelta;
+ if( delta > 0 ) {
+ navigateNext();
+ }
+ else {
+ navigatePrev();
+ }
+ }, 100 );
+
+ }
+
+ /**
+ * Clicking on the progress bar results in a navigation to the
+ * closest approximate horizontal slide using this equation:
+ *
+ * ( clickX / presentationWidth ) * numberOfSlides
+ */
+ function onProgressClicked( event ) {
+
+ event.preventDefault();
+
+ var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
+ var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
+
+ slide( slideIndex );
+
+ }
+
+ /**
+ * Event handler for navigation control buttons.
+ */
+ function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
+ function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
+ function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
+ function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
+ function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
+ function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }
+
+ /**
+ * Handler for the window level 'hashchange' event.
+ */
+ function onWindowHashChange( event ) {
+
+ readURL();
+
+ }
+
+ /**
+ * Handler for the window level 'resize' event.
+ */
+ function onWindowResize( event ) {
+
+ layout();
+
+ }
+
+ /**
+ * Invoked when a slide is and we're in the overview.
+ */
+ function onOverviewSlideClicked( event ) {
+
+ // TODO There's a bug here where the event listeners are not
+ // removed after deactivating the overview.
+ if( eventsAreBound && isOverview() ) {
+ event.preventDefault();
+
+ var element = event.target;
+
+ while( element && !element.nodeName.match( /section/gi ) ) {
+ element = element.parentNode;
+ }
+
+ if( element && !element.classList.contains( 'disabled' ) ) {
+
+ deactivateOverview();
+
+ if( element.nodeName.match( /section/gi ) ) {
+ var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
+ v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
+
+ slide( h, v );
+ }
+
+ }
+ }
+
+ }
+
+
+ // --------------------------------------------------------------------//
+ // ------------------------------- API --------------------------------//
+ // --------------------------------------------------------------------//
+
+
+ return {
+ initialize: initialize,
+ configure: configure,
+ sync: sync,
+
+ // Navigation methods
+ slide: slide,
+ left: navigateLeft,
+ right: navigateRight,
+ up: navigateUp,
+ down: navigateDown,
+ prev: navigatePrev,
+ next: navigateNext,
+ prevFragment: previousFragment,
+ nextFragment: nextFragment,
+
+ // Deprecated aliases
+ navigateTo: slide,
+ navigateLeft: navigateLeft,
+ navigateRight: navigateRight,
+ navigateUp: navigateUp,
+ navigateDown: navigateDown,
+ navigatePrev: navigatePrev,
+ navigateNext: navigateNext,
+
+ // Forces an update in slide layout
+ layout: layout,
+
+ // Returns an object with the available routes as booleans (left/right/top/bottom)
+ availableRoutes: availableRoutes,
+
+ // Returns an object with the available fragments as booleans (prev/next)
+ availableFragments: availableFragments,
+
+ // Toggles the overview mode on/off
+ toggleOverview: toggleOverview,
+
+ // Toggles the "black screen" mode on/off
+ togglePause: togglePause,
+
+ // State checks
+ isOverview: isOverview,
+ isPaused: isPaused,
+
+ // Adds or removes all internal event listeners (such as keyboard)
+ addEventListeners: addEventListeners,
+ removeEventListeners: removeEventListeners,
+
+ // Returns the indices of the current, or specified, slide
+ getIndices: getIndices,
+
+ // Returns the slide at the specified index, y is optional
+ getSlide: function( x, y ) {
+ var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
+
+ if( typeof y !== 'undefined' ) {
+ return verticalSlides ? verticalSlides[ y ] : undefined;
+ }
+
+ return horizontalSlide;
+ },
+
+ // Returns the previous slide element, may be null
+ getPreviousSlide: function() {
+ return previousSlide;
+ },
+
+ // Returns the current slide element
+ getCurrentSlide: function() {
+ return currentSlide;
+ },
+
+ // Returns the current scale of the presentation content
+ getScale: function() {
+ return scale;
+ },
+
+ // Returns the current configuration object
+ getConfig: function() {
+ return config;
+ },
+
+ // Helper method, retrieves query string as a key/value hash
+ getQueryHash: function() {
+ var query = {};
+
+ location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
+ query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
+ } );
+
+ return query;
+ },
+
+ // Returns true if we're currently on the first slide
+ isFirstSlide: function() {
+ return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
+ },
+
+ // Returns true if we're currently on the last slide
+ isLastSlide: function() {
+ if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
+ return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
+ }
+ else {
+ return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
+ }
+ },
+
+ // Forward event binding to the reveal DOM element
+ addEventListener: function( type, listener, useCapture ) {
+ if( 'addEventListener' in window ) {
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
+ }
+ },
+ removeEventListener: function( type, listener, useCapture ) {
+ if( 'addEventListener' in window ) {
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
+ }
+ }
+ };
+
+})();
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/js/reveal.min.js b/src/cpp/session/resources/presentation/revealjs/js/reveal.min.js
new file mode 100644
index 0000000..1b78039
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/js/reveal.min.js
@@ -0,0 +1,8 @@
+/*!
+ * reveal.js 2.4.0 (2013-04-29, 22:06)
+ * http://lab.hakim.se/reveal-js
+ * MIT licensed
+ *
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
+ */
+var Reveal=function(){"use strict";function e(e){return Mt||kt?(window.addEventListener("load",h,!1),c(bt,e),n(),r(),void 0):(document.body.setAttribute("class","no-transforms"),void 0)}function t(){if(Tt.theme=document.querySelector("#theme"),Tt.wrapper=document.querySelector(".reveal"),Tt.slides=document.querySelector(".reveal .slides"),!Tt.wrapper.querySelector(".progress")){var e=document.createElement("div");e.classList.add("progress"),e.innerHTML="<span></span>",Tt.wrapper.appendCh [...]
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/lib/js/classList.js b/src/cpp/session/resources/presentation/revealjs/lib/js/classList.js
new file mode 100644
index 0000000..44f2b4c
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/lib/js/classList.js
@@ -0,0 +1,2 @@
+/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
+if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p<o;p++){if(p in this&&this[p]===q){return p}}return -1},n=function(o,p){this.name=o;this.code=DOMException[o];this.message=p},g=function(p,o){if(o===""){throw new n("SYNTAX_ERR","An invalid or illeg [...]
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/lib/js/head.min.js b/src/cpp/session/resources/presentation/revealjs/lib/js/head.min.js
new file mode 100644
index 0000000..6242b0f
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/lib/js/head.min.js
@@ -0,0 +1,8 @@
+/**
+ Head JS The only script in your <HEAD>
+ Copyright Tero Piirainen (tipiirai)
+ License MIT / http://bit.ly/mit-license
+ Version 0.96
+
+ http://headjs.com
+*/(function(a){function z(){d||(d=!0,s(e,function(a){p(a)}))}function y(c,d){var e=a.createElement("script");e.type="text/"+(c.type||"javascript"),e.src=c.src||c,e.async=!1,e.onreadystatechange=e.onload=function(){var a=e.readyState;!d.done&&(!a||/loaded|complete/.test(a))&&(d.done=!0,d())},(a.body||b).appendChild(e)}function x(a,b){if(a.state==o)return b&&b();if(a.state==n)return k.ready(a.name,b);if(a.state==m)return a.onpreload.push(function(){x(a,b)});a.state=n,y(a.url,function(){a.s [...]
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/revealjs/lib/js/html5shiv.js b/src/cpp/session/resources/presentation/revealjs/lib/js/html5shiv.js
new file mode 100644
index 0000000..50649b9
--- /dev/null
+++ b/src/cpp/session/resources/presentation/revealjs/lib/js/html5shiv.js
@@ -0,0 +1,7 @@
+document.createElement('header');
+document.createElement('nav');
+document.createElement('section');
+document.createElement('article');
+document.createElement('aside');
+document.createElement('footer');
+document.createElement('hgroup');
\ No newline at end of file
diff --git a/src/cpp/session/resources/presentation/slides-images/correct.png b/src/cpp/session/resources/presentation/slides-images/correct.png
new file mode 100644
index 0000000..b34de21
Binary files /dev/null and b/src/cpp/session/resources/presentation/slides-images/correct.png differ
diff --git a/src/cpp/session/resources/presentation/slides-images/incorrect.png b/src/cpp/session/resources/presentation/slides-images/incorrect.png
new file mode 100644
index 0000000..8d70708
Binary files /dev/null and b/src/cpp/session/resources/presentation/slides-images/incorrect.png differ
diff --git a/src/cpp/session/resources/presentation/slides.css b/src/cpp/session/resources/presentation/slides.css
new file mode 100644
index 0000000..01a3933
--- /dev/null
+++ b/src/cpp/session/resources/presentation/slides.css
@@ -0,0 +1,346 @@
+
+.reveal h1 {
+ font-size: 2.5em;
+}
+
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+ margin-bottom: .6em;
+}
+
+.reveal p,
+.reveal table {
+ margin-bottom: 1em;
+}
+
+.reveal li {
+ margin-bottom: .4em;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+ margin-top: .4em;
+}
+
+.reveal .slides {
+ text-align: left;
+}
+
+.reveal small {
+ font-size: 0.85em;
+}
+
+.reveal pre {
+ margin-top: 0;
+ max-width: 95%;
+ border: 1px solid #ccc;
+ white-space: pre-wrap;
+ margin-bottom: 1em;
+}
+
+.reveal pre code {
+ display: block; padding: 0.5em;
+ font-size: 1.6em;
+ line-height: 1.1em;
+ background-color: white;
+ overflow: visible;
+ max-height: none;
+ word-wrap: normal;
+}
+
+.reveal code {
+ overflow: visible;
+ max-height: none;
+}
+
+.reveal code.r {
+ background-color: #F8F8F8;
+}
+
+.reveal code.cpp {
+ background-color: #F8F8F8;
+}
+
+.reveal section del {
+ text-decoration: none;
+ color: #AAB1BA;
+}
+
+.reveal section img {
+ border: none;
+}
+
+.reveal section .fieldError {
+ margin-bottom: 25px;
+}
+
+.reveal section .fieldError span {
+ color: red;
+}
+
+.prompt .reveal .state-background {
+ background: #C6D7DC;
+}
+
+.quiz-multichoice .reveal .state-background {
+ background: rgba(254,220,179,1);
+}
+
+.quiz-multichoice .reveal ul {
+ list-style-type: none;
+ margin-bottom: 30px;
+}
+
+.quiz-multichoice .reveal li {
+ margin-top: 15px;
+}
+
+.quiz-multichoice .reveal .quizFeedback {
+ margin-bottom: 30px;
+}
+
+.quiz-multichoice .reveal .quizFeedback img {
+ border: none;
+ box-shadow: none;
+ background: transparent;
+ float: left;
+ margin-top: -15px;
+}
+
+.quiz-multichoice .reveal .quizFeedback span {
+ font-size: 1.4em;
+ margin-left: 12px;
+}
+
+.section .reveal .state-background {
+ background: #96A2B6;
+}
+
+.section .reveal h1,
+.section .reveal h2,
+.section .reveal p {
+ color: white;
+ margin-top: 50px;
+}
+
+.sub-section .reveal .state-background {
+ background: #E7E8EA
+}
+
+.sub-section .reveal h2,
+.sub-section .reveal p {
+ color: #63717B;
+ margin-top: 50px;
+}
+
+.reveal strong {
+ color: #25679E;
+}
+
+.reveal .controls {
+ right: -20px;
+ bottom: 5px;
+}
+
+.reveal .controls div.navigate-left {
+ top: 75px;
+}
+
+.reveal .controls div.navigate-right {
+ left: 54px;
+ top: 75px;
+}
+
+
+.reveal .controls div.navigate-up {
+ display: none;
+}
+
+.reveal .controls div.navigate-down {
+ display: none;
+}
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+
+.reveal .controls div.navigate-left,
+.reveal .controls div.navigate-left.enabled {
+ border-right-color: #25679E;
+}
+
+.reveal .controls div.navigate-right,
+.reveal .controls div.navigate-right.enabled {
+ border-left-color: #25679E;
+}
+
+.reveal .controls div.navigate-up,
+.reveal .controls div.navigate-up.enabled {
+ border-bottom-color: #25679E;
+}
+
+.reveal .controls div.navigate-down,
+.reveal .controls div.navigate-down.enabled {
+ border-top-color: #25679E;
+}
+
+.reveal .controls div.navigate-left.enabled:hover {
+ border-right-color: #267EC8;
+}
+
+.reveal .controls div.navigate-right.enabled:hover {
+ border-left-color: #267EC8;
+}
+
+.reveal .controls div.navigate-up.enabled:hover {
+ border-bottom-color: #267EC8;
+}
+
+.reveal .controls div.navigate-down.enabled:hover {
+ border-top-color: #267EC8;
+}
+
+.reveal .progress span {
+ background: #25679E;
+}
+
+.reveal .slides>section,
+.reveal .slides>section>section {
+ padding: 0px 0px;
+}
+
+
+.reveal table {
+ border-width: 1px;
+ border-spacing: 2px;
+ border-style: dotted;
+ border-color: gray;
+ border-collapse: collapse;
+ font-size: 0.7em;
+}
+
+.reveal table th {
+ border-width: 1px;
+ padding-left: 10px;
+ padding-right: 25px;
+ font-weight: bold;
+ border-style: dotted;
+ border-color: gray;
+}
+
+.reveal table td {
+ border-width: 1px;
+ padding-left: 10px;
+ padding-right: 25px;
+ border-style: dotted;
+ border-color: gray;
+}
+
+.reveal blockquote {
+ display: block;
+ position: relative;
+ width: 90%;
+ margin: 5px auto;
+ padding: 5px;
+
+ font-style: normal;
+ background: #C6D7DC;
+ border: 1px solid #C6D7DC;
+ box-shadow: none;
+}
+
+.reveal blockquote p:first-child,
+.reveal blockquote p:last-child {
+ display: block;
+}
+
+.reveal blockquote:before {
+ content: '';
+}
+
+.reveal blockquote:after {
+ content: '';
+}
+
+
+.reveal ol,
+.reveal ul {
+ margin: 0 0 .5em 1.2em;
+}
+
+.reveal .slides section {
+ height: 100%;
+}
+
+.reveal .slides section .slideContent h2 {
+ font-size: 1.3em;
+ font-weight: bold;
+}
+
+.reveal .slides section .slideContent h3 {
+ font-size: 1.1em;
+ font-weight: bold;
+}
+
+.reveal .slides section .column {
+ position: fixed;
+ width: 48%;
+ top: 2.5em;
+ bottom: 0;
+}
+
+.reveal .slides section .column1 {
+ left: 0;
+}
+
+.reveal .slides section .column2 {
+ right: 0;
+}
+
+.reveal .slides section .column img {
+ max-width: 95%;
+ max-height: 95%;
+ height: auto;
+}
+
+.reveal .slides section .mediaOnly {
+ position: fixed;
+ width: 100%;
+ top: 2.5em;
+ bottom: 0;
+}
+
+.reveal .slides section .mediaOnly img {
+ max-width: 90%;
+ max-height: 90%;
+ height: auto;
+}
+
+.reveal .slides section .mediaInline img {
+ max-width: 90%;
+ max-height: 50%;
+ width: auto;
+}
+
+.reveal .slides section .mediaOnly video {
+ height: 90%;
+ width: 90%;
+}
+
+.reveal .slides section .mediaInline video {
+ height: 50%;
+ width: 90%;
+}
+
+.reveal .slides section .noTitle {
+ top: 0;
+}
+
+
+
diff --git a/src/cpp/session/resources/presentation/slides.html b/src/cpp/session/resources/presentation/slides.html
new file mode 100644
index 0000000..5f9eb4d
--- /dev/null
+++ b/src/cpp/session/resources/presentation/slides.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="utf-8">
+ <title>#!title#</title>
+
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+
+
+ <!-- web fonts -->
+ <style type="text/css">
+#!google_webfonts#
+#!user_font_imports#
+ </style>
+
+ <!-- reveal.js -->
+ #!reveal_css#
+ #!reveal_theme_css#
+
+ <style type="text/css">
+#!slides_css#
+
+.reveal {
+ font-family: #!reveal_font#, sans-serif;
+}
+
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+ font-family: #!reveal_heading_font#, Impact, sans-serif;
+}
+
+#!user_slides_css#
+
+ </style>
+
+ <!-- reveal print css -->
+ #!reveal_print_css#
+
+#!r_highlight#
+
+#!mathjax#
+
+#!preamble#
+
+#!slides_head#
+
+</head>
+
+<body>
+ <div class="reveal">
+ <div class="slides">
+
+#!slides#
+
+ </div>
+ </div>
+
+ #!reveal_head_js#
+
+ #!reveal_js#
+
+ <script type="text/javascript">
+
+// dynamically built function to get slide specific commands
+function commandsForSlide(indexh) {
+ var cmds = [];
+ switch(indexh) {
+#!slide_commands#
+ default:
+ break;
+ };
+ return cmds;
+}
+
+#!slides_js#
+
+ Reveal.initialize({
+ controls: true,
+ progress: true,
+ history: true,
+ overview: true,
+ center: false,
+ rollingLinks: false,
+ #!reveal_config#
+ theme: 'simple',
+ transition: '#!reveal_transition#',
+ transitionSpeed: '#!reveal_transition_speed#',
+ rtl: #!reveal_rtl#,
+ width: #!reveal_width#,
+ height: #!reveal_height#,
+
+ dependencies: []
+ });
+
+ Reveal.addEventListener( 'ready', function( event ) {
+ // notify container
+ if (window.notifySlideChanged)
+ notifySlideChanged(event.indexh)
+ } );
+
+ Reveal.addEventListener( 'slidechanged', function( event ) {
+
+ // notify container
+ if (window.notifySlideChanged)
+ notifySlideChanged(event.indexh)
+
+ // allow mathjax to re-render
+ if (window.MathJax)
+ window.MathJax.Hub.Rerender(event.currentSlide);
+ });
+
+ // forward command key events to the rstudio frame
+ window.onkeydown = function(e) {
+
+ // allow 'o' to toggle overview mode
+ switch( e.keyCode ) {
+ case 79: case 42: window.Reveal.toggleOverview(); break;
+ }
+
+ if (window.parent.presentationKeydown)
+ window.parent.presentationKeydown(e);
+ }
+
+ window.onresize = function() {
+
+ // bail if autosize is disabled
+ if (!#!reveal_autosize#)
+ return;
+
+ if(window.activeTimer)
+ clearTimeout(window.activeTimer);
+
+ window.activeTimer = setTimeout(function() {
+ window.location.reload(true);
+ }, 300);
+ }
+
+#!init_commands#
+
+ </script>
+
+</body>
+
+</html>
diff --git a/src/cpp/session/resources/presentation/slides.js b/src/cpp/session/resources/presentation/slides.js
new file mode 100644
index 0000000..117d568
--- /dev/null
+++ b/src/cpp/session/resources/presentation/slides.js
@@ -0,0 +1,99 @@
+
+
+// manage media playback, atCommands, etc.
+function mediaManager(media, atCommands) {
+
+ // track state for at command execution
+ nextAtCommandIndex = 0;
+ previousTime = 0;
+
+ // reset at command index and reload after ending
+ media.addEventListener('ended', function(e) {
+ nextAtCommandIndex = 0;
+ media.currentTime = 0;
+ }, false);
+
+ // track time events and fire at commands
+ media.addEventListener('timeupdate', function(e) {
+ // adjust command index for backward seeks
+ if (!media.seeking) {
+ if (media.currentTime < previousTime) {
+ nextAtCommandIndex = 0;
+ for (i = 0; i<atCommands.length; i++) {
+ if (media.currentTime < atCommands[i].at) {
+ nextAtCommandIndex = i;
+ break;
+ }
+ }
+ }
+ previousTime = media.currentTime;
+ }
+
+ // see if a command needs to be triggered
+ if (nextAtCommandIndex < atCommands.length) {
+ nextCommand = atCommands[nextAtCommandIndex];
+ if (media.currentTime > nextCommand.at) {
+ window.parent.dispatchPresentationCommand(nextCommand.command);
+ nextAtCommandIndex++;
+ }
+ }
+ }, false);
+
+ return {
+ play: function() { if (media.play) media.play(); }
+ };
+}
+
+function pausePlayers(players) {
+ if (players != null) {
+ for(var i = 0; i < players.length; i++) {
+ if (!players[i].paused && players[i].pause)
+ players[i].pause();
+ }
+ }
+}
+
+function pauseAllPlayers() {
+ pausePlayers(document.getElementsByTagName('video'));
+ pausePlayers(document.getElementsByTagName('audio'));
+}
+
+
+function notifySlideChanged(indexh) {
+
+ // pause all audio and video
+ pauseAllPlayers();
+
+ // notify parent of slide changed
+ if (window.parent.presentationSlideChanged) {
+ window.parent.presentationSlideChanged(indexh, commandsForSlide(indexh));
+ }
+}
+
+function revealDetectWidth(zoomed) {
+ if (window.innerWidth > 0)
+ {
+ if (zoomed)
+ return window.innerWidth;
+ else
+ return window.innerWidth * 2.3;
+ }
+ else
+ {
+ return 960;
+ }
+}
+
+function revealDetectHeight(zoomed) {
+ if (window.innerHeight > 0)
+ {
+ if (zoomed)
+ return window.innerHeight;
+ else
+ return window.innerHeight * 2.3;
+ }
+ else
+ {
+ return 700;
+ }
+}
diff --git a/src/cpp/session/resources/r_highlight.html b/src/cpp/session/resources/r_highlight.html
new file mode 100644
index 0000000..6fe21ed
--- /dev/null
+++ b/src/cpp/session/resources/r_highlight.html
@@ -0,0 +1,37 @@
+<!-- Styles for R syntax highlighter -->
+<style type="text/css">
+ pre .operator,
+ pre .paren {
+ color: rgb(104, 118, 135)
+ }
+
+ pre .literal {
+ color: rgb(88, 72, 246)
+ }
+
+ pre .number {
+ color: rgb(0, 0, 205);
+ }
+
+ pre .comment {
+ color: rgb(76, 136, 107);
+ }
+
+ pre .keyword {
+ color: rgb(0, 0, 255);
+ }
+
+ pre .identifier {
+ color: rgb(0, 0, 0);
+ }
+
+ pre .string {
+ color: rgb(3, 106, 7);
+ }
+</style>
+
+<!-- R syntax highlighter -->
+<script type="text/javascript">
+var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/</gm,"<")}function f(r,q,p){return RegExp(q,"m"+(r.cI?"i":"")+(p?"g":""))}function b(r){for(var p=0;p<r.childNodes.length;p++){var q=r.childNodes[p];if(q.nodeName=="CODE"){return q}if(!(q.nodeType==3&&q.nodeValue.match(/\s+/))){break}}}function h(t,s){var p="";for(var r=0;r<t.childNodes.length;r++){if(t.childNodes[r].nodeType==3){var q=t.childNodes[r].nodeValue;if(s){q=q.replace(/\n/g,"")}p+=q}else{if(t.chi [...]
+hljs.initHighlightingOnLoad();
+</script>
diff --git a/src/cpp/session/resources/sumatrapdfrestrict.ini b/src/cpp/session/resources/sumatrapdfrestrict.ini
new file mode 100644
index 0000000..9d65945
--- /dev/null
+++ b/src/cpp/session/resources/sumatrapdfrestrict.ini
@@ -0,0 +1,60 @@
+; To apply this configuration, copy this file into
+; the same directory as SumatraPDF.exe.
+
+; All settings listed below can have a value of either
+; 0 for disabling the feature or 1 for enabling the feature
+; (missing settings default to 0).
+
+[Policies]
+; Whether SumatraPDF should be allowed to access the Internet.
+; Needed for:
+; * Checking for updates
+; * Sending crash reports
+InternetAccess = 0
+
+; Whether SumatraPDF should allow access to the file system.
+; Needed for:
+; * Opening files through dialog
+; * Saving file or bookmark shortcut
+; * Opening a web browser after a click on a hyperlink
+; * Launching external PDF viewers, LaTeX source editors or media players
+; * Displaying Frequently Read page (also requires SavePreferences)
+; * Reopening recently opened files
+DiskAccess = 1
+
+; Whether SumatraPDF should save user preferences on exit.
+; Needed for:
+; * Changing settings
+; * Favorites menu
+; * Remembering recently opened files (includes Frequently Read page)
+SavePreferences = 0
+
+; Whether SumatraPDF should be allowed to write to the Registry.
+; Needed for:
+; * Making SumatraPDF a default PDF viewer
+RegistryAccess = 0
+
+; Whether SumatraPDF should be allowed to print.
+; Needed for:
+; * Printing (parts of) a document
+PrinterAccess = 1
+
+; Whether users should be allowed to select and copy content.
+; Needed for:
+; * Selecting with the mouse
+; * Select all
+; * Copying the selection
+CopySelection = 1
+
+; What protocols for links inside documents should be passed
+; on to the operating system (e.g. for opening a browser).
+; Default: http,https,mailto (web links and email addresses)
+LinkProtocols = http,https,mailto
+
+; What file types should be opened in an external application
+; if they're linked to by a (PDF) document and can't be opened
+; within SumatraPDF itself (use "*" for all types)
+; These file types are stored as "PerceivedType" in the Registry,
+; common values: audio, video, image, document, text, system
+; Default: audio,video,webpage
+SafeFileTypes = audio,video,webpage
\ No newline at end of file
diff --git a/src/cpp/session/resources/templates/r_documentation.Rd b/src/cpp/session/resources/templates/r_documentation.Rd
new file mode 100644
index 0000000..863afb0
--- /dev/null
+++ b/src/cpp/session/resources/templates/r_documentation.Rd
@@ -0,0 +1,64 @@
+\name{#!name#}
+\alias{#!name#}
+%- Also NEED an '\alias' for EACH other topic documented here.
+
+\title{
+%% ~~function to do ... ~~
+}
+
+\description{
+%% ~~ A concise (1-5 lines) description of what the function does. ~~
+}
+
+\usage{
+%- simple usage of function documented here.
+}
+
+\arguments{
+ \item{x}{
+ %% ~~Describe \code{x} here~~
+ }
+}
+
+\details{
+%% ~~ If necessary, more details than the description above ~~
+}
+
+\value{
+%% ~Describe the value returned
+%% If it is a LIST, use
+%% \item{comp1 }{Description of 'comp1'}
+%% \item{comp2 }{Description of 'comp2'}
+%% ...
+}
+
+\references{
+%% ~put references to the literature/web site here ~
+}
+
+\author{
+%% ~~who you are~~
+}
+
+\note{
+%% ~~further notes~~
+}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+%% ~~objects to See Also as \code{\link{help}}, ~~~
+}
+
+\examples{
+##---- Should be DIRECTLY executable !! ----
+##-- ==> Define data, use random, or
+##-- use the standard data sets.
+
+
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{ ~kwd1 }
+\keyword{ ~kwd2 }% __ONLY ONE__ keyword per line
diff --git a/src/cpp/session/resources/templates/r_documentation_empty.Rd b/src/cpp/session/resources/templates/r_documentation_empty.Rd
new file mode 100644
index 0000000..4b61df7
--- /dev/null
+++ b/src/cpp/session/resources/templates/r_documentation_empty.Rd
@@ -0,0 +1,5 @@
+\name{#!name#}
+\alias{#!name#}
+
+\title{}
+
diff --git a/src/cpp/session/resources/templates/r_html.Rhtml b/src/cpp/session/resources/templates/r_html.Rhtml
new file mode 100644
index 0000000..5eb6189
--- /dev/null
+++ b/src/cpp/session/resources/templates/r_html.Rhtml
@@ -0,0 +1,22 @@
+<html>
+
+<head>
+<title>Title</title>
+</head>
+
+<body>
+
+<p>This is an R HTML document. When you click the <b>Knit HTML</b> button a web page will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:</p>
+
+<!--begin.rcode
+summary(cars)
+end.rcode-->
+
+<p>You can also embed plots, for example:</p>
+
+<!--begin.rcode fig.width=7, fig.height=6
+plot(cars)
+end.rcode-->
+
+</body>
+</html>
diff --git a/src/cpp/session/resources/templates/r_markdown.Rmd b/src/cpp/session/resources/templates/r_markdown.Rmd
new file mode 100644
index 0000000..9241ee0
--- /dev/null
+++ b/src/cpp/session/resources/templates/r_markdown.Rmd
@@ -0,0 +1,17 @@
+Title
+========================================================
+
+This is an R Markdown document. Markdown is a simple formatting syntax for authoring web pages (click the **Help** toolbar button for more details on using R Markdown).
+
+When you click the **Knit HTML** button a web page will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
+
+```{r}
+summary(cars)
+```
+
+You can also embed plots, for example:
+
+```{r fig.width=7, fig.height=6}
+plot(cars)
+```
+
diff --git a/src/cpp/session/resources/templates/r_presentation.Rpres b/src/cpp/session/resources/templates/r_presentation.Rpres
new file mode 100644
index 0000000..b7e06a7
--- /dev/null
+++ b/src/cpp/session/resources/templates/r_presentation.Rpres
@@ -0,0 +1,28 @@
+#name#
+========================================================
+author:
+date:
+
+First Slide
+========================================================
+
+For more details on authoring R presentations click the
+**Help** button on the toolbar.
+
+- Bullet 1
+- Bullet 2
+- Bullet 3
+
+Slide With Code
+========================================================
+
+```{r}
+summary(cars)
+```
+
+Slide With Plot
+========================================================
+
+```{r, echo=FALSE}
+plot(cars)
+```
diff --git a/src/cpp/session/resources/templates/rcpp.cpp b/src/cpp/session/resources/templates/rcpp.cpp
new file mode 100644
index 0000000..e8ad13b
--- /dev/null
+++ b/src/cpp/session/resources/templates/rcpp.cpp
@@ -0,0 +1,13 @@
+#include <Rcpp.h>
+using namespace Rcpp;
+
+// Below is a simple example of exporting a C++ function to R. You can
+// source this function into an R session using the Rcpp::sourceCpp
+// function (or via the Source button on the editor toolbar)
+
+// For more on using Rcpp click the Help button on the editor toolbar
+
+// [[Rcpp::export]]
+int timesTwo(int x) {
+ return x * 2;
+}
diff --git a/src/cpp/session/resources/templates/shiny/server.R b/src/cpp/session/resources/templates/shiny/server.R
new file mode 100644
index 0000000..5a08476
--- /dev/null
+++ b/src/cpp/session/resources/templates/shiny/server.R
@@ -0,0 +1,21 @@
+
+# This is the server logic for a Shiny web application.
+# You can find out more about building applications with Shiny here:
+#
+# http://www.rstudio.com/shiny/
+#
+
+library(shiny)
+
+shinyServer(function(input, output) {
+
+ output$distPlot <- renderPlot({
+
+ # generate and plot an rnorm distribution with the requested
+ # number of observations
+ dist <- rnorm(input$obs)
+ hist(dist)
+
+ })
+
+})
diff --git a/src/cpp/session/resources/templates/shiny/ui.R b/src/cpp/session/resources/templates/shiny/ui.R
new file mode 100644
index 0000000..e0366b0
--- /dev/null
+++ b/src/cpp/session/resources/templates/shiny/ui.R
@@ -0,0 +1,28 @@
+
+# This is the user-interface definition of a Shiny web application.
+# You can find out more about building applications with Shiny here:
+#
+# http://www.rstudio.com/shiny/
+#
+
+library(shiny)
+
+shinyUI(pageWithSidebar(
+
+ # Application title
+ headerPanel("New Application"),
+
+ # Sidebar with a slider input for number of observations
+ sidebarPanel(
+ sliderInput("obs",
+ "Number of observations:",
+ min = 1,
+ max = 1000,
+ value = 500)
+ ),
+
+ # Show a plot of the generated distribution
+ mainPanel(
+ plotOutput("distPlot")
+ )
+))
diff --git a/src/cpp/session/resources/templates/sweave.Rnw b/src/cpp/session/resources/templates/sweave.Rnw
new file mode 100644
index 0000000..16d211d
--- /dev/null
+++ b/src/cpp/session/resources/templates/sweave.Rnw
@@ -0,0 +1,8 @@
+\documentclass{article}
+
+\begin{document}
+
+
+
+
+\end{document}
\ No newline at end of file
diff --git a/src/cpp/session/rsession.exe.manifest b/src/cpp/session/rsession.exe.manifest
new file mode 100644
index 0000000..8f5e2e7
--- /dev/null
+++ b/src/cpp/session/rsession.exe.manifest
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0"
+ name="rsession"
+ type="win32"/>
+ <description>rsession</description>
+ <!-- Identify the application security requirements. -->
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!--The ID below indicates application support for Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ <!--The ID below indicates application support for Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/src/cpp/session/rsession.rc.in b/src/cpp/session/rsession.rc.in
new file mode 100644
index 0000000..6738ca8
--- /dev/null
+++ b/src/cpp/session/rsession.rc.in
@@ -0,0 +1,29 @@
+#include "winuser.h"
+
+1 RT_MANIFEST "rsession.exe.manifest"
+1 VERSIONINFO
+FILEVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+PRODUCTVERSION ${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "RStudio, Inc.\0"
+ VALUE "FileDescription", "RStudio R Session\0"
+ VALUE "FileVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ VALUE "InternalName", "rsession\0"
+ VALUE "LegalCopyright", "Copyright (C) 2009-11 by RStudio, Inc.\0"
+ VALUE "LegalTrademarks", "RStudio (TM) is a trademark of RStudio, Inc.\0"
+ VALUE "OriginalFilename", "rsession.exe\0"
+ VALUE "ProductName", "RStudio\0"
+ VALUE "ProductVersion", "${CPACK_PACKAGE_VERSION_MAJOR},${CPACK_PACKAGE_VERSION_MINOR},${CPACK_PACKAGE_VERSION_PATCH},0\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
diff --git a/src/cpp/session/session-config.h.in b/src/cpp/session/session-config.h.in
new file mode 100644
index 0000000..84df6c9
--- /dev/null
+++ b/src/cpp/session/session-config.h.in
@@ -0,0 +1,19 @@
+/*
+ * config.h.in
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#define RSTUDIO_VERSION "${CPACK_PACKAGE_VERSION}"
+#cmakedefine RSTUDIO_SERVER
+#cmakedefine RSTUDIO_UNVERSIONED_BUILD
+
diff --git a/src/cpp/session/workers/CMakeLists.txt b/src/cpp/session/workers/CMakeLists.txt
new file mode 100644
index 0000000..05b804a
--- /dev/null
+++ b/src/cpp/session/workers/CMakeLists.txt
@@ -0,0 +1,43 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+project (SESSION_WORKERS)
+
+# include files
+file(GLOB_RECURSE SESSION_WORKERS_HEADER_FILES "*.h*")
+
+
+
+# source files
+set(SESSION_WORKERS_SOURCE_FILES
+ SessionWebRequestWorker.cpp
+)
+
+
+# include directories
+include_directories(
+ ${CORE_SOURCE_DIR}/include
+ ${SESSION_SOURCE_DIR}/include/session/worker_safe
+)
+
+# define library
+add_library(rstudio-session-workers STATIC
+ ${SESSION_WORKERS_SOURCE_FILES}
+ ${SESSION_WORKERS_HEADER_FILES})
+
+# link dependencies
+target_link_libraries(rstudio-session-workers
+ rstudio-core
+)
diff --git a/src/cpp/session/workers/SessionWebRequestWorker.cpp b/src/cpp/session/workers/SessionWebRequestWorker.cpp
new file mode 100644
index 0000000..d792a59
--- /dev/null
+++ b/src/cpp/session/workers/SessionWebRequestWorker.cpp
@@ -0,0 +1,50 @@
+/*
+ * SessionWebRequestWorker.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#include <core/Error.hpp>
+
+#include <session/SessionWorkerContext.hpp>
+
+using namespace core;
+
+namespace session {
+namespace workers {
+namespace web_request {
+
+namespace {
+
+Error webRequest(const json::JsonRpcRequest& request,
+ json::JsonRpcResponse* pResponse)
+{
+
+
+
+ return Success();
+}
+
+} // anonymouys namespace
+
+Error initialize()
+{
+ return worker_context::registerWorkerRpcMethod("web_request", webRequest);
+}
+
+} // namespace web_request
+} // namespace workers
+} // namespace session
+
+
+
+
diff --git a/src/cpp/session/workers/SessionWebRequestWorker.hpp b/src/cpp/session/workers/SessionWebRequestWorker.hpp
new file mode 100644
index 0000000..11061cd
--- /dev/null
+++ b/src/cpp/session/workers/SessionWebRequestWorker.hpp
@@ -0,0 +1,33 @@
+/*
+ * SessionWebRequestWorker.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_WEB_REQUEST_WORKER_HPP
+#define SESSION_WEB_REQUEST_WORKER_HPP
+
+namespace core {
+ class Error;
+}
+
+namespace session {
+namespace workers {
+namespace web_request {
+
+core::Error initialize();
+
+} // namespace web_request
+} // namespace workers
+} // namespace session
+
+#endif // SESSION_WEB_REQUEST_WORKER_HPP
diff --git a/src/cpp/tools/.gitignore b/src/cpp/tools/.gitignore
new file mode 100644
index 0000000..62ba8da
--- /dev/null
+++ b/src/cpp/tools/.gitignore
@@ -0,0 +1,3 @@
+sundown/
+highlight.js/
+revealjs/
diff --git a/src/cpp/tools/extract-rstudio_boost b/src/cpp/tools/extract-rstudio_boost
new file mode 100755
index 0000000..cab5c99
--- /dev/null
+++ b/src/cpp/tools/extract-rstudio_boost
@@ -0,0 +1,102 @@
+
+#!/bin/bash
+
+set -e
+
+INSTALL_DIR=`pwd`
+
+# determine platform
+PLATFORM=`uname`
+
+# define boost variables
+BOOST_VERSION_NUMBER=1.53.0
+BOOST_VERSION=boost_1_53_0
+BOOST_TAR=$BOOST_VERSION.tar.bz2
+
+# remove existing boost artifacts
+rm -rf $BOOST_VERSION
+rm -f $BOOST_TAR
+
+# re-initialize the directory where we'll extract boost sources to
+RSTUDIO_BOOST=rstudio_$BOOST_VERSION
+RSTUDIO_BOOST_SRC_DIR=$INSTALL_DIR/$RSTUDIO_BOOST
+rm -rf $RSTUDIO_BOOST_SRC_DIR
+mkdir -p $RSTUDIO_BOOST_SRC_DIR
+
+# download boost
+BOOST_URL=http://sourceforge.net/projects/boost/files/boost/$BOOST_VERSION_NUMBER/$BOOST_TAR/download?use_mirror=autoselect
+if [ "$PLATFORM" == "Darwin" ]
+then
+ curl -L $BOOST_URL > $BOOST_TAR
+else
+ wget $BOOST_URL -O $BOOST_TAR
+fi
+
+# extract boost
+rm -rf $BOOST_VERSION
+tar --bzip2 -xf $BOOST_TAR
+
+# bootstrap and create bcp
+cd $BOOST_VERSION
+./bootstrap.sh
+./b2 tools/bcp
+cp dist/bin/bcp .
+
+# extract the subset we want
+./bcp --namespace=rstudio_boost --namespace-alias \
+ algorithm \
+ asio \
+ array \
+ bind \
+ chrono \
+ circular_buffer \
+ crc \
+ date_time \
+ filesystem \
+ foreach \
+ format \
+ function \
+ interprocess \
+ iostreams \
+ lambda \
+ lexical_cast \
+ optional \
+ program_options \
+ random \
+ range \
+ ref \
+ regex \
+ scope_exit \
+ signals \
+ smart_ptr \
+ spirit \
+ string_algo \
+ system \
+ test \
+ thread \
+ tokenizer \
+ type_traits \
+ typeof \
+ unordered \
+ utility \
+ variant \
+ config build \
+ $RSTUDIO_BOOST_SRC_DIR
+
+# switch to the install dir
+cd $INSTALL_DIR
+
+# now create a tarball for the extracted version of boost
+RSTUDIO_BOOST_TAR=$RSTUDIO_BOOST.tar.bz2
+rm -f $RSTUDIO_BOOST_TAR
+tar -cjf $RSTUDIO_BOOST_TAR -C $RSTUDIO_BOOST .
+
+# remove intermediate files and directories
+rm -rf $RSTUDIO_BOOST_SRC_DIR
+rm -rf $BOOST_VERSION
+rm -f $BOOST_TAR
+
+
+
+
+
diff --git a/src/cpp/tools/highlight-version b/src/cpp/tools/highlight-version
new file mode 100644
index 0000000..91853c6
--- /dev/null
+++ b/src/cpp/tools/highlight-version
@@ -0,0 +1 @@
+c589dcc424c034d1cf63a998ee68225e4915dfca
diff --git a/src/cpp/tools/revealjs-version b/src/cpp/tools/revealjs-version
new file mode 100644
index 0000000..f735074
--- /dev/null
+++ b/src/cpp/tools/revealjs-version
@@ -0,0 +1 @@
+88eb0af776bce5e07a21d030142c335d3c77cab1
diff --git a/src/cpp/tools/sync-highlight b/src/cpp/tools/sync-highlight
new file mode 100755
index 0000000..0b4e5c1
--- /dev/null
+++ b/src/cpp/tools/sync-highlight
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "./highlight.js" ]; then
+ git clone git at github.com:rstudio/highlight.js.git highlight.js
+ cd highlight.js
+ git remote add upstream git://github.com/isagalaev/highlight.js
+ cd ..
+fi
+
+cd highlight.js
+
+# get the version with the r highlight mode
+git checkout r
+git pull
+VERSION=`git rev-parse HEAD`
+
+# copy files
+TARGET_DIR=../../session/resources
+mkdir -p $TARGET_DIR
+echo "$VERSION" > ../highlight-version
+python tools/build.py r cpp
+
+TARGET_FILE=$TARGET_DIR/r_highlight.html
+
+cat <<'EOF' > $TARGET_FILE
+<!-- Styles for R syntax highlighter -->
+<style type="text/css">
+ pre .operator,
+ pre .paren {
+ color: rgb(104, 118, 135)
+ }
+
+ pre .literal {
+ color: rgb(88, 72, 246)
+ }
+
+ pre .number {
+ color: rgb(0, 0, 205);
+ }
+
+ pre .comment {
+ color: rgb(76, 136, 107);
+ }
+
+ pre .keyword {
+ color: rgb(0, 0, 255);
+ }
+
+ pre .identifier {
+ color: rgb(0, 0, 0);
+ }
+
+ pre .string {
+ color: rgb(3, 106, 7);
+ }
+</style>
+EOF
+echo "\n<!-- R syntax highlighter -->" >> $TARGET_FILE
+echo "<script type=\"text/javascript\">" >> $TARGET_FILE
+cat src/highlight.pack.js >> $TARGET_FILE
+echo "\nhljs.initHighlightingOnLoad();" >> $TARGET_FILE
+echo "</script>" >> $TARGET_FILE
+
+# return to tools dir
+cd ..
diff --git a/src/cpp/tools/sync-revealjs b/src/cpp/tools/sync-revealjs
new file mode 100755
index 0000000..748787c
--- /dev/null
+++ b/src/cpp/tools/sync-revealjs
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "./revealjs" ]; then
+ git clone https://github.com/hakimel/reveal.js.git revealjs
+fi
+
+cd revealjs
+
+# Get version 2.4.0
+git checkout master
+git pull
+git checkout 2.4.0
+VERSION=`git rev-parse HEAD`
+
+# record the version
+echo "$VERSION" > ../revealjs-version
+
+# copy files
+TARGET_DIR=../../session/resources/presentation/revealjs
+mkdir -p $TARGET_DIR
+mkdir -p $TARGET_DIR/css/theme
+mkdir -p $TARGET_DIR/css/print
+mkdir -p $TARGET_DIR/js
+mkdir -p $TARGET_DIR/lib/js
+
+cp LICENSE $TARGET_DIR/LICENSE
+cp css/reveal.css $TARGET_DIR/css
+cp css/reveal.min.css $TARGET_DIR/css
+cp css/theme/simple.css $TARGET_DIR/css/theme
+cp css/print/*.css $TARGET_DIR/css/print
+cp lib/js/*.js $TARGET_DIR/lib/js
+cp js/reveal.js $TARGET_DIR/js
+cp js/reveal.min.js $TARGET_DIR/js
+
+# return to tools dir
+cd ..
diff --git a/src/cpp/tools/sync-sundown b/src/cpp/tools/sync-sundown
new file mode 100755
index 0000000..1deb6ce
--- /dev/null
+++ b/src/cpp/tools/sync-sundown
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "./sundown" ]; then
+ git clone git at github.com:rstudio/sundown.git
+fi
+
+cd sundown
+git clean -dfx
+
+# Use the branch with rstudio-specific changes
+git checkout master
+git pull
+VERSION=`git rev-parse HEAD`
+
+# copy files
+TARGET_DIR=../../core/markdown/sundown
+echo "#define RSTUDIO_SUNDOWN_VERSION $VERSION" > $TARGET_DIR/sundown_version.h
+cp src/autolink.h $TARGET_DIR
+cp src/buffer.h $TARGET_DIR
+cp src/markdown.h $TARGET_DIR
+cp src/*.h $TARGET_DIR
+cp src/*.c $TARGET_DIR
+cp html/*.h $TARGET_DIR
+cp html/*.c $TARGET_DIR
+
+# return to tools dir
+cd ..
diff --git a/src/gwt/.classpath b/src/gwt/.classpath
new file mode 100644
index 0000000..c37721a
--- /dev/null
+++ b/src/gwt/.classpath
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="test"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
+ <classpathentry kind="lib" path="lib/gwt/2.5.1/validation-api-1.0.0.GA.jar" sourcepath="lib/gwt/2.5.1/validation-api-1.0.0.GA-sources.jar"/>
+ <classpathentry kind="lib" path="lib/gin/1.5/aopalliance.jar"/>
+ <classpathentry kind="lib" path="lib/gin/1.5/gin-1.5-post-gwt-2.2.jar" sourcepath="lib/gin/1.5/gin-1.5-post-gwt-2.2.jar"/>
+ <classpathentry kind="lib" path="lib/gin/1.5/guice-assistedinject-snapshot.jar"/>
+ <classpathentry kind="lib" path="lib/gin/1.5/guice-snapshot.jar"/>
+ <classpathentry kind="lib" path="lib/gin/1.5/javax.inject.jar"/>
+ <classpathentry kind="lib" path="lib/gwt/2.5.1/gwt-api-checker.jar"/>
+ <classpathentry kind="lib" path="lib/gwt/2.5.1/validation-api-1.0.0.GA-sources.jar"/>
+ <classpathentry kind="lib" path="lib/selenium/2.37.0/selenium-java-2.37.0-srcs.jar"/>
+ <classpathentry kind="lib" path="lib/selenium/2.37.0/selenium-java-2.37.0.jar"/>
+ <classpathentry kind="lib" path="lib/junit-4.9b3.jar"/>
+ <classpathentry kind="lib" path="lib/selenium/2.37.0/libs/guava-15.0.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/src/gwt/.gitignore b/src/gwt/.gitignore
new file mode 100644
index 0000000..53610d7
--- /dev/null
+++ b/src/gwt/.gitignore
@@ -0,0 +1,7 @@
+bin/
+extras/
+gen/
+lib/
+sdk/
+
+
diff --git a/src/gwt/.idea/.gitignore b/src/gwt/.idea/.gitignore
new file mode 100644
index 0000000..a7c382e
--- /dev/null
+++ b/src/gwt/.idea/.gitignore
@@ -0,0 +1 @@
+workspace.xml
diff --git a/src/gwt/.idea/ant.xml b/src/gwt/.idea/ant.xml
new file mode 100644
index 0000000..4674eea
--- /dev/null
+++ b/src/gwt/.idea/ant.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="AntConfiguration">
+ <defaultAnt bundledAnt="true" />
+ <buildFile url="file://$PROJECT_DIR$/build.xml">
+ <additionalClassPath />
+ <antReference projectDefault="true" />
+ <customJdkName value="" />
+ <maximumHeapSize value="128" />
+ <maximumStackSize value="2" />
+ <properties />
+ </buildFile>
+ </component>
+</project>
+
diff --git a/src/gwt/.idea/artifacts/Client_war_exploded.xml b/src/gwt/.idea/artifacts/Client_war_exploded.xml
new file mode 100644
index 0000000..3447b56
--- /dev/null
+++ b/src/gwt/.idea/artifacts/Client_war_exploded.xml
@@ -0,0 +1,13 @@
+<component name="ArtifactManager">
+ <artifact type="exploded-war" name="Client:war exploded">
+ <output-path>$PROJECT_DIR$/out/artifacts/Client_war_exploded</output-path>
+ <root id="root">
+ <element id="javaee-facet-resources" facet="Client/web/Web" />
+ <element id="directory" name="WEB-INF">
+ <element id="directory" name="classes">
+ <element id="module-output" name="Client" />
+ </element>
+ </element>
+ </root>
+ </artifact>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/compiler.xml b/src/gwt/.idea/compiler.xml
new file mode 100644
index 0000000..d9674ba
--- /dev/null
+++ b/src/gwt/.idea/compiler.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <resourceExtensions>
+ <entry name=".+\.(properties|xml|html|dtd|tld)" />
+ <entry name=".+\.(gif|png|jpeg|jpg)" />
+ </resourceExtensions>
+ <wildcardResourcePatterns>
+ <entry name="?*.properties" />
+ <entry name="?*.xml" />
+ <entry name="?*.gif" />
+ <entry name="?*.png" />
+ <entry name="?*.jpeg" />
+ <entry name="?*.jpg" />
+ <entry name="?*.html" />
+ <entry name="?*.dtd" />
+ <entry name="?*.tld" />
+ <entry name="?*.ftl" />
+ </wildcardResourcePatterns>
+ <annotationProcessing enabled="false" useClasspath="true" />
+ </component>
+ <component name="EclipseCompilerSettings">
+ <option name="GENERATE_NO_WARNINGS" value="true" />
+ <option name="DEPRECATION" value="false" />
+ </component>
+</project>
+
diff --git a/src/gwt/.idea/encodings.xml b/src/gwt/.idea/encodings.xml
new file mode 100644
index 0000000..e206d70
--- /dev/null
+++ b/src/gwt/.idea/encodings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+</project>
+
diff --git a/src/gwt/.idea/inspectionProfiles/Project_Default.xml b/src/gwt/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..72bc19c
--- /dev/null
+++ b/src/gwt/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,40 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0" is_locked="false">
+ <option name="myName" value="Project Default" />
+ <option name="myLocal" value="false" />
+ <inspection_tool class="GWTStyleCheck" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="TOP_LEVEL_CLASS_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="INNER_CLASS_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="METHOD_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="@return at param@throws or @exception" />
+ </value>
+ </option>
+ <option name="FIELD_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="IGNORE_DEPRECATED" value="false" />
+ <option name="IGNORE_JAVADOC_PERIOD" value="true" />
+ <option name="IGNORE_DUPLICATED_THROWS" value="false" />
+ <option name="IGNORE_POINT_TO_ITSELF" value="false" />
+ <option name="myAdditionalJavadocTags" value="" />
+ </inspection_tool>
+ <inspection_tool class="NonJREEmulationClassesInClientCode" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="UNUSED_IMPORT" enabled="true" level="ERROR" enabled_by_default="true" />
+ </profile>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/inspectionProfiles/profiles_settings.xml b/src/gwt/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..3b31283
--- /dev/null
+++ b/src/gwt/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+ <settings>
+ <option name="PROJECT_PROFILE" value="Project Default" />
+ <option name="USE_PROJECT_PROFILE" value="true" />
+ <version value="1.0" />
+ </settings>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/libraries/gin.xml b/src/gwt/.idea/libraries/gin.xml
new file mode 100644
index 0000000..7b553af
--- /dev/null
+++ b/src/gwt/.idea/libraries/gin.xml
@@ -0,0 +1,15 @@
+<component name="libraryTable">
+ <library name="gin">
+ <CLASSES>
+ <root url="file://$PROJECT_DIR$/lib/gin/1.5" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/lib/gin/1.5/guice-snapshot.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gin/1.5/guice-assistedinject-snapshot.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gin/1.5/gin-1.5-post-gwt-2.2.jar!/" />
+ <root url="file://$PROJECT_DIR$/lib/gin/1.5" />
+ </SOURCES>
+ <jarDirectory url="file://$PROJECT_DIR$/lib/gin/1.5" recursive="false" />
+ </library>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/libraries/gwt.xml b/src/gwt/.idea/libraries/gwt.xml
new file mode 100644
index 0000000..a5aa3a6
--- /dev/null
+++ b/src/gwt/.idea/libraries/gwt.xml
@@ -0,0 +1,17 @@
+<component name="libraryTable">
+ <library name="gwt">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/lib/junit-4.9b3.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/gwt-user.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/validation-api-1.0.0.GA-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/validation-api-1.0.0.GA.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/gwt-api-checker.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/gwt-dev.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/gwt-user.jar!/" />
+ <root url="jar://$PROJECT_DIR$/lib/gwt/2.5.1/gwt-dev.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/misc.xml b/src/gwt/.idea/misc.xml
new file mode 100644
index 0000000..83522ec
--- /dev/null
+++ b/src/gwt/.idea/misc.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="DependencyValidationManager">
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </component>
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="FacetAutodetectingManager">
+ <autodetection-disabled>
+ <facet-type id="gwt">
+ <modules>
+ <module name="GWT">
+ <files>
+ <file url="file://$PROJECT_DIR$/bin/com/rstudio/codemirror/CodeMirror.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/bin/com/rstudio/core/Core.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/bin/com/rstudio/studio/PlainTextEditor.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/bin/com/rstudio/studio/RStudio.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/src/com/rstudio/codemirror/CodeMirror.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/src/com/rstudio/core/Core.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/src/com/rstudio/studio/PlainTextEditor.gwt.xml" />
+ <file url="file://$PROJECT_DIR$/src/com/rstudio/studio/RStudio.gwt.xml" />
+ </files>
+ </module>
+ </modules>
+ </facet-type>
+ <facet-type id="web">
+ <modules>
+ <module name="GWT">
+ <files>
+ <file url="file://$PROJECT_DIR$/web/WEB-INF/web.xml" />
+ </files>
+ </module>
+ </modules>
+ </facet-type>
+ </autodetection-disabled>
+ </component>
+ <component name="IdProvider" IDEtalkID="AC9390DF9D3D1C907504F5601CCC6695" />
+ <component name="JavadocGenerationManager">
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="OPTION_SCOPE" value="protected" />
+ <option name="OPTION_HIERARCHY" value="true" />
+ <option name="OPTION_NAVIGATOR" value="true" />
+ <option name="OPTION_INDEX" value="true" />
+ <option name="OPTION_SEPARATE_INDEX" value="true" />
+ <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+ <option name="OPTION_DEPRECATED_LIST" value="true" />
+ <option name="OTHER_OPTIONS" value="" />
+ <option name="HEAP_SIZE" />
+ <option name="LOCALE" />
+ <option name="OPEN_IN_BROWSER" value="true" />
+ </component>
+ <component name="ProjectDetails">
+ <option name="projectName" value="RStudio" />
+ </component>
+ <component name="ProjectResources">
+ <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype>
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/out" />
+ </component>
+</project>
+
diff --git a/src/gwt/.idea/modules.xml b/src/gwt/.idea/modules.xml
new file mode 100644
index 0000000..e47c02a
--- /dev/null
+++ b/src/gwt/.idea/modules.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/GWT.iml" filepath="$PROJECT_DIR$/GWT.iml" />
+ </modules>
+ </component>
+</project>
+
diff --git a/src/gwt/.idea/projectCodeStyle.xml b/src/gwt/.idea/projectCodeStyle.xml
new file mode 100644
index 0000000..67f2b0c
--- /dev/null
+++ b/src/gwt/.idea/projectCodeStyle.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <option name="USE_SAME_INDENTS" value="true" />
+ <option name="OTHER_INDENT_OPTIONS">
+ <value>
+ <option name="INDENT_SIZE" value="3" />
+ <option name="CONTINUATION_INDENT_SIZE" value="6" />
+ <option name="TAB_SIZE" value="3" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </value>
+ </option>
+ <option name="FIELD_NAME_SUFFIX" value="_" />
+ <option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
+ <option name="STATIC_FIELDS_ORDER_WEIGHT" value="7" />
+ <option name="FIELDS_ORDER_WEIGHT" value="6" />
+ <option name="CONSTRUCTORS_ORDER_WEIGHT" value="4" />
+ <option name="STATIC_METHODS_ORDER_WEIGHT" value="1" />
+ <option name="STATIC_INNER_CLASSES_ORDER_WEIGHT" value="2" />
+ <option name="INNER_CLASSES_ORDER_WEIGHT" value="3" />
+ <option name="RIGHT_MARGIN" value="80" />
+ <option name="BRACE_STYLE" value="2" />
+ <option name="CLASS_BRACE_STYLE" value="2" />
+ <option name="METHOD_BRACE_STYLE" value="2" />
+ <option name="ELSE_ON_NEW_LINE" value="true" />
+ <option name="CATCH_ON_NEW_LINE" value="true" />
+ <option name="FINALLY_ON_NEW_LINE" value="true" />
+ <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+ <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
+ <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
+ <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
+ <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
+ <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
+ <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" />
+ <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
+ <option name="CALL_PARAMETERS_WRAP" value="5" />
+ <option name="METHOD_PARAMETERS_WRAP" value="5" />
+ <option name="EXTENDS_LIST_WRAP" value="5" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="METHOD_CALL_CHAIN_WRAP" value="5" />
+ <option name="TERNARY_OPERATION_WRAP" value="5" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="5" />
+ <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
+ <ADDITIONAL_INDENT_OPTIONS fileType="java">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="js">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="jsp">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="sass">
+ <option name="INDENT_SIZE" value="2" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="xml">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <codeStyleSettings language="JavaScript">
+ <option name="BRACE_STYLE" value="2" />
+ <option name="CLASS_BRACE_STYLE" value="2" />
+ <option name="METHOD_BRACE_STYLE" value="2" />
+ <option name="ELSE_ON_NEW_LINE" value="true" />
+ <option name="CATCH_ON_NEW_LINE" value="true" />
+ <option name="FINALLY_ON_NEW_LINE" value="true" />
+ <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+ <option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
+ <option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
+ <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
+ <option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
+ <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
+ <option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" />
+ <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
+ <option name="CALL_PARAMETERS_WRAP" value="5" />
+ <option name="METHOD_PARAMETERS_WRAP" value="5" />
+ <option name="EXTENDS_LIST_WRAP" value="5" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="METHOD_CALL_CHAIN_WRAP" value="5" />
+ <option name="TERNARY_OPERATION_WRAP" value="5" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="5" />
+ <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ </value>
+ </option>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </component>
+</project>
+
diff --git a/src/gwt/.idea/runConfigurations/Run_Client.xml b/src/gwt/.idea/runConfigurations/Run_Client.xml
new file mode 100644
index 0000000..af7c86c
--- /dev/null
+++ b/src/gwt/.idea/runConfigurations/Run_Client.xml
@@ -0,0 +1,21 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration default="false" name="Run Client" type="GWT.ConfigurationType" factoryName="GWT Configuration">
+ <module name="GWT" />
+ <option name="VM_PARAMETERS" value="-Xmx1024m" />
+ <option name="SHELL_PARAMETERS" value="-noserver -war www" />
+ <option name="RUN_PAGE" value="http://localhost:8787" />
+ <option name="GWT_MODULE" value="org.rstudio.studio.RStudioDraft" />
+ <option name="OPEN_IN_BROWSER" value="false" />
+ <RunnerSettings RunnerId="Debug">
+ <option name="DEBUG_PORT" value="61866" />
+ <option name="TRANSPORT" value="0" />
+ <option name="LOCAL" value="true" />
+ </RunnerSettings>
+ <RunnerSettings RunnerId="Run" />
+ <ConfigurationWrapper RunnerId="Debug" />
+ <ConfigurationWrapper RunnerId="Run" />
+ <method>
+ <option name="Make" enabled="false" />
+ </method>
+ </configuration>
+</component>
\ No newline at end of file
diff --git a/src/gwt/.idea/vcs.xml b/src/gwt/.idea/vcs.xml
new file mode 100644
index 0000000..9ab281a
--- /dev/null
+++ b/src/gwt/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
+ </component>
+</project>
+
diff --git a/src/gwt/.project b/src/gwt/.project
new file mode 100644
index 0000000..ffa0a33
--- /dev/null
+++ b/src/gwt/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>client</name>
+ <comment>client project</comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.google.gwt.eclipse.core.gwtProjectValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>com.google.gwt.eclipse.core.gwtNature</nature>
+ </natures>
+</projectDescription>
diff --git a/src/gwt/.settings/com.google.gdt.eclipse.core.prefs b/src/gwt/.settings/com.google.gdt.eclipse.core.prefs
new file mode 100644
index 0000000..ed9aa05
--- /dev/null
+++ b/src/gwt/.settings/com.google.gdt.eclipse.core.prefs
@@ -0,0 +1,3 @@
+#Sun May 10 08:07:32 PDT 2009
+eclipse.preferences.version=1
+jarsExcludedFromWebInfLib=
diff --git a/src/gwt/.settings/com.google.gwt.eclipse.core.prefs b/src/gwt/.settings/com.google.gwt.eclipse.core.prefs
new file mode 100644
index 0000000..5538eff
--- /dev/null
+++ b/src/gwt/.settings/com.google.gwt.eclipse.core.prefs
@@ -0,0 +1,4 @@
+#Tue Apr 12 11:14:05 PDT 2011
+eclipse.preferences.version=1
+entryPointModules=org.rstudio.studio.RStudio
+gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgd3d3IC1sb2NhbFdvcmtlcnMgMl1dPjwvZXh0cmEtYXJncz48dm0tYXJncz48IVtDREFUQVstWG14NTEybV1dPjwvdm0tYXJncz48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
diff --git a/src/gwt/CMakeLists.txt b/src/gwt/CMakeLists.txt
new file mode 100644
index 0000000..59d2673
--- /dev/null
+++ b/src/gwt/CMakeLists.txt
@@ -0,0 +1,29 @@
+#
+# CMakeLists.txt
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# Unless you have received this program directly from RStudio pursuant
+# to the terms of a commercial license agreement with RStudio, then
+# this program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+cmake_minimum_required(VERSION 2.6)
+project (RSTUDIO_GWT)
+
+# invoke ant to build
+add_custom_target(gwt_build ALL)
+add_custom_command(
+ TARGET gwt_build
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND ant)
+
+# installation rules
+install(DIRECTORY www DESTINATION ${RSTUDIO_INSTALL_SUPPORTING})
+install(DIRECTORY extras/rstudio/symbolMaps/
+ DESTINATION ${RSTUDIO_INSTALL_SUPPORTING}/www-symbolmaps)
diff --git a/src/gwt/GWT.iml b/src/gwt/GWT.iml
new file mode 100644
index 0000000..db963d9
--- /dev/null
+++ b/src/gwt/GWT.iml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="gwt" name="GWT">
+ <configuration>
+ <setting name="compilerMaxHeapSize" value="128" />
+ <setting name="gwtScriptOutputStyle" value="PRETTY" />
+ <setting name="gwtSdkUrl" value="file://$MODULE_DIR$/lib/gwt/2.5.1" />
+ <packaging>
+ <module name="com.google.gwt.widgetideas.SliderBar" path="/com.google.gwt.widgetideas.SliderBar" />
+ <module name="org.rstudio.codemirror.CodeMirror" path="/com.rstudio.codemirror.CodeMirror" />
+ <module name="org.rstudio.core.Core" path="/com.rstudio.core.Core" />
+ <module name="org.rstudio.studio.PlainTextEditor" path="/com.rstudio.studio.PlainTextEditor" />
+ <module name="org.rstudio.studio.RStudio" path="/rstudio" />
+ <module name="org.rstudio.studio.RStudioDesktop" path="/rstudio" />
+ <module name="org.rstudio.studio.RStudioDraft" path="/rstudio" />
+ </packaging>
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/bin" />
+ <output-test url="file://$MODULE_DIR$/bin.test" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/.gwt" />
+ <excludeFolder url="file://$MODULE_DIR$/.idea" />
+ <excludeFolder url="file://$MODULE_DIR$/.settings" />
+ <excludeFolder url="file://$MODULE_DIR$/extras" />
+ <excludeFolder url="file://$MODULE_DIR$/gen" />
+ <excludeFolder url="file://$MODULE_DIR$/gwt-unitCache" />
+ <excludeFolder url="file://$MODULE_DIR$/lib" />
+ <excludeFolder url="file://$MODULE_DIR$/sdk" />
+ <excludeFolder url="file://$MODULE_DIR$/tools" />
+ <excludeFolder url="file://$MODULE_DIR$/www/rstudio" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="gin" level="project" />
+ <orderEntry type="library" name="gwt" level="project" />
+ </component>
+</module>
+
diff --git a/src/gwt/RStudio.launch b/src/gwt/RStudio.launch
new file mode 100644
index 0000000..f5e22b4
--- /dev/null
+++ b/src/gwt/RStudio.launch
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/client"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+</listAttribute>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="<?xml version="1.0" encoding="UTF-8"?>
<runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER" javaProject="client" path="1" type="4"/>
"/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8"?>
<runtimeClasspathEntry internalArchive="/client/src" path="3" type="2"/>
"/>
+<listEntry value="<?xml version="1.0" encoding="UTF-8"?>
<runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath">
<memento project="client"/>
</runtimeClasspathEntry>
"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war www -noserver -startupUrl http://localhost:8787 org.rstudio.studio.RStudio"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="client"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx512M"/>
+</launchConfiguration>
diff --git a/src/gwt/acesupport/acemode/auto_brace_insert.js b/src/gwt/acesupport/acemode/auto_brace_insert.js
new file mode 100644
index 0000000..2c47f6e
--- /dev/null
+++ b/src/gwt/acesupport/acemode/auto_brace_insert.js
@@ -0,0 +1,199 @@
+/*
+ * auto_brace_insert.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/auto_brace_insert", function(require, exports, module)
+{
+ var Range = require("ace/range").Range;
+ var TextMode = require("ace/mode/text").Mode;
+
+ (function()
+ {
+ this.$complements = {
+ "(": ")",
+ "[": "]",
+ '"': '"',
+ "{": "}"
+ };
+ this.$reOpen = /^[(["{]$/;
+ this.$reClose = /^[)\]"}]$/;
+
+ // reStop is the set of characters before which we allow ourselves to
+ // automatically insert a closing paren. If any other character
+ // immediately follows the cursor we will NOT do the insert.
+ this.$reStop = /^[;,\s)\]}]$/;
+
+ this.wrapInsert = function(session, __insert, position, text)
+ {
+ if (!this.insertMatching)
+ return __insert.call(session, position, text);
+
+ var cursor = session.selection.getCursor();
+ var typing = session.selection.isEmpty() &&
+ position.row == cursor.row &&
+ position.column == cursor.column;
+
+ if (typing) {
+ var postRng = Range.fromPoints(position, {
+ row: position.row,
+ column: position.column + 1});
+ var postChar = session.doc.getTextRange(postRng);
+ if (this.$reClose.test(postChar) && postChar == text) {
+ session.selection.moveCursorTo(postRng.end.row,
+ postRng.end.column,
+ false);
+ return;
+ }
+ }
+
+ var prevChar = null;
+ if (typing)
+ {
+ var rangeBegin = this.$moveLeft(session.doc, position);
+ prevChar = session.doc.getTextRange(Range.fromPoints(rangeBegin,
+ position));
+ }
+
+ var endPos = __insert.call(session, position, text);
+ // Is this an open paren?
+ if (typing && this.$reOpen.test(text)) {
+ // Is the next char not a character or number?
+ var nextCharRng = Range.fromPoints(endPos, {
+ row: endPos.row,
+ column: endPos.column + 1
+ });
+ var nextChar = session.doc.getTextRange(nextCharRng);
+ if (this.$reStop.test(nextChar) || nextChar.length == 0) {
+ if (this.allowAutoInsert(session, endPos, this.$complements[text])) {
+ session.doc.insert(endPos, this.$complements[text]);
+ session.selection.moveCursorTo(endPos.row, endPos.column, false);
+ }
+ }
+ }
+ else if (typing && text === "\n") {
+ var rangeEnd = this.$moveRight(session.doc, endPos);
+ if (prevChar == "{" && "}" == session.doc.getTextRange(Range.fromPoints(endPos, rangeEnd)))
+ {
+ var indent;
+ if (this.getIndentForOpenBrace)
+ indent = this.getIndentForOpenBrace(this.$moveLeft(session.doc, position));
+ else
+ indent = this.$getIndent(session.doc.getLine(endPos.row - 1));
+ session.doc.insert(endPos, "\n" + indent);
+ session.selection.moveCursorTo(endPos.row, endPos.column, false);
+ }
+ }
+ return endPos;
+ };
+
+ this.allowAutoInsert = function(session, pos, text)
+ {
+ return true;
+ };
+
+ // To enable this, call "this.allowAutoInsert = this.smartAllowAutoInsert"
+ // in the mode subclass
+ this.smartAllowAutoInsert = function(session, pos, text)
+ {
+ if (text !== "'" && text !== '"')
+ return true;
+
+ // Only allow auto-insertion of a quote char if the actual character
+ // that was typed, was the start of a new string token
+
+ if (pos.column == 0)
+ return true;
+
+ var token = this.codeModel.getTokenForPos(pos, false, true);
+ return token &&
+ token.type === 'string' &&
+ token.column === pos.column-1;
+ };
+
+ this.wrapRemove = function(editor, __remove, dir)
+ {
+ var cursor = editor.selection.getCursor();
+ var doc = editor.session.getDocument();
+
+ // Here are some easy-to-spot reasons why it might be impossible for us
+ // to need our special deletion logic.
+ if (!this.insertMatching ||
+ dir != "left" ||
+ !editor.selection.isEmpty() ||
+ editor.$readOnly ||
+ cursor.column == 0 || // hitting backspace at the start of line
+ doc.getLine(cursor.row).length <= cursor.column) {
+
+ return __remove.call(editor, dir);
+ }
+
+ var leftRange = Range.fromPoints(this.$moveLeft(doc, cursor), cursor);
+ var rightRange = Range.fromPoints(cursor, this.$moveRight(doc, cursor));
+ var leftText = doc.getTextRange(leftRange);
+
+ var deleteRight = this.$reOpen.test(leftText) &&
+ this.$complements[leftText] == doc.getTextRange(rightRange);
+
+ __remove.call(editor, dir);
+ if (deleteRight)
+ __remove.call(editor, 'right');
+ };
+
+ this.$moveLeft = function(doc, pos)
+ {
+ if (pos.row == 0 && pos.column == 0)
+ return pos;
+
+ var row = pos.row;
+ var col = pos.column;
+
+ if (col)
+ col--;
+ else
+ {
+ row--;
+ col = doc.getLine(row).length;
+ }
+ return {row: row, column: col};
+ };
+
+ this.$moveRight = function(doc, pos)
+ {
+ var row = pos.row;
+ var col = pos.column;
+
+ if (doc.getLine(row).length != col)
+ col++;
+ else
+ {
+ row++;
+ col = 0;
+ }
+
+ if (row >= doc.getLength())
+ return pos;
+ else
+ return {row: row, column: col};
+ };
+ }).call(TextMode.prototype);
+
+ exports.setInsertMatching = function(insertMatching) {
+ TextMode.prototype.insertMatching = insertMatching;
+ };
+
+});
\ No newline at end of file
diff --git a/src/gwt/acesupport/acemode/c_cpp.js b/src/gwt/acesupport/acemode/c_cpp.js
new file mode 100644
index 0000000..6a2e62e
--- /dev/null
+++ b/src/gwt/acesupport/acemode/c_cpp.js
@@ -0,0 +1,197 @@
+/*
+ * c_cpp.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs <fabian AT ajax DOT org>
+ * Gastón Kleiman <gaston.kleiman AT gmail DOT com>
+ *
+ * Based on Bespin's C/C++ Syntax Plugin by Marc McIntyre.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/c_cpp", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var c_cppHighlightRules = require("mode/c_cpp_highlight_rules").c_cppHighlightRules;
+
+var MatchingBraceOutdent = require("ace/mode/matching_brace_outdent").MatchingBraceOutdent;
+var Range = require("ace/range").Range;
+var CstyleBehaviour = require("ace/mode/behaviour/cstyle").CstyleBehaviour;
+
+var CppStyleFoldMode = null;
+if (!window.NodeWebkit)
+ CppStyleFoldMode = require("mode/c_cpp_fold_mode").FoldMode;
+
+var SweaveBackgroundHighlighter = require("mode/sweave_background_highlighter").SweaveBackgroundHighlighter;
+var RCodeModel = require("mode/r_code_model").RCodeModel;
+var RMatchingBraceOutdent = require("mode/r_matching_brace_outdent").RMatchingBraceOutdent;
+
+
+var Mode = function(suppressHighlighting, doc, session) {
+ this.$session = session;
+ this.$tokenizer = new Tokenizer(new c_cppHighlightRules().getRules());
+ this.$outdent = new MatchingBraceOutdent();
+ this.$r_outdent = {};
+ oop.implement(this.$r_outdent, RMatchingBraceOutdent);
+ this.$behaviour = new CstyleBehaviour();
+ this.codeModel = new RCodeModel(doc, this.$tokenizer, /^r-/, /^\s*\/\*{3,}\s+[Rr]\s*$/);
+ this.$sweaveBackgroundHighlighter = new SweaveBackgroundHighlighter(
+ session,
+ /^\s*\/\*{3,}\s+[Rr]\s*$/,
+ /^\*\/$/,
+ true);
+
+ if (!window.NodeWebkit)
+ this.foldingRules = new CppStyleFoldMode();
+
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+ this.insertChunkInfo = {
+ value: "/*** R\n\n*/\n",
+ position: {row: 1, column: 0}
+ };
+
+ this.toggleCommentLines = function(state, doc, startRow, endRow) {
+ var outdent = true;
+ var re = /^(\s*)\/\//;
+
+ for (var i=startRow; i<= endRow; i++) {
+ if (!re.test(doc.getLine(i))) {
+ outdent = false;
+ break;
+ }
+ }
+
+ if (outdent) {
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i=startRow; i<= endRow; i++)
+ {
+ var line = doc.getLine(i);
+ var m = line.match(re);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = m[0].length;
+ doc.replace(deleteRange, m[1]);
+ }
+ }
+ else {
+ doc.indentRows(startRow, endRow, "//");
+ }
+ };
+
+ this.getLanguageMode = function(position)
+ {
+ return this.$session.getState(position.row).match(/^r-/) ? 'R' : 'C_CPP';
+ };
+
+ this.inRLanguageMode = function(state)
+ {
+ return state.match(/^r-/);
+ };
+
+ this.getNextLineIndent = function(state, line, tab, tabSize, row) {
+
+ if (this.inRLanguageMode(state))
+ return this.codeModel.getNextLineIndent(row, line, state, tab, tabSize);
+
+ var indent = this.$getIndent(line);
+
+ var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
+ var tokens = tokenizedLine.tokens;
+ var endState = tokenizedLine.state;
+
+ if (tokens.length && tokens[tokens.length-1].type == "comment") {
+ return indent;
+ }
+
+ if (state == "start") {
+ var match = line.match(/^.*[\{\(\[]\s*$/);
+ if (match) {
+ indent += tab;
+ }
+ } else if (state == "doc-start") {
+ if (endState == "start") {
+ return "";
+ }
+ var match = line.match(/^\s*(\/?)\*/);
+ if (match) {
+ if (match[1]) {
+ indent += " ";
+ }
+ indent += "* ";
+ }
+ }
+
+ return indent;
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ if (this.inRLanguageMode(state))
+ return this.$r_outdent.checkOutdent(line,input);
+ else
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ if (this.inRLanguageMode(state))
+ return this.$r_outdent.autoOutdent(state, doc, row);
+ else
+ return this.$outdent.autoOutdent(doc, row);
+ };
+
+ this.transformAction = function(state, action, editor, session, text) {
+ if (action === 'insertion') {
+ if (text === "\n") {
+ // If beginning of doxygen comment, provide the end
+ var pos = editor.getSelectionRange().start;
+ var match = /^(\/\*[\*\!]\s*)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[1].length) {
+ return {text: "\n * \n */\n",
+ selection: [1, 3, 1, 3]};
+ }
+ // If newline in a doxygen comment, continue the comment
+ match = /^((\s*\/\/+')\s*)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[2].length) {
+ return {text: "\n" + match[1]};
+ }
+ }
+
+ else if (text === "R") {
+ // If newline to start and embedded R chunk complete the chunk
+ var pos = editor.getSelectionRange().start;
+ var match = /^(\s*\/\*{3,}\s+)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[1].length) {
+ return {text: "R\n\n*/\n",
+ selection: [1,0,1,0]};
+ }
+ }
+ }
+ return false;
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/c_cpp_fold_mode.js b/src/gwt/acesupport/acemode/c_cpp_fold_mode.js
new file mode 100644
index 0000000..275126d
--- /dev/null
+++ b/src/gwt/acesupport/acemode/c_cpp_fold_mode.js
@@ -0,0 +1,85 @@
+/*
+ * c_cpp_fold_mode.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs <fabian AT ajax DOT org>
+ * Gastón Kleiman <gaston.kleiman AT gmail DOT com>
+ *
+ * Based on Bespin's C/C++ Syntax Plugin by Marc McIntyre.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+define("mode/c_cpp_fold_mode", function(require, exports, module) {
+
+
+var oop = require("ace/lib/oop");
+var Range = require("ace/lib/range").Range;
+var BaseFoldMode = require("ace/mode/folding/fold_mode").FoldMode;
+
+var FoldMode = exports.FoldMode = function() {};
+oop.inherits(FoldMode, BaseFoldMode);
+
+(function() {
+
+ this.embeddedRComment = /^\s*\/\*{3,}\s+[Rr]\s*$/
+ this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
+ this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
+
+ this.getFoldWidget = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ if (this.foldingStartMarker.test(line) && !this.embeddedRComment.test(line))
+ return "start";
+ if (foldStyle == "markbeginend"
+ && this.foldingStopMarker
+ && this.foldingStopMarker.test(line))
+ return "end";
+ return "";
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ var match = line.match(this.foldingStartMarker);
+ if (match) {
+ var i = match.index;
+
+ if (match[1])
+ return this.openingBracketBlock(session, match[1], row, i);
+
+ return session.getCommentFoldRange(row, i + match[0].length, 1);
+ }
+
+ if (foldStyle !== "markbeginend")
+ return;
+
+ var match = line.match(this.foldingStopMarker);
+ if (match) {
+ var i = match.index + match[0].length;
+
+ if (match[1])
+ return this.closingBracketBlock(session, match[1], row, i);
+
+ return session.getCommentFoldRange(row, i, -1);
+ }
+ };
+
+}).call(FoldMode.prototype);
+
+});
\ No newline at end of file
diff --git a/src/gwt/acesupport/acemode/c_cpp_highlight_rules.js b/src/gwt/acesupport/acemode/c_cpp_highlight_rules.js
new file mode 100644
index 0000000..bb3950b
--- /dev/null
+++ b/src/gwt/acesupport/acemode/c_cpp_highlight_rules.js
@@ -0,0 +1,226 @@
+/*
+ * c_cpp_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs <fabian AT ajax DOT org>
+ * Gastón Kleiman <gaston.kleiman AT gmail DOT com>
+ *
+ * Based on Bespin's C/C++ Syntax Plugin by Marc McIntyre.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/c_cpp_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var lang = require("ace/lib/lang");
+var DocCommentHighlightRules = require("mode/doc_comment_highlight_rules").DocCommentHighlightRules;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
+var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;
+
+var c_cppHighlightRules = function() {
+
+ var keywords = lang.arrayToMap(
+ ("alignas|alignof|and|and_eq|asm|auto|bitand|bitor|bool|break|" +
+ "case|catch|char|char16_t|char32_t|class|compl|const|" +
+ "constexpr|const_cast|continue|decltype|default|delete|do|" +
+ "double|dynamic_cast|else|enum|explicit|export|extern|false|" +
+ "float|for|friend|goto|if|inline|int|long|mutable|namespace|" +
+ "new|noexcept|not|not_eq|nullptr|operator|or|or_eq|private|" +
+ "protected|public|register|reinterpret_cast|return|short|" +
+ "signed|sizeof|static|static_assert|static_cast|struct|" +
+ "switch|template|this|thread_local|throw|true|try|typedef|" +
+ "typeid|typename|union|unsigned|using|virtual|void|volatile|" +
+ "wchar_t|while|xor|xor_eq").split("|")
+ );
+
+ var buildinConstants = lang.arrayToMap(
+ ("NULL").split("|")
+ );
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ // Attributes
+ token: "comment.doc.tag",
+ regex: "\\/\\/\\s*\\[\\[.*\\]\\].*$"
+ }, {
+ // Roxygen
+ token : "comment",
+ regex : "\\/\\/'",
+ next : "rd-start"
+ }, {
+ // Standard comment
+ token : "comment",
+ regex : "\\/\\/.*$"
+ },
+ DocCommentHighlightRules.getStartRule("doc-start"),
+ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "comment"
+ }, {
+ token : "string", // single line
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : '["].*\\\\$',
+ next : "qqstring"
+ }, {
+ token : "string", // single line
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : "['].*\\\\$",
+ next : "qstring"
+ }, {
+ token : "constant.numeric", // hex
+ regex : "0[xX][0-9a-fA-F]+\\b"
+ }, {
+ token : "constant.numeric", // float
+ regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
+ }, {
+ token : "constant", // <CONSTANT>
+ regex : "<[a-zA-Z0-9.]+>"
+ }, {
+ token : "keyword", // pre-compiler directivs
+ regex : "(?:#include|#pragma|#line|#define|#undef|#ifdef|#else|#elif|#endif|#ifndef)"
+ }, {
+ token : function(value) {
+ if (value == "this")
+ return "variable.language";
+ else if (keywords.hasOwnProperty(value))
+ return "keyword";
+ else if (buildinConstants.hasOwnProperty(value))
+ return "constant.language";
+ else
+ return "identifier";
+ },
+ regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
+ }, {
+ token : "keyword.operator",
+ regex : "!|\\$|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|==|=|!=|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|new|delete|typeof|void)"
+ }, {
+ token : "punctuation.operator",
+ regex : "\\?|\\:|\\,|\\;|\\."
+ }, {
+ token : "paren.lparen",
+ regex : "[[({]"
+ }, {
+ token : "paren.rparen",
+ regex : "[\\])}]"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }
+ ],
+ "comment" : [
+ {
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ next : "start"
+ }, {
+ token : "comment", // comment spanning whole line
+ merge : true,
+ regex : ".+"
+ }
+ ],
+ "qqstring" : [
+ {
+ token : "string",
+ regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
+ next : "start"
+ }, {
+ token : "string",
+ merge : true,
+ regex : '.+'
+ }
+ ],
+ "qstring" : [
+ {
+ token : "string",
+ regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
+ next : "start"
+ }, {
+ token : "string",
+ merge : true,
+ regex : '.+'
+ }
+ ]
+ };
+
+ var rdRules = new TexHighlightRules("comment").getRules();
+
+ // Make all embedded TeX virtual-comment so they don't interfere with
+ // auto-indent.
+ for (var i = 0; i < rdRules["start"].length; i++) {
+ rdRules["start"][i].token += ".virtual-comment";
+ }
+
+ this.addRules(rdRules, "rd-");
+ this.$rules["rd-start"].unshift({
+ token: "text",
+ regex: "^",
+ next: "start"
+ });
+ this.$rules["rd-start"].unshift({
+ token : "keyword",
+ regex : "@(?!@)[^ ]*"
+ });
+ this.$rules["rd-start"].unshift({
+ token : "comment",
+ regex : "@@"
+ });
+ this.$rules["rd-start"].push({
+ token : "comment",
+ regex : "[^%\\\\[({\\])}]+"
+ });
+
+ this.embedRules(DocCommentHighlightRules, "doc-",
+ [ DocCommentHighlightRules.getEndRule("start") ]);
+
+ // Embed R syntax highlighting
+ this.$rules["start"].unshift({
+ token: "support.function.codebegin",
+ regex: "^\\s*\\/\\*{3,}\\s+[Rr]\\s*$",
+ next: "r-start"
+ });
+
+ var rRules = new RHighlightRules().getRules();
+ this.addRules(rRules, "r-");
+ this.$rules["r-start"].unshift({
+ token: "support.function.codeend",
+ regex: "\\*\\/",
+ next: "start"
+ });
+
+
+};
+
+oop.inherits(c_cppHighlightRules, TextHighlightRules);
+
+exports.c_cppHighlightRules = c_cppHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/dcf.js b/src/gwt/acesupport/acemode/dcf.js
new file mode 100644
index 0000000..7c7a299
--- /dev/null
+++ b/src/gwt/acesupport/acemode/dcf.js
@@ -0,0 +1,35 @@
+/*
+ * dcf.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/dcf", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var DcfHighlightRules = require("mode/dcf_highlight_rules").DcfHighlightRules;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new DcfHighlightRules().getRules());
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/dcf_highlight_rules.js b/src/gwt/acesupport/acemode/dcf_highlight_rules.js
new file mode 100644
index 0000000..1f39fa9
--- /dev/null
+++ b/src/gwt/acesupport/acemode/dcf_highlight_rules.js
@@ -0,0 +1,39 @@
+/*
+ * dcf_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/dcf_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var DcfHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+
+ "start" : [ {
+ token : ["keyword", "text"],
+ regex : "^" +"([\\w-]+)" + "(\\:)"
+ }, {
+ token : "text",
+ regex : ".+"
+ } ]
+ };
+};
+oop.inherits(DcfHighlightRules, TextHighlightRules);
+
+exports.DcfHighlightRules = DcfHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/doc_comment_highlight_rules.js b/src/gwt/acesupport/acemode/doc_comment_highlight_rules.js
new file mode 100644
index 0000000..38fde75
--- /dev/null
+++ b/src/gwt/acesupport/acemode/doc_comment_highlight_rules.js
@@ -0,0 +1,80 @@
+/*
+ * doc_comment_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs <fabian AT ajax DOT org>
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/doc_comment_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var DocCommentHighlightRules = function() {
+
+ this.$rules = {
+ "start" : [ {
+ token : "comment.doc.tag",
+ regex : "[@\\\\][\\w\\d_]+" // TODO: fix email addresses
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "\\s+"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "TODO"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "[^@\\\\\\*]+"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "."
+ }]
+ };
+};
+
+oop.inherits(DocCommentHighlightRules, TextHighlightRules);
+
+DocCommentHighlightRules.getStartRule = function(start) {
+ return {
+ token : "comment.doc", // doc comment
+ merge : true,
+ regex : "\\/\\*[\\*\\!]",
+ next : start
+ };
+};
+
+DocCommentHighlightRules.getEndRule = function (start) {
+ return {
+ token : "comment.doc", // closing comment
+ merge : true,
+ regex : "\\*\\/",
+ next : start
+ };
+};
+
+
+exports.DocCommentHighlightRules = DocCommentHighlightRules;
+
+});
diff --git a/src/gwt/acesupport/acemode/markdown.js b/src/gwt/acesupport/acemode/markdown.js
new file mode 100644
index 0000000..8a3a654
--- /dev/null
+++ b/src/gwt/acesupport/acemode/markdown.js
@@ -0,0 +1,44 @@
+/*
+ * markdown.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/markdown", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var MarkdownHighlightRules = require("mode/markdown_highlight_rules").MarkdownHighlightRules;
+var MarkdownFoldMode = require("mode/markdown_folding").FoldMode;
+
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new MarkdownHighlightRules().getRules());
+
+ this.foldingRules = new MarkdownFoldMode();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/markdown_folding.js b/src/gwt/acesupport/acemode/markdown_folding.js
new file mode 100644
index 0000000..319881f
--- /dev/null
+++ b/src/gwt/acesupport/acemode/markdown_folding.js
@@ -0,0 +1,112 @@
+/*
+ * markdown_folding.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/markdown_folding", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var BaseFoldMode = require("ace/mode/folding/fold_mode").FoldMode;
+var Range = require("ace/range").Range;
+
+var FoldMode = exports.FoldMode = function() {};
+oop.inherits(FoldMode, BaseFoldMode);
+
+(function() {
+ this.foldingStartMarker = /^(?:[=-]+\s*$|#{1,6} |`{3})/;
+
+ this.getFoldWidget = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ if (!this.foldingStartMarker.test(line))
+ return "";
+
+ if (line[0] == "`") {
+ if (session.bgTokenizer.getState(row) == "start")
+ return "end";
+ return "start";
+ }
+
+ return "start";
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ var startColumn = line.length;
+ var maxRow = session.getLength();
+ var startRow = row;
+ var endRow = row;
+ if (!line.match(this.foldingStartMarker))
+ return;
+
+ if (line[0] == "`") {
+ if (session.bgTokenizer.getState(row) !== "start") {
+ while (++row < maxRow) {
+ line = session.getLine(row);
+ if (line[0] == "`" & line.substring(0, 3) == "```")
+ break;
+ }
+ return new Range(startRow, startColumn, row, 0);
+ } else {
+ while (row -- > 0) {
+ line = session.getLine(row);
+ if (line[0] == "`" & line.substring(0, 3) == "```")
+ break;
+ }
+ return new Range(row, line.length, startRow, 0);
+ }
+ }
+
+ var token;
+ function isHeading(row) {
+ token = session.getTokens(row)[0];
+ return token && token.type.lastIndexOf(heading, 0) === 0;
+ }
+
+ var heading = "markup.heading";
+ function getLevel() {
+ var ch = token.value[0];
+ if (ch == "=") return 6;
+ if (ch == "-") return 5;
+ return 7 - token.value.search(/[^#]/);
+ }
+
+ if (isHeading(row)) {
+ var startHeadingLevel = getLevel();
+ while (++row < maxRow) {
+ if (!isHeading(row))
+ continue;
+ var level = getLevel();
+ if (level >= startHeadingLevel)
+ break;
+ }
+
+ endRow = row - (!token || ["=", "-"].indexOf(token.value[0]) == -1 ? 1 : 2);
+
+ if (endRow > startRow) {
+ while (endRow > startRow && /^\s*$/.test(session.getLine(endRow)))
+ endRow--;
+ }
+
+ if (endRow > startRow) {
+ var endColumn = session.getLine(endRow).length;
+ return new Range(startRow, startColumn, endRow, endColumn);
+ }
+ }
+ };
+
+}).call(FoldMode.prototype);
+
+});
\ No newline at end of file
diff --git a/src/gwt/acesupport/acemode/markdown_highlight_rules.js b/src/gwt/acesupport/acemode/markdown_highlight_rules.js
new file mode 100644
index 0000000..30348c4
--- /dev/null
+++ b/src/gwt/acesupport/acemode/markdown_highlight_rules.js
@@ -0,0 +1,218 @@
+/*
+ * markdown_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/markdown_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var lang = require("ace/lib/lang");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var MarkdownHighlightRules = function() {
+
+ var slideFields = lang.arrayToMap(
+ ("title|author|date|rtl|depends|autosize|width|height|transition|transition-speed|font-family|css|class|navigation|incremental|left|right|id|audio|video|type|at|help-doc|help-topic|source|console|console-input|execute|pause")
+ .split("|")
+ );
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [ {
+ token : "empty_line",
+ regex : '^$'
+ }, { // code span `
+ token : ["support.function", "support.function", "support.function"],
+ regex : "(`+)([^\\r]*?[^`])(\\1)"
+ }, { // code block
+ token : "support.function",
+ regex : "^[ ]{4}.+"
+ }, { // h1 with equals
+ token: "markup.heading.1",
+ regex: "^\\={3,}\\s*$",
+ next: "fieldblock"
+ }, { // h1
+ token: "markup.heading.1",
+ regex: "^=+(?=\\s*$)"
+ }, { // h2
+ token: "markup.heading.2",
+ regex: "^\\-+(?=\\s*$)"
+ }, { // header
+ token : function(value) {
+ return "markup.heading." + value.search(/[^#]/);
+ },
+ regex : "^#{1,6}(?:[^ #].*| +.*(?:[^ #].*|[^ ]+.* +#+ *))$"
+ }, { // Github style block
+ token : "support.function",
+ regex : "^```[a-zA-Z]+\\s*$",
+ next : "githubblock"
+ }, { // block quote
+ token : "string",
+ regex : "^>[ ].+$",
+ next : "blockquote"
+ }, { // reference
+ token : ["text", "constant", "text", "url", "string", "text"],
+ regex : "^([ ]{0,3}\\[)([^\\]]+)(\\]:\\s*)([^ ]+)(\\s*(?:[\"][^\"]+[\"])?(\\s*))$"
+ }, { // link by reference
+ token : ["text", "keyword", "text", "constant", "text"],
+ regex : "(\\[)((?:[[^\\]]*\\]|[^\\[\\]])*)(\\][ ]?(?:\\n[ ]*)?\\[)(.*?)(\\])"
+ }, { // link by url
+ token : ["text", "keyword", "text", "markup.underline", "string", "text"],
+ regex : "(\\[)"+
+ "(\\[[^\\]]*\\]|[^\\[\\]]*)"+
+ "(\\]\\([ \\t]*)"+
+ "(<?(?:(?:[^\\(]*?\\([^\\)]*?\\)\\S*?)|(?:.*?))>?)"+
+ "((?:[ \t]*\"(?:.*?)\"[ \\t]*)?)"+
+ "(\\))"
+ }, { // HR *
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\*[ ]?){3,}\\s*$"
+ }, { // HR -
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\-[ ]?){3,}\\s*$"
+ }, { // HR _
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\_[ ]?){3,}\\s*$"
+ }, { // MathJax native display \[ ... \]
+ token : "markup.list",
+ regex : "\\\\\\[",
+ next : "mathjaxnativedisplay"
+ }, { // MathJax native inline \( ... \)
+ token : "markup.list",
+ regex : "\\\\\\(",
+ next : "mathjaxnativeinline"
+ }, { // $ escape
+ token : "text",
+ regex : "\\\\\\$"
+ }, { // MathJax $$(?:latex)?
+ token : "markup.list",
+ regex : "\\${2}(?:latex(?:\\s|$))?",
+ next : "mathjaxdisplay"
+ }, { // MathJax $latex
+ token : "markup.list",
+ regex : "\\$latex\\s",
+ next : "mathjaxinline"
+ }, { // MathJax $...$ (org-mode style)
+ token : ["markup.list","support.function","markup.list"],
+ regex : "(\\$)" + "((?!\\s)[^$]*[^$\\s])" + "(\\$)" + "(?![\\w\\d`])"
+ }, { // strong ** __
+ token : ["constant.numeric", "constant.numeric", "constant.numeric"],
+ regex : "([*]{2}|[_]{2}(?=\\S))([^\\r]*?\\S[*_]*)(\\1)"
+ }, { // emphasis * _
+ token : ["constant.language.boolean", "constant.language.boolean", "constant.language.boolean"],
+ regex : "([*]|[_](?=\\S))([^\\r]*?\\S[*_]*)(\\1)"
+ }, { //
+ token : ["text", "url", "text"],
+ regex : "(<)("+
+ "(?:https?|ftp|dict):[^'\">\\s]+"+
+ "|"+
+ "(?:mailto:)?[-.\\w]+\\@[-a-z0-9]+(?:\\.[-a-z0-9]+)*\\.[a-z]+"+
+ ")(>)"
+ }, {
+ token : "text",
+ regex : "[^\\*_%$`\\[#<>\\\\]+"
+ } , {
+ token : "text",
+ regex : "\\\\"
+ } ],
+
+ "blockquote" : [ { // BLockquotes only escape on blank lines.
+ token : "empty_line",
+ regex : "^\\s*$",
+ next : "start"
+ }, {
+ token : "string",
+ regex : ".+"
+ } ],
+
+ "githubblock" : [ {
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : ".+"
+ } ],
+
+ "fieldblock" : [ {
+ token : function(value) {
+ var field = value.slice(0,-1);
+ if (slideFields[field])
+ return "comment.doc.tag";
+ else
+ return "text";
+ },
+ regex : "^" +"[\\w-]+\\:",
+ next : "fieldblockvalue"
+ }, {
+ token : "text",
+ regex : "(?=.+)",
+ next : "start"
+ } ],
+
+ "fieldblockvalue" : [ {
+ token : "text",
+ regex : "$",
+ next : "fieldblock"
+ }, {
+ token : "text",
+ regex : ".+"
+ } ],
+
+ "mathjaxdisplay" : [ {
+ token : "markup.list",
+ regex : "\\${2}",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : "[^\\$]+"
+ } ],
+
+ "mathjaxnativedisplay" : [ {
+ token : "markup.list",
+ regex : "\\\\\\]",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : "[\\s\\S]+?"
+ } ],
+
+ "mathjaxinline" : [ {
+ token : "markup.list",
+ regex : "\\$",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : "[^\\$]+"
+ } ],
+
+ "mathjaxnativeinline" : [ {
+ token : "markup.list",
+ regex : "\\\\\\)",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : "[\\s\\S]+?"
+ } ]
+ };
+};
+oop.inherits(MarkdownHighlightRules, TextHighlightRules);
+
+exports.MarkdownHighlightRules = MarkdownHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/r.js b/src/gwt/acesupport/acemode/r.js
new file mode 100644
index 0000000..011a670
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r.js
@@ -0,0 +1,111 @@
+/*
+ * r.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/r", function(require, exports, module)
+{
+ var Editor = require("ace/editor").Editor;
+ var EditSession = require("ace/edit_session").EditSession;
+ var Range = require("ace/range").Range;
+ var oop = require("ace/lib/oop");
+ var TextMode = require("ace/mode/text").Mode;
+ var Tokenizer = require("ace/tokenizer").Tokenizer;
+ var TextHighlightRules = require("ace/mode/text_highlight_rules")
+ .TextHighlightRules;
+ var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;
+ var RCodeModel = require("mode/r_code_model").RCodeModel;
+ var RMatchingBraceOutdent = require("mode/r_matching_brace_outdent").RMatchingBraceOutdent;
+ var AutoBraceInsert = require("mode/auto_brace_insert").AutoBraceInsert;
+ var unicode = require("ace/unicode");
+
+ var Mode = function(suppressHighlighting, doc, session)
+ {
+ if (suppressHighlighting)
+ this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules());
+ else
+ this.$tokenizer = new Tokenizer(new RHighlightRules().getRules());
+
+ this.codeModel = new RCodeModel(doc, this.$tokenizer, null);
+ this.foldingRules = this.codeModel;
+ };
+ oop.inherits(Mode, TextMode);
+
+ (function()
+ {
+ oop.implement(this, RMatchingBraceOutdent);
+
+ this.tokenRe = new RegExp("^["
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "._]+", "g"
+ );
+
+ this.nonTokenRe = new RegExp("^(?:[^"
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "._]|\s])+", "g"
+ );
+
+ this.$complements = {
+ "(": ")",
+ "[": "]",
+ '"': '"',
+ "'": "'",
+ "{": "}"
+ };
+ this.$reOpen = /^[(["'{]$/;
+ this.$reClose = /^[)\]"'}]$/;
+
+ this.getNextLineIndent = function(state, line, tab, tabSize, row)
+ {
+ return this.codeModel.getNextLineIndent(row, line, state, tab, tabSize);
+ };
+
+ this.allowAutoInsert = this.smartAllowAutoInsert;
+
+ this.getIndentForOpenBrace = function(openBracePos)
+ {
+ return this.codeModel.getIndentForOpenBrace(openBracePos);
+ };
+
+ this.$getIndent = function(line) {
+ var match = line.match(/^(\s+)/);
+ if (match) {
+ return match[1];
+ }
+
+ return "";
+ };
+
+ this.transformAction = function(state, action, editor, session, text) {
+ if (action === 'insertion' && text === "\n") {
+
+ // If newline in a doxygen comment, continue the comment
+ var pos = editor.getSelectionRange().start;
+ var match = /^((\s*#+')\s*)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[2].length) {
+ return {text: "\n" + match[1]};
+ }
+ }
+ return false;
+ };
+ }).call(Mode.prototype);
+ exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/r_background_highlighter.js b/src/gwt/acesupport/acemode/r_background_highlighter.js
new file mode 100644
index 0000000..eaeaba6
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r_background_highlighter.js
@@ -0,0 +1,130 @@
+/*
+ * r_background_highlighter.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/r_background_highlighter", function(require, exports, module)
+{
+ var Range = require("ace/range").Range;
+
+ var RBackgroundHighlighter = function(session) {
+ this.$session = session;
+ this.$doc = session.getDocument();
+
+ var that = this;
+ this.$doc.on('change', function(evt) {
+ that.$onDocChange.apply(that, [evt]);
+ });
+
+ this.$rowState = new Array(this.$doc.getLength());
+ this.$markers = new Array();
+
+ for (var i = 0; i < this.$doc.getLength(); i++)
+ this.$updateRow(i);
+ this.$syncMarkers(0);
+ };
+
+ (function() {
+
+ this.$updateRow = function(row) {
+ if (this.$doc.getLine(row).match("^\\s*#+'.*$"))
+ this.$rowState[row] = true;
+ else {
+ this.$rowState[row] = false;
+ }
+ };
+
+ this.$syncMarkers = function(startRow, endRow) {
+ if (typeof(endRow) == 'undefined')
+ endRow = this.$doc.getLength() - 1;
+
+ for (var row = startRow; row <= endRow; row++) {
+ if (!!this.$rowState[row] != !!this.$markers[row]) {
+ if (this.$rowState[row]) {
+ this.$markers[row] = this.$session.addMarker(new Range(row, 0, row, this.$session.getLine(row).length),
+ "ace_foreign_line",
+ "background",
+ false);
+ }
+ else {
+ this.$session.removeMarker(this.$markers[row]);
+ delete this.$markers[row];
+ }
+ }
+ }
+ };
+
+ this.$insertNewRows = function(index, count) {
+ var args = new Array(count + 2);
+ args[0] = index;
+ args[1] = 0;
+ Array.prototype.splice.apply(this.$rowState, args);
+ };
+
+ this.$removeRows = function(index, count) {
+ var markers = this.$rowState.splice(index, count);
+ };
+
+ this.$onDocChange = function(evt)
+ {
+ var delta = evt.data;
+
+ if (delta.action === "insertLines")
+ {
+ var newLineCount = delta.range.end.row - delta.range.start.row;
+ this.$insertNewRows(delta.range.start.row, newLineCount);
+ for (var i = 0; i < newLineCount; i++)
+ this.$updateRow(delta.range.start.row + i);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else if (delta.action === "insertText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$insertNewRows(delta.range.end.row, 1);
+ this.$updateRow(delta.range.start.row);
+ this.$updateRow(delta.range.start.row + 1);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else
+ {
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row, delta.range.start.row);
+ }
+ }
+ else if (delta.action === "removeLines")
+ {
+ this.$removeRows(delta.range.start.row,
+ delta.range.end.row - delta.range.start.row);
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else if (delta.action === "removeText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$removeRows(delta.range.end.row, 1);
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else
+ {
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row, delta.range.start.row);
+ }
+ }
+ };
+
+ }).call(RBackgroundHighlighter.prototype);
+
+ exports.RBackgroundHighlighter = RBackgroundHighlighter;
+});
diff --git a/src/gwt/acesupport/acemode/r_code_model.js b/src/gwt/acesupport/acemode/r_code_model.js
new file mode 100644
index 0000000..fca7dee
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r_code_model.js
@@ -0,0 +1,1214 @@
+/*
+ * r_code_model.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/r_code_model", function(require, exports, module) {
+
+var Range = require("ace/range").Range;
+var TokenIterator = require("ace/token_iterator").TokenIterator;
+
+var $verticallyAlignFunctionArgs = false;
+
+function comparePoints(pos1, pos2)
+{
+ if (pos1.row != pos2.row)
+ return pos1.row - pos2.row;
+ return pos1.column - pos2.column;
+}
+
+var ScopeManager = require("mode/r_scope_tree").ScopeManager;
+
+var RCodeModel = function(doc, tokenizer, statePattern, codeBeginPattern) {
+ this.$doc = doc;
+ this.$tokenizer = tokenizer;
+ this.$tokens = new Array(doc.getLength());
+ this.$endStates = new Array(doc.getLength());
+ this.$statePattern = statePattern;
+ this.$codeBeginPattern = codeBeginPattern;
+ this.$scopes = new ScopeManager();
+
+ var that = this;
+ this.$doc.on('change', function(evt) {
+ that.$onDocChange.apply(that, [evt]);
+ });
+
+ this.$TokenCursor = function(row, offset)
+ {
+ this.$row = row || 0;
+ this.$offset = offset || 0;
+ };
+ (function() {
+ this.moveToStartOfRow = function(row)
+ {
+ this.$row = row;
+ this.$offset = 0;
+ };
+
+ this.moveToPreviousToken = function()
+ {
+ while (this.$offset == 0 && this.$row > 0)
+ {
+ this.$row--;
+ this.$offset = that.$tokens[this.$row].length;
+ }
+
+ if (this.$offset == 0)
+ return false;
+
+ this.$offset--;
+
+ return true;
+ };
+
+ this.moveToNextToken = function(maxRow)
+ {
+ if (this.$row > maxRow)
+ return;
+
+ this.$offset++;
+
+ while (this.$offset >= that.$tokens[this.$row].length && this.$row < maxRow)
+ {
+ this.$row++;
+ this.$offset = 0;
+ }
+
+ if (this.$offset >= that.$tokens[this.$row].length)
+ return false;
+
+ return true;
+ };
+
+ this.seekToNearestToken = function(position, maxRow)
+ {
+ if (position.row > maxRow)
+ return false;
+ this.$row = position.row;
+ var rowTokens = that.$tokens[this.$row] || [];
+ for (this.$offset = 0; this.$offset < rowTokens.length; this.$offset++)
+ {
+ var token = rowTokens[this.$offset];
+ if (token.column >= position.column)
+ {
+ return true;
+ }
+ }
+ return this.moveToNextToken(maxRow);
+ };
+
+ this.moveBackwardOverMatchingParens = function()
+ {
+ if (!this.moveToPreviousToken())
+ return false;
+ if (this.currentValue() !== ")")
+ return false;
+
+ var success = false;
+ var parenCount = 0;
+ while (this.moveToPreviousToken())
+ {
+ if (this.currentValue() === "(")
+ {
+ if (parenCount == 0)
+ {
+ success = true;
+ break;
+ }
+ parenCount--;
+ }
+ else if (this.currentValue() === ")")
+ {
+ parenCount++;
+ }
+ }
+ return success;
+ };
+
+ this.findToken = function(predicate, maxRow)
+ {
+ do
+ {
+ var t = this.currentToken();
+ if (t && predicate(t))
+ return t;
+ }
+ while (this.moveToNextToken(maxRow));
+ return null;
+ };
+
+ this.currentToken = function()
+ {
+ return (that.$tokens[this.$row] || [])[this.$offset];
+ };
+
+ this.currentValue = function()
+ {
+ return this.currentToken().value;
+ };
+
+ this.currentPosition = function()
+ {
+ var token = this.currentToken();
+ if (token === null)
+ return null;
+ else
+ return {row: this.$row, column: token.column};
+ };
+
+ this.cloneCursor = function()
+ {
+ var clone = new that.$TokenCursor();
+ clone.$row = this.$row;
+ clone.$offset = this.$offset;
+ return clone;
+ };
+
+ this.isFirstSignificantTokenOnLine = function()
+ {
+ return this.$offset == 0;
+ };
+
+ this.isLastSignificantTokenOnLine = function()
+ {
+ return this.$offset == (that.$tokens[this.$row] || []).length - 1;
+ };
+
+ }).call(this.$TokenCursor.prototype);
+
+};
+
+(function () {
+
+ this.$complements = {
+ '(': ')',
+ ')': '(',
+ '[': ']',
+ ']': '[',
+ '{': '}',
+ '}': '{'
+ };
+
+ function pFunction(t)
+ {
+ return t.type == 'keyword' && t.value == 'function';
+ }
+
+ function pAssign(t)
+ {
+ return /\boperator\b/.test(t.type) && /^(=|<-|<<-)$/.test(t.value);
+ }
+
+ function pIdentifier(t)
+ {
+ return /\bidentifier\b/.test(t.type);
+ }
+
+ function findAssocFuncToken(tokenCursor)
+ {
+ if (tokenCursor.currentValue() !== "{")
+ return false;
+ if (!tokenCursor.moveBackwardOverMatchingParens())
+ return false;
+ if (!tokenCursor.moveToPreviousToken())
+ return false;
+ if (!pFunction(tokenCursor.currentToken()))
+ return false;
+ if (!tokenCursor.moveToPreviousToken())
+ return false;
+ if (!pAssign(tokenCursor.currentToken()))
+ return false;
+ if (!tokenCursor.moveToPreviousToken())
+ return false;
+ if (!pIdentifier(tokenCursor.currentToken()))
+ return false;
+
+ return true;
+ }
+
+ this.$buildScopeTreeUpToRow = function(maxrow)
+ {
+ function maybeEvaluateLiteralString(value) {
+ // NOTE: We could evaluate escape sequences and whatnot here as well.
+ // Hard to imagine who would abuse Rnw by putting escape
+ // sequences in chunk labels, though.
+ var match = /^(['"])(.*)\1$/.exec(value);
+ if (!match)
+ return value;
+ else
+ return match[2];
+ }
+
+ function getChunkLabel(reOptions, comment) {
+ var match = reOptions.exec(comment);
+ if (!match)
+ return null;
+ var value = match[1];
+ var values = value.split(',');
+ if (values.length == 0)
+ return null;
+
+ // If first arg has no =, it's a label
+ if (!/=/.test(values[0])) {
+ return values[0].replace(/(^\s+)|(\s+$)/g, '');
+ }
+
+ for (var i = 0; i < values.length; i++) {
+ match = /^\s*label\s*=\s*(.*)$/.exec(values[i]);
+ if (match) {
+ return maybeEvaluateLiteralString(
+ match[1].replace(/(^\s+)|(\s+$)/g, ''));
+ }
+ }
+
+ return null;
+ }
+
+ // It's possible that determining the scope at 'position' may require
+ // parsing beyond the position itself--for example if the position is
+ // on the identifier of a function whose open brace is a few tokens later.
+ // Seems like it would be rare indeed for this distance to be more than 30
+ // rows.
+ maxRow = Math.min(maxrow + 30, this.$doc.getLength() - 1);
+ this.$tokenizeUpToRow(maxRow);
+
+ //console.log("Seeking to " + this.$scopes.parsePos.row + "x"+ this.$scopes.parsePos.column);
+ var tokenCursor = new this.$TokenCursor();
+ if (!tokenCursor.seekToNearestToken(this.$scopes.parsePos, maxRow))
+ return;
+
+ do
+ {
+ this.$scopes.parsePos = tokenCursor.currentPosition();
+ this.$scopes.parsePos.column += tokenCursor.currentValue().length;
+
+ //console.log(" Token: " + tokenCursor.currentValue() + " [" + tokenCursor.currentPosition().row + "x" + tokenCursor.currentPosition().column + "]");
+
+ var tokenType = tokenCursor.currentToken().type;
+ if (/\bsectionhead\b/.test(tokenType))
+ {
+ var sectionHeadMatch = /^#+'?[-=#\s]*(.*?)\s*[-=#]+\s*$/.exec(
+ tokenCursor.currentValue());
+
+ var label = "" + sectionHeadMatch[1];
+ if (label.length == 0)
+ label = "(Untitled)";
+ if (label.length > 50)
+ label = label.substring(0, 50) + "...";
+
+ this.$scopes.onSectionHead(label, tokenCursor.currentPosition());
+ }
+ else if (/\bcodebegin\b/.test(tokenType))
+ {
+ var chunkStartPos = tokenCursor.currentPosition();
+ var chunkPos = {row: chunkStartPos.row + 1, column: 0};
+ var chunkNum = this.$scopes.getTopLevelScopeCount()+1;
+ var chunkLabel = getChunkLabel(this.$codeBeginPattern,
+ tokenCursor.currentValue());
+ var scopeName = "Chunk " + chunkNum;
+ if (chunkLabel)
+ scopeName += ": " + chunkLabel;
+ this.$scopes.onChunkStart(chunkLabel,
+ scopeName,
+ chunkStartPos,
+ chunkPos);
+ }
+ else if (/\bcodeend\b/.test(tokenType))
+ {
+ var pos = tokenCursor.currentPosition();
+ // Close any open functions
+ while (this.$scopes.onScopeEnd(pos))
+ {
+ }
+
+ pos.column += tokenCursor.currentValue().length;
+ this.$scopes.onChunkEnd(pos);
+ }
+ else if (tokenCursor.currentValue() === "{")
+ {
+ var localCursor = tokenCursor.cloneCursor();
+ var startPos;
+ if (findAssocFuncToken(localCursor))
+ {
+ startPos = localCursor.currentPosition();
+ if (localCursor.isFirstSignificantTokenOnLine())
+ startPos.column = 0;
+ this.$scopes.onFunctionScopeStart(localCursor.currentValue(),
+ startPos,
+ tokenCursor.currentPosition());
+ }
+ else
+ {
+ startPos = tokenCursor.currentPosition();
+ if (tokenCursor.isFirstSignificantTokenOnLine())
+ startPos.column = 0;
+ this.$scopes.onScopeStart(startPos);
+ }
+ }
+ else if (tokenCursor.currentValue() === "}")
+ {
+ var pos = tokenCursor.currentPosition();
+ if (tokenCursor.isLastSignificantTokenOnLine())
+ {
+ pos.column = this.$getLine(pos.row).length + 1;
+ }
+ else
+ {
+ pos.column++;
+ }
+ this.$scopes.onScopeEnd(pos);
+ }
+ } while (tokenCursor.moveToNextToken(maxRow));
+ };
+
+ this.$getFoldToken = function(session, foldStyle, row) {
+ this.$tokenizeUpToRow(row);
+
+ if (this.$statePattern && !this.$statePattern.test(this.$endStates[row]))
+ return "";
+
+ var rowTokens = this.$tokens[row];
+
+ if (rowTokens.length == 1 && /\bsectionhead\b/.test(rowTokens[0].type))
+ return rowTokens[0];
+
+ var depth = 0;
+ var unmatchedOpen = null;
+ var unmatchedClose = null;
+
+ for (var i = 0; i < rowTokens.length; i++) {
+ var token = rowTokens[i];
+ if (/\bparen\b/.test(token.type)) {
+ switch (token.value) {
+ case '{':
+ depth++;
+ if (depth == 1) {
+ unmatchedOpen = token;
+ }
+ break;
+ case '}':
+ depth--;
+ if (depth == 0) {
+ unmatchedOpen = null;
+ }
+ if (depth < 0) {
+ unmatchedClose = token;
+ depth = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ if (unmatchedOpen)
+ return unmatchedOpen;
+
+ if (foldStyle == "markbeginend" && unmatchedClose)
+ return unmatchedClose;
+
+ if (rowTokens.length >= 1) {
+ if (/\bcodebegin\b/.test(rowTokens[0].type))
+ return rowTokens[0];
+ else if (/\bcodeend\b/.test(rowTokens[0].type))
+ return rowTokens[0];
+ }
+
+ return null;
+ };
+
+ this.getFoldWidget = function(session, foldStyle, row) {
+ var foldToken = this.$getFoldToken(session, foldStyle, row);
+ if (foldToken == null)
+ return "";
+ if (foldToken.value == '{')
+ return "start";
+ else if (foldToken.value == '}')
+ return "end";
+ else if (/\bcodebegin\b/.test(foldToken.type))
+ return "start";
+ else if (/\bcodeend\b/.test(foldToken.type))
+ return "end";
+ else if (/\bsectionhead\b/.test(foldToken.type))
+ return "start";
+
+ return "";
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var foldToken = this.$getFoldToken(session, foldStyle, row);
+ if (!foldToken)
+ return;
+
+ var pos = {row: row, column: foldToken.column + 1};
+
+ if (foldToken.value == '{') {
+ var end = session.$findClosingBracket(foldToken.value, pos);
+ if (!end)
+ return;
+ return Range.fromPoints(pos, end);
+ }
+ else if (foldToken.value == '}') {
+ var start = session.$findOpeningBracket(foldToken.value, pos);
+ if (!start)
+ return;
+ return Range.fromPoints({row: start.row, column: start.column+1},
+ {row: pos.row, column: pos.column-1});
+ }
+ else if (/\bcodebegin\b/.test(foldToken.type)) {
+ // Find next codebegin or codeend
+ var tokenIterator = new TokenIterator(session, row, 0);
+ for (var tok; tok = tokenIterator.stepForward(); ) {
+ if (/\bcode(begin|end)\b/.test(tok.type)) {
+ var begin = /\bcodebegin\b/.test(tok.type);
+ var tokRow = tokenIterator.getCurrentTokenRow();
+ var endPos = begin
+ ? {row: tokRow-1, column: session.getLine(tokRow-1).length}
+ : {row: tokRow, column: session.getLine(tokRow).length};
+ return Range.fromPoints(
+ {row: row, column: foldToken.column + foldToken.value.length},
+ endPos);
+ }
+ }
+ return;
+ }
+ else if (/\bcodeend\b/.test(foldToken.type)) {
+ var tokenIterator2 = new TokenIterator(session, row, 0);
+ for (var tok2; tok2 = tokenIterator2.stepBackward(); ) {
+ if (/\bcodebegin\b/.test(tok2.type)) {
+ var tokRow2 = tokenIterator2.getCurrentTokenRow();
+ return Range.fromPoints(
+ {row: tokRow2, column: session.getLine(tokRow2).length},
+ {row: row, column: session.getLine(row).length});
+ }
+ }
+ return;
+ }
+ else if (/\bsectionhead\b/.test(foldToken.type)) {
+ var match = /([-=#])\1+\s*$/.exec(foldToken.value);
+ if (!match)
+ return; // this would be surprising
+
+ pos.column += match.index - 1; // Not actually sure why -1 is needed
+ var tokenIterator3 = new TokenIterator(session, row, 0);
+ var lastRow = row;
+ for (var tok3; tok3 = tokenIterator3.stepForward(); ) {
+ if (/\bsectionhead\b/.test(tok3.type)) {
+ break;
+ }
+ lastRow = tokenIterator3.getCurrentTokenRow();
+ }
+
+ return Range.fromPoints(
+ pos,
+ {row: lastRow, column: session.getLine(lastRow).length});
+ }
+
+ return;
+ };
+
+ this.getCurrentScope = function(position, filter)
+ {
+ if (!filter)
+ filter = function(scope) { return true; }
+
+ if (!position)
+ return "";
+ this.$buildScopeTreeUpToRow(position.row);
+
+ var scopePath = this.$scopes.getActiveScopes(position);
+ if (scopePath)
+ {
+ for (var i = scopePath.length-1; i >= 0; i--) {
+ if (filter(scopePath[i]))
+ return scopePath[i];
+ }
+ }
+
+ return null;
+ };
+
+ this.getScopeTree = function()
+ {
+ this.$buildScopeTreeUpToRow(this.$doc.getLength() - 1);
+ return this.$scopes.getScopeList();
+ };
+
+ this.findFunctionDefinitionFromUsage = function(usagePos, functionName)
+ {
+ this.$buildScopeTreeUpToRow(this.$doc.getLength() - 1);
+ return this.$scopes.findFunctionDefinitionFromUsage(usagePos,
+ functionName);
+ };
+
+ this.getIndentForOpenBrace = function(pos)
+ {
+ if (this.$tokenizeUpToRow(pos.row))
+ {
+ var tokenCursor = new this.$TokenCursor();
+ if (tokenCursor.seekToNearestToken(pos, pos.row)
+ && tokenCursor.currentValue() == "{"
+ && tokenCursor.moveBackwardOverMatchingParens())
+ {
+ return this.$getIndent(this.$getLine(tokenCursor.currentPosition().row));
+ }
+ }
+
+ return this.$getIndent(this.$getLine(pos.row));
+ };
+
+ this.getNextLineIndent = function(lastRow, line, endState, tab, tabSize)
+ {
+ if (endState == "qstring" || endState == "qqstring")
+ return "";
+
+ // TODO: optimize
+ var tabAsSpaces = Array(tabSize + 1).join(" ");
+
+ // This lineOverrides nonsense is necessary because the line has not
+ // changed in the real document yet. We need to simulate it by replacing
+ // the real line with the `line` param, and when we finish with this
+ // method, undo the damage and invalidate the row.
+ // To repro the problem without using lineOverrides, comment out this
+ // block of code, and in the editor hit Enter in the middle of a line
+ // that contains a }.
+ this.$lineOverrides = null;
+ if (!(this.$doc.getLine(lastRow) === line))
+ {
+ this.$lineOverrides = {};
+ this.$lineOverrides[lastRow] = line;
+ this.$invalidateRow(lastRow);
+ }
+
+ try
+ {
+ var defaultIndent = lastRow < 0 ? ""
+ : this.$getIndent(this.$getLine(lastRow));
+
+ // jcheng 12/7/2013: It doesn't look to me like $tokenizeUpToRow can return
+ // anything but true, at least not today.
+ if (!this.$tokenizeUpToRow(lastRow))
+ return defaultIndent;
+
+ // If we're in an Sweave/Rmd/etc. document and this line isn't R, then
+ // don't auto-indent
+ if (this.$statePattern && !this.$statePattern.test(endState))
+ return defaultIndent;
+
+ // Used to add extra whitspace if the next line is a continuation of the
+ // previous line (i.e. the last significant token is a binary operator).
+ var continuationIndent = "";
+
+ // The significant token (no whitespace, comments) that most immediately
+ // precedes this line. We don't look back further than 10 rows or so for
+ // performance reasons.
+ var prevToken = this.$findPreviousSignificantToken({row: lastRow, column: this.$getLine(lastRow).length},
+ lastRow - 10);
+
+ if (prevToken
+ && /\bparen\b/.test(prevToken.token.type)
+ && /\)$/.test(prevToken.token.value))
+ {
+ // The previous token was a close-paren ")". Check if this is an
+ // if/while/for/function without braces, in which case we need to
+ // take the indentation of the keyword and indent by one level.
+ //
+ // Example:
+ // if (identical(foo, 1) &&
+ // isTRUE(bar) &&
+ // (!is.null(baz) && !is.na(baz)))
+ // |
+ var openParenPos = this.$walkParensBalanced(
+ prevToken.row,
+ prevToken.row - 10,
+ null,
+ function(parens, paren, pos)
+ {
+ return parens.length === 0;
+ });
+
+ if (openParenPos != null)
+ {
+ var preParenToken = this.$findPreviousSignificantToken(openParenPos, 0);
+ if (preParenToken && preParenToken.token.type === "keyword"
+ && /^(if|while|for|function)$/.test(preParenToken.token.value))
+ {
+ return this.$getIndent(this.$getLine(preParenToken.row)) + tab;
+ }
+ }
+ }
+ else if (prevToken
+ && prevToken.token.type === "keyword"
+ && (prevToken.token.value === "repeat" || prevToken.token.value === "else"))
+ {
+ // Check if this is a "repeat" or (more commonly) "else" without
+ // braces, in which case we need to take the indent of the else/repeat
+ // and increase by one level.
+ return this.$getIndent(this.$getLine(prevToken.row)) + tab;
+ }
+ else if (prevToken && /\boperator\b/.test(prevToken.token.type) && !/\bparen\b/.test(prevToken.token.type))
+ {
+ // Fix issue 2579: If the previous significant token is an operator
+ // (commonly, "+" when used with ggplot) then this line is a
+ // continuation of an expression that was started on a previous
+ // line. This line's indent should then be whatever would normally
+ // be used for a complete statement starting here, plus a tab.
+ continuationIndent = tab;
+ }
+
+ // Walk backwards looking for an open paren, square bracket, or curly
+ // brace, *ignoring matched pairs along the way*. (That's the "balanced"
+ // in $walkParensBalanced.)
+ var openBracePos = this.$walkParensBalanced(
+ lastRow,
+ 0,
+ function(parens, paren, pos)
+ {
+ return /[\[({]/.test(paren) && parens.length === 0;
+ },
+ null);
+
+ if (openBracePos != null)
+ {
+ // OK, we found an open brace; this just means we're not a
+ // top-level expression.
+
+ var nextTokenPos = null;
+
+ if ($verticallyAlignFunctionArgs) {
+ // If the user has selected verticallyAlignFunctionArgs mode in the
+ // prefs, for example:
+ //
+ // soDomethingAwesome(a = 1,
+ // b = 2,
+ // c = 3)
+ //
+ // Then we simply follow the example of the next significant
+ // token. BTW implies that this mode also supports this:
+ //
+ // soDomethingAwesome(
+ // a = 1,
+ // b = 2,
+ // c = 3)
+ //
+ // But not this:
+ //
+ // soDomethingAwesome(a = 1,
+ // b = 2,
+ // c = 3)
+ nextTokenPos = this.$findNextSignificantToken(
+ {
+ row: openBracePos.row,
+ column: openBracePos.column + 1
+ }, lastRow);
+ }
+
+ if (!nextTokenPos)
+ {
+ // Either there wasn't a significant token between the new
+ // line and the previous open brace, or, we're not in
+ // vertical argument alignment mode. Either way, we need
+ // to just indent one level from the open brace's level.
+ return this.getIndentForOpenBrace(openBracePos) +
+ tab + continuationIndent;
+ }
+ else
+ {
+ // Return indent up to next token position.
+ // Note that in hard tab mode, the tab character only counts
+ // as a single character unfortunately. What we really want
+ // is the screen column, but what we have is the document
+ // column, which we can't convert to screen column without
+ // copy-and-pasting a bunch of code from layer/text.js.
+ // As a shortcut, we just pull off the leading whitespace
+ // from the line and include it verbatim in the new indent.
+ // This strategy works fine unless there is a tab in the
+ // line that comes after a non-whitespace character, which
+ // seems like it should be rare.
+ var line = this.$getLine(nextTokenPos.row);
+ var leadingIndent = line.replace(/[^\s].*$/, '');
+
+ var indentWidth = nextTokenPos.column - leadingIndent.length;
+ var tabsToUse = Math.floor(indentWidth / tabSize);
+ var spacesToAdd = indentWidth - (tabSize * tabsToUse);
+ var buffer = "";
+ for (var i = 0; i < tabsToUse; i++)
+ buffer += tab;
+ for (var j = 0; j < spacesToAdd; j++)
+ buffer += " ";
+ var result = leadingIndent + buffer;
+
+ // Compute the size of the indent in spaces (e.g. if a tab
+ // is 4 spaces, and result is "\t\t ", the size is 9)
+ var resultSize = result.replace("\t", tabAsSpaces).length;
+
+ // Sometimes even though verticallyAlignFunctionArgs is used,
+ // the user chooses to manually "break the rules" and use the
+ // non-aligned style, like so:
+ //
+ // plot(foo,
+ // bar, baz,
+ //
+ // Without the below loop, hitting Enter after "baz," causes
+ // the cursor to end up aligned with foo. The loop simply
+ // replaces the indentation with the minimal indentation.
+ //
+ // TODO: Perhaps we can skip the above few lines of code if
+ // there are other lines present
+ var thisIndent;
+ for (var i = nextTokenPos.row + 1; i <= lastRow; i++) {
+ // If a line contains only whitespace, it doesn't count
+ if (!/[^\s]/.test(this.$getLine(i)))
+ continue;
+ // If this line is is a continuation of a multi-line string,
+ // ignore it.
+ var rowEndState = this.$endStates[i-1];
+ if (rowEndState === "qstring" || rowEndState === "qqstring")
+ continue;
+ thisIndent = this.$getLine(i).replace(/[^\s].*$/, '');
+ thisIndentSize = thisIndent.replace("\t", tabAsSpaces).length;
+ if (thisIndentSize < resultSize) {
+ result = thisIndent;
+ resultSize = thisIndentSize;
+ }
+ }
+
+ return result + continuationIndent;
+ }
+ }
+
+ var firstToken = this.$findNextSignificantToken({row: 0, column: 0}, lastRow);
+ if (firstToken)
+ return this.$getIndent(this.$getLine(firstToken.row)) + continuationIndent;
+ else
+ return "" + continuationIndent;
+ }
+ finally
+ {
+ if (this.$lineOverrides)
+ {
+ this.$lineOverrides = null;
+ this.$invalidateRow(lastRow);
+ }
+ }
+ };
+
+ this.getBraceIndent = function(lastRow)
+ {
+ this.$tokenizeUpToRow(lastRow);
+
+ var prevToken = this.$findPreviousSignificantToken({row: lastRow, column: this.$getLine(lastRow).length},
+ lastRow - 10);
+ if (prevToken
+ && /\bparen\b/.test(prevToken.token.type)
+ && /\)$/.test(prevToken.token.value))
+ {
+ var lastPos = this.$walkParensBalanced(
+ prevToken.row,
+ prevToken.row - 10,
+ null,
+ function(parens, paren, pos)
+ {
+ return parens.length == 0;
+ });
+
+ if (lastPos != null)
+ {
+ var preParenToken = this.$findPreviousSignificantToken(lastPos, 0);
+ if (preParenToken && preParenToken.token.type === "keyword"
+ && /^(if|while|for|function)$/.test(preParenToken.token.value))
+ {
+ return this.$getIndent(this.$getLine(preParenToken.row));
+ }
+ }
+ }
+ else if (prevToken
+ && prevToken.token.type === "keyword"
+ && (prevToken.token.value === "repeat" || prevToken.token.value === "else"))
+ {
+ return this.$getIndent(this.$getLine(prevToken.row));
+ }
+
+ return this.$getIndent(lastRow);
+ };
+
+ /**
+ * If headInclusive, then a token will match if it starts at pos.
+ * If tailInclusive, then a token will match if it ends at pos (meaning
+ * token.column + token.length == pos.column, and token.row == pos.row
+ * In all cases, a token will match if pos is after the head and before the
+ * tail.
+ *
+ * If no token is found, null is returned.
+ *
+ * Note that whitespace and comment tokens will never be returned.
+ */
+ this.getTokenForPos = function(pos, headInclusive, tailInclusive)
+ {
+ this.$tokenizeUpToRow(pos.row);
+
+ if (this.$tokens.length <= pos.row)
+ return null;
+ var tokens = this.$tokens[pos.row];
+ for (var i = 0; i < tokens.length; i++)
+ {
+ var token = tokens[i];
+
+ if (headInclusive && pos.column == token.column)
+ return token;
+ if (pos.column <= token.column)
+ return null;
+
+ if (tailInclusive && pos.column == token.column + token.value.length)
+ return token;
+ if (pos.column < token.column + token.value.length)
+ return token;
+ }
+ return null;
+ };
+
+ this.$tokenizeUpToRow = function(lastRow)
+ {
+ // Don't let lastRow be past the end of the document
+ lastRow = Math.min(lastRow, this.$endStates.length - 1);
+
+ var row = 0;
+ var assumeGood = true;
+ for ( ; row <= lastRow; row++)
+ {
+ // No need to tokenize rows until we hit one that has been explicitly
+ // invalidated.
+ if (assumeGood && this.$endStates[row])
+ continue;
+
+ assumeGood = false;
+
+ var state = (row === 0) ? 'start' : this.$endStates[row-1];
+ var lineTokens = this.$tokenizer.getLineTokens(this.$getLine(row), state);
+ if (!this.$statePattern || this.$statePattern.test(lineTokens.state) || this.$statePattern.test(state))
+ this.$tokens[row] = this.$filterWhitespaceAndComments(lineTokens.tokens);
+ else
+ this.$tokens[row] = [];
+
+ // If we ended in the same state that the cache says, then we know that
+ // the cache is up-to-date for the subsequent lines--UNTIL we hit a row
+ // that has been explicitly invalidated.
+ if (lineTokens.state === this.$endStates[row])
+ assumeGood = true;
+ else
+ this.$endStates[row] = lineTokens.state;
+ }
+
+ if (!assumeGood)
+ {
+ // If we get here, it means the last row we saw before we exited
+ // was invalidated or impacted by an invalidated row. We need to
+ // make sure the NEXT row doesn't get ignored next time the tokenizer
+ // makes a pass.
+ if (row < this.$tokens.length)
+ this.$invalidateRow(row);
+ }
+
+ return true;
+ };
+
+ this.$onDocChange = function(evt)
+ {
+ var delta = evt.data;
+
+ if (delta.action === "insertLines")
+ {
+ this.$insertNewRows(delta.range.start.row,
+ delta.range.end.row - delta.range.start.row);
+ }
+ else if (delta.action === "insertText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$invalidateRow(delta.range.start.row);
+ this.$insertNewRows(delta.range.end.row, 1);
+ }
+ else
+ {
+ this.$invalidateRow(delta.range.start.row);
+ }
+ }
+ else if (delta.action === "removeLines")
+ {
+ this.$removeRows(delta.range.start.row,
+ delta.range.end.row - delta.range.start.row);
+ this.$invalidateRow(delta.range.start.row);
+ }
+ else if (delta.action === "removeText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$removeRows(delta.range.end.row, 1);
+ this.$invalidateRow(delta.range.start.row);
+ }
+ else
+ {
+ this.$invalidateRow(delta.range.start.row);
+ }
+ }
+
+ this.$scopes.invalidateFrom(delta.range.start);
+ };
+
+ this.$invalidateRow = function(row)
+ {
+ this.$tokens[row] = null;
+ this.$endStates[row] = null;
+ };
+
+ this.$insertNewRows = function(row, count)
+ {
+ var args = [row, 0];
+ for (var i = 0; i < count; i++)
+ args.push(null);
+ this.$tokens.splice.apply(this.$tokens, args);
+ this.$endStates.splice.apply(this.$endStates, args);
+ };
+
+ this.$removeRows = function(row, count)
+ {
+ this.$tokens.splice(row, count);
+ this.$endStates.splice(row, count);
+ };
+
+ this.$getIndent = function(line)
+ {
+ var match = /^([ \t]*)/.exec(line);
+ if (!match)
+ return ""; // should never happen, but whatever
+ else
+ return match[1];
+ };
+
+ this.$getLine = function(row)
+ {
+ if (this.$lineOverrides && typeof(this.$lineOverrides[row]) != 'undefined')
+ return this.$lineOverrides[row];
+ return this.$doc.getLine(row);
+ };
+
+ this.$walkParens = function(startRow, endRow, fun)
+ {
+ var parenRe = /\bparen\b/;
+
+ if (startRow < endRow) // forward
+ {
+ return (function() {
+ for ( ; startRow <= endRow; startRow++)
+ {
+ var tokens = this.$tokens[startRow];
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (parenRe.test(tokens[i].type))
+ {
+ var value = tokens[i].value;
+ if (!fun(value, {row: startRow, column: tokens[i].column}))
+ return false;
+ }
+ }
+ }
+ return true;
+ }).call(this);
+ }
+ else // backward
+ {
+ return (function() {
+ startRow = Math.max(0, startRow);
+ endRow = Math.max(0, endRow);
+
+ for ( ; startRow >= endRow; startRow--)
+ {
+ var tokens = this.$tokens[startRow];
+ for (var i = tokens.length - 1; i >= 0; i--)
+ {
+ if (parenRe.test(tokens[i].type))
+ {
+ var value = tokens[i].value;
+ if (!fun(value, {row: startRow, column: tokens[i].column}))
+ return false;
+ }
+ }
+ }
+ return true;
+ }).call(this);
+ }
+ };
+
+ // Walks BACKWARD over matched pairs of parens. Stop and return result
+ // when optional function params preMatch or postMatch return true.
+ // preMatch is called when a paren is encountered and BEFORE the parens
+ // stack is modified. postMatch is called after the parens stack is modified.
+ this.$walkParensBalanced = function(startRow, endRow, preMatch, postMatch)
+ {
+ // The current stack of parens that are in effect.
+ var parens = [];
+ var result = null;
+ var that = this;
+ this.$walkParens(startRow, endRow, function(paren, pos)
+ {
+ if (preMatch && preMatch(parens, paren, pos))
+ {
+ result = pos;
+ return false;
+ }
+
+ if (/[\[({]/.test(paren))
+ {
+ if (parens[parens.length - 1] === that.$complements[paren])
+ parens.pop();
+ else
+ return true;
+ }
+ else
+ {
+ parens.push(paren);
+ }
+
+ if (postMatch && postMatch(parens, paren, pos))
+ {
+ result = pos;
+ return false;
+ }
+
+ return true;
+ });
+
+ return result;
+ };
+
+ this.$findNextSignificantToken = function(pos, lastRow)
+ {
+ if (this.$tokens.length == 0)
+ return null;
+ lastRow = Math.min(lastRow, this.$tokens.length - 1);
+
+ var row = pos.row;
+ var col = pos.column;
+ for ( ; row <= lastRow; row++)
+ {
+ var tokens = this.$tokens[row];
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (tokens[i].column + tokens[i].value.length > col)
+ {
+ return {
+ token: tokens[i],
+ row: row,
+ column: Math.max(tokens[i].column, col),
+ offset: i
+ };
+ }
+ }
+
+ col = 0; // After the first row, we'll settle for a token anywhere
+ }
+ return null;
+ };
+
+ this.findNextSignificantToken = function(pos)
+ {
+ return this.$findNextSignificantToken(pos, this.$tokens.length - 1);
+ }
+
+ this.$findPreviousSignificantToken = function(pos, firstRow)
+ {
+ if (this.$tokens.length == 0)
+ return null;
+ firstRow = Math.max(0, firstRow);
+
+ var row = Math.min(pos.row, this.$tokens.length - 1);
+ for ( ; row >= firstRow; row--)
+ {
+ var tokens = this.$tokens[row];
+ if (tokens.length == 0)
+ continue;
+
+ if (row != pos.row)
+ return {
+ row: row,
+ column: tokens[tokens.length - 1].column,
+ token: tokens[tokens.length - 1],
+ offset: tokens.length - 1
+ };
+
+ for (var i = tokens.length - 1; i >= 0; i--)
+ {
+ if (tokens[i].column < pos.column)
+ {
+ return {
+ row: row,
+ column: tokens[i].column,
+ token: tokens[i],
+ offset: i
+ };
+ }
+ }
+ }
+ };
+
+ function isWhitespaceOrComment(token)
+ {
+ // virtual-comment is for roxygen content that needs to be highlighted
+ // as TeX, but for the purposes of the code model should be invisible.
+
+ if (/\bcode(?:begin|end)\b/.test(token.type))
+ return false;
+
+ if (/\bsectionhead\b/.test(token.type))
+ return false;
+
+ return /^\s*$/.test(token.value) ||
+ token.type.match(/\b(?:ace_virtual-)?comment\b/);
+ }
+
+ this.$filterWhitespaceAndComments = function(tokens)
+ {
+ tokens = tokens.filter(function (t) {
+ return !isWhitespaceOrComment(t);
+ });
+
+ for (var i = tokens.length - 1; i >= 0; i--)
+ {
+ if (tokens[i].value.length > 1 && /\bparen\b/.test(tokens[i].type))
+ {
+ var token = tokens[i];
+ tokens.splice(i, 1);
+ for (var j = token.value.length - 1; j >= 0; j--)
+ {
+ var newToken = {
+ type: token.type,
+ value: token.value.charAt(j),
+ column: token.column + j
+ };
+ tokens.splice(i, 0, newToken);
+ }
+ }
+ }
+ return tokens;
+ };
+
+}).call(RCodeModel.prototype);
+
+exports.RCodeModel = RCodeModel;
+
+exports.setVerticallyAlignFunctionArgs = function(verticallyAlign) {
+ $verticallyAlignFunctionArgs = verticallyAlign;
+};
+
+
+});
diff --git a/src/gwt/acesupport/acemode/r_highlight_rules.js b/src/gwt/acesupport/acemode/r_highlight_rules.js
new file mode 100644
index 0000000..548117d
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r_highlight_rules.js
@@ -0,0 +1,188 @@
+/*
+ * r_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/r_highlight_rules", function(require, exports, module)
+{
+
+ var oop = require("ace/lib/oop");
+ var lang = require("ace/lib/lang");
+ var TextHighlightRules = require("ace/mode/text_highlight_rules")
+ .TextHighlightRules;
+ var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
+
+ var RHighlightRules = function()
+ {
+
+ var keywords = lang.arrayToMap(
+ ("function|if|in|break|next|repeat|else|for|return|switch|while|try|tryCatch|stop|warning|require|library|attach|detach|source|setMethod|setGeneric|setGroupGeneric|setClass|setRefClass")
+ .split("|")
+ );
+
+ var buildinConstants = lang.arrayToMap(
+ ("NULL|NA|TRUE|FALSE|T|F|Inf|NaN|NA_integer_|NA_real_|NA_character_|" +
+ "NA_complex_").split("|")
+ );
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ // Roxygen
+ token : "comment.sectionhead",
+ regex : "#+(?!').*(?:----|====|####)\\s*$"
+ },
+ {
+ // Roxygen
+ token : "comment",
+ regex : "#+'",
+ next : "rd-start"
+ },
+ {
+ token : "comment",
+ regex : "#.*$"
+ },
+ {
+ token : "string", // multi line string start
+ regex : '["]',
+ next : "qqstring"
+ },
+ {
+ token : "string", // multi line string start
+ regex : "[']",
+ next : "qstring"
+ },
+ {
+ token : "constant.numeric", // hex
+ regex : "0[xX][0-9a-fA-F]+[Li]?\\b"
+ },
+ {
+ token : "constant.numeric", // number + integer
+ regex : "\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?[iL]?\\b"
+ },
+ {
+ token : "constant.numeric", // number + integer with leading decimal
+ regex : "\\.\\d+(?:[eE][+\\-]?\\d*)?[iL]?\\b"
+ },
+ {
+ token : "constant.language.boolean",
+ regex : "(?:TRUE|FALSE|T|F)\\b"
+ },
+ {
+ token : "identifier",
+ regex : "`.*?`"
+ },
+ {
+ token : function(value)
+ {
+ if (keywords[value])
+ return "keyword";
+ else if (buildinConstants[value])
+ return "constant.language";
+ else if (value == '...' || value.match(/^\.\.\d+$/))
+ return "variable.language";
+ else
+ return "identifier";
+ },
+ regex : "[a-zA-Z.][a-zA-Z0-9._]*\\b"
+ },
+ {
+ token : "keyword.operator",
+ regex : "%%|>=|<=|==|!=|\\->|<\\-|\\|\\||&&|=|\\+|\\-|\\*|/|\\^|>|<|!|&|\\||~|\\$|:"
+ },
+ {
+ token : "keyword.operator", // infix operators
+ regex : "%.*?%"
+ },
+ {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[[({]"
+ },
+ {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[\\])}]"
+ },
+ {
+ token : "text",
+ regex : "\\s+"
+ }
+ ],
+ "qqstring" : [
+ {
+ token : "string",
+ regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
+ next : "start"
+ },
+ {
+ token : "string",
+ regex : '.+'
+ }
+ ],
+ "qstring" : [
+ {
+ token : "string",
+ regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
+ next : "start"
+ },
+ {
+ token : "string",
+ regex : '.+'
+ }
+ ]
+ };
+
+ var rdRules = new TexHighlightRules("comment").getRules();
+
+ // Make all embedded TeX virtual-comment so they don't interfere with
+ // auto-indent.
+ for (var i = 0; i < rdRules["start"].length; i++) {
+ rdRules["start"][i].token += ".virtual-comment";
+ }
+
+ this.addRules(rdRules, "rd-");
+ this.$rules["rd-start"].unshift({
+ token: "text",
+ regex: "^",
+ next: "start"
+ });
+ this.$rules["rd-start"].unshift({
+ token : "keyword",
+ regex : "@(?!@)[^ ]*"
+ });
+ this.$rules["rd-start"].unshift({
+ token : "comment",
+ regex : "@@"
+ });
+ this.$rules["rd-start"].push({
+ token : "comment",
+ regex : "[^%\\\\[({\\])}]+"
+ });
+ };
+
+ oop.inherits(RHighlightRules, TextHighlightRules);
+
+ exports.RHighlightRules = RHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/r_matching_brace_outdent.js b/src/gwt/acesupport/acemode/r_matching_brace_outdent.js
new file mode 100644
index 0000000..c2fd66d
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r_matching_brace_outdent.js
@@ -0,0 +1,82 @@
+/*
+ * r_matching_brace_outdent.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/r_matching_brace_outdent", function(require, exports, module)
+{
+ var Range = require("ace/range").Range;
+
+ // This is a mixin that enables proper brace outdent behavior for R code.
+
+ var RMatchingBraceOutdent = {};
+
+ (function()
+ {
+ this.checkOutdent = function(state, line, input) {
+ if (/^\s+$/.test(line) && /^\s*[\{\}\)]/.test(input))
+ return true;
+
+ // This is the case of a newline being inserted on a line that only
+ // contains }
+ if (/^\s*}\s*$/.test(line) && input == "\n")
+ return true;
+
+ // This is the case of a newline being inserted on a line that contains
+ // a bunch of stuff including }, and the user hits Enter. The input
+ // is not necessarily "\n" because we may auto-insert some padding
+ // as well.
+ //
+ // We don't always want to autoindent in this case; ideally we would
+ // only autoindent if Enter was being hit right before }. But at this
+ // time we don't have that information. So we let the autoOutdent logic
+ // run and trust it to only outdent if appropriate.
+ if (/}\s*$/.test(line) && /\n/.test(input))
+ return true;
+
+ return false;
+ };
+
+ this.autoOutdent = function(state, session, row) {
+ if (row == 0)
+ return 0;
+
+ var line = session.getLine(row);
+
+ var match = line.match(/^(\s*[\}\)])/);
+ if (match)
+ {
+ var column = match[1].length;
+ var openBracePos = session.findMatchingBracket({row: row, column: column});
+
+ if (!openBracePos || openBracePos.row == row) return 0;
+
+ var indent = this.codeModel.getIndentForOpenBrace(openBracePos);
+ session.replace(new Range(row, 0, row, column-1), indent);
+ }
+
+ match = line.match(/^(\s*\{)/);
+ if (match)
+ {
+ var column = match[1].length;
+ var indent = this.codeModel.getBraceIndent(row-1);
+ session.replace(new Range(row, 0, row, column-1), indent);
+ }
+ };
+ }).call(RMatchingBraceOutdent);
+ exports.RMatchingBraceOutdent = RMatchingBraceOutdent;
+});
diff --git a/src/gwt/acesupport/acemode/r_scope_tree.js b/src/gwt/acesupport/acemode/r_scope_tree.js
new file mode 100644
index 0000000..d8185bc
--- /dev/null
+++ b/src/gwt/acesupport/acemode/r_scope_tree.js
@@ -0,0 +1,395 @@
+/*
+ * r_scope_tree.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define('mode/r_scope_tree', function(require, exports, module) {
+
+ function debuglog(str) {
+ //console.log(str);
+ }
+
+ function assert(condition, label) {
+ if (!condition)
+ window.alert("[ASSERTION FAILED] " + label);
+ }
+
+ function comparePoints(pos1, pos2) {
+ if (pos1.row != pos2.row)
+ return pos1.row - pos2.row;
+ return pos1.column - pos2.column;
+ }
+
+
+ var ScopeManager = function() {
+ this.parsePos = {row: 0, column: 0};
+ this.$root = new ScopeNode("(Top Level)", this.parsePos, null,
+ ScopeNode.TYPE_ROOT);
+ };
+
+ (function() {
+
+ this.onSectionHead = function(sectionLabel, sectionPos) {
+ var existingScopes = this.getActiveScopes(sectionPos);
+ if (existingScopes.length == 2 && existingScopes[1].isSection()) {
+ this.$root.closeScope(sectionPos, ScopeNode.TYPE_SECTION);
+ }
+ else if (existingScopes.length != 1)
+ return;
+
+ this.$root.addNode(new ScopeNode(sectionLabel, sectionPos, sectionPos,
+ ScopeNode.TYPE_SECTION));
+ };
+
+ this.onChunkStart = function(chunkLabel, label, chunkStartPos, chunkPos) {
+ // Starting a chunk means closing the previous chunk, if any
+ var prev = this.$root.closeScope(chunkStartPos, ScopeNode.TYPE_CHUNK);
+ if (prev)
+ debuglog("chunk-scope implicit end: " + prev.label);
+
+ debuglog("adding chunk-scope " + label);
+ var node = new ScopeNode(label, chunkPos, chunkStartPos,
+ ScopeNode.TYPE_CHUNK);
+ node.chunkLabel = chunkLabel;
+ this.$root.addNode(node);
+ this.printScopeTree();
+ };
+
+ this.onChunkEnd = function(pos) {
+ var closed = this.$root.closeScope(pos, ScopeNode.TYPE_CHUNK);
+ if (closed)
+ debuglog("chunk-scope end: " + closed.label);
+ else
+ debuglog("extra chunk-scope end");
+ this.printScopeTree();
+ return closed;
+ };
+
+ this.onFunctionScopeStart = function(label, functionStartPos, scopePos) {
+ debuglog("adding function brace-scope " + label);
+ this.$root.addNode(new ScopeNode(label, scopePos, functionStartPos,
+ ScopeNode.TYPE_BRACE));
+ this.printScopeTree();
+ };
+
+ this.onScopeStart = function(pos) {
+ debuglog("adding anon brace-scope");
+ this.$root.addNode(new ScopeNode(null, pos, null,
+ ScopeNode.TYPE_BRACE));
+ this.printScopeTree();
+ };
+
+ this.onScopeEnd = function(pos) {
+ var closed = this.$root.closeScope(pos, ScopeNode.TYPE_BRACE);
+ if (closed)
+ debuglog("brace-scope end: " + closed.label);
+ else
+ debuglog("extra brace-scope end");
+ this.printScopeTree();
+ return closed;
+ };
+
+ this.getActiveScopes = function(pos) {
+ return this.$root.findNode(pos);
+ };
+
+ this.getScopeList = function() {
+ return this.$root.$children;
+ };
+
+ this.findFunctionDefinitionFromUsage = function(usagePos, functionName) {
+ return this.$root.findFunctionDefinitionFromUsage(usagePos,
+ functionName);
+ };
+
+ this.invalidateFrom = function(pos) {
+ pos = {row: Math.max(0, pos.row-1), column: 0};
+ debuglog("Invalidate from " + pos.row + ", " + pos.column);
+ if (comparePoints(this.parsePos, pos) > 0)
+ this.parsePos = this.$root.invalidateFrom(pos);
+ this.printScopeTree();
+ };
+
+ this.getTopLevelScopeCount = function() {
+ return this.$root.$children.length;
+ };
+
+ this.printScopeTree = function() {
+ this.$root.printDebug();
+ };
+
+ }).call(ScopeManager.prototype);
+
+
+
+ var ScopeNode = function(label, start, preamble, scopeType) {
+ this.label = label;
+
+ // The position of the open brace
+ this.start = start;
+
+ // The position of the start of the function declaration (possibly
+ // with added whitespace)
+ this.preamble = preamble || start;
+
+ // The position of the close brance (possibly with added whitespace)
+ this.end = null;
+
+ // Whether this scope is
+ this.scopeType = scopeType;
+
+ // A pointer to the parent scope (if any)
+ this.parentScope = null;
+
+ this.$children = [];
+ };
+
+ ScopeNode.TYPE_ROOT = 1; // document root
+ ScopeNode.TYPE_BRACE = 2; // curly brace
+ ScopeNode.TYPE_CHUNK = 3; // Sweave chunk
+ ScopeNode.TYPE_SECTION = 4; // Section header
+
+ (function() {
+
+ this.isRoot = function() { return this.scopeType == ScopeNode.TYPE_ROOT; };
+ this.isBrace = function() { return this.scopeType == ScopeNode.TYPE_BRACE; };
+ this.isChunk = function() { return this.scopeType == ScopeNode.TYPE_CHUNK; };
+ this.isSection = function() { return this.scopeType == ScopeNode.TYPE_SECTION; };
+ this.isFunction = function() {
+ return this.isBrace() && !!this.label;
+ };
+
+ this.addNode = function(node) {
+ assert(!node.end, "New node is already closed");
+ assert(node.$children.length == 0, "New node already had children");
+
+ // It's possible for this node to be already closed. If that's the
+ // case, we need to open it back up. Example:
+ //
+ // foo <- function() { bar <- function [HERE] }) {
+ //
+ // If [HERE] is replaced with (, then the final brace will cause this
+ // situation to be triggered because the previous brace belonged to
+ // foo but no longer does.
+ this.end = null;
+
+ var index = this.$binarySearch(node.preamble);
+ if (index >= 0) {
+ // This node belongs inside an existing child
+ this.$children[index].addNode(node);
+ }
+ else {
+ // This node belongs directly under this scope. It's possible that
+ // it subsumes some existing children under this scope. (We may not
+ // know about a function scope until after we've seen some of its
+ // children, since function scopes don't get created until we see
+ // their opening brace but any argument defaults that are themselves
+ // functions will have been seen already.)
+
+ index = -(index+1);
+
+ if (index < this.$children.length) {
+ node.$children = this.$children.splice(
+ index, this.$children.length - index);
+ }
+ node.parentScope = this;
+ this.$children.push(node);
+ }
+ };
+
+ this.closeScope = function(pos, scopeType) {
+
+ // NB: This function will never close the "this" node. This is by
+ // design as we don't want the top-level node to ever be closed.
+
+ // No children
+ if (this.$children.length == 0)
+ return null;
+
+ var lastNode = this.$children[this.$children.length-1]
+
+ // Last child is already closed
+ if (lastNode.end)
+ return null;
+
+ // Last child had a descendant that needed to be closed and was the
+ // appropriate type
+ var closedChild = lastNode.closeScope(pos, scopeType);
+ if (closedChild)
+ return closedChild;
+
+ // Close last child, if it's of the type we want to close
+ if (scopeType == lastNode.scopeType) {
+ lastNode.end = pos;
+ // If any descendants are still open, force them closed. This could
+ // be the case for e.g. Sweave chunk being closed while it contains
+ // unclosed brace scopes.
+ lastNode.$forceDescendantsClosed(pos);
+ return lastNode;
+ }
+
+ return null;
+ };
+
+ this.$forceDescendantsClosed = function(pos) {
+ if (this.$children.length == 0)
+ return;
+ var lastNode = this.$children[this.$children.length - 1];
+ if (lastNode.end)
+ return;
+ lastNode.$forceDescendantsClosed(pos);
+ lastNode.end = pos;
+ }
+
+ // Returns array of nodes that contain the position, from outermost to
+ // innermost; or null if no nodes contain it.
+ this.findNode = function(pos) {
+ var index = this.$binarySearch(pos);
+ if (index >= 0) {
+ var result = this.$children[index].findNode(pos);
+ if (result) {
+ if (this.label)
+ result.unshift(this);
+ return result;
+ }
+ if (this.label)
+ return [this];
+ return null;
+ }
+ else {
+ return this.label ? [this] : null;
+ }
+ };
+
+ this.$getFunctionStack = function(pos) {
+ var index = this.$binarySearch(pos);
+ var stack = index >= 0 ? this.$children[index].$getFunctionStack(pos)
+ : [];
+ if (this.label) {
+ stack.push(this);
+ }
+ return stack;
+ };
+
+ this.findFunctionDefinitionFromUsage = function(usagePos, functionName) {
+ var functionStack = this.$getFunctionStack(usagePos);
+ for (var i = 0; i < functionStack.length; i++) {
+ var thisLevel = functionStack[i];
+ for (var j = 0; j < thisLevel.$children.length; j++) {
+ // optionally, short-circuit iteration if usagePos comes before
+ // thisLevel.$children[j].preamble (or .start?)
+ if (thisLevel.$children[j].label == functionName)
+ return thisLevel.$children[j];
+ }
+ }
+
+ return null;
+ };
+
+ // Invalidates everything after pos, and possibly some stuff before.
+ // Returns the position from which parsing should resume.
+ this.invalidateFrom = function(pos) {
+
+ var index = this.$binarySearch(pos);
+
+ var resumePos;
+ if (index >= 0)
+ {
+ // One of the child scopes contains this position (i.e. it's between
+ // the preamble and end). Now figure out if the position is between
+ // the child's start and end.
+
+ if (comparePoints(pos, this.$children[index].start) <= 0)
+ {
+ // The position is between the child's preamble and the start.
+ // We need to drop the child entirely and reparse.
+ resumePos = this.$children[index].preamble;
+ }
+ else
+ {
+ // The position is between the child's start and end. We can keep
+ // the scope, just recurse into the child to make sure its
+ // children get invalidated correctly, and its 'end' property
+ // is nulled out.
+ resumePos = this.$children[index].invalidateFrom(pos);
+
+ // Increment index so this child doesn't get removed.
+ index++;
+ }
+ }
+ else
+ {
+ index = -(index+1);
+ resumePos = pos;
+ }
+
+ if (index < this.$children.length)
+ {
+ this.$children.splice(index, this.$children.length - index);
+ }
+
+ this.end = null;
+
+ return resumePos;
+ };
+
+ // Returns index of the child that contains this position, if it exists;
+ // otherwise, -(index + 1) where index is where such a child would be.
+ this.$binarySearch = function(pos, start /*optional*/, end /*optional*/) {
+ if (typeof(start) === 'undefined')
+ start = 0;
+ if (typeof(end) === 'undefined')
+ end = this.$children.length;
+
+ // No elements left to test
+ if (start === end)
+ return -(start + 1);
+
+ var mid = Math.floor((start + end)/2);
+ var comp = this.$children[mid].comparePosition(pos);
+ if (comp === 0)
+ return mid;
+ else if (comp < 0)
+ return this.$binarySearch(pos, start, mid);
+ else // comp > 0
+ return this.$binarySearch(pos, mid + 1, end);
+ };
+
+ this.comparePosition = function(pos)
+ {
+ // TODO
+ if (comparePoints(pos, this.preamble) < 0)
+ return -1;
+ if (this.end != null && comparePoints(pos, this.end) >= 0)
+ return 1;
+ return 0;
+ };
+
+ this.printDebug = function(indent) {
+ if (typeof(indent) === 'undefined')
+ indent = "";
+
+ debuglog(indent + "\"" + this.label + "\" ["
+ + this.preamble.row + "x" + this.preamble.column
+ + (this.start ? ("-" + this.start.row + "x" + this.start.column) : "")
+ + ", "
+ + (this.end ? (this.end.row + "x" + this.end.column) : "null" ) + "]");
+ for (var i = 0; i < this.$children.length; i++)
+ this.$children[i].printDebug(indent + " ");
+ };
+
+ }).call(ScopeNode.prototype);
+
+
+ exports.ScopeManager = ScopeManager;
+
+});
\ No newline at end of file
diff --git a/src/gwt/acesupport/acemode/rdoc.js b/src/gwt/acesupport/acemode/rdoc.js
new file mode 100644
index 0000000..4d35aa7
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rdoc.js
@@ -0,0 +1,40 @@
+/*
+ * rdoc.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/rdoc", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+var RDocHighlightRules = require("mode/rdoc_highlight_rules").RDocHighlightRules;
+var MatchingBraceOutdent = require("ace/mode/matching_brace_outdent").MatchingBraceOutdent;
+
+var Mode = function(suppressHighlighting) {
+ if (suppressHighlighting)
+ this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules());
+ else
+ this.$tokenizer = new Tokenizer(new RDocHighlightRules().getRules());
+ this.$outdent = new MatchingBraceOutdent();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/rdoc_highlight_rules.js b/src/gwt/acesupport/acemode/rdoc_highlight_rules.js
new file mode 100644
index 0000000..5d08296
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rdoc_highlight_rules.js
@@ -0,0 +1,99 @@
+/*
+ * rdoc_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/rdoc_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var lang = require("ace/lib/lang");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var RDocHighlightRules = function() {
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ token : "comment",
+ regex : "%.*$"
+ }, {
+ token : "text", // non-command
+ regex : "\\\\[$&%#\\{\\}]"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:name|alias|method|S3method|S4method|item|code|preformatted|kbd|pkg|var|env|option|command|author|email|url|source|cite|acronym|href|code|preformatted|link|eqn|deqn|keyword|usage|examples|dontrun|dontshow|figure|if|ifelse|Sexpr|RdOpts|inputencoding|usepackage)\\b",
+ next : "nospell"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
+ }, {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[[({]"
+ }, {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[\\])}]"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }
+ ],
+ // This mode is necessary to prevent spell checking, but to keep the
+ // same syntax highlighting behavior.
+ "nospell" : [
+ {
+ token : "comment",
+ regex : "%.*$",
+ next : "start"
+ }, {
+ token : "nospell.text", // non-command
+ regex : "\\\\[$&%#\\{\\}]"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:name|alias|method|S3method|S4method|item|code|preformatted|kbd|pkg|var|env|option|command|author|email|url|source|cite|acronym|href|code|preformatted|link|eqn|deqn|keyword|usage|examples|dontrun|dontshow|figure|if|ifelse|Sexpr|RdOpts|inputencoding|usepackage)\\b"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])",
+ next : "start"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "[[({]"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "[\\])]"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "}",
+ next : "start"
+ }, {
+ token : "nospell.text",
+ regex : "\\s+"
+ }, {
+ token : "nospell.text",
+ regex : "\\w+"
+ }
+ ]
+ };
+};
+
+oop.inherits(RDocHighlightRules, TextHighlightRules);
+
+exports.RDocHighlightRules = RDocHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/rhtml.js b/src/gwt/acesupport/acemode/rhtml.js
new file mode 100644
index 0000000..c0bed07
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rhtml.js
@@ -0,0 +1,64 @@
+/*
+ * rhtml.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/rhtml", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var HtmlMode = require("ace/mode/html").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var RHtmlHighlightRules = require("mode/rhtml_highlight_rules").RHtmlHighlightRules;
+var SweaveBackgroundHighlighter = require("mode/sweave_background_highlighter").SweaveBackgroundHighlighter;
+var RCodeModel = require("mode/r_code_model").RCodeModel;
+
+var Mode = function(suppressHighlighting, doc, session) {
+ this.$session = session;
+ this.$tokenizer = new Tokenizer(new RHtmlHighlightRules().getRules());
+
+ this.codeModel = new RCodeModel(doc, this.$tokenizer, /^r-/,
+ /^<!--\s*begin.rcode\s*(.*)/);
+ this.foldingRules = this.codeModel;
+ this.$sweaveBackgroundHighlighter = new SweaveBackgroundHighlighter(
+ session,
+ /^<!--\s*begin.rcode\s*(?:.*)/,
+ /^\s*end.rcode\s*-->/,
+ true);
+};
+oop.inherits(Mode, HtmlMode);
+
+(function() {
+ this.insertChunkInfo = {
+ value: "<!--begin.rcode\n\nend.rcode-->\n",
+ position: {row: 0, column: 15}
+ };
+
+ this.getLanguageMode = function(position)
+ {
+ return this.$session.getState(position.row).match(/^r-/) ? 'R' : 'HTML';
+ };
+
+ this.getNextLineIndent = function(state, line, tab, tabSize, row)
+ {
+ return this.codeModel.getNextLineIndent(row, line, state, tab, tabSize);
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/rhtml_highlight_rules.js b/src/gwt/acesupport/acemode/rhtml_highlight_rules.js
new file mode 100644
index 0000000..81e5ed3
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rhtml_highlight_rules.js
@@ -0,0 +1,50 @@
+/*
+ * rhtml_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/rhtml_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;
+var HtmlHighlightRules = require("ace/mode/html_highlight_rules").HtmlHighlightRules;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var RHtmlHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = new HtmlHighlightRules().getRules();
+ this.$rules["start"].unshift({
+ token: "support.function.codebegin",
+ regex: "^<" + "!--\\s*begin.rcode\\s*(?:.*)",
+ next: "r-start"
+ });
+
+ var rRules = new RHighlightRules().getRules();
+ this.addRules(rRules, "r-");
+ this.$rules["r-start"].unshift({
+ token: "support.function.codeend",
+ regex: "^\\s*end.rcode\\s*-->",
+ next: "start"
+ });
+};
+oop.inherits(RHtmlHighlightRules, TextHighlightRules);
+
+exports.RHtmlHighlightRules = RHtmlHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/rmarkdown.js b/src/gwt/acesupport/acemode/rmarkdown.js
new file mode 100644
index 0000000..78c065f
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rmarkdown.js
@@ -0,0 +1,180 @@
+/*
+ * markdown.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+define("mode/rmarkdown", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var MarkdownMode = require("mode/markdown").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var RMarkdownHighlightRules = require("mode/rmarkdown_highlight_rules").RMarkdownHighlightRules;
+var MatchingBraceOutdent = require("ace/mode/matching_brace_outdent").MatchingBraceOutdent;
+var RMatchingBraceOutdent = require("mode/r_matching_brace_outdent").RMatchingBraceOutdent;
+var SweaveBackgroundHighlighter = require("mode/sweave_background_highlighter").SweaveBackgroundHighlighter;
+var RCodeModel = require("mode/r_code_model").RCodeModel;
+var MarkdownFoldMode = require("mode/markdown_folding").FoldMode;
+
+
+var Mode = function(suppressHighlighting, doc, session) {
+ var that = this;
+
+ this.$session = session;
+ this.$tokenizer = new Tokenizer(new RMarkdownHighlightRules().getRules());
+
+ this.$outdent = new MatchingBraceOutdent();
+ this.$r_outdent = {};
+ oop.implement(this.$r_outdent, RMatchingBraceOutdent);
+
+ this.codeModel = new RCodeModel(doc, this.$tokenizer, /^r-/,
+ /^`{3,}\s*\{r(.*)\}\s*$/);
+
+ var markdownFoldingRules = new MarkdownFoldMode();
+
+ this.foldingRules = {
+
+ getFoldWidget: function(session, foldStyle, row) {
+ if (that.getLanguageMode({row: row, column: 0}) == "Markdown")
+ return markdownFoldingRules.getFoldWidget(session, foldStyle, row);
+ else
+ return that.codeModel.getFoldWidget(session, foldStyle, row);
+ },
+
+ getFoldWidgetRange: function(session, foldStyle, row) {
+ if (that.getLanguageMode({row: row, column: 0}) == "Markdown")
+ return markdownFoldingRules.getFoldWidgetRange(session, foldStyle, row);
+ else
+ return that.codeModel.getFoldWidgetRange(session, foldStyle, row);
+ }
+
+ };
+
+ this.$sweaveBackgroundHighlighter = new SweaveBackgroundHighlighter(
+ session,
+ /^`{3,}\s*\{r(?:.*)\}\s*$/,
+ /^`{3,}\s*$/,
+ true);
+};
+oop.inherits(Mode, MarkdownMode);
+
+(function() {
+
+ this.insertChunkInfo = {
+ value: "```{r}\n\n```\n",
+ position: {row: 0, column: 5}
+ };
+
+ this.getLanguageMode = function(position)
+ {
+ if (this.$session.getState(position.row).match(/^r-cpp-(?!r-)/))
+ return 'C_CPP';
+ else
+ return this.$session.getState(position.row).match(/^r-/) ? 'R' : 'Markdown';
+ };
+
+ this.inCppLanguageMode = function(state)
+ {
+ return state.match(/^r-cpp-(?!r-)/);
+ }
+
+ this.inMarkdownLanguageMode = function(state)
+ {
+ return !state.match(/^r-/);
+ }
+
+ this.getNextLineIndent = function(state, line, tab, tabSize, row)
+ {
+ if (!this.inCppLanguageMode(state))
+ return this.codeModel.getNextLineIndent(row, line, state, tab, tabSize);
+ else {
+ // from c_cpp getNextLineIndent
+ var indent = this.$getIndent(line);
+
+ var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
+ var tokens = tokenizedLine.tokens;
+ var endState = tokenizedLine.state;
+
+ if (tokens.length && tokens[tokens.length-1].type == "comment") {
+ return indent;
+ }
+
+ if (state == "r-cpp-start") {
+ var match = line.match(/^.*[\{\(\[]\s*$/);
+ if (match) {
+ indent += tab;
+ }
+ } else if (state == "r-cpp-doc-start") {
+ if (endState == "start") {
+ return "";
+ }
+ var match = line.match(/^\s*(\/?)\*/);
+ if (match) {
+ if (match[1]) {
+ indent += " ";
+ }
+ indent += "* ";
+ }
+ }
+
+ return indent;
+ }
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ if (this.inCppLanguageMode(state))
+ return this.$outdent.checkOutdent(line, input);
+ else
+ return this.$r_outdent.checkOutdent(line,input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ if (this.inCppLanguageMode(state))
+ return this.$outdent.autoOutdent(doc, row);
+ else
+ return this.$r_outdent.autoOutdent(state, doc, row);
+ };
+
+ this.transformAction = function(state, action, editor, session, text) {
+ // from c_cpp.js
+ if (action === 'insertion') {
+ if ((text === "\n") && this.inCppLanguageMode(state)) {
+ // If newline in a doxygen comment, continue the comment
+ var pos = editor.getSelectionRange().start;
+ var match = /^((\s*\/\/+')\s*)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[2].length) {
+ return {text: "\n" + match[1]};
+ }
+ }
+
+ else if ((text === "R") && this.inCppLanguageMode(state)) {
+ // If newline to start and embedded R chunk complete the chunk
+ var pos = editor.getSelectionRange().start;
+ var match = /^(\s*\/\*{3,}\s*)/.exec(session.doc.getLine(pos.row));
+ if (match && editor.getSelectionRange().start.column >= match[1].length) {
+ return {text: "R\n\n*/\n",
+ selection: [1,0,1,0]};
+ }
+ }
+ }
+ return false;
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/rmarkdown_highlight_rules.js b/src/gwt/acesupport/acemode/rmarkdown_highlight_rules.js
new file mode 100644
index 0000000..9f342c6
--- /dev/null
+++ b/src/gwt/acesupport/acemode/rmarkdown_highlight_rules.js
@@ -0,0 +1,65 @@
+/*
+ * markdown_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/rmarkdown_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;
+var c_cppHighlightRules = require("mode/c_cpp_highlight_rules").c_cppHighlightRules;
+var MarkdownHighlightRules = require("mode/markdown_highlight_rules").MarkdownHighlightRules;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var RMarkdownHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = new MarkdownHighlightRules().getRules();
+ this.$rules["start"].unshift({
+ token: "support.function.codebegin",
+ regex: "^`{3,}\\s*\\{r(?:.*)\\}\\s*$",
+ next: "r-start"
+ });
+
+ var rRules = new RHighlightRules().getRules();
+ this.addRules(rRules, "r-");
+ this.$rules["r-start"].unshift({
+ token: "support.function.codeend",
+ regex: "^`{3,}\\s*$",
+ next: "start"
+ });
+
+ this.$rules["start"].unshift({
+ token: "support.function.codebegin",
+ regex: "^`{3,}\\s*\\{r(?:.*)engine\\='Rcpp'(?:.*)\\}\\s*$",
+ next: "r-cpp-start"
+ });
+
+ var cppRules = new c_cppHighlightRules().getRules();
+ this.addRules(cppRules, "r-cpp-");
+ this.$rules["r-cpp-start"].unshift({
+ token: "support.function.codeend",
+ regex: "^`{3,}\\s*$",
+ next: "start"
+ });
+};
+oop.inherits(RMarkdownHighlightRules, TextHighlightRules);
+
+exports.RMarkdownHighlightRules = RMarkdownHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/sweave.js b/src/gwt/acesupport/acemode/sweave.js
new file mode 100644
index 0000000..57f9706
--- /dev/null
+++ b/src/gwt/acesupport/acemode/sweave.js
@@ -0,0 +1,97 @@
+/*
+ * sweave.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/sweave", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+var SweaveBackgroundHighlighter = require("mode/sweave_background_highlighter").SweaveBackgroundHighlighter;
+var SweaveHighlightRules = require("mode/sweave_highlight_rules").SweaveHighlightRules;
+var RCodeModel = require("mode/r_code_model").RCodeModel;
+var RMatchingBraceOutdent = require("mode/r_matching_brace_outdent").RMatchingBraceOutdent;
+var unicode = require("ace/unicode");
+
+var Mode = function(suppressHighlighting, doc, session) {
+ if (suppressHighlighting)
+ this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules());
+ else
+ this.$tokenizer = new Tokenizer(new SweaveHighlightRules().getRules());
+
+ this.codeModel = new RCodeModel(doc, this.$tokenizer, /^r-/, /<<(.*?)>>/);
+ this.foldingRules = this.codeModel;
+ this.$sweaveBackgroundHighlighter = new SweaveBackgroundHighlighter(
+ session,
+ /^\s*\<\<.*\>\>=.*$/,
+ /^\s*@(?:\s.*)?$/,
+ false);
+ this.$session = session;
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+ oop.implement(this, RMatchingBraceOutdent);
+
+ this.tokenRe = new RegExp("^["
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "_]+", "g"
+ );
+
+ this.nonTokenRe = new RegExp("^(?:[^"
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "_]|\s])+", "g"
+ );
+
+ this.$complements = {
+ "(": ")",
+ "[": "]",
+ '"': '"',
+ "'": "'",
+ "{": "}"
+ };
+ this.$reOpen = /^[(["'{]$/;
+ this.$reClose = /^[)\]"'}]$/;
+
+ this.insertChunkInfo = {
+ value: "<<>>=\n\n@\n",
+ position: {row: 0, column: 2}
+ };
+
+ this.getLanguageMode = function(position)
+ {
+ return this.$session.getState(position.row).match(/^r-/) ? 'R' : 'TeX';
+ };
+
+ this.getNextLineIndent = function(state, line, tab, tabSize, row)
+ {
+ return this.codeModel.getNextLineIndent(row, line, state, tab, tabSize);
+ };
+
+ this.allowAutoInsert = this.smartAllowAutoInsert;
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/sweave_background_highlighter.js b/src/gwt/acesupport/acemode/sweave_background_highlighter.js
new file mode 100644
index 0000000..2e99d32
--- /dev/null
+++ b/src/gwt/acesupport/acemode/sweave_background_highlighter.js
@@ -0,0 +1,211 @@
+/*
+ * sweave_background_highlighter.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/sweave_background_highlighter", function(require, exports, module)
+{
+ var Range = require("ace/range").Range;
+
+ var SweaveBackgroundHighlighter = function(session, reCode, reText,
+ textIsTerminator) {
+ this.$session = session;
+ this.$doc = session.getDocument();
+ this.$reCode = reCode;
+ this.$reText = reText;
+ this.$textIsTerminator = textIsTerminator;
+
+ var that = this;
+ this.$doc.on('change', function(evt) {
+ that.$onDocChange.apply(that, [evt]);
+ });
+
+ this.$rowState = new Array(this.$doc.getLength());
+ this.$markers = new Array();
+
+ for (var i = 0; i < this.$doc.getLength(); i++)
+ this.$updateRow(i);
+ this.$syncMarkers(0);
+ };
+
+ (function() {
+
+ var TYPE_TEXT = 'text';
+ var TYPE_BEGIN = 'begin';
+ var TYPE_END = 'end';
+ var TYPE_RCODE = 'r';
+
+ this.$updateRow = function(row) {
+ // classify this row
+ var line = this.$doc.getLine(row);
+
+ var type = TYPE_TEXT;
+ var nextType = TYPE_TEXT;
+ if (line.match(this.$reCode)) {
+ type = TYPE_BEGIN;
+ nextType = TYPE_RCODE;
+ }
+ else if (!this.$textIsTerminator && line.match(this.$reText)) {
+ type = TYPE_END;
+ nextType = TYPE_TEXT;
+ }
+ else if (row > 0) {
+ var prevRowState = this.$rowState[row-1];
+ if (prevRowState === TYPE_BEGIN || prevRowState === TYPE_RCODE) {
+ if (line.match(this.$reText)) {
+ type = TYPE_END;
+ nextType = TYPE_TEXT;
+ } else {
+ type = TYPE_RCODE;
+ nextType = TYPE_RCODE;
+ }
+ }
+ }
+
+ this.$rowState[row] = type;
+ for (var i = row+1; i < this.$rowState.length; i++) {
+ var thisType = this.$rowState[i];
+
+ // If this row begins a code block, we're done. It's not possible
+ // that a change to an earlier row could cause changes to ripple
+ // beyond a TYPE_BEGIN row.
+ if (thisType === TYPE_BEGIN)
+ break;
+
+ // If this row ends a code block, it's more complicated. If
+ // $textIsTerminator is false, then we're done; it's not possible
+ // that a change to an earlier row could cause changes to ripple
+ // beyond this row. However, if $textIsTerminator, and we're now
+ // in text mode, then this row could've been turned into a text
+ // row.
+ if (thisType === TYPE_END) {
+ if (!this.$textIsTerminator) {
+ break;
+ }
+ else if (nextType === TYPE_TEXT) {
+ this.$rowState[i] = TYPE_TEXT;
+ break;
+ }
+ else {
+ // This row was previously TYPE_END, and is still TYPE_END so
+ // it's safe to exit.
+ break;
+ }
+ }
+
+ // Conversely, if $textIsTerminator, it's possible that we removed
+ // a previous reText line that causes a currently-text row to become
+ // a code terminator.
+ if (this.$textIsTerminator &&
+ nextType === TYPE_RCODE &&
+ this.$doc.getLine(i).match(this.$reText))
+ {
+ this.$updateRow(i);
+ break;
+ }
+
+ if (this.$rowState[i] === nextType)
+ break;
+ this.$rowState[i] = nextType;
+ }
+ };
+
+ this.$syncMarkers = function(startRow, rowsChanged) {
+ var dontStopBeforeRow =
+ (typeof(rowsChanged) == 'undefined' ? this.$doc.getLength()
+ : startRow + rowsChanged);
+
+ var endRow = this.$doc.getLength() - 1;
+ for (var row = startRow; row <= endRow; row++) {
+ var foreign = this.$rowState[row] != TYPE_TEXT;
+ if (!!foreign != !!this.$markers[row]) {
+ if (foreign) {
+ this.$markers[row] = this.$session.addMarker(new Range(row, 0, row + 1, 0),
+ "ace_foreign_line",
+ "background",
+ false);
+ }
+ else {
+ this.$session.removeMarker(this.$markers[row]);
+ delete this.$markers[row];
+ }
+ }
+ else if (row > dontStopBeforeRow)
+ break;
+ }
+ };
+
+ this.$insertNewRows = function(index, count) {
+ var args = new Array(count + 2);
+ args[0] = index;
+ args[1] = 0;
+ Array.prototype.splice.apply(this.$rowState, args);
+ };
+
+ this.$removeRows = function(index, count) {
+ var markers = this.$rowState.splice(index, count);
+ };
+
+ this.$onDocChange = function(evt)
+ {
+ var delta = evt.data;
+
+ if (delta.action === "insertLines")
+ {
+ var newLineCount = delta.range.end.row - delta.range.start.row;
+ this.$insertNewRows(delta.range.start.row, newLineCount);
+ for (var i = 0; i < newLineCount; i++)
+ this.$updateRow(delta.range.start.row + i);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else if (delta.action === "insertText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$insertNewRows(delta.range.end.row, 1);
+ this.$updateRow(delta.range.start.row);
+ this.$updateRow(delta.range.start.row + 1);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else
+ {
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row, 1);
+ }
+ }
+ else if (delta.action === "removeLines")
+ {
+ this.$removeRows(delta.range.start.row,
+ delta.range.end.row - delta.range.start.row);
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else if (delta.action === "removeText")
+ {
+ if (this.$doc.isNewLine(delta.text))
+ {
+ this.$removeRows(delta.range.end.row, 1);
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row);
+ }
+ else
+ {
+ this.$updateRow(delta.range.start.row);
+ this.$syncMarkers(delta.range.start.row, 1);
+ }
+ }
+ };
+
+ }).call(SweaveBackgroundHighlighter.prototype);
+
+ exports.SweaveBackgroundHighlighter = SweaveBackgroundHighlighter;
+});
diff --git a/src/gwt/acesupport/acemode/sweave_highlight_rules.js b/src/gwt/acesupport/acemode/sweave_highlight_rules.js
new file mode 100644
index 0000000..c001ba8
--- /dev/null
+++ b/src/gwt/acesupport/acemode/sweave_highlight_rules.js
@@ -0,0 +1,55 @@
+/*
+ * sweave_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/sweave_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
+var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var SweaveHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = new TexHighlightRules().getRules();
+ this.$rules["start"].unshift({
+ token: "comment.codebegin",
+ regex: "^\\s*\\<\\<.*\\>\\>=.*$",
+ next: "r-start"
+ });
+ this.$rules["start"].unshift({
+ token: "comment",
+ regex: "^\\s*@(?:\\s.*)?$"
+ });
+
+ var rRules = new RHighlightRules().getRules();
+ this.addRules(rRules, "r-");
+ this.$rules["r-start"].unshift({
+ token: "comment.codeend",
+ regex: "^\\s*@(?:\\s.*)?$",
+ next: "start"
+ });
+ this.$rules["r-start"].unshift({
+ token: "comment.codebegin",
+ regex: "^\\<\\<.*\\>\\>=.*$",
+ next: "r-start"
+ });
+};
+
+oop.inherits(SweaveHighlightRules, TextHighlightRules);
+
+exports.SweaveHighlightRules = SweaveHighlightRules;
+});
diff --git a/src/gwt/acesupport/acemode/tex.js b/src/gwt/acesupport/acemode/tex.js
new file mode 100644
index 0000000..6c4535d
--- /dev/null
+++ b/src/gwt/acesupport/acemode/tex.js
@@ -0,0 +1,49 @@
+/*
+ * tex.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/tex", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var TextMode = require("ace/mode/text").Mode;
+var Tokenizer = require("ace/tokenizer").Tokenizer;
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
+var MatchingBraceOutdent = require("ace/mode/matching_brace_outdent").MatchingBraceOutdent;
+
+var Mode = function(suppressHighlighting) {
+ if (suppressHighlighting)
+ this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules());
+ else
+ this.$tokenizer = new Tokenizer(new TexHighlightRules().getRules());
+ this.$outdent = new MatchingBraceOutdent();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+
+ this.allowAutoInsert = function() {
+ return false;
+ };
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
diff --git a/src/gwt/acesupport/acemode/tex_highlight_rules.js b/src/gwt/acesupport/acemode/tex_highlight_rules.js
new file mode 100644
index 0000000..40b99f7
--- /dev/null
+++ b/src/gwt/acesupport/acemode/tex_highlight_rules.js
@@ -0,0 +1,108 @@
+/*
+ * tex_highlight_rules.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("mode/tex_highlight_rules", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var lang = require("ace/lib/lang");
+var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
+
+var TexHighlightRules = function(textClass) {
+
+ if (!textClass)
+ textClass = "text";
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ token : "comment",
+ regex : "%.*$"
+ }, {
+ token : textClass, // non-command
+ regex : "\\\\[$&%#\\{\\}]"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:documentclass|usepackage|newcounter|setcounter|addtocounter|value|arabic|stepcounter|newenvironment|renewenvironment|ref|vref|eqref|pageref|label|cite[a-zA-Z]*|tag|begin|end|bibitem)\\b",
+ next : "nospell"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])"
+ }, {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[[({]"
+ }, {
+ // Obviously these are neither keywords nor operators, but
+ // labelling them as such was the easiest way to get them
+ // to be colored distinctly from regular text
+ token : "paren.keyword.operator",
+ regex : "[\\])}]"
+ }, {
+ token : textClass,
+ regex : "\\s+"
+ }
+ ],
+ // This mode is necessary to prevent spell checking, but to keep the
+ // same syntax highlighting behavior. The list of commands comes from
+ // Texlipse.
+ "nospell" : [
+ {
+ token : "comment",
+ regex : "%.*$",
+ next : "start"
+ }, {
+ token : "nospell." + textClass, // non-command
+ regex : "\\\\[$&%#\\{\\}]"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:documentclass|usepackage|newcounter|setcounter|addtocounter|value|arabic|stepcounter|newenvironment|renewenvironment|ref|vref|eqref|pageref|label|cite[a-zA-Z]*|tag|begin|end|bibitem)\\b"
+ }, {
+ token : "keyword", // command
+ regex : "\\\\(?:[a-zA-z0-9]+|[^a-zA-z0-9])",
+ next : "start"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "[[({]"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "[\\])]"
+ }, {
+ token : "paren.keyword.operator",
+ regex : "}",
+ next : "start"
+ }, {
+ token : "nospell." + textClass,
+ regex : "\\s+"
+ }, {
+ token : "nospell." + textClass,
+ regex : "\\w+"
+ }
+ ]
+ };
+};
+
+oop.inherits(TexHighlightRules, TextHighlightRules);
+
+exports.TexHighlightRules = TexHighlightRules;
+});
diff --git a/src/gwt/acesupport/acetheme/default.js b/src/gwt/acesupport/acetheme/default.js
new file mode 100644
index 0000000..dc44e2c
--- /dev/null
+++ b/src/gwt/acesupport/acetheme/default.js
@@ -0,0 +1,24 @@
+/*
+ * default.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+define("theme/default", function(require, exports, module) {
+
+ var dom = require("ace/lib/dom");
+ exports.cssClass = "ace-rs";
+});
diff --git a/src/gwt/acesupport/extern.js b/src/gwt/acesupport/extern.js
new file mode 100644
index 0000000..aabb960
--- /dev/null
+++ b/src/gwt/acesupport/extern.js
@@ -0,0 +1,16 @@
+/*
+ * extern.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+function define(name, func) {}
+function require(name) {}
diff --git a/src/gwt/acesupport/loader.js b/src/gwt/acesupport/loader.js
new file mode 100644
index 0000000..250ad89
--- /dev/null
+++ b/src/gwt/acesupport/loader.js
@@ -0,0 +1,187 @@
+/*
+ * loader.js
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+ if (!String.prototype.trimRight) {
+ var trimEndRegexp = /\s\s*$/;
+ String.prototype.trimRight = function () {
+ return String(this).replace(trimEndRegexp, '');
+ };
+ }
+
+define("rstudio/loader", function(require, exports, module) {
+
+var oop = require("ace/lib/oop");
+var event = require("ace/lib/event");
+var EventEmitter = require("ace/lib/event_emitter").EventEmitter;
+var Editor = require("ace/editor").Editor;
+var EditSession = require("ace/edit_session").EditSession;
+var UndoManager = require("ace/undomanager").UndoManager;
+var Range = require("ace/range").Range;
+
+var RStudioEditor = function(renderer, session) {
+ Editor.call(this, renderer, session);
+ this.setBehavioursEnabled(true);
+};
+oop.inherits(RStudioEditor, Editor);
+
+(function() {
+ this.remove = function(dir) {
+ if (this.session.getMode().wrapRemove) {
+ return this.session.getMode().wrapRemove(this, Editor.prototype.remove, dir);
+ }
+ else {
+ return Editor.prototype.remove.call(this, dir);
+ }
+ };
+
+ this.undo = function() {
+ Editor.prototype.undo.call(this);
+ this._dispatchEvent("undo");
+ };
+
+ this.redo = function() {
+ Editor.prototype.redo.call(this);
+ this._dispatchEvent("redo");
+ };
+}).call(RStudioEditor.prototype);
+
+
+var RStudioEditSession = function(text, mode) {
+ EditSession.call(this, text, mode);
+};
+oop.inherits(RStudioEditSession, EditSession);
+
+(function() {
+ this.insert = function(position, text) {
+ if (this.getMode().wrapInsert) {
+ return this.getMode().wrapInsert(this, EditSession.prototype.insert, position, text);
+ }
+ else {
+ return EditSession.prototype.insert.call(this, position, text);
+ }
+ };
+ this.reindent = function(range) {
+ var mode = this.getMode();
+ if (!mode.getNextLineIndent)
+ return;
+ var start = range.start.row;
+ var end = range.end.row;
+ for (var i = start; i <= end; i++) {
+ // First line is always unindented
+ if (i == 0) {
+ this.applyIndent(i, "");
+ }
+ else {
+ var state = this.getState(i-1);
+ if (state == 'qstring' || state == 'qqstring')
+ continue;
+ var line = this.getLine(i-1);
+ var newline = this.getLine(i);
+
+ var shouldOutdent = mode.checkOutdent(state, " ", newline);
+
+ var newIndent = mode.getNextLineIndent(state,
+ line,
+ this.getTabString(),
+ this.getTabSize(),
+ i-1);
+
+ this.applyIndent(i, newIndent);
+
+ if (shouldOutdent) {
+ mode.autoOutdent(state, this, i);
+ }
+ }
+ }
+ };
+ this.applyIndent = function(lineNum, indent) {
+ var line = this.getLine(lineNum);
+ var matchLen = line.match(/^\s*/g)[0].length;
+ this.replace(new Range(lineNum, 0, lineNum, matchLen), indent);
+ };
+
+ this.setDisableOverwrite = function(disableOverwrite) {
+
+ // Note that 'this' refers to the instance, not the prototype. It's
+ // important that we override set/getOverwrite on a per-instance basis
+ // only.
+
+ if (disableOverwrite) {
+ // jcheng 08/21/2012: The old way we did this (see git history) caused
+ // a weird bug: the console would pick up the overwrite/insert mode of
+ // the active source document iff vim mode was enabled. I could not
+ // figure out why.
+
+ // In case we are already in overwrite mode; set it to false so events
+ // will be fired.
+ this.setOverwrite(false);
+
+ this.setOverwrite = function() { /* no-op */ };
+ this.getOverwrite = function() { return false; }
+ }
+ else {
+ // Restore the standard methods
+ this.setOverwrite = EditSession.prototype.setOverwrite;
+ this.getOverwrite = EditSession.prototype.getOverwrite;
+ }
+ };
+}).call(RStudioEditSession.prototype);
+
+
+var RStudioUndoManager = function() {
+ UndoManager.call(this);
+};
+oop.inherits(RStudioUndoManager, UndoManager);
+
+(function() {
+ this.peek = function() {
+ return this.$undoStack.length ? this.$undoStack[this.$undoStack.length-1]
+ : null;
+ };
+}).call(RStudioUndoManager.prototype);
+
+
+function loadEditor(container) {
+ var env = {};
+
+ var Renderer = require("ace/virtual_renderer").VirtualRenderer;
+
+ var TextMode = require("ace/mode/text").Mode;
+ var theme = {}; // prevent default textmate theme from loading
+
+ env.editor = new RStudioEditor(new Renderer(container, theme), new RStudioEditSession(""));
+ var session = env.editor.getSession();
+ session.setMode(new TextMode());
+ session.setUndoManager(new RStudioUndoManager());
+
+ // We handle these commands ourselves.
+ function squelch(cmd) {
+ env.editor.commands.removeCommand(cmd);
+ }
+ squelch("findnext");
+ squelch("findprevious");
+ squelch("find");
+ squelch("replace");
+ squelch("togglecomment");
+ squelch("gotoline");
+ squelch("foldall");
+ squelch("unfoldall");
+ squelch("touppercase");
+ squelch("tolowercase")
+ return env.editor;
+}
+
+exports.loadEditor = loadEditor;
+});
diff --git a/src/gwt/build.xml b/src/gwt/build.xml
new file mode 100644
index 0000000..47ebdc7
--- /dev/null
+++ b/src/gwt/build.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<!--
+#
+# build.xml
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+<project name="client" default="build" basedir=".">
+
+ <taskdef name="jscomp"
+ classname="com.google.javascript.jscomp.ant.CompileTask"
+ classpath="./tools/compiler/compiler.jar"/>
+ <!-- Configure path to GWT SDK -->
+ <property name="gwt.sdk" value="lib/gwt/2.5.1"/>
+
+ <property name="gwt.extra.args" value=""/>
+ <property name="gwt.main.module" value="org.rstudio.studio.RStudio"/>
+ <property name="ace.bin" value="src/org/rstudio/studio/client/workbench/views/source/editors/text/ace"/>
+
+ <path id="project.class.path">
+ <pathelement location="bin"/>
+ <fileset dir="${gwt.sdk}" includes="*.jar"/>
+ <fileset dir="lib/gin/1.5" includes="*.jar"/>
+ </path>
+
+ <path id="test.class.path">
+ <pathelement location="bin"/>
+ <fileset dir="lib/selenium/2.37.0" includes="*.jar"/>
+ <fileset dir="lib/selenium/2.37.0/libs" includes="*.jar"/>
+ </path>
+
+ <target name="ext">
+ <jscomp compilationLevel="simple" warning="default"
+ debug="false" output="${ace.bin}/acesupport.js">
+ <externs dir="acesupport">
+ <file name="extern.js"/>
+ </externs>
+ <sources dir="acesupport">
+ <file name="loader.js"/>
+ </sources>
+ <sources dir="acesupport/acemode">
+ <file name="auto_brace_insert.js"/>
+ <file name="r_background_highlighter.js"/>
+ <file name="r_highlight_rules.js"/>
+ <file name="r_matching_brace_outdent.js"/>
+ <file name="r_code_model.js"/>
+ <file name="r_scope_tree.js"/>
+ <file name="r.js"/>
+ <file name="tex_highlight_rules.js"/>
+ <file name="tex.js"/>
+ <file name="sweave_background_highlighter.js"/>
+ <file name="sweave_highlight_rules.js"/>
+ <file name="sweave.js"/>
+ <file name="rdoc_highlight_rules.js"/>
+ <file name="rdoc.js"/>
+ <file name="markdown_highlight_rules.js"/>
+ <file name="markdown_folding.js"/>
+ <file name="markdown.js"/>
+ <file name="rmarkdown_highlight_rules.js"/>
+ <file name="rmarkdown.js"/>
+ <file name="rhtml_highlight_rules.js"/>
+ <file name="rhtml.js"/>
+ <file name="dcf_highlight_rules.js"/>
+ <file name="dcf.js"/>
+ <file name="doc_comment_highlight_rules.js"/>
+ <file name="c_cpp_highlight_rules.js"/>
+ <file name="c_cpp.js"/>
+ <file name="c_cpp_fold_mode.js"/>
+ </sources>
+ <sources dir="acesupport/acetheme">
+ <file name="default.js"/>
+ </sources>
+ </jscomp>
+
+ </target>
+
+ <target name="javac" description="Compile java source">
+ <mkdir dir="bin"/>
+ <!-- Compile com.google stuff separately from org.rstudio stuff since
+ theirs have lots of deprecation warnings we can't do anything about -->
+ <javac srcdir="src" includes="com/google/**" encoding="utf-8"
+ destdir="bin"
+ source="1.5" target="1.5" nowarn="true" deprecation="false"
+ debug="true" debuglevel="lines,vars,source"
+ includeantruntime="false">
+ <classpath refid="project.class.path"/>
+ <compilerarg value="-Xlint:-deprecation"/>
+ </javac>
+ <javac srcdir="src" includes="org/rstudio/**" encoding="utf-8"
+ destdir="bin"
+ source="1.5" target="1.5" nowarn="true" deprecation="true"
+ debug="true" debuglevel="lines,vars,source"
+ includeantruntime="false">
+ <classpath refid="project.class.path"/>
+ <compilerarg value="-Xlint"/>
+ </javac>
+ <copy todir="bin">
+ <fileset dir="src" excludes="**/*.java"/>
+ </copy>
+ </target>
+
+ <target name="gwtc" depends="ext,javac" description="GWT compile to JavaScript">
+ <java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
+ <classpath>
+ <pathelement location="src"/>
+ <path refid="project.class.path"/>
+ </classpath>
+ <!-- add jvmarg -Xss16M or similar if you see a StackOverflowError -->
+ <jvmarg value="-Xmx1024M"/>
+ <arg value="-war"/>
+ <arg value="www"/>
+ <arg value="-localWorkers"/>
+ <arg value="4"/>
+ <arg value="-XdisableClassMetadata"/>
+ <arg value="-XdisableCastChecking"/>
+ <arg line="-gen gen"/>
+ <!--<arg line="-style PRETTY"/>-->
+ <arg line="-extra extras"/>
+ <arg line="${gwt.extra.args}"/>
+ <!-- Additional arguments like -logLevel DEBUG -->
+ <arg value="${gwt.main.module}"/>
+ </java>
+ </target>
+
+ <target name="soyc" description="Generate and show SOYC report">
+ <antcall target="gwtc">
+ <param name="gwt.main.module" value="org.rstudio.studio.RStudioDraft"/>
+ <param name="gwt.extra.args" value="${gwt.extra.args} -compileReport"/>
+ </antcall>
+ <exec executable="open" os="Mac OS X">
+ <arg file="extras/rstudio/soycReport/compile-report/index.html"/>
+ </exec>
+ </target>
+
+ <target name="draft" description="Compile using GWT's draft mode">
+ <antcall target="gwtc">
+ <param name="gwt.main.module" value="org.rstudio.studio.RStudioDraft"/>
+ <param name="gwt.extra.args" value="${gwt.extra.args} -draftCompile" />
+ </antcall>
+ </target>
+
+ <target name="superdevmode" description="Run super dev mode">
+ <antcall target="gwtc">
+ <param name="gwt.main.module" value="org.rstudio.studio.RStudioSuperDevMode"/>
+ </antcall>
+ <java failonerror="true" fork="true" classname="com.google.gwt.dev.codeserver.CodeServer">
+ <classpath>
+ <pathelement location="src"/>
+ <path refid="project.class.path"/>
+ </classpath>
+ <jvmarg value="-Xmx2048M"/>
+ <arg value="-src"/>
+ <arg value = "src"/>
+ <arg value="org.rstudio.studio.RStudioSuperDevMode"/>
+ </java>
+ </target>
+
+ <target name="build" depends="gwtc" description="Build this project" />
+
+ <target name="clean" description="Cleans this project">
+ <delete dir="bin" failonerror="false" />
+ <delete dir="gwt-unitCache" failonerror="false" />
+ <delete dir="www/rstudio" failonerror="false" />
+ <delete file="${ace.bin}/acesupport.js" failonerror="false" />
+ <delete dir="gen" failonerror="false" />
+ <delete dir="extras" failonerror="false" />
+ </target>
+
+ <target name="test" description="Runs Selenium tests" depends="build-tests">
+ <parallel>
+ <daemons>
+ <exec executable="lib/selenium/chromedriver/2.7/chromedriver-mac" os="Mac OS X" />
+ <exec executable="lib/selenium/chromedriver/2.7/chromedriver-win.exe" os="Windows NT" />
+ <exec executable="lib/selenium/chromedriver/2.7/chromedriver-linux" os="Linux" />
+ </daemons>
+ <sequential>
+ <java failonerror="true" fork="true" classname="org.junit.runner.JUnitCore">
+ <classpath>
+ <pathelement location="src"/>
+ <path refid="test.class.path"/>
+ </classpath>
+ <arg value="org.rstudio.studio.selenium.RStudioTestSuite"/>
+ </java>
+ </sequential>
+ </parallel>
+ </target>
+
+ <target name="build-tests" description="Builds Selenium tests">
+ <javac srcdir="test" includes="org/rstudio/studio/selenium/**" encoding="utf-8"
+ destdir="bin"
+ source="1.5" target="1.5" nowarn="true" deprecation="true"
+ debug="true" debuglevel="lines,vars,source"
+ includeantruntime="false">
+ <classpath refid="test.class.path"/>
+ </javac>
+ </target>
+
+</project>
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java b/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java
new file mode 100644
index 0000000..42618f3
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -0,0 +1,1397 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.ui.PopupPanel.AnimationType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A standard menu bar widget. A menu bar can contain any number of menu items,
+ * each of which can either fire a {@link com.google.gwt.user.client.Command} or
+ * open a cascaded menu bar.
+ *
+ * <p>
+ * <img class='gallery' src='doc-files/MenuBar.png'/>
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <dl>
+ * <dt>.gwt-MenuBar</dt>
+ * <dd>the menu bar itself</dd>
+ * <dt>.gwt-MenuBar-horizontal</dt>
+ * <dd>dependent style applied to horizontal menu bars</dd>
+ * <dt>.gwt-MenuBar-vertical</dt>
+ * <dd>dependent style applied to vertical menu bars</dd>
+ * <dt>.gwt-MenuBar .gwt-MenuItem</dt>
+ * <dd>menu items</dd>
+ * <dt>.gwt-MenuBar .gwt-MenuItem-selected</dt>
+ * <dd>selected menu items</dd>
+ * <dt>.gwt-MenuBar .gwt-MenuItemSeparator</dt>
+ * <dd>section breaks between menu items</dd>
+ * <dt>.gwt-MenuBar .gwt-MenuItemSeparator .menuSeparatorInner</dt>
+ * <dd>inner component of section separators</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopLeft</dt>
+ * <dd>the top left cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopLeftInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopCenter</dt>
+ * <dd>the top center cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopCenterInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopRight</dt>
+ * <dd>the top right cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupTopRightInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleLeft</dt>
+ * <dd>the middle left cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleLeftInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleCenter</dt>
+ * <dd>the middle center cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleCenterInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleRight</dt>
+ * <dd>the middle right cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupMiddleRightInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomLeft</dt>
+ * <dd>the bottom left cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomLeftInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomCenter</dt>
+ * <dd>the bottom center cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomCenterInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomRight</dt>
+ * <dd>the bottom right cell</dd>
+ * <dt>.gwt-MenuBarPopup .menuPopupBottomRightInner</dt>
+ * <dd>the inner element of the cell</dd>
+ * </dl>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.MenuBarExample}
+ * </p>
+ *
+ * <h3>Use in UiBinder Templates</h3>
+ * <p>
+ * MenuBar elements in UiBinder template files can have a <code>vertical</code>
+ * boolean attribute (which defaults to false), and may have only MenuItem
+ * elements as children. MenuItems may contain HTML and MenuBars.
+ * <p>
+ * For example:
+ *
+ * <pre>
+ * <g:MenuBar>
+ * <g:MenuItem>Higgledy
+ * <g:MenuBar vertical="true">
+ * <g:MenuItem>able</g:MenuItem>
+ * <g:MenuItem>baker</g:MenuItem>
+ * <g:MenuItem>charlie</g:MenuItem>
+ * </g:MenuBar>
+ * </g:MenuItem>
+ * <g:MenuItem>Piggledy
+ * <g:MenuBar vertical="true">
+ * <g:MenuItem>foo</g:MenuItem>
+ * <g:MenuItem>bar</g:MenuItem>
+ * <g:MenuItem>baz</g:MenuItem>
+ * </g:MenuBar>
+ * </g:MenuItem>
+ * <g:MenuItem><b>Pop!</b>
+ * <g:MenuBar vertical="true">
+ * <g:MenuItem>uno</g:MenuItem>
+ * <g:MenuItem>dos</g:MenuItem>
+ * <g:MenuItem>tres</g:MenuItem>
+ * </g:MenuBar>
+ * </g:MenuItem>
+ * </g:MenuBar>
+ * </pre>
+ */
+// Nothing we can do about MenuBar implementing PopupListener until next
+// release.
+ at SuppressWarnings("deprecation")
+public class MenuBar extends Widget implements PopupListener, HasAnimation,
+ HasCloseHandlers<PopupPanel> {
+
+ /**
+ * An {@link ImageBundle} that provides images for {@link MenuBar}.
+ *
+ * @deprecated replaced by {@link Resources}
+ */
+ @Deprecated
+ public interface MenuBarImages extends ImageBundle {
+ /**
+ * An image indicating a {@link MenuItem} has an associated submenu.
+ *
+ * @return a prototype of this image
+ */
+ AbstractImagePrototype menuBarSubMenuIcon();
+ }
+
+ /**
+ * A ClientBundle that contains the default resources for this widget.
+ */
+ public interface Resources extends ClientBundle {
+ /**
+ * An image indicating a {@link MenuItem} has an associated submenu.
+ */
+ @ImageOptions(flipRtl = true)
+ ImageResource menuBarSubMenuIcon();
+ }
+
+ private static final String STYLENAME_DEFAULT = "gwt-MenuBar";
+
+ /**
+ * List of all {@link MenuItem}s and {@link MenuItemSeparator}s.
+ */
+ private ArrayList<UIObject> allItems = new ArrayList<UIObject>();
+
+ /**
+ * List of {@link MenuItem}s, not including {@link MenuItemSeparator}s.
+ */
+ private ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+
+ private Element body;
+
+ private AbstractImagePrototype subMenuIcon = null;
+ private boolean isAnimationEnabled = false;
+ private MenuBar parentMenu;
+ private PopupPanel popup;
+ private MenuItem selectedItem;
+ private MenuBar shownChildMenu;
+ private boolean vertical, autoOpen;
+ private boolean focusOnHover = true;
+
+ /**
+ * Creates an empty horizontal menu bar.
+ */
+ public MenuBar() {
+ this(false);
+ }
+
+ /**
+ * Creates an empty menu bar.
+ *
+ * @param vertical <code>true</code> to orient the menu bar vertically
+ */
+ public MenuBar(boolean vertical) {
+ this(vertical, GWT.<Resources> create(Resources.class));
+ }
+
+ /**
+ * Creates an empty menu bar that uses the specified image bundle for menu
+ * images.
+ *
+ * @param vertical <code>true</code> to orient the menu bar vertically
+ * @param images a bundle that provides images for this menu
+ * @deprecated replaced by {@link #MenuBar(boolean, Resources)}
+ */
+ @Deprecated
+ public MenuBar(boolean vertical, MenuBarImages images) {
+ init(vertical, images.menuBarSubMenuIcon());
+ }
+
+ /**
+ * Creates an empty menu bar that uses the specified ClientBundle for menu
+ * images.
+ *
+ * @param vertical <code>true</code> to orient the menu bar vertically
+ * @param resources a bundle that provides images for this menu
+ */
+ public MenuBar(boolean vertical, Resources resources) {
+ init(vertical,
+ AbstractImagePrototype.create(resources.menuBarSubMenuIcon()));
+ }
+
+ /**
+ * Creates an empty horizontal menu bar that uses the specified image bundle
+ * for menu images.
+ *
+ * @param images a bundle that provides images for this menu
+ * @deprecated replaced by {@link #MenuBar(Resources)}
+ */
+ @Deprecated
+ public MenuBar(MenuBarImages images) {
+ this(false, images);
+ }
+
+ /**
+ * Creates an empty horizontal menu bar that uses the specified ClientBundle
+ * for menu images.
+ *
+ * @param resources a bundle that provides images for this menu
+ */
+ public MenuBar(Resources resources) {
+ this(false, resources);
+ }
+
+ public HandlerRegistration addCloseHandler(CloseHandler<PopupPanel> handler) {
+ return addHandler(handler, CloseEvent.getType());
+ }
+
+ /**
+ * Adds a menu item to the bar.
+ *
+ * @param item the item to be added
+ * @return the {@link MenuItem} object
+ */
+ public MenuItem addItem(MenuItem item) {
+ return insertItem(item, allItems.size());
+ }
+
+ /**
+ * Adds a menu item to the bar containing SafeHtml, that will fire the given
+ * command when it is selected.
+ *
+ * @param html the item's html text
+ * @param cmd the command to be fired
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(SafeHtml html, Command cmd) {
+ return addItem(new MenuItem(html, cmd));
+ }
+
+ /**
+ * Adds a menu item to the bar, that will fire the given command when it is
+ * selected.
+ *
+ * @param text the item's text
+ * @param asHTML <code>true</code> to treat the specified text as html
+ * @param cmd the command to be fired
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, boolean asHTML, Command cmd) {
+ return addItem(new MenuItem(text, asHTML, cmd));
+ }
+
+ /**
+ * Adds a menu item to the bar, that will open the specified menu when it is
+ * selected.
+ *
+ * @param html the item's html text
+ * @param popup the menu to be cascaded from it
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(SafeHtml html, MenuBar popup) {
+ return addItem(new MenuItem(html, popup));
+ }
+
+ /**
+ * Adds a menu item to the bar, that will open the specified menu when it is
+ * selected.
+ *
+ * @param text the item's text
+ * @param asHTML <code>true</code> to treat the specified text as html
+ * @param popup the menu to be cascaded from it
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, boolean asHTML, MenuBar popup) {
+ return addItem(new MenuItem(text, asHTML, popup));
+ }
+
+ /**
+ * Adds a menu item to the bar, that will fire the given command when it is
+ * selected.
+ *
+ * @param text the item's text
+ * @param cmd the command to be fired
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, Command cmd) {
+ return addItem(new MenuItem(text, cmd));
+ }
+
+ /**
+ * Adds a menu item to the bar, that will open the specified menu when it is
+ * selected.
+ *
+ * @param text the item's text
+ * @param popup the menu to be cascaded from it
+ * @return the {@link MenuItem} object created
+ */
+ public MenuItem addItem(String text, MenuBar popup) {
+ return addItem(new MenuItem(text, popup));
+ }
+
+ /**
+ * Adds a thin line to the {@link MenuBar} to separate sections of
+ * {@link MenuItem}s.
+ *
+ * @return the {@link MenuItemSeparator} object created
+ */
+ public MenuItemSeparator addSeparator() {
+ return addSeparator(new MenuItemSeparator());
+ }
+
+ /**
+ * Adds a thin line to the {@link MenuBar} to separate sections of
+ * {@link MenuItem}s.
+ *
+ * @param separator the {@link MenuItemSeparator} to be added
+ * @return the {@link MenuItemSeparator} object
+ */
+ public MenuItemSeparator addSeparator(MenuItemSeparator separator) {
+ return insertSeparator(separator, allItems.size());
+ }
+
+ /**
+ * Removes all menu items from this menu bar.
+ */
+ public void clearItems() {
+ // Deselect the current item
+ selectItem(null);
+
+ Element container = getItemContainerElement();
+ while (DOM.getChildCount(container) > 0) {
+ DOM.removeChild(container, DOM.getChild(container, 0));
+ }
+
+ // Set the parent of all items to null
+ for (UIObject item : allItems) {
+ setItemColSpan(item, 1);
+ if (item instanceof MenuItemSeparator) {
+ ((MenuItemSeparator) item).setParentMenu(null);
+ } else {
+ ((MenuItem) item).setParentMenu(null);
+ }
+ }
+
+ // Clear out all of the items and separators
+ items.clear();
+ allItems.clear();
+ }
+
+ /**
+ * Closes this menu and all child menu popups.
+ *
+ * @param focus true to move focus to the parent
+ */
+ public void closeAllChildren(boolean focus) {
+ if (shownChildMenu != null) {
+ // Hide any open submenus of this item
+ shownChildMenu.onHide(focus);
+ shownChildMenu = null;
+ selectItem(null);
+ }
+ // Close the current popup
+ if (popup != null) {
+ popup.hide();
+ }
+ // If focus is true, set focus to parentMenu
+ if (focus && parentMenu != null) {
+ parentMenu.focus();
+ }
+ }
+
+ /**
+ * Give this MenuBar focus.
+ */
+ public void focus() {
+ FocusPanel.impl.focus(getElement());
+ }
+
+ /**
+ * Gets whether this menu bar's child menus will open when the mouse is moved
+ * over it.
+ *
+ * @return <code>true</code> if child menus will auto-open
+ */
+ public boolean getAutoOpen() {
+ return autoOpen;
+ }
+
+ /**
+ * Get the index of a {@link MenuItem}.
+ *
+ * @return the index of the item, or -1 if it is not contained by this MenuBar
+ */
+ public int getItemIndex(MenuItem item) {
+ return allItems.indexOf(item);
+ }
+
+ /**
+ * Get the index of a {@link MenuItemSeparator}.
+ *
+ * @return the index of the separator, or -1 if it is not contained by this
+ * MenuBar
+ */
+ public int getSeparatorIndex(MenuItemSeparator item) {
+ return allItems.indexOf(item);
+ }
+
+ /**
+ * Adds a menu item to the bar at a specific index.
+ *
+ * @param item the item to be inserted
+ * @param beforeIndex the index where the item should be inserted
+ * @return the {@link MenuItem} object
+ * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+ * range
+ */
+ public MenuItem insertItem(MenuItem item, int beforeIndex)
+ throws IndexOutOfBoundsException {
+ // Check the bounds
+ if (beforeIndex < 0 || beforeIndex > allItems.size()) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ // Add to the list of items
+ allItems.add(beforeIndex, item);
+ int itemsIndex = 0;
+ for (int i = 0; i < beforeIndex; i++) {
+ if (allItems.get(i) instanceof MenuItem) {
+ itemsIndex++;
+ }
+ }
+ items.add(itemsIndex, item);
+
+ // Setup the menu item
+ addItemElement(beforeIndex, item.getElement());
+ item.setParentMenu(this);
+ item.setSelectionStyle(false);
+ updateSubmenuIcon(item);
+ return item;
+ }
+
+ /**
+ * Adds a thin line to the {@link MenuBar} to separate sections of
+ * {@link MenuItem}s at the specified index.
+ *
+ * @param beforeIndex the index where the separator should be inserted
+ * @return the {@link MenuItemSeparator} object
+ * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+ * range
+ */
+ public MenuItemSeparator insertSeparator(int beforeIndex) {
+ return insertSeparator(new MenuItemSeparator(), beforeIndex);
+ }
+
+ /**
+ * Adds a thin line to the {@link MenuBar} to separate sections of
+ * {@link MenuItem}s at the specified index.
+ *
+ * @param separator the {@link MenuItemSeparator} to be inserted
+ * @param beforeIndex the index where the separator should be inserted
+ * @return the {@link MenuItemSeparator} object
+ * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+ * range
+ */
+ public MenuItemSeparator insertSeparator(MenuItemSeparator separator,
+ int beforeIndex) throws IndexOutOfBoundsException {
+ // Check the bounds
+ if (beforeIndex < 0 || beforeIndex > allItems.size()) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (vertical) {
+ setItemColSpan(separator, 2);
+ }
+ addItemElement(beforeIndex, separator.getElement());
+ separator.setParentMenu(this);
+ allItems.add(beforeIndex, separator);
+ return separator;
+ }
+
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
+ /**
+ * Check whether or not this widget will steal keyboard focus when the mouse
+ * hovers over it.
+ *
+ * @return true if enabled, false if disabled
+ */
+ public boolean isFocusOnHoverEnabled() {
+ return focusOnHover;
+ }
+
+ /**
+ * Moves the menu selection down to the next item. If there is no selection,
+ * selects the first item. If there are no items at all, does nothing.
+ */
+ public void moveSelectionDown() {
+ if (selectFirstItemIfNoneSelected()) {
+ return;
+ }
+
+ if (vertical) {
+ selectNextItem();
+ } else {
+ if (selectedItem.getSubMenu() != null
+ && !selectedItem.getSubMenu().getItems().isEmpty()
+ && (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
+ if (shownChildMenu == null) {
+ doItemAction(selectedItem, false, true);
+ }
+ selectedItem.getSubMenu().focus();
+ } else if (parentMenu != null) {
+ if (parentMenu.vertical) {
+ parentMenu.selectNextItem();
+ } else {
+ parentMenu.moveSelectionDown();
+ }
+ }
+ }
+ }
+
+ /**
+ * Moves the menu selection up to the previous item. If there is no selection,
+ * selects the first item. If there are no items at all, does nothing.
+ */
+ public void moveSelectionUp() {
+ if (selectFirstItemIfNoneSelected()) {
+ return;
+ }
+
+ if ((shownChildMenu == null) && vertical) {
+ selectPrevItem();
+ } else if ((parentMenu != null) && parentMenu.vertical) {
+ parentMenu.selectPrevItem();
+ } else {
+ close(true);
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ MenuItem item = findItem(DOM.eventGetTarget(event));
+ switch (DOM.eventGetType(event)) {
+ case Event.ONCLICK: {
+ FocusPanel.impl.focus(getElement());
+ // Fire an item's command when the user clicks on it.
+ if (item != null) {
+ doItemAction(item, true, true);
+ }
+ break;
+ }
+
+ case Event.ONMOUSEOVER: {
+ if (item != null) {
+ itemOver(item, true);
+ }
+ break;
+ }
+
+ case Event.ONMOUSEOUT: {
+ if (item != null) {
+ itemOver(null, true);
+ }
+ break;
+ }
+
+ case Event.ONFOCUS: {
+ selectFirstItemIfNoneSelected();
+ break;
+ }
+
+ case Event.ONKEYDOWN: {
+ int keyCode = DOM.eventGetKeyCode(event);
+ switch (keyCode) {
+ case KeyCodes.KEY_LEFT:
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ moveToNextItem();
+ } else {
+ moveToPrevItem();
+ }
+ eatEvent(event);
+ break;
+ case KeyCodes.KEY_RIGHT:
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ moveToPrevItem();
+ } else {
+ moveToNextItem();
+ }
+ eatEvent(event);
+ break;
+ case KeyCodes.KEY_UP:
+ moveSelectionUp();
+ eatEvent(event);
+ break;
+ case KeyCodes.KEY_DOWN:
+ moveSelectionDown();
+ eatEvent(event);
+ break;
+ case KeyCodes.KEY_ESCAPE:
+ closeAllParentsAndChildren();
+ eatEvent(event);
+ break;
+ case KeyCodes.KEY_TAB:
+ closeAllParentsAndChildren();
+ break;
+ case KeyCodes.KEY_ENTER:
+ if (!selectFirstItemIfNoneSelected()) {
+ doItemAction(selectedItem, true, true);
+ eatEvent(event);
+ }
+ break;
+ } // end switch(keyCode)
+
+ break;
+ } // end case Event.ONKEYDOWN
+ } // end switch (DOM.eventGetType(event))
+ super.onBrowserEvent(event);
+ }
+
+ /**
+ * Closes the menu bar.
+ *
+ * @deprecated Use {@link #addCloseHandler(CloseHandler)} instead
+ */
+ @Deprecated
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+ // If the menu popup was auto-closed, close all of its parents as well.
+ if (autoClosed) {
+ closeAllParents();
+ }
+
+ // When the menu popup closes, remember that no item is
+ // currently showing a popup menu.
+ onHide(!autoClosed);
+ CloseEvent.fire(MenuBar.this, sender);
+ shownChildMenu = null;
+ popup = null;
+ if (parentMenu != null && parentMenu.popup != null) {
+ parentMenu.popup.setPreviewingAllNativeEvents(true);
+ }
+ }
+
+ /**
+ * Removes the specified menu item from the bar.
+ *
+ * @param item the item to be removed
+ */
+ public void removeItem(MenuItem item) {
+ // Unselect if the item is currently selected
+ if (selectedItem == item) {
+ selectItem(null);
+ }
+
+ if (removeItemElement(item)) {
+ setItemColSpan(item, 1);
+ items.remove(item);
+ item.setParentMenu(null);
+ }
+ }
+
+ /**
+ * Removes the specified {@link MenuItemSeparator} from the bar.
+ *
+ * @param separator the separator to be removed
+ */
+ public void removeSeparator(MenuItemSeparator separator) {
+ if (removeItemElement(separator)) {
+ separator.setParentMenu(null);
+ }
+ }
+
+ /**
+ * Select the given MenuItem, which must be a direct child of this MenuBar.
+ *
+ * @param item the MenuItem to select, or null to clear selection
+ */
+ public void selectItem(MenuItem item) {
+ assert item == null || item.getParentMenu() == this;
+
+ if (item == selectedItem) {
+ return;
+ }
+
+ if (selectedItem != null) {
+ selectedItem.setSelectionStyle(false);
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(selectedItem.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", false);
+ }
+ }
+
+ if (vertical
+ && shownChildMenu != null
+ && shownChildMenu == selectedItem.getSubMenu())
+ {
+ shownChildMenu.onHide(false);
+ popup.hide();
+ shownChildMenu = null;
+ }
+ }
+
+ if (item != null) {
+ item.setSelectionStyle(true);
+
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(item.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", true);
+ }
+ }
+
+ Accessibility.setState(getElement(),
+ Accessibility.STATE_ACTIVEDESCENDANT, DOM.getElementAttribute(
+ item.getElement(), "id"));
+ }
+
+ selectedItem = item;
+ }
+
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
+ * Sets whether this menu bar's child menus will open when the mouse is moved
+ * over it.
+ *
+ * @param autoOpen <code>true</code> to cause child menus to auto-open
+ */
+ public void setAutoOpen(boolean autoOpen) {
+ this.autoOpen = autoOpen;
+ }
+
+ /**
+ * Enable or disable auto focus when the mouse hovers over the MenuBar. This
+ * allows the MenuBar to respond to keyboard events without the user having to
+ * click on it, but it will steal focus from other elements on the page.
+ * Enabled by default.
+ *
+ * @param enabled true to enable, false to disable
+ */
+ public void setFocusOnHoverEnabled(boolean enabled) {
+ focusOnHover = enabled;
+ }
+
+ /**
+ * Returns a list containing the <code>MenuItem</code> objects in the menu
+ * bar. If there are no items in the menu bar, then an empty <code>List</code>
+ * object will be returned.
+ *
+ * @return a list containing the <code>MenuItem</code> objects in the menu bar
+ */
+ protected List<MenuItem> getItems() {
+ return this.items;
+ }
+
+ /**
+ * Returns the <code>MenuItem</code> that is currently selected (highlighted)
+ * by the user. If none of the items in the menu are currently selected, then
+ * <code>null</code> will be returned.
+ *
+ * @return the <code>MenuItem</code> that is currently selected, or
+ * <code>null</code> if no items are currently selected
+ */
+ protected MenuItem getSelectedItem() {
+ return this.selectedItem;
+ }
+
+ @Override
+ protected void onDetach() {
+ // When the menu is detached, make sure to close all of its children.
+ if (popup != null) {
+ popup.hide();
+ }
+
+ super.onDetach();
+ }
+
+ /**
+ * <b>Affected Elements:</b>
+ * <ul>
+ * <li>-item# = the {@link MenuItem} at the specified index.</li>
+ * </ul>
+ *
+ * @see UIObject#onEnsureDebugId(String)
+ */
+ @Override
+ protected void onEnsureDebugId(String baseID) {
+ super.onEnsureDebugId(baseID);
+ setMenuItemDebugIds(baseID);
+ }
+
+ /*
+ * Closes all parent menu popups.
+ */
+ void closeAllParents() {
+ if (parentMenu != null) {
+ // The parent menu will recursively call closeAllParents.
+ close(false);
+ } else {
+ // If this is the top most menu, deselect the current item.
+ selectItem(null);
+ }
+ }
+
+ /**
+ * Closes all parent and child menu popups.
+ */
+ void closeAllParentsAndChildren() {
+ closeAllParents();
+ // Ensure the popup is closed even if it has not been enetered
+ // with the mouse or key navigation
+ if (parentMenu == null && popup != null) {
+ popup.hide();
+ }
+ }
+
+ /*
+ * Performs the action associated with the given menu item. If the item has a
+ * popup associated with it, the popup will be shown. If it has a command
+ * associated with it, and 'fireCommand' is true, then the command will be
+ * fired. Popups associated with other items will be hidden.
+ *
+ * @param item the item whose popup is to be shown. @param fireCommand
+ * <code>true</code> if the item's command should be fired, <code>false</code>
+ * otherwise.
+ */
+ void doItemAction(final MenuItem item, boolean fireCommand, boolean focus) {
+ // Should not perform any action if the item is disabled
+ if (!item.isEnabled()) {
+ return;
+ }
+
+ // Ensure that the item is selected.
+ selectItem(item);
+
+ if (item != null) {
+ // if the command should be fired and the item has one, fire it
+ if (fireCommand && item.getCommand() != null) {
+ // Close this menu and all of its parents.
+ closeAllParents();
+
+ // Fire the item's command. The command must be fired in the same event
+ // loop or popup blockers will prevent popups from opening.
+ final Command cmd = item.getCommand();
+ Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ cmd.execute();
+ }
+ });
+
+ // hide any open submenus of this item
+ if (shownChildMenu != null) {
+ shownChildMenu.onHide(focus);
+ popup.hide();
+ shownChildMenu = null;
+ selectItem(null);
+ }
+ } else if (item.getSubMenu() != null) {
+ if (shownChildMenu == null) {
+ // open this submenu
+ openPopup(item);
+ } else if (item.getSubMenu() != shownChildMenu) {
+ // close the other submenu and open this one
+ shownChildMenu.onHide(focus);
+ popup.hide();
+ openPopup(item);
+ } else if (fireCommand && !autoOpen) {
+ // close this submenu
+ shownChildMenu.onHide(focus);
+ popup.hide();
+ shownChildMenu = null;
+ selectItem(item);
+ }
+ } else if (autoOpen && shownChildMenu != null) {
+ // close submenu
+ shownChildMenu.onHide(focus);
+ popup.hide();
+ shownChildMenu = null;
+ }
+ }
+ }
+
+ /**
+ * Visible for testing.
+ */
+ PopupPanel getPopup() {
+ return popup;
+ }
+
+ void itemOver(MenuItem item, boolean focus) {
+ if (item == null) {
+ // Don't clear selection if the currently selected item's menu is showing.
+ if ((selectedItem != null)
+ && (shownChildMenu == selectedItem.getSubMenu())) {
+ return;
+ }
+ }
+
+ if (item != null && !item.isEnabled()) {
+ return;
+ }
+
+ // Style the item selected when the mouse enters.
+ selectItem(item);
+ if (focus && focusOnHover) {
+ focus();
+ }
+
+ // If child menus are being shown, or this menu is itself
+ // a child menu, automatically show an item's child menu
+ // when the mouse enters.
+ if (item != null) {
+ if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) {
+ doItemAction(item, false, focusOnHover);
+ }
+ }
+ }
+
+ /**
+ * Set the IDs of the menu items.
+ *
+ * @param baseID the base ID
+ */
+ void setMenuItemDebugIds(String baseID) {
+ int itemCount = 0;
+ for (MenuItem item : items) {
+ item.ensureDebugId(baseID + "-item" + itemCount);
+ itemCount++;
+ }
+ }
+
+ /**
+ * Show or hide the icon used for items with a submenu.
+ *
+ * @param item the item with or without a submenu
+ */
+ protected void updateSubmenuIcon(MenuItem item) {
+ // The submenu icon only applies to vertical menus
+ if (!vertical) {
+ return;
+ }
+
+ // Get the index of the MenuItem
+ int idx = allItems.indexOf(item);
+ if (idx == -1) {
+ return;
+ }
+
+ Element container = getItemContainerElement();
+ Element tr = DOM.getChild(container, idx);
+ int tdCount = DOM.getChildCount(tr);
+ MenuBar submenu = item.getSubMenu();
+ if (submenu == null || !item.isVisible()) {
+ // Remove the submenu indicator
+ if (tdCount == 2) {
+ DOM.removeChild(tr, DOM.getChild(tr, 1));
+ }
+ setItemColSpan(item, 2);
+ } else if (tdCount == 1) {
+ // Show the submenu indicator
+ setItemColSpan(item, 1);
+ Element td = DOM.createTD();
+ DOM.setElementProperty(td, "vAlign", "middle");
+ DOM.setInnerHTML(td, subMenuIcon.getHTML());
+ setStyleName(td, "subMenuIcon");
+ DOM.appendChild(tr, td);
+ }
+ }
+
+ /**
+ * Physically add the td element of a {@link MenuItem} or
+ * {@link MenuItemSeparator} to this {@link MenuBar}.
+ *
+ * @param beforeIndex the index where the separator should be inserted
+ * @param tdElem the td element to be added
+ */
+ private void addItemElement(int beforeIndex, Element tdElem) {
+ if (vertical) {
+ Element tr = DOM.createTR();
+ DOM.insertChild(body, tr, beforeIndex);
+ DOM.appendChild(tr, tdElem);
+ } else {
+ Element tr = DOM.getChild(body, 0);
+ DOM.insertChild(tr, tdElem, beforeIndex);
+ }
+ }
+
+ /**
+ * Closes this menu (if it is a popup).
+ *
+ * @param focus true to move focus to the parent
+ */
+ private void close(boolean focus) {
+ if (parentMenu != null) {
+ parentMenu.popup.hide(!focus);
+ if (focus) {
+ parentMenu.focus();
+ }
+ }
+ }
+
+ private void eatEvent(Event event) {
+ DOM.eventCancelBubble(event, true);
+ DOM.eventPreventDefault(event);
+ }
+
+ private MenuItem findItem(Element hItem) {
+ for (MenuItem item : items) {
+ if (DOM.isOrHasChild(item.getElement(), hItem)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ private Element getItemContainerElement() {
+ if (vertical) {
+ return body;
+ } else {
+ return DOM.getChild(body, 0);
+ }
+ }
+
+ private void init(boolean vertical, AbstractImagePrototype subMenuIcon) {
+ this.subMenuIcon = subMenuIcon;
+
+ Element table = DOM.createTable();
+ body = DOM.createTBody();
+ DOM.appendChild(table, body);
+
+ if (!vertical) {
+ Element tr = DOM.createTR();
+ DOM.appendChild(body, tr);
+ }
+
+ this.vertical = vertical;
+
+ Element outer = FocusPanel.impl.createFocusable();
+ DOM.appendChild(outer, table);
+ setElement(outer);
+
+ Accessibility.setRole(getElement(), Accessibility.ROLE_MENUBAR);
+
+ sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
+ | Event.ONFOCUS | Event.ONKEYDOWN);
+
+ setStyleName(STYLENAME_DEFAULT);
+ if (vertical) {
+ addStyleDependentName("vertical");
+ } else {
+ addStyleDependentName("horizontal");
+ }
+
+ // Hide focus outline in Mozilla/Webkit/Opera
+ DOM.setStyleAttribute(getElement(), "outline", "0px");
+
+ // Hide focus outline in IE 6/7
+ DOM.setElementAttribute(getElement(), "hideFocus", "true");
+
+ // Deselect items when blurring without a child menu.
+ addDomHandler(new BlurHandler() {
+ public void onBlur(BlurEvent event) {
+ if (shownChildMenu == null) {
+ selectItem(null);
+ }
+ }
+ }, BlurEvent.getType());
+ }
+
+ private void moveToNextItem() {
+ if (selectFirstItemIfNoneSelected()) {
+ return;
+ }
+
+ if (!vertical) {
+ selectNextItem();
+ } else {
+ if (selectedItem.getSubMenu() != null
+ && !selectedItem.getSubMenu().getItems().isEmpty()
+ && (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
+ if (shownChildMenu == null) {
+ doItemAction(selectedItem, false, true);
+ }
+ selectedItem.getSubMenu().focus();
+ } else if (parentMenu != null) {
+ if (!parentMenu.vertical) {
+ parentMenu.selectNextItem();
+ } else {
+ parentMenu.moveToNextItem();
+ }
+ }
+ }
+ }
+
+ private void moveToPrevItem() {
+ if (selectFirstItemIfNoneSelected()) {
+ return;
+ }
+
+ if (!vertical) {
+ selectPrevItem();
+ } else {
+ if ((parentMenu != null) && (!parentMenu.vertical)) {
+ parentMenu.selectPrevItem();
+ } else {
+ close(true);
+ }
+ }
+ }
+
+ /*
+ * This method is called when a menu bar is hidden, so that it can hide any
+ * child popups that are currently being shown.
+ */
+ private void onHide(boolean focus) {
+ if (shownChildMenu != null) {
+ shownChildMenu.onHide(focus);
+ popup.hide();
+ if (focus) {
+ focus();
+ }
+ }
+ }
+
+ /*
+ * This method is called when a menu bar is shown.
+ */
+ private void onShow() {
+ // clear the selection; a keyboard user can cursor down to the first item
+ selectItem(null);
+ }
+
+ private void openPopup(final MenuItem item) {
+ // Only the last popup to be opened should preview all event
+ if (parentMenu != null && parentMenu.popup != null) {
+ parentMenu.popup.setPreviewingAllNativeEvents(false);
+ }
+
+ // Create a new popup for this item, and position it next to
+ // the item (below if this is a horizontal menu bar, to the
+ // right if it's a vertical bar).
+ popup = new DecoratedPopupPanel(true, false, "menuPopup") {
+ {
+ setWidget(item.getSubMenu());
+ setPreviewingAllNativeEvents(true);
+ item.getSubMenu().onShow();
+ }
+
+ @Override
+ protected void onPreviewNativeEvent(NativePreviewEvent event) {
+ // Hook the popup panel's event preview. We use this to keep it from
+ // auto-hiding when the parent menu is clicked.
+ if (!event.isCanceled()) {
+
+ switch (event.getTypeInt()) {
+ case Event.ONMOUSEDOWN:
+ // If the event target is part of the parent menu, suppress the
+ // event altogether.
+ EventTarget target = event.getNativeEvent().getEventTarget();
+ Element parentMenuElement = item.getParentMenu().getElement();
+ if (parentMenuElement.isOrHasChild(Element.as(target))) {
+ event.cancel();
+ return;
+ }
+ super.onPreviewNativeEvent(event);
+ if (event.isCanceled()) {
+ selectItem(null);
+ }
+ return;
+ }
+ }
+ super.onPreviewNativeEvent(event);
+ }
+ };
+ popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
+ popup.setAnimationEnabled(isAnimationEnabled);
+ popup.setStyleName(STYLENAME_DEFAULT + "Popup");
+ String primaryStyleName = getStylePrimaryName();
+ if (!STYLENAME_DEFAULT.equals(primaryStyleName)) {
+ popup.addStyleName(primaryStyleName + "Popup");
+ }
+ popup.addPopupListener(this);
+
+ shownChildMenu = item.getSubMenu();
+ item.getSubMenu().parentMenu = this;
+
+ // Show the popup, ensuring that the menubar's event preview remains on top
+ // of the popup's.
+ popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
+
+ public void setPosition(int offsetWidth, int offsetHeight) {
+
+ // depending on the bidi direction position a menu on the left or right
+ // of its base item
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ if (vertical) {
+ popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() - offsetWidth
+ + 1, item.getAbsoluteTop());
+ } else {
+ popup.setPopupPosition(item.getAbsoluteLeft()
+ + item.getOffsetWidth() - offsetWidth,
+ MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
+ - 1);
+ }
+ } else {
+ if (vertical) {
+ popup.setPopupPosition(MenuBar.this.getAbsoluteLeft()
+ + MenuBar.this.getOffsetWidth() - 1, item.getAbsoluteTop());
+ } else {
+ popup.setPopupPosition(item.getAbsoluteLeft(),
+ MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight()
+ - 1);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes the specified item from the {@link MenuBar} and the physical DOM
+ * structure.
+ *
+ * @param item the item to be removed
+ * @return true if the item was removed
+ */
+ private boolean removeItemElement(UIObject item) {
+ int idx = allItems.indexOf(item);
+ if (idx == -1) {
+ return false;
+ }
+
+ Element container = getItemContainerElement();
+ DOM.removeChild(container, DOM.getChild(container, idx));
+ allItems.remove(idx);
+ return true;
+ }
+
+ /**
+ * Selects the first item in the menu if no items are currently selected. Has
+ * no effect if there are no items.
+ *
+ * @return true if no item was previously selected, false otherwise
+ */
+ private boolean selectFirstItemIfNoneSelected() {
+ if (selectedItem == null) {
+ for (MenuItem nextItem : items) {
+ if (nextItem.isEnabled()) {
+ selectItem(nextItem);
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void selectNextItem() {
+ if (selectedItem == null) {
+ return;
+ }
+
+ int index = items.indexOf(selectedItem);
+ // We know that selectedItem is set to an item that is contained in the
+ // items collection.
+ // Therefore, we know that index can never be -1.
+ assert (index != -1);
+
+ MenuItem itemToBeSelected;
+
+ int firstIndex = index;
+ while (true) {
+ index = index + 1;
+ if (index == items.size()) {
+ // we're at the end, loop around to the start
+ index = 0;
+ }
+ if (index == firstIndex) {
+ itemToBeSelected = items.get(firstIndex);
+ break;
+ } else {
+ itemToBeSelected = items.get(index);
+ if (itemToBeSelected.isEnabled()) {
+ break;
+ }
+ }
+ }
+
+ selectItem(itemToBeSelected);
+ if (shownChildMenu != null) {
+ doItemAction(itemToBeSelected, false, true);
+ }
+ }
+
+ private void selectPrevItem() {
+ if (selectedItem == null) {
+ return;
+ }
+
+ int index = items.indexOf(selectedItem);
+ // We know that selectedItem is set to an item that is contained in the
+ // items collection.
+ // Therefore, we know that index can never be -1.
+ assert (index != -1);
+
+ MenuItem itemToBeSelected;
+
+ int firstIndex = index;
+ while (true) {
+ index = index - 1;
+ if (index < 0) {
+ // we're at the start, loop around to the end
+ index = items.size() - 1;
+ }
+ if (index == firstIndex) {
+ itemToBeSelected = items.get(firstIndex);
+ break;
+ } else {
+ itemToBeSelected = items.get(index);
+ if (itemToBeSelected.isEnabled()) {
+ break;
+ }
+ }
+ }
+
+ selectItem(itemToBeSelected);
+ if (shownChildMenu != null) {
+ doItemAction(itemToBeSelected, false, true);
+ }
+ }
+
+ /**
+ * Set the colspan of a {@link MenuItem} or {@link MenuItemSeparator}.
+ *
+ * @param item the {@link MenuItem} or {@link MenuItemSeparator}
+ * @param colspan the colspan
+ */
+ private void setItemColSpan(UIObject item, int colspan) {
+ DOM.setElementPropertyInt(item.getElement(), "colSpan", colspan);
+ }
+}
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java.diff b/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java.diff
new file mode 100644
index 0000000..445b04f
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/MenuBar.java.diff
@@ -0,0 +1,53 @@
+diff --git a/src/client/src/com/google/gwt/user/client/ui/MenuBar.java b/src/client/src/com/google/gwt/user/client/ui/MenuBar.java
+index 72b373f..961bccb 100644
+--- a/src/client/src/com/google/gwt/user/client/ui/MenuBar.java
++++ b/src/client/src/com/google/gwt/user/client/ui/MenuBar.java
+@@ -676,40 +676,48 @@ public class MenuBar extends Widget implements PopupListener, HasAnimation,
+ * Select the given MenuItem, which must be a direct child of this MenuBar.
+ *
+ * @param item the MenuItem to select, or null to clear selection
+ */
+ public void selectItem(MenuItem item) {
+ assert item == null || item.getParentMenu() == this;
+
+ if (item == selectedItem) {
+ return;
+ }
+
+ if (selectedItem != null) {
+ selectedItem.setSelectionStyle(false);
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(selectedItem.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", false);
+ }
++
++ if (shownChildMenu != null
++ && shownChildMenu == selectedItem.getSubMenu())
++ {
++ shownChildMenu.onHide(false);
++ popup.hide();
++ shownChildMenu = null;
++ }
+ }
+ }
+
+ if (item != null) {
+ item.setSelectionStyle(true);
+
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(item.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", true);
+ }
+ }
+
+ Accessibility.setState(getElement(),
+ Accessibility.STATE_ACTIVEDESCENDANT, DOM.getElementAttribute(
+ item.getElement(), "id"));
+ }
+
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
new file mode 100644
index 0000000..9c988f9
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+
+/**
+ * A panel that adds user-positioned splitters between each of its child
+ * widgets.
+ *
+ * <p>
+ * This panel is used in the same way as {@link DockLayoutPanel}, except that
+ * its children's sizes are always specified in {@link Unit#PX} units, and each
+ * pair of child widgets has a splitter between them that the user can drag.
+ * </p>
+ *
+ * <p>
+ * This widget will <em>only</em> work in standards mode, which requires that
+ * the HTML page in which it is run have an explicit <!DOCTYPE>
+ * declaration.
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-SplitLayoutPanel { the panel itself }</li>
+ * <li>.gwt-SplitLayoutPanel .gwt-SplitLayoutPanel-HDragger { horizontal dragger
+ * }</li>
+ * <li>.gwt-SplitLayoutPanel .gwt-SplitLayoutPanel-VDragger { vertical dragger }
+ * </li>
+ * </ul>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.SplitLayoutPanelExample}
+ * </p>
+ */
+public class SplitLayoutPanel extends DockLayoutPanel {
+
+ class HSplitter extends Splitter {
+ public HSplitter(Widget target, boolean reverse) {
+ super(target, reverse);
+ getElement().getStyle().setPropertyPx("width", splitterSize);
+ setStyleName("gwt-SplitLayoutPanel-HDragger");
+ }
+
+ @Override
+ protected int getAbsolutePosition() {
+ return getAbsoluteLeft();
+ }
+
+ @Override
+ protected double getCenterSize() {
+ return getCenterWidth();
+ }
+
+ @Override
+ protected int getEventPosition(Event event) {
+ return event.getClientX();
+ }
+
+ @Override
+ protected int getTargetPosition() {
+ return target.getAbsoluteLeft();
+ }
+
+ @Override
+ protected int getTargetSize() {
+ return target.getOffsetWidth();
+ }
+ }
+
+ abstract class Splitter extends Widget {
+ protected final Widget target;
+
+ private int offset;
+ private boolean mouseDown;
+ private ScheduledCommand layoutCommand;
+
+ private final boolean reverse;
+ private int minSize;
+
+ private double centerSize, syncedCenterSize;
+
+ public Splitter(Widget target, boolean reverse) {
+ this.target = target;
+ this.reverse = reverse;
+
+ setElement(Document.get().createDivElement());
+ sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEMOVE
+ | Event.ONDBLCLICK);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ switch (event.getTypeInt()) {
+ case Event.ONMOUSEDOWN:
+ mouseDown = true;
+
+ /*
+ * Resize glassElem to take up the entire scrollable window area,
+ * which is the greater of the scroll size and the client size.
+ */
+ int width = Math.max(Window.getClientWidth(),
+ Document.get().getScrollWidth());
+ int height = Math.max(Window.getClientHeight(),
+ Document.get().getScrollHeight());
+ glassElem.getStyle().setHeight(height, Unit.PX);
+ glassElem.getStyle().setWidth(width, Unit.PX);
+ Document.get().getBody().appendChild(glassElem);
+
+ offset = getEventPosition(event) - getAbsolutePosition();
+ Event.setCapture(getElement());
+ event.preventDefault();
+
+ fireEvent(new SplitterBeforeResizeEvent());
+
+ break;
+
+ case Event.ONMOUSEUP:
+ mouseDown = false;
+
+ glassElem.removeFromParent();
+
+ Event.releaseCapture(getElement());
+ event.preventDefault();
+
+ fireEvent(new SplitterResizedEvent());
+
+ break;
+
+ case Event.ONMOUSEMOVE:
+ if (mouseDown) {
+ int size;
+ if (reverse) {
+ size = getTargetPosition() + getTargetSize()
+ - getEventPosition(event) - offset;
+ } else {
+ size = getEventPosition(event) - getTargetPosition() - offset;
+ }
+ setAssociatedWidgetSize(size);
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ public void setMinSize(int minSize) {
+ this.minSize = minSize;
+ LayoutData layout = (LayoutData) target.getLayoutData();
+
+ // Try resetting the associated widget's size, which will enforce the new
+ // minSize value.
+ setAssociatedWidgetSize((int) layout.size);
+ }
+
+ protected abstract int getAbsolutePosition();
+
+ protected abstract double getCenterSize();
+
+ protected abstract int getEventPosition(Event event);
+
+ protected abstract int getTargetPosition();
+
+ protected abstract int getTargetSize();
+
+ private double getMaxSize() {
+ // To avoid seeing stale center size values due to deferred layout
+ // updates, maintain our own copy up to date and resync when the
+ // DockLayoutPanel value changes.
+ double newCenterSize = getCenterSize();
+ if (syncedCenterSize != newCenterSize) {
+ syncedCenterSize = newCenterSize;
+ centerSize = newCenterSize;
+ }
+
+ return Math.max(((LayoutData) target.getLayoutData()).size + centerSize,
+ 0);
+ }
+
+ private void setAssociatedWidgetSize(double size) {
+ double maxSize = getMaxSize();
+ if (size > maxSize) {
+ size = maxSize;
+ }
+
+ if (size < minSize) {
+ size = minSize;
+ }
+
+ LayoutData layout = (LayoutData) target.getLayoutData();
+ if (size == layout.size) {
+ return;
+ }
+
+ // Adjust our view until the deferred layout gets scheduled.
+ centerSize += layout.size - size;
+ layout.size = size;
+
+ // Defer actually updating the layout, so that if we receive many
+ // mouse events before layout/paint occurs, we'll only update once.
+ if (layoutCommand == null) {
+ layoutCommand = new Command() {
+ public void execute() {
+ layoutCommand = null;
+ forceLayout();
+ }
+ };
+ Scheduler.get().scheduleDeferred(layoutCommand);
+ }
+ }
+ }
+
+ class VSplitter extends Splitter {
+ public VSplitter(Widget target, boolean reverse) {
+ super(target, reverse);
+ getElement().getStyle().setPropertyPx("height", splitterSize);
+ setStyleName("gwt-SplitLayoutPanel-VDragger");
+ }
+
+ @Override
+ protected int getAbsolutePosition() {
+ return getAbsoluteTop();
+ }
+
+ @Override
+ protected double getCenterSize() {
+ return getCenterHeight();
+ }
+
+ @Override
+ protected int getEventPosition(Event event) {
+ return event.getClientY();
+ }
+
+ @Override
+ protected int getTargetPosition() {
+ return target.getAbsoluteTop();
+ }
+
+ @Override
+ protected int getTargetSize() {
+ return target.getOffsetHeight();
+ }
+ }
+
+ private static final int DEFAULT_SPLITTER_SIZE = 8;
+
+ /**
+ * The element that masks the screen so we can catch mouse events over
+ * iframes.
+ */
+ private static Element glassElem = null;
+
+ private final int splitterSize;
+
+ /**
+ * Construct a new {@link SplitLayoutPanel} with the default splitter size of
+ * 8px.
+ */
+ public SplitLayoutPanel() {
+ this(DEFAULT_SPLITTER_SIZE);
+ }
+
+ /**
+ * Construct a new {@link SplitLayoutPanel} with the specified splitter size
+ * in pixels.
+ *
+ * @param splitterSize the size of the splitter in pixels
+ */
+ public SplitLayoutPanel(int splitterSize) {
+ super(Unit.PX);
+ this.splitterSize = splitterSize;
+ setStyleName("gwt-SplitLayoutPanel");
+
+ if (glassElem == null) {
+ glassElem = Document.get().createDivElement();
+ glassElem.getStyle().setPosition(Position.ABSOLUTE);
+ glassElem.getStyle().setTop(0, Unit.PX);
+ glassElem.getStyle().setLeft(0, Unit.PX);
+ glassElem.getStyle().setMargin(0, Unit.PX);
+ glassElem.getStyle().setPadding(0, Unit.PX);
+ glassElem.getStyle().setBorderWidth(0, Unit.PX);
+
+ // We need to set the background color or mouse events will go right
+ // through the glassElem. If the SplitPanel contains an iframe, the
+ // iframe will capture the event and the slider will stop moving.
+ glassElem.getStyle().setProperty("background", "white");
+ glassElem.getStyle().setOpacity(0.0);
+ }
+ }
+
+ /**
+ * Return the size of the splitter in pixels.
+ *
+ * @return the splitter size
+ */
+ public int getSplitterSize() {
+ return splitterSize;
+ }
+
+ @Override
+ public void insert(Widget child, Direction direction, double size, Widget before) {
+ super.insert(child, direction, size, before);
+ if (direction != Direction.CENTER) {
+ insertSplitter(child, before);
+ }
+ }
+
+ @Override
+ public boolean remove(Widget child) {
+ assert !(child instanceof Splitter) : "Splitters may not be directly removed";
+
+ int idx = getWidgetIndex(child);
+ if (super.remove(child)) {
+ // Remove the associated splitter, if any.
+ // Now that the widget is removed, idx is the index of the splitter.
+ if (idx < getWidgetCount()) {
+ // Call super.remove(), or we'll end up recursing.
+ super.remove(getWidget(idx));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the minimum allowable size for the given widget.
+ *
+ * <p>
+ * Its associated splitter cannot be dragged to a position that would make it
+ * smaller than this size. This method has no effect for the
+ * {@link DockLayoutPanel.Direction#CENTER} widget.
+ * </p>
+ *
+ * @param child the child whose minimum size will be set
+ * @param minSize the minimum size for this widget
+ */
+ public void setWidgetMinSize(Widget child, int minSize) {
+ assertIsChild(child);
+ Splitter splitter = getAssociatedSplitter(child);
+ // The splitter is null for the center element.
+ if (splitter != null) {
+ splitter.setMinSize(minSize);
+ }
+ }
+
+ private Splitter getAssociatedSplitter(Widget child) {
+ // If a widget has a next sibling, it must be a splitter, because the only
+ // widget that *isn't* followed by a splitter must be the CENTER, which has
+ // no associated splitter.
+ int idx = getWidgetIndex(child);
+ if (idx > -1 && idx < getWidgetCount() - 1) {
+ Widget splitter = getWidget(idx + 1);
+ assert splitter instanceof Splitter : "Expected child widget to be splitter";
+ return (Splitter) splitter;
+ }
+ return null;
+ }
+
+ private void insertSplitter(Widget widget, Widget before) {
+ assert getChildren().size() > 0 : "Can't add a splitter before any children";
+
+ LayoutData layout = (LayoutData) widget.getLayoutData();
+ Splitter splitter = null;
+ switch (getResolvedDirection(layout.direction)) {
+ case WEST:
+ splitter = new HSplitter(widget, false);
+ break;
+ case EAST:
+ splitter = new HSplitter(widget, true);
+ break;
+ case NORTH:
+ splitter = new VSplitter(widget, false);
+ break;
+ case SOUTH:
+ splitter = new VSplitter(widget, true);
+ break;
+ default:
+ assert false : "Unexpected direction";
+ }
+
+ splitter.addHandler(new SplitterBeforeResizeHandler() {
+ public void onSplitterBeforeResize(SplitterBeforeResizeEvent event)
+ {
+ delegateEvent(SplitLayoutPanel.this, event);
+ }
+ }, SplitterBeforeResizeEvent.TYPE);
+ splitter.addHandler(new SplitterResizedHandler() {
+ public void onSplitterResized(SplitterResizedEvent event)
+ {
+ delegateEvent(SplitLayoutPanel.this, event);
+ }
+ }, SplitterResizedEvent.TYPE);
+
+ super.insert(splitter, layout.direction, splitterSize, before);
+ }
+
+ public HandlerRegistration addSplitterBeforeResizeHandler(SplitterBeforeResizeHandler handler)
+ {
+ return addHandler(handler, SplitterBeforeResizeEvent.TYPE);
+ }
+
+ public HandlerRegistration addSplitterResizedHandler(SplitterResizedHandler handler)
+ {
+ return addHandler(handler, SplitterResizedEvent.TYPE);
+ }
+}
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitPanel.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitPanel.java
new file mode 100644
index 0000000..8005252
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitPanel.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+
+import java.util.Iterator;
+
+/**
+ * Abstract base class for {@link HorizontalSplitPanel} and
+ * {@link VerticalSplitPanel}.
+ *
+ * @deprecated Use {@link SplitLayoutPanel} instead, but understand that it is
+ * not a drop in replacement for this class. It requires standards
+ * mode, and is most easily used under a {@link RootLayoutPanel} (as
+ * opposed to a {@link RootPanel}
+ */
+ at Deprecated
+abstract class SplitPanel extends Panel {
+ /**
+ * The element that masks the screen so we can catch mouse events over
+ * iframes.
+ */
+ private static Element glassElem = null;
+
+ /**
+ * Sets an elements positioning to absolute.
+ *
+ * @param elem the element
+ */
+ static void addAbsolutePositoning(Element elem) {
+ DOM.setStyleAttribute(elem, "position", "absolute");
+ }
+
+ /**
+ * Adds clipping to an element.
+ *
+ * @param elem the element
+ */
+ static final void addClipping(final Element elem) {
+ DOM.setStyleAttribute(elem, "overflow", "hidden");
+ }
+
+ /**
+ * Adds as-needed scrolling to an element.
+ *
+ * @param elem the element
+ */
+ static final void addScrolling(final Element elem) {
+ DOM.setStyleAttribute(elem, "overflow", "auto");
+ }
+
+ /**
+ * Sizes and element to consume the full area of its parent using the CSS
+ * properties left, right, top, and bottom. This method is used for all
+ * browsers except IE6/7.
+ *
+ * @param elem the element
+ */
+ static final void expandToFitParentUsingCssOffsets(Element elem) {
+ final String zeroSize = "0px";
+
+ addAbsolutePositoning(elem);
+ setLeft(elem, zeroSize);
+ setRight(elem, zeroSize);
+ setTop(elem, zeroSize);
+ setBottom(elem, zeroSize);
+ }
+
+ /**
+ * Sizes an element to consume the full areas of its parent using 100% width
+ * and height. This method is used on IE6/7 where CSS offsets don't work
+ * reliably.
+ *
+ * @param elem the element
+ */
+ static final void expandToFitParentUsingPercentages(Element elem) {
+ final String zeroSize = "0px";
+ final String fullSize = "100%";
+
+ addAbsolutePositoning(elem);
+ setTop(elem, zeroSize);
+ setLeft(elem, zeroSize);
+ setWidth(elem, fullSize);
+ setHeight(elem, fullSize);
+ }
+
+ /**
+ * Returns the offsetHeight element property.
+ *
+ * @param elem the element
+ * @return the offsetHeight property
+ */
+ static final int getOffsetHeight(Element elem) {
+ return DOM.getElementPropertyInt(elem, "offsetHeight");
+ }
+
+ /**
+ * Returns the offsetWidth element property.
+ *
+ * @param elem the element
+ * @return the offsetWidth property
+ */
+ static final int getOffsetWidth(Element elem) {
+ return DOM.getElementPropertyInt(elem, "offsetWidth");
+ }
+
+ /**
+ * Adds zero or none CSS values for padding, margin and border to prevent
+ * stylesheet overrides. Returns the element for convenience to support
+ * builder pattern.
+ *
+ * @param elem the element
+ * @return the element
+ */
+ static final Element preventBoxStyles(final Element elem) {
+ DOM.setIntStyleAttribute(elem, "padding", 0);
+ DOM.setIntStyleAttribute(elem, "margin", 0);
+ DOM.setStyleAttribute(elem, "border", "none");
+ return elem;
+ }
+
+ /**
+ * Convenience method to set bottom offset of an element.
+ *
+ * @param elem the element
+ * @param size a CSS length value for bottom
+ */
+ static void setBottom(Element elem, String size) {
+ DOM.setStyleAttribute(elem, "bottom", size);
+ }
+
+ /**
+ * Sets the elements css class name.
+ *
+ * @param elem the element
+ * @param className the class name
+ */
+ static final void setClassname(final Element elem, final String className) {
+ DOM.setElementProperty(elem, "className", className);
+ }
+
+ /**
+ * Convenience method to set the height of an element.
+ *
+ * @param elem the element
+ * @param height a CSS length value for the height
+ */
+ static final void setHeight(Element elem, String height) {
+ DOM.setStyleAttribute(elem, "height", height);
+ }
+
+ /**
+ * Convenience method to set the left offset of an element.
+ *
+ * @param elem the element
+ * @param left a CSS length value for left
+ */
+ static final void setLeft(Element elem, String left) {
+ DOM.setStyleAttribute(elem, "left", left);
+ }
+
+ /**
+ * Convenience method to set the right offset of an element.
+ *
+ * @param elem the element
+ * @param right a CSS length value for right
+ */
+ static final void setRight(Element elem, String right) {
+ DOM.setStyleAttribute(elem, "right", right);
+ }
+
+ /**
+ * Convenience method to set the top offset of an element.
+ *
+ * @param elem the element
+ * @param top a CSS length value for top
+ */
+ static final void setTop(Element elem, String top) {
+ DOM.setStyleAttribute(elem, "top", top);
+ }
+
+ /**
+ * Convenience method to set the width of an element.
+ *
+ * @param elem the element
+ * @param width a CSS length value for the width
+ */
+ static final void setWidth(Element elem, String width) {
+ DOM.setStyleAttribute(elem, "width", width);
+ }
+
+ // The enclosed widgets.
+ private final Widget[] widgets = new Widget[2];
+
+ // The elements containing the widgets.
+ private final Element[] elements = new Element[2];
+
+ // The element that acts as the splitter.
+ private final Element splitElem;
+
+ // Indicates whether drag resizing is active.
+ private boolean isResizing = false;
+
+ /**
+ * Initializes the split panel.
+ *
+ * @param mainElem the root element for the split panel
+ * @param splitElem the element that acts as the splitter
+ * @param headElem the element to contain the top or left most widget
+ * @param tailElem the element to contain the bottom or right most widget
+ */
+ SplitPanel(Element mainElem, Element splitElem, Element headElem,
+ Element tailElem) {
+ setElement(mainElem);
+ this.splitElem = splitElem;
+ elements[0] = headElem;
+ elements[1] = tailElem;
+ sinkEvents(Event.MOUSEEVENTS | Event.ONLOSECAPTURE);
+
+ if (glassElem == null) {
+ glassElem = DOM.createDiv();
+ glassElem.getStyle().setProperty("position", "absolute");
+ glassElem.getStyle().setProperty("top", "0px");
+ glassElem.getStyle().setProperty("left", "0px");
+ glassElem.getStyle().setProperty("margin", "0px");
+ glassElem.getStyle().setProperty("padding", "0px");
+ glassElem.getStyle().setProperty("border", "0px");
+
+ // We need to set the background color or mouse events will go right
+ // through the glassElem. If the SplitPanel contains an iframe, the
+ // iframe will capture the event and the slider will stop moving.
+ glassElem.getStyle().setProperty("background", "white");
+ glassElem.getStyle().setProperty("opacity", "0.0");
+ glassElem.getStyle().setProperty("filter", "alpha(opacity=0)");
+ }
+ }
+
+ @Override
+ public void add(Widget w) {
+ if (getWidget(0) == null) {
+ setWidget(0, w);
+ } else if (getWidget(1) == null) {
+ setWidget(1, w);
+ } else {
+ throw new IllegalStateException(
+ "A Splitter can only contain two Widgets.");
+ }
+ }
+
+ /**
+ * Indicates whether the split panel is being resized.
+ *
+ * @return <code>true</code> if the user is dragging the splitter,
+ * <code>false</code> otherwise
+ */
+ public boolean isResizing() {
+ return isResizing;
+ }
+
+ public Iterator<Widget> iterator() {
+ return WidgetIterators.createWidgetIterator(this, widgets);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+
+ case Event.ONMOUSEDOWN: {
+ Element target = DOM.eventGetTarget(event);
+ if (DOM.isOrHasChild(splitElem, target)) {
+ startResizingFrom(DOM.eventGetClientX(event) - getAbsoluteLeft(),
+ DOM.eventGetClientY(event) - getAbsoluteTop());
+ DOM.setCapture(getElement());
+ DOM.eventPreventDefault(event);
+ }
+ break;
+ }
+
+ case Event.ONMOUSEUP: {
+ if (isResizing()) {
+ // The order of these two lines is important. If we release capture
+ // first, then we might trigger an onLoseCapture event before we set
+ // isResizing to false.
+ stopResizing();
+ DOM.releaseCapture(getElement());
+ }
+ break;
+ }
+
+ case Event.ONMOUSEMOVE: {
+ if (isResizing()) {
+ assert DOM.getCaptureElement() != null;
+ onSplitterResize(DOM.eventGetClientX(event) - getAbsoluteLeft(),
+ DOM.eventGetClientY(event) - getAbsoluteTop());
+ DOM.eventPreventDefault(event);
+ }
+ break;
+ }
+
+ // IE automatically releases capture if the user switches windows, so we
+ // need to catch the event and stop resizing.
+ case Event.ONLOSECAPTURE: {
+ if (isResizing()) {
+ stopResizing();
+ }
+ break;
+ }
+ }
+ super.onBrowserEvent(event);
+ }
+
+ @Override
+ public boolean remove(Widget widget) {
+ if (widgets[0] == widget) {
+ setWidget(0, null);
+ return true;
+ } else if (widgets[1] == widget) {
+ setWidget(1, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Moves the position of the splitter.
+ *
+ * @param size the new size of the left region in CSS units (e.g. "10px",
+ * "1em")
+ */
+ public abstract void setSplitPosition(String size);
+
+ /**
+ * Gets the content element for the given index.
+ *
+ * @param index the index of the element, only 0 and 1 are valid.
+ * @return the element
+ */
+ protected Element getElement(int index) {
+ return elements[index];
+ }
+
+ /**
+ * Gets the element that is acting as the splitter.
+ *
+ * @return the element
+ */
+ protected Element getSplitElement() {
+ return splitElem;
+ }
+
+ /**
+ * Gets one of the contained widgets.
+ *
+ * @param index the index of the widget, only 0 and 1 are valid.
+ * @return the widget
+ */
+ protected Widget getWidget(int index) {
+ return widgets[index];
+ }
+
+ /**
+ * <b>Affected Elements:</b>
+ * <ul>
+ * <li>-splitter = the container containing the splitter element.</li>
+ * </ul>
+ *
+ * @see UIObject#onEnsureDebugId(String)
+ */
+ @Override
+ protected void onEnsureDebugId(String baseID) {
+ super.onEnsureDebugId(baseID);
+ ensureDebugId(splitElem, baseID, "splitter");
+ }
+
+ /**
+ * Sets one of the contained widgets.
+ *
+ * @param index the index, only 0 and 1 are valid
+ * @param w the widget
+ */
+ protected final void setWidget(int index, Widget w) {
+ Widget oldWidget = widgets[index];
+
+ // Validate.
+ if (oldWidget == w) {
+ return;
+ }
+
+ // Detach the new child.
+ if (w != null) {
+ w.removeFromParent();
+ }
+
+ // Remove the old child.
+ if (oldWidget != null) {
+ // Orphan old.
+ try {
+ orphan(oldWidget);
+ } finally {
+ // Physical detach old.
+ DOM.removeChild(elements[index], oldWidget.getElement());
+ widgets[index] = null;
+ }
+ }
+
+ // Logical attach new.
+ widgets[index] = w;
+
+ if (w != null) {
+ // Physical attach new.
+ DOM.appendChild(elements[index], w.getElement());
+
+ // Adopt new.
+ adopt(w);
+ }
+ }
+
+ /**
+ * Called on each mouse drag event as the user is dragging the splitter.
+ *
+ * @param x the x coordinate of the mouse relative to the panel's extent
+ * @param y the y coordinate of the mosue relative to the panel's extent
+ */
+ abstract void onSplitterResize(int x, int y);
+
+ /**
+ * Called when the user starts dragging the splitter.
+ *
+ * @param x the x coordinate of the mouse relative to the panel's extent
+ * @param y the y coordinate of the mouse relative to the panel's extent
+ */
+ abstract void onSplitterResizeStarted(int x, int y);
+
+ private void startResizingFrom(int x, int y) {
+ fireEvent(new SplitterBeforeResizeEvent());
+ isResizing = true;
+ onSplitterResizeStarted(x, y);
+
+ // Resize glassElem to take up the entire scrollable window area
+ int height = RootPanel.getBodyElement().getScrollHeight() - 1;
+ int width = RootPanel.getBodyElement().getScrollWidth() - 1;
+ glassElem.getStyle().setProperty("height", height + "px");
+ glassElem.getStyle().setProperty("width", width + "px");
+ RootPanel.getBodyElement().appendChild(glassElem);
+ }
+
+ private void stopResizing() {
+ isResizing = false;
+ RootPanel.getBodyElement().removeChild(glassElem);
+ fireEvent(new SplitterResizedEvent());
+ }
+
+ public HandlerRegistration addSplitterBeforeResizeHandler(SplitterBeforeResizeHandler handler)
+ {
+ return addHandler(handler, SplitterBeforeResizeEvent.TYPE);
+ }
+
+ public HandlerRegistration addSplitterResizedHandler(SplitterResizedHandler handler)
+ {
+ return addHandler(handler, SplitterResizedEvent.TYPE);
+ }
+}
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeEvent.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeEvent.java
new file mode 100644
index 0000000..7ee4ce8
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeEvent.java
@@ -0,0 +1,24 @@
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SplitterBeforeResizeEvent extends GwtEvent<SplitterBeforeResizeHandler>
+{
+ public static final GwtEvent.Type<SplitterBeforeResizeHandler> TYPE =
+ new GwtEvent.Type<SplitterBeforeResizeHandler>();
+
+ @Override
+ protected void dispatch(SplitterBeforeResizeHandler handler)
+ {
+ handler.onSplitterBeforeResize(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SplitterBeforeResizeHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+
+}
+
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeHandler.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeHandler.java
new file mode 100644
index 0000000..4ccfa9b
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitterBeforeResizeHandler.java
@@ -0,0 +1,8 @@
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SplitterBeforeResizeHandler extends EventHandler
+{
+ void onSplitterBeforeResize(SplitterBeforeResizeEvent event);
+}
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedEvent.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedEvent.java
new file mode 100644
index 0000000..37e137d
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedEvent.java
@@ -0,0 +1,24 @@
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SplitterResizedEvent extends GwtEvent<SplitterResizedHandler>
+{
+ public static final GwtEvent.Type<SplitterResizedHandler> TYPE =
+ new GwtEvent.Type<SplitterResizedHandler>();
+
+ @Override
+ protected void dispatch(SplitterResizedHandler handler)
+ {
+ handler.onSplitterResized(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SplitterResizedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+
+}
+
diff --git a/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedHandler.java b/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedHandler.java
new file mode 100644
index 0000000..6b0a31d
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/client/ui/SplitterResizedHandler.java
@@ -0,0 +1,8 @@
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SplitterResizedHandler extends EventHandler
+{
+ void onSplitterResized(SplitterResizedEvent event);
+}
diff --git a/src/gwt/src/com/google/gwt/user/revertchanges.sh b/src/gwt/src/com/google/gwt/user/revertchanges.sh
new file mode 100755
index 0000000..f96554c
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/user/revertchanges.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Overwrites customized files with originals from gwt-user.jar. This should
+# be done with each new release of GWT to make sure we're not pounding over
+# changes made by the GWT team.
+
+cd ../../../../
+pwd
+jar xvf ../lib/gwt/2.3.0-m1/gwt-user.jar \
+ com/google/gwt/user/client/ui/MenuBar.java \
+ com/google/gwt/user/client/ui/SplitPanel.java \
+ com/google/gwt/user/client/ui/SplitLayoutPanel.java
diff --git a/src/gwt/src/com/google/gwt/widgetideas/SliderBar.gwt.xml b/src/gwt/src/com/google/gwt/widgetideas/SliderBar.gwt.xml
new file mode 100644
index 0000000..6244f32
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/widgetideas/SliderBar.gwt.xml
@@ -0,0 +1,6 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
+<module>
+
+ <inherits name='com.google.gwt.user.User'/>
+
+</module>
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidget.java b/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidget.java
new file mode 100644
index 0000000..8566c9c
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidget.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.widgetideas.client;
+
+import com.google.gwt.user.client.Element;
+
+/**
+ * An interface that defines the methods required to support automatic resizing
+ * of the Widget element.
+ */
+public interface ResizableWidget {
+ /**
+ * Get the widget's element.
+ */
+ Element getElement();
+
+ /**
+ * Check if this widget is attached to the page.
+ *
+ * @return true if the widget is attached to the page
+ */
+ boolean isAttached();
+
+ /**
+ * This method is called when the dimensions of the parent element change.
+ * Subclasses should override this method as needed.
+ *
+ * @param width the new client width of the element
+ * @param height the new client height of the element
+ */
+ void onResize(int width, int height);
+}
+
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidgetCollection.java b/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidgetCollection.java
new file mode 100644
index 0000000..9a88f76
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/widgetideas/client/ResizableWidgetCollection.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.widgetideas.client;
+
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.WindowResizeListener;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A collection of {@link ResizableWidget} that periodically checks the outer
+ * dimensions of a widget and redraws it as necessary. Every
+ * {@link ResizableWidgetCollection} uses a timer, so consider the cost when
+ * adding one.
+ *
+ * Typically, a {@link ResizableWidgetCollection} is only needed if you expect
+ * your widgets to resize based on window resizing or other events. Fixed sized
+ * Widgets do not need to be added to a {@link ResizableWidgetCollection} as
+ * they cannot be resized.
+ */
+ at SuppressWarnings("deprecation")
+public class ResizableWidgetCollection implements WindowResizeListener,
+ Iterable<ResizableWidget> {
+ /**
+ * Information about a widgets size.
+ */
+ private static class ResizableWidgetInfo {
+ /**
+ * The current clientHeight.
+ */
+ private int curHeight = 0;
+
+ /**
+ * The current clientWidth.
+ */
+ private int curWidth = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param widget the widget that will be monitored
+ */
+ public ResizableWidgetInfo(ResizableWidget widget) {
+ curWidth = DOM.getElementPropertyInt(widget.getElement(), "clientWidth");
+ curHeight = DOM.getElementPropertyInt(widget.getElement(), "clientHeight");
+ }
+
+ /**
+ * Set the new dimensions of the widget if they changed.
+ *
+ * @param width the new width
+ * @param height the new height
+ * @return true if the dimensions have changed
+ */
+ public boolean setClientSize(int width, int height) {
+ if (width != curWidth || height != curHeight) {
+ this.curWidth = width;
+ this.curHeight = height;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * The default delay between resize checks in milliseconds.
+ */
+ private static final int DEFAULT_RESIZE_CHECK_DELAY = 400;
+
+ /**
+ * A static {@link ResizableWidgetCollection} that can be used in most cases.
+ */
+ private static ResizableWidgetCollection staticCollection = null;
+
+ /**
+ * Get the globally accessible {@link ResizableWidgetCollection}. In most
+ * cases, the global collection can be used for all {@link ResizableWidget}s.
+ *
+ * @return the global {@link ResizableWidgetCollection}
+ */
+ public static ResizableWidgetCollection get() {
+ if (staticCollection == null) {
+ staticCollection = new ResizableWidgetCollection();
+ }
+ return staticCollection;
+ }
+
+ /**
+ * The timer used to periodically compare the dimensions of elements to their
+ * old dimensions.
+ */
+ private Timer resizeCheckTimer = new Timer() {
+ @Override
+ public void run() {
+ // Ignore changes that result from window resize events
+ if (windowHeight != Window.getClientHeight()
+ || windowWidth != Window.getClientWidth()) {
+ windowHeight = Window.getClientHeight();
+ windowWidth = Window.getClientWidth();
+ schedule(resizeCheckDelay);
+ return;
+ }
+
+ // Look for elements that have new dimensions
+ checkWidgetSize();
+
+ // Start checking again
+ if (resizeCheckingEnabled) {
+ schedule(resizeCheckDelay);
+ }
+ }
+ };
+
+ /**
+ * A hash map of the resizable widgets this collection is checking.
+ */
+ private Map<ResizableWidget, ResizableWidgetInfo> widgets = new HashMap<ResizableWidget, ResizableWidgetInfo>();
+
+ /**
+ * The current window height.
+ */
+ private int windowHeight = 0;
+
+ /**
+ * The current window width.
+ */
+ private int windowWidth = 0;
+
+ /**
+ * The hook used to remove the window handler.
+ */
+ private HandlerRegistration windowHandler;
+
+ /**
+ * The delay between resize checks.
+ */
+ private int resizeCheckDelay = DEFAULT_RESIZE_CHECK_DELAY;
+
+ /**
+ * A boolean indicating that resize checking should run.
+ */
+ private boolean resizeCheckingEnabled;
+
+ /**
+ * Create a ResizableWidget.
+ */
+ public ResizableWidgetCollection() {
+ this(DEFAULT_RESIZE_CHECK_DELAY);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param resizeCheckingEnabled false to disable resize checking
+ */
+ public ResizableWidgetCollection(boolean resizeCheckingEnabled) {
+ this(DEFAULT_RESIZE_CHECK_DELAY, resizeCheckingEnabled);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param resizeCheckDelay the delay between checks in milliseconds
+ */
+ public ResizableWidgetCollection(int resizeCheckDelay) {
+ this(resizeCheckDelay, true);
+ }
+
+ /**
+ * Constructor.
+ */
+ protected ResizableWidgetCollection(int resizeCheckDelay,
+ boolean resizeCheckingEnabled) {
+ setResizeCheckDelay(resizeCheckDelay);
+ setResizeCheckingEnabled(resizeCheckingEnabled);
+ }
+
+ /**
+ * Add a resizable widget to the collection.
+ *
+ * @param widget the resizable widget to add
+ */
+ public void add(ResizableWidget widget) {
+ widgets.put(widget, new ResizableWidgetInfo(widget));
+ }
+
+ /**
+ * Check to see if any Widgets have been resized and call their handlers
+ * appropriately.
+ */
+ public void checkWidgetSize() {
+ for (Map.Entry<ResizableWidget, ResizableWidgetInfo> entry : widgets.entrySet()) {
+ ResizableWidget widget = entry.getKey();
+ ResizableWidgetInfo info = entry.getValue();
+ int curWidth = widget.getElement().getPropertyInt("clientWidth");
+ int curHeight = widget.getElement().getPropertyInt("clientHeight");
+
+ // Call the onResize method only if the widget is attached
+ if (info.setClientSize(curWidth, curHeight)) {
+ if (curWidth > 0 && curHeight > 0 && widget.isAttached()) {
+ widget.onResize(curWidth, curHeight);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the delay between resize checks in milliseconds.
+ *
+ * @return the resize check delay
+ */
+ public int getResizeCheckDelay() {
+ return resizeCheckDelay;
+ }
+
+ /**
+ * Check whether or not resize checking is enabled.
+ *
+ * @return true is resize checking is enabled
+ */
+ public boolean isResizeCheckingEnabled() {
+ return resizeCheckingEnabled;
+ }
+
+ public Iterator<ResizableWidget> iterator() {
+ return widgets.keySet().iterator();
+ }
+
+ /**
+ * Called when the browser window is resized.
+ *
+ * @param width the width of the window's client area.
+ * @param height the height of the window's client area.
+ */
+ @Deprecated
+ public void onWindowResized(int width, int height) {
+ checkWidgetSize();
+ }
+
+ /**
+ * Remove a {@link ResizableWidget} from the collection.
+ *
+ * @param widget the widget to remove
+ */
+ public void remove(ResizableWidget widget) {
+ widgets.remove(widget);
+ }
+
+ /**
+ * Set the delay between resize checks in milliseconds.
+ *
+ * @param resizeCheckDelay the new delay
+ */
+ public void setResizeCheckDelay(int resizeCheckDelay) {
+ this.resizeCheckDelay = resizeCheckDelay;
+ }
+
+ /**
+ * Set whether or not resize checking is enabled. If disabled, elements will
+ * still be resized on window events, but the timer will not check their
+ * dimensions periodically.
+ *
+ * @param enabled true to enable the resize checking timer
+ */
+ public void setResizeCheckingEnabled(boolean enabled) {
+ if (enabled && !resizeCheckingEnabled) {
+ resizeCheckingEnabled = true;
+ if (windowHandler == null) {
+ windowHandler = Window.addResizeHandler(new ResizeHandler() {
+ public void onResize(ResizeEvent event) {
+ onWindowResized(event.getWidth(), event.getHeight());
+ }
+ });
+ }
+ resizeCheckTimer.schedule(resizeCheckDelay);
+ } else if (!enabled && resizeCheckingEnabled) {
+ resizeCheckingEnabled = false;
+ if (windowHandler != null) {
+ windowHandler.removeHandler();
+ windowHandler = null;
+ }
+ resizeCheckTimer.cancel();
+ }
+ }
+
+ /**
+ * Inform the {@link ResizableWidgetCollection} that the size of a widget has
+ * changed and already been redrawn. This will prevent the widget from being
+ * redrawn on the next loop.
+ *
+ * @param widget the widget's size that changed
+ */
+ public void updateWidgetSize(ResizableWidget widget) {
+ if (!widget.isAttached()) {
+ return;
+ }
+
+ ResizableWidgetInfo info = widgets.get(widget);
+ if (info != null) {
+ int curWidth = widget.getElement().getPropertyInt("clientWidth");
+ int curHeight = widget.getElement().getPropertyInt("clientHeight");
+ info.setClientSize(curWidth, curHeight);
+ }
+ }
+
+}
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/SliderBar.java b/src/gwt/src/com/google/gwt/widgetideas/client/SliderBar.java
new file mode 100644
index 0000000..44392e6
--- /dev/null
+++ b/src/gwt/src/com/google/gwt/widgetideas/client/SliderBar.java
@@ -0,0 +1,1002 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.widgetideas.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.ChangeListener;
+import com.google.gwt.user.client.ui.ChangeListenerCollection;
+import com.google.gwt.user.client.ui.FocusPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ImageBundle;
+import com.google.gwt.user.client.ui.KeyboardListener;
+import com.google.gwt.user.client.ui.SourcesChangeEvents;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A widget that allows the user to select a value within a range of possible
+ * values using a sliding bar that responds to mouse events.
+ *
+ * <h3>Keyboard Events</h3>
+ * <p>
+ * SliderBar listens for the following key events. Holding down a key will
+ * repeat the action until the key is released. <ul class='css'>
+ * <li>left arrow - shift left one step</li>
+ * <li>right arrow - shift right one step</li>
+ * <li>ctrl+left arrow - jump left 10% of the distance</li>
+ * <li>ctrl+right arrow - jump right 10% of the distance</li>
+ * <li>home - jump to min value</li>
+ * <li>end - jump to max value</li>
+ * <li>space - jump to middle value</li>
+ * </ul>
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3> <ul class='css'> <li>.gwt-SliderBar-shell { primary
+ * style }</li> <li>.gwt-SliderBar-shell-focused { primary style when focused }</li>
+ * <li>.gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves
+ * along }</li> <li>.gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line
+ * that the knob moves along when sliding }</li> <li>.gwt-SliderBar-shell
+ * .gwt-SliderBar-knob { the sliding knob }</li> <li>.gwt-SliderBar-shell
+ * .gwt-SliderBar-knob-sliding { the sliding knob when sliding }</li> <li>
+ * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }</li>
+ * <li>.gwt-SliderBar-shell .gwt-SliderBar-label { the text labels along the
+ * line }</li> </ul>
+ */
+ at SuppressWarnings("deprecation")
+public class SliderBar extends FocusPanel implements ResizableWidget,
+ SourcesChangeEvents {
+ /**
+ * The timer used to continue to shift the knob as the user holds down one of
+ * the left/right arrow keys. Only IE auto-repeats, so we just keep catching
+ * the events.
+ */
+ private class KeyTimer extends Timer {
+ /**
+ * A bit indicating that this is the first run.
+ */
+ private boolean firstRun = true;
+
+ /**
+ * The delay between shifts, which shortens as the user holds down the
+ * button.
+ */
+ private int repeatDelay = 30;
+
+ /**
+ * A bit indicating whether we are shifting to a higher or lower value.
+ */
+ private boolean shiftRight = false;
+
+ /**
+ * The number of steps to shift with each press.
+ */
+ private int multiplier = 1;
+
+ /**
+ * This method will be called when a timer fires. Override it to implement
+ * the timer's logic.
+ */
+ @Override
+ public void run() {
+ // Highlight the knob on first run
+ if (firstRun) {
+ firstRun = false;
+ startSliding(true, false);
+ }
+
+ // Slide the slider bar
+ if (shiftRight) {
+ setCurrentValue(curValue + multiplier * stepSize);
+ } else {
+ setCurrentValue(curValue - multiplier * stepSize);
+ }
+
+ // Repeat this timer until cancelled by keyup event
+ schedule(repeatDelay);
+ }
+
+ /**
+ * Schedules a timer to elapse in the future.
+ *
+ * @param delayMillis how long to wait before the timer elapses, in
+ * milliseconds
+ * @param shiftRight whether to shift up or not
+ * @param multiplier the number of steps to shift
+ */
+ public void schedule(int delayMillis, boolean shiftRight, int multiplier) {
+ firstRun = true;
+ this.shiftRight = shiftRight;
+ this.multiplier = multiplier;
+ super.schedule(delayMillis);
+ }
+ }
+
+ /**
+ * A formatter used to format the labels displayed in the widget.
+ */
+ public static interface LabelFormatter {
+ /**
+ * Generate the text to display in each label based on the label's value.
+ *
+ * Override this method to change the text displayed within the SliderBar.
+ *
+ * @param slider the Slider bar
+ * @param value the value the label displays
+ * @return the text to display for the label
+ */
+ String formatLabel(SliderBar slider, double value);
+ }
+
+ /**
+ * An {@link ImageBundle} that provides images for {@link SliderBar}.
+ */
+ public static interface SliderBarImages extends ImageBundle {
+ /**
+ * An image used for the sliding knob.
+ *
+ * @return a prototype of this image
+ */
+ AbstractImagePrototype slider();
+
+ /**
+ * An image used for the sliding knob.
+ *
+ * @return a prototype of this image
+ */
+ AbstractImagePrototype sliderDisabled();
+
+ /**
+ * An image used for the sliding knob while sliding.
+ *
+ * @return a prototype of this image
+ */
+ AbstractImagePrototype sliderSliding();
+ }
+
+ /**
+ * The change listeners.
+ */
+ private ChangeListenerCollection changeListeners;
+
+ /**
+ * The current value.
+ */
+ private double curValue;
+
+ /**
+ * The knob that slides across the line.
+ */
+ private Image knobImage = new Image();
+
+ /**
+ * The timer used to continue to shift the knob if the user holds down a key.
+ */
+ private KeyTimer keyTimer = new KeyTimer();
+
+ /**
+ * The elements used to display labels above the ticks.
+ */
+ private List<Element> labelElements = new ArrayList<Element>();
+
+ /**
+ * The formatter used to generate label text.
+ */
+ private LabelFormatter labelFormatter;
+
+ /**
+ * The line that the knob moves over.
+ */
+ private Element lineElement;
+
+ /**
+ * The offset between the edge of the shell and the line.
+ */
+ private int lineLeftOffset = 0;
+
+ /**
+ * The maximum slider value.
+ */
+ private double maxValue;
+
+ /**
+ * The minimum slider value.
+ */
+ private double minValue;
+
+ /**
+ * The number of labels to show.
+ */
+ private int numLabels = 0;
+
+ /**
+ * The number of tick marks to show.
+ */
+ private int numTicks = 0;
+
+ /**
+ * A bit indicating whether or not we are currently sliding the slider bar due
+ * to keyboard events.
+ */
+ private boolean slidingKeyboard = false;
+
+ /**
+ * A bit indicating whether or not we are currently sliding the slider bar due
+ * to mouse events.
+ */
+ private boolean slidingMouse = false;
+
+ /**
+ * A bit indicating whether or not the slider is enabled
+ */
+ private boolean enabled = true;
+
+ /**
+ * The images used with the sliding bar.
+ */
+ private SliderBarImages images;
+
+ /**
+ * The size of the increments between knob positions.
+ */
+ private double stepSize;
+
+ /**
+ * The elements used to display tick marks, which are the vertical lines along
+ * the slider bar.
+ */
+ private List<Element> tickElements = new ArrayList<Element>();
+
+ /**
+ * Create a slider bar.
+ *
+ * @param minValue the minimum value in the range
+ * @param maxValue the maximum value in the range
+ */
+ public SliderBar(double minValue, double maxValue) {
+ this(minValue, maxValue, null);
+ }
+
+ /**
+ * Create a slider bar.
+ *
+ * @param minValue the minimum value in the range
+ * @param maxValue the maximum value in the range
+ * @param labelFormatter the label formatter
+ */
+ public SliderBar(double minValue, double maxValue,
+ LabelFormatter labelFormatter) {
+ this(minValue, maxValue, labelFormatter,
+ (SliderBarImages) GWT.create(SliderBarImages.class));
+ }
+
+ /**
+ * Create a slider bar.
+ *
+ * @param minValue the minimum value in the range
+ * @param maxValue the maximum value in the range
+ * @param labelFormatter the label formatter
+ * @param images the images to use for the slider
+ */
+ public SliderBar(double minValue, double maxValue,
+ LabelFormatter labelFormatter, SliderBarImages images) {
+ super();
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.images = images;
+ setLabelFormatter(labelFormatter);
+
+ // Create the outer shell
+ DOM.setStyleAttribute(getElement(), "position", "relative");
+ setStyleName("gwt-SliderBar-shell");
+
+ // Create the line
+ lineElement = DOM.createDiv();
+ DOM.appendChild(getElement(), lineElement);
+ DOM.setStyleAttribute(lineElement, "position", "absolute");
+ DOM.setElementProperty(lineElement, "className", "gwt-SliderBar-line");
+
+ // Create the knob
+ images.slider().applyTo(knobImage);
+ Element knobElement = knobImage.getElement();
+ DOM.appendChild(getElement(), knobElement);
+ DOM.setStyleAttribute(knobElement, "position", "absolute");
+ DOM.setElementProperty(knobElement, "className", "gwt-SliderBar-knob");
+
+ sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS);
+ }
+
+ /**
+ * Add a change listener to this SliderBar.
+ *
+ * @param listener the listener to add
+ */
+ public void addChangeListener(ChangeListener listener) {
+ if (changeListeners == null) {
+ changeListeners = new ChangeListenerCollection();
+ }
+ changeListeners.add(listener);
+ }
+
+ /**
+ * Return the current value.
+ *
+ * @return the current value
+ */
+ public double getCurrentValue() {
+ return curValue;
+ }
+
+ /**
+ * Return the label formatter.
+ *
+ * @return the label formatter
+ */
+ public LabelFormatter getLabelFormatter() {
+ return labelFormatter;
+ }
+
+ /**
+ * Return the max value.
+ *
+ * @return the max value
+ */
+ public double getMaxValue() {
+ return maxValue;
+ }
+
+ /**
+ * Return the minimum value.
+ *
+ * @return the minimum value
+ */
+ public double getMinValue() {
+ return minValue;
+ }
+
+ /**
+ * Return the number of labels.
+ *
+ * @return the number of labels
+ */
+ public int getNumLabels() {
+ return numLabels;
+ }
+
+ /**
+ * Return the number of ticks.
+ *
+ * @return the number of ticks
+ */
+ public int getNumTicks() {
+ return numTicks;
+ }
+
+ /**
+ * Return the step size.
+ *
+ * @return the step size
+ */
+ public double getStepSize() {
+ return stepSize;
+ }
+
+ /**
+ * Return the total range between the minimum and maximum values.
+ *
+ * @return the total range
+ */
+ public double getTotalRange() {
+ if (minValue > maxValue) {
+ return 0;
+ } else {
+ return maxValue - minValue;
+ }
+ }
+
+ /**
+ * @return Gets whether this widget is enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Listen for events that will move the knob.
+ *
+ * @param event the event that occurred
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (enabled) {
+ switch (DOM.eventGetType(event)) {
+ // Unhighlight and cancel keyboard events
+ case Event.ONBLUR:
+ keyTimer.cancel();
+ if (slidingMouse) {
+ DOM.releaseCapture(getElement());
+ slidingMouse = false;
+ slideKnob(event);
+ stopSliding(true, true);
+ } else if (slidingKeyboard) {
+ slidingKeyboard = false;
+ stopSliding(true, true);
+ }
+ unhighlight();
+ break;
+
+ // Highlight on focus
+ case Event.ONFOCUS:
+ highlight();
+ break;
+
+ // Mousewheel events
+ case Event.ONMOUSEWHEEL:
+ int velocityY = DOM.eventGetMouseWheelVelocityY(event);
+ DOM.eventPreventDefault(event);
+ if (velocityY > 0) {
+ shiftRight(1);
+ } else {
+ shiftLeft(1);
+ }
+ break;
+
+ // Shift left or right on key press
+ case Event.ONKEYDOWN:
+ if (!slidingKeyboard) {
+ int multiplier = 1;
+ if (DOM.eventGetCtrlKey(event)) {
+ multiplier = (int) (getTotalRange() / stepSize / 10);
+ }
+
+ switch (DOM.eventGetKeyCode(event)) {
+ case KeyboardListener.KEY_HOME:
+ DOM.eventPreventDefault(event);
+ setCurrentValue(minValue);
+ break;
+ case KeyboardListener.KEY_END:
+ DOM.eventPreventDefault(event);
+ setCurrentValue(maxValue);
+ break;
+ case KeyboardListener.KEY_LEFT:
+ DOM.eventPreventDefault(event);
+ slidingKeyboard = true;
+ startSliding(false, true);
+ shiftLeft(multiplier);
+ keyTimer.schedule(400, false, multiplier);
+ break;
+ case KeyboardListener.KEY_RIGHT:
+ DOM.eventPreventDefault(event);
+ slidingKeyboard = true;
+ startSliding(false, true);
+ shiftRight(multiplier);
+ keyTimer.schedule(400, true, multiplier);
+ break;
+ case 32:
+ DOM.eventPreventDefault(event);
+ setCurrentValue(minValue + getTotalRange() / 2);
+ break;
+ }
+ }
+ break;
+ // Stop shifting on key up
+ case Event.ONKEYUP:
+ keyTimer.cancel();
+ if (slidingKeyboard) {
+ slidingKeyboard = false;
+ stopSliding(true, true);
+ }
+ break;
+
+ // Mouse Events
+ case Event.ONMOUSEDOWN:
+ setFocus(true);
+ slidingMouse = true;
+ DOM.setCapture(getElement());
+ startSliding(true, true);
+ DOM.eventPreventDefault(event);
+ slideKnob(event);
+ break;
+ case Event.ONMOUSEUP:
+ if (slidingMouse) {
+ DOM.releaseCapture(getElement());
+ slidingMouse = false;
+ slideKnob(event);
+ stopSliding(true, true);
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ if (slidingMouse) {
+ slideKnob(event);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * This method is called when the dimensions of the parent element change.
+ * Subclasses should override this method as needed.
+ *
+ * @param width the new client width of the element
+ * @param height the new client height of the element
+ */
+ public void onResize(int width, int height) {
+ // Center the line in the shell
+ int lineWidth = DOM.getElementPropertyInt(lineElement, "offsetWidth");
+ lineLeftOffset = (width / 2) - (lineWidth / 2);
+ DOM.setStyleAttribute(lineElement, "left", lineLeftOffset + "px");
+
+ // Draw the other components
+ drawLabels();
+ drawTicks();
+ drawKnob();
+ }
+
+ /**
+ * Redraw the progress bar when something changes the layout.
+ */
+ public void redraw() {
+ if (isAttached()) {
+ int width = DOM.getElementPropertyInt(getElement(), "clientWidth");
+ int height = DOM.getElementPropertyInt(getElement(), "clientHeight");
+ onResize(width, height);
+ }
+ }
+
+ /**
+ * Remove a change listener from this SliderBar.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeChangeListener(ChangeListener listener) {
+ if (changeListeners != null) {
+ changeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Set the current value and fire the onValueChange event.
+ *
+ * @param curValue the current value
+ */
+ public void setCurrentValue(double curValue) {
+ setCurrentValue(curValue, true);
+ }
+
+ /**
+ * Set the current value and optionally fire the onValueChange event.
+ *
+ * @param curValue the current value
+ * @param fireEvent fire the onValue change event if true
+ */
+ public void setCurrentValue(double curValue, boolean fireEvent) {
+ // Confine the value to the range
+ this.curValue = Math.max(minValue, Math.min(maxValue, curValue));
+ double remainder = (this.curValue - minValue) % stepSize;
+ this.curValue -= remainder;
+
+ // Go to next step if more than halfway there
+ if ((remainder > (stepSize / 2))
+ && ((this.curValue + stepSize) <= maxValue)) {
+ this.curValue += stepSize;
+ }
+
+ // Redraw the knob
+ drawKnob();
+
+ // Fire the onValueChange event
+ if (fireEvent && (changeListeners != null)) {
+ changeListeners.fireChange(this);
+ }
+ }
+
+ /**
+ * Sets whether this widget is enabled.
+ *
+ * @param enabled true to enable the widget, false to disable it
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ if (enabled) {
+ images.slider().applyTo(knobImage);
+ DOM.setElementProperty(lineElement, "className", "gwt-SliderBar-line");
+ } else {
+ images.sliderDisabled().applyTo(knobImage);
+ DOM.setElementProperty(lineElement, "className",
+ "gwt-SliderBar-line gwt-SliderBar-line-disabled");
+ }
+ redraw();
+ }
+
+ /**
+ * Set the label formatter.
+ *
+ * @param labelFormatter the label formatter
+ */
+ public void setLabelFormatter(LabelFormatter labelFormatter) {
+ this.labelFormatter = labelFormatter;
+ }
+
+ /**
+ * Set the max value.
+ *
+ * @param maxValue the current value
+ */
+ public void setMaxValue(double maxValue) {
+ this.maxValue = maxValue;
+ drawLabels();
+ resetCurrentValue();
+ }
+
+ /**
+ * Set the minimum value.
+ *
+ * @param minValue the current value
+ */
+ public void setMinValue(double minValue) {
+ this.minValue = minValue;
+ drawLabels();
+ resetCurrentValue();
+ }
+
+ /**
+ * Set the number of labels to show on the line. Labels indicate the value of
+ * the slider at that point. Use this method to enable labels.
+ *
+ * If you set the number of labels equal to the total range divided by the
+ * step size, you will get a properly aligned "jumping" effect where the knob
+ * jumps between labels.
+ *
+ * Note that the number of labels displayed will be one more than the number
+ * you specify, so specify 1 labels to show labels on either end of the line.
+ * In other words, numLabels is really the number of slots between the labels.
+ *
+ * setNumLabels(0) will disable labels.
+ *
+ * @param numLabels the number of labels to show
+ */
+ public void setNumLabels(int numLabels) {
+ this.numLabels = numLabels;
+ drawLabels();
+ }
+
+ /**
+ * Set the number of ticks to show on the line. A tick is a vertical line that
+ * represents a division of the overall line. Use this method to enable ticks.
+ *
+ * If you set the number of ticks equal to the total range divided by the step
+ * size, you will get a properly aligned "jumping" effect where the knob jumps
+ * between ticks.
+ *
+ * Note that the number of ticks displayed will be one more than the number
+ * you specify, so specify 1 tick to show ticks on either end of the line. In
+ * other words, numTicks is really the number of slots between the ticks.
+ *
+ * setNumTicks(0) will disable ticks.
+ *
+ * @param numTicks the number of ticks to show
+ */
+ public void setNumTicks(int numTicks) {
+ this.numTicks = numTicks;
+ drawTicks();
+ }
+
+ /**
+ * Set the step size.
+ *
+ * @param stepSize the current value
+ */
+ public void setStepSize(double stepSize) {
+ this.stepSize = stepSize;
+ resetCurrentValue();
+ }
+
+ /**
+ * Shift to the left (smaller value).
+ *
+ * @param numSteps the number of steps to shift
+ */
+ public void shiftLeft(int numSteps) {
+ setCurrentValue(getCurrentValue() - numSteps * stepSize);
+ }
+
+ /**
+ * Shift to the right (greater value).
+ *
+ * @param numSteps the number of steps to shift
+ */
+ public void shiftRight(int numSteps) {
+ setCurrentValue(getCurrentValue() + numSteps * stepSize);
+ }
+
+ /**
+ * Format the label to display above the ticks
+ *
+ * Override this method in a subclass to customize the format. By default,
+ * this method returns the integer portion of the value.
+ *
+ * @param value the value at the label
+ * @return the text to put in the label
+ */
+ protected String formatLabel(double value) {
+ if (labelFormatter != null) {
+ return labelFormatter.formatLabel(this, value);
+ } else {
+ return (int) (10 * value) / 10.0 + "";
+ }
+ }
+
+ /**
+ * Get the percentage of the knob's position relative to the size of the line.
+ * The return value will be between 0.0 and 1.0.
+ *
+ * @return the current percent complete
+ */
+ protected double getKnobPercent() {
+ // If we have no range
+ if (maxValue <= minValue) {
+ return 0;
+ }
+
+ // Calculate the relative progress
+ double percent = (curValue - minValue) / (maxValue - minValue);
+ return Math.max(0.0, Math.min(1.0, percent));
+ }
+
+ /**
+ * This method is called immediately after a widget becomes attached to the
+ * browser's document.
+ */
+ @Override
+ protected void onLoad() {
+ // Reset the position attribute of the parent element
+ DOM.setStyleAttribute(getElement(), "position", "relative");
+ ResizableWidgetCollection.get().add(this);
+ redraw();
+ }
+
+ @Override
+ protected void onUnload() {
+ ResizableWidgetCollection.get().remove(this);
+ }
+
+ /**
+ * Draw the knob where it is supposed to be relative to the line.
+ */
+ private void drawKnob() {
+ // Abort if not attached
+ if (!isAttached()) {
+ return;
+ }
+
+ // Move the knob to the correct position
+ Element knobElement = knobImage.getElement();
+ int lineWidth = DOM.getElementPropertyInt(lineElement, "offsetWidth");
+ int knobWidth = DOM.getElementPropertyInt(knobElement, "offsetWidth");
+ int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2));
+ knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth
+ - (knobWidth / 2) - 1);
+ DOM.setStyleAttribute(knobElement, "left", knobLeftOffset + "px");
+ }
+
+ /**
+ * Draw the labels along the line.
+ */
+ private void drawLabels() {
+ // Abort if not attached
+ if (!isAttached()) {
+ return;
+ }
+
+ // Draw the labels
+ int lineWidth = DOM.getElementPropertyInt(lineElement, "offsetWidth");
+ if (numLabels > 0) {
+ // Create the labels or make them visible
+ for (int i = 0; i <= numLabels; i++) {
+ Element label = null;
+ if (i < labelElements.size()) {
+ label = labelElements.get(i);
+ } else { // Create the new label
+ label = DOM.createDiv();
+ DOM.setStyleAttribute(label, "position", "absolute");
+ DOM.setStyleAttribute(label, "display", "none");
+ if (enabled) {
+ DOM.setElementProperty(label, "className", "gwt-SliderBar-label");
+ } else {
+ DOM.setElementProperty(label, "className",
+ "gwt-SliderBar-label-disabled");
+ }
+ DOM.appendChild(getElement(), label);
+ labelElements.add(label);
+ }
+
+ // Set the label text
+ double value = minValue + (getTotalRange() * i / numLabels);
+ DOM.setStyleAttribute(label, "visibility", "hidden");
+ DOM.setStyleAttribute(label, "display", "");
+ DOM.setElementProperty(label, "innerHTML", formatLabel(value));
+
+ // Move to the left so the label width is not clipped by the shell
+ DOM.setStyleAttribute(label, "left", "0px");
+
+ // Position the label and make it visible
+ int labelWidth = DOM.getElementPropertyInt(label, "offsetWidth");
+ int labelLeftOffset = lineLeftOffset + (lineWidth * i / numLabels)
+ - (labelWidth / 2);
+ labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth
+ - labelWidth);
+ labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
+ DOM.setStyleAttribute(label, "left", labelLeftOffset + "px");
+ DOM.setStyleAttribute(label, "visibility", "visible");
+ }
+
+ // Hide unused labels
+ for (int i = (numLabels + 1); i < labelElements.size(); i++) {
+ DOM.setStyleAttribute(labelElements.get(i), "display", "none");
+ }
+ } else { // Hide all labels
+ for (Element elem : labelElements) {
+ DOM.setStyleAttribute(elem, "display", "none");
+ }
+ }
+ }
+
+ /**
+ * Draw the tick along the line.
+ */
+ private void drawTicks() {
+ // Abort if not attached
+ if (!isAttached()) {
+ return;
+ }
+
+ // Draw the ticks
+ int lineWidth = DOM.getElementPropertyInt(lineElement, "offsetWidth");
+ if (numTicks > 0) {
+ // Create the ticks or make them visible
+ for (int i = 0; i <= numTicks; i++) {
+ Element tick = null;
+ if (i < tickElements.size()) {
+ tick = tickElements.get(i);
+ } else { // Create the new tick
+ tick = DOM.createDiv();
+ DOM.setStyleAttribute(tick, "position", "absolute");
+ DOM.setStyleAttribute(tick, "display", "none");
+ DOM.appendChild(getElement(), tick);
+ tickElements.add(tick);
+ }
+ if (enabled) {
+ DOM.setElementProperty(tick, "className", "gwt-SliderBar-tick");
+ } else {
+ DOM.setElementProperty(tick, "className",
+ "gwt-SliderBar-tick gwt-SliderBar-tick-disabled");
+ }
+ // Position the tick and make it visible
+ DOM.setStyleAttribute(tick, "visibility", "hidden");
+ DOM.setStyleAttribute(tick, "display", "");
+ int tickWidth = DOM.getElementPropertyInt(tick, "offsetWidth");
+ int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks)
+ - (tickWidth / 2);
+ tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth
+ - tickWidth);
+ DOM.setStyleAttribute(tick, "left", tickLeftOffset + "px");
+ DOM.setStyleAttribute(tick, "visibility", "visible");
+ }
+
+ // Hide unused ticks
+ for (int i = (numTicks + 1); i < tickElements.size(); i++) {
+ DOM.setStyleAttribute(tickElements.get(i), "display", "none");
+ }
+ } else { // Hide all ticks
+ for (Element elem : tickElements) {
+ DOM.setStyleAttribute(elem, "display", "none");
+ }
+ }
+ }
+
+ /**
+ * Highlight this widget.
+ */
+ private void highlight() {
+ String styleName = getStylePrimaryName();
+ DOM.setElementProperty(getElement(), "className", styleName + " "
+ + styleName + "-focused");
+ }
+
+ /**
+ * Reset the progress to constrain the progress to the current range and
+ * redraw the knob as needed.
+ */
+ private void resetCurrentValue() {
+ setCurrentValue(getCurrentValue());
+ }
+
+ /**
+ * Slide the knob to a new location.
+ *
+ * @param event the mouse event
+ */
+ private void slideKnob(Event event) {
+ int x = DOM.eventGetClientX(event);
+ if (x > 0) {
+ int lineWidth = DOM.getElementPropertyInt(lineElement, "offsetWidth");
+ int lineLeft = DOM.getAbsoluteLeft(lineElement);
+ double percent = (double) (x - lineLeft) / lineWidth * 1.0;
+ setCurrentValue(getTotalRange() * percent + minValue, true);
+ }
+ }
+
+ /**
+ * Start sliding the knob.
+ *
+ * @param highlight true to change the style
+ * @param fireEvent true to fire the event
+ */
+ private void startSliding(boolean highlight, boolean fireEvent) {
+ if (highlight) {
+ DOM.setElementProperty(lineElement, "className",
+ "gwt-SliderBar-line gwt-SliderBar-line-sliding");
+ DOM.setElementProperty(knobImage.getElement(), "className",
+ "gwt-SliderBar-knob gwt-SliderBar-knob-sliding");
+ images.sliderSliding().applyTo(knobImage);
+ }
+ }
+
+ /**
+ * Stop sliding the knob.
+ *
+ * @param unhighlight true to change the style
+ * @param fireEvent true to fire the event
+ */
+ private void stopSliding(boolean unhighlight, boolean fireEvent) {
+ if (unhighlight) {
+ DOM.setElementProperty(lineElement, "className", "gwt-SliderBar-line");
+
+ DOM.setElementProperty(knobImage.getElement(), "className",
+ "gwt-SliderBar-knob");
+ images.slider().applyTo(knobImage);
+ }
+ if (fireEvent && (slideCompletedListeners != null))
+ slideCompletedListeners.fireChange(this);
+ }
+
+ /**
+ * Unhighlight this widget.
+ */
+ private void unhighlight() {
+ DOM.setElementProperty(getElement(), "className", getStylePrimaryName());
+ }
+
+ /**
+ * Add a change completed listener to this SliderBar.
+ *
+ * @param listener the listener to add
+ */
+ private ChangeListenerCollection slideCompletedListeners;
+ public void addSlideCompletedListener(ChangeListener listener) {
+ if (slideCompletedListeners == null) {
+ slideCompletedListeners = new ChangeListenerCollection();
+ }
+ slideCompletedListeners.add(listener);
+ }
+}
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/slider.png b/src/gwt/src/com/google/gwt/widgetideas/client/slider.png
new file mode 100755
index 0000000..390cb43
Binary files /dev/null and b/src/gwt/src/com/google/gwt/widgetideas/client/slider.png differ
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/sliderDisabled.png b/src/gwt/src/com/google/gwt/widgetideas/client/sliderDisabled.png
new file mode 100755
index 0000000..53c2e92
Binary files /dev/null and b/src/gwt/src/com/google/gwt/widgetideas/client/sliderDisabled.png differ
diff --git a/src/gwt/src/com/google/gwt/widgetideas/client/sliderSliding.png b/src/gwt/src/com/google/gwt/widgetideas/client/sliderSliding.png
new file mode 100755
index 0000000..836e9be
Binary files /dev/null and b/src/gwt/src/com/google/gwt/widgetideas/client/sliderSliding.png differ
diff --git a/src/gwt/src/org/rstudio/core/Core.gwt.xml b/src/gwt/src/org/rstudio/core/Core.gwt.xml
new file mode 100644
index 0000000..51a3446
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/Core.gwt.xml
@@ -0,0 +1,59 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
+<module>
+ <inherits name='com.google.gwt.user.User'/>
+ <inherits name='com.google.gwt.json.JSON' />
+ <inherits name='com.google.gwt.http.HTTP' />
+
+ <generate-with class="org.rstudio.core.rebind.command.CommandBundleGenerator" >
+ <when-type-assignable
+ class="org.rstudio.core.client.command.CommandBundle"/>
+ </generate-with>
+ <generate-with class="org.rstudio.core.rebind.command.CommandBinderGenerator" >
+ <when-type-assignable
+ class="org.rstudio.core.client.command.CommandBinder"/>
+ </generate-with>
+ <generate-with class="org.rstudio.core.rebind.AsyncShimGenerator" >
+ <when-type-assignable
+ class="org.rstudio.core.client.AsyncShim"/>
+ </generate-with>
+ <generate-with class="org.rstudio.core.rebind.JavaScriptPassthroughGenerator" >
+ <when-type-assignable
+ class="org.rstudio.core.client.js.JavaScriptPassthrough"/>
+ </generate-with>
+ <generate-with class="org.rstudio.core.rebind.command.JsObjectInjectorGenerator" >
+ <when-type-assignable
+ class="org.rstudio.core.client.js.JsObjectInjector"/>
+ </generate-with>
+
+ <replace-with class="org.rstudio.core.client.dom.impl.DomUtilsStandardImpl">
+ <when-type-is class="org.rstudio.core.client.dom.impl.DomUtilsImpl" />
+ </replace-with>
+
+ <replace-with class="org.rstudio.core.client.dom.impl.DomUtilsIE8Impl">
+ <when-type-is class="org.rstudio.core.client.dom.impl.DomUtilsImpl" />
+ <any>
+ <when-property-is name="user.agent" value="ie8" />
+ </any>
+ </replace-with>
+
+ <replace-with class="org.rstudio.core.client.BrowseCapFirefox">
+ <when-type-is class="org.rstudio.core.client.BrowseCap"/>
+ <when-property-is name="user.agent" value="gecko1_8" />
+ </replace-with>
+ <replace-with class="org.rstudio.core.client.BrowseCapSafari">
+ <when-type-is class="org.rstudio.core.client.BrowseCap"/>
+ <any>
+ <when-property-is name="user.agent" value="safari" />
+ <when-property-is name="user.agent" value="ie9" />
+ </any>
+ </replace-with>
+
+ <replace-with class="org.rstudio.core.client.theme.ThemeFonts.WebThemeFontLoader">
+ <when-type-is class="org.rstudio.core.client.theme.ThemeFonts.ThemeFontLoader"/>
+ </replace-with>
+ <replace-with class="org.rstudio.core.client.theme.ThemeFonts.DesktopThemeFontLoader">
+ <when-type-is class="org.rstudio.core.client.theme.ThemeFonts.ThemeFontLoader"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+</module>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/AsyncShim.java b/src/gwt/src/org/rstudio/core/client/AsyncShim.java
new file mode 100644
index 0000000..55b615f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/AsyncShim.java
@@ -0,0 +1,111 @@
+/*
+ * AsyncShim.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * AsyncShim makes it easy to sequester a class behind GWT.runAsync()
+ * and cause it to be lazy-loaded, while still making it convenient to
+ * call (some types of) methods on it from other parts of the code that
+ * are not in the same fragment.
+ *
+ * For each type that needs to be sequestered, create an *abstract*
+ * AsyncShim subclass with the TTarget type parameter set to the
+ * sequestered type. (The AsyncShim subclass must be at package-level
+ * or public visibility.)
+ *
+ * In your AsyncShim subclass, you'll want to override onDelayLoadSuccess
+ * and onDelayLoadFailure to do whatever your app needs. onDelayLoadSuccess
+ * will be called only once, when the instance of TTarget is first created.
+ *
+ * You can also add any other fields, methods, etc. to your subclass that
+ * are necessary to give onDelayLoadSuccess and/or onDelayLoadFailure any
+ * contextual state they need.
+ *
+ * As mentioned, AsyncShim can make some kinds of methods easy to call from
+ * other fragments. Specifically, any method that returns void and
+ * semantically makes sense to run asynchronously, can be easily passed
+ * through AsyncShim. Just stub out an abstract version of the method on
+ * your AsyncShim subclass, and deferred binding will take care of wiring
+ * everything up. Or, you can have your AsyncShim subclass "implement" an
+ * interface but not actually provide implementations--if these methods
+ * return void, they will also be automatically wired up.
+ *
+ * @param <TTarget> The type to be sequestered
+ */
+public abstract class AsyncShim<TTarget>
+{
+ /**
+ * [DON'T override this, it will be overridden by the code generator]
+ *
+ * This method must be called before the target object is needed.
+ */
+ @Inject
+ public void initialize(Provider<TTarget> provider)
+ {
+ }
+
+ public final void forceLoad(boolean downloadCodeOnly)
+ {
+ forceLoad(downloadCodeOnly, null);
+ }
+
+ /**
+ * [DON'T override this, it will be overridden by the code generator]
+ *
+ * Call this to force the code to be downloaded, and optionally, for
+ * the provider to be invoked
+ *
+ * @param downloadCodeOnly If true, the code will be downloaded but
+ * the provider will not be invoked
+ * @param continuation A command to invoke after the load is complete
+ * (regardless of whether the effort was successful or not). Can be
+ * null.
+ */
+ public void forceLoad(boolean downloadCodeOnly, Command continuation)
+ {
+ }
+
+ /**
+ * You can override this to do something asynchronous between when the
+ * code loads and when the instance is created.
+ * @param continuation You MUST call this (eventually) to allow execution
+ * to proceed
+ */
+ protected void preInstantiationHook(Command continuation)
+ {
+ continuation.execute();
+ }
+
+ /**
+ * You can override this to do something with the delayed type once the
+ * code loads and the instance is created.
+ * @param obj
+ */
+ protected void onDelayLoadSuccess(TTarget obj)
+ {
+ }
+
+ /**
+ * You can (should!) override this to deal with failure cases.
+ * @param reason
+ */
+ protected void onDelayLoadFailure(Throwable reason)
+ {
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Barrier.java b/src/gwt/src/org/rstudio/core/client/Barrier.java
new file mode 100644
index 0000000..558e148
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Barrier.java
@@ -0,0 +1,81 @@
+/*
+ * Barrier.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.event.shared.HandlerManager;
+import org.rstudio.core.client.events.BarrierReleasedEvent;
+import org.rstudio.core.client.events.BarrierReleasedHandler;
+
+/**
+ * Use this class to track when multiple independent operations are all
+ * completed.
+ *
+ * Each operation should call acquire() when it begins to get a token,
+ * then release() on the token when the operation is complete. When all
+ * outstanding tokens are released, then BarrierReleasedEvent is fired.
+ *
+ * As a safety precaution, all but the first call to release() on a
+ * given token is a no-op.
+ */
+public class Barrier
+{
+ public class Token
+ {
+ public Token()
+ {
+ }
+
+ /**
+ * Call when the operation is completed (or errors out, or is aborted,
+ * etc.)
+ */
+ public void release()
+ {
+ if (!released_)
+ {
+ released_ = true;
+ Barrier.this.release();
+ }
+ }
+
+ private boolean released_;
+ }
+
+ /**
+ * Call at the beginning of an operation.
+ */
+ public Token acquire()
+ {
+ count_++;
+ return new Token();
+ }
+
+ public void addBarrierReleasedHandler(BarrierReleasedHandler handler)
+ {
+ handlers_.addHandler(BarrierReleasedEvent.TYPE, handler);
+ }
+
+
+ private void release()
+ {
+ if (--count_ == 0)
+ {
+ handlers_.fireEvent(new BarrierReleasedEvent());
+ }
+ }
+
+ private int count_;
+ private HandlerManager handlers_ = new HandlerManager(this);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/BrowseCap.java b/src/gwt/src/org/rstudio/core/client/BrowseCap.java
new file mode 100644
index 0000000..34952d3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/BrowseCap.java
@@ -0,0 +1,208 @@
+/*
+ * BrowseCap.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import org.rstudio.core.client.theme.ThemeFonts;
+import org.rstudio.core.client.widget.FontDetector;
+import org.rstudio.studio.client.application.Desktop;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+
+public class BrowseCap
+{
+ public static double getFontSkew()
+ {
+ if (hasMetaKey())
+ return -1;
+
+ else if (FIXED_UBUNTU_MONO)
+ {
+ if (isFirefox())
+ return 1;
+ else
+ return 0.4;
+ }
+ else if (!Desktop.isDesktop() && isWindows())
+ return 0.4;
+ else
+ return 0;
+ }
+
+ public static final BrowseCap INSTANCE = GWT.create(BrowseCap.class);
+
+ public boolean suppressBraceHighlighting()
+ {
+ return false;
+ }
+
+ public boolean aceVerticalScrollBarIssue()
+ {
+ return false;
+ }
+
+ public boolean suppressBrowserForwardBack()
+ {
+ return false;
+ }
+
+ public boolean hasWindowFind()
+ {
+ return true;
+ }
+
+ public static boolean hasMetaKey()
+ {
+ return isMacintosh();
+ }
+
+ public static boolean isMacintosh()
+ {
+ return OPERATING_SYSTEM.equals("macintosh");
+ }
+
+ public static boolean isMacintoshDesktop()
+ {
+ return Desktop.isDesktop() && isMacintosh();
+ }
+
+ public static boolean isCocoaDesktop()
+ {
+ return Desktop.isDesktop() && Desktop.getFrame().isCocoa();
+ }
+
+ public static boolean isWindows()
+ {
+ return OPERATING_SYSTEM.equals("windows");
+ }
+
+ public static boolean isWindowsDesktop()
+ {
+ return Desktop.isDesktop() && isWindows();
+ }
+
+ public static boolean isLinux()
+ {
+ return OPERATING_SYSTEM.equals("linux");
+ }
+
+ public static boolean isLinuxDesktop()
+ {
+ return Desktop.isDesktop() && isLinux();
+ }
+
+ public static boolean hasUbuntuFonts()
+ {
+ return FIXED_UBUNTU_MONO;
+ }
+
+ public static boolean isChrome()
+ {
+ return isUserAgent("chrome");
+ }
+
+ public static boolean isChromeLinux()
+ {
+ return isChrome() && isLinux();
+ }
+
+ public static boolean isFirefox()
+ {
+ return isUserAgent("firefox");
+ }
+
+ public static boolean isChromeFrame()
+ {
+ return isUserAgent("chromeframe");
+ }
+
+ public static boolean isRetina()
+ {
+ if (Desktop.isDesktop())
+ return Desktop.getFrame().isRetina();
+ else
+ return getIsRetina();
+ }
+
+ private static native final boolean getIsRetina() /*-{
+ try
+ {
+ return ((('devicePixelRatio' in $wnd) &&
+ ($wnd.devicePixelRatio == 2)) ||
+ (('matchMedia' in $wnd) &&
+ $wnd.matchMedia("(min-resolution:144dpi)").matches));
+ }
+ catch(ex)
+ {
+ return false;
+ }
+ }-*/;
+
+ private static native final boolean isUserAgent(String uaTest) /*-{
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf(uaTest) != -1)
+ return true;
+ else
+ return false;
+ }-*/;
+
+ private static native final String getOperatingSystem() /*-{
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf("linux") != -1) {
+ return "linux";
+ } else if (ua.indexOf("macintosh") != -1) {
+ return "macintosh";
+ }
+ return "windows";
+ }-*/;
+ private static final String OPERATING_SYSTEM = getOperatingSystem();
+
+ private static final boolean getFixedUbuntuMono()
+ {
+ if (isLinux())
+ {
+ // get fixed width font
+ String fixedWidthFont = ThemeFonts.getFixedWidthFont();
+
+ // in desktop mode we'll get an exact match whereas in web mode
+ // we'll get a list of fonts so we need to do an additional probe
+ if (Desktop.isDesktop())
+ return StringUtil.notNull(fixedWidthFont).equals("\"Ubuntu Mono\"");
+ else
+ return FontDetector.isFontSupported("Ubuntu Mono");
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ private static final boolean FIXED_UBUNTU_MONO = getFixedUbuntuMono();
+
+ static
+ {
+ Document.get().getBody().addClassName(OPERATING_SYSTEM);
+
+ if (FIXED_UBUNTU_MONO)
+ {
+ Document.get().getBody().addClassName("ubuntu_mono");
+
+ if (isFirefox())
+ Document.get().getBody().addClassName("ubuntu_mono_firefox");
+ }
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/BrowseCapFirefox.java b/src/gwt/src/org/rstudio/core/client/BrowseCapFirefox.java
new file mode 100644
index 0000000..742d9b6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/BrowseCapFirefox.java
@@ -0,0 +1,24 @@
+/*
+ * BrowseCapFirefox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class BrowseCapFirefox extends BrowseCap
+{
+ @Override
+ public boolean suppressBrowserForwardBack()
+ {
+ return true;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/BrowseCapIE8.java b/src/gwt/src/org/rstudio/core/client/BrowseCapIE8.java
new file mode 100644
index 0000000..070b457
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/BrowseCapIE8.java
@@ -0,0 +1,31 @@
+/*
+ * BrowseCapIE8.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class BrowseCapIE8 extends BrowseCap
+{
+
+ @Override
+ public boolean suppressBraceHighlighting()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean hasWindowFind()
+ {
+ return false;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/BrowseCapSafari.java b/src/gwt/src/org/rstudio/core/client/BrowseCapSafari.java
new file mode 100644
index 0000000..9941ed2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/BrowseCapSafari.java
@@ -0,0 +1,24 @@
+/*
+ * BrowseCapSafari.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class BrowseCapSafari extends BrowseCap
+{
+ @Override
+ public boolean aceVerticalScrollBarIssue()
+ {
+ return true;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/CodeNavigationTarget.java b/src/gwt/src/org/rstudio/core/client/CodeNavigationTarget.java
new file mode 100644
index 0000000..39cc186
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/CodeNavigationTarget.java
@@ -0,0 +1,42 @@
+/*
+ * CodeNavigationTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class CodeNavigationTarget
+{
+ public CodeNavigationTarget(String file)
+ {
+ this(file, null);
+ }
+
+ public CodeNavigationTarget(String file, FilePosition pos)
+ {
+ file_ = file;
+ pos_ = pos;
+ }
+
+ public String getFile()
+ {
+ return file_;
+ }
+
+ public FilePosition getPosition()
+ {
+ return pos_;
+ }
+
+ private final String file_;
+ private final FilePosition pos_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/CommandUtil.java b/src/gwt/src/org/rstudio/core/client/CommandUtil.java
new file mode 100644
index 0000000..b8da578
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/CommandUtil.java
@@ -0,0 +1,40 @@
+/*
+ * CommandUtil.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.user.client.Command;
+
+public class CommandUtil
+{
+ public static final Command NULL = new Command() {
+ public void execute()
+ {
+ }
+ };
+
+ public static Command join(final Command a, final Command b)
+ {
+ return new Command()
+ {
+ public void execute()
+ {
+ if (a != null)
+ a.execute();
+ if (b != null)
+ b.execute();
+ }
+ };
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/CommandWithArg.java b/src/gwt/src/org/rstudio/core/client/CommandWithArg.java
new file mode 100644
index 0000000..2206375
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/CommandWithArg.java
@@ -0,0 +1,20 @@
+/*
+ * CommandWithArg.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public interface CommandWithArg<T>
+{
+ void execute(T arg);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/CsvReader.java b/src/gwt/src/org/rstudio/core/client/CsvReader.java
new file mode 100644
index 0000000..ed2718a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/CsvReader.java
@@ -0,0 +1,109 @@
+/*
+ * CsvReader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class CsvReader implements Iterable<String[]>
+{
+ public CsvReader(String data)
+ {
+ data_ = data;
+ }
+
+ public Iterator<String[]> iterator()
+ {
+ return new Iterator<String[]>()
+ {
+ private int pos = 0;
+
+ public boolean hasNext()
+ {
+ return pos < data_.length();
+ }
+
+ public String[] next()
+ {
+ ArrayList<String> list = new ArrayList<String>();
+ StringBuilder chunk = new StringBuilder();
+
+ final int START = 0;
+ final int IN_UNQUOTED = 1;
+ final int IN_QUOTE = 2;
+ final int QUOTE_ENDED = 3;
+
+ int state = START;
+ for ( ; pos < data_.length(); pos++)
+ {
+ char c = data_.charAt(pos);
+
+ if (c == '\n' && state != IN_QUOTE)
+ {
+ pos++;
+ break;
+ }
+ if (c == ',' && state != IN_QUOTE)
+ {
+ if (state != QUOTE_ENDED)
+ list.add(chunk.toString());
+ chunk = new StringBuilder();
+ state = START;
+ continue;
+ }
+ if (c == '"' && state == START)
+ {
+ state = IN_QUOTE;
+ continue;
+ }
+ if (c == '"' && state == IN_QUOTE)
+ {
+ int lookahead = (pos < data_.length() - 1)
+ ? data_.charAt(pos+1)
+ : -1;
+ if (lookahead == '"')
+ {
+ chunk.append((char)lookahead);
+ pos++;
+ continue;
+ }
+ list.add(chunk.toString());
+ chunk = new StringBuilder();
+ state = QUOTE_ENDED;
+ continue;
+ }
+
+ if (state == START)
+ state = IN_UNQUOTED;
+ chunk.append(c);
+ }
+
+ if (state != QUOTE_ENDED)
+ {
+ list.add(chunk.toString());
+ }
+
+ return list.toArray(new String[0]);
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ private final String data_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/CsvWriter.java b/src/gwt/src/org/rstudio/core/client/CsvWriter.java
new file mode 100644
index 0000000..f88ac45
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/CsvWriter.java
@@ -0,0 +1,56 @@
+/*
+ * CsvWriter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class CsvWriter
+{
+ public void writeValue(String value)
+ {
+ onValueBegin();
+
+ if (!value.contains("\n") && !value.contains(","))
+ sw.append(value);
+ else
+ sw.append('"' + value.replace("\"", "\"\"") + '"');
+ }
+
+ private void onValueBegin()
+ {
+ if (firstValue)
+ firstValue = false;
+ else
+ sw.append(',');
+ }
+
+ public void endLine()
+ {
+ sw.append("\n");
+ firstValue = true;
+ }
+
+ public String getValue()
+ {
+ return sw.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getValue();
+ }
+
+ private boolean firstValue = true;
+ private final StringBuffer sw = new StringBuffer();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Debug.java b/src/gwt/src/org/rstudio/core/client/Debug.java
new file mode 100644
index 0000000..e639ebf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Debug.java
@@ -0,0 +1,119 @@
+/*
+ * Debug.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.user.client.ui.AttachDetachException;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.server.ServerError;
+
+public class Debug
+{
+ public static native void injectDebug() /*-{
+ $Debug = {
+ log: $entry(function(message) {
+ @org.rstudio.core.client.Debug::log(Ljava/lang/String;)(message);
+ })
+ };
+ $wnd['$Debug'] = $Debug;
+ }-*/;
+
+ public static void log(String message)
+ {
+ GWT.log(message, null) ;
+ logToConsole(message) ;
+ }
+
+ public static native void logToConsole(String message) /*-{
+ if (typeof(console) != "undefined")
+ {
+ console.log(message) ;
+ }
+ }-*/ ;
+
+ public static <T> T printValue(String label, T value)
+ {
+ Debug.log(label + '=' + value);
+ return value;
+ }
+
+ public static void printStackTrace(String label)
+ {
+ StringBuffer buf = new StringBuffer(label + "\n");
+ for (StackTraceElement ste : new Throwable().getStackTrace())
+ {
+ buf.append("\tat " + ste + "\n");
+ }
+ log(buf.toString());
+ }
+
+ public static void logError(ServerError error)
+ {
+ Debug.log(error.toString());
+ }
+
+ /**
+ * Same as log() but for short-term messages that should not be checked
+ * in. Making this a different method from log() allows devs to use Find
+ * Usages to get rid of all calls before checking in changes.
+ */
+ public static void devlog(String label)
+ {
+ log(label);
+ }
+
+ public static <T> T devlog(String label, T passthrough)
+ {
+ Debug.devlog(label);
+ return passthrough;
+ }
+
+ public static void dump(JavaScriptObject object)
+ {
+ Debug.log(new JSONObject(object).toString());
+ }
+
+ public static void logAttachDetachException(AttachDetachException ade)
+ {
+ if (ade == null)
+ return;
+
+ for (Throwable t : ade.getCauses())
+ {
+ if (t instanceof AttachDetachException)
+ logAttachDetachException((AttachDetachException) t);
+ else
+ {
+ Debug.devlog(t.toString());
+ for (StackTraceElement ste : t.getStackTrace())
+ Debug.devlog(ste.toString());
+ }
+ }
+ }
+
+ public static void devlogf(String format,
+ Object... args)
+ {
+ int i = 0;
+ for (Object arg : args)
+ {
+ format = format.replaceFirst(Pattern.escape("{" + (i++) + "}"),
+ arg == null ? "NULL" : arg.toString());
+ }
+ devlog(format);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/DebugFilePosition.java b/src/gwt/src/org/rstudio/core/client/DebugFilePosition.java
new file mode 100644
index 0000000..ccbaa77
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/DebugFilePosition.java
@@ -0,0 +1,73 @@
+/*
+ * DebugFilePosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DebugFilePosition extends JavaScriptObject
+{
+ protected DebugFilePosition() {}
+
+ public static native DebugFilePosition create(
+ int line,
+ int endLine,
+ int column,
+ int endColumn) /*-{
+ return {
+ line: line,
+ end_line: endLine,
+ column: column,
+ end_column: endColumn
+ };
+ }-*/;
+
+ public native final int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public native final int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public native final int getEndLine() /*-{
+ return this.end_line;
+ }-*/;
+
+ public native final int getEndColumn() /*-{
+ return this.end_column;
+ }-*/;
+
+ public final int compareTo(DebugFilePosition other)
+ {
+ if (other == null)
+ return 1;
+
+ int result = getLine() - other.getLine();
+ if (result != 0)
+ return result;
+
+ return getColumn() - other.getColumn();
+ }
+
+ public native final DebugFilePosition functionRelativePosition(
+ int startLine) /*-{
+ return {
+ line: this.line - startLine,
+ column: this.column,
+ end_line: this.end_line - startLine,
+ end_column: this.end_column
+ }
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/DuplicateHelper.java b/src/gwt/src/org/rstudio/core/client/DuplicateHelper.java
new file mode 100644
index 0000000..10b95fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/DuplicateHelper.java
@@ -0,0 +1,275 @@
+/*
+ * DuplicateHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import java.util.*;
+
+public class DuplicateHelper
+{
+ /**
+ * Provides information about duplicates in the list that was tested.
+ */
+ public static class DuplicationInfo<T>
+ {
+ public DuplicationInfo(Comparator<T> comparator)
+ {
+ comparator_ = comparator;
+ }
+
+ /**
+ * For a given value, return how many times it appears in the original
+ * value list (or 0 if never).
+ */
+ public int occurrences(T value)
+ {
+ for (Pair<T, Integer> count : valueCounts_)
+ if (0 == comparator_.compare(value, count.first))
+ return count.second;
+ return 0;
+ }
+
+ /**
+ * Returns a list, each element of which is a list of indices of elements
+ * in the original value list whose values are duplicates.
+ *
+ * For example:
+ *
+ * a = ["foo", "bar", "bar", "bar", "foo"]
+ * dupeInfo = detectDupes(a)
+ * dupeInfo.dupes() ==> [ [0,4], [1,2,3] ]
+ */
+ public ArrayList<ArrayList<Integer>> dupes()
+ {
+ return dupes_;
+ }
+
+ void addDupeInfo(T value, ArrayList<Integer> indices)
+ {
+ valueCounts_.add(new Pair<T, Integer>(value, indices.size()));
+ if (indices.size() > 1)
+ dupes_.add(indices);
+ }
+
+ private final Comparator<T> comparator_;
+ private ArrayList<ArrayList<Integer>> dupes_ =
+ new ArrayList<ArrayList<Integer>>();
+ private ArrayList<Pair<T, Integer>> valueCounts_ =
+ new ArrayList<Pair<T, Integer>>();
+ }
+
+ private static class CaseInsensitiveStringComparator implements Comparator<String>
+ {
+ public int compare(String s1, String s2)
+ {
+ return s1.compareToIgnoreCase(s2);
+ }
+ }
+
+ public static <T> int dedupeSortedList(ArrayList<T> list)
+ {
+ int removedCount = 0;
+
+ for (int i = list.size() - 1; i > 0; i--)
+ {
+ T x = list.get(i-1);
+ T y = list.get(i);
+ if (((x == null) == (y == null)) &&
+ ((x == null) || x.equals(y)))
+ {
+ list.remove(i);
+ removedCount++;
+ }
+ }
+
+ return removedCount;
+ }
+
+ /**
+ * Detect duplicates and calculate frequency information in the given
+ * list, according to the given comparator's definition of equality.
+ * The comparator must correctly support not only equality but also
+ * comparisons, since the duplicate detection algorithm relies on sorting.
+ */
+ public static <T> DuplicationInfo<T> detectDupes(List<T> list,
+ final Comparator<T> comparator)
+ {
+ ArrayList<Pair<Integer, T>> sorted = new ArrayList<Pair<Integer, T>>();
+ for (int i = 0; i < list.size(); i++)
+ {
+ sorted.add(new Pair<Integer, T>(i, list.get(i)));
+ }
+
+ // Sort our copy of the list, so dupes are right next to each other
+ Collections.sort(sorted, new Comparator<Pair<Integer, T>>()
+ {
+ public int compare(Pair<Integer, T> left,
+ Pair<Integer, T> right)
+ {
+ return comparator.compare(left.second, right.second);
+ }
+ });
+
+ DuplicationInfo<T> dupeInfo = new DuplicationInfo<T>(comparator);
+ ArrayList<Integer> currentDupes = new ArrayList<Integer>();
+ T lastSeenValue = null;
+ for (Pair<Integer, T> value : sorted)
+ {
+ if (lastSeenValue == null ||
+ comparator.compare(lastSeenValue, value.second) != 0)
+ {
+ // This value isn't the same as the previous one. If we've got
+ // dupes in our list, then add them to the results. Then start
+ // a new list.
+ if (currentDupes.size() > 0)
+ dupeInfo.addDupeInfo(lastSeenValue, currentDupes);
+ currentDupes = new ArrayList<Integer>();
+ }
+
+ // Add ourselves to the current list
+ currentDupes.add(value.first);
+ lastSeenValue = value.second;
+ }
+
+ if (currentDupes.size() > 0)
+ dupeInfo.addDupeInfo(lastSeenValue, currentDupes);
+
+ return dupeInfo;
+ }
+
+ /**
+ * Use Mac OS X style prettifying of paths. Display the filename,
+ * and if there are multiple entries with the same filename, append
+ * a disambiguating folder to those filenames.
+ */
+ public static ArrayList<String> getPathLabels(ArrayList<String> paths,
+ boolean includeExtension)
+ {
+ ArrayList<String> labels = new ArrayList<String>();
+ for (String entry : paths)
+ {
+ if (includeExtension)
+ labels.add(FileSystemItem.getNameFromPath(entry));
+ else
+ labels.add(FileSystemItem.createFile(entry).getStem());
+ }
+
+ DuplicationInfo<String> dupeInfo = DuplicateHelper.detectDupes(
+ labels, new CaseInsensitiveStringComparator());
+
+ for (ArrayList<Integer> dupeList : dupeInfo.dupes())
+ {
+ fixupDupes(paths, dupeList, labels);
+ }
+
+ dupeInfo = DuplicateHelper.detectDupes(
+ labels, new CaseInsensitiveStringComparator());
+
+ // There are edge cases where we may still end up with dupes at this
+ // point. In that case, just disambiguate using the full path.
+ // Example:
+ // ~/foo/tmp/README
+ // ~/bar/tmp/README
+ // ~/foo/README
+ // ~/bar/README
+ for (ArrayList<Integer> dupeList : dupeInfo.dupes())
+ {
+ for (Integer index : dupeList)
+ {
+ FileSystemItem fsi = FileSystemItem.createFile(
+ paths.get(index));
+ String name = includeExtension ? fsi.getName() : fsi.getStem();
+ labels.set(index, disambiguate(name,
+ fsi.getParentPathString()));
+ }
+ }
+
+
+ return labels;
+ }
+
+ private static void fixupDupes(ArrayList<String> fullPaths,
+ ArrayList<Integer> indices,
+ ArrayList<String> labels)
+ {
+ ArrayList<ArrayList<String>> pathElementListList =
+ new ArrayList<ArrayList<String>>();
+
+ for (Integer index : indices)
+ pathElementListList.add(toPathElements(fullPaths.get(index)));
+
+ while (indices.size() > 0)
+ {
+ ArrayList<String> lastPathElements = new ArrayList<String>();
+
+ for (int i = 0; i < pathElementListList.size(); i++)
+ {
+ ArrayList<String> pathElementList = pathElementListList.get(i);
+
+ if (pathElementList.size() == 0)
+ {
+ int trueIndex = indices.get(i);
+ String path = FileSystemItem.createFile(fullPaths.get(trueIndex))
+ .getParentPathString();
+ labels.set(trueIndex,
+ disambiguate(labels.get(trueIndex), path));
+
+ indices.remove(i);
+ pathElementListList.remove(i);
+ i--;
+ }
+ else
+ {
+ lastPathElements.add(
+ pathElementList.remove(pathElementList.size() - 1));
+ }
+ }
+
+
+ DuplicationInfo<String> dupeInfo = DuplicateHelper.detectDupes(
+ lastPathElements,
+ new CaseInsensitiveStringComparator());
+
+ for (int i = 0; i < lastPathElements.size(); i++)
+ {
+ if (1 == dupeInfo.occurrences(lastPathElements.get(i)))
+ {
+ int trueIndex = indices.get(i);
+ labels.set(trueIndex, disambiguate(labels.get(trueIndex),
+ lastPathElements.get(i)));
+
+ indices.remove(i);
+ pathElementListList.remove(i);
+ lastPathElements.remove(i);
+ i--;
+ }
+ }
+
+ assert indices.size() == pathElementListList.size();
+ }
+ }
+
+ private static String disambiguate(String filename, String disambiguatingPath)
+ {
+ return filename + " \u2014 " + disambiguatingPath;
+ }
+
+ private static ArrayList<String> toPathElements(String path)
+ {
+ FileSystemItem fsi = FileSystemItem.createFile(path);
+ return new ArrayList<String>(
+ Arrays.asList(fsi.getParentPathString().split("/")));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/ElementIds.java b/src/gwt/src/org/rstudio/core/client/ElementIds.java
new file mode 100644
index 0000000..a52677f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/ElementIds.java
@@ -0,0 +1,28 @@
+package org.rstudio.core.client;
+
+import com.google.gwt.dom.client.Element;
+
+public class ElementIds
+{
+ public static void assignElementId(Element ele, String id)
+ {
+ ele.setId(ID_PREFIX + id);
+ }
+
+ public static String getElementId(String id)
+ {
+ return ID_PREFIX + id;
+ }
+
+ private final static String ID_PREFIX = "rstudio_";
+
+ public final static String CONSOLE_INPUT = "console_input";
+ public final static String CONSOLE_OUTPUT = "console_output";
+ public final static String FIND_REPLACE_BAR = "find_replace_bar";
+ public final static String HELP_FRAME = "help_frame";
+ public final static String LOADING_SPINNER = "loading_image";
+ public final static String PLOT_IMAGE_FRAME = "plot_image_frame";
+ public final static String POPUP_COMPLETIONS = "popup_completions";
+ public final static String SHELL_WIDGET = "shell_widget";
+ public final static String SOURCE_TEXT_EDITOR = "source_text_editor";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/ExternalJavaScriptLoader.java b/src/gwt/src/org/rstudio/core/client/ExternalJavaScriptLoader.java
new file mode 100644
index 0000000..78838f0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/ExternalJavaScriptLoader.java
@@ -0,0 +1,132 @@
+/*
+ * ExternalJavaScriptLoader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.ScriptElement;
+
+import java.util.LinkedList;
+
+public class ExternalJavaScriptLoader
+{
+ public static interface Callback
+ {
+ void onLoaded();
+ }
+
+ private enum State
+ {
+ Start,
+ Loading,
+ Loaded,
+ Error
+ }
+
+ public static void loadSequentially(String[] urls, final Callback callback)
+ {
+ final LinkedList<ExternalJavaScriptLoader> loaders =
+ new LinkedList<ExternalJavaScriptLoader>();
+
+ for (String url : urls)
+ loaders.add(new ExternalJavaScriptLoader(url));
+
+ Callback innerCallback = new Callback()
+ {
+ public void onLoaded()
+ {
+ if (!loaders.isEmpty())
+ loaders.remove().addCallback(this);
+ else
+ callback.onLoaded();
+ }
+ };
+ innerCallback.onLoaded();
+ }
+
+ public ExternalJavaScriptLoader(String url)
+ {
+ this(Document.get(), url);
+ }
+
+ public ExternalJavaScriptLoader(Document document, String url)
+ {
+ document_ = document;
+ url_ = url;
+ }
+
+ public void addCallback(Callback callback)
+ {
+ switch (state_)
+ {
+ case Start:
+ callbacks_.add(callback);
+ startLoading();
+ break;
+ case Loading:
+ callbacks_.add(callback);
+ break;
+ case Loaded:
+ callback.onLoaded();
+ break;
+ case Error:
+ break;
+ }
+ }
+
+ private void startLoading()
+ {
+ assert state_ == State.Start;
+ ScriptElement script = document_.createScriptElement();
+ script.setType("text/javascript");
+ script.setSrc(url_);
+ registerCallback(script);
+ Element head = document_.getElementsByTagName("head").getItem(0);
+ head.appendChild(script);
+ }
+
+ private native void registerCallback(ScriptElement script) /*-{
+ var self = this;
+ script.onreadystatechange = $entry(function() {
+ if (this.readyState == 'complete')
+ self. at org.rstudio.core.client.ExternalJavaScriptLoader::onLoaded()();
+ });
+ script.onload = $entry(function() {
+ self. at org.rstudio.core.client.ExternalJavaScriptLoader::onLoaded()();
+ });
+ }-*/;
+
+ private void onLoaded()
+ {
+ state_ = State.Loaded;
+ Scheduler.get().scheduleIncremental(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ if (!callbacks_.isEmpty())
+ callbacks_.remove().onLoaded();
+
+ return !callbacks_.isEmpty();
+ }
+ });
+ }
+
+ private LinkedList<Callback> callbacks_ = new LinkedList<Callback>();
+ private State state_ = State.Start;
+ private final String url_;
+ private final Document document_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/FilePosition.java b/src/gwt/src/org/rstudio/core/client/FilePosition.java
new file mode 100644
index 0000000..71328e1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/FilePosition.java
@@ -0,0 +1,46 @@
+/*
+ * FilePosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FilePosition extends JavaScriptObject
+{
+ protected FilePosition() {}
+
+ public static native FilePosition create(int line, int column) /*-{
+ return {line: line, column: column};
+ }-*/;
+
+ public native final int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public native final int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public final int compareTo(FilePosition other)
+ {
+ if (other == null)
+ return 1;
+
+ int result = getLine() - other.getLine();
+ if (result != 0)
+ return result;
+
+ return getColumn() - other.getColumn();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/HandlerRegistrations.java b/src/gwt/src/org/rstudio/core/client/HandlerRegistrations.java
new file mode 100644
index 0000000..a7d0dea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/HandlerRegistrations.java
@@ -0,0 +1,42 @@
+/*
+ * HandlerRegistrations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+import java.util.ArrayList;
+
+public class HandlerRegistrations implements HandlerRegistration
+{
+ public HandlerRegistrations(HandlerRegistration... registrations)
+ {
+ for (HandlerRegistration reg : registrations)
+ add(reg);
+ }
+
+ public void add(HandlerRegistration reg)
+ {
+ registrations_.add(reg);
+ }
+
+ public void removeHandler()
+ {
+ while (registrations_.size() > 0)
+ registrations_.remove(0).removeHandler();
+ }
+
+ private final ArrayList<HandlerRegistration> registrations_ =
+ new ArrayList<HandlerRegistration>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/IntervalTracker.java b/src/gwt/src/org/rstudio/core/client/IntervalTracker.java
new file mode 100644
index 0000000..a1abdf9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/IntervalTracker.java
@@ -0,0 +1,47 @@
+/*
+ * IntervalTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class IntervalTracker
+{
+ public IntervalTracker(long intervalMillis, boolean startElapsed)
+ {
+ threshold_ = intervalMillis;
+ if (!startElapsed)
+ reset();
+ }
+
+ public void reset()
+ {
+ lastTime_ = System.currentTimeMillis();
+ }
+
+ // alternate verison of reset which will prevent subsequent checks
+ // for only the duration specified (rather than the full intervalMillis)
+ public void reset(long preventMillis)
+ {
+ long offsetMillis = Math.max(0, threshold_ - preventMillis);
+ lastTime_ = System.currentTimeMillis() - offsetMillis;
+ }
+
+ public boolean hasElapsed()
+ {
+ return lastTime_ == null
+ || System.currentTimeMillis() - lastTime_ >= threshold_;
+ }
+
+ private Long lastTime_;
+ private final long threshold_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Invalidation.java b/src/gwt/src/org/rstudio/core/client/Invalidation.java
new file mode 100644
index 0000000..e5bcd03
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Invalidation.java
@@ -0,0 +1,45 @@
+/*
+ * Invalidation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Invalidation
+{
+ public class Token
+ {
+ public Token(int thisSequence)
+ {
+ thisSequence_ = thisSequence;
+ }
+
+ public boolean isInvalid()
+ {
+ return thisSequence_ != sequence_;
+ }
+
+ private final int thisSequence_;
+ }
+
+ public void invalidate()
+ {
+ sequence_++;
+ }
+
+ public Token getInvalidationToken()
+ {
+ return new Token(sequence_);
+ }
+
+ private int sequence_ = 0;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/MessageDisplay.java b/src/gwt/src/org/rstudio/core/client/MessageDisplay.java
new file mode 100644
index 0000000..12bc09e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/MessageDisplay.java
@@ -0,0 +1,334 @@
+/*
+ * MessageDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import java.util.List;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Focusable;
+
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+public abstract class MessageDisplay
+{
+ // These constant values correspond to QMessageBox::Icon enum
+ public final static int MSG_INFO = 1;
+ public final static int MSG_WARNING = 2;
+ public final static int MSG_ERROR = 3;
+ public final static int MSG_QUESTION = 4;
+ public final static int MSG_POPUP_BLOCKED = 0;
+
+ public static class PromptWithOptionResult
+ {
+ public String input;
+ public boolean extraOption;
+ }
+
+ public abstract void promptForText(String title,
+ String label,
+ String initialValue,
+ OperationWithInput<String> operation);
+
+ public abstract void promptForText(String title,
+ String label,
+ String initialValue,
+ ProgressOperationWithInput<String> operation);
+
+ public abstract void promptForText(String title,
+ String label,
+ String initialValue,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<String> operation);
+
+ public void promptForPassword(
+ String title,
+ String label,
+ String initialValue,
+ // Null or "" means don't show an extra option
+ String rememberPasswordPrompt,
+ boolean rememberByDefault,
+ ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation)
+ {
+ promptForTextWithOption(title,
+ label,
+ initialValue,
+ true,
+ rememberPasswordPrompt,
+ rememberByDefault,
+ okOperation,
+ cancelOperation);
+ }
+
+ public abstract void promptForTextWithOption(
+ String title,
+ String label,
+ String initialValue,
+ boolean showPasswordMask,
+ // Null or "" means don't show an extra option
+ String extraOption,
+ boolean extraOptionDefault,
+ ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation);
+
+ public abstract void promptForInteger(
+ String title,
+ String label,
+ Integer initialValue,
+ ProgressOperationWithInput<Integer> okOperation,
+ Operation cancelOperation);
+
+ protected abstract DialogBuilder createDialog(int type,
+ String caption,
+ String message);
+
+ public void showMessage(int type, String caption, String message)
+ {
+ createDialog(type, caption, message).showModal();
+ }
+
+ public void showMessage(int type,
+ String caption,
+ String message,
+ Operation dismissed)
+ {
+ createDialog(type, caption, message)
+ .addButton("OK", dismissed)
+ .showModal();
+ }
+
+ public void showMessage(int type,
+ String caption,
+ String message,
+ Operation dismissed,
+ String okLabel,
+ boolean includeCancel)
+ {
+ DialogBuilder dialog = createDialog(type, caption, message)
+ .addButton(okLabel, dismissed);
+ if (includeCancel)
+ dialog.addButton("Cancel");
+ dialog.showModal();
+ }
+
+ public void showMessage(int type,
+ String caption,
+ String message,
+ final Focusable focusAfter)
+ {
+ createDialog(type, caption, message)
+ .addButton("OK", new Operation() {
+
+ public void execute()
+ {
+ FocusHelper.setFocusDeferred(focusAfter);
+ }
+ })
+ .showModal();
+ }
+
+ public void showMessage(int type,
+ String caption,
+ String message,
+ final CanFocus focusAfter)
+ {
+ createDialog(type, caption, message)
+ .addButton("OK", new Operation() {
+
+ public void execute()
+ {
+ FocusHelper.setFocusDeferred(focusAfter);
+ }
+ })
+ .showModal();
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ Operation yesOperation,
+ boolean yesIsDefault)
+ {
+ createDialog(type, caption, message)
+ .addButton("Yes", yesOperation)
+ .addButton("No")
+ .setDefaultButton(yesIsDefault ? 0 : 1)
+ .showModal();
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ ProgressOperation yesOperation,
+ boolean yesIsDefault)
+ {
+ createDialog(type, caption, message)
+ .addButton("Yes", yesOperation)
+ .addButton("No")
+ .setDefaultButton(yesIsDefault ? 0 : 1)
+ .showModal();
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ boolean includeCancel,
+ Operation yesOperation,
+ Operation noOperation,
+ boolean yesIsDefault)
+ {
+ DialogBuilder dialog = createDialog(type, caption, message)
+ .addButton("Yes", yesOperation)
+ .addButton("No", noOperation)
+ .setDefaultButton(yesIsDefault ? 0 : 1);
+ if (includeCancel)
+ dialog.addButton("Cancel");
+ dialog.showModal();
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ boolean includeCancel,
+ final Operation yesOperation,
+ final Operation noOperation,
+ final Operation cancelOperation,
+ String yesLabel,
+ String noLabel,
+ boolean yesIsDefault)
+ {
+ DialogBuilder dialog = createDialog(type, caption, message)
+ .addButton(yesLabel, yesOperation)
+ .addButton(noLabel, noOperation)
+ .setDefaultButton(yesIsDefault ? 0 : 1);
+ if (includeCancel)
+ dialog.addButton("Cancel", cancelOperation);
+ dialog.showModal();
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ boolean includeCancel,
+ ProgressOperation yesOperation,
+ ProgressOperation noOperation,
+ boolean yesIsDefault)
+ {
+ showYesNoMessage(type,
+ caption,
+ message,
+ includeCancel,
+ yesOperation,
+ noOperation,
+ "Yes",
+ "No",
+ yesIsDefault);
+ }
+
+ public void showYesNoMessage(int type,
+ String caption,
+ String message,
+ boolean includeCancel,
+ ProgressOperation yesOperation,
+ ProgressOperation noOperation,
+ String yesLabel,
+ String noLabel,
+ boolean yesIsDefault)
+ {
+ DialogBuilder dialog = createDialog(type, caption, message)
+ .addButton(yesLabel, yesOperation)
+ .addButton(noLabel, noOperation)
+ .setDefaultButton(yesIsDefault ? 0 : 1);
+ if (includeCancel)
+ dialog.addButton("Cancel");
+ dialog.showModal();
+ }
+
+ public void showGenericDialog(int type,
+ String caption,
+ String message,
+ List<String> buttonLabels,
+ List<Operation> buttonOperations,
+ int defaultButton)
+ {
+ DialogBuilder dialog = createDialog(type, caption, message);
+ int numButtons = Math.min(buttonLabels.size(), buttonOperations.size());
+ for (int i = 0; i < numButtons; i++)
+ {
+ dialog.addButton(buttonLabels.get(i),
+ buttonOperations.get(i));
+ }
+ dialog.setDefaultButton(defaultButton);
+ dialog.showModal();
+ }
+
+ public void showErrorMessage(String caption, String message)
+ {
+ createDialog(MSG_ERROR, caption, message).showModal();
+ }
+
+ public void showErrorMessage(String caption,
+ String message,
+ Operation dismissed)
+ {
+ createDialog(MSG_ERROR, caption, message)
+ .addButton("OK", dismissed)
+ .showModal();
+ }
+
+ public void showErrorMessage(String caption,
+ String message,
+ Focusable focusAfter)
+ {
+ showMessage(MSG_ERROR, caption, message, focusAfter);
+ }
+
+ public void showErrorMessage(String caption,
+ String message,
+ CanFocus focusAfter)
+ {
+ showMessage(MSG_ERROR, caption, message, focusAfter);
+ }
+
+ public void showPopupBlockedMessage(Operation yesOperation)
+ {
+ showYesNoMessage(
+ GlobalDisplay.MSG_POPUP_BLOCKED,
+ "Popup Blocked",
+ "We attempted to open an external browser window, but " +
+ "the action was prevented by your popup blocker. You " +
+ "can attempt to open the window again by pressing the " +
+ "\"Try Again\" button below.\n\n" +
+ "NOTE: To prevent seeing this message in the future, you " +
+ "should configure your browser to allow popup windows " +
+ "for " + Window.Location.getHostName() + ".",
+ false,
+ yesOperation,
+ new Operation()
+ {
+ public void execute()
+ {
+
+ }
+ },
+ null,
+ "Try Again",
+ "Cancel",
+ true);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Pair.java b/src/gwt/src/org/rstudio/core/client/Pair.java
new file mode 100644
index 0000000..3057862
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Pair.java
@@ -0,0 +1,27 @@
+/*
+ * Pair.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Pair<TFirst, TSecond>
+{
+ public Pair(TFirst first, TSecond second)
+ {
+ this.first = first;
+ this.second = second;
+ }
+
+ public TFirst first;
+ public TSecond second;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Point.java b/src/gwt/src/org/rstudio/core/client/Point.java
new file mode 100644
index 0000000..25b9185
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Point.java
@@ -0,0 +1,71 @@
+/*
+ * Point.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Point
+{
+ public final int x;
+ public final int y;
+
+ public Point(int x, int y)
+ {
+ super() ;
+ this.x = x ;
+ this.y = y ;
+ }
+
+ public int getX()
+ {
+ return x ;
+ }
+
+ public int getY()
+ {
+ return y ;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31 ;
+ int result = 1 ;
+ result = prime * result + x ;
+ result = prime * result + y ;
+ return result ;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ Point other = (Point) obj ;
+ if (x != other.x)
+ return false ;
+ if (y != other.y)
+ return false ;
+ return true ;
+ }
+
+ @Override
+ public String toString()
+ {
+ return x + ", " + y;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Rectangle.java b/src/gwt/src/org/rstudio/core/client/Rectangle.java
new file mode 100644
index 0000000..59c6ff1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Rectangle.java
@@ -0,0 +1,306 @@
+/*
+ * Rectangle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Rectangle
+{
+ public Rectangle(int x, int y, int width, int height)
+ {
+ super() ;
+ this.x = x ;
+ this.y = y ;
+ this.width = width ;
+ this.height = height ;
+ }
+
+ // Eclipse auto-generated
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31 ;
+ int result = 1 ;
+ result = prime * result + height ;
+ result = prime * result + width ;
+ result = prime * result + x ;
+ result = prime * result + y ;
+ return result ;
+ }
+
+ // Eclipse auto-generated
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ Rectangle other = (Rectangle) obj ;
+ if (height != other.height)
+ return false ;
+ if (width != other.width)
+ return false ;
+ if (x != other.x)
+ return false ;
+ if (y != other.y)
+ return false ;
+ return true ;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Rectangle(x=" + x + ",y=" + y +
+ ",w=" + width + ",h=" + height + ")";
+ }
+
+ public int getLeft()
+ {
+ return x ;
+ }
+
+ public int getTop()
+ {
+ return y ;
+ }
+
+ public int getWidth()
+ {
+ return width ;
+ }
+
+ public int getHeight()
+ {
+ return height ;
+ }
+
+ public int getRight()
+ {
+ return x + width ;
+ }
+
+ public int getBottom()
+ {
+ return y + height ;
+ }
+
+ public Point getLocation()
+ {
+ return new Point(x, y) ;
+ }
+
+ public Size getSize()
+ {
+ return new Size(width, height) ;
+ }
+
+ public Point getCorner(boolean left, boolean top)
+ {
+ return new Point(left ? getLeft() : getRight(),
+ top ? getTop() : getBottom()) ;
+ }
+
+ public Rectangle move(int x, int y)
+ {
+ return new Rectangle(x, y, getWidth(), getHeight());
+ }
+
+ /**
+ * Returns the rectangular intersection of the two rectangles, or null if
+ * the rectangles do not touch anywhere.
+ * @param other The rectangle to intersect with this.
+ * @param canReturnEmptyRect If true, in cases where one of the
+ * rectangles has zero width or height OR cases where the rectangles share
+ * an edge, it's possible for a zero-width or zero-height rectangle to be
+ * returned. If false, then it's guaranteed that the return value will
+ * either be null or a rectangle with a positive area. Note that
+ * regardless of true or false, null can always be returned.
+ * @return The intersection, or null if none.
+ */
+ public Rectangle intersect(Rectangle other, boolean canReturnEmptyRect)
+ {
+ int left = Math.max(x, other.x);
+ int right = Math.min(getRight(), other.getRight());
+ int top = Math.max(getTop(), other.getTop());
+ int bottom = Math.min(getBottom(), other.getBottom());
+
+ if ((canReturnEmptyRect && left <= right && top <= bottom)
+ || (!canReturnEmptyRect && left < right && top < bottom))
+ {
+ return new Rectangle(left, top, right-left, bottom-top);
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Enlarge the rectangle in the given directions by the given amounts.
+ */
+ public Rectangle inflate(int left, int top, int right, int bottom)
+ {
+ return new Rectangle(x - left, y - top,
+ width + left + right, height + top + bottom);
+ }
+
+ /**
+ * Enlarge each side of the rectangle by the given amount. Note that e.g.
+ * 10 will result in 20-unit-greater width and height.
+ * @param inflateBy
+ * @return
+ */
+ public Rectangle inflate(int inflateBy)
+ {
+ return inflate(inflateBy, inflateBy, inflateBy, inflateBy);
+ }
+
+ /**
+ * Return the center point
+ */
+ public Point center()
+ {
+ return new Point((getLeft() + getRight()) / 2,
+ (getTop() + getBottom()) / 2);
+ }
+
+ /**
+ * Create a new rectangle of the given width and height that is centered
+ * relative to this rectangle.
+ */
+ public Rectangle createCenteredRect(int width, int height)
+ {
+ return new Rectangle(
+ (getWidth() - width) / 2,
+ (getHeight() - height) / 2,
+ width,
+ height);
+ }
+
+ /**
+ * Returns true if this rectangle ENTIRELY contains the given rectangle.
+ */
+ public boolean contains(Rectangle other)
+ {
+ return getLeft() <= other.getLeft() &&
+ getTop() <= other.getTop() &&
+ getRight() >= other.getRight() &&
+ getBottom() >= other.getBottom();
+ }
+
+ /**
+ * Intelligently figures out where to move this rectangle within a container
+ * to avoid another rectangle (the avoidee).
+ * @param avoidee The rectangle we're trying to avoid
+ * @param container The rectangle we need to try to stay within, if possible
+ * @return The new location for the rectangle
+ */
+ public Point avoidBounds(final Rectangle avoidee,
+ final Rectangle container)
+ {
+ // Check for nothing to avoid
+ if (avoidee == null)
+ return this.getLocation();
+
+ // Check for no collision
+ if (this.intersect(avoidee, false) == null)
+ return this.getLocation();
+
+ // Figure out whether the avoidee is in the top or bottom half of the
+ // container. vertDir < 0 means top half, vertDir > 0 means bottom half.
+ int vertDir = avoidee.center().getY() - container.center().getY();
+ // Create new bounds that are just below or just above the avoidee,
+ // depending on whether the avoidee is in the top or bottom half of
+ // the container, respectively.
+ Rectangle vertShift = this.move(
+ this.getLeft(),
+ vertDir > 0 ? avoidee.getTop() - this.getHeight()
+ : avoidee.getBottom());
+ // If that resulted in bounds that fit entirely in the container, then
+ // use it. (We prefer vertical shifting to horizontal shifting.)
+ if (container.contains(vertShift))
+ return vertShift.getLocation();
+
+ // Now repeat the algorithm in the horizontal dimension.
+ int horizDir = avoidee.center().getX() - container.center().getX();
+ Rectangle horizShift = this.move(
+ horizDir > 0 ? avoidee.getLeft() - this.getWidth()
+ : avoidee.getRight(),
+ this.getTop());
+ if (container.contains(horizShift))
+ return horizShift.getLocation();
+
+ // Both vertical and horizontal options go off the container. Combine
+ // their effects, then move to within the screen, if possible; or if all
+ // else fails, just center.
+ Rectangle hvShift = new Rectangle(horizShift.getLeft(),
+ vertShift.getTop(),
+ this.getWidth(),
+ this.getHeight());
+ return hvShift.attemptToMoveInto(container,
+ FailureMode.CENTER).getLocation();
+ }
+
+ public enum FailureMode
+ {
+ /**
+ * Center the rect's position in this dimension
+ */
+ CENTER,
+ /**
+ * Don't change the rect's position in this dimension
+ */
+ NO_CHANGE
+ }
+
+ /**
+ * Attempt to move this rectangle so that it fits inside the given container.
+ * If this rectangle is taller and/or wider than the container, then the
+ * failureMode parameter can be used to dictate what the fallback behavior
+ * should be.
+ */
+ public Rectangle attemptToMoveInto(Rectangle container,
+ FailureMode failureMode)
+ {
+ int newX = this.x;
+ int newY = this.y;
+
+ if (getWidth() <= container.getWidth())
+ {
+ newX = Math.min(Math.max(newX, container.getLeft()),
+ container.getRight() - getWidth());
+ }
+ else if (failureMode == FailureMode.CENTER)
+ {
+ newX = container.getLeft() - (getWidth()-container.getWidth())/2;
+ }
+
+ if (getHeight() <= container.getHeight())
+ {
+ newY = Math.min(Math.max(newY, container.getTop()),
+ container.getBottom() - getHeight());
+ }
+ else if (failureMode == FailureMode.CENTER)
+ {
+ newY = container.getTop() - (getHeight()-container.getHeight())/2;
+ }
+
+ return new Rectangle(newX, newY, getWidth(), getHeight());
+ }
+
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int height;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/ResultCallback.java b/src/gwt/src/org/rstudio/core/client/ResultCallback.java
new file mode 100644
index 0000000..2ac280d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/ResultCallback.java
@@ -0,0 +1,26 @@
+/*
+ * ResultCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+/**
+ * Provides a generic interface for handling success and/or failure of an
+ * operation, especially an asynchronous one.
+ */
+public abstract class ResultCallback<TSuccess, TFailure>
+{
+ public void onSuccess(TSuccess result) {}
+ public void onFailure(TFailure info) {}
+ public void onCancelled() {}
+}
diff --git a/src/gwt/src/org/rstudio/core/client/SafeHtmlUtil.java b/src/gwt/src/org/rstudio/core/client/SafeHtmlUtil.java
new file mode 100644
index 0000000..bc84c2a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/SafeHtmlUtil.java
@@ -0,0 +1,107 @@
+/*
+ * SafeHtmlUtil.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+public class SafeHtmlUtil
+{
+ public static void appendDiv(SafeHtmlBuilder sb,
+ String style,
+ String textContent)
+ {
+ sb.append(createOpenTag("div",
+ "class", style));
+ sb.appendEscaped(textContent);
+ sb.appendHtmlConstant("</div>");
+ }
+
+ public static void appendDiv(SafeHtmlBuilder sb,
+ String style,
+ SafeHtml htmlContent)
+ {
+ sb.append(createOpenTag("div",
+ "class", style));
+ sb.append(htmlContent);
+ sb.appendHtmlConstant("</div>");
+ }
+
+ public static void appendSpan(SafeHtmlBuilder sb,
+ String style,
+ String textContent)
+ {
+ sb.append(SafeHtmlUtil.createOpenTag("span",
+ "class", style));
+ sb.appendEscaped(textContent);
+ sb.appendHtmlConstant("</span>");
+ }
+
+ public static void appendSpan(SafeHtmlBuilder sb,
+ String style,
+ SafeHtml htmlContent)
+ {
+ sb.append(SafeHtmlUtil.createOpenTag("span",
+ "class", style));
+ sb.append(htmlContent);
+ sb.appendHtmlConstant("</span>");
+ }
+
+ public static void appendImage(SafeHtmlBuilder sb,
+ String style,
+ ImageResource image)
+ {
+ sb.append(SafeHtmlUtil.createOpenTag("img",
+ "class", style,
+ "src", image.getSafeUri().asString()));
+ sb.appendHtmlConstant("</img>");
+ }
+
+ public static SafeHtml createOpenTag(String tagName,
+ String... attribs)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<").append(tagName);
+ for (int i = 0; i < attribs.length; i += 2)
+ {
+ builder.append(' ')
+ .append(SafeHtmlUtils.htmlEscape(attribs[i]))
+ .append("=\"")
+ .append(SafeHtmlUtils.htmlEscape(attribs[i+1]))
+ .append("\"");
+ }
+ builder.append(">");
+ return SafeHtmlUtils.fromTrustedString(builder.toString());
+ }
+
+ public static SafeHtml createEmpty()
+ {
+ return SafeHtmlUtils.fromSafeConstant("");
+ }
+
+ public static SafeHtml concat(SafeHtml... pieces)
+ {
+ StringBuilder builder = new StringBuilder();
+ for (SafeHtml piece : pieces)
+ {
+ if (piece != null)
+ builder.append(piece.asString());
+ }
+ return SafeHtmlUtils.fromTrustedString(builder.toString());
+ }
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/SafeUriStringImpl.java b/src/gwt/src/org/rstudio/core/client/SafeUriStringImpl.java
new file mode 100644
index 0000000..eda515a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/SafeUriStringImpl.java
@@ -0,0 +1,55 @@
+/*
+ * SafeUriStringImpl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.safehtml.shared.SafeUri;
+
+public class SafeUriStringImpl implements SafeUri
+{
+ public SafeUriStringImpl(String value)
+ {
+ value_ = value;
+ }
+
+ @Override
+ public String asString()
+ {
+ return value_;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return value_.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null ^ value_ == null)
+ return false;
+ if (value_ == null)
+ return false;
+ return value_.equals(o.toString());
+ }
+
+ @Override
+ public String toString()
+ {
+ return value_;
+ }
+
+ private final String value_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/SeparatorManager.java b/src/gwt/src/org/rstudio/core/client/SeparatorManager.java
new file mode 100644
index 0000000..e4d612f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/SeparatorManager.java
@@ -0,0 +1,75 @@
+/*
+ * SeparatorManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import java.util.List;
+
+/**
+ * Implements a generic algorithm for managing visibility of separators in
+ * a list of items, some of which may not be visible.
+ *
+ * @param <TItem> The common supertype of items and separators.
+ */
+public abstract class SeparatorManager<TItem>
+{
+ protected abstract boolean isSeparator(TItem item);
+ protected abstract boolean isVisible(TItem item);
+ protected abstract void setVisible(TItem item, boolean visible);
+
+ public void manageSeparators(List<TItem> items)
+ {
+ /* allItems is a sorted list of items and separators. We'll make
+ * two separate passes
+ */
+
+ // Pass one: make sure two separators never appear consecutively
+ TItem pendingSeparator = null;
+ for (TItem item : items)
+ {
+ boolean isSeparator = isSeparator(item);
+
+ if (isSeparator)
+ {
+ if (pendingSeparator != null)
+ setVisible(pendingSeparator, false);
+ pendingSeparator = item;
+ }
+ else
+ {
+ if (isVisible(item))
+ {
+ if (pendingSeparator != null)
+ {
+ setVisible(pendingSeparator, true);
+ pendingSeparator = null;
+ }
+ }
+ }
+ }
+
+ // Pass 1.5: hide trailing separator
+ if (pendingSeparator != null)
+ setVisible(pendingSeparator, false);
+
+ // Pass two: hide leading separator
+ for (TItem item : items)
+ {
+ if (!isSeparator(item) && isVisible(item))
+ return;
+ if (isSeparator(item))
+ setVisible(item, false);
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/SerializedCommand.java b/src/gwt/src/org/rstudio/core/client/SerializedCommand.java
new file mode 100644
index 0000000..fd86e4c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/SerializedCommand.java
@@ -0,0 +1,22 @@
+/*
+ * SerializedCommand.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.user.client.Command;
+
+public interface SerializedCommand
+{
+ void onExecute(Command continuation);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/SerializedCommandQueue.java b/src/gwt/src/org/rstudio/core/client/SerializedCommandQueue.java
new file mode 100644
index 0000000..b5aa6b3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/SerializedCommandQueue.java
@@ -0,0 +1,107 @@
+/*
+ * SerializedCommandQueue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.user.client.Command;
+
+import java.util.ArrayList;
+
+public class SerializedCommandQueue
+{
+ public SerializedCommandQueue(boolean log)
+ {
+ log_ = log;
+ }
+
+ public SerializedCommandQueue()
+ {
+ this(false);
+ }
+
+ public void addCommand(SerializedCommand command)
+ {
+ addCommand(command, true);
+ }
+
+ public void addCommand(SerializedCommand command, boolean run)
+ {
+ if (command != null)
+ commands_.add(command);
+ log("addCommand");
+ if (run)
+ run();
+ }
+
+ public void addPriorityCommand(SerializedCommand command)
+ {
+ addPriorityCommand(command, true);
+ }
+
+ public void addPriorityCommand(SerializedCommand command, boolean run)
+ {
+ if (command != null)
+ commands_.add(0, command);
+ log("addPriorityCommand");
+ if (run)
+ run();
+ }
+
+ public void run()
+ {
+ if (running_)
+ {
+ log("already running");
+ return;
+ }
+ running_ = true;
+
+ executeNextCommand();
+ }
+
+ private void executeNextCommand()
+ {
+ log("executeNextCommand");
+
+ if (commands_.size() == 0)
+ {
+ log("done");
+ running_ = false;
+ return;
+ }
+
+ SerializedCommand head = commands_.remove(0);
+ head.onExecute(new Command()
+ {
+ public void execute()
+ {
+ log("continuation");
+ executeNextCommand();
+ }
+ });
+ }
+
+ private void log(String label)
+ {
+ if (log_)
+ {
+ Debug.log(hashCode() + " " + label + " size=" + commands_.size());
+ }
+ }
+
+ private boolean running_ = false;
+ private ArrayList<SerializedCommand> commands_
+ = new ArrayList<SerializedCommand>();
+ private final boolean log_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Size.java b/src/gwt/src/org/rstudio/core/client/Size.java
new file mode 100644
index 0000000..28d21f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Size.java
@@ -0,0 +1,70 @@
+/*
+ * Size.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Size
+{
+ public final int width;
+ public final int height;
+
+ public Size(int width, int height)
+ {
+ super() ;
+ this.width = width ;
+ this.height = height ;
+ }
+
+ public int getX()
+ {
+ return width ;
+ }
+
+ public int getY()
+ {
+ return height ;
+ }
+
+ public boolean isEmpty()
+ {
+ return width == 0 && height == 0;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31 ;
+ int result = 1 ;
+ result = prime * result + width ;
+ result = prime * result + height ;
+ return result ;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ Size other = (Size) obj ;
+ if (width != other.width)
+ return false ;
+ if (height != other.height)
+ return false ;
+ return true ;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Stopwatch.java b/src/gwt/src/org/rstudio/core/client/Stopwatch.java
new file mode 100644
index 0000000..4e92a9d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Stopwatch.java
@@ -0,0 +1,36 @@
+/*
+ * Stopwatch.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Stopwatch
+{
+ public Stopwatch()
+ {
+ reset();
+ }
+
+ public void reset()
+ {
+ startTime_ = System.currentTimeMillis();
+ }
+
+ public void mark(String label)
+ {
+ long stopTime = System.currentTimeMillis();
+ Debug.log("[Stopwatch] " + label + ": " + (stopTime - startTime_) + " ms");
+ }
+
+ private long startTime_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/StringUtil.java b/src/gwt/src/org/rstudio/core/client/StringUtil.java
new file mode 100644
index 0000000..dabfff5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/StringUtil.java
@@ -0,0 +1,481 @@
+/*
+ * StringUtil.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.NumberFormat;
+
+import org.rstudio.core.client.dom.DomMetrics;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.regex.Pattern.ReplaceOperation;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+public class StringUtil
+{
+ public static String padRight(String value, int minWidth)
+ {
+ if (value.length() >= minWidth)
+ return value;
+
+ StringBuilder out = new StringBuilder();
+ for (int i = minWidth - value.length(); i > 0; i--)
+ out.append(' ');
+ out.append(value);
+ return out.toString();
+ }
+
+ public static int parseInt(String value, int defaultValue)
+ {
+ try
+ {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException nfe)
+ {
+ return defaultValue;
+ }
+ }
+
+ public static String formatDate(Date date)
+ {
+ if (date == null)
+ return "";
+
+ return DATE_FORMAT.format(date);
+ }
+
+ public static String formatFileSize(long size)
+ {
+ return formatFileSize(new Long(size).intValue());
+ }
+
+ // Given a raw size, convert it to a human-readable value
+ // (e.g. 11580 -> "11.3 KB"). Note that this routine must generally avoid
+ // implicit casts and use only ints; GWT's JavaScript compiler will truncate
+ // values it believes to be ints to Int32 max/min (+/- 2 billion) during
+ // type checking, and this function deals in file and object sizes larger
+ // than that.
+ public static String formatFileSize(int size)
+ {
+ int i = 0, divisor = 1;
+
+ for (; nativeDivide(size, divisor) > 1024 && i < LABELS.length; i++)
+ {
+ divisor *= 1024;
+ }
+
+ return FORMAT.format((double)size / divisor) + " " + LABELS[i];
+ }
+
+ // Peform an integer division and return the result. GWT's division operator
+ // truncates the result to Int32 range.
+ public static native int nativeDivide(int num, int denom)
+ /*-{
+ return num / denom;
+ }-*/;
+
+ public static String prettyFormatNumber(double number)
+ {
+ return PRETTY_NUMBER_FORMAT.format(number);
+ }
+
+ public static String formatGeneralNumber(long number)
+ {
+ String val = number + "";
+ if (val.length() < 5 || (number < 0 && val.length() < 6))
+ return val;
+ return NumberFormat.getFormat("0,000").format(number);
+ }
+
+ public static String formatPercent(double number)
+ {
+ return NumberFormat.getPercentFormat().format(number);
+ }
+
+ public static Size characterExtent(String text)
+ {
+ // split into lines and find the maximum line width
+ String[] lines = text.split("\n");
+ int maxWidth = 0;
+ for (int i=0; i<lines.length; i++)
+ {
+ int width = lines[i].length();
+ if (width > maxWidth)
+ maxWidth = width;
+ }
+
+ return new Size(maxWidth, lines.length);
+ }
+
+ public static String chomp(String string)
+ {
+ if (string.endsWith("\n"))
+ return string.substring(0, string.length()-1);
+ return string;
+ }
+
+ public static boolean isNullOrEmpty(String val)
+ {
+ return val == null || val.length() == 0;
+ }
+
+ // WARNING: I'm pretty sure this will fail for UTF-8
+ public static String textToRLiteral(String value)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append('"');
+
+ for (char c : value.toCharArray())
+ {
+ switch (c)
+ {
+ case '"':
+ sb.append("\\\"");
+ break;
+ case '\n':
+ sb.append("\\n");
+ break;
+ case '\r':
+ sb.append("\\r");
+ break;
+ case '\t':
+ sb.append("\\t");
+ break;
+ case '\b':
+ sb.append("\\b");
+ break;
+ case '\f':
+ sb.append("\\f");
+ break;
+ case '\\':
+ sb.append("\\\\");
+ break;
+ default:
+ if (c < 32 || c > 126)
+ sb.append("\\x").append(toHex(c));
+ else
+ sb.append(c);
+ break;
+ }
+ }
+
+ sb.append('"');
+
+ return sb.toString();
+ }
+
+ private static String toHex(char c)
+ {
+ String table = "0123456789ABCDEF";
+ return table.charAt((c >> 8) & 0xF) + "" + table.charAt(c & 0xF);
+ }
+
+ public static String toRSymbolName(String name)
+ {
+ if (!name.matches("^[a-zA-Z_.][a-zA-Z0-9_.]*$")
+ || name.matches("^.[0-9].*$")
+ || isRKeyword(name))
+ {
+ return "`" + name + "`";
+ }
+ else
+ return name;
+ }
+
+ private static boolean isRKeyword(String identifier)
+ {
+ String ALL_KEYWORDS = "|NULL|NA|TRUE|FALSE|T|F|Inf|NaN|NA_integer_|NA_real_|NA_character_|NA_complex_|function|while|repeat|for|if|in|else|next|break|...|";
+
+ if (identifier.length() > 20 || identifier.contains("|"))
+ return false;
+
+ return ALL_KEYWORDS.indexOf("|" + identifier + "|") >= 0;
+ }
+
+ public static String notNull(String s)
+ {
+ return s == null ? "" : s;
+ }
+
+ public static String indent(String str, String indent)
+ {
+ if (isNullOrEmpty(str))
+ return str;
+
+ return indent + str.replaceAll("\n", "\n" + indent);
+ }
+
+ public static String join(Collection<?> collection,
+ String delim)
+ {
+ String currDelim = "";
+ StringBuilder output = new StringBuilder();
+ for (Object el : collection)
+ {
+ output.append(currDelim).append(el == null ? "" : el.toString());
+ currDelim = delim;
+ }
+ return output.toString();
+ }
+
+ public static String firstNotNullOrEmpty(String[] strings)
+ {
+ for (String s : strings)
+ if (!isNullOrEmpty(s))
+ return s;
+ return null;
+ }
+
+ public static String shortPathName(FileSystemItem item, int maxWidth)
+ {
+ return shortPathName(item, "gwt-Label", maxWidth);
+ }
+
+ public static String shortPathName(FileSystemItem item,
+ String styleName,
+ int maxWidth)
+ {
+ // measure HTML and truncate if necessary
+ String path = item.getPath();
+ Size textSize = DomMetrics.measureHTML(path, styleName);
+ if (textSize.width >= maxWidth)
+ {
+ // shortened directory nam
+ if (item.getParentPath() != null &&
+ item.getParentPath().getParentPath() != null)
+ {
+ path = ".../" +
+ item.getParentPath().getName() + "/" +
+ item.getName();
+ }
+ }
+ return path;
+ }
+
+ public static Iterable<String> getLineIterator(final String text)
+ {
+ return new Iterable<String>()
+ {
+ @Override
+ public Iterator<String> iterator()
+ {
+ return new Iterator<String>()
+ {
+ private int pos = 0;
+ private Pattern newline = Pattern.create("\\r?\\n");
+
+ @Override
+ public boolean hasNext()
+ {
+ return pos < text.length();
+ }
+
+ @Override
+ public String next()
+ {
+ if (pos >= text.length())
+ return null;
+
+ Match match = newline.match(text, pos);
+ String result;
+ if (match == null)
+ {
+ result = text.substring(pos);
+ pos = text.length();
+ }
+ else
+ {
+ result = text.substring(pos, match.getIndex());
+ pos = match.getIndex() + match.getValue().length();
+ }
+ return result;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Removes empty or whitespace-only lines from the beginning and end of the
+ * string.
+ */
+ public static String trimBlankLines(String data)
+ {
+ data = Pattern.create("^[\\r\\n\\t ]*\\n", "g").replaceAll(data, "");
+ data = Pattern.create("\\r?\\n[\\r\\n\\t ]*$", "g").replaceAll(data, "");
+ return data;
+ }
+
+ public static String trimLeft(String str)
+ {
+ return str.replaceFirst("^\\s+", "");
+ }
+
+ public static String trimRight(String str)
+ {
+ return str.replaceFirst("\\s+$", "");
+ }
+
+ /**
+ * Returns the zero or more characters that prefix all of the lines (but see
+ * allowPhantomWhitespace).
+ * @param lines The lines from which to find a common prefix.
+ * @param allowPhantomWhitespace See comment in function body
+ * @return
+ */
+ public static String getCommonPrefix(String[] lines,
+ boolean allowPhantomWhitespace)
+ {
+ if (lines.length == 0)
+ return "";
+
+ /**
+ * allowPhantomWhitespace demands some explanation. Assuming these lines:
+ *
+ * {
+ * "#",
+ * "# hello",
+ * "#",
+ * "# goodbye",
+ * "# hello again"
+ * }
+ *
+ * The result with allowPhantomWhitespace = false would be "#", but with
+ * allowPhantomWhiteSpace = true it would be "# ". Basically phantom
+ * whitespace refers to spots at the end of a line where additional
+ * whitespace would lead to a longer overall prefix but would not change
+ * the visible appearance of the document.
+ */
+
+
+ String prefix = notNull(lines[0]);
+
+ // Usually the prefix gradually gets shorter and shorter.
+ // whitespaceExpansionAllowed means that the prefix might get longer,
+ // because the prefix as it stands is eligible for phantom whitespace
+ // insertion. This is true iff the prefix is the same length as, or longer
+ // than, all of the lines we have processed.
+ boolean whitespaceExpansionAllowed = allowPhantomWhitespace;
+
+ for (int i = 1; i < lines.length && prefix.length() > 0; i++)
+ {
+ String line = notNull(lines[i]);
+ int len = whitespaceExpansionAllowed ? Math.max(prefix.length(), line.length()) :
+ allowPhantomWhitespace ? prefix.length() :
+ Math.min(prefix.length(), line.length());
+ int j;
+ for (j = 0; j < len; j++)
+ {
+ if (j >= prefix.length())
+ {
+ assert whitespaceExpansionAllowed;
+ if (!isWhitespace(line.charAt(j)))
+ break;
+ continue;
+ }
+
+ if (j >= line.length())
+ {
+ assert allowPhantomWhitespace;
+ if (!isWhitespace(prefix.charAt(j)))
+ break;
+ continue;
+ }
+
+ if (prefix.charAt(j) != line.charAt(j))
+ {
+ break;
+ }
+ }
+
+ prefix = j <= prefix.length() ? prefix.substring(0, j)
+ : line.substring(0, j);
+
+ whitespaceExpansionAllowed =
+ whitespaceExpansionAllowed && (prefix.length() >= line.length());
+ }
+
+ return prefix;
+ }
+
+ private static boolean isWhitespace(char c)
+ {
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ case '\u00A0':
+ case '\r':
+ case '\n':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static String pathToTitle(String path)
+ {
+ String val = FileSystemItem.createFile(path).getStem();
+ val = Pattern.create("\\b[a-z]").replaceAll(val, new ReplaceOperation()
+ {
+ @Override
+ public String replace(Match m)
+ {
+ return m.getValue().toUpperCase();
+ }
+ });
+ val = Pattern.create("[-_]").replaceAll(val, " ");
+ return val;
+ }
+
+ public static String joinStrings(List<String> strings, String separator)
+ {
+ String result = "";
+ // GWT's exposed Strings.join often makes the compiler barf; do this
+ // manually
+ for (int i = 0; i < strings.size(); i++)
+ {
+ result += strings.get(i);
+ if (i < strings.size() - 1)
+ result += separator;
+ }
+ return result;
+ }
+
+ private static final String[] LABELS = {
+ "B",
+ "KB",
+ "MB",
+ "GB",
+ "TB"
+ };
+ private static final NumberFormat FORMAT = NumberFormat.getFormat("0.#");
+ private static final NumberFormat PRETTY_NUMBER_FORMAT = NumberFormat.getFormat("#,##0.#####");
+ private static final DateTimeFormat DATE_FORMAT
+ = DateTimeFormat.getFormat("MMM d, yyyy, h:mm a");
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/TimeBufferedCommand.java b/src/gwt/src/org/rstudio/core/client/TimeBufferedCommand.java
new file mode 100644
index 0000000..a0b3258
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/TimeBufferedCommand.java
@@ -0,0 +1,152 @@
+/*
+ * TimeBufferedCommand.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.user.client.Timer;
+
+import java.util.Date;
+
+/**
+ * Manages the execution of logic that should not be run too frequently.
+ * Multiple calls over a (caller-defined) period of time will be coalesced
+ * into one call.
+ *
+ * The command can optionally be run on a scheduled ("passive") basis; use
+ * the two- or three-arg constructor. IMPORTANT NOTE: The implementation of
+ * performAction must check if shouldSchedulePassive is true, and if it is,
+ * then it should call schedulePassive() whenever it is done with the
+ * operation. Failure to do so correctly (e.g. in error cases) will cause
+ * the passive runs to immediately stop occurring.
+ */
+public abstract class TimeBufferedCommand
+{
+ /**
+ * Creates a TimeBufferedCommand that will only run when nudged.
+ */
+ public TimeBufferedCommand(int activeIntervalMillis)
+ {
+ this(-1, -1, activeIntervalMillis);
+ }
+
+ /**
+ * Creates a TimeBufferedCommand that will run when nudged, or every
+ * passiveIntervalMillis milliseconds, whichever comes first.
+ */
+ public TimeBufferedCommand(int passiveIntervalMillis,
+ int activeIntervalMillis)
+ {
+ this(passiveIntervalMillis, passiveIntervalMillis, activeIntervalMillis);
+ }
+
+ /**
+ * Creates a TimeBufferedCommand that will run when nudged, or every
+ * passiveIntervalMillis milliseconds, whichever comes first; with a
+ * custom period before the first "passive" run.
+ */
+ public TimeBufferedCommand(int initialIntervalMillis,
+ int passiveIntervalMillis,
+ int activeIntervalMillis)
+ {
+ this.initialIntervalMillis_ = initialIntervalMillis;
+ this.passiveIntervalMillis_ = passiveIntervalMillis;
+ this.activeIntervalMillis_ = activeIntervalMillis;
+
+ if (initialIntervalMillis_ >= 0 && passiveIntervalMillis_ > 0)
+ scheduleExecution(true, Math.max(1, initialIntervalMillis_));
+ }
+
+ /**
+ * See class javadoc for details about shouldSchedulePassive flag.
+ */
+ protected abstract void performAction(boolean shouldSchedulePassive);
+
+ /**
+ * Request that this command execute soon. (How soon depends
+ * on the activeIntervalMillis constructor param.)
+ */
+ public final void nudge()
+ {
+ scheduleExecution(false, activeIntervalMillis_);
+ }
+
+ public final void suspend()
+ {
+ stopped_ = true;
+ }
+
+ public final void resume()
+ {
+ assert passiveIntervalMillis_ <= 0 : "Cannot call start() on a " +
+ "TimeBufferedCommand that fires on " +
+ "passive intervals. Once stopped, " +
+ "they stay stopped.";
+ if (passiveIntervalMillis_ > 0)
+ throw new IllegalStateException("Cannot call start() on a " +
+ "TimeBufferedCommand that fires on " +
+ "passive intervals. Once stopped, " +
+ "they stay stopped.");
+ stopped_ = false;
+ }
+
+ private final void scheduleExecution(final boolean passive, final int millis)
+ {
+ new Timer() {
+ @Override
+ public void run()
+ {
+ execute(passive, millis);
+ }
+ }.schedule(millis);
+ }
+
+ private final void execute(final boolean passive, int millisAgo)
+ {
+ if (stopped_)
+ return;
+
+ Date now = new Date();
+ // see if we were preempted by someone else executing
+ if (lastExecuted_ != null)
+ {
+ long millisSinceLast = now.getTime() - lastExecuted_.getTime();
+ if (millisSinceLast < millisAgo - 50) // some fudge factor
+ {
+ // Someone executed in front of us. Abort this execute, but
+ // if we're in the passive chain of execution, then reschedule.
+ if (passive)
+ {
+ int gap = passiveIntervalMillis_ - (int)millisSinceLast;
+ gap = Math.max(1, gap); // a non-positive value will cause error
+ scheduleExecution(true, gap);
+ }
+ return;
+ }
+ }
+ lastExecuted_ = now;
+
+ performAction(passive);
+ }
+
+ protected final void schedulePassive()
+ {
+ scheduleExecution(true, passiveIntervalMillis_);
+ }
+
+ private final int initialIntervalMillis_;
+ private final int passiveIntervalMillis_;
+ private final int activeIntervalMillis_;
+ protected Date lastExecuted_;
+ private boolean stopped_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/Triad.java b/src/gwt/src/org/rstudio/core/client/Triad.java
new file mode 100644
index 0000000..1508db4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/Triad.java
@@ -0,0 +1,29 @@
+/*
+ * Triad.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class Triad<TFirst, TSecond, TThird>
+{
+ public Triad(TFirst first, TSecond second, TThird third)
+ {
+ this.first = first;
+ this.second = second;
+ this.third = third;
+ }
+
+ public TFirst first;
+ public TSecond second;
+ public TThird third;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/UnicodeLetters.java b/src/gwt/src/org/rstudio/core/client/UnicodeLetters.java
new file mode 100644
index 0000000..f6d800b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/UnicodeLetters.java
@@ -0,0 +1,86 @@
+/*
+ * UnicodeLetters.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayBoolean;
+
+public class UnicodeLetters
+{
+ public static boolean isLetter(char c)
+ {
+ if (isCached(c))
+ return CACHE.get(c);
+ boolean result = binarySearchData(c);
+ CACHE.set(c, result);
+ return result;
+ }
+
+ private static boolean binarySearchData(char c)
+ {
+ int fromIndex = 0; // index of starting range, INCLUSIVE
+ int toIndex = DATA.length() / 2; // index of ending range, EXCLUSIVE
+
+ while (fromIndex < toIndex)
+ {
+ int testIndex = (fromIndex + toIndex) / 2;
+ int testResult = compareToRange(c, testIndex);
+
+ if (testResult < 0)
+ toIndex = testIndex;
+ else if (testResult > 0)
+ fromIndex = testIndex + 1;
+ else
+ return true;
+ }
+
+ // No ranges left to test
+ return false;
+ }
+
+ private static int compareToRange(char c, int rangeIndex)
+ {
+ if (c < DATA.charAt(rangeIndex*2))
+ return -1;
+
+ if (c > DATA.charAt(rangeIndex*2 + 1))
+ return 1;
+
+ return 0;
+ }
+
+ private static native boolean isCached(int c) /*-{
+ return typeof @org.rstudio.core.client.UnicodeLetters::CACHE[c] != 'undefined';
+ }-*/;
+
+ private static final JsArrayBoolean CACHE = JavaScriptObject.createArray().cast();
+
+ /**
+ * This string is a compact representation of the set of Unicode characters
+ * that we consider to be letters. Each pair of characters represents the
+ * lower and upper bound (both inclusive) of a range of letters.
+ *
+ * For example, "azAZ09" would mean a-z, A-Z, and 0-9 are all considered
+ * letters.
+ *
+ * The ranges are sorted, so they may be binary searched.
+ *
+ * The source for this data is the utf_info.cxx file that's part of the
+ * Hunspell source distribution. In gwt/tools there's a unicode-chars-util.rb
+ * script that can be used to generate a new data string if utf_info.cxx ever
+ * changes.
+ */
+ private static final String DATA = "\u0041\u005A\u0061\u007A\u00AA\u00AA\u00B5\u00B5\u00BA\u00BA\u00C0\u00D6\u00D8\u00F6\u00F8\u0241\u0250\u02C1\u02C6\u02D1\u02E0\u02E4\u02EE\u02EE\u0300\u036F\u037A\u037A\u0386\u0386\u0388\u038A\u038C\u038C\u038E\u03A1\u03A3\u03CE\u03D0\u03F5\u03F7\u0481\u0483\u0486\u048A\u04CE\u04D0\u04F9\u0500\u050F\u0531\u0556\u0559\u0559\u0561\u0587\u0591\u05B9\u05BB\u05BD\u05BF\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05C7\u05D0\u05EA\u05F0\u05F2\u0610\u0615\u0621\u0 [...]
+}
diff --git a/src/gwt/src/org/rstudio/core/client/ValueSink.java b/src/gwt/src/org/rstudio/core/client/ValueSink.java
new file mode 100644
index 0000000..140441e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/ValueSink.java
@@ -0,0 +1,20 @@
+/*
+ * ValueSink.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public interface ValueSink<T>
+{
+ void setValue(T value);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/VirtualConsole.java b/src/gwt/src/org/rstudio/core/client/VirtualConsole.java
new file mode 100644
index 0000000..423d046
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/VirtualConsole.java
@@ -0,0 +1,134 @@
+/*
+ * VirtualConsole.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+/**
+ * Simulates a console that behaves like the R console, specifically with
+ * regard to \r (carriage return) and \b (backspace) characters.
+ */
+public class VirtualConsole
+{
+ public VirtualConsole()
+ {
+ }
+
+ public void submit(String data)
+ {
+ if (StringUtil.isNullOrEmpty(data))
+ return;
+
+ if (CONTROL_SPECIAL.match(data, 0) == null)
+ {
+ text(data);
+ return;
+ }
+
+ int tail = 0;
+ Match match = CONTROL.match(data, 0);
+ while (match != null)
+ {
+ int pos = match.getIndex();
+
+ // If we passed over any plain text on the way to this control
+ // character, add it.
+ text(data.substring(tail, pos));
+
+ tail = pos + 1;
+
+ switch (data.charAt(pos))
+ {
+ case '\r':
+ carriageReturn();
+ break;
+ case '\b':
+ backspace();
+ break;
+ case '\n':
+ newline();
+ break;
+ case '\f':
+ formfeed();
+ break;
+ default:
+ assert false : "Unknown control char, please check regex";
+ text(data.charAt(pos) + "");
+ break;
+ }
+
+ match = match.nextMatch();
+ }
+
+ // If there was any plain text after the last control character, add it
+ text(data.substring(tail));
+ }
+
+ private void backspace()
+ {
+ if (pos == 0)
+ return;
+ o.deleteCharAt(--pos);
+ }
+
+ private void carriageReturn()
+ {
+ if (pos == 0)
+ return;
+ while (pos > 0 && o.charAt(pos - 1) != '\n')
+ pos--;
+ // Now we're either at the beginning of the buffer, or just past a '\n'
+ }
+
+ private void newline()
+ {
+ while (pos < o.length() && o.charAt(pos) != '\n')
+ pos++;
+ // Now we're either at the end of the buffer, or on top of a '\n'
+ text("\n");
+ }
+
+ private void formfeed()
+ {
+ o.setLength(0);
+ }
+
+ private void text(String text)
+ {
+ assert text.indexOf('\r') < 0 && text.indexOf('\b') < 0;
+
+ o.replace(pos, pos + text.length(), text);
+ pos += text.length();
+ }
+
+ @Override
+ public String toString()
+ {
+ return o.toString();
+ }
+
+ public static String consolify(String text)
+ {
+ VirtualConsole console = new VirtualConsole();
+ console.submit(text);
+ return console.toString();
+ }
+
+ private final StringBuilder o = new StringBuilder();
+ private int pos = 0;
+ private static final Pattern CONTROL = Pattern.create("[\r\b\f\n]");
+ private static final Pattern CONTROL_SPECIAL = Pattern.create("[\r\b\f]");
+}
diff --git a/src/gwt/src/org/rstudio/core/client/WidgetHandlerRegistration.java b/src/gwt/src/org/rstudio/core/client/WidgetHandlerRegistration.java
new file mode 100644
index 0000000..5dedabd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/WidgetHandlerRegistration.java
@@ -0,0 +1,64 @@
+/*
+ * WidgetHandlerRegistration.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+import com.google.gwt.event.logical.shared.AttachEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Automatically registers and unregisters a handler as a widget
+ * becomes attached and detached from the DOM.
+ *
+ * To use, create a concrete subclass and override the doRegister
+ * method to do the actual addHandler call.
+ */
+public abstract class WidgetHandlerRegistration
+{
+ public WidgetHandlerRegistration(Widget widget)
+ {
+ widget.addAttachHandler(new AttachEvent.Handler()
+ {
+ @Override
+ public void onAttachOrDetach(AttachEvent event)
+ {
+ unregister();
+ if (event.isAttached())
+ register();
+ }
+ });
+
+ if (widget.isAttached())
+ register();
+ }
+
+ public void register()
+ {
+ registration_ = doRegister();
+ }
+
+ protected abstract HandlerRegistration doRegister();
+
+ private void unregister()
+ {
+ if (registration_ != null)
+ {
+ registration_.removeHandler();
+ registration_ = null;
+ }
+ }
+
+ private HandlerRegistration registration_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/WordWrap.java b/src/gwt/src/org/rstudio/core/client/WordWrap.java
new file mode 100644
index 0000000..714e5a6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/WordWrap.java
@@ -0,0 +1,210 @@
+/*
+ * WordWrap.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client;
+
+public class WordWrap
+{
+ public WordWrap(int maxLineLength, boolean hardWrapIfNecessary)
+ {
+ maxLineLength_ = maxLineLength;
+ hardWrapIfNecessary_ = hardWrapIfNecessary;
+ }
+
+ public void setWrappingEnabled(boolean wrappingEnabled)
+ {
+ wrappingEnabled_ = wrappingEnabled;
+ if (!wrappingEnabled && !atBeginningOfLine())
+ appendRaw("\n");
+ }
+
+ public void appendLine(String line)
+ {
+ if (!wrappingEnabled_)
+ {
+ int lastInsertionRow = row_;
+ int lastInsertionPoint = lineLength_;
+ appendRaw(line + "\n");
+ onChunkWritten(line, lastInsertionRow, lastInsertionPoint, 0);
+ }
+ else
+ {
+ if (forceWrapBefore(line))
+ if (!atBeginningOfLine())
+ wrap();
+
+ processLine(line);
+
+ if (forceWrapAfter(line))
+ appendRaw("\n");
+ }
+ }
+
+ private boolean atBeginningOfLine()
+ {
+ // Don't need to worry about indentation here because indent isn't
+ // written until some content is.
+ return lineLength_ == 0;
+ }
+
+ public String getOutput()
+ {
+ return output_.toString();
+ }
+
+ public int getRow()
+ {
+ return row_;
+ }
+
+ private void processLine(String line)
+ {
+ assert line.indexOf('\n') < 0;
+
+ int origStringPos = 0;
+
+ String trimmed = StringUtil.trimLeft(line);
+ origStringPos = line.length() - trimmed.length();
+ line = trimmed;
+ line = StringUtil.trimRight(line);
+
+ if (line.length() > 0 &&
+ lineLength_ > 0 &&
+ lineLength_ < maxLineLength_)
+ {
+ // We're about to append some content and we're not at the beginning
+ // of the line.
+ appendRaw(" ");
+ }
+
+ // Loop while "line" is too big to fit in the current line without
+ // wrapping
+ while (true)
+ {
+ // chars left
+ int charsLeft = lineLength_ == 0 ? maxLineLength_ - indent_.length()
+ : maxLineLength_ - lineLength_;
+
+ if (line.length() <= charsLeft)
+ break;
+
+ int breakChars = 1;
+
+ // Look for the last space that will fit on the current line
+ int index = line.lastIndexOf(' ', charsLeft);
+ if (index == -1)
+ {
+ if (lineLength_ == 0)
+ {
+ index = line.indexOf(' ', charsLeft);
+ if (index == -1)
+ index = line.length();
+
+ if (hardWrapIfNecessary_ && index > charsLeft)
+ {
+ index = charsLeft;
+ breakChars = 0;
+ }
+ }
+ }
+
+ int insertionRow = row_;
+ int insertionPoint = lineLength_;
+ String thisChunk = "";
+ if (index > 0)
+ {
+ thisChunk = line.substring(0, index);
+ appendRawWithIndent(thisChunk);
+ }
+ wrap();
+ onChunkWritten(thisChunk, insertionRow, insertionPoint, origStringPos);
+
+ int nextLineIndex = Math.min(line.length(), index + breakChars);
+ origStringPos += nextLineIndex;
+ line = line.substring(nextLineIndex);
+ trimmed = StringUtil.trimLeft(line);
+ origStringPos += line.length() - trimmed.length();
+ line = trimmed;
+ }
+
+ // Now just append the rest of the line
+ int lastInsertionRow = row_;
+ int lastInsertionPoint = lineLength_;
+ appendRawWithIndent(line);
+ onChunkWritten(line, lastInsertionRow, lastInsertionPoint, origStringPos);
+ }
+
+ protected void onChunkWritten(String chunk,
+ int insertionRow,
+ int insertionCol,
+ int indexInOriginalString)
+ {
+
+ }
+
+ protected boolean forceWrapBefore(String line)
+ {
+ return isEmpty(line);
+ }
+
+ protected boolean forceWrapAfter(String line)
+ {
+ return isEmpty(line);
+ }
+
+ private boolean isEmpty(String line)
+ {
+ return line.trim().length() == 0;
+ }
+
+ private void wrap()
+ {
+ if (output_.length() > 0)
+ appendRaw("\n");
+ }
+
+ private void appendRawWithIndent(String value)
+ {
+ assert value.indexOf('\n') < 0;
+ if (lineLength_ == 0 && indent_ != null)
+ appendRaw(indent_);
+ appendRaw(value);
+ }
+
+ private void appendRaw(String value)
+ {
+ if (value.length() == 0)
+ return;
+
+ output_.append(value);
+ int index = value.lastIndexOf('\n');
+ if (index < 0)
+ lineLength_ += value.length();
+ else
+ lineLength_ = value.length() - (index + 1);
+
+ for (int i = 0; i < value.length(); i++)
+ if (value.charAt(i) == '\n')
+ row_++;
+ }
+
+ protected String indent_ = "";
+
+ private StringBuilder output_ = new StringBuilder();
+ private int lineLength_;
+ private int row_ = 0;
+ private final int maxLineLength_;
+ private final boolean hardWrapIfNecessary_;
+ private boolean wrappingEnabled_ = true;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/AutoHidingSplitLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/cellview/AutoHidingSplitLayoutPanel.java
new file mode 100644
index 0000000..f0b5184
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/AutoHidingSplitLayoutPanel.java
@@ -0,0 +1,45 @@
+package org.rstudio.core.client.cellview;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+// In the version of GWT currently used, the splitter doesn't hide when the
+// associated child is hidden. This is fixed in later releases of GWT.
+// http://gwt-code-reviews.appspot.com/1880804/patch/9001/10001
+//
+// This class extends the stock GWT SplitLayoutPanel such that it hides the
+// splitter associated with a child widget when that widget is hidden.
+public class AutoHidingSplitLayoutPanel
+ extends SplitLayoutPanel
+{
+ public AutoHidingSplitLayoutPanel(int splitterSize)
+ {
+ super(splitterSize);
+ }
+
+ public AutoHidingSplitLayoutPanel(Style.Unit unit)
+ {
+ }
+
+ @Override
+ public void setWidgetHidden(Widget widget, boolean hidden) {
+ LayoutData layoutData = (LayoutData)widget.getLayoutData();
+
+ if (layoutData.direction != Direction.CENTER) {
+ Widget splitter = getAssociatedSplitter(widget);
+ super.setWidgetHidden(splitter, hidden);
+ }
+
+ super.setWidgetHidden(widget, hidden);
+ }
+
+ // adapted from the private method of the same name in the parent class
+ private Widget getAssociatedSplitter(Widget child) {
+ int idx = getWidgetIndex(child);
+ if (idx > -1 && idx < getWidgetCount() - 1) {
+ return getWidget(idx + 1);
+ }
+ return null;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/ColumnSortInfo.java b/src/gwt/src/org/rstudio/core/client/cellview/ColumnSortInfo.java
new file mode 100644
index 0000000..ef064fe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/ColumnSortInfo.java
@@ -0,0 +1,77 @@
+/*
+ * ColumnSortInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.cellview;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.ColumnSortList;
+
+public class ColumnSortInfo extends JavaScriptObject
+{
+ protected ColumnSortInfo()
+ {
+ }
+
+ public static native ColumnSortInfo create(int columnIndex,
+ boolean ascending) /*-{
+ var sortInfo = new Object();
+ sortInfo.columnIndex = columnIndex;
+ sortInfo.ascending = ascending;
+ return sortInfo;
+ }-*/;
+
+ public final native int getColumnIndex() /*-{
+ return this.columnIndex;
+ }-*/;
+
+ public final native boolean getAscending() /*-{
+ return this.ascending;
+ }-*/;
+
+ public final ColumnSortList.ColumnSortInfo toGwtSortInfo(CellTable<?> table)
+ {
+ return new ColumnSortList.ColumnSortInfo(
+ table.getColumn(getColumnIndex()), getAscending());
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static ColumnSortInfo fromGwtSortInfo(CellTable table,
+ ColumnSortList.ColumnSortInfo si)
+ {
+ return ColumnSortInfo.create(table.getColumnIndex(si.getColumn()),
+ si.isAscending());
+ }
+
+ public static ColumnSortList setSortList(CellTable<?> table,
+ JsArray<ColumnSortInfo> sortArray)
+ {
+ ColumnSortList list = table.getColumnSortList();
+ list.clear();
+ for (int i = 0; i < sortArray.length(); i++)
+ list.insert(i, sortArray.get(i).toGwtSortInfo(table));
+ return list;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static JsArray<ColumnSortInfo> getSortList(CellTable table)
+ {
+ ColumnSortList sortList = table.getColumnSortList();
+ JsArray<ColumnSortInfo> result = JsArray.createArray().cast();
+ for (int i = 0; i < sortList.size(); i++)
+ result.push(fromGwtSortInfo(table, sortList.get(i)));
+ return result;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/ImageButtonColumn.java b/src/gwt/src/org/rstudio/core/client/cellview/ImageButtonColumn.java
new file mode 100644
index 0000000..72c1966
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/ImageButtonColumn.java
@@ -0,0 +1,71 @@
+/*
+ * ImageButtonColumn.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.cellview;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+
+public class ImageButtonColumn<T> extends Column<T, String>
+{
+ public ImageButtonColumn(final AbstractImagePrototype imagePrototype,
+ final OperationWithInput<T> onClick,
+ final String title)
+ {
+ super(new ButtonCell(){
+ @Override
+ public void render(Context context,
+ SafeHtml value,
+ SafeHtmlBuilder sb)
+ {
+ if (value != null)
+ {
+ sb.appendHtmlConstant("<span title=\"" + title + "\" " +
+ "style=\"cursor: pointer;\">");
+ sb.appendHtmlConstant(imagePrototype.getHTML());
+ sb.appendHtmlConstant("</span>");
+ }
+ }
+ });
+
+ setFieldUpdater(new FieldUpdater<T,String>() {
+ public void update(int index, T object, String value)
+ {
+ if (value != null)
+ onClick.execute(object);
+ }
+ });
+ }
+
+
+ @Override
+ public String getValue(T object)
+ {
+ if (showButton(object))
+ return new String();
+ else
+ return null;
+ }
+
+ protected boolean showButton(T object)
+ {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.css b/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.css
new file mode 100644
index 0000000..75de2ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.css
@@ -0,0 +1,12 @@
+.link {
+ color: #0000AA;
+ cursor: pointer;
+}
+
+.linkUnderlined {
+ text-decoration: underline;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.java b/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.java
new file mode 100644
index 0000000..fc2d624
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/LinkColumn.java
@@ -0,0 +1,112 @@
+/*
+ * LinkColumn.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.cellview;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.view.client.ListDataProvider;
+
+
+// package name column which includes a hyperlink to package docs
+public abstract class LinkColumn<T> extends Column<T, String>
+{
+ public LinkColumn(ListDataProvider<T> dataProvider,
+ OperationWithInput<T> onClicked)
+ {
+ this(dataProvider, onClicked, false);
+ }
+
+ public LinkColumn(final ListDataProvider<T> dataProvider,
+ final OperationWithInput<T> onClicked,
+ final boolean alwaysUnderline)
+ {
+ super(new ClickableTextCell(){
+
+ // render anchor using custom styles. detect selection and
+ // add selected style to invert text color
+ @Override
+ protected void render(Context context,
+ SafeHtml value,
+ SafeHtmlBuilder sb)
+ {
+ if (value != null)
+ {
+ Styles styles = RESOURCES.styles();
+ StringBuilder div = new StringBuilder();
+ div.append("<div class=\"");
+ div.append(styles.link());
+ if (alwaysUnderline)
+ div.append(" " + styles.linkUnderlined());
+ div.append("\">");
+
+ sb.appendHtmlConstant(div.toString());
+ sb.append(value);
+ sb.appendHtmlConstant("</div>");
+ }
+ }
+
+ // click event which occurs on the actual package link div
+ // results in showing help for that package
+ @Override
+ public void onBrowserEvent(Context context, Element parent,
+ String value, NativeEvent event,
+ ValueUpdater<String> valueUpdater)
+ {
+ super.onBrowserEvent(context, parent, value, event, valueUpdater);
+ if ("click".equals(event.getType()))
+ {
+ // verify that the click was on the package link
+ JavaScriptObject evTarget = event.getEventTarget().cast();
+ if (Element.is(evTarget) &&
+ Element.as(evTarget).getClassName().startsWith(
+ RESOURCES.styles().link()))
+ {
+ onClicked.execute(
+ dataProvider.getList().get(context.getIndex()));
+ }
+ }
+ }
+ });
+ }
+
+ static interface Styles extends CssResource
+ {
+ String link();
+ String linkUnderlined();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("LinkColumn.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/ScrollingDataGrid.java b/src/gwt/src/org/rstudio/core/client/cellview/ScrollingDataGrid.java
new file mode 100644
index 0000000..ad222bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/ScrollingDataGrid.java
@@ -0,0 +1,37 @@
+/*
+ * ScrollingDataGrid.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.cellview;
+
+import com.google.gwt.user.cellview.client.DataGrid;
+import com.google.gwt.user.client.ui.HeaderPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.view.client.ProvidesKey;
+
+// this class extends GWT's DataGrid with a single method that gives us access
+// to the scrolling panel used by the grid, which we need in order to
+// manipulate the scroll position directly (e.g. to save and restore it)
+public class ScrollingDataGrid<T> extends DataGrid<T>
+{
+ public ScrollingDataGrid(int pageSize, ProvidesKey<T> keyProvider)
+ {
+ super(pageSize, keyProvider);
+ }
+
+ public ScrollPanel getScrollPanel() {
+ HeaderPanel header = (HeaderPanel) getWidget();
+ return (ScrollPanel) header.getContentWidget();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/TriStateCheckboxCell.java b/src/gwt/src/org/rstudio/core/client/cellview/TriStateCheckboxCell.java
new file mode 100644
index 0000000..18dae61
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/cellview/TriStateCheckboxCell.java
@@ -0,0 +1,145 @@
+/*
+ * TriStateCheckboxCell.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.cellview;
+
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.view.client.SelectionModel;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * WARNING: If you use this, take a look at ChangelistTable.NotEditingTextCell,
+ * which was necessary to get the table out of a state where cellIsEditing is
+ * stuck on true due to this cell getting mouseover but not mouseout.
+ */
+public class TriStateCheckboxCell<TKey> implements Cell<Boolean>
+{
+ interface Resources extends ClientBundle
+ {
+ ImageResource checkboxIndeterminate();
+ ImageResource checkboxOn();
+ ImageResource checkboxOff();
+ }
+
+ public TriStateCheckboxCell(SelectionModel<TKey> selectionModel)
+ {
+ selectionModel_ = selectionModel;
+ consumedEvents_ = new HashSet<String>();
+ consumedEvents_.add("click");
+ consumedEvents_.add("keydown");
+ consumedEvents_.add("mouseover");
+ consumedEvents_.add("mouseout");
+ }
+
+ @Override
+ public boolean dependsOnSelection()
+ {
+ return false;
+ }
+
+ @Override
+ public Set<String> getConsumedEvents()
+ {
+ return consumedEvents_;
+ }
+
+ @Override
+ public boolean handlesSelection()
+ {
+ return false;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean isEditing(Context context, Element parent, Boolean value)
+ {
+ // We aren't actually editing here, of course. All we're trying to do
+ // is prevent selection from changing, if the user is clicking on the
+ // checkbox of a cell that's in a selected row.
+ return mouseInCheckbox_ &&
+ selectionModel_.isSelected((TKey) context.getKey());
+ }
+
+ @Override
+ public void onBrowserEvent(Context context,
+ Element parent,
+ Boolean value,
+ NativeEvent event,
+ ValueUpdater<Boolean> booleanValueUpdater)
+ {
+ if (Element.is(event.getEventTarget()) &&
+ Element.as(event.getEventTarget()).getTagName().equalsIgnoreCase("img"))
+ {
+ if ("click".equals(event.getType()))
+ {
+ booleanValueUpdater.update(value == null ? true : !value);
+ }
+ else if ("mouseover".equals(event.getType()))
+ {
+ mouseInCheckbox_ = true;
+ }
+ else if ("mouseout".equals(event.getType()))
+ {
+ // WARNING!!!! Sometimes we get mouseover without a corresponding
+ // mouseout!! See comment at top of this class!
+ mouseInCheckbox_ = false;
+ }
+ }
+ }
+
+ @Override
+ public void render(Context context, Boolean value, SafeHtmlBuilder sb)
+ {
+ ImageResource img;
+ if (value == null)
+ img = RES.checkboxIndeterminate();
+ else if (value)
+ img = RES.checkboxOn();
+ else
+ img = RES.checkboxOff();
+
+ sb.append(SafeHtmlUtils.fromTrustedString(
+ AbstractImagePrototype.create(img).getHTML()));
+ }
+
+ @Override
+ public boolean resetFocus(Context context, Element parent, Boolean value)
+ {
+ return false;
+ }
+
+ @Override
+ public void setValue(Context context, Element parent, Boolean value)
+ {
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ render(context, value, builder);
+ parent.setInnerHTML(builder.toSafeHtml().asString());
+ }
+
+ private final HashSet<String> consumedEvents_;
+ private boolean mouseInCheckbox_;
+ private final SelectionModel<TKey> selectionModel_;
+ private static final Resources RES = GWT.create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/checkboxIndeterminate.png b/src/gwt/src/org/rstudio/core/client/cellview/checkboxIndeterminate.png
new file mode 100644
index 0000000..fec1ed4
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/cellview/checkboxIndeterminate.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/checkboxOff.png b/src/gwt/src/org/rstudio/core/client/cellview/checkboxOff.png
new file mode 100644
index 0000000..968beb6
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/cellview/checkboxOff.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/cellview/checkboxOn.png b/src/gwt/src/org/rstudio/core/client/cellview/checkboxOn.png
new file mode 100644
index 0000000..d4cf19c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/cellview/checkboxOn.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/command/AppCommand.java b/src/gwt/src/org/rstudio/core/client/command/AppCommand.java
new file mode 100644
index 0000000..e79bec5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/AppCommand.java
@@ -0,0 +1,395 @@
+/*
+ * AppCommand.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.MenuItem;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+
+public class AppCommand implements Command, ClickHandler, ImageResourceProvider
+{
+ private class CommandToolbarButton extends ToolbarButton implements
+ EnabledChangedHandler, VisibleChangedHandler
+ {
+ public CommandToolbarButton(String buttonLabel,
+ ImageResourceProvider imageResourceProvider, AppCommand command,
+ boolean synced)
+ {
+ super(buttonLabel, imageResourceProvider, command);
+ command_ = command;
+ synced_ = synced;
+ }
+
+ @Override
+ protected void onAttach()
+ {
+ if (synced_)
+ {
+ setEnabled(command_.isEnabled());
+ setVisible(command_.isVisible());
+ handlerReg_ = command_.addEnabledChangedHandler(this);
+ handlerReg2_ = command_.addVisibleChangedHandler(this);
+ }
+
+ parentToolbar_ = getParentToolbar();
+
+ super.onAttach();
+ }
+
+ @Override
+ protected void onDetach()
+ {
+ super.onDetach();
+
+ if (synced_)
+ {
+ handlerReg_.removeHandler();
+ handlerReg2_.removeHandler();
+ }
+ }
+
+ public void onEnabledChanged(AppCommand command)
+ {
+ setEnabled(command_.isEnabled());
+ }
+
+ public void onVisibleChanged(AppCommand command)
+ {
+ setVisible(command_.isVisible());
+ if (command_.isVisible())
+ setEnabled(command_.isEnabled());
+
+ parentToolbar_.invalidateSeparators();
+ }
+
+ private final AppCommand command_;
+ private boolean synced_ = true;
+ private HandlerRegistration handlerReg_;
+ private HandlerRegistration handlerReg2_;
+ private Toolbar parentToolbar_;
+ }
+
+ public AppCommand()
+ {
+ }
+
+ public void execute()
+ {
+ assert enabled_ : "AppCommand executed when it was not enabled";
+ if (!enabled_)
+ return;
+ assert visible_ : "AppCommand executed when it was not visible";
+ if (!visible_)
+ return;
+
+ if (enableNoHandlerAssertions_)
+ {
+ assert handlers_.getHandlerCount(CommandEvent.TYPE) > 0
+ : "AppCommand executed but nobody was listening";
+ }
+
+ handlers_.fireEvent(new CommandEvent(this));
+ }
+
+ public boolean isEnabled()
+ {
+ return enabled_ && isVisible(); // jcheng 06/30/2010: Hmmmm, smells weird.
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ if (enabled != enabled_)
+ {
+ enabled_ = enabled;
+ handlers_.fireEvent(new EnabledChangedEvent(this));
+ }
+ }
+
+ public boolean isVisible()
+ {
+ return visible_;
+ }
+
+ public void setVisible(boolean visible)
+ {
+ if (!removed_ && visible != visible_)
+ {
+ visible_ = visible;
+ handlers_.fireEvent(new VisibleChangedEvent(this));
+ }
+ }
+
+ public boolean isCheckable()
+ {
+ return checkable_;
+ }
+
+ public void setCheckable(boolean isCheckable)
+ {
+ checkable_ = isCheckable;
+ }
+
+ public boolean isChecked()
+ {
+ return checkable_ && checked_;
+ }
+
+ public void setChecked(boolean checked)
+ {
+ if (!isCheckable())
+ return;
+ checked_ = checked;
+ }
+
+ public boolean preventShortcutWhenDisabled()
+ {
+ return preventShortcutWhenDisabled_;
+ }
+
+ public void setPreventShortcutWhenDisabled(boolean preventShortcut)
+ {
+ preventShortcutWhenDisabled_ = preventShortcut;
+ }
+
+ /**
+ * Hides the command and makes sure it never comes back.
+ */
+ public void remove()
+ {
+ setVisible(false);
+ removed_ = true;
+ }
+
+ public String getDesc()
+ {
+ return desc_;
+ }
+
+ public String getTooltip()
+ {
+ String desc = StringUtil.notNull(getDesc());
+ String shortcut = getShortcutPrettyHtml();
+ shortcut = StringUtil.isNullOrEmpty(shortcut)
+ ? ""
+ : "(" + DomUtils.htmlToText(shortcut) + ")";
+
+ String result = (desc + " " + shortcut).trim();
+ return result.length() == 0 ? null : result;
+ }
+
+ public String getId()
+ {
+ return id_;
+ }
+
+ // Called by CommandBundleGenerator
+ public void setId(String id)
+ {
+ id_ = id;
+ }
+
+ public void setDesc(String desc)
+ {
+ desc_ = desc;
+ }
+
+ public String getLabel()
+ {
+ return label_;
+ }
+
+ public void setLabel(String label)
+ {
+ label_ = label;
+ }
+
+ public String getButtonLabel()
+ {
+ if (buttonLabel_ != null)
+ return buttonLabel_;
+ return getLabel();
+ }
+
+ public void setButtonLabel(String buttonLabel)
+ {
+ buttonLabel_ = buttonLabel;
+ }
+
+ public String getMenuLabel(boolean useMnemonics)
+ {
+ if (menuLabel_ != null)
+ {
+ return AppMenuItem.replaceMnemonics(menuLabel_, useMnemonics ? "&" : "");
+ }
+ return getLabel();
+ }
+
+ public void setMenuLabel(String menuLabel)
+ {
+ menuLabel_ = menuLabel;
+ }
+
+ @Override
+ public ImageResource getImageResource()
+ {
+ if (isCheckable())
+ {
+ return isChecked() ?
+ ThemeResources.INSTANCE.menuCheck() :
+ null;
+ }
+ else
+ {
+ return imageResource_;
+ }
+ }
+
+ @Override
+ public void addRenderedImage(Image image)
+ {
+ }
+
+ public void setImageResource(ImageResource imageResource)
+ {
+ imageResource_ = imageResource;
+ }
+
+ public HandlerRegistration addHandler(CommandHandler handler)
+ {
+ return handlers_.addHandler(CommandEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addEnabledChangedHandler(
+ EnabledChangedHandler handler)
+ {
+ return handlers_.addHandler(EnabledChangedEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addVisibleChangedHandler(
+ VisibleChangedHandler handler)
+ {
+ return handlers_.addHandler(VisibleChangedEvent.TYPE, handler);
+ }
+
+ public void onClick(ClickEvent event)
+ {
+ execute();
+ }
+
+ public ToolbarButton createToolbarButton()
+ {
+ return createToolbarButton(true);
+ }
+
+ public ToolbarButton createToolbarButton(boolean synced)
+ {
+ CommandToolbarButton button = new CommandToolbarButton(getButtonLabel(),
+ this,
+ this,
+ synced);
+ if (getTooltip() != null)
+ button.setTitle(getTooltip());
+ return button;
+ }
+
+ public MenuItem createMenuItem(boolean mainMenu)
+ {
+ return new AppMenuItem(this, mainMenu);
+ }
+
+ public String getMenuHTML(boolean mainMenu)
+ {
+ String label = getMenuLabel(false);
+ String shortcut = shortcut_ != null ? shortcut_.toString(true) : "";
+
+ return formatMenuLabel(
+ getImageResource(), label, shortcut);
+ }
+
+ public static String formatMenuLabel(ImageResource icon,
+ String label,
+ String shortcut)
+ {
+ StringBuilder text = new StringBuilder();
+ text.append("<table border=0 cellpadding=0 cellspacing=0 width='100%'><tr>");
+
+ text.append("<td width=\"25\"><div style=\"width: 25px; margin-top: -10px; margin-bottom: -10px\">");
+ if (icon != null)
+ {
+ text.append(AbstractImagePrototype.create(icon).getHTML());
+ }
+ else
+ {
+ text.append("<br/>");
+ }
+ text.append("</div></td>");
+
+ text.append("<td>" + DomUtils.textToHtml(StringUtil.notNull(label)) + "</td>");
+ if (shortcut != null)
+ text.append("<td align=right nowrap> " + shortcut + "</td>");
+ text.append("</tr></table>");
+
+ return text.toString();
+ }
+
+ public void setShortcut(KeyboardShortcut shortcut)
+ {
+ shortcut_ = shortcut;
+ }
+
+ public String getShortcutRaw()
+ {
+ return shortcut_ != null ? shortcut_.toString(false) : null;
+ }
+
+ public String getShortcutPrettyHtml()
+ {
+ return shortcut_ != null ? shortcut_.toString(true) : null;
+ }
+
+ public static void disableNoHandlerAssertions()
+ {
+ enableNoHandlerAssertions_ = false;
+ }
+
+ private boolean enabled_ = true;
+ private boolean visible_ = true;
+ private boolean removed_ = false;
+ private boolean preventShortcutWhenDisabled_ = true;
+ private boolean checkable_ = false;
+ private boolean checked_ = false;
+ private final HandlerManager handlers_ = new HandlerManager(this);
+
+ private String label_;
+ private String buttonLabel_;
+ private String menuLabel_;
+ private String desc_;
+ private ImageResource imageResource_;
+ private KeyboardShortcut shortcut_;
+ private String id_;
+
+ private static boolean enableNoHandlerAssertions_ = true;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/AppMenuBar.java b/src/gwt/src/org/rstudio/core/client/command/AppMenuBar.java
new file mode 100644
index 0000000..afc9342
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/AppMenuBar.java
@@ -0,0 +1,137 @@
+/*
+ * AppMenuBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+
+import org.rstudio.core.client.dom.DomUtils;
+
+import java.util.List;
+
+public class AppMenuBar extends BaseMenuBar
+{
+
+ public AppMenuBar(boolean vertical)
+ {
+ super(vertical);
+ vertical_ = vertical;
+ setFocusOnHoverEnabled(false);
+ }
+
+ @Override
+ public MenuItem addItem(String text, Command cmd)
+ {
+ return addItem(text, false, cmd);
+ }
+
+ @Override
+ public MenuItem addItem(String text, boolean asHTML, Command cmd)
+ {
+ if (vertical_ && !asHTML)
+ text = AppCommand.formatMenuLabel(null, text, null);
+ return super.addItem(text,
+ true,
+ cmd);
+ }
+
+ @Override
+ public MenuItem addItem(String text, MenuBar popup)
+ {
+ return addItem(text, false, popup);
+ }
+
+ @Override
+ public MenuItem addItem(String text, boolean asHTML, MenuBar popup)
+ {
+ if (!asHTML)
+ {
+ if (vertical_)
+ text = AppCommand.formatMenuLabel(null, text, null);
+ else
+ text = DomUtils.textToHtml(text);
+ }
+
+ return super.addItem(text,
+ true,
+ popup);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ if (++activeMenuCount_ == 2)
+ {
+ listeners_.fireEvent(new SubMenuVisibleChangedEvent(true));
+ }
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ if (--activeMenuCount_ == 1)
+ {
+ listeners_.fireEvent(new SubMenuVisibleChangedEvent(false));
+ }
+ super.onUnload();
+ }
+
+ @Override
+ public MenuItem getSelectedItem()
+ {
+ return super.getSelectedItem();
+ }
+
+ @Override
+ public List<MenuItem> getItems()
+ {
+ return super.getItems();
+ }
+
+ public static HandlerRegistration addSubMenuVisibleChangedHandler(
+ SubMenuVisibleChangedHandler handler)
+ {
+ return listeners_.addHandler(SubMenuVisibleChangedEvent.TYPE, handler);
+ }
+
+ // used to hide the menu bar itself if every item in the menu is a command,
+ // and every command is not visible.
+ // (consider: with a little work this could be more generic, such that any
+ // menu subtree consisting entirely of invisible commands would be hidden,
+ // but there are currently no cases where this is necessary.)
+ public boolean allInvisibleCmds()
+ {
+ for (MenuItem item: super.getItems())
+ {
+ if (item instanceof AppMenuItem)
+ {
+ if (((AppMenuItem)item).cmdVisible())
+ return false;
+ }
+ else
+ return false;
+ }
+ return true;
+ }
+
+ private static final HandlerManager listeners_ = new HandlerManager(null);
+ // Usual value is 1, because main menu counts as an active menu
+ private static int activeMenuCount_;
+ private final boolean vertical_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/AppMenuItem.java b/src/gwt/src/org/rstudio/core/client/command/AppMenuItem.java
new file mode 100644
index 0000000..1af7aa4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/AppMenuItem.java
@@ -0,0 +1,83 @@
+/*
+ * AppMenuItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.ui.MenuItem;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.regex.Pattern.ReplaceOperation;
+
+public class AppMenuItem extends MenuItem
+{
+ public AppMenuItem(AppCommand cmd, boolean mainMenu)
+ {
+ super(cmd.getMenuHTML(mainMenu), true, cmd);
+ cmd_ = cmd;
+ mainMenu_ = mainMenu;
+ setTitle(cmd_.getDesc());
+ }
+
+ @Override
+ public ScheduledCommand getScheduledCommand()
+ {
+ return getScheduledCommand(false);
+ }
+
+ public ScheduledCommand getScheduledCommand(boolean evenIfDisabled)
+ {
+ return evenIfDisabled || cmd_.isEnabled() ? super.getScheduledCommand() : null;
+ }
+
+ public void onShow()
+ {
+ if (cmd_.isEnabled())
+ getElement().removeClassName("disabled");
+ else
+ getElement().addClassName("disabled");
+
+ setVisible(cmd_.isVisible());
+
+ setHTML(cmd_.getMenuHTML(mainMenu_));
+ setTitle(cmd_.getDesc());
+ }
+
+ public boolean cmdVisible()
+ {
+ return cmd_.isVisible();
+ }
+
+ public static String escapeMnemonics(String label)
+ {
+ return label.replace("_", "__");
+ }
+
+ public static String replaceMnemonics(String label, final String replacement)
+ {
+ return Pattern.create("_(_?)").replaceAll(label, new ReplaceOperation()
+ {
+ public String replace(Match m)
+ {
+ if (m.getGroup(1).length() > 0)
+ return "_";
+ else
+ return replacement;
+ }
+ });
+ }
+
+ private final AppCommand cmd_;
+ private final boolean mainMenu_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/BaseMenuBar.java b/src/gwt/src/org/rstudio/core/client/command/BaseMenuBar.java
new file mode 100644
index 0000000..460482d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/BaseMenuBar.java
@@ -0,0 +1,194 @@
+/*
+ * BaseMenuBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.MenuItemSeparator;
+import com.google.gwt.user.client.ui.UIObject;
+import org.rstudio.core.client.SeparatorManager;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class BaseMenuBar extends MenuBar
+{
+ private class PositionComparator implements Comparator<UIObject>
+ {
+ public int compare(UIObject a, UIObject b)
+ {
+ return getPosition(a) - getPosition(b);
+ }
+
+ private int getPosition(UIObject item)
+ {
+ if (item instanceof MenuItem)
+ return getItemIndex((MenuItem) item);
+ else if (item instanceof MenuItemSeparator)
+ return getSeparatorIndex((MenuItemSeparator) item);
+
+ assert false;
+ return -1;
+ }
+ }
+
+ public BaseMenuBar(boolean vertical)
+ {
+ super(vertical);
+ vertical_ = vertical;
+
+ // Would prefer to inject this from the constructor but some
+ // subclasses are instantiated using generated code--don't feel
+ // like messing with all that now
+ eventBus_ = RStudioGinjector.INSTANCE.getEventBus();
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ if (vertical_ && glass++ == 0)
+ eventBus_.fireEvent(new GlassVisibilityEvent(true));
+ super.onLoad();
+ for (MenuItem child : getItems())
+ {
+ if (child instanceof AppMenuItem)
+ ((AppMenuItem)child).onShow();
+ else
+ {
+ // if this is a submenu that consists entirely of hidden commands,
+ // hide the submenu and its flyout icon
+ MenuBar submenu = child.getSubMenu();
+ if (submenu != null &&
+ submenu instanceof AppMenuBar)
+ {
+ boolean visible = child.isVisible();
+ boolean newVisible = !((AppMenuBar)submenu).allInvisibleCmds();
+ if (visible != newVisible)
+ {
+ child.setVisible(newVisible);
+ updateSubmenuIcon(child);
+ }
+ }
+ }
+ }
+ if (autoHideRedundantSeparators_)
+ manageSeparators();
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ super.onUnload();
+ if (vertical_ && --glass == 0)
+ eventBus_.fireEvent(new GlassVisibilityEvent(false));
+ }
+
+ public void setAutoHideRedundantSeparators(boolean value)
+ {
+ autoHideRedundantSeparators_ = value;
+ }
+
+ @Override
+ public MenuItemSeparator insertSeparator(MenuItemSeparator separator,
+ int beforeIndex) throws IndexOutOfBoundsException
+ {
+ MenuItemSeparator value = super.insertSeparator(separator, beforeIndex);
+ separators_.add(value);
+ return value;
+ }
+
+ @Override
+ public void removeSeparator(MenuItemSeparator separator)
+ {
+ separators_.remove(separator);
+ super.removeSeparator(separator);
+ }
+
+ @Override
+ public MenuItem getSelectedItem()
+ {
+ return super.getSelectedItem();
+ }
+
+ private static class MenuSeparatorManager extends SeparatorManager<UIObject>
+ {
+ @Override
+ protected boolean isSeparator(UIObject item)
+ {
+ return item instanceof MenuItemSeparator;
+ }
+
+ @Override
+ protected boolean isVisible(UIObject item)
+ {
+ return item.isVisible();
+ }
+
+ @Override
+ protected void setVisible(UIObject item, boolean visible)
+ {
+ item.setVisible(visible);
+ }
+ }
+
+ /**
+ * Make sure the proper separators appear and disappear
+ */
+ protected void manageSeparators()
+ {
+ if (separators_.size() == 0)
+ return;
+ List<MenuItem> menuItems = getItems();
+ ArrayList<UIObject> allItems =
+ new ArrayList<UIObject>(menuItems.size() + separators_.size());
+ allItems.addAll(separators_);
+ allItems.addAll(menuItems);
+ Collections.sort(allItems, new PositionComparator());
+
+ new MenuSeparatorManager().manageSeparators(allItems);
+ }
+
+ public int getItemCount()
+ {
+ return getItems().size();
+ }
+
+ public ArrayList<MenuItem> getVisibleItems()
+ {
+ ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+ for (MenuItem item : getItems())
+ if (item.isVisible())
+ items.add(item);
+ return items;
+ }
+
+ /**
+ * Reference count for glass visibility. NOTE: Perhaps this should be
+ * hoisted into a more general class so that everyone who raises
+ * GlassVisibilityEvent shares the same refcount.
+ */
+ private static int glass = 0;
+
+ private boolean autoHideRedundantSeparators_ = true;
+ private final ArrayList<MenuItemSeparator> separators_ =
+ new ArrayList<MenuItemSeparator>();
+ private final EventBus eventBus_;
+ private final boolean vertical_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/CommandBinder.java b/src/gwt/src/org/rstudio/core/client/command/CommandBinder.java
new file mode 100644
index 0000000..8f259ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/CommandBinder.java
@@ -0,0 +1,61 @@
+/*
+ * CommandBinder.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+/**
+ * Provides a mechanism for declaritively hooking up command handler methods
+ * to the relevant commands.
+ *
+ * 1) Create a method for each command to be handled. It should have a return
+ * type of void and no parameters. The method should* be named "on[Command]",
+ * for example if the command is named "saveChanges" then the method should
+ * be named "onSaveChanges". The method should have public or package level
+ * accessibility.
+ * 2) Annotate each handler method with @Handler.
+ * 3) Declare a subinterface for CommandBinder that specializes the type
+ * arguments with the appropriate values.
+ * 4) Use GWT.create() to create an instance of your new subinterface, and
+ * call bind().
+ *
+ * [*] Or you can use a different method name, if you provide the command name
+ * to the @Handler attribute.
+ *
+ * Example:
+ *
+ * interface MyCommands extends CommandBundle {
+ * public AppCommand saveChanges();
+ * }
+ *
+ * class MyObject {
+ * interface MyBinder extends CommandBinder<MyCommands, MyObject> {}
+ *
+ * public MyObject(Commands commands) {
+ * ((MyBinder)GWT.create(MyBinder.class)).bind(commands, this);
+ * }
+ *
+ * @Handler
+ * void onSaveChanges();
+ * }
+ *
+ * @param <TCommands> The subtype of CommandBundle that will be used to
+ * find the relevant commands
+ * @param <THandlers> The type of the object that contains the handler methods
+ */
+public interface CommandBinder<TCommands extends CommandBundle, THandlers>
+{
+ HandlerRegistration bind(TCommands commands, THandlers handlers);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/CommandBundle.java b/src/gwt/src/org/rstudio/core/client/command/CommandBundle.java
new file mode 100644
index 0000000..7a3b596
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/CommandBundle.java
@@ -0,0 +1,39 @@
+/*
+ * CommandBundle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import java.util.HashMap;
+
+/**
+ * Marker interface for declaring sets of commands
+ */
+public abstract class CommandBundle
+{
+ public AppCommand getCommandById(String commandId)
+ {
+ return commandsById_.get(commandId);
+ }
+
+ public void addCommand(String id, AppCommand command)
+ {
+ if (commandsById_.containsKey(id))
+ throw new IllegalStateException("Command " + id + " already exists");
+
+ commandsById_.put(id, command);
+ }
+
+ private final HashMap<String, AppCommand> commandsById_ =
+ new HashMap<String, AppCommand>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/CommandContext.java b/src/gwt/src/org/rstudio/core/client/command/CommandContext.java
new file mode 100644
index 0000000..c3f553d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/CommandContext.java
@@ -0,0 +1,19 @@
+/*
+ * CommandContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+public class CommandContext
+{
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/CommandEvent.java b/src/gwt/src/org/rstudio/core/client/command/CommandEvent.java
new file mode 100644
index 0000000..dee5acd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/CommandEvent.java
@@ -0,0 +1,46 @@
+/*
+ * CommandEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CommandEvent extends GwtEvent<CommandHandler>
+{
+ public static final Type<CommandHandler> TYPE = new Type<CommandHandler>();
+
+ public CommandEvent(AppCommand command)
+ {
+ command_ = command;
+ }
+
+ public AppCommand getCommand()
+ {
+ return command_;
+ }
+
+ @Override
+ public Type<CommandHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(CommandHandler handler)
+ {
+ handler.onCommand(command_);
+ }
+
+ private final AppCommand command_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/CommandHandler.java b/src/gwt/src/org/rstudio/core/client/command/CommandHandler.java
new file mode 100644
index 0000000..a1dde36
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/CommandHandler.java
@@ -0,0 +1,22 @@
+/*
+ * CommandHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface CommandHandler extends EventHandler
+{
+ void onCommand(AppCommand command);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/DisabledMenuItem.java b/src/gwt/src/org/rstudio/core/client/command/DisabledMenuItem.java
new file mode 100644
index 0000000..97ac5d9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/DisabledMenuItem.java
@@ -0,0 +1,40 @@
+/*
+ * DisabledMenuItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class DisabledMenuItem extends MenuItem
+{
+ public DisabledMenuItem(String text)
+ {
+ super(text, new Command()
+ {
+ public void execute()
+ {
+ }
+ });
+
+ getElement().addClassName("disabled");
+ }
+
+ @Override
+ public ScheduledCommand getScheduledCommand()
+ {
+ return null;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/EnabledChangedEvent.java b/src/gwt/src/org/rstudio/core/client/command/EnabledChangedEvent.java
new file mode 100644
index 0000000..ef1a087
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/EnabledChangedEvent.java
@@ -0,0 +1,46 @@
+/*
+ * EnabledChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnabledChangedEvent extends GwtEvent<EnabledChangedHandler>
+{
+ public static final Type<EnabledChangedHandler> TYPE = new Type<EnabledChangedHandler>();
+
+ public EnabledChangedEvent(AppCommand command)
+ {
+ command_ = command;
+ }
+
+ public AppCommand getCommand()
+ {
+ return command_;
+ }
+
+ @Override
+ public Type<EnabledChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(EnabledChangedHandler handler)
+ {
+ handler.onEnabledChanged(command_);
+ }
+
+ private final AppCommand command_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/EnabledChangedHandler.java b/src/gwt/src/org/rstudio/core/client/command/EnabledChangedHandler.java
new file mode 100644
index 0000000..3e37dad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/EnabledChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * EnabledChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface EnabledChangedHandler extends EventHandler
+{
+ void onEnabledChanged(AppCommand command);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/Handler.java b/src/gwt/src/org/rstudio/core/client/command/Handler.java
new file mode 100644
index 0000000..868c5ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/Handler.java
@@ -0,0 +1,24 @@
+/*
+ * Handler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+ at Target(ElementType.METHOD)
+public @interface Handler
+{
+ String value() default "";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/ImageResourceProvider.java b/src/gwt/src/org/rstudio/core/client/command/ImageResourceProvider.java
new file mode 100644
index 0000000..7784b7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/ImageResourceProvider.java
@@ -0,0 +1,25 @@
+/*
+ * ImageResourceProvider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.command;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Image;
+
+public interface ImageResourceProvider
+{
+ public ImageResource getImageResource();
+ public void addRenderedImage(Image image);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/KeyboardShortcut.java b/src/gwt/src/org/rstudio/core/client/command/KeyboardShortcut.java
new file mode 100644
index 0000000..49c19eb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/KeyboardShortcut.java
@@ -0,0 +1,174 @@
+/*
+ * KeyboardShortcut.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import org.rstudio.core.client.BrowseCap;
+
+public class KeyboardShortcut
+{
+ public KeyboardShortcut(int keycode)
+ {
+ this(keycode, "");
+ }
+
+ public KeyboardShortcut(int keycode, String groupName)
+ {
+ this(KeyboardShortcut.NONE, keycode, groupName, "");
+ }
+
+ public KeyboardShortcut(int modifiers, int keycode)
+ {
+ this(modifiers, keycode, "", "");
+ }
+
+ public KeyboardShortcut(int modifiers, int keycode,
+ String groupName, String title)
+ {
+ modifiers_ = modifiers;
+ keycode_ = keycode;
+ groupName_ = groupName;
+ order_ = ORDER++;
+ title_ = title;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null) return false;
+
+ KeyboardShortcut that = (KeyboardShortcut) o;
+
+ return keycode_ == that.keycode_
+ && modifiers_ == that.modifiers_;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = modifiers_;
+ result = (result << 8) + keycode_;
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return toString(false);
+ }
+
+ public String toString(boolean pretty)
+ {
+ if (BrowseCap.hasMetaKey() && pretty)
+ {
+ return ((modifiers_ & CTRL) == CTRL ? "⌃" : "")
+ + ((modifiers_ & SHIFT) == SHIFT ? "⇧" : "")
+ + ((modifiers_ & ALT) == ALT ? "⌥" : "")
+ + ((modifiers_ & META) == META ? "⌘" : "")
+ + getKeyName(true);
+ }
+ else
+ {
+ return ((modifiers_ & CTRL) == CTRL ? "Ctrl+" : "")
+ + ((modifiers_ & SHIFT) == SHIFT ? "Shift+" : "")
+ + ((modifiers_ & ALT) == ALT ? "Alt+" : "")
+ + ((modifiers_ & META) == META ? "Meta+" : "")
+ + getKeyName(pretty);
+ }
+ }
+
+ public String getGroupName()
+ {
+ return groupName_;
+ }
+
+ public int getOrder()
+ {
+ return order_;
+ }
+
+ public String getTitle()
+ {
+ return title_;
+ }
+
+ private String getKeyName(boolean pretty)
+ {
+ boolean macStyle = BrowseCap.hasMetaKey() && pretty;
+
+ if (keycode_ == KeyCodes.KEY_ENTER)
+ return macStyle ? "↩" : "Enter";
+ else if (keycode_ == KeyCodes.KEY_LEFT)
+ return macStyle ? "←" : "Left";
+ else if (keycode_ == KeyCodes.KEY_RIGHT)
+ return macStyle ? "→" : "Right";
+ else if (keycode_ == KeyCodes.KEY_UP)
+ return macStyle ? "↑" : "Up";
+ else if (keycode_ == KeyCodes.KEY_DOWN)
+ return macStyle ? "↓" : "Down";
+ else if (keycode_ == KeyCodes.KEY_TAB)
+ return macStyle ? "⇥" : "Tab";
+ else if (keycode_ == KeyCodes.KEY_PAGEUP)
+ return pretty ? "PgUp" : "PageUp";
+ else if (keycode_ == KeyCodes.KEY_PAGEDOWN)
+ return pretty ? "PgDn" : "PageDown";
+ else if (keycode_ == 191)
+ return "/";
+ else if (keycode_ == 192)
+ return "`";
+ else if (keycode_ == 190)
+ return ".";
+ else if (keycode_ == 187)
+ return "=";
+ else if (keycode_ == 188)
+ return "<";
+ else if (keycode_ >= 112 && keycode_ <= 123)
+ return "F" + (keycode_ - 111);
+ else if (keycode_ == 8)
+ return macStyle ? "⌫" : "Backspace";
+
+
+ return Character.toUpperCase((char)keycode_) + "";
+ }
+
+ public static int getModifierValue(NativeEvent e)
+ {
+ int modifiers = 0;
+ if (e.getAltKey())
+ modifiers += ALT;
+ if (e.getCtrlKey())
+ modifiers += CTRL;
+ if (e.getMetaKey())
+ modifiers += META;
+ if (e.getShiftKey())
+ modifiers += SHIFT;
+ return modifiers;
+ }
+
+ private final int modifiers_;
+ private final int keycode_;
+ private String groupName_;
+ private int order_ = 0;
+ private String title_ = "";
+
+ private static int ORDER = 0;
+
+ public static final int NONE = 0;
+ public static final int ALT = 1;
+ public static final int CTRL = 2;
+ public static final int META = 4;
+ public static final int SHIFT = 8;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/MenuCallback.java b/src/gwt/src/org/rstudio/core/client/command/MenuCallback.java
new file mode 100644
index 0000000..eb0c7e2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/MenuCallback.java
@@ -0,0 +1,29 @@
+/*
+ * MenuCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+public interface MenuCallback
+{
+ void beginMainMenu();
+
+ void beginMenu(String label);
+
+ void addCommand(String commandId, AppCommand command);
+ void addSeparator();
+
+ void endMenu();
+
+ void endMainMenu();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/ShortcutInfo.java b/src/gwt/src/org/rstudio/core/client/command/ShortcutInfo.java
new file mode 100644
index 0000000..1c20e48
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/ShortcutInfo.java
@@ -0,0 +1,75 @@
+/*
+ * ShortcutInfo.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShortcutInfo
+{
+ public ShortcutInfo (KeyboardShortcut shortcut, AppCommand command)
+ {
+ shortcuts_ = new ArrayList<String>();
+ description_ = shortcut.getTitle().length() > 0 ?
+ shortcut.getTitle() :
+ command != null ?
+ command.getMenuLabel(false) :
+ "";
+ groupName_ = shortcut.getGroupName();
+ isActive_ = command != null ?
+ (command.isEnabled() && command.isVisible()) :
+ true;
+ order_ = shortcut.getOrder();
+ addShortcut(shortcut);
+ }
+
+ public String getDescription()
+ {
+ return description_;
+ }
+
+ public List<String> getShortcuts()
+ {
+ return shortcuts_;
+ }
+
+ public void addShortcut(KeyboardShortcut shortcut)
+ {
+ shortcuts_.clear();
+ shortcuts_.add(shortcut.toString(true));
+ }
+
+ public String getGroupName()
+ {
+ return groupName_;
+ }
+
+ public boolean isActive()
+ {
+ return isActive_;
+ }
+
+ public int getOrder()
+ {
+ return order_;
+ }
+
+ private List<String> shortcuts_;
+ private String description_;
+ private String groupName_;
+ private boolean isActive_;
+ private int order_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/command/ShortcutManager.java b/src/gwt/src/org/rstudio/core/client/command/ShortcutManager.java
new file mode 100644
index 0000000..8dfa874
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/ShortcutManager.java
@@ -0,0 +1,198 @@
+/*
+ * ShortcutManager.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.events.NativeKeyDownEvent;
+import org.rstudio.core.client.events.NativeKeyDownHandler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+public class ShortcutManager implements NativePreviewHandler,
+ NativeKeyDownHandler
+{
+ public interface Handle
+ {
+ void close();
+ }
+
+ public static final ShortcutManager INSTANCE = new ShortcutManager();
+
+ private ShortcutManager()
+ {
+ Event.addNativePreviewHandler(this);
+ }
+
+ public boolean isEnabled()
+ {
+ return disableCount_ == 0;
+ }
+
+ public Handle disable()
+ {
+ disableCount_++;
+ return new Handle()
+ {
+ private boolean closed_ = false;
+
+ @Override
+ public void close()
+ {
+ if (!closed_)
+ disableCount_--;
+ closed_ = true;
+ }
+ };
+ }
+
+ public void register(int modifiers,
+ int keyCode,
+ AppCommand command,
+ String groupName,
+ String title)
+ {
+ if (!BrowseCap.hasMetaKey() && (modifiers & KeyboardShortcut.META) != 0)
+ return;
+
+ KeyboardShortcut shortcut =
+ new KeyboardShortcut(modifiers, keyCode, groupName, title);
+ if (command == null)
+ {
+ // If the shortcut is unbound, check to see whether there's another
+ // unbound shortcut with the same title; replace it if there is.
+ boolean existingShortcut = false;
+ for (int i = 0; i < unboundShortcuts_.size(); i++) {
+ if (unboundShortcuts_.get(i).getTitle().equals(title)) {
+ unboundShortcuts_.set(i, shortcut);
+ existingShortcut = true;
+ break;
+ }
+ }
+ if (!existingShortcut)
+ unboundShortcuts_.add(shortcut);
+ }
+ else
+ {
+ commands_.put(shortcut, command);
+ command.setShortcut(shortcut);
+ }
+ }
+
+ public void onKeyDown(NativeKeyDownEvent evt)
+ {
+ if (evt.isCanceled())
+ return;
+
+ if (handleKeyDown(evt.getEvent()))
+ evt.cancel();
+ }
+
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.isCanceled())
+ return;
+
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ if (handleKeyDown(event.getNativeEvent()))
+ event.cancel();
+ }
+ }
+
+ public List<ShortcutInfo> getActiveShortcutInfo()
+ {
+ List<ShortcutInfo> info = new ArrayList<ShortcutInfo>();
+
+ HashMap<Command, ShortcutInfo> infoMap =
+ new HashMap<Command, ShortcutInfo>();
+ Set<KeyboardShortcut> shortcuts = commands_.keySet();
+
+ // Create a ShortcutInfo for each unbound shortcut
+ for (KeyboardShortcut shortcut: unboundShortcuts_)
+ {
+ info.add(new ShortcutInfo(shortcut, null));
+ }
+
+ // Create a ShortcutInfo for each command (a command may have multiple
+ // shortcut bindings)
+ for (KeyboardShortcut shortcut: shortcuts)
+ {
+ AppCommand command = commands_.get(shortcut);
+ if (infoMap.containsKey(command))
+ {
+ infoMap.get(command).addShortcut(shortcut);
+ }
+ else
+ {
+ ShortcutInfo shortcutInfo = new ShortcutInfo(shortcut, command);
+ info.add(shortcutInfo);
+ infoMap.put(command, shortcutInfo);
+ }
+ }
+ // Sort the commands back into the order in which they were created
+ // (reading them out of the keyset mangles the original order)
+ Collections.sort(info, new Comparator<ShortcutInfo>()
+ {
+ @Override
+ public int compare(ShortcutInfo o1, ShortcutInfo o2)
+ {
+ return o1.getOrder() - o2.getOrder();
+ }
+ });
+ return info;
+ }
+
+ private boolean handleKeyDown(NativeEvent e)
+ {
+ int modifiers = KeyboardShortcut.getModifierValue(e);
+
+ KeyboardShortcut shortcut = new KeyboardShortcut(modifiers,
+ e.getKeyCode());
+ AppCommand command = commands_.get(shortcut);
+ if (command != null)
+ {
+ boolean enabled = isEnabled() && command.isEnabled();
+
+ // some commands want their keyboard shortcut to pass through
+ // to the browser when they are disabled (e.g. Cmd+W)
+ if (!enabled && !command.preventShortcutWhenDisabled())
+ return false;
+
+ e.preventDefault();
+
+ if (enabled)
+ command.execute();
+ }
+
+ return command != null;
+ }
+
+ private int disableCount_ = 0;
+ private final HashMap<KeyboardShortcut, AppCommand> commands_
+ = new HashMap<KeyboardShortcut, AppCommand>();
+ private ArrayList<KeyboardShortcut> unboundShortcuts_
+ = new ArrayList<KeyboardShortcut>();
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/ShortcutViewer.java b/src/gwt/src/org/rstudio/core/client/command/ShortcutViewer.java
new file mode 100644
index 0000000..e02bc5c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/ShortcutViewer.java
@@ -0,0 +1,117 @@
+/*
+ * ShortcutViewer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.command;
+
+
+import org.rstudio.core.client.widget.ShortcutInfoPanel;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ShortcutViewer implements NativePreviewHandler
+{
+ public interface Binder extends CommandBinder<Commands, ShortcutViewer> {}
+
+ @Inject
+ public ShortcutViewer(
+ Binder binder,
+ Commands commands,
+ GlobalDisplay globalDisplay)
+ {
+ binder.bind(commands, this);
+
+ globalDisplay_ = globalDisplay;
+ }
+
+ @Handler
+ public void onHelpKeyboardShortcuts()
+ {
+ // prevent reentry
+ if (shortcutInfo_ != null)
+ {
+ return;
+ }
+ shortcutInfo_ = new ShortcutInfoPanel(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().showKeyboardShortcutHelp();
+ else
+ openApplicationURL("docs/keyboard.htm");
+ }
+ });
+ RootLayoutPanel.get().add(shortcutInfo_);
+ preview_ = Event.addNativePreviewHandler(this);
+ }
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.isCanceled())
+ return;
+
+ if (event.getTypeInt() == Event.ONKEYDOWN ||
+ event.getTypeInt() == Event.ONMOUSEDOWN)
+ {
+ if (event.getTypeInt() == Event.ONMOUSEDOWN &&
+ event.getNativeEvent().getButton() == NativeEvent.BUTTON_RIGHT)
+ return;
+
+ // Let the user click on the full doc link without dismissing the
+ // popover
+ EventTarget et = event.getNativeEvent().getEventTarget();
+ if (Element.is(et)) {
+ Element e = Element.as(et);
+ if (e.getTagName().equalsIgnoreCase("a"))
+ return;
+ }
+
+ if (shortcutInfo_ != null)
+ RootLayoutPanel.get().remove(shortcutInfo_);
+ shortcutInfo_ = null;
+ if (preview_ != null)
+ preview_.removeHandler();
+ preview_ = null;
+ event.cancel();
+ }
+ }
+
+ private void openApplicationURL(String relativeURL)
+ {
+ String url = GWT.getHostPageBaseURL() + relativeURL;
+ globalDisplay_.openWindow(url);
+ }
+
+ private ShortcutInfoPanel shortcutInfo_ = null;
+ private HandlerRegistration preview_;
+ private GlobalDisplay globalDisplay_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/command/SimpleImageResourceProvider.java b/src/gwt/src/org/rstudio/core/client/command/SimpleImageResourceProvider.java
new file mode 100644
index 0000000..39eafb7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/SimpleImageResourceProvider.java
@@ -0,0 +1,40 @@
+/*
+ * SimpleImageResourceProvider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.command;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Image;
+
+public class SimpleImageResourceProvider implements ImageResourceProvider
+{
+ public SimpleImageResourceProvider(ImageResource image)
+ {
+ imageResource_ = image;
+ }
+
+ @Override
+ public ImageResource getImageResource()
+ {
+ return imageResource_;
+ }
+
+ @Override
+ public void addRenderedImage(Image image)
+ {
+ }
+
+ private ImageResource imageResource_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedEvent.java b/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedEvent.java
new file mode 100644
index 0000000..1907c3e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedEvent.java
@@ -0,0 +1,45 @@
+/*
+ * SubMenuVisibleChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SubMenuVisibleChangedEvent extends GwtEvent<SubMenuVisibleChangedHandler>
+{
+ public static final Type<SubMenuVisibleChangedHandler> TYPE = new Type<SubMenuVisibleChangedHandler>();
+ @Override
+ public Type<SubMenuVisibleChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ public SubMenuVisibleChangedEvent(boolean visible)
+ {
+ visible_ = visible;
+ }
+
+ public boolean isVisible()
+ {
+ return visible_;
+ }
+
+ @Override
+ protected void dispatch(SubMenuVisibleChangedHandler handler)
+ {
+ handler.onSubMenuVisibleChanged(this);
+ }
+
+ private final boolean visible_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedHandler.java b/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedHandler.java
new file mode 100644
index 0000000..d18893b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/SubMenuVisibleChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SubMenuVisibleChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SubMenuVisibleChangedHandler extends EventHandler
+{
+ void onSubMenuVisibleChanged(SubMenuVisibleChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/VisibleChangedEvent.java b/src/gwt/src/org/rstudio/core/client/command/VisibleChangedEvent.java
new file mode 100644
index 0000000..e83bc19
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/VisibleChangedEvent.java
@@ -0,0 +1,46 @@
+/*
+ * VisibleChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class VisibleChangedEvent extends GwtEvent<VisibleChangedHandler>
+{
+ public static final Type<VisibleChangedHandler> TYPE = new Type<VisibleChangedHandler>();
+
+ public VisibleChangedEvent(AppCommand command)
+ {
+ command_ = command;
+ }
+
+ public AppCommand getCommand()
+ {
+ return command_;
+ }
+
+ @Override
+ public Type<VisibleChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(VisibleChangedHandler handler)
+ {
+ handler.onVisibleChanged(command_);
+ }
+
+ private final AppCommand command_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/VisibleChangedHandler.java b/src/gwt/src/org/rstudio/core/client/command/VisibleChangedHandler.java
new file mode 100644
index 0000000..85742ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/VisibleChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * VisibleChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface VisibleChangedHandler extends EventHandler
+{
+ void onVisibleChanged(AppCommand command);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/impl/DesktopMenuCallback.java b/src/gwt/src/org/rstudio/core/client/command/impl/DesktopMenuCallback.java
new file mode 100644
index 0000000..a5f6a01
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/impl/DesktopMenuCallback.java
@@ -0,0 +1,59 @@
+/*
+ * DesktopMenuCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command.impl;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.MenuCallback;
+
+public class DesktopMenuCallback implements MenuCallback
+{
+ public native final void beginMainMenu() /*-{
+ $wnd.desktopMenuCallback.beginMainMenu();
+ }-*/;
+
+ public native final void beginMenu(String label) /*-{
+ label = @org.rstudio.core.client.command.AppMenuItem::replaceMnemonics(Ljava/lang/String;Ljava/lang/String;)(label, "&");
+ $wnd.desktopMenuCallback.beginMenu(label);
+ }-*/;
+
+ public void addCommand(String commandId, AppCommand command)
+ {
+ addCommand(commandId,
+ command.getMenuLabel(true),
+ command.getTooltip(),
+ command.getShortcutRaw(),
+ command.isCheckable());
+ }
+
+ private native void addCommand(String cmdId,
+ String label,
+ String tooltip,
+ String shortcut,
+ boolean isCheckable) /*-{
+ $wnd.desktopMenuCallback.addCommand(cmdId, label, tooltip, shortcut, isCheckable);
+ }-*/;
+
+ public native final void addSeparator() /*-{
+ $wnd.desktopMenuCallback.addSeparator();
+ }-*/;
+
+ public native final void endMenu() /*-{
+ $wnd.desktopMenuCallback.endMenu();
+ }-*/;
+
+ public native final void endMainMenu() /*-{
+ $wnd.desktopMenuCallback.endMainMenu();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/command/impl/WebMenuCallback.java b/src/gwt/src/org/rstudio/core/client/command/impl/WebMenuCallback.java
new file mode 100644
index 0000000..9d641f2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/command/impl/WebMenuCallback.java
@@ -0,0 +1,72 @@
+/*
+ * WebMenuCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.command.impl;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.AppMenuBar;
+import org.rstudio.core.client.command.AppMenuItem;
+import org.rstudio.core.client.command.MenuCallback;
+
+import java.util.Stack;
+
+public class WebMenuCallback implements MenuCallback
+{
+ public void beginMainMenu()
+ {
+ menuStack_.push(new AppMenuBar(false));
+ }
+
+ public void beginMenu(String label)
+ {
+ label = AppMenuItem.replaceMnemonics(label, "");
+
+ AppMenuBar newMenu = new AppMenuBar(true);
+ head().addItem(label, newMenu);
+ menuStack_.push(newMenu);
+ }
+
+ public void addCommand(String commandId, AppCommand command)
+ {
+ head().addItem(command.createMenuItem(true));
+ }
+
+ public void addSeparator()
+ {
+ head().addSeparator();
+ }
+
+ public void endMenu()
+ {
+ menuStack_.pop();
+ }
+
+ public void endMainMenu()
+ {
+ result_ = menuStack_.pop();
+ }
+
+ public AppMenuBar getMenu()
+ {
+ return result_;
+ }
+
+ private AppMenuBar head()
+ {
+ return menuStack_.peek();
+ }
+
+ private final Stack<AppMenuBar> menuStack_ = new Stack<AppMenuBar>();
+ private AppMenuBar result_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/DomMetrics.java b/src/gwt/src/org/rstudio/core/client/dom/DomMetrics.java
new file mode 100644
index 0000000..cdaa98f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/DomMetrics.java
@@ -0,0 +1,102 @@
+/*
+ * DomMetrics.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootPanel;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.widget.FontSizer;
+
+public class DomMetrics
+{
+ public static Size measureHTML(String html)
+ {
+ return measureHTML(html, "gwt-Label");
+ }
+ public static Size measureHTML(String html, String styleName)
+ {
+ // create HTML widget which matches the specified style
+ HTML measureHTML = new HTML();
+ measureHTML.setStyleName(styleName);
+ measureHTML.getElement().getStyle().setFloat(Style.Float.LEFT);
+ measureHTML.setWordWrap(false);
+
+ // add it to the dom (hidden)
+ RootPanel.get().add(measureHTML, -2000, -2000);
+
+ // insert the text (preformatted) and measure it
+ measureHTML.setHTML(html);
+ Size textSize = new Size(measureHTML.getOffsetWidth(),
+ measureHTML.getOffsetHeight());
+ RootPanel.get().remove(measureHTML);
+
+ // return the size
+ return textSize;
+ }
+
+ public static Size measureCode(String code)
+ {
+ return DomMetrics.measureHTML(
+ "<pre>" + DomUtils.textToPreHtml(code) + "</pre>",
+ "ace_editor " + FontSizer.getNormalFontSizeClass());
+ }
+
+ public static Size adjustedElementSize(Size contentSize,
+ Size minimumSize,
+ int contentPad,
+ int clientMargin)
+ {
+ // add the padding
+ contentSize = new Size(contentSize.width + contentPad,
+ contentSize.height + contentPad);
+
+ // enforce the minimum (if specified)
+ if (minimumSize != null)
+ {
+ contentSize = new Size(Math.max(contentSize.width, minimumSize.width),
+ Math.max(contentSize.height, minimumSize.height));
+ }
+
+ // maximum is client area - (margin * 2)
+ Size maximumSize = new Size(Window.getClientWidth() - (clientMargin*2),
+ Window.getClientHeight() - (clientMargin*2));
+ int width = Math.min(contentSize.width, maximumSize.width);
+ int height = Math.min(contentSize.height, maximumSize.height);
+ return new Size(width, height);
+ }
+
+ public static Size adjustedCodeElementSize(String code,
+ int contentPad,
+ int clientMargin)
+ {
+ // line numbers
+ final int LINE_NUMBERS_WIDTH = 100;
+
+ // calculate the size of the text the adjust for line numbers
+ Size textSize = DomMetrics.measureCode(code);
+ textSize = new Size(textSize.width + LINE_NUMBERS_WIDTH,
+ textSize.height);
+
+ // compute the editor size
+ Size minimumSize = new Size(300, 200);
+ Size editorSize = DomMetrics.adjustedElementSize(textSize,
+ minimumSize,
+ contentPad,
+ clientMargin);
+ return editorSize;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/DomUtils.java b/src/gwt/src/org/rstudio/core/client/dom/DomUtils.java
new file mode 100644
index 0000000..2e63089
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/DomUtils.java
@@ -0,0 +1,732 @@
+/*
+ * DomUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayMixed;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.*;
+import com.google.gwt.user.client.ui.UIObject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.dom.impl.DomUtilsImpl;
+import org.rstudio.core.client.dom.impl.NodeRelativePosition;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.application.Desktop;
+
+/**
+ * Helper methods that are mostly useful for interacting with
+ * contentEditable regions.
+ */
+public class DomUtils
+{
+ public interface NodePredicate
+ {
+ boolean test(Node n) ;
+ }
+
+ public static native Element getActiveElement() /*-{
+ return $doc.activeElement;
+ }-*/;
+
+ /**
+ * In IE8, focusing the history table (which is larger than the scroll
+ * panel it's contained in) causes the scroll panel to jump to the top.
+ * Using setActive() solves this problem. Other browsers don't support
+ * setActive but also don't have the scrolling problem.
+ * @param element
+ */
+ public static native void setActive(Element element) /*-{
+ if (element.setActive)
+ element.setActive();
+ else
+ element.focus();
+ }-*/;
+
+ public static int trimLines(Element element, int linesToTrim)
+ {
+ return trimLines(element.getChildNodes(), linesToTrim);
+ }
+
+ public static native void scrollToBottom(Element element) /*-{
+ element.scrollTop = element.scrollHeight;
+ }-*/;
+
+ public static JavaScriptObject splice(JavaScriptObject array,
+ int index,
+ int howMany,
+ String... elements)
+ {
+ JsArrayMixed args = JavaScriptObject.createArray().cast();
+ args.push(index);
+ args.push(howMany);
+ for (String el : elements)
+ args.push(el);
+ return spliceInternal(array, args);
+ }
+
+ private static native JsArrayString spliceInternal(JavaScriptObject array,
+ JsArrayMixed args) /*-{
+ return Array.prototype.splice.apply(array, args);
+ }-*/;
+
+ public static Node findNodeUpwards(Node node,
+ Element scope,
+ NodePredicate predicate)
+ {
+ if (scope != null && !scope.isOrHasChild(node))
+ throw new IllegalArgumentException("Incorrect scope passed to findParentNode");
+
+ for (; node != null; node = node.getParentNode())
+ {
+ if (predicate.test(node))
+ return node;
+ if (scope == node)
+ return null;
+ }
+ return null;
+ }
+
+ public static boolean isEffectivelyVisible(Element element)
+ {
+ while (element != null)
+ {
+ if (!UIObject.isVisible(element))
+ return false;
+
+ // If element never equals body, then the element is not attached
+ if (element == Document.get().getBody())
+ return true;
+
+ element = element.getParentElement();
+ }
+
+ // Element is not attached
+ return false;
+ }
+
+ public static void selectElement(Element el)
+ {
+ impl.selectElement(el);
+ }
+
+ private static final Pattern NEWLINE = Pattern.create("\\n");
+ private static int trimLines(NodeList<Node> nodes, final int linesToTrim)
+ {
+ if (nodes == null || nodes.getLength() == 0 || linesToTrim == 0)
+ return 0;
+
+ int linesLeft = linesToTrim;
+
+ Node node = nodes.getItem(0);
+
+ while (node != null && linesLeft > 0)
+ {
+ switch (node.getNodeType())
+ {
+ case Node.ELEMENT_NODE:
+ if (((Element)node).getTagName().equalsIgnoreCase("br"))
+ {
+ linesLeft--;
+ node = removeAndGetNext(node);
+ continue;
+ }
+ else
+ {
+ int trimmed = trimLines(node.getChildNodes(), linesLeft);
+ linesLeft -= trimmed;
+ if (!node.hasChildNodes())
+ node = removeAndGetNext(node);
+ continue;
+ }
+ case Node.TEXT_NODE:
+ String text = ((Text)node).getData();
+
+ Match lastMatch = null;
+ Match match = NEWLINE.match(text, 0);
+ while (match != null && linesLeft > 0)
+ {
+ lastMatch = match;
+ linesLeft--;
+ match = match.nextMatch();
+ }
+
+ if (linesLeft > 0 || lastMatch == null)
+ {
+ node = removeAndGetNext(node);
+ continue;
+ }
+ else
+ {
+ int index = lastMatch.getIndex() + 1;
+ if (text.length() == index)
+ node.removeFromParent();
+ else
+ ((Text) node).deleteData(0, index);
+ break;
+ }
+ }
+ }
+
+ return linesToTrim - linesLeft;
+ }
+
+ private static Node removeAndGetNext(Node node)
+ {
+ Node next = node.getNextSibling();
+ node.removeFromParent();
+ return next;
+ }
+
+ /**
+ *
+ * @param node
+ * @param pre Count hard returns in text nodes as newlines (only true if
+ * white-space mode is pre*)
+ * @return
+ */
+ public static int countLines(Node node, boolean pre)
+ {
+ switch (node.getNodeType())
+ {
+ case Node.TEXT_NODE:
+ return countLinesInternal((Text)node, pre);
+ case Node.ELEMENT_NODE:
+ return countLinesInternal((Element)node, pre);
+ default:
+ return 0;
+ }
+ }
+
+ private static int countLinesInternal(Text textNode, boolean pre)
+ {
+ if (!pre)
+ return 0;
+ String value = textNode.getData();
+ Pattern pattern = Pattern.create("\\n");
+ int count = 0;
+ Match m = pattern.match(value, 0);
+ while (m != null)
+ {
+ count++;
+ m = m.nextMatch();
+ }
+ return count;
+ }
+
+ private static int countLinesInternal(Element elementNode, boolean pre)
+ {
+ if (elementNode.getTagName().equalsIgnoreCase("br"))
+ return 1;
+
+ int result = 0;
+ NodeList<Node> children = elementNode.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++)
+ result += countLines(children.getItem(i), pre);
+ return result;
+ }
+
+ private final static DomUtilsImpl impl = GWT.create(DomUtilsImpl.class);
+
+ /**
+ * Drives focus to the element, and if (the element is contentEditable and
+ * contains no text) or alwaysDriveSelection is true, also drives window
+ * selection to the contents of the element. This is sometimes necessary
+ * to get focus to move at all.
+ */
+ public static void focus(Element element, boolean alwaysDriveSelection)
+ {
+ impl.focus(element, alwaysDriveSelection);
+ }
+
+ public static native boolean hasFocus(Element element) /*-{
+ return element === $doc.activeElement;
+ }-*/;
+
+ public static void collapseSelection(boolean toStart)
+ {
+ impl.collapseSelection(toStart);
+ }
+
+ public static boolean isSelectionCollapsed()
+ {
+ return impl.isSelectionCollapsed();
+ }
+
+ public static boolean isSelectionInElement(Element element)
+ {
+ return impl.isSelectionInElement(element);
+ }
+
+ /**
+ * Returns true if the window contains an active selection.
+ */
+ public static boolean selectionExists()
+ {
+ return impl.selectionExists();
+ }
+
+ public static boolean contains(Element container, Node descendant)
+ {
+ while (descendant != null)
+ {
+ if (descendant == container)
+ return true ;
+
+ descendant = descendant.getParentNode() ;
+ }
+ return false ;
+ }
+
+ /**
+ * CharacterData.deleteData(node, index, offset)
+ */
+ public static final native void deleteTextData(Text node,
+ int offset,
+ int length) /*-{
+ node.deleteData(offset, length);
+ }-*/;
+
+ public static native void insertTextData(Text node,
+ int offset,
+ String data) /*-{
+ node.insertData(offset, data);
+ }-*/;
+
+ public static Rectangle getCursorBounds()
+ {
+ return getCursorBounds(Document.get()) ;
+ }
+
+ public static Rectangle getCursorBounds(Document doc)
+ {
+ return impl.getCursorBounds(doc);
+ }
+
+ public static String replaceSelection(Document document, String text)
+ {
+ return impl.replaceSelection(document, text);
+ }
+
+ public static String getSelectionText(Document document)
+ {
+ return impl.getSelectionText(document);
+ }
+
+ public static int[] getSelectionOffsets(Element container)
+ {
+ return impl.getSelectionOffsets(container);
+ }
+
+ public static void setSelectionOffsets(Element container,
+ int start,
+ int end)
+ {
+ impl.setSelectionOffsets(container, start, end);
+ }
+
+ public static Text splitTextNodeAt(Element container, int offset)
+ {
+ NodeRelativePosition pos = NodeRelativePosition.toPosition(container, offset) ;
+
+ if (pos != null)
+ {
+ return ((Text)pos.node).splitText(pos.offset) ;
+ }
+ else
+ {
+ Text newNode = container.getOwnerDocument().createTextNode("");
+ container.appendChild(newNode);
+ return newNode;
+ }
+ }
+
+ public static native Element getTableCell(Element table, int row, int col) /*-{
+ return table.rows[row].cells[col] ;
+ }-*/;
+
+ public static void dump(Node node, String label)
+ {
+ StringBuffer buffer = new StringBuffer() ;
+ dump(node, "", buffer, false) ;
+ Debug.log("Dumping " + label + ":\n\n" + buffer.toString()) ;
+ }
+
+ private static void dump(Node node,
+ String indent,
+ StringBuffer out,
+ boolean doSiblings)
+ {
+ if (node == null)
+ return ;
+
+ out.append(indent)
+ .append(node.getNodeName()) ;
+ if (node.getNodeType() != 1)
+ {
+ out.append(": \"")
+ .append(node.getNodeValue())
+ .append("\"");
+ }
+ out.append("\n") ;
+
+ dump(node.getFirstChild(), indent + "\u00A0\u00A0", out, true) ;
+ if (doSiblings)
+ dump(node.getNextSibling(), indent, out, true) ;
+ }
+
+ public static native void ensureVisibleVert(
+ Element container,
+ Element child,
+ int padding) /*-{
+ if (!child)
+ return;
+
+ var height = child.offsetHeight ;
+ var top = 0;
+ while (child && child != container)
+ {
+ top += child.offsetTop ;
+ child = child.offsetParent ;
+ }
+
+ if (!child)
+ return;
+
+ // padding
+ top -= padding;
+ height += padding*2;
+
+ if (top < container.scrollTop)
+ {
+ container.scrollTop = top ;
+ }
+ else if (container.scrollTop + container.offsetHeight < top + height)
+ {
+ container.scrollTop = top + height - container.offsetHeight ;
+ }
+ }-*/;
+
+ // Forked from com.google.gwt.dom.client.Element.scrollIntoView()
+ public static native void scrollIntoViewVert(Element elem) /*-{
+ var top = elem.offsetTop;
+ var height = elem.offsetHeight;
+
+ if (elem.parentNode != elem.offsetParent) {
+ top -= elem.parentNode.offsetTop;
+ }
+
+ var cur = elem.parentNode;
+ while (cur && (cur.nodeType == 1)) {
+ if (top < cur.scrollTop) {
+ cur.scrollTop = top;
+ }
+ if (top + height > cur.scrollTop + cur.clientHeight) {
+ cur.scrollTop = (top + height) - cur.clientHeight;
+ }
+
+ var offsetTop = cur.offsetTop;
+ if (cur.parentNode != cur.offsetParent) {
+ offsetTop -= cur.parentNode.offsetTop;
+ }
+
+ top += offsetTop - cur.scrollTop;
+ cur = cur.parentNode;
+ }
+ }-*/;
+
+ public static Point getRelativePosition(Element container,
+ Element child)
+ {
+ int left = 0, top = 0;
+ while (child != null && child != container)
+ {
+ left += child.getOffsetLeft();
+ top += child.getOffsetTop();
+ child = child.getOffsetParent();
+ }
+
+ return new Point(left, top);
+ }
+
+ public static int ensureVisibleHoriz(Element container,
+ Element child,
+ int paddingLeft,
+ int paddingRight,
+ boolean calculateOnly)
+ {
+ final int scrollLeft = container.getScrollLeft();
+
+ if (child == null)
+ return scrollLeft;
+
+ int width = child.getOffsetWidth();
+ int left = getRelativePosition(container, child).x;
+ left -= paddingLeft;
+ width += paddingLeft + paddingRight;
+
+ int result;
+ if (left < scrollLeft)
+ result = left;
+ else if (scrollLeft + container.getOffsetWidth() < left + width)
+ result = left + width - container.getOffsetWidth();
+ else
+ result = scrollLeft;
+
+ if (!calculateOnly && result != scrollLeft)
+ container.setScrollLeft(result);
+
+ return result;
+ }
+
+ public static native boolean isVisibleVert(Element container,
+ Element child) /*-{
+ if (!container || !child)
+ return false;
+
+ var height = child.offsetHeight;
+ var top = 0;
+ while (child && child != container)
+ {
+ top += child.offsetTop ;
+ child = child.offsetParent ;
+ }
+ if (!child)
+ throw new Error("Child was not in container or " +
+ "container wasn't offset parent");
+
+ var bottom = top + height;
+ var scrollTop = container.scrollTop;
+ var scrollBottom = container.scrollTop + container.clientHeight;
+
+ return (top > scrollTop && top < scrollBottom)
+ || (bottom > scrollTop && bottom < scrollBottom);
+
+ }-*/;
+
+ public static String getHtml(Node node)
+ {
+ switch (node.getNodeType())
+ {
+ case Node.DOCUMENT_NODE:
+ return ((ElementEx)node).getOuterHtml() ;
+ case Node.ELEMENT_NODE:
+ return ((ElementEx)node).getOuterHtml() ;
+ case Node.TEXT_NODE:
+ return node.getNodeValue() ;
+ default:
+ assert false :
+ "Add case statement for node type " + node.getNodeType() ;
+ return node.getNodeValue() ;
+ }
+ }
+
+ public static boolean isDescendant(Node el, Node ancestor)
+ {
+ for (Node parent = el.getParentNode();
+ parent != null;
+ parent = parent.getParentNode())
+ {
+ if (parent.equals(ancestor))
+ return true ;
+ }
+ return false ;
+ }
+
+ /**
+ * Finds a node that matches the predicate.
+ *
+ * @param start The node from which to start.
+ * @param recursive If true, recurses into child nodes.
+ * @param siblings If true, looks at the next sibling from "start".
+ * @param filter The predicate that determines a match.
+ * @return The first matching node encountered in documented order, or null.
+ */
+ public static Node findNode(Node start,
+ boolean recursive,
+ boolean siblings,
+ NodePredicate filter)
+ {
+ if (start == null)
+ return null ;
+
+ if (filter.test(start))
+ return start ;
+
+ if (recursive)
+ {
+ Node result = findNode(start.getFirstChild(), true, true, filter) ;
+ if (result != null)
+ return result ;
+ }
+
+ if (siblings)
+ {
+ Node result = findNode(start.getNextSibling(), recursive, true,
+ filter) ;
+ if (result != null)
+ return result ;
+ }
+
+ return null ;
+ }
+
+ /**
+ * Converts plaintext to HTML, preserving whitespace semantics
+ * as much as possible.
+ */
+ public static String textToHtml(String text)
+ {
+ // Order of these replacement operations is important.
+ return
+ text.replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll("\\n", "<br />")
+ .replaceAll("\\t", " ")
+ .replaceAll(" ", " ")
+ .replaceAll(" (?! )", " ")
+ .replaceAll(" $", " ")
+ .replaceAll("^ ", " ");
+ }
+
+ public static String textToPreHtml(String text)
+ {
+ // Order of these replacement operations is important.
+ return
+ text.replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll("\\t", " ");
+ }
+
+ public static String htmlToText(String html)
+ {
+ Element el = DOM.createSpan();
+ el.setInnerHTML(html);
+ return el.getInnerText();
+ }
+
+ /**
+ * Similar to Element.getInnerText() but converts br tags to newlines.
+ */
+ public static String getInnerText(Element el)
+ {
+ return getInnerText(el, false);
+ }
+
+ public static String getInnerText(Element el, boolean pasteMode)
+ {
+ StringBuilder out = new StringBuilder();
+ getInnerText(el, out, pasteMode);
+ return out.toString();
+ }
+
+ private static void getInnerText(Node node,
+ StringBuilder out,
+ boolean pasteMode)
+ {
+ if (node == null)
+ return;
+
+ for (Node child = node.getFirstChild();
+ child != null;
+ child = child.getNextSibling())
+ {
+ switch (child.getNodeType())
+ {
+ case Node.TEXT_NODE:
+ out.append(child.getNodeValue());
+ break;
+ case Node.ELEMENT_NODE:
+ Element childEl = (Element) child;
+ String tag = childEl.getTagName().toLowerCase();
+ // Sometimes when pasting text (e.g. from IntelliJ) into console
+ // the line breaks turn into <br _moz_dirty="true"/> or whatever.
+ // We want to keep them in those cases. But in other cases
+ // the _moz_dirty breaks are just spurious.
+ if (tag.equals("br") && (pasteMode || !childEl.hasAttribute("_moz_dirty")))
+ out.append("\n");
+ else if (tag.equals("script") || tag.equals("style"))
+ continue;
+ getInnerText(child, out, pasteMode);
+ break;
+ }
+ }
+ }
+
+ public static void setInnerText(Element el, String plainText)
+ {
+ el.setInnerText("");
+ if (plainText == null || plainText.length() == 0)
+ return;
+
+ Document doc = el.getOwnerDocument();
+
+ Pattern pattern = Pattern.create("\\n");
+ int tail = 0;
+ Match match = pattern.match(plainText, 0);
+ while (match != null)
+ {
+ if (tail != match.getIndex())
+ {
+ String line = plainText.substring(tail, match.getIndex());
+ el.appendChild(doc.createTextNode(line));
+ }
+ el.appendChild(doc.createBRElement());
+ tail = match.getIndex() + 1;
+ match = match.nextMatch();
+ }
+
+ if (tail < plainText.length())
+ el.appendChild(doc.createTextNode(plainText.substring(tail)));
+ }
+
+ public static boolean isSelectionAsynchronous()
+ {
+ return impl.isSelectionAsynchronous();
+ }
+
+ public static boolean isCommandClick(NativeEvent nativeEvt)
+ {
+ boolean commandKey;
+
+ // NOTE: on mac desktop we still check for CtrlKey because under Qt 4.8
+ // on the mac the reporting of Ctrl and Meta keys is inverted)
+ if (BrowseCap.isMacintosh() && !Desktop.isDesktop())
+ {
+ commandKey = nativeEvt.getMetaKey();
+ }
+ else
+ {
+ commandKey = nativeEvt.getCtrlKey();
+ }
+
+ return (nativeEvt.getButton() == NativeEvent.BUTTON_LEFT) && commandKey;
+ }
+
+ public static final native void setStyle(Element element,
+ String name,
+ String value) /*-{
+ element.style[name] = value;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/ElementEx.java b/src/gwt/src/org/rstudio/core/client/dom/ElementEx.java
new file mode 100644
index 0000000..64d24d8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/ElementEx.java
@@ -0,0 +1,72 @@
+/*
+ * ElementEx.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.user.client.Element;
+
+public class ElementEx extends Element
+{
+ protected ElementEx()
+ {
+ }
+
+ public final native boolean getContentEditable() /*-{
+ return !!this.contentEditable ;
+ }-*/;
+
+ public final native void normalize() /*-{
+ this.normalize() ;
+ }-*/;
+
+ public final native String getOuterHtml() /*-{
+ if (typeof(this.outerHTML) != 'undefined')
+ return this.outerHTML;
+
+ // Firefox does not support the outerHTML property
+ var copy = this.cloneNode(true);
+ var tmpContainer = document.createElement(this.parentNode.tagName);
+ tmpContainer.appendChild(copy);
+ return tmpContainer.innerHTML;
+ }-*/;
+
+ public final native String getAttribute(String attribName, int mode) /*-{
+ var result = this.getAttribute(attribName, mode);
+ return (result == null) ? '' : result + '';
+ }-*/;
+
+ public final int getClientLeft()
+ {
+ int left = getAbsoluteLeft();
+ ElementEx iFrame = getOwningIFrame();
+ if (iFrame != null)
+ left += iFrame.getClientLeft();
+ return left;
+ }
+
+ public final int getClientTop()
+ {
+ int top = getAbsoluteTop();
+ ElementEx iFrame = getOwningIFrame();
+ if (iFrame != null)
+ top += iFrame.getClientTop();
+ return top;
+ }
+
+ private final native ElementEx getOwningIFrame() /*-{
+ var doc = this.ownerDocument;
+ var win = doc.parentWindow || doc.defaultView;
+ return win.frameElement;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/IFrameElementEx.java b/src/gwt/src/org/rstudio/core/client/dom/IFrameElementEx.java
new file mode 100644
index 0000000..926c040
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/IFrameElementEx.java
@@ -0,0 +1,28 @@
+/*
+ * IFrameElementEx.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.dom.client.IFrameElement;
+
+public class IFrameElementEx extends IFrameElement
+{
+ protected IFrameElementEx()
+ {
+ }
+
+ public final native WindowEx getContentWindow() /*-{
+ return this.contentWindow ;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/NativeScreen.java b/src/gwt/src/org/rstudio/core/client/dom/NativeScreen.java
new file mode 100644
index 0000000..47e6de2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/NativeScreen.java
@@ -0,0 +1,35 @@
+/*
+ * NativeScreen.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class NativeScreen extends JavaScriptObject
+{
+ protected NativeScreen() {}
+
+ public static native NativeScreen get() /*-{
+ return $wnd.screen;
+ }-*/;
+
+ public final native int getAvailHeight() /*-{
+ return this.availHeight;
+ }-*/;
+
+ public final native int getAvailWidth() /*-{
+ return this.availWidth;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/NativeWindow.java b/src/gwt/src/org/rstudio/core/client/dom/NativeWindow.java
new file mode 100644
index 0000000..86cbd32
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/NativeWindow.java
@@ -0,0 +1,67 @@
+/*
+ * NativeWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+
+public class NativeWindow extends JavaScriptObject
+{
+ protected NativeWindow() {}
+
+ public static final native NativeWindow get() /*-{
+ return $wnd ;
+ }-*/;
+
+ public static final native NativeWindow get(Document doc) /*-{
+ return doc.defaultView || doc.parentWindow;
+ }-*/;
+
+ public final native Document getDocument() /*-{
+ return this.document ;
+ }-*/;
+
+ public final native int getPageXOffset() /*-{
+ if (this.pageXOffset)
+ return this.pageXOffset ;
+ if (this.scrollX)
+ return this.scrollX;
+ if (this.document.body && this.document.body.scrollLeft)
+ return this.document.body.scrollLeft;
+ if (this.document.documentElement && this.document.documentElement.scrollLeft)
+ return this.document.documentElement.scrollLeft;
+ return 0;
+ }-*/;
+
+ public final native int getPageYOffset() /*-{
+ if (this.pageYOffset)
+ return this.pageYOffset ;
+ if (this.scrollY)
+ return this.scrollY;
+ if (this.document.body && this.document.body.scrollTop)
+ return this.document.body.scrollTop;
+ if (this.document.documentElement && this.document.documentElement.scrollTop)
+ return this.document.documentElement.scrollTop;
+ return 0;
+ }-*/;
+
+ public final native void focus() /*-{
+ this.focus() ;
+ }-*/;
+
+ public final native void print() /*-{
+ this.print();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/TextEx.java b/src/gwt/src/org/rstudio/core/client/dom/TextEx.java
new file mode 100644
index 0000000..e24c79c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/TextEx.java
@@ -0,0 +1,51 @@
+/*
+ * TextEx.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Text;
+
+import java.util.ArrayList;
+
+public class TextEx extends Text
+{
+ protected TextEx()
+ {
+ }
+
+ public static ArrayList<TextEx> allTextNodes(Node node)
+ {
+ ArrayList<TextEx> results = new ArrayList<TextEx>() ;
+ allTextNodesHelper(results, node) ;
+ return results ;
+ }
+
+ private static void allTextNodesHelper(ArrayList<TextEx> list, Node node)
+ {
+ if (node.getNodeType() == Node.ELEMENT_NODE)
+ {
+ for (Node child = node.getFirstChild();
+ child != null;
+ child = child.getNextSibling())
+ {
+ allTextNodesHelper(list, child);
+ }
+ }
+ else if (node.getNodeType() == Node.TEXT_NODE)
+ {
+ list.add((TextEx) node) ;
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/WindowEx.java b/src/gwt/src/org/rstudio/core/client/dom/WindowEx.java
new file mode 100644
index 0000000..3a162d1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/WindowEx.java
@@ -0,0 +1,175 @@
+/*
+ * WindowEx.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+import org.rstudio.core.client.Point;
+
+public class WindowEx extends JavaScriptObject
+{
+ public static native WindowEx get() /*-{
+ return $wnd;
+ }-*/;
+
+ protected WindowEx()
+ {
+ }
+
+ public final native void focus() /*-{
+ this.focus();
+ }-*/;
+
+ public final native void print() /*-{
+ this.print() ;
+ }-*/;
+
+ public final native void back() /*-{
+ this.history.back() ;
+ }-*/;
+
+ public final native void forward() /*-{
+ this.history.forward() ;
+ }-*/;
+
+ public final native void removeSelection() /*-{
+ selection = this.getSelection();
+ if (selection != null) {
+ selection.removeAllRanges();
+ }
+ }-*/;
+
+ public final native boolean find(String term,
+ boolean matchCase,
+ boolean searchUpward,
+ boolean wrapAround,
+ boolean wholeWord) /*-{
+ return this.find(term, matchCase, searchUpward, wrapAround, wholeWord);
+ }-*/;
+
+ public final native String getLocationHref() /*-{
+ return this.location.href ;
+ }-*/;
+
+ public final native boolean isSecure() /*-{
+ return 'https:' == this.location.protocol;
+ }-*/;
+
+ public final native void reload() /*-{
+ this.location.reload(true);
+ }-*/;
+
+ public final native void setLocationHref(String helpURL) /*-{
+ this.location.href = helpURL ;
+ }-*/;
+
+ public final native void replaceLocationHref(String helpURL) /*-{
+ this.location.replace(helpURL) ;
+ }-*/;
+
+ public final Point getScrollPosition()
+ {
+ JsArrayInteger pos = getScrollPositionInternal();
+ return new Point(pos.get(0), pos.get(1));
+ }
+
+ public final void setScrollPosition(Point pos)
+ {
+ setScrollPositionInternal(pos.x, pos.y);
+ }
+
+ private final native JsArrayInteger getScrollPositionInternal() /*-{
+ return [this.scrollX, this.scrollY];
+ }-*/;
+
+ private final native void setScrollPositionInternal(int x, int y) /*-{
+ this.scrollTo(x, y);
+ }-*/;
+
+ public final native void close() /*-{
+ this.close();
+ }-*/;
+
+ public final native boolean isClosed() /*-{
+ return this.closed;
+ }-*/;
+
+ public final native void resizeTo(int width, int height) /*-{
+ this.resizeTo(width, height);
+ }-*/;
+
+ public final native Document getDocument() /*-{
+ return this.document;
+ }-*/;
+
+ public static HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return handlers_.addHandler(FocusEvent.getType(), handler);
+ }
+
+ public static HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return handlers_.addHandler(BlurEvent.getType(), handler);
+ }
+
+ private static void fireFocusHandlers()
+ {
+ NativeEvent nativeEvent = Document.get().createFocusEvent();
+ FocusEvent.fireNativeEvent(nativeEvent, new HasHandlers()
+ {
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+ });
+ }
+
+ private static void fireBlurHandlers()
+ {
+ NativeEvent nativeEvent = Document.get().createBlurEvent();
+ BlurEvent.fireNativeEvent(nativeEvent, new HasHandlers()
+ {
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+ });
+ }
+
+ static {
+ registerNativeListeners();
+ }
+
+ private static native void registerNativeListeners() /*-{
+ $wnd.onfocus = function() {
+ @org.rstudio.core.client.dom.WindowEx::fireFocusHandlers()();
+ };
+ $wnd.onblur = function() {
+ @org.rstudio.core.client.dom.WindowEx::fireBlurHandlers()();
+ };
+ }-*/;
+
+ private static final HandlerManager handlers_ = new HandlerManager(null);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsIE8Impl.java b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsIE8Impl.java
new file mode 100644
index 0000000..b84c4b0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsIE8Impl.java
@@ -0,0 +1,172 @@
+/*
+ * DomUtilsIE8Impl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.dom.ElementEx;
+
+public class DomUtilsIE8Impl implements DomUtilsImpl
+{
+ public void focus(Element element, boolean alwaysDriveSelection)
+ {
+ ElementEx el = (ElementEx) element;
+ el.focus();
+ }
+
+ public native final void collapseSelection(boolean toStart) /*-{
+ var rng = document.selection.createRange();
+ rng.collapse(toStart);
+ }-*/;
+
+ public native final boolean isSelectionCollapsed() /*-{
+ var rng = document.selection.createRange();
+ var testRng = rng.duplicate();
+ testRng.collapse(true);
+ return rng.isEqual(testRng);
+ }-*/;
+
+ public native final boolean isSelectionInElement(Element element) /*-{
+ var rng = element.ownerDocument.selection.createRange();
+ var el = rng.parentElement();
+
+ while (el != null)
+ {
+ if (el === element)
+ return true ;
+
+ el = el.parentNode ;
+ }
+ return false ;
+ }-*/;
+
+ private native String getSelectionType() /*-{
+ return document.selection.type;
+ }-*/;
+
+ public boolean selectionExists()
+ {
+ return !("None".equals(getSelectionType())) && !isSelectionCollapsed();
+ }
+
+ public Rectangle getCursorBounds(Document doc)
+ {
+ JsArrayInteger result = getSelectionBoundsInternal(doc);
+ return new Rectangle(
+ result.get(0),
+ result.get(1),
+ result.get(2),
+ result.get(3)
+ );
+ }
+
+ private native JsArrayInteger getSelectionBoundsInternal(Document doc) /*-{
+ var rng = doc.selection.createRange();
+ return [
+ rng.boundingLeft,
+ rng.boundingTop,
+ rng.boundingWidth,
+ rng.boundingHeight];
+ }-*/;
+
+ private native final Element getSelectionParentElement(Document doc) /*-{
+ return doc.selection.createRange().parentElement();
+ }-*/;
+
+ public native final String replaceSelection(Document doc,
+ String text) /*-{
+ var rng = doc.selection.createRange();
+ var orig = rng.text;
+ var html = @org.rstudio.core.client.dom.DomUtils::textToHtml(Ljava/lang/String;)(text);
+ rng.pasteHTML(html);
+ rng.select();
+ return orig;
+ }-*/;
+
+ public native final String getSelectionText(Document document) /*-{
+ var rng = doc.selection.createRange();
+ return rng.text;
+ }-*/;
+
+ public int[] getSelectionOffsets(Element container)
+ {
+ if (!isSelectionInElement(container))
+ return null;
+
+ JsArrayInteger results = getSelectionOffsetsJs(container);
+ if (results == null)
+ return null;
+ else
+ return new int[] {results.get(0), results.get(1)};
+ }
+
+ private native final JsArrayInteger getSelectionOffsetsJs(
+ Element container) /*-{
+ var rng = container.ownerDocument.body.createTextRange();
+ rng.moveToElementText(container);
+ rng.collapse(true);
+ var sel = container.ownerDocument.selection.createRange();
+
+ var startOffset = 0;
+ var endOffset = 0;
+ // Ideally this would be a binary search instead of linear probing
+ while (rng.compareEndPoints("EndToEnd", sel) < 0) {
+ rng.moveEnd("character");
+ startOffset++;
+ }
+ while (rng.compareEndPoints("StartToStart", sel) < 0) {
+ rng.moveStart("character");
+ endOffset++;
+ }
+
+ if (!rng.isEqual(sel))
+ return null;
+ else
+ return [startOffset, endOffset];
+ }-*/;
+
+ public native final void setSelectionOffsets(Element container,
+ int start,
+ int end) /*-{
+ var rng = container.ownerDocument.body.createTextRange();
+ rng.moveToElementText(container);
+ for (var i = 0; i < end; i++)
+ rng.moveEnd('character');
+ for (var j = 0; j < start; j++)
+ rng.moveStart('character');
+
+ var containerRng = container.ownerDocument.body.createTextRange();
+ containerRng.moveToElementText(container);
+ if (rng.compareEndPoints('EndToEnd', containerRng) > 0) {
+ rng.setEndPoint('EndToEnd', containerRng);
+ }
+
+ rng.select();
+ container.focus();
+ }-*/;
+
+ public boolean isSelectionAsynchronous()
+ {
+ return true;
+ }
+
+ @Override
+ public void selectElement(Element el)
+ {
+ throw new UnsupportedOperationException("DomUtilsIE8Impl.selectElement not implemented");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsImpl.java b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsImpl.java
new file mode 100644
index 0000000..1e2b5bd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsImpl.java
@@ -0,0 +1,46 @@
+/*
+ * DomUtilsImpl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import org.rstudio.core.client.Rectangle;
+
+public interface DomUtilsImpl
+{
+ void focus(Element element, boolean alwaysDriveSelection);
+
+ void collapseSelection(boolean toStart);
+
+ boolean isSelectionCollapsed();
+
+ boolean isSelectionInElement(Element element);
+
+ boolean selectionExists();
+
+ Rectangle getCursorBounds(Document doc);
+
+ String replaceSelection(Document document, String text);
+
+ String getSelectionText(Document document);
+
+ int[] getSelectionOffsets(Element container);
+
+ void setSelectionOffsets(Element container, int start, int end);
+
+ boolean isSelectionAsynchronous();
+
+ void selectElement(Element el);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsStandardImpl.java b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsStandardImpl.java
new file mode 100644
index 0000000..eb68ed2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/DomUtilsStandardImpl.java
@@ -0,0 +1,198 @@
+/*
+ * DomUtilsStandardImpl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Text;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.ElementEx;
+import org.rstudio.core.client.dom.NativeWindow;
+
+public class DomUtilsStandardImpl implements DomUtilsImpl
+{
+ public void focus(Element element, boolean alwaysDriveSelection)
+ {
+ ElementEx el = (ElementEx)element ;
+
+ el.focus() ;
+ if (alwaysDriveSelection
+ || (el.getContentEditable() &&
+ (el.getInnerText() == null || el.getInnerText() == "")))
+ {
+ Document doc = el.getOwnerDocument();
+ Range range = Range.create(doc) ;
+ range.selectNodeContents(el) ;
+ Selection sel = Selection.get(NativeWindow.get(doc)) ;
+ sel.setRange(range);
+ }
+
+ NativeWindow.get().focus();
+ }
+
+ public void collapseSelection(boolean toStart)
+ {
+ Selection sel = Selection.get() ;
+ if (sel == null || sel.getRangeCount() <= 0)
+ return ;
+ Range range = sel.getRangeAt(0) ;
+ range.collapse(toStart) ;
+ sel.removeAllRanges() ;
+ sel.addRange(range) ;
+ }
+
+ public boolean isSelectionCollapsed()
+ {
+ Selection sel = Selection.get() ;
+ return sel != null
+ && sel.getRangeCount() == 1
+ && sel.getRangeAt(0).isCollapsed() ;
+ }
+
+ public boolean isSelectionInElement(Element element)
+ {
+ Range rng = getSelectionRange(
+ NativeWindow.get(element.getOwnerDocument()), false) ;
+ if (rng == null)
+ return false ;
+ return DomUtils.contains(element, rng.getCommonAncestorContainer()) ;
+ }
+
+ public boolean selectionExists()
+ {
+ Selection sel = Selection.get() ;
+ if (sel == null || sel.getRangeCount() == 0)
+ return false ;
+ if (sel.getRangeCount() > 1)
+ return true ;
+ return !sel.getRangeAt(0).isCollapsed() ;
+ }
+
+ public Range getSelectionRange(NativeWindow window, boolean clone)
+ {
+ Selection sel = Selection.get(window) ;
+ if (sel.getRangeCount() != 1)
+ return null ;
+
+ Range result = sel.getRangeAt(0) ;
+ if (clone)
+ result = result.cloneRange() ;
+ return result ;
+ }
+
+ public Rectangle getCursorBounds(Document doc)
+ {
+
+ Selection sel = Selection.get(NativeWindow.get(doc));
+ Range selRng = sel.getRangeAt(0);
+ if (selRng == null)
+ return null;
+ sel.removeAllRanges();
+ SpanElement span = doc.createSpanElement() ;
+
+ Range rng = selRng.cloneRange();
+ rng.collapse(true);
+ rng.insertNode(span) ;
+
+ int x = span.getAbsoluteLeft() ;
+ int y = span.getAbsoluteTop() ;
+ int w = 0;
+ int h = span.getOffsetHeight() ;
+ Rectangle result = new Rectangle(x, y, w, h) ;
+
+ ElementEx parent = (ElementEx)span.getParentElement() ;
+ parent.removeChild(span) ;
+ parent.normalize() ;
+ sel.setRange(selRng);
+ return result;
+ }
+
+ public String replaceSelection(Document document, String text)
+ {
+ if (!isSelectionInElement(document.getBody()))
+ throw new IllegalStateException("Selection is not active");
+
+ Range rng = getSelectionRange(NativeWindow.get(document), true) ;
+ String orig = rng.toStringJs();
+ rng.deleteContents() ;
+
+ Text textNode = document.createTextNode(text) ;
+ rng.insertNode(textNode) ;
+ rng.selectNode(textNode);
+
+ Selection.get(NativeWindow.get(document)).setRange(rng);
+
+ return orig;
+ }
+
+ public String getSelectionText(Document document)
+ {
+ Range range = getSelectionRange(NativeWindow.get(document), false);
+ if (range == null || range.isCollapsed())
+ return null;
+ else
+ return range.toStringJs();
+ }
+
+ public int[] getSelectionOffsets(Element container)
+ {
+ Range rng = getSelectionRange(
+ NativeWindow.get(container.getOwnerDocument()),
+ false) ;
+
+ if (rng == null)
+ return null;
+
+ int start = NodeRelativePosition.toOffset(container,
+ new NodeRelativePosition(rng.getStartContainer(),
+ rng.getStartOffset())) ;
+ int end = NodeRelativePosition.toOffset(container,
+ new NodeRelativePosition(rng.getEndContainer(),
+ rng.getEndOffset())) ;
+ if (start >= 0 && end >= 0)
+ return new int[] {start, end};
+ else
+ return null ;
+ }
+
+ public void setSelectionOffsets(Element container, int start, int end)
+ {
+ NodeRelativePosition startp = NodeRelativePosition.toPosition(container, start);
+ NodeRelativePosition endp = NodeRelativePosition.toPosition(container, end);
+
+ Document doc = container.getOwnerDocument();
+ Range rng = Range.create(doc) ;
+ rng.setStart(startp.node, startp.offset) ;
+ rng.setEnd(endp.node, endp.offset) ;
+ Selection.get(NativeWindow.get(doc)).setRange(rng) ;
+
+ }
+
+ public boolean isSelectionAsynchronous()
+ {
+ return false;
+ }
+
+ @Override
+ public void selectElement(Element el)
+ {
+ Document doc = el.getOwnerDocument();
+ Range rng = Range.create(doc);
+ rng.selectNode(el);
+ Selection.get(NativeWindow.get(doc)).setRange(rng);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/NodeRelativePosition.java b/src/gwt/src/org/rstudio/core/client/dom/impl/NodeRelativePosition.java
new file mode 100644
index 0000000..72efddf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/NodeRelativePosition.java
@@ -0,0 +1,155 @@
+/*
+ * NodeRelativePosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Text;
+import org.rstudio.core.client.Debug;
+
+public class NodeRelativePosition
+{
+ /**
+ * Converts a node-relative position to an absolute character offset,
+ * or -1 if the node-based position is not present in the editor.
+ */
+ public static int toOffset(Element container, NodeRelativePosition position)
+ {
+ int[] counter = new int[] {0};
+ if (toOffsetHelper(container, position, counter))
+ return counter[0];
+ return -1;
+ }
+
+ private static boolean toOffsetHelper(Node here,
+ NodeRelativePosition target,
+ int[] counter)
+ {
+ if (target.node.equals(here))
+ {
+ switch (here.getNodeType())
+ {
+ case Node.TEXT_NODE:
+ counter[0] += target.offset;
+ return true;
+ case Node.ELEMENT_NODE:
+ NodeList<Node> children = here.getChildNodes();
+ for (int i = 0; i < target.offset; i++)
+ toOffsetHelper(children.getItem(i), target, counter);
+ return true;
+ default:
+ Debug.log("Unexpected node type for selection offset: "
+ + here.getNodeType());
+ return false;
+ }
+ }
+ else
+ {
+ if (here.getNodeType() == Node.TEXT_NODE)
+ {
+ counter[0] += ((Text)here).getLength();
+ return false;
+ }
+ else if (here.getNodeType() == Node.ELEMENT_NODE)
+ {
+ Element el = (Element) here;
+ String tag = el.getTagName().toLowerCase();
+ if (tag.equals("br"))
+ {
+ counter[0] += 1;
+ return false;
+ }
+ if (tag.equals("script") || tag.equals("style"))
+ return false;
+
+ // Otherwise continue to iteration code below
+ }
+
+ NodeList<Node> children = here.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++)
+ if (toOffsetHelper(children.getItem(i), target, counter))
+ return true;
+
+ return false;
+ }
+ }
+
+ /**
+ * Convert an absolute character-based offset to a DOM node and offset.
+ * Returns null if the offset points off the end of the DOM.
+ */
+ public static NodeRelativePosition toPosition(Element container, int offset)
+ {
+ if (offset < 0)
+ throw new IllegalArgumentException("Offset must not be negative") ;
+
+ int[] counter = new int[] {offset};
+ NodeRelativePosition result = toPositionHelper(container, counter);
+ if (result == null && counter[0] == 0)
+ return new NodeRelativePosition(container, container.getChildCount());
+ return result;
+ }
+
+ private static NodeRelativePosition toPositionHelper(Node here,
+ int[] counter)
+ {
+ switch (here.getNodeType())
+ {
+ case Node.TEXT_NODE:
+ Text text = (Text) here;
+ if (counter[0] <= text.getLength())
+ return new NodeRelativePosition(here, counter[0]);
+ counter[0] -= text.getLength();
+ return null;
+ case Node.ELEMENT_NODE:
+ Element el = (Element) here;
+ String tagName = el.getTagName().toLowerCase();
+ if (tagName.equals("br"))
+ {
+ if (counter[0] <= 0)
+ return new NodeRelativePosition(here, 0);
+ counter[0] -= 1;
+ return null;
+ }
+ else if (tagName.equals("script") || tagName.equals("style"))
+ return null;
+ break;
+ }
+
+ NodeList<Node> children = here.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++)
+ {
+ NodeRelativePosition result = toPositionHelper(
+ children.getItem(i), counter);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ public final Node node ;
+
+ public final int offset ;
+
+ public NodeRelativePosition(Node node, int offset)
+ {
+ super() ;
+ this.node = node ;
+ this.offset = offset ;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/Range.java b/src/gwt/src/org/rstudio/core/client/dom/impl/Range.java
new file mode 100644
index 0000000..37b9815
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/Range.java
@@ -0,0 +1,127 @@
+/*
+ * Range.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Node;
+
+class Range extends JavaScriptObject
+{
+ protected Range()
+ {
+ }
+
+ public static native Range create(Document doc) /*-{
+ return doc.createRange() ;
+ }-*/;
+
+ public final native boolean isCollapsed() /*-{
+ return this.collapsed ;
+ }-*/;
+
+ public final native Node getCommonAncestorContainer() /*-{
+ return this.commonAncestorContainer ;
+ }-*/;
+
+ public final native Node getEndContainer() /*-{
+ return this.endContainer ;
+ }-*/;
+
+ public final native int getEndOffset() /*-{
+ return this.endOffset ;
+ }-*/;
+
+ public final native Node getStartContainer() /*-{
+ return this.startContainer ;
+ }-*/;
+
+ public final native int getStartOffset() /*-{
+ return this.startOffset ;
+ }-*/;
+
+ public final native JavaScriptObject cloneContents() /*-{
+ return this.cloneContents() ;
+ }-*/;
+
+ public final native Range cloneRange() /*-{
+ return this.cloneRange() ;
+ }-*/;
+
+ public final native void collapse(boolean toStart) /*-{
+ return this.collapse(toStart) ;
+ }-*/;
+
+ public final native short compareBoundaryPoints(short how,
+ Range sourceRange) /*-{
+ return this.compareBoundaryPoints(how, sourceRange) ;
+ }-*/;
+
+ public final native void deleteContents() /*-{
+ return this.deleteContents() ;
+ }-*/;
+
+ public final native void detach() /*-{
+ return this.detach() ;
+ }-*/;
+
+ public final native JavaScriptObject extractContents() /*-{
+ return this.extractContents() ;
+ }-*/;
+
+ public final native void insertNode(Node newNode) /*-{
+ return this.insertNode(newNode) ;
+ }-*/;
+
+ public final native void selectNode(Node refNode) /*-{
+ return this.selectNode(refNode) ;
+ }-*/;
+
+ public final native void selectNodeContents(Node refNode) /*-{
+ return this.selectNodeContents(refNode) ;
+ }-*/;
+
+ public final native void setEnd(Node refNode, int offset) /*-{
+ return this.setEnd(refNode, offset) ;
+ }-*/;
+
+ public final native void setEndAfter(Node refNode) /*-{
+ return this.setEndAfter(refNode) ;
+ }-*/;
+
+ public final native void setEndBefore(Node refNode) /*-{
+ return this.setEndBefore(refNode) ;
+ }-*/;
+
+ public final native void setStart(Node refNode, int offset) /*-{
+ return this.setStart(refNode, offset) ;
+ }-*/;
+
+ public final native void setStartAfter(Node refNode) /*-{
+ return this.setStartAfter(refNode) ;
+ }-*/;
+
+ public final native void setStartBefore(Node refNode) /*-{
+ return this.setStartBefore(refNode) ;
+ }-*/;
+
+ public final native void surroundContents(Node newParent) /*-{
+ return this.surroundContents(newParent) ;
+ }-*/;
+
+ public final native String toStringJs() /*-{
+ return this.toString();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/dom/impl/Selection.java b/src/gwt/src/org/rstudio/core/client/dom/impl/Selection.java
new file mode 100644
index 0000000..a1eaa93
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/dom/impl/Selection.java
@@ -0,0 +1,73 @@
+/*
+ * Selection.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.dom.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Node;
+import org.rstudio.core.client.dom.NativeWindow;
+
+class Selection extends JavaScriptObject
+{
+ protected Selection()
+ {
+ }
+
+ public static native Selection get() /*-{
+ return $wnd.getSelection() ;
+ }-*/;
+
+ public static native Selection get(NativeWindow window) /*-{
+ return window.getSelection() ;
+ }-*/;
+
+ public final native int getRangeCount() /*-{
+ return this.rangeCount ;
+ }-*/;
+
+ public final native Range getRangeAt(int index) /*-{
+ return this.getRangeAt(index) ;
+ }-*/;
+
+ public final native void removeAllRanges() /*-{
+ return this.removeAllRanges() ;
+ }-*/;
+
+ public final native void addRange(Range range) /*-{
+ return this.addRange(range) ;
+ }-*/;
+
+ public final native Node getAnchorNode() /*-{
+ return this.anchorNode ;
+ }-*/;
+
+ public final native int getAnchorOffset() /*-{
+ return this.anchorOffset ;
+ }-*/;
+
+ public final native Node getFocusNode() /*-{
+ return this.focusNode ;
+ }-*/;
+
+ public final native int getFocusOffset() /*-{
+ return this.focusOffset ;
+ }-*/;
+
+ public final void setRange(Range selection)
+ {
+ removeAllRanges() ;
+ if (selection != null)
+ addRange(selection) ;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedEvent.java b/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedEvent.java
new file mode 100644
index 0000000..e9003ea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedEvent.java
@@ -0,0 +1,34 @@
+/*
+ * BarrierReleasedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BarrierReleasedEvent extends GwtEvent<BarrierReleasedHandler>
+{
+ @Override
+ public Type<BarrierReleasedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(BarrierReleasedHandler handler)
+ {
+ handler.onBarrierReleased(this);
+ }
+
+ public static final Type<BarrierReleasedHandler> TYPE = new Type<BarrierReleasedHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedHandler.java b/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedHandler.java
new file mode 100644
index 0000000..b158d28
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/BarrierReleasedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * BarrierReleasedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface BarrierReleasedHandler extends EventHandler
+{
+ void onBarrierReleased(BarrierReleasedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/BeforeShowEvent.java b/src/gwt/src/org/rstudio/core/client/events/BeforeShowEvent.java
new file mode 100644
index 0000000..7645449
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/BeforeShowEvent.java
@@ -0,0 +1,38 @@
+/*
+ * BeforeShowEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BeforeShowEvent extends GwtEvent<BeforeShowHandler>
+{
+ public BeforeShowEvent()
+ {
+ }
+
+ @Override
+ public Type<BeforeShowHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(BeforeShowHandler handler)
+ {
+ handler.onBeforeShow(this);
+ }
+
+ public static final Type<BeforeShowHandler> TYPE = new Type<BeforeShowHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/BeforeShowHandler.java b/src/gwt/src/org/rstudio/core/client/events/BeforeShowHandler.java
new file mode 100644
index 0000000..3397ef5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/BeforeShowHandler.java
@@ -0,0 +1,22 @@
+/*
+ * BeforeShowHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface BeforeShowHandler extends EventHandler
+{
+ void onBeforeShow(BeforeShowEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureHeightEvent.java b/src/gwt/src/org/rstudio/core/client/events/EnsureHeightEvent.java
new file mode 100644
index 0000000..1f021bb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureHeightEvent.java
@@ -0,0 +1,49 @@
+/*
+ * EnsureHeightEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnsureHeightEvent extends GwtEvent<EnsureHeightHandler>
+{
+ public static final Type<EnsureHeightHandler> TYPE
+ = new Type<EnsureHeightHandler>();
+
+ public static final int MAXIMIZED = -1;
+
+ public EnsureHeightEvent(int height)
+ {
+ height_ = height;
+ }
+
+ public int getHeight()
+ {
+ return height_;
+ }
+
+ @Override
+ public Type<EnsureHeightHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(EnsureHeightHandler handler)
+ {
+ handler.onEnsureHeight(this);
+ }
+
+ private final int height_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureHeightHandler.java b/src/gwt/src/org/rstudio/core/client/events/EnsureHeightHandler.java
new file mode 100644
index 0000000..9e30672
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureHeightHandler.java
@@ -0,0 +1,22 @@
+/*
+ * EnsureHeightHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface EnsureHeightHandler extends EventHandler
+{
+ void onEnsureHeight(EnsureHeightEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenEvent.java b/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenEvent.java
new file mode 100644
index 0000000..1437747
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenEvent.java
@@ -0,0 +1,38 @@
+/*
+ * EnsureHiddenEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnsureHiddenEvent extends GwtEvent<EnsureHiddenHandler>
+{
+ public EnsureHiddenEvent()
+ {
+ }
+
+ @Override
+ public Type<EnsureHiddenHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(EnsureHiddenHandler handler)
+ {
+ handler.onEnsureHidden(this);
+ }
+
+ public static final Type<EnsureHiddenHandler> TYPE = new Type<EnsureHiddenHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenHandler.java b/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenHandler.java
new file mode 100644
index 0000000..5059098
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureHiddenHandler.java
@@ -0,0 +1,22 @@
+/*
+ * EnsureHiddenHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface EnsureHiddenHandler extends EventHandler
+{
+ void onEnsureHidden(EnsureHiddenEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleEvent.java b/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleEvent.java
new file mode 100644
index 0000000..11d8085
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleEvent.java
@@ -0,0 +1,52 @@
+/*
+ * EnsureVisibleEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnsureVisibleEvent extends GwtEvent<EnsureVisibleHandler>
+{
+ public static final Type<EnsureVisibleHandler> TYPE
+ = new Type<EnsureVisibleHandler>();
+
+ public EnsureVisibleEvent()
+ {
+ this(true);
+ }
+
+ public EnsureVisibleEvent(boolean activate)
+ {
+ activate_ = activate;
+ }
+
+ public boolean getActivate()
+ {
+ return activate_;
+ }
+
+ @Override
+ public Type<EnsureVisibleHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(EnsureVisibleHandler handler)
+ {
+ handler.onEnsureVisible(this);
+ }
+
+ private final boolean activate_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleHandler.java b/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleHandler.java
new file mode 100644
index 0000000..2a41120
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/EnsureVisibleHandler.java
@@ -0,0 +1,22 @@
+/*
+ * EnsureVisibleHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface EnsureVisibleHandler extends EventHandler
+{
+ void onEnsureVisible(EnsureVisibleEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasContextMenuHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasContextMenuHandlers.java
new file mode 100644
index 0000000..b781f17
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasContextMenuHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasContextMenuHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasContextMenuHandlers
+{
+ HandlerRegistration addContextMenuHandler(ContextMenuHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasEnsureHeightHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasEnsureHeightHandlers.java
new file mode 100644
index 0000000..d49a8e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasEnsureHeightHandlers.java
@@ -0,0 +1,22 @@
+/*
+ * HasEnsureHeightHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasEnsureHeightHandlers
+{
+ HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasEnsureHiddenHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasEnsureHiddenHandlers.java
new file mode 100644
index 0000000..bfbfa33
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasEnsureHiddenHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasEnsureHiddenHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasEnsureHiddenHandlers
+{
+ HandlerRegistration addEnsureHiddenHandler(EnsureHiddenHandler handler);
+ void ensureHidden();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasEnsureVisibleHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasEnsureVisibleHandlers.java
new file mode 100644
index 0000000..ee6320c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasEnsureVisibleHandlers.java
@@ -0,0 +1,22 @@
+/*
+ * HasEnsureVisibleHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasEnsureVisibleHandlers
+{
+ HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasNativeKeyHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasNativeKeyHandlers.java
new file mode 100644
index 0000000..69938a1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasNativeKeyHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasNativeKeyHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasNativeKeyHandlers
+{
+ HandlerRegistration addNativeKeyDownHandler(NativeKeyDownHandler handler);
+ HandlerRegistration addNativeKeyPressHandler(NativeKeyPressHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasSelectionCommitHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasSelectionCommitHandlers.java
new file mode 100644
index 0000000..abd5e64
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasSelectionCommitHandlers.java
@@ -0,0 +1,24 @@
+/*
+ * HasSelectionCommitHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+
+public interface HasSelectionCommitHandlers<I> extends HasHandlers
+{
+ HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<I> handler) ;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasTabCloseHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasTabCloseHandlers.java
new file mode 100644
index 0000000..a2f2c2e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasTabCloseHandlers.java
@@ -0,0 +1,22 @@
+/*
+ * HasTabCloseHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasTabCloseHandlers
+{
+ HandlerRegistration addTabCloseHandler(TabCloseHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasTabClosedHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasTabClosedHandlers.java
new file mode 100644
index 0000000..b786b98
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasTabClosedHandlers.java
@@ -0,0 +1,22 @@
+/*
+ * HasTabClosedHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasTabClosedHandlers
+{
+ HandlerRegistration addTabClosedHandler(TabClosedHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasTabClosingHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasTabClosingHandlers.java
new file mode 100644
index 0000000..6dcf804
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasTabClosingHandlers.java
@@ -0,0 +1,22 @@
+/*
+ * HasTabClosingHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasTabClosingHandlers
+{
+ HandlerRegistration addTabClosingHandler(TabClosingHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/HasWindowStateChangeHandlers.java b/src/gwt/src/org/rstudio/core/client/events/HasWindowStateChangeHandlers.java
new file mode 100644
index 0000000..bc6fa65
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/HasWindowStateChangeHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasWindowStateChangeHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public interface HasWindowStateChangeHandlers
+{
+ HandlerRegistration addWindowStateChangeHandler(
+ WindowStateChangeHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownEvent.java b/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownEvent.java
new file mode 100644
index 0000000..67b02d0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownEvent.java
@@ -0,0 +1,72 @@
+/*
+ * NativeKeyDownEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HasHandlers;
+
+public class NativeKeyDownEvent extends GwtEvent<NativeKeyDownHandler>
+{
+ public static final GwtEvent.Type<NativeKeyDownHandler> TYPE =
+ new GwtEvent.Type<NativeKeyDownHandler>();
+
+ public NativeKeyDownEvent(NativeEvent event)
+ {
+ event_ = event;
+ }
+
+ public NativeEvent getEvent()
+ {
+ return event_;
+ }
+
+ public boolean isCanceled()
+ {
+ return handled_;
+ }
+
+ public void cancel()
+ {
+ handled_ = true;
+ }
+
+ public static boolean fire(NativeEvent event, HasHandlers target)
+ {
+ NativeKeyDownEvent evt = new NativeKeyDownEvent(event);
+ target.fireEvent(evt);
+ if (evt.isCanceled())
+ {
+ event.preventDefault();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Type<NativeKeyDownHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(NativeKeyDownHandler handler)
+ {
+ handler.onKeyDown(this);
+ }
+
+ private final NativeEvent event_;
+ private boolean handled_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownHandler.java b/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownHandler.java
new file mode 100644
index 0000000..43af5bd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/NativeKeyDownHandler.java
@@ -0,0 +1,22 @@
+/*
+ * NativeKeyDownHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface NativeKeyDownHandler extends EventHandler
+{
+ void onKeyDown(NativeKeyDownEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressEvent.java b/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressEvent.java
new file mode 100644
index 0000000..9a0c8c9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressEvent.java
@@ -0,0 +1,81 @@
+/*
+ * NativeKeyPressEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HasHandlers;
+
+public class NativeKeyPressEvent extends GwtEvent<NativeKeyPressHandler>
+{
+ public static final GwtEvent.Type<NativeKeyPressHandler> TYPE =
+ new GwtEvent.Type<NativeKeyPressHandler>();
+
+ protected NativeKeyPressEvent(NativeEvent event)
+ {
+ event_ = event;
+ }
+
+ public NativeEvent getEvent()
+ {
+ return event_;
+ }
+
+ public boolean isCanceled()
+ {
+ return handled_;
+ }
+
+ public void cancel()
+ {
+ handled_ = true;
+ }
+
+ public char getCharCode()
+ {
+ return getCharCode(event_);
+ }
+
+ public static boolean fire(NativeEvent event, HasHandlers target)
+ {
+ NativeKeyPressEvent evt = new NativeKeyPressEvent(event);
+ target.fireEvent(evt);
+ if (evt.isCanceled())
+ {
+ event.preventDefault();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Type<NativeKeyPressHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(NativeKeyPressHandler handler)
+ {
+ handler.onKeyPress(this);
+ }
+
+ private native char getCharCode(NativeEvent e)/*-{
+ return e.charCode || e.keyCode;
+ }-*/;
+
+ private final NativeEvent event_;
+ private boolean handled_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressHandler.java b/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressHandler.java
new file mode 100644
index 0000000..c92938a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/NativeKeyPressHandler.java
@@ -0,0 +1,22 @@
+/*
+ * NativeKeyPressHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface NativeKeyPressHandler extends EventHandler
+{
+ void onKeyPress(NativeKeyPressEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/SelectionCommitEvent.java b/src/gwt/src/org/rstudio/core/client/events/SelectionCommitEvent.java
new file mode 100644
index 0000000..2281ec5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/SelectionCommitEvent.java
@@ -0,0 +1,69 @@
+/*
+ * SelectionCommitEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SelectionCommitEvent<I> extends GwtEvent<SelectionCommitHandler<I>>
+{
+ private static Type<SelectionCommitHandler<?>> TYPE;
+
+ public static <I> void fire(HasSelectionCommitHandlers<I> source, I selectedItem) {
+ if (TYPE != null) {
+ SelectionCommitEvent<I> event = new SelectionCommitEvent<I>(selectedItem);
+ source.fireEvent(event);
+ }
+ }
+
+ public static Type<SelectionCommitHandler<?>> getType() {
+ if (TYPE == null) {
+ TYPE = new Type<SelectionCommitHandler<?>>();
+ }
+ return TYPE;
+ }
+
+ private final I selectedItem;
+
+ /**
+ * Creates a new selection event.
+ *
+ * @param selectedItem selected item
+ */
+ protected SelectionCommitEvent(I selectedItem) {
+ this.selectedItem = selectedItem;
+ }
+
+ // The instance knows its BeforeSelectionHandler is of type I, but the TYPE
+ // field itself does not, so we have to do an unsafe cast here.
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public final Type<SelectionCommitHandler<I>> getAssociatedType() {
+ return (Type) TYPE;
+ }
+
+ /**
+ * Gets the selected item.
+ *
+ * @return the selected item
+ */
+ public I getSelectedItem() {
+ return selectedItem;
+ }
+
+ @Override
+ protected void dispatch(SelectionCommitHandler<I> handler) {
+ handler.onSelectionCommit(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/SelectionCommitHandler.java b/src/gwt/src/org/rstudio/core/client/events/SelectionCommitHandler.java
new file mode 100644
index 0000000..249cb0f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/SelectionCommitHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SelectionCommitHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SelectionCommitHandler<I> extends EventHandler
+{
+ void onSelectionCommit(SelectionCommitEvent<I> event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabCloseEvent.java b/src/gwt/src/org/rstudio/core/client/events/TabCloseEvent.java
new file mode 100644
index 0000000..99a990b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabCloseEvent.java
@@ -0,0 +1,51 @@
+/*
+ * TabCloseEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Indicates that the specified tab is about to close. Happens after
+ * TabClosingEvent but before TabClosedEvent.
+ */
+public class TabCloseEvent extends GwtEvent<TabCloseHandler>
+{
+ public static final GwtEvent.Type<TabCloseHandler> TYPE =
+ new GwtEvent.Type<TabCloseHandler>();
+
+ public TabCloseEvent(int tabIndex)
+ {
+ tabIndex_ = tabIndex;
+ }
+
+ public int getTabIndex()
+ {
+ return tabIndex_;
+ }
+
+ @Override
+ protected void dispatch(TabCloseHandler handler)
+ {
+ handler.onTabClose(this);
+ }
+
+ @Override
+ public GwtEvent.Type<TabCloseHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final int tabIndex_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabCloseHandler.java b/src/gwt/src/org/rstudio/core/client/events/TabCloseHandler.java
new file mode 100644
index 0000000..762ca66
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabCloseHandler.java
@@ -0,0 +1,22 @@
+/*
+ * TabCloseHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface TabCloseHandler extends EventHandler
+{
+ void onTabClose(TabCloseEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabClosedEvent.java b/src/gwt/src/org/rstudio/core/client/events/TabClosedEvent.java
new file mode 100644
index 0000000..4a53d4d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabClosedEvent.java
@@ -0,0 +1,46 @@
+/*
+ * TabClosedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class TabClosedEvent extends GwtEvent<TabClosedHandler>
+{
+ public static final Type<TabClosedHandler> TYPE = new Type<TabClosedHandler>();
+
+ public TabClosedEvent(int tabIndex)
+ {
+ tabIndex_ = tabIndex;
+ }
+
+ public int getTabIndex()
+ {
+ return tabIndex_;
+ }
+
+ @Override
+ public Type<TabClosedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(TabClosedHandler handler)
+ {
+ handler.onTabClosed(this);
+ }
+
+ private int tabIndex_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabClosedHandler.java b/src/gwt/src/org/rstudio/core/client/events/TabClosedHandler.java
new file mode 100644
index 0000000..5a3a6c8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabClosedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * TabClosedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface TabClosedHandler extends EventHandler
+{
+ void onTabClosed(TabClosedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabClosingEvent.java b/src/gwt/src/org/rstudio/core/client/events/TabClosingEvent.java
new file mode 100644
index 0000000..916eda0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabClosingEvent.java
@@ -0,0 +1,58 @@
+/*
+ * TabClosingEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class TabClosingEvent extends GwtEvent<TabClosingHandler>
+{
+ public static final GwtEvent.Type<TabClosingHandler> TYPE =
+ new GwtEvent.Type<TabClosingHandler>();
+
+ public TabClosingEvent(int tabIndex)
+ {
+ tabIndex_ = tabIndex;
+ }
+
+ public int getTabIndex()
+ {
+ return tabIndex_;
+ }
+
+ public boolean isCancelled()
+ {
+ return cancelled_;
+ }
+
+ public void cancel()
+ {
+ cancelled_ = true;
+ }
+
+ @Override
+ protected void dispatch(TabClosingHandler handler)
+ {
+ handler.onTabClosing(this);
+ }
+
+ @Override
+ public GwtEvent.Type<TabClosingHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final int tabIndex_;
+ private boolean cancelled_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/TabClosingHandler.java b/src/gwt/src/org/rstudio/core/client/events/TabClosingHandler.java
new file mode 100644
index 0000000..5ae11c2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/TabClosingHandler.java
@@ -0,0 +1,22 @@
+/*
+ * TabClosingHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface TabClosingHandler extends EventHandler
+{
+ void onTabClosing(TabClosingEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeEvent.java b/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeEvent.java
new file mode 100644
index 0000000..7c5bae5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeEvent.java
@@ -0,0 +1,48 @@
+/*
+ * WindowStateChangeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.layout.WindowState;
+
+public class WindowStateChangeEvent extends GwtEvent<WindowStateChangeHandler>
+{
+ public static final Type<WindowStateChangeHandler> TYPE =
+ new Type<WindowStateChangeHandler>();
+
+ public WindowStateChangeEvent(WindowState newState)
+ {
+ newState_ = newState;
+ }
+
+ public WindowState getNewState()
+ {
+ return newState_;
+ }
+
+ @Override
+ public Type<WindowStateChangeHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(WindowStateChangeHandler handler)
+ {
+ handler.onWindowStateChange(this);
+ }
+
+ private final WindowState newState_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeHandler.java b/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeHandler.java
new file mode 100644
index 0000000..f33037a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/events/WindowStateChangeHandler.java
@@ -0,0 +1,22 @@
+/*
+ * WindowStateChangeHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface WindowStateChangeHandler extends EventHandler
+{
+ void onWindowStateChange(WindowStateChangeEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/FileSystemContext.java b/src/gwt/src/org/rstudio/core/client/files/FileSystemContext.java
new file mode 100644
index 0000000..abca6e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/FileSystemContext.java
@@ -0,0 +1,95 @@
+/*
+ * FileSystemContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.widget.ProgressIndicator;
+
+public interface FileSystemContext
+{
+ public interface Callbacks
+ {
+ void onNavigated();
+ void onError(String errorMessage);
+ void onDirectoryCreated(FileSystemItem directory);
+ }
+
+ MessageDisplay messageDisplay();
+
+ void setCallbacks(Callbacks callbacks);
+
+ String combine(String root, String name);
+ FileSystemItem[] parseDir(String dirPath);
+ boolean isAbsolute(String path);
+
+ /**
+ * @return The current directory
+ */
+ String pwd();
+ FileSystemItem pwdItem();
+
+ /**
+ * Begin navigating the context to the specified relative or absolute
+ * path. An onNavigated() callback will be fired when navigation
+ * completes--the results of cd() and ls() are stale until this happens.
+ * @param relativeOrAbsolutePath
+ */
+ void cd(String relativeOrAbsolutePath);
+
+ /**
+ * Get the contents of the current directory
+ * @return
+ */
+ FileSystemItem[] ls();
+
+ /**
+ * Equivalent to calling cd(".")
+ */
+ void refresh();
+
+ /**
+ * Begin creating a folder with the specified name in the current directory.
+ * An onContentsChanged() callback will be fired when it is complete (if
+ * successful--otherwise onError).
+ * @param folderName
+ */
+ void mkdir(String folderName, ProgressIndicator progress);
+
+ /**
+ * Checks if a name is valid
+ * @param name A name for a file or directory (not a full path).
+ * @return An error string if name is NOT valid; otherwise, null.
+ */
+ String validatePathElement(String name, boolean forCreation);
+
+ /**
+ * Finds the item in the current directory for the given name. If no
+ * item is found, null is returned if onlyIfExists is true. Otherwise,
+ * a new item is returned--either a directory or a file, depending on
+ * createAsDirectory.
+ * @param name
+ * @param onlyIfExists
+ * @param createAsDirectory
+ * @return
+ */
+ FileSystemItem itemForName(String name,
+ boolean onlyIfExists,
+ boolean createAsDirectory);
+
+ ImageResource getIcon(FileSystemItem item);
+
+ boolean isRoot(FileSystemItem item);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/FileSystemItem.java b/src/gwt/src/org/rstudio/core/client/files/FileSystemItem.java
new file mode 100644
index 0000000..6b77a07
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/FileSystemItem.java
@@ -0,0 +1,420 @@
+/*
+ * FileSystemItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.common.vcs.StatusAndPathInfo;
+
+import java.util.Date;
+import java.util.HashMap;
+
+// NOTE: this class is represented as a native JavaScriptObject for
+// straightforward RPC handling
+
+public class FileSystemItem extends JavaScriptObject
+{
+ protected FileSystemItem()
+ {
+ }
+
+ public static FileSystemItem createDir(String path)
+ {
+ return create(path, true, -1, 0);
+ }
+
+ public static FileSystemItem createFile(String path)
+ {
+ return create(path, false, -1, 0);
+ }
+
+ private static final native FileSystemItem create(String path,
+ boolean dir,
+ double length,
+ double lastModified) /*-{
+ // Boost "complete" function crashes rsession if it
+ // sees e.g. "C:" as opposed to "C:/"
+ if (path.match(/^[a-z]:$/im))
+ path = path + "/";
+
+ var fileEntry = new Object();
+ fileEntry.path = path ;
+ fileEntry.dir = dir ;
+ fileEntry.length = length;
+ fileEntry.lastModified = lastModified;
+ return fileEntry ;
+ }-*/;
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ private final native String getRawPath() /*-{
+ return this.raw_path || this.path;
+ }-*/;
+
+ public final String getName()
+ {
+ return getNameFromPath(getRawPath());
+ }
+
+ public final String getExtension()
+ {
+ return getExtensionFromPath(getName());
+ }
+
+ public final String getStem()
+ {
+ String name = getName();
+ int extensionLength = getExtension().length();
+ return name.substring(0, name.length() - extensionLength);
+ }
+
+ public static String getExtensionFromPath(String path)
+ {
+ String filename = getNameFromPath(path);
+ int lastDotIndex = filename.lastIndexOf('.');
+ if (lastDotIndex != -1)
+ return filename.substring(lastDotIndex);
+ else
+ return "";
+ }
+
+ public final native boolean isDirectory() /*-{
+ return this.dir;
+ }-*/;
+
+ public final int getLength()
+ {
+ return getLengthNative();
+ }
+
+ public final Date getLastModified()
+ {
+ Double lastModified = new Double(getLastModifiedNative());
+ return new Date(lastModified.longValue());
+ }
+
+ public final FileSystemItem getParentPath()
+ {
+ String parentPath;
+ String path = getPath();
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash <= 0)
+ {
+ return null;
+ }
+ else
+ {
+ parentPath = path.substring(0, lastSlash);
+ return FileSystemItem.createDir(parentPath);
+ }
+ }
+
+ public final String getParentPathString()
+ {
+ FileSystemItem parentPath = getParentPath();
+ if (parentPath != null)
+ return getParentPath().getPath();
+ else
+ return "";
+ }
+
+ public final String completePath(String name)
+ {
+ String path = getPath();
+ if (path.length() == 0)
+ return name ;
+ else
+ return path + "/" + name;
+ }
+
+ public final boolean isWithinHome()
+ {
+ String path = getPath();
+ return path.startsWith(HOME_PREFIX) &&
+ path.length() > HOME_PREFIX.length();
+ }
+
+ public final String homeRelativePath()
+ {
+ if (isWithinHome())
+ {
+ return getPath().substring(HOME_PREFIX.length());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public final String getPathRelativeTo(FileSystemItem other)
+ {
+ String otherPath = other.getPath();
+ if (!otherPath.endsWith("/"))
+ otherPath = otherPath + "/";
+
+ if (getPath().startsWith(otherPath) &&
+ (getPath().length() > otherPath.length()))
+ {
+ return getPath().substring(otherPath.length());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public final boolean isPublicFolder()
+ {
+ return isDirectory() && PUBLIC.equals(homeRelativePath());
+ }
+
+ // RStudio-specific code should use FileTypeRegistry.getIconForFile() instead
+ public final ImageResource getIcon()
+ {
+ if (isDirectory())
+ {
+ if (isPublicFolder())
+ return RES.iconPublicFolder();
+ else
+ return RES.iconFolder();
+ }
+
+ Match m = EXT_PATTERN.match(getName(), 0);
+ if (m == null)
+ return RES.iconText();
+
+ String lowerExt = m.getValue().toLowerCase();
+ if (lowerExt.equals(".csv"))
+ {
+ return RES.iconCsv();
+ }
+ else if (lowerExt.equals(".pdf"))
+ {
+ return RES.iconPdf();
+ }
+ else if (lowerExt.equals(".jpg") || lowerExt.equals(".jpeg") ||
+ lowerExt.equals(".gif") || lowerExt.equals(".bmp") ||
+ lowerExt.equals(".tiff") || lowerExt.equals(".tif") ||
+ lowerExt.equals(".png"))
+ {
+ return RES.iconPng();
+ }
+ else
+ {
+ return RES.iconText();
+ }
+ }
+
+ public final String mimeType()
+ {
+ return mimeType("text/plain");
+ }
+
+ public final String mimeType(String defaultType)
+ {
+ String ext = getExtension().toLowerCase();
+ if (ext.length() > 0)
+ {
+ String mimeExt = ext.substring(1).toLowerCase();
+ String mimeType = MIME_TYPES.get(mimeExt);
+ if (mimeType != null)
+ return mimeType;
+ else
+ return defaultType;
+ }
+ else
+ {
+ return defaultType;
+ }
+ }
+
+ public final static boolean areEqual(FileSystemItem a, FileSystemItem b)
+ {
+ if (a == null && b == null)
+ return true;
+ else if (a == null && b != null)
+ return false;
+ else if (a != null && b == null)
+ return false;
+ else
+ return a.equalTo(b);
+ }
+
+ public final boolean equalTo(FileSystemItem other)
+ {
+ if (other==null)
+ return false;
+
+ return compareTo(other) == 0;
+ }
+
+ public final int compareTo(FileSystemItem other)
+ {
+ // If we ever need to compare files that don't share the same
+ // parent, then maybe we would need to compare parent directory
+ // before anything else.
+
+ if (isDirectory() ^ other.isDirectory())
+ return isDirectory() ? -1 : 1;
+ return String.CASE_INSENSITIVE_ORDER.compare(getPath(),
+ other.getPath());
+
+ }
+
+ public final static FileSystemItem home()
+ {
+ return createDir(HOME_PATH);
+ }
+
+ public final static String getNameFromPath(String path)
+ {
+ if (path.equals(""))
+ return "";
+
+ if (path.equals("/"))
+ return "/";
+
+ while (path.endsWith("/"))
+ path = path.substring(0, path.length() - 1);
+
+ return path.substring(Math.max(0, path.lastIndexOf('/') + 1));
+ }
+
+ private final native int getLengthNative() /*-{
+ return this.length;
+ }-*/;
+
+ private final native double getLastModifiedNative() /*-{
+ return this.lastModified;
+ }-*/;
+
+ public final native StatusAndPathInfo getGitStatus() /*-{
+ return this.git_status;
+ }-*/;
+
+ public final native StatusAndPathInfo getSVNStatus() /*-{
+ return this.svn_status;
+ }-*/;
+
+ // NOTE: should be synced with mime type database in FilePath.cpp
+ private final static HashMap<String,String> MIME_TYPES =
+ new HashMap<String,String>();
+ static
+ {
+ MIME_TYPES.put( "htm", "text/html" );
+ MIME_TYPES.put( "html", "text/html" );
+ MIME_TYPES.put( "css", "text/css" );
+ MIME_TYPES.put( "gif", "image/gif" );
+ MIME_TYPES.put( "jpg", "image/jpeg" );
+ MIME_TYPES.put( "jpeg", "image/jpeg" );
+ MIME_TYPES.put( "jpe", "image/jpeg" );
+ MIME_TYPES.put( "png", "image/png" );
+ MIME_TYPES.put( "js", "text/javascript" );
+ MIME_TYPES.put( "pdf", "application/pdf" );
+ MIME_TYPES.put( "svg", "image/svg+xml" );
+ MIME_TYPES.put( "swf", "application/x-shockwave-flash" );
+ MIME_TYPES.put( "ttf", "application/x-font-ttf" );
+
+ // markdown types
+ MIME_TYPES.put( "md", "text/x-markdown" );
+ MIME_TYPES.put( "mdtxt", "text/x-markdown" );
+ MIME_TYPES.put( "markdown", "text/x-markdown" );
+
+ // programming languages
+ MIME_TYPES.put( "f", "text/x-fortran" );
+
+ // other types we are likely to serve
+ MIME_TYPES.put( "xml", "text/xml" );
+ MIME_TYPES.put( "csv", "text/csv" );
+ MIME_TYPES.put( "ico", "image/x-icon" );
+ MIME_TYPES.put( "zip", "application/zip" );
+ MIME_TYPES.put( "bz", "application/x-bzip");
+ MIME_TYPES.put( "bz2", "application/x-bzip2");
+ MIME_TYPES.put( "gz", "application/x-gzip");
+ MIME_TYPES.put( "tar", "application/x-tar");
+
+ // yet more types...
+
+ MIME_TYPES.put( "shtml", "text/html" );
+ MIME_TYPES.put( "tsv", "text/tab-separated-values" );
+ MIME_TYPES.put( "tab", "text/tab-separated-values" );
+ MIME_TYPES.put( "dcf", "text/debian-control-file" );
+ MIME_TYPES.put( "txt", "text/plain" );
+ MIME_TYPES.put( "mml", "text/mathml" );
+ MIME_TYPES.put( "log", "text/plain");
+ MIME_TYPES.put( "out", "text/plain");
+ MIME_TYPES.put( "r", "text/x-r-source");
+ MIME_TYPES.put( "rd", "text/x-r-doc");
+ MIME_TYPES.put( "rnw", "text/x-r-sweave");
+ MIME_TYPES.put( "rmd", "text/x-r-markdown");
+ MIME_TYPES.put( "rhtml", "text/x-r-html");
+ MIME_TYPES.put( "rpres", "text/x-r-presentation");
+ MIME_TYPES.put( "rout", "text/plain");
+ MIME_TYPES.put( "po", "text/plain");
+ MIME_TYPES.put( "pot", "text/plain");
+ MIME_TYPES.put( "gitignore", "text/plain");
+ MIME_TYPES.put( "rbuildignore","text/plain");
+
+ MIME_TYPES.put( "tif", "image/tiff" );
+ MIME_TYPES.put( "tiff", "image/tiff" );
+ MIME_TYPES.put( "bmp", "image/bmp" );
+ MIME_TYPES.put( "ps", "application/postscript" );
+ MIME_TYPES.put( "eps", "application/postscript" );
+ MIME_TYPES.put( "dvi", "application/x-dvi" );
+
+ MIME_TYPES.put( "atom", "application/atom+xml" );
+ MIME_TYPES.put( "rss", "application/rss+xml" );
+
+ MIME_TYPES.put( "doc", "application/msword" );
+ MIME_TYPES.put( "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" );
+ MIME_TYPES.put( "odt", "application/vnd.oasis.opendocument.text" );
+ MIME_TYPES.put( "rtf", "application/rtf" );
+ MIME_TYPES.put( "xls", "application/vnd.ms-excel" );
+ MIME_TYPES.put( "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" );
+ MIME_TYPES.put( "ods", "application/x-vnd.oasis.opendocument.spreadsheet" );
+ MIME_TYPES.put( "ppt", "application/vnd.ms-powerpoint" );
+ MIME_TYPES.put( "pps", "application/vnd.ms-powerpoint" );
+ MIME_TYPES.put( "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" );
+
+ MIME_TYPES.put( "sit", "application/x-stuffit" );
+ MIME_TYPES.put( "sxw", "application/vnd.sun.xml.writer" );
+
+ MIME_TYPES.put( "iso", "application/octet-stream" );
+ MIME_TYPES.put( "dmg", "application/octet-stream" );
+ MIME_TYPES.put( "exe", "application/octet-stream" );
+ MIME_TYPES.put( "dll", "application/octet-stream" );
+ MIME_TYPES.put( "deb", "application/octet-stream" );
+ MIME_TYPES.put( "xpi", "application/x-xpinstall" );
+
+ MIME_TYPES.put( "mp2", "audio/mpeg" );
+ MIME_TYPES.put( "mp3", "audio/mpeg" );
+
+ MIME_TYPES.put( "mpg", "video/mpeg" );
+ MIME_TYPES.put( "mpeg", "video/mpeg" );
+ MIME_TYPES.put( "flv", "video/x-flv" );
+ }
+
+ private static final Pattern EXT_PATTERN = Pattern.create("\\.[^.]+$");
+ private static final FileIconResources RES = FileIconResources.INSTANCE;
+
+ public static final String HOME_PATH = "~";
+ public static final String HOME_PREFIX = HOME_PATH + "/";
+ private static final String PUBLIC = "Public";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/PosixFileSystemContext.java b/src/gwt/src/org/rstudio/core/client/files/PosixFileSystemContext.java
new file mode 100644
index 0000000..aee4cd8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/PosixFileSystemContext.java
@@ -0,0 +1,154 @@
+/*
+ * PosixFileSystemContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files;
+
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+import java.util.ArrayList;
+
+public abstract class PosixFileSystemContext implements FileSystemContext
+{
+ public PosixFileSystemContext()
+ {
+ workingDir_ = "~";
+ }
+
+ public void setCallbacks(Callbacks callbacks)
+ {
+ callbacks_ = callbacks;
+ }
+
+ public String combine(String root, String name)
+ {
+ if (name == null || name.length() == 0)
+ return root;
+
+ // Is it absolute?
+ if (isAbsolute(name))
+ return name;
+
+ if (root == null || root.length() == 0)
+ return name;
+
+ if (root.endsWith("/"))
+ return root + name;
+ else
+ return root + "/" + name;
+ }
+
+ public FileSystemItem[] parseDir(String dirPath)
+ {
+ ArrayList<FileSystemItem> results = new ArrayList<FileSystemItem>();
+
+ if (dirPath.startsWith("/"))
+ results.add(FileSystemItem.createDir("/"));
+
+ Pattern pattern = Pattern.create("[^/]+");
+ Match m = pattern.match(dirPath, 0);
+ while (m != null)
+ {
+ results.add(FileSystemItem.createDir(
+ dirPath.substring(0, m.getIndex() + m.getValue().length())));
+
+ m = m.nextMatch();
+ }
+
+ return results.toArray(new FileSystemItem[0]);
+ }
+
+ public boolean isAbsolute(String path)
+ {
+ if (path.startsWith("/") || path.startsWith("~/") || path.equals("~"))
+ return true;
+
+ // Detect if this is a Windows root--necessary for Windows RDesktop.
+ if (path.length() >= 2
+ && isAsciiLetter(path.charAt(0))
+ && path.charAt(1) == ':')
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isAsciiLetter(char c)
+ {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+
+ public String pwd()
+ {
+ return workingDir_;
+ }
+
+ public FileSystemItem pwdItem()
+ {
+ return FileSystemItem.createDir(workingDir_);
+ }
+
+ public FileSystemItem[] ls()
+ {
+ return contents_;
+ }
+
+ public String validatePathElement(String name, boolean forCreation)
+ {
+ if (name == null || name.length() == 0)
+ return "Name is empty";
+ if (name.startsWith(" ") || name.endsWith(" "))
+ return "Names should not start or end with spaces";
+ if (name.contains("/"))
+ return "Illegal character: /";
+ if (forCreation && (name.equals(".") || name.equals("..")))
+ return "Illegal name";
+
+ return null;
+ }
+
+ public FileSystemItem itemForName(String name,
+ boolean onlyIfExists,
+ boolean createAsDirectory)
+ {
+ assert validatePathElement(name, true) == null;
+
+ if (contents_ == null)
+ return null;
+ for (FileSystemItem fsi : contents_)
+ if (fsi.getName().equalsIgnoreCase(name))
+ return fsi;
+
+ if (onlyIfExists)
+ return null;
+ else
+ {
+ String path = combine(workingDir_, name);
+ if (createAsDirectory)
+ return FileSystemItem.createDir(path);
+ else
+ return FileSystemItem.createFile(path);
+ }
+ }
+
+ public boolean isRoot(FileSystemItem item)
+ {
+ return item.isDirectory() && item.getPath().equals("~");
+ }
+
+ protected String workingDir_;
+ protected FileSystemItem[] contents_;
+ protected Callbacks callbacks_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog.java
new file mode 100644
index 0000000..d0e1ebe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog.java
@@ -0,0 +1,161 @@
+/*
+ * ChooseFolderDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+import java.util.ArrayList;
+
+
+public class ChooseFolderDialog extends FileSystemDialog
+{
+ public ChooseFolderDialog(String title,
+ FileSystemContext context,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ super(title, null, "Choose", context, "", operation);
+ }
+
+ @Override
+ protected String getFilenameLabel()
+ {
+ return "Folder";
+ }
+
+ @Override
+ protected FileSystemItem[] ls()
+ {
+ FileSystemItem[] items = super.ls();
+ ArrayList<FileSystemItem> dirs = new ArrayList<FileSystemItem>();
+ for (FileSystemItem item : items)
+ if (item.isDirectory())
+ dirs.add(item);
+ return dirs.toArray(new FileSystemItem[0]);
+ }
+
+ @Override
+ protected Widget createTopWidget()
+ {
+ Widget topWidget = super.createTopWidget();
+
+ filename_.addKeyUpHandler(new KeyUpHandler()
+ {
+ public void onKeyUp(KeyUpEvent event)
+ {
+ maybeInvalidateSelection();
+ }
+ });
+ filename_.addKeyPressHandler(new KeyPressHandler()
+ {
+ public void onKeyPress(KeyPressEvent event)
+ {
+ maybeInvalidateSelection();
+ }
+ });
+
+ return topWidget;
+ }
+
+ private void maybeInvalidateSelection()
+ {
+ String selectedValue = directory_.getSelectedValue();
+ if (selectedValue != null && !selectedValue.equals(filename_.getText()))
+ directory_.setSelectedRow(null);
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ super.onDialogShown();
+ directory_.setFocus(true);
+ }
+
+ @Override
+ public void onNavigated()
+ {
+ super.onNavigated();
+ FileSystemItem[] dirs = context_.parseDir(context_.pwd());
+ filename_.setText(dirs[dirs.length - 1].getName());
+ }
+
+ @Override
+ public void onSelection(SelectionEvent<FileSystemItem> event)
+ {
+ super.onSelection(event);
+ FileSystemItem item = event.getSelectedItem();
+ if (item != null && item.isDirectory())
+ filename_.setText(item.getName());
+ }
+
+ @Override
+ protected boolean shouldAccept()
+ {
+ return getEffectiveDirectoryWithValidation() != null;
+ }
+
+ private String getEffectiveDirectoryWithValidation()
+ {
+ filename_.setText(filename_.getText().trim());
+ String name = filename_.getText();
+
+ // This handles the special case of "~"
+ if (context_.isAbsolute(name))
+ return name;
+
+ if (name.length() == 0)
+ return context_.pwd();
+
+ if (name.contains("/"))
+ return context_.combine(context_.pwd(), name);
+
+ // If an item is selected (highlighted) in the browse control, then
+ // only use it IF it is the same as the name in the name textbox. The
+ // name textbox takes precedence.
+ FileSystemItem selectedItem = directory_.getSelectedItem();
+ if (selectedItem != null && selectedItem.getName().equals(name))
+ return selectedItem.getPath();
+
+ if (name.equals(context_.pwdItem().getName()))
+ {
+ // The identity condition
+ return context_.pwd();
+ }
+
+ FileSystemItem item = context_.itemForName(name,
+ true,
+ false);
+ if (item == null)
+ {
+ onError("The folder does not exist.");
+ return null;
+ }
+
+ return item.getPath();
+ }
+
+ @Override
+ protected FileSystemItem getSelectedItem()
+ {
+ return FileSystemItem.createDir(getEffectiveDirectoryWithValidation());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog2.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog2.java
new file mode 100644
index 0000000..adc5568
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/ChooseFolderDialog2.java
@@ -0,0 +1,83 @@
+/*
+ * ChooseFolderDialog2.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+import java.util.ArrayList;
+
+public class ChooseFolderDialog2 extends FileSystemDialog
+{
+ public ChooseFolderDialog2(String title,
+ FileSystemContext context,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ super(title, null, "Choose", context, "", operation);
+ }
+
+ @Override
+ protected String getFilenameLabel()
+ {
+ return "Folder";
+ }
+
+ @Override
+ protected Widget createTopWidget()
+ {
+ Widget topWidget = super.createTopWidget();
+ filename_.setEnabled(false);
+ filename_.getElement().getStyle().setBackgroundColor("transparent");
+ return topWidget;
+ }
+
+ @Override
+ protected FileSystemItem[] ls()
+ {
+ FileSystemItem[] items = super.ls();
+ ArrayList<FileSystemItem> dirs = new ArrayList<FileSystemItem>();
+ for (FileSystemItem item : items)
+ if (item.isDirectory())
+ dirs.add(item);
+ return dirs.toArray(new FileSystemItem[0]);
+ }
+
+ @Override
+ public void onNavigated()
+ {
+ super.onNavigated();
+ filename_.setText(context_.pwd());
+ }
+
+ @Override
+ public void onSelection(SelectionEvent<FileSystemItem> event)
+ {
+ super.onSelection(event);
+ FileSystemItem item = event.getSelectedItem();
+ if (item != null)
+ filename_.setText(item.getPath());
+ else
+ filename_.setText("");
+ }
+
+ @Override
+ protected FileSystemItem getSelectedItem()
+ {
+ return FileSystemItem.createDir(filename_.getText());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/DirectoryContentsWidget.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/DirectoryContentsWidget.java
new file mode 100644
index 0000000..6707dfe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/DirectoryContentsWidget.java
@@ -0,0 +1,403 @@
+/*
+ * DirectoryContentsWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HTMLTable;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.DoubleClickState;
+import org.rstudio.core.client.widget.ScrollPanelWithClick;
+import org.rstudio.core.client.widget.SimplePanelWithProgress;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+
+import java.util.HashMap;
+
+public class DirectoryContentsWidget
+ extends Composite
+ implements HasSelectionHandlers<FileSystemItem>,
+ HasSelectionCommitHandlers<FileSystemItem>,
+ HasFocusHandlers, HasBlurHandlers
+{
+
+ private static class FlexTableEx extends FlexTable
+ {
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+ }
+
+ public DirectoryContentsWidget(FileSystemContext context)
+ {
+ context_ = context;
+ table_ = new FlexTableEx();
+ table_.getElement().setTabIndex(0);
+ table_.setCellSpacing(0);
+ table_.setCellPadding(2);
+ table_.setSize("100%", "100%");
+
+ scrollPanel_ = new ScrollPanelWithClick(table_);
+ scrollPanel_.setSize("100%", "100%");
+
+ progressPanel_ = new SimplePanelWithProgress();
+ progressPanel_.setWidget(null);
+
+ initWidget(progressPanel_);
+
+ setStylePrimaryName(styles_.contents());
+
+ hookMouseEvents();
+ }
+
+ private void hookMouseEvents()
+ {
+ table_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ focusImpl_.focus(table_.getElement());
+
+ HTMLTable.Cell cell = table_.getCellForEvent(event);
+ if (cell != null)
+ {
+ setSelectedRow(cell.getRowIndex());
+
+ if (doubleClick_.checkForDoubleClick(event.getNativeEvent()))
+ {
+ SelectionCommitEvent.fire(DirectoryContentsWidget.this,
+ getSelectedItem());
+ }
+ }
+ else
+ setSelectedRow(null);
+ }
+ });
+ table_.addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ }
+ });
+ table_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_DOWN:
+ moveBy(event, 1);
+ break;
+ case KeyCodes.KEY_UP:
+ moveBy(event, -1);
+ break;
+ case KeyCodes.KEY_PAGEDOWN:
+ moveBy(event, 12);
+ break;
+ case KeyCodes.KEY_PAGEUP:
+ moveBy(event, -12);
+ break;
+ case KeyCodes.KEY_HOME:
+ event.preventDefault();
+ event.stopPropagation();
+ if (table_.getRowCount() > 0)
+ setSelectedRow(0);
+ break;
+ case KeyCodes.KEY_END:
+ event.preventDefault();
+ event.stopPropagation();
+ if (table_.getRowCount() > 0)
+ setSelectedRow(table_.getRowCount() - 1);
+ break;
+ case KeyCodes.KEY_ENTER:
+ event.preventDefault();
+ event.stopPropagation();
+ SelectionCommitEvent.fire(DirectoryContentsWidget.this,
+ getSelectedItem());
+ break;
+ }
+ }
+
+ private void moveBy(KeyDownEvent event, int offset)
+ {
+ event.stopPropagation();
+ event.preventDefault();
+ moveSelection(offset);
+ }
+ });
+
+ scrollPanel_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ setSelectedRow(null);
+ }
+ });
+ scrollPanel_.addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ }
+ });
+ }
+
+ private void moveSelection(int offset)
+ {
+ if (selectedRow_ == null)
+ {
+ if (table_.getRowCount() > 0)
+ setSelectedRow(0);
+ return;
+ }
+
+ int row = selectedRow_.intValue() + offset;
+ row = Math.max(0, Math.min(table_.getRowCount()-1, row));
+ setSelectedRow(row);
+ }
+
+ public void setSelectedRow(Integer row)
+ {
+ if (selectedRow_ != null)
+ {
+ table_.getRowFormatter().removeStyleName(
+ selectedRow_.intValue(),
+ "gwt-MenuItem-selected");
+ selectedRow_ = null;
+ selectedValue_ = null;
+ }
+
+ if (row != null
+ && row.intValue() >= 0
+ && row.intValue() < table_.getRowCount())
+ {
+ selectedRow_ = row.intValue();
+ table_.getRowFormatter().addStyleName(
+ selectedRow_,
+ "gwt-MenuItem-selected");
+ selectedValue_ = table_.getText(row.intValue(), COL_NAME);
+
+ TableRowElement rowEl = ((TableElement)table_.getElement().cast())
+ .getRows().getItem(selectedRow_);
+ int horizScroll = scrollPanel_.getHorizontalScrollPosition();
+ rowEl.scrollIntoView();
+ scrollPanel_.setHorizontalScrollPosition(horizScroll);
+ }
+
+ SelectionEvent.fire(DirectoryContentsWidget.this,
+ getSelectedItem());
+ }
+
+ public String getSelectedValue()
+ {
+ return selectedValue_;
+ }
+
+ public FileSystemItem getSelectedItem()
+ {
+ return items_.get(getSelectedValue());
+ }
+
+ public void showProgress(boolean show)
+ {
+ if (show)
+ progressPanel_.showProgress(300);
+ else
+ progressPanel_.setWidget(scrollPanel_);
+ }
+
+ public void clearContents()
+ {
+ table_.removeAllRows();
+ items_.clear();
+ selectedRow_ = null;
+ selectedValue_ = null;
+ scrollPanel_.scrollToTop();
+ scrollPanel_.scrollToLeft();
+ }
+
+ public void setContents(FileSystemItem[] contents,
+ FileSystemItem parentDirectory)
+ {
+ clearContents();
+
+ if (parentDirectory != null)
+ addItem(parentDirectory,
+ "..",
+ FileIconResources.INSTANCE.iconUpFolder());
+
+ for (FileSystemItem fsi : contents)
+ addItem(fsi, null, null);
+
+ showProgress(false);
+ }
+
+ private int addItem(FileSystemItem item,
+ String customName,
+ ImageResource customIcon)
+ {
+ if (customName == null)
+ customName = item.getName();
+ if (customIcon == null)
+ customIcon = context_.getIcon(item);
+
+ items_.put(customName, item);
+
+ int newRow = table_.insertRow(table_.getRowCount());
+ table_.setWidget(
+ newRow,
+ COL_ICON,
+ new Image(customIcon));
+ table_.setText(newRow, COL_NAME, customName);
+
+ table_.getCellFormatter().setStylePrimaryName(newRow,
+ COL_ICON,
+ styles_.columnIcon());
+ table_.getCellFormatter().setStylePrimaryName(newRow,
+ COL_NAME,
+ styles_.columnName());
+
+ if (!item.isDirectory())
+ {
+ table_.setText(newRow,
+ COL_SIZE,
+ StringUtil.formatFileSize(item.getLength()));
+
+ table_.setText(newRow,
+ COL_TIMESTAMP,
+ StringUtil.formatDate(item.getLastModified()));
+
+ table_.getCellFormatter().setStylePrimaryName(newRow,
+ COL_SIZE,
+ styles_.columnSize());
+ table_.getCellFormatter().setStylePrimaryName(newRow,
+ COL_TIMESTAMP,
+ styles_.columnDate());
+ }
+ else
+ {
+ ((FlexTable.FlexCellFormatter)table_.getCellFormatter()).setColSpan(
+ newRow, COL_NAME, 3);
+ }
+ return newRow;
+ }
+
+ public Point getScrollPosition()
+ {
+ return new Point(scrollPanel_.getHorizontalScrollPosition(),
+ scrollPanel_.getVerticalScrollPosition());
+ }
+
+ public void setScrollPosition(Point p)
+ {
+ scrollPanel_.setVerticalScrollPosition(p.getY());
+ scrollPanel_.setHorizontalScrollPosition(p.getX());
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<FileSystemItem> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<FileSystemItem> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType());
+ }
+
+ public void addDirectory(FileSystemItem directory)
+ {
+ int rowNum = addItem(directory, null, null);
+
+ TableElement table = (TableElement) table_.getElement().cast();
+ TableRowElement row = table.getRows().getItem(rowNum);
+ row.scrollIntoView();
+ scrollPanel_.setHorizontalScrollPosition(0);
+ setSelectedRow(rowNum);
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return table_.addFocusHandler(handler);
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return table_.addBlurHandler(handler);
+ }
+
+ public void setFocus(boolean focused)
+ {
+ if (focused)
+ focusImpl_.focus(table_.getElement());
+ else
+ focusImpl_.blur(table_.getElement());
+ }
+
+ private HashMap<String, FileSystemItem> items_ =
+ new HashMap<String, FileSystemItem>();
+ private final DoubleClickState doubleClick_ = new DoubleClickState();
+ private Integer selectedRow_;
+ private String selectedValue_;
+ private final FlexTableEx table_;
+ private final ScrollPanelWithClick scrollPanel_;
+ private final SimplePanelWithProgress progressPanel_;
+ private static final int COL_ICON = 0;
+ private static final int COL_NAME = 1;
+ private static final int COL_SIZE = 2;
+ private static final int COL_TIMESTAMP = 3;
+ private final FileDialogStyles styles_ = FileDialogResources.INSTANCE.styles();
+
+ private final FocusImpl focusImpl_ = FocusImpl.getFocusImplForPanel();
+ private final FileSystemContext context_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialog.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialog.java
new file mode 100644
index 0000000..42a44be
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialog.java
@@ -0,0 +1,223 @@
+/*
+ * FileDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+public abstract class FileDialog extends FileSystemDialog
+{
+ protected FileDialog(String title,
+ String caption,
+ String buttonName,
+ boolean promptOnOverwrite,
+ boolean allowNonexistentFile,
+ FileSystemContext context,
+ String filter,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ super(title, caption, buttonName, context, filter, operation);
+
+ promptOnOverwrite_ = promptOnOverwrite;
+ allowNonexistentFile_ = allowNonexistentFile;
+ }
+
+ /**
+ * This should ONLY be called from the accept() method, otherwise the
+ * filename may contain invalid data which could throw an exception.
+ * @return
+ */
+ @Override
+ protected FileSystemItem getSelectedItem()
+ {
+ return context_.itemForName(filename_.getText(), false, false);
+ }
+
+ @Override
+ protected String getFilenameLabel()
+ {
+ return "File name";
+ }
+
+ /**
+ * Validate the current state of the dialog. Subclasses can override
+ * this but super.shouldAccept() MUST be the last validation that occurs.
+ * @return true if the dialog is in a valid state for acceptance
+ */
+ protected boolean shouldAccept()
+ {
+ filename_.setText(filename_.getText().trim());
+
+ String filename = filename_.getText();
+
+ if (filename.length() == 0)
+ return false;
+
+ int lastIndex = filename.lastIndexOf('/');
+ if (lastIndex >= 0)
+ {
+ String dir = filename.substring(0, lastIndex);
+ if (dir.length() == 0)
+ dir = "/";
+ String file = filename.substring(lastIndex + 1);
+
+ // Targeted fix for "611: Permission denied error when attempting to
+ // browse /shared folder in open file dialog". The /shared folder
+ // doesn't have list permissions.
+ if (dir.equals("/shared"))
+ {
+ cd(filename);
+ return false;
+ }
+
+ filename_.setText(file);
+ filename_.setEnabled(false);
+ attemptAcceptOnNextNavigate_ = true;
+ cd(dir);
+ return false;
+ }
+
+ String filenameValidationError = context_.validatePathElement(filename, true);
+ if (filenameValidationError != null)
+ {
+ filename_.selectAll();
+ showError(filenameValidationError);
+ return false;
+ }
+
+ if (navigateIfDirectory())
+ return false;
+
+ boolean useExactFilename = directory_.getSelectedValue() != null
+ && directory_.getSelectedValue().equals(filename);
+
+ if (!useExactFilename || getAlwaysMungeFilename())
+ {
+ filename = mungeFilename(filename);
+ filename_.setText(filename);
+ }
+
+ if (navigateIfDirectory())
+ return false;
+
+ FileSystemItem item = context_.itemForName(filename, true, false);
+ if (item == null)
+ {
+ if (!allowNonexistentFile_)
+ {
+ showError("File does not exist");
+ return false;
+ }
+ }
+ else
+ {
+ if (item.isDirectory())
+ {
+ assert false : "This case should be covered by navigateIfDirectory";
+ return false;
+ }
+ else if (promptOnOverwrite_)
+ {
+ /* WARNING. showOverwritePrompt() MAY CAUSE accept() TO BE CALLED
+ DIRECTLY. ALL OTHER VALIDATION *MUST* BE COMPLETE BEFORE
+ CALLING showOverwritePrompt()!!! */
+ showOverwritePrompt();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean navigateIfDirectory()
+ {
+ FileSystemItem item = context_.itemForName(filename_.getText(),
+ true,
+ false);
+ if (item != null && item.isDirectory())
+ {
+ filename_.setText("");
+ cd(item.getName());
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gives subclasses an opportunity to change the filename before acceptance.
+ * This happens AFTER validation so it's imperative that no potentially
+ * illegal values be returned from this method.
+ */
+ protected String mungeFilename(String filename)
+ {
+ return filename;
+ }
+
+ protected boolean getAlwaysMungeFilename()
+ {
+ return false;
+ }
+
+ @Override
+ protected void cd(String path)
+ {
+ filename_.setEnabled(false);
+ super.cd(path);
+ }
+
+ @Override
+ public void onNavigated()
+ {
+ super.onNavigated();
+
+ filename_.setEnabled(true);
+ if (attemptAcceptOnNextNavigate_)
+ {
+ attemptAcceptOnNextNavigate_ = false;
+ maybeAccept();
+ }
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ filename_.setFocus(true);
+ filename_.selectAll();
+ }
+
+ @Override
+ public void onSelection(SelectionEvent<FileSystemItem> event)
+ {
+ super.onSelection(event);
+
+ FileSystemItem item = event.getSelectedItem();
+ if (item != null && !item.isDirectory())
+ filename_.setText(item.getName());
+ }
+
+ @Override
+ public void onError(String errorMessage)
+ {
+ attemptAcceptOnNextNavigate_ = false;
+ super.onError(errorMessage);
+ }
+
+ protected boolean promptOnOverwrite_;
+ protected boolean allowNonexistentFile_;
+ private boolean attemptAcceptOnNextNavigate_ = false;
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogResources.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogResources.java
new file mode 100644
index 0000000..c3c67c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogResources.java
@@ -0,0 +1,42 @@
+/*
+ * FileDialogResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface FileDialogResources extends ClientBundle
+{
+ public static final FileDialogResources INSTANCE =
+ GWT.create(FileDialogResources.class);
+
+ @Source("FileDialogStyles.css")
+ FileDialogStyles styles();
+
+ @Source("dirseparator.png")
+ DataResource dirseparator();
+
+ @Source("home.png")
+ DataResource home();
+
+ @Source("home.png")
+ ImageResource homeImage();
+
+ ImageResource fade();
+
+ ImageResource browse();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.css b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.css
new file mode 100644
index 0000000..0141c68
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.css
@@ -0,0 +1,133 @@
+ at external gwt-CheckBox-FilesSelectAll;
+ at external gwt-DialogBox;
+ at external gwt-MenuItem-selected;
+ at external breadcrumb, breadcrumb-filepane;
+
+ at url DIRSEPARATOR dirseparator;
+ at url HOME home;
+
+.contents, .filename {
+ background-color: white;
+ border: #cfd2d4 solid 1px;
+}
+
+.filenamePanel {
+ width: 498px;
+}
+
+.filenameLabel {
+ margin-right: 3px;
+}
+
+.filename {
+ background-color: white;
+ width: 100%;
+ outline: none;
+ height: 22px;
+ font-size: 12px;
+}
+
+.contents {
+ font-size: 12px;
+ width: 500px;
+ height: 300px;
+}
+
+.contents td {
+ cursor: default;
+ white-space: nowrap;
+ overflow-x: hidden;
+}
+
+.columnIcon {
+ width: 20px;
+}
+
+.columnName {
+}
+
+.columnSize {
+ width: 80px;
+ color: #606060;
+ text-align: right;
+ padding-right: 16px;
+}
+
+.columnDate {
+ width: 160px;
+ color: #606060;
+}
+
+.contents tr.gwt-MenuItem-selected .columnSize,
+.contents tr.gwt-MenuItem-selected .columnDate {
+ color: white;
+}
+
+.gwt-CheckBox-FilesSelectAll {
+ margin-left: 2px;
+}
+
+.gwt-DialogBox .breadcrumbFrame {
+ width: 500px;
+ margin-top: 6px;
+ border: #cfd2d4 solid 1px;
+ border-bottom: none;
+}
+
+.breadcrumbFrame {
+ height: 20px;
+}
+
+.breadcrumb {
+ position: relative;
+ white-space: nowrap;
+ overflow: hidden;
+ background-color: #eeeff1;
+ font-size: 11px;
+}
+.breadcrumb, .breadcrumb table, .breadcrumb td {
+ height: 20px;
+}
+.breadcrumb-filepane {
+ background-color: transparent;
+ border: none;
+}
+.breadcrumb-filepane, .breadcrumb-filepane table, .breadcrumb-filepane td {
+ height: 20px;
+}
+.breadcrumb table.path a {
+ float: left;
+ text-decoration: none;
+ color: #0945bf;
+ margin: 0;
+ padding: 3px 6px 0 11px;
+ height: 18px;
+ background: DIRSEPARATOR center left no-repeat;
+}
+
+.breadcrumb table.path a.home {
+ background: none;
+ padding-left: 28px;
+ background: HOME 0 1px no-repeat;
+}
+
+.breadcrumb table.path a.last {
+ padding-right: 8px;
+}
+
+.breadcrumb .fade {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 25px;
+ height: 24px;
+}
+
+.goUp {
+ cursor: pointer;
+ margin: 0 3px 0 12px;
+}
+
+.browse {
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.java
new file mode 100644
index 0000000..5509ba1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileDialogStyles.java
@@ -0,0 +1,41 @@
+/*
+ * FileDialogStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface FileDialogStyles extends CssResource
+{
+
+ String contents();
+
+ String filenamePanel();
+ String filenameLabel();
+ String filename();
+
+ String breadcrumbFrame();
+ String breadcrumb();
+ String path();
+ String home();
+ String last();
+ String fade();
+ String goUp();
+ String browse();
+
+ String columnIcon();
+ String columnName();
+ String columnSize();
+ String columnDate();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/FileSystemDialog.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileSystemDialog.java
new file mode 100644
index 0000000..0d2b3fe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/FileSystemDialog.java
@@ -0,0 +1,425 @@
+/*
+ * FileSystemDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+public abstract class FileSystemDialog extends ModalDialogBase
+ implements SelectionCommitHandler<FileSystemItem>, FileSystemContext.Callbacks,
+ SelectionHandler<FileSystemItem>,
+ ProgressIndicator
+{
+ private class NewFolderHandler implements ClickHandler,
+ ProgressOperationWithInput<String>
+ {
+ public void onClick(ClickEvent event)
+ {
+ context_.messageDisplay().promptForText("New Folder",
+ "Folder name",
+ null,
+ this);
+ }
+
+ public void execute(final String input, final ProgressIndicator progress)
+ {
+ context_.mkdir(input, new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ progress.onProgress(message);
+ }
+
+ public void clearProgress()
+ {
+ progress.clearProgress();
+ }
+
+ public void onCompleted()
+ {
+ progress.onCompleted();
+ context_.cd(input);
+ }
+
+ public void onError(String message)
+ {
+ progress.onError(message);
+ }
+ });
+ }
+ }
+
+ static
+ {
+ FileDialogResources.INSTANCE.styles().ensureInjected();
+ }
+
+ public FileSystemDialog(String title,
+ String caption,
+ String buttonName,
+ FileSystemContext context,
+ String filter,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ context_ = context;
+ context_.setCallbacks(this);
+ filterExtension_ = extractFilterExtension(filter);
+ operation_ = operation;
+
+ setTitle(caption);
+ setText(title);
+
+ addLeftButton(new ThemedButton("New Folder", new NewFolderHandler()));
+
+ addOkButton(new ThemedButton(buttonName, new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ maybeAccept();
+ }
+ }));
+ addCancelButton(new ThemedButton("Cancel", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ if (invokeOperationEvenOnCancel_)
+ {
+ operation_.execute(null, FileSystemDialog.this);
+ }
+ closeDialog();
+ }
+ }));
+
+ addDomHandler(new KeyDownHandler() {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.stopPropagation();
+ event.preventDefault();
+ maybeAccept();
+ }
+ }
+ }, KeyDownEvent.getType());
+
+ progress_ = addProgressIndicator();
+ }
+
+ /**
+ * Subclasses that pass true for the promptOnOverwrite constructor arg
+ * must override this method. If the user elects to overwrite, the
+ * subclass should call accept() directly (this will cause all validation
+ * to be bypassed).
+ */
+ protected void showOverwritePrompt()
+ {
+ assert false :
+ "Subclasses should override showOvewritePrompt() " +
+ "if promptOnOverwrite is true";
+ }
+
+ /**
+ * Accept if validation passes
+ */
+ protected final void maybeAccept()
+ {
+ if (shouldAccept())
+ accept();
+ }
+
+ protected boolean shouldAccept()
+ {
+ return true;
+ }
+
+ protected void accept()
+ {
+ operation_.execute(getSelectedItem(), this);
+ }
+
+ protected abstract FileSystemItem getSelectedItem();
+
+ @Override
+ public void showModal()
+ {
+ super.showModal();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ center();
+ }
+ });
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ breadcrumb_ = new PathBreadcrumbWidget(context_);
+ breadcrumb_.addSelectionCommitHandler(this);
+
+ directory_ = new DirectoryContentsWidget(context_);
+ directory_.addSelectionHandler(this);
+ directory_.addSelectionCommitHandler(this);
+ directory_.showProgress(true);
+
+ DockPanel dockPanel = new DockPanel();
+ Widget topWidget = createTopWidget();
+ if (topWidget != null)
+ dockPanel.add(topWidget, DockPanel.NORTH);
+ dockPanel.add(breadcrumb_, DockPanel.NORTH);
+ dockPanel.add(directory_, DockPanel.CENTER);
+
+ return dockPanel;
+ }
+
+ protected Widget createTopWidget()
+ {
+ String nameLabel = getFilenameLabel();
+ if (nameLabel == null)
+ return null;
+
+ HorizontalPanel filenamePanel = new HorizontalPanel();
+ FileDialogStyles styles = FileDialogResources.INSTANCE.styles();
+ filenamePanel.setStylePrimaryName(styles.filenamePanel());
+ filenamePanel.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
+
+ Label filenameLabel = new Label(nameLabel + ":", false);
+ filenameLabel.setStylePrimaryName(styles.filenameLabel());
+ filenamePanel.add(filenameLabel);
+
+ filename_ = new TextBox();
+ if (initialFilename_ != null)
+ filename_.setText(initialFilename_);
+ filename_.setStylePrimaryName(styles.filename());
+ filenamePanel.add(filename_);
+ filenamePanel.setCellWidth(filename_, "100%");
+
+ return filenamePanel;
+ }
+
+ /**
+ * If non-null, the filename textbox will be inserted at the top
+ * of the dialog with the given label shown next to it.
+ *
+ * If null, the filename textbox will not be created.
+ */
+ protected abstract String getFilenameLabel();
+
+ /**
+ * Set the contents of the filename box
+ */
+ public void setFilename(String filename)
+ {
+ if (filename_ != null)
+ filename_.setText(filename);
+ else
+ initialFilename_ = filename;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ }
+
+ @Override
+ public void onPreviewNativeEvent(Event.NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ // Let directory browser handle its own Enter
+ return;
+ }
+ super.onPreviewNativeEvent(event); //To change body of overridden methods use File | Settings | File Templates.
+ }
+
+ public void onSelectionCommit(SelectionCommitEvent<FileSystemItem> event)
+ {
+ FileSystemItem item = event.getSelectedItem();
+ if (item != null && item.isDirectory())
+ cd(item);
+ else
+ maybeAccept();
+ }
+
+ protected void cd(String path)
+ {
+ if (REMEMBER_SCROLL_POSITION)
+ {
+ if (currentDir_ != null)
+ scrollPositions_.put(currentDir_, directory_.getScrollPosition());
+ }
+
+ directory_.clearContents();
+ directory_.showProgress(true);
+ context_.cd(path);
+ }
+
+ protected void cd(FileSystemItem dir)
+ {
+ assert dir.isDirectory();
+ cd(dir.getPath());
+ }
+
+ public void onSelection(SelectionEvent<FileSystemItem> event)
+ {
+ }
+
+ public void onNavigated()
+ {
+ String dir = context_.pwd();
+
+ final FileSystemItem[] parsedDir = context_.parseDir(dir);
+ breadcrumb_.setDirectory(parsedDir);
+ directory_.setContents(
+ ls(),
+ parsedDir.length > 1 ? parsedDir[parsedDir.length-2] : null);
+
+ if (REMEMBER_SCROLL_POSITION)
+ {
+ if (scrollPositions_.containsKey(dir))
+ directory_.setScrollPosition(scrollPositions_.get(dir));
+ }
+ currentDir_ = dir;
+ }
+
+ protected FileSystemItem[] ls()
+ {
+ FileSystemItem[] items = context_.ls();
+ if (items == null)
+ return new FileSystemItem[0];
+
+ ArrayList<FileSystemItem> filtered = new ArrayList<FileSystemItem>();
+ for (int i = 0; i < items.length; i++)
+ {
+ if (items[i].isDirectory())
+ filtered.add(items[i]);
+ else if (filterExtension_ == null)
+ filtered.add(items[i]);
+ else if (filterExtension_.equalsIgnoreCase(items[i].getExtension()))
+ filtered.add(items[i]);
+ }
+
+ Collections.sort(filtered, new Comparator<FileSystemItem>() {
+ public int compare(FileSystemItem o1, FileSystemItem o2)
+ {
+ return o1.compareTo(o2);
+ }
+ });
+ FileSystemItem[] clone = new FileSystemItem[filtered.size()];
+ return filtered.toArray(clone);
+
+
+ }
+
+ public void onProgress(String message)
+ {
+ progress_.onProgress(message);
+ }
+
+ public void clearProgress()
+ {
+ progress_.clearProgress();
+ }
+
+ public void onCompleted()
+ {
+ progress_.onCompleted();
+ }
+
+ public void onError(String errorMessage)
+ {
+ progress_.onError(errorMessage);
+ onNavigated();
+ }
+
+ protected void showError(String errorMessage)
+ {
+ context_.messageDisplay().showErrorMessage("Error", errorMessage);
+ }
+
+ public void onDirectoryCreated(FileSystemItem directory)
+ {
+ directory_.addDirectory(directory);
+ }
+
+ /**
+ * If true, hitting the Cancel button will result in the action operation
+ * (passed in the constructor) to be invoked with a null value, rather
+ * than having the dialog just close.
+ */
+ public void setInvokeOperationEvenOnCancel(boolean invoke)
+ {
+ invokeOperationEvenOnCancel_ = invoke;
+ }
+
+ // NOTE: web mode only supports a single one-extension filter (whereas
+ // desktop mode supports full multi-filetype, multi-extension filtering).
+ // to support more sophisticated filtering we'd need to both add the
+ // UI as well as update this function to extract a list of filters
+ private String extractFilterExtension(String filter)
+ {
+ if (StringUtil.isNullOrEmpty(filter))
+ {
+ return null;
+ }
+ else
+ {
+ Pattern p = Pattern.create("\\(\\*(\\.[^)]*)\\)$");
+ Match m = p.match(filter, 0);
+ if (m == null)
+ return null;
+ else
+ return m.getGroup(1);
+ }
+ }
+
+
+ private final HashMap<String, Point> scrollPositions_ =
+ new HashMap<String, Point>();
+ private final String filterExtension_;
+ private String currentDir_;
+ protected final FileSystemContext context_;
+ private final ProgressOperationWithInput<FileSystemItem> operation_;
+ private PathBreadcrumbWidget breadcrumb_;
+ protected DirectoryContentsWidget directory_;
+ private static final boolean REMEMBER_SCROLL_POSITION = false;
+ protected TextBox filename_;
+ private String initialFilename_;
+ private boolean invokeOperationEvenOnCancel_;
+ private final ProgressIndicator progress_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/OpenFileDialog.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/OpenFileDialog.java
new file mode 100644
index 0000000..e78ec5b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/OpenFileDialog.java
@@ -0,0 +1,30 @@
+/*
+ * OpenFileDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+public class OpenFileDialog extends FileDialog
+{
+ public OpenFileDialog(String title,
+ FileSystemContext context,
+ String filter,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ super(title, null, "Open", false, false, context, filter, operation);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/PathBreadcrumbWidget.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/PathBreadcrumbWidget.java
new file mode 100644
index 0000000..8a2fef3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/PathBreadcrumbWidget.java
@@ -0,0 +1,235 @@
+/*
+ * PathBreadcrumbWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+
+public class PathBreadcrumbWidget
+ extends Composite
+ implements HasSelectionCommitHandlers<FileSystemItem>,
+ RequiresResize
+{
+ public PathBreadcrumbWidget(FileSystemContext context)
+ {
+ context_ = context;
+
+ pathPanel_ = new HorizontalPanel();
+ pathPanel_.setStylePrimaryName(RES.styles().path());
+
+ if (INCLUDE_UP_LINK)
+ {
+ linkUp_ = new Anchor();
+ linkUp_.setTitle("Go to parent directory");
+ linkUp_.setVisible(false);
+ linkUp_.setStylePrimaryName(RES.styles().goUp());
+ Image image = new Image(FileIconResources.INSTANCE.iconUpFolder());
+ linkUp_.getElement().appendChild(image.getElement());
+ }
+ else
+ linkUp_ = null;
+
+ panel_ = new HorizontalPanel();
+ panel_.setSize("100%", "100%");
+ panel_.add(pathPanel_);
+ if (linkUp_ != null)
+ {
+ panel_.add(linkUp_);
+ panel_.setCellHorizontalAlignment(linkUp_, HorizontalPanel.ALIGN_RIGHT);
+ panel_.setCellVerticalAlignment(linkUp_, HorizontalPanel.ALIGN_MIDDLE);
+ }
+
+ outer_ = new SimplePanel();
+ outer_.setWidget(panel_);
+ outer_.setStylePrimaryName(RES.styles().breadcrumb());
+
+ Image fade = new Image(RES.fade());
+ fade.setStylePrimaryName(STYLES.fade());
+ fade_ = fade.getElement();
+ outer_.getElement().appendChild(fade_);
+
+ if (linkUp_ != null)
+ {
+ linkUp_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ FileSystemItem[] dirs = context_.parseDir(
+ context_.pwdItem().getPath());
+ if (dirs.length > 1)
+ context_.cd(dirs[dirs.length - 2].getPath());
+ }
+ });
+ }
+
+ DockLayoutPanel frame = new DockLayoutPanel(Unit.PX);
+
+ Image browse = new Image(RES.browse());
+ browse.setStyleName(STYLES.browse());
+ frame.addEast(browse, RES.browse().getWidth());
+ browse.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ browse();
+ }
+ });
+
+ frame.add(outer_);
+ frame.setStyleName(STYLES.breadcrumbFrame());
+ initWidget(frame);
+ }
+
+ public void setDirectory(FileSystemItem[] pathElements)
+ {
+ pathPanel_.clear();
+
+ if (linkUp_ != null)
+ linkUp_.setVisible(pathElements.length > 1);
+
+ Anchor lastAnchor = null;
+ for (FileSystemItem item : pathElements)
+ lastAnchor = addAnchor(item);
+
+ if (lastAnchor != null)
+ lastAnchor.addStyleName(RES.styles().last());
+
+ onWidthsChanged();
+ }
+
+ private void onWidthsChanged()
+ {
+ DOM.setElementPropertyInt(
+ outer_.getElement(),
+ "scrollLeft",
+ DOM.getElementPropertyInt(outer_.getElement(), "scrollWidth"));
+
+ int scrollPos = DOM.getElementPropertyInt(outer_.getElement(),
+ "scrollLeft");
+ if (scrollPos > 0)
+ {
+ fade_.getStyle().setDisplay(Style.Display.BLOCK);
+ fade_.getStyle().setLeft(scrollPos, Style.Unit.PX);
+ }
+ else
+ {
+ fade_.getStyle().setDisplay(Style.Display.NONE);
+ }
+ }
+
+ private Anchor addAnchor(final FileSystemItem item)
+ {
+ boolean isHome = context_.isRoot(item);
+ Anchor link = new Anchor(isHome ? "Home"
+ : item.getName(),
+ false);
+ link.setTitle(item.getPath());
+ if (isHome)
+ link.addStyleName(RES.styles().home());
+
+ link.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ SelectionCommitEvent.fire(PathBreadcrumbWidget.this, item);
+ }
+ });
+
+ pathPanel_.add(link);
+ return link;
+ }
+
+ private void browse()
+ {
+ if (Desktop.isDesktop())
+ {
+ FileSystemContext tempContext =
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext();
+ RStudioGinjector.INSTANCE.getFileDialogs().chooseFolder(
+ "Go To Folder",
+ tempContext,
+ null,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+ context_.cd(input.getPath());
+ indicator.onCompleted();
+ }
+ });
+ }
+ else
+ {
+ context_.messageDisplay().promptForText(
+ "Go To Folder",
+ "Path to folder (use ~ for home directory):",
+ "",
+ new OperationWithInput<String>() {
+
+ @Override
+ public void execute(String input)
+ {
+ if (input == null)
+ return;
+
+ context_.cd(input);
+ }
+
+ });
+ }
+
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<FileSystemItem> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType());
+ }
+
+ public void onResize()
+ {
+ onWidthsChanged();
+ }
+
+ private final HorizontalPanel panel_;
+ private final HorizontalPanel pathPanel_;
+ private final Anchor linkUp_;
+ private final Element fade_;
+ private final FileSystemContext context_;
+ private FileDialogResources RES = FileDialogResources.INSTANCE;
+ private FileDialogStyles STYLES = RES.styles();
+ private static final boolean INCLUDE_UP_LINK = false;
+ private SimplePanel outer_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/SaveFileDialog.java b/src/gwt/src/org/rstudio/core/client/files/filedialog/SaveFileDialog.java
new file mode 100644
index 0000000..a362b86
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/files/filedialog/SaveFileDialog.java
@@ -0,0 +1,88 @@
+/*
+ * SaveFileDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.files.filedialog;
+
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+public class SaveFileDialog extends FileDialog
+{
+
+ public SaveFileDialog(String title,
+ FileSystemContext context,
+ String defaultExtension,
+ boolean forceDefaultExtension,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ super(title, null, "Save", true, true, context, "", operation);
+ defaultExtension_ = defaultExtension;
+ forceDefaultExtension_ = forceDefaultExtension;
+ }
+
+ @Override
+ protected void showOverwritePrompt()
+ {
+ context_.messageDisplay().showYesNoMessage(
+ MessageDisplay.MSG_WARNING,
+ "Confirm Overwrite",
+ "This file already exists. Do you want to replace it?",
+ false,
+ new Operation()
+ {
+ public void execute()
+ {
+ accept();
+ }
+ },
+ null,
+ true);
+ }
+
+ @Override
+ protected String mungeFilename(String filename)
+ {
+ if (StringUtil.isNullOrEmpty(defaultExtension_))
+ return filename;
+ else
+ {
+ // if there is no extension or the extension doesn't match
+ // the default and we are set to force the default then we
+ // need to add one
+ String ext = FileSystemItem.getExtensionFromPath(filename);
+ if (ext.length() == 0 ||
+ (forceDefaultExtension_ && (ext != defaultExtension_)))
+ {
+ return filename + defaultExtension_;
+ }
+ else
+ {
+ return filename;
+ }
+ }
+ }
+
+ @Override
+ protected boolean getAlwaysMungeFilename()
+ {
+ return forceDefaultExtension_;
+ }
+
+ private final String defaultExtension_;
+ private final boolean forceDefaultExtension_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/browse.png b/src/gwt/src/org/rstudio/core/client/files/filedialog/browse.png
new file mode 100644
index 0000000..c7655df
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/files/filedialog/browse.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/dirseparator.png b/src/gwt/src/org/rstudio/core/client/files/filedialog/dirseparator.png
new file mode 100644
index 0000000..0202391
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/files/filedialog/dirseparator.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/fade.png b/src/gwt/src/org/rstudio/core/client/files/filedialog/fade.png
new file mode 100644
index 0000000..931b833
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/files/filedialog/fade.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/files/filedialog/home.png b/src/gwt/src/org/rstudio/core/client/files/filedialog/home.png
new file mode 100644
index 0000000..8cf00ea
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/files/filedialog/home.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/js/BaseExpression.java b/src/gwt/src/org/rstudio/core/client/js/BaseExpression.java
new file mode 100644
index 0000000..4168cc1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/js/BaseExpression.java
@@ -0,0 +1,20 @@
+/*
+ * BaseExpression.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.js;
+
+public @interface BaseExpression
+{
+ String value();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/js/JavaScriptPassthrough.java b/src/gwt/src/org/rstudio/core/client/js/JavaScriptPassthrough.java
new file mode 100644
index 0000000..03ce070
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/js/JavaScriptPassthrough.java
@@ -0,0 +1,19 @@
+/*
+ * JavaScriptPassthrough.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.js;
+
+public interface JavaScriptPassthrough
+{
+}
diff --git a/src/gwt/src/org/rstudio/core/client/js/JsObject.java b/src/gwt/src/org/rstudio/core/client/js/JsObject.java
new file mode 100644
index 0000000..4bfb869
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/js/JsObject.java
@@ -0,0 +1,139 @@
+/*
+ * JsObject.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.js;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class JsObject extends JavaScriptObject
+{
+ public static native JsObject createJsObject() /*-{
+ return {};
+ }-*/;
+
+ protected JsObject() {}
+
+ public final native boolean hasKey(String key) /*-{
+ return typeof(this[key]) != 'undefined';
+ }-*/;
+
+ public final native String getValueType(String key) /*-{
+ return typeof(this[key]);
+ }-*/;
+
+ public final native <T extends JavaScriptObject> T getObject(String key) /*-{
+ return this[key];
+ }-*/;
+
+ public final native void setObject(String key, JavaScriptObject value) /*-{
+ this[key] = value;
+ }-*/;
+
+ public final native String getString(String key) /*-{
+ return this[key];
+ }-*/;
+
+ public final native String getString(String key, boolean autoconvert) /*-{
+ if (autoconvert)
+ return this[key] + "";
+ else
+ return this[key];
+ }-*/;
+
+ public final native void setString(String key, String value) /*-{
+ this[key] = value;
+ }-*/;
+
+ public final native void unset(String key) /*-{
+ delete this[key];
+ }-*/;
+
+ public final Integer getInteger(String key)
+ {
+ if (!hasKey(key) || !getValueType(key).equals("number"))
+ return null;
+ return _getInteger(key);
+ }
+
+ public final native int _getInteger(String key) /*-{
+ return this[key];
+ }-*/;
+
+ public final void setInteger(String key, Integer value)
+ {
+ if (value == null)
+ setObject(key, null);
+ else
+ _setInteger(key, value.intValue());
+ }
+
+ public final native void _setInteger(String key, int value) /*-{
+ this[key] = value;
+ }-*/;
+
+ public final Double getDouble(String key)
+ {
+ if (!hasKey(key) || !getValueType(key).equals("number"))
+ return null;
+ return _getDouble(key);
+ }
+
+ public final native double _getDouble(String key) /*-{
+ return this[key];
+ }-*/;
+
+ public final void setDouble(String key, Double value)
+ {
+ if (value == null)
+ setObject(key, null);
+ else
+ _setDouble(key, value.doubleValue());
+ }
+
+ public final native void _setDouble(String key, double value) /*-{
+ this[key] = value;
+ }-*/;
+
+ public final Boolean getBoolean(String key)
+ {
+ if (!hasKey(key) || !getValueType(key).equals("boolean"))
+ return null;
+ return _getBoolean(key);
+ }
+
+ public final native boolean _getBoolean(String key) /*-{
+ return this[key];
+ }-*/;
+
+ public final void setBoolean(String key, Boolean value)
+ {
+ if (value == null)
+ setObject(key, null);
+ else
+ _setBoolean(key, value.booleanValue());
+ }
+
+ public final native void _setBoolean(String key, boolean value) /*-{
+ this[key] = value;
+ }-*/;
+
+ public final native JsArrayString keys() /*-{
+ var keys = [];
+ for (var key in this) {
+ keys.push(key);
+ }
+ return keys;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/js/JsObjectInjector.java b/src/gwt/src/org/rstudio/core/client/js/JsObjectInjector.java
new file mode 100644
index 0000000..ee90b2b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/js/JsObjectInjector.java
@@ -0,0 +1,20 @@
+/*
+ * JsObjectInjector.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.js;
+
+public interface JsObjectInjector<T>
+{
+ void injectObject(T value);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/js/JsUtil.java b/src/gwt/src/org/rstudio/core/client/js/JsUtil.java
new file mode 100644
index 0000000..00c10e7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/js/JsUtil.java
@@ -0,0 +1,134 @@
+/*
+ * JsUtil.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.js;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+import java.util.Iterator;
+
+public class JsUtil
+{
+ public static Iterable<String> asIterable(final JsArrayString array)
+ {
+ return new Iterable<String>()
+ {
+ @Override
+ public Iterator<String> iterator()
+ {
+ return new Iterator<String>()
+ {
+ int i = 0;
+
+ @Override
+ public boolean hasNext()
+ {
+ return array.length() > i;
+ }
+
+ @Override
+ public String next()
+ {
+ return array.get(i++);
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ public static <T extends JavaScriptObject> Iterable<T> asIterable(
+ final JsArray<T> array)
+ {
+ return new Iterable<T>()
+ {
+ @Override
+ public Iterator<T> iterator()
+ {
+ return new Iterator<T>()
+ {
+ int i = 0;
+
+ @Override
+ public boolean hasNext()
+ {
+ return array.length() > i;
+ }
+
+ @Override
+ public T next()
+ {
+ return array.get(i++);
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ public static boolean areEqual(JsArrayString a, JsArrayString b)
+ {
+ if (a == null && b == null)
+ return true;
+ else if (a == null && b != null)
+ return false;
+ else if (a != null && b == null)
+ return false;
+ else if (a.length() != b.length())
+ return false;
+ else
+ {
+ for (int i=0; i<a.length(); i++)
+ {
+ if (!a.get(i).equals(b.get(i)))
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ public static String[] toStringArray(JsArrayString strings)
+ {
+ String[] result = new String[strings.length()];
+ for (int i = 0; i < strings.length(); i++)
+ result[i] = strings.get(i);
+ return result;
+ }
+
+ public static JsArrayString toJsArrayString(Iterable<String> strings)
+ {
+ JsArrayString result = JsArrayString.createArray().cast();
+ for (String s : strings)
+ result.push(s);
+ return result;
+ }
+
+ public native static JavaScriptObject createEmptyArray(int length) /*-{
+ return new Array(length);
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLog.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLog.java
new file mode 100644
index 0000000..92ba579
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLog.java
@@ -0,0 +1,52 @@
+/*
+ * RequestLog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.jsonrpc;
+
+import java.util.ArrayList;
+
+public class RequestLog
+{
+ public static RequestLogEntry log(String requestId, String requestData)
+ {
+ RequestLogEntry entry = new RequestLogEntry(System.currentTimeMillis(),
+ requestId, requestData);
+ entries_.add(entry);
+
+ for (int i = 0; entries_.size() > MAX_ENTRIES && i < entries_.size(); i++)
+ {
+ RequestLogEntry oldEntry = entries_.get(i);
+ if (!oldEntry.isAlive())
+ {
+ entries_.remove(i);
+ i--;
+ }
+ }
+
+ return entry;
+ }
+
+ public static RequestLogEntry[] getEntries()
+ {
+ RequestLogEntry[] entries = new RequestLogEntry[entries_.size()];
+ for (int i = 0; i < entries.length; i++)
+ entries[i] = entries_.get(i).clone();
+ return entries;
+ }
+
+ private static final ArrayList<RequestLogEntry> entries_ =
+ new ArrayList<RequestLogEntry>();
+
+ private static final int MAX_ENTRIES = 50;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLogEntry.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLogEntry.java
new file mode 100644
index 0000000..5194f7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RequestLogEntry.java
@@ -0,0 +1,149 @@
+/*
+ * RequestLogEntry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.jsonrpc;
+
+import org.rstudio.core.client.CsvWriter;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+public class RequestLogEntry
+{
+ public static class ResponseType
+ {
+ public static final int None = 0;
+ public static final int Normal = 1;
+ public static final int Error = 2;
+ public static final int Cancelled = 3;
+ public static final int Unknown = 4;
+ }
+
+ public RequestLogEntry(long requestTime,
+ String requestId,
+ String requestData)
+ {
+ requestTime_ = requestTime;
+ requestId_ = requestId;
+ requestData_ = requestData;
+ }
+
+ public long getRequestTime()
+ {
+ return requestTime_;
+ }
+
+ public String getRequestId()
+ {
+ return requestId_;
+ }
+
+ public String getRequestData()
+ {
+ return requestData_;
+ }
+
+ public Long getResponseTime()
+ {
+ return responseTime_;
+ }
+
+ public String getResponseData()
+ {
+ return responseData_;
+ }
+
+ public void logResponse(int responseType, String data)
+ {
+ responseType_ = responseType;
+ responseTime_ = System.currentTimeMillis();
+ responseData_ = data;
+ }
+
+ public int getResponseType()
+ {
+ return responseType_;
+ }
+
+ public boolean isAlive()
+ {
+ return responseType_ == ResponseType.None;
+ }
+
+ public String getRequestMethodName()
+ {
+ if (requestData_.equals("[REDACTED]"))
+ return requestData_;
+
+ Pattern p = Pattern.create("\\\"method\\\":\\s*\\\"([^\"]+)\\\"");
+ Match match = p.match(requestData_, 0);
+ if (match == null)
+ return null;
+ return match.getGroup(1);
+ }
+
+ public RequestLogEntry clone()
+ {
+ RequestLogEntry clone = new RequestLogEntry(requestTime_,
+ requestId_,
+ requestData_);
+ clone.responseType_ = responseType_;
+ clone.responseData_ = responseData_;
+ clone.responseTime_ = responseTime_;
+ return clone;
+ }
+
+ public void toCsv(CsvWriter writer)
+ {
+ writer.writeValue(requestTime_ + "");
+ writer.writeValue(requestId_);
+ writer.writeValue(requestData_);
+ writer.writeValue(responseType_ + "");
+ if (responseType_ != ResponseType.None)
+ {
+ writer.writeValue(responseTime_.toString());
+ writer.writeValue(responseData_);
+ }
+ writer.endLine();
+ }
+
+ public static RequestLogEntry fromValues(String[] line)
+ {
+ if (line.length == 0 || (line.length == 1 && line[0].length() == 0))
+ return null;
+
+ long reqTime = Long.parseLong(line[0]);
+ String reqId = line[1];
+ String reqData = line[2];
+ int respType = Integer.parseInt(line[3]);
+ Long respTime = null;
+ String respData = null;
+ if (respType != ResponseType.None)
+ {
+ respTime = Long.parseLong(line[4]);
+ respData = line[5];
+ }
+ RequestLogEntry entry = new RequestLogEntry(reqTime, reqId, reqData);
+ entry.responseType_ = respType;
+ entry.responseTime_ = respTime;
+ entry.responseData_ = respData;
+ return entry;
+ }
+
+ private final long requestTime_;
+ private final String requestId_;
+ private final String requestData_;
+ private Long responseTime_;
+ private String responseData_;
+ private int responseType_ = ResponseType.None;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcError.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcError.java
new file mode 100644
index 0000000..43bcc72
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcError.java
@@ -0,0 +1,92 @@
+/*
+ * RpcError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.jsonrpc;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONValue;
+
+public class RpcError extends JavaScriptObject
+{
+ public static final native RpcError create(int code, String message) /*-{
+ var error = new Object();
+ error.code = code ;
+ error.message = message ;
+ return error ;
+ }-*/;
+
+ protected RpcError()
+ {
+ }
+
+ // no error
+ public final static int SUCCESS = 0 ;
+
+ // couldn't connect to service (method not executed)
+ public final static int CONNECTION_ERROR = 1 ;
+
+ // service is currently unavailable
+ public final static int UNAVAILABLE = 2;
+
+ // not authorized to access service or method (method not executed)
+ public final static int UNAUTHORIZED = 3 ;
+
+ // provided client id is invalid (method not executed)
+ public final static int INVALID_CLIENT_ID = 4;
+
+ // protocol errors (method not executed)
+ public final static int PARSE_ERROR = 5 ;
+ public final static int INVALID_REQUEST = 6 ;
+ public final static int METHOD_NOT_FOUND = 7 ;
+ public final static int PARAM_MISSING = 8 ;
+ public final static int PARAM_TYPE_MISMATCH = 9;
+ public final static int PARAM_INVALID = 10;
+ public final static int METHOD_UNEXEPECTED = 11 ;
+ public final static int INVALID_CLIENT_VERSION = 12;
+ public final static int SERVER_OFFLINE = 13;
+
+ // execution error (method was executed and returned known error state)
+ public final static int EXECUTION_ERROR = 100;
+
+ // transmission error (application state indeterminate)
+ public final static int TRANSMISSION_ERROR = 200;
+
+ public final native int getCode() /*-{
+ return this.code;
+ }-*/;
+
+ public final native String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+ public final native RpcUnderlyingError getError() /*-{
+ return this.error;
+ }-*/;
+
+ public final JSONValue getClientInfo()
+ {
+ return new JSONObject(this).get("client_info");
+ }
+
+ public final String getEndUserMessage()
+ {
+ RpcUnderlyingError underlyingError = getError();
+ if (underlyingError != null)
+ return underlyingError.getMessage();
+ else
+ return getMessage();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcObjectList.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcObjectList.java
new file mode 100644
index 0000000..d6b9579
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcObjectList.java
@@ -0,0 +1,50 @@
+/*
+ * RpcObjectList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.jsonrpc;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.ArrayList;
+
+public class RpcObjectList<T extends JavaScriptObject> extends JavaScriptObject
+{
+ protected RpcObjectList()
+ {
+ }
+
+ public final native int length() /*-{
+ for (key in this)
+ {
+ return this[key].length;
+ }
+ }-*/;
+
+ public final native T get(int index) /*-{
+ var el = {};
+ for (key in this)
+ {
+ el[key] = this[key][index];
+ }
+ return el;
+ }-*/;
+
+ public final ArrayList<T> toArrayList()
+ {
+ ArrayList<T> result = new ArrayList<T>(length());
+ for (int i = 0; i < length(); i++)
+ result.add(get(i));
+ return result;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequest.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequest.java
new file mode 100644
index 0000000..1b18cef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequest.java
@@ -0,0 +1,215 @@
+/*
+ * RpcRequest.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.jsonrpc;
+
+import com.google.gwt.http.client.*;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.user.client.Random;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry.ResponseType;
+
+// NOTE: RpcRequest is an immutable object (all fields are marked final).
+// this means that it is safe to re-submit an RpcRequest since the
+// re-submission will always be identical to the initial submission (useful
+// for retries after network or authentication errors)
+public class RpcRequest
+{
+ public static final boolean TRACE = false ;
+
+ public RpcRequest(String url,
+ String method,
+ JSONArray params,
+ JSONObject kwparams,
+ boolean redactLog,
+ String sourceWindow,
+ String clientId,
+ double clientVersion)
+ {
+ url_ = url;
+ method_ = method;
+ params_ = params ;
+ kwparams_ = kwparams;
+ redactLog_ = redactLog;
+ if (sourceWindow != null)
+ sourceWindow_ = new JSONString(sourceWindow);
+ else
+ sourceWindow_ = null;
+ if (clientId != null)
+ clientId_ = new JSONString(clientId);
+ else
+ clientId_ = null;
+ clientVersion_ = new JSONNumber(clientVersion);
+ }
+
+ public void send(RpcRequestCallback callback)
+ {
+ // final references for access from anonymous class
+ final RpcRequest enclosingRequest = this ;
+ final RpcRequestCallback requestCallback = callback ;
+
+ // build json request object
+ JSONObject request = new JSONObject() ;
+ request.put("method", new JSONString(method_)) ;
+ if ( params_ != null )
+ request.put("params", params_);
+ if ( kwparams_ != null)
+ request.put("kwparams", kwparams_);
+
+ // add src window if we have it
+ if (sourceWindow_ != null)
+ request.put("sourceWnd", sourceWindow_);
+
+ // add client id if we have it
+ if (clientId_ != null)
+ request.put("clientId", clientId_);
+
+ // add client version
+ request.put("version", clientVersion_);
+
+ // configure request builder
+ RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url_);
+ builder.setHeader("Content-Type", "application/json") ;
+ builder.setHeader("Accept", "application/json");
+ String requestId = Integer.toString(Random.nextInt());
+ builder.setHeader("X-RS-RID", requestId);
+
+ // send request
+ try
+ {
+ String requestString = request.toString();
+ if (TRACE)
+ Debug.log("Request: " + requestString) ;
+
+ requestLogEntry_ = RequestLog.log(requestId,
+ redactLog_ ? "[REDACTED]"
+ : requestString);
+
+ request_ = builder.sendRequest(requestString, new RequestCallback() {
+
+ public void onError(Request request, Throwable exception)
+ {
+ requestLogEntry_.logResponse(ResponseType.Error,
+ exception.getLocalizedMessage());
+ // ERROR: Request failed
+ RpcError error = RpcError.create(
+ RpcError.TRANSMISSION_ERROR,
+ exception.getLocalizedMessage());
+ requestCallback.onError(enclosingRequest, error) ;
+ }
+
+ public void onResponseReceived(Request request,
+ Response response)
+ {
+ // only accept 200 responses
+ int status = response.getStatusCode();
+ if ( status == 200 )
+ {
+ // attempt to parse the response
+ RpcResponse rpcResponse = null ;
+ try
+ {
+ String responseText = response.getText();
+ if (TRACE)
+ Debug.log("Response: " + responseText) ;
+ requestLogEntry_.logResponse(ResponseType.Normal,
+ responseText);
+ rpcResponse = RpcResponse.parse(responseText);
+
+ // response received and validated, process it!
+ requestCallback.onResponseReceived(enclosingRequest,
+ rpcResponse) ;
+ }
+ catch(Exception e)
+ {
+ // ERROR: Unable to parse JSON
+ RpcError error = RpcError.create(
+ RpcError.TRANSMISSION_ERROR,
+ e.getLocalizedMessage());
+ requestCallback.onError(enclosingRequest, error) ;
+ }
+ }
+ else
+ {
+ // ERROR: Non-200 response from server
+
+ // default error message
+ String message = "Status code " +
+ Integer.toString(status) +
+ " returned";
+
+ // override error message for status code 0
+ if (status == 0)
+ {
+ message = "Unable to establish connection with R session";
+ }
+
+
+ requestLogEntry_.logResponse(ResponseType.Unknown,
+ message);
+ RpcError error = RpcError.create(
+ RpcError.TRANSMISSION_ERROR,
+ message) ;
+ requestCallback.onError(enclosingRequest, error);
+ }
+ };
+ });
+ }
+ catch(RequestException e)
+ {
+ // ERROR: general request failure
+
+ String message = e.getLocalizedMessage();
+
+ if (requestLogEntry_ != null)
+ requestLogEntry_.logResponse(ResponseType.Unknown, message);
+
+ RpcError error = RpcError.create(RpcError.TRANSMISSION_ERROR,
+ message);
+ requestCallback.onError(enclosingRequest, error);
+ }
+ }
+
+ public void cancel()
+ {
+ if (request_ != null)
+ {
+ request_.cancel();
+ request_ = null;
+ }
+
+ if (requestLogEntry_ != null)
+ {
+ requestLogEntry_.logResponse(ResponseType.Cancelled, "Cancelled");
+ requestLogEntry_ = null;
+ }
+ }
+
+ final private String url_ ;
+ final private String method_ ;
+ final private JSONArray params_ ;
+ final private JSONObject kwparams_;
+ private final boolean redactLog_;
+ final private JSONString sourceWindow_;
+ final private JSONString clientId_;
+ final private JSONNumber clientVersion_;
+ private Request request_ = null;
+ private RequestLogEntry requestLogEntry_ = null;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequestCallback.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequestCallback.java
new file mode 100644
index 0000000..18672b0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcRequestCallback.java
@@ -0,0 +1,22 @@
+/*
+ * RpcRequestCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.jsonrpc;
+
+public interface RpcRequestCallback
+{
+ void onError(RpcRequest request, RpcError error) ;
+ void onResponseReceived(RpcRequest request, RpcResponse response);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponse.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponse.java
new file mode 100644
index 0000000..9500c18
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponse.java
@@ -0,0 +1,89 @@
+/*
+ * RpcResponse.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.jsonrpc;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.json.client.JSONParser;
+import com.google.gwt.json.client.JSONValue;
+
+public class RpcResponse extends JavaScriptObject
+{
+ protected RpcResponse()
+ {
+
+ }
+
+ public final static RpcResponse parse(String json)
+ {
+ try
+ {
+ // we first call parseStrict so we can use the browser
+ // json parser (for performance) whenever possible)
+ JSONValue val = JSONParser.parseStrict(json);
+ return val.isObject().getJavaScriptObject().cast();
+ }
+ catch(Exception e)
+ {
+ try
+ {
+ // there are some cases where json emitted by our
+ // server isn't parsable by parseStrict (for example,
+ // see bug #3025). for these situations we call
+ // parseLenient (which in turn calls eval)
+ JSONValue val = JSONParser.parseLenient(json);
+ return val.isObject().getJavaScriptObject().cast();
+ }
+ catch(Exception e2)
+ {
+ return null;
+ }
+ }
+ }
+
+ public final native static RpcResponse create(RpcError error) /*-{
+ var response = new Object();
+ response.error = error ;
+ return response ;
+ }-*/;
+
+ public final RpcError getError()
+ {
+ return getField("error");
+ }
+
+ public final String getAsyncHandle()
+ {
+ return getField("asyncHandle");
+ }
+
+ public final <T> T getResult()
+ {
+ T field = this.<T>getField("result");
+ return field;
+ }
+
+ private static Boolean wrapBoolean(boolean value)
+ {
+ return value;
+ }
+
+ public final native <T> T getField(String name) /*-{
+ var value = this[name];
+ if (typeof(value) == 'boolean')
+ return @org.rstudio.core.client.jsonrpc.RpcResponse::wrapBoolean(Z)(value);
+ return value;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponseHandler.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponseHandler.java
new file mode 100644
index 0000000..609d31b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcResponseHandler.java
@@ -0,0 +1,20 @@
+/*
+ * RpcResponseHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.jsonrpc;
+
+public abstract class RpcResponseHandler
+{
+ public abstract void onResponseReceived(RpcResponse response);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcUnderlyingError.java b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcUnderlyingError.java
new file mode 100644
index 0000000..8c0e19e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/jsonrpc/RpcUnderlyingError.java
@@ -0,0 +1,38 @@
+/*
+ * RpcUnderlyingError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.jsonrpc;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RpcUnderlyingError extends JavaScriptObject
+{
+ protected RpcUnderlyingError()
+ {
+ }
+
+ public final native int getCode() /*-{
+ return this.code;
+ }-*/;
+
+ public final native String getCategory() /*-{
+ return this.category;
+ }-*/;
+
+ public final native String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/AnimationHelper.java b/src/gwt/src/org/rstudio/core/client/layout/AnimationHelper.java
new file mode 100644
index 0000000..bb5f171
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/AnimationHelper.java
@@ -0,0 +1,214 @@
+/*
+ * AnimationHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.layout.client.Layout;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.theme.WindowFrame;
+
+import static org.rstudio.core.client.layout.WindowState.*;
+
+class AnimationHelper
+{
+ public static AnimationHelper create(BinarySplitLayoutPanel panel,
+ LogicalWindow top,
+ LogicalWindow bottom,
+ int normal,
+ int splitterHeight,
+ boolean animate)
+ {
+ boolean focusGoesOnTop = animate && focusGoesOnTop(top, bottom);
+
+ int splitterPos;
+ boolean splitterPosFromTop;
+ if (bottom.getState() == WindowState.NORMAL)
+ {
+ splitterPos = normal;
+ splitterPosFromTop = false;
+ }
+ else if (top.getState() == WindowState.HIDE)
+ {
+ splitterPos = -splitterHeight;
+ splitterPosFromTop = true;
+ }
+ else if (bottom.getState() == WindowState.HIDE)
+ {
+ splitterPos = -splitterHeight;
+ splitterPosFromTop = false;
+ }
+ else if (top.getState() == WindowState.MINIMIZE)
+ {
+ splitterPos = top.getMinimized().getDesiredHeight();
+ splitterPosFromTop = true;
+ }
+ else if (bottom.getState() == WindowState.MINIMIZE)
+ {
+ splitterPos = bottom.getMinimized().getDesiredHeight();
+ splitterPosFromTop = false;
+ }
+ else
+ {
+ throw new RuntimeException("Unexpected condition");
+ }
+
+ return new AnimationHelper(panel,
+ getVisible(top),
+ getVisible(bottom),
+ top.getNormal(),
+ bottom.getNormal(),
+ top.getActiveWidget(),
+ bottom.getActiveWidget(),
+ splitterPos,
+ splitterPosFromTop,
+ bottom.getState() == WindowState.NORMAL,
+ animate,
+ focusGoesOnTop);
+ }
+
+ private static Widget getVisible(LogicalWindow window)
+ {
+ return window.getNormal().isVisible() ? window.getNormal() :
+ window.getMinimized().isVisible() ? window.getMinimized() :
+ null;
+ }
+
+ public AnimationHelper(BinarySplitLayoutPanel panel,
+ Widget startWidgetTop,
+ Widget startWidgetBottom,
+ Widget animWidgetTop,
+ Widget animWidgetBottom,
+ Widget endWidgetTop,
+ Widget endWidgetBottom,
+ int endSplitterPos,
+ boolean splitterPosFromTop,
+ boolean splitterVisible,
+ boolean animate,
+ boolean focusGoesOnTop)
+ {
+ panel_ = panel;
+ startWidgetTop_ = startWidgetTop;
+ startWidgetBottom_ = startWidgetBottom;
+ animWidgetTop_ = animWidgetTop;
+ animWidgetBottom_ = animWidgetBottom;
+ endWidgetTop_ = endWidgetTop;
+ endWidgetBottom_ = endWidgetBottom;
+ endSplitterPos_ = endSplitterPos;
+ splitterPosFromTop_ = splitterPosFromTop;
+ splitterVisible_ = splitterVisible;
+ animate_ = animate;
+ focusGoesOnTop_ = focusGoesOnTop;
+ }
+
+ public void animate()
+ {
+ panel_.setSplitterVisible(false);
+
+ if (startWidgetTop_ != animWidgetTop_)
+ panel_.setTopWidget(animWidgetTop_, true);
+ if (startWidgetBottom_ != animWidgetBottom_)
+ panel_.setBottomWidget(animWidgetBottom_, true);
+
+ panel_.forceLayout();
+
+ panel_.setSplitterPos(endSplitterPos_,
+ splitterPosFromTop_);
+ if (animate_)
+ {
+ panel_.animate(250, new Layout.AnimationCallback()
+ {
+ public void onAnimationComplete()
+ {
+ finish();
+ ((WindowFrame)(focusGoesOnTop_
+ ? endWidgetTop_
+ : endWidgetBottom_)).focus();
+ }
+
+ public void onLayout(Layout.Layer layer, double progress)
+ {
+ }
+ });
+ }
+ else
+ {
+ finish();
+ }
+ }
+
+ private void finish()
+ {
+ panel_.setSplitterVisible(splitterVisible_);
+
+ if (animWidgetTop_ != endWidgetTop_)
+ panel_.setTopWidget(endWidgetTop_, true);
+ if (animWidgetBottom_ != endWidgetBottom_)
+ panel_.setBottomWidget(endWidgetBottom_, true);
+
+ if (endWidgetTop_ != startWidgetTop_)
+ setParentZindex(startWidgetTop_, -10);
+ setParentZindex(endWidgetTop_, 0);
+
+ if (endWidgetBottom_ != startWidgetBottom_)
+ setParentZindex(startWidgetBottom_, -10);
+ setParentZindex(endWidgetBottom_, 0);
+
+ panel_.onResize();
+ }
+
+ private static boolean focusGoesOnTop(LogicalWindow top, LogicalWindow bottom)
+ {
+ // If one window is maximized and the other is minimized, focus the
+ // maximized one.
+ // If both windows are "normal", focus the one that was previously
+ // minimized.
+
+ if (top.getState() == MAXIMIZE || bottom.getState() == MAXIMIZE ||
+ top.getState() == EXCLUSIVE || bottom.getState() == EXCLUSIVE)
+ {
+ assert top.getState() == MINIMIZE || bottom.getState() == MINIMIZE
+ || top.getState() == HIDE || bottom.getState() == HIDE;
+ // If one of the windows is minimized, focus the other one.
+ return top.getState() == MAXIMIZE || top.getState() == EXCLUSIVE;
+ }
+
+ assert top.getState() == NORMAL && bottom.getState() == NORMAL;
+ assert top.getNormal().isVisible() || bottom.getNormal().isVisible();
+
+ return !top.getNormal().isVisible();
+ }
+
+ public static void setParentZindex(Widget widget, int zIndex)
+ {
+ if (widget != null)
+ widget.getElement().getParentElement().getStyle().setZIndex(zIndex);
+ }
+
+ BinarySplitLayoutPanel panel_;
+
+ Widget startWidgetTop_;
+ Widget startWidgetBottom_;
+
+ Widget animWidgetTop_;
+ Widget animWidgetBottom_;
+
+ Widget endWidgetTop_;
+ Widget endWidgetBottom_;
+
+ int endSplitterPos_;
+ boolean splitterPosFromTop_;
+ boolean splitterVisible_;
+ private final boolean animate_;
+ private final boolean focusGoesOnTop_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/BinarySplitLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/layout/BinarySplitLayoutPanel.java
new file mode 100644
index 0000000..369c9ad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/BinarySplitLayoutPanel.java
@@ -0,0 +1,337 @@
+/*
+ * BinarySplitLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.layout.client.Layout;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.*;
+
+public class BinarySplitLayoutPanel extends LayoutPanel
+ implements MouseDownHandler, MouseMoveHandler, MouseUpHandler
+{
+ public BinarySplitLayoutPanel(Widget[] widgets, int splitterHeight)
+ {
+ widgets_ = widgets;
+ splitterHeight_ = splitterHeight;
+
+ setWidgets(widgets);
+
+ splitterPos_ = 300;
+ topIsFixed_ = false;
+ splitter_ = new HTML();
+ splitter_.setStylePrimaryName("gwt-SplitLayoutPanel-VDragger");
+ splitter_.addMouseDownHandler(this);
+ splitter_.addMouseMoveHandler(this);
+ splitter_.addMouseUpHandler(this);
+ splitter_.getElement().getStyle().setZIndex(200);
+ add(splitter_);
+ setWidgetLeftRight(splitter_, 0, Style.Unit.PX, 0, Style.Unit.PX);
+ setWidgetBottomHeight(splitter_,
+ splitterPos_, Style.Unit.PX,
+ splitterHeight_, Style.Unit.PX);
+ }
+
+ public void setWidgets(Widget[] widgets)
+ {
+ for (Widget w : widgets_)
+ remove(w);
+
+ widgets_ = widgets;
+ for (Widget w : widgets)
+ {
+ add(w);
+ setWidgetLeftRight(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
+ setWidgetTopHeight(w, 0, Style.Unit.PX, 100, Style.Unit.PX);
+ w.setVisible(false);
+ AnimationHelper.setParentZindex(w, -10);
+ }
+
+ if (top_ >= 0)
+ setTopWidget(top_, true);
+ if (bottom_ >= 0)
+ setBottomWidget(bottom_, true);
+ }
+
+ @Override
+ protected void onAttach()
+ {
+ super.onAttach();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ offsetHeight_ = getOffsetHeight();
+ }
+ });
+ }
+
+ public HandlerRegistration addSplitterBeforeResizeHandler(
+ SplitterBeforeResizeHandler handler)
+ {
+ return addHandler(handler, SplitterBeforeResizeEvent.TYPE);
+ }
+
+ public HandlerRegistration addSplitterResizedHandler(
+ SplitterResizedHandler handler)
+ {
+ return addHandler(handler, SplitterResizedEvent.TYPE);
+ }
+
+ public void setTopWidget(Widget widget, boolean manageVisibility)
+ {
+ if (widget == null)
+ {
+ setTopWidget(-1, manageVisibility);
+ return;
+ }
+
+ for (int i = 0; i < widgets_.length; i++)
+ if (widgets_[i] == widget)
+ {
+ setTopWidget(i, manageVisibility);
+ return;
+ }
+
+ assert false;
+ }
+
+ public void setTopWidget(int widgetIndex, boolean manageVisibility)
+ {
+ if (manageVisibility && top_ >= 0)
+ widgets_[top_].setVisible(false);
+
+ top_ = widgetIndex;
+ if (bottom_ == top_)
+ setBottomWidget(-1, manageVisibility);
+
+ if (manageVisibility && top_ >= 0)
+ widgets_[top_].setVisible(true);
+
+ updateLayout();
+ }
+
+ public void setBottomWidget(Widget widget, boolean manageVisibility)
+ {
+ if (widget == null)
+ {
+ setBottomWidget(-1, manageVisibility);
+ return;
+ }
+
+ for (int i = 0; i < widgets_.length; i++)
+ if (widgets_[i] == widget)
+ {
+ setBottomWidget(i, manageVisibility);
+ return;
+ }
+
+ assert false;
+ }
+
+ public void setBottomWidget(int widgetIndex, boolean manageVisibility)
+ {
+ if (manageVisibility && bottom_ >= 0)
+ widgets_[bottom_].setVisible(false);
+
+ bottom_ = widgetIndex;
+ if (top_ == bottom_)
+ setTopWidget(-1, manageVisibility);
+
+ if (manageVisibility && bottom_ >= 0)
+ widgets_[bottom_].setVisible(true);
+
+ updateLayout();
+ }
+
+ public boolean isSplitterVisible()
+ {
+ return splitter_.isVisible();
+ }
+
+ public void setSplitterVisible(boolean visible)
+ {
+ splitter_.setVisible(visible);
+ }
+
+ public void setSplitterPos(int splitterPos, boolean fromTop)
+ {
+ if (isVisible() && isAttached() && splitter_.isVisible())
+ {
+ splitterPos = Math.min(getOffsetHeight() - splitterHeight_,
+ splitterPos);
+ }
+
+ if (splitter_.isVisible())
+ splitterPos = Math.max(splitterHeight_, splitterPos);
+
+ if (splitterPos_ == splitterPos
+ && topIsFixed_ == fromTop
+ && offsetHeight_ == getOffsetHeight())
+ {
+ return;
+ }
+
+ splitterPos_ = splitterPos;
+ topIsFixed_ = fromTop;
+ offsetHeight_ = getOffsetHeight();
+ if (topIsFixed_)
+ {
+ setWidgetTopHeight(splitter_,
+ splitterPos_,
+ Style.Unit.PX,
+ splitterHeight_,
+ Style.Unit.PX);
+ }
+ else
+ {
+ setWidgetBottomHeight(splitter_,
+ splitterPos_,
+ Style.Unit.PX,
+ splitterHeight_,
+ Style.Unit.PX);
+ }
+
+ updateLayout();
+ }
+
+ public int getSplitterBottom()
+ {
+ assert !topIsFixed_;
+ return splitterPos_;
+ }
+
+ private void updateLayout()
+ {
+ if (topIsFixed_)
+ {
+ if (top_ >= 0)
+ setWidgetTopHeight(widgets_[top_],
+ 0,
+ Style.Unit.PX,
+ splitterPos_,
+ Style.Unit.PX);
+
+ if (bottom_ >= 0)
+ setWidgetTopBottom(widgets_[bottom_],
+ splitterPos_ + splitterHeight_,
+ Style.Unit.PX,
+ 0,
+ Style.Unit.PX);
+ }
+ else
+ {
+ if (top_ >= 0)
+ setWidgetTopBottom(widgets_[top_],
+ 0,
+ Style.Unit.PX,
+ splitterPos_ + splitterHeight_,
+ Style.Unit.PX);
+
+ if (bottom_ >= 0)
+ setWidgetBottomHeight(widgets_[bottom_],
+ 0,
+ Style.Unit.PX,
+ splitterPos_,
+ Style.Unit.PX);
+ }
+
+ // Not sure why, but onResize() doesn't seem to get called unless we
+ // do this manually. This matters for ShellPane scroll position updating.
+ animate(0, new Layout.AnimationCallback()
+ {
+ public void onAnimationComplete()
+ {
+ onResize();
+ }
+
+ public void onLayout(Layout.Layer layer, double progress)
+ {
+ }
+ });
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+ // getOffsetHeight() > 0 is to deal with Firefox tab tear-off, which
+ // causes us to be resized to 0 (bug 1586)
+ if (offsetHeight_ > 0 && splitter_.isVisible() && getOffsetHeight() > 0)
+ {
+ double pct = ((double)splitterPos_ / offsetHeight_);
+ int newPos = (int) Math.round(getOffsetHeight() * pct);
+ setSplitterPos(newPos, topIsFixed_);
+ }
+ }
+
+ public void onMouseDown(MouseDownEvent event)
+ {
+ resizing_ = true;
+ Event.setCapture(splitter_.getElement());
+ event.preventDefault();
+ event.stopPropagation();
+ fireEvent(new SplitterBeforeResizeEvent());
+ }
+
+ public void onMouseMove(MouseMoveEvent event)
+ {
+ if (event.getNativeButton() == 0)
+ resizing_ = false;
+
+ if (!resizing_)
+ return;
+
+ event.preventDefault();
+ event.stopPropagation();
+ if (topIsFixed_)
+ setSplitterPos(event.getRelativeY(getElement()), true);
+ else
+ setSplitterPos(getOffsetHeight() - event.getRelativeY(getElement()),
+ false);
+ }
+
+ public void onMouseUp(MouseUpEvent event)
+ {
+ if (resizing_)
+ {
+ resizing_ = false;
+ Event.releaseCapture(splitter_.getElement());
+ fireEvent(new SplitterResizedEvent());
+ }
+ }
+
+ public int getSplitterHeight()
+ {
+ return splitterHeight_;
+ }
+
+ private int top_;
+ private int bottom_;
+
+ private HTML splitter_;
+ private int splitterPos_;
+ private int splitterHeight_;
+ // If true, then bottom widget should scale and top widget should stay
+ // fixed. If false, then vice versa.
+ private boolean topIsFixed_ = true;
+ private Widget[] widgets_;
+ private boolean resizing_;
+ private int offsetHeight_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/DelayFadeInHelper.java b/src/gwt/src/org/rstudio/core/client/layout/DelayFadeInHelper.java
new file mode 100644
index 0000000..843fae5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/DelayFadeInHelper.java
@@ -0,0 +1,73 @@
+/*
+ * DelayFadeInHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Widget;
+
+public class DelayFadeInHelper
+{
+ public DelayFadeInHelper(Widget widget)
+ {
+ widget_ = widget;
+ }
+
+ public void beginShow()
+ {
+ hide();
+
+ final Object nonce = new Object();
+ nonce_ = nonce;
+ new Timer()
+ {
+ @Override
+ public void run()
+ {
+ if (nonce_ == nonce)
+ {
+ animation_ = new FadeInAnimation(
+ widget_, 1, null);
+ animation_.run(250);
+ }
+ }
+ }.schedule(750);
+ }
+
+ public void hide()
+ {
+ stopPending();
+ widget_.setVisible(false);
+ // jcheng: The next line shouldn't be necessary since we just set visible
+ // to false, but there was an annoying bug where it seemed the Stop
+ // button's visibility was being set to true when the Compile PDF panel is
+ // introduced. For some reason opacity is not affected, so this fixes it.
+ widget_.getElement().getStyle().setOpacity(0.0);
+ }
+
+ private void stopPending()
+ {
+ nonce_ = null;
+ if (animation_ != null)
+ {
+ animation_.cancel();
+ animation_ = null;
+ }
+ }
+
+ private Object nonce_;
+ private FadeInAnimation animation_;
+
+ private final Widget widget_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/DualWindowLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/layout/DualWindowLayoutPanel.java
new file mode 100644
index 0000000..2c056ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/DualWindowLayoutPanel.java
@@ -0,0 +1,629 @@
+/*
+ * DualWindowLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.events.EnsureHeightEvent;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.events.WindowStateChangeHandler;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+
+import static org.rstudio.core.client.layout.WindowState.*;
+
+
+/**
+ * This class implements the minimizing/maximizing behavior between two
+ * window frames.
+ */
+public class DualWindowLayoutPanel extends SimplePanel
+ implements ProvidesResize,
+ RequiresResize
+{
+ private static class NormalHeight
+ {
+ public NormalHeight(int height,
+ Integer containerHeight,
+ Integer windowHeight)
+ {
+ height_ = height;
+ containerHeight_ = containerHeight;
+ windowHeight_ = windowHeight;
+ }
+
+ public int getHeight()
+ {
+ return height_;
+ }
+
+ public int getContainerHeight(int defaultValue)
+ {
+ assert defaultValue > 0;
+ if (containerHeight_ == null)
+ containerHeight_ = defaultValue;
+ return containerHeight_.intValue();
+ }
+
+ public int getWindowHeight(int defaultValue)
+ {
+ assert defaultValue > 0;
+ if (windowHeight_ == null)
+ windowHeight_ = defaultValue;
+ return windowHeight_.intValue();
+ }
+
+ public int getHeightScaledTo(int containerHeight)
+ {
+ if (containerHeight_ == null
+ || containerHeight_.intValue() == containerHeight
+ || containerHeight <= 0)
+ {
+ return height_;
+ }
+
+ double pct = (double)containerHeight / containerHeight_.intValue();
+ return (int)(pct * height_);
+ }
+
+ private int height_;
+ private Integer containerHeight_;
+ private Integer windowHeight_;
+ }
+
+ private class WindowStateChangeManager
+ implements WindowStateChangeHandler
+ {
+ public WindowStateChangeManager(Session session)
+ {
+ session_ = session;
+ }
+
+ public void onWindowStateChange(WindowStateChangeEvent event)
+ {
+ switch (event.getNewState())
+ {
+ case EXCLUSIVE:
+ windowA_.transitionToState(EXCLUSIVE);
+ windowB_.transitionToState(HIDE);
+ layout(windowA_, windowB_);
+ break;
+ case MAXIMIZE:
+ windowA_.transitionToState(MAXIMIZE);
+ windowB_.transitionToState(MINIMIZE);
+ layout(windowA_, windowB_);
+ break;
+ case MINIMIZE:
+ windowA_.transitionToState(MINIMIZE);
+ windowB_.transitionToState(MAXIMIZE);
+ layout(windowA_, windowB_);
+ break;
+ case NORMAL:
+ windowA_.transitionToState(NORMAL);
+ windowB_.transitionToState(NORMAL);
+ layout(windowA_, windowB_);
+ break;
+ case HIDE:
+ windowA_.transitionToState(HIDE);
+ windowB_.transitionToState(EXCLUSIVE);
+ layout(windowA_, windowB_);
+ break;
+ }
+
+ // Defer this because layout changes are deferred by LayoutPanel.
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ session_.persistClientState();
+ }
+ });
+ }
+
+ private final Session session_;
+ }
+
+ private static class State extends JavaScriptObject
+ {
+ protected State() {}
+
+ public native final boolean hasSplitterPos() /*-{
+ return typeof(this.splitterpos) != 'undefined';
+ }-*/;
+
+ public native final int getSplitterPos() /*-{
+ return this.splitterpos;
+ }-*/;
+
+ public native final void setSplitterPos(int pos) /*-{
+ this.splitterpos = pos;
+ }-*/;
+
+ public native final String getTopWindowState() /*-{
+ return this.topwindowstate;
+ }-*/;
+
+ public native final void setTopWindowState(String state) /*-{
+ this.topwindowstate = state;
+ }-*/;
+
+ public native final boolean hasPanelHeight() /*-{
+ return typeof(this.panelheight) != 'undefined';
+ }-*/;
+
+ public native final int getPanelHeight() /*-{
+ return this.panelheight;
+ }-*/;
+
+ public native final void setPanelHeight(int height) /*-{
+ this.panelheight = height;
+ }-*/;
+
+ public native final boolean hasWindowHeight() /*-{
+ return typeof(this.windowheight) != 'undefined';
+ }-*/;
+
+ public native final int getWindowHeight() /*-{
+ return this.windowheight;
+ }-*/;
+
+ public native final void setWindowHeight(int height) /*-{
+ this.windowheight = height;
+ }-*/;
+
+ public static boolean equals(State a, State b)
+ {
+ if (a == null ^ b == null)
+ return false;
+ if (a == null)
+ return true;
+
+ if (a.hasSplitterPos() ^ b.hasSplitterPos())
+ return false;
+ if (a.hasSplitterPos() && a.getSplitterPos() != b.getSplitterPos())
+ return false;
+
+ if (a.hasPanelHeight() ^ b.hasPanelHeight())
+ return false;
+ if (a.hasPanelHeight() && a.getPanelHeight() != b.getPanelHeight())
+ return false;
+
+ if (a.hasWindowHeight() ^ b.hasWindowHeight())
+ return false;
+ if (a.hasWindowHeight() && a.getWindowHeight() != b.getWindowHeight())
+ return false;
+
+ if (a.getTopWindowState() == null ^ b.getTopWindowState() == null)
+ return false;
+ if (a.getTopWindowState() != null
+ && !a.getTopWindowState().equals(b.getTopWindowState()))
+ return false;
+
+ return true;
+ }
+ }
+
+ /**
+ * Helper class to make the minimized/maximized state and splitter
+ * position persist across browser sessions.
+ */
+ private class WindowLayoutStateValue extends JSObjectStateValue
+ {
+ public WindowLayoutStateValue(ClientInitState clientState,
+ String clientStateKeyName,
+ WindowState topWindowDefaultState,
+ int defaultSplitterPos)
+ {
+ super("windowlayoutstate",
+ clientStateKeyName,
+ ClientState.PROJECT_PERSISTENT,
+ clientState,
+ true);
+ topWindowDefaultState_ = topWindowDefaultState;
+ defaultSplitterPos_ = defaultSplitterPos;
+
+ finishInit(clientState);
+ }
+
+ @Override
+ protected void onInit(JsObject value)
+ {
+ normalHeight_ = new NormalHeight(defaultSplitterPos_, null, null);
+ WindowState topWindowState = topWindowDefaultState_;
+
+ try
+ {
+ if (value != null)
+ {
+ State state = value.cast();
+ if (state.hasSplitterPos())
+ {
+ // This logic is a little tortured. At startup time, we don't
+ // have the height of this panel (getOffsetHeight()) since it
+ // isn't attached to the document yet. But if we wait until
+ // we have the height to restore the size, then the user would
+ // see some jumpiness in the UI.
+
+ // So instead we persist both the panel height and window
+ // height at save time, and assume that the difference between
+ // the two will remain the same, and use the new window height
+ // at load time to work backward to the new offset height.
+ // That's probably not a great assumption, it would be better
+ // to have a priori knowledge of the height of the panel. But
+ // given the low severity of the number being slightly off,
+ // this seems fine for the foreseeable future.
+
+ if (state.hasWindowHeight() && state.hasPanelHeight() &&
+ state.getWindowHeight() != Window.getClientHeight())
+ {
+ int deltaY = state.getWindowHeight() - state.getPanelHeight();
+ int newPanelHeight = Window.getClientHeight() - deltaY;
+ // Use percentage value
+ double pct = (double) state.getSplitterPos()
+ / state.getPanelHeight();
+ normalHeight_ = new NormalHeight(
+ (int)(pct * newPanelHeight),
+ newPanelHeight,
+ Window.getClientHeight());
+ }
+ else
+ {
+ // Use absolute value
+ normalHeight_ = new NormalHeight(
+ state.getSplitterPos(),
+ state.hasPanelHeight() ? state.getPanelHeight()
+ : null,
+ state.hasWindowHeight() ? state.getWindowHeight()
+ : null);
+ }
+ }
+ if (state.getTopWindowState() != null)
+ topWindowState = WindowState.valueOf(state.getTopWindowState());
+
+ lastKnownValue_ = state;
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.log("Error restoring dual window state: " + e.toString());
+ }
+
+ windowA_.onWindowStateChange(
+ new WindowStateChangeEvent(topWindowState));
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ if (layout_.isSplitterVisible())
+ {
+ normalHeight_ = new NormalHeight(layout_.getSplitterBottom(),
+ layout_.getOffsetHeight(),
+ Window.getClientHeight());
+ }
+
+ State state = JsObject.createJsObject().cast();
+ state.setSplitterPos(normalHeight_.getHeight());
+ state.setTopWindowState(windowA_.getState().toString());
+ state.setPanelHeight(normalHeight_.getContainerHeight(getOffsetHeight()));
+ state.setWindowHeight(normalHeight_.getWindowHeight(Window.getClientHeight()));
+ return state.cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ State state = getValue().cast();
+
+ if (state.getSplitterPos() > state.getPanelHeight()
+ || state.getSplitterPos() < 0)
+ {
+ Debug.log("Invalid splitter position detected: "
+ + state.getSplitterPos() + "/" + state.getPanelHeight());
+ return false;
+ }
+
+ if (!State.equals(lastKnownValue_, state))
+ {
+ lastKnownValue_ = state;
+ return true;
+ }
+ return false;
+ }
+
+ private State lastKnownValue_;
+ private final WindowState topWindowDefaultState_;
+ private final int defaultSplitterPos_;
+ }
+
+ public DualWindowLayoutPanel(final EventBus eventBus,
+ final LogicalWindow windowA,
+ final LogicalWindow windowB,
+ Session session,
+ String clientStateKeyName,
+ final WindowState topWindowDefaultState,
+ final int defaultSplitterPos)
+ {
+ windowA_ = windowA;
+ windowB_ = windowB;
+ session_ = session;
+ setSize("100%", "100%");
+ layout_ = new BinarySplitLayoutPanel(new Widget[] {
+ windowA.getNormal(), windowA.getMinimized(),
+ windowB.getNormal(), windowB.getMinimized()}, 3);
+ layout_.setSize("100%", "100%");
+
+ topWindowStateChangeManager_ = new WindowStateChangeManager(session);
+ bottomWindowStateChangeManager_ = new WindowStateChangeHandler()
+ {
+ public void onWindowStateChange(WindowStateChangeEvent event)
+ {
+ WindowState topState;
+ switch (event.getNewState())
+ {
+ case NORMAL:
+ topState = NORMAL;
+ break;
+ case MAXIMIZE:
+ topState = MINIMIZE;
+ break;
+ case MINIMIZE:
+ topState = MAXIMIZE;
+ break;
+ case HIDE:
+ topState = EXCLUSIVE;
+ break;
+ case EXCLUSIVE:
+ topState = HIDE;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown WindowState " + event.getNewState());
+ }
+ windowA_.onWindowStateChange(
+ new WindowStateChangeEvent(topState));
+ }
+ };
+
+ hookEvents();
+
+ new WindowLayoutStateValue(session.getSessionInfo().getClientState(),
+ clientStateKeyName,
+ topWindowDefaultState,
+ defaultSplitterPos);
+
+ setWidget(layout_);
+
+ if (eventBus != null)
+ {
+ layout_.addSplitterBeforeResizeHandler(new SplitterBeforeResizeHandler()
+ {
+ public void onSplitterBeforeResize(SplitterBeforeResizeEvent event)
+ {
+ // If the splitter ends up causing a minimize operation, then
+ // we'll need to have saved the normal height for when the
+ // user decides to restore the panel.
+ snapMinimizeNormalHeight_ = new NormalHeight(
+ layout_.getSplitterBottom(),
+ layout_.getOffsetHeight(),
+ Window.getClientHeight());
+
+ eventBus.fireEvent(new GlassVisibilityEvent(true));
+ }
+ });
+ layout_.addSplitterResizedHandler(new SplitterResizedHandler()
+ {
+ public void onSplitterResized(SplitterResizedEvent event)
+ {
+ WindowState topState = resizePanes(layout_.getSplitterBottom());
+
+ // we're already in normal if the splitter is being invoked
+ if (topState != WindowState.NORMAL)
+ {
+ topWindowStateChangeManager_.onWindowStateChange(
+ new WindowStateChangeEvent(topState));
+ }
+
+ eventBus.fireEvent(new GlassVisibilityEvent(false));
+ }
+ });
+ }
+ }
+
+ // resize the panes based on the specified bottom height and return the
+ // new window state for the top pane (this implements snap to minimize)
+ private WindowState resizePanes(int bottom)
+ {
+ WindowState topState = null;
+
+ int height = layout_.getOffsetHeight();
+
+ // If the height of upper or lower panel is smaller than this
+ // then that panel will minimize
+ final int MIN_HEIGHT = 60;
+
+ if (bottom < MIN_HEIGHT)
+ {
+ topState = WindowState.MAXIMIZE;
+ normalHeight_ = snapMinimizeNormalHeight_;
+ }
+ else if (bottom >= height - MIN_HEIGHT)
+ {
+ topState = WindowState.MINIMIZE;
+ normalHeight_ = snapMinimizeNormalHeight_;
+ }
+ else
+ {
+ topState = WindowState.NORMAL;
+ normalHeight_ = new NormalHeight(bottom,
+ height,
+ Window.getClientHeight());
+ }
+
+ session_.persistClientState();
+
+ return topState;
+ }
+
+
+
+ private void hookEvents()
+ {
+ registrations_.add(
+ windowA_.addWindowStateChangeHandler(topWindowStateChangeManager_));
+ registrations_.add(
+ windowB_.addWindowStateChangeHandler(bottomWindowStateChangeManager_));
+ registrations_.add(
+ windowA_.addEnsureHeightHandler(new EnsureHeightChangeManager(true)));
+ registrations_.add(
+ windowB_.addEnsureHeightHandler(new EnsureHeightChangeManager(false)));
+ }
+
+ private void unhookEvents()
+ {
+ registrations_.removeHandler();
+ }
+
+ public void replaceWindows(LogicalWindow windowA,
+ LogicalWindow windowB)
+ {
+ unhookEvents();
+ windowA_ = windowA;
+ windowB_ = windowB;
+ hookEvents();
+
+ layout_.setWidgets(new Widget[] {
+ windowA_.getNormal(), windowA_.getMinimized(),
+ windowB_.getNormal(), windowB_.getMinimized() });
+
+ Scheduler.get().scheduleFinally(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ windowA_.onWindowStateChange(new WindowStateChangeEvent(NORMAL));
+ }
+ });
+ }
+
+ public void onResize()
+ {
+ if (layout_ != null)
+ {
+ layout_.onResize();
+ }
+ }
+
+ private void layout(final LogicalWindow top,
+ final LogicalWindow bottom)
+ {
+ AnimationHelper.create(layout_,
+ top,
+ bottom,
+ normalHeight_.getHeightScaledTo(getOffsetHeight()),
+ layout_.getSplitterHeight(),
+ isVisible() && isAttached()).animate();
+ }
+
+ public void setTopWindowState(WindowState state)
+ {
+ topWindowStateChangeManager_.onWindowStateChange(
+ new WindowStateChangeEvent(state));
+ }
+
+ private class EnsureHeightChangeManager implements EnsureHeightHandler
+ {
+ public EnsureHeightChangeManager(boolean isTopWindow)
+ {
+ isTopWindow_ = isTopWindow;
+ }
+
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ // constants
+ final int FRAME = 52;
+ final int MINIMUM = 160;
+
+ // get the target window and target height
+ LogicalWindow targetWindow = isTopWindow_ ? windowA_ : windowB_;
+ int targetHeight = event.getHeight() + FRAME;
+
+ // ignore if we are already maximized
+ if (targetWindow.getState() == WindowState.MAXIMIZE)
+ return;
+
+ // ignore if we are already high enough
+ if (targetWindow.getActiveWidget().getOffsetHeight() >= targetHeight)
+ return;
+
+ // calculate height of other pane
+ int aHeight = windowA_.getActiveWidget().getOffsetHeight();
+ int bHeight = windowB_.getActiveWidget().getOffsetHeight();
+ int chromeHeight = layout_.getOffsetHeight() - aHeight - bHeight;
+ int otherHeight = layout_.getOffsetHeight() -
+ chromeHeight -
+ targetHeight;
+
+ // see if we need to offset to acheive minimum other height
+ int offset = 0;
+ if (otherHeight < MINIMUM)
+ offset = MINIMUM - otherHeight;
+
+ // determine the height (only the bottom can be sizes explicitly
+ // so for the top we need to derive it's height from the implied
+ // bottom height that we already computed)
+ int height = isTopWindow_ ? (otherHeight + offset) :
+ (targetHeight - offset);
+
+ // ignore if this will reduce our size
+ if (height <= targetWindow.getActiveWidget().getOffsetHeight())
+ return;
+
+ // resize bottom
+ WindowState topState = resizePanes(height);
+
+ if (topState != null)
+ {
+ topWindowStateChangeManager_.onWindowStateChange(
+ new WindowStateChangeEvent(topState));
+ }
+ }
+
+ private boolean isTopWindow_;
+
+ }
+
+ private BinarySplitLayoutPanel layout_;
+ private NormalHeight normalHeight_;
+ private LogicalWindow windowA_;
+ private LogicalWindow windowB_;
+ private final Session session_;
+ private WindowStateChangeManager topWindowStateChangeManager_;
+ private WindowStateChangeHandler bottomWindowStateChangeManager_;
+ private HandlerRegistrations registrations_ = new HandlerRegistrations();
+
+ private NormalHeight snapMinimizeNormalHeight_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/FadeInAnimation.java b/src/gwt/src/org/rstudio/core/client/layout/FadeInAnimation.java
new file mode 100644
index 0000000..0937f50
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/FadeInAnimation.java
@@ -0,0 +1,72 @@
+/*
+ * FadeInAnimation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+
+public class FadeInAnimation extends Animation
+{
+ public FadeInAnimation(Widget widget,
+ double targetOpacity,
+ Command callback)
+ {
+ this(new ArrayList<Widget>(), targetOpacity, callback);
+ widgets_.add(widget);
+ }
+
+ public FadeInAnimation(ArrayList<Widget> widgets,
+ double targetOpacity,
+ Command callback)
+ {
+ this.widgets_ = widgets;
+ targetOpacity_ = targetOpacity;
+ callback_ = callback;
+ }
+
+ @Override
+ protected void onStart()
+ {
+ for (Widget w : widgets_)
+ w.getElement().getStyle().setDisplay(Style.Display.BLOCK);
+ super.onStart();
+ }
+
+ @Override
+ protected void onUpdate(double progress)
+ {
+ for (Widget w : widgets_)
+ w.getElement().getStyle().setOpacity(targetOpacity_ * progress);
+ }
+
+ @Override
+ protected void onComplete()
+ {
+ for (Widget w : widgets_)
+ {
+ w.getElement().getStyle().setOpacity(targetOpacity_);
+ }
+ if (callback_ != null)
+ callback_.execute();
+ }
+
+ private ArrayList<Widget> widgets_;
+ private final double targetOpacity_;
+ private final Command callback_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/FadeOutAnimation.java b/src/gwt/src/org/rstudio/core/client/layout/FadeOutAnimation.java
new file mode 100644
index 0000000..46f85d3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/FadeOutAnimation.java
@@ -0,0 +1,54 @@
+/*
+ * FadeOutAnimation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+
+public class FadeOutAnimation extends Animation
+{
+ public FadeOutAnimation(ArrayList<Widget> widgets, Command callback)
+ {
+ this.widgets_ = widgets;
+ callback_ = callback;
+ }
+
+ @Override
+ protected void onUpdate(double progress)
+ {
+ for (Widget w : widgets_)
+ w.getElement().getStyle().setOpacity(1.0-progress);
+ }
+
+ @Override
+ protected void onComplete()
+ {
+ for (Widget w : widgets_)
+ {
+ Style style = w.getElement().getStyle();
+ style.setDisplay(Style.Display.NONE);
+ style.setOpacity(1.0);
+ }
+ if (callback_ != null)
+ callback_.execute();
+ }
+
+ private ArrayList<Widget> widgets_;
+ private final Command callback_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/LogicalWindow.java b/src/gwt/src/org/rstudio/core/client/layout/LogicalWindow.java
new file mode 100644
index 0000000..8d63724
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/LogicalWindow.java
@@ -0,0 +1,146 @@
+/*
+ * LogicalWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.rstudio.core.client.events.EnsureHeightEvent;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.HasWindowStateChangeHandlers;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.events.WindowStateChangeHandler;
+import org.rstudio.core.client.theme.MinimizedWindowFrame;
+import org.rstudio.core.client.theme.WindowFrame;
+
+import static org.rstudio.core.client.layout.WindowState.*;
+
+/**
+ * Represents the combination of states and objects that model a single
+ * logical window in the DualWindowLayoutPanel.
+ */
+public class LogicalWindow implements HasWindowStateChangeHandlers,
+ WindowStateChangeHandler,
+ EnsureHeightHandler
+{
+ public LogicalWindow(WindowFrame normal,
+ MinimizedWindowFrame minimized)
+ {
+ normal_ = normal;
+ minimized_ = minimized;
+
+ normal_.addWindowStateChangeHandler(this);
+ normal_.addEnsureHeightHandler(this);
+ minimized_.addWindowStateChangeHandler(this);
+ }
+
+ public WindowFrame getNormal()
+ {
+ return normal_;
+ }
+
+ public MinimizedWindowFrame getMinimized()
+ {
+ return minimized_;
+ }
+
+ public void focus()
+ {
+ assert state_ != MINIMIZE && state_ != HIDE;
+ normal_.focus();
+ }
+
+ public Widget getActiveWidget()
+ {
+ switch (state_)
+ {
+ case EXCLUSIVE:
+ case MAXIMIZE:
+ case NORMAL:
+ return normal_;
+ case MINIMIZE:
+ return minimized_;
+ case HIDE:
+ return null;
+ }
+ assert false;
+ throw new IllegalStateException("Unknown state " + state_);
+ }
+
+ public HandlerRegistration addWindowStateChangeHandler(
+ WindowStateChangeHandler handler)
+ {
+ return events_.addHandler(WindowStateChangeEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(
+ EnsureHeightHandler handler)
+ {
+ return events_.addHandler(EnsureHeightEvent.TYPE, handler);
+ }
+
+ public void onWindowStateChange(WindowStateChangeEvent event)
+ {
+ WindowState newState = event.getNewState();
+ if (state_ == EXCLUSIVE && newState == MAXIMIZE)
+ newState = NORMAL;
+ if (newState == state_)
+ newState = NORMAL;
+ events_.fireEvent(new WindowStateChangeEvent(newState));
+ }
+
+ public void transitionToState(WindowState newState)
+ {
+ if (newState == MAXIMIZE)
+ normal_.addStyleDependentName("maximized");
+ else
+ normal_.removeStyleDependentName("maximized");
+
+ if (newState == EXCLUSIVE)
+ normal_.addStyleDependentName("exclusive");
+ else
+ normal_.removeStyleDependentName("exclusive");
+
+ state_ = newState;
+
+ if (getActiveWidget() == normal_)
+ normal_.onBeforeShow();
+ }
+
+ public WindowState getState()
+ {
+ return state_;
+ }
+
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ if (event.getHeight() == EnsureHeightEvent.MAXIMIZED)
+ {
+ if (getState() != WindowState.MAXIMIZE)
+ events_.fireEvent(new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+ else
+ {
+ events_.fireEvent(event);
+ }
+ }
+
+ private HandlerManager events_ = new HandlerManager(this);
+ private WindowFrame normal_;
+ private MinimizedWindowFrame minimized_;
+ private WindowState state_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/RequiresVisibilityChanged.java b/src/gwt/src/org/rstudio/core/client/layout/RequiresVisibilityChanged.java
new file mode 100644
index 0000000..5dbe1c0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/RequiresVisibilityChanged.java
@@ -0,0 +1,20 @@
+/*
+ * RequiresVisibilityChanged.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+public interface RequiresVisibilityChanged
+{
+ void onVisibilityChanged(boolean visible);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/ScreenUtils.java b/src/gwt/src/org/rstudio/core/client/layout/ScreenUtils.java
new file mode 100644
index 0000000..f9c3a7d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/ScreenUtils.java
@@ -0,0 +1,66 @@
+/*
+ * ScreenUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.layout;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.dom.NativeScreen;
+import org.rstudio.studio.client.application.Desktop;
+
+public class ScreenUtils
+{
+ public static Size getAdjustedWindowSize(Size preferredSize)
+ {
+ // compute available height (trim to max)
+ NativeScreen screen = NativeScreen.get();
+ int height = Math.min(screen.getAvailHeight(), preferredSize.height);
+
+ // trim height for large monitors
+ if (screen.getAvailHeight() >= (preferredSize.height-100))
+ {
+ if (BrowseCap.isMacintosh())
+ height = height - 107;
+ else if (BrowseCap.isWindows())
+ height = height - 89;
+ else
+ height = height - 80;
+ }
+ else
+ {
+ // adjust for window framing, etc.
+ if (Desktop.isDesktop())
+ height = height - 40;
+ else
+ height = height - 60;
+
+ // extra adjustment for firefox on windows (extra chrome in url bar)
+ if (BrowseCap.isWindows() && BrowseCap.isFirefox())
+ height = height - 25;
+ }
+
+ // extra adjustment for chrome on linux (which misreports the
+ // available height, excluding the menubar/taskbar)
+ if (BrowseCap.isLinux() && BrowseCap.isChrome())
+ height = height - 50;
+
+ // compute width (trim to max)
+ int width = Math.min(preferredSize.width, screen.getAvailWidth() - 20);
+
+ // return size
+ return new Size(width, height);
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/layout/WindowState.java b/src/gwt/src/org/rstudio/core/client/layout/WindowState.java
new file mode 100644
index 0000000..3fbca84
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/layout/WindowState.java
@@ -0,0 +1,24 @@
+/*
+ * WindowState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.layout;
+
+public enum WindowState
+{
+ MINIMIZE,
+ MAXIMIZE,
+ NORMAL,
+ HIDE,
+ EXCLUSIVE
+}
diff --git a/src/gwt/src/org/rstudio/core/client/patch/DiffMatchPatch.java b/src/gwt/src/org/rstudio/core/client/patch/DiffMatchPatch.java
new file mode 100644
index 0000000..568d23e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/patch/DiffMatchPatch.java
@@ -0,0 +1,58 @@
+/*
+ * DiffMatchPatch.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.patch;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.HeadElement;
+import com.google.gwt.dom.client.ScriptElement;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.TextResource;
+
+public class DiffMatchPatch
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("diff_match_patch.js")
+ TextResource diff_match_patch();
+ }
+
+ static
+ {
+ injectJavascript(
+ ((Resources) GWT.create(Resources.class)).diff_match_patch().getText());
+ }
+
+ private static void injectJavascript(String source)
+ {
+ Document doc = Document.get();
+ HeadElement head = (HeadElement) doc.getElementsByTagName("head").getItem(0);
+ if (head == null)
+ {
+ head = doc.createHeadElement();
+ doc.insertBefore(head, doc.getBody());
+ }
+ ScriptElement script = doc.createScriptElement(
+ source);
+ script.setType("text/javascript");
+ head.appendChild(script);
+ }
+
+ public static native String diff(String s1, String s2) /*-{
+ var dmp = new $wnd.diff_match_patch();
+ var patches = dmp.patch_make(s1, s2);
+ return dmp.patch_toText(patches);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/patch/SubstringDiff.java b/src/gwt/src/org/rstudio/core/client/patch/SubstringDiff.java
new file mode 100644
index 0000000..836e214
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/patch/SubstringDiff.java
@@ -0,0 +1,111 @@
+/*
+ * SubstringDiff.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.patch;
+
+public class SubstringDiff
+{
+ public SubstringDiff(String origVal, String newVal)
+ {
+ // Figure out how many characters at the beginning of the two strings
+ // are identical.
+ int headLimit = Math.min(origVal.length(), newVal.length());
+ int head;
+ for (head = 0;
+ head < headLimit && origVal.charAt(head) == newVal.charAt(head);
+ head++)
+ {}
+
+ // Figure out how many characters at the end of the two strings are
+ // identical, but don't go past the range we established in the above
+ // step (i.e., anything already in the head can't be part of the tail).
+ int tailDelta = newVal.length() - origVal.length();
+ int tailLimit = Math.max(head, head - tailDelta);
+ int tail;
+ for (tail = origVal.length();
+ tail > tailLimit && origVal.charAt(tail-1) == newVal.charAt(tail+tailDelta-1);
+ tail--)
+ {}
+
+ // Now we have a chunk of newVal that is unique (it may simply be "")
+ // and offset_/length_ show what region within oldDoc it replaces.
+ replacement_ = newVal.substring(head, tail + tailDelta);
+ offset_ = head;
+ length_ = tail - head;
+ }
+
+ public String getReplacement()
+ {
+ return replacement_;
+ }
+
+ public int getOffset()
+ {
+ return offset_;
+ }
+
+ public int getLength()
+ {
+ return length_;
+ }
+
+ public String patch(String original)
+ {
+ if (isEmpty())
+ return original;
+
+ return original.substring(0, offset_)
+ + replacement_
+ + original.substring(offset_ + length_);
+ }
+
+ /**
+ * @return True iff there was no difference between the strings.
+ */
+ public boolean isEmpty()
+ {
+ return length_ == 0 && replacement_.length() == 0;
+ }
+
+ private final int offset_;
+ private final int length_;
+ private final String replacement_;
+
+
+
+ /*
+ public static void test()
+ {
+ test("", "", 0, 0, "");
+ test("a", "a", 1, 0, "");
+ test("ab", "ab", 2, 0, "");
+ test("ab", "a", 1, 1, "");
+ test("abc", "ac", 1, 1, "");
+ test("abc", "adc", 1, 1, "d");
+ test("abc", "bc", 0, 1, "");
+ test("bc", "abc", 0, 0, "a");
+ test("a\nb\nc", "a\nc", 2, 2, "");
+ }
+ static void test(String old, String neu, int offset, int len, String repl)
+ {
+ SubstringDiff diff = new SubstringDiff(old, neu);
+ assert diff.getOffset() == offset
+ && diff.getLength() == len
+ && diff.getReplacement().equals(repl) :
+ "\"" + old + "\" - \"" + neu + "\" => " +
+ "\"" + diff.getReplacement() + "\" [" + diff.getOffset() + ", " + diff.getLength() + "]";
+ assert diff.patch(old).equals(neu);
+ }
+ */
+}
diff --git a/src/gwt/src/org/rstudio/core/client/patch/diff_match_patch.js b/src/gwt/src/org/rstudio/core/client/patch/diff_match_patch.js
new file mode 100755
index 0000000..735777b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/patch/diff_match_patch.js
@@ -0,0 +1,46 @@
+(function(){var a;function diff_match_patch(){function b(){for(var c=0,e=1,d=2;e!=d;){c++;e=d;d<<=1}return c}this.Diff_Timeout=1;this.Diff_EditCost=4;this.Diff_DualThreshold=32;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=b()}a=diff_match_patch.prototype;
+a.diff_main=function(b,c,e){if(b==c)return[[0,b]];if(typeof e=="undefined")e=true;var d=e,f=this.diff_commonPrefix(b,c);e=b.substring(0,f);b=b.substring(f);c=c.substring(f);f=this.diff_commonSuffix(b,c);var h=b.substring(b.length-f);b=b.substring(0,b.length-f);c=c.substring(0,c.length-f);b=this.diff_compute(b,c,d);e&&b.unshift([0,e]);h&&b.push([0,h]);this.diff_cleanupMerge(b);return b};
+a.diff_compute=function(b,c,e){var d;if(!b)return[[1,c]];if(!c)return[[-1,b]];d=b.length>c.length?b:c;var f=b.length>c.length?c:b,h=d.indexOf(f);if(h!=-1){d=[[1,d.substring(0,h)],[0,f],[1,d.substring(h+f.length)]];if(b.length>c.length)d[0][0]=d[2][0]=-1;return d}if(d=this.diff_halfMatch(b,c)){var g=d[0];b=d[1];f=d[2];c=d[3];d=d[4];g=this.diff_main(g,f,e);e=this.diff_main(b,c,e);return g.concat([[0,d]],e)}if(e&&(b.length<100||c.length<100))e=false;if(e){g=this.diff_linesToChars(b,c);b=g[0 [...]
+g=g[2]}(d=this.diff_map(b,c))||(d=[[-1,b],[1,c]]);if(e){this.diff_charsToLines(d,g);this.diff_cleanupSemantic(d);d.push([0,""]);c=b=e=0;for(f=g="";e<d.length;){switch(d[e][0]){case 1:c++;f+=d[e][1];break;case -1:b++;g+=d[e][1];break;case 0:if(b>=1&&c>=1){g=this.diff_main(g,f,false);d.splice(e-b-c,b+c);e=e-b-c;for(b=g.length-1;b>=0;b--)d.splice(e,0,g[b]);e+=g.length}b=c=0;f=g="";break}e++}d.pop()}return d};
+a.diff_linesToChars=function(b,c){function e(h){for(var g="",i=0,k=-1,j=d.length;k<h.length-1;){k=h.indexOf("\n",i);if(k==-1)k=h.length-1;var l=h.substring(i,k+1);i=k+1;if(f.hasOwnProperty?f.hasOwnProperty(l):f[l]!==undefined)g+=String.fromCharCode(f[l]);else{g+=String.fromCharCode(j);f[l]=j;d[j++]=l}}return g}var d=[],f={};d[0]="";b=e(b);c=e(c);return[b,c,d]};a.diff_charsToLines=function(b,c){for(var e=0;e<b.length;e++){for(var d=b[e][1],f=[],h=0;h<d.length;h++)f[h]=c[d.charCodeAt(h)];b [...]
+a.diff_map=function(b,c){var e=(new Date).getTime()+this.Diff_Timeout*1E3,d=b.length,f=c.length,h=d+f-1,g=this.Diff_DualThreshold*2<h,i=[],k=[],j={},l={};j[1]=0;l[1]=0;for(var m,o,p,n={},u=false,s=!!n.hasOwnProperty,r=(d+f)%2,q=0;q<h;q++){if(this.Diff_Timeout>0&&(new Date).getTime()>e)return null;i[q]={};for(var t=-q;t<=q;t+=2){m=t==-q||t!=q&&j[t-1]<j[t+1]?j[t+1]:j[t-1]+1;o=m-t;if(g){p=m+","+o;if(r&&(s?n.hasOwnProperty(p):n[p]!==undefined))u=true;r||(n[p]=q)}for(;!u&&m<d&&o<f&&b.charAt(m [...]
+o++;if(g){p=m+","+o;if(r&&(s?n.hasOwnProperty(p):n[p]!==undefined))u=true;r||(n[p]=q)}}j[t]=m;i[q][m+","+o]=true;if(m==d&&o==f)return this.diff_path1(i,b,c);else if(u){k=k.slice(0,n[p]+1);e=this.diff_path1(i,b.substring(0,m),c.substring(0,o));return e.concat(this.diff_path2(k,b.substring(m),c.substring(o)))}}if(g){k[q]={};for(t=-q;t<=q;t+=2){m=t==-q||t!=q&&l[t-1]<l[t+1]?l[t+1]:l[t-1]+1;o=m-t;p=d-m+","+(f-o);if(!r&&(s?n.hasOwnProperty(p):n[p]!==undefined))u=true;if(r)n[p]=q;for(;!u&&m<d&& [...]
+m-1)==c.charAt(f-o-1);){m++;o++;p=d-m+","+(f-o);if(!r&&(s?n.hasOwnProperty(p):n[p]!==undefined))u=true;if(r)n[p]=q}l[t]=m;k[q][m+","+o]=true;if(u){i=i.slice(0,n[p]+1);e=this.diff_path1(i,b.substring(0,d-m),c.substring(0,f-o));return e.concat(this.diff_path2(k,b.substring(d-m),c.substring(f-o)))}}}}return null};
+a.diff_path1=function(b,c,e){for(var d=[],f=c.length,h=e.length,g=null,i=b.length-2;i>=0;i--)for(;1;)if(b[i].hasOwnProperty?b[i].hasOwnProperty(f-1+","+h):b[i][f-1+","+h]!==undefined){f--;if(g===-1)d[0][1]=c.charAt(f)+d[0][1];else d.unshift([-1,c.charAt(f)]);g=-1;break}else if(b[i].hasOwnProperty?b[i].hasOwnProperty(f+","+(h-1)):b[i][f+","+(h-1)]!==undefined){h--;if(g===1)d[0][1]=e.charAt(h)+d[0][1];else d.unshift([1,e.charAt(h)]);g=1;break}else{f--;h--;if(g===0)d[0][1]=c.charAt(f)+d[0][ [...]
+c.charAt(f)]);g=0}return d};
+a.diff_path2=function(b,c,e){for(var d=[],f=0,h=c.length,g=e.length,i=null,k=b.length-2;k>=0;k--)for(;1;)if(b[k].hasOwnProperty?b[k].hasOwnProperty(h-1+","+g):b[k][h-1+","+g]!==undefined){h--;if(i===-1)d[f-1][1]+=c.charAt(c.length-h-1);else d[f++]=[-1,c.charAt(c.length-h-1)];i=-1;break}else if(b[k].hasOwnProperty?b[k].hasOwnProperty(h+","+(g-1)):b[k][h+","+(g-1)]!==undefined){g--;if(i===1)d[f-1][1]+=e.charAt(e.length-g-1);else d[f++]=[1,e.charAt(e.length-g-1)];i=1;break}else{h--;g--;if(i [...]
+1][1]+=c.charAt(c.length-h-1);else d[f++]=[0,c.charAt(c.length-h-1)];i=0}return d};a.diff_commonPrefix=function(b,c){if(!b||!c||b.charCodeAt(0)!==c.charCodeAt(0))return 0;for(var e=0,d=Math.min(b.length,c.length),f=d,h=0;e<f;){if(b.substring(h,f)==c.substring(h,f))h=e=f;else d=f;f=Math.floor((d-e)/2+e)}return f};
+a.diff_commonSuffix=function(b,c){if(!b||!c||b.charCodeAt(b.length-1)!==c.charCodeAt(c.length-1))return 0;for(var e=0,d=Math.min(b.length,c.length),f=d,h=0;e<f;){if(b.substring(b.length-f,b.length-h)==c.substring(c.length-f,c.length-h))h=e=f;else d=f;f=Math.floor((d-e)/2+e)}return f};
+a.diff_halfMatch=function(b,c){function e(i,k,j){for(var l=i.substring(j,j+Math.floor(i.length/4)),m=-1,o="",p,n,u,s;(m=k.indexOf(l,m+1))!=-1;){var r=h.diff_commonPrefix(i.substring(j),k.substring(m)),q=h.diff_commonSuffix(i.substring(0,j),k.substring(0,m));if(o.length<q+r){o=k.substring(m-q,m)+k.substring(m,m+r);p=i.substring(0,j-q);n=i.substring(j+r);u=k.substring(0,m-q);s=k.substring(m+r)}}return o.length>=i.length/2?[p,n,u,s,o]:null}var d=b.length>c.length?b:c,f=b.length>c.length?c:b [...]
+10||f.length<1)return null;var h=this,g=e(d,f,Math.ceil(d.length/4));d=e(d,f,Math.ceil(d.length/2));if(!g&&!d)return null;else g=d?g?g[4].length>d[4].length?g:d:d:g;if(b.length>c.length){b=g[0];c=g[1];d=g[2];f=g[3]}else{d=g[0];f=g[1];b=g[2];c=g[3]}g=g[4];return[b,c,d,f,g]};
+a.diff_cleanupSemantic=function(b){for(var c=false,e=[],d=0,f=null,h=0,g=0,i=0;h<b.length;){if(b[h][0]==0){e[d++]=h;g=i;i=0;f=b[h][1]}else{i+=b[h][1].length;if(f!==null&&f.length<=g&&f.length<=i){b.splice(e[d-1],0,[-1,f]);b[e[d-1]+1][0]=1;d--;d--;h=d>0?e[d-1]:-1;i=g=0;f=null;c=true}}h++}c&&this.diff_cleanupMerge(b);this.diff_cleanupSemanticLossless(b)};
+a.diff_cleanupSemanticLossless=function(b){function c(s,r){if(!s||!r)return 5;var q=0;if(s.charAt(s.length-1).match(e)||r.charAt(0).match(e)){q++;if(s.charAt(s.length-1).match(d)||r.charAt(0).match(d)){q++;if(s.charAt(s.length-1).match(f)||r.charAt(0).match(f)){q++;if(s.match(h)||r.match(g))q++}}}return q}for(var e=/[^a-zA-Z0-9]/,d=/\s/,f=/[\r\n]/,h=/\n\r?\n$/,g=/^\r?\n\r?\n/,i=1;i<b.length-1;){if(b[i-1][0]==0&&b[i+1][0]==0){var k=b[i-1][1],j=b[i][1],l=b[i+1][1],m=this.diff_commonSuffix( [...]
+j.substring(j.length-m);k=k.substring(0,k.length-m);j=o+j.substring(0,j.length-m);l=o+l}m=k;o=j;for(var p=l,n=c(k,j)+c(j,l);j.charAt(0)===l.charAt(0);){k+=j.charAt(0);j=j.substring(1)+l.charAt(0);l=l.substring(1);var u=c(k,j)+c(j,l);if(u>=n){n=u;m=k;o=j;p=l}}if(b[i-1][1]!=m){if(m)b[i-1][1]=m;else{b.splice(i-1,1);i--}b[i][1]=o;if(p)b[i+1][1]=p;else{b.splice(i+1,1);i--}}}i++}};
+a.diff_cleanupEfficiency=function(b){for(var c=false,e=[],d=0,f="",h=0,g=false,i=false,k=false,j=false;h<b.length;){if(b[h][0]==0){if(b[h][1].length<this.Diff_EditCost&&(k||j)){e[d++]=h;g=k;i=j;f=b[h][1]}else{d=0;f=""}k=j=false}else{if(b[h][0]==-1)j=true;else k=true;if(f&&(g&&i&&k&&j||f.length<this.Diff_EditCost/2&&g+i+k+j==3)){b.splice(e[d-1],0,[-1,f]);b[e[d-1]+1][0]=1;d--;f="";if(g&&i){k=j=true;d=0}else{d--;h=d>0?e[d-1]:-1;k=j=false}c=true}}h++}c&&this.diff_cleanupMerge(b)};
+a.diff_cleanupMerge=function(b){b.push([0,""]);for(var c=0,e=0,d=0,f="",h="",g;c<b.length;)switch(b[c][0]){case 1:d++;h+=b[c][1];c++;break;case -1:e++;f+=b[c][1];c++;break;case 0:if(e!==0||d!==0){if(e!==0&&d!==0){g=this.diff_commonPrefix(h,f);if(g!==0){if(c-e-d>0&&b[c-e-d-1][0]==0)b[c-e-d-1][1]+=h.substring(0,g);else{b.splice(0,0,[0,h.substring(0,g)]);c++}h=h.substring(g);f=f.substring(g)}g=this.diff_commonSuffix(h,f);if(g!==0){b[c][1]=h.substring(h.length-g)+b[c][1];h=h.substring(0,h.le [...]
+f.substring(0,f.length-g)}}if(e===0)b.splice(c-e-d,e+d,[1,h]);else d===0?b.splice(c-e-d,e+d,[-1,f]):b.splice(c-e-d,e+d,[-1,f],[1,h]);c=c-e-d+(e?1:0)+(d?1:0)+1}else if(c!==0&&b[c-1][0]==0){b[c-1][1]+=b[c][1];b.splice(c,1)}else c++;e=d=0;h=f="";break}b[b.length-1][1]===""&&b.pop();e=false;for(c=1;c<b.length-1;){if(b[c-1][0]==0&&b[c+1][0]==0)if(b[c][1].substring(b[c][1].length-b[c-1][1].length)==b[c-1][1]){b[c][1]=b[c-1][1]+b[c][1].substring(0,b[c][1].length-b[c-1][1].length);b[c+1][1]=b[c- [...]
+1][1];b.splice(c-1,1);e=true}else if(b[c][1].substring(0,b[c+1][1].length)==b[c+1][1]){b[c-1][1]+=b[c+1][1];b[c][1]=b[c][1].substring(b[c+1][1].length)+b[c+1][1];b.splice(c+1,1);e=true}c++}e&&this.diff_cleanupMerge(b)};a.diff_xIndex=function(b,c){var e=0,d=0,f=0,h=0,g;for(g=0;g<b.length;g++){if(b[g][0]!==1)e+=b[g][1].length;if(b[g][0]!==-1)d+=b[g][1].length;if(e>c)break;f=e;h=d}if(b.length!=g&&b[g][0]===-1)return h;return h+(c-f)};
+a.diff_prettyHtml=function(b){for(var c=[],e=0,d=0;d<b.length;d++){var f=b[d][0],h=b[d][1],g=h.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/\n/g,"¶<BR>");switch(f){case 1:c[d]='<INS STYLE="background:#E6FFE6;" TITLE="i='+e+'">'+g+"</INS>";break;case -1:c[d]='<DEL STYLE="background:#FFE6E6;" TITLE="i='+e+'">'+g+"</DEL>";break;case 0:c[d]='<SPAN TITLE="i='+e+'">'+g+"</SPAN>";break}if(f!==-1)e+=h.length}return c.join("")};
+a.diff_text1=function(b){for(var c=[],e=0;e<b.length;e++)if(b[e][0]!==1)c[e]=b[e][1];return c.join("")};a.diff_text2=function(b){for(var c=[],e=0;e<b.length;e++)if(b[e][0]!==-1)c[e]=b[e][1];return c.join("")};a.diff_levenshtein=function(b){for(var c=0,e=0,d=0,f=0;f<b.length;f++){var h=b[f][0],g=b[f][1];switch(h){case 1:e+=g.length;break;case -1:d+=g.length;break;case 0:c+=Math.max(e,d);d=e=0;break}}c+=Math.max(e,d);return c};
+a.diff_toDelta=function(b){for(var c=[],e=0;e<b.length;e++)switch(b[e][0]){case 1:c[e]="+"+encodeURI(b[e][1]);break;case -1:c[e]="-"+b[e][1].length;break;case 0:c[e]="="+b[e][1].length;break}return c.join("\t").replace(/\x00/g,"%00").replace(/%20/g," ")};
+a.diff_fromDelta=function(b,c){var e=[],d=0,f=0;c=c.replace(/%00/g,"\u0000");c=c.split(/\t/g);for(var h=0;h<c.length;h++){var g=c[h].substring(1);switch(c[h].charAt(0)){case "+":try{e[d++]=[1,decodeURI(g)]}catch(i){throw new Error("Illegal escape in diff_fromDelta: "+g);}break;case "-":case "=":var k=parseInt(g,10);if(isNaN(k)||k<0)throw new Error("Invalid number in diff_fromDelta: "+g);g=b.substring(f,f+=k);if(c[h].charAt(0)=="=")e[d++]=[0,g];else e[d++]=[-1,g];break;default:if(c[h])thr [...]
+c[h]);}}if(f!=b.length)throw new Error("Delta length ("+f+") does not equal source text length ("+b.length+").");return e};a.match_main=function(b,c,e){e=Math.max(0,Math.min(e,b.length));return b==c?0:b.length?b.substring(e,e+c.length)==c?e:this.match_bitap(b,c,e):-1};
+a.match_bitap=function(b,c,e){function d(s,r){s=s/c.length;r=Math.abs(e-r);if(!h.Match_Distance)return r?1:s;return s+r/h.Match_Distance}if(c.length>this.Match_MaxBits)throw new Error("Pattern too long for this browser.");var f=this.match_alphabet(c),h=this,g=this.Match_Threshold,i=b.indexOf(c,e);if(i!=-1){g=Math.min(d(0,i),g);i=b.lastIndexOf(c,e+c.length);if(i!=-1)g=Math.min(d(0,i),g)}var k=1<<c.length-1;i=-1;for(var j,l,m=c.length+b.length,o,p=0;p<c.length;p++){j=0;for(l=m;j<l;){if(d(p [...]
+l;else m=l;l=Math.floor((m-j)/2+j)}m=l;j=Math.max(1,e-l+1);var n=Math.min(e+l,b.length)+c.length;l=Array(n+2);l[n+1]=(1<<p)-1;for(n=n;n>=j;n--){var u=f[b.charAt(n-1)];l[n]=p===0?(l[n+1]<<1|1)&u:(l[n+1]<<1|1)&u|(o[n+1]|o[n])<<1|1|o[n+1];if(l[n]&k){u=d(p,n-1);if(u<=g){g=u;i=n-1;if(i>e)j=Math.max(1,2*e-i);else break}}}if(d(p+1,e)>g)break;o=l}return i};a.match_alphabet=function(b){for(var c={},e=0;e<b.length;e++)c[b.charAt(e)]=0;for(e=0;e<b.length;e++)c[b.charAt(e)]|=1<<b.length-e-1;return c};
+a.patch_addContext=function(b,c){if(c.length!=0){for(var e=c.substring(b.start2,b.start2+b.length1),d=0;c.indexOf(e)!=c.lastIndexOf(e)&&e.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;){d+=this.Patch_Margin;e=c.substring(b.start2-d,b.start2+b.length1+d)}d+=this.Patch_Margin;(e=c.substring(b.start2-d,b.start2))&&b.diffs.unshift([0,e]);(c=c.substring(b.start2+b.length1,b.start2+b.length1+d))&&b.diffs.push([0,c]);b.start1-=e.length;b.start2-=e.length;b.length1+=e.length+c.le [...]
+e.length+c.length}};
+a.patch_make=function(b,c,e){var d;if(typeof b=="string"&&typeof c=="string"&&typeof e=="undefined"){d=b;c=this.diff_main(d,c,true);if(c.length>2){this.diff_cleanupSemantic(c);this.diff_cleanupEfficiency(c)}}else if(typeof b=="object"&&typeof c=="undefined"&&typeof e=="undefined"){c=b;d=this.diff_text1(c)}else if(typeof b=="string"&&typeof c=="object"&&typeof e=="undefined"){d=b;c=c}else if(typeof b=="string"&&typeof c=="string"&&typeof e=="object"){d=b;c=e}else throw new Error("Unknown [...]
+0)return[];e=[];b=new patch_obj;var f=0,h=0,g=0,i=d;d=d;for(var k=0;k<c.length;k++){var j=c[k][0],l=c[k][1];if(!f&&j!==0){b.start1=h;b.start2=g}switch(j){case 1:b.diffs[f++]=c[k];b.length2+=l.length;d=d.substring(0,g)+l+d.substring(g);break;case -1:b.length1+=l.length;b.diffs[f++]=c[k];d=d.substring(0,g)+d.substring(g+l.length);break;case 0:if(l.length<=2*this.Patch_Margin&&f&&c.length!=k+1){b.diffs[f++]=c[k];b.length1+=l.length;b.length2+=l.length}else if(l.length>=2*this.Patch_Margin)i [...]
+i);e.push(b);b=new patch_obj;f=0;i=d;h=g}break}if(j!==1)h+=l.length;if(j!==-1)g+=l.length}if(f){this.patch_addContext(b,i);e.push(b)}return e};a.patch_deepCopy=function(b){for(var c=[],e=0;e<b.length;e++){var d=b[e],f=new patch_obj;f.diffs=[];for(var h=0;h<d.diffs.length;h++)f.diffs[h]=d.diffs[h].slice();f.start1=d.start1;f.start2=d.start2;f.length1=d.length1;f.length2=d.length2;c[e]=f}return c};
+a.patch_apply=function(b,c){if(b.length==0)return[c,[]];b=this.patch_deepCopy(b);var e=this.patch_addPadding(b);c=e+c+e;this.patch_splitMax(b);for(var d=0,f=[],h=0;h<b.length;h++){var g=b[h].start2+d,i=this.diff_text1(b[h].diffs),k,j=-1;if(i.length>this.Match_MaxBits){k=this.match_main(c,i.substring(0,this.Match_MaxBits),g);if(k!=-1){j=this.match_main(c,i.substring(i.length-this.Match_MaxBits),g+i.length-this.Match_MaxBits);if(j==-1||k>=j)k=-1}}else k=this.match_main(c,i,g);if(k==-1){f[h [...]
+b[h].length2-b[h].length1}else{f[h]=true;d=k-g;g=j==-1?c.substring(k,k+i.length):c.substring(k,j+this.Match_MaxBits);if(i==g)c=c.substring(0,k)+this.diff_text2(b[h].diffs)+c.substring(k+i.length);else{g=this.diff_main(i,g,false);if(i.length>this.Match_MaxBits&&this.diff_levenshtein(g)/i.length>this.Patch_DeleteThreshold)f[h]=false;else{this.diff_cleanupSemanticLossless(g);i=0;var l;for(j=0;j<b[h].diffs.length;j++){var m=b[h].diffs[j];if(m[0]!==0)l=this.diff_xIndex(g,i);if(m[0]===1)c=c.su [...]
+k+l)+m[1]+c.substring(k+l);else if(m[0]===-1)c=c.substring(0,k+l)+c.substring(k+this.diff_xIndex(g,i+m[1].length));if(m[0]!==-1)i+=m[1].length}}}}}c=c.substring(e.length,c.length-e.length);return[c,f]};
+a.patch_addPadding=function(b){for(var c=this.Patch_Margin,e="",d=1;d<=c;d++)e+=String.fromCharCode(d);for(d=0;d<b.length;d++){b[d].start1+=c;b[d].start2+=c}d=b[0];var f=d.diffs;if(f.length==0||f[0][0]!=0){f.unshift([0,e]);d.start1-=c;d.start2-=c;d.length1+=c;d.length2+=c}else if(c>f[0][1].length){var h=c-f[0][1].length;f[0][1]=e.substring(f[0][1].length)+f[0][1];d.start1-=h;d.start2-=h;d.length1+=h;d.length2+=h}d=b[b.length-1];f=d.diffs;if(f.length==0||f[f.length-1][0]!=0){f.push([0,e]) [...]
+c;d.length2+=c}else if(c>f[f.length-1][1].length){h=c-f[f.length-1][1].length;f[f.length-1][1]+=e.substring(0,h);d.length1+=h;d.length2+=h}return e};
+a.patch_splitMax=function(b){for(var c=0;c<b.length;c++)if(b[c].length1>this.Match_MaxBits){var e=b[c];b.splice(c--,1);for(var d=this.Match_MaxBits,f=e.start1,h=e.start2,g="";e.diffs.length!==0;){var i=new patch_obj,k=true;i.start1=f-g.length;i.start2=h-g.length;if(g!==""){i.length1=i.length2=g.length;i.diffs.push([0,g])}for(;e.diffs.length!==0&&i.length1<d-this.Patch_Margin;){g=e.diffs[0][0];var j=e.diffs[0][1];if(g===1){i.length2+=j.length;h+=j.length;i.diffs.push(e.diffs.shift());k=fa [...]
+-1&&i.diffs.length==1&&i.diffs[0][0]==0&&j.length>2*d){i.length1+=j.length;f+=j.length;k=false;i.diffs.push([g,j]);e.diffs.shift()}else{j=j.substring(0,d-i.length1-this.Patch_Margin);i.length1+=j.length;f+=j.length;if(g===0){i.length2+=j.length;h+=j.length}else k=false;i.diffs.push([g,j]);if(j==e.diffs[0][1])e.diffs.shift();else e.diffs[0][1]=e.diffs[0][1].substring(j.length)}}g=this.diff_text2(i.diffs);g=g.substring(g.length-this.Patch_Margin);j=this.diff_text1(e.diffs).substring(0,this [...]
+if(j!==""){i.length1+=j.length;i.length2+=j.length;if(i.diffs.length!==0&&i.diffs[i.diffs.length-1][0]===0)i.diffs[i.diffs.length-1][1]+=j;else i.diffs.push([0,j])}k||b.splice(++c,0,i)}}};a.patch_toText=function(b){for(var c=[],e=0;e<b.length;e++)c[e]=b[e];return c.join("")};
+a.patch_fromText=function(b){var c=[];if(!b)return c;b=b.replace(/%00/g,"\u0000");b=b.split("\n");for(var e=0;e<b.length;){var d=b[e].match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/);if(!d)throw new Error("Invalid patch string: "+b[e]);var f=new patch_obj;c.push(f);f.start1=parseInt(d[1],10);if(d[2]===""){f.start1--;f.length1=1}else if(d[2]=="0")f.length1=0;else{f.start1--;f.length1=parseInt(d[2],10)}f.start2=parseInt(d[3],10);if(d[4]===""){f.start2--;f.length2=1}else if(d[4]=="0")f.length2 [...]
+f.length2=parseInt(d[4],10)}for(e++;e<b.length;){d=b[e].charAt(0);try{var h=decodeURI(b[e].substring(1))}catch(g){throw new Error("Illegal escape in patch_fromText: "+h);}if(d=="-")f.diffs.push([-1,h]);else if(d=="+")f.diffs.push([1,h]);else if(d==" ")f.diffs.push([0,h]);else if(d=="@")break;else if(d!=="")throw new Error('Invalid patch mode "'+d+'" in: '+h);e++}}return c};function patch_obj(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0}
+patch_obj.prototype.toString=function(){var b,c;b=this.length1===0?this.start1+",0":this.length1==1?this.start1+1:this.start1+1+","+this.length1;c=this.length2===0?this.start2+",0":this.length2==1?this.start2+1:this.start2+1+","+this.length2;b=["@@ -"+b+" +"+c+" @@\n"];var e;for(c=0;c<this.diffs.length;c++){switch(this.diffs[c][0]){case 1:e="+";break;case -1:e="-";break;case 0:e=" ";break}b[c+1]=e+encodeURI(this.diffs[c][1])+"\n"}return b.join("").replace(/\x00/g,"%00").replace(/%20/g," ")};
+window.diff_match_patch=diff_match_patch;window.patch_obj=patch_obj;window.DIFF_DELETE=-1;window.DIFF_INSERT=1;window.DIFF_EQUAL=0;})()
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.css b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.css
new file mode 100644
index 0000000..68ee5a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.css
@@ -0,0 +1,79 @@
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external fixedWidthFont;
+
+ at external gwt-CheckBox, gwt-RadioButton, gwt-Label;
+
+.preferencesDialog .gwt-CheckBox,
+.preferencesDialog .gwt-RadioButton {
+ display: block;
+}
+
+.spaced {
+ margin-bottom: 12px;
+}
+
+.lessSpaced {
+ margin-bottom: 4px;
+}
+
+.extraSpaced {
+ margin-bottom: 23px;
+}
+
+.preferencesDialog .tight {
+ margin-bottom: 2px;
+}
+
+.nudgeRight {
+ margin-left: 2px;
+}
+
+.headerLabel {
+ font-weight: bold;
+ margin-bottom: 10px;
+}
+
+ at if user.agent gecko1_8 { .nudgeRight {
+ margin-left: 5px;
+}
+}
+
+.sectionChooser {
+ background-color: white;
+ border: 1px solid #999;
+}
+
+.sectionChooserInner {
+ background-color: transparent;
+ width: 100%;
+}
+
+.section {
+ width: 100%;
+ padding: 7px 2px 5px 2px;
+}
+.section * {
+ cursor: default;
+}
+
+
+.activeSection {
+ background: #D6E9F8;
+}
+
+.indent {
+ margin-left: 20px;
+}
+
+.textBoxWithChooser {
+ width: 350px;
+}
+
+.infoLabel {
+ font-style: italic;
+ font-size: 0.9em;
+}
+
+
+
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.java b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.java
new file mode 100644
index 0000000..0140696
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBase.java
@@ -0,0 +1,217 @@
+/*
+ * PreferencesDialogBase.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.prefs;
+
+
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.ReloadEvent;
+
+public abstract class PreferencesDialogBase<T> extends ModalDialogBase
+{
+ protected PreferencesDialogBase(String caption,
+ String panelContainerStyle,
+ boolean showApplyButton,
+ PreferencesDialogPaneBase<T>[] panes)
+ {
+ super();
+ setText(caption);
+ panes_ = panes;
+
+ PreferencesDialogBaseResources res =
+ PreferencesDialogBaseResources.INSTANCE;
+
+ sectionChooser_ = new SectionChooser();
+
+ ThemedButton okButton = new ThemedButton("OK", new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ attemptSaveChanges(new Operation() {
+ @Override
+ public void execute()
+ {
+ closeDialog();
+ }
+ });
+ }
+ });
+ addOkButton(okButton);
+ addCancelButton();
+
+ if (showApplyButton)
+ {
+ addButton(new ThemedButton("Apply", new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ attemptSaveChanges();
+ }
+ }));
+ }
+
+ progressIndicator_ = addProgressIndicator(false);
+ panel_ = new DockLayoutPanel(Unit.PX);
+ panel_.setStyleName(panelContainerStyle);
+ container_ = new FlowPanel();
+ container_.getElement().getStyle().setPaddingLeft(10, Unit.PX);
+
+ addStyleName(res.styles().preferencesDialog());
+
+
+ for (final PreferencesDialogPaneBase<T> pane : panes_)
+ {
+ sectionChooser_.addSection(pane.getIcon(), pane.getName());
+ pane.setWidth("100%");
+ pane.setDialog(this);
+ pane.setProgressIndicator(progressIndicator_);
+ container_.add(pane);
+ setPaneVisibility(pane, false);
+ pane.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ sectionChooser_.select(container_.getWidgetIndex(pane));
+ }
+ });
+ }
+
+ panel_.addWest(sectionChooser_, sectionChooser_.getDesiredWidth());
+ panel_.add(container_);
+
+ sectionChooser_.addSelectionHandler(new SelectionHandler<Integer>()
+ {
+ public void onSelection(SelectionEvent<Integer> e)
+ {
+ Integer index = e.getSelectedItem();
+
+ if (currentIndex_ != null)
+ setPaneVisibility(panes_[currentIndex_], false);
+
+ currentIndex_ = index;
+
+ if (currentIndex_ != null)
+ setPaneVisibility(panes_[currentIndex_], true);
+ }
+ });
+
+ sectionChooser_.select(0);
+ }
+
+ public void initialize(T prefs)
+ {
+ for (PreferencesDialogPaneBase<T> pane : panes_)
+ pane.initialize(prefs);
+ }
+
+ public void activatePane(int index)
+ {
+ sectionChooser_.select(index);
+ }
+
+ private void setPaneVisibility(PreferencesDialogPaneBase<T> pane, boolean visible)
+ {
+ pane.getElement().getStyle().setDisplay(visible
+ ? Display.BLOCK
+ : Display.NONE);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return panel_;
+ }
+
+ protected void hidePane(int index)
+ {
+ sectionChooser_.hideSection(index);
+ }
+
+ protected void attemptSaveChanges()
+ {
+ attemptSaveChanges(null);
+ }
+
+ private void attemptSaveChanges(final Operation onCompleted)
+ {
+ if (validate())
+ {
+ // apply changes
+ T prefs = createEmptyPrefs();
+ boolean restartRequired = false;
+ for (PreferencesDialogPaneBase<T> pane : panes_)
+ if (pane.onApply(prefs))
+ restartRequired = true;
+
+ // perform save
+ progressIndicator_.onProgress("Saving...");
+ doSaveChanges(prefs, onCompleted, progressIndicator_, restartRequired);
+ }
+ }
+
+ protected abstract T createEmptyPrefs();
+
+ protected abstract void doSaveChanges(T prefs,
+ Operation onCompleted,
+ ProgressIndicator progressIndicator,
+ boolean reload);
+
+ protected void reload()
+ {
+ RStudioGinjector.INSTANCE.getEventBus().fireEvent(new ReloadEvent());
+ }
+
+
+ void forceClosed(final Command onClosed)
+ {
+ attemptSaveChanges(new Operation() {
+ @Override
+ public void execute()
+ {
+ closeDialog();
+ onClosed.execute();
+ }
+ });
+ }
+
+ private boolean validate()
+ {
+ for (PreferencesDialogPaneBase<T> pane : panes_)
+ if (!pane.validate())
+ return false;
+ return true;
+ }
+
+ private DockLayoutPanel panel_;
+ private PreferencesDialogPaneBase<T>[] panes_;
+ private FlowPanel container_;
+ private Integer currentIndex_;
+ private final ProgressIndicator progressIndicator_;
+ private final SectionChooser sectionChooser_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBaseResources.java b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBaseResources.java
new file mode 100644
index 0000000..99cd849
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogBaseResources.java
@@ -0,0 +1,53 @@
+/*
+ * PreferencesDialogBaseResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.prefs;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface PreferencesDialogBaseResources extends ClientBundle
+{
+ public interface Styles extends CssResource
+ {
+ String preferencesDialog();
+
+ String sectionChooser();
+ String sectionChooserInner();
+ String section();
+ String activeSection();
+ String indent();
+ String tight();
+ String nudgeRight();
+ String spaced();
+ String lessSpaced();
+ String extraSpaced();
+ String textBoxWithChooser();
+ String infoLabel();
+ String headerLabel();
+ }
+
+ @Source("PreferencesDialogBase.css")
+ Styles styles();
+
+ ImageResource iconCodeEditing();
+ ImageResource iconCompilePdf();
+ ImageResource iconR();
+ ImageResource iconSpelling();
+ ImageResource iconSourceControl();
+
+ static PreferencesDialogBaseResources INSTANCE = (PreferencesDialogBaseResources)GWT.create(PreferencesDialogBaseResources.class) ;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogPaneBase.java b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogPaneBase.java
new file mode 100644
index 0000000..1b212bb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/prefs/PreferencesDialogPaneBase.java
@@ -0,0 +1,140 @@
+/*
+ * PreferencesDialogPaneBase.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.prefs;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+import org.rstudio.core.client.widget.ProgressIndicator;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class PreferencesDialogPaneBase<T> extends VerticalPanel
+implements HasEnsureVisibleHandlers
+{
+ public abstract ImageResource getIcon();
+
+ public boolean validate()
+ {
+ return true;
+ }
+
+ public abstract String getName();
+
+ protected abstract void initialize(T prefs);
+
+ /**
+ * @return True if reload of the browser UI is required
+ */
+ public abstract boolean onApply(T prefs);
+
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ public void registerEnsureVisibleHandler(HasEnsureVisibleHandlers widget)
+ {
+ widget.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ fireEvent(new EnsureVisibleEvent());
+ }
+ });
+ }
+
+ public void setProgressIndicator(ProgressIndicator progressIndicator)
+ {
+ progressIndicator_ = progressIndicator;
+ }
+
+ protected ProgressIndicator getProgressIndicator()
+ {
+ return progressIndicator_;
+ }
+
+
+ protected Widget indent(Widget widget)
+ {
+ widget.addStyleName(res_.styles().indent());
+ return widget;
+ }
+
+ protected Widget tight(Widget widget)
+ {
+ widget.addStyleName(res_.styles().tight());
+ return widget;
+ }
+
+ protected Widget lessSpaced(Widget widget)
+ {
+ if (!BrowseCap.isLinuxDesktop())
+ {
+ widget.addStyleName(res_.styles().lessSpaced());
+ return widget;
+ }
+ else
+ {
+ return widget;
+ }
+ }
+
+ protected Widget spaced(Widget widget)
+ {
+ widget.addStyleName(res_.styles().spaced());
+ return widget;
+ }
+
+ protected Widget extraSpaced(Widget widget)
+ {
+ widget.addStyleName(res_.styles().extraSpaced());
+ return widget;
+ }
+
+ protected Widget nudgeRight(Widget widget)
+ {
+ widget.addStyleName(res_.styles().nudgeRight());
+ return widget;
+ }
+
+ protected Widget textBoxWithChooser(Widget widget)
+ {
+ widget.addStyleName(res_.styles().textBoxWithChooser());
+ return widget;
+ }
+
+ protected void forceClosed(Command onClosed)
+ {
+ dialog_.forceClosed(onClosed);
+ }
+
+ void setDialog(PreferencesDialogBase<T> dialog)
+ {
+ dialog_ = dialog;
+ }
+
+ private ProgressIndicator progressIndicator_;
+ private final PreferencesDialogBaseResources res_ =
+ PreferencesDialogBaseResources.INSTANCE;
+
+ private PreferencesDialogBase<T> dialog_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/SectionChooser.java b/src/gwt/src/org/rstudio/core/client/prefs/SectionChooser.java
new file mode 100644
index 0000000..b6845d9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/prefs/SectionChooser.java
@@ -0,0 +1,105 @@
+/*
+ * SectionChooser.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.prefs;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+class SectionChooser extends SimplePanel implements
+ HasSelectionHandlers<Integer>
+{
+ private class ClickableVerticalPanel extends VerticalPanel
+ implements HasClickHandlers
+ {
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+ }
+
+ public SectionChooser()
+ {
+ setStyleName(res_.styles().sectionChooser());
+ inner_.setStyleName(res_.styles().sectionChooserInner());
+ setWidget(inner_);
+ }
+
+ public void addSection(ImageResource icon, String name)
+ {
+ Image img = new Image(icon.getSafeUri());
+ img.setSize("36px", "25px");
+ Label label = new Label(name, false);
+ final ClickableVerticalPanel panel = new ClickableVerticalPanel();
+ panel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
+ panel.add(img);
+ panel.add(label);
+ panel.setStyleName(res_.styles().section());
+
+ panel.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ select(inner_.getWidgetIndex(panel));
+ }
+ });
+
+ inner_.add(panel);
+ }
+
+ public void select(Integer index)
+ {
+ if (selectedIndex_ != null)
+ inner_.getWidget(selectedIndex_).removeStyleName(res_.styles().activeSection());
+
+ selectedIndex_ = index;
+
+ if (index != null)
+ inner_.getWidget(index).addStyleName(res_.styles().activeSection());
+
+ SelectionEvent.fire(this, index);
+ }
+
+ public void hideSection(Integer index)
+ {
+ if (index != null)
+ inner_.getWidget(index).setVisible(false);
+ }
+
+ public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ public int getDesiredWidth()
+ {
+ return 102;
+ }
+
+ private Integer selectedIndex_;
+ private final VerticalPanel inner_ = new VerticalPanel();
+ private static final PreferencesDialogBaseResources res_ =
+ PreferencesDialogBaseResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/iconCodeEditing.png b/src/gwt/src/org/rstudio/core/client/prefs/iconCodeEditing.png
new file mode 100644
index 0000000..6c6808b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/prefs/iconCodeEditing.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/iconCompilePdf.png b/src/gwt/src/org/rstudio/core/client/prefs/iconCompilePdf.png
new file mode 100644
index 0000000..c37014d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/prefs/iconCompilePdf.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/iconR.png b/src/gwt/src/org/rstudio/core/client/prefs/iconR.png
new file mode 100755
index 0000000..1461aa8
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/prefs/iconR.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/iconSourceControl.png b/src/gwt/src/org/rstudio/core/client/prefs/iconSourceControl.png
new file mode 100644
index 0000000..26d3c91
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/prefs/iconSourceControl.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/prefs/iconSpelling.png b/src/gwt/src/org/rstudio/core/client/prefs/iconSpelling.png
new file mode 100644
index 0000000..5dff079
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/prefs/iconSpelling.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/regex/Match.java b/src/gwt/src/org/rstudio/core/client/regex/Match.java
new file mode 100644
index 0000000..efe4dd3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/regex/Match.java
@@ -0,0 +1,47 @@
+/*
+ * Match.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.regex;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Match extends JavaScriptObject
+{
+ protected Match() {}
+
+ public final native String getValue() /*-{
+ return this.value ;
+ }-*/ ;
+
+ public final native int getIndex() /*-{
+ return this.index ;
+ }-*/ ;
+
+ public final native Match nextMatch() /*-{
+ return @org.rstudio.core.client.regex.Match::doNextMatch(Lorg/rstudio/core/client/regex/Pattern;Ljava/lang/String;I)(this.pattern, this.input, this.next);
+ }-*/;
+
+ public final native String getGroup(int number) /*-{
+ return this.match[number];
+ }-*/;
+
+ public final native boolean hasGroup(int number) /*-{
+ return typeof(this.match[number]) != 'undefined';
+ }-*/;
+
+ private static Match doNextMatch(Pattern pattern, String input, int index)
+ {
+ return pattern.match(input, index);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/regex/Pattern.java b/src/gwt/src/org/rstudio/core/client/regex/Pattern.java
new file mode 100644
index 0000000..37b8a5b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/regex/Pattern.java
@@ -0,0 +1,102 @@
+/*
+ * Pattern.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.regex;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Pattern extends JavaScriptObject
+{
+ public interface ReplaceOperation
+ {
+ String replace(Match m);
+ }
+
+ protected Pattern() {}
+
+ public static native Pattern create(String pattern) /*-{
+ return new RegExp(pattern, 'gm') ;
+ }-*/ ;
+
+ public static native Pattern create(String pattern, String flags) /*-{
+ return new RegExp(pattern, flags) ;
+ }-*/ ;
+
+ public final native Match match(String input, int index) /*-{
+ this.lastIndex = index ;
+ var result = this.exec(input) ;
+ if (result == null)
+ return null ;
+ return {
+ value: result[0],
+ index: result.index,
+ input: input,
+ next: this.lastIndex,
+ pattern: this,
+ match: result
+ } ;
+ }-*/ ;
+
+ public static String escape(String str)
+ {
+ // Replace every character with its \\uXXXX equivalent
+
+ StringBuilder output = new StringBuilder();
+ for (int i = 0; i < str.length(); i++)
+ {
+ char c = str.charAt(i);
+ String hexStr = Integer.toHexString(c);
+ output.append("\\u");
+ for (int j = 4 - hexStr.length(); j > 0; j--)
+ output.append('0');
+ output.append(hexStr);
+ }
+ return output.toString();
+ }
+
+ public native final String replaceAll(String str, String substr) /*-{
+ return str.replace(this, substr);
+ }-*/;
+
+ public final String replaceAll(String str, ReplaceOperation op)
+ {
+ StringBuilder result = new StringBuilder();
+ int tail = 0; // Index of last character copied/replaced from source str
+ Match match = match(str, 0);
+ while (match != null)
+ {
+ if (tail < match.getIndex())
+ result.append(str, tail, match.getIndex());
+
+ result.append(op.replace(match));
+
+ tail = match.getIndex() + match.getValue().length();
+
+ match = match.nextMatch();
+ }
+
+ if (tail < str.length())
+ result.append(str, tail, str.length());
+
+ return result.toString();
+ }
+
+ public static native String replace(String find,
+ String repl,
+ boolean caseSensitive) /*-{
+ return find.replace(
+ new RegExp(find, caseSensitive ? "g" : "ig"),
+ repl);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/resources/CoreResources.java b/src/gwt/src/org/rstudio/core/client/resources/CoreResources.java
new file mode 100644
index 0000000..1e21abe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/resources/CoreResources.java
@@ -0,0 +1,39 @@
+/*
+ * CoreResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.resources;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface CoreResources extends ClientBundle
+{
+ public static final CoreResources INSTANCE = GWT.create(CoreResources.class);
+
+ @CssResource.NotStrict
+ CoreStyles styles();
+
+ @Source("progress_gray.gif")
+ DataResource progress_gray_as_data();
+ ImageResource progress();
+ ImageResource progress_gray();
+ ImageResource progress_large();
+ ImageResource progress_large_gray();
+
+ @Source("clear.gif")
+ DataResource clear();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/resources/CoreStyles.java b/src/gwt/src/org/rstudio/core/client/resources/CoreStyles.java
new file mode 100644
index 0000000..6c514ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/resources/CoreStyles.java
@@ -0,0 +1,22 @@
+/*
+ * CoreStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.resources;
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface CoreStyles extends CssResource
+{
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/resources/StaticDataResource.java b/src/gwt/src/org/rstudio/core/client/resources/StaticDataResource.java
new file mode 100644
index 0000000..0c0ba4e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/resources/StaticDataResource.java
@@ -0,0 +1,24 @@
+/*
+ * StaticDataResource.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.resources;
+
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.ext.ResourceGeneratorType;
+import org.rstudio.core.rebind.StaticDataResourceGenerator;
+
+ at ResourceGeneratorType(StaticDataResourceGenerator.class)
+public interface StaticDataResource extends DataResource
+{
+}
diff --git a/src/gwt/src/org/rstudio/core/client/resources/clear.gif b/src/gwt/src/org/rstudio/core/client/resources/clear.gif
new file mode 100644
index 0000000..35d42e8
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/resources/clear.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/resources/progress.gif b/src/gwt/src/org/rstudio/core/client/resources/progress.gif
new file mode 100644
index 0000000..d5dfe0f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/resources/progress.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/resources/progress_gray.gif b/src/gwt/src/org/rstudio/core/client/resources/progress_gray.gif
new file mode 100644
index 0000000..bef3cda
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/resources/progress_gray.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/resources/progress_large.gif b/src/gwt/src/org/rstudio/core/client/resources/progress_large.gif
new file mode 100644
index 0000000..f7b442f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/resources/progress_large.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/resources/progress_large_gray.gif b/src/gwt/src/org/rstudio/core/client/resources/progress_large_gray.gif
new file mode 100644
index 0000000..4a626ce
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/resources/progress_large_gray.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/resources/styles.css b/src/gwt/src/org/rstudio/core/client/resources/styles.css
new file mode 100644
index 0000000..eaa32b1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/resources/styles.css
@@ -0,0 +1,102 @@
+.DEBUG {
+ border: 1px solid red;
+ background-color: lime;
+}
+
+.rstudio-Toolbar {
+ height: 23px;
+ width: 100%;
+ background-color: #F2F3F4;
+ border-top: 1px solid #FFF;
+ border-bottom: 1px solid #BBC1C5;
+}
+
+.rstudio-SecondaryToolbar {
+ height: 23px;
+}
+
+.rstudio-SecondaryToolbar .rstudio-StrongLabel {
+ font-weight: bold;
+}
+
+
+.gwt-Button-Toolbar {
+ height: 19px;
+ background-color: transparent;
+ margin: 1px;
+ padding: 0;
+ border: none;
+}
+
+.gwt-Button-Toolbar:active {
+ border: 1px inset #ccc;
+}
+
+.gwt-Button-Toolbar:hover {
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #ccc #999 #999 #aaa;
+}
+
+.gwt-Button-Toolbar[disabled] {
+ cursor: default;
+ color: #888;
+}
+
+.gwt-Button-Toolbar[disabled]:hover {
+ border: 1px outset #ccc;
+}
+
+.gwt-DecoratedPopupPanel-Toolbar .popupMiddleCenter {
+ background: white;
+}
+
+.gwt-DialogBox-ModalDialog .Caption {
+ font-weight: bold;
+}
+
+.rstudio-DialogBoxProgressPanel {
+ height: 25px;
+}
+
+.rstudio-DialogBoxProgressPanel .gwt-Label-ProgressText {
+ color: grey;
+ padding-left: 4px;
+}
+
+.rstudio-DialogBoxVerticalInputLabel {
+ margin-bottom: 2px;
+}
+
+.rstudio-ModalDialogButtonSpacer {
+ width: 5px;
+}
+
+
+.rstudio-HyperlinkLabel {
+ color: #0000AA;
+}
+
+.rstudio-HyperlinkLabel-Link {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+
+.rstudio-StatusIndicator {
+
+}
+
+.rstudio-StatusIndicator .Message {
+ font-weight: bold;
+ padding: 3px 3px 3px 3px;
+}
+
+.rstudio-StatusIndicator .Message-Info {
+ background-color: #FFEC8B;
+}
+
+.rstudio-StatusIndicator .Message-Error {
+ background-color: #FF6347;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/tex/TexMagicComment.java b/src/gwt/src/org/rstudio/core/client/tex/TexMagicComment.java
new file mode 100644
index 0000000..52584e0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/tex/TexMagicComment.java
@@ -0,0 +1,82 @@
+/*
+ * TexMagicComment.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.tex;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+public class TexMagicComment
+{
+ public static ArrayList<TexMagicComment> parseComments(String code)
+ {
+ ArrayList<TexMagicComment> comments = new ArrayList<TexMagicComment>();
+
+ Iterable<String> lines = StringUtil.getLineIterator(code);
+
+ for (String line : lines)
+ {
+ line = line.trim();
+ if (line.length() == 0)
+ {
+ continue;
+ }
+ else if (line.startsWith("%"))
+ {
+
+ Match match = magicCommentPattern_.match(line, 0);
+ if (match != null)
+ {
+ if (match.hasGroup(1) && match.hasGroup(2) && match.hasGroup(3))
+ {
+ comments.add(new TexMagicComment(match.getGroup(1),
+ match.getGroup(2),
+ match.getGroup(3)));
+
+ }
+ }
+
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return comments;
+ }
+
+
+ public TexMagicComment(String scope, String variable, String value)
+ {
+ scope_ = scope;
+ variable_ = variable;
+ value_ = value;
+ }
+
+ public String getScope() { return scope_; }
+ public String getVariable() { return variable_; }
+ public String getValue() { return value_; }
+
+
+ private final String scope_;
+ private final String variable_;
+ private final String value_;
+
+ private static final Pattern magicCommentPattern_ =
+ Pattern.create("%\\s*!(\\w+)\\s+(\\w+)\\s*=\\s*(.*)$");
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/DocTabLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/theme/DocTabLayoutPanel.java
new file mode 100644
index 0000000..54d45d4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/DocTabLayoutPanel.java
@@ -0,0 +1,373 @@
+/*
+ * DocTabLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.DomUtils.NodePredicate;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+/**
+ * A tab panel that is styled for document tabs.
+ */
+public class DocTabLayoutPanel
+ extends TabLayoutPanel
+ implements HasTabClosingHandlers,
+ HasTabCloseHandlers,
+ HasTabClosedHandlers
+{
+ public DocTabLayoutPanel(boolean closeableTabs,
+ int padding,
+ int rightMargin)
+ {
+ super(BAR_HEIGHT, Style.Unit.PX);
+ closeableTabs_ = closeableTabs;
+ padding_ = padding;
+ rightMargin_ = rightMargin;
+ styles_ = ThemeResources.INSTANCE.themeStyles();
+ addStyleName(styles_.docTabPanel());
+ addStyleName(styles_.moduleTabPanel());
+ }
+
+ @Override
+ public void add(final Widget child, String text)
+ {
+ add(child, null, text, null);
+ }
+
+ public void add(final Widget child,
+ ImageResource icon,
+ String text,
+ String tooltip)
+ {
+ if (closeableTabs_)
+ {
+ super.add(child, new DocTab(icon, text, tooltip, new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ int index = getWidgetIndex(child);
+ if (index >= 0)
+ {
+ tryCloseTab(index, null);
+ }
+ }
+ }));
+ }
+ else
+ {
+ super.add(child, text);
+ }
+ }
+
+ public boolean tryCloseTab(int index, Command onClosed)
+ {
+ TabClosingEvent event = new TabClosingEvent(index);
+ fireEvent(event);
+ if (event.isCancelled())
+ return false;
+
+ closeTab(index, onClosed);
+ return true;
+ }
+
+ public void closeTab(int index, Command onClosed)
+ {
+ if (remove(index))
+ {
+ if (onClosed != null)
+ onClosed.execute();
+ }
+ }
+
+ @Override
+ public void selectTab(int index)
+ {
+ super.selectTab(index);
+ ensureSelectedTabIsVisible(true);
+ }
+
+ public void ensureSelectedTabIsVisible(boolean animate)
+ {
+ if (currentAnimation_ != null)
+ {
+ currentAnimation_.cancel();
+ currentAnimation_ = null;
+ }
+
+ int index = getSelectedIndex();
+ if (index < 0)
+ return;
+ Widget tabWidget = getTabWidget(index);
+
+ Element tabBar = (Element) DomUtils.findNode(
+ getElement(),
+ true,
+ false,
+ new NodePredicate()
+ {
+ public boolean test(Node n)
+ {
+ if (n.getNodeType() != Node.ELEMENT_NODE)
+ return false;
+ return ((Element) n).getClassName()
+ .contains("gwt-TabLayoutPanelTabs");
+ }
+ });
+
+ if (!isVisible() || !isAttached() || tabBar.getOffsetWidth() == 0)
+ return; // not yet loaded
+
+ final Element tabBarParent = tabBar.getParentElement();
+
+ final int start = tabBarParent.getScrollLeft();
+ int end = DomUtils.ensureVisibleHoriz(tabBarParent,
+ tabWidget.getElement(),
+ padding_,
+ padding_ + rightMargin_,
+ true);
+
+ // When tabs are closed, the overall width shrinks, and this can lead
+ // to cases where there's too much empty space on the screen
+ Widget lastTab = getTabWidget(getWidgetCount() - 1);
+ int edge = DomUtils.getRelativePosition(tabBarParent,
+ lastTab.getElement()).x;
+ edge += lastTab.getOffsetWidth();
+ end = Math.min(end,
+ Math.max(0,
+ edge - (tabBarParent.getOffsetWidth() - rightMargin_)));
+
+ if (edge <= tabBarParent.getOffsetWidth() - rightMargin_)
+ end = 0;
+
+ if (start != end)
+ {
+ if (!animate)
+ {
+ tabBarParent.setScrollLeft(end);
+ }
+ else
+ {
+ final int finalEnd = end;
+ currentAnimation_ = new Animation() {
+ @Override
+ protected void onUpdate(double progress)
+ {
+ double delta = (finalEnd - start) * progress;
+ tabBarParent.setScrollLeft((int) (start + delta));
+ }
+
+ @Override
+ protected void onComplete()
+ {
+ if (this == currentAnimation_)
+ {
+ tabBarParent.setScrollLeft(finalEnd);
+ currentAnimation_ = null;
+ }
+ }
+ };
+ currentAnimation_.run(Math.max(200,
+ Math.min(1500,
+ Math.abs(end - start)*2)));
+ }
+ }
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+ ensureSelectedTabIsVisible(false);
+ }
+
+
+ @Override
+ public boolean remove(int index)
+ {
+ if ((index < 0) || (index >= getWidgetCount())) {
+ return false;
+ }
+
+ fireEvent(new TabCloseEvent(index));
+
+ if (getSelectedIndex() == index)
+ {
+ boolean closingLastTab = index == getWidgetCount() - 1;
+ int indexToSelect = closingLastTab
+ ? index - 1
+ : index + 1;
+ if (indexToSelect >= 0)
+ selectTab(indexToSelect);
+ }
+
+ if (!super.remove(index))
+ return false;
+
+ fireEvent(new TabClosedEvent(index));
+ ensureSelectedTabIsVisible(true);
+ return true;
+ }
+
+ @Override
+ public void add(Widget child, String text, boolean asHtml)
+ {
+ if (asHtml)
+ throw new UnsupportedOperationException("HTML tab names not supported");
+
+ add(child, text);
+ }
+
+ @Override
+ public void add(Widget child, Widget tab)
+ {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ private class DocTab extends Composite
+ {
+
+ private DocTab(ImageResource icon,
+ String title,
+ String tooltip,
+ ClickHandler clickHandler)
+ {
+ HorizontalPanel layoutPanel = new HorizontalPanel();
+ layoutPanel.setStylePrimaryName(styles_.tabLayout());
+ layoutPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);
+
+ HTML left = new HTML();
+ left.setStylePrimaryName(styles_.tabLayoutLeft());
+ layoutPanel.add(left);
+
+ contentPanel_ = new HorizontalPanel();
+ contentPanel_.setTitle(tooltip);
+ contentPanel_.setStylePrimaryName(styles_.tabLayoutCenter());
+ contentPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+
+ if (icon != null)
+ contentPanel_.add(imageForIcon(icon));
+
+ label_ = new Label(title, false);
+ appendDirtyMarker();
+ contentPanel_.add(label_);
+
+ Image img = new Image(ThemeResources.INSTANCE.closeTab());
+ img.setStylePrimaryName(styles_.closeTabButton());
+ img.addClickHandler(clickHandler);
+ contentPanel_.add(img);
+
+ layoutPanel.add(contentPanel_);
+
+ HTML right = new HTML();
+ right.setStylePrimaryName(styles_.tabLayoutRight());
+ layoutPanel.add(right);
+
+ initWidget(layoutPanel);
+ }
+
+ private void appendDirtyMarker()
+ {
+ SpanElement span = Document.get().createSpanElement();
+ span.setInnerText("*");
+ span.setClassName(styles_.dirtyTabIndicator());
+ label_.getElement().appendChild(span);
+ }
+
+ public void replaceTitle(String title)
+ {
+ label_.setText(title);
+ appendDirtyMarker();
+ }
+
+ public void replaceTooltip(String tooltip)
+ {
+ contentPanel_.setTitle(tooltip);
+ }
+
+ public void replaceIcon(ImageResource icon)
+ {
+ if (contentPanel_.getWidget(0) instanceof Image)
+ contentPanel_.remove(0);
+ contentPanel_.insert(imageForIcon(icon), 0);
+ }
+
+ private Image imageForIcon(ImageResource icon)
+ {
+ Image image = new Image(icon);
+ image.setStylePrimaryName(styles_.docTabIcon());
+ return image;
+ }
+
+ private final Label label_;
+
+ private final HorizontalPanel contentPanel_;
+ }
+ public void replaceDocName(int index,
+ ImageResource icon,
+ String title,
+ String tooltip)
+ {
+ DocTab tab = (DocTab) getTabWidget(index);
+ tab.replaceIcon(icon);
+ tab.replaceTitle(title);
+ tab.replaceTooltip(tooltip);
+ }
+
+ public HandlerRegistration addTabClosingHandler(TabClosingHandler handler)
+ {
+ return addHandler(handler, TabClosingEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addTabCloseHandler(
+ TabCloseHandler handler)
+ {
+ return addHandler(handler, TabCloseEvent.TYPE);
+ }
+
+ public HandlerRegistration addTabClosedHandler(TabClosedHandler handler)
+ {
+ return addHandler(handler, TabClosedEvent.TYPE);
+ }
+
+ public int getTabsEffectiveWidth()
+ {
+ if (getWidgetCount() == 0)
+ return 0;
+
+ Widget leftTabWidget = getTabWidget(0);
+ Widget rightTabWidget = getTabWidget(getWidgetCount()-1);
+ return (rightTabWidget.getAbsoluteLeft() + rightTabWidget.getOffsetWidth())
+ - leftTabWidget.getAbsoluteLeft();
+ }
+
+ public static final int BAR_HEIGHT = 24;
+
+ private final boolean closeableTabs_;
+ private int padding_;
+ private int rightMargin_;
+ private final ThemeStyles styles_;
+ private Animation currentAnimation_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/MinimizedModuleTabLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/theme/MinimizedModuleTabLayoutPanel.java
new file mode 100644
index 0000000..aab0afa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/MinimizedModuleTabLayoutPanel.java
@@ -0,0 +1,76 @@
+/*
+ * MinimizedModuleTabLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+/**
+ * This is the minimized version of the Data|File|Plot|Package|Help group
+ */
+public class MinimizedModuleTabLayoutPanel
+ extends MinimizedWindowFrame
+ implements HasSelectionHandlers<Integer>
+{
+ public MinimizedModuleTabLayoutPanel()
+ {
+ super(null, new HorizontalPanel());
+ addStyleName(ThemeResources.INSTANCE.themeStyles().moduleTabPanel());
+ addStyleName(ThemeResources.INSTANCE.themeStyles().minimized());
+ }
+
+ public void setTabs(String[] tabNames)
+ {
+ HorizontalPanel horiz = (HorizontalPanel) getExtraWidget();
+ horiz.clear();
+
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+ for (int i = 0; i < tabNames.length; i++)
+ {
+ String tabName = tabNames[i];
+ if (tabName == null)
+ continue;
+ ModuleTabLayoutPanel.ModuleTab tab
+ = new ModuleTabLayoutPanel.ModuleTab(tabName, styles, false);
+ tab.addStyleName("gwt-TabLayoutPanelTab");
+ final Integer thisIndex = i;
+ tab.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ SelectionEvent.fire(
+ MinimizedModuleTabLayoutPanel.this,
+ thisIndex);
+ }
+ });
+
+ horiz.add(tab);
+ }
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<Integer> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/MinimizedWindowFrame.java b/src/gwt/src/org/rstudio/core/client/theme/MinimizedWindowFrame.java
new file mode 100644
index 0000000..d33fa75
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/MinimizedWindowFrame.java
@@ -0,0 +1,153 @@
+/*
+ * MinimizedWindowFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.events.HasWindowStateChangeHandlers;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.events.WindowStateChangeHandler;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+public class MinimizedWindowFrame
+ extends Composite
+ implements HasWindowStateChangeHandlers
+{
+ private static class ClickDockLayoutPanel extends DockLayoutPanel
+ {
+ private ClickDockLayoutPanel(Style.Unit unit)
+ {
+ super(unit);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+ }
+
+ public MinimizedWindowFrame(String title)
+ {
+ this(title, null);
+ }
+
+ public MinimizedWindowFrame(String title, Widget extraWidget)
+ {
+ ThemeStyles themeStyles = ThemeResources.INSTANCE.themeStyles();
+
+ layout_ = new ClickDockLayoutPanel(Style.Unit.PX);
+ layout_.setStylePrimaryName(themeStyles.minimizedWindow());
+
+ int leftPadding = title != null ? 8 : 4;
+ layout_.addWest(createDiv(themeStyles.left()), leftPadding);
+ layout_.addEast(createDiv(themeStyles.right()), 8);
+
+ HorizontalPanel inner = new HorizontalPanel();
+ inner.setWidth("100%");
+ inner.setStylePrimaryName(themeStyles.center());
+
+ if (title != null)
+ {
+ Label titleLabel = new Label(title);
+ titleLabel.setStylePrimaryName(themeStyles.title());
+
+ SimplePanel headerPanel = new SimplePanel();
+ headerPanel.setStylePrimaryName(themeStyles.primaryWindowFrameHeader());
+ headerPanel.setWidget(titleLabel);
+
+ inner.add(headerPanel);
+ if (extraWidget == null)
+ {
+ inner.setCellWidth(headerPanel, "100%");
+ }
+ }
+
+ if (extraWidget != null)
+ {
+ extraWidget_ = extraWidget;
+ inner.add(extraWidget);
+ inner.setCellWidth(extraWidget, "100%");
+ }
+
+ HTML minimize = createDiv(themeStyles.minimize());
+ minimize.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ fireEvent(new WindowStateChangeEvent(WindowState.MINIMIZE));
+ }
+ });
+ inner.add(minimize);
+
+ HTML maximize = createDiv(themeStyles.maximize());
+ maximize.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ fireEvent(new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+ });
+ inner.add(maximize);
+
+ layout_.add(inner);
+
+ layout_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ fireEvent(new WindowStateChangeEvent(WindowState.NORMAL));
+ }
+ });
+
+ initWidget(layout_);
+ }
+
+ protected Widget getExtraWidget()
+ {
+ return extraWidget_;
+ }
+
+ private HTML createDiv(String className)
+ {
+ HTML html = new HTML();
+ html.setStylePrimaryName(className);
+ return html;
+ }
+
+ public HandlerRegistration addWindowStateChangeHandler(
+ WindowStateChangeHandler handler)
+ {
+ return addHandler(handler, WindowStateChangeEvent.TYPE);
+ }
+
+ public int getDesiredHeight()
+ {
+ return 30;
+ }
+
+ private ClickDockLayoutPanel layout_;
+ private Widget extraWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/ModuleTabLayoutPanel.java b/src/gwt/src/org/rstudio/core/client/theme/ModuleTabLayoutPanel.java
new file mode 100644
index 0000000..24e7c90
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/ModuleTabLayoutPanel.java
@@ -0,0 +1,174 @@
+/*
+ * ModuleTabLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.DoubleClickState;
+
+public class ModuleTabLayoutPanel extends TabLayoutPanel
+{
+ public static class ModuleTab extends Composite
+ {
+ public ModuleTab(String title, ThemeStyles styles, boolean canClose)
+ {
+ layoutPanel_ = new HorizontalPanel();
+ layoutPanel_.setStylePrimaryName(styles.tabLayout());
+
+ HTML left = new HTML();
+ left.setStylePrimaryName(styles.tabLayoutLeft());
+ layoutPanel_.add(left);
+
+ HorizontalPanel center = new HorizontalPanel();
+ center.setStylePrimaryName(styles.tabLayoutCenter());
+ Label label = new Label(title, false);
+ center.add(label);
+ if (canClose)
+ {
+ closeButton_ = new Image(ThemeResources.INSTANCE.closeTab());
+ closeButton_.setStylePrimaryName(styles.closeTabButton());
+ center.add(closeButton_);
+ }
+ layoutPanel_.add(center);
+
+ HTML right = new HTML();
+ right.setStylePrimaryName(styles.tabLayoutRight());
+ layoutPanel_.add(right);
+
+ addDomHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ // Stop double-click of tab from selecting the tab title text
+ event.preventDefault();
+ }
+ }, MouseDownEvent.getType());
+
+ initWidget(layoutPanel_);
+ }
+
+ public Widget getWidget()
+ {
+ return super.getWidget();
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addCloseButtonClickHandler(ClickHandler handler)
+ {
+ return closeButton_.addClickHandler(handler);
+ }
+
+ private HorizontalPanel layoutPanel_;
+ private Image closeButton_;
+ }
+
+ public ModuleTabLayoutPanel(final WindowFrame owner)
+ {
+ super(BAR_HEIGHT, Style.Unit.PX);
+ owner_ = owner;
+ styles_ = ThemeResources.INSTANCE.themeStyles();
+ addStyleName(styles_.moduleTabPanel());
+ addDomHandler(new MouseDownHandler() {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ if (!isWithinTopBand(event.getNativeEvent()))
+ return;
+ // Stop click-drag selection from working in top band
+ event.preventDefault();
+ }
+ }, MouseDownEvent.getType());
+ addDomHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ if (!isWithinTopBand(event.getNativeEvent()))
+ return;
+
+ event.preventDefault();
+ event.stopPropagation();
+ if (doubleClickState_.checkForDoubleClick(event.getNativeEvent()))
+ {
+ owner.fireEvent(new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+ }
+ }, ClickEvent.getType());
+ }
+
+ private boolean isWithinTopBand(NativeEvent event)
+ {
+ int absTop = getAbsoluteTop();
+ return absTop + BAR_HEIGHT > event.getClientY();
+ }
+
+ @Override
+ public void add(Widget child, String text)
+ {
+ add(child, text, false);
+ }
+
+ @Override
+ public void add(Widget child, String text, boolean asHtml)
+ {
+ add(child, text, asHtml, null);
+ }
+
+ public void add(final Widget child, String text, boolean asHtml,
+ ClickHandler closeHandler)
+ {
+ if (asHtml)
+ throw new UnsupportedOperationException("HTML tab names not supported");
+
+ ModuleTab tab = new ModuleTab(text, styles_, closeHandler != null);
+ super.add(child, tab);
+
+ if (closeHandler != null)
+ tab.addCloseButtonClickHandler(closeHandler);
+ }
+
+ @Override
+ public void selectTab(int index)
+ {
+ super.selectTab(Math.max(0, Math.min(index, getWidgetCount() - 1)));
+ if (index == 0)
+ owner_.addStyleName(styles_.firstTabSelected());
+ else
+ owner_.removeStyleName(styles_.firstTabSelected());
+ }
+
+ @Override
+ public void add(Widget child, Widget tab)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ private final ThemeStyles styles_;
+
+ private final DoubleClickState doubleClickState_ = new DoubleClickState();
+ public static final int BAR_HEIGHT = 23;
+ private final WindowFrame owner_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/PrimaryWindowFrame.java b/src/gwt/src/org/rstudio/core/client/theme/PrimaryWindowFrame.java
new file mode 100644
index 0000000..26bca7d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/PrimaryWindowFrame.java
@@ -0,0 +1,99 @@
+/*
+ * PrimaryWindowFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.DoubleClickState;
+
+public class PrimaryWindowFrame extends WindowFrame
+{
+ private static class ClickFlowPanel extends FlowPanel implements
+ HasClickHandlers,
+ HasMouseDownHandlers
+ {
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+ }
+
+ public PrimaryWindowFrame(String title,
+ Widget mainWidget)
+ {
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+
+ panel_ = new ClickFlowPanel();
+ panel_.setStylePrimaryName(styles.primaryWindowFrameHeader());
+
+ Label label = new Label(title);
+ label.setStylePrimaryName(styles.title());
+ panel_.addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ }
+ });
+ panel_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ focus();
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (doubleClickState_.checkForDoubleClick(event.getNativeEvent()))
+ fireEvent(new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+ });
+
+ subtitle_ = new Label();
+ subtitle_.setStylePrimaryName(styles.subtitle());
+
+ panel_.add(label);
+ panel_.add(subtitle_);
+
+ setHeaderWidget(panel_);
+
+ setMainWidget(mainWidget);
+ }
+
+ public void setSubtitle(String subtitle)
+ {
+ subtitle_.setText(subtitle);
+ }
+
+ public void addLeftWidget(Widget widget)
+ {
+ panel_.add(widget);
+ }
+
+ private final DoubleClickState doubleClickState_ = new DoubleClickState();
+ private final Label subtitle_;
+ private final ClickFlowPanel panel_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableResources.java b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableResources.java
new file mode 100644
index 0000000..ea0c016
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableResources.java
@@ -0,0 +1,24 @@
+/*
+ * RStudioCellTableResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.user.cellview.client.CellTable;
+
+public interface RStudioCellTableResources extends CellTable.Resources
+{
+ @Source(RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS)
+ @Override
+ RStudioCellTableStyle cellTableStyle();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.css b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.css
new file mode 100644
index 0000000..1131462
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.css
@@ -0,0 +1,143 @@
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+
+ at def selectionBorderWidth 1px;
+
+
+.cellTableWidget {
+ font-size: 9pt;
+ background-color: white;
+ outline: none;
+}
+
+.cellTableFirstColumn {
+}
+
+.cellTableLastColumn {
+
+}
+
+.cellTableFooter {
+ border-top: 1px solid #BBB;
+ padding: 3px 4px;
+ text-align: left;
+ overflow: hidden;
+ cursor: default;
+}
+
+.cellTableHeader {
+ border-bottom: 1px solid #d6dadc;
+ border-right: 1px solid #d6dadc;
+ border-top: 1px solid white;
+ border-left: 1px solid white;
+ padding: 1px 2px;
+ text-align: left;
+ font-weight: normal;
+ font-size: 11px;
+ color: #3c474d;
+ background-color: #eeeff1;
+ overflow: hidden;
+ cursor: default;
+}
+
+.cellTableCell {
+ /* CHANGE: use less padding (originally was 15px) */
+ padding: 2px 4px;
+ overflow: hidden;
+
+ /* CHANGE: use default cursor rather than pointer/caret */
+ cursor: default;
+ border: none;
+ outline: none;
+}
+
+.cellTableCell:first-child {
+ padding: 0 4px;
+}
+.cellTableCell:first-child+.cellTableCell {
+ padding: 0 4px;
+}
+
+.cellTableFirstColumnFooter {
+
+}
+
+.cellTableFirstColumnHeader {
+
+}
+
+.cellTableLastColumnFooter {
+
+}
+
+.cellTableLastColumnHeader {
+
+}
+
+.cellTableSortableHeader {
+ cursor: pointer;
+}
+
+.cellTableSortableHeader:hover {
+ color: #6c6b6b;
+}
+
+.cellTableSortedHeaderAscending {
+
+}
+
+.cellTableSortedHeaderDescending {
+
+}
+
+.cellTableEvenRow {
+ /*background: #ffffff;*/
+}
+
+.cellTableEvenRowCell {
+}
+
+.cellTableOddRow {
+ /*background-color: #eef4fb;*/
+}
+
+.cellTableOddRowCell {
+}
+
+.cellTableHoveredRow {
+ /* CHANGE: Disable hover effects */
+ /* background: #eee; */
+}
+
+.cellTableHoveredRowCell {
+}
+
+
+.cellTableKeyboardSelectedRow {
+}
+
+.cellTableKeyboardSelectedRowCell {
+}
+
+.cellTableSelectedRow {
+ /* CHANGE: use same selected background color as History table */
+ background: #CCC;
+ /* color: white; */
+ height: auto;
+ overflow: auto;
+}
+
+.cellTableSelectedRowCell {
+}
+
+.cellTableWidget:focus .cellTableSelectedRow {
+ background: rgb(146, 193, 240);
+}
+
+.cellTableKeyboardSelectedCell {
+}
+
+.cellTableLoading {
+ margin: 30px;
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.java b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.java
new file mode 100644
index 0000000..3449050
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioCellTableStyle.java
@@ -0,0 +1,22 @@
+/*
+ * RStudioCellTableStyle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.user.cellview.client.CellTable;
+
+public interface RStudioCellTableStyle extends CellTable.Style
+{
+ String RSTUDIO_DEFAULT_CSS = "org/rstudio/core/client/theme/RStudioCellTableStyle.css";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridResources.java b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridResources.java
new file mode 100644
index 0000000..1181c13
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridResources.java
@@ -0,0 +1,25 @@
+/*
+ * RStudioDataGridResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.user.cellview.client.DataGrid;
+import com.google.gwt.user.cellview.client.DataGrid.Style;
+
+public interface RStudioDataGridResources extends DataGrid.Resources
+{
+ @Source(RStudioDataGridStyle.RSTUDIO_DEFAULT_CSS)
+ @Override
+ Style dataGridStyle();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.css b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.css
new file mode 100644
index 0000000..7cfc829
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.css
@@ -0,0 +1,164 @@
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+
+ at def selectionBorderWidth 1px;
+
+
+.dataGridWidget {
+ font-size: 9pt;
+ background-color: white;
+ outline: none;
+}
+
+.dataGridFirstColumn {
+}
+
+.dataGridLastColumn {
+
+}
+
+.dataGridFooter {
+ border-top: 1px solid #BBB;
+ padding: 3px 4px;
+ text-align: left;
+ overflow: hidden;
+ cursor: default;
+}
+
+.dataGridHeader {
+ border-bottom: 1px solid #d6dadc;
+ border-right: 1px solid #d6dadc;
+ border-top: 1px solid white;
+ border-left: 1px solid white;
+ padding: 1px 2px;
+ text-align: left;
+ font-weight: normal;
+ font-size: 11px;
+ color: #3c474d;
+ background-color: #eeeff1;
+ overflow: hidden;
+ cursor: default;
+}
+
+.dataGridCell {
+ /* CHANGE: use less padding (originally was 15px) */
+ padding: 2px 4px;
+ overflow: hidden;
+
+ /* CHANGE: use default cursor rather than pointer/caret */
+ cursor: default;
+
+}
+
+.dataGridCell:first-child {
+ padding: 0 4px;
+}
+.dataGridCell:first-child+.dataGridCell {
+ padding: 0 4px;
+}
+
+.dataGridFirstColumnFooter {
+
+}
+
+.dataGridFirstColumnHeader {
+
+}
+
+.dataGridLastColumnFooter {
+
+}
+
+.dataGridLastColumnHeader {
+
+}
+
+.dataGridSortableHeader {
+ cursor: pointer;
+}
+
+.dataGridSortableHeader:hover {
+ color: #6c6b6b;
+}
+
+.dataGridSortedHeaderAscending {
+
+}
+
+.dataGridSortedHeaderDescending {
+
+}
+
+.dataGridEvenRow {
+ /*background: #ffffff;*/
+}
+
+.dataGridEvenRowCell {
+ border: selectionBorderWidth solid #ffffff;
+}
+
+.dataGridOddRow {
+ /*background-color: #eef4fb;*/
+}
+
+.dataGridOddRowCell {
+ /*border: selectionBorderWidth solid #eef4fb;*/
+ border: selectionBorderWidth solid #ffffff;
+}
+
+.dataGridHoveredRow {
+ /* CHANGE: Disable hover effects */
+ /* background: #eee; */
+}
+
+.dataGridHoveredRowCell {
+ /* CHANGE: Disable hover effects */
+ /* border: selectionBorderWidth solid #eee; */
+}
+
+
+.dataGridKeyboardSelectedRow {
+}
+
+.dataGridKeyboardSelectedRowCell {
+ border-top: 1px dotted gray !important;
+ border-bottom: 1px dotted gray !important;
+}
+.dataGridKeyboardSelectedRowCell:first-child {
+ border-left: 1px dotted gray;
+}
+.dataGridKeyboardSelectedRowCell:first-child + td + td {
+ border-right: 1px dotted gray;
+}
+
+.dataGridSelectedRow {
+ /* CHANGE: use same selected background color as History table */
+ background: #CCC;
+ /* color: white; */
+ height: auto;
+ overflow: auto;
+}
+
+.dataGridSelectedRowCell {
+ /* CHANGE: use same selected background color as History table */
+ border: selectionBorderWidth solid #CCC;
+}
+
+.dataGridWidget:focus .dataGridSelectedRow {
+ background: rgb(146, 193, 240);
+}
+.dataGridWidget:focus .dataGridSelectedRowCell {
+ border-color: rgb(146, 193, 240);
+}
+
+/**
+ * The keyboard selected cell is visible over selection.
+ */
+.dataGridKeyboardSelectedCell {
+ border: selectionBorderWidth solid transparent;
+}
+
+.dataGridLoading {
+ margin: 30px;
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.java b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.java
new file mode 100644
index 0000000..1edb034
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/RStudioDataGridStyle.java
@@ -0,0 +1,22 @@
+/*
+ * RStudioDataGridStyle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.user.cellview.client.DataGrid;
+
+public interface RStudioDataGridStyle extends DataGrid.Style
+{
+ String RSTUDIO_DEFAULT_CSS = "org/rstudio/core/client/theme/RStudioCellTableStyle.css";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/ShadowBorder.java b/src/gwt/src/org/rstudio/core/client/theme/ShadowBorder.java
new file mode 100644
index 0000000..51ce9a8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/ShadowBorder.java
@@ -0,0 +1,105 @@
+/*
+ * ShadowBorder.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+/**
+ * Creates the visual effect of a "window" with shadowed border and
+ * titlebar. This widget isn't designed to contain any content, but
+ * rather be layered behind the content that should be shadowed (see
+ * WindowFrame).
+ */
+class ShadowBorder extends Composite
+{
+ public ShadowBorder()
+ {
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+
+ layout_ = new LayoutPanel();
+
+ addPanel(styles.NW(), LEFT, TOP);
+ addPanel(styles.N(), CENTER, TOP);
+ addPanel(styles.NE(), RIGHT, TOP);
+ addPanel(styles.W(), LEFT, MIDDLE);
+ addPanel(styles.C(), CENTER, MIDDLE);
+ addPanel(styles.E(), RIGHT, MIDDLE);
+ addPanel(styles.SW(), LEFT, BOTTOM);
+ addPanel(styles.S(), CENTER, BOTTOM);
+ addPanel(styles.SE(), RIGHT, BOTTOM);
+
+ initWidget(layout_);
+ }
+
+ private DivElement addPanel(String styleName, int halign, int valign)
+ {
+ DivElement div = Document.get().createDivElement();
+ div.setClassName(styleName);
+
+ Style style = div.getStyle();
+ style.setPosition(Style.Position.ABSOLUTE);
+
+ switch (halign)
+ {
+ case LEFT:
+ style.setPropertyPx("left", 0);
+ style.setPropertyPx("width", 10);
+ break;
+ case CENTER:
+ style.setPropertyPx("left", 10);
+ style.setPropertyPx("right", 10);
+ break;
+ case RIGHT:
+ style.setPropertyPx("right", 0);
+ style.setPropertyPx("width", 10);
+ break;
+ }
+ switch (valign)
+ {
+ case TOP:
+ style.setPropertyPx("top", 0);
+ style.setPropertyPx("height", 26);
+ break;
+ case MIDDLE:
+ style.setPropertyPx("top", 26);
+ style.setPropertyPx("bottom", 26);
+ break;
+ case BOTTOM:
+ style.setPropertyPx("bottom", 0);
+ style.setPropertyPx("height", 26);
+ }
+
+ layout_.getElement().appendChild(div);
+ return div;
+ }
+
+ private final LayoutPanel layout_;
+
+ private static final int LEFT=0, CENTER=1, RIGHT=2;
+ private static final int TOP=0, MIDDLE=1, BOTTOM=2;
+
+ public static final int TOP_SHADOW_WIDTH = 3,
+ LEFT_SHADOW_WIDTH = 3,
+ RIGHT_SHADOW_WIDTH = 3,
+ BOTTOM_SHADOW_WIDTH = 5,
+ TITLEBAR_REGION_BOTTOM = 26,
+ CONTENT_REGION_TOP = 26;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/ThemeFonts.java b/src/gwt/src/org/rstudio/core/client/theme/ThemeFonts.java
new file mode 100644
index 0000000..0f801f9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/ThemeFonts.java
@@ -0,0 +1,74 @@
+/*
+ * ThemeFonts.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.core.client.GWT;
+import org.rstudio.core.client.BrowseCap;
+
+
+public class ThemeFonts
+{
+ private static final ThemeFontLoader fontLoader =
+ GWT.create(ThemeFontLoader.class);
+ private static final String proportionalFont =
+ fontLoader.getProportionalFont();
+ private static final String fixedWidthFont = fontLoader.getFixedWidthFont();
+
+ public static String getProportionalFont()
+ {
+ return proportionalFont;
+ }
+
+ public static String getFixedWidthFont()
+ {
+ return fixedWidthFont;
+ }
+
+ static interface ThemeFontLoader
+ {
+ String getProportionalFont();
+ String getFixedWidthFont();
+ }
+
+ static class DesktopThemeFontLoader implements ThemeFontLoader
+ {
+ public native final String getProportionalFont() /*-{
+ return $wnd.desktop.proportionalFont();
+ }-*/;
+
+ public native final String getFixedWidthFont() /*-{
+ return $wnd.desktop.fixedWidthFont();
+ }-*/;
+ }
+
+ static class WebThemeFontLoader implements ThemeFontLoader
+ {
+ public String getProportionalFont()
+ {
+ String font = BrowseCap.hasUbuntuFonts() ? "Ubuntu, " : "";
+ return font + "\"Lucida Sans\", \"DejaVu Sans\", \"Lucida Grande\", \"Segoe UI\", Verdana, Helvetica, sans-serif";
+ }
+
+ public String getFixedWidthFont()
+ {
+ if (BrowseCap.isMacintosh())
+ return "Monaco, monospace";
+ else if (BrowseCap.isLinux())
+ return "\"Ubuntu Mono\", \"Droid Sans Mono\", \"DejaVu Sans Mono\", monospace";
+ else
+ return "Consolas, \"Lucida Console\", monospace";
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/WindowFrame.java b/src/gwt/src/org/rstudio/core/client/theme/WindowFrame.java
new file mode 100644
index 0000000..4e902be
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/WindowFrame.java
@@ -0,0 +1,354 @@
+/*
+ * WindowFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.layout.RequiresVisibilityChanged;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.BeforeShowCallback;
+import org.rstudio.core.client.widget.CanFocus;
+
+public class WindowFrame extends Composite
+ implements HasWindowStateChangeHandlers,
+ ProvidesResize,
+ RequiresResize,
+ EnsureVisibleHandler,
+ EnsureHeightHandler
+{
+ public WindowFrame(Widget mainWidget)
+ {
+ this();
+ setMainWidget(mainWidget);
+ }
+ public WindowFrame()
+ {
+ final ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+
+ border_ = new ShadowBorder();
+ border_.setSize("100%", "100%");
+
+ borderPositioner_ = new SimplePanel();
+ borderPositioner_.add(border_);
+
+ HTML maximize = new HTML();
+ maximize.setStylePrimaryName(styles.maximize());
+ maximize.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ maximize();
+ }
+ });
+
+ HTML minimize = new HTML();
+ minimize.setStylePrimaryName(styles.minimize());
+ minimize.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ minimize();
+ }
+ });
+
+ frame_ = new LayoutPanel();
+ frame_.setStylePrimaryName(styles.windowframe());
+
+ frame_.add(borderPositioner_);
+ frame_.setWidgetTopBottom(borderPositioner_, 0, Style.Unit.PX,
+ 0, Style.Unit.PX);
+ frame_.setWidgetLeftRight(borderPositioner_, 0, Style.Unit.PX,
+ 0, Style.Unit.PX);
+
+ frame_.add(maximize);
+ frame_.setWidgetTopHeight(maximize,
+ ShadowBorder.TOP_SHADOW_WIDTH + 4, Style.Unit.PX,
+ 14, Style.Unit.PX);
+ frame_.setWidgetRightWidth(maximize,
+ ShadowBorder.RIGHT_SHADOW_WIDTH + 7, Style.Unit.PX,
+ 14, Style.Unit.PX);
+
+ frame_.add(minimize);
+ frame_.setWidgetTopHeight(minimize,
+ ShadowBorder.TOP_SHADOW_WIDTH + 4, Style.Unit.PX,
+ 14, Style.Unit.PX);
+ frame_.setWidgetRightWidth(minimize,
+ ShadowBorder.RIGHT_SHADOW_WIDTH + 25, Style.Unit.PX,
+ 14, Style.Unit.PX);
+
+ initWidget(frame_);
+ }
+
+ @Override
+ public void setVisible(boolean visible)
+ {
+ super.setVisible(visible);
+
+ if (main_ instanceof RequiresVisibilityChanged)
+ ((RequiresVisibilityChanged)main_).onVisibilityChanged(visible);
+ if (fill_ instanceof RequiresVisibilityChanged)
+ ((RequiresVisibilityChanged)fill_).onVisibilityChanged(visible);
+ if (header_ instanceof RequiresVisibilityChanged)
+ ((RequiresVisibilityChanged)header_).onVisibilityChanged(visible);
+ }
+
+ private void maximize()
+ {
+ fireEvent(new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+
+ private void minimize()
+ {
+ fireEvent(new WindowStateChangeEvent(WindowState.MINIMIZE));
+ }
+
+ public HandlerRegistration addWindowStateChangeHandler(
+ WindowStateChangeHandler handler)
+ {
+ return addHandler(handler, WindowStateChangeEvent.TYPE);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(
+ EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ /**
+ * Puts a widget in the main content area (under the title bar).
+ */
+ public void setMainWidget(Widget widget)
+ {
+ if (widget != null)
+ setFillWidget(null);
+ if (header_ == null)
+ setHeaderWidget(previousHeader_);
+
+ if (main_ != null)
+ {
+ frame_.remove(main_);
+ main_ = null;
+
+ if (ensureVisibleRegistration_ != null)
+ {
+ ensureVisibleRegistration_.removeHandler();
+ ensureVisibleRegistration_ = null;
+ }
+
+ if (ensureHeightRegistration_ != null)
+ {
+ ensureHeightRegistration_.removeHandler();
+ ensureHeightRegistration_ = null;
+ }
+ }
+
+ main_ = widget;
+
+ if (main_ != null)
+ {
+ if (main_ instanceof HasEnsureVisibleHandlers)
+ {
+ ensureVisibleRegistration_ =
+ ((HasEnsureVisibleHandlers)main_).addEnsureVisibleHandler(this);
+ }
+
+ if (main_ instanceof HasEnsureHeightHandlers)
+ {
+ ensureHeightRegistration_ =
+ ((HasEnsureHeightHandlers)main_).addEnsureHeightHandler(this);
+ }
+
+ frame_.add(main_);
+ frame_.setWidgetLeftRight(
+ main_,
+ ShadowBorder.LEFT_SHADOW_WIDTH, Style.Unit.PX,
+ ShadowBorder.RIGHT_SHADOW_WIDTH, Style.Unit.PX);
+ frame_.setWidgetTopBottom(main_,
+ ShadowBorder.CONTENT_REGION_TOP, Style.Unit.PX,
+ ShadowBorder.BOTTOM_SHADOW_WIDTH, Style.Unit.PX);
+ }
+ }
+
+ /**
+ * Puts a widget in the header area (the title bar).
+ */
+ public void setHeaderWidget(Widget widget)
+ {
+ if (widget != null)
+ setFillWidget(null);
+
+ if (header_ != null)
+ {
+ frame_.remove(header_);
+ previousHeader_ = header_;
+ header_ = null;
+ }
+
+ header_ = widget;
+
+ if (header_ != null)
+ {
+ frame_.add(header_);
+ frame_.setWidgetLeftRight(
+ header_,
+ ShadowBorder.LEFT_SHADOW_WIDTH, Style.Unit.PX,
+ ShadowBorder.RIGHT_SHADOW_WIDTH + 39, Style.Unit.PX);
+ frame_.setWidgetTopHeight(
+ header_,
+ ShadowBorder.TOP_SHADOW_WIDTH, Style.Unit.PX,
+ ShadowBorder.TITLEBAR_REGION_BOTTOM, Style.Unit.PX);
+ }
+ }
+
+ /**
+ * Puts a widget in the whole space (includes both header and main content
+ * areas--i.e. everything inside the shadow border).
+ */
+ public void setFillWidget(Widget widget)
+ {
+ if (widget != null)
+ {
+ setHeaderWidget(null);
+ setMainWidget(null);
+ }
+
+ if (fill_ != null)
+ {
+ frame_.remove(fill_);
+ fill_ = null;
+
+ if (ensureVisibleRegistration_ != null)
+ {
+ ensureVisibleRegistration_.removeHandler();
+ ensureVisibleRegistration_ = null;
+ }
+
+ if (ensureHeightRegistration_ != null)
+ {
+ ensureHeightRegistration_.removeHandler();
+ ensureHeightRegistration_ = null;
+ }
+ }
+
+ fill_ = widget;
+
+ if (fill_ != null)
+ {
+ if (fill_ instanceof HasEnsureVisibleHandlers)
+ {
+ ensureVisibleRegistration_ =
+ ((HasEnsureVisibleHandlers)fill_).addEnsureVisibleHandler(this);
+ }
+
+ if (fill_ instanceof HasEnsureHeightHandlers)
+ {
+ ensureHeightRegistration_ =
+ ((HasEnsureHeightHandlers)fill_).addEnsureHeightHandler(this);
+ }
+
+ frame_.add(fill_);
+ frame_.setWidgetLeftRight(fill_,
+ ShadowBorder.LEFT_SHADOW_WIDTH, Style.Unit.PX,
+ ShadowBorder.RIGHT_SHADOW_WIDTH, Style.Unit.PX);
+ frame_.setWidgetTopBottom(fill_,
+ ShadowBorder.TOP_SHADOW_WIDTH, Style.Unit.PX,
+ ShadowBorder.BOTTOM_SHADOW_WIDTH, Style.Unit.PX);
+ }
+ }
+
+ public void setContextButton(Widget button, int width, int height)
+ {
+ if (contextButton_ != null)
+ {
+ contextButton_.removeFromParent();
+ contextButton_ = null;
+ }
+
+ if (button != null)
+ {
+ contextButton_ = button;
+ frame_.add(button);
+ frame_.setWidgetRightWidth(button, 48, Unit.PX, width, Unit.PX);
+ frame_.setWidgetTopHeight(button, 3, Unit.PX, height, Unit.PX);
+ // Without z-index, the header widget will obscure the context button
+ // if the former is set after the latter.
+ frame_.getWidgetContainerElement(button).getStyle().setZIndex(10);
+ }
+ }
+
+ public void onResize()
+ {
+ if (frame_ != null)
+ frame_.onResize();
+ }
+
+ public void focus()
+ {
+ if (main_ != null)
+ {
+ if (main_ instanceof CanFocus)
+ ((CanFocus)main_).focus();
+ }
+ else if (fill_ != null)
+ {
+ if (fill_ instanceof CanFocus)
+ ((CanFocus)fill_).focus();
+ }
+ }
+
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ if (!isVisible())
+ fireEvent(new WindowStateChangeEvent(WindowState.NORMAL));
+ }
+
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ fireEvent(event);
+ }
+
+ public void onBeforeShow()
+ {
+ if (main_ instanceof BeforeShowCallback)
+ ((BeforeShowCallback)main_).onBeforeShow();
+ if (fill_ instanceof BeforeShowCallback)
+ ((BeforeShowCallback)fill_).onBeforeShow();
+ }
+
+ public Widget getFillWidget()
+ {
+ return fill_;
+ }
+
+ private final LayoutPanel frame_;
+ private final ShadowBorder border_;
+ private final SimplePanel borderPositioner_;
+ private Widget main_;
+ private Widget header_;
+ private Widget fill_;
+ private Widget contextButton_;
+ private HandlerRegistration ensureVisibleRegistration_;
+ private HandlerRegistration ensureHeightRegistration_;
+ private Widget previousHeader_;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/ThemeResources.java b/src/gwt/src/org/rstudio/core/client/theme/res/ThemeResources.java
new file mode 100644
index 0000000..3b5997c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/res/ThemeResources.java
@@ -0,0 +1,194 @@
+/*
+ * ThemeResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme.res;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+
+public interface ThemeResources extends ClientBundle
+{
+ public static final ThemeResources INSTANCE = GWT.create(ThemeResources.class);
+
+ ThemeStyles themeStyles();
+
+ @Source("dialogTopLeft.png")
+ DataResource dialogTopLeft();
+ @Source("dialogTop.png")
+ DataResource dialogTop();
+ @Source("dialogTopRight.png")
+ DataResource dialogTopRight();
+ @Source("dialogLeft.png")
+ DataResource dialogLeft();
+ @Source("dialogRight.png")
+ DataResource dialogRight();
+ @Source("dialogBottomLeft.png")
+ DataResource dialogBottomLeft();
+ @Source("dialogBottom.png")
+ DataResource dialogBottom();
+ @Source("dialogBottomRight.png")
+ DataResource dialogBottomRight();
+
+ @Source("podTopLeft.png")
+ DataResource podTopLeft();
+ @Source("podTop.png")
+ DataResource podTop();
+ @Source("podTopRight.png")
+ DataResource podTopRight();
+ @Source("podLeft.png")
+ DataResource podLeft();
+ @Source("podRight.png")
+ DataResource podRight();
+ @Source("podBottomLeft.png")
+ DataResource podBottomLeft();
+ @Source("podBottom.png")
+ DataResource podBottom();
+ @Source("podBottomRight.png")
+ DataResource podBottomRight();
+
+ @Source("verticalHandle.png")
+ DataResource verticalHandle();
+ @Source("horizontalHandle.png")
+ DataResource horizontalHandle();
+
+ ImageResource rstudio();
+ ImageResource rstudio_small();
+
+ @Source("backgroundGradient.png")
+ DataResource backgroundGradient();
+
+ ImageResource activeDocTabLeft();
+ ImageResource activeDocTabRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource activeDocTabTile();
+ ImageResource docTabLeft();
+ ImageResource docTabRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource docTabTile();
+ @Source("tabBackground.png")
+ DataResource tabBackground();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Both)
+ ImageResource clear();
+
+ @Source("toolbarBackground.png")
+ DataResource toolbarBackground();
+ @Source("toolbarBackground2.png")
+ DataResource toolbarBackground2();
+ @Source("desktopGlobalToolbarBackground.png")
+ DataResource desktopGlobalToolbarBackground();
+ @Source("webGlobalToolbarLeft.png")
+ DataResource webGlobalToolbarLeft();
+ @Source("webGlobalToolbarRight.png")
+ DataResource webGlobalToolbarRight();
+ @Source("webGlobalToolbarTile.png")
+ DataResource webGlobalToolbarTile();
+
+ @Source("multiPodActiveTabLeft.png")
+ DataResource multiPodActiveTabLeft();
+ @Source("multiPodActiveTabRight.png")
+ DataResource multiPodActiveTabRight();
+ @Source("multiPodActiveTabTile.png")
+ DataResource multiPodActiveTabTile();
+ @Source("multiPodTabLeft.png")
+ DataResource multiPodTabLeft();
+ @Source("multiPodTabRight.png")
+ DataResource multiPodTabRight();
+ @Source("multiPodTop.png")
+ DataResource multiPodTop();
+ @Source("multiPodTopFade.png")
+ DataResource multiPodTopFade();
+
+ ImageResource menuBevel();
+
+ @Source("closeTabSelected.png")
+ ImageResource closeTab();
+
+ ImageResource toolbarSeparator();
+
+ ImageResource menuDownArrow();
+ ImageResource linkDownArrow();
+
+ @Source("maximize.png")
+ DataResource maximize();
+ @Source("maximizeSelected.png")
+ DataResource maximizeSelected();
+ @Source("minimize.png")
+ DataResource minimize();
+ @Source("minimizeSelected.png")
+ DataResource minimizeSelected();
+ @Source("restore.png")
+ DataResource restore();
+ @Source("restoreSelected.png")
+ DataResource restoreSelected();
+
+ @Source("podMinimizedLeft.png")
+ DataResource podMinimizedLeft();
+ @Source("podMinimizedTile.png")
+ DataResource podMinimizedTile();
+ @Source("podMinimizedRight.png")
+ DataResource podMinimizedRight();
+
+ @Source("searchFieldLeft.png")
+ DataResource searchFieldLeft();
+ @Source("searchFieldTile.png")
+ DataResource searchFieldTile();
+ @Source("searchFieldRight.png")
+ DataResource searchFieldRight();
+
+ ImageResource clearSearch();
+
+ @Source("workspaceSectionHeaderTile.png")
+ DataResource workspaceSectionHeaderTile();
+ ImageResource zoomDataset();
+
+ @Source("inlineEditIcon.png")
+ DataResource inlineEditIcon();
+ @Source("inlineDeleteIcon.png")
+ DataResource inlineDeleteIcon();
+
+ ImageResource smallMagGlassIcon();
+ ImageResource dropDownArrow();
+ ImageResource mediumDropDownArrow();
+ ImageResource chevron();
+
+ ImageResource help();
+
+ ImageResource warningSmall();
+ ImageResource infoSmall();
+ ImageResource errorSmall();
+
+ ImageResource codeTransform();
+
+ ImageResource closeChevron();
+
+ ImageResource removePackage();
+
+ ImageResource newsButton();
+
+ @Source("activeBreakpoint.png")
+ DataResource activeBreakpoint();
+ @Source("inactiveBreakpoint.png")
+ DataResource inactiveBreakpoint();
+ @Source("pendingBreakpoint.png")
+ DataResource pendingBreakpoint();
+ @Source("executingLine.png")
+ DataResource executingLine();
+
+ ImageResource menuCheck();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/ThemeStyles.java b/src/gwt/src/org/rstudio/core/client/theme/res/ThemeStyles.java
new file mode 100644
index 0000000..6a88a19
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/res/ThemeStyles.java
@@ -0,0 +1,157 @@
+/*
+ * ThemeStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.theme.res;
+
+import com.google.gwt.resources.client.CssResource;
+
+/**
+ * .docTab
+ * %table.docTable
+ * %tr
+ * %td.docTabLayoutLeft
+ * %td.docTabLayoutCenter
+ * %td.docTabLayoutRight
+ */
+public interface ThemeStyles extends CssResource
+{
+ public static ThemeStyles INSTANCE = ThemeResources.INSTANCE.themeStyles();
+
+ String NW();
+ String N();
+ String NE();
+ String W();
+ String C();
+ String E();
+ String SW();
+ String S();
+ String SE();
+
+ String windowframe();
+
+ String primaryWindowFrameHeader();
+ String title();
+ String subtitle();
+
+ String docTabPanel();
+ String docTabIcon();
+ String docMenuScroll();
+
+ String closeTabButton();
+
+ String tabLayout();
+ String tabLayoutLeft();
+ String tabLayoutCenter();
+ String tabLayoutRight();
+ String dirtyTab();
+ String dirtyTabIndicator();
+
+ String toolbar();
+ String secondaryToolbar();
+ String globalToolbar();
+ String desktopGlobalToolbar();
+ String webGlobalToolbar();
+ String webHeaderBarCommandsProjectMenu();
+ String toolbarButton();
+ String noLabel();
+ String toolbarButtonPushed();
+ String emptyProjectMenu();
+
+ String scrollableMenuBar();
+
+ String moduleTabPanel();
+ String minimized();
+
+ String firstTabSelected();
+
+ String toolbarSeparator();
+
+ String toolbarButtonMenu();
+ String toolbarButtonMenuOnly();
+ String toolbarButtonLabel();
+ String toolbarButtonLeftImage();
+ String toolbarButtonRightImage();
+ String toolbarFileLabel();
+
+ String windowFrameToolbarButton();
+
+ String statusBarMenu();
+
+ String maximize();
+ String minimize();
+
+ String left();
+ String right();
+ String center();
+
+ String minimizedWindow();
+
+ String header();
+ String mainMenu();
+
+ String miniToolbar();
+
+ String search();
+ String searchMagGlass();
+ String searchBoxContainer();
+ String searchBoxContainer2();
+ String searchBox();
+ String clearSearch();
+
+ String dialogBottomPanel();
+
+ String dialogMessage();
+ String sessionAbendMessage();
+ String applicationHeaderStrong();
+
+ String environmentDataFrameCol();
+
+ String odd();
+
+ String linkDownArrow();
+
+ String showFile();
+ String showFileFixed();
+
+ String fileUploadPanel();
+ String fileUploadField();
+ String fileUploadTipLabel();
+
+ String fileList();
+ String parentDirIcon();
+
+ String locatorPanel();
+
+ String multiPodUtilityArea();
+
+ String tabOverflowPopup();
+
+ String miniDialogPopupPanel();
+ String miniDialogContainer();
+ String miniDialogCaption();
+ String miniDialogTools();
+
+ String selectWidget();
+ String textBoxWithButton();
+
+ String selectableText();
+ String forceMacScrollbars();
+
+ String adornedText();
+
+ String fullscreenCaptionIcon();
+ String fullscreenCaptionLabel();
+
+ String presentationNavigatorLabel();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/activeBreakpoint.png b/src/gwt/src/org/rstudio/core/client/theme/res/activeBreakpoint.png
new file mode 100644
index 0000000..cc90024
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/activeBreakpoint.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabLeft.png
new file mode 100755
index 0000000..499e5d8
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabRight.png
new file mode 100755
index 0000000..061f762
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabTile.png
new file mode 100755
index 0000000..a8891d2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/activeDocTabTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/backgroundGradient.png b/src/gwt/src/org/rstudio/core/client/theme/res/backgroundGradient.png
new file mode 100755
index 0000000..1e2928e
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/backgroundGradient.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/chevron.png b/src/gwt/src/org/rstudio/core/client/theme/res/chevron.png
new file mode 100644
index 0000000..5a405d2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/chevron.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/clear.gif b/src/gwt/src/org/rstudio/core/client/theme/res/clear.gif
new file mode 100644
index 0000000..c95709f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/clear.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/clearSearch.png b/src/gwt/src/org/rstudio/core/client/theme/res/clearSearch.png
new file mode 100644
index 0000000..fe196ff
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/clearSearch.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/closeChevron.png b/src/gwt/src/org/rstudio/core/client/theme/res/closeChevron.png
new file mode 100644
index 0000000..ef6cf44
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/closeChevron.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/closeTab.png b/src/gwt/src/org/rstudio/core/client/theme/res/closeTab.png
new file mode 100644
index 0000000..f0b7eb9
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/closeTab.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/closeTabSelected.png b/src/gwt/src/org/rstudio/core/client/theme/res/closeTabSelected.png
new file mode 100644
index 0000000..f036142
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/closeTabSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/codeTransform.png b/src/gwt/src/org/rstudio/core/client/theme/res/codeTransform.png
new file mode 100644
index 0000000..7939f2b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/codeTransform.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/desktopGlobalToolbarBackground.png b/src/gwt/src/org/rstudio/core/client/theme/res/desktopGlobalToolbarBackground.png
new file mode 100644
index 0000000..dff41b3
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/desktopGlobalToolbarBackground.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottom.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottom.png
new file mode 100644
index 0000000..3749f03
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottom.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomLeft.png
new file mode 100644
index 0000000..c3d30cf
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomRight.png
new file mode 100644
index 0000000..46e5a68
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogLeft.png
new file mode 100644
index 0000000..1a26c63
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogRight.png
new file mode 100644
index 0000000..08a6e9c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogTop.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTop.png
new file mode 100644
index 0000000..0beb3cb
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTop.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopLeft.png
new file mode 100644
index 0000000..0ab391e
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopRight.png
new file mode 100644
index 0000000..4e7d81f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dialogTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/docTabLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/docTabLeft.png
new file mode 100755
index 0000000..1252b18
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/docTabLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/docTabRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/docTabRight.png
new file mode 100755
index 0000000..c0d0d50
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/docTabRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/docTabTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/docTabTile.png
new file mode 100755
index 0000000..ba9e624
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/docTabTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/dropDownArrow.png b/src/gwt/src/org/rstudio/core/client/theme/res/dropDownArrow.png
new file mode 100644
index 0000000..c6f4feb
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/dropDownArrow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/errorSmall.png b/src/gwt/src/org/rstudio/core/client/theme/res/errorSmall.png
new file mode 100755
index 0000000..650318b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/errorSmall.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/executingLine.png b/src/gwt/src/org/rstudio/core/client/theme/res/executingLine.png
new file mode 100644
index 0000000..9e1c2e2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/executingLine.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/help.png b/src/gwt/src/org/rstudio/core/client/theme/res/help.png
new file mode 100644
index 0000000..ea2aefc
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/help.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/horizontalHandle.png b/src/gwt/src/org/rstudio/core/client/theme/res/horizontalHandle.png
new file mode 100644
index 0000000..235e9e5
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/horizontalHandle.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/inactiveBreakpoint.png b/src/gwt/src/org/rstudio/core/client/theme/res/inactiveBreakpoint.png
new file mode 100644
index 0000000..fdc0ce3
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/inactiveBreakpoint.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/infoSmall.png b/src/gwt/src/org/rstudio/core/client/theme/res/infoSmall.png
new file mode 100755
index 0000000..e9a8268
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/infoSmall.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/inlineDeleteIcon.png b/src/gwt/src/org/rstudio/core/client/theme/res/inlineDeleteIcon.png
new file mode 100755
index 0000000..f1a8625
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/inlineDeleteIcon.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/inlineEditIcon.png b/src/gwt/src/org/rstudio/core/client/theme/res/inlineEditIcon.png
new file mode 100755
index 0000000..80d1dd8
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/inlineEditIcon.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/linkDownArrow.png b/src/gwt/src/org/rstudio/core/client/theme/res/linkDownArrow.png
new file mode 100755
index 0000000..bec31dd
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/linkDownArrow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/maximize.png b/src/gwt/src/org/rstudio/core/client/theme/res/maximize.png
new file mode 100644
index 0000000..78a3ce0
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/maximize.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/maximizeSelected.png b/src/gwt/src/org/rstudio/core/client/theme/res/maximizeSelected.png
new file mode 100644
index 0000000..9a4770a
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/maximizeSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/mediumDropDownArrow.png b/src/gwt/src/org/rstudio/core/client/theme/res/mediumDropDownArrow.png
new file mode 100644
index 0000000..2f38dd5
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/mediumDropDownArrow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/menuBevel.png b/src/gwt/src/org/rstudio/core/client/theme/res/menuBevel.png
new file mode 100644
index 0000000..ff8e858
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/menuBevel.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/menuCheck.png b/src/gwt/src/org/rstudio/core/client/theme/res/menuCheck.png
new file mode 100644
index 0000000..7bf4498
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/menuCheck.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/menuDownArrow.png b/src/gwt/src/org/rstudio/core/client/theme/res/menuDownArrow.png
new file mode 100644
index 0000000..f75fb14
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/menuDownArrow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/minimize.png b/src/gwt/src/org/rstudio/core/client/theme/res/minimize.png
new file mode 100644
index 0000000..25bc3f0
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/minimize.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/minimizeSelected.png b/src/gwt/src/org/rstudio/core/client/theme/res/minimizeSelected.png
new file mode 100644
index 0000000..1e41737
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/minimizeSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabLeft.png
new file mode 100644
index 0000000..8f733af
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabRight.png
new file mode 100644
index 0000000..1e0050f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabTile.png
new file mode 100644
index 0000000..6769b21
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodActiveTabTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabLeft.png
new file mode 100644
index 0000000..6ca4709
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabRight.png
new file mode 100644
index 0000000..b5fd258
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTabRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTop.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTop.png
new file mode 100644
index 0000000..fa41181
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTop.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopFade.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopFade.png
new file mode 100644
index 0000000..3b1df9f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopFade.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopLeft.png
new file mode 100755
index 0000000..0ff5182
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopRight.png
new file mode 100755
index 0000000..0b0ff4f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/multiPodTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/newsButton.png b/src/gwt/src/org/rstudio/core/client/theme/res/newsButton.png
new file mode 100644
index 0000000..45975dd
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/newsButton.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/pendingBreakpoint.png b/src/gwt/src/org/rstudio/core/client/theme/res/pendingBreakpoint.png
new file mode 100644
index 0000000..271c3d1
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/pendingBreakpoint.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podBottom.png b/src/gwt/src/org/rstudio/core/client/theme/res/podBottom.png
new file mode 100644
index 0000000..d6969e4
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podBottom.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podBottomLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/podBottomLeft.png
new file mode 100644
index 0000000..5332665
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podBottomRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/podBottomRight.png
new file mode 100644
index 0000000..bd235af
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/podLeft.png
new file mode 100644
index 0000000..8142a44
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedLeft.png
new file mode 100644
index 0000000..d0cc3e1
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedRight.png
new file mode 100644
index 0000000..de0be3f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedTile.png
new file mode 100755
index 0000000..4c2c56d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podMinimizedTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/podRight.png
new file mode 100644
index 0000000..7f4bdea
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podTop.png b/src/gwt/src/org/rstudio/core/client/theme/res/podTop.png
new file mode 100644
index 0000000..02b88c9
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podTop.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podTopLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/podTopLeft.png
new file mode 100644
index 0000000..7bedfc6
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/podTopRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/podTopRight.png
new file mode 100644
index 0000000..51d833e
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/podTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/removePackage.png b/src/gwt/src/org/rstudio/core/client/theme/res/removePackage.png
new file mode 100644
index 0000000..553326f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/removePackage.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/restore.png b/src/gwt/src/org/rstudio/core/client/theme/res/restore.png
new file mode 100755
index 0000000..d6e00d5
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/restore.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/restoreSelected.png b/src/gwt/src/org/rstudio/core/client/theme/res/restoreSelected.png
new file mode 100755
index 0000000..448ef87
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/restoreSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/rstudio.png b/src/gwt/src/org/rstudio/core/client/theme/res/rstudio.png
new file mode 100644
index 0000000..abfffa4
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/rstudio.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/rstudio_small.png b/src/gwt/src/org/rstudio/core/client/theme/res/rstudio_small.png
new file mode 100644
index 0000000..e80f403
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/rstudio_small.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldLeft.png
new file mode 100644
index 0000000..69f7d59
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldRight.png
new file mode 100644
index 0000000..7ad3650
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldTile.png
new file mode 100644
index 0000000..d6d6066
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/searchFieldTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/smallMagGlassIcon.png b/src/gwt/src/org/rstudio/core/client/theme/res/smallMagGlassIcon.png
new file mode 100644
index 0000000..f7216c2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/smallMagGlassIcon.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/tabBackground.png b/src/gwt/src/org/rstudio/core/client/theme/res/tabBackground.png
new file mode 100644
index 0000000..948ac32
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/tabBackground.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/themeStyles.css b/src/gwt/src/org/rstudio/core/client/theme/res/themeStyles.css
new file mode 100644
index 0000000..810245d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/theme/res/themeStyles.css
@@ -0,0 +1,1123 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external fixedWidthFont;
+ at external ace_editor, ace_text-layer, ace_gutter, ace_gutter-layer, ace_gutter-cell;
+ at external ace_breakpoint, ace_inactive-breakpoint, ace_pending-breakpoint, ace_executing-line;
+ at external ace_sb;
+ at external cueText;
+ at external search;
+ at external highlight, disabled;
+ at external gwt-Button, gwt-Button-DialogAction, gwt-Button-DefaultDialogAction;
+ at external gwt-CheckBox;
+ at external gwt-DialogBox, gwt-DialogBox-ModalDialog;
+ at external gwt-PopupPanelGlass;
+ at external gwt-Label;
+ at external gwt-MenuItem, gwt-MenuItem-selected, subMenuIcon, subMenuIcon-selected;
+ at external gwt-MenuItemSeparator, menuSeparatorInner;
+ at external gwt-MenuBar, gwt-MenuBar-vertical;
+ at external gwt-SplitLayoutPanel-HDragger, gwt-SplitLayoutPanel-VDragger;
+ at external gwt-SuggestBoxPopup, item-selected;
+ at external gwt-TabLayoutPanel-Workbench;
+ at external gwt-TabLayoutPanelTab, gwt-TabLayoutPanelTab-selected;
+ at external gwt-TabLayoutPanelTabs;
+ at external gwt-DecoratedPopupPanel;
+
+ at external windowframe, windowframe-maximized, windowframe-exclusive;
+
+ at external windows, macintosh, linux;
+ at external ubuntu_mono;
+
+ at external dialogTopLeft, dialogTopLeftInner,
+ dialogTopCenter, dialogTopCenterInner,
+ dialogTopRight, dialogTopRightInner,
+ dialogMiddleLeft, dialogMiddleCenter,
+ dialogMiddleCenterInner, dialogMiddleRight, dialogBottomLeft,
+ dialogBottomCenter, dialogBottomRight;
+
+ at external popupTopLeft, popupTopLeftInner,
+ popupTopCenter, popupTopCenterInner,
+ popupTopRight, popupTopRightInner,
+ popupMiddleLeft, popupMiddleCenter,
+ popupMiddleCenterInner, popupMiddleRight, popupBottomLeft,
+ popupBottomCenter, popupBottomRight;
+
+ at external Caption;
+
+ at external EditDialog;
+
+ at external avoid-move-cursor;
+
+ at url DIALOGTOPLEFT dialogTopLeft;
+ at url DIALOGTOP dialogTop;
+ at url DIALOGTOPRIGHT dialogTopRight;
+ at url DIALOGLEFT dialogLeft;
+ at url DIALOGRIGHT dialogRight;
+ at url DIALOGBOTTOMLEFT dialogBottomLeft;
+ at url DIALOGBOTTOM dialogBottom;
+ at url DIALOGBOTTOMRIGHT dialogBottomRight;
+ at url PODTOPLEFT podTopLeft;
+ at url PODTOP podTop;
+ at url PODTOPRIGHT podTopRight;
+ at url PODLEFT podLeft;
+ at url PODRIGHT podRight;
+ at url PODBOTTOMLEFT podBottomLeft;
+ at url PODBOTTOM podBottom;
+ at url PODBOTTOMRIGHT podBottomRight;
+ at url HORIZONTALHANDLE horizontalHandle;
+ at url VERTICALHANDLE verticalHandle;
+ at url BACKGROUNDGRADIENT backgroundGradient;
+ at url MAXIMIZE maximize;
+ at url MAXIMIZESELECTED maximizeSelected;
+ at url RESTORE restore;
+ at url RESTORESELECTED restoreSelected;
+ at url MINIMIZE minimize;
+ at url MINIMIZESELECTED minimizeSelected;
+ at url PODMINIMIZEDLEFT podMinimizedLeft;
+ at url PODMINIMIZEDRIGHT podMinimizedRight;
+ at url PODMINIMIZEDTILE podMinimizedTile;
+
+ at url TABBACKGROUND tabBackground;
+
+ at url TOOLBARBACKGROUND toolbarBackground;
+ at url TOOLBARBACKGROUND2 toolbarBackground2;
+ at url DESKTOPGLOBALTOOLBARBACKGROUND desktopGlobalToolbarBackground;
+ at url WEBGLOBALTOOLBARLEFT webGlobalToolbarLeft;
+ at url WEBGLOBALTOOLBARRIGHT webGlobalToolbarRight;
+ at url WEBGLOBALTOOLBARTILE webGlobalToolbarTile;
+
+ at url MULTIPODACTIVETABLEFT multiPodActiveTabLeft;
+ at url MULTIPODACTIVETABRIGHT multiPodActiveTabRight;
+ at url MULTIPODACTIVETABTILE multiPodActiveTabTile;
+ at url MULTIPODTABLEFT multiPodTabLeft;
+ at url MULTIPODTABRIGHT multiPodTabRight;
+ at url MULTIPODTOP multiPodTop;
+ at url MULTIPODTOPFADE multiPodTopFade;
+
+ at url SEARCHFIELDLEFT searchFieldLeft;
+ at url SEARCHFIELDTILE searchFieldTile;
+ at url SEARCHFIELDRIGHT searchFieldRight;
+
+ at url WORKSPACESECTIONHEADERTILE workspaceSectionHeaderTile;
+
+ at url ACTIVEBREAKPOINT activeBreakpoint;
+ at url INACTIVEBREAKPOINT inactiveBreakpoint;
+ at url PENDINGBREAKPOINT pendingBreakpoint;
+ at url EXECUTINGLINE executingLine;
+
+body.windows, body.macintosh, body.linux {}
+
+body {
+ background: BACKGROUNDGRADIENT repeat-x top #e1e2e5;
+}
+
+body {
+ font-family: proportionalFont;
+ font-size: 12px;
+ -webkit-user-select: none;
+}
+
+select {
+ font-size: 12px;
+}
+
+ at if rstudio.desktop true {
+.macintosh select {
+ font-size: 11px;
+ text-indent: 0.1em;
+}
+}
+
+input[type=text] {
+ font-family: proportionalFont;
+}
+input[type=text][disabled] {
+ background-color: transparent;
+}
+button, input[type="reset"], input[type="button"], input[type="submit"] {
+ color: black;
+}
+.fixedWidthFont {
+ font-family: fixedWidthFont !important;
+}
+
+.gwt-Label {
+ cursor: default;
+}
+
+.selectableText {
+ -webkit-user-select: text;
+ cursor: text;
+}
+
+ at if rstudio.desktop false {
+ at if user.agent safari {
+.macintosh .forceMacScrollbars ::-webkit-scrollbar {
+ -webkit-appearance: none;
+ width: 12px;
+ height: 12px;
+}
+
+.macintosh .forceMacScrollbars ::-webkit-scrollbar-track {
+
+ background: rgb(251,251,251);
+ -webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.35);
+ -webkit-border-radius: 5px;
+}
+
+.macintosh .forceMacScrollbars ::-webkit-scrollbar-thumb {
+
+ border: 2px solid rgb(251,251,251);
+ background: rgba(0,0,0,.22);
+ -webkit-border-radius: 6px;
+ -webkit-background-clip: padding-box;
+}
+}
+ at else {
+.forceMacScrollbars {}
+}
+}
+ at else {
+.forceMacScrollbars {}
+}
+
+.gwt-DialogBox input[type=text] {
+ border: 1px solid #999;
+ height: 17px;
+}
+.gwt-DialogBox .search input[type=text] {
+ border: none;
+ height: 100%;
+}
+
+iframe {
+ border: none;
+}
+
+button {
+ cursor: pointer;
+}
+
+textarea {
+ outline: none;
+}
+
+pre {
+ font-family: fixedWidthFont;
+ font-size: 12px;
+}
+
+.ace_editor {
+ border: none !important;
+}
+
+.ace_editor, .ace_text-layer {
+ font-family: fixedWidthFont !important;
+}
+
+.ace_breakpoint,
+.ace_pending-breakpoint,
+.ace_inactive-breakpoint,
+.ace_executing-line
+{
+ background-position: 10% center;
+ background-repeat: no-repeat;
+}
+
+.ace_breakpoint
+{
+ background-image: ACTIVEBREAKPOINT;
+}
+
+.ace_pending-breakpoint
+{
+ background-image: PENDINGBREAKPOINT;
+}
+
+.ace_inactive-breakpoint
+{
+ background-image: INACTIVEBREAKPOINT;
+}
+
+.ace_executing-line
+{
+ background-image: EXECUTINGLINE;
+}
+
+.ace_sb
+{
+ z-index: 6;
+}
+
+.gwt-MenuBar {
+ cursor: default;
+}
+
+.gwt-MenuBar>table {
+ border-spacing: 0;
+}
+.gwt-MenuBar-vertical {
+ cursor: default;
+}
+
+.gwt-MenuBar .subMenuIcon {
+ width: 7px;
+}
+
+.gwt-MenuItem {
+ padding: 4px;
+ white-space: nowrap;
+}
+.gwt-MenuItem.disabled {
+ color: #888;
+ background-image: none;
+}
+.gwt-MenuItem.disabled img {
+ opacity: 0.3;
+}
+
+.gwt-MenuItemSeparator .menuSeparatorInner{
+ border-top: #BABABA 1px solid;
+ margin-top: 1px;
+ margin-bottom: 2px;
+}
+
+.scrollableMenuBar .gwt-MenuItem {
+ padding-right: 20px;
+}
+
+.highlight {
+ color: #900 !important;
+}
+
+ at if rstudio.desktop false {
+.gwt-PopupPanelGlass {
+ background-color: black;
+ filter: literal("alpha(opacity = 15)") !important;
+ opacity: 0.15; /* non-IE */
+ z-index: 1000;
+}
+}
+
+ at if rstudio.desktop true {
+ at sprite .gwt-PopupPanelGlass {
+ gwt-image: 'clear';
+}
+}
+
+
+.cueText {
+ color: #CCC;
+}
+
+.adornedText {
+ color: #606060;
+}
+
+
+/** Dialogs **/
+
+.miniDialogPopupPanel {
+}
+
+.miniDialogPopupPanel .miniDialogContainer {
+ position: relative;
+ top: -32px;
+ margin-bottom: -32px;
+}
+
+.miniDialogPopupPanel .miniDialogCaption {
+ font-weight: normal;
+ font-size: 11px;
+ cursor: default;
+ margin-bottom: 10px;
+ margin-left: -2px;
+ padding-top: 1px;
+}
+
+.miniDialogPopupPanel .miniDialogTools {
+ position: relative;
+ top: -2px;
+ right: -12px;
+}
+
+.gwt-DialogBox-ModalDialog, .gwt-DecoratedPopupPanel {
+ z-index: 1001;
+}
+
+.gwt-SuggestBoxPopup {
+ z-index: 1002;
+ cursor: default;
+}
+
+.gwt-DialogBox .dialogTopLeft, .miniDialogPopupPanel .popupTopLeft {
+ background: DIALOGTOPLEFT no-repeat;
+}
+.gwt-DialogBox .dialogTopLeft, .gwt-DialogBox .dialogTopLeftInner, .miniDialogPopupPanel .popupTopLeft, .miniDialogPopupPanel .popupTopLeftInner {
+ width: 32px;
+ height: 32px;
+}
+.gwt-DialogBox .dialogTopCenter, .miniDialogPopupPanel .popupTopCenter {
+ background: DIALOGTOP repeat-x;
+ height: 32px;
+}
+.gwt-DialogBox .dialogTopCenterInner .Caption {
+ font-weight: normal;
+ font-size: 11px;
+ cursor: default;
+ padding-top: 7px;
+ margin-top: 1px;
+ margin-left: -2px;
+}
+.gwt-DialogBox .dialogTopRight, .miniDialogPopupPanel .popupTopRight {
+ background: DIALOGTOPRIGHT no-repeat;
+}
+.gwt-DialogBox .dialogTopRight, .gwt-DialogBox .dialogTopRightInner, .miniDialogPopupPanel .popupTopRight, .miniDialogPopupPanel .popupTopRightInner {
+ width: 32px;
+ height: 32px;
+}
+.gwt-DialogBox .dialogMiddleLeft, .miniDialogPopupPanel .popupMiddleLeft {
+ background: DIALOGLEFT repeat-y;
+ width: 32px;
+}
+.gwt-DialogBox .dialogMiddleCenter, .miniDialogPopupPanel .popupMiddleCenter {
+ background: #F3F4F4;
+}
+.gwt-DialogBox .dialogMiddleCenterInner, .miniDialogPopupPanel .popupMiddleCenterInner {
+ margin: 12px -1px -8px -1px;
+ position: relative;
+ z-index: 100;
+}
+
+.dialogBottomPanel {
+ margin-top: 8px;
+}
+
+.gwt-DialogBox .dialogMiddleRight, .miniDialogPopupPanel .popupMiddleRight {
+ background: DIALOGRIGHT repeat-y;
+ width: 32px;
+}
+.gwt-DialogBox .dialogBottomLeft, .miniDialogPopupPanel .popupBottomLeft {
+ background: DIALOGBOTTOMLEFT no-repeat;
+ width: 32px;
+ height: 32px;
+}
+.gwt-DialogBox .dialogBottomCenter, .miniDialogPopupPanel .popupBottomCenter {
+ background: DIALOGBOTTOM repeat-x;
+}
+.gwt-DialogBox .dialogBottomRight, .miniDialogPopupPanel .popupBottomRight {
+ background: DIALOGBOTTOMRIGHT no-repeat;
+ width: 32px;
+ height: 32px;
+}
+
+.header {
+ height: 28px;
+ padding: 0 0 4px 32px;
+ font-size: 11px;
+}
+.header * {
+ white-space: nowrap;
+}
+
+.mainMenu {
+ font-size: 11px;
+ font-weight: bold;
+ color: #494949;
+}
+
+.mainMenu .gwt-MenuItem {
+ padding: 5px 9px 5px 9px;
+ height: 15px;
+ text-shadow: 0px 1px 0px #FFFFFF;
+}
+.mainMenu .gwt-MenuItem-selected {
+ text-shadow: none;
+}
+.mainMenu .gwt-MenuItem-selected span {
+ color: white !important;
+ opacity: 1.0;
+}
+
+.gwt-MenuItem-selected, .subMenuIcon-selected, .gwt-SuggestBoxPopup .item-selected {
+ background-color: #D6E9F8;
+}
+.mainMenu .gwt-MenuItem-selected, .mainMenu .subMenuIcon-selected {
+ background-color: #79B7F1;
+ color: white;
+ height: 3px;
+}
+
+
+.windowframe .maximize {
+ background-image: MAXIMIZE;
+ z-index: 50;
+ cursor: pointer;
+}
+.windowframe-maximized .maximize,
+.windowframe-exclusive .maximize {
+ background-image: RESTORE;
+ z-index: 50;
+}
+.windowframe .minimize {
+ background-image: MINIMIZE;
+ z-index: 50;
+ cursor: pointer;
+}
+.windowframe .maximize:hover {
+ background-image: MAXIMIZESELECTED;
+}
+.windowframe-maximized .maximize:hover,
+.windowframe-exclusive .maximize:hover {
+ background-image: RESTORESELECTED;
+}
+.windowframe .minimize:hover {
+ background-image: MINIMIZESELECTED;
+}
+
+.windowframe-exclusive .minimize {
+ display: none;
+}
+
+.C { background-color: white; }
+.N { background-image: PODTOP; }
+.NE { background-image: PODTOPRIGHT; }
+.E { background-image: PODRIGHT; }
+.SE { background-image: PODBOTTOMRIGHT; }
+.S { background-image: PODBOTTOM; }
+.SW { background-image: PODBOTTOMLEFT; }
+.W { background-image: PODLEFT; }
+.NW { background-image: PODTOPLEFT; }
+
+
+.minimizedWindow .left {
+ width: 8px;
+ background-image: PODMINIMIZEDLEFT;
+}
+.minimizedWindow div {
+ height: 30px;
+}
+.minimizedWindow .right {
+ width: 8px;
+ background-image: PODMINIMIZEDRIGHT;
+}
+.minimizedWindow .minimize {
+ width: 14px;
+ height: 14px;
+ background: RESTORE center no-repeat;
+ margin: 4px 4px 0 0;
+ cursor: pointer;
+}
+.minimizedWindow .maximize {
+ width: 14px;
+ height: 14px;
+ background: MAXIMIZE center no-repeat;
+ margin: 4px 3px 0 0;
+ cursor: pointer;
+}
+.minimizedWindow .maximize:hover {
+ background-image: MAXIMIZESELECTED;
+}
+.minimizedWindow .minimize:hover {
+ background-image: RESTORESELECTED;
+}
+.minimizedWindow .center {
+ background-image: PODMINIMIZEDTILE;
+ padding-top: 3px;
+ padding-bottom: 5px;
+}
+.minimizedWindow .title {
+ margin-left: -4px;
+}
+.minimizedWindow {
+ margin-left: -1px;
+ margin-right: -1px;
+}
+
+
+
+.gwt-SplitLayoutPanel-HDragger, .gwt-SplitLayoutPanel-VDragger {
+ background-color: transparent !important;
+ border: 0px;
+ cursor: move;
+}
+body.avoid-move-cursor .gwt-SplitLayoutPanel-HDragger {
+ cursor: e-resize;
+}
+body.avoid-move-cursor .gwt-SplitLayoutPanel-VDragger {
+ cursor: n-resize;
+}
+.gwt-SplitLayoutPanel-HDragger {
+ background: VERTICALHANDLE no-repeat center;
+}
+.gwt-SplitLayoutPanel-VDragger {
+ background: HORIZONTALHANDLE no-repeat center;
+}
+
+.primaryWindowFrameHeader {
+ padding: 4px 0 0 12px;
+ margin-right: 10px;
+ white-space: nowrap;
+}
+.title, .subtitle {
+ display: inline;
+ color: black;
+ font-size: 11px;
+ font-weight: bold;
+ text-shadow: white 0px 1px 0px;
+ cursor: default;
+}
+.subtitle {
+ color: #999;
+ margin-left: 6px;
+}
+
+.gwt-TabLayoutPanelTab {
+ float: left;
+}
+
+/** Document tabs **/
+/*
+ at def TABSTRIPHEIGHT 24px;
+ at def TABHEIGHT 18px;
+
+.docTabPanel .gwt-TabLayoutPanelTabs {
+ background-image: TABBACKGROUND;
+ height: 100%;
+ padding-left: 6px;
+ font-size: 11px;
+}
+
+.docTabPanel .gwt-TabLayoutPanelTab {
+ cursor: default;
+ margin-right: 2px;
+}
+.docTabPanel .gwt-TabLayoutPanelTab-selected {
+}
+*/
+.docTabPanel .closeTabButton {
+ margin-top: 4px;
+ margin-left: 3px;
+}
+.gwt-TabLayoutPanel-Workbench .gwt-TabLayoutPanelTab .closeTabButton {
+ position: relative;
+ top: 5px;
+ margin-left: 4px;
+}
+.closeTabButton {
+ filter: literal("alpha(opacity = 40)") !important;
+ opacity: 0.4;
+}
+
+.closeTabButton:hover {
+ filter: literal("alpha(opacity = 100)") !important;
+ opacity: 1.0;
+ cursor: pointer;
+}
+.tabLayout {
+}
+.docTabPanel.moduleTabPanel .gwt-TabLayoutPanelTab .gwt-Label {
+ font-weight: normal;
+ padding-top: 0;
+}
+.docTabIcon {
+ margin-top: 2px;
+ margin-right: 3px;
+}
+
+.docMenuScroll {
+ overflow-y: scroll !important;
+ overflow-x: hidden;
+ margin: -4px;
+ padding-right: 15px;
+}
+
+/*
+.docTabPanel .tabLayout {
+ height: TABSTRIPHEIGHT;
+}
+ at sprite .docTabPanel .tabLayoutLeft {
+ gwt-image: 'docTabLeft';
+}
+ at sprite .docTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutLeft {
+ gwt-image: 'activeDocTabLeft';
+}
+
+ at sprite .docTabPanel .tabLayoutCenter {
+ gwt-image: 'docTabTile';
+ padding-right: 2px;
+ padding-left: 6px;
+ vertical-align:middle;
+}
+ at sprite .docTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutCenter {
+ gwt-image: 'activeDocTabTile';
+}
+
+ at sprite .docTabPanel .tabLayoutRight {
+ gwt-image: 'docTabRight';
+}
+ at sprite .docTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutRight {
+ gwt-image: 'activeDocTabRight';
+}
+*/
+.dirtyTab {
+ color: #a00;
+}
+
+.dirtyTabIndicator {
+ display: none;
+}
+.dirtyTab .dirtyTabIndicator {
+ display: inline;
+}
+
+/** Module tabs **/
+.gwt-TabLayoutPanelTab {
+ cursor: default;
+}
+
+.moduleTabPanel .gwt-TabLayoutPanelTabs {
+ font-size: 11px;
+ height: 24px;
+ background: MULTIPODTOP top repeat-x;
+ background-color: white;
+ cursor: default;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab {
+ font-size: 11px;
+}
+.moduleTabPanel.minimized .gwt-TabLayoutPanelTab {
+ margin-top: -1px;
+}
+
+.moduleTabPanel .gwt-TabLayoutPanelTab .tabLayoutLeft {
+ width: 3px;
+ height: 23px;
+ background: MULTIPODTABLEFT right top no-repeat;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab .tabLayoutCenter {
+ height: 23px;
+ padding-left: 6px;
+ padding-right: 6px;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab .tabLayoutRight {
+ width: 3px;
+ height: 23px;
+ background: MULTIPODTABRIGHT left top no-repeat;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab .gwt-Label {
+ font-weight: bold;
+ padding-top: 4px;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutLeft {
+ height: 24px;
+ width: 6px;
+ background: MULTIPODACTIVETABLEFT right top no-repeat;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutCenter {
+ height: 24px;
+ background: MULTIPODACTIVETABTILE top repeat-x;
+ padding-left: 3px;
+ padding-right: 3px;
+}
+.moduleTabPanel .gwt-TabLayoutPanelTab-selected .tabLayoutRight {
+ height: 24px;
+ width: 6px;
+ background: MULTIPODACTIVETABRIGHT left top no-repeat;
+}
+.moduleTabPanel .toolbar {
+ background-image: TOOLBARBACKGROUND2;
+}
+.multiPodUtilityArea {
+ background: MULTIPODTOPFADE left top repeat-y;
+}
+.firstTabSelected {
+}
+
+/** Toolbar **/
+
+.toolbar .gwt-CheckBox {
+ position: relative;
+ top: 1px;
+ margin: 0 9px 0 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+.toolbar {
+ font-size: 11px;
+ width: 100%;
+ height: 23px;
+ background: TOOLBARBACKGROUND repeat-x top;
+ padding: 0 0 0 6px;
+}
+
+.toolbar * {
+ margin-top: 0px;
+}
+
+.toolbar input[type=checkbox] {
+ margin: 0 3px 0 0;
+ max-height: 14px;
+}
+
+.toolbarButton {
+ border: none;
+ background-color: transparent;
+ margin: 0 8px 0 0;
+ padding: 0;
+ vertical-align: top;
+ height: 21px;
+ overflow: hidden;
+ cursor: pointer;
+}
+.toolbarButton.noLabel {
+ margin-right: 4px;
+}
+ at if user.agent safari gecko1_8 {
+ .toolbarButtonPushed {
+ position: relative;
+ top: 1px;
+ }
+}
+.toolbarButtonPushed {
+}
+.toolbarButton[disabled] {
+ /* joecheng: This renders poorly in ie8 */
+ /*filter: literal("alpha(opacity = 50)") !important;*/
+ opacity: 0.3;
+ color: #333;
+ cursor: default;
+}
+
+.toolbarButton[disabled]:active {
+ top: 0px;
+}
+.toolbarSeparator {
+ margin-right: 5px;
+}
+
+.toolbarFileLabel {
+ margin-right: 7px;
+ white-space: nowrap;
+}
+
+
+.secondaryToolbar {
+ background: TABBACKGROUND bottom repeat-x;
+ height: TABSTRIPHEIGHT;
+}
+.toolbarButtonRightImage {
+ display: none;
+}
+.toolbarButtonMenu .toolbarButtonRightImage {
+ display: inline;
+ margin: 4px 0 0 2px;
+}
+.toolbarButtonMenuOnly {
+ position: relative;
+ top: -2px;
+}
+.toolbarButtonMenuOnly.toolbarButtonPushed {
+ position: relative;
+ top: -1px;
+}
+.toolbarButtonLeftImage {
+ margin-top: 2px;
+}
+.toolbarButtonLabel {
+ margin: 4px 0 0 2px;
+ font-family: proportionalFont;
+ font-size: 11px !important;
+ white-space: nowrap;
+}
+
+.globalToolbar {
+ height:27px;
+ z-index: 1;
+}
+
+.globalToolbar .toolbarButton {
+ margin-top: -1px;
+}
+
+.globalToolbar .toolbarButtonMenu .toolbarButtonLabel {
+ margin-left: 4px;
+ margin-right: 2px;
+}
+
+.emptyProjectMenu .toolbarButtonRightImage {
+ margin-left: 4px;
+}
+
+.emptyProjectMenu .toolbarButtonLabel {
+ color: #494949;
+}
+
+ at if user.agent gecko1_8 { .webHeaderBarCommandsProjectMenu {
+ margin-top: 1px;
+ margin-left: 14px;
+ }
+ }
+ @else { .webHeaderBarCommandsProjectMenu {
+ margin-top: 2px;
+ margin-left: 15px;
+ }
+ }
+
+
+.webGlobalToolbar {
+ margin-top: -3px;
+ background: none;
+}
+
+ @if user.agent gecko1_8 { .webGlobalToolbar {
+ margin-left: 53px;
+ padding-right: 49px;
+ }
+ }
+ @else { .webGlobalToolbar {
+ margin-left: 55px;
+ padding-right: 51px;
+ }
+ }
+
+.desktopGlobalToolbar {
+ background: DESKTOPGLOBALTOOLBARBACKGROUND bottom repeat-x;
+}
+
+
+.windowFrameToolbarButton {
+ margin-top: -2px;
+ margin-left: 4px;
+}
+
+.statusBarMenu {
+ font-size: 11px;
+}
+.statusBarMenu .gwt-MenuItem {
+ padding-right: 30px;
+}
+
+.miniToolbar {
+ margin-top: 4px;
+}
+.miniToolbar img {
+ margin-right: 6px;
+}
+
+
+/** Help Search box **/
+.search {
+ position: relative;
+ top: -1px;
+ margin-right: 8px;
+ width: 120px;
+ height: 18px;
+}
+.search .left {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 6px;
+ height: 18px;
+ background: SEARCHFIELDLEFT top no-repeat;
+}
+.search .center {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 6px;
+ right: 6px;
+ background: SEARCHFIELDTILE top repeat-x;
+}
+.search .right {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 6px;
+ height: 18px;
+ background: SEARCHFIELDRIGHT top no-repeat;
+}
+.searchMagGlass {
+ position: absolute;
+ top: 3px;
+ left: 0;
+ width: 14px;
+ height: 13px;
+}
+.clearSearch {
+ position: absolute;
+ top: 4px;
+ right: 0;
+ width: 11px;
+ height: 11px;
+}
+.searchBoxContainer {
+ position: absolute;
+ top: 0;
+ bottom: 1px;
+ left: 18px;
+ right: 18px;
+}
+.searchBoxContainer2 {
+ position: absolute;
+ top: 0;
+ bottom: 1px;
+ left: 0;
+ right: 2px;
+}
+.searchBox {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ margin: 0;
+ outline: none;
+ font-size: 10px;
+ background-color: transparent;
+}
+body.windows .searchBox {
+ font-size: 11px;
+}
+body.ubuntu_mono .searchBox {
+ font-size: 11px;
+}
+
+/** Workbench **/
+
+.dialogMessage {
+ padding-left: 8px;
+ padding-top: 12px;
+ width: 300px;
+ padding-bottom: 12px;
+}
+
+.sessionAbendMessage {
+ padding-left: 8px;
+ width: 300px;
+}
+
+.applicationHeaderStrong {
+ font-weight: bold;
+}
+
+.linkDownArrow {
+ cursor: pointer;
+}
+
+ at sprite td.environmentDataFrameCol {
+ gwt-image: 'zoomDataset';
+ background-position: right center;
+}
+
+.odd {
+ background-color: #f2f2f2;
+}
+
+.EditDialog {
+ border: 1px solid #BBB;
+ background-color: white;
+}
+
+.showFile {
+ font-size: 12px;
+ padding-left: 5px;
+ border: 1px solid #BBB;
+ background-color: white;
+}
+
+.showFileFixed {
+ font-size: 12px;
+ padding-left: 5px;
+ border: 1px solid #BBB;
+ background-color: white;
+}
+
+.fileUploadPanel {
+ width: 350px;
+}
+
+.fileUploadPanel .fileUploadField {
+ margin-left: 7px;
+ margin-top: 10px;
+ margin-bottom: 8px;
+}
+
+.fileUploadPanel .fileUploadTipLabel {
+ border: 1px solid #BBB;
+ padding: 5px;
+ font-size: 11px;
+}
+
+.fileList {
+ font-size: 12px;
+}
+
+.fileList input[type=checkbox] {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.fileList .parentDirIcon {
+ cursor: pointer;
+}
+
+.locatorPanel {
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+
+.locatorPanel:hover {
+ cursor: crosshair;
+}
+
+.tabOverflowPopup .gwt-MenuBar>table {
+ width: 100%;
+}
+.tabOverflowPopup .search {
+ margin: 2px 0 2px 0;
+ min-width: 130px;
+ width: 100%;
+}
+
+.selectWidget {
+ margin-bottom: 12px;
+}
+
+.selectWidget .gwt-Label {
+ margin-right: 4px;
+ margin-left: 4px;
+}
+
+.textBoxWithButton input[type=text] {
+ position: relative;
+ top: 1px;
+ height: 21px;
+ border: 1px solid #999;
+}
+
+.textBoxWithButton .gwt-Label {
+ margin-bottom: 1px;
+}
+
+
+.fullscreenCaptionIcon {
+ margin-top: -1px;
+ margin-right: 5px;
+}
+
+.fullscreenCaptionLabel {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ font-size: 11px;
+ cursor: default;
+}
+
+.presentationNavigatorLabel {
+ max-width: 200px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground.png b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground.png
new file mode 100644
index 0000000..59a225f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground2.png b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground2.png
new file mode 100644
index 0000000..c095eb2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarBackground2.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/toolbarSeparator.png b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarSeparator.png
new file mode 100644
index 0000000..6efd66c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/toolbarSeparator.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/verticalHandle.png b/src/gwt/src/org/rstudio/core/client/theme/res/verticalHandle.png
new file mode 100644
index 0000000..bea071f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/verticalHandle.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/warningSmall.png b/src/gwt/src/org/rstudio/core/client/theme/res/warningSmall.png
new file mode 100644
index 0000000..211c762
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/warningSmall.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarLeft.png b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarLeft.png
new file mode 100755
index 0000000..acb485f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarRight.png b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarRight.png
new file mode 100755
index 0000000..cf96ad1
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarTile.png
new file mode 100755
index 0000000..fcb902b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/webGlobalToolbarTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/workspaceSectionHeaderTile.png b/src/gwt/src/org/rstudio/core/client/theme/res/workspaceSectionHeaderTile.png
new file mode 100755
index 0000000..ecc7680
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/workspaceSectionHeaderTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/theme/res/zoomDataset.png b/src/gwt/src/org/rstudio/core/client/theme/res/zoomDataset.png
new file mode 100644
index 0000000..3657706
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/theme/res/zoomDataset.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/AnchorableFrame.java b/src/gwt/src/org/rstudio/core/client/widget/AnchorableFrame.java
new file mode 100644
index 0000000..c0f1c02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/AnchorableFrame.java
@@ -0,0 +1,108 @@
+/*
+ * AnchorableFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+
+public class AnchorableFrame extends RStudioFrame
+{
+ public AnchorableFrame()
+ {
+ this(true);
+ }
+
+ public AnchorableFrame(boolean autoFocus)
+ {
+ autoFocus_ = autoFocus;
+ setStylePrimaryName("rstudio-HelpFrame");
+ getElement().getStyle().setBackgroundColor("white");
+ }
+
+ public void navigate(final String url)
+ {
+ RepeatingCommand navigateCommand = new RepeatingCommand() {
+ @Override
+ public boolean execute()
+ {
+ if (getIFrame() != null && getWindow() != null)
+ {
+ // if this is the same page but without an anchor qualification
+ // then reload (so we preserve the anchor location)
+ if (isBasePageOfCurrentAnchor(url))
+ {
+ getWindow().reload();
+ }
+ // if it's the same page then set the anchor and force a reload
+ else if (isSamePage(url))
+ {
+ getWindow().replaceLocationHref(url);
+ getWindow().reload();
+ }
+ // otherwise a new url, merely replacing will force a reload
+ else
+ {
+ getWindow().replaceLocationHref(url);
+ }
+
+ if (autoFocus_)
+ getWindow().focus();
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ };
+
+ if (navigateCommand.execute())
+ Scheduler.get().scheduleFixedDelay(navigateCommand, 50);
+ }
+
+ private boolean isBasePageOfCurrentAnchor(String newUrl)
+ {
+ // make sure there is an existing url to compare to
+ String existingUrl = getWindow().getLocationHref();
+ if (existingUrl == null)
+ return false;
+
+ return newUrl.equals(stripAnchor(existingUrl));
+ }
+
+ private boolean isSamePage(String newUrl)
+ {
+ // make sure there is an existing url to compare to
+ String existingUrl = getWindow().getLocationHref();
+ if (existingUrl == null)
+ return false;
+
+ return stripAnchor(newUrl).equals(stripAnchor(existingUrl));
+ }
+
+ private String stripAnchor(String url)
+ {
+ int hashPos = url.lastIndexOf('#');
+ if (hashPos != -1)
+ return url.substring(0, hashPos);
+ else
+ return url;
+ }
+
+ private final boolean autoFocus_;
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/BeforeShowCallback.java b/src/gwt/src/org/rstudio/core/client/widget/BeforeShowCallback.java
new file mode 100644
index 0000000..e9d9fb8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/BeforeShowCallback.java
@@ -0,0 +1,20 @@
+/*
+ * BeforeShowCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface BeforeShowCallback
+{
+ void onBeforeShow();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/BottomScrollPanel.java b/src/gwt/src/org/rstudio/core/client/widget/BottomScrollPanel.java
new file mode 100644
index 0000000..deb2b07
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/BottomScrollPanel.java
@@ -0,0 +1,105 @@
+/*
+ * BottomScrollPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.dom.DomUtils;
+
+/**
+ * An implementation of ScrollPanel that defaults its scroll position to the
+ * bottom. If resized while the scroll position is at the bottom, the scroll
+ * position will stay at the bottom (even if the panel is shrinking in size).
+ */
+public class BottomScrollPanel extends ScrollPanel
+{
+ public BottomScrollPanel()
+ {
+ addScrollHandler(new ScrollHandler()
+ {
+ public void onScroll(ScrollEvent event)
+ {
+ scrolledToBottom_ =
+
+ (getVerticalScrollPosition() ==
+ getMaximumVerticalScrollPosition() ||
+
+ ((getVerticalScrollPosition() + getOffsetHeight()) ==
+ getElement().getScrollHeight()));
+ }
+ });
+ }
+
+ public BottomScrollPanel(Widget widget)
+ {
+ this();
+ setWidget(widget);
+ }
+
+ public boolean isScrolledToBottom()
+ {
+ return scrolledToBottom_;
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ scrollToBottom();
+ }
+
+ @Override
+ public void onResize()
+ {
+ if (scrolledToBottom_)
+ scrollToBottom();
+ super.onResize();
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ DomUtils.scrollToBottom(getElement());
+ scrolledToBottom_ = true;
+ }
+
+ public void onContentSizeChanged()
+ {
+ if (scrolledToBottom_)
+ scrollToBottom();
+ }
+
+ public void saveScrollPosition()
+ {
+ vScroll_ = scrolledToBottom_ ? null : getVerticalScrollPosition();
+ hScroll_ = getHorizontalScrollPosition();
+ }
+
+ public void restoreScrollPosition()
+ {
+ if (vScroll_ == null)
+ scrollToBottom();
+ else
+ setVerticalScrollPosition(vScroll_);
+
+ setHorizontalScrollPosition(hScroll_);
+ }
+
+ private boolean scrolledToBottom_;
+ private Integer vScroll_;
+ private int hScroll_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/CanFocus.java b/src/gwt/src/org/rstudio/core/client/widget/CanFocus.java
new file mode 100644
index 0000000..7c7e6cc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/CanFocus.java
@@ -0,0 +1,20 @@
+/*
+ * CanFocus.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface CanFocus
+{
+ void focus();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.css b/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.css
new file mode 100644
index 0000000..482cbdc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.css
@@ -0,0 +1,9 @@
+.helpImage {
+ margin-right: 4px;
+ margin-bottom: -4px;
+}
+
+.helpLink {
+ font-size: 0.9em;
+ margin-top: 2px;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.java b/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.java
new file mode 100644
index 0000000..d13f662
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/CaptionWithHelp.java
@@ -0,0 +1,125 @@
+/*
+ * CaptionWithHelp.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+public class CaptionWithHelp extends Composite
+{
+ public CaptionWithHelp(String caption, String helpCaption)
+ {
+ this(caption, helpCaption, null);
+ }
+
+ public CaptionWithHelp(String caption,
+ String helpCaption,
+ final String rstudioLinkName)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ rstudioLinkName_ = rstudioLinkName;
+
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.setWidth("100%");
+ captionLabel_ = new Label(caption);
+ panel.add(captionLabel_);
+ helpPanel_ = new HorizontalPanel();
+ Image helpImage = new Image(ThemeResources.INSTANCE.help());
+ helpImage.setStylePrimaryName(styles.helpImage());
+ helpPanel_.add(helpImage);
+ HyperlinkLabel link = new HyperlinkLabel(helpCaption);
+ link.addStyleName(styles.helpLink());
+ link.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ if (rstudioLinkName_ != null)
+ globalDisplay_.openRStudioLink(rstudioLinkName_,
+ includeVersionInfo_);
+ }
+ });
+ helpPanel_.add(link);
+ panel.add(helpPanel_);
+ panel.setCellHorizontalAlignment(helpPanel_,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+
+ initWidget(panel);
+ }
+
+ public void setCaption(String caption)
+ {
+ captionLabel_.setText(caption);
+ }
+
+ public void setRStudioLinkName(String linkName)
+ {
+ rstudioLinkName_ = linkName;
+ }
+
+ public void setIncludeVersionInfo(boolean include)
+ {
+ includeVersionInfo_ = include;
+ }
+
+ public void setHelpVisible(boolean visible)
+ {
+ helpPanel_.setVisible(visible);
+ }
+
+ @Inject
+ void initialize(GlobalDisplay globalDisplay)
+ {
+ globalDisplay_ = globalDisplay;
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("CaptionWithHelp.css")
+ Styles styles();
+ }
+
+ static interface Styles extends CssResource
+ {
+ String helpImage();
+ String helpLink();
+ }
+
+ private static Styles styles = GWT.<Resources>create(Resources.class).styles();
+
+ public static void ensureStylesInjected()
+ {
+ styles.ensureInjected();
+ }
+
+ private Label captionLabel_;
+ private String rstudioLinkName_;
+ private boolean includeVersionInfo_ = true;
+ private HorizontalPanel helpPanel_;
+ private GlobalDisplay globalDisplay_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/CenterPanel.java b/src/gwt/src/org/rstudio/core/client/widget/CenterPanel.java
new file mode 100644
index 0000000..7e5429d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/CenterPanel.java
@@ -0,0 +1,30 @@
+/*
+ * CenterPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class CenterPanel extends DockPanel
+{
+ public CenterPanel(Widget widget)
+ {
+ add(widget, DockPanel.CENTER);
+ setCellHorizontalAlignment(widget, DockPanel.ALIGN_CENTER);
+ setCellVerticalAlignment(widget, DockPanel.ALIGN_MIDDLE);
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/CheckboxLabel.java b/src/gwt/src/org/rstudio/core/client/widget/CheckboxLabel.java
new file mode 100644
index 0000000..268ac87
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/CheckboxLabel.java
@@ -0,0 +1,56 @@
+/*
+ * CheckboxLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+public class CheckboxLabel implements IsWidget
+{
+ public CheckboxLabel(final CheckBox checkbox, String label)
+ {
+ label_ = new Label(label);
+
+ label_.getElement().getStyle().setCursor(Cursor.DEFAULT);
+ label_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+
+ checkbox.setValue(!checkbox.getValue(), true);
+ }
+ });
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return label_;
+ }
+
+ public Label getLabel()
+ {
+ return label_;
+ }
+
+ private final Label label_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ClickImage.java b/src/gwt/src/org/rstudio/core/client/widget/ClickImage.java
new file mode 100644
index 0000000..72893d9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ClickImage.java
@@ -0,0 +1,58 @@
+/*
+ * ClickImage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Image;
+
+public class ClickImage extends Image
+{
+ public ClickImage()
+ {
+ super();
+ commonInit();
+ }
+
+ public ClickImage(ImageResource resource)
+ {
+ super(resource);
+ commonInit();
+ }
+
+ public ClickImage(String url)
+ {
+ super(url);
+ commonInit();
+ }
+
+ public ClickImage(String url, int left, int top, int width, int height)
+ {
+ super(url, left, top, width, height);
+ commonInit();
+ }
+
+ public ClickImage(Element element)
+ {
+ super(element);
+ commonInit();
+ }
+
+ private void commonInit()
+ {
+ getElement().getStyle().setCursor(Cursor.POINTER);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/DialogBuilder.java b/src/gwt/src/org/rstudio/core/client/widget/DialogBuilder.java
new file mode 100644
index 0000000..13cee6e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/DialogBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * DialogBuilder.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface DialogBuilder
+{
+ DialogBuilder addButton(String label);
+ DialogBuilder addButton(String label,
+ Operation operation);
+ DialogBuilder addButton(String label,
+ ProgressOperation operation);
+
+ DialogBuilder setDefaultButton(int index);
+
+ void showModal();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/DirectoryChooserTextBox.java b/src/gwt/src/org/rstudio/core/client/widget/DirectoryChooserTextBox.java
new file mode 100644
index 0000000..27eb9f7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/DirectoryChooserTextBox.java
@@ -0,0 +1,87 @@
+/*
+ * DirectoryChooserTextBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Focusable;
+
+public class DirectoryChooserTextBox extends TextBoxWithButton
+{
+ public DirectoryChooserTextBox(String label,
+ String emptyLabel,
+ Focusable focusAfter)
+ {
+ this(label,
+ emptyLabel,
+ focusAfter,
+ RStudioGinjector.INSTANCE.getFileDialogs(),
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext());
+ }
+
+ public DirectoryChooserTextBox(String label, Focusable focusAfter)
+ {
+ this(label, "", focusAfter);
+ }
+
+
+ public DirectoryChooserTextBox(String label,
+ Focusable focusAfter,
+ FileDialogs fileDialogs,
+ FileSystemContext fsContext)
+ {
+ this(label, "", focusAfter, fileDialogs, fsContext);
+ }
+
+ public DirectoryChooserTextBox(String label,
+ String emptyLabel,
+ final Focusable focusAfter,
+ final FileDialogs fileDialogs,
+ final FileSystemContext fsContext)
+ {
+ super(label, emptyLabel, "Browse...", null);
+
+ addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs.chooseFolder(
+ "Choose Directory",
+ fsContext,
+ FileSystemItem.createDir(getText()),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ setText(input.getPath());
+ indicator.onCompleted();
+ if (focusAfter != null)
+ focusAfter.setFocus(true);
+ }
+ });
+ }
+ });
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/DoubleClickState.java b/src/gwt/src/org/rstudio/core/client/widget/DoubleClickState.java
new file mode 100644
index 0000000..87874ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/DoubleClickState.java
@@ -0,0 +1,71 @@
+/*
+ * DoubleClickState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.NativeEvent;
+import org.rstudio.core.client.Point;
+
+import java.util.Date;
+
+/**
+ * Helper class to make it easy to detect double-clicks in click handlers.
+ */
+public class DoubleClickState
+{
+ public boolean checkForDoubleClick(NativeEvent event)
+ {
+ if (event.getButton() != NativeEvent.BUTTON_LEFT)
+ {
+ lastClickPos_ = null;
+ lastClickTime_ = null;
+ return false;
+ }
+
+ Date now = new Date();
+
+ if (!isDoubleClick(event, now))
+ {
+ lastClickPos_ = new Point(event.getClientX(), event.getClientY());
+ lastClickTime_ = now;
+ return false;
+ }
+ else
+ {
+ // Prevent three clicks from generating two double clicks
+ lastClickPos_ = null;
+ lastClickTime_ = null;
+ return true;
+ }
+ }
+
+ private boolean isDoubleClick(NativeEvent event, Date now)
+ {
+ if (lastClickPos_ == null || lastClickTime_ == null)
+ return false;
+
+ long millisSinceLast = now.getTime() - lastClickTime_.getTime();
+ if (millisSinceLast > 500)
+ return false;
+
+ if (Math.abs(lastClickPos_.x - event.getClientX()) > 3
+ || Math.abs(lastClickPos_.y - event.getClientY()) > 3)
+ return false;
+
+ return true;
+ }
+
+ private Point lastClickPos_;
+ private Date lastClickTime_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/DynamicIFrame.java b/src/gwt/src/org/rstudio/core/client/widget/DynamicIFrame.java
new file mode 100644
index 0000000..ddbf70f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/DynamicIFrame.java
@@ -0,0 +1,64 @@
+/*
+ * DynamicIFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.user.client.ui.Frame;
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.resources.StaticDataResource;
+
+public abstract class DynamicIFrame extends Frame
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("dynamicFrame.html")
+ StaticDataResource dynamicFrame();
+ }
+
+ public DynamicIFrame()
+ {
+ Resources res = GWT.create(Resources.class);
+ setUrl(res.dynamicFrame().getSafeUri().asString());
+ attachCallback();
+ }
+
+ protected abstract void onFrameLoaded();
+
+ protected IFrameElementEx getIFrame()
+ {
+ return getElement().cast();
+ }
+
+ protected WindowEx getWindow()
+ {
+ return getIFrame().getContentWindow();
+ }
+
+ protected final Document getDocument()
+ {
+ return getWindow().getDocument();
+ }
+
+ private native final void attachCallback() /*-{
+ var self = this;
+ var el = this. at com.google.gwt.user.client.ui.UIObject::getElement()();
+ el.__dynamic_init__ = $entry(function() {
+ self. at org.rstudio.core.client.widget.DynamicIFrame::onFrameLoaded()();
+ });
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FastSelectTable.java b/src/gwt/src/org/rstudio/core/client/widget/FastSelectTable.java
new file mode 100644
index 0000000..49912a9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FastSelectTable.java
@@ -0,0 +1,660 @@
+/*
+ * FastSelectTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.NativeWindow;
+import org.rstudio.core.client.widget.events.SelectionChangedEvent;
+import org.rstudio.core.client.widget.events.SelectionChangedHandler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+public class FastSelectTable<TItemInput, TItemOutput, TItemOutput2> extends Widget
+ implements HasAllMouseHandlers, HasClickHandlers, HasAllKeyHandlers
+{
+ public interface ItemCodec<T, TItemOutput, TItemOutput2>
+ {
+ TableRowElement getRowForItem(T entry);
+ void onRowsChanged(TableSectionElement tbody);
+ TItemOutput getOutputForRow(TableRowElement row);
+ TItemOutput2 getOutputForRow2(TableRowElement row);
+ boolean isValueRow(TableRowElement row);
+ boolean hasNonValueRows();
+
+ Integer logicalOffsetToPhysicalOffset(TableElement table, int offset);
+ Integer physicalOffsetToLogicalOffset(TableElement table, int offset);
+ int getLogicalRowCount(TableElement table);
+ }
+
+ public FastSelectTable(ItemCodec<TItemInput, TItemOutput, TItemOutput2> codec,
+ String selectedClassName,
+ boolean focusable,
+ boolean allowMultiSelect)
+ {
+ codec_ = codec;
+ selectedClassName_ = selectedClassName;
+ focusable_ = focusable;
+ allowMultiSelect_ = allowMultiSelect;
+
+ table_ = Document.get().createTableElement();
+ if (focusable_)
+ table_.setTabIndex(0);
+ table_.setCellPadding(0);
+ table_.setCellSpacing(0);
+ table_.setBorder(0);
+ table_.getStyle().setCursor(Cursor.DEFAULT);
+ setElement(table_);
+
+ addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT)
+ return;
+
+ event.preventDefault();
+
+ NativeWindow.get().focus();
+ DomUtils.setActive(getElement());
+
+ Element cell = getEventTargetCell((Event) event.getNativeEvent());
+ if (cell == null)
+ return;
+ TableRowElement row = (TableRowElement) cell.getParentElement();
+ if (codec_.isValueRow(row))
+ handleRowClick(event, row);
+ }
+ });
+ addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.preventDefault();
+ }
+ });
+
+ addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ handleKeyDown(event);
+ }
+ });
+ }
+
+ public void setCellPadding(int padding)
+ {
+ table_.setCellPadding(padding);
+ }
+
+ public void setCellSpacing(int spacing)
+ {
+ table_.setCellSpacing(spacing);
+ }
+
+ public void setOwningScrollPanel(ScrollPanel scrollPanel)
+ {
+ scrollPanel_ = scrollPanel;
+ }
+
+ private void handleRowClick(MouseDownEvent event, TableRowElement row)
+ {
+ int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ modifiers &= ~KeyboardShortcut.ALT; // ALT has no effect
+
+ if (!allowMultiSelect_)
+ modifiers = KeyboardShortcut.NONE;
+
+ // We'll treat Ctrl and Meta as equivalent--and normalize to Ctrl.
+ if (KeyboardShortcut.META == (modifiers & KeyboardShortcut.META))
+ modifiers |= KeyboardShortcut.CTRL;
+ modifiers &= ~KeyboardShortcut.META;
+
+ if (modifiers == KeyboardShortcut.NONE)
+ {
+ // Select only the target row
+ clearSelection();
+ setSelected(row, true);
+ }
+ else if (modifiers == KeyboardShortcut.CTRL)
+ {
+ // Toggle the target row
+ setSelected(row, !isSelected(row));
+ }
+ else
+ {
+ // SHIFT or CTRL+SHIFT
+
+ int target = row.getRowIndex();
+ Integer min = null;
+ Integer max = null;
+ for (TableRowElement selectedRow : selectedRows_)
+ {
+ if (min == null)
+ min = selectedRow.getRowIndex();
+ max = selectedRow.getRowIndex();
+ }
+
+ int offset; // selection offset
+ int length; // selection length
+
+ if (min == null)
+ {
+ // Nothing is selected
+ offset = target;
+ length = 1;
+ }
+ else if (target < min)
+ {
+ // Select target..max
+ offset = target;
+ length = max - target + 1;
+ }
+ else if (target > max)
+ {
+ offset = min;
+ length = target - min + 1;
+ }
+ else
+ {
+ // target is in between min and max
+ if (modifiers == (KeyboardShortcut.CTRL | KeyboardShortcut.SHIFT))
+ {
+ offset = min;
+ length = target - min + 1;
+ }
+ else
+ {
+ offset = target;
+ length = 1;
+ }
+ }
+
+ clearSelection();
+ if (length > 0)
+ {
+ setSelectedPhysical(offset, length, true);
+ }
+ }
+ }
+
+ private void handleKeyDown(KeyDownEvent event)
+ {
+ int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_UP:
+ case KeyCodes.KEY_DOWN:
+ break;
+ default:
+ return;
+ }
+
+ if (!allowMultiSelect_)
+ modifiers = KeyboardShortcut.NONE;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch (modifiers)
+ {
+ case 0:
+ case KeyboardShortcut.SHIFT:
+ break;
+ default:
+ return;
+ }
+
+ sortSelectedRows();
+ int min = table_.getRows().getLength();
+ int max = -1;
+ if (selectedRows_.size() > 0)
+ {
+ min = selectedRows_.get(0).getRowIndex();
+ max = selectedRows_.get(selectedRows_.size() - 1).getRowIndex();
+ }
+
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_UP:
+ {
+ Integer row = findNextValueRow(min, true);
+ if (row != null)
+ {
+ if (modifiers != KeyboardShortcut.SHIFT)
+ clearSelection();
+ setSelectedPhysical(row, 1, true);
+ ensureRowVisible(row);
+ }
+ break;
+ }
+ case KeyCodes.KEY_DOWN:
+ {
+ Integer row = findNextValueRow(max, false);
+ if (row != null)
+ {
+ if (modifiers != KeyboardShortcut.SHIFT)
+ clearSelection();
+ setSelectedPhysical(row, 1, true);
+ ensureRowVisible(row);
+ }
+ break;
+ }
+ }
+ }
+
+ private void ensureRowVisible(final int row)
+ {
+ if (scrollPanel_ != null)
+ DomUtils.ensureVisibleVert(scrollPanel_.getElement(), getRow(row), 0);
+ }
+
+ private Integer findNextValueRow(int physicalRowIndex, boolean up)
+ {
+ int limit = up ? -1 : table_.getRows().getLength();
+ int increment = up ? -1 : 1;
+ for (int i = physicalRowIndex + increment; i != limit; i += increment)
+ {
+ if (codec_.isValueRow(getRow(i)))
+ return i;
+ }
+ return null;
+ }
+
+ public void clearSelection()
+ {
+ while (selectedRows_.size() > 0)
+ setSelected(selectedRows_.get(0), false);
+ }
+
+ public void addItems(Iterable<TItemInput> items, boolean top)
+ {
+ TableSectionElement tbody = Document.get().createTBodyElement();
+ for (TItemInput item : items)
+ tbody.appendChild(codec_.getRowForItem(item));
+ if (top)
+ addToTop(tbody);
+ else
+ getElement().appendChild(tbody);
+
+ codec_.onRowsChanged(tbody);
+ }
+
+ protected void addToTop(TableSectionElement tbody)
+ {
+ getElement().insertFirst(tbody);
+ }
+
+ public void clear()
+ {
+ table_.setInnerText("");
+ selectedRows_.clear();
+ }
+
+ public void focus()
+ {
+ if (focusable_)
+ table_.focus();
+ }
+
+ public int getRowCount()
+ {
+ return codec_.getLogicalRowCount(table_);
+ }
+
+ public void removeTopRows(int rowCount)
+ {
+ if (rowCount <= 0)
+ return;
+
+ NodeList<TableSectionElement> tBodies = table_.getTBodies();
+ for (int i = 0; i < tBodies.getLength(); i++)
+ {
+ rowCount = removeTopRows(tBodies.getItem(i), rowCount);
+ if (rowCount == 0)
+ return;
+ }
+ }
+
+ private int removeTopRows(TableSectionElement tbody, int rowCount)
+ {
+ while (rowCount > 0 && tbody.getRows().getLength() >= 0)
+ {
+ TableRowElement topRow = tbody.getRows().getItem(0);
+ if (codec_.isValueRow(topRow))
+ rowCount--;
+ selectedRows_.remove(topRow);
+ topRow.removeFromParent();
+ }
+
+ if (tbody.getRows().getLength() > 0)
+ codec_.onRowsChanged(tbody);
+ else
+ tbody.removeFromParent();
+
+ return rowCount;
+ }
+
+ public ArrayList<Integer> getSelectedRowIndexes()
+ {
+ sortSelectedRows();
+
+ ArrayList<Integer> results = new ArrayList<Integer>();
+ for (TableRowElement row : selectedRows_)
+ results.add(codec_.physicalOffsetToLogicalOffset(table_,
+ row.getRowIndex()));
+ return results;
+ }
+
+ private boolean isSelected(TableRowElement tr)
+ {
+ return tr.getClassName().contains(selectedClassName_);
+ }
+
+ @Deprecated
+ public void setSelected(int row, boolean selected)
+ {
+ setSelected(getRow(row), selected);
+ }
+
+ public void setSelected(int offset, int length, boolean selected)
+ {
+ if (codec_.hasNonValueRows())
+ {
+ // If the codec might have stuck in some non-value rows, we need
+ // to translate the given offset/length to the actual row
+ // offset/length, which may be different (greater).
+
+ Integer start = codec_.logicalOffsetToPhysicalOffset(table_, offset);
+ Integer end = codec_.logicalOffsetToPhysicalOffset(table_,
+ offset + length);
+
+ if (start == null || end == null)
+ {
+ return;
+ }
+
+ offset = start;
+ length = end - start;
+ }
+
+ setSelectedPhysical(offset, length, selected);
+ }
+
+ private void setSelectedPhysical(int offset, int length, boolean selected)
+ {
+ for (int i = 0; i < length; i++)
+ setSelected(getRow(offset + i), selected);
+ }
+
+ public void setSelected(TableRowElement row, boolean selected)
+ {
+ try
+ {
+ if (row.getParentElement().getParentElement() != table_)
+ return;
+ }
+ catch (NullPointerException npe)
+ {
+ return;
+ }
+
+ boolean isCurrentlySelected = isSelected(row);
+ if (isCurrentlySelected == selected)
+ return;
+
+ if (selected && !codec_.isValueRow(row))
+ return;
+
+ setStyleName(row, selectedClassName_, selected);
+ if (selected)
+ selectedRows_.add(row);
+ else
+ selectedRows_.remove(row);
+
+ if (selected && !allowMultiSelect_)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ fireEvent(new SelectionChangedEvent());
+ }
+ });
+ }
+ }
+
+ public ArrayList<TItemOutput> getSelectedValues()
+ {
+ sortSelectedRows();
+
+ ArrayList<TItemOutput> results = new ArrayList<TItemOutput>();
+ for (TableRowElement row : selectedRows_)
+ results.add(codec_.getOutputForRow(row));
+ return results;
+ }
+
+ private void sortSelectedRows()
+ {
+ Collections.sort(selectedRows_, new Comparator<TableRowElement>()
+ {
+ public int compare(TableRowElement r1, TableRowElement r2)
+ {
+ return r1.getRowIndex() - r2.getRowIndex();
+ }
+ });
+ }
+
+ public ArrayList<TItemOutput2> getSelectedValues2()
+ {
+ sortSelectedRows();
+
+ ArrayList<TItemOutput2> results = new ArrayList<TItemOutput2>();
+ for (TableRowElement row : selectedRows_)
+ results.add(codec_.getOutputForRow2(row));
+ return results;
+ }
+
+ public boolean moveSelectionUp()
+ {
+ if (selectedRows_.size() == 0)
+ return false;
+
+ sortSelectedRows();
+ int top = selectedRows_.get(0).getRowIndex();
+
+ NodeList<TableRowElement> rows = table_.getRows();
+ TableRowElement rowToSelect = null;
+ while (--top >= 0)
+ {
+ TableRowElement row = rows.getItem(top);
+ if (codec_.isValueRow(row))
+ {
+ rowToSelect = row;
+ break;
+ }
+ }
+ if (rowToSelect == null)
+ return false;
+
+ clearSelection();
+ setSelected(rowToSelect, true);
+ return true;
+ }
+
+ public boolean moveSelectionDown()
+ {
+ if (selectedRows_.size() == 0)
+ return false;
+
+ sortSelectedRows();
+ int bottom = selectedRows_.get(selectedRows_.size() - 1).getRowIndex();
+
+ NodeList<TableRowElement> rows = table_.getRows();
+ TableRowElement rowToSelect = null;
+ while (++bottom < rows.getLength())
+ {
+ TableRowElement row = rows.getItem(bottom);
+ if (codec_.isValueRow(row))
+ {
+ rowToSelect = row;
+ break;
+ }
+ }
+ if (rowToSelect == null)
+ return false;
+
+ clearSelection();
+ setSelected(rowToSelect, true);
+ return true;
+ }
+
+ private TableRowElement getRow(int row)
+ {
+ return (TableRowElement) table_.getRows().getItem(row).cast();
+ }
+
+ public TableRowElement getTopRow()
+ {
+ if (table_.getRows().getLength() > 0)
+ return getRow(0);
+ else
+ return null;
+ }
+
+ public ArrayList<TableRowElement> getSelectedRows()
+ {
+ return new ArrayList<TableRowElement>(selectedRows_);
+ }
+
+ public Rectangle getSelectionRect()
+ {
+ if (selectedRows_.size() == 0)
+ return null;
+
+ sortSelectedRows();
+
+ TableRowElement first = selectedRows_.get(0);
+ TableRowElement last = selectedRows_.get(selectedRows_.size() - 1);
+ int top = first.getOffsetTop();
+ int bottom = last.getOffsetTop() + last.getOffsetHeight();
+ int left = first.getOffsetLeft();
+ int width = first.getOffsetWidth();
+ return new Rectangle(left, top, width, bottom - top);
+ }
+
+ protected Element getEventTargetCell(Event event) {
+ Element td = DOM.eventGetTarget(event);
+ for (; td != null; td = DOM.getParent(td)) {
+ // If it's a TD, it might be the one we're looking for.
+ if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
+ // Make sure it's directly a part of this table before returning
+ // it.
+ Element tr = DOM.getParent(td);
+ Element body = DOM.getParent(tr);
+ Element table = DOM.getParent(body);
+ if (table == getElement()) {
+ return td;
+ }
+ }
+ // If we run into this table's body, we're out of options.
+ if (td == getElement()) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public HandlerRegistration addMouseUpHandler(MouseUpHandler handler)
+ {
+ return addDomHandler(handler, MouseUpEvent.getType());
+ }
+
+ public HandlerRegistration addMouseOutHandler(MouseOutHandler handler)
+ {
+ return addDomHandler(handler, MouseOutEvent.getType());
+ }
+
+ public HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
+ {
+ return addDomHandler(handler, MouseOverEvent.getType());
+ }
+
+ public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler)
+ {
+ return addDomHandler(handler, MouseWheelEvent.getType());
+ }
+
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+
+ public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler)
+ {
+ return addDomHandler(handler, MouseMoveEvent.getType());
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addKeyUpHandler(KeyUpHandler handler)
+ {
+ return addDomHandler(handler, KeyUpEvent.getType());
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
+ {
+ return addDomHandler(handler, KeyPressEvent.getType());
+ }
+
+ public HandlerRegistration addSelectionChangedHandler(
+ SelectionChangedHandler handler)
+ {
+ assert !allowMultiSelect_ : "Selection changed event will only fire " +
+ "if multiselect is disabled";
+ return addHandler(handler, SelectionChangedEvent.TYPE);
+ }
+
+ private final ArrayList<TableRowElement> selectedRows_ = new ArrayList<TableRowElement>();
+ private final ItemCodec<TItemInput, TItemOutput, TItemOutput2> codec_;
+ private final TableElement table_;
+ private final String selectedClassName_;
+ private final boolean allowMultiSelect_;
+ private ScrollPanel scrollPanel_;
+ private final boolean focusable_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FileChooserTextBox.java b/src/gwt/src/org/rstudio/core/client/widget/FileChooserTextBox.java
new file mode 100644
index 0000000..09c8935
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FileChooserTextBox.java
@@ -0,0 +1,71 @@
+/*
+ * FileChooserTextBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Focusable;
+
+public class FileChooserTextBox extends TextBoxWithButton
+{
+
+
+ public FileChooserTextBox(String label, Focusable focusAfter)
+ {
+ this(label, "", focusAfter, null);
+ }
+
+ public FileChooserTextBox(String label,
+ String emptyLabel,
+ final Focusable focusAfter,
+ final Command onChosen)
+ {
+ super(label, emptyLabel, "Browse...", null);
+
+
+
+ addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ RStudioGinjector.INSTANCE.getFileDialogs().openFile(
+ "Choose File",
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext(),
+ FileSystemItem.createFile(getText()),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ setText(input.getPath());
+ indicator.onCompleted();
+ if (focusAfter != null)
+ focusAfter.setFocus(true);
+ if (onChosen != null)
+ onChosen.execute();
+ }
+ });
+ }
+ });
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.java b/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.java
new file mode 100644
index 0000000..24c71df
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.java
@@ -0,0 +1,118 @@
+/*
+ * FindTextBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class FindTextBox extends Composite implements HasValue<String>,
+ CanFocus
+{
+ interface MyUiBinder extends UiBinder<Widget, FindTextBox>
+ {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ public FindTextBox(String cueText)
+ {
+ textBox_ = new TextBoxWithCue(cueText);
+ initWidget(uiBinder.createAndBindUi(this));
+
+ setIconVisible(false);
+
+ Style style = getElement().getStyle();
+ style.setPosition(Position.RELATIVE);
+ style.setTop(1, Unit.PX);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return textBox_.addValueChangeHandler(handler);
+ }
+
+ public String getValue()
+ {
+ return textBox_.getText() ;
+ }
+
+ public void setValue(String text)
+ {
+ textBox_.setText(text) ;
+ }
+
+ public void setValue(String text, boolean fireEvents)
+ {
+ textBox_.setValue(text, fireEvents);
+ }
+
+ public void setIconVisible(boolean visible)
+ {
+ textBoxDiv_.getStyle().setLeft(visible ? 18 : 0, Unit.PX);
+ icon_.setVisible(visible);
+ }
+
+ public void focus()
+ {
+ textBox_.setFocus(true);
+ }
+
+ public void setTabIndex(int index)
+ {
+ textBox_.setTabIndex(index);
+ }
+
+ public void addKeyDownHandler(KeyDownHandler keyDownHandler)
+ {
+ textBox_.addKeyDownHandler(keyDownHandler);
+ }
+
+ public void addKeyUpHandler(KeyUpHandler keyUpHandler)
+ {
+ textBox_.addKeyUpHandler(keyUpHandler);
+ }
+
+ public void setOverrideWidth(int pixels)
+ {
+ searchDiv_.getStyle().setWidth(pixels, Unit.PX);
+ }
+
+ public void selectAll()
+ {
+ textBox_.selectAll();
+ }
+
+ @UiField(provided=true)
+ TextBox textBox_;
+ @UiField
+ DivElement searchDiv_;
+ @UiField
+ Image icon_;
+ @UiField
+ DivElement textBoxDiv_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.ui.xml
new file mode 100644
index 0000000..edd8262
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FindTextBox.ui.xml
@@ -0,0 +1,25 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.core.client.theme.res.ThemeResources'/>
+
+ <g:HTMLPanel>
+ <div ui:field='searchDiv_'
+ class="{res.themeStyles.search}"
+ style="margin-right: 3px">
+ <div class="{res.themeStyles.left}"></div>
+ <div class="{res.themeStyles.center}">
+ <g:Image ui:field='icon_'
+ resource='{res.smallMagGlassIcon}'
+ styleName='{res.themeStyles.searchMagGlass}' />
+ <div ui:field='textBoxDiv_'
+ class="{res.themeStyles.searchBoxContainer2}">
+ <g:TextBox ui:field='textBox_'
+ styleName='{res.themeStyles.searchBox}'/>
+ </div>
+ </div>
+ <div class="{res.themeStyles.right}"></div>
+ </div>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FocusContext.java b/src/gwt/src/org/rstudio/core/client/widget/FocusContext.java
new file mode 100644
index 0000000..02d1eb1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FocusContext.java
@@ -0,0 +1,68 @@
+/*
+ * FocusContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.dom.DomUtils;
+
+import com.google.gwt.dom.client.Document;
+
+
+public class FocusContext
+{
+ public void record()
+ {
+ try
+ {
+ originallyActiveElement_ = DomUtils.getActiveElement();
+ }
+ catch(Exception e)
+ {
+ }
+ }
+
+ public void clear()
+ {
+ originallyActiveElement_ = null;
+ }
+
+ public void restore()
+ {
+ try
+ {
+ if (originallyActiveElement_ != null
+ && !originallyActiveElement_.getTagName().equalsIgnoreCase("body"))
+ {
+ Document doc = originallyActiveElement_.getOwnerDocument();
+ if (doc != null)
+ {
+ originallyActiveElement_.focus();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // focus() fail if the element is no longer visible. It's
+ // easier to just catch this than try to detect it.
+
+ // Also originallyActiveElement_.getTagName() can fail with:
+ // "Permission denied to access property 'tagName' from a non-chrome context"
+ // possibly due to Firefox "anonymous div" issue.
+ }
+ originallyActiveElement_ = null;
+ }
+
+ private com.google.gwt.dom.client.Element originallyActiveElement_ = null;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FocusHelper.java b/src/gwt/src/org/rstudio/core/client/widget/FocusHelper.java
new file mode 100644
index 0000000..7db5260
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FocusHelper.java
@@ -0,0 +1,42 @@
+/*
+ * FocusHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.ui.Focusable;
+
+public class FocusHelper
+{
+ public static void setFocusDeferred(final CanFocus canFocus)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ canFocus.focus();
+ }
+ });
+ }
+
+ public static void setFocusDeferred(final Focusable focusable)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ focusable.setFocus(true);
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FontDetector.java b/src/gwt/src/org/rstudio/core/client/widget/FontDetector.java
new file mode 100644
index 0000000..5a40e89
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FontDetector.java
@@ -0,0 +1,90 @@
+/*
+ * FontDetector.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.Debug;
+
+import com.google.gwt.canvas.client.Canvas;
+import com.google.gwt.canvas.dom.client.Context2d;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class FontDetector
+{
+ public static boolean isFontSupported(String fontName)
+ {
+ SimplePanel panel = null;
+ try
+ {
+ // default font name as a reference point
+ final String defaultFontName = "Arial";
+ if (defaultFontName.equals(fontName))
+ return true;
+
+ // make sure canvas is supported
+ if (!Canvas.isSupported())
+ return false;
+
+ // add a temporary div to the dom
+ panel = new SimplePanel();
+ panel.setHeight("200px");
+ panel.getElement().getStyle().setVisibility(Visibility.HIDDEN);
+ panel.getElement().getStyle().setOverflow(Overflow.SCROLL);
+ RootPanel.get().add(panel, -2000, -2000);
+
+ // add a canvas element to the div and get the 2d drawing context
+ final Canvas canvas = Canvas.createIfSupported();
+ canvas.setWidth("512px");
+ canvas.setHeight("64px");
+ canvas.getElement().getStyle().setLeft(400, Unit.PX);
+ canvas.getElement().getStyle().setBackgroundColor("#ffe");
+ panel.add(canvas);
+ final Context2d ctx = canvas.getContext2d();
+ ctx.setFillStyle("#000000");
+
+ // closure to generate a hash for a font
+ class HashGenerator {
+ public String getHash(String fontName)
+ {
+ ctx.setFont("57px " + fontName + ", " + defaultFontName);
+ int width = canvas.getOffsetWidth();
+ int height = canvas.getOffsetHeight();
+ ctx.clearRect(0, 0, width, height);
+ ctx.fillText("TheQuickBrownFox", 2, 50);
+ return canvas.toDataUrl();
+ }};
+
+ // get hashes and compare them
+ HashGenerator hashGenerator = new HashGenerator();
+ String defaultHash = hashGenerator.getHash(defaultFontName);
+ String fontHash = hashGenerator.getHash(fontName);
+ return !defaultHash.equals(fontHash);
+ }
+ catch(Exception ex)
+ {
+ Debug.log(ex.toString());
+ return false;
+ }
+ finally
+ {
+ if (panel != null)
+ RootPanel.get().remove(panel);
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FontSizer.css b/src/gwt/src/org/rstudio/core/client/widget/FontSizer.css
new file mode 100644
index 0000000..4b55fc3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FontSizer.css
@@ -0,0 +1,26 @@
+ at external windows, macintosh, linux, ubuntu_mono, ubuntu_mono_firefox;
+
+.macintosh .normalSize, .macintosh .normalSize td, .macintosh .normalSize pre {
+ line-height: 1.45;
+}
+
+ @if rstudio.desktop true { .windows .normalSize, .windows .normalSize td, .windows .normalSize pre {
+ line-height: 1.20;
+ }
+ }
+ @else { .windows .normalSize, .windows .normalSize td, .windows .normalSize pre {
+ line-height: 1.1;
+ }
+ }
+
+.ubuntu_mono .normalSize, .ubuntu_mono .normalSize td, .ubuntu_mono .normalSize pre {
+ line-height: 1.2;
+}
+
+.ubuntu_mono_firefox .normalSize, .ubuntu_mono_firefox .normalSize td, .ubuntu_mono_firefox .normalSize pre {
+ line-height: 1.1;
+}
+
+.normalSize, .normalSize td, .normalSize pre {
+ line-height: 1.25;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FontSizer.java b/src/gwt/src/org/rstudio/core/client/widget/FontSizer.java
new file mode 100644
index 0000000..4dbdaf6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FontSizer.java
@@ -0,0 +1,91 @@
+/*
+ * FontSizer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.StyleElement;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.UIObject;
+import org.rstudio.core.client.BrowseCap;
+
+
+public class FontSizer
+{
+ static interface Resources extends ClientBundle
+ {
+ @Source("FontSizer.css")
+ Styles styles();
+ }
+
+ static interface Styles extends CssResource
+ {
+ String normalSize();
+ }
+
+ private static Styles styles = GWT.<Resources>create(Resources.class).styles();
+
+ public static void ensureStylesInjected()
+ {
+ styles.ensureInjected();
+ }
+
+ public static void injectStylesIntoDocument(Document doc)
+ {
+ StyleElement style = doc.createStyleElement();
+ style.setType("text/css");
+ style.setInnerText(styles.getText());
+ doc.getBody().appendChild(style);
+ }
+
+ public static void applyNormalFontSize(UIObject object)
+ {
+ object.addStyleName(styles.normalSize());
+ }
+
+ public static void applyNormalFontSize(Element element)
+ {
+ element.addClassName(styles.normalSize());
+ }
+
+ public static String getNormalFontSizeClass()
+ {
+ return styles.normalSize();
+ }
+
+ public static void setNormalFontSize(Document document, double size)
+ {
+ size = size + BrowseCap.getFontSkew();
+
+ final String STYLE_EL_ID = "__rstudio_normal_size";
+
+ Element oldStyle = document.getElementById(STYLE_EL_ID);
+
+ StyleElement style = document.createStyleElement();
+ style.setAttribute("type", "text/css");
+ style.setInnerText("." + styles.normalSize() + ", " +
+ "." + styles.normalSize() + " td, " +
+ "." + styles.normalSize() + " pre" +
+ " {font-size:" + size + "pt !important;}");
+ document.getBody().appendChild(style);
+
+ if (oldStyle != null)
+ oldStyle.removeFromParent();
+
+ style.setId(STYLE_EL_ID);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/FullscreenPopupPanel.java b/src/gwt/src/org/rstudio/core/client/widget/FullscreenPopupPanel.java
new file mode 100644
index 0000000..95b417a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/FullscreenPopupPanel.java
@@ -0,0 +1,150 @@
+/*
+ * FullscreenPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class FullscreenPopupPanel extends ModalPopupPanel
+{
+ public FullscreenPopupPanel(Widget titleWidget,
+ Widget mainWidget,
+ boolean closeOnEscape)
+ {
+ super(false, false, closeOnEscape);
+
+ NineUpBorder border = new NineUpBorder(RES, 32, 20, 17, 20);
+ if (titleWidget != null)
+ addTitleWidget(border, titleWidget);
+ addCloseButton(border);
+ border.setSize("100%", "100%");
+ border.setFillColor("white");
+ border.setWidget(mainWidget);
+ setWidget(border);
+ setGlassEnabled(true);
+
+ Style popupStyle = getElement().getStyle();
+ popupStyle.setZIndex(1001);
+ popupStyle.setPosition(Style.Position.ABSOLUTE);
+ popupStyle.setTop(0, Unit.PX);
+ popupStyle.setBottom(0, Unit.PX);
+ popupStyle.setLeft(0, Unit.PX);
+ popupStyle.setRight(0, Unit.PX);
+
+ Style contentStyle =
+ ((Element) getElement().getFirstChild()).getStyle();
+ contentStyle.setWidth(100, Unit.PCT);
+ contentStyle.setHeight(100, Unit.PCT);
+ }
+
+ private void addTitleWidget(NineUpBorder border, Widget titleWidget)
+ {
+ LayoutPanel layoutPanel = border.getLayoutPanel();
+ layoutPanel.add(titleWidget);
+ layoutPanel.setWidgetTopHeight(titleWidget,
+ 13, Unit.PX,
+ 21, Unit.PX);
+ layoutPanel.setWidgetLeftRight(titleWidget,
+ 27, Unit.PX,
+ 27+RES.close().getWidth() + 15, Unit.PX);
+ }
+
+ private void addCloseButton(NineUpBorder border)
+ {
+ Image closeIcon = new Image(RES.close());
+ closeIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER);
+ closeIcon.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ close();
+ }
+ });
+
+ LayoutPanel layoutPanel = border.getLayoutPanel();
+ layoutPanel.add(closeIcon);
+ layoutPanel.setWidgetTopHeight(closeIcon,
+ 15, Unit.PX,
+ closeIcon.getHeight(), Unit.PX);
+ layoutPanel.setWidgetRightWidth(closeIcon,
+ 27, Unit.PX,
+ closeIcon.getWidth(), Unit.PX);
+ }
+
+ interface Resources extends NineUpBorder.Resources, ClientBundle
+ {
+ @Override
+ @Source("NineUpBorder.css")
+ Styles styles();
+
+ @Override
+ @Source("fullscreenPopupTopLeft.png")
+ ImageResource topLeft();
+
+ @Override
+ @Source("fullscreenPopupTop.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource top();
+
+ @Override
+ @Source("fullscreenPopupTopRight.png")
+ ImageResource topRight();
+
+ @Override
+ @Source("fullscreenPopupLeft.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource left();
+
+ @Override
+ @Source("fullscreenPopupRight.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource right();
+
+ @Override
+ @Source("fullscreenPopupBottomLeft.png")
+ ImageResource bottomLeft();
+
+ @Override
+ @Source("fullscreenPopupBottom.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource bottom();
+
+ @Override
+ @Source("fullscreenPopupBottomRight.png")
+ ImageResource bottomRight();
+
+ @Source("fullscreenPopupClose.png")
+ ImageResource close();
+ }
+
+ public interface Styles extends NineUpBorder.Styles
+ {
+ }
+
+ private static final Resources RES = GWT.<Resources>create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/GlassAttacher.java b/src/gwt/src/org/rstudio/core/client/widget/GlassAttacher.java
new file mode 100644
index 0000000..c1e3bff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/GlassAttacher.java
@@ -0,0 +1,69 @@
+/*
+ * GlassAttacher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Similar to GlassPanel, except it doesn't introduce its own LayoutPanel but
+ * rather re-uses an existing LayoutPanel. We have found that excessively deeply
+ * element nesting is causing performance problems for Ace on some platforms
+ * (specifically desktop on Xubuntu 12.04).
+ */
+public class GlassAttacher
+{
+ public GlassAttacher(LayoutPanel panel)
+ {
+ panel_ = panel;
+ glass_ = createGlassWidget();
+ }
+
+ public void setGlass(boolean on)
+ {
+ if (on)
+ {
+ if (glass_.getParent() != panel_)
+ {
+ panel_.add(glass_);
+ panel_.setWidgetLeftRight(glass_, 0, Unit.PX, 0, Unit.PX);
+ panel_.setWidgetTopBottom(glass_, 0, Unit.PX, 0, Unit.PX);
+ }
+ }
+ else
+ {
+ if (glass_.getParent() == panel_)
+ panel_.remove(glass_);
+ }
+
+ }
+
+ private static Widget createGlassWidget()
+ {
+ HTML glass = new HTML();
+ glass.setSize("100%", "100%");
+ Element glassElem = glass.getElement();
+ glassElem.getStyle().setBackgroundColor("white");
+ glassElem.getStyle().setProperty("opacity", "0.0");
+ glassElem.getStyle().setProperty("filter", "alpha(opacity=0)");
+ return glass;
+ }
+
+ private final LayoutPanel panel_;
+ private final Widget glass_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/GlassPanel.java b/src/gwt/src/org/rstudio/core/client/widget/GlassPanel.java
new file mode 100644
index 0000000..d0c673f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/GlassPanel.java
@@ -0,0 +1,52 @@
+/*
+ * GlassPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.*;
+
+public class GlassPanel extends ResizeComposite
+{
+ public GlassPanel(Widget child)
+ {
+ child_ = child;
+ panel_ = new LayoutPanel();
+
+ panel_.add(child);
+ panel_.setWidgetLeftRight(child, 0, Unit.PX, 0, Unit.PX);
+ panel_.setWidgetTopBottom(child, 0, Unit.PX, 0, Unit.PX);
+
+ glass_ = new GlassAttacher(panel_);
+
+ setGlass(false);
+
+ initWidget(panel_);
+ }
+
+ public void setGlass(boolean enabled)
+ {
+ glass_.setGlass(enabled);
+ }
+
+ public Element getChildContainerElement()
+ {
+ return panel_.getWidgetContainerElement(child_);
+ }
+
+ private LayoutPanel panel_;
+ private GlassAttacher glass_;
+ private final Widget child_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HasButtonMethods.java b/src/gwt/src/org/rstudio/core/client/widget/HasButtonMethods.java
new file mode 100644
index 0000000..4638d7c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HasButtonMethods.java
@@ -0,0 +1,22 @@
+/*
+ * HasButtonMethods.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.HasVisibility;
+
+public interface HasButtonMethods extends HasClickHandlers, HasVisibility
+{
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HasCustomizableToolbar.java b/src/gwt/src/org/rstudio/core/client/widget/HasCustomizableToolbar.java
new file mode 100644
index 0000000..fab0096
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HasCustomizableToolbar.java
@@ -0,0 +1,27 @@
+/*
+ * HasCustomizableToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+public interface HasCustomizableToolbar
+{
+ public interface Customizer
+ {
+ void setToolbarContents(Toolbar toolbar);
+ }
+
+ void installCustomToolbar(Customizer customizer);
+ void removeCustomToolbar();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HeaderBreaksItemCodec.java b/src/gwt/src/org/rstudio/core/client/widget/HeaderBreaksItemCodec.java
new file mode 100644
index 0000000..5923fa1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HeaderBreaksItemCodec.java
@@ -0,0 +1,182 @@
+/*
+ * HeaderBreaksItemCodec.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.*;
+import org.rstudio.core.client.widget.FastSelectTable.ItemCodec;
+
+/**
+ * Provides plumbing for item codecs that may introduce non-value "header"
+ * rows based on differences between two rows.
+ */
+public abstract class HeaderBreaksItemCodec<T, TItemOutput, TItemOutput2>
+ implements ItemCodec<T, TItemOutput, TItemOutput2>
+{
+ /**
+ * Returns true if there should be a break introduced for "row".
+ */
+ protected abstract boolean needsBreak(TableRowElement prevRow,
+ TableRowElement row);
+
+ /**
+ * Add a break for the given row. (This probably means adding a new row above
+ * the given row.)
+ * @param row The row for which a new break should be introduced.
+ * @return Return the number of extra (non-value) rows just introduced.
+ * Make sure that your implementation of isValueRow returns false for
+ * any rows you introduce!
+ */
+ protected abstract int addBreak(TableRowElement row);
+
+ public void onRowsChanged(TableSectionElement tbody)
+ {
+ if (!hasNonValueRows())
+ return;
+
+ TableRowElement lastRow = null;
+
+ Node previousSibling = tbody.getPreviousSibling();
+ if (previousSibling != null
+ && previousSibling.getNodeType() == Node.ELEMENT_NODE
+ && ((Element)previousSibling).getTagName().equalsIgnoreCase("tbody"))
+ {
+ TableSectionElement prevbody = (TableSectionElement) previousSibling;
+ NodeList<TableRowElement> prevrows = prevbody.getRows();
+ if (prevrows.getLength() > 0)
+ {
+ TableRowElement lastRowEl = prevrows.getItem(prevrows.getLength()-1);
+ if (isValueRow(lastRowEl))
+ {
+ lastRow = lastRowEl;
+ }
+ }
+ }
+
+ int totalExtraRows = 0;
+ final NodeList<TableRowElement> rows = tbody.getRows();
+ for (int i = 0; i < rows.getLength(); i++)
+ {
+ TableRowElement row = rows.getItem(i);
+ if (needsBreak(lastRow, row))
+ {
+ int extraRows = addBreak(row);
+ i += extraRows;
+ totalExtraRows += extraRows;
+ }
+
+ lastRow = row;
+ }
+
+ tbody.setPropertyInt(EXTRA_ROWS, totalExtraRows);
+ }
+
+ public Integer logicalOffsetToPhysicalOffset(TableElement table, int offset)
+ {
+ if (!hasNonValueRows())
+ return offset;
+
+ NodeList<TableSectionElement> bodies = table.getTBodies();
+ int skew = 0;
+ int pos = 0;
+ for (int i = 0; i < bodies.getLength(); i++)
+ {
+ TableSectionElement body = bodies.getItem(i);
+ NodeList<TableRowElement> rows = body.getRows();
+ int rowCount = rows.getLength();
+ int extraRows = body.getPropertyInt(EXTRA_ROWS);
+ int max = (pos - skew) + (rowCount - extraRows);
+ if (max <= offset)
+ {
+ // It's safe to skip this whole tbody. These are not the
+ // rows we're looking for.
+ pos += rowCount;
+ skew += extraRows;
+ }
+ else
+ {
+ NodeList<TableRowElement> allRows = table.getRows();
+ for (; pos < allRows.getLength(); pos++)
+ {
+ TableRowElement row = allRows.getItem(pos);
+ if (!isValueRow(row))
+ skew++;
+ else if (offset == (pos - skew))
+ return pos;
+ }
+ }
+ }
+
+ if (pos - skew == offset)
+ return pos;
+ else
+ return null;
+ }
+
+ public Integer physicalOffsetToLogicalOffset(TableElement table, int offset)
+ {
+ if (!hasNonValueRows())
+ return offset;
+
+ if (offset >= table.getRows().getLength())
+ return null;
+
+ NodeList<TableSectionElement> bodies = table.getTBodies();
+ int logicalOffset = 0;
+ for (int i = 0; offset > 0 && i < bodies.getLength(); i++)
+ {
+ TableSectionElement body = bodies.getItem(i);
+ NodeList<TableRowElement> rows = body.getRows();
+ int rowCount = rows.getLength();
+ int extraRows = body.getPropertyInt(EXTRA_ROWS);
+ if (rowCount < offset)
+ {
+ logicalOffset += rowCount - extraRows;
+ offset -= rowCount;
+ }
+ else
+ {
+ // It's in here
+ for (int j = 0; offset > 0 && j < rows.getLength(); j++)
+ {
+ offset--;
+ if (isValueRow(rows.getItem(j)))
+ logicalOffset++;
+ }
+ }
+ }
+
+ return logicalOffset;
+ }
+
+ public int getLogicalRowCount(TableElement table)
+ {
+ if (!hasNonValueRows())
+ return table.getRows().getLength();
+
+ NodeList<TableSectionElement> bodies = table.getTBodies();
+ int logicalOffset = 0;
+ for (int i = 0; i < bodies.getLength(); i++)
+ {
+ TableSectionElement body = bodies.getItem(i);
+ NodeList<TableRowElement> rows = body.getRows();
+ int rowCount = rows.getLength();
+ int extraRows = body.getPropertyInt(EXTRA_ROWS);
+ logicalOffset += rowCount - extraRows;
+ }
+ return logicalOffset;
+ }
+
+ private static final String EXTRA_ROWS = "extrarows";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HelpButton.java b/src/gwt/src/org/rstudio/core/client/widget/HelpButton.java
new file mode 100644
index 0000000..ae237c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HelpButton.java
@@ -0,0 +1,65 @@
+/*
+ * HelpButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+
+public class HelpButton extends Composite
+{
+ public static void addHelpButton(SelectWidget selectWidget,
+ String rstudioLinkName)
+ {
+ selectWidget.addWidget(createHelpButton(rstudioLinkName));
+ }
+
+ public static HelpButton createHelpButton(String rstudioLinkName)
+ {
+ HelpButton helpButton = new HelpButton(rstudioLinkName);
+ Style style = helpButton.getElement().getStyle();
+ style.setMarginTop(3, Unit.PX);
+ style.setMarginLeft(4, Unit.PX);
+ return helpButton;
+ }
+
+ public HelpButton(final String rstudioLinkName)
+ {
+ Image helpImage = new Image(ThemeResources.INSTANCE.help());
+ helpImage.getElement().getStyle().setCursor(Cursor.POINTER);
+ helpImage.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ GlobalDisplay globalDisplay =
+ RStudioGinjector.INSTANCE.getGlobalDisplay();
+ globalDisplay.openRStudioLink(rstudioLinkName);
+ }
+ });
+
+
+ initWidget(helpImage);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HorizontalCenterPanel.java b/src/gwt/src/org/rstudio/core/client/widget/HorizontalCenterPanel.java
new file mode 100644
index 0000000..7e4f4c6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HorizontalCenterPanel.java
@@ -0,0 +1,40 @@
+/*
+ * HorizontalCenterPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class HorizontalCenterPanel extends DockPanel
+{
+ public HorizontalCenterPanel(Widget widget, int verticalOffset)
+ {
+ widget_ = widget;
+ VerticalPanel verticalPadWidget = new VerticalPanel();
+ add(verticalPadWidget, DockPanel.NORTH);
+ setCellHeight(verticalPadWidget, verticalOffset + "px");
+ add(widget_, DockPanel.CENTER);
+ setCellHorizontalAlignment(widget, DockPanel.ALIGN_CENTER);
+ }
+
+ protected Widget getWidget()
+ {
+ return widget_;
+ }
+
+ private final Widget widget_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HtmlFormModalDialog.java b/src/gwt/src/org/rstudio/core/client/widget/HtmlFormModalDialog.java
new file mode 100644
index 0000000..b92c82b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HtmlFormModalDialog.java
@@ -0,0 +1,138 @@
+/*
+ * HtmlFormModalDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+
+
+public abstract class HtmlFormModalDialog<T> extends ModalDialogBase
+{
+ public HtmlFormModalDialog(String title,
+ final String progressMessage,
+ String actionURL,
+ final OperationWithInput<T> operation)
+ {
+ super(new FormPanel());
+ setText(title);
+
+ final FormPanel formPanel = (FormPanel)getContainerPanel();
+ formPanel.getElement().getStyle().setProperty("margin", "0px");
+ formPanel.getElement().getStyle().setProperty("padding", "0px");
+ formPanel.setAction(actionURL);
+ setFormPanelEncodingAndMethod(formPanel);
+
+ final ProgressIndicator progressIndicator = addProgressIndicator();
+
+ ThemedButton okButton = new ThemedButton("OK", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ try
+ {
+ formPanel.submit();
+ }
+ catch (final JavaScriptException e)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ if ("Access is denied.".equals(
+ StringUtil.notNull(e.getDescription()).trim()))
+ {
+ progressIndicator.onError(
+ "Please use a complete file path.");
+ }
+ else
+ {
+ Debug.log(e.toString());
+ progressIndicator.onError(e.getDescription());
+ }
+ }
+ });
+ }
+ catch (final Exception e)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ Debug.log(e.toString());
+ progressIndicator.onError(e.getMessage());
+ }
+ });
+ }
+ }
+ });
+ addOkButton(okButton);
+ addCancelButton();
+
+ formPanel.addSubmitHandler(new SubmitHandler() {
+ public void onSubmit(SubmitEvent event) {
+ if (validate())
+ {
+ progressIndicator.onProgress(progressMessage);
+ }
+ else
+ {
+ event.cancel();
+ }
+ }
+ });
+
+ formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() {
+ public void onSubmitComplete(SubmitCompleteEvent event) {
+
+ String resultsText = event.getResults();
+ if (resultsText != null)
+ {
+ try
+ {
+ T results = parseResults(resultsText);
+ progressIndicator.onCompleted();
+ operation.execute(results);
+ }
+ catch(Exception e)
+ {
+ progressIndicator.onError(e.getMessage());
+ }
+ }
+ else
+ {
+ progressIndicator.onError(
+ "Unexpected empty response from server");
+ }
+ }
+ });
+ }
+
+ protected void setFormPanelEncodingAndMethod(FormPanel formPanel)
+ {
+ formPanel.setEncoding(FormPanel.ENCODING_URLENCODED);
+ formPanel.setMethod(FormPanel.METHOD_POST);
+ }
+
+ protected abstract boolean validate();
+ protected abstract T parseResults(String results) throws Exception;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/HyperlinkLabel.java b/src/gwt/src/org/rstudio/core/client/widget/HyperlinkLabel.java
new file mode 100644
index 0000000..5efcf2b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/HyperlinkLabel.java
@@ -0,0 +1,103 @@
+/*
+ * HyperlinkLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.user.client.ui.Label;
+import org.rstudio.core.client.HandlerRegistrations;
+
+public class HyperlinkLabel extends Label
+{
+ public HyperlinkLabel()
+ {
+ super();
+ this.setStyleName("rstudio-HyperlinkLabel");
+ }
+
+ public HyperlinkLabel(String caption, ClickHandler clickHandler)
+ {
+ super(caption);
+ clickHandler_ = clickHandler ;
+ this.setStyleName("rstudio-HyperlinkLabel");
+ }
+
+ public HyperlinkLabel(String caption)
+ {
+ this(caption, null);
+ }
+
+ // must call this before the element is loaded
+ public void setClickHandler(ClickHandler clickHandler)
+ {
+ clickHandler_ = clickHandler;
+ }
+
+ private class MouseHandlers implements MouseOverHandler,
+ MouseOutHandler
+ {
+ public void onMouseOver(MouseOverEvent event)
+ {
+ if (!alwaysUnderline_)
+ addStyleDependentName("Link");
+ }
+
+ public void onMouseOut(MouseOutEvent event)
+ {
+ if ( !alwaysUnderline_)
+ removeStyleDependentName("Link");
+ }
+ }
+
+ public void setAlwaysUnderline(boolean alwaysUnderline)
+ {
+ alwaysUnderline_ = alwaysUnderline;
+ if (alwaysUnderline_)
+ addStyleDependentName("Link");
+ else
+ removeStyleDependentName("Link");
+ }
+
+ public void setClearUnderlineOnClick(boolean clearOnClick)
+ {
+ clearUnderlineOnClick_ = clearOnClick;
+ }
+
+
+ @Override
+ protected void onLoad()
+ {
+ releaseOnUnload_.add(addMouseOverHandler(mouseHandlers_));
+ releaseOnUnload_.add(addMouseOutHandler(mouseHandlers_));
+ if (clickHandler_ != null)
+ releaseOnUnload_.add(addClickHandler(new ClickHandler() {
+
+ public void onClick(ClickEvent event)
+ {
+ if (clearUnderlineOnClick_)
+ removeStyleDependentName("Link");
+ clickHandler_.onClick(event);
+ }
+
+ }));
+ }
+
+ private MouseHandlers mouseHandlers_ = new MouseHandlers();
+ private ClickHandler clickHandler_ ;
+ private final HandlerRegistrations releaseOnUnload_ = new HandlerRegistrations();
+
+
+ private boolean alwaysUnderline_ = false;
+ private boolean clearUnderlineOnClick_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ImageFrame.java b/src/gwt/src/org/rstudio/core/client/widget/ImageFrame.java
new file mode 100644
index 0000000..98e515c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ImageFrame.java
@@ -0,0 +1,125 @@
+/*
+ * ImageFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.studio.client.application.Desktop;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Frame;
+
+public class ImageFrame extends Frame
+{
+ public ImageFrame()
+ {
+ setUrl("javascript:false");
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ new Timer() {
+ @Override
+ public void run()
+ {
+ // No way to tell when iframe is actually ready to be
+ // manipulated (sometimes contentWindow is null). Need
+ // to probe and retry.
+ if (!isReadyForContent(getElement()))
+ {
+ this.schedule(200);
+ }
+ else
+ {
+ // under Qt 4.8 on the Mac if we set the width and height of
+ // the image in the iframe to 100% then the cpu gets pegged for
+ // ~3 seconds every time we replace the image url
+ String sizing = "width=\"100%\" height=\"100%\"";
+ if (Desktop.isDesktop() && BrowseCap.isMacintosh())
+ sizing = "";
+
+ setupContent(getElement(), sizing);
+ replaceLocation(getElement(), url_);
+ }
+ }
+ }.schedule(100);
+ }
+
+ public void setMarginWidth(int width)
+ {
+ DOM.setElementAttribute(getElement(),
+ "marginwidth",
+ Integer.toString(width));
+ }
+
+ public void setMarginHeight(int height)
+ {
+ DOM.setElementAttribute(getElement(),
+ "marginheight",
+ Integer.toString(height));
+ }
+
+ public void setImageUrl(String url)
+ {
+ url_ = url;
+ if (isAttached())
+ replaceLocation(getElement(), url);
+ }
+
+ private native final boolean replaceLocation(Element el, String url) /*-{
+ if (!el.contentWindow.document)
+ return false;
+ var img = el.contentWindow.document.getElementById('img');
+ if (!img)
+ return false;
+ if (url && url != 'javascript:false') {
+ img.style.display = 'inline';
+ img.src = url;
+ }
+ else {
+ img.style.display = 'none';
+ }
+ return true;
+ }-*/;
+
+ private native boolean isReadyForContent(Element el) /*-{
+ return el != null
+ && el.contentWindow != null
+ && el.contentWindow.document != null;
+ }-*/;
+
+ private native void setupContent(Element el, String sizing) /*-{
+ var doc = el.contentWindow.document;
+
+ // setupContent can get called multiple times, as progress causes the
+ // widget to be loaded/unloaded. This condition checks if we're already
+ // set up.
+ if (doc.getElementById('img'))
+ return;
+
+ doc.open();
+ doc.write(
+ '<html><head></head>' +
+ '<body style="margin: 0; padding: 0; overflow: hidden; border: none">' +
+ '<img id="img" ' + sizing + ' style="display: none" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D">' +
+ '</body></html>');
+ doc.close();
+ }-*/;
+
+ private String url_ = "javascript:false";
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/InfoBar.java b/src/gwt/src/org/rstudio/core/client/widget/InfoBar.java
new file mode 100644
index 0000000..b44af40
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/InfoBar.java
@@ -0,0 +1,91 @@
+/*
+ * InfoBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+public class InfoBar extends Composite
+{
+ public static final int INFO = 0;
+ public static final int WARNING = 1;
+ public static final int ERROR = 2;
+
+ public InfoBar(int mode)
+ {
+ this(mode, null);
+ }
+
+ public InfoBar(int mode, ClickHandler dismissHandler)
+ {
+ switch(mode)
+ {
+ case WARNING:
+ icon_ = new Image(ThemeResources.INSTANCE.warningSmall());
+ break;
+ case ERROR:
+ icon_ = new Image(ThemeResources.INSTANCE.errorSmall());
+ break;
+ case INFO:
+ default:
+ icon_ = new Image(ThemeResources.INSTANCE.infoSmall());
+ break;
+
+ }
+
+ initWidget(binder.createAndBindUi(this));
+
+ if (dismissHandler != null)
+ dismiss_.addClickHandler(dismissHandler);
+ else
+ dismiss_.setVisible(false);
+ }
+
+
+ public String getText()
+ {
+ return label_.getText();
+ }
+
+ public void setText(String text)
+ {
+ label_.setText(text);
+ }
+
+ public int getHeight()
+ {
+ return 19;
+ }
+
+ @UiField
+ protected DockLayoutPanel container_;
+ @UiField(provided = true)
+ protected Image icon_;
+ @UiField
+ protected Label label_;
+ @UiField
+ Image dismiss_;
+
+ interface MyBinder extends UiBinder<Widget, InfoBar>{}
+ private static MyBinder binder = GWT.create(MyBinder.class);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/InfoBar.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/InfoBar.ui.xml
new file mode 100644
index 0000000..cf34d14
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/InfoBar.ui.xml
@@ -0,0 +1,46 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="themeRes" type="org.rstudio.core.client.theme.res.ThemeResources"/>
+
+
+ <ui:style>
+ .outer {
+ width: 100%;
+ height: 18px;
+ background-color: #ffd;
+ border-bottom: 1px solid #bcc1c5;
+ }
+ .icon {
+ margin-top: 2px;
+ margin-left: 7px;
+ }
+ .label {
+ font-size: 11px;
+ margin-top: 2px;
+ color: #555;
+ }
+ .dismiss {
+ cursor: pointer;
+ margin-top: 4px;
+ margin-right: 4px;
+ }
+ </ui:style>
+
+ <g:FlowPanel>
+ <g:DockLayoutPanel ui:field="container_" styleName="{style.outer}">
+ <g:west size="26">
+ <g:Image ui:field="icon_" styleName="{style.icon}"/>
+ </g:west>
+ <g:center>
+ <g:Label ui:field="label_" styleName="{style.label}" wordWrap="false"/>
+ </g:center>
+ <g:east size="13">
+ <g:Image ui:field="dismiss_"
+ resource="{themeRes.closeTab}"
+ styleName="{style.dismiss}"
+ title="Dismiss"/>
+ </g:east>
+ </g:DockLayoutPanel>
+ </g:FlowPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.java b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.java
new file mode 100644
index 0000000..15c7ca1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.java
@@ -0,0 +1,233 @@
+/*
+ * InlineToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+
+import org.rstudio.core.client.StringUtil;
+
+public class InlineToolbarButton extends Composite implements HasButtonMethods
+{
+ private class SimpleHasHandlers extends HandlerManager implements HasHandlers
+ {
+ private SimpleHasHandlers()
+ {
+ super(null);
+ }
+ }
+
+ interface Binder extends UiBinder<Widget, InlineToolbarButton>
+ {}
+
+ @UiConstructor
+ public InlineToolbarButton(ImageResource icon,
+ String label,
+ String description)
+ {
+ icon_ = new Image(icon);
+ label_ = new SpanLabel(label, false);
+ zeroHeightPanel_ = new ZeroHeightPanel(icon.getWidth(),
+ icon.getHeight(),
+ 4);
+
+ if (StringUtil.isNullOrEmpty(label))
+ label_.setVisible(false);
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ setTitle(description);
+ }
+
+ public void setLabel(String text)
+ {
+ label_.setText(text);
+ label_.setVisible(!StringUtil.isNullOrEmpty(text));
+ }
+
+ public void setMenu(final ToolbarPopupMenu menu)
+ {
+ /*
+ * We want clicks on this button to toggle the visibility of the menu,
+ * as well as having the menu auto-hide itself as it normally does.
+ * It's necessary to manually track the visibility (menuShowing) because
+ * in the case where the menu is showing, clicking on this button first
+ * causes the menu to auto-hide and then our mouseDown handler is called
+ * (so we can't rely on menu.isShowing(), it'll always be false by the
+ * time you get into the mousedown handler).
+ */
+
+ final boolean[] menuShowing = new boolean[1];
+
+ addDomHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (menuShowing[0])
+ {
+ menu.hide();
+ }
+ else
+ {
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ menu.setPopupPosition(
+ InlineToolbarButton.this.getAbsoluteLeft(),
+ InlineToolbarButton.this.getAbsoluteTop() +
+ InlineToolbarButton.this.getOffsetHeight() +
+ 8 /* toolbar area under botton */);
+ }
+ });
+ }
+ menuShowing[0] = true;
+ }
+ }, MouseDownEvent.getType());
+
+ menu.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ menuShowing[0] = false;
+ }
+ });
+ }
+ });
+ }
+
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler clickHandler)
+ {
+ /*
+ * When we directly subscribe to this widget's ClickEvent, sometimes the
+ * click gets ignored (inconsistent repro but it happens enough to be
+ * annoying). Doing it this way fixes it.
+ */
+
+ hasHandlers_.addHandler(ClickEvent.getType(), clickHandler);
+
+ final HandlerRegistration mouseDown = addDomHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ down_ = true;
+ }
+ }, MouseDownEvent.getType());
+
+ final HandlerRegistration mouseOut = addDomHandler(new MouseOutHandler()
+ {
+ public void onMouseOut(MouseOutEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ down_ = false;
+ }
+ }, MouseOutEvent.getType());
+
+ final HandlerRegistration mouseUp = addDomHandler(new MouseUpHandler()
+ {
+ public void onMouseUp(MouseUpEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (down_)
+ {
+ down_ = false;
+
+ NativeEvent clickEvent = Document.get().createClickEvent(
+ 1,
+ event.getScreenX(),
+ event.getScreenY(),
+ event.getClientX(),
+ event.getClientY(),
+ event.getNativeEvent().getCtrlKey(),
+ event.getNativeEvent().getAltKey(),
+ event.getNativeEvent().getShiftKey(),
+ event.getNativeEvent().getMetaKey());
+ DomEvent.fireNativeEvent(clickEvent, hasHandlers_);
+ }
+ }
+ }, MouseUpEvent.getType());
+
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ mouseDown.removeHandler();
+ mouseOut.removeHandler();
+ mouseUp.removeHandler();
+ }
+ };
+ }
+
+ public void click()
+ {
+ NativeEvent clickEvent = Document.get().createClickEvent(
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false);
+ DomEvent.fireNativeEvent(clickEvent, hasHandlers_);
+ }
+
+ @UiField(provided = true)
+ Image icon_;
+
+ @UiField(provided = true)
+ SpanLabel label_;
+
+ @UiField(provided = true)
+ ZeroHeightPanel zeroHeightPanel_;
+
+ private SimpleHasHandlers hasHandlers_ = new SimpleHasHandlers();
+ private boolean down_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.ui.xml
new file mode 100644
index 0000000..0fd1427
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarButton.ui.xml
@@ -0,0 +1,30 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .toolbarButton {
+ display: inline;
+ cursor: pointer;
+ }
+
+ .toolbarButton:active {
+ position: relative;
+ top: 1px;
+ }
+
+ .label {
+ margin-left: -1px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.toolbarButton}">
+ <widget:ZeroHeightPanel ui:field="zeroHeightPanel_">
+ <widget:child>
+ <g:Image ui:field="icon_" />
+ </widget:child>
+ </widget:ZeroHeightPanel>
+ <widget:SpanLabel ui:field="label_" styleName="{style.label}"/>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarSeparator.java b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarSeparator.java
new file mode 100644
index 0000000..baea2c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/InlineToolbarSeparator.java
@@ -0,0 +1,31 @@
+/*
+ * InlineToolbarSeparator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+public class InlineToolbarSeparator extends Composite
+{
+ public InlineToolbarSeparator()
+ {
+ ZeroHeightPanel panel = new ZeroHeightPanel(2, 20, 5);
+ panel.addChild(new Image(ThemeResources.INSTANCE.toolbarSeparator()));
+ initWidget(panel);
+ getElement().getStyle().setMarginLeft(4, Style.Unit.PX);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/IsWidgetAdapter.java b/src/gwt/src/org/rstudio/core/client/widget/IsWidgetAdapter.java
new file mode 100644
index 0000000..a6df357
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/IsWidgetAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * IsWidgetAdapter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+
+public class IsWidgetAdapter implements IsWidget
+{
+ public IsWidgetAdapter(Widget widget)
+ {
+ widget_ = widget;
+ }
+
+ public Widget asWidget()
+ {
+ return widget_;
+ }
+
+ private final Widget widget_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/IsWidgetWithHeight.java b/src/gwt/src/org/rstudio/core/client/widget/IsWidgetWithHeight.java
new file mode 100644
index 0000000..8168a48
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/IsWidgetWithHeight.java
@@ -0,0 +1,22 @@
+/*
+ * IsWidgetWithHeight.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface IsWidgetWithHeight extends IsWidget
+{
+ int getHeight();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/LeftCenterRightBorder.java b/src/gwt/src/org/rstudio/core/client/widget/LeftCenterRightBorder.java
new file mode 100644
index 0000000..e5dbc94
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/LeftCenterRightBorder.java
@@ -0,0 +1,101 @@
+/*
+ * LeftCenterRightBorder.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.ui.*;
+
+public class LeftCenterRightBorder extends ResizeComposite implements AcceptsOneWidget
+{
+ public interface Resources
+ {
+ ImageResource left();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource center();
+ ImageResource right();
+ }
+
+ public LeftCenterRightBorder(Resources resources,
+ int marginTop,
+ int marginRight,
+ int marginBottom,
+ int marginLeft)
+ {
+ marginTop_ = marginTop;
+ marginRight_ = marginRight;
+ marginBottom_ = marginBottom;
+ marginLeft_ = marginLeft;
+ panel_ = new LayoutPanel();
+
+ Image left = new Image(resources.left());
+ Image center = new Image(resources.center());
+ Image right = new Image(resources.right());
+
+ panel_.add(left);
+ panel_.setWidgetLeftWidth(left, 0, Unit.PX, left.getWidth(), Unit.PX);
+ panel_.setWidgetTopHeight(left, 0, Unit.PX, left.getHeight(), Unit.PX);
+
+ panel_.add(center);
+ panel_.setWidgetLeftRight(center,
+ left.getWidth(),
+ Unit.PX,
+ right.getWidth(),
+ Unit.PX);
+ panel_.setWidgetTopHeight(center,
+ 0,
+ Unit.PX,
+ center.getHeight(),
+ Unit.PX);
+ Element centerEl = panel_.getWidgetContainerElement(center);
+ centerEl.getStyle().setBackgroundImage("url(" + center.getUrl() + ")");
+ centerEl.getStyle().setProperty("backgroundRepeat", "repeat-x");
+ center.setVisible(false);
+
+ panel_.add(right);
+ panel_.setWidgetRightWidth(right, 0, Unit.PX, right.getWidth(), Unit.PX);
+ panel_.setWidgetTopHeight(right, 0, Unit.PX, right.getHeight(), Unit.PX);
+
+ initWidget(panel_);
+ }
+
+ @Override
+ public void setWidget(IsWidget w)
+ {
+ if (w_ != null)
+ {
+ panel_.remove(w_);
+ w_ = null;
+ }
+
+ w_ = w;
+ if (w_ != null)
+ {
+ panel_.add(w_);
+ panel_.setWidgetLeftRight(w_, marginLeft_, Unit.PX, marginRight_, Unit.PX);
+ panel_.setWidgetTopBottom(w_, marginTop_, Unit.PX, marginBottom_, Unit.PX);
+ }
+ }
+
+ private final LayoutPanel panel_;
+ private IsWidget w_;
+ private final int marginTop_;
+ private final int marginRight_;
+ private final int marginBottom_;
+ private final int marginLeft_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.css b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.css
new file mode 100644
index 0000000..de7a921
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.css
@@ -0,0 +1,54 @@
+.container {
+ display: inline-block;
+ height: 20px;
+ margin-right: 3px;
+}
+
+.container td {
+ cursor: pointer;
+}
+
+ at sprite .leftLeft {
+ gwt-image: 'leftToggleLeftOff';
+ width: auto;
+ padding-left: 7px;
+ padding-right: 4px;
+}
+
+ at sprite .leftOn .leftLeft {
+ gwt-image: 'leftToggleLeftOn';
+ width: auto;
+}
+
+ at sprite .leftRight {
+ gwt-image: 'leftToggleRightOff';
+ width: value('leftToggleRightOff.getWidth', 'px');
+}
+
+ at sprite .leftOn .leftRight {
+ gwt-image: 'leftToggleRightOn';
+}
+
+ at sprite .rightLeft {
+ gwt-image: 'rightToggleLeftOff';
+ width: value('rightToggleLeftOff.getWidth', 'px');
+}
+
+ at sprite .rightOn .rightLeft {
+ gwt-image: 'rightToggleLeftOn';
+}
+
+ at sprite .rightRight {
+ gwt-image: 'rightToggleRightOff';
+ background-position: top right;
+ width: auto;
+ padding-left: 5px;
+ padding-right: 9px;
+}
+
+ at sprite .rightOn .rightRight {
+ gwt-image: 'rightToggleRightOn';
+ background-position: top right;
+ width: auto;
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.java b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.java
new file mode 100644
index 0000000..f4273b9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.java
@@ -0,0 +1,98 @@
+/*
+ * LeftRightToggleButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Widget;
+
+public class LeftRightToggleButton extends Widget
+ implements HasClickHandlers
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("images/LeftToggleLeftOn.png")
+ ImageResource leftToggleLeftOn();
+ @Source("images/LeftToggleRightOn.png")
+ ImageResource leftToggleRightOn();
+ @Source("images/LeftToggleLeftOff.png")
+ ImageResource leftToggleLeftOff();
+ @Source("images/LeftToggleRightOff.png")
+ ImageResource leftToggleRightOff();
+ @Source("images/RightToggleLeftOn.png")
+ ImageResource rightToggleLeftOn();
+ @Source("images/RightToggleRightOn.png")
+ ImageResource rightToggleRightOn();
+ @Source("images/RightToggleLeftOff.png")
+ ImageResource rightToggleLeftOff();
+ @Source("images/RightToggleRightOff.png")
+ ImageResource rightToggleRightOff();
+
+ @Source("LeftRightToggleButton.css")
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String container();
+ String leftLeft();
+ String leftRight();
+ String rightLeft();
+ String rightRight();
+ String leftOn();
+ String rightOn();
+ }
+
+ interface Binder extends UiBinder<Element, LeftRightToggleButton>
+ {}
+
+ public LeftRightToggleButton(String leftLabel, String rightLabel,
+ boolean leftIsOn)
+ {
+ setElement(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+ Styles styles = GWT.<Resources>create(Resources.class).styles();
+ left_.setInnerText(leftLabel);
+ right_.setInnerText(rightLabel);
+ if (leftIsOn)
+ addStyleName(styles.leftOn());
+ else
+ addStyleName(styles.rightOn());
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ static
+ {
+ GWT.<Resources>create(Resources.class).styles().ensureInjected();
+ }
+
+ @UiField
+ Element left_;
+ @UiField
+ Element right_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.ui.xml
new file mode 100644
index 0000000..a9ca84e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/LeftRightToggleButton.ui.xml
@@ -0,0 +1,16 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res"
+ type="org.rstudio.core.client.widget.LeftRightToggleButton.Resources"/>
+
+ <table class="{res.styles.container}" cellpadding="0" cellspacing="0">
+ <tr>
+ <td class="{res.styles.leftLeft}" ui:field="left_"/>
+ <td class="{res.styles.leftRight}"/>
+ <td class="{res.styles.rightLeft}"/>
+ <td class="{res.styles.rightRight}" ui:field="right_"/>
+ </tr>
+ </table>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MenuLabel.java b/src/gwt/src/org/rstudio/core/client/widget/MenuLabel.java
new file mode 100644
index 0000000..d7b5404
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MenuLabel.java
@@ -0,0 +1,22 @@
+/*
+ * MenuLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface MenuLabel extends IsWidget, HasClickHandlers
+{
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MessageDialog.java b/src/gwt/src/org/rstudio/core/client/widget/MessageDialog.java
new file mode 100644
index 0000000..6575619
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MessageDialog.java
@@ -0,0 +1,152 @@
+/*
+ * MessageDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.images.MessageDialogImages;
+
+public class MessageDialog extends ModalDialogBase
+{
+ public final static int INFO = 1;
+ public final static int WARNING = 2;
+ public final static int ERROR = 3;
+ public final static int QUESTION = 4;
+ public final static int POPUP_BLOCKED = 0;
+
+ public MessageDialog(int type, String caption, String message)
+ {
+ this(type, caption, labelForMessage(message));
+ }
+
+ public MessageDialog(int type, String caption, Widget message)
+ {
+ type_ = type;
+ setText(caption);
+ messageWidget_ = message;
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_CENTER);
+ }
+
+ public ThemedButton addButton(String label,
+ final Operation operation,
+ boolean isDefault,
+ boolean isCancel)
+ {
+ ThemedButton button = new ThemedButton(label, new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (operation != null)
+ operation.execute();
+ closeDialog();
+ }
+ });
+
+ addButton(button, isDefault, isCancel);
+
+ return button;
+ }
+
+ public ThemedButton addButton(String label,
+ final ProgressOperation operation,
+ boolean isDefault,
+ boolean isCancel)
+ {
+ if (operation != null && progress_ == null)
+ progress_ = addProgressIndicator();
+
+ ThemedButton button = new ThemedButton(label, new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (operation != null)
+ operation.execute(progress_);
+ else
+ closeDialog();
+ }
+ });
+
+ addButton(button, isDefault, isCancel);
+
+ return button;
+ }
+
+ private void addButton(ThemedButton button,
+ boolean isDefault,
+ boolean isCancel)
+ {
+ if (isDefault)
+ addOkButton(button);
+ else if (isCancel)
+ addCancelButton(button);
+ else
+ addButton(button);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ HorizontalPanel horizontalPanel = new HorizontalPanel();
+ horizontalPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
+
+ // add image
+ MessageDialogImages images = MessageDialogImages.INSTANCE;
+ Image image = null;
+ switch(type_)
+ {
+ case INFO:
+ image = new Image(images.dialog_info());
+ break;
+ case WARNING:
+ image = new Image(images.dialog_warning());
+ break;
+ case ERROR:
+ image = new Image(images.dialog_error());
+ break;
+ case QUESTION:
+ image = new Image(images.dialog_question());
+ break;
+ case POPUP_BLOCKED:
+ image = new Image(images.dialog_popup_blocked());
+ break;
+ }
+ horizontalPanel.add(image);
+
+ // add message widget
+ horizontalPanel.add(messageWidget_);
+
+ return horizontalPanel;
+ }
+
+ public static Label labelForMessage(String message)
+ {
+ Label label = new MultiLineLabel(message);
+ label.setStylePrimaryName(
+ ThemeResources.INSTANCE.themeStyles().dialogMessage());
+ return label;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ focusOkButton();
+ }
+
+ private int type_ ;
+ private Widget messageWidget_ ;
+ private ProgressIndicator progress_ ;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MessageDialogLabel.java b/src/gwt/src/org/rstudio/core/client/widget/MessageDialogLabel.java
new file mode 100644
index 0000000..2e582b3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MessageDialogLabel.java
@@ -0,0 +1,43 @@
+/*
+ * MessageDialogLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+public class MessageDialogLabel extends MultiLineLabel
+{
+ public MessageDialogLabel()
+ {
+ setStyle();
+ }
+
+ public MessageDialogLabel(String text)
+ {
+ super(text);
+ setStyle();
+ }
+
+ public MessageDialogLabel(String text, boolean wordWrap)
+ {
+ super(text, wordWrap);
+ setStyle();
+ }
+
+ private void setStyle()
+ {
+ setStylePrimaryName(
+ ThemeResources.INSTANCE.themeStyles().dialogMessage());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MiniDialogPopupPanel.java b/src/gwt/src/org/rstudio/core/client/widget/MiniDialogPopupPanel.java
new file mode 100644
index 0000000..cae58b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MiniDialogPopupPanel.java
@@ -0,0 +1,119 @@
+/*
+ * MiniDialogPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.DecoratedPopupPanel;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class MiniDialogPopupPanel extends DecoratedPopupPanel
+{
+ public MiniDialogPopupPanel()
+ {
+ super();
+ commonInit();
+ }
+
+ public MiniDialogPopupPanel(boolean autoHide)
+ {
+ super(autoHide);
+ commonInit();
+ }
+
+ public MiniDialogPopupPanel(boolean autoHide, boolean modal)
+ {
+ super(autoHide, modal);
+ commonInit();
+ }
+
+ private void commonInit()
+ {
+ addStyleName(ThemeStyles.INSTANCE.miniDialogPopupPanel());
+
+ verticalPanel_ = new VerticalPanel();
+ verticalPanel_.setStyleName(ThemeStyles.INSTANCE.miniDialogContainer());
+
+ // title bar
+ HorizontalPanel titleBar = new HorizontalPanel();
+ titleBar.setWidth("100%");
+
+ captionLabel_ = new Label();
+ captionLabel_.setStyleName(ThemeStyles.INSTANCE.miniDialogCaption());
+ titleBar.add(captionLabel_);
+ titleBar.setCellHorizontalAlignment(captionLabel_,
+ HasHorizontalAlignment.ALIGN_LEFT);
+
+ HorizontalPanel toolsPanel = new HorizontalPanel();
+ toolsPanel.setStyleName(ThemeStyles.INSTANCE.miniDialogTools());
+ ToolbarButton hideButton = new ToolbarButton(
+ ThemeResources.INSTANCE.closeChevron(),
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ MiniDialogPopupPanel.this.hideMiniDialog();
+ }
+ });
+ hideButton.setTitle("Close");
+ toolsPanel.add(hideButton);
+ titleBar.add(toolsPanel);
+ titleBar.setCellHorizontalAlignment(toolsPanel,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+
+ verticalPanel_.add(titleBar);
+
+ // main widget
+ verticalPanel_.add(createMainWidget());
+
+ setWidget(verticalPanel_);
+ }
+
+ public void setCaption(String caption)
+ {
+ captionLabel_.setText(caption);
+ }
+
+ protected abstract Widget createMainWidget();
+
+ protected void hideMiniDialog()
+ {
+ hide();
+ restorePreviouslyFocusedElement();
+ }
+
+ // TODO: refactor so the originally active element code is
+ // shared between MiniDialogPopupPanel and ModalDialogBase
+
+ public void recordPreviouslyFocusedElement()
+ {
+ focusContext_.record();
+ }
+
+ protected void restorePreviouslyFocusedElement()
+ {
+ focusContext_.restore();
+ }
+
+ private VerticalPanel verticalPanel_;
+ private Label captionLabel_ ;
+ private FocusContext focusContext_ = new FocusContext();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ModalDialog.java b/src/gwt/src/org/rstudio/core/client/widget/ModalDialog.java
new file mode 100644
index 0000000..24fd00d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ModalDialog.java
@@ -0,0 +1,123 @@
+/*
+ * ModalDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+
+
+public abstract class ModalDialog<T> extends ModalDialogBase
+{
+ public ModalDialog(String caption,
+ OperationWithInput<T> operation)
+ {
+ this(caption, operation, null);
+ }
+
+ public ModalDialog(String caption,
+ final OperationWithInput<T> operation,
+ Operation cancelOperation)
+ {
+ super();
+
+ ThemedButton okButton = new ThemedButton("OK", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ final T input = collectInput();
+ validateAndGo(input, new Command()
+ {
+ @Override
+ public void execute()
+ {
+ closeDialog();
+ if (operation != null)
+ operation.execute(input);
+ onSuccess();
+ }
+ });
+ }
+ });
+
+ commonInit(caption, okButton, cancelOperation);
+ }
+
+ protected void onSuccess()
+ {
+ }
+
+
+ public ModalDialog(String caption,
+ final ProgressOperationWithInput<T> operation)
+ {
+ this(caption, operation, null);
+ }
+
+ public ModalDialog(String caption,
+ final ProgressOperationWithInput<T> operation,
+ Operation cancelOperation)
+ {
+ super();
+
+ final ProgressIndicator progressIndicator = addProgressIndicator();
+
+ ThemedButton okButton = new ThemedButton("OK", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ final T input = collectInput();
+ validateAndGo(input, new Command()
+ {
+ @Override
+ public void execute()
+ {
+ operation.execute(input, progressIndicator);
+ onSuccess();
+ }
+ });
+ }
+ });
+
+ commonInit(caption, okButton, cancelOperation);
+ }
+
+ private void commonInit(String caption,
+ ThemedButton okButton,
+ final Operation cancelOperation)
+ {
+ setText(caption);
+ addOkButton(okButton);
+ ThemedButton cancelButton = addCancelButton();
+ if (cancelOperation != null)
+ {
+ cancelButton.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ cancelOperation.execute();
+ }
+ });
+ }
+ }
+
+ protected abstract T collectInput();
+
+ protected void validateAndGo(T input, Command executeOnSuccess)
+ {
+ if (validate(input))
+ executeOnSuccess.execute();
+ }
+
+ protected abstract boolean validate(T input);
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ModalDialogBase.java b/src/gwt/src/org/rstudio/core/client/widget/ModalDialogBase.java
new file mode 100644
index 0000000..a6b7190
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ModalDialogBase.java
@@ -0,0 +1,519 @@
+/*
+ * ModalDialogBase.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.command.ShortcutManager;
+import org.rstudio.core.client.command.ShortcutManager.Handle;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.NativeWindow;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import java.util.ArrayList;
+
+
+public abstract class ModalDialogBase extends DialogBox
+{
+ protected ModalDialogBase()
+ {
+ this(null);
+ }
+
+ protected ModalDialogBase(SimplePanel containerPanel)
+ {
+ // core initialization. passing false for modal works around
+ // modal PopupPanel supressing global keyboard accelerators (like
+ // Ctrl-N or Ctrl-T). modality is achieved via setGlassEnabled(true)
+ super(false, false);
+ setGlassEnabled(true);
+ addStyleDependentName("ModalDialog");
+
+ // main panel used to host UI
+ mainPanel_ = new VerticalPanel();
+ bottomPanel_ = new HorizontalPanel();
+ bottomPanel_.setStyleName(ThemeStyles.INSTANCE.dialogBottomPanel());
+ bottomPanel_.setWidth("100%");
+ buttonPanel_ = new HorizontalPanel();
+ leftButtonPanel_ = new HorizontalPanel();
+ bottomPanel_.add(leftButtonPanel_);
+ bottomPanel_.add(buttonPanel_);
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+ mainPanel_.add(bottomPanel_);
+
+ // embed main panel in a custom container if specified
+ containerPanel_ = containerPanel;
+ if (containerPanel_ != null)
+ {
+ containerPanel_.setWidget(mainPanel_);
+ setWidget(containerPanel_);
+ }
+ else
+ {
+ setWidget(mainPanel_);
+ }
+
+ addDomHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // Is this too aggressive? Alternatively we could only filter out
+ // keycodes that are known to be problematic (pgup/pgdown)
+ event.stopPropagation();
+ }
+ }, KeyDownEvent.getType());
+ }
+
+ @Override
+ protected void beginDragging(MouseDownEvent event)
+ {
+ // Prevent text selection from occurring when moving the dialog box
+ event.preventDefault();
+ super.beginDragging(event);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ ModalDialogTracker.onShow(this);
+ if (shortcutDisableHandle_ != null)
+ shortcutDisableHandle_.close();
+ shortcutDisableHandle_ = ShortcutManager.INSTANCE.disable();
+
+ try
+ {
+ // 728: Focus remains in Source view when message dialog pops up over it
+ NativeWindow.get().focus();
+ }
+ catch(Throwable e)
+ {
+ }
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ if (shortcutDisableHandle_ != null)
+ shortcutDisableHandle_.close();
+ shortcutDisableHandle_ = null;
+
+ ModalDialogTracker.onHide(this);
+
+ super.onUnload();
+ }
+
+ public boolean isEscapeDisabled()
+ {
+ return escapeDisabled_;
+ }
+
+ public void setEscapeDisabled(boolean escapeDisabled)
+ {
+ escapeDisabled_ = escapeDisabled;
+ }
+
+ public void showModal()
+ {
+ if (mainWidget_ == null)
+ {
+ mainWidget_ = createMainWidget();
+
+ // get the main widget to line up with the right edge of the buttons.
+ mainWidget_.getElement().getStyle().setMarginRight(2, Unit.PX);
+
+ mainPanel_.insert(mainWidget_, 0);
+ }
+
+ originallyActiveElement_ = DomUtils.getActiveElement();
+ if (originallyActiveElement_ != null)
+ originallyActiveElement_.blur();
+
+ // position the dialog
+ positionAndShowDialog(new Command() {
+ @Override
+ public void execute()
+ {
+ // defer shown notification to allow all elements to render
+ // before attempting to interact w/ them programatically (e.g. setFocus)
+ Timer timer = new Timer() {
+ public void run() {
+ onDialogShown();
+ }
+ };
+ timer.schedule(100);
+ }
+ });
+ }
+
+
+ protected abstract Widget createMainWidget() ;
+
+ protected void positionAndShowDialog(Command onCompleted)
+ {
+ super.center();
+ onCompleted.execute();
+ }
+
+ protected void onDialogShown()
+ {
+ }
+
+ protected void addOkButton(ThemedButton okButton)
+ {
+ okButton_ = okButton;
+ okButton_.addStyleDependentName("DialogAction");
+ okButton_.setDefault(defaultOverrideButton_ == null);
+ addButton(okButton_);
+ }
+
+ protected void setOkButtonCaption(String caption)
+ {
+ okButton_.setText(caption);
+ }
+
+ protected void enableOkButton(boolean enabled)
+ {
+ okButton_.setEnabled(enabled);
+ }
+
+ protected void clickOkButton()
+ {
+ okButton_.click();
+ }
+
+ protected void setOkButtonVisible(boolean visible)
+ {
+ okButton_.setVisible(visible);
+ }
+
+ protected void focusOkButton()
+ {
+ if (okButton_ != null)
+ FocusHelper.setFocusDeferred(okButton_);
+ }
+
+ protected void enableCancelButton(boolean enabled)
+ {
+ cancelButton_.setEnabled(enabled);
+ }
+
+ protected void setDefaultOverrideButton(ThemedButton button)
+ {
+ if (button != defaultOverrideButton_)
+ {
+ if (defaultOverrideButton_ != null)
+ defaultOverrideButton_.setDefault(false);
+
+ defaultOverrideButton_ = button;
+ if (okButton_ != null)
+ okButton_.setDefault(defaultOverrideButton_ == null);
+
+ if (defaultOverrideButton_ != null)
+ defaultOverrideButton_.setDefault(true);
+ }
+ }
+
+ protected ThemedButton addCancelButton()
+ {
+ ThemedButton cancelButton = createCancelButton(null);
+ addCancelButton(cancelButton);
+ return cancelButton;
+ }
+
+ protected ThemedButton createCancelButton(final Operation cancelOperation)
+ {
+ return new ThemedButton("Cancel", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ if (cancelOperation != null)
+ cancelOperation.execute();
+ closeDialog();
+ }
+ });
+ }
+
+ protected void addCancelButton(ThemedButton cancelButton)
+ {
+ cancelButton_ = cancelButton;
+ cancelButton_.addStyleDependentName("DialogAction");
+ addButton(cancelButton_);
+ }
+
+ protected void addLeftButton(ThemedButton button)
+ {
+ button.addStyleDependentName("DialogAction");
+ button.addStyleDependentName("DialogActionLeft");
+ leftButtonPanel_.add(button);
+ allButtons_.add(button);
+ }
+
+ protected void addLeftWidget(Widget widget)
+ {
+ leftButtonPanel_.add(widget);
+ }
+
+
+ protected void addButton(ThemedButton button)
+ {
+ button.addStyleDependentName("DialogAction");
+ buttonPanel_.add(button);
+ allButtons_.add(button);
+ }
+
+ protected void setButtonAlignment(HorizontalAlignmentConstant alignment)
+ {
+ bottomPanel_.setCellHorizontalAlignment(buttonPanel_, alignment);
+ }
+
+ protected ProgressIndicator addProgressIndicator()
+ {
+ return addProgressIndicator(true);
+ }
+
+ protected ProgressIndicator addProgressIndicator(
+ final boolean closeOnCompleted)
+ {
+ final SlideLabel label = new SlideLabel(true);
+ Element labelEl = label.getElement();
+ Style labelStyle = labelEl.getStyle();
+ labelStyle.setPosition(Style.Position.ABSOLUTE);
+ labelStyle.setLeft(0, Style.Unit.PX);
+ labelStyle.setRight(0, Style.Unit.PX);
+ labelStyle.setTop(-12, Style.Unit.PX);
+ getWidget().getElement().getParentElement().appendChild(labelEl);
+
+ return new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ if (message == null)
+ {
+ label.setText("", true);
+ if (showing_)
+ clearProgress();
+ }
+ else
+ {
+ label.setText(message, false);
+ if (!showing_)
+ {
+ enableControls(false);
+ label.show();
+ showing_ = true;
+ }
+ }
+ }
+
+ public void onCompleted()
+ {
+ clearProgress();
+ if (closeOnCompleted)
+ closeDialog();
+ }
+
+ public void onError(String message)
+ {
+ clearProgress();
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Error", message);
+ }
+
+ @Override
+ public void clearProgress()
+ {
+ if (showing_)
+ {
+ enableControls(true);
+ label.hide();
+ showing_ = false;
+ }
+
+ }
+
+ private boolean showing_;
+ };
+ }
+
+ public void closeDialog()
+ {
+ hide();
+ removeFromParent();
+
+ try
+ {
+ if (originallyActiveElement_ != null
+ && !originallyActiveElement_.getTagName().equalsIgnoreCase("body"))
+ {
+ Document doc = originallyActiveElement_.getOwnerDocument();
+ if (doc != null)
+ {
+ originallyActiveElement_.focus();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // focus() fail if the element is no longer visible. It's
+ // easier to just catch this than try to detect it.
+
+ // Also originallyActiveElement_.getTagName() can fail with:
+ // "Permission denied to access property 'tagName' from a non-chrome context"
+ // possibly due to Firefox "anonymous div" issue.
+ }
+ originallyActiveElement_ = null;
+ }
+
+ protected SimplePanel getContainerPanel()
+ {
+ return containerPanel_;
+ }
+
+ protected void enableControls(boolean enabled)
+ {
+ enableButtons(enabled);
+ onEnableControls(enabled);
+ }
+
+ protected void onEnableControls(boolean enabled)
+ {
+
+ }
+
+ @Override
+ public void onPreviewNativeEvent(Event.NativePreviewEvent event)
+ {
+ if (!ModalDialogTracker.isTopMost(this))
+ return;
+
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ switch (nativeEvent.getKeyCode())
+ {
+ case KeyCodes.KEY_ENTER:
+ ThemedButton defaultButton = defaultOverrideButton_ == null
+ ? okButton_
+ : defaultOverrideButton_;
+ if ((defaultButton != null) && defaultButton.isEnabled())
+ {
+ nativeEvent.preventDefault();
+ nativeEvent.stopPropagation();
+ event.cancel();
+ defaultButton.click();
+ }
+ break;
+ case KeyCodes.KEY_ESCAPE:
+ if (escapeDisabled_)
+ break;
+ onEscapeKeyDown(event);
+ break;
+ }
+ }
+ }
+
+ protected void onEscapeKeyDown(Event.NativePreviewEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (cancelButton_ == null)
+ {
+ if ((okButton_ != null) && okButton_.isEnabled())
+ {
+ nativeEvent.preventDefault();
+ nativeEvent.stopPropagation();
+ event.cancel();
+ okButton_.click();
+ }
+ }
+ else if (cancelButton_.isEnabled())
+ {
+ nativeEvent.preventDefault();
+ nativeEvent.stopPropagation();
+ event.cancel();
+ cancelButton_.click();
+ }
+ }
+
+ private void enableButtons(boolean enabled)
+ {
+ for (int i=0; i<allButtons_.size(); i++)
+ allButtons_.get(i).setEnabled(enabled);
+ }
+
+ public void move(Point p, boolean allowAnimation)
+ {
+ if (!isShowing() || !allowAnimation)
+ {
+ // Don't animate if not showing
+ setPopupPosition(p.getX(), p.getY());
+ return;
+ }
+
+ if (currentAnimation_ != null)
+ {
+ currentAnimation_.cancel();
+ currentAnimation_ = null;
+ }
+
+ final int origLeft = getPopupLeft();
+ final int origTop = getPopupTop();
+ final int deltaX = p.getX() - origLeft;
+ final int deltaY = p.getY() - origTop;
+
+ currentAnimation_ = new Animation()
+ {
+ @Override
+ protected void onUpdate(double progress)
+ {
+ if (!isShowing())
+ cancel();
+ else
+ {
+ setPopupPosition((int)(origLeft + deltaX * progress),
+ (int)(origTop + deltaY * progress));
+ }
+ }
+ };
+ currentAnimation_.run(200);
+ }
+
+ private Handle shortcutDisableHandle_;
+
+ private boolean escapeDisabled_;
+ private SimplePanel containerPanel_;
+ private VerticalPanel mainPanel_ ;
+ private HorizontalPanel bottomPanel_;
+ private HorizontalPanel buttonPanel_;
+ private HorizontalPanel leftButtonPanel_;
+ private ThemedButton okButton_;
+ private ThemedButton cancelButton_;
+ private ThemedButton defaultOverrideButton_;
+ private ArrayList<ThemedButton> allButtons_ = new ArrayList<ThemedButton>();
+ private Widget mainWidget_ ;
+ private com.google.gwt.dom.client.Element originallyActiveElement_;
+ private Animation currentAnimation_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ModalDialogTracker.java b/src/gwt/src/org/rstudio/core/client/widget/ModalDialogTracker.java
new file mode 100644
index 0000000..156abed
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ModalDialogTracker.java
@@ -0,0 +1,42 @@
+/*
+ * ModalDialogTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.PopupPanel;
+
+import java.util.ArrayList;
+
+public class ModalDialogTracker
+{
+ public static void onShow(PopupPanel panel)
+ {
+ dialogStack_.add(panel);
+ }
+
+ public static boolean isTopMost(PopupPanel panel)
+ {
+ return !dialogStack_.isEmpty() &&
+ dialogStack_.get(dialogStack_.size()-1) == panel;
+ }
+
+ public static void onHide(PopupPanel panel)
+ {
+ while (dialogStack_.remove(panel))
+ {}
+ }
+
+ private static ArrayList<PopupPanel> dialogStack_ =
+ new ArrayList<PopupPanel>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ModalPopupPanel.java b/src/gwt/src/org/rstudio/core/client/widget/ModalPopupPanel.java
new file mode 100644
index 0000000..e3260ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ModalPopupPanel.java
@@ -0,0 +1,93 @@
+/*
+ * ModalPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.ui.PopupPanel;
+import org.rstudio.core.client.command.ShortcutManager;
+import org.rstudio.core.client.command.ShortcutManager.Handle;
+import org.rstudio.core.client.dom.DomUtils;
+
+public class ModalPopupPanel extends PopupPanel
+{
+ public ModalPopupPanel(boolean autoHide,
+ boolean modal,
+ boolean closeOnEscape)
+ {
+ super(autoHide, modal);
+ closeOnEscape_ = closeOnEscape;
+ setGlassEnabled(true);
+ }
+
+ @Override
+ protected void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (closeOnEscape_ &&
+ event.getTypeInt() == Event.ONKEYDOWN &&
+ event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE &&
+ ModalDialogTracker.isTopMost(this))
+ {
+ close();
+ event.cancel();
+ event.getNativeEvent().preventDefault();
+ event.getNativeEvent().stopPropagation();
+ }
+ super.onPreviewNativeEvent(event);
+ }
+
+ public void close()
+ {
+ hide();
+ removeFromParent();
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ originallyFocused_ = DomUtils.getActiveElement();
+ if (originallyFocused_ != null)
+ originallyFocused_.blur();
+
+ if (shortcutDisableHandle_ != null)
+ shortcutDisableHandle_.close();
+ shortcutDisableHandle_ = ShortcutManager.INSTANCE.disable();
+
+ ModalDialogTracker.onShow(this);
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ ModalDialogTracker.onHide(this);
+
+ if (shortcutDisableHandle_ != null)
+ shortcutDisableHandle_.close();
+ shortcutDisableHandle_ = null;
+
+ super.onUnload();
+
+ if (originallyFocused_ != null)
+ originallyFocused_.focus();
+ }
+
+ private Handle shortcutDisableHandle_;
+ private Element originallyFocused_;
+ private final boolean closeOnEscape_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MultiLineLabel.java b/src/gwt/src/org/rstudio/core/client/widget/MultiLineLabel.java
new file mode 100644
index 0000000..8512c48
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MultiLineLabel.java
@@ -0,0 +1,46 @@
+/*
+ * MultiLineLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.Label;
+import org.rstudio.core.client.dom.DomUtils;
+
+public class MultiLineLabel extends Label
+{
+ public MultiLineLabel()
+ {
+ }
+
+ public MultiLineLabel(String text)
+ {
+ super(text);
+ }
+
+ public MultiLineLabel(String text, boolean wordWrap)
+ {
+ super(text, wordWrap);
+ }
+
+ @Override
+ public void setText(String text)
+ {
+ getElement().setInnerHTML(DomUtils.textToHtml(text));
+ }
+
+ public void setHtml(String html)
+ {
+ getElement().setInnerHTML(html);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MultiSelectCellTable.java b/src/gwt/src/org/rstudio/core/client/widget/MultiSelectCellTable.java
new file mode 100644
index 0000000..d4021bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MultiSelectCellTable.java
@@ -0,0 +1,337 @@
+/*
+ * MultiSelectCellTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.ProvidesKey;
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.dom.DomUtils;
+
+public class MultiSelectCellTable<T> extends CellTable<T>
+ implements HasKeyDownHandlers, HasClickHandlers, HasMouseDownHandlers,
+ HasContextMenuHandlers
+{
+ public MultiSelectCellTable()
+ {
+ commonInit();
+ }
+
+ public MultiSelectCellTable(int pageSize)
+ {
+ super(pageSize);
+ commonInit();
+ }
+
+ public MultiSelectCellTable(ProvidesKey<T> keyProvider)
+ {
+ super(keyProvider);
+ commonInit();
+ }
+
+ public MultiSelectCellTable(int pageSize, Resources resources)
+ {
+ super(pageSize, resources);
+ commonInit();
+ }
+
+ public MultiSelectCellTable(int pageSize, ProvidesKey<T> keyProvider)
+ {
+ super(pageSize, keyProvider);
+ commonInit();
+ }
+
+ public MultiSelectCellTable(int pageSize,
+ Resources resources,
+ ProvidesKey<T> keyProvider)
+ {
+ super(pageSize, resources, keyProvider);
+ commonInit();
+ }
+
+ public MultiSelectCellTable(int pageSize,
+ Resources resources,
+ ProvidesKey<T> keyProvider,
+ Widget loadingIndicator)
+ {
+ super(pageSize, resources, keyProvider, loadingIndicator);
+ commonInit();
+ }
+
+ @Override
+ public void setFocus(boolean focused)
+ {
+ if (focused)
+ DomUtils.focus(getElement(), false);
+ }
+
+ private void commonInit()
+ {
+ setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+ getElement().setTabIndex(-1);
+
+ addDomHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ DomUtils.focus(getElement(), false);
+ }
+ }, ClickEvent.getType());
+
+ addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ MultiSelectCellTable.this.handleKeyDown(event);
+ }
+ });
+
+ addDomHandler(new ContextMenuHandler() {
+ @Override
+ public void onContextMenu(ContextMenuEvent event)
+ {
+ MultiSelectCellTable.this.handleContextMenu(event);
+ }
+ }, ContextMenuEvent.getType());
+ }
+
+ @Override
+ protected boolean isKeyboardNavigationSuppressed()
+ {
+ return true;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void clearSelection()
+ {
+ if (getSelectionModel() instanceof MultiSelectionModel)
+ ((MultiSelectionModel)getSelectionModel()).clear();
+ }
+
+ private void handleKeyDown(KeyDownEvent event)
+ {
+ int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ switch (event.getNativeKeyCode())
+ {
+ // TODO: Handle home/end, pageup/pagedown
+ case KeyCodes.KEY_UP:
+ case KeyCodes.KEY_DOWN:
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch (modifiers)
+ {
+ case 0:
+ case KeyboardShortcut.SHIFT:
+ break;
+ default:
+ return;
+ }
+
+ moveSelection(event.getNativeKeyCode() == KeyCodes.KEY_UP,
+ modifiers == KeyboardShortcut.SHIFT);
+
+ break;
+ case 'A':
+ if (modifiers == (BrowseCap.hasMetaKey() ? KeyboardShortcut.META
+ : KeyboardShortcut.CTRL))
+ {
+ if (getSelectionModel() instanceof MultiSelectionModel)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ for (T item : getVisibleItems())
+ getSelectionModel().setSelected(item, true);
+ }
+ }
+ break;
+ }
+
+ }
+
+
+ // handle context-menu clicks. this implementation (specifically the
+ // detection of table rows from dom events) is based on the code in
+ // AbstractCellTable.onBrowserEvent2. we first determine if the click
+ // applies to a row in the table -- if it does then we squelch the
+ // standard handling of the event and update the selection and then
+ // forward the event on to any external listeners
+ private void handleContextMenu(ContextMenuEvent cmEvent)
+ {
+ // bail if there are no context menu handlers
+ if (handlerManager_.getHandlerCount(ContextMenuEvent.getType()) == 0)
+ return;
+
+ // Get the event target.
+ NativeEvent event = cmEvent.getNativeEvent();
+ EventTarget eventTarget = event.getEventTarget();
+ if (!Element.is(eventTarget))
+ return;
+ final Element target = event.getEventTarget().cast();
+
+ // always squelch default handling (when there is a handler)
+ event.stopPropagation();
+ event.preventDefault();
+
+ // find the table cell element then get its parent and cast to row
+ TableCellElement tableCell = findNearestParentCell(target);
+ if (tableCell == null)
+ return;
+ Element trElem = tableCell.getParentElement();
+ if (trElem == null)
+ return;
+ TableRowElement tr = TableRowElement.as(trElem);
+
+ // get the section of the row and confirm it is a tbody (as opposed
+ // to a thead or tfoot)
+ Element sectionElem = tr.getParentElement();
+ if (sectionElem == null)
+ return;
+ TableSectionElement section = TableSectionElement.as(sectionElem);
+ if (section != getTableBodyElement())
+ return;
+
+ // determine the row/item target
+ int row = tr.getSectionRowIndex();
+ T item = getVisibleItem(row);
+
+ // if this row isn't already selected then clear the existing selection
+ if (!getSelectionModel().isSelected(item))
+ clearSelection();
+
+ // select the clicked on item
+ getSelectionModel().setSelected(item, true);
+
+ // forward the event
+ DomEvent.fireNativeEvent(event, handlerManager_);
+ }
+
+ // forked from private AbstractCellTable.findNearestParentCell
+ private TableCellElement findNearestParentCell(Element elem) {
+ while ((elem != null) && (elem != getElement())) {
+ // TODO: We need is() implementations in all Element subclasses.
+ // This would allow us to use TableCellElement.is() -- much cleaner.
+ String tagName = elem.getTagName();
+ if ("td".equalsIgnoreCase(tagName) || "th".equalsIgnoreCase(tagName)) {
+ return elem.cast();
+ }
+ elem = elem.getParentElement();
+ }
+ return null;
+ }
+
+ @Override
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public void moveSelection(boolean up, boolean extend)
+ {
+ if (getVisibleItemCount() == 0)
+ return;
+
+ int min = getVisibleItemCount();
+ int max = -1;
+
+ for (int i = 0; i < getVisibleItemCount(); i++)
+ {
+ if (getSelectionModel().isSelected(getVisibleItem(i)))
+ {
+ max = i;
+ if (min > i)
+ min = i;
+ }
+ }
+
+ if (up)
+ {
+ int row = Math.max(0, min - 1);
+ if (!canSelectVisibleRow(row))
+ row = min;
+
+ if (!extend)
+ clearSelection();
+ getSelectionModel().setSelected(getVisibleItem(row), true);
+ ensureRowVisible(row, true);
+ }
+ else
+ {
+ int row = Math.min(getVisibleItemCount()-1, max + 1);
+ if (!canSelectVisibleRow(row))
+ row = max;
+
+ if (!extend)
+ clearSelection();
+ getSelectionModel().setSelected(getVisibleItem(row), true);
+ ensureRowVisible(row, false);
+ }
+ }
+
+ private void ensureRowVisible(int row, boolean alignWithTop)
+ {
+ Element el;
+ if (row == 0 && alignWithTop)
+ el = (getElement().<TableElement>cast()).getRows().getItem(0);
+ else
+ el = getRowElement(row);
+
+ if (el != null)
+ DomUtils.scrollIntoViewVert(el);
+ }
+
+ protected boolean canSelectVisibleRow(int visibleRow)
+ {
+ return true;
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler)
+ {
+ return handlerManager_.addHandler(ContextMenuEvent.getType(), handler);
+ }
+
+ // we have our own HandlerManager so that we can fire the ContextMenuEvent
+ // to listeners after we have done our own handling of it (specifically
+ // we need to nix the browser context menu and update the selection).
+ private final HandlerManager handlerManager_ = new HandlerManager(this);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/MultipleItemSuggestTextBox.java b/src/gwt/src/org/rstudio/core/client/widget/MultipleItemSuggestTextBox.java
new file mode 100644
index 0000000..6c57824
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/MultipleItemSuggestTextBox.java
@@ -0,0 +1,106 @@
+/*
+ * MultipleItemSuggestTextBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.StringUtil;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.TextBoxBase;
+
+// TextBox designed for use with SuggestBox that supports the entry
+// of multiple items
+public class MultipleItemSuggestTextBox extends TextBoxBase
+{
+ public MultipleItemSuggestTextBox()
+ {
+ super(Document.get().createTextInputElement());
+ setStyleName("gwt-TextBox");
+ }
+
+ public List<String> getItems()
+ {
+ ArrayList<String> items = new ArrayList<String>();
+ String text = super.getText();
+ if (!StringUtil.isNullOrEmpty(text))
+ {
+ // split text
+ String[] words = text.split("[ ,]");
+
+ // return non-empty
+ for (int i=0; i<words.length; i++)
+ {
+ String word = words[i].trim();
+ if (word.length() > 0)
+ items.add(word);
+ }
+ }
+ return items;
+ }
+
+
+ @Override
+ public String getText()
+ {
+ // get text
+ String text = super.getText();
+ if (text == null)
+ return "";
+
+ // if it ends with one of the separators then return empty
+ if (text.endsWith(",") || text.endsWith(" "))
+ return "";
+
+ // split text
+ String[] words = text.split("[ ,]");
+
+ // if no words then empty
+ if (words.length == 0)
+ return "";
+
+ // return last word
+ return words[words.length-1];
+ }
+
+ @Override
+ public void setText(String text)
+ {
+ if (StringUtil.isNullOrEmpty(text))
+ {
+ setText("");
+ }
+ else
+ {
+ // find last separator
+ String fullText = super.getText();
+ int lastCommaIndex = fullText.lastIndexOf(',');
+ int lastSpaceIndex = fullText.lastIndexOf(' ');
+ int lastSepIndex = Math.max(lastCommaIndex, lastSpaceIndex);
+
+ // create new text
+ String previous = "";
+ if (lastSepIndex != -1)
+ {
+ previous = fullText.substring(0, lastSepIndex);
+ if (!previous.endsWith(" "))
+ previous = previous + " ";
+ }
+ super.setText(previous + text);
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.css b/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.css
new file mode 100644
index 0000000..4d4b2f3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.css
@@ -0,0 +1,71 @@
+ at sprite .topLeftClass {
+ gwt-image: 'topLeft';
+ width: value('topLeft.getWidth', 'px');
+ height: value('topLeft.getHeight', 'px');
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+ at sprite .topClass {
+ gwt-image: 'top';
+ height: value('top.getHeight', 'px');
+ position: absolute;
+ top: 0;
+ left: value('topLeft.getWidth', 'px');
+ right: value('topRight.getWidth', 'px');
+}
+
+ at sprite .topRightClass {
+ gwt-image: 'topRight';
+ width: value('topRight.getWidth', 'px');
+ height: value('topRight.getHeight', 'px');
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+ at sprite .leftClass {
+ gwt-image: 'left';
+ width: value('left.getWidth', 'px');
+ position: absolute;
+ left: 0;
+ top: value('topLeft.getHeight', 'px');
+ bottom: value('bottomLeft.getHeight', 'px');
+}
+
+ at sprite .rightClass {
+ gwt-image: 'right';
+ width: value('right.getWidth', 'px');
+ position: absolute;
+ right: 0;
+ top: value('topRight.getHeight', 'px');
+ bottom: value('bottomRight.getHeight', 'px');
+}
+
+ at sprite .bottomLeftClass {
+ gwt-image: 'bottomLeft';
+ width: value('bottomLeft.getWidth', 'px');
+ height: value('bottomLeft.getHeight', 'px');
+ position: absolute;
+ bottom: 0;
+ left: 0;
+}
+
+ at sprite .bottomClass {
+ gwt-image: 'bottom';
+ height: value('bottom.getHeight', 'px');
+ position: absolute;
+ bottom: 0;
+ left: value('bottomLeft.getWidth', 'px');
+ right: value('bottomRight.getWidth', 'px');
+}
+
+ at sprite .bottomRightClass {
+ gwt-image: 'bottomRight';
+ width: value('bottomRight.getWidth', 'px');
+ height: value('bottomRight.getHeight', 'px');
+ position: absolute;
+ bottom: 0;
+ right: 0;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.java b/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.java
new file mode 100644
index 0000000..bd1ca82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/NineUpBorder.java
@@ -0,0 +1,122 @@
+/*
+ * NineUpBorder.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ClientBundle.Source;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.ui.*;
+
+public class NineUpBorder extends ResizeComposite implements AcceptsOneWidget
+{
+ // This needs to NOT extend ClientBundle, because doing so causes spurious
+ // errors in Eclipse
+ public interface Resources
+ {
+ ImageResource topLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource top();
+ ImageResource topRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource left();
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource right();
+ ImageResource bottomLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource bottom();
+ ImageResource bottomRight();
+
+ @Source("NineUpBorder.css")
+ Styles styles();
+ }
+
+ public interface Styles extends CssResource
+ {
+ String topLeftClass();
+ String topClass();
+ String topRightClass();
+ String leftClass();
+ String rightClass();
+ String bottomLeftClass();
+ String bottomClass();
+ String bottomRightClass();
+ }
+
+ public NineUpBorder(Resources resources,
+ int marginTop,
+ int marginRight,
+ int marginBottom,
+ int marginLeft)
+ {
+ resources.styles().ensureInjected();
+
+ panel_ = new LayoutPanel();
+
+ addBgPanel(resources.styles().topLeftClass());
+ addBgPanel(resources.styles().topClass());
+ addBgPanel(resources.styles().topRightClass());
+ addBgPanel(resources.styles().leftClass());
+ addBgPanel(resources.styles().rightClass());
+ addBgPanel(resources.styles().bottomLeftClass());
+ addBgPanel(resources.styles().bottomClass());
+ addBgPanel(resources.styles().bottomRightClass());
+
+ inner_ = new SimplePanel();
+ panel_.add(inner_);
+ panel_.setWidgetTopBottom(inner_,
+ marginTop,
+ Unit.PX,
+ marginBottom,
+ Unit.PX);
+ panel_.setWidgetLeftRight(inner_,
+ marginLeft,
+ Unit.PX,
+ marginRight,
+ Unit.PX);
+
+ initWidget(panel_);
+ }
+
+ private void addBgPanel(String className)
+ {
+ DivElement div = Document.get().createDivElement();
+ div.setClassName(className);
+ panel_.getElement().appendChild(div);
+ }
+
+ @Override
+ public void setWidget(IsWidget w)
+ {
+ inner_.setWidget(w);
+ }
+
+ public LayoutPanel getLayoutPanel()
+ {
+ return panel_;
+ }
+
+ public void setFillColor(String fillColor)
+ {
+ inner_.getElement().getStyle().setBackgroundColor(fillColor);
+ }
+
+ private final LayoutPanel panel_;
+ private final SimplePanel inner_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/NullProgressIndicator.java b/src/gwt/src/org/rstudio/core/client/widget/NullProgressIndicator.java
new file mode 100644
index 0000000..05a1865
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/NullProgressIndicator.java
@@ -0,0 +1,45 @@
+/*
+ * NullProgressIndicator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import org.rstudio.studio.client.RStudioGinjector;
+
+public class NullProgressIndicator implements ProgressIndicator
+{
+
+ @Override
+ public void onProgress(String message)
+ {
+ }
+
+ @Override
+ public void onCompleted()
+ {
+ }
+
+ @Override
+ public void onError(String message)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage("Error",
+ message);
+ }
+
+ @Override
+ public void clearProgress()
+ {
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/NumericTextBox.java b/src/gwt/src/org/rstudio/core/client/widget/NumericTextBox.java
new file mode 100644
index 0000000..91388cd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/NumericTextBox.java
@@ -0,0 +1,87 @@
+/*
+ * NumericTextBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.user.client.ui.TextBox;
+import org.rstudio.core.client.command.KeyboardShortcut;
+
+public class NumericTextBox extends TextBox
+{
+ public NumericTextBox()
+ {
+ super();
+ init();
+ }
+
+ protected NumericTextBox(Element element)
+ {
+ super(element);
+ init();
+ }
+
+ private void init()
+ {
+ addFocusHandler(new FocusHandler()
+ {
+ @Override
+ public void onFocus(FocusEvent event)
+ {
+ selectAll();
+ }
+ });
+
+ addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ if (modifiers == KeyboardShortcut.NONE
+ && (event.isUpArrow() || event.isDownArrow()))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ try
+ {
+ int value = Integer.parseInt(getText());
+ value += event.isUpArrow() ? 1 : -1;
+ setValue(value + "", true);
+ selectAll();
+ }
+ catch (NumberFormatException nfe)
+ {
+ // just ignore
+ }
+ }
+ }
+ });
+
+ addKeyPressHandler(new KeyPressHandler()
+ {
+ @Override
+ public void onKeyPress(KeyPressEvent event)
+ {
+ char charCode = event.getCharCode();
+ if (charCode >= '0' && charCode <= '9')
+ return;
+ if (Character.isLetterOrDigit(charCode))
+ event.preventDefault();
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/NumericValueWidget.java b/src/gwt/src/org/rstudio/core/client/widget/NumericValueWidget.java
new file mode 100644
index 0000000..2bf1b9c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/NumericValueWidget.java
@@ -0,0 +1,116 @@
+/*
+ * NumericValueWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+import org.rstudio.studio.client.RStudioGinjector;
+
+public class NumericValueWidget extends Composite
+ implements HasValue<String>,
+ HasEnsureVisibleHandlers
+{
+ public NumericValueWidget(String label)
+ {
+ FlowPanel flowPanel = new FlowPanel();
+ flowPanel.add(new SpanLabel(label, true));
+
+ textBox_ = new TextBox();
+ textBox_.setWidth("30px");
+ textBox_.getElement().getStyle().setMarginLeft(0.6, Unit.EM);
+ flowPanel.add(textBox_);
+
+ initWidget(flowPanel);
+ }
+
+ public String getValue()
+ {
+ return textBox_.getValue();
+ }
+
+ public void setValue(String value)
+ {
+ textBox_.setValue(value);
+ }
+
+ public void setValue(String value, boolean fireEvents)
+ {
+ textBox_.setValue(value, fireEvents);
+ }
+
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler)
+ {
+ return textBox_.addValueChangeHandler(handler);
+ }
+
+ public boolean validate(String fieldName)
+ {
+ return validateRange(fieldName, null, null);
+ }
+
+ public boolean validatePositive(String fieldName)
+ {
+ return validateRange(fieldName, 1, null);
+ }
+
+ /**
+ * Make sure field is a valid integer in the range [min, max). If min or max
+ * are null, then 0 and infinity are assumed, respectively.
+ */
+ public boolean validateRange(String fieldName, Integer min, Integer max)
+ {
+ String value = textBox_.getValue().trim();
+ if (!value.matches("^\\d+$"))
+ {
+ fireEvent(new EnsureVisibleEvent());
+ textBox_.getElement().focus();
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Error",
+ fieldName + " must be a valid number.");
+ return false;
+ }
+ if (min != null || max != null)
+ {
+ int intVal = Integer.parseInt(value);
+ if (min != null && intVal < min)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Error",
+ fieldName + " must be greater than or equal to " + min + ".");
+ return false;
+ }
+ if (max != null && intVal >= max)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Error",
+ fieldName + " must be less than " + max + ".");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ private TextBox textBox_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/Operation.java b/src/gwt/src/org/rstudio/core/client/widget/Operation.java
new file mode 100644
index 0000000..8bf905e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/Operation.java
@@ -0,0 +1,20 @@
+/*
+ * Operation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface Operation
+{
+ void execute();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/OperationWithInput.java b/src/gwt/src/org/rstudio/core/client/widget/OperationWithInput.java
new file mode 100644
index 0000000..b912411
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/OperationWithInput.java
@@ -0,0 +1,20 @@
+/*
+ * OperationWithInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface OperationWithInput<T>
+{
+ void execute(T input);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/PreWidget.java b/src/gwt/src/org/rstudio/core/client/widget/PreWidget.java
new file mode 100644
index 0000000..34116dd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/PreWidget.java
@@ -0,0 +1,91 @@
+/*
+ * PreWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.PasteEvent;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+
+public class PreWidget extends Widget implements HasKeyDownHandlers,
+ HasClickHandlers
+{
+ public PreWidget()
+ {
+ setElement(Document.get().createPreElement());
+ getElement().setTabIndex(0);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public HandlerRegistration addPasteHandler(final PasteEvent.Handler handler)
+ {
+ // GWT doesn't understand paste events via BrowserEvents.Paste, so we need
+ // to manually sink and register for the paste event.
+ sinkEvents(Event.ONPASTE);
+ pasteHandlers_.add(handler);
+ return new HandlerRegistration()
+ {
+ @Override
+ public void removeHandler()
+ {
+ pasteHandlers_.remove(handler);
+ }
+ };
+ }
+
+ public void setText(String text)
+ {
+ getElement().setInnerText(text);
+ }
+
+ public void appendText(String text)
+ {
+ getElement().setInnerText(getElement().getInnerText() + text);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event)
+ {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONPASTE)
+ {
+ for (PasteEvent.Handler handler: pasteHandlers_)
+ {
+ handler.onPaste(new PasteEvent(getClipboardText(event)));
+ }
+ }
+ }
+
+ private final native String getClipboardText(Event event) /*-{
+ return event.clipboardData.getData('text/plain');
+ }-*/;
+
+ private ArrayList<PasteEvent.Handler> pasteHandlers_ =
+ new ArrayList<PasteEvent.Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.css b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.css
new file mode 100644
index 0000000..99c7412
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.css
@@ -0,0 +1,37 @@
+ at external dialogTop, dialogContent;
+
+.labelCell {
+ padding-right: 6px;
+}
+
+.buttonCell button {
+ position: relative;
+ left: 3px;
+ top: 2px;
+ margin-left: 0;
+}
+
+.progressCell img {
+ position: relative;
+ top: 1px;
+}
+
+.progressDialog .dialogTop {
+ display: none;
+}
+
+.progressDialog .dialogContent {
+ margin-top: 4px;
+}
+
+.progressDialog {
+ position: fixed !important;
+ top: 0 !important;
+}
+
+.displayWidget {
+ background-color: white;
+ border: solid #CCC 1px !important;
+ margin: 0;
+ height: 250px;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.java
new file mode 100644
index 0000000..8424da8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.java
@@ -0,0 +1,185 @@
+/*
+ * ProgressDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.dom.DomMetrics;
+
+public abstract class ProgressDialog extends ModalDialogBase
+{
+ interface Resources extends ClientBundle
+ {
+ ImageResource progress();
+
+ @Source("ProgressDialog.css")
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String progressDialog();
+ String labelCell();
+ String progressCell();
+ String buttonCell();
+ String displayWidget();
+ }
+
+ interface Binder extends UiBinder<Widget, ProgressDialog>
+ {}
+
+ public static void ensureStylesInjected()
+ {
+ resources_.styles().ensureInjected();
+ }
+
+ public ProgressDialog(String title)
+ {
+ this(title, null);
+ }
+
+ public ProgressDialog(String title, Object param)
+ {
+ addStyleName(resources_.styles().progressDialog());
+
+ setText(title);
+
+ display_ = createDisplayWidget(param);
+ display_.addStyleName(resources_.styles().displayWidget());
+ Style style = display_.getElement().getStyle();
+ double skewFactor = (12 + BrowseCap.getFontSkew()) / 12.0;
+ int width = Math.min((int)(skewFactor * 660),
+ Window.getClientWidth() - 100);
+ style.setWidth(width, Unit.PX);
+
+ progressAnim_ = new Image(resources_.progress().getSafeUri());
+ stopButton_ = new ThemedButton("Stop");
+ centralWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ setLabel(title);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return centralWidget_;
+ }
+
+ protected abstract Widget createDisplayWidget(Object param);
+
+ @Override
+ protected void onUnload()
+ {
+ super.onUnload();
+ unregisterHandlers();
+ }
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && KeyboardShortcut.getModifierValue(event.getNativeEvent()) == KeyboardShortcut.NONE)
+ {
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ stopButton_.click();
+ event.cancel();
+ return;
+ }
+ else if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ if (handleEnterKey())
+ {
+ event.cancel();
+ return;
+ }
+ }
+ }
+
+ super.onPreviewNativeEvent(event);
+ }
+
+ protected ThemedButton stopButton()
+ {
+ return stopButton_;
+ }
+
+ protected void addHandlerRegistration(HandlerRegistration reg)
+ {
+ registrations_.add(reg);
+ }
+
+ protected void unregisterHandlers()
+ {
+ registrations_.removeHandler();
+ }
+
+ protected void setLabel(String text)
+ {
+ if (BrowseCap.isChrome() || BrowseCap.isCocoaDesktop())
+ {
+ Size labelSize = DomMetrics.measureHTML(text);
+ labelCell_.getStyle().setWidth(labelSize.width + 10, Unit.PX);
+ }
+ label_.setText(text);
+ }
+
+ protected void hideProgress()
+ {
+ progressAnim_.getElement().getStyle().setDisplay(Style.Display.NONE);
+ }
+
+ protected boolean handleEnterKey()
+ {
+ return false;
+ }
+
+
+ private HandlerRegistrations registrations_ = new HandlerRegistrations();
+
+ @UiField(provided = true)
+ Widget display_;
+
+ @UiField(provided = true)
+ Image progressAnim_;
+ @UiField
+ Label label_;
+ @UiField
+ TableCellElement labelCell_;
+ @UiField(provided = true)
+ ThemedButton stopButton_;
+ private Widget centralWidget_;
+
+
+ private static final Resources resources_ = GWT.<Resources>create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.ui.xml
new file mode 100644
index 0000000..4f37755
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressDialog.ui.xml
@@ -0,0 +1,20 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:r='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field="res" type="org.rstudio.core.client.widget.ProgressDialog.Resources"/>
+
+ <g:HTMLPanel>
+ <table>
+ <tr>
+ <td width="100%" class="{res.styles.labelCell}" ui:field="labelCell_"><g:Label ui:field="label_" /></td>
+ <td align="right" width="1%" class="{res.styles.progressCell}"><g:Image ui:field="progressAnim_" /></td>
+ <td align="right" width="1%" class="{res.styles.buttonCell}"><r:ThemedButton text="Stop" ui:field="stopButton_" /></td>
+ </tr>
+ <tr>
+ <td colspan="3"><g:Widget ui:field="display_"/></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressImage.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressImage.java
new file mode 100644
index 0000000..058a01e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressImage.java
@@ -0,0 +1,55 @@
+/*
+ * ProgressImage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.resources.CoreResources;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class ProgressImage extends Composite
+{
+ public ProgressImage()
+ {
+ this(CoreResources.INSTANCE.progress());
+ }
+
+ public ProgressImage(ImageResource image)
+ {
+ image_ = image;
+ panel_ = new SimplePanel();
+ initWidget(panel_);
+ }
+
+ public void show(boolean show)
+ {
+ if (!show)
+ {
+ panel_.setWidget(null);
+ setVisible(false);
+ }
+ else
+ {
+ final Image img = new Image(image_);
+ panel_.setWidget(img);
+ setVisible(true);
+ }
+ }
+
+ private ImageResource image_;
+ private SimplePanel panel_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressIndicator.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressIndicator.java
new file mode 100644
index 0000000..d26883d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressIndicator.java
@@ -0,0 +1,23 @@
+/*
+ * ProgressIndicator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface ProgressIndicator
+{
+ void onProgress(String message);
+ void onCompleted();
+ void onError(String message);
+ void clearProgress();
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressOperation.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressOperation.java
new file mode 100644
index 0000000..c552093
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressOperation.java
@@ -0,0 +1,20 @@
+/*
+ * ProgressOperation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface ProgressOperation
+{
+ void execute(ProgressIndicator indicator);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressOperationWithInput.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressOperationWithInput.java
new file mode 100644
index 0000000..b53bf9c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressOperationWithInput.java
@@ -0,0 +1,20 @@
+/*
+ * ProgressOperationWithInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public interface ProgressOperationWithInput<T>
+{
+ void execute(T input, ProgressIndicator indicator);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ProgressPanel.java b/src/gwt/src/org/rstudio/core/client/widget/ProgressPanel.java
new file mode 100644
index 0000000..7e8bd8e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ProgressPanel.java
@@ -0,0 +1,73 @@
+/*
+ * ProgressPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import org.rstudio.core.client.widget.images.ProgressImages;
+
+public class ProgressPanel extends Composite
+{
+ public ProgressPanel()
+ {
+ this(ProgressImages.createLarge());
+ }
+
+ public ProgressPanel(Image progressImage)
+ {
+ progressImage_ = progressImage;
+ HorizontalCenterPanel progressPanel = new HorizontalCenterPanel(
+ progressImage_,
+ 100);
+ progressImage_.setVisible(false);
+ progressPanel.setSize("100%", "100%");
+ initWidget(progressPanel);
+ }
+
+ public void beginProgressOperation(int delayMs)
+ {
+ clearTimer();
+ progressImage_.setVisible(false);
+
+ timer_ = new Timer() {
+ public void run() {
+ if (timer_ != this)
+ return; // This should never happen, but, just in case
+
+ progressImage_.setVisible(true);
+ }
+ };
+ timer_.schedule(delayMs);
+ }
+
+ public void endProgressOperation()
+ {
+ clearTimer();
+ progressImage_.setVisible(false);
+ }
+
+ private void clearTimer()
+ {
+ if (timer_ != null)
+ {
+ timer_.cancel();
+ timer_ = null;
+ }
+ }
+
+ private final Image progressImage_ ;
+ private Timer timer_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/RStudioFrame.java b/src/gwt/src/org/rstudio/core/client/widget/RStudioFrame.java
new file mode 100644
index 0000000..84cec77
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/RStudioFrame.java
@@ -0,0 +1,47 @@
+/*
+ * RStudioFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.dom.WindowEx;
+
+import com.google.gwt.user.client.ui.Frame;
+
+public class RStudioFrame extends Frame
+{
+ public RStudioFrame()
+ {
+ this(null);
+ }
+
+ public RStudioFrame(String url)
+ {
+ super();
+ if (url != null)
+ setUrl(url);
+ }
+
+ public WindowEx getWindow()
+ {
+ return getIFrame().getContentWindow();
+ }
+
+ public IFrameElementEx getIFrame()
+ {
+ return getElement().cast();
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.css b/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.css
new file mode 100644
index 0000000..158de70
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.css
@@ -0,0 +1,4 @@
+
+.resizeGripper {
+ cursor: move;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.java b/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.java
new file mode 100644
index 0000000..3a10f20
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ResizeGripper.java
@@ -0,0 +1,167 @@
+/*
+ * ResizeGripper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+
+public class ResizeGripper extends Composite
+{
+ public interface Observer
+ {
+ void onResizingStarted();
+ void onResizing(int xDelta, int yDelta);
+ void onResizingCompleted();
+ }
+
+ public ResizeGripper(Observer observer)
+ {
+ observer_ = observer;
+ gripperImageResource_ = RESOURCES.resizeGripper();
+ Image image = new Image(gripperImageResource_);
+ initWidget(image);
+ setStylePrimaryName(RESOURCES.styles().resizeGripper());
+
+ DOM.sinkEvents(getElement(),
+ Event.ONMOUSEDOWN |
+ Event.ONMOUSEMOVE |
+ Event.ONMOUSEUP |
+ Event.ONLOSECAPTURE);
+ }
+
+ public int getImageWidth()
+ {
+ return gripperImageResource_.getWidth();
+ }
+
+ public int getImageHeight()
+ {
+ return gripperImageResource_.getHeight();
+ }
+
+ @Override
+ public void onBrowserEvent(Event event)
+ {
+ switch(DOM.eventGetType(event))
+ {
+ case Event.ONMOUSEDOWN:
+ {
+ startResizing();
+
+ lastX_ = DOM.eventGetClientX(event);
+ lastY_ = DOM.eventGetClientY(event);
+
+ DOM.setCapture(getElement());
+
+ observer_.onResizingStarted();
+
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ }
+
+ case Event.ONMOUSEMOVE:
+ {
+ if (isResizing())
+ {
+ int x = DOM.eventGetClientX(event);
+ int y = DOM.eventGetClientY(event);
+
+ int xDelta = x - lastX_;
+ int yDelta = y - lastY_;
+
+ lastX_ = DOM.eventGetClientX(event);
+ lastY_ = DOM.eventGetClientY(event);
+
+ observer_.onResizing(xDelta, yDelta);
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+
+ case Event.ONMOUSEUP:
+ {
+ if (isResizing())
+ {
+ stopResizing();
+ DOM.releaseCapture(getElement());
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+
+ case Event.ONLOSECAPTURE: // IE-only
+ {
+ if (isResizing())
+ stopResizing();
+ break;
+ }
+ }
+ }
+
+ private boolean isResizing()
+ {
+ return sizing_;
+ }
+
+ private void startResizing()
+ {
+ sizing_ = true;
+ }
+
+ private void stopResizing()
+ {
+ sizing_ = false;
+ lastX_ = 0;
+ lastY_ = 0;
+ observer_.onResizingCompleted();
+ }
+
+ interface Styles extends CssResource
+ {
+ String resizeGripper();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("ResizeGripper.css")
+ Styles styles();
+
+ ImageResource resizeGripper();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private final ImageResource gripperImageResource_;
+ private final Observer observer_;
+
+ private boolean sizing_ = false;
+ private int lastX_ = 0;
+ private int lastY_ = 0;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ScrollPanelWithClick.java b/src/gwt/src/org/rstudio/core/client/widget/ScrollPanelWithClick.java
new file mode 100644
index 0000000..fe1411c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ScrollPanelWithClick.java
@@ -0,0 +1,45 @@
+/*
+ * ScrollPanelWithClick.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ScrollPanelWithClick extends ScrollPanel
+{
+ public ScrollPanelWithClick()
+ {
+ }
+
+ public ScrollPanelWithClick(Widget widget)
+ {
+ super(widget);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ScrollableToolbarPopupMenu.java b/src/gwt/src/org/rstudio/core/client/widget/ScrollableToolbarPopupMenu.java
new file mode 100644
index 0000000..928a80d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ScrollableToolbarPopupMenu.java
@@ -0,0 +1,94 @@
+/*
+ * ScrollableToolbarPopupMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+
+public class ScrollableToolbarPopupMenu extends ToolbarPopupMenu
+{
+ @Override
+ protected ToolbarMenuBar createMenuBar()
+ {
+ final ScrollableToolbarMenuBar menuBar = new ScrollableToolbarMenuBar(true);
+ menuBar.addSelectionHandler(new SelectionHandler<MenuItem>()
+ {
+ public void onSelection(SelectionEvent<MenuItem> event)
+ {
+ ensureSelectedIsVisible();
+ }
+ });
+ return menuBar;
+ }
+
+ public void ensureSelectedIsVisible()
+ {
+ if (menuBar_.getSelectedItem() != null)
+ {
+ DomUtils.ensureVisibleVert(scrollPanel_.getElement(),
+ menuBar_.getSelectedItem().getElement(),
+ 0);
+ }
+ }
+
+ @Override
+ protected Widget wrapMenuBar(ToolbarMenuBar menuBar)
+ {
+ scrollPanel_ = new ScrollPanel(menuBar);
+ scrollPanel_.addStyleName(ThemeStyles.INSTANCE.scrollableMenuBar());
+ scrollPanel_.getElement().getStyle().setOverflowY(Overflow.AUTO);
+ scrollPanel_.getElement().getStyle().setOverflowX(Overflow.HIDDEN);
+ scrollPanel_.getElement().getStyle().setProperty("maxHeight",
+ getMaxHeight() + "px");
+ return scrollPanel_;
+ }
+
+ protected int getMaxHeight()
+ {
+ return 300;
+ }
+
+ protected class ScrollableToolbarMenuBar extends ToolbarMenuBar
+ implements HasSelectionHandlers<MenuItem>
+ {
+ public ScrollableToolbarMenuBar(boolean vertical)
+ {
+ super(vertical);
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<MenuItem> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ @Override
+ public void selectItem(MenuItem item)
+ {
+ super.selectItem(item);
+ SelectionEvent.fire(this, item);
+ }
+ }
+
+ private ScrollPanel scrollPanel_;
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SearchDisplay.java b/src/gwt/src/org/rstudio/core/client/widget/SearchDisplay.java
new file mode 100644
index 0000000..9b1ab48
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SearchDisplay.java
@@ -0,0 +1,44 @@
+/*
+ * SearchDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+
+public interface SearchDisplay extends
+ HasValue<String>,
+ HasSelectionCommitHandlers<String>,
+ HasSelectionHandlers<SuggestOracle.Suggestion>,
+ HasCloseHandlers<SearchDisplay>,
+ HasFocusHandlers,
+ HasBlurHandlers,
+ HasText,
+ HasKeyDownHandlers,
+ CanFocus
+{
+ void setAutoSelectEnabled(boolean selectsFirstItem);
+ void clear();
+
+ // NOTE: only works if you are using the default display!
+ DefaultSuggestionDisplay getSuggestionDisplay();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.java b/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.java
new file mode 100644
index 0000000..8ff5861
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.java
@@ -0,0 +1,330 @@
+/*
+ * SearchWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
+import com.google.gwt.user.client.ui.SuggestBox.SuggestionDisplay;
+
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+public class SearchWidget extends Composite implements SearchDisplay
+{
+ interface MyUiBinder extends UiBinder<Widget, SearchWidget> {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ class FocusSuggestBox extends SuggestBox implements HasFocusHandlers,
+ HasBlurHandlers
+ {
+ FocusSuggestBox(SuggestOracle oracle)
+ {
+ super(oracle);
+ }
+
+ FocusSuggestBox(SuggestOracle oracle, TextBoxBase textBox)
+ {
+ super(oracle, textBox);
+ }
+
+ FocusSuggestBox(SuggestOracle oracle,
+ TextBoxBase textBox,
+ SuggestionDisplay suggestDisplay)
+ {
+ super(oracle, textBox, suggestDisplay);
+ }
+
+
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+ }
+
+
+ public SearchWidget(SuggestOracle oracle)
+ {
+ this(oracle, null);
+ }
+
+ public SearchWidget(SuggestOracle oracle,
+ SuggestionDisplay suggestDisplay)
+ {
+ this(oracle, new TextBox(), suggestDisplay);
+ }
+
+ public SearchWidget(SuggestOracle oracle,
+ TextBoxBase textBox,
+ SuggestionDisplay suggestDisplay)
+ {
+ this(oracle, textBox, suggestDisplay, true);
+ }
+
+ public SearchWidget(SuggestOracle oracle,
+ TextBoxBase textBox,
+ SuggestionDisplay suggestDisplay,
+ boolean continuousSearch)
+ {
+ textBox.getElement().setAttribute("spellcheck", "false");
+
+ if (suggestDisplay != null)
+ suggestBox_ = new FocusSuggestBox(oracle, textBox, suggestDisplay);
+ else
+ suggestBox_ = new FocusSuggestBox(oracle, textBox);
+
+ initWidget(uiBinder.createAndBindUi(this));
+ close_.setVisible(false);
+
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+
+ suggestBox_.setStylePrimaryName(styles.searchBox());
+ suggestBox_.setAutoSelectEnabled(false) ;
+ addKeyDownHandler(new KeyDownHandler() {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_ENTER:
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ SelectionCommitEvent.fire(SearchWidget.this,
+ suggestBox_.getText()) ;
+ }
+ }) ;
+ break ;
+ case KeyCodes.KEY_ESCAPE:
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ // defer the handling of ESC so that it doesn't end up
+ // inside other UI (the editor) if/when the parent search
+ // ui is dismissed
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute()
+ {
+ if (getSuggestionDisplay().isSuggestionListShowing())
+ {
+ getSuggestionDisplay().hideSuggestions();
+ setText("", true);
+ }
+ else
+ {
+ CloseEvent.fire(SearchWidget.this, SearchWidget.this);
+ }
+ }
+ });
+
+ break ;
+ }
+ }
+ }) ;
+
+ if (continuousSearch)
+ {
+ // Unlike SuggestBox's ValueChangeEvent impl, we want the
+ // event to fire as soon as the value changes
+ suggestBox_.addKeyUpHandler(new KeyUpHandler() {
+ public void onKeyUp(KeyUpEvent event)
+ {
+ String value = suggestBox_.getText();
+ if (!value.equals(lastValueSent_))
+ {
+ updateLastValue(value);
+ ValueChangeEvent.fire(SearchWidget.this, value);
+ }
+ }
+ });
+ }
+ suggestBox_.addValueChangeHandler(new ValueChangeHandler<String>()
+ {
+ public void onValueChange(ValueChangeEvent<String> evt)
+ {
+ if (!evt.getValue().equals(lastValueSent_))
+ {
+ updateLastValue(evt.getValue());
+ delegateEvent(SearchWidget.this, evt);
+ }
+ }
+ });
+
+ close_.addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+
+ suggestBox_.setText("");
+ ValueChangeEvent.fire(suggestBox_, "");
+ }
+ });
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return suggestBox_.addFocusHandler(handler);
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return suggestBox_.addBlurHandler(handler);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<String> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType()) ;
+ }
+
+ public HandlerRegistration addKeyDownHandler(final KeyDownHandler handler)
+ {
+ return suggestBox_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (ignore_ = !ignore_)
+ handler.onKeyDown(event);
+ }
+
+ private boolean ignore_ = false;
+ });
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<SuggestOracle.Suggestion> handler)
+ {
+ return suggestBox_.addSelectionHandler(handler);
+ }
+
+ public HandlerRegistration addCloseHandler(
+ CloseHandler<SearchDisplay> handler)
+ {
+ return addHandler(handler, CloseEvent.getType());
+ }
+
+ @Override
+ public void setAutoSelectEnabled(boolean selectsFirstItem)
+ {
+ suggestBox_.setAutoSelectEnabled(selectsFirstItem);
+
+ }
+
+ public String getText()
+ {
+ return suggestBox_.getText() ;
+ }
+
+ public void setText(String text)
+ {
+ suggestBox_.setText(text) ;
+ }
+
+ public void setText(String text, boolean fireEvents)
+ {
+ suggestBox_.setValue(text, fireEvents);
+ }
+
+ @Override
+ public String getValue()
+ {
+ return suggestBox_.getValue();
+ }
+
+ @Override
+ public void setValue(String value)
+ {
+ suggestBox_.setValue(value);
+ }
+
+ @Override
+ public void setValue(String value, boolean fireEvents)
+ {
+ suggestBox_.setValue(value, fireEvents);
+ }
+
+ public void setIcon(ImageResource image)
+ {
+ icon_.setResource(image);
+ }
+
+ public void focus()
+ {
+ suggestBox_.setFocus(true);
+ }
+
+ public void clear()
+ {
+ setText("", true);
+ close_.setVisible(false);
+ }
+
+ // NOTE: only works if you are using the default display!
+ public DefaultSuggestionDisplay getSuggestionDisplay()
+ {
+ return (DefaultSuggestionDisplay) suggestBox_.getSuggestionDisplay();
+ }
+
+ protected ValueBoxBase<String> getTextBox()
+ {
+ return suggestBox_.getValueBox();
+ }
+
+ private void updateLastValue(String value)
+ {
+ lastValueSent_ = value;
+ close_.setVisible(lastValueSent_.length() > 0);
+ }
+
+ @UiField(provided=true)
+ FocusSuggestBox suggestBox_;
+ @UiField
+ Image close_;
+ @UiField
+ Image icon_;
+
+ private String lastValueSent_ = null;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.ui.xml
new file mode 100644
index 0000000..baafb6e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SearchWidget.ui.xml
@@ -0,0 +1,26 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:f='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field='res' type='org.rstudio.core.client.theme.res.ThemeResources'/>
+
+ <g:HTMLPanel>
+ <div class="{res.themeStyles.search}">
+ <div class="{res.themeStyles.left}"></div>
+ <div class="{res.themeStyles.center}">
+ <g:Image ui:field='icon_'
+ resource='{res.smallMagGlassIcon}'
+ styleName='{res.themeStyles.searchMagGlass}' />
+ <div class="{res.themeStyles.searchBoxContainer}">
+ <f:SearchWidget.FocusSuggestBox ui:field='suggestBox_'
+ styleName='{res.themeStyles.searchBox}'/>
+ </div>
+ <g:Image ui:field='close_'
+ resource='{res.clearSearch}'
+ styleName='{res.themeStyles.clearSearch}'/>
+ </div>
+ <div class="{res.themeStyles.right}"></div>
+ </div>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SecondaryToolbar.java b/src/gwt/src/org/rstudio/core/client/widget/SecondaryToolbar.java
new file mode 100644
index 0000000..8dc18ea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SecondaryToolbar.java
@@ -0,0 +1,37 @@
+/*
+ * SecondaryToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public class SecondaryToolbar extends Toolbar
+{
+ public SecondaryToolbar()
+ {
+ this(false);
+ }
+
+ public SecondaryToolbar(boolean appearAsPrimary)
+ {
+ super();
+
+ if (!appearAsPrimary)
+ addStyleName(styles_.secondaryToolbar());
+ }
+
+ @Override
+ public int getHeight()
+ {
+ return 24;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SelectWidget.java b/src/gwt/src/org/rstudio/core/client/widget/SelectWidget.java
new file mode 100644
index 0000000..01a296c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SelectWidget.java
@@ -0,0 +1,179 @@
+/*
+ * SelectWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class SelectWidget extends Composite
+{
+ public SelectWidget(String label, String[] options)
+ {
+ this(label, options, false);
+ }
+
+ public SelectWidget(String label, String[] options, boolean listOnLeft)
+ {
+ this(label, options, null, false, true, listOnLeft);
+ }
+
+ public SelectWidget(String label,
+ String[] options,
+ String[] values,
+ boolean isMultipleSelect)
+ {
+ this(label, options, values, isMultipleSelect, false, false);
+ }
+
+ public SelectWidget(String label,
+ String[] options,
+ String[] values,
+ boolean isMultipleSelect,
+ boolean horizontalLayout,
+ boolean listOnLeft)
+ {
+ if (values == null)
+ values = options;
+
+ listBox_ = new ListBox(isMultipleSelect);
+ for (int i = 0; i < options.length; i++)
+ listBox_.addItem(options[i], values[i]);
+
+ Panel panel = null;
+ if (horizontalLayout)
+ {
+ horizontalPanel_ = new HorizontalPanel();
+ Label labelWidget = new Label(label);
+ if (listOnLeft)
+ {
+ horizontalPanel_.add(listBox_);
+ horizontalPanel_.add(labelWidget);
+ }
+ else
+ {
+ horizontalPanel_.add(labelWidget);
+ horizontalPanel_.add(listBox_);
+ }
+
+ horizontalPanel_.setCellVerticalAlignment(
+ labelWidget,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+ panel = horizontalPanel_;
+ }
+ else
+ {
+ flowPanel_ = new FlowPanel();
+ flowPanel_.add(new Label(label, true));
+ panel = flowPanel_;
+ panel.add(listBox_);
+ }
+
+ initWidget(panel);
+ addStyleName(ThemeResources.INSTANCE.themeStyles().selectWidget());
+ }
+
+ public HandlerRegistration addChangeHandler(ChangeHandler handler)
+ {
+ return listBox_.addChangeHandler(handler);
+ }
+
+ public ListBox getListBox()
+ {
+ return listBox_;
+ }
+
+ public void setChoices(String[] options)
+ {
+ setChoices(options, options);
+ }
+
+ public void setChoices(String[] options, String[] values)
+ {
+ listBox_.clear();
+ for (int i = 0; i < options.length; i++)
+ addChoice(options[i], values[i]);
+
+ if (listBox_.getItemCount() > 0)
+ listBox_.setSelectedIndex(0);
+ }
+
+ public void addChoice(String option, String value)
+ {
+ listBox_.addItem(option, value);
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ listBox_.setEnabled(enabled);
+ }
+
+ public boolean isEnabled()
+ {
+ return listBox_.isEnabled();
+ }
+
+ public boolean setValue(String value)
+ {
+ for (int i = 0; i < listBox_.getItemCount(); i++)
+ if (value.equals(listBox_.getValue(i)))
+ {
+ listBox_.setSelectedIndex(i);
+ return true;
+ }
+ return false;
+ }
+
+ public String getValue()
+ {
+ if (listBox_.getSelectedIndex() < 0)
+ return null;
+ return listBox_.getValue(listBox_.getSelectedIndex());
+ }
+
+
+ public void addWidget(Widget widget)
+ {
+ if (horizontalPanel_ != null)
+ {
+ horizontalPanel_.add(widget);
+ horizontalPanel_.setCellVerticalAlignment(
+ widget,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+ }
+ else
+ {
+ flowPanel_.add(widget);
+ }
+ }
+
+ public void insertValue(int index, String label, String value)
+ {
+ listBox_.insertItem(label, value, index);
+ }
+
+ private HorizontalPanel horizontalPanel_ = null;
+ private FlowPanel flowPanel_ = null;
+ private final ListBox listBox_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.java b/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.java
new file mode 100644
index 0000000..e253d82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.java
@@ -0,0 +1,105 @@
+/*
+ * ShortcutInfoPanel.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import java.util.List;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.ShortcutInfo;
+import org.rstudio.core.client.command.ShortcutManager;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FocusPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ShortcutInfoPanel extends Composite
+{
+ private static ShortcutInfoPanelUiBinder uiBinder = GWT
+ .create(ShortcutInfoPanelUiBinder.class);
+
+ interface ShortcutInfoPanelUiBinder extends
+ UiBinder<Widget, ShortcutInfoPanel>
+ {
+ }
+
+ public ShortcutInfoPanel(final Command onShowFullDocs)
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ List<ShortcutInfo> shortcuts =
+ ShortcutManager.INSTANCE.getActiveShortcutInfo();
+ String[][] groupNames = {
+ new String[] { "Tabs/Panes", "Files", "Console" },
+ new String[] { "Source Navigation", "Execute" },
+ new String[] { "Source Editor", "Debug" },
+ new String[] { "Source Control", "Build", "Other" }
+ };
+ int pctWidth = 100 / groupNames.length;
+ sb.appendHtmlConstant("<table width='100%'><tr>");
+ for (String[] colGroupNames: groupNames)
+ {
+ sb.appendHtmlConstant("<td width='" + pctWidth + "%'>");
+ for (String colGroupName: colGroupNames)
+ {
+ sb.appendHtmlConstant("<h2>");
+ sb.appendEscaped(colGroupName);
+ sb.appendHtmlConstant("</h2><table>");
+ for (int i = 0; i < shortcuts.size(); i++)
+ {
+ ShortcutInfo info = shortcuts.get(i);
+ if (info.getDescription() == null ||
+ info.getShortcuts().size() == 0 ||
+ !info.getGroupName().equals(colGroupName))
+ {
+ continue;
+ }
+ sb.appendHtmlConstant("<tr><td><strong>");
+ sb.appendHtmlConstant(
+ StringUtil.joinStrings(info.getShortcuts(), ", "));
+ sb.appendHtmlConstant("</strong></td><td>");
+ sb.appendEscaped(info.getDescription());
+ sb.appendHtmlConstant("</td></tr>");
+ }
+ sb.appendHtmlConstant("</table>");
+ }
+ sb.appendHtmlConstant("</td>");
+ }
+ sb.appendHtmlConstant("</td></tr></table>");
+ HTMLPanel panel = new HTMLPanel(sb.toSafeHtml());
+ shortcutPanel.add(panel);
+
+ shortcutDocLink.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onShowFullDocs.execute();
+ }
+ });
+ }
+
+ @UiField HTMLPanel shortcutPanel;
+ @UiField FocusPanel focusPanel;
+ @UiField Anchor shortcutDocLink;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.ui.xml
new file mode 100644
index 0000000..3f7c406
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ShortcutInfoPanel.ui.xml
@@ -0,0 +1,77 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style>
+ @eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ .shortcutHeader
+ {
+ font-size: 150%;
+ width: 66%;
+ }
+
+ .shortcutPanel
+ {
+ position: absolute;
+ top: 5%;
+ left: 5%;
+ height: 85%;
+ width: 85%;
+ overflow: auto;
+ background-color: rgba(20, 20, 20, 0.8);
+ color: white;
+ padding: 20px;
+ -webkit-box-shadow: 0px 0px 18px rgba(50, 50, 50, 0.8);
+ -moz-box-shadow: 0px 0px 18px rgba(50, 50, 50, 0.8);
+ box-shadow: 0px 0px 18px rgba(50, 50, 50, 0.66);
+ border-radius: 10px;
+ }
+
+ .shortcutOuterPanel
+ {
+ z-index: 1000;
+ height: 100%;
+ width: 100%;
+ }
+
+ .shortcutPanel:focus
+ {
+ outline:none
+ }
+
+ .shortcutPanel strong
+ {
+ color: #c0c0ff;
+ font-family: fixedWidthFont;
+ }
+
+ .shortcutPanel td
+ {
+ vertical-align: top;
+ }
+
+ .shortcutDocLink
+ {
+ float: right;
+ width: 33%;
+ text-align: right;
+ vertical-align: top;
+ text-decoration: underline;
+ opacity: 0.7;
+ cursor: pointer;
+ }
+ </ui:style>
+ <g:FocusPanel styleName="{style.shortcutOuterPanel}">
+ <g:FocusPanel ui:field="focusPanel" styleName="{style.shortcutPanel}">
+ <g:HTMLPanel>
+ <g:Anchor styleName="{style.shortcutDocLink}"
+ text="See All Shortcuts..."
+ ui:field="shortcutDocLink"></g:Anchor>
+ <g:Label styleName="{style.shortcutHeader}"
+ text="Keyboard Shortcut Quick Reference"></g:Label>
+ <g:HTMLPanel ui:field="shortcutPanel">
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+ </g:FocusPanel>
+ </g:FocusPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ShowContentDialog.java b/src/gwt/src/org/rstudio/core/client/widget/ShowContentDialog.java
new file mode 100644
index 0000000..3b7122a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ShowContentDialog.java
@@ -0,0 +1,100 @@
+/*
+ * ShowContentDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.dom.DomMetrics;
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+public class ShowContentDialog extends ModalDialogBase
+{
+ public ShowContentDialog(String title, String content)
+ {
+ this(title, content, new Size(0,0));
+ }
+
+ public ShowContentDialog(String title, String content, Size preferredSize)
+ {
+ setText(title);
+ preferredSize_ = preferredSize;
+
+ if (content.startsWith("<html>") || content.startsWith("<!DOCTYPE "))
+ {
+ content_ = content;
+ styleName_ = ThemeResources.INSTANCE.themeStyles().showFile();
+ isFixedFont_ = false;
+ }
+ else
+ {
+ content_ = "<pre>" + content + "</pre>";
+ styleName_ = ThemeResources.INSTANCE.themeStyles().showFileFixed();
+ isFixedFont_ = true;
+ }
+
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_CENTER);
+
+ addButtons();
+ }
+
+ protected void addButtons()
+ {
+ ThemedButton closeButton = new ThemedButton("Close", new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ closeDialog();
+ }
+ });
+ addOkButton(closeButton);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // main widget is scroll panel with embeddeed html
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setStylePrimaryName(styleName_);
+ HTML htmlContent = new HTML(content_);
+ if (isFixedFont_)
+ FontSizer.applyNormalFontSize(htmlContent);
+ scrollPanel.setWidget(htmlContent);
+
+ // if we don't have a preferred size then size based on content
+ Size size = preferredSize_ ;
+ if (size.isEmpty())
+ size = DomMetrics.measureHTML(content_, styleName_);
+
+ // compute the widget size and set it
+ Size minimumSize = new Size(300, 300);
+ size = DomMetrics.adjustedElementSize(size,
+ minimumSize,
+ 70, // pad
+ 100); // client margin
+ scrollPanel.setSize(size.width + "px", size.height + "px");
+
+ // return it
+ return scrollPanel;
+ }
+
+ private String content_;
+ private String styleName_;
+ private boolean isFixedFont_;
+ private Size preferredSize_;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SimpleMenuLabel.java b/src/gwt/src/org/rstudio/core/client/widget/SimpleMenuLabel.java
new file mode 100644
index 0000000..9701f56
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SimpleMenuLabel.java
@@ -0,0 +1,37 @@
+/*
+ * SimpleMenuLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+
+public class SimpleMenuLabel extends Composite implements MenuLabel
+{
+ public SimpleMenuLabel (Label label)
+ {
+ label_ = label;
+ initWidget(label);
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return label_.addClickHandler(handler);
+ }
+
+ private Label label_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SimplePanelWithProgress.java b/src/gwt/src/org/rstudio/core/client/widget/SimplePanelWithProgress.java
new file mode 100644
index 0000000..9a7a7b6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SimplePanelWithProgress.java
@@ -0,0 +1,59 @@
+/*
+ * SimplePanelWithProgress.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ProvidesResize;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class SimplePanelWithProgress extends SimplePanel
+ implements ProvidesResize,
+ RequiresResize
+{
+ public SimplePanelWithProgress()
+ {
+ loadProgressPanel_ = new ProgressPanel();
+ }
+
+ public SimplePanelWithProgress(Image progressImage)
+ {
+ loadProgressPanel_ = new ProgressPanel(progressImage);
+ }
+
+ @Override
+ public void setWidget(Widget widget)
+ {
+ if (loadProgressPanel_.equals(getWidget()))
+ loadProgressPanel_.endProgressOperation();
+ super.setWidget(widget);
+
+ }
+
+ public void showProgress(int delayMs)
+ {
+ setWidget(loadProgressPanel_);
+ loadProgressPanel_.beginProgressOperation(delayMs);
+ }
+
+ public void onResize()
+ {
+ if (getWidget() instanceof RequiresResize)
+ ((RequiresResize)getWidget()).onResize();
+ }
+
+ private ProgressPanel loadProgressPanel_ = new ProgressPanel();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.css b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.css
new file mode 100644
index 0000000..7768ff3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.css
@@ -0,0 +1,48 @@
+ at url SLIDELABELBOTTOM slideLabelBottom;
+ at url SLIDELABELBOTTOMLEFT slideLabelBottomLeft;
+ at url SLIDELABELBOTTOMRIGHT slideLabelBottomRight;
+ at url SLIDELABELFILL slideLabelFill;
+ at url SLIDELABELLEFT slideLabelLeft;
+ at url SLIDELABELRIGHT slideLabelRight;
+
+
+.curtain {
+ overflow-y: hidden;
+ height: 0;
+ position: relative;
+ z-index: 1000;
+}
+.content {
+ max-width: 500px;
+}
+.progress {
+ margin-right: 8px;
+}
+.border {
+}
+
+.W {
+ background: SLIDELABELLEFT repeat-y top right;
+ width: 18px;
+}
+.C {
+ background: SLIDELABELFILL repeat;
+}
+.E {
+ background: SLIDELABELRIGHT repeat-y top left;
+ width: 18px;
+}
+.SW {
+ background: SLIDELABELBOTTOMLEFT no-repeat top right;
+ width: 18px;
+ height: 18px;
+}
+.S {
+ background: SLIDELABELBOTTOM repeat-x top center;
+ height: 18px;
+}
+.SE {
+ background: SLIDELABELBOTTOMRIGHT no-repeat top left;
+ width: 18px;
+ height: 18px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.java b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.java
new file mode 100644
index 0000000..b500f09
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.java
@@ -0,0 +1,271 @@
+/*
+ * SlideLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.resources.CoreResources;
+
+public class SlideLabel extends Widget
+{
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.style().ensureInjected();
+ }
+
+ interface Binder extends UiBinder<Element, SlideLabel> {}
+ private static Binder binder = GWT.create(Binder.class);
+
+ interface Resources extends ClientBundle
+ {
+ @Source("SlideLabel.css")
+ SlideLabelCss style();
+
+ @Source("slideLabelBottom.png")
+ DataResource slideLabelBottom();
+ @Source("slideLabelBottomLeft.png")
+ DataResource slideLabelBottomLeft();
+ @Source("slideLabelBottomRight.png")
+ DataResource slideLabelBottomRight();
+ @Source("slideLabelFill.png")
+ DataResource slideLabelFill();
+ @Source("slideLabelLeft.png")
+ DataResource slideLabelLeft();
+ @Source("slideLabelRight.png")
+ DataResource slideLabelRight();
+ }
+
+ interface SlideLabelCss extends CssResource
+ {
+ String curtain();
+ String border();
+ String progress();
+ String content();
+
+ String W();
+ String C();
+ String E();
+ String SW();
+ String S();
+ String SE();
+ }
+
+ public static Command show(String label,
+ boolean asHtml,
+ boolean showProgressSpinner,
+ final LayoutPanel panel)
+ {
+ final SlideLabel slideLabel = showInternal(label,
+ asHtml,
+ showProgressSpinner,
+ panel);
+ slideLabel.show();
+ return new Command()
+ {
+ public void execute()
+ {
+ panel.remove(slideLabel);
+ }
+ };
+ }
+
+ public static void show(String label,
+ boolean asHtml,
+ boolean showProgressSpinner,
+ int autoHideMillis,
+ final LayoutPanel panel)
+ {
+ final SlideLabel slideLabel = showInternal(label,
+ asHtml,
+ showProgressSpinner,
+ panel);
+ slideLabel.show(autoHideMillis, new Command()
+ {
+ public void execute()
+ {
+ panel.remove(slideLabel);
+ }
+ });
+ }
+
+ private static SlideLabel showInternal(String label,
+ boolean asHtml,
+ boolean showProgressSpinner,
+ LayoutPanel panel)
+ {
+ final SlideLabel slideLabel = new SlideLabel(showProgressSpinner);
+ slideLabel.setText(label, asHtml);
+ panel.add(slideLabel);
+ panel.setWidgetLeftRight(slideLabel,
+ 0, Style.Unit.PX,
+ 0, Style.Unit.PX);
+ panel.setWidgetTopHeight(slideLabel,
+ 0, Style.Unit.PX,
+ 100, Style.Unit.PX);
+ panel.forceLayout();
+ return slideLabel;
+ }
+
+ public SlideLabel(boolean showProgressSpinner)
+ {
+ setElement(binder.createAndBindUi(this));
+ if (showProgressSpinner)
+ progress_.setSrc(CoreResources.INSTANCE.progress_gray_as_data().getSafeUri().asString());
+ else
+ progress_.getStyle().setDisplay(Style.Display.NONE);
+ curtain_.getStyle().setHeight(0, Style.Unit.PX);
+ }
+
+ public void setText(String label, boolean asHtml)
+ {
+ if (asHtml)
+ content_.setInnerHTML(label);
+ else
+ content_.setInnerText(label);
+ }
+
+ public void show()
+ {
+ show(-1, null);
+ }
+
+ public void show(final int autoHideMillis, final Command executeOnComplete)
+ {
+ assert autoHideMillis >= 0 || executeOnComplete == null:
+ "Possible error: executeOnComplete will never be called with " +
+ "negative value for autoHideMillis";
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ stopCurrentAnimation();
+ currentAnimation_ = new Animation() {
+ @Override
+ protected void onStart()
+ {
+ setVisible(true);
+ curtain_.getStyle().setHeight(0, Style.Unit.PX);
+ height = content_.getOffsetHeight() + 14 + 14;
+ super.onStart();
+ }
+
+ @Override
+ protected void onUpdate(double progress)
+ {
+ setHeight(height * progress);
+ }
+
+ @Override
+ protected void onComplete()
+ {
+ currentAnimation_ = null;
+ if (autoHideMillis >= 0)
+ {
+ currentAutoHideTimer_ = new Timer() {
+ @Override
+ public void run()
+ {
+ currentAutoHideTimer_ = null;
+ hide(executeOnComplete);
+ }
+ };
+ currentAutoHideTimer_.schedule(autoHideMillis);
+ }
+ }
+
+ private int height;
+ };
+ currentAnimation_.run(ANIM_MILLIS);
+ }
+ });
+ }
+
+ public void hide()
+ {
+ hide(null);
+ }
+
+ public void hide(final Command executeOnComplete)
+ {
+ stopCurrentAnimation();
+ final int height = curtain_.getOffsetHeight();
+ currentAnimation_ = new Animation() {
+ @Override
+ protected void onUpdate(double progress)
+ {
+ setHeight(height * (1-progress));
+ }
+
+ @Override
+ protected void onComplete()
+ {
+ currentAnimation_ = null;
+ super.onComplete();
+ setVisible(false);
+ if (executeOnComplete != null)
+ executeOnComplete.execute();
+ }
+ };
+ currentAnimation_.run(ANIM_MILLIS);
+ }
+
+ private void setHeight(double height)
+ {
+ curtain_.getStyle().setHeight((int)height, Style.Unit.PX);
+ }
+
+ private void stopCurrentAnimation()
+ {
+ if (currentAnimation_ != null)
+ {
+ currentAnimation_.cancel();
+ currentAnimation_ = null;
+ }
+
+ if (currentAutoHideTimer_ != null)
+ {
+ currentAutoHideTimer_.cancel();
+ currentAutoHideTimer_ = null;
+ }
+ }
+
+ @UiField
+ DivElement curtain_;
+ @UiField
+ DivElement content_;
+ @UiField
+ TableElement border_;
+ @UiField
+ ImageElement progress_;
+
+ private Animation currentAnimation_;
+ private Timer currentAutoHideTimer_;
+
+ private static final int ANIM_MILLIS = 250;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.ui.xml
new file mode 100644
index 0000000..555e587
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SlideLabel.ui.xml
@@ -0,0 +1,32 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.core.client.widget.SlideLabel.Resources"/>
+
+ <div ui:field="curtain_" class="{res.style.curtain}">
+ <div style="position: absolute;
+ bottom: 0; left: 0; right: 0; height: 360px;
+ vertical-align: bottom">
+ <table ui:field="border_" class="border" border="0" cellpadding="0" cellspacing="0" align="center" height="100%">
+ <tr>
+ <td class="{res.style.W}"/>
+ <td class="{res.style.C}" valign="bottom">
+ <table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td valign="middle"><img class="{res.style.progress}" ui:field="progress_" width="14" height="14" /></td>
+ <td valign="middle"><div class="{res.style.content}" ui:field="content_" /></td>
+ </tr>
+ </table>
+ </td>
+ <td class="{res.style.E}"/>
+ </tr>
+ <tr>
+ <td class="{res.style.SW}"/>
+ <td class="{res.style.S}"/>
+ <td class="{res.style.SE}"/>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SmallButton.css b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.css
new file mode 100644
index 0000000..a209efa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.css
@@ -0,0 +1,29 @@
+.smallButton {
+ display: block;
+ padding: 0;
+ margin: 0;
+ border: none;
+ background: transparent;
+ outline: none;
+}
+
+ at sprite .buttonLeft {
+ gwt-image: 'smallButtonLeft';
+ min-width: value('smallButtonLeft.getWidth', 'px');
+ width: value('smallButtonLeft.getWidth', 'px');
+}
+ at sprite .buttonCenter {
+ gwt-image: 'smallButtonTile';
+
+}
+ at sprite .buttonRight {
+ gwt-image: 'smallButtonRight';
+ min-width: value('smallButtonRight.getWidth', 'px');
+ width: value('smallButtonRight.getWidth', 'px');
+}
+
+.buttonContent {
+ font-size: 9px;
+ color: black;
+ white-space: nowrap;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SmallButton.java b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.java
new file mode 100644
index 0000000..1777e9a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.java
@@ -0,0 +1,121 @@
+/*
+ * SmallButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FocusWidget;
+import org.rstudio.core.client.command.AppCommand;
+
+public class SmallButton extends FocusWidget
+ implements HasClickHandlers
+{
+ interface MyBinder extends UiBinder<Element, SmallButton> {}
+ private static final MyBinder binder = GWT.create(MyBinder.class);
+ static {
+ ((Resources)GWT.create(Resources.class)).styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("SmallButton.css")
+ Styles styles();
+
+ ImageResource smallButtonLeft();
+ ImageResource smallButtonRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource smallButtonTile();
+ }
+
+ interface Styles extends CssResource
+ {
+ String smallButton();
+ String buttonLeft();
+ String buttonCenter();
+ String buttonRight();
+ String buttonContent();
+ }
+
+ public SmallButton(AppCommand command)
+ {
+ this();
+ setText(command.getButtonLabel());
+ setTitle(command.getTooltip());
+ addClickHandler(command);
+ }
+
+ public SmallButton(String text)
+ {
+ this(text, false);
+ }
+
+ public SmallButton(String text, boolean asHtml)
+ {
+ this();
+ setText(text, asHtml);
+ }
+
+ public SmallButton()
+ {
+ setElement(binder.createAndBindUi(this));
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public void setText(String text)
+ {
+ setText(text, false);
+ }
+
+ public void setText(String text, boolean asHtml)
+ {
+ if (asHtml)
+ content_.setInnerHTML(text);
+ else
+ content_.setInnerText(text);
+ }
+
+ public void fillWidth()
+ {
+ table_.setWidth("100%");
+ }
+
+ public void click()
+ {
+ ((ButtonElement)getElement().cast()).click();
+ }
+
+ @UiField
+ DivElement content_;
+ @UiField
+ TableElement table_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SmallButton.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.ui.xml
new file mode 100644
index 0000000..dc25404
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SmallButton.ui.xml
@@ -0,0 +1,14 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.core.client.widget.SmallButton.Resources'/>
+ <button class="{res.styles.smallButton}">
+ <table ui:field="table_" cellpadding='0' cellspacing='0' border='0'>
+ <tr>
+ <td class='{res.styles.buttonLeft}'></td>
+ <td class='{res.styles.buttonCenter}'><div ui:field='content_' class='{res.styles.buttonContent}'></div></td>
+ <td class='{res.styles.buttonRight}'></td>
+ </tr>
+ </table>
+ </button>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/SpanLabel.java b/src/gwt/src/org/rstudio/core/client/widget/SpanLabel.java
new file mode 100644
index 0000000..4582af4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/SpanLabel.java
@@ -0,0 +1,33 @@
+/*
+ * SpanLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.Label;
+
+public class SpanLabel extends Label
+{
+ public SpanLabel()
+ {
+ this("", true);
+ }
+
+ public SpanLabel(String label, boolean wordWrap)
+ {
+ super(Document.get().createSpanElement());
+ setText(label);
+ setWordWrap(wordWrap);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithButton.java b/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithButton.java
new file mode 100644
index 0000000..cd64c87
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithButton.java
@@ -0,0 +1,174 @@
+/*
+ * TextBoxWithButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+
+public class TextBoxWithButton extends Composite
+ implements HasValueChangeHandlers<String>,
+ CanFocus
+{
+ public TextBoxWithButton(String label, String action, ClickHandler handler)
+ {
+ this(label, "", action, handler);
+ }
+
+ public TextBoxWithButton(String label,
+ String emptyLabel,
+ String action,
+ ClickHandler handler)
+ {
+ this(label, emptyLabel, action, null, handler);
+ }
+
+ public TextBoxWithButton(String label,
+ String emptyLabel,
+ String action,
+ HelpButton helpButton,
+ ClickHandler handler)
+ {
+ emptyLabel_ = emptyLabel;
+
+ textBox_ = new TextBox();
+ textBox_.setWidth("100%");
+ textBox_.setReadOnly(true);
+
+ themedButton_ = new ThemedButton(action, handler);
+
+ inner_ = new HorizontalPanel();
+ inner_.add(textBox_);
+ inner_.add(themedButton_);
+ inner_.setCellWidth(textBox_, "100%");
+ inner_.setWidth("100%");
+
+ FlowPanel outer = new FlowPanel();
+ if (label != null)
+ {
+ Label lblCaption = new Label(label, true);
+ if (helpButton != null)
+ {
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.add(lblCaption);
+ helpButton.getElement().getStyle().setMarginLeft(5, Unit.PX);
+ panel.add(helpButton);
+ outer.add(panel);
+ }
+ else
+ {
+ outer.add(lblCaption);
+ }
+ }
+ outer.add(inner_);
+ initWidget(outer);
+
+ addStyleName(ThemeResources.INSTANCE.themeStyles().textBoxWithButton());
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return themedButton_.addClickHandler(handler);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+
+ public void focusButton()
+ {
+ themedButton_.setFocus(true);
+ }
+
+ // use a special adornment when the displayed key matches an
+ // arbitrary default value
+ public void setUseDefaultValue(String useDefaultValue)
+ {
+ useDefaultValue_ = useDefaultValue;
+ }
+
+ public void setText(String text)
+ {
+ text_ = text;
+
+ if (text_.equals(useDefaultValue_))
+ textBox_.setText("[Use Default] " + text);
+ else if (text.length() > 0)
+ textBox_.setText(text);
+ else
+ textBox_.setText(emptyLabel_);
+
+ ValueChangeEvent.fire(this, getText());
+ }
+
+ public String getText()
+ {
+ return text_;
+ }
+
+ public void setTextWidth(String width)
+ {
+ inner_.setCellWidth(textBox_, width);
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ textBox_.setReadOnly(readOnly);
+ }
+
+ public void click()
+ {
+ themedButton_.click();
+ }
+
+ public boolean isEnabled()
+ {
+ return themedButton_.isEnabled();
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ textBox_.setEnabled(enabled);
+ themedButton_.setEnabled(enabled);
+ }
+
+ protected TextBox getTextBox()
+ {
+ return textBox_;
+ }
+
+ @Override
+ public void focus()
+ {
+ textBox_.setFocus(true);
+ }
+
+ private HorizontalPanel inner_;
+ private TextBox textBox_;
+ private ThemedButton themedButton_;
+ private String emptyLabel_;
+ private String useDefaultValue_;
+ private String text_ = "";
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithCue.java b/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithCue.java
new file mode 100644
index 0000000..f6d7352
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/TextBoxWithCue.java
@@ -0,0 +1,133 @@
+/*
+ * TextBoxWithCue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.TextBox;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.WindowEx;
+
+public class TextBoxWithCue extends TextBox
+{
+ public TextBoxWithCue(String cueText)
+ {
+ cueText_ = cueText;
+ getElement().setAttribute("spellcheck", "false");
+ }
+
+ public TextBoxWithCue(String cueText, Element element)
+ {
+ super(element);
+ cueText_ = cueText;
+ element.setAttribute("spellcheck", "false");
+ }
+
+ public String getCueText()
+ {
+ return cueText_;
+ }
+
+ public void setCueText(String cueText)
+ {
+ cueText_ = cueText;
+ }
+
+ @Override
+ public String getText()
+ {
+ return isCueMode() ? "" : super.getText();
+ }
+
+ @Override
+ protected void onAttach()
+ {
+ super.onAttach();
+ if (!StringUtil.isNullOrEmpty(cueText_))
+ hookEvents();
+ }
+
+ @Override
+ protected void onDetach()
+ {
+ super.onDetach();
+ unhookEvents();
+ }
+
+ private void hookEvents()
+ {
+ unhookEvents();
+
+ FocusHandler focusHandler = new FocusHandler()
+ {
+ public void onFocus(FocusEvent event)
+ {
+ if (DomUtils.hasFocus(getElement()))
+ {
+ if (isCueMode())
+ {
+ setText("");
+ removeStyleName(CUE_STYLE);
+ }
+ }
+ }
+ };
+
+ BlurHandler blurHandler = new BlurHandler()
+ {
+ public void onBlur(BlurEvent event)
+ {
+ if (getText().length() == 0)
+ {
+ addStyleName(CUE_STYLE);
+ setText(cueText_);
+ }
+ }
+ };
+
+ registrations_ = new HandlerRegistration[] {
+ addFocusHandler(focusHandler),
+ addBlurHandler(blurHandler),
+ WindowEx.addFocusHandler(focusHandler),
+ WindowEx.addBlurHandler(blurHandler)
+ };
+
+ blurHandler.onBlur(null);
+ }
+
+ private boolean isCueMode()
+ {
+ return (getStyleName() + " ").indexOf(CUE_STYLE + " ") >= 0;
+ }
+
+ private void unhookEvents()
+ {
+ if (registrations_ != null)
+ {
+ for (HandlerRegistration reg : registrations_)
+ reg.removeHandler();
+ registrations_ = null;
+ }
+ }
+
+ private String cueText_;
+ private final String CUE_STYLE = "cueText";
+ private HandlerRegistration[] registrations_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/TextEntryModalDialog.java b/src/gwt/src/org/rstudio/core/client/widget/TextEntryModalDialog.java
new file mode 100644
index 0000000..cbc958c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/TextEntryModalDialog.java
@@ -0,0 +1,148 @@
+/*
+ * TextEntryModalDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.StringUtil;
+
+public class TextEntryModalDialog extends ModalDialog<String>
+{
+ public TextEntryModalDialog(String title,
+ String caption,
+ String defaultValue,
+ boolean usePasswordMask,
+ String extraOptionPrompt,
+ boolean extraOptionDefault,
+ boolean numbersOnly,
+ int selectionIndex,
+ int selectionLength, String okButtonCaption,
+ int width,
+ ProgressOperationWithInput<String> okOperation,
+ Operation cancelOperation)
+ {
+ super(title, okOperation, cancelOperation);
+ numbersOnly_ = numbersOnly;
+ selectionIndex_ = selectionIndex;
+ selectionLength_ = selectionLength;
+ width_ = width;
+ textBox_ = usePasswordMask ? new PasswordTextBox() :
+ numbersOnly ? new NumericTextBox() :
+ new TextBox();
+ textBox_.setWidth("100%");
+ captionLabel_ = new Label(caption);
+
+ extraOption_ = new CheckBox(StringUtil.notNull(extraOptionPrompt));
+ extraOption_.setVisible(
+ !StringUtil.isNullOrEmpty(extraOptionPrompt));
+ extraOption_.setValue(extraOptionDefault);
+
+ if (okButtonCaption != null)
+ setOkButtonCaption(okButtonCaption);
+
+ if (defaultValue != null)
+ textBox_.setText(defaultValue);
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ textBox_.setFocus(true);
+
+ if (textBox_.getText().length() > 0)
+ {
+ if (selectionIndex_ >= 0 && selectionLength_ >= 0)
+ {
+ int offset = Math.min(selectionIndex_, textBox_.getText().length());
+ int length = Math.min(selectionLength_,
+ textBox_.getText().length() - offset);
+ textBox_.setSelectionRange(offset, length);
+ }
+ else
+ textBox_.selectAll();
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel verticalPanel = new VerticalPanel();
+ verticalPanel.setSpacing(6);
+ verticalPanel.setWidth(width_ + "px");
+ verticalPanel.add(captionLabel_);
+ verticalPanel.add(textBox_);
+ verticalPanel.add(extraOption_);
+ if (extraOption_.isVisible())
+ verticalPanel.getElement().getStyle().setMarginBottom(10, Unit.PX);
+ return verticalPanel;
+ }
+
+ @Override
+ protected String collectInput()
+ {
+ return textBox_.getText();
+ }
+
+ @Override
+ protected boolean validate(String input)
+ {
+ if (input.length() == 0)
+ {
+ MessageDialog dialog = new MessageDialog(MessageDialog.ERROR,
+ "Error",
+ "You must enter a value.");
+ dialog.addButton("OK", (Operation)null, true, true);
+ dialog.showModal();
+ textBox_.setFocus(true);
+ return false;
+ }
+
+ if (numbersOnly_)
+ {
+ try
+ {
+ Integer.parseInt(input.trim());
+ }
+ catch (NumberFormatException nfe)
+ {
+ MessageDialog dialog = new MessageDialog(MessageDialog.ERROR,
+ "Error",
+ "Not a valid number.");
+ dialog.addButton("OK", (Operation)null, true, true);
+ dialog.showModal();
+ textBox_.setFocus(true);
+ textBox_.selectAll();
+ return false;
+ }
+ }
+
+ return true ;
+ }
+
+ public boolean getExtraOption()
+ {
+ return extraOption_.getValue() != null
+ && extraOption_.getValue();
+ }
+
+
+ private int width_;
+ private Label captionLabel_;
+ private TextBox textBox_;
+ private CheckBox extraOption_;
+ private final boolean numbersOnly_;
+ private final int selectionIndex_;
+ private final int selectionLength_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.css b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.css
new file mode 100644
index 0000000..f6276e4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.css
@@ -0,0 +1,136 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+
+ at external gwt-Button-DialogAction, gwt-Button-DialogActionLeft,
+ gwt-Button-DefaultDialogAction;
+
+ at url BUTTONLEFTFOCUSENABLED buttonLeftFocusEnabled;
+ at url BUTTONLEFTFOCUSSELECTED buttonLeftFocusSelected;
+ at url BUTTONLEFTFOCUSPRESSED buttonLeftFocusPressed;
+ at url BUTTONLEFTENABLED buttonLeftEnabled;
+ at url BUTTONLEFTDISABLED buttonLeftDisabled;
+ at url BUTTONLEFTSELECTED buttonLeftSelected;
+ at url BUTTONLEFTPRESSED buttonLeftPressed;
+ at url BUTTONRIGHTFOCUSENABLED buttonRightFocusEnabled;
+ at url BUTTONRIGHTFOCUSSELECTED buttonRightFocusSelected;
+ at url BUTTONRIGHTFOCUSPRESSED buttonRightFocusPressed;
+ at url BUTTONRIGHTENABLED buttonRightEnabled;
+ at url BUTTONRIGHTDISABLED buttonRightDisabled;
+ at url BUTTONRIGHTSELECTED buttonRightSelected;
+ at url BUTTONRIGHTPRESSED buttonRightPressed;
+ at url BUTTONTILEFOCUSENABLED buttonTileFocusEnabled;
+ at url BUTTONTILEFOCUSSELECTED buttonTileFocusSelected;
+ at url BUTTONTILEFOCUSPRESSED buttonTileFocusPressed;
+ at url BUTTONTILEENABLED buttonTileEnabled;
+ at url BUTTONTILEDISABLED buttonTileDisabled;
+ at url BUTTONTILESELECTED buttonTileSelected;
+ at url BUTTONTILEPRESSED buttonTilePressed;
+
+.gwt-Button-DialogAction, .themedButton {
+ padding: 0;
+ margin: 0;
+ margin-left: 8px;
+ margin-right: -1px;
+ border: none;
+ background: transparent;
+ outline: none;
+}
+
+.gwt-Button-DialogActionLeft, .left {
+ margin-left: -1px;
+ margin-right: 8px;
+}
+
+.themedButton.tight {
+ margin-left: -3px !important;
+ margin-right: -3px !important;
+}
+
+.buttonLeft {
+ background: BUTTONLEFTENABLED no-repeat;
+ width: 8px;
+ min-width: 8px;
+ height: 29px;
+}
+
+.buttonCenter {
+ background: BUTTONTILEENABLED repeat-x;
+ height: 26px;
+ padding-bottom: 3px;
+ min-width: 60px;
+}
+
+.buttonRight {
+ background: BUTTONRIGHTENABLED no-repeat;
+ width: 8px;
+ min-width: 8px;
+ height: 29px;
+}
+
+.buttonContent {
+ text-align: center;
+ font-size: 12px;
+ font-family: proportionalFont;
+}
+
+button.gwt-Button-DefaultDialogAction .buttonLeft {
+ background-image: BUTTONLEFTFOCUSENABLED;
+}
+button.gwt-Button-DefaultDialogAction .buttonCenter {
+ background-image: BUTTONTILEFOCUSENABLED;
+}
+button.gwt-Button-DefaultDialogAction .buttonRight {
+ background-image: BUTTONRIGHTFOCUSENABLED;
+}
+
+button:focus .buttonLeft {
+ background-image: BUTTONLEFTSELECTED;
+}
+button:focus .buttonCenter {
+ background-image: BUTTONTILESELECTED;
+}
+button:focus .buttonRight {
+ background-image: BUTTONRIGHTSELECTED;
+}
+
+button.gwt-Button-DefaultDialogAction:focus .buttonLeft {
+ background-image: BUTTONLEFTFOCUSSELECTED;
+}
+button.gwt-Button-DefaultDialogAction:focus .buttonCenter {
+ background-image: BUTTONTILEFOCUSSELECTED;
+}
+button.gwt-Button-DefaultDialogAction:focus .buttonRight {
+ background-image: BUTTONRIGHTFOCUSSELECTED;
+}
+
+button:active .buttonLeft {
+ background-image: BUTTONLEFTPRESSED;
+}
+button:active .buttonCenter {
+ background-image: BUTTONTILEPRESSED;
+}
+button:active .buttonRight {
+ background-image: BUTTONRIGHTPRESSED;
+}
+
+button.gwt-Button-DefaultDialogAction:active .buttonLeft {
+ background-image: BUTTONLEFTFOCUSPRESSED;
+}
+button.gwt-Button-DefaultDialogAction:active .buttonCenter {
+ background-image: BUTTONTILEFOCUSPRESSED;
+}
+button.gwt-Button-DefaultDialogAction:active .buttonRight {
+ background-image: BUTTONRIGHTFOCUSPRESSED;
+}
+
+button[disabled] .buttonLeft {
+ background-image: BUTTONLEFTDISABLED !important;
+}
+button[disabled] .buttonCenter {
+ background-image: BUTTONTILEDISABLED !important;
+}
+button[disabled] .buttonRight {
+ background-image: BUTTONRIGHTDISABLED !important;
+}
+button[disabled] .buttonContent {
+ color: #AAA;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.java b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.java
new file mode 100644
index 0000000..b22b7ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.java
@@ -0,0 +1,192 @@
+/*
+ * ThemedButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+public class ThemedButton extends FocusWidget implements HasClickHandlers
+{
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("ThemedButton.css")
+ Styles styles();
+
+ @Source("buttonLeftFocusEnabled.png")
+ DataResource buttonLeftFocusEnabled();
+ @Source("buttonLeftFocusSelected.png")
+ DataResource buttonLeftFocusSelected();
+ @Source("buttonLeftFocusPressed.png")
+ DataResource buttonLeftFocusPressed();
+ @Source("buttonLeftEnabled.png")
+ DataResource buttonLeftEnabled();
+ @Source("buttonLeftDisabled.png")
+ DataResource buttonLeftDisabled();
+ @Source("buttonLeftSelected.png")
+ DataResource buttonLeftSelected();
+ @Source("buttonLeftPressed.png")
+ DataResource buttonLeftPressed();
+ @Source("buttonRightFocusEnabled.png")
+ DataResource buttonRightFocusEnabled();
+ @Source("buttonRightFocusSelected.png")
+ DataResource buttonRightFocusSelected();
+ @Source("buttonRightFocusPressed.png")
+ DataResource buttonRightFocusPressed();
+ @Source("buttonRightEnabled.png")
+ DataResource buttonRightEnabled();
+ @Source("buttonRightDisabled.png")
+ DataResource buttonRightDisabled();
+ @Source("buttonRightSelected.png")
+ DataResource buttonRightSelected();
+ @Source("buttonRightPressed.png")
+ DataResource buttonRightPressed();
+ @Source("buttonTileFocusEnabled.png")
+ DataResource buttonTileFocusEnabled();
+ @Source("buttonTileFocusSelected.png")
+ DataResource buttonTileFocusSelected();
+ @Source("buttonTileFocusPressed.png")
+ DataResource buttonTileFocusPressed();
+ @Source("buttonTileEnabled.png")
+ DataResource buttonTileEnabled();
+ @Source("buttonTileDisabled.png")
+ DataResource buttonTileDisabled();
+ @Source("buttonTileSelected.png")
+ DataResource buttonTileSelected();
+ @Source("buttonTilePressed.png")
+ DataResource buttonTilePressed();
+ }
+
+ static interface Styles extends CssResource
+ {
+ String themedButton();
+ String left();
+ String buttonLeft();
+ String buttonCenter();
+ String buttonRight();
+ String buttonContent();
+ String tight();
+ }
+
+
+ interface MyUiBinder extends UiBinder<ButtonElement, ThemedButton>{}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ public ThemedButton(String title)
+ {
+ this(title, null);
+ }
+
+ public ThemedButton(String title, ClickHandler clickHandler)
+ {
+ button_ = uiBinder.createAndBindUi(this);
+ setElement(button_);
+
+ setStylePrimaryName("gwt-Button");
+ addStyleName(RESOURCES.styles().themedButton());
+
+ content_.setInnerText(title);
+
+ if (clickHandler != null)
+ addClickHandler(clickHandler);
+ }
+
+ public void setLeftAligned(boolean isLeft)
+ {
+ if (isLeft)
+ addStyleName(RESOURCES.styles().left());
+ else
+ removeStyleName(RESOURCES.styles().left());
+ }
+
+ public HandlerRegistration addClickHandler(final ClickHandler clickHandler)
+ {
+ // Suppress click event if button is disabled
+ return addDomHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ if (isEnabled())
+ clickHandler.onClick(event);
+ }
+ }, ClickEvent.getType());
+ }
+
+ public boolean isEnabled()
+ {
+ return !button_.isDisabled();
+ }
+
+ public void setEnabled(boolean isEnabled)
+ {
+ button_.setDisabled(!isEnabled);
+ }
+
+ public void setDefault(boolean isDefault)
+ {
+ if (isDefault != isDefault_)
+ {
+ isDefault_ = isDefault;
+ if (isDefault_)
+ addStyleDependentName("DefaultDialogAction");
+ else
+ removeStyleDependentName("DefaultDialogAction");
+ }
+ }
+
+ public void setTight(boolean tight)
+ {
+ if (tight)
+ addStyleName(RESOURCES.styles().tight());
+ else
+ removeStyleName(RESOURCES.styles().tight());
+ }
+
+ public boolean isDefault()
+ {
+ return isDefault_;
+ }
+
+ public void setText(String text)
+ {
+ content_.setInnerText(text);
+ }
+
+ public void click()
+ {
+ button_.click();
+ }
+
+ ButtonElement button_;
+ boolean isDefault_ = false;
+
+ @UiField
+ DivElement content_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.ui.xml
new file mode 100644
index 0000000..4dbe337
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ThemedButton.ui.xml
@@ -0,0 +1,14 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.core.client.widget.ThemedButton.Resources'/>
+ <button>
+ <table cellpadding='0' cellspacing='0' border='0'>
+ <tr>
+ <td class='{res.styles.buttonLeft}'></td>
+ <td class='{res.styles.buttonCenter}'><div ui:field='content_' class='{res.styles.buttonContent}'></div></td>
+ <td class='{res.styles.buttonRight}'></td>
+ </tr>
+ </table>
+ </button>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.css b/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.css
new file mode 100644
index 0000000..0e07b6a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.css
@@ -0,0 +1,63 @@
+ at external popupTopLeft, popupTopCenter, popupTopRight,
+ popupMiddleLeft, popupMiddleCenter, popupMiddleRight,
+ popupBottomLeft, popupBottomCenter, popupBottomRight,
+ popupContent;
+ at external gwt-MenuBarPopup, menuPopupTopLeft, menuPopupTopCenter, menuPopupTopRight,
+ menuPopupMiddleLeft, menuPopupMiddleCenter, menuPopupMiddleRight,
+ menuPopupBottomLeft, menuPopupBottomCenter, menuPopupBottomRight,
+ menuPopupContent;
+ at external gwt-SuggestBoxPopup, suggestPopupTopLeft, suggestPopupTopCenter, suggestPopupTopRight,
+ suggestPopupMiddleLeft, suggestPopupMiddleCenter, suggestPopupMiddleRight,
+ suggestPopupBottomLeft, suggestPopupBottomCenter, suggestPopupBottomRight,
+ suggestPopupContent;
+ at external gwt-MenuBar-vertical;
+
+.themedPopupPanel {
+ margin: -6px;
+ margin-top: 0px;
+}
+
+ at sprite .themedPopupPanel .popupTopLeft, .menuPopupTopLeft, .suggestPopupTopLeft {
+ gwt-image: 'popupTopLeft';
+ width: value('popupTopLeft.getWidth', 'px');
+ height: value('popupTopLeft.getHeight', 'px');
+}
+ at sprite .themedPopupPanel .popupTopCenter, .menuPopupTopCenter, .suggestPopupTopCenter {
+ gwt-image: 'popupTopCenter';
+ height: value('popupTopCenter.getHeight', 'px');
+}
+ at sprite .themedPopupPanel .popupTopRight, .menuPopupTopRight, .suggestPopupTopRight {
+ gwt-image: 'popupTopRight';
+ width: value('popupTopRight.getWidth', 'px');
+ height: value('popupTopRight.getHeight', 'px');
+}
+ at sprite .themedPopupPanel .popupMiddleLeft, .menuPopupMiddleLeft, .suggestPopupMiddleLeft {
+ gwt-image: 'popupMiddleLeft';
+ width: value('popupMiddleLeft.getWidth', 'px');
+}
+.themedPopupPanel .popupMiddleCenter, .menuPopupMiddleCenter, .suggestPopupMiddleCenter {
+ background-color: white;
+}
+ at sprite .themedPopupPanel .popupMiddleRight, .menuPopupMiddleRight, .suggestPopupMiddleRight {
+ gwt-image: 'popupMiddleRight';
+ width: value('popupMiddleRight.getWidth', 'px');
+}
+ at sprite .themedPopupPanel .popupBottomLeft, .menuPopupBottomLeft, .suggestPopupBottomLeft {
+ gwt-image: 'popupBottomLeft';
+ width: value('popupBottomLeft.getWidth', 'px');
+ height: value('popupBottomLeft.getHeight', 'px');
+}
+ at sprite .themedPopupPanel .popupBottomCenter, .menuPopupBottomCenter, .suggestPopupBottomCenter {
+ gwt-image: 'popupBottomCenter';
+ height: value('popupBottomCenter.getHeight', 'px');
+}
+ at sprite .themedPopupPanel .popupBottomRight, .menuPopupBottomRight, .suggestPopupBottomRight {
+ gwt-image: 'popupBottomRight';
+ width: value('popupBottomRight.getWidth', 'px');
+ height: value('popupBottomRight.getHeight', 'px');
+}
+
+.gwt-MenuBarPopup {
+ margin: -5px;
+ margin-top: 0;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.java b/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.java
new file mode 100644
index 0000000..d0ded58
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ThemedPopupPanel.java
@@ -0,0 +1,85 @@
+/*
+ * ThemedPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.ui.DecoratedPopupPanel;
+
+public class ThemedPopupPanel extends DecoratedPopupPanel
+{
+ public interface Resources extends ClientBundle
+ {
+ @Source("ThemedPopupPanel.css")
+ Styles styles();
+
+ ImageResource popupTopLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource popupTopCenter();
+ ImageResource popupTopRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource popupMiddleLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource popupMiddleRight();
+ ImageResource popupBottomLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource popupBottomCenter();
+ ImageResource popupBottomRight();
+ }
+
+ public interface Styles extends CssResource
+ {
+ String themedPopupPanel();
+ }
+
+ public ThemedPopupPanel()
+ {
+ super();
+ commonInit(RES);
+ }
+
+ public ThemedPopupPanel(boolean autoHide)
+ {
+ super(autoHide);
+ commonInit(RES);
+ }
+
+ public ThemedPopupPanel(boolean autoHide, boolean modal)
+ {
+ super(autoHide, modal);
+ commonInit(RES);
+ }
+
+ public ThemedPopupPanel(boolean autoHide, boolean modal, Resources res)
+ {
+ super(autoHide, modal);
+ commonInit(res);
+ }
+
+ private void commonInit(Resources res)
+ {
+ addStyleName(res.styles().themedPopupPanel());
+ }
+
+ private static Resources RES = GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/Toolbar.java b/src/gwt/src/org/rstudio/core/client/widget/Toolbar.java
new file mode 100644
index 0000000..e8ec64d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/Toolbar.java
@@ -0,0 +1,314 @@
+/*
+ * Toolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
+import org.rstudio.core.client.SeparatorManager;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+import java.util.AbstractList;
+
+public class Toolbar extends Composite
+{
+ private static class ChildWidgetList extends AbstractList<Widget>
+ {
+ private ChildWidgetList(ComplexPanel panel)
+ {
+ panel_ = panel;
+ }
+
+ @Override
+ public Widget get(int index)
+ {
+ return panel_.getWidget(index);
+ }
+
+ @Override
+ public int size()
+ {
+ return panel_.getWidgetCount();
+ }
+
+ private ComplexPanel panel_;
+ }
+
+ private class ToolbarSeparatorManager extends SeparatorManager<Widget>
+ {
+ @Override
+ protected boolean isSeparator(Widget item)
+ {
+ return styles_.toolbarSeparator().equals(item.getStylePrimaryName());
+ }
+
+ @Override
+ protected boolean isVisible(Widget item)
+ {
+ return item.isVisible();
+ }
+
+ @Override
+ protected void setVisible(Widget item, boolean visible)
+ {
+ item.setVisible(visible);
+ }
+ }
+
+ public Toolbar(Widget[] leftWidgets, Widget[] rightWidgets)
+ {
+ this();
+
+ if (leftWidgets != null)
+ {
+ for (int i = 0; i < leftWidgets.length; i++)
+ {
+ if (i > 0)
+ addLeftSeparator();
+ addLeftWidget(leftWidgets[i]);
+ }
+ }
+
+ if (rightWidgets != null)
+ {
+ for (int i = 0; i < rightWidgets.length; i++)
+ {
+ if (i > 0)
+ addRightSeparator();
+ addRightWidget(rightWidgets[i]);
+ }
+ }
+ }
+
+ public Toolbar()
+ {
+ super();
+
+ horizontalPanel_ = new HorizontalPanel();
+ horizontalPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ leftToolbarPanel_ = new HorizontalPanel();
+ leftToolbarPanel_.setVerticalAlignment(
+ HasVerticalAlignment.ALIGN_MIDDLE);
+ horizontalPanel_.add(leftToolbarPanel_);
+ horizontalPanel_.setCellHorizontalAlignment(
+ leftToolbarPanel_,
+ HasHorizontalAlignment.ALIGN_LEFT);
+
+ rightToolbarPanel_ = new HorizontalPanel();
+ rightToolbarPanel_.setVerticalAlignment(
+ HasVerticalAlignment.ALIGN_MIDDLE);
+ horizontalPanel_.add(rightToolbarPanel_);
+ horizontalPanel_.setCellHorizontalAlignment(
+ rightToolbarPanel_,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+
+ initWidget(horizontalPanel_);
+ setStyleName(styles_.toolbar());
+ }
+
+ protected void manageSeparators()
+ {
+ separatorsInvalidated_ = false;
+ new ToolbarSeparatorManager().manageSeparators(
+ new ChildWidgetList(leftToolbarPanel_));
+ new ToolbarSeparatorManager().manageSeparators(
+ new ChildWidgetList(rightToolbarPanel_));
+ }
+
+ public void invalidateSeparators()
+ {
+ if (!separatorsInvalidated_)
+ {
+ separatorsInvalidated_ = true;
+ Scheduler.get().scheduleFinally(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ manageSeparators();
+ }
+ });
+ }
+ }
+
+ public <TWidget extends Widget> TWidget addLeftWidget(TWidget widget)
+ {
+ leftToolbarPanel_.add(widget);
+ invalidateSeparators();
+ return widget;
+ }
+
+ public <TWidget extends Widget> TWidget addLeftWidget(
+ TWidget widget,
+ VerticalAlignmentConstant alignment)
+ {
+ addLeftWidget(widget);
+ leftToolbarPanel_.setCellVerticalAlignment(widget, alignment);
+ invalidateSeparators();
+ return widget;
+ }
+
+ public interface MenuSource
+ {
+ ToolbarPopupMenu getMenu();
+ }
+
+ public Widget addLeftPopupMenu(Label label, final ToolbarPopupMenu menu)
+ {
+ return addToolbarPopupMenu(new SimpleMenuLabel(label), menu, true);
+ }
+
+ public Widget addLeftPopupMenu(MenuLabel label, final ToolbarPopupMenu menu)
+ {
+ return addToolbarPopupMenu(label, menu, true);
+ }
+
+ public Widget addRightPopupMenu(MenuLabel label, final ToolbarPopupMenu menu)
+ {
+ return addToolbarPopupMenu(label, menu, false);
+ }
+
+ public Widget addLeftSeparator()
+ {
+ Image sep = new Image(ThemeResources.INSTANCE.toolbarSeparator());
+ sep.setStylePrimaryName(styles_.toolbarSeparator());
+ leftToolbarPanel_.add(sep);
+ invalidateSeparators();
+ return sep;
+ }
+
+ public Widget addRightSeparator()
+ {
+ Image sep = new Image(ThemeResources.INSTANCE.toolbarSeparator());
+ sep.setStylePrimaryName(styles_.toolbarSeparator());
+ rightToolbarPanel_.add(sep);
+ invalidateSeparators();
+ return sep;
+ }
+
+ public <TWidget extends Widget> TWidget addRightWidget(TWidget widget)
+ {
+ rightToolbarPanel_.add(widget);
+ invalidateSeparators();
+ return widget;
+ }
+
+ public void removeLeftWidget(Widget widget)
+ {
+ leftToolbarPanel_.remove(widget);
+ }
+
+ public void removeLeftWidgets()
+ {
+ removeAllWidgets(leftToolbarPanel_);
+ }
+
+ public void removeRightWidget(Widget widget)
+ {
+ rightToolbarPanel_.remove(widget);
+ }
+
+ public void removeRightWidgets()
+ {
+ removeAllWidgets(rightToolbarPanel_);
+ }
+
+ public void removeAllWidgets()
+ {
+ removeLeftWidgets();
+ removeRightWidgets();
+ }
+
+ public int getHeight()
+ {
+ int offsetHeight = getOffsetHeight();
+ if (offsetHeight != 0)
+ return offsetHeight;
+ else
+ return DEFAULT_HEIGHT;
+ }
+
+ private void removeAllWidgets(HorizontalPanel panel)
+ {
+ for (int i = panel.getWidgetCount()-1; i >= 0; i--)
+ panel.remove(i);
+ }
+
+ private Widget addToolbarPopupMenu(
+ MenuLabel label,
+ final ToolbarPopupMenu menu,
+ boolean left)
+ {
+ return addPopupMenu(label, new MenuSource() {
+ @Override
+ public ToolbarPopupMenu getMenu()
+ {
+ return menu;
+ }
+ }, left);
+ }
+
+
+ private Widget addPopupMenu(final MenuLabel menuLabel,
+ final MenuSource menuSource,
+ boolean left)
+ {
+ final Widget label = menuLabel.asWidget();
+ label.setStylePrimaryName("rstudio-StrongLabel") ;
+ label.getElement().getStyle().setCursor(Style.Cursor.DEFAULT);
+ label.getElement().getStyle().setOverflow(Overflow.HIDDEN);
+ if (left)
+ addLeftWidget(label);
+ else
+ addRightWidget(label);
+ Image image = new Image(ThemeResources.INSTANCE.menuDownArrow());
+ image.getElement().getStyle().setMarginLeft(5, Unit.PX);
+ image.getElement().getStyle().setMarginRight(8, Unit.PX);
+ image.getElement().getStyle().setMarginBottom(2, Unit.PX);
+ if (left)
+ addLeftWidget(image);
+ else
+ addRightWidget(image);
+
+ final ClickHandler clickHandler = new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ ToolbarPopupMenu menu = menuSource.getMenu();
+ menu.showRelativeTo(label);
+ menu.getElement().getStyle().setPaddingTop(3, Style.Unit.PX);
+ }
+ };
+ menuLabel.addClickHandler(clickHandler);
+ image.addClickHandler(clickHandler);
+
+ return image;
+ }
+
+
+ private HorizontalPanel horizontalPanel_ ;
+ private HorizontalPanel leftToolbarPanel_ ;
+ private HorizontalPanel rightToolbarPanel_ ;
+ protected final ThemeStyles styles_ = ThemeResources.INSTANCE.themeStyles();
+ private boolean separatorsInvalidated_ = false;
+
+ public static final int DEFAULT_HEIGHT = 22;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.java b/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.java
new file mode 100644
index 0000000..c3c58f8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.java
@@ -0,0 +1,417 @@
+/*
+ * ToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.*;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.ImageResourceProvider;
+import org.rstudio.core.client.command.SimpleImageResourceProvider;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+
+public class ToolbarButton extends FocusWidget
+{
+ private class SimpleHasHandlers extends HandlerManager implements HasHandlers
+ {
+ private SimpleHasHandlers()
+ {
+ super(null);
+ }
+ }
+
+ public <T extends EventHandler> ToolbarButton(
+ String text,
+ ImageResource leftImg,
+ final HandlerManager eventBus,
+ final GwtEvent<? extends T> targetEvent)
+ {
+ this(text, leftImg, new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ eventBus.fireEvent(targetEvent);
+ }
+ });
+ }
+
+ public <T extends EventHandler> ToolbarButton(
+ ImageResource img,
+ final HandlerManager eventBus,
+ final GwtEvent<? extends T> targetEvent)
+ {
+ this(null, img, eventBus, targetEvent);
+ }
+
+ public ToolbarButton(String text,
+ ImageResourceProvider leftImageProvider,
+ ClickHandler clickHandler)
+ {
+ this(text, leftImageProvider, null, clickHandler);
+ }
+
+ public ToolbarButton(String text,
+ ImageResource leftImage,
+ ClickHandler clickHandler)
+ {
+ this(text, new SimpleImageResourceProvider(leftImage), clickHandler);
+ }
+
+ public ToolbarButton(ImageResource image,
+ ClickHandler clickHandler)
+ {
+ this(null, image, clickHandler);
+ }
+
+ public ToolbarButton(ToolbarPopupMenu menu, boolean rightAlignMenu)
+ {
+ this((String)null,
+ ThemeResources.INSTANCE.menuDownArrow(),
+ (ImageResource) null,
+ (ClickHandler) null);
+
+ addMenuHandlers(menu, rightAlignMenu);
+
+ addStyleName(styles_.toolbarButtonMenu());
+ addStyleName(styles_.toolbarButtonMenuOnly());
+ }
+
+ public ToolbarButton(String text,
+ ImageResource leftImage,
+ ToolbarPopupMenu menu)
+ {
+ this(text, leftImage, menu, false);
+ }
+
+ public ToolbarButton(String text,
+ ImageResourceProvider leftImage,
+ ToolbarPopupMenu menu)
+ {
+ this(text, leftImage, menu, false);
+ }
+
+ public ToolbarButton(String text,
+ ImageResource leftImage,
+ ToolbarPopupMenu menu,
+ boolean rightAlignMenu)
+ {
+ this(text,
+ new SimpleImageResourceProvider(leftImage),
+ menu,
+ rightAlignMenu);
+ }
+
+ public ToolbarButton(String text,
+ ImageResourceProvider leftImage,
+ ToolbarPopupMenu menu,
+ boolean rightAlignMenu)
+ {
+ this(text, leftImage, ThemeResources.INSTANCE.menuDownArrow(), null);
+
+ addMenuHandlers(menu, rightAlignMenu);
+
+ addStyleName(styles_.toolbarButtonMenu());
+ }
+
+
+ private void addMenuHandlers(final ToolbarPopupMenu popupMenu,
+ final boolean rightAlign)
+ {
+ menu_ = popupMenu;
+ /*
+ * We want clicks on this button to toggle the visibility of the menu,
+ * as well as having the menu auto-hide itself as it normally does.
+ * It's necessary to manually track the visibility (menuShowing) because
+ * in the case where the menu is showing, clicking on this button first
+ * causes the menu to auto-hide and then our mouseDown handler is called
+ * (so we can't rely on menu.isShowing(), it'll always be false by the
+ * time you get into the mousedown handler).
+ */
+
+ final boolean[] menuShowing = new boolean[1];
+
+ addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ addStyleName(styles_.toolbarButtonPushed());
+ // Some menus are rebuilt on every invocation. Ask the menu for
+ // the most up-to-date version before proceeding.
+ popupMenu.getDynamicPopupMenu(
+ new ToolbarPopupMenu.DynamicPopupMenuCallback()
+ {
+ @Override
+ public void onPopupMenu(final ToolbarPopupMenu menu)
+ {
+ if (menuShowing[0])
+ menu.hide();
+ else
+ {
+ if (rightAlign)
+ {
+ menu.setPopupPositionAndShow(new PositionCallback()
+ {
+ @Override
+ public void setPosition(int offsetWidth,
+ int offsetHeight)
+ {
+ menu.setPopupPosition(
+ (rightImageWidget_ != null ?
+ rightImageWidget_.getAbsoluteLeft() :
+ leftImageWidget_.getAbsoluteLeft())
+ + 20 - offsetWidth,
+ ToolbarButton.this.getAbsoluteTop() +
+ ToolbarButton.this.getOffsetHeight());
+ }
+ });
+ }
+ else
+ {
+ menu.showRelativeTo(ToolbarButton.this);
+ }
+ menuShowing[0] = true;
+ }
+ }
+ });
+ }
+ });
+ popupMenu.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ removeStyleName(styles_.toolbarButtonPushed());
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ menuShowing[0] = false;
+ }
+ });
+ }
+ });
+ }
+
+ private ToolbarButton(String text,
+ ImageResource leftImage,
+ ImageResource rightImage,
+ ClickHandler clickHandler)
+ {
+ this(text,
+ new SimpleImageResourceProvider(leftImage),
+ rightImage,
+ clickHandler);
+ }
+
+ private ToolbarButton(String text,
+ ImageResourceProvider leftImage,
+ ImageResource rightImage,
+ ClickHandler clickHandler)
+ {
+ super();
+
+ setElement(binder.createAndBindUi(this));
+
+ this.setStylePrimaryName(styles_.toolbarButton());
+
+ setText(text);
+ if (leftImage != null &&
+ leftImage.getImageResource() != null)
+ {
+ leftImageWidget_ = new Image(leftImage.getImageResource());
+ leftImage.addRenderedImage(leftImageWidget_);
+ }
+ else
+ leftImageWidget_ = new Image();
+ leftImageWidget_.setStylePrimaryName(styles_.toolbarButtonLeftImage());
+ leftImageCell_.appendChild(leftImageWidget_.getElement());
+ if (rightImage != null)
+ {
+ rightImageWidget_ = new Image(rightImage);
+ rightImageWidget_.setStylePrimaryName(styles_.toolbarButtonRightImage());
+ rightImageCell_.appendChild(rightImageWidget_.getElement());
+ }
+
+ if (clickHandler != null)
+ addClickHandler(clickHandler);
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler clickHandler)
+ {
+ /*
+ * When we directly subscribe to this widget's ClickEvent, sometimes the
+ * click gets ignored (inconsistent repro but it happens enough to be
+ * annoying). Doing it this way fixes it.
+ */
+
+ hasHandlers_.addHandler(ClickEvent.getType(), clickHandler);
+
+ final HandlerRegistration mouseDown = addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ addStyleName(styles_.toolbarButtonPushed());
+ down_ = true;
+ }
+ });
+
+ final HandlerRegistration mouseOut = addMouseOutHandler(new MouseOutHandler()
+ {
+ public void onMouseOut(MouseOutEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ removeStyleName(styles_.toolbarButtonPushed());
+ down_ = false;
+ }
+ });
+
+ final HandlerRegistration mouseUp = addMouseUpHandler(new MouseUpHandler()
+ {
+ public void onMouseUp(MouseUpEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (down_)
+ {
+ down_ = false;
+ removeStyleName(styles_.toolbarButtonPushed());
+
+ NativeEvent clickEvent = Document.get().createClickEvent(
+ 1,
+ event.getScreenX(),
+ event.getScreenY(),
+ event.getClientX(),
+ event.getClientY(),
+ event.getNativeEvent().getCtrlKey(),
+ event.getNativeEvent().getAltKey(),
+ event.getNativeEvent().getShiftKey(),
+ event.getNativeEvent().getMetaKey());
+ DomEvent.fireNativeEvent(clickEvent, hasHandlers_);
+ }
+ }
+ });
+
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ mouseDown.removeHandler();
+ mouseOut.removeHandler();
+ mouseUp.removeHandler();
+ }
+ };
+ }
+
+ public void click()
+ {
+ NativeEvent clickEvent = Document.get().createClickEvent(
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false);
+ DomEvent.fireNativeEvent(clickEvent, hasHandlers_);
+ }
+
+ protected Toolbar getParentToolbar()
+ {
+ Widget parent = getParent();
+ while (parent != null)
+ {
+ if (parent instanceof Toolbar)
+ return (Toolbar) parent;
+ parent = parent.getParent();
+ }
+
+ return null;
+ }
+
+ public ToolbarPopupMenu getMenu()
+ {
+ return menu_;
+ }
+
+ public void setLeftImage(ImageResource imageResource)
+ {
+ leftImageWidget_.setResource(imageResource);
+ }
+
+ public void setText(String label)
+ {
+ if (!StringUtil.isNullOrEmpty(label))
+ {
+ label_.setInnerText(label);
+ label_.getStyle().setDisplay(Display.BLOCK);
+ removeStyleName(styles_.noLabel());
+ }
+ else
+ {
+ label_.getStyle().setDisplay(Display.NONE);
+ addStyleName(styles_.noLabel());
+ }
+ }
+
+ public String getText()
+ {
+ return StringUtil.notNull(label_.getInnerText());
+ }
+
+ private boolean down_;
+
+ private SimpleHasHandlers hasHandlers_ = new SimpleHasHandlers();
+
+ interface Binder extends UiBinder<Element, ToolbarButton> { }
+
+ private ToolbarPopupMenu menu_;
+ private static final Binder binder = GWT.create(Binder.class);
+
+ private static final ThemeStyles styles_ = ThemeResources.INSTANCE.themeStyles();
+
+ @UiField
+ TableCellElement leftImageCell_;
+ @UiField
+ TableCellElement rightImageCell_;
+ @UiField
+ DivElement label_;
+ private Image leftImageWidget_;
+ private Image rightImageWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.ui.xml
new file mode 100644
index 0000000..647bad3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarButton.ui.xml
@@ -0,0 +1,14 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.core.client.theme.res.ThemeResources'/>
+ <button>
+ <table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td valign="top" ui:field="leftImageCell_"></td>
+ <td valign="top"><div ui:field="label_" class="{res.themeStyles.toolbarButtonLabel}"/></td>
+ <td valign="top" ui:field="rightImageCell_"></td>
+ </tr>
+ </table>
+ </button>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarFileLabel.java b/src/gwt/src/org/rstudio/core/client/widget/ToolbarFileLabel.java
new file mode 100644
index 0000000..5666982
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarFileLabel.java
@@ -0,0 +1,69 @@
+/*
+ * ToolbarFileLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import com.google.gwt.user.client.ui.Image;
+
+public class ToolbarFileLabel
+{
+ public ToolbarFileLabel(Toolbar toolbar, int maxNameWidth)
+ {
+ this(toolbar, maxNameWidth, false);
+ }
+
+ public ToolbarFileLabel(Toolbar toolbar,
+ int maxNameWidth,
+ boolean addToRight)
+ {
+ ThemeStyles styles = ThemeStyles.INSTANCE;
+ maxNameWidth_ = maxNameWidth;
+ fileImage_ = new Image();
+ fileLabel_ = new ToolbarLabel();
+ fileLabel_.addStyleName(styles.subtitle());
+ fileLabel_.addStyleName(styles.toolbarFileLabel());
+
+ if (addToRight)
+ {
+ toolbar.addRightWidget(fileImage_);
+ toolbar.addLeftWidget(fileLabel_);
+ }
+ else
+ {
+ toolbar.addLeftWidget(fileImage_);
+ toolbar.addLeftWidget(fileLabel_);
+ }
+ }
+
+ public void setFileName(String fileName)
+ {
+ fileImage_.setResource(RStudioGinjector.INSTANCE.getFileTypeRegistry()
+ .getIconForFilename(fileName));
+
+ String shortFileName = StringUtil.shortPathName(
+ FileSystemItem.createFile(fileName),
+ ThemeStyles.INSTANCE.subtitle(),
+ maxNameWidth_);
+ fileLabel_.setText(shortFileName);
+ }
+
+ private final int maxNameWidth_;
+ private final Image fileImage_;
+ private final ToolbarLabel fileLabel_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarLabel.java b/src/gwt/src/org/rstudio/core/client/widget/ToolbarLabel.java
new file mode 100644
index 0000000..f7ee769
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarLabel.java
@@ -0,0 +1,38 @@
+/*
+ * ToolbarLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.user.client.ui.Label;
+
+public class ToolbarLabel extends Label
+{
+ public ToolbarLabel(String text)
+ {
+ super(text);
+ commonInit();
+ }
+
+ public ToolbarLabel()
+ {
+ super();
+ commonInit();
+
+ }
+
+ private void commonInit()
+ {
+ addStyleDependentName("Toolbar");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenu.java b/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenu.java
new file mode 100644
index 0000000..355ab16
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenu.java
@@ -0,0 +1,210 @@
+/*
+ * ToolbarPopupMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.command.AppMenuItem;
+import org.rstudio.core.client.command.BaseMenuBar;
+
+public class ToolbarPopupMenu extends ThemedPopupPanel
+{
+ // Extensibility point for dynamically constructed popup menus. The default
+ // implementation returns itself, but extensions can do some work to build
+ // the menu and return the built menu. Callers can use this in combination
+ // with getDynamicPopupMenu() when an up-to-date instance of the object is
+ // required.
+ public interface DynamicPopupMenuCallback
+ {
+ public void onPopupMenu(ToolbarPopupMenu menu);
+ }
+
+ public ToolbarPopupMenu()
+ {
+ super(true);
+ add(wrapMenuBar(menuBar_ = createMenuBar()));
+ }
+
+ protected ToolbarMenuBar createMenuBar()
+ {
+ return new ToolbarMenuBar(true);
+ }
+
+ protected Widget wrapMenuBar(ToolbarMenuBar toolbarMenuBar)
+ {
+ return toolbarMenuBar;
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ super.onUnload();
+ menuBar_.selectItem(null);
+ }
+
+ public void selectItem(MenuItem menuItem)
+ {
+ menuBar_.selectItem(menuItem);
+ }
+
+ public void addItem(MenuItem menuItem)
+ {
+ ScheduledCommand command = menuItem.getScheduledCommand();
+ if (command == null && menuItem instanceof AppMenuItem)
+ command = ((AppMenuItem)menuItem).getScheduledCommand(true);
+ if (command != null)
+ menuItem.setScheduledCommand(new ToolbarPopupMenuCommand(command));
+ menuBar_.addItem(menuItem);
+ }
+
+ public void insertItem(MenuItem menuItem, int beforeIndex)
+ {
+ ScheduledCommand command = menuItem.getScheduledCommand() ;
+ if (command != null)
+ menuItem.setScheduledCommand(new ToolbarPopupMenuCommand(command));
+ menuBar_.insertItem(menuItem, beforeIndex) ;
+ }
+
+ public void removeItem(MenuItem menuItem)
+ {
+ menuBar_.removeItem(menuItem) ;
+ }
+
+ public boolean containsItem(MenuItem menuItem)
+ {
+ return menuBar_.getItemIndex(menuItem) >= 0 ;
+ }
+
+ public void clearItems()
+ {
+ menuBar_.clearItems() ;
+ }
+
+ public void addSeparator()
+ {
+ menuBar_.addSeparator();
+ }
+
+ public int getItemCount()
+ {
+ return menuBar_.getItemCount() ;
+ }
+
+ public void focus()
+ {
+ menuBar_.focus();
+ }
+
+ public void getDynamicPopupMenu(DynamicPopupMenuCallback callback)
+ {
+ callback.onPopupMenu(this);
+ }
+
+ private class ToolbarPopupMenuCommand implements ScheduledCommand
+ {
+ public ToolbarPopupMenuCommand(ScheduledCommand coreCommand)
+ {
+ coreCommand_ = coreCommand;
+ }
+ public void execute()
+ {
+ Scheduler.get().scheduleFinally(coreCommand_);
+ hide();
+ }
+
+ private ScheduledCommand coreCommand_;
+ }
+
+ protected class ToolbarMenuBar extends BaseMenuBar
+ {
+ public ToolbarMenuBar(boolean vertical)
+ {
+ super(vertical) ;
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ nativePreviewReg_.removeHandler();
+ super.onUnload();
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ nativePreviewReg_ = Event.addNativePreviewHandler(new NativePreviewHandler()
+ {
+ public void onPreviewNativeEvent(NativePreviewEvent e)
+ {
+ if (e.getTypeInt() == Event.ONKEYDOWN)
+ {
+ switch (e.getNativeEvent().getKeyCode())
+ {
+ case KeyCodes.KEY_ESCAPE:
+ e.cancel();
+ hide();
+ break;
+ case KeyCodes.KEY_DOWN:
+ e.cancel();
+ moveSelectionDown();
+ break;
+ case KeyCodes.KEY_UP:
+ e.cancel();
+ moveSelectionUp();
+ break;
+ case KeyCodes.KEY_ENTER:
+ e.cancel();
+ final MenuItem menuItem = getSelectedItem();
+ if (menuItem != null)
+ {
+ NativeEvent evt = Document.get().createClickEvent(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false);
+ menuItem.getElement().dispatchEvent(evt);
+ }
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ public int getItemCount()
+ {
+ return getItems().size() ;
+ }
+
+ private HandlerRegistration nativePreviewReg_;
+ }
+
+ protected ToolbarMenuBar menuBar_ ;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenuButton.java b/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenuButton.java
new file mode 100644
index 0000000..e1119d0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ToolbarPopupMenuButton.java
@@ -0,0 +1,69 @@
+/*
+ * ToolbarPopupMenuButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import org.rstudio.studio.client.common.icons.StandardIcons;
+
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class ToolbarPopupMenuButton extends ToolbarButton
+ implements HasValueChangeHandlers<String>
+{
+ public ToolbarPopupMenuButton()
+ {
+ super("",
+ StandardIcons.INSTANCE.empty_command(),
+ new ToolbarPopupMenu());
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ public void addMenuItem(final String value)
+ {
+ ToolbarPopupMenu menu = getMenu();
+
+ menu.addItem(new MenuItem(value, new Command()
+ {
+ @Override
+ public void execute()
+ {
+ setText(value);
+ }
+ }));
+ }
+
+ @Override
+ public void setText(String text)
+ {
+ boolean changed = !getText().equals(text);
+
+ super.setText(text);
+
+ if (changed)
+ ValueChangeEvent.fire(ToolbarPopupMenuButton.this, text);
+
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WindowLauncher.java b/src/gwt/src/org/rstudio/core/client/widget/WindowLauncher.java
new file mode 100644
index 0000000..9589b6d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WindowLauncher.java
@@ -0,0 +1,24 @@
+/*
+ * WindowLauncher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+public class WindowLauncher
+{
+ public static native void showPreformattedContent(String title, String content) /*-{
+ var win = window.open("", "_blank", "width=600,height=800,menubar=0,toolbar=0,location=0,status=0,scrollbars=1,resizable=1,directories=0");
+ win.document.write("<head><title>" + title + "</title></head><body><pre>" + content + "</pre></body>");
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/Wizard.css b/src/gwt/src/org/rstudio/core/client/widget/Wizard.css
new file mode 100644
index 0000000..2683ccd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/Wizard.css
@@ -0,0 +1,64 @@
+.mainWidget {
+ width: 510px;
+ height: 300px;
+}
+
+.headerLabel {
+ font-size: 14px;
+ font-weight: bold;
+ cursor: default;
+}
+
+.headerPanel {
+ width: 400px;
+ height: 42px;
+}
+
+.subcaptionLabel {
+ font-size: 12px;
+ color: #707070;
+ margin-top: 3px;
+ cursor: default;
+}
+
+.wizardBodyPanel {
+ width: 533px;
+ height: 244px;
+ margin-left: -10px;
+ margin-right: -13px;
+}
+
+.wizardPageSelector {
+
+}
+
+ at sprite .wizardBackButton {
+ gwt-image: 'wizardBackButton';
+ border: none;
+ font-weight: bold;
+ cursor: pointer;
+ padding-left: 26px;
+ padding-top: 6px;
+}
+
+ at sprite .wizardPageSelectorItem {
+ width: 100%;
+ gwt-image: 'wizardPageSelectorBackground';
+}
+
+ at sprite .wizardPageSelectorItemFirst {
+ gwt-image: 'wizardPageSelectorBackgroundFirst';
+}
+
+ at sprite .wizardPageSelectorItemLast {
+ gwt-image: 'wizardPageSelectorBackgroundLast';
+}
+
+.wizardPageSelectorItem:hover {
+ background: #DAE7F6;
+}
+
+ at sprite .wizardPageBackground {
+ gwt-image: 'wizardPageBackground';
+}
+
diff --git a/src/gwt/src/org/rstudio/core/client/widget/Wizard.java b/src/gwt/src/org/rstudio/core/client/widget/Wizard.java
new file mode 100644
index 0000000..df3eb02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/Wizard.java
@@ -0,0 +1,412 @@
+/*
+ * Wizard.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.CommandWithArg;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.layout.client.Layout.AnimationCallback;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+
+public class Wizard<I,T> extends ModalDialog<T>
+{
+ public Wizard(String caption,
+ String subCaption,
+ I initialData,
+ final ProgressOperationWithInput<T> operation)
+ {
+ super(caption, operation);
+ initialData_ = initialData;
+ subCaption_ = subCaption;
+
+ setOkButtonCaption("Create Project");
+ setOkButtonVisible(false);
+ }
+
+ protected void addPage(WizardPage<I,T> page)
+ {
+ pages_.add(page);
+
+ if (page instanceof WizardNavigationPage<?,?>)
+ {
+ ((WizardNavigationPage<I,T>) page).setSelectionHandler(
+ new CommandWithArg<WizardPage<I,T>>() {
+ @Override
+ public void execute(WizardPage<I, T> page)
+ {
+ showPage(page);
+ }
+ });
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ WizardResources res = WizardResources.INSTANCE;
+ WizardResources.Styles styles = res.styles();
+
+ VerticalPanel mainWidget = new VerticalPanel();
+ mainWidget.addStyleName(styles.mainWidget());
+
+ headerPanel_ = new LayoutPanel();
+ headerPanel_.addStyleName(styles.headerPanel());
+
+ // layout consants
+ final int kTopMargin = 5;
+ final int kLeftMargin = 8;
+ final int kCaptionWidth = 400;
+ final int kCaptionHeight = 30;
+ final int kPageUILeftMargin = 123;
+
+ // first page caption
+ subCaptionLabel_ = new Label(subCaption_);
+ subCaptionLabel_.addStyleName(styles.headerLabel());
+ headerPanel_.add(subCaptionLabel_);
+ headerPanel_.setWidgetLeftWidth(subCaptionLabel_,
+ kTopMargin, Unit.PX,
+ kCaptionWidth, Unit.PX);
+ headerPanel_.setWidgetTopHeight(subCaptionLabel_,
+ kLeftMargin, Unit.PX,
+ kCaptionHeight, Unit.PX);
+
+ // second page back button
+ ImageResource bkImg = res.wizardBackButton();
+ backButton_ = new Label("Back");
+ backButton_.addStyleName(styles.wizardBackButton());
+ headerPanel_.add(backButton_);
+ headerPanel_.setWidgetLeftWidth(backButton_,
+ kTopMargin - 2, Unit.PX,
+ bkImg.getWidth(), Unit.PX);
+ headerPanel_.setWidgetTopHeight(backButton_,
+ kTopMargin - 2, Unit.PX,
+ bkImg.getHeight(), Unit.PX);
+ backButton_.setVisible(false);
+ backButton_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ goBack();
+ }
+ });
+
+ // second page caption label
+ pageCaptionLabel_ = new Label();
+ pageCaptionLabel_.addStyleName(styles.headerLabel());
+ headerPanel_.add(pageCaptionLabel_);
+ headerPanel_.setWidgetLeftWidth(pageCaptionLabel_,
+ kPageUILeftMargin, Unit.PX,
+ kCaptionWidth, Unit.PX);
+ headerPanel_.setWidgetTopHeight(pageCaptionLabel_,
+ kLeftMargin, Unit.PX,
+ kCaptionHeight, Unit.PX);
+ pageCaptionLabel_.setVisible(false);
+
+
+ mainWidget.add(headerPanel_);
+
+ // main body panel for transitions
+ bodyPanel_ = new LayoutPanel();
+ bodyPanel_.addStyleName(styles.wizardBodyPanel());
+ bodyPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
+ mainWidget.add(bodyPanel_);
+
+ // page selection panel
+ pageSelector_ = new WizardPageSelector<I,T>(
+ pages_,
+ new CommandWithArg<WizardPage<I,T>>() {
+ @Override
+ public void execute(WizardPage<I, T> page)
+ {
+ showPage(page);
+ }
+ });
+ bodyPanel_.add(pageSelector_);
+ bodyPanel_.setWidgetTopBottom(pageSelector_, 0, Unit.PX, 0, Unit.PX);
+ bodyPanel_.setWidgetLeftRight(pageSelector_, 0, Unit.PX, 0, Unit.PX);
+ bodyPanel_.setWidgetVisible(pageSelector_, true);
+
+ // add pages and make them invisible
+ for (int i=0; i<pages_.size(); i++)
+ {
+ WizardPage<I,T> page = pages_.get(i);
+ addAndInitializePage(page);
+ }
+
+
+
+ return mainWidget;
+ }
+
+
+ private void addAndInitializePage(WizardPage<I,T> page)
+ {
+ page.setSize("100%", "100%");
+
+ bodyPanel_.add(page);
+ bodyPanel_.setWidgetTopBottom(page, 0, Unit.PX, 0, Unit.PX);
+ bodyPanel_.setWidgetLeftRight(page, 0, Unit.PX, 0, Unit.PX);
+ bodyPanel_.setWidgetVisible(page, false);
+
+ page.initialize(initialData_);
+
+ // recursively initialize child pages
+ if (page instanceof WizardNavigationPage<?,?>)
+ {
+ WizardNavigationPage<I,T> navPage = (WizardNavigationPage<I,T>)page;
+ ArrayList<WizardPage<I,T>> pages = navPage.getPages();
+ for (int i=0; i<pages.size(); i++)
+ addAndInitializePage(pages.get(i));
+ }
+ }
+
+ @Override
+ protected T collectInput()
+ {
+ WizardPage<I,T> inputPage = activeInputPage();
+ if (inputPage != null)
+ {
+ T input = ammendInput(inputPage.collectInput());
+ return input;
+ }
+ else
+ return null;
+ }
+
+ @Override
+ protected boolean validate(T input)
+ {
+ WizardPage<I,T> inputPage = activeInputPage();
+ if (inputPage != null)
+ return inputPage.validate(input);
+ else
+ return false;
+ }
+
+ private WizardPage<I,T> activeInputPage()
+ {
+ if (activePage_ != null &&
+ !(activePage_ instanceof WizardNavigationPage<?,?>))
+ {
+ return activePage_;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private void animate(final Widget from,
+ final Widget to,
+ boolean rightToLeft,
+ final Command onCompleted)
+ {
+ // protect against multiple calls
+ if (isAnimating_)
+ return;
+
+
+ bodyPanel_.setWidgetVisible(to, true);
+
+ int width = getOffsetWidth();
+
+ bodyPanel_.setWidgetLeftWidth(from,
+ 0, Unit.PX,
+ width, Unit.PX);
+ bodyPanel_.setWidgetLeftWidth(to,
+ rightToLeft ? width : -width, Unit.PX,
+ width, Unit.PX);
+ bodyPanel_.forceLayout();
+
+ bodyPanel_.setWidgetLeftWidth(from,
+ rightToLeft ? -width : width, Unit.PX,
+ width, Unit.PX);
+ bodyPanel_.setWidgetLeftWidth(to,
+ 0, Unit.PX,
+ width, Unit.PX);
+
+ isAnimating_ = true;
+
+ bodyPanel_.animate(300, new AnimationCallback()
+ {
+ @Override
+ public void onAnimationComplete()
+ {
+ bodyPanel_.setWidgetVisible(from, false);
+
+ bodyPanel_.setWidgetLeftRight(to, 0, Unit.PX, 0, Unit.PX);
+ bodyPanel_.forceLayout();
+
+ isAnimating_ = false;
+
+ onCompleted.execute();
+ }
+ @Override
+ public void onLayout(Layer layer, double progress)
+ {
+ }
+ });
+ }
+
+ private void showPage(final WizardPage<I,T> page)
+ {
+ // ask whether the page will accept the navigation
+ if (!page.acceptNavigation())
+ return;
+
+ // determine behavior based on whether this is standard page or
+ // a navigation page
+ Widget fromWidget;
+ final boolean okButtonVisible;
+
+ // are we navigating from the main selector?
+ if (activePage_ == null)
+ {
+ fromWidget = pageSelector_;
+ okButtonVisible = !(page instanceof WizardNavigationPage<?,?>);
+ activeParentNavigationPage_ = null;
+ }
+ // otherwise we must be navigating from a navigation page
+ else
+ {
+ fromWidget = activePage_;
+ okButtonVisible = true;
+ activeParentNavigationPage_ = activePage_;
+ }
+
+
+ animate(fromWidget, page, true, new Command() {
+ @Override
+ public void execute()
+ {
+ // set active page
+ activePage_ = page;
+
+ // update header
+ subCaptionLabel_.setVisible(false);
+ backButton_.setVisible(true);
+ pageCaptionLabel_.setText(page.getPageCaption());
+ pageCaptionLabel_.setVisible(true);
+
+ // make ok button visible
+ setOkButtonVisible(okButtonVisible);
+
+ // call hook
+ onPageActivated(page, okButtonVisible);
+
+ // set focus
+ FocusHelper.setFocusDeferred(page);
+ }
+ });
+ }
+
+
+ private void goBack()
+ {
+ final boolean isNavigationPage = activeParentNavigationPage_ != null;
+
+ // determine behavior based on whether we are going back to a
+ // navigation page or a selector page
+ Widget toWidget;
+ if (activeParentNavigationPage_ != null)
+ {
+ toWidget = activeParentNavigationPage_;
+ }
+ else
+ {
+ toWidget = pageSelector_;
+ }
+
+ final String pageCaptionLabel = isNavigationPage ?
+ activeParentNavigationPage_.getPageCaption() : "";
+
+ final WizardPage<I,T> newActivePage =
+ isNavigationPage ? activeParentNavigationPage_ : null;
+
+ final CanFocus focusWidget = (CanFocus)toWidget;
+
+ activeParentNavigationPage_ = null;
+
+ animate(activePage_, toWidget, false, new Command() {
+ @Override
+ public void execute()
+ {
+ // update active page
+ activePage_ = newActivePage;
+
+ // update header
+ subCaptionLabel_.setVisible(!isNavigationPage);
+ backButton_.setVisible(isNavigationPage);
+ pageCaptionLabel_.setVisible(isNavigationPage);
+ pageCaptionLabel_.setText(pageCaptionLabel);
+
+ // make ok button invisible
+ setOkButtonVisible(false);
+
+ // call hook
+ onSelectorActivated();
+
+ // set focus
+ focusWidget.focus();
+ }
+ });
+ }
+
+ protected void onPageActivated(WizardPage<I,T> page, boolean okButtonVisible)
+ {
+ }
+
+
+ protected void onSelectorActivated()
+ {
+ }
+
+ protected T ammendInput(T input)
+ {
+ return input;
+ }
+
+
+
+ private final I initialData_;
+
+ private final String subCaption_;
+
+ private LayoutPanel headerPanel_;
+ private Label subCaptionLabel_;
+ private Label backButton_;
+ private Label pageCaptionLabel_;
+
+
+
+ private LayoutPanel bodyPanel_;
+ private WizardPageSelector<I,T> pageSelector_;
+ private ArrayList<WizardPage<I,T>> pages_ = new ArrayList<WizardPage<I,T>>();
+ private WizardPage<I,T> activePage_ = null;
+ private WizardPage<I,T> activeParentNavigationPage_ = null;
+ private boolean isAnimating_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WizardNavigationPage.java b/src/gwt/src/org/rstudio/core/client/widget/WizardNavigationPage.java
new file mode 100644
index 0000000..275eb57
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WizardNavigationPage.java
@@ -0,0 +1,87 @@
+/*
+ * WizardNavigationPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.CommandWithArg;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Widget;
+
+public class WizardNavigationPage<I,T> extends WizardPage<I,T>
+{
+ public WizardNavigationPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage,
+ ArrayList<WizardPage<I,T>> pages)
+ {
+ super(title,
+ subTitle,
+ pageCaption,
+ image,
+ largeImage,
+ new WizardPageSelector<I,T>(pages));
+
+ pages_ = pages;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setSelectionHandler(CommandWithArg<WizardPage<I,T>> onSelected)
+ {
+ ((WizardPageSelector<I,T>)getWidget()).setSelectionHandler(
+ onSelected);
+ }
+
+
+ public ArrayList<WizardPage<I,T>> getPages()
+ {
+ return pages_;
+ }
+
+ @Override
+ public void focus()
+ {
+ getWidget().getElement().focus();
+ }
+
+ @Override
+ protected Widget createWidget()
+ {
+ // handled in the constructor
+ return null;
+ }
+
+ @Override
+ protected void initialize(I initData)
+ {
+ }
+
+ @Override
+ protected T collectInput()
+ {
+ return null;
+ }
+
+ @Override
+ protected boolean validate(T input)
+ {
+ return false;
+ }
+
+ private ArrayList<WizardPage<I,T>> pages_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WizardPage.java b/src/gwt/src/org/rstudio/core/client/widget/WizardPage.java
new file mode 100644
index 0000000..1c589c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WizardPage.java
@@ -0,0 +1,129 @@
+/*
+ * WizardPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class WizardPage<I,T> extends Composite
+ implements WizardPageInfo, CanFocus
+{
+ public WizardPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage)
+ {
+ this(title, subTitle, pageCaption, image, largeImage, null);
+ }
+
+ public WizardPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage,
+ Widget widget)
+ {
+ title_ = title;
+ subTitle_ = subTitle;
+ pageCaption_ = pageCaption;
+ image_ = image;
+ largeImage_ = largeImage;
+
+ if (widget != null)
+ {
+ initWidget(widget);
+ }
+ else
+ {
+ WizardResources.Styles styles = WizardResources.INSTANCE.styles();
+
+ LayoutPanel layoutPanel = new LayoutPanel();
+
+ Image pageImage = new Image(largeImage_);
+ layoutPanel.add(pageImage);
+ layoutPanel.setWidgetLeftWidth(pageImage,
+ 8, Unit.PX,
+ pageImage.getWidth(), Unit.PX);
+ layoutPanel.setWidgetTopHeight(pageImage,
+ 10, Unit.PX,
+ pageImage.getHeight(), Unit.PX);
+
+
+
+ Widget pageWidget = createWidget();
+
+ layoutPanel.add(pageWidget);
+ layoutPanel.setWidgetLeftRight(pageWidget,
+ 133, Unit.PX,
+ 15, Unit.PX);
+ layoutPanel.setWidgetTopBottom(pageWidget,
+ 10, Unit.PX,
+ 0, Unit.PX);
+
+
+ initWidget(layoutPanel);
+ addStyleName(styles.wizardPageBackground());
+ }
+ }
+
+ public String getTitle()
+ {
+ return title_;
+ }
+
+ public String getSubTitle()
+ {
+ return subTitle_;
+ }
+
+ public String getPageCaption()
+ {
+ return pageCaption_;
+ }
+
+ public ImageResource getImage()
+ {
+ return image_;
+ }
+
+ public ImageResource getLargeImage()
+ {
+ return largeImage_;
+ }
+
+ abstract protected Widget createWidget();
+
+ abstract protected void initialize(I initData);
+
+ abstract protected T collectInput();
+
+ abstract protected boolean validate(T input);
+
+ protected boolean acceptNavigation()
+ {
+ return true;
+ }
+
+ private final String title_;
+ private final String subTitle_;
+ private final String pageCaption_;
+ private final ImageResource image_;
+ private final ImageResource largeImage_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WizardPageInfo.java b/src/gwt/src/org/rstudio/core/client/widget/WizardPageInfo.java
new file mode 100644
index 0000000..751444d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WizardPageInfo.java
@@ -0,0 +1,26 @@
+/*
+ * WizardPageInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.resources.client.ImageResource;
+
+public interface WizardPageInfo
+{
+ String getTitle();
+ String getSubTitle();
+ String getPageCaption();
+ ImageResource getImage();
+ ImageResource getLargeImage();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WizardPageSelector.java b/src/gwt/src/org/rstudio/core/client/widget/WizardPageSelector.java
new file mode 100644
index 0000000..fa2b0a6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WizardPageSelector.java
@@ -0,0 +1,141 @@
+/*
+ * WizardPageSelector.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.CommandWithArg;
+
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LayoutPanel;
+
+public class WizardPageSelector<I,T> extends Composite
+ implements CanFocus
+{
+ public WizardPageSelector(ArrayList<WizardPage<I,T>> pages)
+ {
+ this(pages, null);
+ }
+
+ public WizardPageSelector(ArrayList<WizardPage<I,T>> pages,
+ final CommandWithArg<WizardPage<I,T>> onSelected)
+ {
+ onSelected_ = onSelected;
+
+ WizardResources res = WizardResources.INSTANCE;
+ WizardResources.Styles styles = res.styles();
+
+ FlowPanel pageSelectorPanel = new FlowPanel();
+ pageSelectorPanel.addStyleName(styles.wizardPageSelector());
+ pageSelectorPanel.setSize("100%", "100%");
+ for (int i=0; i<pages.size(); i++)
+ {
+ final WizardPage<I,T> page = pages.get(i);
+ PageSelectorItem pageSelector =
+ new PageSelectorItem(page, new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onSelected_.execute(page);
+ }
+ });
+
+ if (i==0)
+ pageSelector.addStyleName(styles.wizardPageSelectorItemFirst());
+
+ if (i == (pages.size() -1))
+ pageSelector.addStyleName(styles.wizardPageSelectorItemLast());
+
+ pageSelectorPanel.add(pageSelector);
+ }
+
+ initWidget(pageSelectorPanel);
+ }
+
+ public void setSelectionHandler(CommandWithArg<WizardPage<I,T>> onSelected)
+ {
+ onSelected_ = onSelected;
+ }
+
+ private class PageSelectorItem extends Composite
+ {
+ PageSelectorItem(final WizardPageInfo pageInfo, ClickHandler clickHandler)
+ {
+ WizardResources res = WizardResources.INSTANCE;
+ WizardResources.Styles styles = res.styles();
+
+ LayoutPanel layoutPanel = new LayoutPanel();
+ layoutPanel.addStyleName(styles.wizardPageSelectorItem());
+
+ Image image = new Image(pageInfo.getImage());
+ layoutPanel.add(image);
+ layoutPanel.setWidgetLeftWidth(image,
+ 10, Unit.PX,
+ image.getWidth(), Unit.PX);
+ layoutPanel.setWidgetTopHeight(image,
+ 40-(image.getHeight()/2), Unit.PX,
+ image.getHeight(), Unit.PX);
+
+
+ FlowPanel captionPanel = new FlowPanel();
+ Label titleLabel = new Label(pageInfo.getTitle());
+ titleLabel.addStyleName(styles.headerLabel());
+ captionPanel.add(titleLabel);
+ Label subTitleLabel = new Label(pageInfo.getSubTitle());
+ subTitleLabel.addStyleName(styles.subcaptionLabel());
+ captionPanel.add(subTitleLabel);
+ layoutPanel.add(captionPanel);
+ layoutPanel.setWidgetLeftWidth(captionPanel,
+ 10 + image.getWidth() + 12, Unit.PX,
+ 450, Unit.PX);
+ layoutPanel.setWidgetTopHeight(captionPanel,
+ 19, Unit.PX,
+ 55, Unit.PX);
+
+
+ Image arrowImage = new Image(res.wizardDisclosureArrow());
+ layoutPanel.add(arrowImage);
+ layoutPanel.setWidgetRightWidth(arrowImage,
+ 20, Unit.PX,
+ arrowImage.getWidth(),
+ Unit.PX);
+ layoutPanel.setWidgetTopHeight(arrowImage,
+ 40-(arrowImage.getHeight()/2), Unit.PX,
+ arrowImage.getHeight(), Unit.PX);
+
+
+ layoutPanel.addDomHandler(clickHandler, ClickEvent.getType());
+
+
+ initWidget(layoutPanel);
+ }
+ }
+
+ @Override
+ public void focus()
+ {
+ getElement().focus();
+
+ }
+
+ private CommandWithArg<WizardPage<I,T>> onSelected_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/WizardResources.java b/src/gwt/src/org/rstudio/core/client/widget/WizardResources.java
new file mode 100644
index 0000000..7288ec7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/WizardResources.java
@@ -0,0 +1,63 @@
+/*
+ * WizardResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+
+public interface WizardResources extends ClientBundle
+{
+ interface Styles extends CssResource
+ {
+ String mainWidget();
+ String headerLabel();
+ String headerPanel();
+ String subcaptionLabel();
+ String wizardBodyPanel();
+ String wizardPageSelector();
+ String wizardPageSelectorItem();
+ String wizardPageSelectorItemFirst();
+ String wizardPageSelectorItemLast();
+ String wizardPageBackground();
+ String wizardBackButton();
+ }
+
+ @Source("Wizard.css")
+ Styles styles();
+
+ ImageResource wizardBackButton();
+ ImageResource wizardDisclosureArrow();
+
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource wizardPageSelectorBackground();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource wizardPageSelectorBackgroundFirst();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource wizardPageSelectorBackgroundLast();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource wizardPageBackground();
+
+ static WizardResources INSTANCE =
+ (WizardResources)GWT.create(WizardResources.class);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.java b/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.java
new file mode 100644
index 0000000..32af102
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.java
@@ -0,0 +1,53 @@
+/*
+ * ZeroHeightPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.uibinder.client.*;
+import com.google.gwt.user.client.ui.*;
+
+/**
+ * Displays a widget without letting its effective height disturb the layout.
+ * The height/width of the wrapped widget must be fixed and known in advance.
+ */
+public class ZeroHeightPanel extends Composite
+{
+ interface Binder extends UiBinder<Widget, ZeroHeightPanel>
+ {}
+
+ @UiConstructor
+ public ZeroHeightPanel(int width,
+ int height,
+ int yOffset)
+ {
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+ outer_.getElement().getStyle().setWidth(width, Style.Unit.PX);
+ inner_.getElement().getStyle().setWidth(width, Style.Unit.PX);
+ inner_.getElement().getStyle().setHeight(height, Style.Unit.PX);
+ inner_.getElement().getStyle().setBottom(-yOffset, Style.Unit.PX);
+ }
+
+ @UiChild
+ public void addChild(Widget w)
+ {
+ inner_.add(w);
+ }
+
+ @UiField
+ HTMLPanel outer_;
+ @UiField
+ FlowPanel inner_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.ui.xml b/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.ui.xml
new file mode 100644
index 0000000..05ae8a6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/ZeroHeightPanel.ui.xml
@@ -0,0 +1,23 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .outer {
+ display: inline-block;
+ height: 0;
+ position: relative;
+ overflow: visible;
+ }
+ .inner {
+ display: inline-block;
+ position: absolute;
+ left: 0;
+ }
+ </ui:style>
+
+ <g:HTMLPanel ui:field="outer_" styleName="{style.outer}">
+ <g:FlowPanel ui:field="inner_" styleName="{style.inner}"/>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftDisabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftDisabled.png
new file mode 100644
index 0000000..0af0f4c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftEnabled.png
new file mode 100644
index 0000000..b8e4fd2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusEnabled.png
new file mode 100644
index 0000000..023cf3d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusPressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusPressed.png
new file mode 100644
index 0000000..adeba31
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusPressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusSelected.png
new file mode 100644
index 0000000..528acc8
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftFocusSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftPressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftPressed.png
new file mode 100644
index 0000000..f4c5f16
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftPressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonLeftSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftSelected.png
new file mode 100644
index 0000000..d8844be
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonLeftSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightDisabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightDisabled.png
new file mode 100644
index 0000000..2518649
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightEnabled.png
new file mode 100644
index 0000000..d45e032
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusEnabled.png
new file mode 100644
index 0000000..b6e27bf
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusPressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusPressed.png
new file mode 100644
index 0000000..836f464
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusPressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusSelected.png
new file mode 100644
index 0000000..cc534c0
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightFocusSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightPressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightPressed.png
new file mode 100644
index 0000000..2025acf
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightPressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonRightSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonRightSelected.png
new file mode 100644
index 0000000..77ec65c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonRightSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileDisabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileDisabled.png
new file mode 100644
index 0000000..6eed4b6
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileEnabled.png
new file mode 100644
index 0000000..bfdc956
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusEnabled.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusEnabled.png
new file mode 100644
index 0000000..0705019
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusEnabled.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusPressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusPressed.png
new file mode 100644
index 0000000..c330461
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusPressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusSelected.png
new file mode 100644
index 0000000..3bde594
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileFocusSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTilePressed.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTilePressed.png
new file mode 100644
index 0000000..92e3a14
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTilePressed.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/buttonTileSelected.png b/src/gwt/src/org/rstudio/core/client/widget/buttonTileSelected.png
new file mode 100644
index 0000000..8dce207
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/buttonTileSelected.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/dynamicFrame.html b/src/gwt/src/org/rstudio/core/client/widget/dynamicFrame.html
new file mode 100644
index 0000000..a997d23
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/dynamicFrame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript">
+ window.onload = function() {
+ if (window.frameElement && window.frameElement.__dynamic_init__)
+ window.frameElement.__dynamic_init__();
+ };
+ </script>
+</head>
+<body></body>
+</html>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityEvent.java b/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityEvent.java
new file mode 100644
index 0000000..ff5b37c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityEvent.java
@@ -0,0 +1,47 @@
+/*
+ * GlassVisibilityEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class GlassVisibilityEvent extends GwtEvent<GlassVisibilityHandler>
+{
+ public static final Type<GlassVisibilityHandler> TYPE =
+ new Type<GlassVisibilityHandler>();
+
+ public GlassVisibilityEvent(boolean show)
+ {
+ show_ = show;
+ }
+
+ public boolean isShow()
+ {
+ return show_;
+ }
+
+ @Override
+ public Type<GlassVisibilityHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(GlassVisibilityHandler handler)
+ {
+ handler.onGlass(this);
+ }
+
+ private final boolean show_;
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityHandler.java b/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityHandler.java
new file mode 100644
index 0000000..5d1e425
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/events/GlassVisibilityHandler.java
@@ -0,0 +1,22 @@
+/*
+ * GlassVisibilityHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface GlassVisibilityHandler extends EventHandler
+{
+ void onGlass(GlassVisibilityEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedEvent.java b/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedEvent.java
new file mode 100644
index 0000000..a014767
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * SelectionChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SelectionChangedEvent extends GwtEvent<SelectionChangedHandler>
+{
+ public static final Type<SelectionChangedHandler> TYPE =
+ new Type<SelectionChangedHandler>();
+
+ @Override
+ public Type<SelectionChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SelectionChangedHandler handler)
+ {
+ handler.onSelectionChanged(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedHandler.java b/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedHandler.java
new file mode 100644
index 0000000..d93b87b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/events/SelectionChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SelectionChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SelectionChangedHandler extends EventHandler
+{
+ void onSelectionChanged(SelectionChangedEvent e);
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottom.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottom.png
new file mode 100644
index 0000000..79d64f7
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottom.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomLeft.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomLeft.png
new file mode 100644
index 0000000..cf7f104
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomRight.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomRight.png
new file mode 100644
index 0000000..33b80f2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupClose.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupClose.png
new file mode 100644
index 0000000..78dffe2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupClose.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupLeft.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupLeft.png
new file mode 100644
index 0000000..1fb8093
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupRight.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupRight.png
new file mode 100644
index 0000000..08a6e9c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTop.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTop.png
new file mode 100644
index 0000000..bbf0379
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTop.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopLeft.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopLeft.png
new file mode 100644
index 0000000..05a2d6f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopRight.png b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopRight.png
new file mode 100644
index 0000000..8be4816
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/fullscreenPopupTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOff.png b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOff.png
new file mode 100644
index 0000000..2abd72d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOff.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOn.png b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOn.png
new file mode 100644
index 0000000..278697b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleLeftOn.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOff.png b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOff.png
new file mode 100644
index 0000000..31097ce
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOff.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOn.png b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOn.png
new file mode 100644
index 0000000..77ca43b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/LeftToggleRightOn.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/MessageDialogImages.java b/src/gwt/src/org/rstudio/core/client/widget/images/MessageDialogImages.java
new file mode 100644
index 0000000..e0e55ec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/images/MessageDialogImages.java
@@ -0,0 +1,30 @@
+/*
+ * MessageDialogImages.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.images;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface MessageDialogImages extends ClientBundle
+{
+ public static final MessageDialogImages INSTANCE =
+ GWT.create(MessageDialogImages.class);
+ ImageResource dialog_info();
+ ImageResource dialog_error();
+ ImageResource dialog_warning();
+ ImageResource dialog_question();
+ ImageResource dialog_popup_blocked();
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/ProgressImages.java b/src/gwt/src/org/rstudio/core/client/widget/images/ProgressImages.java
new file mode 100644
index 0000000..a48a5b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/images/ProgressImages.java
@@ -0,0 +1,41 @@
+/*
+ * ProgressImages.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.client.widget.images;
+
+import com.google.gwt.user.client.ui.Image;
+import org.rstudio.core.client.resources.CoreResources;
+
+public class ProgressImages
+{
+ public static Image createSmall()
+ {
+ return new Image(CoreResources.INSTANCE.progress()) ;
+ }
+
+ public static Image createSmallGray()
+ {
+ return new Image(CoreResources.INSTANCE.progress_gray());
+ }
+
+ public static Image createLarge()
+ {
+ return new Image(CoreResources.INSTANCE.progress_large());
+ }
+
+ public static Image createLargeGray()
+ {
+ return new Image(CoreResources.INSTANCE.progress_large_gray());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOff.png b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOff.png
new file mode 100644
index 0000000..c123fd0
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOff.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOn.png b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOn.png
new file mode 100644
index 0000000..c41c34d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleLeftOn.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOff.png b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOff.png
new file mode 100644
index 0000000..8458e61
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOff.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOn.png b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOn.png
new file mode 100644
index 0000000..8f66367
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/RightToggleRightOn.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/dialog_error.png b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_error.png
new file mode 100755
index 0000000..859ef67
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_error.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/dialog_info.png b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_info.png
new file mode 100755
index 0000000..831584e
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_info.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/dialog_popup_blocked.png b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_popup_blocked.png
new file mode 100644
index 0000000..5a6e216
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_popup_blocked.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/dialog_question.png b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_question.png
new file mode 100755
index 0000000..d76f976
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_question.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/dialog_warning.png b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_warning.png
new file mode 100755
index 0000000..e362b5d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/images/dialog_warning.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/images/test.htm b/src/gwt/src/org/rstudio/core/client/widget/images/test.htm
new file mode 100644
index 0000000..8843941
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/client/widget/images/test.htm
@@ -0,0 +1,3 @@
+<html>
+<div style="background: url(RightToggleRightOn.png) no-repeat top right; float: left; font-family: Lucida Grande; font-size: 11px; height: 20px; padding: 3px 6px 0 3px; font-weight: bold">History</div>
+</html>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupBottomCenter.png b/src/gwt/src/org/rstudio/core/client/widget/popupBottomCenter.png
new file mode 100644
index 0000000..689324a
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupBottomCenter.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupBottomLeft.png b/src/gwt/src/org/rstudio/core/client/widget/popupBottomLeft.png
new file mode 100644
index 0000000..0afae18
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupBottomRight.png b/src/gwt/src/org/rstudio/core/client/widget/popupBottomRight.png
new file mode 100644
index 0000000..cd405b4
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupMiddleLeft.png b/src/gwt/src/org/rstudio/core/client/widget/popupMiddleLeft.png
new file mode 100644
index 0000000..52dfbb0
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupMiddleLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupMiddleRight.png b/src/gwt/src/org/rstudio/core/client/widget/popupMiddleRight.png
new file mode 100644
index 0000000..e1cad03
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupMiddleRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupTopCenter.png b/src/gwt/src/org/rstudio/core/client/widget/popupTopCenter.png
new file mode 100644
index 0000000..890a2bc
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupTopCenter.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupTopLeft.png b/src/gwt/src/org/rstudio/core/client/widget/popupTopLeft.png
new file mode 100644
index 0000000..fbbb703
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/popupTopRight.png b/src/gwt/src/org/rstudio/core/client/widget/popupTopRight.png
new file mode 100644
index 0000000..d4beafe
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/popupTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/progress.gif b/src/gwt/src/org/rstudio/core/client/widget/progress.gif
new file mode 100644
index 0000000..6fde68c
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/progress.gif differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/resizeGripper.png b/src/gwt/src/org/rstudio/core/client/widget/resizeGripper.png
new file mode 100644
index 0000000..632345a
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/resizeGripper.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottom.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottom.png
new file mode 100755
index 0000000..293f667
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottom.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomLeft.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomLeft.png
new file mode 100755
index 0000000..664b032
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomRight.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomRight.png
new file mode 100755
index 0000000..a2244a2
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelFill.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelFill.png
new file mode 100755
index 0000000..688a2fc
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelFill.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelLeft.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelLeft.png
new file mode 100755
index 0000000..b6679a9
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/slideLabelRight.png b/src/gwt/src/org/rstudio/core/client/widget/slideLabelRight.png
new file mode 100755
index 0000000..49643a7
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/slideLabelRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/smallButtonLeft.png b/src/gwt/src/org/rstudio/core/client/widget/smallButtonLeft.png
new file mode 100755
index 0000000..38a25a6
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/smallButtonLeft.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/smallButtonRight.png b/src/gwt/src/org/rstudio/core/client/widget/smallButtonRight.png
new file mode 100755
index 0000000..b0ca78b
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/smallButtonRight.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/smallButtonTile.png b/src/gwt/src/org/rstudio/core/client/widget/smallButtonTile.png
new file mode 100755
index 0000000..bbc3993
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/smallButtonTile.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardBackButton.png b/src/gwt/src/org/rstudio/core/client/widget/wizardBackButton.png
new file mode 100755
index 0000000..211abe7
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardBackButton.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardDisclosureArrow.png b/src/gwt/src/org/rstudio/core/client/widget/wizardDisclosureArrow.png
new file mode 100755
index 0000000..78cac5f
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardDisclosureArrow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardListInnerShadow.png b/src/gwt/src/org/rstudio/core/client/widget/wizardListInnerShadow.png
new file mode 100755
index 0000000..dcf26ed
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardListInnerShadow.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardPageBackground.png b/src/gwt/src/org/rstudio/core/client/widget/wizardPageBackground.png
new file mode 100644
index 0000000..062880e
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardPageBackground.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackground.png b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackground.png
new file mode 100755
index 0000000..03833ef
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackground.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundFirst.png b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundFirst.png
new file mode 100755
index 0000000..1c88f76
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundFirst.png differ
diff --git a/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundLast.png b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundLast.png
new file mode 100755
index 0000000..125087d
Binary files /dev/null and b/src/gwt/src/org/rstudio/core/client/widget/wizardPageSelectorBackgroundLast.png differ
diff --git a/src/gwt/src/org/rstudio/core/rebind/AsyncShimGenerator.java b/src/gwt/src/org/rstudio/core/rebind/AsyncShimGenerator.java
new file mode 100644
index 0000000..b94f25d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/AsyncShimGenerator.java
@@ -0,0 +1,262 @@
+/*
+ * AsyncShimGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.*;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+
+import java.io.PrintWriter;
+
+public class AsyncShimGenerator extends Generator
+{
+ private class Helper
+ {
+ public Helper(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws Exception
+ {
+ logger_ = logger;
+ context_ = context;
+ baseType_ = context_.getTypeOracle().getType(typeName);
+ packageName_ = baseType_.getPackage().getName();
+
+ }
+
+ private void doAssert(boolean assertion, String message)
+ {
+ if (!assertion)
+ throw new IllegalArgumentException(message);
+ }
+
+ public String generate() throws Exception
+ {
+ String simpleName = baseType_.getName().replace('.', '_') + "__Impl";
+
+ PrintWriter printWriter = context_.tryCreate(
+ logger_, packageName_, simpleName);
+ if (printWriter != null)
+ {
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, simpleName);
+ factory.setSuperclass(baseType_.getName());
+ factory.addImport("com.google.gwt.core.client.GWT");
+ factory.addImport("com.google.gwt.core.client.RunAsyncCallback");
+ factory.addImport("com.google.inject.Provider");
+ factory.addImport("com.google.gwt.user.client.Command");
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitBody(writer);
+
+ // Close the class and commit it
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+ }
+ return packageName_ + "." + simpleName;
+ }
+
+ private void emitBody(SourceWriter w) throws NotFoundException
+ {
+ JClassType baseClass = context_.getTypeOracle().getType(
+ "org.rstudio.core.client.AsyncShim");
+ JClassType c = baseType_.asParameterizationOf(baseClass.isGenericType());
+ JType delayedType = c.isParameterized().getTypeArgs()[0];
+
+ w.println();
+ w.println("private " + delayedType.getQualifiedSourceName() + " o;");
+ w.println("private Provider<" + delayedType.getQualifiedSourceName() + "> po;");
+ w.println();
+ w.println("@Override");
+ w.println("public void initialize(Provider<"
+ + delayedType.getQualifiedSourceName()
+ + "> provider) {");
+ w.indentln("po = provider;");
+ w.println("}");
+ w.println();
+
+ w.println("@Override");
+ w.println("public void forceLoad(boolean downloadCodeOnly, Command continuation) {");
+ w.indentln("load(downloadCodeOnly ? -1 : 0, new Object[] {continuation});");
+ w.println("}");
+ w.println();
+
+ StringSourceWriter w_switch = new StringSourceWriter();
+
+ int methodNum = 0;
+
+ for (JMethod method : baseType_.getOverridableMethods())
+ {
+ if (!method.isAbstract())
+ continue;
+
+ doAssert(method.getReturnType().equals(JPrimitiveType.VOID),
+ "Async method had a non-void return type");
+
+ w.print("public final void ");
+ w.print(method.getName() + "(");
+ String delim = "";
+ for (JParameter param : method.getParameters())
+ {
+ w.print(delim);
+ delim = ", ";
+ w.print(param.getType().getQualifiedSourceName());
+ w.print(" " + param.getName());
+ }
+ w.print(") ");
+ if (method.getThrows().length > 0)
+ {
+ w.print("throws ");
+ String delim2 = "";
+ for (JType eType : method.getThrows())
+ {
+ w.print(delim2);
+ delim2 = ", ";
+ w.print(eType.getQualifiedSourceName());
+ }
+ }
+ w.println("{");
+ w.indent();
+
+ methodNum++;
+ if (method.getParameters().length == 0)
+ {
+ w.println("load(" + methodNum + ", null);");
+ }
+ else
+ {
+ w.println("load(" + methodNum + ", new Object[] {");
+ w.indent();
+ String delim3 = "";
+ for (JParameter p : method.getParameters())
+ {
+ w.print(delim3);
+ delim3 = ", ";
+ w.print(p.getName());
+ }
+ w.outdent();
+ w.println("});");
+ }
+
+ w.outdent();
+ w.println("}");
+ w.println();
+
+
+ w_switch.println("case " + methodNum + ":");
+ w_switch.indent();
+ w_switch.print("o." + method.getName() + "(");
+ String delim4 = "";
+ for (int i = 0; i < method.getParameters().length; i++)
+ {
+ w_switch.print(delim4);
+ delim4 = ", ";
+ w_switch.print("(");
+ w_switch.print(method.getParameters()[i].getType().getQualifiedSourceName());
+ w_switch.print(")");
+ w_switch.print("args[" + i + "]");
+ }
+ w_switch.println(");");
+ w_switch.println("break;");
+ w_switch.outdent();
+ }
+
+ w.println("private void load(final int method, final Object[] args) {");
+ w.indent();
+ w.println("GWT.runAsync(new RunAsyncCallback() {");
+ w.indent();
+ w.println("public void onFailure(Throwable reason) {");
+ w.indent();
+ w.println("try {");
+ w.indentln("onDelayLoadFailure(reason);");
+ w.println("} finally {");
+ w.indentln("if (method <= 0 && args[0] != null) ((Command)args[0]).execute();");
+ w.println("}");
+ w.outdent();
+ w.println("}");
+ w.println("public void onSuccess() {");
+ w.indent();
+ w.println("preInstantiationHook(new Command() {");
+ w.indent();
+ w.println("public void execute() {");
+ w.indent();
+ w.println("onSuccess2();");
+ w.outdent();
+ w.println("}");
+ w.outdent();
+ w.println("});");
+ w.outdent();
+ w.println("}");
+ w.println("private void onSuccess2() {");
+ w.indent();
+ w.println("try {");
+ w.indent();
+ w.println("if (method < 0) return; // download code only");
+ w.println("if (o == null) {");
+ w.indent();
+ w.println("o = po.get();");
+ w.println("onDelayLoadSuccess(o);");
+ w.outdent();
+ w.println("}");
+ w.println("switch (method) {");
+ w.println("case 0: break;");
+ w.println(w_switch.toString());
+ w.println("}");
+ w.outdent();
+ w.println("} finally {");
+ w.indentln("if (method <= 0 && args[0] != null) ((Command)args[0]).execute();");
+ w.println("}");
+ w.outdent();
+ w.println("}");
+ w.outdent();
+ w.println("});");
+ w.outdent();
+ w.println("}");
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final JClassType baseType_;
+ private final String packageName_;
+ }
+
+ @Override
+ public String generate(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws UnableToCompleteException
+ {
+ try
+ {
+ return new Helper(logger,
+ context,
+ typeName).generate();
+ }
+ catch (UnableToCompleteException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/JavaScriptPassthroughGenerator.java b/src/gwt/src/org/rstudio/core/rebind/JavaScriptPassthroughGenerator.java
new file mode 100644
index 0000000..7ef0751
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/JavaScriptPassthroughGenerator.java
@@ -0,0 +1,155 @@
+/*
+ * JavaScriptPassthroughGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import org.rstudio.core.client.js.BaseExpression;
+
+import java.io.PrintWriter;
+
+public class JavaScriptPassthroughGenerator extends Generator
+{
+ private class Helper
+ {
+ public Helper(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws Exception
+ {
+ logger_ = logger;
+ context_ = context;
+ baseType_ = context_.getTypeOracle().getType(typeName);
+ packageName_ = baseType_.getPackage().getName();
+
+ BaseExpression be = baseType_.getAnnotation(BaseExpression.class);
+ if (be == null)
+ {
+ logger_.log(Type.ERROR, "Missing @BaseExpression class annotation");
+ throw new UnableToCompleteException();
+ }
+
+ baseExpression_ = be.value();
+
+ }
+
+ public String generate() throws Exception
+ {
+ String simpleName = baseType_.getName().replace('.', '_') + "__Impl";
+
+ PrintWriter printWriter = context_.tryCreate(
+ logger_, packageName_, simpleName);
+ if (printWriter != null)
+ {
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, simpleName);
+ factory.addImplementedInterface(baseType_.getName());
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitBody(writer);
+
+ // Close the class and commit it
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+ }
+ return packageName_ + "." + simpleName;
+ }
+
+ private void emitBody(SourceWriter w) throws NotFoundException
+ {
+ for (JMethod method : baseType_.getMethods())
+ {
+ if (!method.isAbstract())
+ continue;
+
+ w.println();
+ w.print("public native final ");
+ w.print(method.getReturnType().getQualifiedSourceName());
+ w.print(" ");
+ w.print(method.getName());
+ w.print("(");
+ printParams(w, method, true);
+ w.println(") /*-{");
+
+ w.indent();
+ if (!method.getReturnType().getQualifiedSourceName().equals("void"))
+ w.print("return ");
+ w.print(baseExpression_);
+ w.print("." + method.getName() + "(");
+ printParams(w, method, false);
+ w.println(");");
+ w.outdent();
+
+ w.println("}-*/;");
+ }
+ }
+
+ private void printParams(SourceWriter w,
+ JMethod method,
+ boolean withTypes)
+ {
+ JParameter[] parameters = method.getParameters();
+ for (int i = 0; i < parameters.length; i++)
+ {
+ if (i > 0)
+ w.print(", ");
+ if (withTypes)
+ {
+ w.print(parameters[i].getType().getQualifiedSourceName());
+ w.print(" ");
+ }
+ w.print(parameters[i].getName());
+ }
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final String baseExpression_;
+ private final JClassType baseType_;
+ private final String packageName_;
+ }
+
+ @Override
+ public String generate(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws UnableToCompleteException
+ {
+ try
+ {
+ return new Helper(logger,
+ context,
+ typeName).generate();
+ }
+ catch (UnableToCompleteException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/StaticDataResourceGenerator.java b/src/gwt/src/org/rstudio/core/rebind/StaticDataResourceGenerator.java
new file mode 100644
index 0000000..c9b35b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/StaticDataResourceGenerator.java
@@ -0,0 +1,83 @@
+/*
+ * StaticDataResourceGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.resources.ext.AbstractResourceGenerator;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+import org.rstudio.core.client.resources.StaticDataResource;
+
+import java.net.URL;
+
+/**
+ * Forked from GWT's DataResourceGenerator. Only difference is that the call
+ * to context.deploy passes true for xhrCompatible.
+ */
+public final class StaticDataResourceGenerator extends AbstractResourceGenerator
+{
+ @Override
+ public String createAssignment(TreeLogger logger, ResourceContext context,
+ JMethod method) throws UnableToCompleteException
+ {
+
+ URL[] resources = ResourceGeneratorUtil.findResources(logger, context,
+ method);
+
+ if (resources.length != 1) {
+ logger.log(TreeLogger.ERROR, "Exactly one resource must be specified",
+ null);
+ throw new UnableToCompleteException();
+ }
+
+ URL resource = resources[0];
+ String outputUrlExpression = context.deploy(resource, null, true);
+
+ SourceWriter sw = new StringSourceWriter();
+ // Write the expression to create the subtype.
+ sw.println("new " + StaticDataResource.class.getName() + "() {");
+ sw.indent();
+
+ // Convenience when examining the generated code.
+ sw.println("// " + resource.toExternalForm());
+
+ sw.println("public String getUrl() {");
+ sw.indent();
+ sw.println("return " + outputUrlExpression + ";");
+ sw.outdent();
+ sw.println("}");
+
+ sw.println("public com.google.gwt.safehtml.shared.SafeUri getSafeUri() {");
+ sw.indent();
+ sw.println("return new org.rstudio.core.client.SafeUriStringImpl(" + outputUrlExpression + ");");
+ sw.outdent();
+ sw.println("}");
+
+ sw.println("public String getName() {");
+ sw.indent();
+ sw.println("return \"" + method.getName() + "\";");
+ sw.outdent();
+ sw.println("}");
+
+ sw.outdent();
+ sw.println("}");
+
+ return sw.toString();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/command/CommandBinderGenerator.java b/src/gwt/src/org/rstudio/core/rebind/command/CommandBinderGenerator.java
new file mode 100644
index 0000000..f058e7e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/command/CommandBinderGenerator.java
@@ -0,0 +1,187 @@
+/*
+ * CommandBinderGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind.command;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import org.rstudio.core.client.command.Handler;
+
+import java.io.PrintWriter;
+
+public class CommandBinderGenerator extends Generator
+{
+ private class Helper
+ {
+ public Helper(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws Exception
+ {
+ logger_ = logger;
+ context_ = context;
+ binderType_ = context_.getTypeOracle().getType(typeName);
+ packageName_ = binderType_.getPackage().getName();
+
+ JClassType[] interfaces = binderType_.getImplementedInterfaces();
+ doAssert(interfaces.length == 1,
+ binderType_.getQualifiedSourceName() + " extends >1 interfaces");
+ JParameterizedType parentType = interfaces[0].isParameterized();
+ doAssert(parentType != null, "No parent type");
+ JClassType[] classTypes = parentType.getTypeArgs();
+ doAssert(classTypes.length == 2, "Unexpected number of type args");
+ commandsType_ = classTypes[0];
+ handlersType_ = classTypes[1];
+ }
+
+ private void doAssert(boolean assertion, String message)
+ {
+ if (!assertion)
+ throw new IllegalArgumentException(message);
+ }
+
+ public String generate() throws Exception
+ {
+ String simpleName = binderType_.getName().replace('.', '_') + "__Impl";
+
+ PrintWriter printWriter = context_.tryCreate(
+ logger_, packageName_, simpleName);
+ if (printWriter != null)
+ {
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, simpleName);
+ factory.addImplementedInterface(binderType_.getName());
+ factory.addImport("com.google.gwt.event.shared.HandlerRegistration");
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitBind(writer);
+
+ // Close the class and commit it
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+ }
+ return packageName_ + "." + simpleName;
+ }
+
+ private void emitBind(SourceWriter w)
+ {
+ w.print("public HandlerRegistration bind(final ");
+ w.print(commandsType_.getQualifiedSourceName());
+ w.print(" commands, final ");
+ w.print(handlersType_.getQualifiedSourceName());
+ w.println(" handlers) {");
+ w.indent();
+
+ w.print("final java.util.ArrayList<HandlerRegistration> regs = ");
+ w.println("new java.util.ArrayList<HandlerRegistration>();");
+
+ for (JMethod method : handlersType_.getMethods())
+ {
+ Handler h = method.getAnnotation(Handler.class);
+ if (h == null)
+ continue;
+
+ if (!method.getReturnType().equals(JPrimitiveType.VOID))
+ logger_.log(TreeLogger.Type.WARN,
+ "Handler method (" + handlersType_.getQualifiedSourceName()
+ + "." + method.getName() + ") has non-void return type "
+ + method.getReturnType().getQualifiedSourceName());
+
+ if (method.getParameters().length != 0)
+ throw new IllegalArgumentException(
+ "Handler methods must not have arguments ("
+ + method.getName() + ")");
+
+ String methodName = method.getName();
+ String commandName = h.value();
+ if (commandName == null || commandName.length() == 0)
+ {
+ if (methodName.length() < 3
+ || !methodName.startsWith("on")
+ || Character.isLowerCase(methodName.charAt(2)))
+ {
+ throw new IllegalArgumentException(
+ "Invalid handler method name " + methodName);
+ }
+
+ commandName = Character.toLowerCase(methodName.charAt(2))
+ + methodName.substring(3);
+ }
+
+ w.print("regs.add(commands." + commandName + "().addHandler(");
+ w.println("new org.rstudio.core.client.command.CommandHandler() {");
+ w.indent();
+ w.println("public void onCommand(org.rstudio.core.client.command.AppCommand command) {");
+ w.indent();
+ w.println("handlers." + methodName + "();");
+ w.outdent();
+ w.println("}");
+ w.outdent();
+ w.println("}));");
+ }
+
+ w.println("return new HandlerRegistration() {");
+ w.indent();
+ w.println("public void removeHandler() {");
+ w.indent();
+ w.println("for (HandlerRegistration h : regs)");
+ w.indentln("h.removeHandler();");
+ w.outdent();
+ w.println("}");
+ w.outdent();
+ w.println("};");
+
+ w.outdent();
+ w.println("}");
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final JClassType binderType_;
+ private final String packageName_;
+ private final JClassType commandsType_;
+ private final JClassType handlersType_;
+ }
+
+ @Override
+ public String generate(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws UnableToCompleteException
+ {
+ try
+ {
+ return new Helper(logger,
+ context,
+ typeName).generate();
+ }
+ catch (UnableToCompleteException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/command/CommandBundleGenerator.java b/src/gwt/src/org/rstudio/core/rebind/command/CommandBundleGenerator.java
new file mode 100644
index 0000000..bcad721
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/command/CommandBundleGenerator.java
@@ -0,0 +1,515 @@
+/*
+ * CommandBundleGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind.command;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * This generator runs at compile time (like all GWT Generators) and creates
+ * implementations for any subinterfaces of CommandBundle that are found in
+ * client code.
+ */
+public class CommandBundleGenerator extends Generator
+{
+ @Override
+ public String generate(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws UnableToCompleteException
+ {
+ try
+ {
+ return new CommandBundleGeneratorHelper(logger,
+ context,
+ typeName).generate();
+ }
+ catch (UnableToCompleteException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+ }
+}
+
+/**
+ * The actual logic for type generation is moved into this separate class
+ * so we can access a lot of the common info as fields instead of passing
+ * them around.
+ */
+class CommandBundleGeneratorHelper
+{
+ CommandBundleGeneratorHelper(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws Exception
+ {
+ logger_ = logger;
+ context_ = context;
+ bundleType_ = context_.getTypeOracle().getType(typeName);
+ commandMethods_ = getMethods(true, false, false);
+ menuMethods_ = getMethods(false, true, false);
+ shortcutsMethods_ = getMethods(false, false, true);
+ resourceMap_ = context_.getResourcesOracle().getResourceMap();
+ packageName_ = bundleType_.getPackage().getName();
+ }
+
+ /**
+ * Generates the impl class and returns its name.
+ */
+ public String generate() throws Exception
+ {
+ ImageResourceInfo images = generateImageBundle();
+
+ simpleName_ = bundleType_.getName().replace('.', '_') + "__Impl";
+
+ PrintWriter printWriter = context_.tryCreate(
+ logger_, packageName_, simpleName_);
+ if (printWriter != null)
+ {
+ // I don't fully understand why images is sometimes null but we better
+ // not get into a situation where we are generating this type but don't
+ // know what images we can use. Empirically it seems like images is
+ // always null only when printWriter is also null.
+ assert images != null;
+
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, simpleName_);
+ factory.setSuperclass(bundleType_.getName());
+ factory.addImport("org.rstudio.core.client.command.AppCommand");
+ factory.addImport("org.rstudio.core.client.command.MenuCallback");
+ factory.addImport("org.rstudio.core.client.command.ShortcutManager");
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitConstructor(writer, images);
+ emitCommandFields(writer);
+ emitMenus(writer);
+ emitShortcuts(writer);
+ emitCommandAccessors(writer);
+
+ // Close the class and commit it
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+ }
+ return packageName_ + "." + simpleName_;
+ }
+
+ private void emitConstructor(SourceWriter writer, ImageResourceInfo images)
+ throws UnableToCompleteException
+ {
+ writer.println("public " + simpleName_ + "() {");
+
+ // Get additional properties from XML resource file, if exists
+ Map<String, Element> props = getCommandProperties();
+ // Implement the methods for the commands
+ for (JMethod method : commandMethods_)
+ emitCommandInitializers(writer, props, method, images);
+
+ writer.println();
+ writer.indentln("__registerShortcuts();");
+
+ writer.println("}");
+ }
+
+ private void emitCommandFields(SourceWriter writer)
+ throws UnableToCompleteException
+ {
+ // Declare the fields for the commands
+ for (JMethod method : commandMethods_)
+ {
+ String name = method.getName();
+ writer.println("private AppCommand " + name + "_;");
+ }
+ }
+
+ private void emitMenus(SourceWriter writer) throws UnableToCompleteException
+ {
+ for (JMethod method : menuMethods_)
+ {
+ String name = method.getName();
+ NodeList nodes = getConfigDoc("/commands/menu[@id='" + name + "']");
+ if (nodes.getLength() == 0)
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Unable to find config info for menu " + name);
+ throw new UnableToCompleteException();
+ }
+ else if (nodes.getLength() > 1)
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Duplicate menu entries for menu " + name);
+ }
+
+ String menuClass = new MenuEmitter(logger_,
+ context_,
+ bundleType_,
+ (Element) nodes.item(0)).generate();
+
+ writer.println("public void " + name + "(MenuCallback callback) {");
+ writer.indentln("new " + menuClass +
+ "(this).createMenu(callback);");
+ writer.println("}");
+ }
+ }
+
+ private void emitShortcuts(SourceWriter writer) throws UnableToCompleteException
+ {
+ writer.println("private void __registerShortcuts() {");
+ writer.indent();
+ NodeList nodes = getConfigDoc("/commands/shortcuts");
+ for (int i = 0; i < nodes.getLength(); i++)
+ {
+ NodeList groups = nodes.item(i).getChildNodes();
+ for (int j = 0; j < groups.getLength(); j++)
+ {
+ if (groups.item(j).getNodeType() != Node.ELEMENT_NODE)
+ continue;
+ String groupName = ((Element) groups.item(j)).getAttribute("name");
+ new ShortcutsEmitter(logger_, groupName,
+ (Element) groups.item(j)).generate(writer);
+ }
+ }
+ writer.outdent();
+ writer.println("}");
+ }
+
+ private JMethod[] getMethods(boolean includeCommands,
+ boolean includeMenus,
+ boolean includeShortcuts)
+ throws UnableToCompleteException
+ {
+ ArrayList<JMethod> methods = new ArrayList<JMethod>();
+ for (JMethod method : bundleType_.getMethods())
+ {
+ if (!method.isAbstract())
+ continue;
+ validateMethod(method);
+ if (!includeCommands && isCommandMethod(method))
+ continue;
+ if (!includeMenus && isMenuMethod(method))
+ continue;
+ if (!includeShortcuts && isShortcutsMethod(method))
+ continue;
+ methods.add(method);
+ }
+ return methods.toArray(new JMethod[methods.size()]);
+ }
+
+ // Log and throw if anything is awry about the declaration
+ private void validateMethod(JMethod method) throws UnableToCompleteException
+ {
+ if (isMenuMethod(method))
+ {
+ if (method.getParameters().length != 1)
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Method " + method +
+ " had the wrong number of parameters (expected 1)");
+ throw new UnableToCompleteException();
+ }
+
+ String paramType =
+ method.getParameters()[0].getType().getQualifiedSourceName();
+ if (!paramType.equals("org.rstudio.core.client.command.MenuCallback"))
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Method " + method +
+ " had wrong parameter type (expected " +
+ "org.rstudio.core.client.command.MenuCallback)");
+ throw new UnableToCompleteException();
+ }
+ }
+ else
+ {
+ if (method.getParameters().length != 0)
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Method " + method +
+ " had parameters where none were expected");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ if (!isCommandMethod(method)
+ && !isMenuMethod(method)
+ && !isShortcutsMethod(method))
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Method " + method +
+ " had an unexpected return type");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ private boolean isCommandMethod(JMethod method)
+ {
+ return method.getReturnType().getQualifiedSourceName().equals(
+ "org.rstudio.core.client.command.AppCommand");
+ }
+
+ private boolean isMenuMethod(JMethod method)
+ {
+ String sourceName = method.getReturnType().getQualifiedSourceName();
+ return sourceName.equals("void");
+ }
+
+ private boolean isShortcutsMethod(JMethod method)
+ {
+ String sourceName = method.getReturnType().getQualifiedSourceName();
+ return sourceName.equals("org.rstudio.core.client.command.ShortcutManager");
+ }
+
+ /**
+ * Emit the getter method for the command--it is implemented as a cached
+ * lazy-load. e.g.:
+ *
+ * public AppCommand newSourceDoc() {
+ * if (newSourceDoc_ == null) {
+ * newSourceDoc_ = new AppCommand();
+ * // call various setters...
+ * }
+ * return newSourceDoc_;
+ * }
+ */
+ private void emitCommandInitializers(SourceWriter writer,
+ Map<String, Element> props,
+ JMethod method,
+ ImageResourceInfo images)
+ {
+ String name = method.getName();
+ writer.println(name + "_ = new AppCommand();");
+
+ setProperty(writer, name, props.get(name), "id");
+ setProperty(writer, name, props.get(name), "desc");
+ setProperty(writer, name, props.get(name), "label");
+ setProperty(writer, name, props.get(name), "buttonLabel");
+ setProperty(writer, name, props.get(name), "menuLabel");
+ // Any additional textual properties would be added here...
+
+ setPropertyBool(writer, name, props.get(name), "visible");
+ setPropertyBool(writer, name, props.get(name), "enabled");
+ setPropertyBool(writer, name, props.get(name),
+ "preventShortcutWhenDisabled");
+ setPropertyBool(writer, name, props.get(name), "checkable");
+ setPropertyBool(writer, name, props.get(name), "checked");
+
+ if (images.hasImage(name))
+ {
+ writer.println(name + "_.setImageResource("
+ + images.getImageRef(name) + ");");
+ }
+
+ writer.println("addCommand(\"" + Generator.escape(name) + "\", " + name + "_);");
+ writer.println();
+ }
+
+ private void emitCommandAccessors(SourceWriter writer)
+ {
+ for (JMethod method : commandMethods_)
+ {
+ String name = method.getName();
+ writer.println("public AppCommand " + name + "() {");
+ writer.indent();
+ writer.println("return " + name + "_;");
+ writer.outdent();
+ writer.println("}");
+ }
+ }
+
+ private void setProperty(SourceWriter writer,
+ String name,
+ Element props,
+ String propertyName)
+ {
+ if (props == null)
+ return;
+ // This check is important because getAttribute() returns empty string
+ // even if the attribute isn't present, which is not what we want. In
+ // the command system, empty string is distinct from null.
+ if (!props.hasAttribute(propertyName))
+ return;
+
+ String value = props.getAttribute(propertyName);
+
+ String setter = "set" + Character.toUpperCase(propertyName.charAt(0))
+ + propertyName.substring(1);
+ writer.println(name + "_." + setter
+ + "(\"" + Generator.escape(value) + "\");");
+ }
+
+ private void setPropertyBool(SourceWriter writer,
+ String name,
+ Element props,
+ String propertyName)
+ {
+ if (props == null)
+ return;
+ // This check is important because getAttribute() returns empty string
+ // even if the attribute isn't present, which is not what we want. In
+ // the command system, empty string is distinct from null.
+ if (!props.hasAttribute(propertyName))
+ return;
+
+ String value = props.getAttribute(propertyName);
+
+ String setter = "set" + Character.toUpperCase(propertyName.charAt(0))
+ + propertyName.substring(1);
+ writer.println(name + "_." + setter
+ + "(" + value + ");");
+ }
+
+ private ImageResourceInfo generateImageBundle()
+ {
+ String className = bundleType_.getSimpleSourceName() + "__AutoGenResources";
+ String pathToInstance = packageName_ + "." + className + ".INSTANCE";
+ ImageResourceInfo iri = new ImageResourceInfo(pathToInstance);
+
+ PrintWriter printWriter = context_.tryCreate(logger_,
+ packageName_,
+ className);
+ if (printWriter == null)
+ return null;
+
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, className);
+ factory.addImport("com.google.gwt.core.client.GWT");
+ factory.addImport("com.google.gwt.resources.client.*");
+ factory.makeInterface();
+ factory.addImplementedInterface("ClientBundle");
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ for (JMethod method : commandMethods_)
+ {
+ String commandId = method.getName();
+
+ String key = packageName_.replace('.', '/') + "/" + commandId + ".png";
+ if (resourceMap_.containsKey(key))
+ {
+ writer.println("ImageResource " + commandId + "();");
+ iri.addImage(commandId);
+ }
+ }
+ writer.println("public static final " + className + " INSTANCE = " +
+ "(" + className + ")GWT.create(" + className + ".class);");
+
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+
+ return iri;
+
+ }
+
+ public Map<String, Element> getCommandProperties() throws UnableToCompleteException
+ {
+ Map<String, Element> properties = new HashMap<String, Element>();
+
+ NodeList nodes = getConfigDoc("/commands/cmd");
+
+ for (int i = 0; i < nodes.getLength(); i++)
+ {
+ Element cmd = (Element) nodes.item(i);
+ String id = cmd.getAttribute("id");
+
+ properties.put(id, cmd);
+ }
+
+ return properties;
+ }
+
+ private NodeList getConfigDoc(String xpath) throws UnableToCompleteException
+ {
+ try
+ {
+ String resourceName =
+ bundleType_.getQualifiedSourceName().replace('.', '/') + ".cmd.xml";
+ Resource resource = resourceMap_.get(resourceName);
+ if (resource == null)
+ return null;
+
+ Object result = XPathFactory.newInstance().newXPath().evaluate(
+ xpath,
+ new InputSource(resource.getLocation()),
+ XPathConstants.NODESET);
+ return (NodeList) result;
+ }
+ catch (Exception e)
+ {
+ logger_.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final JClassType bundleType_;
+ private final JMethod[] commandMethods_;
+ private final JMethod[] menuMethods_;
+ @SuppressWarnings("unused")
+ private final JMethod[] shortcutsMethods_;
+ private final Map<String, Resource> resourceMap_;
+ private final String packageName_;
+ private String simpleName_;
+}
+
+class ImageResourceInfo
+{
+ public ImageResourceInfo(String imagesRefPath)
+ {
+ this.imagesRefPath_ = imagesRefPath;
+ }
+
+ public void addImage(String commandId)
+ {
+ imageIds_.add(commandId);
+ }
+
+ public boolean hasImage(String commandId)
+ {
+ return imageIds_.contains(commandId);
+ }
+
+ public String getImageRef(String commandId)
+ {
+ assert hasImage(commandId);
+ return imagesRefPath_ + "." + commandId + "()";
+ }
+
+ private final String imagesRefPath_;
+ private final HashSet<String> imageIds_ = new HashSet<String>();
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/command/JsObjectInjectorGenerator.java b/src/gwt/src/org/rstudio/core/rebind/command/JsObjectInjectorGenerator.java
new file mode 100644
index 0000000..cb5940d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/command/JsObjectInjectorGenerator.java
@@ -0,0 +1,164 @@
+/*
+ * JsObjectInjectorGenerator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind.command;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.*;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import org.rstudio.core.client.js.BaseExpression;
+
+import java.io.PrintWriter;
+
+public class JsObjectInjectorGenerator extends Generator
+{
+ private class Helper
+ {
+ public Helper(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws Exception
+ {
+ logger_ = logger;
+ context_ = context;
+ baseType_ = context_.getTypeOracle().getType(typeName);
+ packageName_ = baseType_.getPackage().getName();
+
+ BaseExpression be = baseType_.getAnnotation(BaseExpression.class);
+ if (be == null)
+ {
+ logger_.log(Type.ERROR, "Missing @BaseExpression class annotation");
+ throw new UnableToCompleteException();
+ }
+
+ baseExpression_ = be.value();
+
+ }
+
+ public String generate() throws Exception
+ {
+ String simpleName = baseType_.getName().replace('.', '_') + "__Impl";
+
+ PrintWriter printWriter = context_.tryCreate(
+ logger_, packageName_, simpleName);
+ if (printWriter != null)
+ {
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, simpleName);
+ factory.addImplementedInterface(baseType_.getName());
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitBody(writer);
+
+ // Close the class and commit it
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+ }
+ return packageName_ + "." + simpleName;
+ }
+
+ private void emitBody(SourceWriter w) throws NotFoundException
+ {
+ JClassType baseClass = context_.getTypeOracle().getType(
+ "org.rstudio.core.client.js.JsObjectInjector");
+ JClassType c = baseType_.asParameterizationOf(baseClass.isGenericType());
+ JType typeToInject = c.isParameterized().getTypeArgs()[0];
+
+ w.print("public native final void injectObject(");
+ w.print(typeToInject.getQualifiedSourceName());
+ w.println(" value) /*-{");
+ w.indent();
+
+ w.println(baseExpression_ + " = {");
+ w.indent();
+
+ JMethod[] methods = typeToInject.isClassOrInterface().getMethods();
+ for (int i = 0; i < methods.length; i++)
+ {
+ JMethod method = methods[i];
+ final JParameter[] jParameters = method.getParameters();
+
+ StringBuilder argString = new StringBuilder();
+ for (int j = 0; j < jParameters.length; j++)
+ {
+ argString.append("_").append(j);
+ if (j < jParameters.length - 1)
+ argString.append(", ");
+ }
+
+ w.println(method.getName() + ": function(" + argString + ") {");
+ w.indent();
+
+ if (!method.getReturnType().getQualifiedSourceName().equals("void"))
+ w.print("return ");
+ w.print("value.@");
+ w.print(typeToInject.getQualifiedSourceName());
+ w.print("::");
+ w.print(method.getName());
+ w.print("(");
+ for (JParameter param : jParameters)
+ w.print(param.getType().getJNISignature());
+ w.print(")(");
+ w.print(argString.toString());
+ w.println(");");
+
+ w.outdent();
+ w.print("}");
+ w.println((i < methods.length - 1) ? "," : "");
+ }
+
+ w.outdent();
+ w.println("};");
+
+ w.outdent();
+ w.println("}-*/;");
+
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final String baseExpression_;
+ private final JClassType baseType_;
+ private final String packageName_;
+ }
+
+ @Override
+ public String generate(TreeLogger logger,
+ GeneratorContext context,
+ String typeName) throws UnableToCompleteException
+ {
+ try
+ {
+ return new Helper(logger,
+ context,
+ typeName).generate();
+ }
+ catch (UnableToCompleteException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Barf", e);
+ throw new UnableToCompleteException();
+ }
+
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/command/MenuEmitter.java b/src/gwt/src/org/rstudio/core/rebind/command/MenuEmitter.java
new file mode 100644
index 0000000..cf2dd6d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/command/MenuEmitter.java
@@ -0,0 +1,153 @@
+/*
+ * MenuEmitter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind.command;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.PrintWriter;
+
+public class MenuEmitter
+{
+ public MenuEmitter(TreeLogger logger,
+ GeneratorContext context,
+ JClassType bundleType,
+ Element menuEl) throws UnableToCompleteException
+ {
+ logger_ = logger;
+ context_ = context;
+ bundleType_ = bundleType;
+ menuEl_ = menuEl;
+ menuId_ = menuEl.getAttribute("id");
+ if (menuId_.length() == 0)
+ {
+ logger.log(TreeLogger.Type.ERROR, "Menu must have an id attribute");
+ throw new UnableToCompleteException();
+ }
+ packageName_ = bundleType.getPackage().getName();
+ }
+
+ public String generate() throws UnableToCompleteException
+ {
+ String className = bundleType_.getSimpleSourceName() + "__Menu_" + menuId_;
+
+ PrintWriter printWriter = context_.tryCreate(logger_,
+ packageName_,
+ className);
+ if (printWriter == null)
+ return null;
+
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName_, className);
+ factory.addImport("org.rstudio.core.client.Debug");
+ factory.addImport("org.rstudio.core.client.command.MenuCallback");
+ SourceWriter writer = factory.createSourceWriter(context_, printWriter);
+
+ emitFields(writer);
+ emitConstructor(writer, className);
+ emitMethod(writer);
+ writer.outdent();
+ writer.println("}");
+ context_.commit(logger_, printWriter);
+
+ return packageName_ + "." + className;
+ }
+
+ private void emitFields(SourceWriter writer)
+ {
+ writer.println("private "
+ + bundleType_.getQualifiedSourceName()
+ + " cmds;");
+ }
+
+ private void emitConstructor(SourceWriter writer, String className)
+ {
+ writer.println("public " + className + "("
+ + bundleType_.getQualifiedSourceName() + " commands) {");
+ writer.indentln("this.cmds = commands;");
+ writer.println("}");
+ }
+
+ private void emitMethod(SourceWriter writer) throws UnableToCompleteException
+ {
+ writer.println("public void createMenu(MenuCallback callback) {");
+ writer.indent();
+
+ writer.println("callback.beginMainMenu();");
+ // Vertical defaults to true
+ emitMenu(writer, menuEl_);
+ writer.println("callback.endMainMenu();");
+
+ writer.outdent();
+ writer.println("}");
+ }
+
+ private void emitMenu(SourceWriter writer, Element el) throws UnableToCompleteException
+ {
+ for (Node n = el.getFirstChild(); n != null; n = n.getNextSibling())
+ {
+ if (n.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ Element child = (Element)n;
+
+ if (child.getTagName().equals("cmd"))
+ {
+ String cmdId = child.getAttribute("refid");
+ writer.print("callback.addCommand(");
+ writer.print("\"" + Generator.escape(cmdId) + "\", ");
+ writer.println("this.cmds." + cmdId + "());");
+ }
+ else if (child.getTagName().equals("separator"))
+ {
+ writer.println("callback.addSeparator();");
+ }
+ else if (child.getTagName().equals("menu"))
+ {
+ String label = child.getAttribute("label");
+ writer.println("callback.beginMenu(\"" +
+ Generator.escape(label) +
+ "\");");
+ emitMenu(writer, child);
+ writer.println("callback.endMenu();");
+ }
+ else if (child.getTagName().equals("dynamic"))
+ {
+ String dynamicClass = child.getAttribute("class");
+ writer.println("new " + dynamicClass + "().execute(callback);");
+ }
+ else
+ {
+ logger_.log(TreeLogger.Type.ERROR,
+ "Unexpected tag " + el.getTagName() + " in menu");
+ throw new UnableToCompleteException();
+ }
+ }
+ }
+
+ private final TreeLogger logger_;
+ private final GeneratorContext context_;
+ private final JClassType bundleType_;
+ private final String menuId_;
+ private final Element menuEl_;
+ private final String packageName_;
+}
diff --git a/src/gwt/src/org/rstudio/core/rebind/command/ShortcutsEmitter.java b/src/gwt/src/org/rstudio/core/rebind/command/ShortcutsEmitter.java
new file mode 100644
index 0000000..f97e904
--- /dev/null
+++ b/src/gwt/src/org/rstudio/core/rebind/command/ShortcutsEmitter.java
@@ -0,0 +1,236 @@
+/*
+ * ShortcutsEmitter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.core.rebind.command;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.user.rebind.SourceWriter;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import java.io.StringWriter;
+
+public class ShortcutsEmitter
+{
+ public ShortcutsEmitter(TreeLogger logger,
+ String groupName,
+ Element shortcutsEl) throws UnableToCompleteException
+ {
+ logger_ = logger;
+ shortcutsEl_ = shortcutsEl;
+ groupName_ = groupName;
+ }
+
+ public void generate(SourceWriter writer) throws UnableToCompleteException
+ {
+ NodeList children = shortcutsEl_.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++)
+ {
+ Node childNode = children.item(i);
+ if (childNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+ Element childEl = (Element)childNode;
+ if (!childEl.getTagName().equals("shortcut"))
+ {
+ logger_.log(Type.ERROR, "Unexpected element: " + elementToString(childEl));
+ throw new UnableToCompleteException();
+ }
+
+ String condition = childEl.getAttribute("if");
+ String command = childEl.getAttribute("refid");
+ String shortcutValue = childEl.getAttribute("value");
+ String title = childEl.getAttribute("title");
+
+ // Use null when we don't have a command associated with the shortcut,
+ // otherwise refer to the function that returns the command
+ command += command.isEmpty() ? "null" : "()";
+
+ if (shortcutValue.length() == 0)
+ {
+ logger_.log(Type.ERROR, "Required attribute shortcut was missing\n" + elementToString(childEl));
+ throw new UnableToCompleteException();
+ }
+
+ printShortcut(writer, condition, shortcutValue,
+ command, groupName_, title);
+ }
+ }
+
+ private void printShortcut(SourceWriter writer,
+ String condition,
+ String shortcutValue,
+ String command,
+ String shortcutGroup,
+ String title) throws UnableToCompleteException
+ {
+ String[] chunks = shortcutValue.split("\\+");
+ int modifiers = KeyboardShortcut.NONE;
+ boolean cmd = false;
+ for (int i = 0; i < chunks.length - 1; i++)
+ {
+ String m = chunks[i];
+ if (m.equals("Ctrl"))
+ modifiers += KeyboardShortcut.CTRL;
+ else if (m.equals("Meta"))
+ modifiers += KeyboardShortcut.META;
+ else if (m.equals("Alt"))
+ modifiers += KeyboardShortcut.ALT;
+ else if (m.equals("Shift"))
+ modifiers += KeyboardShortcut.SHIFT;
+ else if (m.equals("Cmd"))
+ cmd = true;
+ else
+ {
+ logger_.log(Type.ERROR, "Invalid shortcut " + shortcutValue);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ String key = toKey(chunks[chunks.length - 1]);
+ if (key == null)
+ {
+ logger_.log(Type.ERROR, "Invalid shortcut " + shortcutValue + ", only " +
+ "modified alphanumeric characters, enter, " +
+ "left, right, up, down, pageup, pagedown, " +
+ "and tab are valid");
+ throw new UnableToCompleteException();
+ }
+
+ if (!condition.isEmpty())
+ {
+ writer.println("if (" + condition + ") {");
+ writer.indent();
+ }
+
+ if (cmd)
+ {
+ writer.println("ShortcutManager.INSTANCE.register(" +
+ (modifiers| KeyboardShortcut.CTRL) + ", " +
+ key + ", " +
+ command + ", " +
+ "\"" + shortcutGroup + "\", " +
+ "\"" + title + "\");");
+ writer.println("ShortcutManager.INSTANCE.register(" +
+ (modifiers| KeyboardShortcut.META) + ", " +
+ key + ", " +
+ command + ", " +
+ "\"" + shortcutGroup + "\", " +
+ "\"" + title + "\");");
+ }
+ else
+ {
+ writer.println("ShortcutManager.INSTANCE.register(" +
+ modifiers + ", " +
+ key + ", " +
+ command + ", " +
+ "\"" + shortcutGroup + "\", " +
+ "\"" + title + "\");");
+ }
+
+ if (!condition.isEmpty())
+ {
+ writer.outdent();
+ writer.println("}");
+ }
+ }
+
+ private String toKey(String val)
+ {
+ if (val.matches("^[a-zA-Z0-9]$"))
+ return "'" + val.toUpperCase() + "'";
+ if (val.equals("/"))
+ return "191";
+ if (val.equalsIgnoreCase("enter"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER";
+ if (val.equalsIgnoreCase("right"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_RIGHT";
+ if (val.equalsIgnoreCase("left"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_LEFT";
+ if (val.equalsIgnoreCase("up"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_UP";
+ if (val.equalsIgnoreCase("down"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_DOWN";
+ if (val.equalsIgnoreCase("tab"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_TAB";
+ if (val.equalsIgnoreCase("pageup"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_PAGEUP";
+ if (val.equalsIgnoreCase("pagedown"))
+ return "com.google.gwt.event.dom.client.KeyCodes.KEY_PAGEDOWN";
+ if (val.equalsIgnoreCase("F1"))
+ return "112";
+ if (val.equalsIgnoreCase("F2"))
+ return "113";
+ if (val.equalsIgnoreCase("F3"))
+ return "114";
+ if (val.equalsIgnoreCase("F4"))
+ return "115";
+ if (val.equalsIgnoreCase("F5"))
+ return "116";
+ if (val.equalsIgnoreCase("F6"))
+ return "117";
+ if (val.equalsIgnoreCase("F7"))
+ return "118";
+ if (val.equalsIgnoreCase("F8"))
+ return "119";
+ if (val.equalsIgnoreCase("F9"))
+ return "120";
+ if (val.equalsIgnoreCase("F10"))
+ return "121";
+ if (val.equalsIgnoreCase("F11"))
+ return "122";
+ if (val.equalsIgnoreCase("F12"))
+ return "123";
+ if (val.equals("`"))
+ return "192";
+ if (val.equals("."))
+ return "190";
+ if (val.equals("="))
+ return "187";
+ if (val.equals("<"))
+ return "188";
+ if (val.equals("Backspace"))
+ return "8";
+ return null;
+ }
+
+ private String elementToString(Element el) throws UnableToCompleteException
+ {
+ try
+ {
+ javax.xml.transform.TransformerFactory tfactory = TransformerFactory.newInstance();
+ javax.xml.transform.Transformer xform = tfactory.newTransformer();
+ javax.xml.transform.Source src = new DOMSource(el);
+ java.io.StringWriter writer = new StringWriter();
+ Result result = new javax.xml.transform.stream.StreamResult(writer);
+ xform.transform(src, result);
+ return writer.toString();
+ }
+ catch (Exception e)
+ {
+ logger_.log(Type.ERROR, "Error attempting to stringify some XML", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ private final TreeLogger logger_;
+ private final Element shortcutsEl_;
+ private final String groupName_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/.gitignore b/src/gwt/src/org/rstudio/studio/.gitignore
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/.gitignore
@@ -0,0 +1,2 @@
+
+
diff --git a/src/gwt/src/org/rstudio/studio/RStudio.gwt.xml b/src/gwt/src/org/rstudio/studio/RStudio.gwt.xml
new file mode 100644
index 0000000..1afc0d5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/RStudio.gwt.xml
@@ -0,0 +1,129 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd">
+<module rename-to='rstudio'>
+
+ <!-- GWT module dependencies -->
+ <inherits name='com.google.gwt.user.User'/>
+
+ <inherits name='com.google.gwt.widgetideas.SliderBar'/>
+
+ <!-- Gin module dependencies -->
+ <inherits name="com.google.gwt.inject.Inject"/>
+
+ <!-- Use default GWT style sheet -->
+ <!--<inherits name='com.google.gwt.user.theme.standard.Standard'/>-->
+
+ <define-property name="rstudio.desktop" values="true,false"/>
+ <property-provider name="rstudio.desktop"><![CDATA[
+ return window.desktop ? "true" : "false";
+ ]]></property-provider>
+ <set-property name="user.agent" value="safari">
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </set-property>
+ <collapse-property name="rstudio.desktop" values="*"/>
+
+ <define-property name="rstudio.cocoa" values="true,false"/>
+ <property-provider name="rstudio.cocoa"><![CDATA[
+ return window.desktop && window.desktop.isCocoa() ? "true" : "false";
+ ]]></property-provider>
+ <collapse-property name="rstudio.cocoa" values="*"/>
+
+ <!-- RStudio module dependencies -->
+ <inherits name='org.rstudio.core.Core' />
+
+ <!-- Specify the app entry point class. -->
+ <entry-point class='org.rstudio.studio.client.RStudio'/>
+
+ <set-property name="user.agent" value="safari,gecko1_8" />
+ <collapse-property name="user.agent" values="*" />
+
+ <replace-with class="org.rstudio.studio.client.impl.BrowserFenceUnsupported">
+ <when-type-is class="org.rstudio.studio.client.impl.BrowserFence" />
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.impl.BrowserFenceSupported">
+ <when-type-is class="org.rstudio.studio.client.impl.BrowserFence" />
+ <any>
+ <when-property-is name="user.agent" value="safari" />
+ <when-property-is name="user.agent" value="gecko1_8" />
+ </any>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.common.impl.WebWindowOpener">
+ <when-type-is class="org.rstudio.studio.client.common.WindowOpener" />
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.common.impl.DesktopWindowOpener">
+ <when-type-is class="org.rstudio.studio.client.common.WindowOpener" />
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.common.impl.WebFileDialogs">
+ <when-type-is class="org.rstudio.studio.client.common.FileDialogs" />
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.common.impl.DesktopFileDialogs">
+ <when-type-is class="org.rstudio.studio.client.common.FileDialogs" />
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.application.ui.impl.WebApplicationHeader">
+ <when-type-is class="org.rstudio.studio.client.application.ui.ApplicationHeader" />
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.application.ui.impl.DesktopApplicationHeader">
+ <when-type-is class="org.rstudio.studio.client.application.ui.ApplicationHeader" />
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.workbench.views.packages.impl.WebCRANChooser">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.packages.CRANChooser"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.workbench.views.packages.impl.DesktopCRANChooser">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.packages.CRANChooser"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.workbench.views.packages.impl.WebCRANChooser">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.packages.CRANChooser"/>
+ <when-property-is name="rstudio.cocoa" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.workbench.views.plots.ui.impl.WebActionsWidget">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.plots.ui.ActionsWidget"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.workbench.views.plots.ui.impl.DesktopActionsWidget">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.plots.ui.ActionsWidget"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.workbench.views.plots.ui.export.impl.ExportPlotWeb">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.workbench.views.plots.ui.export.impl.ExportPlotDesktop">
+ <when-type-is class="org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.common.dialog.WebDialogBuilderFactory">
+ <when-type-is class="org.rstudio.studio.client.common.dialog.DialogBuilderFactory"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.common.dialog.DesktopDialogBuilderFactory">
+ <when-type-is class="org.rstudio.studio.client.common.dialog.DialogBuilderFactory"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+
+ <replace-with class="org.rstudio.studio.client.common.impl.WebTextInput">
+ <when-type-is class="org.rstudio.studio.client.common.TextInput"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.common.impl.DesktopTextInput">
+ <when-type-is class="org.rstudio.studio.client.common.TextInput"/>
+ <when-property-is name="rstudio.desktop" value="true"/>
+ </replace-with>
+ <replace-with class="org.rstudio.studio.client.common.impl.WebTextInput">
+ <when-type-is class="org.rstudio.studio.client.common.TextInput"/>
+ <when-property-is name="rstudio.cocoa" value="true"/>
+ </replace-with>
+
+ <!-- define permutations for emulated and native stack modes -->
+ <set-property name="compiler.stackMode" value="emulated,native"/>
+ <set-configuration-property name="compiler.emulatedStack.recordLineNumbers" value="true" />
+
+ <!--<set-configuration-property name="CssResource.style" value="pretty" />-->
+ <set-configuration-property name="UiBinder.useSafeHtmlTemplates" value="true" />
+</module>
diff --git a/src/gwt/src/org/rstudio/studio/RStudioDraft.gwt.xml b/src/gwt/src/org/rstudio/studio/RStudioDraft.gwt.xml
new file mode 100644
index 0000000..3d34c0f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/RStudioDraft.gwt.xml
@@ -0,0 +1,4 @@
+<module rename-to="rstudio">
+ <inherits name="org.rstudio.studio.RStudio"/>
+ <set-property name="compiler.stackMode" value="emulated"/>
+</module>
diff --git a/src/gwt/src/org/rstudio/studio/RStudioSuperDevMode.gwt.xml b/src/gwt/src/org/rstudio/studio/RStudioSuperDevMode.gwt.xml
new file mode 100644
index 0000000..ab9244a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/RStudioSuperDevMode.gwt.xml
@@ -0,0 +1,7 @@
+<module rename-to="rstudio">
+ <inherits name="org.rstudio.studio.RStudio" />
+ <set-property name="compiler.useSourceMaps" value="true"/>
+ <set-property name="compiler.stackMode" value="native"/>
+ <add-linker name="xsiframe"/>
+ <set-configuration-property name="devModeRedirectEnabled" value="true"/>
+</module>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/RStudio.java b/src/gwt/src/org/rstudio/studio/client/RStudio.java
new file mode 100644
index 0000000..ccbee38
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/RStudio.java
@@ -0,0 +1,249 @@
+/*
+ * RStudio.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.cellview.LinkColumn;
+import org.rstudio.core.client.files.filedialog.FileDialogResources;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.resources.CoreResources;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.CaptionWithHelp;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.ProgressDialog;
+import org.rstudio.core.client.widget.ResizeGripper;
+import org.rstudio.core.client.widget.SlideLabel;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.core.client.widget.ThemedPopupPanel;
+import org.rstudio.core.client.widget.WizardResources;
+import org.rstudio.core.client.widget.images.ProgressImages;
+import org.rstudio.studio.client.application.ui.AboutDialogContents;
+import org.rstudio.studio.client.application.ui.appended.ApplicationEndedPopupPanel;
+import org.rstudio.studio.client.application.ui.serializationprogress.ApplicationSerializationProgress;
+import org.rstudio.studio.client.application.ui.support.SupportPopupMenu;
+import org.rstudio.studio.client.common.StudioResources;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorListResources;
+import org.rstudio.studio.client.common.mirrors.ChooseMirrorDialog;
+import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;
+import org.rstudio.studio.client.common.spelling.ui.SpellingCustomDictionariesWidget;
+import org.rstudio.studio.client.common.vcs.CreateKeyDialog;
+import org.rstudio.studio.client.common.vcs.ShowPublicKeyDialog;
+import org.rstudio.studio.client.common.vcs.SshKeyWidget;
+import org.rstudio.studio.client.common.vcs.ignore.IgnoreDialog;
+import org.rstudio.studio.client.htmlpreview.HTMLPreviewApplication;
+import org.rstudio.studio.client.impl.BrowserFence;
+import org.rstudio.studio.client.pdfviewer.PDFViewerApplication;
+import org.rstudio.studio.client.projects.ui.newproject.NewProjectResources;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesDialogResources;
+import org.rstudio.studio.client.shiny.ShinyApplicationSatellite;
+import org.rstudio.studio.client.workbench.codesearch.ui.CodeSearchResources;
+import org.rstudio.studio.client.workbench.prefs.views.PreferencesDialog;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
+import org.rstudio.studio.client.workbench.views.buildtools.ui.BuildPaneResources;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+import org.rstudio.studio.client.workbench.views.files.ui.FilesListCellTableResources;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryPane;
+import org.rstudio.studio.client.workbench.views.history.view.Shelf;
+import org.rstudio.studio.client.workbench.views.packages.ui.CheckForUpdatesDialog;
+import org.rstudio.studio.client.workbench.views.packages.ui.InstallPackageDialog;
+import org.rstudio.studio.client.workbench.views.packages.ui.PackagesCellTableResources;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotResources;
+import org.rstudio.studio.client.workbench.views.plots.ui.manipulator.ManipulatorResources;
+import org.rstudio.studio.client.workbench.views.source.editors.codebrowser.CodeBrowserEditingTargetWidget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.DiffFrame;
+import org.rstudio.studio.client.workbench.views.environment.dataimport.ImportFileSettingsDialog;
+
+public class RStudio implements EntryPoint
+{
+ public void onModuleLoad()
+ {
+ Debug.injectDebug();
+
+ Document.get().getBody().getStyle().setBackgroundColor("#e1e2e5");
+
+ BrowserFence fence = GWT.create(BrowserFence.class);
+ fence.go(new Command()
+ {
+ public void execute()
+ {
+ Command dismissProgressAnimation = showProgress();
+ delayLoadApplication(dismissProgressAnimation);
+ }
+ });
+ }
+
+ private Command showProgress()
+ {
+ String progressUrl = ProgressImages.createLargeGray().getUrl();
+ final DivElement div = Document.get().createDivElement();
+ StringBuilder str = new StringBuilder();
+ str.append("<img src=\"");
+ str.append(progressUrl);
+ str.append("\"");
+ if (BrowseCap.isRetina())
+ str.append("width=24 height=24");
+ str.append("/>");
+ div.setInnerHTML(str.toString());
+ div.getStyle().setWidth(100, Style.Unit.PCT);
+ div.getStyle().setMarginTop(200, Style.Unit.PX);
+ div.getStyle().setProperty("textAlign", "center");
+ div.getStyle().setZIndex(1000);
+ ElementIds.assignElementId(div, ElementIds.LOADING_SPINNER);
+ Document.get().getBody().appendChild(div);
+
+ return new Command()
+ {
+ public void execute()
+ {
+ try
+ {
+ Document.get().getBody().removeChild(div);
+ }
+ catch (Exception e)
+ {
+ Debug.log(e.toString());
+ }
+ }
+ };
+ }
+
+ private void delayLoadApplication(final Command dismissProgressAnimation)
+ {
+ GWT.runAsync(new RunAsyncCallback()
+ {
+ public void onFailure(Throwable reason)
+ {
+ dismissProgressAnimation.execute();
+ Window.alert("Error: " + reason.getMessage());
+ }
+
+ public void onSuccess()
+ {
+ AceEditor.load(new Command()
+ {
+ public void execute()
+ {
+ ensureStylesInjected();
+
+ String view = Window.Location.getParameter("view");
+ if ("review_changes".equals(view))
+ {
+ RStudioGinjector.INSTANCE.getVCSApplication().go(
+ RootLayoutPanel.get(),
+ dismissProgressAnimation);
+ }
+ else if (PDFViewerApplication.NAME.equals(view))
+ {
+ RStudioGinjector.INSTANCE.getPDFViewerApplication().go(
+ RootLayoutPanel.get(),
+ dismissProgressAnimation);
+ }
+ else if (HTMLPreviewApplication.NAME.equals(view))
+ {
+ RStudioGinjector.INSTANCE.getHTMLPreviewApplication().go(
+ RootLayoutPanel.get(),
+ dismissProgressAnimation);
+ }
+ else if (ShinyApplicationSatellite.NAME.equals(view))
+ {
+ RStudioGinjector.INSTANCE.getShinyApplicationSatellite().go(
+ RootLayoutPanel.get(),
+ dismissProgressAnimation);
+ }
+ else
+ {
+ RStudioGinjector.INSTANCE.getApplication().go(
+ RootLayoutPanel.get(),
+ dismissProgressAnimation);
+ }
+ }
+ });
+ }
+ });
+ }
+
+ private void ensureStylesInjected()
+ {
+ ThemeResources.INSTANCE.themeStyles().ensureInjected();
+ CoreResources.INSTANCE.styles().ensureInjected();
+ StudioResources.INSTANCE.styles().ensureInjected();
+ ConsoleResources.INSTANCE.consoleStyles().ensureInjected();
+ FileDialogResources.INSTANCE.styles().ensureInjected();
+ ManipulatorResources.INSTANCE.manipulatorStyles().ensureInjected();
+ PackagesCellTableResources.INSTANCE.cellTableStyle().ensureInjected();
+ FilesListCellTableResources.INSTANCE.cellTableStyle().ensureInjected();
+ ExportPlotResources.INSTANCE.styles().ensureInjected();
+ CodeSearchResources.INSTANCE.styles().ensureInjected();
+ CompileErrorListResources.INSTANCE.styles().ensureInjected();
+ BuildPaneResources.INSTANCE.styles().ensureInjected();
+
+ ProgressDialog.ensureStylesInjected();
+ SupportPopupMenu.ensureStylesInjected();
+ SlideLabel.ensureStylesInjected();
+ ThemedButton.ensureStylesInjected();
+ ThemedPopupPanel.ensureStylesInjected();
+ InstallPackageDialog.ensureStylesInjected();
+ ApplicationEndedPopupPanel.ensureStylesInjected();
+ ApplicationSerializationProgress.ensureStylesInjected();
+ HistoryPane.ensureStylesInjected();
+ Shelf.ensureStylesInjected();
+ ImportFileSettingsDialog.ensureStylesInjected();
+ FindReplaceBar.ensureStylesInjected();
+ FontSizer.ensureStylesInjected();
+ PreferencesDialogBaseResources.INSTANCE.styles().ensureInjected();
+ PreferencesDialog.ensureStylesInjected();
+ ProjectPreferencesDialogResources.INSTANCE.styles().ensureInjected();
+ LinkColumn.ensureStylesInjected();
+ CaptionWithHelp.ensureStylesInjected();
+ CheckForUpdatesDialog.ensureStylesInjected();
+ UnsavedChangesDialog.ensureStylesInjected();
+ ChooseMirrorDialog.ensureStylesInjected();
+ ResizeGripper.ensureStylesInjected();
+ LineTableView.ensureStylesInjected();
+ ChangelistTable.ensureStylesInjected();
+ DiffFrame.ensureStylesInjected();
+ CodeBrowserEditingTargetWidget.ensureStylesInjected();
+ ShowPublicKeyDialog.ensureStylesInjected();
+ CreateKeyDialog.ensureStylesInjected();
+ SshKeyWidget.ensureStylesInjected();
+ IgnoreDialog.ensureStylesInjected();
+ SpellingCustomDictionariesWidget.ensureStylesInjected();
+ RPubsUploadDialog.ensureStylesInjected();
+ WizardResources.INSTANCE.styles().ensureInjected();
+ NewProjectResources.INSTANCE.styles().ensureInjected();
+ AboutDialogContents.ensureStylesInjected();
+
+ StyleInjector.inject(
+ "button::-moz-focus-inner {border:0}");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/RStudioGinModule.java b/src/gwt/src/org/rstudio/studio/client/RStudioGinModule.java
new file mode 100644
index 0000000..b1e3ea5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/RStudioGinModule.java
@@ -0,0 +1,338 @@
+/*
+ * RStudioGinModule.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client;
+
+import com.google.gwt.inject.client.AbstractGinModule;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Singleton;
+import com.google.inject.name.Names;
+
+import org.rstudio.core.client.command.ShortcutViewer;
+import org.rstudio.studio.client.application.ApplicationInterrupt;
+import org.rstudio.studio.client.application.ApplicationQuit;
+import org.rstudio.studio.client.application.ApplicationView;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.application.ui.ApplicationWindow;
+import org.rstudio.studio.client.common.ConsoleDispatcher;
+import org.rstudio.studio.client.common.DefaultGlobalDisplay;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfServerOperations;
+import org.rstudio.studio.client.common.console.ConsoleProcess.ConsoleProcessFactory;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.common.debugging.BreakpointManager;
+import org.rstudio.studio.client.common.debugging.DebugCommander;
+import org.rstudio.studio.client.common.debugging.DebuggingServerOperations;
+import org.rstudio.studio.client.common.filetypes.FileTypeCommands;
+import org.rstudio.studio.client.common.latex.LatexProgramRegistry;
+import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror;
+import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations;
+import org.rstudio.studio.client.common.rnw.RnwWeaveRegistry;
+import org.rstudio.studio.client.common.rpubs.model.RPubsServerOperations;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.common.shiny.model.ShinyServerOperations;
+import org.rstudio.studio.client.common.spelling.model.SpellingServerOperations;
+import org.rstudio.studio.client.common.synctex.Synctex;
+import org.rstudio.studio.client.common.synctex.model.SynctexServerOperations;
+import org.rstudio.studio.client.common.vcs.AskPassManager;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.VCSServerOperations;
+import org.rstudio.studio.client.common.vcs.ignore.Ignore;
+import org.rstudio.studio.client.common.vcs.ignore.IgnoreDialog;
+import org.rstudio.studio.client.htmlpreview.HTMLPreview;
+import org.rstudio.studio.client.htmlpreview.HTMLPreviewPresenter;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewServerOperations;
+import org.rstudio.studio.client.htmlpreview.ui.HTMLPreviewApplicationView;
+import org.rstudio.studio.client.htmlpreview.ui.HTMLPreviewApplicationWindow;
+import org.rstudio.studio.client.htmlpreview.ui.HTMLPreviewPanel;
+import org.rstudio.studio.client.pdfviewer.PDFViewer;
+import org.rstudio.studio.client.pdfviewer.PDFViewerPresenter;
+import org.rstudio.studio.client.pdfviewer.ui.PDFViewerApplicationView;
+import org.rstudio.studio.client.pdfviewer.ui.PDFViewerApplicationWindow;
+import org.rstudio.studio.client.pdfviewer.ui.PDFViewerPanel;
+import org.rstudio.studio.client.projects.Projects;
+import org.rstudio.studio.client.projects.model.ProjectsServerOperations;
+import org.rstudio.studio.client.server.Server;
+import org.rstudio.studio.client.server.remote.RemoteServer;
+import org.rstudio.studio.client.shiny.ShinyApplication;
+import org.rstudio.studio.client.shiny.ShinyApplicationPresenter;
+import org.rstudio.studio.client.shiny.ShinyApps;
+import org.rstudio.studio.client.shiny.ui.ShinyApplicationPanel;
+import org.rstudio.studio.client.shiny.ui.ShinyApplicationView;
+import org.rstudio.studio.client.shiny.ui.ShinyApplicationWindow;
+import org.rstudio.studio.client.vcs.VCSApplicationView;
+import org.rstudio.studio.client.vcs.ui.VCSApplicationWindow;
+import org.rstudio.studio.client.workbench.ClientStateUpdater;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchListManager;
+import org.rstudio.studio.client.workbench.WorkbenchMainView;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchServerOperations;
+import org.rstudio.studio.client.workbench.codesearch.ui.CodeSearchWidget;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.MetaServerOperations;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.WorkbenchListsServerOperations;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+import org.rstudio.studio.client.workbench.prefs.model.PrefsServerOperations;
+import org.rstudio.studio.client.workbench.ui.WorkbenchScreen;
+import org.rstudio.studio.client.workbench.ui.WorkbenchTab;
+import org.rstudio.studio.client.workbench.views.buildtools.BuildPresenter;
+import org.rstudio.studio.client.workbench.views.buildtools.BuildPane;
+import org.rstudio.studio.client.workbench.views.buildtools.BuildTab;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildServerOperations;
+import org.rstudio.studio.client.workbench.views.choosefile.ChooseFile;
+import org.rstudio.studio.client.workbench.views.choosefile.model.ChooseFileServerOperations;
+import org.rstudio.studio.client.workbench.views.console.ConsolePane;
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleServerOperations;
+import org.rstudio.studio.client.workbench.views.console.shell.Shell;
+import org.rstudio.studio.client.workbench.views.console.shell.ShellPane;
+import org.rstudio.studio.client.workbench.views.data.Data;
+import org.rstudio.studio.client.workbench.views.data.DataPane;
+import org.rstudio.studio.client.workbench.views.data.DataTab;
+import org.rstudio.studio.client.workbench.views.data.model.DataServerOperations;
+import org.rstudio.studio.client.workbench.views.edit.Edit;
+import org.rstudio.studio.client.workbench.views.edit.model.EditServerOperations;
+import org.rstudio.studio.client.workbench.views.edit.ui.EditView;
+import org.rstudio.studio.client.workbench.views.environment.EnvironmentPane;
+import org.rstudio.studio.client.workbench.views.environment.EnvironmentPresenter;
+import org.rstudio.studio.client.workbench.views.environment.EnvironmentTab;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentServerOperations;
+import org.rstudio.studio.client.workbench.views.files.Files;
+import org.rstudio.studio.client.workbench.views.files.FilesPane;
+import org.rstudio.studio.client.workbench.views.files.FilesTab;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputPane;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputPresenter;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputTab;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesServerOperations;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.SourceCppOutputPane;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.SourceCppOutputPresenter;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.SourceCppOutputTab;
+import org.rstudio.studio.client.workbench.views.help.Help;
+import org.rstudio.studio.client.workbench.views.help.HelpPane;
+import org.rstudio.studio.client.workbench.views.help.HelpTab;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+import org.rstudio.studio.client.workbench.views.help.search.HelpSearch;
+import org.rstudio.studio.client.workbench.views.help.search.HelpSearchWidget;
+import org.rstudio.studio.client.workbench.views.history.History;
+import org.rstudio.studio.client.workbench.views.history.HistoryTab;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryServerOperations;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryPane;
+import org.rstudio.studio.client.workbench.views.output.compilepdf.CompilePdfOutputPane;
+import org.rstudio.studio.client.workbench.views.output.compilepdf.CompilePdfOutputPresenter;
+import org.rstudio.studio.client.workbench.views.output.compilepdf.CompilePdfOutputTab;
+import org.rstudio.studio.client.workbench.views.packages.Packages;
+import org.rstudio.studio.client.workbench.views.packages.PackagesPane;
+import org.rstudio.studio.client.workbench.views.packages.PackagesTab;
+import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.Plots;
+import org.rstudio.studio.client.workbench.views.plots.PlotsPane;
+import org.rstudio.studio.client.workbench.views.plots.PlotsTab;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.presentation.Presentation;
+import org.rstudio.studio.client.workbench.views.presentation.PresentationPane;
+import org.rstudio.studio.client.workbench.views.presentation.PresentationTab;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
+import org.rstudio.studio.client.workbench.views.source.Source;
+import org.rstudio.studio.client.workbench.views.source.SourcePane;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetSource;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerPresenter;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerServerOperations;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+import org.rstudio.studio.client.workbench.views.source.model.TexServerOperations;
+import org.rstudio.studio.client.workbench.views.vcs.VCSTab;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTablePresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPanel;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenterImpl;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitPane;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitReviewPanel;
+import org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNPane;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPanel;
+import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPresenter;
+import org.rstudio.studio.client.workbench.views.viewer.ViewerPane;
+import org.rstudio.studio.client.workbench.views.viewer.ViewerPresenter;
+import org.rstudio.studio.client.workbench.views.viewer.ViewerTab;
+import org.rstudio.studio.client.workbench.views.viewer.model.ViewerServerOperations;
+
+public class RStudioGinModule extends AbstractGinModule
+{
+ @Override
+ protected void configure()
+ {
+ bind(EventBus.class).in(Singleton.class);
+ bind(Session.class).in(Singleton.class);
+ bind(Projects.class).in(Singleton.class);
+ bind(Satellite.class).in(Singleton.class);
+ bind(SatelliteManager.class).in(Singleton.class);
+ bind(AskPassManager.class).in(Singleton.class);
+ bind(ProfilerPresenter.class).asEagerSingleton();
+ bind(WorkbenchContext.class).asEagerSingleton();
+ bind(WorkbenchListManager.class).asEagerSingleton();
+ bind(ApplicationQuit.class).asEagerSingleton();
+ bind(ApplicationInterrupt.class).asEagerSingleton();
+ bind(ClientStateUpdater.class).asEagerSingleton();
+ bind(ConsoleProcessFactory.class).asEagerSingleton();
+ bind(RnwWeaveRegistry.class).asEagerSingleton();
+ bind(LatexProgramRegistry.class).asEagerSingleton();
+ bind(Commands.class).in(Singleton.class);
+ bind(DefaultCRANMirror.class).in(Singleton.class);
+ bind(ChooseFile.class).in(Singleton.class);
+ bind(ConsoleDispatcher.class).in(Singleton.class);
+ bind(FileTypeCommands.class).in(Singleton.class);
+ bind(Synctex.class).in(Singleton.class);
+ bind(PDFViewer.class).in(Singleton.class);
+ bind(HTMLPreview.class).in(Singleton.class);
+ bind(ShinyApplication.class).in(Singleton.class);
+ bind(BreakpointManager.class).asEagerSingleton();
+ bind(DebugCommander.class).asEagerSingleton();
+ bind(ShortcutViewer.class).asEagerSingleton();
+ bind(ShinyApps.class).asEagerSingleton();
+
+ bind(ApplicationView.class).to(ApplicationWindow.class)
+ .in(Singleton.class) ;
+
+ bind(VCSApplicationView.class).to(VCSApplicationWindow.class)
+ .in(Singleton.class);
+ bind(ReviewPresenter.class).to(ReviewPresenterImpl.class);
+
+ bind(PDFViewerApplicationView.class).to(PDFViewerApplicationWindow.class);
+ bind(HTMLPreviewApplicationView.class).to(HTMLPreviewApplicationWindow.class);
+ bind(ShinyApplicationView.class).to(ShinyApplicationWindow.class);
+
+ bind(Server.class).to(RemoteServer.class) ;
+ bind(WorkbenchServerOperations.class).to(RemoteServer.class) ;
+
+ bind(EditingTargetSource.class).to(EditingTargetSource.Impl.class);
+
+ // Bind workbench views
+ bindPane("Console", ConsolePane.class); // eager loaded
+ bind(Source.Display.class).to(SourcePane.class);
+ bind(History.Display.class).to(HistoryPane.class);
+ bind(Data.Display.class).to(DataPane.class);
+ bind(Files.Display.class).to(FilesPane.class);
+ bind(Plots.Display.class).to(PlotsPane.class);
+ bind(Packages.Display.class).to(PackagesPane.class);
+ bind(Help.Display.class).to(HelpPane.class);
+ bind(Edit.Display.class).to(EditView.class);
+ bind(GitPresenter.Display.class).to(GitPane.class);
+ bind(SVNPresenter.Display.class).to(SVNPane.class);
+ bind(BuildPresenter.Display.class).to(BuildPane.class);
+ bind(Presentation.Display.class).to(PresentationPane.class);
+ bind(EnvironmentPresenter.Display.class).to(EnvironmentPane.class);
+ bind(ViewerPresenter.Display.class).to(ViewerPane.class);
+ bind(Ignore.Display.class).to(IgnoreDialog.class);
+ bind(CompilePdfOutputPresenter.Display.class).to(CompilePdfOutputPane.class);
+ bind(FindOutputPresenter.Display.class).to(FindOutputPane.class);
+ bind(SourceCppOutputPresenter.Display.class).to(SourceCppOutputPane.class);
+ bindTab("History", HistoryTab.class);
+ bindTab("Data", DataTab.class);
+ bindTab("Files", FilesTab.class);
+ bindTab("Plots", PlotsTab.class);
+ bindTab("Packages", PackagesTab.class);
+ bindTab("Help", HelpTab.class);
+ bindTab("VCS", VCSTab.class);
+ bindTab("Build", BuildTab.class);
+ bindTab("Presentation", PresentationTab.class);
+ bindTab("Environment", EnvironmentTab.class);
+ bindTab("Viewer", ViewerTab.class);
+ bindTab("Compile PDF", CompilePdfOutputTab.class);
+ bindTab("Find", FindOutputTab.class);
+ bindTab("Source Cpp", SourceCppOutputTab.class);
+
+ bind(Shell.Display.class).to(ShellPane.class) ;
+
+ bind(HelpSearch.Display.class).to(HelpSearchWidget.class) ;
+ bind(CodeSearch.Display.class).to(CodeSearchWidget.class);
+
+ bind(GitReviewPresenter.Display.class).to(GitReviewPanel.class);
+ bind(SVNReviewPresenter.Display.class).to(SVNReviewPanel.class);
+ bind(LineTablePresenter.Display.class).to(LineTableView.class);
+ bind(HistoryPresenter.DisplayBuilder.class).to(
+ HistoryPanel.Builder.class);
+
+ bind(PDFViewerPresenter.Display.class).to(PDFViewerPanel.class);
+ bind(HTMLPreviewPresenter.Display.class).to(HTMLPreviewPanel.class);
+ bind(ShinyApplicationPresenter.Display.class).to(ShinyApplicationPanel.class);
+
+ bind(GlobalDisplay.class)
+ .to(DefaultGlobalDisplay.class)
+ .in(Singleton.class) ;
+
+ bind(ApplicationServerOperations.class).to(RemoteServer.class) ;
+ bind(ChooseFileServerOperations.class).to(RemoteServer.class) ;
+ bind(CodeToolsServerOperations.class).to(RemoteServer.class) ;
+ bind(ConsoleServerOperations.class).to(RemoteServer.class) ;
+ bind(SourceServerOperations.class).to(RemoteServer.class) ;
+ bind(DataServerOperations.class).to(RemoteServer.class);
+ bind(FilesServerOperations.class).to(RemoteServer.class) ;
+ bind(HistoryServerOperations.class).to(RemoteServer.class) ;
+ bind(PlotsServerOperations.class).to(RemoteServer.class) ;
+ bind(PackagesServerOperations.class).to(RemoteServer.class) ;
+ bind(HelpServerOperations.class).to(RemoteServer.class) ;
+ bind(EditServerOperations.class).to(RemoteServer.class) ;
+ bind(MirrorsServerOperations.class).to(RemoteServer.class);
+ bind(VCSServerOperations.class).to(RemoteServer.class);
+ bind(GitServerOperations.class).to(RemoteServer.class);
+ bind(SVNServerOperations.class).to(RemoteServer.class);
+ bind(PrefsServerOperations.class).to(RemoteServer.class);
+ bind(ProjectsServerOperations.class).to(RemoteServer.class);
+ bind(CodeSearchServerOperations.class).to(RemoteServer.class);
+ bind(WorkbenchListsServerOperations.class).to(RemoteServer.class);
+ bind(CryptoServerOperations.class).to(RemoteServer.class);
+ bind(TexServerOperations.class).to(RemoteServer.class);
+ bind(SpellingServerOperations.class).to(RemoteServer.class);
+ bind(CompilePdfServerOperations.class).to(RemoteServer.class);
+ bind(FindInFilesServerOperations.class).to(RemoteServer.class);
+ bind(SynctexServerOperations.class).to(RemoteServer.class);
+ bind(HTMLPreviewServerOperations.class).to(RemoteServer.class);
+ bind(ShinyServerOperations.class).to(RemoteServer.class);
+ bind(ShinyAppsServerOperations.class).to(RemoteServer.class);
+ bind(RPubsServerOperations.class).to(RemoteServer.class);
+ bind(BuildServerOperations.class).to(RemoteServer.class);
+ bind(PresentationServerOperations.class).to(RemoteServer.class);
+ bind(EnvironmentServerOperations.class).to(RemoteServer.class);
+ bind(DebuggingServerOperations.class).to(RemoteServer.class);
+ bind(MetaServerOperations.class).to(RemoteServer.class);
+ bind(ViewerServerOperations.class).to(RemoteServer.class);
+ bind(ProfilerServerOperations.class).to(RemoteServer.class);
+
+ bind(WorkbenchMainView.class).to(WorkbenchScreen.class) ;
+
+ bind(DocDisplay.class).to(AceEditor.class);
+ }
+
+ private <T extends WorkbenchTab> void bindTab(String name, Class<T> clazz)
+ {
+ bind(WorkbenchTab.class).annotatedWith(Names.named(name)).to(clazz);
+ }
+
+ private <T extends Widget> void bindPane(String name, Class<T> clazz)
+ {
+ bind(Widget.class).annotatedWith(Names.named(name)).to(clazz);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/RStudioGinModuleOverlay.java b/src/gwt/src/org/rstudio/studio/client/RStudioGinModuleOverlay.java
new file mode 100644
index 0000000..eb12059
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/RStudioGinModuleOverlay.java
@@ -0,0 +1,26 @@
+/*
+ * RStudioGinModuleOverlay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client;
+
+public class RStudioGinModuleOverlay extends RStudioGinModule
+{
+ @Override
+ protected void configure()
+ {
+ super.configure();
+
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/RStudioGinjector.java b/src/gwt/src/org/rstudio/studio/client/RStudioGinjector.java
new file mode 100644
index 0000000..56ca8b7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/RStudioGinjector.java
@@ -0,0 +1,112 @@
+/*
+ * RStudioGinjector.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.inject.client.GinModules;
+import com.google.gwt.inject.client.Ginjector;
+
+import org.rstudio.core.client.widget.CaptionWithHelp;
+import org.rstudio.studio.client.application.Application;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.ui.ProjectPopupMenu;
+import org.rstudio.studio.client.application.ui.impl.DesktopApplicationHeader;
+import org.rstudio.studio.client.application.ui.impl.WebApplicationHeader;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.compilepdf.dialog.CompilePdfProgressDialog;
+import org.rstudio.studio.client.common.fileexport.FileExport;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.NewFileMenu;
+import org.rstudio.studio.client.common.impl.DesktopFileDialogs;
+import org.rstudio.studio.client.common.latex.LatexProgramRegistry;
+import org.rstudio.studio.client.common.rnw.RnwWeaveRegistry;
+import org.rstudio.studio.client.common.rnw.RnwWeaveSelectWidget;
+import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;
+import org.rstudio.studio.client.common.spelling.SpellChecker;
+import org.rstudio.studio.client.common.spelling.ui.SpellingCustomDictionariesWidget;
+import org.rstudio.studio.client.htmlpreview.HTMLPreviewApplication;
+import org.rstudio.studio.client.notebook.CompileNotebookOptionsDialog;
+import org.rstudio.studio.client.pdfviewer.PDFViewerApplication;
+import org.rstudio.studio.client.projects.ui.newproject.CodeFilesList;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesPane;
+import org.rstudio.studio.client.projects.ui.prefs.buildtools.BuildToolsPackagePanel;
+import org.rstudio.studio.client.shiny.ShinyApplication;
+import org.rstudio.studio.client.shiny.ShinyApplicationSatellite;
+import org.rstudio.studio.client.shiny.ui.ShinyViewerTypePopupMenu;
+import org.rstudio.studio.client.vcs.VCSApplication;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.RCompletionManager;
+import org.rstudio.studio.client.workbench.views.source.DocsMenu;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetCodeExecution;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetCompilePdfHelper;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetPresentationHelper;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetPreviewHtmlHelper;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNCommandHandler;
+import org.rstudio.studio.client.workbench.views.environment.ClearAllDialog;
+
+ at GinModules(RStudioGinModuleOverlay.class)
+public interface RStudioGinjector extends Ginjector
+{
+ void injectMembers(NewFileMenu newFileMenu);
+ void injectMembers(DocsMenu docsMenu);
+ void injectMembers(DesktopApplicationHeader desktopApplicationHeader);
+ void injectMembers(WebApplicationHeader webApplicationHeader);
+ void injectMembers(AceEditor aceEditor);
+ void injectMembers(DesktopFileDialogs desktopFileDialogs);
+ void injectMembers(RCompletionManager rCompletionManager);
+ void injectMembers(SVNCommandHandler svnCommandHandler);
+ void injectMembers(CaptionWithHelp captionWithHelp);
+ void injectMembers(RnwWeaveSelectWidget selectWidget);
+ void injectMembers(CompilePdfProgressDialog compilePdfProgressDialog);
+ void injectMembers(TextEditingTargetCompilePdfHelper compilePdfHelper);
+ void injectMembers(TextEditingTargetPreviewHtmlHelper previewHtmlHelper);
+ void injectMembers(SpellChecker spellChecker);
+ void injectMembers(SpellingCustomDictionariesWidget widget);
+ void injectMembers(FileExport fileExport);
+ void injectMembers(RPubsUploadDialog uploadDialog);
+ void injectMembers(CompileNotebookOptionsDialog notebookOptionsDialog);
+ void injectMembers(ProjectPreferencesPane projectPrefsPane);
+ void injectMembers(BuildToolsPackagePanel buildToolsPackagePanel);
+ void injectMembers(CodeFilesList codeFilesList);
+ void injectMembers(ProjectPopupMenu projectPopupMenu);
+ void injectMembers(ClearAllDialog clearAllDialog);
+ void injectMembers(TextEditingTargetPresentationHelper presHelper);
+ void injectMembers(EditingTargetCodeExecution codeExecution);
+
+ public static final RStudioGinjector INSTANCE = GWT.create(RStudioGinjector.class);
+
+ Application getApplication() ;
+ VCSApplication getVCSApplication();
+ PDFViewerApplication getPDFViewerApplication();
+ HTMLPreviewApplication getHTMLPreviewApplication();
+ ShinyApplicationSatellite getShinyApplicationSatellite();
+ ShinyApplication getShinyApplication();
+ ShinyViewerTypePopupMenu getShinyViewerTypePopupMenu();
+ EventBus getEventBus();
+ GlobalDisplay getGlobalDisplay();
+ RemoteFileSystemContext getRemoteFileSystemContext();
+ FileDialogs getFileDialogs();
+ FileTypeRegistry getFileTypeRegistry();
+ RnwWeaveRegistry getRnwWeaveRegistry();
+ LatexProgramRegistry getLatexProgramRegistry();
+ Commands getCommands();
+ UIPrefs getUIPrefs();
+ Session getSession();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/Application.java b/src/gwt/src/org/rstudio/studio/client/application/Application.java
new file mode 100644
index 0000000..f8205e4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/Application.java
@@ -0,0 +1,723 @@
+/*
+ * Application.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.Barrier;
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Barrier.Token;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.events.BarrierReleasedEvent;
+import org.rstudio.core.client.events.BarrierReleasedHandler;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.*;
+import org.rstudio.studio.client.application.model.ProductInfo;
+import org.rstudio.studio.client.application.model.SessionSerializationAction;
+import org.rstudio.studio.client.application.ui.AboutDialog;
+import org.rstudio.studio.client.application.ui.RequestLogVisualization;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.SuperDevMode;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.projects.Projects;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.ClientStateUpdater;
+import org.rstudio.studio.client.workbench.Workbench;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.model.Agreement;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+ at Singleton
+public class Application implements ApplicationEventHandlers
+{
+ public interface Binder extends CommandBinder<Commands, Application> {}
+
+ @Inject
+ public Application(ApplicationView view,
+ GlobalDisplay globalDisplay,
+ EventBus events,
+ Binder binder,
+ Commands commands,
+ Server server,
+ Session session,
+ Projects projects,
+ SatelliteManager satelliteManager,
+ ApplicationUncaughtExceptionHandler uncaughtExHandler,
+ Provider<UIPrefs> uiPrefs,
+ Provider<Workbench> workbench,
+ Provider<EventBus> eventBusProvider,
+ Provider<ClientStateUpdater> clientStateUpdater,
+ Provider<ApplicationClientInit> pClientInit,
+ Provider<ApplicationQuit> pApplicationQuit,
+ Provider<ApplicationInterrupt> pApplicationInterrupt,
+ Provider<AceThemes> pAceThemes)
+ {
+ // save references
+ view_ = view ;
+ globalDisplay_ = globalDisplay;
+ events_ = events;
+ session_ = session;
+ commands_ = commands;
+ satelliteManager_ = satelliteManager;
+ clientStateUpdater_ = clientStateUpdater;
+ server_ = server;
+ uiPrefs_ = uiPrefs;
+ workbench_ = workbench;
+ eventBusProvider_ = eventBusProvider;
+ pClientInit_ = pClientInit;
+ pApplicationQuit_ = pApplicationQuit;
+ pApplicationInterrupt_ = pApplicationInterrupt;
+ pAceThemes_ = pAceThemes;
+
+ // bind to commands
+ binder.bind(commands_, this);
+
+ // register as main window
+ satelliteManager.initialize();
+
+ // subscribe to events
+ events.addHandler(LogoutRequestedEvent.TYPE, this);
+ events.addHandler(UnauthorizedEvent.TYPE, this);
+ events.addHandler(ReloadEvent.TYPE, this);
+ events.addHandler(QuitEvent.TYPE, this);
+ events.addHandler(SuicideEvent.TYPE, this);
+ events.addHandler(SessionAbendWarningEvent.TYPE, this);
+ events.addHandler(SessionSerializationEvent.TYPE, this);
+ events.addHandler(ServerUnavailableEvent.TYPE, this);
+ events.addHandler(InvalidClientVersionEvent.TYPE, this);
+ events.addHandler(ServerOfflineEvent.TYPE, this);
+
+ // register for uncaught exceptions
+ uncaughtExHandler.register();
+ }
+
+ public void go(final RootLayoutPanel rootPanel,
+ final Command dismissLoadingProgress)
+ {
+ Widget w = view_.getWidget();
+ rootPanel.add(w);
+ rootPanel.setWidgetTopBottom(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
+ rootPanel.setWidgetLeftRight(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
+
+ // attempt init
+ pClientInit_.get().execute(
+ new ServerRequestCallback<SessionInfo>() {
+
+ public void onResponseReceived(final SessionInfo sessionInfo)
+ {
+ // initialize workbench after verifying agreement
+ verifyAgreement(sessionInfo, new Operation() {
+ public void execute()
+ {
+ dismissLoadingProgress.execute();
+
+ session_.setSessionInfo(sessionInfo);
+
+ // hide the workbench if we have a project parameter
+ // (since we are going to redirect anyway)
+ if (haveProjectParameter())
+ hideWorkbench(rootPanel);
+
+ // initialize workbench
+ initializeWorkbench();
+
+ // reload application if we have a project parameter
+ if (haveProjectParameter())
+ reloadApplication(sessionInfo.getSwitchToProject());
+ }
+ });
+ }
+
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ dismissLoadingProgress.execute();
+
+ globalDisplay_.showErrorMessage("RStudio Initialization Error",
+ error.getUserMessage());
+ }
+ }) ;
+ }
+
+ private void reloadApplication(final String switchToProject)
+ {
+ // use a last chance save barrier since we typically call this very
+ // early in the lifetime of the application before client/server
+ // sync has occurred
+ Barrier barrier = new Barrier();
+ barrier.addBarrierReleasedHandler(new BarrierReleasedHandler() {
+ @Override
+ public void onBarrierReleased(BarrierReleasedEvent event)
+ {
+ if (switchToProject.length() > 0)
+ pApplicationQuit_.get().forceSwitchProject(switchToProject);
+ else
+ reloadWindowWithDelay(true);
+ }
+ });
+
+ Token token = barrier.acquire();
+ try
+ {
+ events_.fireEvent(new LastChanceSaveEvent(barrier));
+ }
+ finally
+ {
+ token.release();
+ }
+ }
+
+ @Handler
+ public void onShowToolbar()
+ {
+ setToolbarPref(true);
+ }
+
+ @Handler
+ public void onHideToolbar()
+ {
+ setToolbarPref(false);
+ }
+
+
+ @Handler
+ void onShowAboutDialog()
+ {
+ server_.getProductInfo(new ServerRequestCallback<ProductInfo>()
+ {
+ @Override
+ public void onResponseReceived(ProductInfo info)
+ {
+ AboutDialog about = new AboutDialog(info);
+ about.showModal();
+ }
+ @Override
+ public void onError(ServerError error)
+ {
+ }
+ });
+ }
+
+ @Handler
+ public void onGoToFileFunction()
+ {
+ view_.performGoToFunction();
+ }
+
+
+ public void onUnauthorized(UnauthorizedEvent event)
+ {
+ navigateToSignIn();
+ }
+
+ public void onServerOffline(ServerOfflineEvent event)
+ {
+ cleanupWorkbench();
+ view_.showApplicationOffline();
+ }
+
+ public void onLogoutRequested(LogoutRequestedEvent event)
+ {
+ navigateWindowTo("auth-sign-out");
+ }
+
+ @Handler
+ public void onHelpUsingRStudio()
+ {
+ String customDocsURL = session_.getSessionInfo().docsURL();
+ if (customDocsURL.length() > 0)
+ globalDisplay_.openWindow(customDocsURL);
+ else
+ globalDisplay_.openRStudioLink("docs");
+ }
+
+ private void showAgreement()
+ {
+ globalDisplay_.openWindow(server_.getApplicationURL("agreement"));
+ }
+
+ @Handler
+ public void onRstudioSupport()
+ {
+ globalDisplay_.openRStudioLink("support");
+ }
+
+ @Handler
+ public void onRstudioAgreement()
+ {
+ showAgreement();
+ }
+
+ @Handler
+ public void onUpdateCredentials()
+ {
+ server_.updateCredentials();
+ }
+
+ @Handler
+ public void onRaiseException() {
+ throw new RuntimeException("foo");
+ }
+
+ @Handler
+ public final native void onRaiseException2() /*-{
+ $wnd.welfkjweg();
+ }-*/;
+
+ @Handler
+ public void onShowRequestLog()
+ {
+ GWT.runAsync(new RunAsyncCallback()
+ {
+ public void onFailure(Throwable reason)
+ {
+ Window.alert(reason.toString());
+ }
+
+ public void onSuccess()
+ {
+ final RequestLogVisualization viz = new RequestLogVisualization();
+ final RootLayoutPanel root = RootLayoutPanel.get();
+ root.add(viz);
+ root.setWidgetTopBottom(viz, 10, Unit.PX, 10, Unit.PX);
+ root.setWidgetLeftRight(viz, 10, Unit.PX, 10, Unit.PX);
+ viz.addCloseHandler(new CloseHandler<RequestLogVisualization>()
+ {
+ public void onClose(CloseEvent<RequestLogVisualization> event)
+ {
+ root.remove(viz);
+ }
+ });
+ }
+ });
+ }
+
+ @Handler
+ public void onLogFocusedElement()
+ {
+ Element el = DomUtils.getActiveElement();
+ DomUtils.dump(el, "Focused Element: ");
+ }
+
+ @Handler
+ public void onRefreshSuperDevMode()
+ {
+ SuperDevMode.reload();
+ }
+
+ @Handler
+ public void onZoomActualSize()
+ {
+ // only supported in cocoa desktop
+ if (BrowseCap.isCocoaDesktop())
+ Desktop.getFrame().macZoomActualSize();
+ }
+
+ @Handler
+ public void onZoomIn()
+ {
+ // pass on to cocoa desktop (qt desktop intercepts)
+ if (BrowseCap.isCocoaDesktop())
+ Desktop.getFrame().macZoomIn();
+ }
+
+ @Handler
+ public void onZoomOut()
+ {
+ // pass on to cocoa desktop (qt desktop intercepts)
+ if (BrowseCap.isCocoaDesktop())
+ Desktop.getFrame().macZoomOut();
+ }
+
+ public void onSessionSerialization(SessionSerializationEvent event)
+ {
+ switch(event.getAction().getType())
+ {
+ case SessionSerializationAction.LOAD_DEFAULT_WORKSPACE:
+ view_.showSerializationProgress(
+ "Loading workspace" + getSuffix(event),
+ false, // non-modal, appears to user as std latency
+ 500, // willing to show progress earlier since
+ // this will always be at workbench startup
+ 0); // no timeout
+ break;
+ case SessionSerializationAction.SAVE_DEFAULT_WORKSPACE:
+ view_.showSerializationProgress(
+ "Saving workspace image" + getSuffix(event),
+ true, // modal, inputs will fall dead anyway
+ 0, // show immediately
+ 0); // no timeout
+ break;
+ case SessionSerializationAction.SUSPEND_SESSION:
+ view_.showSerializationProgress(
+ "Backing up R session...",
+ true, // modal, inputs will fall dead anyway
+ 0, // show immediately
+ 60000); // timeout after 60 seconds. this is done
+ // in case the user suspends or loses
+ // connectivity during the backup (in which
+ // case the 'completed' event dies with
+ // server and is never received by the client
+ break;
+ case SessionSerializationAction.RESUME_SESSION:
+ view_.showSerializationProgress(
+ "Resuming R session...",
+ false, // non-modal, appears to user as std latency
+ 2000, // don't show this for reasonable restore time
+ // (happens inline while using a running
+ // workbench so be more conservative)
+ 0); // no timeout
+ break;
+ case SessionSerializationAction.COMPLETED:
+ view_.hideSerializationProgress();
+ break;
+ }
+ }
+
+ private String getSuffix(SessionSerializationEvent event)
+ {
+ SessionSerializationAction action = event.getAction();
+ String targetPath = action.getTargetPath();
+ if (targetPath != null)
+ {
+ String verb = " from ";
+ if (action.getType() == SessionSerializationAction.SAVE_DEFAULT_WORKSPACE)
+ verb = " to ";
+ return verb + targetPath + "...";
+ }
+ else
+ {
+ return "...";
+ }
+ }
+
+ public void onServerUnavailable(ServerUnavailableEvent event)
+ {
+ view_.hideSerializationProgress();
+ }
+
+ public void onReload(ReloadEvent event)
+ {
+ cleanupWorkbench();
+
+ reloadWindowWithDelay(false);
+ }
+
+ public void onQuit(QuitEvent event)
+ {
+ cleanupWorkbench();
+
+ // only show the quit state in server mode (desktop mode has its
+ // own handling triggered to process exit)
+ if (!Desktop.isDesktop())
+ {
+ // if we are switching projects then reload after a delay (to allow
+ // the R session to fully exit on the server)
+ if (event.getSwitchProjects())
+ {
+ reloadWindowWithDelay(true);
+ }
+ else
+ {
+ view_.showApplicationQuit();
+ }
+ }
+ }
+
+
+ private void reloadWindowWithDelay(final boolean baseUrlOnly)
+ {
+ new Timer() {
+ @Override
+ public void run()
+ {
+ if (baseUrlOnly)
+ Window.Location.replace(GWT.getHostPageBaseURL());
+ else
+ Window.Location.reload();
+ }
+ }.schedule(100);
+ }
+
+ public void onSuicide(SuicideEvent event)
+ {
+ cleanupWorkbench();
+ view_.showApplicationSuicide(event.getMessage());
+ }
+
+ public void onClientDisconnected(ClientDisconnectedEvent event)
+ {
+ cleanupWorkbench();
+ view_.showApplicationDisconnected();
+ }
+
+ public void onInvalidClientVersion(InvalidClientVersionEvent event)
+ {
+ cleanupWorkbench();
+ view_.showApplicationUpdateRequired();
+ }
+
+ public void onSessionAbendWarning(SessionAbendWarningEvent event)
+ {
+ view_.showSessionAbendWarning();
+ }
+
+ private void verifyAgreement(SessionInfo sessionInfo,
+ final Operation verifiedOperation)
+ {
+ // get the agreement (if any)
+ final Agreement agreement = sessionInfo.pendingAgreement();
+
+ // if there is an agreement then prompt user for agreement (otherwise just
+ // execute the verifiedOperation immediately)
+ if (agreement != null)
+ {
+ // append updated to the title if necessary
+ String title = agreement.getTitle();
+ if (agreement.getUpdated())
+ title += " (Updated)";
+
+ view_.showApplicationAgreement(
+
+ // title and contents
+ title,
+ agreement.getContents(),
+
+ // bail to sign in page if the user doesn't confirm
+ new Operation()
+ {
+ public void execute()
+ {
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().setPendingQuit(
+ DesktopFrame.PENDING_QUIT_AND_EXIT);
+ server_.quitSession(false,
+ null,
+ new SimpleRequestCallback<Void>());
+ }
+ else
+ navigateToSignIn();
+ }
+ },
+
+ // user confirmed
+ new Operation() {
+ public void execute()
+ {
+ // call verified operation
+ verifiedOperation.execute();
+
+ // record agreement on server
+ server_.acceptAgreement(agreement,
+ new VoidServerRequestCallback());
+ }
+ }
+
+ );
+
+ }
+ else
+ {
+ // no agreement pending
+ verifiedOperation.execute();
+ }
+ }
+
+ private void navigateWindowTo(String relativeUrl)
+ {
+ cleanupWorkbench();
+
+ String url = GWT.getHostPageBaseURL() + relativeUrl;
+ Window.Location.replace(url);
+ }
+
+ private void initializeWorkbench()
+ {
+ pAceThemes_.get();
+
+ // subscribe to ClientDisconnected event (wait to do this until here
+ // because there were spurious ClientDisconnected events occuring
+ // after a session interrupt sequence. we couldn't figure out why,
+ // and since this is a temporary hack why not add another temporary
+ // hack to go with it here :-)
+ // TOOD: move this back tot he constructor after we revise the
+ // interrupt hack(s)
+ events_.addHandler(ClientDisconnectedEvent.TYPE, this);
+
+ // create workbench
+ Workbench wb = workbench_.get();
+ eventBusProvider_.get().fireEvent(new SessionInitEvent()) ;
+
+ // disable commands
+ SessionInfo sessionInfo = session_.getSessionInfo();
+ if (!sessionInfo.getAllowShell())
+ {
+ commands_.showShellDialog().remove();
+ }
+ if (!sessionInfo.getAllowPackageInstallation())
+ {
+ commands_.installPackage().remove();
+ commands_.updatePackages().remove();
+ }
+ if (!sessionInfo.getAllowVcs())
+ {
+ commands_.versionControlProjectSetup().remove();
+ }
+ if (!sessionInfo.getAllowFileDownloads())
+ {
+ commands_.exportFiles().remove();
+ }
+
+ // disable rpubs if requested
+ if (!sessionInfo.getAllowRpubsPublish())
+ {
+ commands_.publishHTML().remove();
+ commands_.presentationPublishToRpubs().remove();
+ }
+
+ // hide the agreement menu item if we don't have one
+ if (!session_.getSessionInfo().hasAgreement())
+ commands_.rstudioAgreement().setVisible(false);
+
+ // show workbench
+ view_.showWorkbenchView(wb.getMainView().asWidget());
+
+ // hide zoom actual size everywhere but cocoa desktop
+ if (!BrowseCap.isCocoaDesktop())
+ {
+ commands_.zoomActualSize().remove();
+ }
+
+ // hide zoom in and zoom out in web mode
+ if (!Desktop.isDesktop())
+ {
+ commands_.zoomIn().remove();
+ commands_.zoomOut().remove();
+ }
+
+ // toolbar (must be after call to showWorkbenchView because
+ // showing the toolbar repositions the workbench view widget)
+ showToolbar( uiPrefs_.get().toolbarVisible().getValue());
+
+ // sync to changes in the toolbar visibility state
+ uiPrefs_.get().toolbarVisible().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ showToolbar(event.getValue());
+ }
+ });
+
+ clientStateUpdaterInstance_ = clientStateUpdater_.get();
+ }
+
+ private void setToolbarPref(boolean showToolbar)
+ {
+ uiPrefs_.get().toolbarVisible().setGlobalValue(showToolbar);
+ uiPrefs_.get().writeUIPrefs();
+ }
+
+ private void showToolbar(boolean showToolbar)
+ {
+ // show or hide the toolbar
+ view_.showToolbar(showToolbar);
+
+ // manage commands
+ commands_.showToolbar().setVisible(!showToolbar);
+ commands_.hideToolbar().setVisible(showToolbar);
+ }
+
+ private void hideWorkbench(final RootLayoutPanel rootPanel)
+ {
+ final Label w = new Label();
+ w.getElement().getStyle().setBackgroundColor("#e1e2e5");
+ rootPanel.add(w);
+ rootPanel.setWidgetTopBottom(w, 0, Style.Unit.PX,
+ 0, Style.Unit.PX);
+ rootPanel.setWidgetLeftRight(w, 0, Style.Unit.PX,
+ 0, Style.Unit.PX);
+ }
+
+ private void cleanupWorkbench()
+ {
+ server_.disconnect();
+
+ satelliteManager_.closeAllSatellites();
+
+ if (clientStateUpdaterInstance_ != null)
+ {
+ clientStateUpdaterInstance_.suspend();
+ clientStateUpdaterInstance_ = null;
+ }
+ }
+
+ private void navigateToSignIn()
+ {
+ navigateWindowTo("auth-sign-in");
+ }
+
+ private boolean haveProjectParameter()
+ {
+ return Window.Location.getParameter("project") != null;
+ }
+
+ private final ApplicationView view_ ;
+ private final GlobalDisplay globalDisplay_ ;
+ private final EventBus events_;
+ private final Session session_;
+ private final Commands commands_;
+ private final SatelliteManager satelliteManager_;
+ private final Provider<ClientStateUpdater> clientStateUpdater_;
+ private final Server server_;
+ private final Provider<UIPrefs> uiPrefs_;
+ private final Provider<Workbench> workbench_;
+ private final Provider<EventBus> eventBusProvider_;
+ private final Provider<ApplicationClientInit> pClientInit_;
+ private final Provider<ApplicationQuit> pApplicationQuit_;
+ private final Provider<ApplicationInterrupt> pApplicationInterrupt_;
+ private final Provider<AceThemes> pAceThemes_;
+
+ private ClientStateUpdater clientStateUpdaterInstance_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ApplicationClientInit.java b/src/gwt/src/org/rstudio/studio/client/application/ApplicationClientInit.java
new file mode 100644
index 0000000..f7117ee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ApplicationClientInit.java
@@ -0,0 +1,169 @@
+/*
+ * ApplicationClientInit.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+public class ApplicationClientInit
+{
+ @Inject
+ public ApplicationClientInit(ApplicationServerOperations server,
+ GlobalDisplay globalDisplay)
+ {
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ }
+
+ public void execute(final ServerRequestCallback<SessionInfo> requestCallback)
+ {
+ // reset internal state
+ timedOut_ = false;
+ timeoutTimer_ = null;
+
+ // send the request
+ final ServerRequestCallback<SessionInfo> rpcRequestCallback =
+ new ServerRequestCallback<SessionInfo>() {
+ @Override
+ public void onResponseReceived(SessionInfo sessionInfo)
+ {
+ if (!timedOut_)
+ {
+ cancelTimeoutTimer();
+ requestCallback.onResponseReceived(sessionInfo);
+ }
+ }
+ @Override
+ public void onError(ServerError error)
+ {
+ if (!timedOut_)
+ {
+ cancelTimeoutTimer();
+ requestCallback.onError(error);
+ }
+ }
+ };
+ server_.clientInit(rpcRequestCallback);
+
+
+ // wait for 60 seconds then ask the user if they want to issue an
+ // interrupt to the server
+ int timeoutMs = 60000;
+ timeoutTimer_ = new Timer() {
+ public void run()
+ {
+ // set timed out flag
+ timedOut_ = true;
+
+ // cancel our request
+ rpcRequestCallback.cancel();
+
+ // ask the user if they want to attempt to interrupt the server
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+
+ // caption
+ "Initializing RStudio",
+
+ // message
+ "The RStudio server is taking a long time to respond. It is " +
+ "possible that your R session has become unresponsive. " +
+ "Do you want to terminate the currently running R session?",
+
+ // don't include cancel
+ false,
+
+ // Yes operation
+ new Operation() { public void execute() {
+
+ // call interrupt then call this method back on success
+ server_.abort(null, new ServerRequestCallback<Void>() {
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ // reload the application
+ reloadWithDelay(1000);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // if we get an error during interrupt then just
+ // forward the error on to the original handler
+ requestCallback.onError(error);
+ }
+
+ });
+
+ }},
+
+ // No operation
+ new Operation() { public void execute() {
+
+ // keep trying (reload to clear out any crufty app
+ // or networking state)
+ reloadWithDelay(1);
+ }},
+
+ // Cancel operation (none)
+ null,
+
+ "Terminate R",
+ "Keep Waiting",
+
+ // default to No
+ false);
+ }
+ };
+
+ // activate the timer
+ timeoutTimer_.schedule(timeoutMs);
+ }
+
+ private void reloadWithDelay(int delayMs)
+ {
+ // need a delay so the server has time to fully process the
+ // interrupt and go offline
+ Timer delayTimer = new Timer() {
+ public void run()
+ {
+ Window.Location.reload();
+ }
+ };
+ delayTimer.schedule(delayMs);
+ }
+
+ private void cancelTimeoutTimer()
+ {
+ if (timeoutTimer_ != null)
+ {
+ timeoutTimer_.cancel();
+ timeoutTimer_ = null;
+ }
+ }
+
+ private final ApplicationServerOperations server_;
+ private final GlobalDisplay globalDisplay_ ;
+ private Timer timeoutTimer_ = null;
+ private boolean timedOut_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ApplicationInterrupt.java b/src/gwt/src/org/rstudio/studio/client/application/ApplicationInterrupt.java
new file mode 100644
index 0000000..39d56e7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ApplicationInterrupt.java
@@ -0,0 +1,209 @@
+/*
+ * ApplicationInterrupt.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.ReloadEvent;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.projects.Projects;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.user.client.Timer;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ApplicationInterrupt
+{
+ public interface Binder extends CommandBinder<Commands, ApplicationInterrupt> {}
+
+ public interface InterruptHandler
+ {
+ public void onInterruptFinished();
+ }
+
+ @Inject
+ public ApplicationInterrupt(GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ ApplicationServerOperations server,
+ Provider<WorkbenchContext> pWorkbenchContext,
+ Commands commands,
+ Binder binder)
+ {
+ // save references
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ server_ = server;
+ pWorkbenchContext_ = pWorkbenchContext;
+
+ // bind to commands
+ binder.bind(commands, this);
+
+ }
+
+ public void interruptR(final InterruptHandler handler)
+ {
+ if (interruptRequestCounter_ == 0)
+ {
+ interruptRequestCounter_ = 1;
+ interruptUnresponsiveTimer_ = new Timer() {
+ @Override
+ public void run()
+ {
+ showInterruptUnresponsiveDialog();
+ }
+ };
+ interruptUnresponsiveTimer_.schedule(10000);
+
+ server_.interrupt(new VoidServerRequestCallback() {
+ @Override
+ public void onSuccess()
+ {
+ finishInterrupt(handler);
+ }
+
+ @Override
+ public void onFailure()
+ {
+ finishInterrupt(handler);
+ }
+ });
+ }
+ else
+ {
+ interruptRequestCounter_++;
+
+ if (interruptRequestCounter_ >= 3)
+ {
+ interruptUnresponsiveTimer_.cancel();
+ showInterruptUnresponsiveDialog();
+ }
+ }
+ }
+
+ @Handler
+ void onInterruptR()
+ {
+ interruptR(null);
+ }
+
+
+ @Handler
+ public void onTerminateR()
+ {
+ showTerminationDialog(
+ TERMINATION_CONSEQUENCE_MSG +
+ "\n\n" +
+ "Are you sure you want to terminate R?");
+ }
+
+ private void showInterruptUnresponsiveDialog()
+ {
+ showTerminationDialog(
+ "R is not responding to your request to interrupt processing so " +
+ "to stop the current operation you may need to terminate R entirely." +
+ "\n\n" +
+ TERMINATION_CONSEQUENCE_MSG +
+ "\n\n" +
+ "Do you want to terminate R now?");
+ }
+
+
+ private void showTerminationDialog(String message)
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Terminate R",
+ message,
+ false,
+ new ProgressOperation() {
+ @Override
+ public void execute(ProgressIndicator indicator)
+ {
+ setPendinqQuit(DesktopFrame.PENDING_QUIT_RESTART_AND_RELOAD);
+
+ // determine the next session project
+ String nextProj = pWorkbenchContext_.get()
+ .getActiveProjectFile();
+ if (nextProj == null)
+ nextProj = Projects.NONE;
+
+ // force the abort
+ server_.abort(nextProj,
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ protected void onSuccess()
+ {
+ if (!Desktop.isDesktop())
+ eventBus_.fireEvent(new ReloadEvent());
+ }
+ @Override
+ protected void onFailure()
+ {
+ setPendinqQuit(DesktopFrame.PENDING_QUIT_NONE);
+ }
+ });
+ }
+ },
+ new ProgressOperation() {
+
+ @Override
+ public void execute(ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ }
+ },
+ false);
+ }
+
+ private void setPendinqQuit(int pendingQuit)
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().setPendingQuit(pendingQuit);
+ }
+
+ private void finishInterrupt(InterruptHandler handler)
+ {
+ interruptRequestCounter_ = 0;
+ interruptUnresponsiveTimer_.cancel();
+ if (handler != null)
+ {
+ handler.onInterruptFinished();
+ }
+ }
+
+ private int interruptRequestCounter_ = 0;
+ private Timer interruptUnresponsiveTimer_ = null;
+
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus eventBus_;
+ private final Provider<WorkbenchContext> pWorkbenchContext_;
+ private final ApplicationServerOperations server_;
+
+ private final static String TERMINATION_CONSEQUENCE_MSG =
+ "Terminating R will cause your R session to immediately abort. " +
+ "Active computations will be interrupted and unsaved source file " +
+ "changes and workspace objects will be discarded.";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ApplicationQuit.java b/src/gwt/src/org/rstudio/studio/client/application/ApplicationQuit.java
new file mode 100644
index 0000000..02ca267
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ApplicationQuit.java
@@ -0,0 +1,659 @@
+/*
+ * ApplicationQuit.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.Barrier;
+import org.rstudio.core.client.Barrier.Token;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.BarrierReleasedEvent;
+import org.rstudio.core.client.events.BarrierReleasedHandler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.HandleUnsavedChangesEvent;
+import org.rstudio.studio.client.application.events.HandleUnsavedChangesHandler;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.application.events.SaveActionChangedEvent;
+import org.rstudio.studio.client.application.events.SaveActionChangedHandler;
+import org.rstudio.studio.client.application.events.SuspendAndRestartEvent;
+import org.rstudio.studio.client.application.events.SuspendAndRestartHandler;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.application.model.SaveAction;
+import org.rstudio.studio.client.application.model.SuspendOptions;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog.Result;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleRestartRCompletedEvent;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.source.SourceShim;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ApplicationQuit implements SaveActionChangedHandler,
+ HandleUnsavedChangesHandler,
+ SuspendAndRestartHandler
+{
+ public interface Binder extends CommandBinder<Commands, ApplicationQuit> {}
+
+ @Inject
+ public ApplicationQuit(ApplicationServerOperations server,
+ GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ WorkbenchContext workbenchContext,
+ SourceShim sourceShim,
+ Provider<UIPrefs> pUiPrefs,
+ Commands commands,
+ Binder binder)
+ {
+ // save references
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ workbenchContext_ = workbenchContext;
+ sourceShim_ = sourceShim;
+ pUiPrefs_ = pUiPrefs;
+
+ // bind to commands
+ binder.bind(commands, this);
+
+ // subscribe to events
+ eventBus.addHandler(SaveActionChangedEvent.TYPE, this);
+ eventBus.addHandler(HandleUnsavedChangesEvent.TYPE, this);
+ eventBus.addHandler(SuspendAndRestartEvent.TYPE, this);
+ }
+
+
+ // notification that we are ready to quit
+ public interface QuitContext
+ {
+ void onReadyToQuit(boolean saveChanges);
+ }
+
+ public void forceSwitchProject(final String switchToProject)
+ {
+ ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
+ sourceShim_.getUnsavedChanges();
+ sourceShim_.handleUnsavedChangesBeforeExit(
+ unsavedSourceDocs,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ performQuit(true, switchToProject);
+ }
+ });
+ }
+
+ public void prepareForQuit(final String caption,
+ final QuitContext quitContext)
+ {
+ if (workbenchContext_.isServerBusy())
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.QUESTION,
+ caption,
+ "The R session is currently busy. Are you sure you want to quit?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ handleUnsavedChanges(caption, quitContext);
+ }},
+ true);
+ }
+ else
+ {
+ // if we aren't restoring source documents then close them all now
+ if (!pUiPrefs_.get().restoreSourceDocuments().getValue())
+ {
+ sourceShim_.closeAllSourceDocs(caption, new Command() {
+ @Override
+ public void execute()
+ {
+ handleUnsavedChanges(caption, quitContext);
+ }
+ });
+ }
+ else
+ {
+ handleUnsavedChanges(caption, quitContext);
+ }
+ }
+ }
+
+ private void handleUnsavedChanges(String caption,
+ final QuitContext quitContext)
+ {
+ // see what the unsaved changes situation is and prompt accordingly
+ final int saveAction = saveAction_.getAction();
+ ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
+ sourceShim_.getUnsavedChanges();
+
+ // no unsaved changes at all
+ if (saveAction != SaveAction.SAVEASK && unsavedSourceDocs.size() == 0)
+ {
+ quitContext.onReadyToQuit(saveAction == SaveAction.SAVE);
+ return;
+ }
+
+ // just an unsaved environment
+ if (unsavedSourceDocs.size() == 0)
+ {
+ // confirm quit and do it
+ String prompt = "Save workspace image to " +
+ workbenchContext_.getREnvironmentPath() + "?";
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_QUESTION,
+ caption,
+ prompt,
+ true,
+ new Operation() { public void execute()
+ {
+ quitContext.onReadyToQuit(true);
+ }},
+ new Operation() { public void execute()
+ {
+ quitContext.onReadyToQuit(false);
+ }},
+ new Operation() { public void execute()
+ {
+ }},
+ "Save",
+ "Don't Save",
+ true);
+ }
+
+ // a single unsaved document
+ else if (saveAction != SaveAction.SAVEASK &&
+ unsavedSourceDocs.size() == 1)
+ {
+ sourceShim_.saveWithPrompt(
+ unsavedSourceDocs.get(0),
+ sourceShim_.revertUnsavedChangesBeforeExitCommand(new Command() {
+ @Override
+ public void execute()
+ {
+ quitContext.onReadyToQuit(saveAction == SaveAction.SAVE);
+ }}),
+ null);
+ }
+
+ // multiple save targets
+ else
+ {
+ ArrayList<UnsavedChangesTarget> unsaved =
+ new ArrayList<UnsavedChangesTarget>();
+ if (saveAction == SaveAction.SAVEASK)
+ unsaved.add(globalEnvTarget_);
+ unsaved.addAll(unsavedSourceDocs);
+ new UnsavedChangesDialog(
+ caption,
+ unsaved,
+ new OperationWithInput<UnsavedChangesDialog.Result>() {
+
+ @Override
+ public void execute(Result result)
+ {
+ ArrayList<UnsavedChangesTarget> saveTargets =
+ result.getSaveTargets();
+
+ // remote global env target from list (if specified) and
+ // compute the saveChanges value
+ boolean saveGlobalEnv = saveAction == SaveAction.SAVE;
+ if (saveAction == SaveAction.SAVEASK)
+ saveGlobalEnv = saveTargets.remove(globalEnvTarget_);
+ final boolean saveChanges = saveGlobalEnv;
+
+ // save specified documents and then quit
+ sourceShim_.handleUnsavedChangesBeforeExit(
+ saveTargets,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ quitContext.onReadyToQuit(saveChanges);
+ }
+ });
+ }
+
+ },
+
+ // no cancel operation
+ null
+
+ ).showModal();
+ }
+
+ }
+
+ public void performQuit(boolean saveChanges,
+ String switchToProject)
+ {
+ performQuit(null, saveChanges, switchToProject);
+ }
+
+ public void performQuit(String progressMessage,
+ boolean saveChanges,
+ String switchToProject)
+ {
+ performQuit(progressMessage, saveChanges, switchToProject, null);
+ }
+
+ public void performQuit(String progressMessage,
+ boolean saveChanges,
+ String switchToProject,
+ Command onQuitAcknowledged)
+ {
+ new QuitCommand(progressMessage,
+ saveChanges,
+ switchToProject,
+ onQuitAcknowledged).execute();
+ }
+
+ @Override
+ public void onSaveActionChanged(SaveActionChangedEvent event)
+ {
+ saveAction_ = event.getAction();
+ }
+
+ @Override
+ public void onHandleUnsavedChanges(HandleUnsavedChangesEvent event)
+ {
+ // command which will be used to callback the server
+ class HandleUnsavedCommand implements Command
+ {
+ public HandleUnsavedCommand(boolean handled)
+ {
+ handled_ = handled;
+ }
+
+ @Override
+ public void execute()
+ {
+ // this codepath is for when the user quits R using the q()
+ // function -- in this case our standard client quit codepath
+ // isn't invoked, and as a result the desktop is not notified
+ // that there is a pending quit (so thinks R has crashed when
+ // the process exits). since this codepath is only for the quit
+ // case (and not the restart or restart and reload cases)
+ // we can set the pending quit bit here
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().setPendingQuit(
+ DesktopFrame.PENDING_QUIT_AND_EXIT);
+ }
+
+ server_.handleUnsavedChangesCompleted(
+ handled_,
+ new VoidServerRequestCallback());
+ }
+
+ private final boolean handled_;
+ };
+
+ // get unsaved source docs
+ ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
+ sourceShim_.getUnsavedChanges();
+
+ if (unsavedSourceDocs.size() == 1)
+ {
+ sourceShim_.saveWithPrompt(
+ unsavedSourceDocs.get(0),
+ sourceShim_.revertUnsavedChangesBeforeExitCommand(
+ new HandleUnsavedCommand(true)),
+ new HandleUnsavedCommand(false));
+ }
+ else if (unsavedSourceDocs.size() > 1)
+ {
+ new UnsavedChangesDialog(
+ "Quit R Session",
+ unsavedSourceDocs,
+ new OperationWithInput<UnsavedChangesDialog.Result>() {
+ @Override
+ public void execute(Result result)
+ {
+ // save specified documents and then quit
+ sourceShim_.handleUnsavedChangesBeforeExit(
+ result.getSaveTargets(),
+ new HandleUnsavedCommand(true));
+ }
+ },
+ new HandleUnsavedCommand(false)
+ ).showModal();
+ }
+ else
+ {
+ new HandleUnsavedCommand(true).execute();
+ }
+ }
+
+
+ @Handler
+ public void onRestartR()
+ {
+ boolean saveChanges = saveAction_.getAction() != SaveAction.NOSAVE;
+ eventBus_.fireEvent(new SuspendAndRestartEvent(
+ SuspendOptions.createSaveMinimal(saveChanges),
+ null));
+
+ }
+
+ @Override
+ public void onSuspendAndRestart(final SuspendAndRestartEvent event)
+ {
+ // set restart pending for desktop
+ setPendinqQuit(DesktopFrame.PENDING_QUIT_AND_RESTART);
+
+ ProgressIndicator progress = new GlobalProgressDelayer(
+ globalDisplay_,
+ 200,
+ "Restarting R...").getIndicator();
+
+ // perform the suspend and restart
+ eventBus_.fireEvent(
+ new RestartStatusEvent(RestartStatusEvent.RESTART_INITIATED));
+ server_.suspendForRestart(event.getSuspendOptions(),
+ new VoidServerRequestCallback(progress) {
+ @Override
+ protected void onSuccess()
+ {
+ // send pings until the server restarts
+ sendPing(event.getAfterRestartCommand(), 200, 25, new Command() {
+
+ @Override
+ public void execute()
+ {
+ eventBus_.fireEvent(new RestartStatusEvent(
+ RestartStatusEvent.RESTART_COMPLETED));
+
+ }
+
+ });
+ }
+ @Override
+ protected void onFailure()
+ {
+ eventBus_.fireEvent(
+ new RestartStatusEvent(RestartStatusEvent.RESTART_COMPLETED));
+
+ setPendinqQuit(DesktopFrame.PENDING_QUIT_NONE);
+ }
+ });
+
+ }
+
+ private void setPendinqQuit(int pendingQuit)
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().setPendingQuit(pendingQuit);
+ }
+
+ private void sendPing(final String afterRestartCommand,
+ int delayMs,
+ final int maxRetries,
+ final Command onCompleted)
+ {
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+
+ private int retries_ = 0;
+ private boolean pingDelivered_ = false;
+ private boolean pingInFlight_ = false;
+
+ @Override
+ public boolean execute()
+ {
+ // if we've already delivered the ping or our retry count
+ // is exhausted then return false
+ if (pingDelivered_ || (++retries_ > maxRetries))
+ return false;
+
+ if (!pingInFlight_)
+ {
+ pingInFlight_ = true;
+ server_.ping(new VoidServerRequestCallback() {
+ @Override
+ protected void onSuccess()
+ {
+ pingInFlight_ = false;
+
+ if (!pingDelivered_)
+ {
+ pingDelivered_ = true;
+
+ // issue after restart command
+ if (!StringUtil.isNullOrEmpty(afterRestartCommand))
+ {
+ eventBus_.fireEvent(
+ new SendToConsoleEvent(afterRestartCommand,
+ true, true));
+ }
+ // otherwise make sure the console knows we
+ // restarted (ensure prompt and set focus)
+ else
+ {
+ eventBus_.fireEvent(
+ new ConsoleRestartRCompletedEvent());
+ }
+ }
+
+ if (onCompleted != null)
+ onCompleted.execute();
+ }
+
+ @Override
+ protected void onFailure()
+ {
+ pingInFlight_ = false;
+
+ if (onCompleted != null)
+ onCompleted.execute();
+ }
+ });
+ }
+
+ // keep trying until the ping is delivered
+ return true;
+ }
+
+ }, delayMs);
+
+
+ }
+
+
+ @Handler
+ public void onQuitSession()
+ {
+ prepareForQuit("Quit R Session", new QuitContext() {
+ public void onReadyToQuit(boolean saveChanges)
+ {
+ performQuit(saveChanges, null);
+ }
+ });
+ }
+
+ private UnsavedChangesTarget globalEnvTarget_ = new UnsavedChangesTarget()
+ {
+ @Override
+ public String getId()
+ {
+ return "F59C8727-3C63-41F4-989C-B1E1D47760E3";
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return FileIconResources.INSTANCE.iconRdata();
+ }
+
+ @Override
+ public String getTitle()
+ {
+ return "Workspace image (.RData)";
+ }
+
+ @Override
+ public String getPath()
+ {
+ return workbenchContext_.getREnvironmentPath();
+ }
+
+ };
+
+ private String buildSwitchMessage(String switchToProject)
+ {
+ String msg = !switchToProject.equals("none") ?
+ "Switching to project " +
+ FileSystemItem.createFile(switchToProject).getParentPathString() :
+ "Closing project";
+ return msg + "...";
+ }
+
+ private class QuitCommand implements Command
+ {
+ public QuitCommand(String progressMessage,
+ boolean saveChanges,
+ String switchToProject,
+ Command onQuitAcknowledged)
+ {
+ progressMessage_ = progressMessage;
+ saveChanges_ = saveChanges;
+ switchToProject_ = switchToProject;
+ onQuitAcknowledged_ = onQuitAcknowledged;
+ }
+
+ public void execute()
+ {
+ // show delayed progress
+ String msg = progressMessage_;
+ if (msg == null)
+ {
+ msg = switchToProject_ != null ?
+ buildSwitchMessage(switchToProject_) :
+ "Quitting R Session...";
+ }
+ final GlobalProgressDelayer progress = new GlobalProgressDelayer(
+ globalDisplay_,
+ 250,
+ msg);
+
+ // Use a barrier and LastChanceSaveEvent to allow source documents
+ // and client state to be synchronized before quitting.
+ Barrier barrier = new Barrier();
+ barrier.addBarrierReleasedHandler(new BarrierReleasedHandler()
+ {
+ public void onBarrierReleased(BarrierReleasedEvent event)
+ {
+ // All last chance save operations have completed (or possibly
+ // failed). Now do the real quit.
+
+ // notify the desktop frame that we are about to quit
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().setPendingQuit(switchToProject_ != null ?
+ DesktopFrame.PENDING_QUIT_RESTART_AND_RELOAD :
+ DesktopFrame.PENDING_QUIT_AND_EXIT);
+ }
+
+ server_.quitSession(
+ saveChanges_,
+ switchToProject_,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ // clear progress only if we aren't switching projects
+ // (otherwise we want to leave progress up until
+ // the app reloads)
+ if (switchToProject_ == null)
+ progress.dismiss();
+
+ // fire onQuitAcknowledged
+ if (onQuitAcknowledged_ != null)
+ onQuitAcknowledged_.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progress.dismiss();
+
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().setPendingQuit(
+ DesktopFrame.PENDING_QUIT_NONE);
+ }
+ }
+ });
+ }
+ });
+
+ // We acquire a token to make sure that the barrier doesn't fire before
+ // all the LastChanceSaveEvent listeners get a chance to acquire their
+ // own tokens.
+ Token token = barrier.acquire();
+ try
+ {
+ eventBus_.fireEvent(new LastChanceSaveEvent(barrier));
+ }
+ finally
+ {
+ token.release();
+ }
+ }
+
+ private final boolean saveChanges_;
+ private final String switchToProject_;
+ private final String progressMessage_;
+ private final Command onQuitAcknowledged_;
+
+ };
+
+ private final ApplicationServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final Provider<UIPrefs> pUiPrefs_;
+ private final EventBus eventBus_;
+ private final WorkbenchContext workbenchContext_;
+ private final SourceShim sourceShim_;
+
+ private SaveAction saveAction_ = SaveAction.saveAsk();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ApplicationUncaughtExceptionHandler.java b/src/gwt/src/org/rstudio/studio/client/application/ApplicationUncaughtExceptionHandler.java
new file mode 100644
index 0000000..ec66b73
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ApplicationUncaughtExceptionHandler.java
@@ -0,0 +1,83 @@
+/*
+ * ApplicationUncaughtExceptionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import org.rstudio.studio.client.server.ClientException;
+import org.rstudio.studio.client.server.Server;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.event.shared.UmbrellaException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ApplicationUncaughtExceptionHandler
+ implements UncaughtExceptionHandler
+{
+ @Inject
+ public ApplicationUncaughtExceptionHandler(Server server)
+ {
+ server_ = server;
+ }
+
+ public void register()
+ {
+ // set uncaught exception handler (first save default so we can call it)
+ defaultUncaughtExceptionHandler_ = GWT.getUncaughtExceptionHandler();
+ GWT.setUncaughtExceptionHandler(this);
+ }
+
+ public void onUncaughtException(Throwable e)
+ {
+ logException(e);
+ }
+
+ private void logException(Throwable e)
+ {
+ try
+ {
+ // call the default handler if there is one
+ if (defaultUncaughtExceptionHandler_ != null)
+ defaultUncaughtExceptionHandler_.onUncaughtException(e);
+
+ // log uncaught exception
+ server_.logException(ClientException.create(unwrap(e)),
+ new VoidServerRequestCallback());
+
+ }
+ catch(Throwable throwable)
+ {
+ // make sure exceptions never escape the uncaught handler
+ }
+ }
+
+ private Throwable unwrap(Throwable e)
+ {
+ if (e instanceof UmbrellaException)
+ {
+ UmbrellaException ue = (UmbrellaException) e;
+ if(ue.getCauses().size() == 1)
+ return unwrap(ue.getCauses().iterator().next());
+ }
+
+ return e;
+ }
+
+
+ private final Server server_;
+ private UncaughtExceptionHandler defaultUncaughtExceptionHandler_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ApplicationView.java b/src/gwt/src/org/rstudio/studio/client/application/ApplicationView.java
new file mode 100644
index 0000000..a8c0d00
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ApplicationView.java
@@ -0,0 +1,60 @@
+/*
+ * ApplicationView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.widget.Operation;
+
+public interface ApplicationView
+{
+ // show application agreement
+ void showApplicationAgreement(String title,
+ String contents,
+ Operation doNotAcceptOperation,
+ Operation acceptOperation);
+
+ // set current main view for application
+ void showWorkbenchView(Widget widget);
+
+ // toolbar
+ void showToolbar(boolean showToolbar);
+
+ // go to function
+ void performGoToFunction();
+
+ // application exit states
+ void showApplicationQuit();
+ void showApplicationSuicide(String reason);
+ void showApplicationDisconnected();
+ void showApplicationOffline();
+ void showApplicationUpdateRequired();
+
+ // error messages
+ void showSessionAbendWarning();
+
+ // progress
+ void showSerializationProgress(String message,
+ boolean modal,
+ int delayMs,
+ int timeoutMs);
+ void hideSerializationProgress();
+
+ Widget getWidget() ;
+
+ void showWarning(boolean severe, String message);
+ void hideWarning();
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/application/Desktop.java b/src/gwt/src/org/rstudio/studio/client/application/Desktop.java
new file mode 100644
index 0000000..8821a40
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/Desktop.java
@@ -0,0 +1,31 @@
+/*
+ * Desktop.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.core.client.GWT;
+
+public class Desktop
+{
+ public static native boolean isDesktop() /*-{
+ return !!$wnd.desktop;
+ }-*/;
+
+ public static DesktopFrame getFrame()
+ {
+ return desktopFrame_;
+ }
+
+ private static final DesktopFrame desktopFrame_ = GWT.create(DesktopFrame.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/DesktopFrame.java b/src/gwt/src/org/rstudio/studio/client/application/DesktopFrame.java
new file mode 100644
index 0000000..23d62ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/DesktopFrame.java
@@ -0,0 +1,133 @@
+/*
+ * DesktopFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import org.rstudio.core.client.js.BaseExpression;
+import org.rstudio.core.client.js.JavaScriptPassthrough;
+
+/**
+ * This is an interface straight through to a C++ object that lives
+ * in the Qt desktop frame.
+ */
+ at BaseExpression("$wnd.desktop")
+public interface DesktopFrame extends JavaScriptPassthrough
+{
+ boolean isCocoa();
+ void browseUrl(String url);
+ String getOpenFileName(String caption, String dir, String filter);
+ String getSaveFileName(String caption,
+ String dir,
+ String defaultExtension,
+ boolean forceDefaultExtension);
+ String getExistingDirectory(String caption, String dir);
+ void undo();
+ void redo();
+ void clipboardCut();
+ void clipboardCopy();
+ void clipboardPaste();
+ String getUriForPath(String path);
+ void onWorkbenchInitialized(String scratchDir);
+ void showFolder(String path);
+ void showFile(String path);
+ void openMinimalWindow(String name, String url, int width, int height);
+ void activateSatelliteWindow(String name);
+ void prepareForSatelliteWindow(String name, int width, int height);
+ void copyImageToClipboard(int clientLeft,
+ int clientTop,
+ int clientWidth,
+ int clientHeight);
+
+ boolean supportsClipboardMetafile();
+
+ int showMessageBox(int type,
+ String caption,
+ String message,
+ String buttons,
+ int defaultButton,
+ int cancelButton);
+
+ String promptForText(String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ String rememberPasswordPrompt,
+ boolean rememberByDefault,
+ boolean numbersOnly,
+ int selectionStart,
+ int selectionLength, String okButtonCaption);
+
+ void showAboutDialog();
+ void bringMainFrameToFront();
+
+ String getRVersion();
+ String chooseRVersion();
+ boolean canChooseRVersion();
+
+ boolean isRetina();
+
+ void cleanClipboard();
+
+ public static final int PENDING_QUIT_NONE = 0;
+ public static final int PENDING_QUIT_AND_EXIT = 1;
+ public static final int PENDING_QUIT_AND_RESTART = 2;
+ public static final int PENDING_QUIT_RESTART_AND_RELOAD = 3;
+
+ void setPendingQuit(int pendingQuit);
+ void launchSession(boolean reload);
+
+ void openProjectInNewWindow(String projectFilePath);
+
+ void openTerminal(String terminalPath,
+ String workingDirectory,
+ String extraPathEntries);
+
+ String getFixedWidthFontList();
+ String getFixedWidthFont();
+ void setFixedWidthFont(String font);
+
+ String getZoomLevels();
+ double getZoomLevel();
+ void setZoomLevel(double zoomLevel);
+
+ // mac-specific zoom calls
+ void macZoomActualSize();
+ void macZoomIn();
+ void macZoomOut();
+
+ String getDesktopSynctexViewer();
+
+ void externalSynctexPreview(String pdfPath, int page);
+
+ void externalSynctexView(String pdfFile,
+ String srcFile,
+ int line,
+ int column);
+
+ boolean supportsFullscreenMode();
+ void toggleFullscreenMode();
+ void showKeyboardShortcutHelp();
+
+ void reloadZoomWindow();
+
+ void setViewerUrl(String url);
+
+ boolean isOSXMavericks();
+
+ String getScrollingCompensationType();
+
+ void setBusy(boolean busy);
+
+ void setWindowTitle(String title);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/DesktopHooks.java b/src/gwt/src/org/rstudio/studio/client/application/DesktopHooks.java
new file mode 100644
index 0000000..cfc26cd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/DesktopHooks.java
@@ -0,0 +1,188 @@
+/*
+ * DesktopHooks.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.core.client.GWT;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.BaseExpression;
+import org.rstudio.core.client.js.JsObjectInjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.SaveActionChangedEvent;
+import org.rstudio.studio.client.application.events.SaveActionChangedHandler;
+import org.rstudio.studio.client.application.events.SuicideEvent;
+import org.rstudio.studio.client.application.model.SaveAction;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.Server;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.SourceShim;
+
+/**
+ * Any methods on this class are automatically made available to the
+ * Qt frame code.
+ */
+public class DesktopHooks
+{
+ @BaseExpression("$wnd.desktopHooks")
+ interface DesktopHooksInjector extends JsObjectInjector<DesktopHooks> {}
+ private static final DesktopHooksInjector injector =
+ GWT.create(DesktopHooksInjector.class);
+
+ @Inject
+ public DesktopHooks(Commands commands,
+ EventBus events,
+ Session session,
+ GlobalDisplay globalDisplay,
+ Provider<UIPrefs> pUIPrefs,
+ Server server,
+ FileTypeRegistry fileTypeRegistry,
+ WorkbenchContext workbenchContext,
+ SourceShim sourceShim)
+ {
+ commands_ = commands;
+ events_ = events;
+ session_ = session;
+ globalDisplay_ = globalDisplay;
+ pUIPrefs_ = pUIPrefs;
+ server_ = server;
+ fileTypeRegistry_ = fileTypeRegistry;
+ workbenchContext_ = workbenchContext;
+ sourceShim_ = sourceShim;
+
+ events_.addHandler(SaveActionChangedEvent.TYPE,
+ new SaveActionChangedHandler()
+ {
+ public void onSaveActionChanged(SaveActionChangedEvent event)
+ {
+ saveAction_ = event.getAction();
+ }
+ });
+
+ injector.injectObject(this);
+
+ addCopyHook();
+ }
+
+ private native void addCopyHook() /*-{
+ var clean = function() {
+ setTimeout(function() {
+ $wnd.desktop.cleanClipboard(false);
+ }, 100)
+ };
+ $wnd.addEventListener("copy", clean, true);
+ $wnd.addEventListener("cut", clean, true);
+ }-*/;
+
+
+ String getActiveProjectDir()
+ {
+ if (workbenchContext_.getActiveProjectDir() != null)
+ return workbenchContext_.getActiveProjectDir().getPath();
+ else
+ return "";
+ }
+
+ void invokeCommand(String cmdId)
+ {
+ commands_.getCommandById(cmdId).execute();
+ }
+
+ boolean isCommandVisible(String commandId)
+ {
+ AppCommand command = commands_.getCommandById(commandId);
+ return command != null && command.isVisible();
+ }
+
+ boolean isCommandEnabled(String commandId)
+ {
+ AppCommand command = commands_.getCommandById(commandId);
+ return command != null && command.isEnabled();
+ }
+
+ boolean isCommandChecked(String commandId)
+ {
+ AppCommand command = commands_.getCommandById(commandId);
+ return command != null && command.isChecked();
+ }
+
+ String getCommandLabel(String commandId)
+ {
+ AppCommand command = commands_.getCommandById(commandId);
+ return command != null ? command.getMenuLabel(true) : "";
+ }
+
+ void openFile(String filePath)
+ {
+ // get the file system item
+ FileSystemItem file = FileSystemItem.createFile(filePath);
+
+ if (file.isDirectory())
+ return;
+
+ // this used to be possible but shouldn't be anymore
+ // (since we screen out .rproj from calling sendMessage
+ // within DesktopMain.cpp
+ if (file.getExtension().equalsIgnoreCase(".rproj"))
+ return;
+
+ // open the file. pass false for second param to prevent
+ // the default handler (the browser) from taking it
+ fileTypeRegistry_.openFile(file, false);
+ }
+
+ void quitR()
+ {
+ commands_.quitSession().execute();
+ }
+
+ void notifyRCrashed()
+ {
+ events_.fireEvent(new SuicideEvent(""));
+ }
+
+ int getSaveAction()
+ {
+ return saveAction_.getAction();
+ }
+
+ String getREnvironmentPath()
+ {
+ return workbenchContext_.getREnvironmentPath();
+ }
+
+ String getSumatraPdfExePath()
+ {
+ return session_.getSessionInfo().getSumatraPdfExePath();
+ }
+
+ private final Commands commands_;
+ private final EventBus events_;
+ private final Session session_;
+ private final GlobalDisplay globalDisplay_;
+ private final Provider<UIPrefs> pUIPrefs_;
+ private final Server server_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final WorkbenchContext workbenchContext_;
+ private final SourceShim sourceShim_;
+
+ private SaveAction saveAction_ = SaveAction.saveAsk();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/IgnoredUpdates.java b/src/gwt/src/org/rstudio/studio/client/application/IgnoredUpdates.java
new file mode 100644
index 0000000..b236100
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/IgnoredUpdates.java
@@ -0,0 +1,76 @@
+/*
+ * IgnoredUpdates.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class IgnoredUpdates extends JavaScriptObject
+{
+ protected IgnoredUpdates() {}
+
+ public static final native IgnoredUpdates create() /*-{
+ return { updates: [] };
+ }-*/;
+
+ public final native JsArrayString getIgnoredUpdates() /*-{
+ return this.updates;
+ }-*/;
+
+ public final native void setIgnoredUpdates(JsArrayString updates) /*-{
+ this.updates = updates;
+ }-*/;
+
+ public final void addIgnoredUpdate(String update)
+ {
+ JsArrayString newUpdateList = create().getIgnoredUpdates();
+ JsArrayString existingUpdateList = getIgnoredUpdates();
+
+ for (int i = 0; i < existingUpdateList.length(); i++)
+ {
+ // We want to discard any updates we're ignoring that are older than
+ // the one we're ignoring now--i.e. if we're currently ignoring
+ // { 0.98.407, 0.99.440 }, and we were just asked to ignore
+ // 0.98.411, the new set should be { 0.98.411, 0.99.440 }. Do this by
+ // only keeping updates in the list that are newer than the update
+ // we're about to add.
+ if (compareVersions(update, existingUpdateList.get(i)) < 0)
+ {
+ newUpdateList.push(existingUpdateList.get(i));
+ }
+ }
+ newUpdateList.push(update);
+ setIgnoredUpdates(newUpdateList);
+ }
+
+ // Returns:
+ // < 0 if version1 is earlier than version 2
+ // 0 if version1 and version2 are the same
+ // > 0 if version1 is later than version 2
+ private final int compareVersions(String version1, String version2)
+ {
+ String[] v1parts = version1.split(".");
+ String[] v2parts = version2.split(".");
+ int numParts = Math.min(v1parts.length, v2parts.length);
+ for (int i = 0; i < numParts; i++)
+ {
+ int result = Integer.parseInt(v1parts[i]) -
+ Integer.parseInt(v2parts[i]);
+ if (result != 0)
+ return result;
+ }
+ return 0;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ApplicationEventHandlers.java b/src/gwt/src/org/rstudio/studio/client/application/events/ApplicationEventHandlers.java
new file mode 100644
index 0000000..f4c4561
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ApplicationEventHandlers.java
@@ -0,0 +1,29 @@
+/*
+ * ApplicationEventHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+public interface ApplicationEventHandlers extends LogoutRequestedHandler,
+ UnauthorizedHandler,
+ ReloadEvent.Handler,
+ QuitHandler,
+ SuicideHandler,
+ SessionAbendWarningHandler,
+ SessionSerializationHandler,
+ ServerUnavailableHandler,
+ ClientDisconnectedHandler,
+ InvalidClientVersionHandler,
+ ServerOfflineHandler
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeEvent.java
new file mode 100644
index 0000000..1c43ebb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeEvent.java
@@ -0,0 +1,46 @@
+/*
+ * ChangeFontSizeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ChangeFontSizeEvent extends GwtEvent<ChangeFontSizeHandler>
+{
+ public static final Type<ChangeFontSizeHandler> TYPE = new Type<ChangeFontSizeHandler>();
+
+ public ChangeFontSizeEvent(double fontSize)
+ {
+ fontSize_ = fontSize;
+ }
+
+ public double getFontSize()
+ {
+ return fontSize_;
+ }
+
+ private final double fontSize_;
+
+ @Override
+ public Type<ChangeFontSizeHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ChangeFontSizeHandler handler)
+ {
+ handler.onChangeFontSize(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeHandler.java
new file mode 100644
index 0000000..d16b023
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ChangeFontSizeHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ChangeFontSizeHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ChangeFontSizeHandler extends EventHandler
+{
+ void onChangeFontSize(ChangeFontSizeEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedEvent.java
new file mode 100644
index 0000000..ea1f006
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * ClientDisconnectedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ClientDisconnectedEvent extends GwtEvent<ClientDisconnectedHandler>
+{
+ public static final GwtEvent.Type<ClientDisconnectedHandler> TYPE =
+ new GwtEvent.Type<ClientDisconnectedHandler>();
+
+ @Override
+ protected void dispatch(ClientDisconnectedHandler handler)
+ {
+ handler.onClientDisconnected(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ClientDisconnectedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedHandler.java
new file mode 100644
index 0000000..c5f26f8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ClientDisconnectedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ClientDisconnectedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ClientDisconnectedHandler extends EventHandler
+{
+ void onClientDisconnected(ClientDisconnectedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/DeferredInitCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/DeferredInitCompletedEvent.java
new file mode 100644
index 0000000..fd23dc1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/DeferredInitCompletedEvent.java
@@ -0,0 +1,44 @@
+/*
+ * DeferredInitCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class DeferredInitCompletedEvent extends GwtEvent<DeferredInitCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onDeferredInitCompleted(DeferredInitCompletedEvent event);
+ }
+
+ public DeferredInitCompletedEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onDeferredInitCompleted(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/EventBus.java b/src/gwt/src/org/rstudio/studio/client/application/events/EventBus.java
new file mode 100644
index 0000000..30fb23f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/EventBus.java
@@ -0,0 +1,55 @@
+/*
+ * EventBus.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.logical.shared.AttachEvent;
+import com.google.gwt.event.logical.shared.AttachEvent.Handler;
+import com.google.gwt.event.logical.shared.HasAttachHandlers;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class EventBus extends HandlerManager
+{
+ public EventBus()
+ {
+ super(null) ;
+ }
+
+ /**
+ * Similar to 2-arg form of addHandler, but automatically removes handler
+ * when the HasAttachHandlers object detaches.
+ *
+ * If the HasAttachHandlers object detaches and reattaches, the handler
+ * will NOT automatically resubscribe.
+ */
+ public <H extends EventHandler> void addHandler(
+ HasAttachHandlers removeWhenDetached, Type<H> type, H handler)
+ {
+ final HandlerRegistration reg = addHandler(type, handler);
+ removeWhenDetached.addAttachHandler(new Handler()
+ {
+ @Override
+ public void onAttachOrDetach(AttachEvent event)
+ {
+ if (!event.isAttached())
+ reg.removeHandler();
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesEvent.java
new file mode 100644
index 0000000..2611257
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesEvent.java
@@ -0,0 +1,41 @@
+/*
+ * HandleUnsavedChangesEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class HandleUnsavedChangesEvent extends GwtEvent<HandleUnsavedChangesHandler>
+{
+ public static final GwtEvent.Type<HandleUnsavedChangesHandler> TYPE =
+ new GwtEvent.Type<HandleUnsavedChangesHandler>();
+
+ public HandleUnsavedChangesEvent()
+ {
+ }
+
+
+ @Override
+ protected void dispatch(HandleUnsavedChangesHandler handler)
+ {
+ handler.onHandleUnsavedChanges(this);
+ }
+
+ @Override
+ public GwtEvent.Type<HandleUnsavedChangesHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesHandler.java
new file mode 100644
index 0000000..240dc63
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/HandleUnsavedChangesHandler.java
@@ -0,0 +1,22 @@
+/*
+ * HandleUnsavedChangesHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface HandleUnsavedChangesHandler extends EventHandler
+{
+ void onHandleUnsavedChanges(HandleUnsavedChangesEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionEvent.java
new file mode 100644
index 0000000..0260cb2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionEvent.java
@@ -0,0 +1,35 @@
+/*
+ * InvalidClientVersionEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class InvalidClientVersionEvent extends GwtEvent<InvalidClientVersionHandler>
+{
+ public static final GwtEvent.Type<InvalidClientVersionHandler> TYPE =
+ new GwtEvent.Type<InvalidClientVersionHandler>();
+
+ @Override
+ protected void dispatch(InvalidClientVersionHandler handler)
+ {
+ handler.onInvalidClientVersion(this);
+ }
+
+ @Override
+ public GwtEvent.Type<InvalidClientVersionHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionHandler.java
new file mode 100644
index 0000000..26a6e2f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/InvalidClientVersionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * InvalidClientVersionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface InvalidClientVersionHandler extends EventHandler
+{
+ void onInvalidClientVersion(InvalidClientVersionEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedEvent.java
new file mode 100644
index 0000000..2ef61a2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * LogoutRequestedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class LogoutRequestedEvent extends GwtEvent<LogoutRequestedHandler>
+{
+ public static final GwtEvent.Type<LogoutRequestedHandler> TYPE =
+ new GwtEvent.Type<LogoutRequestedHandler>();
+
+ @Override
+ protected void dispatch(LogoutRequestedHandler handler)
+ {
+ handler.onLogoutRequested(this);
+ }
+
+ @Override
+ public GwtEvent.Type<LogoutRequestedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedHandler.java
new file mode 100644
index 0000000..b30069d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/LogoutRequestedHandler.java
@@ -0,0 +1,23 @@
+/*
+ * LogoutRequestedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+
+public interface LogoutRequestedHandler extends EventHandler
+{
+ void onLogoutRequested(LogoutRequestedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/QuitEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/QuitEvent.java
new file mode 100644
index 0000000..bcfa214
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/QuitEvent.java
@@ -0,0 +1,47 @@
+/*
+ * QuitEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class QuitEvent extends GwtEvent<QuitHandler>
+{
+ public static final GwtEvent.Type<QuitHandler> TYPE =
+ new GwtEvent.Type<QuitHandler>();
+
+ public QuitEvent(boolean switchProjects)
+ {
+ switchProjects_ = switchProjects;
+ }
+
+ public boolean getSwitchProjects()
+ {
+ return switchProjects_;
+ }
+
+ @Override
+ protected void dispatch(QuitHandler handler)
+ {
+ handler.onQuit(this);
+ }
+
+ @Override
+ public GwtEvent.Type<QuitHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final boolean switchProjects_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/QuitHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/QuitHandler.java
new file mode 100644
index 0000000..6666cad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/QuitHandler.java
@@ -0,0 +1,22 @@
+/*
+ * QuitHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface QuitHandler extends EventHandler
+{
+ void onQuit(QuitEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ReloadEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/ReloadEvent.java
new file mode 100644
index 0000000..854f149
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ReloadEvent.java
@@ -0,0 +1,44 @@
+/*
+ * ReloadEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ReloadEvent extends GwtEvent<ReloadEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onReload(ReloadEvent event);
+ }
+
+ public ReloadEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onReload(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/RestartStatusEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/RestartStatusEvent.java
new file mode 100644
index 0000000..8ac87a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/RestartStatusEvent.java
@@ -0,0 +1,55 @@
+/*
+ * RestartStatusEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class RestartStatusEvent extends GwtEvent<RestartStatusEvent.Handler>
+{
+ public final static int RESTART_INITIATED = 0;
+ public final static int RESTART_COMPLETED = 1;
+
+ public interface Handler extends EventHandler
+ {
+ void onRestartStatus(RestartStatusEvent event);
+ }
+
+ public RestartStatusEvent(int status)
+ {
+ status_ = status;
+ }
+
+ public int getStatus()
+ {
+ return status_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onRestartStatus(this);
+ }
+
+
+ private final int status_;
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedEvent.java
new file mode 100644
index 0000000..bf97bde
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedEvent.java
@@ -0,0 +1,50 @@
+/*
+ * SaveActionChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.rstudio.studio.client.application.model.SaveAction;
+
+public class SaveActionChangedEvent extends GwtEvent<SaveActionChangedHandler>
+{
+ public static final GwtEvent.Type<SaveActionChangedHandler> TYPE =
+ new GwtEvent.Type<SaveActionChangedHandler>();
+
+ public SaveActionChangedEvent(SaveAction action)
+ {
+ action_ = action;
+ }
+
+ public SaveAction getAction()
+ {
+ return action_;
+ }
+
+ @Override
+ protected void dispatch(SaveActionChangedHandler handler)
+ {
+ handler.onSaveActionChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SaveActionChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+
+ private SaveAction action_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedHandler.java
new file mode 100644
index 0000000..021f283
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SaveActionChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SaveActionChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SaveActionChangedHandler extends EventHandler
+{
+ void onSaveActionChanged(SaveActionChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineEvent.java
new file mode 100644
index 0000000..77fcd39
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineEvent.java
@@ -0,0 +1,41 @@
+/*
+ * ServerOfflineEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class ServerOfflineEvent extends GwtEvent<ServerOfflineHandler>
+{
+ public static final GwtEvent.Type<ServerOfflineHandler> TYPE =
+ new GwtEvent.Type<ServerOfflineHandler>();
+
+ public ServerOfflineEvent()
+ {
+ }
+
+
+ @Override
+ protected void dispatch(ServerOfflineHandler handler)
+ {
+ handler.onServerOffline(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ServerOfflineHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineHandler.java
new file mode 100644
index 0000000..a17da6e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ServerOfflineHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ServerOfflineHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ServerOfflineHandler extends EventHandler
+{
+ void onServerOffline(ServerOfflineEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableEvent.java
new file mode 100644
index 0000000..7acebd5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableEvent.java
@@ -0,0 +1,41 @@
+/*
+ * ServerUnavailableEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class ServerUnavailableEvent extends GwtEvent<ServerUnavailableHandler>
+{
+ public static final GwtEvent.Type<ServerUnavailableHandler> TYPE =
+ new GwtEvent.Type<ServerUnavailableHandler>();
+
+ public ServerUnavailableEvent()
+ {
+ }
+
+
+ @Override
+ protected void dispatch(ServerUnavailableHandler handler)
+ {
+ handler.onServerUnavailable(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ServerUnavailableHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableHandler.java
new file mode 100644
index 0000000..8d1492e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/ServerUnavailableHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ServerUnavailableHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ServerUnavailableHandler extends EventHandler
+{
+ void onServerUnavailable(ServerUnavailableEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningEvent.java
new file mode 100644
index 0000000..fd25c8a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningEvent.java
@@ -0,0 +1,36 @@
+/*
+ * SessionAbendWarningEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SessionAbendWarningEvent
+ extends GwtEvent<SessionAbendWarningHandler>
+{
+ public static final GwtEvent.Type<SessionAbendWarningHandler> TYPE =
+ new GwtEvent.Type<SessionAbendWarningHandler>();
+
+ @Override
+ protected void dispatch(SessionAbendWarningHandler handler)
+ {
+ handler.onSessionAbendWarning(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SessionAbendWarningHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningHandler.java
new file mode 100644
index 0000000..ffae084
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SessionAbendWarningHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SessionAbendWarningHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SessionAbendWarningHandler extends EventHandler
+{
+ void onSessionAbendWarning(SessionAbendWarningEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationEvent.java
new file mode 100644
index 0000000..a3ef7a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationEvent.java
@@ -0,0 +1,49 @@
+/*
+ * SessionSerializationEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.application.model.SessionSerializationAction;
+
+public class SessionSerializationEvent extends GwtEvent<SessionSerializationHandler>
+{
+ public static final GwtEvent.Type<SessionSerializationHandler> TYPE =
+ new GwtEvent.Type<SessionSerializationHandler>();
+
+ public SessionSerializationEvent(SessionSerializationAction action)
+ {
+ action_ = action;
+ }
+
+ public SessionSerializationAction getAction()
+ {
+ return action_;
+ }
+
+ @Override
+ protected void dispatch(SessionSerializationHandler handler)
+ {
+ handler.onSessionSerialization(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SessionSerializationHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+
+ private SessionSerializationAction action_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationHandler.java
new file mode 100644
index 0000000..79aa098
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SessionSerializationHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SessionSerializationHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SessionSerializationHandler extends EventHandler
+{
+ void onSessionSerialization(SessionSerializationEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SuicideEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/SuicideEvent.java
new file mode 100644
index 0000000..8af75ea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SuicideEvent.java
@@ -0,0 +1,48 @@
+/*
+ * SuicideEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class SuicideEvent extends GwtEvent<SuicideHandler>
+{
+ public static final GwtEvent.Type<SuicideHandler> TYPE =
+ new GwtEvent.Type<SuicideHandler>();
+
+ public SuicideEvent(String message)
+ {
+ message_ = message;
+ }
+
+ public String getMessage()
+ {
+ return message_;
+ }
+
+ @Override
+ protected void dispatch(SuicideHandler handler)
+ {
+ handler.onSuicide(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SuicideHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private String message_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SuicideHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/SuicideHandler.java
new file mode 100644
index 0000000..f9c393e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SuicideHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SuicideHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SuicideHandler extends EventHandler
+{
+ void onSuicide(SuicideEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartEvent.java
new file mode 100644
index 0000000..dc773ad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartEvent.java
@@ -0,0 +1,62 @@
+/*
+ * SuspendAndRestartEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import org.rstudio.studio.client.application.model.SuspendOptions;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SuspendAndRestartEvent extends GwtEvent<SuspendAndRestartHandler>
+{
+ public static final GwtEvent.Type<SuspendAndRestartHandler> TYPE =
+ new GwtEvent.Type<SuspendAndRestartHandler>();
+
+ public SuspendAndRestartEvent(SuspendOptions suspendOptions,
+ String afterRestartCommand)
+ {
+ suspendOptions_ = suspendOptions;
+ afterRestartCommand_ = afterRestartCommand;
+ }
+
+ public SuspendAndRestartEvent(String afterRestartCommand)
+ {
+ this(SuspendOptions.createSaveAll(false), afterRestartCommand);
+ }
+
+ public SuspendOptions getSuspendOptions()
+ {
+ return suspendOptions_;
+ }
+
+ public String getAfterRestartCommand()
+ {
+ return afterRestartCommand_;
+ }
+
+ @Override
+ protected void dispatch(SuspendAndRestartHandler handler)
+ {
+ handler.onSuspendAndRestart(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SuspendAndRestartHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final SuspendOptions suspendOptions_;
+ private final String afterRestartCommand_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartHandler.java
new file mode 100644
index 0000000..ba372af
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/SuspendAndRestartHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SuspendAndRestartHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SuspendAndRestartHandler extends EventHandler
+{
+ void onSuspendAndRestart(SuspendAndRestartEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedEvent.java b/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedEvent.java
new file mode 100644
index 0000000..f47189a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * UnauthorizedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class UnauthorizedEvent extends GwtEvent<UnauthorizedHandler>
+{
+ public static final GwtEvent.Type<UnauthorizedHandler> TYPE =
+ new GwtEvent.Type<UnauthorizedHandler>();
+
+ @Override
+ protected void dispatch(UnauthorizedHandler handler)
+ {
+ handler.onUnauthorized(this);
+ }
+
+ @Override
+ public GwtEvent.Type<UnauthorizedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedHandler.java b/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedHandler.java
new file mode 100644
index 0000000..6254f9c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/events/UnauthorizedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * UnauthorizedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface UnauthorizedHandler extends EventHandler
+{
+ void onUnauthorized(UnauthorizedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/ApplicationServerOperations.java b/src/gwt/src/org/rstudio/studio/client/application/model/ApplicationServerOperations.java
new file mode 100644
index 0000000..288a92b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/ApplicationServerOperations.java
@@ -0,0 +1,72 @@
+/*
+ * ApplicationServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.Agreement;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.PrefsServerOperations;
+
+public interface ApplicationServerOperations extends PrefsServerOperations
+{
+ // establish new session for this client
+ void clientInit(ServerRequestCallback<SessionInfo> requestCallback);
+
+ // interrupt the current session
+ void interrupt(ServerRequestCallback<Void> requestCallback);
+
+ // abort the current session
+ void abort(String nextSessionProject,
+ ServerRequestCallback<Void> requestCallback);
+
+ // agree to the application agreement
+ void acceptAgreement(Agreement agreement,
+ ServerRequestCallback<Void> requestCallback);
+
+ // suspend the current session
+ void suspendSession(boolean force,
+ ServerRequestCallback<Void> requestCallback) ;
+
+ // handle unsaved changes completed
+ void handleUnsavedChangesCompleted(
+ boolean handled,
+ ServerRequestCallback<Void> requestCallback);
+
+ // quit the current session
+ void quitSession(boolean saveWorkspace,
+ String switchToProjectPath,
+ ServerRequestCallback<Void> requestCallback);
+
+ // verify current credentials
+ void updateCredentials();
+
+ // get an application URL
+ String getApplicationURL(String pathName);
+
+ String getFileUrl(FileSystemItem file);
+
+ void suspendForRestart(SuspendOptions options,
+ ServerRequestCallback<Void> requestCallback);
+ void ping(ServerRequestCallback<Void> requestCallback);
+
+ public void checkForUpdates(
+ boolean manual,
+ ServerRequestCallback<UpdateCheckResult> requestCallback);
+
+ public void getProductInfo(
+ ServerRequestCallback<ProductInfo> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/HttpLogEntry.java b/src/gwt/src/org/rstudio/studio/client/application/model/HttpLogEntry.java
new file mode 100644
index 0000000..10cafc1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/HttpLogEntry.java
@@ -0,0 +1,72 @@
+/*
+ * HttpLogEntry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.Date;
+
+public class HttpLogEntry extends JavaScriptObject
+{
+ public final static int CONNECTION_RECEIVED = 1;
+ public final static int CONNECTION_DEQUEUED = 2;
+ public final static int CONNECTION_RESPONDED = 3;
+ public final static int CONNECTION_TERMINATED = 4;
+ public final static int CONNECTION_ERROR = 5;
+
+ protected HttpLogEntry()
+ {
+
+ }
+
+ public native final int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final String getTypeAsString()
+ {
+ switch(getType())
+ {
+ case CONNECTION_RECEIVED:
+ return "Connection Received";
+ case CONNECTION_DEQUEUED :
+ return "Connection Dequeued";
+ case CONNECTION_RESPONDED:
+ return "Connection Responded";
+ case CONNECTION_TERMINATED:
+ return "Connection Terminated";
+ case CONNECTION_ERROR:
+ return "Connection Error";
+ default:
+ return "(Unknown)";
+ }
+ }
+
+ public native final String getRequestId() /*-{
+ return this.id;
+ }-*/;
+
+
+ public final Date getTimestamp()
+ {
+ Double timestamp = new Double(getTimestampNative());
+ return new Date(timestamp.longValue());
+ }
+
+ private final native double getTimestampNative() /*-{
+ return this.ts;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/ProductInfo.java b/src/gwt/src/org/rstudio/studio/client/application/model/ProductInfo.java
new file mode 100644
index 0000000..e427139
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/ProductInfo.java
@@ -0,0 +1,30 @@
+/*
+ * ProductInfo.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ProductInfo extends JavaScriptObject
+{
+ protected ProductInfo() {}
+
+ public final native String getVersion() /*-{
+ return this.version;
+ }-*/;
+
+ public final native String getNotice() /*-{
+ return this.notice;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/SaveAction.java b/src/gwt/src/org/rstudio/studio/client/application/model/SaveAction.java
new file mode 100644
index 0000000..14e0319
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/SaveAction.java
@@ -0,0 +1,38 @@
+/*
+ * SaveAction.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SaveAction extends JavaScriptObject
+{
+ public final static int NOSAVE = 0;
+ public final static int SAVE = 1;
+ public final static int SAVEASK = -1;
+
+ protected SaveAction()
+ {
+ }
+
+ public native static final SaveAction saveAsk() /*-{
+ var saveAction = new Object();
+ saveAction.action = -1;
+ return saveAction
+ }-*/;
+
+ public native final int getAction() /*-{
+ return this.action;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/SessionSerializationAction.java b/src/gwt/src/org/rstudio/studio/client/application/model/SessionSerializationAction.java
new file mode 100644
index 0000000..1f95623
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/SessionSerializationAction.java
@@ -0,0 +1,39 @@
+/*
+ * SessionSerializationAction.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SessionSerializationAction extends JavaScriptObject
+{
+ public final static int SAVE_DEFAULT_WORKSPACE = 1;
+ public final static int LOAD_DEFAULT_WORKSPACE = 2;
+ public final static int SUSPEND_SESSION = 3;
+ public final static int RESUME_SESSION = 4;
+ public final static int COMPLETED = 5;
+
+ protected SessionSerializationAction()
+ {
+
+ }
+
+ public native final int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public native final String getTargetPath() /*-{
+ return this.targetPath;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/SuspendOptions.java b/src/gwt/src/org/rstudio/studio/client/application/model/SuspendOptions.java
new file mode 100644
index 0000000..b3d2010
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/SuspendOptions.java
@@ -0,0 +1,73 @@
+/*
+ * SuspendOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SuspendOptions extends JavaScriptObject
+{
+ protected SuspendOptions()
+ {
+ }
+
+ public static final SuspendOptions createSaveAll(boolean excludePackages)
+ {
+ return create(false, false, excludePackages);
+ }
+
+ public static final SuspendOptions createSaveMinimal(boolean saveWorkspace)
+ {
+ return create(true, saveWorkspace, false);
+ }
+
+ private static native final SuspendOptions create(boolean saveMinimal,
+ boolean saveWorkspace,
+ boolean excludePackages) /*-{
+ var options = new Object();
+ options.save_minimal = saveMinimal;
+ options.save_workspace = saveWorkspace;
+ options.exclude_packages = excludePackages;
+ return options;
+ }-*/;
+
+ /*
+ * Indidates that only a minimal amount of session state should be
+ * saved (e.g. working directory and up-arrow history).
+ *
+ * If this option is true then the save_workspace option will be
+ * consulted to determine whether the workspace should also be saved.
+ *
+ * If this option is false then the exclude_packages option will be
+ * consulted to determine whether to exclude packages
+ */
+ public native final boolean getSaveMinimal() /*-{
+ return this.save_minimal;
+ }-*/;
+
+ /*
+ * This option is only consulted if save_minimal is true
+ */
+ public native final boolean getSaveWorkspace() /*-{
+ return this.save_workspace;
+ }-*/;
+
+ /*
+ * This option is only consulted if save_minimal is false
+ */
+ public native final boolean getExcludePackages() /*-{
+ return this.exclude_packages;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/model/UpdateCheckResult.java b/src/gwt/src/org/rstudio/studio/client/application/model/UpdateCheckResult.java
new file mode 100644
index 0000000..e8669ec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/model/UpdateCheckResult.java
@@ -0,0 +1,38 @@
+/*
+ * UpdateCheckResult.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class UpdateCheckResult extends JavaScriptObject
+{
+ protected UpdateCheckResult() {}
+
+ public final native String getUpdateMessage() /*-{
+ return this['update-message'].trim();
+ }-*/;
+
+ public final native int getUpdateUrgency() /*-{
+ return parseInt(this['update-urgent']);
+ }-*/;
+
+ public final native String getUpdateUrl() /*-{
+ return this['update-url'].trim();
+ }-*/;
+
+ public final native String getUpdateVersion() /*-{
+ return this['update-version'].trim();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialog.java b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialog.java
new file mode 100644
index 0000000..a858c25
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialog.java
@@ -0,0 +1,50 @@
+/*
+ * AboutDialog.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application.ui;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.application.model.ProductInfo;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Widget;
+
+public class AboutDialog extends ModalDialogBase
+{
+ public AboutDialog(ProductInfo info)
+ {
+ setText("About RStudio");
+ ThemedButton OKButton = new ThemedButton("OK",
+ new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event) {
+ closeDialog();
+ }
+ });
+ addOkButton(OKButton);
+ contents_ = new AboutDialogContents(info);
+ setWidth("600px");
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return contents_;
+ }
+
+ private AboutDialogContents contents_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.java b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.java
new file mode 100644
index 0000000..81a6b74
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.java
@@ -0,0 +1,59 @@
+/*
+ * AboutDialogContents.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import org.rstudio.studio.client.application.model.ProductInfo;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.Widget;
+
+public class AboutDialogContents extends Composite
+{
+ public static void ensureStylesInjected()
+ {
+ new AboutDialogContents();
+ }
+
+ private static AboutDialogContentsUiBinder uiBinder = GWT
+ .create(AboutDialogContentsUiBinder.class);
+
+ interface AboutDialogContentsUiBinder extends
+ UiBinder<Widget, AboutDialogContents>
+ {
+ }
+
+ private AboutDialogContents()
+ {
+ uiBinder.createAndBindUi(this);
+ }
+
+ public AboutDialogContents(ProductInfo info)
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+ versionLabel.setText(info.getVersion());
+ userAgentLabel.setText(Window.Navigator.getUserAgent());
+ noticeBox.setValue(info.getNotice());
+ }
+
+ @UiField InlineLabel versionLabel;
+ @UiField InlineLabel userAgentLabel;
+ @UiField TextArea noticeBox;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.ui.xml
new file mode 100644
index 0000000..9fd3aee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/AboutDialogContents.ui.xml
@@ -0,0 +1,76 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:with field='res' type='org.rstudio.core.client.theme.res.ThemeResources' />
+ <ui:style>
+ @eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+ .aboutBox {
+ -webkit-user-select: auto;
+ }
+
+ .productName {
+ font-size: 20pt;
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ .productVersion {
+ margin-bottom: 5px;
+ }
+
+ .userAgent {
+ text-align: center;
+ font-size: 8pt;
+ margin-top: 10px;
+ margin-bottom: 15px;
+ }
+
+ .noticeBox {
+ clear: both;
+ width: 100%;
+ font-family: fixedWidthFont;
+ margin-top: 15px;
+ margin-bottom: 15px;
+ }
+
+ .logo {
+ float: left;
+ margin-right: 20px;
+ }
+
+ .productInfo {
+ text-align: left;
+ overflow: hidden;
+ margin-left: auto;
+ margin-right: auto;
+ width: 65%;
+ }
+
+ .outerProductInfo {
+ text-align: center;
+ }
+ </ui:style>
+ <g:HTMLPanel styleName="{style.aboutBox}">
+ <g:HTMLPanel styleName="{style.outerProductInfo}">
+ <g:HTMLPanel styleName="{style.productInfo}">
+ <g:Image resource='{res.rstudio}' styleName="{style.logo}"/>
+ <g:Label text="RStudio" styleName="{style.productName}"></g:Label>
+ <g:HTMLPanel styleName="{style.productVersion}">
+ <g:InlineLabel text="Version "></g:InlineLabel>
+ <g:InlineLabel ui:field="versionLabel"></g:InlineLabel>
+ <g:InlineLabel text="– © 2009-2013 RStudio, Inc."></g:InlineLabel>
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+ <g:HTMLPanel styleName="{style.userAgent}">
+ <g:InlineLabel ui:field="userAgentLabel"></g:InlineLabel>
+ </g:HTMLPanel>
+ <g:InlineLabel
+ text="Unless you have received this program directly from RStudio pursuant to the terms of a commercial license agreement with RStudio, then this program is licensed to you under the terms of version 3 of the GNU"></g:InlineLabel>
+ <g:Anchor href="http://www.gnu.org/licenses/agpl-3.0.txt"
+ text="Affero General Public License."
+ target="_blank"></g:Anchor>
+ <g:TextArea ui:field="noticeBox" styleName="{style.noticeBox}"
+ visibleLines="15" readOnly="true"></g:TextArea>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationAgreementDialog.java b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationAgreementDialog.java
new file mode 100644
index 0000000..0352e8e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationAgreementDialog.java
@@ -0,0 +1,67 @@
+/*
+ * ApplicationAgreementDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ShowContentDialog;
+import org.rstudio.core.client.widget.ThemedButton;
+
+public class ApplicationAgreementDialog extends ShowContentDialog
+{
+ public ApplicationAgreementDialog(String title,
+ String contents,
+ Operation doNotAcceptOperation,
+ Operation acceptOperation)
+ {
+ super(title,
+ contents,
+ new Size(800, 1000));
+
+ doNotAcceptOperation_ = doNotAcceptOperation;
+ acceptOperation_ = acceptOperation ;
+ }
+
+ @Override
+ protected void addButtons()
+ {
+ // default button is do not accept
+ ThemedButton doNotAcceptButton = new ThemedButton("I Do Not Agree",
+ new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ closeDialog();
+ doNotAcceptOperation_.execute();
+ }
+ });
+ addOkButton(doNotAcceptButton);
+
+ // accept button
+ ThemedButton acceptButton = new ThemedButton("I Agree",
+ new ClickHandler() {
+
+ public void onClick(ClickEvent event)
+ {
+ closeDialog();
+ acceptOperation_.execute();
+ }
+ });
+ addButton(acceptButton);
+ }
+
+ private Operation doNotAcceptOperation_;
+ private Operation acceptOperation_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationHeader.java b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationHeader.java
new file mode 100644
index 0000000..f24d2f8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationHeader.java
@@ -0,0 +1,27 @@
+/*
+ * ApplicationHeader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface ApplicationHeader extends IsWidget
+{
+ int getPreferredHeight();
+
+ void showToolbar(boolean showToolbar);
+ boolean isToolbarVisible();
+
+ void focusGoToFunction();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationWindow.java b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationWindow.java
new file mode 100644
index 0000000..b82f8df
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/ApplicationWindow.java
@@ -0,0 +1,290 @@
+/*
+ * ApplicationWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.ApplicationView;
+import org.rstudio.studio.client.application.ui.appended.ApplicationEndedPopupPanel;
+import org.rstudio.studio.client.application.ui.serializationprogress.ApplicationSerializationProgress;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.codesearch.ui.CodeSearchDialog;
+
+ at Singleton
+public class ApplicationWindow extends Composite
+ implements ApplicationView,
+ RequiresResize,
+ ProvidesResize
+{
+ @Inject
+ public ApplicationWindow(ApplicationHeader applicationHeader,
+ GlobalDisplay globalDisplay,
+ Provider<CodeSearch> pCodeSearch)
+ {
+ globalDisplay_ = globalDisplay;
+ pCodeSearch_ = pCodeSearch;
+
+ // occupy full client area of the window
+ Window.enableScrolling(false);
+ Window.setMargin("0px");
+
+ // app ui contained within a vertical panel
+ applicationPanel_ = new LayoutPanel();
+
+ // header bar
+ applicationHeader_ = applicationHeader;
+ Widget applicationHeaderWidget = applicationHeader_.asWidget();
+ applicationHeaderWidget.setWidth("100%");
+ applicationPanel_.add(applicationHeader_);
+ updateHeaderTopBottom();
+ applicationHeaderWidget.setVisible(false);
+
+ // main view container
+ initWidget(applicationPanel_);
+ }
+
+ public void showToolbar(boolean showToolbar)
+ {
+ applicationHeader_.showToolbar(showToolbar);
+ updateHeaderTopBottom();
+ updateWorkbenchTopBottom();
+ applicationPanel_.forceLayout();
+ }
+
+ public void performGoToFunction()
+ {
+ new CodeSearchDialog(pCodeSearch_).showModal();
+ }
+
+ public void showApplicationAgreement(String title,
+ String contents,
+ Operation doNotAcceptOperation,
+ Operation acceptOperation)
+ {
+ new ApplicationAgreementDialog(title,
+ contents,
+ doNotAcceptOperation,
+ acceptOperation).showModal();
+ }
+
+ public Widget getWidget()
+ {
+ return this ;
+ }
+
+ public void showApplicationQuit()
+ {
+ ApplicationEndedPopupPanel.showQuit();
+ }
+
+ public void showApplicationSuicide(String reason)
+ {
+ ApplicationEndedPopupPanel.showSuicide(reason);
+ }
+
+ public void showApplicationDisconnected()
+ {
+ ApplicationEndedPopupPanel.showDisconnected();
+ }
+
+ public void showApplicationOffline()
+ {
+ ApplicationEndedPopupPanel.showOffline();
+ }
+
+ public void showApplicationUpdateRequired()
+ {
+ globalDisplay_.showMessage(
+ GlobalDisplay.MSG_INFO,
+ "Application Updated",
+ "An updated version of RStudio is available. Your browser will " +
+ "now be refreshed with the new version. All current work and data " +
+ "will be preserved during the update.",
+ new Operation() {
+ public void execute()
+ {
+ Window.Location.reload();
+ }
+
+ });
+ }
+
+ public void showWorkbenchView(Widget workbenchScreen)
+ {
+ workbenchScreen_ = workbenchScreen;
+
+ applicationHeader_.asWidget().setVisible(true);
+ applicationPanel_.add(workbenchScreen_);
+ updateWorkbenchTopBottom();
+ applicationPanel_.setWidgetLeftRight(workbenchScreen_,
+ COMPONENT_SPACING,
+ Style.Unit.PX,
+ COMPONENT_SPACING,
+ Style.Unit.PX);
+ }
+
+ public void showWarning(boolean severe, String message)
+ {
+ if (warningBar_ == null)
+ {
+ warningBar_ = new WarningBar();
+ warningBar_.addCloseHandler(new CloseHandler<WarningBar>()
+ {
+ public void onClose(CloseEvent<WarningBar> warningBarCloseEvent)
+ {
+ hideWarning();
+ }
+ });
+ applicationPanel_.add(warningBar_);
+ applicationPanel_.setWidgetBottomHeight(warningBar_,
+ COMPONENT_SPACING,
+ Unit.PX,
+ warningBar_.getHeight(),
+ Unit.PX);
+ applicationPanel_.setWidgetLeftRight(warningBar_,
+ COMPONENT_SPACING, Unit.PX,
+ COMPONENT_SPACING, Unit.PX);
+
+ workbenchBottom_ = COMPONENT_SPACING*2 + warningBar_.getHeight();
+ if (workbenchScreen_ != null)
+ updateWorkbenchTopBottom();
+
+ applicationPanel_.animate(250);
+ }
+ warningBar_.setSeverity(severe);
+ warningBar_.setText(message);
+ }
+
+ private void updateHeaderTopBottom()
+ {
+ int headerHeight = applicationHeader_.getPreferredHeight();
+ applicationPanel_.setWidgetTopHeight(applicationHeader_,
+ 0,
+ Style.Unit.PX,
+ headerHeight,
+ Style.Unit.PX);
+ applicationPanel_.setWidgetLeftRight(applicationHeader_,
+ 0,
+ Style.Unit.PX,
+ 0,
+ Style.Unit.PX);
+ }
+
+ private void updateWorkbenchTopBottom()
+ {
+ applicationPanel_.setWidgetTopBottom(
+ workbenchScreen_,
+ applicationHeader_.getPreferredHeight(),
+ Unit.PX,
+ workbenchBottom_,
+ Unit.PX);
+ }
+
+ public void hideWarning()
+ {
+ if (warningBar_ != null)
+ {
+ applicationPanel_.remove(warningBar_);
+ warningBar_ = null;
+
+ workbenchBottom_ = COMPONENT_SPACING;
+ if (workbenchScreen_ != null)
+ updateWorkbenchTopBottom();
+
+ applicationPanel_.animate(250);
+ }
+ }
+
+ public void showSessionAbendWarning()
+ {
+ globalDisplay_.showErrorMessage(
+ "R Session Error",
+ "The previous R session was abnormally terminated due to " +
+ "an unexpected crash.\n\n" +
+ "You may have lost workspace data as a result of this crash.");
+ }
+
+ public void showSerializationProgress(String msg,
+ boolean modal,
+ int delayMs,
+ int timeoutMs)
+ {
+ // hide any existing progress
+ hideSerializationProgress();
+
+ // create and show progress
+ activeSerializationProgress_ =
+ new ApplicationSerializationProgress(msg, modal, delayMs);
+
+ // implement timeout for *this* serialization progress instance if
+ // requested (check to ensure the same instance because another
+ // serialization progress could occur in the meantime and we don't
+ // want to hide it)
+ if (timeoutMs > 0)
+ {
+ final ApplicationSerializationProgress timeoutSerializationProgress =
+ activeSerializationProgress_;
+ new Timer() {
+ @Override
+ public void run()
+ {
+ if (timeoutSerializationProgress == activeSerializationProgress_)
+ hideSerializationProgress();
+ }
+ }.schedule(timeoutMs);
+ }
+ }
+
+ public void hideSerializationProgress()
+ {
+ if (activeSerializationProgress_ != null)
+ {
+ activeSerializationProgress_.hide();
+ activeSerializationProgress_ = null;
+ }
+ }
+
+ public void onResize()
+ {
+ applicationPanel_.onResize();
+ }
+
+ // main applilcation UI components
+ private LayoutPanel applicationPanel_ ;
+ private ApplicationHeader applicationHeader_ ;
+
+ // active serialization progress message
+ private ApplicationSerializationProgress activeSerializationProgress_;
+
+
+
+ private static final int COMPONENT_SPACING = 6;
+ private Widget workbenchScreen_;
+ private WarningBar warningBar_;
+ private int workbenchBottom_ = COMPONENT_SPACING;
+ private final GlobalDisplay globalDisplay_;
+ private final Provider<CodeSearch> pCodeSearch_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/GlobalToolbar.java b/src/gwt/src/org/rstudio/studio/client/application/ui/GlobalToolbar.java
new file mode 100644
index 0000000..59dfd33
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/GlobalToolbar.java
@@ -0,0 +1,200 @@
+/*
+ * GlobalToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.FocusContext;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Provider;
+
+
+public class GlobalToolbar extends Toolbar
+{
+ public GlobalToolbar(Commands commands,
+ EventBus eventBus,
+ Provider<CodeSearch> pCodeSearch)
+ {
+ super();
+ commands_ = commands;
+ pCodeSearch_ = pCodeSearch;
+ ThemeResources res = ThemeResources.INSTANCE;
+ addStyleName(res.themeStyles().globalToolbar());
+
+
+ // add new source doc commands
+ newMenu_ = new ToolbarPopupMenu();
+ newMenu_.addItem(commands.newSourceDoc().createMenuItem(false));
+ newMenu_.addSeparator();
+ newMenu_.addItem(commands.newTextDoc().createMenuItem(false));
+ newMenu_.addItem(commands.newCppDoc().createMenuItem(false));
+ newMenu_.addSeparator();
+ newMenu_.addItem(commands.newSweaveDoc().createMenuItem(false));
+ newMenu_.addItem(commands.newRMarkdownDoc().createMenuItem(false));
+ newMenu_.addItem(commands.newRHTMLDoc().createMenuItem(false));
+ newMenu_.addItem(commands.newRPresentationDoc().createMenuItem(false));
+ newMenu_.addSeparator();
+ newMenu_.addItem(commands.newRDocumentationDoc().createMenuItem(false));
+
+
+ // create and add new menu
+ StandardIcons icons = StandardIcons.INSTANCE;
+ ToolbarButton newButton = new ToolbarButton("",
+ icons.stock_new(),
+ newMenu_);
+ addLeftWidget(newButton);
+ addLeftSeparator();
+
+ // open button + mru
+ addLeftWidget(commands.openSourceDoc().createToolbarButton());
+
+ ToolbarPopupMenu mruMenu = new ToolbarPopupMenu();
+ mruMenu.addItem(commands.mru0().createMenuItem(false));
+ mruMenu.addItem(commands.mru1().createMenuItem(false));
+ mruMenu.addItem(commands.mru2().createMenuItem(false));
+ mruMenu.addItem(commands.mru3().createMenuItem(false));
+ mruMenu.addItem(commands.mru4().createMenuItem(false));
+ mruMenu.addItem(commands.mru5().createMenuItem(false));
+ mruMenu.addItem(commands.mru6().createMenuItem(false));
+ mruMenu.addItem(commands.mru7().createMenuItem(false));
+ mruMenu.addItem(commands.mru8().createMenuItem(false));
+ mruMenu.addItem(commands.mru9().createMenuItem(false));
+ mruMenu.addSeparator();
+ mruMenu.addItem(commands.clearRecentFiles().createMenuItem(false));
+
+ ToolbarButton mruButton = new ToolbarButton(mruMenu, false);
+ mruButton.setTitle("Open recent files");
+ addLeftWidget(mruButton);
+ addLeftSeparator();
+
+
+ addLeftWidget(commands.saveSourceDoc().createToolbarButton());
+ addLeftWidget(commands.saveAllSourceDocs().createToolbarButton());
+ addLeftSeparator();
+
+ addLeftWidget(commands.printSourceDoc().createToolbarButton());
+
+ addLeftSeparator();
+ CodeSearch codeSearch = pCodeSearch_.get();
+ codeSearch.setObserver(new CodeSearch.Observer() {
+ @Override
+ public void onCancel()
+ {
+ codeSearchFocusContext_.restore();
+ }
+
+ @Override
+ public void onCompleted()
+ {
+ codeSearchFocusContext_.clear();
+ }
+
+ @Override
+ public String getCueText()
+ {
+ return null;
+ }
+ });
+
+ searchWidget_ = codeSearch.getSearchWidget();
+ addLeftWidget(searchWidget_);
+ }
+
+ public void completeInitialization(SessionInfo sessionInfo)
+ {
+ StandardIcons icons = StandardIcons.INSTANCE;
+
+ if (sessionInfo.isVcsEnabled())
+ {
+ addLeftSeparator();
+
+ ToolbarPopupMenu vcsMenu = new ToolbarPopupMenu();
+ vcsMenu.addItem(commands_.vcsFileDiff().createMenuItem(false));
+ vcsMenu.addItem(commands_.vcsFileLog().createMenuItem(false));
+ vcsMenu.addItem(commands_.vcsFileRevert().createMenuItem(false));
+ vcsMenu.addSeparator();
+ vcsMenu.addItem(commands_.vcsViewOnGitHub().createMenuItem(false));
+ vcsMenu.addItem(commands_.vcsBlameOnGitHub().createMenuItem(false));
+ vcsMenu.addSeparator();
+ vcsMenu.addItem(commands_.vcsCommit().createMenuItem(false));
+ vcsMenu.addSeparator();
+ vcsMenu.addItem(commands_.vcsPull().createMenuItem(false));
+ vcsMenu.addItem(commands_.vcsCleanup().createMenuItem(false));
+ vcsMenu.addItem(commands_.vcsPush().createMenuItem(false));
+ vcsMenu.addSeparator();
+ vcsMenu.addItem(commands_.vcsShowHistory().createMenuItem(false));
+ vcsMenu.addSeparator();
+ vcsMenu.addItem(commands_.versionControlProjectSetup().createMenuItem(false));
+
+ ImageResource vcsIcon = null;
+ if (sessionInfo.getVcsName().equals(VCSConstants.GIT_ID))
+ vcsIcon = icons.git();
+ else if (sessionInfo.getVcsName().equals(VCSConstants.SVN_ID))
+ vcsIcon = icons.svn();
+
+ ToolbarButton vcsButton = new ToolbarButton(
+ null,
+ vcsIcon,
+ vcsMenu);
+ vcsButton.setTitle("Version control");
+ addLeftWidget(vcsButton);
+ }
+
+ if (sessionInfo.getShinyappsInstalled())
+ {
+ addLeftSeparator();
+ ToolbarButton deployButton =
+ commands_.shinyAppsDeploy().createToolbarButton();
+ deployButton.setText("Deploy App");
+ addLeftWidget(deployButton);
+ }
+
+ // project popup menu
+ ProjectPopupMenu projectMenu = new ProjectPopupMenu(sessionInfo,
+ commands_);
+ addRightWidget(projectMenu.getToolbarButton());
+ }
+
+ @Override
+ public int getHeight()
+ {
+ return 27;
+ }
+
+ public void focusGoToFunction()
+ {
+ codeSearchFocusContext_.record();
+ FocusHelper.setFocusDeferred((CanFocus)searchWidget_);
+ }
+
+ private final Commands commands_;
+ private final ToolbarPopupMenu newMenu_;
+ private final Provider<CodeSearch> pCodeSearch_;
+ private final Widget searchWidget_;
+ private final FocusContext codeSearchFocusContext_ = new FocusContext();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/ProjectPopupMenu.java b/src/gwt/src/org/rstudio/studio/client/application/ui/ProjectPopupMenu.java
new file mode 100644
index 0000000..099aa16
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/ProjectPopupMenu.java
@@ -0,0 +1,105 @@
+/*
+ * ProjectPopupMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.projects.ProjectMRUList;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.inject.Inject;
+
+public class ProjectPopupMenu extends ToolbarPopupMenu
+{
+ public ProjectPopupMenu(SessionInfo sessionInfo, Commands commands)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ addItem(commands.newProject().createMenuItem(false));
+ addSeparator();
+ addItem(commands.openProject().createMenuItem(false));
+ addItem(commands.openProjectInNewWindow().createMenuItem(false));
+ addSeparator();
+ addItem(commands.projectMru0().createMenuItem(false));
+ addItem(commands.projectMru1().createMenuItem(false));
+ addItem(commands.projectMru2().createMenuItem(false));
+ addItem(commands.projectMru3().createMenuItem(false));
+ addItem(commands.projectMru4().createMenuItem(false));
+ addItem(commands.projectMru5().createMenuItem(false));
+ addItem(commands.projectMru6().createMenuItem(false));
+ addItem(commands.projectMru7().createMenuItem(false));
+ addItem(commands.projectMru8().createMenuItem(false));
+ addItem(commands.projectMru9().createMenuItem(false));
+ addSeparator();
+ addItem(commands.closeProject().createMenuItem(false));
+ addSeparator();
+ addItem(commands.projectOptions().createMenuItem(false));
+
+ activeProjectFile_ = sessionInfo.getActiveProjectFile();
+
+
+
+ }
+
+ @Inject
+ void initialize(ProjectMRUList mruList)
+ {
+ mruList_ = mruList;
+ }
+
+ public ToolbarButton getToolbarButton()
+ {
+ if (toolbarButton_ == null)
+ {
+ String buttonText = activeProjectFile_ != null ?
+ mruList_.getQualifiedLabel(activeProjectFile_) :
+ "Project: (None)";
+
+ toolbarButton_ = new ToolbarButton(
+ buttonText,
+ RESOURCES.projectMenu(),
+ this,
+ true);
+
+ if (activeProjectFile_ != null)
+ toolbarButton_.setTitle(activeProjectFile_);
+
+ if (activeProjectFile_ == null)
+ {
+ toolbarButton_.addStyleName(
+ ThemeResources.INSTANCE.themeStyles().emptyProjectMenu());
+ }
+ }
+
+ return toolbarButton_;
+ }
+
+ interface Resources extends ClientBundle
+ {
+ ImageResource projectMenu();
+ }
+
+ private static final Resources RESOURCES =
+ (Resources) GWT.create(Resources.class);
+ private final String activeProjectFile_;
+ private ToolbarButton toolbarButton_ = null;
+ private ProjectMRUList mruList_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogDetail.java b/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogDetail.java
new file mode 100644
index 0000000..0d6d5a3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogDetail.java
@@ -0,0 +1,47 @@
+/*
+ * RequestLogDetail.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry;
+
+public class RequestLogDetail extends Composite
+{
+ public RequestLogDetail(RequestLogEntry entry)
+ {
+ String req = entry.getRequestData();
+ String resp = entry.getResponseData();
+
+ final FlowPanel panel = new FlowPanel();
+ panel.getElement().getStyle().setOverflow(Overflow.AUTO);
+
+ HTML html = new HTML();
+ html.setText("Request ID: " + entry.getRequestId() + "\n\n"
+ + "== REQUEST ======\n"
+ + req
+ + "\n\n"
+ + "== RESPONSE ======\n"
+ + resp
+ + "\n");
+ html.getElement().getStyle().setProperty("whiteSpace", "pre-wrap");
+
+ panel.add(html);
+
+ initWidget(panel);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogVisualization.java b/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogVisualization.java
new file mode 100644
index 0000000..d439673
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/RequestLogVisualization.java
@@ -0,0 +1,352 @@
+/*
+ * RequestLogVisualization.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.CsvReader;
+import org.rstudio.core.client.CsvWriter;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.jsonrpc.RequestLog;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry.ResponseType;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ScrollPanelWithClick;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class RequestLogVisualization extends Composite
+ implements HasCloseHandlers<RequestLogVisualization>, NativePreviewHandler
+{
+ private class TextBoxDialog extends ModalDialog<String>
+ {
+ private TextBoxDialog(String caption,
+ String initialValue,
+ OperationWithInput<String> operation)
+ {
+ super(caption, operation);
+ textArea_ = new TextArea();
+ textArea_.setSize("400px", "300px");
+ textArea_.setText(initialValue);
+ }
+
+ @Override
+ protected String collectInput()
+ {
+ return textArea_.getText();
+ }
+
+ @Override
+ protected boolean validate(String input)
+ {
+ return true;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return textArea_;
+ }
+
+ private final TextArea textArea_;
+ }
+
+ public RequestLogVisualization()
+ {
+ overviewPanel_ = new LayoutPanel();
+ overviewPanel_.getElement().getStyle().setProperty("borderRight",
+ "2px dashed #888");
+ scrollPanel_ = new ScrollPanelWithClick(overviewPanel_);
+ scrollPanel_.setSize("100%", "100%");
+ scrollPanel_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ detail_.setWidget(instructions_);
+ }
+ });
+
+
+ SplitLayoutPanel outerPanel = new SplitLayoutPanel();
+ outerPanel.getElement().getStyle().setBackgroundColor("white");
+ outerPanel.getElement().getStyle().setZIndex(500);
+ outerPanel.getElement().getStyle().setOpacity(0.9);
+
+ detail_ = new SimplePanel();
+ detail_.getElement().getStyle().setBackgroundColor("#FFE");
+
+ instructions_ = new HTML();
+ instructions_.setHTML("<p>Click on a request to see details. Click on the " +
+ "background to show these instructions again.</p>" +
+ "<h4>Available commands:</h4>" +
+ "<ul>" +
+ "<li>Esc: Close</li>" +
+ "<li>P: Play/pause</li>" +
+ "<li>E: Export</li>" +
+ "<li>I: Import</li>" +
+ "<li>+/-: Zoom in/out</li>" +
+ "</ul>");
+ detail_.setWidget(instructions_);
+
+ outerPanel.addSouth(detail_, 200);
+ outerPanel.add(scrollPanel_);
+
+ initWidget(outerPanel);
+
+ handlerRegistration_ = Event.addNativePreviewHandler(this);
+
+ timer_ = new Timer() {
+ @Override
+ public void run()
+ {
+ refresh(true, false);
+ }
+ };
+
+ refresh(true, true);
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ timer_.cancel();
+ super.onUnload();
+ }
+
+ private void refresh(boolean reloadEntries, boolean scrollToEnd)
+ {
+ if (reloadEntries)
+ {
+ entries_ = RequestLog.getEntries();
+ now_ = System.currentTimeMillis();
+ }
+
+ overviewPanel_.clear();
+
+ startTime_ = entries_[0].getRequestTime();
+ long duration = now_ - startTime_;
+ int totalWidth = (int) (duration * scaleMillisToPixels_);
+ totalHeight_ = entries_.length * BAR_HEIGHT;
+
+ overviewPanel_.setSize(totalWidth + "px", totalHeight_ + "px");
+
+ for (int i = 0, entriesLength = entries_.length; i < entriesLength; i++)
+ {
+ RequestLogEntry entry = entries_[i];
+ addEntry(i, entry);
+ }
+
+ if (scrollToEnd)
+ {
+ scrollPanel_.scrollToTop();
+ scrollPanel_.scrollToRight();
+ }
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ scrollPanel_.scrollToTop();
+ scrollPanel_.scrollToRight();
+ }
+ });
+ }
+
+ private void addEntry(int i, final RequestLogEntry entry)
+ {
+ int top = totalHeight_ - (i+1) * BAR_HEIGHT;
+ int left = (int) ((entry.getRequestTime() - startTime_) * scaleMillisToPixels_);
+ long endTime = entry.getResponseTime() != null
+ ? entry.getResponseTime()
+ : now_;
+ int right = Math.max(0, (int) ((now_ - endTime) * scaleMillisToPixels_) - 1);
+
+ boolean active = entry.getResponseType() == ResponseType.None;
+
+ HTML html = new HTML();
+ html.getElement().getStyle().setOverflow(Overflow.VISIBLE);
+ html.getElement().getStyle().setProperty("whiteSpace", "nowrap");
+ html.setText(entry.getRequestMethodName() + (active ? " (active)" : ""));
+ if (active)
+ html.getElement().getStyle().setFontWeight(FontWeight.BOLD);
+ String color;
+ switch (entry.getResponseType())
+ {
+ case ResponseType.Error:
+ color = "red";
+ break;
+ case ResponseType.None:
+ color = "#f99";
+ break;
+ case ResponseType.Normal:
+ color = "#88f";
+ break;
+ case ResponseType.Cancelled:
+ color = "#E0E0E0";
+ break;
+ case ResponseType.Unknown:
+ default:
+ color = "yellow";
+ break;
+ }
+ html.getElement().getStyle().setBackgroundColor(color);
+ html.getElement().getStyle().setCursor(Cursor.POINTER);
+
+ html.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ event.stopPropagation();
+ detail_.clear();
+ RequestLogDetail entryDetail = new RequestLogDetail(entry);
+ entryDetail.setSize("100%", "100%");
+ detail_.setWidget(entryDetail);
+ }
+ });
+
+ overviewPanel_.add(html);
+ overviewPanel_.setWidgetTopHeight(html, top, Unit.PX, BAR_HEIGHT, Unit.PX);
+ overviewPanel_.setWidgetLeftRight(html, left, Unit.PX, right, Unit.PX);
+ overviewPanel_.getWidgetContainerElement(html).getStyle().setOverflow(Overflow.VISIBLE);
+ }
+
+ public HandlerRegistration addCloseHandler(CloseHandler<RequestLogVisualization> handler)
+ {
+ return addHandler(handler, CloseEvent.getType());
+ }
+
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ int keyCode = event.getNativeEvent().getKeyCode();
+ if (keyCode == KeyCodes.KEY_ESCAPE)
+ {
+ CloseEvent.fire(RequestLogVisualization.this,
+ RequestLogVisualization.this);
+ handlerRegistration_.removeHandler();
+ }
+ else if (keyCode == 'R'
+ && KeyboardShortcut.getModifierValue(event.getNativeEvent()) == 0)
+ {
+ refresh(true, true);
+ }
+ else if (keyCode == 'P')
+ {
+ if (timerIsRunning_)
+ timer_.cancel();
+ else
+ {
+ timer_.run();
+ timer_.scheduleRepeating(PERIOD_MILLIS);
+ }
+ timerIsRunning_ = !timerIsRunning_;
+ }
+ else if (keyCode == 'E')
+ {
+ CsvWriter writer = new CsvWriter();
+ writer.writeValue(now_ + "");
+ writer.endLine();
+ for (RequestLogEntry entry : entries_)
+ entry.toCsv(writer);
+
+ TextBoxDialog dialog = new TextBoxDialog("Export",
+ writer.getValue(),
+ null);
+ dialog.showModal();
+ }
+ else if (keyCode == 'I')
+ {
+ TextBoxDialog dialog = new TextBoxDialog(
+ "Import",
+ "",
+ new OperationWithInput<String>()
+ {
+ public void execute(String input)
+ {
+ CsvReader reader = new CsvReader(input);
+ ArrayList<RequestLogEntry> entries = new ArrayList<RequestLogEntry>();
+ Iterator<String[]> it = reader.iterator();
+ String now = it.next()[0];
+ while (it.hasNext())
+ {
+ String[] line = it.next();
+ RequestLogEntry entry =
+ RequestLogEntry.fromValues(line);
+ if (entry != null)
+ entries.add(entry);
+ }
+ now_ = Long.parseLong(now);
+ entries_ = entries.toArray(new RequestLogEntry[0]);
+ refresh(false, true);
+ }
+ });
+ dialog.showModal();
+ }
+ }
+ else if (event.getTypeInt() == Event.ONKEYPRESS)
+ {
+ if (event.getNativeEvent().getKeyCode() == '+')
+ {
+ scaleMillisToPixels_ *= 2.0;
+ refresh(false, false);
+ }
+ else if (event.getNativeEvent().getKeyCode() == '-')
+ {
+ scaleMillisToPixels_ /= 2.0;
+ refresh(false, false);
+ }
+ }
+ }
+
+
+ private static final int BAR_HEIGHT = 15;
+ private double scaleMillisToPixels_ = 0.02;
+ private long now_;
+ private RequestLogEntry[] entries_;
+ private int totalHeight_;
+ private LayoutPanel overviewPanel_;
+ private long startTime_;
+ private ScrollPanelWithClick scrollPanel_;
+ private HandlerRegistration handlerRegistration_;
+ private Timer timer_;
+ private boolean timerIsRunning_;
+ private static final int PERIOD_MILLIS = 2000;
+ private SimplePanel detail_;
+ private HTML instructions_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.css b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.css
new file mode 100644
index 0000000..09adce4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.css
@@ -0,0 +1,39 @@
+.warning, .error {
+
+}
+
+.warningBar {
+ margin-left: 1px;
+ margin-right: 1px;
+}
+
+ at sprite .left {
+ gwt-image: 'warningBarLeft';
+}
+
+ at sprite .right {
+ gwt-image: 'warningBarRight';
+}
+
+ at sprite .center {
+ gwt-image: 'warningBarTile';
+ white-space: nowrap;
+ overflow-x: hidden;
+}
+
+.warningIcon {
+ position: relative;
+ top: 4px;
+ margin-left: 2px;
+ filter: literal("alpha(opacity = 50)") !important;
+ opacity: 0.5; /* non-IE */
+}
+
+.label {
+ position: relative;
+}
+
+.dismiss {
+ cursor: pointer;
+ margin-right: 2px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.java b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.java
new file mode 100644
index 0000000..8db1a02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.java
@@ -0,0 +1,120 @@
+/*
+ * WarningBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Widget;
+
+public class WarningBar extends Composite
+ implements HasCloseHandlers<WarningBar>
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("WarningBar.css")
+ Styles styles();
+
+ ImageResource warningBarLeft();
+ ImageResource warningBarRight();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource warningBarTile();
+ ImageResource warningIconSmall();
+ }
+
+ interface Styles extends CssResource
+ {
+ String warningBar();
+ String left();
+ String right();
+ String center();
+ String warningIcon();
+ String label();
+ String dismiss();
+
+ String warning();
+ String error();
+ }
+
+ interface Binder extends UiBinder<Widget, WarningBar>{}
+ static final Binder binder = GWT.create(Binder.class);
+
+ public WarningBar()
+ {
+ initWidget(binder.createAndBindUi(this));
+ dismiss_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ CloseEvent.fire(WarningBar.this, WarningBar.this);
+ }
+ });
+ }
+
+ public void setText(String value)
+ {
+ label_.setInnerText(value);
+ }
+
+ public void setSeverity(boolean severe)
+ {
+ if (severe)
+ {
+ addStyleName(styles_.error());
+ removeStyleName(styles_.warning());
+ }
+ else
+ {
+ addStyleName(styles_.warning());
+ removeStyleName(styles_.error());
+ }
+ }
+
+ public int getHeight()
+ {
+ return 28;
+ }
+
+ public HandlerRegistration addCloseHandler(CloseHandler<WarningBar> handler)
+ {
+ return addHandler(handler, CloseEvent.getType());
+ }
+
+ @UiField
+ SpanElement label_;
+ @UiField
+ Image dismiss_;
+
+ private static final Styles styles_ =
+ ((Resources) GWT.create(Resources.class)).styles();
+ static
+ {
+ styles_.ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.ui.xml
new file mode 100644
index 0000000..45c8cbe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/WarningBar.ui.xml
@@ -0,0 +1,28 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.application.ui.WarningBar.Resources"/>
+ <ui:with field="themeRes" type="org.rstudio.core.client.theme.res.ThemeResources"/>
+
+ <g:HTMLPanel>
+ <table class="{res.styles.warningBar}"
+ cellpadding="0" cellspacing="0" border="0" width="100%">
+ <tr>
+ <td class="{res.styles.left}"></td>
+ <td class="{res.styles.center}" valign="top">
+ <g:Image resource="{res.warningIconSmall}"
+ styleName="{res.styles.warningIcon}"/>
+ <span ui:field="label_" class="{res.styles.label}"/>
+ </td>
+ <td class="{res.styles.center}" align="right">
+ <g:Image ui:field="dismiss_"
+ resource="{themeRes.closeTab}"
+ styleName="{res.styles.dismiss}"
+ title="Dismiss"/>
+ </td>
+ <td class="{res.styles.right}"></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.css b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.css
new file mode 100644
index 0000000..d93b0c5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.css
@@ -0,0 +1,71 @@
+
+
+.applicationEndedPopupPanel {
+ z-index: 1001;
+}
+
+.glass {
+ background-color: black;
+ filter: literal("alpha(opacity = 15)") !important;
+ opacity: 0.15; /* non-IE */
+ z-index: 1000;
+}
+
+.mainPanel {
+ width: 530px;
+ height: 350px;
+}
+
+.contentPanel {
+ width: 260px;
+}
+
+.captionLabel {
+ font-size: 1.7em;
+ font-weight: bold;
+ margin-bottom: 15px;
+}
+
+.descriptionLabel {
+ margin-bottom: 30px;
+}
+
+ at sprite .NW {
+ gwt-image: 'panelTopLeft';
+ width: 14px;
+ height: 14px;
+}
+ at sprite .N {
+ gwt-image: 'panelTop';
+ height: 14px;
+}
+ at sprite .NE {
+ gwt-image: 'panelTopRight';
+ width: 14px;
+ height: 14px;
+}
+ at sprite .W {
+ gwt-image: 'panelLeft';
+ width: 14px;
+}
+.C {
+ background-color: #f3f3f4;
+}
+ at sprite .E {
+ gwt-image: 'panelRight';
+ width: 14px;
+}
+ at sprite .SW {
+ gwt-image: 'panelBottomLeft';
+ width: 14px;
+ height: 14px;
+}
+ at sprite .S {
+ gwt-image: 'panelBottom';
+ height: 14px;
+}
+ at sprite .SE {
+ gwt-image: 'panelBottomRight';
+ width: 14px;
+ height: 14px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.java b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.java
new file mode 100644
index 0000000..8a70a3d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.java
@@ -0,0 +1,271 @@
+/*
+ * ApplicationEndedPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.appended;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.widget.CenterPanel;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.studio.client.application.Desktop;
+
+public class ApplicationEndedPopupPanel extends PopupPanel
+{
+ public static void showQuit()
+ {
+ asyncShow(QUIT, "", null);
+ }
+
+ public static void showSuicide(String reason)
+ {
+ String description = "<p>R encountered a fatal error.";
+ if (reason.length() > 0)
+ description += ": " + reason;
+ description += "</p>The session was terminated.";
+ asyncShow(SUICIDE, description, null);
+ }
+
+ public static void showDisconnected()
+ {
+ String description =
+ "This browser was disconnected from the R session because another " +
+ "browser connected (only one browser at a time may be connected " +
+ "to an RStudio session). You may reconnect using the button below.";
+
+ asyncShow(DISCONNECTED, description, null);
+ }
+
+ public static void showOffline()
+ {
+ String description =
+ "RStudio is temporarily offline due to system maintenance. We " +
+ "apologize for the inconvenience, please try again in a few minutes.";
+
+ asyncShow(OFFLINE, description, null);
+ }
+
+ public static void prefetch(Command continuation)
+ {
+ asyncShow(PREFETCH, null, continuation);
+ }
+
+ private static void asyncShow(final int mode,
+ final String description,
+ final Command continuation)
+ {
+ GWT.runAsync(new RunAsyncCallback()
+ {
+ public void onFailure(Throwable reason)
+ {
+ Window.alert(description);
+
+ if (continuation != null)
+ continuation.execute();
+ }
+
+ public void onSuccess()
+ {
+ if (mode == PREFETCH)
+ return;
+ new ApplicationEndedPopupPanel(mode, description).center();
+
+ if (continuation != null)
+ continuation.execute();
+ }
+ });
+ }
+
+ private static final int PREFETCH = 0 ;
+ private static final int QUIT = 1 ;
+ private static final int SUICIDE = 2;
+ private static final int DISCONNECTED = 3;
+ private static final int OFFLINE = 4;
+
+ private ApplicationEndedPopupPanel(int mode, String description)
+ {
+ super(false, false);
+ setStylePrimaryName(RESOURCES.styles().applicationEndedPopupPanel());
+ setGlassEnabled(true);
+ setGlassStyleName(RESOURCES.styles().glass());
+
+ // main panel
+ HorizontalPanel horizontalPanel = new HorizontalPanel();
+ horizontalPanel.setSpacing(10);
+
+ // create widgets and make mode dependent customizations
+ Image image;
+ Label captionLabel = new Label();
+ captionLabel.setStylePrimaryName(RESOURCES.styles().captionLabel());
+ final FancyButton button = new FancyButton();
+ button.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ reloadApplication();
+ }
+ });
+ FocusHelper.setFocusDeferred(button);
+
+ switch(mode)
+ {
+ case QUIT:
+ image = new Image(RESOURCES.applicationQuit());
+ captionLabel.setText("R Session Ended");
+ button.setText("Start New Session");
+ break;
+
+ case SUICIDE:
+ image = new Image(RESOURCES.applicationSuicide());
+ captionLabel.setText("R Session Aborted");
+ button.setText("Start New Session");
+ break;
+
+ case DISCONNECTED:
+ image = new Image(RESOURCES.applicationDisconnected());
+ captionLabel.setText("R Session Disconnected");
+ button.setText("Reconnect");
+ break;
+
+ case OFFLINE:
+ image = new Image(RESOURCES.applicationOffline());
+ captionLabel.setText("RStudio Temporarily Offline");
+ button.setText("Reconnect");
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown mode " + mode);
+ }
+
+ // add image
+ horizontalPanel.add(image);
+
+ // captions and button
+ VerticalPanel contentPanel = new VerticalPanel();
+ contentPanel.setStylePrimaryName(RESOURCES.styles().contentPanel());
+ contentPanel.add(captionLabel);
+ HTML descriptionLabel = new HTML(description);
+ descriptionLabel.setStylePrimaryName(RESOURCES.styles().descriptionLabel());
+ contentPanel.add(descriptionLabel);
+ contentPanel.add(button);
+ horizontalPanel.add(contentPanel);
+
+ // center the horizontal panel within the popup
+ CenterPanel mainPanel = new CenterPanel(horizontalPanel);
+ mainPanel.setStylePrimaryName(RESOURCES.styles().mainPanel());
+
+ setWidget(((MyUiBinder)GWT.create(MyUiBinder.class)).createAndBindUi(this));
+ content_.setWidget(mainPanel);
+ }
+
+ @Override
+ public void onPreviewNativeEvent(Event.NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ switch (nativeEvent.getKeyCode())
+ {
+ case KeyCodes.KEY_ENTER:
+
+ nativeEvent.preventDefault();
+ nativeEvent.stopPropagation();
+ reloadApplication();
+ break;
+ }
+ }
+ }
+
+ private void reloadApplication()
+ {
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().launchSession(true);
+ }
+ else
+ {
+ Window.Location.reload();
+ }
+ }
+
+ static interface Styles extends CssResource
+ {
+ String applicationEndedPopupPanel();
+ String glass();
+ String mainPanel();
+ String contentPanel();
+ String captionLabel();
+ String descriptionLabel();
+
+ String NW();
+ String N();
+ String NE();
+ String W();
+ String C();
+ String E();
+ String SW();
+ String S();
+ String SE();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("ApplicationEndedPopupPanel.css")
+ Styles styles();
+
+ ImageResource applicationQuit();
+ ImageResource applicationSuicide();
+ ImageResource applicationDisconnected();
+ ImageResource applicationOffline();
+
+ ImageResource panelTopLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource panelTop();
+ ImageResource panelTopRight();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource panelLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource panelRight();
+
+ ImageResource panelBottomLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource panelBottom();
+ ImageResource panelBottomRight();
+ }
+
+ interface MyUiBinder extends UiBinder<Widget, ApplicationEndedPopupPanel> {}
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ @UiField
+ SimplePanel content_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.ui.xml
new file mode 100644
index 0000000..9499084
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/ApplicationEndedPopupPanel.ui.xml
@@ -0,0 +1,28 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.application.ui.appended.ApplicationEndedPopupPanel.Resources"/>
+
+ <g:HTMLPanel>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="{res.styles.NW}"></td>
+ <td class="{res.styles.N}"></td>
+ <td class="{res.styles.NE}"></td>
+ </tr>
+ <tr>
+ <td class="{res.styles.W}"></td>
+ <td class="{res.styles.C}">
+ <g:SimplePanel ui:field="content_"/>
+ </td>
+ <td class="{res.styles.E}"></td>
+ </tr>
+ <tr>
+ <td class="{res.styles.SW}"></td>
+ <td class="{res.styles.S}"></td>
+ <td class="{res.styles.SE}"></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.css b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.css
new file mode 100644
index 0000000..fd29d15
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.css
@@ -0,0 +1,31 @@
+button.fancy {
+ padding: 0;
+ border: 0 none;
+ margin: 0 0 0 -3px;
+ outline: none;
+ cursor: pointer;
+ background-color: #f3f3f4;
+}
+button.fancy:focus {
+ outline: none;
+}
+
+ at sprite button.fancy .left {
+ gwt-image: 'buttonLeft';
+ width: 11px;
+ height: 38px;
+}
+ at sprite button.fancy .inner {
+ gwt-image: 'buttonTile';
+ color: white;
+ font-weight: bold;
+ font-size: 13px;
+ height: 38px;
+ padding: 5px;
+ padding-top: 9px;
+}
+ at sprite button.fancy .right {
+ gwt-image: 'buttonRight';
+ width: 11px;
+ height: 38px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.java b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.java
new file mode 100644
index 0000000..c2eb8dc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.java
@@ -0,0 +1,83 @@
+/*
+ * FancyButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.appended;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+public class FancyButton extends FocusWidget
+{
+ static
+ {
+ Resources res = GWT.create(Resources.class);
+ res.styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("FancyButton.css")
+ Styles styles();
+
+ @ImageOptions(repeatStyle = RepeatStyle.None)
+ ImageResource buttonLeft();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource buttonTile();
+
+ @ImageOptions(repeatStyle = RepeatStyle.None)
+ ImageResource buttonRight();
+ }
+
+ interface Styles extends CssResource
+ {
+ String fancy();
+ String left();
+ String inner();
+ String right();
+ }
+
+ interface Binder extends UiBinder<ButtonElement, FancyButton> {}
+
+ public FancyButton()
+ {
+ Binder binder = GWT.create(Binder.class);
+ setElement(binder.createAndBindUi(this));
+ }
+
+ public void setText(String text)
+ {
+ content_.setInnerText(text);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler clickHandler)
+ {
+ return addDomHandler(clickHandler, ClickEvent.getType());
+ }
+
+ @UiField
+ TableCellElement content_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.ui.xml
new file mode 100644
index 0000000..1fcfda8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/FancyButton.ui.xml
@@ -0,0 +1,14 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.application.ui.appended.FancyButton.Resources"/>
+
+ <button class="{res.styles.fancy}" type="submit"><table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td class="{res.styles.left}"></td>
+ <td ui:field='content_' class="{res.styles.inner}" valign="top"></td>
+ <td class="{res.styles.right}"></td>
+ </tr>
+ </table></button>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationDisconnected.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationDisconnected.png
new file mode 100755
index 0000000..1f6bca9
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationDisconnected.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationOffline.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationOffline.png
new file mode 100644
index 0000000..58144a9
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationOffline.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationQuit.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationQuit.png
new file mode 100755
index 0000000..5bf7164
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationQuit.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationSuicide.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationSuicide.png
new file mode 100755
index 0000000..e808d64
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/applicationSuicide.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonLeft.png
new file mode 100644
index 0000000..9481cf7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonRight.png
new file mode 100644
index 0000000..0527703
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonTile.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonTile.png
new file mode 100644
index 0000000..cab4f4e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/buttonTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottom.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottom.png
new file mode 100644
index 0000000..f5f6356
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottom.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomLeft.png
new file mode 100644
index 0000000..48a25bc
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomRight.png
new file mode 100644
index 0000000..b030588
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelBottomRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelLeft.png
new file mode 100644
index 0000000..fe33637
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelRight.png
new file mode 100644
index 0000000..cf6cef3
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTop.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTop.png
new file mode 100644
index 0000000..1db8a6e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTop.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopLeft.png
new file mode 100644
index 0000000..6285fea
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopRight.png
new file mode 100644
index 0000000..784e4c1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/appended/panelTopRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/DesktopApplicationHeader.java b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/DesktopApplicationHeader.java
new file mode 100644
index 0000000..0c10fca
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/DesktopApplicationHeader.java
@@ -0,0 +1,367 @@
+/*
+ * DesktopApplicationHeader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.impl;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.impl.DesktopMenuCallback;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.ApplicationQuit;
+import org.rstudio.studio.client.application.ApplicationQuit.QuitContext;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.DesktopHooks;
+import org.rstudio.studio.client.application.IgnoredUpdates;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.application.model.UpdateCheckResult;
+import org.rstudio.studio.client.application.ui.ApplicationHeader;
+import org.rstudio.studio.client.application.ui.GlobalToolbar;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.debugging.ErrorManager;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.files.events.ShowFolderEvent;
+import org.rstudio.studio.client.workbench.views.files.events.ShowFolderHandler;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DesktopApplicationHeader implements ApplicationHeader
+{
+ public interface Binder
+ extends CommandBinder<Commands, DesktopApplicationHeader>
+ {
+ }
+ private static Binder binder_ = GWT.create(Binder.class);
+
+ public DesktopApplicationHeader()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(Commands commands,
+ EventBus events,
+ final Session session,
+ ApplicationServerOperations server,
+ Provider<DesktopHooks> pDesktopHooks,
+ Provider<CodeSearch> pCodeSearch,
+ Provider<UIPrefs> pUIPrefs,
+ ErrorManager errorManager,
+ GlobalDisplay globalDisplay,
+ ApplicationQuit appQuit)
+ {
+ session_ = session;
+ eventBus_= events;
+ pUIPrefs_ = pUIPrefs;
+ globalDisplay_ = globalDisplay;
+ ignoredUpdates_ = IgnoredUpdates.create();
+ server_ = server;
+ appQuit_ = appQuit;
+ binder_.bind(commands, this);
+ commands.mainMenu(new DesktopMenuCallback());
+
+ pDesktopHooks.get();
+
+ commands.uploadFile().remove();
+ commands.exportFiles().remove();
+ commands.updateCredentials().remove();
+
+ commands.checkForUpdates().setVisible(true);
+ commands.showLogFiles().setVisible(true);
+ commands.diagnosticsReport().setVisible(true);
+ commands.showFolder().setVisible(true);
+
+ events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ final SessionInfo sessionInfo = session.getSessionInfo();
+
+ toolbar_.completeInitialization(sessionInfo);
+
+ new JSObjectStateValue(
+ "updates",
+ "ignoredUpdates",
+ ClientState.PERSISTENT,
+ session_.getSessionInfo().getClientState(),
+ false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ if (value != null)
+ ignoredUpdates_ = value.cast();
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ ignoredUpdatesDirty_ = false;
+ return ignoredUpdates_.cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ return ignoredUpdatesDirty_;
+ }
+ };
+
+ Scheduler.get().scheduleFinally(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ Desktop.getFrame().onWorkbenchInitialized(
+ sessionInfo.getScratchDir());
+ if (!sessionInfo.getDisableCheckForUpdates() &&
+ pUIPrefs_.get().checkForUpdates().getValue())
+ {
+ checkForUpdates(false);
+ }
+ }
+ });
+ }
+ });
+
+ events.addHandler(ShowFolderEvent.TYPE, new ShowFolderHandler()
+ {
+ public void onShowFolder(ShowFolderEvent event)
+ {
+ Desktop.getFrame().showFolder(event.getPath().getPath());
+ }
+ });
+
+ toolbar_ = new GlobalToolbar(commands,
+ events,
+ pCodeSearch);
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+ toolbar_.addStyleName(styles.desktopGlobalToolbar());
+ }
+
+ public void showToolbar(boolean showToolbar)
+ {
+ toolbar_.setVisible(showToolbar);
+ }
+
+ public boolean isToolbarVisible()
+ {
+ return toolbar_.isVisible();
+ }
+
+ public void focusGoToFunction()
+ {
+ toolbar_.focusGoToFunction();
+ }
+
+ @Handler
+ void onUndoDummy()
+ {
+ Desktop.getFrame().undo();
+ }
+
+ @Handler
+ void onRedoDummy()
+ {
+ Desktop.getFrame().redo();
+ }
+
+ @Handler
+ void onCutDummy()
+ {
+ Desktop.getFrame().clipboardCut();
+ }
+
+ @Handler
+ void onCopyDummy()
+ {
+ Desktop.getFrame().clipboardCopy();
+ }
+
+ @Handler
+ void onPasteDummy()
+ {
+ Desktop.getFrame().clipboardPaste();
+ }
+
+ @Handler
+ void onShowLogFiles()
+ {
+ Desktop.getFrame().showFolder(session_.getSessionInfo().getLogDir());
+ }
+
+ @Handler
+ void onDiagnosticsReport()
+ {
+ eventBus_.fireEvent(
+ new SendToConsoleEvent("rstudio::diagnosticsReport()", true));
+
+ new Timer() {
+ @Override
+ public void run()
+ {
+ Desktop.getFrame().showFolder("~/rstudio-diagnostics");
+ }
+ }.schedule(1000);
+
+ }
+
+ @Handler
+ void onCheckForUpdates()
+ {
+ checkForUpdates(true);
+ }
+
+ public int getPreferredHeight()
+ {
+ if (toolbar_.isVisible())
+ return 32;
+ else
+ return 5;
+ }
+
+ public Widget asWidget()
+ {
+ return toolbar_;
+ }
+
+ private void checkForUpdates(final boolean manual)
+ {
+ server_.checkForUpdates(manual,
+ new ServerRequestCallback<UpdateCheckResult>()
+ {
+ @Override
+ public void onResponseReceived(UpdateCheckResult result)
+ {
+ respondToUpdateCheck(result, manual);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error Checking for Updates",
+ "An error occurred while checking for updates: "
+ + error.getMessage());
+ }
+ });
+ }
+
+ private void respondToUpdateCheck(final UpdateCheckResult result,
+ boolean manual)
+ {
+ boolean ignoredUpdate = false;
+ if (result.getUpdateVersion().length() > 0)
+ {
+ JsArrayString ignoredUpdates = ignoredUpdates_.getIgnoredUpdates();
+ for (int i = 0; i < ignoredUpdates.length(); i++)
+ {
+ if (ignoredUpdates.get(i).equals(result.getUpdateVersion()))
+ {
+ ignoredUpdate = true;
+ }
+ }
+ }
+ if (result.getUpdateVersion().length() > 0 &&
+ !ignoredUpdate)
+ {
+ ArrayList<String> buttonLabels = new ArrayList<String>();
+ ArrayList<Operation> buttonOperations = new ArrayList<Operation>();
+
+ buttonLabels.add("Quit and Download...");
+ buttonOperations.add(new Operation() {
+ @Override
+ public void execute()
+ {
+ appQuit_.prepareForQuit("Update RStudio", new QuitContext()
+ {
+ @Override
+ public void onReadyToQuit(boolean saveChanges)
+ {
+ Desktop.getFrame().browseUrl(result.getUpdateUrl());
+ appQuit_.performQuit(saveChanges, null);
+ }
+ });
+ }
+ });
+
+ buttonLabels.add("Remind Later");
+ buttonOperations.add(new Operation() {
+ @Override
+ public void execute()
+ {
+ // Don't do anything here; the prompt will re-appear the next
+ // time we do an update check
+ }
+ });
+
+ // Only provide the option to ignore the update if it's not urgent.
+ if (result.getUpdateUrgency() == 0)
+ {
+ buttonLabels.add("Ignore Update");
+ buttonOperations.add(new Operation() {
+ @Override
+ public void execute()
+ {
+ ignoredUpdates_.addIgnoredUpdate(result.getUpdateVersion());
+ ignoredUpdatesDirty_ = true;
+ }
+ });
+ }
+
+ globalDisplay_.showGenericDialog(GlobalDisplay.MSG_QUESTION,
+ "Update Available",
+ result.getUpdateMessage(),
+ buttonLabels,
+ buttonOperations, 0);
+ }
+ else if (manual)
+ {
+ globalDisplay_.showMessage(GlobalDisplay.MSG_INFO,
+ "No Update Available",
+ "You're using the newest version of RStudio.");
+ }
+ }
+
+ private Session session_;
+ private EventBus eventBus_;
+ private GlobalToolbar toolbar_;
+ private GlobalDisplay globalDisplay_;
+ Provider<UIPrefs> pUIPrefs_;
+ private ApplicationServerOperations server_;
+ private IgnoredUpdates ignoredUpdates_;
+ private boolean ignoredUpdatesDirty_ = false;
+ private ApplicationQuit appQuit_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/WebApplicationHeader.java b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/WebApplicationHeader.java
new file mode 100644
index 0000000..59bf29d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/WebApplicationHeader.java
@@ -0,0 +1,381 @@
+/*
+ * WebApplicationHeader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application.ui.impl;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.command.*;
+import org.rstudio.core.client.command.impl.WebMenuCallback;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.core.client.widget.MessageDialogLabel;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.LogoutRequestedEvent;
+import org.rstudio.studio.client.application.ui.ApplicationHeader;
+import org.rstudio.studio.client.application.ui.GlobalToolbar;
+import org.rstudio.studio.client.application.ui.ProjectPopupMenu;
+import org.rstudio.studio.client.application.ui.impl.header.HeaderPanel;
+import org.rstudio.studio.client.application.ui.impl.header.MenubarPanel;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.dialog.WebDialogBuilderFactory;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+public class WebApplicationHeader extends Composite implements ApplicationHeader
+{
+ public WebApplicationHeader()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(
+ final Commands commands,
+ EventBus eventBus,
+ GlobalDisplay globalDisplay,
+ ThemeResources themeResources,
+ final Session session,
+ Provider<CodeSearch> pCodeSearch)
+ {
+ eventBus_ = eventBus;
+ globalDisplay_ = globalDisplay;
+
+ // Use the outer panel to just aggregate the menu bar/account area,
+ // with the logo. The logo can't be inside the HorizontalPanel because
+ // it needs to overflow out of the top of the panel, and it was much
+ // easier to do this with absolute positioning.
+ outerPanel_ = new FlowPanel();
+ outerPanel_.getElement().getStyle().setPosition(Position.RELATIVE);
+
+ // large logo
+ logoLarge_ = new Image(ThemeResources.INSTANCE.rstudio());
+ ((ImageElement)logoLarge_.getElement().cast()).setAlt("RStudio");
+ Style style = logoLarge_.getElement().getStyle();
+ style.setPosition(Position.ABSOLUTE);
+ style.setTop(5, Unit.PX);
+ style.setLeft(18, Unit.PX);
+
+ // small logo
+ logoSmall_ = new Image(ThemeResources.INSTANCE.rstudio_small());
+ ((ImageElement)logoSmall_.getElement().cast()).setAlt("RStudio");
+ style = logoSmall_.getElement().getStyle();
+ style.setPosition(Position.ABSOLUTE);
+ style.setTop(5, Unit.PX);
+ style.setLeft(18, Unit.PX);
+
+ // header container
+ headerBarPanel_ = new HorizontalPanel() ;
+ headerBarPanel_.setStylePrimaryName(themeResources.themeStyles().header());
+ headerBarPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ headerBarPanel_.setWidth("100%");
+
+ if (BrowseCap.INSTANCE.suppressBrowserForwardBack())
+ suppressBrowserForwardBack();
+
+ // override Cmd+W keybaord shortcut for Chrome
+ if (BrowseCap.isChrome())
+ {
+ int modifiers = (BrowseCap.hasMetaKey() ? KeyboardShortcut.META :
+ KeyboardShortcut.CTRL) |
+ KeyboardShortcut.ALT;
+
+ AppCommand closeSourceDoc = commands.closeSourceDoc();
+ closeSourceDoc.setShortcut(new KeyboardShortcut(modifiers, 'W'));
+ ShortcutManager.INSTANCE.register(
+ modifiers, 'W', closeSourceDoc, "", "");
+ }
+
+ // main menu
+ advertiseEditingShortcuts(globalDisplay, commands);
+ WebMenuCallback menuCallback = new WebMenuCallback();
+ commands.mainMenu(menuCallback);
+ mainMenu_ = menuCallback.getMenu();
+ mainMenu_.setAutoHideRedundantSeparators(false);
+ fixup(mainMenu_);
+ mainMenu_.addStyleName(themeResources.themeStyles().mainMenu());
+ AppMenuBar.addSubMenuVisibleChangedHandler(new SubMenuVisibleChangedHandler()
+ {
+ public void onSubMenuVisibleChanged(SubMenuVisibleChangedEvent event)
+ {
+ // When submenus of the main menu appear, glass over any iframes
+ // so that mouse clicks can make the menus disappear
+ if (event.isVisible())
+ eventBus_.fireEvent(new GlassVisibilityEvent(true));
+ else
+ eventBus_.fireEvent(new GlassVisibilityEvent(false));
+ }
+ });
+ headerBarPanel_.add(mainMenu_);
+
+ HTML spacer = new HTML();
+ headerBarPanel_.add(spacer);
+ headerBarPanel_.setCellWidth(spacer, "100%");
+
+ // commands panel (no widgets added until after session init)
+ headerBarCommandsPanel_ = new HorizontalPanel();
+ headerBarPanel_.add(headerBarCommandsPanel_);
+ headerBarPanel_.setCellHorizontalAlignment(headerBarCommandsPanel_,
+ HorizontalPanel.ALIGN_RIGHT);
+
+ eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler()
+ {
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ SessionInfo sessionInfo = session.getSessionInfo();
+
+ // only show the user identity if we are in server mode
+ if (sessionInfo.getShowIdentity())
+ initCommandsPanel(sessionInfo);
+
+ // complete toolbar initialization
+ toolbar_.completeInitialization(sessionInfo);
+
+ // add project tools to main menu
+ projectMenuButton_ =
+ new ProjectPopupMenu(sessionInfo, commands).getToolbarButton();
+ projectMenuButton_.addStyleName(
+ ThemeStyles.INSTANCE.webHeaderBarCommandsProjectMenu());
+ headerBarPanel_.add(projectMenuButton_);
+ showProjectMenu(!toolbar_.isVisible());
+ }
+ });
+
+ // create toolbar
+ toolbar_ = new GlobalToolbar(commands,
+ eventBus,
+ pCodeSearch);
+ toolbar_.addStyleName(themeResources.themeStyles().webGlobalToolbar());
+
+ // initialize widget
+ initWidget(outerPanel_);
+ }
+
+ public void showToolbar(boolean showToolbar)
+ {
+ outerPanel_.clear();
+
+ if (showToolbar)
+ {
+ HeaderPanel headerPanel = new HeaderPanel(headerBarPanel_, toolbar_);
+ outerPanel_.add(headerPanel);
+ outerPanel_.add(logoLarge_);
+ mainMenu_.getElement().getStyle().setMarginLeft(18, Unit.PX);
+ preferredHeight_ = 65;
+ showProjectMenu(false);
+ }
+ else
+ {
+ MenubarPanel menubarPanel = new MenubarPanel(headerBarPanel_);
+ outerPanel_.add(menubarPanel);
+ outerPanel_.add(logoSmall_);
+ mainMenu_.getElement().getStyle().setMarginLeft(0, Unit.PX);
+ preferredHeight_ = 45;
+ showProjectMenu(true);
+ }
+ }
+
+ public boolean isToolbarVisible()
+ {
+ return !projectMenuButton_.isVisible();
+ }
+
+ public void focusGoToFunction()
+ {
+ toolbar_.focusGoToFunction();
+ }
+
+ private void showProjectMenu(boolean show)
+ {
+ projectMenuButton_.setVisible(show);
+ }
+
+
+
+ private native final void suppressBrowserForwardBack() /*-{
+ var outerWindow = $wnd.parent;
+ if (outerWindow.addEventListener) {
+ var handler = function(evt) {
+ if ((evt.keyCode == 37 || evt.keyCode == 39) && (evt.metaKey && !evt.ctrlKey && !evt.shiftKey && !evt.altKey)) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ }
+ };
+ outerWindow.addEventListener('keydown', handler, false);
+ $wnd.addEventListener('keydown', handler, false);
+ }
+ }-*/;
+
+ private void advertiseEditingShortcuts(final GlobalDisplay display,
+ final Commands commands)
+ {
+ int mod = BrowseCap.hasMetaKey() ? KeyboardShortcut.META : KeyboardShortcut.CTRL;
+
+ commands.undoDummy().setShortcut(new KeyboardShortcut(mod, 'Z'));
+ commands.redoDummy().setShortcut(new KeyboardShortcut(mod| KeyboardShortcut.SHIFT, 'Z'));
+
+ commands.cutDummy().setShortcut(new KeyboardShortcut(mod, 'X'));
+ commands.copyDummy().setShortcut(new KeyboardShortcut(mod, 'C'));
+ commands.pasteDummy().setShortcut(new KeyboardShortcut(mod, 'V'));
+
+ CommandHandler useKeyboardNotification = new CommandHandler()
+ {
+ public void onCommand(AppCommand command)
+ {
+ MessageDialogLabel label = new MessageDialogLabel();
+ label.setHtml("Your browser does not allow access to your<br/>" +
+ "computer's clipboard. As a result you must<br/>" +
+ "use keyboard shortcuts for:" +
+ "<br/><br/><table cellpadding=0 cellspacing=0 border=0>" +
+ makeRow(commands.undoDummy()) +
+ makeRow(commands.redoDummy()) +
+ makeRow(commands.cutDummy()) +
+ makeRow(commands.copyDummy()) +
+ makeRow(commands.pasteDummy()) +
+ "</table>"
+ );
+ new WebDialogBuilderFactory().create(
+ GlobalDisplay.MSG_WARNING,
+ "Use Keyboard Shortcut",
+ label).showModal();
+ }
+
+ private String makeRow(AppCommand cmd)
+ {
+ String textAlign = BrowseCap.hasMetaKey()
+ ? "text-align: right"
+ : "";
+ return "<tr><td>" + cmd.getMenuLabel(true) + "</td>" +
+ "<td style='padding-left: 12px; " + textAlign + "'>"
+ + cmd.getShortcutPrettyHtml() + "</td></tr>";
+ }
+ };
+
+ commands.undoDummy().addHandler(useKeyboardNotification);
+ commands.redoDummy().addHandler(useKeyboardNotification);
+ commands.cutDummy().addHandler(useKeyboardNotification);
+ commands.copyDummy().addHandler(useKeyboardNotification);
+ commands.pasteDummy().addHandler(useKeyboardNotification);
+ }
+
+ public int getPreferredHeight()
+ {
+ return preferredHeight_;
+ }
+
+ /**
+ * Without this fixup, the main menu doesn't properly deselect its items
+ * when the mouse takes focus away.
+ */
+ private void fixup(final AppMenuBar mainMenu)
+ {
+ mainMenu.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ // Only dismiss the selection if the panel that just closed belongs
+ // to the currently selected item. Otherwise, the selected item
+ // has already changed and we don't want to mess with it. (This is
+ // NOT an edge case, it is very common.)
+ MenuItem menuItem = mainMenu.getSelectedItem();
+ if (menuItem != null)
+ {
+ MenuBar subMenu = menuItem.getSubMenu();
+ if (subMenu != null &&
+ popupPanelCloseEvent.getTarget() != null &&
+ subMenu.equals(popupPanelCloseEvent.getTarget().getWidget()))
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ mainMenu.selectItem(null);
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ private void initCommandsPanel(final SessionInfo sessionInfo)
+ {
+ // add username
+ Label usernameLabel = new Label();
+ usernameLabel.setText(sessionInfo.getUserIdentity());
+ headerBarCommandsPanel_.add(usernameLabel);
+ headerBarCommandsPanel_.add(createCommandSeparator());
+
+ // signout link
+ Widget signoutLink = createCommandLink("Sign Out", new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ eventBus_.fireEvent(new LogoutRequestedEvent());
+ }
+ });
+ headerBarCommandsPanel_.add(signoutLink);
+ }
+
+ private Widget createCommandSeparator()
+ {
+ return new HTML(" | ");
+ }
+
+ private Widget createCommandLink(String caption, ClickHandler clickHandler)
+ {
+ HyperlinkLabel link = new HyperlinkLabel(caption, clickHandler);
+ return link;
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ private int preferredHeight_;
+ private FlowPanel outerPanel_;
+ private Image logoLarge_;
+ private Image logoSmall_;
+ private HorizontalPanel headerBarPanel_;
+ private HorizontalPanel headerBarCommandsPanel_;
+ private ToolbarButton projectMenuButton_;
+ private AppMenuBar mainMenu_;
+ private GlobalToolbar toolbar_;
+ private EventBus eventBus_;
+ private GlobalDisplay globalDisplay_;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.css b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.css
new file mode 100644
index 0000000..51ec43e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.css
@@ -0,0 +1,18 @@
+.panel {
+ width: 100%;
+ padding: 0 7px 0 7px;
+ margin-top: 10px;
+ overflow: visible;
+}
+
+ at sprite .left {
+ gwt-image: 'headerPanelLeft';
+}
+
+ at sprite .center {
+ gwt-image: 'headerPanelTile';
+}
+
+ at sprite .right {
+ gwt-image: 'headerPanelRight';
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.java b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.java
new file mode 100644
index 0000000..23def6b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.java
@@ -0,0 +1,68 @@
+/*
+ * HeaderPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.impl.header;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+
+public class HeaderPanel extends Composite
+{
+ static
+ {
+ ((Resources)GWT.create(Resources.class)).styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("HeaderPanel.css")
+ Styles styles();
+
+ ImageResource headerPanelLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource headerPanelTile();
+ ImageResource headerPanelRight();
+ }
+
+ interface Styles extends CssResource
+ {
+ String panel();
+ String left();
+ String center();
+ String right();
+ }
+
+ interface MyUiBinder extends UiBinder<Widget, HeaderPanel> {}
+
+ public HeaderPanel(Widget topLineWidget, Widget toolbarWidget)
+ {
+ topLineWidget_ = topLineWidget;
+ toolbarWidget_ = toolbarWidget;
+ initWidget(((MyUiBinder)GWT.create(MyUiBinder.class)).createAndBindUi(this));
+ }
+
+ @UiField(provided = true)
+ final Widget topLineWidget_;
+
+ @UiField(provided = true)
+ final Widget toolbarWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.ui.xml
new file mode 100644
index 0000000..5ec2ab8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/HeaderPanel.ui.xml
@@ -0,0 +1,19 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.studio.client.application.ui.impl.header.HeaderPanel.Resources'/>
+
+ <g:HTMLPanel>
+ <table class="{res.styles.panel}" cellpadding='0' cellspacing='0' border='0'>
+ <tr>
+ <td class="{res.styles.left}"></td>
+ <td class="{res.styles.center}" valign="middle">
+ <g:Widget ui:field="topLineWidget_" />
+ <g:Widget ui:field="toolbarWidget_" />
+ </td>
+ <td class="{res.styles.right}"></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.css b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.css
new file mode 100644
index 0000000..ad5aaf7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.css
@@ -0,0 +1,18 @@
+.panel {
+ width: 100%;
+ padding: 0 7px 0 7px;
+ margin-top: 10px;
+ overflow: visible;
+}
+
+ at sprite .left {
+ gwt-image: 'menubarLeft';
+}
+
+ at sprite .center {
+ gwt-image: 'menubarTile';
+}
+
+ at sprite .right {
+ gwt-image: 'menubarRight';
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.java b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.java
new file mode 100644
index 0000000..2e299ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.java
@@ -0,0 +1,64 @@
+/*
+ * MenubarPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.impl.header;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+
+public class MenubarPanel extends Composite
+{
+ static
+ {
+ ((Resources)GWT.create(Resources.class)).styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("MenubarPanel.css")
+ Styles styles();
+
+ ImageResource menubarLeft();
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource menubarTile();
+ ImageResource menubarRight();
+ }
+
+ interface Styles extends CssResource
+ {
+ String panel();
+ String left();
+ String center();
+ String right();
+ }
+
+ interface MyUiBinder extends UiBinder<Widget, MenubarPanel> {}
+
+ public MenubarPanel(Widget widget)
+ {
+ widget_ = widget;
+ initWidget(((MyUiBinder)GWT.create(MyUiBinder.class)).createAndBindUi(this));
+ }
+
+ @UiField(provided = true)
+ final Widget widget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.ui.xml
new file mode 100644
index 0000000..a7851d6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/MenubarPanel.ui.xml
@@ -0,0 +1,19 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field='res' type='org.rstudio.studio.client.application.ui.impl.header.MenubarPanel.Resources'/>
+
+ <g:HTMLPanel>
+ <table class="{res.styles.panel}" cellpadding='0' cellspacing='0' border='0'>
+ <tr>
+ <td class="{res.styles.left}"></td>
+ <td class="{res.styles.center}" valign="middle">
+ <g:Widget ui:field="widget_" />
+ </td>
+ <td class="{res.styles.right}"></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
+
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelLeft.png
new file mode 100755
index 0000000..a1eb0ee
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelRight.png
new file mode 100755
index 0000000..b6f1e80
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelTile.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelTile.png
new file mode 100755
index 0000000..4f24fc5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/headerPanelTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarLeft.png
new file mode 100755
index 0000000..96985a5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarRight.png
new file mode 100755
index 0000000..3193c54
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarTile.png b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarTile.png
new file mode 100755
index 0000000..657cee8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/impl/header/menubarTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/projectMenu.png b/src/gwt/src/org/rstudio/studio/client/application/ui/projectMenu.png
new file mode 100644
index 0000000..8186f93
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/projectMenu.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/rstudio_sm.png b/src/gwt/src/org/rstudio/studio/client/application/ui/rstudio_sm.png
new file mode 100644
index 0000000..5d407f0
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/rstudio_sm.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.css b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.css
new file mode 100644
index 0000000..4d9fb69
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.css
@@ -0,0 +1,37 @@
+.panel {
+ z-index: 1001;
+}
+
+.glass {
+ background-color: black;
+ filter: literal("alpha(opacity = 0)") !important;
+ opacity: 0.0; /* non-IE */
+ z-index: 1000;
+}
+
+.label {
+ position: relative;
+ display: inline;
+ top: 3px;
+ margin-left: 4px;
+ font-weight: bold;
+}
+
+ at sprite .left {
+ gwt-image: 'statusPopupLeft';
+}
+
+ at sprite .center {
+ gwt-image: 'statusPopupTile';
+ white-space: nowrap;
+ font-weight: bold;
+}
+
+.spinner {
+ position: relative;
+ top: 8px;
+}
+
+ at sprite .right {
+ gwt-image: 'statusPopupRight';
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.java b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.java
new file mode 100644
index 0000000..0176aa2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgress.java
@@ -0,0 +1,120 @@
+/*
+ * ApplicationSerializationProgress.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.application.ui.serializationprogress;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+
+public class ApplicationSerializationProgress extends PopupPanel
+{
+ public ApplicationSerializationProgress(String message,
+ boolean modal,
+ int delayMs)
+ {
+ super(false, modal);
+ addStyleName(RESOURCES.styles().panel());
+
+ if (modal)
+ {
+ //setGlassEnabled(true);
+ setGlassStyleName(RESOURCES.styles().glass());
+ }
+
+ ApplicationSerializationProgressLabel label =
+ new ApplicationSerializationProgressLabel();
+ label.setText(message);
+
+ // set widget
+ setWidget(label);
+
+ // show (after specified delay so long as we were not already hidden)
+ if (delayMs > 0)
+ {
+ new Timer() { public void run()
+ {
+ if (!wasHidden_)
+ showProgress();
+
+ }}.schedule(delayMs);
+ }
+ else
+ {
+ showProgress();
+ }
+ }
+
+ @Override
+ public void hide()
+ {
+ super.hide();
+ wasHidden_ = true;
+ }
+
+ private void showProgress()
+ {
+ setPopupPositionAndShow( new PopupPanel.PositionCallback()
+ {
+ public void setPosition(int width, int height)
+ {
+ setPopupPosition((Window.getClientWidth()/2) - (width/2), 0);
+ }
+ });
+ }
+
+ private boolean wasHidden_ = false;
+
+ public interface Styles extends CssResource
+ {
+ String panel();
+ String glass();
+ String spinner();
+ String label();
+ String left();
+ String center();
+ String right();
+ }
+
+ public interface Resources extends ClientBundle
+ {
+ @Source("ApplicationSerializationProgress.css")
+ Styles styles();
+
+ ImageResource spinnerManilla();
+ ImageResource statusPopupLeft();
+ ImageResource statusPopupRight();
+ @ImageOptions(repeatStyle=RepeatStyle.Horizontal)
+ ImageResource statusPopupTile();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ StyleInjector.inject(
+ "." + RESOURCES.styles().glass() +
+ " {filter: alpha(opacity = 0) !important;}");
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.java b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.java
new file mode 100644
index 0000000..4205534
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.java
@@ -0,0 +1,43 @@
+/*
+ * ApplicationSerializationProgressLabel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.serializationprogress;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ApplicationSerializationProgressLabel extends Composite
+{
+ interface MyBinder
+ extends UiBinder<Widget, ApplicationSerializationProgressLabel>
+ {}
+ private static MyBinder binder = GWT.create(MyBinder.class);
+
+ public ApplicationSerializationProgressLabel()
+ {
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public void setText(String text)
+ {
+ label_.setText(text);
+ }
+
+ @UiField
+ Label label_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.ui.xml b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.ui.xml
new file mode 100644
index 0000000..7e4497b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/ApplicationSerializationProgressLabel.ui.xml
@@ -0,0 +1,21 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.application.ui.serializationprogress.ApplicationSerializationProgress.Resources"/>
+
+ <g:HTMLPanel>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="{res.styles.left}"></td>
+ <td class="{res.styles.center}" valign="top" nowrap="nowrap">
+ <nobr>
+ <g:Image resource="{res.spinnerManilla}" styleName="{res.styles.spinner}"/>
+ <g:Label ui:field="label_" styleName="{res.styles.label}" wordWrap="false"/>
+ </nobr>
+ </td>
+ <td class="{res.styles.right}"></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/spinnerManilla.gif b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/spinnerManilla.gif
new file mode 100644
index 0000000..da36137
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/spinnerManilla.gif differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupLeft.png
new file mode 100755
index 0000000..e908736
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupRight.png
new file mode 100755
index 0000000..445c449
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupTile.png b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupTile.png
new file mode 100755
index 0000000..d12363d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/serializationprogress/statusPopupTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.css b/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.css
new file mode 100644
index 0000000..ac0edb8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.css
@@ -0,0 +1,9 @@
+
+.supportPopupMenu {
+ margin-top: 3px !important;
+ font-size: 11px;
+ width: 145px;
+ /* note: we don't size to content because safari was not properly
+ responding to showRelativeTo unless the width was set in CSS
+ (the offset width passed to the PositionCallback was very large */
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.java b/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.java
new file mode 100644
index 0000000..8cfa36c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/application/ui/support/SupportPopupMenu.java
@@ -0,0 +1,103 @@
+/*
+ * SupportPopupMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.application.ui.support;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.PopupPanel;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.core.client.widget.ThemedPopupPanel;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+public class SupportPopupMenu extends ThemedPopupPanel
+{
+ public SupportPopupMenu(GlobalDisplay globalDisplay)
+ {
+ super(true, // auto-hide
+ false); // non-modal
+
+ setStylePrimaryName(RES.styles().supportPopupMenu());
+
+ // setup table to contain menu items
+ FlexTable supportTable = new FlexTable();
+ supportTable.setHeight("0px");
+ supportTable.getColumnFormatter().setWidth(0, "100%");
+
+ // report a bug
+ addMenuItem(supportTable,
+ "Report a Bug",
+ "bugs at rstudio.org",
+ globalDisplay);
+
+ // suggest a feature
+ addMenuItem(supportTable,
+ "Suggest a Feature",
+ "feedback at rstudio.org",
+ globalDisplay);
+
+ // ask a question
+ addMenuItem(supportTable,
+ "Ask a Question",
+ "support at rstudio.org",
+ globalDisplay);
+
+ // set widget
+ this.setWidget(supportTable);
+ }
+
+
+ private void addMenuItem(FlexTable supportTable,
+ String caption,
+ final String email,
+ final GlobalDisplay globalDisplay)
+ {
+ // maintain reference to containing class for closing
+ final PopupPanel popupPanel = this;
+
+ // create a hyperlink label for this URL
+ HyperlinkLabel link = new HyperlinkLabel(caption, new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ globalDisplay.openEmailComposeWindow(email, null);
+ popupPanel.hide();
+ }
+ });
+
+ int row = supportTable.getRowCount();
+ supportTable.setWidget(row, 0, link);
+ }
+
+ static Resources RES = (Resources)GWT.create(Resources.class);
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("SupportPopupMenu.css")
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String supportPopupMenu();
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarLeft.png b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarLeft.png
new file mode 100644
index 0000000..c5962f5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarRight.png b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarRight.png
new file mode 100644
index 0000000..a12deb2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarTile.png b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarTile.png
new file mode 100644
index 0000000..95862fb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/warningBarTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/application/ui/warningIconSmall.png b/src/gwt/src/org/rstudio/studio/client/application/ui/warningIconSmall.png
new file mode 100755
index 0000000..1103b84
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/application/ui/warningIconSmall.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/AutoGlassAttacher.java b/src/gwt/src/org/rstudio/studio/client/common/AutoGlassAttacher.java
new file mode 100644
index 0000000..76e769d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/AutoGlassAttacher.java
@@ -0,0 +1,40 @@
+/*
+ * AutoGlassAttacher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.user.client.ui.LayoutPanel;
+import org.rstudio.core.client.widget.GlassAttacher;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.core.client.widget.events.GlassVisibilityHandler;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class AutoGlassAttacher extends GlassAttacher
+{
+ public AutoGlassAttacher(LayoutPanel panel)
+ {
+ super(panel);
+
+ EventBus eventBus = RStudioGinjector.INSTANCE.getEventBus();
+ eventBus.addHandler(GlassVisibilityEvent.TYPE, new GlassVisibilityHandler()
+ {
+ public void onGlass(GlassVisibilityEvent event)
+ {
+ setGlass(event.isShow());
+ }
+ });
+ setGlass(false);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/AutoGlassPanel.java b/src/gwt/src/org/rstudio/studio/client/common/AutoGlassPanel.java
new file mode 100644
index 0000000..179601d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/AutoGlassPanel.java
@@ -0,0 +1,45 @@
+/*
+ * AutoGlassPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.widget.GlassPanel;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.core.client.widget.events.GlassVisibilityHandler;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+
+/**
+ * Automatically glasses over the child element in response to the global
+ * GlassVisibilityEvent event. Use this to prevent splitters from losing their
+ * draggability over iframes.
+ */
+public class AutoGlassPanel extends GlassPanel
+{
+ public AutoGlassPanel(Widget child)
+ {
+ super(child);
+
+ EventBus eventBus = RStudioGinjector.INSTANCE.getEventBus();
+ eventBus.addHandler(GlassVisibilityEvent.TYPE, new GlassVisibilityHandler()
+ {
+ public void onGlass(GlassVisibilityEvent event)
+ {
+ setGlass(event.isShow());
+ }
+ });
+ setGlass(false);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/CommandLineHistory.java b/src/gwt/src/org/rstudio/studio/client/common/CommandLineHistory.java
new file mode 100644
index 0000000..e968b25
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/CommandLineHistory.java
@@ -0,0 +1,97 @@
+/*
+ * CommandLineHistory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.user.client.ui.HasText;
+import org.rstudio.core.client.StringUtil;
+
+import java.util.ArrayList;
+
+public class CommandLineHistory
+{
+ public CommandLineHistory(HasText input)
+ {
+ input_ = input;
+ }
+
+ public void setHistory(ArrayList<String> history)
+ {
+ history_.clear();
+ for (String entry : history)
+ addToHistory(entry);
+ resetPosition();
+ }
+
+ public void addToHistory(String command)
+ {
+ if (StringUtil.isNullOrEmpty(command))
+ return;
+
+ if (history_.size() > 0
+ && command.equals(history_.get(history_.size() - 1)))
+ {
+ // do not allow dupes
+ return;
+ }
+
+ history_.add(command);
+ }
+
+ public void navigateHistory(int offset)
+ {
+ int newPos = getPositionAtOffset(offset);
+
+ if (newPos == historyPos_)
+ return; // no-op due to boundary limits
+
+ if (historyPos_ == history_.size())
+ {
+ historyTail_ = input_.getText();
+ }
+
+ input_.setText(
+ newPos < history_.size() ? history_.get(newPos) :
+ historyTail_ != null ? historyTail_ :
+ "");
+ historyPos_ = newPos;
+ }
+
+ public String getHistoryEntry(int offset)
+ {
+ int pos = getPositionAtOffset(offset);
+ return history_.get(pos);
+ }
+
+ public void resetPosition()
+ {
+ historyPos_ = history_.size();
+ historyTail_ = "";
+ }
+
+ private int getPositionAtOffset(int offset)
+ {
+ int pos = historyPos_ + offset;
+ return Math.max(0, Math.min(pos, history_.size()));
+ }
+
+ private final ArrayList<String> history_ = new ArrayList<String>() ;
+ private int historyPos_ ;
+ // If you start typing a command, then go up in history, then go down,
+ // then what you had previously typed should still be there. This is
+ // that value--it is loaded/saved whenever history nagivation takes you
+ // into/out of that final history position (history_.size()).
+ private String historyTail_;
+ private final HasText input_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/ConsoleDispatcher.java b/src/gwt/src/org/rstudio/studio/client/common/ConsoleDispatcher.java
new file mode 100644
index 0000000..a40eb6d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/ConsoleDispatcher.java
@@ -0,0 +1,190 @@
+/*
+ * ConsoleDispatcher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+
+ at Singleton
+public class ConsoleDispatcher
+{
+ @Inject
+ public ConsoleDispatcher(EventBus eventBus,
+ Commands commands,
+ FileDialogs fileDialogs,
+ WorkbenchContext workbenchContext,
+ Session session,
+ RemoteFileSystemContext fsContext)
+ {
+ eventBus_ = eventBus;
+ commands_ = commands;
+ fileDialogs_ = fileDialogs;
+ workbenchContext_ = workbenchContext;
+ session_ = session;
+ fsContext_ = fsContext;
+ }
+
+ public void executeSetWd(FileSystemItem dir, boolean activateConsole)
+ {
+ String escaped = dir.getPath().replaceAll("\\\\", "\\\\\\\\");
+ if (escaped.equals("~"))
+ escaped = "~/";
+ eventBus_.fireEvent(
+ new SendToConsoleEvent("setwd(\"" + escaped + "\")", true));
+
+ if (activateConsole)
+ commands_.activateConsole().execute();
+ }
+
+ public void executeCommand(String command, FileSystemItem targetFile)
+ {
+ String code = command + "(\"" + targetFile.getPath() + "\")";
+ eventBus_.fireEvent(new SendToConsoleEvent(code, true));
+ }
+
+
+ public void saveFileAsThenExecuteCommand(String caption,
+ final String defaultExtension,
+ boolean forceExtension,
+ final String command)
+ {
+ fileDialogs_.saveFile(
+ caption,
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ defaultExtension,
+ forceExtension,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(
+ FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ executeCommand(command, input);
+ indicator.onCompleted();
+ }
+ });
+ }
+
+ public void chooseFileThenExecuteCommand(String caption,
+ final String command)
+ {
+ fileDialogs_.openFile(
+ caption,
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input, ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ executeCommand(command, input);
+ indicator.onCompleted();
+ }
+ });
+
+ }
+
+
+ public void executeSourceCommand(String path,
+ TextFileType fileType,
+ String encoding,
+ boolean contentKnownToBeAscii,
+ boolean echo,
+ boolean focus,
+ boolean debug)
+ {
+
+ StringBuilder code = new StringBuilder();
+
+ if (fileType.isCpp())
+ {
+ // use a relative path if possible
+ String relativePath = FileSystemItem.createFile(path).getPathRelativeTo(
+ workbenchContext_.getCurrentWorkingDir());
+ if (relativePath != null)
+ path = relativePath;
+
+ code.append("Rcpp::sourceCpp(" + escapedPath(path) + ")");
+ }
+ else
+ {
+ String escapedPath = escapedPath(path);
+ String systemEncoding = session_.getSessionInfo().getSystemEncoding();
+ boolean isSystemEncoding =
+ normalizeEncoding(encoding).equals(normalizeEncoding(systemEncoding));
+
+ if (contentKnownToBeAscii || isSystemEncoding)
+ code.append((debug ? "debugSource" : "source") +
+ "(" + escapedPath);
+ else
+ {
+ code.append((debug ? "debugSource" : "source.with.encoding") +
+ "(" + escapedPath + ", encoding='" +
+ (!StringUtil.isNullOrEmpty(encoding) ? encoding : "UTF-8") +
+ "'");
+ }
+
+ if (echo)
+ code.append(", echo=TRUE");
+ code.append(")");
+ }
+
+
+ eventBus_.fireEvent(new SendToConsoleEvent(code.toString(), true));
+
+ if (focus)
+ commands_.activateConsole().execute();
+ }
+
+ private String escapedPath(String path)
+ {
+ String escapedPath = "'" +
+ path.replace("\\", "\\\\").replace("'", "\\'") +
+ "'";
+ return escapedPath;
+ }
+
+ private String normalizeEncoding(String str)
+ {
+ return StringUtil.notNull(str).replaceAll("[- ]", "").toLowerCase();
+ }
+
+
+ private final EventBus eventBus_;
+ private final Commands commands_;
+ private final FileDialogs fileDialogs_;
+ private final WorkbenchContext workbenchContext_;
+ private final Session session_;
+ private final RemoteFileSystemContext fsContext_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/DefaultGlobalDisplay.java b/src/gwt/src/org/rstudio/studio/client/common/DefaultGlobalDisplay.java
new file mode 100644
index 0000000..1645342
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/DefaultGlobalDisplay.java
@@ -0,0 +1,354 @@
+/*
+ * DefaultGlobalDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandHandler;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.application.ApplicationView;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.core.client.widget.DialogBuilder;
+import org.rstudio.studio.client.common.dialog.DialogBuilderFactory;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+public class DefaultGlobalDisplay extends GlobalDisplay
+{
+ @Inject
+ public DefaultGlobalDisplay(Provider<ApplicationView> view,
+ Commands commands,
+ Session session,
+ ApplicationServerOperations server)
+ {
+ view_ = view;
+ session_ = session;
+ server_ = server;
+
+ commands.showWarningBar().addHandler(new CommandHandler()
+ {
+ public void onCommand(AppCommand command)
+ {
+ view_.get().showWarning(false, "This is a warning!");
+ }
+ });
+ }
+
+ @Override
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ final OperationWithInput<String> operation)
+ {
+ ((TextInput)GWT.create(TextInput.class)).promptForText(
+ title, label, initialValue, false, false, -1, -1, null,
+ new ProgressOperationWithInput<String>()
+ {
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ operation.execute(input);
+ }
+ },
+ null);
+ }
+
+ @Override
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ ProgressOperationWithInput<String> operation)
+ {
+ ((TextInput)GWT.create(TextInput.class)).promptForText(
+ title, label, initialValue, false, false, -1, -1, null, operation, null);
+ }
+
+ @Override
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ int selectionOffset,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<String> operation)
+ {
+ ((TextInput)GWT.create(TextInput.class)).promptForText(
+ title,
+ label,
+ initialValue,
+ false,
+ false,
+ selectionOffset,
+ selectionLength,
+ okButtonCaption,
+ operation,
+ null);
+ }
+
+ @Override
+ public void promptForTextWithOption(
+ String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ String extraOptionPrompt,
+ boolean extraOptionDefault,
+ ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation)
+ {
+ ((TextInput)GWT.create(TextInput.class)).promptForTextWithOption(
+ title,
+ label,
+ initialValue,
+ usePasswordMask,
+ extraOptionPrompt,
+ extraOptionDefault,
+ -1,
+ -1,
+ null,
+ okOperation,
+ cancelOperation);
+ }
+
+ @Override
+ public void promptForInteger(String title,
+ String label,
+ Integer initialValue,
+ final ProgressOperationWithInput<Integer> okOperation,
+ Operation cancelOperation)
+ {
+ ((TextInput)GWT.create(TextInput.class)).promptForText(
+ title,
+ label,
+ initialValue == null ? "" : initialValue.toString(),
+ false,
+ true,
+ -1,
+ -1,
+ null,
+ new ProgressOperationWithInput<String>()
+ {
+ @Override
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ int value = Integer.parseInt(input.trim());
+ okOperation.execute(value, indicator);
+ }
+ },
+ cancelOperation);
+ }
+
+ @Override
+ protected DialogBuilder createDialog(int type,
+ String caption,
+ String message)
+ {
+ return ((DialogBuilderFactory)GWT.create(DialogBuilderFactory.class))
+ .create(type, caption, message);
+ }
+
+ public Command showProgress(String message)
+ {
+ return SlideLabel.show(message, false, true, RootLayoutPanel.get());
+ }
+
+ public void showWarningBar(boolean severe, String message)
+ {
+ view_.get().showWarning(severe, message);
+ }
+
+ public void hideWarningBar()
+ {
+ view_.get().hideWarning();
+ }
+
+ public ProgressIndicator getProgressIndicator(final String errorCaption)
+ {
+ return new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ dismissProgress();
+ dismissProgress_ = showProgress(message);
+ }
+
+ public void clearProgress()
+ {
+ dismissProgress();
+ }
+
+ private void dismissProgress()
+ {
+ if (dismissProgress_ != null)
+ dismissProgress_.execute();
+ dismissProgress_ = null;
+ }
+
+ public void onCompleted()
+ {
+ dismissProgress();
+ }
+
+ public void onError(String message)
+ {
+ dismissProgress();
+ showMessage(GlobalDisplay.MSG_ERROR, errorCaption, message);
+ }
+
+ private Command dismissProgress_;
+ };
+ }
+
+ @Override
+ public void openWindow(String url)
+ {
+ openWindow(url, null);
+ }
+
+ @Override
+ public void openWindow(String url, NewWindowOptions options)
+ {
+ if (options == null)
+ options = new NewWindowOptions();
+
+ windowOpener_.openWindow(this,
+ url,
+ options);
+ }
+
+ @Override
+ public void openProgressWindow(String name,
+ String message,
+ OperationWithInput<WindowEx> openOperation)
+ {
+ String url = server_.getApplicationURL("progress");
+ url += "?message=" + URL.encodeQueryString(message);
+ NewWindowOptions options = new NewWindowOptions();
+ options.setName(name);
+ options.setCallback(openOperation);
+ openWindow(url, options);
+ }
+
+ @Override
+ public void openMinimalWindow(String url, int width, int height)
+ {
+ openMinimalWindow(url, false, width, height);
+ }
+
+ @Override
+ public void openMinimalWindow(String url,
+ boolean showLocation,
+ int width,
+ int height)
+ {
+ NewWindowOptions options = new NewWindowOptions();
+ options.setName("_blank");
+ options.setFocus(true);
+ openMinimalWindow(url, showLocation, width, height, options);
+ }
+
+ @Override
+ public void openMinimalWindow(String url,
+ boolean showLocation,
+ int width,
+ int height,
+ NewWindowOptions options)
+ {
+ windowOpener_.openMinimalWindow(this,
+ url,
+ options,
+ width,
+ height,
+ showLocation);
+ }
+
+ @Override
+ public void openSatelliteWindow(String name, int width, int height)
+ {
+ windowOpener_.openSatelliteWindow(this, name, width, height);
+ }
+
+
+ @Override
+ public void openEmailComposeWindow(String to, String subject)
+ {
+ // determine gmail url
+ String gmailURL = "https://mail.google.com/";
+ String user = session_.getSessionInfo().getUserIdentity();
+ if (user == null) // for desktop mode
+ user = "foo at gmail.com";
+ String[] userComponents = user.split("@");
+ if ( (userComponents.length == 2) &&
+ ("gmail.com").equalsIgnoreCase(userComponents[1]))
+ {
+ gmailURL += "mail/";
+ }
+ else
+ {
+ gmailURL += "a/" + userComponents[1] + "/";
+ }
+
+ // calculate URL
+ String url = gmailURL + "?fs=1&view=cm";
+ url += "&to=" + URL.encodeQueryString(to);
+ if (subject != null)
+ url += "&subject=" + URL.encodeQueryString(subject);
+
+ // open window
+ openWindow(url);
+ }
+
+ @Override
+ public void openRStudioLink(String linkName, boolean includeVersionInfo)
+ {
+ // build url
+ final SessionInfo sessionInfo = session_.getSessionInfo();
+ String url = "http://www.rstudio.org/links/" ;
+ url += URL.encodePathSegment(linkName) ;
+ if (includeVersionInfo)
+ {
+ url += "?version=" + URL.encodeQueryString(sessionInfo.getRstudioVersion());
+ url += "&mode=" + URL.encodeQueryString(sessionInfo.getMode());
+ }
+
+ // open window
+ openWindow(url);
+ }
+
+ @Override
+ public void showHtmlFile(String path)
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().showFile(path);
+ else
+ openWindow(server_.getFileUrl(FileSystemItem.createFile(path)));
+ }
+
+ private final Provider<ApplicationView> view_;
+ private final Session session_;
+ private final ApplicationServerOperations server_;
+ private final WindowOpener windowOpener_ = GWT.create(WindowOpener.class);
+
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/common/DelayedProgressRequestCallback.java b/src/gwt/src/org/rstudio/studio/client/common/DelayedProgressRequestCallback.java
new file mode 100644
index 0000000..230a903
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/DelayedProgressRequestCallback.java
@@ -0,0 +1,50 @@
+/*
+ * DelayedProgressRequestCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public abstract class DelayedProgressRequestCallback<T>
+ extends ServerRequestCallback<T>
+{
+ public DelayedProgressRequestCallback(String progressMessage)
+ {
+ indicator_ = new GlobalProgressDelayer(
+ RStudioGinjector.INSTANCE.getGlobalDisplay(),
+ 500,
+ progressMessage).getIndicator();
+ }
+
+ @Override
+ public void onResponseReceived(T response)
+ {
+ indicator_.onCompleted();
+ onSuccess(response);
+ }
+
+ protected abstract void onSuccess(T response);
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator_.onError(error.getUserMessage());
+ }
+
+ private ProgressIndicator indicator_;
+};
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/FileDialogs.java b/src/gwt/src/org/rstudio/studio/client/common/FileDialogs.java
new file mode 100644
index 0000000..bdea6ec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/FileDialogs.java
@@ -0,0 +1,45 @@
+/*
+ * FileDialogs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+public interface FileDialogs
+{
+ void openFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ ProgressOperationWithInput<FileSystemItem> operation);
+
+ void openFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ String filter,
+ ProgressOperationWithInput<FileSystemItem> operation);
+
+ void saveFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ String defaultExtension,
+ boolean forceDefaultExtension,
+ ProgressOperationWithInput<FileSystemItem> operation);
+
+ void chooseFolder(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialDir,
+ ProgressOperationWithInput<FileSystemItem> operation);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/FilePathUtils.java b/src/gwt/src/org/rstudio/studio/client/common/FilePathUtils.java
new file mode 100644
index 0000000..ca411e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/FilePathUtils.java
@@ -0,0 +1,64 @@
+/*
+ * FilePathUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class FilePathUtils
+{
+ public static String friendlyFileName(String unfriendlyFileName)
+ {
+ int idx = unfriendlyFileName.lastIndexOf("/");
+ if (idx < 0)
+ {
+ idx = unfriendlyFileName.lastIndexOf("\\");
+ }
+ return unfriendlyFileName.substring(
+ idx + 1, unfriendlyFileName.length()).trim();
+ }
+
+ public static String dirFromFile(String fileName)
+ {
+ int idx = fileName.lastIndexOf("/");
+ return idx > 0 ?
+ fileName.substring(0, idx) :
+ fileName;
+ }
+
+ public static String normalizePath (String path, String workingDirectory)
+ {
+ // Examine the path to see if it appears to be absolute. An absolute path
+ // - begins with ~ , or
+ // - begins with / (Unix-like systems), or
+ // - begins with F:/ (Windows systems), where F is an alphabetic drive
+ // letter.
+ if (path.startsWith(FileSystemItem.HOME_PREFIX) ||
+ path.startsWith("/") ||
+ path.matches("^[a-zA-Z]:\\/.*"))
+ {
+ return path;
+ }
+
+ // if the path appears to be relative, prepend the working directory
+ // (consider: should we try to handle ..-style relative notation here?)
+ String prefix = new String(workingDirectory +
+ (workingDirectory.endsWith("/") ? "" : "/"));
+ String relative = new String(path.startsWith("./") ?
+ path.substring(2, path.length()) : path);
+
+ return prefix + relative;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/GlobalDisplay.java b/src/gwt/src/org/rstudio/studio/client/common/GlobalDisplay.java
new file mode 100644
index 0000000..8dc9cc9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/GlobalDisplay.java
@@ -0,0 +1,109 @@
+/*
+ * GlobalDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.widget.*;
+
+public abstract class GlobalDisplay extends MessageDisplay
+{
+ public static class NewWindowOptions
+ {
+ public NewWindowOptions()
+ {
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public boolean isFocus()
+ {
+ return focus;
+ }
+
+ public void setFocus(boolean focus)
+ {
+ this.focus = focus;
+ }
+
+ public OperationWithInput<WindowEx> getCallback()
+ {
+ return callback;
+ }
+
+ public void setCallback(OperationWithInput<WindowEx> callback)
+ {
+ this.callback = callback;
+ }
+
+ private String name = "_blank";
+ private boolean focus = true;
+ private OperationWithInput<WindowEx> callback;
+ }
+
+ public abstract void openWindow(String url);
+ public abstract void openWindow(String url, NewWindowOptions options);
+
+ public abstract void openProgressWindow(String name,
+ String message,
+ OperationWithInput<WindowEx> openOperation);
+
+ public abstract void openMinimalWindow(String url, int width, int height);
+
+ public abstract void openMinimalWindow(String url,
+ boolean showLocation,
+ int width,
+ int height);
+
+ public abstract void openMinimalWindow(String url,
+ boolean showLocation,
+ int width,
+ int height,
+ NewWindowOptions options);
+
+ public abstract void openSatelliteWindow(String name, int width, int height);
+
+ public abstract void openEmailComposeWindow(String to, String subject);
+
+ public abstract void showHtmlFile(String path);
+
+ public void openRStudioLink(String linkName)
+ {
+ openRStudioLink(linkName, true);
+ }
+
+ public abstract void openRStudioLink(String linkName,
+ boolean includeVersionInfo);
+
+ /**
+ * Shows a non-modal progress message. Execute the returned command
+ * to dismiss.
+ */
+ public abstract Command showProgress(String message);
+
+ public abstract void showWarningBar(boolean severe, String message);
+ public abstract void hideWarningBar();
+
+ public abstract ProgressIndicator getProgressIndicator(String errorCaption);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/GlobalProgressDelayer.java b/src/gwt/src/org/rstudio/studio/client/common/GlobalProgressDelayer.java
new file mode 100644
index 0000000..0a08321
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/GlobalProgressDelayer.java
@@ -0,0 +1,84 @@
+/*
+ * GlobalProgressDelayer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.widget.ProgressIndicator;
+
+import com.google.gwt.user.client.Timer;
+
+public class GlobalProgressDelayer
+{
+ public GlobalProgressDelayer(GlobalDisplay globalDisplay,
+ final int delayMillis,
+ String progressMessage)
+ {
+ globalDisplay_ = globalDisplay;
+ progressMessage_ = progressMessage;
+
+ timer_ = new Timer()
+ {
+ @Override
+ public void run()
+ {
+ ensureIndicator();
+ }
+ };
+ timer_.schedule(delayMillis);
+ }
+
+ // NOTE: auto-creates the indicator if it doesn't already exist
+ public ProgressIndicator getIndicator()
+ {
+ ensureIndicator();
+ return indicator_;
+ }
+
+ public void setMessage(String progressMessage)
+ {
+ if (indicator_ != null)
+ indicator_.onProgress(progressMessage);
+ else
+ progressMessage_ = progressMessage;
+ }
+
+ public void dismiss()
+ {
+ if (timer_ != null )
+ {
+ timer_.cancel();
+ timer_ = null;
+ }
+
+ if (indicator_ != null)
+ {
+ indicator_.onCompleted();
+ indicator_ = null;
+ }
+ }
+
+ private void ensureIndicator()
+ {
+ if (indicator_ == null)
+ {
+ indicator_ = globalDisplay_.getProgressIndicator("Error");
+ indicator_.onProgress(progressMessage_);
+ }
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private String progressMessage_;
+ private Timer timer_;
+ private ProgressIndicator indicator_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/HelpLink.java b/src/gwt/src/org/rstudio/studio/client/common/HelpLink.java
new file mode 100644
index 0000000..03849e9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/HelpLink.java
@@ -0,0 +1,57 @@
+/*
+ * HelpLink.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+
+public class HelpLink extends Composite
+{
+ public HelpLink(String caption, final String rstudioLink)
+ {
+ HorizontalPanel helpPanel = new HorizontalPanel();
+
+
+ Image helpImage = new Image(ThemeResources.INSTANCE.help());
+ helpImage.getElement().getStyle().setMarginRight(4, Unit.PX);
+ helpPanel.add(helpImage);
+ helpLink_ = new HyperlinkLabel(caption);
+ helpLink_.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().openRStudioLink(
+ rstudioLink);
+ }
+ });
+ helpPanel.add(helpLink_);
+
+ initWidget(helpPanel);
+ }
+
+ public void setCaption(String caption)
+ {
+ helpLink_.setText(caption);
+ }
+
+ private HyperlinkLabel helpLink_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/ImageMenuItem.java b/src/gwt/src/org/rstudio/studio/client/common/ImageMenuItem.java
new file mode 100644
index 0000000..6d1ad3c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/ImageMenuItem.java
@@ -0,0 +1,23 @@
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class ImageMenuItem
+{
+ public static MenuItem create(ImageResource res,
+ String text,
+ ScheduledCommand command)
+ {
+ SafeHtmlBuilder shb = new SafeHtmlBuilder();
+ shb.appendHtmlConstant("<img src=\"" +
+ res.getSafeUri().asString() +
+ "\" style=\"vertical-align: middle; " +
+ "margin-right: 4px;\" />");
+ shb.appendEscaped(text);
+
+ return new MenuItem(shb.toSafeHtml(), command);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/JSONUtils.java b/src/gwt/src/org/rstudio/studio/client/common/JSONUtils.java
new file mode 100644
index 0000000..23e8994
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/JSONUtils.java
@@ -0,0 +1,46 @@
+/*
+ * JSONUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common;
+
+import java.util.List;
+
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.json.client.JSONString;
+
+public class JSONUtils
+{
+ public static <T extends Number> JSONArray toJSONNumberArray (List<T> list)
+ {
+ JSONArray json = new JSONArray();
+ for (int i = 0; i < list.size(); i++)
+ {
+ json.set(i, new JSONNumber(list.get(i).intValue()));
+ }
+ return json;
+ }
+
+ public static <T> JSONArray toJSONStringArray(List<T> list)
+ {
+ JSONArray json = new JSONArray();
+ for (int i = 0; i < list.size(); i++)
+ {
+ json.set(i, new JSONString((String)list.get(i)));
+ }
+ return json;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/NotifyingSplitLayoutPanel.java b/src/gwt/src/org/rstudio/studio/client/common/NotifyingSplitLayoutPanel.java
new file mode 100644
index 0000000..670906a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/NotifyingSplitLayoutPanel.java
@@ -0,0 +1,49 @@
+/*
+ * NotifyingSplitLayoutPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class NotifyingSplitLayoutPanel extends SplitLayoutPanel
+{
+ @Inject
+ public NotifyingSplitLayoutPanel(int splitterSize, EventBus events)
+ {
+ super(splitterSize);
+
+ events_ = events;
+
+ addSplitterBeforeResizeHandler(new SplitterBeforeResizeHandler()
+ {
+ public void onSplitterBeforeResize(SplitterBeforeResizeEvent event)
+ {
+ events_.fireEvent(new GlassVisibilityEvent(true));
+ }
+ });
+
+ addSplitterResizedHandler(new SplitterResizedHandler()
+ {
+ public void onSplitterResized(SplitterResizedEvent event)
+ {
+ events_.fireEvent(new GlassVisibilityEvent(false));
+ }
+ });
+ }
+
+ private final EventBus events_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/PackagesHelpLink.java b/src/gwt/src/org/rstudio/studio/client/common/PackagesHelpLink.java
new file mode 100644
index 0000000..9ea879e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/PackagesHelpLink.java
@@ -0,0 +1,26 @@
+/*
+ * PackagesHelpLink.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common;
+
+import org.rstudio.studio.client.common.HelpLink;
+
+public class PackagesHelpLink extends HelpLink
+{
+ public PackagesHelpLink()
+ {
+ super("Developing Packages with RStudio", "building_packages");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/ReadOnlyValue.java b/src/gwt/src/org/rstudio/studio/client/common/ReadOnlyValue.java
new file mode 100644
index 0000000..c65252d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/ReadOnlyValue.java
@@ -0,0 +1,22 @@
+/*
+ * ReadOnlyValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+
+public interface ReadOnlyValue<T> extends HasValueChangeHandlers<T>
+{
+ T getValue();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/SimpleRequestCallback.java b/src/gwt/src/org/rstudio/studio/client/common/SimpleRequestCallback.java
new file mode 100644
index 0000000..67f50f7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/SimpleRequestCallback.java
@@ -0,0 +1,44 @@
+/*
+ * SimpleRequestCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public class SimpleRequestCallback<T> extends ServerRequestCallback<T>
+{
+ public SimpleRequestCallback()
+ {
+ this("Error");
+ }
+
+ public SimpleRequestCallback(String caption)
+ {
+ caption_ = caption;
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ caption_,
+ error.getUserMessage());
+ }
+
+ private String caption_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/StudioResources.java b/src/gwt/src/org/rstudio/studio/client/common/StudioResources.java
new file mode 100644
index 0000000..bb6085f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/StudioResources.java
@@ -0,0 +1,26 @@
+/*
+ * StudioResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+
+public interface StudioResources extends ClientBundle
+{
+ public static final StudioResources INSTANCE = GWT.create(StudioResources.class);
+
+ @Source("cstyles.css")
+ StudioStyles styles();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/StudioStyles.java b/src/gwt/src/org/rstudio/studio/client/common/StudioStyles.java
new file mode 100644
index 0000000..24ccf76
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/StudioStyles.java
@@ -0,0 +1,21 @@
+/*
+ * StudioStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface StudioStyles extends CssResource
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/StyleUtils.java b/src/gwt/src/org/rstudio/studio/client/common/StyleUtils.java
new file mode 100644
index 0000000..9b3ccaa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/StyleUtils.java
@@ -0,0 +1,36 @@
+/*
+ * StyleUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+
+import org.rstudio.studio.client.application.Desktop;
+
+import com.google.gwt.user.client.ui.Widget;
+
+public class StyleUtils
+{
+ public static void forceMacScrollbars(Widget widget)
+ {
+ if (!Desktop.isDesktop() &&
+ BrowseCap.isMacintosh() &&
+ !BrowseCap.isFirefox())
+ {
+ widget.addStyleName(ThemeStyles.INSTANCE.forceMacScrollbars());
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/SuperDevMode.java b/src/gwt/src/org/rstudio/studio/client/common/SuperDevMode.java
new file mode 100644
index 0000000..240af9e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/SuperDevMode.java
@@ -0,0 +1,40 @@
+/*
+ * SuperDevMode.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.core.client.GWT;
+
+public class SuperDevMode
+{
+ public final static native void reload() /*-{
+ $wnd.__gwt_bookmarklet_params = {
+ server_url:'http://localhost:9876/',
+ module_name:'rstudio'
+ };
+
+ var s = $doc.createElement('script');
+ s.src = 'http://localhost:9876/dev_mode_on.js';
+ void($doc.getElementsByTagName('head')[0].appendChild(s));
+ }-*/ ;
+
+
+ public static boolean isActive()
+ {
+ return
+ GWT.getModuleBaseURL().equals("http://localhost:8787/rstudio/") &&
+ GWT.getModuleBaseForStaticFiles().equals("http://localhost:9876/rstudio/");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/TextInput.java b/src/gwt/src/org/rstudio/studio/client/common/TextInput.java
new file mode 100644
index 0000000..bc4b766
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/TextInput.java
@@ -0,0 +1,47 @@
+/*
+ * TextInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.core.client.MessageDisplay.PromptWithOptionResult;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+
+public interface TextInput
+{
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ boolean numbersOnly,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<String> okOperation,
+ Operation cancelOperation);
+
+ void promptForTextWithOption(
+ String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ // Null or "" means don't prompt for remembering pw
+ String extraOptionPrompt,
+ boolean extraOptionDefault,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/Value.java b/src/gwt/src/org/rstudio/studio/client/common/Value.java
new file mode 100644
index 0000000..1e7cc42
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/Value.java
@@ -0,0 +1,78 @@
+/*
+ * Value.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.HasValue;
+
+public class Value<T> implements HasValue<T>, ReadOnlyValue<T>
+{
+ public Value(T initialValue)
+ {
+ handlers_ = new HandlerManager(this);
+ value_ = initialValue;
+ }
+
+ public T getValue()
+ {
+ return value_;
+ }
+
+ public void setValue(T value)
+ {
+ setValue(value, false);
+ }
+
+ public void setValue(T value, boolean fireEvents)
+ {
+ if (!areEqual(value_, value))
+ {
+ value_ = value;
+ if (fireEvents)
+ ValueChangeEvent.fire(this, value);
+ }
+ }
+
+ public void fireChangeEvent()
+ {
+ ValueChangeEvent.fire(this, value_);
+ }
+
+ private boolean areEqual(T a, T b)
+ {
+ if (a == null ^ b == null)
+ return false;
+ if (a == null)
+ return true;
+ return a.equals(b);
+ }
+
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler)
+ {
+ return handlers_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ private final HandlerManager handlers_;
+ private T value_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/WindowOpener.java b/src/gwt/src/org/rstudio/studio/client/common/WindowOpener.java
new file mode 100644
index 0000000..2206fb2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/WindowOpener.java
@@ -0,0 +1,36 @@
+/*
+ * WindowOpener.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common;
+
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+
+public interface WindowOpener
+{
+ void openWindow(GlobalDisplay globalDisplay,
+ final String url,
+ NewWindowOptions options);
+
+ void openMinimalWindow(GlobalDisplay globalDisplay,
+ final String url,
+ NewWindowOptions options,
+ int width,
+ int height,
+ boolean showLocation);
+
+ void openSatelliteWindow(GlobalDisplay globalDisplay,
+ String viewName,
+ int width,
+ int height);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/codetools/CodeToolsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/codetools/CodeToolsServerOperations.java
new file mode 100644
index 0000000..f19cc76
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/codetools/CodeToolsServerOperations.java
@@ -0,0 +1,30 @@
+/*
+ * CodeToolsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.codetools;
+
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchServerOperations;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+
+public interface CodeToolsServerOperations extends HelpServerOperations,
+ CodeSearchServerOperations
+{
+ void getCompletions(String line, int cursorPos,
+ ServerRequestCallback<Completions> completions);
+
+ void getHelpAtCursor(
+ String line, int cursorPos,
+ ServerRequestCallback<org.rstudio.studio.client.server.Void> callback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/codetools/Completions.java b/src/gwt/src/org/rstudio/studio/client/common/codetools/Completions.java
new file mode 100644
index 0000000..a2e9c72
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/codetools/Completions.java
@@ -0,0 +1,79 @@
+/*
+ * Completions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.codetools;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class Completions extends JavaScriptObject
+{
+ public static native Completions createCompletions(String token,
+ JsArrayString completions,
+ JsArrayString packages,
+ String fguess) /*-{
+ return {
+ token: [token],
+ results: completions,
+ packages: packages,
+ fguess: fguess ? [fguess] : null
+ };
+ }-*/;
+
+ protected Completions()
+ {
+ }
+
+ public final native String getToken() /*-{
+ return this.token[0] ;
+ }-*/;
+
+ public final native JsArrayString getCompletions() /*-{
+ return this.results ;
+ }-*/;
+
+ public final native JsArrayString getPackages() /*-{
+ // Packages end up as arrays of arrays because I suck at R.
+ // results: [["base"], null, null, ["graphics"], null]
+ // => ["base", null, null, "graphics", null]
+
+ return this.packages;
+ }-*/;
+
+ /**
+ * If rcompgen thinks we're doing function args, this
+ * returns the name of the function it thinks we're in
+ */
+ public final native String getGuessedFunctionName() /*-{
+ if (!this.fguess)
+ return null ;
+ return this.fguess[0] ;
+ }-*/;
+
+ public final native void setCacheable(boolean cacheable) /*-{
+ this.nocache = !cacheable;
+ }-*/;
+
+ public final native boolean isCacheable() /*-{
+ return !this.nocache;
+ }-*/;
+
+ public final native void setSuggestOnAccept(boolean suggestOnAccept) /*-{
+ this.suggestOnAccept = suggestOnAccept;
+ }-*/;
+
+ public final native boolean getSuggestOnAccept() /*-{
+ return !!this.suggestOnAccept;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompileError.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileError.java
new file mode 100644
index 0000000..fd494e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileError.java
@@ -0,0 +1,107 @@
+/*
+ * CompileError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compile;
+
+import org.rstudio.core.client.js.JsUtil;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class CompileError extends JavaScriptObject
+{
+ // all possible errors across all compilation types should be listed
+ // here -- we do this because the way we display error icons is via
+ // css styles and it's a bit complicated to dynamically compose the
+ // stylesheet for different compilation scenarios
+ public static final int ERROR = 0;
+ public static final int WARNING = 1;
+ public static final int BOX = 2; // LaTeX bad box error
+
+ protected CompileError()
+ {
+ }
+
+ public final native int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ /*
+ * Can return -1 for unknown line
+ */
+ public final native int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public final native int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public final native String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+ /*
+ * Can return empty string for no log path
+ */
+ public final native String getLogPath() /*-{
+ return this.log_path;
+ }-*/;
+
+ /*
+ * Can return -1 for unknown line
+ */
+ public final native int getLogLine() /*-{
+ return this.log_line;
+ }-*/;
+
+ public final native boolean getShowErrorList() /*-{
+ if (this.show_error_list === null)
+ return true;
+ else
+ return this.show_error_list;
+ }-*/;
+
+ public final static boolean showErrorList(JsArray<CompileError> errors)
+ {
+ if (errors == null)
+ return false;
+
+ for (CompileError error : JsUtil.asIterable(errors))
+ {
+ if ((error.getType() == CompileError.ERROR) &&
+ error.getShowErrorList())
+ return true;
+ }
+
+ return false;
+ }
+
+ public final static CompileError getFirstError(JsArray<CompileError> errors)
+ {
+ for (int i=0; i<errors.length(); i++)
+ {
+ CompileError error = errors.get(i);
+ if (error.getType() == CompileError.ERROR)
+ return error;
+ }
+
+ return null;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutput.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutput.java
new file mode 100644
index 0000000..a1d4ed0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutput.java
@@ -0,0 +1,37 @@
+/*
+ * CompileOutput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compile;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompileOutput extends JavaScriptObject
+{
+ protected CompileOutput()
+ {
+ }
+
+ public static final int kCommand = 0;
+ public static final int kNormal = 1;
+ public static final int kError = 2;
+
+ public native final int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public native final String getOutput() /*-{
+ return this.output;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBuffer.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBuffer.java
new file mode 100644
index 0000000..6b5653e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBuffer.java
@@ -0,0 +1,88 @@
+/*
+ * CompileOutputBuffer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.compile;
+
+import org.rstudio.core.client.VirtualConsole;
+import org.rstudio.core.client.widget.BottomScrollPanel;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.PreWidget;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+
+import com.google.gwt.user.client.ui.Composite;
+
+public class CompileOutputBuffer extends Composite
+ implements CompileOutputDisplay
+{
+ public CompileOutputBuffer()
+ {
+ output_ = new PreWidget();
+ output_.setStylePrimaryName(
+ ConsoleResources.INSTANCE.consoleStyles().output());
+ FontSizer.applyNormalFontSize(output_);
+
+ scrollPanel_ = new BottomScrollPanel();
+ scrollPanel_.setSize("100%", "100%");
+ scrollPanel_.add(output_);
+
+ initWidget(scrollPanel_);
+ }
+
+ public void append(String output)
+ {
+ virtualConsole_.submit(output);
+ output_.setText(virtualConsole_.toString());
+
+ scrollPanel_.onContentSizeChanged();
+ }
+
+ @Override
+ public void writeCommand(String command)
+ {
+ append(command);
+
+ }
+
+ @Override
+ public void writeOutput(String output)
+ {
+ append(output);
+
+ }
+
+ @Override
+ public void writeError(String error)
+ {
+ append(error);
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ scrollPanel_.scrollToBottom();
+ }
+
+ @Override
+ public void clear()
+ {
+ output_.setText("");
+ virtualConsole_ = new VirtualConsole();
+ }
+
+ private PreWidget output_;
+ private VirtualConsole virtualConsole_ = new VirtualConsole();
+ private BottomScrollPanel scrollPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBufferWithHighlight.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBufferWithHighlight.java
new file mode 100644
index 0000000..d1e3796
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputBufferWithHighlight.java
@@ -0,0 +1,102 @@
+/*
+ * CompileOutputBufferWithHighlight.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.compile;
+
+import org.rstudio.core.client.widget.BottomScrollPanel;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.PreWidget;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.user.client.ui.Composite;
+
+public class CompileOutputBufferWithHighlight extends Composite
+ implements CompileOutputDisplay
+{
+ public CompileOutputBufferWithHighlight()
+ {
+ styles_ = ConsoleResources.INSTANCE.consoleStyles();
+
+ output_ = new PreWidget();
+ output_.setStylePrimaryName(styles_.output());
+ output_.addStyleName("ace_text-layer");
+ output_.addStyleName("ace_line");
+ output_.setWidth("100%");
+ FontSizer.applyNormalFontSize(output_);
+
+ scrollPanel_ = new BottomScrollPanel();
+ scrollPanel_.setSize("100%", "100%");
+ scrollPanel_.addStyleName("ace_editor");
+ scrollPanel_.addStyleName("ace_scroller");
+ scrollPanel_.setWidget(output_);
+
+ initWidget(scrollPanel_);
+ }
+
+
+ @Override
+ public void writeCommand(String command)
+ {
+ write(command, styles_.command() + ConsoleResources.KEYWORD_CLASS_NAME);
+ }
+
+ @Override
+ public void writeOutput(String output)
+ {
+ write(output, styles_.output());
+ }
+
+ @Override
+ public void writeError(String error)
+ {
+ write(error, getErrorClass());
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ scrollPanel_.scrollToBottom();
+ }
+
+ @Override
+ public void clear()
+ {
+ output_.setText("");
+ }
+
+ private void write(String output, String className)
+ {
+ SpanElement span = Document.get().createSpanElement();
+ span.setClassName(className);
+ span.setInnerText(output);
+ output_.getElement().appendChild(span);
+
+ scrollPanel_.onContentSizeChanged();
+ }
+
+ private String getErrorClass()
+ {
+ return styles_.output() + " " +
+ RStudioGinjector.INSTANCE.getUIPrefs().getThemeErrorClass();
+ }
+
+ PreWidget output_;
+ private BottomScrollPanel scrollPanel_;
+ private ConsoleResources.ConsoleStyles styles_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputDisplay.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputDisplay.java
new file mode 100644
index 0000000..3c4d87c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompileOutputDisplay.java
@@ -0,0 +1,32 @@
+/*
+ * CompileOutputDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.compile;
+
+import com.google.gwt.user.client.ui.Widget;
+
+public interface CompileOutputDisplay
+{
+ Widget asWidget();
+
+ public void writeCommand(String command);
+ public void writeOutput(String output);
+ public void writeError(String error);
+
+ public void clear();
+ public void scrollToBottom();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/CompilePanel.java b/src/gwt/src/org/rstudio/studio/client/common/compile/CompilePanel.java
new file mode 100644
index 0000000..08880b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/CompilePanel.java
@@ -0,0 +1,202 @@
+/*
+ * CompilePanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.compile;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.widget.LeftRightToggleButton;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorList;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class CompilePanel extends Composite
+{
+ public CompilePanel()
+ {
+ this(new CompileOutputBuffer());
+ }
+
+ public CompilePanel(CompileOutputDisplay outputDisplay)
+ {
+
+ panel_ = new SimplePanel();
+ outputDisplay_ = outputDisplay;
+
+ panel_.setWidget(outputDisplay_.asWidget());
+ errorList_ = new CompileErrorList();
+
+ initWidget(panel_);
+ }
+
+ public void connectToolbar(Toolbar toolbar)
+ {
+ Commands commands = RStudioGinjector.INSTANCE.getCommands();
+ ImageResource stopImage = commands.interruptR().getImageResource();
+ stopButton_ = new ToolbarButton(stopImage, null);
+ stopButton_.setVisible(false);
+ toolbar.addRightWidget(stopButton_);
+
+ showOutputButton_ = new LeftRightToggleButton("Output", "Issues", false);
+ showOutputButton_.setVisible(false);
+ showOutputButton_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ showOutputButton_.setVisible(false);
+ showErrorsButton_.setVisible(true);
+ panel_.setWidget(outputDisplay_.asWidget());
+ outputDisplay_.scrollToBottom();
+ }
+ });
+ toolbar.addRightWidget(showOutputButton_);
+
+ showErrorsButton_ = new LeftRightToggleButton("Output", "Issues", true);
+ showErrorsButton_.setVisible(false);
+ showErrorsButton_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ showOutputButton_.setVisible(true);
+ showErrorsButton_.setVisible(false);
+ panel_.setWidget(errorList_);
+ }
+ });
+ toolbar.addRightWidget(showErrorsButton_);
+ }
+
+ // NOTE: targetFileName enables optional suppressing of the file
+ // name header in the error list -- it's fine to pass null for this
+ // and in that case there will always be a header
+ public void compileStarted(String targetFileName)
+ {
+ clearAll();
+
+ targetFileName_ = targetFileName;
+
+ showOutputButton_.setVisible(false);
+ showErrorsButton_.setVisible(false);
+ stopButton_.setVisible(true);
+ }
+
+ public void clearAll()
+ {
+ targetFileName_ = null;
+ showOutputButton_.setVisible(false);
+ showErrorsButton_.setVisible(false);
+ stopButton_.setVisible(false);
+ outputDisplay_.clear();
+ errorList_.clear();
+ panel_.setWidget(outputDisplay_.asWidget());
+ }
+
+ public void showOutput(CompileOutput output)
+ {
+ switch(output.getType())
+ {
+ case CompileOutput.kCommand:
+ outputDisplay_.writeCommand(output.getOutput());
+ break;
+ case CompileOutput.kNormal:
+ outputDisplay_.writeOutput(output.getOutput());
+ break;
+ case CompileOutput.kError:
+ outputDisplay_.writeError(output.getOutput());
+ break;
+ }
+ }
+
+ public void showOutput(String output)
+ {
+ outputDisplay_.writeOutput(output);
+ }
+
+ public void showErrors(String basePath,
+ JsArray<CompileError> errors,
+ int autoSelect)
+ {
+ showErrors(basePath, errors, autoSelect, false);
+ }
+
+ public void showErrors(String basePath,
+ JsArray<CompileError> errors,
+ int autoSelect,
+ boolean alwaysShowList)
+ {
+ errorList_.showErrors(targetFileName_,
+ basePath,
+ errors,
+ autoSelect);
+
+ if (alwaysShowList || CompileError.showErrorList(errors))
+ {
+ panel_.setWidget(errorList_);
+ showOutputButton_.setVisible(true);
+ }
+ else
+ {
+ showErrorsButton_.setVisible(true);
+ }
+ }
+
+
+ public void scrollToBottom()
+ {
+ outputDisplay_.scrollToBottom();
+ }
+
+ public void compileCompleted()
+ {
+ stopButton_.setVisible(false);
+
+ if (isErrorPanelShowing())
+ errorList_.focus();
+ }
+
+ public HasClickHandlers stopButton()
+ {
+ return stopButton_;
+ }
+
+ public HasSelectionCommitHandlers<CodeNavigationTarget> errorList()
+ {
+ return errorList_;
+ }
+
+ private boolean isErrorPanelShowing()
+ {
+ return errorList_.isAttached();
+ }
+
+ private String targetFileName_;
+
+ private ToolbarButton stopButton_;
+ private LeftRightToggleButton showOutputButton_;
+ private LeftRightToggleButton showErrorsButton_;
+ private SimplePanel panel_;
+ private CompileOutputDisplay outputDisplay_;
+ private CompileErrorList errorList_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorItemCodec.java b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorItemCodec.java
new file mode 100644
index 0000000..b120c67
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorItemCodec.java
@@ -0,0 +1,199 @@
+/*
+ * CompileErrorItemCodec.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compile.errorlist;
+
+import com.google.gwt.dom.client.*;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.HeaderBreaksItemCodec;
+import org.rstudio.studio.client.common.compile.CompileError;
+
+public class CompileErrorItemCodec
+ extends HeaderBreaksItemCodec<CompileError, CodeNavigationTarget, CodeNavigationTarget>
+{
+ public CompileErrorItemCodec(CompileErrorListResources resources,
+ boolean showFileHeaders)
+ {
+ resources_ = resources;
+ showFileHeaders_ = showFileHeaders;
+ }
+
+ public boolean getShowFileHandlers()
+ {
+ return showFileHeaders_;
+ }
+
+ public void setShowFileHeaders(boolean show)
+ {
+ showFileHeaders_ = show;
+ }
+
+ public void setFileHeaderBasePath(String basePath)
+ {
+ fileHeaderBasePath_ = basePath;
+ }
+
+ @Override
+ public TableRowElement getRowForItem(CompileError entry)
+ {
+ TableRowElement tr = Document.get().createTRElement();
+ tr.setAttribute(DATA_PATH,
+ entry.getPath());
+ tr.setAttribute(DATA_LINE,
+ entry.getLine() + "");
+ tr.setAttribute(DATA_COLUMN,
+ entry.getColumn() + "");
+ tr.setAttribute(LOG_PATH,
+ entry.getLogPath());
+ tr.setAttribute(LOG_LINE,
+ entry.getLogLine() + "");
+
+ TableCellElement tdIcon = Document.get().createTDElement();
+ tdIcon.setClassName(resources_.styles().iconCell());
+ DivElement iconDiv = Document.get().createDivElement();
+ iconDiv.setClassName(
+ entry.getType() == CompileError.ERROR ? resources_.styles().errorIcon() :
+ entry.getType() == CompileError.WARNING ? resources_.styles().warningIcon() :
+ resources_.styles().boxIcon());
+ tdIcon.appendChild(iconDiv);
+ tr.appendChild(tdIcon);
+
+ TableCellElement tdLine = Document.get().createTDElement();
+ tdLine.setClassName(resources_.styles().lineCell());
+ if (entry.getLine() >= 0)
+ tdLine.setInnerText("Line " + entry.getLine());
+ tr.appendChild(tdLine);
+
+ TableCellElement tdMsg = Document.get().createTDElement();
+ tdMsg.setClassName(resources_.styles().messageCell());
+ tdMsg.setInnerText(entry.getMessage());
+ tr.appendChild(tdMsg);
+
+ TableCellElement tdDiscButton = maybeCreateDisclosureButton(entry);
+ if (tdDiscButton != null)
+ tr.appendChild(tdDiscButton);
+
+ return tr;
+
+ }
+
+ protected TableCellElement maybeCreateDisclosureButton(CompileError entry)
+ {
+ if (entry.getLogLine() != -1)
+ {
+ TableCellElement td = Document.get().createTDElement();
+ td.setClassName(resources_.styles().disclosure());
+ td.setVAlign("middle");
+
+ DivElement div = Document.get().createDivElement();
+ div.setTitle("View error or warning within the log file");
+ div.setClassName(resources_.styles().disclosure());
+
+ td.appendChild(div);
+ return td;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean needsBreak(TableRowElement prevRow, TableRowElement row)
+ {
+ if (!showFileHeaders_)
+ return false;
+
+ if (prevRow == null)
+ return true;
+
+ String a = StringUtil.notNull(row.getAttribute(DATA_PATH));
+ String b = StringUtil.notNull(prevRow.getAttribute(DATA_PATH));
+ return !a.equals(b);
+ }
+
+ @Override
+ protected int addBreak(TableRowElement row)
+ {
+ TableRowElement headerRow = Document.get().createTRElement();
+ headerRow.setClassName(resources_.styles().headerRow());
+
+ TableCellElement cell = Document.get().createTDElement();
+ cell.setColSpan(3);
+
+ String path = row.getAttribute(DATA_PATH);
+ if (fileHeaderBasePath_ != null)
+ {
+ if (path.startsWith(fileHeaderBasePath_))
+ path = path.substring(fileHeaderBasePath_.length());
+ }
+ cell.setInnerText(path);
+
+ headerRow.appendChild(cell);
+
+ row.getParentElement().insertBefore(headerRow, row);
+
+ return 1;
+ }
+
+ @Override
+ public CodeNavigationTarget getOutputForRow(TableRowElement row)
+ {
+ String path = row.getAttribute(DATA_PATH);
+ int line = Integer.parseInt(row.getAttribute(DATA_LINE));
+ if (line < 0) // If we couldn't figure out the line
+ line = 1;
+ int column = Integer.parseInt(row.getAttribute(DATA_COLUMN));
+ if (column < 0) // If we couldn't figure out the column
+ column = 1;
+
+ return new CodeNavigationTarget(path,
+ FilePosition.create(line, column));
+ }
+
+ @Override
+ public CodeNavigationTarget getOutputForRow2(TableRowElement row)
+ {
+ String path = row.getAttribute(LOG_PATH);
+ int line = Integer.parseInt(row.getAttribute(LOG_LINE));
+ if (line < 0) // If we couldn't figure out the line
+ line = 1;
+ return new CodeNavigationTarget(path,
+ FilePosition.create(line, 1));
+ }
+
+ @Override
+ public boolean isValueRow(TableRowElement row)
+ {
+ return row.hasAttribute(DATA_LINE);
+ }
+
+ @Override
+ public boolean hasNonValueRows()
+ {
+ return showFileHeaders_;
+ }
+
+ private final CompileErrorListResources resources_;
+ private boolean showFileHeaders_;
+ private String fileHeaderBasePath_ = null;
+
+ private static final String DATA_PATH = "data-path";
+ private static final String DATA_LINE = "data-line";
+ private static final String DATA_COLUMN = "data-column";
+ private static final String LOG_PATH = "log-path";
+ private static final String LOG_LINE = "log-line";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.css b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.css
new file mode 100644
index 0000000..763b913
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.css
@@ -0,0 +1,67 @@
+
+.iconCell {
+ vertical-align: top;
+ width: 34px;
+ padding: 2px 3px;
+}
+
+.lineCell {
+ color: #777;
+ vertical-align: top;
+ white-space: nowrap;
+ width: 1%;
+ padding-right: 8px;
+}
+
+.selectedRow .lineCell {
+ color: #666;
+}
+
+.messageCell {
+ width: 100%;
+ vertical-align: top;
+ padding-right: 8px;
+}
+
+.selectedRow {
+ background-color: #ccc;
+}
+*:focus .selectedRow {
+ background-color: rgb(146, 193, 240)
+}
+ at if user.agent ie8 {
+ .selectedRow {
+ background-color: rgb(146, 193, 240)
+ }
+}
+
+.headerRow td {
+ font-weight: bold;
+ padding: 2px 2px 2px 3px;
+ width: 100%;
+}
+
+.table {
+ outline: none;
+}
+
+ at sprite .errorIcon {
+ gwt-image: 'error';
+}
+
+ at sprite .warningIcon {
+ gwt-image: 'warning';
+}
+
+ at sprite .boxIcon {
+ gwt-image: 'badbox';
+}
+
+div.disclosure {
+ cursor: pointer;
+ margin-top: 1px;
+ margin-right: 3px;
+}
+ at sprite .selectedRow div.disclosure {
+ gwt-image: 'logContextButton';
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.java b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.java
new file mode 100644
index 0000000..bcf993d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorList.java
@@ -0,0 +1,206 @@
+/*
+ * CompileErrorList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compile.errorlist;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.DoubleClickState;
+import org.rstudio.core.client.widget.FastSelectTable;
+import org.rstudio.studio.client.common.compile.CompileError;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.TableColElement;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.ScrollPanel;
+
+public class CompileErrorList extends Composite
+ implements HasSelectionCommitHandlers<CodeNavigationTarget>
+{
+ public static final int AUTO_SELECT_NONE = 0;
+ public static final int AUTO_SELECT_FIRST = 1;
+ public static final int AUTO_SELECT_FIRST_ERROR = 2;
+
+ public CompileErrorList()
+ {
+ codec_ = new CompileErrorItemCodec(res_, false);
+
+ errorTable_ = new FastSelectTable<CompileError, CodeNavigationTarget, CodeNavigationTarget>(
+ codec_,
+ res_.styles().selectedRow(),
+ true,
+ false);
+ setWidths();
+ errorTable_.setStyleName(res_.styles().table());
+ errorTable_.setSize("100%", "100%");
+ errorTable_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (doubleClick_.checkForDoubleClick(event.getNativeEvent()))
+ {
+ fireSelectionCommittedEvent();
+ }
+ }
+ private final DoubleClickState doubleClick_ = new DoubleClickState();
+ });
+ errorTable_.addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ fireSelectionCommittedEvent();
+ }
+ });
+
+ errorTable_.addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ Element el = DOM.eventGetTarget((Event) event.getNativeEvent());
+ if (el != null
+ && el.getTagName().equalsIgnoreCase("div")
+ && el.getClassName().equals(res_.styles().disclosure()))
+ {
+ ArrayList<CodeNavigationTarget> values =
+ errorTable_.getSelectedValues2();
+ if (values.size() == 1)
+ {
+ fireSelectionCommitedEvent(values.get(0));
+ }
+ }
+ }
+ });
+
+
+ ScrollPanel scrollPanel = new ScrollPanel(errorTable_);
+ scrollPanel.setSize("100%", "100%");
+ initWidget(scrollPanel);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<CodeNavigationTarget> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType());
+ }
+
+ public void showErrors(String targetFile,
+ String basePath,
+ JsArray<CompileError> errors,
+ int autoSelect)
+ {
+ boolean showFileHeaders = false;
+ ArrayList<CompileError> errorList = new ArrayList<CompileError>();
+ int firstErrorIndex = -1;
+ for (int i=0; i<errors.length(); i++)
+ {
+ CompileError error = errors.get(i);
+ if (firstErrorIndex == -1 && error.getType() == CompileError.ERROR)
+ firstErrorIndex = i;
+
+ if (!error.getPath().equals(targetFile))
+ showFileHeaders = true;
+
+ errorList.add(error);
+ }
+
+ codec_.setShowFileHeaders(showFileHeaders);
+ codec_.setFileHeaderBasePath(basePath);
+ errorTable_.addItems(errorList, false);
+
+ if (autoSelect == AUTO_SELECT_FIRST)
+ {
+ selectFirstItem();
+ }
+ else if (autoSelect == AUTO_SELECT_FIRST_ERROR)
+ {
+ if (firstErrorIndex != -1)
+ errorTable_.setSelected(firstErrorIndex, 1, true);
+ }
+ }
+
+ public void selectFirstItem()
+ {
+ if (errorTable_.getRowCount() > 0)
+ errorTable_.setSelected(0, 1, true);
+ }
+
+ public void focus()
+ {
+ errorTable_.focus();
+ }
+
+ public void clear()
+ {
+ errorTable_.clear();
+ setWidths();
+ }
+
+ private void setWidths()
+ {
+ setColumnClasses(errorTable_.getElement().<TableElement>cast(),
+ res_.styles().iconCell(),
+ res_.styles().lineCell(),
+ res_.styles().messageCell());
+ }
+
+ private void setColumnClasses(TableElement table,
+ String... classes)
+ {
+ TableColElement colGroupElement = Document.get().createColGroupElement();
+ for (String clazz : classes)
+ {
+ TableColElement colElement = Document.get().createColElement();
+ colElement.setClassName(clazz);
+ colGroupElement.appendChild(colElement);
+ }
+ table.appendChild(colGroupElement);
+ }
+
+ private void fireSelectionCommittedEvent()
+ {
+ ArrayList<CodeNavigationTarget> values = errorTable_.getSelectedValues();
+ if (values.size() == 1)
+ fireSelectionCommitedEvent(values.get(0));
+ }
+
+ private void fireSelectionCommitedEvent(CodeNavigationTarget target)
+ {
+ SelectionCommitEvent.fire(this, target);
+ }
+
+ private final CompileErrorItemCodec codec_;
+ private final FastSelectTable<CompileError, CodeNavigationTarget, CodeNavigationTarget> errorTable_;
+ private final CompileErrorListResources res_ = CompileErrorListResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorListResources.java b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorListResources.java
new file mode 100644
index 0000000..2d00a17
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/CompileErrorListResources.java
@@ -0,0 +1,57 @@
+/*
+ * CompileErrorListResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compile.errorlist;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface CompileErrorListResources extends ClientBundle
+{
+ public static interface Styles extends CssResource
+ {
+ String table();
+ String headerRow();
+ String selectedRow();
+ String iconCell();
+ String errorIcon();
+ String warningIcon();
+ String boxIcon();
+ String lineCell();
+ String messageCell();
+ String disclosure();
+ }
+
+ @Source("images/error.png")
+ ImageResource error();
+
+ @Source("org/rstudio/core/client/theme/res/warningSmall.png")
+ ImageResource warning();
+
+ @Source("images/badbox.png")
+ ImageResource badbox();
+
+ @Source("CompileErrorList.css")
+ Styles styles();
+
+ @Source("images/logContextButton.png")
+ ImageResource logContextButton();
+
+ public static CompileErrorListResources INSTANCE =
+ (CompileErrorListResources)GWT.create(CompileErrorListResources.class) ;
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/badbox.png b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/badbox.png
new file mode 100644
index 0000000..f5fb411
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/badbox.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/error.png b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/error.png
new file mode 100644
index 0000000..e82a519
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/error.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/logContextButton.png b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/logContextButton.png
new file mode 100644
index 0000000..c2f854c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/compile/errorlist/images/logContextButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/dialog/CompilePdfProgressDialog.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/dialog/CompilePdfProgressDialog.java
new file mode 100644
index 0000000..3594c3d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/dialog/CompilePdfProgressDialog.java
@@ -0,0 +1,153 @@
+/*
+ * CompilePdfProgressDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compilepdf.dialog;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.ProgressDialog;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutputBuffer;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorList;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfOutputEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfErrorsEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfResult;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class CompilePdfProgressDialog extends ProgressDialog
+ implements CompilePdfOutputEvent.Handler,
+ CompilePdfErrorsEvent.Handler,
+ CompilePdfCompletedEvent.Handler,
+ HasClickHandlers,
+ HasSelectionCommitHandlers<CodeNavigationTarget>
+{
+ public CompilePdfProgressDialog()
+ {
+ super("Compiling PDF...");
+
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ errorList_ = new CompileErrorList();
+
+ addHandlerRegistration(eventBus_.addHandler(
+ CompilePdfOutputEvent.TYPE, this));
+ addHandlerRegistration(eventBus_.addHandler(
+ CompilePdfErrorsEvent.TYPE, this));
+ addHandlerRegistration(eventBus_.addHandler(
+ CompilePdfCompletedEvent.TYPE, this));
+ }
+
+
+ @Inject
+ void initialize(EventBus eventBus)
+ {
+ eventBus_ = eventBus;
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return stopButton().addClickHandler(handler);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<CodeNavigationTarget> handler)
+ {
+ return errorList_.addSelectionCommitHandler(handler);
+ }
+
+ public void dismiss()
+ {
+ closeDialog();
+ }
+
+ @Override
+ protected Widget createDisplayWidget(Object param)
+ {
+ container_ = new SimplePanel();
+ int maxHeight = Window.getClientHeight() - 150;
+ int height = Math.min(500, maxHeight);
+ container_.getElement().getStyle().setHeight(height, Unit.PX);
+
+ output_ = new CompileOutputBuffer();
+ container_.setWidget(output_);
+ return container_;
+ }
+
+ @Override
+ public void onCompilePdfOutput(CompilePdfOutputEvent event)
+ {
+ output_.append(event.getOutput());
+ }
+
+ @Override
+ public void onCompilePdfErrors(CompilePdfErrorsEvent event)
+ {
+ errors_ = event.getErrors();
+ }
+
+ @Override
+ public void onCompilePdfCompleted(CompilePdfCompletedEvent event)
+ {
+ hideProgress();
+
+ CompilePdfResult result = event.getResult();
+ if (result.getSucceeded())
+ {
+ closeDialog();
+ }
+ else
+ {
+ // show error list if there are errors
+ String label = "Compile PDF failed";
+ if (CompileError.showErrorList(errors_))
+ {
+ label += " (double-click to view source location of error)";
+ errorList_.showErrors(result.getTargetFile(),
+ null,
+ errors_,
+ CompileErrorList.AUTO_SELECT_FIRST);
+ container_.setWidget(errorList_);
+ errorList_.focus();
+ }
+
+ // update the label and stop button
+ setLabel(label);
+ stopButton().setText("Close");
+ }
+ }
+
+ private EventBus eventBus_;
+
+ private SimplePanel container_;
+ private CompileOutputBuffer output_;
+ private CompileErrorList errorList_;
+ private JsArray<CompileError> errors_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfCompletedEvent.java
new file mode 100644
index 0000000..8287f1e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfCompletedEvent.java
@@ -0,0 +1,54 @@
+/*
+ * CompilePdfCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compilepdf.events;
+
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfResult;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CompilePdfCompletedEvent extends GwtEvent<CompilePdfCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCompilePdfCompleted(CompilePdfCompletedEvent event);
+ }
+
+ public CompilePdfCompletedEvent(CompilePdfResult result)
+ {
+ result_ = result;
+ }
+
+ public CompilePdfResult getResult()
+ {
+ return result_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCompilePdfCompleted(this);
+ }
+
+ private final CompilePdfResult result_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfErrorsEvent.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfErrorsEvent.java
new file mode 100644
index 0000000..62f4247
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfErrorsEvent.java
@@ -0,0 +1,55 @@
+/*
+ * CompilePdfErrorsEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compilepdf.events;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CompilePdfErrorsEvent extends GwtEvent<CompilePdfErrorsEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCompilePdfErrors(CompilePdfErrorsEvent event);
+ }
+
+ public CompilePdfErrorsEvent(JsArray<CompileError> errors)
+ {
+ errors_ = errors;
+ }
+
+ public JsArray<CompileError> getErrors()
+ {
+ return errors_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCompilePdfErrors(this);
+ }
+
+ private JsArray<CompileError> errors_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfOutputEvent.java
new file mode 100644
index 0000000..1c2f84d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfOutputEvent.java
@@ -0,0 +1,52 @@
+/*
+ * CompilePdfOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compilepdf.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CompilePdfOutputEvent extends GwtEvent<CompilePdfOutputEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCompilePdfOutput(CompilePdfOutputEvent event);
+ }
+
+ public CompilePdfOutputEvent(String output)
+ {
+ output_ = output;
+ }
+
+ public String getOutput()
+ {
+ return output_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCompilePdfOutput(this);
+ }
+
+ private String output_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfStartedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfStartedEvent.java
new file mode 100644
index 0000000..7e1bdb7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/events/CompilePdfStartedEvent.java
@@ -0,0 +1,73 @@
+/*
+ * CompilePdfStartedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.compilepdf.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CompilePdfStartedEvent extends GwtEvent<CompilePdfStartedEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public final native String getTargetFile() /*-{
+ return this.target_file;
+ }-*/;
+
+ public final native String getPdfPath() /*-{
+ return this.pdf_path;
+ }-*/;
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onCompilePdfStarted(CompilePdfStartedEvent event);
+ }
+
+ public CompilePdfStartedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getTargetFile()
+ {
+ return data_.getTargetFile();
+ }
+
+ public String getPdfPath()
+ {
+ return data_.getPdfPath();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCompilePdfStarted(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfResult.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfResult.java
new file mode 100644
index 0000000..54df9ca
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfResult.java
@@ -0,0 +1,51 @@
+/*
+ * CompilePdfResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compilepdf.model;
+
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompilePdfResult extends JavaScriptObject
+{
+ protected CompilePdfResult()
+ {
+ }
+
+ public final native boolean getSucceeded() /*-{
+ return this.succeeded;
+ }-*/;
+
+ public final native String getTargetFile() /*-{
+ return this.target_file;
+ }-*/;
+
+ public final native String getPdfPath() /*-{
+ return this.pdf_path;
+ }-*/;
+
+ public final native String getViewPdfUrl() /*-{
+ return this.view_pdf_url;
+ }-*/;
+
+ public final native boolean isSynctexAvailable() /*-{
+ return this.synctex_available;
+ }-*/;
+
+ public final native PdfLocation getPdfLocation() /*-{
+ return this.pdf_location;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfServerOperations.java
new file mode 100644
index 0000000..d4572f8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfServerOperations.java
@@ -0,0 +1,46 @@
+/*
+ * CompilePdfServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compilepdf.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface CompilePdfServerOperations
+{
+ // returns true to indicate that the compile has started, returns false
+ // to indicate that the compile pdf could not be started because another
+ // compile is currently in progress. pass the terminateExisting flag
+ // to terminate a running compile
+ void compilePdf(FileSystemItem targetFile,
+ String encoding,
+ SourceLocation sourceLocation,
+ String completedAction,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ // check whether compile pdf is running
+ void isCompilePdfRunning(ServerRequestCallback<Boolean> requestCallback);
+
+ // terminate any running pdf compilation
+ void terminateCompilePdf(ServerRequestCallback<Boolean> requestCallback);
+
+ // notify the server that the compile pdf tab was closed
+ void compilePdfClosed(ServerRequestCallback<Void> requestCallback);
+
+ // get a file url (used for showing in external browser)
+ String getFileUrl(FileSystemItem file);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfState.java b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfState.java
new file mode 100644
index 0000000..847476e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/compilepdf/model/CompilePdfState.java
@@ -0,0 +1,48 @@
+/*
+ * CompilePdfState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.compilepdf.model;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class CompilePdfState extends JavaScriptObject
+{
+ protected CompilePdfState()
+ {
+ }
+
+ public final native boolean isTabVisible() /*-{
+ return this.tab_visible;
+ }-*/;
+
+ public final native boolean isRunning() /*-{
+ return this.running;
+ }-*/;
+
+ public final native String getTargetFile() /*-{
+ return this.target_file;
+ }-*/;
+
+ public final native String getOutput() /*-{
+ return this.output;
+ }-*/;
+
+ public final native JsArray<CompileError> getErrors() /*-{
+ return this.errors;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleOutputEvent.java
new file mode 100644
index 0000000..83fc007
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleOutputEvent.java
@@ -0,0 +1,66 @@
+/*
+ * ConsoleOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.common.console.ConsoleOutputEvent.Handler;
+
+public class ConsoleOutputEvent extends GwtEvent<Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onConsoleOutput(ConsoleOutputEvent event);
+ }
+
+ public interface HasHandlers extends com.google.gwt.event.shared.HasHandlers
+ {
+ HandlerRegistration addConsoleOutputHandler(Handler handler);
+ }
+
+ public ConsoleOutputEvent(String output, boolean error)
+ {
+ output_ = output;
+ error_ = error;
+ }
+
+ public String getOutput()
+ {
+ return output_;
+ }
+
+ public boolean isError()
+ {
+ return error_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onConsoleOutput(this);
+ }
+
+ private final String output_;
+ private final boolean error_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcess.java b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcess.java
new file mode 100644
index 0000000..944c98a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcess.java
@@ -0,0 +1,346 @@
+/*
+ * ConsoleProcess.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.shell.ShellInput;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleServerOperations;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+
+public class ConsoleProcess implements ConsoleOutputEvent.HasHandlers,
+ ConsolePromptEvent.HasHandlers,
+ ProcessExitEvent.HasHandlers
+{
+ @Singleton
+ public static class ConsoleProcessFactory
+ {
+ @Inject
+ public ConsoleProcessFactory(ConsoleServerOperations server,
+ final CryptoServerOperations cryptoServer,
+ EventBus eventBus,
+ final Session session,
+ final Satellite satellite,
+ final SatelliteManager satelliteManager)
+ {
+ server_ = server;
+ eventBus_ = eventBus;
+
+ eventBus_.addHandler(SessionInitEvent.TYPE, new SessionInitHandler()
+ {
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ JsArray<ConsoleProcessInfo> procs =
+ session.getSessionInfo().getConsoleProcesses();
+
+ for (int i = 0; i < procs.length(); i++)
+ {
+ final ConsoleProcessInfo proc = procs.get(i);
+
+ // Note on reaping of console processes -- when isDialog
+ // is false it is the responsibility of the calling code
+ // to reap the console process (no automatic reaping is
+ // done). the isDialog == false codepath below handles
+ // the case where a client_init happens and the original
+ // callling code is no longer hooked up. There is still
+ // some leakiness here though if a console process with
+ // isDialog == false exits when no client is connected (in
+ // that case it will never be reaped).
+
+ // TODO: clean this up and/or eliminate isDialog flag (since
+ // all known instances currently use isDialog == true)
+
+ connectToProcess(
+ proc,
+ new ServerRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(
+ final ConsoleProcess cproc)
+ {
+ if (proc.isDialog())
+ {
+ // first determine whether to create and/or
+ // show the dialog immdiately
+ boolean createDialog = false;
+ boolean showDialog = false;
+
+ // standard dialog -- always show it
+ if (!proc.getShowOnOutput())
+ {
+ createDialog = true;
+ showDialog = true;
+ }
+
+ // showOnOutput dialog that already has
+ // output -- make sure the user sees it
+ //
+ // NOTE: we have to trim the buffered output
+ // for the comparison because when the password
+ // manager provides a password the back-end
+ // process sometimes echos a newline back to us
+ //
+ else if (proc.getBufferedOutput().trim().length() > 0)
+ {
+ createDialog = true;
+ showDialog = true;
+ }
+
+ // showOnOutput dialog that has exited
+ // and has no output -- reap it
+ else if (proc.getExitCode() != null)
+ {
+ cproc.reap(new VoidServerRequestCallback());
+ }
+
+ // showOnOutput dialog with no output that is
+ // still running -- crate but don't show yet
+ else
+ {
+ createDialog = true;
+ }
+
+ // take indicated actions
+ if (createDialog)
+ {
+ ConsoleProgressDialog dlg = new ConsoleProgressDialog(
+ proc.getCaption(),
+ cproc,
+ proc.getBufferedOutput(),
+ proc.getExitCode(),
+ cryptoServer);
+
+ if (showDialog)
+ dlg.showModal();
+ else
+ dlg.showOnOutput();
+ }
+ }
+ else
+ {
+ cproc.addProcessExitHandler(new ProcessExitEvent.Handler()
+ {
+ @Override
+ public void onProcessExit(ProcessExitEvent event)
+ {
+ cproc.reap(new VoidServerRequestCallback());
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+ }
+ });
+
+ eventBus_.addHandler(
+ ConsoleProcessCreatedEvent.TYPE,
+ new ConsoleProcessCreatedEvent.Handler()
+ {
+ private boolean handleEvent(String targetWindow)
+ {
+ // calculate the current window name
+ String window = StringUtil.notNull(
+ satellite.getSatelliteName());
+
+ // handle it if the target is us
+ if (window.equals(targetWindow))
+ return true;
+
+ // also handle if we are the main window and the specified
+ // satellite doesn't exist
+ if (!satellite.isCurrentWindowSatellite() &&
+ !satelliteManager.satelliteWindowExists(targetWindow))
+ return true;
+
+ // othewise don't handle
+ else
+ return false;
+ }
+
+
+ @Override
+ public void onConsoleProcessCreated(
+ ConsoleProcessCreatedEvent event)
+ {
+ if (!handleEvent(event.getTargetWindow()))
+ return;
+
+ ConsoleProcessInfo procInfo = event.getProcessInfo();
+ ConsoleProcess proc = new ConsoleProcess(server_,
+ eventBus_,
+ procInfo);
+
+ ConsoleProgressDialog dlg = new ConsoleProgressDialog(
+ procInfo.getCaption(),
+ proc,
+ procInfo.getBufferedOutput(),
+ procInfo.getExitCode(),
+ cryptoServer);
+
+ if (procInfo.getShowOnOutput())
+ dlg.showOnOutput();
+ else
+ dlg.showModal();
+ }
+ });
+ }
+
+ public void connectToProcess(
+ ConsoleProcessInfo procInfo,
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ requestCallback.onResponseReceived(new ConsoleProcess(server_,
+ eventBus_,
+ procInfo));
+ }
+
+ private final ConsoleServerOperations server_;
+ private final EventBus eventBus_;
+ }
+
+ private ConsoleProcess(ConsoleServerOperations server,
+ EventBus eventBus,
+ final ConsoleProcessInfo procInfo)
+ {
+ server_ = server;
+ procInfo_ = procInfo;
+ registrations_.add(eventBus.addHandler(
+ ServerConsoleOutputEvent.TYPE,
+ new ServerConsoleOutputEvent.Handler()
+ {
+ @Override
+ public void onServerConsoleOutput(
+ ServerConsoleOutputEvent event)
+ {
+ if (event.getProcessHandle().equals(procInfo.getHandle()))
+ fireEvent(new ConsoleOutputEvent(event.getOutput(),
+ event.getError()));
+ }
+ }));
+ registrations_.add(eventBus.addHandler(
+ ServerConsolePromptEvent.TYPE,
+ new ServerConsolePromptEvent.Handler()
+ {
+ @Override
+ public void onServerConsolePrompt(
+ ServerConsolePromptEvent event)
+ {
+ if (event.getProcessHandle().equals(procInfo.getHandle()))
+ fireEvent(new ConsolePromptEvent(event.getPrompt()));
+ }
+ }));
+ registrations_.add(eventBus.addHandler(
+ ServerProcessExitEvent.TYPE,
+ new ServerProcessExitEvent.Handler()
+ {
+ @Override
+ public void onServerProcessExit(ServerProcessExitEvent event)
+ {
+ if (event.getProcessHandle().equals(procInfo.getHandle()))
+ {
+ // no more events are coming
+ registrations_.removeHandler();
+
+ fireEvent(new ProcessExitEvent(event.getExitCode()));
+ }
+ }
+ }
+ ));
+ }
+
+ public ConsoleProcessInfo getProcessInfo()
+ {
+ return procInfo_;
+ }
+
+ public void start(ServerRequestCallback<Void> requestCallback)
+ {
+ server_.processStart(procInfo_.getHandle(), requestCallback);
+ }
+
+ public void writeStandardInput(ShellInput input,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ server_.processWriteStdin(procInfo_.getHandle(), input, requestCallback);
+ }
+
+ public void interrupt(ServerRequestCallback<Void> requestCallback)
+ {
+ server_.processInterrupt(procInfo_.getHandle(), requestCallback);
+ }
+
+ public void reap(ServerRequestCallback<Void> requestCallback)
+ {
+ server_.processReap(procInfo_.getHandle(), requestCallback);
+ }
+
+ @Override
+ public HandlerRegistration addConsoleOutputHandler(
+ ConsoleOutputEvent.Handler handler)
+ {
+ return handlers_.addHandler(ConsoleOutputEvent.TYPE, handler);
+ }
+
+ @Override
+ public HandlerRegistration addConsolePromptHandler(
+ ConsolePromptEvent.Handler handler)
+ {
+ return handlers_.addHandler(ConsolePromptEvent.TYPE, handler);
+ }
+
+ @Override
+ public HandlerRegistration addProcessExitHandler(
+ ProcessExitEvent.Handler handler)
+ {
+ return handlers_.addHandler(ProcessExitEvent.TYPE, handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ private HandlerRegistrations registrations_ = new HandlerRegistrations();
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ private final ConsoleServerOperations server_;
+ private final ConsoleProcessInfo procInfo_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessCreatedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessCreatedEvent.java
new file mode 100644
index 0000000..96a3090
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessCreatedEvent.java
@@ -0,0 +1,75 @@
+/*
+ * ConsoleProcessCreatedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.common.console.ConsoleProcessCreatedEvent.Handler;
+
+public class ConsoleProcessCreatedEvent extends GwtEvent<Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data() {}
+
+ public native final ConsoleProcessInfo getProcessInfo() /*-{
+ return this.process_info;
+ }-*/;
+
+ public native final String getTargetWindow() /*-{
+ return this.target_window;
+ }-*/;
+
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onConsoleProcessCreated(ConsoleProcessCreatedEvent event);
+ }
+
+ public ConsoleProcessCreatedEvent(Data data)
+ {
+ processInfo_ = data.getProcessInfo();
+ targetWindow_ = data.getTargetWindow();
+ }
+
+ public ConsoleProcessInfo getProcessInfo()
+ {
+ return processInfo_;
+ }
+
+ public String getTargetWindow()
+ {
+ return targetWindow_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onConsoleProcessCreated(this);
+ }
+
+ private final ConsoleProcessInfo processInfo_;
+ private final String targetWindow_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessInfo.java b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessInfo.java
new file mode 100644
index 0000000..909e46e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ConsoleProcessInfo.java
@@ -0,0 +1,61 @@
+/*
+ * ConsoleProcessInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.js.JsObject;
+
+public class ConsoleProcessInfo extends JavaScriptObject
+{
+ public static final int INTERACTION_NEVER = 0;
+ public static final int INTERACTION_POSSIBLE = 1;
+ public static final int INTERACTION_ALWAYS = 2;
+
+ protected ConsoleProcessInfo() {}
+
+ public final native String getHandle() /*-{
+ return this.handle;
+ }-*/;
+
+ public final native String getCaption() /*-{
+ return this.caption;
+ }-*/;
+
+ public final native boolean isDialog() /*-{
+ return this.dialog;
+ }-*/;
+
+ public final native boolean getShowOnOutput() /*-{
+ return this.show_on_output;
+ }-*/;
+
+ public final native int getInteractionMode() /*-{
+ return this.interaction_mode;
+ }-*/;
+
+ public final native int getMaxOutputLines() /*-{
+ return this.max_output_lines;
+ }-*/;
+
+ public final native String getBufferedOutput() /*-{
+ return this.buffered_output;
+ }-*/;
+
+ public final Integer getExitCode()
+ {
+ JsObject self = this.cast();
+ return self.getInteger("exit_code");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ConsolePromptEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ConsolePromptEvent.java
new file mode 100644
index 0000000..d05ef1f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ConsolePromptEvent.java
@@ -0,0 +1,59 @@
+/*
+ * ConsolePromptEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.common.console.ConsolePromptEvent.Handler;
+
+public class ConsolePromptEvent extends GwtEvent<Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onConsolePrompt(ConsolePromptEvent event);
+ }
+
+ public interface HasHandlers extends com.google.gwt.event.shared.HasHandlers
+ {
+ HandlerRegistration addConsolePromptHandler(Handler handler);
+ }
+
+ public ConsolePromptEvent(String prompt)
+ {
+ prompt_ = prompt;
+ }
+
+ public String getPrompt()
+ {
+ return prompt_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onConsolePrompt(this);
+ }
+
+ private final String prompt_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ProcessExitEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ProcessExitEvent.java
new file mode 100644
index 0000000..30869e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ProcessExitEvent.java
@@ -0,0 +1,59 @@
+/*
+ * ProcessExitEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.common.console.ProcessExitEvent.Handler;
+
+public class ProcessExitEvent extends GwtEvent<Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onProcessExit(ProcessExitEvent event);
+ }
+
+ public interface HasHandlers extends com.google.gwt.event.shared.HasHandlers
+ {
+ HandlerRegistration addProcessExitHandler(Handler handler);
+ }
+
+ public ProcessExitEvent(int exitCode)
+ {
+ exitCode_ = exitCode;
+ }
+
+ public int getExitCode()
+ {
+ return exitCode_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onProcessExit(this);
+ }
+
+ private final int exitCode_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsoleOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsoleOutputEvent.java
new file mode 100644
index 0000000..928aa27
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsoleOutputEvent.java
@@ -0,0 +1,81 @@
+/*
+ * ServerConsoleOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ServerConsoleOutputEvent
+ extends GwtEvent<ServerConsoleOutputEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onServerConsoleOutput(ServerConsoleOutputEvent event);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data() {}
+
+ public native final String getHandle() /*-{ return this.handle; }-*/;
+ public native final String getOutput() /*-{ return this.output; }-*/;
+ public native final boolean isError() /*-{ return this.error; }-*/;
+ }
+
+
+ public ServerConsoleOutputEvent(String procHandle,
+ String output,
+ boolean error)
+ {
+
+ procHandle_ = procHandle;
+ output_ = output;
+ error_ = error;
+ }
+
+ public String getProcessHandle()
+ {
+ return procHandle_;
+ }
+
+ public String getOutput()
+ {
+ return output_;
+ }
+
+ public boolean getError()
+ {
+ return error_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onServerConsoleOutput(this);
+ }
+
+ private final String procHandle_;
+ private final String output_;
+ private final boolean error_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsolePromptEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsolePromptEvent.java
new file mode 100644
index 0000000..713fd36
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ServerConsolePromptEvent.java
@@ -0,0 +1,71 @@
+/*
+ * ServerConsolePromptEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ServerConsolePromptEvent
+ extends GwtEvent<ServerConsolePromptEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onServerConsolePrompt(ServerConsolePromptEvent event);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data() {}
+
+ public native final String getHandle() /*-{ return this.handle; }-*/;
+ public native final String getPrompt() /*-{ return this.prompt; }-*/;
+ }
+
+
+ public ServerConsolePromptEvent(String procHandle, String prompt)
+ {
+
+ procHandle_ = procHandle;
+ prompt_ = prompt;
+ }
+
+ public String getProcessHandle()
+ {
+ return procHandle_;
+ }
+
+ public String getPrompt()
+ {
+ return prompt_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onServerConsolePrompt(this);
+ }
+
+ private final String procHandle_;
+ private final String prompt_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/console/ServerProcessExitEvent.java b/src/gwt/src/org/rstudio/studio/client/common/console/ServerProcessExitEvent.java
new file mode 100644
index 0000000..90a46c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/console/ServerProcessExitEvent.java
@@ -0,0 +1,69 @@
+/*
+ * ServerProcessExitEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.console;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ServerProcessExitEvent extends GwtEvent<ServerProcessExitEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onServerProcessExit(ServerProcessExitEvent event);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data() {}
+
+ public native final String getHandle() /*-{ return this.handle; }-*/;
+ public native final int getExitCode() /*-{ return this.exitCode; }-*/;
+ }
+
+ public ServerProcessExitEvent(String procHandle,
+ int exitCode)
+ {
+ procHandle_ = procHandle;
+ exitCode_ = exitCode;
+ }
+
+ public String getProcessHandle()
+ {
+ return procHandle_;
+ }
+
+ public int getExitCode()
+ {
+ return exitCode_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onServerProcessExit(this);
+ }
+
+ private final String procHandle_;
+ private final int exitCode_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/crypto/CryptoServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/crypto/CryptoServerOperations.java
new file mode 100644
index 0000000..705c601
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/crypto/CryptoServerOperations.java
@@ -0,0 +1,22 @@
+/*
+ * CryptoServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.crypto;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface CryptoServerOperations
+{
+ void getPublicKey(ServerRequestCallback<PublicKeyInfo> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/crypto/PublicKeyInfo.java b/src/gwt/src/org/rstudio/studio/client/common/crypto/PublicKeyInfo.java
new file mode 100644
index 0000000..cc6bba1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/crypto/PublicKeyInfo.java
@@ -0,0 +1,30 @@
+/*
+ * PublicKeyInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.crypto;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PublicKeyInfo extends JavaScriptObject
+{
+ protected PublicKeyInfo() {}
+
+ public native final String getExponent() /*-{
+ return this.exponent;
+ }-*/;
+
+ public native final String getModulo() /*-{
+ return this.modulo;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/crypto/RSAEncrypt.java b/src/gwt/src/org/rstudio/studio/client/common/crypto/RSAEncrypt.java
new file mode 100644
index 0000000..f6d485d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/crypto/RSAEncrypt.java
@@ -0,0 +1,104 @@
+/*
+ * RSAEncrypt.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.crypto;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.ExternalJavaScriptLoader;
+import org.rstudio.core.client.ExternalJavaScriptLoader.Callback;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public class RSAEncrypt
+{
+ public interface ResponseCallback
+ {
+ void onSuccess(String encryptedData);
+ void onFailure(ServerError error);
+ }
+
+
+ public static void encrypt_ServerOnly(
+ final CryptoServerOperations server,
+ final String input,
+ final ResponseCallback callback)
+ {
+ if (Desktop.isDesktop())
+ {
+ // Don't encrypt for desktop, Windows can't decrypt it.
+ callback.onSuccess(input);
+ return;
+ }
+
+ loader_.addCallback(new Callback()
+ {
+ @Override
+ public void onLoaded()
+ {
+ server.getPublicKey(new ServerRequestCallback<PublicKeyInfo>()
+ {
+ @Override
+ public void onResponseReceived(PublicKeyInfo response)
+ {
+
+ callback.onSuccess(encrypt(input,
+ response.getExponent(),
+ response.getModulo()));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onFailure(error);
+ }
+ });
+ }
+ });
+ }
+
+ public static void encrypt_ServerOnly(final PublicKeyInfo publicKeyInfo,
+ final String input,
+ final CommandWithArg<String> callback)
+ {
+ if (Desktop.isDesktop())
+ {
+ // Don't encrypt for desktop, Windows can't decrypt it.
+ callback.execute(input);
+ return;
+ }
+
+ loader_.addCallback(new Callback()
+ {
+ @Override
+ public void onLoaded()
+ {
+ callback.execute(encrypt(input,
+ publicKeyInfo.getExponent(),
+ publicKeyInfo.getModulo()));
+
+ }
+ });
+ }
+
+
+ private static native String encrypt(String value,
+ String exponent,
+ String modulo) /*-{
+ return $wnd.encrypt(value, exponent, modulo);
+ }-*/;
+
+ private static final ExternalJavaScriptLoader loader_ =
+ new ExternalJavaScriptLoader("js/encrypt.min.js");
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/cstyles.css b/src/gwt/src/org/rstudio/studio/client/common/cstyles.css
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/cstyles.css
@@ -0,0 +1 @@
+
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/BreakpointManager.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/BreakpointManager.java
new file mode 100644
index 0000000..d260bfa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/BreakpointManager.java
@@ -0,0 +1,898 @@
+/*
+ * BreakpointManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.common.FilePathUtils;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.debugging.events.BreakpointsSavedEvent;
+import org.rstudio.studio.client.common.debugging.events.PackageLoadedEvent;
+import org.rstudio.studio.client.common.debugging.events.PackageUnloadedEvent;
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.debugging.model.BreakpointState;
+import org.rstudio.studio.client.common.debugging.model.FunctionState;
+import org.rstudio.studio.client.common.debugging.model.FunctionSteps;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputHandler;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.ContextDepthChangedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugSourceCompletedEvent;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+// Provides management for breakpoints.
+//
+// The typical workflow for interactively adding a new breakpoint is as follows:
+// 1) The user clicks on the gutter of the editor, which generates an editor
+// event (BreakpointSetEvent)
+// 2) The editing target (which maintains a reference to the breakpoint manager)
+// asks the manager to create a breakpoint, and passes the new breakpoint
+// back to the editing surface (addOrUpdateBreakpoint)
+// 3) The breakpoint manager checks to see whether the source code for the
+// function on disk is identical to the source code for the function as
+// it exists in the R session (get_function_sync_state). If it isn't,
+// it defers setting the breakpoint.
+// 4) The breakpoint manager fetches the steps and substeps of the function in
+// which the breakpoint occurs from the server, and updates the breakpoint
+// with this information (get_function_steps)
+// 5) The breakpoint manager combines the breakpoint with all of the other
+// breakpoints for the function, and makes a single call to the server to
+// update the function's breakpoints (set_function_breakpoints)
+// 6) If successful, the breakpoint manager emits a BreakpointsSavedEvent, which
+// is picked up by the editing target, which updates the display to show that
+// the breakpoint is now enabled.
+
+ at Singleton
+public class BreakpointManager
+ implements SessionInitHandler,
+ ContextDepthChangedEvent.Handler,
+ PackageLoadedEvent.Handler,
+ PackageUnloadedEvent.Handler,
+ ConsoleWriteInputHandler,
+ RestartStatusEvent.Handler,
+ DebugSourceCompletedEvent.Handler
+{
+ public interface Binder
+ extends CommandBinder<Commands, BreakpointManager> {}
+
+ @Inject
+ public BreakpointManager(
+ DebuggingServerOperations server,
+ EventBus events,
+ Session session,
+ WorkbenchContext workbench,
+ Binder binder,
+ Commands commands,
+ GlobalDisplay globalDisplay)
+ {
+ server_ = server;
+ events_ = events;
+ session_ = session;
+ workbench_ = workbench;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+
+ commands_.debugClearBreakpoints().setEnabled(false);
+
+ // this singleton class is constructed before the session is initialized,
+ // so wait until the session init happens to grab our persisted state
+ events_.addHandler(SessionInitEvent.TYPE, this);
+ events_.addHandler(ConsoleWriteInputEvent.TYPE, this);
+ events_.addHandler(ContextDepthChangedEvent.TYPE, this);
+ events_.addHandler(PackageLoadedEvent.TYPE, this);
+ events_.addHandler(PackageUnloadedEvent.TYPE, this);
+ events_.addHandler(RestartStatusEvent.TYPE, this);
+ events_.addHandler(DebugSourceCompletedEvent.TYPE, this);
+
+ binder.bind(commands, this);
+ }
+
+ // Public methods ---------------------------------------------------------
+
+ public Breakpoint setTopLevelBreakpoint(
+ final String path,
+ final int lineNumber)
+ {
+ final Breakpoint breakpoint = addBreakpoint(Breakpoint.create(
+ currentBreakpointId_++,
+ path,
+ "toplevel",
+ lineNumber,
+ path.equals(activeSource_) ?
+ Breakpoint.STATE_INACTIVE :
+ Breakpoint.STATE_ACTIVE,
+ Breakpoint.TYPE_TOPLEVEL));
+
+ // If we're actively sourcing this file, we can't set breakpoints in
+ // it just yet
+ if (path.equals(activeSource_))
+ breakpoint.setPendingDebugCompletion(true);
+
+ notifyServer(breakpoint, true, true);
+
+ ArrayList<Breakpoint> bps = new ArrayList<Breakpoint>();
+ bps.add(breakpoint);
+ return breakpoint;
+ }
+
+ public Breakpoint setBreakpoint(
+ final String path,
+ final String functionName,
+ int lineNumber,
+ final boolean immediately)
+ {
+ // create the new breakpoint and arguments for the server call
+ final Breakpoint breakpoint = addBreakpoint(Breakpoint.create(
+ currentBreakpointId_++,
+ path,
+ functionName,
+ lineNumber,
+ immediately ?
+ Breakpoint.STATE_PROCESSING :
+ Breakpoint.STATE_INACTIVE,
+ Breakpoint.TYPE_FUNCTION));
+ notifyServer(breakpoint, true, false);
+
+ // If the breakpoint is in a function that is active on the callstack,
+ // it's being set on the stored rather than the executing copy. It's
+ // possible to set it right now, but it will probably violate user
+ // expectations. Process it when the function is no longer executing.
+ if (activeFunctions_.contains(
+ new FileFunction(breakpoint)))
+ {
+ breakpoint.setPendingDebugCompletion(true);
+ markInactiveBreakpoint(breakpoint);
+ }
+ else
+ {
+ server_.getFunctionState(functionName, path, lineNumber,
+ new ServerRequestCallback<FunctionState>()
+ {
+ @Override
+ public void onResponseReceived(FunctionState state)
+ {
+ if (state.isPackageFunction())
+ {
+ breakpoint.markAsPackageBreakpoint(state.getPackageName());
+ }
+ // If the breakpoint is not to be set immediately,
+ // stop processing now
+ if (!immediately)
+ return;
+
+ // if the function lines up with the version on the server, set
+ // the breakpoint now
+ if (state.getSyncState())
+ {
+ prepareAndSetFunctionBreakpoints(
+ new FileFunction(breakpoint));
+ }
+ // otherwise, save an inactive breakpoint--we'll revisit the
+ // marker the next time the file is sourced or the package is
+ // rebuilt
+ else
+ {
+ markInactiveBreakpoint(breakpoint);
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // if we can't figure out whether the function is in sync,
+ // leave it inactive for now
+ markInactiveBreakpoint(breakpoint);
+ }
+ });
+ }
+
+ breakpointStateDirty_ = true;
+ return breakpoint;
+ }
+
+ public void removeBreakpoint(int breakpointId)
+ {
+ Breakpoint breakpoint = getBreakpoint(breakpointId);
+ if (breakpoint != null)
+ {
+ breakpoints_.remove(breakpoint);
+ if (breakpoint.getState() == Breakpoint.STATE_ACTIVE &&
+ breakpoint.getType() == Breakpoint.TYPE_FUNCTION)
+ {
+ setFunctionBreakpoints(new FileFunction(breakpoint));
+ }
+ notifyServer(breakpoint, false,
+ breakpoint.getType() == Breakpoint.TYPE_TOPLEVEL);
+ }
+ onBreakpointAddOrRemove();
+ }
+
+ public void moveBreakpoint(int breakpointId)
+ {
+ // because of Java(Script)'s reference semantics, the editor's instance
+ // of the breakpoint object is the same one we have here, so we don't
+ // need to update the line number--we just need to persist the new state.
+ breakpointStateDirty_ = true;
+
+ // the breakpoint knows its position in the function, which needs to be
+ // recalculated; do that the next time we set breakpoints on this function
+ Breakpoint breakpoint = getBreakpoint(breakpointId);
+ if (breakpoint != null)
+ {
+ breakpoint.markStepsNeedUpdate();
+ notifyServer(breakpoint, true, false);
+ }
+ }
+
+ public ArrayList<Breakpoint> getBreakpointsInFile(String fileName)
+ {
+ ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (breakpoint.isInFile(fileName))
+ {
+ breakpoints.add(breakpoint);
+ }
+ }
+ return breakpoints;
+ }
+
+ // Event handlers ----------------------------------------------------------
+
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ // Establish a persistent object for the breakpoints. Note that this
+ // object is read by the server on init, so the scope/name pair here
+ // needs to match the pair on the server.
+ new JSObjectStateValue(
+ "debug-breakpoints",
+ "debugBreakpointsState",
+ ClientState.PROJECT_PERSISTENT,
+ session_.getSessionInfo().getClientState(),
+ false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ if (value != null)
+ {
+ BreakpointState state = value.cast();
+
+ // restore all of the breakpoints
+ JsArray<Breakpoint> breakpoints =
+ state.getPersistedBreakpoints();
+ for (int idx = 0; idx < breakpoints.length(); idx++)
+ {
+ Breakpoint breakpoint = breakpoints.get(idx);
+
+ // make sure the next breakpoint we create after a restore
+ // has a value larger than any existing breakpoint
+ currentBreakpointId_ = Math.max(
+ currentBreakpointId_,
+ breakpoint.getBreakpointId() + 1);
+
+ addBreakpoint(breakpoint);
+ }
+
+ // this initialization happens after the source windows are
+ // up, so fire an event to the editor to show all known
+ // breakpoints. as new source windows are opened, they will
+ // call getBreakpointsInFile to populate themselves.
+ events_.fireEvent(
+ new BreakpointsSavedEvent(breakpoints_, true));
+ }
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ BreakpointState state =
+ BreakpointState.create();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ state.addPersistedBreakpoint(breakpoint);
+ }
+ breakpointStateDirty_ = false;
+ return state.cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ return breakpointStateDirty_;
+ }
+ };
+ }
+
+ @Override
+ public void onConsoleWriteInput(ConsoleWriteInputEvent event)
+ {
+ // when a file is sourced, replay all the breakpoints in the file.
+ RegExp sourceExp = RegExp.compile("source(.with.encoding)?\\('([^']*)'.*");
+ MatchResult fileMatch = sourceExp.exec(event.getInput());
+ if (fileMatch == null || fileMatch.getGroupCount() == 0)
+ {
+ return;
+ }
+ String path = FilePathUtils.normalizePath(
+ fileMatch.getGroup(2),
+ workbench_.getCurrentWorkingDir().getPath());
+ resetBreakpointsInPath(path, true);
+ }
+
+ @Override
+ public void onDebugSourceCompleted(DebugSourceCompletedEvent event)
+ {
+ if (event.getSucceeded())
+ {
+ resetBreakpointsInPath(
+ FilePathUtils.normalizePath(
+ event.getPath(),
+ workbench_.getCurrentWorkingDir().getPath()),
+ true);
+ }
+ }
+
+ @Override
+ public void onContextDepthChanged(ContextDepthChangedEvent event)
+ {
+ // When we move around in debug context and hit a breakpoint, the initial
+ // evaluation state is a temporary construction that needs to be stepped
+ // past to begin actually evaluating the function. Step past it
+ // immediately.
+ JsArray<CallFrame> frames = event.getCallFrames();
+ Set<FileFunction> activeFunctions = new TreeSet<FileFunction>();
+ boolean hasSourceEquiv = false;
+ for (int idx = 0; idx < frames.length(); idx++)
+ {
+ CallFrame frame = frames.get(idx);
+ String functionName = frame.getFunctionName();
+ String fileName = frame.getFileName();
+ if (functionName.equals(".doTrace") &&
+ event.isServerInitiated())
+ {
+ events_.fireEvent(new SendToConsoleEvent(
+ DebugCommander.NEXT_COMMAND, true));
+ }
+ activeFunctions.add(
+ new FileFunction(functionName, fileName, "", false));
+ if (frame.isSourceEquiv())
+ {
+ activeSource_ = fileName;
+ hasSourceEquiv = true;
+ }
+ }
+
+ // For any functions that were previously active in the callstack but
+ // are no longer active, enable any pending breakpoints for those
+ // functions.
+ Set<FileFunction> enableFunctions = new TreeSet<FileFunction>();
+ for (FileFunction function: activeFunctions_)
+ {
+ if (!activeFunctions.contains(function))
+ {
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (breakpoint.isPendingDebugCompletion() &&
+ breakpoint.getState() == Breakpoint.STATE_INACTIVE &&
+ function.containsBreakpoint(breakpoint))
+ {
+ enableFunctions.add(function);
+ }
+ }
+ }
+ }
+
+ for (FileFunction function: enableFunctions)
+ {
+ prepareAndSetFunctionBreakpoints(function);
+ }
+
+ // Record the new frame list.
+ activeFunctions_ = activeFunctions;
+
+ // When we finish executing a top-level source, activate the top-level
+ // breakpoints in the file we were sourcing.
+ if (!hasSourceEquiv && activeSource_ != null)
+ {
+ activateTopLevelBreakpoints(activeSource_);
+ activeSource_ = null;
+ }
+ }
+
+ @Handler
+ public void onDebugClearBreakpoints()
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.QUESTION,
+ "Clear All Breakpoints",
+ "Are you sure you want to remove all the breakpoints in this " +
+ "project?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ clearAllBreakpoints();
+ }
+ },
+ false);
+ }
+
+ @Override
+ public void onPackageLoaded(PackageLoadedEvent event)
+ {
+ updatePackageBreakpoints(event.getPackageName(), true);
+ }
+
+ @Override
+ public void onPackageUnloaded(PackageUnloadedEvent event)
+ {
+ updatePackageBreakpoints(event.getPackageName(), false);
+ }
+
+ @Override
+ public void onRestartStatus(RestartStatusEvent event)
+ {
+ if (event.getStatus() == RestartStatusEvent.RESTART_INITIATED)
+ {
+ // Restarting R unloads all the packages, so mark all active package
+ // breakpoints as inactive when this happens.
+ ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (breakpoint.isPackageBreakpoint())
+ {
+ breakpoint.setState(Breakpoint.STATE_INACTIVE);
+ breakpoints.add(breakpoint);
+ }
+ }
+ notifyBreakpointsSaved(breakpoints, true);
+ }
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private void setFunctionBreakpoints(FileFunction function)
+ {
+ ArrayList<String> steps = new ArrayList<String>();
+ final ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (function.containsBreakpoint(breakpoint))
+ {
+ steps.add(breakpoint.getFunctionSteps());
+ breakpoints.add(breakpoint);
+ }
+ }
+ server_.setFunctionBreakpoints(
+ function.functionName,
+ function.fileName,
+ function.packageName,
+ steps,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ for (Breakpoint breakpoint: breakpoints)
+ {
+ breakpoint.setState(Breakpoint.STATE_ACTIVE);
+ }
+ notifyBreakpointsSaved(breakpoints, true);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ discardUnsettableBreakpoints(breakpoints);
+ }
+ });
+ }
+
+ private void prepareAndSetFunctionBreakpoints(final FileFunction function)
+ {
+ // look over the list of breakpoints in this function and see if any are
+ // marked inactive, or if they need their steps refreshed (necessary
+ // when a function has had steps added or removed in the editor)
+ final ArrayList<Breakpoint> inactiveBreakpoints =
+ new ArrayList<Breakpoint>();
+ int[] inactiveLines = new int[]{};
+ int numLines = 0;
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (function.containsBreakpoint(breakpoint) &&
+ (breakpoint.getState() != Breakpoint.STATE_ACTIVE ||
+ breakpoint.needsUpdatedSteps()))
+ {
+ inactiveBreakpoints.add(breakpoint);
+ inactiveLines[numLines++] = breakpoint.getLineNumber();
+ }
+ }
+
+ // if we found breakpoints that aren't yet active, try to get the
+ // corresponding steps from the function
+ if (inactiveBreakpoints.size() > 0)
+ {
+ server_.getFunctionSteps(
+ function.functionName,
+ function.fileName,
+ function.packageName,
+ inactiveLines,
+ new ServerRequestCallback<JsArray<FunctionSteps>> () {
+ @Override
+ public void onResponseReceived
+ (JsArray<FunctionSteps> response)
+ {
+ // found the function and the steps in the function; next,
+ // ask the server to set the breakpoint
+ if (response.length() > 0)
+ {
+ processFunctionSteps(inactiveBreakpoints, response);
+ setFunctionBreakpoints(function);
+ }
+ // no results: discard the breakpoints
+ else
+ {
+ discardUnsettableBreakpoints(inactiveBreakpoints);
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ discardUnsettableBreakpoints(inactiveBreakpoints);
+ }
+ });
+ }
+ else
+ {
+ setFunctionBreakpoints(function);
+ }
+ }
+
+ private void discardUnsettableBreakpoints(ArrayList<Breakpoint> breakpoints)
+ {
+ if (breakpoints.size() == 0)
+ {
+ return;
+ }
+ for (Breakpoint breakpoint: breakpoints)
+ {
+ breakpoints_.remove(breakpoint);
+ }
+ onBreakpointAddOrRemove();
+ notifyBreakpointsSaved(breakpoints, false);
+ }
+
+ private void resetBreakpointsInPath(String path, boolean isFile)
+ {
+ Set<FileFunction> functionsToBreak = new TreeSet<FileFunction>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ // set this breakpoint if it's a function breakpoint in the file
+ // (or path) given
+ boolean processBreakpoint =
+ (breakpoint.getType() == Breakpoint.TYPE_FUNCTION) &&
+ (isFile ?
+ breakpoint.isInFile(path) :
+ breakpoint.isInPath(path));
+ if (processBreakpoint)
+ {
+ functionsToBreak.add(new FileFunction(breakpoint));
+ }
+ }
+ for (FileFunction function: functionsToBreak)
+ {
+ prepareAndSetFunctionBreakpoints(function);
+ }
+ }
+
+ private void markInactiveBreakpoint(Breakpoint breakpoint)
+ {
+ breakpoint.setState(Breakpoint.STATE_INACTIVE);
+ ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
+ breakpoints.add(breakpoint);
+ notifyBreakpointsSaved(breakpoints, true);
+ }
+
+ private void processFunctionSteps(
+ ArrayList<Breakpoint> breakpoints,
+ JsArray<FunctionSteps> stepList)
+ {
+ ArrayList<Breakpoint> unSettableBreakpoints =
+ new ArrayList<Breakpoint>();
+
+ // Walk through the array of breakpoints for which we requested function
+ // steps and the array of results from the server in lock-step, populating
+ // each breakpoint with its steps.
+ for (int i = 0; i < breakpoints.size() &&
+ i < stepList.length(); i++)
+ {
+ FunctionSteps steps = stepList.get(i);
+ Breakpoint breakpoint = breakpoints.get(i);
+ if (steps.getSteps().length() > 0)
+ {
+ // if the server set this breakpoint on a different line than
+ // requested, make sure there's not already a breakpoint on that
+ // line; if there is, discard this one.
+ if (breakpoint.getLineNumber() != steps.getLineNumber())
+ {
+ for (Breakpoint possibleDupe: breakpoints_)
+ {
+ if (breakpoint.getPath().equals(
+ possibleDupe.getPath()) &&
+ steps.getLineNumber() ==
+ possibleDupe.getLineNumber() &&
+ breakpoint.getBreakpointId() !=
+ possibleDupe.getBreakpointId())
+ {
+ breakpoint.setState(Breakpoint.STATE_REMOVING);
+ unSettableBreakpoints.add(breakpoint);
+ }
+ }
+ }
+ breakpoint.addFunctionSteps(steps.getName(),
+ steps.getLineNumber(),
+ steps.getSteps());
+ }
+ else
+ {
+ unSettableBreakpoints.add(breakpoint);
+ }
+ }
+ discardUnsettableBreakpoints(unSettableBreakpoints);
+ }
+
+ private void notifyBreakpointsSaved(
+ ArrayList<Breakpoint> breakpoints,
+ boolean saved)
+ {
+ breakpointStateDirty_ = true;
+ events_.fireEvent(
+ new BreakpointsSavedEvent(breakpoints, saved));
+ }
+
+ private Breakpoint getBreakpoint (int breakpointId)
+ {
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (breakpoint.getBreakpointId() == breakpointId)
+ {
+ return breakpoint;
+ }
+ }
+ return null;
+ }
+
+ private Breakpoint addBreakpoint (Breakpoint breakpoint)
+ {
+ breakpoints_.add(breakpoint);
+ onBreakpointAddOrRemove();
+ return breakpoint;
+ }
+
+ private void updatePackageBreakpoints(String packageName, boolean enable)
+ {
+ Set<FileFunction> functionsToBreak = new TreeSet<FileFunction>();
+ ArrayList<Breakpoint> breakpointsToDisable = new ArrayList<Breakpoint>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ if (breakpoint.isPackageBreakpoint() &&
+ breakpoint.getPackageName().equals(packageName))
+ {
+ if (enable)
+ {
+ functionsToBreak.add(new FileFunction(breakpoint));
+ }
+ else
+ {
+ breakpoint.setState(Breakpoint.STATE_INACTIVE);
+ breakpointsToDisable.add(breakpoint);
+ }
+ }
+ }
+ if (enable)
+ {
+ for (FileFunction function: functionsToBreak)
+ {
+ prepareAndSetFunctionBreakpoints(function);
+ }
+ }
+ else
+ {
+ notifyBreakpointsSaved(breakpointsToDisable, true);
+ }
+ }
+
+ private void clearAllBreakpoints()
+ {
+ Set<FileFunction> functions = new TreeSet<FileFunction>();
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ breakpoint.setState(Breakpoint.STATE_REMOVING);
+ if (breakpoint.getType () == Breakpoint.TYPE_FUNCTION)
+ functions.add(new FileFunction(breakpoint));
+ }
+ // Remove the breakpoints from each unique function that had breakpoints
+ // set previously
+ for (FileFunction function: functions)
+ {
+ server_.setFunctionBreakpoints(
+ function.functionName,
+ function.fileName,
+ function.packageName,
+ new ArrayList<String>(),
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ // There's a possibility here that the breakpoints were
+ // not successfully cleared, so we may be in a temporarily
+ // confusing state, but no error message will be less
+ // confusing.
+ }
+ });
+ }
+
+ server_.removeAllBreakpoints(new VoidServerRequestCallback());
+ notifyBreakpointsSaved(new ArrayList<Breakpoint>(breakpoints_), false);
+ breakpoints_.clear();
+ onBreakpointAddOrRemove();
+ }
+
+ private void onBreakpointAddOrRemove()
+ {
+ breakpointStateDirty_ = true;
+ commands_.debugClearBreakpoints().setEnabled(breakpoints_.size() > 0);
+ }
+
+ private void notifyServer(Breakpoint breakpoint, boolean added, boolean arm)
+ {
+ ArrayList<Breakpoint> bps = new ArrayList<Breakpoint>();
+ bps.add(breakpoint);
+ server_.updateBreakpoints(bps, added, arm,
+ new VoidServerRequestCallback());
+ }
+
+ private void activateTopLevelBreakpoints(String path)
+ {
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ ArrayList<Breakpoint> activatedBreakpoints =
+ new ArrayList<Breakpoint>();
+ if (breakpoint.isPendingDebugCompletion() &&
+ breakpoint.getState() == Breakpoint.STATE_INACTIVE &&
+ breakpoint.getType() == Breakpoint.TYPE_TOPLEVEL &&
+ breakpoint.getPath().equals(path))
+ {
+ // If this is a top-level breakpoint in the file that we
+ // just finished sourcing, activate the breakpoint.
+ breakpoint.setPendingDebugCompletion(false);
+ breakpoint.setState(Breakpoint.STATE_ACTIVE);
+ activatedBreakpoints.add(breakpoint);
+ }
+ if (activatedBreakpoints.size() > 0)
+ notifyBreakpointsSaved(activatedBreakpoints, true);
+ }
+ }
+
+ // Private classes ---------------------------------------------------------
+
+ class FileFunction implements Comparable<FileFunction>
+ {
+ public String functionName;
+ public String fileName;
+ public String packageName;
+
+ boolean fullPath;
+
+ public FileFunction (
+ String fun, String file, String pkg, boolean useFullPath)
+ {
+ functionName = fun;
+ fileName = file.trim();
+ packageName = pkg;
+ fullPath = useFullPath;
+ }
+
+ public FileFunction (String fun, String file, String pkg)
+ {
+ this(fun, file, pkg, true);
+ }
+
+ public FileFunction (Breakpoint breakpoint)
+ {
+ this(breakpoint.getFunctionName(),
+ breakpoint.getPath(),
+ breakpoint.getPackageName());
+ }
+
+ public boolean containsBreakpoint(Breakpoint breakpoint)
+ {
+ if (!breakpoint.getFunctionName().equals(functionName))
+ {
+ return false;
+ }
+ if (fullPath)
+ {
+ return breakpoint.getPath().equals(fileName);
+ }
+ else
+ {
+ return FilePathUtils.friendlyFileName(breakpoint.getPath()).equals(
+ FilePathUtils.friendlyFileName(fileName));
+ }
+ }
+
+ @Override
+ public int compareTo(FileFunction other)
+ {
+ int fun = functionName.compareTo(other.functionName);
+ if (fun != 0)
+ {
+ return fun;
+ }
+ if (!fullPath || !other.fullPath)
+ {
+ return FilePathUtils.friendlyFileName(fileName).compareTo(
+ FilePathUtils.friendlyFileName(other.fileName));
+ }
+ return fileName.compareTo(other.fileName);
+ }
+ }
+
+ private final DebuggingServerOperations server_;
+ private final EventBus events_;
+ private final Session session_;
+ private final WorkbenchContext workbench_;
+ private final GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+
+ private ArrayList<Breakpoint> breakpoints_ = new ArrayList<Breakpoint>();
+ private Set<FileFunction> activeFunctions_ = new TreeSet<FileFunction>();
+ private String activeSource_;
+
+ private boolean breakpointStateDirty_ = false;
+ private int currentBreakpointId_ = 0;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/DebugCommander.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/DebugCommander.java
new file mode 100644
index 0000000..a826492
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/DebugCommander.java
@@ -0,0 +1,256 @@
+/*
+ * DebugCommander.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging;
+
+import org.rstudio.core.client.DebugFilePosition;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.ApplicationInterrupt;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.BusyEvent;
+import org.rstudio.studio.client.workbench.events.BusyHandler;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.LineData;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+
+// DebugCommander is responsible for managing top-level metadata concerning
+// debug sessions (both function and top-level) and for processing the basic
+// debug commands (run, step, etc) in the appropriate context.
+ at Singleton
+public class DebugCommander
+ implements SessionInitHandler,
+ RestartStatusEvent.Handler,
+ BusyHandler
+{
+ public interface Binder
+ extends CommandBinder<Commands, DebugCommander> {}
+
+ public enum DebugMode
+ {
+ Normal,
+ Function,
+ TopLevel
+ }
+
+ @Inject
+ public DebugCommander(
+ Binder binder,
+ Commands commands,
+ EventBus eventBus,
+ Session session,
+ ApplicationInterrupt interrupt)
+ {
+ eventBus_ = eventBus;
+ session_ = session;
+ commands_ = commands;
+ interrupt_ = interrupt;
+
+ eventBus_.addHandler(SessionInitEvent.TYPE, this);
+ eventBus_.addHandler(RestartStatusEvent.TYPE, this);
+ eventBus_.addHandler(BusyEvent.TYPE, this);
+
+ binder.bind(commands, this);
+
+ setDebugCommandsEnabled(false);
+ commands_.debugBreakpoint().setEnabled(false);
+ }
+
+ // Command and event handlers ----------------------------------------------
+
+ @Handler
+ void onDebugContinue()
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(
+ CONTINUE_COMMAND, true, true));
+ }
+
+ @Handler
+ void onDebugStop()
+ {
+ // If R is busy when a debug stop is requested, interrupt it and wait
+ // for the interrupt to complete before killing the debugger.
+ if (busy_)
+ {
+ interrupt_.interruptR(new ApplicationInterrupt.InterruptHandler()
+ {
+ @Override
+ public void onInterruptFinished()
+ {
+ stopDebugging();
+ }
+ });
+ }
+ else
+ {
+ stopDebugging();
+ }
+ }
+
+ @Handler
+ void onDebugStep()
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(NEXT_COMMAND, true, true));
+ }
+
+ @Handler
+ void onDebugStepInto()
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(STEP_INTO_COMMAND, true, true));
+ }
+
+ @Handler
+ void onDebugFinish()
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(FINISH_COMMAND, true, true));
+ }
+
+ @Override
+ public void onRestartStatus(RestartStatusEvent event)
+ {
+ if (event.getStatus() == RestartStatusEvent.RESTART_INITIATED)
+ {
+ // Restarting R cleans up the state we use to persist information about
+ // the debug session on the server, so we need to kill the client's
+ // debug session when this happens
+ if (debugMode_ == DebugMode.TopLevel)
+ {
+ highlightDebugPosition(previousLineData_, true);
+ leaveDebugMode();
+ }
+ }
+ }
+
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ if (!session_.getSessionInfo().getHaveAdvancedStepCommands())
+ {
+ commands_.debugStepInto().remove();
+ commands_.debugFinish().remove();
+ }
+ }
+
+ @Override
+ public void onBusy(BusyEvent event)
+ {
+ busy_ = event.isBusy();
+ }
+
+ // Public methods ----------------------------------------------------------
+
+ public void enterDebugMode(DebugMode mode)
+ {
+ // when entering function debug context, save the current top-level debug
+ // mode so we can restore it later
+ if (mode == DebugMode.Function)
+ {
+ setDebugging(true);
+ }
+ setAdvancedCommandsVisible(mode == DebugMode.Function);
+ debugMode_ = mode;
+ }
+
+ public void leaveDebugMode()
+ {
+ setDebugging(false);
+ debugMode_ = DebugMode.Normal;
+ debugFile_ = "";
+ }
+
+ public DebugMode getDebugMode()
+ {
+ return debugMode_;
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private void highlightDebugPosition(LineData lineData, boolean finished)
+ {
+ FileSystemItem sourceFile = FileSystemItem.createFile(debugFile_);
+ DebugFilePosition position = DebugFilePosition.create(
+ lineData.getLineNumber(),
+ lineData.getEndLineNumber(),
+ lineData.getCharacterNumber(),
+ lineData.getEndCharacterNumber());
+ eventBus_.fireEvent(new OpenSourceFileEvent(sourceFile,
+ (FilePosition) position.cast(),
+ FileTypeRegistry.R,
+ finished ?
+ NavigationMethod.DebugEnd :
+ NavigationMethod.DebugStep));
+ }
+
+ private void setDebugging(boolean debugging)
+ {
+ if (debugging_ != debugging)
+ {
+ debugging_ = debugging;
+ setDebugCommandsEnabled(debugging_);
+ eventBus_.fireEvent(new DebugModeChangedEvent(debugging_));
+ }
+ }
+
+ private void setDebugCommandsEnabled(boolean enabled)
+ {
+ commands_.debugContinue().setEnabled(enabled);
+ commands_.debugStep().setEnabled(enabled);
+ commands_.debugStop().setEnabled(enabled);
+ commands_.debugStepInto().setEnabled(enabled);
+ commands_.debugFinish().setEnabled(enabled);
+ }
+
+ private void setAdvancedCommandsVisible(boolean visible)
+ {
+ commands_.debugFinish().setVisible(visible);
+ commands_.debugStepInto().setVisible(visible);
+ }
+
+ private void stopDebugging()
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(STOP_COMMAND, true, true));
+ }
+
+ public static final String STOP_COMMAND = "Q";
+ public static final String NEXT_COMMAND = "n";
+ public static final String CONTINUE_COMMAND = "c";
+ public static final String STEP_INTO_COMMAND = "s";
+ public static final String FINISH_COMMAND = "f";
+
+ private final EventBus eventBus_;
+ private final Session session_;
+ private final Commands commands_;
+ private final ApplicationInterrupt interrupt_;
+
+ private DebugMode debugMode_ = DebugMode.Normal;
+ private String debugFile_ = "";
+ private LineData previousLineData_ = null;
+ private boolean debugging_ = false;
+ private boolean busy_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/DebuggingServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/DebuggingServerOperations.java
new file mode 100644
index 0000000..eaeb71d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/DebuggingServerOperations.java
@@ -0,0 +1,62 @@
+/*
+ * DebuggingServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JsArray;
+
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.debugging.model.FunctionState;
+import org.rstudio.studio.client.common.debugging.model.FunctionSteps;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+public interface DebuggingServerOperations
+{
+ public void getFunctionSteps(
+ String functionName,
+ String fileName,
+ String packageName,
+ int[] lineNumbers,
+ ServerRequestCallback<JsArray<FunctionSteps>> requestCallback);
+
+ public void setFunctionBreakpoints(
+ String functionName,
+ String fileName,
+ String packageName,
+ ArrayList<String> steps,
+ ServerRequestCallback<Void> requestCallback);
+
+ public void getFunctionState(
+ String functionName,
+ String fileName,
+ int lineNumber,
+ ServerRequestCallback<FunctionState> requestCallback);
+
+ public void setErrorManagementType(
+ int type,
+ ServerRequestCallback<Void> requestCallback);
+
+ public void updateBreakpoints(
+ ArrayList<Breakpoint> breakpoints,
+ boolean set,
+ boolean arm,
+ ServerRequestCallback<Void> requestCallback);
+
+ public void removeAllBreakpoints(
+ ServerRequestCallback<Void> requestCallback);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ErrorManager.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/ErrorManager.java
new file mode 100644
index 0000000..7482eab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/ErrorManager.java
@@ -0,0 +1,216 @@
+/*
+ * ErrorManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.debugging;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.debugging.events.ErrorHandlerChangedEvent;
+import org.rstudio.studio.client.common.debugging.model.ErrorHandlerType;
+import org.rstudio.studio.client.common.debugging.model.ErrorManagerState;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ErrorManager
+ implements DebugModeChangedEvent.Handler,
+ ErrorHandlerChangedEvent.Handler,
+ SessionInitHandler
+{
+ public interface Binder
+ extends CommandBinder<Commands, ErrorManager> {}
+
+ private enum DebugHandlerState
+ {
+ None,
+ Pending
+ }
+
+ @Inject
+ public ErrorManager(EventBus events,
+ Binder binder,
+ Commands commands,
+ DebuggingServerOperations server,
+ Session session)
+ {
+ events_ = events;
+ server_ = server;
+ commands_ = commands;
+ session_ = session;
+ binder.bind(commands, this);
+
+ events_.addHandler(DebugModeChangedEvent.TYPE, this);
+ events_.addHandler(ErrorHandlerChangedEvent.TYPE, this);
+ events_.addHandler(SessionInitEvent.TYPE, this);
+ }
+
+ // Event and command handlers ----------------------------------------------
+
+ @Override
+ public void onDebugModeChanged(DebugModeChangedEvent event)
+ {
+ // if we expected to go into debug mode, this is what we were waiting
+ // for--change the error handler back to whatever it was formerly
+ if (event.debugging() &&
+ debugHandlerState_ == DebugHandlerState.Pending)
+ {
+ setErrorManagementType(previousHandlerType_);
+ }
+ debugHandlerState_ = DebugHandlerState.None;
+ }
+
+ @Override
+ public void onErrorHandlerChanged(ErrorHandlerChangedEvent event)
+ {
+ int newType = event.getHandlerType().getType();
+ if (newType != errorManagerState_.getErrorHandlerType())
+ {
+ errorManagerState_.setErrorHandlerType(newType);
+ syncHandlerCommandsCheckedState();
+ }
+ }
+
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ errorManagerState_ = session_.getSessionInfo().getErrorState();
+ if (session_.getSessionInfo().getHaveSrcrefAttribute())
+ {
+ syncHandlerCommandsCheckedState();
+ }
+ else
+ {
+ // If we don't have source references, disable the commands that
+ // set error handlers (these generally work on user code only, and
+ // we can't reliably distinguish user code without source refs)
+ commands_.errorsMessage().setEnabled(false);
+ commands_.errorsTraceback().setEnabled(false);
+ commands_.errorsBreak().setEnabled(false);
+ }
+ }
+
+ @Handler
+ public void onErrorsMessage()
+ {
+ setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_MESSAGE);
+ }
+
+ @Handler
+ public void onErrorsTraceback()
+ {
+ setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_TRACEBACK);
+ }
+
+ @Handler
+ public void onErrorsBreak()
+ {
+ setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_BREAK);
+ }
+
+ // Public methods ----------------------------------------------------------
+
+ public void setDebugSessionHandlerType(
+ int type,
+ final ServerRequestCallback<Void> callback)
+ {
+ if (type == errorManagerState_.getErrorHandlerType())
+ return;
+
+ setErrorManagementType(type, new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ previousHandlerType_ = errorManagerState_.getErrorHandlerType();
+ debugHandlerState_ = DebugHandlerState.Pending;
+ callback.onResponseReceived(v);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onError(error);
+ }
+ });
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private int getErrorHandlerType()
+ {
+ return errorManagerState_.getErrorHandlerType();
+ }
+
+ private void setErrorManagementTypeCommand(int type)
+ {
+ // The error handler may be currently overridden for debug mode. If the
+ // user changes the setting via command during debug mode, we don't want
+ // to change it back when leaving debug mode.
+ debugHandlerState_ = DebugHandlerState.None;
+ setErrorManagementType(type);
+ }
+
+ private void setErrorManagementType(
+ int type,
+ ServerRequestCallback<Void> callback)
+ {
+ server_.setErrorManagementType(type, callback);
+ }
+
+ private void setErrorManagementType(int type)
+ {
+ setErrorManagementType(type,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ // No action necessary--the server emits an event when the handler
+ // type changes
+ }
+ });
+ }
+
+ private void syncHandlerCommandsCheckedState()
+ {
+ int type = getErrorHandlerType();
+ commands_.errorsMessage().setChecked(
+ type == ErrorHandlerType.ERRORS_MESSAGE);
+ commands_.errorsTraceback().setChecked(
+ type == ErrorHandlerType.ERRORS_TRACEBACK);
+ commands_.errorsBreak().setChecked(
+ type == ErrorHandlerType.ERRORS_BREAK);
+ }
+
+ private final EventBus events_;
+ private final DebuggingServerOperations server_;
+ private final Session session_;
+ private final Commands commands_;
+
+ private DebugHandlerState debugHandlerState_ = DebugHandlerState.None;
+ private ErrorManagerState errorManagerState_;
+ private int previousHandlerType_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/events/BreakpointsSavedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/BreakpointsSavedEvent.java
new file mode 100644
index 0000000..b3e4890
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/BreakpointsSavedEvent.java
@@ -0,0 +1,65 @@
+/*
+ * BreakpointSavedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.debugging.events;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BreakpointsSavedEvent
+ extends GwtEvent<BreakpointsSavedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onBreakpointsSaved(BreakpointsSavedEvent event);
+ }
+
+ public BreakpointsSavedEvent(
+ ArrayList<Breakpoint> breakpoints, boolean successful)
+ {
+ breakpoints_ = breakpoints;
+ successful_ = successful;
+ }
+
+ public boolean successful()
+ {
+ return successful_;
+ }
+
+ public ArrayList<Breakpoint> breakpoints()
+ {
+ return breakpoints_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBreakpointsSaved(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private ArrayList<Breakpoint> breakpoints_;
+ private boolean successful_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/events/ErrorHandlerChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/ErrorHandlerChangedEvent.java
new file mode 100644
index 0000000..19d3701
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/ErrorHandlerChangedEvent.java
@@ -0,0 +1,55 @@
+/*
+ * ErrorHandlerChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.debugging.events;
+
+import org.rstudio.studio.client.common.debugging.model.ErrorHandlerType;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ErrorHandlerChangedEvent
+ extends GwtEvent<ErrorHandlerChangedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onErrorHandlerChanged(ErrorHandlerChangedEvent event);
+ }
+
+ public ErrorHandlerChangedEvent(ErrorHandlerType type)
+ {
+ type_ = type;
+ }
+
+ public ErrorHandlerType getHandlerType()
+ {
+ return type_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onErrorHandlerChanged(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private ErrorHandlerType type_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageLoadedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageLoadedEvent.java
new file mode 100644
index 0000000..47b8563
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageLoadedEvent.java
@@ -0,0 +1,53 @@
+/*
+ * PackageLoadedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.debugging.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PackageLoadedEvent
+ extends GwtEvent<PackageLoadedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPackageLoaded(PackageLoadedEvent event);
+ }
+
+ public PackageLoadedEvent(String packageName)
+ {
+ packageName_ = packageName;
+ }
+
+ public String getPackageName()
+ {
+ return packageName_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPackageLoaded(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private String packageName_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageUnloadedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageUnloadedEvent.java
new file mode 100644
index 0000000..0594542
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/PackageUnloadedEvent.java
@@ -0,0 +1,53 @@
+/*
+ * PackageUnloadedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.debugging.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PackageUnloadedEvent
+ extends GwtEvent<PackageUnloadedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPackageUnloaded(PackageUnloadedEvent event);
+ }
+
+ public PackageUnloadedEvent(String packageName)
+ {
+ packageName_ = packageName;
+ }
+
+ public String getPackageName()
+ {
+ return packageName_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPackageUnloaded(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private String packageName_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/events/UnhandledErrorEvent.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/UnhandledErrorEvent.java
new file mode 100644
index 0000000..d36a045
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/events/UnhandledErrorEvent.java
@@ -0,0 +1,55 @@
+/*
+ * UnhandledErrorEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.debugging.events;
+
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class UnhandledErrorEvent
+ extends GwtEvent<UnhandledErrorEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onUnhandledError(UnhandledErrorEvent event);
+ }
+
+ public UnhandledErrorEvent(UnhandledError err)
+ {
+ err_ = err;
+ }
+
+ public UnhandledError getError()
+ {
+ return err_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onUnhandledError(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private UnhandledError err_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/Breakpoint.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/Breakpoint.java
new file mode 100644
index 0000000..7424cd3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/Breakpoint.java
@@ -0,0 +1,188 @@
+/*
+ * Breakpoint.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+// This class represents a breakpoint in nearly every context:
+// instances are used in the editor (AceEditorWidget), in the breakpoint manager
+// (BreakpointManager), and are also persisted raw in the project state
+// (BreakpointState).
+public class Breakpoint extends JavaScriptObject
+{
+ protected Breakpoint() {}
+
+ // Syntactically an enum would be preferable here but GWT enum wrappers
+ // don't serialize well inside native JS objects. Do not change these values;
+ // they are persisted in project storage.
+ public final static int STATE_PROCESSING = 0;
+ public final static int STATE_ACTIVE = 1;
+ public final static int STATE_INACTIVE = 2;
+ public final static int STATE_REMOVING = 3;
+
+ public final static int TYPE_FUNCTION = 0;
+ public final static int TYPE_TOPLEVEL = 1;
+
+ public native static Breakpoint create(
+ int breakpointId,
+ String path,
+ String functionName,
+ int lineNumber,
+ int initialState,
+ int type)
+ /*-{
+ return {
+ id : breakpointId,
+ path : path.trim(),
+ line_number : lineNumber,
+ function_steps : "",
+ function_name : functionName,
+ state : initialState,
+ editor_state: initialState,
+ editor_line_number: lineNumber,
+ is_pending_debug_completion: false,
+ needs_updated_steps: false,
+ type: type,
+ is_package_breakpoint: false,
+ package_name: ""
+ };
+ }-*/;
+
+ public final native void addFunctionSteps(
+ String function_name,
+ int lineNumber,
+ String functionSteps)
+ /*-{
+ this.function_name = function_name;
+ this.line_number = lineNumber;
+ this.function_steps = functionSteps;
+ }-*/;
+
+ public final native int getBreakpointId()
+ /*-{
+ return this.id;
+ }-*/;
+
+ public final native int getLineNumber()
+ /*-{
+ return this.line_number;
+ }-*/;
+
+ public final native String getFunctionName()
+ /*-{
+ return this.function_name;
+ }-*/;
+
+ public final native String getFunctionSteps()
+ /*-{
+ return this.function_steps;
+ }-*/;
+
+ public final native int getState()
+ /*-{
+ return this.state;
+ }-*/;
+
+ public final native void setState (int state)
+ /*-{
+ this.state = state;
+ // when the breakpoint becomes active, clear the flag indicating that
+ // it needs updating
+ if (this.state == 1)
+ {
+ this.needs_updated_steps = false;
+ }
+ }-*/;
+
+ public final native int getEditorState()
+ /*-{
+ return this.editor_state;
+ }-*/;
+
+ public final native void setEditorState (int state)
+ /*-{
+ this.editor_state = state;
+ }-*/;
+
+ public final native int getEditorLineNumber()
+ /*-{
+ return this.editor_line_number;
+ }-*/;
+
+ public final native void setEditorLineNumber(int lineNumber)
+ /*-{
+ this.editor_line_number = lineNumber;
+ }-*/;
+
+ public final native void moveToLineNumber(int lineNumber)
+ /*-{
+ this.line_number = lineNumber;
+ this.editor_line_number = lineNumber;
+ }-*/;
+
+ public final native boolean isPendingDebugCompletion()
+ /*-{
+ return this.is_pending_debug_completion;
+ }-*/;
+
+ public final native boolean setPendingDebugCompletion(boolean pending)
+ /*-{
+ this.is_pending_debug_completion = pending;
+ }-*/;
+
+ public final native boolean needsUpdatedSteps()
+ /*-{
+ return this.needs_updated_steps;
+ }-*/;
+
+ public final native void markStepsNeedUpdate()
+ /*-{
+ this.needs_updated_steps = true;
+ }-*/;
+
+ public final native boolean isInFile(String filename)
+ /*-{
+ return this.path == filename;
+ }-*/;
+
+ public final native boolean isInPath(String path)
+ /*-{
+ return this.path.indexOf(path) == 0;
+ }-*/;
+
+ public final native String getPath()
+ /*-{
+ return this.path;
+ }-*/;
+
+ public final native int getType()
+ /*-{
+ return this.type;
+ }-*/;
+
+ public final native String getPackageName() /*-{
+ return this.package_name;
+ }-*/;
+
+ public final native boolean isPackageBreakpoint() /*-{
+ return this.is_package_breakpoint;
+ }-*/;
+
+ public final native void markAsPackageBreakpoint(String packageName) /*-{
+ this.is_package_breakpoint = true;
+ this.package_name = packageName;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/BreakpointState.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/BreakpointState.java
new file mode 100644
index 0000000..3dce3c3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/BreakpointState.java
@@ -0,0 +1,36 @@
+/*
+ * BreakpointState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class BreakpointState extends JavaScriptObject
+{
+ protected BreakpointState() {}
+
+ public static final native BreakpointState create () /*-{
+ return { breakpoints: [] };
+ }-*/;
+
+ public final native void addPersistedBreakpoint(Breakpoint breakpoint) /*-{
+ this.breakpoints.push(breakpoint);
+ }-*/;
+
+ public final native JsArray<Breakpoint> getPersistedBreakpoints() /*-{
+ return this.breakpoints ? this.breakpoints : [];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorFrame.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorFrame.java
new file mode 100644
index 0000000..6abf91a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorFrame.java
@@ -0,0 +1,31 @@
+/*
+ * ErrorFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import org.rstudio.studio.client.workbench.views.environment.events.LineData;
+
+public class ErrorFrame extends LineData
+{
+ protected ErrorFrame() {}
+
+ public final native String getFunctionName() /*-{
+ return this.func;
+ }-*/;
+
+ public final native String getFileName() /*-{
+ return this.file.trim();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorHandlerType.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorHandlerType.java
new file mode 100644
index 0000000..9c06b6c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorHandlerType.java
@@ -0,0 +1,34 @@
+/*
+ * ErrorHandlerType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ErrorHandlerType extends JavaScriptObject
+{
+ // Error handler types understood by the server. These values are persisted
+ // in user settings, so their meaning must be preserved.
+ public static final int ERRORS_MESSAGE = 0;
+ public static final int ERRORS_TRACEBACK = 1;
+ public static final int ERRORS_BREAK = 2;
+ public static final int ERRORS_CUSTOM = 3;
+
+ protected ErrorHandlerType() {}
+
+ public final native int getType() /*-{
+ return this.type;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorManagerState.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorManagerState.java
new file mode 100644
index 0000000..941357b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/ErrorManagerState.java
@@ -0,0 +1,31 @@
+/*
+ * ErrorManagerState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ErrorManagerState extends JavaScriptObject
+{
+ protected ErrorManagerState() {}
+
+ public final native int getErrorHandlerType() /*-{
+ return this.error_handler_type;
+ }-*/;
+
+ public final native void setErrorHandlerType(int type) /*-{
+ this.error_handler_type = type;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionState.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionState.java
new file mode 100644
index 0000000..5fba80e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionState.java
@@ -0,0 +1,35 @@
+/*
+ * FunctionState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FunctionState extends JavaScriptObject
+{
+ protected FunctionState() {}
+
+ public final native boolean getSyncState() /*-{
+ return this.sync_state;
+ }-*/;
+
+ public final native boolean isPackageFunction() /*-{
+ return this.is_package_function;
+ }-*/;
+
+ public final native String getPackageName() /*-{
+ return this.package_name;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionSteps.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionSteps.java
new file mode 100644
index 0000000..44bd019
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/FunctionSteps.java
@@ -0,0 +1,35 @@
+/*
+ * FunctionSteps.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FunctionSteps extends JavaScriptObject
+{
+ protected FunctionSteps() {}
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native int getLineNumber() /*-{
+ return this.line;
+ }-*/;
+
+ public final native String getSteps() /*-{
+ return this.at;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/TopLevelLineData.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/TopLevelLineData.java
new file mode 100644
index 0000000..7583238
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/TopLevelLineData.java
@@ -0,0 +1,28 @@
+package org.rstudio.studio.client.common.debugging.model;
+
+import org.rstudio.studio.client.workbench.views.environment.events.LineData;
+
+public class TopLevelLineData extends LineData
+{
+ protected TopLevelLineData() {}
+
+ public static final int STATE_PAUSED = 0;
+ public static final int STATE_INJECTION_SITE = 1;
+ public static final int STATE_FINISHED = 2;
+
+ public final native int getStep() /*-{
+ return this.step;
+ }-*/;
+
+ public final native boolean getFinished() /*-{
+ return this.state == 2;
+ }-*/;
+
+ public final native int getState() /*-{
+ return this.state;
+ }-*/;
+
+ public final native boolean getNeedsBreakpointInjection() /*-{
+ return this.needs_breakpoint_injection;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/model/UnhandledError.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/UnhandledError.java
new file mode 100644
index 0000000..a3249a3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/model/UnhandledError.java
@@ -0,0 +1,32 @@
+/*
+ * UnhandledError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class UnhandledError extends JavaScriptObject
+{
+ protected UnhandledError() {}
+
+ public final native String getErrorMessage() /*-{
+ return this.message;
+ }-*/;
+
+ public final native JsArray<ErrorFrame> getErrorFrames() /*-{
+ return this.frames;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.java
new file mode 100644
index 0000000..220a1ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.java
@@ -0,0 +1,112 @@
+/*
+ * ConsoleError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.ui;
+
+import org.rstudio.studio.client.common.debugging.model.ErrorFrame;
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ConsoleError extends Composite
+{
+ private static ConsoleErrorUiBinder uiBinder = GWT
+ .create(ConsoleErrorUiBinder.class);
+
+ interface ConsoleErrorUiBinder extends UiBinder<Widget, ConsoleError>
+ {
+ }
+
+ public interface Observer
+ {
+ void onErrorBoxResize();
+ void showSourceForFrame(ErrorFrame frame);
+ void runCommandWithDebug(String command);
+ }
+
+ public ConsoleError(UnhandledError err,
+ String errorClass,
+ Observer observer,
+ String command)
+ {
+ observer_ = observer;
+ command_ = command;
+
+ initWidget(uiBinder.createAndBindUi(this));
+
+ errorMessage.setText(err.getErrorMessage().trim());
+ errorMessage.addStyleName(errorClass);
+
+ ClickHandler showHideTraceback = new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ setTracebackVisible(!showingTraceback_);
+ observer_.onErrorBoxResize();
+ }
+ };
+
+ ClickHandler rerunWithDebug = new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ observer_.runCommandWithDebug(command_);
+ }
+ };
+
+ showTracebackText.addClickHandler(showHideTraceback);
+ showTracebackImage.addClickHandler(showHideTraceback);
+ rerunText.addClickHandler(rerunWithDebug);
+ rerunImage.addClickHandler(rerunWithDebug);
+
+ for (int i = err.getErrorFrames().length() - 1; i >= 0; i--)
+ {
+ ConsoleErrorFrame frame = new ConsoleErrorFrame(i + 1,
+ err.getErrorFrames().get(i), observer_);
+ framePanel.add(frame);
+ }
+ }
+
+ public void setTracebackVisible(boolean visible)
+ {
+ showingTraceback_ = visible;
+ showTracebackText.setText(showingTraceback_ ?
+ "Hide Traceback" : "Show Traceback");
+ framePanel.setVisible(showingTraceback_);
+ }
+
+ @UiField Anchor showTracebackText;
+ @UiField Image showTracebackImage;
+ @UiField Anchor rerunText;
+ @UiField Image rerunImage;
+ @UiField HTMLPanel framePanel;
+ @UiField Label errorMessage;
+
+ private Observer observer_;
+ private boolean showingTraceback_ = false;
+ private String command_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.ui.xml b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.ui.xml
new file mode 100644
index 0000000..b386a6c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleError.ui.xml
@@ -0,0 +1,71 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style>
+ @eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+
+ .consoleErrorBox {
+ position: relative;
+ overflow: hidden;
+ margin: 5px 5px 5px 0px;
+ padding: 5px 0px 5px 0px;
+ }
+
+ .consoleErrorCommands {
+ top: 5px;
+ float: right;
+ margin-right: 10px;
+ margin-bottom: 15px;
+ line-height: 80%;
+ position: relative;
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ }
+
+ .errorCommandText {
+ font-family: proportionalFont;
+ font-size: 80%;
+ opacity: 0.50;
+ cursor: pointer;
+ }
+
+ .errorCommandText:hover {
+ opacity: 1;
+ }
+
+ .errorCommandImage {
+ cursor: pointer;
+ }
+
+ .errorMessage {
+ display: inline;
+ }
+
+ .framePanel {
+ margin-top: 1em;
+ margin-left: 0.5em;
+ }
+ </ui:style>
+ <ui:image src="traceback.png" field="traceback"></ui:image>
+ <ui:image src="rerun.png" field="rerun"></ui:image>
+ <g:HTMLPanel styleName="{style.consoleErrorBox} ace_console_error">
+ <g:HTMLPanel styleName="{style.consoleErrorCommands}">
+ <g:HTMLPanel>
+ <g:Image resource="{traceback}" ui:field="showTracebackImage" styleName="{style.errorCommandImage}"></g:Image>
+ <g:Anchor styleName="{style.errorCommandText}" ui:field="showTracebackText">
+ Show Traceback
+ </g:Anchor>
+ </g:HTMLPanel>
+ <g:HTMLPanel>
+ <g:Image resource="{rerun}" ui:field="rerunImage" styleName="{style.errorCommandImage}"></g:Image>
+ <g:Anchor styleName="{style.errorCommandText}" ui:field="rerunText">
+ Rerun with Debug
+ </g:Anchor>
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+ <g:Label ui:field="errorMessage" styleName="{style.errorMessage}"></g:Label>
+ <g:HTMLPanel ui:field="framePanel" visible="false" styleName="{style.framePanel}">
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.java b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.java
new file mode 100644
index 0000000..3249a98
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.java
@@ -0,0 +1,81 @@
+/*
+ * ConsoleErrorFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.debugging.ui;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.debugging.model.ErrorFrame;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ConsoleErrorFrame extends Composite
+{
+
+ private static ConsoleErrorFrameUiBinder uiBinder = GWT
+ .create(ConsoleErrorFrameUiBinder.class);
+
+ interface ConsoleErrorFrameUiBinder extends
+ UiBinder<Widget, ConsoleErrorFrame>
+ {
+ }
+
+ public ConsoleErrorFrame(int number, ErrorFrame frame,
+ ConsoleError.Observer observer)
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+
+ frame_ = frame;
+ observer_ = observer;
+
+ boolean hasSource = !frame.getFileName().isEmpty();
+ functionName.setText(frame.getFunctionName() + (hasSource ? " at" : ""));
+ frameNumber.setText((new Integer(number)).toString());
+ if (hasSource)
+ {
+ sourceLink.setText(
+ FileSystemItem.getNameFromPath(frame.getFileName()) +
+ "#" + frame.getLineNumber());
+ sourceLink.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (frame_ != null)
+ {
+ observer_.showSourceForFrame(frame_);
+ }
+ }
+ });
+ }
+ }
+
+ @UiField
+ Label functionName;
+ @UiField
+ Anchor sourceLink;
+ @UiField
+ Label frameNumber;
+
+ private ConsoleError.Observer observer_;
+ private ErrorFrame frame_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.ui.xml b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.ui.xml
new file mode 100644
index 0000000..52ac3aa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/ConsoleErrorFrame.ui.xml
@@ -0,0 +1,40 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style>
+ .function {
+ display: inline;
+ }
+
+ .sourceLink {
+ cursor: pointer;
+ }
+
+ .functionBlock {
+ display: inline-block;
+ }
+
+ .frameNumber {
+ vertical-align: top;
+ float: left;
+ font-size: 80%;
+ width: 1.5em;
+ text-align: right;
+ margin-top: 0.3em;
+ white-space: nowrap;
+ }
+
+ .frame {
+ clear: both;
+ }
+ </ui:style>
+ <g:HTMLPanel styleName="{style.frame}">
+ <g:Label styleName="{style.frameNumber} ace_constant ace_numeric"
+ ui:field="frameNumber"></g:Label>
+ <g:HTMLPanel styleName="{style.functionBlock}">
+ <g:Label ui:field="functionName" styleName="{style.function}"></g:Label>
+ <g:Anchor ui:field="sourceLink"
+ styleName="{style.sourceLink} ace_constant ace_numeric"></g:Anchor>
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/rerun.png b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/rerun.png
new file mode 100644
index 0000000..5cfdc0d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/rerun.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/traceback.png b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/traceback.png
new file mode 100644
index 0000000..fdbfff6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/debugging/ui/traceback.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/dialog/DesktopDialogBuilderFactory.java b/src/gwt/src/org/rstudio/studio/client/common/dialog/DesktopDialogBuilderFactory.java
new file mode 100644
index 0000000..442c910
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/dialog/DesktopDialogBuilderFactory.java
@@ -0,0 +1,127 @@
+/*
+ * DesktopDialogBuilderFactory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.dialog;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.widget.DialogBuilder;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+public class DesktopDialogBuilderFactory implements DialogBuilderFactory
+{
+ static class Builder extends DialogBuilderBase
+ {
+ public Builder(int type, String caption, String message)
+ {
+ super(type, caption);
+ message_ = message;
+ }
+
+ @Override
+ public void showModal()
+ {
+ if (buttons_.size() == 0)
+ addButton("OK");
+
+ StringBuilder buttons = new StringBuilder();
+ String delim = "";
+ for (ButtonSpec button : buttons_)
+ {
+ buttons.append(delim).append(button.label);
+ delim = "|";
+ }
+
+ int result = Desktop.getFrame().showMessageBox(type,
+ caption,
+ message_,
+ buttons.toString(),
+ defaultButton_,
+ buttons_.size() - 1);
+
+ if (result >= buttons_.size())
+ return;
+
+ // If button has an operation, execute it in a deferred way. This
+ // keeps the semantics more consistent with the web version of
+ // the message dialog, which executes asynchronously.
+
+ final ButtonSpec buttonSpec = buttons_.get(result);
+ if (buttonSpec.operation != null)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ if (buttonSpec.operation != null)
+ buttonSpec.operation.execute();
+ }
+ });
+ }
+ else if (buttonSpec.progressOperation != null)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ final GlobalDisplay globalDisplay = RStudioGinjector.INSTANCE
+ .getGlobalDisplay();
+
+ buttonSpec.progressOperation.execute(new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ if (dismissProgress_ != null)
+ dismissProgress_.execute();
+ dismissProgress_ = globalDisplay.showProgress(message);
+ }
+
+ public void clearProgress()
+ {
+ if (dismissProgress_ != null)
+ dismissProgress_.execute();
+ }
+
+ public void onCompleted()
+ {
+ if (dismissProgress_ != null)
+ dismissProgress_.execute();
+ }
+
+ public void onError(String message)
+ {
+ if (dismissProgress_ != null)
+ dismissProgress_.execute();
+
+ globalDisplay.showErrorMessage("Error", message);
+ }
+ });
+ }
+ });
+ }
+ }
+
+ private final String message_;
+ private Command dismissProgress_;
+ }
+
+ public DialogBuilder create(int type, String caption, String message)
+ {
+ return new Builder(type, caption, message);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderBase.java b/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderBase.java
new file mode 100644
index 0000000..5dd5bde
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderBase.java
@@ -0,0 +1,75 @@
+/*
+ * DialogBuilderBase.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.dialog;
+
+import org.rstudio.core.client.widget.DialogBuilder;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressOperation;
+
+import java.util.ArrayList;
+
+public abstract class DialogBuilderBase implements DialogBuilder
+{
+ protected static class ButtonSpec
+ {
+ public String label;
+ public Operation operation;
+ public ProgressOperation progressOperation;
+ }
+
+ public DialogBuilderBase(int type, String caption)
+ {
+ this.type = type;
+ this.caption = caption;
+ }
+
+ public DialogBuilder addButton(String label)
+ {
+ return addButton(label, (Operation)null);
+ }
+
+ public DialogBuilder addButton(String label, Operation operation)
+ {
+ ButtonSpec button = new ButtonSpec();
+ button.label = label;
+ button.operation = operation;
+ buttons_.add(button);
+
+ return this;
+ }
+
+ public DialogBuilder addButton(String label, ProgressOperation operation)
+ {
+ ButtonSpec button = new ButtonSpec();
+ button.label = label;
+ button.progressOperation = operation;
+ buttons_.add(button);
+
+ return this;
+ }
+
+ public DialogBuilder setDefaultButton(int index)
+ {
+ defaultButton_ = index;
+ return this;
+ }
+
+ public abstract void showModal();
+
+ protected final int type;
+ protected final String caption;
+ protected ArrayList<ButtonSpec> buttons_ = new ArrayList<ButtonSpec>();
+ protected int defaultButton_ = 0;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderFactory.java b/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderFactory.java
new file mode 100644
index 0000000..a9cf8cc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/dialog/DialogBuilderFactory.java
@@ -0,0 +1,22 @@
+/*
+ * DialogBuilderFactory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.dialog;
+
+import org.rstudio.core.client.widget.DialogBuilder;
+
+public interface DialogBuilderFactory
+{
+ DialogBuilder create(int type, String caption, String message);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/dialog/WebDialogBuilderFactory.java b/src/gwt/src/org/rstudio/studio/client/common/dialog/WebDialogBuilderFactory.java
new file mode 100644
index 0000000..4759359
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/dialog/WebDialogBuilderFactory.java
@@ -0,0 +1,77 @@
+/*
+ * WebDialogBuilderFactory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.dialog;
+
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.widget.*;
+
+public class WebDialogBuilderFactory implements DialogBuilderFactory
+{
+ static class Builder extends DialogBuilderBase
+ {
+ Builder(int type, String caption, Widget message)
+ {
+ super(type, caption);
+ message_ = message;
+ }
+
+ @Override
+ public void showModal()
+ {
+ if (buttons_.size() == 0)
+ addButton("OK");
+
+ createDialog().showModal();
+ }
+
+ private MessageDialog createDialog()
+ {
+ MessageDialog messageDialog = new MessageDialog(type,
+ caption,
+ message_);
+ for (int i = 0; i < buttons_.size(); i++)
+ {
+ ButtonSpec button = buttons_.get(i);
+ if (button.progressOperation != null)
+ {
+ messageDialog.addButton(button.label,
+ button.progressOperation,
+ defaultButton_ == i,
+ i == buttons_.size() - 1);
+ }
+ else
+ {
+ messageDialog.addButton(button.label,
+ button.operation,
+ defaultButton_ == i,
+ i == buttons_.size() - 1);
+ }
+ }
+ return messageDialog;
+ }
+
+ private final Widget message_;
+ }
+
+ public DialogBuilder create(int type, String caption, String message)
+ {
+ return new Builder(type, caption, MessageDialog.labelForMessage(message));
+ }
+
+ public DialogBuilder create(int type, String caption, Widget messageWidget)
+ {
+ return new Builder(type, caption, messageWidget);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/fileexport/FileExport.java b/src/gwt/src/org/rstudio/studio/client/common/fileexport/FileExport.java
new file mode 100644
index 0000000..9143f2d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/fileexport/FileExport.java
@@ -0,0 +1,142 @@
+/*
+ * FileExport.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.fileexport;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+import com.google.inject.Inject;
+
+public class FileExport
+{
+ public FileExport()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ void initialize(GlobalDisplay globalDisplay,
+ FilesServerOperations server)
+ {
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ }
+
+ public void export(String caption, String description, FileSystemItem file)
+ {
+ ArrayList<FileSystemItem> files = new ArrayList<FileSystemItem>();
+ files.add(file);
+ export(caption, description, null, files);
+ }
+
+ public void export(String caption,
+ String description,
+ final FileSystemItem parentDir,
+ final ArrayList<FileSystemItem> files)
+ {
+ // validation: some files provided
+ if (files.size() == 0)
+ return ;
+
+ // case: single file which is not a folder
+ if ((files.size()) == 1 && !files.get(0).isDirectory())
+ {
+ final FileSystemItem file = files.get(0);
+
+ showFileExport(caption,
+ description,
+ file.getStem(),
+ file.getExtension(),
+ new ProgressOperationWithInput<String>(){
+ public void execute(String name, ProgressIndicator progress)
+ {
+ // progress complete
+ progress.onCompleted();
+
+ // execute the download (open in a new window)
+ globalDisplay_.openWindow(server_.getFileExportUrl(name, file));
+
+ }
+ });
+ }
+
+ // case: folder or multiple files
+ else
+ {
+ // determine the default zip file name based on the selection
+ String defaultArchiveName;
+ if (files.size() == 1)
+ defaultArchiveName = files.get(0).getStem();
+ else
+ defaultArchiveName = "rstudio-export";
+
+ // prompt user
+ final String ZIP = ".zip";
+ showFileExport(caption,
+ description,
+ defaultArchiveName,
+ ZIP,
+ new ProgressOperationWithInput<String>(){
+
+ public void execute(String archiveName, ProgressIndicator progress)
+ {
+ // progress complete
+ progress.onCompleted();
+
+ // force zip extension in case the user deleted it
+ if (!archiveName.endsWith(ZIP))
+ archiveName += ZIP;
+
+ // build list of filenames
+ ArrayList<String> filenames = new ArrayList<String>();
+ for (FileSystemItem file : files)
+ filenames.add(file.getName());
+
+ // execute the download (open in a new window)
+ globalDisplay_.openWindow(server_.getFileExportUrl(archiveName,
+ parentDir,
+ filenames));
+ }
+ });
+
+ }
+ }
+
+ private void showFileExport(String caption,
+ String description,
+ String defaultName,
+ String defaultExtension,
+ ProgressOperationWithInput<String> operation)
+ {
+ globalDisplay_.promptForText(
+ caption,
+ "The " + description + " will be downloaded to your " +
+ "computer. Please specify a name for the downloaded file:",
+ defaultName + defaultExtension,
+ -1, -1,
+ "Download",
+ operation);
+ }
+
+ private GlobalDisplay globalDisplay_;
+ private FilesServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/BrowserType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/BrowserType.java
new file mode 100644
index 0000000..782713e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/BrowserType.java
@@ -0,0 +1,33 @@
+/*
+ * BrowserType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenFileInBrowserEvent;
+
+public class BrowserType extends FileType
+{
+ public BrowserType()
+ {
+ super("browser");
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ eventBus.fireEvent(new OpenFileInBrowserEvent(file));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/CodeBrowserType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/CodeBrowserType.java
new file mode 100644
index 0000000..fa717d8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/CodeBrowserType.java
@@ -0,0 +1,33 @@
+/*
+ * CodeBrowserType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class CodeBrowserType extends EditableFileType
+{
+ public CodeBrowserType()
+ {
+ super("r_code_browser", "R Code Browser",
+ FileIconResources.INSTANCE.iconRdoc());
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ assert false : "CodeBrowserType doesn't operate on filesystem files";
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/CppFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/CppFileType.java
new file mode 100644
index 0000000..1653c8c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/CppFileType.java
@@ -0,0 +1,77 @@
+/*
+ * CppFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import java.util.HashSet;
+
+import com.google.gwt.resources.client.ImageResource;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class CppFileType extends TextFileType
+{
+ CppFileType(String id, String ext, ImageResource icon, boolean isCpp)
+ {
+ super(id, "C/C++", EditorLanguage.LANG_CPP, ext, icon,
+ false, false, isCpp, false, false, false,
+ false, false, false, false, false, false);
+
+ isCpp_ = isCpp;
+ }
+
+ @Override
+ public boolean isCpp()
+ {
+ return isCpp_;
+ }
+
+ @Override
+ public boolean canSource()
+ {
+ return isCpp();
+ }
+
+ @Override
+ public boolean canSourceWithEcho()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean canSourceOnSave()
+ {
+ return canSource();
+ }
+
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ if (isCpp())
+ {
+ result.add(commands.commentUncomment());
+ result.add(commands.reflowComment());
+ result.add(commands.sourceActiveDocument());
+ result.add(commands.sourceActiveDocumentWithEcho());
+ result.add(commands.rcppHelp());
+ }
+ return result;
+ }
+
+ private final boolean isCpp_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/DataFrameType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/DataFrameType.java
new file mode 100644
index 0000000..792b503
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/DataFrameType.java
@@ -0,0 +1,33 @@
+/*
+ * DataFrameType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class DataFrameType extends EditableFileType
+{
+ public DataFrameType()
+ {
+ super("r_dataframe", "R Data Frame",
+ FileIconResources.INSTANCE.iconRdata());
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ assert false : "DataFrameType doesn't operate on filesystem files";
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/EditableFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/EditableFileType.java
new file mode 100644
index 0000000..acd8bd2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/EditableFileType.java
@@ -0,0 +1,40 @@
+/*
+ * EditableFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+
+public abstract class EditableFileType extends FileType
+{
+ public EditableFileType(String id, String label, ImageResource defaultIcon)
+ {
+ super(id);
+ label_ = label;
+ defaultIcon_ = defaultIcon;
+ }
+
+ public String getLabel()
+ {
+ return label_;
+ }
+
+ public ImageResource getDefaultIcon()
+ {
+ return defaultIcon_;
+ }
+
+ private final String label_;
+ private final ImageResource defaultIcon_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileIconResources.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileIconResources.java
new file mode 100644
index 0000000..8fcb976
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileIconResources.java
@@ -0,0 +1,54 @@
+/*
+ * FileIconResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface FileIconResources extends ClientBundle
+{
+ public static final FileIconResources INSTANCE =
+ GWT.create(FileIconResources.class);
+
+ ImageResource iconCsv();
+ ImageResource iconFolder();
+ ImageResource iconPublicFolder();
+ ImageResource iconUpFolder();
+ ImageResource iconPdf();
+ ImageResource iconPng();
+ ImageResource iconRdata();
+ ImageResource iconRproject();
+ ImageResource iconRdoc();
+ ImageResource iconRhistory();
+ ImageResource iconRprofile();
+ ImageResource iconTex();
+ ImageResource iconText();
+ ImageResource iconMarkdown();
+ ImageResource iconH();
+ ImageResource iconC();
+ ImageResource iconHpp();
+ ImageResource iconCpp();
+ ImageResource iconHTML();
+ ImageResource iconCss();
+ ImageResource iconJavascript();
+ ImageResource iconRsweave();
+ ImageResource iconRd();
+ ImageResource iconRhtml();
+ ImageResource iconRmarkdown();
+ ImageResource iconRpresentation();
+ ImageResource iconSourceViewer();
+ ImageResource iconProfiler();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileType.java
new file mode 100644
index 0000000..ff1e5f9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileType.java
@@ -0,0 +1,50 @@
+/*
+ * FileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+
+import java.util.ArrayList;
+
+public abstract class FileType
+{
+ static ArrayList<FileType> ALL_FILE_TYPES = new ArrayList<FileType>();
+
+ protected FileType(String id)
+ {
+ id_ = id;
+ ALL_FILE_TYPES.add(this);
+ }
+
+ public String getTypeId()
+ {
+ return id_;
+ }
+
+ public void openFile(FileSystemItem file,
+ FilePosition position,
+ NavigationMethod navMethod,
+ EventBus eventBus)
+ {
+ openFile(file, null, NavigationMethod.Default, eventBus);
+ }
+
+ protected abstract void openFile(FileSystemItem file, EventBus eventBus);
+
+ private final String id_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeCommands.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeCommands.java
new file mode 100644
index 0000000..a63b8ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeCommands.java
@@ -0,0 +1,116 @@
+/*
+ * FileTypeCommands.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.HTMLCapabilities;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.packages.events.InstalledPackagesChangedEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.InstalledPackagesChangedHandler;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class FileTypeCommands
+{
+ public static class CommandWithId
+ {
+ private CommandWithId(String commandId, AppCommand command)
+ {
+ this.commandId = commandId;
+ this.command = command;
+ }
+
+ public final String commandId;
+ public final AppCommand command;
+ }
+
+ @Inject
+ public FileTypeCommands(Session session,
+ EventBus eventBus,
+ final HTMLPreviewServerOperations server)
+ {
+ session_ = session;
+
+ eventBus.addHandler(InstalledPackagesChangedEvent.TYPE,
+ new InstalledPackagesChangedHandler() {
+ @Override
+ public void onInstalledPackagesChanged(InstalledPackagesChangedEvent e)
+ {
+ server.getHTMLCapabilities(
+ new ServerRequestCallback<HTMLCapabilities>() {
+
+ @Override
+ public void onResponseReceived(HTMLCapabilities caps)
+ {
+ setHTMLCapabilities(caps);
+ }
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+ });
+ }
+
+ public TextFileType[] statusBarFileTypes()
+ {
+ ArrayList<TextFileType> types = new ArrayList<TextFileType>();
+ types.add(FileTypeRegistry.R);
+ types.add(FileTypeRegistry.SWEAVE);
+ types.add(FileTypeRegistry.RMARKDOWN);
+ types.add(FileTypeRegistry.RHTML);
+ types.add(FileTypeRegistry.RPRESENTATION);
+ types.add(FileTypeRegistry.RD);
+ types.add(FileTypeRegistry.TEXT);
+ types.add(FileTypeRegistry.DCF);
+ types.add(FileTypeRegistry.TEX);
+ types.add(FileTypeRegistry.MARKDOWN);
+ types.add(FileTypeRegistry.HTML);
+ types.add(FileTypeRegistry.CSS);
+ types.add(FileTypeRegistry.JS);
+ types.add(FileTypeRegistry.CPP);
+
+ return (TextFileType[])types.toArray(new TextFileType[0]);
+ }
+
+ public HTMLCapabilities getHTMLCapabiliites()
+ {
+ if (htmlCapabilities_ == null)
+ setHTMLCapabilities(session_.getSessionInfo().getHTMLCapabilities());
+
+ return htmlCapabilities_;
+ }
+
+ public void setHTMLCapabilities(HTMLCapabilities caps)
+ {
+ htmlCapabilities_ = caps;
+ }
+
+ private final Session session_;
+
+ private HTMLCapabilities htmlCapabilities_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeRegistry.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeRegistry.java
new file mode 100644
index 0000000..0fec015
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/FileTypeRegistry.java
@@ -0,0 +1,468 @@
+/*
+ * FileTypeRegistry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+import java.util.HashMap;
+
+ at Singleton
+public class FileTypeRegistry
+{
+ private static final FileIconResources ICONS = FileIconResources.INSTANCE;
+
+ public static final TextFileType TEXT =
+ new TextFileType("text", "Text File", EditorLanguage.LANG_PLAIN, "",
+ ICONS.iconText(),
+ true,
+ false, false, false, false, false, false, false, false, false, true, false);
+
+ public static final TextFileType R =
+ new RFileType("r_source", "R Script", EditorLanguage.LANG_R, ".R",
+ ICONS.iconRdoc());
+
+ public static final TextFileType RD =
+ new TextFileType("r_doc", "Rd File", EditorLanguage.LANG_RDOC, ".Rd",
+ ICONS.iconRd(),
+ true, // word-wrap
+ true, // source on save aka preview on save
+ false, false, false,
+ true, // preview html
+ false, false, false, false,
+ true, // check spelling
+ false);
+
+ public static final TextFileType DCF =
+ new TextFileType("dcf", "DCF", EditorLanguage.LANG_DCF, ".dcf",
+ ICONS.iconText(), false, false, false, false, false,
+ false, false, false, false, false, false, false);
+
+ public static final TextFileType NAMESPACE =
+ new TextFileType("r_namespace", "NAMESPACE", EditorLanguage.LANG_R, "",
+ ICONS.iconText(), false, false, false, false, false,
+ false, false, false, false, false, false, false);
+
+ public static final TextFileType SWEAVE =
+ new SweaveFileType("sweave", "R Sweave",
+ EditorLanguage.LANG_SWEAVE, ".Rnw",ICONS.iconRsweave());
+
+ public static final TexFileType TEX =
+ new TexFileType("tex", "TeX", EditorLanguage.LANG_TEX, ".tex",
+ ICONS.iconTex());
+
+ public static final PlainTextFileType RHISTORY =
+ new PlainTextFileType("r_history", "R History", ".Rhistory",
+ ICONS.iconRhistory(),
+ true);
+
+ public static final RWebContentFileType RMARKDOWN =
+ new RWebContentFileType("r_markdown", "R Markdown", EditorLanguage.LANG_RMARKDOWN,
+ ".Rmd", ICONS.iconRmarkdown(), true);
+
+ public static final RWebContentFileType RPRESENTATION = new RPresentationFileType();
+
+ public static final WebContentFileType MARKDOWN =
+ new WebContentFileType("markdown", "Markdown", EditorLanguage.LANG_MARKDOWN,
+ ".md", ICONS.iconMarkdown(), true);
+
+
+ public static final RWebContentFileType RHTML =
+ new RWebContentFileType("r_html", "R HTML", EditorLanguage.LANG_RHTML,
+ ".Rhtml", ICONS.iconRhtml(), false);
+
+ public static final WebContentFileType HTML =
+ new WebContentFileType("html", "HTML", EditorLanguage.LANG_HTML,
+ ".html", ICONS.iconHTML(), false);
+
+ public static final TextFileType CSS =
+ new TextFileType("css", "CSS", EditorLanguage.LANG_CSS, ".css",
+ ICONS.iconCss(),
+ true,
+ false, false, false, false, false, false, false, false, false, false, false);
+
+ public static final TextFileType JS =
+ new TextFileType("js", "JavaScript", EditorLanguage.LANG_JAVASCRIPT, ".js",
+ ICONS.iconJavascript(),
+ true,
+ false, false, false, false, false, false, false, false, false, false, false);
+
+
+ public static final TextFileType H = new CppFileType("h", ".h", ICONS.iconH(), false);
+ public static final TextFileType C = new CppFileType("c", ".c", ICONS.iconC(), false);
+ public static final TextFileType HPP = new CppFileType("hpp", ".hpp", ICONS.iconHpp(), true);
+ public static final TextFileType CPP = new CppFileType("cpp", ".cpp", ICONS.iconCpp(), true);
+
+
+
+ public static final RDataType RDATA = new RDataType();
+ public static final RProjectType RPROJECT = new RProjectType();
+
+ public static final DataFrameType DATAFRAME = new DataFrameType();
+ public static final UrlContentType URLCONTENT = new UrlContentType();
+ public static final CodeBrowserType CODEBROWSER = new CodeBrowserType();
+ public static final ProfilerType PROFILER = new ProfilerType();
+
+ public static final BrowserType BROWSER = new BrowserType();
+
+ @Inject
+ public FileTypeRegistry(EventBus eventBus,
+ Satellite satellite,
+ Session session,
+ GlobalDisplay globalDisplay,
+ FilesServerOperations server)
+ {
+ eventBus_ = eventBus;
+ satellite_ = satellite;
+ server_ = server;
+ session_ = session;
+ globalDisplay_ = globalDisplay;
+
+ if (!satellite_.isCurrentWindowSatellite())
+ exportEditFileCallback();
+
+ FileIconResources icons = ICONS;
+
+ register("", TEXT, icons.iconText());
+ register("*.txt", TEXT, icons.iconText());
+ register("*.log", TEXT, icons.iconText());
+ register("README", TEXT, icons.iconText());
+ register(".gitignore", TEXT, icons.iconText());
+ register(".Rbuildignore", TEXT, icons.iconText());
+ register("*.r", R, icons.iconRdoc());
+ register("*.q", R, icons.iconRdoc());
+ register("*.s", R, icons.iconRdoc());
+ register(".rprofile", R, icons.iconRprofile());
+ register("Rprofile.site", R, icons.iconRprofile());
+ register("DESCRIPTION", DCF, icons.iconText());
+ register("TUTORIAL", DCF, icons.iconText());
+ register("NAMESPACE", NAMESPACE, icons.iconText());
+ register("*.rhistory", RHISTORY, icons.iconRhistory());
+ register("*.rproj", RPROJECT, icons.iconRproject());
+ register("*.rnw", SWEAVE, icons.iconRsweave());
+ register("*.snw", SWEAVE, icons.iconRsweave());
+ register("*.nw", SWEAVE, icons.iconRsweave());
+ register("*.tex", TEX, icons.iconTex());
+ register("*.latex", TEX, icons.iconTex());
+ register("*.sty", TEX, icons.iconTex());
+ register("*.cls", TEX, icons.iconTex());
+ register("*.bbl", TEX, icons.iconTex());
+ register("*.dtx", TEX, icons.iconTex());
+ register("*.ins", TEX, icons.iconTex());
+ register("*.rhtml", RHTML, icons.iconRhtml());
+ register("*.htm", HTML, icons.iconHTML());
+ register("*.html", HTML, icons.iconHTML());
+ register("*.css", CSS, icons.iconCss());
+ register("*.js", JS, icons.iconJavascript());
+ register("*.rmd", RMARKDOWN, icons.iconRmarkdown());
+ register("*.rmarkdown", RMARKDOWN, icons.iconRmarkdown());
+ register("*.rpres", RPRESENTATION, icons.iconRpresentation());
+ register("*.md", MARKDOWN, icons.iconMarkdown());
+ register("*.mdtxt", MARKDOWN, icons.iconMarkdown());
+ register("*.markdown", MARKDOWN, icons.iconMarkdown());
+ register("*.bib", TEXT, icons.iconText());
+ register("*.c", C, icons.iconC());
+ register("*.cpp", CPP, icons.iconCpp());
+ register("*.h", H, icons.iconH());
+ register("*.hpp", HPP, icons.iconHpp());
+ register("*.f", TEXT, icons.iconText());
+ register("*.Rout.save", TEXT, icons.iconText());
+ register("*.rd", RD, icons.iconRd());
+ register("*.rdata", RDATA, icons.iconRdata());
+ register("*.rda", RDATA, icons.iconRdata());
+ register("*.Rproj", RPROJECT, icons.iconRproject());
+ register("*.dcf", DCF, icons.iconText());
+
+ registerIcon(".jpg", icons.iconPng());
+ registerIcon(".jpeg", icons.iconPng());
+ registerIcon(".gif", icons.iconPng());
+ registerIcon(".bmp", icons.iconPng());
+ registerIcon(".tiff", icons.iconPng());
+ registerIcon(".tif", icons.iconPng());
+ registerIcon(".png", icons.iconPng());
+
+ registerIcon(".pdf", icons.iconPdf());
+ registerIcon(".csv", icons.iconCsv());
+
+ for (FileType fileType : FileType.ALL_FILE_TYPES)
+ {
+ assert !fileTypesByTypeName_.containsKey(fileType.getTypeId());
+ fileTypesByTypeName_.put(fileType.getTypeId(), fileType);
+ }
+ }
+
+ public void openFile(FileSystemItem file)
+ {
+ openFile(file, true);
+ }
+
+ public void openFile(final FileSystemItem file, final boolean canUseBrowser)
+ {
+ FileType fileType = getTypeForFile(file);
+ if (fileType != null)
+ {
+ fileType.openFile(file, eventBus_);
+ }
+ else
+ {
+ // build default command to use if we have an error or the
+ // file is not a text file
+ final Command defaultCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ if (canUseBrowser)
+ {
+ if (session_.getSessionInfo().getAllowFileDownloads())
+ {
+ BROWSER.openFile(file, eventBus_);
+ }
+ else
+ {
+ globalDisplay_.showErrorMessage(
+ "File Download Error",
+ "Unable to show file because file downloads are " +
+ "restricted on this server.\n");
+ }
+ }
+ }
+ };
+
+ // check with the server to see if this is likely to be a text file
+ server_.isTextFile(file.getPath(),
+ new ServerRequestCallback<Boolean>() {
+ @Override
+ public void onResponseReceived(Boolean isText)
+ {
+ if (isText)
+ TEXT.openFile(file, eventBus_);
+ else
+ defaultCommand.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ defaultCommand.execute();
+ }
+ });
+ }
+ }
+
+ public void editFile(FileSystemItem file)
+ {
+ editFile(file, null);
+ }
+
+ public void editFile(FileSystemItem file, FilePosition position)
+ {
+ editFile(file, position, false);
+ }
+
+ public void editFile(FileSystemItem file,
+ FilePosition position,
+ boolean highlightLine)
+ {
+ if (satellite_.isCurrentWindowSatellite())
+ {
+ satellite_.focusMainWindow();
+ callSatelliteEditFile(file.cast(), position.cast(), highlightLine);
+ }
+ else
+ {
+ FileType fileType = getTypeForFile(file);
+ if (fileType != null && !(fileType instanceof TextFileType))
+ fileType = TEXT;
+
+ if (fileType != null)
+ fileType.openFile(file,
+ position,
+ highlightLine ?
+ NavigationMethod.HighlightLine :
+ NavigationMethod.Default,
+ eventBus_);
+ }
+ }
+
+ private void satelliteEditFile(JavaScriptObject file,
+ JavaScriptObject position,
+ boolean highlightLine)
+ {
+ FileSystemItem fsi = file.cast();
+ FilePosition pos = position.cast();
+ editFile(fsi, pos);
+ }
+
+ private final native void exportEditFileCallback()/*-{
+ var registry = this;
+ $wnd.editFileFromRStudioSatellite = $entry(
+ function(file, position, highlightLine) {
+ registry. at org.rstudio.studio.client.common.filetypes.FileTypeRegistry::satelliteEditFile(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;Z)(file,position,highlightLine);
+ }
+ );
+ }-*/;
+
+ private final native void callSatelliteEditFile(
+ JavaScriptObject file,
+ JavaScriptObject position,
+ boolean highlightLine)/*-{
+ $wnd.opener.editFileFromRStudioSatellite(file, position, highlightLine);
+ }-*/;
+
+
+ public FileType getTypeByTypeName(String name)
+ {
+ return fileTypesByTypeName_.get(name);
+ }
+
+
+
+
+ public FileType getTypeForFile(FileSystemItem file)
+ {
+ // last ditch default type -- see if this either a known text file type
+ // or (for server mode) NOT a known binary type. the result of this is
+ // that unknown files types are treated as text and opened in the editor
+ // (we don't do this on desktop because in that case users have the
+ // recourse of using a local editor)
+ String defaultType = Desktop.isDesktop() ? "application/octet-stream" :
+ "text/plain";
+ return getTypeForFile(file, defaultType);
+ }
+
+ public FileType getTypeForFile(FileSystemItem file, String defaultType)
+ {
+ if (file != null)
+ {
+ String filename = file.getName().toLowerCase();
+ FileType result = fileTypesByFilename_.get(filename);
+ if (result != null)
+ return result;
+
+ String extension = FileSystemItem.getExtensionFromPath(filename);
+ result = fileTypesByFileExtension_.get(extension);
+ if (result != null)
+ return result;
+
+ if (defaultType != null)
+ {
+ String mimeType = file.mimeType(defaultType);
+ if (mimeType.startsWith("text/"))
+ return TEXT;
+ }
+ }
+
+ return null;
+ }
+
+ public TextFileType getTextTypeForFile(FileSystemItem file)
+ {
+ FileType type = getTypeForFile(file);
+ if (type != null && type instanceof TextFileType)
+ return (TextFileType) type;
+ else
+ return TEXT;
+ }
+
+ public ImageResource getIconForFile(FileSystemItem file)
+ {
+ if (file.isDirectory())
+ {
+ if (file.isPublicFolder())
+ return ICONS.iconPublicFolder();
+ else
+ return ICONS.iconFolder();
+ }
+
+ return getIconForFilename(file.getName());
+ }
+
+ public ImageResource getIconForFilename(String filename)
+ {
+ ImageResource icon = iconsByFilename_.get(filename.toLowerCase());
+ if (icon != null)
+ return icon;
+ String ext = FileSystemItem.getExtensionFromPath(filename);
+ icon = iconsByFileExtension_.get(ext.toLowerCase());
+ if (icon != null)
+ return icon;
+
+ return ICONS.iconText();
+ }
+
+ private void register(String filespec, FileType fileType, ImageResource icon)
+ {
+ if (filespec.startsWith("*."))
+ {
+ String ext = filespec.substring(1).toLowerCase();
+ if (ext.equals("."))
+ ext = "";
+ fileTypesByFileExtension_.put(ext,
+ fileType);
+ if (icon != null)
+ iconsByFileExtension_.put(ext, icon);
+ }
+ else if (filespec.length() == 0)
+ {
+ fileTypesByFileExtension_.put("", fileType);
+ if (icon != null)
+ iconsByFileExtension_.put("", icon);
+ }
+ else
+ {
+ assert filespec.indexOf("*") < 0 : "Unexpected filespec format";
+ fileTypesByFilename_.put(filespec.toLowerCase(), fileType);
+ if (icon != null)
+ iconsByFileExtension_.put(filespec.toLowerCase(), icon);
+ }
+ }
+
+ private void registerIcon(String extension, ImageResource icon)
+ {
+ iconsByFileExtension_.put(extension, icon);
+ }
+
+ private final HashMap<String, FileType> fileTypesByFileExtension_ =
+ new HashMap<String, FileType>();
+ private final HashMap<String, FileType> fileTypesByFilename_ =
+ new HashMap<String, FileType>();
+ private final HashMap<String, FileType> fileTypesByTypeName_ =
+ new HashMap<String, FileType>();
+ private final HashMap<String, ImageResource> iconsByFileExtension_ =
+ new HashMap<String, ImageResource>();
+ private final HashMap<String, ImageResource> iconsByFilename_ =
+ new HashMap<String, ImageResource>();
+ private final EventBus eventBus_;
+ private final Satellite satellite_;
+ private final Session session_;
+ private final GlobalDisplay globalDisplay_;
+ private final FilesServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/NewFileMenu.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/NewFileMenu.java
new file mode 100644
index 0000000..34eea1a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/NewFileMenu.java
@@ -0,0 +1,48 @@
+/*
+ * NewFileMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.MenuCallback;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import java.util.ArrayList;
+
+public abstract class NewFileMenu
+{
+ public NewFileMenu()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ public void execute(MenuCallback callback)
+ {
+ for (FileTypeCommands.CommandWithId cmd : fileTypeCommands_)
+ callback.addCommand(cmd.commandId, cmd.command);
+ }
+
+ @Inject
+ void initialize(FileTypeCommands fileTypeCommands)
+ {
+ fileTypeCommands_ = getFileTypeCommands(fileTypeCommands);
+ }
+
+ protected abstract ArrayList<FileTypeCommands.CommandWithId>
+ getFileTypeCommands(FileTypeCommands fileTypeCommands);
+
+ private ArrayList<FileTypeCommands.CommandWithId> fileTypeCommands_ =
+ new ArrayList<FileTypeCommands.CommandWithId>();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/PlainTextFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/PlainTextFileType.java
new file mode 100644
index 0000000..5714c9f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/PlainTextFileType.java
@@ -0,0 +1,47 @@
+/*
+ * PlainTextFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+
+public class PlainTextFileType extends TextFileType
+{
+ PlainTextFileType(String id,
+ String label,
+ String defaultExtension,
+ ImageResource defaultIcon,
+ boolean wordWrap)
+ {
+ super(id,
+ label,
+ EditorLanguage.LANG_PLAIN,
+ defaultExtension,
+ defaultIcon,
+ wordWrap,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false);
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/ProfilerType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/ProfilerType.java
new file mode 100644
index 0000000..4a2bcf5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/ProfilerType.java
@@ -0,0 +1,33 @@
+/*
+ * ProfilerType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class ProfilerType extends EditableFileType
+{
+ public ProfilerType()
+ {
+ super("r_profiler", "R Profiler",
+ FileIconResources.INSTANCE.iconRdoc());
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ assert false : "Profiler doesn't operate on filesystem files";
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/RDataType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RDataType.java
new file mode 100644
index 0000000..5d213cf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RDataType.java
@@ -0,0 +1,33 @@
+/*
+ * RDataType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenDataFileEvent;
+
+public class RDataType extends FileType
+{
+ RDataType()
+ {
+ super("r_data");
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ eventBus.fireEvent(new OpenDataFileEvent(file));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/RFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RFileType.java
new file mode 100644
index 0000000..dda61fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RFileType.java
@@ -0,0 +1,67 @@
+/*
+ * RFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import java.util.HashSet;
+
+public class RFileType extends TextFileType
+{
+ RFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource defaultIcon)
+ {
+ super(id,
+ label,
+ editorLanguage,
+ defaultExtension,
+ defaultIcon,
+ false, true, true, true, true, false,
+ false, false, false, true, false, true);
+ }
+
+ @Override
+ public boolean getWordWrap()
+ {
+ return RStudioGinjector.INSTANCE.getUIPrefs().softWrapRFiles().getValue();
+ }
+
+ @Override
+ public boolean canCompileNotebook()
+ {
+ return true;
+ }
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ result.add(commands.jumpTo());
+ result.add(commands.jumpToMatching());
+ result.add(commands.goToHelp());
+ result.add(commands.goToFunctionDefinition());
+ result.add(commands.insertSection());
+ result.add(commands.codeCompletion());
+ result.add(commands.debugBreakpoint());
+ return result;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/RPresentationFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RPresentationFileType.java
new file mode 100644
index 0000000..2acb96e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RPresentationFileType.java
@@ -0,0 +1,44 @@
+/*
+ * RPresentationFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.filetypes;
+
+import java.util.HashSet;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class RPresentationFileType extends RWebContentFileType
+{
+ public RPresentationFileType()
+ {
+ super("r_presentation",
+ "R Presentation",
+ EditorLanguage.LANG_RMARKDOWN,
+ ".Rpres",
+ FileIconResources.INSTANCE.iconRpresentation(),
+ true,
+ false);
+ }
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ result.add(commands.authoringRPresentationsHelp());
+ return result;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/RProjectType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RProjectType.java
new file mode 100644
index 0000000..94ba925
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RProjectType.java
@@ -0,0 +1,33 @@
+/*
+ * RProjectType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.projects.events.OpenProjectFileEvent;
+
+public class RProjectType extends FileType
+{
+ RProjectType()
+ {
+ super("r_proj");
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ eventBus.fireEvent(new OpenProjectFileEvent(file));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/RWebContentFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RWebContentFileType.java
new file mode 100644
index 0000000..8ff0455
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/RWebContentFileType.java
@@ -0,0 +1,101 @@
+/*
+ * RWebContentFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import java.util.HashSet;
+
+public class RWebContentFileType extends TextFileType
+{
+ RWebContentFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource icon,
+ boolean isMarkdown)
+ {
+ this(id, label, editorLanguage, defaultExtension, icon, isMarkdown, true);
+ }
+
+ RWebContentFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource icon,
+ boolean isMarkdown,
+ boolean previewIsKnit)
+ {
+ super(id,
+ label,
+ editorLanguage,
+ defaultExtension,
+ icon,
+ true, // word-wrap
+ false,
+ true,
+ true,
+ false,
+ !previewIsKnit, // preview-html
+ previewIsKnit, // knit-html
+ false,
+ true,
+ false,
+ true,
+ true);
+
+ isMarkdown_ = isMarkdown;
+ }
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ if (isMarkdown_)
+ result.add(commands.markdownHelp());
+ result.add(commands.jumpTo());
+ result.add(commands.jumpToMatching());
+ result.add(commands.goToHelp());
+ result.add(commands.goToFunctionDefinition());
+ return result;
+ }
+
+ @Override
+ public Pattern getRnwStartPatternBegin()
+ {
+ if (isMarkdown_)
+ return RNW_START_PATTERN_MD;
+ else
+ return RNW_START_PATTERN_HTML;
+ }
+
+ @Override
+ public Pattern getRnwStartPatternEnd()
+ {
+ return null;
+ }
+
+ private final boolean isMarkdown_;
+
+ private static final Pattern RNW_START_PATTERN_MD =
+ Pattern.create("^`{3,}\\s*\\{r");
+ private static final Pattern RNW_START_PATTERN_HTML =
+ Pattern.create("^\\<!--\\s*begin.rcode\\s*");
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/SweaveFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/SweaveFileType.java
new file mode 100644
index 0000000..c5423a8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/SweaveFileType.java
@@ -0,0 +1,82 @@
+/*
+ * SweaveFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import java.util.HashSet;
+
+public class SweaveFileType extends TextFileType
+{
+ public static final String TEX_LANG_MODE = "TeX";
+
+ SweaveFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource icon)
+ {
+ super(id,
+ label,
+ editorLanguage,
+ defaultExtension,
+ icon,
+ true,
+ false,
+ true,
+ true,
+ false,
+ false,
+ false,
+ true,
+ true,
+ false,
+ true,
+ true);
+ }
+
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ result.add(commands.jumpTo());
+ result.add(commands.jumpToMatching());
+ result.add(commands.goToHelp());
+ result.add(commands.goToFunctionDefinition());
+ return result;
+ }
+
+ @Override
+ public Pattern getRnwStartPatternBegin()
+ {
+ return RNW_START_PATTERN;
+ }
+
+ @Override
+ public Pattern getRnwStartPatternEnd()
+ {
+ return RNW_END_PATTERN;
+ }
+
+ private static final Pattern RNW_START_PATTERN =
+ Pattern.create("^\\s*\\<\\<");
+ private static final Pattern RNW_END_PATTERN =
+ Pattern.create("\\>\\>=");
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/TexFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/TexFileType.java
new file mode 100644
index 0000000..b616ace
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/TexFileType.java
@@ -0,0 +1,59 @@
+/*
+ * TexFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import java.util.HashSet;
+
+public class TexFileType extends TextFileType
+{
+ TexFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource icon)
+ {
+ super(id,
+ label,
+ editorLanguage,
+ defaultExtension,
+ icon,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ true,
+ false);
+ }
+
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ result.add(commands.commentUncomment());
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/TextFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/TextFileType.java
new file mode 100644
index 0000000..eef2719
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/TextFileType.java
@@ -0,0 +1,373 @@
+/*
+ * TextFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import com.google.gwt.resources.client.ImageResource;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.UnicodeLetters;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Token;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
+
+import java.util.HashSet;
+
+public class TextFileType extends EditableFileType
+{
+ public static final String R_LANG_MODE = "R";
+
+ TextFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource defaultIcon,
+ boolean wordWrap,
+ boolean canSourceOnSave,
+ boolean canExecuteCode,
+ boolean canExecuteAllCode,
+ boolean canExecuteToCurrentLine,
+ boolean canPreviewHTML,
+ boolean canKnitToHTML,
+ boolean canCompilePDF,
+ boolean canExecuteChunks,
+ boolean canAutoIndent,
+ boolean canCheckSpelling,
+ boolean canShowScopeTree)
+ {
+ super(id, label, defaultIcon);
+ editorLanguage_ = editorLanguage;
+ defaultExtension_ = defaultExtension;
+ wordWrap_ = wordWrap;
+ canSourceOnSave_ = canSourceOnSave;
+ canExecuteCode_ = canExecuteCode;
+ canExecuteAllCode_ = canExecuteAllCode;
+ canExecuteToCurrentLine_ = canExecuteToCurrentLine;
+ canKnitToHTML_ = canKnitToHTML;
+ canPreviewHTML_ = canPreviewHTML;
+ canCompilePDF_ = canCompilePDF;
+ canExecuteChunks_ = canExecuteChunks;
+ canAutoIndent_ = canAutoIndent;
+ canCheckSpelling_ = canCheckSpelling;
+ canShowScopeTree_ = canShowScopeTree;
+ }
+
+ @Override
+ public void openFile(FileSystemItem file,
+ FilePosition position,
+ NavigationMethod navMethod,
+ EventBus eventBus)
+ {
+ eventBus.fireEvent(new OpenSourceFileEvent(file,
+ position,
+ this,
+ navMethod));
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ openFile(file, null, NavigationMethod.Default, eventBus);
+ }
+
+ public EditorLanguage getEditorLanguage()
+ {
+ return editorLanguage_;
+ }
+
+ public boolean getWordWrap()
+ {
+ return wordWrap_;
+ }
+
+ public boolean canSource()
+ {
+ return canExecuteCode_ && !canExecuteChunks_;
+ }
+
+ public boolean canSourceWithEcho()
+ {
+ return canSource();
+ }
+
+ public boolean canSourceOnSave()
+ {
+ return canSourceOnSave_;
+ }
+
+ public boolean canExecuteCode()
+ {
+ return canExecuteCode_;
+ }
+
+ public boolean canExecuteAllCode()
+ {
+ return canExecuteAllCode_;
+ }
+
+ public boolean canExecuteToCurrentLine()
+ {
+ return canExecuteToCurrentLine_;
+ }
+
+ public boolean canKnitToHTML()
+ {
+ return canKnitToHTML_;
+ }
+
+ public boolean canPreviewHTML()
+ {
+ return canPreviewHTML_;
+ }
+
+ public boolean canCompilePDF()
+ {
+ return canCompilePDF_;
+ }
+
+ public boolean canCompileNotebook()
+ {
+ return false;
+ }
+
+ public boolean canAuthorContent()
+ {
+ return canKnitToHTML() || canPreviewHTML() || canCompilePDF();
+ }
+
+ public boolean canExecuteChunks()
+ {
+ return canExecuteChunks_;
+ }
+
+ public boolean canAutoIndent()
+ {
+ return canAutoIndent_;
+ }
+
+ public boolean canCheckSpelling()
+ {
+ return canCheckSpelling_;
+ }
+
+ public boolean canShowScopeTree()
+ {
+ return canShowScopeTree_;
+ }
+
+ public boolean isR()
+ {
+ return FileTypeRegistry.R.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isRnw()
+ {
+ return FileTypeRegistry.SWEAVE.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isRd()
+ {
+ return FileTypeRegistry.RD.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isRmd()
+ {
+ return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isRpres()
+ {
+ return FileTypeRegistry.RPRESENTATION.getTypeId().equals(getTypeId());
+ }
+
+ public boolean requiresKnit()
+ {
+ return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId()) ||
+ FileTypeRegistry.RHTML.getTypeId().equals(getTypeId()) ||
+ FileTypeRegistry.RPRESENTATION.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isMarkdown()
+ {
+ return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId()) ||
+ FileTypeRegistry.MARKDOWN.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isPlainMarkdown()
+ {
+ return FileTypeRegistry.MARKDOWN.getTypeId().equals(getTypeId());
+ }
+
+ public boolean isC()
+ {
+ return EditorLanguage.LANG_CPP.equals(getEditorLanguage());
+ }
+
+ public boolean isCpp()
+ {
+ return false;
+ }
+
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> results = new HashSet<AppCommand>();
+ results.add(commands.saveSourceDoc());
+ results.add(commands.reopenSourceDocWithEncoding());
+ results.add(commands.saveSourceDocAs());
+ results.add(commands.saveSourceDocWithEncoding());
+ results.add(commands.printSourceDoc());
+ results.add(commands.vcsFileLog());
+ results.add(commands.vcsFileDiff());
+ results.add(commands.vcsFileRevert());
+ results.add(commands.vcsViewOnGitHub());
+ results.add(commands.vcsBlameOnGitHub());
+ results.add(commands.goToLine());
+ if (canExecuteCode())
+ {
+ results.add(commands.executeCode());
+ results.add(commands.executeCodeWithoutFocus());
+ results.add(commands.executeLastCode());
+ results.add(commands.extractFunction());
+ results.add(commands.extractLocalVariable());
+ results.add(commands.commentUncomment());
+ results.add(commands.reindent());
+ results.add(commands.reflowComment());
+ }
+ if (canExecuteAllCode())
+ {
+ results.add(commands.executeAllCode());
+ results.add(commands.sourceActiveDocument());
+ results.add(commands.sourceActiveDocumentWithEcho());
+ }
+ if (canExecuteToCurrentLine())
+ {
+ results.add(commands.executeToCurrentLine());
+ results.add(commands.executeFromCurrentLine());
+ results.add(commands.executeCurrentFunction());
+ results.add(commands.executeCurrentSection());
+ }
+ if (canKnitToHTML())
+ {
+ results.add(commands.knitDocument());
+ results.add(commands.usingRMarkdownHelp());
+ }
+ if (canPreviewHTML())
+ {
+ results.add(commands.previewHTML());
+ }
+ if (canCompilePDF())
+ {
+ results.add(commands.compilePDF());
+ results.add(commands.synctexSearch());
+ }
+ if (canCompileNotebook())
+ {
+ results.add(commands.compileNotebook());
+ }
+ if (canExecuteChunks())
+ {
+ results.add(commands.insertChunk());
+ results.add(commands.executeCurrentChunk());
+ results.add(commands.executeNextChunk());
+ }
+ if (canCheckSpelling())
+ {
+ results.add(commands.checkSpelling());
+ }
+ results.add(commands.findReplace());
+ results.add(commands.findNext());
+ results.add(commands.findPrevious());
+ results.add(commands.findFromSelection());
+ results.add(commands.replaceAndFind());
+ results.add(commands.setWorkingDirToActiveDoc());
+ results.add(commands.debugDumpContents());
+ results.add(commands.debugImportDump());
+ return results;
+ }
+
+ public String getDefaultExtension()
+ {
+ return defaultExtension_;
+ }
+
+ public TokenPredicate getTokenPredicate()
+ {
+ return new TokenPredicate()
+ {
+ @Override
+ public boolean test(Token token, int row, int column)
+ {
+ return reTextType_.match(token.getType(), 0) != null
+ && reNospellType_.match(token.getType(), 0) == null;
+ }
+ };
+ }
+
+ public CharClassifier getCharPredicate()
+ {
+ return new CharClassifier()
+ {
+ @Override
+ public CharClass classify(char c)
+ {
+ if (UnicodeLetters.isLetter(c))
+ return CharClass.Word;
+ else if (c == '\'')
+ return CharClass.Boundary;
+ else
+ return CharClass.NonWord;
+ }
+ };
+ }
+
+ /**
+ * Returns a regex pattern that will match against the beginning of
+ * Rnw lines, right up to the index where options begin.
+ */
+ public Pattern getRnwStartPatternBegin()
+ {
+ return null;
+ }
+
+ public Pattern getRnwStartPatternEnd()
+ {
+ return null;
+ }
+
+ private final EditorLanguage editorLanguage_;
+ private final boolean wordWrap_;
+ private final boolean canSourceOnSave_;
+ private final boolean canExecuteCode_;
+ private final boolean canExecuteAllCode_;
+ private final boolean canExecuteToCurrentLine_;
+ private final boolean canPreviewHTML_;
+ private final boolean canKnitToHTML_;
+ private final boolean canCompilePDF_;
+ private final boolean canExecuteChunks_;
+ private final boolean canAutoIndent_;
+ private final boolean canCheckSpelling_;
+ private boolean canShowScopeTree_;
+ private final String defaultExtension_;
+
+ private static Pattern reTextType_ = Pattern.create("\\btext\\b");
+ private static Pattern reNospellType_ = Pattern.create("\\bnospell\\b");
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/UrlContentType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/UrlContentType.java
new file mode 100644
index 0000000..c235066
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/UrlContentType.java
@@ -0,0 +1,33 @@
+/*
+ * UrlContentType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+
+public class UrlContentType extends EditableFileType
+{
+ public UrlContentType()
+ {
+ super("urlcontent", "Generic Content",
+ FileIconResources.INSTANCE.iconText());
+ }
+
+ @Override
+ public void openFile(FileSystemItem file, EventBus eventBus)
+ {
+ assert false : "urlcontent doesn't apply to filesystem files";
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/WebContentFileType.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/WebContentFileType.java
new file mode 100644
index 0000000..3df18b3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/WebContentFileType.java
@@ -0,0 +1,65 @@
+/*
+ * WebContentFileType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes;
+
+import java.util.HashSet;
+
+import com.google.gwt.resources.client.ImageResource;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.studio.client.common.reditor.EditorLanguage;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class WebContentFileType extends TextFileType
+{
+ WebContentFileType(String id,
+ String label,
+ EditorLanguage editorLanguage,
+ String defaultExtension,
+ ImageResource icon,
+ boolean isMarkdown)
+ {
+ super(id,
+ label,
+ editorLanguage,
+ defaultExtension,
+ icon,
+ true, // word-wrap
+ false,
+ isMarkdown, // allow code execution in markdown
+ false,
+ false,
+ true, // preview-html
+ false,
+ false,
+ false,
+ false,
+ true,
+ false);
+
+ isMarkdown_ = isMarkdown;
+ }
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands(Commands commands)
+ {
+ HashSet<AppCommand> result = super.getSupportedCommands(commands);
+ if (isMarkdown_)
+ result.add(commands.markdownHelp());
+ return result;
+ }
+
+ private final boolean isMarkdown_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileEvent.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileEvent.java
new file mode 100644
index 0000000..b53801f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileEvent.java
@@ -0,0 +1,48 @@
+/*
+ * OpenDataFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class OpenDataFileEvent extends GwtEvent<OpenDataFileHandler>
+{
+ public static final GwtEvent.Type<OpenDataFileHandler> TYPE =
+ new GwtEvent.Type<OpenDataFileHandler>();
+
+ public OpenDataFileEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ protected void dispatch(OpenDataFileHandler handler)
+ {
+ handler.onOpenDataFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<OpenDataFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileSystemItem file_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileHandler.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileHandler.java
new file mode 100644
index 0000000..659dd41
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenDataFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenDataFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenDataFileHandler extends EventHandler
+{
+ void onOpenDataFile(OpenDataFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserEvent.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserEvent.java
new file mode 100644
index 0000000..fe8288a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserEvent.java
@@ -0,0 +1,47 @@
+/*
+ * OpenFileInBrowserEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class OpenFileInBrowserEvent extends GwtEvent<OpenFileInBrowserHandler>
+{
+ public static Type<OpenFileInBrowserHandler> TYPE = new Type<OpenFileInBrowserHandler>();
+
+ public OpenFileInBrowserEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ public Type<OpenFileInBrowserHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(OpenFileInBrowserHandler handler)
+ {
+ handler.onOpenFileInBrowser(this);
+ }
+
+ private final FileSystemItem file_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserHandler.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserHandler.java
new file mode 100644
index 0000000..3683ecc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenFileInBrowserHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenFileInBrowserHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenFileInBrowserHandler extends EventHandler
+{
+ void onOpenFileInBrowser(OpenFileInBrowserEvent file);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileEvent.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileEvent.java
new file mode 100644
index 0000000..0a074e7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileEvent.java
@@ -0,0 +1,91 @@
+/*
+ * OpenPresentationSourceFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+
+public class OpenPresentationSourceFileEvent extends GwtEvent<OpenPresentationSourceFileHandler>
+{
+ public static final GwtEvent.Type<OpenPresentationSourceFileHandler> TYPE =
+ new GwtEvent.Type<OpenPresentationSourceFileHandler>();
+
+ public OpenPresentationSourceFileEvent(FileSystemItem file,
+ TextFileType fileType,
+ FilePosition position)
+ {
+ this(file, fileType, position, null);
+ }
+
+
+ public OpenPresentationSourceFileEvent(FileSystemItem file,
+ TextFileType fileType,
+ String pattern)
+ {
+ this(file, fileType, null, pattern);
+ }
+
+
+ public OpenPresentationSourceFileEvent(FileSystemItem file,
+ TextFileType fileType,
+ FilePosition position,
+ String pattern)
+ {
+ file_ = file;
+ position_ = position;
+ fileType_ = fileType;
+ pattern_ = pattern;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ public TextFileType getFileType()
+ {
+ return fileType_;
+ }
+
+ public FilePosition getPosition()
+ {
+ return position_;
+ }
+
+ public String getPattern()
+ {
+ return pattern_;
+ }
+
+ @Override
+ protected void dispatch(OpenPresentationSourceFileHandler handler)
+ {
+ handler.onOpenPresentationSourceFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<OpenPresentationSourceFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileSystemItem file_;
+ private final FilePosition position_;
+ private final TextFileType fileType_;
+ private final String pattern_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileHandler.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileHandler.java
new file mode 100644
index 0000000..7ef4426
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenPresentationSourceFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenPresentationSourceFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenPresentationSourceFileHandler extends EventHandler
+{
+ void onOpenPresentationSourceFile(OpenPresentationSourceFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileEvent.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileEvent.java
new file mode 100644
index 0000000..e89ec94
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileEvent.java
@@ -0,0 +1,96 @@
+/*
+ * OpenSourceFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+
+public class OpenSourceFileEvent extends GwtEvent<OpenSourceFileHandler>
+{
+ public static final GwtEvent.Type<OpenSourceFileHandler> TYPE =
+ new GwtEvent.Type<OpenSourceFileHandler>();
+
+ public enum NavigationMethod
+ {
+ Default,
+ HighlightLine,
+ DebugStep,
+ DebugFrame,
+ DebugEnd
+ }
+
+ public OpenSourceFileEvent(FileSystemItem file, TextFileType fileType)
+ {
+ this(file, null, fileType);
+ }
+
+ public OpenSourceFileEvent(FileSystemItem file,
+ FilePosition position,
+ TextFileType fileType)
+ {
+ this(file, position, fileType, NavigationMethod.Default);
+ }
+
+ public OpenSourceFileEvent(FileSystemItem file,
+ FilePosition position,
+ TextFileType fileType,
+ NavigationMethod navMethod)
+ {
+ file_ = file;
+ position_ = position;
+ fileType_ = fileType;
+ navigationMethod_ = navMethod;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ public TextFileType getFileType()
+ {
+ return fileType_;
+ }
+
+ public FilePosition getPosition()
+ {
+ return position_;
+ }
+
+ public NavigationMethod getNavigationMethod()
+ {
+ return navigationMethod_;
+ }
+
+ @Override
+ protected void dispatch(OpenSourceFileHandler handler)
+ {
+ handler.onOpenSourceFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<OpenSourceFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileSystemItem file_;
+ private final FilePosition position_;
+ private final TextFileType fileType_;
+ private final NavigationMethod navigationMethod_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileHandler.java b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileHandler.java
new file mode 100644
index 0000000..f9efa7a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/filetypes/events/OpenSourceFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenSourceFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.filetypes.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenSourceFileHandler extends EventHandler
+{
+ void onOpenSourceFile(OpenSourceFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconC.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconC.png
new file mode 100644
index 0000000..ec199d1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconC.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCpp.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCpp.png
new file mode 100644
index 0000000..75a5a15
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCpp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCss.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCss.png
new file mode 100644
index 0000000..ad013ac
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCss.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCsv.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCsv.png
new file mode 100644
index 0000000..31b2b3c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconCsv.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconFolder.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconFolder.png
new file mode 100644
index 0000000..a53b171
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconFolder.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconH.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconH.png
new file mode 100644
index 0000000..aa4afe6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconH.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHTML.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHTML.png
new file mode 100644
index 0000000..6d4cfc9
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHTML.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHpp.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHpp.png
new file mode 100644
index 0000000..441c196
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconHpp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconJavascript.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconJavascript.png
new file mode 100644
index 0000000..f496444
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconJavascript.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconMarkdown.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconMarkdown.png
new file mode 100644
index 0000000..85b17c4
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconMarkdown.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPdf.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPdf.png
new file mode 100644
index 0000000..3350521
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPdf.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPng.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPng.png
new file mode 100644
index 0000000..8bcf071
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPng.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconProfiler.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconProfiler.png
new file mode 100644
index 0000000..93e2c8c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconProfiler.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPublicFolder.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPublicFolder.png
new file mode 100644
index 0000000..f8a06b6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconPublicFolder.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRd.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRd.png
new file mode 100644
index 0000000..957d47e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRd.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdata.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdata.png
new file mode 100644
index 0000000..4f042ef
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdata.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdoc.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdoc.png
new file mode 100644
index 0000000..84701d7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRdoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhistory.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhistory.png
new file mode 100644
index 0000000..3083075
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhtml.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhtml.png
new file mode 100644
index 0000000..1fabb60
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRhtml.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRmarkdown.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRmarkdown.png
new file mode 100644
index 0000000..2ba2caf
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRmarkdown.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRpresentation.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRpresentation.png
new file mode 100644
index 0000000..70b79c8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRpresentation.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRprofile.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRprofile.png
new file mode 100644
index 0000000..ada8636
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRprofile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRproject.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRproject.png
new file mode 100644
index 0000000..b939f2a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRproject.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRsweave.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRsweave.png
new file mode 100755
index 0000000..c07c2be
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconRsweave.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconSourceViewer.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconSourceViewer.png
new file mode 100644
index 0000000..2344aa3
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconSourceViewer.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconTex.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconTex.png
new file mode 100644
index 0000000..ced8750
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconTex.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconText.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconText.png
new file mode 100644
index 0000000..1361a4e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconText.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconUpFolder.png b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconUpFolder.png
new file mode 100644
index 0000000..d51b544
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/filetypes/iconUpFolder.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/StandardIcons.java b/src/gwt/src/org/rstudio/studio/client/common/icons/StandardIcons.java
new file mode 100644
index 0000000..4f6329f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/icons/StandardIcons.java
@@ -0,0 +1,38 @@
+/*
+ * StandardIcons.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.icons;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface StandardIcons extends ClientBundle
+{
+ public static final StandardIcons INSTANCE = GWT.create(StandardIcons.class);
+ ImageResource stock_new();
+ ImageResource chunk_menu();
+ ImageResource go_up();
+ ImageResource right_arrow();
+ ImageResource click_feedback();
+ ImageResource more_actions();
+ ImageResource import_dataset();
+ ImageResource empty_command();
+ ImageResource show_log();
+ ImageResource help();
+ ImageResource function();
+ ImageResource git();
+ ImageResource svn();
+ ImageResource viewer_window();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/chunk_menu.png b/src/gwt/src/org/rstudio/studio/client/common/icons/chunk_menu.png
new file mode 100644
index 0000000..7b0e2bb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/chunk_menu.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/click_feedback.png b/src/gwt/src/org/rstudio/studio/client/common/icons/click_feedback.png
new file mode 100644
index 0000000..f2059ca
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/click_feedback.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/empty_command.png b/src/gwt/src/org/rstudio/studio/client/common/icons/empty_command.png
new file mode 100644
index 0000000..9871035
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/empty_command.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/function.png b/src/gwt/src/org/rstudio/studio/client/common/icons/function.png
new file mode 100644
index 0000000..637c497
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/function.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/git.png b/src/gwt/src/org/rstudio/studio/client/common/icons/git.png
new file mode 100644
index 0000000..e088814
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/git.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/go_up.png b/src/gwt/src/org/rstudio/studio/client/common/icons/go_up.png
new file mode 100644
index 0000000..69dce58
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/go_up.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/help.png b/src/gwt/src/org/rstudio/studio/client/common/icons/help.png
new file mode 100644
index 0000000..844d378
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/help.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/import_dataset.png b/src/gwt/src/org/rstudio/studio/client/common/icons/import_dataset.png
new file mode 100644
index 0000000..fe83c3e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/import_dataset.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/more_actions.png b/src/gwt/src/org/rstudio/studio/client/common/icons/more_actions.png
new file mode 100644
index 0000000..726ef4f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/more_actions.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/right_arrow.png b/src/gwt/src/org/rstudio/studio/client/common/icons/right_arrow.png
new file mode 100644
index 0000000..f7c657f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/right_arrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/show_log.png b/src/gwt/src/org/rstudio/studio/client/common/icons/show_log.png
new file mode 100644
index 0000000..5f6afa1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/show_log.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/stock_new.png b/src/gwt/src/org/rstudio/studio/client/common/icons/stock_new.png
new file mode 100644
index 0000000..1398478
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/stock_new.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/svn.png b/src/gwt/src/org/rstudio/studio/client/common/icons/svn.png
new file mode 100644
index 0000000..01ed6dc
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/svn.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/icons/viewer_window.png b/src/gwt/src/org/rstudio/studio/client/common/icons/viewer_window.png
new file mode 100644
index 0000000..bd49bfe
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/icons/viewer_window.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopFileDialogs.java b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopFileDialogs.java
new file mode 100644
index 0000000..b8574ee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopFileDialogs.java
@@ -0,0 +1,201 @@
+/*
+ * DesktopFileDialogs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.inject.Inject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.NullProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+public class DesktopFileDialogs implements FileDialogs
+{
+ private abstract class FileDialogOperation
+ {
+ abstract String operation(String caption, String dir);
+
+ protected boolean shouldUpdateDetails()
+ {
+ return false;
+ }
+
+ public void execute(
+ final String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ final ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ final String dir = initialFilePath == null
+ ? fsContext.pwd()
+ : initialFilePath.getPath();
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ String file = operation(caption, dir);
+
+ FileSystemItem item =
+ StringUtil.isNullOrEmpty(file)
+ ? null
+ : FileSystemItem.createFile(file);
+
+ if (item != null && shouldUpdateDetails())
+ {
+ server_.stat(item.getPath(), new ServerRequestCallback<FileSystemItem>()
+ {
+ @Override
+ public void onResponseReceived(FileSystemItem response)
+ {
+ operation.execute(response, new NullProgressIndicator());
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error",
+ error.getUserMessage());
+ operation.execute(null, new NullProgressIndicator());
+ }
+ });
+ }
+ else
+ {
+ operation.execute(item, new NullProgressIndicator());
+ }
+ }
+ });
+ }
+ }
+
+ public DesktopFileDialogs()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(FilesServerOperations server,
+ GlobalDisplay globalDisplay)
+ {
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ }
+
+ public void openFile(final String caption,
+ final FileSystemContext fsContext,
+ final FileSystemItem initialFilePath,
+ final ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ openFile(caption, fsContext, initialFilePath, "", operation);
+ }
+
+ public void openFile(final String caption,
+ final FileSystemContext fsContext,
+ final FileSystemItem initialFilePath,
+ final String filter,
+ final ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ new FileDialogOperation()
+ {
+ @Override
+ protected boolean shouldUpdateDetails()
+ {
+ return true;
+ }
+
+ @Override
+ String operation(String caption, String dir)
+ {
+ String fileName = Desktop.getFrame().getOpenFileName(caption,
+ dir,
+ filter);
+ if (fileName != null)
+ {
+ updateWorkingDirectory(fileName, fsContext);
+ }
+ return fileName;
+ }
+ }.execute(caption, fsContext, initialFilePath, operation);
+ }
+
+ public void saveFile(final String caption,
+ final FileSystemContext fsContext,
+ final FileSystemItem initialFilePath,
+ final String defaultExtension,
+ final boolean forceDefaultExtension,
+ final ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ new FileDialogOperation()
+ {
+ @Override
+ String operation(String caption, String dir)
+ {
+ String fileName = Desktop.getFrame().getSaveFileName(
+ caption, dir, defaultExtension, forceDefaultExtension);
+
+ if (fileName != null)
+ {
+ updateWorkingDirectory(fileName, fsContext);
+ }
+ return fileName;
+ }
+ }.execute(caption,
+ fsContext,
+ initialFilePath,
+ operation);
+ }
+
+ public void chooseFolder(String caption,
+ FileSystemContext fsContext,
+ final FileSystemItem initialDir,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ new FileDialogOperation()
+ {
+ @Override
+ String operation(String caption, String dir)
+ {
+ return Desktop.getFrame().getExistingDirectory(
+ caption,
+ initialDir != null ? initialDir.getPath() : null);
+ }
+ }.execute(caption, fsContext, null, operation);
+ }
+
+ private void updateWorkingDirectory(String fileName,
+ FileSystemContext fsContext)
+ {
+ if (fileName != null)
+ {
+ String parentPath =
+ FileSystemItem.createFile(fileName).getParentPathString();
+ if (!StringUtil.isNullOrEmpty(parentPath))
+ fsContext.cd(parentPath);
+ }
+ }
+
+ private FilesServerOperations server_;
+ private GlobalDisplay globalDisplay_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopTextInput.java b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopTextInput.java
new file mode 100644
index 0000000..5847247
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopTextInput.java
@@ -0,0 +1,103 @@
+/*
+ * DesktopTextInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import org.rstudio.core.client.MessageDisplay.PromptWithOptionResult;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.TextInput;
+
+public class DesktopTextInput implements TextInput
+{
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ boolean numbersOnly,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<String> okOperation,
+ Operation cancelOperation)
+ {
+ String result = Desktop.getFrame().promptForText(title,
+ label,
+ initialValue,
+ usePasswordMask,
+ "",
+ false,
+ numbersOnly,
+ selectionStart,
+ selectionLength,
+ okButtonCaption);
+ if (StringUtil.isNullOrEmpty(result))
+ {
+ if (cancelOperation != null)
+ cancelOperation.execute();
+ }
+ else
+ {
+ String[] lines = result.split("\\n");
+ okOperation.execute(lines[0],
+ RStudioGinjector.INSTANCE
+ .getGlobalDisplay()
+ .getProgressIndicator("Error"));
+ }
+ }
+
+ @Override
+ public void promptForTextWithOption(String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ String extraOptionPrompt,
+ boolean extraOptionDefault,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation)
+ {
+ String result = Desktop.getFrame().promptForText(title,
+ label,
+ initialValue,
+ usePasswordMask,
+ extraOptionPrompt,
+ extraOptionDefault,
+ false,
+ selectionStart,
+ selectionLength,
+ okButtonCaption);
+ if (StringUtil.isNullOrEmpty(result))
+ {
+ if (cancelOperation != null)
+ cancelOperation.execute();
+ }
+ else
+ {
+ PromptWithOptionResult presult = new PromptWithOptionResult();
+ String[] lines = result.split("\\n");
+ presult.input = lines[0];
+ presult.extraOption = "1".equals(lines[1]);
+ okOperation.execute(presult,
+ RStudioGinjector.INSTANCE
+ .getGlobalDisplay()
+ .getProgressIndicator("Error"));
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopWindowOpener.java b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopWindowOpener.java
new file mode 100644
index 0000000..6727c01
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/DesktopWindowOpener.java
@@ -0,0 +1,86 @@
+/*
+ * DesktopWindowOpener.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+import org.rstudio.studio.client.common.satellite.SatelliteUtils;
+
+import com.google.gwt.core.client.GWT;
+
+public class DesktopWindowOpener extends WebWindowOpener
+{
+ @Override
+ public void openWindow(GlobalDisplay globalDisplay,
+ String url,
+ NewWindowOptions options)
+ {
+ // open externally if we have a protocol and aren't an app url
+ if (hasProtocol(url) && !isAppUrl(url))
+ {
+ Desktop.getFrame().browseUrl(url);
+
+ assert options.getCallback() == null;
+ }
+ else
+ {
+ // if this is a relative url then prepend the host page base
+ // url (so Qt correctly navigates)
+ if (!hasProtocol(url))
+ {
+ if (url.startsWith("/"))
+ url = url.substring(1);
+ url = GWT.getHostPageBaseURL() + url;
+ }
+
+ super.openWindow(globalDisplay,
+ url,
+ options);
+ }
+ }
+
+ @Override
+ public void openMinimalWindow(GlobalDisplay globalDisplay,
+ String url,
+ NewWindowOptions options,
+ int width,
+ int height,
+ boolean showLocation)
+ {
+ Desktop.getFrame().openMinimalWindow(options.getName(),
+ url,
+ width,
+ height);
+ }
+
+ @Override
+ public void openSatelliteWindow(GlobalDisplay globalDisplay,
+ String mode,
+ int width,
+ int height)
+ {
+ String windowName = SatelliteUtils.getSatelliteWindowName(mode);
+ Desktop.getFrame().prepareForSatelliteWindow(windowName, width, height);
+ super.openSatelliteWindow(globalDisplay, mode, width, height);
+ }
+
+ @Override
+ protected boolean showPopupBlockedMessage()
+ {
+ return false;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/WebFileDialogs.java b/src/gwt/src/org/rstudio/studio/client/common/impl/WebFileDialogs.java
new file mode 100644
index 0000000..f1f10f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/WebFileDialogs.java
@@ -0,0 +1,108 @@
+/*
+ * WebFileDialogs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.files.filedialog.ChooseFolderDialog2;
+import org.rstudio.core.client.files.filedialog.FileDialog;
+import org.rstudio.core.client.files.filedialog.OpenFileDialog;
+import org.rstudio.core.client.files.filedialog.SaveFileDialog;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.common.FileDialogs;
+
+public class WebFileDialogs implements FileDialogs
+{
+ public void openFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ openFile(caption, fsContext, initialFilePath, "", operation);
+ }
+
+ public void openFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ String filter,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ OpenFileDialog dialog = new OpenFileDialog(caption,
+ fsContext,
+ filter,
+ operation);
+
+ dialog.setInvokeOperationEvenOnCancel(true);
+
+ finishInit(fsContext, initialFilePath, dialog);
+ }
+
+ public void saveFile(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ String defaultExtension,
+ boolean forceDefaultExtension,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ SaveFileDialog dialog = new SaveFileDialog(caption,
+ fsContext,
+ defaultExtension,
+ forceDefaultExtension,
+ operation);
+
+ dialog.setInvokeOperationEvenOnCancel(true);
+
+ finishInit(fsContext, initialFilePath, dialog);
+ }
+
+ public void chooseFolder(String caption,
+ FileSystemContext fsContext,
+ FileSystemItem initialDir,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ ChooseFolderDialog2 dialog = new ChooseFolderDialog2(caption,
+ fsContext,
+ operation);
+ dialog.setInvokeOperationEvenOnCancel(true);
+ if (initialDir != null)
+ fsContext.cd(initialDir.getPath());
+ else
+ fsContext.refresh();
+ dialog.showModal();
+ }
+
+ private void finishInit(FileSystemContext fsContext,
+ FileSystemItem initialFilePath,
+ FileDialog dialog)
+ {
+ if (initialFilePath != null)
+ {
+ if (initialFilePath.isDirectory())
+ {
+ fsContext.cd(initialFilePath.getPath());
+ }
+ else
+ {
+ dialog.setFilename(initialFilePath.getName());
+ fsContext.cd(initialFilePath.getParentPathString());
+ }
+ }
+ else
+ {
+ fsContext.refresh();
+ }
+ dialog.showModal();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/WebTextInput.java b/src/gwt/src/org/rstudio/studio/client/common/impl/WebTextInput.java
new file mode 100644
index 0000000..e8fbbff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/WebTextInput.java
@@ -0,0 +1,128 @@
+/*
+ * WebTextInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import org.rstudio.core.client.MessageDisplay.PromptWithOptionResult;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.TextEntryModalDialog;
+import org.rstudio.studio.client.common.TextInput;
+import org.rstudio.studio.client.common.Value;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+
+public class WebTextInput implements TextInput
+{
+ public void promptForText(String title,
+ String label,
+ String initialValue,
+ boolean usePasswordMask,
+ boolean numbersOnly,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ ProgressOperationWithInput<String> okOperation,
+ Operation cancelOperation)
+ {
+ new TextEntryModalDialog(title,
+ label,
+ initialValue,
+ usePasswordMask,
+ null,
+ false,
+ numbersOnly,
+ selectionStart,
+ selectionLength,
+ okButtonCaption,
+ 300,
+ okOperation,
+ cancelOperation).showModal();
+ }
+
+ @Override
+ public void promptForTextWithOption(
+ String title,
+ String label,
+ String initialValue,
+ final boolean usePasswordMask,
+ String extraOptionPrompt,
+ boolean extraOptionDefault,
+ int selectionStart,
+ int selectionLength,
+ String okButtonCaption,
+ final ProgressOperationWithInput<PromptWithOptionResult> okOperation,
+ Operation cancelOperation)
+ {
+ // This variable introduces a level of pointer indirection that lets us
+ // get around passing TextEntryModalDialog a reference to itself in its
+ // own constructor.
+ final Value<TextEntryModalDialog> pDialog = new Value<TextEntryModalDialog>(null);
+
+ final TextEntryModalDialog dialog = new TextEntryModalDialog(
+ title,
+ label,
+ initialValue,
+ usePasswordMask,
+ extraOptionPrompt,
+ extraOptionDefault,
+ false,
+ selectionStart,
+ selectionLength,
+ okButtonCaption,
+ 300,
+ new ProgressOperationWithInput<String>()
+ {
+ @Override
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ PromptWithOptionResult result = new PromptWithOptionResult();
+ result.input = input;
+ result.extraOption = pDialog.getValue().getExtraOption();
+ okOperation.execute(result, indicator);
+ }
+ },
+ cancelOperation) {
+
+ @Override
+ protected void positionAndShowDialog(final Command onCompleted)
+ {
+ setPopupPositionAndShow(new PositionCallback() {
+
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ int left = (Window.getClientWidth()/2) - (offsetWidth/2);
+ int top = (Window.getClientHeight()/2) - (offsetHeight/2);
+
+ if (usePasswordMask)
+ top = 50;
+
+ setPopupPosition(left, top);
+
+ onCompleted.execute();
+ }
+
+ });
+ }
+
+ };
+
+ pDialog.setValue(dialog, false);
+
+ dialog.showModal();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/impl/WebWindowOpener.java b/src/gwt/src/org/rstudio/studio/client/common/impl/WebWindowOpener.java
new file mode 100644
index 0000000..24460ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/impl/WebWindowOpener.java
@@ -0,0 +1,192 @@
+/*
+ * WebWindowOpener.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.UrlBuilder;
+import com.google.gwt.user.client.Window;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+import org.rstudio.studio.client.common.WindowOpener;
+import org.rstudio.studio.client.common.satellite.SatelliteUtils;
+
+public class WebWindowOpener implements WindowOpener
+{
+ public void openWindow(final GlobalDisplay globalDisplay,
+ final String url,
+ final NewWindowOptions options)
+ {
+ openWindowInternal(globalDisplay, url, options, "", -1, -1);
+ }
+
+ public void openMinimalWindow(GlobalDisplay globalDisplay,
+ String url,
+ NewWindowOptions options,
+ int width,
+ int height,
+ boolean showLocation)
+ {
+ webOpenMinimalWindow(globalDisplay,
+ url,
+ options,
+ width,
+ height,
+ showLocation);
+ }
+
+ public void openSatelliteWindow(GlobalDisplay globalDisplay,
+ String viewName,
+ int width,
+ int height)
+ {
+ // build url
+ UrlBuilder urlBuilder = Window.Location.createUrlBuilder();
+ urlBuilder.setParameter("view", viewName);
+
+ // setup options
+ NewWindowOptions options = new NewWindowOptions();
+ options.setName(SatelliteUtils.getSatelliteWindowName(viewName));
+ options.setFocus(true);
+
+ // open window (force web codepath b/c desktop needs this so
+ // that window.opener is hooked up)
+ webOpenMinimalWindow(globalDisplay,
+ urlBuilder.buildString(),
+ options,
+ width,
+ height,
+ false);
+ }
+
+ protected boolean showPopupBlockedMessage()
+ {
+ return true;
+ }
+
+ protected boolean hasProtocol(String url)
+ {
+ return Pattern.create("^([a-zA-Z]+:)").match(url, 0) != null;
+ }
+
+ protected boolean isAppUrl(String url)
+ {
+ return url.startsWith(GWT.getHostPageBaseURL());
+ }
+
+ // enable callers to prevent subclass implementations from taking
+ // the open window by calling this directly
+ private void webOpenMinimalWindow(GlobalDisplay globalDisplay,
+ String url,
+ NewWindowOptions options,
+ int width,
+ int height,
+ boolean showLocation)
+ {
+ String loc = showLocation ? "1" : "0";
+ String features = "width=" + width + "," +
+ "height=" + height + "," +
+ "menubar=0,toolbar=0,location=" + loc + "," +
+ "status=0,scrollbars=1,resizable=1,directories=0";
+
+ openWindowInternal(globalDisplay, url, options, features, width, height);
+ }
+
+ private void openWindowInternal(GlobalDisplay globalDisplay,
+ final String url,
+ NewWindowOptions options,
+ final String features,
+ final int width,
+ final int height)
+ {
+ String name = options.getName();
+ final boolean focus = options.isFocus();
+ final OperationWithInput<WindowEx> openOperation = options.getCallback();
+
+ if (name == null)
+ name = "_blank";
+
+ if (!name.equals("_blank")
+ && !name.equals("_top")
+ && !name.equals("_parent")
+ && !name.equals("_self"))
+ {
+ name += "_" + clientId;
+ }
+
+ // Need to make the URL absolute because IE resolves relative URLs
+ // against the JavaScript file location, not the window.location like
+ // the other browsers do
+ final String absUrl = Pattern.create("^/|([a-zA-Z]+:)").match(url, 0) == null
+ ? GWT.getHostPageBaseURL() + url
+ : url;
+
+ final String finalName = name;
+ WindowEx window = doOpenWindow(absUrl, finalName, features, focus);
+ if (window == null)
+ {
+ if (showPopupBlockedMessage())
+ {
+ globalDisplay.showPopupBlockedMessage(new Operation()
+ {
+ public void execute()
+ {
+ WindowEx window = doOpenWindow(absUrl,
+ finalName,
+ features,
+ focus);
+ if (window != null)
+ {
+ if (openOperation != null)
+ openOperation.execute(window);
+ }
+ }
+ });
+ }
+ }
+ else
+ {
+ if (openOperation != null)
+ openOperation.execute(window);
+ }
+ }
+
+ private native WindowEx doOpenWindow(String url,
+ String name,
+ String features,
+ boolean focus)/*-{
+ var window = $wnd.open(url, name, features);
+ if (!window)
+ {
+ // popup was blocked
+ return null;
+ }
+
+ if (focus)
+ {
+ try {
+ window.focus();
+ } catch(e) {}
+ }
+
+ return window;
+ }-*/;
+
+ private static final String clientId = (int)(Math.random() * 10000) + "";
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramRegistry.java b/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramRegistry.java
new file mode 100644
index 0000000..c53ba9d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramRegistry.java
@@ -0,0 +1,98 @@
+/*
+ * LatexProgramRegistry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.latex;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class LatexProgramRegistry
+{
+ @Inject
+ public LatexProgramRegistry(Provider<Session> pSession)
+ {
+ pSession_ = pSession;
+ }
+
+ public String[] getTypeNames()
+ {
+ ArrayList<String> types = getTypes();
+ String[] typeNames = new String[types.size()];
+ for (int i=0; i< types.size(); i++)
+ typeNames[i] = types.get(i);
+ return typeNames;
+ }
+
+ public String getPrintableTypeNames()
+ {
+
+ String[] typeNames = getTypeNames();
+
+ if (typeNames.length == 1)
+ return typeNames[0];
+ else if (typeNames.length == 2)
+ return typeNames[0] + " and " + typeNames[1];
+ else
+ {
+ StringBuffer str = new StringBuffer();
+
+ for (int i=0; i<typeNames.length; i++)
+ {
+ str.append(typeNames[i]);
+ if (i != (typeNames.length - 1))
+ str.append(", ");
+ if (i == (typeNames.length - 2))
+ str.append("and ");
+ }
+ return str.toString();
+ }
+ }
+
+ public ArrayList<String> getTypes()
+ {
+ if (latexProgramTypes_ == null)
+ {
+ JsArrayString types =
+ pSession_.get().getSessionInfo().getLatexProgramTypes();
+
+ latexProgramTypes_ = new ArrayList<String>();
+ for (int i=0; i<types.length(); i++)
+ latexProgramTypes_.add(types.get(i));
+ }
+ return latexProgramTypes_;
+ }
+
+ public String findTypeIgnoreCase(String name)
+ {
+ for (String latexProgram : getTypes())
+ {
+ if (latexProgram.equalsIgnoreCase(name))
+ return latexProgram;
+ }
+
+ return null;
+ }
+
+
+
+ private final Provider<Session> pSession_;
+ private ArrayList<String> latexProgramTypes_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramSelectWidget.java b/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramSelectWidget.java
new file mode 100644
index 0000000..570d24c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/latex/LatexProgramSelectWidget.java
@@ -0,0 +1,33 @@
+/*
+ * LatexProgramSelectWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.latex;
+
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.RStudioGinjector;
+
+public class LatexProgramSelectWidget extends SelectWidget
+{
+ public LatexProgramSelectWidget()
+ {
+ super("Typeset LaTeX into PDF using:", latexProgramRegistry_.getTypeNames());
+
+ HelpButton.addHelpButton(this, "latex_program");
+ }
+
+
+ public static final LatexProgramRegistry latexProgramRegistry_ =
+ RStudioGinjector.INSTANCE.getLatexProgramRegistry();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.css b/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.css
new file mode 100644
index 0000000..0fa0f82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.css
@@ -0,0 +1,6 @@
+
+
+.mainWidget {
+ width: 420px;
+ height: 285px;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.java b/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.java
new file mode 100644
index 0000000..8eec9ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/ChooseMirrorDialog.java
@@ -0,0 +1,182 @@
+/*
+ * ChooseMirrorDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.mirrors;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.SimplePanelWithProgress;
+import org.rstudio.core.client.widget.images.ProgressImages;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerDataSource;
+import org.rstudio.studio.client.server.ServerError;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ChooseMirrorDialog<T extends JavaScriptObject> extends ModalDialog<T>
+{
+ public interface Source<T extends JavaScriptObject>
+ extends ServerDataSource<JsArray<T>>
+ {
+ String getType();
+ String getLabel(T mirror);
+ String getURL(T mirror);
+ }
+
+ public ChooseMirrorDialog(GlobalDisplay globalDisplay,
+ Source<T> mirrorSource,
+ OperationWithInput<T> inputOperation)
+ {
+ super("Choose " + mirrorSource.getType() + " Mirror", inputOperation);
+ globalDisplay_ = globalDisplay;
+ mirrorSource_ = mirrorSource;
+ enableOkButton(false);
+ }
+
+ @Override
+ protected T collectInput()
+ {
+ if (listBox_ != null && listBox_.getSelectedIndex() >= 0)
+ {
+ return mirrors_.get(listBox_.getSelectedIndex());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean validate(T input)
+ {
+ if (input == null)
+ {
+ globalDisplay_.showErrorMessage("Error",
+ "Please select a CRAN Mirror");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // create progress container
+ final SimplePanelWithProgress panel = new SimplePanelWithProgress(
+ ProgressImages.createLargeGray());
+ panel.setStylePrimaryName(RESOURCES.styles().mainWidget());
+
+ // show progress (with delay)
+ panel.showProgress(200);
+
+ // query data source for packages
+ mirrorSource_.requestData(new SimpleRequestCallback<JsArray<T>>() {
+
+ @Override
+ public void onResponseReceived(JsArray<T> mirrors)
+ {
+ // keep internal list of mirrors
+ mirrors_ = new ArrayList<T>(mirrors.length());
+
+ // create list box and select default item
+ listBox_ = new ListBox(false);
+ listBox_.setVisibleItemCount(18); // all
+ listBox_.setWidth("100%");
+ if (mirrors.length() > 0)
+ {
+ for(int i=0; i<mirrors.length(); i++)
+ {
+ T mirror = mirrors.get(i);
+ mirrors_.add(mirror);
+ String item = mirrorSource_.getLabel(mirror);
+ String value = mirrorSource_.getURL(mirror);
+ listBox_.addItem(item, value);
+ }
+
+ listBox_.setSelectedIndex(0);
+ enableOkButton(true);
+ }
+
+ // set it into the panel
+ panel.setWidget(listBox_);
+
+ // update ok button on changed
+ listBox_.addDoubleClickHandler(new DoubleClickHandler() {
+ @Override
+ public void onDoubleClick(DoubleClickEvent event)
+ {
+ clickOkButton();
+ }
+ });
+
+
+ // if the list box is larger than the space we initially allocated
+ // then increase the panel height
+ final int kDefaultPanelHeight = 285;
+ if (listBox_.getOffsetHeight() > kDefaultPanelHeight)
+ panel.setHeight(listBox_.getOffsetHeight() + "px");
+
+ // set focus
+ FocusHelper.setFocusDeferred(listBox_);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ closeDialog();
+ super.onError(error);
+ }
+ });
+
+ return panel;
+ }
+
+ static interface Styles extends CssResource
+ {
+ String mainWidget();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("ChooseMirrorDialog.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private final GlobalDisplay globalDisplay_ ;
+ private final Source<T> mirrorSource_;
+ private ArrayList<T> mirrors_ = null;
+ private ListBox listBox_ = null;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/DefaultCRANMirror.java b/src/gwt/src/org/rstudio/studio/client/common/mirrors/DefaultCRANMirror.java
new file mode 100644
index 0000000..8da76aa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/DefaultCRANMirror.java
@@ -0,0 +1,105 @@
+/*
+ * DefaultCRANMirror.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.mirrors;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.mirrors.model.CRANMirror;
+import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class DefaultCRANMirror
+{
+ @Inject
+ public DefaultCRANMirror(MirrorsServerOperations server,
+ GlobalDisplay globalDisplay)
+ {
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ }
+
+ public void choose(OperationWithInput<CRANMirror> onChosen)
+ {
+ new ChooseMirrorDialog<CRANMirror>(globalDisplay_,
+ mirrorDS_,
+ onChosen).showModal();
+ }
+
+ public void configure(final Command onConfigured)
+ {
+ // show dialog
+ new ChooseMirrorDialog<CRANMirror>(
+ globalDisplay_,
+ mirrorDS_,
+ new OperationWithInput<CRANMirror>() {
+ @Override
+ public void execute(final CRANMirror mirror)
+ {
+ server_.setCRANMirror(
+ mirror,
+ new SimpleRequestCallback<Void>("Error Setting CRAN Mirror") {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ // successfully set, call onConfigured
+ onConfigured.execute();
+ }
+ });
+ }
+ }).showModal();
+ }
+
+ private final MirrorsServerOperations server_;
+
+ private final GlobalDisplay globalDisplay_;
+
+ private final ChooseMirrorDialog.Source<CRANMirror> mirrorDS_ =
+ new ChooseMirrorDialog.Source<CRANMirror>() {
+
+ @Override
+ public String getType()
+ {
+ return "CRAN";
+ }
+
+ @Override
+ public String getLabel(CRANMirror mirror)
+ {
+ return mirror.getName() + " - " + mirror.getHost();
+ }
+
+ @Override
+ public String getURL(CRANMirror mirror)
+ {
+ return mirror.getURL();
+ }
+
+ @Override
+ public void requestData(
+ ServerRequestCallback<JsArray<CRANMirror>> requestCallback)
+ {
+ server_.getCRANMirrors(requestCallback);
+ }
+ };
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/BioconductorMirror.java b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/BioconductorMirror.java
new file mode 100644
index 0000000..f0e74b0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/BioconductorMirror.java
@@ -0,0 +1,40 @@
+/*
+ * BioconductorMirror.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.mirrors.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class BioconductorMirror extends JavaScriptObject
+{
+ protected BioconductorMirror()
+ {
+ }
+
+ public final static native BioconductorMirror create(String name,
+ String url) /*-{
+ var mirror = new Object();
+ mirror.name = name;
+ mirror.url = url;
+ return mirror;
+ }-*/;
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getURL() /*-{
+ return this.url;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/CRANMirror.java b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/CRANMirror.java
new file mode 100644
index 0000000..8ef3144
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/CRANMirror.java
@@ -0,0 +1,60 @@
+/*
+ * CRANMirror.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.mirrors.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class CRANMirror extends JavaScriptObject
+{
+ protected CRANMirror()
+ {
+ }
+
+ public final static native CRANMirror empty() /*-{
+ var cranMirror = new Object();
+ cranMirror.name = "";
+ cranMirror.host = "";
+ cranMirror.url = "";
+ cranMirror.country = "";
+ return cranMirror;
+ }-*/;
+
+ public final boolean isEmpty()
+ {
+ return getName() == null || getName().length() == 0;
+ }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getHost() /*-{
+ return this.host;
+ }-*/;
+
+ public final native String getURL() /*-{
+ return this.url;
+ }-*/;
+
+ public final native String getCountry() /*-{
+ return this.country;
+ }-*/;
+
+ public final String getDisplay()
+ {
+ return getName() +" - " + getHost();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/MirrorsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/MirrorsServerOperations.java
new file mode 100644
index 0000000..5cf627c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/mirrors/model/MirrorsServerOperations.java
@@ -0,0 +1,29 @@
+/*
+ * MirrorsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.mirrors.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import com.google.gwt.core.client.JsArray;
+
+public interface MirrorsServerOperations
+{
+ void setCRANMirror(CRANMirror mirror,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getCRANMirrors(
+ ServerRequestCallback<JsArray<CRANMirror>> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/r/RStringToken.java b/src/gwt/src/org/rstudio/studio/client/common/r/RStringToken.java
new file mode 100644
index 0000000..f87e030
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/r/RStringToken.java
@@ -0,0 +1,36 @@
+/*
+ * RStringToken.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.r;
+
+public class RStringToken extends RToken
+{
+ public RStringToken(int tokenType,
+ String content,
+ int offset,
+ int length,
+ boolean wellFormed)
+ {
+ super(tokenType, content, offset, length) ;
+
+ wellFormed_ = wellFormed ;
+ }
+
+ public boolean isWellFormed()
+ {
+ return wellFormed_ ;
+ }
+
+ private final boolean wellFormed_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/r/RToken.java b/src/gwt/src/org/rstudio/studio/client/common/r/RToken.java
new file mode 100644
index 0000000..9b59e4d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/r/RToken.java
@@ -0,0 +1,105 @@
+/*
+ * RToken.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.r;
+
+public class RToken
+{
+ public RToken(int tokenType, String content, int offset, int length)
+ {
+ super() ;
+ this.tokenType_ = tokenType ;
+ this.content_ = content ;
+ this.offset_ = offset ;
+ this.length_ = length ;
+ }
+
+ public int getTokenType()
+ {
+ return tokenType_ ;
+ }
+ public String getContent()
+ {
+ return content_ ;
+ }
+ public int getOffset()
+ {
+ return offset_ ;
+ }
+ public int getLength()
+ {
+ return length_ ;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31 ;
+ int result = 1 ;
+ result = prime * result + ((content_ == null) ? 0 : content_.hashCode()) ;
+ result = prime * result + length_ ;
+ result = prime * result + offset_ ;
+ result = prime * result + tokenType_ ;
+ return result ;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ RToken other = (RToken) obj ;
+ if (content_ == null)
+ {
+ if (other.content_ != null)
+ return false ;
+ } else if (!content_.equals(other.content_))
+ return false ;
+ if (length_ != other.length_)
+ return false ;
+ if (offset_ != other.offset_)
+ return false ;
+ if (tokenType_ != other.tokenType_)
+ return false ;
+ return true ;
+ }
+
+ public static final int LPAREN = '(' ;
+ public static final int RPAREN = ')' ;
+ public static final int LBRACKET = '[' ;
+ public static final int RBRACKET = ']' ;
+ public static final int LBRACE = '{' ;
+ public static final int RBRACE = '}' ;
+ public static final int COMMA = ',' ;
+ public static final int SEMI = ';' ;
+ public static final int WHITESPACE = 0x1001 ;
+ public static final int STRING = 0x1002 ;
+ public static final int NUMBER = 0x1003 ;
+ public static final int ID = 0x1004 ;
+ public static final int OPER = 0x1005 ;
+ public static final int UOPER = 0x1006 ;
+ public static final int ERROR = 0x1007 ;
+ public static final int LDBRACKET = 0x1008 ; // [[
+ public static final int RDBRACKET = 0x1009 ; // ]]
+ public static final int COMMENT = 0x100A ;
+
+ private final int tokenType_ ;
+ private final String content_ ;
+ private final int offset_ ;
+ private final int length_ ;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/r/RTokenizer.java b/src/gwt/src/org/rstudio/studio/client/common/r/RTokenizer.java
new file mode 100644
index 0000000..2be15b0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/r/RTokenizer.java
@@ -0,0 +1,305 @@
+/*
+ * RTokenizer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.r;
+
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+import java.util.ArrayList;
+
+public class RTokenizer
+{
+ public RTokenizer(String data)
+ {
+ this.data_ = data ;
+ this.pos_ = 0 ;
+ }
+
+ public static ArrayList<RToken> asTokens(String code)
+ {
+ ArrayList<RToken> results = new ArrayList<RToken>() ;
+ RTokenizer rt = new RTokenizer(code) ;
+ RToken t ;
+ while (null != (t = rt.nextToken()))
+ results.add(t) ;
+ return results ;
+ }
+
+ public RToken nextToken()
+ {
+ if (eol())
+ return null ;
+
+ char c = peek() ;
+
+ switch (c)
+ {
+ case '(': case ')':
+ case '{': case '}':
+ case ';': case ',':
+ return consumeToken(c, 1) ;
+ case '[':
+ if (peek(1, false) == '[')
+ return consumeToken(RToken.LDBRACKET, 2) ;
+ else
+ return consumeToken(c, 1) ;
+ case ']':
+ if (peek(1, false) == ']')
+ return consumeToken(RToken.RDBRACKET, 2) ;
+ else
+ return consumeToken(c, 1) ;
+ case '"':
+ case '\'':
+ return matchStringLiteral() ;
+ case '`':
+ return matchQuotedIdentifier();
+ case '#':
+ return matchComment();
+ case '%':
+ return matchUserOperator();
+ case ' ': case '\t': case '\r': case '\n':
+ case '\u00A0': case '\u3000':
+ return matchWhitespace() ;
+ }
+
+ char cNext = peek(1, false) ;
+
+ if ((c >= '0' && c <= '9')
+ || (c == '.' && cNext >= '0' && cNext <= '9'))
+ {
+ RToken numberToken = matchNumber() ;
+ if (numberToken.getLength() > 0)
+ return numberToken ;
+
+ assert false : "matchNumber() returned a zero-length token" ;
+ }
+
+ if (Character.isLetter(c) || c == '.')
+ {
+ // From Section 10.3.2, identifiers must not start with
+ // a period followed by a digit.
+ //
+ // Since we're not checking that the second character is
+ // not a digit, we must match on identifiers AFTER we have
+ // already tried to match on number.
+ return matchIdentifier() ;
+ }
+
+ RToken oper = matchOperator() ;
+ if (oper != null)
+ return oper ;
+
+ // Error!!
+ return consumeToken(RToken.ERROR, 1) ;
+ }
+
+ private RToken matchWhitespace()
+ {
+ String whitespace = peek("[\\s\\u00A0]+") ;
+ assert whitespace != null ;
+ return consumeToken(RToken.WHITESPACE, whitespace.length()) ;
+ }
+
+ private RToken matchStringLiteral()
+ {
+ int start = pos_ ;
+ char quot = eat() ;
+
+ assert quot == '"' || quot == '\'' ;
+
+ boolean wellFormed = false ;
+
+ while (!eol())
+ {
+ eatUntil("[\\\\\'\"]", true) ;
+ if (eol())
+ break ;
+
+ char c = eat() ;
+ if (c == quot)
+ {
+ wellFormed = true ;
+ break ;
+ }
+
+ if (c == '\\')
+ {
+ if (!eol())
+ eat() ;
+ // Actually the escape expression can be longer than
+ // just the backslash plus one character--but we don't
+ // need to distinguish escape expressions from other
+ // literal text other than for the purposes of breaking
+ // out of the string
+ }
+ }
+
+ return new RStringToken(RToken.STRING,
+ data_.substring(start, pos_),
+ start,
+ pos_-start, wellFormed) ;
+ }
+
+ private RToken matchNumber()
+ {
+ String num = peek("0x[0-9a-fA-F]*L?") ;
+ if (num == null)
+ num = peek("[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*)?[Li]?") ;
+
+ // We should only be in this method if 0-9 was matched, so this should
+ // be a safe assumption
+ assert num != null ;
+
+ return consumeToken(RToken.NUMBER, num.length()) ;
+ }
+
+ private RToken matchIdentifier()
+ {
+ int start = pos_ ;
+ eat() ;
+ String rest = peek("[\\w.]*") ;
+ pos_ += (rest != null ? rest : "").length() ;
+ return new RToken(RToken.ID,
+ data_.substring(start, pos_),
+ start,
+ pos_ - start) ;
+ }
+
+ private RToken matchQuotedIdentifier()
+ {
+ String iden = peek("`[^`]*`") ;
+ if (iden == null)
+ return consumeToken(RToken.ERROR, 1);
+ else
+ return consumeToken(RToken.ID, iden.length());
+ }
+
+ private RToken matchComment()
+ {
+ String comment = peek("#.*?$");
+ return consumeToken(RToken.COMMENT, comment.length());
+ }
+
+ private RToken matchUserOperator()
+ {
+ String oper = peek("%[^%]*%") ;
+ if (oper == null)
+ return consumeToken(RToken.ERROR, 1) ;
+ else
+ return consumeToken(RToken.UOPER, oper.length()) ;
+ }
+
+ private RToken matchOperator()
+ {
+ char cNext = peek(1, false) ;
+
+ switch (peek())
+ {
+ case '+': case '*': case '/':
+ case '^': case '&': case '|':
+ case '~': case '$': case ':':
+ // single-character operators
+ return consumeToken(RToken.OPER, 1) ;
+ case '-': // also ->
+ return consumeToken(RToken.OPER, cNext == '>' ? 2 : 1) ;
+ case '>': // also >=
+ return consumeToken(RToken.OPER, cNext == '=' ? 2 : 1) ;
+ case '<': // also <- and <=
+ return consumeToken(RToken.OPER, cNext == '=' ? 2 :
+ cNext == '-' ? 2 :
+ 1) ;
+ case '=': // also ==
+ return consumeToken(RToken.OPER, cNext == '=' ? 2 : 1) ;
+ case '!': // also !=
+ return consumeToken(RToken.OPER, cNext == '=' ? 2 : 1) ;
+ default:
+ return null ;
+ }
+ }
+
+ private boolean eol()
+ {
+ return pos_ >= data_.length() ;
+ }
+
+ private char peek()
+ {
+ return peek(0, true) ;
+ }
+
+ private char peek(int lookahead, boolean throwOnEOL)
+ {
+ if (!throwOnEOL && (pos_ + lookahead) >= data_.length())
+ return 0 ;
+ return data_.charAt(pos_ + lookahead) ;
+ }
+
+ private char eat()
+ {
+ char result = data_.charAt(pos_) ;
+ pos_++ ; // don't inline--we want the previous line to throw if EOL
+ return result ;
+ }
+
+ private String peek(String regex)
+ {
+ Match match = Pattern.create(regex).match(data_, pos_) ;
+ if (match == null)
+ return null ;
+ int idx = match.getIndex() ;
+ if (idx != pos_)
+ return null ;
+
+ return match.getValue() ;
+ }
+
+ private String eatUntil(String regex, boolean eatAllOnFailure)
+ {
+ int start = pos_ ;
+ Match match = Pattern.create(regex).match(data_, pos_) ;
+ if (match == null)
+ {
+ if (eatAllOnFailure)
+ {
+ pos_ = data_.length() ;
+ return data_.substring(start) ;
+ }
+ else
+ {
+ return null ;
+ }
+ }
+ else
+ {
+ pos_ = match.getIndex() ;
+ return data_.substring(start, pos_) ;
+ }
+ }
+
+ private RToken consumeToken(int tokenType, int length)
+ {
+ if (length == 0)
+ throw new IllegalArgumentException("Can't create zero-length token") ;
+ if (pos_ + length > data_.length())
+ throw new IllegalArgumentException("Premature EOF") ;
+
+ int start = pos_ ;
+ pos_ += length ;
+ return new RToken(tokenType, data_.substring(start, pos_), start, length) ;
+ }
+
+ private final String data_ ;
+ private int pos_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/reditor/EditorLanguage.java b/src/gwt/src/org/rstudio/studio/client/common/reditor/EditorLanguage.java
new file mode 100644
index 0000000..18e4ab1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/reditor/EditorLanguage.java
@@ -0,0 +1,88 @@
+/*
+ * EditorLanguage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.reditor;
+
+/**
+ * Models a language for CodeMirror.
+ *
+ * == HOW TO ADD A NEW LANGUAGE TO THE SOURCE EDITOR ==
+ * 1) Edit ./resources/colors.css, add all necessary CSS rules there
+ * 2) Put your parser file in ./resources/
+ * 3) Add your parser to REditorResources, following the example of the other
+ * parsers
+ * 4) Add your parser to this class's ALL_PARSER_URLS
+ * 5) In this class, add a static LANG_xyz field for your language
+ * 6) In this class, edit the static getLanguageForExtension to return your
+ * EditorLanguage for any applicable extensions
+ */
+public class EditorLanguage
+{
+ public static final EditorLanguage LANG_R = new EditorLanguage(
+ "mode/r", true);
+ public static final EditorLanguage LANG_RDOC = new EditorLanguage(
+ "mode/rdoc", false);
+ public static final EditorLanguage LANG_TEX = new EditorLanguage(
+ "mode/tex", false);
+ public static final EditorLanguage LANG_SWEAVE = new EditorLanguage(
+ "mode/sweave", true);
+ public static final EditorLanguage LANG_PLAIN = new EditorLanguage(
+ "ace/mode/text", false);
+ public static final EditorLanguage LANG_MARKDOWN = new EditorLanguage(
+ "mode/markdown", false);
+ public static final EditorLanguage LANG_RMARKDOWN = new EditorLanguage(
+ "mode/rmarkdown", true);
+ public static final EditorLanguage LANG_DCF = new EditorLanguage(
+ "mode/dcf", false);
+ public static final EditorLanguage LANG_HTML = new EditorLanguage(
+ "ace/mode/html", false);
+ public static final EditorLanguage LANG_RHTML = new EditorLanguage(
+ "mode/rhtml", true);
+ public static final EditorLanguage LANG_CSS = new EditorLanguage(
+ "ace/mode/css", true);
+ public static final EditorLanguage LANG_JAVASCRIPT = new EditorLanguage(
+ "ace/mode/javascript", true);
+ public static final EditorLanguage LANG_CPP = new EditorLanguage(
+ "mode/c_cpp", true);
+
+ /**
+ *
+ * @param parserName The name of the parser--it's found at the top of the
+ * parser .js fil
+ * e. This MUST match the value inside the .js file or else
+ * dynamic language switching (Save As... with a different extension)
+ * won't work.
+ * @param useRCompletion If true, then Tab is intercepted for completion
+ */
+ public EditorLanguage(
+ String parserName,
+ boolean useRCompletion)
+ {
+ parserName_ = parserName;
+ useRCompletion_ = useRCompletion;
+ }
+
+ public String getParserName()
+ {
+ return parserName_;
+ }
+
+ public boolean useRCompletion()
+ {
+ return useRCompletion_;
+ }
+
+ private final String parserName_;
+ private final boolean useRCompletion_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeave.java b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeave.java
new file mode 100644
index 0000000..94b89a0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeave.java
@@ -0,0 +1,44 @@
+/*
+ * RnwWeave.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rnw;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RnwWeave extends JavaScriptObject
+{
+ protected RnwWeave()
+ {
+ }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getPackageName() /*-{
+ return this.package_name;
+ }-*/;
+
+ public final native boolean getInjectConcordance() /*-{
+ return this.inject_concordance;
+ }-*/;
+
+ public final native boolean usesCodeForOptions() /*-{
+ return this.uses_code_for_options;
+ }-*/;
+
+ public final native boolean forceEchoOnExec() /*-{
+ return this.force_echo_on_exec;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveDirective.java b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveDirective.java
new file mode 100644
index 0000000..cfdecc8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveDirective.java
@@ -0,0 +1,54 @@
+/*
+ * RnwWeaveDirective.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rnw;
+
+import org.rstudio.core.client.tex.TexMagicComment;
+import org.rstudio.studio.client.RStudioGinjector;
+
+public class RnwWeaveDirective
+{
+ public static RnwWeaveDirective fromTexMagicComment(
+ TexMagicComment magicComment)
+ {
+ if (magicComment.getScope().equalsIgnoreCase("rnw") &&
+ magicComment.getVariable().equalsIgnoreCase("weave"))
+ {
+ return new RnwWeaveDirective(magicComment.getValue());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private RnwWeaveDirective(String rnwWeaveName)
+ {
+ rnwWeaveName_ = rnwWeaveName;
+ }
+
+ public String getName()
+ {
+ return rnwWeaveName_;
+ }
+
+ public RnwWeave getRnwWeave()
+ {
+ return RStudioGinjector.INSTANCE.getRnwWeaveRegistry()
+ .findTypeIgnoreCase(getName());
+ }
+
+
+ private String rnwWeaveName_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveRegistry.java b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveRegistry.java
new file mode 100644
index 0000000..839a8d3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveRegistry.java
@@ -0,0 +1,93 @@
+/*
+ * RnwWeaveRegistry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rnw;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class RnwWeaveRegistry
+{
+ @Inject
+ public RnwWeaveRegistry(Provider<Session> pSession)
+ {
+ pSession_ = pSession;
+ }
+
+ public String[] getTypeNames()
+ {
+ ArrayList<RnwWeave> weaveTypes = getTypes();
+ String[] typeNames = new String[weaveTypes.size()];
+ for (int i=0; i<weaveTypes.size(); i++)
+ typeNames[i] = weaveTypes.get(i).getName();
+ return typeNames;
+ }
+
+ public String getPrintableTypeNames()
+ {
+ StringBuffer str = new StringBuffer();
+ String[] typeNames = getTypeNames();
+ for (int i=0; i<typeNames.length; i++)
+ {
+ str.append(typeNames[i]);
+ if (i != (typeNames.length - 1))
+ {
+ if (typeNames.length > 2)
+ str.append(", ");
+ else
+ str.append(" ");
+ }
+ if (i == (typeNames.length - 2))
+ str.append("and ");
+ }
+ return str.toString();
+ }
+
+ public ArrayList<RnwWeave> getTypes()
+ {
+ if (weaveTypes_ == null)
+ {
+ JsArray<RnwWeave> types =
+ pSession_.get().getSessionInfo().getRnwWeaveTypes();
+
+ weaveTypes_ = new ArrayList<RnwWeave>();
+ for (int i=0; i<types.length(); i++)
+ weaveTypes_.add(types.get(i));
+ }
+ return weaveTypes_;
+ }
+
+ public RnwWeave findTypeIgnoreCase(String name)
+ {
+ for (RnwWeave rnwWeave : getTypes())
+ {
+ if (rnwWeave.getName().equalsIgnoreCase(name))
+ return rnwWeave;
+ }
+
+ return null;
+ }
+
+
+
+ private final Provider<Session> pSession_;
+ private ArrayList<RnwWeave> weaveTypes_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveSelectWidget.java b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveSelectWidget.java
new file mode 100644
index 0000000..5d7dd39
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rnw/RnwWeaveSelectWidget.java
@@ -0,0 +1,125 @@
+/*
+ * RnwWeaveSelectWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rnw;
+
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.TexCapabilities;
+import org.rstudio.studio.client.workbench.views.source.model.TexServerOperations;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+
+import com.google.inject.Inject;
+
+public class RnwWeaveSelectWidget extends SelectWidget
+{
+ public RnwWeaveSelectWidget()
+ {
+ super("Weave Rnw files using:", rnwWeaveRegistry_.getTypeNames());
+
+ HelpButton.addHelpButton(this, "rnw_weave_method");
+
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ this.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ RnwWeave weave = rnwWeaveRegistry_.findTypeIgnoreCase(getValue());
+ verifyAvailable(weave);
+
+ }
+ });
+ }
+
+ protected void verifyAvailable(final RnwWeave weave)
+ {
+ // first check if it was already available at startup
+ TexCapabilities texCap = session_.getSessionInfo().getTexCapabilities();
+ if (texCap.isRnwWeaveAvailable(weave))
+ return;
+
+ server_.getTexCapabilities(new ServerRequestCallback<TexCapabilities>() {
+
+ @Override
+ public void onResponseReceived(TexCapabilities capabilities)
+ {
+ if (!capabilities.isRnwWeaveAvailable(weave))
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.QUESTION,
+ "Confirm Change",
+ "The " + weave.getPackageName() + " package is required " +
+ "for " + weave.getName() + " weaving, " +
+ "however it is not currently installed. You should " +
+ "ensure that " + weave.getPackageName() + " is installed " +
+ "prior to compiling a PDF." +
+ "\n\nAre you sure you want to change this option?",
+ false,
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ }
+ },
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ setValue(rnwWeaveRegistry_.getTypes().get(0).getName());
+ }
+ },
+ false );
+
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+
+ });
+ }
+
+ @Inject
+ void initialize(GlobalDisplay globalDisplay,
+ TexServerOperations server,
+ Session session)
+ {
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ session_ = session;
+ }
+
+
+ private TexServerOperations server_;
+ private GlobalDisplay globalDisplay_;
+ private Session session_;
+
+ public static final RnwWeaveRegistry rnwWeaveRegistry_ =
+ RStudioGinjector.INSTANCE.getRnwWeaveRegistry();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/RPubsPresenter.java b/src/gwt/src/org/rstudio/studio/client/common/rpubs/RPubsPresenter.java
new file mode 100644
index 0000000..1b6a489
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rpubs/RPubsPresenter.java
@@ -0,0 +1,66 @@
+/*
+ * RPubsPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rpubs;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.rpubs.model.RPubsServerOperations;
+import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.inject.Inject;
+
+public class RPubsPresenter
+{
+ public interface Binder extends CommandBinder<Commands, RPubsPresenter>
+ {}
+
+ public interface Context
+ {
+ String getContextId();
+ String getTitle();
+ String getHtmlFile();
+ boolean isPublished();
+ }
+
+ @Inject
+ public RPubsPresenter(Binder binder,
+ Commands commands,
+ final GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ RPubsServerOperations server)
+ {
+ binder.bind(commands, this);
+ }
+
+ public void setContext(Context context)
+ {
+ context_ = context;
+ }
+
+ @Handler
+ public void onPublishHTML()
+ {
+ RPubsUploadDialog dlg = new RPubsUploadDialog(context_.getContextId(),
+ context_.getTitle(),
+ context_.getHtmlFile(),
+ context_.isPublished());
+ dlg.showModal();
+ }
+
+ private Context context_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/events/RPubsUploadStatusEvent.java b/src/gwt/src/org/rstudio/studio/client/common/rpubs/events/RPubsUploadStatusEvent.java
new file mode 100644
index 0000000..49226ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rpubs/events/RPubsUploadStatusEvent.java
@@ -0,0 +1,77 @@
+/*
+ * RPubsUploadStatusEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rpubs.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class RPubsUploadStatusEvent extends GwtEvent<RPubsUploadStatusEvent.Handler>
+{
+ public static class Status extends JavaScriptObject
+ {
+ protected Status()
+ {
+ }
+
+ public final native String getContextId() /*-{
+ return this.contextId;
+ }-*/;
+
+ public final native String getId() /*-{
+ return this.id;
+ }-*/;
+
+ public final native String getContinueUrl() /*-{
+ return this.continueUrl;
+ }-*/;
+
+ public final native String getError() /*-{
+ return this.error;
+ }-*/;
+
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onRPubsPublishStatus(RPubsUploadStatusEvent event);
+ }
+
+ public RPubsUploadStatusEvent(Status status)
+ {
+ status_ = status;
+ }
+
+ public Status getStatus()
+ {
+ return status_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onRPubsPublishStatus(this);
+ }
+
+ private final Status status_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/model/RPubsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/rpubs/model/RPubsServerOperations.java
new file mode 100644
index 0000000..98c2ad6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rpubs/model/RPubsServerOperations.java
@@ -0,0 +1,33 @@
+/*
+ * RPubsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.rpubs.model;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface RPubsServerOperations
+{
+ void rpubsIsPublished(String htmlFile,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void rpubsUpload(String contextId,
+ String title,
+ String htmlFile,
+ boolean isUpdate,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void rpubsTerminateUpload(String contextId,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.css b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.css
new file mode 100644
index 0000000..511e3da
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.css
@@ -0,0 +1,25 @@
+
+
+.mainWidget {
+ width: 400px;
+ height: 180px;
+}
+
+.headerPanel {
+ margin-bottom: 12px;
+}
+
+.headerLabel {
+ margin-left: 12px;
+ font-weight: bold;
+ font-size: 16px;
+ display: none;
+}
+
+.descLabel {
+ margin-bottom: 14px;
+}
+
+.progressImage {
+ margin-right: 5px;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.java b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.java
new file mode 100644
index 0000000..93b2fa6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/RPubsUploadDialog.java
@@ -0,0 +1,362 @@
+/*
+ * RPubsUploadDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.rpubs.ui;
+
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.resources.CoreResources;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressImage;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.rpubs.events.RPubsUploadStatusEvent;
+import org.rstudio.studio.client.common.rpubs.model.RPubsServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class RPubsUploadDialog extends ModalDialogBase
+{
+ public RPubsUploadDialog(String contextId,
+ String title,
+ String htmlFile,
+ boolean isPublished)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ setText("Publish to RPubs");
+ title_ = title;
+ htmlFile_ = htmlFile;
+ isPublished_ = isPublished;
+ contextId_ = contextId;
+ }
+
+ @Inject
+ void initialize(GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ RPubsServerOperations server)
+ {
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ server_ = server;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ Styles styles = RESOURCES.styles();
+
+ SimplePanel mainPanel = new SimplePanel();
+ mainPanel.addStyleName(styles.mainWidget());
+
+ VerticalPanel verticalPanel = new VerticalPanel();
+
+ HorizontalPanel headerPanel = new HorizontalPanel();
+ headerPanel.addStyleName(styles.headerPanel());
+ headerPanel.add(new Image(RESOURCES.publishLarge()));
+
+ Label headerLabel = new Label("Publish to RPubs");
+ headerLabel.addStyleName(styles.headerLabel());
+ headerPanel.add(headerLabel);
+ headerPanel.setCellVerticalAlignment(headerLabel,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+
+ verticalPanel.add(headerPanel);
+
+ String msg;
+ if (!isPublished_)
+ {
+ msg = "RPubs is a free service from RStudio for sharing " +
+ "R Markdown documents on the web. Click Publish to get " +
+ "started.";
+ }
+ else
+ {
+ msg = "This document has already been published on RPubs. You can " +
+ "choose to either update the existing RPubs document, or " +
+ "create a new one.";
+ }
+ Label descLabel = new Label(msg);
+ descLabel.addStyleName(styles.descLabel());
+ verticalPanel.add(descLabel);
+
+ HTML warningLabel = new HTML(
+ "<strong>IMPORTANT: All documents published to RPubs are " +
+ "publicly visible.</strong> You should " +
+ "only publish documents that you wish to share publicly.");
+ verticalPanel.add(warningLabel);
+
+ ThemedButton cancelButton = createCancelButton(new Operation() {
+ @Override
+ public void execute()
+ {
+ // if an upload is in progress then terminate it
+ if (uploadInProgress_)
+ {
+ server_.rpubsTerminateUpload(contextId_,
+ new VoidServerRequestCallback());
+
+ if (uploadProgressWindow_ != null)
+ uploadProgressWindow_.close();
+ }
+ }
+
+ });
+
+ continueButton_ = new ThemedButton("Publish", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ performUpload(false);
+ }
+ });
+
+ updateButton_ = new ThemedButton("Update Existing", new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ performUpload(true);
+ }
+ });
+
+ createButton_ = new ThemedButton("Create New", new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ performUpload(false);
+ }
+ });
+
+ if (!isPublished_)
+ {
+ addOkButton(continueButton_);
+ addCancelButton(cancelButton);
+ }
+ else
+ {
+ addOkButton(updateButton_);
+ addButton(createButton_);
+ addCancelButton(cancelButton);
+ }
+
+ mainPanel.setWidget(verticalPanel);
+ return mainPanel;
+ }
+
+
+ protected void onUnload()
+ {
+ eventRegistrations_.removeHandler();
+ super.onUnload();
+ }
+
+ private void performUpload(final boolean modify)
+ {
+ // set state
+ uploadInProgress_ = true;
+
+ if (Desktop.isDesktop())
+ {
+ performUpload(null, modify);
+ }
+ else
+ {
+ // randomize the name so firefox doesn't prevent us from reactivating
+ // the window programatically
+ globalDisplay_.openProgressWindow(
+ "_rpubs_upload" + (int)(Math.random() * 10000),
+ PROGRESS_MESSAGE,
+ new OperationWithInput<WindowEx>() {
+
+ @Override
+ public void execute(WindowEx window)
+ {
+ performUpload(window, modify);
+ }
+ });
+ }
+
+ }
+
+
+ private void performUpload(final WindowEx progressWindow,
+ boolean modify)
+ {
+ // record progress window
+ uploadProgressWindow_ = progressWindow;
+
+ // show progress
+ showProgressPanel();
+
+ // subscribe to notification of upload completion
+ eventRegistrations_.add(
+ eventBus_.addHandler(RPubsUploadStatusEvent.TYPE,
+ new RPubsUploadStatusEvent.Handler()
+ {
+ @Override
+ public void onRPubsPublishStatus(RPubsUploadStatusEvent event)
+ {
+ // make sure it applies to our context
+ RPubsUploadStatusEvent.Status status = event.getStatus();
+ if (!status.getContextId().equals(contextId_))
+ return;
+
+ uploadInProgress_ = false;
+
+ closeDialog();
+
+ if (!StringUtil.isNullOrEmpty(status.getError()))
+ {
+ if (progressWindow != null)
+ progressWindow.close();
+
+ new ConsoleProgressDialog("Upload Error Occurred",
+ status.getError(),
+ 1).showModal();
+ }
+ else
+ {
+ if (progressWindow != null)
+ {
+ progressWindow.replaceLocationHref(status.getContinueUrl());
+ }
+ else
+ {
+ globalDisplay_.openWindow(status.getContinueUrl());
+ }
+ }
+
+ }
+ }));
+
+ // initiate the upload
+ server_.rpubsUpload(
+ contextId_,
+ title_,
+ htmlFile_,
+ modify,
+ new ServerRequestCallback<Boolean>() {
+
+ @Override
+ public void onResponseReceived(Boolean response)
+ {
+ if (!response.booleanValue())
+ {
+ closeDialog();
+ globalDisplay_.showErrorMessage(
+ "Error",
+ "Unable to continue " +
+ "(another publish is currently running)");
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ closeDialog();
+ globalDisplay_.showErrorMessage("Error",
+ error.getUserMessage());
+ }
+ });
+ }
+
+ private void showProgressPanel()
+ {
+ // disable continue button
+ continueButton_.setVisible(false);
+ updateButton_.setVisible(false);
+ createButton_.setVisible(false);
+ enableOkButton(false);
+
+ // add progress
+ HorizontalPanel progressPanel = new HorizontalPanel();
+ ProgressImage progressImage = new ProgressImage(
+ CoreResources.INSTANCE.progress_gray());
+ progressImage.addStyleName(RESOURCES.styles().progressImage());
+ progressImage.show(true);
+ progressPanel.add(progressImage);
+ progressPanel.add(new Label(PROGRESS_MESSAGE));
+ addLeftWidget(progressPanel);
+ }
+
+ static interface Styles extends CssResource
+ {
+ String mainWidget();
+ String headerPanel();
+ String headerLabel();
+ String descLabel();
+ String progressImage();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("RPubsUploadDialog.css")
+ Styles styles();
+
+ ImageResource publishLarge();
+ }
+
+ private final boolean isPublished_;
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private ThemedButton continueButton_;
+ private ThemedButton updateButton_;
+ private ThemedButton createButton_;
+
+ private final String title_;
+ private final String htmlFile_;
+ private final String contextId_;
+
+ private boolean uploadInProgress_ = false;
+ private WindowEx uploadProgressWindow_ = null;
+
+ private GlobalDisplay globalDisplay_;
+ private EventBus eventBus_;
+ private RPubsServerOperations server_;
+
+ private HandlerRegistrations eventRegistrations_ = new HandlerRegistrations();
+
+ private static final String PROGRESS_MESSAGE = "Uploading document to RPubs...";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/publishLarge.png b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/publishLarge.png
new file mode 100644
index 0000000..e5a4236
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/common/rpubs/ui/publishLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/Satellite.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/Satellite.java
new file mode 100644
index 0000000..40b8915
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/Satellite.java
@@ -0,0 +1,229 @@
+/*
+ * Satellite.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.server.remote.ClientEventDispatcher;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class Satellite implements HasCloseHandlers<Satellite>
+{
+ @Inject
+ public Satellite(Session session,
+ EventBus eventBus,
+ Commands commands,
+ Provider<UIPrefs> pUIPrefs)
+ {
+ session_ = session;
+ pUIPrefs_ = pUIPrefs;
+ commands_ = commands;
+ eventDispatcher_ = new ClientEventDispatcher(eventBus);
+ }
+
+ public void initialize(String name,
+ CommandWithArg<JavaScriptObject> onReactivated)
+ {
+ onReactivated_ = onReactivated;
+ initializeNative(name);
+
+ // NOTE: Desktop doesn't seem to get onWindowClosing events in Qt 4.8
+ // so we instead rely on an explicit callback from the desktop frame
+ // to notifyRStudioSatelliteClosing
+ if (!Desktop.isDesktop())
+ {
+ Window.addWindowClosingHandler(new ClosingHandler() {
+ @Override
+ public void onWindowClosing(ClosingEvent event)
+ {
+ fireCloseEvent();
+ }
+ });
+ }
+ }
+
+ @Override
+ public HandlerRegistration addCloseHandler(CloseHandler<Satellite> handler)
+ {
+ return handlerManager_.addHandler(CloseEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlerManager_.fireEvent(event);
+ }
+
+ public native final void flushPendingEvents(String name) /*-{
+ $wnd.opener.flushPendingEvents(name);
+ }-*/;
+
+ // satellite windows should call this during startup to setup a
+ // communication channel with the main window
+ private native void initializeNative(String name) /*-{
+
+ // global flag used to conditionalize behavior
+ $wnd.isRStudioSatellite = true;
+ $wnd.RStudioSatelliteName = name;
+
+ // export setSessionInfo callback
+ var satellite = this;
+ $wnd.setRStudioSatelliteSessionInfo = $entry(
+ function(sessionInfo) {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::setSessionInfo(Lcom/google/gwt/core/client/JavaScriptObject;)(sessionInfo);
+ }
+ );
+
+ // export setParams callback
+ $wnd.setRStudioSatelliteParams = $entry(
+ function(params) {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::setParams(Lcom/google/gwt/core/client/JavaScriptObject;)(params);
+ }
+ );
+
+ // export notifyReactivated callback
+ $wnd.notifyRStudioSatelliteReactivated = $entry(
+ function(params) {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::notifyReactivated(Lcom/google/gwt/core/client/JavaScriptObject;)(params);
+ }
+ );
+
+
+ // export notifyClosing
+ $wnd.notifyRStudioSatelliteClosing = $entry(function() {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::fireCloseEvent()();
+ });
+
+ // export event notification callback
+ $wnd.dispatchEventToRStudioSatellite = $entry(
+ function(clientEvent) {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::dispatchEvent(Lcom/google/gwt/core/client/JavaScriptObject;)(clientEvent);
+ }
+ );
+
+ // export command notification callback
+ $wnd.dispatchCommandToRStudioSatellite = $entry(
+ function(commandId) {
+ satellite. at org.rstudio.studio.client.common.satellite.Satellite::dispatchCommand(Ljava/lang/String;)(commandId);
+ }
+ );
+
+ // register (this will call the setSessionInfo back)
+ $wnd.opener.registerAsRStudioSatellite(name, $wnd);
+ }-*/;
+
+
+ // check whether the current window is a satellite
+ public native boolean isCurrentWindowSatellite() /*-{
+ return !!$wnd.isRStudioSatellite;
+ }-*/;
+
+ // get the name of the current satellite window (null if not a satellite)
+ public native String getSatelliteName() /*-{
+ return $wnd.RStudioSatelliteName;
+ }-*/;
+
+ public JavaScriptObject getParams()
+ {
+ return params_;
+ }
+
+ public void focusMainWindow()
+ {
+ if (Desktop.isDesktop())
+ focusMainWindowDesktop();
+ else
+ focusMainWindowWeb();
+ }
+
+ private void focusMainWindowDesktop()
+ {
+ Desktop.getFrame().bringMainFrameToFront();
+ }
+
+ private native void focusMainWindowWeb() /*-{
+ $wnd.opener.focus();
+ }-*/;
+
+ // called by main window to initialize sessionInfo
+ private void setSessionInfo(JavaScriptObject si)
+ {
+ // get the session info and set it
+ SessionInfo sessionInfo = si.<SessionInfo>cast();
+ session_.setSessionInfo(sessionInfo);
+
+ // ensure ui prefs initialize
+ pUIPrefs_.get();
+ }
+
+ // called by main window to setParams
+ private void setParams(JavaScriptObject params)
+ {
+ params_ = params;
+ }
+
+
+ // called by main window to notify us of reactivation with a new
+ // set of params
+ private void notifyReactivated(JavaScriptObject params)
+ {
+ if (onReactivated_ != null)
+ onReactivated_.execute(params);
+ }
+
+ private void fireCloseEvent()
+ {
+ CloseEvent.fire(this, this);
+ }
+
+ // called by main window to deliver events
+ private void dispatchEvent(JavaScriptObject clientEvent)
+ {
+ eventDispatcher_.enqueEventAsJso(clientEvent);
+ }
+
+ // called by main window to deliver commands
+ private void dispatchCommand(String commandId)
+ {
+ commands_.getCommandById(commandId).execute();
+ }
+
+ private final Session session_;
+ private final Provider<UIPrefs> pUIPrefs_;
+ private final ClientEventDispatcher eventDispatcher_;
+ private final HandlerManager handlerManager_ = new HandlerManager(this);
+ private final Commands commands_;
+ private JavaScriptObject params_ = null;
+ private CommandWithArg<JavaScriptObject> onReactivated_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplication.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplication.java
new file mode 100644
index 0000000..c7e2b32
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplication.java
@@ -0,0 +1,103 @@
+/*
+ * SatelliteApplication.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Provider;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+public class SatelliteApplication
+{
+ public SatelliteApplication(
+ String name,
+ SatelliteApplicationView view,
+ Satellite satellite,
+ Provider<AceThemes> pAceThemes,
+ ApplicationUncaughtExceptionHandler uncaughtExHandler)
+ {
+ name_ = name;
+ view_ = view;
+ satellite_ = satellite;
+ pAceThemes_ = pAceThemes;
+ uncaughtExHandler_ = uncaughtExHandler;
+ }
+
+ /**
+ * Have subclasses override and return true if the satellite application is
+ * not ready to process remote server events until some time after the
+ * satellite window is created.
+ * @return
+ */
+ protected boolean manuallyFlushPendingEvents()
+ {
+ return false;
+ }
+
+ public void go(RootLayoutPanel rootPanel,
+ final Command dismissLoadingProgress)
+ {
+ // indicate that we are a satellite window
+ satellite_.initialize(name_,
+ new CommandWithArg<JavaScriptObject> () {
+ @Override
+ public void execute(JavaScriptObject params)
+ {
+ view_.reactivate(params);
+ }
+ });
+
+ if (!manuallyFlushPendingEvents())
+ {
+ flushPendingEvents();
+ }
+
+ // inject ace themes
+ pAceThemes_.get();
+
+ // register for uncaught exceptions (do this after calling
+ // initSatelliteWindow b/c it depends on Server)
+ uncaughtExHandler_.register();
+
+ // create the widget
+ Widget w = view_.getWidget();
+ rootPanel.add(w);
+ rootPanel.setWidgetTopBottom(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
+ rootPanel.setWidgetLeftRight(w, 0, Style.Unit.PX, 0, Style.Unit.PX);
+
+ // show the view
+ view_.show(satellite_.getParams());
+
+ // dismiss loading progress
+ dismissLoadingProgress.execute();
+ }
+
+ protected void flushPendingEvents()
+ {
+ satellite_.flushPendingEvents(name_);
+ }
+
+
+ private final String name_;
+ private final SatelliteApplicationView view_;
+ private final Satellite satellite_;
+ private final Provider<AceThemes> pAceThemes_;
+ private final ApplicationUncaughtExceptionHandler uncaughtExHandler_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplicationView.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplicationView.java
new file mode 100644
index 0000000..cf4b26e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteApplicationView.java
@@ -0,0 +1,26 @@
+/*
+ * SatelliteApplicationView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.ui.Widget;
+
+public interface SatelliteApplicationView
+{
+ Widget getWidget() ;
+
+ void show(JavaScriptObject params);
+ void reactivate(JavaScriptObject params);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteManager.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteManager.java
new file mode 100644
index 0000000..981eca7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteManager.java
@@ -0,0 +1,446 @@
+/*
+ * SatelliteManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.layout.ScreenUtils;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Window;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class SatelliteManager implements CloseHandler<Window>
+{
+ @Inject
+ public SatelliteManager(
+ Session session,
+ Provider<ApplicationUncaughtExceptionHandler> pUncaughtExceptionHandler)
+ {
+ session_ = session;
+ pUncaughtExceptionHandler_ = pUncaughtExceptionHandler;
+ }
+
+ // the main window should call this method during startup to set itself
+ // up to manage and communicate with the satellite windows
+ public void initialize()
+ {
+ // export the registration hook used by satellites
+ exportSatelliteRegistrationCallback();
+
+ // handle onClosed to automatically close all satellites
+ Window.addCloseHandler(this);
+ }
+
+ // open a satellite window (re-activate existing if possible)
+ public void openSatellite(String name,
+ JavaScriptObject params,
+ Size preferredSize)
+ {
+ // satellites can't launch other satellites -- this is because the
+ // delegating/forwarding of remote server calls and events doesn't
+ // cascade correctly -- it wouldn't be totally out of the question
+ // to make this work but we'd rather not have this complexity
+ // if we don't need to.
+ if (isCurrentWindowSatellite())
+ {
+ Debug.log("Satellite windows can't launch other satellites");
+ assert false;
+ return;
+ }
+
+ // check for a re-activation of an existing window
+ for (ActiveSatellite satellite : satellites_)
+ {
+ if (satellite.getName().equals(name))
+ {
+ WindowEx window = satellite.getWindow();
+ if (!window.isClosed())
+ {
+ // for web mode bring the window to the front, notify
+ // it that it has been reactivated, then exit.
+ if (!Desktop.isDesktop())
+ {
+ // don't do this for chrome (since it doesn't allow
+ // window.focus). for chrome we'll just fall through
+ // and openSatelliteWindow will be called and the
+ // window will be reloaded)
+ if (!BrowseCap.isChrome())
+ {
+ window.focus();
+ callNotifyReactivated(window, params);
+ return;
+ }
+ }
+ // desktop mode: activate and return
+ else
+ {
+ Desktop.getFrame().activateSatelliteWindow(
+ SatelliteUtils.getSatelliteWindowName(satellite.getName()));
+ callNotifyReactivated(window, params);
+ return;
+ }
+ }
+ }
+ }
+
+ // Start buffering events sent to this satellite. That way, we won't miss
+ // anything while the satellite is being loaded/reactivated
+ if (!pendingEventsBySatelliteName_.containsKey(name))
+ {
+ pendingEventsBySatelliteName_.put(name,
+ new ArrayList<JavaScriptObject>());
+ }
+
+ // record satellite params for subsequent setting (this value is read
+ // by the satellite within the call to registerAsSatellite)
+ if (params != null)
+ satelliteParams_.put(name, params);
+
+ // open the satellite - it will call us back on registerAsSatellite
+ // at which time we'll call setSessionInfo, setParams, etc.
+ Size windowSize = ScreenUtils.getAdjustedWindowSize(preferredSize);
+ RStudioGinjector.INSTANCE.getGlobalDisplay().openSatelliteWindow(
+ name,
+ windowSize.width,
+ windowSize.height);
+ }
+
+ public boolean satelliteWindowExists(String name)
+ {
+ return getSatelliteWindowObject(name) != null;
+ }
+
+ public WindowEx getSatelliteWindowObject(String name)
+ {
+ for (ActiveSatellite satellite : satellites_)
+ if (satellite.getName().equals(name) &&
+ !satellite.getWindow().isClosed())
+ return satellite.getWindow();
+
+ return null;
+ }
+
+ public void activateSatelliteWindow(String name)
+ {
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().activateSatelliteWindow(
+ SatelliteUtils.getSatelliteWindowName(name));
+ }
+ else
+ {
+ for (ActiveSatellite satellite : satellites_)
+ {
+ if (satellite.getName().equals(name) &&
+ !satellite.getWindow().isClosed())
+ {
+ satellite.getWindow().focus();
+ break;
+ }
+ }
+ }
+ }
+
+ // close all satellite windows
+ public void closeAllSatellites()
+ {
+ for (ActiveSatellite satellite : satellites_)
+ {
+ try
+ {
+ satellite.getWindow().close();
+ }
+ catch(Throwable e)
+ {
+ }
+ }
+ satellites_.clear();
+ pendingEventsBySatelliteName_.clear();
+ }
+
+ // close one satellite window
+ public void closeSatelliteWindow(String name)
+ {
+ for (ActiveSatellite satellite : satellites_)
+ {
+ if (satellite.getName().equals(name) &&
+ !satellite.getWindow().isClosed())
+ {
+ try
+ {
+ satellite.getWindow().close();
+ }
+ catch(Throwable e)
+ {
+ }
+ break;
+ }
+ }
+ }
+
+ // dispatch an event to all satellites
+ public void dispatchEvent(JavaScriptObject clientEvent)
+ {
+ // list of windows to remove (because they were closed)
+ ArrayList<ActiveSatellite> removeWindows = null;
+
+ // iterate over the satellites (make a copy to avoid races if
+ // for some reason firing an event creates or destroys a satellite)
+ @SuppressWarnings("unchecked")
+ ArrayList<ActiveSatellite> satellites =
+ (ArrayList<ActiveSatellite>)satellites_.clone();
+ for (ActiveSatellite satellite : satellites)
+ {
+ try
+ {
+ // If we're buffering events for this satellite, then don't dispatch
+ // them
+ if (pendingEventsBySatelliteName_.containsKey(satellite.getName()))
+ continue;
+
+ WindowEx satelliteWnd = satellite.getWindow();
+ if (satelliteWnd.isClosed())
+ {
+ if (removeWindows == null)
+ removeWindows = new ArrayList<ActiveSatellite>();
+ removeWindows.add(satellite);
+ }
+ else
+ {
+ callDispatchEvent(satelliteWnd, clientEvent);
+ }
+ }
+ catch(Throwable e)
+ {
+ }
+ }
+
+ for (Entry<String, ArrayList<JavaScriptObject>> entry :
+ pendingEventsBySatelliteName_.entrySet())
+ {
+ entry.getValue().add(clientEvent);
+ }
+
+ // remove windows if necessary
+ if (removeWindows != null)
+ {
+ for (ActiveSatellite satellite : removeWindows)
+ {
+ satellites_.remove(satellite);
+ }
+ }
+ }
+
+ // dispatch a command to all satellites.
+ public void dispatchCommand(AppCommand command)
+ {
+ for (ActiveSatellite satellite: satellites_)
+ {
+ callDispatchCommand(satellite.getWindow(), command.getId());
+ }
+ }
+
+ // close all satellites when we are closed
+ @Override
+ public void onClose(CloseEvent<Window> event)
+ {
+ closeAllSatellites();
+ }
+
+ // called by satellites to connect themselves with the main window
+ private void registerAsSatellite(final String name, JavaScriptObject wnd)
+ {
+ // get the satellite and add it to our list. in some cases (such as
+ // the Ctrl+R reload of an existing satellite window) we actually
+ // already have a reference to this satellite in our list so in that
+ // case we make sure not to add a duplicate
+ WindowEx satelliteWnd = wnd.<WindowEx>cast();
+ ActiveSatellite satellite = new ActiveSatellite(name, satelliteWnd);
+ if (!satellites_.contains(satellite))
+ satellites_.add(satellite);
+
+ // call setSessionInfo
+ callSetSessionInfo(satelliteWnd, session_.getSessionInfo());
+
+ // call setParams
+ JavaScriptObject params = satelliteParams_.get(name);
+ if (params != null)
+ callSetParams(satelliteWnd, params);
+ }
+
+ private void flushPendingEvents(String name)
+ {
+ ArrayList<JavaScriptObject> events =
+ pendingEventsBySatelliteName_.remove(name);
+
+ if (events == null || events.size() == 0)
+ return;
+
+ for (ActiveSatellite satellite :
+ new ArrayList<ActiveSatellite>(satellites_))
+ {
+ if (satellite.getName().equals(name)
+ && !satellite.getWindow().isClosed())
+ {
+ for (JavaScriptObject evt : events)
+ {
+ try
+ {
+ callDispatchEvent(satellite.getWindow(), evt);
+ }
+ catch (Exception e)
+ {
+ pUncaughtExceptionHandler_.get().onUncaughtException(e);
+ }
+ }
+ }
+ }
+ }
+
+ // export the global function required for satellites to register
+ private native void exportSatelliteRegistrationCallback() /*-{
+ var manager = this;
+ $wnd.registerAsRStudioSatellite = $entry(
+ function(name, satelliteWnd) {
+ manager. at org.rstudio.studio.client.common.satellite.SatelliteManager::registerAsSatellite(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(name, satelliteWnd);
+ }
+ );
+ $wnd.flushPendingEvents = $entry(
+ function(name) {
+ manager. at org.rstudio.studio.client.common.satellite.SatelliteManager::flushPendingEvents(Ljava/lang/String;)(name);
+ }
+ );
+ }-*/;
+
+ // call setSessionInfo on a satellite
+ private native void callSetSessionInfo(JavaScriptObject satellite,
+ JavaScriptObject sessionInfo) /*-{
+ satellite.setRStudioSatelliteSessionInfo(sessionInfo);
+ }-*/;
+
+ // call setParams on a satellite
+ private native void callSetParams(JavaScriptObject satellite,
+ JavaScriptObject params) /*-{
+ satellite.setRStudioSatelliteParams(params);
+ }-*/;
+
+ // call notifyReactivated on a satellite
+ private native void callNotifyReactivated(JavaScriptObject satellite,
+ JavaScriptObject params) /*-{
+ satellite.notifyRStudioSatelliteReactivated(params);
+ }-*/;
+
+ // dispatch event to a satellite
+ private native void callDispatchEvent(JavaScriptObject satellite,
+ JavaScriptObject clientEvent) /*-{
+ satellite.dispatchEventToRStudioSatellite(clientEvent);
+ }-*/;
+
+ // dispatch command to a satellite
+ private native void callDispatchCommand(JavaScriptObject satellite,
+ String commandId) /*-{
+ satellite.dispatchCommandToRStudioSatellite(commandId);
+ }-*/;
+
+ // check whether the current window is a satellite (note this method
+ // is also implemented in the Satellite class -- we don't want this class
+ // to depend on Satellite so we duplicate the definition)
+ private native boolean isCurrentWindowSatellite() /*-{
+ return !!$wnd.isRStudioSatellite;
+ }-*/;
+
+
+ // alert callback (used for testing html preview sandbox)
+ //private void showAlert(String message)
+ //{
+ // RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage("Alert",
+ // message);
+ //}
+
+ //private native void exportSatelliteAlertCallback() /*-{
+ // var manager = this;
+ // $wnd.rstudioSatelliteAlert = $entry(
+ // function(message) {
+ // manager. at org.rstudio.studio.client.common.satellite.SatelliteManager::showAlert(Ljava/lang/String;)(message);
+ // }
+ // );
+ //}-*/;
+
+ private final Session session_;
+ private final Provider<ApplicationUncaughtExceptionHandler> pUncaughtExceptionHandler_;
+ private final ArrayList<ActiveSatellite> satellites_ =
+ new ArrayList<ActiveSatellite>();
+
+ private final HashMap<String,JavaScriptObject> satelliteParams_ =
+ new HashMap<String,JavaScriptObject>();
+
+ private final HashMap<String, ArrayList<JavaScriptObject>>
+ pendingEventsBySatelliteName_ = new HashMap<String, ArrayList<JavaScriptObject>>();
+
+ private class ActiveSatellite
+ {
+ public ActiveSatellite(String name, WindowEx window)
+ {
+ name_ = name;
+ window_ = window;
+ }
+
+ public String getName()
+ {
+ return name_;
+ }
+
+ public WindowEx getWindow()
+ {
+ return window_;
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ if (other == null)
+ return false;
+
+ ActiveSatellite otherSatellite = (ActiveSatellite)other;
+
+ return getName().equals(otherSatellite.getName()) &&
+ getWindow().equals(otherSatellite.getWindow());
+ }
+
+ private final String name_;
+ private final WindowEx window_;
+ }
+
+}
+
+
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteUtils.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteUtils.java
new file mode 100644
index 0000000..a116ae8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteUtils.java
@@ -0,0 +1,23 @@
+/*
+ * SatelliteUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+public class SatelliteUtils
+{
+ public static String getSatelliteWindowName(String mode)
+ {
+ return "_rstudio_satellite_" + mode;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteWindow.java b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteWindow.java
new file mode 100644
index 0000000..b166e09
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/satellite/SatelliteWindow.java
@@ -0,0 +1,103 @@
+/*
+ * SatelliteWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.satellite;
+
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.application.events.ChangeFontSizeEvent;
+import org.rstudio.studio.client.application.events.ChangeFontSizeHandler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.ProvidesResize;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.inject.Provider;
+
+
+public abstract class SatelliteWindow extends Composite
+ implements RequiresResize,
+ ProvidesResize
+{
+ public SatelliteWindow(Provider<EventBus> pEventBus,
+ Provider<FontSizeManager> pFontSizeManager)
+ {
+ // save references
+ pEventBus_ = pEventBus;
+ pFontSizeManager_ = pFontSizeManager;
+
+ // occupy full client area of the window
+ if (!allowScrolling())
+ Window.enableScrolling(false);
+ Window.setMargin("0px");
+
+ // create application panel
+ mainPanel_ = new LayoutPanel();
+
+ // init widget
+ initWidget(mainPanel_);
+ }
+
+ protected boolean allowScrolling()
+ {
+ return false;
+ }
+
+ // show the satellite window (subclasses shouldn't override this method,
+ // rather they should override the abstract onInitialize method)
+ public void show(JavaScriptObject params)
+ {
+ // react to font size changes
+ EventBus eventBus = pEventBus_.get();
+ eventBus.addHandler(ChangeFontSizeEvent.TYPE, new ChangeFontSizeHandler()
+ {
+ public void onChangeFontSize(ChangeFontSizeEvent event)
+ {
+ FontSizer.setNormalFontSize(Document.get(), event.getFontSize());
+ }
+ });
+ FontSizeManager fontSizeManager = pFontSizeManager_.get();
+ FontSizer.setNormalFontSize(Document.get(), fontSizeManager.getSize());
+
+ // disable no handler assertions
+ AppCommand.disableNoHandlerAssertions();
+
+ // allow subclasses to initialize
+ onInitialize(mainPanel_, params);
+ }
+
+ @Override
+ public void onResize()
+ {
+ mainPanel_.onResize();
+ }
+
+ abstract protected void onInitialize(LayoutPanel mainPanel,
+ JavaScriptObject params);
+
+ protected LayoutPanel getMainPanel()
+ {
+ return mainPanel_;
+ }
+
+ private final Provider<EventBus> pEventBus_;
+ private final Provider<FontSizeManager> pFontSizeManager_;
+ private LayoutPanel mainPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shell/ShellDisplay.java b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellDisplay.java
new file mode 100644
index 0000000..5a7eae0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellDisplay.java
@@ -0,0 +1,52 @@
+/*
+ * ShellDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shell;
+
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.studio.client.workbench.model.ConsoleAction;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+
+public interface ShellDisplay extends ShellOutputWriter,
+ CanFocus,
+ HasKeyPressHandlers
+{
+ void consoleWriteInput(String input);
+ void consoleWritePrompt(String prompt);
+ void consolePrompt(String prompt, boolean showInput) ;
+ void ensureInputVisible() ;
+ InputEditorDisplay getInputEditorDisplay() ;
+ void clearOutput() ;
+ String processCommandEntry() ;
+ int getCharacterWidth() ;
+ boolean isPromptEmpty();
+ String getPromptText();
+
+ void setReadOnly(boolean readOnly);
+
+ void playbackActions(RpcObjectList<ConsoleAction> actions);
+
+ int getMaxOutputLines();
+ void setMaxOutputLines(int maxLines);
+
+ HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler);
+
+ Widget getShellWidget();
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInput.java b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInput.java
new file mode 100644
index 0000000..89df668
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInput.java
@@ -0,0 +1,55 @@
+/*
+ * ShellInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shell;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShellInput extends JavaScriptObject
+{
+ protected ShellInput()
+ {
+ }
+
+ public native static ShellInput create(String text, boolean echoInput) /*-{
+ return {
+ interrupt: false,
+ text: text,
+ echo_input: echoInput
+ };
+ }-*/;
+
+ public native static ShellInput createInterrupt() /*-{
+ return {
+ interrupt: true,
+ text: "",
+ echo_input: true
+ };
+ }-*/;
+
+
+
+ public native final boolean getInterrupt() /*-{
+ return this.interrupt;
+ }-*/;
+
+ public native final String getText() /*-{
+ return this.text;
+ }-*/;
+
+ public native final boolean getEchoInput() /*-{
+ return this.echo_input;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInteractionManager.java b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInteractionManager.java
new file mode 100644
index 0000000..94c75d2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellInteractionManager.java
@@ -0,0 +1,336 @@
+/*
+ * ShellInteractionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shell;
+
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.CommandLineHistory;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.common.crypto.PublicKeyInfo;
+import org.rstudio.studio.client.common.crypto.RSAEncrypt;
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+
+
+public class ShellInteractionManager implements ShellOutputWriter
+{
+ public ShellInteractionManager(ShellDisplay display,
+ CryptoServerOperations server,
+ CommandWithArg<ShellInput> inputHandler)
+ {
+ display_ = display;
+ server_ = server;
+ input_ = display_.getInputEditorDisplay();
+ historyManager_ = new CommandLineHistory(input_);
+ inputHandler_ = inputHandler;
+
+ display_.addCapturingKeyDownHandler(new InputKeyDownHandler());
+ }
+
+ public void setHistoryEnabled(boolean enabled)
+ {
+ historyEnabled_ = enabled;
+ }
+
+ @Override
+ public void consoleWriteOutput(String output)
+ {
+ output = maybeSuppressOutputPrefix(output);
+ if (StringUtil.isNullOrEmpty(output))
+ return;
+
+ display_.consoleWriteOutput(output);
+ }
+
+ private String maybeSuppressOutputPrefix(String output)
+ {
+ if (!Desktop.isDesktop() || !BrowseCap.isWindows())
+ return output;
+
+ if (StringUtil.isNullOrEmpty(outputPrefixToSuppress_))
+ return output;
+
+ String prefix = outputPrefixToSuppress_;
+ outputPrefixToSuppress_ = null;
+
+ if (output.startsWith(prefix))
+ return output.substring(prefix.length());
+
+ return output;
+ }
+
+ @Override
+ public void consoleWriteError(String error)
+ {
+ // show the error in the console then re-prompt
+ display_.consoleWriteError(
+ "Error: " + error + "\n");
+ if (lastPromptText_ != null)
+ consolePrompt(lastPromptText_, false);
+ }
+
+ @Override
+ public void consoleWriteExtendedError(
+ String error, UnhandledError traceInfo,
+ boolean expand, String command)
+ {
+ }
+
+ @Override
+ public void consoleWritePrompt(String prompt)
+ {
+ consolePrompt(prompt);
+ }
+
+ private void processInput(final CommandWithArg<ShellInput> onInputReady)
+ {
+ // get the current prompt text
+ String promptText = display_.getPromptText();
+
+ // process command entry
+ String commandEntry = display_.processCommandEntry();
+ if (addToHistory_)
+ historyManager_.addToHistory(commandEntry);
+
+ // input is entry + newline
+ String input = commandEntry + "\n";
+
+ outputPrefixToSuppress_ = null;
+ // update console with prompt and input
+ display_.consoleWritePrompt(promptText);
+ final boolean echoInput = showInputForPrompt(promptText);
+ if (echoInput)
+ {
+ display_.consoleWriteInput(input);
+ if (Desktop.isDesktop() && BrowseCap.isWindows())
+ outputPrefixToSuppress_ = commandEntry;
+ }
+
+ // encrypt the input and return it
+ encryptInput(input, new CommandWithArg<String>() {
+
+ @Override
+ public void execute(String arg)
+ {
+ onInputReady.execute(ShellInput.create(arg, echoInput));
+ }
+ });
+ }
+
+ private void navigateHistory(int offset)
+ {
+ historyManager_.navigateHistory(offset);
+ display_.ensureInputVisible();
+ }
+
+ private void consolePrompt(String prompt)
+ {
+ // determine whether we should add this to the history
+ boolean addToHistory = false;
+
+ if (historyEnabled_)
+ {
+ // figure out what the suffix of the default prompt is by inspecting
+ // the first prompt which comes our way
+ if (defaultPromptSuffix_ == null)
+ {
+ if (prompt.length() > 1)
+ defaultPromptSuffix_ = prompt.substring(prompt.length()-2);
+ else if (prompt.length() > 0)
+ defaultPromptSuffix_ = prompt;
+
+ addToHistory = true;
+ }
+ else if (prompt.endsWith(defaultPromptSuffix_))
+ {
+ addToHistory = true;
+ }
+ }
+
+ consolePrompt(prompt, addToHistory);
+ }
+
+ private void consolePrompt(String prompt, boolean addToHistory)
+ {
+ boolean showInput = showInputForPrompt(prompt);
+ display_.consolePrompt(prompt, showInput) ;
+
+ addToHistory_ = addToHistory && showInput;
+ historyManager_.resetPosition();
+ lastPromptText_ = prompt ;
+
+ // set focus on the first prompt
+ if (!firstPromptShown_)
+ {
+ firstPromptShown_ = true;
+ display_.getInputEditorDisplay().setFocus(true);
+ }
+ }
+
+ private boolean showInputForPrompt(String prompt)
+ {
+ String promptLower = prompt.trim().toLowerCase();
+ boolean hasPassword = promptLower.contains("password") ||
+ promptLower.contains("passphrase");
+
+
+ // if there is no password or passphrase then show input
+ if (!hasPassword)
+ {
+ return true;
+ }
+ else
+ {
+ // detect yes/no prompt and make that an exception (subversion
+ // does a yes/no for asking whether to store the password unencrypted)
+ boolean hasYesNo = promptLower.endsWith("(yes/no)?") ||
+ promptLower.endsWith("(y/n)?");
+ return hasYesNo;
+ }
+ }
+
+ private final class InputKeyDownHandler implements KeyDownHandler
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ int keyCode = event.getNativeKeyCode();
+ int modifiers = KeyboardShortcut.getModifierValue(
+ event.getNativeEvent());
+
+ if (historyEnabled_ && event.isUpArrow() && modifiers == 0)
+ {
+ InputEditorDisplay input = display_.getInputEditorDisplay();
+ if (input.getCurrentLineNum() == 0)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ navigateHistory(-1);
+ }
+ }
+ else if (historyEnabled_ && event.isDownArrow() && modifiers == 0)
+ {
+ InputEditorDisplay input = display_.getInputEditorDisplay();
+ if (input.getCurrentLineNum() == input.getCurrentLineCount() - 1)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ navigateHistory(1);
+ }
+ }
+ else if (keyCode == KeyCodes.KEY_ENTER && modifiers == 0)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ processInput(inputHandler_);
+ }
+ else if (modifiers == KeyboardShortcut.CTRL && keyCode == 'C')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (display_.isPromptEmpty())
+ display_.consoleWriteOutput("^C");
+
+ inputHandler_.execute(ShellInput.createInterrupt());
+ }
+ else if (modifiers == KeyboardShortcut.CTRL && keyCode == 'L')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ display_.clearOutput();
+ }
+ }
+ }
+
+ private void encryptInput(final String input,
+ final CommandWithArg<String> onInputReady)
+ {
+ if (Desktop.isDesktop())
+ {
+ onInputReady.execute(input);
+ }
+ else if (publicKeyInfo_ != null)
+ {
+ RSAEncrypt.encrypt_ServerOnly(publicKeyInfo_, input, onInputReady);
+ }
+ else
+ {
+ server_.getPublicKey(new ServerRequestCallback<PublicKeyInfo>() {
+
+ @Override
+ public void onResponseReceived(PublicKeyInfo publicKeyInfo)
+ {
+ publicKeyInfo_ = publicKeyInfo;
+ RSAEncrypt.encrypt_ServerOnly(publicKeyInfo_,
+ input,
+ onInputReady);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ consoleWriteError(error.getUserMessage());
+ }
+
+ });
+ }
+
+ }
+
+
+ private final ShellDisplay display_;
+
+ private boolean addToHistory_ ;
+ private boolean historyEnabled_ = true;
+ private String lastPromptText_ ;
+ private String defaultPromptSuffix_ = null;
+
+ private boolean firstPromptShown_ = false;
+
+ private final InputEditorDisplay input_ ;
+ private final CommandLineHistory historyManager_;
+
+ private final CommandWithArg<ShellInput> inputHandler_;
+
+ private final CryptoServerOperations server_;
+ private PublicKeyInfo publicKeyInfo_ = null;
+
+ /* Hack to fix echoing problems on Windows.
+ * For echoed input like username, Windows always echoes input back to the
+ * client. We don't have a good way to avoid this happening on the server,
+ * nor can we simply not echo locally on the client because there is a
+ * several-hundred-millisecond delay between when we send input and when the
+ * server echoes it back to us (normally would be a much shorter delay but
+ * consoleio.exe makes it longer due to console polling instead of
+ * streaming). Therefore, we echo the input locally, and then look for the
+ * same string at the head of the next output event. If we find it, we strip
+ * it off.
+ */
+ private String outputPrefixToSuppress_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shell/ShellOutputWriter.java b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellOutputWriter.java
new file mode 100644
index 0000000..7e9f95c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellOutputWriter.java
@@ -0,0 +1,27 @@
+/*
+ * ShellOutputWriter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shell;
+
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+
+public interface ShellOutputWriter
+{
+ void consoleWriteError(String string);
+ void consoleWriteExtendedError(
+ String string, UnhandledError traceInfo,
+ boolean expand, String command);
+ void consoleWriteOutput(String output) ;
+ void consoleWritePrompt(String prompt);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shell/ShellWidget.java b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellWidget.java
new file mode 100644
index 0000000..597b363
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shell/ShellWidget.java
@@ -0,0 +1,805 @@
+/*
+ * ShellWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shell;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Text;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.VirtualConsole;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.core.client.widget.BottomScrollPanel;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.PreWidget;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.debugging.model.ErrorFrame;
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+import org.rstudio.studio.client.common.debugging.ui.ConsoleError;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.workbench.model.ConsoleAction;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+import org.rstudio.studio.client.workbench.views.console.events.RunCommandWithDebugEvent;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor.NewLineMode;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.PasteEvent;
+
+public class ShellWidget extends Composite implements ShellDisplay,
+ RequiresResize,
+ ConsoleError.Observer
+{
+ public ShellWidget(AceEditor editor, EventBus events)
+ {
+ styles_ = ConsoleResources.INSTANCE.consoleStyles();
+ events_ = events;
+
+ SelectInputClickHandler secondaryInputHandler = new SelectInputClickHandler();
+
+ output_ = new PreWidget();
+ output_.setStylePrimaryName(styles_.output());
+ output_.addClickHandler(secondaryInputHandler);
+ ElementIds.assignElementId(output_.getElement(),
+ ElementIds.CONSOLE_OUTPUT);
+ output_.addPasteHandler(secondaryInputHandler);
+
+ pendingInput_ = new PreWidget();
+ pendingInput_.setStyleName(styles_.output());
+ pendingInput_.addClickHandler(secondaryInputHandler);
+
+ prompt_ = new HTML() ;
+ prompt_.setStylePrimaryName(styles_.prompt()) ;
+ prompt_.addStyleName(KEYWORD_CLASS_NAME);
+
+ input_ = editor ;
+ input_.setShowLineNumbers(false);
+ input_.setShowPrintMargin(false);
+ if (!Desktop.isDesktop())
+ input_.setNewLineMode(NewLineMode.Unix);
+ input_.setUseWrapMode(true);
+ input_.setPadding(0);
+ input_.autoHeight();
+ final Widget inputWidget = input_.asWidget();
+ ElementIds.assignElementId(inputWidget.getElement(),
+ ElementIds.CONSOLE_INPUT);
+ input_.addClickHandler(secondaryInputHandler) ;
+ inputWidget.addStyleName(styles_.input());
+ input_.addCursorChangedHandler(new CursorChangedHandler()
+ {
+ public void onCursorChanged(CursorChangedEvent event)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ input_.scrollToCursor(scrollPanel_, 8, 60);
+ }
+ });
+ }
+ });
+ input_.addCapturingKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // If the user hits Page-Up from inside the console input, we need
+ // to simulate pageup because focus is not contained in the scroll
+ // panel (it's in the hidden textarea that Ace uses under the
+ // covers).
+
+ int keyCode = event.getNativeKeyCode();
+ switch (keyCode)
+ {
+ case KeyCodes.KEY_PAGEUP:
+ event.stopPropagation();
+ event.preventDefault();
+
+ // Can't scroll any further up. Return before we change focus.
+ if (scrollPanel_.getVerticalScrollPosition() == 0)
+ return;
+
+ scrollPanel_.focus();
+ int newScrollTop = scrollPanel_.getVerticalScrollPosition() -
+ scrollPanel_.getOffsetHeight() + 40;
+ scrollPanel_.setVerticalScrollPosition(Math.max(0, newScrollTop));
+ break;
+ }
+ }
+ });
+
+ inputLine_ = new DockPanel();
+ inputLine_.setHorizontalAlignment(DockPanel.ALIGN_LEFT);
+ inputLine_.setVerticalAlignment(DockPanel.ALIGN_TOP);
+ inputLine_.add(prompt_, DockPanel.WEST);
+ inputLine_.setCellWidth(prompt_, "1");
+ inputLine_.add(input_.asWidget(), DockPanel.CENTER);
+ inputLine_.setCellWidth(input_.asWidget(), "100%");
+ inputLine_.setWidth("100%");
+
+ verticalPanel_ = new VerticalPanel() ;
+ verticalPanel_.setStylePrimaryName(styles_.console());
+ verticalPanel_.addStyleName("ace_text-layer");
+ verticalPanel_.addStyleName("ace_line");
+ FontSizer.applyNormalFontSize(verticalPanel_);
+ verticalPanel_.add(output_) ;
+ verticalPanel_.add(pendingInput_) ;
+ verticalPanel_.add(inputLine_) ;
+ verticalPanel_.setWidth("100%") ;
+
+ scrollPanel_ = new ClickableScrollPanel() ;
+ scrollPanel_.setWidget(verticalPanel_) ;
+ scrollPanel_.addStyleName("ace_editor");
+ scrollPanel_.addStyleName("ace_scroller");
+ scrollPanel_.addClickHandler(secondaryInputHandler);
+ scrollPanel_.addKeyDownHandler(secondaryInputHandler);
+
+ secondaryInputHandler.setInput(editor);
+
+ scrollToBottomCommand_ = new TimeBufferedCommand(5)
+ {
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ if (!DomUtils.selectionExists())
+ scrollPanel_.scrollToBottom();
+ }
+ };
+
+ initWidget(scrollPanel_) ;
+
+ addCopyHook(getElement());
+ }
+
+ private native void addCopyHook(Element element) /*-{
+ if ($wnd.desktop) {
+ var clean = function() {
+ setTimeout(function() {
+ $wnd.desktop.cleanClipboard(true);
+ }, 100)
+ };
+ element.addEventListener("copy", clean, true);
+ element.addEventListener("cut", clean, true);
+ }
+ }-*/;
+
+
+ public void scrollToBottom()
+ {
+ scrollPanel_.scrollToBottom();
+ }
+
+ private boolean initialized_ = false;
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ if (!initialized_)
+ {
+ initialized_ = true;
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ doOnLoad();
+ scrollPanel_.scrollToBottom();
+ }
+ });
+ }
+
+ ElementIds.assignElementId(this.getElement(), ElementIds.SHELL_WIDGET);
+ }
+
+ protected void doOnLoad()
+ {
+ input_.autoHeight();
+ // Console scroll pos jumps on first typing without this, because the
+ // textarea is in the upper left corner of the screen and when focus
+ // moves to it scrolling ensues.
+ input_.forceCursorChange();
+ }
+
+ public void setSuppressPendingInput(boolean suppressPendingInput)
+ {
+ suppressPendingInput_ = suppressPendingInput;
+ }
+
+ public void consoleWriteError(final String error)
+ {
+ clearPendingInput();
+ output(error, getErrorClass(), false);
+
+ // Pick up the last element emitted to the console. If we get extended
+ // information for this error, we'll need to swap out the simple error
+ // element for the extended error element.
+ Element outputElement = output_.getElement();
+ Node errorNode = outputElement.getChild(
+ outputElement.getChildCount() - 1);
+ if (clearErrors_)
+ {
+ errorNodes_.clear();
+ clearErrors_ = false;
+ }
+ errorNodes_.put(error, errorNode);
+ }
+
+ public void consoleWriteExtendedError(
+ final String error, UnhandledError traceInfo,
+ boolean expand, String command)
+ {
+ if (errorNodes_.containsKey(error))
+ {
+ Node errorNode = errorNodes_.get(error);
+ clearPendingInput();
+ ConsoleError errorWidget = new ConsoleError(
+ traceInfo, getErrorClass(), this, command);
+
+ if (expand)
+ errorWidget.setTracebackVisible(true);
+
+ // The widget must be added to the root panel to have its event handlers
+ // wired properly, but this isn't an ideal structure; consider showing
+ // console output as cell widgets in a virtualized scrolling CellTable
+ // so we can easily add arbitrary controls.
+ RootPanel.get().add(errorWidget);
+ output_.getElement().replaceChild(errorWidget.getElement(),
+ errorNode);
+
+ scrollPanel_.onContentSizeChanged();
+ errorNodes_.remove(error);
+ }
+ }
+
+ @Override
+ public void showSourceForFrame(ErrorFrame frame)
+ {
+ if (events_ == null)
+ return;
+ FileSystemItem sourceFile = FileSystemItem.createFile(
+ frame.getFileName());
+ events_.fireEvent(new OpenSourceFileEvent(sourceFile,
+ FilePosition.create(
+ frame.getLineNumber(),
+ frame.getCharacterNumber()),
+ FileTypeRegistry.R,
+ NavigationMethod.HighlightLine));
+ }
+
+ @Override
+ public void runCommandWithDebug(String command)
+ {
+ events_.fireEvent(new RunCommandWithDebugEvent(command));
+ }
+
+ public void consoleWriteOutput(final String output)
+ {
+ clearPendingInput();
+ output(output, styles_.output(), false);
+ }
+
+ public void consoleWriteInput(final String input)
+ {
+ clearPendingInput();
+ output(input, styles_.command() + KEYWORD_CLASS_NAME, false);
+ }
+
+ private void clearPendingInput()
+ {
+ pendingInput_.setText("");
+ pendingInput_.setVisible(false);
+ }
+
+ public void consoleWritePrompt(final String prompt)
+ {
+ output(prompt, styles_.prompt() + KEYWORD_CLASS_NAME, false);
+ clearErrors_ = true;
+ }
+
+ public void consolePrompt(String prompt, boolean showInput)
+ {
+ if (prompt != null)
+ prompt = VirtualConsole.consolify(prompt);
+
+ prompt_.getElement().setInnerText(prompt);
+ //input_.clear() ;
+ ensureInputVisible();
+
+ // Deal gracefully with multi-line prompts
+ int promptLines = StringUtil.notNull(prompt).split("\\n").length;
+ input_.asWidget().getElement().getStyle().setPaddingTop((promptLines - 1) * 15,
+ Unit.PX);
+
+ input_.setPasswordMode(!showInput);
+ clearErrors_ = true;
+ }
+
+ public void ensureInputVisible()
+ {
+ scrollPanel_.scrollToBottom();
+ }
+
+ private String getErrorClass()
+ {
+ return styles_.error() + " " +
+ RStudioGinjector.INSTANCE.getUIPrefs().getThemeErrorClass();
+ }
+
+ private boolean output(String text,
+ String className,
+ boolean addToTop)
+ {
+ if (text.indexOf('\f') >= 0)
+ clearOutput();
+
+ Node node;
+ boolean isOutput = StringUtil.isNullOrEmpty(className)
+ || className.equals(styles_.output());
+
+ if (isOutput && !addToTop && trailingOutput_ != null)
+ {
+ // Short-circuit the case where we're appending output to the
+ // bottom, and there's already some output there. We need to
+ // treat this differently in case the new output uses control
+ // characters to pound over parts of the previous output.
+
+ int oldLineCount = DomUtils.countLines(trailingOutput_, true);
+ trailingOutputConsole_.submit(text);
+ trailingOutput_.setNodeValue(
+ ensureNewLine(trailingOutputConsole_.toString()));
+ int newLineCount = DomUtils.countLines(trailingOutput_, true);
+ lines_ += newLineCount - oldLineCount;
+ }
+ else
+ {
+ Element outEl = output_.getElement();
+
+ text = VirtualConsole.consolify(text);
+ if (isOutput)
+ {
+ VirtualConsole console = new VirtualConsole();
+ console.submit(text);
+ String consoleSnapshot = console.toString();
+
+ // We use ensureNewLine to make sure that even if output
+ // doesn't end with \n, a prompt will appear on its own line.
+ // However, if we call ensureNewLine indiscriminantly (i.e.
+ // on an output that's going to be followed by another output)
+ // we can end up inserting newlines where they don't belong.
+ //
+ // It's safe to add a newline when we're appending output to
+ // the end of the console, because if the next append is also
+ // output, we'll use the contents of VirtualConsole and the
+ // newline we add here will be plowed over.
+ //
+ // If we're prepending output to the top of the console, then
+ // it's safe to add a newline if the next chunk (which is already
+ // there) is something besides output.
+ if (!addToTop ||
+ (!outEl.hasChildNodes()
+ || outEl.getFirstChild().getNodeType() != Node.TEXT_NODE))
+ {
+ consoleSnapshot = ensureNewLine(consoleSnapshot);
+ }
+
+ node = Document.get().createTextNode(consoleSnapshot);
+ if (!addToTop)
+ {
+ trailingOutput_ = (Text) node;
+ trailingOutputConsole_ = console;
+ }
+ }
+ else
+ {
+ SpanElement span = Document.get().createSpanElement();
+ span.setClassName(className);
+ span.setInnerText(text);
+ node = span;
+ if (!addToTop)
+ {
+ trailingOutput_ = null;
+ trailingOutputConsole_ = null;
+ }
+ }
+
+ if (addToTop)
+ outEl.insertFirst(node);
+ else
+ outEl.appendChild(node);
+
+ lines_ += DomUtils.countLines(node, true);
+ }
+ boolean result = !trimExcess();
+
+ scrollPanel_.onContentSizeChanged();
+ if (scrollPanel_.isScrolledToBottom())
+ scrollToBottomCommand_.nudge();
+
+ return result;
+ }
+
+ private String ensureNewLine(String s)
+ {
+ if (s.length() == 0 || s.charAt(s.length() - 1) == '\n')
+ return s;
+ else
+ return s + '\n';
+ }
+
+ private boolean trimExcess()
+ {
+ if (maxLines_ <= 0)
+ return false; // No limit in effect
+
+ int linesToTrim = lines_ - maxLines_;
+ if (linesToTrim > 0)
+ {
+ lines_ -= DomUtils.trimLines(output_.getElement(),
+ lines_ - maxLines_);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void playbackActions(final RpcObjectList<ConsoleAction> actions)
+ {
+ Scheduler.get().scheduleIncremental(new RepeatingCommand()
+ {
+ private int i = actions.length() - 1;
+ private int chunksize = 1000;
+
+ public boolean execute()
+ {
+ int end = i - chunksize;
+ chunksize = 10;
+ for (; i > end && i >= 0; i--)
+ {
+ // User hit Ctrl+L at some point--we're done.
+ if (cleared_)
+ return false;
+
+ boolean canContinue = false;
+
+ ConsoleAction action = actions.get(i);
+ switch (action.getType())
+ {
+ case ConsoleAction.INPUT:
+ canContinue = output(action.getData() + "\n",
+ styles_.command() + " " + KEYWORD_CLASS_NAME,
+ true);
+ break;
+ case ConsoleAction.OUTPUT:
+ canContinue = output(action.getData(),
+ styles_.output(),
+ true);
+ break;
+ case ConsoleAction.ERROR:
+ canContinue = output(action.getData(),
+ styles_.error(),
+ true);
+ break;
+ case ConsoleAction.PROMPT:
+ canContinue = output(action.getData(),
+ styles_.prompt() + " " + KEYWORD_CLASS_NAME,
+ true);
+ break;
+ }
+ if (!canContinue)
+ return false;
+ }
+ if (!DomUtils.selectionExists())
+ scrollPanel_.scrollToBottom();
+
+ return i >= 0;
+ }
+ });
+ }
+
+ public void focus()
+ {
+ input_.setFocus(true) ;
+ }
+
+ /**
+ * Directs focus/selection to the input box when a (different) widget
+ * is clicked.
+ */
+ private class SelectInputClickHandler implements ClickHandler,
+ KeyDownHandler,
+ PasteEvent.Handler
+ {
+ public void onClick(ClickEvent event)
+ {
+ // If clicking on the input panel already, stop propagation.
+ if (event.getSource() == input_)
+ {
+ event.stopPropagation();
+ return;
+ }
+
+ // Don't drive focus to the input unless there is no selection.
+ // Otherwise it would interfere with the ability to select stuff
+ // from the output buffer for copying to the clipboard.
+ // (BUG: DomUtils.selectionExists() doesn't work in a timely
+ // fashion on IE8.)
+ if (!DomUtils.selectionExists() && isInputOnscreen())
+ {
+ input_.setFocus(true) ;
+ DomUtils.collapseSelection(false);
+ }
+ }
+
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getSource() == input_)
+ return;
+
+ // Filter out some keystrokes you might reasonably expect to keep
+ // focus inside the output pane
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_PAGEDOWN:
+ case KeyCodes.KEY_PAGEUP:
+ case KeyCodes.KEY_HOME:
+ case KeyCodes.KEY_END:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_ALT:
+ case KeyCodes.KEY_SHIFT:
+ case 224: // META (Command) on Firefox/Mac
+ return;
+ case 91: case 93: // Left/Right META (Command), but also [ and ], on Safari
+ if (event.isMetaKeyDown())
+ return;
+ break;
+ case 'C':
+ if (event.isControlKeyDown() || event.isMetaKeyDown())
+ return;
+ break;
+ }
+ input_.setFocus(true);
+ delegateEvent(input_.asWidget(), event);
+ }
+
+ public void onPaste(PasteEvent event)
+ {
+ // When pasting, focus the input so it'll receive the pasted text
+ input_.setFocus(true);
+ }
+
+ public void setInput(AceEditor input)
+ {
+ input_ = input;
+ }
+
+ private AceEditor input_;
+ }
+
+ private boolean isInputOnscreen()
+ {
+ return DomUtils.isVisibleVert(scrollPanel_.getElement(),
+ inputLine_.getElement());
+ }
+
+ protected class ClickableScrollPanel extends BottomScrollPanel
+ {
+ private ClickableScrollPanel()
+ {
+ super();
+ getElement().setTabIndex(-1);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public void focus()
+ {
+ getElement().focus();
+ }
+ }
+
+ public void clearOutput()
+ {
+ output_.setText("") ;
+ lines_ = 0;
+ cleared_ = true;
+ trailingOutput_ = null;
+ trailingOutputConsole_ = null;
+ }
+
+ public InputEditorDisplay getInputEditorDisplay()
+ {
+ return input_ ;
+ }
+
+ public String processCommandEntry()
+ {
+ // parse out the command text
+ String promptText = prompt_.getElement().getInnerText();
+ String commandText = input_.getCode();
+ input_.setText("");
+ // Force render to avoid subtle command movement in the console, caused
+ // by the prompt disappearing before the input line does
+ input_.forceImmediateRender();
+ prompt_.setHTML("");
+
+ SpanElement pendingPrompt = Document.get().createSpanElement();
+ pendingPrompt.setInnerText(promptText);
+ pendingPrompt.setClassName(styles_.prompt() + " " + KEYWORD_CLASS_NAME);
+
+ if (!suppressPendingInput_ && !input_.isPasswordMode())
+ {
+ SpanElement pendingInput = Document.get().createSpanElement();
+ String[] lines = StringUtil.notNull(commandText).split("\n");
+ String firstLine = lines.length > 0 ? lines[0] : "";
+ pendingInput.setInnerText(firstLine + "\n");
+ pendingInput.setClassName(styles_.command() + " " + KEYWORD_CLASS_NAME);
+ pendingInput_.getElement().appendChild(pendingPrompt);
+ pendingInput_.getElement().appendChild(pendingInput);
+ pendingInput_.setVisible(true);
+ }
+
+ ensureInputVisible();
+
+ return commandText ;
+ }
+
+ public HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler)
+ {
+ return input_.addCapturingKeyDownHandler(handler) ;
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
+ {
+ return input_.addKeyPressHandler(handler) ;
+ }
+
+ public int getCharacterWidth()
+ {
+ // create width checker label and add it to the root panel
+ Label widthChecker = new Label();
+ widthChecker.setStylePrimaryName(styles_.console());
+ FontSizer.applyNormalFontSize(widthChecker);
+ RootPanel.get().add(widthChecker, -1000, -1000);
+
+ // put the text into the label, measure it, and remove it
+ String text = new String("abcdefghijklmnopqrstuvwzyz0123456789");
+ widthChecker.setText(text);
+ int labelWidth = widthChecker.getOffsetWidth();
+ RootPanel.get().remove(widthChecker);
+
+ // compute the points per character
+ int pointsPerCharacter = labelWidth / text.length();
+
+ // compute client width
+ int clientWidth = getElement().getClientWidth();
+ int offsetWidth = getOffsetWidth();
+ if (clientWidth == offsetWidth)
+ {
+ // if the two widths are the same then there are no scrollbars.
+ // however, we know there will eventually be a scrollbar so we
+ // should offset by an estimated amount
+ // (is there a more accurate way to estimate this?)
+ final int ESTIMATED_SCROLLBAR_WIDTH = 19;
+ clientWidth -= ESTIMATED_SCROLLBAR_WIDTH;
+ }
+
+ // compute character width (add pad so characters aren't flush to right)
+ final int RIGHT_CHARACTER_PAD = 2;
+ int width = (clientWidth / pointsPerCharacter) - RIGHT_CHARACTER_PAD ;
+
+ // enforce a minimum width
+ final int MINIMUM_WIDTH = 30;
+ return Math.max(width, MINIMUM_WIDTH);
+ }
+
+ public boolean isPromptEmpty()
+ {
+ return StringUtil.isNullOrEmpty(prompt_.getText());
+ }
+
+ public String getPromptText()
+ {
+ return StringUtil.notNull(prompt_.getText());
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ input_.setReadOnly(readOnly);
+ }
+
+ public int getMaxOutputLines()
+ {
+ return maxLines_;
+ }
+
+ public void setMaxOutputLines(int maxLines)
+ {
+ maxLines_ = maxLines;
+ trimExcess();
+ }
+
+ @Override
+ public Widget getShellWidget()
+ {
+ return this;
+ }
+
+ public void onResize()
+ {
+ if (getWidget() instanceof RequiresResize)
+ ((RequiresResize)getWidget()).onResize();
+ }
+
+ @Override
+ public void onErrorBoxResize()
+ {
+ scrollPanel_.onContentSizeChanged();
+ }
+
+ private int lines_ = 0;
+ private int maxLines_ = -1;
+ private boolean cleared_ = false;
+ private final PreWidget output_ ;
+ private PreWidget pendingInput_ ;
+ // Save a reference to the most recent output text node in case the
+ // next bit of output contains \b or \r control characters
+ private Text trailingOutput_ ;
+ private VirtualConsole trailingOutputConsole_ ;
+ private final HTML prompt_ ;
+ protected final AceEditor input_ ;
+ private final DockPanel inputLine_ ;
+ private final VerticalPanel verticalPanel_ ;
+ protected final ClickableScrollPanel scrollPanel_ ;
+ private ConsoleResources.ConsoleStyles styles_;
+ private final TimeBufferedCommand scrollToBottomCommand_;
+ private boolean suppressPendingInput_;
+ private final EventBus events_;
+
+ // A list of errors that have occurred between console prompts.
+ private Map<String, Node> errorNodes_ = new TreeMap<String, Node>();
+ private boolean clearErrors_ = false;
+
+ private static final String KEYWORD_CLASS_NAME = ConsoleResources.KEYWORD_CLASS_NAME;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyAppsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyAppsServerOperations.java
new file mode 100644
index 0000000..bc9dbb0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyAppsServerOperations.java
@@ -0,0 +1,41 @@
+/*
+ * ShinyAppsServerOperations.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shiny.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.shiny.model.ShinyAppsApplicationInfo;
+import org.rstudio.studio.client.shiny.model.ShinyAppsDeploymentRecord;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+public interface ShinyAppsServerOperations
+{
+ void removeShinyAppsAccount(String accountName,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getShinyAppsAccountList(
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void connectShinyAppsAccount(String command,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getShinyAppsAppList(String accountName,
+ ServerRequestCallback<JsArray<ShinyAppsApplicationInfo>> requestCallback);
+
+ void getShinyAppsDeployments(String dir,
+ ServerRequestCallback<JsArray<ShinyAppsDeploymentRecord>> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyCapabilities.java b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyCapabilities.java
new file mode 100644
index 0000000..c8c0454
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyCapabilities.java
@@ -0,0 +1,39 @@
+/*
+ * ShinyCapabilities.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyCapabilities extends JavaScriptObject
+{
+ protected ShinyCapabilities()
+ {
+ }
+
+ public static final native ShinyCapabilities createDefault() /*-{
+ var caps = new Object();
+ caps.installed = false ;
+ return caps;
+ }-*/;
+
+ public native final boolean getInstalled() /*-{
+ return this.installed;
+ }-*/;
+
+ public final boolean hasAllCapabilities()
+ {
+ return getInstalled();
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyServerOperations.java
new file mode 100644
index 0000000..2bb2721
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/shiny/model/ShinyServerOperations.java
@@ -0,0 +1,37 @@
+/*
+ * ShinyServerOperations.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.shiny.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.shiny.model.ShinyRunCmd;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+
+public interface ShinyServerOperations
+{
+ void getShinyCapabilities(
+ ServerRequestCallback<ShinyCapabilities> requestCallback);
+
+ void getShinyViewerType(
+ ServerRequestCallback<ShinyViewerType> requestCallback);
+
+ void setShinyViewerType(
+ int viewerType,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getShinyRunCmd(
+ String shinyAppDir,
+ ServerRequestCallback<ShinyRunCmd> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellChecker.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellChecker.java
new file mode 100644
index 0000000..8632b50
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellChecker.java
@@ -0,0 +1,237 @@
+/*
+ * SpellChecker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.spelling;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.spelling.model.SpellCheckerResult;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchList;
+import org.rstudio.studio.client.workbench.WorkbenchListManager;
+import org.rstudio.studio.client.workbench.events.ListChangedEvent;
+import org.rstudio.studio.client.workbench.events.ListChangedHandler;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class SpellChecker
+{
+ public interface Context
+ {
+ ArrayList<String> readDictionary();
+ void writeDictionary(ArrayList<String> words);
+
+ void invalidateAllWords();
+ void invalidateMisspelledWords();
+
+ void releaseOnDismiss(HandlerRegistration handler);
+ }
+
+ public SpellChecker(Context context)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ // save reference to context and read its dictionary
+ context_ = context;
+ contextDictionary_ = context_.readDictionary();
+
+
+ // subscribe to spelling service changes (these occur when when the
+ // spelling dictionaries are changed)
+ context_.releaseOnDismiss(spellingService_.addChangeHandler(
+ new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ context_.invalidateAllWords();
+ }
+ }));
+
+ // subscribe to spelling prefs changes (invalidateAll on changes)
+ uiPrefs_.ignoreWordsInUppercase().addValueChangeHandler(
+ prefChangedHandler_);
+ uiPrefs_.ignoreWordsWithNumbers().addValueChangeHandler(
+ prefChangedHandler_);
+
+ // subscribe to user dictionary changes
+ context_.releaseOnDismiss(userDictionary_.addListChangedHandler(
+ new ListChangedHandler() {
+ @Override
+ public void onListChanged(ListChangedEvent event)
+ {
+ // detect whether this is the first delivery of the list
+ // or if it is an update
+ boolean isUpdate = userDictionaryWords_ != null;
+
+ userDictionaryWords_ = event.getList();
+
+ updateIgnoredWordsIndex();
+
+ if (isUpdate)
+ context_.invalidateMisspelledWords();
+ }
+ }));
+ }
+
+ @Inject
+ void intialize(SpellingService spellingService,
+ WorkbenchListManager workbenchListManager,
+ UIPrefs uiPrefs)
+ {
+ spellingService_ = spellingService;
+ userDictionary_ = workbenchListManager.getUserDictionaryList();
+ uiPrefs_ = uiPrefs;
+ }
+
+ public void checkSpelling(
+ List<String> words,
+ final ServerRequestCallback<SpellCheckerResult> callback)
+ {
+ // allocate results
+ final SpellCheckerResult spellCheckerResult = new SpellCheckerResult();
+
+ if (words.isEmpty())
+ {
+ callback.onResponseReceived(spellCheckerResult);
+ return;
+ }
+
+ // only send words to the server that aren't ignored
+ final ArrayList<String> wordsToCheck = new ArrayList<String>();
+ for (int i = 0; i<words.size(); i++)
+ {
+ String word = words.get(i);
+ if (isWordIgnored(word))
+ spellCheckerResult.getCorrect().add(word);
+ else
+ wordsToCheck.add(word);
+ }
+
+ // call the service to check the non-ignored words
+ spellingService_.checkSpelling(
+ wordsToCheck,
+ new ServerRequestCallback<SpellCheckerResult>() {
+
+ @Override
+ public void onResponseReceived(SpellCheckerResult result)
+ {
+ spellCheckerResult.getCorrect().addAll(result.getCorrect());
+ spellCheckerResult.getIncorrect().addAll(result.getIncorrect());
+ callback.onResponseReceived(spellCheckerResult);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onError(error);
+ }
+
+ });
+ }
+
+ public void suggestionList(String word,
+ ServerRequestCallback<JsArrayString> callback)
+ {
+ spellingService_.suggestionList(word, callback);
+ }
+
+ public void addToUserDictionary(final String word)
+ {
+ userDictionary_.append(word);
+ }
+
+ public void addIgnoredWord(String word)
+ {
+ contextDictionary_.add(word);
+ context_.writeDictionary(contextDictionary_);
+
+ updateIgnoredWordsIndex();
+
+ context_.invalidateMisspelledWords();
+ }
+
+ private boolean isWordIgnored(String word)
+ {
+ if (allIgnoredWords_.contains(word))
+ return true;
+ else if (ignoreUppercaseWord(word))
+ return true;
+ else if (ignoreWordWithNumbers(word))
+ return true;
+ else
+ return false;
+ }
+
+ private boolean ignoreUppercaseWord(String word)
+ {
+ if (!uiPrefs_.ignoreWordsInUppercase().getValue())
+ return false;
+
+ for (char c: word.toCharArray())
+ {
+ if(!Character.isUpperCase(c))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean ignoreWordWithNumbers(String word)
+ {
+ if (!uiPrefs_.ignoreWordsWithNumbers().getValue())
+ return false;
+
+ for (char c: word.toCharArray())
+ {
+ if(Character.isDigit(c))
+ return true;
+ }
+ return false;
+ }
+
+ private void updateIgnoredWordsIndex()
+ {
+ allIgnoredWords_.clear();
+ allIgnoredWords_.addAll(userDictionaryWords_);
+ allIgnoredWords_.addAll(contextDictionary_);
+ }
+
+ private ValueChangeHandler<Boolean> prefChangedHandler_ =
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ context_.invalidateAllWords();
+ }
+ };
+
+ private final Context context_;
+
+ private WorkbenchList userDictionary_;
+ private ArrayList<String> userDictionaryWords_;
+ private ArrayList<String> contextDictionary_ = new ArrayList<String>();
+ private final HashSet<String> allIgnoredWords_ = new HashSet<String>();
+
+ private SpellingService spellingService_;
+ private UIPrefs uiPrefs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellingService.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellingService.java
new file mode 100644
index 0000000..e418920
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/SpellingService.java
@@ -0,0 +1,233 @@
+/*
+ * SpellingService.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.spelling;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.common.spelling.model.SpellCheckerResult;
+import org.rstudio.studio.client.common.spelling.model.SpellingServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.prefs.model.SpellingPrefsContext;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.HasChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class SpellingService implements HasChangeHandlers
+{
+ @Inject
+ public SpellingService(SpellingServerOperations server,
+ UIPrefs uiPrefs)
+ {
+ server_ = server;
+ uiPrefs_ = uiPrefs;
+
+ uiPrefs.spellingDictionaryLanguage().addValueChangeHandler(
+ new ValueChangeHandler<String>(){
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ invalidateCache();
+ }
+ });
+
+ uiPrefs.spellingCustomDictionaries().addValueChangeHandler(
+ new ValueChangeHandler<JsArrayString>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<JsArrayString> event)
+ {
+ invalidateCache();
+ }
+ });
+ }
+
+ public void checkSpelling(
+ List<String> words,
+ final ServerRequestCallback<SpellCheckerResult> callback)
+ {
+ // results to return
+ final SpellCheckerResult spellCheckerResult = new SpellCheckerResult();
+
+ // only send words to the server that aren't in the cache
+ final ArrayList<String> wordsToCheck = new ArrayList<String>();
+ for (int i = 0; i<words.size(); i++)
+ {
+ String word = words.get(i);
+ Boolean isCorrect = previousResults_.get(word);
+ if (isCorrect != null)
+ {
+ if (isCorrect)
+ spellCheckerResult.getCorrect().add(word);
+ else
+ spellCheckerResult.getIncorrect().add(word);
+ }
+ else
+ {
+ wordsToCheck.add(word);
+ }
+ }
+
+ // if there are no words to check then return
+ if (wordsToCheck.size() == 0)
+ {
+ callback.onResponseReceived(spellCheckerResult);
+ return;
+ }
+
+ // hit the server
+ server_.checkSpelling(JsUtil.toJsArrayString(wordsToCheck),
+ new ServerRequestCallback<JsArrayInteger>() {
+
+ @Override
+ public void onResponseReceived(JsArrayInteger result)
+ {
+ // get misspelled indexes
+ ArrayList<Integer> misspelledIndexes = new ArrayList<Integer>();
+ for (int i=0; i<result.length(); i++)
+ misspelledIndexes.add(result.get(i));
+
+ // determine correct/incorrect status and populate result & cache
+ for (int i=0; i<wordsToCheck.size(); i++)
+ {
+ String word = wordsToCheck.get(i);
+ if (misspelledIndexes.contains(i))
+ {
+ spellCheckerResult.getIncorrect().add(word);
+ previousResults_.put(word, false);
+ }
+ else
+ {
+ spellCheckerResult.getCorrect().add(word);
+ previousResults_.put(word, true);
+ }
+ }
+
+ // return result
+ callback.onResponseReceived(spellCheckerResult);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onError(error);
+ }
+ });
+ }
+
+ public void suggestionList(String word,
+ ServerRequestCallback<JsArrayString> callback)
+ {
+ server_.suggestionList(word, callback);
+ }
+
+ public void addCustomDictionary(
+ String dictPath,
+ final ServerRequestCallback<JsArrayString> callback)
+ {
+ server_.addCustomDictionary(dictPath, new CustomDictCallback(callback));
+ }
+
+ public void removeCustomDictionary(
+ String name,
+ ServerRequestCallback<JsArrayString> callback)
+ {
+ server_.removeCustomDictionary(name, new CustomDictCallback(callback));
+ }
+
+ public void installAllDictionaries(
+ ServerRequestCallback<SpellingPrefsContext> requestCallback)
+ {
+ server_.installAllDictionaries(requestCallback);
+ }
+
+ public void invalidateCache()
+ {
+ previousResults_.clear();
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ handlerManager_);
+ }
+
+ @Override
+ public HandlerRegistration addChangeHandler(ChangeHandler handler)
+ {
+ return handlerManager_.addHandler(ChangeEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlerManager_.fireEvent(event);
+ }
+
+ private class CustomDictCallback extends ServerRequestCallback<JsArrayString>
+ {
+ public CustomDictCallback(ServerRequestCallback<JsArrayString> callback)
+ {
+ clientCallback_ = callback;
+ }
+
+ @Override
+ public void onResponseReceived(JsArrayString customDicts)
+ {
+ // the underlying spelling dictionaries have changed so we need
+ // to update the ui-pref -- this will result in an invalidation
+ // of our results cache. note that if for some reason the user
+ // does not press the OK or Apply button in the dialog then cross
+ // process notification of the new custom dictionary state won't
+ // occur and other IDE instances will have invalid dictionary
+ // results caches until they are restarted.
+ uiPrefs_.spellingCustomDictionaries().setGlobalValue(customDicts);
+
+ // pass through to the caller
+ clientCallback_.onResponseReceived(customDicts);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ clientCallback_.onError(error);
+ }
+
+ private final ServerRequestCallback<JsArrayString> clientCallback_;
+ };
+
+
+ private final SpellingServerOperations server_;
+ private final UIPrefs uiPrefs_;
+
+ private HashMap<String,Boolean> previousResults_ =
+ new HashMap<String,Boolean>();
+
+ HandlerManager handlerManager_ = new HandlerManager(this);
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellCheckerResult.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellCheckerResult.java
new file mode 100644
index 0000000..2bcc2f9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellCheckerResult.java
@@ -0,0 +1,41 @@
+/*
+ * SpellCheckerResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.spelling.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SpellCheckerResult
+{
+ public SpellCheckerResult()
+ {
+ correct_ = new ArrayList<String>();
+ incorrect_ = new ArrayList<String>();
+ }
+
+ public List<String> getCorrect()
+ {
+ return correct_;
+ }
+
+ public List<String> getIncorrect()
+ {
+ return incorrect_;
+ }
+
+ private final List<String> correct_;
+ private final List<String> incorrect_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingLanguage.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingLanguage.java
new file mode 100644
index 0000000..ded2cab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingLanguage.java
@@ -0,0 +1,32 @@
+/*
+ * SpellingLanguage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.spelling.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SpellingLanguage extends JavaScriptObject
+{
+ protected SpellingLanguage()
+ {
+ }
+
+ public native final String getId() /*-{
+ return this.id;
+ }-*/;
+
+ public native final String getName() /*-{
+ return this.name;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingServerOperations.java
new file mode 100644
index 0000000..65f00e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/model/SpellingServerOperations.java
@@ -0,0 +1,43 @@
+/*
+ * SpellingServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.spelling.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.prefs.model.SpellingPrefsContext;
+
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.core.client.JsArrayString;
+
+public interface SpellingServerOperations
+{
+ // check the specified array of words, returning an array of integer
+ // indexes for mis-spelled words
+ void checkSpelling(JsArrayString words,
+ ServerRequestCallback<JsArrayInteger> requestCallback);
+
+ void suggestionList(String word,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void getWordChars(ServerRequestCallback<String> requestCallback);
+
+ void addCustomDictionary(String dictPath,
+ ServerRequestCallback<JsArrayString> callback);
+
+ void removeCustomDictionary(String name,
+ ServerRequestCallback<JsArrayString> callback);
+
+ void installAllDictionaries(
+ ServerRequestCallback<SpellingPrefsContext> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.css b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.css
new file mode 100644
index 0000000..ba57ac1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.css
@@ -0,0 +1,14 @@
+
+
+.helpButton {
+ margin-left: 3px;
+}
+
+.listBox {
+ width: 220px;
+ margin-right: 4px;
+}
+
+.button {
+ width: 60px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.java
new file mode 100644
index 0000000..2e062bd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingCustomDictionariesWidget.java
@@ -0,0 +1,221 @@
+/*
+ * SpellingCustomDictionariesWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.spelling.ui;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.spelling.SpellingService;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.inject.Inject;
+
+public class SpellingCustomDictionariesWidget extends Composite
+{
+ public SpellingCustomDictionariesWidget()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ VerticalPanel panel = new VerticalPanel();
+
+ HorizontalPanel labelPanel = new HorizontalPanel();
+ Label label = new Label("Custom dictionaries:");
+ labelPanel.add(label);
+ HelpButton helpButton = new HelpButton("custom_dictionaries");
+ helpButton.addStyleName(RES.styles().helpButton());
+ labelPanel.add(helpButton);
+ panel.add(labelPanel);
+
+ HorizontalPanel dictionariesPanel = new HorizontalPanel();
+ listBox_ = new ListBox(false);
+ listBox_.addStyleName(RES.styles().listBox());
+ listBox_.getElement().<SelectElement>cast().setSize(4);
+ dictionariesPanel.add(listBox_);
+
+ VerticalPanel buttonPanel = new VerticalPanel();
+ SmallButton buttonAdd = createButton("Add...");
+ buttonAdd.addClickHandler(addButtonClicked_);
+ buttonPanel.add(buttonAdd);
+ SmallButton buttonRemove = createButton("Remove...");
+ buttonRemove.addClickHandler(removeButtonClicked_);
+ buttonPanel.add(buttonRemove);
+ dictionariesPanel.add(buttonPanel);
+
+ panel.add(dictionariesPanel);
+
+ initWidget(panel);
+ }
+
+ @Inject
+ void initialize(SpellingService spellingService,
+ GlobalDisplay globalDisplay,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileSystemContext)
+ {
+ spellingService_ = spellingService;
+ globalDisplay_= globalDisplay;
+ fileDialogs_ = fileDialogs;
+ fileSystemContext_ = fileSystemContext;
+ }
+
+ public void setDictionaries(JsArrayString dictionaries)
+ {
+ listBox_.clear();
+ for (int i=0; i<dictionaries.length(); i++)
+ listBox_.addItem(dictionaries.get(i));
+ }
+
+ public void setProgressIndicator(ProgressIndicator progressIndicator)
+ {
+ progressIndicator_ = progressIndicator;
+ }
+
+
+
+ private ClickHandler addButtonClicked_ = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.openFile(
+ "Add Custom Dictionary (*.dic)",
+ fileSystemContext_,
+ FileSystemItem.home(),
+ "Dictionaries (*.dic)",
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ @Override
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ if (input == null)
+ return;
+
+ progressIndicator_.onProgress("Adding dictionary...");
+
+ spellingService_.addCustomDictionary(
+ input.getPath(),
+ customDictRequestCallback_);
+ }
+
+ });
+ }
+ };
+
+
+ private ClickHandler removeButtonClicked_ = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ // get selected index
+ int index = listBox_.getSelectedIndex();
+ if (index != -1)
+ {
+ final String dictionary = listBox_.getValue(index);
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Confirm Remove",
+ "Are you sure you want to remove the " + dictionary +
+ " custom dictionary?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ progressIndicator_.onProgress("Removing dictionary...");
+
+ spellingService_.removeCustomDictionary(
+ dictionary,
+ customDictRequestCallback_);
+ }
+ },
+ false);
+
+ }
+ }
+ };
+
+ private ServerRequestCallback<JsArrayString> customDictRequestCallback_ =
+ new ServerRequestCallback<JsArrayString>() {
+
+ @Override
+ public void onResponseReceived(JsArrayString dictionaries)
+ {
+ progressIndicator_.onCompleted();
+ setDictionaries(dictionaries);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progressIndicator_.onError(error.getUserMessage());
+ }
+ };
+
+ private SmallButton createButton(String caption)
+ {
+ SmallButton button = new SmallButton(caption);
+ button.addStyleName(RES.styles().button());
+ button.fillWidth();
+ return button;
+ }
+
+ private final ListBox listBox_;
+ private ProgressIndicator progressIndicator_;
+ private SpellingService spellingService_;
+ private GlobalDisplay globalDisplay_;
+ private FileDialogs fileDialogs_;
+ private RemoteFileSystemContext fileSystemContext_;
+
+ static interface Styles extends CssResource
+ {
+ String helpButton();
+ String listBox();
+ String button();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("SpellingCustomDictionariesWidget.css")
+ Styles styles();
+ }
+
+ static Resources RES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingLanguageSelectWidget.java b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingLanguageSelectWidget.java
new file mode 100644
index 0000000..9b6c610
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/spelling/ui/SpellingLanguageSelectWidget.java
@@ -0,0 +1,114 @@
+/*
+ * SpellingLanguageSelectWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.spelling.ui;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.common.spelling.model.SpellingLanguage;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.user.client.ui.ListBox;
+
+public class SpellingLanguageSelectWidget extends SelectWidget
+{
+ public SpellingLanguageSelectWidget(
+ final CommandWithArg<String> onInstallLanguages)
+ {
+ super("Main dictionary language:",
+ new String[0],
+ new String[0],
+ false,
+ true,
+ false);
+
+ HelpButton.addHelpButton(this, "spelling_dictionaries");
+
+ getListBox().addChangeHandler(new ChangeHandler() {
+
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ if (getListBox().getSelectedIndex() == installIndex_)
+ {
+ setSelectedLanguage(currentLangId_);
+ String progress = allLanguagesInstalled_ ?
+ "Downloading dictionaries..." :
+ "Downloading additional languages...";
+ onInstallLanguages.execute(progress);
+ }
+ else
+ {
+ currentLangId_ = getSelectedLanguage();
+ }
+ }
+
+ });
+ }
+
+ public void setLanguages(boolean allLanguagesInstalled,
+ JsArray<SpellingLanguage> languages)
+ {
+ languages_ = languages;
+ installIndex_ = languages.length();
+ allLanguagesInstalled_ = allLanguagesInstalled;
+ String[] choices = new String[languages.length()+1];
+ String[] values = new String[languages.length()+1];
+ for (int i=0; i<languages.length(); i++)
+ {
+ SpellingLanguage language = languages.get(i);
+ choices[i] = language.getName();
+ values[i] = language.getId();
+ }
+ if (allLanguagesInstalled)
+ choices[installIndex_] = "Update Dictionaries...";
+ else
+ choices[installIndex_] = "Install More Languages...";
+ values[installIndex_] = "";
+
+ setChoices(choices, values);
+ }
+
+ public void setSelectedLanguage(String langId)
+ {
+ for (int i=0; i<languages_.length(); i++)
+ {
+ if (langId.equals(languages_.get(i).getId()))
+ {
+ currentLangId_ = langId;
+ getListBox().setSelectedIndex(i);
+ return;
+ }
+ }
+
+ // if we couldn't find this lang id then reset
+ getListBox().setSelectedIndex(0);
+ currentLangId_ = getListBox().getValue(0);
+ }
+
+ public String getSelectedLanguage()
+ {
+ ListBox listBox = getListBox();
+ return listBox.getValue(listBox.getSelectedIndex());
+ }
+
+ private String currentLangId_ = null;
+ private int installIndex_ = -1;
+ private boolean allLanguagesInstalled_ = false;
+ private JsArray<SpellingLanguage> languages_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/styles.css b/src/gwt/src/org/rstudio/studio/client/common/styles.css
new file mode 100644
index 0000000..54093ba
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/styles.css
@@ -0,0 +1,88 @@
+.gwt-Label-ApplicationHeaderStrong {
+ font-weight: bold;
+}
+
+.gwt-HorizontalSplitPanel-Workbench {
+
+}
+
+.gwt-HorizontalSplitPanel-Workbench .hsplitter {
+ background: white;
+}
+
+.gwt-DecoratedTabPanel-Workbench {
+
+}
+
+.gwt-DecoratedTabPanel-Workbench .gwt-TabPanelBottom {
+ padding: 0px;
+}
+
+.rstudio-RCodeEditor-EditDialog {
+ border: 1px solid #BBB;
+}
+
+
+.rstudio-FolderList {
+ border: 1px solid #BBB;
+}
+
+.rstudio-NewFileChooser {
+ width: 150px;
+}
+
+.rstudio-FileUploadPanel {
+ width: 250px;
+}
+
+
+
+.selected {
+ background-color: rgb(146, 193, 240);
+}
+
+.edit {
+ background-color: white;
+}
+
+.pending {
+ background-color: silver;
+ text-color: gray;
+}
+
+tr.sectionHead td {
+ text-transform: uppercase;
+ font-weight: bold;
+ background-color: #DDD;
+ color: #666;
+ font-size: 7pt;
+}
+
+.rstudio-HelpFrame {
+ outline: none ;
+ border: none ;
+}
+
+.rstudio-HeaderView {
+
+}
+
+.rstudio-SessionEnded .Caption {
+ font-weight: bold;
+}
+
+.rstudio-Login .NormalCaption {
+ font-weight: bold;
+}
+
+.rstudio-Login .ErrorCaption {
+ color: red;
+}
+
+.rstudio-Login .Form {
+
+}
+
+.rstudio-Login .Form .FieldLabel {
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/Synctex.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/Synctex.java
new file mode 100644
index 0000000..27d5b2c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/Synctex.java
@@ -0,0 +1,462 @@
+/*
+ * Synctex.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.synctex;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfStartedEvent;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.synctex.events.SynctexStatusChangedEvent;
+import org.rstudio.studio.client.common.synctex.events.SynctexViewPdfEvent;
+import org.rstudio.studio.client.common.synctex.events.SynctexEditFileEvent;
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.common.synctex.model.SynctexServerOperations;
+import org.rstudio.studio.client.pdfviewer.PDFViewerApplication;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class Synctex implements CompilePdfStartedEvent.Handler,
+ CompilePdfCompletedEvent.Handler,
+ SynctexEditFileEvent.Handler
+{
+ @Inject
+ public Synctex(GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ Commands commands,
+ SynctexServerOperations server,
+ FileTypeRegistry fileTypeRegistry,
+ Session session,
+ UIPrefs prefs,
+ Satellite satellite,
+ SatelliteManager satelliteManager)
+ {
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ commands_ = commands;
+ server_ = server;
+ fileTypeRegistry_ = fileTypeRegistry;
+ session_ = session;
+ prefs_ = prefs;
+ satellite_ = satellite;
+ satelliteManager_ = satelliteManager;
+
+ // main window and satellite export callbacks to eachother
+ if (!satellite.isCurrentWindowSatellite())
+ {
+ registerMainWindowCallbacks();
+
+ eventBus_.addHandler(SynctexEditFileEvent.TYPE, this);
+ }
+ else if (isCurrentWindowPdfViewerSatellite())
+ {
+ registerSatelliteCallbacks();
+
+ satellite_.addCloseHandler(new CloseHandler<Satellite>() {
+ @Override
+ public void onClose(CloseEvent<Satellite> event)
+ {
+ callNotifyPdfViewerClosed(pdfPath_);
+ }
+ });
+ }
+
+ // fixup synctex tooltips for macos
+ if (BrowseCap.isMacintosh())
+ fixupSynctexCommandDescription(commands_.synctexSearch());
+
+ // disable commands at the start
+ setNoSynctexStatus();
+
+ // subscribe to compile pdf status event so we can update command status
+ eventBus_.addHandler(CompilePdfStartedEvent.TYPE, this);
+ eventBus_.addHandler(CompilePdfCompletedEvent.TYPE, this);
+ }
+
+ @Override
+ public void onCompilePdfStarted(CompilePdfStartedEvent event)
+ {
+ setNoSynctexStatus();
+ }
+
+ @Override
+ public void onCompilePdfCompleted(CompilePdfCompletedEvent event)
+ {
+ String pdfPreview = prefs_.getPdfPreviewValue();
+
+ boolean synctexSupported =
+ // internal previewer
+ (pdfPreview.equals(UIPrefs.PDF_PREVIEW_RSTUDIO) &&
+ session_.getSessionInfo().isInternalPdfPreviewEnabled()) ||
+ // platform-specific desktop previewer
+ (pdfPreview.equals(UIPrefs.PDF_PREVIEW_DESKTOP_SYNCTEX) &&
+ Desktop.isDesktop());
+
+ boolean synctexAvailable = synctexSupported &&
+ event.getResult().isSynctexAvailable();
+
+ if (synctexAvailable)
+ setSynctexStatus(event.getResult().getTargetFile(),
+ event.getResult().getPdfPath());
+ else
+ setNoSynctexStatus();
+
+ // if this was a desktop synctex preview then invoke it directly
+ // (internal previews are handled by the compile pdf window directly)
+ if (event.getResult().getSucceeded() && handleDesktopSynctex())
+ {
+ int page = 1;
+ if (event.getResult().isSynctexAvailable())
+ page = event.getResult().getPdfLocation().getPage();
+ Desktop.getFrame().externalSynctexPreview(
+ event.getResult().getPdfPath(),
+ page);
+ }
+ }
+
+ @Override
+ public void onSynctexEditFile(SynctexEditFileEvent event)
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().bringMainFrameToFront();
+
+ goToSourceLocation(event.getSourceLocation());
+ }
+
+
+ public boolean isSynctexAvailable()
+ {
+ return pdfPath_ != null;
+ }
+
+ public void enableCommands(boolean enabled)
+ {
+ commands_.synctexSearch().setVisible(enabled);
+ commands_.synctexSearch().setEnabled(enabled);
+ }
+
+ // NOTE: the original design was for a single internal pdf viewer. for
+ // that configuration we could keep a global pdfPath_ around and be
+ // confident that it was always correct. we were also globally managing
+ // the state of the synctex command based on any external viewer closing.
+ // now that we optionally support desktop viewers for synctex this
+ // assumption may not hold -- specfically there might be multiple active
+ // PDF viewers for different document or we might not know that the
+ // external viewer has closed . we have explicitly chosen to
+ // avoid the complexity of tracking distinct viewer states. if we want
+ // to do this we probably should do the following:
+ //
+ // - always keep the the syncex command available in all editors
+ // so long as there is at least one preview window alive; OR
+ //
+ // - for cases where we do know whether the window is still alive
+ // editors could dynamically show/hide their synctex button
+ // based on that more granular state
+ //
+ public boolean forwardSearch(final String pdfFile,
+ SourceLocation sourceLocation)
+ {
+ if (handleDesktopSynctex())
+ {
+ // apply concordane
+ final ProgressIndicator indicator = getSyncProgress();
+ server_.applyForwardConcordance(
+ pdfFile,
+ sourceLocation,
+ new ServerRequestCallback<SourceLocation>() {
+ @Override
+ public void onResponseReceived(SourceLocation sourceLocation)
+ {
+ indicator.onCompleted();
+
+ if (sourceLocation != null)
+ {
+ Desktop.getFrame().externalSynctexView(
+ pdfFile,
+ sourceLocation.getFile(),
+ sourceLocation.getLine(),
+ sourceLocation.getColumn());
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+
+ return true;
+ }
+
+ // use internal viewer
+ else
+ {
+ // return false if there is no pdf viewer
+ WindowEx window = satelliteManager_.getSatelliteWindowObject(
+ PDFViewerApplication.NAME);
+ if (window == null)
+ return false;
+
+ // activate the satellite
+ satelliteManager_.activateSatelliteWindow(PDFViewerApplication.NAME);
+
+ // execute the forward search
+ callForwardSearch(window, targetFile_, sourceLocation);
+
+ return true;
+ }
+ }
+
+ public void inverseSearch(PdfLocation pdfLocation)
+ {
+ // switch back to the main window
+ satellite_.focusMainWindow();
+
+ // warn firefox users that this doesn't really work in Firefox
+ if (BrowseCap.isFirefox())
+ SynctexUtils.maybeShowFirefoxWarning("source editor");
+
+ // do the inverse search
+ callInverseSearch(pdfLocation);
+ }
+
+
+ private void doForwardSearch(String rootDocument,
+ JavaScriptObject sourceLocationObject)
+ {
+ SourceLocation sourceLocation = sourceLocationObject.cast();
+
+ final ProgressIndicator indicator = getSyncProgress();
+ server_.synctexForwardSearch(
+ rootDocument,
+ sourceLocation,
+ new ServerRequestCallback<PdfLocation>() {
+
+ @Override
+ public void onResponseReceived(PdfLocation location)
+ {
+ indicator.onCompleted();
+
+ if (location != null)
+ eventBus_.fireEvent(new SynctexViewPdfEvent(location));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+ }
+
+ private void doInverseSearch(JavaScriptObject pdfLocationObject)
+ {
+ PdfLocation pdfLocation = pdfLocationObject.cast();
+
+ final ProgressIndicator indicator = getSyncProgress();
+ server_.synctexInverseSearch(
+ pdfLocation,
+ new ServerRequestCallback<SourceLocation>() {
+
+ @Override
+ public void onResponseReceived(SourceLocation location)
+ {
+ indicator.onCompleted();
+
+ if (location != null)
+ goToSourceLocation(location);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+ private void doDesktopInverseSearch(String file, int line, int column)
+ {
+ // apply concordance
+ final ProgressIndicator indicator = getSyncProgress();
+ server_.applyInverseConcordance(
+ SourceLocation.create(file, line, column, true),
+ new ServerRequestCallback<SourceLocation>() {
+ @Override
+ public void onResponseReceived(SourceLocation sourceLocation)
+ {
+ indicator.onCompleted();
+
+ if (sourceLocation != null)
+ goToSourceLocation(sourceLocation);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+
+ }
+
+ private void goToSourceLocation(SourceLocation location)
+ {
+ FilePosition position = FilePosition.create(
+ location.getLine(),
+ Math.min(1, location.getColumn()));
+
+ fileTypeRegistry_.editFile(
+ FileSystemItem.createFile(location.getFile()),
+ position);
+ }
+
+
+ private void notifyPdfViewerClosed(String pdfFile)
+ {
+ setNoSynctexStatus();
+ }
+
+ private boolean isCurrentWindowPdfViewerSatellite()
+ {
+ return satellite_.isCurrentWindowSatellite() &&
+ satellite_.getSatelliteName().equals(PDFViewerApplication.NAME);
+
+ }
+
+ private boolean handleDesktopSynctex()
+ {
+ return Desktop.isDesktop() &&
+ !satellite_.isCurrentWindowSatellite() &&
+ prefs_.getPdfPreviewValue().equals(
+ UIPrefs.PDF_PREVIEW_DESKTOP_SYNCTEX);
+ }
+
+ private ProgressIndicator getSyncProgress()
+ {
+ return new GlobalProgressDelayer(globalDisplay_,
+ 500,
+ "Syncing...").getIndicator();
+ }
+
+ private void setNoSynctexStatus()
+ {
+ setSynctexStatus(null, null);
+ }
+
+ private void setSynctexStatus(String targetFile,
+ String pdfPath)
+ {
+ // set flag and fire event
+ if (!StringUtil.notNull(pdfPath_).equals(StringUtil.notNull(pdfPath)))
+ {
+ targetFile_ = targetFile;
+ pdfPath_ = pdfPath;
+ eventBus_.fireEvent(new SynctexStatusChangedEvent(targetFile,
+ pdfPath));
+ }
+ }
+
+ private void fixupSynctexCommandDescription(AppCommand command)
+ {
+ String desc = command.getDesc().replace("Ctrl+", "Cmd+");
+ command.setDesc(desc);
+ }
+
+ private native void registerMainWindowCallbacks() /*-{
+ var synctex = this;
+ $wnd.synctexInverseSearch = $entry(
+ function(pdfLocation) {
+ synctex. at org.rstudio.studio.client.common.synctex.Synctex::doInverseSearch(Lcom/google/gwt/core/client/JavaScriptObject;)(pdfLocation);
+ }
+ );
+
+ $wnd.desktopSynctexInverseSearch = $entry(
+ function(file,line,column) {
+ synctex. at org.rstudio.studio.client.common.synctex.Synctex::doDesktopInverseSearch(Ljava/lang/String;II)(file,line,column);
+ }
+ );
+
+ $wnd.synctexNotifyPdfViewerClosed = $entry(
+ function(pdfPath) {
+ synctex. at org.rstudio.studio.client.common.synctex.Synctex::notifyPdfViewerClosed(Ljava/lang/String;)(pdfPath);
+ }
+ );
+ }-*/;
+
+ private final native void callInverseSearch(JavaScriptObject pdfLocation)/*-{
+ $wnd.opener.synctexInverseSearch(pdfLocation);
+ }-*/;
+
+ private final native void callNotifyPdfViewerClosed(String pdfPath) /*-{
+ $wnd.opener.synctexNotifyPdfViewerClosed(pdfPath);
+ }-*/;
+
+ private native void registerSatelliteCallbacks() /*-{
+ var synctex = this;
+ $wnd.synctexForwardSearch = $entry(
+ function(rootDocument, sourceLocation) {
+ synctex. at org.rstudio.studio.client.common.synctex.Synctex::doForwardSearch(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(rootDocument, sourceLocation);
+ }
+ );
+ }-*/;
+
+ private native void callForwardSearch(JavaScriptObject satellite,
+ String rootDocument,
+ JavaScriptObject sourceLocation) /*-{
+ satellite.synctexForwardSearch(rootDocument, sourceLocation);
+ }-*/;
+
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus eventBus_;
+ private final Commands commands_;
+ private final SynctexServerOperations server_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final Session session_;
+ private final UIPrefs prefs_;
+ private final Satellite satellite_;
+ private final SatelliteManager satelliteManager_;
+ private String pdfPath_ = null;
+ private String targetFile_ = "";
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/SynctexUtils.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/SynctexUtils.java
new file mode 100644
index 0000000..61a0e63
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/SynctexUtils.java
@@ -0,0 +1,53 @@
+/*
+ * SynctexUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.common.synctex;
+
+import org.rstudio.studio.client.application.Desktop;
+
+public class SynctexUtils
+{
+ public static String getDesktopSynctexViewer()
+ {
+ if (Desktop.isDesktop())
+ return Desktop.getFrame().getDesktopSynctexViewer();
+ else
+ return "";
+ }
+
+ public static void maybeShowFirefoxWarning(String target)
+ {
+ /*
+ if (!messageShown_)
+ {
+ messageShown_ = true;
+
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showMessage(
+ MessageDialog.POPUP_BLOCKED,
+ "Unable to Switch Windows",
+ "The " + target + " was updated to sync to the current location " +
+ "however Firefox has prevented RStudio from switching to the " +
+ target + " window.\n\n" +
+ "To avoid this problem in the future you may want to use " +
+ "Google Chrome rather than Firefox when compiling and " +
+ "previewing PDFs.");
+ }
+ */
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean messageShown_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexEditFileEvent.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexEditFileEvent.java
new file mode 100644
index 0000000..f16f437
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexEditFileEvent.java
@@ -0,0 +1,55 @@
+/*
+ * SynctexEditFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.synctex.events;
+
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SynctexEditFileEvent extends GwtEvent<SynctexEditFileEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSynctexEditFile(SynctexEditFileEvent event);
+ }
+
+ public SynctexEditFileEvent(SourceLocation sourceLocation)
+ {
+ sourceLocation_ = sourceLocation;
+ }
+
+
+ public SourceLocation getSourceLocation()
+ {
+ return sourceLocation_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSynctexEditFile(this);
+ }
+
+ private final SourceLocation sourceLocation_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexStatusChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexStatusChangedEvent.java
new file mode 100644
index 0000000..dd5b3f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexStatusChangedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * SynctexStatusChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.synctex.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SynctexStatusChangedEvent extends GwtEvent<SynctexStatusChangedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSynctexStatusChanged(SynctexStatusChangedEvent event);
+ }
+
+ public SynctexStatusChangedEvent(String targetFile, String pdfPath)
+ {
+ targetFile_ = targetFile;
+ pdfPath_ = pdfPath;
+ }
+
+ public boolean isAvailable()
+ {
+ return getPdfPath() != null;
+ }
+
+ public String getTargetFile()
+ {
+ return targetFile_;
+ }
+
+ public String getPdfPath()
+ {
+ return pdfPath_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSynctexStatusChanged(this);
+ }
+
+ private final String pdfPath_;
+ private final String targetFile_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexViewPdfEvent.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexViewPdfEvent.java
new file mode 100644
index 0000000..c99a96d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/events/SynctexViewPdfEvent.java
@@ -0,0 +1,55 @@
+/*
+ * SynctexViewPdfEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.synctex.events;
+
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SynctexViewPdfEvent extends GwtEvent<SynctexViewPdfEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSynctexViewPdf(SynctexViewPdfEvent event);
+ }
+
+ public SynctexViewPdfEvent(PdfLocation pdfLocation)
+ {
+ pdfLocation_ = pdfLocation;
+ }
+
+
+ public PdfLocation getPdfLocation()
+ {
+ return pdfLocation_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSynctexViewPdf(this);
+ }
+
+ private final PdfLocation pdfLocation_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/model/PdfLocation.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/PdfLocation.java
new file mode 100644
index 0000000..0605bc9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/PdfLocation.java
@@ -0,0 +1,85 @@
+/*
+ * PdfLocation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.synctex.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PdfLocation extends JavaScriptObject
+{
+ protected PdfLocation()
+ {
+ }
+
+ public final static native PdfLocation create(String file,
+ int page,
+ double x,
+ double y,
+ double width,
+ double height,
+ boolean fromClick) /*-{
+ var location = new Object();
+ location.file = file;
+ location.page = page;
+ location.x = x;
+ location.y = y;
+ location.width = width;
+ location.height = height;
+ location.from_click = fromClick;
+ return location;
+ }-*/;
+
+ public final native String getFile() /*-{
+ return this.file;
+ }-*/;
+
+ public native final int getPage() /*-{
+ return this.page;
+ }-*/;
+
+ public native final double getX() /*-{
+ return this.x;
+ }-*/;
+
+ public native final double getY() /*-{
+ return this.y;
+ }-*/;
+
+ public native final double getWidth() /*-{
+ return this.width;
+ }-*/;
+
+ public native final double getHeight() /*-{
+ return this.height;
+ }-*/;
+
+ public native final boolean isFromClick() /*-{
+ return this.from_click;
+ }-*/;
+
+ public final String toDebugString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(getFile());
+ str.append("; Page ");
+ str.append(getPage());
+ str.append(" {" + (int)getX() + ", " + (int)getY() + ", " +
+ (int)getWidth() + ", " + (int)getHeight() + "}");
+ if (isFromClick())
+ str.append(" [From Click]");
+ return str.toString();
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SourceLocation.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SourceLocation.java
new file mode 100644
index 0000000..04ed868
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SourceLocation.java
@@ -0,0 +1,55 @@
+/*
+ * SourceLocation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.synctex.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SourceLocation extends JavaScriptObject
+{
+ protected SourceLocation()
+ {
+ }
+
+
+ public final static native SourceLocation create(String file,
+ int line,
+ int column,
+ boolean fromClick) /*-{
+ var location = new Object();
+ location.file = file;
+ location.line = line;
+ location.column = column;
+ location.from_click = fromClick;
+ return location;
+ }-*/;
+
+ public native final String getFile() /*-{
+ return this.file;
+ }-*/;
+
+ public native final int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public native final int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public native final boolean fromClick() /*-{
+ return this.from_click;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SynctexServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SynctexServerOperations.java
new file mode 100644
index 0000000..22ac37e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/synctex/model/SynctexServerOperations.java
@@ -0,0 +1,37 @@
+/*
+ * SynctexServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.synctex.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface SynctexServerOperations
+{
+ void applyForwardConcordance(String rootDocument,
+ SourceLocation sourceLocation,
+ ServerRequestCallback<SourceLocation> callback);
+
+ void synctexForwardSearch(String rootDocument,
+ SourceLocation sourceLocation,
+ ServerRequestCallback<PdfLocation> callback);
+
+
+ void synctexInverseSearch(PdfLocation pdfLocation,
+ ServerRequestCallback<SourceLocation> callback);
+
+ void applyInverseConcordance(SourceLocation sourceLocation,
+ ServerRequestCallback<SourceLocation> callback);
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/AllStatus.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/AllStatus.java
new file mode 100644
index 0000000..756ff02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/AllStatus.java
@@ -0,0 +1,35 @@
+/*
+ * AllStatus.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class AllStatus extends JavaScriptObject
+{
+ protected AllStatus() {}
+
+ public native final JsArray<StatusAndPathInfo> getStatus() /*-{
+ return this.status;
+ }-*/;
+
+ public native final BranchesInfo getBranches() /*-{
+ return this.branches;
+ }-*/;
+
+ public native final RemoteBranchInfo getRemoteBranchInfo() /*-{
+ return this.remote_branch_info;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/AskPassManager.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/AskPassManager.java
new file mode 100644
index 0000000..aaa1cc5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/AskPassManager.java
@@ -0,0 +1,161 @@
+/*
+ * AskPassManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.MessageDisplay.PromptWithOptionResult;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.crypto.RSAEncrypt;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.AskPassEvent;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class AskPassManager
+{
+ @Inject
+ public AskPassManager(final VCSServerOperations server,
+ EventBus eventBus,
+ final GlobalDisplay globalDisplay,
+ final Satellite satellite,
+ final SatelliteManager satelliteManager)
+ {
+
+ eventBus.addHandler(AskPassEvent.TYPE, new AskPassEvent.Handler()
+ {
+ private boolean handleAskPass(String targetWindow)
+ {
+ // calculate the current window name
+ String window = StringUtil.notNull(satellite.getSatelliteName());
+
+ // handle it if the target is us
+ if (window.equals(targetWindow))
+ return true;
+
+ // also handle if we are the main window and the specified
+ // satellite doesn't exist
+ if (!satellite.isCurrentWindowSatellite() &&
+ !satelliteManager.satelliteWindowExists(targetWindow))
+ return true;
+
+ // othewise don't handle
+ else
+ return false;
+ }
+
+ @Override
+ public void onAskPass(final AskPassEvent e)
+ {
+ if (!handleAskPass(e.getWindow()))
+ return;
+
+ askpassPending_ = true;
+
+ globalDisplay.promptForPassword(
+ "Password",
+ e.getPrompt(),
+ "",
+ e.getRememberPasswordPrompt(),
+ rememberByDefault_,
+ new ProgressOperationWithInput<PromptWithOptionResult>()
+ {
+ @Override
+ public void execute(final PromptWithOptionResult result,
+ final ProgressIndicator indicator)
+ {
+ askpassPending_ = false;
+
+ rememberByDefault_ = result.extraOption;
+
+ RSAEncrypt.encrypt_ServerOnly(
+ server,
+ result.input,
+ new RSAEncrypt.ResponseCallback()
+ {
+ @Override
+ public void onSuccess(String encryptedData)
+ {
+ server.askpassCompleted(
+ encryptedData,
+ !StringUtil.isNullOrEmpty(e.getRememberPasswordPrompt())
+ && result.extraOption,
+ new VoidServerRequestCallback(indicator));
+
+ }
+
+ @Override
+ public void onFailure(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+ },
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ askpassPending_ = false;
+
+ server.askpassCompleted(
+ null, false,
+ new SimpleRequestCallback<Void>());
+ }
+ });
+ }
+ });
+
+ // if there is an askpass pending when the window closes then send an
+ // askpass cancel
+ Window.addWindowClosingHandler(new ClosingHandler() {
+
+ @Override
+ public void onWindowClosing(ClosingEvent event)
+ {
+ if (askpassPending_)
+ {
+ askpassPending_ = false;
+
+ server.askpassCompleted(null,
+ false,
+ new SimpleRequestCallback<Void>());
+ }
+
+ }
+ });
+
+
+ }
+
+
+ private boolean rememberByDefault_ = true;
+ private boolean askpassPending_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/BranchesInfo.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/BranchesInfo.java
new file mode 100644
index 0000000..b02b11d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/BranchesInfo.java
@@ -0,0 +1,35 @@
+/*
+ * BranchesInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class BranchesInfo extends JavaScriptObject
+{
+ protected BranchesInfo()
+ {
+ }
+
+ public native final String getActiveBranch() /*-{
+ if (this.activeIndex === null)
+ return null;
+ return this.branches[this.activeIndex];
+ }-*/;
+
+ public native final JsArrayString getBranches() /*-{
+ return this.branches;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.css b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.css
new file mode 100644
index 0000000..da83998
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.css
@@ -0,0 +1,32 @@
+
+
+.entryLabel {
+ margin-bottom: 2px;
+}
+
+.mainWidget {
+ width: 350px;
+}
+
+.keyPathTextBox {
+ margin-top: 2px;
+ background-color: transparent;
+}
+
+.newSection {
+ margin-top: 10px;
+}
+
+.lastSection {
+ margin-bottom: 40px;
+}
+
+.passphrase {
+ width: 170px;
+ margin-right: 10px;
+}
+
+.passphraseConfirm {
+ width: 165px;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.java
new file mode 100644
index 0000000..7f708c9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyDialog.java
@@ -0,0 +1,297 @@
+/*
+ * CreateKeyDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.CaptionWithHelp;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ShowContentDialog;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.crypto.RSAEncrypt;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PasswordTextBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class CreateKeyDialog extends ModalDialog<CreateKeyOptions>
+{
+ public CreateKeyDialog(String rsaSshKeyPath,
+ final VCSServerOperations server,
+ final OperationWithInput<String> onCompleted)
+ {
+ super("Create RSA Key", new ProgressOperationWithInput<CreateKeyOptions>() {
+
+ @Override
+ public void execute(final CreateKeyOptions input,
+ final ProgressIndicator indicator)
+ {
+ final ProgressOperationWithInput<CreateKeyOptions>
+ thisOperation = this;
+
+ indicator.onProgress("Creating RSA Key...");
+
+ RSAEncrypt.encrypt_ServerOnly(
+ server,
+ input.getPassphrase(),
+ new RSAEncrypt.ResponseCallback() {
+ @Override
+ public void onSuccess(final String encryptedData)
+ {
+ // substitute encrypted data
+ final CreateKeyOptions options = CreateKeyOptions.create(
+ input.getPath(),
+ input.getType(),
+ encryptedData,
+ input.getOverwrite());
+
+ // call server to create the key
+ server.createSshKey(
+ options,
+ new ServerRequestCallback<CreateKeyResult>() {
+
+ @Override
+ public void onResponseReceived(CreateKeyResult res)
+ {
+ if (res.getFailedKeyExists())
+ {
+ indicator.clearProgress();
+
+ confirmOverwriteKey(
+ input.getPath(),
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ // re-execute with overwrite == true
+ thisOperation.execute(
+ CreateKeyOptions.create(
+ options.getPath(),
+ options.getType(),
+ input.getPassphrase(),
+ true),
+ indicator);
+ }
+ });
+
+ }
+ else
+ {
+ // close the dialog
+ indicator.onCompleted();
+
+ // update the key path
+ if (res.getExitStatus() == 0)
+ onCompleted.execute(input.getPath());
+
+ else if (input.getOverwrite())
+ onCompleted.execute(null);
+
+ // show the output
+ new ShowContentDialog(
+ "Create RSA Key",
+ res.getOutput()).showModal();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+ }
+
+ @Override
+ public void onFailure(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ }
+ );
+ }
+ });
+
+ rsaSshKeyPath_ = FileSystemItem.createDir(rsaSshKeyPath);
+
+ setOkButtonCaption("Create");
+ }
+
+ @Override
+ protected CreateKeyOptions collectInput()
+ {
+ if (!getPassphrase().equals(getConfirmPassphrase()))
+ return null;
+ else
+ return CreateKeyOptions.create(rsaSshKeyPath_.getPath(),
+ "rsa",
+ getPassphrase(),
+ false);
+ }
+
+ @Override
+ protected boolean validate(CreateKeyOptions input)
+ {
+ if (input != null)
+ {
+ return true;
+ }
+ else
+ {
+ GlobalDisplay display = RStudioGinjector.INSTANCE.getGlobalDisplay();
+
+ if (!getPassphrase().equals(getConfirmPassphrase()))
+ {
+ display.showErrorMessage(
+ "Non-Matching Passphrases",
+ "The passphrase and passphrase confirmation do not match.",
+ txtConfirmPassphrase_);
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ Styles styles = RESOURCES.styles();
+
+ VerticalPanel panel = new VerticalPanel();
+ panel.addStyleName(styles.mainWidget());
+
+ VerticalPanel namePanel = new VerticalPanel();
+ namePanel.setWidth("100%");
+
+ // path
+ CaptionWithHelp pathCaption = new CaptionWithHelp(
+ "The RSA key will be created at:",
+ "SSH/RSA key management",
+ "rsa_key_help");
+ pathCaption.setIncludeVersionInfo(false);
+ pathCaption.setWidth("100%");
+ namePanel.add(pathCaption);
+
+ TextBox txtKeyPath = new TextBox();
+ txtKeyPath.addStyleName(styles.keyPathTextBox());
+ txtKeyPath.setReadOnly(true);
+ txtKeyPath.setText(rsaSshKeyPath_.getPath());
+ txtKeyPath.setWidth("100%");
+ namePanel.add(txtKeyPath);
+
+ panel.add(namePanel);
+
+ HorizontalPanel passphrasePanel = new HorizontalPanel();
+ passphrasePanel.addStyleName(styles.newSection());
+
+ VerticalPanel passphrasePanel1 = new VerticalPanel();
+ Label passphraseLabel1 = new Label("Passphrase (optional):");
+ passphraseLabel1.addStyleName(styles.entryLabel());
+ passphrasePanel1.add(passphraseLabel1);
+ txtPassphrase_ = new PasswordTextBox();
+ txtPassphrase_.addStyleName(styles.passphrase());
+
+ passphrasePanel1.add(txtPassphrase_);
+ passphrasePanel.add(passphrasePanel1);
+
+ VerticalPanel passphrasePanel2 = new VerticalPanel();
+ passphrasePanel2.addStyleName(styles.lastSection());
+ Label passphraseLabel2 = new Label("Confirm:");
+ passphraseLabel2.addStyleName(styles.entryLabel());
+ passphrasePanel2.add(passphraseLabel2);
+ txtConfirmPassphrase_ = new PasswordTextBox();
+ txtConfirmPassphrase_.addStyleName(styles.passphraseConfirm());
+ passphrasePanel2.add(txtConfirmPassphrase_);
+ passphrasePanel.add(passphrasePanel2);
+
+ panel.add(passphrasePanel);
+
+
+ return panel;
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ FocusHelper.setFocusDeferred(txtPassphrase_);
+ }
+
+
+ private static void confirmOverwriteKey(String path, Operation onConfirmed)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showYesNoMessage(
+ MessageDialog.WARNING,
+ "Key Already Exists",
+ "An RSA key already exists at " + path + ". " +
+ "Do you want to overwrite the existing key?",
+ onConfirmed,
+ false);
+ }
+
+ private String getPassphrase()
+ {
+ return txtPassphrase_.getText().trim();
+ }
+
+ private String getConfirmPassphrase()
+ {
+ return txtConfirmPassphrase_.getText().trim();
+ }
+
+ static interface Styles extends CssResource
+ {
+ String entryLabel();
+ String keyPathTextBox();
+ String mainWidget();
+ String newSection();
+ String lastSection();
+ String passphrase();
+ String passphraseConfirm();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("CreateKeyDialog.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private TextBox txtPassphrase_;
+ private TextBox txtConfirmPassphrase_;
+
+ private final FileSystemItem rsaSshKeyPath_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyOptions.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyOptions.java
new file mode 100644
index 0000000..da7620b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyOptions.java
@@ -0,0 +1,52 @@
+/*
+ * CreateKeyOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CreateKeyOptions extends JavaScriptObject
+{
+ protected CreateKeyOptions()
+ {
+ }
+
+ public native static final CreateKeyOptions create(String path,
+ String type,
+ String passphrase,
+ boolean overwrite) /*-{
+ var options = new Object();
+ options.path = path;
+ options.type = type;
+ options.passphrase = passphrase;
+ options.overwrite = overwrite;
+ return options;
+ }-*/;
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public native final String getPassphrase() /*-{
+ return this.passphrase;
+ }-*/;
+
+ public native final boolean getOverwrite() /*-{
+ return this.overwrite;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyResult.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyResult.java
new file mode 100644
index 0000000..9131e01
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/CreateKeyResult.java
@@ -0,0 +1,38 @@
+/*
+ * CreateKeyResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CreateKeyResult extends JavaScriptObject
+{
+ protected CreateKeyResult()
+ {
+ }
+
+ // check this value first to see if the operation failed
+ // due to the key already existing
+ public native final boolean getFailedKeyExists() /*-{
+ return this.failed_key_exists;
+ }-*/;
+
+ public native final int getExitStatus() /*-{
+ return this.exit_status;
+ }-*/;
+
+ public native final String getOutput() /*-{
+ return this.output;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/DiffResult.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/DiffResult.java
new file mode 100644
index 0000000..d0a42c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/DiffResult.java
@@ -0,0 +1,39 @@
+/*
+ * DiffResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DiffResult extends JavaScriptObject
+{
+ protected DiffResult() {}
+
+ /**
+ * The encoding that we assumed for the underlying source file. This is
+ * important because we need to reverse the encoding when sending the
+ * diff back to the server as a patch.
+ */
+ public native final String getSourceEncoding() /*-{
+ return this.source_encoding;
+ }-*/;
+
+ /**
+ * The actual value of the diff. This is always in UTF-8 (or if no encoding
+ * could be determined, the raw bytes).
+ */
+ public native final String getDecodedValue() /*-{
+ return this.decoded_value;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/GitServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/GitServerOperations.java
new file mode 100644
index 0000000..594cea0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/GitServerOperations.java
@@ -0,0 +1,145 @@
+/*
+ * GitServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JsArray;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitCount;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+
+import java.util.ArrayList;
+
+public interface GitServerOperations extends VCSServerOperations
+{
+ public enum PatchMode
+ {
+ Working(0),
+ Stage(1);
+
+ PatchMode(int intVal)
+ {
+ intVal_ = intVal;
+ }
+
+ public int getValue()
+ {
+ return intVal_;
+ }
+
+ private final int intVal_;
+ }
+
+ void gitAdd(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+ void gitRemove(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+ void gitDiscard(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+ void gitRevert(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+ void gitStage(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+ void gitUnstage(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback);
+
+ void gitAllStatus(
+ ServerRequestCallback<AllStatus> requestCallback);
+
+ void gitFullStatus(
+ ServerRequestCallback<JsArray<StatusAndPathInfo>> requestCallback);
+
+ void gitListBranches(ServerRequestCallback<BranchesInfo> requestCallback);
+
+ void gitCheckout(String id,
+ ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void gitCommit(String message,
+ boolean amend,
+ boolean signOff,
+ ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void gitDiffFile(String path,
+ PatchMode patchMode,
+ int contextLines,
+ boolean noSizeWarning,
+ ServerRequestCallback<DiffResult> requestCallback);
+
+ /**
+ * @param patch The patch, in UTF-8 encoding
+ * @param mode Whether the patch should be applied to working copy or index
+ * @param sourceEncoding The encoding that the patch should be transcoded to
+ * before applying
+ * @param requestCallback
+ */
+ void gitApplyPatch(String patch, PatchMode mode, String sourceEncoding,
+ ServerRequestCallback<Void> requestCallback);
+
+ void gitHistoryCount(String spec,
+ FileSystemItem fileFilter,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback);
+ /**
+ * @param spec Revision list or description. "" for default.
+ * @param maxentries Limit the number of entries returned. -1 for no limit.
+ */
+ void gitHistory(String spec,
+ FileSystemItem fileFilter,
+ int skip,
+ int maxentries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback);
+
+ void gitShow(String rev,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback);
+
+ void gitShowFile(String rev,
+ String filename,
+ ServerRequestCallback<String> requestCallback);
+
+ void gitExportFile(String rev,
+ String filename,
+ String targetPath,
+ ServerRequestCallback<Void> requestCallback);
+
+ void gitPush(ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void gitPull(ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void gitSshPublicKey(String privateKeyPath,
+ ServerRequestCallback<String> requestCallback);
+
+ void gitHasRepo(String directory,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void gitInitRepo(String directory,
+ ServerRequestCallback<Void> requestCallback);
+
+ void gitGetIgnores(String path,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void gitSetIgnores(String path,
+ String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void gitGithubRemoteUrl(String view,
+ String path,
+ ServerRequestCallback<String> callback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ProcessResult.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/ProcessResult.java
new file mode 100644
index 0000000..40b3856
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ProcessResult.java
@@ -0,0 +1,30 @@
+/*
+ * ProcessResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ProcessResult extends JavaScriptObject
+{
+ protected ProcessResult() {}
+
+ public final native String getOutput() /*-{
+ return this.output;
+ }-*/;
+
+ public final native int getExitCode() /*-{
+ return this.exit_code;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/RemoteBranchInfo.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/RemoteBranchInfo.java
new file mode 100644
index 0000000..9edc860
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/RemoteBranchInfo.java
@@ -0,0 +1,33 @@
+/*
+ * RemoteBranchInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RemoteBranchInfo extends JavaScriptObject
+{
+ protected RemoteBranchInfo()
+ {
+ }
+
+ public native final String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public native final int getCommitsBehind() /*-{
+ return this.commits_behind;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/SVNServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/SVNServerOperations.java
new file mode 100644
index 0000000..34f6b35
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/SVNServerOperations.java
@@ -0,0 +1,107 @@
+/*
+ * SVNServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitCount;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+
+import java.util.ArrayList;
+
+public interface SVNServerOperations extends VCSServerOperations
+{
+ void svnAdd(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnDelete(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnRevert(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnResolve(String accept,
+ ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnStatus(
+ ServerRequestCallback<JsArray<StatusAndPathInfo>> requestCallback);
+
+ void svnUpdate(ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void svnCleanup(ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnCommit(
+ ArrayList<String> paths,
+ String message,
+ ServerRequestCallback<ConsoleProcess> requestCallback);
+
+ void svnDiffFile(String path,
+ Integer contextLines,
+ boolean noSizeWarning,
+ ServerRequestCallback<DiffResult> requestCallback);
+
+ void svnApplyPatch(String path,
+ String patch,
+ String sourceEncoding,
+ ServerRequestCallback<Void> requestCallback);
+
+ /**
+ *
+ * @param revision Revision number to start at (-1 for latest)
+ * @param path The path to show revisions for (null for .)
+ * @param searchText
+ * @param requestCallback
+ */
+ void svnHistoryCount(int revision,
+ FileSystemItem path,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback);
+
+ /**
+ *
+ * @param revision Revision number to start at (-1 for latest)
+ * @param path The path to show revisions for (null for .)
+ * @param maxentries -1 for no limit
+ * @param searchText
+ * @param requestCallback
+ */
+ void svnHistory(int revision,
+ FileSystemItem path,
+ int skip,
+ int maxentries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback);
+
+ void svnShow(int revision,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback);
+
+ void svnShowFile(int revision,
+ String filename,
+ ServerRequestCallback<String> requestCallback);
+
+
+ void svnGetIgnores(String path,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void svnSetIgnores(String path,
+ String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.css b/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.css
new file mode 100644
index 0000000..ae28475
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.css
@@ -0,0 +1,14 @@
+
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external fixedWidthFont;
+
+.viewPublicKeyContent {
+ font-family: fixedWidthFont;
+ font-size: 12px;
+}
+
+.viewPublicKeyLabel {
+ margin-bottom: 7px;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.java
new file mode 100644
index 0000000..95da324
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ShowPublicKeyDialog.java
@@ -0,0 +1,110 @@
+/*
+ * ShowPublicKeyDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.ThemedButton;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ShowPublicKeyDialog extends ModalDialogBase
+{
+
+ public ShowPublicKeyDialog(String caption, String publicKey)
+ {
+ publicKey_ = publicKey;
+
+ setText(caption);
+
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_CENTER);
+
+ ThemedButton closeButton = new ThemedButton("Close",
+ new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ closeDialog();
+ }
+ });
+ addOkButton(closeButton);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel panel = new VerticalPanel();
+
+ int mod = BrowseCap.hasMetaKey() ? KeyboardShortcut.META :
+ KeyboardShortcut.CTRL;
+ String cmdText = new KeyboardShortcut(mod, 'C').toString(true);
+ HTML label = new HTML("Press " + cmdText +
+ " to copy the key to the clipboard");
+ label.addStyleName(RES.styles().viewPublicKeyLabel());
+ panel.add(label);
+
+ textArea_ = new TextArea();
+ textArea_.setReadOnly(true);
+ textArea_.setText(publicKey_);
+ textArea_.addStyleName(RES.styles().viewPublicKeyContent());
+ textArea_.setSize("400px", "250px");
+ textArea_.getElement().setAttribute("spellcheck", "false");
+ FontSizer.applyNormalFontSize(textArea_.getElement());
+
+ panel.add(textArea_);
+
+ return panel;
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ textArea_.selectAll();
+ FocusHelper.setFocusDeferred(textArea_);
+ }
+
+ static interface Styles extends CssResource
+ {
+ String viewPublicKeyContent();
+ String viewPublicKeyLabel();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("ShowPublicKeyDialog.css")
+ Styles styles();
+ }
+
+ static Resources RES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ private final String publicKey_;
+ private TextArea textArea_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.css b/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.css
new file mode 100644
index 0000000..dea829a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.css
@@ -0,0 +1,20 @@
+
+.viewPublicKeyLink {
+ font-size: 11px;
+}
+
+.captionPanel {
+ margin-bottom: 2px;
+}
+
+.keyPath {
+ background-color: transparent;
+}
+
+.keyPathNone {
+ color: gray;
+}
+
+.sshButtonPanel {
+ margin-top: 4px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.java
new file mode 100644
index 0000000..b0a5608
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/SshKeyWidget.java
@@ -0,0 +1,214 @@
+/*
+ * SshKeyWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.core.client.widget.NullProgressIndicator;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+
+public class SshKeyWidget extends Composite
+{
+ public SshKeyWidget(GitServerOperations server, String textWidth)
+
+ {
+ server_ = server;
+ progressIndicator_ = new NullProgressIndicator();
+
+ FlowPanel panel = new FlowPanel();
+
+ // caption panel
+ HorizontalPanel captionPanel = new HorizontalPanel();
+ captionPanel.addStyleName(RES.styles().captionPanel());
+ captionPanel.setWidth(textWidth);
+ Label sshKeyPathLabel = new Label("SSH RSA Key:");
+ captionPanel.add(sshKeyPathLabel);
+ captionPanel.setCellHorizontalAlignment(
+ sshKeyPathLabel,
+ HasHorizontalAlignment.ALIGN_LEFT);
+
+ HorizontalPanel linkPanel = new HorizontalPanel();
+ publicKeyLink_ = new HyperlinkLabel("View public key");
+ publicKeyLink_.addStyleName(RES.styles().viewPublicKeyLink());
+ publicKeyLink_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ viewPublicKey();
+ }
+ });
+ linkPanel.add(publicKeyLink_);
+ captionPanel.add(publicKeyLink_);
+ captionPanel.setCellHorizontalAlignment(
+ publicKeyLink_,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+ panel.add(captionPanel);
+
+
+ // chooser
+ txtSshKeyPath_ = new TextBox();
+ txtSshKeyPath_.addStyleName(RES.styles().keyPath());
+ txtSshKeyPath_.setReadOnly(true);
+ txtSshKeyPath_.setWidth(textWidth);
+ panel.add(txtSshKeyPath_);
+
+
+ // ssh key path action buttons
+ HorizontalPanel sshButtonPanel = new HorizontalPanel();
+ sshButtonPanel.addStyleName(RES.styles().sshButtonPanel());
+ createKeyButton_ = new SmallButton();
+ createKeyButton_.setText("Create RSA Key...");
+ createKeyButton_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ showCreateKeyDialog();
+ }
+ });
+ sshButtonPanel.add(createKeyButton_);
+ panel.add(sshButtonPanel);
+
+ initWidget(panel);
+ }
+
+ public void setProgressIndicator(ProgressIndicator progressIndicator)
+ {
+ progressIndicator_ = progressIndicator;
+ }
+
+ public void setRsaSshKeyPath(String rsaSshKeyPath,
+ boolean haveRsaSshKey)
+ {
+ rsaSshKeyPath_ = rsaSshKeyPath;
+ if (haveRsaSshKey)
+ setSshKey(rsaSshKeyPath_);
+ else
+ setSshKey(NONE);
+ }
+
+ private void setSshKey(String keyPath)
+ {
+ txtSshKeyPath_.setText(keyPath);
+ if (keyPath.equals(NONE))
+ {
+ publicKeyLink_.setVisible(false);
+ txtSshKeyPath_.addStyleName(RES.styles().keyPathNone());
+ }
+ else
+ {
+ publicKeyLink_.setVisible(true);
+ txtSshKeyPath_.removeStyleName(RES.styles().keyPathNone());
+ }
+ }
+
+ private void showCreateKeyDialog()
+ {
+ new CreateKeyDialog(
+ rsaSshKeyPath_,
+ server_,
+ new OperationWithInput<String>()
+ {
+ @Override
+ public void execute(String keyPath)
+ {
+ if (keyPath != null)
+ setSshKey(keyPath);
+ else
+ setSshKey(NONE);
+ }
+ }).showModal();
+ }
+
+
+ private void viewPublicKey()
+ {
+ progressIndicator_.onProgress("Reading public key...");
+
+ // compute path to public key
+ FileSystemItem privKey =
+ FileSystemItem.createFile(txtSshKeyPath_.getText());
+ FileSystemItem keyDir = privKey.getParentPath();
+ final String keyPath = keyDir.completePath(privKey.getStem() + ".pub");
+
+ server_.gitSshPublicKey(keyPath,
+ new ServerRequestCallback<String> () {
+
+ @Override
+ public void onResponseReceived(String publicKeyContents)
+ {
+ progressIndicator_.onCompleted();
+
+ new ShowPublicKeyDialog("Public Key",
+ publicKeyContents).showModal();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ String msg = "Error attempting to read key '" + keyPath + "' (" +
+ error.getUserMessage() + ")";
+ progressIndicator_.onError(msg);
+ }
+ });
+ }
+
+
+ static interface Styles extends CssResource
+ {
+ String viewPublicKeyLink();
+ String captionPanel();
+ String sshButtonPanel();
+ String keyPath();
+ String keyPathNone();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("SshKeyWidget.css")
+ Styles styles();
+ }
+
+ static Resources RES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ private HyperlinkLabel publicKeyLink_;
+ private TextBox txtSshKeyPath_;
+ private SmallButton createKeyButton_;
+
+ private final GitServerOperations server_;
+ private ProgressIndicator progressIndicator_;
+ private String rsaSshKeyPath_;
+
+ private static final String NONE = "(None)";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPath.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPath.java
new file mode 100644
index 0000000..b218f0c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPath.java
@@ -0,0 +1,138 @@
+/*
+ * StatusAndPath.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.core.client.StringUtil;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class StatusAndPath
+{
+ public static class PathComparator implements Comparator<StatusAndPath>
+ {
+ private String[] splitDirAndName(String path)
+ {
+ int index = path.lastIndexOf("/");
+ if (index < 0)
+ index = path.lastIndexOf("\\");
+ if (index < 0)
+ return new String[] { "", path };
+ else
+ return new String[] { path.substring(0, index),
+ path.substring(index + 1) };
+ }
+
+ @Override
+ public int compare(StatusAndPath a, StatusAndPath b)
+ {
+ String[] splitA = splitDirAndName(a.getPath());
+ String[] splitB = splitDirAndName(b.getPath());
+ int result = splitA[0].compareTo(splitB[0]);
+ if (result == 0)
+ result = splitA[1].compareTo(splitB[1]);
+ return result;
+ }
+ }
+
+ public static ArrayList<StatusAndPath> fromInfos(
+ JsArray<StatusAndPathInfo> infos)
+ {
+ if (infos == null)
+ return null;
+
+ ArrayList<StatusAndPath> result = new ArrayList<StatusAndPath>();
+ for (int i = 0; i < infos.length(); i++)
+ {
+ result.add(new StatusAndPath(infos.get(i)));
+ }
+ return result;
+ }
+
+ public static StatusAndPath fromInfo(StatusAndPathInfo info)
+ {
+ return info == null ? null : new StatusAndPath(info);
+ }
+
+ private StatusAndPath(StatusAndPathInfo info)
+ {
+ status_ = StringUtil.notNull(info.getStatus());
+ path_ = info.getPath();
+ rawPath_ = info.getRawPath();
+ changelist_ = info.getChangelist();
+ discardable_ = info.isDiscardable();
+ directory_ = info.isDirectory();
+ }
+
+ public String getStatus()
+ {
+ return status_;
+ }
+
+ public String getPath()
+ {
+ return path_;
+ }
+
+ public String getRawPath()
+ {
+ return rawPath_;
+ }
+
+ public String getChangelist()
+ {
+ return changelist_;
+ }
+
+ public boolean isDiscardable()
+ {
+ return discardable_;
+ }
+
+ public boolean isDirectory()
+ {
+ return directory_;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ StatusAndPath that = (StatusAndPath) o;
+ return path_ != null ? path_.equals(that.path_) : that.path_ == null;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return path_ != null ? path_.hashCode() : 0;
+ }
+
+ public StatusAndPathInfo toInfo()
+ {
+ return StatusAndPathInfo.create(status_,
+ path_,
+ rawPath_,
+ discardable_,
+ directory_);
+ }
+
+ private final String status_;
+ private final String path_;
+ private final String rawPath_;
+ private final boolean discardable_;
+ private final boolean directory_;
+ private final String changelist_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPathInfo.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPathInfo.java
new file mode 100644
index 0000000..da686bb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/StatusAndPathInfo.java
@@ -0,0 +1,61 @@
+/*
+ * StatusAndPathInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class StatusAndPathInfo extends JavaScriptObject
+{
+ protected StatusAndPathInfo()
+ {}
+
+ public native final String getStatus() /*-{
+ return this.status;
+ }-*/;
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final String getRawPath() /*-{
+ return this.raw_path;
+ }-*/;
+
+ public native final String getChangelist() /*-{
+ return this.changelist || "";
+ }-*/;
+
+ public native final boolean isDiscardable() /*-{
+ return !!this.discardable;
+ }-*/;
+
+ public native final boolean isDirectory() /*-{
+ return !!this.is_directory;
+ }-*/;
+
+ public native static StatusAndPathInfo create(String status,
+ String path,
+ String rawPath,
+ boolean discardable,
+ boolean directory) /*-{
+ return {
+ status: status,
+ path: path,
+ raw_path: rawPath,
+ discardable: discardable,
+ is_directory: directory
+ };
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSConstants.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSConstants.java
new file mode 100644
index 0000000..000b792
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSConstants.java
@@ -0,0 +1,23 @@
+/*
+ * VCSConstants.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+public class VCSConstants
+{
+ public static final String GIT_ID = "Git";
+ public static final String SVN_ID = "SVN";
+ public static final String NO_ID = "none";
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSServerOperations.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSServerOperations.java
new file mode 100644
index 0000000..b67113f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/VCSServerOperations.java
@@ -0,0 +1,33 @@
+/*
+ * VCSServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+public interface VCSServerOperations extends CryptoServerOperations
+{
+ void askpassCompleted(String value, boolean remember,
+ ServerRequestCallback<Void> requestCallback);
+
+ void createSshKey(CreateKeyOptions options,
+ ServerRequestCallback<CreateKeyResult> requestCallback);
+
+ void vcsClone(VcsCloneOptions options,
+ ServerRequestCallback<ConsoleProcess> requestCallback);
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsCloneOptions.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsCloneOptions.java
new file mode 100644
index 0000000..3ba2ca3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsCloneOptions.java
@@ -0,0 +1,60 @@
+/*
+ * VcsCloneOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class VcsCloneOptions extends JavaScriptObject
+{
+ protected VcsCloneOptions()
+ {
+ }
+
+ public native static final VcsCloneOptions create(String vcsName,
+ String repoUrl,
+ String username,
+ String directoryName,
+ String parentPath)
+ /*-{
+ var options = new Object();
+ options.vcs_name = vcsName;
+ options.repo_url = repoUrl;
+ options.username = username;
+ options.directory_name = directoryName;
+ options.parent_path = parentPath;
+ return options;
+ }-*/;
+
+ public native final String getVcsName() /*-{
+ return this.vcs_name;
+ }-*/;
+
+
+ public native final String getRepoUrl() /*-{
+ return this.repo_url;
+ }-*/;
+
+ public native final String getUsername() /*-{
+ return this.username;
+ }-*/;
+
+ public native final String getDirectoryName() /*-{
+ return this.directory_name;
+ }-*/;
+
+ public native final String getParentPath() /*-{
+ return this.parent_path;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsHelpLink.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsHelpLink.java
new file mode 100644
index 0000000..a503029
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/VcsHelpLink.java
@@ -0,0 +1,26 @@
+/*
+ * VcsHelpLink.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.common.vcs;
+
+import org.rstudio.studio.client.common.HelpLink;
+
+public class VcsHelpLink extends HelpLink
+{
+ public VcsHelpLink()
+ {
+ super("Using Version Control with RStudio", "using_version_control");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/Ignore.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/Ignore.java
new file mode 100644
index 0000000..fced582
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/Ignore.java
@@ -0,0 +1,321 @@
+/*
+ * Ignore.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs.ignore;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.vcs.ProcessResult;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Ignore
+{
+ public interface Strategy
+ {
+ public interface Filter
+ {
+ boolean includeFile(FileSystemItem file);
+ }
+
+ String getDialogCaption();
+
+ String getIgnoresCaption();
+
+ String getHelpLinkName();
+
+ Filter getFilter();
+
+ void getIgnores(String path,
+ ServerRequestCallback<ProcessResult> requestCallback);
+
+ void setIgnores(String path,
+ String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback);
+ }
+
+ public interface Display
+ {
+ void setDialogCaption(String caption);
+ void setIgnoresCaption(String caption);
+ void setHelpLinkName(String helpLinkName);
+ ProgressIndicator progressIndicator();
+ HasClickHandlers saveButton();
+
+ void setCurrentPath(String path);
+ String getCurrentPath();
+ HandlerRegistration addPathChangedHandler(
+ ValueChangeHandler<String> handler);
+
+ void setIgnored(String ignored);
+ String getIgnored();
+
+ void focusIgnored();
+
+ void scrollToBottom();
+
+ void showModal();
+ }
+
+ @Inject
+ public Ignore(GlobalDisplay globalDisplay,
+ Session session,
+ Provider<Display> pDisplay)
+ {
+ globalDisplay_ = globalDisplay;
+ session_ = session;
+ pDisplay_ = pDisplay;
+ }
+
+ public void showDialog(ArrayList<String> paths, Strategy strategy)
+ {
+ IgnoreList ignoreList = createIgnoreList(paths, strategy.getFilter());
+ if (ignoreList != null)
+ showDialog(ignoreList, strategy);
+ }
+
+ public void showDialog(final IgnoreList ignoreList,
+ final Strategy strategy)
+ {
+ // show progress
+ final ProgressIndicator globalIndicator = new GlobalProgressDelayer(
+ globalDisplay_,
+ 500,
+ "Getting ignored files for path...").getIndicator();
+
+ // get existing ignores
+ final String fullPath = projPathToFullPath(ignoreList.getPath());
+ strategy.getIgnores(fullPath,
+ new ServerRequestCallback<ProcessResult>() {
+
+ @Override
+ public void onResponseReceived(final ProcessResult result)
+ {
+ globalIndicator.onCompleted();
+
+ if (checkForProcessError(strategy.getDialogCaption(), result))
+ return;
+
+ // show the ignore dialog
+ String ignored = getIgnored(ignoreList, result.getOutput());
+ showDialog(fullPath, ignored, strategy);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalIndicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+ private void showDialog(final String initialPath,
+ String ignores,
+ final Strategy strategy)
+ {
+ final Display display = pDisplay_.get();
+ final ProgressIndicator indicator = display.progressIndicator();
+
+ display.setDialogCaption(strategy.getDialogCaption());
+ display.setIgnoresCaption(strategy.getIgnoresCaption());
+ display.setHelpLinkName(strategy.getHelpLinkName());
+ display.setCurrentPath(initialPath);
+ display.setIgnored(ignores);
+ display.scrollToBottom();
+
+ display.addPathChangedHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ display.setIgnored("");
+
+ indicator.onProgress("Getting ignored files for path...");
+
+ strategy.getIgnores(display.getCurrentPath(),
+ new ServerRequestCallback<ProcessResult>() {
+
+ @Override
+ public void onResponseReceived(final ProcessResult result)
+ {
+ indicator.clearProgress();
+
+ if (checkForProcessError(strategy.getDialogCaption(), result))
+ return;
+
+ display.setIgnored(result.getOutput());
+ display.focusIgnored();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }});
+ }
+ });
+
+ display.saveButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ indicator.onProgress("Setting ignored files for path...");
+
+ strategy.setIgnores(
+ display.getCurrentPath(),
+ display.getIgnored(),
+ new ServerRequestCallback<ProcessResult>() {
+
+ @Override
+ public void onResponseReceived(ProcessResult result)
+ {
+ indicator.onCompleted();
+ checkForProcessError(strategy.getDialogCaption(), result);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+
+ }
+ });
+
+ display.showModal();
+ }
+
+ private String projPathToFullPath(String projPath)
+ {
+ FileSystemItem projDir = session_.getSessionInfo().getActiveProjectDir();
+ return projPath.length() > 0 ? projDir.completePath(projPath) :
+ projDir.getPath();
+ }
+
+ // compute the new list of ignores based on the initial/existing
+ // set of paths and path(s) the user wants to add
+ private IgnoreList createIgnoreList(ArrayList<String> paths,
+ Ignore.Strategy.Filter filter)
+ {
+ // special case for empty path list -- make the project root the
+ // parent path and return an empty list
+ if (paths.size() == 0)
+ return new IgnoreList("", new ArrayList<String>());
+
+ // get the parent path of the first element
+ FileSystemItem firstPath = FileSystemItem.createFile(paths.get(0));
+ String parentPath = firstPath.getParentPathString();
+
+ // confirm that all of the elements start with that path and take the
+ // remainder of their paths for our list
+ ArrayList<String> ignored = new ArrayList<String>();
+ for (String path : paths)
+ {
+ String thisParent =
+ FileSystemItem.createFile(path).getParentPathString();
+
+ if (!parentPath.equals(thisParent))
+ {
+ GlobalDisplay gDisp = RStudioGinjector.INSTANCE.getGlobalDisplay();
+ gDisp.showMessage(
+ MessageDialog.ERROR,
+ "Error: Multiple Directories",
+ "The selected files are not all within the same directory " +
+ "(you can only ignore multiple files in one operation if " +
+ "they are located within the same directory).");
+
+ return null;
+ }
+
+ // apply a filter if we have one
+ if (filter != null)
+ {
+ FileSystemItem file = FileSystemItem.createFile(path);
+ if (!filter.includeFile(file))
+ continue;
+ }
+
+ // compute the parent relative directory
+ if (parentPath.length() == 0)
+ ignored.add(path);
+ else
+ ignored.add(path.substring(thisParent.length() + 1));
+ }
+
+ return new IgnoreList(parentPath, ignored);
+ }
+
+ private String getIgnored(IgnoreList ignoreList, String existingIgnored)
+ {
+ // split existing ignored into list
+ ArrayList<String> ignored = new ArrayList<String>();
+ Iterable<String> existing = StringUtil.getLineIterator(existingIgnored);
+ for (String item : existing)
+ {
+ item = item.trim();
+ if (item.length() > 0)
+ ignored.add(item);
+ }
+
+
+ // for any element not already in the list add it
+ for (String item : ignoreList.getFiles())
+ if (!ignored.contains(item))
+ ignored.add(item);
+
+ // return as a string
+ return StringUtil.join(ignored, "\n");
+ }
+
+ // check for an error and show the console progress dialog
+ // if there was one
+ private boolean checkForProcessError(String caption, ProcessResult result)
+ {
+ if (result.getExitCode() != 0)
+ {
+ new ConsoleProgressDialog(caption,
+ result.getOutput(),
+ result.getExitCode()).showModal();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private final Session session_;
+ private final Provider<Display> pDisplay_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.css b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.css
new file mode 100644
index 0000000..17da534
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.css
@@ -0,0 +1,14 @@
+
+
+.dirChooser {
+ margin-bottom: 8px;
+}
+
+.ignoresCaption {
+ margin-bottom: 2px;
+}
+
+.editorFrame {
+ border: 1px solid #BBB;
+ background-color: white;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.java
new file mode 100644
index 0000000..82a0b82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreDialog.java
@@ -0,0 +1,199 @@
+/*
+ * IgnoreDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs.ignore;
+
+import org.rstudio.core.client.widget.CaptionWithHelp;
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class IgnoreDialog extends ModalDialogBase
+ implements Ignore.Display
+{
+ @Inject
+ public IgnoreDialog()
+ {
+ dirChooser_ = new DirectoryChooserTextBox("Directory:",
+ "",
+ null);
+ dirChooser_.addStyleName(RES.styles().dirChooser());
+
+ ignoresCaption_ = new CaptionWithHelp("Ignore:",
+ "Specifying ignored files");
+ ignoresCaption_.setIncludeVersionInfo(false);
+ ignoresCaption_.addStyleName(RES.styles().ignoresCaption());
+
+ editor_ = new AceEditor();
+ editor_.setUseWrapMode(false);
+ editor_.setShowLineNumbers(false);
+
+ saveButton_ = new ThemedButton("Save", (ClickHandler)null);
+ addButton(saveButton_);
+ addCancelButton();
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+
+ progressIndicator_ = addProgressIndicator();
+ }
+
+ @Override
+ public void setDialogCaption(String caption)
+ {
+ setText(caption);
+ }
+
+ @Override
+ public void setIgnoresCaption(String caption)
+ {
+ ignoresCaption_.setCaption(caption);
+ }
+
+ @Override
+ public void setHelpLinkName(String helpLinkName)
+ {
+ ignoresCaption_.setRStudioLinkName(helpLinkName);
+ }
+
+ @Override
+ public ProgressIndicator progressIndicator()
+ {
+ return progressIndicator_;
+ }
+
+ @Override
+ public HasClickHandlers saveButton()
+ {
+ return saveButton_;
+ }
+
+ @Override
+ public void setCurrentPath(String path)
+ {
+ dirChooser_.setText(path);
+ }
+
+ @Override
+ public String getCurrentPath()
+ {
+ return dirChooser_.getText();
+ }
+
+ @Override
+ public HandlerRegistration addPathChangedHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return dirChooser_.addValueChangeHandler(handler);
+ }
+
+
+ @Override
+ public void setIgnored(String ignored)
+ {
+ editor_.setCode(ignored.trim(), false);
+ }
+
+ @Override
+ public void focusIgnored()
+ {
+ editor_.focus();
+ }
+
+ @Override
+ public String getIgnored()
+ {
+ String ignored = editor_.getCode();
+ if (ignored.length() > 0)
+ ignored = ignored.trim() + "\n";
+ return ignored;
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ editor_.scrollToBottom();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel verticalPanel = new VerticalPanel();
+
+ verticalPanel.add(dirChooser_);
+
+ verticalPanel.add(ignoresCaption_);
+
+ final String aceWidth = "400px";
+ final String aceHeight = "300px";
+
+ Widget editorWidget = editor_.getWidget();
+ editorWidget.setSize(aceWidth, aceHeight);
+
+ SimplePanel panel = new SimplePanel();
+ panel.addStyleName(RES.styles().editorFrame());
+ panel.setSize(aceWidth, aceHeight);
+ panel.setWidget(editor_.getWidget());
+ verticalPanel.add(panel);
+
+ return verticalPanel;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ editor_.focus();
+ }
+
+ static interface Styles extends CssResource
+ {
+ String dirChooser();
+ String ignoresCaption();
+ String editorFrame();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("IgnoreDialog.css")
+ Styles styles();
+ }
+
+ static Resources RES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ private final DirectoryChooserTextBox dirChooser_;
+ private final CaptionWithHelp ignoresCaption_;
+ private final AceEditor editor_ ;
+ private final ThemedButton saveButton_;
+ private final ProgressIndicator progressIndicator_;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreList.java b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreList.java
new file mode 100644
index 0000000..2f18b3e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/common/vcs/ignore/IgnoreList.java
@@ -0,0 +1,32 @@
+/*
+ * IgnoreList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.vcs.ignore;
+
+import java.util.ArrayList;
+
+public class IgnoreList
+{
+ public IgnoreList(String path, ArrayList<String> files)
+ {
+ path_ = path;
+ files_ = files;
+ }
+
+ public String getPath() { return path_; }
+ public ArrayList<String> getFiles() { return files_; }
+
+ private final String path_;
+ private final ArrayList<String> files_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreview.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreview.java
new file mode 100644
index 0000000..ac2fc34
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreview.java
@@ -0,0 +1,47 @@
+/*
+ * HTMLPreview.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.htmlpreview.events.ShowHTMLPreviewEvent;
+import org.rstudio.studio.client.htmlpreview.events.ShowHTMLPreviewHandler;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class HTMLPreview
+{
+ @Inject
+ public HTMLPreview(EventBus eventBus,
+ final SatelliteManager satelliteManager)
+ {
+ eventBus.addHandler(ShowHTMLPreviewEvent.TYPE,
+ new ShowHTMLPreviewHandler() {
+ @Override
+ public void onShowHTMLPreview(ShowHTMLPreviewEvent event)
+ {
+ // open the window
+ satelliteManager.openSatellite(HTMLPreviewApplication.NAME,
+ event.getParams(),
+ new Size(850,1100));
+
+ }
+ });
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewApplication.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewApplication.java
new file mode 100644
index 0000000..7353c28
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewApplication.java
@@ -0,0 +1,40 @@
+/*
+ * HTMLPreviewApplication.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview;
+
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteApplication;
+import org.rstudio.studio.client.htmlpreview.ui.HTMLPreviewApplicationView;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class HTMLPreviewApplication extends SatelliteApplication
+{
+ public static final String NAME = "preview";
+
+ @Inject
+ public HTMLPreviewApplication(HTMLPreviewApplicationView view,
+ Satellite satellite,
+ Provider<AceThemes> pAceThemes,
+ ApplicationUncaughtExceptionHandler exHandler)
+ {
+ super(NAME, view, satellite, pAceThemes, exHandler);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewPresenter.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewPresenter.java
new file mode 100644
index 0000000..5989952
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/HTMLPreviewPresenter.java
@@ -0,0 +1,437 @@
+/*
+ * HTMLPreviewPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.fileexport.FileExport;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.rpubs.RPubsPresenter;
+import org.rstudio.studio.client.common.rpubs.events.RPubsUploadStatusEvent;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewCompletedEvent;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewOutputEvent;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewStartedEvent;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewResult;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewServerOperations;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.StringStateValue;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class HTMLPreviewPresenter implements IsWidget, RPubsPresenter.Context
+{
+ public interface Binder extends CommandBinder<Commands, HTMLPreviewPresenter>
+ {}
+
+ public interface Display extends IsWidget
+ {
+ void showProgress(String caption);
+ void setProgressCaption(String caption);
+ void showProgressOutput(String output);
+ void stopProgress();
+ void closeProgress();
+ HandlerRegistration addProgressClickHandler(ClickHandler handler);
+
+ void showPreview(String url,
+ String htmlFile,
+ boolean enableSaveAs,
+ boolean enablePublish,
+ boolean enableRefresh,
+ boolean enableShowLog);
+
+ void print();
+
+ String getDocumentTitle();
+
+ void setPublishButtonLabel(String label);
+
+ void showLog(String log);
+ }
+
+ @Inject
+ public HTMLPreviewPresenter(Display view,
+ Binder binder,
+ final Commands commands,
+ GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ Satellite satellite,
+ Session session,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileSystemContext,
+ HTMLPreviewServerOperations server,
+ RPubsPresenter rpubsPresenter,
+ Provider<FileExport> pFileExport)
+ {
+ view_ = view;
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ session_ = session;
+ fileDialogs_ = fileDialogs;
+ fileSystemContext_ = fileSystemContext;
+ pFileExport_ = pFileExport;
+
+ rpubsPresenter.setContext(this);
+
+ binder.bind(commands, this);
+
+ // disable rpubs if requested
+ if (!session.getSessionInfo().getAllowRpubsPublish())
+ {
+ commands.publishHTML().remove();
+ }
+
+ // map Ctrl-R to our internal refresh handler
+ Event.addNativePreviewHandler(new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ NativeEvent ne = event.getNativeEvent();
+ int mod = KeyboardShortcut.getModifierValue(ne);
+ if ((mod == KeyboardShortcut.META ||
+ (mod == KeyboardShortcut.CTRL && !BrowseCap.hasMetaKey()))
+ && ne.getKeyCode() == 'R')
+ {
+ ne.preventDefault();
+ ne.stopPropagation();
+ commands.refreshHtmlPreview().execute();
+ }
+ }
+ }
+ });
+
+ satellite.addCloseHandler(new CloseHandler<Satellite>()
+ {
+ @Override
+ public void onClose(CloseEvent<Satellite> event)
+ {
+ if (previewRunning_)
+ terminateRunningPreview();
+ }
+ });
+
+ eventBus.addHandler(HTMLPreviewStartedEvent.TYPE,
+ new HTMLPreviewStartedEvent.Handler()
+ {
+ @Override
+ public void onHTMLPreviewStarted(HTMLPreviewStartedEvent event)
+ {
+ previewRunning_ = true;
+ lastPreviewOutput_ = new StringBuilder();
+ view_.showProgress("Knitting...");
+ view_.addProgressClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (previewRunning_)
+ terminateRunningPreview();
+ else
+ view_.closeProgress();
+ }
+
+ });
+ }
+ });
+
+ eventBus.addHandler(HTMLPreviewOutputEvent.TYPE,
+ new HTMLPreviewOutputEvent.Handler()
+ {
+ @Override
+ public void onHTMLPreviewOutput(HTMLPreviewOutputEvent event)
+ {
+ String output = event.getOutput();
+ view_.showProgressOutput(output);
+ lastPreviewOutput_.append(output);
+ }
+ });
+
+ eventBus.addHandler(HTMLPreviewCompletedEvent.TYPE,
+ new HTMLPreviewCompletedEvent.Handler()
+ {
+ @Override
+ public void onHTMLPreviewCompleted(HTMLPreviewCompletedEvent event)
+ {
+ previewRunning_ = false;
+
+ HTMLPreviewResult result = event.getResult();
+ if (result.getSucceeded())
+ {
+ lastSuccessfulPreview_ = result;
+ view_.closeProgress();
+ view_.showPreview(
+ server_.getApplicationURL(result.getPreviewURL()),
+ result.getHtmlFile(),
+ result.getEnableSaveAs(),
+ isMarkdownFile(result.getSourceFile()) &&
+ session_.getSessionInfo().getAllowRpubsPublish(),
+ result.getEnableRefresh(),
+ lastPreviewOutput_.length() > 0);
+
+ isPublished_ = result.getPreviouslyPublished();
+ if (isPublished_)
+ view_.setPublishButtonLabel("Republish");
+ }
+ else
+ {
+ view_.setProgressCaption("Preview failed");
+ view_.stopProgress();
+ }
+ }
+ });
+
+ eventBus.addHandler(RPubsUploadStatusEvent.TYPE,
+ new org.rstudio.studio.client.common.rpubs.events.RPubsUploadStatusEvent.Handler()
+ {
+ @Override
+ public void onRPubsPublishStatus(
+ RPubsUploadStatusEvent event)
+ {
+ // make sure it applies to our context
+ RPubsUploadStatusEvent.Status status = event.getStatus();
+ if (!status.getContextId().equals(getContextId()))
+ return;
+
+ if (StringUtil.isNullOrEmpty(status.getError())
+ && !isPublished_)
+ {
+ isPublished_ = true;
+ view_.setPublishButtonLabel("Republish");
+ }
+ }
+ });
+
+ new StringStateValue(
+ MODULE_HTML_PREVIEW,
+ KEY_SAVEAS_DIR,
+ ClientState.PERSISTENT,
+ session_.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(String value)
+ {
+ savePreviewDir_ = value;
+ }
+
+ @Override
+ protected String getValue()
+ {
+ return savePreviewDir_;
+ }
+ };
+ }
+
+ private boolean isMarkdownFile(String file)
+ {
+ FileSystemItem fsi = FileSystemItem.createFile(file);
+ FileTypeRegistry ftReg = RStudioGinjector.INSTANCE.getFileTypeRegistry();
+ FileType fileType = ftReg.getTypeForFile(fsi);
+ return (fileType != null) &&
+ (fileType.equals(FileTypeRegistry.MARKDOWN) ||
+ fileType.equals(FileTypeRegistry.RMARKDOWN));
+ }
+
+ public void onActivated(HTMLPreviewParams params)
+ {
+ lastPreviewParams_ = params;
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Handler
+ public void onOpenHtmlExternal()
+ {
+ if (lastSuccessfulPreview_ != null)
+ {
+ String htmlFile = lastSuccessfulPreview_.getHtmlFile();
+ globalDisplay_.showHtmlFile(htmlFile);
+ }
+ }
+
+ @Handler
+ public void onSaveHtmlPreviewAsLocalFile()
+ {
+ if (lastSuccessfulPreview_ != null)
+ {
+ final FileSystemItem htmlFile = FileSystemItem.createFile(
+ lastSuccessfulPreview_.getHtmlFile());
+ pFileExport_.get().export("Download to Local File",
+ "web page",
+ htmlFile);
+ }
+ }
+
+ @Handler
+ public void onSaveHtmlPreviewAs()
+ {
+ if (lastSuccessfulPreview_ != null)
+ {
+ FileSystemItem defaultDir = savePreviewDir_ != null ?
+ FileSystemItem.createDir(savePreviewDir_) :
+ FileSystemItem.home();
+
+ final FileSystemItem sourceFile = FileSystemItem.createFile(
+ lastSuccessfulPreview_.getHtmlFile());
+
+ FileSystemItem initialFilePath =
+ FileSystemItem.createFile(defaultDir.completePath(
+ sourceFile.getStem()));
+
+ fileDialogs_.saveFile(
+ "Save File As",
+ fileSystemContext_,
+ initialFilePath,
+ sourceFile.getExtension(),
+ false,
+ new ProgressOperationWithInput<FileSystemItem>(){
+
+ @Override
+ public void execute(FileSystemItem targetFile,
+ ProgressIndicator indicator)
+ {
+ if (targetFile == null || sourceFile.equalTo(targetFile))
+ {
+ indicator.onCompleted();
+ return;
+ }
+
+ indicator.onProgress("Saving File...");
+
+ server_.copyFile(sourceFile,
+ targetFile,
+ true,
+ new VoidServerRequestCallback(indicator));
+
+ savePreviewDir_ = targetFile.getParentPathString();
+ session_.persistClientState();
+ }
+ });
+ }
+ }
+
+ @Handler
+ public void onRefreshHtmlPreview()
+ {
+ server_.previewHTML(lastPreviewParams_,
+ new SimpleRequestCallback<Boolean>());
+ }
+
+ @Handler
+ public void onShowHtmlPreviewLog()
+ {
+ view_.showLog(lastPreviewOutput_.toString());
+ }
+
+ @Override
+ public String getContextId()
+ {
+ return "HTMLPreview";
+ }
+
+ @Override
+ public String getTitle()
+ {
+ String title = StringUtil.notNull(view_.getDocumentTitle());
+ if (title.length() == 0)
+ {
+ String htmlFile = getHtmlFile();
+ if (htmlFile != null)
+ {
+ FileSystemItem fsi = FileSystemItem.createFile(htmlFile);
+ return fsi.getStem();
+ }
+ else
+ {
+ return "(Untitled)";
+ }
+ }
+ else
+ {
+ return title;
+ }
+ }
+
+ @Override
+ public String getHtmlFile()
+ {
+ if (lastSuccessfulPreview_ != null)
+ return lastSuccessfulPreview_.getHtmlFile();
+ else
+ return null;
+ }
+
+ @Override
+ public boolean isPublished()
+ {
+ return isPublished_;
+ }
+
+ private void terminateRunningPreview()
+ {
+ server_.terminatePreviewHTML(new VoidServerRequestCallback());
+ }
+
+ private final Display view_;
+ private boolean previewRunning_ = false;
+ private HTMLPreviewParams lastPreviewParams_;
+ private HTMLPreviewResult lastSuccessfulPreview_;
+ private StringBuilder lastPreviewOutput_ = new StringBuilder();
+
+ private String savePreviewDir_;
+ private boolean isPublished_;
+ private static final String MODULE_HTML_PREVIEW = "html_preview";
+ private static final String KEY_SAVEAS_DIR = "saveAsDir";
+
+ private final GlobalDisplay globalDisplay_;
+ private final FileDialogs fileDialogs_;
+ private final Session session_;
+ private final RemoteFileSystemContext fileSystemContext_;
+ private final HTMLPreviewServerOperations server_;
+ private final Provider<FileExport> pFileExport_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewCompletedEvent.java
new file mode 100644
index 0000000..6c5c1d8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewCompletedEvent.java
@@ -0,0 +1,54 @@
+/*
+ * HTMLPreviewCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.events;
+
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewResult;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class HTMLPreviewCompletedEvent extends GwtEvent<HTMLPreviewCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onHTMLPreviewCompleted(HTMLPreviewCompletedEvent event);
+ }
+
+ public HTMLPreviewCompletedEvent(HTMLPreviewResult result)
+ {
+ result_ = result;
+ }
+
+ public HTMLPreviewResult getResult()
+ {
+ return result_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onHTMLPreviewCompleted(this);
+ }
+
+ private final HTMLPreviewResult result_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewOutputEvent.java
new file mode 100644
index 0000000..bc956ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewOutputEvent.java
@@ -0,0 +1,52 @@
+/*
+ * HTMLPreviewOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class HTMLPreviewOutputEvent extends GwtEvent<HTMLPreviewOutputEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onHTMLPreviewOutput(HTMLPreviewOutputEvent event);
+ }
+
+ public HTMLPreviewOutputEvent(String output)
+ {
+ output_ = output;
+ }
+
+ public String getOutput()
+ {
+ return output_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onHTMLPreviewOutput(this);
+ }
+
+ private String output_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewStartedEvent.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewStartedEvent.java
new file mode 100644
index 0000000..51ff14e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/HTMLPreviewStartedEvent.java
@@ -0,0 +1,64 @@
+/*
+ * HTMLPreviewStartedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class HTMLPreviewStartedEvent extends GwtEvent<HTMLPreviewStartedEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public final native String getTargetFile() /*-{
+ return this.target_file;
+ }-*/;
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onHTMLPreviewStarted(HTMLPreviewStartedEvent event);
+ }
+
+ public HTMLPreviewStartedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getTargetFile()
+ {
+ return data_.getTargetFile();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onHTMLPreviewStarted(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewEvent.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewEvent.java
new file mode 100644
index 0000000..ec60dfa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ShowHTMLPreviewEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.events;
+
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowHTMLPreviewEvent extends GwtEvent<ShowHTMLPreviewHandler>
+{
+ public static final GwtEvent.Type<ShowHTMLPreviewHandler> TYPE =
+ new GwtEvent.Type<ShowHTMLPreviewHandler>();
+
+ public ShowHTMLPreviewEvent(HTMLPreviewParams params)
+ {
+ params_ = params;
+ }
+
+ public HTMLPreviewParams getParams()
+ {
+ return params_;
+ }
+
+ @Override
+ protected void dispatch(ShowHTMLPreviewHandler handler)
+ {
+ handler.onShowHTMLPreview(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowHTMLPreviewHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private HTMLPreviewParams params_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewHandler.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewHandler.java
new file mode 100644
index 0000000..2884f85
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/events/ShowHTMLPreviewHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowHTMLPreviewHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowHTMLPreviewHandler extends EventHandler
+{
+ void onShowHTMLPreview(ShowHTMLPreviewEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewParams.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewParams.java
new file mode 100644
index 0000000..ba05671
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewParams.java
@@ -0,0 +1,58 @@
+/*
+ * HTMLPreviewParams.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class HTMLPreviewParams extends JavaScriptObject
+{
+ protected HTMLPreviewParams()
+ {
+ }
+
+ public final static native HTMLPreviewParams create(String path,
+ String encoding,
+ boolean isMarkdown,
+ boolean requiresKnit,
+ boolean isNotebook)/*-{
+ var params = new Object();
+ params.path = path;
+ params.encoding = encoding;
+ params.is_markdown = isMarkdown;
+ params.requires_knit = requiresKnit;
+ params.is_notebook = isNotebook;
+ return params;
+ }-*/;
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public final native String getEncoding() /*-{
+ return this.encoding;
+ }-*/;
+
+ public final native boolean isMarkdown() /*-{
+ return this.is_markdown;
+ }-*/;
+
+ public final native boolean getRequiresKnit() /*-{
+ return this.requires_knit;
+ }-*/;
+
+ public final native boolean isNotebook() /*-{
+ return this.is_notebook;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewResult.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewResult.java
new file mode 100644
index 0000000..59ac669
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewResult.java
@@ -0,0 +1,53 @@
+/*
+ * HTMLPreviewResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class HTMLPreviewResult extends JavaScriptObject
+{
+ protected HTMLPreviewResult()
+ {
+ }
+
+ public final native boolean getSucceeded() /*-{
+ return this.succeeded;
+ }-*/;
+
+ public final native String getPreviewURL() /*-{
+ return this.preview_url;
+ }-*/;
+
+ public final native String getSourceFile() /*-{
+ return this.source_file;
+ }-*/;
+
+ public final native String getHtmlFile() /*-{
+ return this.html_file;
+ }-*/;
+
+ public final native boolean getEnableSaveAs() /*-{
+ return this.enable_saveas;
+ }-*/;
+
+ public final native boolean getEnableRefresh() /*-{
+ return this.enable_refresh;
+ }-*/;
+
+ public final native boolean getPreviouslyPublished() /*-{
+ return this.previously_published;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewServerOperations.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewServerOperations.java
new file mode 100644
index 0000000..8271f32
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/model/HTMLPreviewServerOperations.java
@@ -0,0 +1,40 @@
+/*
+ * HTMLPreviewServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.rpubs.model.RPubsServerOperations;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.HTMLCapabilities;
+
+public interface HTMLPreviewServerOperations extends RPubsServerOperations
+{
+ void previewHTML(HTMLPreviewParams params,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void terminatePreviewHTML(ServerRequestCallback<Void> requestCallback);
+
+ void getHTMLCapabilities(ServerRequestCallback<HTMLCapabilities> callback);
+
+ // copy file
+ void copyFile(FileSystemItem sourceFile,
+ FileSystemItem targetFile,
+ boolean overwrite,
+ ServerRequestCallback<Void> requestCallback);
+
+ String getApplicationURL(String pathName);
+ String getFileUrl(FileSystemItem file);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationView.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationView.java
new file mode 100644
index 0000000..8655205
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationView.java
@@ -0,0 +1,22 @@
+/*
+ * HTMLPreviewApplicationView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.ui;
+
+import org.rstudio.studio.client.common.satellite.SatelliteApplicationView;
+
+public interface HTMLPreviewApplicationView extends SatelliteApplicationView
+{
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationWindow.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationWindow.java
new file mode 100644
index 0000000..663f5e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewApplicationWindow.java
@@ -0,0 +1,83 @@
+/*
+ * HTMLPreviewApplicationWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.satellite.SatelliteWindow;
+import org.rstudio.studio.client.htmlpreview.HTMLPreviewPresenter;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+
+ at Singleton
+public class HTMLPreviewApplicationWindow extends SatelliteWindow
+ implements HTMLPreviewApplicationView
+{
+
+ @Inject
+ public HTMLPreviewApplicationWindow(Provider<HTMLPreviewPresenter> pPresenter,
+ Provider<EventBus> pEventBus,
+ Provider<FontSizeManager> pFSManager)
+ {
+ super(pEventBus, pFSManager);
+
+ pPresenter_ = pPresenter;
+
+ }
+
+ @Override
+ protected void onInitialize(LayoutPanel mainPanel, JavaScriptObject params)
+ {
+ Window.setTitle("RStudio: Preview HTML");
+
+ // create the presenter and activate it with the passed params
+ HTMLPreviewParams htmlPreviewParams = params.<HTMLPreviewParams>cast();
+ presenter_ = pPresenter_.get();
+ presenter_.onActivated(htmlPreviewParams);
+
+ // make it fill the containing layout panel
+ Widget presWidget = presenter_.asWidget();
+ mainPanel.add(presWidget);
+ mainPanel.setWidgetLeftRight(presWidget, 0, Unit.PX, 0, Unit.PX);
+ mainPanel.setWidgetTopBottom(presWidget, 0, Unit.PX, 0, Unit.PX);
+ }
+
+ @Override
+ public void reactivate(JavaScriptObject params)
+ {
+ if (params != null)
+ {
+ HTMLPreviewParams htmlPreviewParams = params.<HTMLPreviewParams>cast();
+ presenter_.onActivated(htmlPreviewParams);
+ }
+ }
+
+ @Override
+ public Widget getWidget()
+ {
+ return this;
+ }
+
+ private final Provider<HTMLPreviewPresenter> pPresenter_;
+ private HTMLPreviewPresenter presenter_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewPanel.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewPanel.java
new file mode 100644
index 0000000..9d53a20
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewPanel.java
@@ -0,0 +1,278 @@
+/*
+ * HTMLPreviewPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.ui;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.ResizeComposite;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.FindTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.AnchorableFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarLabel;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.htmlpreview.HTMLPreviewPresenter;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class HTMLPreviewPanel extends ResizeComposite
+ implements HTMLPreviewPresenter.Display
+{
+ @Inject
+ public HTMLPreviewPanel(Commands commands)
+ {
+ LayoutPanel panel = new LayoutPanel();
+
+ Toolbar toolbar = createToolbar(commands);
+ int tbHeight = toolbar.getHeight();
+ panel.add(toolbar);
+ panel.setWidgetLeftRight(toolbar, 0, Unit.PX, 0, Unit.PX);
+ panel.setWidgetTopHeight(toolbar, 0, Unit.PX, tbHeight, Unit.PX);
+
+ previewFrame_ = new AnchorableFrame();
+ previewFrame_.setSize("100%", "100%");
+ panel.add(previewFrame_);
+ panel.setWidgetLeftRight(previewFrame_, 0, Unit.PX, 0, Unit.PX);
+ panel.setWidgetTopBottom(previewFrame_, tbHeight+1, Unit.PX, 0, Unit.PX);
+
+ initWidget(panel);
+ }
+
+ private Toolbar createToolbar(Commands commands)
+ {
+ Toolbar toolbar = new Toolbar();
+
+ toolbar.addLeftWidget(new ToolbarLabel("Preview: "));
+ fileLabel_ = new ToolbarLabel();
+ fileLabel_.addStyleName(ThemeStyles.INSTANCE.subtitle());
+ fileLabel_.getElement().getStyle().setMarginRight(7, Unit.PX);
+ toolbar.addLeftWidget(fileLabel_);
+
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands.openHtmlExternal().createToolbarButton());
+
+ showLogButtonSeparator_ = toolbar.addLeftSeparator();
+ showLogButton_ = commands.showHtmlPreviewLog().createToolbarButton();
+ toolbar.addLeftWidget(showLogButton_);
+
+ saveHtmlPreviewAsSeparator_ = toolbar.addLeftSeparator();
+ if (Desktop.isDesktop())
+ {
+ saveHtmlPreviewAs_ = commands.saveHtmlPreviewAs().createToolbarButton();
+ toolbar.addLeftWidget(saveHtmlPreviewAs_);
+ }
+ else
+ {
+ ToolbarPopupMenu menu = new ToolbarPopupMenu();
+ menu.addItem(commands.saveHtmlPreviewAs().createMenuItem(false));
+ menu.addItem(commands.saveHtmlPreviewAsLocalFile().createMenuItem(false));
+
+ saveHtmlPreviewAs_ = toolbar.addLeftWidget(new ToolbarButton(
+ "Save As",
+ commands.saveSourceDoc().getImageResource(),
+ menu));
+
+
+ }
+
+ publishButtonSeparator_ = toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(
+ publishButton_ = commands.publishHTML().createToolbarButton());
+
+
+ findTextBox_ = new FindTextBox("Find");
+ findTextBox_.setIconVisible(true);
+ findTextBox_.setOverrideWidth(120);
+ findTextBox_.getElement().getStyle().setMarginRight(6, Unit.PX);
+ toolbar.addRightWidget(findTextBox_);
+
+ findTextBox_.addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // enter key triggers a find
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ findInTopic(findTextBox_.getValue().trim(), findTextBox_);
+ findTextBox_.focus();
+ }
+ else if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ findTextBox_.setValue("");
+ }
+ }
+
+ private void findInTopic(String term, CanFocus findInputSource)
+ {
+ // get content window
+ WindowEx contentWindow = previewFrame_.getWindow();
+ if (contentWindow == null)
+ return;
+
+ if (!contentWindow.find(term, false, false, true, false))
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showMessage(
+ MessageDialog.INFO,
+ "Find in Page",
+ "No occurences found",
+ findInputSource);
+ }
+ }
+
+ });
+
+ refreshButtonSeparator_ = toolbar.addRightSeparator();
+ refreshButton_ = toolbar.addRightWidget(
+ commands.refreshHtmlPreview().createToolbarButton());
+
+
+ return toolbar;
+ }
+
+
+ @Override
+ public void showLog(String log)
+ {
+ final HTMLPreviewProgressDialog dialog =
+ new HTMLPreviewProgressDialog("Log");
+ dialog.showOutput(log);
+ dialog.stopProgress();
+ dialog.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ dialog.dismiss();
+ }
+ });
+
+ }
+
+ @Override
+ public void showProgress(String caption)
+ {
+ closeProgress();
+ activeProgressDialog_ = new HTMLPreviewProgressDialog(caption, 500);
+ }
+
+ @Override
+ public void setProgressCaption(String caption)
+ {
+ activeProgressDialog_.setCaption(caption);
+ }
+
+ @Override
+ public HandlerRegistration addProgressClickHandler(ClickHandler handler)
+ {
+ return activeProgressDialog_.addClickHandler(handler);
+ }
+
+ @Override
+ public void showProgressOutput(String output)
+ {
+ activeProgressDialog_.showOutput(output);
+ }
+
+ @Override
+ public void stopProgress()
+ {
+ activeProgressDialog_.stopProgress();
+ }
+
+ @Override
+ public void closeProgress()
+ {
+ if (activeProgressDialog_ != null)
+ {
+ activeProgressDialog_.dismiss();
+ activeProgressDialog_ = null;
+ }
+ }
+
+ @Override
+ public void showPreview(String url,
+ String htmlFile,
+ boolean enableSaveAs,
+ boolean enablePublish,
+ boolean enableRefresh,
+ boolean enableShowLog)
+ {
+ String shortFileName = StringUtil.shortPathName(
+ FileSystemItem.createFile(htmlFile),
+ ThemeStyles.INSTANCE.subtitle(),
+ 300);
+ fileLabel_.setText(shortFileName);
+ showLogButtonSeparator_.setVisible(enableShowLog);
+ showLogButton_.setVisible(enableShowLog);
+ saveHtmlPreviewAsSeparator_.setVisible(enableSaveAs);
+ saveHtmlPreviewAs_.setVisible(enableSaveAs);
+ publishButtonSeparator_.setVisible(enablePublish);
+ publishButton_.setVisible(enablePublish);
+ refreshButtonSeparator_.setVisible(enableRefresh);
+ refreshButton_.setVisible(enableRefresh);
+ previewFrame_.navigate(url);
+ }
+
+ @Override
+ public void print()
+ {
+ WindowEx window = previewFrame_.getWindow();
+ window.focus();
+ window.print();
+ }
+
+ @Override
+ public String getDocumentTitle()
+ {
+ return previewFrame_.getWindow().getDocument().getTitle();
+ }
+
+ @Override
+ public void setPublishButtonLabel(String label)
+ {
+ publishButton_.setText(label);
+ }
+
+ private final AnchorableFrame previewFrame_;
+ private ToolbarLabel fileLabel_;
+ private FindTextBox findTextBox_;
+ private Widget saveHtmlPreviewAsSeparator_;
+ private Widget saveHtmlPreviewAs_;
+ private Widget publishButtonSeparator_;
+ private ToolbarButton publishButton_;
+ private Widget showLogButtonSeparator_;
+ private ToolbarButton showLogButton_;
+ private Widget refreshButtonSeparator_;
+ private ToolbarButton refreshButton_;
+ private HTMLPreviewProgressDialog activeProgressDialog_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewProgressDialog.java b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewProgressDialog.java
new file mode 100644
index 0000000..0cc529d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/htmlpreview/ui/HTMLPreviewProgressDialog.java
@@ -0,0 +1,90 @@
+/*
+ * HTMLPreviewProgressDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.htmlpreview.ui;
+
+
+import org.rstudio.core.client.widget.ProgressDialog;
+import org.rstudio.studio.client.common.compile.CompileOutputBuffer;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+
+public class HTMLPreviewProgressDialog extends ProgressDialog
+ implements HasClickHandlers
+
+{
+ public HTMLPreviewProgressDialog(String caption)
+ {
+ this(caption, -1);
+ }
+
+ public HTMLPreviewProgressDialog(String caption, int maxHeight)
+ {
+ super(caption, new Integer(maxHeight));
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return stopButton().addClickHandler(handler);
+ }
+
+ public void setCaption(String caption)
+ {
+ setLabel(caption);
+ }
+
+ public void showOutput(String output)
+ {
+ if (!isShowing())
+ showModal();
+
+ output_.append(output);
+ }
+
+ public void stopProgress()
+ {
+ hideProgress();
+ stopButton().setText("Close");
+ }
+
+ public void dismiss()
+ {
+ closeDialog();
+ }
+
+ @Override
+ protected Widget createDisplayWidget(Object param)
+ {
+ SimplePanel panel = new SimplePanel();
+ int height = Window.getClientHeight() - 150;
+ int maxHeight = (Integer)param;
+ if (maxHeight != -1)
+ height = Math.min(maxHeight, height);
+ panel.getElement().getStyle().setHeight(height, Unit.PX);
+
+ output_ = new CompileOutputBuffer();
+ panel.setWidget(output_);
+ return panel;
+ }
+
+ private CompileOutputBuffer output_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/impl/BrowserFence.java b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFence.java
new file mode 100644
index 0000000..bc20a1d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFence.java
@@ -0,0 +1,22 @@
+/*
+ * BrowserFence.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.impl;
+
+import com.google.gwt.user.client.Command;
+
+public interface BrowserFence
+{
+ void go(Command command);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceSupported.java b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceSupported.java
new file mode 100644
index 0000000..4dc891a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceSupported.java
@@ -0,0 +1,25 @@
+/*
+ * BrowserFenceSupported.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.impl;
+
+import com.google.gwt.user.client.Command;
+
+public class BrowserFenceSupported implements BrowserFence
+{
+ public void go(Command command)
+ {
+ command.execute();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceUnsupported.java b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceUnsupported.java
new file mode 100644
index 0000000..ab7c336
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/impl/BrowserFenceUnsupported.java
@@ -0,0 +1,28 @@
+/*
+ * BrowserFenceUnsupported.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+
+public class BrowserFenceUnsupported implements BrowserFence
+{
+ public void go(Command command)
+ {
+ String url = GWT.getHostPageBaseURL() + "unsupported_browser.htm";
+ Window.Location.replace(url);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptions.java b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptions.java
new file mode 100644
index 0000000..46e2c06
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptions.java
@@ -0,0 +1,74 @@
+/*
+ * CompileNotebookOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.notebook;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompileNotebookOptions extends JavaScriptObject
+{
+ public static final String TYPE_DEFAULT = "default";
+ public static final String TYPE_STITCH = "stitch";
+ public static final String TYPE_SPIN = "spin";
+
+ public static native CompileNotebookOptions create(String id,
+ String prefix,
+ String suffix,
+ boolean sessionInfo,
+ String notebookTitle,
+ String notebookAuthor,
+ String notebookType)
+ /*-{
+ return {
+ id: id,
+ prefix: prefix,
+ suffix: suffix,
+ session_info: sessionInfo,
+ notebook_title: notebookTitle,
+ notebook_author: notebookAuthor,
+ notebook_type: notebookType
+ };
+ }-*/;
+
+ protected CompileNotebookOptions()
+ {}
+
+ public native final String getId() /*-{
+ return this.id;
+ }-*/;
+
+ public native final String getPrefix() /*-{
+ return this.prefix;
+ }-*/;
+
+ public native final String getSuffix() /*-{
+ return this.suffix;
+ }-*/;
+
+ public native final boolean getSessionInfo() /*-{
+ return this.session_info;
+ }-*/;
+
+ public native final String getNotebookTitle() /*-{
+ return this.notebook_title;
+ }-*/;
+
+ public native final String getNotebookAuthor() /*-{
+ return this.notebook_author;
+ }-*/;
+
+ public native final String getNotebookType() /*-{
+ return this.notebook_type;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.java b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.java
new file mode 100644
index 0000000..5a63c51
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.java
@@ -0,0 +1,190 @@
+/*
+ * CompileNotebookOptionsDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.notebook;
+
+import java.util.Date;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.filetypes.FileTypeCommands;
+
+public class CompileNotebookOptionsDialog extends ModalDialog<CompileNotebookOptions>
+{
+ interface Binder extends UiBinder<Widget, CompileNotebookOptionsDialog>
+ {}
+
+ public CompileNotebookOptionsDialog(
+ String docId,
+ String defaultTitle,
+ String defaultAuthor,
+ String defaultType,
+ final OperationWithInput<CompileNotebookOptions> operation)
+ {
+ super("Compile Notebook from R Script", operation);
+ docId_ = docId;
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ widget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+ txtTitle_.setText(defaultTitle);
+ txtAuthor_.setText(defaultAuthor);
+
+ if (showTypes_)
+ {
+ setType(defaultType);
+
+ typeLabelPanel_.setCellVerticalAlignment(
+ lblType_,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+
+ HelpButton helpButton = HelpButton.createHelpButton("notebook_types");
+ typeLabelPanel_.add(helpButton);
+ typeLabelPanel_.setCellVerticalAlignment(
+ helpButton,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+
+
+ divTypeSelector_.getStyle().setPaddingBottom(10, Unit.PX);
+ }
+ else
+ {
+ setType(CompileNotebookOptions.TYPE_DEFAULT);
+ divTypeSelector_.getStyle().setDisplay(Style.Display.NONE);
+ }
+
+ setOkButtonCaption("Compile");
+ }
+
+ @Inject
+ void initialize(FileTypeCommands fileTypeCommands)
+ {
+ showTypes_ = fileTypeCommands.getHTMLCapabiliites().isStitchSupported();
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ txtTitle_.setFocus(true);
+ txtTitle_.selectAll();
+ }
+
+ @Override
+ protected CompileNotebookOptions collectInput()
+ {
+ return CompileNotebookOptions.create(docId_,
+ createPrefix(),
+ createSuffix(),
+ true,
+ txtTitle_.getValue().trim(),
+ txtAuthor_.getValue().trim(),
+ getType());
+ }
+
+ private String createPrefix()
+ {
+ StringBuilder builder = new StringBuilder();
+ String title = txtTitle_.getValue().trim();
+ if (title.length() > 0)
+ {
+ builder.append("### ")
+ .append(SafeHtmlUtils.htmlEscape(title))
+ .append("\n");
+ }
+
+ String author = txtAuthor_.getValue().trim();
+ if (author.length() > 0)
+ {
+ builder.append(SafeHtmlUtils.htmlEscape(author))
+ .append(" --- ");
+ }
+ builder.append("*");
+ builder.append(StringUtil.formatDate(new Date()));
+ builder.append("*");
+
+ return builder.toString();
+ }
+
+ private String createSuffix()
+ {
+ return "";
+ }
+
+ @Override
+ protected boolean validate(CompileNotebookOptions input)
+ {
+ return true;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return widget_;
+ }
+
+ private String getType()
+ {
+ return listType_.getValue(listType_.getSelectedIndex());
+ }
+
+ private void setType(String type)
+ {
+ int typeIndex = 0;
+ for (int i=0; i<listType_.getItemCount(); i++)
+ {
+ if (type.equals(listType_.getValue(i)))
+ {
+ typeIndex = i;
+ break;
+ }
+ }
+ listType_.setSelectedIndex(typeIndex);
+ }
+
+ private final String docId_;
+
+ @UiField
+ TextBox txtTitle_;
+ @UiField
+ TextBox txtAuthor_;
+ @UiField
+ DivElement divTypeSelector_;
+ @UiField
+ HorizontalPanel typeLabelPanel_;
+ @UiField
+ Label lblType_;
+ @UiField
+ ListBox listType_;
+
+ private boolean showTypes_;
+
+ private Widget widget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.ui.xml
new file mode 100644
index 0000000..851533b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookOptionsDialog.ui.xml
@@ -0,0 +1,44 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:style>
+ .dialog input {
+ width: 300px;
+ }
+ .dialog {
+ width: 305px;
+ }
+
+ .type {
+ width: 100%;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.dialog}">
+
+ <label>A Notebook is a standalone HTML file that contains the
+ code and output from your R script.</label>
+
+ <p>
+ <label>Title (optional):</label><br/>
+ <g:TextBox ui:field="txtTitle_" />
+ </p>
+ <p>
+ <label>Author (optional):</label><br/>
+ <g:TextBox ui:field="txtAuthor_" />
+ </p>
+
+ <div ui:field="divTypeSelector_">
+ <g:HorizontalPanel ui:field="typeLabelPanel_">
+ <g:Label ui:field="lblType_" text="Notebook type:"/>
+ </g:HorizontalPanel>
+ <g:ListBox ui:field="listType_"
+ styleName="{style.type}">
+ <g:item value="default">(Default)</g:item>
+ <g:item value="spin">knitr::spin</g:item>
+ </g:ListBox>
+ </div>
+
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookPrefs.java b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookPrefs.java
new file mode 100644
index 0000000..ce00d99
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookPrefs.java
@@ -0,0 +1,58 @@
+/*
+ * CompileNotebookPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.notebook;
+
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompileNotebookPrefs extends JavaScriptObject
+{
+ protected CompileNotebookPrefs() {}
+
+ public static final CompileNotebookPrefs createDefault()
+ {
+ return create("", CompileNotebookOptions.TYPE_DEFAULT);
+ }
+
+ public static final native CompileNotebookPrefs create(String author,
+ String type)
+
+ /*-{
+ var prefs = new Object();
+ prefs.author = author;
+ prefs.type = type;
+ return prefs;
+ }-*/;
+
+ public native final String getAuthor() /*-{
+ return this.author;
+ }-*/;
+
+ public native final String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public static native boolean areEqual(CompileNotebookPrefs a,
+ CompileNotebookPrefs b) /*-{
+ if (a === null ^ b === null)
+ return false;
+ if (a === null)
+ return true;
+ return a.author === b.author &&
+ a.type === b.type;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookResult.java b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookResult.java
new file mode 100644
index 0000000..903faab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/notebook/CompileNotebookResult.java
@@ -0,0 +1,31 @@
+/*
+ * CompileNotebookResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.notebook;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompileNotebookResult extends JavaScriptObject
+{
+ protected CompileNotebookResult()
+ {}
+
+ public native final boolean getSucceeded() /*-{
+ return this.succeeded;
+ }-*/;
+
+ public native final String getFailureMessage() /*-{
+ return this.failure_message;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewer.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewer.java
new file mode 100644
index 0000000..792fecd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewer.java
@@ -0,0 +1,50 @@
+/*
+ * PDFViewer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.pdfviewer.events.ShowPDFViewerEvent;
+import org.rstudio.studio.client.pdfviewer.events.ShowPDFViewerHandler;
+import org.rstudio.studio.client.pdfviewer.model.PDFViewerParams;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class PDFViewer
+{
+ @Inject
+ public PDFViewer(EventBus eventBus,
+ final SatelliteManager satelliteManager)
+ {
+ eventBus.addHandler(ShowPDFViewerEvent.TYPE,
+ new ShowPDFViewerHandler()
+ {
+ @Override
+ public void onShowPDFViewer(ShowPDFViewerEvent event)
+ {
+ // setup params
+ PDFViewerParams params = PDFViewerParams.create();
+
+ // open the window
+ satelliteManager.openSatellite(PDFViewerApplication.NAME,
+ params,
+ new Size(1070,1200));
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerApplication.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerApplication.java
new file mode 100644
index 0000000..68c399e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerApplication.java
@@ -0,0 +1,55 @@
+/*
+ * PDFViewerApplication.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer;
+
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteApplication;
+import org.rstudio.studio.client.pdfviewer.events.InitCompleteEvent;
+import org.rstudio.studio.client.pdfviewer.ui.PDFViewerApplicationView;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class PDFViewerApplication extends SatelliteApplication
+{
+ public static final String NAME = "pdf";
+
+ @Inject
+ public PDFViewerApplication(
+ PDFViewerApplicationView view,
+ Satellite satellite,
+ Provider<AceThemes> pAceThemes,
+ ApplicationUncaughtExceptionHandler uncaughtExHandler)
+ {
+ super(NAME, view, satellite, pAceThemes, uncaughtExHandler);
+ view.addInitCompleteHandler(new InitCompleteEvent.Handler() {
+ @Override
+ public void onInitComplete(InitCompleteEvent event)
+ {
+ flushPendingEvents();
+ }
+ });
+ }
+
+ @Override
+ protected boolean manuallyFlushPendingEvents()
+ {
+ return true;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerPresenter.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerPresenter.java
new file mode 100644
index 0000000..3050faf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/PDFViewerPresenter.java
@@ -0,0 +1,522 @@
+/*
+ * PDFViewerPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.*;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+import org.rstudio.studio.client.common.compilepdf.dialog.CompilePdfProgressDialog;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfStartedEvent;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfResult;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfServerOperations;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.synctex.Synctex;
+import org.rstudio.studio.client.common.synctex.events.SynctexStatusChangedEvent;
+import org.rstudio.studio.client.common.synctex.events.SynctexViewPdfEvent;
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+import org.rstudio.studio.client.pdfviewer.events.InitCompleteEvent;
+import org.rstudio.studio.client.pdfviewer.events.PageClickEvent;
+import org.rstudio.studio.client.pdfviewer.model.PDFViewerParams;
+import org.rstudio.studio.client.pdfviewer.model.SyncTexCoordinates;
+import org.rstudio.studio.client.pdfviewer.pdfjs.PDFView;
+import org.rstudio.studio.client.pdfviewer.pdfjs.events.PDFLoadEvent;
+import org.rstudio.studio.client.pdfviewer.pdfjs.events.PageChangeEvent;
+import org.rstudio.studio.client.pdfviewer.ui.PDFViewerToolbarDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class PDFViewerPresenter implements IsWidget,
+ CompilePdfStartedEvent.Handler,
+ CompilePdfCompletedEvent.Handler,
+ SynctexViewPdfEvent.Handler,
+ SynctexStatusChangedEvent.Handler
+{
+ public interface Binder extends CommandBinder<Commands, PDFViewerPresenter>
+ {}
+
+ public interface Display extends IsWidget,
+ PageClickEvent.HasPageClickHandlers
+ {
+ void setURL(String url);
+ PDFViewerToolbarDisplay getToolbarDisplay();
+ HandlerRegistration addInitCompleteHandler(
+ InitCompleteEvent.Handler handler);
+ void closeWindow();
+ void toggleThumbnails();
+
+ void setStatusText(String text);
+
+ void updateSelectedPage(int pageNumber);
+
+ /**
+ * @return Coordinates of the page area that's at the top of the viewer,
+ * or null if no pages loaded (or no page is currently onscreen for some
+ * other reason).
+ */
+ SyncTexCoordinates getTopCoordinates();
+
+ /**
+ * @return Coordinates of the page area that's at the bottom of the viewer,
+ * or null if no pages loaded (or no page is currently onscreen for some
+ * other reason).
+ */
+ SyncTexCoordinates getBottomCoordinates();
+
+ void navigateTo(PdfLocation pdfLocation);
+ }
+
+ @Inject
+ public PDFViewerPresenter(Display view,
+ EventBus eventBus,
+ Binder binder,
+ Commands commands,
+ FileTypeRegistry fileTypeRegistry,
+ CompilePdfServerOperations server,
+ GlobalDisplay globalDisplay,
+ Synctex synctex,
+ Satellite satellite)
+ {
+ view_ = view;
+ fileTypeRegistry_ = fileTypeRegistry;
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+ synctex_ = synctex;
+
+ binder.bind(commands, this);
+
+
+
+ eventBus.addHandler(CompilePdfStartedEvent.TYPE, this);
+ eventBus.addHandler(CompilePdfCompletedEvent.TYPE, this);
+
+ eventBus.addHandler(SynctexViewPdfEvent.TYPE, this);
+
+ synctex_.enableCommands(false);
+ eventBus.addHandler(SynctexStatusChangedEvent.TYPE, this);
+
+ satellite.addCloseHandler(new CloseHandler<Satellite>() {
+ @Override
+ public void onClose(CloseEvent<Satellite> event)
+ {
+ if (compileIsRunning_)
+ terminateRunningCompile();
+ }
+ });
+
+ final PDFViewerToolbarDisplay toolbar = view_.getToolbarDisplay();
+
+ toolbar.getViewPdfExternal().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ commands_.showPdfExternal().execute();
+ }
+ });
+
+ toolbar.getJumpToSource().setVisible(false);
+ toolbar.getJumpToSource().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ commands_.synctexSearch().execute();
+ }
+ });
+
+ toolbar.getPrevButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ PDFView.previousPage();
+ }
+ });
+ toolbar.getNextButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ PDFView.nextPage();
+ }
+ });
+ toolbar.getThumbnailsButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ view_.toggleThumbnails();
+ }
+ });
+
+ final HasValue<String> pageNumber =
+ toolbar.getPageNumber();
+ pageNumber.addValueChangeHandler(new ValueChangeHandler<String>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ String value = pageNumber.getValue();
+ try
+ {
+ int intVal = Integer.parseInt(value);
+ if (intVal != PDFView.currentPage()
+ && intVal >= 1 && intVal <= PDFView.pageCount())
+ {
+ PDFView.goToPage(intVal);
+ toolbar.selectPageNumber();
+ return;
+ }
+ }
+ catch (NullPointerException ignored)
+ {
+ }
+ catch (NumberFormatException ignored)
+ {
+ }
+
+ pageNumber.setValue(PDFView.currentPage() + "", false);
+ }
+ });
+ toolbar.getZoomIn().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ PDFView.zoomIn();
+ }
+ });
+ toolbar.getZoomOut().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ PDFView.zoomOut();
+ }
+ });
+
+ releaseOnDismiss_.add(PDFView.addPageChangeHandler(new PageChangeEvent.Handler()
+ {
+ @Override
+ public void onPageChange(PageChangeEvent event)
+ {
+ updatePageNumber();
+ }
+ }));
+ releaseOnDismiss_.add(PDFView.addPDFLoadHandler(new PDFLoadEvent.Handler()
+ {
+ @Override
+ public void onPDFLoad(PDFLoadEvent event)
+ {
+ if (executeOnLoad_ != null)
+ {
+ executeOnLoad_.execute();
+ executeOnLoad_ = null;
+ }
+ else
+ {
+ PDFView.scrollToTop();
+ }
+ }
+ }));
+
+ view_.addPageClickHandler(new PageClickEvent.Handler()
+ {
+ @Override
+ public void onPageClick(PageClickEvent event)
+ {
+ if (synctex_.isSynctexAvailable())
+ synctexInverseSearch(event.getCoordinates(), true);
+ }
+ });
+ }
+
+ private void updatePageNumber()
+ {
+ view_.getToolbarDisplay().getPageNumber().setValue(
+ PDFView.currentPage() + "", false);
+ view_.updateSelectedPage(PDFView.currentPage());
+ }
+
+ public void onActivated(PDFViewerParams pdfParams)
+ {
+ }
+
+
+ @Override
+ public void onCompilePdfStarted(CompilePdfStartedEvent event)
+ {
+ view_.getToolbarDisplay().setPdfFile(
+ FileSystemItem.createFile(event.getPdfPath()));
+
+ updateState(true);
+
+ dismissProgressDialog();
+ PDFView.setLoadingVisible(false);
+
+ activeProgressDialog_ = new CompilePdfProgressDialog();
+
+ activeProgressDialog_.addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (compileIsRunning_)
+ {
+ terminateRunningCompile();
+ }
+ else
+ {
+ dismissProgressDialog();
+
+ if (PDFView.pageCount() == 0)
+ view_.closeWindow();
+ }
+ }
+
+ });
+
+ activeProgressDialog_.addSelectionCommitHandler(
+ new SelectionCommitHandler<CodeNavigationTarget>() {
+
+ @Override
+ public void onSelectionCommit(
+ SelectionCommitEvent<CodeNavigationTarget> event)
+ {
+ CodeNavigationTarget target = event.getSelectedItem();
+
+ editFile(FileSystemItem.createFile(target.getFile()),
+ target.getPosition());
+ dismissProgressDialog();
+ }
+ });
+
+ activeProgressDialog_.showModal();
+ }
+
+ @Override
+ public void onCompilePdfCompleted(CompilePdfCompletedEvent event)
+ {
+ CompilePdfResult result = event.getResult();
+
+ updateState(false, result);
+
+ if (result.getSucceeded())
+ {
+ final JavaScriptObject navigateDest =
+ result.getPdfPath().equals(StringUtil.notNull(lastSuccessfulPdfPath_))
+ ? PDFView.getNavigateDest()
+ : null;
+ final PdfLocation pdfLocation = result.getPdfLocation();
+ executeOnLoad_ = new Command()
+ {
+ @Override
+ public void execute()
+ {
+ // Even if we're going to navigate to a pdfLocation, we need to
+ // first navigate to navigateDest if available so that we can
+ // restore the scale and scrollLeft.
+ if (navigateDest != null)
+ PDFView.navigateTo(navigateDest);
+ if (pdfLocation != null)
+ view_.navigateTo(pdfLocation);
+ }
+ };
+
+ lastSuccessfulPdfPath_ = result.getPdfPath();
+ view_.setURL(result.getViewPdfUrl());
+ }
+ }
+
+ @Override
+ public void onSynctexViewPdf(SynctexViewPdfEvent event)
+ {
+ navigateToLocation(event.getPdfLocation());
+ }
+
+ @Override
+ public void onSynctexStatusChanged(SynctexStatusChangedEvent event)
+ {
+ synctex_.enableCommands(synctex_.isSynctexAvailable());
+
+ view_.getToolbarDisplay().getJumpToSource().setVisible(
+ synctex_.isSynctexAvailable());
+
+ if (synctex_.isSynctexAvailable())
+ {
+ String mod = BrowseCap.isMacintosh() ? "Cmd" : "Ctrl";
+ view_.setStatusText("Source: " + event.getTargetFile() +
+ " (" + mod + "+Click to sync to source editor)");
+ }
+ else
+ {
+ view_.setStatusText("");
+ }
+ }
+
+ @Handler
+ public void onSynctexSearch()
+ {
+ synctexInverseSearch(view_.getTopCoordinates(), false);
+ }
+
+ private void synctexInverseSearch(SyncTexCoordinates coord,
+ boolean fromClick)
+ {
+ String pdfPath = lastResult_.getPdfPath();
+ if (pdfPath != null)
+ {
+ synctex_.inverseSearch(PdfLocation.create(pdfPath,
+ coord.getPageNum(),
+ coord.getX(),
+ coord.getY(),
+ 0,
+ 0,
+ fromClick));
+ }
+ }
+
+ @Handler
+ public void onShowPdfExternal()
+ {
+ String pdfPath = getCompiledPdfPath();
+ if (pdfPath != null)
+ {
+ if (Desktop.isDesktop())
+ {
+ Desktop.getFrame().showFile(pdfPath);
+ }
+ else
+ {
+ String pdfURL = server_.getFileUrl(
+ FileSystemItem.createFile(pdfPath));
+ NewWindowOptions options = new NewWindowOptions();
+ options.setName("_rstudio_compile_pdf");
+ globalDisplay_.openWindow(pdfURL, options);
+ }
+ }
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ public HandlerRegistration addInitCompleteHandler(InitCompleteEvent.Handler handler)
+ {
+ return view_.addInitCompleteHandler(handler);
+ }
+
+ private void editFile(FileSystemItem file, FilePosition position)
+ {
+ fileTypeRegistry_.editFile(file, position);
+
+ // firefox and chrome frame won't allow window re-activation
+ // so we close the parent window to force this
+ if (BrowseCap.isFirefox() || BrowseCap.isChromeFrame())
+ view_.closeWindow();
+ }
+
+ private void updateState(boolean running)
+ {
+ updateState(running, null);
+ }
+
+ private void updateState(boolean running, CompilePdfResult result)
+ {
+ compileIsRunning_ = running;
+ lastResult_ = result;
+
+ boolean havePdf = getCompiledPdfPath() != null;
+ commands_.showPdfExternal().setEnabled(havePdf);
+ }
+
+ private String getCompiledPdfPath()
+ {
+ if (lastResult_ != null)
+ return lastResult_.getPdfPath();
+ else
+ return null;
+ }
+
+ private void dismissProgressDialog()
+ {
+ if (activeProgressDialog_ != null)
+ {
+ activeProgressDialog_.dismiss();
+ activeProgressDialog_ = null;
+ }
+ }
+
+ private void terminateRunningCompile()
+ {
+ server_.terminateCompilePdf(new ServerRequestCallback<Boolean>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error",
+ error.getUserMessage());
+
+ }
+ });
+ }
+
+ private void navigateToLocation(PdfLocation location)
+ {
+ view_.navigateTo(location);
+ }
+
+ private boolean compileIsRunning_ = false;
+
+ private CompilePdfProgressDialog activeProgressDialog_;
+ private CompilePdfResult lastResult_ = null;
+
+ private final Display view_;
+ private final Synctex synctex_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final CompilePdfServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+
+ private HandlerRegistrations releaseOnDismiss_ = new HandlerRegistrations();
+
+ private String lastSuccessfulPdfPath_;
+
+ private Command executeOnLoad_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/InitCompleteEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/InitCompleteEvent.java
new file mode 100644
index 0000000..5dad10d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/InitCompleteEvent.java
@@ -0,0 +1,40 @@
+/*
+ * InitCompleteEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class InitCompleteEvent extends GwtEvent<InitCompleteEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onInitComplete(InitCompleteEvent event);
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onInitComplete(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/PageClickEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/PageClickEvent.java
new file mode 100644
index 0000000..126bdb0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/PageClickEvent.java
@@ -0,0 +1,59 @@
+/*
+ * PageClickEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.pdfviewer.model.SyncTexCoordinates;
+
+public class PageClickEvent extends GwtEvent<PageClickEvent.Handler>
+{
+ public interface HasPageClickHandlers
+ {
+ HandlerRegistration addPageClickHandler(PageClickEvent.Handler handler);
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onPageClick(PageClickEvent event);
+ }
+
+ public PageClickEvent(SyncTexCoordinates coordinates)
+ {
+ coordinates_ = coordinates;
+ }
+
+ public SyncTexCoordinates getCoordinates()
+ {
+ return coordinates_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPageClick(this);
+ }
+
+ private final SyncTexCoordinates coordinates_;
+
+ public static Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerEvent.java
new file mode 100644
index 0000000..0a79cce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerEvent.java
@@ -0,0 +1,39 @@
+/*
+ * ShowPDFViewerEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowPDFViewerEvent extends GwtEvent<ShowPDFViewerHandler>
+{
+ public static final GwtEvent.Type<ShowPDFViewerHandler> TYPE =
+ new GwtEvent.Type<ShowPDFViewerHandler>();
+
+ public ShowPDFViewerEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(ShowPDFViewerHandler handler)
+ {
+ handler.onShowPDFViewer(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowPDFViewerHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerHandler.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerHandler.java
new file mode 100644
index 0000000..d8308ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/events/ShowPDFViewerHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowPDFViewerHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowPDFViewerHandler extends EventHandler
+{
+ void onShowPDFViewer(ShowPDFViewerEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/PDFViewerParams.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/PDFViewerParams.java
new file mode 100644
index 0000000..f5d9d1d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/PDFViewerParams.java
@@ -0,0 +1,29 @@
+/*
+ * PDFViewerParams.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PDFViewerParams extends JavaScriptObject
+{
+ protected PDFViewerParams()
+ {
+ }
+
+ public final static native PDFViewerParams create() /*-{
+ var params = new Object();
+ return params;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/SyncTexCoordinates.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/SyncTexCoordinates.java
new file mode 100644
index 0000000..60c922f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/model/SyncTexCoordinates.java
@@ -0,0 +1,44 @@
+/*
+ * SyncTexCoordinates.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.model;
+
+public class SyncTexCoordinates
+{
+ public SyncTexCoordinates(int pageNum, int x, int y)
+ {
+ pageNum_ = pageNum;
+ x_ = x;
+ y_ = y;
+ }
+
+ public int getPageNum()
+ {
+ return pageNum_;
+ }
+
+ public int getX()
+ {
+ return x_;
+ }
+
+ public int getY()
+ {
+ return y_;
+ }
+
+ private final int pageNum_;
+ private final int x_;
+ private final int y_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdf.min.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdf.min.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PDFView.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PDFView.java
new file mode 100644
index 0000000..fccdacf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PDFView.java
@@ -0,0 +1,159 @@
+/*
+ * PDFView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs;
+
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.pdfviewer.pdfjs.events.PDFLoadEvent;
+import org.rstudio.studio.client.pdfviewer.pdfjs.events.PageChangeEvent;
+import org.rstudio.studio.client.pdfviewer.pdfjs.events.ScaleChangeEvent;
+
+public class PDFView extends JavaScriptObject
+{
+ protected PDFView()
+ {
+ }
+
+ public static native void nextPage() /*-{
+ $wnd.PDFView.page++;
+ }-*/;
+
+ public static native void previousPage() /*-{
+ $wnd.PDFView.page-- ;
+ }-*/;
+
+ public static native int currentPage() /*-{
+ return $wnd.PDFView.page;
+ }-*/;
+
+ public static native int pageCount() /*-{
+ return $wnd.PDFView.pages.length;
+ }-*/;
+
+ public static native void goToPage(int page) /*-{
+ $wnd.PDFView.page = page;
+ }-*/;
+
+ public static native double currentScale() /*-{
+ return $wnd.PDFView.currentScale;
+ }-*/;
+
+ public static native void zoomIn() /*-{
+ $wnd.PDFView.zoomIn() ;
+ }-*/;
+
+ public static native void zoomOut() /*-{
+ $wnd.PDFView.zoomOut() ;
+ }-*/;
+
+ public native static void parseScale(String value) /*-{
+ $wnd.PDFView.parseScale(value);
+ }-*/;
+
+ public static HandlerRegistration addPageChangeHandler(PageChangeEvent.Handler handler)
+ {
+ return handlers_.addHandler(PageChangeEvent.TYPE, handler);
+ }
+
+ public static HandlerRegistration addScaleChangeHandler(ScaleChangeEvent.Handler handler)
+ {
+ return handlers_.addHandler(ScaleChangeEvent.TYPE, handler);
+ }
+
+ public static HandlerRegistration addPDFLoadHandler(PDFLoadEvent.Handler handler)
+ {
+ return handlers_.addHandler(PDFLoadEvent.TYPE, handler);
+ }
+
+ public static native void toggleSidebar() /*-{
+ $wnd.PDFView.toggleSidebar();
+ }-*/;
+
+ public native static void initializeEvents() /*-{
+
+ var _setInitialView = $wnd.PDFView.setInitialView;
+ $wnd.PDFView.setInitialView = $entry(function(storedHash, scale) {
+ _setInitialView.call($wnd.PDFView, storedHash, scale);
+ @org.rstudio.studio.client.pdfviewer.pdfjs.PDFView::firePDFLoadEvent()();
+ });
+
+ $wnd.addEventListener(
+ "pagechange",
+ $entry(function(evt) {
+ @org.rstudio.studio.client.pdfviewer.pdfjs.PDFView::firePageChangeEvent()();
+ }),
+ true);
+
+ $wnd.addEventListener(
+ "scalechange",
+ $entry(function(evt) {
+ @org.rstudio.studio.client.pdfviewer.pdfjs.PDFView::fireScaleChangeEvent()();
+ }),
+ true);
+ }-*/;
+
+ private static void firePageChangeEvent()
+ {
+ handlers_.fireEvent(new PageChangeEvent());
+ }
+
+ private static void fireScaleChangeEvent()
+ {
+ handlers_.fireEvent(new ScaleChangeEvent());
+ }
+
+ private static void firePDFLoadEvent()
+ {
+ handlers_.fireEvent(new PDFLoadEvent());
+ }
+
+ public static void setLoadingVisible(boolean visible)
+ {
+ Element el = Document.get().getElementById("loading");
+ if (visible)
+ el.removeAttribute("hidden");
+ else
+ el.setAttribute("hidden", "hidden");
+ }
+
+ public static native void navigateTo(JavaScriptObject dest) /*-{
+ if (dest == null)
+ return;
+
+ $wnd.PDFView.parseScale(dest.scale);
+ $wnd.scrollTo(dest.x, dest.y);
+ }-*/;
+
+ public static native JavaScriptObject getNavigateDest() /*-{
+ if ($wnd.PDFView.pages.length == 0)
+ return null;
+ return {
+ scale: $wnd.PDFView.currentScaleValue,
+ x: $wnd.scrollX,
+ y: $wnd.scrollY
+ };
+ }-*/;
+
+ public static native void scrollToTop() /*-{
+ $wnd.scrollTo($wnd.scrollX, 32); // a little padding on top
+ }-*/;
+
+ private static final HandlerManager handlers_ =
+ new HandlerManager(PDFView.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJs.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJs.java
new file mode 100644
index 0000000..7c016ec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJs.java
@@ -0,0 +1,84 @@
+/*
+ * PdfJs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.LinkElement;
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.ExternalJavaScriptLoader;
+import org.rstudio.core.client.ExternalJavaScriptLoader.Callback;
+
+public class PdfJs
+{
+ public static void preload()
+ {
+ load(null);
+ }
+
+ public static void load(final Command command)
+ {
+ // TODO: This got less robust when we started loading multiple JS files.
+ // ExternalJavaScriptLoader.loadSequentially does not defend against
+ // multiple invocations the way ExternalJavaScriptLoader.addCallback does.
+ // Should figure out a generic mechanism to deal with this (i.e.
+ // ExternalJavaScriptLoader takes a variable number of URLs, not just
+ // one).
+
+ final PdfJsResources res = PdfJsResources.INSTANCE;
+ ExternalJavaScriptLoader.loadSequentially(
+ new String[] {
+ res.compatibilityJs().getSafeUri().asString(),
+ res.pdfjs().getSafeUri().asString(),
+ //res.debuggerJs().getSafeUri().asString(),
+ res.viewerJs().getSafeUri().asString(),
+ },
+ new Callback()
+ {
+ @Override
+ public void onLoaded()
+ {
+ if (!initialized_)
+ {
+ PDFView.initializeEvents();
+
+ LinkElement styleLink = Document.get().createLinkElement();
+ styleLink.setType("text/css");
+ styleLink.setRel("stylesheet");
+ styleLink.setHref(res.viewerCss().getSafeUri().asString());
+ Document.get().getElementsByTagName("head").getItem(0)
+ .appendChild(styleLink);
+
+ initialize(res.pdfjs().getSafeUri().asString());
+
+ initialized_ = true;
+ }
+
+ if (command != null)
+ command.execute();
+ }
+ }
+ );
+
+ }
+
+ private static native void initialize(String pdfjsUrl) /*-{
+ if (@org.rstudio.studio.client.application.Desktop::isDesktop()())
+ $wnd.PDFJS.disableWorker = true;
+ else
+ $wnd.PDFJS.workerSrc = pdfjsUrl;
+ }-*/;
+
+ private static boolean initialized_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJsResources.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJsResources.java
new file mode 100644
index 0000000..1c11fa7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/PdfJsResources.java
@@ -0,0 +1,39 @@
+/*
+ * PdfJsResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import org.rstudio.core.client.resources.StaticDataResource;
+
+public interface PdfJsResources extends ClientBundle
+{
+ public static PdfJsResources INSTANCE = GWT.create(PdfJsResources.class);
+
+ @Source("pdf.min.js")
+ StaticDataResource pdfjs();
+
+ @Source("compatibility.min.js")
+ StaticDataResource compatibilityJs();
+
+ @Source("debugger.min.js")
+ StaticDataResource debuggerJs();
+
+ @Source("viewer.min.js")
+ StaticDataResource viewerJs();
+
+ @Source("viewer.css")
+ StaticDataResource viewerCss();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.js
new file mode 100644
index 0000000..5c192c9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.js
@@ -0,0 +1,254 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// Checking if the typed arrays are supported
+(function checkTypedArrayCompatibility() {
+ if (typeof Uint8Array !== 'undefined') {
+ // some mobile version might not support Float64Array
+ if (typeof Float64Array === 'undefined')
+ window.Float64Array = Float32Array;
+
+ return;
+ }
+
+ function subarray(start, end) {
+ return new TypedArray(this.slice(start, end));
+ }
+
+ function setArrayOffset(array, offset) {
+ if (arguments.length < 2)
+ offset = 0;
+ for (var i = 0, n = array.length; i < n; ++i, ++offset)
+ this[offset] = array[i] & 0xFF;
+ }
+
+ function TypedArray(arg1) {
+ var result;
+ if (typeof arg1 === 'number') {
+ result = [];
+ for (var i = 0; i < arg1; ++i)
+ result[i] = 0;
+ } else
+ result = arg1.slice(0);
+
+ result.subarray = subarray;
+ result.buffer = result;
+ result.byteLength = result.length;
+ result.set = setArrayOffset;
+
+ if (typeof arg1 === 'object' && arg1.buffer)
+ result.buffer = arg1.buffer;
+
+ return result;
+ }
+
+ window.Uint8Array = TypedArray;
+
+ // we don't need support for set, byteLength for 32-bit array
+ // so we can use the TypedArray as well
+ window.Uint32Array = TypedArray;
+ window.Int32Array = TypedArray;
+ window.Uint16Array = TypedArray;
+ window.Float32Array = TypedArray;
+ window.Float64Array = TypedArray;
+})();
+
+// Object.create() ?
+(function checkObjectCreateCompatibility() {
+ if (typeof Object.create !== 'undefined')
+ return;
+
+ Object.create = function objectCreate(proto) {
+ var constructor = function objectCreateConstructor() {};
+ constructor.prototype = proto;
+ return new constructor();
+ };
+})();
+
+// Object.defineProperty() ?
+(function checkObjectDefinePropertyCompatibility() {
+ if (typeof Object.defineProperty !== 'undefined')
+ return;
+
+ Object.defineProperty = function objectDefineProperty(obj, name, def) {
+ delete obj[name];
+ if ('get' in def)
+ obj.__defineGetter__(name, def['get']);
+ if ('set' in def)
+ obj.__defineSetter__(name, def['set']);
+ if ('value' in def) {
+ obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
+ this.__defineGetter__(name, function objectDefinePropertyGetter() {
+ return value;
+ });
+ return value;
+ });
+ obj[name] = def.value;
+ }
+ };
+})();
+
+// Object.keys() ?
+(function checkObjectKeysCompatibility() {
+ if (typeof Object.keys !== 'undefined')
+ return;
+
+ Object.keys = function objectKeys(obj) {
+ var result = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i))
+ result.push(i);
+ }
+ return result;
+ };
+})();
+
+// No XMLHttpRequest.response ?
+(function checkXMLHttpRequestResponseCompatibility() {
+ var xhrPrototype = XMLHttpRequest.prototype;
+ if ('response' in xhrPrototype ||
+ 'mozResponseArrayBuffer' in xhrPrototype ||
+ 'mozResponse' in xhrPrototype ||
+ 'responseArrayBuffer' in xhrPrototype)
+ return;
+ // IE ?
+ if (typeof VBArray !== 'undefined') {
+ Object.defineProperty(xhrPrototype, 'response', {
+ get: function xmlHttpRequestResponseGet() {
+ return new Uint8Array(new VBArray(this.responseBody).toArray());
+ }
+ });
+ return;
+ }
+
+ // other browsers
+ function responseTypeSetter() {
+ // will be only called to set "arraybuffer"
+ this.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+ if (typeof xhrPrototype.overrideMimeType === 'function') {
+ Object.defineProperty(xhrPrototype, 'responseType',
+ { set: responseTypeSetter });
+ }
+ function responseGetter() {
+ var text = this.responseText;
+ var i, n = text.length;
+ var result = new Uint8Array(n);
+ for (i = 0; i < n; ++i)
+ result[i] = text.charCodeAt(i) & 0xFF;
+ return result;
+ }
+ Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
+})();
+
+// window.btoa (base64 encode function) ?
+(function checkWindowBtoaCompatibility() {
+ if ('btoa' in window)
+ return;
+
+ var digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+ window.btoa = function windowBtoa(chars) {
+ var buffer = '';
+ var i, n;
+ for (i = 0, n = chars.length; i < n; i += 3) {
+ var b1 = chars.charCodeAt(i) & 0xFF;
+ var b2 = chars.charCodeAt(i + 1) & 0xFF;
+ var b3 = chars.charCodeAt(i + 2) & 0xFF;
+ var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+ var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+ var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
+ buffer += (digits.charAt(d1) + digits.charAt(d2) +
+ digits.charAt(d3) + digits.charAt(d4));
+ }
+ return buffer;
+ };
+})();
+
+// Function.prototype.bind ?
+(function checkFunctionPrototypeBindCompatibility() {
+ if (typeof Function.prototype.bind !== 'undefined')
+ return;
+
+ Function.prototype.bind = function functionPrototypeBind(obj) {
+ var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
+ var bound = function functionPrototypeBindBound() {
+ var args = Array.prototype.concat.apply(headArgs, arguments);
+ return fn.apply(obj, args);
+ };
+ return bound;
+ };
+})();
+
+// IE9 text/html data URI
+(function checkDocumentDocumentModeCompatibility() {
+ if (!('documentMode' in document) || document.documentMode !== 9)
+ return;
+ // overriding the src property
+ var originalSrcDescriptor = Object.getOwnPropertyDescriptor(
+ HTMLIFrameElement.prototype, 'src');
+ Object.defineProperty(HTMLIFrameElement.prototype, 'src', {
+ get: function htmlIFrameElementPrototypeSrcGet() { return this.$src; },
+ set: function htmlIFrameElementPrototypeSrcSet(src) {
+ this.$src = src;
+ if (src.substr(0, 14) != 'data:text/html') {
+ originalSrcDescriptor.set.call(this, src);
+ return;
+ }
+ // for text/html, using blank document and then
+ // document's open, write, and close operations
+ originalSrcDescriptor.set.call(this, 'about:blank');
+ setTimeout((function htmlIFrameElementPrototypeSrcOpenWriteClose() {
+ var doc = this.contentDocument;
+ doc.open('text/html');
+ doc.write(src.substr(src.indexOf(',') + 1));
+ doc.close();
+ }).bind(this), 0);
+ },
+ enumerable: true
+ });
+})();
+
+// HTMLElement dataset property
+(function checkDatasetProperty() {
+ var div = document.createElement('div');
+ if ('dataset' in div)
+ return; // dataset property exists
+ var oldCreateElement = document.createElement;
+ document.createElement = function newCreateElement() {
+ var result = oldCreateElement.apply(document, arguments);
+ if (arguments[0] === 'div') {
+ // creating dataset property for the div elements
+ result.dataset = {};
+ }
+ return result;
+ };
+})();
+
+// Check console compatability
+(function checkConsoleCompatibility() {
+ if (typeof console == 'undefined') {
+ console = {log: function() {}};
+ }
+})();
+
+// Check onclick compatibility in Opera
+(function checkOnClickCompatibility() {
+ // workaround for reported Opera bug DSK-354448:
+ // onclick fires on disabled buttons with opaque content
+ function ignoreIfTargetDisabled(event) {
+ if (isDisabled(event.target)) {
+ event.stopPropagation();
+ }
+ }
+ function isDisabled(node) {
+ return node.disabled || (node.parentNode && isDisabled(node.parentNode));
+ }
+ if (navigator.userAgent.indexOf('Opera') != -1) {
+ // use browser detection since we cannot feature-check this bug
+ document.addEventListener('click', ignoreIfTargetDisabled, true);
+ }
+})();
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.min.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.min.js
new file mode 100644
index 0000000..4b8ba68
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/compatibility.min.js
@@ -0,0 +1,9 @@
+(function(){function c(b,c){return new a(this.slice(b,c))}function b(a,b){2>arguments.length&&(b=0);for(var c=0,g=a.length;c<g;++c,++b)this[b]=a[c]&255}function a(a){var d;if("number"===typeof a){d=[];for(var e=0;e<a;++e)d[e]=0}else d=a.slice(0);d.subarray=c;d.buffer=d;d.byteLength=d.length;d.set=b;"object"===typeof a&&a.buffer&&(d.buffer=a.buffer);return d}"undefined"!==typeof Uint8Array?"undefined"===typeof Float64Array&&(window.Float64Array=Float32Array):(window.Uint8Array=a,window.Ui [...]
+a,window.Int32Array=a,window.Uint16Array=a,window.Float32Array=a,window.Float64Array=a)})();(function(){"undefined"===typeof Object.create&&(Object.create=function(c){var b=function(){};b.prototype=c;return new b})})();
+(function(){"undefined"===typeof Object.defineProperty&&(Object.defineProperty=function(c,b,a){delete c[b];"get"in a&&c.__defineGetter__(b,a.get);"set"in a&&c.__defineSetter__(b,a.set);"value"in a&&(c.__defineSetter__(b,function(a){this.__defineGetter__(b,function(){return a});return a}),c[b]=a.value)})})();(function(){"undefined"===typeof Object.keys&&(Object.keys=function(c){var b=[],a;for(a in c)c.hasOwnProperty(a)&&b.push(a);return b})})();
+(function(){function c(){this.overrideMimeType("text/plain; charset=x-user-defined")}var b=XMLHttpRequest.prototype;"response"in b||"mozResponseArrayBuffer"in b||"mozResponse"in b||"responseArrayBuffer"in b||("undefined"!==typeof VBArray?Object.defineProperty(b,"response",{get:function(){return new Uint8Array((new VBArray(this.responseBody)).toArray())}}):("function"===typeof b.overrideMimeType&&Object.defineProperty(b,"responseType",{set:c}),Object.defineProperty(b,"response",{get:funct [...]
+this.responseText,b,c=a.length,e=new Uint8Array(c);for(b=0;b<c;++b)e[b]=a.charCodeAt(b)&255;return e}})))})();
+(function(){"btoa"in window||(window.btoa=function(c){var b="",a,f;for(a=0,f=c.length;a<f;a+=3)var d=c.charCodeAt(a)&255,e=c.charCodeAt(a+1)&255,g=c.charCodeAt(a+2)&255,h=(d&3)<<4|e>>4,e=a+1<f?(e&15)<<2|g>>6:64,g=a+2<f?g&63:64,b=b+("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(d>>2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(e)+"ABCDEFGHIJKLMNOPQRSTUV [...]
+return b})})();(function(){"undefined"===typeof Function.prototype.bind&&(Function.prototype.bind=function(c){var b=this,a=Array.prototype.slice.call(arguments,1);return function(){var f=Array.prototype.concat.apply(a,arguments);return b.apply(c,f)}})})();
+(function(){if("documentMode"in document&&9===document.documentMode){var c=Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype,"src");Object.defineProperty(HTMLIFrameElement.prototype,"src",{get:function(){return this.$src},set:function(b){this.$src=b;"data:text/html"!=b.substr(0,14)?c.set.call(this,b):(c.set.call(this,"about:blank"),setTimeout(function(){var a=this.contentDocument;a.open("text/html");a.write(b.substr(b.indexOf(",")+1));a.close()}.bind(this),0))},enumerable:!0})}})();
+(function(){if(!("dataset"in document.createElement("div"))){var c=document.createElement;document.createElement=function(){var b=c.apply(document,arguments);"div"===arguments[0]&&(b.dataset={});return b}}})();(function(){"undefined"==typeof console&&(console={log:function(){}})})();(function(){function c(a){b(a.target)&&a.stopPropagation()}function b(a){return a.disabled||a.parentNode&&b(a.parentNode)}-1!=navigator.userAgent.indexOf("Opera")&&document.addEventListener("click",c,!0)})();
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.js
new file mode 100644
index 0000000..610a638
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.js
@@ -0,0 +1,475 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var FontInspector = (function FontInspectorClosure() {
+ var fonts;
+ var panelWidth = 300;
+ var active = false;
+ var fontAttribute = 'data-font-name';
+ function removeSelection() {
+ var divs = document.querySelectorAll('div[' + fontAttribute + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = '';
+ }
+ }
+ function resetSelection() {
+ var divs = document.querySelectorAll('div[' + fontAttribute + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = 'debuggerHideText';
+ }
+ }
+ function selectFont(fontName, show) {
+ var divs = document.querySelectorAll('div[' + fontAttribute + '=' +
+ fontName + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = show ? 'debuggerShowText' : 'debuggerHideText';
+ }
+ }
+ function textLayerClick(e) {
+ if (!e.target.dataset.fontName || e.target.tagName != 'DIV')
+ return;
+ var fontName = e.target.dataset.fontName;
+ var selects = document.getElementsByTagName('input');
+ for (var i = 0; i < selects.length; ++i) {
+ var select = selects[i];
+ if (select.dataset.fontName != fontName) continue;
+ select.checked = !select.checked;
+ selectFont(fontName, select.checked);
+ select.scrollIntoView();
+ }
+ }
+ return {
+ // Poperties/functions needed by PDFBug.
+ id: 'FontInspector',
+ name: 'Font Inspector',
+ panel: null,
+ manager: null,
+ init: function init() {
+ var panel = this.panel;
+ panel.setAttribute('style', 'padding: 5px;');
+ var tmp = document.createElement('button');
+ tmp.addEventListener('click', resetSelection);
+ tmp.textContent = 'Refresh';
+ panel.appendChild(tmp);
+
+ fonts = document.createElement('div');
+ panel.appendChild(fonts);
+ },
+ enabled: false,
+ get active() {
+ return active;
+ },
+ set active(value) {
+ active = value;
+ if (active) {
+ document.body.addEventListener('click', textLayerClick, true);
+ resetSelection();
+ } else {
+ document.body.removeEventListener('click', textLayerClick, true);
+ removeSelection();
+ }
+ },
+ // FontInspector specific functions.
+ fontAdded: function fontAdded(fontObj, url) {
+ function properties(obj, list) {
+ var moreInfo = document.createElement('table');
+ for (var i = 0; i < list.length; i++) {
+ var tr = document.createElement('tr');
+ var td1 = document.createElement('td');
+ td1.textContent = list[i];
+ tr.appendChild(td1);
+ var td2 = document.createElement('td');
+ td2.textContent = obj[list[i]].toString();
+ tr.appendChild(td2);
+ moreInfo.appendChild(tr);
+ }
+ return moreInfo;
+ }
+ var moreInfo = properties(fontObj, ['name', 'type']);
+ var m = /url\(['"]?([^\)"']+)/.exec(url);
+ var fontName = fontObj.loadedName;
+ var font = document.createElement('div');
+ var name = document.createElement('span');
+ name.textContent = fontName;
+ var download = document.createElement('a');
+ download.href = m[1];
+ download.textContent = 'Download';
+ var logIt = document.createElement('a');
+ logIt.href = '';
+ logIt.textContent = 'Log';
+ logIt.addEventListener('click', function(event) {
+ event.preventDefault();
+ console.log(fontObj);
+ });
+ var select = document.createElement('input');
+ select.setAttribute('type', 'checkbox');
+ select.dataset.fontName = fontName;
+ select.addEventListener('click', (function(select, fontName) {
+ return (function() {
+ selectFont(fontName, select.checked);
+ });
+ })(select, fontName));
+ font.appendChild(select);
+ font.appendChild(name);
+ font.appendChild(document.createTextNode(' '));
+ font.appendChild(download);
+ font.appendChild(document.createTextNode(' '));
+ font.appendChild(logIt);
+ font.appendChild(moreInfo);
+ fonts.appendChild(font);
+ // Somewhat of a hack, should probably add a hook for when the text layer
+ // is done rendering.
+ setTimeout(function() {
+ if (this.active)
+ resetSelection();
+ }.bind(this), 2000);
+ }
+ };
+})();
+
+// Manages all the page steppers.
+var StepperManager = (function StepperManagerClosure() {
+ var steppers = [];
+ var stepperDiv = null;
+ var stepperControls = null;
+ var stepperChooser = null;
+ var breakPoints = {};
+ return {
+ // Poperties/functions needed by PDFBug.
+ id: 'Stepper',
+ name: 'Stepper',
+ panel: null,
+ manager: null,
+ init: function init() {
+ var self = this;
+ this.panel.setAttribute('style', 'padding: 5px;');
+ stepperControls = document.createElement('div');
+ stepperChooser = document.createElement('select');
+ stepperChooser.addEventListener('change', function(event) {
+ self.selectStepper(this.value);
+ });
+ stepperControls.appendChild(stepperChooser);
+ stepperDiv = document.createElement('div');
+ this.panel.appendChild(stepperControls);
+ this.panel.appendChild(stepperDiv);
+ if (sessionStorage.getItem('pdfjsBreakPoints'))
+ breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints'));
+ },
+ enabled: false,
+ active: false,
+ // Stepper specific functions.
+ create: function create(pageIndex) {
+ var debug = document.createElement('div');
+ debug.id = 'stepper' + pageIndex;
+ debug.setAttribute('hidden', true);
+ debug.className = 'stepper';
+ stepperDiv.appendChild(debug);
+ var b = document.createElement('option');
+ b.textContent = 'Page ' + (pageIndex + 1);
+ b.value = pageIndex;
+ stepperChooser.appendChild(b);
+ var initBreakPoints = breakPoints[pageIndex] || [];
+ var stepper = new Stepper(debug, pageIndex, initBreakPoints);
+ steppers.push(stepper);
+ if (steppers.length === 1)
+ this.selectStepper(pageIndex, false);
+ return stepper;
+ },
+ selectStepper: function selectStepper(pageIndex, selectPanel) {
+ if (selectPanel)
+ this.manager.selectPanel(1);
+ for (var i = 0; i < steppers.length; ++i) {
+ var stepper = steppers[i];
+ if (stepper.pageIndex == pageIndex)
+ stepper.panel.removeAttribute('hidden');
+ else
+ stepper.panel.setAttribute('hidden', true);
+ }
+ var options = stepperChooser.options;
+ for (var i = 0; i < options.length; ++i) {
+ var option = options[i];
+ option.selected = option.value == pageIndex;
+ }
+ },
+ saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
+ breakPoints[pageIndex] = bps;
+ sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
+ }
+ };
+})();
+
+// The stepper for each page's IRQueue.
+var Stepper = (function StepperClosure() {
+ function Stepper(panel, pageIndex, initialBreakPoints) {
+ this.panel = panel;
+ this.len;
+ this.breakPoint = 0;
+ this.nextBreakPoint = null;
+ this.pageIndex = pageIndex;
+ this.breakPoints = initialBreakPoints;
+ this.currentIdx = -1;
+ }
+ Stepper.prototype = {
+ init: function init(IRQueue) {
+ // Shorter way to create element and optionally set textContent.
+ function c(tag, textContent) {
+ var d = document.createElement(tag);
+ if (textContent)
+ d.textContent = textContent;
+ return d;
+ }
+ var panel = this.panel;
+ this.len = IRQueue.fnArray.length;
+ var content = c('div', 'c=continue, s=step');
+ var table = c('table');
+ content.appendChild(table);
+ table.cellSpacing = 0;
+ var headerRow = c('tr');
+ table.appendChild(headerRow);
+ headerRow.appendChild(c('th', 'Break'));
+ headerRow.appendChild(c('th', 'Idx'));
+ headerRow.appendChild(c('th', 'fn'));
+ headerRow.appendChild(c('th', 'args'));
+
+ for (var i = 0; i < IRQueue.fnArray.length; i++) {
+ var line = c('tr');
+ line.className = 'line';
+ line.dataset.idx = i;
+ table.appendChild(line);
+ var checked = this.breakPoints.indexOf(i) != -1;
+ var args = IRQueue.argsArray[i] ? IRQueue.argsArray[i] : [];
+
+ var breakCell = c('td');
+ var cbox = c('input');
+ cbox.type = 'checkbox';
+ cbox.className = 'points';
+ cbox.checked = checked;
+ var self = this;
+ cbox.onclick = (function(x) {
+ return function() {
+ if (this.checked)
+ self.breakPoints.push(x);
+ else
+ self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
+ StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
+ }
+ })(i);
+
+ breakCell.appendChild(cbox);
+ line.appendChild(breakCell);
+ line.appendChild(c('td', i.toString()));
+ line.appendChild(c('td', IRQueue.fnArray[i]));
+ line.appendChild(c('td', args.join(', ')));
+ }
+ panel.appendChild(content);
+ var self = this;
+ },
+ getNextBreakPoint: function getNextBreakPoint() {
+ this.breakPoints.sort(function(a, b) { return a - b; });
+ for (var i = 0; i < this.breakPoints.length; i++) {
+ if (this.breakPoints[i] > this.currentIdx)
+ return this.breakPoints[i];
+ }
+ return null;
+ },
+ breakIt: function breakIt(idx, callback) {
+ StepperManager.selectStepper(this.pageIndex, true);
+ var self = this;
+ var dom = document;
+ self.currentIdx = idx;
+ var listener = function(e) {
+ switch (e.keyCode) {
+ case 83: // step
+ dom.removeEventListener('keydown', listener, false);
+ self.nextBreakPoint = self.currentIdx + 1;
+ self.goTo(-1);
+ callback();
+ break;
+ case 67: // continue
+ dom.removeEventListener('keydown', listener, false);
+ var breakPoint = self.getNextBreakPoint();
+ self.nextBreakPoint = breakPoint;
+ self.goTo(-1);
+ callback();
+ break;
+ }
+ }
+ dom.addEventListener('keydown', listener, false);
+ self.goTo(idx);
+ },
+ goTo: function goTo(idx) {
+ var allRows = this.panel.getElementsByClassName('line');
+ for (var x = 0, xx = allRows.length; x < xx; ++x) {
+ var row = allRows[x];
+ if (row.dataset.idx == idx) {
+ row.style.backgroundColor = 'rgb(251,250,207)';
+ row.scrollIntoView();
+ } else {
+ row.style.backgroundColor = null;
+ }
+ }
+ }
+ };
+ return Stepper;
+})();
+
+var Stats = (function Stats() {
+ var stats = [];
+ function clear(node) {
+ while (node.hasChildNodes())
+ node.removeChild(node.lastChild);
+ }
+ function getStatIndex(pageNumber) {
+ for (var i = 0, ii = stats.length; i < ii; ++i)
+ if (stats[i].pageNumber === pageNumber)
+ return i;
+ return false;
+ }
+ return {
+ // Poperties/functions needed by PDFBug.
+ id: 'Stats',
+ name: 'Stats',
+ panel: null,
+ manager: null,
+ init: function init() {
+ this.panel.setAttribute('style', 'padding: 5px;');
+ PDFJS.enableStats = true;
+ },
+ enabled: false,
+ active: false,
+ // Stats specific functions.
+ add: function(pageNumber, stat) {
+ if (!stat)
+ return;
+ var statsIndex = getStatIndex(pageNumber);
+ if (statsIndex !== false) {
+ var b = stats[statsIndex];
+ this.panel.removeChild(b.div);
+ stats.splice(statsIndex, 1);
+ }
+ var wrapper = document.createElement('div');
+ wrapper.className = 'stats';
+ var title = document.createElement('div');
+ title.className = 'title';
+ title.textContent = 'Page: ' + pageNumber;
+ var statsDiv = document.createElement('div');
+ statsDiv.textContent = stat.toString();
+ wrapper.appendChild(title);
+ wrapper.appendChild(statsDiv);
+ stats.push({ pageNumber: pageNumber, div: wrapper });
+ stats.sort(function(a, b) { return a.pageNumber - b.pageNumber});
+ clear(this.panel);
+ for (var i = 0, ii = stats.length; i < ii; ++i)
+ this.panel.appendChild(stats[i].div);
+ }
+ };
+})();
+
+// Manages all the debugging tools.
+var PDFBug = (function PDFBugClosure() {
+ var panelWidth = 300;
+ var buttons = [];
+ var activePanel = null;
+
+ return {
+ tools: [
+ FontInspector,
+ StepperManager,
+ Stats
+ ],
+ enable: function(ids) {
+ var all = false, tools = this.tools;
+ if (ids.length === 1 && ids[0] === 'all')
+ all = true;
+ for (var i = 0; i < tools.length; ++i) {
+ var tool = tools[i];
+ if (all || ids.indexOf(tool.id) !== -1)
+ tool.enabled = true;
+ }
+ if (!all) {
+ // Sort the tools by the order they are enabled.
+ tools.sort(function(a, b) {
+ var indexA = ids.indexOf(a.id);
+ indexA = indexA < 0 ? tools.length : indexA;
+ var indexB = ids.indexOf(b.id);
+ indexB = indexB < 0 ? tools.length : indexB;
+ return indexA - indexB;
+ });
+ }
+ },
+ init: function init() {
+ /*
+ * Basic Layout:
+ * PDFBug
+ * Controls
+ * Panels
+ * Panel
+ * Panel
+ * ...
+ */
+ var ui = document.createElement('div');
+ ui.id = 'PDFBug';
+
+ var controls = document.createElement('div');
+ controls.setAttribute('class', 'controls');
+ ui.appendChild(controls);
+
+ var panels = document.createElement('div');
+ panels.setAttribute('class', 'panels');
+ ui.appendChild(panels);
+
+ document.body.appendChild(ui);
+ document.body.style.paddingRight = panelWidth + 'px';
+
+ // Initialize all the debugging tools.
+ var tools = this.tools;
+ for (var i = 0; i < tools.length; ++i) {
+ var tool = tools[i];
+ var panel = document.createElement('div');
+ var panelButton = document.createElement('button');
+ panelButton.textContent = tool.name;
+ var self = this;
+ panelButton.addEventListener('click', (function(selected) {
+ return function(event) {
+ event.preventDefault();
+ self.selectPanel(selected);
+ };
+ })(i));
+ controls.appendChild(panelButton);
+ panels.appendChild(panel);
+ tool.panel = panel;
+ tool.manager = this;
+ if (tool.enabled)
+ tool.init();
+ else
+ panel.textContent = tool.name + ' is disabled. To enable add ' +
+ ' "' + tool.id + '" to the pdfBug parameter ' +
+ 'and refresh (seperate multiple by commas).';
+ buttons.push(panelButton);
+ }
+ this.selectPanel(0);
+ },
+ selectPanel: function selectPanel(index) {
+ if (index === activePanel)
+ return;
+ activePanel = index;
+ var tools = this.tools;
+ for (var j = 0; j < tools.length; ++j) {
+ if (j == index) {
+ buttons[j].setAttribute('class', 'active');
+ tools[j].active = true;
+ tools[j].panel.removeAttribute('hidden');
+ } else {
+ buttons[j].setAttribute('class', '');
+ tools[j].active = false;
+ tools[j].panel.setAttribute('hidden', 'true');
+ }
+ }
+ }
+ };
+})();
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.min.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.min.js
new file mode 100644
index 0000000..2233530
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/debugger.min.js
@@ -0,0 +1,16 @@
+var FontInspector=function(){function g(){for(var b=document.querySelectorAll("div["+e+"]"),a=0,c=b.length;a<c;++a)b[a].className="debuggerHideText"}function f(b,a){for(var c=document.querySelectorAll("div["+e+"="+b+"]"),d=0,f=c.length;d<f;++d)c[d].className=a?"debuggerShowText":"debuggerHideText"}function a(b){if(b.target.dataset.fontName&&"DIV"==b.target.tagName)for(var b=b.target.dataset.fontName,a=document.getElementsByTagName("input"),c=0;c<a.length;++c){var e=a[c];e.dataset.fontNam [...]
+!e.checked,f(b,e.checked),e.scrollIntoView())}}var c,d=!1,e="data-font-name";return{id:"FontInspector",name:"Font Inspector",panel:null,manager:null,init:function(){var b=this.panel;b.setAttribute("style","padding: 5px;");var a=document.createElement("button");a.addEventListener("click",g);a.textContent="Refresh";b.appendChild(a);c=document.createElement("div");b.appendChild(c)},enabled:!1,get active(){return d},set active(b){if(d=b)document.body.addEventListener("click",a,!0),g();else{d [...]
+a,!0);for(var b=document.querySelectorAll("div["+e+"]"),c=0,f=b.length;c<f;++c)b[c].className=""}},fontAdded:function(b,a){var e=function(b,a){for(var c=document.createElement("table"),e=0;e<a.length;e++){var d=document.createElement("tr"),f=document.createElement("td");f.textContent=a[e];d.appendChild(f);f=document.createElement("td");f.textContent=b[a[e]].toString();d.appendChild(f);c.appendChild(d)}return c}(b,["name","type"]),d=/url\(['"]?([^\)"']+)/.exec(a),l=b.loadedName,i=document [...]
+j=document.createElement("span");j.textContent=l;var o=document.createElement("a");o.href=d[1];o.textContent="Download";d=document.createElement("a");d.href="";d.textContent="Log";d.addEventListener("click",function(a){a.preventDefault();console.log(b)});var m=document.createElement("input");m.setAttribute("type","checkbox");m.dataset.fontName=l;m.addEventListener("click",function(a,b){return function(){f(b,a.checked)}}(m,l));i.appendChild(m);i.appendChild(j);i.appendChild(document.creat [...]
+i.appendChild(o);i.appendChild(document.createTextNode(" "));i.appendChild(d);i.appendChild(e);c.appendChild(i);setTimeout(function(){this.active&&g()}.bind(this),2E3)}}}(),StepperManager=function(){var g=[],f=null,a=null,c=null,d={};return{id:"Stepper",name:"Stepper",panel:null,manager:null,init:function(){var e=this;this.panel.setAttribute("style","padding: 5px;");a=document.createElement("div");c=document.createElement("select");c.addEventListener("change",function(){e.selectStepper(t [...]
+a.appendChild(c);f=document.createElement("div");this.panel.appendChild(a);this.panel.appendChild(f);sessionStorage.getItem("pdfjsBreakPoints")&&(d=JSON.parse(sessionStorage.getItem("pdfjsBreakPoints")))},enabled:!1,active:!1,create:function(a){var b=document.createElement("div");b.id="stepper"+a;b.setAttribute("hidden",!0);b.className="stepper";f.appendChild(b);var h=document.createElement("option");h.textContent="Page "+(a+1);h.value=a;c.appendChild(h);b=new Stepper(b,a,d[a]||[]);g.pus [...]
+g.length&&this.selectStepper(a,!1);return b},selectStepper:function(a,b){b&&this.manager.selectPanel(1);for(var d=0;d<g.length;++d){var f=g[d];f.pageIndex==a?f.panel.removeAttribute("hidden"):f.panel.setAttribute("hidden",!0)}f=c.options;for(d=0;d<f.length;++d){var k=f[d];k.selected=k.value==a}},saveBreakPoints:function(a,b){d[a]=b;sessionStorage.setItem("pdfjsBreakPoints",JSON.stringify(d))}}}(),Stepper=function(){function g(f,a,c){this.panel=f;this.len;this.breakPoint=0;this.nextBreakP [...]
+this.pageIndex=a;this.breakPoints=c;this.currentIdx=-1}g.prototype={init:function(f){function a(a,b){var c=document.createElement(a);b&&(c.textContent=b);return c}var c=this.panel;this.len=f.fnArray.length;var d=a("div","c=continue, s=step"),e=a("table");d.appendChild(e);e.cellSpacing=0;var b=a("tr");e.appendChild(b);b.appendChild(a("th","Break"));b.appendChild(a("th","Idx"));b.appendChild(a("th","fn"));b.appendChild(a("th","args"));for(b=0;b<f.fnArray.length;b++){var h=a("tr");h.classNa [...]
+h.dataset.idx=b;e.appendChild(h);var g=-1!=this.breakPoints.indexOf(b),k=f.argsArray[b]?f.argsArray[b]:[],l=a("td"),i=a("input");i.type="checkbox";i.className="points";i.checked=g;var j=this;i.onclick=function(a){return function(){this.checked?j.breakPoints.push(a):j.breakPoints.splice(j.breakPoints.indexOf(a),1);StepperManager.saveBreakPoints(j.pageIndex,j.breakPoints)}}(b);l.appendChild(i);h.appendChild(l);h.appendChild(a("td",b.toString()));h.appendChild(a("td",f.fnArray[b]));h.append [...]
+k.join(", ")))}c.appendChild(d);j=this},getNextBreakPoint:function(){this.breakPoints.sort(function(a,c){return a-c});for(var f=0;f<this.breakPoints.length;f++)if(this.breakPoints[f]>this.currentIdx)return this.breakPoints[f];return null},breakIt:function(f,a){StepperManager.selectStepper(this.pageIndex,!0);var c=this,d=document;c.currentIdx=f;var e=function(b){switch(b.keyCode){case 83:d.removeEventListener("keydown",e,!1);c.nextBreakPoint=c.currentIdx+1;c.goTo(-1);a();break;case 67:d.r [...]
+e,!1),b=c.getNextBreakPoint(),c.nextBreakPoint=b,c.goTo(-1),a()}};d.addEventListener("keydown",e,!1);c.goTo(f)},goTo:function(f){for(var a=this.panel.getElementsByClassName("line"),c=0,d=a.length;c<d;++c){var e=a[c];e.dataset.idx==f?(e.style.backgroundColor="rgb(251,250,207)",e.scrollIntoView()):e.style.backgroundColor=null}}};return g}(),Stats=function(){function g(a){for(;a.hasChildNodes();)a.removeChild(a.lastChild)}function f(c){for(var d=0,e=a.length;d<e;++d)if(a[d].pageNumber===c)r [...]
+var a=[];return{id:"Stats",name:"Stats",panel:null,manager:null,init:function(){this.panel.setAttribute("style","padding: 5px;");PDFJS.enableStats=!0},enabled:!1,active:!1,add:function(c,d){if(d){var e=f(c);!1!==e&&(this.panel.removeChild(a[e].div),a.splice(e,1));e=document.createElement("div");e.className="stats";var b=document.createElement("div");b.className="title";b.textContent="Page: "+c;var h=document.createElement("div");h.textContent=d.toString();e.appendChild(b);e.appendChild(h [...]
+div:e});a.sort(function(a,b){return a.pageNumber-b.pageNumber});g(this.panel);e=0;for(b=a.length;e<b;++e)this.panel.appendChild(a[e].div)}}}}(),PDFBug=function(){var g=[],f=null;return{tools:[FontInspector,StepperManager,Stats],enable:function(a){var c=!1,d=this.tools;1===a.length&&"all"===a[0]&&(c=!0);for(var e=0;e<d.length;++e){var b=d[e];if(c||-1!==a.indexOf(b.id))b.enabled=!0}c||d.sort(function(b,c){var e=a.indexOf(b.id),e=0>e?d.length:e,f=a.indexOf(c.id),f=0>f?d.length:f;return e-f} [...]
+document.createElement("div");a.id="PDFBug";var c=document.createElement("div");c.setAttribute("class","controls");a.appendChild(c);var d=document.createElement("div");d.setAttribute("class","panels");a.appendChild(d);document.body.appendChild(a);document.body.style.paddingRight="300px";for(var a=this.tools,e=0;e<a.length;++e){var b=a[e],f=document.createElement("div"),n=document.createElement("button");n.textContent=b.name;var k=this;n.addEventListener("click",function(a){return functio [...]
+k.selectPanel(a)}}(e));c.appendChild(n);d.appendChild(f);b.panel=f;b.manager=this;b.enabled?b.init():f.textContent=b.name+' is disabled. To enable add "'+b.id+'" to the pdfBug parameter and refresh (seperate multiple by commas).';g.push(n)}this.selectPanel(0)},selectPanel:function(a){if(a!==f){f=a;for(var c=this.tools,d=0;d<c.length;++d)d==a?(g[d].setAttribute("class","active"),c[d].active=!0,c[d].panel.removeAttribute("hidden")):(g[d].setAttribute("class",""),c[d].active=!1,c[d].panel. [...]
+"true"))}}}}();
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PDFLoadEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PDFLoadEvent.java
new file mode 100644
index 0000000..360c9b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PDFLoadEvent.java
@@ -0,0 +1,40 @@
+/*
+ * PDFLoadEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PDFLoadEvent extends GwtEvent<PDFLoadEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPDFLoad(PDFLoadEvent event);
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPDFLoad(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PageChangeEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PageChangeEvent.java
new file mode 100644
index 0000000..03879d4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/PageChangeEvent.java
@@ -0,0 +1,44 @@
+/*
+ * PageChangeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PageChangeEvent extends GwtEvent<PageChangeEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPageChange(PageChangeEvent event);
+ }
+
+ public PageChangeEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPageChange(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/ScaleChangeEvent.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/ScaleChangeEvent.java
new file mode 100644
index 0000000..f793c12
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/events/ScaleChangeEvent.java
@@ -0,0 +1,44 @@
+/*
+ * ScaleChangeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.pdfjs.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ScaleChangeEvent extends GwtEvent<ScaleChangeEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onScaleChange(ScaleChangeEvent event);
+ }
+
+ public ScaleChangeEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onScaleChange(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.js
new file mode 100644
index 0000000..d043a51
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.js
@@ -0,0 +1,32598 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+var PDFJS = {};
+
+(function pdfjsWrapper() {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+
+ PDFJS.build = 'c684dfc';
+
+ // Files are inserted below - see Makefile
+ /* PDFJSSCRIPT_INCLUDE_ALL */
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var globalScope = (typeof window === 'undefined') ? this : window;
+
+var isWorker = (typeof window == 'undefined');
+
+var ERRORS = 0, WARNINGS = 1, TODOS = 5;
+var verbosity = WARNINGS;
+
+// The global PDFJS object exposes the API
+// In production, it will be declared outside a global wrapper
+// In development, it will be declared here
+if (!globalScope.PDFJS) {
+ globalScope.PDFJS = {};
+}
+
+// getPdf()
+// Convenience function to perform binary Ajax GET
+// Usage: getPdf('http://...', callback)
+// getPdf({
+// url:String ,
+// [,progress:Function, error:Function]
+// },
+// callback)
+function getPdf(arg, callback) {
+ var params = arg;
+ if (typeof arg === 'string')
+ params = { url: arg };
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', params.url);
+ xhr.mozResponseType = xhr.responseType = 'arraybuffer';
+ var protocol = params.url.indexOf(':') < 0 ? window.location.protocol :
+ params.url.substring(0, params.url.indexOf(':') + 1);
+ xhr.expected = (protocol === 'http:' || protocol === 'https:') ? 200 : 0;
+
+ if ('progress' in params)
+ xhr.onprogress = params.progress || undefined;
+
+ if ('error' in params)
+ xhr.onerror = params.error || undefined;
+
+ xhr.onreadystatechange = function getPdfOnreadystatechange(e) {
+ if (xhr.readyState === 4) {
+ if (xhr.status === xhr.expected) {
+ var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
+ xhr.responseArrayBuffer || xhr.response);
+ callback(data);
+ } else if (params.error) {
+ params.error(e);
+ }
+ }
+ };
+ xhr.send(null);
+}
+globalScope.PDFJS.getPdf = getPdf;
+globalScope.PDFJS.pdfBug = false;
+
+var Page = (function PageClosure() {
+ function Page(xref, pageNumber, pageDict, ref) {
+ this.pageNumber = pageNumber;
+ this.pageDict = pageDict;
+ this.xref = xref;
+ this.ref = ref;
+
+ this.displayReadyPromise = null;
+ }
+
+ Page.prototype = {
+ getPageProp: function Page_getPageProp(key) {
+ return this.pageDict.get(key);
+ },
+ inheritPageProp: function Page_inheritPageProp(key) {
+ var dict = this.pageDict;
+ var obj = dict.get(key);
+ while (obj === undefined) {
+ dict = dict.get('Parent');
+ if (!dict)
+ break;
+ obj = dict.get(key);
+ }
+ return obj;
+ },
+ get content() {
+ return shadow(this, 'content', this.getPageProp('Contents'));
+ },
+ get resources() {
+ return shadow(this, 'resources', this.inheritPageProp('Resources'));
+ },
+ get mediaBox() {
+ var obj = this.inheritPageProp('MediaBox');
+ // Reset invalid media box to letter size.
+ if (!isArray(obj) || obj.length !== 4)
+ obj = [0, 0, 612, 792];
+ return shadow(this, 'mediaBox', obj);
+ },
+ get view() {
+ var mediaBox = this.mediaBox;
+ var cropBox = this.inheritPageProp('CropBox');
+ if (!isArray(cropBox) || cropBox.length !== 4)
+ return shadow(this, 'view', mediaBox);
+
+ // From the spec, 6th ed., p.963:
+ // "The crop, bleed, trim, and art boxes should not ordinarily
+ // extend beyond the boundaries of the media box. If they do, they are
+ // effectively reduced to their intersection with the media box."
+ cropBox = Util.intersect(cropBox, mediaBox);
+ if (!cropBox)
+ return shadow(this, 'view', mediaBox);
+
+ return shadow(this, 'view', cropBox);
+ },
+ get annotations() {
+ return shadow(this, 'annotations', this.inheritPageProp('Annots'));
+ },
+ get rotate() {
+ var rotate = this.inheritPageProp('Rotate') || 0;
+ // Normalize rotation so it's a multiple of 90 and between 0 and 270
+ if (rotate % 90 != 0) {
+ rotate = 0;
+ } else if (rotate >= 360) {
+ rotate = rotate % 360;
+ } else if (rotate < 0) {
+ // The spec doesn't cover negatives, assume its counterclockwise
+ // rotation. The following is the other implementation of modulo.
+ rotate = ((rotate % 360) + 360) % 360;
+ }
+ return shadow(this, 'rotate', rotate);
+ },
+
+ getOperatorList: function Page_getOperatorList(handler, dependency) {
+ var xref = this.xref;
+ var content = this.content;
+ var resources = this.resources;
+ if (isArray(content)) {
+ // fetching items
+ var streams = [];
+ var i, n = content.length;
+ for (i = 0; i < n; ++i)
+ streams.push(xref.fetchIfRef(content[i]));
+ content = new StreamsSequenceStream(streams);
+ } else if (isStream(content)) {
+ content.reset();
+ } else if (!content) {
+ // replacing non-existent page content with empty one
+ content = new Stream(new Uint8Array(0));
+ }
+
+ var pe = this.pe = new PartialEvaluator(
+ xref, handler, 'p' + this.pageNumber + '_');
+
+ return pe.getOperatorList(content, resources, dependency);
+ },
+
+ getLinks: function Page_getLinks() {
+ var links = [];
+ var annotations = pageGetAnnotations();
+ var i, n = annotations.length;
+ for (i = 0; i < n; ++i) {
+ if (annotations[i].type != 'Link')
+ continue;
+ links.push(annotations[i]);
+ }
+ return links;
+ },
+ getAnnotations: function Page_getAnnotations() {
+ var xref = this.xref;
+ function getInheritableProperty(annotation, name) {
+ var item = annotation;
+ while (item && !item.has(name)) {
+ item = item.get('Parent');
+ }
+ if (!item)
+ return null;
+ return item.get(name);
+ }
+ function isValidUrl(url) {
+ if (!url)
+ return false;
+ var colon = url.indexOf(':');
+ if (colon < 0)
+ return false;
+ var protocol = url.substr(0, colon);
+ switch (protocol) {
+ case 'http':
+ case 'https':
+ case 'ftp':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ var annotations = this.annotations || [];
+ var i, n = annotations.length;
+ var items = [];
+ for (i = 0; i < n; ++i) {
+ var annotationRef = annotations[i];
+ var annotation = xref.fetch(annotationRef);
+ if (!isDict(annotation))
+ continue;
+ var subtype = annotation.get('Subtype');
+ if (!isName(subtype))
+ continue;
+ var rect = annotation.get('Rect');
+
+ var item = {};
+ item.type = subtype.name;
+ item.rect = rect;
+ switch (subtype.name) {
+ case 'Link':
+ var a = annotation.get('A');
+ if (a) {
+ switch (a.get('S').name) {
+ case 'URI':
+ var url = a.get('URI');
+ // TODO: pdf spec mentions urls can be relative to a Base
+ // entry in the dictionary.
+ if (!isValidUrl(url))
+ url = '';
+ item.url = url;
+ break;
+ case 'GoTo':
+ item.dest = a.get('D');
+ break;
+ default:
+ TODO('other link types');
+ }
+ } else if (annotation.has('Dest')) {
+ // simple destination link
+ var dest = annotation.get('Dest');
+ item.dest = isName(dest) ? dest.name : dest;
+ }
+ break;
+ case 'Widget':
+ var fieldType = getInheritableProperty(annotation, 'FT');
+ if (!isName(fieldType))
+ break;
+ item.fieldType = fieldType.name;
+ // Building the full field name by collecting the field and
+ // its ancestors 'T' properties and joining them using '.'.
+ var fieldName = [];
+ var namedItem = annotation, ref = annotationRef;
+ while (namedItem) {
+ var parent = namedItem.get('Parent');
+ var parentRef = namedItem.getRaw('Parent');
+ var name = namedItem.get('T');
+ if (name) {
+ fieldName.unshift(stringToPDFString(name));
+ } else {
+ // The field name is absent, that means more than one field
+ // with the same name may exist. Replacing the empty name
+ // with the '`' plus index in the parent's 'Kids' array.
+ // This is not in the PDF spec but necessary to id the
+ // the input controls.
+ var kids = parent.get('Kids');
+ var j, jj;
+ for (j = 0, jj = kids.length; j < jj; j++) {
+ var kidRef = kids[j];
+ if (kidRef.num == ref.num && kidRef.gen == ref.gen)
+ break;
+ }
+ fieldName.unshift('`' + j);
+ }
+ namedItem = parent;
+ ref = parentRef;
+ }
+ item.fullName = fieldName.join('.');
+ var alternativeText = stringToPDFString(annotation.get('TU') || '');
+ item.alternativeText = alternativeText;
+ var da = getInheritableProperty(annotation, 'DA') || '';
+ var m = /([\d\.]+)\sTf/.exec(da);
+ if (m)
+ item.fontSize = parseFloat(m[1]);
+ item.textAlignment = getInheritableProperty(annotation, 'Q');
+ item.flags = getInheritableProperty(annotation, 'Ff') || 0;
+ break;
+ case 'Text':
+ var content = annotation.get('Contents');
+ var title = annotation.get('T');
+ item.content = stringToPDFString(content || '');
+ item.title = stringToPDFString(title || '');
+ item.name = !annotation.has('Name') ? 'Note' :
+ annotation.get('Name').name;
+ break;
+ default:
+ TODO('unimplemented annotation type: ' + subtype.name);
+ break;
+ }
+ items.push(item);
+ }
+ return items;
+ }
+ };
+
+ return Page;
+})();
+
+/**
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
+ * `PDFDoc`, this one doesn't have any job management code.
+ * Right now there exists one PDFDocument on the main thread + one object
+ * for each worker. If there is no worker support enabled, there are two
+ * `PDFDocument` objects on the main thread created.
+ */
+var PDFDocument = (function PDFDocumentClosure() {
+ function PDFDocument(arg, callback) {
+ if (isStream(arg))
+ init.call(this, arg);
+ else if (isArrayBuffer(arg))
+ init.call(this, new Stream(arg));
+ else
+ error('PDFDocument: Unknown argument type');
+ }
+
+ function init(stream) {
+ assertWellFormed(stream.length > 0, 'stream must have data');
+ this.stream = stream;
+ this.setup();
+ this.acroForm = this.catalog.catDict.get('AcroForm');
+ }
+
+ function find(stream, needle, limit, backwards) {
+ var pos = stream.pos;
+ var end = stream.end;
+ var str = '';
+ if (pos + limit > end)
+ limit = end - pos;
+ for (var n = 0; n < limit; ++n)
+ str += stream.getChar();
+ stream.pos = pos;
+ var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
+ if (index == -1)
+ return false; /* not found */
+ stream.pos += index;
+ return true; /* found */
+ }
+
+ PDFDocument.prototype = {
+ get linearization() {
+ var length = this.stream.length;
+ var linearization = false;
+ if (length) {
+ linearization = new Linearization(this.stream);
+ if (linearization.length != length)
+ linearization = false;
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'linearization', linearization);
+ },
+ get startXRef() {
+ var stream = this.stream;
+ var startXRef = 0;
+ var linearization = this.linearization;
+ if (linearization) {
+ // Find end of first obj.
+ stream.reset();
+ if (find(stream, 'endobj', 1024))
+ startXRef = stream.pos + 6;
+ } else {
+ // Find startxref by jumping backward from the end of the file.
+ var step = 1024;
+ var found = false, pos = stream.end;
+ while (!found && pos > 0) {
+ pos -= step - 'startxref'.length;
+ if (pos < 0)
+ pos = 0;
+ stream.pos = pos;
+ found = find(stream, 'startxref', step, true);
+ }
+ if (found) {
+ stream.skip(9);
+ var ch;
+ do {
+ ch = stream.getChar();
+ } while (Lexer.isSpace(ch));
+ var str = '';
+ while ((ch - '0') <= 9) {
+ str += ch;
+ ch = stream.getChar();
+ }
+ startXRef = parseInt(str, 10);
+ if (isNaN(startXRef))
+ startXRef = 0;
+ }
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'startXRef', startXRef);
+ },
+ get mainXRefEntriesOffset() {
+ var mainXRefEntriesOffset = 0;
+ var linearization = this.linearization;
+ if (linearization)
+ mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
+ // shadow the prototype getter with a data property
+ return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
+ },
+ // Find the header, remove leading garbage and setup the stream
+ // starting from the header.
+ checkHeader: function PDFDocument_checkHeader() {
+ var stream = this.stream;
+ stream.reset();
+ if (find(stream, '%PDF-', 1024)) {
+ // Found the header, trim off any garbage before it.
+ stream.moveStart();
+ return;
+ }
+ // May not be a PDF file, continue anyway.
+ },
+ setup: function PDFDocument_setup(ownerPassword, userPassword) {
+ this.checkHeader();
+ var xref = new XRef(this.stream,
+ this.startXRef,
+ this.mainXRefEntriesOffset);
+ this.xref = xref;
+ this.catalog = new Catalog(xref);
+ },
+ get numPages() {
+ var linearization = this.linearization;
+ var num = linearization ? linearization.numPages : this.catalog.numPages;
+ // shadow the prototype getter
+ return shadow(this, 'numPages', num);
+ },
+ getDocumentInfo: function PDFDocument_getDocumentInfo() {
+ var info;
+ if (this.xref.trailer.has('Info')) {
+ var infoDict = this.xref.trailer.get('Info');
+
+ info = {};
+ infoDict.forEach(function(key, value) {
+ info[key] = typeof value !== 'string' ? value :
+ stringToPDFString(value);
+ });
+ }
+
+ return shadow(this, 'getDocumentInfo', info);
+ },
+ getFingerprint: function PDFDocument_getFingerprint() {
+ var xref = this.xref, fileID;
+ if (xref.trailer.has('ID')) {
+ fileID = '';
+ var id = xref.trailer.get('ID')[0];
+ id.split('').forEach(function(el) {
+ fileID += Number(el.charCodeAt(0)).toString(16);
+ });
+ } else {
+ // If we got no fileID, then we generate one,
+ // from the first 100 bytes of PDF
+ var data = this.stream.bytes.subarray(0, 100);
+ var hash = calculateMD5(data, 0, data.length);
+ fileID = '';
+ for (var i = 0, length = hash.length; i < length; i++) {
+ fileID += Number(hash[i]).toString(16);
+ }
+ }
+
+ return shadow(this, 'getFingerprint', fileID);
+ },
+ getPage: function PDFDocument_getPage(n) {
+ return this.catalog.getPage(n);
+ }
+ };
+
+ return PDFDocument;
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+function log(msg) {
+ if (console && console.log)
+ console.log(msg);
+ else if (print)
+ print(msg);
+}
+
+function warn(msg) {
+ if (verbosity >= WARNINGS)
+ log('Warning: ' + msg);
+}
+
+function backtrace() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
+}
+
+function error(msg) {
+ log('Error: ' + msg);
+ log(backtrace());
+ throw new Error(msg);
+}
+
+function TODO(what) {
+ if (verbosity >= TODOS)
+ log('TODO: ' + what);
+}
+
+function malformed(msg) {
+ error('Malformed PDF: ' + msg);
+}
+
+function assert(cond, msg) {
+ if (!cond)
+ error(msg);
+}
+
+// In a well-formed PDF, |cond| holds. If it doesn't, subsequent
+// behavior is undefined.
+function assertWellFormed(cond, msg) {
+ if (!cond)
+ malformed(msg);
+}
+
+function shadow(obj, prop, value) {
+ Object.defineProperty(obj, prop, { value: value,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return value;
+}
+
+function bytesToString(bytes) {
+ var str = '';
+ var length = bytes.length;
+ for (var n = 0; n < length; ++n)
+ str += String.fromCharCode(bytes[n]);
+ return str;
+}
+
+function stringToBytes(str) {
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var n = 0; n < length; ++n)
+ bytes[n] = str.charCodeAt(n) & 0xFF;
+ return bytes;
+}
+
+var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
+
+var Util = PDFJS.Util = (function UtilClosure() {
+ function Util() {}
+
+ Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
+ var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
+ return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
+ };
+
+ Util.makeCssCmyk = function Util_makeCssCmyk(c, m, y, k) {
+ c = (new DeviceCmykCS()).getRgb([c, m, y, k]);
+ var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0;
+ return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
+ };
+
+ // For 2d affine transforms
+ Util.applyTransform = function Util_applyTransform(p, m) {
+ var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+ var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+ return [xt, yt];
+ };
+
+ Util.applyInverseTransform = function Util_applyTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [xt, yt];
+ };
+
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+ };
+
+ // Apply a generic 3d matrix M on a 3-vector v:
+ // | a b c | | X |
+ // | d e f | x | Y |
+ // | g h i | | Z |
+ // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+ // with v as [X,Y,Z]
+ Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+ return [
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+ ];
+ }
+
+ // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+ // For coordinate systems whose origin lies in the bottom-left, this
+ // means normalization to (BL,TR) ordering. For systems with origin in the
+ // top-left, this means (TL,BR) ordering.
+ Util.normalizeRect = function Util_normalizeRect(rect) {
+ var r = rect.slice(0); // clone rect
+ if (rect[0] > rect[2]) {
+ r[0] = rect[2];
+ r[2] = rect[0];
+ }
+ if (rect[1] > rect[3]) {
+ r[1] = rect[3];
+ r[3] = rect[1];
+ }
+ return r;
+ }
+
+ // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+ // intersection of rect1 and rect2. If no intersection, returns 'false'
+ // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+ Util.intersect = function Util_intersect(rect1, rect2) {
+ function compare(a, b) {
+ return a - b;
+ };
+
+ // Order points along the axes
+ var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
+ orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
+ result = [];
+
+ rect1 = Util.normalizeRect(rect1);
+ rect2 = Util.normalizeRect(rect2);
+
+ // X: first and second points belong to different rectangles?
+ if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
+ (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
+ // Intersection must be between second and third points
+ result[0] = orderedX[1];
+ result[2] = orderedX[2];
+ } else {
+ return false;
+ }
+
+ // Y: first and second points belong to different rectangles?
+ if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
+ (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
+ // Intersection must be between second and third points
+ result[1] = orderedY[1];
+ result[3] = orderedY[2];
+ } else {
+ return false;
+ }
+
+ return result;
+ };
+
+ Util.sign = function Util_sign(num) {
+ return num < 0 ? -1 : 1;
+ };
+
+ return Util;
+})();
+
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+ function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ switch (rotate) {
+ case -180:
+ case 180:
+ rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+ break;
+ case -270:
+ case 90:
+ rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+ break;
+ case -90:
+ case 270:
+ rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+ break;
+ case 360:
+ case 0:
+ default:
+ rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+ break;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA == 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = {
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([x, y], this.transform);
+ },
+ convertToViewportRectangle:
+ function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+ var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+ return [tl[0], tl[1], br[0], br[1]];
+ },
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([x, y], this.transform);
+ }
+ };
+ return PageViewport;
+})();
+
+var PDFStringTranslateTable = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
+ 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
+ 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
+ 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
+];
+
+function stringToPDFString(str) {
+ var i, n = str.length, str2 = '';
+ if (str[0] === '\xFE' && str[1] === '\xFF') {
+ // UTF16BE BOM
+ for (i = 2; i < n; i += 2)
+ str2 += String.fromCharCode(
+ (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1));
+ } else {
+ for (i = 0; i < n; ++i) {
+ var code = PDFStringTranslateTable[str.charCodeAt(i)];
+ str2 += code ? String.fromCharCode(code) : str.charAt(i);
+ }
+ }
+ return str2;
+}
+
+function isBool(v) {
+ return typeof v == 'boolean';
+}
+
+function isInt(v) {
+ return typeof v == 'number' && ((v | 0) == v);
+}
+
+function isNum(v) {
+ return typeof v == 'number';
+}
+
+function isString(v) {
+ return typeof v == 'string';
+}
+
+function isNull(v) {
+ return v === null;
+}
+
+function isName(v) {
+ return v instanceof Name;
+}
+
+function isCmd(v, cmd) {
+ return v instanceof Cmd && (!cmd || v.cmd == cmd);
+}
+
+function isDict(v, type) {
+ return v instanceof Dict && (!type || v.get('Type').name == type);
+}
+
+function isArray(v) {
+ return v instanceof Array;
+}
+
+function isStream(v) {
+ return typeof v == 'object' && v != null && ('getChar' in v);
+}
+
+function isArrayBuffer(v) {
+ return typeof v == 'object' && v != null && ('byteLength' in v);
+}
+
+function isRef(v) {
+ return v instanceof Ref;
+}
+
+function isPDFFunction(v) {
+ var fnDict;
+ if (typeof v != 'object')
+ return false;
+ else if (isDict(v))
+ fnDict = v;
+ else if (isStream(v))
+ fnDict = v.dict;
+ else
+ return false;
+ return fnDict.has('FunctionType');
+}
+
+/**
+ * 'Promise' object.
+ * Each object that is stored in PDFObjects is based on a Promise object that
+ * contains the status of the object and the data. There migth be situations,
+ * where a function want to use the value of an object, but it isn't ready at
+ * that time. To get a notification, once the object is ready to be used, s.o.
+ * can add a callback using the `then` method on the promise that then calls
+ * the callback once the object gets resolved.
+ * A promise can get resolved only once and only once the data of the promise
+ * can be set. If any of these happens twice or the data is required before
+ * it was set, an exception is throw.
+ */
+var Promise = PDFJS.Promise = (function PromiseClosure() {
+ var EMPTY_PROMISE = {};
+
+ /**
+ * If `data` is passed in this constructor, the promise is created resolved.
+ * If there isn't data, it isn't resolved at the beginning.
+ */
+ function Promise(name, data) {
+ this.name = name;
+ this.isRejected = false;
+ this.error = null;
+ // If you build a promise and pass in some data it's already resolved.
+ if (data != null) {
+ this.isResolved = true;
+ this._data = data;
+ this.hasData = true;
+ } else {
+ this.isResolved = false;
+ this._data = EMPTY_PROMISE;
+ }
+ this.callbacks = [];
+ this.errbacks = [];
+ this.progressbacks = [];
+ };
+ /**
+ * Builds a promise that is resolved when all the passed in promises are
+ * resolved.
+ * @param {Promise[]} promises Array of promises to wait for.
+ * @return {Promise} New dependant promise.
+ */
+ Promise.all = function Promise_all(promises) {
+ var deferred = new Promise();
+ var unresolved = promises.length;
+ var results = [];
+ if (unresolved === 0) {
+ deferred.resolve(results);
+ return deferred;
+ }
+ for (var i = 0, ii = promises.length; i < ii; ++i) {
+ var promise = promises[i];
+ promise.then((function(i) {
+ return function(value) {
+ results[i] = value;
+ unresolved--;
+ if (unresolved === 0)
+ deferred.resolve(results);
+ };
+ })(i));
+ }
+ return deferred;
+ };
+ Promise.prototype = {
+ hasData: false,
+
+ set data(value) {
+ if (value === undefined) {
+ return;
+ }
+ if (this._data !== EMPTY_PROMISE) {
+ error('Promise ' + this.name +
+ ': Cannot set the data of a promise twice');
+ }
+ this._data = value;
+ this.hasData = true;
+
+ if (this.onDataCallback) {
+ this.onDataCallback(value);
+ }
+ },
+
+ get data() {
+ if (this._data === EMPTY_PROMISE) {
+ error('Promise ' + this.name + ': Cannot get data that isn\'t set');
+ }
+ return this._data;
+ },
+
+ onData: function Promise_onData(callback) {
+ if (this._data !== EMPTY_PROMISE) {
+ callback(this._data);
+ } else {
+ this.onDataCallback = callback;
+ }
+ },
+
+ resolve: function Promise_resolve(data) {
+ if (this.isResolved) {
+ error('A Promise can be resolved only once ' + this.name);
+ }
+ if (this.isRejected) {
+ error('The Promise was already rejected ' + this.name);
+ }
+
+ this.isResolved = true;
+ this.data = data || null;
+ var callbacks = this.callbacks;
+
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ progress: function Promise_progress(data) {
+ var callbacks = this.progressbacks;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ reject: function Promise_reject(reason) {
+ if (this.isRejected) {
+ error('A Promise can be rejected only once ' + this.name);
+ }
+ if (this.isResolved) {
+ error('The Promise was already resolved ' + this.name);
+ }
+
+ this.isRejected = true;
+ this.error = reason || null;
+ var errbacks = this.errbacks;
+
+ for (var i = 0, ii = errbacks.length; i < ii; i++) {
+ errbacks[i].call(null, reason);
+ }
+ },
+
+ then: function Promise_then(callback, errback, progressback) {
+ if (!callback) {
+ error('Requiring callback' + this.name);
+ }
+
+ // If the promise is already resolved, call the callback directly.
+ if (this.isResolved) {
+ var data = this.data;
+ callback.call(null, data);
+ } else if (this.isRejected && errback) {
+ var error = this.error;
+ errback.call(null, error);
+ } else {
+ this.callbacks.push(callback);
+ if (errback)
+ this.errbacks.push(errback);
+ }
+
+ if (progressback)
+ this.progressbacks.push(progressback);
+ }
+ };
+
+ return Promise;
+})();
+
+var StatTimer = (function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length)
+ str += pad;
+ return str;
+ }
+ function StatTimer() {
+ this.started = {};
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function StatTimer_time(name) {
+ if (!this.enabled)
+ return;
+ if (name in this.started)
+ throw 'Timer is already running for ' + name;
+ this.started[name] = Date.now();
+ },
+ timeEnd: function StatTimer_timeEnd(name) {
+ if (!this.enabled)
+ return;
+ if (!(name in this.started))
+ throw 'Timer has not been started for ' + name;
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function StatTimer_toString() {
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest)
+ longest = name.length;
+ }
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedAray} source Either a url to a PDF is located or a
+ * typed array already populated with data.
+ * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ */
+PDFJS.getDocument = function getDocument(source) {
+ var promise = new PDFJS.Promise();
+ var transport = new WorkerTransport(promise);
+ if (typeof source === 'string') {
+ // fetch url
+ PDFJS.getPdf(
+ {
+ url: source,
+ progress: function getPDFProgress(evt) {
+ if (evt.lengthComputable)
+ promise.progress({
+ loaded: evt.loaded,
+ total: evt.total
+ });
+ },
+ error: function getPDFError(e) {
+ promise.reject('Unexpected server response of ' +
+ e.target.status + '.');
+ }
+ },
+ function getPDFLoad(data) {
+ transport.sendData(data);
+ });
+ } else {
+ // assuming the source is array, instantiating directly from it
+ transport.sendData(source);
+ }
+ return promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function() {
+ function PDFDocumentProxy(pdfInfo, transport) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ }
+ PDFDocumentProxy.prototype = {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @param {number} The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+ * object.
+ */
+ getPage: function(number) {
+ return this.transport.getPage(number);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ */
+ getDestinations: function() {
+ var promise = new PDFJS.Promise();
+ var destinations = this.pdfInfo.destinations;
+ promise.resolve(destinations);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb array,
+ * dest: dest obj,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function() {
+ var promise = new PDFJS.Promise();
+ var outline = this.pdfInfo.outline;
+ promise.resolve(outline);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {object} that has
+ * info and metadata properties. Info is an {object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function() {
+ var promise = new PDFJS.Promise();
+ var info = this.pdfInfo.info;
+ var metadata = this.pdfInfo.metadata;
+ promise.resolve({
+ info: info,
+ metadata: metadata ? new PDFJS.Metadata(metadata) : null
+ });
+ return promise;
+ },
+ destroy: function() {
+ this.transport.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+})();
+
+var PDFPageProxy = (function PDFPageProxyClosure() {
+ function PDFPageProxy(pageInfo, transport) {
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
+ this.objs = transport.objs;
+ this.renderInProgress = false;
+ }
+ PDFPageProxy.prototype = {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageInfo.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PageViewport} Contains 'width' and 'height' properties along
+ * with transforms required for rendering.
+ */
+ getViewport: function(scale, rotate) {
+ if (arguments.length < 2)
+ rotate = this.rotate;
+ return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} of the
+ * annotation objects.
+ */
+ getAnnotations: function() {
+ if (this.annotationsPromise)
+ return this.annotationsPromise;
+
+ var promise = new PDFJS.Promise();
+ this.annotationsPromise = promise;
+ this.transport.getAnnotations(this.pageInfo.pageIndex);
+ return promise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {object} params A parameter object that supports:
+ * {
+ * canvasContext(required): A 2D context of a DOM Canvas object.,
+ * textLayer(optional): An object that has beginLayout, endLayout, and
+ * appendText functions.
+ * }.
+ * @return {Promise} A promise that is resolved when the page finishes
+ * rendering.
+ */
+ render: function(params) {
+ this.renderInProgress = true;
+
+ var promise = new Promise();
+ var stats = this.stats;
+ stats.time('Overall');
+ // If there is no displayReadyPromise yet, then the operatorList was never
+ // requested before. Make the request and create the promise.
+ if (!this.displayReadyPromise) {
+ this.displayReadyPromise = new Promise();
+ this.destroyed = false;
+
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1
+ });
+ }
+
+ var self = this;
+ function complete(error) {
+ self.renderInProgress = false;
+ if (self.destroyed) {
+ delete self.operatorList;
+ delete self.displayReadyPromise;
+ }
+
+ if (error)
+ promise.reject(error);
+ else
+ promise.resolve();
+ };
+
+ // Once the operatorList and fonts are loaded, do the actual rendering.
+ this.displayReadyPromise.then(
+ function pageDisplayReadyPromise() {
+ if (self.destroyed) {
+ complete();
+ return;
+ }
+
+ var gfx = new CanvasGraphics(params.canvasContext,
+ this.objs, params.textLayer);
+ try {
+ this.display(gfx, params.viewport, complete);
+ } catch (e) {
+ complete(e);
+ }
+ }.bind(this),
+ function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ }
+ );
+
+ return promise;
+ },
+ /**
+ * For internal use only.
+ */
+ startRenderingFromOperatorList:
+ function PDFPageWrapper_startRenderingFromOperatorList(operatorList,
+ fonts) {
+ var self = this;
+ this.operatorList = operatorList;
+
+ var displayContinuation = function pageDisplayContinuation() {
+ // Always defer call to display() to work around bug in
+ // Firefox error reporting from XHR callbacks.
+ setTimeout(function pageSetTimeout() {
+ self.displayReadyPromise.resolve();
+ });
+ };
+
+ this.ensureFonts(fonts,
+ function pageStartRenderingFromOperatorListEnsureFonts() {
+ displayContinuation();
+ }
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ ensureFonts: function PDFPageWrapper_ensureFonts(fonts, callback) {
+ this.stats.time('Font Loading');
+ // Convert the font names to the corresponding font obj.
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ fonts[i] = this.objs.objs[fonts[i]].data;
+ }
+
+ // Load all the fonts
+ FontLoader.bind(
+ fonts,
+ function pageEnsureFontsFontObjs(fontObjs) {
+ this.stats.timeEnd('Font Loading');
+
+ callback.call(this);
+ }.bind(this)
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ display: function PDFPageWrapper_display(gfx, viewport, callback) {
+ var stats = this.stats;
+ stats.time('Rendering');
+
+ gfx.beginDrawing(viewport);
+
+ var startIdx = 0;
+ var length = this.operatorList.fnArray.length;
+ var operatorList = this.operatorList;
+ var stepper = null;
+ if (PDFJS.pdfBug && StepperManager.enabled) {
+ stepper = StepperManager.create(this.pageNumber - 1);
+ stepper.init(operatorList);
+ stepper.nextBreakPoint = stepper.getNextBreakPoint();
+ }
+
+ var self = this;
+ function next() {
+ startIdx =
+ gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+ if (startIdx == length) {
+ gfx.endDrawing();
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ if (callback) callback();
+ }
+ }
+ next();
+ },
+ /**
+ * Stub for future feature.
+ */
+ getTextContent: function() {
+ var promise = new PDFJS.Promise();
+ var textContent = 'page text'; // not implemented
+ promise.resolve(textContent);
+ return promise;
+ },
+ /**
+ * Stub for future feature.
+ */
+ getOperationList: function() {
+ var promise = new PDFJS.Promise();
+ var operationList = { // not implemented
+ dependencyFontsID: null,
+ operatorList: null
+ };
+ promise.resolve(operationList);
+ return promise;
+ },
+ /**
+ * Destroys resources allocated by the page.
+ */
+ destroy: function() {
+ this.destroyed = true;
+
+ if (!this.renderInProgress) {
+ delete this.operatorList;
+ delete this.displayReadyPromise;
+ }
+ }
+ };
+ return PDFPageProxy;
+})();
+/**
+ * For internal use only.
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+ function WorkerTransport(promise) {
+ this.workerReadyPromise = promise;
+ this.objs = new PDFObjects();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.fontsLoading = {};
+
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+ // as it arrives on the worker. Chrome added this with version 15.
+ if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+ var workerSrc = PDFJS.workerSrc;
+ if (typeof workerSrc === 'undefined') {
+ error('No PDFJS.workerSrc specified');
+ }
+
+ try {
+ var worker;
+ if (PDFJS.isFirefoxExtension) {
+ // The firefox extension can't load the worker from the resource://
+ // url so we have to inline the script and then use the blob loader.
+ var bb = new MozBlobBuilder();
+ bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
+ var blobUrl = window.URL.createObjectURL(bb.getBlob());
+ worker = new Worker(blobUrl);
+ } else {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ worker = new Worker(workerSrc);
+ }
+
+ var messageHandler = new MessageHandler('main', worker);
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('test', function transportTest(supportTypedArray) {
+ if (supportTypedArray) {
+ this.worker = worker;
+ this.setupMessageHandler(messageHandler);
+ } else {
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ }.bind(this));
+
+ var testObj = new Uint8Array(1);
+ // Some versions of Opera throw a DATA_CLONE_ERR on
+ // serializing the typed array.
+ messageHandler.send('test', testObj);
+ return;
+ } catch (e) {
+ warn('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ if (this.worker)
+ this.worker.terminate();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ },
+ setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var fakeWorker = {
+ postMessage: function WorkerTransport_postMessage(obj) {
+ fakeWorker.onmessage({data: obj});
+ },
+ terminate: function WorkerTransport_terminate() {}
+ };
+
+ var messageHandler = new MessageHandler('main', fakeWorker);
+ this.setupMessageHandler(messageHandler);
+
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ WorkerMessageHandler.setup(messageHandler);
+ },
+
+ setupMessageHandler:
+ function WorkerTransport_setupMessageHandler(messageHandler) {
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ this.pdfDocument = pdfDocument;
+ this.workerReadyPromise.resolve(pdfDocument);
+ }, this);
+
+ messageHandler.on('GetPage', function transportPage(data) {
+ var pageInfo = data.pageInfo;
+ var page = new PDFPageProxy(pageInfo, this);
+ this.pageCache[pageInfo.pageIndex] = page;
+ var promise = this.pagePromises[pageInfo.pageIndex];
+ promise.resolve(page);
+ }, this);
+
+ messageHandler.on('GetAnnotations', function transportAnnotations(data) {
+ var annotations = data.annotations;
+ var promise = this.pageCache[data.pageIndex].annotationsPromise;
+ promise.resolve(annotations);
+ }, this);
+
+ messageHandler.on('RenderPage', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+ var depFonts = data.depFonts;
+
+ page.stats.timeEnd('Page Request');
+ page.startRenderingFromOperatorList(data.operatorList, depFonts);
+ }, this);
+
+ messageHandler.on('obj', function transportObj(data) {
+ var id = data[0];
+ var type = data[1];
+ if (this.objs.hasData(id))
+ return;
+
+ switch (type) {
+ case 'JpegStream':
+ var imageData = data[2];
+ loadJpegStream(id, imageData, this.objs);
+ break;
+ case 'Image':
+ var imageData = data[2];
+ this.objs.resolve(id, imageData);
+ break;
+ case 'Font':
+ var name = data[2];
+ var file = data[3];
+ var properties = data[4];
+
+ if (file) {
+ // Rewrap the ArrayBuffer in a stream.
+ var fontFileDict = new Dict();
+ file = new Stream(file, 0, file.length, fontFileDict);
+ }
+
+ // At this point, only the font object is created but the font is
+ // not yet attached to the DOM. This is done in `FontLoader.bind`.
+ var font = new Font(name, file, properties);
+ this.objs.resolve(id, font);
+ break;
+ default:
+ error('Got unkown object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('PageError', function transportError(data) {
+ var page = this.pageCache[data.pageNum - 1];
+ if (page.displayReadyPromise)
+ page.displayReadyPromise.reject(data.error);
+ else
+ error(data.error);
+ }, this);
+
+ messageHandler.on('JpegDecode', function(data, promise) {
+ var imageData = data[0];
+ var components = data[1];
+ if (components != 3 && components != 1)
+ error('Only 3 component or 1 component can be returned');
+
+ var img = new Image();
+ img.onload = (function messageHandler_onloadClosure() {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+
+ if (components == 3) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components == 1) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ promise.resolve({ data: buf, width: width, height: height});
+ }).bind(this);
+ var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+ img.src = src;
+ });
+ },
+
+ sendData: function WorkerTransport_sendData(data) {
+ this.messageHandler.send('GetDocRequest', data);
+ },
+
+ getPage: function WorkerTransport_getPage(pageNumber, promise) {
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises)
+ return this.pagePromises[pageIndex];
+ var promise = new PDFJS.Promise('Page ' + pageNumber);
+ this.pagePromises[pageIndex] = promise;
+ this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
+ return promise;
+ },
+
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+ this.messageHandler.send('GetAnnotationsRequest',
+ { pageIndex: pageIndex });
+ }
+ };
+ return WorkerTransport;
+
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// <canvas> contexts store most of the state we need natively.
+// However, PDF needs a bit more state, which we store here.
+
+var TextRenderingMode = {
+ FILL: 0,
+ STROKE: 1,
+ FILL_STROKE: 2,
+ INVISIBLE: 3,
+ FILL_ADD_TO_PATH: 4,
+ STROKE_ADD_TO_PATH: 5,
+ FILL_STROKE_ADD_TO_PATH: 6,
+ ADD_TO_PATH: 7
+};
+
+// Minimal font size that would be used during canvas fillText operations.
+var MIN_FONT_SIZE = 1;
+
+function createScratchCanvas(width, height) {
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+}
+
+function addContextCurrentTransform(ctx) {
+ // If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
+ if (!ctx.mozCurrentTransform) {
+ // Store the original context
+ ctx._originalSave = ctx.save;
+ ctx._originalRestore = ctx.restore;
+ ctx._originalRotate = ctx.rotate;
+ ctx._originalScale = ctx.scale;
+ ctx._originalTranslate = ctx.translate;
+ ctx._originalTransform = ctx.transform;
+
+ ctx._transformMatrix = [1, 0, 0, 1, 0, 0];
+ ctx._transformStack = [];
+
+ Object.defineProperty(ctx, 'mozCurrentTransform', {
+ get: function getCurrentTransform() {
+ return this._transformMatrix;
+ }
+ });
+
+ Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
+ get: function getCurrentTransformInverse() {
+ // Calculation done using WolframAlpha:
+ // http://www.wolframalpha.com/input/?
+ // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
+
+ var m = this._transformMatrix;
+ var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
+
+ var ad_bc = a * d - b * c;
+ var bc_ad = b * c - a * d;
+
+ return [
+ d / ad_bc,
+ b / bc_ad,
+ c / bc_ad,
+ a / ad_bc,
+ (d * e - c * f) / bc_ad,
+ (b * e - a * f) / ad_bc
+ ];
+ }
+ });
+
+ ctx.save = function ctxSave() {
+ var old = this._transformMatrix;
+ this._transformStack.push(old);
+ this._transformMatrix = old.slice(0, 6);
+
+ this._originalSave();
+ };
+
+ ctx.restore = function ctxRestore() {
+ var prev = this._transformStack.pop();
+ if (prev) {
+ this._transformMatrix = prev;
+ this._originalRestore();
+ }
+ };
+
+ ctx.translate = function ctxTranslate(x, y) {
+ var m = this._transformMatrix;
+ m[4] = m[0] * x + m[2] * y + m[4];
+ m[5] = m[1] * x + m[3] * y + m[5];
+
+ this._originalTranslate(x, y);
+ };
+
+ ctx.scale = function ctxScale(x, y) {
+ var m = this._transformMatrix;
+ m[0] = m[0] * x;
+ m[1] = m[1] * x;
+ m[2] = m[2] * y;
+ m[3] = m[3] * y;
+
+ this._originalScale(x, y);
+ };
+
+ ctx.transform = function ctxTransform(a, b, c, d, e, f) {
+ var m = this._transformMatrix;
+ this._transformMatrix = [
+ m[0] * a + m[2] * b,
+ m[1] * a + m[3] * b,
+ m[0] * c + m[2] * d,
+ m[1] * c + m[3] * d,
+ m[0] * e + m[2] * f + m[4],
+ m[1] * e + m[3] * f + m[5]
+ ];
+
+ ctx._originalTransform(a, b, c, d, e, f);
+ };
+
+ ctx.rotate = function ctxRotate(angle) {
+ var cosValue = Math.cos(angle);
+ var sinValue = Math.sin(angle);
+
+ var m = this._transformMatrix;
+ this._transformMatrix = [
+ m[0] * cosValue + m[2] * sinValue,
+ m[1] * cosValue + m[3] * sinValue,
+ m[0] * (-sinValue) + m[2] * cosValue,
+ m[1] * (-sinValue) + m[3] * cosValue,
+ m[4],
+ m[5]
+ ];
+
+ this._originalRotate(angle);
+ };
+ }
+}
+
+var CanvasExtraState = (function CanvasExtraStateClosure() {
+ function CanvasExtraState(old) {
+ // Are soft masks and alpha values shapes or opacities?
+ this.alphaIsShape = false;
+ this.fontSize = 0;
+ this.fontSizeScale = 1;
+ this.textMatrix = IDENTITY_MATRIX;
+ this.fontMatrix = IDENTITY_MATRIX;
+ this.leading = 0;
+ // Current point (in user coordinates)
+ this.x = 0;
+ this.y = 0;
+ // Start of text line (in text coordinates)
+ this.lineX = 0;
+ this.lineY = 0;
+ // Character and word spacing
+ this.charSpacing = 0;
+ this.wordSpacing = 0;
+ this.textHScale = 1;
+ this.textRenderingMode = TextRenderingMode.FILL;
+ // Color spaces
+ this.fillColorSpace = new DeviceGrayCS();
+ this.fillColorSpaceObj = null;
+ this.strokeColorSpace = new DeviceGrayCS();
+ this.strokeColorSpaceObj = null;
+ this.fillColorObj = null;
+ this.strokeColorObj = null;
+ // Default fore and background colors
+ this.fillColor = '#000000';
+ this.strokeColor = '#000000';
+ // Note: fill alpha applies to all non-stroking operations
+ this.fillAlpha = 1;
+ this.strokeAlpha = 1;
+ this.lineWidth = 1;
+
+ this.old = old;
+ }
+
+ CanvasExtraState.prototype = {
+ clone: function CanvasExtraState_clone() {
+ return Object.create(this);
+ },
+ setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+ };
+ return CanvasExtraState;
+})();
+
+var CanvasGraphics = (function CanvasGraphicsClosure() {
+ // Defines the time the executeOperatorList is going to be executing
+ // before it stops and shedules a continue of execution.
+ var kExecutionTime = 15;
+
+ function CanvasGraphics(canvasCtx, objs, textLayer) {
+ this.ctx = canvasCtx;
+ this.current = new CanvasExtraState();
+ this.stateStack = [];
+ this.pendingClip = null;
+ this.res = null;
+ this.xobjs = null;
+ this.objs = objs;
+ this.textLayer = textLayer;
+ if (canvasCtx) {
+ addContextCurrentTransform(canvasCtx);
+ }
+ }
+
+ var LINE_CAP_STYLES = ['butt', 'round', 'square'];
+ var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
+ var NORMAL_CLIP = {};
+ var EO_CLIP = {};
+
+ CanvasGraphics.prototype = {
+ slowCommands: {
+ 'stroke': true,
+ 'closeStroke': true,
+ 'fill': true,
+ 'eoFill': true,
+ 'fillStroke': true,
+ 'eoFillStroke': true,
+ 'closeFillStroke': true,
+ 'closeEOFillStroke': true,
+ 'showText': true,
+ 'showSpacedText': true,
+ 'setStrokeColorSpace': true,
+ 'setFillColorSpace': true,
+ 'setStrokeColor': true,
+ 'setStrokeColorN': true,
+ 'setFillColor': true,
+ 'setFillColorN': true,
+ 'setStrokeGray': true,
+ 'setFillGray': true,
+ 'setStrokeRGBColor': true,
+ 'setFillRGBColor': true,
+ 'setStrokeCMYKColor': true,
+ 'setFillCMYKColor': true,
+ 'paintJpegXObject': true,
+ 'paintImageXObject': true,
+ 'paintImageMaskXObject': true,
+ 'shadingFill': true
+ },
+
+ beginDrawing: function CanvasGraphics_beginDrawing(viewport) {
+ var transform = viewport.transform;
+ this.ctx.save();
+ this.ctx.transform.apply(this.ctx, transform);
+
+ if (this.textLayer)
+ this.textLayer.beginLayout();
+ },
+
+ executeOperatorList: function CanvasGraphics_executeOperatorList(
+ operatorList,
+ executionStartIdx, continueCallback,
+ stepper) {
+ var argsArray = operatorList.argsArray;
+ var fnArray = operatorList.fnArray;
+ var i = executionStartIdx || 0;
+ var argsArrayLen = argsArray.length;
+
+ // Sometimes the OperatorList to execute is empty.
+ if (argsArrayLen == i) {
+ return i;
+ }
+
+ var executionEndIdx;
+ var endTime = Date.now() + kExecutionTime;
+
+ var objs = this.objs;
+ var fnName;
+ var slowCommands = this.slowCommands;
+
+ while (true) {
+ if (stepper && i === stepper.nextBreakPoint) {
+ stepper.breakIt(i, continueCallback);
+ return i;
+ }
+
+ fnName = fnArray[i];
+
+ if (fnName !== 'dependency') {
+ this[fnName].apply(this, argsArray[i]);
+ } else {
+ var deps = argsArray[i];
+ for (var n = 0, nn = deps.length; n < nn; n++) {
+ var depObjId = deps[n];
+
+ // If the promise isn't resolved yet, add the continueCallback
+ // to the promise and bail out.
+ if (!objs.isResolved(depObjId)) {
+ objs.get(depObjId, continueCallback);
+ return i;
+ }
+ }
+ }
+
+ i++;
+
+ // If the entire operatorList was executed, stop as were done.
+ if (i == argsArrayLen) {
+ return i;
+ }
+
+ // If the execution took longer then a certain amount of time, shedule
+ // to continue exeution after a short delay.
+ // However, this is only possible if a 'continueCallback' is passed in.
+ if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
+ setTimeout(continueCallback, 0);
+ return i;
+ }
+
+ // If the operatorList isn't executed completely yet OR the execution
+ // time was short enough, do another execution round.
+ }
+ },
+
+ endDrawing: function CanvasGraphics_endDrawing() {
+ this.ctx.restore();
+
+ if (this.textLayer)
+ this.textLayer.endLayout();
+ },
+
+ // Graphics state
+ setLineWidth: function CanvasGraphics_setLineWidth(width) {
+ this.current.lineWidth = width;
+ this.ctx.lineWidth = width;
+ },
+ setLineCap: function CanvasGraphics_setLineCap(style) {
+ this.ctx.lineCap = LINE_CAP_STYLES[style];
+ },
+ setLineJoin: function CanvasGraphics_setLineJoin(style) {
+ this.ctx.lineJoin = LINE_JOIN_STYLES[style];
+ },
+ setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
+ this.ctx.miterLimit = limit;
+ },
+ setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
+ this.ctx.mozDash = dashArray;
+ this.ctx.mozDashOffset = dashPhase;
+ this.ctx.webkitLineDash = dashArray;
+ this.ctx.webkitLineDashOffset = dashPhase;
+ },
+ setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
+ TODO('set rendering intent: ' + intent);
+ },
+ setFlatness: function CanvasGraphics_setFlatness(flatness) {
+ TODO('set flatness: ' + flatness);
+ },
+ setGState: function CanvasGraphics_setGState(states) {
+ for (var i = 0, ii = states.length; i < ii; i++) {
+ var state = states[i];
+ var key = state[0];
+ var value = state[1];
+
+ switch (key) {
+ case 'LW':
+ this.setLineWidth(value);
+ break;
+ case 'LC':
+ this.setLineCap(value);
+ break;
+ case 'LJ':
+ this.setLineJoin(value);
+ break;
+ case 'ML':
+ this.setMiterLimit(value);
+ break;
+ case 'D':
+ this.setDash(value[0], value[1]);
+ break;
+ case 'RI':
+ this.setRenderingIntent(value);
+ break;
+ case 'FL':
+ this.setFlatness(value);
+ break;
+ case 'Font':
+ this.setFont(state[1], state[2]);
+ break;
+ case 'CA':
+ this.current.strokeAlpha = state[1];
+ break;
+ case 'ca':
+ this.current.fillAlpha = state[1];
+ this.ctx.globalAlpha = state[1];
+ break;
+ }
+ }
+ },
+ save: function CanvasGraphics_save() {
+ this.ctx.save();
+ var old = this.current;
+ this.stateStack.push(old);
+ this.current = old.clone();
+ },
+ restore: function CanvasGraphics_restore() {
+ var prev = this.stateStack.pop();
+ if (prev) {
+ this.current = prev;
+ this.ctx.restore();
+ }
+ },
+ transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
+ this.ctx.transform(a, b, c, d, e, f);
+ },
+
+ // Path
+ moveTo: function CanvasGraphics_moveTo(x, y) {
+ this.ctx.moveTo(x, y);
+ this.current.setCurrentPoint(x, y);
+ },
+ lineTo: function CanvasGraphics_lineTo(x, y) {
+ this.ctx.lineTo(x, y);
+ this.current.setCurrentPoint(x, y);
+ },
+ curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) {
+ this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
+ this.current.setCurrentPoint(x3, y3);
+ },
+ curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) {
+ var current = this.current;
+ this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
+ current.setCurrentPoint(x3, y3);
+ },
+ curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) {
+ this.curveTo(x1, y1, x3, y3, x3, y3);
+ this.current.setCurrentPoint(x3, y3);
+ },
+ closePath: function CanvasGraphics_closePath() {
+ this.ctx.closePath();
+ },
+ rectangle: function CanvasGraphics_rectangle(x, y, width, height) {
+ this.ctx.rect(x, y, width, height);
+ },
+ stroke: function CanvasGraphics_stroke(consumePath) {
+ consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+ var ctx = this.ctx;
+ var strokeColor = this.current.strokeColor;
+ if (this.current.lineWidth === 0)
+ ctx.lineWidth = this.getSinglePixelWidth();
+ // For stroke we want to temporarily change the global alpha to the
+ // stroking alpha.
+ ctx.globalAlpha = this.current.strokeAlpha;
+ if (strokeColor && strokeColor.hasOwnProperty('type') &&
+ strokeColor.type === 'Pattern') {
+ // for patterns, we transform to pattern space, calculate
+ // the pattern, call stroke, and restore to user space
+ ctx.save();
+ ctx.strokeStyle = strokeColor.getPattern(ctx);
+ ctx.stroke();
+ ctx.restore();
+ } else {
+ ctx.stroke();
+ }
+ if (consumePath)
+ this.consumePath();
+ // Restore the global alpha to the fill alpha
+ ctx.globalAlpha = this.current.fillAlpha;
+ },
+ closeStroke: function CanvasGraphics_closeStroke() {
+ this.closePath();
+ this.stroke();
+ },
+ fill: function CanvasGraphics_fill(consumePath) {
+ consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+ var ctx = this.ctx;
+ var fillColor = this.current.fillColor;
+
+ if (fillColor && fillColor.hasOwnProperty('type') &&
+ fillColor.type === 'Pattern') {
+ ctx.save();
+ ctx.fillStyle = fillColor.getPattern(ctx);
+ ctx.fill();
+ ctx.restore();
+ } else {
+ ctx.fill();
+ }
+ if (consumePath)
+ this.consumePath();
+ },
+ eoFill: function CanvasGraphics_eoFill() {
+ var savedFillRule = this.setEOFillRule();
+ this.fill();
+ this.restoreFillRule(savedFillRule);
+ },
+ fillStroke: function CanvasGraphics_fillStroke() {
+ this.fill(false);
+ this.stroke(false);
+
+ this.consumePath();
+ },
+ eoFillStroke: function CanvasGraphics_eoFillStroke() {
+ var savedFillRule = this.setEOFillRule();
+ this.fillStroke();
+ this.restoreFillRule(savedFillRule);
+ },
+ closeFillStroke: function CanvasGraphics_closeFillStroke() {
+ this.closePath();
+ this.fillStroke();
+ },
+ closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
+ var savedFillRule = this.setEOFillRule();
+ this.closePath();
+ this.fillStroke();
+ this.restoreFillRule(savedFillRule);
+ },
+ endPath: function CanvasGraphics_endPath() {
+ this.consumePath();
+ },
+
+ // Clipping
+ clip: function CanvasGraphics_clip() {
+ this.pendingClip = NORMAL_CLIP;
+ },
+ eoClip: function CanvasGraphics_eoClip() {
+ this.pendingClip = EO_CLIP;
+ },
+
+ // Text
+ beginText: function CanvasGraphics_beginText() {
+ this.current.textMatrix = IDENTITY_MATRIX;
+ this.current.x = this.current.lineX = 0;
+ this.current.y = this.current.lineY = 0;
+ },
+ endText: function CanvasGraphics_endText() {
+ },
+ setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
+ this.current.charSpacing = spacing;
+ },
+ setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
+ this.current.wordSpacing = spacing;
+ },
+ setHScale: function CanvasGraphics_setHScale(scale) {
+ this.current.textHScale = scale / 100;
+ },
+ setLeading: function CanvasGraphics_setLeading(leading) {
+ this.current.leading = -leading;
+ },
+ setFont: function CanvasGraphics_setFont(fontRefName, size) {
+ var fontObj = this.objs.get(fontRefName);
+ var current = this.current;
+
+ if (!fontObj)
+ error('Can\'t find font for ' + fontRefName);
+
+ // Slice-clone matrix so we can manipulate it without affecting original
+ if (fontObj.fontMatrix)
+ current.fontMatrix = fontObj.fontMatrix.slice(0);
+ else
+ current.fontMatrix = IDENTITY_MATRIX.slice(0);
+
+ // A valid matrix needs all main diagonal elements to be non-zero
+ // This also ensures we bypass FF bugzilla bug #719844.
+ if (current.fontMatrix[0] === 0 ||
+ current.fontMatrix[3] === 0) {
+ warn('Invalid font matrix for font ' + fontRefName);
+ }
+
+ // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
+ // and in some docs this can be negative (inverted x-y axes).
+ // We implement this condition with fontMatrix.
+ if (size < 0) {
+ size = -size;
+ current.fontMatrix[0] *= -1;
+ current.fontMatrix[3] *= -1;
+ }
+
+ this.current.font = fontObj;
+ this.current.fontSize = size;
+
+ if (fontObj.coded)
+ return; // we don't need ctx.font for Type3 fonts
+
+ var name = fontObj.loadedName || 'sans-serif';
+ var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
+ (fontObj.bold ? 'bold' : 'normal');
+
+ var italic = fontObj.italic ? 'italic' : 'normal';
+ var serif = fontObj.isSerifFont ? 'serif' : 'sans-serif';
+ var typeface = '"' + name + '", ' + serif;
+
+ // Some font backends cannot handle fonts below certain size.
+ // Keeping the font at minimal size and using the fontSizeScale to change
+ // the current transformation matrix before the fillText/strokeText.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
+ var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
+ this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 :
+ size / MIN_FONT_SIZE;
+
+ var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
+ this.ctx.font = rule;
+ },
+ setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
+ if (mode >= TextRenderingMode.FILL_ADD_TO_PATH)
+ TODO('unsupported text rendering mode: ' + mode);
+ this.current.textRenderingMode = mode;
+ },
+ setTextRise: function CanvasGraphics_setTextRise(rise) {
+ TODO('text rise: ' + rise);
+ },
+ moveText: function CanvasGraphics_moveText(x, y) {
+ this.current.x = this.current.lineX += x;
+ this.current.y = this.current.lineY += y;
+ },
+ setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
+ this.setLeading(-y);
+ this.moveText(x, y);
+ },
+ setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
+ this.current.textMatrix = [a, b, c, d, e, f];
+
+ this.current.x = this.current.lineX = 0;
+ this.current.y = this.current.lineY = 0;
+ },
+ nextLine: function CanvasGraphics_nextLine() {
+ this.moveText(0, this.current.leading);
+ },
+ applyTextTransforms: function CanvasGraphics_applyTextTransforms() {
+ var ctx = this.ctx;
+ var current = this.current;
+ var textHScale = current.textHScale;
+ var fontMatrix = current.fontMatrix || IDENTITY_MATRIX;
+
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.scale(1, -1);
+ ctx.translate(current.x, -1 * current.y);
+ ctx.transform.apply(ctx, fontMatrix);
+ ctx.scale(textHScale, 1);
+ },
+ getTextGeometry: function CanvasGraphics_getTextGeometry() {
+ var geometry = {};
+ var ctx = this.ctx;
+ var font = this.current.font;
+ var ctxMatrix = ctx.mozCurrentTransform;
+ if (ctxMatrix) {
+ var bl = Util.applyTransform([0, 0], ctxMatrix);
+ var tr = Util.applyTransform([1, 1], ctxMatrix);
+ geometry.x = bl[0];
+ geometry.y = bl[1];
+ geometry.hScale = tr[0] - bl[0];
+ geometry.vScale = tr[1] - bl[1];
+ }
+ geometry.spaceWidth = font.spaceWidth;
+ return geometry;
+ },
+
+ showText: function CanvasGraphics_showText(str, skipTextSelection) {
+ var ctx = this.ctx;
+ var current = this.current;
+ var font = current.font;
+ var glyphs = font.charsToGlyphs(str);
+ var fontSize = current.fontSize;
+ var fontSizeScale = current.fontSizeScale;
+ var charSpacing = current.charSpacing;
+ var wordSpacing = current.wordSpacing;
+ var textHScale = current.textHScale;
+ var fontMatrix = current.fontMatrix || IDENTITY_MATRIX;
+ var textHScale2 = textHScale * fontMatrix[0];
+ var glyphsLength = glyphs.length;
+ var textLayer = this.textLayer;
+ var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
+ var textSelection = textLayer && !skipTextSelection ? true : false;
+ var textRenderingMode = current.textRenderingMode;
+
+ // Type3 fonts - each glyph is a "mini-PDF"
+ if (font.coded) {
+ ctx.save();
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.translate(current.x, current.y);
+
+ ctx.scale(textHScale, 1);
+
+ if (textSelection) {
+ this.save();
+ ctx.scale(1, -1);
+ text.geom = this.getTextGeometry();
+ this.restore();
+ }
+ for (var i = 0; i < glyphsLength; ++i) {
+
+ var glyph = glyphs[i];
+ if (glyph === null) {
+ // word break
+ this.ctx.translate(wordSpacing, 0);
+ continue;
+ }
+
+ this.save();
+ ctx.scale(fontSize, fontSize);
+ ctx.transform.apply(ctx, fontMatrix);
+ this.executeOperatorList(glyph.operatorList);
+ this.restore();
+
+ var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
+ var width = transformed[0] * fontSize +
+ Util.sign(current.fontMatrix[0]) * charSpacing;
+
+ ctx.translate(width, 0);
+ current.x += width * textHScale;
+
+ text.str += glyph.unicode;
+ text.length++;
+ text.canvasWidth += width;
+ }
+ ctx.restore();
+ } else {
+ ctx.save();
+ this.applyTextTransforms();
+
+ var lineWidth = current.lineWidth;
+ var scale = Math.abs(current.textMatrix[0] * fontMatrix[0]);
+ if (scale == 0 || lineWidth == 0)
+ lineWidth = this.getSinglePixelWidth();
+ else
+ lineWidth /= scale;
+
+ if (textSelection)
+ text.geom = this.getTextGeometry();
+
+ if (fontSizeScale != 1.0) {
+ ctx.scale(fontSizeScale, fontSizeScale);
+ lineWidth /= fontSizeScale;
+ }
+
+ ctx.lineWidth = lineWidth;
+
+ var x = 0;
+ for (var i = 0; i < glyphsLength; ++i) {
+ var glyph = glyphs[i];
+ if (glyph === null) {
+ // word break
+ x += Util.sign(current.fontMatrix[0]) * wordSpacing;
+ continue;
+ }
+
+ var character = glyph.fontChar;
+ var charWidth = glyph.width * fontSize * 0.001 +
+ Util.sign(current.fontMatrix[0]) * charSpacing;
+
+ if (!glyph.disabled) {
+ var scaledX = x / fontSizeScale;
+ switch (textRenderingMode) {
+ default: // other unsupported rendering modes
+ case TextRenderingMode.FILL:
+ case TextRenderingMode.FILL_ADD_TO_PATH:
+ ctx.fillText(character, scaledX, 0);
+ break;
+ case TextRenderingMode.STROKE:
+ case TextRenderingMode.STROKE_ADD_TO_PATH:
+ ctx.strokeText(character, scaledX, 0);
+ break;
+ case TextRenderingMode.FILL_STROKE:
+ case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
+ ctx.fillText(character, scaledX, 0);
+ ctx.strokeText(character, scaledX, 0);
+ break;
+ case TextRenderingMode.INVISIBLE:
+ break;
+ }
+ }
+
+ x += charWidth;
+
+ var glyphUnicode = glyph.unicode === ' ' ? '\u00A0' : glyph.unicode;
+ var glyphUnicodeLength = glyphUnicode.length;
+ //reverse an arabic ligature
+ if (glyphUnicodeLength > 1 &&
+ isRTLRangeFor(glyphUnicode.charCodeAt(0))) {
+ for (var ii = glyphUnicodeLength - 1; ii >= 0; ii--)
+ text.str += glyphUnicode[ii];
+ } else
+ text.str += glyphUnicode;
+ text.length += glyphUnicodeLength;
+ text.canvasWidth += charWidth;
+ }
+ current.x += x * textHScale2;
+ ctx.restore();
+ }
+
+ if (textSelection)
+ this.textLayer.appendText(text, font.loadedName, fontSize);
+
+ return text;
+ },
+ showSpacedText: function CanvasGraphics_showSpacedText(arr) {
+ var ctx = this.ctx;
+ var current = this.current;
+ var font = current.font;
+ var fontSize = current.fontSize;
+ var textHScale = current.textHScale;
+ if (!font.coded)
+ textHScale *= (current.fontMatrix || IDENTITY_MATRIX)[0];
+ var arrLength = arr.length;
+ var textLayer = this.textLayer;
+ var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
+ var textSelection = textLayer ? true : false;
+
+ if (textSelection) {
+ ctx.save();
+ // Type3 fonts - each glyph is a "mini-PDF" (see also showText)
+ if (font.coded) {
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.scale(1, -1);
+ ctx.translate(current.x, -1 * current.y);
+ ctx.scale(textHScale, 1);
+ } else
+ this.applyTextTransforms();
+ text.geom = this.getTextGeometry();
+ ctx.restore();
+ }
+
+ for (var i = 0; i < arrLength; ++i) {
+ var e = arr[i];
+ if (isNum(e)) {
+ var spacingLength = -e * 0.001 * fontSize * textHScale;
+ current.x += spacingLength;
+
+ if (textSelection) {
+ // Emulate precise spacing via HTML spaces
+ text.canvasWidth += spacingLength;
+ if (e < 0 && text.geom.spaceWidth > 0) { // avoid div by zero
+ var numFakeSpaces = Math.round(-e / text.geom.spaceWidth);
+ if (numFakeSpaces > 0) {
+ text.str += '\u00A0';
+ text.length++;
+ }
+ }
+ }
+ } else if (isString(e)) {
+ var shownText = this.showText(e, true);
+
+ if (textSelection) {
+ if (shownText.str === ' ') {
+ text.str += '\u00A0';
+ } else {
+ text.str += shownText.str;
+ }
+ text.canvasWidth += shownText.canvasWidth;
+ text.length += shownText.length;
+ }
+ } else {
+ malformed('TJ array element ' + e + ' is not string or num');
+ }
+ }
+
+ if (textSelection)
+ this.textLayer.appendText(text, font.loadedName, fontSize);
+ },
+ nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
+ this.nextLine();
+ this.showText(text);
+ },
+ nextLineSetSpacingShowText:
+ function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing,
+ charSpacing,
+ text) {
+ this.setWordSpacing(wordSpacing);
+ this.setCharSpacing(charSpacing);
+ this.nextLineShowText(text);
+ },
+
+ // Type3 fonts
+ setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
+ // We can safely ignore this since the width should be the same
+ // as the width in the Widths array.
+ },
+ setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth,
+ yWidth,
+ llx,
+ lly,
+ urx,
+ ury) {
+ // TODO According to the spec we're also suppose to ignore any operators
+ // that set color or include images while processing this type3 font.
+ this.rectangle(llx, lly, urx - llx, ury - lly);
+ this.clip();
+ this.endPath();
+ },
+
+ // Color
+ setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) {
+ this.current.strokeColorSpace = ColorSpace.fromIR(raw);
+ },
+ setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) {
+ this.current.fillColorSpace = ColorSpace.fromIR(raw);
+ },
+ setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) {
+ var cs = this.current.strokeColorSpace;
+ var rgbColor = cs.getRgb(arguments);
+ var color = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
+ },
+ getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) {
+ if (IR[0] == 'TilingPattern') {
+ var args = IR[1];
+ var base = cs.base;
+ var color;
+ if (base) {
+ var baseComps = base.numComps;
+
+ color = [];
+ for (var i = 0; i < baseComps; ++i)
+ color.push(args[i]);
+
+ color = base.getRgb(color);
+ }
+ var pattern = new TilingPattern(IR, color, this.ctx, this.objs);
+ } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
+ var pattern = Pattern.shadingFromIR(IR);
+ } else {
+ error('Unkown IR type ' + IR[0]);
+ }
+ return pattern;
+ },
+ setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
+ var cs = this.current.strokeColorSpace;
+
+ if (cs.name == 'Pattern') {
+ this.current.strokeColor = this.getColorN_Pattern(arguments, cs);
+ } else {
+ this.setStrokeColor.apply(this, arguments);
+ }
+ },
+ setFillColor: function CanvasGraphics_setFillColor(/*...*/) {
+ var cs = this.current.fillColorSpace;
+ var rgbColor = cs.getRgb(arguments);
+ var color = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
+ },
+ setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
+ var cs = this.current.fillColorSpace;
+
+ if (cs.name == 'Pattern') {
+ this.current.fillColor = this.getColorN_Pattern(arguments, cs);
+ } else {
+ this.setFillColor.apply(this, arguments);
+ }
+ },
+ setStrokeGray: function CanvasGraphics_setStrokeGray(gray) {
+ if (!(this.current.strokeColorSpace instanceof DeviceGrayCS))
+ this.current.strokeColorSpace = new DeviceGrayCS();
+
+ var color = Util.makeCssRgb(gray, gray, gray);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
+ },
+ setFillGray: function CanvasGraphics_setFillGray(gray) {
+ if (!(this.current.fillColorSpace instanceof DeviceGrayCS))
+ this.current.fillColorSpace = new DeviceGrayCS();
+
+ var color = Util.makeCssRgb(gray, gray, gray);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
+ },
+ setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
+ if (!(this.current.strokeColorSpace instanceof DeviceRgbCS))
+ this.current.strokeColorSpace = new DeviceRgbCS();
+
+ var color = Util.makeCssRgb(r, g, b);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
+ },
+ setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
+ if (!(this.current.fillColorSpace instanceof DeviceRgbCS))
+ this.current.fillColorSpace = new DeviceRgbCS();
+
+ var color = Util.makeCssRgb(r, g, b);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
+ },
+ setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) {
+ if (!(this.current.strokeColorSpace instanceof DeviceCmykCS))
+ this.current.strokeColorSpace = new DeviceCmykCS();
+
+ var color = Util.makeCssCmyk(c, m, y, k);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
+ },
+ setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) {
+ if (!(this.current.fillColorSpace instanceof DeviceCmykCS))
+ this.current.fillColorSpace = new DeviceCmykCS();
+
+ var color = Util.makeCssCmyk(c, m, y, k);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
+ },
+
+ shadingFill: function CanvasGraphics_shadingFill(patternIR) {
+ var ctx = this.ctx;
+
+ this.save();
+ var pattern = Pattern.shadingFromIR(patternIR);
+ ctx.fillStyle = pattern.getPattern(ctx);
+
+ var inv = ctx.mozCurrentTransformInverse;
+ if (inv) {
+ var canvas = ctx.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+
+ var bl = Util.applyTransform([0, 0], inv);
+ var br = Util.applyTransform([0, height], inv);
+ var ul = Util.applyTransform([width, 0], inv);
+ var ur = Util.applyTransform([width, height], inv);
+
+ var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
+ var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
+ var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
+ var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+
+ this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
+ } else {
+ // HACK to draw the gradient onto an infinite rectangle.
+ // PDF gradients are drawn across the entire image while
+ // Canvas only allows gradients to be drawn in a rectangle
+ // The following bug should allow us to remove this.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
+
+ this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
+ }
+
+ this.restore();
+ },
+
+ // Images
+ beginInlineImage: function CanvasGraphics_beginInlineImage() {
+ error('Should not call beginInlineImage');
+ },
+ beginImageData: function CanvasGraphics_beginImageData() {
+ error('Should not call beginImageData');
+ },
+
+ paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
+ bbox) {
+ this.save();
+
+ if (matrix && isArray(matrix) && 6 == matrix.length)
+ this.transform.apply(this, matrix);
+
+ if (bbox && isArray(bbox) && 4 == bbox.length) {
+ var width = bbox[2] - bbox[0];
+ var height = bbox[3] - bbox[1];
+ this.rectangle(bbox[0], bbox[1], width, height);
+ this.clip();
+ this.endPath();
+ }
+ },
+
+ paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
+ this.restore();
+ },
+
+ paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
+ var domImage = this.objs.get(objId);
+ if (!domImage) {
+ error('Dependent image isn\'t ready yet');
+ }
+
+ this.save();
+
+ var ctx = this.ctx;
+ // scale the image to the unit square
+ ctx.scale(1 / w, -1 / h);
+
+ ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
+ 0, -h, w, h);
+
+ this.restore();
+ },
+
+ paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
+ imgArray, inverseDecode, width, height) {
+ function applyStencilMask(buffer, inverseDecode) {
+ var imgArrayPos = 0;
+ var i, j, mask, buf;
+ // removing making non-masked pixels transparent
+ var bufferPos = 3; // alpha component offset
+ for (i = 0; i < height; i++) {
+ mask = 0;
+ for (j = 0; j < width; j++) {
+ if (!mask) {
+ buf = imgArray[imgArrayPos++];
+ mask = 128;
+ }
+ if (!(buf & mask) == inverseDecode) {
+ buffer[bufferPos] = 0;
+ }
+ bufferPos += 4;
+ mask >>= 1;
+ }
+ }
+ }
+
+ this.save();
+
+ var ctx = this.ctx;
+ var w = width, h = height;
+ // scale the image to the unit square
+ ctx.scale(1 / w, -1 / h);
+
+ var tmpCanvas = createScratchCanvas(w, h);
+ var tmpCtx = tmpCanvas.getContext('2d');
+
+ var fillColor = this.current.fillColor;
+ tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
+ fillColor.type === 'Pattern') ?
+ fillColor.getPattern(tmpCtx) : fillColor;
+ tmpCtx.fillRect(0, 0, w, h);
+
+ var imgData = tmpCtx.getImageData(0, 0, w, h);
+ var pixels = imgData.data;
+
+ applyStencilMask(pixels, inverseDecode);
+
+ tmpCtx.putImageData(imgData, 0, 0);
+ ctx.drawImage(tmpCanvas, 0, -h);
+ this.restore();
+ },
+
+ paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
+ var imgData = this.objs.get(objId);
+ if (!imgData)
+ error('Dependent image isn\'t ready yet');
+
+ this.save();
+ var ctx = this.ctx;
+ var w = imgData.width;
+ var h = imgData.height;
+ // scale the image to the unit square
+ ctx.scale(1 / w, -1 / h);
+
+ var tmpCanvas = createScratchCanvas(w, h);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ this.putBinaryImageData(tmpCtx, imgData, w, h);
+
+ ctx.drawImage(tmpCanvas, 0, -h);
+ this.restore();
+ },
+
+ putBinaryImageData: function CanvasGraphics_putBinaryImageData() {
+ //
+ },
+
+ // Marked content
+
+ markPoint: function CanvasGraphics_markPoint(tag) {
+ TODO('Marked content');
+ },
+ markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
+ TODO('Marked content');
+ },
+ beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
+ TODO('Marked content');
+ },
+ beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
+ tag, properties) {
+ TODO('Marked content');
+ },
+ endMarkedContent: function CanvasGraphics_endMarkedContent() {
+ TODO('Marked content');
+ },
+
+ // Compatibility
+
+ beginCompat: function CanvasGraphics_beginCompat() {
+ TODO('ignore undefined operators (should we do that anyway?)');
+ },
+ endCompat: function CanvasGraphics_endCompat() {
+ TODO('stop ignoring undefined operators');
+ },
+
+ // Helper functions
+
+ consumePath: function CanvasGraphics_consumePath() {
+ if (this.pendingClip) {
+ var savedFillRule = null;
+ if (this.pendingClip == EO_CLIP)
+ savedFillRule = this.setEOFillRule();
+
+ this.ctx.clip();
+
+ this.pendingClip = null;
+ if (savedFillRule !== null)
+ this.restoreFillRule(savedFillRule);
+ }
+ this.ctx.beginPath();
+ },
+ // We generally keep the canvas context set for
+ // nonzero-winding, and just set evenodd for the operations
+ // that need them.
+ setEOFillRule: function CanvasGraphics_setEOFillRule() {
+ var savedFillRule = this.ctx.mozFillRule;
+ this.ctx.mozFillRule = 'evenodd';
+ return savedFillRule;
+ },
+ restoreFillRule: function CanvasGraphics_restoreFillRule(rule) {
+ this.ctx.mozFillRule = rule;
+ },
+ getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
+ var inverse = this.ctx.mozCurrentTransformInverse;
+ return Math.abs(inverse[0] + inverse[2]);
+ }
+ };
+
+ return CanvasGraphics;
+})();
+
+if (!isWorker) {
+ // Feature detection if the browser can use an Uint8Array directly as imgData.
+ var canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+
+ try {
+ ctx.putImageData({
+ width: 1,
+ height: 1,
+ data: new Uint8Array(4)
+ }, 0, 0);
+
+ CanvasGraphics.prototype.putBinaryImageData =
+ function CanvasGraphicsPutBinaryImageDataNative(ctx, imgData) {
+ ctx.putImageData(imgData, 0, 0);
+ };
+ } catch (e) {
+ CanvasGraphics.prototype.putBinaryImageData =
+ function CanvasGraphicsPutBinaryImageDataShim(ctx, imgData, w, h) {
+ var tmpImgData = ctx.getImageData(0, 0, w, h);
+
+ // Copy over the imageData pixel by pixel.
+ var tmpImgDataPixels = tmpImgData.data;
+ var len = tmpImgDataPixels.length;
+
+ while (len--) {
+ tmpImgDataPixels[len] = imgData.data[len];
+ }
+
+ ctx.putImageData(tmpImgData, 0, 0);
+ };
+ }
+}
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var Name = (function NameClosure() {
+ function Name(name) {
+ this.name = name;
+ }
+
+ Name.prototype = {};
+
+ return Name;
+})();
+
+var Cmd = (function CmdClosure() {
+ function Cmd(cmd) {
+ this.cmd = cmd;
+ }
+
+ Cmd.prototype = {};
+
+ var cmdCache = {};
+
+ Cmd.get = function Cmd_get(cmd) {
+ var cmdValue = cmdCache[cmd];
+ if (cmdValue)
+ return cmdValue;
+
+ return cmdCache[cmd] = new Cmd(cmd);
+ };
+
+ return Cmd;
+})();
+
+var Dict = (function DictClosure() {
+ // xref is optional
+ function Dict(xref) {
+ // Map should only be used internally, use functions below to access.
+ var map = Object.create(null);
+
+ this.assignXref = function Dict_assingXref(newXref) {
+ xref = newXref;
+ };
+
+ // automatically dereferences Ref objects
+ this.get = function Dict_get(key1, key2, key3) {
+ var value;
+ if (typeof (value = map[key1]) != 'undefined' || key1 in map ||
+ typeof key2 == 'undefined') {
+ return xref ? xref.fetchIfRef(value) : value;
+ }
+ if (typeof (value = map[key2]) != 'undefined' || key2 in map ||
+ typeof key3 == 'undefined') {
+ return xref ? xref.fetchIfRef(value) : value;
+ }
+ value = map[key3] || null;
+ return xref ? xref.fetchIfRef(value) : value;
+ };
+
+ // no dereferencing
+ this.getRaw = function Dict_getRaw(key) {
+ return map[key];
+ };
+
+ // creates new map and dereferences all Refs
+ this.getAll = function Dict_getAll() {
+ var all = {};
+ for (var key in map) {
+ var obj = this.get(key);
+ all[key] = obj instanceof Dict ? obj.getAll() : obj;
+ }
+ return all;
+ };
+
+ this.set = function Dict_set(key, value) {
+ map[key] = value;
+ };
+
+ this.has = function Dict_has(key) {
+ return key in map;
+ };
+
+ this.forEach = function Dict_forEach(callback) {
+ for (var key in map) {
+ callback(key, this.get(key));
+ }
+ };
+ };
+
+ return Dict;
+})();
+
+var Ref = (function RefClosure() {
+ function Ref(num, gen) {
+ this.num = num;
+ this.gen = gen;
+ }
+
+ Ref.prototype = {};
+
+ return Ref;
+})();
+
+// The reference is identified by number and generation,
+// this structure stores only one instance of the reference.
+var RefSet = (function RefSetClosure() {
+ function RefSet() {
+ this.dict = {};
+ }
+
+ RefSet.prototype = {
+ has: function RefSet_has(ref) {
+ return !!this.dict['R' + ref.num + '.' + ref.gen];
+ },
+
+ put: function RefSet_put(ref) {
+ this.dict['R' + ref.num + '.' + ref.gen] = ref;
+ }
+ };
+
+ return RefSet;
+})();
+
+var Catalog = (function CatalogClosure() {
+ function Catalog(xref) {
+ this.xref = xref;
+ var obj = xref.getCatalogObj();
+ assertWellFormed(isDict(obj), 'catalog object is not a dictionary');
+ this.catDict = obj;
+ }
+
+ Catalog.prototype = {
+ get metadata() {
+ var stream = this.catDict.get('Metadata');
+ var metadata;
+ if (stream && isDict(stream.dict)) {
+ var type = stream.dict.get('Type');
+ var subtype = stream.dict.get('Subtype');
+
+ if (isName(type) && isName(subtype) &&
+ type.name === 'Metadata' && subtype.name === 'XML') {
+ metadata = stringToPDFString(bytesToString(stream.getBytes()));
+ }
+ }
+
+ return shadow(this, 'metadata', metadata);
+ },
+ get toplevelPagesDict() {
+ var pagesObj = this.catDict.get('Pages');
+ assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary');
+ // shadow the prototype getter
+ return shadow(this, 'toplevelPagesDict', pagesObj);
+ },
+ get documentOutline() {
+ var xref = this.xref;
+ var obj = this.catDict.get('Outlines');
+ var root = { items: [] };
+ if (isDict(obj)) {
+ obj = obj.getRaw('First');
+ var processed = new RefSet();
+ if (isRef(obj)) {
+ var queue = [{obj: obj, parent: root}];
+ // to avoid recursion keeping track of the items
+ // in the processed dictionary
+ processed.put(obj);
+ while (queue.length > 0) {
+ var i = queue.shift();
+ var outlineDict = xref.fetchIfRef(i.obj);
+ if (outlineDict === null)
+ continue;
+ if (!outlineDict.has('Title'))
+ error('Invalid outline item');
+ var dest = outlineDict.get('A');
+ if (dest)
+ dest = dest.get('D');
+ else if (outlineDict.has('Dest')) {
+ dest = outlineDict.getRaw('Dest');
+ if (isName(dest))
+ dest = dest.name;
+ }
+ var title = outlineDict.get('Title');
+ var outlineItem = {
+ dest: dest,
+ title: stringToPDFString(title),
+ color: outlineDict.get('C') || [0, 0, 0],
+ count: outlineDict.get('Count'),
+ bold: !!(outlineDict.get('F') & 2),
+ italic: !!(outlineDict.get('F') & 1),
+ items: []
+ };
+ i.parent.items.push(outlineItem);
+ obj = outlineDict.getRaw('First');
+ if (isRef(obj) && !processed.has(obj)) {
+ queue.push({obj: obj, parent: outlineItem});
+ processed.put(obj);
+ }
+ obj = outlineDict.getRaw('Next');
+ if (isRef(obj) && !processed.has(obj)) {
+ queue.push({obj: obj, parent: i.parent});
+ processed.put(obj);
+ }
+ }
+ }
+ }
+ obj = root.items.length > 0 ? root.items : null;
+ return shadow(this, 'documentOutline', obj);
+ },
+ get numPages() {
+ var obj = this.toplevelPagesDict.get('Count');
+ assertWellFormed(
+ isInt(obj),
+ 'page count in top level pages object is not an integer'
+ );
+ // shadow the prototype getter
+ return shadow(this, 'num', obj);
+ },
+ traverseKids: function Catalog_traverseKids(pagesDict) {
+ var pageCache = this.pageCache;
+ var kids = pagesDict.get('Kids');
+ assertWellFormed(isArray(kids),
+ 'page dictionary kids object is not an array');
+ for (var i = 0, ii = kids.length; i < ii; ++i) {
+ var kid = kids[i];
+ assertWellFormed(isRef(kid),
+ 'page dictionary kid is not a reference');
+ var obj = this.xref.fetch(kid);
+ if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) {
+ pageCache.push(new Page(this.xref, pageCache.length, obj, kid));
+ } else { // must be a child page dictionary
+ assertWellFormed(
+ isDict(obj),
+ 'page dictionary kid reference points to wrong type of object'
+ );
+ this.traverseKids(obj);
+ }
+ }
+ },
+ get destinations() {
+ function fetchDestination(dest) {
+ return isDict(dest) ? dest.get('D') : dest;
+ }
+
+ var xref = this.xref;
+ var dests = {}, nameTreeRef, nameDictionaryRef;
+ var obj = this.catDict.get('Names');
+ if (obj)
+ nameTreeRef = obj.getRaw('Dests');
+ else if (this.catDict.has('Dests'))
+ nameDictionaryRef = this.catDict.get('Dests');
+
+ if (nameDictionaryRef) {
+ // reading simple destination dictionary
+ obj = nameDictionaryRef;
+ obj.forEach(function catalogForEach(key, value) {
+ if (!value) return;
+ dests[key] = fetchDestination(value);
+ });
+ }
+ if (nameTreeRef) {
+ // reading name tree
+ var processed = new RefSet();
+ processed.put(nameTreeRef);
+ var queue = [nameTreeRef];
+ while (queue.length > 0) {
+ var i, n;
+ obj = xref.fetch(queue.shift());
+ if (obj.has('Kids')) {
+ var kids = obj.get('Kids');
+ for (i = 0, n = kids.length; i < n; i++) {
+ var kid = kids[i];
+ if (processed.has(kid))
+ error('invalid destinations');
+ queue.push(kid);
+ processed.put(kid);
+ }
+ continue;
+ }
+ var names = obj.get('Names');
+ for (i = 0, n = names.length; i < n; i += 2) {
+ dests[names[i]] = fetchDestination(xref.fetchIfRef(names[i + 1]));
+ }
+ }
+ }
+ return shadow(this, 'destinations', dests);
+ },
+ getPage: function Catalog_getPage(n) {
+ var pageCache = this.pageCache;
+ if (!pageCache) {
+ pageCache = this.pageCache = [];
+ this.traverseKids(this.toplevelPagesDict);
+ }
+ return this.pageCache[n - 1];
+ }
+ };
+
+ return Catalog;
+})();
+
+var XRef = (function XRefClosure() {
+ function XRef(stream, startXRef, mainXRefEntriesOffset) {
+ this.stream = stream;
+ this.entries = [];
+ this.xrefstms = {};
+ var trailerDict = this.readXRef(startXRef);
+ trailerDict.assignXref(this);
+ this.trailer = trailerDict;
+ // prepare the XRef cache
+ this.cache = [];
+
+ var encrypt = trailerDict.get('Encrypt');
+ if (encrypt) {
+ var fileId = trailerDict.get('ID');
+ this.encrypt = new CipherTransformFactory(encrypt,
+ fileId[0] /*, password */);
+ }
+
+ // get the root dictionary (catalog) object
+ if (!(this.root = trailerDict.get('Root')))
+ error('Invalid root reference');
+ }
+
+ XRef.prototype = {
+ readXRefTable: function XRef_readXRefTable(parser) {
+ // Example of cross-reference table:
+ // xref
+ // 0 1 <-- subsection header (first obj #, obj count)
+ // 0000000000 65535 f <-- actual object (offset, generation #, f/n)
+ // 23 2 <-- subsection header ... and so on ...
+ // 0000025518 00002 n
+ // 0000025635 00000 n
+ // trailer
+ // ...
+
+ // Outer loop is over subsection headers
+ var obj;
+ while (!isCmd(obj = parser.getObj(), 'trailer')) {
+ var first = obj,
+ count = parser.getObj();
+
+ if (!isInt(first) || !isInt(count))
+ error('Invalid XRef table: wrong types in subsection header');
+
+ // Inner loop is over objects themselves
+ for (var i = 0; i < count; i++) {
+ var entry = {};
+ entry.offset = parser.getObj();
+ entry.gen = parser.getObj();
+ var type = parser.getObj();
+
+ if (isCmd(type, 'f'))
+ entry.free = true;
+ else if (isCmd(type, 'n'))
+ entry.uncompressed = true;
+
+ // Validate entry obj
+ if (!isInt(entry.offset) || !isInt(entry.gen) ||
+ !(entry.free || entry.uncompressed)) {
+ error('Invalid entry in XRef subsection: ' + first + ', ' + count);
+ }
+
+ if (!this.entries[i + first])
+ this.entries[i + first] = entry;
+ }
+ }
+
+ // Sanity check: as per spec, first object must have these properties
+ if (this.entries[0] &&
+ !(this.entries[0].gen === 65535 && this.entries[0].free))
+ error('Invalid XRef table: unexpected first object');
+
+ // Sanity check
+ if (!isCmd(obj, 'trailer'))
+ error('Invalid XRef table: could not find trailer dictionary');
+
+ // Read trailer dictionary, e.g.
+ // trailer
+ // << /Size 22
+ // /Root 20R
+ // /Info 10R
+ // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
+ // >>
+ // The parser goes through the entire stream << ... >> and provides
+ // a getter interface for the key-value table
+ var dict = parser.getObj();
+ if (!isDict(dict))
+ error('Invalid XRef table: could not parse trailer dictionary');
+
+ return dict;
+ },
+ readXRefStream: function XRef_readXRefStream(stream) {
+ var streamParameters = stream.parameters;
+ var byteWidths = streamParameters.get('W');
+ var range = streamParameters.get('Index');
+ if (!range)
+ range = [0, streamParameters.get('Size')];
+ var i, j;
+ while (range.length > 0) {
+ var first = range[0], n = range[1];
+ if (!isInt(first) || !isInt(n))
+ error('Invalid XRef range fields: ' + first + ', ' + n);
+ var typeFieldWidth = byteWidths[0];
+ var offsetFieldWidth = byteWidths[1];
+ var generationFieldWidth = byteWidths[2];
+ if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) ||
+ !isInt(generationFieldWidth)) {
+ error('Invalid XRef entry fields length: ' + first + ', ' + n);
+ }
+ for (i = 0; i < n; ++i) {
+ var type = 0, offset = 0, generation = 0;
+ for (j = 0; j < typeFieldWidth; ++j)
+ type = (type << 8) | stream.getByte();
+ // if type field is absent, its default value = 1
+ if (typeFieldWidth == 0)
+ type = 1;
+ for (j = 0; j < offsetFieldWidth; ++j)
+ offset = (offset << 8) | stream.getByte();
+ for (j = 0; j < generationFieldWidth; ++j)
+ generation = (generation << 8) | stream.getByte();
+ var entry = {};
+ entry.offset = offset;
+ entry.gen = generation;
+ switch (type) {
+ case 0:
+ entry.free = true;
+ break;
+ case 1:
+ entry.uncompressed = true;
+ break;
+ case 2:
+ break;
+ default:
+ error('Invalid XRef entry type: ' + type);
+ }
+ if (!this.entries[first + i])
+ this.entries[first + i] = entry;
+ }
+ range.splice(0, 2);
+ }
+ return streamParameters;
+ },
+ indexObjects: function XRef_indexObjects() {
+ // Simple scan through the PDF content to find objects,
+ // trailers and XRef streams.
+ function readToken(data, offset) {
+ var token = '', ch = data[offset];
+ while (ch !== 13 && ch !== 10) {
+ if (++offset >= data.length)
+ break;
+ token += String.fromCharCode(ch);
+ ch = data[offset];
+ }
+ return token;
+ }
+ function skipUntil(data, offset, what) {
+ var length = what.length, dataLength = data.length;
+ var skipped = 0;
+ // finding byte sequence
+ while (offset < dataLength) {
+ var i = 0;
+ while (i < length && data[offset + i] == what[i])
+ ++i;
+ if (i >= length)
+ break; // sequence found
+
+ offset++;
+ skipped++;
+ }
+ return skipped;
+ }
+ var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
+ var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114,
+ 101, 102]);
+ var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
+ var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
+
+ var stream = this.stream;
+ stream.pos = 0;
+ var buffer = stream.getBytes();
+ var position = stream.start, length = buffer.length;
+ var trailers = [], xrefStms = [];
+ var state = 0;
+ var currentToken;
+ while (position < length) {
+ var ch = buffer[position];
+ if (ch === 32 || ch === 9 || ch === 13 || ch === 10) {
+ ++position;
+ continue;
+ }
+ if (ch === 37) { // %-comment
+ do {
+ ++position;
+ ch = buffer[position];
+ } while (ch !== 13 && ch !== 10);
+ continue;
+ }
+ var token = readToken(buffer, position);
+ var m;
+ if (token === 'xref') {
+ position += skipUntil(buffer, position, trailerBytes);
+ trailers.push(position);
+ position += skipUntil(buffer, position, startxrefBytes);
+ } else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) {
+ this.entries[m[1]] = {
+ offset: position,
+ gen: m[2] | 0,
+ uncompressed: true
+ };
+
+ var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
+ var content = buffer.subarray(position, position + contentLength);
+
+ // checking XRef stream suspect
+ // (it shall have '/XRef' and next char is not a letter)
+ var xrefTagOffset = skipUntil(content, 0, xrefBytes);
+ if (xrefTagOffset < contentLength &&
+ content[xrefTagOffset + 5] < 64) {
+ xrefStms.push(position);
+ this.xrefstms[position] = 1; // don't read it recursively
+ }
+
+ position += contentLength;
+ } else
+ position += token.length + 1;
+ }
+ // reading XRef streams
+ for (var i = 0, ii = xrefStms.length; i < ii; ++i) {
+ this.readXRef(xrefStms[i]);
+ }
+ // finding main trailer
+ var dict;
+ for (var i = 0, ii = trailers.length; i < ii; ++i) {
+ stream.pos = trailers[i];
+ var parser = new Parser(new Lexer(stream), true, null);
+ var obj = parser.getObj();
+ if (!isCmd(obj, 'trailer'))
+ continue;
+ // read the trailer dictionary
+ if (!isDict(dict = parser.getObj()))
+ continue;
+ // taking the first one with 'ID'
+ if (dict.has('ID'))
+ return dict;
+ }
+ // no tailer with 'ID', taking last one (if exists)
+ if (dict)
+ return dict;
+ // nothing helps
+ error('Invalid PDF structure');
+ },
+ readXRef: function XRef_readXRef(startXRef) {
+ var stream = this.stream;
+ stream.pos = startXRef;
+
+ try {
+ var parser = new Parser(new Lexer(stream), true, null);
+ var obj = parser.getObj();
+ var dict;
+
+ // Get dictionary
+ if (isCmd(obj, 'xref')) {
+ // Parse end-of-file XRef
+ dict = this.readXRefTable(parser);
+
+ // Recursively get other XRefs 'XRefStm', if any
+ obj = dict.get('XRefStm');
+ if (isInt(obj)) {
+ var pos = obj;
+ // ignore previously loaded xref streams
+ // (possible infinite recursion)
+ if (!(pos in this.xrefstms)) {
+ this.xrefstms[pos] = 1;
+ this.readXRef(pos);
+ }
+ }
+ } else if (isInt(obj)) {
+ // Parse in-stream XRef
+ if (!isInt(parser.getObj()) ||
+ !isCmd(parser.getObj(), 'obj') ||
+ !isStream(obj = parser.getObj())) {
+ error('Invalid XRef stream');
+ }
+ dict = this.readXRefStream(obj);
+ }
+
+ // Recursively get previous dictionary, if any
+ obj = dict.get('Prev');
+ if (isInt(obj))
+ this.readXRef(obj);
+ else if (isRef(obj)) {
+ // The spec says Prev must not be a reference, i.e. "/Prev NNN"
+ // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
+ this.readXRef(obj.num);
+ }
+
+ return dict;
+ } catch (e) {
+ log('(while reading XRef): ' + e);
+ }
+
+ warn('Indexing all PDF objects');
+ return this.indexObjects();
+ },
+ getEntry: function XRef_getEntry(i) {
+ var e = this.entries[i];
+ if (e === null)
+ return null;
+ return e.free ? null : e; // returns null is the entry is free
+ },
+ fetchIfRef: function XRef_fetchIfRef(obj) {
+ if (!isRef(obj))
+ return obj;
+ return this.fetch(obj);
+ },
+ fetch: function XRef_fetch(ref, suppressEncryption) {
+ assertWellFormed(isRef(ref), 'ref object is not a reference');
+ var num = ref.num;
+ if (num in this.cache)
+ return this.cache[num];
+
+ var e = this.getEntry(num);
+
+ // the referenced entry can be free
+ if (e === null)
+ return (this.cache[num] = e);
+
+ var gen = ref.gen;
+ var stream, parser;
+ if (e.uncompressed) {
+ if (e.gen != gen)
+ error('inconsistent generation in XRef');
+ stream = this.stream.makeSubStream(e.offset);
+ parser = new Parser(new Lexer(stream), true, this);
+ var obj1 = parser.getObj();
+ var obj2 = parser.getObj();
+ var obj3 = parser.getObj();
+ if (!isInt(obj1) || obj1 != num ||
+ !isInt(obj2) || obj2 != gen ||
+ !isCmd(obj3)) {
+ error('bad XRef entry');
+ }
+ if (!isCmd(obj3, 'obj')) {
+ // some bad pdfs use "obj1234" and really mean 1234
+ if (obj3.cmd.indexOf('obj') == 0) {
+ num = parseInt(obj3.cmd.substring(3), 10);
+ if (!isNaN(num))
+ return num;
+ }
+ error('bad XRef entry');
+ }
+ if (this.encrypt && !suppressEncryption) {
+ try {
+ e = parser.getObj(this.encrypt.createCipherTransform(num, gen));
+ } catch (ex) {
+ // almost all streams must be encrypted, but sometimes
+ // they are not probably due to some broken generators
+ // re-trying without encryption
+ return this.fetch(ref, true);
+ }
+ } else {
+ e = parser.getObj();
+ }
+ // Don't cache streams since they are mutable (except images).
+ if (!isStream(e) || e instanceof JpegStream)
+ this.cache[num] = e;
+ return e;
+ }
+
+ // compressed entry
+ stream = this.fetch(new Ref(e.offset, 0));
+ if (!isStream(stream))
+ error('bad ObjStm stream');
+ var first = stream.parameters.get('First');
+ var n = stream.parameters.get('N');
+ if (!isInt(first) || !isInt(n)) {
+ error('invalid first and n parameters for ObjStm stream');
+ }
+ parser = new Parser(new Lexer(stream), false, this);
+ var i, entries = [], nums = [];
+ // read the object numbers to populate cache
+ for (i = 0; i < n; ++i) {
+ num = parser.getObj();
+ if (!isInt(num)) {
+ error('invalid object number in the ObjStm stream: ' + num);
+ }
+ nums.push(num);
+ var offset = parser.getObj();
+ if (!isInt(offset)) {
+ error('invalid object offset in the ObjStm stream: ' + offset);
+ }
+ }
+ // read stream objects for cache
+ for (i = 0; i < n; ++i) {
+ entries.push(parser.getObj());
+ this.cache[nums[i]] = entries[i];
+ }
+ e = entries[e.gen];
+ if (!e) {
+ error('bad XRef entry for compressed object');
+ }
+ return e;
+ },
+ getCatalogObj: function XRef_getCatalogObj() {
+ return this.root;
+ }
+ };
+
+ return XRef;
+})();
+
+/**
+ * A PDF document and page is built of many objects. E.g. there are objects
+ * for fonts, images, rendering code and such. These objects might get processed
+ * inside of a worker. The `PDFObjects` implements some basic functions to
+ * manage these objects.
+ */
+var PDFObjects = (function PDFObjectsClosure() {
+ function PDFObjects() {
+ this.objs = {};
+ }
+
+ PDFObjects.prototype = {
+ objs: null,
+
+ /**
+ * Internal function.
+ * Ensures there is an object defined for `objId`. Stores `data` on the
+ * object *if* it is created.
+ */
+ ensureObj: function PDFObjects_ensureObj(objId, data) {
+ if (this.objs[objId])
+ return this.objs[objId];
+ return this.objs[objId] = new Promise(objId, data);
+ },
+
+ /**
+ * If called *without* callback, this returns the data of `objId` but the
+ * object needs to be resolved. If it isn't, this function throws.
+ *
+ * If called *with* a callback, the callback is called with the data of the
+ * object once the object is resolved. That means, if you call this
+ * function and the object is already resolved, the callback gets called
+ * right away.
+ */
+ get: function PDFObjects_get(objId, callback) {
+ // If there is a callback, then the get can be async and the object is
+ // not required to be resolved right now
+ if (callback) {
+ this.ensureObj(objId).then(callback);
+ return null;
+ }
+
+ // If there isn't a callback, the user expects to get the resolved data
+ // directly.
+ var obj = this.objs[objId];
+
+ // If there isn't an object yet or the object isn't resolved, then the
+ // data isn't ready yet!
+ if (!obj || !obj.isResolved)
+ error('Requesting object that isn\'t resolved yet ' + objId);
+
+ return obj.data;
+ },
+
+ /**
+ * Resolves the object `objId` with optional `data`.
+ */
+ resolve: function PDFObjects_resolve(objId, data) {
+ var objs = this.objs;
+
+ // In case there is a promise already on this object, just resolve it.
+ if (objs[objId]) {
+ objs[objId].resolve(data);
+ } else {
+ this.ensureObj(objId, data);
+ }
+ },
+
+ onData: function PDFObjects_onData(objId, callback) {
+ this.ensureObj(objId).onData(callback);
+ },
+
+ isResolved: function PDFObjects_isResolved(objId) {
+ var objs = this.objs;
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].isResolved;
+ }
+ },
+
+ hasData: function PDFObjects_hasData(objId) {
+ var objs = this.objs;
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].hasData;
+ }
+ },
+
+ /**
+ * Sets the data of an object but *doesn't* resolve it.
+ */
+ setData: function PDFObjects_setData(objId, data) {
+ // Watchout! If you call `this.ensureObj(objId, data)` you're going to
+ // create a *resolved* promise which shouldn't be the case!
+ this.ensureObj(objId).data = data;
+ }
+ };
+ return PDFObjects;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var PDFFunction = (function PDFFunctionClosure() {
+ var CONSTRUCT_SAMPLED = 0;
+ var CONSTRUCT_INTERPOLATED = 2;
+ var CONSTRUCT_STICHED = 3;
+ var CONSTRUCT_POSTSCRIPT = 4;
+
+ return {
+ getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps,
+ str) {
+ var length = 1;
+ for (var i = 0, ii = size.length; i < ii; i++)
+ length *= size[i];
+ length *= outputSize;
+
+ var array = [];
+ var codeSize = 0;
+ var codeBuf = 0;
+ // 32 is a valid bps so shifting won't work
+ var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
+
+ var strBytes = str.getBytes((length * bps + 7) / 8);
+ var strIdx = 0;
+ for (var i = 0; i < length; i++) {
+ while (codeSize < bps) {
+ codeBuf <<= 8;
+ codeBuf |= strBytes[strIdx++];
+ codeSize += 8;
+ }
+ codeSize -= bps;
+ array.push((codeBuf >> codeSize) * sampleMul);
+ codeBuf &= (1 << codeSize) - 1;
+ }
+ return array;
+ },
+
+ getIR: function PDFFunction_getIR(xref, fn) {
+ var dict = fn.dict;
+ if (!dict)
+ dict = fn;
+
+ var types = [this.constructSampled,
+ null,
+ this.constructInterpolated,
+ this.constructStiched,
+ this.constructPostScript];
+
+ var typeNum = dict.get('FunctionType');
+ var typeFn = types[typeNum];
+ if (!typeFn)
+ error('Unknown type of function');
+
+ return typeFn.call(this, fn, dict, xref);
+ },
+
+ fromIR: function PDFFunction_fromIR(IR) {
+ var type = IR[0];
+ switch (type) {
+ case CONSTRUCT_SAMPLED:
+ return this.constructSampledFromIR(IR);
+ case CONSTRUCT_INTERPOLATED:
+ return this.constructInterpolatedFromIR(IR);
+ case CONSTRUCT_STICHED:
+ return this.constructStichedFromIR(IR);
+ case CONSTRUCT_POSTSCRIPT:
+ default:
+ return this.constructPostScriptFromIR(IR);
+ }
+ },
+
+ parse: function PDFFunction_parse(xref, fn) {
+ var IR = this.getIR(xref, fn);
+ return this.fromIR(IR);
+ },
+
+ constructSampled: function PDFFunction_constructSampled(str, dict) {
+ function toMultiArray(arr) {
+ var inputLength = arr.length;
+ var outputLength = arr.length / 2;
+ var out = [];
+ var index = 0;
+ for (var i = 0; i < inputLength; i += 2) {
+ out[index] = [arr[i], arr[i + 1]];
+ ++index;
+ }
+ return out;
+ }
+ var domain = dict.get('Domain');
+ var range = dict.get('Range');
+
+ if (!domain || !range)
+ error('No domain or range');
+
+ var inputSize = domain.length / 2;
+ var outputSize = range.length / 2;
+
+ domain = toMultiArray(domain);
+ range = toMultiArray(range);
+
+ var size = dict.get('Size');
+ var bps = dict.get('BitsPerSample');
+ var order = dict.get('Order');
+ if (!order)
+ order = 1;
+ if (order !== 1)
+ error('No support for cubic spline interpolation: ' + order);
+
+ var encode = dict.get('Encode');
+ if (!encode) {
+ encode = [];
+ for (var i = 0; i < inputSize; ++i) {
+ encode.push(0);
+ encode.push(size[i] - 1);
+ }
+ }
+ encode = toMultiArray(encode);
+
+ var decode = dict.get('Decode');
+ if (!decode)
+ decode = range;
+ else
+ decode = toMultiArray(decode);
+
+ var samples = this.getSampleArray(size, outputSize, bps, str);
+
+ return [
+ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
+ outputSize, Math.pow(2, bps) - 1, range
+ ];
+ },
+
+ constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) {
+ // See chapter 3, page 109 of the PDF reference
+ function interpolate(x, xmin, xmax, ymin, ymax) {
+ return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin)));
+ }
+
+ return function constructSampledFromIRResult(args) {
+ // See chapter 3, page 110 of the PDF reference.
+ var m = IR[1];
+ var domain = IR[2];
+ var encode = IR[3];
+ var decode = IR[4];
+ var samples = IR[5];
+ var size = IR[6];
+ var n = IR[7];
+ var mask = IR[8];
+ var range = IR[9];
+
+ if (m != args.length)
+ error('Incorrect number of arguments: ' + inputSize + ' != ' +
+ args.length);
+
+ var x = args;
+
+ // Building the cube vertices: its part and sample index
+ // http://rjwagner49.com/Mathematics/Interpolation.pdf
+ var cubeVertices = 1 << m;
+ var cubeN = new Float64Array(cubeVertices);
+ var cubeVertex = new Uint32Array(cubeVertices);
+ for (var j = 0; j < cubeVertices; j++)
+ cubeN[j] = 1;
+
+ var k = n, pos = 1;
+ // Map x_i to y_j for 0 <= i < m using the sampled function.
+ for (var i = 0; i < m; ++i) {
+ // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
+ var domain_2i = domain[i][0];
+ var domain_2i_1 = domain[i][1];
+ var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
+
+ // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1,
+ // Encode_2i, Encode_2i+1)
+ var e = interpolate(xi, domain_2i, domain_2i_1,
+ encode[i][0], encode[i][1]);
+
+ // e_i' = min(max(e_i, 0), Size_i - 1)
+ var size_i = size[i];
+ e = Math.min(Math.max(e, 0), size_i - 1);
+
+ // Adjusting the cube: N and vertex sample index
+ var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1;
+ var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0);
+ var n1 = e - e0; // (e - e0) / (e1 - e0);
+ var offset0 = e0 * k;
+ var offset1 = offset0 + k; // e1 * k
+ for (var j = 0; j < cubeVertices; j++) {
+ if (j & pos) {
+ cubeN[j] *= n1;
+ cubeVertex[j] += offset1;
+ } else {
+ cubeN[j] *= n0;
+ cubeVertex[j] += offset0;
+ }
+ }
+
+ k *= size_i;
+ pos <<= 1;
+ }
+
+ var y = new Float64Array(n);
+ for (var j = 0; j < n; ++j) {
+ // Sum all cube vertices' samples portions
+ var rj = 0;
+ for (var i = 0; i < cubeVertices; i++)
+ rj += samples[cubeVertex[i] + j] * cubeN[i];
+
+ // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
+ // Decode_2j, Decode_2j+1)
+ rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
+
+ // y_j = min(max(r_j, range_2j), range_2j+1)
+ y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
+ }
+
+ return y;
+ }
+ },
+
+ constructInterpolated: function PDFFunction_constructInterpolated(str,
+ dict) {
+ var c0 = dict.get('C0') || [0];
+ var c1 = dict.get('C1') || [1];
+ var n = dict.get('N');
+
+ if (!isArray(c0) || !isArray(c1))
+ error('Illegal dictionary for interpolated function');
+
+ var length = c0.length;
+ var diff = [];
+ for (var i = 0; i < length; ++i)
+ diff.push(c1[i] - c0[i]);
+
+ return [CONSTRUCT_INTERPOLATED, c0, diff, n];
+ },
+
+ constructInterpolatedFromIR:
+ function PDFFunction_constructInterpolatedFromIR(IR) {
+ var c0 = IR[1];
+ var diff = IR[2];
+ var n = IR[3];
+
+ var length = diff.length;
+
+ return function constructInterpolatedFromIRResult(args) {
+ var x = n == 1 ? args[0] : Math.pow(args[0], n);
+
+ var out = [];
+ for (var j = 0; j < length; ++j)
+ out.push(c0[j] + (x * diff[j]));
+
+ return out;
+
+ }
+ },
+
+ constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
+ var domain = dict.get('Domain');
+
+ if (!domain)
+ error('No domain');
+
+ var inputSize = domain.length / 2;
+ if (inputSize != 1)
+ error('Bad domain for stiched function');
+
+ var fnRefs = dict.get('Functions');
+ var fns = [];
+ for (var i = 0, ii = fnRefs.length; i < ii; ++i)
+ fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
+
+ var bounds = dict.get('Bounds');
+ var encode = dict.get('Encode');
+
+ return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
+ },
+
+ constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
+ var domain = IR[1];
+ var bounds = IR[2];
+ var encode = IR[3];
+ var fnsIR = IR[4];
+ var fns = [];
+
+ for (var i = 0, ii = fnsIR.length; i < ii; i++) {
+ fns.push(PDFFunction.fromIR(fnsIR[i]));
+ }
+
+ return function constructStichedFromIRResult(args) {
+ var clip = function constructStichedFromIRClip(v, min, max) {
+ if (v > max)
+ v = max;
+ else if (v < min)
+ v = min;
+ return v;
+ };
+
+ // clip to domain
+ var v = clip(args[0], domain[0], domain[1]);
+ // calulate which bound the value is in
+ for (var i = 0, ii = bounds.length; i < ii; ++i) {
+ if (v < bounds[i])
+ break;
+ }
+
+ // encode value into domain of function
+ var dmin = domain[0];
+ if (i > 0)
+ dmin = bounds[i - 1];
+ var dmax = domain[1];
+ if (i < bounds.length)
+ dmax = bounds[i];
+
+ var rmin = encode[2 * i];
+ var rmax = encode[2 * i + 1];
+
+ var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
+
+ // call the appropropriate function
+ return fns[i]([v2]);
+ };
+ },
+
+ constructPostScript: function PDFFunction_constructPostScript(fn, dict,
+ xref) {
+ var domain = dict.get('Domain');
+ var range = dict.get('Range');
+
+ if (!domain)
+ error('No domain.');
+
+ if (!range)
+ error('No range.');
+
+ var lexer = new PostScriptLexer(fn);
+ var parser = new PostScriptParser(lexer);
+ var code = parser.parse();
+
+ return [CONSTRUCT_POSTSCRIPT, domain, range, code];
+ },
+
+ constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR(
+ IR) {
+ var domain = IR[1];
+ var range = IR[2];
+ var code = IR[3];
+ var numOutputs = range.length / 2;
+ var evaluator = new PostScriptEvaluator(code);
+ // Cache the values for a big speed up, the cache size is limited though
+ // since the number of possible values can be huge from a PS function.
+ var cache = new FunctionCache();
+ return function constructPostScriptFromIRResult(args) {
+ var initialStack = [];
+ for (var i = 0, ii = (domain.length / 2); i < ii; ++i) {
+ initialStack.push(args[i]);
+ }
+
+ var key = initialStack.join('_');
+ if (cache.has(key))
+ return cache.get(key);
+
+ var stack = evaluator.execute(initialStack);
+ var transformed = [];
+ for (i = numOutputs - 1; i >= 0; --i) {
+ var out = stack.pop();
+ var rangeIndex = 2 * i;
+ if (out < range[rangeIndex])
+ out = range[rangeIndex];
+ else if (out > range[rangeIndex + 1])
+ out = range[rangeIndex + 1];
+ transformed[i] = out;
+ }
+ cache.set(key, transformed);
+ return transformed;
+ };
+ }
+ };
+})();
+
+var FunctionCache = (function FunctionCacheClosure() {
+ // Of 10 PDF's with type4 functions the maxium number of distinct values seen
+ // was 256. This still may need some tweaking in the future though.
+ var MAX_CACHE_SIZE = 1024;
+ function FunctionCache() {
+ this.cache = {};
+ this.total = 0;
+ }
+ FunctionCache.prototype = {
+ has: function FunctionCache_has(key) {
+ return key in this.cache;
+ },
+ get: function FunctionCache_get(key) {
+ return this.cache[key];
+ },
+ set: function FunctionCache_set(key, value) {
+ if (this.total < MAX_CACHE_SIZE) {
+ this.cache[key] = value;
+ this.total++;
+ }
+ }
+ };
+ return FunctionCache;
+})();
+
+var PostScriptStack = (function PostScriptStackClosure() {
+ var MAX_STACK_SIZE = 100;
+ function PostScriptStack(initialStack) {
+ this.stack = initialStack || [];
+ }
+
+ PostScriptStack.prototype = {
+ push: function PostScriptStack_push(value) {
+ if (this.stack.length >= MAX_STACK_SIZE)
+ error('PostScript function stack overflow.');
+ this.stack.push(value);
+ },
+ pop: function PostScriptStack_pop() {
+ if (this.stack.length <= 0)
+ error('PostScript function stack underflow.');
+ return this.stack.pop();
+ },
+ copy: function PostScriptStack_copy(n) {
+ if (this.stack.length + n >= MAX_STACK_SIZE)
+ error('PostScript function stack overflow.');
+ var stack = this.stack;
+ for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++)
+ stack.push(stack[i]);
+ },
+ index: function PostScriptStack_index(n) {
+ this.push(this.stack[this.stack.length - n - 1]);
+ },
+ // rotate the last n stack elements p times
+ roll: function PostScriptStack_roll(n, p) {
+ var stack = this.stack;
+ var l = stack.length - n;
+ var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t;
+ for (i = l, j = r; i < j; i++, j--) {
+ t = stack[i]; stack[i] = stack[j]; stack[j] = t;
+ }
+ for (i = l, j = c - 1; i < j; i++, j--) {
+ t = stack[i]; stack[i] = stack[j]; stack[j] = t;
+ }
+ for (i = c, j = r; i < j; i++, j--) {
+ t = stack[i]; stack[i] = stack[j]; stack[j] = t;
+ }
+ }
+ };
+ return PostScriptStack;
+})();
+var PostScriptEvaluator = (function PostScriptEvaluatorClosure() {
+ function PostScriptEvaluator(operators, operands) {
+ this.operators = operators;
+ this.operands = operands;
+ }
+ PostScriptEvaluator.prototype = {
+ execute: function PostScriptEvaluator_execute(initialStack) {
+ var stack = new PostScriptStack(initialStack);
+ var counter = 0;
+ var operators = this.operators;
+ var length = operators.length;
+ var operator, a, b;
+ while (counter < length) {
+ operator = operators[counter++];
+ if (typeof operator == 'number') {
+ // Operator is really an operand and should be pushed to the stack.
+ stack.push(operator);
+ continue;
+ }
+ switch (operator) {
+ // non standard ps operators
+ case 'jz': // jump if false
+ b = stack.pop();
+ a = stack.pop();
+ if (!a)
+ counter = b;
+ break;
+ case 'j': // jump
+ a = stack.pop();
+ counter = a;
+ break;
+
+ // all ps operators in alphabetical order (excluding if/ifelse)
+ case 'abs':
+ a = stack.pop();
+ stack.push(Math.abs(a));
+ break;
+ case 'add':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a + b);
+ break;
+ case 'and':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b))
+ stack.push(a && b);
+ else
+ stack.push(a & b);
+ break;
+ case 'atan':
+ a = stack.pop();
+ stack.push(Math.atan(a));
+ break;
+ case 'bitshift':
+ b = stack.pop();
+ a = stack.pop();
+ if (a > 0)
+ stack.push(a << b);
+ else
+ stack.push(a >> b);
+ break;
+ case 'ceiling':
+ a = stack.pop();
+ stack.push(Math.ceil(a));
+ break;
+ case 'copy':
+ a = stack.pop();
+ stack.copy(a);
+ break;
+ case 'cos':
+ a = stack.pop();
+ stack.push(Math.cos(a));
+ break;
+ case 'cvi':
+ a = stack.pop() | 0;
+ stack.push(a);
+ break;
+ case 'cvr':
+ // noop
+ break;
+ case 'div':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a / b);
+ break;
+ case 'dup':
+ stack.copy(1);
+ break;
+ case 'eq':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a == b);
+ break;
+ case 'exch':
+ stack.roll(2, 1);
+ break;
+ case 'exp':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(Math.pow(a, b));
+ break;
+ case 'false':
+ stack.push(false);
+ break;
+ case 'floor':
+ a = stack.pop();
+ stack.push(Math.floor(a));
+ break;
+ case 'ge':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a >= b);
+ break;
+ case 'gt':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a > b);
+ break;
+ case 'idiv':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push((a / b) | 0);
+ break;
+ case 'index':
+ a = stack.pop();
+ stack.index(a);
+ break;
+ case 'le':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a <= b);
+ break;
+ case 'ln':
+ a = stack.pop();
+ stack.push(Math.log(a));
+ break;
+ case 'log':
+ a = stack.pop();
+ stack.push(Math.log(a) / Math.LN10);
+ break;
+ case 'lt':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a < b);
+ break;
+ case 'mod':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a % b);
+ break;
+ case 'mul':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a * b);
+ break;
+ case 'ne':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a != b);
+ break;
+ case 'neg':
+ a = stack.pop();
+ stack.push(-b);
+ break;
+ case 'not':
+ a = stack.pop();
+ if (isBool(a) && isBool(b))
+ stack.push(a && b);
+ else
+ stack.push(a & b);
+ break;
+ case 'or':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b))
+ stack.push(a || b);
+ else
+ stack.push(a | b);
+ break;
+ case 'pop':
+ stack.pop();
+ break;
+ case 'roll':
+ b = stack.pop();
+ a = stack.pop();
+ stack.roll(a, b);
+ break;
+ case 'round':
+ a = stack.pop();
+ stack.push(Math.round(a));
+ break;
+ case 'sin':
+ a = stack.pop();
+ stack.push(Math.sin(a));
+ break;
+ case 'sqrt':
+ a = stack.pop();
+ stack.push(Math.sqrt(a));
+ break;
+ case 'sub':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a - b);
+ break;
+ case 'true':
+ stack.push(true);
+ break;
+ case 'truncate':
+ a = stack.pop();
+ a = a < 0 ? Math.ceil(a) : Math.floor(a);
+ stack.push(a);
+ break;
+ case 'xor':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b))
+ stack.push(a != b);
+ else
+ stack.push(a ^ b);
+ break;
+ default:
+ error('Unknown operator ' + operator);
+ break;
+ }
+ }
+ return stack.stack;
+ }
+ };
+ return PostScriptEvaluator;
+})();
+
+var PostScriptParser = (function PostScriptParserClosure() {
+ function PostScriptParser(lexer) {
+ this.lexer = lexer;
+ this.operators = [];
+ this.token;
+ this.prev;
+ }
+ PostScriptParser.prototype = {
+ nextToken: function PostScriptParser_nextToken() {
+ this.prev = this.token;
+ this.token = this.lexer.getToken();
+ },
+ accept: function PostScriptParser_accept(type) {
+ if (this.token.type == type) {
+ this.nextToken();
+ return true;
+ }
+ return false;
+ },
+ expect: function PostScriptParser_expect(type) {
+ if (this.accept(type))
+ return true;
+ error('Unexpected symbol: found ' + this.token.type + ' expected ' +
+ type + '.');
+ },
+ parse: function PostScriptParser_parse() {
+ this.nextToken();
+ this.expect(PostScriptTokenTypes.LBRACE);
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ return this.operators;
+ },
+ parseBlock: function PostScriptParser_parseBlock() {
+ while (true) {
+ if (this.accept(PostScriptTokenTypes.NUMBER)) {
+ this.operators.push(this.prev.value);
+ } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
+ this.operators.push(this.prev.value);
+ } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+ this.parseCondition();
+ } else {
+ return;
+ }
+ }
+ },
+ parseCondition: function PostScriptParser_parseCondition() {
+ // Add two place holders that will be updated later
+ var conditionLocation = this.operators.length;
+ this.operators.push(null, null);
+
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ if (this.accept(PostScriptTokenTypes.IF)) {
+ // The true block is right after the 'if' so it just falls through on
+ // true else it jumps and skips the true block.
+ this.operators[conditionLocation] = this.operators.length;
+ this.operators[conditionLocation + 1] = 'jz';
+ } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+ var jumpLocation = this.operators.length;
+ this.operators.push(null, null);
+ var endOfTrue = this.operators.length;
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ this.expect(PostScriptTokenTypes.IFELSE);
+ // The jump is added at the end of the true block to skip the false
+ // block.
+ this.operators[jumpLocation] = this.operators.length;
+ this.operators[jumpLocation + 1] = 'j';
+
+ this.operators[conditionLocation] = endOfTrue;
+ this.operators[conditionLocation + 1] = 'jz';
+ } else {
+ error('PS Function: error parsing conditional.');
+ }
+ }
+ };
+ return PostScriptParser;
+})();
+
+var PostScriptTokenTypes = {
+ LBRACE: 0,
+ RBRACE: 1,
+ NUMBER: 2,
+ OPERATOR: 3,
+ IF: 4,
+ IFELSE: 5
+};
+
+var PostScriptToken = (function PostScriptTokenClosure() {
+ function PostScriptToken(type, value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ var opCache = {};
+
+ PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
+ var opValue = opCache[op];
+ if (opValue)
+ return opValue;
+
+ return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
+ };
+
+ PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
+ '{');
+ PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
+ '}');
+ PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
+ PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
+ 'IFELSE');
+ return PostScriptToken;
+})();
+
+var PostScriptLexer = (function PostScriptLexerClosure() {
+ function PostScriptLexer(stream) {
+ this.stream = stream;
+ }
+ PostScriptLexer.prototype = {
+ getToken: function PostScriptLexer_getToken() {
+ var s = '';
+ var ch;
+ var comment = false;
+ var stream = this.stream;
+
+ // skip comments
+ while (true) {
+ if (!(ch = stream.getChar()))
+ return EOF;
+
+ if (comment) {
+ if (ch == '\x0a' || ch == '\x0d')
+ comment = false;
+ } else if (ch == '%') {
+ comment = true;
+ } else if (!Lexer.isSpace(ch)) {
+ break;
+ }
+ }
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case '+': case '-': case '.':
+ return new PostScriptToken(PostScriptTokenTypes.NUMBER,
+ this.getNumber(ch));
+ case '{':
+ return PostScriptToken.LBRACE;
+ case '}':
+ return PostScriptToken.RBRACE;
+ }
+ // operator
+ var str = ch.toLowerCase();
+ while (true) {
+ ch = stream.lookChar();
+ if (ch === null)
+ break;
+ ch = ch.toLowerCase();
+ if (ch >= 'a' && ch <= 'z')
+ str += ch;
+ else
+ break;
+ stream.skip();
+ }
+ switch (str) {
+ case 'if':
+ return PostScriptToken.IF;
+ case 'ifelse':
+ return PostScriptToken.IFELSE;
+ default:
+ return PostScriptToken.getOperator(str);
+ }
+ },
+ getNumber: function PostScriptLexer_getNumber(ch) {
+ var str = ch;
+ var stream = this.stream;
+ while (true) {
+ ch = stream.lookChar();
+ if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')
+ str += ch;
+ else
+ break;
+ stream.skip();
+ }
+ var value = parseFloat(str);
+ if (isNaN(value))
+ error('Invalid floating point number: ' + value);
+ return value;
+ }
+ };
+ return PostScriptLexer;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var ISOAdobeCharset = [
+ '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar',
+ 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
+ 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero',
+ 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+ 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
+ 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
+ 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent',
+ 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
+ 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
+ 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl',
+ 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
+ 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
+ 'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde',
+ 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla',
+ 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine',
+ 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash',
+ 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
+ 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter',
+ 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior',
+ 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
+ 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde',
+ 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute',
+ 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
+ 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex',
+ 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute',
+ 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
+ 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
+ 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
+ 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis',
+ 'ugrave', 'yacute', 'ydieresis', 'zcaron'
+];
+
+var ExpertCharset = [
+ '.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle',
+ 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior',
+ 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma',
+ 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle',
+ 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle',
+ 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle',
+ 'colon', 'semicolon', 'commasuperior', 'threequartersemdash',
+ 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior',
+ 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
+ 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
+ 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
+ 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
+ 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
+ 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall',
+ 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall',
+ 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary',
+ 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle',
+ 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
+ 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
+ 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall',
+ 'Cedillasmall', 'onequarter', 'onehalf', 'threequarters',
+ 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths',
+ 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
+ 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
+ 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
+ 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
+ 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
+ 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
+ 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall',
+ 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
+ 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall',
+ 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
+ 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall',
+ 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
+ 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
+ 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall',
+ 'Ydieresissmall'
+];
+
+var ExpertSubsetCharset = [
+ '.notdef', 'space', 'dollaroldstyle', 'dollarsuperior',
+ 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
+ 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction',
+ 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
+ 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle',
+ 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior',
+ 'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior',
+ 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
+ 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
+ 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
+ 'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted',
+ 'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter',
+ 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths',
+ 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
+ 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
+ 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
+ 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
+ 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
+ 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
+ 'periodinferior', 'commainferior'
+];
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var CIDToUnicodeMaps = {
+ 'Adobe-Japan1': [[32, 160], {f: 12, c: 33}, [45, 8209], {f: 46, c: 46}, 165,
+ {f: 2, c: 93}, [95, 818], [96, 768], {f: 27, c: 97}, 166, 125, [732, 771],
+ [700, 8217], 92, [699, 8216], 124, [126, 8764], {f: 3, c: 161}, 8260, 402,
+ 0, 164, 8220, 171, {f: 2, c: 8249}, {f: 2, c: 64257}, [8210, 8211], 0, 0,
+ [183, 8729], 0, 8226, 8218, 8222, 8221, 187, 0, 0, 191, {f: 2, c: 769},
+ [175, 772], {f: 3, c: 774}, 778, [184, 807], 779, 808, 780, [822, 8212],
+ 198, 170, 321, 216, 338, 186, 230, 305, 322, 248, 339, 223, 173, 169, 172,
+ 174, 0, 0, {f: 2, c: 178}, 181, 185, {f: 3, c: 188}, {f: 6, c: 192},
+ {f: 16, c: 199}, 0, {f: 6, c: 217}, {f: 6, c: 224}, {f: 16, c: 231}, 0,
+ {f: 7, c: 249}, 352, 376, 381, [773, 8254], 353, 8482, 382, 0, 8194,
+ {s: 91}, 65512, {s: 3}, {f: 63, c: 65377}, {s: 243}, [8195, 12288],
+ {f: 2, c: 12289}, 65292, 65294, 12539, {f: 2, c: 65306}, 65311, 65281,
+ {f: 2, c: 12443}, 180, 65344, 168, 65342, 65507, 65343, {f: 2, c: 12541},
+ {f: 2, c: 12445}, 12291, 20189, {f: 3, c: 12293}, 12540, 8213, 8208, 65295,
+ 65340, [12316, 65374], 8214, 65372, 8230, 8229, {s: 4}, {f: 2, c: 65288},
+ {f: 2, c: 12308}, 65339, 65341, 65371, 65373, {f: 10, c: 12296}, 65291,
+ [8722, 65293], 177, 215, 247, 65309, 8800, 65308, 65310, {f: 2, c: 8806},
+ 8734, 8756, 9794, 9792, 176, {f: 2, c: 8242}, 8451, 65509, 65284,
+ {f: 2, c: 65504}, 65285, 65283, 65286, 65290, 65312, 167, 9734, 9733, 9675,
+ 9679, 9678, 9671, 9670, 9633, 9632, 9651, 9650, 9661, 9660, 8251, 12306,
+ 8594, {f: 2, c: 8592}, 8595, 12307, 8712, 8715, {f: 2, c: 8838},
+ {f: 2, c: 8834}, 8746, 8745, {f: 2, c: 8743}, 65506, 8658, 8660, 8704,
+ 8707, 8736, 8869, 8978, 8706, 8711, 8801, 8786, {f: 2, c: 8810}, 8730,
+ 8765, 8733, 8757, {f: 2, c: 8747}, 8491, 8240, 9839, 9837, 9834,
+ {f: 2, c: 8224}, 182, 9711, {f: 10, c: 65296}, {f: 26, c: 65313},
+ {f: 26, c: 65345}, {f: 83, c: 12353}, {f: 86, c: 12449}, {f: 17, c: 913},
+ {f: 7, c: 931}, {f: 17, c: 945}, {f: 7, c: 963}, {f: 6, c: 1040}, 1025,
+ {f: 32, c: 1046}, 1105, {f: 26, c: 1078}, 20124, 21782, 23043, 38463,
+ 21696, 24859, 25384, 23030, 36898, 33909, 33564, 31312, 24746, 25569,
+ 28197, 26093, 33894, 33446, 39925, 26771, 22311, 26017, 25201, 23451,
+ 22992, 34427, 39156, 32098, 32190, 39822, 25110, 31903, 34999, 23433,
+ 24245, 25353, 26263, 26696, 38343, 38797, 26447, 20197, 20234, 20301,
+ 20381, 20553, 22258, 22839, 22996, 23041, 23561, 24799, 24847, 24944,
+ 26131, 26885, 28858, 30031, 30064, 31227, 32173, 32239, 32963, 33806,
+ [12176, 34915], 35586, 36949, 36986, 21307, 20117, 20133, 22495, 32946,
+ 37057, 30959, [12032, 19968], 22769, 28322, 36920, 31282, 33576, 33419,
+ 39983, 20801, 21360, 21693, 21729, 22240, 23035, 24341, 39154, 28139,
+ 32996, 34093, 38498, 38512, 38560, 38907, 21515, 21491, 23431, 28879,
+ [12155, 32701], 36802, [12204, 38632], 21359, 40284, 31418, 19985, 30867,
+ [12165, 33276], 28198, 22040, 21764, 27421, 34074, 39995, 23013, 21417,
+ 28006, [12128, 29916], 38287, 22082, 20113, 36939, 38642, 33615, 39180,
+ 21473, 21942, 23344, 24433, 26144, 26355, 26628, 27704, 27891, 27945,
+ 29787, 30408, 31310, 38964, 33521, 34907, 35424, 37613, 28082, 30123,
+ 30410, 39365, 24742, 35585, 36234, 38322, 27022, 21421, 20870, 22290,
+ 22576, 22852, 23476, 24310, 24616, 25513, 25588, 27839, 28436, 28814,
+ 28948, 29017, 29141, 29503, 32257, 33398, 33489, 34199, 36960, 37467,
+ 40219, 22633, 26044, 27738, 29989, 20985, 22830, 22885, 24448, 24540,
+ 25276, 26106, 27178, 27431, 27572, 29579, 32705, 35158, 40236, 40206,
+ [12009, 40644], 23713, 27798, 33659, 20740, 23627, 25014, 33222, 26742,
+ 29281, [12036, 20057], 20474, 21368, 24681, 28201, 31311, [12211, 38899],
+ 19979, 21270, 20206, 20309, 20285, 20385, 20339, 21152, 21487, 22025,
+ 22799, 23233, 23478, 23521, 31185, 26247, 26524, 26550, 27468, 27827,
+ [12117, 28779], 29634, 31117, [12146, 31166], 31292, 31623, 33457, 33499,
+ 33540, 33655, 33775, 33747, 34662, 35506, 22057, 36008, 36838, 36942,
+ 38686, 34442, 20420, 23784, 25105, [12123, 29273], 30011, 33253, 33469,
+ 34558, 36032, 38597, 39187, 39381, 20171, 20250, 35299, 22238, 22602,
+ 22730, 24315, 24555, 24618, 24724, 24674, 25040, 25106, 25296, 25913,
+ 39745, 26214, 26800, 28023, 28784, 30028, 30342, 32117, 33445, 34809,
+ 38283, 38542, [12185, 35997], 20977, 21182, 22806, 21683, 23475, 23830,
+ 24936, 27010, 28079, 30861, 33995, 34903, 35442, 37799, 39608, 28012,
+ 39336, 34521, 22435, 26623, 34510, 37390, 21123, 22151, 21508, 24275,
+ 25313, 25785, 26684, 26680, 27579, 29554, 30906, 31339, 35226,
+ [12179, 35282], 36203, 36611, 37101, 38307, 38548, [12208, 38761], 23398,
+ 23731, 27005, {f: 2, c: 38989}, 25499, 31520, 27179, 27263, 26806, 39949,
+ 28511, 21106, 21917, 24688, 25324, 27963, 28167, 28369, 33883, 35088,
+ 36676, 19988, 39993, 21494, 26907, 27194, 38788, 26666, 20828, 31427,
+ 33970, 37340, 37772, 22107, 40232, 26658, 33541, 33841, 31909, 21000,
+ 33477, [12129, 29926], 20094, 20355, 20896, 23506, 21002, 21208, 21223,
+ 24059, 21914, 22570, 23014, 23436, 23448, 23515, [12082, 24178], 24185,
+ 24739, 24863, 24931, 25022, 25563, 25954, 26577, 26707, 26874, 27454,
+ 27475, 27735, 28450, 28567, 28485, 29872, [12130, 29976], 30435, 30475,
+ 31487, 31649, 31777, 32233, [12152, 32566], 32752, 32925, 33382, 33694,
+ 35251, 35532, 36011, 36996, 37969, 38291, 38289, 38306, 38501, 38867,
+ 39208, 33304, 20024, 21547, 23736, 24012, 29609, 30284, 30524, 23721,
+ 32747, 36107, 38593, 38929, 38996, 39000, 20225, 20238, 21361, 21916,
+ 22120, 22522, 22855, 23305, 23492, 23696, 24076, 24190, 24524, 25582,
+ 26426, 26071, 26082, 26399, 26827, 26820, 27231, 24112, 27589, 27671,
+ 27773, 30079, 31048, 23395, 31232, 32000, 24509, 35215, 35352, 36020,
+ 36215, 36556, 36637, 39138, 39438, [12004, 12225, 39740], [12018, 20096],
+ 20605, 20736, 22931, 23452, 25135, 25216, 25836, 27450, 29344, 30097,
+ 31047, 32681, 34811, 35516, 35696, 25516, 33738, 38816, 21513, 21507,
+ 21931, 26708, 27224, 35440, 30759, 26485, [12233, 40653], 21364, 23458,
+ 33050, 34384, 36870, 19992, 20037, 20167, 20241, 21450, 21560, 23470,
+ [12088, 24339], 24613, 25937, 26429, 27714, 27762, 27875, 28792, 29699,
+ 31350, 31406, 31496, 32026, 31998, 32102, 26087, [12124, 29275], 21435,
+ 23621, 24040, 25298, 25312, 25369, 28192, 34394, 35377, 36317, 37624,
+ 28417, 31142, [12226, 39770], 20136, {f: 2, c: 20139}, 20379, 20384, 20689,
+ 20807, 31478, 20849, 20982, 21332, 21281, 21375, 21483, 21932, 22659,
+ 23777, 24375, 24394, 24623, 24656, 24685, 25375, 25945, 27211, 27841,
+ 29378, 29421, 30703, 33016, 33029, 33288, 34126, 37111, 37857, 38911,
+ 39255, 39514, 20208, 20957, 23597, 26241, 26989, 23616, 26354, 26997,
+ [12127, 29577], 26704, 31873, 20677, 21220, 22343, [12081, 24062], 37670,
+ [12100, 26020], 27427, 27453, 29748, 31105, 31165, 31563, 32202, 33465,
+ 33740, 34943, 35167, 35641, 36817, [12198, 37329], 21535, 37504, 20061,
+ 20534, 21477, 21306, 29399, 29590, 30697, 33510, 36527, 39366, 39368,
+ 39378, 20855, 24858, 34398, 21936, 31354, 20598, 23507, 36935, 38533,
+ 20018, 27355, 37351, 23633, 23624, 25496, 31391, 27795, 38772, 36705,
+ 31402, 29066, 38536, 31874, 26647, 32368, 26705, 37740, 21234, 21531,
+ 34219, 35347, 32676, 36557, 37089, 21350, 34952, 31041, 20418, 20670,
+ 21009, 20804, 21843, 22317, 29674, 22411, 22865, 24418, 24452, 24693,
+ 24950, 24935, 25001, 25522, 25658, 25964, 26223, 26690, 28179, 30054,
+ 31293, 31995, 32076, 32153, 32331, 32619, 33550, 33610, 34509, 35336,
+ 35427, 35686, 36605, 38938, 40335, 33464, 36814, 39912, 21127, 25119,
+ 25731, 28608, 38553, 26689, 20625, [12107, 27424], 27770, 28500,
+ [12147, 31348], 32080, [12174, 34880], 35363, [12105, 26376], 20214, 20537,
+ 20518, 20581, 20860, 21048, 21091, 21927, 22287, 22533, 23244, 24314,
+ 25010, 25080, 25331, 25458, 26908, 27177, 29309, [12125, 29356], 29486,
+ 30740, 30831, 32121, 30476, 32937, [12178, 35211], 35609, 36066, 36562,
+ 36963, 37749, 38522, 38997, 39443, 40568, 20803, 21407, 21427, 24187,
+ 24358, 28187, 28304, [12126, 29572], 29694, 32067, 33335, [12180, 35328],
+ 35578, 38480, 20046, 20491, 21476, 21628, 22266, 22993, 23396,
+ [12080, 24049], 24235, 24359, [12094, 25144], 25925, 26543, 28246, 29392,
+ 31946, 34996, 32929, 32993, 33776, [11969, 34382], 35463, 36328, 37431,
+ 38599, 39015, [12238, 40723], 20116, 20114, 20237, 21320, 21577, 21566,
+ 23087, 24460, 24481, 24735, 26791, 27278, 29786, 30849, 35486, 35492,
+ 35703, 37264, 20062, 39881, 20132, 20348, 20399, 20505, 20502, 20809,
+ 20844, 21151, 21177, 21246, 21402, [12061, 21475], 21521, 21518, 21897,
+ 22353, 22434, 22909, 23380, 23389, 23439, [12079, 24037], 24039, 24055,
+ 24184, 24195, 24218, 24247, 24344, 24658, 24908, 25239, 25304, 25511,
+ 25915, 26114, 26179, 26356, 26477, 26657, 26775, 27083, 27743, 27946,
+ 28009, 28207, 28317, 30002, 30343, 30828, 31295, 31968, 32005, 32024,
+ 32094, 32177, 32789, 32771, 32943, 32945, 33108, 33167, 33322, 33618,
+ [12175, 34892], 34913, 35611, 36002, 36092, 37066, 37237, 37489, 30783,
+ 37628, 38308, 38477, 38917, [12217, 39321], [12220, 39640], 40251, 21083,
+ 21163, 21495, 21512, 22741, 25335, 28640, 35946, 36703, 40633, 20811,
+ 21051, 21578, 22269, 31296, 37239, 40288, [12234, 40658], 29508, 28425,
+ 33136, 29969, 24573, 24794, [12219, 39592], 29403, 36796, 27492, 38915,
+ 20170, 22256, 22372, 22718, 23130, 24680, 25031, 26127, 26118, 26681,
+ 26801, 28151, 30165, 32058, [12169, 33390], 39746, 20123, 20304, 21449,
+ 21766, 23919, 24038, 24046, 26619, 27801, 29811, 30722, 35408, 37782,
+ 35039, 22352, 24231, 25387, 20661, 20652, 20877, 26368, 21705, 22622,
+ 22971, 23472, 24425, 25165, 25505, 26685, 27507, 28168, 28797, 37319,
+ 29312, 30741, 30758, 31085, 25998, 32048, 33756, 35009, 36617, 38555,
+ 21092, 22312, 26448, 32618, 36001, 20916, 22338, 38442, 22586, 27018,
+ 32948, 21682, 23822, 22524, 30869, 40442, 20316, 21066, 21643, 25662,
+ 26152, 26388, 26613, 31364, 31574, 32034, 37679, 26716, 39853, 31545,
+ 21273, 20874, 21047, 23519, 25334, 25774, 25830, 26413, 27578, 34217,
+ 38609, 30352, 39894, 25420, 37638, 39851, [12139, 30399], 26194, 19977,
+ 20632, 21442, [12077, 23665], 24808, 25746, 25955, 26719, 29158, 29642,
+ 29987, 31639, 32386, 34453, 35715, 36059, 37240, 39184, 26028, 26283,
+ 27531, 20181, 20180, 20282, 20351, 21050, 21496, 21490, 21987, 22235,
+ [12064, 22763], 22987, 22985, 23039, [12070, 23376], 23629, 24066, 24107,
+ 24535, 24605, 25351, [12096, 25903], 23388, 26031, 26045, 26088, 26525,
+ [12108, 27490], 27515, [12114, 27663], 29509, 31049, 31169, [12151, 31992],
+ 32025, 32043, 32930, 33026, [12164, 33267], 35222, 35422, 35433, 35430,
+ 35468, 35566, 36039, 36060, 38604, 39164, [12013, 27503], 20107, 20284,
+ 20365, 20816, 23383, 23546, 24904, 25345, 26178, 27425, 28363, 27835,
+ 29246, 29885, 30164, 30913, [12144, 31034], [12157, 32780], [12159, 32819],
+ [12163, 33258], 33940, 36766, 27728, [12229, 40575], 24335, 35672, 40235,
+ 31482, 36600, 23437, 38635, 19971, 21489, 22519, 22833, 23241, 23460,
+ 24713, 28287, 28422, 30142, 36074, 23455, 34048, 31712, 20594, 26612,
+ 33437, 23649, 34122, 32286, 33294, 20889, 23556, 25448, 36198, 26012,
+ 29038, 31038, 32023, 32773, 35613, [12190, 36554], 36974, 34503, 37034,
+ 20511, 21242, 23610, 26451, 28796, 29237, 37196, 37320, 37675, 33509,
+ 23490, 24369, 24825, 20027, 21462, 23432, [12095, 25163], 26417, 27530,
+ 29417, 29664, 31278, 33131, 36259, 37202, [12216, 39318], 20754, 21463,
+ 21610, 23551, 25480, 27193, 32172, 38656, 22234, 21454, 21608, 23447,
+ 23601, 24030, 20462, 24833, 25342, 27954, 31168, 31179, 32066, 32333,
+ 32722, 33261, [12168, 33311], 33936, 34886, 35186, 35728, 36468, 36655,
+ 36913, 37195, 37228, 38598, 37276, 20160, 20303, 20805, [12055, 21313],
+ 24467, 25102, 26580, 27713, 28171, 29539, 32294, 37325, 37507, 21460,
+ 22809, 23487, 28113, 31069, 32302, 31899, 22654, 29087, 20986, 34899,
+ 36848, 20426, 23803, 26149, 30636, 31459, 33308, 39423, 20934, 24490,
+ 26092, 26991, 27529, 28147, 28310, 28516, 30462, 32020, 24033, 36981,
+ 37255, 38918, 20966, 21021, 25152, 26257, 26329, 28186, 24246, 32210,
+ 32626, 26360, 34223, 34295, 35576, 21161, 21465, [12069, 22899], 24207,
+ 24464, 24661, 37604, 38500, 20663, 20767, 21213, 21280, 21319, 21484,
+ 21736, 21830, 21809, 22039, 22888, 22974, 23100, 23477, 23558,
+ [12073, 23567], 23569, 23578, 24196, 24202, 24288, 24432, 25215, 25220,
+ 25307, 25484, 25463, 26119, 26124, 26157, 26230, 26494, 26786, 27167,
+ 27189, 27836, 28040, 28169, 28248, 28988, 28966, 29031, 30151, 30465,
+ 30813, 30977, 31077, 31216, 31456, 31505, 31911, 32057, 32918, 33750,
+ 33931, 34121, 34909, 35059, 35359, 35388, 35412, 35443, 35937, 36062,
+ 37284, 37478, 37758, 37912, 38556, 38808, 19978, 19976, 19998, 20055,
+ 20887, 21104, 22478, 22580, 22732, 23330, 24120, 24773, 25854, 26465,
+ 26454, 27972, 29366, 30067, 31331, 33976, 35698, 37304, 37664, 22065,
+ 22516, 39166, 25325, 26893, 27542, 29165, 32340, 32887, [12170, 33394],
+ 35302, [12215, 39135], 34645, 36785, 23611, 20280, 20449, 20405, 21767,
+ 23072, 23517, 23529, [12092, 24515], 24910, 25391, 26032, 26187, 26862,
+ 27035, 28024, 28145, 30003, 30137, 30495, 31070, 31206, 32051,
+ [12162, 33251], 33455, 34218, 35242, 35386, [12189, 36523], [12191, 36763],
+ 36914, 37341, 38663, [12040, 20154], 20161, 20995, 22645, 22764, 23563,
+ 29978, 23613, 33102, 35338, 36805, 38499, 38765, 31525, 35535, 38920,
+ 37218, 22259, 21416, 36887, 21561, 22402, 24101, 25512, [12116, 27700],
+ 28810, 30561, 31883, 32736, 34928, 36930, 37204, 37648, 37656, 38543,
+ 29790, 39620, 23815, 23913, 25968, 26530, 36264, 38619, 25454, 26441,
+ 26905, 33733, 38935, 38592, 35070, 28548, 25722, [12072, 23544], 19990,
+ 28716, 30045, 26159, 20932, 21046, 21218, 22995, 24449, 24615, 25104,
+ 25919, 25972, 26143, 26228, 26866, 26646, 27491, 28165, 29298,
+ [12131, 29983], 30427, 31934, 32854, 22768, 35069, [11972, 35199], 35488,
+ 35475, 35531, 36893, 37266, [11992, 38738], 38745, [12011, 25993], 31246,
+ 33030, 38587, 24109, 24796, 25114, 26021, 26132, 26512, [12143, 30707],
+ 31309, 31821, 32318, 33034, 36012, [12186, 36196], 36321, 36447, 30889,
+ 20999, 25305, 25509, 25666, 25240, 35373, 31363, 31680, 35500, 38634,
+ 32118, [12166, 33292], 34633, 20185, 20808, 21315, 21344, 23459, 23554,
+ 23574, 24029, 25126, 25159, 25776, 26643, 26676, 27849, 27973, 27927,
+ 26579, 28508, 29006, 29053, 26059, 31359, 31661, 32218, 32330, 32680,
+ 33146, [12167, 33307], 33337, 34214, 35438, 36046, 36341, 36984, 36983,
+ 37549, 37521, 38275, 39854, 21069, 21892, 28472, 28982, 20840, 31109,
+ 32341, 33203, 31950, 22092, 22609, 23720, 25514, 26366, 26365, 26970,
+ 29401, 30095, 30094, 30990, 31062, 31199, 31895, 32032, 32068, 34311,
+ 35380, 38459, 36961, [12239, 40736], 20711, 21109, 21452, 21474, 20489,
+ 21930, 22766, 22863, 29245, 23435, 23652, 21277, 24803, 24819, 25436,
+ 25475, 25407, 25531, 25805, 26089, 26361, 24035, 27085, 27133, 28437,
+ 29157, 20105, 30185, 30456, 31379, 31967, 32207, 32156, 32865, 33609,
+ 33624, 33900, 33980, 34299, 35013, [12187, 36208], 36865, 36973, 37783,
+ 38684, 39442, 20687, 22679, 24974, 33235, 34101, 36104, 36896, 20419,
+ 20596, 21063, 21363, 24687, 25417, 26463, 28204, [12188, 36275], 36895,
+ 20439, 23646, 36042, 26063, 32154, 21330, 34966, 20854, 25539, 23384,
+ 23403, 23562, 25613, 26449, 36956, 20182, 22810, 22826, 27760, 35409,
+ 21822, 22549, 22949, 24816, 25171, 26561, 33333, 26965, 38464, 39364,
+ 39464, 20307, 22534, 23550, 32784, 23729, 24111, 24453, 24608, 24907,
+ 25140, 26367, 27888, 28382, 32974, 33151, 33492, 34955, 36024, 36864,
+ 36910, 38538, 40667, 39899, 20195, 21488, [12068, 22823], 31532, 37261,
+ 38988, 40441, 28381, 28711, 21331, 21828, 23429, 25176, 25246, 25299,
+ 27810, 28655, 29730, 35351, 37944, 28609, 35582, 33592, 20967, 34552,
+ 21482, 21481, 20294, 36948, [12192, 36784], 22890, 33073, 24061, 31466,
+ 36799, 26842, [12181, 35895], 29432, 40008, 27197, 35504, 20025, 21336,
+ 22022, 22374, 25285, 25506, 26086, 27470, 28129, 28251, 28845, 30701,
+ 31471, 31658, 32187, 32829, 32966, 34507, 35477, 37723, 22243, 22727,
+ 24382, 26029, 26262, 27264, 27573, 30007, 35527, 20516, 30693, 22320,
+ 24347, 24677, 26234, 27744, 30196, 31258, 32622, 33268, 34584, 36933,
+ 39347, 31689, 30044, [12149, 31481], 31569, 33988, 36880, 31209, 31378,
+ 33590, 23265, 30528, 20013, 20210, 23449, 24544, 25277, 26172, 26609,
+ 27880, [12173, 34411], 34935, 35387, 37198, 37619, 39376, 27159, 28710,
+ 29482, 33511, 33879, 36015, 19969, 20806, 20939, 21899, 23541, 24086,
+ 24115, 24193, 24340, 24373, 24427, 24500, 25074, 25361, 26274, 26397,
+ 28526, 29266, 30010, 30522, 32884, 33081, 33144, 34678, 35519, 35548,
+ 36229, 36339, 37530, [11985, 12199, 38263], 38914, [12227, 40165], 21189,
+ 25431, 30452, 26389, 27784, 29645, 36035, 37806, 38515, 27941, 22684,
+ 26894, 27084, 36861, 37786, 30171, 36890, 22618, 26626, 25524, 27131,
+ 20291, 28460, 26584, 36795, 34086, 32180, 37716, 26943, 28528, 22378,
+ 22775, 23340, 32044, [12118, 29226], 21514, 37347, 40372, 20141, 20302,
+ 20572, 20597, 21059, 35998, 21576, 22564, 23450, 24093, 24213, 24237,
+ 24311, 24351, 24716, 25269, 25402, 25552, 26799, 27712, 30855, 31118,
+ 31243, 32224, 33351, 35330, 35558, 36420, 36883, 37048, 37165, 37336,
+ [12237, 40718], 27877, 25688, 25826, 25973, 28404, 30340, 31515, 36969,
+ 37841, 28346, 21746, 24505, 25764, 36685, 36845, 37444, 20856, 22635,
+ 22825, 23637, 24215, 28155, 32399, 29980, 36028, 36578, 39003, 28857,
+ 20253, 27583, 28593, [12133, 30000], 38651, 20814, 21520, 22581, 22615,
+ 22956, 23648, 24466, [12099, 26007], 26460, 28193, 30331, 33759, 36077,
+ 36884, 37117, 37709, 30757, 30778, 21162, 24230, [12063, 22303], 22900,
+ 24594, 20498, 20826, 20908, 20941, [12049, 20992], 21776, 22612, 22616,
+ 22871, 23445, 23798, 23947, 24764, 25237, 25645, 26481, 26691, 26812,
+ 26847, 30423, 28120, 28271, 28059, 28783, 29128, 24403, 30168, 31095,
+ 31561, 31572, 31570, 31958, 32113, 21040, 33891, 34153, 34276, 35342,
+ 35588, [12182, 35910], 36367, 36867, 36879, 37913, 38518, 38957, 39472,
+ 38360, 20685, 21205, 21516, 22530, 23566, 24999, 25758, 27934, 30643,
+ 31461, 33012, 33796, 36947, 37509, 23776, 40199, 21311, 24471, 24499,
+ 28060, 29305, 30563, 31167, 31716, 27602, 29420, 35501, 26627, 27233,
+ 20984, 31361, 26932, 23626, 40182, 33515, 23493, [12195, 37193], 28702,
+ 22136, 23663, 24775, 25958, 27788, 35930, 36929, 38931, 21585, 26311,
+ 37389, 22856, 37027, 20869, 20045, 20970, 34201, 35598, 28760, 25466,
+ 37707, 26978, 39348, 32260, 30071, 21335, 26976, 36575, 38627, 27741,
+ [12038, 20108], 23612, 24336, 36841, 21250, 36049, [12161, 32905], 34425,
+ 24319, [12103, 26085], 20083, [12042, 20837], 22914, 23615, 38894, 20219,
+ 22922, 24525, 35469, 28641, 31152, 31074, 23527, 33905, 29483, 29105,
+ 24180, 24565, 25467, 25754, 29123, 31896, 20035, 24316, 20043, 22492,
+ 22178, 24745, 28611, 32013, 33021, 33075, 33215, 36786, 35223, 34468,
+ 24052, 25226, 25773, 35207, 26487, 27874, 27966, 29750, 30772, 23110,
+ 32629, 33453, [12218, 39340], 20467, 24259, 25309, 25490, 25943, 26479,
+ 30403, 29260, 32972, 32954, 36649, 37197, 20493, 22521, 23186, 26757,
+ 26995, 29028, 29437, 36023, 22770, 36064, 38506, 36889, 34687, 31204,
+ 30695, 33833, 20271, 21093, 21338, 25293, 26575, 27850, [12137, 30333],
+ 31636, 31893, 33334, 34180, 36843, 26333, 28448, 29190, 32283, 33707,
+ 39361, [12008, 40614], 20989, 31665, 30834, 31672, 32903, 31560, 27368,
+ 24161, 32908, 30033, 30048, [12043, 20843], 37474, 28300, 30330, 37271,
+ 39658, 20240, 32624, 25244, 31567, 38309, 40169, 22138, 22617, 34532,
+ 38588, 20276, 21028, 21322, 21453, 21467, 24070, 25644, 26001, 26495,
+ 27710, 27726, 29256, 29359, 29677, 30036, 32321, 33324, 34281, 36009,
+ 31684, [12196, 37318], 29033, 38930, 39151, 25405, 26217, 30058, 30436,
+ 30928, 34115, 34542, 21290, 21329, 21542, 22915, 24199, 24444, 24754,
+ 25161, 25209, 25259, 26000, [12112, 27604], 27852, 30130, [12138, 30382],
+ 30865, 31192, 32203, 32631, 32933, 34987, 35513, 36027, 36991,
+ [12206, 38750], [12214, 39131], 27147, 31800, 20633, 23614, 24494, 26503,
+ 27608, 29749, 30473, 32654, [12240, 40763], 26570, 31255, 21305,
+ [12134, 30091], 39661, 24422, 33181, 33777, 32920, 24380, 24517, 30050,
+ 31558, 36924, 26727, 23019, 23195, 32016, 30334, 35628, 20469, 24426,
+ 27161, 27703, 28418, 29922, 31080, 34920, 35413, 35961, 24287, 25551,
+ 30149, 31186, 33495, 37672, 37618, 33948, 34541, 39981, 21697, 24428,
+ 25996, 27996, 28693, 36007, 36051, 38971, 25935, 29942, 19981, 20184,
+ 22496, 22827, 23142, 23500, 20904, 24067, 24220, 24598, 25206, 25975,
+ 26023, 26222, 28014, [12119, 29238], 31526, 33104, 33178, 33433, 35676,
+ 36000, 36070, 36212, [12201, 38428], 38468, 20398, 25771, 27494, 33310,
+ 33889, 34154, 37096, 23553, 26963, [12213, 39080], 33914, 34135, 20239,
+ 21103, 24489, 24133, 26381, 31119, 33145, 35079, 35206, 28149, 24343,
+ 25173, 27832, 20175, 29289, 39826, 20998, 21563, 22132, 22707, 24996,
+ 25198, 28954, 22894, 31881, 31966, 32027, 38640, [12098, 25991], 32862,
+ 19993, 20341, 20853, 22592, 24163, 24179, 24330, 26564, 20006, 34109,
+ 38281, 38491, [12150, 31859], [12212, 38913], 20731, 22721, 30294, 30887,
+ 21029, 30629, 34065, 31622, 20559, 22793, [12122, 29255], 31687, 32232,
+ 36794, 36820, 36941, 20415, 21193, 23081, 24321, 38829, 20445, 33303,
+ 37610, 22275, 25429, 27497, 29995, 35036, 36628, 31298, 21215, 22675,
+ 24917, 25098, 26286, [11935, 27597], 31807, 33769, 20515, 20472, 21253,
+ 21574, 22577, 22857, 23453, 23792, 23791, 23849, 24214, 25265, 25447,
+ 25918, [12101, 26041], 26379, 27861, 27873, 28921, 30770, 32299, 32990,
+ 33459, 33804, 34028, 34562, 35090, 35370, 35914, 37030, 37586, 39165,
+ 40179, 40300, 20047, 20129, 20621, 21078, 22346, 22952, 24125,
+ {f: 2, c: 24536}, 25151, 26292, 26395, 26576, 26834, 20882, 32033, 32938,
+ 33192, 35584, 35980, 36031, 37502, 38450, 21536, 38956, 21271, 20693,
+ [12056, 21340], 22696, 25778, 26420, 29287, 30566, 31302, 37350, 21187,
+ 27809, 27526, 22528, 24140, 22868, 26412, 32763, 20961, 30406, 25705,
+ 30952, 39764, [12231, 40635], 22475, 22969, 26151, 26522, 27598, 21737,
+ 27097, 24149, 33180, 26517, 39850, 26622, 40018, 26717, 20134, 20451,
+ [12060, 21448], 25273, 26411, 27819, 36804, 20397, 32365, 40639, 19975,
+ 24930, 28288, 28459, 34067, 21619, 26410, 39749, [11922, 24051], 31637,
+ 23724, 23494, 34588, 28234, 34001, 31252, 33032, 22937, 31885,
+ [11936, 27665], 30496, 21209, 22818, 28961, 29279, [12141, 30683], 38695,
+ 40289, 26891, 23167, 23064, 20901, 21517, 21629, 26126, 30431, 36855,
+ 37528, 40180, 23018, 29277, 28357, 20813, 26825, 32191, 32236,
+ [12207, 38754], 40634, 25720, 27169, 33538, 22916, 23391, [12113, 27611],
+ 29467, 30450, 32178, 32791, 33945, 20786, [12106, 26408], 40665,
+ [12140, 30446], 26466, 21247, 39173, 23588, 25147, 31870, 36016, 21839,
+ 24758, 32011, [12200, 38272], 21249, 20063, 20918, 22812, 29242, 32822,
+ 37326, 24357, [12142, 30690], 21380, 24441, 32004, 34220, 35379, 36493,
+ 38742, 26611, 34222, 37971, 24841, 24840, 27833, 30290, 35565, 36664,
+ 21807, 20305, 20778, 21191, 21451, 23461, 24189, 24736, 24962, 25558,
+ 26377, 26586, 28263, 28044, {f: 2, c: 29494}, 30001, 31056, 35029, 35480,
+ 36938, [12194, 37009], 37109, 38596, 34701, [12067, 22805], 20104, 20313,
+ 19982, 35465, 36671, 38928, 20653, 24188, 22934, 23481, 24248, 25562,
+ 25594, 25793, 26332, 26954, 27096, 27915, 28342, 29076, [12132, 29992],
+ 31407, [12154, 32650], 32768, 33865, 33993, 35201, 35617, 36362, 36965,
+ 38525, 39178, 24958, 25233, 27442, 27779, 28020, 32716, 32764, 28096,
+ 32645, 34746, 35064, 26469, 33713, 38972, 38647, 27931, 32097, 33853,
+ 37226, 20081, 21365, 23888, 27396, 28651, 34253, 34349, 35239, 21033,
+ 21519, 23653, 26446, 26792, 29702, 29827, 30178, 35023, 35041,
+ [12197, 37324], 38626, 38520, 24459, 29575, [12148, 31435], 33870, 25504,
+ 30053, 21129, 27969, 28316, 29705, 30041, 30827, 31890, 38534,
+ [12015, 31452], [12243, 40845], 20406, 24942, 26053, 34396, 20102, 20142,
+ 20698, 20001, 20940, 23534, 26009, 26753, 28092, 29471, 30274, 30637,
+ 31260, 31975, 33391, 35538, 36988, 37327, 38517, 38936, [12050, 21147],
+ 32209, 20523, 21400, 26519, 28107, 29136, 29747, 33256, 36650, 38563,
+ 40023, 40607, 29792, 22593, 28057, 32047, 39006, 20196, 20278, 20363,
+ 20919, 21169, 23994, 24604, 29618, 31036, 33491, 37428, 38583, 38646,
+ 38666, 40599, 40802, 26278, 27508, 21015, 21155, 28872, 35010, 24265,
+ 24651, 24976, 28451, 29001, 31806, 32244, 32879, 34030, 36899, 37676,
+ 21570, 39791, 27347, 28809, 36034, 36335, 38706, 21172, 23105, 24266,
+ 24324, 26391, 27004, 27028, 28010, 28431, 29282, 29436, 31725,
+ [12156, 32769], 32894, 34635, 37070, 20845, 40595, 31108, 32907, 37682,
+ 35542, 20525, 21644, 35441, 27498, 36036, 33031, 24785, 26528, 40434,
+ 20121, 20120, 39952, 35435, 34241, 34152, 26880, 28286, 30871, 33109,
+ 24332, 19984, 19989, 20010, 20017, [12034, 20022], 20028, [12035, 20031],
+ 20034, 20054, 20056, 20098, [12037, 20101], 35947, 20106, 33298, 24333,
+ 20110, {f: 2, c: 20126}, [12039, 20128], 20130, 20144, 20147, 20150, 20174,
+ 20173, 20164, 20166, 20162, 20183, 20190, 20205, 20191, 20215, 20233,
+ 20314, 20272, 20315, 20317, 20311, 20295, 20342, 20360, 20367, 20376,
+ 20347, 20329, 20336, 20369, 20335, 20358, 20374, 20760, 20436, 20447,
+ 20430, 20440, 20443, 20433, 20442, 20432, {f: 2, c: 20452}, 20506, 20520,
+ 20500, 20522, 20517, 20485, 20252, 20470, 20513, 20521, 20524, 20478,
+ 20463, 20497, 20486, 20547, 20551, 26371, 20565, 20560, 20552, 20570,
+ 20566, 20588, 20600, 20608, 20634, 20613, 20660, 20658, {f: 2, c: 20681},
+ 20659, 20674, 20694, 20702, 20709, 20717, 20707, 20718, 20729, 20725,
+ 20745, {f: 2, c: 20737}, 20758, 20757, 20756, 20762, 20769, 20794, 20791,
+ 20796, 20795, [12041, 20799], [11918, 20800], 20818, 20812, 20820, 20834,
+ 31480, {f: 2, c: 20841}, 20846, 20864, [12044, 20866], 22232, 20876, 20873,
+ 20879, 20881, 20883, 20885, [12045, 20886], 20900, 20902, 20898,
+ {f: 2, c: 20905}, [12046, 20907], 20915, {f: 2, c: 20913}, 20912, 20917,
+ 20925, 20933, 20937, 20955, [12047, 20960], 34389, 20969, 20973, 20976,
+ [12048, 20981], 20990, 20996, 21003, 21012, 21006, 21031, 21034, 21038,
+ 21043, 21049, 21071, 21060, {f: 2, c: 21067}, 21086, 21076, 21098, 21108,
+ 21097, 21107, 21119, 21117, 21133, 21140, 21138, 21105, 21128, 21137,
+ 36776, 36775, {f: 2, c: 21164}, 21180, 21173, 21185, 21197, 21207, 21214,
+ 21219, 21222, 39149, 21216, 21235, 21237, 21240, [12051, 21241], 21254,
+ 21256, 30008, 21261, 21264, 21263, [12052, 21269], [12053, 21274], 21283,
+ 21295, 21297, 21299, [12054, 21304], 21312, 21318, 21317, 19991, 21321,
+ 21325, 20950, 21342, [12057, 21353], 21358, 22808, 21371, 21367,
+ [12058, 21378], 21398, 21408, 21414, 21413, 21422, 21424, [12059, 21430],
+ 21443, 31762, 38617, 21471, 26364, 29166, 21486, 21480, 21485, 21498,
+ 21505, 21565, 21568, {f: 2, c: 21548}, 21564, 21550, 21558, 21545, 21533,
+ 21582, 21647, 21621, 21646, 21599, 21617, 21623, 21616, 21650, 21627,
+ 21632, 21622, 21636, 21648, 21638, 21703, 21666, 21688, 21669, 21676,
+ 21700, 21704, 21672, 21675, 21698, 21668, 21694, 21692, 21720,
+ {f: 2, c: 21733}, 21775, 21780, 21757, 21742, 21741, 21754, 21730, 21817,
+ 21824, 21859, 21836, 21806, 21852, 21829, {f: 2, c: 21846}, 21816, 21811,
+ 21853, 21913, 21888, 21679, 21898, 21919, 21883, 21886, 21912, 21918,
+ 21934, 21884, 21891, 21929, 21895, 21928, 21978, 21957, 21983, 21956,
+ 21980, 21988, 21972, 22036, 22007, 22038, 22014, 22013, 22043, 22009,
+ 22094, 22096, 29151, 22068, 22070, 22066, 22072, 22123, 22116, 22063,
+ 22124, 22122, 22150, 22144, 22154, 22176, 22164, 22159, 22181, 22190,
+ 22198, 22196, 22210, 22204, 22209, 22211, 22208, 22216, 22222, 22225,
+ 22227, [12062, 22231], 22254, 22265, 22272, 22271, 22276, 22281, 22280,
+ 22283, 22285, 22291, 22296, 22294, 21959, 22300, 22310, {f: 2, c: 22327},
+ 22350, 22331, 22336, 22351, 22377, 22464, 22408, 22369, 22399, 22409,
+ 22419, 22432, 22451, 22436, 22442, 22448, 22467, 22470, 22484,
+ {f: 2, c: 22482}, 22538, 22486, 22499, 22539, 22553, 22557, 22642, 22561,
+ 22626, 22603, 22640, 27584, 22610, 22589, 22649, 22661, 22713, 22687,
+ 22699, 22714, 22750, 22715, 22712, 22702, 22725, 22739, 22737, 22743,
+ 22745, 22744, 22757, 22748, 22756, 22751, 22767, 22778, 22777,
+ {f: 3, c: 22779}, [12065, 22786], [12066, 22794], 22800, 22811, 26790,
+ 22821, {f: 2, c: 22828}, 22834, 22840, 22846, 31442, 22869, 22864, 22862,
+ 22874, 22872, 22882, 22880, 22887, 22892, 22889, 22904, 22913, 22941,
+ 20318, 20395, 22947, 22962, 22982, 23016, 23004, 22925, {f: 2, c: 23001},
+ 23077, 23071, 23057, 23068, 23049, 23066, 23104, 23148, 23113,
+ {f: 2, c: 23093}, 23138, 23146, 23194, 23228, 23230, 23243, 23234, 23229,
+ 23267, 23255, 23270, 23273, 23254, {f: 2, c: 23290}, 23308, 23307, 23318,
+ 23346, 23248, 23338, 23350, 23358, 23363, 23365, 23360, 23377, 23381,
+ {f: 2, c: 23386}, 23397, 23401, 23408, 23411, 23413, 23416, 25992, 23418,
+ [12071, 23424], 23427, 23462, 23480, 23491, 23495, 23497, 23508, 23504,
+ 23524, 23526, 23522, 23518, 23525, 23531, 23536, 23542, 23539, 23557,
+ {f: 2, c: 23559}, 23565, 23571, 23584, [11920, 12074, 23586], 23592,
+ [12075, 23608], 23609, 23617, 23622, 23630, 23635, 23632, 23631, 23409,
+ 23660, [12076, 23662], 20066, 23670, 23673, 23692, 23697, 23700, 22939,
+ 23723, 23739, 23734, 23740, 23735, 23749, 23742, 23751, 23769, 23785,
+ 23805, 23802, 23789, 23948, 23786, 23819, 23829, 23831, 23900, 23839,
+ 23835, 23825, 23828, 23842, 23834, 23833, 23832, 23884, 23890, 23886,
+ 23883, 23916, 23923, 23926, 23943, 23940, 23938, 23970, 23965, 23980,
+ 23982, 23997, 23952, 23991, 23996, 24009, 24013, 24019, 24018, 24022,
+ [12078, 24027], 24043, 24050, 24053, 24075, 24090, 24089, 24081, 24091,
+ {f: 2, c: 24118}, 24132, 24131, 24128, 24142, 24151, 24148, 24159, 24162,
+ 24164, 24135, {f: 2, c: 24181}, [11923, 12083, 24186], 40636,
+ [12084, 24191], 24224, {f: 2, c: 24257}, 24264, 24272, 24271, 24278, 24291,
+ 24285, {f: 2, c: 24282}, 24290, 24289, {f: 2, c: 24296}, 24300, 24305,
+ 24307, 24304, [12085, 24308], 24312, [12086, 24318], 24323, 24329, 24413,
+ 24412, [12087, 24331], 24337, 24342, 24361, 24365, 24376, 24385, 24392,
+ 24396, 24398, 24367, [11924, 24401], {f: 2, c: 24406}, 24409,
+ [12090, 24417], 24429, [12091, 24435], 24439, 24451, 24450, 24447, 24458,
+ 24456, 24465, 24455, 24478, 24473, 24472, 24480, 24488, 24493, 24508,
+ 24534, 24571, 24548, 24568, 24561, 24541, 24755, 24575, 24609, 24672,
+ 24601, 24592, 24617, 24590, 24625, 24603, 24597, 24619, 24614, 24591,
+ 24634, 24666, 24641, 24682, 24695, 24671, 24650, 24646, 24653, 24675,
+ 24643, 24676, 24642, 24684, 24683, 24665, 24705, 24717, 24807, 24707,
+ 24730, 24708, 24731, {f: 2, c: 24726}, 24722, 24743, 24715, 24801, 24760,
+ 24800, 24787, 24756, 24560, 24765, 24774, 24757, 24792, 24909, 24853,
+ 24838, {f: 2, c: 24822}, 24832, 24820, 24826, 24835, 24865, 24827, 24817,
+ {f: 2, c: 24845}, 24903, 24894, 24872, 24871, 24906, 24895, 24892, 24876,
+ 24884, 24893, 24898, 24900, 24947, 24951, {f: 3, c: 24920}, 24939, 24948,
+ 24943, 24933, 24945, 24927, 24925, 24915, 24949, 24985, 24982, 24967,
+ 25004, 24980, 24986, 24970, 24977, 25003, 25006, 25036, 25034, 25033,
+ 25079, 25032, 25027, 25030, 25018, 25035, 32633, 25037, 25062, 25059,
+ 25078, 25082, 25076, 25087, 25085, 25084, 25086, 25088, [12093, 25096],
+ 25097, 25101, 25100, 25108, 25115, 25118, 25121, 25130, 25134, 25136,
+ {f: 2, c: 25138}, 25153, 25166, 25182, 25187, 25179, 25184, 25192, 25212,
+ 25218, 25225, 25214, {f: 2, c: 25234}, 25238, 25300, 25219, 25236, 25303,
+ 25297, 25275, 25295, 25343, 25286, 25812, 25288, 25308, 25292, 25290,
+ 25282, 25287, 25243, 25289, 25356, 25326, 25329, 25383, 25346, 25352,
+ 25327, 25333, 25424, 25406, 25421, 25628, 25423, 25494, 25486, 25472,
+ 25515, 25462, 25507, 25487, 25481, 25503, 25525, 25451, 25449, 25534,
+ 25577, 25536, 25542, 25571, 25545, 25554, 25590, 25540, 25622, 25652,
+ 25606, 25619, 25638, 25654, 25885, 25623, 25640, 25615, 25703, 25711,
+ 25718, 25678, 25898, 25749, 25747, 25765, 25769, 25736, 25788, 25818,
+ 25810, 25797, 25799, 25787, 25816, 25794, 25841, 25831, 33289,
+ {f: 2, c: 25824}, 25260, 25827, 25839, 25900, 25846, 25844, 25842, 25850,
+ 25856, 25853, 25880, 25884, 25861, 25892, 25891, 25899, [12097, 25908],
+ [11929, 25909], 25911, 25910, 25912, 30027, 25928, 25942, 25941, 25933,
+ 25944, 25950, 25949, 25970, 25976, {f: 2, c: 25986}, 35722, 26011, 26015,
+ 26027, 26039, 26051, 26054, 26049, 26052, 26060, 26066, 26075, 26073,
+ [12102, 26080], [11931, 26081], 26097, 26482, 26122, 26115, 26107, 26483,
+ {f: 2, c: 26165}, 26164, 26140, 26191, 26180, 26185, 26177, 26206, 26205,
+ 26212, {f: 2, c: 26215}, 26207, 26210, 26224, 26243, 26248, 26254, 26249,
+ 26244, 26264, 26269, 26305, 26297, 26313, 26302, 26300, 26308, 26296,
+ 26326, 26330, 26336, 26175, 26342, 26345, [12104, 26352], 26357, 26359,
+ 26383, 26390, 26398, {f: 2, c: 26406}, 38712, 26414, 26431, 26422, 26433,
+ 26424, 26423, 26438, 26462, 26464, 26457, {f: 2, c: 26467}, 26505, 26480,
+ 26537, 26492, 26474, 26508, 26507, 26534, 26529, 26501, 26551, 26607,
+ 26548, 26604, 26547, 26601, 26552, 26596, 26590, 26589, 26594, 26606,
+ 26553, 26574, 26566, 26599, 27292, 26654, 26694, 26665, 26688, 26701,
+ 26674, 26702, 26803, 26667, 26713, 26723, 26743, 26751, 26783, 26767,
+ 26797, 26772, 26781, 26779, 26755, 27310, 26809, 26740, 26805, 26784,
+ 26810, 26895, 26765, 26750, 26881, 26826, 26888, 26840, 26914, 26918,
+ 26849, 26892, 26829, 26836, 26855, 26837, 26934, 26898, 26884, 26839,
+ 26851, 26917, 26873, 26848, 26863, 26920, 26922, 26906, 26915, 26913,
+ 26822, 27001, 26999, 26972, 27000, 26987, 26964, 27006, 26990, 26937,
+ 26996, 26941, 26969, 26928, 26977, 26974, 26973, 27009, 26986, 27058,
+ 27054, 27088, 27071, 27073, 27091, 27070, 27086, 23528, 27082, 27101,
+ 27067, 27075, 27047, 27182, 27025, 27040, 27036, 27029, 27060, 27102,
+ 27112, 27138, 27163, 27135, 27402, 27129, 27122, 27111, 27141, 27057,
+ 27166, 27117, 27156, 27115, 27146, 27154, 27329, 27171, 27155, 27204,
+ 27148, 27250, 27190, 27256, 27207, 27234, 27225, 27238, 27208, 27192,
+ 27170, 27280, 27277, 27296, 27268, {f: 2, c: 27298}, 27287, 34327, 27323,
+ 27331, 27330, 27320, 27315, 27308, 27358, 27345, 27359, 27306, 27354,
+ 27370, 27387, 27397, 34326, 27386, 27410, 27414, 39729, 27423, 27448,
+ 27447, 30428, 27449, 39150, 27463, 27459, 27465, 27472, 27481, 27476,
+ 27483, 27487, 27489, 27512, [12109, 27513], {f: 2, c: 27519}, 27524, 27523,
+ 27533, 27544, 27541, 27550, 27556, {f: 2, c: 27562}, 27567, 27570, 27569,
+ [12110, 27571], 27575, 27580, 27590, [12111, 27595], 27603, 27615, 27628,
+ 27627, 27635, 27631, 40638, 27656, 27667, [12115, 27668], 27675, 27684,
+ 27683, 27742, 27733, 27746, 27754, 27778, 27789, 27802, 27777, 27803,
+ 27774, 27752, 27763, 27794, 27792, 27844, 27889, 27859, 27837, 27863,
+ 27845, 27869, 27822, 27825, 27838, 27834, 27867, 27887, 27865, 27882,
+ 27935, 34893, 27958, 27947, 27965, 27960, 27929, 27957, 27955, 27922,
+ 27916, 28003, 28051, 28004, 27994, 28025, 27993, 28046, 28053, 28644,
+ 28037, 28153, 28181, 28170, 28085, 28103, 28134, 28088, 28102, 28140,
+ 28126, 28108, 28136, 28114, 28101, 28154, 28121, 28132, 28117, 28138,
+ 28142, 28205, 28270, 28206, 28185, 28274, 28255, 28222, 28195, 28267,
+ 28203, 28278, 28237, 28191, 28227, 28218, 28238, 28196, 28415, 28189,
+ 28216, 28290, 28330, 28312, 28361, 28343, 28371, 28349, 28335, 28356,
+ 28338, {f: 2, c: 28372}, 28303, 28325, 28354, 28319, 28481, 28433, 28748,
+ 28396, 28408, 28414, 28479, 28402, 28465, 28399, 28466, 28364, 28478,
+ 28435, 28407, 28550, 28538, 28536, 28545, 28544, 28527, 28507, 28659,
+ 28525, 28546, 28540, 28504, 28558, 28561, 28610, 28518, 28595, 28579,
+ 28577, 28580, 28601, 28614, 28586, 28639, 28629, 28652, 28628, 28632,
+ 28657, 28654, 28635, 28681, 28683, 28666, 28689, 28673, 28687, 28670,
+ 28699, 28698, 28532, 28701, 28696, 28703, 28720, 28734, 28722, 28753,
+ 28771, 28825, 28818, 28847, 28913, 28844, 28856, 28851, 28846, 28895,
+ 28875, 28893, 28889, 28937, 28925, 28956, 28953, 29029, 29013, 29064,
+ 29030, 29026, 29004, 29014, 29036, 29071, 29179, 29060, 29077, 29096,
+ 29100, 29143, 29113, 29118, 29138, 29129, 29140, 29134, 29152, 29164,
+ 29159, 29173, 29180, 29177, 29183, 29197, 29200, 29211, 29224, 29229,
+ 29228, 29232, 29234, [12120, 29243], 29244, [12121, 29247], 29248, 29254,
+ 29259, 29272, 29300, 29310, 29314, 29313, 29319, 29330, 29334, 29346,
+ 29351, 29369, 29362, 29379, 29382, 29380, 29390, 29394, 29410,
+ {f: 2, c: 29408}, 29433, 29431, 20495, 29463, 29450, 29468, 29462, 29469,
+ 29492, 29487, 29481, 29477, 29502, {f: 2, c: 29518}, 40664, 29527, 29546,
+ 29544, 29552, 29560, 29557, 29563, 29562, 29640, 29619, 29646, 29627,
+ 29632, 29669, 29678, 29662, 29858, 29701, 29807, 29733, 29688, 29746,
+ 29754, 29781, 29759, 29791, 29785, 29761, 29788, 29801, 29808, 29795,
+ 29802, 29814, 29822, 29835, 29854, 29863, 29898, 29903, 29908, 29681,
+ 29920, 29923, 29927, 29929, 29934, 29938, {f: 2, c: 29936}, 29944, 29943,
+ 29956, 29955, 29957, 29964, 29966, 29965, 29973, 29971, 29982, 29990,
+ 29996, 30012, 30020, 30029, 30026, 30025, 30043, 30022, 30042, 30057,
+ 30052, 30055, 30059, 30061, 30072, 30070, {f: 2, c: 30086}, 30068, 30090,
+ 30089, 30082, 30100, 30106, 30109, 30117, 30115, 30146, 30131, 30147,
+ 30133, 30141, 30136, 30140, 30129, 30157, 30154, 30162, 30169, 30179,
+ 30174, {f: 2, c: 30206}, 30204, 30209, 30192, 30202, {f: 2, c: 30194},
+ 30219, 30221, 30217, 30239, 30247, {f: 3, c: 30240}, 30244, 30260, 30256,
+ 30267, {f: 2, c: 30279}, 30278, 30300, 30296, {f: 2, c: 30305},
+ {f: 3, c: 30312}, 30311, 30316, 30320, 30322, [12136, 30326], 30328, 30332,
+ 30336, 30339, 30344, 30347, 30350, 30358, 30355, {f: 2, c: 30361}, 30384,
+ 30388, {f: 3, c: 30392}, 30402, 30413, 30422, 30418, 30430, 30433, 30437,
+ 30439, 30442, 34351, 30459, 30472, 30471, 30468, 30505, 30500, 30494,
+ {f: 2, c: 30501}, 30491, {f: 2, c: 30519}, 30535, 30554, 30568, 30571,
+ 30555, 30565, 30591, 30590, 30585, 30606, 30603, 30609, 30624, 30622,
+ 30640, 30646, 30649, 30655, {f: 2, c: 30652}, 30651, 30663, 30669, 30679,
+ 30682, 30684, 30691, 30702, 30716, 30732, 30738, 31014, 30752, 31018,
+ 30789, 30862, 30836, 30854, 30844, 30874, 30860, 30883, 30901, 30890,
+ 30895, 30929, 30918, 30923, 30932, 30910, 30908, 30917, 30922, 30956,
+ 30951, 30938, 30973, 30964, 30983, 30994, 30993, 31001, 31020, 31019,
+ 31040, 31072, 31063, 31071, 31066, 31061, 31059, 31098, 31103, 31114,
+ 31133, 31143, 40779, 31146, 31150, 31155, {f: 2, c: 31161}, 31177, 31189,
+ 31207, 31212, 31201, 31203, 31240, 31245, {f: 2, c: 31256}, 31264, 31263,
+ 31104, 31281, 31291, 31294, 31287, 31299, 31319, 31305, {f: 2, c: 31329},
+ 31337, 40861, 31344, 31353, 31357, 31368, 31383, 31381, 31384, 31382,
+ 31401, 31432, 31408, 31414, 31429, 31428, 31423, 36995, 31431, 31434,
+ 31437, 31439, 31445, 31443, {f: 2, c: 31449}, 31453, {f: 2, c: 31457},
+ 31462, 31469, 31472, 31490, 31503, 31498, 31494, 31539, {f: 2, c: 31512},
+ 31518, 31541, 31528, 31542, 31568, 31610, 31492, 31565, 31499, 31564,
+ 31557, 31605, 31589, 31604, 31591, {f: 2, c: 31600}, 31596, 31598, 31645,
+ 31640, 31647, 31629, 31644, 31642, 31627, 31634, 31631, 31581, 31641,
+ 31691, 31681, 31692, 31695, 31668, 31686, 31709, 31721, 31761, 31764,
+ 31718, 31717, 31840, 31744, 31751, 31763, 31731, 31735, 31767, 31757,
+ 31734, 31779, 31783, 31786, 31775, 31799, 31787, 31805, 31820, 31811,
+ 31828, 31823, 31808, 31824, 31832, 31839, 31844, 31830, 31845, 31852,
+ 31861, 31875, 31888, 31908, 31917, 31906, 31915, 31905, 31912, 31923,
+ 31922, 31921, 31918, 31929, 31933, 31936, 31941, 31938, 31960, 31954,
+ 31964, 31970, 39739, 31983, 31986, 31988, 31990, 31994, 32006, 32002,
+ 32028, 32021, 32010, 32069, 32075, 32046, 32050, 32063, 32053, 32070,
+ 32115, 32086, 32078, 32114, 32104, 32110, 32079, 32099, 32147, 32137,
+ 32091, 32143, 32125, 32155, 32186, 32174, 32163, 32181, 32199, 32189,
+ 32171, 32317, 32162, 32175, 32220, 32184, 32159, 32176, 32216, 32221,
+ 32228, 32222, 32251, 32242, 32225, 32261, 32266, 32291, 32289, 32274,
+ 32305, 32287, 32265, 32267, 32290, 32326, 32358, 32315, 32309, 32313,
+ 32323, 32311, 32306, 32314, 32359, 32349, 32342, 32350, {f: 2, c: 32345},
+ 32377, 32362, 32361, 32380, 32379, 32387, 32213, 32381, 36782, 32383,
+ {f: 2, c: 32392}, 32396, 32402, 32400, {f: 2, c: 32403}, 32406, 32398,
+ {f: 2, c: 32411}, 32568, 32570, 32581, {f: 3, c: 32588}, 32592,
+ [12153, 32593], 32597, 32596, 32600, {f: 2, c: 32607}, {f: 2, c: 32616},
+ 32615, 32632, 32642, 32646, 32643, 32648, 32647, 32652, 32660, 32670,
+ 32669, 32666, 32675, 32687, 32690, 32697, 32686, 32694, 32696, 35697,
+ {f: 2, c: 32709}, 32714, 32725, 32724, 32737, 32742, 32745, 32755, 32761,
+ 39132, 32774, 32772, 32779, [12158, 32786], {f: 2, c: 32792}, 32796, 32801,
+ 32808, 32831, 32827, 32842, 32838, 32850, 32856, 32858, 32863, 32866,
+ 32872, 32883, 32882, 32880, 32886, 32889, 32893, [12160, 32895], 32900,
+ 32902, 32901, 32923, 32915, 32922, 32941, 20880, 32940, 32987, 32997,
+ 32985, 32989, 32964, 32986, 32982, 33033, 33007, 33009, 33051, 33065,
+ 33059, 33071, 33099, 38539, 33094, 33086, 33107, 33105, 33020, 33137,
+ 33134, {f: 2, c: 33125}, 33140, 33155, 33160, 33162, 33152, 33154, 33184,
+ 33173, 33188, 33187, 33119, 33171, 33193, 33200, 33205, 33214, 33208,
+ 33213, 33216, 33218, 33210, 33225, 33229, 33233, 33241, 33240, 33224,
+ 33242, {f: 2, c: 33247}, 33255, {f: 2, c: 33274}, 33278, {f: 2, c: 33281},
+ 33285, 33287, 33290, 33293, 33296, 33302, 33321, 33323, 33336, 33331,
+ 33344, 33369, 33368, 33373, 33370, 33375, 33380, 33378, 33384,
+ {f: 2, c: 33386}, 33326, 33393, 33399, [12171, 33400], 33406, 33421, 33426,
+ 33451, 33439, 33467, 33452, 33505, 33507, 33503, 33490, 33524, 33523,
+ 33530, 33683, 33539, 33531, 33529, 33502, 33542, 33500, 33545, 33497,
+ 33589, 33588, 33558, 33586, 33585, 33600, 33593, 33616, 33605, 33583,
+ 33579, {f: 2, c: 33559}, 33669, 33690, 33706, 33695, 33698, 33686, 33571,
+ 33678, 33671, 33674, 33660, 33717, 33651, 33653, 33696, 33673, 33704,
+ 33780, 33811, 33771, 33742, 33789, 33795, 33752, 33803, 33729, 33783,
+ 33799, 33760, 33778, 33805, 33826, 33824, 33725, 33848, 34054, 33787,
+ 33901, 33834, 33852, 34138, 33924, 33911, 33899, 33965, 33902, 33922,
+ 33897, 33862, 33836, 33903, 33913, 33845, 33994, 33890, 33977, 33983,
+ 33951, 34009, 33997, 33979, 34010, 34000, 33985, 33990, 34006, 33953,
+ 34081, 34047, 34036, {f: 2, c: 34071}, 34092, 34079, 34069, 34068, 34044,
+ 34112, 34147, 34136, 34120, 34113, 34306, 34123, 34133, 34176, 34212,
+ 34184, 34193, 34186, 34216, 34157, 34196, 34203, 34282, 34183, 34204,
+ 34167, 34174, 34192, 34249, 34234, 34255, 34233, 34256, 34261, 34269,
+ 34277, 34268, 34297, 34314, 34323, 34315, 34302, 34298, 34310, 34338,
+ 34330, 34352, 34367, [12172, 34381], 20053, 34388, 34399, 34407, 34417,
+ 34451, 34467, {f: 2, c: 34473}, {f: 2, c: 34443}, 34486, 34479, 34500,
+ 34502, 34480, 34505, 34851, 34475, 34516, 34526, 34537, 34540, 34527,
+ 34523, 34543, 34578, 34566, 34568, 34560, 34563, 34555, 34577, 34569,
+ 34573, 34553, 34570, 34612, 34623, 34615, 34619, 34597, 34601, 34586,
+ 34656, 34655, 34680, 34636, 34638, 34676, 34647, 34664, 34670, 34649,
+ 34643, 34659, 34666, 34821, 34722, 34719, 34690, 34735, 34763, 34749,
+ 34752, 34768, 38614, 34731, 34756, 34739, 34759, 34758, 34747, 34799,
+ 34802, 34784, 34831, 34829, 34814, {f: 2, c: 34806}, 34830, 34770, 34833,
+ 34838, 34837, 34850, 34849, 34865, 34870, 34873, 34855, 34875, 34884,
+ 34882, 34898, 34905, 34910, 34914, 34923, 34945, 34942, 34974, 34933,
+ 34941, 34997, 34930, 34946, 34967, 34962, 34990, 34969, 34978, 34957,
+ 34980, 34992, 35007, 34993, {f: 2, c: 35011}, 35028, {f: 2, c: 35032},
+ 35037, 35065, 35074, 35068, 35060, 35048, 35058, 35076, 35084, 35082,
+ 35091, 35139, 35102, 35109, {f: 2, c: 35114}, 35137, 35140, 35131, 35126,
+ 35128, 35148, 35101, 35168, 35166, 35174, 35172, 35181, 35178, 35183,
+ 35188, 35191, [12177, 35198], 35203, 35208, 35210, 35219, 35224, 35233,
+ 35241, 35238, 35244, 35247, 35250, 35258, 35261, {f: 2, c: 35263}, 35290,
+ {f: 2, c: 35292}, 35303, 35316, 35320, 35331, 35350, 35344, 35340, 35355,
+ 35357, 35365, 35382, 35393, 35419, 35410, 35398, 35400, 35452, 35437,
+ 35436, 35426, 35461, 35458, 35460, 35496, 35489, 35473, {f: 2, c: 35493},
+ 35482, 35491, 35524, 35533, 35522, 35546, 35563, 35571, 35559, 35556,
+ 35569, 35604, 35552, 35554, 35575, 35550, 35547, 35596, 35591, 35610,
+ 35553, 35606, 35600, 35607, 35616, 35635, 38827, 35622, 35627, 35646,
+ 35624, 35649, 35660, 35663, 35662, 35657, 35670, 35675, 35674, 35691,
+ 35679, 35692, 35695, 35700, 35709, 35712, 35724, 35726, {f: 2, c: 35730},
+ 35734, {f: 2, c: 35737}, 35898, 35905, 35903, 35912, 35916, 35918, 35920,
+ [12183, 35925], 35938, 35948, [12184, 35960], 35962, 35970, 35977, 35973,
+ 35978, {f: 2, c: 35981}, 35988, 35964, 35992, 25117, 36013, 36010, 36029,
+ {f: 2, c: 36018}, 36014, 36022, 36040, 36033, 36068, 36067, 36058, 36093,
+ {f: 2, c: 36090}, {f: 2, c: 36100}, 36106, 36103, 36111, 36109, 36112,
+ 40782, 36115, 36045, 36116, 36118, 36199, 36205, 36209, 36211, 36225,
+ 36249, 36290, 36286, 36282, 36303, 36314, 36310, 36300, 36315, 36299,
+ {f: 2, c: 36330}, 36319, 36323, 36348, {f: 2, c: 36360}, 36351,
+ {f: 2, c: 36381}, 36368, 36383, 36418, 36405, 36400, 36404, 36426, 36423,
+ 36425, 36428, 36432, 36424, 36441, 36452, 36448, 36394, 36451, 36437,
+ 36470, 36466, 36476, 36481, 36487, 36485, 36484, 36491, 36490, 36499,
+ 36497, 36500, 36505, 36522, 36513, 36524, 36528, 36550, 36529, 36542,
+ 36549, 36552, 36555, 36571, 36579, 36604, 36603, 36587, 36606, 36618,
+ 36613, 36629, 36626, 36633, 36627, 36636, 36639, 36635, 36620, 36646,
+ 36659, 36667, 36665, 36677, 36674, 36670, 36684, 36681, 36678, 36686,
+ 36695, 36700, {f: 3, c: 36706}, 36764, 36767, 36771, 36781, 36783, 36791,
+ 36826, 36837, 36834, 36842, 36847, 36999, 36852, 36869, {f: 2, c: 36857},
+ 36881, 36885, 36897, 36877, 36894, 36886, 36875, 36903, 36918, 36917,
+ 36921, 36856, {f: 4, c: 36943}, 36878, 36937, 36926, 36950, 36952, 36958,
+ 36968, 36975, 36982, 38568, 36978, 36994, 36989, 36993, 36992, 37002,
+ 37001, 37007, 37032, 37039, 37041, 37045, 37090, 37092, 25160, 37083,
+ 37122, 37138, 37145, 37170, 37168, 37194, 37206, 37208, 37219, 37221,
+ 37225, 37235, 37234, 37259, 37257, 37250, 37282, 37291, 37295, 37290,
+ 37301, 37300, 37306, {f: 2, c: 37312}, 37321, 37323, 37328, 37334, 37343,
+ 37345, 37339, 37372, {f: 2, c: 37365}, 37406, 37375, 37396, 37420, 37397,
+ 37393, 37470, 37463, 37445, 37449, 37476, 37448, 37525, 37439, 37451,
+ 37456, 37532, 37526, 37523, 37531, 37466, 37583, 37561, 37559, 37609,
+ 37647, 37626, 37700, 37678, 37657, 37666, 37658, 37667, 37690, 37685,
+ 37691, 37724, 37728, 37756, 37742, 37718, 37808, {f: 2, c: 37804}, 37780,
+ 37817, {f: 2, c: 37846}, 37864, 37861, 37848, 37827, 37853, 37840, 37832,
+ 37860, 37914, 37908, 37907, 37891, 37895, 37904, 37942, 37931, 37941,
+ 37921, 37946, 37953, 37970, 37956, 37979, 37984, 37986, 37982, 37994,
+ 37417, 38000, 38005, 38007, 38013, 37978, 38012, 38014, 38017, 38015,
+ 38274, 38279, 38282, 38292, 38294, {f: 2, c: 38296}, 38304, 38312, 38311,
+ 38317, 38332, 38331, 38329, 38334, 38346, 28662, 38339, 38349, 38348,
+ 38357, 38356, 38358, 38364, 38369, 38373, 38370, 38433, 38440,
+ {f: 2, c: 38446}, 38466, 38476, 38479, 38475, 38519, 38492, 38494, 38493,
+ 38495, 38502, 38514, 38508, 38541, 38552, 38549, 38551, 38570, 38567,
+ {f: 2, c: 38577}, 38576, 38580, [12202, 38582], 38584, [12203, 38585],
+ 38606, 38603, 38601, 38605, 35149, 38620, 38669, 38613, 38649, 38660,
+ 38662, 38664, 38675, 38670, 38673, 38671, 38678, 38681, 38692, 38698,
+ 38704, 38713, {f: 2, c: 38717}, 38724, 38726, 38728, 38722, 38729, 38748,
+ 38752, 38756, 38758, 38760, 21202, 38763, 38769, 38777, 38789, 38780,
+ 38785, 38778, 38790, 38795, {f: 2, c: 38799}, 38812, 38824, 38822, 38819,
+ {f: 2, c: 38835}, 38851, 38854, 38856, [12209, 38859], 38876,
+ [12210, 38893], 40783, 38898, 31455, 38902, 38901, 38927, 38924, 38968,
+ 38948, 38945, 38967, 38973, 38982, 38991, 38987, 39019, {f: 3, c: 39023},
+ 39028, 39027, 39082, 39087, 39089, 39094, 39108, 39107, 39110, 39145,
+ 39147, 39171, 39177, 39186, 39188, 39192, 39201, {f: 2, c: 39197}, 39204,
+ 39200, 39212, 39214, {f: 2, c: 39229}, 39234, 39241, 39237, 39248, 39243,
+ {f: 2, c: 39249}, 39244, 39253, {f: 2, c: 39319}, 39333, {f: 2, c: 39341},
+ 39356, 39391, 39387, 39389, 39384, 39377, {f: 2, c: 39405},
+ {f: 2, c: 39409}, 39419, 39416, 39425, 39439, 39429, 39394, 39449, 39467,
+ 39479, 39493, 39490, 39488, 39491, 39486, 39509, 39501, 39515, 39511,
+ 39519, 39522, 39525, 39524, 39529, 39531, 39530, 39597, 39600, 39612,
+ 39616, 39631, 39633, {f: 2, c: 39635}, 39646, [12221, 39647],
+ {f: 2, c: 39650}, 39654, 39663, 39659, 39662, 39668, 39665, 39671, 39675,
+ 39686, 39704, 39706, 39711, {f: 2, c: 39714}, [12222, 39717],
+ {f: 4, c: 39719}, 39726, [12223, 39727], [12224, 39730], 39748, 39747,
+ 39759, {f: 2, c: 39757}, 39761, 39768, 39796, 39827, 39811, 39825,
+ {f: 2, c: 39830}, {f: 2, c: 39839}, 39848, 39860, 39872, 39882, 39865,
+ 39878, 39887, {f: 2, c: 39889}, 39907, 39906, 39908, 39892, 39905, 39994,
+ 39922, 39921, 39920, 39957, 39956, 39945, 39955, 39948, 39942, 39944,
+ 39954, 39946, 39940, 39982, 39963, 39973, 39972, 39969, 39984, 40007,
+ 39986, 40006, 39998, 40026, 40032, 40039, 40054, 40056, 40167, 40172,
+ 40176, 40201, 40200, 40171, 40195, 40198, 40234, 40230, 40367, 40227,
+ 40223, 40260, 40213, 40210, 40257, 40255, 40254, 40262, 40264,
+ {f: 2, c: 40285}, 40292, 40273, 40272, 40281, 40306, 40329, 40327, 40363,
+ 40303, 40314, 40346, 40356, 40361, 40370, 40388, 40385, 40379, 40376,
+ 40378, 40390, 40399, 40386, 40409, 40403, 40440, 40422, 40429, 40431,
+ 40445, {f: 2, c: 40474}, 40478, [12228, 40565], 40569, 40573, 40577, 40584,
+ {f: 2, c: 40587}, 40594, 40597, 40593, 40605, [12230, 40613], 40617, 40632,
+ 40618, 40621, 38753, 40652, {f: 3, c: 40654}, 40660, 40668, 40670, 40669,
+ 40672, 40677, 40680, 40687, 40692, {f: 2, c: 40694}, [12235, 40697],
+ {f: 2, c: 40699}, [12236, 40701], {f: 2, c: 40711}, 30391, 40725, 40737,
+ 40748, 40766, [12241, 40778], [12242, 40786], 40788, 40803,
+ {f: 3, c: 40799}, {f: 2, c: 40806}, 40812, 40810, 40823, 40818, 40822,
+ 40853, [12244, 40860], [12245, 40864], 22575, 27079, 36953, 29796, 0,
+ {f: 76, c: 9472}, {f: 20, c: 9312}, {f: 10, c: 8544}, 13129, 13076, 0,
+ 13133, 0, 13095, 0, 13110, 13137, 0, 13069, 13094, 0, 13099, 13130, 0,
+ {f: 3, c: 13212}, {f: 2, c: 13198}, 13252, 13217, 12317, 12319, 8470,
+ 13261, 0, {f: 5, c: 12964}, {f: 2, c: 12849}, 12857, 13182, 13181, 13180,
+ 8750, 8721, {s: 3}, 8735, 8895, 0, 0, 21854, {s: 7}, 167133, 0, 0, 28976,
+ 0, 40407, {s: 4}, 64054, 0, 0, 22169, 15694, {s: 4}, 20448, 0, 0, 36544, 0,
+ 194797, {s: 4}, 153716, 32363, 33606, 167670, {s: 3}, 40572, 0, 0, 26171,
+ 0, 40628, {s: 4}, 26629, {s: 5}, 23650, 0, 194780, 0, 32353, 0, 0, 64070,
+ {s: 5}, 34083, 37292, {s: 7}, 34796, {s: 8}, 25620, 0, 0, 39506, {s: 4},
+ 64074, 0, 194692, {s: 4}, 31774, {s: 6}, 64016, 25681, 0, 0, 63980, 22625,
+ 39002, 0, 194679, {s: 3}, 31153, 0, 28678, {s: 9}, 22218, {s: 3}, 21085, 0,
+ 28497, 37297, {s: 10}, 64106, {s: 6}, 38960, 0, 40629, {s: 9}, 33802,
+ 63939, {f: 2, c: 63890}, 63897, 0, 34847, 194575, 0, 194771, 194584,
+ {s: 7}, 137754, 23643, {s: 4}, 25890, 0, 0, 26618, 0, 26766, 0, 148432,
+ 194848, {s: 21}, 34110, {s: 15}, 30562, {s: 12}, 65075, 0,
+ {f: 2, c: 65073}, {s: 4}, 65072, {f: 2, c: 65077}, {f: 2, c: 65081}, 0, 0,
+ {f: 2, c: 65079}, {f: 2, c: 65087}, {f: 2, c: 65085}, {f: 4, c: 65089},
+ {f: 2, c: 65083}, {s: 41}, {f: 3, c: 12436}, 0, 0, 22099, {s: 41}, 65508,
+ 65287, 65282, 0, 9665, 9655, 8681, 8679, 8678, 8680, 9634, 9831, 9825,
+ 9828, 9826, 13216, 13218, {f: 2, c: 13220}, 13207, 8467, 13208, 13235,
+ 13234, 13233, 13232, {f: 3, c: 13189}, 13259, 13200, 13268, 13206, 13090,
+ 13078, 13080, 13077, 13059, 13091, 13143, 13122, 13113, 13115, 13056,
+ 13105, 13127, 13086, 13098, 0, 13183, 8481, 9742, 12342, 12320, {s: 3},
+ {f: 9, c: 9352}, {f: 20, c: 9332}, 12881, {f: 10, c: 8560},
+ {f: 10, c: 12882}, {f: 26, c: 9372}, 12867, 12861, 12863, 12852, 12856,
+ 12851, 12860, 12866, 12862, 12854, 12853, 12859, 12864, 12858, 12976,
+ 12973, 12969, 12975, 12948, 12970, 12952, 12971, 12946, 12945, 12947,
+ 12972, 12974, 12950, {s: 8}, {f: 3, c: 9131}, 0, {f: 3, c: 9127}, 0, 13260,
+ 13061, 0, 0, 13215, 13219, 13222, 0, 0, 12958, {f: 2, c: 13192}, 13256,
+ 8749, 0, 12848, {f: 6, c: 12842}, 12855, 12865, 10145, {s: 3}, 9673, 9824,
+ 9829, 9827, 9830, {f: 4, c: 9728}, 9758, {f: 2, c: 9756}, 9759, 12953,
+ 9450, {f: 2, c: 8554}, {s: 3}, {f: 8, c: 9601}, 9615, 9614, 9613, 9612,
+ 9611, 9610, 9609, {f: 2, c: 9620}, {f: 2, c: 9581}, 9584, 9583, 9552, 9566,
+ 9578, 9569, {f: 2, c: 9698}, 9701, 9700, 0, 0, {f: 3, c: 9585}, {s: 20},
+ 20956, 29081, {f: 9, c: 10102}, {s: 3}, {f: 2, c: 8570}, {s: 3}, 8575,
+ 8458, 8457, 0, 0, 12292, 8646, {f: 2, c: 8644}, 0, {f: 4, c: 12535}, 0, 0,
+ 12957, {s: 3}, 13179, {s: 3}, 13107, 13134, {s: 30}, 32394, 35100, 37704,
+ 37512, 34012, 20425, 28859, 26161, 26824, 37625, 26363, 24389,
+ [12033, 20008], 20193, 20220, 20224, 20227, 20281, 20310, 20370, 20362,
+ 20378, 20372, 20429, 20544, 20514, 20479, 20510, 20550, 20592, 20546,
+ 20628, 20724, 20696, 20810, 20836, 20893, 20926, 20972, 21013, 21148,
+ 21158, 21184, 21211, 21248, 0, 21284, 21362, 21395, 21426, 21469, 64014,
+ 21660, 21642, 21673, 21759, 21894, 22361, 22373, 22444, 22472, 22471,
+ 64015, 0, 22686, 22706, 22795, 22867, 22875, 22877, 22883, 22948, 22970,
+ 23382, 23488, 29999, 23512, 0, 23582, 23718, 23738, 23797, 23847, 23891, 0,
+ 23874, 23917, {f: 2, c: 23992}, 24016, 24353, 24372, 24423, 24503, 24542,
+ 24669, 24709, 24714, 24798, 24789, 24864, 24818, 24849, 24887, 24880,
+ 24984, 25107, 25254, 25589, 25696, 25757, 25806, 25934, 26112, 26133,
+ 26121, 26158, 0, 26148, 26213, 26199, 26201, 64018, 26227, 26265, 26272,
+ 26290, 26303, 26362, 26382, 0, 26470, 26555, 26706, 26560, 0, 26692, 26831,
+ 64019, 26984, 64020, 27032, 27106, 27184, 27243, 27206, 27251, 27262,
+ 27362, 27364, 27606, 27711, 27740, 27782, 27759, 27866, 27908, 28039,
+ 28015, 28054, 28076, 28111, 28152, 28146, 28156, 28217, 28252, 28199,
+ 28220, 28351, 28552, 28597, 28661, 28677, 28679, 28712, 28805, 28843,
+ 28943, 28932, 29020, {f: 2, c: 28998}, 0, 29121, 29182, 29361, 29374,
+ 29476, 64022, 29559, 29629, 29641, 29654, 29667, 29650, 29703, 29685,
+ 29734, 29738, 29737, 29742, 0, 29833, 29855, 29953, 30063, 30338, 30364,
+ 30366, 30363, 30374, 64023, 30534, 21167, 30753, 30798, 30820, 30842,
+ 31024, {f: 3, c: 64024}, 31124, 64027, 31131, 31441, 31463, 64028, 31467,
+ 31646, 64029, 32072, 0, 32183, 32160, 32214, 32338, 32583, 32673, 64030,
+ 33537, 33634, 33663, 33735, 33782, 33864, 33972, 34131, 34137, 34155,
+ 64031, 34224, {f: 2, c: 64032}, 34823, 35061, 35346, 35383, 35449, 35495,
+ 35518, 35551, 64034, 35574, 35667, 35711, 36080, 36084, 36114, 36214,
+ 64035, 36559, 0, 64037, 36967, 37086, 64038, 37141, 37159, 37338, 37335,
+ 37342, {f: 2, c: 37357}, {f: 2, c: 37348}, 37382, 37392, 37386, 37434,
+ 37440, 37436, 37454, 37465, 37457, 37433, 37479, 37543, {f: 2, c: 37495},
+ 37607, 37591, 37593, 37584, 64039, 37589, 37600, 37587, 37669, 37665,
+ 37627, 64040, 37662, 37631, 37661, 37634, 37744, 37719, 37796, 37830,
+ 37854, 37880, 37937, 37957, 37960, 38290, 0, 64041, 38557, 38575, 38707,
+ 38715, 38723, 38733, 38735, [12205, 38737], 0, 38999, 39013,
+ {f: 2, c: 64042}, 39207, 64044, 39326, 39502, 39641, 39644, 39797, 39794,
+ 39823, 39857, 39867, 39936, 40304, 40299, 64045, 40473, 40657, {s: 636},
+ 8364, 8486, 0, 0, 64256, {f: 2, c: 64259}, 257, 299, 363, 275, 333, 256,
+ 298, 362, 274, 332, {f: 4, c: 8539}, {f: 2, c: 8531}, 8304,
+ {f: 6, c: 8308}, {f: 10, c: 8320}, 461, 282, 0, 7868, 463, 0, 296, 465, 0,
+ 467, 366, 360, 462, 283, 0, 7869, 464, 0, 297, 466, 0, 468, 367, 361, 593,
+ 8049, 8048, 509, 0, 596, 0, 0, 601, 0, 0, 602, 0, 0, 603, 8051, 8050, 0,
+ 331, 629, 652, 0, 0, 658, 643, 720, {s: 682}, {f: 10, c: 12832}, {s: 108},
+ {f: 4, c: 12892}, {f: 15, c: 12977}, {s: 50}, {f: 26, c: 9424},
+ {f: 26, c: 9398}, {s: 48}, {f: 47, c: 13008}, 0, {f: 10, c: 12928}, 12944,
+ {f: 6, c: 12938}, 0, 12959, {s: 6}, {f: 2, c: 12960}, 12955, 12954, 12963,
+ 12962, 12951, 0, 12956, 12949, {s: 6}, 9676, {s: 11}, 10111,
+ {f: 10, c: 9451}, {s: 510}, 8414, {s: 815}, 13274, {s: 3}, 8448, 13250, 0,
+ 0, 8453, 0, 13169, 0, 0, 13197, 13211, {s: 3}, {f: 2, c: 13271}, {s: 3},
+ {f: 2, c: 13057}, 13060, 13062, 0, 13064, 0, 13063, 13066, 0, 13065, 0,
+ 13067, 0, 13068, {f: 6, c: 13070}, 0, 13079, 0, 13081, 0, {f: 4, c: 13082},
+ {f: 3, c: 13087}, 13092, 0, 13093, 0, 0, {f: 2, c: 13096}, 0, 13101, 0, 0,
+ {f: 3, c: 13102}, 13106, 0, 0, {f: 2, c: 13108}, 13116, {s: 3}, 13111, 0,
+ 13112, 13114, 13117, 13121, {f: 3, c: 13118}, {f: 4, c: 13123}, 13128,
+ {f: 2, c: 13131}, {f: 2, c: 13135}, 0, 0, 13138, 13140, 0, 0, 13139,
+ {f: 2, c: 13141}, {s: 132}, 8501, 976, 8714, 8463, 0, 981, 987, 977, 0,
+ {f: 2, c: 9832}, 9836, {s: 5}, 12347, 0, {f: 3, c: 12339}, 8252, 8265,
+ {s: 5}, 8723, 0, 8771, {f: 2, c: 8818}, {s: 6}, {f: 2, c: 12312},
+ {f: 2, c: 65375}, {s: 10}, 9115, {f: 2, c: 9117}, 9120, {s: 4}, 9121,
+ {f: 2, c: 9123}, 9126, {s: 12}, [9116, 9119, 9122, 9125, 9130], {s: 8},
+ 9986, 0, 0, 12349, 0, 12447, 0, 0, 8709, 8864, 8854, 8856, 8853, 8855,
+ {s: 4}, 9664, 9654, {s: 4}, 8656, 8596, {f: 2, c: 8600}, {f: 2, c: 8598},
+ 8652, 8651, {s: 10}, 12336, 8967, {s: 8}, 10048, 10047, {s: 7}, 9643, 0,
+ 9642, 0, 10010, {s: 12}, 9702, {s: 4}, 10070, {s: 379}, {f: 2, c: 65093},
+ {s: 679}, 64103, 64098, 32227, [12232, 40643], 28331, 64082, 64061, 64069,
+ 64062, 27114, 28212, 64096, 64071, 64056, 64066, 64078, 34395, 64105,
+ 64052, 64099, 25581, 25802, 30799, 64084, 63856, 64077, 64097, 64072,
+ 64076, {f: 2, c: 64091}, 64081, 64067, 64090, 28041, 29376, 0, 194885,
+ 64086, 64080, 64049, 64059, 24034, 64063, 64101, 21373, 64055, 64095,
+ 24501, 64064, 0, 64083, 0, 64085, 64104, 64068, 64089, 26202, 64053, 64075,
+ 64100, 64065, 64048, 0, 64057, 64051, 27493, 64058, 27599, 64050, 25150,
+ 64079, 63773, 63964, 63798, 28122, 63952, 26310, 27511, 64087, 37706, 0,
+ 37636, {s: 120}, 133390, {s: 120}, 35999, 11991, [11965, 158033], {s: 5},
+ 37555, 38321, 0, 0, 194812, {s: 13}, 194965, {s: 8}, 194794, 0, 26478,
+ 11974, 0, 194594, {s: 13}, 13314, 0, 0, 26083, {s: 4}, 134071, {s: 10},
+ 171339, 0, 194611, 24378, {s: 8}, 11945, 0, 20465, {s: 7}, 63753, {s: 7},
+ 11964, 0, 0, 194732, 26435, {s: 3}, 133732, 35329, 25142, 0, 0, 21555,
+ 23067, {s: 3}, 25221, 0, 0, 194819, {s: 6}, 21567, {s: 9}, 27506, {s: 4},
+ 29986, 19256, 0, 0, 24063, {s: 6}, 194827, 29626, 134047, {s: 3}, 194600,
+ 0, 194849, {s: 5}, 194623, {s: 16}, 194675, {f: 2, c: 11916}, 23577,
+ {s: 3}, 131083, 23426, 194642, {s: 5}, 11997, [11999, 39136],
+ [11998, 169599], 14221, 0, [11927, 14586], 0, 194887, 0, [11909, 20155],
+ 131490, {s: 7}, 13599, 0, 194738, 0, 0, [11971, 35200], {s: 4}, 31237,
+ {s: 4}, 35498, 0, 32085, 0, 28568, {s: 7}, 25591, 30246, {s: 4},
+ [11978, 163767], {s: 5}, 146686, {s: 5}, 13351, 0, 0, 33067, 0, 0, 194842,
+ {s: 5}, 11950, {s: 5}, 194714, {s: 3}, 194831, {s: 19}, 22305, 135741,
+ 194586, 0, 64003, {s: 7}, 21534, 15240, 20839, {s: 4}, 63839, {s: 9},
+ 20023, {s: 13}, [11946, 150804], 24421, 23020, 194658, 0, 24217, {s: 46},
+ 13416, {s: 8}, 21200, {s: 9}, 26625, 0, 195024, 195039, {s: 5}, 153215, 0,
+ 0, 11959, {s: 4}, 36534, 63775, {s: 3}, 63875, {s: 5}, 31867, 63906, 0,
+ 63898, 0, [11961, 32770], 157360, {s: 4}, [11911, 132648], 0, 0, 131210,
+ 194604, [11915, 13630], {s: 4}, 21589, 0, 22841, 0, 0, 23414, 194669,
+ 23572, 14306, 23782, 0, 20040, 0, 0, 194742, {s: 4}, 158105, 25371, 0, 0,
+ 26211, 0, 194779, 0, 0, 27126, 27014, {s: 3}, 27596, 0, 28183, 0, 0, 27818,
+ {s: 3}, [11942, 20012], 0, 0, 29935, 30069, 30188, 30286, 16305, 30570,
+ 30633, {s: 6}, 31571, 0, 0, 16996, {s: 3}, 194924, 0, 0, 32328, {s: 5},
+ 11955, {s: 4}, 33089, 17491, 0, [11966, 33401], [11967, 64094],
+ [11968, 64093], 0, 20857, 33626, {s: 3}, 17701, 0, 34292, 131248, {s: 4},
+ 34429, 0, 13358, 35014, {s: 6}, 18406, {s: 8}, 36808, {s: 19}, 166279, 0,
+ 0, 167447, 0, 0, 38969, {s: 6}, 39432, {s: 4}, 39903, {s: 10}, 148206,
+ {s: 5}, 21385, 0, 64017, 194785, 0, 146622, 132625, 0, {f: 2, c: 19972},
+ 19999, 20011, {f: 2, c: 20015}, {f: 2, c: 20032}, 20036, [11907, 20058],
+ 20095, 20109, 20118, 20153, 20176, 20192, 20221, 20223, 20235, 20245,
+ 20320, 20283, 20297, 20308, 20346, {f: 2, c: 20349}, 20375, 20414, 20431,
+ 20477, {f: 2, c: 20480}, 20496, 20507, 20519, 20526, 20567, 20582, 20586,
+ 20539, 20623, 20630, 20636, 20684, 20710, 20713, 20719, 20744, 20747,
+ 20752, 20763, 20766, 20831, 20897, 20924, 0, 20974, 20980, 20993,
+ [11913, 20994], 21011, 21065, 21089, 21094, 21139, 21192, 21232,
+ {f: 2, c: 21258}, 21310, 21324, 21323, 21345, 21356, 21419, 21466, 21478,
+ 21493, 21543, 21581, 21606, 21611, 21620, 21645, 21654, 21665, 21677,
+ 21689, 21695, 21702, 21709, 21774, 21803, 21813, 21834, 21856, 0, 21896,
+ 21902, 22024, {f: 2, c: 22030}, 22071, 22079, 22089, 22091, 22095, 22118,
+ 22121, 22127, {f: 2, c: 22129}, 22165, 22170, {f: 2, c: 22188}, 22193,
+ 22217, 22237, 22244, 22282, 22293, 22307, 22319, {f: 2, c: 22323}, 22348,
+ 22384, 22412, 22428, 22456, 22502, 22509, {f: 2, c: 22517}, 22527, 22537,
+ 22560, 22578, 22652, 22656, 22697, 22734, 22736, 22740, 22746, 22761,
+ 22796, 22820, 22831, 22881, 22893, 22986, 22994, 23005, {f: 2, c: 23011},
+ 23044, 23052, 23075, 23111, 23125, 23139, 23149, 23166, 23198, 23207,
+ 23212, 23219, 23264, 23296, 23321, 23333, 23341, 23361, 23420,
+ {f: 2, c: 23422}, 23434, [11919, 23587], 23595, 23600, 23651, 23657, 23676,
+ 23755, 23762, 23796, 23844, 23846, 23875, 23878, 23882, 23954, 23956,
+ 23961, 23968, 24024, 24032, 24056, 24064, 24082, {f: 2, c: 24084}, 24088,
+ 24110, 24152, {f: 2, c: 24171}, 24232, 24234, {f: 2, c: 24254}, 0, 24274,
+ 24327, 24334, {f: 2, c: 24348}, 24354, 24360, 24374, 24379, 24384,
+ [12089, 24400], 24408, 24420, 24457, 24476, 24487, 24484, 24495, 24504,
+ [11926, 24516], 24521, 24545, 24553, 24557, 24572, 24599, 24602, 24627,
+ 24673, 24703, 24734, 24740, 24752, 24779, 24795, 24824, {f: 3, c: 24850},
+ 24860, 24956, 24973, 24991, 25000, 25026, 25055, 25109, 25129, 25155,
+ 25158, [11928, 25164], 25169, 25174, 25284, 25340, 25354, 25357, 25368,
+ 25401, {f: 2, c: 25410}, 25445, 25460, 25469, 25476, 25479, 25488, 25502,
+ 25553, 25564, 25609, 25616, 25634, 25684, 25691, 25709, 25723,
+ {f: 2, c: 25790}, 25829, 25847, 25851, 25860, 25878, 25881, 25927, 25959,
+ 25985, 25989, 26050, 26096, 26098, 26156, 26188, {f: 2, c: 26203}, 26209,
+ 26219, 0, 26276, 26312, 26348, 26373, 26387, 26419, 26440, 26444, 26486,
+ 26491, 26544, 26546, 26617, 26583, 26585, 26608, 26668, {f: 2, c: 26672},
+ 26715, 26738, 26741, 26746, 26756, 26789, 26802, 26832, 26838, 26856,
+ 26861, {f: 2, c: 26864}, 26876, 26897, 26899, 26933, 26939, 26967, 26979,
+ 26994, {f: 2, c: 27007}, 27046, 27053, 27063, {f: 2, c: 27094}, 27137,
+ 27151, 27157, 27176, 27188, 27198, 27205, {f: 2, c: 27216}, 27222, 27227,
+ 27267, 27273, 27281, {f: 3, c: 27293}, 27356, 27367, 27372, 27422, 27428,
+ 27445, 27462, 27478, 27488, 27522, 27582, 27617, 27633, 27664, 27699,
+ [11937, 27701], 11938, 27737, 27766, 27771, 27781, 27797, 27804, 27856,
+ 27860, 27862, 27872, {f: 2, c: 27883}, 27886, 27914, 27918, 27921, 27950,
+ 27991, 27998, 28005, 28034, 28095, 28100, 28106, 28118, 28137, 28194,
+ 28241, 28359, 28362, 28366, 28413, 28442, 28458, 28463, 28467, 28506,
+ 28510, 28514, 28541, 28555, 28557, 28562, 28564, 28570, {f: 2, c: 28583},
+ 28598, 28634, 28638, 0, 28729, 28732, 0, 28756, {f: 2, c: 28765}, 28772,
+ [11939, 28780], 28798, 28801, 28821, 28855, {f: 2, c: 28883}, 28888, 28892,
+ 28935, 28960, 28977, 29002, 29010, 29024, 29049, 29074, 0, 29131, 29139,
+ 29142, 29184, 29213, 29227, 29240, 29249, 29267, {f: 2, c: 29269}, 29276,
+ 29325, [11944, 29357], 29364, 29383, 29435, {f: 2, c: 29444}, 29480, 29489,
+ 29507, 29548, 29564, 29571, {f: 2, c: 29573}, 29589, {f: 3, c: 29598},
+ 29606, 29611, 29621, 29623, 29628, 29647, 29657, 29673, 29684, 29693,
+ 29700, 29706, {f: 2, c: 29722}, 29732, 29736, 29740, {f: 3, c: 29743},
+ 29753, 29764, 29767, 29771, 29773, 29777, 29783, 29798, 29803, 29809,
+ 29824, {f: 3, c: 29829}, 29840, 29848, 29852, 29856, 29859, 29864, 29867,
+ 29877, 29887, 29896, 29914, 29918, 30030, 30073, 30081, 30096,
+ [12135, 30098], 30099, 30132, 30180, 30201, 30208, 30218, {f: 2, c: 30229},
+ 30233, 30238, 30253, 30261, 30275, 30283, 30309, 30317, 30319, 30321,
+ 30324, {f: 2, c: 30372}, 30405, 30412, 30444, 30460, 30516, 30518, 30556,
+ {f: 2, c: 30559}, 30578, 30589, 30613, 30634, 30694, 30704, 30708, 30726,
+ 30754, {f: 2, c: 30765}, 30768, 30773, 30824, 30878, 30920, 30924, 30926,
+ 30948, {f: 2, c: 30944}, 30962, 30967, 30971, 31025, 0, [11949, 31035],
+ 31037, 31045, {f: 2, c: 31067}, 31115, 31126, 31128, [12145, 31160], 31163,
+ 31178, 31194, 31235, 31241, 31249, 31262, 31277, 31289, 31301, 31308,
+ 31325, 0, 31341, 31352, 31392, 31395, 31411, {f: 2, c: 31419}, 31430,
+ 31495, 31508, 31527, 31537, 31559, 31566, 31584, 31593, 31597, 31602,
+ 31633, 31663, 31703, 31705, 31755, 31759, 31776, 31782, 31793, 31798,
+ 31825, 31833, 31847, 31854, 31856, 31932, 31935, {f: 2, c: 31944}, 31959,
+ 31961, 31965, 31979, {f: 3, c: 32007}, 32019, 32029, 32035, 32065, 32083,
+ 32089, 32093, 32122, 32134, {f: 2, c: 32139}, 32204, 32235, 32241, 32249,
+ 32264, 32273, 32277, 32288, 32327, 32354, 32366, 32371, 32397, 32401,
+ 32408, 32580, 32591, [11947, 11954, 32594], [11953, 32595], 32609, 32657,
+ 32703, 32718, 32735, 32741, 32748, {f: 2, c: 32750}, 32762, 32782, 32785,
+ 32788, 32804, 32806, 32826, 32828, 32864, 32881, 32885, 32926, 32934,
+ 32939, {f: 2, c: 32983}, 33046, 33048, 33082, 33098, 33100, 33153, 33156,
+ 33204, 33231, 33273, 33283, 33313, 33330, 33332, 33350, 33355, 33359,
+ 33422, 33454, 33463, 33470, 33478, 33534, 33603, 33617, 33621, 33670,
+ 33677, 33682, 33688, 33705, {f: 2, c: 33727}, 33770, 33807, 33809, 33866,
+ 33910, 33960, 33967, 33984, 33986, 34032, 34045, 34060, 34100, 34142,
+ 34191, 34231, 34254, 34221, 34322, 34345, 34386, 34403, 34412, 34415,
+ 34426, 34445, 34449, 34456, {f: 2, c: 34471}, 34554, 34557, 34571, 34579,
+ 34585, 34590, 34600, 34622, 34673, 34696, 34713, {f: 2, c: 34732}, 34741,
+ 34774, 34795, 34797, 34817, 0, 34822, 34827, 34836, 34844, 34902, 34911,
+ [11970, 34916], 34968, 34986, {f: 2, c: 35005}, 35018, 35026, 35035,
+ {f: 2, c: 35056}, 35078, {f: 3, c: 35096}, 35111, 35120, 35134, 35195,
+ 35284, 35286, 35301, 35313, 35335, 35343, 35349, 35362, 35406, 35455,
+ 35572, 35615, 35639, {f: 2, c: 35651}, 35668, 35740, 35742, 35911, 35924,
+ 35955, 36004, 36057, 36065, 36088, 36094, 36123, 36201, 36204, 36228,
+ 36237, 36245, 36262, 36294, 36302, 36324, 36332, 36384, 36427, 36460,
+ 36464, 36474, 36498, 36526, 36531, 36561, 36564, 36601, 36631, 36662,
+ 36774, [12193, 36789], [11981, 36790], 0, 36832, 36836, 36854, 36866,
+ 36908, 36932, 37000, 37013, 37017, 37019, 37026, 37044, 37079, 37085,
+ 37108, 37143, 37148, 37169, 37178, 37181, 37192, 37211, 37217, 37220,
+ 37262, 37278, 37288, {f: 2, c: 37293}, 37298, 37308, 37360, 37367, 37371,
+ 37383, 37416, 37427, 37432, 37443, 37447, 37455, 37472, 37570,
+ {f: 2, c: 37579}, 37599, 37645, 37653, 37663, 37671, 37703, 37714, 0,
+ 37738, 37741, 37787, 37818, 37801, 37825, 37834, 37858, 37882, 37885,
+ 37903, 37940, 37951, 37973, 37995, 38002, [11986, 38264], 38310, 38313, 0,
+ 38324, 38333, 38362, [11983, 11990, 38429], 38465, 38488, 38532, 38564,
+ 38569, 38610, 195060, 38622, 38633, 38641, 38658, 38665, 38746, 38755,
+ 38766, 38771, 38810, 38818, {f: 2, c: 38837}, 38873, 38878, 38900, 38922,
+ 38926, 38942, 38947, 38955, 38974, {f: 2, c: 38994}, 39001, 39020, 39096,
+ 39098, 39103, 39112, 39141, {f: 2, c: 39218}, 39232, 39245, 39260, 39263,
+ 39345, {f: 2, c: 39353}, 39369, 39426, 39446, 39460, 39463,
+ {f: 2, c: 39469}, 39478, 39480, 39498, 39510, {f: 2, c: 39605}, 39673,
+ 39683, 39712, {f: 2, c: 39731}, 39795, 39801, 39847, 39873, 39879, 39895,
+ 39911, 39915, 39927, 39930, 39933, 39947, 39975, 39978, 39990, 40001,
+ 40019, 40035, 40048, 40055, 40194, 40258, 40263, 40291, 40297, 40316,
+ 40318, 40333, 40369, 40387, 40391, 40406, 40415, 40427, 40436, 40469,
+ 40477, 40612, 40616, 40620, 40679, 40686, 40720, 40722, 40727, 40729,
+ 40751, 40759, 40761, 40769, 40773, 40791, 40808, 40817, 40821, 40848,
+ 40852, 40866, 0, 13317, 194564, 22048, 24267, 11925, 0, 144954, 0, 28665,
+ 28390, 29107, [11940, 64073], {s: 4}, [11980, 64102], 0, 23986, 0, 20435,
+ 20697, 20720, 20931, 22134, 27220, 27905, 28112, 28226, 28377, 29668,
+ 29729, 30060, 30801, 34805, 144382, 29608, 15091, 13531, 17420, 16010, 0,
+ 0, 19432, 0, 16090, 15138, 0, 17786, 16531, 0, 18021, 16643, 17043, 18094,
+ 13448, 140809, {f: 3, c: 63584}, 63610, 63615, {s: 23}, {f: 2, c: 8836},
+ {f: 2, c: 8842}, 8713, 0, {f: 2, c: 8965}, {s: 9}, {f: 2, c: 8741},
+ {s: 14}, 8802, 0, 8773, 8776, {f: 2, c: 8822}, {s: 4}, 8487, {s: 209},
+ {f: 2, c: 8922}, 8533, 8984, {f: 2, c: 7742}, {f: 2, c: 504}, 470, 472,
+ 474, 476, 260, 728, 317, 346, 350, 356, 377, 379, 261, 731, 318, 347, 711,
+ 351, 357, 378, 733, 380, 340, 258, 313, 262, 268, 280, 270, 323, 327, 336,
+ 344, 368, 354, 341, 259, 314, 263, 269, 281, 271, 273, 324, 328, 337, 345,
+ 369, 355, 729, 264, 284, 292, 308, 348, 364, 265, 285, 293, 309, 349, 365,
+ 625, 651, 638, 620, 622, 633, 648, 598, 627, 637, 642, 656, 635, 621, 607,
+ 626, 669, 654, 609, 624, 641, 295, 661, 660, 614, 664, 450, 595, 599, 644,
+ 608, 403, 616, 649, 600, 604, 606, 592, 623, 650, 612, 594, 653, 613, 674,
+ 673, 597, 657, 634, 615, 865, 712, 716, 721, 8255, 783, {f: 5, c: 741}, 0,
+ 0, 805, 812, 825, 796, {f: 2, c: 799}, 829, 809, 815, 734, 804, 816, 828,
+ 820, {f: 2, c: 797}, {f: 2, c: 792}, 810, {f: 2, c: 826}, 794, {s: 3},
+ {f: 2, c: 610}, 618, 628, 630, 632, 640, 655, 665, 668, 671, 688, 690, 695,
+ 704, {f: 2, c: 736}, {s: 6}, 8862, {s: 287}, 12348, 12543, 0,
+ {f: 2, c: 12310}, 9838, 9835, {f: 2, c: 10548}, 10687, 0, 12448, 0,
+ {f: 2, c: 10746}, {s: 13}, 962, {f: 10, c: 9461}, {f: 2, c: 9750}, 9649,
+ {f: 10, c: 12784}, 0, {f: 6, c: 12794}, {f: 15, c: 9150}, 0, 0, 10003, 0,
+ 9251, 9166, {f: 4, c: 9680}, {f: 2, c: 8263}, 0, 8273, 8258,
+ {f: 16, c: 12688}, {s: 13}, {f: 2, c: 9136}, {f: 12, c: 9842},
+ {f: 2, c: 12441}, 8413, {s: 450}, 20296, 20319, 20330, 20332, 20494, 20504,
+ 20545, 20722, 20688, 20742, 20739, 20789, 20821, 20823, 13493, 20938,
+ 20962, 21079, 21196, 21206, 21243, 21276, 21347, 21405, 21522, 21631,
+ 21640, 21840, 21889, 21933, 21966, 22075, 22174, 22185, 22195, 22391,
+ 22396, 135963, 22479, 22500, 22628, 22665, 136302, 22738, 22752, 34369,
+ 22923, 22930, 22979, 23059, 23143, 23159, 23172, 23236, 137405, 23421,
+ 23443, 23570, 64060, 136884, 23674, 23695, 23711, 23715, 23722, 23760,
+ 138804, 23821, 23879, 23937, 23972, 23975, 24011, 24158, 24313, 24320,
+ 24322, 24355, 24381, 24404, 24445, 24589, 24596, 24600, 24629, 24647,
+ 24733, 24788, 24797, 24875, 25020, 25017, 25122, 25178, 25199, 25302,
+ 25468, 25573, 25721, 25796, 25808, 25897, 26013, 26170, 26146, 26155,
+ 26160, 26163, 26184, 143812, {f: 2, c: 26231}, 26253, 26299, 26331, 26344,
+ 26439, 26497, 26515, 26520, 26523, 26620, 26653, 26787, 26890, 26953,
+ 144836, 26946, 26980, 27045, 27087, 15286, 15299, 27113, 27125, 145215,
+ 27195, 145251, 27284, 27301, 15375, 27419, 27436, 27495, 27561, 27565,
+ 27607, 27647, 27653, 27764, 27800, 27899, 27846, 27953, 27961, 27967,
+ 27992, 28052, 28074, 28123, 28125, 28228, 28254, 28337, 28353, 28432,
+ 28505, 28513, 28542, 28556, 28576, 28604, 28615, 28618, 28656, 28750,
+ 28789, 28836, 28900, 28971, 28958, 28974, 29009, 29032, 29061, 29063,
+ 29114, 29124, 29205, 15935, 29339, 149489, 29479, 29520, 29542, 29602,
+ 29739, 29766, 29794, 29805, 29862, 29865, 29897, 29951, 29975, 16242,
+ 30158, 30210, 30216, 30308, 30337, 30365, 30378, 30390, 30414, 30420,
+ 30438, 30449, 30474, 30489, {f: 2, c: 30541}, 30586, 30592, 30612, 30688,
+ 152718, 30787, 30830, 30896, 152846, 30893, 30976, 31004, 31022, 31028,
+ 31046, 31097, 31176, 153457, 31188, 31198, 31211, 31213, 31365, 154052,
+ 31438, 31485, 31506, 31533, 31547, 31599, 31745, 31795, 155041, 31853,
+ 31865, 31887, 31892, 31904, 31957, 32049, 32092, 32131, 32166, 32194,
+ 32296, 32663, 32731, 32821, 32823, 32970, 32992, 33011, 33120,
+ {f: 2, c: 33127}, 33133, 33211, 33226, 33239, 17499, 33376, 33396, 158463,
+ 33441, {f: 2, c: 33443}, 33449, 33471, 33493, 33533, 33536, 33570, 33581,
+ 33594, 33607, 33661, 33703, 33743, 33745, 33761, 33793, 33798, 33887,
+ 33904, 33907, 33925, 33950, 33978, 159296, 34098, 34078, 34095, 34148,
+ 34170, 34188, 34210, 34251, 34285, 34303, {f: 2, c: 34308}, 34320, 159988,
+ 34328, 34360, 34391, 34402, 17821, 34421, 34488, 34556, 34695, 17898,
+ 34826, 34832, 35022, 161412, 35122, 35129, 35136, 35220, 35318, 35399,
+ 35421, 35425, 35445, 35536, 35654, 35673, 35689, 35741, 35913, 35944,
+ 36271, 36305, 36311, 36387, 36413, 36475, 164471, 18500, 36602, 36638,
+ 36653, 36692, 164813, 36840, 36846, 36872, 36909, 37015, 37043, 37054,
+ {f: 2, c: 37060}, 37063, 37103, 37140, 37142, {f: 2, c: 37154}, 37167,
+ 37172, 37251, 37361, 37705, {f: 2, c: 37732}, 37795, 37855, 37892, 37939,
+ 37962, 37987, 38001, 38286, 38303, 38316, 38326, 38347, 38352, 38355,
+ 18864, 38366, 38565, 38639, 38734, 38805, 38830, 38842, 38849, 38857,
+ 38875, 38998, 39143, 39256, 39427, 39617, 39619, 39630, 39638, 39682,
+ 39688, 19479, 39725, 39774, 39782, 39812, 39818, 39838, 39886, 39909,
+ 39928, 39971, {f: 2, c: 40015}, 40037, {f: 2, c: 40221}, 40259, 40274,
+ 40330, 40342, 40384, 40364, 40380, 172432, 40423, 40455, 40606, 40623,
+ 40855, 131209, 19970, 19983, 19986, 20009, 20014, 20039, 131234, 20049,
+ 13318, 131236, 20073, 20125, 13356, 20156, 20163, 20168, 20203, 20186,
+ 20209, 20213, 20246, 20324, 20279, 20286, 20312, 131603, {f: 2, c: 20343},
+ 20354, 20357, 20454, 20402, 20421, 20427, 20434, 13418, 20466, 20499,
+ 20508, 20558, 20563, 20579, 20643, 20616, {f: 2, c: 20626}, 20629, 20650,
+ 131883, 20657, {f: 2, c: 20666}, 20676, 20679, 20723, 131969, 20686,
+ 131953, 20692, 20705, 13458, 132089, 20759, 132170, 20832, 132361, 20851,
+ 20867, 20875, 13500, 20888, 20899, 20909, 13511, 132566, 20979, 21010,
+ 21014, 132943, 21077, 21084, 21100, 21111, 21124, 21122, 133127, 21144,
+ 133178, 21156, {f: 2, c: 21178}, 21194, 21201, 133305, 21239, 21301, 21314,
+ 133500, 133533, 21351, 21370, 21412, 21428, 133843, 21431, 21440, 133917,
+ {f: 2, c: 13661}, 21461, 13667, 21492, 21540, 21544, 13678, 21571, 21602,
+ 21612, 21653, 21664, 21670, 21678, 21687, 21690, 21699, 134469, 21740,
+ 21743, 21745, 21747, {f: 2, c: 21760}, 21769, 21820, 21825, 13734, 21831,
+ 13736, 21860, 134625, 21885, 21890, 21905, 13765, 21970, 134805, 134765,
+ 21951, 21961, 21964, 21969, 21981, 13786, 21986, 134756, 21993, 22056,
+ 135007, 22023, 22032, 22064, 13812, 22077, 22080, 22087, 22110, 22112,
+ 22125, 13829, 22152, 22156, 22173, 22184, 22194, 22213, 22221, 22239,
+ 22248, {f: 2, c: 22262}, 135681, 135765, 22313, 135803, {f: 2, c: 22341},
+ 22349, 135796, 22376, 22383, {f: 3, c: 22387}, 22395, 135908, 135895,
+ 22426, {f: 2, c: 22429}, 22440, 22487, 135933, 22476, 135990, 136004,
+ 22494, 22512, 13898, 22520, 22523, 22525, 22532, 22558, 22567, 22585,
+ 136132, 22601, 22604, 22631, {f: 2, c: 22666}, 22669, {f: 2, c: 22671},
+ 22676, 22685, 22698, 22705, 136301, 22723, 22733, 22754, {f: 2, c: 22771},
+ {f: 2, c: 22789}, 22797, 22804, 136663, 13969, 22845, 13977, 22854, 13974,
+ 158761, 22879, 136775, {f: 2, c: 22901}, 22908, 22943, 22958, 22972, 22984,
+ 22989, 23006, 23015, 23022, 136966, 137026, 14031, 23053, 23063, 23079,
+ 23085, 23141, 23162, 23179, 23196, {f: 2, c: 23199}, 23202, 23217, 23221,
+ 23226, 23231, 23258, 23260, 23269, 23280, 23278, 23285, 23304, 23319,
+ 23348, 23372, 23378, 23400, 23407, 23425, 23428, 137667, 23446, 23468,
+ {f: 2, c: 14177}, 23502, 23510, 14188, 14187, 23537, 23549, 14197, 23555,
+ 23593, 138326, 23647, {f: 2, c: 23655}, 23664, 138541, 138565, 138616,
+ 138594, 23688, 23690, 14273, 138657, 138652, 23712, 23714, 23719, 138642,
+ 23725, 23733, 138679, 23753, 138720, 138803, 23814, 23824, 23851, 23837,
+ 23840, 23857, 23865, 14312, 23905, 23914, 14324, 23920, 139038, 14333,
+ 23944, 14336, 23959, 23984, 23988, 139126, 24017, 24023, 139258, 24036,
+ 24041, 14383, 14390, 14400, 24095, 24126, 24137, 14428, 24150, 14433,
+ {f: 2, c: 24173}, 139643, 24229, 24236, 24249, 24262, 24281, 140062, 24317,
+ 24328, 140205, 24350, 24391, 24419, 24434, 24446, 24463, 24482, 24519,
+ 24523, {f: 3, c: 24530}, 24546, {f: 2, c: 24558}, 24563, 14615, 24610,
+ 24612, 14618, 24652, 24725, 24744, 141043, 24753, 24766, 24776, 24793,
+ 24814, 24821, 24848, 24857, 24862, 24890, 14703, 24897, 24902, 24928,
+ 141403, {f: 2, c: 24978}, 24983, 24997, 25005, 141483, 25045, 25053, 25077,
+ 141711, 25123, 25170, 25185, 25188, 25211, 25197, 25203, 25241, 25301,
+ 142008, 25341, 25347, 25360, {f: 2, c: 142159}, 25394, 25397,
+ {f: 2, c: 25403}, 25409, 25412, 25422, 142150, 25433, 142365, 142246,
+ 25452, 25497, 142372, 25492, 25533, {f: 2, c: 25556}, 25568,
+ {f: 2, c: 25579}, 25586, 25630, 25637, 25641, 25647, 25690, 25693, 25715,
+ 25725, 25735, 25745, 25759, {f: 2, c: 25803}, 25813, 25815, 142817, 25828,
+ 25855, 14958, 25871, 25876, 14963, 25886, 25906, 25924, 25940, 25963,
+ 25978, 25988, 25994, 26034, 26037, 26040, 26047, 26057, 26068, 15062,
+ 26105, 26108, 26116, 26120, 26145, 26154, 26181, 26193, 26190, 15082,
+ 143811, 143861, 143798, 26218, {f: 2, c: 26220}, 26235, 26240, 26256,
+ 26258, 15118, 26285, 26289, 26293, 15130, 15132, 15063, 26369, 26386,
+ 144242, 26393, 144339, 144338, 26445, 26452, 26461, 144336, 144356, 144341,
+ 26484, 144346, 26514, 144351, 33635, 26640, 26563, 26568, 26578, 26587,
+ 26615, 144458, 144465, 144459, 26648, 26655, 26669, 144485, 26675, 26683,
+ 26686, 26693, 26697, 26700, 26709, 26711, 15223, 26731, 26734, 26748,
+ 26754, 26768, 26774, 15213, {f: 3, c: 26776}, 26780, {f: 2, c: 26794},
+ 26804, 26811, 26875, 144612, 144730, 26819, 26821, 26828, 26841,
+ {f: 2, c: 26852}, 26860, 26871, 26883, 26887, 15239, 144788, 15245, 26950,
+ 26985, 26988, 27002, 27026, 15268, 27030, 27056, 27066, 27068, 27072,
+ 27089, 144953, 144967, 144952, 27107, {f: 2, c: 27118}, 27123, 15309,
+ 27124, 27134, 27153, 27162, 27165, 145180, {f: 2, c: 27186}, 27199, 27209,
+ 27258, 27214, 27218, 27236, 145164, 27275, 15344, 27297, 145252, 27307,
+ 27325, 27334, 27348, 27344, 27357, 145407, 145383, {f: 3, c: 27377}, 27389,
+ 145444, 27403, {f: 3, c: 27407}, 145469, 27415, 15398, 27439, 27466, 27480,
+ 27500, 27509, [11934, 27514], 27521, 27547, 27566, 146072, 27581,
+ {f: 3, c: 27591}, 27610, {f: 2, c: 27622}, 27630, 27650, 27658, 27662,
+ 27702, 146559, 27725, 27739, 27757, 27780, 27785, 15555, 27796, 27799,
+ 27821, 27842, 15570, 27868, 27881, 27885, 146688, 27904, 27940,
+ {f: 2, c: 27942}, 27751, 27951, 27964, 27995, 28000, 28016,
+ {f: 2, c: 28032}, 28042, 28045, 28049, 28056, 146752, 146938, 146937,
+ 146899, 28075, 28078, 28084, 28098, 27956, 28104, 28110, 28127, 28150,
+ 28214, 28190, 15633, 28210, {f: 2, c: 28232}, {f: 2, c: 28235}, 28239,
+ {f: 2, c: 28243}, 28247, 28259, 15646, 28307, 28327, 28340, 28355, 28469,
+ 28395, 28409, 28411, 28426, 28428, 28440, 28453, 28470, 28476, 147326,
+ 28498, 28503, 28512, 28520, 28560, 28566, 28606, 28575, 28581, 28591,
+ 15716, {f: 2, c: 28616}, 28649, 147606, 28668, 28672, 28682, 28707, 147715,
+ 28730, 28739, 28743, 28747, 15770, 28773, 28777, 28782, 28790, 28806,
+ 28823, 147910, 28831, 28849, 147966, 28908, 28874, 28881, 28931, 28934,
+ 28936, 28940, 15808, 28975, 29008, 29011, 29022, 15828, 29078, 29056,
+ 29083, 29088, 29090, {f: 2, c: 29102}, 148412, 29145, 29148, 29191, 15877,
+ 29236, 29241, 29250, 29271, 29283, 149033, {f: 2, c: 29294}, 29304, 29311,
+ 29326, 149157, 29358, 29360, 29377, 15968, 29388, 15974, 15976, 29427,
+ 29434, 29447, 29458, {f: 2, c: 29464}, 16003, 29497, 29484, 29491, 29501,
+ 29522, 16020, 29547, 149654, {f: 2, c: 29550}, 29553, 29569, 29578, 29588,
+ 29592, 29596, 29605, 29625, 29631, 29637, 29643, 29665, 29671, 29689,
+ 29715, 29690, 29697, 29779, 29760, 29763, 29778, 29789, 29825, 29832,
+ 150093, 29842, 29847, 29849, 29857, 29861, 29866, 29881, 29883, 29882,
+ 29910, 29912, 29931, 150358, 29946, 150383, 29984, 29988, 29994, 16215,
+ 150550, {f: 2, c: 30013}, 30016, 30024, 30032, 30034, 30066, 30065, 30074,
+ {f: 2, c: 30077}, 30092, 16245, 30114, 16247, 30128, 30135,
+ {f: 2, c: 30143}, 30150, 30159, 30163, 30173, {f: 2, c: 30175}, 30183,
+ 30190, 30193, 30211, 30232, 30215, 30223, 16302, 151054, 30227,
+ {f: 2, c: 30235}, 151095, 30245, 30248, 30268, 30259, 151146, 16329, 30273,
+ 151179, 30281, 30293, 16343, 30318, 30357, 30369, 30368, {f: 2, c: 30375},
+ 30383, 151626, 30409, 151637, 30440, 151842, 30487, 30490, 30509, 30517,
+ 151977, 16441, 152037, 152013, 30552, 152094, 30588, 152140, 16472, 30618,
+ 30623, 30626, 30628, {f: 2, c: 30686}, 30692, 30698, 30700, 30715, 152622,
+ 30725, 30729, 30733, 30745, 30764, 30791, 30826, 152793, 30858, 30868,
+ 30884, 30877, 30879, 30907, 30933, 30950, {f: 2, c: 30969}, 30974, 152999,
+ 30992, 31003, 31013, 31050, 31064, 16645, 31079, 31090, 31125, 31137,
+ 31145, 31156, 31170, 31175, {f: 2, c: 31180}, 31190, 16712, 153513, 153524,
+ 16719, 31242, 31253, 31259, 16739, 31288, 31303, 31318, 31321, 31324,
+ 31327, 31335, 31338, 31349, 31362, 31370, 31376, 31404, 154068, 16820,
+ 31417, 31422, 16831, 31436, 31464, 31476, 154340, 154339, 154353, 31549,
+ 31530, {f: 2, c: 31534}, 16870, 16883, 31615, 31553, 16878, 31573, 31609,
+ 31588, 31590, 31603, 154546, 16903, 31632, 31643, 16910, 31669, 31676,
+ 31685, 31690, 154699, 154724, 31700, 31702, 31706, 31722, 31728, 31747,
+ 31758, 31813, 31818, 31831, 31838, 31841, 31849, 31855, 155182, 155222,
+ 155237, 31910, 155234, {f: 2, c: 31926}, 155352, 31940, 155330, 31949,
+ 155368, 155427, 31974, 155484, 31989, 32003, 17094, 32018, 32030, 155616,
+ 155604, {f: 2, c: 32061}, 32064, 32071, 155660, 155643, 17110, 32090,
+ 32106, 32112, 17117, 32127, 155671, 32136, 32151, 155744, 32157, 32167,
+ 32170, 32182, 32192, 32215, 32217, 32230, 17154, 155885, 64088, 32272,
+ 32279, 32285, 32295, 32300, 32325, 32373, 32382, {f: 2, c: 32390}, 17195,
+ 32410, 17219, 32572, 32571, 32574, 32579, 13505, 156272, 156294,
+ {f: 2, c: 32611}, 32621, {f: 2, c: 32637}, 32656, 20859, 146702, 32662,
+ 32668, 32685, 156674, 32707, 32719, 32739, 32754, 32778, 32776, 32790,
+ 32812, 32816, 32835, 32870, 32891, 32921, 32924, 32932, 32935, 32952,
+ 157310, 32965, 32981, 32998, 33037, 33013, 33019, 17390, 33077, 33054,
+ 17392, 33060, 33063, 33068, 157469, 33085, 17416, 33129, 17431, 17436,
+ 33157, 17442, 33176, 33202, 33217, 33219, 33238, 33243, 157917, 33252,
+ 157930, 33260, 33277, 33279, 158063, 33284, 158173, 33305, 33314, 158238,
+ 33340, 33353, 33349, 158296, 17526, 17530, 33367, 158348, 33372, 33379,
+ 158391, 17553, 33405, 33407, 33411, 33418, 33427, {f: 2, c: 33447}, 33458,
+ 33460, 33466, 33468, 33506, 33512, 33527, {f: 2, c: 33543}, 33548, 33620,
+ 33563, 33565, 33584, 33596, 33604, 33623, 17598, 17620, 17587,
+ {f: 2, c: 33684}, 33691, 33693, 33737, 33744, 33748, 33757, 33765, 33785,
+ 33813, 158835, 33815, 33849, 33871, {f: 2, c: 33873}, {f: 2, c: 33881},
+ 33884, 158941, 33893, 33912, 33916, 33921, 17677, 33943, 33958, 33982,
+ 17672, {f: 2, c: 33998}, 34003, 159333, 34023, 34026, 34031, 34033, 34042,
+ 34075, {f: 2, c: 34084}, 34091, 34127, 34159, 17731, 34129,
+ {f: 2, c: 34145}, 159636, 34171, 34173, 34175, 34177, 34182, 34195, 34205,
+ 34207, 159736, {f: 2, c: 159734}, 34236, 34247, 34250, {f: 2, c: 34264},
+ 34271, 34273, 34278, 34294, 34304, 34321, 34334, 34337, 34340, 34343,
+ 160013, 34361, 34364, 160057, 34368, 34387, 34390, 34423, 34439, 34441,
+ {f: 2, c: 34460}, 34481, 34483, 34497, 34499, 34513, 34517, 34519, 34531,
+ 34534, 17848, 34565, 34567, 34574, 34576, 34591, 34593, 34595, 34609,
+ 34618, 34624, 34627, 34641, 34648, {f: 2, c: 34660}, 34674, 34684, 160731,
+ 160730, 34727, 34697, 34699, 34707, 34720, 160766, 17893, 34750, 160784,
+ 34753, 34766, 34783, 160841, 34787, {f: 2, c: 34789}, 34794, 34835, 34856,
+ 34862, 34866, 34876, 17935, 34890, 34904, 161301, 161300, 34921, 161329,
+ 34927, 34976, 35004, 35008, 161427, 35025, 35027, 17985, 35073, 161550,
+ 35127, 161571, 35138, 35141, 35145, 161618, 35170, 35209, 35216, 35231,
+ 35248, 35255, 35288, 35307, 18081, 35315, 35325, 35327, 18095, 35345,
+ 35348, 162181, 35361, 35381, 35390, 35397, 35405, 35416, 35502, 35472,
+ 35511, 35543, 35580, 162436, 35594, 35589, 35597, 35612, 35629, 18188,
+ 35665, 35678, 35702, 35713, 35723, {f: 2, c: 35732}, 35897, 162739, 35901,
+ 162750, 162759, 35909, 35919, 35927, 35945, 35949, 163000, 35987, 35986,
+ 35993, 18276, 35995, 36054, 36053, 163232, 36081, 163344, 36105, 36110,
+ 36296, 36313, 36364, 18429, 36349, 36358, 163978, 36372, 36374,
+ {f: 2, c: 36385}, 36391, 164027, 18454, 36406, 36409, 36436, 36450, 36461,
+ 36463, 36504, 36510, 36533, 36539, 164482, 18510, 164595, 36608, 36616,
+ 36651, 36672, 36682, 36696, 164876, 36772, 36788, 164949, 36801, 36806,
+ 64036, 36810, 36813, 36819, 36821, 36849, 36853, 36859, 36876, 36919,
+ 165227, 36931, 36957, {f: 2, c: 165320}, 36997, 37004, 37008, 37025, 18613,
+ 37040, 37046, 37059, 37064, 165591, 37084, 37087, 165626, 37110, 37106,
+ 37120, 37099, {f: 2, c: 37118}, 37124, 37126, 37144, 37150, 37175, 37177,
+ {f: 2, c: 37190}, 37207, 37209, 37236, 37241, 37253, 37299, 37302,
+ {f: 2, c: 37315}, 166217, 166214, 37356, 37377, {f: 2, c: 37398}, 166251,
+ 37442, 37450, 37462, 37473, 37477, 37480, 166280, {f: 2, c: 37500}, 37503,
+ 37513, 37517, 37527, 37529, 37535, 37547, {f: 2, c: 166330}, 37554,
+ {f: 2, c: 37567}, 37574, 37582, 37605, 37649, 166430, 166441, 37623, 37673,
+ 166513, 166467, 37713, 37722, 37739, 37745, 37747, 37793, 166553, 166605,
+ 37768, 37771, 37775, 37790, 37877, 166628, 166621, 37873, 37831, 37852,
+ 37863, 37897, {f: 2, c: 37910}, 37883, 37938, 37947, 166849, 166895, 37997,
+ 37999, 38265, 38278, {f: 2, c: 38284}, 167184, 167281, 38344, 167419,
+ 167455, 38444, {f: 2, c: 38451}, 167478, 38460, 38497, 167561, 38530,
+ 167659, 38554, 167730, 18919, 38579, 38586, 38589, 18938, 167928, 38616,
+ 38618, 38621, 18948, 38676, 38691, 18985, 38710, 38721, 38727, 38743,
+ 38747, 38762, 168608, 168625, 38806, 38814, {f: 2, c: 38833}, 38846, 38860,
+ 38865, 38868, 38872, 38881, 38897, 38916, 38925, 38932, 38934, 19132,
+ 169104, {f: 2, c: 38962}, 38949, 38983, 39014, 39083, 39085, 39088, 169423,
+ 39095, {f: 2, c: 39099}, 39106, 39111, 39115, 39137, 39139, 39146,
+ {f: 2, c: 39152}, 39155, 39176, 19259, 169712, {f: 2, c: 39190}, 169753,
+ {f: 3, c: 39194}, 169808, 39217, {f: 3, c: 39226}, 39233, 39238, 39246,
+ 39264, 39331, 39334, 39357, 39359, 39363, 39380, 39385, 39390, 170182,
+ 39408, 39417, 39420, 39434, 39441, 39450, 39456, 39473, 39492, 39500,
+ 39512, 19394, 39599, 19402, 39607, 19410, 39609, 170610, 39622, 39632,
+ 39634, 39637, 39648, 39653, 39657, 39692, 39696, 39698, 39702, 39708,
+ 39723, 39741, 19488, 39755, 39779, 39781, {f: 2, c: 39787},
+ {f: 2, c: 39798}, 39846, 39852, 171483, 39858, 39864, 39870, 39923, 39896,
+ 39901, 39914, 39919, 39918, 171541, 171658, 171593, 39958,
+ {f: 3, c: 39960}, 39965, 39970, 39977, 171716, 39985, 39991, 40005, 40028,
+ 171753, {f: 2, c: 40009}, 171739, 40020, 40024, 40027, 40029, 40031,
+ {f: 3, c: 40041}, {f: 2, c: 40045}, 40050, 40053, 40058, 40166, 40178,
+ 40203, [171982, 171991], 40209, {f: 2, c: 40215}, 172079, 19652, 172058,
+ 40242, 19665, 40266, 40287, 40290, 172281, 172162, 40307, {f: 2, c: 40310},
+ 40324, 40345, 40353, 40383, 40373, 40377, 40381, 40393, 40410, 40416,
+ 40419, 19719, 40458, 40450, 40461, 40476, 40571, 139800, 40576, 40581,
+ 40603, 172940, 40637, 173111, 40671, 40703, 40706, 19831, 40707, 40762,
+ 40765, 40774, 40787, 40789, 40792, 173553, 40797, 173570, 40809, 40813,
+ 40816, 173746, 11948, 13844, 14509, 15820, 16348, 17854, 17936, 19326,
+ 19512, 19681, 19980, {f: 2, c: 20003}, 20089, 20211, 20236, 20249, 20267,
+ 20270, 20273, 20356, 20382, 20407, 20484, 20492, 20556, 20575, 20578,
+ 20599, 20622, 20638, 20642, 20675, 20712, 20721, 20734, 20743,
+ {f: 3, c: 20748}, 20787, 20792, 20852, 20868, 20920, 20922, 20936, 20943,
+ 20945, {f: 2, c: 20947}, 20952, 20959, 20997, 21030, 21032, 21035,
+ {f: 2, c: 21041}, 21045, 21052, 21082, 21088, 21102, {f: 2, c: 21112},
+ 21130, 21132, 21217, 21225, 21233, 21251, 21265, 21279, 21293, 21298,
+ 21309, 21349, 21357, 21369, 21374, 21396, 21401, 21418, 21423, 21434,
+ 21441, {f: 2, c: 21444}, 21472, 21523, 21546, 21553, {f: 2, c: 21556},
+ 21580, 21671, 21674, 21681, 21691, 21710, 21738, 21756, 21765, 21768,
+ 21781, 21799, 21802, 21814, 21841, 21862, 21903, 21906, 21908, 21924,
+ 21938, 21955, 21958, 21971, 21979, 21996, 21998, 22001, 22006, 22008,
+ 22021, 22029, {f: 2, c: 22033}, 22060, 22069, 22073, 22093, 22100, 22149,
+ 22175, 22182, 22199, 22220, 22223, 22233, 22241, 22251, 22253, 22257,
+ 22279, 22284, {f: 2, c: 22298}, 22301, 22316, 22318, {f: 2, c: 22333},
+ 22367, 22379, 22381, 22394, 22403, 22423, 22446, 22485, 22503, 22541,
+ 22566, 22605, 22607, 22623, 22637, 22655, 22657, 22680, 22716, 22815,
+ 22819, 22873, 22905, 22935, 22959, 22963, 23007, 23025, 23032, 23218,
+ 23224, 23274, 23286, 23323, 23325, 23329, 23352, 23479, 23511, 23520,
+ 23583, 23594, 23596, 23606, 23641, 23644, 23661, 23773, 23809, 23860,
+ 23869, 23897, 23934, 23939, 24007, 24057, 24104, 24114, 24117, 24155,
+ 24168, 24170, 24183, 24192, 24203, 24243, 24253, 24273, {f: 2, c: 24276},
+ 24397, 24492, 24554, 24583, 24649, 24660, 24679, 24763, 24772, 24829,
+ 24842, 24854, 24874, 24886, 24926, 24932, 24955, 24957, 24959, 24989,
+ 25016, 25052, 25058, 25061, 25064, 25092, 25095, 25137, 25145, 25149,
+ 25210, 25232, 25256, 25306, 25332, 25366, 25386, 25398, 25414, 25419,
+ 25427, 25457, 25461, 25471, 25474, 25482, {f: 2, c: 25518}, 25578,
+ {f: 2, c: 25592}, 25618, 25624, 25632, 25636, 25642, 25653, 25661, 25663,
+ 25682, 25695, 25716, 25744, {f: 2, c: 25752}, 25772, 25779, 25837, 25840,
+ 25883, 25887, 25902, 25929, 25952, 26002, 26005, 26036, 26046, 26056,
+ 26062, 26064, 26079, 26238, {f: 2, c: 26251}, 26291, 26304, 26319, 26405,
+ 26421, 26453, 26496, 26511, 26513, 26532, 26545, 26549, 26558, 26664,
+ 26758, 26859, 26869, 26903, 26931, 26936, 26971, 26981, 27048, 27051,
+ 27055, 27109, 27121, 27210, 27221, 27239, 27249, 27311, {f: 2, c: 27336},
+ 27395, 27451, 27455, {f: 2, c: 27517}, 27568, 27639, 27641, 27652, 27657,
+ 27661, 27692, 27722, 27730, 27732, 27769, 27820, 27828, 27858, 28001,
+ 28028, 28089, 28144, 28229, 28275, 28283, 28285, 28297, 28348,
+ {f: 2, c: 28378}, 28454, 28457, 28464, 28551, 28573, 28590, 28599, 28685,
+ 28704, 28745, 28824, 28848, {f: 2, c: 28885}, 28997, 29106, 29172, 29207,
+ 29215, 29251, {f: 2, c: 29263}, 29274, 29280, 29288, 29303, 29316, 29385,
+ 29413, 29428, 29442, 29451, 29470, 29474, {f: 2, c: 29498}, 29517, 29528,
+ 29543, 29810, 29871, 29919, 29924, 29940, 29947, 29974, 29985, 30015,
+ 30046, 30105, 30116, 30145, 30148, 30156, 30167, 30172, 30177, 30191,
+ 30212, 30220, 30237, 30258, 30264, 30277, 30282, 30303, 30381, 30397,
+ 30425, 30443, 30448, 30457, 30464, 30478, 30498, 30504, 30511, 30521,
+ 30526, 30533, 30538, 30543, 30558, 30564, 30567, 30572, 30596,
+ {f: 2, c: 30604}, 30614, 30631, 30639, 30647, 30654, 30665, 30673, 30681,
+ 30705, 30775, 30812, 30846, 30872, 30881, 30897, 30899, 30921, 30931,
+ 30988, 31007, {f: 2, c: 31015}, 31039, 31042, 31060, 31083, 31100, 31147,
+ 31172, 31210, 31234, 31244, 31280, 31290, 31300, 31360, 31366, 31380,
+ 31413, 31421, 31486, 31531, 31607, 31648, 31660, 31664, 31720, 31730,
+ 31736, 31740, 31742, 31753, 31784, 31791, 31810, {f: 2, c: 31826},
+ {f: 3, c: 31835}, 31858, 31869, 31879, 31902, 31930, 31943, 31955, 31962,
+ 32060, 32077, 32130, 32133, 32141, 32145, 32158, 32179, 32185, 32208,
+ 32229, {f: 2, c: 32245}, 32303, 32310, 32324, 32367, 32376, 32385, 32573,
+ 32603, 32605, 32613, 32625, {f: 2, c: 32639}, 32651, 32674,
+ {f: 3, c: 32765}, 32775, 32781, 32798, 32825, 32904, 32910, 32975, 32980,
+ 33005, 33008, 33015, 33018, 33022, 33027, 33047, 33072, 33111, 33135,
+ 33139, 33163, 33168, 33179, 33182, 33227, 33237, {f: 2, c: 33245}, 33249,
+ 33263, 33270, 33280, 33291, {f: 2, c: 33299}, 33306, 33338, 33348, 33389,
+ 33412, 33417, 33425, 33450, 33456, 33488, 33514, 33519, 33526, 33622,
+ 33656, 33784, 33788, 33880, 33939, 33969, 33981, 34043, 34118, 34134,
+ 34141, 34181, 34200, 34370, 34374, 34496, 34580, 34594, 34606, 34617,
+ 34653, 34683, 34700, 34702, {f: 2, c: 34711}, 34718, 34723, 34734, 34751,
+ 34761, 34778, 34840, 34843, 34861, 34874, 34885, 34891, 34894, 34901,
+ 34906, 34926, {f: 3, c: 34970}, 35021, 35040, 35055, {f: 2, c: 35086},
+ 35110, 35125, 35162, 35164, 35179, 35184, 35196, 35237, 35253, 35260,
+ 35285, 35401, 35415, 35431, 35454, 35462, 35478, 35510, 35529, 35537,
+ 35549, 35564, 35573, 35590, 35599, 35601, 35653, 35666, 35693, 35704,
+ 35708, 35710, 35717, 35743, 35915, 35923, 35963, 36026, 36037, 36041,
+ 36050, 36076, 36085, 36087, 36097, 36099, 36119, 36124, 36206, 36241,
+ 36255, 36267, 36274, 36309, 36327, {f: 2, c: 36337}, 36340, 36353, 36363,
+ 36390, 36401, {f: 2, c: 36416}, 36429, 36431, 36444, 36449, 36457, 36465,
+ 36469, 36471, 36489, 36496, 36501, 36506, 36519, 36521, 36525, 36584,
+ 36592, 36615, 36632, 36645, 36647, 36652, 36661, 36666, 36675, 36679,
+ 36689, 36693, {f: 3, c: 36768}, 36773, 36868, 36891, 36911, 36940, 36955,
+ 36976, 36980, 36985, 37003, 37016, 37024, 37042, 37053, 37065, 37104,
+ 37125, 37157, 37210, 37223, 37242, 37258, 37265, 37269, 37296, 37307,
+ 37309, 37314, 37317, 37376, 37385, 37411, 37494, 37518, 37551,
+ {f: 2, c: 37563}, 37569, 37571, 37573, 37576, 37652, 37683, 37686, 37720,
+ 37759, 37762, 37770, 37819, 37836, 37862, 37881, 37890, {f: 2, c: 37901},
+ 37934, 37964, 38280, 38305, 38335, 38342, 38345, {f: 2, c: 38353}, 38368,
+ 38372, 38374, 38436, 38449, 38456, 38461, 38484, 38516, 38523, 38527,
+ 38529, 38531, 38537, 38550, 38574, 38659, 38683, {f: 2, c: 38689}, 38696,
+ 38705, 38759, 38774, 38781, 38783, 38809, 38815, 38828, 38841, 38861,
+ 38880, 38895, 38919, 38950, 38958, {f: 2, c: 39010}, 39092, 39109, 39170,
+ 39185, 39189, 39221, 39240, 39252, 39262, 39393, 39436, 39440, 39459,
+ 39489, 39505, {f: 2, c: 39613}, 39681, 39689, 39691, {f: 2, c: 39693},
+ 39705, 39733, 39752, 39765, 39784, 39808, 39814, 39824, 39837, 39856,
+ 39871, 39880, 39935, 39938, 39964, 39989, 40004, 40022, 40033, 40040,
+ 40240, 40253, 40298, 40315, 40421, 40425, 40435, 40570, {f: 3, c: 40578},
+ 40624, 40676, 40688, 40690, 40713, 40719, 40724, 40731, 40738, 40742,
+ {f: 2, c: 40746}, 40756, 40794, 40815, 40862, 40869, 131317, 151044,
+ 151538, 163187, 194581, 194630, 194713, 194726, 194789, 195038, 13790,
+ {s: 4}, 172722, 0, 0, 131416, {s: 4}, 132529, 0, 0, 132844, {s: 6}, 134488,
+ {s: 21}, 154060, {s: 9}, 14756, 14776, 142914, 0, 0, 14940, 0, 0, 143339,
+ 0, 0, 162228, 0, 15044, 15051, {s: 5}, 14981, {s: 8}, 15347, 27384, {s: 5},
+ 15665, {s: 9}, 147531, 0, 15936, 14497, {s: 34}, 158878, {s: 12}, 18207,
+ 162876, {s: 4}, 18462, {s: 71}, 39709, 39724, 20482, 20958, 21255, 23532,
+ 63784, 26142, 63785, 28746, 64021, 21857, 27706, 31328, 156492, 34819,
+ 38315, 38741, 171581, 173594],
+ 'Adobe-Korea1': [{f: 95, c: 32}, 8361, 8208, 169, 0, 0, [12288, 12644],
+ {f: 2, c: 12289}, 12539, 8229, [8230, 8943], 168, 12291, {f: 2, c: 8211},
+ 8214, 65340, 65374, {f: 2, c: 8216}, {f: 2, c: 8220}, {f: 2, c: 12308},
+ {f: 10, c: 12296}, 177, 215, 247, 8800, {f: 2, c: 8804}, 8734, 8756, 176,
+ {f: 2, c: 8242}, 8451, 8491, {f: 2, c: 65504}, 65509, 9794, 9792, 8736,
+ 8869, 8978, 8706, 8711, 8801, 8786, 167, 8251, 9734, 9733, 9675, 9679,
+ 9678, 9671, 9670, 9633, 9632, 9651, 9650, 9661, 9660, 8594,
+ {f: 2, c: 8592}, {f: 2, c: 8595}, 12307, 171, 187, 8730, 8765, 8733, 8757,
+ {f: 2, c: 8747}, 8712, 8715, {f: 2, c: 8838}, {f: 2, c: 8834}, 8746, 8745,
+ {f: 2, c: 8743}, 65506, 8658, 8660, 8704, 8707, 180, 732, 711, 728, 733,
+ 730, 729, 184, 731, 161, 191, 8758, 8750, 8721, 8719, 164, 8457, 8240,
+ 9665, 9664, 9655, 9654, 9828, {f: 2, c: 9824}, 9829, 9831, 9827, 9673,
+ 9672, 9635, {f: 2, c: 9680}, 9618, {f: 2, c: 9636}, 9640, 9639, 9638, 9641,
+ 9832, 9743, 9742, 9756, 9758, 182, {f: 2, c: 8224}, 8597, 8599, 8601, 8598,
+ 8600, 9837, {f: 2, c: 9833}, 9836, 12927, 12828, 8470, 13255, 8482, 13250,
+ 13272, 8481, {f: 59, c: 65281}, 65510, {f: 33, c: 65341}, 65507,
+ {f: 51, c: 12593}, {f: 42, c: 12645}, {f: 10, c: 8560}, {f: 10, c: 8544},
+ {f: 17, c: 913}, {f: 7, c: 931}, {f: 17, c: 945}, {f: 7, c: 963}, 9472,
+ 9474, 9484, 9488, 9496, 9492, 9500, 9516, 9508, 9524, 9532, 9473, 9475,
+ 9487, 9491, 9499, 9495, 9507, 9523, 9515, 9531, 9547, 9504, 9519, 9512,
+ 9527, 9535, 9501, 9520, 9509, 9528, 9538, 9490, 9489, 9498, 9497, 9494,
+ 9493, 9486, 9485, {f: 2, c: 9502}, {f: 2, c: 9505}, {f: 2, c: 9510},
+ {f: 2, c: 9513}, {f: 2, c: 9517}, {f: 2, c: 9521}, {f: 2, c: 9525},
+ {f: 2, c: 9529}, {f: 2, c: 9533}, {f: 2, c: 9536}, {f: 8, c: 9539},
+ {f: 3, c: 13205}, 8467, 13208, 13252, {f: 4, c: 13219}, {f: 10, c: 13209},
+ 13258, {f: 3, c: 13197}, 13263, {f: 2, c: 13192}, 13256, {f: 2, c: 13223},
+ {f: 10, c: 13232}, {f: 5, c: 13184}, {f: 6, c: 13242}, {f: 5, c: 13200},
+ 8486, {f: 2, c: 13248}, {f: 3, c: 13194}, 13270, 13253, {f: 3, c: 13229},
+ 13275, {f: 4, c: 13225}, 13277, 13264, 13267, 13251, 13257, 13276, 13254,
+ 198, 208, 170, 294, 306, 319, 321, 216, 338, 186, 222, 358, 330,
+ {f: 28, c: 12896}, {f: 26, c: 9424}, {f: 15, c: 9312}, 189,
+ {f: 2, c: 8531}, 188, 190, {f: 4, c: 8539}, 230, 273, 240, 295, 305, 307,
+ 312, 320, 322, 248, 339, 223, 254, 359, 331, 329, {f: 28, c: 12800},
+ {f: 26, c: 9372}, {f: 15, c: 9332}, 185, {f: 2, c: 178}, 8308, 8319,
+ {f: 4, c: 8321}, {f: 83, c: 12353}, {f: 86, c: 12449}, {f: 6, c: 1040},
+ 1025, {f: 32, c: 1046}, 1105, {f: 26, c: 1078}, {f: 2, c: 44032}, 44036,
+ {f: 4, c: 44039}, {f: 8, c: 44048}, {f: 5, c: 44057}, 44064, 44068,
+ {f: 2, c: 44076}, {f: 3, c: 44079}, {f: 2, c: 44088}, 44092, 44096, 44107,
+ 44109, 44116, 44120, 44124, {f: 2, c: 44144}, 44148, {f: 2, c: 44151},
+ 44154, {f: 2, c: 44160}, {f: 4, c: 44163}, {f: 4, c: 44169}, 44176, 44180,
+ {f: 2, c: 44188}, {f: 3, c: 44191}, {f: 3, c: 44200}, 44204,
+ {f: 2, c: 44207}, {f: 2, c: 44216}, {f: 3, c: 44219}, 44225, 44228, 44232,
+ 44236, 44245, 44247, {f: 2, c: 44256}, 44260, {f: 2, c: 44263}, 44266,
+ 44268, {f: 3, c: 44271}, 44275, {f: 2, c: 44277}, {f: 2, c: 44284}, 44288,
+ 44292, 44294, {f: 2, c: 44300}, 44303, 44305, 44312, 44316, 44320, 44329,
+ {f: 2, c: 44332}, {f: 2, c: 44340}, 44344, 44348, {f: 2, c: 44356}, 44359,
+ 44361, 44368, 44372, 44376, 44385, 44387, {f: 2, c: 44396}, 44400,
+ {f: 4, c: 44403}, {f: 3, c: 44411}, 44415, {f: 2, c: 44417},
+ {f: 2, c: 44424}, 44428, 44432, {f: 2, c: 44444}, 44452, 44471,
+ {f: 2, c: 44480}, 44484, 44488, {f: 2, c: 44496}, 44499, 44508, 44512,
+ 44516, {f: 2, c: 44536}, 44540, {f: 3, c: 44543}, {f: 2, c: 44552}, 44555,
+ 44557, 44564, {f: 2, c: 44592}, 44596, {f: 2, c: 44599}, 44602,
+ {f: 2, c: 44608}, 44611, {f: 2, c: 44613}, 44618, {f: 3, c: 44620}, 44624,
+ 44628, 44630, {f: 2, c: 44636}, {f: 3, c: 44639}, 44645, {f: 2, c: 44648},
+ 44652, 44656, {f: 2, c: 44664}, {f: 3, c: 44667}, {f: 2, c: 44676}, 44684,
+ {f: 3, c: 44732}, 44736, 44740, {f: 2, c: 44748}, {f: 3, c: 44751},
+ {f: 2, c: 44760}, 44764, 44776, 44779, 44781, 44788, 44792, 44796,
+ {f: 2, c: 44807}, 44813, 44816, {f: 2, c: 44844}, 44848, 44850, 44852,
+ {f: 2, c: 44860}, 44863, {f: 3, c: 44865}, {f: 2, c: 44872}, 44880,
+ {f: 2, c: 44892}, {f: 2, c: 44900}, 44921, 44928, 44932, 44936,
+ {f: 2, c: 44944}, 44949, 44956, {f: 2, c: 44984}, 44988, 44992,
+ {f: 3, c: 44999}, 45003, {f: 2, c: 45005}, 45012, 45020, {f: 2, c: 45032},
+ {f: 2, c: 45040}, 45044, 45048, {f: 2, c: 45056}, 45060, 45068, 45072,
+ 45076, {f: 2, c: 45084}, 45096, {f: 2, c: 45124}, 45128, 45130, 45132,
+ 45134, {f: 3, c: 45139}, 45143, 45145, 45149, {f: 2, c: 45180}, 45184,
+ 45188, {f: 2, c: 45196}, 45199, 45201, {f: 3, c: 45208}, 45212,
+ {f: 4, c: 45215}, {f: 2, c: 45224}, {f: 5, c: 45227}, 45233,
+ {f: 3, c: 45235}, 45240, 45244, {f: 2, c: 45252}, {f: 3, c: 45255},
+ {f: 2, c: 45264}, 45268, 45272, 45280, 45285, {f: 2, c: 45320},
+ {f: 2, c: 45323}, 45328, {f: 2, c: 45330}, {f: 2, c: 45336},
+ {f: 3, c: 45339}, {f: 3, c: 45347}, 45352, 45356, {f: 2, c: 45364},
+ {f: 3, c: 45367}, {f: 2, c: 45376}, 45380, 45384, {f: 2, c: 45392},
+ {f: 2, c: 45396}, 45400, 45404, 45408, {f: 2, c: 45432}, 45436, 45440,
+ 45442, {f: 2, c: 45448}, 45451, 45453, {f: 3, c: 45458}, 45464, 45468,
+ 45480, 45516, 45520, 45524, {f: 2, c: 45532}, 45535, {f: 2, c: 45544},
+ 45548, 45552, 45561, 45563, 45565, {f: 2, c: 45572}, 45576,
+ {f: 2, c: 45579}, {f: 2, c: 45588}, 45591, 45593, 45600, 45620, 45628,
+ 45656, 45660, 45664, {f: 2, c: 45672}, {f: 2, c: 45684}, 45692,
+ {f: 2, c: 45700}, 45705, {f: 2, c: 45712}, 45716, {f: 3, c: 45720},
+ {f: 2, c: 45728}, 45731, {f: 2, c: 45733}, 45738, 45740, 45744, 45748,
+ {f: 2, c: 45768}, 45772, 45776, 45778, {f: 2, c: 45784}, 45787, 45789,
+ 45794, {f: 3, c: 45796}, 45800, {f: 5, c: 45803}, {f: 3, c: 45811},
+ {f: 5, c: 45815}, {f: 3, c: 45823}, 45828, 45832, {f: 2, c: 45840},
+ {f: 3, c: 45843}, 45852, {f: 3, c: 45908}, 45912, {f: 2, c: 45915},
+ {f: 2, c: 45918}, {f: 2, c: 45924}, 45927, 45929, 45931, 45934,
+ {f: 2, c: 45936}, 45940, 45944, {f: 2, c: 45952}, {f: 3, c: 45955}, 45964,
+ 45968, 45972, {f: 2, c: 45984}, 45992, 45996, {f: 2, c: 46020}, 46024,
+ {f: 2, c: 46027}, 46030, 46032, {f: 2, c: 46036}, 46039, 46041, 46043,
+ 46045, 46048, 46052, 46056, 46076, 46096, 46104, 46108, 46112,
+ {f: 2, c: 46120}, 46123, 46132, {f: 2, c: 46160}, 46164, 46168,
+ {f: 2, c: 46176}, 46179, 46181, 46188, 46208, 46216, 46237, 46244, 46248,
+ 46252, 46261, 46263, 46265, 46272, 46276, 46280, 46288, 46293,
+ {f: 2, c: 46300}, 46304, {f: 2, c: 46307}, 46310, {f: 2, c: 46316}, 46319,
+ 46321, 46328, {f: 2, c: 46356}, 46360, {f: 2, c: 46363}, {f: 2, c: 46372},
+ {f: 4, c: 46375}, {f: 2, c: 46384}, 46388, 46392, {f: 2, c: 46400},
+ {f: 3, c: 46403}, {f: 3, c: 46411}, 46416, 46420, {f: 2, c: 46428},
+ {f: 3, c: 46431}, {f: 2, c: 46496}, 46500, 46504, {f: 2, c: 46506},
+ {f: 2, c: 46512}, {f: 3, c: 46515}, {f: 3, c: 46523}, 46528, 46532,
+ {f: 2, c: 46540}, {f: 3, c: 46543}, 46552, 46572, {f: 2, c: 46608}, 46612,
+ 46616, 46629, 46636, 46644, 46664, 46692, 46696, {f: 2, c: 46748}, 46752,
+ 46756, {f: 2, c: 46763}, 46769, 46804, 46832, 46836, 46840,
+ {f: 2, c: 46848}, 46853, {f: 2, c: 46888}, 46892, {f: 2, c: 46895},
+ {f: 2, c: 46904}, 46907, 46916, 46920, 46924, {f: 2, c: 46932}, 46944,
+ 46948, 46952, {f: 2, c: 46960}, 46963, 46965, {f: 2, c: 46972}, 46976,
+ 46980, {f: 2, c: 46988}, {f: 4, c: 46991}, {f: 4, c: 46998}, 47004, 47008,
+ {f: 2, c: 47016}, {f: 3, c: 47019}, {f: 2, c: 47028}, 47032, 47047, 47049,
+ {f: 2, c: 47084}, 47088, 47092, {f: 2, c: 47100}, {f: 3, c: 47103},
+ {f: 3, c: 47111}, 47116, 47120, {f: 2, c: 47128}, 47131, 47133,
+ {f: 2, c: 47140}, 47144, 47148, {f: 2, c: 47156}, {f: 3, c: 47159}, 47168,
+ 47172, 47185, 47187, {f: 2, c: 47196}, 47200, 47204, {f: 2, c: 47212},
+ 47215, 47217, 47224, 47228, 47245, 47272, 47280, 47284, 47288,
+ {f: 2, c: 47296}, 47299, 47301, 47308, 47312, 47316, 47325, 47327, 47329,
+ {f: 2, c: 47336}, 47340, 47344, {f: 2, c: 47352}, 47355, 47357, 47364,
+ 47384, 47392, {f: 2, c: 47420}, 47424, 47428, 47436, 47439, 47441,
+ {f: 2, c: 47448}, 47452, 47456, {f: 2, c: 47464}, 47467, 47469,
+ {f: 2, c: 47476}, 47480, 47484, {f: 2, c: 47492}, 47495, {f: 2, c: 47497},
+ {f: 2, c: 47501}, {f: 2, c: 47532}, 47536, 47540, {f: 2, c: 47548}, 47551,
+ 47553, {f: 2, c: 47560}, 47564, {f: 5, c: 47566}, {f: 2, c: 47576}, 47579,
+ {f: 2, c: 47581}, 47585, {f: 3, c: 47587}, 47592, 47596, {f: 2, c: 47604},
+ {f: 4, c: 47607}, {f: 2, c: 47616}, 47624, 47637, {f: 2, c: 47672}, 47676,
+ 47680, 47682, {f: 2, c: 47688}, 47691, {f: 2, c: 47693}, {f: 3, c: 47699},
+ 47704, 47708, {f: 2, c: 47716}, {f: 3, c: 47719}, {f: 2, c: 47728}, 47732,
+ 47736, {f: 3, c: 47747}, 47751, 47756, {f: 2, c: 47784}, {f: 2, c: 47787},
+ 47792, 47794, {f: 2, c: 47800}, 47803, 47805, 47812, 47816,
+ {f: 2, c: 47832}, 47868, 47872, 47876, 47885, 47887, 47889, 47896, 47900,
+ 47904, 47913, 47915, {f: 3, c: 47924}, 47928, {f: 4, c: 47931},
+ {f: 2, c: 47940}, 47943, 47945, 47949, {f: 2, c: 47951}, 47956, 47960,
+ 47969, 47971, 47980, 48008, 48012, 48016, 48036, 48040, 48044, 48052,
+ 48055, 48064, 48068, 48072, 48080, 48083, {f: 2, c: 48120}, 48124,
+ {f: 2, c: 48127}, 48130, {f: 2, c: 48136}, {f: 3, c: 48139}, 48143, 48145,
+ {f: 5, c: 48148}, {f: 5, c: 48155}, {f: 2, c: 48164}, 48167, 48169, 48173,
+ {f: 2, c: 48176}, 48180, 48184, {f: 2, c: 48192}, {f: 3, c: 48195}, 48201,
+ {f: 2, c: 48204}, 48208, 48221, {f: 2, c: 48260}, 48264, {f: 2, c: 48267},
+ 48270, {f: 2, c: 48276}, 48279, {f: 2, c: 48281}, {f: 2, c: 48288}, 48292,
+ {f: 2, c: 48295}, {f: 2, c: 48304}, {f: 3, c: 48307}, {f: 2, c: 48316},
+ 48320, 48324, 48333, {f: 3, c: 48335}, 48341, 48344, 48348,
+ {f: 3, c: 48372}, 48376, 48380, {f: 2, c: 48388}, 48391, 48393, 48400,
+ 48404, 48420, 48428, 48448, {f: 2, c: 48456}, 48460, 48464,
+ {f: 2, c: 48472}, 48484, 48488, {f: 2, c: 48512}, 48516, {f: 4, c: 48519},
+ {f: 2, c: 48528}, 48531, 48533, {f: 2, c: 48537}, 48540, 48548, 48560,
+ 48568, {f: 2, c: 48596}, 48600, 48604, 48617, 48624, 48628, 48632, 48640,
+ 48643, 48645, {f: 2, c: 48652}, 48656, 48660, {f: 2, c: 48668}, 48671,
+ {f: 2, c: 48708}, 48712, 48716, 48718, {f: 2, c: 48724}, 48727,
+ {f: 3, c: 48729}, {f: 2, c: 48736}, 48740, 48744, 48746, {f: 2, c: 48752},
+ {f: 3, c: 48755}, {f: 3, c: 48763}, 48768, 48772, {f: 2, c: 48780},
+ {f: 3, c: 48783}, {f: 2, c: 48792}, 48808, {f: 2, c: 48848}, 48852,
+ {f: 2, c: 48855}, 48864, {f: 3, c: 48867}, 48876, 48897, {f: 2, c: 48904},
+ {f: 2, c: 48920}, {f: 3, c: 48923}, {f: 2, c: 48960}, 48964, 48968,
+ {f: 2, c: 48976}, 48981, 49044, 49072, 49093, {f: 2, c: 49100}, 49104,
+ 49108, 49116, 49119, 49121, 49212, 49233, 49240, 49244, 49248,
+ {f: 2, c: 49256}, {f: 2, c: 49296}, 49300, 49304, {f: 2, c: 49312}, 49315,
+ 49317, {f: 2, c: 49324}, {f: 2, c: 49327}, {f: 4, c: 49331},
+ {f: 2, c: 49340}, {f: 3, c: 49343}, 49349, {f: 2, c: 49352}, 49356, 49360,
+ {f: 2, c: 49368}, {f: 3, c: 49371}, {f: 2, c: 49380}, 49384, 49388,
+ {f: 2, c: 49396}, 49399, 49401, 49408, 49412, 49416, 49424, 49429,
+ {f: 5, c: 49436}, {f: 2, c: 49443}, {f: 2, c: 49446}, {f: 2, c: 49452},
+ {f: 3, c: 49455}, 49462, {f: 2, c: 49464}, 49468, 49472, {f: 2, c: 49480},
+ {f: 3, c: 49483}, {f: 2, c: 49492}, 49496, 49500, {f: 2, c: 49508},
+ {f: 3, c: 49511}, 49520, 49524, 49528, 49541, {f: 3, c: 49548}, 49552,
+ 49556, 49558, {f: 2, c: 49564}, 49567, 49569, 49573, {f: 2, c: 49576},
+ 49580, 49584, 49597, 49604, 49608, 49612, 49620, {f: 2, c: 49623}, 49632,
+ 49636, 49640, {f: 2, c: 49648}, 49651, {f: 2, c: 49660}, 49664, 49668,
+ {f: 2, c: 49676}, 49679, 49681, {f: 2, c: 49688}, 49692, {f: 2, c: 49695},
+ {f: 2, c: 49704}, 49707, 49709, 49711, {f: 2, c: 49713}, 49716, 49736,
+ {f: 2, c: 49744}, 49748, 49752, 49760, 49765, {f: 2, c: 49772}, 49776,
+ 49780, {f: 2, c: 49788}, 49791, 49793, {f: 2, c: 49800}, 49808, 49816,
+ 49819, 49821, {f: 2, c: 49828}, 49832, {f: 2, c: 49836}, {f: 2, c: 49844},
+ 49847, 49849, {f: 2, c: 49884}, 49888, {f: 2, c: 49891}, {f: 3, c: 49899},
+ 49903, 49905, 49910, {f: 2, c: 49912}, {f: 2, c: 49915}, 49920,
+ {f: 2, c: 49928}, {f: 2, c: 49932}, {f: 3, c: 49939}, 49944, 49948,
+ {f: 2, c: 49956}, {f: 2, c: 49960}, 49989, {f: 2, c: 50024}, 50028, 50032,
+ 50034, {f: 2, c: 50040}, {f: 2, c: 50044}, 50052, 50056, 50060, 50112,
+ {f: 2, c: 50136}, 50140, {f: 2, c: 50143}, 50146, {f: 2, c: 50152}, 50157,
+ {f: 2, c: 50164}, 50168, 50184, 50192, 50212, 50220, 50224, 50228,
+ {f: 2, c: 50236}, 50248, {f: 2, c: 50276}, 50280, 50284, {f: 2, c: 50292},
+ 50297, 50304, 50324, 50332, 50360, 50364, 50409, {f: 2, c: 50416}, 50420,
+ 50424, 50426, {f: 3, c: 50431}, 50444, 50448, 50452, 50460,
+ {f: 2, c: 50472}, 50476, 50480, {f: 2, c: 50488}, 50491, 50493,
+ {f: 2, c: 50500}, {f: 3, c: 50504}, {f: 3, c: 50508}, {f: 3, c: 50515},
+ {f: 3, c: 50519}, {f: 2, c: 50525}, {f: 2, c: 50528}, 50532, 50536,
+ {f: 2, c: 50544}, {f: 3, c: 50547}, {f: 2, c: 50556}, 50560, 50564, 50567,
+ {f: 2, c: 50572}, 50575, 50577, 50581, {f: 2, c: 50583}, 50588, 50592,
+ 50601, {f: 2, c: 50612}, {f: 2, c: 50616}, {f: 4, c: 50619},
+ {f: 7, c: 50628}, 50636, 50638, {f: 2, c: 50640}, 50644, 50648,
+ {f: 2, c: 50656}, 50659, 50661, {f: 3, c: 50668}, 50672, 50676,
+ {f: 2, c: 50678}, {f: 6, c: 50684}, {f: 4, c: 50693}, 50700, 50704,
+ {f: 2, c: 50712}, {f: 2, c: 50715}, {f: 2, c: 50724}, 50728,
+ {f: 3, c: 50732}, 50736, {f: 3, c: 50739}, 50743, 50745, 50747,
+ {f: 2, c: 50752}, 50756, 50760, {f: 2, c: 50768}, {f: 3, c: 50771},
+ {f: 2, c: 50780}, 50784, 50796, 50799, 50801, {f: 2, c: 50808}, 50812,
+ 50816, {f: 2, c: 50824}, 50827, 50829, {f: 2, c: 50836}, 50840, 50844,
+ {f: 2, c: 50852}, 50855, 50857, {f: 2, c: 50864}, 50868, {f: 3, c: 50872},
+ {f: 2, c: 50880}, 50883, 50885, {f: 2, c: 50892}, 50896, 50900,
+ {f: 2, c: 50908}, {f: 2, c: 50912}, {f: 2, c: 50920}, 50924, 50928,
+ {f: 2, c: 50936}, 50941, {f: 2, c: 50948}, 50952, 50956, {f: 2, c: 50964},
+ 50967, 50969, {f: 2, c: 50976}, 50980, 50984, {f: 2, c: 50992}, 50995,
+ 50997, 50999, {f: 2, c: 51004}, 51008, 51012, 51018, {f: 2, c: 51020},
+ 51023, {f: 8, c: 51025}, 51036, 51040, 51048, 51051, {f: 2, c: 51060},
+ 51064, {f: 3, c: 51068}, {f: 3, c: 51075}, {f: 4, c: 51079}, 51086,
+ {f: 2, c: 51088}, 51092, {f: 3, c: 51094}, 51098, {f: 2, c: 51104},
+ {f: 4, c: 51107}, {f: 2, c: 51116}, 51120, 51124, {f: 2, c: 51132},
+ {f: 3, c: 51135}, {f: 2, c: 51144}, 51148, 51150, 51152, 51160, 51165,
+ 51172, 51176, 51180, {f: 2, c: 51200}, 51204, 51208, 51210,
+ {f: 2, c: 51216}, 51219, {f: 2, c: 51221}, {f: 2, c: 51228}, 51232, 51236,
+ {f: 2, c: 51244}, 51247, 51249, 51256, 51260, 51264, {f: 2, c: 51272},
+ {f: 2, c: 51276}, 51284, {f: 2, c: 51312}, 51316, 51320, 51322,
+ {f: 2, c: 51328}, 51331, {f: 3, c: 51333}, {f: 3, c: 51339}, 51348, 51357,
+ 51359, 51361, 51368, {f: 2, c: 51388}, 51396, 51400, 51404,
+ {f: 2, c: 51412}, 51415, 51417, {f: 2, c: 51424}, 51428, 51445,
+ {f: 2, c: 51452}, 51456, {f: 3, c: 51460}, {f: 2, c: 51468}, 51471, 51473,
+ 51480, 51500, 51508, {f: 2, c: 51536}, 51540, 51544, {f: 2, c: 51552},
+ 51555, 51564, 51568, 51572, 51580, {f: 2, c: 51592}, 51596, 51600,
+ {f: 2, c: 51608}, 51611, 51613, {f: 2, c: 51648}, 51652, {f: 2, c: 51655},
+ 51658, {f: 2, c: 51664}, 51667, {f: 2, c: 51669}, {f: 2, c: 51673},
+ {f: 2, c: 51676}, 51680, 51682, 51684, 51687, {f: 2, c: 51692},
+ {f: 3, c: 51695}, {f: 2, c: 51704}, 51708, 51712, {f: 2, c: 51720},
+ {f: 3, c: 51723}, 51732, 51736, 51753, {f: 2, c: 51788}, 51792, 51796,
+ {f: 2, c: 51804}, {f: 3, c: 51807}, 51816, 51837, 51844, 51864,
+ {f: 2, c: 51900}, 51904, 51908, {f: 2, c: 51916}, 51919, 51921, 51923,
+ {f: 2, c: 51928}, 51936, 51948, 51956, 51976, 51984, 51988, 51992,
+ {f: 2, c: 52000}, 52033, {f: 2, c: 52040}, 52044, 52048, {f: 2, c: 52056},
+ 52061, 52068, {f: 2, c: 52088}, 52124, 52152, 52180, 52196, 52199, 52201,
+ {f: 2, c: 52236}, 52240, 52244, {f: 2, c: 52252}, {f: 2, c: 52257},
+ {f: 3, c: 52263}, 52268, 52270, 52272, {f: 2, c: 52280}, {f: 4, c: 52283},
+ {f: 2, c: 52292}, 52296, 52300, {f: 2, c: 52308}, {f: 3, c: 52311}, 52320,
+ 52324, 52326, 52328, 52336, 52341, {f: 2, c: 52376}, 52380, 52384,
+ {f: 2, c: 52392}, {f: 3, c: 52395}, {f: 2, c: 52404}, 52408, 52412,
+ {f: 2, c: 52420}, 52423, 52425, 52432, 52436, 52452, 52460, 52464, 52481,
+ {f: 2, c: 52488}, 52492, 52496, {f: 2, c: 52504}, 52507, 52509, 52516,
+ 52520, 52524, 52537, 52572, 52576, 52580, {f: 2, c: 52588}, 52591, 52593,
+ 52600, 52616, {f: 2, c: 52628}, 52632, 52636, {f: 2, c: 52644}, 52647,
+ 52649, 52656, 52676, 52684, 52688, 52712, 52716, 52720, {f: 2, c: 52728},
+ 52731, 52733, 52740, 52744, 52748, 52756, 52761, {f: 2, c: 52768}, 52772,
+ 52776, {f: 2, c: 52784}, 52787, 52789, {f: 2, c: 52824}, 52828,
+ {f: 3, c: 52831}, {f: 2, c: 52840}, 52843, 52845, {f: 2, c: 52852}, 52856,
+ 52860, {f: 2, c: 52868}, 52871, 52873, {f: 2, c: 52880}, 52884, 52888,
+ {f: 2, c: 52896}, {f: 3, c: 52899}, {f: 2, c: 52908}, 52929,
+ {f: 2, c: 52964}, 52968, {f: 2, c: 52971}, {f: 2, c: 52980},
+ {f: 3, c: 52983}, {f: 2, c: 52992}, 52996, 53000, {f: 2, c: 53008}, 53011,
+ 53013, 53020, 53024, 53028, {f: 2, c: 53036}, {f: 3, c: 53039}, 53048,
+ {f: 2, c: 53076}, 53080, 53084, {f: 2, c: 53092}, 53095, 53097,
+ {f: 2, c: 53104}, 53108, 53112, 53120, 53125, 53132, 53153, 53160, 53168,
+ 53188, {f: 2, c: 53216}, 53220, 53224, {f: 2, c: 53232}, 53235, 53237,
+ 53244, 53248, 53252, 53265, 53272, 53293, {f: 2, c: 53300}, 53304, 53308,
+ {f: 2, c: 53316}, 53319, 53321, 53328, 53332, 53336, 53344,
+ {f: 2, c: 53356}, 53360, 53364, {f: 2, c: 53372}, 53377, {f: 2, c: 53412},
+ 53416, 53420, {f: 2, c: 53428}, 53431, 53433, {f: 2, c: 53440}, 53444,
+ {f: 2, c: 53448}, {f: 2, c: 53456}, {f: 3, c: 53459}, {f: 2, c: 53468},
+ 53472, 53476, {f: 2, c: 53484}, {f: 3, c: 53487}, 53496, 53517,
+ {f: 2, c: 53552}, 53556, 53560, 53562, {f: 2, c: 53568}, {f: 3, c: 53571},
+ {f: 2, c: 53580}, 53584, 53588, {f: 2, c: 53596}, 53599, 53601, 53608,
+ 53612, 53628, 53636, 53640, {f: 2, c: 53664}, 53668, 53672,
+ {f: 2, c: 53680}, 53683, 53685, 53690, 53692, 53696, 53720, 53748, 53752,
+ 53767, 53769, 53776, {f: 2, c: 53804}, 53808, 53812, {f: 2, c: 53820},
+ 53823, 53825, 53832, 53852, 53860, {f: 2, c: 53888}, 53892, 53896,
+ {f: 2, c: 53904}, 53909, 53916, 53920, 53924, 53932, 53937,
+ {f: 2, c: 53944}, 53948, {f: 2, c: 53951}, 53954, {f: 2, c: 53960}, 53963,
+ 53972, 53976, 53980, {f: 2, c: 53988}, {f: 2, c: 54000}, 54004, 54008,
+ {f: 2, c: 54016}, 54019, 54021, {f: 3, c: 54028}, 54032, 54036, 54038,
+ {f: 2, c: 54044}, {f: 3, c: 54047}, 54053, {f: 2, c: 54056}, 54060, 54064,
+ {f: 2, c: 54072}, {f: 3, c: 54075}, {f: 2, c: 54084}, {f: 2, c: 54140},
+ 54144, 54148, {f: 2, c: 54156}, {f: 3, c: 54159}, {f: 2, c: 54168}, 54172,
+ 54176, {f: 2, c: 54184}, 54187, 54189, 54196, 54200, 54204,
+ {f: 2, c: 54212}, {f: 2, c: 54216}, 54224, 54232, 54241, 54243,
+ {f: 2, c: 54252}, 54256, 54260, {f: 2, c: 54268}, 54271, 54273, 54280,
+ 54301, 54336, 54340, 54364, 54368, 54372, 54381, 54383, {f: 2, c: 54392},
+ 54396, {f: 2, c: 54399}, 54402, {f: 2, c: 54408}, 54411, 54413, 54420,
+ 54441, 54476, 54480, 54484, 54492, 54495, 54504, 54508, 54512, 54520,
+ 54523, 54525, 54532, 54536, 54540, {f: 2, c: 54548}, 54551,
+ {f: 2, c: 54588}, 54592, 54596, {f: 2, c: 54604}, 54607, 54609,
+ {f: 2, c: 54616}, 54620, 54624, 54629, {f: 2, c: 54632}, 54635, 54637,
+ {f: 2, c: 54644}, 54648, 54652, {f: 2, c: 54660}, {f: 3, c: 54663}, 54672,
+ 54693, {f: 2, c: 54728}, 54732, 54736, 54738, {f: 2, c: 54744}, 54747,
+ 54749, {f: 2, c: 54756}, 54760, 54764, {f: 2, c: 54772}, 54775, 54777,
+ {f: 2, c: 54784}, 54788, 54792, {f: 2, c: 54800}, {f: 3, c: 54803}, 54812,
+ 54816, 54820, 54829, {f: 2, c: 54840}, 54844, 54848, 54853,
+ {f: 2, c: 54856}, 54859, 54861, 54865, {f: 2, c: 54868}, 54872, 54876,
+ 54887, 54889, {f: 2, c: 54896}, 54900, 54915, 54917, {f: 2, c: 54924},
+ 54928, 54932, 54941, 54943, 54945, 54952, 54956, 54960, 54969, 54971,
+ {f: 2, c: 54980}, 54984, 54988, 54993, 54996, 54999, 55001, 55008, 55012,
+ 55016, 55024, 55029, {f: 2, c: 55036}, 55040, 55044, 55057,
+ {f: 2, c: 55064}, 55068, 55072, {f: 2, c: 55080}, 55083, 55085,
+ {f: 2, c: 55092}, 55096, 55100, 55108, 55111, 55113, {f: 2, c: 55120},
+ 55124, {f: 4, c: 55126}, {f: 2, c: 55136}, 55139, 55141, 55145, 55148,
+ 55152, 55156, {f: 2, c: 55164}, 55169, {f: 2, c: 55176}, 55180, 55184,
+ {f: 2, c: 55192}, 55195, 55197, 20285, 20339, 20551, 20729, 21152, 21487,
+ 21621, 21733, 22025, 23233, 23478, 26247, {f: 2, c: 26550}, 26607, 27468,
+ 29634, 30146, 31292, 33499, 33540, 34903, 34952, 35382, [36040, 63747],
+ 36303, 36603, 36838, 39381, 21051, 21364, 21508, 24682, 24932, 27580,
+ 29647, 33050, 35258, [12179, 35282], 38307, 20355, 21002, 22718, 22904,
+ 23014, [12082, 24178], 24185, 25031, 25536, 26438, 26604, 26751, 28567,
+ 30286, 30475, 30965, 31240, 31487, 31777, 32925, [12169, 33390], 33393,
+ 35563, 38291, 20075, 21917, 26359, 28212, 30883, 31469, 33883, 35088,
+ 34638, 38824, 21208, 22350, 22570, 23884, 24863, 25022, 25121, 25954,
+ 26577, 27204, 28187, [12130, 29976], 30131, 30435, 30640, 32058, 37039,
+ {f: 2, c: 37969}, 40853, 21283, 23724, 30002, 32987, 37440, 38296, 21083,
+ 22536, 23004, 23713, 23831, 24247, 24378, 24394, 24951, 27743, 30074,
+ 30086, 31968, 32115, 32177, 32652, 33108, 33313, 34193, 35137, 35611,
+ 37628, [38477, 64009], 40007, 20171, 20215, 20491, 20977, 22607, 24887,
+ 24894, 24936, 25913, 27114, 28433, 30117, 30342, 30422, 31623, 33445,
+ 33995, 37799, 38283, 21888, 23458, 22353, 31923, 32697, 37301, 20520,
+ 21435, 23621, 24040, 25298, 25454, 25818, 25831, 28192, 28844, 31067,
+ 36317, 36382, 36989, 37445, 37624, 20094, 20214, 20581, [12081, 24062],
+ 24314, 24838, 26967, 33137, 34388, 36423, 37749, 39467, 20062, 20625,
+ 26480, 26688, 20745, 21133, 21138, 27298, 30652, 37392, 40660, 21163,
+ 24623, 36850, 20552, 25001, 25581, 25802, 26684, 27268, 28608, 33160,
+ 35233, 38548, 22533, 29309, [12125, 29356], 29956, 32121, 32365, 32937,
+ [12178, 35211, 64010], 35700, 36963, 40273, 25225, 27770, 28500, 32080,
+ 32570, 35363, 20860, 24906, 31645, 35609, 37463, 37772, 20140, 20435,
+ 20510, 20670, 20742, 21185, 21197, 21375, 22384, 22659, 24218, 24465,
+ 24950, 25004, 25806, 25964, 26223, 26299, [26356, 63745], 26775, 28039,
+ 28805, 28913, 29855, 29861, 29898, 30169, 30828, 30956, 31455, 31478,
+ 32069, 32147, 32789, 32831, 33051, 33686, 35686, 36629, 36885, 37857,
+ 38915, 38968, 39514, 39912, 20418, 21843, 22586, [22865, 63753], 23395,
+ 23622, 24760, 25106, 26690, 26800, 26856, 28330, 30028, 30328, 30926,
+ 31293, 31995, 32363, 32380, 35336, 35489, 35903, 38542, 40388, 21476,
+ 21481, 21578, 21617, 22266, 22993, 23396, 23611, 24235, 25335, 25911,
+ 25925, 25970, 26272, 26543, 27073, 27837, 30204, 30352, 30590, 31295,
+ 32660, 32771, 32929, 33167, 33510, 33533, 33776, 34241, 34865, 34996,
+ 35493, 36764, 37678, 38599, 39015, [12220, 39640], [12238, 40723], 21741,
+ 26011, 26354, 26767, 31296, [12181, 35895], 40288, 22256, 22372, 23825,
+ 26118, 26801, 26829, 28414, 29736, 34974, 39908, 27752, [12219, 39592],
+ 20379, 20844, 20849, 21151, 23380, [12079, 24037], 24656, 24685, 25329,
+ 25511, 25915, 29657, 31354, 34467, 36002, 38799, [20018, 63749], 23521,
+ [12093, 25096], 26524, [12128, 29916], 31185, 33747, 35463, 35506, 36328,
+ 36942, 37707, 38982, [24275, 64011], 27112, 34303, 37101, 20896, 23448,
+ 23532, 24931, 26874, 27454, 28748, 29743, 29912, 31649, 32592, 33733,
+ 35264, 36011, 38364, 39208, 21038, 24669, 25324, 36866, 20362, 20809,
+ 21281, 22745, 24291, 26336, 27960, 28826, 29378, 29654, 31568, 33009,
+ 37979, 21350, 25499, 32619, 20054, 20608, 22602, 22750, 24618, 24871,
+ 25296, 27088, 39745, 23439, 32024, 32945, 36703, 20132, 20689, 21676,
+ 21932, 23308, 23968, 24039, 25898, 25934, 26657, 27211, 29409, 30350,
+ 30703, 32094, 32761, 33184, 34126, 34527, 36611, 36686, 37066, 39171,
+ 39509, 39851, 19992, 20037, 20061, 20167, 20465, 20855, 21246, 21312,
+ [12061, 21475], [21477, 63750], 21646, 22036, 22389, 22434, 23495, 23943,
+ 24272, 25084, 25304, 25937, 26552, 26601, 27083, 27472, 27590, 27628,
+ 27714, 28317, 28792, 29399, 29590, 29699, 30655, 30697, 31350, 32127,
+ 32777, [12165, 33276], 33285, 33290, 33503, 34914, 35635, 36092, 36544,
+ 36881, 37041, 37476, 37558, 39378, 39493, 40169, 40407,
+ [12244, 40860, 63751, 63752], 22283, 23616, 33738, 38816, 38827, 40628,
+ 21531, 31384, 32676, 35033, 36557, 37089, 22528, 23624, 25496, 31391,
+ 23470, [12088, 24339], 31353, 31406, 33422, 36524, 20518, 21048, 21240,
+ 21367, 22280, 25331, 25458, 27402, 28099, 30519, 21413, 29527, 34152,
+ 36470, 38357, 26426, 27331, 28528, 35437, 36556, 39243, 26231, 27512,
+ 36020, [12225, 39740], 21483, 22317, 22862, 25542, 27131, 29674, 30789,
+ 31418, 31429, 31998, 33909, 35215, 36211, 36917, 38312, 21243, 22343,
+ 30023, 31584, 33740, 37406, 27224, 20811, 21067, 21127, 25119, 26840,
+ 26997, 38553, 20677, 21156, 21220, 25027, [12100, 26020], 26681, 27135,
+ 29822, 31563, 33465, 33771, 35250, 35641, 36817, 39241, 20170, 22935,
+ 25810, 26129, 27278, 29748, 31105, 31165, 33449, {f: 2, c: 34942}, 35167,
+ 37670, 20235, 21450, 24613, 25201, 27762, 32026, 32102, 20120, 20834,
+ 30684, 32943, 20225, 20238, 20854, 20864, 21980, 22120, 22331, 22522,
+ 22524, 22804, 22855, 22931, 23492, 23696, 23822, [12080, 24049], 24190,
+ 24524, 25216, 26071, 26083, {f: 2, c: 26398}, 26462, 26827, 26820, 27231,
+ 27450, 27683, 27773, 27778, 28103, 29592, 29734, 29738, 29826, 29859,
+ 30072, 30079, 30849, 30959, 31041, {f: 2, c: 31047}, 31098, 31637, 32000,
+ 32186, 32648, 32774, 32813, 32908, 35352, 35663, [35912, 63744], 36215,
+ 37665, 37668, 39138, 39249, {f: 2, c: 39438}, 39525, 40594, 32202, 20342,
+ 21513, 25326, 26708, [12198, 37329, 63754], 21931, 20794, 23068, 25062,
+ [25295, 63835], 25343, 37027, [35582, 63837], 26262, 29014, 38627, 25423,
+ 25466, 21335, 26511, 26976, 28275, 30007, 32013, 34930, 22218, 23064,
+ 20035, 20839, [22856, 63756], 26608, 32784, [12069, 22899, 63873],
+ [24180, 63886], [25754, 63889], [31178, 63893], [24565, 63907], 24684,
+ 25288, [25467, 63908], [23527, 63839, 63914], 23511, 21162, 22900, 24361,
+ [24594, 63840], 29785, 39377, 28611, 33215, 36786, 24817, 33126,
+ [23615, 63933], 23273, 35365, [26491, 63944], [32016, 63951], 33021, 23612,
+ [27877, 63971], [21311, 63979], [28346, 63980], 22810, [33590, 63998],
+ [20025, 63838], 20150, 20294, 21934, 22296, 22727, 24406, 26039, 26086,
+ 27264, 27573, 28237, 30701, 31471, 31774, 32222, 34507, 34962, 37170,
+ 37723, 25787, 28606, 29562, 30136, 36948, 21846, 22349, 25018, 25812,
+ 26311, 28129, 28251, 28525, 28601, 30192, 32835, 33213, 34113, 35203,
+ 35527, 35674, 37663, 27795, 30035, 31572, 36367, 36957, 21776, 22530,
+ 22616, 24162, 25095, 25758, 26848, 30070, [31958, 64003], 34739, 40680,
+ 20195, 22408, 22382, [12068, 22823], 23565, 23729, 24118, 24453, 25140,
+ 25825, 29619, 33274, 34955, 36024, 38538, 40667, [23429, 64004], 24503,
+ 24755, 20498, [12049, 20992], 21040, 22294, 22581, 22615, 23566, 23648,
+ 23798, 23947, [24230, 64001], 24466, 24764, 25361, 25481, 25623, 26691,
+ 26873, 27330, 28120, 28193, 28372, 28644, 29182, 30428, 30585, 31153,
+ 31291, 33796, 35241, 36077, 36339, 36424, 36867, 36884, 36947, 37117,
+ 37709, 38518, 38876, 27602, 28678, 29272, 29346, 29544, 30563, 31167,
+ 31716, 32411, [35712, 63834], 22697, 24775, 25958, 26109, 26302, 27788,
+ 28958, 29129, 35930, 38931, 20077, 31361, 20189, 20908, 20941, 21205,
+ 21516, 24999, 26481, 26704, 26847, [27934, 64005], 28540, 30140, 30643,
+ 31461, 33012, 33891, 37509, 20828, [12099, 26007], 26460, 26515, 30168,
+ 31431, 33651, [12182, 35910], 36887, 38957, 23663, 33216, 33434, 36929,
+ 36975, 37389, 24471, 23965, 27225, 29128, 30331, 31561, 34276, 35588,
+ 37159, 39472, [21895, 63755], [25078, 63757], [30313, 63758],
+ [32645, 63759], [34367, 63760], [34746, 63761], [35064, 63762],
+ [37007, 63763], [27931, 63765], [28889, 63766], [29662, 63767], 32097,
+ [33853, 63768], [37226, 63769], [39409, 63770], [20098, 63771],
+ [21365, 63772], [27396, 63773], 27410, 28734, [29211, 63774],
+ [34349, 63775], [40478, 63776], 21068, 36771, [23888, 63777], 25829, 25900,
+ 27414, [28651, 63778], 31811, 32412, [34253, 63779], [35172, 63780], 35261,
+ [25289, 63781], [33240, 63782], [34847, 63783], [24266, 63784],
+ [26391, 63785], [28010, 63786], [29436, 63787], 29701, 29807, 34690,
+ [37086, 63788], [20358, 63789], 23821, 24480, 33802, [20919, 63790],
+ [25504, 63861], [30053, 63862], [20142, 63863], 20486, [20841, 63864],
+ [20937, 63865], [26753, 63866], 27153, 31918, 31921, [31975, 63867],
+ [33391, 63868], [35538, 63869], 36635, [37327, 63870], 20406, 20791,
+ [21237, 63871], [21570, 63872], [24300, 63874], 24942, 25150,
+ [26053, 63875], 27354, [28670, 63876], [31018, 63877], 34268, 34851,
+ [38317, 63878], 39522, [39530, 63879], [40599, 63880], [40654, 63881],
+ [12050, 21147, 63882], [26310, 63883], [27511, 63884], 28701, 31019,
+ [36706, 63885], 38722, [24976, 63887], [25088, 63888], 25891,
+ [28451, 63890], [29001, 63891], [29833, 63892], [32244, 63894],
+ [32879, 63895], [34030, 63897], [36646, 63896], [36899, 63898],
+ [37706, 63899], 20925, [21015, 63900], [21155, 63901], 27916,
+ [28872, 63903], [35010, 63904], [24265, 63906], 25986, [27566, 63909],
+ 28610, [31806, 63910], [29557, 63911], [20196, 63912], 20278,
+ [22265, 63913], 23738, [23994, 63915], [24604, 63916], [29618, 63917],
+ 31533, [32666, 63919], 32718, [32838, 63920], 36894, [37428, 63921],
+ [38646, 63922], [38728, 63923], [38936, 63924], 40801, [20363, 63925],
+ 28583, [31150, 63926], [37300, 63927], [38583, 63928], [21214, 63791],
+ 25736, [25796, 63792], [27347, 63793], 28510, 28696, [29200, 63794],
+ [30439, 63795], [12156, 32769, 63796], [34310, 63797], [34396, 63798],
+ [36335, 63799], 36613, [38706, 63800], [39791, 63801], [40442, 63802],
+ [12228, 40565], [30860, 63803], [31103, 63804], [32160, 63805],
+ [33737, 63806], [37636, 63807], [12229, 40575, 63808], 40595,
+ [35542, 63809], [22751, 63810], [24324, 63811], 26407, 28711, 29903,
+ [31840, 63812], [32894, 63813], 20769, 28712, [29282, 63814],
+ [30922, 63815], [36034, 63816], 36058, 36084, [38647, 63817],
+ [20102, 63930], [20698, 63931], [23534, 63932], 24278, [26009, 63934],
+ [29134, 63936], [30274, 63937], 30637, 32842, [34044, 63938],
+ [36988, 63939], 39719, [12243, 40845, 63940], [22744, 63818], 23105,
+ [23650, 63819], [27155, 63820], [28122, 63821], [28431, 63822], 30267,
+ [32047, 63823], [32311, 63824], 34078, 35128, 37860, [38475, 63825],
+ [21129, 63943], 26066, [26611, 63945], 27060, [27969, 63946],
+ [28316, 63947], 28687, [29705, 63948], 29792, [30041, 63949], 30244,
+ [30827, 63950], 35628, [39006, 63952], [20845, 63953], [25134, 63954],
+ [38520, 63955], 20374, [20523, 63956], [23833, 63957], [28138, 63958],
+ 32184, [36650, 63959], [24459, 63960], [24900, 63961], [26647, 63962],
+ [38534, 63964], [21202, 63826], [32907, 63827], [20956, 63828],
+ [20940, 63829], 26974, [31260, 63830], [32190, 63831], [33777, 63832],
+ [38517, 63833], 20442, [21033, 63965], 21400, [21519, 63966], 21774,
+ [23653, 63967], 24743, [26446, 63969], [26792, 63970], 28012, 29313, 29432,
+ [29702, 63972], 29827, [30178, 63973], 31852, [32633, 63974], 32696, 33673,
+ [35023, 63975], [35041, 63976], [12197, 37324, 63977], 37328,
+ [38626, 63978], 39881, [21533, 63981], 28542, [29136, 63982],
+ [29848, 63983], [34298, 63984], 36522, [38563, 63985], [40023, 63986],
+ [40607, 63987], [26519, 63988], [28107, 63989], 29747, [33256, 63990],
+ 38678, 30764, [12148, 31435, 63991], [31520, 63992], [31890, 63993], 25705,
+ 29802, 30194, 30908, 30952, [12218, 39340], 39764, [12231, 40635], 23518,
+ 24149, 28448, 33180, 33707, 37000, 19975, 21325, 23081, 24018, 24398,
+ 24930, 25405, 26217, 26364, 28415, 28459, 28771, 30622, 33836, 34067,
+ 34875, 36627, 39237, 39995, 21788, 25273, 26411, 27819, 33545, 35178,
+ 38778, 20129, 22916, {f: 2, c: 24536}, 26395, 32178, 32596, 33426, 33579,
+ 33725, 36638, 37017, 22475, 22969, 23186, 23504, 26151, 26522, 26757,
+ 27599, 29028, 32629, 36023, 36067, 36993, 39749, 33032, 35978, 38476,
+ 39488, [12230, 40613], 23391, 27667, 29467, 30450, 30431, 33804, 20906,
+ 35219, 20813, 20885, 21193, 26825, 27796, 30468, 30496, 32191, 32236,
+ [12207, 38754], 40629, 28357, 34065, 20901, 21517, 21629, 26126, 26269,
+ 26919, 28319, [12139, 30399], 30609, 33559, 33986, 34719, 37225, 37528,
+ 40180, 34946, 20398, 20882, 21215, 22982, 24125, 24917, {f: 2, c: 25720},
+ 26286, 26576, 27169, 27597, [12113, 27611], 29279, 29281, 29761, 30520,
+ [12141, 30683], 32791, 33468, 33541, 35584, 35624, 35980, [12106, 26408],
+ 27792, 29287, [12140, 30446], 30566, 31302, 40361, 27519, 27794, 22818,
+ 26406, 33945, 21359, 22675, 22937, 24287, 25551, 26164, 26483, 28218,
+ 29483, 31447, 33495, 37672, 21209, 24043, 25006, 25035, 25098, 25287,
+ 25771, [12102, 26080], 26969, 27494, [12111, 27595], 28961, 29687, 30045,
+ 32326, 33310, 33538, 34154, 35491, 36031, 38695, 40289, 22696, 40664,
+ 20497, 21006, 21563, 21839, [12098, 25991], 27766, {f: 2, c: 32010}, 32862,
+ 34442, [12200, 38272], 38639, 21247, 27797, 29289, 21619, 23194, 23614,
+ 23883, 24396, 24494, 26410, 26806, 26979, 28220, 28228, 30473,
+ [12150, 31859], 32654, 34183, 35598, 36855, 38753, 40692, 23735, 24758,
+ 24845, 25003, 25935, {f: 2, c: 26107}, 27665, 27887, 29599, 29641, 32225,
+ 38292, 23494, 34588, 35600, 21085, 21338, 25293, 25615, 25778, 26420,
+ 27192, 27850, 29632, 29854, 31636, 31893, 32283, 33162, 33334, 34180,
+ 36843, 38649, 39361, 20276, 21322, 21453, 21467, 25292, 25644, 25856,
+ 26001, 27075, 27886, 28504, 29677, 30036, 30242, 30436, 30460, 30928,
+ [30971, 63844], 31020, 32070, 33324, 34784, 36820, 38930, 39151, 21187,
+ 25300, 25765, 28196, 28497, 30332, 36299, 37297, 37474, 39662, 39747,
+ 20515, 20621, 22346, 22952, 23592, 24135, 24439, 25151, 25918,
+ [12101, 26041], 26049, 26121, 26507, 27036, 28354, 30917, 32033, 32938,
+ 33152, 33323, 33459, 33953, 34444, 35370, 35607, 37030, 38450, 40848,
+ 20493, 20467, 22521, 24472, 25308, 25490, 26479, 28227, 28953, 30403,
+ 32972, 32986, {f: 2, c: 35060}, 35097, 36064, 36649, 37197, 38506, 20271,
+ 20336, 24091, 26575, 26658, [12137, 30333], 30334, 39748, 24161, 27146,
+ 29033, 29140, 30058, 32321, 34115, 34281, 39132, 20240, 31567, 32624,
+ 38309, 20961, 24070, 26805, 27710, 27726, 27867, 29359, 31684, 33539,
+ 27861, 29754, 20731, 21128, 22721, 25816, 27287, 29863, 30294, 30887,
+ 34327, 38370, 38713, 21342, 24321, 35722, 36776, 36783, 37002, 21029,
+ 30629, 40009, 40712, 19993, 20482, 20853, 23643, 24183, 26142, 26170,
+ 26564, 26821, 28851, 29953, 30149, 31177, 31453, 36647, 39200, 39432,
+ 20445, 22561, 22577, 23542, 26222, 27493, 27921, 28282, 28541, 29668,
+ 29995, 33769, 35036, 35091, 35676, 36628, 20239, 20693, 21264,
+ [12056, 21340], 23443, [24489, 63846], 26381, 31119, 33145, 33583, 34068,
+ 35079, 35206, 36665, [36667, 64007], 39333, 39954, 26412, 20086, 20472,
+ 22857, 23553, {f: 2, c: 23791}, 25447, 26834, 28925, 29090, 29739, 32299,
+ 34028, 34562, 36898, 37586, 40179, [19981, 63847], 20184, 20463, 20613,
+ 21078, 21103, 21542, 21648, 22496, 22827, 23142, 23386, 23413, 23500,
+ 24220, 25206, 25975, 26023, 28014, 28325, [12119, 29238], 31526, 31807,
+ [12152, 32566], {f: 2, c: 33104}, 33178, 33344, 33433, 33705, 35331, 36000,
+ 36070, 36091, 36212, 36282, 37096, 37340, [12201, 38428], 38468, 39385,
+ 40167, [21271, 63843], 20998, 21545, 22132, 22707, 22868, 22894, 24575,
+ 24996, 25198, 26128, 27774, 28954, 30406, 31881, 31966, 32027, 33452,
+ 36033, 38640, 20315, 24343, 24447, 25282, 23849, 26379, 26842, 30844,
+ 32323, 40300, 19989, 20633, [12052, 21269], 21290, 21329, 22915, 23138,
+ 24199, 24754, 24970, 25161, 25209, 26000, 26503, 27047, [12112, 27604],
+ {f: 3, c: 27606}, 27832, 29749, 30202, 30738, 30865, 31189, 31192, 31875,
+ 32203, 32737, 32933, 33086, 33218, 33778, 34586, 35048, 35513, 35692,
+ 36027, 37145, [12206, 38750], [12214, 39131], [12240, 40763], 22188, 23338,
+ 24428, 25996, 27315, 27567, 27996, 28657, 28693, 29277, 29613, 36007,
+ 36051, 38971, 24977, 27703, 32856, 39425, 20045, 20107, 20123, 20181,
+ 20282, 20284, 20351, 20447, 20735, 21490, 21496, 21766, 21987, 22235,
+ [12064, 22763], 22882, 23057, 23531, 23546, 23556, 24051, 24107, 24473,
+ 24605, 25448, 26012, 26031, 26614, 26619, 26797, 27515, 27801, 27863,
+ 28195, 28681, 29509, 30722, 31038, 31040, 31072, 31169, 31721, 32023,
+ 32114, 32902, 33293, 33678, 34001, 34503, 35039, 35408, 35422, 35613,
+ 36060, 36198, 36781, 37034, 39164, 39391, 40605, 21066, 26388, 20632,
+ 21034, [12077, 23665], 25955, 27733, 29642, 29987, 30109, 31639, 33948,
+ 37240, 38704, 20087, 25746, [27578, 63856], 29022, 34217, 19977, 26441,
+ 26862, 28183, 33439, 34072, 34923, 25591, 28545, 37394, 39087, 19978,
+ 20663, 20687, 20767, 21830, 21930, 22039, 23360, 23577, 23776, 24120,
+ 24202, 24224, 24258, 24819, 26705, 27233, 28248, 29245, 29248,
+ [29376, 63994], 30456, 31077, 31665, 32724, 35059, 35316, 35443, 35937,
+ 36062, 38684, [22622, 63852], 29885, 36093, 21959, 31329, [32034, 63850],
+ [12170, 33394], 29298, [12131, 29983], 29989, 31513, 22661, 22779, 23996,
+ 24207, 24246, 24464, 24661, 25234, 25471, 25933, 26257, 26329, 26360,
+ 26646, 26866, 29312, 29790, 31598, 32110, 32214, 32626, 32997, 33298,
+ 34223, 35199, 35475, 36893, 37604, [12233, 40653], [12239, 40736],
+ [12067, 22805], 22893, 24109, 24796, 26132, 26227, 26512, 27728, 28101,
+ 28511, [12143, 30707], 30889, 33990, 37323, 37675, 20185, 20682, 20808,
+ 21892, 23307, 23459, 25159, 25982, 26059, 28210, 29053, 29697, 29764,
+ 29831, 29887, 30316, 31146, 32218, 32341, 32680, 33146, 33203, 33337,
+ 34330, 34796, 35445, 36323, 36984, 37521, 37925, 39245, 39854, 21352,
+ 23633, 26964, 27844, 27945, 28203, [12166, 33292], 34203, 35131, 35373,
+ [35498, 63855, 63905], 38634, 40807, 21089, 26297, 27570, 32406, 34814,
+ 36109, 38275, 38493, 25885, 28041, 29166, 22478, 22995, 23468, 24615,
+ 24826, 25104, 26143, 26207, 29481, 29689, 30427, [30465, 63853], 31596,
+ 32854, 32882, 33125, 35488, 37266, 19990, 21218, 27506, 27927, 31237,
+ 31545, 32048, 36016, 21484, 22063, 22609, 23477, [12073, 23567], 23569,
+ 24034, 25152, 25475, 25620, 26157, 26803, 27836, 28040, 28335, 28703,
+ 28836, 29138, 29990, 30095, 30094, 30233, 31505, 31712, 31787, 32032,
+ 32057, 34092, 34157, 34311, 35380, 36877, 36961, 37045, 37559, 38902,
+ 39479, 20439, 23660, 26463, 28049, 31903, 32396, 35606, 36118, 36895,
+ 23403, 24061, 25613, 33984, 36956, 39137, [29575, 63841, 63963], 23435,
+ 24730, 26494, 28126, 35359, 35494, 36865, 38924, 21047, 28753, 30862,
+ 37782, 34928, 37335, 20462, 21463, 22013, 22234, 22402, 22781, 23234,
+ 23432, 23723, 23744, 24101, 24833, 25101, [12095, 25163], 25480, 25628,
+ 25910, [25976, 63849], 27193, 27530, [12116, 27700], 27929, 28465, 29159,
+ 29417, 29560, 29703, 29874, 30246, 30561, 31168, 31319, 31466, 31929,
+ 32143, 32172, 32353, 32670, 33065, 33585, 33936, 34010, 34282, 34966,
+ 35504, 35728, 36664, 36930, 36995, 37228, 37526, 37561, 38539,
+ {f: 2, c: 38567}, 38614, 38656, 38920, [12216, 39318], 39635, 39706, 21460,
+ 22654, 22809, 23408, 23487, 28113, 28506, 29087, 29729, 29881, 32901,
+ 33789, 24033, 24455, 24490, 24642, 26092, 26642, 26991, 27219, 27529,
+ 27957, 28147, 29667, 30462, 30636, 31565, 32020, 33059, 33308, 33600,
+ 34036, 34147, 35426, 35524, 37255, 37662, 38918, 39348, 25100, 34899,
+ 36848, 37477, 23815, 23847, 23913, 29791, 33181, 34664, 28629,
+ [25342, 63859], 32722, 35126, 35186, 19998, 20056, 20711, 21213, 21319,
+ 25215, 26119, 32361, 34821, 38494, 20365, 21273, 22070, 22987, 23204,
+ [12075, 23608], 23630, 23629, 24066, 24337, 24643, 26045, 26159, 26178,
+ 26558, 26612, 29468, [12142, 30690], [12144, 31034], 32709, 33940, 33997,
+ 35222, 35430, 35433, 35553, [12183, 35925], 35962, 22516, 23508, 24335,
+ 24687, 25325, 26893, 27542, 28252, 29060, 31698, 34645, [35672, 63996],
+ 36606, [12215, 39135], 39166, 20280, 20353, 20449, 21627, 23072, 23480,
+ 24892, 26032, 26216, 29180, 30003, 31070, 32051, 33102, [12162, 33251],
+ 33688, 34218, 34254, 34563, 35338, [12189, 36523], [12191, 36763], 36805,
+ 22833, 23460, 23526, 24713, 23529, 23563, [12092, 24515], 27777, 28145,
+ 28683, 29978, 33455, 35574, [20160, 63997], [12055, 21313], 38617,
+ [12114, 27663], 20126, 20420, 20818, 21854, 23077, 23784, 25105,
+ [12123, 29273], 33469, 33706, 34558, 34905, 35357, 38463, 38597, 39187,
+ 40201, 40285, 22538, 23731, 23997, 24132, [24801, 63929], 24853, 25569,
+ [27138, 63764, 63836, 63935], 28197, 37122, 37716, 38990, 39952, 40823,
+ 23433, 23736, 25353, 26191, 26696, 30524, 38593, 38797, 38996, 39839,
+ 26017, 35585, 36555, 38332, 21813, 23721, 24022, 24245, 26263, 30284,
+ 33780, 38343, 22739, 25276, 29390, 40232, 20208, 22830, 24591, 26171,
+ 27523, 31207, 40230, 21395, 21696, 22467, 23830, 24859, 26326, 28079,
+ 30861, 33406, 38552, 38724, 21380, 25212, 25494, 28082, 32266, 33099,
+ 38989, 27387, 32588, 40367, 40474, 20063, 20539, 20918, 22812, 24825,
+ 25590, 26928, 29242, 32822, 37326, 24369, 32004, [33509, 63860], 33903,
+ 33979, 34277, 36493, 20335, 22756, 23363, 24665, 25562, 25880, 25965,
+ 26264, 26954, 27171, 27915, 28673, 29036, 30162, 30221, 31155, 31344,
+ [12154, 32650], 35140, 35731, 37312, 38525, 39178, 22276, 24481, 26044,
+ 28417, 30208, 31142, 35486, 39341, [12226, 39770], 40812, 20740, 25014,
+ 25233, 27277, 33222, 20547, 22576, 24422, 28937, [12180, 35328], 35578,
+ 23420, 34326, 20474, 20796, 22196, 22852, 25513, 28153, 23978, 26989,
+ 20870, 20104, 20313, 22914, 27487, 27741, 29877, 30998, 33287, 33349,
+ 33593, 36671, 36701, 39192, 20134, 22495, 24441, [26131, 63968], 30123,
+ 32377, 35695, 36870, 39515, 22181, 22567, 23032, 23071, 23476, 24310,
+ 25424, 25403, 26941, 27783, 27839, 28046, 28051, 28149, 28436, 28895,
+ 28982, 29017, 29123, 29141, 30799, 30831, 31605, 32227, 32303, 34893,
+ 36575, 37467, 40182, 24709, 28037, 29105, 38321, 21421, 26579, 28814,
+ 28976, 29744, 33398, 33490, 38331, 39653, 40573, 26308, 29121,
+ [33865, 63854], 22603, 23992, 24433, 26144, 26254, 27001, 27054, 27704,
+ 27891, 28214, 28481, 28634, 28699, 28719, 29008, 29151, 29552, 29787,
+ 29908, 30408, 31310, 32403, 33521, 35424, 36814, 37704, 38681, 20034,
+ 20522, 21000, 21473, 26355, 27757, 28618, 29450, 30591, 31330, 33454,
+ 34269, 34306, 35028, 35427, 35709, 35947, 37555, 38675, 38928, 20116,
+ 20237, 20425, 20658, 21320, 21566, 21555, 21978, 22626, 22714, 22887,
+ 23067, 23524, 24735, 25034, 25942, 26111, 26212, 26791, 27738, 28595,
+ 28879, 29100, 29522, 31613, 34568, 35492, 39986, 40711, 23627, 27779,
+ 29508, [12127, 29577], 37434, 28331, 29797, 30239, 31337, 32277, 34314,
+ 20800, 22725, 25793, 29934, 29973, 30320, 32705, 37013, 38605, 39252,
+ 28198, [12129, 29926], {f: 2, c: 31401}, 33253, 34521, 34680, 35355, 23113,
+ 23436, 23451, 26785, 26880, 28003, 29609, 29715, 29740, 30871, 32233,
+ 32747, 33048, 33109, 33694, 35916, [38446, 63942], 38929, [12104, 26352],
+ 24448, 26106, 26505, 27754, 29579, 20525, 23043, 27498, 30702, 22806,
+ 23916, 24013, 29477, 30031, 20709, 20985, 22575, 22829, 22934, 23002,
+ 23525, 23970, 25303, 25622, 25747, 25854, 26332, 27208, 29183, 29796,
+ 31368, 31407, 32327, 32350, 32768, 33136, 34799, 35201, 35616, 36953,
+ 36992, 39250, 24958, 27442, 28020, 32287, 35109, 36785, 20433, 20653,
+ 20887, 21191, 22471, 22665, 23481, 24248, 24898, 27029, 28044, 28263,
+ 28342, 29076, 29794, [12132, 29992], 29996, 32883, 33592, 33993, 36362,
+ 37780, 37854, 20110, 20305, 20598, 20778, [12060, 21448], 21451, 21491,
+ 23431, 23507, 23588, 24858, 24962, 26100, [12124, 29275], 29591, 29760,
+ 30402, 31056, 31121, 31161, 32006, [12155, 32701], 33419, 34261, 34398,
+ 36802, 36935, 37109, 37354, 38533, [12204, 38632], 38633, 21206, 24423,
+ 26093, 26161, 26671, 29020, 31286, 37057, 38922, 20113, 27218, 27550,
+ 28560, 29065, 32792, 33464, 34131, 36939, 38549, 38642, 38907, 34074,
+ 39729, 20112, 29066, 38596, 20803, 21407, 21729, 22291, 22290, 22435,
+ 23195, 23236, 23491, 24616, 24895, 25588, 27781, 27961, 28274, 28304,
+ 29232, 29503, 29783, 33489, 34945, 36677, 36960, 38498, 39000, 40219,
+ [12105, 26376], 36234, 37470, 20301, 20553, 20702, 21361, 22285, 22996,
+ 23041, 23561, 24944, 26256, 28205, 29234, 29771, 32239, 32963, 33806,
+ 33894, 34111, 34655, 34907, 35096, 35586, 36949, [12209, 38859], 39759,
+ 20083, 20369, 20754, 20842, 21807, 21929, 23418, 23461, {f: 2, c: 24188},
+ 24254, 24736, 24799, {f: 2, c: 24840}, 25540, 25912, 26377, 26580, 26586,
+ {f: 2, c: 26977}, 27833, 27943, 28216, 28641, {f: 2, c: 29494}, 29788,
+ 30001, 30290, 32173, 33278, 33848, 35029, 35480, 35547, 35565, 36400,
+ 36418, 36938, 36926, 36986, [12195, 37193], 37321, 37742, 22537, 27603,
+ [12161, 32905], 32946, 20801, 22891, 23609, 28516, 29607, 32996, 36103,
+ 37399, 38287, [12160, 32895], 25102, 28700, 32104, 34701, 22432, 24681,
+ 24903, 27575, 35518, 37504, 38577, [12036, 20057], 21535, 28139, 34093,
+ 38512, [12211, 38899], 39150, 25558, 27875, [12194, 37009], 20957, 25033,
+ 33210, 40441, 20381, 20506, 20736, 23452, 24847, 25087, 25836, 26885,
+ 27589, 30097, 30691, 32681, 33380, 34191, 34811, [12176, 34915], 35516,
+ 35696, 37291, [12038, 20108], 20197, 20234, 22839, 23016, 24050, 24347,
+ 24411, 24609, 29246, 29669, [30064, 63842], 30157, 31227, [12157, 32780],
+ [12159, 32819], 32900, 33505, 33617, 36029, 36019, 36999, 39156, 39180,
+ 28727, 30410, 32714, 32716, 32764, 35610, [12040, 20154], 20161, 20995,
+ 21360, [21693, 63902], 22240, 23035, 23493, 24341, 24525, 28270, 32106,
+ 33589, 34451, 35469, 38765, 38775, [12032, 19968], 20314, 20350, 22777,
+ [12103, 26085], 28322, 36920, 37808, 39353, 20219, 22764, 22922, 23001,
+ 24641, 31252, 33615, 36035, [12042, 20837], 21316, 20173, 21097, 23381,
+ 33471, 20180, [21050, 63999], 21672, 22985, 23039, [12070, 23376], 23383,
+ 23388, 24675, 24904, 28363, [28825, 63995], 29038, 29574, 29943, 30133,
+ 30913, 32043, 32773, [12163, 33258], 33576, 34071, 34249, 35566, 36039,
+ 38604, 20316, 21242, 22204, 26027, 26152, 28796, 28856, 29237, 32189,
+ 33421, 37196, 38592, 40306, 23409, 26855, 27544, 28538, 30430, 23697,
+ 26283, 28507, 31668, 31786, 34870, 38620, 19976, 20183, 21280, 22580,
+ 22715, 22767, 22892, 23559, 24115, 24196, 24373, 25484, 26290, 26454,
+ 27167, 27299, 27404, 28479, 29254, 29520, 29835, 31456, 31911, 33144,
+ 33247, 33255, 33674, 33900, 34083, 34196, 34255, 35037, 36115, 37292,
+ [12199, 38263], 38556, 20877, 21705, 22312, 23472, 25165, 26448, 26685,
+ 26771, 28221, 28371, 28797, 32289, 35009, 36001, 36617, 40779, 40782,
+ 29229, 31631, 35533, 37658, 20295, 20302, 20786, 21632, 22992, 24213,
+ 25269, 26485, 26990, 27159, 27822, 28186, 29401, 29482, 30141, 31672,
+ 32053, 33511, 33785, 33879, 34295, 35419, 36015, 36487, 36889, 37048,
+ 38606, 40799, 21219, 21514, 23265, 23490, 25688, 25973, 28404, 29380,
+ 30340, 31309, 31515, 31821, 32318, 32735, 33659, 35627, 36042,
+ [12186, 36196], 36321, 36447, 36842, 36857, 36969, 37841, 20291, 20346,
+ 20659, 20840, 20856, 21069, 21098, 22625, 22652, 22880, 23560, 23637,
+ 24283, 24731, 25136, 26643, 27583, 27656, 28593, 29006, 29728,
+ [12133, 30000], 30008, 30033, 30322, 31564, 31627, 31661, 31686, 32399,
+ 35438, 36670, 36681, 37439, 37523, 37666, 37931, 38651, 39002, 39019,
+ 39198, [20999, 64000], 25130, 25240, 27993, 30308, 31434, 31680, 32118,
+ 21344, 23742, 24215, 28472, 28857, 31896, 38673, 39822, 40670, 25509,
+ 25722, 34678, 19969, 20117, 20141, 20572, 20597, 21576, 22979, 23450,
+ 24128, 24237, 24311, 24449, 24773, 25402, 25919, 25972, 26060, 26230,
+ 26232, 26622, 26984, 27273, 27491, 27712, 28096, 28136, 28191, 28254,
+ 28702, 28833, 29582, 29693, 30010, 30555, 30855, 31118, 31243, 31357,
+ 31934, 32142, 33351, 35330, 35562, 35998, 37165, 37194, 37336, 37478,
+ 37580, 37664, 38662, 38742, 38748, 38914, [12237, 40718], 21046, 21137,
+ 21884, 22564, 24093, 24351, 24716, 25552, 26799, 28639, 31085, 31532,
+ 33229, 34234, 35069, 35576, 36420, 37261, 38500, 38555, 38717, 38988,
+ [12241, 40778], 20430, 20806, 20939, 21161, 22066, 24340, 24427, 25514,
+ 25805, 26089, 26177, 26362, 26361, 26397, 26781, 26839, 27133, 28437,
+ 28526, 29031, 29157, [12118, 29226], 29866, 30522, 31062, 31066, 31199,
+ 31264, 31381, 31895, 31967, 32068, 32368, 32903, 34299, 34468, 35412,
+ 35519, 36249, 36481, 36896, 36973, 37347, 38459, 38613, [12227, 40165],
+ 26063, 31751, [12188, 36275], 37827, 23384, 23562, 21330, 25305, 29469,
+ 20519, 23447, 24478, 24752, 24939, 26837, 28121, 29742, 31278, 32066,
+ 32156, 32305, 33131, 36394, 36405, 37758, 37912, 20304, 22352, 24038,
+ 24231, 25387, 32618, 20027, 20303, 20367, 20570, 23005, 32964, 21610,
+ 21608, 22014, 22863, 23449, 24030, 24282, 26205, 26417, 26609, 26666,
+ 27880, 27954, 28234, 28557, 28855, 29664, 30087, 31820, 32002, 32044,
+ 32162, [12168, 33311], 34523, 35387, 35461, [12187, 36208], 36490, 36659,
+ 36913, 37198, 37202, 37956, 39376, [12149, 31481], 31909, 20426, 20737,
+ 20934, 22472, 23535, 23803, 26201, 27197, 27994, 28310, 28652, 28940,
+ 30063, 31459, 34850, 36897, 36981, 38603, 39423, 33537, 20013, 20210,
+ 34886, 37325, 21373, 27355, 26987, 27713, 33914, 22686, 24974, 26366,
+ 25327, 28893, 29969, 30151, 32338, 33976, 35657, 36104, 20043, 21482,
+ 21675, 22320, 22336, 24535, 25345, 25351, 25711, [12096, 25903], 26088,
+ 26234, 26525, 26547, [12108, 27490], 27744, 27802, 28460, 30693, 30757,
+ 31049, 31063, 32025, 32930, 33026, [12164, 33267], 33437, 33463, 34584,
+ 35468, 36100, 36286, 36978, 30452, 31257, 31287, 32340, 32887, 21767,
+ 21972, 22645, 25391, 25634, 26185, 26187, 26733, 27035, 27524, 27941,
+ 28337, 29645, 29800, 29857, 30043, 30137, 30433, 30494, 30603, 31206,
+ 32265, 32285, 33275, 34095, 34967, 35386, 36049, 36587,
+ [12192, 36784, 63857], 36914, 37805, 38499, 38515, 38663, 20356, 21489,
+ 23018, 23241, 24089, 26702, 29894, 30142, 31209, 31378, 33187, 34541,
+ 36074, 36300, 36845, 26015, 26389, 22519, 28503, 32221, 36655, 37878,
+ 38598, 24501, 25074, 28548, 19988, 20376, 20511, 21449, 21983, 23919,
+ 24046, 27425, 27492, 30923, 31642, 36425, [12190, 36554, 63746], 36974,
+ 25417, 25662, 30528, 31364, 37679, 38015, 40810, 25776, 28591, 29158,
+ 29864, 29914, 31428, 31762, 32386, 31922, 32408, 35738, 36106, 38013,
+ 39184, 39244, 21049, 23519, 25830, 26413, 32046, 20717, [21443, 63851],
+ 22649, {f: 2, c: 24920}, 25082, 26028, 31449, 35730, 35734, 20489, 20513,
+ 21109, 21809, 23100, 24288, 24432, 24884, 25950, 26124, 26166, 26274,
+ 27085, 28356, 28466, 29462, 30241, 31379, 33081, 33369, 33750, 33980,
+ 20661, 22512, 23488, 23528, 24425, 25505, 30758, 32181, 33756, 34081,
+ 37319, 37365, 20874, 26613, 31574, 36012, 20932, 22971, 24765, 34389,
+ 20508, 21076, 23610, 24957, 25114, [25299, 64002], 25842, 26021, 28364,
+ 30240, 33034, 36448, 38495, 38587, 20191, 21315, 21912, 22825, 24029,
+ 25797, 27849, 28154, 29588, 31359, [12167, 33307], 34214, 36068, 36368,
+ 36983, 37351, 38369, 38433, 38854, 20984, 21746, 21894, 24505, 25764,
+ 28552, 32180, 36639, 36685, 37941, 20681, 23574, 27838, 28155, 29979,
+ 30651, 31805, 31844, 35449, 35522, 22558, 22974, 24086, 25463, 29266,
+ 30090, 30571, 35548, 36028, 36626, 24307, 26228, 28152, 32893, 33729,
+ 35531, [12205, 38737], 39894, 21059, 26367, 28053, 28399, 32224, 35558,
+ 36910, 36958, 39636, 21021, 21119, 21736, 24980, 25220, 25307, 26786,
+ 26898, 26970, 27189, 28818, 28966, 30813, 30977, 30990, 31186, 31245,
+ 32918, [12171, 33400], 33493, 33609, 34121, 35970, 36229, 37218, 37259,
+ 37294, 20419, 22225, 29165, 30679, 34560, 35320, [12072, 23544], 24534,
+ 26449, 37032, 21474, 22618, 23541, 24740, 24961, 25696, 32317, 32880,
+ 34085, 37507, 25774, 20652, 23828, 26368, 22684, 25277, 25512, 26894,
+ 27000, 27166, 28267, 30394, 31179, 33467, 33833, 35535, 36264, 36861,
+ 37138, 37195, 37276, 37648, 37656, 37786, 38619, 39478, 39949, 19985,
+ 30044, 31069, 31482, 31569, 31689, 32302, 33988, 36441, 36468, 36600,
+ 36880, 26149, 26943, 29763, 20986, 26414, 40668, 20805, 24544, 27798,
+ 34802, 34909, 34935, 24756, 33205, 33795, 36101, 21462, 21561, 22068,
+ 23094, 23601, 28810, 32736, 32858, 33030, 33261, 36259, 37257, 39519,
+ 40434, 20596, 20164, 21408, 24827, 28204, 23652, 20360, 20516, 21988,
+ 23769, 24159, 24677, 26772, 27835, 28100, 29118, 30164, 30196, 30305,
+ 31258, 31305, 32199, 32251, 32622, 33268, 34473, 36636, 38601, 39347,
+ [12242, 40786], 21063, 21189, 39149, 35242, 19971, 26578, 28422, 20405,
+ 23522, 26517, [27784, 63858], 28024, 29723, 30759, 37341, 37756, 34756,
+ 31204, 31281, 24555, 20182, 21668, 21822, 22702, 22949, 24816, 25171,
+ 25302, 26422, 26965, 33333, 38464, 39345, 39389, 20524, 21331, 21828,
+ 22396, 25176, 25826, 26219, 26589, 28609, 28655, 29730, 29752, 35351,
+ 37944, 21585, 22022, 22374, 24392, 24986, 27470, 28760, 28845, 32187,
+ 35477, 22890, 33067, 25506, 30472, 32829, 36010, 22612, 25645, 27067,
+ 23445, 24081, 28271, 34153, 20812, 21488, 22826, 24608, 24907, 27526,
+ 27760, 27888, 31518, 32974, 33492, 36294, 37040, 39089, 25799, 28580,
+ 25745, 25860, 20814, 21520, [12063, 22303], 35342, 24927, 26742, 30171,
+ 31570, 32113, 36890, 22534, 27084, 33151, 35114, 36864, 38969, 20600,
+ 22871, 22956, 25237, 36879, 39722, 24925, 29305, 38358, 22369, 23110,
+ 24052, 25226, 25773, 25850, 26487, 27874, 27966, 29228, 29750, 30772,
+ 32631, 33453, 36315, 38935, 21028, 22338, 26495, 29256, 29923, 36009,
+ 36774, 37393, 38442, [12043, 20843], 21485, 25420, 20329, 21764, 24726,
+ 25943, 27803, 28031, 29260, 29437, 31255, 35207, [12185, 35997], 24429,
+ 28558, 28921, 33192, 24846, [20415, 63845], 20559, 25153, [12122, 29255],
+ 31687, 32232, 32745, 36941, 38829, 39449, 36022, 22378, 24179, 26544,
+ 33805, 35413, 21536, 23318, 24163, 24290, 24330, 25987, 32954, 34109,
+ 38281, 38491, 20296, 21253, 21261, 21263, 21638, 21754, 22275, 24067,
+ 24598, 25243, 25265, 25429, 27873, 28006, 30129, 30770, 32990, 33071,
+ 33502, 33889, 33970, 34957, 35090, 36875, 37610, 39165, 39825, 24133,
+ [26292, 64006], 26333, 28689, 29190, 20469, 21117, 24426, 24915, 26451,
+ 27161, 28418, 29922, 31080, 34920, 35961, 39111, 39108, 39491, 21697,
+ 31263, 26963, 35575, 35914, [12213, 39080], 39342, 24444, 25259, 30130,
+ [12138, 30382], 34987, 36991, 38466, 21305, 24380, 24517, [27852, 63848],
+ 29644, 30050, [12134, 30091], 31558, 33534, 39325, 20047, 36924, 19979,
+ 20309, 21414, 22799, 24264, 26160, 27827, 29781, 33655, 34662, 36032,
+ 36944, 38686, 39957, 22737, 23416, 34384, 35604, 40372, 23506, 24680,
+ 24717, 26097, 27735, 28450, 28579, 28698, 32597, 32752, {f: 2, c: 38289},
+ 38480, 38867, 21106, 36676, 20989, 21547, 21688, 21859, 21898, 27323,
+ 28085, 32216, 33382, 37532, 38519, 40569, 21512, 21704, 30418, 34532,
+ 38308, 38356, 38492, 20130, 20233, 23022, 23270, 24055, 24658, 25239,
+ 26477, 26689, 27782, 28207, 32568, 32923, 33322, 38917, 20133, 20565,
+ 21683, 22419, 22874, 23401, 23475, 25032, 26999, 28023, 28707, 34809,
+ 35299, 35442, 35559, 36994, 39405, 39608, 21182, 26680, 20502, 24184,
+ 26447, 33607, [12175, 34892, 64008], 20139, 21521, 22190, 29670, 37141,
+ 38911, 39177, 39255, [12217, 39321], 22099, 22687, 34395, 35377, 25010,
+ 27382, 29563, 36562, 27463, 38570, 39511, 22869, 29184, 36203,
+ [12208, 38761], 20436, 23796, 24358, 25080, 26203, 27883, 28843,
+ [12126, 29572], 29625, 29694, 30505, 30541, 32067, 32098, 32291, 33335,
+ 34898, 36066, 37449, 39023, 23377, [12147, 31348], [12174, 34880],
+ [12212, 38913], 23244, 20448, 21332, 22846, 23805, 25406, 28025, 29433,
+ 33029, 33031, 33698, 37583, 38960, 20136, 20804, 21009, 22411, 24418,
+ 27842, 28366, 28677, 28752, 28847, 29074, 29673, [29801, 63918], 33610,
+ 34722, 34913, 36872, 37026, 37795, 39336, 20846, 24407, 24800, 24935,
+ 26291, 34137, 36426, 37295, 38795, 20046, 20114, 21628, 22741, 22778,
+ 22909, 23733, 24359, [12094, 25142], 25160, 26122, 26215, 27627, 28009,
+ 28111, 28246, 28408, 28564, 28640, 28649, 28765, 29392, 29733, 29786,
+ 29920, 30355, 31068, 31946, 32286, 32993, 33446, 33899, 33983, 34382,
+ 34399, 34676, 35703, 35946, 37804, 38912, 39013, 24785, 25110, 37239,
+ 23130, 26127, 28151, 28222, 29759, 39746, 24573, 24794, 31503, 21700,
+ 24344, 27742, 27859, 27946, 28888, 32005, 34425, 35340, 40251, 21270,
+ 21644, 23301, 27194, [12117, 28779], 30069, 31117, [12146, 31166], 33457,
+ 33775, 35441, 35649, 36008, 38772, 25844, 25899, {f: 2, c: 30906}, 31339,
+ 20024, 21914, 22864, 23462, 24187, 24739, 25563, 27489, 26213, 26707,
+ 28185, 29029, 29872, 32008, 36996, 39529, 39973, 27963, [28369, 63748],
+ 29502, 35905, 38346, 20976, 24140, 24488, 24653, 24822, 24880, 24908,
+ {f: 2, c: 26179}, 27045, 27841, 28255, 28361, 28514, 29004, 29852, 30343,
+ 31681, 31783, 33618, 34647, 36945, 38541, [12232, 40643], 21295, 22238,
+ 24315, 24458, 24674, 24724, 25079, 26214, 26371, 27292, 28142, 28590,
+ 28784, 29546, 32362, 33214, 33588, 34516, 35496, 36036, 21123, 29554,
+ 23446, 27243, 37892, 21742, 22150, 23389, 25928, 25989, 26313, 26783,
+ 28045, 28102, [12120, 29243], 32948, 37237, 39501, 20399, 20505, 21402,
+ 21518, 21564, 21897, 21957, 24127, 24460, 26429, 29030, 29661, 36869,
+ 21211, 21235, 22628, 22734, 28932, 29071, 29179, 34224, 35347,
+ [26248, 63941], 34216, 21927, 26244, 29002, 33841, 21321, 21913, 27585,
+ 24409, 24509, 25582, 26249, 28999, 35569, 36637, 40638, 20241, 25658,
+ 28875, 30054, 34407, 24676, 35662, 40440, 20807, 20982, 21256, 27958,
+ 33016, [12234, 40657], 26133, 27427, 28824, 30165, 21507, 23673, 32007,
+ 35350, [12107, 27424], 27453, 27462, 21560, 24688, 27965, 32725, 33288,
+ 20694, 20958, 21916, 22123, 22221, 23020, 23305, 24076, 24985, 24984,
+ 25137, 26206, 26342, 29081, {f: 2, c: 29113}, 29351, 31143, 31232, 32690,
+ 35440, {s: 163}, {f: 4, c: 12310}, {s: 14}, 8223, 8219, {f: 2, c: 8314},
+ {s: 7}, 8316, 0, {f: 2, c: 8317}, {s: 23}, 700, {s: 44}, 8942, 8759,
+ {s: 20}, {f: 10, c: 10122}, {s: 36}, {f: 26, c: 9398}, {s: 61},
+ {f: 2, c: 8826}, {f: 2, c: 8910}, {f: 2, c: 8832}, {f: 4, c: 8816}, 0,
+ 8842, 0, 8843, {f: 2, c: 8822}, 8825, {f: 2, c: 8922}, {s: 5}, 8773, 8771,
+ 8776, 0, 8868, {s: 78}, 8244, {s: 11}, 9839, {s: 4}, 8258, {s: 4}, 10045,
+ 0, 0, 8226, {s: 4}, {f: 2, c: 8249}, {s: 16}, 10010, 10006, 0, 9711,
+ {s: 3}, 10070, 0, 9676, {s: 24}, 9775, {s: 6}, 12320, 0, {f: 10, c: 10102},
+ {s: 17}, 12306, 12342, {s: 13}, 8710, 0, 8735, 0, {f: 2, c: 8741}, 0, 8787,
+ 8785, {f: 2, c: 8806}, 8723, {f: 3, c: 8853}, 0, 8980, 0, 0, 8802, 0, 9649,
+ 0, 8738, 8784, 0, 0, 8867, 0, 0, {f: 2, c: 8814}, 8837, 8836, 8713, 8716,
+ {f: 2, c: 8891}, 8794, 8966, {s: 6}, 12958, 0, 8252, {s: 11}, 9702, {s: 3},
+ 9663, 9653, 9657, 9667, {s: 4}, 9674, 12849, 12857, 13259, {f: 5, c: 9327},
+ {s: 18}, 8656, 8655, 8653, {s: 37}, 8657, 8659, {s: 8}, 8626, 8625, 0,
+ 8628, 8624, 8627, {s: 14}, 8636, 8640, {s: 10}, {f: 2, c: 8644}, {s: 144},
+ {f: 5, c: 9347}, {s: 33}, 12948, {s: 15}, 12965, {s: 93}, 8672, 8674, 8673,
+ 8675, {s: 4}, 8678, 8680, 8679, 8681, {s: 20}, 9757, 9759, {s: 76}, 12944,
+ {f: 6, c: 12938}, {s: 15}, {f: 2, c: 12318}, 8246, 0, 8245, {s: 3}, 12540,
+ 0, 0, {f: 2, c: 44034}, {f: 2, c: 44037}, {f: 5, c: 44043}, 44056,
+ {f: 2, c: 44062}, {f: 3, c: 44065}, {f: 7, c: 44069}, 44078,
+ {f: 6, c: 44082}, {f: 2, c: 44090}, {f: 3, c: 44093}, {f: 10, c: 44097},
+ 44108, {f: 6, c: 44110}, {f: 3, c: 44117}, {f: 3, c: 44121},
+ {f: 19, c: 44125}, {f: 2, c: 44146}, {f: 2, c: 44149}, 44153,
+ {f: 5, c: 44155}, 44162, {f: 2, c: 44167}, {f: 3, c: 44173},
+ {f: 3, c: 44177}, {f: 7, c: 44181}, 44190, {f: 6, c: 44194}, 44203,
+ {f: 2, c: 44205}, {f: 7, c: 44209}, 44218, {f: 3, c: 44222},
+ {f: 2, c: 44226}, {f: 3, c: 44229}, {f: 3, c: 44233}, {f: 8, c: 44237},
+ 44246, {f: 8, c: 44248}, {f: 2, c: 44258}, {f: 2, c: 44261}, 44265, 44267,
+ {f: 2, c: 44269}, 44274, 44276, {f: 5, c: 44279}, {f: 2, c: 44286},
+ {f: 3, c: 44289}, 44293, {f: 5, c: 44295}, 44302, 44304, {f: 6, c: 44306},
+ {f: 3, c: 44313}, {f: 3, c: 44317}, {f: 8, c: 44321}, {f: 2, c: 44330},
+ {f: 6, c: 44334}, {f: 2, c: 44342}, {f: 3, c: 44345}, {f: 7, c: 44349},
+ 44358, 44360, {f: 6, c: 44362}, {f: 3, c: 44369}, {f: 3, c: 44373},
+ {f: 8, c: 44377}, 44386, {f: 8, c: 44388}, {f: 2, c: 44398},
+ {f: 2, c: 44401}, {f: 4, c: 44407}, 44414, 44416, {f: 5, c: 44419},
+ {f: 2, c: 44426}, {f: 3, c: 44429}, {f: 11, c: 44433}, {f: 6, c: 44446},
+ {f: 18, c: 44453}, {f: 8, c: 44472}, {f: 2, c: 44482}, {f: 3, c: 44485},
+ {f: 7, c: 44489}, 44498, {f: 8, c: 44500}, {f: 3, c: 44509},
+ {f: 3, c: 44513}, {f: 19, c: 44517}, {f: 2, c: 44538}, {f: 2, c: 44541},
+ {f: 6, c: 44546}, 44554, 44556, {f: 6, c: 44558}, {f: 27, c: 44565},
+ {f: 2, c: 44594}, {f: 2, c: 44597}, 44601, {f: 5, c: 44603}, 44610, 44612,
+ {f: 3, c: 44615}, 44619, 44623, {f: 3, c: 44625}, 44629, {f: 5, c: 44631},
+ 44638, {f: 3, c: 44642}, {f: 2, c: 44646}, {f: 2, c: 44650},
+ {f: 3, c: 44653}, {f: 7, c: 44657}, 44666, {f: 6, c: 44670},
+ {f: 6, c: 44678}, {f: 47, c: 44685}, 44735, {f: 3, c: 44737},
+ {f: 7, c: 44741}, 44750, {f: 6, c: 44754}, {f: 2, c: 44762},
+ {f: 11, c: 44765}, {f: 2, c: 44777}, 44780, {f: 6, c: 44782},
+ {f: 3, c: 44789}, {f: 3, c: 44793}, {f: 10, c: 44797}, {f: 4, c: 44809},
+ {f: 2, c: 44814}, {f: 27, c: 44817}, {f: 2, c: 44846}, 44849, 44851,
+ {f: 7, c: 44853}, 44862, 44864, {f: 4, c: 44868}, {f: 6, c: 44874},
+ {f: 11, c: 44881}, {f: 6, c: 44894}, {f: 19, c: 44902}, {f: 6, c: 44922},
+ {f: 3, c: 44929}, {f: 3, c: 44933}, {f: 7, c: 44937}, {f: 3, c: 44946},
+ {f: 6, c: 44950}, {f: 27, c: 44957}, {f: 2, c: 44986}, {f: 3, c: 44989},
+ {f: 6, c: 44993}, 45002, 45004, {f: 5, c: 45007}, {f: 7, c: 45013},
+ {f: 11, c: 45021}, {f: 6, c: 45034}, {f: 2, c: 45042}, {f: 3, c: 45045},
+ {f: 7, c: 45049}, {f: 2, c: 45058}, {f: 7, c: 45061}, {f: 3, c: 45069},
+ {f: 3, c: 45073}, {f: 7, c: 45077}, {f: 10, c: 45086}, {f: 27, c: 45097},
+ {f: 2, c: 45126}, 45129, 45131, 45133, {f: 4, c: 45135}, 45142, 45144,
+ {f: 3, c: 45146}, {f: 30, c: 45150}, {f: 2, c: 45182}, {f: 3, c: 45185},
+ {f: 7, c: 45189}, 45198, 45200, {f: 6, c: 45202}, 45211, {f: 2, c: 45213},
+ {f: 5, c: 45219}, 45226, 45232, 45234, {f: 2, c: 45238}, {f: 3, c: 45241},
+ {f: 7, c: 45245}, 45254, {f: 6, c: 45258}, {f: 2, c: 45266},
+ {f: 3, c: 45269}, {f: 7, c: 45273}, {f: 4, c: 45281}, {f: 34, c: 45286},
+ 45322, {f: 3, c: 45325}, 45329, {f: 4, c: 45332}, 45338, {f: 5, c: 45342},
+ {f: 2, c: 45350}, {f: 3, c: 45353}, {f: 7, c: 45357}, 45366,
+ {f: 6, c: 45370}, {f: 2, c: 45378}, {f: 3, c: 45381}, {f: 7, c: 45385},
+ {f: 2, c: 45394}, {f: 2, c: 45398}, {f: 3, c: 45401}, {f: 3, c: 45405},
+ {f: 23, c: 45409}, {f: 2, c: 45434}, {f: 3, c: 45437}, 45441,
+ {f: 5, c: 45443}, 45450, 45452, {f: 4, c: 45454}, {f: 3, c: 45461},
+ {f: 3, c: 45465}, {f: 11, c: 45469}, {f: 35, c: 45481}, {f: 3, c: 45517},
+ {f: 3, c: 45521}, {f: 7, c: 45525}, 45534, {f: 8, c: 45536},
+ {f: 2, c: 45546}, {f: 3, c: 45549}, {f: 8, c: 45553}, 45562, 45564,
+ {f: 6, c: 45566}, {f: 2, c: 45574}, {f: 2, c: 45577}, {f: 7, c: 45581},
+ 45590, 45592, {f: 6, c: 45594}, {f: 19, c: 45601}, {f: 7, c: 45621},
+ {f: 27, c: 45629}, {f: 3, c: 45657}, {f: 3, c: 45661}, {f: 7, c: 45665},
+ {f: 10, c: 45674}, {f: 6, c: 45686}, {f: 7, c: 45693}, {f: 3, c: 45702},
+ {f: 6, c: 45706}, {f: 2, c: 45714}, {f: 3, c: 45717}, {f: 5, c: 45723},
+ 45730, 45732, {f: 3, c: 45735}, 45739, {f: 3, c: 45741}, {f: 3, c: 45745},
+ {f: 19, c: 45749}, {f: 2, c: 45770}, {f: 3, c: 45773}, 45777,
+ {f: 5, c: 45779}, 45786, 45788, {f: 4, c: 45790}, 45795, 45799,
+ {f: 2, c: 45801}, {f: 3, c: 45808}, 45814, {f: 3, c: 45820},
+ {f: 2, c: 45826}, {f: 3, c: 45829}, {f: 7, c: 45833}, 45842,
+ {f: 6, c: 45846}, {f: 55, c: 45853}, 45911, {f: 2, c: 45913}, 45917,
+ {f: 4, c: 45920}, 45926, 45928, 45930, {f: 2, c: 45932}, 45935,
+ {f: 2, c: 45938}, {f: 3, c: 45941}, {f: 7, c: 45945}, 45954,
+ {f: 6, c: 45958}, {f: 3, c: 45965}, {f: 3, c: 45969}, {f: 11, c: 45973},
+ {f: 6, c: 45986}, {f: 3, c: 45993}, {f: 23, c: 45997}, {f: 2, c: 46022},
+ {f: 2, c: 46025}, 46029, 46031, {f: 3, c: 46033}, 46038, 46040, 46042,
+ 46044, {f: 2, c: 46046}, {f: 3, c: 46049}, {f: 3, c: 46053},
+ {f: 19, c: 46057}, {f: 19, c: 46077}, {f: 7, c: 46097}, {f: 3, c: 46105},
+ {f: 3, c: 46109}, {f: 7, c: 46113}, 46122, {f: 8, c: 46124},
+ {f: 27, c: 46133}, {f: 2, c: 46162}, {f: 3, c: 46165}, {f: 7, c: 46169},
+ 46178, 46180, {f: 6, c: 46182}, {f: 19, c: 46189}, {f: 7, c: 46209},
+ {f: 20, c: 46217}, {f: 6, c: 46238}, {f: 3, c: 46245}, {f: 3, c: 46249},
+ {f: 8, c: 46253}, 46262, 46264, {f: 6, c: 46266}, {f: 3, c: 46273},
+ {f: 3, c: 46277}, {f: 7, c: 46281}, {f: 4, c: 46289}, {f: 6, c: 46294},
+ {f: 2, c: 46302}, {f: 2, c: 46305}, 46309, {f: 5, c: 46311}, 46318, 46320,
+ {f: 6, c: 46322}, {f: 27, c: 46329}, {f: 2, c: 46358}, {f: 2, c: 46361},
+ {f: 7, c: 46365}, 46374, {f: 5, c: 46379}, {f: 2, c: 46386},
+ {f: 3, c: 46389}, {f: 7, c: 46393}, 46402, {f: 5, c: 46406},
+ {f: 2, c: 46414}, {f: 3, c: 46417}, {f: 7, c: 46421}, 46430,
+ {f: 62, c: 46434}, {f: 2, c: 46498}, {f: 3, c: 46501}, 46505,
+ {f: 4, c: 46508}, 46514, {f: 5, c: 46518}, {f: 2, c: 46526},
+ {f: 3, c: 46529}, {f: 7, c: 46533}, 46542, {f: 6, c: 46546},
+ {f: 19, c: 46553}, {f: 35, c: 46573}, {f: 2, c: 46610}, {f: 3, c: 46613},
+ {f: 12, c: 46617}, {f: 6, c: 46630}, {f: 7, c: 46637}, {f: 19, c: 46645},
+ {f: 27, c: 46665}, {f: 3, c: 46693}, {f: 51, c: 46697}, {f: 2, c: 46750},
+ {f: 3, c: 46753}, {f: 6, c: 46757}, {f: 4, c: 46765}, {f: 34, c: 46770},
+ {f: 27, c: 46805}, {f: 3, c: 46833}, {f: 3, c: 46837}, {f: 7, c: 46841},
+ {f: 3, c: 46850}, {f: 34, c: 46854}, {f: 2, c: 46890}, {f: 2, c: 46893},
+ {f: 7, c: 46897}, 46906, {f: 8, c: 46908}, {f: 3, c: 46917},
+ {f: 3, c: 46921}, {f: 7, c: 46925}, {f: 10, c: 46934}, {f: 3, c: 46945},
+ {f: 3, c: 46949}, {f: 7, c: 46953}, 46962, 46964, {f: 6, c: 46966},
+ {f: 2, c: 46974}, {f: 3, c: 46977}, {f: 7, c: 46981}, 46990,
+ {f: 3, c: 46995}, {f: 2, c: 47002}, {f: 3, c: 47005}, {f: 7, c: 47009},
+ 47018, {f: 6, c: 47022}, {f: 2, c: 47030}, {f: 14, c: 47033}, 47048,
+ {f: 34, c: 47050}, {f: 2, c: 47086}, {f: 3, c: 47089}, {f: 7, c: 47093},
+ 47102, {f: 5, c: 47106}, {f: 2, c: 47114}, {f: 3, c: 47117},
+ {f: 7, c: 47121}, 47130, 47132, {f: 6, c: 47134}, {f: 2, c: 47142},
+ {f: 3, c: 47145}, {f: 7, c: 47149}, 47158, {f: 6, c: 47162},
+ {f: 3, c: 47169}, {f: 12, c: 47173}, 47186, {f: 8, c: 47188},
+ {f: 2, c: 47198}, {f: 3, c: 47201}, {f: 7, c: 47205}, 47214, 47216,
+ {f: 6, c: 47218}, {f: 3, c: 47225}, {f: 16, c: 47229}, {f: 26, c: 47246},
+ {f: 7, c: 47273}, {f: 3, c: 47281}, {f: 3, c: 47285}, {f: 7, c: 47289},
+ 47298, 47300, {f: 6, c: 47302}, {f: 3, c: 47309}, {f: 3, c: 47313},
+ {f: 8, c: 47317}, 47326, 47328, {f: 6, c: 47330}, {f: 2, c: 47338},
+ {f: 3, c: 47341}, {f: 7, c: 47345}, 47354, 47356, {f: 6, c: 47358},
+ {f: 19, c: 47365}, {f: 7, c: 47385}, {f: 27, c: 47393}, {f: 2, c: 47422},
+ {f: 3, c: 47425}, {f: 7, c: 47429}, {f: 2, c: 47437}, 47440,
+ {f: 6, c: 47442}, {f: 2, c: 47450}, {f: 3, c: 47453}, {f: 7, c: 47457},
+ 47466, 47468, {f: 6, c: 47470}, {f: 2, c: 47478}, {f: 3, c: 47481},
+ {f: 7, c: 47485}, 47494, 47496, {f: 2, c: 47499}, {f: 29, c: 47503},
+ {f: 2, c: 47534}, {f: 3, c: 47537}, {f: 7, c: 47541}, 47550, 47552,
+ {f: 6, c: 47554}, {f: 2, c: 47562}, 47565, {f: 5, c: 47571}, 47578, 47580,
+ {f: 2, c: 47583}, 47586, {f: 2, c: 47590}, {f: 3, c: 47593},
+ {f: 7, c: 47597}, 47606, {f: 5, c: 47611}, {f: 6, c: 47618},
+ {f: 12, c: 47625}, {f: 34, c: 47638}, {f: 2, c: 47674}, {f: 3, c: 47677},
+ 47681, {f: 5, c: 47683}, 47690, 47692, {f: 4, c: 47695}, {f: 2, c: 47702},
+ {f: 3, c: 47705}, {f: 7, c: 47709}, 47718, {f: 6, c: 47722},
+ {f: 2, c: 47730}, {f: 3, c: 47733}, {f: 10, c: 47737}, 47750,
+ {f: 4, c: 47752}, {f: 27, c: 47757}, 47786, {f: 3, c: 47789}, 47793,
+ {f: 5, c: 47795}, 47802, 47804, {f: 6, c: 47806}, {f: 3, c: 47813},
+ {f: 15, c: 47817}, {f: 34, c: 47834}, {f: 3, c: 47869}, {f: 3, c: 47873},
+ {f: 8, c: 47877}, 47886, 47888, {f: 6, c: 47890}, {f: 3, c: 47897},
+ {f: 3, c: 47901}, {f: 8, c: 47905}, 47914, {f: 8, c: 47916}, 47927,
+ {f: 2, c: 47929}, {f: 5, c: 47935}, 47942, 47944, {f: 3, c: 47946}, 47950,
+ {f: 3, c: 47953}, {f: 3, c: 47957}, {f: 8, c: 47961}, 47970,
+ {f: 8, c: 47972}, {f: 27, c: 47981}, {f: 3, c: 48009}, {f: 3, c: 48013},
+ {f: 19, c: 48017}, {f: 3, c: 48037}, {f: 3, c: 48041}, {f: 7, c: 48045},
+ {f: 2, c: 48053}, {f: 8, c: 48056}, {f: 3, c: 48065}, {f: 3, c: 48069},
+ {f: 7, c: 48073}, {f: 2, c: 48081}, {f: 36, c: 48084}, {f: 2, c: 48122},
+ {f: 2, c: 48125}, 48129, {f: 5, c: 48131}, 48138, 48142, 48144,
+ {f: 2, c: 48146}, {f: 2, c: 48153}, {f: 4, c: 48160}, 48166, 48168,
+ {f: 3, c: 48170}, {f: 2, c: 48174}, {f: 2, c: 48178}, {f: 3, c: 48181},
+ {f: 7, c: 48185}, 48194, {f: 3, c: 48198}, {f: 2, c: 48202},
+ {f: 2, c: 48206}, {f: 12, c: 48209}, {f: 38, c: 48222}, {f: 2, c: 48262},
+ {f: 2, c: 48265}, 48269, {f: 5, c: 48271}, 48278, 48280, {f: 5, c: 48283},
+ {f: 2, c: 48290}, {f: 2, c: 48293}, {f: 7, c: 48297}, 48306,
+ {f: 6, c: 48310}, {f: 2, c: 48318}, {f: 3, c: 48321}, {f: 8, c: 48325},
+ 48334, {f: 3, c: 48338}, {f: 2, c: 48342}, {f: 3, c: 48345},
+ {f: 23, c: 48349}, 48375, {f: 3, c: 48377}, {f: 7, c: 48381}, 48390, 48392,
+ {f: 6, c: 48394}, {f: 3, c: 48401}, {f: 15, c: 48405}, {f: 7, c: 48421},
+ {f: 19, c: 48429}, {f: 7, c: 48449}, {f: 2, c: 48458}, {f: 3, c: 48461},
+ {f: 7, c: 48465}, {f: 10, c: 48474}, {f: 3, c: 48485}, {f: 23, c: 48489},
+ {f: 2, c: 48514}, {f: 2, c: 48517}, {f: 5, c: 48523}, 48530, 48532,
+ {f: 3, c: 48534}, 48539, {f: 7, c: 48541}, {f: 11, c: 48549},
+ {f: 7, c: 48561}, {f: 27, c: 48569}, {f: 2, c: 48598}, {f: 3, c: 48601},
+ {f: 12, c: 48605}, {f: 6, c: 48618}, {f: 3, c: 48625}, {f: 3, c: 48629},
+ {f: 7, c: 48633}, {f: 2, c: 48641}, 48644, {f: 6, c: 48646},
+ {f: 2, c: 48654}, {f: 3, c: 48657}, {f: 7, c: 48661}, 48670,
+ {f: 36, c: 48672}, {f: 2, c: 48710}, {f: 3, c: 48713}, 48717,
+ {f: 5, c: 48719}, 48726, 48728, {f: 4, c: 48732}, {f: 2, c: 48738},
+ {f: 3, c: 48741}, 48745, {f: 5, c: 48747}, 48754, {f: 5, c: 48758},
+ {f: 2, c: 48766}, {f: 3, c: 48769}, {f: 7, c: 48773}, 48782,
+ {f: 6, c: 48786}, {f: 14, c: 48794}, {f: 39, c: 48809}, {f: 2, c: 48850},
+ {f: 2, c: 48853}, {f: 7, c: 48857}, {f: 2, c: 48865}, {f: 6, c: 48870},
+ {f: 20, c: 48877}, {f: 6, c: 48898}, {f: 14, c: 48906}, 48922,
+ {f: 34, c: 48926}, {f: 2, c: 48962}, {f: 3, c: 48965}, {f: 7, c: 48969},
+ {f: 3, c: 48978}, {f: 62, c: 48982}, {f: 27, c: 49045}, {f: 20, c: 49073},
+ {f: 6, c: 49094}, {f: 2, c: 49102}, {f: 3, c: 49105}, {f: 7, c: 49109},
+ {f: 2, c: 49117}, 49120, {f: 90, c: 49122}, {f: 20, c: 49213},
+ {f: 6, c: 49234}, {f: 3, c: 49241}, {f: 3, c: 49245}, {f: 7, c: 49249},
+ {f: 38, c: 49258}, {f: 2, c: 49298}, {f: 3, c: 49301}, {f: 7, c: 49305},
+ 49314, 49316, {f: 6, c: 49318}, 49326, {f: 2, c: 49329}, {f: 5, c: 49335},
+ 49342, {f: 3, c: 49346}, {f: 2, c: 49350}, {f: 2, c: 49354},
+ {f: 3, c: 49357}, {f: 7, c: 49361}, 49370, {f: 6, c: 49374},
+ {f: 2, c: 49382}, {f: 3, c: 49385}, {f: 7, c: 49389}, 49398, 49400,
+ {f: 6, c: 49402}, {f: 3, c: 49409}, {f: 3, c: 49413}, {f: 7, c: 49417},
+ {f: 4, c: 49425}, {f: 6, c: 49430}, {f: 2, c: 49441}, 49445,
+ {f: 4, c: 49448}, 49454, {f: 4, c: 49458}, 49463, {f: 2, c: 49466},
+ {f: 3, c: 49469}, {f: 7, c: 49473}, 49482, {f: 6, c: 49486},
+ {f: 2, c: 49494}, {f: 3, c: 49497}, {f: 7, c: 49501}, 49510,
+ {f: 6, c: 49514}, {f: 3, c: 49521}, {f: 3, c: 49525}, {f: 12, c: 49529},
+ {f: 6, c: 49542}, 49551, {f: 3, c: 49553}, 49557, {f: 5, c: 49559}, 49566,
+ 49568, {f: 3, c: 49570}, {f: 2, c: 49574}, {f: 2, c: 49578},
+ {f: 3, c: 49581}, {f: 12, c: 49585}, {f: 6, c: 49598}, {f: 3, c: 49605},
+ {f: 3, c: 49609}, {f: 7, c: 49613}, {f: 2, c: 49621}, {f: 7, c: 49625},
+ {f: 3, c: 49633}, {f: 3, c: 49637}, {f: 7, c: 49641}, 49650,
+ {f: 8, c: 49652}, {f: 2, c: 49662}, {f: 3, c: 49665}, {f: 7, c: 49669},
+ 49678, 49680, {f: 6, c: 49682}, {f: 2, c: 49690}, {f: 2, c: 49693},
+ {f: 7, c: 49697}, 49706, 49708, 49710, 49712, 49715, {f: 19, c: 49717},
+ {f: 7, c: 49737}, {f: 2, c: 49746}, {f: 3, c: 49749}, {f: 7, c: 49753},
+ {f: 4, c: 49761}, {f: 6, c: 49766}, {f: 2, c: 49774}, {f: 3, c: 49777},
+ {f: 7, c: 49781}, 49790, 49792, {f: 6, c: 49794}, {f: 6, c: 49802},
+ {f: 7, c: 49809}, {f: 2, c: 49817}, 49820, {f: 6, c: 49822},
+ {f: 2, c: 49830}, {f: 3, c: 49833}, {f: 6, c: 49838}, 49846, 49848,
+ {f: 34, c: 49850}, {f: 2, c: 49886}, {f: 2, c: 49889}, {f: 6, c: 49893},
+ 49902, 49904, {f: 4, c: 49906}, 49911, 49914, {f: 3, c: 49917},
+ {f: 7, c: 49921}, {f: 2, c: 49930}, {f: 5, c: 49934}, {f: 2, c: 49942},
+ {f: 3, c: 49945}, {f: 7, c: 49949}, {f: 2, c: 49958}, {f: 27, c: 49962},
+ {f: 34, c: 49990}, {f: 2, c: 50026}, {f: 3, c: 50029}, 50033,
+ {f: 5, c: 50035}, {f: 2, c: 50042}, {f: 6, c: 50046}, {f: 3, c: 50053},
+ {f: 3, c: 50057}, {f: 51, c: 50061}, {f: 23, c: 50113}, {f: 2, c: 50138},
+ {f: 2, c: 50141}, 50145, {f: 5, c: 50147}, {f: 3, c: 50154},
+ {f: 6, c: 50158}, {f: 2, c: 50166}, {f: 15, c: 50169}, {f: 7, c: 50185},
+ {f: 19, c: 50193}, {f: 7, c: 50213}, {f: 3, c: 50221}, {f: 3, c: 50225},
+ {f: 7, c: 50229}, {f: 10, c: 50238}, {f: 27, c: 50249}, {f: 2, c: 50278},
+ {f: 3, c: 50281}, {f: 7, c: 50285}, {f: 3, c: 50294}, {f: 6, c: 50298},
+ {f: 19, c: 50305}, {f: 7, c: 50325}, {f: 27, c: 50333}, {f: 3, c: 50361},
+ {f: 44, c: 50365}, {f: 6, c: 50410}, {f: 2, c: 50418}, {f: 3, c: 50421},
+ 50425, {f: 4, c: 50427}, {f: 10, c: 50434}, {f: 3, c: 50445},
+ {f: 3, c: 50449}, {f: 7, c: 50453}, {f: 11, c: 50461}, {f: 2, c: 50474},
+ {f: 3, c: 50477}, {f: 7, c: 50481}, 50490, 50492, {f: 6, c: 50494},
+ {f: 2, c: 50502}, 50507, {f: 4, c: 50511}, 50518, {f: 3, c: 50522}, 50527,
+ {f: 2, c: 50530}, {f: 3, c: 50533}, {f: 7, c: 50537}, 50546,
+ {f: 6, c: 50550}, {f: 2, c: 50558}, {f: 3, c: 50561}, {f: 2, c: 50565},
+ {f: 4, c: 50568}, 50574, 50576, {f: 3, c: 50578}, 50582, {f: 3, c: 50585},
+ {f: 3, c: 50589}, {f: 8, c: 50593}, {f: 10, c: 50602}, {f: 2, c: 50614},
+ 50618, {f: 5, c: 50623}, 50635, 50637, 50639, {f: 2, c: 50642},
+ {f: 3, c: 50645}, {f: 7, c: 50649}, 50658, 50660, {f: 6, c: 50662}, 50671,
+ {f: 3, c: 50673}, 50677, {f: 4, c: 50680}, {f: 3, c: 50690},
+ {f: 3, c: 50697}, {f: 3, c: 50701}, {f: 7, c: 50705}, 50714,
+ {f: 7, c: 50717}, {f: 2, c: 50726}, {f: 3, c: 50729}, 50735,
+ {f: 2, c: 50737}, 50742, 50744, 50746, {f: 4, c: 50748}, {f: 2, c: 50754},
+ {f: 3, c: 50757}, {f: 7, c: 50761}, 50770, {f: 6, c: 50774},
+ {f: 2, c: 50782}, {f: 11, c: 50785}, {f: 2, c: 50797}, 50800,
+ {f: 6, c: 50802}, {f: 2, c: 50810}, {f: 3, c: 50813}, {f: 7, c: 50817},
+ 50826, 50828, {f: 6, c: 50830}, {f: 2, c: 50838}, {f: 3, c: 50841},
+ {f: 7, c: 50845}, 50854, 50856, {f: 6, c: 50858}, {f: 2, c: 50866},
+ {f: 3, c: 50869}, {f: 5, c: 50875}, 50882, 50884, {f: 6, c: 50886},
+ {f: 2, c: 50894}, {f: 3, c: 50897}, {f: 7, c: 50901}, {f: 2, c: 50910},
+ {f: 6, c: 50914}, {f: 2, c: 50922}, {f: 3, c: 50925}, {f: 7, c: 50929},
+ {f: 3, c: 50938}, {f: 6, c: 50942}, {f: 2, c: 50950}, {f: 3, c: 50953},
+ {f: 7, c: 50957}, 50966, 50968, {f: 6, c: 50970}, {f: 2, c: 50978},
+ {f: 3, c: 50981}, {f: 7, c: 50985}, 50994, 50996, 50998, {f: 4, c: 51000},
+ {f: 2, c: 51006}, {f: 3, c: 51009}, {f: 5, c: 51013}, 51019, 51022, 51024,
+ {f: 3, c: 51033}, {f: 3, c: 51037}, {f: 7, c: 51041}, {f: 2, c: 51049},
+ {f: 8, c: 51052}, {f: 2, c: 51062}, {f: 3, c: 51065}, {f: 4, c: 51071},
+ 51078, {f: 3, c: 51083}, 51087, {f: 2, c: 51090}, 51093, 51097,
+ {f: 5, c: 51099}, 51106, {f: 5, c: 51111}, {f: 2, c: 51118},
+ {f: 3, c: 51121}, {f: 7, c: 51125}, 51134, {f: 6, c: 51138},
+ {f: 2, c: 51146}, 51149, 51151, {f: 7, c: 51153}, {f: 4, c: 51161},
+ {f: 6, c: 51166}, {f: 3, c: 51173}, {f: 3, c: 51177}, {f: 19, c: 51181},
+ {f: 2, c: 51202}, {f: 3, c: 51205}, 51209, {f: 5, c: 51211}, 51218, 51220,
+ {f: 5, c: 51223}, {f: 2, c: 51230}, {f: 3, c: 51233}, {f: 7, c: 51237},
+ 51246, 51248, {f: 6, c: 51250}, {f: 3, c: 51257}, {f: 3, c: 51261},
+ {f: 7, c: 51265}, {f: 2, c: 51274}, {f: 6, c: 51278}, {f: 27, c: 51285},
+ {f: 2, c: 51314}, {f: 3, c: 51317}, 51321, {f: 5, c: 51323}, 51330, 51332,
+ {f: 3, c: 51336}, {f: 6, c: 51342}, {f: 8, c: 51349}, 51358, 51360,
+ {f: 6, c: 51362}, {f: 19, c: 51369}, {f: 6, c: 51390}, {f: 3, c: 51397},
+ {f: 3, c: 51401}, {f: 7, c: 51405}, 51414, 51416, {f: 6, c: 51418},
+ {f: 2, c: 51426}, {f: 16, c: 51429}, {f: 6, c: 51446}, {f: 2, c: 51454},
+ {f: 3, c: 51457}, {f: 5, c: 51463}, 51470, 51472, {f: 6, c: 51474},
+ {f: 19, c: 51481}, {f: 7, c: 51501}, {f: 27, c: 51509}, {f: 2, c: 51538},
+ {f: 3, c: 51541}, {f: 7, c: 51545}, 51554, {f: 8, c: 51556},
+ {f: 3, c: 51565}, {f: 3, c: 51569}, {f: 7, c: 51573}, {f: 11, c: 51581},
+ {f: 2, c: 51594}, {f: 3, c: 51597}, {f: 7, c: 51601}, 51610, 51612,
+ {f: 34, c: 51614}, {f: 2, c: 51650}, {f: 2, c: 51653}, 51657,
+ {f: 5, c: 51659}, 51666, 51668, {f: 2, c: 51671}, 51675, {f: 2, c: 51678},
+ 51681, 51683, {f: 2, c: 51685}, {f: 4, c: 51688}, 51694, {f: 6, c: 51698},
+ {f: 2, c: 51706}, {f: 3, c: 51709}, {f: 7, c: 51713}, 51722,
+ {f: 6, c: 51726}, {f: 3, c: 51733}, {f: 16, c: 51737}, {f: 34, c: 51754},
+ {f: 2, c: 51790}, {f: 3, c: 51793}, {f: 7, c: 51797}, 51806,
+ {f: 6, c: 51810}, {f: 20, c: 51817}, {f: 6, c: 51838}, {f: 19, c: 51845},
+ {f: 35, c: 51865}, {f: 2, c: 51902}, {f: 3, c: 51905}, {f: 7, c: 51909},
+ 51918, 51920, 51922, {f: 4, c: 51924}, {f: 6, c: 51930}, {f: 11, c: 51937},
+ {f: 7, c: 51949}, {f: 19, c: 51957}, {f: 7, c: 51977}, {f: 3, c: 51985},
+ {f: 3, c: 51989}, {f: 7, c: 51993}, {f: 31, c: 52002}, {f: 6, c: 52034},
+ {f: 2, c: 52042}, {f: 3, c: 52045}, {f: 7, c: 52049}, {f: 3, c: 52058},
+ {f: 6, c: 52062}, {f: 19, c: 52069}, {f: 34, c: 52090}, {f: 27, c: 52125},
+ {f: 27, c: 52153}, {f: 15, c: 52181}, {f: 2, c: 52197}, 52200,
+ {f: 34, c: 52202}, {f: 2, c: 52238}, {f: 3, c: 52241}, {f: 7, c: 52245},
+ {f: 3, c: 52254}, {f: 4, c: 52259}, {f: 2, c: 52266}, 52269, 52271,
+ {f: 7, c: 52273}, 52282, {f: 5, c: 52287}, {f: 2, c: 52294},
+ {f: 3, c: 52297}, {f: 7, c: 52301}, 52310, {f: 6, c: 52314},
+ {f: 3, c: 52321}, 52325, 52327, {f: 7, c: 52329}, {f: 4, c: 52337},
+ {f: 34, c: 52342}, {f: 2, c: 52378}, {f: 3, c: 52381}, {f: 7, c: 52385},
+ 52394, {f: 6, c: 52398}, {f: 2, c: 52406}, {f: 3, c: 52409},
+ {f: 7, c: 52413}, 52422, 52424, {f: 6, c: 52426}, {f: 3, c: 52433},
+ {f: 15, c: 52437}, {f: 7, c: 52453}, {f: 3, c: 52461}, {f: 16, c: 52465},
+ {f: 6, c: 52482}, {f: 2, c: 52490}, {f: 3, c: 52493}, {f: 7, c: 52497},
+ 52506, 52508, {f: 6, c: 52510}, {f: 3, c: 52517}, {f: 3, c: 52521},
+ {f: 12, c: 52525}, {f: 34, c: 52538}, {f: 3, c: 52573}, {f: 3, c: 52577},
+ {f: 7, c: 52581}, 52590, 52592, {f: 6, c: 52594}, {f: 15, c: 52601},
+ {f: 11, c: 52617}, {f: 2, c: 52630}, {f: 3, c: 52633}, {f: 7, c: 52637},
+ 52646, 52648, {f: 6, c: 52650}, {f: 19, c: 52657}, {f: 7, c: 52677},
+ {f: 3, c: 52685}, {f: 23, c: 52689}, {f: 3, c: 52713}, {f: 3, c: 52717},
+ {f: 7, c: 52721}, 52730, 52732, {f: 6, c: 52734}, {f: 3, c: 52741},
+ {f: 3, c: 52745}, {f: 7, c: 52749}, {f: 4, c: 52757}, {f: 6, c: 52762},
+ {f: 2, c: 52770}, {f: 3, c: 52773}, {f: 7, c: 52777}, 52786, 52788,
+ {f: 34, c: 52790}, {f: 2, c: 52826}, {f: 2, c: 52829}, {f: 6, c: 52834},
+ 52842, 52844, {f: 6, c: 52846}, {f: 2, c: 52854}, {f: 3, c: 52857},
+ {f: 7, c: 52861}, 52870, 52872, {f: 6, c: 52874}, {f: 2, c: 52882},
+ {f: 3, c: 52885}, {f: 7, c: 52889}, 52898, {f: 6, c: 52902},
+ {f: 19, c: 52910}, {f: 34, c: 52930}, {f: 2, c: 52966}, {f: 2, c: 52969},
+ {f: 7, c: 52973}, 52982, {f: 6, c: 52986}, {f: 2, c: 52994},
+ {f: 3, c: 52997}, {f: 7, c: 53001}, 53010, 53012, {f: 6, c: 53014},
+ {f: 3, c: 53021}, {f: 3, c: 53025}, {f: 7, c: 53029}, 53038,
+ {f: 6, c: 53042}, {f: 27, c: 53049}, {f: 2, c: 53078}, {f: 3, c: 53081},
+ {f: 7, c: 53085}, 53094, 53096, {f: 6, c: 53098}, {f: 2, c: 53106},
+ {f: 3, c: 53109}, {f: 7, c: 53113}, {f: 4, c: 53121}, {f: 6, c: 53126},
+ {f: 20, c: 53133}, {f: 6, c: 53154}, {f: 7, c: 53161}, {f: 19, c: 53169},
+ {f: 27, c: 53189}, {f: 2, c: 53218}, {f: 3, c: 53221}, {f: 7, c: 53225},
+ 53234, 53236, {f: 6, c: 53238}, {f: 3, c: 53245}, {f: 3, c: 53249},
+ {f: 12, c: 53253}, {f: 6, c: 53266}, {f: 20, c: 53273}, {f: 6, c: 53294},
+ {f: 2, c: 53302}, {f: 3, c: 53305}, {f: 7, c: 53309}, 53318, 53320,
+ {f: 6, c: 53322}, {f: 3, c: 53329}, {f: 3, c: 53333}, {f: 7, c: 53337},
+ {f: 11, c: 53345}, {f: 2, c: 53358}, {f: 3, c: 53361}, {f: 7, c: 53365},
+ {f: 3, c: 53374}, {f: 34, c: 53378}, {f: 2, c: 53414}, {f: 3, c: 53417},
+ {f: 7, c: 53421}, 53430, 53432, {f: 6, c: 53434}, {f: 2, c: 53442},
+ {f: 3, c: 53445}, {f: 6, c: 53450}, 53458, {f: 6, c: 53462},
+ {f: 2, c: 53470}, {f: 3, c: 53473}, {f: 7, c: 53477}, 53486,
+ {f: 6, c: 53490}, {f: 20, c: 53497}, {f: 34, c: 53518}, {f: 2, c: 53554},
+ {f: 3, c: 53557}, 53561, {f: 5, c: 53563}, 53570, {f: 6, c: 53574},
+ {f: 2, c: 53582}, {f: 3, c: 53585}, {f: 7, c: 53589}, 53598, 53600,
+ {f: 6, c: 53602}, {f: 3, c: 53609}, {f: 15, c: 53613}, {f: 7, c: 53629},
+ {f: 3, c: 53637}, {f: 23, c: 53641}, {f: 2, c: 53666}, {f: 3, c: 53669},
+ {f: 7, c: 53673}, 53682, 53684, {f: 4, c: 53686}, 53691, {f: 3, c: 53693},
+ {f: 23, c: 53697}, {f: 27, c: 53721}, {f: 3, c: 53749}, {f: 14, c: 53753},
+ 53768, {f: 6, c: 53770}, {f: 27, c: 53777}, {f: 2, c: 53806},
+ {f: 3, c: 53809}, {f: 7, c: 53813}, 53822, 53824, {f: 6, c: 53826},
+ {f: 19, c: 53833}, {f: 7, c: 53853}, {f: 27, c: 53861}, {f: 2, c: 53890},
+ {f: 3, c: 53893}, {f: 7, c: 53897}, {f: 3, c: 53906}, {f: 6, c: 53910},
+ {f: 3, c: 53917}, {f: 3, c: 53921}, {f: 7, c: 53925}, {f: 4, c: 53933},
+ {f: 6, c: 53938}, {f: 2, c: 53946}, {f: 2, c: 53949}, 53953,
+ {f: 5, c: 53955}, 53962, {f: 8, c: 53964}, {f: 3, c: 53973},
+ {f: 3, c: 53977}, {f: 7, c: 53981}, {f: 10, c: 53990}, {f: 2, c: 54002},
+ {f: 3, c: 54005}, {f: 7, c: 54009}, 54018, 54020, {f: 6, c: 54022}, 54031,
+ {f: 3, c: 54033}, 54037, {f: 5, c: 54039}, 54046, {f: 3, c: 54050},
+ {f: 2, c: 54054}, {f: 2, c: 54058}, {f: 3, c: 54061}, {f: 7, c: 54065},
+ 54074, {f: 6, c: 54078}, {f: 54, c: 54086}, {f: 2, c: 54142},
+ {f: 3, c: 54145}, {f: 7, c: 54149}, 54158, {f: 6, c: 54162},
+ {f: 2, c: 54170}, {f: 3, c: 54173}, {f: 7, c: 54177}, 54186, 54188,
+ {f: 6, c: 54190}, {f: 3, c: 54197}, {f: 3, c: 54201}, {f: 7, c: 54205},
+ {f: 2, c: 54214}, {f: 6, c: 54218}, {f: 7, c: 54225}, {f: 8, c: 54233},
+ 54242, {f: 8, c: 54244}, {f: 2, c: 54254}, {f: 3, c: 54257},
+ {f: 7, c: 54261}, 54270, 54272, {f: 6, c: 54274}, {f: 20, c: 54281},
+ {f: 34, c: 54302}, {f: 3, c: 54337}, {f: 23, c: 54341}, {f: 3, c: 54365},
+ {f: 3, c: 54369}, {f: 8, c: 54373}, 54382, {f: 8, c: 54384},
+ {f: 2, c: 54394}, {f: 2, c: 54397}, 54401, {f: 5, c: 54403}, 54410, 54412,
+ {f: 6, c: 54414}, {f: 20, c: 54421}, {f: 34, c: 54442}, {f: 3, c: 54477},
+ {f: 3, c: 54481}, {f: 7, c: 54485}, {f: 2, c: 54493}, {f: 8, c: 54496},
+ {f: 3, c: 54505}, {f: 3, c: 54509}, {f: 7, c: 54513}, {f: 2, c: 54521},
+ 54524, {f: 6, c: 54526}, {f: 3, c: 54533}, {f: 3, c: 54537},
+ {f: 7, c: 54541}, 54550, {f: 36, c: 54552}, {f: 2, c: 54590},
+ {f: 3, c: 54593}, {f: 7, c: 54597}, 54606, 54608, {f: 6, c: 54610},
+ {f: 2, c: 54618}, {f: 3, c: 54621}, {f: 4, c: 54625}, {f: 2, c: 54630},
+ 54634, 54636, {f: 6, c: 54638}, {f: 2, c: 54646}, {f: 3, c: 54649},
+ {f: 7, c: 54653}, 54662, {f: 6, c: 54666}, {f: 20, c: 54673},
+ {f: 34, c: 54694}, {f: 2, c: 54730}, {f: 3, c: 54733}, 54737,
+ {f: 5, c: 54739}, 54746, 54748, {f: 6, c: 54750}, {f: 2, c: 54758},
+ {f: 3, c: 54761}, {f: 7, c: 54765}, 54774, 54776, {f: 6, c: 54778},
+ {f: 2, c: 54786}, {f: 3, c: 54789}, {f: 7, c: 54793}, 54802,
+ {f: 6, c: 54806}, {f: 3, c: 54813}, {f: 3, c: 54817}, {f: 8, c: 54821},
+ {f: 10, c: 54830}, {f: 2, c: 54842}, {f: 3, c: 54845}, {f: 4, c: 54849},
+ {f: 2, c: 54854}, 54858, 54860, {f: 3, c: 54862}, {f: 2, c: 54866},
+ {f: 2, c: 54870}, {f: 3, c: 54873}, {f: 10, c: 54877}, 54888,
+ {f: 6, c: 54890}, {f: 2, c: 54898}, {f: 14, c: 54901}, 54916,
+ {f: 6, c: 54918}, {f: 2, c: 54926}, {f: 3, c: 54929}, {f: 8, c: 54933},
+ 54942, 54944, {f: 6, c: 54946}, {f: 3, c: 54953}, {f: 3, c: 54957},
+ {f: 8, c: 54961}, 54970, {f: 8, c: 54972}, {f: 2, c: 54982},
+ {f: 3, c: 54985}, {f: 4, c: 54989}, {f: 2, c: 54994}, {f: 2, c: 54997},
+ 55000, {f: 6, c: 55002}, {f: 3, c: 55009}, {f: 3, c: 55013},
+ {f: 7, c: 55017}, {f: 4, c: 55025}, {f: 6, c: 55030}, {f: 2, c: 55038},
+ {f: 3, c: 55041}, {f: 12, c: 55045}, {f: 6, c: 55058}, {f: 2, c: 55066},
+ {f: 3, c: 55069}, {f: 7, c: 55073}, 55082, 55084, {f: 6, c: 55086},
+ {f: 2, c: 55094}, {f: 3, c: 55097}, {f: 7, c: 55101}, {f: 2, c: 55109},
+ 55112, {f: 6, c: 55114}, {f: 2, c: 55122}, 55125, {f: 6, c: 55130}, 55138,
+ 55140, {f: 3, c: 55142}, {f: 2, c: 55146}, {f: 3, c: 55149},
+ {f: 3, c: 55153}, {f: 7, c: 55157}, {f: 3, c: 55166}, {f: 6, c: 55170},
+ {f: 2, c: 55178}, {f: 3, c: 55181}, {f: 7, c: 55185}, 55194, 55196,
+ {f: 6, c: 55198}],
+ 'Adobe-CNS1': [{f: 95, c: 32}, {s: 3}, 12288, 65292, {f: 2, c: 12289}, 65294,
+ 8226, 65307, 65306, 65311, 65281, 65072, 8230, 8229, 65104, 65380, 65106,
+ 183, {f: 4, c: 65108}, 65372, 8211, 65073, 8212, {s: 4}, {f: 2, c: 65288},
+ {f: 2, c: 65077}, 65371, 65373, {f: 2, c: 65079}, {f: 2, c: 12308},
+ {f: 2, c: 65081}, {f: 2, c: 12304}, {f: 2, c: 65083}, {f: 2, c: 12298},
+ {f: 2, c: 65085}, {f: 2, c: 12296}, {f: 2, c: 65087}, {f: 2, c: 12300},
+ {f: 2, c: 65089}, {f: 2, c: 12302}, {f: 2, c: 65091}, {f: 6, c: 65113},
+ {f: 2, c: 8216}, {f: 2, c: 8220}, {f: 2, c: 12317}, 8245, 8242, 65283,
+ 65286, 65290, 8251, 167, 12291, 9675, 9679, 9651, 9650, 9678, 9734, 9733,
+ 9671, 9670, 9633, 9632, 9661, 9660, 12963, 8453, 8254, 0, 65343, 0,
+ {f: 2, c: 65097}, {f: 2, c: 65101}, {f: 2, c: 65099}, {f: 3, c: 65119},
+ 65291, 65293, 215, 247, 177, 8730, 65308, 65310, 65309, {f: 2, c: 8806},
+ 8800, 8734, 8786, 8801, {f: 5, c: 65122}, 8764, {f: 2, c: 8745}, 8869,
+ 8736, 8735, 8895, 13266, 13265, 8747, 8750, 8757, 8756, 9792, 9794, 9793,
+ 9737, 8593, 8595, 8594, 8592, {f: 2, c: 8598}, 8601, 8600, 8741, 8739, 0,
+ 0, 65295, 65340, 65284, 165, 12306, {f: 2, c: 162}, 65285, 65312, 8451,
+ 8457, {f: 3, c: 65129}, 13269, {f: 3, c: 13212}, 13262, 13217,
+ {f: 2, c: 13198}, 13252, 176, [20825, 58834], [20827, 58835],
+ [20830, 58837], [20829, 58836], 20833, 20835, 21991, [29929, 58044],
+ [31950, 58191], {f: 8, c: 9601}, 9615, 9614, 9613, 9612, 9611, 9610, 9609,
+ 9532, 9524, 9516, 9508, 9500, 9620, 9472, 9474, 9621, 9484, 9488, 9492,
+ 9496, {f: 2, c: 9581}, 9584, 9583, 9552, 9566, 9578, 9569, {f: 2, c: 9698},
+ 9701, 9700, {f: 3, c: 9585}, {f: 10, c: 65296}, {f: 10, c: 8544},
+ {f: 9, c: 12321}, 0, [21316, 57443], 0, {f: 26, c: 65313},
+ {f: 26, c: 65345}, {f: 17, c: 913}, {f: 7, c: 931}, {f: 17, c: 945},
+ {f: 7, c: 963}, {f: 37, c: 12549}, 729, 714, 711, 715, [9312, 63153],
+ [9313, 63154], [9314, 63155], [9315, 63156], [9316, 63157], [9317, 63158],
+ [9318, 63159], [9319, 63160], [9320, 63161], [9321, 63162], [9332, 63163],
+ [9333, 63164], [9334, 63165], [9335, 63166], [9336, 63167], [9337, 63168],
+ [9338, 63169], [9339, 63170], [9340, 63171], [9341, 63172], [8560, 63173],
+ [8561, 63174], [8562, 63175], [8563, 63176], [8564, 63177], [8565, 63178],
+ [8566, 63179], [8567, 63180], [8568, 63181], [8569, 63182], [12033, 20008],
+ [12034, 20022, 63183], [12035, 20031, 63184], [12037, 20101, 63185],
+ [12039, 20128, 63186], [12044, 20866, 63187], [12045, 20886, 63188],
+ [12046, 20907, 63189], [12051, 21241, 63190], [12054, 21304, 63191],
+ [12057, 21353, 63192], [12059, 21430, 63193],
+ [12065, 12066, 22786, 22794, 63194], [12071, 23424, 63195],
+ [12078, 24027, 63196], [12083, 24186, 63197], [12084, 24191, 63198],
+ [12085, 24308], [12089, 24400, 63200], [12090, 24417, 63201],
+ [12097, 25908, 63202], [12102, 26080], [12135, 30098, 63204],
+ [12136, 30326], [12193, 36789, 63206], [12202, 38582], {f: 32, c: 9216},
+ 9249, [12032, 19968], [12036, 20057], 19969, 19971, 20035, 20061, 20102,
+ [12038, 20108], [12040, 20154], [12041, 20799], [12042, 20837],
+ [12043, 20843], [12047, 20960], [12049, 20992], 20993, [12050, 21147],
+ [12052, 21269], [12055, 21313], [12056, 21340], [12060, 21448], 19977,
+ 19979, 19976, 19978, 20011, 20024, 20961, 20037, 20040, 20063, 20062,
+ 20110, 20129, [20800, 64012], 20995, 21242, 21315, 21449, [12061, 21475],
+ [12063, 22303], [12064, 22763], [12067, 22805], [12068, 22823],
+ [12069, 22899], [12070, 23376], 23377, 23379, [12072, 23544],
+ [12073, 23567], [12074, 23586], [12075, 23608], [12077, 23665], 24029,
+ [12079, 24037], [12080, 24049], {f: 2, c: 24050}, [12081, 24062],
+ [12082, 24178], [12086, 24318], [12087, 24331], [12088, 24339], 25165,
+ 19985, 19984, 19981, 20013, 20016, 20025, 20043, 23609, 20104, 20113,
+ 20117, 20114, 20116, 20130, 20161, 20160, 20163, {f: 2, c: 20166}, 20173,
+ {f: 2, c: 20170}, 20164, 20803, 20801, 20839, {f: 2, c: 20845}, 20844,
+ 20887, 20982, {f: 3, c: 20998}, 21243, {f: 2, c: 21246}, 21270, 21305,
+ 21320, 21319, 21317, 21342, 21380, 21451, 21450, 21453, 22764, 22825,
+ 22827, 22826, 22829, 23380, 23569, 23588, 23610, 23663, 24052, 24187,
+ 24319, {f: 2, c: 24340}, [12092, 24515], [12093, 25096], [12094, 25142],
+ [12095, 25163], 25166, [12096, 25903], [12098, 25991], [12099, 26007],
+ [12100, 26020], [12101, 26041], [12103, 26085], [12104, 26352],
+ [12105, 26376], [12106, 26408], [12107, 27424], [12108, 27490],
+ [12109, 27513], [12111, 27595], [12112, 27604], [12113, 27611],
+ [12114, 27663], [12116, 27700], [12117, 28779], [12118, 29226],
+ [12119, 29238], [12120, 29243], [12122, 29255], [12123, 29273],
+ [12124, 29275], [12125, 29356], 29579, 19993, 19990, 19989, 19988, 19992,
+ 20027, 20045, 20047, 20046, 20197, 20184, {f: 4, c: 20180},
+ {f: 2, c: 20195}, 20185, 20190, 20805, 20804, {f: 2, c: 20873}, 20908,
+ {f: 2, c: 20985}, 20984, 21002, 21152, 21151, [21253, 57435], 21254, 21271,
+ 21277, 20191, 21322, 21321, 21345, 21344, 21359, 21358, 21435, 21487,
+ 21476, 21491, 21484, 21486, 21481, 21480, 21500, 21496, 21493, 21483,
+ 21478, 21482, 21490, 21489, 21488, 21477, 21485, 21499, 22235, 22234,
+ 22806, 22830, 22833, 22900, 22902, 23381, 23427, 23612, 24040, 24039,
+ 24038, {f: 2, c: 24066}, 24179, 24188, 24321, 24344, 24343, 24517, 25098,
+ {f: 2, c: 25171}, 25170, 25169, 26021, 26086, 26414, 26412,
+ {f: 2, c: 26410}, 26413, 27491, 27597, 27665, 27664, 27704, 27713, 27712,
+ 27710, 29359, [12126, 29572], [12127, 29577], [12128, 29916],
+ [12129, 29926], [12130, 29976], [12131, 29983], [12132, 29992], 29993,
+ [12133, 30000], {f: 3, c: 30001}, [12134, 30091], [12137, 30333],
+ [12138, 30382], [12139, 30399], [12140, 30446], [12141, 30683],
+ [12142, 30690], [12143, 30707], [12144, 31034], [12146, 31166],
+ [12147, 31348], [12148, 31435], {f: 2, c: 19998}, {f: 2, c: 20050}, 20073,
+ 20121, 20132, 20134, 20133, 20223, 20233, 20249, 20234, 20245, 20237,
+ {f: 2, c: 20240}, 20239, 20210, 20214, 20219, 20208, 20211, 20221, 20225,
+ 20235, 20809, 20807, 20806, 20808, 20840, 20849, 20877, 20912, 21015,
+ {f: 2, c: 21009}, 21006, 21014, 21155, 21256, 21281, 21280,
+ {f: 2, c: 21360}, 21513, 21519, 21516, 21514, 21520, 21505, 21515, 21508,
+ 21521, 21517, 21512, 21507, 21518, 21510, 21522, 22240, 22238, 22237,
+ 22323, 22320, 22312, 22317, 22316, 22319, 22313, {f: 2, c: 22809},
+ {f: 2, c: 22839}, 22916, 22904, 22915, 22909, 22905, 22914, 22913,
+ {f: 2, c: 23383}, {f: 2, c: 23431}, 23429, 23433, 23546, 23574, 23673,
+ 24030, 24070, 24182, 24180, 24335, 24347, 24537, 24534, 25102,
+ {f: 2, c: 25100}, 25104, 25187, 25179, 25176, 25910, 26089, 26088,
+ {f: 2, c: 26092}, {f: 2, c: 26354}, 26377, 26429, 26420, 26417, 26421,
+ 27425, 27492, 27515, 27670, 27741, 27735, 27737, {f: 2, c: 27743}, 27728,
+ 27733, 27745, 27739, {f: 2, c: 27725}, 28784, 29279, 29277, 30334,
+ [12149, 31481], [12150, 31859], [12151, 31992], [12152, 32566],
+ [12154, 32650], [12155, 32701], [12156, 32769], 32771, [12157, 32780],
+ [12158, 32786], [12159, 32819], [12160, 32895], [12161, 32905],
+ {f: 2, c: 32907}, [12162, 33251], [12163, 33258], [12164, 33267],
+ [12165, 33276], [12166, 33292], [12167, 33307], [12168, 33311],
+ [12169, 33390], [12170, 33394], 33406, [12173, 34411], [12174, 34880],
+ [12175, 34892], [12176, 34915], 35199, 38433, 20018, 20136, 20301, 20303,
+ 20295, 20311, 20318, 20276, 20315, 20309, 20272, {f: 2, c: 20304}, 20285,
+ 20282, 20280, 20291, 20308, 20284, 20294, 20323, 20316, 20320, 20271,
+ 20302, 20278, 20313, 20317, 20296, 20314, 20812, 20811, 20813, 20853,
+ {f: 2, c: 20918}, 21029, 21028, {f: 2, c: 21033}, 21032, 21163,
+ {f: 2, c: 21161}, 21164, 21283, 21363, 21365, 21533, 21549, 21534, 21566,
+ 21542, 21582, 21543, 21574, 21571, 21555, 21576, 21570, 21531, 21545,
+ 21578, 21561, 21563, 21560, 21550, {f: 2, c: 21557}, 21536, 21564, 21568,
+ 21553, 21547, 21535, 21548, 22250, 22256, 22244, 22251, 22346, 22353,
+ 22336, 22349, 22343, 22350, 22334, 22352, 22351, 22331, 22767, 22846,
+ 22941, 22930, 22952, 22942, 22947, 22937, 22934, 22925, 22948, 22931,
+ 22922, 22949, 23389, 23388, {f: 2, c: 23386}, 23436, 23435, 23439, 23596,
+ {f: 2, c: 23616}, 23615, 23614, {f: 2, c: 23696}, 23700, 23692, 24043,
+ 24076, 24207, 24199, 24202, 24311, 24324, 24351, 24420, 24418, 24439,
+ 24441, 24536, 24524, 24535, 24525, 24561, 24555, 24568, 24554, 25106,
+ 25105, 25220, 25239, 25238, 25216, 25206, 25225, 25197, 25226, 25212,
+ 25214, 25209, 25203, 25234, 25199, 25240, 25198, 25237, 25235, 25233,
+ 25222, 25913, 25915, 25912, 26097, 26356, 26463, {f: 4, c: 26446}, 26460,
+ 26454, [26462, 57801], 26441, 26438, 26464, 26451, 26455, 27493, 27599,
+ 27714, 27742, 27801, 27777, {f: 2, c: 27784}, 27781, 27803, 27754, 27770,
+ 27792, 27760, 27788, 27752, 27798, 27794, 27773, 27779, 27762, 27774,
+ 27764, 27782, 27766, 27789, 27796, 27800, 27778, 28790, {f: 2, c: 28796},
+ 28792, 29282, 29281, 29280, 29380, 29378, 29590, 29996, 29995,
+ {f: 2, c: 30007}, 30338, 30447, 30691, 31169, 31168, 31167, 31350, 31995,
+ 32597, 32918, 32915, 32925, 32920, 32923, 32922, 32946, 33391, 33426,
+ 33419, 33421, [12178, 35211], [12179, 35282], [12180, 35328],
+ [12181, 35895], [12182, 35910], [12183, 35925], [12185, 35997],
+ [12186, 36196], [12187, 36208], [12188, 36275], [12189, 36523],
+ [12190, 36554], [12191, 36763], [12192, 36784], 36802, 36806, 36805, 36804,
+ 24033, [12194, 37009], 37026, 37034, 37030, 37027, [12195, 37193],
+ [12196, 37318], [12197, 37324], 38450, 38446, 38449, 38442, 38444, 20006,
+ 20054, 20083, 20107, 20123, 20126, {f: 2, c: 20139}, 20335, 20381, 20365,
+ 20339, 20351, 20332, 20379, 20363, 20358, 20355, 20336, 20341, 20360,
+ 20329, 20347, 20374, 20350, 20367, 20369, 20346, 20820, 20818, 20821,
+ 20841, 20855, 20854, 20856, 20925, 20989, 21051, 21048, 21047, 21050,
+ 21040, 21038, 21046, 21057, 21182, 21179, 21330, 21332, 21331, 21329,
+ 21350, {f: 3, c: 21367}, 21462, 21460, 21463, 21619, 21621, 21654, 21624,
+ 21653, 21632, 21627, 21623, 21636, 21650, 21638, 21628, 21648, 21617,
+ 21622, 21644, 21658, 21602, 21608, 21643, 21629, 21646, 22266, 22403,
+ 22391, 22378, 22377, 22369, 22374, 22372, 22396, 22812, 22857,
+ {f: 2, c: 22855}, 22852, 22868, 22974, 22971, 22996, 22969, 22958, 22993,
+ 22982, 22992, 22989, 22987, 22995, 22986, 22959, 22963, 22994, 22981,
+ 23391, 23396, 23395, 23447, 23450, 23448, 23452, 23449, 23451, 23578,
+ 23624, {f: 2, c: 23621}, 23735, 23713, 23736, 23721, 23723, 23729, 23731,
+ 24088, 24090, 24086, 24085, 24091, 24081, 24184, 24218, 24215, 24220,
+ {f: 2, c: 24213}, 24310, {f: 2, c: 24358}, 24361, {f: 2, c: 24448}, 24447,
+ 24444, 24541, 24544, 24573, 24565, 24575, 24591, 24596, 24623, 24629,
+ 24598, 24618, 24597, 24609, 24615, 24617, 24619, 24603, 25110, 25109,
+ 25151, 25150, 25152, 25215, 25289, 25292, 25284, 25279, 25282, 25273,
+ 25298, 25307, 25259, {f: 2, c: 25299}, 25291, 25288, 25256, 25277, 25276,
+ [25296, 60582], 25305, 25287, 25293, 25269, 25306, 25265, 25304,
+ {f: 2, c: 25302}, 25286, 25260, [25294, 61010], 25918, 26023, 26044, 26106,
+ 26132, 26131, 26124, 26118, 26114, 26126, 26112, 26127, 26133, 26122,
+ 26119, 26381, 26379, 26477, 26507, 26517, 26481, 26524, 26483, 26487,
+ 26503, 26525, 26519, {f: 2, c: 26479}, 26495, 26505, 26494, 26512, 26485,
+ 26522, 26515, 26492, 26474, 26482, 27427, {f: 2, c: 27494}, 27519, 27667,
+ 27675, 27875, 27880, 27891, 27825, 27852, 27877, 27827, {f: 2, c: 27837},
+ 27836, 27874, 27819, 27861, 27859, 27832, 27844, 27833, 27841, 27822,
+ 27863, 27845, 27889, 27839, 27835, 27873, 27867, 27850, 27820, 27887,
+ 27868, 27862, 27872, 28821, 28814, 28818, 28810, 28825, {f: 2, c: 29228},
+ 29240, 29256, 29287, 29289, 29376, 29390, 29401, 29399, 29392, 29609,
+ 29608, 29599, 29611, 29605, 30013, 30109, {f: 2, c: 30105}, 30340, 30402,
+ 30450, 30452, 30693, 30717, 31038, {f: 2, c: 31040}, 31177, 31176, 31354,
+ 31353, 31482, 31998, 32596, 32652, 32651, [32773, 58236], 32954, 32933,
+ 32930, 32945, 32929, 32939, 32937, 32948, 32938, 32943, 33253, 33278,
+ 33293, 33459, 33437, 33433, 33453, 33469, 33439, 33465, 33457, 33452,
+ 33445, 33455, 33464, 33443, 33456, 33470, 33463, 34382, 34417, 21021,
+ 34920, 36555, 36814, 36820, 36817, 37045, 37048, 37041, 37046, 37319,
+ [12198, 37329], [12199, 38263], [12200, 38272], [12201, 38428], 38464,
+ 38463, 38459, 38468, 38466, [12203, 38585], [12204, 38632], 38738,
+ [12206, 38750], 20127, {f: 2, c: 20141}, 20449, 20405, 20399, 20415, 20448,
+ 20433, 20431, 20445, 20419, 20406, 20440, 20447, 20426, 20439, 20398,
+ 20432, 20420, 20418, 20442, 20430, 20446, 20407, 20823, 20882, 20881,
+ 20896, 21070, 21059, 21066, 21069, 21068, 21067, 21063, 21191, 21193,
+ 21187, 21185, 21261, 21335, 21371, 21402, 21467, 21676, 21696, 21672,
+ 21710, 21705, 21688, 21670, 21683, 21703, 21698, 21693, 21674, 21697,
+ 21700, 21704, 21679, 21675, 21681, 21691, 21673, 21671, 21695, 22271,
+ 22402, 22411, 22432, 22435, 22434, 22478, 22446, 22419, 22869, 22865,
+ 22863, 22862, 22864, 23004, 23000, 23039, 23011, 23016, 23043, 23013,
+ 23018, 23002, 23014, 23041, 23035, 23401, 23459, 23462, 23460, 23458,
+ 23461, 23553, {f: 2, c: 23630}, 23629, 23627, 23769, 23762, 24055, 24093,
+ 24101, 24095, 24189, 24224, 24230, 24314, 24328, 24365, 24421, 24456,
+ 24453, {f: 2, c: 24458}, 24455, 24460, 24457, 24594, 24605, 24608, 24613,
+ 24590, 24616, 24653, 24688, 24680, [24674, 60712], 24646, 24643, 24684,
+ 24683, 24682, 24676, 25153, 25308, 25366, 25353, 25340, 25325, 25345,
+ 25326, 25341, 25351, 25329, 25335, 25327, 25324, 25342, 25332, 25361,
+ 25346, 25919, 25925, 26027, 26045, 26082, 26149, 26157, 26144, 26151,
+ 26159, 26143, 26152, 26161, 26148, 26359, 26623, 26579, 26609, 26580,
+ 26576, 26604, 26550, 26543, 26613, 26601, 26607, 26564, 26577, 26548,
+ 26586, 26597, 26552, 26575, 26590, 26611, 26544, 26585, 26594, 26589,
+ 26578, 27498, 27523, 27526, 27573, 27602, 27607, 27679, 27849, 27915,
+ 27954, 27946, 27969, 27941, 27916, 27953, 27934, 27927, 27963,
+ {f: 2, c: 27965}, 27958, 27931, 27893, 27961, 27943, 27960, 27945, 27950,
+ 27957, 27918, 27947, 28843, 28858, 28851, 28844, 28847, 28845, 28856,
+ 28846, 28836, 29232, 29298, 29295, 29300, 29417, {f: 2, c: 29408}, 29623,
+ 29642, 29627, 29618, 29645, 29632, 29619, 29978, 29997, 30031, 30028,
+ 30030, 30027, 30123, {f: 2, c: 30116}, {f: 2, c: 30114}, 30328,
+ {f: 3, c: 30342}, 30408, 30406, 30403, 30405, 30465, 30457, 30456, 30473,
+ 30475, 30462, 30460, 30471, 30684, 30722, 30740, {f: 2, c: 30732}, 31046,
+ 31049, 31048, 31047, {f: 2, c: 31161}, {f: 2, c: 31185}, 31179, 31359,
+ 31361, 31487, 31485, 31869, 32002, 32005, 32000, 32009, 32007, 32004,
+ 32006, 32568, 32654, 32703, 32784, 32781, 32785, 32822, 32982, 32997,
+ 32986, {f: 2, c: 32963}, 32972, 32993, 32987, 32974, 32990, 32996, 32989,
+ 33268, 33314, 33511, 33539, 33541, 33507, 33499, 33510, 33540, 33509,
+ 33538, 33545, 33490, 33495, 33521, 33537, 33500, 33492, 33489, 33502,
+ 33491, 33503, 33519, 33542, 34384, 34425, 34427, 34426, 34893, 34923,
+ 35201, 35284, 35336, {f: 2, c: 35330}, 35998, 36000, 36212, 36211, 36276,
+ 36557, 36556, 36848, 36838, 36834, 36842, 36837, 36845, 36843, 36836,
+ 36840, 37066, 37070, 37057, 37059, 37195, 37194, 37325, 38274, 38480,
+ {f: 3, c: 38475}, [12207, 38754], [12208, 38761], [12209, 38859],
+ [12210, 38893], [12211, 38899], [12212, 38913], [12213, 39080],
+ [12214, 39131], [12215, 39135], [12216, 39318], [12217, 39321], 20056,
+ 20147, {f: 2, c: 20492}, 20515, 20463, 20518, 20517, 20472, [20521, 57375],
+ 20502, 20486, 20540, 20511, 20506, 20498, 20497, 20474, 20480, 20500,
+ 20520, 20465, 20513, 20491, 20505, 20504, 20467, 20462, 20525, 20522,
+ 20478, 20523, 20489, 20860, {f: 2, c: 20900}, 20898, 20941, 20940, 20934,
+ 20939, 21078, 21084, 21076, 21083, 21085, 21290, [21375, 57459], 21407,
+ 21405, 21471, 21736, 21776, 21761, 21815, 21756, 21733, 21746, 21766,
+ 21754, 21780, 21737, 21741, 21729, 21769, 21742, 21738, 21734, 21799,
+ 21767, 21757, 21775, {f: 2, c: 22275}, 22466, 22484, 22475, 22467, 22537,
+ 22799, {f: 2, c: 22871}, 22874, 23057, 23064, 23068, 23071, 23067, 23059,
+ 23020, 23072, 23075, 23081, 23077, 23052, 23049, 23403, 23640, 23472,
+ 23475, 23478, 23476, 23470, 23477, 23481, 23480, 23556, 23633, 23637,
+ 23632, 23789, 23805, 23803, 23786, 23784, 23792, 23798, 23809, 23796,
+ 24046, 24109, 24107, 24235, 24237, 24231, 24369, 24466, 24465, 24464,
+ 24665, 24675, 24677, 24656, 24661, 24685, 24681, 24687, 24708, 24735,
+ 24730, 24717, 24724, 24716, 24709, 24726, 25159, 25331, 25352, 25343,
+ 25422, 25406, 25391, 25429, 25410, 25414, 25423, 25417, 25402, 25424,
+ 25405, {f: 2, c: 25386}, 25384, 25421, 25420, {f: 2, c: 25928}, 26009,
+ 26049, 26053, 26178, 26185, 26191, 26179, 26194, 26188, 26181, 26177,
+ 26360, {f: 2, c: 26388}, 26391, 26657, 26680, 26696, 26694, 26707, 26681,
+ 26690, 26708, 26665, 26803, 26647, 26700, 26705, 26685, 26612, 26704,
+ 26688, 26684, 26691, 26666, 26693, 26643, 26648, 26689, 27530, 27529,
+ 27575, 27683, {f: 2, c: 27687}, 27686, 27684, 27888, 28010, 28053, 28040,
+ 28039, 28006, 28024, 28023, 27993, 28051, 28012, 28041, 28014, 27994,
+ 28020, 28009, 28044, 28042, 28025, 28037, 28005, 28052, 28874, 28888,
+ 28900, 28889, 28872, 28879, 29241, 29305, 29436, 29433, 29437, 29432,
+ 29431, 29574, 29677, 29705, 29678, 29664, 29674, 29662, 30036, 30045,
+ 30044, 30042, 30041, 30142, 30149, 30151, {f: 2, c: 30130}, 30141, 30140,
+ 30137, 30146, 30136, 30347, 30384, 30410, {f: 2, c: 30413}, 30505,
+ {f: 2, c: 30495}, 30504, 30697, 30768, 30759, 30776, 30749, 30772, 30775,
+ 30757, 30765, 30752, 30751, 30770, 31061, 31056, 31072, 31071, 31062,
+ 31070, 31069, 31063, 31066, 31204, [31203, 60418], 31207, 31199, 31206,
+ 31209, 31192, 31364, 31368, 31449, 31494, 31505, 31881, 32033, 32023,
+ 32011, 32010, 32032, 32034, 32020, 32016, 32021, 32026, 32028, 32013,
+ 32025, 32027, 32570, 32607, 32660, 32709, 32705, 32774, 32772, 32792,
+ 32789, 32793, 32791, 32829, 32831, 33009, 33026, 33008, 33029, 33005,
+ 33012, 33030, 33016, 33011, 33032, 33021, 33034, 33020, 33007, 33261,
+ 33260, 33280, 33296, {f: 2, c: 33322}, 33320, 33324, 33467, 33579, 33618,
+ 33620, 33610, 33592, 33616, 33609, 33589, 33588, 33615, 33586, 33593,
+ 33590, 33559, 33600, 33585, 33576, 33603, 34388, 34442, 34474, 34451,
+ 34468, 34473, 34444, 34467, 34460, 34928, 34935, {f: 2, c: 34945}, 34941,
+ 34937, 35352, 35344, 35342, 35340, 35349, 35338, 35351, 35347, 35350,
+ 35343, 35345, 35912, 35962, 35961, {f: 2, c: 36001}, [36215, 58442], 36524,
+ 36562, 36564, 36559, 36785, 36865, 36870, 36855, 36864, 36858, 36852,
+ 36867, 36861, 36869, 36856, 37013, 37089, 37085, 37090, 37202, 37197,
+ 37196, 37336, 37341, 37335, 37340, 37337, 38275, {f: 2, c: 38498}, 38497,
+ 38491, 38493, 38500, 38488, 38494, 38587, 39138, [12218, 39340],
+ [12219, 39592], [12220, 39640], [12222, 39717], [12224, 39730],
+ [12225, 39740], 20094, 20602, [20605, 57382], 20572, 20551, 20547, 20556,
+ 20570, 20553, 20581, 20598, 20558, 20565, 20597, 20596, 20599, 20559,
+ 20495, 20591, 20589, 20828, 20885, 20976, 21098, 21103, 21202, 21209,
+ 21208, 21205, 21264, 21263, 21273, {f: 2, c: 21311}, 21310, 21443, 26364,
+ 21830, 21866, 21862, 21828, 21854, 21857, 21827, 21834, 21809, 21846,
+ 21839, 21845, 21807, 21860, 21816, 21806, 21852, 21804, 21859, 21811,
+ 21825, 21847, 22280, 22283, 22281, 22495, 22533, 22538, 22534, 22496,
+ 22500, 22522, 22530, 22581, 22519, 22521, 22816, 22882, 23094, 23105,
+ 23113, 23142, 23146, 23104, 23100, 23138, 23130, 23110, 23114, 23408,
+ 23495, 23493, 23492, 23490, 23487, 23494, 23561, 23560, 23559, 23648,
+ {f: 2, c: 23644}, 23815, 23814, 23822, 23835, 23830, 23842, 23825, 23849,
+ 23828, 23833, 23844, 23847, 23831, 24034, 24120, 24118, 24115, 24119,
+ {f: 2, c: 24247}, 24246, 24245, 24254, 24373, 24375, 24407, 24428, 24425,
+ 24427, 24471, 24473, 24478, 24472, 24481, 24480, 24476, 24703, 24739,
+ 24713, 24736, 24744, 24779, 24756, 24806, 24765, 24773, 24763, 24757,
+ 24796, 24764, 24792, 24789, 24774, 24799, 24760, 24794, 24775,
+ {f: 2, c: 25114}, 25160, 25504, 25511, 25458, 25494, 25506, 25509, 25463,
+ 25447, 25496, 25514, 25457, 25513, 25481, 25475, 25499, 25451, 25512,
+ 25476, 25480, 25497, 25505, 25516, 25490, 25487, 25472, 25467, 25449,
+ 25448, 25466, 25949, 25942, 25937, 25945, 25943, 21855, 25935, 25944,
+ 25941, 25940, 26012, 26011, 26028, 26063, {f: 2, c: 26059}, 26062, 26205,
+ 26202, 26212, 26216, 26214, 26206, 26361, 21207, 26395, 26753, 26799,
+ 26786, 26771, 26805, 26751, 26742, 26801, 26791, 26775, 26800, 26755,
+ 26820, 26797, 26758, 26757, 26772, 26781, 26792, 26783, 26785, 26754,
+ 27442, 27578, {f: 2, c: 27627}, 27691, 28046, 28092, 28147, 28121, 28082,
+ 28129, 28108, 28132, 28155, 28154, 28165, 28103, 28107, 28079, 28113,
+ 28078, 28126, 28153, 28088, 28151, 28149, 28101, 28114, 28186, 28085,
+ 28122, 28139, 28120, 28138, 28145, 28142, 28136, 28102, 28100, 28074,
+ 28140, 28095, 28134, 28921, {f: 2, c: 28937}, 28925, 28911, 29245, 29309,
+ 29313, 29468, 29467, 29462, 29459, 29465, 29575, 29701, 29706, 29699,
+ 29702, 29694, 29709, 29920, {f: 2, c: 29942}, 29980, 29986,
+ {f: 2, c: 30053}, 30050, 30064, 30095, {f: 2, c: 30164}, 30133, 30154,
+ 30157, 30350, 30420, 30418, 30427, 30519, 30526, 30524, 30518, 30520,
+ 30522, 30827, 30787, 30798, 31077, 31080, 31085, 31227, 31378, 31381,
+ 31520, 31528, 31515, 31532, 31526, 31513, 31518, 31534, 31890, 31895,
+ 31893, 32070, 32067, 32113, 32046, 32057, 32060, 32064, 32048, 32051,
+ 32068, 32047, 32066, 32050, 32049, 32573, 32670, 32666, 32716, 32718,
+ 32722, 32796, 32842, 32838, 33071, 33046, 33059, 33067, 33065, 33072,
+ 33060, 33282, 33333, 33335, 33334, 33337, 33678, 33694, 33688, 33656,
+ 33698, 33686, 33725, 33707, 33682, 33674, 33683, 33673, 33696, 33655,
+ {f: 2, c: 33659}, 33670, 33703, 34389, 24426, 34503, 34496, 34486, 34500,
+ 34485, 34502, 34507, 34481, 34479, 34505, 34899, 34974, 34952, 34987,
+ 34962, 34966, 34957, 34955, 35219, 35215, 35370, 35357, 35363, 35365,
+ 35377, 35373, 35359, 35355, 35362, 35913, 35930, 36009, 36012, 36011,
+ 36008, 36010, 36007, 36199, 36198, 36286, 36282, 36571, 36575, 36889,
+ 36877, 36890, 36887, 36899, 36895, 36893, 36880, 36885, 36894, 36896,
+ 36879, 36898, 36886, 36891, 36884, 37096, 37101, [37117, 58488], 37207,
+ 37326, 37365, 37350, 37347, 37351, 37357, 37353, 38281, 38506, 38517,
+ 38515, 38520, 38512, 38516, {f: 2, c: 38518}, 38508, 38592, 38634, 38633,
+ 31456, 31455, {f: 2, c: 38914}, [12226, 39770], [12227, 40165],
+ [12228, 40565], [12229, 40575], [12230, 40613], [12231, 40635], 20642,
+ 20621, 20613, 20633, 20625, 20608, 20630, 20632, 20634, 26368, 20977,
+ 21106, {f: 2, c: 21108}, 21097, 21214, 21213, 21211, 21338, 21413, 21883,
+ 21888, 21927, 21884, 21898, 21917, 21912, 21890, 21916, 21930, 21908,
+ 21895, 21899, 21891, 21939, 21934, 21919, 21822, 21938, 21914, 21947,
+ 21932, 21937, 21886, 21897, 21931, 21913, 22285, 22575, 22570, 22580,
+ 22564, {f: 2, c: 22576}, 22561, 22557, 22560, {f: 2, c: 22777}, 22880,
+ [23159, 57587], 23194, 23167, 23186, 23195, 23207, 23411, 23409, 23506,
+ 23500, 23507, 23504, {f: 2, c: 23562}, 23601, 23884, 23888, 23860, 23879,
+ 24061, 24133, 24125, 24128, 24131, 24190, 24266, {f: 2, c: 24257}, 24260,
+ 24380, 24429, {f: 2, c: 24489}, 24488, 24785, 24801, 24754, 24758, 24800,
+ 24860, 24867, 24826, 24853, 24816, 24827, 24820, 24936, 24817, 24846,
+ 24822, 24841, 24832, 24850, 25119, 25161, 25507, 25484, 25551, 25536,
+ 25577, 25545, 25542, 25549, 25554, 25571, 25552, 25569, 25558,
+ {f: 2, c: 25581}, 25462, 25588, 25578, 25563, 25682, 25562, 25593, 25950,
+ 25958, {f: 2, c: 25954}, 26001, 26000, 26031, 26222, 26224, [26228, 57786],
+ 26230, 26223, 26257, 26234, 26238, 26231, {f: 2, c: 26366}, 26399, 26397,
+ 26874, 26837, 26848, 26840, 26839, 26885, 26847, 26869, 26862, 26855,
+ 26873, 26834, 26866, 26851, 26827, 26829, 26893, 26898, 26894, 26825,
+ 26842, 26990, 26875, 27454, 27450, 27453, 27544, 27542, 27580, 27631,
+ {f: 2, c: 27694}, 27692, [28207, 57904], 28216, 28244, 28193, 28210, 28263,
+ 28234, 28192, 28197, 28195, 28187, 28251, 28248, 28196, 28246, 28270,
+ 28205, 28198, 28271, 28212, 28237, 28218, 28204, 28227, [28189, 57901],
+ 28222, 28363, 28297, 28185, 28238, 28259, 28228, 28274, 28265, 28255,
+ {f: 2, c: 28953}, 28966, 28976, 28961, 28982, [29038, 57958], 28956, 29260,
+ 29316, 29312, 29494, 29477, 29492, 29481, 29754, 29738, 29747, 29730,
+ 29733, {f: 2, c: 29749}, 29748, 29743, 29723, 29734, 29736,
+ {f: 2, c: 29989}, 30059, 30058, 30178, 30171, 30179, 30169, 30168, 30174,
+ 30176, {f: 2, c: 30331}, 30358, 30355, 30388, 30428, 30543, 30701, 30813,
+ 30828, 30831, 31245, 31240, 31243, 31237, 31232, 31384, 31383, 31382,
+ 31461, 31459, 31561, 31574, 31558, 31568, 31570, 31572, 31565, 31563,
+ 31567, [31569, 60510], 31903, 31909, 32094, 32080, 32104, 32085, 32043,
+ 32110, 32114, 32097, 32102, 32098, 32112, 32115, 21892, {f: 2, c: 32724},
+ 32779, 32850, 32901, 33109, 33108, 33099, 33105, 33102, 33081, 33094,
+ 33086, 33100, 33107, 33140, 33298, 33308, 33769, 33795, 33784, 33805,
+ 33760, 33733, 33803, [33729, 58309], 33775, 33777, 33780, 33879, 33802,
+ 33776, 33804, 33740, 33789, 33778, 33738, 33848, 33806, 33796, 33756,
+ 33799, 33748, 33759, 34395, 34527, 34521, 34541, 34516, 34523, 34532,
+ 34512, 34526, 34903, {f: 2, c: 35009}, 34993, 35203, 35222, 35387, 35424,
+ 35413, 35422, 35388, 35393, 35412, 35419, 35408, 35398, 35380, 35386,
+ 35382, 35414, 35937, 35970, 36015, 36028, 36019, 36029, 36033, 36027,
+ 36032, 36020, 36023, 36022, 36031, 36024, 36234, 36229, 36225, 36302,
+ 36317, 36299, 36314, 36305, 36300, 36315, 36294, 36603, 36600, 36604,
+ 36764, 36910, 36917, 36913, 36920, 36914, 36918, 37122, 37109, 37129,
+ 37118, 37219, 37221, 37327, {f: 2, c: 37396}, 37411, 37385, 37406, 37389,
+ 37392, 37383, 37393, 38292, 38287, 38283, 38289, 38291, 38290, 38286,
+ 38538, 38542, 38539, 38525, {f: 2, c: 38533}, 38541, 38514, 38532, 38593,
+ 38597, 38596, {f: 2, c: 38598}, 38639, 38642, 38860, {f: 2, c: 38917},
+ 38920, 39143, 39146, 39151, 39145, 39154, 39149, 39342, 39341,
+ [12232, 40643], [12233, 40653], [12234, 40657], 20098, 20653, 20661,
+ {f: 2, c: 20658}, 20677, 20670, 20652, 20663, 20667, 20655, 20679, 21119,
+ 21111, 21117, 21215, 21222, 21220, {f: 2, c: 21218}, 21295, 21983, 21992,
+ 21971, 21990, 21966, 21980, 21959, 21969, {f: 2, c: 21987}, 21999, 21978,
+ 21985, {f: 2, c: 21957}, 21989, 21961, {f: 2, c: 22290}, 22622, 22609,
+ 22616, 22615, 22618, 22612, 22635, 22604, 22637, 22602, 22626, 22610,
+ 22603, 22887, 23233, 23241, 23244, 23230, 23229, 23228, 23219, 23234,
+ 23218, 23913, 23919, 24140, 24185, 24265, 24264, 24338, 24409, 24492,
+ 24494, 24858, 24847, 24904, 24863, 24819, 24859, 24825, 24833, 24840,
+ 24910, 24908, 24900, 24909, 24894, 24884, 24871, 24845, 24838, 24887,
+ {f: 2, c: 25121}, 25619, 25662, 25630, 25642, 25645, 25661, 25644, 25615,
+ 25628, 25620, 25613, 25654, {f: 2, c: 25622}, 25606, 25964, 26015, 26032,
+ 26263, 26249, {f: 2, c: 26247}, 26262, 26244, 26264, 26253, 26371, 27028,
+ 26989, 26970, 26999, 26976, 26964, 26997, 26928, 27010, 26954, 26984,
+ 26987, 26974, 26963, 27001, 27014, 26973, 26979, 26971, 27463, 27506,
+ 27584, 27583, 27603, 27645, 28322, 28335, 28371, 28342, 28354, 28304,
+ 28317, 28359, 28357, 28325, 28312, 28348, 28346, 28331, 28369, 28310,
+ 28316, 28356, 28372, 28330, 28327, 28340, 29006, 29017, 29033, 29028,
+ 29001, 29031, 29020, 29036, 29030, 29004, 29029, 29022, 28998, 29032,
+ 29014, 29242, 29266, 29495, 29509, 29503, 29502, 29807, 29786, 29781,
+ 29791, 29790, 29761, 29759, 29785, 29787, [29788, 58019], 30070, 30072,
+ 30208, 30192, 30209, 30194, 30193, 30202, 30207, 30196, 30195,
+ {f: 2, c: 30430}, 30555, 30571, 30566, 30558, 30563, 30585, 30570, 30572,
+ 30556, 30565, 30568, 30562, 30702, 30862, 30896, {f: 2, c: 30871}, 30860,
+ 30857, 30844, 30865, 30867, 30847, 31098, 31103, 31105, 33836, 31165,
+ 31260, 31258, 31264, 31252, 31263, 31262, {f: 2, c: 31391}, 31607, 31680,
+ 31584, 31598, 31591, 31921, 31923, 31925, 32147, 32121, 32145, 32129,
+ 32143, 32091, 32622, {f: 2, c: 32617}, 32626, 32681, 32680, 32676, 32854,
+ 32856, 32902, 32900, 33137, 33136, 33144, 33125, 33134, 33139, 33131,
+ {f: 2, c: 33145}, 33126, 33285, 33351, 33922, 33911, 33853, 33841, 33909,
+ 33894, 33899, 33865, 33900, 33883, 33852, 33845, 33889, 33891, 33897,
+ 33901, 33862, 34398, 34396, 34399, 34553, 34579, 34568, 34567, 34560,
+ 34558, 34555, {f: 2, c: 34562}, 34566, 34570, 34905, 35039, 35028, 35033,
+ 35036, 35032, 35037, 35041, 35018, 35029, 35026, 35228, 35299, 35435,
+ {f: 2, c: 35442}, 35430, 35433, 35440, 35463, 35452, 35427, 35488, 35441,
+ 35461, 35437, 35426, 35438, 35436, 35449, 35451, 35390, 35432, 35938,
+ 35978, 35977, 36042, {f: 2, c: 36039}, 36036, 36018, 36035, 36034, 36037,
+ 36321, 36319, 36328, 36335, 36339, 36346, 36330, 36324, 36326, 36530,
+ 36611, 36617, 36606, 36618, 36767, 36786, 36939, 36938, 36947, 36930,
+ 36948, 36924, 36949, 36944, 36935, 36943, 36942, 36941, 36945, 36926,
+ 36929, 37138, 37143, 37228, 37226, 37225, 37321, 37431, 37463, 37432,
+ 37437, 37440, 37438, 37467, 37451, 37476, 37457, 37428, 37449, 37453,
+ 37445, 37433, 37439, 37466, 38296, 38552, {f: 2, c: 38548}, 38605, 38603,
+ {f: 2, c: 38601}, 38647, 38651, 38649, 38646, 38742, 38772, 38774,
+ {f: 2, c: 38928}, 38931, 38922, 38930, 38924, 39164, 39156,
+ {f: 2, c: 39165}, 39347, 39345, 39348, 39649, 40169, 40578, [12237, 40718],
+ [12238, 40723], [12239, 40736], 20711, 20718, 20709, 20694, [20717, 60903],
+ 20698, 20693, 20687, 20689, 20721, 20686, 20713, 20834, 20979, 21123,
+ 21122, 21297, 21421, 22014, 22016, 22043, 22039, 22013, 22036, 22022,
+ 22025, {f: 2, c: 22029}, 22007, 22038, 22047, 22024, 22032, 22006, 22296,
+ 22294, 22645, 22654, 22659, 22675, 22666, 22649, 22661, 22653, 22781,
+ 22821, 22818, 22820, 22890, 22889, 23265, 23270, 23273, 23255, 23254,
+ 23256, 23267, 23413, 23518, 23527, 23521, {f: 2, c: 23525}, 23528, 23522,
+ 23524, 23519, 23565, 23650, 23940, 23943, 24155, 24163, 24149, 24151,
+ 24148, 24275, 24278, 24330, 24390, 24432, 24505, 24903, 24895, 24907,
+ 24951, {f: 2, c: 24930}, 24927, 24922, 24920, 24949, 25130, 25735, 25688,
+ 25684, 25764, 25720, 25695, 25722, 25681, 25703, 25652, 25709, 25723,
+ 25970, 26017, 26071, 26070, 26274, 26280, 26269, 27036, 27048, 27029,
+ 27073, 27054, 27091, 27083, 27035, 27063, 27067, 27051, 27060, 27088,
+ 27085, 27053, 27084, 27046, 27075, 27043, 27465, 27468, 27699, 28467,
+ 28436, 28414, 28435, 28404, 28457, 28478, 28448, 28460, 28431, 28418,
+ 28450, 28415, 28399, 28422, 28465, 28472, 28466, 28451, 28437, 28459,
+ 28463, 28552, 28458, 28396, 28417, 28402, 28364, 28407, 29076, 29081,
+ 29053, 29066, 29060, 29074, 29246, 29330, 29334, 29508, 29520, 29796,
+ 29795, 29802, 29808, 29805, 29956, 30097, 30247, 30221, 30219, 30217,
+ 30227, 30433, 30435, 30596, 30589, 30591, 30561, 30913, 30879, 30887,
+ 30899, 30889, 30883, {f: 2, c: 31118}, 31117, 31278, 31281, 31402, 31401,
+ 31469, 31471, 31649, 31637, 31627, 31605, 31639, 31645, 31636, 31631,
+ [31672, 58170], 31623, 31620, 31929, {f: 2, c: 31933}, 32187, 32176, 32156,
+ {f: 2, c: 32189}, 32160, 32202, 32180, 32178, 32177, 32186, 32162, 32191,
+ 32181, 32184, 32173, [32210, 58202], 32199, 32172, 32624, {f: 2, c: 32736},
+ 32735, 32862, 32858, 32903, 33104, 33152, 33167, 33160, 33162, 33151,
+ 33154, 33255, 33274, 33287, 33300, 33310, 33355, 33993, 33983, 33990,
+ 33988, 33945, 33950, 33970, 33948, 33995, 33976, 33984, 34003, 33936,
+ 33980, 34001, 33994, 34623, 34588, 34619, 34594, 34597, 34612, 34584,
+ 34645, 34615, 34601, 35059, 35074, 35060, 35065, 35064, 35069, 35048,
+ 35098, 35055, 35494, 35468, 35486, 35491, 35469, 35489, 35475, 35492,
+ 35498, 35493, 35496, 35480, 35473, 35482, 35495, 35946, 35981, 35980,
+ 36051, {f: 2, c: 36049}, 36203, 36249, 36245, 36348, 36628, 36626, 36629,
+ 36627, 36771, 36960, 36952, 36956, 36963, 36953, 36958, 36962, 36957,
+ 36955, 37145, 37144, 37150, 37237, 37240, 37239, 37236, 37496, 37548,
+ 37504, 37509, 37528, 37526, 37499, 37523, 37532, 37544, 37500, 37521,
+ 38305, {f: 2, c: 38312}, 38307, 38309, 38308, 38553, 38556, 38555, 38604,
+ 38610, 38656, 38780, 38789, 38902, {f: 2, c: 38935}, 39087, 39089, 39171,
+ 39173, 39180, 39177, 39361, {f: 2, c: 39599}, 39654, {f: 2, c: 39745},
+ 40180, 40182, 40179, 40636, [12240, 40763], [12241, 40778], 20740, 20736,
+ 20731, 20725, 20729, 20738, {f: 2, c: 20744}, 20741, 20956,
+ {f: 3, c: 21127}, 21133, 21130, 21232, 21426, 22062, 22075, 22073, 22066,
+ 22079, 22068, 22057, 22099, 22094, 22103, 22132, 22070, {f: 2, c: 22063},
+ 22656, 22687, 22686, 22707, 22684, 22702, 22697, 22694, 22893, 23305,
+ 23291, 23307, 23285, 23308, 23304, 23534, 23532, 23529, 23531,
+ {f: 2, c: 23652}, 23965, 23956, 24162, 24159, 24161, 24290, 24282, 24287,
+ 24285, 24291, 24288, 24392, 24433, 24503, 24501, 24950, 24935, 24942,
+ 24925, 24917, 24962, 24956, 24944, 24939, 24958, 24999, 24976, 25003,
+ 24974, 25004, 24986, 24996, 24980, 25006, 25134, 25705, 25711, 25721,
+ 25758, 25778, 25736, [25744, 57745], 25776, 25765, 25747, 25749, 25769,
+ 25746, 25774, 25773, 25771, 25754, 25772, 25753, 25762, 25779, 25973,
+ {f: 2, c: 25975}, 26286, 26283, 26292, 26289, 27171, 27167, 27112, 27137,
+ 27166, 27161, 27133, 27169, 27155, 27146, 27123, 27138, 27141, 27117,
+ 27153, 27472, 27470, 27556, {f: 2, c: 27589}, 28479, 28540, 28548, 28497,
+ 28518, 28500, 28550, 28525, 28507, 28536, 28526, 28558, 28538, 28528,
+ 28516, 28567, 28504, 28373, 28527, 28512, 28511, 29087, 29100, 29105,
+ 29096, 29270, 29339, 29518, 29527, 29801, 29835, 29827, 29822, 29824,
+ 30079, 30240, 30249, 30239, 30244, 30246, {f: 2, c: 30241}, 30362, 30394,
+ 30436, 30606, 30599, 30604, 30609, 30603, 30923, 30917, 30906, 30922,
+ 30910, 30933, 30908, 30928, 31295, 31292, 31296, 31293, 31287, 31291,
+ 31407, 31406, 31661, 31665, 31684, 31668, {f: 2, c: 31686}, 31681, 31648,
+ 31692, 31946, 32224, 32244, 32239, 32251, 32216, 32236, 32221, 32232,
+ 32227, 32218, 32222, 32233, 32158, 32217, 32242, 32249, 32629, 32631,
+ 32687, 32745, 32806, {f: 3, c: 33179}, 33184, 33178, 33176, 34071, 34109,
+ 34074, 34030, {f: 2, c: 34092}, 34067, 34065, 34083, 34081, 34068, 34028,
+ 34085, 34047, 34054, 34690, 34676, 34678, 34656, 34662, 34680, 34664,
+ 34649, 34647, 34636, 34643, 34907, 34909, 35088, 35079, {f: 2, c: 35090},
+ 35093, 35082, 35516, 35538, 35527, 35524, 35477, 35531, 35576, 35506,
+ 35529, 35522, 35519, 35504, 35542, 35533, 35510, 35513, 35547, 35916,
+ 35918, 35948, 36064, 36062, 36070, 36068, {f: 2, c: 36076},
+ {f: 2, c: 36066}, 36060, 36074, 36065, 36205, 36255, 36259, 36395, 36368,
+ 36381, 36386, 36367, 36393, 36383, 36385, 36382, 36538, 36637, 36635,
+ 36639, 36649, 36646, 36650, 36636, 36638, 36645, 36969, 36974, 36968,
+ 36973, 36983, 37168, 37165, 37159, 37169, 37255, 37257, 37259, 37251,
+ 37573, 37563, 37559, 37610, 37604, 37569, 37555, 37564, 37586, 37575,
+ 37616, 37554, 38317, 38321, 38660, {f: 2, c: 38662}, 38665, 38752, 38797,
+ 38795, 38799, 38945, 38955, 38940, 39091, 39178, 39187, 39186, 39192,
+ 39389, 39376, 39391, 39387, 39377, 39381, 39378, 39385, 39607,
+ {f: 2, c: 39662}, 39719, 39749, 39748, 39799, 39791, 40198, 40201, 40195,
+ 40617, 40638, 40654, 22696, [12242, 40786], 20754, 20760, 20756, 20752,
+ 20757, 20864, 20906, 20957, 21137, 21139, 21235, 22105, 22123, 22137,
+ 22121, 22116, 22136, 22122, 22120, 22117, 22129, 22127, 22124, 22114,
+ 22134, 22721, 22718, 22727, 22725, 22894, 23325, 23348, 23416, 23536,
+ 23566, 24394, 25010, 24977, 25001, 24970, 25037, 25014, 25022, 25034,
+ 25032, 25136, 25797, 25793, 25803, {f: 2, c: 25787}, 25818, 25796, 25799,
+ 25794, 25805, 25791, 25810, 25812, 25790, 25972, 26310, 26313, 26297,
+ 26308, 26311, 26296, 27197, 27192, 27194, 27225, 27243, 27224, 27193,
+ 27204, 27234, 27233, 27211, 27207, 27189, 27231, 27208, 27481, 27511,
+ 27653, 28610, 28593, 28577, 28611, 28580, 28609, 28583, 28595, 28608,
+ 28601, [28598, 60318], 28582, 28576, 28596, 29118, 29129, 29136, 29138,
+ 29128, 29141, 29113, 29134, 29145, 29148, {f: 2, c: 29123}, 29544, 29852,
+ 29859, 29848, 29855, 29854, 29922, {f: 2, c: 29964}, 30260, 30264, 30266,
+ 30439, 30437, 30624, {f: 2, c: 30622}, 30629, 30952, 30938, 30956, 30951,
+ 31142, {f: 2, c: 31309}, 31302, 31308, 31307, 31418, 31705, 31761, 31689,
+ 31716, 31707, 31713, 31721, 31718, {f: 2, c: 31957}, 32266, 32273, 32264,
+ 32283, 32291, 32286, [32285, 58211], 32265, 32272, 32633, 32690,
+ {f: 2, c: 32752}, 32750, [32808, 58239], 33203, 33193, 33192, 33275, 33288,
+ {f: 2, c: 33368}, 34122, 34137, 34120, {f: 2, c: 34152}, 34115, 34121,
+ 34157, 34154, 34142, 34691, 34719, 34718, 34722, 34701, 34913, 35114,
+ 35122, 35109, 35115, 35105, 35242, [35238, 58391], 35558, 35578, 35563,
+ 35569, 35584, 35548, 35559, 35566, 35582, {f: 2, c: 35585}, 35575, 35565,
+ 35571, 35574, 35580, 35947, 35949, 35987, 36084, 36420, 36401, 36404,
+ 36418, 36409, 36405, 36667, 36655, 36664, 36659, 36776, 36774, 36981,
+ 36980, 36984, 36978, 36988, 36986, 37172, 37266, 37664, 37686, 37624,
+ 37683, 37679, 37666, 37628, 37675, 37636, 37658, 37648, 37670, 37665,
+ 37653, 37678, 37657, 38331, {f: 2, c: 38567}, 38570, 38613, 38670, 38673,
+ 38678, 38669, 38675, 38671, 38747, [38748, 58565], 38758, 38808, 38960,
+ 38968, 38971, 38967, 38957, 38969, 38948, 39184, 39208, 39198, 39195,
+ 39201, 39194, 39405, 39394, 39409, 39608, 39612, 39675, 39661, 39720,
+ 39825, 40213, 40227, 40230, 40232, 40210, 40219, 40664, 40660,
+ [12243, 40845], [12244, 40860], 20778, 20767, 20769, 20786, 21237, 22158,
+ 22144, 22160, 22149, 22151, 22159, 22741, 22739, 22737, 22734, 23344,
+ 23338, 23332, 23418, 23607, 23656, 23996, 23994, 23997, 23992, 24171,
+ 24396, 24509, 25033, 25026, 25031, 25062, 25035, 25138, 25140, 25806,
+ 25802, 25816, 25824, 25840, 25830, 25836, 25841, 25826, 25837,
+ {f: 2, c: 25986}, 26329, 26326, 27264, 27284, 27268, 27298, 27292, 27355,
+ 27299, 27262, 27287, 27280, 27296, 27484, 27566, 27610, 27656, 28632,
+ 28657, {f: 2, c: 28639}, 28635, 28644, 28651, 28655, 28544, 28652, 28641,
+ 28649, 28629, 28654, 28656, 29159, [29151, 60361], 29166, 29158, 29157,
+ 29165, 29164, 29172, 29152, 29237, 29254, 29552, 29554, 29865, 29872,
+ 29862, 29864, 30278, 30274, 30284, 30442, 30643, 30634, 30640, 30636,
+ 30631, 30637, 30703, 30967, 30970, 30964, 30959, 30977, 31143, 31146,
+ 31319, 31423, 31751, 31757, 31742, 31735, 31756, 31712, 31968, 31964,
+ 31966, 31970, 31967, 31961, 31965, 32302, 32318, 32326, 32311, 32306,
+ 32323, 32299, 32317, 32305, 32325, 32321, 32308, 32313, 32328, 32309,
+ 32319, 32303, 32580, 32755, 32764, {f: 2, c: 32881}, 32880, 32879, 32883,
+ 33222, 33219, 33210, 33218, 33216, 33215, 33213, 33225, 33214, 33256,
+ 33289, 33393, 34218, 34180, 34174, 34204, 34193, 34196, 34223, 34203,
+ 34183, 34216, 34186, 34214, 34407, 34752, 34769, 34739, 34770, 34758,
+ 34731, 34747, 34746, 34760, 34763, 35131, 35126, 35140, 35128, 35133,
+ 35244, 35598, 35607, 35609, 35611, 35594, 35616, 35613, 35588, 35600,
+ 35905, 35903, 35955, 36090, 36093, 36092, 36088, 36091, 36264, 36425,
+ 36427, 36424, 36426, 36676, 36670, 36674, 36677, 36671, 36991, 36989,
+ 36996, {f: 2, c: 36993}, 36992, 37177, 37283, 37278, 37276, 37709, 37762,
+ 37672, 37749, 37706, 37733, 37707, 37656, 37758, 37740, 37723, 37744,
+ 37722, 37716, {f: 3, c: 38346}, 38344, 38342, 38577, 38584, 38614, 38684,
+ 38686, 38816, 38867, 38982, 39094, 39221, 39425, 39423, 39854, 39851,
+ 39850, 39853, 40251, 40255, 40587, 40655, 40670, {f: 2, c: 40668}, 40667,
+ 40766, 40779, 21474, 22165, 22190, 22745, 22744, 23352, 24413, 25059,
+ 25139, 25844, 25842, 25854, 25862, {f: 2, c: 25850}, 25847, 26039, 26332,
+ 26406, 27315, 27308, 27331, 27323, 27320, 27330, {f: 2, c: 27310}, 27487,
+ 27512, 27567, 28681, 28683, 28670, 28678, 28666, 28689, 28687,
+ {f: 2, c: 29179}, 29182, 29176, 29559, 29557, 29863, 29887, 29973, 30294,
+ 30296, 30290, 30653, 30655, {f: 2, c: 30651}, 30990, 31150,
+ {f: 2, c: 31329}, 31328, {f: 2, c: 31428}, 31787, 31783, 31786, 31774,
+ 31779, 31777, 31975, {f: 2, c: 32340}, 32350, 32346, 32353, 32338, 32345,
+ 32584, 32761, 32763, 32887, 32886, 33229, 33231, 33290, 34255, 34217,
+ 34253, 34256, 34249, 34224, 34234, 34233, 34799, 34796, 34802, 34784,
+ 35206, 35250, 35316, 35624, 35641, 35628, 35627, 35920, 36101, 36441,
+ 36451, 36454, 36452, 36447, 36437, 36544, 36681, 36685, 36999, 36995,
+ 37000, {f: 2, c: 37291}, 37328, 37780, 37770, 37782, 37794, 37811, 37806,
+ 37804, 37808, 37784, 37786, 37783, 38356, 38358, 38352, 38357, 38626,
+ 38620, 38617, 38619, 38622, 38692, 38819, 38822, 38829, 38905, 38989,
+ 38991, 38988, 38990, 38995, 39098, {f: 2, c: 39230}, 39229, 39214, 39333,
+ 39438, 39617, 39683, 39686, 39759, 39758, 39757, 39882, 39881, 39933,
+ 39880, 39872, 40273, 40285, 40288, 40672, 40725, 40748, 20787, 22181,
+ 22184, {f: 2, c: 22750}, 22754, 23541, 40848, 24300, 25074, 25079, 25078,
+ 25077, 25856, 25871, 26336, 26333, 27365, 27357, 27354, 27347, 28699,
+ 28703, 28712, 28698, 28701, 28693, 28696, 29190, 29197, 29272, 29346,
+ 29560, 29562, 29885, 29898, 29923, 30087, 30086, 30303, 30305, 30663,
+ 31001, 31153, 31339, 31337, {f: 2, c: 31806}, 31800, 31805, 31799, 31808,
+ 32363, 32365, 32377, {f: 2, c: 32361}, 32371, 32645, 32694, 32697, 32696,
+ 33240, 34281, 34269, 34282, 34261, {f: 2, c: 34276}, 34295, 34811, 34821,
+ 34829, 34809, 34814, 35168, 35167, 35158, 35166, 35649, 35676, 35672,
+ 35657, 35674, {f: 2, c: 35662}, 35654, 35673, 36104, 36106, 36476, 36466,
+ 36487, 36470, 36460, 36474, 36468, 36692, 36686, 36781, {f: 2, c: 37002},
+ 37297, 37294, 37857, 37841, 37855, 37827, 37832, {f: 2, c: 37852}, 37846,
+ 37858, 37837, 37848, 37860, 37847, 37864, 38364, 38580, 38627, 38698,
+ 38695, 38753, 38876, 38907, 39006, 39000, 39003, 39100, 39237, 39241,
+ 39446, 39449, 39693, 39912, 39911, 39894, 39899, 40329, 40289, 40306,
+ 40298, 40300, 40594, 40599, 40595, 40628, 21240, 22199, 22198, 22196,
+ 22204, 22756, 23360, 23363, 23421, 23542, 24009, 25080, 25082, 25880,
+ 25876, 25881, 26342, 26407, 27372, 28734, 28720, 28722, 29200, 29563,
+ 29903, 30306, 30309, 31014, 31018, 31020, 31019, 31431, 31478, 31820,
+ 31811, 31821, {f: 2, c: 31983}, 36782, 32381, 32380, 32386, 32588, 32768,
+ 33242, 33382, 34299, 34297, 34321, 34298, 34310, 34315, 34311, 34314,
+ {f: 2, c: 34836}, 35172, 35258, 35320, 35696, 35692, 35686, 35695, 35679,
+ 35691, 36111, 36109, 36489, 36481, 36485, 36482, 37300, 37323, 37912,
+ 37891, 37885, 38369, 38704, 39108, 39250, 39249, 39336, 39467, 39472,
+ 39479, 39477, 39955, 39949, 40569, 40629, 40680, 40751, 40799, 40803,
+ 40801, {f: 2, c: 20791}, 22209, 22208, 22210, 22804, 23660, 24013, 25084,
+ 25086, 25885, 25884, 26005, 26345, 27387, 27396, 27386, 27570, 28748,
+ 29211, 29351, 29910, 29908, 30313, 30675, 31824, 32399, 32396, 32700,
+ 34327, 34349, 34330, 34851, 34850, 34849, 34847, 35178, 35180, 35261,
+ 35700, 35703, 35709, 36115, 36490, 36493, 36491, 36703, 36783, 37306,
+ 37934, 37939, 37941, 37946, 37944, 37938, 37931, 38370, {f: 2, c: 38712},
+ 38706, [38911, 58586], 39015, 39013, 39255, 39493, 39491, 39488, 39486,
+ 39631, 39764, 39761, 39981, 39973, 40367, 40372, 40386, 40376, 40605,
+ 40687, 40729, 40796, {f: 2, c: 40806}, 20796, 20795, 22216, 22218, 22217,
+ 23423, 24020, 24018, 24398, 25087, 25892, 27402, 27489, 28753, 28760,
+ 29568, 29924, 30090, 30318, 30316, 31155, 31840, 31839, 32894, 32893,
+ 33247, 35186, 35183, 35324, 35712, {f: 2, c: 36118}, 36497, 36499, 36705,
+ 37192, 37956, {f: 2, c: 37969}, {f: 2, c: 38717}, 38851, 38849, 39019,
+ 39253, 39509, 39501, 39634, 39706, 40009, 39985, 39998, 39995, 40403,
+ 40407, 40756, 40812, 40810, 40852, 22220, 24022, 25088, 25891, 25899,
+ 25898, 26348, 27408, 29914, 31434, 31844, 31843, 31845, 32403, 32406,
+ 32404, 33250, 34360, 34367, 34865, 35722, 37008, 37007, 37987, 37984,
+ 37988, 38760, 39023, 39260, {f: 2, c: 39514}, 39511, {f: 2, c: 39635},
+ 39633, 40020, 40023, 40022, 40421, 40607, 40692, 22225, 22761, 25900,
+ 28766, {f: 2, c: 30321}, [30679, 60226], 32592, 32648, 34870, 34873, 34914,
+ 35731, 35730, 35734, 33399, 36123, 37312, 37994, 38722, 38728, 38724,
+ 38854, 39024, 39519, 39714, 39768, 40031, {f: 2, c: 40441},
+ {f: 2, c: 40572}, 40711, 40823, 40818, 24307, 27414, 28771, 31852, 31854,
+ 34875, 35264, 36513, 37313, 38002, 38000, 39025, 39262, 39638, 39715,
+ 40652, 28772, 30682, 35738, 38007, 38857, 39522, 39525, 32412, 35740,
+ 36522, 37317, {f: 2, c: 38013}, 38012, {f: 2, c: 40055}, 40695, 35924,
+ 38015, 40474, 29224, 39530, 39729, 40475, 40478, 31858, 20034, 20060,
+ [12048, 20981], [12053, 21274], [12058, 21378], 19975, 19980, 20039, 20109,
+ [12062, 22231], [12076, 23662], [12091, 24435], 19983, 20871, 19982, 20014,
+ 20115, 20162, 20169, 20168, 20888, 21244, 21356, 21433, 22304, 22787,
+ 22828, [23568, 60417], 24063, 26081, [12110, 27571], 27596, [12115, 27668],
+ [12121, 29247], 20017, 20028, 20200, 20188, 20201, 20193, 20189, 20186,
+ 21004, 21001, 21276, 21324, {f: 2, c: 22306}, 22807, 22831, 23425, 23428,
+ 23570, 23611, 23668, 23667, 24068, 24192, 24194, 24521, 25097, 25168,
+ 27669, 27702, 27715, 27711, 27707, 29358, 29360, 29578, [12145, 31160],
+ 32906, 38430, 20238, 20248, 20268, 20213, 20244, 20209, 20224, 20215,
+ 20232, 20253, 20226, 20229, 20258, 20243, 20228, 20212, 20242, 20913,
+ 21011, 21008, 21158, 21282, 21279, 21325, 21386, 21511, 22241, 22239,
+ 22318, 22314, 22324, 22844, 22912, 22908, 22917, 22907, 22910, 22903,
+ 22911, 23382, 23573, 23589, 23676, {f: 2, c: 23674}, 23678, 24031,
+ [24181, 57646], 24196, 24322, 24346, 24436, 24533, 24532, 24527, 25180,
+ 25182, 25188, 25185, 25190, 25186, 25177, 25184, 25178, 25189, 25911,
+ 26095, 26094, 26430, 26425, 26424, 26427, 26426, 26431, 26428, 26419,
+ 27672, 27718, 27730, 27740, 27727, [27722, 60796], 27732, {f: 2, c: 27723},
+ 28785, 29278, {f: 2, c: 29364}, 29582, 29994, 30335, 31349, [12153, 32593],
+ [12171, 33400], 33404, 33408, 33405, 33407, [12172, 34381], [12177, 35198],
+ 37017, [37015, 59347], 37016, 37019, 37012, 38434, 38436, 38432, 38435,
+ 20310, 20283, 20322, 20297, 20307, 20324, 20286, 20327, 20306, 20319,
+ 20289, 20312, 20269, 20275, 20287, 20321, 20879, 20921, 21020, 21022,
+ 21025, {f: 2, c: 21165}, 21257, 21347, 21362, {f: 2, c: 21390}, 21552,
+ 21559, 21546, 21588, 21573, 21529, 21532, 21541, 21528, 21565, 21583,
+ 21569, 21544, 21540, 21575, 22254, 22247, 22245, 22337, 22341, 22348,
+ 22345, 22347, 22354, 22790, 22848, 22950, 22936, 22944, 22935, 22926,
+ 22946, 22928, 22927, 22951, 22945, 23438, 23442, 23592, 23594, 23693,
+ 23695, 23688, 23691, 23689, 23698, 23690, 23686, 23699, 23701, 24032,
+ 24074, 24078, 24203, 24201, 24204, 24200, 24205, 24325, 24349, 24440,
+ 24438, 24530, 24529, 24528, 24557, 24552, 24558, 24563, 24545, 24548,
+ 24547, 24570, 24559, 24567, 24571, 24576, 24564, 25146, 25219, 25228,
+ {f: 2, c: 25230}, 25236, 25223, 25201, 25211, 25210, 25200, 25217, 25224,
+ 25207, 25213, 25202, 25204, 26096, 26100, 26099, 26098, 26101, 26437,
+ 26439, 26457, 26453, 26444, 26440, 26461, 26445, 26458, 26443, 27600,
+ {f: 2, c: 27673}, 27768, 27751, 27755, 27780, 27787, 27791, 27761, 27759,
+ 27753, 27802, 27757, 27783, 27797, [27804, 57900], 27750, 27763, 27749,
+ 27771, 27790, 28788, 28794, 29283, 29375, 29373, 29379, 29382, 29377,
+ 29370, 29381, 29589, 29591, {f: 2, c: 29587}, 29586, 30010, 30009,
+ {f: 2, c: 30100}, 30337, 31037, 32820, 32917, 32921, 32912, 32914, 32924,
+ 33424, 33423, 33413, 33422, 33425, 33427, 33418, {f: 2, c: 33411},
+ [12184, 35960], 36809, 36799, 37023, 37025, 37029, 37022, 37031, 37024,
+ 38448, 38440, 38447, 38445, 20019, 20376, 20348, 20357, 20349, 20352,
+ 20359, 20342, 20340, 20361, 20356, 20343, 20300, 20375, 20330, 20378,
+ 20345, 20353, 20344, 20368, 20380, 20372, 20382, 20370, 20354, 20373,
+ 20331, 20334, 20894, 20924, 20926, 21045, {f: 2, c: 21042}, 21062, 21041,
+ 21180, {f: 2, c: 21258}, 21308, 21394, 21396, 21639, 21631, 21633, 21649,
+ 21634, 21640, 21611, 21626, 21630, 21605, 21612, 21620, 21606, 21645,
+ 21615, 21601, 21600, 21656, 21603, 21607, 21604, 22263, 22265, 22383,
+ 22386, 22381, 22379, 22385, 22384, 22390, 22400, 22389, 22395,
+ {f: 2, c: 22387}, 22370, 22376, 22397, 22796, 22853, 22965, 22970, 22991,
+ 22990, 22962, 22988, 22977, 22966, 22972, 22979, 22998, 22961, 22973,
+ 22976, 22984, 22964, 22983, 23394, 23397, 23443, 23445, 23620, 23623,
+ 23726, 23716, 23712, 23733, 23727, 23720, 23724, 23711, 23715, 23725,
+ 23714, 23722, 23719, 23709, 23717, 23734, 23728, 23718, 24087, 24084,
+ 24089, 24360, {f: 3, c: 24354}, 24404, 24450, 24446, 24445, 24542, 24549,
+ 24621, 24614, 24601, 24626, 24587, 24628, 24586, 24599, 24627, 24602,
+ 24606, 24620, 24610, 24589, 24592, 24622, 24595, 24593, 24588, 24585,
+ 24604, 25108, 25149, 25261, 25268, 25297, 25278, 25258, 25270, 25290,
+ 25262, 25267, 25263, 25275, 25257, 25264, 25272, 25917, 26024, 26043,
+ 26121, 26108, 26116, 26130, 26120, 26107, 26115, 26123, 26125, 26117,
+ 26109, 26129, 26128, 26358, 26378, 26501, 26476, 26510, 26514, 26486,
+ 26491, 26520, 26502, 26500, 26484, 26509, 26508, 26490, 26527, 26513,
+ 26521, 26499, 26493, 26497, {f: 2, c: 26488}, 26516, 27429, 27520, 27518,
+ 27614, 27677, 27795, 27884, 27883, 27886, 27865, 27830, 27860, 27821,
+ 27879, 27831, 27856, 27842, 27834, 27843, 27846, 27885, 27890, 27858,
+ 27869, 27828, 27786, 27805, 27776, 27870, 27840, 27952, 27853, 27847,
+ 27824, 27897, 27855, 27881, 27857, 28820, 28824, 28805, 28819, 28806,
+ 28804, 28817, 28822, 28802, 28826, 28803, 29290, 29398, 29387, 29400,
+ 29385, 29404, 29394, 29396, 29402, 29388, 29393, 29604, 29601, 29613,
+ 29606, 29602, 29600, 29612, 29597, 29917, 29928, {f: 2, c: 30015}, 30014,
+ 30092, 30104, 30383, 30451, 30449, 30448, 30453, 30712, 30716, 30713,
+ 30715, 30714, 30711, 31042, 31039, 31173, 31352, 31355, 31483, 31861,
+ 31997, 32821, 32911, 32942, 32931, 32952, 32949, 32941, 33312, 33440,
+ 33472, 33451, 33434, 33432, 33435, 33461, 33447, 33454, 33468, 33438,
+ 33466, 33460, 33448, 33441, 33449, 33474, 33444, 33475, 33462, 33442,
+ 34416, 34415, {f: 2, c: 34413}, 35926, 36818, 36811, 36819, 36813, 36822,
+ 36821, 36823, 37042, 37044, 37039, 37043, 37040, 38457, 38461, 38460,
+ 38458, 38467, 20429, 20421, 20435, 20402, 20425, 20427, 20417, 20436,
+ 20444, 20441, [20411, 60346], 20403, 20443, 20423, 20438, 20410, 20416,
+ 20409, 20460, 21060, 21065, 21184, 21186, 21309, 21372, 21399, 21398,
+ 21401, 21400, 21690, 21665, 21677, 21669, 21711, 21699, 33549, 21687,
+ 21678, 21718, 21686, {f: 2, c: 21701}, 21664, 21616, 21692, 21666, 21694,
+ 21618, 21726, 21680, 22453, {f: 2, c: 22430}, 22436, 22412, 22423, 22429,
+ 22427, 22420, 22424, 22415, 22425, 22437, 22426, 22421, 22772, 22797,
+ 22867, 23009, 23006, 23022, 23040, 23025, 23005, 23034, 23037, 23036,
+ 23030, 23012, 23026, 23031, 23003, 23017, 23027, 23029, 23008, 23038,
+ 23028, 23021, 23464, 23628, 23760, 23768, 23756, 23767, 23755, 23771,
+ 23774, 23770, 23753, 23751, 23754, 23766, {f: 2, c: 23763}, 23759, 23752,
+ 23750, 23758, 23775, 23800, 24057, {f: 3, c: 24097}, 24096, 24100, 24240,
+ 24228, 24226, 24219, 24227, 24229, 24327, 24366, 24406, 24454, 24631,
+ 24633, 24660, 24690, 24670, 24645, 24659, 24647, 24649, 24667, 24652,
+ 24640, 24642, 24671, 24612, 24644, 24664, 24678, 24686, {f: 2, c: 25154},
+ 25295, 25357, 25355, 25333, 25358, 25347, 25323, 25337, 25359, 25356,
+ 25336, 25334, 25344, {f: 2, c: 25363}, 25338, 25365, 25339, 25328, 25921,
+ 25923, 26026, 26047, 26166, 26145, 26162, 26165, 26140, 26150, 26146,
+ 26163, 26155, 26170, 26141, 26164, 26169, 26158, {f: 2, c: 26383}, 26561,
+ 26610, 26568, 26554, 26588, 26555, 26616, 26584, 26560, 26551, 26565,
+ 26603, 26596, 26591, 26549, 26573, 26547, 26615, 26614, 26606, 26595,
+ 26562, 26553, 26574, 26599, 26608, 26546, 26620, 26566, 26605, 26572,
+ 26542, 26598, 26587, 26618, {f: 2, c: 26569}, 26563, 26602, 26571, 27432,
+ 27522, 27524, 27574, 27606, 27608, 27616, {f: 2, c: 27680}, 27944, 27956,
+ 27949, 27935, 27964, 27967, 27922, 27914, 27866, 27955, 27908, 27929,
+ 27962, 27930, 27921, 27904, 27933, 27970, 27905, 27928, 27959, 27907,
+ 27919, 27968, 27911, 27936, 27948, 27912, 27938, 27913, 27920, 28855,
+ 28831, 28862, 28849, 28848, 28833, {f: 2, c: 28852}, 28841, 29249,
+ {f: 2, c: 29257}, 29292, 29296, 29299, 29294, 29386, 29412, 29416, 29419,
+ 29407, 29418, 29414, 29411, 29573, 29644, 29634, 29640, 29637, 29625,
+ 29622, 29621, 29620, 29675, 29631, 29639, 29630, 29635, 29638, 29624,
+ 29643, 29932, 29934, 29998, {f: 2, c: 30023}, 30119, 30122, 30329, 30404,
+ 30472, {f: 3, c: 30467}, 30474, 30455, 30459, 30458, {f: 2, c: 30695},
+ 30726, {f: 2, c: 30737}, 30725, 30736, 30735, 30734, [30729, 58095], 30723,
+ 30739, 31050, 31052, 31051, 31045, 31044, 31189, 31181, 31183, 31190,
+ 31182, 31360, 31358, 31441, {f: 2, c: 31488}, 31866, {f: 2, c: 31864},
+ {f: 3, c: 31871}, 32003, 32008, 32001, 32600, 32657, 32653, 32702, 32775,
+ {f: 2, c: 32782}, 32788, 32823, 32984, 32967, 32992, 32977, 32968, 32962,
+ 32976, 32965, 32995, 32985, 32988, 32970, 32981, 32969, 32975, 32983,
+ 32998, 32973, 33279, 33313, 33428, 33497, 33534, 33529, 33543, 33512,
+ 33536, 33493, 33594, 33515, 33494, 33524, 33516, 33505, 33522, 33525,
+ 33548, 33531, 33526, 33520, 33514, 33508, 33504, 33530, 33523, 33517,
+ 34423, 34420, 34428, 34419, 34881, 34894, 34919, 34922, 34921, 35283,
+ 35332, 35335, 36210, 36835, 36833, 36846, 36832, 37105, 37053, 37055,
+ 37077, 37061, 37054, 37063, 37067, 37064, [37332, 60294], 37331, 38484,
+ 38479, 38481, 38483, 38474, 38478, 20510, 20485, 20487, 20499, 20514,
+ 20528, 20507, 20469, 20468, 20531, 20535, 20524, {f: 2, c: 20470}, 20503,
+ 20508, 20512, 20519, 20533, 20527, 20529, 20494, 20826, 20884, 20883,
+ 20938, {f: 2, c: 20932}, 20936, 20942, 21089, 21082, 21074,
+ {f: 2, c: 21086}, 21077, 21090, 21197, 21262, 21406, 21798, 21730, 21783,
+ 21778, 21735, 21747, 21732, 21786, 21759, 21764, 21768, 21739, 21777,
+ 21765, 21745, 21770, 21755, {f: 2, c: 21751}, 21728, 21774, 21763, 21771,
+ {f: 2, c: 22273}, 22476, 22578, 22485, 22482, 22458, 22470, 22461, 22460,
+ 22456, 22454, 22463, 22471, 22480, 22457, 22465, 22798, 22858, 23065,
+ 23062, {f: 2, c: 23085}, 23061, 23055, 23063, 23050, 23070, 23091, 23404,
+ 23463, 23469, 23468, 23555, 23638, 23636, 23788, 23807, 23790, 23793,
+ 23799, 23808, 23801, 24105, 24104, 24232, 24238, 24234, 24236, 24371,
+ 24368, 24423, 24669, 24666, 24679, 24641, 24738, 24712, 24704, 24722,
+ 24705, 24733, 24707, 24725, 24731, 24727, 24711, 24732, 24718, 25113,
+ 25158, 25330, 25360, 25430, 25388, {f: 2, c: 25412}, 25398, 25411, 25572,
+ 25401, 25419, 25418, 25404, 25385, 25409, 25396, 25432, 25428, 25433,
+ 25389, 25415, 25395, 25434, 25425, 25400, 25431, 25408, 25416, 25930,
+ 25926, 26054, {f: 2, c: 26051}, 26050, 26186, 26207, 26183, 26193,
+ {f: 2, c: 26386}, 26655, 26650, 26697, {f: 2, c: 26674}, 26683, 26699,
+ 26703, 26646, 26673, 26652, 26677, 26667, 26669, 26671, 26702, 26692,
+ 26676, 26653, 26642, 26644, 26662, 26664, 26670, 26701, 26682, 26661,
+ 26656, 27436, 27439, 27437, 27441, 27444, 27501, 32898, 27528, 27622,
+ 27620, 27624, 27619, 27618, 27623, 27685, 28026, {f: 2, c: 28003}, 28022,
+ 27917, 28001, 28050, 27992, 28002, 28013, 28015, 28049, 28045, 28143,
+ 28031, 28038, 27998, [28007, 59078], 28000, 28055, 28016, 28028, 27999,
+ 28034, 28056, 27951, 28008, 28043, 28030, 28032, 28036, 27926, 28035,
+ 28027, 28029, 28021, 28048, 28892, 28883, 28881, 28893, 28875, 32569,
+ 28898, 28887, 28882, 28894, 28896, 28884, 28877, {f: 3, c: 28869}, 28890,
+ 28878, 28897, 29250, 29304, 29303, 29302, 29440, 29434, 29428, 29438,
+ 29430, 29427, 29435, 29441, 29651, 29657, 29669, 29654, 29628, 29671,
+ 29667, 29673, 29660, 29650, 29659, 29652, 29661, 29658, {f: 2, c: 29655},
+ 29672, {f: 2, c: 29918}, {f: 2, c: 29940}, 29985, 30043, 30047, 30128,
+ 30145, 30139, 30148, 30144, 30143, 30134, 30138, 30346, 30409, 30493,
+ 30491, 30480, 30483, 30482, 30499, 30481, 30485, {f: 2, c: 30489}, 30498,
+ 30503, 30755, 30764, 30754, 30773, 30767, 30760, 30766, 30763, 30753,
+ 30761, 30771, 30762, 30769, 31060, 31067, 31055, 31068, 31059, 31058,
+ 31057, {f: 2, c: 31211}, 31200, 31214, 31213, 31210, 31196, 31198, 31197,
+ 31366, 31369, 31365, {f: 2, c: 31371}, 31370, 31367, 31448, 31504, 31492,
+ 31507, 31493, 31503, 31496, 31498, 31502, 31497, 31506, 31876, 31889,
+ 31882, 31884, 31880, 31885, 31877, 32030, 32029, 32017, 32014, 32024,
+ 32022, 32019, 32031, 32018, 32015, 32012, 32604, 32609, 32606, 32608,
+ 32605, 32603, 32662, 32658, 32707, 32706, 32704, 32790, 32830, 32825,
+ 33018, 33010, 33017, 33013, 33025, 33019, 33024, 33281, 33327, 33317,
+ 33587, 33581, 33604, 33561, 33617, 33573, 33622, 33599, 33601, 33574,
+ 33564, 33570, 33602, 33614, 33563, 33578, 33544, 33596, 33613, 33558,
+ 33572, 33568, 33591, 33583, 33577, 33607, 33605, 33612, 33619, 33566,
+ 33580, 33611, 33575, 33608, 34387, 34386, 34466, 34472, 34454, 34445,
+ 34449, 34462, 34439, 34455, 34438, 34443, 34458, 34437, 34469, 34457,
+ 34465, 34471, 34453, 34456, 34446, 34461, 34448, 34452, {f: 2, c: 34883},
+ 34925, {f: 2, c: 34933}, 34930, 34944, 34929, 34943, 34927, 34947, 34942,
+ 34932, 34940, 35346, 35911, 35927, 35963, 36004, 36003, 36214, 36216,
+ 36277, 36279, 36278, 36561, 36563, 36862, 36853, 36866, 36863, 36859,
+ 36868, 36860, 36854, 37078, 37088, {f: 2, c: 37081}, 37091, 37087, 37093,
+ 37080, 37083, 37079, 37084, 37092, 37200, {f: 2, c: 37198}, 37333, 37346,
+ 37338, 38492, 38495, 38588, 39139, [12221, 39647], [12223, 39727], 20095,
+ 20592, 20586, 20577, 20574, 20576, 20563, 20555, 20573, 20594, 20552,
+ 20557, 20545, 20571, 20554, 20578, 20501, 20549, 20575, 20585, 20587,
+ {f: 2, c: 20579}, 20550, 20544, 20590, 20595, 20567, 20561, 20944, 21099,
+ 21101, 21100, 21102, 21206, 21203, 21293, 21404, {f: 2, c: 21877}, 21820,
+ 21837, 21840, 21812, 21802, 21841, 21858, 21814, 21813, 21808, 21842,
+ 21829, 21772, 21810, 21861, 21838, 21817, 21832, 21805, 21819, 21824,
+ 21835, 22282, 22279, 22523, 22548, 22498, 22518, 22492, 22516, 22528,
+ 22509, 22525, 22536, 22520, 22539, 22515, 22479, 22535, 22510, 22499,
+ 22514, 22501, 22508, 22497, 22542, 22524, 22544, 22503, 22529, 22540,
+ 22513, 22505, 22512, 22541, 22532, 22876, 23136, 23128, 23125,
+ [23143, 60437], 23134, 23096, 23093, 23149, 23120, 23135, 23141, 23148,
+ 23123, 23140, 23127, 23107, 23133, 23122, 23108, 23131, 23112, 23182,
+ 23102, 23117, 23097, 23116, 23152, 23145, 23111, 23121, 23126, 23106,
+ 23132, 23410, 23406, 23489, 23488, 23641, 23838, 23819, 23837, 23834,
+ 23840, 23820, 23848, 23821, 23846, 23845, 23823, 23856, 23826, 23843,
+ 23839, 23854, 24126, 24116, 24241, 24244, 24249, {f: 2, c: 24242}, 24374,
+ 24376, 24475, 24470, 24479, 24714, 24720, 24710, 24766, 24752, 24762,
+ {f: 2, c: 24787}, 24783, 24804, 24793, 24797, 24776, 24753, 24795, 24759,
+ 24778, 24767, 24771, 24781, 24768, 25394, 25445, 25482, 25474, 25469,
+ 25533, 25502, 25517, 25501, 25495, 25515, 25486, 25455, 25479, 25488,
+ 25454, 25519, 25461, 25500, 25453, 25518, 25468, 25508, 25403, 25503,
+ 25464, 25477, 25473, 25489, 25485, 25456, 25939, 26061, 26213, 26209,
+ 26203, 26201, 26204, 26210, 26392, 26745, 26759, 26768, 26780,
+ {f: 2, c: 26733}, 26798, 26795, 26966, 26735, 26787, 26796, 26793, 26741,
+ 26740, 26802, 26767, 26743, 26770, 26748, 26731, 26738, 26794, 26752,
+ 26737, 26750, 26779, 26774, 26763, 26784, 26761, 26788, 26744, 26747,
+ 26769, 26764, 26762, 26749, 27446, 27443, {f: 2, c: 27447}, 27537, 27535,
+ {f: 2, c: 27533}, 27532, 27690, 28096, 28075, 28084, 28083, 28276, 28076,
+ 28137, 28130, 28087, 28150, 28116, 28160, 28104, 28128, 28127, 28118,
+ 28094, 28133, {f: 2, c: 28124}, 28123, 28148, 28106, 28093, 28141, 28144,
+ 28090, 28117, 28098, 28111, 28105, 28112, 28146, 28115, 28157, 28119,
+ 28109, 28131, 28091, 28922, 28941, 28919, 28951, 28916, 28940, 28912,
+ 28932, 28915, 28944, 28924, 28927, 28934, 28947, 28928, 28920, 28918,
+ 28939, 28930, 28942, 29310, {f: 2, c: 29307}, 29311, 29469, 29463, 29447,
+ 29457, 29464, 29450, 29448, 29439, 29455, 29470, 29576, 29686, 29688,
+ 29685, 29700, 29697, 29693, 29703, 29696, 29690, 29692, 29695, 29708,
+ 29707, 29684, 29704, 30052, 30051, 30158, 30162, 30159, {f: 2, c: 30155},
+ 30161, 30160, 30351, 30345, 30419, 30521, 30511, 30509, {f: 2, c: 30513},
+ 30516, 30515, 30525, 30501, 30523, 30517, 30792, 30802, 30793, 30797,
+ 30794, 30796, 30758, 30789, 30800, 31076, 31079, {f: 2, c: 31081}, 31075,
+ 31083, 31073, 31163, 31226, 31224, {f: 2, c: 31222}, 31375, 31380, 31376,
+ 31541, 31547, 31540, 31525, 31536, 31522, 31524, 31539, 31512, 31530,
+ 31517, 31537, 31531, 31533, 31535, 31538, 31544, 31514, 31523, 31892,
+ 31896, 31894, 31907, 32053, 32061, 32056, 32054, 32058, 32069, 32044,
+ 32041, 32065, 32071, {f: 2, c: 32062}, 32074, 32059, 32040, 32611, 32661,
+ {f: 2, c: 32668}, 32667, {f: 2, c: 32714}, 32717, {f: 2, c: 32720}, 32711,
+ 32719, 32713, 32799, 32798, 32795, 32839, 32835, 32840, 33048, 33061,
+ 33049, 33051, 33069, 33055, 33068, 33054, 33057, 33045, 33063, 33053,
+ 33058, 33297, 33336, 33331, 33338, 33332, 33330, 33396, 33680, 33699,
+ 33704, 33677, 33658, 33651, 33700, 33652, 33679, 33665, 33685, 33689,
+ 33653, 33684, 33705, 33661, 33667, 33676, 33693, 33691, 33706, 33675,
+ 33662, 33701, 33711, 33672, 33687, 33712, 33663, 33702, 33671, 33710,
+ 33654, 34393, 34390, 34495, 34487, 34498, 34497, 34501, 34490, 34480,
+ 34504, 34489, 34483, 34488, 34508, 34484, {f: 2, c: 34491}, 34499,
+ {f: 2, c: 34493}, 34898, 34953, 34965, 34984, 34978, 34986, 34970, 34961,
+ 34977, 34975, 34968, 34983, 34969, 34971, 34967, 34980, 34988, 34956,
+ 34963, 34958, 35202, 35286, 35289, 35285, 35376, 35367, 35372, 35358,
+ 35897, 35899, {f: 2, c: 35932}, 35965, 36005, 36221, 36219, 36217, 36284,
+ 36290, 36281, 36287, 36289, 36568, 36574, 36573, 36572, 36567,
+ {f: 2, c: 36576}, 36900, 36875, 36881, 36892, 36876, 36897, 37103, 37098,
+ 37104, 37108, {f: 2, c: 37106}, 37076, {f: 2, c: 37099}, 37097, 37206,
+ 37208, 37210, 37203, 37205, 37356, 37364, 37361, 37363, 37368, 37348,
+ 37369, {f: 2, c: 37354}, 37367, 37352, 37358, 38266, 38278, 38280, 38524,
+ 38509, 38507, 38513, 38511, 38591, 38762, 38916, 39141, 39319, 20635,
+ 20629, 20628, 20638, 20619, 20643, 20611, 20620, 20622, 20637, 20584,
+ 20636, 20626, 20610, 20615, 20831, 20948, 21266, 21265, 21412, 21415,
+ 21905, 21928, 21925, 21933, 21879, 22085, 21922, 21907, 21896, 21903,
+ 21941, 21889, 21923, 21906, 21924, 21885, 21900, 21926, 21887, 21909,
+ 21921, 21902, 22284, 22569, 22583, 22553, 22558, 22567, 22563, 22568,
+ 22517, 22600, 22565, 22556, 22555, 22579, 22591, 22582, 22574, 22585,
+ 22584, 22573, 22572, 22587, 22881, 23215, 23188, 23199, 23162, 23202,
+ 23198, 23160, 23206, 23164, 23205, 23212, 23189, 23214, 23095, 23172,
+ 23178, 23191, 23171, 23179, 23209, 23163, 23165, 23180, 23196, 23183,
+ 23187, 23197, 23530, 23501, 23499, 23508, 23505, 23498, 23502, 23564,
+ 23600, 23863, 23875, 23915, 23873, 23883, 23871, 23861, 23889, 23886,
+ 23893, 23859, 23866, 23890, 23869, 23857, 23897, 23874, 23865, 23881,
+ 23864, 23868, 23858, 23862, 23872, 23877, 24132, 24129, [24408, 57673],
+ 24486, 24485, 24491, 24777, 24761, 24780, 24802, 24782, 24772, 24852,
+ 24818, 24842, 24854, 24837, 24821, 24851, 24824, 24828, 24830, 24769,
+ 24835, 24856, 24861, 24848, 24831, 24836, 24843, 25162, 25492, 25521,
+ 25520, 25550, 25573, 25576, 25583, 25539, 25757, 25587, 25546, 25568,
+ 25590, 25557, 25586, 25589, 25697, 25567, 25534, 25565, 25564, 25540,
+ 25560, 25555, 25538, 25543, 25548, 25547, 25544, 25584, 25559, 25561,
+ 25906, 25959, 25962, 25956, 25948, 25960, 25957, 25996, {f: 2, c: 26013},
+ 26030, 26064, 26066, 26236, 26220, 26235, 26240, 26225, 26233, 26218,
+ 26226, 26369, 26892, 26835, 26884, 26844, 26922, 26860, 26858, 26865,
+ 26895, 26838, 26871, 26859, 26852, 26870, 26899, 26896, 26867, 26849,
+ 26887, 26828, 26888, 26992, 26804, 26897, 26863, 26822, 26900, 26872,
+ 26832, 26877, 26876, 26856, 26891, 26890, 26903, 26830, 26824,
+ {f: 2, c: 26845}, 26854, 26868, 26833, 26886, 26836, 26857, 26901, 26917,
+ 26823, 27449, 27451, 27455, 27452, 27540, 27543, 27545, 27541, 27581,
+ 27632, {f: 2, c: 27634}, 27696, 28156, {f: 2, c: 28230}, 28191, 28233,
+ 28296, {f: 2, c: 28220}, 28229, 28258, 28203, 28223, 28225, 28253, 28275,
+ 28188, 28211, 28235, 28224, 28241, 28219, 28163, 28206, 28254, 28264,
+ 28252, 28257, 28209, 28200, 28256, 28273, 28267, 28217, 28194, 28208,
+ 28243, 28261, 28199, 28280, 28260, 28279, 28245, 28281, 28242, 28262,
+ {f: 2, c: 28213}, 28250, 28960, 28958, 28975, 28923, 28974, 28977, 28963,
+ 28965, 28962, 28978, 28959, 28968, 28986, 28955, 29259, 29274,
+ {f: 2, c: 29320}, 29318, 29317, 29323, 29458, 29451, 29488, 29474, 29489,
+ 29491, 29479, 29490, 29485, 29478, 29475, 29493, 29452, 29742, 29740,
+ 29744, 29739, 29718, 29722, 29729, 29741, 29745, 29732, 29731, 29725,
+ 29737, 29728, 29746, 29947, 29999, 30063, 30060, 30183, 30170, 30177,
+ 30182, 30173, 30175, 30180, 30167, 30357, 30354, 30426, {f: 2, c: 30534},
+ 30532, 30541, 30533, 30538, 30542, {f: 2, c: 30539}, 30686, 30700, 30816,
+ {f: 2, c: 30820}, 30812, 30829, 30833, 30826, 30830, 30832, 30825, 30824,
+ 30814, 30818, 31092, 31091, 31090, 31088, 31234, 31242, 31235, 31244,
+ 31236, 31385, 31462, 31460, 31562, 31559, 31556, 31560, 31564, 31566,
+ 31552, 31576, 31557, 31906, 31902, 31912, 31905, 32088, 32111, 32099,
+ 32083, 32086, 32103, 32106, 32079, 32109, 32092, 32107, 32082, 32084,
+ 32105, 32081, 32095, 32078, {f: 2, c: 32574}, {f: 2, c: 32613}, 32674,
+ {f: 2, c: 32672}, 32727, 32849, {f: 2, c: 32847}, 33022, 32980, 33091,
+ 33098, 33106, 33103, 33095, 33085, 33101, 33082, 33254, 33262,
+ {f: 3, c: 33271}, 33284, {f: 2, c: 33340}, 33343, 33397, 33595,
+ [33743, 60382], 33785, 33827, 33728, 33768, 33810, 33767, 33764, 33788,
+ 33782, 33808, 33734, 33736, 33771, 33763, 33727, 33793, 33757, 33765,
+ 33752, 33791, 33761, 33739, 33742, 33750, 33781, 33737, 33801,
+ [33807, 58332], 33758, 33809, 33798, 33730, 33779, 33749, 33786, 33735,
+ 33745, 33770, 33811, 33690, 33731, 33772, 33774, 33732, 33787, 33751,
+ 33762, 33819, 33755, 33790, 34520, 34530, 34534, 34515, 34531, 34522,
+ 34538, 34525, 34539, 34524, 34540, 34537, 34519, 34536, 34513, 34888,
+ 34902, 34901, 35002, 35031, 35001, 35000, 35008, 35006, 34998, 35004,
+ 34999, 35005, 34994, 35073, 35017, 35221, 35224, 35223, 35293,
+ {f: 2, c: 35290}, 35406, 35405, 35385, 35417, 35392, {f: 2, c: 35415},
+ {f: 2, c: 35396}, 35410, 35400, 35409, 35402, 35404, 35407, 35935, 35969,
+ 35968, 36026, 36030, 36016, 36025, 36021, 36228, 36224, 36233, 36312,
+ 36307, 36301, 36295, 36310, 36316, 36303, 36309, 36313, 36296, 36311,
+ 36293, 36591, 36599, 36602, 36601, 36582, 36590, 36581, 36597,
+ {f: 2, c: 36583}, 36598, 36587, 36593, 36588, 36596, 36585, 36909, 36916,
+ 36911, 37126, 37164, [37124, 60367], 37119, 37116, 37128, 37113, 37115,
+ 37121, 37120, 37127, 37125, 37123, 37217, 37220, 37215, 37218, 37216,
+ 37377, 37386, 37413, 37379, 37402, 37414, 37391, 37388, 37376, 37394,
+ 37375, 37373, 37382, 37380, 37415, 37378, 37404, 37412, 37401, 37399,
+ 37381, 37398, 38267, 38285, 38284, 38288, 38535, 38526, {f: 2, c: 38536},
+ 38531, 38528, 38594, 38600, 38595, 38641, 38640, 38764, 38768, 38766,
+ 38919, 39081, 39147, 40166, [12235, 40697], {f: 2, c: 20099}, 20150, 20669,
+ 20671, 20678, 20654, 20676, 20682, 20660, 20680, 20674, 20656, 20673,
+ 20666, 20657, 20683, 20681, 20662, 20664, 20951, 21114, 21112,
+ {f: 2, c: 21115}, 21955, 21979, 21964, 21968, 21963, 21962, 21981,
+ [21952, 64013], 21972, 21956, 21993, 21951, 21970, 21901, 21967, 21973,
+ 21986, 21974, 21960, 22002, 21965, 21977, 21954, 22292, 22611, 22632,
+ 22628, 22607, 22605, 22601, 22639, 22613, 22606, 22621, 22617, 22629,
+ 22619, 22589, 22627, 22641, 22780, 23239, 23236, 23243, 23226, 23224,
+ 23217, 23221, 23216, 23231, 23240, 23227, 23238, 23223, 23232, 23242,
+ 23220, 23222, 23245, 23225, 23184, 23510, {f: 2, c: 23512}, 23583, 23603,
+ 23921, 23907, 23882, 23909, 23922, 23916, 23902, 23912, 23911, 23906,
+ 24048, 24143, 24142, 24138, 24141, 24139, 24261, 24268, 24262, 24267,
+ 24263, 24384, 24495, 24493, 24823, {f: 2, c: 24905}, 24875, 24901, 24886,
+ 24882, 24878, 24902, 24879, 24911, 24873, 24896, 25120, 37224, 25123,
+ 25125, 25124, 25541, 25585, 25579, 25616, 25618, 25609, 25632, 25636,
+ 25651, 25667, 25631, 25621, 25624, 25657, 25655, {f: 2, c: 25634}, 25612,
+ 25638, 25648, 25640, 25665, 25653, 25647, 25610, 25626, 25664, 25637,
+ 25639, 25611, 25575, 25627, 25646, 25633, 25614, 25967, 26002, 26067,
+ 26246, 26252, 26261, 26256, 26251, 26250, 26265, 26260, 26232, 26400,
+ 26982, 26975, 26936, 26958, 26978, 26993, 26943, 26949, 26986, 26937,
+ 26946, 26967, 26969, 27002, {f: 2, c: 26952}, 26933, 26988, 26931, 26941,
+ 26981, 26864, 27000, 26932, 26985, 26944, 26991, 26948, 26998, 26968,
+ 26945, 26996, 26956, 26939, 26955, 26935, 26972, 26959, 26961, 26930,
+ 26962, 26927, 27003, 26940, 27462, 27461, 27459, 27458, 27464, 27457,
+ 27547, {f: 2, c: 27643}, 27641, {f: 2, c: 27639}, 28315, 28374, 28360,
+ 28303, 28352, 28319, {f: 2, c: 28307}, 28320, 28337, 28345, 28358, 28370,
+ 28349, 28353, 28318, 28361, 28343, 28336, 28365, 28326, 28367, 28338,
+ 28350, 28355, 28380, 28376, 28313, 28306, 28302, 28301, 28324, 28321,
+ 28351, 28339, 28368, 28362, 28311, 28334, 28323, 28999, 29012, 29010,
+ 29027, 29024, 28993, 29021, [29026, 61080], 29042, 29048, 29034, 29025,
+ 28994, 29016, 28995, 29003, 29040, 29023, 29008, 29011, 28996, 29005,
+ 29018, 29263, 29325, 29324, 29329, 29328, 29326, 29500, 29506, 29499,
+ 29498, 29504, 29514, 29513, 29764, {f: 2, c: 29770}, 29778, 29777, 29783,
+ 29760, {f: 2, c: 29775}, 29774, 29762, 29766, 29773, 29780, 29921, 29951,
+ 29950, 29949, 29981, 30073, 30071, 27011, 30191, 30223, 30211, 30199,
+ 30206, 30204, [30201, 60782], 30200, 30224, 30203, 30198, 30189, 30197,
+ 30205, 30361, 30389, 30429, 30549, {f: 2, c: 30559}, 30546, 30550, 30554,
+ 30569, 30567, 30548, 30553, 30573, 30688, 30855, 30874, 30868, 30863,
+ 30852, 30869, {f: 2, c: 30853}, 30881, 30851, 30841, 30873, 30848, 30870,
+ 30843, 31100, 31106, 31101, 31097, 31249, {f: 2, c: 31256}, 31250, 31255,
+ 31253, 31266, 31251, 31259, 31248, 31395, 31394, 31390, 31467, 31590,
+ 31588, 31597, 31604, 31593, 31602, 31589, 31603, 31601, 31600, 31585,
+ 31608, 31606, 31587, 31922, 31924, 31919, 32136, 32134, 32128, 32141,
+ 32127, 32133, 32122, 32142, 32123, 32131, 32124, 32140, 32148, 32132,
+ 32125, 32146, 32621, 32619, {f: 2, c: 32615}, 32620, 32678, 32677, 32679,
+ {f: 2, c: 32731}, 32801, 33124, 33120, 33143, 33116, 33129, 33115, 33122,
+ 33138, 26401, 33118, 33142, 33127, 33135, 33092, 33121, 33309, 33353,
+ 33348, 33344, 33346, 33349, 34033, 33855, 33878, 33910, 33913, 33935,
+ 33933, 33893, 33873, 33856, 33926, 33895, 33840, 33869, 33917, 33882,
+ 33881, 33908, 33907, 33885, 34055, 33886, 33847, 33850, 33844, 33914,
+ 33859, 33912, 33842, 33861, 33833, 33753, 33867, 33839, 33858, 33837,
+ 33887, 33904, 33849, 33870, 33868, 33874, 33903, 33989, 33934, 33851,
+ 33863, 33846, 33843, 33896, 33918, 33860, 33835, 33888, 33876, 33902,
+ 33872, 34571, 34564, 34551, 34572, 34554, 34518, 34549, 34637, 34552,
+ 34574, 34569, 34561, 34550, 34573, 34565, 35030, 35019, {f: 2, c: 35021},
+ 35038, 35035, 35034, 35020, 35024, 35205, 35227, 35295, 35301, 35300,
+ 35297, 35296, 35298, 35292, 35302, 35446, 35462, 35455, 35425, 35391,
+ 35447, 35458, 35460, 35445, 35459, 35457, 35444, 35450, 35900, 35915,
+ 35914, 35941, 35940, 35942, 35974, {f: 2, c: 35972}, 36044,
+ {f: 2, c: 36200}, 36241, 36236, {f: 2, c: 36238}, 36237, {f: 2, c: 36243},
+ 36240, 36242, 36336, 36320, 36332, 36337, 36334, 36304, 36329, 36323,
+ 36322, 36327, 36338, 36331, 36340, 36614, 36607, 36609, 36608, 36613,
+ {f: 2, c: 36615}, 36610, [36619, 60507], 36946, 36927, 36932, 36937, 36925,
+ 37136, 37133, 37135, 37137, 37142, 37140, 37131, 37134, {f: 2, c: 37230},
+ 37448, 37458, 37424, 37434, 37478, 37427, 37477, 37470, 37507, 37422,
+ 37450, 37446, 37485, 37484, 37455, 37472, 37479, 37487, 37430, 37473,
+ 37488, 37425, 37460, 37475, 37456, 37490, 37454, 37459, 37452, 37462,
+ 37426, 38303, 38300, 38302, 38299, {f: 2, c: 38546}, 38545, 38551, 38606,
+ 38650, 38653, 38648, 38645, 38771, {f: 2, c: 38775}, 38770, 38927,
+ {f: 2, c: 38925}, 39084, 39158, 39161, 39343, 39346, 39344, 39349, 39597,
+ 39595, 39771, 40170, 40173, 40167, 40576, [12236, 40701], 20710, 20692,
+ 20695, 20712, 20723, 20699, 20714, 20701, 20708, 20691, 20716, 20720,
+ 20719, 20707, 20704, 20952, {f: 2, c: 21120}, 21225, 21227, 21296, 21420,
+ 22055, 22037, 22028, 22034, 22012, 22031, 22044, 22017, 22035, 22018,
+ 22010, 22045, 22020, 22015, 22009, 22665, 22652, 22672, 22680, 22662,
+ 22657, 22655, 22644, 22667, 22650, 22663, 22673, 22670, 22646, 22658,
+ 22664, 22651, 22676, 22671, 22782, 22891, 23260, 23278, 23269, 23253,
+ 23274, 23258, 23277, 23275, 23283, 23266, 23264, 23259, 23276, 23262,
+ 23261, 23257, 23272, 23263, 23415, 23520, 23523, 23651, 23938, 23936,
+ 23933, 23942, 23930, 23937, 23927, 23946, 23945, 23944, 23934, 23932,
+ 23949, 23929, 23935, {f: 2, c: 24152}, 24147, 24280, 24273, 24279, 24270,
+ 24284, 24277, 24281, 24274, 24276, 24388, 24387, 24431, 24502, 24876,
+ 24872, 24897, 24926, 24945, 24947, {f: 2, c: 24914}, 24946, 24940, 24960,
+ 24948, 24916, 24954, 24923, 24933, 24891, 24938, 24929, 24918, 25129,
+ 25127, 25131, 25643, 25677, 25691, 25693, 25716, 25718, {f: 2, c: 25714},
+ 25725, 25717, 25702, 25766, 25678, 25730, 25694, 25692, 25675, 25683,
+ 25696, 25680, 25727, 25663, 25708, 25707, 25689, 25701, 25719, 25971,
+ 26016, 26273, 26272, 26271, 26373, 26372, 26402, 27057, 27062, 27081,
+ 27040, 27086, 27030, 27056, 27052, 27068, 27025, 27033, 27022, 27047,
+ 27021, 27049, 27070, 27055, 27071, 27076, 27069, 27044, 27092, 27065,
+ 27082, 27034, 27087, 27059, 27027, 27050, 27041, 27038, 27097, 27031,
+ 27024, 27074, 27061, 27045, 27078, 27466, 27469, 27467, {f: 3, c: 27550},
+ {f: 2, c: 27587}, 27646, 28366, 28405, 28401, 28419, 28453, 28408, 28471,
+ 28411, 28462, 28425, 28494, {f: 2, c: 28441}, 28455, 28440, 28475, 28434,
+ 28397, 28426, 28470, 28531, 28409, 28398, 28461, 28480, 28464, 28476,
+ 28469, 28395, 28423, 28430, 28483, 28421, 28413, 28406, 28473, 28444,
+ 28412, 28474, 28447, 28429, 28446, 28424, 28449, 29063, 29072, 29065,
+ 29056, 29061, 29058, 29071, 29051, 29062, 29057, 29079, 29252, 29267,
+ 29335, 29333, 29331, 29507, 29517, 29521, 29516, 29794, 29811, 29809,
+ 29813, 29810, 29799, 29806, 29952, {f: 2, c: 29954}, 30077, 30096, 30230,
+ 30216, 30220, 30229, 30225, 30218, 30228, 30392, 30593, 30588, 30597,
+ 30594, 30574, 30592, 30575, 30590, 30595, 30898, 30890, 30900, 30893,
+ 30888, 30846, 30891, 30878, 30885, 30880, 30892, 30882, 30884, 31128,
+ {f: 2, c: 31114}, 31126, 31125, 31124, 31123, 31127, 31112, 31122, 31120,
+ 31275, 31306, 31280, 31279, 31272, 31270, 31400, {f: 2, c: 31403}, 31470,
+ 31624, 31644, 31626, 31633, 31632, 31638, 31629, 31628, 31643, 31630,
+ 31621, 31640, 21124, 31641, 31652, 31618, 31931, 31935, 31932, 31930,
+ 32167, 32183, 32194, 32163, 32170, 32193, 32192, 32197, 32157, 32206,
+ 32196, 32198, {f: 2, c: 32203}, 32175, 32185, 32150, 32188, 32159, 32166,
+ 32174, 32169, 32161, 32201, 32627, {f: 2, c: 32738}, 32741, 32734, 32804,
+ 32861, 32860, 33161, 33158, 33155, 33159, 33165, 33164, 33163, 33301,
+ 33943, 33956, 33953, 33951, 33978, 33998, 33986, 33964, 33966, 33963,
+ 33977, 33972, 33985, 33997, 33962, 33946, 33969, 34000, 33949, 33959,
+ 33979, 33954, 33940, 33991, 33996, 33947, 33961, 33967, [33960, 58327],
+ 34006, 33944, 33974, 33999, 33952, 34007, 34004, 34002, 34011, 33968,
+ 33937, 34401, 34611, 34595, 34600, 34667, 34624, 34606, 34590, 34593,
+ 34585, 34587, 34627, 34604, 34625, 34622, 34630, 34592, 34610, 34602,
+ 34605, 34620, 34578, 34618, 34609, 34613, 34626, {f: 2, c: 34598}, 34616,
+ 34596, 34586, 34608, 34577, 35063, 35047, {f: 2, c: 35057}, 35066, 35070,
+ 35054, 35068, 35062, 35067, 35056, 35052, 35051, 35229, 35233, 35231,
+ 35230, 35305, 35307, 35304, 35499, 35481, 35467, 35474, 35471, 35478,
+ 35901, {f: 2, c: 35944}, 36053, 36047, 36055, 36246, 36361, 36354, 36351,
+ 36365, 36349, 36362, 36355, 36359, 36358, 36357, 36350, 36352, 36356,
+ {f: 2, c: 36624}, 36622, 36621, 37155, 37148, 37152, 37154, 37151, 37149,
+ 37146, 37156, 37153, 37147, 37242, 37234, 37241, 37235, 37541, 37540,
+ 37494, 37531, 37498, 37536, 37524, 37546, 37517, 37542, 37530, 37547,
+ 37497, 37527, 37503, 37539, 37614, 37518, 37506, 37525, 37538, 37501,
+ 37512, 37537, 37514, 37510, 37516, 37529, 37543, 37502, 37511, 37545,
+ 37533, 37515, 37421, 38558, 38561, 38655, 38744, 38781, 38778, 38782,
+ 38787, 38784, 38786, 38779, 38788, 38785, 38783, 38862, 38861, 38934,
+ {f: 2, c: 39085}, 39170, 39168, 39175, 39325, 39324, 39363, 39353, 39355,
+ 39354, 39362, 39357, 39367, 39601, 39651, 39655, {f: 2, c: 39742},
+ {f: 2, c: 39776}, 39775, {f: 2, c: 40177}, 40181, 40615, 20735, 20739,
+ 20784, 20728, {f: 2, c: 20742}, 20726, 20734, {f: 2, c: 20747}, 20733,
+ 20746, {f: 2, c: 21131}, 21233, 21231, 22088, 22082, 22092, 22069, 22081,
+ 22090, 22089, 22086, 22104, 22106, 22080, 22067, 22077, 22060, 22078,
+ 22072, 22058, 22074, 22298, 22699, 22685, 22705, 22688, 22691, 22703,
+ 22700, 22693, 22689, 22783, 23295, 23284, 23293, 23287, 23286, 23299,
+ 23288, 23298, 23289, 23297, 23303, 23301, 23311, 23655, 23961, 23959,
+ 23967, 23954, 23970, 23955, 23957, 23968, 23964, 23969, 23962, 23966,
+ 24169, 24157, 24160, 24156, 32243, 24283, 24286, 24289, 24393, 24498,
+ 24971, 24963, 24953, 25009, 25008, 24994, 24969, 24987, 24979, 25007,
+ 25005, 24991, 24978, 25002, 24993, 24973, 24934, 25011, 25133, 25710,
+ 25712, 25750, 25760, 25733, 25751, 25756, 25743, 25739, 25738, 25740,
+ 25763, 25759, 25704, 25777, 25752, 25974, 25978, 25977, 25979,
+ {f: 2, c: 26034}, 26293, 26288, 26281, 26290, 26295, 26282, 26287, 27136,
+ 27142, 27159, 27109, 27128, 27157, 27121, 27108, 27168, 27135, 27116,
+ 27106, 27163, 27165, 27134, 27175, 27122, 27118, 27156, 27127, 27111,
+ 27200, 27144, 27110, 27131, 27149, 27132, 27115, 27145, 27140, 27160,
+ 27173, 27151, 27126, 27174, 27143, 27124, 27158, 27473, 27557, 27555,
+ 27554, 27558, 27649, 27648, 27647, 27650, 28481, 28454, 28542, 28551,
+ 28614, 28562, 28557, 28553, 28556, 28514, 28495, 28549, 28506, 28566,
+ 28534, 28524, 28546, 28501, 28530, 28498, 28496, 28503, 28564, 28563,
+ 28509, 28416, 28513, 28523, 28541, 28519, 28560, 28499, 28555, 28521,
+ 28543, 28565, 28515, 28535, 28522, 28539, 29106, 29103, 29083, 29104,
+ 29088, 29082, 29097, 29109, 29085, 29093, 29086, 29092, 29089, 29098,
+ 29084, 29095, 29107, 29336, 29338, 29528, 29522, {f: 3, c: 29534}, 29533,
+ 29531, 29537, 29530, 29529, 29538, 29831, {f: 2, c: 29833}, 29830, 29825,
+ 29821, 29829, 29832, 29820, [29817, 58868], 29960, 29959, 30078, 30245,
+ 30238, 30233, 30237, 30236, 30243, 30234, 30248, 30235, {f: 3, c: 30364},
+ 30363, 30605, 30607, 30601, 30600, 30925, 30907, 30927, 30924, 30929,
+ 30926, 30932, 30920, {f: 2, c: 30915}, 30921, 31130, 31137, 31136, 31132,
+ 31138, [31131, 59175], 27510, 31289, 31410, 31412, 31411, 31671, 31691,
+ 31678, 31660, 31694, 31663, 31673, 31690, 31669, 31941, 31944, 31948,
+ 31947, 32247, 32219, 32234, 32231, 32215, 32225, 32259, 32250, 32230,
+ 32246, 32241, 32240, 32238, 32223, 32630, 32684, 32688, 32685, 32749,
+ 32747, 32746, 32748, 32742, 32744, 32868, 32871, 33187, 33183, 33182,
+ 33173, 33186, 33177, 33175, 33302, 33359, 33363, 33362, 33360, 33358,
+ 33361, 34084, 34107, 34063, 34048, 34089, 34062, 34057, 34061, 34079,
+ 34058, 34087, 34076, 34043, 34091, 34042, 34056, 34060, 34036, 34090,
+ 34034, 34069, 34039, 34027, 34035, 34044, 34066, 34026, 34025, 34070,
+ 34046, 34088, 34077, 34094, 34050, 34045, 34078, 34038, 34097, 34086,
+ {f: 2, c: 34023}, 34032, 34031, 34041, 34072, 34080, 34096, 34059, 34073,
+ 34095, 34402, 34646, {f: 2, c: 34659}, 34679, 34785, 34675, 34648, 34644,
+ 34651, 34642, 34657, 34650, 34641, 34654, 34669, 34666, 34640, 34638,
+ 34655, 34653, 34671, 34668, 34682, 34670, 34652, 34661, 34639, 34683,
+ 34677, 34658, 34663, 34665, 34906, 35077, 35084, 35092, 35083,
+ {f: 3, c: 35095}, 35078, 35094, 35089, 35086, 35081, 35234, 35236, 35235,
+ 35309, 35312, 35308, 35535, 35526, 35512, 35539, 35537, {f: 2, c: 35540},
+ 35515, 35543, 35518, 35520, 35525, 35544, 35523, 35514, 35517, 35545,
+ 35902, 35917, 35983, 36069, 36063, 36057, 36072, 36058, 36061, 36071,
+ 36256, 36252, 36257, 36251, 36384, 36387, 36389, 36388, 36398, 36373,
+ 36379, 36374, 36369, 36377, {f: 2, c: 36390}, 36372, 36370, 36376, 36371,
+ 36380, 36375, 36378, 36652, 36644, 36632, 36634, 36640, 36643,
+ {f: 2, c: 36630}, 36979, 36976, 36975, 36967, 36971, 37167, 37163,
+ {f: 2, c: 37161}, 37170, 37158, 37166, {f: 2, c: 37253}, 37258,
+ {f: 2, c: 37249}, 37252, 37248, 37584, {f: 2, c: 37571}, 37568, 37593,
+ 37558, 37583, 37617, 37599, 37592, 37609, 37591, 37597, 37580, 37615,
+ 37570, 37608, 37578, 37576, 37582, 37606, 37581, 37589, 37577, 37600,
+ 37598, 37607, 37585, 37587, 37557, 37601, 37669, 37574, 37556, 38268,
+ 38316, 38315, 38318, 38320, 38564, 38562, 38611, 38661, 38664, 38658,
+ 38746, 38794, 38798, 38792, 38864, 38863, 38942, 38941, 38950, 38953,
+ 38952, 38944, 38939, 38951, 39090, 39176, 39162, 39185, 39188,
+ {f: 2, c: 39190}, 39189, 39388, 39373, 39375, {f: 2, c: 39379}, 39374,
+ 39369, [39382, 60270], 39384, 39371, 39383, 39372, 39603, 39660, 39659,
+ 39667, 39666, 39665, 39750, 39747, 39783, 39796, 39793, 39782, 39798,
+ 39797, 39792, 39784, 39780, 39788, 40188, 40186, 40189, 40191, 40183,
+ 40199, 40192, 40185, 40187, 40200, 40197, 40196, 40579, 40659,
+ {f: 2, c: 40719}, 20764, 20755, 20759, 20762, 20753, 20958, 21300, 21473,
+ 22128, 22112, 22126, 22131, 22118, 22115, 22125, 22130, 22110, 22135,
+ 22300, 22299, 22728, 22717, 22729, 22719, 22714, 22722, 22716, 22726,
+ 23319, 23321, 23323, 23329, 23316, 23315, 23312, 23318, [23336, 59539],
+ 23322, 23328, 23326, 23535, 23980, 23985, 23977, 23975, 23989, 23984,
+ 23982, 23978, 23976, 23986, 23981, 23983, 23988, {f: 2, c: 24167}, 24166,
+ 24175, 24297, 24295, 24294, 24296, 24293, 24395, 24508, 24507, 24989,
+ 25000, 24982, 25029, 25012, 25030, 25025, 25036, 25018, 25023, 25016,
+ 24972, 25815, 25814, 25808, 25807, 25801, 25789, 25737, 25795, 25819,
+ 25843, 25817, 25907, 25983, 25980, 26018, 26312, 26302, 26304,
+ {f: 2, c: 26314}, 26319, 26301, 26299, 26298, 26316, 26403, 27188, 27238,
+ 27209, 27239, 27186, 27240, 27198, 27229, 27245, 27254, 27227, 27217,
+ 27176, 27226, 27195, 27199, 27201, 27242, 27236, 27216, 27215, 27220,
+ 27247, 27241, 27232, 27196, 27230, 27222, 27221, {f: 2, c: 27213}, 27206,
+ 27477, 27476, 27478, 27559, {f: 2, c: 27562}, 27592, 27591, 27652, 27651,
+ 27654, 28589, 28619, 28579, 28615, 28604, 28622, 28616, 28510, 28612,
+ 28605, 28574, 28618, 28584, 28676, 28581, 28590, 28602, 28588, 28586,
+ 28623, 28607, 28600, 28578, 28617, 28587, 28621, 28591, 28594, 28592,
+ 29125, 29122, 29119, 29112, 29142, {f: 2, c: 29120}, 29131, 29140, 29130,
+ 29127, 29135, 29117, 29144, 29116, 29126, {f: 2, c: 29146},
+ {f: 2, c: 29341}, 29545, {f: 2, c: 29542}, 29548, 29541, 29547, 29546,
+ 29823, 29850, 29856, 29844, 29842, 29845, 29857, 29963, 30080, 30255,
+ 30253, 30257, 30269, 30259, 30268, 30261, 30258, 30256, 30395, 30438,
+ 30618, 30621, 30625, 30620, 30619, {f: 2, c: 30626}, 30613, 30617, 30615,
+ 30941, 30953, 30949, 30954, 30942, 30947, 30939, {f: 2, c: 30945}, 30957,
+ {f: 2, c: 30943}, 31140, 31300, 31304, 31303, 31414, 31416, 31413, 31409,
+ 31415, 31710, 31715, 31719, 31709, 31701, 31717, 31706, 31720, 31737,
+ 31700, 31722, 31714, 31708, 31723, 31704, 31711, 31954, 31956, 31959,
+ {f: 2, c: 31952}, 32274, 32289, 32279, 32268, {f: 2, c: 32287}, 32275,
+ 32270, 32284, 32277, 32282, 32290, 32267, 32271, 32278, 32269, 32276,
+ 32293, 32292, 32579, {f: 2, c: 32635}, 32634, 32689, 32751, 32810, 32809,
+ 32876, 33201, 33190, 33198, 33209, 33205, 33195, 33200, 33196, 33204,
+ 33202, 33207, 33191, 33266, {f: 3, c: 33365}, 34134, 34117, 34155, 34125,
+ 34131, 34145, 34136, 34112, 34118, 34148, 34113, 34146, 34116, 34129,
+ 34119, 34147, 34110, 34139, 34161, 34126, 34158, 34165, 34133, 34151,
+ 34144, 34188, 34150, 34141, 34132, 34149, 34156, 34403, 34405, 34404,
+ 34724, 34715, 34703, 34711, 34707, 34706, 34696, 34689, 34710, 34712,
+ 34681, 34695, 34723, 34693, {f: 2, c: 34704}, 34717, 34692, 34708, 34716,
+ 34714, 34697, 35102, 35110, 35120, {f: 2, c: 35117}, 35111, 35121, 35106,
+ 35113, 35107, 35119, 35116, 35103, 35313, 35552, 35554, 35570,
+ {f: 2, c: 35572}, 35549, 35604, 35556, 35551, 35568, 35528, 35550, 35553,
+ 35560, 35583, 35567, 35579, {f: 2, c: 35985}, 35984, 36085, 36078, 36081,
+ 36080, 36083, 36204, 36206, 36261, 36263, 36403, 36414, 36408, 36416,
+ 36421, 36406, {f: 2, c: 36412}, 36417, 36400, 36415, 36541, [36662, 60329],
+ 36654, 36661, 36658, 36665, 36663, 36660, 36982, 36985, 36987, 36998,
+ 37114, 37171, {f: 2, c: 37173}, 37267, {f: 2, c: 37264}, 37261, 37263,
+ 37671, 37662, 37640, 37663, 37638, 37647, 37754, 37688, 37692, 37659,
+ 37667, 37650, 37633, 37702, 37677, 37646, 37645, 37579, 37661, 37626,
+ 37651, 37625, 37623, 37684, 37634, 37668, 37631, 37673, 37689, 37685,
+ 37674, 37652, 37644, 37643, 37630, 37641, 37632, 37627, 37654, 38332,
+ 38349, 38334, {f: 2, c: 38329}, 38326, 38335, 38325, 38333, 38569, 38612,
+ 38667, 38674, 38672, 38809, 38807, 38804, 38896, 38904, 38965, 38959,
+ 38962, 39204, 39199, 39207, 39209, 39326, 39406, 39404, 39397, 39396,
+ 39408, 39395, 39402, 39401, 39399, 39609, 39615, 39604, 39611, 39670,
+ 39674, 39673, 39671, 39731, 39808, 39813, 39815, 39804, 39806, 39803,
+ 39810, 39827, 39826, 39824, 39802, 39829, 39805, 39816, 40229, 40215,
+ 40224, 40222, 40212, 40233, 40221, 40216, 40226, 40208, 40217, 40223,
+ 40584, {f: 2, c: 40582}, 40622, 40621, {f: 2, c: 40661}, 40698, 40722,
+ 40765, 20774, 20773, 20770, 20772, 20768, 20777, 21236, 22163,
+ {f: 2, c: 22156}, 22150, 22148, 22147, 22142, 22146, 22143, 22145, 22742,
+ 22740, 22735, 22738, 23341, 23333, 23346, 23331, 23340, 23335, 23334,
+ 23343, 23342, 23419, {f: 2, c: 23537}, 23991, 24172, 24170, 24510, 25027,
+ 25013, 25020, 25063, 25056, 25061, 25060, 25064, 25054, 25839, 25833,
+ 25827, 25835, 25828, 25832, 25985, 25984, 26038, 26074, 26322, 27277,
+ 27286, 27265, 27301, 27273, 27295, 27291, 27297, 27294, 27271, 27283,
+ 27278, 27285, 27267, 27304, 27300, 27281, 27263, 27302, 27290, 27269,
+ 27276, 27282, 27483, 27565, 27657, 28620, 28585, 28660, 28628, 28643,
+ 28636, 28653, 28647, 28646, 28638, 28658, 28637, 28642, 28648, 29153,
+ 29169, 29160, 29170, 29156, 29168, 29154, 29555, {f: 2, c: 29550}, 29847,
+ 29874, 29867, 29840, 29866, 29869, 29873, 29861, 29871, {f: 3, c: 29968},
+ 29967, 30084, 30275, {f: 2, c: 30280}, 30279, 30372, 30441, 30645, 30635,
+ 30642, 30647, 30646, 30644, 30641, 30632, 30704, 30963, 30973, 30978,
+ {f: 2, c: 30971}, 30975, 30962, 30981, 30969, 30974, 30980, 31147, 31144,
+ 31324, 31323, 31318, 31320, 31316, 31322, 31422, {f: 2, c: 31424}, 31749,
+ 31759, 31730, 31744, 31743, 31739, 31758, 31732, 31755, 31731, 31746,
+ 31753, 31747, 31745, 31736, 31741, [31750, 58176], {f: 2, c: 31728}, 31760,
+ 31754, 31976, 32301, 32316, 32322, 32307, 38984, 32312, 32298, 32329,
+ 32320, 32327, 32297, 32332, 32304, 32315, 32310, 32324, 32314, 32581,
+ 32639, 32638, 32637, 32756, 32754, 32812, 33211, 33220, 33228, 33226,
+ 33221, 33223, 33212, 33257, 33371, 33370, 33372, 34179, 34176, 34191,
+ 34215, 34197, 34208, 34187, 34211, 34171, 34212, 34202, 34206, 34167,
+ 34172, 34185, 34209, 34170, 34168, 34135, 34190, 34198, 34182, 34189,
+ 34201, 34205, 34177, 34210, 34178, 34184, 34181, 34169, 34166, 34200,
+ 34192, 34207, 34408, 34750, 34730, 34733, 34757, 34736, 34732, 34745,
+ 34741, 34748, 34734, 34761, 34755, 34754, 34764, 34743, 34735, 34756,
+ 34762, 34740, 34742, 34751, 34744, 34749, 34782, 34738, 35125, 35123,
+ 35132, 35134, 35137, 35154, 35127, 35138, 35245, 35247, 35246,
+ {f: 2, c: 35314}, 35614, 35608, 35606, 35601, 35589, 35595, 35618, 35599,
+ 35602, 35605, 35591, 35597, 35592, 35590, 35612, 35603, 35610, 35919,
+ 35952, 35954, 35953, 35951, 35989, 35988, 36089, 36207, 36430, 36429,
+ 36435, 36432, 36428, 36423, 36675, 36672, 36997, 36990, 37176, 37274,
+ 37282, 37275, 37273, 37279, 37281, 37277, 37280, 37793, 37763, 37807,
+ 37732, 37718, 37703, 37756, 37720, 37724, 37750, 37705, {f: 2, c: 37712},
+ 37728, 37741, 37775, 37708, 37738, 37753, 37719, 37717, 37714, 37711,
+ 37745, 37751, 37755, 37729, 37726, 37731, 37735, 37710, 37721, 38343,
+ 38336, 38345, 38339, 38341, 38327, 38574, 38576, 38572, 38688, 38687,
+ 38680, 38685, 38681, 38810, 38817, 38812, 38814, 38813, 38869, 38868,
+ 38897, 38977, 38980, 38986, 38985, 38981, 38979, 39205, {f: 2, c: 39211},
+ 39210, 39219, 39218, 39215, 39213, 39217, 39216, 39320, 39331, 39329,
+ 39426, 39418, 39412, 39415, 39417, 39416, 39414, 39419, {f: 2, c: 39421},
+ 39420, 39427, 39614, 39678, 39677, 39681, 39676, 39752, 39834, 39848,
+ 39838, 39835, 39846, 39841, 39845, 39844, 39814, 39842, 39840, 39855,
+ 40243, 40257, 40295, 40246, {f: 2, c: 40238}, 40241, 40248, 40240, 40261,
+ {f: 2, c: 40258}, 40254, 40247, 40256, 40253, 32757, 40237, 40586, 40585,
+ 40589, 40624, 40648, 40666, 40699, 40703, 40740, 40739, 40738, 40788,
+ [12245, 40864], 20785, {f: 2, c: 20781}, 22168, 22172, 22167, 22170, 22173,
+ 22169, 22896, 23356, {f: 2, c: 23657}, 24000, {f: 2, c: 24173}, 25048,
+ 25055, {f: 2, c: 25069}, 25073, 25066, 25072, 25067, 25046, 25065, 25855,
+ 25860, 25853, 25848, 25857, 25859, 25852, 26004, 26075, {f: 2, c: 26330},
+ 26328, 27333, 27321, 27325, 27361, 27334, 27322, {f: 2, c: 27318}, 27335,
+ 27316, 27309, 27486, 27593, 27659, 28679, {f: 2, c: 28684}, 28673, 28677,
+ 28692, 28686, {f: 2, c: 28671}, 28667, 28710, 28668, 28663, 28682,
+ [29185, 60224], 29183, 29177, 29187, 29181, 29558, 29880, 29888, 29877,
+ 29889, 29886, 29878, 29883, 29890, 29972, 29971, 30300, 30308, 30297,
+ 30288, 30291, 30295, 30298, 30374, 30397, 30444, 30658, 30650, 30988,
+ {f: 2, c: 30995}, 30985, 30992, 30994, 30993, 31149, 31148, 31327, 31772,
+ 31785, 31769, 31776, 31775, 31789, 31773, 31782, 31784, 31778, 31781,
+ 31792, 32348, 32336, 32342, 32355, 32344, 32354, 32351, 32337, 32352,
+ 32343, 32339, 32693, 32691, {f: 2, c: 32759}, 32885, {f: 2, c: 33233},
+ 33232, 33375, 33374, 34228, 34246, 34240, 34243, 34242, 34227, 34229,
+ 34237, 34247, 34244, 34239, 34251, 34254, 34248, 34245, 34225, 34230,
+ 34258, 34340, 34232, 34231, 34238, 34409, 34791, 34790, 34786, 34779,
+ 34795, 34794, 34789, 34783, 34803, 34788, 34772, 34780, 34771, 34797,
+ 34776, 34787, 34775, 34777, 34817, 34804, 34792, 34781, 35155, 35147,
+ 35151, 35148, 35142, {f: 2, c: 35152}, 35145, 35626, 35623, 35619, 35635,
+ 35632, 35637, 35655, 35631, 35644, 35646, 35633, 35621, 35639, 35622,
+ 35638, 35630, 35620, 35643, 35645, 35642, 35906, 35957, 35993, 35992,
+ 35991, 36094, 36100, 36098, 36096, 36444, 36450, 36448, 36439, 36438,
+ 36446, 36453, 36455, 36443, 36442, 36449, 36445, 36457, 36436,
+ {f: 3, c: 36678}, 36683, 37160, {f: 2, c: 37178}, 37182, 37288, 37285,
+ 37287, 37295, 37290, 37813, 37772, 37778, 37815, 37787, 37789, 37769,
+ 37799, 37774, 37802, 37790, 37798, 37781, 37768, 37785, 37791, 37760,
+ 37773, 37809, 37777, 37810, 37796, 37800, 37812, 37795, {f: 2, c: 38354},
+ 38353, 38579, 38615, 38618, 24002, 38623, 38616, 38621, 38691, 38690,
+ 38693, 38828, 38830, 38824, 38827, 38820, 38826, 38818, 38821, 38871,
+ 38873, 38870, 38872, 38906, {f: 3, c: 38992}, 39096, 39233, 39228, 39226,
+ 39439, 39435, 39433, 39437, 39428, 39441, 39434, 39429, 39431, 39430,
+ 39616, 39644, 39688, {f: 2, c: 39684}, 39721, 39733, 39754, 39756, 39755,
+ 39879, 39878, 39875, 39871, 39873, 39861, 39864, 39891, 39862, 39876,
+ 39865, 39869, 40284, 40275, 40271, 40266, 40283, 40267, 40281, 40278,
+ 40268, 40279, 40274, 40276, 40287, 40280, 40282, 40590, 40588, 40671,
+ 40705, 40704, [40726, 58693], 40741, 40747, 40746, 40745, 40744, 40780,
+ 40789, {f: 2, c: 20788}, 21142, 21239, 21428, 22187, 22189,
+ {f: 2, c: 22182}, 22186, 22188, 22746, 22749, 22747, 22802,
+ {f: 3, c: 23357}, 24003, 24176, 24511, 25083, 25863, 25872, 25869, 25865,
+ 25868, 25870, 25988, 26078, 26077, 26334, 27367, 27360, 27340, 27345,
+ 27353, 27339, 27359, 27356, 27344, 27371, 27343, 27341, 27358, 27488,
+ 27568, 27660, 28697, 28711, 28704, 28694, 28715, {f: 3, c: 28705}, 28713,
+ 28695, 28708, 28700, 29196, 29194, 29191, 29186, 29189, {f: 2, c: 29349},
+ 29348, 29347, 29345, 29899, 29893, 29879, 29891, 29974, 30304,
+ {f: 2, c: 30665}, 30660, 30705, 31005, 31003, 31009, 31004, 30999, 31006,
+ 31152, {f: 2, c: 31335}, 31795, 31804, 31801, 31788, 31803, 31980, 31978,
+ 32374, 32373, 32376, 32368, 32375, 32367, 32378, 32370, 32372, 32360,
+ 32587, 32586, 32643, 32646, 32695, {f: 2, c: 32765}, 32888, 33239, 33237,
+ 33291, 33380, 33377, 33379, 34283, 34289, 34285, 34265, 34273, 34280,
+ 34266, 34263, 34284, 34290, 34296, 34264, 34271, 34275, 34268, 34257,
+ 34288, 34278, 34287, 34270, 34274, 34816, 34810, 34819, {f: 2, c: 34806},
+ 34825, 34828, 34827, 34822, 34812, 34824, 34815, 34826, 34818, 35170,
+ {f: 2, c: 35162}, 35159, 35169, 35164, 35160, 35165, 35161, 35208, 35255,
+ 35254, 35318, 35664, 35656, 35658, 35648, 35667, 35670, 35668, 35659,
+ 35669, 35665, 35650, 35666, 35671, 35907, 35959, 35958, 35994,
+ {f: 2, c: 36102}, 36105, 36268, 36266, 36269, 36267, 36461, 36472, 36467,
+ 36458, 36463, 36475, 36546, 36690, 36689, {f: 2, c: 36687}, 36691, 36788,
+ 37184, 37183, 37296, 37293, 37854, 37831, 37839, 37826, 37850, 37840,
+ 37881, 37868, 37836, 37849, 37801, 37862, 37834, 37844, 37870, 37859,
+ 37845, 37828, 37838, 37824, 37842, 37797, 37863, 38269, {f: 2, c: 38362},
+ 38625, 38697, {f: 2, c: 38699}, 38696, 38694, 38835, 38839, 38838,
+ {f: 3, c: 38877}, 39004, 39001, 39005, 38999, 39103, 39101, 39099, 39102,
+ 39240, 39239, 39235, {f: 2, c: 39334}, 39450, 39445, 39461, 39453, 39460,
+ 39451, 39458, 39456, 39463, 39459, 39454, 39452, 39444, 39618, 39691,
+ 39690, 39694, 39692, 39735, {f: 2, c: 39914}, 39904, 39902, 39908, 39910,
+ 39906, 39920, 39892, 39895, 39916, 39900, 39897, 39909, 39893, 39905,
+ 39898, 40311, 40321, 40330, 40324, 40328, 40305, 40320, 40312, 40326,
+ {f: 2, c: 40331}, 40317, 40299, {f: 2, c: 40308}, 40304, 40297, 40325,
+ 40307, 40315, 40322, 40303, 40313, 40319, 40327, 40296, 40596, 40593,
+ 40640, 40700, 40749, {f: 2, c: 40768}, 40781, {f: 3, c: 40790}, 21303,
+ 22194, 22197, 22195, 22755, 23365, {f: 2, c: 24006}, {f: 2, c: 24302},
+ {f: 2, c: 24512}, 25081, 25879, 25878, 25877, 25875, 26079, 26344,
+ {f: 2, c: 26339}, 27379, 27376, 27370, 27368, 27385, 27377,
+ {f: 2, c: 27374}, 28732, 28725, 28719, 28727, 28724, 28721, 28738, 28728,
+ 28735, 28730, 28729, 28714, 28736, 28731, 28723, 28737, {f: 2, c: 29203},
+ 29352, 29565, 29564, 29882, 30379, 30378, 30398, 30445, 30668,
+ {f: 2, c: 30670}, 30669, 30706, 31013, 31011, {f: 2, c: 31015}, 31012,
+ 31017, 31154, 31342, {f: 2, c: 31340}, 31479, 31817, 31816, 31818, 31815,
+ 31813, 31982, 32379, 32382, 32385, 32384, 32698, 32767, 32889, 33243,
+ 33241, {f: 2, c: 33384}, 34338, 34303, 34305, 34302, 34331, 34304, 34294,
+ 34308, 34313, 34309, 34316, 34301, 34841, {f: 2, c: 34832}, 34839, 34835,
+ 34838, 35171, 35174, 35257, 35319, 35680, 35690, 35677, 35688, 35683,
+ 35685, 35687, 35693, 36270, 36486, 36488, 36484, 36697, {f: 2, c: 36694},
+ 36693, 36696, 36698, 37005, 37187, 37185, 37303, 37301, {f: 2, c: 37298},
+ 37899, 37907, 37883, 37920, 37903, 37908, 37886, 37909, 37904, 37928,
+ 37913, 37901, 37877, 37888, 37879, 37895, 37902, 37910, 37906, 37882,
+ 37897, 37880, 37948, 37898, 37887, 37884, 37900, 37878, 37905, 37894,
+ 38366, 38368, 38367, {f: 2, c: 38702}, 38841, 38843, {f: 2, c: 38909},
+ 39008, {f: 2, c: 39010}, 39007, {f: 2, c: 39105}, 39248, 39246, 39257,
+ 39244, 39243, 39251, 39474, 39476, 39473, 39468, 39466, 39478, 39465,
+ 39470, 39480, 39469, 39623, 39626, 39622, 39696, 39698, 39697, 39947,
+ 39944, 39927, 39941, 39954, 39928, 40000, 39943, 39950, 39942, 39959,
+ 39956, 39945, 40351, 40345, 40356, 40349, 40338, 40344, 40336, 40347,
+ 40352, 40340, 40348, 40362, 40343, 40353, 40346, 40354, 40360, 40350,
+ 40355, 40383, 40361, 40342, {f: 2, c: 40358}, 40601, 40603, 40602, 40677,
+ 40676, 40679, 40678, 40752, 40750, 40795, 40800, 40798, 40797, 40793,
+ 40849, 20794, 20793, 21144, 21143, 22211, {f: 2, c: 22205}, 23368, 23367,
+ 24011, 24015, 24305, 25085, 25883, 27394, 27388, 27395, 27384, 27392,
+ {f: 2, c: 28739}, 28746, {f: 2, c: 28744}, {f: 2, c: 28741}, 29213, 29210,
+ 29209, 29566, 29975, 30314, 30672, 31021, 31025, 31023, 31828, 31827,
+ 31986, 32394, [32391, 60229], 32392, 32395, 32390, 32397, 32589, 32699,
+ 32816, 33245, 34328, 34346, 34342, 34335, 34339, 34332, 34329, 34343,
+ 34350, 34337, 34336, 34345, 34334, 34341, 34857, 34845, 34843, 34848,
+ 34852, 34844, 34859, 34890, 35181, 35177, 35182, 35179, 35322, 35705,
+ 35704, 35653, {f: 2, c: 35706}, 36112, 36116, 36271, 36494, 36492, 36702,
+ 36699, 36701, 37190, {f: 2, c: 37188}, 37305, 37951, 37947, 37942, 37929,
+ 37949, 37936, 37945, 37930, 37943, 37932, 37952, 37937, 38373, 38372,
+ 38371, 38709, 38714, 38847, 38881, 39012, 39113, 39110, 39104, 39256,
+ 39254, 39481, 39485, 39494, 39492, 39490, 39489, 39482, 39487, 39629,
+ 39701, {f: 2, c: 39703}, 39702, 39738, 39762, 39979, 39965, 39964, 39980,
+ 39971, {f: 2, c: 39976}, 39972, 39969, 40375, 40374, 40380, 40385, 40391,
+ 40394, 40399, 40382, 40389, 40387, 40379, 40373, 40398, {f: 2, c: 40377},
+ 40364, 40392, 40369, 40365, 40396, 40371, 40397, 40370, 40570, 40604,
+ 40683, 40686, 40685, 40731, 40728, 40730, 40753, 40782, 40805, 40804,
+ 40850, 20153, 22214, 22213, 22219, 22897, {f: 2, c: 23371}, 24021, 24017,
+ 24306, 25889, 25888, 25894, 25890, 27403, {f: 2, c: 27400}, 27661,
+ {f: 3, c: 28757}, 28754, {f: 2, c: 29214}, 29353, 29567, 29912, 29909,
+ 29913, 29911, 30317, 30381, 31029, 31156, {f: 2, c: 31344}, 31831, 31836,
+ 31833, 31835, 31834, 31988, 31985, 32401, 32591, 32647, 33246, 33387,
+ {f: 2, c: 34356}, 34355, 34348, 34354, 34358, 34860, 34856, 34854, 34858,
+ 34853, 35185, 35263, 35262, 35323, 35710, 35716, 35714, 35718, 35717,
+ 35711, 36117, 36501, 36500, 36506, 36498, 36496, {f: 2, c: 36502}, 36704,
+ 36706, 37191, 37964, 37968, {f: 2, c: 37962}, 37967, 37959, 37957,
+ {f: 2, c: 37960}, 37958, 38719, 38883, 39018, 39017, 39115, 39252, 39259,
+ 39502, {f: 2, c: 39507}, 39500, 39503, 39496, 39498, 39497, 39506, 39504,
+ 39632, 39705, 39723, 39739, 39766, 39765, 40006, 40008, 39999, 40004,
+ 39993, 39987, 40001, 39996, 39991, 39988, 39986, 39997, 39990, 40411,
+ 40402, 40414, 40410, 40395, 40400, 40412, 40401, 40415, 40425, 40409,
+ 40408, 40406, 40437, 40405, 40413, 40630, 40688, 40757, 40755, 40754,
+ 40770, 40811, 40853, 40866, 20797, 21145, 22760, 22759, 22898, 23373,
+ 24024, 34863, 24399, 25089, {f: 2, c: 25091}, 25897, 25893, 26006, 26347,
+ {f: 2, c: 27409}, 27407, 27594, 28763, 28762, 29218, 29570, 29569, 29571,
+ 30320, 30676, 31847, 31846, 32405, 33388, 34362, 34368, 34361, 34364,
+ 34353, 34363, 34366, 34864, 34866, 34862, 34867, 35190, 35188, 35187,
+ 35326, 35724, 35726, 35723, 35720, 35909, 36121, 36504, 36708, 36707,
+ 37308, 37986, 37973, 37981, 37975, 37982, {f: 2, c: 38852}, 38912, 39510,
+ 39513, {f: 3, c: 39710}, 40018, 40024, 40016, 40010, 40013, 40011, 40021,
+ 40025, 40012, 40014, 40443, 40439, 40431, 40419, 40427, 40440, 40420,
+ 40438, 40417, 40430, 40422, 40434, [40432, 60370], 40418, 40428, 40436,
+ 40435, 40424, 40429, 40642, 40656, {f: 2, c: 40690}, 40710, 40732, 40760,
+ 40759, 40758, 40771, 40783, 40817, 40816, {f: 2, c: 40814}, 22227, 22221,
+ 23374, 23661, 25901, {f: 2, c: 26349}, 27411, 28767, 28769, 28765, 28768,
+ 29219, 29915, 29925, 30677, 31032, 31159, 31158, 31850, 32407, 32649,
+ 33389, 34371, 34872, 34871, 34869, 34891, {f: 2, c: 35732},
+ {f: 3, c: 36510}, 36509, 37310, 37309, 37314, 37995, {f: 2, c: 37992},
+ 38629, 38726, 38723, 38727, 38855, 38885, 39518, 39637, 39769, 40035,
+ 40039, 40038, 40034, 40030, 40032, 40450, 40446, 40455, 40451, 40454,
+ 40453, {f: 2, c: 40448}, 40457, 40447, 40445, 40452, 40608, 40734, 40774,
+ {f: 3, c: 40820}, 22228, 25902, 26040, {f: 2, c: 27416}, 27415, 27418,
+ 28770, 29222, 29354, {f: 2, c: 30680}, 31033, 31849, 31851, 31990, 32410,
+ 32408, 32411, 32409, {f: 2, c: 33248}, {f: 3, c: 34374}, {f: 2, c: 35193},
+ 35196, 35195, 35327, {f: 2, c: 35736}, 36517, 36516, 36515, 37998, 37997,
+ 37999, 38001, 38003, 38729, 39026, 39263, 40040, 40046, 40045, 40459,
+ 40461, 40464, 40463, 40466, 40465, 40609, 40693, 40713, 40775, 40824,
+ 40827, 40826, 40825, 22302, 28774, 31855, 34876, 36274, 36518, 37315,
+ 38004, 38008, 38006, 38005, 39520, [39726, 60830], 40052, 40051, 40049,
+ 40053, 40468, 40467, 40694, 40714, 40868, 28776, 28773, 31991, 34410,
+ 34878, 34877, 34879, 35742, 35996, 36521, 36553, 38731, {f: 2, c: 39027},
+ 39116, 39265, 39339, 39524, {f: 2, c: 39526}, 39716, 40469, 40471, 40776,
+ 25095, 27422, 29223, 34380, 36520, 38018, {f: 2, c: 38016}, 39529, 39528,
+ 40473, 34379, 35743, 38019, 40057, 40631, 30325, 39531, 40058, 40477,
+ {f: 2, c: 28777}, 29225, 40612, 40830, 40777, 40856, {s: 97}, 65075, 0,
+ 65076, 65103, [168, 776, 63208], [710, 63209, 65342], [12541, 63210],
+ [12542, 63211], [12445, 63212], [12446, 63213], 0, [12293, 63216],
+ [12294, 63217], [12295, 63218], [12540, 63219], [63220, 65339],
+ [63221, 65341], [10045, 63222], [12353, 63223], [12354, 63224],
+ [12355, 63225], [12356, 63226], [12357, 63227], [12358, 63228],
+ [12359, 63229], [12360, 63230], [12361, 63231], [12362, 63232],
+ [12363, 63233], [12364, 63234], [12365, 63235], [12366, 63236],
+ [12367, 63237], [12368, 63238], [12369, 63239], [12370, 63240],
+ [12371, 63241], [12372, 63242], [12373, 63243], [12374, 63244],
+ [12375, 63245], [12376, 63246], [12377, 63247], [12378, 63248],
+ [12379, 63249], [12380, 63250], [12381, 63251], [12382, 63252],
+ [12383, 63253], [12384, 63254], [12385, 63255], [12386, 63256],
+ [12387, 63257], [12388, 63258], [12389, 63259], [12390, 63260],
+ [12391, 63261], [12392, 63262], [12393, 63263], [12394, 63264],
+ [12395, 63265], [12396, 63266], [12397, 63267], [12398, 63268],
+ [12399, 63269], [12400, 63270], [12401, 63271], [12402, 63272],
+ [12403, 63273], [12404, 63274], [12405, 63275], [12406, 63276],
+ [12407, 63277], [12408, 63278], [12409, 63279], [12410, 63280],
+ [12411, 63281], [12412, 63282], [12413, 63283], [12414, 63284],
+ [12415, 63285], [12416, 63286], [12417, 63287], [12418, 63288],
+ [12419, 63289], [12420, 63290], [12421, 63291], [12422, 63292],
+ [12423, 63293], [12424, 63294], [12425, 63295], [12426, 63296],
+ [12427, 63297], [12428, 63298], [12429, 63299], [12430, 63300],
+ [12431, 63301], [12432, 63302], [12433, 63303], [12434, 63304],
+ [12435, 63305], [12449, 63306], [12450, 63307], [12451, 63308],
+ [12452, 63309], [12453, 63310], [12454, 63311], [12455, 63312],
+ [12456, 63313], [12457, 63314], [12458, 63315], [12459, 63316],
+ [12460, 63317], [12461, 63318], [12462, 63319], [12463, 63320],
+ [12464, 63321], [12465, 63322], [12466, 63323], [12467, 63324],
+ [12468, 63325], [12469, 63326], [12470, 63327], [12471, 63328],
+ [12472, 63329], [12473, 63330], [12474, 63331], [12475, 63332],
+ [12476, 63333], [12477, 63334], [12478, 63335], [12479, 63336],
+ [12480, 63337], [12481, 63338], [12482, 63339], [12483, 63340],
+ [12484, 63341], [12485, 63342], [12486, 63343], [12487, 63344],
+ [12488, 63345], [12489, 63346], [12490, 63347], [12491, 63348],
+ [12492, 63349], [12493, 63350], [12494, 63351], [12495, 63352],
+ [12496, 63353], [12497, 63354], [12498, 63355], [12499, 63356],
+ [12500, 63357], [12501, 63358], [12502, 63359], [12503, 63360],
+ [12504, 63361], [12505, 63362], [12506, 63363], [12507, 63364],
+ [12508, 63365], [12509, 63366], [12510, 63367], [12511, 63368],
+ [12512, 63369], [12513, 63370], [12514, 63371], [12515, 63372],
+ [12516, 63373], [12517, 63374], [12518, 63375], [12519, 63376],
+ [12520, 63377], [12521, 63378], [12522, 63379], [12523, 63380],
+ [12524, 63381], [12525, 63382], [12526, 63383], [12527, 63384],
+ [12528, 63385], [12529, 63386], [12530, 63387], [12531, 63388],
+ [12532, 63389], [12533, 63390], [12534, 63391], [1040, 63392],
+ [1041, 63393], [1042, 63394], [1043, 63395], [1044, 63396], [1045, 63397],
+ [1025, 63398], [1046, 63399], [1047, 63400], [1048, 63401], [1049, 63402],
+ [1050, 63403], [1051, 63404], [1052, 63405], [1053, 63406], [1054, 63407],
+ [1055, 63408], [1056, 63409], [1057, 63410], [1058, 63411], [1059, 63412],
+ [1060, 63413], [1061, 63414], [1062, 63415], [1063, 63416], [1064, 63417],
+ [1065, 63418], [1066, 63419], [1067, 63420], [1068, 63421], [1069, 63422],
+ [1070, 63423], [1071, 63424], [1072, 63425], [1073, 63426], [1074, 63427],
+ [1075, 63428], [1076, 63429], [1077, 63430], [1105, 63431], [1078, 63432],
+ [1079, 63433], [1080, 63434], [1081, 63435], [1082, 63436], [1083, 63437],
+ [1084, 63438], [1085, 63439], [1086, 63440], [1087, 63441], [1088, 63442],
+ [1089, 63443], [1090, 63444], [1091, 63445], [1092, 63446], [1093, 63447],
+ [1094, 63448], [1095, 63449], [1096, 63450], [1097, 63451], [1098, 63452],
+ [1099, 63453], [1100, 63454], [1101, 63455], [1102, 63456], [1103, 63457],
+ [8679, 63458], [8632, 63459], [8633, 63460], [20033, 63461],
+ [63462, 131276], [20058, 63463], [63464, 131210], [20994, 63465],
+ [17553, 63466], 63467, [20872, 63468], [13853, 63469], [63470, 161287],
+ {s: 40}, [172, 63511, 65506], [63512, 65508], [63513, 65287],
+ [63514, 65282], [12849, 63515], [8470, 63516], [8481, 63517], 30849,
+ [37561, 58501], 35023, 22715, 24658, 31911, 23290, 9556, 9574, 9559, 9568,
+ 9580, 9571, 9562, 9577, 9565, 9554, 9572, 9557, {s: 3}, 9560, 9575, 9563,
+ 9555, 9573, 9558, 9567, 9579, 9570, 9561, 9576, 9564, 9553, {s: 5}, 9619,
+ {s: 26}, [58129, 147159], [22462, 58130], [58131, 159443], [28990, 58132],
+ [58133, 153568], [27042, 58135], [58136, 166889], [23412, 58137],
+ [31305, 58138], [58139, 153825], [58140, 169177], [31333, 58141],
+ [31357, 58142], [58143, 154028], [31419, 58144], [31408, 58145],
+ [31426, 58146], [31427, 58147], [29137, 58148], [58149, 156813],
+ [16842, 58150], [31450, 58151], [31453, 58152], [31466, 58153],
+ [16879, 58154], [21682, 58155], [58156, 154625], [31499, 58157],
+ [31573, 58158], [31529, 58159], [58160, 152334], [58161, 154878],
+ [31650, 58162], [31599, 58163], [33692, 58164], [58165, 154548],
+ [58166, 158847], [31696, 58167], [33825, 58168], [31634, 58169], 0,
+ [58171, 154912], 0, [33938, 58174], [31738, 58175], 0, [31797, 58177],
+ [58178, 154817], [31812, 58179], [31875, 58180], [58181, 149634],
+ [31910, 58182], [58184, 148856], [31945, 58185], [31943, 58186],
+ [31974, 58187], 0, [31987, 58189], [31989, 58190], [32359, 58192],
+ [17693, 58193], [58194, 159300], [32093, 58195], [58196, 159446],
+ [32137, 58198], [32171, 58199], [28981, 58200], [32179, 58201], 32214,
+ [58203, 147543], [58204, 155689], [32228, 58205], [15635, 58206],
+ [32245, 58207], [58208, 137209], [32229, 58209], [58210, 164717], 0,
+ [58212, 155937], [58213, 155994], [32366, 58214], 0, [17195, 58216],
+ [37996, 58217], [32295, 58218], [32576, 58219], [32577, 58220],
+ [32583, 58221], [31030, 58222], [58223, 156368], [39393, 58224],
+ [32663, 58225], [58226, 156497], [32675, 58227], [58228, 136801],
+ [58229, 131176], [17756, 58230], [58231, 145254], [58233, 164666],
+ [32762, 58234], [58235, 156809], 0, [32776, 58237], [32797, 58238], 0,
+ [32815, 58240], [58241, 172167], [58242, 158915], [32827, 58243],
+ [32828, 58244], [32865, 58245], [58246, 141076], [18825, 58247],
+ [58248, 157222], [58249, 146915], [58250, 157416], [26405, 58251],
+ [32935, 58252], [58253, 166472], [33031, 58254], [33050, 58255],
+ [22704, 58256], [58257, 141046], [27775, 58258], [58259, 156824],
+ [25831, 58261], [58262, 136330], [33304, 58263], [58264, 137310],
+ [27219, 58265], [58266, 150117], [58267, 150165], [17530, 58268],
+ [33321, 58269], [58271, 158290], [58272, 146814], [20473, 58273],
+ [58274, 136445], [34018, 58275], [33634, 58276], 0, [58278, 149927],
+ [58279, 144688], [58280, 137075], [58281, 146936], [33450, 58282],
+ [26907, 58283], [58284, 194964], [16859, 58285], [34123, 58286],
+ [33488, 58287], [33562, 58288], [58289, 134678], [58290, 137140],
+ [14017, 58291], [58292, 143741], [58293, 144730], [33403, 58294],
+ [33506, 58295], [33560, 58296], [58297, 147083], [58298, 159139],
+ [58299, 158469], [58300, 158615], [58301, 144846], [15807, 58302],
+ [33565, 58303], [21996, 58304], [33669, 58305], [17675, 58306],
+ [58307, 159141], [33708, 58308], 0, [33747, 58310], [58312, 159444],
+ [27223, 58313], [34138, 58314], [13462, 58315], [58316, 159298],
+ [33880, 58318], [58319, 154596], [33905, 58320], [15827, 58321],
+ [17636, 58322], [27303, 58323], [33866, 58324], [31064, 58326], 0,
+ [58328, 158614], [58329, 159351], [58330, 159299], [34014, 58331], 0,
+ [33681, 58333], [17568, 58334], [33939, 58335], [34020, 58336],
+ [58337, 154769], [16960, 58338], [58339, 154816], [17731, 58340],
+ [34100, 58341], [23282, 58342], 0, [17703, 58344], [34163, 58345],
+ [17686, 58346], [26559, 58347], [34326, 58348], [58349, 165413],
+ [58350, 165435], [34241, 58351], [58352, 159880], [34306, 58353],
+ [58354, 136578], [58355, 159949], [58356, 194994], [17770, 58357],
+ [34344, 58358], [13896, 58359], [58360, 137378], [21495, 58361],
+ [58362, 160666], [34430, 58363], 0, [58365, 172280], [34798, 58366],
+ [58367, 142375], [34737, 58368], [34778, 58369], [34831, 58370, 60990],
+ [22113, 58371], [34412, 58372], [26710, 58373], [17935, 58374],
+ [34885, 58375], [34886, 58376], [58377, 161248], [58378, 146873],
+ [58379, 161252], [34910, 58380], [34972, 58381], [18011, 58382],
+ [34996, 58383], [34997, 58384], [35013, 58386], [58388, 161551],
+ [35207, 58389], {s: 3}, [35239, 58393], [35260, 58394], [58395, 166437],
+ [35303, 58396], [58397, 162084], [58398, 162493], [35484, 58399],
+ [30611, 58400], [37374, 58401], [35472, 58402], [58403, 162393],
+ [31465, 58404], [58405, 162618], [18195, 58407], [58408, 162616],
+ [29052, 58409], [35596, 58410], [35615, 58411], [58412, 152624],
+ [58413, 152933], [35647, 58414], 0, [35661, 58416], [35497, 58417],
+ [58418, 150138], [35728, 58419], [35739, 58420], [35503, 58421],
+ [58422, 136927], [17941, 58423], [34895, 58424], [35995, 58425],
+ [58426, 163156], [58427, 163215], [58428, 195028], [14117, 58429],
+ [58430, 163155], [36054, 58431], [58432, 163224], [58433, 163261],
+ [36114, 58434], [36099, 58435], [58436, 137488], [36059, 58437],
+ [28764, 58438], [36113, 58439], [16080, 58441], 0, [36265, 58443],
+ [58444, 163842], [58445, 135188], [58446, 149898], [15228, 58447],
+ [58448, 164284], [58449, 160012], [31463, 58450], [36525, 58451],
+ [36534, 58452], [36547, 58453], [37588, 58454], [36633, 58455],
+ [36653, 58456], [58457, 164709], [58458, 164882], [36773, 58459],
+ [37635, 58460], [58461, 172703], [58462, 133712], [36787, 58463], 0,
+ [58465, 166366], [58466, 165181], [58467, 146875], [24312, 58468],
+ [58469, 143970], [36857, 58470], 0, [58474, 140069], [14720, 58475],
+ [58476, 159447], [36919, 58477], [58478, 165180], [58479, 162494],
+ [36961, 58480], [58481, 165228], [58482, 165387], [37032, 58483],
+ [58484, 165651], [37060, 58485], [58486, 165606], [37038, 58487], 0,
+ [37223, 58489], [37289, 58491], [37316, 58492], [31916, 58493],
+ [58494, 166195], [58495, 138889], [37390, 58496], [27807, 58497],
+ [37441, 58498], [37474, 58499], [58500, 153017], [58502, 166598],
+ [58503, 146587], [58504, 166668], [58505, 153051], [58506, 134449],
+ [37676, 58507], [37739, 58508], [58509, 166625], [58510, 166891],
+ [23235, 58512], [58513, 166626], [58514, 166629], [18789, 58515],
+ [37444, 58516], [58517, 166892], [58518, 166969], [58519, 166911],
+ [37747, 58520], [37979, 58521], [36540, 58522], [38277, 58523],
+ [38310, 58524], [37926, 58525], [38304, 58526], [28662, 58527],
+ [17081, 58528], [58530, 165592], [58531, 135804], [58532, 146990],
+ [18911, 58533], [27676, 58534], [38523, 58535], [38550, 58536],
+ [16748, 58537], [38563, 58538], [58539, 159445], [25050, 58540], 58541,
+ [30965, 58542], [58543, 166624], [38589, 58544], [21452, 58545],
+ [18849, 58546], [58547, 158904], [58548, 131700], [58549, 156688],
+ [58550, 168111], [58551, 168165], [58552, 150225], [58553, 137493],
+ [58554, 144138], [38705, 58555], [34370, 58556], [38710, 58557],
+ [18959, 58558], [17725, 58559], [17797, 58560], [58561, 150249],
+ [28789, 58562], [23361, 58563], [38683, 58564], 0, [58566, 168405],
+ [38743, 58567], [23370, 58568], [58569, 168427], [38751, 58570],
+ [37925, 58571], [20688, 58572], [58573, 143543], [58574, 143548],
+ [38793, 58575], [38815, 58576], [38833, 58577], [38846, 58578],
+ [38848, 58579], [38866, 58580], [38880, 58581], [58582, 152684],
+ [38894, 58583], [29724, 58584], [58585, 169011], 0, [38901, 58587],
+ [58588, 168989], [58589, 162170], [19153, 58590], [38964, 58591],
+ [38963, 58592], [38987, 58593], [39014, 58594], [15118, 58595],
+ [58596, 160117], [15697, 58597], [58598, 132656], [58599, 147804],
+ [58600, 153350], [39114, 58601], [39095, 58602], [39112, 58603],
+ [39111, 58604], [19199, 58605], [58606, 159015], [58607, 136915],
+ [21936, 58608], [39137, 58609], [39142, 58610], [39148, 58611],
+ [37752, 58612], [39225, 58613], [58614, 150057], [19314, 58615],
+ [58616, 170071], [58617, 170245], [39413, 58618], [39436, 58619],
+ [39483, 58620], [39440, 58621], [39512, 58622], [58623, 153381],
+ [14020, 58624], [58625, 168113], [58626, 170965], [39648, 58627],
+ [39650, 58628], [58629, 170757], [39668, 58630], [19470, 58631],
+ [39700, 58632], [39725, 58633], [58634, 165376], [20532, 58635],
+ [39732, 58636], [14531, 58638], [58639, 143485], [39760, 58640],
+ [39744, 58641], [58642, 171326], [23109, 58643], [58644, 137315],
+ [39822, 58645], [39938, 58647], [39935, 58648], [39948, 58649],
+ [58650, 171624], [40404, 58651], [58652, 171959], [58653, 172434],
+ [58654, 172459], [58655, 172257], [58656, 172323], [58657, 172511],
+ [40318, 58658], [40323, 58659], [58660, 172340], [40462, 58661],
+ [40388, 58663], [58665, 172435], [58666, 172576], [58667, 137531],
+ [58668, 172595], [40249, 58669], [58670, 172217], [58671, 172724],
+ [40592, 58672], [40597, 58673], [40606, 58674], [40610, 58675],
+ [19764, 58676], [40618, 58677], [40623, 58678], [58679, 148324],
+ [40641, 58680], [15200, 58681], [14821, 58682], [15645, 58683],
+ [20274, 58684], [14270, 58685], [58686, 166955], [40706, 58687],
+ [40712, 58688], [19350, 58689], [37924, 58690], [58691, 159138],
+ [40727, 58692, 60836], 0, [40761, 58694], [22175, 58695], [22154, 58696],
+ [40773, 58697], [39352, 58698], [58699, 168075], [38898, 58700],
+ [33919, 58701], 0, [40809, 58703], [31452, 58704], [40846, 58705],
+ [29206, 58706], [19390, 58707], [58708, 149877], [58709, 149947],
+ [29047, 58710], [58711, 150008], [58712, 148296], [58713, 150097],
+ [29598, 58714], [58715, 166874], [58716, 137466], [31135, 58717],
+ [58718, 166270], [58719, 167478], [37737, 58720], [37875, 58721],
+ [58722, 166468], [37612, 58723], [37761, 58724], [37835, 58725],
+ [58726, 166252], [58727, 148665], [29207, 58728], [16107, 58729],
+ [30578, 58730], [31299, 58731], [28880, 58732], [58733, 148595],
+ [58734, 148472], [29054, 58735], [58736, 137199], [28835, 58737],
+ [58738, 137406], [58739, 144793], [16071, 58740], [58741, 137349],
+ [58742, 152623], [58743, 137208], [14114, 58744], [58745, 136955],
+ [58746, 137273], [14049, 58747], [58748, 137076], [58749, 137425],
+ [58750, 155467], [14115, 58751], [58752, 136896], [22363, 58753],
+ [58754, 150053], [58755, 136190], [58756, 135848], [58757, 136134],
+ [58758, 136374], [34051, 58759, 58761], [58760, 145062], 0, [33877, 58762],
+ [58763, 149908], [58764, 160101], [58765, 146993], [58766, 152924],
+ [58767, 147195], [58768, 159826], [17652, 58769], [58770, 145134],
+ [58771, 170397], [58772, 159526], [26617, 58773], [14131, 58774],
+ [15381, 58775], [15847, 58776], [22636, 58777], [58778, 137506],
+ [26640, 58779], [16471, 58780], [58781, 145215], [58782, 147681],
+ [58783, 147595], [58784, 147727], [58785, 158753], [21707, 58786],
+ [22174, 58787], [58788, 157361], [22162, 58789], [58790, 135135],
+ [58791, 134056], [58792, 134669], 0, [58794, 166675], [37788, 58795],
+ [20216, 58796], [20779, 58797], [14361, 58798], [58799, 148534],
+ [20156, 58800], [58801, 132197], 0, [20299, 58803], [20362, 58804],
+ [58805, 153169], [23144, 58806], [58807, 131499], [58808, 132043],
+ [14745, 58809], [58810, 131850], [58811, 132116], [13365, 58812],
+ [20265, 58813], [58814, 131776], [58815, 167603], [58816, 131701],
+ [35546, 58817], [58818, 131596], [20120, 58819], [20685, 58820],
+ [20749, 58821], [20386, 58822], [20227, 58823], [58824, 150030],
+ [58825, 147082], [20290, 58826], [20526, 58827], [20588, 58828],
+ [20609, 58829], [20428, 58830], [20453, 58831], [20568, 58832],
+ [20732, 58833], [28278, 58838], [58839, 144789], [58840, 147001],
+ [58841, 147135], [28018, 58842], [58843, 137348], [58844, 147081],
+ [20904, 58845], [20931, 58846], [58847, 132576], [17629, 58848],
+ [58849, 132259], [58850, 132242], [58851, 132241], [36218, 58852],
+ [58853, 166556], [58854, 132878], [21081, 58855], [21156, 58856],
+ [58857, 133235], [21217, 58858], 0, [18042, 58860], [29068, 58861],
+ [58862, 148364], [58863, 134176], [58864, 149932], [58865, 135396],
+ [27089, 58866], [58867, 134685], 0, [16094, 58869], [29849, 58870],
+ [29716, 58871], [29782, 58872], [29592, 58873], [19342, 58874],
+ [58875, 150204], [58876, 147597], [21456, 58877], [13700, 58878],
+ [29199, 58879], [58880, 147657], [21940, 58881], [58882, 131909],
+ [21709, 58883], [58884, 134086], [22301, 58885], [37469, 58886],
+ [38644, 58887], [22493, 58889], [22413, 58890], [22399, 58891],
+ [13886, 58892], [22731, 58893], [23193, 58894], [58895, 166470],
+ [58896, 136954], [58897, 137071], [58898, 136976], [23084, 58899],
+ [22968, 58900], [23166, 58902], [23247, 58903], [23058, 58904],
+ [58905, 153926], [58906, 137715], [58907, 137313], [58908, 148117],
+ [14069, 58909], [27909, 58910], [29763, 58911], [23073, 58912],
+ [58913, 155267], [23169, 58914], [58915, 166871], [58916, 132115],
+ [37856, 58917], [29836, 58918], [58919, 135939], [28933, 58920],
+ [18802, 58921], [37896, 58922], [58923, 166395], [37821, 58924],
+ [14240, 58925], [23582, 58926], [23710, 58927], [24158, 58928],
+ [24136, 58929], [58930, 137622], [58931, 137596], [58932, 146158],
+ [24269, 58933], [23375, 58934], [58935, 137475], [58936, 137476],
+ [14081, 58937], [58938, 137376], [14045, 58939], [58940, 136958],
+ [14035, 58941], [33066, 58942], [58943, 166471], [58944, 138682],
+ [58945, 144498], [58946, 166312], [24332, 58947, 60916], [24334, 58948],
+ [58949, 137511], [58950, 137131], [23147, 58951], [58952, 137019],
+ [23364, 58953], [58955, 161277], [34912, 58956], [24702, 58957],
+ [58958, 141408], [58959, 140843], [24539, 58960], [16056, 58961],
+ [58962, 140719], [58963, 140734], [58964, 168072], [58965, 159603],
+ [25024, 58966], [58967, 131134], [58968, 131142], [58969, 140827],
+ [24985, 58970], [24984, 58971], [24693, 58972], [58973, 142491],
+ [58974, 142599], [58975, 149204], [58976, 168269], [25713, 58977],
+ [58978, 149093], [58979, 142186], [14889, 58980], [58981, 142114],
+ [58982, 144464], [58983, 170218], [58984, 142968], [25399, 58985],
+ [25782, 58987], [25393, 58988], [25553, 58989], [58990, 149987],
+ [58991, 142695], [25252, 58992], [58993, 142497], [25659, 58994],
+ [25963, 58995], [26994, 58996], [15348, 58997], [58998, 143502],
+ [58999, 144045], [59000, 149897], [59001, 144043], [21773, 59002],
+ [59003, 144096], [59004, 137433], [59005, 169023], [26318, 59006],
+ [59007, 144009], [59008, 143795], [15072, 59009], [59011, 152964],
+ [59012, 166690], [59013, 152975], [59014, 136956], [59015, 152923],
+ [59016, 152613], [30958, 59017], [59018, 143619], [59019, 137258],
+ [59020, 143924], [13412, 59021], [59022, 143887], [59023, 143746],
+ [59024, 148169], [26254, 59025], [59026, 159012], [26219, 59027],
+ [19347, 59028], [26160, 59029], [59030, 161904], [59031, 138731],
+ [26211, 59032], [59033, 144082], [59034, 144097], [26142, 59035],
+ [59036, 153714], [14545, 59037], [59038, 145466], [59039, 145340],
+ [15257, 59040], [59041, 145314], [59042, 144382], [29904, 59043],
+ [15254, 59044], [59046, 149034], [26806, 59047], 0, [15300, 59049],
+ [27326, 59050], [59052, 145365], [59053, 148615], [27187, 59054],
+ [27218, 59055], [27337, 59056], [27397, 59057], [59058, 137490],
+ [25873, 59059], [26776, 59060], [27212, 59061], [15319, 59062],
+ [27258, 59063], [27479, 59064], [59065, 147392], [59066, 146586],
+ [37792, 59067], [37618, 59068], [59069, 166890], [59070, 166603],
+ [37513, 59071], [59072, 163870], [59073, 166364], [37991, 59074],
+ [28069, 59075], [28427, 59076], 0, [59079, 147327], [15759, 59080],
+ [28164, 59081], [59082, 147516], [23101, 59083], [28170, 59084],
+ [22599, 59085], [27940, 59086], [30786, 59087], [28987, 59088],
+ [59089, 148250], [59090, 148086], [28913, 59091], [29264, 59092, 61085],
+ [29319, 59093], [29332, 59094], [59095, 149391], [59096, 149285],
+ [20857, 59097], [59098, 150180], [59099, 132587], [29818, 59100],
+ [59101, 147192], [59102, 144991], [59103, 150090], [59104, 149783],
+ [59105, 155617], [16134, 59106], [16049, 59107], [59108, 150239],
+ [59109, 166947], [59110, 147253], [24743, 59111], [16115, 59112],
+ [29900, 59113], [29756, 59114], [37767, 59115], [29751, 59116],
+ [17567, 59117], [59118, 159210], [17745, 59119], [30083, 59120],
+ [16227, 59121], [59122, 150745], [59123, 150790], [16216, 59124],
+ [30037, 59125], [30323, 59126], [59127, 173510], 0, [29800, 59129, 61070],
+ [59130, 166604], [59131, 149931], [59132, 149902], [15099, 59133],
+ [15821, 59134], [59135, 150094], [16127, 59136], [59137, 149957],
+ [59138, 149747], [37370, 59139], [22322, 59140], [37698, 59141],
+ [59142, 166627], [59143, 137316], [20703, 59144], [59145, 152097],
+ [59146, 152039], [30584, 59147], [59148, 143922], [30478, 59149],
+ [30479, 59150], [30587, 59151], [59152, 149143], [59153, 145281],
+ [14942, 59154], [59155, 149744], [29752, 59156], [29851, 59157],
+ [16063, 59158], [59159, 150202], [59160, 150215], [16584, 59161],
+ [59162, 150166], [59163, 156078], [37639, 59164], [59165, 152961],
+ [30750, 59166], [30861, 59167], [30856, 59168], [30930, 59169],
+ [29648, 59170], [31065, 59171], [59172, 161601], [59173, 153315],
+ [16654, 59174], 0, 0, [31141, 59177], [27181, 59178], [59179, 147194],
+ [31290, 59180], [31220, 59181], [16750, 59182], [59183, 136934],
+ [16690, 59184], [37429, 59185], [31217, 59186], [59187, 134476],
+ [59188, 149900], [59189, 131737], [59190, 146874], [59191, 137070],
+ [13719, 59192], [21867, 59193], [13680, 59194], [13994, 59195],
+ [59196, 131540], [59197, 134157], [31458, 59198], [23129, 59199],
+ [59200, 141045], [59201, 154287], [59202, 154268], [23053, 59203],
+ [59204, 131675], [30960, 59205], [23082, 59206], [59207, 154566],
+ [31486, 59208], [16889, 59209], [31837, 59210], [31853, 59211],
+ [16913, 59212], [59213, 154547], [59214, 155324], [59215, 155302],
+ [31949, 59216], [59217, 150009], [59218, 137136], [31886, 59219],
+ [31868, 59220], [31918, 59221], [27314, 59222], [32220, 59223],
+ [32263, 59224], [32211, 59225], [32590, 59226], [59227, 156257],
+ [59228, 155996], [59229, 162632], [32151, 59230], [59231, 155266],
+ [17002, 59232], [59233, 158581], [59234, 133398], [26582, 59235],
+ [59236, 131150], [59237, 144847], [22468, 59238], [59239, 156690],
+ [59240, 156664], [32733, 59242], [31527, 59243], [59244, 133164],
+ [59245, 154345], [59246, 154947], [31500, 59247], [59248, 155150],
+ [39398, 59249], [34373, 59250], [39523, 59251], [27164, 59252],
+ [59253, 144447], [59255, 150007], [59256, 157101], [39455, 59257],
+ [59258, 157088], 0, [59260, 160039], [59261, 158929], [17642, 59262],
+ [33079, 59263], [17410, 59264], [32966, 59265], [33033, 59266],
+ [33090, 59267], [59268, 157620], [39107, 59269], [59270, 158274],
+ [33378, 59271], [33381, 59272], [59273, 158289], [33875, 59274],
+ [59275, 159143], [34320, 59276], [59277, 160283], [23174, 59278],
+ [16767, 59279], [59280, 137280], [23339, 59281], [59282, 137377],
+ [23268, 59283], [59284, 137432], [34464, 59285], [59286, 195004],
+ [59287, 146831], [34861, 59288], [59289, 160802], [23042, 59290],
+ [34926, 59291], [20293, 59292], [34951, 59293], [35007, 59294],
+ [35046, 59295], [35173, 59296], [35149, 59297], [59298, 153219],
+ [35156, 59299], [59300, 161669], [59301, 161668], [59302, 166901],
+ [59303, 166873], [59304, 166812], [59305, 166393], [16045, 59306],
+ [33955, 59307], [18165, 59308], [18127, 59309], [14322, 59310],
+ [35389, 59311], [35356, 59312], [59313, 169032], [24397, 59314],
+ [37419, 59315], [59316, 148100], [26068, 59317], [28969, 59318],
+ [28868, 59319], [59320, 137285], [40301, 59321], [35999, 59322],
+ [36073, 59323], [59324, 163292], [22938, 59325], [30659, 59326],
+ [23024, 59327], [14036, 59329], [36394, 59330], [36519, 59331],
+ [59332, 150537], [36656, 59333], [36682, 59334], [17140, 59335],
+ [27736, 59336], [28603, 59337], [59338, 140065], [18587, 59339],
+ [28537, 59340], [28299, 59341], [59342, 137178], [39913, 59343],
+ [14005, 59344], [59345, 149807], [37051, 59346], 0, [21873, 59348],
+ [18694, 59349], [37307, 59350], [37892, 59351], [59352, 166475],
+ [16482, 59353], [59354, 166652], [37927, 59355], [59356, 166941],
+ [59357, 166971], [34021, 59358], [35371, 59359], [38297, 59360],
+ [38311, 59361], [38295, 59362], [38294, 59363], [59364, 167220],
+ [29765, 59365], [16066, 59366], [59367, 149759], [59368, 150082],
+ [59369, 148458], [16103, 59370], [59371, 143909], [38543, 59372],
+ [59373, 167655], [59374, 167526], [59375, 167525], [16076, 59376],
+ [59377, 149997], [59378, 150136], [59379, 147438], [29714, 59380],
+ [29803, 59381], [16124, 59382], [38721, 59383], [59384, 168112],
+ [26695, 59385], [18973, 59386], [59387, 168083], [59388, 153567], 0,
+ [37736, 59390], [59391, 166281], [59392, 166950], [59393, 166703],
+ [59394, 156606], [37562, 59395], [23313, 59396], [35689, 59397],
+ [18748, 59398], [29689, 59399], [59400, 147995], [38811, 59401], 0,
+ [39224, 59403], [59404, 134950], [24001, 59405], [59406, 166853],
+ [59407, 150194], [38943, 59408], [59409, 169178], [37622, 59410],
+ [59411, 169431], [37349, 59412], [17600, 59413], [59414, 166736],
+ [59415, 150119], [59416, 166756], [39132, 59417], [59418, 166469],
+ [16128, 59419], [37418, 59420], [18725, 59421], [33812, 59422],
+ [39227, 59423], [39245, 59424], [59425, 162566], [15869, 59426], 0,
+ [19311, 59428], [39338, 59429], [39516, 59430], [59431, 166757],
+ [59432, 153800], [27279, 59433], [39457, 59434], [23294, 59435],
+ [39471, 59436], [59437, 170225], [19344, 59438], [59439, 170312],
+ [39356, 59440], [19389, 59441], [19351, 59442], [37757, 59443],
+ [22642, 59444], [59445, 135938], [22562, 59446], [59447, 149944],
+ [59448, 136424], [30788, 59449], [59450, 141087], [59451, 146872],
+ [26821, 59452], [15741, 59453], [37976, 59454], [14631, 59455],
+ [24912, 59456], [59457, 141185], [59458, 141675], [24839, 59459],
+ [40015, 59460], [40019, 59461], [40059, 59462], [39989, 59463],
+ [39952, 59464], [39807, 59465], [39887, 59466], [59467, 171565],
+ [39839, 59468], [59469, 172533], [59470, 172286], [40225, 59471],
+ [19630, 59472], [59473, 147716], [40472, 59474], [19632, 59475],
+ [40204, 59476], [59477, 172468], [59478, 172269], [59479, 172275],
+ [59480, 170287], [40357, 59481], [33981, 59482], [59483, 159250],
+ [59484, 159711], [59485, 158594], [34300, 59486], [17715, 59487],
+ [59488, 159140], [59489, 159364], [59490, 159216], [33824, 59491],
+ [34286, 59492], [59493, 159232], [59494, 145367], [59495, 155748],
+ [31202, 59496], [59497, 144796], [59498, 144960], [59500, 149982],
+ [15714, 59501], [37851, 59502], [37566, 59503], [37704, 59504],
+ [59505, 131775], [30905, 59506], [37495, 59507], [37965, 59508],
+ [20452, 59509], [13376, 59510], [36964, 59511], [59512, 152925],
+ [30781, 59513], [30804, 59514], [30902, 59515], [30795, 59516],
+ [59517, 137047], [59518, 143817], [59519, 149825], [13978, 59520],
+ [20338, 59521], [28634, 59522], [28633, 59523], 0, [28702, 59524, 59525],
+ [21524, 59526], [59527, 147893], [22459, 59528], [22771, 59529],
+ [22410, 59530], [40214, 59531], [22487, 59532], [28980, 59533],
+ [13487, 59534], [59535, 147884], [29163, 59536], [59537, 158784],
+ [59538, 151447], 0, [59540, 137141], [59541, 166473], [24844, 59542],
+ [23246, 59543], [23051, 59544], [17084, 59545], [59546, 148616],
+ [14124, 59547], [19323, 59548], [59549, 166396], [37819, 59550],
+ [37816, 59551], [59552, 137430], [59553, 134941], [33906, 59554],
+ [59555, 158912], [59556, 136211], [59557, 148218], [59558, 142374],
+ [59559, 148417], [22932, 59560], [59561, 146871], [59562, 157505],
+ [32168, 59563], [59564, 155995], [59565, 155812], [59566, 149945],
+ [59567, 149899], [59568, 166394], [37605, 59569], [29666, 59570],
+ [16105, 59571], [29876, 59572], [59573, 166755], [59574, 137375],
+ [16097, 59575], [59576, 150195], [27352, 59577], [29683, 59578],
+ [29691, 59579], [16086, 59580], [59581, 150078], [59582, 150164],
+ [59583, 137177], [59584, 150118], [59585, 132007], [59586, 136228],
+ [59587, 149989], [29768, 59588], [59589, 149782], [28837, 59590],
+ [59591, 149878], [37508, 59592], [29670, 59593], [37727, 59594],
+ [59595, 132350], [37681, 59596], [59597, 166606], [59598, 166422],
+ [37766, 59599], [59600, 166887], [59601, 153045], [18741, 59602],
+ [59603, 166530], [29035, 59604], [59605, 149827], [59606, 134399],
+ [22180, 59607], [59608, 132634], [59609, 134123], [59610, 134328],
+ [21762, 59611], [31172, 59612], [59613, 137210], [32254, 59614],
+ [59615, 136898], [59616, 150096], [59617, 137298], [17710, 59618],
+ [37889, 59619], [14090, 59620], [59621, 166592], [59622, 149933],
+ [22960, 59623], [59624, 137407], [59625, 137347], [59626, 160900],
+ [23201, 59627], [14050, 59628], [59629, 146779], [14000, 59630],
+ [37471, 59631], [23161, 59632], [59633, 166529], [59634, 137314],
+ [37748, 59635], [15565, 59636], [59637, 133812], [19094, 59638],
+ [14730, 59639], [20724, 59640], [15721, 59641], [15692, 59642],
+ [59643, 136092], [29045, 59644], [17147, 59645], [59646, 164376],
+ [28175, 59647], [59648, 168164], [17643, 59649], [27991, 59650],
+ [59651, 163407], [28775, 59652], [27823, 59653], [15574, 59654],
+ [59655, 147437], [59656, 146989], [28162, 59657], [28428, 59658],
+ [15727, 59659], [59660, 132085], [30033, 59661], [14012, 59662],
+ [13512, 59663], [18048, 59664], [16090, 59665], [18545, 59666],
+ [22980, 59667], [37486, 59668], [18750, 59669], [36673, 59670],
+ [59671, 166940], [59672, 158656], [22546, 59673], [22472, 59674],
+ [14038, 59675], [59676, 136274], [28926, 59677], [59678, 148322],
+ [59679, 150129], [59680, 143331], [59681, 135856], [59682, 140221],
+ [26809, 59683], [26983, 59684], [59685, 136088], [59686, 144613],
+ [59687, 162804], [59688, 145119], [59689, 166531], [59690, 145366],
+ [59691, 144378], [59692, 150687], [27162, 59693], [59694, 145069],
+ [59695, 158903], [33854, 59696], [17631, 59697], [17614, 59698],
+ [59699, 159014], [59700, 159057], [59701, 158850], [59702, 159710], 0, 0,
+ [33597, 59705], [59706, 137018], [33773, 59707], [59708, 158848],
+ [59709, 159827], [59710, 137179], [22921, 59711], [23170, 59712],
+ [59713, 137139], [23137, 59714], [23153, 59715], [59716, 137477],
+ [59717, 147964], [14125, 59718], [23023, 59719], [59720, 137020],
+ [14023, 59721], [29070, 59722], [37776, 59723], [26266, 59724],
+ [59725, 148133], [23150, 59726], [23083, 59727], [59728, 148115],
+ [27179, 59729], [59730, 147193], [59731, 161590], [59732, 148571],
+ [59733, 148170], [28957, 59734], [59735, 148057], [59736, 166369],
+ [20400, 59737], [59738, 159016], [23746, 59739], [59740, 148686],
+ [59741, 163405], [59742, 148413], [27148, 59743], [59744, 148054],
+ [59745, 135940], 0, [28979, 59747], [59748, 148457], [15781, 59749],
+ [27871, 59750], [59751, 194597], [23019, 59754], [24412, 59757],
+ [59764, 144128], [31955, 59776], [59783, 162548], [59786, 153334],
+ [59790, 162584], [36972, 59791], [33270, 59795], [30476, 59797],
+ [27810, 59799], [22269, 59800], [22633, 59828], [26465, 59832],
+ [23646, 59838], [22770, 59841], [28857, 59843], [26627, 59853],
+ [36795, 59859], [36796, 59861], [20001, 59871], [31545, 59898],
+ [15820, 59902], [29482, 57990, 59909], [30048, 59912], [22586, 59920],
+ [33446, 59932], [27018, 59940], [24803, 59944], [20206, 59984],
+ [39364, 60002], [40639, 60023], [21249, 60025], [26528, 60038],
+ [24808, 60046], [20916, 60053], [31363, 60064], [39994, 60075],
+ [31432, 60093], [26906, 60098], [22956, 60100], [22592, 60102],
+ [21610, 60114], [24807, 60123], [22138, 60125], [26965, 60132],
+ [39983, 60133], [34725, 60134], [23584, 60141], [24075, 60143],
+ [26398, 60147], [33965, 60157], [35713, 60161], [20088, 60166],
+ [25283, 60176], [26709, 60180], 0, [33533, 60190], [35237, 60194],
+ [36768, 60196], [38840, 60198], [38983, 60200], [39613, 60201],
+ [24497, 60218], [26184, 60219], [26303, 60220], [60221, 162425], 0,
+ [60225, 149946], 0, 0, [60230, 131910], [26382, 60232], [26904, 60233],
+ [60235, 161367], [60236, 155618], [60239, 161278], [60240, 139418],
+ [18640, 60241], [19128, 60242], [60244, 166554], [60247, 147515],
+ [60250, 150085], [60251, 132554], [20946, 60252], [60253, 132625],
+ [22943, 60254], [60255, 138920], [15294, 60256], [60257, 146687],
+ [14747, 60262], [60264, 165352], [60265, 170441], [14178, 60266],
+ [60267, 139715], [35678, 60268], [60269, 166734], 0, [29193, 60274],
+ [60276, 134264], [60280, 132985], [36570, 60281], [21135, 60283],
+ [29041, 60285], [60288, 147274], [60289, 150183], [21948, 60290],
+ [60293, 158546], [13427, 60295], [60297, 161330], [18200, 60299],
+ [60303, 149823], [20582, 60305], [13563, 60306], [60307, 144332], 0,
+ [18300, 60310], [60311, 166216], [60315, 138640], 0, [60320, 162834],
+ [36950, 60321], [60323, 151450], [35682, 60324], [23899, 60327],
+ [60328, 158711], 0, [60331, 137500], [35562, 60332], [60333, 150006],
+ [60335, 147439], [19392, 60337], [60340, 141083], [37989, 60341],
+ [60342, 153569], [24981, 60343], [23079, 60344], [60345, 194765], 0,
+ [60348, 148769], [20074, 60350], [60351, 149812], [38486, 60352],
+ [28047, 60353], [60354, 158909], [35191, 60356], [60359, 156689], 0,
+ [31554, 60363], [60364, 168128], [60365, 133649], 0, [31301, 60369],
+ [39462, 60372], [13919, 60374], [60375, 156777], [60376, 131105],
+ [31107, 60377], [23852, 60380], [60381, 144665], 0, [18128, 60384],
+ [30011, 60386], [34917, 60387], [22710, 60389], [14108, 60390],
+ [60391, 140685], [15444, 60394], [37505, 60397], [60398, 139642],
+ [37680, 60400], [60402, 149968], [27705, 60403], [60406, 134904],
+ [34855, 60407], [35061, 60408], [60409, 141606], [60410, 164979],
+ [60411, 137137], [28344, 60412], [60413, 150058], [60414, 137248],
+ [14756, 60415], 0, 0, [17727, 60419], [26294, 60420], [60421, 171181],
+ [60422, 170148], [35139, 60423], [16607, 60427], [60428, 136714],
+ [14753, 60429], [60430, 145199], [60431, 164072], [60432, 136133],
+ [29101, 60433], [33638, 60434], [60436, 168360], 0, [19639, 60438],
+ [60439, 159919], [60440, 166315], [60445, 147834], [31555, 60446],
+ [31102, 60447], [28597, 60449], [60450, 172767], [27139, 60451],
+ [60452, 164632], [21410, 60453], [60454, 159239], [37823, 60455],
+ [26678, 60456], [38749, 59389, 60457], [60458, 164207], [60460, 158133],
+ [60461, 136173], [60462, 143919], [23941, 60464], [60465, 166960],
+ [22293, 60467], [38947, 60468], [60469, 166217], [23979, 60470],
+ [60471, 149896], [26046, 60472], [27093, 60473], [21458, 60474],
+ [60475, 150181], [60476, 147329], [15377, 60477], [26422, 60478],
+ [60482, 139169], [13770, 60490], [18682, 60493], 0, [30728, 60496],
+ [37461, 60497], [17394, 60499], [17375, 60501], [23032, 60505], 0,
+ [22155, 60518], [60520, 169449], [36882, 60541], [21953, 60546],
+ [17673, 60551], [32383, 60552], [28502, 60553], [27313, 60554],
+ [13540, 60556], [60558, 161949], [14138, 60559], 0, [60562, 163876],
+ [60565, 162366], [15851, 60567], [60569, 146615], [60574, 156248],
+ [22207, 60575], [36366, 60577], [23405, 60578], [25566, 60581], 0,
+ [25904, 60585], [22061, 60586], [21530, 60588], [60591, 171416],
+ [19581, 60592], [22050, 60593], [22046, 60594], [32585, 60595],
+ [22901, 60597], [60598, 146752], [34672, 60599], [33047, 60604],
+ [40286, 60605], [36120, 60606], [30267, 60607], [40005, 60608],
+ [30286, 60609], [30649, 60610], [37701, 60611], [21554, 60612],
+ [33096, 60613], [33527, 60614], [22053, 60615], [33074, 60616],
+ [33816, 60617], [32957, 60618], [21994, 60619], [31074, 60620],
+ [22083, 60621], [21526, 60622], [60623, 134813], [13774, 60624],
+ [22021, 57509, 60625], [22001, 60626], [26353, 60627], [60628, 164578],
+ [13869, 60629], [30004, 60630], [22000, 60631], [21946, 60632],
+ [21655, 60633], [21874, 60634], [60635, 134209], [60636, 134294],
+ [24272, 57652, 60637], [60639, 134774], [60640, 142434], [60641, 134818],
+ [40619, 60642], [32090, 60643], 0, [60645, 135285], [25245, 60646],
+ [38765, 60647], [21652, 60648], [36045, 60649], [29174, 60650],
+ [37238, 60651], [25596, 60652], [25529, 60653], [25598, 60654],
+ [21865, 60655], [60656, 142147], [40050, 60657], [60658, 143027],
+ [20890, 60659], [13535, 60660], [60661, 134567], [20903, 60662],
+ [21581, 60663], [21790, 60664], [21779, 60665], [30310, 60666],
+ [36397, 60667], [60668, 157834], [30129, 60669], [32950, 60670],
+ [34820, 60671], 0, [35015, 60673], [33206, 60674], [33820, 60675],
+ [17644, 60677], [29444, 60678], [33547, 60681], [22139, 60683],
+ [37232, 60690], [37384, 60692], [60696, 134905], [29286, 60697],
+ [18254, 60699], [60701, 163833], [16634, 60703], [40029, 60704],
+ [25887, 60705], [18675, 60707], [60708, 149472], [60709, 171388], 0,
+ [60713, 161187], 60715, [60716, 155720], [29091, 60718], [32398, 60719],
+ [40272, 60720], [13687, 60723], [27826, 60725], [21351, 60726],
+ [14812, 60728], [60731, 149016], [33325, 60734], [21579, 60735], 60739,
+ [14930, 60740], [29556, 60742], [60743, 171692], [19721, 60744],
+ [39917, 60745], 0, [19547, 60748], [60751, 171998], [33884, 60752],
+ [60754, 160434], [25390, 60757], [32037, 60758], [14890, 60761],
+ [36872, 60762], [21196, 60763], [15988, 60764], [13946, 60765],
+ [17897, 60766], [60767, 132238], [30272, 60768], [23280, 60769],
+ [60770, 134838], [30842, 60771], [18358, 60772], [22695, 60773],
+ [16575, 60774], [22140, 60775], [39819, 60776], [23924, 60777],
+ [30292, 60778], [60779, 173108], [40581, 60780], [19681, 60781], 0,
+ [14331, 60783], [24857, 60784], [60786, 148466], 60787, [22109, 60788],
+ [60792, 171526], [21044, 60793], [13741, 60795], 0, [40316, 60797],
+ [31830, 60798], [39737, 60799], [22494, 60800], [23635, 60802],
+ [25811, 60803], [60804, 169168], [60805, 156469], [34477, 60807],
+ [60808, 134440], [60811, 134513], 60812, [20990, 60813], [60814, 139023],
+ [23950, 60815], [38659, 60816], [60817, 138705], [40577, 60818],
+ [36940, 60819], [31519, 60820], [39682, 60821], [23761, 60822],
+ [31651, 60823], [25192, 60824], [25397, 60825], [39679, 60826],
+ [31695, 60827], [39722, 60828], [31870, 60829], 0, [31810, 60831],
+ [31878, 60832], [39957, 60833], [31740, 60834], [39689, 60835], 0, 39982,
+ [40794, 60839], [21875, 60840], [23491, 60841], [20477, 60842],
+ [40600, 60843], [20466, 60844], [21088, 60845], [21201, 60847],
+ [22375, 60848], [20566, 60849], [22967, 60850], [24082, 60851],
+ [38856, 60852], [40363, 60853], [36700, 60854], [21609, 60855],
+ [38836, 60856], [39232, 60857], [38842, 60858], [21292, 60859],
+ [24880, 60860], [26924, 60861], [21466, 60862], [39946, 60863],
+ [40194, 60864], [19515, 60865], [38465, 60866], [27008, 60867],
+ [20646, 60868], [30022, 60869], [60870, 137069], [39386, 60871],
+ [21107, 60872], 60873, [37209, 60874], [38529, 60875], [37212, 60876],
+ 60877, [37201, 60878], [60879, 167575], [25471, 60880], [27338, 60882],
+ [22033, 60883], [37262, 60884], [30074, 60885], [25221, 60886],
+ [29519, 60888], [31856, 60889], [60890, 154657], 60892, [30422, 60894],
+ [39837, 60895], [20010, 60896], [60897, 134356], [33726, 60898],
+ [34882, 60899], 60900, [23626, 60901], [27072, 60902], 0, 0,
+ [21023, 60905], [24053, 60906], [20174, 60907], [27697, 60908],
+ [60909, 131570], [20281, 60910], [21660, 60911], 0, [21146, 60913],
+ [36226, 60914], [13822, 60915], 0, [13811, 60917], 60918, [27474, 60919],
+ [37244, 60920], [40869, 60921], [39831, 60922], [38958, 60923],
+ [39092, 60924], [39610, 60925], [40616, 60926], [40580, 60927],
+ [31508, 60929], 60930, [27642, 60931], [34840, 60932], [32632, 60933],
+ 60934, [22048, 60935], [60936, 173642], [36471, 60937], [40787, 60938],
+ 60939, [36308, 60940], [36431, 60941], [40476, 60942], [36353, 60943],
+ [25218, 60944], [60945, 164733], [36392, 60946], [36469, 60947],
+ [31443, 60948], [31294, 60950], [30936, 60951], [27882, 60952],
+ [35431, 60953], [30215, 60954], [40742, 60956], [27854, 60957],
+ [34774, 60958], [30147, 60959], [60960, 172722], [30803, 60961],
+ [36108, 60963], [29410, 60964], [29553, 60965], [35629, 60966],
+ [29442, 60967], [29937, 60968], [36075, 60969], [60970, 150203],
+ [34351, 60971], [24506, 60972], [34976, 60973], [17591, 60974], 60975,
+ [60977, 159237], 60978, [35454, 60979], [60980, 140571], 60981,
+ [24829, 60982], [30311, 60983], [39639, 60984], [40260, 60985],
+ [37742, 58859, 60986], [39823, 60987], [34805, 60988], 60989, 0,
+ [36087, 60991], [29484, 60992], [38689, 60993], [39856, 60994],
+ [13782, 60995], [29362, 60996], [19463, 60997], [31825, 60998],
+ [39242, 60999], [24921, 61001], [19460, 61002], [40598, 61003],
+ [24957, 61004], 61005, [22367, 61006], [24943, 61007], [25254, 61008],
+ [25145, 61009], 0, [14940, 61011], [25058, 61012], [21418, 61013],
+ [25444, 61015], [26626, 61016], [13778, 61017], [23895, 61018],
+ [36826, 61020], [61021, 167481], 61022, [20697, 61023], [30982, 61025],
+ [21298, 61026], [38456, 61027], [61028, 134971], [16485, 61029], 61030,
+ [30718, 61031], 61032, [31938, 61033], [61034, 155418], [31962, 61035],
+ [31277, 61036], [32870, 61037], [32867, 61038], [32077, 61039],
+ [29957, 61040], [29938, 61041], [35220, 61042], [33306, 61043],
+ [26380, 61044], [32866, 61045], [61046, 160902], [32859, 61047],
+ [29936, 61048], [33027, 61049], [30500, 61050], [35209, 61051],
+ [61052, 157644], [30035, 61053], [34729, 61055], [34766, 61056],
+ [33224, 61057], [34700, 61058], [35401, 61059], [36013, 61060],
+ [35651, 61061], [30507, 61062], [29944, 61063], [34010, 61064],
+ [27058, 61066], [36262, 61067], 61068, [35241, 58392, 61069], 0,
+ [28089, 61071], [34753, 61072], [61073, 147473], [29927, 61074],
+ [15835, 61075], [29046, 61076], [24740, 57702, 61077], [24988, 61078],
+ [15569, 61079], 0, [24695, 61081], 61082, [32625, 61083], 0,
+ [24809, 61086], [19326, 61087], [57344, 132423], [37595, 57345],
+ [57346, 132575], [57347, 147397], [34124, 57348], [17077, 57349],
+ [29679, 57350], [20917, 57351], [13897, 57352], [57353, 149826],
+ [57354, 166372], [37700, 57355], [57356, 137691], [33518, 57357],
+ [57358, 146632], [30780, 57359], [26436, 57360], [25311, 57361],
+ [57362, 149811], [57363, 166314], [57364, 131744], [57365, 158643],
+ [57366, 135941], [20395, 57367], [57368, 140525], [20488, 57369],
+ [57370, 159017], [57371, 162436], [57372, 144896], [57373, 150193],
+ [57374, 140563], 0, [57376, 131966], [24484, 57377], [57378, 131968],
+ [57379, 131911], [28379, 57380], [57381, 132127], 20702, [20737, 57383],
+ [13434, 57384], [20750, 57385], [39020, 57386], [14147, 57387],
+ [33814, 57388], [57389, 149924], [57390, 132231], [20832, 57391],
+ [57392, 144308], [20842, 57393], [57394, 134143], [57395, 139516],
+ [57396, 131813], [57397, 140592], [57398, 132494], [57399, 143923],
+ [57400, 137603], [23426, 57401], [34685, 57402], [57403, 132531],
+ [57404, 146585], [20914, 57405], [20920, 57406], [40244, 57407],
+ [20937, 57408], [20943, 57409], [20945, 57410], [15580, 57411],
+ [20947, 57412], [57413, 150182], [20915, 57414], 0, 0, [20973, 57417],
+ [33741, 57418], [26942, 57419], [57420, 145197], [24443, 57421],
+ [21003, 57422], [21030, 57423], [21052, 57424], [21173, 57425],
+ [21079, 57426], [21140, 57427], [21177, 57428], [21189, 57429],
+ [31765, 57430], [34114, 57431], [21216, 57432], [34317, 57433],
+ [57434, 158483], 0, [57436, 166622], [21833, 57437], [28377, 57438],
+ [57439, 147328], [57440, 133460], [57441, 147436], [21299, 57442], 0,
+ [57444, 134114], [27851, 57445], [57446, 136998], [26651, 57447],
+ [29653, 57448], [24650, 57449], [16042, 57450], [14540, 57451],
+ [57452, 136936], [29149, 57453], [17570, 57454], [21357, 57455],
+ [21364, 57456], [57457, 165547], [21374, 57458], 0, [57460, 136598],
+ [57461, 136723], [30694, 57462], [21395, 57463], [57464, 166555],
+ [21408, 57465], [21419, 57466], [21422, 57467], [29607, 57468],
+ [57469, 153458], [16217, 57470], [29596, 57471], [21441, 57472],
+ [21445, 57473], [27721, 57474], [20041, 57475], [22526, 57476],
+ [21465, 57477], [15019, 57478], [57479, 134031], [21472, 57480],
+ [57481, 147435], [57482, 142755], [21494, 57483], [57484, 134263],
+ [21523, 57485], [28793, 57486], [21803, 57487], [26199, 57488],
+ [27995, 57489], [21613, 57490], [57491, 158547], [57492, 134516],
+ [21853, 57493], [21647, 57494], [21668, 57495], [18342, 57496],
+ [57497, 136973], [57498, 134877], [15796, 57499], [57500, 134477],
+ [57501, 166332], [57502, 140952], [21831, 57503], [19693, 57504],
+ [21551, 57505], [29719, 57506], [21894, 57507], [21929, 57508], 0,
+ [57510, 137431], [57511, 147514], [17746, 57512], [57513, 148533],
+ [26291, 57514], [57515, 135348], [22071, 57516], [26317, 57517],
+ [57518, 144010], [26276, 57519], 0, [22093, 57521], [22095, 57522],
+ [30961, 57523], [22257, 57524], [38791, 57525], [21502, 57526],
+ [22272, 57527], [22255, 57528], [22253, 57529], [57530, 166758],
+ [13859, 57531], [57532, 135759], [22342, 57533], [57534, 147877],
+ [27758, 57535], [28811, 57536], [22338, 57537], [14001, 57538],
+ [57539, 158846], [22502, 57540], [57541, 136214], [22531, 57542],
+ [57543, 136276], [57544, 148323], [22566, 57545], [57546, 150517], 0,
+ [22698, 57548], [13665, 57549], [22752, 57550], [22748, 57551],
+ [57552, 135740], [22779, 57553], [23551, 57554], [22339, 57555],
+ [57556, 172368], [57557, 148088], [37843, 57558], [13729, 57559],
+ [22815, 57560], [26790, 57561], [14019, 57562], [28249, 57563],
+ [57564, 136766], [23076, 57565], 0, [57567, 136850], [34053, 57568],
+ [22985, 57569], [57570, 134478], [57571, 158849], [57572, 159018],
+ [57573, 137180], [23001, 57574], [57575, 137211], [57576, 137138],
+ [57577, 159142], [28017, 57578], [57579, 137256], [57580, 136917],
+ [23033, 57581], [57582, 159301], [23211, 57583], [23139, 57584],
+ [14054, 57585], [57586, 149929], 0, [14088, 57588], [23190, 57589],
+ [29797, 57590], [23251, 57591], [57592, 159649], [57593, 140628],
+ [57595, 137489], [14130, 57596], [57597, 136888], [24195, 57598],
+ [21200, 57599], [23414, 57600], [25992, 57601], [23420, 57602],
+ [57603, 162318], [16388, 57604], [18525, 57605], [57606, 131588],
+ [23509, 57607], [57609, 137780], [57610, 154060], [57611, 132517],
+ [23539, 57612], [23453, 57613], [19728, 57614], [23557, 57615],
+ [57616, 138052], [23571, 57617], [29646, 57618], [23572, 57619],
+ [57620, 138405], [57621, 158504], [23625, 57622], [18653, 57623],
+ [23685, 57624], [23785, 57625], [23791, 57626], [23947, 57627],
+ [57628, 138745], [57629, 138807], [23824, 57630], [23832, 57631],
+ [23878, 57632], [57633, 138916], [23738, 57634], [24023, 57635],
+ [33532, 57636], [14381, 57637], [57638, 149761], [57639, 139337],
+ [57640, 139635], [33415, 57641], [14390, 57642], [15298, 57643],
+ [24110, 57644], [27274, 57645], 0, 57647, [57648, 148668], [57649, 134355],
+ [21414, 57650], [20151, 57651], 0, [21416, 57653], [57654, 137073],
+ [24073, 57655], 57656, [57657, 164994], [24313, 57658], [24315, 57659],
+ [14496, 57660], [24316, 57661], [26686, 57662], [37915, 57663],
+ [24333, 57664], [57665, 131521], [57666, 194708], [15070, 57667],
+ [57669, 135994], [24378, 57670], [57671, 157832], [57672, 140240],
+ [57674, 140401], [24419, 57675], [57677, 159342], [24434, 57678],
+ [37696, 57679], [57680, 166454], [24487, 57681], [23990, 57682],
+ [15711, 57683], [57684, 152144], [57685, 139114], [57686, 159992],
+ [57687, 140904], [37334, 57688], [57689, 131742], [57690, 166441],
+ [24625, 57691], [26245, 57692], [14691, 57694], [15815, 57695],
+ [13881, 57696], [22416, 57697], [57698, 141236], [31089, 57699],
+ [15936, 57700], [24734, 57701], 0, 0, [57704, 149890], [57705, 149903],
+ [57706, 162387], [29860, 57707], [20705, 57708], [23200, 57709],
+ [24932, 57710], [24898, 57712], [57713, 194726], [57714, 159442],
+ [24961, 57715], [20980, 57716], [57717, 132694], [24967, 57718],
+ [23466, 57719], [57720, 147383], [57721, 141407], [25043, 57722],
+ [57723, 166813], [57724, 170333], [25040, 57725], [14642, 57726],
+ [57727, 141696], [57728, 141505], [24611, 57729], [24924, 57730],
+ [25886, 57731], [25483, 57732], [57733, 131352], [25285, 57734],
+ [57735, 137072], [25301, 57736], [57737, 142861], [25452, 57738],
+ [57739, 149983], [14871, 57740], [25656, 57741], [25592, 57742],
+ [57743, 136078], [57744, 137212], [28554, 57746], [57747, 142902], 0,
+ [57750, 153373], [25825, 57751], [25829, 57752], [38011, 57753],
+ [14950, 57754], [25658, 57755], [14935, 57756], [25933, 57757],
+ [28438, 57758], [57759, 150056], [57760, 150051], [25989, 57761],
+ [25965, 57762], [25951, 57763], 0, [26037, 57765], [57766, 149824],
+ [19255, 57767], [26065, 57768], [16600, 57769], [57770, 137257], 57771,
+ [26083, 57772], [24543, 57773], [57774, 144384], [26136, 57775],
+ [57776, 143863], [57777, 143864], [26180, 57778], [57779, 143780],
+ [57780, 143781], [26187, 57781], [57782, 134773], [26215, 57783],
+ [57784, 152038], [26227, 57785], 0, [57788, 143921], [57789, 165364],
+ [57790, 143816], [57791, 152339], [30661, 57792], [57793, 141559],
+ [39332, 57794], [26370, 57795], [57796, 148380], [57797, 150049],
+ [27130, 57799], [57800, 145346], 0, [26471, 57802], [26466, 57803],
+ [57804, 147917], [57805, 168173], [26583, 57806], [17641, 57807],
+ [26658, 57808], [28240, 57809], [37436, 57810], [26625, 57811],
+ [57812, 144358], [57813, 159136], [26717, 57814], [57815, 144495],
+ [27105, 57816], [27147, 57817], [57818, 166623], [26995, 57819],
+ [26819, 57820], [57821, 144845], [26881, 57822], [26880, 57823],
+ [14849, 57825], [57826, 144956], [15232, 57827], [26540, 57828],
+ [26977, 57829], [57830, 166474], [17148, 57831], [26934, 57832],
+ [27032, 57833], [15265, 57834], [57835, 132041], [33635, 57836],
+ [20624, 57837], [27129, 57838], [57839, 144985], [57840, 139562],
+ [27205, 57841], [57842, 145155], [27293, 57843], [15347, 57844],
+ [26545, 57845], [27336, 57846], [57847, 168348], [15373, 57848],
+ [27421, 57849], [57850, 133411], [24798, 57851, 60308], [27445, 57852],
+ [27508, 57853], [57854, 141261], [28341, 57855], [57856, 146139], 0,
+ [57858, 137560], [14144, 57859], [21537, 57860], [57861, 146266],
+ [27617, 57862], [57863, 147196], [27612, 57864], [27703, 57865],
+ [57866, 140427], [57867, 149745], [57868, 158545], [27738, 57869],
+ [33318, 57870], [27769, 57871], [57872, 146876], [17605, 57873],
+ [57874, 146877], [57875, 147876], [57876, 149772], [57877, 149760],
+ [57878, 146633], [14053, 57879], [15595, 57880], [57881, 134450],
+ [39811, 57882], [57883, 143865], [57884, 140433], [32655, 57885],
+ [26679, 57886], [57887, 159013], [57888, 159137], [57889, 159211],
+ [28054, 57890], [27996, 57891], [28284, 57892], [28420, 57893],
+ [57894, 149887], [57895, 147589], [57896, 159346], [34099, 57897],
+ [57898, 159604], [20935, 57899], 0, 0, [33838, 57902], [57903, 166689], 0,
+ [57905, 146991], [29779, 57906], [57907, 147330], [31180, 57908],
+ [28239, 57909], [23185, 57910], [57911, 143435], [28664, 57912],
+ [14093, 57913], [28573, 57914], [57915, 146992], [28410, 57916],
+ [57917, 136343], [57918, 147517], [17749, 57919], [37872, 57920],
+ [28484, 57921], [28508, 57922], [15694, 57923], [28532, 57924],
+ [57925, 168304], [15675, 57926], [28575, 57927], [57928, 147780],
+ [28627, 57929], [57930, 147601], [57931, 147797], [57932, 147513],
+ [57933, 147440], [57934, 147380], [57935, 147775], [20959, 57936],
+ [57937, 147798], [57938, 147799], [57939, 147776], [57940, 156125],
+ [28747, 57941], [28798, 57942], [28839, 57943], 0, [28876, 57945],
+ [28885, 57946], [28886, 57947], [28895, 57948], [16644, 57949],
+ [15848, 57950], [29108, 57951], [29078, 57952], [57953, 148087],
+ [28971, 57954], [28997, 57955], [23176, 57956], [29002, 57957], 0,
+ [57960, 148325], [29007, 57961], [37730, 57962], [57963, 148161],
+ [28972, 57964], [57965, 148570], [57966, 150055], [57967, 150050],
+ [29114, 57968], [57969, 166888], [28861, 57970], [29198, 57971],
+ [37954, 57972], [29205, 57973], [22801, 57974], [37955, 57975],
+ [29220, 57976], [37697, 57977], [57978, 153093], [29230, 57979],
+ [29248, 57980], [57981, 149876], [26813, 57982], [29269, 57983],
+ [29271, 57984], [15957, 57985], [57986, 143428], [26637, 57987],
+ [28477, 57988], [29314, 57989], 0, [29483, 57991], [57992, 149539],
+ [57993, 165931], [18669, 57994], [57995, 165892], [29480, 57996],
+ [29486, 57997], [29647, 57998], [29610, 57999], [58000, 134202],
+ [58001, 158254], [29641, 58002], [29769, 58003], [58004, 147938],
+ [58005, 136935], [58006, 150052], [26147, 58007], [14021, 58008],
+ [58009, 149943], [58010, 149901], [58011, 150011], [29687, 58012],
+ [29717, 58013], [26883, 58014], [58015, 150054], [29753, 58016],
+ [16087, 58018], 0, [58020, 141485], [29792, 58021], [58022, 167602],
+ [29767, 58023], [29668, 58024], [29814, 58025], [33721, 58026],
+ [29804, 58027], [29812, 58029], [37873, 58030], [27180, 58031],
+ [29826, 58032], [18771, 58033], [58034, 150156], [58035, 147807],
+ [58036, 150137], [58037, 166799], [23366, 58038], [58039, 166915],
+ [58040, 137374], [29896, 58041], [58042, 137608], [29966, 58043],
+ [29982, 58045], [58046, 167641], [58047, 137803], [23511, 58048],
+ [58049, 167596], [37765, 58050], [30029, 58051], [30026, 58052],
+ [30055, 58053], [30062, 58054], [58055, 151426], [16132, 58056],
+ [58057, 150803], [30094, 58058], [29789, 58059], [30110, 58060],
+ [30132, 58061], [30210, 58062], [30252, 58063], [30289, 58064],
+ [30287, 58065], [30319, 58066], 58067, [58068, 156661], [30352, 58069],
+ [33263, 58070], [14328, 58071], [58072, 157969], [58073, 157966],
+ [30369, 58074], [30373, 58075], [30391, 58076], [30412, 58077],
+ [58078, 159647], [33890, 58079], [58080, 151709], [58081, 151933],
+ [58082, 138780], [30494, 58083], [30502, 58084], [30528, 58085],
+ [25775, 58086], [58087, 152096], [30552, 58088], [58089, 144044],
+ [30639, 58090], [58091, 166244], [58092, 166248], [58093, 136897],
+ [30708, 58094], 0, [26826, 58098], [30895, 58099], [30919, 58100],
+ [30931, 58101], [38565, 58102], [31022, 58103], [58104, 153056],
+ [30935, 58105], [31028, 58106], [30897, 58107], [58108, 161292],
+ [36792, 58109], [34948, 58110], [58113, 140828], [31110, 58114],
+ [35072, 58115], [26882, 58116], [31104, 58117], [58118, 153687],
+ [31133, 58119], [58120, 162617], [31036, 58121], [31145, 58122],
+ [28202, 58123], [58124, 160038], [16040, 58125], [31174, 58126],
+ [58127, 168205], [31188, 58128], 0, [21797, 62526], 0, [62528, 134210],
+ [62529, 134421], [62530, 151851], [21904, 62531], [62532, 142534],
+ [14828, 62533], [62534, 131905], [36422, 62535], [62536, 150968],
+ [62537, 169189], 0, [62539, 164030], [30586, 62540], [62541, 142392],
+ [14900, 62542], [18389, 62543], [62544, 164189], [62545, 158194],
+ [62546, 151018], [25821, 62547], [62548, 134524], [62549, 135092],
+ [62550, 134357], 0, [25741, 62552], [36478, 62553], [62554, 134806], 0,
+ [62556, 135012], [62557, 142505], [62558, 164438], [62559, 148691], 0,
+ [62561, 134470], [62562, 170573], [62563, 164073], [18420, 62564],
+ [62565, 151207], [62566, 142530], [39602, 62567], [14951, 62568],
+ [62569, 169460], [16365, 62570], [13574, 62571], [62572, 152263],
+ [62573, 169940], 0, [62575, 142660], [40302, 62576], [38933, 62577], 0,
+ [17369, 62579], 0, [25780, 62581], [21731, 62582], 0, [62584, 142282], 0,
+ [14843, 62586], 0, [62588, 157402], [62589, 157462], [62590, 162208],
+ [25834, 62591], [62592, 151634], [62593, 134211], [36456, 62594], 0,
+ [62596, 166732], [62597, 132913], 0, [18443, 62599], [62600, 131497],
+ [16378, 62601], [22643, 62602], [62603, 142733], 0, [62605, 148936],
+ [62606, 132348], [62607, 155799], [62608, 134988], 0, [21881, 62610], 0,
+ [17338, 62612], 0, [19124, 62614], [62615, 141926], [62616, 135325],
+ [33194, 62617], [39157, 62618], [62619, 134556], [25465, 62620],
+ [14846, 62621], [62622, 141173], [36288, 62623], [22177, 62624],
+ [25724, 62625], [15939, 62626], 0, [62628, 173569], [62629, 134665],
+ [62630, 142031], 0, 0, [62633, 135368], [62634, 145858], [14738, 62635],
+ [14854, 62636], [62637, 164507], [13688, 62638], [62639, 155209],
+ [62640, 139463], 0, 0, [62643, 142514], [62644, 169760], [13500, 62645],
+ [27709, 62646], [62647, 151099], 0, 0, [62650, 161140], [62651, 142987],
+ [62652, 139784], [62653, 173659], [62654, 167117], [62655, 134778],
+ [62656, 134196], [62683, 161337], [62684, 142286], [62687, 142417],
+ [14872, 62689], [62691, 135367], [62693, 173618], [62695, 167122],
+ [62696, 167321], [62697, 167114], [38314, 62698], 0, [62706, 161630],
+ [28992, 62708], 0, [20822, 62385], 0, [20616, 62487], 0, [13459, 62489],
+ [20870, 62491], [24130, 63037], [20997, 62495], [21031, 62436],
+ [21113, 62497], 0, [13651, 62504], [21442, 62505], [21343, 62715], 0,
+ [21823, 62520], 0, [21976, 59986], [13789, 62722], [22049, 63067], 0,
+ [22100, 60044], [60148, 135291], 0, [60153, 135379], 0, [61095, 135934], 0,
+ 0, [14265, 60104], [23745, 61099], [23829, 63066], [23894, 63030],
+ [14392, 63036], [20097, 62477], [24253, 63038], [14612, 63042],
+ [25017, 63050], [25232, 63054], [25368, 63056], [25690, 63063],
+ [25745, 62381], [33133, 62709], [33156, 59922], [33171, 59924],
+ [26624, 63080], [15292, 63093], [29327, 60517], [29389, 59781], 0,
+ [29497, 59785], [30018, 59811], [30172, 59817], [16320, 59818],
+ [60278, 151205], [16343, 59820], 0, 30336, [30348, 59824, 151388],
+ [16552, 59845], [30777, 59846], [16643, 59855], [31377, 59863],
+ [31771, 59876], [31981, 59884], [32659, 62658], [32686, 59892], 0,
+ [33535, 59936], [22623, 59981], [34482, 59960], 0, [34699, 59963],
+ [35143, 59969], 0, [35369, 59972], 0, [36465, 59988], [60484, 164233],
+ [36528, 59990], 0, [37214, 62443], [37260, 62441], [39182, 60051],
+ [39196, 60054], 0, 0, [39809, 60066], [40384, 60080], [40339, 60078],
+ [40620, 60085], [19857, 60540], 0, 37818, [40571, 60084], [28809, 63148],
+ [29512, 59788], 0, [31129, 59858], [36791, 59997], 0, [39234, 60056],
+ {s: 193}, 8364, {s: 4}, [12443, 63518], [12444, 63519], [11904, 63520],
+ {f: 5, c: 62211}, [62216, 131340], 62217, [62218, 131281], [62219, 131277],
+ {f: 2, c: 62220}, [62222, 131275], [62223, 139240], 62224, [62225, 131274],
+ {f: 4, c: 62226}, [62230, 131342], {f: 2, c: 62231}, {f: 2, c: 62776},
+ [62778, 138177], [62779, 194680], [12205, 38737, 62780], [62781, 131206],
+ [20059, 62782], [20155, 62783], [13630, 62784], [23587, 62785],
+ [24401, 62786], [24516, 62787], [14586, 62788], [25164, 62789],
+ [25909, 62790], [27514, 62791], [27701, 62792], [27706, 62793],
+ [28780, 62794], [29227, 62795], [20012, 62796], [29357, 62797],
+ [62798, 149737], [32594, 62799], [31035, 62800], [31993, 62801],
+ [32595, 62802], [62803, 156266], [13505, 62804], [62806, 156491],
+ [32770, 62807], [32896, 62808], [62809, 157202], [62810, 158033],
+ [21341, 62811], [34916, 62812], [35265, 62813], [62814, 161970],
+ [35744, 62815], [36125, 62816], [38021, 62817], [38264, 62818],
+ [38271, 62819], [38376, 62820], [62821, 167439], [38886, 62822],
+ [39029, 62823], [39118, 62824], [39134, 62825], [39267, 62826],
+ [62827, 170000], [40060, 62828], [40479, 62829], [40644, 62830],
+ [27503, 62831], [62832, 63751], [20023, 62833], [62834, 131207],
+ [38429, 62835], [25143, 62836], [38050, 62837], [11908, 63521],
+ [11910, 63522], [11911, 63523], [11912, 63524], [11914, 63525],
+ [11916, 63526], [11917, 63527], [11925, 63528], [11932, 63529],
+ [11941, 63531], [11943, 63532], [11946, 63533], [11948, 63534],
+ [11950, 63535], [11958, 63536], [11964, 63537], [11966, 63538],
+ [11978, 63540], [11980, 63541], [11981, 63542], [11983, 63543],
+ [11990, 63544], [11991, 63545], [11998, 63546], [62368, 172969],
+ [62369, 135493], [25866, 62371], [20029, 62374], [28381, 62375],
+ [40270, 62376], [37343, 62377], [62380, 161589], [20250, 62382],
+ [20264, 62383], [20392, 62384], [20852, 62386], [20892, 62387],
+ [20964, 62388], [21153, 62389], [21160, 62390], [21307, 62391],
+ [21326, 62392], [21457, 62393], [21464, 62394], [22242, 62395],
+ [22768, 62396], [22788, 62397], [22791, 62398], [22834, 62399],
+ [22836, 62400], [23398, 62401], [23454, 62402], [23455, 62403],
+ [23706, 62404], [24198, 62405], [24635, 62406], [25993, 62407],
+ [26622, 62408], [26628, 62409], [26725, 62410], [27982, 62411],
+ [28860, 62412], [30005, 62413], [32420, 62414], [32428, 62415],
+ [32442, 62416], [32455, 62417], [32463, 62418], [32479, 62419],
+ [32518, 62420], [32567, 62421], [33402, 62422], [33487, 62423],
+ [33647, 62424], [35270, 62425], [35774, 62426], [35810, 62427],
+ [36710, 62428], [36711, 62429], [36718, 62430], [29713, 62431],
+ [31996, 62432], [32205, 62433], [26950, 62434], [31433, 62435],
+ [30904, 62442], [32956, 62444], [36107, 62446], [33014, 62447],
+ [62448, 133607], [32927, 62451], [40647, 62452], [19661, 62453],
+ [40393, 62454], [40460, 62455], [19518, 62456], [62457, 171510],
+ [62458, 159758], [40458, 62459], [62460, 172339], [13761, 62461],
+ [28314, 62463], [33342, 62464], [29977, 62465], [18705, 62467],
+ [39532, 62468], [39567, 62469], [40857, 62470], [31111, 62471],
+ [62472, 164972], [62473, 138698], [62474, 132560], [62475, 142054],
+ [20004, 62476], [20096, 62478], [20103, 62479], [20159, 62480],
+ [20203, 62481], [20279, 62482], [13388, 62483], [20413, 62484],
+ [15944, 62485], [20483, 62486], [13437, 62488], [13477, 62490],
+ [22789, 62492], [20955, 62493], [20988, 62494], [20105, 62496],
+ [21136, 62498], [21287, 62499], [13767, 62500], [21417, 62501],
+ [13649, 62502], [21424, 62503], [21539, 62506], [13677, 62507],
+ [13682, 62508], [13953, 62509], [21651, 62510], [21667, 62511],
+ [21684, 62512], [21689, 62513], [21712, 62514], [21743, 62515],
+ [21784, 62516], [21795, 62517], [21800, 62518], [13720, 62519],
+ [13733, 62521], [13759, 62522], [21975, 62523], [13765, 62524],
+ [62525, 163204], [16467, 62538], [62551, 135412], [62555, 134155],
+ [62574, 161992], [62580, 155813], [62583, 142668], [62585, 135287],
+ [62587, 135279], [62595, 139681], [62609, 134550], [16571, 62611],
+ [62631, 142537], [22098, 62641], [62642, 134961], [62657, 157724],
+ [62659, 135375], [62660, 141315], [62661, 141625], [13819, 62662],
+ [62663, 152035], [62664, 134796], [62665, 135053], [62666, 134826],
+ [16275, 62667], [62668, 134960], [62669, 134471], [62670, 135503],
+ [62671, 134732], [62673, 134827], [62674, 134057], [62675, 134472],
+ [62676, 135360], [62677, 135485], [16377, 62678], [62679, 140950],
+ [25650, 62680], [62681, 135085], [62682, 144372], [62685, 134526],
+ [62686, 134527], [62688, 142421], [62690, 134808], [62692, 134958],
+ [62694, 158544], [21708, 62699], [33476, 62700], [21945, 62701],
+ [62703, 171715], [39974, 62704], [39606, 62705], [62707, 142830],
+ [33004, 62710], [23580, 62711], [62712, 157042], [33076, 62713],
+ [14231, 62714], [62716, 164029], [37302, 62717], [62718, 134906],
+ [62719, 134671], [62720, 134775], [62721, 134907], [62723, 151019],
+ [13833, 62724], [62725, 134358], [22191, 62726], [62727, 141237],
+ [62728, 135369], [62729, 134672], [62730, 134776], [62731, 135288],
+ [62732, 135496], [62733, 164359], [62734, 136277], [62735, 134777],
+ [62736, 151120], [62737, 142756], [23124, 62738], [62739, 135197],
+ [62740, 135198], [62741, 135413], [62742, 135414], [22428, 62743],
+ [62744, 134673], [62745, 161428], [62746, 164557], [62747, 135093],
+ [62748, 134779], [62749, 151934], [14083, 62750], [62751, 135094],
+ [62752, 135552], [62753, 152280], [62754, 172733], [62755, 149978],
+ [62756, 137274], [62757, 147831], [62758, 164476], [22681, 62759],
+ [21096, 62760], [13850, 62761], [62762, 153405], [31666, 62763],
+ [23400, 62764], [18432, 62765], [19244, 62766], [40743, 62767],
+ [18919, 62768], [39967, 62769], [39821, 62770], [62771, 154484],
+ [62772, 143677], [22011, 62773], [13810, 62774], [22153, 62775],
+ [23870, 63028], [23880, 63029], [15868, 63031], [14351, 63032],
+ [23972, 63033], [23993, 63034], [14368, 63035], [24357, 63039],
+ [24451, 63040], [14600, 63041], [14655, 63043], [14669, 63044],
+ [24791, 63045], [24893, 63046], [23781, 63047], [14729, 63048],
+ [25015, 63049], [25039, 63051], [14776, 63052], [25132, 63053],
+ [25317, 63055], [14840, 63057], [22193, 63058], [14851, 63059],
+ [25570, 63060], [25595, 63061], [25607, 63062], [14923, 63064],
+ [25792, 63065], [40863, 63068], [14999, 63069], [25990, 63070],
+ [15037, 63071], [26111, 63072], [26195, 63073], [15090, 63074],
+ [26258, 63075], [15138, 63076], [26390, 63077], [15170, 63078],
+ [26532, 63079], [15192, 63081], [26698, 63082], [26756, 63083],
+ [15218, 63084], [15217, 63085], [15227, 63086], [26889, 63087],
+ [26947, 63088], [29276, 63089], [26980, 63090], [27039, 63091],
+ [27013, 63092], [27094, 63094], [15325, 63095], [27237, 63096],
+ [27252, 63097], [27249, 63098], [27266, 63099], [15340, 63100],
+ [27289, 63101], [15346, 63102], [27307, 63103], [27317, 63104],
+ [27348, 63105], [27382, 63106], [27521, 63107], [27585, 63108],
+ [27626, 63109], [27765, 63110], [27818, 63111], [15563, 63112],
+ [27906, 63113], [27910, 63114], [27942, 63115], [28033, 63116],
+ [15599, 63117], [28068, 63118], [28081, 63119], [28181, 63120],
+ [28184, 63121], [28201, 63122], [28294, 63123], [63124, 166336],
+ [28347, 63125], [28386, 63126], [28378, 63127], [40831, 63128],
+ [28392, 63129], [28393, 63130], [28452, 63131], [28468, 63132],
+ [15686, 63133], [63134, 147265], [28545, 63135], [28606, 63136],
+ [15722, 63137], [15733, 63138], [29111, 63139], [23705, 63140],
+ [15754, 63141], [28716, 63142], [15761, 63143], [28752, 63144],
+ [28756, 63145], [28783, 63146], [28799, 63147], [63149, 131877],
+ [17345, 63150], [13809, 63151], [63152, 134872], [13902, 58134],
+ [15789, 58172], [58173, 154725], [26237, 58183], [31860, 58188],
+ [29837, 58197], [32402, 58215], [17667, 58232], [58260, 151480],
+ [58270, 133901], [58277, 158474], [13438, 58311], [58317, 143087],
+ [58325, 146613], [58343, 159385], [34673, 58364], [25537, 58385],
+ [30583, 58387], [35210, 58390], [58406, 147343], [35660, 58415],
+ [58440, 150729], [18730, 58464], [58471, 172052], [58472, 165564],
+ [58473, 165121], [15088, 58490], [28815, 58511], [58529, 140922],
+ [58637, 158120], [58646, 148043], [26760, 58662], [58664, 139611],
+ [40802, 58702], [37830, 58793], [58802, 131967], [37734, 58888],
+ [37519, 58901], [34324, 58954], [58986, 173147], [16784, 59010],
+ [26511, 59045], [26654, 59048], [14435, 59051], [59077, 149996],
+ [15129, 59128], [33942, 59176], [59241, 149858], [14818, 59254],
+ [33920, 59259], [17262, 59328], [38769, 59402], [39323, 59427],
+ [18733, 59499], [28439, 59703], [59704, 160009], [28838, 59746],
+ [59752, 150095], [32357, 59753], [23855, 59755], [15859, 59756],
+ [59758, 150109], [59759, 137183], [32164, 59760], [33830, 59761],
+ [21637, 59762], [59763, 146170], [59765, 131604], [22398, 59766],
+ [59767, 133333], [59768, 132633], [16357, 59769], [59770, 139166],
+ [59771, 172726], [28675, 59772], [59773, 168283], [23920, 59774],
+ [29583, 59775], [59777, 166489], [59778, 168992], [20424, 59779],
+ [32743, 59780], [29456, 59782], [29496, 59784], [29505, 59787],
+ [16041, 59789], [29173, 59792], [59793, 149746], [29665, 59794],
+ [16074, 59796], [16081, 59798], [29721, 59801], [29726, 59802],
+ [29727, 59803], [16098, 59804], [16112, 59805], [16116, 59806],
+ [16122, 59807], [29907, 59808], [16142, 59809], [16211, 59810],
+ [30061, 59812], [30066, 59813], [30093, 59814], [16252, 59815],
+ [30152, 59816], [30285, 59819], [30324, 59821], [16348, 59822],
+ [30330, 59823], [29064, 59825], [22051, 59826], [35200, 59827],
+ [16413, 59829], [30531, 59830], [16441, 59831], [16453, 59833],
+ [13787, 59834], [30616, 59835], [16490, 59836], [16495, 59837],
+ [30654, 59839], [30667, 59840], [30744, 59842], [30748, 59844],
+ [30791, 59847], [30801, 59848], [30822, 59849], [33864, 59850],
+ [59851, 152885], [31027, 59852], [31026, 59854], [16649, 59856],
+ [31121, 59857], [31238, 59860], [16743, 59862], [16818, 59864],
+ [31420, 59865], [33401, 59866], [16836, 59867], [31439, 59868],
+ [31451, 59869], [16847, 59870], [31586, 59872], [31596, 59873],
+ [31611, 59874], [31762, 59875], [16992, 59877], [17018, 59878],
+ [31867, 59879], [31900, 59880], [17036, 59881], [31928, 59882],
+ [17044, 59883], [36755, 59885], [28864, 59886], [59887, 134351],
+ [32207, 59888], [32212, 59889], [32208, 59890], [32253, 59891],
+ [32692, 59893], [29343, 59894], [17303, 59895], [32800, 59896],
+ [32805, 59897], [32814, 59899], [32817, 59900], [32852, 59901],
+ [22452, 59903], [28832, 59904], [32951, 59905], [33001, 59906],
+ [17389, 59907], [33036, 59908], [33038, 59910], [33042, 59911],
+ [33044, 59913], [17409, 59914], [15161, 59915], [33110, 59916],
+ [33113, 59917], [33114, 59918], [17427, 59919], [33148, 59921],
+ [17445, 59923], [17453, 59925], [33189, 59926], [22511, 59927],
+ [33217, 59928], [33252, 59929], [33364, 59930], [17551, 59931],
+ [33398, 59933], [33482, 59934], [33496, 59935], [17584, 59937],
+ [33623, 59938], [38505, 59939], [33797, 59941], [28917, 59942],
+ [33892, 59943], [33928, 59945], [17668, 59946], [33982, 59947],
+ [34017, 59948], [34040, 59949], [34064, 59950], [34104, 59951],
+ [34130, 59952], [17723, 59953], [34159, 59954], [34160, 59955],
+ [34272, 59956], [17783, 59957], [34418, 59958], [34450, 59959],
+ [34543, 59961], [38469, 59962], [17926, 59964], [17943, 59965],
+ [34990, 59966], [35071, 59967], [35108, 59968], [35217, 59970],
+ [59971, 162151], [35384, 59973], [35476, 59974], [35508, 59975],
+ [35921, 59976], [36052, 59977], [36082, 59978], [36124, 59979],
+ [18328, 59980], [36291, 59982], [18413, 59983], [36410, 59985],
+ [22356, 59987], [22005, 59989], [18487, 59991], [36558, 59992],
+ [36578, 59993], [36580, 59994], [36589, 59995], [36594, 59996],
+ [36801, 59998], [36810, 59999], [36812, 60000], [36915, 60001],
+ [18605, 60003], [39136, 60004], [37395, 60005], [18718, 60006],
+ [37416, 60007], [37464, 60008], [37483, 60009], [37553, 60010],
+ [37550, 60011], [37567, 60012], [37603, 60013], [37611, 60014],
+ [37619, 60015], [37620, 60016], [37629, 60017], [37699, 60018],
+ [37764, 60019], [37805, 60020], [18757, 60021], [18769, 60022],
+ [37911, 60024], [37917, 60026], [37933, 60027], [37950, 60028],
+ [18794, 60029], [37972, 60030], [38009, 60031], [38189, 60032],
+ [38306, 60033], [18855, 60034], [38388, 60035], [38451, 60036],
+ [18917, 60037], [18980, 60039], [38720, 60040], [18997, 60041],
+ [38834, 60042], [38850, 60043], [19172, 60045], [39097, 60047],
+ [19225, 60048], [39153, 60049], [22596, 60050], [39193, 60052],
+ [39223, 60055], [39261, 60057], [39266, 60058], [19312, 60059],
+ [39365, 60060], [19357, 60061], [39484, 60062], [39695, 60063],
+ [39785, 60065], [39901, 60067], [39921, 60068], [39924, 60069],
+ [19565, 60070], [39968, 60071], [14191, 60072], [60073, 138178],
+ [40265, 60074], [40702, 60076], [22096, 60077], [40381, 60079],
+ [40444, 60081], [38134, 60082], [36790, 60083], [40625, 60086],
+ [40637, 60087], [40646, 60088], [38108, 60089], [40674, 60090],
+ [40689, 60091], [40696, 60092], [40772, 60094], [60095, 131220],
+ [60096, 131767], [60097, 132000], [38083, 60099], [60101, 132311],
+ [38081, 60103], [60105, 132565], [60106, 132629], [60107, 132726],
+ [60108, 136890], [22359, 60109], [29043, 60110], [60111, 133826],
+ [60112, 133837], [60113, 134079], [60115, 194619], [60116, 134091],
+ [21662, 60117], [60118, 134139], [60119, 134203], [60120, 134227],
+ [60121, 134245], [60122, 134268], [60124, 134285], [60126, 134325],
+ [60127, 134365], [60128, 134381], [60129, 134511], [60130, 134578],
+ [60131, 134600], [60135, 134660], [60136, 134670], [60137, 134871],
+ [60138, 135056], [60139, 134957], [60140, 134771], [60142, 135100],
+ [60144, 135260], [60145, 135247], [60146, 135286], [60149, 135304],
+ [60150, 135318], [13895, 60151], [60152, 135359], [60154, 135471],
+ [60155, 135483], [21348, 60156], [60158, 135907], [60159, 136053],
+ [60160, 135990], [60162, 136567], [60163, 136729], [60164, 137155],
+ [60165, 137159], [28859, 60167], [60168, 137261], [60169, 137578],
+ [60170, 137773], [60171, 137797], [60172, 138282], [60173, 138352],
+ [60174, 138412], [60175, 138952], [60177, 138965], [60178, 139029],
+ [29080, 60179], [60181, 139333], [27113, 60182], [14024, 60183],
+ [60184, 139900], [60185, 140247], [60186, 140282], [60187, 141098],
+ [60188, 141425], [60189, 141647], [60191, 141671], [60192, 141715],
+ [60193, 142037], [60195, 142056], [60197, 142094], [60199, 142143],
+ [60202, 142412], [60204, 142472], [60205, 142519], [60206, 154600],
+ [60207, 142600], [60208, 142610], [60209, 142775], [60210, 142741],
+ [60211, 142914], [60212, 143220], [60213, 143308], [60214, 143411],
+ [60215, 143462], [60216, 144159], [60217, 144350], [60222, 144743],
+ [60223, 144883], [60227, 144922], [60228, 145174], [22709, 60231],
+ [60234, 146087], [60237, 146961], [60238, 147129], [60243, 147737],
+ [60245, 148206], [60246, 148237], [60248, 148276], [60249, 148374],
+ [60258, 148484], [60259, 148694], [22408, 60260], [60261, 149108],
+ [60263, 149295], [60271, 149522], [60272, 149755], [60273, 150037],
+ [60275, 150208], [22885, 60277], [60279, 151430], [60282, 151596],
+ [22335, 60284], [60286, 152217], [60287, 152601], [60291, 152646],
+ [60292, 152686], [60296, 152895], [60298, 152926], [60300, 152930],
+ [60301, 152934], [60302, 153543], [60304, 153693], [60309, 153859],
+ [60312, 154286], [60313, 154505], [60314, 154630], [22433, 60316],
+ [29009, 60317], [60319, 155906], [60322, 156082], [60325, 156674],
+ [60326, 156746], [60330, 156804], [60334, 156808], [60336, 156946],
+ [60338, 157119], [60339, 157365], [22201, 60347], [60349, 157436],
+ [13848, 60355], [60357, 157593], [60358, 157806], [60360, 157790],
+ [60362, 157895], [60366, 157990], [60368, 158009], [60371, 158202],
+ [60373, 158253], [60378, 158260], [60379, 158555], [60383, 158621],
+ [60385, 158884], [60388, 159150], [60392, 159819], [60393, 160205],
+ [60395, 160384], [60396, 160389], [60399, 160395], [60401, 160486],
+ [38047, 60404], [60405, 160848], [14009, 60416], [60424, 161740],
+ [60425, 161880], [22230, 60426], [60435, 162269], [60441, 162301],
+ [60442, 162314], [60443, 162571], [60444, 163174], [60448, 163849],
+ [60459, 163875], [60463, 163912], [60466, 163971], [60479, 163984],
+ [60480, 164084], [60481, 164142], [60483, 164175], [60485, 164271],
+ [60486, 164378], [60487, 164614], [60488, 164655], [60489, 164746],
+ [60491, 164968], [60492, 165546], [25574, 60494], [60495, 166230],
+ [60498, 166328], [60500, 166375], [60502, 166376], [60503, 166726],
+ [60504, 166868], [60506, 166921], [60508, 167877], [60509, 168172],
+ [60511, 168208], [60512, 168252], [15863, 60513], [60514, 168286],
+ [60515, 150218], [36816, 60516], [60519, 169191], [60521, 169392],
+ [60522, 169400], [60523, 169778], [60524, 170193], [60525, 170313],
+ [60526, 170346], [60527, 170435], [60528, 170536], [60529, 170766],
+ [60530, 171354], [60531, 171419], [32415, 60532], [60533, 171768],
+ [60534, 171811], [19620, 60535], [38215, 60536], [60537, 172691],
+ [29090, 60538], [60539, 172799], [60542, 173515], [19868, 60543],
+ [60544, 134300], [36798, 60545], [36794, 60547], [60548, 140464],
+ [36793, 60549], [60550, 150163], [20202, 60555], [60557, 166700],
+ [36480, 60560], [60561, 137205], [60563, 166764], [60564, 166809],
+ [60566, 157359], [60568, 161365], [60570, 153141], [60571, 153942],
+ [20122, 60572], [60573, 155265], [60576, 134765], [60579, 147080],
+ [60580, 150686], [60583, 137206], [60584, 137339], [60587, 154698],
+ [60589, 152337], [15814, 60590], [60596, 155352], [19996, 60600],
+ [60601, 135146], [60602, 134473], [60603, 145082], [60638, 151880],
+ [21982, 60644], [34694, 60672], [60676, 135361], [60679, 149254],
+ [23440, 60680], [60682, 157843], [60684, 141044], [60685, 163119],
+ [60686, 147875], [60687, 163187], [60688, 159440], [60689, 160438],
+ [60691, 135641], [60693, 146684], [60694, 173737], [60695, 134828],
+ [60698, 138402], [60700, 151490], [60702, 135147], [60706, 142752],
+ [60710, 135148], [60711, 134666], [60714, 135149], [60717, 135559],
+ [19994, 60721], [19972, 60722], [23309, 60724], [13996, 60727],
+ [21373, 60729], [13989, 60730], [22682, 60732], [60733, 150382],
+ [22442, 60736], [60737, 154261], [60738, 133497], [60741, 140389],
+ [60746, 146686], [60747, 171824], [60749, 151465], [60750, 169374],
+ [60753, 146870], [60755, 157619], [60756, 145184], [60759, 147191],
+ [60760, 146988], [60785, 143578], [60789, 135849], [22439, 60790],
+ [60791, 149859], [60794, 159918], [60801, 137068], [60806, 160100],
+ [60809, 159010], [60810, 150242], [39963, 60837], [60838, 149822],
+ [15878, 60846], [60881, 159011], [60887, 132092], [60891, 146685],
+ [60893, 149785], [22394, 60904], [21722, 60912], [29050, 60928],
+ [60949, 150135], [60955, 166490], [60962, 194624], [60976, 137275],
+ [61000, 155993], [61014, 144373], [61019, 166850], [61024, 138566],
+ [61054, 159441], [13877, 61065], [61084, 166701], [21024, 61088],
+ [15384, 61089], [61090, 146631], [61091, 155351], [61092, 161366],
+ [61093, 152881], [61094, 137540], [61096, 170243], [61097, 159196],
+ [61098, 159917], [61100, 156077], [61101, 166415], [61102, 145015],
+ [61103, 131310], [61104, 157766], [61105, 151310], [17762, 61106],
+ [23327, 61107], [61108, 156492], [40784, 61109], [40614, 61110],
+ [61111, 156267], [20962, 57415], [21314, 57416], [26285, 57520],
+ [22620, 57547], [21843, 57566], [15749, 57594], [24928, 57608],
+ [18606, 57668], [38845, 57676], [57693, 137335], [24755, 57703],
+ [33828, 57711], [38932, 57748], [57749, 147596], [57764, 143486],
+ [57787, 138813], [15147, 57798], [15666, 57824], [57857, 132021],
+ [28801, 57944], [23708, 57959], [58017, 132547], [14128, 58028],
+ [58096, 136054], [58097, 150034], [58111, 166699], [58112, 155779],
+ [256, 62233], [193, 62234], [461, 62235], [192, 62236], [274, 62237],
+ [201, 62238], [282, 62239], [200, 62240], [332, 62241], [211, 62242],
+ [465, 62243], [210, 62244], 62245, [7870, 62246], 62247, [7872, 62248],
+ [202, 62249], [257, 62250], [225, 62251], [462, 62252], [224, 62253],
+ [593, 62254], [275, 62255], [233, 62256], [283, 62257], [232, 62258],
+ [299, 62259], [237, 62260], [464, 62261], [236, 62262], [333, 62263],
+ [243, 62264], [466, 62265], [242, 62266], [363, 62267], [250, 62268],
+ [468, 62269], [249, 62270], [470, 62271], [472, 62272], [474, 62273],
+ [476, 62274], [252, 62275], 62276, [7871, 62277], 62278, [7873, 62279],
+ [234, 62280], [609, 62281], [643, 63551], [592, 63552], [603, 63553],
+ [596, 63554], [629, 63555], [339, 63556], [248, 63557], [331, 63558],
+ [650, 63559], [618, 63560], {f: 2, c: 62282}, [11933, 63530],
+ [11974, 63539], [12003, 63547], 20539, 28158, [62841, 171123], 62842,
+ [15817, 62843], 34959, [62845, 147790], 28791, 23797, [19232, 62848],
+ [62849, 152013], [13657, 62850], [62851, 154928], 24866, [62853, 166450],
+ 36775, 37366, 29073, 26393, 29626, [62859, 144001], [62860, 172295],
+ [15499, 62861], [62862, 137600], [19216, 62863], 30948, 29698, 20910,
+ [62867, 165647], [16393, 62868], 27235, [62870, 172730], [16931, 62871],
+ 34319, 31274, [62875, 170311], [62876, 166634], 38741, 28749, 21284,
+ [62880, 139390], 37876, 30425, [62883, 166371], 62884, 30685, 20131, 20464,
+ 20668, 20015, 20247, 62891, 21556, 32139, 22674, 22736, [62896, 138678],
+ 24210, 24217, 24514, [62900, 141074], 25995, [62902, 144377], 26905, 27203,
+ [62905, 146531], 27903, 29184, [62909, 148741], 29580, [16091, 62911],
+ [62912, 150035], 23317, 29881, 35715, [62916, 154788], [62917, 153237],
+ 31379, 31724, 31939, 32364, 33528, 34199, 62924, 34960, 62926, 36537,
+ 62928, 36815, 34143, 39392, 37409, 62933, [62934, 167353], [62935, 136255],
+ [16497, 62936], [17058, 62937], 23066, 39016, 26475, [17014, 62944], 22333,
+ 34262, [62948, 149883], 33471, [62950, 160013], [19585, 62951],
+ [62952, 159092], 23931, [62954, 158485], [62955, 159678], {f: 2, c: 62956},
+ 23446, 62959, 32347],
+ 'Adobe-GB1': [{f: 95, c: 32}, {f: 3, c: 12288}, [183, 12539], 713, 711, 168,
+ 12291, 12293, 8212, 65374, 8214, [8230, 8943], {f: 2, c: 8216},
+ {f: 2, c: 8220}, {f: 2, c: 12308}, {f: 8, c: 12296}, {f: 2, c: 12310},
+ {f: 2, c: 12304}, 177, 215, 247, 8758, {f: 2, c: 8743}, 8721, 8719, 8746,
+ 8745, 8712, 8759, 8730, 8869, 8741, 8736, 8978, 8857, 8747, 8750, 8801,
+ 8780, 8776, 8765, 8733, 8800, {f: 2, c: 8814}, {f: 2, c: 8804}, 8734, 8757,
+ 8756, 9794, 9792, 176, {f: 2, c: 8242}, 8451, 65284, 164, {f: 2, c: 65504},
+ 8240, 167, 8470, 9734, 9733, 9675, 9679, 9678, 9671, 9670, 9633, 9632,
+ 9651, 9650, 8251, 8594, {f: 2, c: 8592}, 8595, 12307, {f: 20, c: 9352},
+ {f: 20, c: 9332}, {f: 10, c: 9312}, {f: 10, c: 12832}, {f: 12, c: 8544},
+ {f: 3, c: 65281}, 65509, {f: 89, c: 65285}, 65507, {f: 83, c: 12353},
+ {f: 86, c: 12449}, {f: 17, c: 913}, {f: 7, c: 931}, {f: 17, c: 945},
+ {f: 7, c: 963}, {f: 7, c: 59277}, {f: 2, c: 65077}, {f: 2, c: 65081},
+ {f: 2, c: 65087}, {f: 2, c: 65085}, {f: 4, c: 65089}, {f: 2, c: 59284},
+ {f: 2, c: 65083}, {f: 2, c: 65079}, 65073, 59286, {f: 2, c: 65075},
+ {f: 6, c: 1040}, 1025, {f: 32, c: 1046}, 1105, {f: 26, c: 1078}, 257, 225,
+ 462, 224, 275, 233, 283, 232, 299, 237, 464, 236, 333, 243, 466, 242, 363,
+ 250, 468, 249, 470, 472, 474, 476, 252, 234, 593, 7743, 324, 328, 505, 609,
+ {f: 37, c: 12549}, 0, {f: 76, c: 9472}, {s: 126}, 21834, 38463, 22467,
+ 25384, 21710, 21769, 21696, 30353, 30284, 34108, 30702, 33406, 30861,
+ 29233, 38552, 38797, 27688, 23433, 20474, 25353, 26263, 23736, 33018,
+ 26696, 32942, 26114, 30414, 20985, 25942, 29100, 32753, 34948, 20658,
+ 22885, 25034, 28595, 33453, 25420, 25170, 21485, 21543, 31494,
+ [12043, 20843], 30116, 24052, 25300, 36299, 38774, 25226, 32793, 22365,
+ 38712, 32610, 29240, [12137, 30333], 26575, 30334, 25670, 20336, 36133,
+ 25308, 31255, 26001, 29677, 25644, 25203, 33324, 39041, 26495, 29256,
+ 25198, 25292, 20276, 29923, 21322, 21150, 32458, 37030, 24110, 26758,
+ 27036, 33152, 32465, 26834, 30917, 34444, 38225, 20621, 35876, 33502,
+ 32990, 21253, 35090, 21093, 34180, 38649, 20445, 22561, 39281, 23453,
+ 25265, 25253, 26292, 35961, 40077, 29190, 26479, 30865, 24754, 21329,
+ 21271, 36744, 32972, 36125, 38049, 20493, 29384, 22791, 24811, 28953,
+ 34987, 22868, 33519, 26412, 31528, 23849, 32503, 29997, 27893, 36454,
+ 36856, 36924, [12240, 40763], [12112, 27604], 37145, 31508, 24444, 30887,
+ 34006, 34109, 27605, 27609, 27606, 24065, 24199, 30201, 38381, 25949,
+ 24330, 24517, 36767, 22721, 33218, 36991, 38491, 38829, 36793, 32534,
+ 36140, 25153, 20415, 21464, 21342, {f: 2, c: 36776}, 36779, 36941, 26631,
+ 24426, 33176, 34920, 40150, 24971, 21035, 30250, 24428, 25996, 28626,
+ 28392, 23486, 25672, 20853, 20912, 26564, 19993, 31177, 39292, 28851,
+ 30149, 24182, 29627, 33760, 25773, 25320, 38069, 27874, 21338, 21187,
+ 25615, 38082, 31636, 20271, 24091, 33334, 33046, 33162, 28196, 27850,
+ 39539, 25429, [12056, 21340], 21754, 34917, 22496, 19981, 24067, 27493,
+ 31807, 37096, 24598, 25830, 29468, 35009, 26448, 25165, 36130, 30572,
+ 36393, 37319, 24425, 33756, 34081, 39184, 21442, 34453, 27531, 24813,
+ 24808, 28799, 33485, 33329, 20179, 27815, 34255, 25805, 31961, 27133,
+ 26361, 33609, 21397, 31574, 20391, 20876, 27979, 23618, 36461, 25554,
+ 21449, 33580, 33590, 26597, 30900, 25661, 23519, 23700, 24046, 35815,
+ 25286, 26612, 35962, 25600, 25530, 34633, 39307, 35863, 32544, 38130,
+ 20135, 38416, 39076, 26124, 29462, 22330, 23581, 24120, 38271, 20607,
+ 32928, [12058, 21378], 25950, 30021, 21809, 20513, 36229, 25220, 38046,
+ 26397, 22066, 28526, 24034, 21557, 28818, 36710, 25199, 25764, 25507,
+ 24443, 28552, 37108, [12162, 33251], [12192, 36784], 23576, 26216, 24561,
+ 27785, 38472, 36225, 34924, 25745, 31216, 22478, 27225, 25104, 21576,
+ 20056, 31243, 24809, 28548, 35802, 25215, 36894, 39563, 31204, 21507,
+ 30196, 25345, 21273, 27744, 36831, 24347, 39536, 32827, 40831, 20360,
+ 23610, [12186, 36196], 32709, 26021, 28861, 20805, 20914, [12173, 34411],
+ 23815, 23456, 25277, 37228, 30068, 36364, 31264, 24833, 31609, 20167,
+ 32504, 30597, 19985, 33261, 21021, 20986, 27249, 21416, 36487, 38148,
+ 38607, 28353, 38500, 26970, 30784, 20648, 30679, 25616, 35302, 22788,
+ 25571, 24029, 31359, 26941, 20256, 33337, 21912, 20018, 30126, 31383,
+ 24162, 24202, 38383, 21019, 21561, 28810, 25462, 38180, 22402, 26149,
+ 26943, 37255, 21767, 28147, 32431, 34850, 25139, 32496, 30133, 33576,
+ 30913, 38604, 36766, 24904, 29943, 35789, 27492, 21050, 36176, 27425,
+ 32874, 33905, 22257, 21254, 20174, 19995, 20945, 31895, 37259, 31751,
+ 20419, 36479, 31713, 31388, 25703, 23828, 20652, 33030, 30209, 31929,
+ 28140, 32736, 26449, 23384, [12072, 23544], 30923, 25774, 25619, 25514,
+ 25387, 38169, 25645, 36798, 31572, 30249, 25171, [12068, 22823], 21574,
+ [12109, 27513], 20643, 25140, 24102, 27526, 20195, 36151, 34955, 24453,
+ 36910, 24608, 32829, 25285, 20025, 21333, 37112, 25528, 32966, 26086,
+ 27694, 20294, 24814, 28129, 35806, 24377, 34507, 24403, 25377, 20826,
+ 33633, 26723, [12049, 20992], 25443, 36424, 20498, 23707, 31095, 23548,
+ 21040, 31291, 24764, 36947, 30423, 24503, 24471, 30340, 36460, 28783,
+ 30331, 31561, 30634, 20979, 37011, 22564, 20302, 28404, 36842, 25932,
+ 31515, 29380, 28068, 32735, 23265, 25269, 24213, 22320, 33922, 31532,
+ 24093, 24351, 36882, 32532, 39072, 25474, 28359, 30872, 28857, 20856,
+ 38747, 22443, 30005, 20291, 30008, 24215, 24806, 22880, 28096, 27583,
+ 30857, 21500, 38613, 20939, 20993, 25481, 21514, 38035, 35843, 36300,
+ 29241, 30879, 34678, 36845, 35853, 21472, 19969, 30447, 21486, 38025,
+ 39030, [12237, 40718], 38189, 23450, 35746, 20002, 19996, 20908, 33891,
+ 25026, 21160, 26635, 20375, 24683, 20923, 27934, 20828, 25238,
+ [12099, 26007], 38497, [12182, 35910], 36887, 30168, 37117, 30563, 27602,
+ 29322, 29420, 35835, 22581, 30585, 36172, 26460, 38208, 32922, 24230,
+ 28193, 22930, 31471, 30701, 38203, 27573, 26029, 32526, 22534, 20817,
+ 38431, 23545, 22697, 21544, 36466, 25958, 39039, 22244, 38045, 30462,
+ 36929, 25479, 21702, 22810, 22842, 22427, 36530, 26421, 36346, 33333,
+ 21057, 24816, 22549, 34558, 23784, 40517, 20420, 39069, 35769, 23077,
+ 24694, 21380, 25212, 36943, 37122, 39295, 24681, [12157, 32780],
+ [12041, 20799], [12159, 32819], 23572, 39285, 27953, [12038, 20108], 36144,
+ 21457, 32602, 31567, 20240, 20047, 38400, 27861, 29648, 34281, 24070,
+ 30058, 32763, 27146, 30718, 38034, 32321, 20961, 28902, 21453, 36820,
+ 33539, 36137, 29359, 39277, 27867, 22346, 33459, [12101, 26041], 32938,
+ 25151, 38450, 22952, 20223, 35775, 32442, 25918, 33778, [12206, 38750],
+ 21857, 39134, 32933, 21290, 35837, 21536, 32954, 24223, 27832, 36153,
+ 33452, 37210, 21545, 27675, 20998, 32439, 22367, 28954, 27774, 31881,
+ 22859, 20221, 24575, 24868, 31914, 20016, 23553, 26539, 34562, 23792,
+ 38155, 39118, 30127, 28925, 36898, 20911, 32541, 35773, 22857, 20964,
+ 20315, 21542, 22827, 25975, 32932, 23413, 25206, 25282, 36752, 24133,
+ 27679, 31526, 20239, 20440, 26381, 28014, 28074, 31119, 34993, 24343,
+ 29995, 25242, 36741, 20463, 37340, 26023, 33071, 33105, 24220, 33104,
+ 36212, 21103, 35206, 36171, 22797, 20613, 20184, [12201, 38428],
+ [12119, 29238], 33145, 36127, 23500, 35747, 38468, 22919, 32538, 21648,
+ 22134, 22030, 35813, 25913, 27010, 38041, 30422, 28297, [12082, 24178],
+ [12130, 29976], 26438, 26577, 31487, 32925, 36214, 24863, 31174, 25954,
+ 36195, 20872, 21018, 38050, 32568, 32923, 32434, 23703, 28207, 26464,
+ 31705, 30347, [12220, 39640], 33167, 32660, 31957, 25630, 38224, 31295,
+ 21578, 21733, 27468, 25601, [12093, 25096], 40509, 33011, 30105, 21106,
+ [12208, 38761], 33883, 26684, 34532, 38401, 38548, 38124, 20010, 21508,
+ 32473, 26681, 36319, 32789, 26356, 24218, 32697, 22466, 32831, 26775,
+ [12079, 24037], 25915, 21151, 24685, 40858, 20379, 36524, 20844, 23467,
+ [12088, 24339], 24041, 27742, 25329, 36129, 20849, 38057, 21246, 27807,
+ 33503, 29399, 22434, 26500, 36141, 22815, 36764, 33735, 21653, 31629,
+ 20272, 27837, 23396, 22993, [12238, 40723], 21476, 34506, [12219, 39592],
+ [12181, 35895], 32929, 25925, 39038, 22266, 38599, 21038, [12128, 29916],
+ 21072, 23521, 25346, 35074, 20054, 25296, 24618, 26874, 20851, 23448,
+ 20896, 35266, 31649, 39302, 32592, 24815, 28748, 36143, 20809,
+ [12084, 24191], 36891, 29808, 35268, 22317, 30789, 24402, 40863, 38394,
+ 36712, [12225, 39740], 35809, 30328, 26690, 26588, 36330, 36149, 21053,
+ 36746, 28378, 26829, 38149, 37101, 22269, 26524, 35065, 36807, 21704,
+ 39608, 23401, 28023, 27686, 20133, 23475, 39559, 37219, 25000, 37039,
+ 38889, 21547, 28085, 23506, 20989, 21898, 32597, 32752, 25788, 25421,
+ 26097, 25022, 24717, 28938, 27735, 27721, 22831, 26477, 33322, 22741,
+ 22158, 35946, 27627, 37085, 22909, 32791, 21495, 28009, 21621, 21917,
+ 33655, 33743, 26680, [12146, 31166], 21644, 20309, 21512, 30418, 35977,
+ 38402, 27827, 28088, 36203, 35088, 40548, 36154, 22079, [12234, 40657],
+ 30165, 24456, 29408, 24680, 21756, 20136, 27178, 34913, 24658, 36720,
+ 21700, 28888, 34425, 40511, 27946, 23439, 24344, 32418, 21897, 20399,
+ 29492, 21564, 21402, 20505, 21518, 21628, 20046, 24573, 29786, 22774,
+ 33899, 32993, 34676, 29392, 31946, 28246, 24359, 34382, 21804, 25252,
+ 20114, 27818, 25143, 33457, 21719, 21326, 29502, 28369, 30011, 21010,
+ 21270, 35805, 27088, 24458, 24576, 28142, 22351, 27426, 29615, 26707,
+ 36824, 32531, 25442, 24739, 21796, 30186, 35938, 28949, 28067, 23462,
+ 24187, 33618, 24908, 40644, 30970, 34647, 31783, 30343, 20976, 24822,
+ 29004, 26179, 24140, 24653, 35854, 28784, 25381, 36745, 24509, 24674,
+ 34516, 22238, 27585, 24724, 24935, 21321, 24800, 26214, 36159, 31229,
+ 20250, 28905, 27719, 35763, 35826, 32472, 33636, 26127, 23130, 39746,
+ 27985, 28151, 35905, 27963, 20249, [12117, 28779], 33719, 25110, 24785,
+ 38669, 36135, 31096, 20987, 22334, 22522, 26426, 30072, 31293, 31215,
+ 31637, 32908, 39269, 36857, 28608, 35749, 40481, 23020, 32489, 32521,
+ 21513, 26497, 26840, 36753, 31821, 38598, 21450, 24613, 30142, 27762,
+ 21363, 23241, 32423, 25380, [12047, 20960], 33034, [12080, 24049], 34015,
+ 25216, 20864, 23395, 20238, 31085, 21058, 24760, 27982, 23492, 23490,
+ 35745, 35760, 26082, 24524, 38469, 22931, 32487, 32426, 22025, 26551,
+ 22841, 20339, 23478, 21152, 33626, 39050, 36158, 30002, 38078, 20551,
+ 31292, 20215, 26550, 39550, 23233, 27516, 30417, 22362, 23574, 31546,
+ 38388, 29006, 20860, 32937, 33392, 22904, 32516, 33575, 26816, 26604,
+ 30897, 30839, 25315, 25441, 31616, 20461, 21098, 20943, 33616, 27099,
+ 37492, 36341, 36145, 35265, 38190, 31661, 20214, 20581, 33328, 21073,
+ 39279, 28176, 28293, 28071, 24314, 20725, 23004, 23558, 27974, 27743,
+ 30086, 33931, 26728, 22870, 35762, 21280, 37233, 38477, 34121, 26898,
+ 30977, 28966, 33014, 20132, 37066, 27975, 39556, 23047, 22204, 25605,
+ 38128, 30699, 20389, 33050, 29409, [12179, 35282], 39290, 32564, 32478,
+ 21119, 25945, 37237, 36735, 36739, 21483, 31382, 25581, 25509, 30342,
+ 31224, 34903, 38454, 25130, 21163, 33410, 26708, 26480, 25463, 30571,
+ 31469, 27905, 32467, 35299, 22992, 25106, 34249, 33445, 30028, 20511,
+ 20171, 30117, 35819, 23626, [12081, 24062], 31563, [12100, 26020],
+ [12198, 37329], 20170, 27941, 35167, 32039, 38182, 20165, 35880, 36827,
+ 38771, 26187, 31105, 36817, 28908, 28024, 23613, 21170, 33606, 20834,
+ 33550, 30555, 26230, 40120, 20140, 24778, 31934, 31923, 32463, 20117,
+ 35686, 26223, 39048, 38745, 22659, 25964, 38236, 24452, 30153, 38742,
+ 31455, 31454, 20928, 28847, 31384, 25578, 31350, 32416, 29590,
+ [12210, 38893], 20037, 28792, 20061, 37202, 21417, 25937, 26087,
+ [12165, 33276], 33285, 21646, 23601, 30106, 38816, 25304, 29401, 30141,
+ 23621, 39545, 33738, 23616, 21632, 30697, 20030, 27822, 32858, 25298,
+ 25454, 24040, 20855, 36317, 36382, 38191, 20465, 21477, 24807, 28844,
+ 21095, 25424, 40515, 23071, 20518, 30519, 21367, 32482, 25733, 25899,
+ 25225, 25496, 20500, 29237, 35273, 20915, 35776, 32477, 22343, 33740,
+ 38055, 20891, 21531, 23803, 20426, 31459, 27994, 37089, 39567, 21888,
+ 21654, 21345, 21679, 24320, 25577, 26999, 20975, 24936, 21002, 22570,
+ 21208, 22350, 30733, 30475, 24247, 24951, 31968, 25179, 25239, 20130,
+ 28821, 32771, 25335, 28900, 38752, 22391, 33499, 26607, 26869, 30933,
+ 39063, 31185, 22771, 21683, 21487, 28212, 20811, 21051, 23458, 35838,
+ 32943, 21827, 22438, 24691, 22353, 21549, 31354, 24656, 23380, 25511,
+ 25248, [12061, 21475], 25187, 23495, 26543, 21741, 31391, 33510, 37239,
+ 24211, 35044, 22840, 22446, 25358, 36328, 33007, 22359, 31607, 20393,
+ 24555, 23485, 27454, 21281, 31568, 29378, 26694, 30719, 30518, 26103,
+ 20917, 20111, 30420, 23743, 31397, 33909, 22862, 39745, 20608, 39304,
+ 24871, 28291, 22372, 26118, 25414, 22256, 25324, 25193, 24275, 38420,
+ 22403, 25289, 21895, 34593, 33098, 36771, 21862, 33713, 26469, 36182,
+ 34013, 23146, 26639, 25318, 31726, 38417, 20848, 28572, 35888, 25597,
+ 35272, 25042, 32518, 28866, 28389, 29701, 27028, 29436, 24266, 37070,
+ 26391, 28010, 25438, 21171, 29282, [12156, 32769], 20332, 23013, 37226,
+ 28889, 28061, 21202, 20048, 38647, 38253, 34174, 30922, 32047, 20769,
+ 22418, 25794, 32907, 31867, 27882, 26865, 26974, 20919, 21400, 26792,
+ 29313, 40654, 31729, 29432, 31163, 28435, 29702, 26446, [12197, 37324],
+ 40100, 31036, 33673, 33620, 21519, 26647, 20029, 21385, 21169, 30782,
+ 21382, 21033, 20616, 20363, 20432, 30178, [12148, 31435], 31890, 27813,
+ [12202, 38582], [12050, 21147], 29827, 21737, 20457, 32852, 33714, 36830,
+ 38256, 24265, 24604, 28063, 24088, 25947, 33080, 38142, 24651, 28860,
+ 32451, 31918, 20937, 26753, 31921, 33391, 20004, 36742, 37327, 26238,
+ 20142, 35845, 25769, 32842, 20698, 30103, 29134, 23525, 36797, 28518,
+ 20102, 25730, 38243, 24278, 26009, 21015, 35010, 28872, 21155, 29454,
+ 29747, 26519, 30967, 38678, 20020, 37051, 40158, 28107, 20955, 36161,
+ 21533, 25294, 29618, 33777, 38646, 40836, 38083, 20278, 32666, 20940,
+ 28789, 38517, 23725, 39046, 21478, 20196, 28316, 29705, 27060, 30827,
+ 39311, 30041, 21016, 30244, 27969, 26611, 20845, 40857, 32843, 21657,
+ 31548, 31423, 38534, 22404, 25314, 38471, 27004, 23044, 25602, 31699,
+ 28431, 38475, 33446, 21346, 39045, 24208, 28809, 25523, 21348, 34383,
+ 40065, 40595, 30860, 38706, 36335, 36162, [12229, 40575], 28510, 31108,
+ 24405, 38470, 25134, 39540, 21525, 38109, 20387, 26053, 23653, 23649,
+ 32533, 34385, 27695, 24459, 29575, 28388, 32511, 23782, 25371, 23402,
+ 28390, 21365, 20081, 25504, 30053, 25249, 36718, 20262, 20177, 27814,
+ 32438, 35770, 33821, 34746, 32599, 36923, 38179, 31657, 39585, 35064,
+ 33853, 27931, 39558, 32476, 22920, [12231, 40635], 29595, 30721, 34434,
+ 39532, 39554, 22043, 21527, 22475, 20080, 40614, 21334, 36808, 33033,
+ 30610, 39314, 34542, 28385, 34067, 26364, 24930, 28459, 35881, 33426,
+ 33579, 30450, 27667, 24537, 33725, 29483, 33541, 38170, [12113, 27611],
+ [12141, 30683], 38086, 21359, 33538, 20882, 24125, 35980, 36152, 20040,
+ 29611, 26522, 26757, 37238, 38665, 29028, 27809, 30473, 23186, 38209,
+ 27599, 32654, 26151, 23504, 22969, 23194, 38376, 38391, 20204, 33804,
+ 33945, 27308, 30431, 38192, 29467, 26790, 23391, 30511, 37274, 38753,
+ 31964, 36855, 35868, 24357, [12150, 31859], 31192, 35269, 27852, 34588,
+ 23494, 24130, 26825, 30496, 32501, 20885, 20813, 21193, 23081, 32517,
+ [12207, 38754], 33495, 25551, 30596, 34256, 31186, 28218, 24217, 22937,
+ 34065, 28781, 27665, 25279, [12139, 30399], 25935, 24751, 38397, 26126,
+ 34719, 40483, 38125, 21517, 21629, 35884, {f: 2, c: 25720}, 34321, 27169,
+ 33180, 30952, 25705, 39764, 25273, 26411, 33707, 22696, 40664, 27819,
+ 28448, 23518, 38476, 35851, 29279, 26576, 25287, 29281, 20137, 22982,
+ 27597, 22675, 26286, 24149, 21215, 24917, [12106, 26408], [12140, 30446],
+ 30566, 29287, 31302, 25343, 21738, 21584, 38048, 37027, 23068, 32435,
+ 27670, 20035, 22902, 32784, 22856, 21335, 30007, 38590, 22218, 25376,
+ 33041, 24700, 38393, 28118, 21602, 39297, 20869, 23273, 33021, 22958,
+ 38675, 20522, 27877, 23612, 25311, 20320, 21311, 33147, 36870, 28346,
+ 34091, 25288, 24180, 30910, 25781, 25467, 24565, 23064, 37247, 40479,
+ 23615, 25423, 32834, 23421, 21870, 38218, 38221, 28037, 24744, 26592,
+ 29406, 20957, 23425, 25319, 27870, [12124, 29275], 25197, 38062, 32445,
+ 33043, 27987, 20892, 24324, 22900, 21162, 24594, [12069, 22899], 26262,
+ 34384, 30111, 25386, 25062, 31983, 35834, 21734, 27431, 40485, 27572,
+ 34261, 21589, 20598, 27812, 21866, 36276, 29228, 24085, 24597, 29750,
+ 25293, 25490, 29260, 24472, 28227, 27966, 25856, 28504, 30424, 30928,
+ 30460, 30036, 21028, 21467, 20051, 24222, 26049, 32810, 32982, 25243,
+ 21638, 21032, 28846, 34957, 36305, 27873, 21624, 32986, 22521, 35060,
+ 36180, 38506, 37197, 20329, 27803, 21943, 30406, 30768, 25256, 28921,
+ 28558, 24429, 34028, 26842, 30844, 31735, 33192, 26379, 40527, 25447,
+ 30896, 22383, 30738, 38713, 25209, 25259, 21128, 29749, 27607, 21860,
+ 33086, 30130, [12138, 30382], 21305, 30174, 20731, 23617, 35692, 31687,
+ 20559, [12122, 29255], 39575, 39128, 28418, 29922, 31080, 25735, 30629,
+ 25340, 39057, 36139, 21697, 32856, 20050, 22378, 33529, 33805, 24179,
+ 20973, 29942, 35780, 23631, 22369, 27900, 39047, 23110, 30772, 39748,
+ 36843, 31893, 21078, 25169, 38138, 20166, 33670, 33889, 33769, 33970,
+ 22484, 26420, 22275, 26222, 28006, 35889, 26333, 28689, 26399, 27450,
+ 26646, 25114, 22971, 19971, 20932, 28422, 26578, 27791, 20854, 26827,
+ 22855, 27495, 30054, 23822, 33040, 40784, 26071, 31048, 31041, 39569,
+ 36215, 23682, 20062, 20225, 21551, 22865, 30732, 22120, [12115, 27668],
+ 36804, 24323, 27773, 27875, 35755, 25488, 24688, 27965, 29301, 25190,
+ 38030, 38085, 21315, 36801, 31614, 20191, 35878, 20094, 40660, 38065,
+ 38067, 21069, 28508, 36963, 27973, 35892, 22545, 23884, [12107, 27424],
+ 27465, 26538, 21595, 33108, 32652, 22681, 34103, 24378, 25250, 27207,
+ 38201, 25970, 24708, 26725, 30631, 20052, 20392, 24039, 38808, 25772,
+ 32728, 23789, 20431, 31373, 20999, 33540, 19988, 24623, 31363, 38054,
+ 20405, 20146, 31206, 29748, 21220, 33465, 25810, 31165, 23517, 27777,
+ 38738, 36731, 27682, 20542, 21375, 28165, 25806, 26228, 27696, 24773,
+ 39031, 35831, 24198, 29756, 31351, 31179, 19992, 37041, 29699, 27714,
+ 22234, 37195, 27845, 36235, 21306, 34502, 26354, 36527, 23624, 39537,
+ 28192, 21462, 23094, 40843, 36259, 21435, 22280, 39079, 26435, 37275,
+ 27849, 20840, 30154, 25331, [12125, 29356], 21048, 21149, 32570, 28820,
+ 30264, 21364, 40522, 27063, 30830, 38592, 35033, 32676, 28982, 29123,
+ 20873, 26579, 29924, 22756, 25880, 22199, 35753, 39286, 25200, 32469,
+ 24825, 28909, 22764, 20161, [12040, 20154], 24525, 38887, 20219, 35748,
+ 20995, 22922, 32427, 25172, 20173, [12103, 26085], 25102, 33592, 33993,
+ 33635, 34701, 29076, 28342, 23481, 32466, 20887, 25545, 26580,
+ [12161, 32905], 33593, 34837, 20754, 23418, 22914, 36785, 20083, 27741,
+ [12042, 20837], 35109, 36719, 38446, 34122, 29790, 38160, 38384, 28070,
+ 33509, 24369, 25746, 27922, 33832, 33134, 40131, 22622, 36187, 19977,
+ 21441, 20254, 25955, 26705, 21971, 20007, 25620, 39578, 25195, 23234,
+ 29791, [12170, 33394], 28073, 26862, 20711, 33678, 30722, 26432, 21049,
+ 27801, 32433, 20667, 21861, 29022, 31579, 26194, 29642, 33515, 26441,
+ [12077, 23665], 21024, 29053, 34923, 38378, 38485, 25797, 36193, 33203,
+ 21892, 27733, 25159, 32558, 22674, 20260, 21830, 36175, 26188, 19978,
+ 23578, 35059, 26786, 25422, 31245, 28903, 33421, 21242, 38902, 23569,
+ 21736, 37045, 32461, 22882, 36170, 34503, [12166, 33292], 33293, 36198,
+ 25668, 23556, 24913, 28041, 31038, 35774, 30775, 30003, 21627, 20280,
+ [12189, 36523], 28145, 23072, 32453, 31070, 27784, 23457, 23158, 29978,
+ 32958, 24910, 28183, 22768, [12131, 29983], 29989, 29298, 21319, 32499,
+ 30465, 30427, 21097, 32988, 22307, 24072, 22833, 29422, 26045, 28287,
+ 35799, [12075, 23608], 34417, [12055, 21313], [12143, 30707], 25342, 26102,
+ 20160, [12215, 39135], 34432, 23454, 35782, 21490, [12142, 30690], 20351,
+ 23630, 39542, 22987, 24335, [12144, 31034], [12064, 22763], 19990, 26623,
+ 20107, 25325, 35475, 36893, 21183, 26159, 21980, 22124, 36866, 20181,
+ 20365, 37322, 39280, [12114, 27663], 24066, 24643, 23460, 35270, 35797,
+ 25910, [12095, 25163], [12216, 39318], 23432, 23551, 25480, 21806, 21463,
+ 30246, 20861, 34092, 26530, 26803, 27530, 25234, 36755, 21460, 33298,
+ 28113, 30095, 20070, 36174, 23408, 29087, 34223, 26257, 26329, 32626,
+ 34560, [12233, 40653], [12239, 40736], 23646, 26415, 36848, 26641, 26463,
+ 25101, 31446, 22661, 24246, 25968, 28465, 24661, 21047, 32781, 25684,
+ 34928, 29993, 24069, 26643, 25332, 38684, 21452, 29245, 35841,
+ [12116, 27700], 30561, 31246, 21550, 30636, 39034, 33308, 35828, 30805,
+ 26388, 28865, 26031, 25749, 22070, 24605, 31169, 21496, 19997, 27515,
+ 32902, 23546, 21987, 22235, 20282, 20284, 39282, 24051, 26494, 32824,
+ 24578, 39042, 36865, 23435, 35772, 35829, 25628, 33368, 25822, 22013,
+ 33487, 37221, 20439, 32032, 36895, 31903, 20723, 22609, 28335, 23487,
+ 35785, 32899, 37240, 33948, 31639, 34429, 38539, 38543, 32485, 39635,
+ 30862, 23681, 31319, 36930, 38567, 31071, 23385, 25439, 31499, 34001,
+ 26797, 21766, 32553, 29712, 32034, 38145, 25152, 22604, 20182, 23427,
+ 22905, 22612, 29549, 25374, 36427, 36367, 32974, 33492, 25260, 21488,
+ 27888, 37214, 22826, 24577, 27760, 22349, 25674, 36138, 30251, 28393,
+ 22363, 27264, 30192, 28525, 35885, 35848, 22374, 27631, 34962, 30899,
+ 25506, 21497, 28845, 27748, 22616, 25642, 22530, 26848, 33179, 21776,
+ 31958, 20504, 36538, 28108, 36255, 28907, 25487, 28059, 28372, 32486,
+ 33796, 26691, 36867, 28120, 38518, 35752, 22871, 29305, 34276, 33150,
+ 30140, 35466, 26799, 21076, 36386, 38161, 25552, 39064, 36420, 21884,
+ 20307, 26367, 22159, 24789, 28053, 21059, 23625, 22825, 28155, 22635,
+ [12133, 30000], 29980, 24684, 33300, 33094, 25361, 26465, 36834, 30522,
+ 36339, 36148, 38081, 24086, 21381, 21548, 28867, 27712, 24311, 20572,
+ 20141, 24237, 25402, 33351, 36890, 26704, 37230, 30643, 21516, 38108,
+ 24420, 31461, 26742, 25413, 31570, 32479, 30171, 20599, 25237, 22836,
+ 36879, 20984, 31171, 31361, 22270, 24466, 36884, 28034, 23648,
+ [12063, 22303], 21520, 20820, 28237, 22242, 25512, 39059, 33151, 34581,
+ 35114, 36864, 21534, 23663, 33216, 25302, 25176, 33073, 40501, 38464,
+ 39534, 39548, 26925, 22949, 25299, 21822, 25366, 21703, 34521, 27964,
+ 23043, [12129, 29926], 34972, 27498, 22806, 35916, 24367, 28286, 29609,
+ 39037, 20024, 28919, 23436, 30871, 25405, 26202, 30358, 24779, 23451,
+ 23113, 19975, 33109, 27754, 29579, 20129, 26505, [12153, 32593], 24448,
+ 26106, 26395, 24536, 22916, 23041, 24013, 24494, 21361, 38886, 36829,
+ 26693, 22260, 21807, 24799, 20026, 28493, 32500, 33479, 33806, 22996,
+ 20255, 20266, 23614, 32428, 26410, 34074, 21619, 30031, 32963, 21890,
+ 39759, 20301, 28205, 35859, 23561, 24944, 21355, 30239, 28201, 34442,
+ [12098, 25991], 38395, 32441, 21563, 31283, 32010, 38382, 21985, 32705,
+ 29934, 25373, 34583, 28065, 31389, 25105, 26017, 21351, 25569, 27779,
+ 24043, 21596, 38056, 20044, 27745, 35820, 23627, [12102, 26080], 33436,
+ 26791, 21566, 21556, [12111, 27595], 27494, 20116, 25410, 21320, 33310,
+ 20237, 20398, 22366, 25098, 38654, 26212, 29289, 21247, 21153, 24735,
+ 35823, 26132, 29081, 26512, 35199, 30802, 30717, 26224, 22075, 21560,
+ 38177, 29306, 31232, 24687, 24076, 24713, 33181, [12067, 22805], 24796,
+ 29060, 28911, 28330, 27728, 29312, 27268, 34989, 24109, 20064, 23219,
+ 21916, 38115, 27927, 31995, 38553, 25103, 32454, 30606, 34430, 21283,
+ 38686, 36758, 26247, 23777, 20384, 29421, 19979, 21414, 22799, 21523,
+ 25472, 38184, 20808, 20185, 40092, 32420, 21688, 36132, 34900, 33335,
+ 38386, 28046, 24358, 23244, 26174, 38505, 29616, 29486, 21439, 33146,
+ 39301, 32673, 23466, 38519, 38480, 32447, 30456, 21410, 38262,
+ [12217, 39321], 31665, 35140, 28248, 20065, 32724, 31077, 35814, 24819,
+ 21709, 20139, 39033, 24055, 27233, 20687, 21521, 35937, 33831, 30813,
+ 38660, 21066, 21742, 22179, 38144, 28040, 23477, 28102, 26195,
+ [12073, 23567], 23389, 26657, 32918, 21880, 31505, 25928, 26964, 20123,
+ 27463, 34638, 38795, 21327, 25375, 25658, 37034, 26012, 32961, 35856,
+ 20889, 26800, 21368, 34809, 25032, 27844, 27899, 35874, 23633, 34218,
+ 33455, 38156, 27427, [12191, 36763], 26032, 24571, [12092, 24515], 20449,
+ 34885, 26143, 33125, 29481, 24826, 20852, 21009, 22411, 24418, 37026,
+ [12175, 34892], 37266, 24184, 26447, 24615, 22995, 20804, 20982, 33016,
+ 21256, 27769, 38596, 29066, 20241, 20462, 32670, 26429, 21957, 38152,
+ 31168, 34966, 32483, 22687, 25100, 38656, 34394, 22040, 39035, 24464,
+ 35768, 33988, 37207, 21465, 26093, 24207, 30044, 24676, 32110, 23167,
+ 32490, 32493, 36713, 21927, 23459, 24748, 26059, [12126, 29572], 36873,
+ 30307, 30505, 32474, 38772, 34203, 23398, [12147, 31348], 38634,
+ [12174, 34880], 21195, 29071, 24490, 26092, 35810, 23547, 39535, 24033,
+ 27529, 27739, 35757, 35759, 36874, 36805, 21387, 25276, 40486, 40493,
+ 21568, 20011, 33469, [12123, 29273], 34460, 23830, 34905, 28079, 38597,
+ 21713, 20122, 35766, 28937, 21693, 38409, 28895, 28153, 30416, 20005,
+ 30740, 34578, 23721, 24310, [12180, 35328], 39068, 38414, 28814, 27839,
+ 22852, 25513, 30524, 34893, 28436, 33395, 22576, 29141, 21388, 30746,
+ 38593, 21761, 24422, 28976, 23476, 35866, 39564, 27523, 22830, 40495,
+ 31207, 26472, 25196, 20335, 30113, [12154, 32650], 27915, 38451, 27687,
+ 20208, 30162, 20859, 26679, 28478, 36992, 33136, 22934, 29814, 25671,
+ 23591, 36965, 31377, 35875, 23002, 21676, 33280, 33647, 35201, 32768,
+ 26928, 22094, 32822, 29239, 37326, 20918, 20063, 39029, 25494, 19994,
+ 21494, 26355, 33099, 22812, 28082, [12032, 19968], 22777, 21307, 25558,
+ 38129, 20381, 20234, [12176, 34915], 39056, 22839, 36951, 31227, 20202,
+ 33008, 30097, 27778, 23452, 23016, 24413, 26885, 34433, 20506, 24050,
+ [12036, 20057], 30691, 20197, 33402, 25233, 26131, [12194, 37009], 23673,
+ 20159, 24441, 33222, 36920, 32900, 30123, 20134, 35028, 24847, 27589,
+ 24518, 20041, 30410, 28322, 35811, 35758, 35850, 35793, 24322, 32764,
+ 32716, 32462, 33589, 33643, 22240, 27575, [12211, 38899], 38452, 23035,
+ 21535, 38134, 28139, 23493, 39278, 23609, 24341, 38544, 21360, 33521,
+ 27185, 23156, 40560, 24212, 32552, 33721, {f: 2, c: 33828}, 33639, 34631,
+ 36814, 36194, 30408, 24433, 39062, 30828, 26144, 21727, 25317, 20323,
+ 33219, 30152, 24248, 38605, 36362, 34553, 21647, 27891, 28044, 27704,
+ 24703, 21191, [12132, 29992], 24189, 20248, 24736, 24551, 23588, 30001,
+ 37038, 38080, 29369, 27833, 28216, [12195, 37193], 26377, 21451, 21491,
+ 20305, 37321, 35825, [12060, 21448], 24188, 36802, 28132, 20110, 30402,
+ 27014, 34398, 24858, 33286, 20313, 20446, 36926, 40060, 24841, 28189,
+ 28180, 38533, 20104, 23089, [12204, 38632], 19982, 23679, 31161, 23431,
+ 35821, [12155, 32701], [12127, 29577], 22495, 33419, 37057, 21505, 36935,
+ 21947, 23786, 24481, 24840, 27442, 29425, 32946, 35465, 28020, 23507,
+ 35029, 39044, 35947, 39533, 40499, 28170, 20900, 20803, 22435, 34945,
+ 21407, 25588, 36757, 22253, 21592, 22278, 29503, 28304, 32536, 36828,
+ 33489, 24895, 24616, 38498, [12104, 26352], 32422, 36234, 36291, 38053,
+ 23731, 31908, [12105, 26376], 24742, 38405, 32792, 20113, 37095, 21248,
+ 38504, 20801, 36816, 34164, 37213, 26197, 38901, 23381, 21277, 30776,
+ 26434, 26685, 21705, 28798, 23472, 36733, 20877, 22312, 21681, 25874,
+ 26242, 36190, 36163, 33039, 33900, 36973, 31967, 20991, 34299, 26531,
+ 26089, 28577, 34468, 36481, 22122, 36896, 30338, 28790, 29157, 36131,
+ 25321, 21017, 27901, 36156, 24590, 22686, 24974, 26366, 36192, 25166,
+ 21939, 28195, 26413, 36711, 38113, 38392, 30504, 26629, 27048, 21643,
+ 20045, 28856, 35784, 25688, 25995, 23429, 31364, 20538, 23528, 30651,
+ 27617, 35449, 31896, 27838, 30415, 26025, 36759, 23853, 23637, 34360,
+ 26632, 21344, 25112, 31449, 28251, 32509, 27167, 31456, 24432, 28467,
+ 24352, 25484, 28072, 26454, 19976, 24080, 36134, 20183, 32960, 30260,
+ 38556, 25307, 26157, 25214, 27836, 36213, 29031, 32617, 20806, 32903,
+ 21484, 36974, 25240, 21746, 34544, 36761, 32773, 38167, 34071, 36825,
+ 27993, 29645, 26015, 30495, 29956, 30759, 33275, 36126, 38024, 20390,
+ 26517, 30137, 35786, 38663, 25391, 38215, 38453, 33976, 25379, 30529,
+ 24449, 29424, 20105, 24596, 25972, 25327, 27491, 25919, 24103, 30151,
+ 37073, 35777, 33437, 26525, [12096, 25903], 21553, 34584, 30693, 32930,
+ 33026, 27713, 20043, 32455, 32844, 30452, 26893, 27542, 25191, 20540,
+ 20356, 22336, 25351, [12108, 27490], 36286, 21482, 26088, 32440, 24535,
+ 25370, 25527, [12164, 33267], 33268, 32622, 24092, 23769, 21046, 26234,
+ 31209, 31258, 36136, 28825, 30164, 28382, 27835, 31378, 20013, 30405,
+ 24544, 38047, 34935, 32456, 31181, 32959, 37325, 20210, 20247,
+ [12168, 33311], 21608, 24030, 27954, 35788, 31909, 36724, 32920, 24090,
+ 21650, 30385, 23449, 26172, 39588, 29664, 26666, 34523, 26417, 29482,
+ 35832, 35803, 36880, [12149, 31481], 28891, 29038, 25284, 30633, 22065,
+ 20027, 33879, 26609, 21161, 34496, 36142, 38136, 31569, 20303, 27880,
+ 31069, 39547, 25235, [12118, 29226], 25341, 19987, 30742, 36716, 25776,
+ 36186, 31686, 26729, 24196, 35013, 22918, 25758, 22766, 29366, 26894,
+ 38181, 36861, 36184, 22368, 32512, 35846, 20934, 25417, 25305, 21331,
+ 26700, 29730, 33537, 37196, 21828, 30528, 28796, 27978, 20857, 21672,
+ 36164, 23039, 28363, 28100, 23388, 32043, 20180, 31869, 28371,
+ [12070, 23376], [12163, 33258], 28173, 23383, 39683, 26837, 36394, 23447,
+ 32508, 24635, 32437, 37049, [12187, 36208], 22863, 25549, 31199,
+ [12188, 36275], 21330, 26063, 31062, 35781, 38459, 32452, 38075, 32386,
+ 22068, 37257, 26368, 32618, 23562, 36981, 26152, 24038, 20304, 26590,
+ 20570, 20316, 22352, 24231, 20109, 19980, 20800, 19984, 24319, 21317,
+ 19989, 20120, 19998, [12224, 39730], 23404, 22121, [12033, 20008], 31162,
+ [12035, 20031], [12052, 21269], 20039, 22829, [12120, 29243], 21358, 27664,
+ 22239, 32996, 39319, 27603, 30590, 40727, [12034, 20022], 20127, 40720,
+ 20060, 20073, 20115, 33416, 23387, 21868, 22031, 20164, 21389, 21405,
+ 21411, 21413, 21422, 38757, 36189, [12053, 21274], 21493, 21286, 21294,
+ 21310, 36188, 21350, 21347, 20994, 21000, 21006, 21037, 21043,
+ {f: 2, c: 21055}, 21068, 21086, 21089, 21084, 33967, 21117, 21122, 21121,
+ 21136, 21139, [12044, 20866], 32596, 20155, 20163, 20169, 20162, 20200,
+ 20193, 20203, 20190, 20251, 20211, 20258, 20324, 20213, 20261, 20263,
+ 20233, 20267, 20318, 20327, 25912, 20314, 20317, 20319, 20311, 20274,
+ 20285, 20342, 20340, 20369, 20361, 20355, 20367, 20350, 20347, 20394,
+ 20348, 20396, 20372, 20454, 20456, 20458, 20421, 20442, 20451, 20444,
+ 20433, 20447, 20472, 20521, 20556, 20467, 20524, 20495, 20526, 20525,
+ 20478, 20508, 20492, 20517, 20520, 20606, 20547, 20565, 20552, 20558,
+ 20588, 20603, 20645, 20647, 20649, 20666, 20694, 20742, 20717, 20716,
+ 20710, 20718, 20743, 20747, 20189, 27709, 20312, 20325, 20430,
+ [12245, 40864], 27718, 31860, 20846, 24061, 40649, 39320, 20865, 22804,
+ [12051, 21241], 21261, 35335, 21264, 20971, 22809, 20821, [12039, 20128],
+ 20822, 20147, 34926, 34980, 20149, 33044, 35026, 31104, 23348, 34819,
+ 32696, [12046, 20907], 20913, 20925, 20924, 20935, [12045, 20886], 20898,
+ 20901, 35744, {f: 2, c: 35750}, 35754, {f: 2, c: 35764}, 35767,
+ {f: 2, c: 35778}, 35787, 35791, 35790, {f: 3, c: 35794}, 35798,
+ {f: 2, c: 35800}, 35804, {f: 2, c: 35807}, 35812, {f: 2, c: 35816}, 35822,
+ 35824, 35827, 35830, 35833, 35836, {f: 2, c: 35839}, 35842, 35844, 35847,
+ 35852, 35855, {f: 2, c: 35857}, {f: 3, c: 35860}, 35865, 35867, 35864,
+ 35869, {f: 3, c: 35871}, 35877, 35879, {f: 2, c: 35882}, {f: 2, c: 35886},
+ {f: 2, c: 35890}, {f: 2, c: 35893}, [12057, 21353], 21370, 38429, 38434,
+ 38433, 38449, 38442, 38461, 38460, 38466, 38473, 38484, 38495, 38503,
+ 38508, 38514, 38516, 38536, 38541, 38551, 38576, 37015, 37019, 37021,
+ 37017, 37036, 37025, 37044, 37043, 37046, 37050, 37048, 37040, 37071,
+ 37061, 37054, 37072, 37060, 37063, 37075, 37094, 37090, 37084, 37079,
+ 37083, 37099, 37103, 37118, 37124, 37154, 37150, 37155, 37169, 37167,
+ 37177, 37187, 37190, 21005, 22850, 21154, {f: 2, c: 21164}, 21182, 21759,
+ 21200, 21206, 21232, 21471, 29166, 30669, [12085, 24308], [12048, 20981],
+ 20988, [12223, 39727], [12059, 21430], 24321, 30042, 24047, 22348, 22441,
+ 22433, 22654, 22716, 22725, 22737, 22313, 22316, 22314, 22323, 22329,
+ {f: 2, c: 22318}, 22364, 22331, 22338, 22377, 22405, 22379, 22406, 22396,
+ 22395, 22376, 22381, 22390, 22387, 22445, 22436, 22412, 22450, 22479,
+ 22439, 22452, 22419, 22432, 22485, 22488, 22490, 22489, 22482, 22456,
+ 22516, 22511, 22520, 22500, 22493, 22539, 22541, 22525, 22509, 22528,
+ 22558, 22553, 22596, 22560, 22629, 22636, 22657, 22665, 22682, 22656,
+ 39336, 40729, 25087, 33401, 33405, 33407, 33423, 33418, 33448, 33412,
+ 33422, 33425, 33431, 33433, 33451, 33464, 33470, 33456, 33480, 33482,
+ 33507, 33432, 33463, 33454, {f: 2, c: 33483}, 33473, 33449, 33460, 33441,
+ 33450, 33439, 33476, 33486, 33444, 33505, 33545, 33527, 33508, 33551,
+ 33543, 33500, 33524, 33490, 33496, 33548, 33531, 33491, 33553, 33562,
+ 33542, {f: 2, c: 33556}, 33504, 33493, 33564, 33617, {f: 2, c: 33627},
+ 33544, 33682, 33596, 33588, 33585, 33691, 33630, 33583, 33615, 33607,
+ 33603, 33631, 33600, 33559, 33632, 33581, 33594, 33587, 33638, 33637,
+ 33640, 33563, 33641, 33644, 33642, {f: 2, c: 33645}, 33712, 33656,
+ {f: 2, c: 33715}, 33696, 33706, 33683, 33692, 33669, 33660, 33718, 33705,
+ 33661, 33720, 33659, 33688, 33694, 33704, 33722, 33724, 33729, 33793,
+ 33765, 33752, 22535, 33816, 33803, 33757, 33789, 33750, 33820, 33848,
+ 33809, 33798, 33748, 33759, 33807, 33795, {f: 2, c: 33784}, 33770, 33733,
+ 33728, 33830, 33776, 33761, 33884, 33873, 33882, 33881, 33907,
+ {f: 2, c: 33927}, 33914, 33929, 33912, 33852, 33862, 33897, 33910, 33932,
+ 33934, 33841, 33901, 33985, 33997, 34000, 34022, 33981, 34003, 33994,
+ 33983, 33978, 34016, 33953, 33977, 33972, 33943, 34021, 34019, 34060,
+ 29965, 34104, 34032, 34105, 34079, 34106, 34134, 34107, 34047, 34044,
+ 34137, 34120, 34152, 34148, 34142, 34170, 30626, 34115, 34162, 34171,
+ 34212, 34216, 34183, 34191, 34169, 34222, 34204, 34181, 34233, 34231,
+ 34224, 34259, 34241, 34268, 34303, 34343, 34309, 34345, 34326, 34364,
+ [12086, 24318], 24328, 22844, 22849, 32823, 22869, 22874, 22872, 21263,
+ [12074, 23586], 23589, 23596, 23604, 25164, 25194, 25247, 25275, 25290,
+ 25306, 25303, 25326, 25378, 25334, 25401, 25419, 25411, 25517, 25590,
+ 25457, 25466, 25486, 25524, 25453, 25516, 25482, 25449, 25518, 25532,
+ 25586, 25592, 25568, 25599, 25540, 25566, 25550, 25682, 25542, 25534,
+ 25669, 25665, 25611, 25627, 25632, 25612, 25638, 25633, 25694, 25732,
+ 25709, 25750, 25722, {f: 2, c: 25783}, 25753, 25786, 25792, 25808, 25815,
+ 25828, 25826, 25865, 25893, 25902, [12087, 24331], 24530, 29977, 24337,
+ 21343, 21489, 21501, 21481, 21480, 21499, 21522, 21526, 21510, 21579,
+ {f: 3, c: 21586}, 21590, 21571, 21537, 21591, 21593, 21539, 21554, 21634,
+ 21652, 21623, 21617, 21604, {f: 2, c: 21658}, 21636, 21622, 21606, 21661,
+ 21712, 21677, 21698, 21684, 21714, 21671, 21670, {f: 2, c: 21715}, 21618,
+ 21667, 21717, 21691, 21695, 21708, {f: 2, c: 21721}, 21724,
+ {f: 2, c: 21673}, 21668, 21725, 21711, 21726, 21787, 21735, 21792, 21757,
+ 21780, 21747, {f: 2, c: 21794}, 21775, 21777, 21799, 21802, 21863, 21903,
+ 21941, 21833, 21869, 21825, 21845, 21823, 21840, 21820, 21815, 21846,
+ {f: 3, c: 21877}, 21811, 21808, 21852, 21899, 21970, 21891, 21937, 21945,
+ 21896, 21889, 21919, 21886, 21974, 21905, 21883, 21983, {f: 2, c: 21949},
+ 21908, 21913, 21994, 22007, 21961, 22047, 21969, {f: 2, c: 21995}, 21972,
+ 21990, 21981, 21956, 21999, 21989, {f: 2, c: 22002}, {f: 2, c: 21964},
+ 21992, 22005, 21988, 36756, 22046, 22024, 22028, 22017, 22052, 22051,
+ 22014, 22016, 22055, 22061, 22104, 22073, 22103, 22060, 22093, 22114,
+ 22105, 22108, 22092, 22100, 22150, 22116, 22129, 22123, {f: 2, c: 22139},
+ 22149, 22163, 22191, 22228, [12062, 22231], 22237, 22241, 22261, 22251,
+ 22265, 22271, 22276, 22282, 22281, 22300, 24079, 24089, 24084, 24081,
+ 24113, {f: 2, c: 24123}, 24119, 24132, 24148, 24155, 24158, 24161, 23692,
+ 23674, 23693, 23696, 23702, 23688, {f: 2, c: 23704}, 23697, 23706, 23708,
+ 23733, 23714, 23741, 23724, 23723, 23729, 23715, 23745, 23735, 23748,
+ 23762, 23780, 23755, 23781, {f: 2, c: 23810}, 23847, 23846, 23854, 23844,
+ 23838, 23814, 23835, 23896, 23870, 23860, 23869, 23916, 23899, 23919,
+ 23901, 23915, 23883, 23882, 23913, 23924, 23938, 23961, 23965, 35955,
+ 23991, 24005, [12091, 24435], 24439, 24450, 24455, 24457, 24460, 24469,
+ 24473, 24476, 24488, 24493, 24501, 24508, 34914, [12090, 24417], 29357,
+ 29360, 29364, {f: 2, c: 29367}, 29379, 29377, 29390, 29389, 29394, 29416,
+ 29423, 29417, 29426, 29428, 29431, 29441, 29427, 29443, {f: 2, c: 29434},
+ 29463, 29459, 29473, 29450, 29470, 29469, 29461, 29474, 29497, 29477,
+ 29484, 29496, 29489, 29520, 29517, 29527, 29536, 29548, 29551, 29566,
+ [12167, 33307], 22821, 39143, 22820, [12065, 22786], 39267,
+ {f: 6, c: 39271}, 39284, 39287, 39293, 39296, 39300, 39303, 39306, 39309,
+ {f: 2, c: 39312}, {f: 3, c: 39315}, 24192, 24209, 24203, 24214, 24229,
+ 24224, 24249, 24245, 24254, 24243, 36179, 24274, 24273, 24283, 24296,
+ 24298, 33210, 24516, 24521, 24534, 24527, 24579, 24558, 24580, 24545,
+ 24548, 24574, {f: 2, c: 24581}, 24554, 24557, 24568, 24601, 24629, 24614,
+ 24603, 24591, 24589, 24617, 24619, 24586, 24639, 24609, {f: 2, c: 24696},
+ 24699, 24698, 24642, 24682, 24701, 24726, 24730, 24749, 24733, 24707,
+ 24722, 24716, 24731, 24812, 24763, 24753, 24797, 24792, 24774, 24794,
+ 24756, 24864, 24870, 24853, 24867, 24820, 24832, 24846, 24875, 24906,
+ 24949, 25004, 24980, 24999, 25015, 25044, 25077, 24541, 38579, 38377,
+ 38379, 38385, 38387, {f: 2, c: 38389}, 38396, 38398, {f: 2, c: 38403},
+ 38406, 38408, {f: 4, c: 38410}, 38415, 38418, {f: 3, c: 38421},
+ {f: 2, c: 38425}, 20012, [12121, 29247], 25109, 27701, 27732, 27740, 27722,
+ 27811, 27781, 27792, 27796, 27788, {f: 2, c: 27752}, 27764, 27766, 27782,
+ 27817, 27856, 27860, 27821, {f: 2, c: 27895}, 27889, 27863, 27826, 27872,
+ 27862, 27898, 27883, 27886, 27825, 27859, 27887, 27902, 27961, 27943,
+ 27916, 27971, 27976, 27911, 27908, 27929, 27918, 27947, 27981, 27950,
+ 27957, 27930, 27983, 27986, 27988, 27955, 28049, 28015, 28062, 28064,
+ 27998, {f: 2, c: 28051}, 27996, 28000, 28028, 28003, 28186, 28103, 28101,
+ 28126, 28174, 28095, 28128, 28177, 28134, 28125, 28121, 28182, 28075,
+ 28172, 28078, 28203, 28270, 28238, 28267, 28338, 28255, 28294,
+ {f: 2, c: 28243}, 28210, 28197, 28228, 28383, 28337, 28312, 28384, 28461,
+ 28386, 28325, 28327, 28349, 28347, 28343, 28375, 28340, 28367, 28303,
+ 28354, 28319, 28514, {f: 2, c: 28486}, 28452, 28437, 28409, 28463, 28470,
+ 28491, 28532, 28458, 28425, 28457, 28553, 28557, 28556, 28536, 28530,
+ 28540, 28538, 28625, 28617, 28583, 28601, 28598, 28610, 28641, 28654,
+ 28638, 28640, 28655, 28698, 28707, 28699, 28729, 28725, 28751, 28766,
+ [12071, 23424], 23428, 23445, 23443, 23461, 23480, 29999, 39582, 25652,
+ 23524, 23534, 35120, 23536, 36423, 35591, 36790, 36819, 36821, 36837,
+ 36846, 36836, 36841, 36838, 36851, 36840, 36869, 36868, 36875, 36902,
+ 36881, 36877, 36886, 36897, {f: 2, c: 36917}, 36909, 36911, 36932,
+ {f: 2, c: 36945}, 36944, 36968, 36952, 36962, 36955, 26297, 36980, 36989,
+ 36994, 37000, 36995, 37003, [12089, 24400], 24407, 24406, 24408, 23611,
+ 21675, 23632, 23641, 23409, 23651, 23654, 32700, 24362, 24361, 24365,
+ 33396, 24380, 39739, [12076, 23662], 22913, 22915, 22925, {f: 2, c: 22953},
+ 22947, 22935, 22986, 22955, 22942, 22948, 22994, 22962, 22959, 22999,
+ 22974, {f: 2, c: 23045}, 23005, 23048, 23011, 23000, 23033, 23052, 23049,
+ 23090, 23092, 23057, 23075, 23059, 23104, 23143, 23114, 23125, 23100,
+ 23138, 23157, 33004, 23210, 23195, 23159, 23162, 23230, 23275, 23218,
+ 23250, 23252, 23224, 23264, 23267, 23281, 23254, 23270, 23256, 23260,
+ 23305, 23319, 23318, 23346, 23351, 23360, 23573, 23580, 23386, 23397,
+ 23411, 23377, 23379, 23394, 39541, {f: 2, c: 39543}, 39546, 39551, 39549,
+ {f: 2, c: 39552}, 39557, 39560, 39562, 39568, {f: 2, c: 39570}, 39574,
+ 39576, {f: 3, c: 39579}, {f: 2, c: 39583}, {f: 2, c: 39586}, 39589, 39591,
+ 32415, 32417, 32419, 32421, {f: 2, c: 32424}, 32429, 32432, 32446,
+ {f: 3, c: 32448}, 32457, {f: 2, c: 32459}, 32464, 32468, 32471, 32475,
+ {f: 2, c: 32480}, 32488, 32491, {f: 2, c: 32494}, {f: 2, c: 32497}, 32525,
+ 32502, {f: 2, c: 32506}, 32510, {f: 3, c: 32513}, {f: 2, c: 32519},
+ {f: 2, c: 32523}, 32527, {f: 2, c: 32529}, 32535, 32537, 32540, 32539,
+ 32543, {f: 7, c: 32545}, {f: 4, c: 32554}, {f: 5, c: 32559}, 32565,
+ [12083, 24186], 30079, [12078, 24027], 30014, 37013, 29582, 29585, 29614,
+ 29602, 29599, 29647, 29634, 29649, 29623, 29619, 29632, 29641, 29640,
+ 29669, 29657, 39036, 29706, 29673, 29671, 29662, 29626, 29682, 29711,
+ 29738, 29787, 29734, 29733, 29736, 29744, 29742, 29740, 29723, 29722,
+ 29761, 29788, 29783, 29781, 29785, 29815, 29805, 29822, 29852, 29838,
+ {f: 2, c: 29824}, 29831, 29835, 29854, {f: 2, c: 29864}, 29840, 29863,
+ 29906, 29882, {f: 3, c: 38890}, 26444, 26451, 26462, 26440, 26473, 26533,
+ 26503, 26474, 26483, 26520, 26535, 26485, 26536, 26526, 26541, 26507,
+ 26487, 26492, 26608, 26633, 26584, 26634, 26601, 26544, 26636, 26585,
+ 26549, 26586, 26547, 26589, 26624, 26563, 26552, 26594, 26638, 26561,
+ 26621, {f: 2, c: 26674}, {f: 2, c: 26720}, 26702, 26722, 26692, 26724,
+ 26755, 26653, 26709, 26726, 26689, 26727, 26688, 26686, 26698, 26697,
+ 26665, 26805, 26767, 26740, 26743, 26771, 26731, 26818, 26990, 26876,
+ {f: 2, c: 26911}, 26873, 26916, 26864, 26891, 26881, 26967, 26851, 26896,
+ 26993, 26937, 26976, 26946, 26973, 27012, 26987, 27008, 27032, 27000,
+ 26932, 27084, {f: 2, c: 27015}, 27086, 27017, 26982, 26979, 27001, 27035,
+ 27047, 27067, 27051, 27053, 27092, 27057, 27073, 27082, 27103, 27029,
+ 27104, 27021, 27135, 27183, 27117, {f: 2, c: 27159}, 27237, 27122, 27204,
+ 27198, 27296, 27216, 27227, 27189, 27278, 27257, 27197, 27176, 27224,
+ 27260, 27281, 27280, 27305, 27287, 27307, 29495, 29522, {f: 2, c: 27521},
+ 27527, 27524, {f: 2, c: 27538}, 27533, {f: 2, c: 27546}, 27553, 27562,
+ 36715, 36717, {f: 3, c: 36721}, {f: 2, c: 36725}, 36728, 36727,
+ {f: 2, c: 36729}, 36732, 36734, {f: 2, c: 36737}, 36740, 36743, 36747,
+ {f: 3, c: 36749}, 36760, 36762, 36558, 25099, 25111, 25115, 25119, 25122,
+ 25121, 25125, 25124, 25132, 33255, 29935, 29940, 29951, 29967, 29969,
+ 29971, [12097, 25908], {f: 3, c: 26094}, 26122, 26137, 26482, 26115, 26133,
+ 26112, 28805, 26359, 26141, 26164, 26161, 26166, 26165, 32774, 26207,
+ 26196, 26177, 26191, 26198, 26209, 26199, 26231, 26244, 26252, 26279,
+ 26269, 26302, {f: 2, c: 26331}, 26342, 26345, {f: 2, c: 36146}, 36150,
+ 36155, 36157, 36160, {f: 2, c: 36165}, {f: 2, c: 36168}, 36167, 36173,
+ 36181, 36185, 35271, {f: 3, c: 35274}, {f: 4, c: 35278}, 29294, 29343,
+ 29277, 29286, 29295, {f: 2, c: 29310}, 29316, 29323, 29325, 29327, 29330,
+ 25352, 25394, 25520, 25663, 25816, 32772, 27626, 27635, 27645, 27637,
+ 27641, 27653, 27655, 27654, 27661, 27669, {f: 3, c: 27672}, 27681, 27689,
+ 27684, 27690, 27698, 25909, 25941, 25963, 29261, 29266, 29270, 29232,
+ 34402, 21014, 32927, 32924, 32915, 32956, 26378, 32957, 32945, 32939,
+ 32941, 32948, 32951, {f: 4, c: 32999}, 32987, 32962, 32964, 32985, 32973,
+ 32983, 26384, 32989, 33003, 33009, 33012, 33005, {f: 2, c: 33037}, 33010,
+ 33020, 26389, 33042, 35930, 33078, 33054, 33068, 33048, 33074, 33096,
+ 33100, 33107, 33140, {f: 2, c: 33113}, 33137, 33120, 33129,
+ {f: 2, c: 33148}, 33133, 33127, 22605, 23221, 33160, 33154, 33169, 28373,
+ 33187, 33194, 33228, 26406, 33226, 33211, 33217, 33190, 27428, 27447,
+ 27449, 27459, 27462, 27481, {f: 3, c: 39121}, 39125, {f: 2, c: 39129},
+ [12110, 27571], 24384, 27586, 35315, 26000, 40785, 26003, 26044, 26054,
+ 26052, 26051, 26060, 26062, 26066, 26070, 28800, 28828, 28822, 28829,
+ 28859, 28864, 28855, 28843, 28849, 28904, 28874, 28944, 28947, 28950,
+ 28975, 28977, 29043, 29020, 29032, 28997, 29042, 29002, 29048, 29050,
+ 29080, 29107, 29109, 29096, 29088, 29152, 29140, 29159, 29177, 29213,
+ 29224, 28780, 28952, 29030, 29113, 25150, 25149, 25155, {f: 2, c: 25160},
+ 31035, 31040, 31046, 31049, {f: 2, c: 31067}, 31059, 31066, 31074, 31063,
+ 31072, 31087, 31079, 31098, 31109, 31114, 31130, 31143, 31155, 24529,
+ 24528, 24636, 24669, 24666, 24679, 24641, 24665, 24675, 24747, 24838,
+ 24845, 24925, 25001, 24989, 25035, 25041, 25094, 32896, [12160, 32895],
+ 27795, 27894, 28156, 30710, 30712, 30720, 30729, {f: 2, c: 30743}, 30737,
+ 26027, 30765, {f: 2, c: 30748}, {f: 3, c: 30777}, 30751, 30780, 30757,
+ 30764, 30755, 30761, 30798, 30829, {f: 2, c: 30806}, 30758, 30800, 30791,
+ 30796, 30826, 30875, 30867, 30874, 30855, 30876, 30881, 30883, 30898,
+ 30905, 30885, 30932, 30937, 30921, 30956, 30962, 30981, 30964, 30995,
+ 31012, 31006, 31028, 40859, [12235, 40697], {f: 2, c: 40699}, 30449, 30468,
+ 30477, 30457, {f: 2, c: 30471}, 30490, 30498, 30489, 30509, 30502, 30517,
+ 30520, {f: 2, c: 30544}, 30535, 30531, 30554, 30568, 30562, 30565, 30591,
+ 30605, 30589, 30592, 30604, 30609, {f: 2, c: 30623}, 30640, 30645, 30653,
+ 30010, 30016, 30030, 30027, 30024, 30043, 30066, 30073, 30083, 32600,
+ 32609, 32607, 35400, 32616, 32628, 32625, 32633, 32641, 32638, 30413,
+ 30437, 34866, {f: 3, c: 38021}, 38027, 38026, {f: 2, c: 38028},
+ {f: 2, c: 38031}, 38036, 38039, 38037, {f: 3, c: 38042}, {f: 2, c: 38051},
+ 38059, 38058, 38061, 38060, {f: 2, c: 38063}, 38066, 38068,
+ {f: 5, c: 38070}, {f: 2, c: 38076}, 38079, 38084, {f: 7, c: 38088},
+ {f: 3, c: 38096}, {f: 3, c: 38101}, 38105, 38104, 38107, {f: 3, c: 38110},
+ 38114, {f: 2, c: 38116}, {f: 2, c: 38119}, 38122, 38121, 38123,
+ {f: 2, c: 38126}, {f: 3, c: 38131}, 38135, 38137, {f: 2, c: 38140}, 38143,
+ 38147, 38146, {f: 2, c: 38150}, {f: 2, c: 38153}, {f: 3, c: 38157},
+ {f: 5, c: 38162}, 38168, 38171, {f: 3, c: 38173}, 38178, {f: 2, c: 38186},
+ 38185, 38188, {f: 2, c: 38193}, 38196, {f: 3, c: 38198}, 38204,
+ {f: 2, c: 38206}, 38210, 38197, {f: 3, c: 38212}, 38217, 38220,
+ {f: 2, c: 38222}, {f: 3, c: 38226}, {f: 4, c: 38230}, 38235,
+ {f: 2, c: 38238}, 38237, {f: 2, c: 38241}, {f: 9, c: 38244}, 38255,
+ {f: 3, c: 38257}, 38202, 30695, 30700, 38601, 31189, 31213, 31203, 31211,
+ 31238, 23879, 31235, 31234, 31262, 31252, 31289, 31287, 31313, 40655,
+ 39333, 31344, 30344, 30350, 30355, 30361, 30372, 29918, 29920, 29996,
+ 40480, 40482, {f: 5, c: 40488}, 40498, 40497, 40502, 40504, 40503,
+ {f: 2, c: 40505}, 40510, {f: 2, c: 40513}, 40516, {f: 4, c: 40518},
+ {f: 2, c: 40523}, 40526, 40529, 40533, 40535, {f: 3, c: 40538}, 40542,
+ 40547, {f: 7, c: 40550}, 40561, 40557, 40563, [12135, 30098], 30100, 30102,
+ 30112, 30109, 30124, 30115, {f: 2, c: 30131}, 30136, 30148, 30129, 30128,
+ 30147, 30146, 30166, 30157, 30179, 30184, 30182, 30180, 30187, 30183,
+ 30211, 30193, 30204, 30207, 30224, 30208, 30213, 30220, 30231, 30218,
+ 30245, 30232, 30229, 30233, 30235, 30268, 30242, 30240, 30272, 30253,
+ 30256, 30271, 30261, 30275, 30270, 30259, 30285, 30302, 30292, 30300,
+ 30294, 30315, 30319, 32714, 31462, {f: 2, c: 31352}, 31360, 31366, 31368,
+ 31381, 31398, 31392, 31404, 31400, 31405, 31411, 34916, 34921, 34930,
+ 34941, 34943, 34946, 34978, 35014, 34999, 35004, 35017, 35042, 35022,
+ 35043, 35045, 35057, 35098, 35068, 35048, 35070, 35056, 35105, 35097,
+ 35091, 35099, 35082, 35124, 35115, 35126, 35137, 35174, 35195,
+ [12134, 30091], 32997, 30386, 30388, 30684, [12158, 32786], 32788, 32790,
+ 32796, 32800, 32802, {f: 3, c: 32805}, 32809, 32808, 32817, 32779, 32821,
+ 32835, 32838, 32845, 32850, 32873, 32881, 35203, 39032, 39040, 39043,
+ 39049, {f: 2, c: 39052}, 39055, 39060, {f: 2, c: 39066}, {f: 2, c: 39070},
+ {f: 2, c: 39073}, {f: 2, c: 39077}, [12172, 34381], 34388, 34412, 34414,
+ 34431, 34426, 34428, 34427, 34472, 34445, 34443, 34476, 34461, 34471,
+ 34467, 34474, 34451, 34473, 34486, 34500, 34485, 34510, 34480, 34490,
+ 34481, 34479, 34505, 34511, 34484, 34537, {f: 2, c: 34545}, 34541, 34547,
+ 34512, 34579, 34526, 34548, 34527, 34520, 34513, 34563, 34567, 34552,
+ 34568, 34570, 34573, 34569, 34595, 34619, 34590, 34597, 34606, 34586,
+ 34622, 34632, 34612, 34609, 34601, 34615, 34623, 34690, 34594,
+ {f: 2, c: 34685}, 34683, 34656, 34672, 34636, 34670, 34699, 34643, 34659,
+ 34684, 34660, 34649, 34661, 34707, 34735, 34728, 34770, 34758, 34696,
+ 34693, 34733, 34711, 34691, 34731, 34789, 34732, 34741, 34739, 34763,
+ 34771, 34749, 34769, 34752, 34762, 34779, 34794, 34784, 34798, 34838,
+ 34835, 34814, 34826, 34843, 34849, 34873, 34876, [12152, 32566], 32578,
+ {f: 2, c: 32580}, 33296, 31482, 31485, 31496, {f: 2, c: 31491}, 31509,
+ 31498, 31531, 31503, 31559, 31544, 31530, 31513, 31534, 31537, 31520,
+ 31525, 31524, 31539, 31550, 31518, 31576, 31578, 31557, 31605, 31564,
+ 31581, 31584, 31598, 31611, 31586, 31602, 31601, 31632, {f: 2, c: 31654},
+ 31672, 31660, 31645, 31656, 31621, 31658, 31644, 31650, 31659, 31668,
+ 31697, 31681, 31692, 31709, 31706, {f: 2, c: 31717}, 31722, 31756, 31742,
+ 31740, 31759, 31766, 31755, 31775, 31786, 31782, 31800, 31809, 31808,
+ 33278, {f: 2, c: 33281}, 33284, 33260, 34884, {f: 3, c: 33313}, 33325,
+ 33327, 33320, 33323, 33336, 33339, {f: 2, c: 33331}, 33342, 33348, 33353,
+ 33355, 33359, 33370, 33375, 33384, 34942, 34949, 34952, 35032, 35039,
+ 35166, 32669, 32671, 32679, {f: 2, c: 32687}, 32690, 31868, 25929, 31889,
+ 31901, 31900, 31902, 31906, 31922, {f: 2, c: 31932}, 31937, 31943,
+ {f: 2, c: 31948}, 31944, 31941, 31959, 31976, [12169, 33390], 26280, 32703,
+ 32718, 32725, 32741, 32737, 32742, 32745, 32750, 32755, [12151, 31992],
+ 32119, 32166, 32174, 32327, 32411, 40632, 40628, 36211, 36228, 36244,
+ 36241, 36273, 36199, 36205, 35911, 35913, 37194, 37200, {f: 2, c: 37198},
+ 37220, 37218, 37217, 37232, 37225, 37231, {f: 2, c: 37245}, 37234, 37236,
+ 37241, 37260, 37253, 37264, 37261, 37265, {f: 2, c: 37282}, 37290,
+ {f: 3, c: 37293}, 37301, 37300, 37306, [12183, 35925], 40574, 36280, 36331,
+ 36357, 36441, 36457, 36277, 36287, 36284, 36282, 36292, {f: 2, c: 36310},
+ 36314, 36318, {f: 2, c: 36302}, 36315, 36294, 36332, {f: 2, c: 36343},
+ 36323, 36345, 36347, 36324, 36361, 36349, 36372, 36381, 36383, 36396,
+ 36398, 36387, 36399, 36410, 36416, 36409, 36405, 36413, 36401, 36425,
+ {f: 2, c: 36417}, {f: 2, c: 36433}, 36426, 36464, 36470, 36476, 36463,
+ 36468, 36485, 36495, 36500, 36496, 36508, 36510, [12184, 35960], 35970,
+ 35978, 35973, 35992, 35988, 26011, 35286, 35294, 35290, 35292, 35301,
+ 35307, 35311, 35390, 35622, 38739, 38633, 38643, 38639, 38662, 38657,
+ 38664, 38671, 38670, 38698, 38701, 38704, 38718, 40832, 40835,
+ {f: 6, c: 40837}, 40844, 40702, 40715, 40717, [12203, 38585],
+ {f: 2, c: 38588}, 38606, 38610, 30655, 38624, 37518, 37550, 37576, 37694,
+ 37738, 37834, 37775, 37950, 37995, 40063, 40066, {f: 4, c: 40069}, 31267,
+ 40075, 40078, {f: 3, c: 40080}, {f: 2, c: 40084}, {f: 2, c: 40090},
+ {f: 6, c: 40094}, {f: 5, c: 40101}, 40107, {f: 2, c: 40109},
+ {f: 8, c: 40112}, {f: 4, c: 40122}, {f: 4, c: 40132}, {f: 7, c: 40138},
+ {f: 3, c: 40147}, {f: 3, c: 40151}, {f: 2, c: 40156}, 40159, 40162, 38780,
+ 38789, {f: 2, c: 38801}, 38804, 38831, 38827, 38819, 38834, 38836, 39601,
+ 39600, 39607, 40536, 39606, 39610, 39612, 39617, 39616, 39621, 39618,
+ {f: 2, c: 39627}, 39633, 39749, 39747, 39751, 39753, 39752, 39757, 39761,
+ 39144, 39181, 39214, 39253, 39252, [12221, 39647], 39649, 39654, 39663,
+ 39659, 39675, 39661, 39673, 39688, 39695, 39699, 39711, 39715,
+ {f: 2, c: 40637}, 32315, 40578, {f: 2, c: 40583}, 40587, 40594, 37846,
+ 40605, 40607, {f: 3, c: 40667}, 40672, 40671, 40674, 40681, 40679, 40677,
+ 40682, 40687, 40738, 40748, 40751, 40761, 40759, {f: 2, c: 40765}, 40772,
+ 12295, {s: 13}, 30362, 34297, 31001, 24859, 39599, 35158, 22761, 32631,
+ 25850, 25943, 38930, 36774, 32070, 24171, 32129, 37770, 35607, 39165,
+ 23542, 22577, 39825, 36649, [12185, 35997], 37575, 29437, 20633, 24970,
+ 32179, 31558, 30050, 25987, 24163, 38281, 37002, 32232, 36022, 35722,
+ 36783, 36782, 27161, 40009, 30303, 28693, 28657, 36051, 25839, 39173,
+ 25765, 37474, 37457, 39361, 35036, 36001, 21443, 34870, 27544, 24922,
+ 24920, 29158, 33980, 33369, 20489, 28356, 21408, 20596, 28204, 23652,
+ 35435, 25881, 25723, 34796, 39262, 35730, 32399, 37855, 29987, 38369,
+ 39019, 22580, 22039, [12199, 38263], 20767, 33144, 24288, 26274, 37396,
+ [12190, 36554], 24505, 22645, 38515, 35183, 31281, 25074, 35488, 39425,
+ 36978, 39347, [12242, 40786], 29118, 34909, 34802, 23541, 30087, 36490,
+ 31820, 32162, 37276, 37604, 38619, 30990, 20786, 35320, 34389, 20659,
+ 30241, 38358, 21109, 37656, 32020, 32189, 36781, 35422, 36060, 32880,
+ 24478, 21474, 36517, 31428, 37679, 36948, 24118, 36024, 25812, 21934,
+ 37170, 25763, 33213, 24986, 35477, 24392, 30070, 25803, 40680, 34153,
+ 27284, 25623, 23798, 31153, 23566, 29128, 37159, 25973, 28364, 36958,
+ 32224, 39003, 40670, 22666, 38651, 28593, 37347, 35519, 35548, 37336,
+ 38914, 37664, 35330, 26481, 21205, 26847, 20941, [12222, 39717], 29346,
+ 29544, 35712, 36077, 37709, 37723, 26039, 32222, 38538, 23565, 22136,
+ 38931, 37389, 22890, 22702, 40285, 38989, 35355, 24801, 39187, 20818,
+ 29246, 39180, 36019, 30332, 32624, 38309, 31020, 37353, 29033, 31684,
+ 36009, 39151, 35370, 32033, [12214, 39131], 35513, 24290, 36027, 32027,
+ 22707, 22894, 24996, 31966, 35920, 26963, 37586, [12213, 39080], 30219,
+ 39342, 32299, 35575, 40179, 33178, 36667, 25771, 36628, 36070, 24489,
+ 36000, 35331, 23142, 32283, 35442, 37411, 33995, 24185, 36245, 36123,
+ 23713, 21083, 37628, 32177, 23831, 37804, 25841, 40255, 38307, 37499,
+ 20491, 32102, 40852, 38799, 36002, 37390, 28317, 27083, 36092, 34865,
+ 39015, 21102, 38364, 35264, 39208, 24931, 36011, 24291, 35215, 27512,
+ [12244, 40860], 38312, 36556, 35437, 27331, 36020, 21130, 36645, 37707,
+ 22283, 36942, 39405, 38867, 28450, 34399, 38305, 40372, 36032, 36703,
+ 40251, 32005, 22778, 35703, 28396, 22057, 33775, 30059, 21123, 35441,
+ 25079, 22750, 27489, 29872, 36996, 32233, 35594, 25582, 36637, 36036,
+ 31330, 26371, 29172, 21295, 35569, 35496, 32362, 33911, 28222, 29554,
+ 36008, 31117, 25802, 27231, 31309, 39249, 35663, 40388, 32318, 32221,
+ 26997, 36655, 32026, 25824, 24190, 34186, 21137, 28639, 35336, 35352,
+ 38555, 32380, 32000, 22846, 33698, 38960, 36040, 37440, 20729, 39381,
+ 27570, 30435, 22533, 31627, 38291, 33393, 32216, 32365, 27298, 40572,
+ 25536, 25791, 31777, 20745, 34214, 27323, 37970, 36368, 36068,
+ [12178, 35211], 37749, 33382, 21133, 39198, 28472, 28666, 28567, 23559,
+ 28479, 34083, 27123, 22892, 35611, 37292, 33184, 28550, 39509, 23308,
+ 25898, 37496, 30703, 20709, 39171, 32371, 32094, 36686, 36611, 38542,
+ 31680, 28500, 32080, 35489, 32202, 37670, 20677, 35641, 36914, 29180,
+ 30433, 21185, 33686, 39912, 39514, 32147, 38968, 37857, 24465, 30169,
+ 31478, 31998, 33290, 39378, 33289, 25818, 37624, 25084, 21127, 40273,
+ 32121, 35258, 35363, 32118, 37406, 36557, 39423, 38283, 20977, 38982,
+ 27579, 35506, 22718, 25031, 25715, 24235, 35122, 35463, 22602, 20744,
+ 23532, 31014, 26336, 34407, 24011, 31418, 39243, 28528, 25844, 38346,
+ 34847, 33240, 33802, 20358, 36084, 34253, 27396, 25876, 31811, 38348,
+ 34349, 28734, 35733, 25900, 35261, 25078, 32412, 29211, 28651, 25736,
+ 21214, 28551, 27138, 37939, 22744, 39006, 31852, 38626, 28757, 35023,
+ 39881, 31150, 40599, 21426, 21237, 31019, 27511, 28701, 38584, 20486,
+ 32879, 34030, 36899, 37934, 24976, 28451, 31806, 25986, 33225, 37832,
+ 25088, 29001, 32244, 31975, 20841, 36635, 35538, 30274, 36988, 37904,
+ 29557, 33256, 37168, 40023, 36035, 40801, 37428, 38728, 23994, 38936,
+ 39230, 21129, [12243, 40845], 32894, 22184, 31840, 22751, 25871, 38580,
+ 27155, 23105, 25695, 31757, 34310, 30439, 39025, 24300, 29200, 25796,
+ 28407, 34396, 39791, 36034, 37682, 38520, 39522, 37569, 23650, 32311,
+ 24942, 28670, 32209, 24018, 25891, 23423, 28772, 20098, 25476, 36650,
+ 20523, 20374, 28138, 32184, 35542, 34367, 32645, 37007, 38012, 31854,
+ 39486, 39409, 32097, 23229, 29802, 30908, 34718, [12218, 39340], 39393,
+ 21966, 36023, [12230, 40613], 36067, 36993, 30622, 39237, 34875, 28415,
+ 35646, 37672, 37466, 36031, 37762, [12200, 38272], 24758, 20497, 37683,
+ 22818, 35598, 24396, 35219, 32191, 32236, 24287, 28357, 25003, 38313,
+ 40180, 37528, 35628, 35584, 30045, 37385, 32013, 38627, 25747, 33126,
+ 24817, 39719, 39186, 25836, 33193, 25862, 37312, [12227, 40165], 32886,
+ 22169, 38007, 37811, 27320, 29552, 23527, 25840, 28632, 37397, 32016,
+ 33215, 28611, 36786, 30247, 35582, 27472, 40407, 27590, 22036, 28442,
+ 30436, 40848, 36064, 22132, 40300, 39449, 39108, 38971, 36007, 34315,
+ 24977, 35413, 28497, 38935, 25778, 37610, 20693, 27192, 35676, 33229,
+ [12241, 40778], 39438, 35912, 21843, 27683, 35350, 29309, 37370, 37467,
+ 36983, 31805, 35609, 37666, 37463, 28154, 35700, 22649, 27085, 21958,
+ 22715, 34196, 25654, 37740, 27211, 21932, 20689, 32761, 31429, 31434,
+ 27453, 35242, 23522, 36629, 27691, 20670, 38915, 35531, 24950, 29898,
+ 31406, 36264, 21312, 36544, 39493, 40818, 39028, 27402, 21240, 40306,
+ 30906, 35731, 39250, 25854, 32350, 29105, 38860, 35469, 32009, 27054,
+ 32104, 36575, 37613, 38287, 28516, 28753, 34217, 39955, 36093, 20632,
+ 21930, 39479, 25475, 28544, 27578, 32023, 31721, 26348, 38275, 38493,
+ 36109, 32341, 20663, 36062, 29138, 32057, 36050, 25448, 25885, 25086,
+ 35373, 32051, 23529, 23352, 33102, 28402, 32882, 32361, 21213, 32854,
+ 24107, 29509, 28629, 35433, 26178, 34645, 23526, 35672, 39387, 21218,
+ 36969, 37323, 39166, 35222, 35430, 22781, 29560, 27166, 36664, 26360,
+ 36118, 23660, 34899, 27193, 31466, 25976, 24101, 38617, 35504, 38918,
+ 35500, 30889, 29197, 32114, 39164, 39686, 32883, 24939, 38924, 35359,
+ 35494, 25851, 34311, 35380, 32901, 38614, 38568, 32143, 27506, 23403,
+ 25613, 32302, 29795, 37782, 29562, 25787, 33274, 24907, 25892, 36010,
+ 30321, 28760, 22727, 35674, 35527, 22022, 28271, 29145, 28644, 32295,
+ 35342, 39472, 35588, 37563, 38988, 39636, 26781, 36028, 37941, 24307,
+ 32893, 28916, 37509, 32113, 38957, 22294, 22615, 22296, 38973, 40213,
+ 39345, 39389, 27234, 31402, 35178, 24398, 28771, 38929, 33836, 32178,
+ [12209, 38859], 36949, 22285, 29234, 28656, 32173, 33894, 20553, 20702,
+ 32239, 35586, 34907, 32862, 32011, 31337, 21839, 25790, 34680, 28198,
+ 31401, 21978, 37794, 28879, 35491, 28961, 34154, 22626, 38695, 21209,
+ 35492, 37675, 29351, 35186, 32722, 37521, 25138, 32048, 34662, 36676,
+ 23805, 20448, 29433, 22151, 37697, 39854, 32406, 36066, 37532, 38289,
+ 39023, 38570, 29694, 29563, 32291, 39201, 25010, 32171, 38002, 37129,
+ 35443, 38911, 38917, 34157, 22210, 37559, 26313, 22063, 21332, 25406,
+ 33029, 35559, 23531, 28681, 35613, 37573, 37313, 33288, 37561, 32137,
+ 38920, 35377, 32210, 32396, 36562, 25080, 36984, 30316, 32098, 23416,
+ 21211, 35426, 23563, 39348, 35347, 35338, 36956, 22739, 40201, 40232,
+ 21854, 20126, 35357, 38329, 40573, 22196, 38996, 38331, 33399, 21421,
+ 30831, 35578, 39511, 40230, 26954, 25562, 30221, 38525, 30306, 39178,
+ 27171, 22575, 35617, 34277, 29242, [12212, 38913], 26989, 33865, 37291,
+ 37541, 38948, 36986, 20736, 34811, 34269, 20740, 25014, 32681, 35427,
+ 35696, 35516, 35695, 32377, 34093, 38512, 37504, 39154, 38577, 27387,
+ 23344, 40441, 25033, 32403, 29801, 34722, 29151, 29074, 34821, 36111,
+ 31310, 21938, 25793, 20653, 30320, 36404, 20778, 24962, 37109, 37438,
+ 29494, 35480, 36671, 39192, [12226, 39770], 28417, 33287, 23996, 35486,
+ 39729, 29508, 35709, 38928, 39341, 40219, 28149, 36677, 22290, 21729,
+ 22291, 32227, 36960, 39000, 32004, 36493, 38000, 38322, 38642, 37142,
+ 38549, 36939, 34292, 37270, 26248, 38620, 36617, 25890, 26283, 36106,
+ 36124, 33247, 38015, 26839, 31432, 36012, 25799, 21063, 28580, 36042,
+ 36104, 36555, 37720, 38296, 35408, 40779, 20661, 27656, 30430, 26028,
+ 36670, 23940, 26855, 25136, 32187, 24373, 28466, 24115, 36076, 33081,
+ 36249, 34756, 36685, 37754, 36889, 35998, 37341, 20597, 35386, 37806,
+ 38499, 24128, 30309, 37165, 35657, 32340, 32887, 22519, 34937, 32025,
+ 25711, 25842, 24159, 36074, 28399, 37912, 32066, 31278, 33131, 34886,
+ 35589, 36600, 30394, 26205, 39519, 35576, 35461, 29165, 30682, 22225,
+ 36015, 37956, 31689, 39376, 23560, 30938, 36681, 36090, 27137, 33674,
+ 35037, 22941, 22767, 29376, 37648, 36101, 22684, 32180, 35524, 28310,
+ 28609, 36039, 28460, 32156, 32317, 32305, 37138, 35419, 32068, 38013,
+ 21959, 21401, 21428, 38760, 36107, 21293, 21297, 36094, 21060, 21132,
+ 21108, 20660, 20480, 20630, 20757, 20738, 20756, 20796, 20791, 20712,
+ 20674, 20795, 20752, 20794, 20681, 31988, 40652, 22213, 40172, 35131,
+ 33248, 35329, 35344, 35340, 35349, 35635, 35406, 35365, 35393, 35382,
+ 35398, 35412, 35416, 35410, 35462, 35460, 35455, 35440, 35452, 35445,
+ 35436, 35438, 35533, 35554, 35425, 35482, 35493, {f: 2, c: 35473}, 35535,
+ 35537, 35529, 35547, 35543, 35522, 35510, 35574, 35563, 35604, 35585,
+ 35556, 35565, 35580, 35571, 35558, 35566, 35550, 35624, 35740, 35606,
+ 35610, 35600, 35627, 35629, 35670, 35673, 35662, 35742, 35691, 35734,
+ 38488, 37178, 37140, 37172, 37087, 37174, 37126, 37192, 33467, 21233,
+ 24048, 22538, 22745, 22754, 22752, 22746, 22497, 22607, 22550, 22610,
+ 22557, 22628, 34188, 34131, 34294, 33703, 33799, 34031, 33511, 34338,
+ 34086, 22603, 29026, 34136, 34045, 34126, 34184, 34234, 29334, 28366,
+ 34113, 34254, 34130, 33984, 33874, 33892, 33940, 33845, 34207, 34133,
+ 40367, 33939, 32264, 34118, 34146, 34078, 39488, 34362, 37795, 34167,
+ 34334, 34298, 34308, 34282, 34330, 22889, 23607, 25451, 25718, 25759,
+ 25681, 25692, 25779, 25860, 25878, 25847, 25852, 25883, 22064, 22072,
+ 22216, 22182, 21764, 21692, 22144, 22109, 22112, 22069, 22006, 22118,
+ 22130, 22156, 22117, 22044, 22062, 21993, 22038, 22208, 22029, 22195,
+ 22209, 22127, 36705, 22198, 22165, 22279, 24131, 24172, 24152, 24151,
+ 23943, 23796, 23888, 23852, 23975, 23968, 23959, 23821, 23992, 23937,
+ 24020, 24480, 29559, 29505, 29546, 29499, 29547, 29568, 29564, 39136,
+ 39219, 39145, 39228, {f: 2, c: 39146}, 39149, 39156, 39177, 39185, 39195,
+ 39223, 39231, 39235, {f: 3, c: 39240}, 39244, 39266, 24289, 36065, 25082,
+ 25006, 24938, 24894, 24757, 24884, 25036, 24927, 25064, 24827, 24887,
+ 24818, 24947, 24860, 24978, 38274, 38278, 38344, 38286, 38292, 38284,
+ 38373, 38317, 38315, 39726, 38316, 38334, 38326, 39721, 38335, 38333,
+ 38332, 38339, 38347, 38356, 38352, 38357, 38366, 28739, 28505, 28711,
+ 28696, 28668, 28039, 28025, 28254, 28590, 28687, 28408, 28527, 28150,
+ 28543, 28678, 28576, 28683, 28775, 28740, 28677, 28535, 28704, 28703,
+ 28722, 28712, 28765, 39467, 36999, 36885, 37008, 23656, 24371, 23285,
+ 23255, 23296, 23149, 23304, 23372, 23207, 23291, 23307, 23329, 23338,
+ 23321, 39380, 39391, 39385, 39478, 39515, 39377, 39384, 39501, 39498,
+ 39394, 39530, 39439, 39437, 39429, 39490, 39469, 39446, 39489, 39470,
+ 39480, {f: 2, c: 39491}, 39503, 39525, 39524, 31993, 32006, 32002,
+ {f: 2, c: 32007}, 32394, 32028, 32021, 32019, 32058, 32050, 32049, 32272,
+ 32060, 32064, 32063, 32093, 32078, 32115, 32134, 32131, 32136, 32190,
+ 32186, 32203, 32212, 32196, 32158, 32172, 32185, 32163, 32176, 32199,
+ 32217, 32215, 32249, 32242, 32354, 32230, 32246, 32241, 32267, 32225,
+ 32265, 32285, 32287, 32286, 32301, 32266, 32273, 32381, 32313, 32309,
+ 32306, 32326, 32325, 32392, 32346, 32338, 32366, 32382, 32368, 32367,
+ 32408, 29859, 29771, 29903, 38922, 29885, 29759, 29833, 29862, 29908,
+ 29914, 38873, 38878, 38876, 27050, 27370, 26776, 26838, 27141, 26783,
+ 27355, 27379, 27368, 27359, 27273, 26895, 27208, 26984, 27071, 27194,
+ 27292, 27410, 27422, 27357, 27111, 27407, 27414, 27372, 27354, 27384,
+ 27315, 27367, 27299, 27347, 27358, 27556, 27550, 27566, 27563, 27567,
+ 36564, 36571, 36594, 36603, 36708, 36601, 36604, 36587, 36580, 36706,
+ 36602, 36606, 36618, 36615, 36613, 36626, 36646, {f: 2, c: 36638}, 36636,
+ 36659, 36678, 36692, 25108, 25127, 29964, 26311, 26308, 26249, 26326,
+ 36033, 36016, 36026, 36029, 36100, 36018, 36037, 36112, 36049, 36058,
+ 36053, 36075, 36071, 36091, 35224, 35244, 35233, 35263, 35238, 35247,
+ 35250, 35255, 27647, 27660, 27692, 29272, 26407, 33110, 33242, 33051,
+ 33214, 33121, 33231, 27487, {f: 2, c: 39086}, 39094, 39100, 39110, 39112,
+ 36674, 40783, 26005, 29036, 29010, 29079, 29121, 29148, 29182, 31152,
+ 31118, 31146, 25055, 24932, 25059, 25095, 28585, 30959, 30893, 30824,
+ 30904, 31018, 31025, 30820, 30973, 30951, 30947, 40853, 30616, 30558,
+ 30652, 32646, 32648, {f: 3, c: 37330}, 37337, 37335, 37333, 37367, 37351,
+ 37348, 37702, 37365, 37369, 37384, 37414, 37445, 37393, 37392, 37377,
+ 37415, 37380, 37413, 37376, 37434, 37478, 37431, 37427, 37461, 37437,
+ 37432, 37470, {f: 2, c: 37484}, 37439, 37984, 37424, 37449, 37448, 37453,
+ 37422, 37433, 37944, 37548, 37536, 37498, 37546, 37614, 37583, 37891,
+ 37603, 37946, 37553, 37542, 37799, 37526, 37580, 37545, 37877, 37523,
+ 37503, 37801, 37530, 37658, 37547, 37507, 37899, 37544, 37539, 37906,
+ 37688, 37617, 37847, 37605, 37616, 37615, 37608, 37564, 37597, 37622,
+ {f: 2, c: 37926}, 37571, 37599, 37606, 37650, 37638, 37737, 37659, 37696,
+ 37633, 37653, 37678, 37699, {f: 2, c: 37639}, 37663, 37657, 37733, 37703,
+ 37750, 37716, 37732, 37802, 37744, 37764, 37860, 37848, 37928, 37767,
+ 37836, 37784, 37816, 37823, 37798, 37808, 37813, 37964, 37858,
+ {f: 2, c: 37852}, 37837, 37854, 37827, 37831, 37841, 37908, 37917, 37879,
+ 37989, 37907, 37997, 37920, 38009, 37881, 37913, 37962, 37938, 37951,
+ 37972, 37987, 37758, 31329, 40169, 40182, 40199, 40198, 40227, 40327,
+ 40469, 40221, 40223, 40421, 40239, 40409, 40240, 40258, 40478, 40275,
+ 40477, 40288, 40274, 40435, 40284, 40289, 40339, 40298, 40303, 40329,
+ 40344, 40346, 40384, 40357, 40361, 40386, 40380, 40474, 40403, 40410,
+ 40431, 40422, 40434, 40440, 40460, 40442, 40475, 30308, 30296, 30311,
+ 30210, {f: 2, c: 30278}, 30281, 30238, 30267, {f: 2, c: 30317}, 30313,
+ 30322, 31431, 31414, 35168, 35123, 35165, 35143, 35128, 35172, 30392,
+ 32814, 32812, 32889, 32885, 38919, {f: 2, c: 38926}, 38945, 38940, 28481,
+ 38950, 38967, 38990, 38995, 39027, 39010, 39001, 39013, 39020, 39024,
+ 34787, 34822, 34566, 34851, 34806, 34554, 34799, 34692, 34832, 34760,
+ 34833, 34747, 34766, 32588, 31716, 31591, 31849, 31731, 31744, 31691,
+ 31836, 31774, 31787, 31779, 31850, 31839, 33380, 33387, 35018, 32677,
+ 31986, 31990, 31965, 32310, 40617, 36274, 37317, 37315, 40570, 36489,
+ 36428, 36498, 36474, 36437, 36506, 36491, 36499, 36497, 36513, 36451,
+ 36522, 36518, 35316, 35318, 38746, 38722, 38717, 38724, 40788, 40799,
+ 40793, 40800, 40796, 40806, 40812, 40810, 40823, [12236, 40701], 40703,
+ 40713, 35726, 38014, 37864, 39799, 39796, 39809, 39811, 39822, 40056,
+ 31308, 39826, 40031, 39824, 39853, 39834, 39850, 39838, 40045, 39851,
+ 39837, 40024, 39873, 40058, 39985, 39993, 39971, 39991, 39872, 39882,
+ 39879, 39933, 39894, {f: 2, c: 39914}, 39905, 39908, 39911, 39901, 39906,
+ 39920, 39899, 39924, 39892, 40029, 39944, 39952, 39949, 39954, 39945,
+ 39935, 39968, 39986, 39981, 39976, 39973, 39977, 39987, 39998, 40008,
+ 39995, 39989, 40005, 40022, 40020, 40018, 40039, 38851, 38845, 38857,
+ 40379, 39631, 39638, 39637, 39768, 39758, 39255, 39260, 39714, 40695,
+ 40690, 35180, 38342, 37686, 24390, 34068, 32404, 40803, 22137, 40725,
+ 22081, 39662, 35079, 31296, 39091, 38308, 39693, 36852, 24409, 31339,
+ 39138, 20642, 34193, 20760, 25458, 21067, 30543, 32397, 26310, 30637,
+ [12228, 40565], 22217, 40692, 28635, 25054, 30663, 28720, 40629, 34890,
+ 38370, 38854, 31844, 32308, 38822, 40623, 22220, 39089, 27311, 32590,
+ 31984, 20418, 32363, 40569, 22190, 39706, 33903, 31142, 31858, 39634,
+ 38587, 32251, 35069, 30787, {f: 10, c: 8560}, {f: 2, c: 714}, 729, 8211,
+ 8213, 8229, 8245, 8453, 8457, {f: 4, c: 8598}, 8725, 8735, 8739, 8786,
+ {f: 2, c: 8806}, 8895, {f: 36, c: 9552}, {f: 15, c: 9601}, {f: 3, c: 9619},
+ {f: 2, c: 9660}, {f: 4, c: 9698}, 9737, 8853, 12306, {f: 2, c: 12317},
+ {f: 9, c: 12321}, 12963, {f: 2, c: 13198}, {f: 3, c: 13212}, 13217, 13252,
+ 13262, {f: 2, c: 13265}, 13269, 65072, 65506, 65508, 8481, 12849, 8208,
+ 12540, {f: 2, c: 12443}, {f: 2, c: 12541}, 12294, {f: 2, c: 12445},
+ {f: 10, c: 65097}, {f: 4, c: 65108}, {f: 14, c: 65113}, {f: 4, c: 65128},
+ 12350, {f: 12, c: 12272}, 19970, {f: 3, c: 19972}, 19983, 19986, 19991,
+ {f: 3, c: 19999}, 20003, 20006, 20009, {f: 2, c: 20014}, 20017, 20019,
+ 20021, 20023, 20028, {f: 3, c: 20032}, 20036, 20038, 20042, 20049, 20053,
+ 20055, {f: 2, c: 20058}, {f: 4, c: 20066}, {f: 2, c: 20071},
+ {f: 6, c: 20074}, 20082, {f: 10, c: 20084}, {f: 3, c: 20095},
+ {f: 2, c: 20099}, [12037, 20101], 20103, 20106, 20112, {f: 2, c: 20118},
+ 20121, {f: 2, c: 20124}, 20131, 20138, {f: 3, c: 20143}, 20148,
+ {f: 4, c: 20150}, {f: 3, c: 20156}, 20168, 20172, {f: 2, c: 20175}, 20178,
+ {f: 3, c: 20186}, 20192, 20194, {f: 2, c: 20198}, 20201, {f: 3, c: 20205},
+ 20209, 20212, {f: 3, c: 20216}, 20220, 20222, 20224, {f: 7, c: 20226},
+ {f: 2, c: 20235}, {f: 5, c: 20242}, {f: 2, c: 20252}, 20257, 20259,
+ {f: 2, c: 20264}, {f: 3, c: 20268}, 20273, 20275, 20277, 20279, 20281,
+ 20283, {f: 5, c: 20286}, {f: 2, c: 20292}, {f: 6, c: 20295}, 20306, 20308,
+ 20310, {f: 2, c: 20321}, 20326, 20328, {f: 2, c: 20330}, {f: 2, c: 20333},
+ {f: 2, c: 20337}, 20341, {f: 4, c: 20343}, 20349, {f: 3, c: 20352}, 20357,
+ 20359, 20362, 20364, 20366, 20368, {f: 2, c: 20370}, 20373,
+ {f: 3, c: 20376}, 20380, {f: 2, c: 20382}, {f: 2, c: 20385}, 20388, 20395,
+ 20397, {f: 5, c: 20400}, {f: 9, c: 20406}, {f: 2, c: 20416},
+ {f: 4, c: 20422}, {f: 3, c: 20427}, {f: 5, c: 20434}, 20441, 20443, 20450,
+ {f: 2, c: 20452}, 20455, {f: 2, c: 20459}, 20464, 20466, {f: 4, c: 20468},
+ 20473, {f: 3, c: 20475}, 20479, {f: 5, c: 20481}, {f: 2, c: 20487}, 20490,
+ 20494, 20496, 20499, {f: 3, c: 20501}, 20507, {f: 2, c: 20509}, 20512,
+ {f: 3, c: 20514}, 20519, {f: 11, c: 20527}, 20539, 20541, {f: 4, c: 20543},
+ {f: 3, c: 20548}, {f: 2, c: 20554}, 20557, {f: 5, c: 20560},
+ {f: 4, c: 20566}, 20571, {f: 8, c: 20573}, {f: 6, c: 20582},
+ {f: 7, c: 20589}, {f: 3, c: 20600}, {f: 2, c: 20604}, {f: 4, c: 20609},
+ {f: 2, c: 20614}, {f: 4, c: 20617}, {f: 8, c: 20622}, 20631,
+ {f: 8, c: 20634}, 20644, 20646, {f: 2, c: 20650}, {f: 4, c: 20654}, 20662,
+ {f: 2, c: 20664}, {f: 2, c: 20668}, {f: 3, c: 20671}, {f: 2, c: 20675},
+ {f: 3, c: 20678}, {f: 5, c: 20682}, 20688, {f: 3, c: 20690},
+ {f: 3, c: 20695}, {f: 3, c: 20699}, {f: 6, c: 20703}, {f: 3, c: 20713},
+ {f: 4, c: 20719}, 20724, {f: 3, c: 20726}, 20730, {f: 4, c: 20732}, 20737,
+ 20739, 20741, 20746, {f: 4, c: 20748}, 20753, 20755, {f: 2, c: 20758},
+ {f: 6, c: 20761}, 20768, {f: 8, c: 20770}, {f: 7, c: 20779},
+ {f: 4, c: 20787}, {f: 2, c: 20792}, {f: 2, c: 20797}, 20802, 20807, 20810,
+ 20812, {f: 3, c: 20814}, 20819, {f: 3, c: 20823}, 20827, {f: 5, c: 20829},
+ {f: 2, c: 20835}, {f: 2, c: 20838}, 20842, 20847, 20850, 20858,
+ {f: 2, c: 20862}, {f: 2, c: 20867}, {f: 2, c: 20870}, {f: 2, c: 20874},
+ {f: 4, c: 20878}, {f: 2, c: 20883}, 20888, 20890, {f: 3, c: 20893}, 20897,
+ 20899, {f: 5, c: 20902}, {f: 2, c: 20909}, 20916, {f: 3, c: 20920},
+ {f: 2, c: 20926}, {f: 3, c: 20929}, 20933, 20936, 20938, 20942, 20944,
+ {f: 9, c: 20946}, 20956, {f: 2, c: 20958}, {f: 2, c: 20962},
+ {f: 6, c: 20965}, 20972, 20974, 20978, 20980, 20983, 20990,
+ {f: 2, c: 20996}, 21001, {f: 2, c: 21003}, {f: 2, c: 21007},
+ {f: 3, c: 21011}, 21020, {f: 2, c: 21022}, {f: 3, c: 21025},
+ {f: 3, c: 21029}, 21034, 21036, 21039, {f: 2, c: 21041}, {f: 2, c: 21044},
+ 21052, 21054, {f: 2, c: 21061}, {f: 2, c: 21064}, {f: 2, c: 21070},
+ {f: 2, c: 21074}, 21077, {f: 4, c: 21079}, 21085, {f: 2, c: 21087},
+ {f: 3, c: 21090}, 21094, 21096, {f: 3, c: 21099}, {f: 2, c: 21104}, 21107,
+ {f: 7, c: 21110}, 21118, 21120, {f: 3, c: 21124}, 21131, {f: 2, c: 21134},
+ 21138, {f: 7, c: 21140}, 21148, {f: 4, c: 21156}, {f: 3, c: 21166},
+ {f: 10, c: 21172}, 21184, 21186, {f: 3, c: 21188}, 21192, 21194,
+ {f: 4, c: 21196}, 21201, {f: 2, c: 21203}, 21207, 21210, 21212,
+ {f: 2, c: 21216}, 21219, {f: 11, c: 21221}, {f: 3, c: 21234},
+ {f: 2, c: 21238}, {f: 3, c: 21243}, {f: 4, c: 21249}, 21255,
+ {f: 4, c: 21257}, 21262, {f: 4, c: 21265}, 21272, {f: 2, c: 21275},
+ {f: 2, c: 21278}, 21282, {f: 2, c: 21284}, {f: 3, c: 21287},
+ {f: 2, c: 21291}, 21296, {f: 6, c: 21298}, [12054, 21304],
+ {f: 2, c: 21308}, 21314, 21316, 21318, {f: 3, c: 21323}, 21328,
+ {f: 2, c: 21336}, 21339, 21341, 21349, 21352, 21354, {f: 2, c: 21356},
+ 21362, 21366, 21369, {f: 4, c: 21371}, {f: 2, c: 21376}, 21379,
+ {f: 2, c: 21383}, 21386, {f: 7, c: 21390}, {f: 2, c: 21398},
+ {f: 2, c: 21403}, 21406, 21409, 21412, 21415, {f: 3, c: 21418},
+ {f: 3, c: 21423}, 21427, 21429, {f: 4, c: 21431}, {f: 3, c: 21436}, 21440,
+ {f: 4, c: 21444}, {f: 3, c: 21454}, {f: 2, c: 21458}, 21461, 21466,
+ {f: 3, c: 21468}, 21473, 21479, 21492, 21498, {f: 3, c: 21502}, 21506,
+ 21509, 21511, 21515, 21524, {f: 3, c: 21528}, 21532, 21538,
+ {f: 2, c: 21540}, 21546, 21552, 21555, {f: 2, c: 21558}, 21562, 21565,
+ 21567, {f: 2, c: 21569}, {f: 2, c: 21572}, 21575, 21577, {f: 4, c: 21580},
+ 21585, 21594, {f: 5, c: 21597}, 21603, 21605, 21607, {f: 8, c: 21609},
+ 21620, {f: 2, c: 21625}, {f: 2, c: 21630}, 21633, 21635, 21637,
+ {f: 4, c: 21639}, 21645, 21649, 21651, {f: 2, c: 21655}, 21660,
+ {f: 5, c: 21662}, 21669, 21678, 21680, 21682, {f: 3, c: 21685},
+ {f: 2, c: 21689}, 21694, 21699, 21701, {f: 2, c: 21706}, 21718, 21720,
+ 21723, 21728, {f: 3, c: 21730}, {f: 2, c: 21739}, {f: 3, c: 21743},
+ {f: 6, c: 21748}, 21755, 21758, 21760, {f: 2, c: 21762}, 21765, 21768,
+ {f: 5, c: 21770}, {f: 2, c: 21778}, {f: 6, c: 21781}, {f: 4, c: 21788},
+ 21793, {f: 2, c: 21797}, {f: 2, c: 21800}, 21803, 21805, 21810,
+ {f: 3, c: 21812}, {f: 4, c: 21816}, 21821, 21824, 21826, 21829,
+ {f: 2, c: 21831}, {f: 4, c: 21835}, {f: 2, c: 21841}, 21844,
+ {f: 5, c: 21847}, 21853, {f: 2, c: 21855}, {f: 2, c: 21858},
+ {f: 2, c: 21864}, 21867, {f: 6, c: 21871}, {f: 2, c: 21881}, 21885, 21887,
+ {f: 2, c: 21893}, {f: 3, c: 21900}, 21904, {f: 2, c: 21906},
+ {f: 3, c: 21909}, {f: 2, c: 21914}, 21918, {f: 7, c: 21920},
+ {f: 2, c: 21928}, 21931, 21933, {f: 2, c: 21935}, 21940, 21942, 21944,
+ 21946, 21948, {f: 5, c: 21951}, 21960, {f: 2, c: 21962}, {f: 2, c: 21967},
+ 21973, {f: 3, c: 21975}, 21979, 21982, 21984, 21986, 21991,
+ {f: 2, c: 21997}, {f: 2, c: 22000}, 22004, {f: 5, c: 22008}, 22015,
+ {f: 4, c: 22018}, 22023, {f: 2, c: 22026}, {f: 4, c: 22032}, 22037,
+ {f: 2, c: 22041}, 22045, {f: 3, c: 22048}, {f: 2, c: 22053}, 22056,
+ {f: 2, c: 22058}, 22067, 22071, 22074, {f: 3, c: 22076}, 22080,
+ {f: 10, c: 22082}, {f: 5, c: 22095}, {f: 2, c: 22101}, {f: 2, c: 22106},
+ {f: 2, c: 22110}, 22113, 22115, 22119, {f: 2, c: 22125}, 22128, 22131,
+ 22133, 22135, 22138, {f: 3, c: 22141}, {f: 4, c: 22145}, {f: 4, c: 22152},
+ 22157, {f: 3, c: 22160}, 22164, {f: 3, c: 22166}, {f: 9, c: 22170},
+ {f: 2, c: 22180}, 22183, {f: 5, c: 22185}, {f: 3, c: 22192}, 22197,
+ {f: 4, c: 22200}, {f: 3, c: 22205}, {f: 2, c: 22211}, {f: 2, c: 22214},
+ 22219, {f: 4, c: 22221}, {f: 2, c: 22226}, {f: 2, c: 22229},
+ {f: 2, c: 22232}, 22236, 22243, {f: 6, c: 22245}, 22252, {f: 2, c: 22254},
+ {f: 2, c: 22258}, {f: 3, c: 22262}, {f: 2, c: 22267}, {f: 3, c: 22272},
+ 22277, 22284, {f: 4, c: 22286}, {f: 2, c: 22292}, 22295, {f: 3, c: 22297},
+ {f: 2, c: 22301}, {f: 3, c: 22304}, {f: 4, c: 22308}, 22315,
+ {f: 2, c: 22321}, {f: 5, c: 22324}, {f: 2, c: 22332}, 22335, 22337,
+ {f: 4, c: 22339}, {f: 2, c: 22344}, 22347, {f: 5, c: 22354},
+ {f: 2, c: 22360}, {f: 2, c: 22370}, 22373, 22375, 22380, 22382,
+ {f: 3, c: 22384}, {f: 2, c: 22388}, {f: 3, c: 22392}, {f: 5, c: 22397},
+ {f: 4, c: 22407}, {f: 5, c: 22413}, {f: 7, c: 22420}, {f: 4, c: 22428},
+ 22437, 22440, 22442, 22444, {f: 3, c: 22447}, 22451, {f: 3, c: 22453},
+ {f: 9, c: 22457}, {f: 7, c: 22468}, {f: 2, c: 22476}, {f: 2, c: 22480},
+ 22483, {f: 2, c: 22486}, {f: 2, c: 22491}, 22494, {f: 2, c: 22498},
+ {f: 8, c: 22501}, 22510, {f: 4, c: 22512}, {f: 2, c: 22517},
+ {f: 2, c: 22523}, {f: 2, c: 22526}, 22529, {f: 2, c: 22531},
+ {f: 2, c: 22536}, 22540, {f: 3, c: 22542}, {f: 3, c: 22546},
+ {f: 2, c: 22551}, {f: 3, c: 22554}, 22559, {f: 2, c: 22562},
+ {f: 5, c: 22565}, {f: 4, c: 22571}, {f: 2, c: 22578}, {f: 14, c: 22582},
+ {f: 5, c: 22597}, 22606, 22608, 22611, {f: 2, c: 22613}, {f: 5, c: 22617},
+ {f: 3, c: 22623}, 22627, {f: 5, c: 22630}, {f: 8, c: 22637},
+ {f: 3, c: 22646}, {f: 4, c: 22650}, 22655, 22658, 22660, {f: 3, c: 22662},
+ {f: 7, c: 22667}, {f: 5, c: 22676}, 22683, 22685, {f: 8, c: 22688},
+ {f: 4, c: 22698}, {f: 4, c: 22703}, {f: 7, c: 22708}, 22717,
+ {f: 2, c: 22719}, {f: 3, c: 22722}, 22726, {f: 9, c: 22728}, 22738, 22740,
+ {f: 2, c: 22742}, {f: 3, c: 22747}, 22753, 22755, {f: 4, c: 22757}, 22762,
+ 22765, {f: 2, c: 22769}, {f: 2, c: 22772}, {f: 2, c: 22775},
+ {f: 2, c: 22779}, {f: 4, c: 22782}, 22787, {f: 2, c: 22789},
+ {f: 2, c: 22792}, [12066, 22794], {f: 2, c: 22795}, 22798,
+ {f: 4, c: 22800}, {f: 2, c: 22807}, 22811, {f: 2, c: 22813},
+ {f: 2, c: 22816}, 22819, 22822, 22824, 22828, 22832, {f: 2, c: 22834},
+ {f: 2, c: 22837}, 22843, 22845, {f: 2, c: 22847}, 22851, {f: 2, c: 22853},
+ 22858, {f: 2, c: 22860}, 22864, {f: 2, c: 22866}, 22873, {f: 5, c: 22875},
+ 22881, {f: 2, c: 22883}, {f: 3, c: 22886}, 22891, 22893, {f: 4, c: 22895},
+ 22901, 22903, {f: 3, c: 22906}, {f: 3, c: 22910}, 22917, 22921,
+ {f: 2, c: 22923}, {f: 4, c: 22926}, {f: 2, c: 22932}, 22936,
+ {f: 3, c: 22938}, {f: 4, c: 22943}, {f: 2, c: 22950}, {f: 2, c: 22956},
+ {f: 2, c: 22960}, {f: 6, c: 22963}, 22970, {f: 2, c: 22972},
+ {f: 7, c: 22975}, {f: 3, c: 22983}, {f: 4, c: 22988}, {f: 2, c: 22997},
+ 23001, 23003, {f: 5, c: 23006}, 23012, {f: 2, c: 23014}, {f: 3, c: 23017},
+ {f: 12, c: 23021}, 23034, {f: 3, c: 23036}, 23040, 23042, {f: 2, c: 23050},
+ {f: 4, c: 23053}, 23058, {f: 4, c: 23060}, {f: 3, c: 23065},
+ {f: 2, c: 23069}, {f: 2, c: 23073}, 23076, {f: 3, c: 23078},
+ {f: 7, c: 23082}, 23091, 23093, {f: 5, c: 23095}, {f: 3, c: 23101},
+ {f: 4, c: 23106}, {f: 2, c: 23111}, {f: 10, c: 23115}, {f: 4, c: 23126},
+ {f: 7, c: 23131}, {f: 3, c: 23139}, {f: 2, c: 23144}, {f: 2, c: 23147},
+ {f: 6, c: 23150}, {f: 2, c: 23160}, {f: 4, c: 23163}, {f: 18, c: 23168},
+ {f: 7, c: 23187}, {f: 11, c: 23196}, {f: 2, c: 23208}, {f: 7, c: 23211},
+ 23220, {f: 2, c: 23222}, {f: 4, c: 23225}, {f: 2, c: 23231},
+ {f: 6, c: 23235}, {f: 2, c: 23242}, {f: 5, c: 23245}, 23251, 23253,
+ {f: 3, c: 23257}, {f: 3, c: 23261}, 23266, {f: 2, c: 23268},
+ {f: 2, c: 23271}, 23274, {f: 5, c: 23276}, {f: 3, c: 23282},
+ {f: 5, c: 23286}, {f: 4, c: 23292}, {f: 7, c: 23297}, 23306,
+ {f: 9, c: 23309}, 23320, {f: 7, c: 23322}, {f: 8, c: 23330},
+ {f: 5, c: 23339}, 23345, 23347, {f: 2, c: 23349}, {f: 7, c: 23353},
+ {f: 11, c: 23361}, {f: 3, c: 23373}, 23378, 23382, 23390, {f: 2, c: 23392},
+ {f: 2, c: 23399}, {f: 3, c: 23405}, 23410, 23412, {f: 2, c: 23414}, 23417,
+ {f: 2, c: 23419}, 23422, 23426, 23430, 23434, {f: 2, c: 23437},
+ {f: 3, c: 23440}, 23444, 23446, 23455, {f: 3, c: 23463}, {f: 4, c: 23468},
+ {f: 2, c: 23473}, 23479, {f: 3, c: 23482}, {f: 2, c: 23488}, 23491,
+ {f: 4, c: 23496}, {f: 3, c: 23501}, 23505, {f: 9, c: 23508}, 23520, 23523,
+ 23530, 23533, 23535, {f: 4, c: 23537}, 23543, {f: 2, c: 23549}, 23552,
+ {f: 2, c: 23554}, 23557, 23564, 23568, {f: 2, c: 23570}, 23575, 23577,
+ 23579, {f: 4, c: 23582}, 23587, 23590, {f: 4, c: 23592}, {f: 4, c: 23597},
+ {f: 2, c: 23602}, {f: 2, c: 23605}, {f: 2, c: 23619}, {f: 2, c: 23622},
+ {f: 2, c: 23628}, {f: 3, c: 23634}, {f: 3, c: 23638}, {f: 4, c: 23642},
+ 23647, 23655, {f: 3, c: 23657}, 23661, 23664, {f: 7, c: 23666},
+ {f: 4, c: 23675}, 23680, {f: 5, c: 23683}, {f: 3, c: 23689},
+ {f: 2, c: 23694}, {f: 2, c: 23698}, 23701, {f: 4, c: 23709},
+ {f: 5, c: 23716}, 23722, {f: 3, c: 23726}, 23730, 23732, 23734,
+ {f: 4, c: 23737}, 23742, 23744, {f: 2, c: 23746}, {f: 6, c: 23749},
+ {f: 6, c: 23756}, {f: 6, c: 23763}, {f: 7, c: 23770}, {f: 2, c: 23778},
+ 23783, 23785, {f: 2, c: 23787}, {f: 2, c: 23790}, {f: 3, c: 23793}, 23797,
+ {f: 4, c: 23799}, 23804, {f: 4, c: 23806}, {f: 2, c: 23812},
+ {f: 5, c: 23816}, {f: 5, c: 23823}, 23829, {f: 3, c: 23832},
+ {f: 2, c: 23836}, {f: 5, c: 23839}, 23845, 23848, {f: 2, c: 23850},
+ {f: 5, c: 23855}, {f: 8, c: 23861}, {f: 8, c: 23871}, {f: 2, c: 23880},
+ {f: 3, c: 23885}, {f: 7, c: 23889}, {f: 2, c: 23897}, 23900,
+ {f: 11, c: 23902}, 23914, {f: 2, c: 23917}, {f: 4, c: 23920},
+ {f: 12, c: 23925}, 23939, {f: 2, c: 23941}, {f: 15, c: 23944}, 23960,
+ {f: 3, c: 23962}, {f: 2, c: 23966}, {f: 6, c: 23969}, {f: 15, c: 23976},
+ 23993, 23995, {f: 8, c: 23997}, {f: 5, c: 24006}, 24012, {f: 4, c: 24014},
+ 24019, {f: 6, c: 24021}, 24028, {f: 2, c: 24031}, {f: 2, c: 24035}, 24042,
+ {f: 2, c: 24044}, {f: 2, c: 24053}, {f: 5, c: 24056}, {f: 2, c: 24063},
+ 24068, 24071, {f: 3, c: 24073}, {f: 2, c: 24077}, {f: 2, c: 24082}, 24087,
+ {f: 7, c: 24094}, {f: 3, c: 24104}, 24108, {f: 2, c: 24111}, 24114,
+ {f: 2, c: 24116}, {f: 2, c: 24121}, {f: 2, c: 24126}, 24129,
+ {f: 6, c: 24134}, {f: 7, c: 24141}, 24150, {f: 2, c: 24153},
+ {f: 2, c: 24156}, 24160, {f: 7, c: 24164}, {f: 5, c: 24173}, 24181, 24183,
+ {f: 3, c: 24193}, 24197, {f: 2, c: 24200}, {f: 3, c: 24204}, 24210, 24216,
+ 24219, 24221, {f: 4, c: 24225}, {f: 3, c: 24232}, 24236, {f: 5, c: 24238},
+ 24244, {f: 4, c: 24250}, {f: 10, c: 24255}, {f: 6, c: 24267},
+ {f: 2, c: 24276}, {f: 4, c: 24279}, {f: 3, c: 24284}, {f: 4, c: 24292},
+ 24297, 24299, {f: 6, c: 24301}, 24309, {f: 2, c: 24312}, {f: 3, c: 24315},
+ {f: 3, c: 24325}, 24329, {f: 3, c: 24332}, 24336, 24338, 24340, 24342,
+ {f: 2, c: 24345}, {f: 3, c: 24348}, {f: 4, c: 24353}, 24360,
+ {f: 2, c: 24363}, 24366, 24368, 24370, 24372, {f: 3, c: 24374}, 24379,
+ {f: 3, c: 24381}, {f: 5, c: 24385}, 24391, {f: 3, c: 24393}, 24397, 24399,
+ 24401, 24404, {f: 3, c: 24410}, {f: 3, c: 24414}, 24419, 24421,
+ {f: 2, c: 24423}, 24427, {f: 2, c: 24430}, 24434, {f: 3, c: 24436}, 24440,
+ 24442, {f: 3, c: 24445}, 24451, 24454, {f: 3, c: 24461}, {f: 2, c: 24467},
+ 24470, {f: 2, c: 24474}, 24477, 24479, {f: 6, c: 24482}, {f: 2, c: 24491},
+ {f: 6, c: 24495}, 24502, 24504, {f: 2, c: 24506}, {f: 5, c: 24510},
+ {f: 2, c: 24519}, {f: 2, c: 24522}, 24526, {f: 3, c: 24531},
+ {f: 3, c: 24538}, {f: 2, c: 24542}, {f: 2, c: 24546}, {f: 2, c: 24549},
+ {f: 2, c: 24552}, 24556, {f: 2, c: 24559}, {f: 3, c: 24562},
+ {f: 2, c: 24566}, {f: 2, c: 24569}, 24572, {f: 3, c: 24583},
+ {f: 2, c: 24587}, {f: 2, c: 24592}, 24595, {f: 2, c: 24599}, 24602,
+ {f: 2, c: 24606}, {f: 3, c: 24610}, {f: 3, c: 24620}, {f: 5, c: 24624},
+ {f: 5, c: 24630}, {f: 2, c: 24637}, 24640, {f: 7, c: 24644}, 24652,
+ {f: 2, c: 24654}, 24657, {f: 2, c: 24659}, {f: 3, c: 24662},
+ {f: 2, c: 24667}, {f: 4, c: 24670}, {f: 2, c: 24677}, 24686,
+ {f: 2, c: 24689}, {f: 2, c: 24692}, 24695, 24702, {f: 3, c: 24704},
+ {f: 4, c: 24709}, {f: 2, c: 24714}, {f: 4, c: 24718}, 24723, 24725,
+ {f: 3, c: 24727}, 24732, 24734, {f: 2, c: 24737}, {f: 2, c: 24740}, 24743,
+ {f: 2, c: 24745}, 24750, 24752, 24755, 24759, {f: 2, c: 24761},
+ {f: 8, c: 24765}, {f: 3, c: 24775}, {f: 5, c: 24780}, {f: 3, c: 24786},
+ {f: 2, c: 24790}, 24793, 24795, 24798, {f: 4, c: 24802}, 24810, 24821,
+ {f: 2, c: 24823}, {f: 4, c: 24828}, {f: 4, c: 24834}, 24839,
+ {f: 3, c: 24842}, {f: 5, c: 24848}, {f: 4, c: 24854}, {f: 2, c: 24861},
+ {f: 2, c: 24865}, 24869, {f: 3, c: 24872}, {f: 8, c: 24876},
+ {f: 2, c: 24885}, {f: 6, c: 24888}, {f: 8, c: 24896}, 24905, 24909,
+ {f: 2, c: 24911}, {f: 3, c: 24914}, {f: 2, c: 24918}, 24921,
+ {f: 2, c: 24923}, 24926, {f: 2, c: 24928}, {f: 2, c: 24933}, 24937,
+ {f: 2, c: 24940}, 24943, {f: 2, c: 24945}, 24948, {f: 10, c: 24952},
+ {f: 7, c: 24963}, {f: 2, c: 24972}, 24975, 24979, {f: 5, c: 24981},
+ {f: 2, c: 24987}, {f: 6, c: 24990}, {f: 2, c: 24997}, 25002, 25005,
+ {f: 3, c: 25007}, {f: 3, c: 25011}, {f: 6, c: 25016}, {f: 3, c: 25023},
+ {f: 4, c: 25027}, {f: 4, c: 25037}, 25043, {f: 9, c: 25045},
+ {f: 3, c: 25056}, {f: 2, c: 25060}, 25063, {f: 9, c: 25065},
+ {f: 2, c: 25075}, 25081, 25083, 25085, {f: 5, c: 25089}, 25097, 25107,
+ 25113, {f: 3, c: 25116}, 25120, 25123, 25126, {f: 2, c: 25128}, 25131,
+ 25133, 25135, 25137, 25141, [12094, 25142], {f: 5, c: 25144}, 25154,
+ {f: 3, c: 25156}, 25162, {f: 2, c: 25167}, {f: 3, c: 25173},
+ {f: 2, c: 25177}, {f: 7, c: 25180}, {f: 2, c: 25188}, 25192,
+ {f: 2, c: 25201}, {f: 2, c: 25204}, {f: 2, c: 25207}, {f: 2, c: 25210},
+ 25213, {f: 3, c: 25217}, {f: 4, c: 25221}, {f: 6, c: 25227}, 25236, 25241,
+ {f: 3, c: 25244}, 25251, {f: 2, c: 25254}, {f: 2, c: 25257},
+ {f: 4, c: 25261}, {f: 3, c: 25266}, {f: 3, c: 25270}, 25274, 25278,
+ {f: 2, c: 25280}, 25283, 25291, 25295, 25297, 25301, {f: 2, c: 25309},
+ {f: 2, c: 25312}, 25316, {f: 2, c: 25322}, 25328, 25330, 25333,
+ {f: 4, c: 25336}, 25344, {f: 4, c: 25347}, {f: 4, c: 25354},
+ {f: 2, c: 25359}, {f: 4, c: 25362}, {f: 3, c: 25367}, 25372,
+ {f: 2, c: 25382}, 25385, {f: 3, c: 25388}, {f: 2, c: 25392},
+ {f: 6, c: 25395}, {f: 2, c: 25403}, {f: 3, c: 25407}, 25412,
+ {f: 2, c: 25415}, 25418, {f: 4, c: 25425}, {f: 8, c: 25430}, 25440,
+ {f: 3, c: 25444}, 25450, 25452, {f: 2, c: 25455}, {f: 3, c: 25459},
+ {f: 2, c: 25464}, {f: 4, c: 25468}, 25473, {f: 2, c: 25477}, 25483, 25485,
+ 25489, {f: 3, c: 25491}, 25495, {f: 7, c: 25497}, 25505, 25508, 25510,
+ 25515, 25519, {f: 2, c: 25521}, {f: 2, c: 25525}, 25529, 25531, 25533,
+ 25535, {f: 3, c: 25537}, 25541, {f: 2, c: 25543}, {f: 3, c: 25546}, 25553,
+ {f: 3, c: 25555}, {f: 3, c: 25559}, {f: 3, c: 25563}, 25567, 25570,
+ {f: 5, c: 25572}, {f: 2, c: 25579}, {f: 3, c: 25583}, 25587, 25589, 25591,
+ {f: 4, c: 25593}, 25598, {f: 2, c: 25603}, {f: 5, c: 25606}, 25614,
+ {f: 2, c: 25617}, {f: 2, c: 25621}, {f: 3, c: 25624}, 25629, 25631,
+ {f: 4, c: 25634}, {f: 3, c: 25639}, 25643, {f: 6, c: 25646}, 25653,
+ {f: 3, c: 25655}, {f: 2, c: 25659}, 25662, 25664, {f: 2, c: 25666}, 25673,
+ {f: 6, c: 25675}, 25683, {f: 3, c: 25685}, {f: 3, c: 25689}, 25693,
+ {f: 7, c: 25696}, 25704, {f: 3, c: 25706}, 25710, {f: 3, c: 25712},
+ {f: 2, c: 25716}, 25719, {f: 6, c: 25724}, 25731, 25734, {f: 8, c: 25737},
+ 25748, {f: 2, c: 25751}, {f: 4, c: 25754}, {f: 3, c: 25760},
+ {f: 3, c: 25766}, 25770, 25775, 25777, 25780, 25782, 25785, 25789, 25795,
+ 25798, {f: 2, c: 25800}, 25804, 25807, 25809, 25811, {f: 2, c: 25813},
+ 25817, {f: 3, c: 25819}, 25823, 25825, 25827, 25829, {f: 5, c: 25831},
+ {f: 2, c: 25837}, 25843, {f: 2, c: 25845}, {f: 2, c: 25848}, 25853, 25855,
+ {f: 3, c: 25857}, 25861, {f: 2, c: 25863}, {f: 5, c: 25866},
+ {f: 2, c: 25872}, 25875, 25877, 25879, 25882, 25884, {f: 4, c: 25886},
+ {f: 4, c: 25894}, 25901, {f: 4, c: 25904}, 25911, 25914, {f: 2, c: 25916},
+ {f: 5, c: 25920}, {f: 2, c: 25926}, {f: 2, c: 25930}, {f: 2, c: 25933},
+ 25936, {f: 3, c: 25938}, 25944, 25946, 25948, {f: 3, c: 25951},
+ {f: 2, c: 25956}, {f: 4, c: 25959}, {f: 3, c: 25965}, 25969, 25971, 25974,
+ {f: 9, c: 25977}, {f: 3, c: 25988}, {f: 3, c: 25992}, {f: 3, c: 25997},
+ 26002, 26004, 26006, 26008, 26010, {f: 2, c: 26013}, 26016,
+ {f: 2, c: 26018}, 26022, 26024, 26026, 26030, {f: 6, c: 26033}, 26040,
+ {f: 2, c: 26042}, {f: 3, c: 26046}, 26050, {f: 4, c: 26055}, 26061,
+ {f: 2, c: 26064}, {f: 3, c: 26067}, {f: 8, c: 26072}, 26081,
+ {f: 2, c: 26083}, {f: 2, c: 26090}, {f: 4, c: 26098}, {f: 2, c: 26104},
+ {f: 5, c: 26107}, 26113, {f: 2, c: 26116}, {f: 3, c: 26119}, 26123, 26125,
+ {f: 3, c: 26128}, {f: 3, c: 26134}, {f: 3, c: 26138}, 26142,
+ {f: 4, c: 26145}, 26150, {f: 4, c: 26153}, 26158, 26160, {f: 2, c: 26162},
+ {f: 5, c: 26167}, 26173, {f: 2, c: 26175}, {f: 7, c: 26180},
+ {f: 2, c: 26189}, {f: 2, c: 26192}, {f: 2, c: 26200}, {f: 2, c: 26203},
+ 26206, 26208, {f: 2, c: 26210}, 26213, 26215, {f: 5, c: 26217},
+ {f: 3, c: 26225}, 26229, {f: 2, c: 26232}, {f: 3, c: 26235},
+ {f: 3, c: 26239}, 26243, {f: 2, c: 26245}, {f: 2, c: 26250},
+ {f: 4, c: 26253}, {f: 4, c: 26258}, {f: 5, c: 26264}, {f: 4, c: 26270},
+ {f: 4, c: 26275}, {f: 2, c: 26281}, {f: 2, c: 26284}, {f: 5, c: 26287},
+ {f: 4, c: 26293}, {f: 4, c: 26298}, {f: 5, c: 26303}, 26309, 26312,
+ {f: 12, c: 26314}, {f: 2, c: 26327}, 26330, {f: 2, c: 26334},
+ {f: 5, c: 26337}, {f: 2, c: 26343}, {f: 2, c: 26346}, {f: 3, c: 26349},
+ 26353, {f: 2, c: 26357}, {f: 2, c: 26362}, 26365, {f: 2, c: 26369},
+ {f: 4, c: 26372}, 26380, {f: 2, c: 26382}, {f: 3, c: 26385}, 26390,
+ {f: 3, c: 26392}, 26396, 26398, {f: 6, c: 26400}, 26409, 26414, 26416,
+ {f: 2, c: 26418}, {f: 4, c: 26422}, {f: 2, c: 26427}, {f: 2, c: 26430},
+ 26433, {f: 2, c: 26436}, 26439, {f: 2, c: 26442}, 26445, 26450,
+ {f: 2, c: 26452}, {f: 5, c: 26455}, 26461, {f: 3, c: 26466},
+ {f: 2, c: 26470}, {f: 2, c: 26475}, 26478, 26484, 26486, {f: 4, c: 26488},
+ 26493, 26496, {f: 2, c: 26498}, {f: 2, c: 26501}, 26504, 26506,
+ {f: 4, c: 26508}, {f: 4, c: 26513}, 26518, 26521, 26523, {f: 3, c: 26527},
+ 26532, 26534, 26537, 26540, 26542, {f: 2, c: 26545}, 26548,
+ {f: 8, c: 26553}, 26562, {f: 10, c: 26565}, {f: 3, c: 26581}, 26587, 26591,
+ 26593, {f: 2, c: 26595}, {f: 3, c: 26598}, {f: 2, c: 26602},
+ {f: 2, c: 26605}, 26610, {f: 8, c: 26613}, 26622, {f: 4, c: 26625}, 26630,
+ 26637, 26640, 26642, {f: 2, c: 26644}, {f: 5, c: 26648}, {f: 3, c: 26654},
+ {f: 7, c: 26658}, {f: 7, c: 26667}, {f: 3, c: 26676}, {f: 2, c: 26682},
+ 26687, 26695, 26699, 26701, 26703, 26706, {f: 10, c: 26710}, 26730,
+ {f: 8, c: 26732}, 26741, {f: 9, c: 26744}, 26754, 26756, {f: 8, c: 26759},
+ {f: 3, c: 26768}, {f: 3, c: 26772}, {f: 4, c: 26777}, 26782,
+ {f: 2, c: 26784}, {f: 3, c: 26787}, {f: 4, c: 26793}, 26798,
+ {f: 2, c: 26801}, 26804, {f: 10, c: 26806}, 26817, {f: 6, c: 26819}, 26826,
+ 26828, {f: 4, c: 26830}, {f: 2, c: 26835}, 26841, {f: 4, c: 26843},
+ {f: 2, c: 26849}, {f: 3, c: 26852}, {f: 6, c: 26856}, 26863,
+ {f: 3, c: 26866}, {f: 3, c: 26870}, 26875, {f: 4, c: 26877},
+ {f: 3, c: 26882}, {f: 5, c: 26886}, 26892, 26897, {f: 12, c: 26899},
+ {f: 3, c: 26913}, {f: 8, c: 26917}, {f: 2, c: 26926}, {f: 3, c: 26929},
+ {f: 4, c: 26933}, {f: 3, c: 26938}, 26942, {f: 2, c: 26944},
+ {f: 7, c: 26947}, {f: 8, c: 26955}, {f: 2, c: 26965}, {f: 2, c: 26968},
+ {f: 2, c: 26971}, 26975, {f: 2, c: 26977}, {f: 2, c: 26980}, 26983,
+ {f: 2, c: 26985}, 26988, {f: 2, c: 26991}, {f: 3, c: 26994}, 26998,
+ {f: 2, c: 27002}, {f: 3, c: 27005}, 27009, 27011, 27013, {f: 3, c: 27018},
+ {f: 6, c: 27022}, {f: 2, c: 27030}, {f: 2, c: 27033}, {f: 10, c: 27037},
+ 27049, 27052, {f: 2, c: 27055}, {f: 2, c: 27058}, {f: 2, c: 27061},
+ {f: 3, c: 27064}, {f: 3, c: 27068}, 27072, {f: 8, c: 27074}, 27087,
+ {f: 3, c: 27089}, {f: 6, c: 27093}, {f: 3, c: 27100}, {f: 6, c: 27105},
+ {f: 5, c: 27112}, {f: 4, c: 27118}, {f: 9, c: 27124}, 27134, 27136,
+ {f: 2, c: 27139}, {f: 4, c: 27142}, {f: 8, c: 27147}, {f: 3, c: 27156},
+ {f: 4, c: 27162}, 27168, 27170, {f: 4, c: 27172}, 27177, {f: 4, c: 27179},
+ 27184, {f: 3, c: 27186}, {f: 2, c: 27190}, {f: 2, c: 27195},
+ {f: 5, c: 27199}, {f: 2, c: 27205}, {f: 2, c: 27209}, {f: 4, c: 27212},
+ {f: 7, c: 27217}, 27226, {f: 3, c: 27228}, 27232, {f: 2, c: 27235},
+ {f: 11, c: 27238}, {f: 7, c: 27250}, {f: 2, c: 27258}, {f: 3, c: 27261},
+ {f: 3, c: 27265}, {f: 4, c: 27269}, {f: 4, c: 27274}, 27279,
+ {f: 2, c: 27282}, {f: 2, c: 27285}, {f: 4, c: 27288}, {f: 3, c: 27293},
+ 27297, {f: 5, c: 27300}, 27306, {f: 2, c: 27309}, {f: 3, c: 27312},
+ {f: 4, c: 27316}, {f: 2, c: 27321}, {f: 7, c: 27324}, {f: 15, c: 27332},
+ {f: 6, c: 27348}, 27356, {f: 7, c: 27360}, 27369, 27371, {f: 6, c: 27373},
+ {f: 4, c: 27380}, {f: 2, c: 27385}, {f: 8, c: 27388}, {f: 5, c: 27397},
+ {f: 4, c: 27403}, {f: 2, c: 27408}, {f: 3, c: 27411}, {f: 7, c: 27415},
+ 27423, {f: 2, c: 27429}, {f: 10, c: 27432}, {f: 4, c: 27443}, 27448,
+ {f: 2, c: 27451}, {f: 4, c: 27455}, {f: 2, c: 27460}, 27464,
+ {f: 2, c: 27466}, {f: 3, c: 27469}, {f: 8, c: 27473}, {f: 5, c: 27482},
+ 27488, {f: 2, c: 27496}, {f: 7, c: 27499}, {f: 4, c: 27507}, 27514,
+ {f: 4, c: 27517}, 27525, 27528, 27532, {f: 4, c: 27534}, {f: 2, c: 27540},
+ 27543, 27545, {f: 2, c: 27548}, {f: 2, c: 27551}, {f: 2, c: 27554},
+ {f: 5, c: 27557}, {f: 2, c: 27564}, {f: 2, c: 27568}, 27574,
+ {f: 2, c: 27576}, {f: 3, c: 27580}, 27584, {f: 2, c: 27587},
+ {f: 4, c: 27591}, 27596, 27598, {f: 2, c: 27600}, 27608, 27610,
+ {f: 5, c: 27612}, {f: 8, c: 27618}, {f: 3, c: 27628}, {f: 3, c: 27632},
+ 27636, {f: 3, c: 27638}, {f: 3, c: 27642}, 27646, {f: 5, c: 27648},
+ {f: 3, c: 27657}, 27662, 27666, 27671, {f: 3, c: 27676}, 27680, 27685,
+ 27693, 27697, 27699, {f: 2, c: 27702}, {f: 4, c: 27705}, {f: 2, c: 27710},
+ {f: 3, c: 27715}, 27720, {f: 5, c: 27723}, {f: 3, c: 27729}, 27734,
+ {f: 3, c: 27736}, {f: 2, c: 27746}, {f: 3, c: 27749}, {f: 5, c: 27755},
+ 27761, 27763, 27765, {f: 2, c: 27767}, {f: 3, c: 27770}, {f: 2, c: 27775},
+ 27780, 27783, {f: 2, c: 27786}, {f: 2, c: 27789}, {f: 2, c: 27793},
+ {f: 4, c: 27797}, 27802, {f: 3, c: 27804}, 27808, 27810, 27816, 27820,
+ {f: 2, c: 27823}, {f: 4, c: 27828}, 27834, {f: 4, c: 27840},
+ {f: 3, c: 27846}, 27851, {f: 3, c: 27853}, {f: 2, c: 27857},
+ {f: 3, c: 27864}, {f: 2, c: 27868}, 27871, 27876, {f: 2, c: 27878}, 27881,
+ {f: 2, c: 27884}, 27890, 27892, 27897, {f: 2, c: 27903}, {f: 2, c: 27906},
+ {f: 2, c: 27909}, {f: 3, c: 27912}, 27917, {f: 3, c: 27919},
+ {f: 4, c: 27923}, 27928, {f: 2, c: 27932}, {f: 6, c: 27935}, 27942,
+ {f: 2, c: 27944}, {f: 2, c: 27948}, {f: 2, c: 27951}, 27956,
+ {f: 3, c: 27958}, 27962, {f: 2, c: 27967}, 27970, 27972, 27977, 27980,
+ 27984, {f: 4, c: 27989}, 27995, 27997, 27999, {f: 2, c: 28001},
+ {f: 2, c: 28004}, {f: 2, c: 28007}, {f: 3, c: 28011}, {f: 4, c: 28016},
+ {f: 2, c: 28021}, {f: 2, c: 28026}, {f: 5, c: 28029}, {f: 2, c: 28035},
+ 28038, {f: 2, c: 28042}, 28045, {f: 2, c: 28047}, 28050, {f: 5, c: 28054},
+ 28060, 28066, 28069, {f: 2, c: 28076}, {f: 2, c: 28080}, {f: 2, c: 28083},
+ {f: 2, c: 28086}, {f: 6, c: 28089}, {f: 3, c: 28097}, {f: 3, c: 28104},
+ {f: 4, c: 28109}, {f: 4, c: 28114}, 28119, {f: 3, c: 28122}, 28127,
+ {f: 2, c: 28130}, 28133, {f: 3, c: 28135}, 28141, {f: 2, c: 28143}, 28146,
+ 28148, 28152, {f: 8, c: 28157}, {f: 4, c: 28166}, 28171, 28175,
+ {f: 2, c: 28178}, 28181, {f: 2, c: 28184}, {f: 2, c: 28187},
+ {f: 2, c: 28190}, 28194, {f: 2, c: 28199}, 28202, 28206, {f: 2, c: 28208},
+ 28211, {f: 3, c: 28213}, 28217, {f: 3, c: 28219}, {f: 4, c: 28223},
+ {f: 8, c: 28229}, {f: 4, c: 28239}, 28245, 28247, {f: 2, c: 28249},
+ {f: 2, c: 28252}, {f: 11, c: 28256}, {f: 2, c: 28268}, {f: 14, c: 28272},
+ {f: 3, c: 28288}, 28292, {f: 2, c: 28295}, {f: 5, c: 28298},
+ {f: 5, c: 28305}, 28311, {f: 3, c: 28313}, 28318, {f: 2, c: 28320},
+ {f: 2, c: 28323}, 28326, {f: 2, c: 28328}, {f: 4, c: 28331}, 28336, 28339,
+ 28341, {f: 2, c: 28344}, 28348, {f: 3, c: 28350}, 28355, 28358,
+ {f: 3, c: 28360}, 28365, 28368, 28370, 28374, {f: 2, c: 28376},
+ {f: 3, c: 28379}, 28387, 28391, {f: 2, c: 28394}, {f: 2, c: 28397},
+ {f: 2, c: 28400}, 28403, {f: 2, c: 28405}, {f: 5, c: 28410}, 28416,
+ {f: 3, c: 28419}, {f: 2, c: 28423}, {f: 5, c: 28426}, {f: 3, c: 28432},
+ {f: 4, c: 28438}, {f: 5, c: 28443}, 28449, {f: 4, c: 28453}, 28462, 28464,
+ {f: 2, c: 28468}, 28471, {f: 5, c: 28473}, 28480, {f: 4, c: 28482},
+ {f: 3, c: 28488}, 28492, {f: 3, c: 28494}, {f: 2, c: 28498},
+ {f: 3, c: 28501}, {f: 2, c: 28506}, 28509, {f: 3, c: 28511}, 28515, 28517,
+ {f: 6, c: 28519}, 28529, 28531, {f: 2, c: 28533}, 28537, 28539,
+ {f: 2, c: 28541}, {f: 3, c: 28545}, 28549, {f: 2, c: 28554},
+ {f: 8, c: 28559}, {f: 4, c: 28568}, {f: 3, c: 28573}, {f: 2, c: 28578},
+ {f: 2, c: 28581}, 28584, {f: 4, c: 28586}, {f: 2, c: 28591}, 28594,
+ {f: 2, c: 28596}, {f: 2, c: 28599}, {f: 6, c: 28602}, {f: 5, c: 28612},
+ {f: 7, c: 28618}, {f: 2, c: 28627}, {f: 2, c: 28630}, {f: 2, c: 28633},
+ {f: 2, c: 28636}, {f: 2, c: 28642}, {f: 6, c: 28645}, {f: 2, c: 28652},
+ {f: 8, c: 28658}, 28667, 28669, {f: 6, c: 28671}, {f: 2, c: 28679}, 28682,
+ {f: 3, c: 28684}, 28688, {f: 3, c: 28690}, {f: 2, c: 28694}, 28697, 28700,
+ 28702, {f: 2, c: 28705}, {f: 3, c: 28708}, {f: 7, c: 28713}, 28721,
+ {f: 2, c: 28723}, {f: 3, c: 28726}, {f: 4, c: 28730}, {f: 4, c: 28735},
+ {f: 7, c: 28741}, {f: 2, c: 28749}, 28752, {f: 3, c: 28754},
+ {f: 2, c: 28758}, {f: 4, c: 28761}, {f: 4, c: 28767}, {f: 2, c: 28773},
+ {f: 3, c: 28776}, 28782, {f: 4, c: 28785}, 28791, {f: 3, c: 28793}, 28797,
+ {f: 4, c: 28801}, {f: 3, c: 28806}, {f: 3, c: 28811}, {f: 3, c: 28815},
+ 28819, {f: 2, c: 28823}, {f: 2, c: 28826}, {f: 13, c: 28830}, 28848, 28850,
+ {f: 3, c: 28852}, 28858, {f: 2, c: 28862}, {f: 4, c: 28868}, 28873,
+ {f: 4, c: 28875}, {f: 8, c: 28880}, 28890, {f: 3, c: 28892},
+ {f: 4, c: 28896}, 28901, 28906, 28910, {f: 4, c: 28912}, {f: 2, c: 28917},
+ 28920, {f: 3, c: 28922}, {f: 11, c: 28926}, {f: 5, c: 28939},
+ {f: 2, c: 28945}, 28948, 28951, {f: 6, c: 28955}, {f: 4, c: 28962},
+ {f: 8, c: 28967}, {f: 4, c: 28978}, {f: 14, c: 28983}, {f: 3, c: 28998},
+ 29003, 29005, {f: 3, c: 29007}, {f: 9, c: 29011}, 29021, {f: 3, c: 29023},
+ 29027, 29029, {f: 2, c: 29034}, 29037, {f: 3, c: 29039}, {f: 4, c: 29044},
+ 29049, {f: 2, c: 29051}, {f: 6, c: 29054}, {f: 5, c: 29061},
+ {f: 4, c: 29067}, {f: 2, c: 29072}, 29075, {f: 2, c: 29077},
+ {f: 5, c: 29082}, {f: 7, c: 29089}, {f: 3, c: 29097}, {f: 4, c: 29101},
+ 29106, 29108, {f: 3, c: 29110}, {f: 4, c: 29114}, {f: 2, c: 29119}, 29122,
+ {f: 4, c: 29124}, {f: 5, c: 29129}, {f: 3, c: 29135}, 29139,
+ {f: 3, c: 29142}, {f: 2, c: 29146}, {f: 2, c: 29149}, {f: 4, c: 29153},
+ {f: 5, c: 29160}, {f: 5, c: 29167}, {f: 4, c: 29173}, {f: 2, c: 29178},
+ 29181, {f: 7, c: 29183}, {f: 6, c: 29191}, {f: 2, c: 29198},
+ {f: 10, c: 29201}, 29212, {f: 10, c: 29214}, 29225, 29227,
+ {f: 3, c: 29229}, {f: 2, c: 29235}, 29244, {f: 7, c: 29248},
+ {f: 3, c: 29257}, {f: 4, c: 29262}, {f: 3, c: 29267}, 29271, 29274, 29276,
+ 29278, 29280, {f: 3, c: 29283}, 29288, {f: 4, c: 29290}, {f: 2, c: 29296},
+ {f: 2, c: 29299}, {f: 3, c: 29302}, {f: 2, c: 29307}, {f: 2, c: 29314},
+ {f: 5, c: 29317}, 29324, 29326, {f: 2, c: 29328}, {f: 3, c: 29331},
+ {f: 8, c: 29335}, {f: 2, c: 29344}, {f: 4, c: 29347}, {f: 4, c: 29352},
+ 29358, {f: 3, c: 29361}, 29365, {f: 6, c: 29370}, {f: 3, c: 29381},
+ {f: 4, c: 29385}, 29391, 29393, {f: 4, c: 29395}, 29400, {f: 4, c: 29402},
+ 29407, {f: 6, c: 29410}, {f: 2, c: 29418}, {f: 2, c: 29429},
+ {f: 3, c: 29438}, 29442, {f: 6, c: 29444}, {f: 3, c: 29451},
+ {f: 4, c: 29455}, 29460, {f: 3, c: 29464}, {f: 2, c: 29471},
+ {f: 2, c: 29475}, {f: 3, c: 29478}, 29485, {f: 2, c: 29487},
+ {f: 2, c: 29490}, 29493, 29498, {f: 2, c: 29500}, 29504, {f: 2, c: 29506},
+ {f: 7, c: 29510}, {f: 2, c: 29518}, 29521, {f: 4, c: 29523},
+ {f: 8, c: 29528}, {f: 7, c: 29537}, 29545, 29550, 29553, {f: 2, c: 29555},
+ 29558, 29561, 29565, 29567, {f: 3, c: 29569}, {f: 2, c: 29573}, 29576,
+ 29578, {f: 2, c: 29580}, {f: 2, c: 29583}, {f: 4, c: 29586},
+ {f: 4, c: 29591}, {f: 3, c: 29596}, {f: 2, c: 29600}, {f: 6, c: 29603},
+ 29610, {f: 2, c: 29612}, 29617, {f: 3, c: 29620}, {f: 2, c: 29624},
+ {f: 4, c: 29628}, 29633, {f: 5, c: 29635}, {f: 2, c: 29643}, 29646,
+ {f: 7, c: 29650}, {f: 4, c: 29658}, 29663, {f: 4, c: 29665}, 29670, 29672,
+ {f: 3, c: 29674}, {f: 4, c: 29678}, {f: 11, c: 29683}, {f: 4, c: 29695},
+ 29700, {f: 2, c: 29703}, {f: 4, c: 29707}, {f: 9, c: 29713},
+ {f: 6, c: 29724}, {f: 2, c: 29731}, 29735, 29737, 29739, 29741, 29743,
+ {f: 2, c: 29745}, {f: 5, c: 29751}, {f: 2, c: 29757}, 29760,
+ {f: 9, c: 29762}, {f: 9, c: 29772}, 29782, 29784, 29789, {f: 3, c: 29792},
+ {f: 5, c: 29796}, {f: 2, c: 29803}, {f: 2, c: 29806}, {f: 5, c: 29809},
+ {f: 6, c: 29816}, 29823, 29826, {f: 3, c: 29828}, 29832, 29834,
+ {f: 2, c: 29836}, 29839, {f: 11, c: 29841}, 29853, {f: 4, c: 29855},
+ {f: 2, c: 29860}, {f: 6, c: 29866}, {f: 9, c: 29873}, {f: 2, c: 29883},
+ {f: 12, c: 29886}, {f: 4, c: 29899}, {f: 2, c: 29904}, 29907,
+ {f: 5, c: 29909}, 29915, 29917, 29919, 29921, 29925, {f: 7, c: 29927},
+ {f: 4, c: 29936}, 29941, {f: 7, c: 29944}, {f: 4, c: 29952},
+ {f: 7, c: 29957}, 29966, 29968, 29970, {f: 4, c: 29972}, 29979,
+ {f: 2, c: 29981}, {f: 3, c: 29984}, 29988, {f: 2, c: 29990}, 29994, 29998,
+ 30004, 30006, 30009, {f: 2, c: 30012}, 30015, {f: 4, c: 30017},
+ {f: 2, c: 30022}, {f: 2, c: 30025}, 30029, {f: 4, c: 30032},
+ {f: 4, c: 30037}, {f: 4, c: 30046}, {f: 2, c: 30051}, {f: 3, c: 30055},
+ {f: 6, c: 30060}, 30067, 30069, 30071, {f: 5, c: 30074}, {f: 3, c: 30080},
+ {f: 2, c: 30084}, {f: 3, c: 30088}, {f: 3, c: 30092}, 30096, 30099, 30101,
+ 30104, {f: 2, c: 30107}, 30110, 30114, {f: 5, c: 30118}, 30125,
+ {f: 2, c: 30134}, {f: 2, c: 30138}, {f: 3, c: 30143}, 30150,
+ {f: 2, c: 30155}, {f: 4, c: 30158}, 30163, 30167, 30170, {f: 2, c: 30172},
+ {f: 3, c: 30175}, 30181, 30185, {f: 4, c: 30188}, {f: 2, c: 30194},
+ {f: 4, c: 30197}, {f: 2, c: 30202}, {f: 2, c: 30205}, 30212,
+ {f: 4, c: 30214}, {f: 2, c: 30222}, {f: 4, c: 30225}, 30230, 30234,
+ {f: 2, c: 30236}, 30243, 30248, 30252, {f: 2, c: 30254}, {f: 2, c: 30257},
+ {f: 2, c: 30262}, {f: 2, c: 30265}, 30269, 30273, {f: 2, c: 30276}, 30280,
+ {f: 2, c: 30282}, {f: 6, c: 30286}, 30293, 30295, {f: 3, c: 30297}, 30301,
+ {f: 2, c: 30304}, 30310, 30312, 30314, {f: 3, c: 30323}, [12136, 30326],
+ 30327, {f: 2, c: 30329}, {f: 3, c: 30335}, 30339, 30341, {f: 2, c: 30345},
+ {f: 2, c: 30348}, {f: 2, c: 30351}, 30354, {f: 2, c: 30356},
+ {f: 2, c: 30359}, {f: 9, c: 30363}, {f: 9, c: 30373}, {f: 2, c: 30383},
+ 30387, {f: 3, c: 30389}, 30393, {f: 4, c: 30395}, {f: 2, c: 30400},
+ {f: 2, c: 30403}, 30407, 30409, {f: 2, c: 30411}, 30419, 30421,
+ {f: 2, c: 30425}, {f: 2, c: 30428}, 30432, 30434, 30438, {f: 6, c: 30440},
+ 30448, 30451, {f: 3, c: 30453}, {f: 2, c: 30458}, 30461, {f: 2, c: 30463},
+ {f: 2, c: 30466}, {f: 2, c: 30469}, 30474, 30476, {f: 11, c: 30478},
+ {f: 4, c: 30491}, 30497, {f: 3, c: 30499}, 30503, {f: 3, c: 30506}, 30510,
+ {f: 5, c: 30512}, 30521, 30523, {f: 3, c: 30525}, 30530, {f: 3, c: 30532},
+ {f: 7, c: 30536}, {f: 8, c: 30546}, {f: 2, c: 30556}, {f: 2, c: 30559},
+ 30564, 30567, {f: 2, c: 30569}, {f: 12, c: 30573}, {f: 3, c: 30586},
+ {f: 3, c: 30593}, {f: 6, c: 30598}, {f: 2, c: 30607}, {f: 5, c: 30611},
+ {f: 5, c: 30617}, 30625, {f: 2, c: 30627}, 30630, 30632, 30635,
+ {f: 2, c: 30638}, {f: 2, c: 30641}, 30644, {f: 5, c: 30646}, 30654,
+ {f: 7, c: 30656}, {f: 5, c: 30664}, {f: 9, c: 30670}, {f: 2, c: 30680},
+ {f: 5, c: 30685}, 30692, 30694, 30696, 30698, {f: 3, c: 30704},
+ {f: 2, c: 30708}, 30711, {f: 4, c: 30713}, {f: 6, c: 30723},
+ {f: 2, c: 30730}, {f: 3, c: 30734}, 30739, 30741, 30745, 30747, 30750,
+ {f: 3, c: 30752}, 30756, 30760, {f: 2, c: 30762}, {f: 2, c: 30766},
+ {f: 3, c: 30769}, {f: 2, c: 30773}, 30781, 30783, {f: 2, c: 30785}, 30788,
+ 30790, {f: 4, c: 30792}, 30797, 30799, 30801, {f: 2, c: 30803},
+ {f: 5, c: 30808}, {f: 6, c: 30814}, {f: 3, c: 30821}, 30825,
+ {f: 7, c: 30832}, {f: 4, c: 30840}, {f: 10, c: 30845}, 30856,
+ {f: 2, c: 30858}, {f: 2, c: 30863}, 30866, {f: 3, c: 30868}, 30873,
+ {f: 2, c: 30877}, 30880, 30882, 30884, 30886, 30888, {f: 3, c: 30890},
+ {f: 2, c: 30894}, {f: 3, c: 30901}, 30907, 30909, {f: 2, c: 30911},
+ {f: 3, c: 30914}, {f: 3, c: 30918}, {f: 4, c: 30924}, {f: 3, c: 30929},
+ {f: 3, c: 30934}, {f: 8, c: 30939}, {f: 3, c: 30948}, {f: 3, c: 30953},
+ {f: 2, c: 30957}, {f: 2, c: 30960}, 30963, {f: 2, c: 30965},
+ {f: 2, c: 30968}, {f: 2, c: 30971}, {f: 3, c: 30974}, {f: 3, c: 30978},
+ {f: 8, c: 30982}, {f: 4, c: 30991}, {f: 5, c: 30996}, {f: 4, c: 31002},
+ {f: 5, c: 31007}, 31013, {f: 3, c: 31015}, {f: 4, c: 31021},
+ {f: 2, c: 31026}, {f: 5, c: 31029}, 31037, 31039, {f: 4, c: 31042}, 31047,
+ {f: 9, c: 31050}, {f: 2, c: 31060}, {f: 2, c: 31064}, 31073,
+ {f: 2, c: 31075}, 31078, {f: 4, c: 31081}, 31086, {f: 7, c: 31088}, 31097,
+ {f: 5, c: 31099}, {f: 2, c: 31106}, {f: 4, c: 31110}, {f: 2, c: 31115},
+ {f: 10, c: 31120}, {f: 11, c: 31131}, {f: 2, c: 31144}, {f: 3, c: 31147},
+ 31151, 31154, {f: 4, c: 31156}, [12145, 31160], 31164, 31167, 31170,
+ {f: 2, c: 31172}, {f: 2, c: 31175}, 31178, 31180, {f: 3, c: 31182},
+ {f: 2, c: 31187}, {f: 2, c: 31190}, {f: 6, c: 31193}, {f: 3, c: 31200},
+ 31205, 31208, 31210, 31212, 31214, {f: 7, c: 31217}, {f: 2, c: 31225},
+ 31228, {f: 2, c: 31230}, 31233, {f: 2, c: 31236}, {f: 4, c: 31239}, 31244,
+ {f: 5, c: 31247}, {f: 2, c: 31253}, {f: 2, c: 31256}, {f: 3, c: 31259},
+ 31263, {f: 2, c: 31265}, {f: 10, c: 31268}, {f: 2, c: 31279}, 31282,
+ {f: 3, c: 31284}, 31288, 31290, 31294, {f: 5, c: 31297}, {f: 5, c: 31303},
+ {f: 2, c: 31311}, {f: 5, c: 31314}, {f: 9, c: 31320}, {f: 6, c: 31331},
+ 31338, {f: 4, c: 31340}, {f: 3, c: 31345}, 31349, {f: 4, c: 31355}, 31362,
+ 31365, 31367, {f: 4, c: 31369}, {f: 3, c: 31374}, {f: 2, c: 31379},
+ {f: 3, c: 31385}, 31390, {f: 4, c: 31393}, 31399, 31403, {f: 4, c: 31407},
+ {f: 2, c: 31412}, {f: 3, c: 31415}, {f: 4, c: 31419}, {f: 4, c: 31424},
+ 31430, 31433, {f: 10, c: 31436}, {f: 2, c: 31447}, {f: 4, c: 31450},
+ {f: 2, c: 31457}, 31460, {f: 3, c: 31463}, {f: 2, c: 31467}, 31470,
+ {f: 6, c: 31472}, {f: 2, c: 31479}, {f: 2, c: 31483}, 31486,
+ {f: 3, c: 31488}, 31493, 31495, 31497, {f: 3, c: 31500}, 31504,
+ {f: 2, c: 31506}, {f: 3, c: 31510}, 31514, {f: 2, c: 31516}, 31519,
+ {f: 3, c: 31521}, 31527, 31529, 31533, {f: 2, c: 31535}, 31538,
+ {f: 4, c: 31540}, 31545, 31547, 31549, {f: 6, c: 31551}, 31560, 31562,
+ {f: 2, c: 31565}, 31571, 31573, 31575, 31577, 31580, {f: 2, c: 31582},
+ 31585, {f: 4, c: 31587}, {f: 6, c: 31592}, {f: 2, c: 31599},
+ {f: 2, c: 31603}, 31606, 31608, 31610, {f: 2, c: 31612}, 31615,
+ {f: 4, c: 31617}, {f: 5, c: 31622}, 31628, {f: 2, c: 31630},
+ {f: 3, c: 31633}, 31638, {f: 4, c: 31640}, {f: 3, c: 31646},
+ {f: 3, c: 31651}, {f: 3, c: 31662}, {f: 2, c: 31666}, {f: 3, c: 31669},
+ {f: 7, c: 31673}, {f: 2, c: 31682}, 31685, 31688, 31690, {f: 4, c: 31693},
+ 31698, {f: 5, c: 31700}, {f: 2, c: 31707}, {f: 3, c: 31710},
+ {f: 2, c: 31714}, {f: 2, c: 31719}, {f: 3, c: 31723}, {f: 2, c: 31727},
+ 31730, {f: 3, c: 31732}, {f: 4, c: 31736}, 31741, 31743, {f: 6, c: 31745},
+ {f: 3, c: 31752}, 31758, {f: 6, c: 31760}, {f: 7, c: 31767}, 31776, 31778,
+ {f: 2, c: 31780}, {f: 2, c: 31784}, {f: 12, c: 31788}, {f: 4, c: 31801},
+ 31810, {f: 8, c: 31812}, {f: 14, c: 31822}, {f: 2, c: 31837},
+ {f: 3, c: 31841}, {f: 4, c: 31845}, 31851, 31853, {f: 3, c: 31855},
+ {f: 6, c: 31861}, {f: 11, c: 31870}, {f: 7, c: 31882}, {f: 2, c: 31891},
+ 31894, {f: 3, c: 31897}, {f: 2, c: 31904}, 31907, {f: 4, c: 31910},
+ {f: 3, c: 31915}, {f: 2, c: 31919}, {f: 5, c: 31924}, {f: 2, c: 31930},
+ {f: 2, c: 31935}, {f: 3, c: 31938}, 31942, 31945, 31947, {f: 7, c: 31950},
+ 31960, {f: 2, c: 31962}, {f: 6, c: 31969}, {f: 6, c: 31977}, 31985, 31987,
+ 31989, 31991, 31994, {f: 2, c: 31996}, 31999, 32001, 32003, 32012,
+ {f: 2, c: 32014}, {f: 2, c: 32017}, 32022, 32024, {f: 3, c: 32029},
+ {f: 4, c: 32035}, {f: 3, c: 32040}, {f: 3, c: 32044}, {f: 5, c: 32052},
+ 32059, {f: 2, c: 32061}, 32065, 32067, 32069, {f: 7, c: 32071}, 32079,
+ {f: 12, c: 32081}, {f: 2, c: 32095}, {f: 3, c: 32099}, 32103,
+ {f: 5, c: 32105}, {f: 2, c: 32111}, {f: 2, c: 32116}, 32120,
+ {f: 7, c: 32122}, 32130, {f: 2, c: 32132}, 32135, {f: 5, c: 32138},
+ {f: 3, c: 32144}, {f: 8, c: 32148}, 32157, {f: 3, c: 32159},
+ {f: 2, c: 32164}, {f: 4, c: 32167}, 32175, {f: 3, c: 32181}, 32188,
+ {f: 4, c: 32192}, {f: 2, c: 32197}, {f: 2, c: 32200}, {f: 5, c: 32204},
+ 32211, {f: 2, c: 32213}, {f: 3, c: 32218}, 32223, 32226, {f: 2, c: 32228},
+ 32231, {f: 2, c: 32234}, {f: 2, c: 32237}, 32240, 32243, 32245,
+ {f: 2, c: 32247}, 32250, {f: 12, c: 32252}, {f: 4, c: 32268},
+ {f: 9, c: 32274}, 32284, {f: 3, c: 32288}, {f: 3, c: 32292},
+ {f: 3, c: 32296}, 32300, {f: 2, c: 32303}, 32307, 32312, 32314, 32316,
+ {f: 2, c: 32319}, {f: 3, c: 32322}, {f: 10, c: 32328}, 32339,
+ {f: 4, c: 32342}, {f: 3, c: 32347}, {f: 3, c: 32351}, {f: 6, c: 32355},
+ 32364, {f: 2, c: 32369}, {f: 5, c: 32372}, {f: 2, c: 32378},
+ {f: 3, c: 32383}, {f: 5, c: 32387}, 32393, 32395, 32398, {f: 3, c: 32400},
+ 32405, 32407, {f: 2, c: 32409}, {f: 2, c: 32413}, 32430, 32436,
+ {f: 2, c: 32443}, 32470, 32484, 32492, 32505, 32522, 32528, 32542, 32567,
+ 32569, {f: 7, c: 32571}, 32579, {f: 6, c: 32582}, 32589, 32591,
+ {f: 2, c: 32594}, 32598, 32601, {f: 4, c: 32603}, 32608, {f: 5, c: 32611},
+ {f: 3, c: 32619}, 32623, 32627, {f: 2, c: 32629}, 32632, {f: 4, c: 32634},
+ {f: 2, c: 32639}, {f: 3, c: 32642}, 32647, 32649, 32651, 32653,
+ {f: 5, c: 32655}, {f: 5, c: 32661}, {f: 2, c: 32667}, 32672,
+ {f: 2, c: 32674}, 32678, 32680, {f: 5, c: 32682}, 32689, {f: 5, c: 32691},
+ {f: 2, c: 32698}, 32702, 32704, {f: 3, c: 32706}, {f: 4, c: 32710}, 32715,
+ 32717, {f: 3, c: 32719}, 32723, {f: 2, c: 32726}, {f: 6, c: 32729},
+ {f: 3, c: 32738}, {f: 2, c: 32743}, {f: 4, c: 32746}, 32751, 32754,
+ {f: 5, c: 32756}, 32762, {f: 3, c: 32765}, 32770, {f: 4, c: 32775},
+ {f: 2, c: 32782}, 32785, 32787, {f: 2, c: 32794}, {f: 3, c: 32797}, 32801,
+ {f: 2, c: 32803}, 32811, 32813, {f: 2, c: 32815}, 32818, 32820,
+ {f: 2, c: 32825}, 32828, 32830, {f: 2, c: 32832}, {f: 2, c: 32836},
+ {f: 3, c: 32839}, {f: 4, c: 32846}, 32851, 32853, 32855, 32857,
+ {f: 3, c: 32859}, {f: 10, c: 32863}, {f: 4, c: 32875}, 32884, 32888,
+ {f: 3, c: 32890}, {f: 2, c: 32897}, 32904, 32906, {f: 6, c: 32909},
+ {f: 2, c: 32916}, 32919, 32921, 32926, 32931, {f: 3, c: 32934}, 32940,
+ 32944, 32947, {f: 2, c: 32949}, {f: 2, c: 32952}, 32955, 32965,
+ {f: 5, c: 32967}, {f: 7, c: 32975}, 32984, {f: 2, c: 32991},
+ {f: 2, c: 32994}, 32998, 33006, 33013, 33015, 33017, 33019,
+ {f: 4, c: 33022}, {f: 2, c: 33027}, {f: 2, c: 33031}, {f: 2, c: 33035},
+ 33045, 33047, 33049, {f: 2, c: 33052}, {f: 13, c: 33055}, {f: 2, c: 33069},
+ 33072, {f: 3, c: 33075}, 33079, {f: 4, c: 33082}, {f: 7, c: 33087}, 33095,
+ 33097, 33101, 33103, 33106, {f: 2, c: 33111}, {f: 5, c: 33115},
+ {f: 3, c: 33122}, 33128, 33130, 33132, 33135, {f: 2, c: 33138},
+ {f: 3, c: 33141}, 33153, {f: 5, c: 33155}, 33161, {f: 4, c: 33163}, 33168,
+ {f: 6, c: 33170}, 33177, {f: 2, c: 33182}, {f: 2, c: 33185},
+ {f: 2, c: 33188}, 33191, {f: 8, c: 33195}, {f: 6, c: 33204}, 33212,
+ {f: 2, c: 33220}, {f: 2, c: 33223}, 33227, 33230, {f: 8, c: 33232}, 33241,
+ {f: 4, c: 33243}, {f: 2, c: 33249}, {f: 3, c: 33252}, 33257, 33259,
+ {f: 5, c: 33262}, {f: 5, c: 33269}, 33277, 33279, 33283, 33291,
+ {f: 2, c: 33294}, 33297, 33299, {f: 6, c: 33301}, 33309, 33312,
+ {f: 4, c: 33316}, 33321, 33326, 33330, 33338, {f: 2, c: 33340},
+ {f: 5, c: 33343}, {f: 2, c: 33349}, 33352, 33354, {f: 3, c: 33356},
+ {f: 8, c: 33360}, {f: 4, c: 33371}, {f: 4, c: 33376}, 33381, 33383,
+ {f: 2, c: 33385}, {f: 2, c: 33388}, {f: 2, c: 33397}, [12171, 33400],
+ {f: 2, c: 33403}, {f: 2, c: 33408}, 33411, {f: 3, c: 33413}, 33417, 33420,
+ 33424, {f: 4, c: 33427}, {f: 2, c: 33434}, 33438, 33440, {f: 2, c: 33442},
+ 33447, 33458, {f: 2, c: 33461}, 33466, 33468, {f: 2, c: 33471},
+ {f: 2, c: 33474}, {f: 2, c: 33477}, 33481, 33488, 33494, {f: 2, c: 33497},
+ 33501, 33506, {f: 3, c: 33512}, {f: 3, c: 33516}, 33520, {f: 2, c: 33522},
+ {f: 2, c: 33525}, 33528, 33530, {f: 5, c: 33532}, {f: 2, c: 33546}, 33549,
+ 33552, {f: 2, c: 33554}, 33558, {f: 2, c: 33560}, {f: 10, c: 33565},
+ {f: 2, c: 33577}, 33582, 33584, 33586, 33591, 33595, {f: 3, c: 33597},
+ {f: 2, c: 33601}, {f: 2, c: 33604}, 33608, {f: 5, c: 33610}, 33619,
+ {f: 5, c: 33621}, 33629, 33634, {f: 7, c: 33648}, {f: 2, c: 33657},
+ {f: 7, c: 33662}, {f: 2, c: 33671}, {f: 3, c: 33675}, {f: 3, c: 33679},
+ {f: 2, c: 33684}, 33687, {f: 2, c: 33689}, 33693, 33695, 33697,
+ {f: 4, c: 33699}, {f: 4, c: 33708}, 33717, 33723, {f: 2, c: 33726},
+ {f: 3, c: 33730}, 33734, {f: 2, c: 33736}, 33739, {f: 2, c: 33741},
+ {f: 4, c: 33744}, 33749, 33751, {f: 3, c: 33753}, 33758, {f: 3, c: 33762},
+ {f: 3, c: 33766}, {f: 4, c: 33771}, {f: 5, c: 33779}, {f: 3, c: 33786},
+ {f: 3, c: 33790}, 33794, 33797, {f: 2, c: 33800}, 33808, {f: 6, c: 33810},
+ {f: 3, c: 33817}, {f: 6, c: 33822}, {f: 3, c: 33833}, {f: 4, c: 33837},
+ {f: 3, c: 33842}, {f: 2, c: 33846}, {f: 3, c: 33849}, {f: 8, c: 33854},
+ {f: 2, c: 33863}, {f: 7, c: 33866}, {f: 4, c: 33875}, 33880,
+ {f: 4, c: 33885}, 33890, 33893, {f: 2, c: 33895}, 33898, 33902, 33904,
+ 33906, 33908, 33913, {f: 7, c: 33915}, {f: 4, c: 33923}, 33930, 33933,
+ {f: 4, c: 33935}, {f: 2, c: 33941}, 33944, {f: 2, c: 33946},
+ {f: 4, c: 33949}, {f: 13, c: 33954}, {f: 2, c: 33968}, 33971,
+ {f: 3, c: 33973}, 33979, 33982, {f: 2, c: 33986}, {f: 4, c: 33989}, 33996,
+ {f: 2, c: 33998}, 34002, {f: 2, c: 34004}, {f: 6, c: 34007}, 34014,
+ {f: 2, c: 34017}, 34020, {f: 5, c: 34023}, 34029, {f: 11, c: 34033}, 34046,
+ {f: 12, c: 34048}, {f: 4, c: 34061}, 34066, {f: 2, c: 34069},
+ {f: 2, c: 34072}, {f: 3, c: 34075}, 34080, 34082, {f: 2, c: 34084},
+ {f: 4, c: 34087}, {f: 9, c: 34094}, {f: 3, c: 34110}, 34114,
+ {f: 2, c: 34116}, 34119, {f: 3, c: 34123}, {f: 3, c: 34127}, 34132, 34135,
+ {f: 4, c: 34138}, {f: 3, c: 34143}, 34147, {f: 3, c: 34149},
+ {f: 2, c: 34155}, {f: 4, c: 34158}, 34163, {f: 2, c: 34165}, 34168,
+ {f: 2, c: 34172}, {f: 5, c: 34175}, 34182, 34185, 34187, {f: 2, c: 34189},
+ 34192, {f: 2, c: 34194}, {f: 6, c: 34197}, {f: 2, c: 34205},
+ {f: 4, c: 34208}, 34213, 34215, {f: 3, c: 34219}, {f: 6, c: 34225}, 34232,
+ {f: 6, c: 34235}, {f: 7, c: 34242}, {f: 3, c: 34250}, {f: 2, c: 34257},
+ 34260, {f: 6, c: 34262}, {f: 6, c: 34270}, {f: 3, c: 34278},
+ {f: 9, c: 34283}, 34293, {f: 2, c: 34295}, {f: 3, c: 34300},
+ {f: 4, c: 34304}, {f: 3, c: 34312}, {f: 5, c: 34316}, {f: 4, c: 34322},
+ {f: 3, c: 34327}, {f: 3, c: 34331}, {f: 3, c: 34335}, {f: 4, c: 34339},
+ 34344, {f: 3, c: 34346}, {f: 10, c: 34350}, 34361, 34363, {f: 2, c: 34365},
+ {f: 13, c: 34368}, {f: 2, c: 34386}, {f: 4, c: 34390}, 34395, 34397,
+ {f: 2, c: 34400}, {f: 4, c: 34403}, {f: 3, c: 34408}, 34413,
+ {f: 2, c: 34415}, {f: 7, c: 34418}, {f: 7, c: 34435}, {f: 5, c: 34446},
+ 34452, {f: 6, c: 34454}, {f: 5, c: 34462}, {f: 2, c: 34469}, 34475,
+ {f: 2, c: 34477}, {f: 2, c: 34482}, {f: 3, c: 34487}, {f: 5, c: 34491},
+ {f: 3, c: 34497}, 34501, 34504, {f: 2, c: 34508}, {f: 2, c: 34514},
+ {f: 3, c: 34517}, 34522, {f: 2, c: 34524}, {f: 4, c: 34528},
+ {f: 4, c: 34533}, {f: 3, c: 34538}, 34543, {f: 3, c: 34549},
+ {f: 3, c: 34555}, 34559, 34561, {f: 2, c: 34564}, {f: 2, c: 34571},
+ {f: 4, c: 34574}, 34580, 34582, 34585, 34587, 34589, {f: 2, c: 34591},
+ 34596, {f: 3, c: 34598}, {f: 4, c: 34602}, {f: 2, c: 34607},
+ {f: 2, c: 34610}, {f: 2, c: 34613}, {f: 3, c: 34616}, {f: 2, c: 34620},
+ {f: 7, c: 34624}, {f: 2, c: 34634}, 34637, {f: 4, c: 34639}, 34644, 34646,
+ 34648, {f: 6, c: 34650}, {f: 2, c: 34657}, {f: 7, c: 34663}, 34671,
+ {f: 3, c: 34673}, 34677, 34679, {f: 2, c: 34681}, {f: 3, c: 34687},
+ {f: 2, c: 34694}, {f: 2, c: 34697}, 34700, {f: 5, c: 34702},
+ {f: 3, c: 34708}, {f: 6, c: 34712}, {f: 2, c: 34720}, {f: 5, c: 34723},
+ {f: 2, c: 34729}, 34734, {f: 3, c: 34736}, 34740, {f: 4, c: 34742}, 34748,
+ {f: 2, c: 34750}, {f: 3, c: 34753}, 34757, 34759, 34761, {f: 2, c: 34764},
+ {f: 2, c: 34767}, {f: 7, c: 34772}, {f: 4, c: 34780}, {f: 2, c: 34785},
+ 34788, {f: 4, c: 34790}, 34795, 34797, {f: 2, c: 34800}, {f: 3, c: 34803},
+ {f: 2, c: 34807}, 34810, {f: 2, c: 34812}, {f: 4, c: 34815}, 34820,
+ {f: 3, c: 34823}, {f: 5, c: 34827}, 34834, 34836, {f: 4, c: 34839},
+ {f: 3, c: 34844}, 34848, {f: 13, c: 34852}, {f: 3, c: 34867},
+ {f: 2, c: 34871}, 34874, {f: 3, c: 34877}, {f: 3, c: 34881},
+ {f: 3, c: 34887}, 34891, {f: 5, c: 34894}, {f: 2, c: 34901}, 34904, 34906,
+ 34908, {f: 3, c: 34910}, {f: 2, c: 34918}, 34922, 34925, 34927, 34929,
+ {f: 4, c: 34931}, 34936, {f: 3, c: 34938}, 34944, 34947, {f: 2, c: 34950},
+ {f: 2, c: 34953}, 34956, {f: 4, c: 34958}, {f: 3, c: 34963},
+ {f: 5, c: 34967}, {f: 5, c: 34973}, 34979, {f: 6, c: 34981}, 34988,
+ {f: 3, c: 34990}, {f: 5, c: 34994}, {f: 4, c: 35000}, {f: 4, c: 35005},
+ {f: 2, c: 35011}, {f: 2, c: 35015}, {f: 3, c: 35019}, {f: 2, c: 35024},
+ 35027, {f: 2, c: 35030}, {f: 2, c: 35034}, 35038, {f: 2, c: 35040},
+ {f: 2, c: 35046}, {f: 7, c: 35049}, 35058, {f: 3, c: 35061},
+ {f: 2, c: 35066}, {f: 3, c: 35071}, {f: 4, c: 35075}, {f: 2, c: 35080},
+ {f: 5, c: 35083}, 35089, {f: 5, c: 35092}, {f: 5, c: 35100},
+ {f: 3, c: 35106}, {f: 4, c: 35110}, {f: 4, c: 35116}, 35121, 35125, 35127,
+ {f: 2, c: 35129}, {f: 5, c: 35132}, {f: 2, c: 35138}, {f: 2, c: 35141},
+ {f: 14, c: 35144}, {f: 6, c: 35159}, {f: 3, c: 35169}, 35173,
+ {f: 3, c: 35175}, 35179, {f: 2, c: 35181}, {f: 2, c: 35184},
+ {f: 8, c: 35187}, {f: 2, c: 35196}, [12177, 35198], 35200, 35202,
+ {f: 2, c: 35204}, {f: 4, c: 35207}, {f: 3, c: 35212}, {f: 3, c: 35216},
+ {f: 2, c: 35220}, 35223, {f: 8, c: 35225}, {f: 4, c: 35234},
+ {f: 3, c: 35239}, 35243, {f: 2, c: 35245}, {f: 2, c: 35248},
+ {f: 4, c: 35251}, {f: 2, c: 35256}, {f: 2, c: 35259}, 35262, 35267, 35277,
+ {f: 3, c: 35283}, {f: 3, c: 35287}, 35291, 35293, {f: 4, c: 35295}, 35300,
+ {f: 4, c: 35303}, {f: 3, c: 35308}, {f: 3, c: 35312}, 35317, 35319,
+ {f: 7, c: 35321}, {f: 3, c: 35332}, 35337, 35339, 35341, 35343,
+ {f: 2, c: 35345}, 35348, 35351, {f: 2, c: 35353}, 35356, 35358,
+ {f: 3, c: 35360}, 35364, {f: 4, c: 35366}, {f: 2, c: 35371},
+ {f: 3, c: 35374}, {f: 2, c: 35378}, 35381, {f: 3, c: 35383},
+ {f: 3, c: 35387}, {f: 2, c: 35391}, {f: 4, c: 35394}, 35399,
+ {f: 5, c: 35401}, 35407, 35409, 35411, {f: 2, c: 35414}, {f: 2, c: 35417},
+ {f: 2, c: 35420}, {f: 2, c: 35423}, {f: 2, c: 35428}, {f: 2, c: 35431},
+ 35434, 35439, 35444, {f: 3, c: 35446}, {f: 2, c: 35450}, {f: 2, c: 35453},
+ {f: 4, c: 35456}, 35464, {f: 2, c: 35467}, {f: 3, c: 35470}, 35476,
+ {f: 2, c: 35478}, 35481, {f: 3, c: 35483}, 35487, 35490, 35495,
+ {f: 3, c: 35497}, {f: 3, c: 35501}, 35505, {f: 3, c: 35507},
+ {f: 2, c: 35511}, {f: 2, c: 35514}, {f: 2, c: 35517}, {f: 2, c: 35520},
+ 35523, {f: 2, c: 35525}, 35528, 35530, 35532, 35534, 35536,
+ {f: 3, c: 35539}, {f: 3, c: 35544}, 35549, {f: 3, c: 35551}, 35555, 35557,
+ {f: 3, c: 35560}, 35564, {f: 2, c: 35567}, 35570, {f: 2, c: 35572}, 35577,
+ 35579, 35581, 35583, 35587, 35590, {f: 2, c: 35592}, {f: 3, c: 35595},
+ 35599, {f: 3, c: 35601}, 35605, 35608, 35612, {f: 3, c: 35614},
+ {f: 4, c: 35618}, 35623, {f: 2, c: 35625}, {f: 5, c: 35630},
+ {f: 5, c: 35636}, {f: 4, c: 35642}, {f: 10, c: 35647}, {f: 4, c: 35658},
+ {f: 6, c: 35664}, 35671, 35675, {f: 9, c: 35677}, {f: 4, c: 35687},
+ {f: 2, c: 35693}, {f: 3, c: 35697}, {f: 2, c: 35701}, {f: 5, c: 35704},
+ {f: 2, c: 35710}, {f: 9, c: 35713}, {f: 3, c: 35723}, {f: 3, c: 35727},
+ 35732, {f: 5, c: 35735}, 35741, 35743, 35756, 35761, 35771, 35783, 35792,
+ 35818, 35849, 35870, {f: 9, c: 35896}, {f: 4, c: 35906}, {f: 2, c: 35914},
+ {f: 3, c: 35917}, {f: 4, c: 35921}, {f: 4, c: 35926}, {f: 6, c: 35931},
+ {f: 7, c: 35939}, {f: 7, c: 35948}, {f: 4, c: 35956}, {f: 7, c: 35963},
+ {f: 2, c: 35971}, {f: 3, c: 35974}, 35979, {f: 7, c: 35981},
+ {f: 3, c: 35989}, {f: 4, c: 35993}, 35999, {f: 4, c: 36003},
+ {f: 2, c: 36013}, 36017, 36021, 36025, 36030, 36038, 36041,
+ {f: 6, c: 36043}, 36052, {f: 4, c: 36054}, 36059, 36061, 36063, 36069,
+ {f: 2, c: 36072}, {f: 6, c: 36078}, {f: 5, c: 36085}, {f: 5, c: 36095},
+ {f: 2, c: 36102}, 36105, 36108, 36110, {f: 5, c: 36113}, {f: 4, c: 36119},
+ 36128, {f: 2, c: 36177}, 36183, 36191, 36197, {f: 3, c: 36200}, 36204,
+ {f: 2, c: 36206}, {f: 2, c: 36209}, {f: 9, c: 36216}, {f: 2, c: 36226},
+ {f: 4, c: 36230}, {f: 5, c: 36236}, {f: 2, c: 36242}, {f: 3, c: 36246},
+ {f: 5, c: 36250}, {f: 3, c: 36256}, {f: 4, c: 36260}, {f: 8, c: 36265},
+ {f: 2, c: 36278}, 36281, 36283, 36285, {f: 3, c: 36288}, 36293,
+ {f: 4, c: 36295}, 36301, 36304, {f: 4, c: 36306}, {f: 2, c: 36312}, 36316,
+ {f: 3, c: 36320}, {f: 3, c: 36325}, 36329, {f: 2, c: 36333},
+ {f: 3, c: 36336}, 36340, 36342, 36348, {f: 7, c: 36350}, {f: 3, c: 36358},
+ 36363, {f: 2, c: 36365}, {f: 3, c: 36369}, {f: 8, c: 36373},
+ {f: 2, c: 36384}, {f: 5, c: 36388}, 36395, 36397, 36400, {f: 2, c: 36402},
+ {f: 3, c: 36406}, {f: 2, c: 36411}, {f: 2, c: 36414}, 36419,
+ {f: 2, c: 36421}, {f: 4, c: 36429}, {f: 2, c: 36435}, {f: 3, c: 36438},
+ {f: 9, c: 36442}, {f: 2, c: 36452}, {f: 2, c: 36455}, {f: 2, c: 36458},
+ 36462, 36465, 36467, 36469, {f: 3, c: 36471}, 36475, {f: 2, c: 36477},
+ 36480, {f: 3, c: 36482}, 36486, 36488, 36492, 36494, {f: 5, c: 36501},
+ 36507, 36509, {f: 2, c: 36511}, {f: 3, c: 36514}, {f: 3, c: 36519},
+ {f: 2, c: 36525}, {f: 2, c: 36528}, {f: 7, c: 36531}, {f: 5, c: 36539},
+ {f: 9, c: 36545}, {f: 3, c: 36559}, 36563, {f: 6, c: 36565},
+ {f: 3, c: 36572}, {f: 4, c: 36576}, {f: 6, c: 36581}, {f: 6, c: 36588},
+ {f: 5, c: 36595}, 36605, {f: 4, c: 36607}, 36612, 36614, 36616,
+ {f: 7, c: 36619}, 36627, {f: 5, c: 36630}, {f: 5, c: 36640},
+ {f: 2, c: 36647}, {f: 4, c: 36651}, {f: 3, c: 36656}, {f: 4, c: 36660},
+ {f: 2, c: 36665}, {f: 2, c: 36668}, {f: 2, c: 36672}, 36675,
+ {f: 2, c: 36679}, {f: 3, c: 36682}, {f: 5, c: 36687}, {f: 10, c: 36693},
+ 36704, 36707, 36709, 36714, 36736, 36748, 36754, 36765, {f: 3, c: 36768},
+ {f: 2, c: 36772}, 36775, 36778, 36780, {f: 2, c: 36787}, [12193, 36789],
+ {f: 2, c: 36791}, {f: 3, c: 36794}, {f: 2, c: 36799}, 36803, 36806,
+ {f: 5, c: 36809}, 36815, 36818, {f: 2, c: 36822}, 36826, {f: 2, c: 36832},
+ 36835, 36839, 36844, 36847, {f: 2, c: 36849}, {f: 2, c: 36853},
+ {f: 3, c: 36858}, {f: 2, c: 36862}, {f: 2, c: 36871}, 36876, 36878, 36883,
+ 36888, 36892, {f: 2, c: 36900}, {f: 6, c: 36903}, {f: 2, c: 36912},
+ {f: 2, c: 36915}, 36919, {f: 2, c: 36921}, 36925, {f: 2, c: 36927}, 36931,
+ {f: 2, c: 36933}, {f: 3, c: 36936}, 36940, 36950, {f: 2, c: 36953}, 36957,
+ 36959, 36961, 36964, {f: 2, c: 36966}, {f: 3, c: 36970}, {f: 3, c: 36975},
+ 36979, 36982, 36985, 36987, 36990, {f: 2, c: 36997}, 37001,
+ {f: 3, c: 37004}, 37010, 37012, 37014, 37016, 37018, 37020,
+ {f: 3, c: 37022}, {f: 2, c: 37028}, {f: 3, c: 37031}, 37035, 37037, 37042,
+ 37047, {f: 2, c: 37052}, {f: 2, c: 37055}, {f: 2, c: 37058}, 37062,
+ {f: 2, c: 37064}, {f: 3, c: 37067}, 37074, {f: 3, c: 37076},
+ {f: 3, c: 37080}, 37086, 37088, {f: 3, c: 37091}, {f: 2, c: 37097}, 37100,
+ 37102, {f: 4, c: 37104}, {f: 2, c: 37110}, {f: 4, c: 37113},
+ {f: 3, c: 37119}, 37123, 37125, {f: 2, c: 37127}, {f: 8, c: 37130}, 37139,
+ 37141, {f: 2, c: 37143}, {f: 4, c: 37146}, {f: 3, c: 37151},
+ {f: 3, c: 37156}, {f: 5, c: 37160}, 37166, 37171, 37173, {f: 2, c: 37175},
+ {f: 8, c: 37179}, {f: 2, c: 37188}, 37191, 37201, {f: 4, c: 37203},
+ {f: 2, c: 37208}, {f: 2, c: 37211}, {f: 2, c: 37215}, {f: 3, c: 37222},
+ 37227, 37229, 37235, {f: 3, c: 37242}, {f: 5, c: 37248}, 37254, 37256,
+ 37258, {f: 2, c: 37262}, {f: 3, c: 37267}, {f: 3, c: 37271},
+ {f: 5, c: 37277}, {f: 6, c: 37284}, {f: 4, c: 37296}, {f: 4, c: 37302},
+ {f: 5, c: 37307}, 37314, 37316, [12196, 37318], 37320, 37328, 37334,
+ {f: 2, c: 37338}, {f: 5, c: 37342}, {f: 2, c: 37349}, 37352,
+ {f: 11, c: 37354}, 37366, 37368, {f: 5, c: 37371}, {f: 2, c: 37378},
+ {f: 3, c: 37381}, {f: 3, c: 37386}, 37391, {f: 2, c: 37394},
+ {f: 8, c: 37398}, {f: 4, c: 37407}, 37412, {f: 6, c: 37416}, 37423,
+ {f: 2, c: 37425}, {f: 2, c: 37429}, {f: 2, c: 37435}, {f: 4, c: 37441},
+ {f: 2, c: 37446}, {f: 3, c: 37450}, {f: 3, c: 37454}, {f: 3, c: 37458},
+ 37462, {f: 2, c: 37464}, {f: 2, c: 37468}, {f: 3, c: 37471},
+ {f: 3, c: 37475}, {f: 5, c: 37479}, {f: 6, c: 37486}, {f: 3, c: 37493},
+ 37497, {f: 3, c: 37500}, {f: 2, c: 37505}, 37508, {f: 8, c: 37510},
+ {f: 2, c: 37519}, 37522, {f: 2, c: 37524}, 37527, 37529, 37531,
+ {f: 3, c: 37533}, {f: 2, c: 37537}, 37540, 37543, 37549, {f: 2, c: 37551},
+ {f: 5, c: 37554}, 37560, 37562, {f: 4, c: 37565}, 37570, 37572, 37574,
+ {f: 3, c: 37577}, {f: 2, c: 37581}, {f: 2, c: 37584}, {f: 10, c: 37587},
+ 37598, {f: 3, c: 37600}, 37607, 37609, {f: 2, c: 37611}, {f: 4, c: 37618},
+ 37623, {f: 3, c: 37625}, {f: 4, c: 37629}, {f: 4, c: 37634},
+ {f: 7, c: 37641}, 37649, {f: 2, c: 37651}, {f: 2, c: 37654},
+ {f: 3, c: 37660}, 37665, {f: 3, c: 37667}, 37671, {f: 2, c: 37673},
+ {f: 2, c: 37676}, {f: 2, c: 37680}, {f: 2, c: 37684}, 37687,
+ {f: 5, c: 37689}, 37695, 37698, {f: 2, c: 37700}, {f: 3, c: 37704}, 37708,
+ {f: 6, c: 37710}, {f: 3, c: 37717}, {f: 2, c: 37721}, {f: 8, c: 37724},
+ {f: 3, c: 37734}, 37739, {f: 3, c: 37741}, {f: 4, c: 37745},
+ {f: 3, c: 37751}, {f: 3, c: 37755}, {f: 3, c: 37759}, 37763,
+ {f: 2, c: 37765}, {f: 2, c: 37768}, {f: 4, c: 37771}, {f: 6, c: 37776},
+ 37783, {f: 9, c: 37785}, {f: 2, c: 37796}, 37800, 37803, 37805, 37807,
+ {f: 2, c: 37809}, 37812, {f: 2, c: 37814}, {f: 6, c: 37817},
+ {f: 3, c: 37824}, {f: 3, c: 37828}, 37833, 37835, {f: 3, c: 37838},
+ {f: 4, c: 37842}, {f: 3, c: 37849}, 37856, 37859, {f: 3, c: 37861},
+ {f: 12, c: 37865}, 37878, 37880, {f: 9, c: 37882}, {f: 7, c: 37892},
+ {f: 4, c: 37900}, 37905, {f: 3, c: 37909}, {f: 3, c: 37914},
+ {f: 2, c: 37918}, {f: 5, c: 37921}, {f: 5, c: 37929}, {f: 3, c: 37935},
+ 37940, {f: 2, c: 37942}, 37945, {f: 3, c: 37947}, {f: 4, c: 37952},
+ {f: 5, c: 37957}, 37963, {f: 5, c: 37965}, 37971, {f: 11, c: 37973},
+ {f: 2, c: 37985}, 37988, {f: 5, c: 37990}, 37996, {f: 2, c: 37998}, 38001,
+ {f: 4, c: 38003}, 38008, {f: 2, c: 38010}, {f: 5, c: 38016}, 38033, 38038,
+ 38040, 38087, 38095, {f: 2, c: 38099}, 38106, 38118, 38139, 38172, 38176,
+ 38183, 38195, 38205, 38211, 38216, 38219, 38229, 38234, 38240, 38254,
+ {f: 2, c: 38260}, {f: 7, c: 38264}, 38273, {f: 2, c: 38276},
+ {f: 2, c: 38279}, 38282, 38285, 38288, 38290, {f: 3, c: 38293},
+ {f: 8, c: 38297}, 38306, {f: 2, c: 38310}, 38314, {f: 4, c: 38318},
+ {f: 3, c: 38323}, {f: 2, c: 38327}, 38330, {f: 3, c: 38336},
+ {f: 2, c: 38340}, 38343, 38345, {f: 3, c: 38349}, {f: 3, c: 38353},
+ {f: 5, c: 38359}, 38365, {f: 2, c: 38367}, {f: 2, c: 38371},
+ {f: 2, c: 38374}, 38380, 38399, 38407, 38419, 38424, 38427, 38430, 38432,
+ {f: 7, c: 38435}, {f: 3, c: 38443}, {f: 2, c: 38447}, {f: 4, c: 38455},
+ 38462, 38465, 38467, 38474, {f: 2, c: 38478}, {f: 3, c: 38481},
+ {f: 2, c: 38486}, {f: 2, c: 38489}, 38492, 38494, 38496, {f: 2, c: 38501},
+ 38507, {f: 3, c: 38509}, 38513, {f: 4, c: 38521}, {f: 7, c: 38526}, 38535,
+ 38537, 38540, {f: 3, c: 38545}, 38550, 38554, {f: 10, c: 38557}, 38569,
+ {f: 5, c: 38571}, 38578, 38581, 38583, 38586, 38591, {f: 2, c: 38594},
+ 38600, {f: 2, c: 38602}, {f: 2, c: 38608}, {f: 2, c: 38611},
+ {f: 2, c: 38615}, 38618, {f: 3, c: 38621}, 38625, {f: 4, c: 38628},
+ {f: 4, c: 38635}, {f: 2, c: 38640}, {f: 2, c: 38644}, 38648, 38650,
+ {f: 2, c: 38652}, 38655, {f: 2, c: 38658}, 38661, {f: 3, c: 38666},
+ {f: 3, c: 38672}, {f: 2, c: 38676}, {f: 5, c: 38679}, 38685,
+ {f: 8, c: 38687}, {f: 2, c: 38696}, {f: 2, c: 38699}, {f: 2, c: 38702},
+ 38705, {f: 5, c: 38707}, {f: 3, c: 38714}, {f: 3, c: 38719}, 38723,
+ {f: 3, c: 38725}, {f: 8, c: 38729}, [12205, 38737], {f: 2, c: 38740},
+ {f: 2, c: 38743}, {f: 2, c: 38748}, 38751, {f: 2, c: 38755},
+ {f: 2, c: 38758}, {f: 9, c: 38762}, 38773, {f: 5, c: 38775},
+ {f: 8, c: 38781}, {f: 5, c: 38790}, 38796, 38798, 38800, 38803,
+ {f: 3, c: 38805}, {f: 7, c: 38809}, {f: 2, c: 38817}, {f: 2, c: 38820},
+ {f: 4, c: 38823}, 38828, 38830, {f: 2, c: 38832}, 38835, {f: 8, c: 38837},
+ {f: 5, c: 38846}, {f: 2, c: 38852}, {f: 2, c: 38855}, 38858,
+ {f: 6, c: 38861}, {f: 5, c: 38868}, {f: 2, c: 38874}, 38877,
+ {f: 7, c: 38879}, 38888, {f: 5, c: 38894}, 38900, {f: 8, c: 38903}, 38912,
+ 38916, 38921, 38923, 38925, {f: 3, c: 38932}, {f: 3, c: 38937},
+ {f: 4, c: 38941}, {f: 2, c: 38946}, 38949, {f: 6, c: 38951},
+ {f: 2, c: 38958}, {f: 6, c: 38961}, {f: 2, c: 38969}, 38972,
+ {f: 8, c: 38974}, {f: 5, c: 38983}, {f: 4, c: 38991}, {f: 3, c: 38997},
+ 39002, {f: 2, c: 39004}, {f: 3, c: 39007}, {f: 2, c: 39011}, 39014,
+ {f: 3, c: 39016}, {f: 2, c: 39021}, 39026, 39051, 39054, 39058, 39061,
+ 39065, 39075, {f: 5, c: 39081}, 39088, 39090, {f: 2, c: 39092},
+ {f: 5, c: 39095}, {f: 7, c: 39101}, 39109, 39111, {f: 5, c: 39113},
+ {f: 2, c: 39119}, 39124, {f: 2, c: 39126}, {f: 2, c: 39132}, 39137,
+ {f: 4, c: 39139}, 39148, 39150, {f: 2, c: 39152}, 39155, {f: 7, c: 39157},
+ {f: 4, c: 39167}, 39172, {f: 3, c: 39174}, 39179, {f: 2, c: 39182},
+ {f: 4, c: 39188}, {f: 2, c: 39193}, {f: 2, c: 39196}, {f: 2, c: 39199},
+ {f: 6, c: 39202}, {f: 5, c: 39209}, {f: 4, c: 39215}, {f: 3, c: 39220},
+ {f: 4, c: 39224}, 39229, {f: 3, c: 39232}, 39236, {f: 2, c: 39238},
+ {f: 4, c: 39245}, 39251, 39254, {f: 4, c: 39256}, 39261, {f: 3, c: 39263},
+ 39268, 39270, 39283, {f: 2, c: 39288}, 39291, 39294, {f: 2, c: 39298},
+ 39305, 39308, 39310, {f: 11, c: 39322}, {f: 2, c: 39334}, {f: 3, c: 39337},
+ {f: 2, c: 39343}, 39346, {f: 12, c: 39349}, {f: 14, c: 39362}, 39379,
+ {f: 2, c: 39382}, 39386, 39388, 39390, 39392, {f: 10, c: 39395},
+ {f: 3, c: 39406}, {f: 13, c: 39410}, 39424, {f: 3, c: 39426},
+ {f: 7, c: 39430}, {f: 6, c: 39440}, {f: 2, c: 39447}, {f: 17, c: 39450},
+ 39468, 39471, {f: 5, c: 39473}, {f: 5, c: 39481}, 39487, {f: 4, c: 39494},
+ {f: 2, c: 39499}, 39502, {f: 5, c: 39504}, 39510, {f: 2, c: 39512},
+ {f: 3, c: 39516}, {f: 2, c: 39520}, 39523, {f: 4, c: 39526}, 39531, 39538,
+ 39555, 39561, {f: 2, c: 39565}, {f: 2, c: 39572}, 39577, 39590,
+ {f: 6, c: 39593}, {f: 4, c: 39602}, 39609, 39611, {f: 3, c: 39613},
+ {f: 2, c: 39619}, {f: 5, c: 39622}, {f: 2, c: 39629}, 39632, 39639,
+ {f: 6, c: 39641}, 39648, {f: 4, c: 39650}, {f: 4, c: 39655}, 39660,
+ {f: 9, c: 39664}, 39674, {f: 7, c: 39676}, {f: 2, c: 39684}, 39687,
+ {f: 4, c: 39689}, 39694, {f: 3, c: 39696}, {f: 6, c: 39700},
+ {f: 4, c: 39707}, {f: 2, c: 39712}, 39716, 39718, 39720, {f: 4, c: 39722},
+ 39728, {f: 8, c: 39731}, {f: 4, c: 39741}, 39750, {f: 3, c: 39754}, 39760,
+ {f: 2, c: 39762}, {f: 3, c: 39765}, 39769, {f: 20, c: 39771},
+ {f: 4, c: 39792}, {f: 2, c: 39797}, {f: 9, c: 39800}, 39810,
+ {f: 10, c: 39812}, 39823, {f: 7, c: 39827}, {f: 2, c: 39835},
+ {f: 11, c: 39839}, 39852, {f: 17, c: 39855}, {f: 5, c: 39874}, 39880,
+ {f: 9, c: 39883}, 39893, {f: 4, c: 39895}, 39900, {f: 3, c: 39902}, 39907,
+ {f: 2, c: 39909}, 39913, {f: 4, c: 39916}, {f: 3, c: 39921},
+ {f: 8, c: 39925}, 39934, {f: 8, c: 39936}, {f: 3, c: 39946},
+ {f: 2, c: 39950}, 39953, {f: 12, c: 39956}, {f: 2, c: 39969}, 39972,
+ {f: 2, c: 39974}, {f: 3, c: 39978}, {f: 3, c: 39982}, 39988, 39990, 39992,
+ 39994, {f: 2, c: 39996}, {f: 6, c: 39999}, {f: 2, c: 40006},
+ {f: 8, c: 40010}, 40019, 40021, {f: 4, c: 40025}, 40030, {f: 7, c: 40032},
+ {f: 5, c: 40040}, {f: 10, c: 40046}, 40057, 40059, {f: 2, c: 40061}, 40064,
+ {f: 2, c: 40067}, {f: 2, c: 40073}, 40076, 40079, 40083, {f: 4, c: 40086},
+ 40093, 40106, 40108, 40111, 40121, {f: 5, c: 40126}, {f: 2, c: 40136},
+ {f: 2, c: 40145}, {f: 2, c: 40154}, {f: 2, c: 40160}, {f: 2, c: 40163},
+ {f: 3, c: 40166}, {f: 2, c: 40170}, {f: 6, c: 40173}, 40181,
+ {f: 15, c: 40183}, 40200, {f: 11, c: 40202}, {f: 5, c: 40214}, 40220,
+ 40222, {f: 3, c: 40224}, {f: 2, c: 40228}, 40231, {f: 6, c: 40233},
+ {f: 10, c: 40241}, {f: 3, c: 40252}, {f: 2, c: 40256}, {f: 14, c: 40259},
+ {f: 8, c: 40276}, {f: 2, c: 40286}, {f: 8, c: 40290}, 40299,
+ {f: 2, c: 40301}, {f: 2, c: 40304}, {f: 20, c: 40307}, 40328,
+ {f: 9, c: 40330}, {f: 4, c: 40340}, 40345, {f: 10, c: 40347},
+ {f: 3, c: 40358}, {f: 5, c: 40362}, {f: 4, c: 40368}, {f: 6, c: 40373},
+ {f: 3, c: 40381}, 40385, 40387, {f: 14, c: 40389}, {f: 3, c: 40404}, 40408,
+ {f: 10, c: 40411}, {f: 8, c: 40423}, {f: 2, c: 40432}, {f: 4, c: 40436},
+ {f: 17, c: 40443}, {f: 8, c: 40461}, {f: 4, c: 40470}, 40476, 40484, 40487,
+ 40494, 40496, 40500, {f: 2, c: 40507}, 40512, 40525, 40528,
+ {f: 3, c: 40530}, 40534, 40537, 40541, {f: 4, c: 40543}, 40549,
+ {f: 2, c: 40558}, 40562, 40564, {f: 3, c: 40566}, 40571, {f: 2, c: 40576},
+ {f: 4, c: 40579}, {f: 2, c: 40585}, {f: 6, c: 40588}, {f: 3, c: 40596},
+ {f: 5, c: 40600}, 40606, {f: 5, c: 40608}, {f: 2, c: 40615},
+ {f: 5, c: 40618}, {f: 4, c: 40624}, {f: 2, c: 40630}, {f: 2, c: 40633},
+ 40636, {f: 4, c: 40639}, [12232, 40643], {f: 4, c: 40645},
+ {f: 2, c: 40650}, 40656, {f: 2, c: 40658}, {f: 3, c: 40661},
+ {f: 2, c: 40665}, 40673, {f: 2, c: 40675}, 40678, {f: 4, c: 40683},
+ {f: 2, c: 40688}, 40691, {f: 2, c: 40693}, 40696, 40698, {f: 9, c: 40704},
+ 40714, 40716, 40719, {f: 2, c: 40721}, 40724, 40726, 40728,
+ {f: 6, c: 40730}, 40737, {f: 9, c: 40739}, {f: 2, c: 40749},
+ {f: 7, c: 40752}, 40760, 40762, 40764, {f: 5, c: 40767}, {f: 5, c: 40773},
+ {f: 3, c: 40780}, 40787, {f: 4, c: 40789}, {f: 2, c: 40794},
+ {f: 2, c: 40797}, 40802, {f: 2, c: 40804}, {f: 3, c: 40807}, 40811,
+ {f: 5, c: 40813}, {f: 4, c: 40819}, {f: 7, c: 40824}, {f: 2, c: 40833},
+ {f: 2, c: 40846}, {f: 3, c: 40849}, {f: 3, c: 40854}, {f: 2, c: 40861},
+ {f: 5, c: 40865}, 63788, {f: 3, c: 64013}, 64017, {f: 2, c: 64019}, 64024,
+ {f: 3, c: 64031}, {f: 2, c: 64035}, {f: 3, c: 64039}, 11905,
+ [59414, 131207], [59415, 131209], [59416, 131276], 11908, 13427, 13383,
+ 11912, 11915, 59422, 13726, 13850, 13838, 11916, 11927, 14702, 14616,
+ 59430, 14799, 14815, 14963, 14800, {f: 2, c: 59435}, 15182, 15470, 15584,
+ 11943, [59441, 136663], 59442, 11946, 16470, 16735, 11950, 17207, 11955,
+ {f: 2, c: 11958}, [59451, 141711], 17329, 17324, 11963, 17373, 17622,
+ 18017, 17996, [59459, 132361], 18211, 18217, 18300, 18317, 11978, 18759,
+ 18810, 18813, {f: 2, c: 18818}, {f: 2, c: 18821}, 18847, 18843, 18871,
+ 18870, [59476, 133533], [59477, 147966], 19619, {f: 3, c: 19615}, 19575,
+ 19618, {f: 7, c: 19731}, 19886, 59492, {s: 226}, 8364, 165, 0, 0, 12351,
+ {s: 17}, 12436, {s: 14}, 12535, 12537, 12536, 12538, 0, {f: 3, c: 12339},
+ {f: 3, c: 12344}, {f: 3, c: 12586}, {f: 24, c: 12704}, 11904,
+ {f: 2, c: 11906}, {f: 3, c: 11909}, {f: 2, c: 11913}, {f: 10, c: 11917},
+ {f: 2, c: 11928}, {f: 12, c: 11931}, {f: 2, c: 11944}, {f: 3, c: 11947},
+ {f: 4, c: 11951}, {f: 2, c: 11956}, {f: 3, c: 11960}, {f: 14, c: 11964},
+ {f: 41, c: 11979}, {f: 71, c: 13312}, {f: 43, c: 13384},
+ {f: 298, c: 13428}, {f: 111, c: 13727}, {f: 11, c: 13839},
+ {f: 765, c: 13851}, {f: 85, c: 14617}, {f: 96, c: 14703},
+ {f: 14, c: 14801}, {f: 147, c: 14816}, {f: 218, c: 14964},
+ {f: 287, c: 15183}, {f: 113, c: 15471}, {f: 885, c: 15585},
+ {f: 264, c: 16471}, {f: 471, c: 16736}, {f: 116, c: 17208},
+ {f: 4, c: 17325}, {f: 43, c: 17330}, {f: 248, c: 17374},
+ {f: 373, c: 17623}, {f: 20, c: 17997}, {f: 193, c: 18018},
+ {f: 5, c: 18212}, {f: 82, c: 18218}, {f: 16, c: 18301}, {f: 441, c: 18318},
+ {f: 50, c: 18760}, {f: 2, c: 18811}, {f: 4, c: 18814}, 18820,
+ {f: 20, c: 18823}, {f: 3, c: 18844}, {f: 22, c: 18848}, {f: 703, c: 18872},
+ {f: 39, c: 19576}, {f: 111, c: 19620}, {f: 148, c: 19738},
+ {f: 7, c: 19887}]
+};
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var ColorSpace = (function ColorSpaceClosure() {
+ // Constructor should define this.numComps, this.defaultColor, this.name
+ function ColorSpace() {
+ error('should not call ColorSpace constructor');
+ }
+
+ ColorSpace.prototype = {
+ // Input: array of size numComps representing color component values
+ // Output: array of rgb values, each value ranging from [0.1]
+ getRgb: function ColorSpace_getRgb(color) {
+ error('Should not call ColorSpace.getRgb: ' + color);
+ },
+ // Input: Uint8Array of component values, each value scaled to [0,255]
+ // Output: Uint8Array of rgb values, each value scaled to [0,255]
+ getRgbBuffer: function ColorSpace_getRgbBuffer(input) {
+ error('Should not call ColorSpace.getRgbBuffer: ' + input);
+ }
+ };
+
+ ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
+ var IR = ColorSpace.parseToIR(cs, xref, res);
+ if (IR instanceof AlternateCS)
+ return IR;
+
+ return ColorSpace.fromIR(IR);
+ };
+
+ ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
+ var name = isArray(IR) ? IR[0] : IR;
+
+ switch (name) {
+ case 'DeviceGrayCS':
+ return new DeviceGrayCS();
+ case 'DeviceRgbCS':
+ return new DeviceRgbCS();
+ case 'DeviceCmykCS':
+ return new DeviceCmykCS();
+ case 'PatternCS':
+ var basePatternCS = IR[1];
+ if (basePatternCS)
+ basePatternCS = ColorSpace.fromIR(basePatternCS);
+ return new PatternCS(basePatternCS);
+ case 'IndexedCS':
+ var baseIndexedCS = IR[1];
+ var hiVal = IR[2];
+ var lookup = IR[3];
+ return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
+ case 'AlternateCS':
+ var numComps = IR[1];
+ var alt = IR[2];
+ var tintFnIR = IR[3];
+
+ return new AlternateCS(numComps, ColorSpace.fromIR(alt),
+ PDFFunction.fromIR(tintFnIR));
+ case 'LabCS':
+ var whitePoint = IR[1].WhitePoint;
+ var blackPoint = IR[1].BlackPoint;
+ var range = IR[1].Range;
+ return new LabCS(whitePoint, blackPoint, range);
+ default:
+ error('Unkown name ' + name);
+ }
+ return null;
+ };
+
+ ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
+ if (isName(cs)) {
+ var colorSpaces = res.get('ColorSpace');
+ if (isDict(colorSpaces)) {
+ var refcs = colorSpaces.get(cs.name);
+ if (refcs)
+ cs = refcs;
+ }
+ }
+
+ cs = xref.fetchIfRef(cs);
+ var mode;
+
+ if (isName(cs)) {
+ mode = cs.name;
+ this.mode = mode;
+
+ switch (mode) {
+ case 'DeviceGray':
+ case 'G':
+ return 'DeviceGrayCS';
+ case 'DeviceRGB':
+ case 'RGB':
+ return 'DeviceRgbCS';
+ case 'DeviceCMYK':
+ case 'CMYK':
+ return 'DeviceCmykCS';
+ case 'Pattern':
+ return ['PatternCS', null];
+ default:
+ error('unrecognized colorspace ' + mode);
+ }
+ } else if (isArray(cs)) {
+ mode = cs[0].name;
+ this.mode = mode;
+
+ switch (mode) {
+ case 'DeviceGray':
+ case 'G':
+ return 'DeviceGrayCS';
+ case 'DeviceRGB':
+ case 'RGB':
+ return 'DeviceRgbCS';
+ case 'DeviceCMYK':
+ case 'CMYK':
+ return 'DeviceCmykCS';
+ case 'CalGray':
+ return 'DeviceGrayCS';
+ case 'CalRGB':
+ return 'DeviceRgbCS';
+ case 'ICCBased':
+ var stream = xref.fetchIfRef(cs[1]);
+ var dict = stream.dict;
+ var numComps = dict.get('N');
+ if (numComps == 1)
+ return 'DeviceGrayCS';
+ if (numComps == 3)
+ return 'DeviceRgbCS';
+ if (numComps == 4)
+ return 'DeviceCmykCS';
+ break;
+ case 'Pattern':
+ var basePatternCS = cs[1];
+ if (basePatternCS)
+ basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
+ return ['PatternCS', basePatternCS];
+ case 'Indexed':
+ case 'I':
+ var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
+ var hiVal = cs[2] + 1;
+ var lookup = xref.fetchIfRef(cs[3]);
+ return ['IndexedCS', baseIndexedCS, hiVal, lookup];
+ case 'Separation':
+ case 'DeviceN':
+ var name = cs[1];
+ var numComps = 1;
+ if (isName(name))
+ numComps = 1;
+ else if (isArray(name))
+ numComps = name.length;
+ var alt = ColorSpace.parseToIR(cs[2], xref, res);
+ var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
+ return ['AlternateCS', numComps, alt, tintFnIR];
+ case 'Lab':
+ var params = cs[1].getAll();
+ return ['LabCS', params];
+ default:
+ error('unimplemented color space object "' + mode + '"');
+ }
+ } else {
+ error('unrecognized color space object: "' + cs + '"');
+ }
+ return null;
+ };
+ /**
+ * Checks if a decode map matches the default decode map for a color space.
+ * This handles the general decode maps where there are two values per
+ * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
+ * This does not handle Lab, Indexed, or Pattern decode maps since they are
+ * slightly different.
+ * @param {Array} decode Decode map (usually from an image).
+ * @param {Number} n Number of components the color space has.
+ */
+ ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
+ if (!decode)
+ return true;
+
+ if (n * 2 !== decode.length) {
+ warning('The decode map is not the correct length');
+ return true;
+ }
+ for (var i = 0, ii = decode.length; i < ii; i += 2) {
+ if (decode[i] != 0 || decode[i + 1] != 1)
+ return false;
+ }
+ return true;
+ };
+
+ return ColorSpace;
+})();
+
+/**
+ * Alternate color space handles both Separation and DeviceN color spaces. A
+ * Separation color space is actually just a DeviceN with one color component.
+ * Both color spaces use a tinting function to convert colors to a base color
+ * space.
+ */
+var AlternateCS = (function AlternateCSClosure() {
+ function AlternateCS(numComps, base, tintFn) {
+ this.name = 'Alternate';
+ this.numComps = numComps;
+ this.defaultColor = [];
+ for (var i = 0; i < numComps; ++i)
+ this.defaultColor.push(1);
+ this.base = base;
+ this.tintFn = tintFn;
+ }
+
+ AlternateCS.prototype = {
+ getRgb: function AlternateCS_getRgb(color) {
+ var tinted = this.tintFn(color);
+ return this.base.getRgb(tinted);
+ },
+ getRgbBuffer: function AlternateCS_getRgbBuffer(input, bits) {
+ var tintFn = this.tintFn;
+ var base = this.base;
+ var scale = 1 / ((1 << bits) - 1);
+ var length = input.length;
+ var pos = 0;
+ var baseNumComps = base.numComps;
+ var baseBuf = new Uint8Array(baseNumComps * length);
+ var numComps = this.numComps;
+ var scaled = [];
+
+ for (var i = 0; i < length; i += numComps) {
+ for (var z = 0; z < numComps; ++z)
+ scaled[z] = input[i + z] * scale;
+
+ var tinted = tintFn(scaled);
+ for (var j = 0; j < baseNumComps; ++j)
+ baseBuf[pos++] = 255 * tinted[j];
+ }
+ return base.getRgbBuffer(baseBuf, 8);
+ },
+ isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ }
+ };
+
+ return AlternateCS;
+})();
+
+var PatternCS = (function PatternCSClosure() {
+ function PatternCS(baseCS) {
+ this.name = 'Pattern';
+ this.base = baseCS;
+ }
+ PatternCS.prototype = {};
+
+ return PatternCS;
+})();
+
+var IndexedCS = (function IndexedCSClosure() {
+ function IndexedCS(base, highVal, lookup) {
+ this.name = 'Indexed';
+ this.numComps = 1;
+ this.defaultColor = [0];
+ this.base = base;
+ this.highVal = highVal;
+
+ var baseNumComps = base.numComps;
+ var length = baseNumComps * highVal;
+ var lookupArray = new Uint8Array(length);
+
+ if (isStream(lookup)) {
+ var bytes = lookup.getBytes(length);
+ lookupArray.set(bytes);
+ } else if (isString(lookup)) {
+ for (var i = 0; i < length; ++i)
+ lookupArray[i] = lookup.charCodeAt(i);
+ } else {
+ error('Unrecognized lookup table: ' + lookup);
+ }
+ this.lookup = lookupArray;
+ }
+
+ IndexedCS.prototype = {
+ getRgb: function IndexedCS_getRgb(color) {
+ var numComps = this.base.numComps;
+ var start = color[0] * numComps;
+ var c = [];
+
+ for (var i = start, ii = start + numComps; i < ii; ++i)
+ c.push(this.lookup[i]);
+
+ return this.base.getRgb(c);
+ },
+ getRgbBuffer: function IndexedCS_getRgbBuffer(input) {
+ var base = this.base;
+ var numComps = base.numComps;
+ var lookup = this.lookup;
+ var length = input.length;
+ var baseBuf = new Uint8Array(length * numComps);
+ var baseBufPos = 0;
+
+ for (var i = 0; i < length; ++i) {
+ var lookupPos = input[i] * numComps;
+ for (var j = 0; j < numComps; ++j) {
+ baseBuf[baseBufPos++] = lookup[lookupPos + j];
+ }
+ }
+
+ return base.getRgbBuffer(baseBuf, 8);
+ },
+ isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
+ // indexed color maps shouldn't be changed
+ return true;
+ }
+ };
+ return IndexedCS;
+})();
+
+var DeviceGrayCS = (function DeviceGrayCSClosure() {
+ function DeviceGrayCS() {
+ this.name = 'DeviceGray';
+ this.numComps = 1;
+ this.defaultColor = [0];
+ }
+
+ DeviceGrayCS.prototype = {
+ getRgb: function DeviceGrayCS_getRgb(color) {
+ var c = color[0];
+ return [c, c, c];
+ },
+ getRgbBuffer: function DeviceGrayCS_getRgbBuffer(input, bits) {
+ var scale = 255 / ((1 << bits) - 1);
+ var length = input.length;
+ var rgbBuf = new Uint8Array(length * 3);
+ for (var i = 0, j = 0; i < length; ++i) {
+ var c = (scale * input[i]) | 0;
+ rgbBuf[j++] = c;
+ rgbBuf[j++] = c;
+ rgbBuf[j++] = c;
+ }
+ return rgbBuf;
+ },
+ isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ }
+ };
+ return DeviceGrayCS;
+})();
+
+var DeviceRgbCS = (function DeviceRgbCSClosure() {
+ function DeviceRgbCS() {
+ this.name = 'DeviceRGB';
+ this.numComps = 3;
+ this.defaultColor = [0, 0, 0];
+ }
+ DeviceRgbCS.prototype = {
+ getRgb: function DeviceRgbCS_getRgb(color) {
+ return color;
+ },
+ getRgbBuffer: function DeviceRgbCS_getRgbBuffer(input, bits) {
+ if (bits == 8)
+ return input;
+ var scale = 255 / ((1 << bits) - 1);
+ var i, length = input.length;
+ var rgbBuf = new Uint8Array(length);
+ for (i = 0; i < length; ++i)
+ rgbBuf[i] = (scale * input[i]) | 0;
+ return rgbBuf;
+ },
+ isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ }
+ };
+ return DeviceRgbCS;
+})();
+
+var DeviceCmykCS = (function DeviceCmykCSClosure() {
+ function DeviceCmykCS() {
+ this.name = 'DeviceCMYK';
+ this.numComps = 4;
+ this.defaultColor = [0, 0, 0, 1];
+ }
+ DeviceCmykCS.prototype = {
+ getRgb: function DeviceCmykCS_getRgb(color) {
+ var c = color[0], m = color[1], y = color[2], k = color[3];
+
+ // CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
+ c = (c * (1 - k) + k);
+ m = (m * (1 - k) + k);
+ y = (y * (1 - k) + k);
+
+ // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
+ var r = (1 - c);
+ var g = (1 - m);
+ var b = (1 - y);
+
+ return [r, g, b];
+ },
+ getRgbBuffer: function DeviceCmykCS_getRgbBuffer(colorBuf, bits) {
+ var scale = 1 / ((1 << bits) - 1);
+ var length = colorBuf.length / 4;
+ var rgbBuf = new Uint8Array(length * 3);
+ var rgbBufPos = 0;
+ var colorBufPos = 0;
+
+ for (var i = 0; i < length; i++) {
+ var cmyk = [];
+ for (var j = 0; j < 4; ++j)
+ cmyk.push(scale * colorBuf[colorBufPos++]);
+
+ var rgb = this.getRgb(cmyk);
+ for (var j = 0; j < 3; ++j)
+ rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255);
+ }
+
+ return rgbBuf;
+ },
+ isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ }
+ };
+
+ return DeviceCmykCS;
+})();
+
+//
+// LabCS: Based on "PDF Reference, Sixth Ed", p.250
+//
+var LabCS = (function LabCSClosure() {
+ function LabCS(whitePoint, blackPoint, range) {
+ this.name = 'Lab';
+ this.numComps = 3;
+ this.defaultColor = [0, 0, 0];
+
+ if (!whitePoint)
+ error('WhitePoint missing - required for color space Lab');
+ blackPoint = blackPoint || [0, 0, 0];
+ range = range || [-100, 100, -100, 100];
+
+ // Translate args to spec variables
+ this.XW = whitePoint[0];
+ this.YW = whitePoint[1];
+ this.ZW = whitePoint[2];
+ this.amin = range[0];
+ this.amax = range[1];
+ this.bmin = range[2];
+ this.bmax = range[3];
+
+ // These are here just for completeness - the spec doesn't offer any
+ // formulas that use BlackPoint in Lab
+ this.XB = blackPoint[0];
+ this.YB = blackPoint[1];
+ this.ZB = blackPoint[2];
+
+ // Validate vars as per spec
+ if (this.XW < 0 || this.ZW < 0 || this.YW !== 1)
+ error('Invalid WhitePoint components, no fallback available');
+
+ if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
+ warn('Invalid BlackPoint, falling back to default');
+ this.XB = this.YB = this.ZB = 0;
+ }
+
+ if (this.amin > this.amax || this.bmin > this.bmax) {
+ warn('Invalid Range, falling back to defaults');
+ this.amin = -100;
+ this.amax = 100;
+ this.bmin = -100;
+ this.bmax = 100;
+ }
+ };
+
+ // Function g(x) from spec
+ function g(x) {
+ if (x >= 6 / 29)
+ return x * x * x;
+ else
+ return (108 / 841) * (x - 4 / 29);
+ }
+
+ LabCS.prototype = {
+ getRgb: function LabCS_getRgb(color) {
+ // Ls,as,bs <---> L*,a*,b* in the spec
+ var Ls = color[0], as = color[1], bs = color[2];
+
+ // Adjust limits of 'as' and 'bs'
+ as = as > this.amax ? this.amax : as;
+ as = as < this.amin ? this.amin : as;
+ bs = bs > this.bmax ? this.bmax : bs;
+ bs = bs < this.bmin ? this.bmin : bs;
+
+ // Computes intermediate variables X,Y,Z as per spec
+ var M = (Ls + 16) / 116;
+ var L = M + (as / 500);
+ var N = M - (bs / 200);
+ var X = this.XW * g(L);
+ var Y = this.YW * g(M);
+ var Z = this.ZW * g(N);
+
+ // XYZ to RGB 3x3 matrix, from:
+ // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC18
+ var XYZtoRGB = [3.240479, -1.537150, -0.498535,
+ -0.969256, 1.875992, 0.041556,
+ 0.055648, -0.204043, 1.057311];
+
+ return Util.apply3dTransform(XYZtoRGB, [X, Y, Z]);
+ },
+ getRgbBuffer: function LabCS_getRgbBuffer(input, bits) {
+ if (bits == 8)
+ return input;
+ var scale = 255 / ((1 << bits) - 1);
+ var i, length = input.length / 3;
+ var rgbBuf = new Uint8Array(length);
+
+ var j = 0;
+ for (i = 0; i < length; ++i) {
+ // Convert L*, a*, s* into RGB
+ var rgb = this.getRgb([input[i], input[i + 1], input[i + 2]]);
+ rgbBuf[j++] = rgb[0];
+ rgbBuf[j++] = rgb[1];
+ rgbBuf[j++] = rgb[2];
+ }
+
+ return rgbBuf;
+ },
+ isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
+ // From Table 90 in Adobe's:
+ // "Document management - Portable document format", 1st ed, 2008
+ if (decodeMap[0] === 0 && decodeMap[1] === 100 &&
+ decodeMap[2] === this.amin && decodeMap[3] === this.amax &&
+ decodeMap[4] === this.bmin && decodeMap[5] === this.bmax)
+ return true;
+ else
+ return false;
+ }
+ };
+ return LabCS;
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var ARCFourCipher = (function ARCFourCipherClosure() {
+ function ARCFourCipher(key) {
+ this.a = 0;
+ this.b = 0;
+ var s = new Uint8Array(256);
+ var i, j = 0, tmp, keyLength = key.length;
+ for (i = 0; i < 256; ++i)
+ s[i] = i;
+ for (i = 0; i < 256; ++i) {
+ tmp = s[i];
+ j = (j + tmp + key[i % keyLength]) & 0xFF;
+ s[i] = s[j];
+ s[j] = tmp;
+ }
+ this.s = s;
+ }
+
+ ARCFourCipher.prototype = {
+ encryptBlock: function ARCFourCipher_encryptBlock(data) {
+ var i, n = data.length, tmp, tmp2;
+ var a = this.a, b = this.b, s = this.s;
+ var output = new Uint8Array(n);
+ for (i = 0; i < n; ++i) {
+ a = (a + 1) & 0xFF;
+ tmp = s[a];
+ b = (b + tmp) & 0xFF;
+ tmp2 = s[b];
+ s[a] = tmp2;
+ s[b] = tmp;
+ output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF];
+ }
+ this.a = a;
+ this.b = b;
+ return output;
+ }
+ };
+ ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
+
+ return ARCFourCipher;
+})();
+
+var calculateMD5 = (function calculateMD5Closure() {
+ var r = new Uint8Array([
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
+
+ var k = new Int32Array([
+ -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
+ -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
+ 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
+ 643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
+ 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
+ 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
+ -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
+ -722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
+ -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
+ -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
+ -145523070, -1120210379, 718787259, -343485551]);
+
+ function hash(data, offset, length) {
+ var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
+ // pre-processing
+ var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
+ var padded = new Uint8Array(paddedLength);
+ var i, j, n;
+ for (i = 0; i < length; ++i)
+ padded[i] = data[offset++];
+ padded[i++] = 0x80;
+ n = paddedLength - 8;
+ while (i < n)
+ padded[i++] = 0;
+ padded[i++] = (length << 3) & 0xFF;
+ padded[i++] = (length >> 5) & 0xFF;
+ padded[i++] = (length >> 13) & 0xFF;
+ padded[i++] = (length >> 21) & 0xFF;
+ padded[i++] = (length >>> 29) & 0xFF;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ // chunking
+ // TODO ArrayBuffer ?
+ var w = new Int32Array(16);
+ for (i = 0; i < paddedLength;) {
+ for (j = 0; j < 16; ++j, i += 4) {
+ w[j] = (padded[i] | (padded[i + 1] << 8) |
+ (padded[i + 2] << 16) | (padded[i + 3] << 24));
+ }
+ var a = h0, b = h1, c = h2, d = h3, f, g;
+ for (j = 0; j < 64; ++j) {
+ if (j < 16) {
+ f = (b & c) | ((~b) & d);
+ g = j;
+ } else if (j < 32) {
+ f = (d & b) | ((~d) & c);
+ g = (5 * j + 1) & 15;
+ } else if (j < 48) {
+ f = b ^ c ^ d;
+ g = (3 * j + 5) & 15;
+ } else {
+ f = c ^ (b | (~d));
+ g = (7 * j) & 15;
+ }
+ var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
+ d = c;
+ c = b;
+ b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
+ a = tmp;
+ }
+ h0 = (h0 + a) | 0;
+ h1 = (h1 + b) | 0;
+ h2 = (h2 + c) | 0;
+ h3 = (h3 + d) | 0;
+ }
+ return new Uint8Array([
+ h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
+ h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
+ h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
+ h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
+ ]);
+ }
+ return hash;
+})();
+
+var NullCipher = (function NullCipherClosure() {
+ function NullCipher() {
+ }
+
+ NullCipher.prototype = {
+ decryptBlock: function NullCipher_decryptBlock(data) {
+ return data;
+ }
+ };
+
+ return NullCipher;
+})();
+
+var AES128Cipher = (function AES128CipherClosure() {
+ var rcon = new Uint8Array([
+ 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
+ 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
+ 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
+ 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
+ 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+ 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6,
+ 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
+ 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
+ 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
+ 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e,
+ 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
+ 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
+ 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02,
+ 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
+ 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
+ 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
+ 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb,
+ 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
+ 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
+ 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
+ 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
+ 0x74, 0xe8, 0xcb, 0x8d]);
+
+ var s = new Uint8Array([
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
+ 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+ 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
+ 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
+ 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+ 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
+ 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
+ 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+ 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
+ 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
+ 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+ 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
+ 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
+ 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+ 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
+ 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
+ 0xb0, 0x54, 0xbb, 0x16]);
+
+ var inv_s = new Uint8Array([
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
+ 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+ 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
+ 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
+ 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+ 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
+ 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
+ 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+ 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
+ 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
+ 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+ 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
+ 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
+ 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+ 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
+ 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
+ 0x55, 0x21, 0x0c, 0x7d]);
+
+ var mix = new Uint32Array([
+ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927,
+ 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45,
+ 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb,
+ 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381,
+ 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf,
+ 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66,
+ 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28,
+ 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012,
+ 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec,
+ 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e,
+ 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd,
+ 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7,
+ 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89,
+ 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b,
+ 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815,
+ 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f,
+ 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa,
+ 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8,
+ 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36,
+ 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c,
+ 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742,
+ 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea,
+ 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4,
+ 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e,
+ 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360,
+ 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502,
+ 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87,
+ 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd,
+ 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3,
+ 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621,
+ 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f,
+ 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55,
+ 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26,
+ 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844,
+ 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba,
+ 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480,
+ 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce,
+ 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67,
+ 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929,
+ 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713,
+ 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed,
+ 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f,
+ 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
+
+ function expandKey128(cipherKey) {
+ var b = 176, result = new Uint8Array(b);
+ result.set(cipherKey);
+ for (var j = 16, i = 1; j < b; ++i) {
+ // RotWord
+ var t1 = result[j - 3], t2 = result[j - 2],
+ t3 = result[j - 1], t4 = result[j - 4];
+ // SubWord
+ t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4];
+ // Rcon
+ t1 = t1 ^ rcon[i];
+ for (var n = 0; n < 4; ++n) {
+ result[j] = (t1 ^= result[j - 16]); j++;
+ result[j] = (t2 ^= result[j - 16]); j++;
+ result[j] = (t3 ^= result[j - 16]); j++;
+ result[j] = (t4 ^= result[j - 16]); j++;
+ }
+ }
+ return result;
+ }
+
+ function decrypt128(input, key) {
+ var state = new Uint8Array(16);
+ state.set(input);
+ var i, j, k;
+ var t, u, v;
+ // AddRoundKey
+ for (j = 0, k = 160; j < 16; ++j, ++k)
+ state[j] ^= key[k];
+ for (i = 9; i >= 1; --i) {
+ // InvShiftRows
+ t = state[13]; state[13] = state[9]; state[9] = state[5];
+ state[5] = state[1]; state[1] = t;
+ t = state[14]; u = state[10]; state[14] = state[6];
+ state[10] = state[2]; state[6] = t; state[2] = u;
+ t = state[15]; u = state[11]; v = state[7]; state[15] = state[3];
+ state[11] = t; state[7] = u; state[3] = v;
+ // InvSubBytes
+ for (j = 0; j < 16; ++j)
+ state[j] = inv_s[state[j]];
+ // AddRoundKey
+ for (j = 0, k = i * 16; j < 16; ++j, ++k)
+ state[j] ^= key[k];
+ // InvMixColumns
+ for (j = 0; j < 16; j += 4) {
+ var s0 = mix[state[j]], s1 = mix[state[j + 1]],
+ s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
+ t = (s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^
+ (s3 >>> 24) ^ (s3 << 8));
+ state[j] = (t >>> 24) & 0xFF;
+ state[j + 1] = (t >> 16) & 0xFF;
+ state[j + 2] = (t >> 8) & 0xFF;
+ state[j + 3] = t & 0xFF;
+ }
+ }
+ // InvShiftRows
+ t = state[13]; state[13] = state[9]; state[9] = state[5];
+ state[5] = state[1]; state[1] = t;
+ t = state[14]; u = state[10]; state[14] = state[6];
+ state[10] = state[2]; state[6] = t; state[2] = u;
+ t = state[15]; u = state[11]; v = state[7]; state[15] = state[3];
+ state[11] = t; state[7] = u; state[3] = v;
+ for (j = 0; j < 16; ++j) {
+ // InvSubBytes
+ state[j] = inv_s[state[j]];
+ // AddRoundKey
+ state[j] ^= key[j];
+ }
+ return state;
+ }
+
+ function AES128Cipher(key) {
+ this.key = expandKey128(key);
+ this.buffer = new Uint8Array(16);
+ this.bufferPosition = 0;
+ }
+
+ function decryptBlock2(data) {
+ var i, j, ii, sourceLength = data.length,
+ buffer = this.buffer, bufferLength = this.bufferPosition,
+ result = [], iv = this.iv;
+ for (i = 0; i < sourceLength; ++i) {
+ buffer[bufferLength] = data[i];
+ ++bufferLength;
+ if (bufferLength < 16)
+ continue;
+ // buffer is full, decrypting
+ var plain = decrypt128(buffer, this.key);
+ // xor-ing the IV vector to get plain text
+ for (j = 0; j < 16; ++j)
+ plain[j] ^= iv[j];
+ iv = buffer;
+ result.push(plain);
+ buffer = new Uint8Array(16);
+ bufferLength = 0;
+ }
+ // saving incomplete buffer
+ this.buffer = buffer;
+ this.bufferLength = bufferLength;
+ this.iv = iv;
+ if (result.length == 0)
+ return new Uint8Array([]);
+ if (result.length == 1)
+ return result[0];
+ // combining plain text blocks into one
+ var output = new Uint8Array(16 * result.length);
+ for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
+ output.set(result[i], j);
+ return output;
+ }
+
+ AES128Cipher.prototype = {
+ decryptBlock: function AES128Cipher_decryptBlock(data) {
+ var i, sourceLength = data.length;
+ var buffer = this.buffer, bufferLength = this.bufferPosition;
+ // waiting for IV values -- they are at the start of the stream
+ for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength)
+ buffer[bufferLength] = data[i];
+ if (bufferLength < 16) {
+ // need more data
+ this.bufferLength = bufferLength;
+ return new Uint8Array([]);
+ }
+ this.iv = buffer;
+ this.buffer = new Uint8Array(16);
+ this.bufferLength = 0;
+ // starting decryption
+ this.decryptBlock = decryptBlock2;
+ return this.decryptBlock(data.subarray(16));
+ }
+ };
+
+ return AES128Cipher;
+})();
+
+var CipherTransform = (function CipherTransformClosure() {
+ function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
+ this.stringCipherConstructor = stringCipherConstructor;
+ this.streamCipherConstructor = streamCipherConstructor;
+ }
+ CipherTransform.prototype = {
+ createStream: function CipherTransform_createStream(stream) {
+ var cipher = new this.streamCipherConstructor();
+ return new DecryptStream(stream,
+ function cipherTransformDecryptStream(data) {
+ return cipher.decryptBlock(data);
+ }
+ );
+ },
+ decryptString: function CipherTransform_decryptString(s) {
+ var cipher = new this.stringCipherConstructor();
+ var data = stringToBytes(s);
+ data = cipher.decryptBlock(data);
+ return bytesToString(data);
+ }
+ };
+ return CipherTransform;
+})();
+
+var CipherTransformFactory = (function CipherTransformFactoryClosure() {
+ function prepareKeyData(fileId, password, ownerPassword, userPassword,
+ flags, revision, keyLength, encryptMetadata) {
+ var defaultPasswordBytes = new Uint8Array([
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
+ 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
+ 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
+ var hashData = new Uint8Array(100), i = 0, j, n;
+ if (password) {
+ n = Math.min(32, password.length);
+ for (; i < n; ++i)
+ hashData[i] = password[i];
+ }
+ j = 0;
+ while (i < 32) {
+ hashData[i++] = defaultPasswordBytes[j++];
+ }
+ // as now the padded password in the hashData[0..i]
+ for (j = 0, n = ownerPassword.length; j < n; ++j)
+ hashData[i++] = ownerPassword[j];
+ hashData[i++] = flags & 0xFF;
+ hashData[i++] = (flags >> 8) & 0xFF;
+ hashData[i++] = (flags >> 16) & 0xFF;
+ hashData[i++] = (flags >>> 24) & 0xFF;
+ for (j = 0, n = fileId.length; j < n; ++j)
+ hashData[i++] = fileId[j];
+ if (revision >= 4 && !encryptMetadata) {
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ }
+ var hash = calculateMD5(hashData, 0, i);
+ var keyLengthInBytes = keyLength >> 3;
+ if (revision >= 3) {
+ for (j = 0; j < 50; ++j) {
+ hash = calculateMD5(hash, 0, keyLengthInBytes);
+ }
+ }
+ var encryptionKey = hash.subarray(0, keyLengthInBytes);
+ var cipher, checkData;
+
+ if (revision >= 3) {
+ // padded password in hashData, we can use this array for user
+ // password check
+ i = 32;
+ for (j = 0, n = fileId.length; j < n; ++j)
+ hashData[i++] = fileId[j];
+ cipher = new ARCFourCipher(encryptionKey);
+ var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
+ n = encryptionKey.length;
+ var derivedKey = new Uint8Array(n), k;
+ for (j = 1; j <= 19; ++j) {
+ for (k = 0; k < n; ++k)
+ derivedKey[k] = encryptionKey[k] ^ j;
+ cipher = new ARCFourCipher(derivedKey);
+ checkData = cipher.encryptBlock(checkData);
+ }
+ } else {
+ cipher = new ARCFourCipher(encryptionKey);
+ checkData = cipher.encryptBlock(hashData.subarray(0, 32));
+ }
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] != checkData[j])
+ error('incorrect password');
+ }
+ return encryptionKey;
+ }
+
+ var identityName = new Name('Identity');
+
+ function CipherTransformFactory(dict, fileId, password) {
+ var filter = dict.get('Filter');
+ if (!isName(filter) || filter.name != 'Standard')
+ error('unknown encryption method');
+ this.dict = dict;
+ var algorithm = dict.get('V');
+ if (!isInt(algorithm) ||
+ (algorithm != 1 && algorithm != 2 && algorithm != 4))
+ error('unsupported encryption algorithm');
+ this.algorithm = algorithm;
+ var keyLength = dict.get('Length') || 40;
+ if (!isInt(keyLength) ||
+ keyLength < 40 || (keyLength % 8) != 0)
+ error('invalid key length');
+ // prepare keys
+ var ownerPassword = stringToBytes(dict.get('O'));
+ var userPassword = stringToBytes(dict.get('U'));
+ var flags = dict.get('P');
+ var revision = dict.get('R');
+ var encryptMetadata =
+ dict.get('EncryptMetadata') !== false; // makes true as default value
+ var fileIdBytes = stringToBytes(fileId);
+ var passwordBytes;
+ if (password)
+ passwordBytes = stringToBytes(password);
+
+ this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
+ ownerPassword, userPassword,
+ flags, revision,
+ keyLength, encryptMetadata);
+ if (algorithm == 4) {
+ this.cf = dict.get('CF');
+ this.stmf = dict.get('StmF') || identityName;
+ this.strf = dict.get('StrF') || identityName;
+ this.eff = dict.get('EFF') || this.strf;
+ }
+ }
+
+ function buildObjectKey(num, gen, encryptionKey, isAes) {
+ var key = new Uint8Array(encryptionKey.length + 9), i, n;
+ for (i = 0, n = encryptionKey.length; i < n; ++i)
+ key[i] = encryptionKey[i];
+ key[i++] = num & 0xFF;
+ key[i++] = (num >> 8) & 0xFF;
+ key[i++] = (num >> 16) & 0xFF;
+ key[i++] = gen & 0xFF;
+ key[i++] = (gen >> 8) & 0xFF;
+ if (isAes) {
+ key[i++] = 0x73;
+ key[i++] = 0x41;
+ key[i++] = 0x6C;
+ key[i++] = 0x54;
+ }
+ var hash = calculateMD5(key, 0, i);
+ return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
+ }
+
+ function buildCipherConstructor(cf, name, num, gen, key) {
+ var cryptFilter = cf.get(name.name);
+ var cfm;
+ if (cryptFilter != null)
+ cfm = cryptFilter.get('CFM');
+ if (!cfm || cfm.name == 'None') {
+ return function cipherTransformFactoryBuildCipherConstructorNone() {
+ return new NullCipher();
+ };
+ }
+ if ('V2' == cfm.name) {
+ return function cipherTransformFactoryBuildCipherConstructorV2() {
+ return new ARCFourCipher(
+ buildObjectKey(num, gen, key, false));
+ };
+ }
+ if ('AESV2' == cfm.name) {
+ return function cipherTransformFactoryBuildCipherConstructorAESV2() {
+ return new AES128Cipher(
+ buildObjectKey(num, gen, key, true));
+ };
+ }
+ error('Unknown crypto method');
+ }
+
+ CipherTransformFactory.prototype = {
+ createCipherTransform:
+ function CipherTransformFactory_createCipherTransform(num, gen) {
+ if (this.algorithm == 4) {
+ return new CipherTransform(
+ buildCipherConstructor(this.cf, this.stmf,
+ num, gen, this.encryptionKey),
+ buildCipherConstructor(this.cf, this.strf,
+ num, gen, this.encryptionKey));
+ }
+ // algorithms 1 and 2
+ var key = buildObjectKey(num, gen, this.encryptionKey, false);
+ var cipherConstructor = function buildCipherCipherConstructor() {
+ return new ARCFourCipher(key);
+ };
+ return new CipherTransform(cipherConstructor, cipherConstructor);
+ }
+ };
+
+ return CipherTransformFactory;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var PartialEvaluator = (function PartialEvaluatorClosure() {
+ function PartialEvaluator(xref, handler, uniquePrefix) {
+ this.state = new EvalState();
+ this.stateStack = [];
+
+ this.xref = xref;
+ this.handler = handler;
+ this.uniquePrefix = uniquePrefix;
+ this.objIdCounter = 0;
+ }
+
+ var OP_MAP = {
+ // Graphics state
+ w: 'setLineWidth',
+ J: 'setLineCap',
+ j: 'setLineJoin',
+ M: 'setMiterLimit',
+ d: 'setDash',
+ ri: 'setRenderingIntent',
+ i: 'setFlatness',
+ gs: 'setGState',
+ q: 'save',
+ Q: 'restore',
+ cm: 'transform',
+
+ // Path
+ m: 'moveTo',
+ l: 'lineTo',
+ c: 'curveTo',
+ v: 'curveTo2',
+ y: 'curveTo3',
+ h: 'closePath',
+ re: 'rectangle',
+ S: 'stroke',
+ s: 'closeStroke',
+ f: 'fill',
+ F: 'fill',
+ 'f*': 'eoFill',
+ B: 'fillStroke',
+ 'B*': 'eoFillStroke',
+ b: 'closeFillStroke',
+ 'b*': 'closeEOFillStroke',
+ n: 'endPath',
+
+ // Clipping
+ W: 'clip',
+ 'W*': 'eoClip',
+
+ // Text
+ BT: 'beginText',
+ ET: 'endText',
+ Tc: 'setCharSpacing',
+ Tw: 'setWordSpacing',
+ Tz: 'setHScale',
+ TL: 'setLeading',
+ Tf: 'setFont',
+ Tr: 'setTextRenderingMode',
+ Ts: 'setTextRise',
+ Td: 'moveText',
+ TD: 'setLeadingMoveText',
+ Tm: 'setTextMatrix',
+ 'T*': 'nextLine',
+ Tj: 'showText',
+ TJ: 'showSpacedText',
+ "'": 'nextLineShowText',
+ '"': 'nextLineSetSpacingShowText',
+
+ // Type3 fonts
+ d0: 'setCharWidth',
+ d1: 'setCharWidthAndBounds',
+
+ // Color
+ CS: 'setStrokeColorSpace',
+ cs: 'setFillColorSpace',
+ SC: 'setStrokeColor',
+ SCN: 'setStrokeColorN',
+ sc: 'setFillColor',
+ scn: 'setFillColorN',
+ G: 'setStrokeGray',
+ g: 'setFillGray',
+ RG: 'setStrokeRGBColor',
+ rg: 'setFillRGBColor',
+ K: 'setStrokeCMYKColor',
+ k: 'setFillCMYKColor',
+
+ // Shading
+ sh: 'shadingFill',
+
+ // Images
+ BI: 'beginInlineImage',
+ ID: 'beginImageData',
+ EI: 'endInlineImage',
+
+ // XObjects
+ Do: 'paintXObject',
+
+ // Marked content
+ MP: 'markPoint',
+ DP: 'markPointProps',
+ BMC: 'beginMarkedContent',
+ BDC: 'beginMarkedContentProps',
+ EMC: 'endMarkedContent',
+
+ // Compatibility
+ BX: 'beginCompat',
+ EX: 'endCompat'
+ };
+
+ function splitCombinedOperations(operations) {
+ // Two operations can be combined together, trying to find which two
+ // operations were concatenated.
+ for (var i = operations.length - 1; i > 0; i--) {
+ var op1 = operations.substring(0, i), op2 = operations.substring(i);
+ if (op1 in OP_MAP && op2 in OP_MAP)
+ return [op1, op2]; // operations found
+ }
+ return null;
+ }
+
+ PartialEvaluator.prototype = {
+ getOperatorList: function PartialEvaluator_getOperatorList(stream,
+ resources,
+ dependency,
+ queue) {
+
+ var self = this;
+ var xref = this.xref;
+ var handler = this.handler;
+ var uniquePrefix = this.uniquePrefix || '';
+
+ function insertDependency(depList) {
+ fnArray.push('dependency');
+ argsArray.push(depList);
+ for (var i = 0, ii = depList.length; i < ii; i++) {
+ var dep = depList[i];
+ if (dependency.indexOf(dep) == -1) {
+ dependency.push(depList[i]);
+ }
+ }
+ }
+
+ function handleSetFont(fontName, font) {
+ var loadedName = null;
+
+ var fontRes = resources.get('Font');
+
+ assert(fontRes, 'fontRes not available');
+
+ font = xref.fetchIfRef(font) || fontRes.get(fontName);
+ assertWellFormed(isDict(font));
+ ++self.objIdCounter;
+ if (!font.translated) {
+ font.translated = self.translateFont(font, xref, resources,
+ dependency);
+ if (font.translated) {
+ // keep track of each font we translated so the caller can
+ // load them asynchronously before calling display on a page
+ loadedName = 'font_' + uniquePrefix + self.objIdCounter;
+ font.translated.properties.loadedName = loadedName;
+ font.loadedName = loadedName;
+
+ var translated = font.translated;
+ // Convert the file to an ArrayBuffer which will be turned back into
+ // a Stream in the main thread.
+ if (translated.file)
+ translated.file = translated.file.getBytes();
+ if (translated.properties.file) {
+ translated.properties.file =
+ translated.properties.file.getBytes();
+ }
+
+ handler.send('obj', [
+ loadedName,
+ 'Font',
+ translated.name,
+ translated.file,
+ translated.properties
+ ]);
+ }
+ }
+ loadedName = loadedName || font.loadedName;
+
+ // Ensure the font is ready before the font is set
+ // and later on used for drawing.
+ // OPTIMIZE: This should get insert to the operatorList only once per
+ // page.
+ insertDependency([loadedName]);
+ return loadedName;
+ }
+
+ function buildPaintImageXObject(image, inline) {
+ var dict = image.dict;
+ var w = dict.get('Width', 'W');
+ var h = dict.get('Height', 'H');
+
+ var imageMask = dict.get('ImageMask', 'IM') || false;
+ if (imageMask) {
+ // This depends on a tmpCanvas beeing filled with the
+ // current fillStyle, such that processing the pixel
+ // data can't be done here. Instead of creating a
+ // complete PDFImage, only read the information needed
+ // for later.
+
+ var width = dict.get('Width', 'W');
+ var height = dict.get('Height', 'H');
+ var bitStrideLength = (width + 7) >> 3;
+ var imgArray = image.getBytes(bitStrideLength * height);
+ var decode = dict.get('Decode', 'D');
+ var inverseDecode = !!decode && decode[0] > 0;
+
+ fn = 'paintImageMaskXObject';
+ args = [imgArray, inverseDecode, width, height];
+ return;
+ }
+
+ // If there is no imageMask, create the PDFImage and a lot
+ // of image processing can be done here.
+ var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
+ insertDependency([objId]);
+ args = [objId, w, h];
+
+ var softMask = dict.get('SMask', 'IM') || false;
+ if (!softMask && image instanceof JpegStream &&
+ image.isNativelySupported(xref, resources)) {
+ // These JPEGs don't need any more processing so we can just send it.
+ fn = 'paintJpegXObject';
+ handler.send('obj', [objId, 'JpegStream', image.getIR()]);
+ return;
+ }
+
+ fn = 'paintImageXObject';
+
+ PDFImage.buildImage(function(imageObj) {
+ var drawWidth = imageObj.drawWidth;
+ var drawHeight = imageObj.drawHeight;
+ var imgData = {
+ width: drawWidth,
+ height: drawHeight,
+ data: new Uint8Array(drawWidth * drawHeight * 4)
+ };
+ var pixels = imgData.data;
+ imageObj.fillRgbaBuffer(pixels, drawWidth, drawHeight);
+ handler.send('obj', [objId, 'Image', imgData]);
+ }, handler, xref, resources, image, inline);
+ }
+
+ if (!queue)
+ queue = {};
+
+ if (!queue.argsArray) {
+ queue.argsArray = [];
+ }
+ if (!queue.fnArray) {
+ queue.fnArray = [];
+ }
+
+ var fnArray = queue.fnArray, argsArray = queue.argsArray;
+ var dependencyArray = dependency || [];
+
+ resources = resources || new Dict();
+ var xobjs = resources.get('XObject') || new Dict();
+ var patterns = resources.get('Pattern') || new Dict();
+ var parser = new Parser(new Lexer(stream), false, xref);
+ var res = resources;
+ var hasNextObj = false, nextObj;
+ var args = [], obj;
+ var TILING_PATTERN = 1, SHADING_PATTERN = 2;
+
+ while (true) {
+ if (hasNextObj) {
+ obj = nextObj;
+ hasNextObj = false;
+ } else {
+ obj = parser.getObj();
+ if (isEOF(obj))
+ break;
+ }
+
+ if (isCmd(obj)) {
+ var cmd = obj.cmd;
+ var fn = OP_MAP[cmd];
+ if (!fn) {
+ // invalid content command, trying to recover
+ var cmds = splitCombinedOperations(cmd);
+ if (cmds) {
+ cmd = cmds[0];
+ fn = OP_MAP[cmd];
+ // feeding other command on the next interation
+ hasNextObj = true;
+ nextObj = Cmd.get(cmds[1]);
+ }
+ }
+ assertWellFormed(fn, 'Unknown command "' + cmd + '"');
+ // TODO figure out how to type-check vararg functions
+
+ if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
+ // compile tiling patterns
+ var patternName = args[args.length - 1];
+ // SCN/scn applies patterns along with normal colors
+ if (isName(patternName)) {
+ var pattern = patterns.get(patternName.name);
+ if (pattern) {
+ var dict = isStream(pattern) ? pattern.dict : pattern;
+ var typeNum = dict.get('PatternType');
+
+ if (typeNum == TILING_PATTERN) {
+ // Create an IR of the pattern code.
+ var depIdx = dependencyArray.length;
+ var operatorList = this.getOperatorList(pattern,
+ dict.get('Resources') || resources, dependencyArray);
+
+ // Add the dependencies that are required to execute the
+ // operatorList.
+ insertDependency(dependencyArray.slice(depIdx));
+
+ args = TilingPattern.getIR(operatorList, dict, args);
+ }
+ else if (typeNum == SHADING_PATTERN) {
+ var shading = dict.get('Shading');
+ var matrix = dict.get('Matrix');
+ var pattern = Pattern.parseShading(shading, matrix, xref,
+ res);
+ args = pattern.getIR();
+ } else {
+ error('Unkown PatternType ' + typeNum);
+ }
+ }
+ }
+ } else if (cmd == 'Do' && !args[0].code) {
+ // eagerly compile XForm objects
+ var name = args[0].name;
+ var xobj = xobjs.get(name);
+ if (xobj) {
+ assertWellFormed(isStream(xobj), 'XObject should be a stream');
+
+ var type = xobj.dict.get('Subtype');
+ assertWellFormed(
+ isName(type),
+ 'XObject should have a Name subtype'
+ );
+
+ if ('Form' == type.name) {
+ var matrix = xobj.dict.get('Matrix');
+ var bbox = xobj.dict.get('BBox');
+
+ fnArray.push('paintFormXObjectBegin');
+ argsArray.push([matrix, bbox]);
+
+ // This adds the operatorList of the xObj to the current queue.
+ var depIdx = dependencyArray.length;
+
+ // Pass in the current `queue` object. That means the `fnArray`
+ // and the `argsArray` in this scope is reused and new commands
+ // are added to them.
+ this.getOperatorList(xobj,
+ xobj.dict.get('Resources') || resources,
+ dependencyArray, queue);
+
+ // Add the dependencies that are required to execute the
+ // operatorList.
+ insertDependency(dependencyArray.slice(depIdx));
+
+ fn = 'paintFormXObjectEnd';
+ args = [];
+ } else if ('Image' == type.name) {
+ buildPaintImageXObject(xobj, false);
+ } else {
+ error('Unhandled XObject subtype ' + type.name);
+ }
+ }
+ } else if (cmd == 'Tf') { // eagerly collect all fonts
+ args[0] = handleSetFont(args[0].name);
+ } else if (cmd == 'EI') {
+ buildPaintImageXObject(args[0], true);
+ }
+
+ switch (fn) {
+ // Parse the ColorSpace data to a raw format.
+ case 'setFillColorSpace':
+ case 'setStrokeColorSpace':
+ args = [ColorSpace.parseToIR(args[0], xref, resources)];
+ break;
+ case 'shadingFill':
+ var shadingRes = res.get('Shading');
+ if (!shadingRes)
+ error('No shading resource found');
+
+ var shading = shadingRes.get(args[0].name);
+ if (!shading)
+ error('No shading object found');
+
+ var shadingFill = Pattern.parseShading(shading, null, xref, res);
+ var patternIR = shadingFill.getIR();
+ args = [patternIR];
+ fn = 'shadingFill';
+ break;
+ case 'setGState':
+ var dictName = args[0];
+ var extGState = resources.get('ExtGState');
+
+ if (!isDict(extGState) || !extGState.has(dictName.name))
+ break;
+
+ var gsState = extGState.get(dictName.name);
+
+ // This array holds the converted/processed state data.
+ var gsStateObj = [];
+
+ gsState.forEach(
+ function canvasGraphicsSetGStateForEach(key, value) {
+ switch (key) {
+ case 'Type':
+ break;
+ case 'LW':
+ case 'LC':
+ case 'LJ':
+ case 'ML':
+ case 'D':
+ case 'RI':
+ case 'FL':
+ case 'CA':
+ case 'ca':
+ gsStateObj.push([key, value]);
+ break;
+ case 'Font':
+ gsStateObj.push([
+ 'Font',
+ handleSetFont(null, value[0]),
+ value[1]
+ ]);
+ break;
+ case 'OP':
+ case 'op':
+ case 'OPM':
+ case 'BG':
+ case 'BG2':
+ case 'UCR':
+ case 'UCR2':
+ case 'TR':
+ case 'TR2':
+ case 'HT':
+ case 'SM':
+ case 'SA':
+ case 'BM':
+ case 'SMask':
+ case 'AIS':
+ case 'TK':
+ TODO('graphic state operator ' + key);
+ break;
+ default:
+ warn('Unknown graphic state operator ' + key);
+ break;
+ }
+ }
+ );
+ args = [gsStateObj];
+ break;
+ } // switch
+
+ fnArray.push(fn);
+ argsArray.push(args);
+ args = [];
+ } else if (obj != null) {
+ assertWellFormed(args.length <= 33, 'Too many arguments');
+ args.push(obj instanceof Dict ? obj.getAll() : obj);
+ }
+ }
+
+ return queue;
+ },
+
+ extractDataStructures: function
+ partialEvaluatorExtractDataStructures(dict, baseDict,
+ xref, properties) {
+ // 9.10.2
+ var toUnicode = dict.get('ToUnicode') ||
+ baseDict.get('ToUnicode');
+ if (toUnicode)
+ properties.toUnicode = this.readToUnicode(toUnicode, xref);
+
+ if (properties.composite) {
+ // CIDSystemInfo helps to match CID to glyphs
+ var cidSystemInfo = dict.get('CIDSystemInfo');
+ if (isDict(cidSystemInfo)) {
+ properties.cidSystemInfo = {
+ registry: cidSystemInfo.get('Registry'),
+ ordering: cidSystemInfo.get('Ordering'),
+ supplement: cidSystemInfo.get('Supplement')
+ };
+ }
+
+ var cidToGidMap = dict.get('CIDToGIDMap');
+ if (isStream(cidToGidMap))
+ properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
+ }
+
+ var flags = properties.flags;
+ var differences = [];
+ var baseEncoding = !!(flags & FontFlags.Symbolic) ?
+ Encodings.symbolsEncoding : Encodings.StandardEncoding;
+ var hasEncoding = dict.has('Encoding');
+ if (hasEncoding) {
+ var encoding = dict.get('Encoding');
+ if (isDict(encoding)) {
+ var baseName = encoding.get('BaseEncoding');
+ if (baseName)
+ baseEncoding = Encodings[baseName.name];
+ else
+ hasEncoding = false; // base encoding was not provided
+
+ // Load the differences between the base and original
+ if (encoding.has('Differences')) {
+ var diffEncoding = encoding.get('Differences');
+ var index = 0;
+ for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
+ var data = diffEncoding[j];
+ if (isNum(data))
+ index = data;
+ else
+ differences[index++] = data.name;
+ }
+ }
+ } else if (isName(encoding)) {
+ baseEncoding = Encodings[encoding.name];
+ } else {
+ error('Encoding is not a Name nor a Dict');
+ }
+ }
+
+ properties.differences = differences;
+ properties.baseEncoding = baseEncoding;
+ properties.hasEncoding = hasEncoding;
+ },
+
+ readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref) {
+ var cmapObj = toUnicode;
+ var charToUnicode = [];
+ if (isName(cmapObj)) {
+ var isIdentityMap = cmapObj.name.substr(0, 9) == 'Identity-';
+ if (!isIdentityMap)
+ error('ToUnicode file cmap translation not implemented');
+ } else if (isStream(cmapObj)) {
+ var tokens = [];
+ var token = '';
+ var beginArrayToken = {};
+
+ var cmap = cmapObj.getBytes(cmapObj.length);
+ for (var i = 0, ii = cmap.length; i < ii; i++) {
+ var octet = cmap[i];
+ if (octet == 0x20 || octet == 0x0D || octet == 0x0A ||
+ octet == 0x3C || octet == 0x5B || octet == 0x5D) {
+ switch (token) {
+ case 'usecmap':
+ error('usecmap is not implemented');
+ break;
+
+ case 'beginbfchar':
+ case 'beginbfrange':
+ case 'begincidchar':
+ case 'begincidrange':
+ token = '';
+ tokens = [];
+ break;
+
+ case 'endcidrange':
+ case 'endbfrange':
+ for (var j = 0, jj = tokens.length; j < jj; j += 3) {
+ var startRange = tokens[j];
+ var endRange = tokens[j + 1];
+ var code = tokens[j + 2];
+ if (code == 0xFFFF) {
+ // CMap is broken, assuming code == startRange
+ code = startRange;
+ }
+ if (isArray(code)) {
+ var codeindex = 0;
+ while (startRange <= endRange) {
+ charToUnicode[startRange] = code[codeindex++];
+ ++startRange;
+ }
+ } else {
+ while (startRange <= endRange) {
+ charToUnicode[startRange] = code++;
+ ++startRange;
+ }
+ }
+ }
+ break;
+
+ case 'endcidchar':
+ case 'endbfchar':
+ for (var j = 0, jj = tokens.length; j < jj; j += 2) {
+ var index = tokens[j];
+ var code = tokens[j + 1];
+ charToUnicode[index] = code;
+ }
+ break;
+
+ case '':
+ break;
+
+ default:
+ if (token[0] >= '0' && token[0] <= '9')
+ token = parseInt(token, 10); // a number
+ tokens.push(token);
+ token = '';
+ }
+ switch (octet) {
+ case 0x5B:
+ // begin list parsing
+ tokens.push(beginArrayToken);
+ break;
+ case 0x5D:
+ // collect array items
+ var items = [], item;
+ while (tokens.length &&
+ (item = tokens.pop()) != beginArrayToken)
+ items.unshift(item);
+ tokens.push(items);
+ break;
+ }
+ } else if (octet == 0x3E) {
+ if (token.length) {
+ if (token.length <= 4) {
+ // parsing hex number
+ tokens.push(parseInt(token, 16));
+ token = '';
+ } else {
+ // parsing hex UTF-16BE numbers
+ var str = [];
+ for (var k = 0, kk = token.length; k < kk; k += 4) {
+ var b = parseInt(token.substr(k, 4), 16);
+ if (b <= 0x10) {
+ k += 4;
+ b = (b << 16) | parseInt(token.substr(k, 4), 16);
+ b -= 0x10000;
+ str.push(0xD800 | (b >> 10));
+ str.push(0xDC00 | (b & 0x3FF));
+ break;
+ }
+ str.push(b);
+ }
+ tokens.push(String.fromCharCode.apply(String, str));
+ token = '';
+ }
+ }
+ } else {
+ token += String.fromCharCode(octet);
+ }
+ }
+ }
+ return charToUnicode;
+ },
+ readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
+ // Extract the encoding from the CIDToGIDMap
+ var glyphsData = cidToGidStream.getBytes();
+
+ // Set encoding 0 to later verify the font has an encoding
+ var result = [];
+ for (var j = 0, jj = glyphsData.length; j < jj; j++) {
+ var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
+ if (glyphID == 0)
+ continue;
+
+ var code = j >> 1;
+ result[code] = glyphID;
+ }
+ return result;
+ },
+
+ extractWidths: function PartialEvaluator_extractWidths(dict,
+ xref,
+ descriptor,
+ properties) {
+ var glyphsWidths = [];
+ var defaultWidth = 0;
+ if (properties.composite) {
+ defaultWidth = dict.get('DW') || 1000;
+
+ var widths = dict.get('W');
+ if (widths) {
+ var start = 0, end = 0;
+ for (var i = 0, ii = widths.length; i < ii; i++) {
+ var code = widths[i];
+ if (isArray(code)) {
+ for (var j = 0, jj = code.length; j < jj; j++)
+ glyphsWidths[start++] = code[j];
+ start = 0;
+ } else if (start) {
+ var width = widths[++i];
+ for (var j = start; j <= code; j++)
+ glyphsWidths[j] = width;
+ start = 0;
+ } else {
+ start = code;
+ }
+ }
+ }
+ } else {
+ var firstChar = properties.firstChar;
+ var widths = dict.get('Widths');
+ if (widths) {
+ var j = firstChar;
+ for (var i = 0, ii = widths.length; i < ii; i++)
+ glyphsWidths[j++] = widths[i];
+ defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
+ } else {
+ // Trying get the BaseFont metrics (see comment above).
+ var baseFontName = dict.get('BaseFont');
+ if (isName(baseFontName)) {
+ var metrics = this.getBaseFontMetrics(baseFontName.name);
+
+ glyphsWidths = metrics.widths;
+ defaultWidth = metrics.defaultWidth;
+ }
+ }
+ }
+
+ properties.defaultWidth = defaultWidth;
+ properties.widths = glyphsWidths;
+ },
+
+ getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) {
+ var defaultWidth = 0, widths = [];
+ var glyphWidths = Metrics[stdFontMap[name] || name];
+ if (isNum(glyphWidths)) {
+ defaultWidth = glyphWidths;
+ } else {
+ widths = glyphWidths;
+ }
+
+ return {
+ defaultWidth: defaultWidth,
+ widths: widths
+ };
+ },
+
+ translateFont: function PartialEvaluator_translateFont(dict,
+ xref,
+ resources,
+ dependency) {
+ var baseDict = dict;
+ var type = dict.get('Subtype');
+ assertWellFormed(isName(type), 'invalid font Subtype');
+
+ var composite = false;
+ if (type.name == 'Type0') {
+ // If font is a composite
+ // - get the descendant font
+ // - set the type according to the descendant font
+ // - get the FontDescriptor from the descendant font
+ var df = dict.get('DescendantFonts');
+ if (!df)
+ return null;
+
+ dict = isArray(df) ? xref.fetchIfRef(df[0]) : df;
+
+ type = dict.get('Subtype');
+ assertWellFormed(isName(type), 'invalid font Subtype');
+ composite = true;
+ }
+ var maxCharIndex = composite ? 0xFFFF : 0xFF;
+
+ var descriptor = dict.get('FontDescriptor');
+ if (!descriptor) {
+ if (type.name == 'Type3') {
+ // FontDescriptor is only required for Type3 fonts when the document
+ // is a tagged pdf. Create a barbebones one to get by.
+ descriptor = new Dict();
+ descriptor.set('FontName', new Name(type.name));
+ } else {
+ // Before PDF 1.5 if the font was one of the base 14 fonts, having a
+ // FontDescriptor was not required.
+ // This case is here for compatibility.
+ var baseFontName = dict.get('BaseFont');
+ if (!isName(baseFontName))
+ return null;
+
+ // Using base font name as a font name.
+ baseFontName = baseFontName.name.replace(/[,_]/g, '-');
+ var metrics = this.getBaseFontMetrics(baseFontName);
+
+ // Simulating descriptor flags attribute
+ var fontNameWoStyle = baseFontName.split('-')[0];
+ var flags = (serifFonts[fontNameWoStyle] ||
+ (fontNameWoStyle.search(/serif/gi) != -1) ? FontFlags.Serif : 0) |
+ (symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic :
+ FontFlags.Nonsymbolic);
+
+ var properties = {
+ type: type.name,
+ widths: metrics.widths,
+ defaultWidth: metrics.defaultWidth,
+ flags: flags,
+ firstChar: 0,
+ lastChar: maxCharIndex
+ };
+ this.extractDataStructures(dict, dict, xref, properties);
+
+ return {
+ name: baseFontName,
+ dict: baseDict,
+ properties: properties
+ };
+ }
+ }
+
+ // According to the spec if 'FontDescriptor' is declared, 'FirstChar',
+ // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
+ // to ignore this rule when a variant of a standart font is used.
+ // TODO Fill the width array depending on which of the base font this is
+ // a variant.
+ var firstChar = dict.get('FirstChar') || 0;
+ var lastChar = dict.get('LastChar') || maxCharIndex;
+ var fontName = descriptor.get('FontName');
+ // Some bad pdf's have a string as the font name.
+ if (isString(fontName))
+ fontName = new Name(fontName);
+ assertWellFormed(isName(fontName), 'invalid font name');
+
+ var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
+ if (fontFile) {
+ if (fontFile.dict) {
+ var subtype = fontFile.dict.get('Subtype');
+ if (subtype)
+ subtype = subtype.name;
+
+ var length1 = fontFile.dict.get('Length1');
+
+ var length2 = fontFile.dict.get('Length2');
+ }
+ }
+
+ var properties = {
+ type: type.name,
+ subtype: subtype,
+ file: fontFile,
+ length1: length1,
+ length2: length2,
+ composite: composite,
+ fixedPitch: false,
+ fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX,
+ firstChar: firstChar || 0,
+ lastChar: lastChar || maxCharIndex,
+ bbox: descriptor.get('FontBBox'),
+ ascent: descriptor.get('Ascent'),
+ descent: descriptor.get('Descent'),
+ xHeight: descriptor.get('XHeight'),
+ capHeight: descriptor.get('CapHeight'),
+ flags: descriptor.get('Flags'),
+ italicAngle: descriptor.get('ItalicAngle'),
+ coded: false
+ };
+ this.extractWidths(dict, xref, descriptor, properties);
+ this.extractDataStructures(dict, baseDict, xref, properties);
+
+ if (type.name === 'Type3') {
+ properties.coded = true;
+ var charProcs = dict.get('CharProcs').getAll();
+ var fontResources = dict.get('Resources') || resources;
+ properties.charProcOperatorList = {};
+ for (var key in charProcs) {
+ var glyphStream = charProcs[key];
+ properties.charProcOperatorList[key] =
+ this.getOperatorList(glyphStream, fontResources, dependency);
+ }
+ }
+
+ return {
+ name: fontName.name,
+ dict: baseDict,
+ file: fontFile,
+ properties: properties
+ };
+ }
+ };
+
+ return PartialEvaluator;
+})();
+
+var EvalState = (function EvalStateClosure() {
+ function EvalState() {
+ // Are soft masks and alpha values shapes or opacities?
+ this.alphaIsShape = false;
+ this.fontSize = 0;
+ this.textMatrix = IDENTITY_MATRIX;
+ this.leading = 0;
+ // Start of text line (in text coordinates)
+ this.lineX = 0;
+ this.lineY = 0;
+ // Character and word spacing
+ this.charSpacing = 0;
+ this.wordSpacing = 0;
+ this.textHScale = 1;
+ // Color spaces
+ this.fillColorSpace = null;
+ this.strokeColorSpace = null;
+ }
+ EvalState.prototype = {
+ };
+ return EvalState;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * Maximum time to wait for a font to be loaded by font-face rules.
+ */
+var kMaxWaitForFontFace = 1000;
+
+// Unicode Private Use Area
+var kCmapGlyphOffset = 0xE000;
+var kSizeOfGlyphArea = 0x1900;
+var kSymbolicFontGlyphOffset = 0xF000;
+
+// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
+// except for Type 3 fonts
+var kPDFGlyphSpaceUnits = 1000;
+
+// Until hinting is fully supported this constant can be used
+var kHintingEnabled = false;
+
+var FontFlags = {
+ FixedPitch: 1,
+ Serif: 2,
+ Symbolic: 4,
+ Script: 8,
+ Nonsymbolic: 32,
+ Italic: 64,
+ AllCap: 65536,
+ SmallCap: 131072,
+ ForceBold: 262144
+};
+
+var Encodings = {
+ ExpertEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle',
+ 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior',
+ 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma',
+ 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle',
+ 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle',
+ 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon',
+ 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior',
+ 'questionsmall', '', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
+ 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior',
+ 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior',
+ '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '',
+ 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
+ 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
+ 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall',
+ 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall',
+ 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary',
+ 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall',
+ '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall',
+ 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '',
+ 'figuredash', 'hypheninferior', '', '', 'Ogoneksmall', 'Ringsmall',
+ 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters',
+ 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths',
+ 'seveneighths', 'onethird', 'twothirds', '', '', 'zerosuperior',
+ 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior',
+ 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior',
+ 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior',
+ 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior',
+ 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
+ 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
+ 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall',
+ 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall',
+ 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
+ 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall',
+ 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
+ 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
+ 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall',
+ 'Ydieresissmall'],
+ MacExpertEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclamsmall', 'Hungarumlautsmall', 'centoldstyle',
+ 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
+ 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
+ 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle',
+ 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
+ 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
+ 'nineoldstyle', 'colon', 'semicolon', '', 'threequartersemdash', '',
+ 'questionsmall', '', '', '', '', 'Ethsmall', '', '', 'onequarter',
+ 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths',
+ 'seveneighths', 'onethird', 'twothirds', '', '', '', '', '', '', 'ff',
+ 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '', 'parenrightinferior',
+ 'Circumflexsmall', 'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall',
+ 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall',
+ 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
+ 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
+ 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah',
+ 'Tildesmall', '', '', 'asuperior', 'centsuperior', '', '', '', '',
+ 'Aacutesmall', 'Agravesmall', 'Acircumflexsmall', 'Adieresissmall',
+ 'Atildesmall', 'Aringsmall', 'Ccedillasmall', 'Eacutesmall', 'Egravesmall',
+ 'Ecircumflexsmall', 'Edieresissmall', 'Iacutesmall', 'Igravesmall',
+ 'Icircumflexsmall', 'Idieresissmall', 'Ntildesmall', 'Oacutesmall',
+ 'Ogravesmall', 'Ocircumflexsmall', 'Odieresissmall', 'Otildesmall',
+ 'Uacutesmall', 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall', '',
+ 'eightsuperior', 'fourinferior', 'threeinferior', 'sixinferior',
+ 'eightinferior', 'seveninferior', 'Scaronsmall', '', 'centinferior',
+ 'twoinferior', '', 'Dieresissmall', '', 'Caronsmall', 'osuperior',
+ 'fiveinferior', '', 'commainferior', 'periodinferior', 'Yacutesmall', '',
+ 'dollarinferior', '', 'Thornsmall', '', 'nineinferior', 'zeroinferior',
+ 'Zcaronsmall', 'AEsmall', 'Oslashsmall', 'questiondownsmall',
+ 'oneinferior', 'Lslashsmall', '', '', '', '', '', '', 'Cedillasmall', '',
+ '', '', '', '', 'OEsmall', 'figuredash', 'hyphensuperior', '', '', '', '',
+ 'exclamdownsmall', '', 'Ydieresissmall', '', 'onesuperior', 'twosuperior',
+ 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior',
+ 'sevensuperior', 'ninesuperior', 'zerosuperior', '', 'esuperior',
+ 'rsuperior', 'tsuperior', '', '', 'isuperior', 'ssuperior', 'dsuperior',
+ '', '', '', '', '', 'lsuperior', 'Ogoneksmall', 'Brevesmall',
+ 'Macronsmall', 'bsuperior', 'nsuperior', 'msuperior', 'commasuperior',
+ 'periodsuperior', 'Dotaccentsmall', 'Ringsmall'],
+ MacRomanEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
+ 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus',
+ 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three',
+ 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon',
+ 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+ 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
+ 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '',
+ 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis',
+ 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', 'atilde',
+ 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
+ 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute',
+ 'ograve', 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave',
+ 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling',
+ 'section', 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright',
+ 'trademark', 'acute', 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity',
+ 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff',
+ 'summation', 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine',
+ 'Omega', 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot',
+ 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft',
+ 'guillemotright', 'ellipsis', '', 'Agrave', 'Atilde', 'Otilde', 'OE',
+ 'oe', 'endash', 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft',
+ 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction',
+ 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl',
+ 'periodcentered', 'quotesinglbase', 'quotedblbase', 'perthousand',
+ 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute',
+ 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple',
+ 'Ograve', 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex',
+ 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut',
+ 'ogonek', 'caron'],
+ StandardEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
+ 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus',
+ 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three',
+ 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon',
+ 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+ 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
+ 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
+ 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'exclamdown',
+ 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
+ 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
+ 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl',
+ 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase',
+ 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
+ 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex',
+ 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla',
+ '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '',
+ '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae',
+ '', '', '', 'dotlessi', '', '', 'lslash', 'oslash', 'oe', 'germandbls'],
+ WinAnsiEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
+ 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus',
+ 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three',
+ 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon',
+ 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+ 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
+ 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde',
+ 'bullet', 'Euro', 'bullet', 'quotesinglbase', 'florin', 'quotedblbase',
+ 'ellipsis', 'dagger', 'daggerdbl', 'circumflex', 'perthousand', 'Scaron',
+ 'guilsinglleft', 'OE', 'bullet', 'Zcaron', 'bullet', 'bullet', 'quoteleft',
+ 'quoteright', 'quotedblleft', 'quotedblright', 'bullet', 'endash',
+ 'emdash', 'tilde', 'trademark', 'scaron', 'guilsinglright', 'oe', 'bullet',
+ 'zcaron', 'Ydieresis', '', 'exclamdown', 'cent', 'sterling',
+ 'currency', 'yen', 'brokenbar', 'section', 'dieresis', 'copyright',
+ 'ordfeminine', 'guillemotleft', 'logicalnot', 'hyphen', 'registered',
+ 'macron', 'degree', 'plusminus', 'twosuperior', 'threesuperior', 'acute',
+ 'mu', 'paragraph', 'periodcentered', 'cedilla', 'onesuperior',
+ 'ordmasculine', 'guillemotright', 'onequarter', 'onehalf', 'threequarters',
+ 'questiondown', 'Agrave', 'Aacute', 'Acircumflex', 'Atilde', 'Adieresis',
+ 'Aring', 'AE', 'Ccedilla', 'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis',
+ 'Igrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Eth', 'Ntilde', 'Ograve',
+ 'Oacute', 'Ocircumflex', 'Otilde', 'Odieresis', 'multiply', 'Oslash',
+ 'Ugrave', 'Uacute', 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn',
+ 'germandbls', 'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis',
+ 'aring', 'ae', 'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis',
+ 'igrave', 'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde', 'ograve',
+ 'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide', 'oslash',
+ 'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute', 'thorn',
+ 'ydieresis'],
+ symbolsEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'exclam', 'universal', 'numbersign', 'existential', 'percent',
+ 'ampersand', 'suchthat', 'parenleft', 'parenright', 'asteriskmath', 'plus',
+ 'comma', 'minus', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
+ 'equal', 'greater', 'question', 'congruent', 'Alpha', 'Beta', 'Chi',
+ 'Delta', 'Epsilon', 'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa',
+ 'Lambda', 'Mu', 'Nu', 'Omicron', 'Pi', 'Theta', 'Rho', 'Sigma', 'Tau',
+ 'Upsilon', 'sigma1', 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft',
+ 'therefore', 'bracketright', 'perpendicular', 'underscore', 'radicalex',
+ 'alpha', 'beta', 'chi', 'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota',
+ 'phi1', 'kappa', 'lambda', 'mu', 'nu', 'omicron', 'pi', 'theta', 'rho',
+ 'sigma', 'tau', 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta',
+ 'braceleft', 'bar', 'braceright', 'similar', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', 'Euro', 'Upsilon1', 'minute', 'lessequal',
+ 'fraction', 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade',
+ 'arrowboth', 'arrowleft', 'arrowup', 'arrowright', 'arrowdown', 'degree',
+ 'plusminus', 'second', 'greaterequal', 'multiply', 'proportional',
+ 'partialdiff', 'bullet', 'divide', 'notequal', 'equivalence',
+ 'approxequal', 'ellipsis', 'arrowvertex', 'arrowhorizex', 'carriagereturn',
+ 'aleph', 'Ifraktur', 'Rfraktur', 'weierstrass', 'circlemultiply',
+ 'circleplus', 'emptyset', 'intersection', 'union', 'propersuperset',
+ 'reflexsuperset', 'notsubset', 'propersubset', 'reflexsubset', 'element',
+ 'notelement', 'angle', 'gradient', 'registerserif', 'copyrightserif',
+ 'trademarkserif', 'product', 'radical', 'dotmath', 'logicalnot',
+ 'logicaland', 'logicalor', 'arrowdblboth', 'arrowdblleft', 'arrowdblup',
+ 'arrowdblright', 'arrowdbldown', 'lozenge', 'angleleft', 'registersans',
+ 'copyrightsans', 'trademarksans', 'summation', 'parenlefttp',
+ 'parenleftex', 'parenleftbt', 'bracketlefttp', 'bracketleftex',
+ 'bracketleftbt', 'bracelefttp', 'braceleftmid', 'braceleftbt', 'braceex',
+ '', 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt',
+ 'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp',
+ 'bracketrightex', 'bracketrightbt', 'bracerighttp', 'bracerightmid',
+ 'bracerightbt'],
+ zapfDingbatsEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'space', 'a1', 'a2', 'a202', 'a3', 'a4', 'a5', 'a119', 'a118', 'a117',
+ 'a11', 'a12', 'a13', 'a14', 'a15', 'a16', 'a105', 'a17', 'a18', 'a19',
+ 'a20', 'a21', 'a22', 'a23', 'a24', 'a25', 'a26', 'a27', 'a28', 'a6', 'a7',
+ 'a8', 'a9', 'a10', 'a29', 'a30', 'a31', 'a32', 'a33', 'a34', 'a35', 'a36',
+ 'a37', 'a38', 'a39', 'a40', 'a41', 'a42', 'a43', 'a44', 'a45', 'a46',
+ 'a47', 'a48', 'a49', 'a50', 'a51', 'a52', 'a53', 'a54', 'a55', 'a56',
+ 'a57', 'a58', 'a59', 'a60', 'a61', 'a62', 'a63', 'a64', 'a65', 'a66',
+ 'a67', 'a68', 'a69', 'a70', 'a71', 'a72', 'a73', 'a74', 'a203', 'a75',
+ 'a204', 'a76', 'a77', 'a78', 'a79', 'a81', 'a82', 'a83', 'a84', 'a97',
+ 'a98', 'a99', 'a100', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', 'a101', 'a102', 'a103', 'a104', 'a106', 'a107', 'a108', 'a112',
+ 'a111', 'a110', 'a109', 'a120', 'a121', 'a122', 'a123', 'a124', 'a125',
+ 'a126', 'a127', 'a128', 'a129', 'a130', 'a131', 'a132', 'a133', 'a134',
+ 'a135', 'a136', 'a137', 'a138', 'a139', 'a140', 'a141', 'a142', 'a143',
+ 'a144', 'a145', 'a146', 'a147', 'a148', 'a149', 'a150', 'a151', 'a152',
+ 'a153', 'a154', 'a155', 'a156', 'a157', 'a158', 'a159', 'a160', 'a161',
+ 'a163', 'a164', 'a196', 'a165', 'a192', 'a166', 'a167', 'a168', 'a169',
+ 'a170', 'a171', 'a172', 'a173', 'a162', 'a174', 'a175', 'a176', 'a177',
+ 'a178', 'a179', 'a193', 'a180', 'a199', 'a181', 'a200', 'a182', '', 'a201',
+ 'a183', 'a184', 'a197', 'a185', 'a194', 'a198', 'a186', 'a195', 'a187',
+ 'a188', 'a189', 'a190', 'a191']
+};
+
+/**
+ * Hold a map of decoded fonts and of the standard fourteen Type1
+ * fonts and their acronyms.
+ */
+var stdFontMap = {
+ 'ArialNarrow': 'Helvetica',
+ 'ArialNarrow-Bold': 'Helvetica-Bold',
+ 'ArialNarrow-BoldItalic': 'Helvetica-BoldOblique',
+ 'ArialNarrow-Italic': 'Helvetica-Oblique',
+ 'ArialBlack': 'Helvetica',
+ 'ArialBlack-Bold': 'Helvetica-Bold',
+ 'ArialBlack-BoldItalic': 'Helvetica-BoldOblique',
+ 'ArialBlack-Italic': 'Helvetica-Oblique',
+ 'Arial': 'Helvetica',
+ 'Arial-Bold': 'Helvetica-Bold',
+ 'Arial-BoldItalic': 'Helvetica-BoldOblique',
+ 'Arial-Italic': 'Helvetica-Oblique',
+ 'Arial-BoldItalicMT': 'Helvetica-BoldOblique',
+ 'Arial-BoldMT': 'Helvetica-Bold',
+ 'Arial-ItalicMT': 'Helvetica-Oblique',
+ 'ArialMT': 'Helvetica',
+ 'Courier-Bold': 'Courier-Bold',
+ 'Courier-BoldItalic': 'Courier-BoldOblique',
+ 'Courier-Italic': 'Courier-Oblique',
+ 'CourierNew': 'Courier',
+ 'CourierNew-Bold': 'Courier-Bold',
+ 'CourierNew-BoldItalic': 'Courier-BoldOblique',
+ 'CourierNew-Italic': 'Courier-Oblique',
+ 'CourierNewPS-BoldItalicMT': 'Courier-BoldOblique',
+ 'CourierNewPS-BoldMT': 'Courier-Bold',
+ 'CourierNewPS-ItalicMT': 'Courier-Oblique',
+ 'CourierNewPSMT': 'Courier',
+ 'Helvetica-Bold': 'Helvetica-Bold',
+ 'Helvetica-BoldItalic': 'Helvetica-BoldOblique',
+ 'Helvetica-Italic': 'Helvetica-Oblique',
+ 'Symbol-Bold': 'Symbol',
+ 'Symbol-BoldItalic': 'Symbol',
+ 'Symbol-Italic': 'Symbol',
+ 'TimesNewRoman': 'Times-Roman',
+ 'TimesNewRoman-Bold': 'Times-Bold',
+ 'TimesNewRoman-BoldItalic': 'Times-BoldItalic',
+ 'TimesNewRoman-Italic': 'Times-Italic',
+ 'TimesNewRomanPS': 'Times-Roman',
+ 'TimesNewRomanPS-Bold': 'Times-Bold',
+ 'TimesNewRomanPS-BoldItalic': 'Times-BoldItalic',
+ 'TimesNewRomanPS-BoldItalicMT': 'Times-BoldItalic',
+ 'TimesNewRomanPS-BoldMT': 'Times-Bold',
+ 'TimesNewRomanPS-Italic': 'Times-Italic',
+ 'TimesNewRomanPS-ItalicMT': 'Times-Italic',
+ 'TimesNewRomanPSMT': 'Times-Roman',
+ 'TimesNewRomanPSMT-Bold': 'Times-Bold',
+ 'TimesNewRomanPSMT-BoldItalic': 'Times-BoldItalic',
+ 'TimesNewRomanPSMT-Italic': 'Times-Italic'
+};
+
+/**
+ * Holds the map of the non-standard fonts that might be included as a standard
+ * fonts without glyph data.
+ */
+var nonStdFontMap = {
+ 'ComicSansMS': 'Comic Sans MS',
+ 'ComicSansMS-Bold': 'Comic Sans MS-Bold',
+ 'ComicSansMS-BoldItalic': 'Comic Sans MS-BoldItalic',
+ 'ComicSansMS-Italic': 'Comic Sans MS-Italic',
+ 'LucidaConsole': 'Courier',
+ 'LucidaConsole-Bold': 'Courier-Bold',
+ 'LucidaConsole-BoldItalic': 'Courier-BoldOblique',
+ 'LucidaConsole-Italic': 'Courier-Oblique'
+};
+
+var serifFonts = {
+ 'Adobe Jenson': true, 'Adobe Text': true, 'Albertus': true,
+ 'Aldus': true, 'Alexandria': true, 'Algerian': true,
+ 'American Typewriter': true, 'Antiqua': true, 'Apex': true,
+ 'Arno': true, 'Aster': true, 'Aurora': true,
+ 'Baskerville': true, 'Bell': true, 'Bembo': true,
+ 'Bembo Schoolbook': true, 'Benguiat': true, 'Berkeley Old Style': true,
+ 'Bernhard Modern': true, 'Berthold City': true, 'Bodoni': true,
+ 'Bauer Bodoni': true, 'Book Antiqua': true, 'Bookman': true,
+ 'Bordeaux Roman': true, 'Californian FB': true, 'Calisto': true,
+ 'Calvert': true, 'Capitals': true, 'Cambria': true,
+ 'Cartier': true, 'Caslon': true, 'Catull': true,
+ 'Centaur': true, 'Century Old Style': true, 'Century Schoolbook': true,
+ 'Chaparral': true, 'Charis SIL': true, 'Cheltenham': true,
+ 'Cholla Slab': true, 'Clarendon': true, 'Clearface': true,
+ 'Cochin': true, 'Colonna': true, 'Computer Modern': true,
+ 'Concrete Roman': true, 'Constantia': true, 'Cooper Black': true,
+ 'Corona': true, 'Ecotype': true, 'Egyptienne': true,
+ 'Elephant': true, 'Excelsior': true, 'Fairfield': true,
+ 'FF Scala': true, 'Folkard': true, 'Footlight': true,
+ 'FreeSerif': true, 'Friz Quadrata': true, 'Garamond': true,
+ 'Gentium': true, 'Georgia': true, 'Gloucester': true,
+ 'Goudy Old Style': true, 'Goudy Schoolbook': true, 'Goudy Pro Font': true,
+ 'Granjon': true, 'Guardian Egyptian': true, 'Heather': true,
+ 'Hercules': true, 'High Tower Text': true, 'Hiroshige': true,
+ 'Hoefler Text': true, 'Humana Serif': true, 'Imprint': true,
+ 'Ionic No. 5': true, 'Janson': true, 'Joanna': true,
+ 'Korinna': true, 'Lexicon': true, 'Liberation Serif': true,
+ 'Linux Libertine': true, 'Literaturnaya': true, 'Lucida': true,
+ 'Lucida Bright': true, 'Melior': true, 'Memphis': true,
+ 'Miller': true, 'Minion': true, 'Modern': true,
+ 'Mona Lisa': true, 'Mrs Eaves': true, 'MS Serif': true,
+ 'Museo Slab': true, 'New York': true, 'Nimbus Roman': true,
+ 'NPS Rawlinson Roadway': true, 'Palatino': true, 'Perpetua': true,
+ 'Plantin': true, 'Plantin Schoolbook': true, 'Playbill': true,
+ 'Poor Richard': true, 'Rawlinson Roadway': true, 'Renault': true,
+ 'Requiem': true, 'Rockwell': true, 'Roman': true,
+ 'Rotis Serif': true, 'Sabon': true, 'Scala': true,
+ 'Seagull': true, 'Sistina': true, 'Souvenir': true,
+ 'STIX': true, 'Stone Informal': true, 'Stone Serif': true,
+ 'Sylfaen': true, 'Times': true, 'Trajan': true,
+ 'Trinité': true, 'Trump Mediaeval': true, 'Utopia': true,
+ 'Vale Type': true, 'Bitstream Vera': true, 'Vera Serif': true,
+ 'Versailles': true, 'Wanted': true, 'Weiss': true,
+ 'Wide Latin': true, 'Windsor': true, 'XITS': true
+};
+
+var symbolsFonts = {
+ 'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true
+};
+
+// Some characters, e.g. copyrightserif, mapped to the private use area and
+// might not be displayed using standard fonts. Mapping/hacking well-known chars
+// to the similar equivalents in the normal characters range.
+function mapPrivateUseChars(code) {
+ switch (code) {
+ case 0xF8E9: // copyrightsans
+ case 0xF6D9: // copyrightserif
+ return 0x00A9; // copyright
+ default:
+ return code;
+ }
+}
+
+var FontLoader = {
+ listeningForFontLoad: false,
+
+ bind: function fontLoaderBind(fonts, callback) {
+ function checkFontsLoaded() {
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ var fontObj = fonts[i];
+ if (fontObj.loading) {
+ return false;
+ }
+ }
+
+ document.documentElement.removeEventListener(
+ 'pdfjsFontLoad', checkFontsLoaded, false);
+
+ callback();
+ return true;
+ }
+
+ var rules = [], names = [], fontsToLoad = [];
+ var fontCreateTimer = 0;
+
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ var font = fonts[i];
+
+ // Add the font to the DOM only once or skip if the font
+ // is already loaded.
+ if (font.attached || font.loading == false) {
+ continue;
+ }
+ font.attached = true;
+
+ fontsToLoad.push(font);
+
+ var str = '';
+ var data = font.data;
+ if (data) {
+ var length = data.length;
+ for (var j = 0; j < length; j++)
+ str += String.fromCharCode(data[j]);
+
+ var rule = font.bindDOM(str);
+ if (rule) {
+ rules.push(rule);
+ names.push(font.loadedName);
+ }
+ }
+ }
+
+ this.listeningForFontLoad = false;
+ if (!isWorker && rules.length) {
+ FontLoader.prepareFontLoadEvent(rules, names, fontsToLoad);
+ }
+
+ if (!checkFontsLoaded()) {
+ document.documentElement.addEventListener(
+ 'pdfjsFontLoad', checkFontsLoaded, false);
+ }
+ },
+ // Set things up so that at least one pdfjsFontLoad event is
+ // dispatched when all the @font-face |rules| for |names| have been
+ // loaded in a subdocument. It's expected that the load of |rules|
+ // has already started in this (outer) document, so that they should
+ // be ordered before the load in the subdocument.
+ prepareFontLoadEvent: function fontLoaderPrepareFontLoadEvent(rules, names,
+ fonts) {
+ /** Hack begin */
+ // There's no event when a font has finished downloading so the
+ // following code is a dirty hack to 'guess' when a font is
+ // ready. This code will be obsoleted by Mozilla bug 471915.
+ //
+ // The only reliable way to know if a font is loaded in Gecko
+ // (at the moment) is document.onload in a document with
+ // a @font-face rule defined in a "static" stylesheet. We use a
+ // subdocument in an <iframe>, set up properly, to know when
+ // our @font-face rule was loaded. However, the subdocument and
+ // outer document can't share CSS rules, so the inner document
+ // is only part of the puzzle. The second piece is an invisible
+ // div created in order to force loading of the @font-face in
+ // the *outer* document. (The font still needs to be loaded for
+ // its metrics, for reflow). We create the div first for the
+ // outer document, then create the iframe. Unless something
+ // goes really wonkily, we expect the @font-face for the outer
+ // document to be processed before the inner. That's still
+ // fragile, but seems to work in practice.
+ //
+ // The postMessage() hackery was added to work around chrome bug
+ // 82402.
+
+ // Validate the names parameter -- the values can used to construct HTML.
+ if (!/^\w+$/.test(names.join(''))) {
+ error('Invalid font name(s): ' + names.join());
+
+ // Normally the error-function throws. But if a malicious code
+ // intercepts the function call then the return is needed.
+ return;
+ }
+
+ var div = document.createElement('div');
+ div.setAttribute('style',
+ 'visibility: hidden;' +
+ 'width: 10px; height: 10px;' +
+ 'position: absolute; top: 0px; left: 0px;');
+ var html = '';
+ for (var i = 0, ii = names.length; i < ii; ++i) {
+ html += '<span style="font-family:' + names[i] + '">Hi</span>';
+ }
+ div.innerHTML = html;
+ document.body.appendChild(div);
+
+ if (!this.listeningForFontLoad) {
+ window.addEventListener(
+ 'message',
+ function fontLoaderMessage(e) {
+ var fontNames = JSON.parse(e.data);
+ for (var i = 0, ii = fonts.length; i < ii; ++i) {
+ var font = fonts[i];
+ font.loading = false;
+ }
+ var evt = document.createEvent('Events');
+ evt.initEvent('pdfjsFontLoad', true, false);
+ document.documentElement.dispatchEvent(evt);
+ },
+ false);
+ this.listeningForFontLoad = true;
+ }
+
+ // XXX we should have a time-out here too, and maybe fire
+ // pdfjsFontLoadFailed?
+ var src = '<!DOCTYPE HTML><html><head>';
+ src += '<style type="text/css">';
+ for (var i = 0, ii = rules.length; i < ii; ++i) {
+ src += rules[i];
+ }
+ src += '</style>';
+ src += '<script type="application/javascript">';
+ var fontNamesArray = '';
+ for (var i = 0, ii = names.length; i < ii; ++i) {
+ fontNamesArray += '"' + names[i] + '", ';
+ }
+ src += ' var fontNames=[' + fontNamesArray + '];\n';
+ src += ' window.onload = function fontLoaderOnload() {\n';
+ src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n';
+ src += ' }';
+ // Hack so the end script tag isn't counted if this is inline JS.
+ src += '</scr' + 'ipt></head><body>';
+ for (var i = 0, ii = names.length; i < ii; ++i) {
+ src += '<p style="font-family:\'' + names[i] + '\'">Hi</p>';
+ }
+ src += '</body></html>';
+ var frame = document.createElement('iframe');
+ frame.src = 'data:text/html,' + src;
+ frame.setAttribute('style',
+ 'visibility: hidden;' +
+ 'width: 10px; height: 10px;' +
+ 'position: absolute; top: 0px; left: 0px;');
+ document.body.appendChild(frame);
+ /** Hack end */
+ }
+};
+
+var UnicodeRanges = [
+ { 'begin': 0x0000, 'end': 0x007F }, // Basic Latin
+ { 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement
+ { 'begin': 0x0100, 'end': 0x017F }, // Latin Extended-A
+ { 'begin': 0x0180, 'end': 0x024F }, // Latin Extended-B
+ { 'begin': 0x0250, 'end': 0x02AF }, // IPA Extensions
+ { 'begin': 0x02B0, 'end': 0x02FF }, // Spacing Modifier Letters
+ { 'begin': 0x0300, 'end': 0x036F }, // Combining Diacritical Marks
+ { 'begin': 0x0370, 'end': 0x03FF }, // Greek and Coptic
+ { 'begin': 0x2C80, 'end': 0x2CFF }, // Coptic
+ { 'begin': 0x0400, 'end': 0x04FF }, // Cyrillic
+ { 'begin': 0x0530, 'end': 0x058F }, // Armenian
+ { 'begin': 0x0590, 'end': 0x05FF }, // Hebrew
+ { 'begin': 0xA500, 'end': 0xA63F }, // Vai
+ { 'begin': 0x0600, 'end': 0x06FF }, // Arabic
+ { 'begin': 0x07C0, 'end': 0x07FF }, // NKo
+ { 'begin': 0x0900, 'end': 0x097F }, // Devanagari
+ { 'begin': 0x0980, 'end': 0x09FF }, // Bengali
+ { 'begin': 0x0A00, 'end': 0x0A7F }, // Gurmukhi
+ { 'begin': 0x0A80, 'end': 0x0AFF }, // Gujarati
+ { 'begin': 0x0B00, 'end': 0x0B7F }, // Oriya
+ { 'begin': 0x0B80, 'end': 0x0BFF }, // Tamil
+ { 'begin': 0x0C00, 'end': 0x0C7F }, // Telugu
+ { 'begin': 0x0C80, 'end': 0x0CFF }, // Kannada
+ { 'begin': 0x0D00, 'end': 0x0D7F }, // Malayalam
+ { 'begin': 0x0E00, 'end': 0x0E7F }, // Thai
+ { 'begin': 0x0E80, 'end': 0x0EFF }, // Lao
+ { 'begin': 0x10A0, 'end': 0x10FF }, // Georgian
+ { 'begin': 0x1B00, 'end': 0x1B7F }, // Balinese
+ { 'begin': 0x1100, 'end': 0x11FF }, // Hangul Jamo
+ { 'begin': 0x1E00, 'end': 0x1EFF }, // Latin Extended Additional
+ { 'begin': 0x1F00, 'end': 0x1FFF }, // Greek Extended
+ { 'begin': 0x2000, 'end': 0x206F }, // General Punctuation
+ { 'begin': 0x2070, 'end': 0x209F }, // Superscripts And Subscripts
+ { 'begin': 0x20A0, 'end': 0x20CF }, // Currency Symbol
+ { 'begin': 0x20D0, 'end': 0x20FF }, // Combining Diacritical Marks For Symbols
+ { 'begin': 0x2100, 'end': 0x214F }, // Letterlike Symbols
+ { 'begin': 0x2150, 'end': 0x218F }, // Number Forms
+ { 'begin': 0x2190, 'end': 0x21FF }, // Arrows
+ { 'begin': 0x2200, 'end': 0x22FF }, // Mathematical Operators
+ { 'begin': 0x2300, 'end': 0x23FF }, // Miscellaneous Technical
+ { 'begin': 0x2400, 'end': 0x243F }, // Control Pictures
+ { 'begin': 0x2440, 'end': 0x245F }, // Optical Character Recognition
+ { 'begin': 0x2460, 'end': 0x24FF }, // Enclosed Alphanumerics
+ { 'begin': 0x2500, 'end': 0x257F }, // Box Drawing
+ { 'begin': 0x2580, 'end': 0x259F }, // Block Elements
+ { 'begin': 0x25A0, 'end': 0x25FF }, // Geometric Shapes
+ { 'begin': 0x2600, 'end': 0x26FF }, // Miscellaneous Symbols
+ { 'begin': 0x2700, 'end': 0x27BF }, // Dingbats
+ { 'begin': 0x3000, 'end': 0x303F }, // CJK Symbols And Punctuation
+ { 'begin': 0x3040, 'end': 0x309F }, // Hiragana
+ { 'begin': 0x30A0, 'end': 0x30FF }, // Katakana
+ { 'begin': 0x3100, 'end': 0x312F }, // Bopomofo
+ { 'begin': 0x3130, 'end': 0x318F }, // Hangul Compatibility Jamo
+ { 'begin': 0xA840, 'end': 0xA87F }, // Phags-pa
+ { 'begin': 0x3200, 'end': 0x32FF }, // Enclosed CJK Letters And Months
+ { 'begin': 0x3300, 'end': 0x33FF }, // CJK Compatibility
+ { 'begin': 0xAC00, 'end': 0xD7AF }, // Hangul Syllables
+ { 'begin': 0xD800, 'end': 0xDFFF }, // Non-Plane 0 *
+ { 'begin': 0x10900, 'end': 0x1091F }, // Phoenicia
+ { 'begin': 0x4E00, 'end': 0x9FFF }, // CJK Unified Ideographs
+ { 'begin': 0xE000, 'end': 0xF8FF }, // Private Use Area (plane 0)
+ { 'begin': 0x31C0, 'end': 0x31EF }, // CJK Strokes
+ { 'begin': 0xFB00, 'end': 0xFB4F }, // Alphabetic Presentation Forms
+ { 'begin': 0xFB50, 'end': 0xFDFF }, // Arabic Presentation Forms-A
+ { 'begin': 0xFE20, 'end': 0xFE2F }, // Combining Half Marks
+ { 'begin': 0xFE10, 'end': 0xFE1F }, // Vertical Forms
+ { 'begin': 0xFE50, 'end': 0xFE6F }, // Small Form Variants
+ { 'begin': 0xFE70, 'end': 0xFEFF }, // Arabic Presentation Forms-B
+ { 'begin': 0xFF00, 'end': 0xFFEF }, // Halfwidth And Fullwidth Forms
+ { 'begin': 0xFFF0, 'end': 0xFFFF }, // Specials
+ { 'begin': 0x0F00, 'end': 0x0FFF }, // Tibetan
+ { 'begin': 0x0700, 'end': 0x074F }, // Syriac
+ { 'begin': 0x0780, 'end': 0x07BF }, // Thaana
+ { 'begin': 0x0D80, 'end': 0x0DFF }, // Sinhala
+ { 'begin': 0x1000, 'end': 0x109F }, // Myanmar
+ { 'begin': 0x1200, 'end': 0x137F }, // Ethiopic
+ { 'begin': 0x13A0, 'end': 0x13FF }, // Cherokee
+ { 'begin': 0x1400, 'end': 0x167F }, // Unified Canadian Aboriginal Syllabics
+ { 'begin': 0x1680, 'end': 0x169F }, // Ogham
+ { 'begin': 0x16A0, 'end': 0x16FF }, // Runic
+ { 'begin': 0x1780, 'end': 0x17FF }, // Khmer
+ { 'begin': 0x1800, 'end': 0x18AF }, // Mongolian
+ { 'begin': 0x2800, 'end': 0x28FF }, // Braille Patterns
+ { 'begin': 0xA000, 'end': 0xA48F }, // Yi Syllables
+ { 'begin': 0x1700, 'end': 0x171F }, // Tagalog
+ { 'begin': 0x10300, 'end': 0x1032F }, // Old Italic
+ { 'begin': 0x10330, 'end': 0x1034F }, // Gothic
+ { 'begin': 0x10400, 'end': 0x1044F }, // Deseret
+ { 'begin': 0x1D000, 'end': 0x1D0FF }, // Byzantine Musical Symbols
+ { 'begin': 0x1D400, 'end': 0x1D7FF }, // Mathematical Alphanumeric Symbols
+ { 'begin': 0xFF000, 'end': 0xFFFFD }, // Private Use (plane 15)
+ { 'begin': 0xFE00, 'end': 0xFE0F }, // Variation Selectors
+ { 'begin': 0xE0000, 'end': 0xE007F }, // Tags
+ { 'begin': 0x1900, 'end': 0x194F }, // Limbu
+ { 'begin': 0x1950, 'end': 0x197F }, // Tai Le
+ { 'begin': 0x1980, 'end': 0x19DF }, // New Tai Lue
+ { 'begin': 0x1A00, 'end': 0x1A1F }, // Buginese
+ { 'begin': 0x2C00, 'end': 0x2C5F }, // Glagolitic
+ { 'begin': 0x2D30, 'end': 0x2D7F }, // Tifinagh
+ { 'begin': 0x4DC0, 'end': 0x4DFF }, // Yijing Hexagram Symbols
+ { 'begin': 0xA800, 'end': 0xA82F }, // Syloti Nagri
+ { 'begin': 0x10000, 'end': 0x1007F }, // Linear B Syllabary
+ { 'begin': 0x10140, 'end': 0x1018F }, // Ancient Greek Numbers
+ { 'begin': 0x10380, 'end': 0x1039F }, // Ugaritic
+ { 'begin': 0x103A0, 'end': 0x103DF }, // Old Persian
+ { 'begin': 0x10450, 'end': 0x1047F }, // Shavian
+ { 'begin': 0x10480, 'end': 0x104AF }, // Osmanya
+ { 'begin': 0x10800, 'end': 0x1083F }, // Cypriot Syllabary
+ { 'begin': 0x10A00, 'end': 0x10A5F }, // Kharoshthi
+ { 'begin': 0x1D300, 'end': 0x1D35F }, // Tai Xuan Jing Symbols
+ { 'begin': 0x12000, 'end': 0x123FF }, // Cuneiform
+ { 'begin': 0x1D360, 'end': 0x1D37F }, // Counting Rod Numerals
+ { 'begin': 0x1B80, 'end': 0x1BBF }, // Sundanese
+ { 'begin': 0x1C00, 'end': 0x1C4F }, // Lepcha
+ { 'begin': 0x1C50, 'end': 0x1C7F }, // Ol Chiki
+ { 'begin': 0xA880, 'end': 0xA8DF }, // Saurashtra
+ { 'begin': 0xA900, 'end': 0xA92F }, // Kayah Li
+ { 'begin': 0xA930, 'end': 0xA95F }, // Rejang
+ { 'begin': 0xAA00, 'end': 0xAA5F }, // Cham
+ { 'begin': 0x10190, 'end': 0x101CF }, // Ancient Symbols
+ { 'begin': 0x101D0, 'end': 0x101FF }, // Phaistos Disc
+ { 'begin': 0x102A0, 'end': 0x102DF }, // Carian
+ { 'begin': 0x1F030, 'end': 0x1F09F } // Domino Tiles
+];
+
+var MacStandardGlyphOrdering = [
+ '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl',
+ 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
+ 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
+ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+ 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft',
+ 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b',
+ 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
+ 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
+ 'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde',
+ 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis',
+ 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
+ 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve',
+ 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex',
+ 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet',
+ 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute',
+ 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal',
+ 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi',
+ 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash',
+ 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin',
+ 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis',
+ 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash',
+ 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright',
+ 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency',
+ 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered',
+ 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex',
+ 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex',
+ 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute',
+ 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron',
+ 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
+ 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar',
+ 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply',
+ 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter',
+ 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla',
+ 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
+
+function getUnicodeRangeFor(value) {
+ for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) {
+ var range = UnicodeRanges[i];
+ if (value >= range.begin && value < range.end)
+ return i;
+ }
+ return -1;
+}
+
+function isRTLRangeFor(value) {
+ var range = UnicodeRanges[13];
+ if (value >= range.begin && value < range.end)
+ return true;
+ range = UnicodeRanges[11];
+ if (value >= range.begin && value < range.end)
+ return true;
+ return false;
+}
+
+function isSpecialUnicode(unicode) {
+ return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
+ (unicode >= kCmapGlyphOffset &&
+ unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
+}
+
+/**
+ * 'Font' is the class the outside world should use, it encapsulate all the font
+ * decoding logics whatever type it is (assuming the font type is supported).
+ *
+ * For example to read a Type1 font and to attach it to the document:
+ * var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
+ * type1Font.bind();
+ */
+var Font = (function FontClosure() {
+ function Font(name, file, properties) {
+ this.name = name;
+ this.coded = properties.coded;
+ this.charProcOperatorList = properties.charProcOperatorList;
+ this.sizes = [];
+
+ var names = name.split('+');
+ names = names.length > 1 ? names[1] : names[0];
+ names = names.split(/[-,_]/g)[0];
+ this.isSerifFont = !!(properties.flags & FontFlags.Serif);
+ this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
+
+ var type = properties.type;
+ this.type = type;
+
+ // If the font is to be ignored, register it like an already loaded font
+ // to avoid the cost of waiting for it be be loaded by the platform.
+ if (properties.ignore) {
+ this.loadedName = this.isSerifFont ? 'serif' : 'sans-serif';
+ this.loading = false;
+ return;
+ }
+
+ this.differences = properties.differences;
+ this.widths = properties.widths;
+ this.defaultWidth = properties.defaultWidth;
+ this.composite = properties.composite;
+ this.hasEncoding = properties.hasEncoding;
+
+ this.fontMatrix = properties.fontMatrix;
+ this.widthMultiplier = 1.0;
+ if (properties.type == 'Type3') {
+ this.encoding = properties.baseEncoding;
+ return;
+ }
+
+ // Trying to fix encoding using glyph CIDSystemInfo.
+ this.loadCidToUnicode(properties);
+
+ if (properties.toUnicode)
+ this.toUnicode = properties.toUnicode;
+ else
+ this.rebuildToUnicode(properties);
+
+ this.toFontChar = this.buildToFontChar(this.toUnicode);
+
+ if (!file) {
+ // The file data is not specified. Trying to fix the font name
+ // to be used with the canvas.font.
+ var fontName = name.replace(/[,_]/g, '-');
+ fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
+
+ this.bold = (fontName.search(/bold/gi) != -1);
+ this.italic = (fontName.search(/oblique/gi) != -1) ||
+ (fontName.search(/italic/gi) != -1);
+
+ // Use 'name' instead of 'fontName' here because the original
+ // name ArialBlack for example will be replaced by Helvetica.
+ this.black = (name.search(/Black/g) != -1);
+
+ this.encoding = properties.baseEncoding;
+ this.noUnicodeAdaptation = true;
+ this.loadedName = fontName.split('-')[0];
+ this.loading = false;
+ return;
+ }
+
+ var data;
+ switch (type) {
+ case 'Type1':
+ case 'CIDFontType0':
+ this.mimetype = 'font/opentype';
+
+ var subtype = properties.subtype;
+ var cff = (subtype == 'Type1C' || subtype == 'CIDFontType0C') ?
+ new CFFFont(file, properties) : new Type1Font(name, file, properties);
+
+ // Wrap the CFF data inside an OTF font file
+ data = this.convert(name, cff, properties);
+ break;
+
+ case 'TrueType':
+ case 'CIDFontType2':
+ this.mimetype = 'font/opentype';
+
+ // Repair the TrueType file. It is can be damaged in the point of
+ // view of the sanitizer
+ data = this.checkAndRepair(name, file, properties);
+ break;
+
+ default:
+ warn('Font ' + properties.type + ' is not supported');
+ break;
+ }
+
+ this.data = data;
+ this.fontMatrix = properties.fontMatrix;
+ this.widthMultiplier = !properties.fontMatrix ? 1.0 :
+ 1.0 / properties.fontMatrix[0];
+ this.encoding = properties.baseEncoding;
+ this.loadedName = properties.loadedName;
+ this.loading = true;
+ };
+
+ var numFonts = 0;
+ function getUniqueName() {
+ return 'pdfFont' + numFonts++;
+ }
+
+ function stringToArray(str) {
+ var array = [];
+ for (var i = 0, ii = str.length; i < ii; ++i)
+ array[i] = str.charCodeAt(i);
+
+ return array;
+ };
+
+ function arrayToString(arr) {
+ var str = '';
+ for (var i = 0, ii = arr.length; i < ii; ++i)
+ str += String.fromCharCode(arr[i]);
+
+ return str;
+ };
+
+ function int16(bytes) {
+ return (bytes[0] << 8) + (bytes[1] & 0xff);
+ };
+
+ function int32(bytes) {
+ return (bytes[0] << 24) + (bytes[1] << 16) +
+ (bytes[2] << 8) + (bytes[3] & 0xff);
+ };
+
+ function getMaxPower2(number) {
+ var maxPower = 0;
+ var value = number;
+ while (value >= 2) {
+ value /= 2;
+ maxPower++;
+ }
+
+ value = 2;
+ for (var i = 1; i < maxPower; i++)
+ value *= 2;
+ return value;
+ };
+
+ function string16(value) {
+ return String.fromCharCode((value >> 8) & 0xff) +
+ String.fromCharCode(value & 0xff);
+ };
+
+ function safeString16(value) {
+ // clamp value to the 16-bit int range
+ value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value;
+ return String.fromCharCode((value >> 8) & 0xff) +
+ String.fromCharCode(value & 0xff);
+ };
+
+ function string32(value) {
+ return String.fromCharCode((value >> 24) & 0xff) +
+ String.fromCharCode((value >> 16) & 0xff) +
+ String.fromCharCode((value >> 8) & 0xff) +
+ String.fromCharCode(value & 0xff);
+ };
+
+ function createOpenTypeHeader(sfnt, file, numTables) {
+ // Windows hates the Mac TrueType sfnt version number
+ if (sfnt == 'true')
+ sfnt = string32(0x00010000);
+
+ // sfnt version (4 bytes)
+ var header = sfnt;
+
+ // numTables (2 bytes)
+ header += string16(numTables);
+
+ // searchRange (2 bytes)
+ var tablesMaxPower2 = getMaxPower2(numTables);
+ var searchRange = tablesMaxPower2 * 16;
+ header += string16(searchRange);
+
+ // entrySelector (2 bytes)
+ header += string16(Math.log(tablesMaxPower2) / Math.log(2));
+
+ // rangeShift (2 bytes)
+ header += string16(numTables * 16 - searchRange);
+
+ file.file += header;
+ file.virtualOffset += header.length;
+ };
+
+ function createTableEntry(file, tag, data) {
+ // offset
+ var offset = file.virtualOffset;
+
+ // length
+ var length = data.length;
+
+ // Per spec tables must be 4-bytes align so add padding as needed
+ while (data.length & 3)
+ data.push(0x00);
+
+ while (file.virtualOffset & 3)
+ file.virtualOffset++;
+
+ // checksum
+ var checksum = 0, n = data.length;
+ for (var i = 0; i < n; i += 4)
+ checksum = (checksum + int32([data[i], data[i + 1], data[i + 2],
+ data[i + 3]])) | 0;
+
+ var tableEntry = (tag + string32(checksum) +
+ string32(offset) + string32(length));
+ file.file += tableEntry;
+ file.virtualOffset += data.length;
+ };
+
+ function getRanges(glyphs) {
+ // Array.sort() sorts by characters, not numerically, so convert to an
+ // array of characters.
+ var codes = [];
+ var length = glyphs.length;
+ for (var n = 0; n < length; ++n)
+ codes.push({ unicode: glyphs[n].unicode, code: n });
+ codes.sort(function fontGetRangesSort(a, b) {
+ return a.unicode - b.unicode;
+ });
+
+ // Split the sorted codes into ranges.
+ var ranges = [];
+ for (var n = 0; n < length; ) {
+ var start = codes[n].unicode;
+ var codeIndices = [codes[n].code];
+ ++n;
+ var end = start;
+ while (n < length && end + 1 == codes[n].unicode) {
+ codeIndices.push(codes[n].code);
+ ++end;
+ ++n;
+ }
+ ranges.push([start, end, codeIndices]);
+ }
+
+ return ranges;
+ };
+
+ function createCMapTable(glyphs, deltas) {
+ var ranges = getRanges(glyphs);
+
+ var numTables = 1;
+ var cmap = '\x00\x00' + // version
+ string16(numTables) + // numTables
+ '\x00\x03' + // platformID
+ '\x00\x01' + // encodingID
+ string32(4 + numTables * 8); // start of the table record
+
+ var segCount = ranges.length + 1;
+ var segCount2 = segCount * 2;
+ var searchRange = getMaxPower2(segCount) * 2;
+ var searchEntry = Math.log(segCount) / Math.log(2);
+ var rangeShift = 2 * segCount - searchRange;
+
+ // Fill up the 4 parallel arrays describing the segments.
+ var startCount = '';
+ var endCount = '';
+ var idDeltas = '';
+ var idRangeOffsets = '';
+ var glyphsIds = '';
+ var bias = 0;
+
+ if (deltas) {
+ for (var i = 0; i < segCount - 1; i++) {
+ var range = ranges[i];
+ var start = range[0];
+ var end = range[1];
+ var offset = (segCount - i) * 2 + bias * 2;
+ bias += (end - start + 1);
+
+ startCount += string16(start);
+ endCount += string16(end);
+ idDeltas += string16(0);
+ idRangeOffsets += string16(offset);
+
+ var codes = range[2];
+ for (var j = 0, jj = codes.length; j < jj; ++j)
+ glyphsIds += string16(deltas[codes[j]]);
+ }
+ } else {
+ for (var i = 0; i < segCount - 1; i++) {
+ var range = ranges[i];
+ var start = range[0];
+ var end = range[1];
+ var startCode = range[2][0];
+
+ startCount += string16(start);
+ endCount += string16(end);
+ idDeltas += string16((startCode - start + 1) & 0xFFFF);
+ idRangeOffsets += string16(0);
+ }
+ }
+
+ endCount += '\xFF\xFF';
+ startCount += '\xFF\xFF';
+ idDeltas += '\x00\x01';
+ idRangeOffsets += '\x00\x00';
+
+ var format314 = '\x00\x00' + // language
+ string16(segCount2) +
+ string16(searchRange) +
+ string16(searchEntry) +
+ string16(rangeShift) +
+ endCount + '\x00\x00' + startCount +
+ idDeltas + idRangeOffsets + glyphsIds;
+
+ return stringToArray(cmap +
+ '\x00\x04' + // format
+ string16(format314.length + 4) + // length
+ format314);
+ };
+
+ function createOS2Table(properties, charstrings, override) {
+ override = override || {
+ unitsPerEm: 0,
+ yMax: 0,
+ yMin: 0,
+ ascent: 0,
+ descent: 0
+ };
+
+ var ulUnicodeRange1 = 0;
+ var ulUnicodeRange2 = 0;
+ var ulUnicodeRange3 = 0;
+ var ulUnicodeRange4 = 0;
+
+ var firstCharIndex = null;
+ var lastCharIndex = 0;
+
+ if (charstrings) {
+ for (var i = 0; i < charstrings.length; ++i) {
+ var code = charstrings[i].unicode;
+ if (firstCharIndex > code || !firstCharIndex)
+ firstCharIndex = code;
+ if (lastCharIndex < code)
+ lastCharIndex = code;
+
+ var position = getUnicodeRangeFor(code);
+ if (position < 32) {
+ ulUnicodeRange1 |= 1 << position;
+ } else if (position < 64) {
+ ulUnicodeRange2 |= 1 << position - 32;
+ } else if (position < 96) {
+ ulUnicodeRange3 |= 1 << position - 64;
+ } else if (position < 123) {
+ ulUnicodeRange4 |= 1 << position - 96;
+ } else {
+ error('Unicode ranges Bits > 123 are reserved for internal usage');
+ }
+ }
+ } else {
+ // TODO
+ firstCharIndex = 0;
+ lastCharIndex = 255;
+ }
+
+ var unitsPerEm = override.unitsPerEm || kPDFGlyphSpaceUnits;
+ var typoAscent = override.ascent || properties.ascent;
+ var typoDescent = override.descent || properties.descent;
+ var winAscent = override.yMax || typoAscent;
+ var winDescent = -override.yMin || -typoDescent;
+
+ // if there is a units per em value but no other override
+ // then scale the calculated ascent
+ if (unitsPerEm != kPDFGlyphSpaceUnits &&
+ 'undefined' == typeof(override.ascent)) {
+ // if the font units differ to the PDF glyph space units
+ // then scale up the values
+ typoAscent = Math.round(typoAscent * unitsPerEm / kPDFGlyphSpaceUnits);
+ typoDescent = Math.round(typoDescent * unitsPerEm / kPDFGlyphSpaceUnits);
+ winAscent = typoAscent;
+ winDescent = -typoDescent;
+ }
+
+ return '\x00\x03' + // version
+ '\x02\x24' + // xAvgCharWidth
+ '\x01\xF4' + // usWeightClass
+ '\x00\x05' + // usWidthClass
+ '\x00\x00' + // fstype (0 to let the font loads via font-face on IE)
+ '\x02\x8A' + // ySubscriptXSize
+ '\x02\xBB' + // ySubscriptYSize
+ '\x00\x00' + // ySubscriptXOffset
+ '\x00\x8C' + // ySubscriptYOffset
+ '\x02\x8A' + // ySuperScriptXSize
+ '\x02\xBB' + // ySuperScriptYSize
+ '\x00\x00' + // ySuperScriptXOffset
+ '\x01\xDF' + // ySuperScriptYOffset
+ '\x00\x31' + // yStrikeOutSize
+ '\x01\x02' + // yStrikeOutPosition
+ '\x00\x00' + // sFamilyClass
+ '\x00\x00\x06' +
+ String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) +
+ '\x00\x00\x00\x00\x00\x00' + // Panose
+ string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)
+ string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)
+ string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)
+ string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)
+ '\x2A\x32\x31\x2A' + // achVendID
+ string16(properties.italicAngle ? 1 : 0) + // fsSelection
+ string16(firstCharIndex ||
+ properties.firstChar) + // usFirstCharIndex
+ string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
+ string16(typoAscent) + // sTypoAscender
+ string16(typoDescent) + // sTypoDescender
+ '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
+ string16(winAscent) + // usWinAscent
+ string16(winDescent) + // usWinDescent
+ '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
+ '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
+ string16(properties.xHeight) + // sxHeight
+ string16(properties.capHeight) + // sCapHeight
+ string16(0) + // usDefaultChar
+ string16(firstCharIndex || properties.firstChar) + // usBreakChar
+ '\x00\x03'; // usMaxContext
+ };
+
+ function createPostTable(properties) {
+ var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
+ return '\x00\x03\x00\x00' + // Version number
+ string32(angle) + // italicAngle
+ '\x00\x00' + // underlinePosition
+ '\x00\x00' + // underlineThickness
+ string32(properties.fixedPitch) + // isFixedPitch
+ '\x00\x00\x00\x00' + // minMemType42
+ '\x00\x00\x00\x00' + // maxMemType42
+ '\x00\x00\x00\x00' + // minMemType1
+ '\x00\x00\x00\x00'; // maxMemType1
+ };
+
+ function createNameTable(name) {
+ var strings = [
+ 'Original licence', // 0.Copyright
+ name, // 1.Font family
+ 'Unknown', // 2.Font subfamily (font weight)
+ name, // 3.Unique ID
+ name, // 4.Full font name
+ 'Version 0.11', // 5.Version
+ name // 6.Postscript name
+ ];
+
+ // Mac want 1-byte per character strings while Windows want
+ // 2-bytes per character, so duplicate the names table
+ var stringsUnicode = [];
+ for (var i = 0, ii = strings.length; i < ii; i++) {
+ var str = strings[i];
+
+ var strUnicode = '';
+ for (var j = 0, jj = str.length; j < jj; j++)
+ strUnicode += string16(str.charCodeAt(j));
+ stringsUnicode.push(strUnicode);
+ }
+
+ var names = [strings, stringsUnicode];
+ var platforms = ['\x00\x01', '\x00\x03'];
+ var encodings = ['\x00\x00', '\x00\x01'];
+ var languages = ['\x00\x00', '\x04\x09'];
+
+ var namesRecordCount = strings.length * platforms.length;
+ var nameTable =
+ '\x00\x00' + // format
+ string16(namesRecordCount) + // Number of names Record
+ string16(namesRecordCount * 12 + 6); // Storage
+
+ // Build the name records field
+ var strOffset = 0;
+ for (var i = 0, ii = platforms.length; i < ii; i++) {
+ var strs = names[i];
+ for (var j = 0, jj = strs.length; j < jj; j++) {
+ var str = strs[j];
+ var nameRecord =
+ platforms[i] + // platform ID
+ encodings[i] + // encoding ID
+ languages[i] + // language ID
+ string16(j) + // name ID
+ string16(str.length) +
+ string16(strOffset);
+ nameTable += nameRecord;
+ strOffset += str.length;
+ }
+ }
+
+ nameTable += strings.join('') + stringsUnicode.join('');
+ return nameTable;
+ }
+
+ Font.prototype = {
+ name: null,
+ font: null,
+ mimetype: null,
+ encoding: null,
+
+ checkAndRepair: function Font_checkAndRepair(name, font, properties) {
+ function readTableEntry(file) {
+ var tag = file.getBytes(4);
+ tag = String.fromCharCode(tag[0]) +
+ String.fromCharCode(tag[1]) +
+ String.fromCharCode(tag[2]) +
+ String.fromCharCode(tag[3]);
+
+ var checksum = int32(file.getBytes(4));
+ var offset = int32(file.getBytes(4));
+ var length = int32(file.getBytes(4));
+
+ // Read the table associated data
+ var previousPosition = file.pos;
+ file.pos = file.start ? file.start : 0;
+ file.skip(offset);
+ var data = file.getBytes(length);
+ file.pos = previousPosition;
+
+ if (tag == 'head') {
+ // clearing checksum adjustment
+ data[8] = data[9] = data[10] = data[11] = 0;
+ data[17] |= 0x20; //Set font optimized for cleartype flag
+ }
+
+ return {
+ tag: tag,
+ checksum: checksum,
+ length: length,
+ offset: offset,
+ data: data
+ };
+ };
+
+ function readOpenTypeHeader(ttf) {
+ return {
+ version: arrayToString(ttf.getBytes(4)),
+ numTables: int16(ttf.getBytes(2)),
+ searchRange: int16(ttf.getBytes(2)),
+ entrySelector: int16(ttf.getBytes(2)),
+ rangeShift: int16(ttf.getBytes(2))
+ };
+ };
+
+ function createGlyphNameMap(glyphs, ids, properties) {
+ var glyphNames = properties.glyphNames;
+ if (!glyphNames) {
+ properties.glyphNameMap = {};
+ return;
+ }
+ var glyphsLength = glyphs.length;
+ var glyphNameMap = {};
+ var encoding = [];
+ for (var i = 0; i < glyphsLength; ++i) {
+ var glyphName = glyphNames[ids[i]];
+ if (!glyphName)
+ continue;
+ var unicode = glyphs[i].unicode;
+ glyphNameMap[glyphName] = unicode;
+ var code = glyphs[i].code;
+ encoding[code] = glyphName;
+ }
+ properties.glyphNameMap = glyphNameMap;
+ if (!properties.hasEncoding)
+ properties.baseEncoding = encoding;
+ }
+
+ function readCMapTable(cmap, font) {
+ var start = (font.start ? font.start : 0) + cmap.offset;
+ font.pos = start;
+
+ var version = int16(font.getBytes(2));
+ var numRecords = int16(font.getBytes(2));
+
+ var records = [];
+ for (var i = 0; i < numRecords; i++) {
+ records.push({
+ platformID: int16(font.getBytes(2)),
+ encodingID: int16(font.getBytes(2)),
+ offset: int32(font.getBytes(4))
+ });
+ }
+
+ // Check that table are sorted by platformID then encodingID,
+ records.sort(function fontReadCMapTableSort(a, b) {
+ return ((a.platformID << 16) + a.encodingID) -
+ ((b.platformID << 16) + b.encodingID);
+ });
+
+ var tables = [records[0]];
+ for (var i = 1; i < numRecords; i++) {
+ // The sanitizer will drop the font if 2 tables have the same
+ // platformID and the same encodingID, this will be correct for
+ // most cases but if the font has been made for Mac it could
+ // exist a few platformID: 1, encodingID: 0 but with a different
+ // language field and that's correct. But the sanitizer does not
+ // seem to support this case.
+ var current = records[i];
+ var previous = records[i - 1];
+ if (((current.platformID << 16) + current.encodingID) <=
+ ((previous.platformID << 16) + previous.encodingID))
+ continue;
+ tables.push(current);
+ }
+
+ var missing = numRecords - tables.length;
+ if (missing) {
+ numRecords = tables.length;
+ var data = string16(version) + string16(numRecords);
+
+ for (var i = 0; i < numRecords; i++) {
+ var table = tables[i];
+ data += string16(table.platformID) +
+ string16(table.encodingID) +
+ string32(table.offset);
+ }
+
+ for (var i = 0, ii = data.length; i < ii; i++)
+ cmap.data[i] = data.charCodeAt(i);
+ }
+
+ for (var i = 0; i < numRecords; i++) {
+ var table = tables[i];
+ font.pos = start + table.offset;
+
+ var format = int16(font.getBytes(2));
+ var length = int16(font.getBytes(2));
+ var language = int16(font.getBytes(2));
+
+ if (format == 0) {
+ // Characters below 0x20 are controls characters that are hardcoded
+ // into the platform so if some characters in the font are assigned
+ // under this limit they will not be displayed so let's rewrite the
+ // CMap.
+ var glyphs = [];
+ var ids = [];
+ for (var j = 0; j < 256; j++) {
+ var index = font.getByte();
+ if (index) {
+ glyphs.push({ unicode: j, code: j });
+ ids.push(index);
+ }
+ }
+ return {
+ glyphs: glyphs,
+ ids: ids,
+ hasShortCmap: true
+ };
+ } else if (format == 4) {
+ // re-creating the table in format 4 since the encoding
+ // might be changed
+ var segCount = (int16(font.getBytes(2)) >> 1);
+ font.getBytes(6); // skipping range fields
+ var segIndex, segments = [];
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments.push({ end: int16(font.getBytes(2)) });
+ }
+ font.getBytes(2);
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].start = int16(font.getBytes(2));
+ }
+
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].delta = int16(font.getBytes(2));
+ }
+
+ var offsetsCount = 0;
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ var segment = segments[segIndex];
+ var rangeOffset = int16(font.getBytes(2));
+ if (!rangeOffset) {
+ segment.offsetIndex = -1;
+ continue;
+ }
+
+ var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
+ segment.offsetIndex = offsetIndex;
+ offsetsCount = Math.max(offsetsCount, offsetIndex +
+ segment.end - segment.start + 1);
+ }
+
+ var offsets = [];
+ for (var j = 0; j < offsetsCount; j++)
+ offsets.push(int16(font.getBytes(2)));
+
+ var glyphs = [], ids = [];
+
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ var segment = segments[segIndex];
+ var start = segment.start, end = segment.end;
+ var delta = segment.delta, offsetIndex = segment.offsetIndex;
+
+ for (var j = start; j <= end; j++) {
+ if (j == 0xFFFF)
+ continue;
+
+ var glyphCode = offsetIndex < 0 ? j :
+ offsets[offsetIndex + j - start];
+ glyphCode = (glyphCode + delta) & 0xFFFF;
+ if (glyphCode == 0)
+ continue;
+
+ glyphs.push({ unicode: j, code: j });
+ ids.push(glyphCode);
+ }
+ }
+
+ return {
+ glyphs: glyphs,
+ ids: ids
+ };
+ } else if (format == 6) {
+ // Format 6 is a 2-bytes dense mapping, which means the font data
+ // lives glue together even if they are pretty far in the unicode
+ // table. (This looks weird, so I can have missed something), this
+ // works on Linux but seems to fails on Mac so let's rewrite the
+ // cmap table to a 3-1-4 style
+ var firstCode = int16(font.getBytes(2));
+ var entryCount = int16(font.getBytes(2));
+
+ var glyphs = [];
+ var ids = [];
+ for (var j = 0; j < entryCount; j++) {
+ var glyphCode = int16(font.getBytes(2));
+ var code = firstCode + j;
+
+ glyphs.push({ unicode: code, code: code });
+ ids.push(glyphCode);
+ }
+
+ return {
+ glyphs: glyphs,
+ ids: ids
+ };
+ }
+ }
+ error('Unsupported cmap table format');
+ };
+
+ function sanitizeMetrics(font, header, metrics, numGlyphs) {
+ if (!header && !metrics)
+ return;
+
+ // The vhea/vmtx tables are not required, so it happens that
+ // some fonts embed a vmtx table without a vhea table. In this
+ // situation the sanitizer assume numOfLongVerMetrics = 1. As
+ // a result it tries to read numGlyphs - 1 SHORT from the vmtx
+ // table, and if it is not possible, the font is rejected.
+ // So remove the vmtx table if there is no vhea table.
+ if (!header && metrics) {
+ metrics.data = null;
+ return;
+ }
+
+ font.pos = (font.start ? font.start : 0) + header.offset;
+ font.pos += header.length - 2;
+ var numOfMetrics = int16(font.getBytes(2));
+
+ var numOfSidebearings = numGlyphs - numOfMetrics;
+ var numMissing = numOfSidebearings -
+ ((hmtx.length - numOfMetrics * 4) >> 1);
+ if (numMissing > 0) {
+ font.pos = (font.start ? font.start : 0) + metrics.offset;
+ var entries = '';
+ for (var i = 0, ii = hmtx.length; i < ii; i++)
+ entries += String.fromCharCode(font.getByte());
+ for (var i = 0; i < numMissing; i++)
+ entries += '\x00\x00';
+ metrics.data = stringToArray(entries);
+ }
+ };
+
+ function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) {
+ if (sourceEnd - sourceStart <= 12) {
+ // glyph with data less than 12 is invalid one
+ return 0;
+ }
+ var glyf = source.subarray(sourceStart, sourceEnd);
+ var contoursCount = (glyf[0] << 8) | glyf[1];
+ if (contoursCount & 0x8000) {
+ // complex glyph, writing as is
+ dest.set(glyf, destStart);
+ return glyf.length;
+ }
+
+ var j = 10, flagsCount = 0;
+ for (var i = 0; i < contoursCount; i++) {
+ var endPoint = (glyf[j] << 8) | glyf[j + 1];
+ flagsCount = endPoint + 1;
+ j += 2;
+ }
+ // skipping instructions
+ var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
+ j += 2 + instructionsLength;
+ // validating flags
+ var coordinatesLength = 0;
+ for (var i = 0; i < flagsCount; i++) {
+ var flag = glyf[j++];
+ if (flag & 0xC0) {
+ // reserved flags must be zero, rejecting
+ return 0;
+ }
+ var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) +
+ ((flag & 4) ? 1 : (flag & 32) ? 0 : 2);
+ coordinatesLength += xyLength;
+ if (flag & 8) {
+ var repeat = glyf[j++];
+ i += repeat;
+ coordinatesLength += repeat * xyLength;
+ }
+ }
+ var glyphDataLength = j + coordinatesLength;
+ if (glyphDataLength > glyf.length) {
+ // not enough data for coordinates
+ return 0;
+ }
+ if (glyf.length - glyphDataLength > 3) {
+ // truncating and aligning to 4 bytes the long glyph data
+ glyphDataLength = (glyphDataLength + 3) & ~3;
+ dest.set(glyf.subarray(0, glyphDataLength), destStart);
+ return glyphDataLength;
+ }
+ // glyph data is fine
+ dest.set(glyf, destStart);
+ return glyf.length;
+ }
+
+ function sanitizeGlyphLocations(loca, glyf, numGlyphs,
+ isGlyphLocationsLong) {
+ var itemSize, itemDecode, itemEncode;
+ if (isGlyphLocationsLong) {
+ itemSize = 4;
+ itemDecode = function fontItemDecodeLong(data, offset) {
+ return (data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3];
+ };
+ itemEncode = function fontItemEncodeLong(data, offset, value) {
+ data[offset] = (value >>> 24) & 0xFF;
+ data[offset + 1] = (value >> 16) & 0xFF;
+ data[offset + 2] = (value >> 8) & 0xFF;
+ data[offset + 3] = value & 0xFF;
+ };
+ } else {
+ itemSize = 2;
+ itemDecode = function fontItemDecode(data, offset) {
+ return (data[offset] << 9) | (data[offset + 1] << 1);
+ };
+ itemEncode = function fontItemEncode(data, offset, value) {
+ data[offset] = (value >> 9) & 0xFF;
+ data[offset + 1] = (value >> 1) & 0xFF;
+ };
+ }
+ var locaData = loca.data;
+ // removing the invalid glyphs
+ var oldGlyfData = glyf.data;
+ var oldGlyfDataLength = oldGlyfData.length;
+ var newGlyfData = new Uint8Array(oldGlyfDataLength);
+ var startOffset = itemDecode(locaData, 0);
+ var writeOffset = 0;
+ itemEncode(locaData, 0, writeOffset);
+ for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
+ var endOffset = itemDecode(locaData, j);
+ if (endOffset > oldGlyfDataLength) {
+ // glyph end offset points outside glyf data, rejecting the glyph
+ itemEncode(locaData, j, writeOffset);
+ startOffset = endOffset;
+ continue;
+ }
+
+ var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
+ newGlyfData, writeOffset);
+ writeOffset += newLength;
+ itemEncode(locaData, j, writeOffset);
+ startOffset = endOffset;
+ }
+
+ if (writeOffset == 0) {
+ // glyf table cannot be empty -- redoing the glyf and loca tables
+ // to have single glyph with one point
+ var simpleGlyph = new Uint8Array(
+ [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
+ for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize)
+ itemEncode(locaData, j, simpleGlyph.length);
+ glyf.data = simpleGlyph;
+ return;
+ }
+
+ glyf.data = newGlyfData.subarray(0, writeOffset);
+ }
+
+ function findEmptyGlyphs(locaTable, isGlyphLocationsLong, emptyGlyphIds) {
+ var itemSize, itemDecode;
+ if (isGlyphLocationsLong) {
+ itemSize = 4;
+ itemDecode = function fontItemDecodeLong(data, offset) {
+ return (data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3];
+ };
+ } else {
+ itemSize = 2;
+ itemDecode = function fontItemDecode(data, offset) {
+ return (data[offset] << 9) | (data[offset + 1] << 1);
+ };
+ }
+ var data = locaTable.data, length = data.length;
+ var lastOffset = itemDecode(data, 0);
+ for (var i = itemSize, j = 0; i < length; i += itemSize, j++) {
+ var offset = itemDecode(data, i);
+ if (offset == lastOffset)
+ emptyGlyphIds[j] = true;
+ lastOffset = offset;
+ }
+ }
+
+ function readGlyphNameMap(post, properties) {
+ var start = (font.start ? font.start : 0) + post.offset;
+ font.pos = start;
+
+ var length = post.length, end = start + length;
+ var version = int32(font.getBytes(4));
+ // skip rest to the tables
+ font.getBytes(28);
+
+ var glyphNames;
+ switch (version) {
+ case 0x00010000:
+ glyphNames = MacStandardGlyphOrdering;
+ break;
+ case 0x00020000:
+ var numGlyphs = int16(font.getBytes(2));
+ var glyphNameIndexes = [];
+ for (var i = 0; i < numGlyphs; ++i)
+ glyphNameIndexes.push(int16(font.getBytes(2)));
+ var customNames = [];
+ while (font.pos < end) {
+ var stringLength = font.getByte();
+ var string = '';
+ for (var i = 0; i < stringLength; ++i)
+ string += font.getChar();
+ customNames.push(string);
+ }
+ glyphNames = [];
+ for (var i = 0; i < numGlyphs; ++i) {
+ var j = glyphNameIndexes[i];
+ if (j < 258) {
+ glyphNames.push(MacStandardGlyphOrdering[j]);
+ continue;
+ }
+ glyphNames.push(customNames[j - 258]);
+ }
+ break;
+ case 0x00030000:
+ break;
+ default:
+ warn('Unknown/unsupported post table version ' + version);
+ break;
+ }
+ properties.glyphNames = glyphNames;
+ }
+
+ function isOS2Valid(os2Table) {
+ var data = os2Table.data;
+ // usWinAscent == 0 makes font unreadable by windows
+ var usWinAscent = (data[74] << 8) | data[75];
+ if (usWinAscent == 0)
+ return false;
+
+ return true;
+ }
+
+ // Check that required tables are present
+ var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
+ 'hmtx', 'maxp', 'name', 'post'];
+
+ var header = readOpenTypeHeader(font);
+ var numTables = header.numTables;
+
+ var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf, os2;
+ var tables = [];
+ for (var i = 0; i < numTables; i++) {
+ var table = readTableEntry(font);
+ var index = requiredTables.indexOf(table.tag);
+ if (index != -1) {
+ if (table.tag == 'cmap')
+ cmap = table;
+ else if (table.tag == 'post')
+ post = table;
+ else if (table.tag == 'maxp')
+ maxp = table;
+ else if (table.tag == 'hhea')
+ hhea = table;
+ else if (table.tag == 'hmtx')
+ hmtx = table;
+ else if (table.tag == 'head')
+ head = table;
+ else if (table.tag == 'OS/2')
+ os2 = table;
+
+ requiredTables.splice(index, 1);
+ } else {
+ if (table.tag == 'vmtx')
+ vmtx = table;
+ else if (table.tag == 'vhea')
+ vhea = table;
+ else if (table.tag == 'loca')
+ loca = table;
+ else if (table.tag == 'glyf')
+ glyf = table;
+ }
+ tables.push(table);
+ }
+
+ var numTables = tables.length + requiredTables.length;
+
+ // header and new offsets. Table entry information is appended to the
+ // end of file. The virtualOffset represents where to put the actual
+ // data of a particular table;
+ var ttf = {
+ file: '',
+ virtualOffset: numTables * (4 * 4)
+ };
+
+ // The new numbers of tables will be the last one plus the num
+ // of missing tables
+ createOpenTypeHeader(header.version, ttf, numTables);
+
+ // Invalid OS/2 can break the font for the Windows
+ if (os2 && !isOS2Valid(os2)) {
+ tables.splice(tables.indexOf(os2), 1);
+ os2 = null;
+ }
+
+ // Ensure the [h/v]mtx tables contains the advance width and
+ // sidebearings information for numGlyphs in the maxp table
+ font.pos = (font.start || 0) + maxp.offset;
+ var version = int16(font.getBytes(4));
+ var numGlyphs = int16(font.getBytes(2));
+
+ sanitizeMetrics(font, hhea, hmtx, numGlyphs);
+ sanitizeMetrics(font, vhea, vmtx, numGlyphs);
+
+ var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
+ if (head && loca && glyf) {
+ sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
+ }
+
+ var emptyGlyphIds = [];
+ if (glyf)
+ findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds);
+
+ // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
+ // Sometimes it's 0. That needs to be fixed
+ if (hhea.data[10] == 0 && hhea.data[11] == 0) {
+ hhea.data[10] = 0xFF;
+ hhea.data[11] = 0xFF;
+ }
+
+ // The 'post' table has glyphs names.
+ if (post) {
+ readGlyphNameMap(post, properties);
+ }
+
+ var glyphs, ids;
+ if (properties.type == 'CIDFontType2') {
+ // Replace the old CMAP table with a shiny new one
+ // Type2 composite fonts map characters directly to glyphs so the cmap
+ // table must be replaced.
+ // canvas fillText will reencode some characters even if the font has a
+ // glyph at that position - e.g. newline is converted to a space and
+ // U+00AD (soft hyphen) is not drawn.
+ // So, offset all the glyphs by 0xFF to avoid these cases and use
+ // the encoding to map incoming characters to the new glyph positions
+ if (!cmap) {
+ cmap = {
+ tag: 'cmap',
+ data: null
+ };
+ tables.push(cmap);
+ }
+
+ var cidToGidMap = properties.cidToGidMap || [];
+ var gidToCidMap = [0];
+ if (cidToGidMap.length > 0) {
+ for (var j = cidToGidMap.length - 1; j >= 0; j--) {
+ var gid = cidToGidMap[j];
+ if (gid)
+ gidToCidMap[gid] = j;
+ }
+ // filling the gaps using CID above the CIDs currently used in font
+ var nextCid = cidToGidMap.length;
+ for (var i = 1; i < numGlyphs; i++) {
+ if (!gidToCidMap[i])
+ gidToCidMap[i] = nextCid++;
+ }
+ }
+
+ glyphs = [];
+ ids = [];
+
+ var usedUnicodes = [];
+ var unassignedUnicodeItems = [];
+ for (var i = 1; i < numGlyphs; i++) {
+ var cid = gidToCidMap[i] || i;
+ var unicode = this.toFontChar[cid];
+ if (!unicode || typeof unicode !== 'number' ||
+ isSpecialUnicode(unicode) || unicode in usedUnicodes) {
+ unassignedUnicodeItems.push(i);
+ continue;
+ }
+ usedUnicodes[unicode] = true;
+ glyphs.push({ unicode: unicode, code: cid });
+ ids.push(i);
+ }
+ // trying to fit as many unassigned symbols as we can
+ // in the range allocated for the user defined symbols
+ var unusedUnicode = kCmapGlyphOffset;
+ for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
+ var i = unassignedUnicodeItems[j];
+ var cid = gidToCidMap[i] || i;
+ while (unusedUnicode in usedUnicodes)
+ unusedUnicode++;
+ if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea)
+ break;
+ var unicode = unusedUnicode++;
+ this.toFontChar[cid] = unicode;
+ usedUnicodes[unicode] = true;
+ glyphs.push({ unicode: unicode, code: cid });
+ ids.push(i);
+ }
+ } else {
+ var cmapTable = readCMapTable(cmap, font);
+
+ glyphs = cmapTable.glyphs;
+ ids = cmapTable.ids;
+
+ var hasShortCmap = !!cmapTable.hasShortCmap;
+ var toFontChar = this.toFontChar;
+
+ if (hasShortCmap && ids.length == numGlyphs) {
+ // Fixes the short cmap tables -- some generators use incorrect
+ // glyph id.
+ for (var i = 0, ii = ids.length; i < ii; i++)
+ ids[i] = i;
+ }
+
+ var unusedUnicode = kCmapGlyphOffset;
+ var glyphNames = properties.glyphNames || [];
+ var encoding = properties.baseEncoding;
+ var differences = properties.differences;
+ if (toFontChar && toFontChar.length > 0) {
+ // checking if cmap is just identity map
+ var isIdentity = true;
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ if (glyphs[i].unicode != i + 1) {
+ isIdentity = false;
+ break;
+ }
+ }
+ // if it is, replacing with meaningful toUnicode values
+ if (isIdentity && !this.isSymbolicFont) {
+ var usedUnicodes = [], unassignedUnicodeItems = [];
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var unicode = toFontChar[i + 1];
+ if (!unicode || typeof unicode !== 'number' ||
+ unicode in usedUnicodes) {
+ unassignedUnicodeItems.push(i);
+ continue;
+ }
+ glyphs[i].unicode = unicode;
+ usedUnicodes[unicode] = true;
+ }
+ for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
+ var i = unassignedUnicodeItems[j];
+ while (unusedUnicode in usedUnicodes)
+ unusedUnicode++;
+ var cid = i + 1;
+ // override only if unicode mapping is not specified
+ if (!(cid in toFontChar))
+ toFontChar[cid] = unusedUnicode;
+ glyphs[i].unicode = unusedUnicode++;
+ }
+ this.useToFontChar = true;
+ }
+ }
+
+ // remove glyph references outside range of avaialable glyphs or empty
+ var glyphsRemoved = 0;
+ for (var i = ids.length - 1; i >= 0; i--) {
+ if (ids[i] < numGlyphs &&
+ (!emptyGlyphIds[ids[i]] || this.isSymbolicFont))
+ continue;
+ ids.splice(i, 1);
+ glyphs.splice(i, 1);
+ glyphsRemoved++;
+ }
+
+ // checking if it's a "true" symbolic font
+ if (this.isSymbolicFont) {
+ var minUnicode = 0xFFFF, maxUnicode = 0;
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var unicode = glyphs[i].unicode;
+ minUnicode = Math.min(minUnicode, unicode);
+ maxUnicode = Math.max(maxUnicode, unicode);
+ }
+ // high byte must be the same for min and max unicodes
+ if ((maxUnicode & 0xFF00) != (minUnicode & 0xFF00))
+ this.isSymbolicFont = false;
+ }
+
+ // heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding
+ // does not set properly
+ if (glyphsRemoved > 2) {
+ warn('Switching TrueType encoding to MacRomanEncoding for ' +
+ this.name + ' font');
+ encoding = Encodings.MacRomanEncoding;
+ }
+
+ if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) {
+ // Re-encode short map encoding to unicode -- that simplifies the
+ // resolution of MacRoman encoded glyphs logic for TrueType fonts:
+ // copying all characters to private use area, all mapping all known
+ // glyphs to the unicodes. The glyphs and ids arrays will grow.
+ var usedUnicodes = [];
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var code = glyphs[i].unicode;
+ var gid = ids[i];
+ glyphs[i].unicode += kCmapGlyphOffset;
+ toFontChar[code] = glyphs[i].unicode;
+
+ var glyphName = glyphNames[gid] || encoding[code];
+ if (glyphName in GlyphsUnicode) {
+ var unicode = GlyphsUnicode[glyphName];
+ if (unicode in usedUnicodes)
+ continue;
+
+ usedUnicodes[unicode] = true;
+ glyphs.push({
+ unicode: unicode,
+ code: glyphs[i].code
+ });
+ ids.push(gid);
+ toFontChar[code] = unicode;
+ }
+ }
+ this.useToFontChar = true;
+ } else if (!this.isSymbolicFont && (this.hasEncoding ||
+ properties.glyphNames || differences.length > 0)) {
+ // Re-encode cmap encoding to unicode, based on the 'post' table data
+ // diffrence array or base encoding
+ var reverseMap = [];
+ for (var i = 0, ii = glyphs.length; i < ii; i++)
+ reverseMap[glyphs[i].unicode] = i;
+
+ var newGlyphUnicodes = [];
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var code = glyphs[i].unicode;
+ var changeCode = false;
+ var gid = ids[i];
+
+ var glyphName = glyphNames[gid];
+ if (!glyphName) {
+ glyphName = differences[code] || encoding[code];
+ changeCode = true;
+ }
+ if (glyphName in GlyphsUnicode) {
+ var unicode = GlyphsUnicode[glyphName];
+ if (!unicode || reverseMap[unicode] === i)
+ continue; // unknown glyph name or in its own place
+
+ newGlyphUnicodes[i] = unicode;
+ if (changeCode)
+ toFontChar[code] = unicode;
+ delete reverseMap[code];
+ }
+ }
+ for (var index in newGlyphUnicodes) {
+ var unicode = newGlyphUnicodes[index];
+ if (reverseMap[unicode]) {
+ // avoiding assigning to the same unicode
+ glyphs[index].unicode = unusedUnicode++;
+ continue;
+ }
+ glyphs[index].unicode = unicode;
+ reverseMap[unicode] = index;
+ }
+ this.useToFontChar = true;
+ }
+
+ // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range.
+ if (this.isSymbolicFont) {
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var code = glyphs[i].unicode & 0xFF;
+ var fontCharCode = kSymbolicFontGlyphOffset | code;
+ glyphs[i].unicode = toFontChar[code] = fontCharCode;
+ }
+ this.useToFontChar = true;
+ }
+
+ createGlyphNameMap(glyphs, ids, properties);
+ this.glyphNameMap = properties.glyphNameMap;
+ }
+
+ // Converting glyphs and ids into font's cmap table
+ cmap.data = createCMapTable(glyphs, ids);
+ var unicodeIsEnabled = [];
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ unicodeIsEnabled[glyphs[i].unicode] = true;
+ }
+ this.unicodeIsEnabled = unicodeIsEnabled;
+
+ if (!os2) {
+ // extract some more font properties from the OpenType head and
+ // hhea tables; yMin and descent value are always negative
+ var override = {
+ unitsPerEm: int16([head.data[18], head.data[19]]),
+ yMax: int16([head.data[42], head.data[43]]),
+ yMin: int16([head.data[38], head.data[39]]) - 0x10000,
+ ascent: int16([hhea.data[4], hhea.data[5]]),
+ descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
+ };
+
+ tables.push({
+ tag: 'OS/2',
+ data: stringToArray(createOS2Table(properties, glyphs, override))
+ });
+ }
+
+ // Rewrite the 'post' table if needed
+ if (requiredTables.indexOf('post') != -1) {
+ tables.push({
+ tag: 'post',
+ data: stringToArray(createPostTable(properties))
+ });
+ }
+
+ // Rewrite the 'name' table if needed
+ if (requiredTables.indexOf('name') != -1) {
+ tables.push({
+ tag: 'name',
+ data: stringToArray(createNameTable(this.name))
+ });
+ }
+
+ // Tables needs to be written by ascendant alphabetic order
+ tables.sort(function tables_sort(a, b) {
+ return (a.tag > b.tag) - (a.tag < b.tag);
+ });
+
+ // rewrite the tables but tweak offsets
+ for (var i = 0, ii = tables.length; i < ii; i++) {
+ var table = tables[i];
+ var data = [];
+
+ var tableData = table.data;
+ for (var j = 0, jj = tableData.length; j < jj; j++)
+ data.push(tableData[j]);
+ createTableEntry(ttf, table.tag, data);
+ }
+
+ // Add the table datas
+ for (var i = 0, ii = tables.length; i < ii; i++) {
+ var table = tables[i];
+ var tableData = table.data;
+ ttf.file += arrayToString(tableData);
+
+ // 4-byte aligned data
+ while (ttf.file.length & 3)
+ ttf.file += String.fromCharCode(0);
+ }
+
+ return stringToArray(ttf.file);
+ },
+
+ convert: function Font_convert(fontName, font, properties) {
+ function isFixedPitch(glyphs) {
+ for (var i = 0, ii = glyphs.length - 1; i < ii; i++) {
+ if (glyphs[i] != glyphs[i + 1])
+ return false;
+ }
+ return true;
+ }
+
+ // The offsets object holds at the same time a representation of where
+ // to write the table entry information about a table and another offset
+ // representing the offset where to draw the actual data of a particular
+ // table
+ var kRequiredTablesCount = 9;
+
+ var otf = {
+ file: '',
+ virtualOffset: 9 * (4 * 4)
+ };
+
+ createOpenTypeHeader('\x4F\x54\x54\x4F', otf, 9);
+
+ var charstrings = font.charstrings;
+ properties.fixedPitch = isFixedPitch(charstrings);
+
+ var glyphNameMap = {};
+ for (var i = 0; i < charstrings.length; ++i) {
+ var charstring = charstrings[i];
+ glyphNameMap[charstring.glyph] = charstring.unicode;
+ }
+ this.glyphNameMap = glyphNameMap;
+
+ if (!properties.hasEncoding && (properties.subtype == 'Type1C' ||
+ properties.subtype == 'CIDFontType0C')) {
+ var encoding = [];
+ for (var i = 0; i < charstrings.length; ++i) {
+ var charstring = charstrings[i];
+ encoding[charstring.code] = charstring.glyph;
+ }
+ properties.baseEncoding = encoding;
+ }
+ if (properties.subtype == 'CIDFontType0C') {
+ var toFontChar = [];
+ for (var i = 0; i < charstrings.length; ++i) {
+ var charstring = charstrings[i];
+ toFontChar[charstring.code] = charstring.unicode;
+ }
+ this.toFontChar = toFontChar;
+ }
+
+ var fields = {
+ // PostScript Font Program
+ 'CFF ': font.data,
+
+ // OS/2 and Windows Specific metrics
+ 'OS/2': stringToArray(createOS2Table(properties, charstrings)),
+
+ // Character to glyphs mapping
+ 'cmap': createCMapTable(charstrings.slice(),
+ ('glyphIds' in font) ? font.glyphIds : null),
+
+ // Font header
+ 'head': (function fontFieldsHead() {
+ return stringToArray(
+ '\x00\x01\x00\x00' + // Version number
+ '\x00\x00\x10\x00' + // fontRevision
+ '\x00\x00\x00\x00' + // checksumAdjustement
+ '\x5F\x0F\x3C\xF5' + // magicNumber
+ '\x00\x00' + // Flags
+ '\x03\xE8' + // unitsPerEM (defaulting to 1000)
+ '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date
+ '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date
+ '\x00\x00' + // xMin
+ safeString16(properties.descent) + // yMin
+ '\x0F\xFF' + // xMax
+ safeString16(properties.ascent) + // yMax
+ string16(properties.italicAngle ? 2 : 0) + // macStyle
+ '\x00\x11' + // lowestRecPPEM
+ '\x00\x00' + // fontDirectionHint
+ '\x00\x00' + // indexToLocFormat
+ '\x00\x00'); // glyphDataFormat
+ })(),
+
+ // Horizontal header
+ 'hhea': (function fontFieldsHhea() {
+ return stringToArray(
+ '\x00\x01\x00\x00' + // Version number
+ safeString16(properties.ascent) + // Typographic Ascent
+ safeString16(properties.descent) + // Typographic Descent
+ '\x00\x00' + // Line Gap
+ '\xFF\xFF' + // advanceWidthMax
+ '\x00\x00' + // minLeftSidebearing
+ '\x00\x00' + // minRightSidebearing
+ '\x00\x00' + // xMaxExtent
+ safeString16(properties.capHeight) + // caretSlopeRise
+ safeString16(Math.tan(properties.italicAngle) *
+ properties.xHeight) + // caretSlopeRun
+ '\x00\x00' + // caretOffset
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // metricDataFormat
+ string16(charstrings.length + 1)); // Number of HMetrics
+ })(),
+
+ // Horizontal metrics
+ 'hmtx': (function fontFieldsHmtx() {
+ var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
+ for (var i = 0, ii = charstrings.length; i < ii; i++) {
+ var charstring = charstrings[i];
+ var width = 'width' in charstring ? charstring.width : 0;
+ hmtx += string16(width) + string16(0);
+ }
+ return stringToArray(hmtx);
+ })(),
+
+ // Maximum profile
+ 'maxp': (function fontFieldsMaxp() {
+ return stringToArray(
+ '\x00\x00\x50\x00' + // Version number
+ string16(charstrings.length + 1)); // Num of glyphs
+ })(),
+
+ // Naming tables
+ 'name': stringToArray(createNameTable(fontName)),
+
+ // PostScript informations
+ 'post': stringToArray(createPostTable(properties))
+ };
+
+ for (var field in fields)
+ createTableEntry(otf, field, fields[field]);
+
+ for (var field in fields) {
+ var table = fields[field];
+ otf.file += arrayToString(table);
+ }
+
+ return stringToArray(otf.file);
+ },
+
+ buildToFontChar: function Font_buildToFontChar(toUnicode) {
+ var result = [];
+ var unusedUnicode = kCmapGlyphOffset;
+ for (var i = 0, ii = toUnicode.length; i < ii; i++) {
+ var unicode = toUnicode[i];
+ var fontCharCode = typeof unicode === 'object' ? unusedUnicode++ :
+ unicode;
+ if (typeof unicode !== 'undefined')
+ result[i] = fontCharCode;
+ }
+ return result;
+ },
+
+ rebuildToUnicode: function Font_rebuildToUnicode(properties) {
+ var firstChar = properties.firstChar, lastChar = properties.lastChar;
+ var map = [];
+ if (properties.composite) {
+ var isIdentityMap = this.cidToUnicode.length == 0;
+ for (var i = firstChar, ii = lastChar; i <= ii; i++) {
+ // TODO missing map the character according font's CMap
+ var cid = i;
+ map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];
+ }
+ } else {
+ for (var i = firstChar, ii = lastChar; i <= ii; i++) {
+ var glyph = properties.differences[i];
+ if (!glyph)
+ glyph = properties.baseEncoding[i];
+ if (!!glyph && (glyph in GlyphsUnicode))
+ map[i] = GlyphsUnicode[glyph];
+ }
+ }
+ this.toUnicode = map;
+ },
+
+ loadCidToUnicode: function Font_loadCidToUnicode(properties) {
+ if (!properties.cidSystemInfo)
+ return;
+
+ var cidToUnicodeMap = [], unicodeToCIDMap = [];
+ this.cidToUnicode = cidToUnicodeMap;
+ this.unicodeToCID = unicodeToCIDMap;
+
+ var cidSystemInfo = properties.cidSystemInfo;
+ var cidToUnicode;
+ if (cidSystemInfo) {
+ cidToUnicode = CIDToUnicodeMaps[
+ cidSystemInfo.registry + '-' + cidSystemInfo.ordering];
+ }
+
+ if (!cidToUnicode)
+ return; // identity encoding
+
+ var cid = 1, i, j, k, ii;
+ for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
+ var unicode = cidToUnicode[i];
+ if (isArray(unicode)) {
+ var length = unicode.length;
+ for (j = 0; j < length; j++) {
+ cidToUnicodeMap[cid] = unicode[j];
+ unicodeToCIDMap[unicode[j]] = cid;
+ }
+ cid++;
+ } else if (typeof unicode === 'object') {
+ var fillLength = unicode.f;
+ if (fillLength) {
+ k = unicode.c;
+ for (j = 0; j < fillLength; ++j) {
+ cidToUnicodeMap[cid] = k;
+ unicodeToCIDMap[k] = cid;
+ cid++;
+ k++;
+ }
+ } else
+ cid += unicode.s;
+ } else if (unicode) {
+ cidToUnicodeMap[cid] = unicode;
+ unicodeToCIDMap[unicode] = cid;
+ cid++;
+ } else
+ cid++;
+ }
+ },
+
+ bindDOM: function Font_bindDOM(data) {
+ var fontName = this.loadedName;
+
+ // Add the font-face rule to the document
+ var url = ('url(data:' + this.mimetype + ';base64,' +
+ window.btoa(data) + ');');
+ var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
+
+ var styleElement = document.createElement('style');
+ document.documentElement.getElementsByTagName('head')[0].appendChild(
+ styleElement);
+
+ var styleSheet = styleElement.sheet;
+ styleSheet.insertRule(rule, styleSheet.cssRules.length);
+
+ if (PDFJS.pdfBug && FontInspector.enabled)
+ FontInspector.fontAdded(this, url);
+
+ return rule;
+ },
+
+ get spaceWidth() {
+ // trying to estimate space character width
+ var possibleSpaceReplacements = ['space', 'minus', 'one', 'i'];
+ var width;
+ for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
+ var glyphName = possibleSpaceReplacements[i];
+ // if possible, getting width by glyph name
+ if (glyphName in this.widths) {
+ width = this.widths[glyphName];
+ break;
+ }
+ var glyphUnicode = GlyphsUnicode[glyphName];
+ // finding the charcode via unicodeToCID map
+ var charcode = 0;
+ if (this.composite)
+ charcode = this.unicodeToCID[glyphUnicode];
+ // ... via toUnicode map
+ if (!charcode && 'toUnicode' in this)
+ charcode = this.toUnicode.indexOf(glyphUnicode);
+ // setting it to unicode if negative or undefined
+ if (!(charcode > 0))
+ charcode = glyphUnicode;
+ // trying to get width via charcode
+ width = this.widths[charcode];
+ if (width)
+ break; // the non-zero width found
+ }
+ width = (width || this.defaultWidth) * this.widthMultiplier;
+ return shadow(this, 'spaceWidth', width);
+ },
+
+ charToGlyph: function Font_charToGlyph(charcode) {
+ var fontCharCode, width, operatorList, disabled;
+
+ var width = this.widths[charcode];
+
+ switch (this.type) {
+ case 'CIDFontType0':
+ if (this.noUnicodeAdaptation) {
+ width = this.widths[this.unicodeToCID[charcode] || charcode];
+ fontCharCode = mapPrivateUseChars(charcode);
+ break;
+ }
+ fontCharCode = this.toFontChar[charcode] || charcode;
+ break;
+ case 'CIDFontType2':
+ if (this.noUnicodeAdaptation) {
+ width = this.widths[this.unicodeToCID[charcode] || charcode];
+ fontCharCode = mapPrivateUseChars(charcode);
+ break;
+ }
+ fontCharCode = this.toFontChar[charcode] || charcode;
+ break;
+ case 'Type1':
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ if (!isNum(width))
+ width = this.widths[glyphName];
+ if (this.noUnicodeAdaptation) {
+ fontCharCode = mapPrivateUseChars(GlyphsUnicode[glyphName] ||
+ charcode);
+ break;
+ }
+ fontCharCode = this.glyphNameMap[glyphName] ||
+ GlyphsUnicode[glyphName] || charcode;
+ break;
+ case 'Type3':
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ operatorList = this.charProcOperatorList[glyphName];
+ fontCharCode = charcode;
+ break;
+ case 'TrueType':
+ if (this.useToFontChar) {
+ fontCharCode = this.toFontChar[charcode] || charcode;
+ break;
+ }
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ if (!glyphName)
+ glyphName = Encodings.StandardEncoding[charcode];
+ if (!isNum(width))
+ width = this.widths[glyphName];
+ if (this.noUnicodeAdaptation) {
+ fontCharCode = GlyphsUnicode[glyphName] || charcode;
+ break;
+ }
+ if (!this.hasEncoding || this.isSymbolicFont) {
+ fontCharCode = this.useToFontChar ? this.toFontChar[charcode] :
+ charcode;
+ break;
+ }
+
+ // MacRoman encoding address by re-encoding the cmap table
+ fontCharCode = glyphName in this.glyphNameMap ?
+ this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
+ break;
+ default:
+ warn('Unsupported font type: ' + this.type);
+ break;
+ }
+
+ var unicodeChars = !('toUnicode' in this) ? charcode :
+ this.toUnicode[charcode] || charcode;
+ if (typeof unicodeChars === 'number')
+ unicodeChars = String.fromCharCode(unicodeChars);
+
+ width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier;
+ disabled = this.unicodeIsEnabled ?
+ !this.unicodeIsEnabled[fontCharCode] : false;
+
+ return {
+ fontChar: String.fromCharCode(fontCharCode),
+ unicode: unicodeChars,
+ width: width,
+ disabled: disabled,
+ operatorList: operatorList
+ };
+ },
+
+ charsToGlyphs: function Font_charsToGlyphs(chars) {
+ var charsCache = this.charsCache;
+ var glyphs;
+
+ // if we translated this string before, just grab it from the cache
+ if (charsCache) {
+ glyphs = charsCache[chars];
+ if (glyphs)
+ return glyphs;
+ }
+
+ // lazily create the translation cache
+ if (!charsCache)
+ charsCache = this.charsCache = Object.create(null);
+
+ glyphs = [];
+
+ if (this.composite) {
+ // composite fonts have multi-byte strings convert the string from
+ // single-byte to multi-byte
+ // XXX assuming CIDFonts are two-byte - later need to extract the
+ // correct byte encoding according to the PDF spec
+ var length = chars.length - 1; // looping over two bytes at a time so
+ // loop should never end on the last byte
+ for (var i = 0; i < length; i++) {
+ var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
+ var glyph = this.charToGlyph(charcode);
+ glyphs.push(glyph);
+ // placing null after each word break charcode (ASCII SPACE)
+ if (charcode == 0x20)
+ glyphs.push(null);
+ }
+ }
+ else {
+ for (var i = 0, ii = chars.length; i < ii; ++i) {
+ var charcode = chars.charCodeAt(i);
+ var glyph = this.charToGlyph(charcode);
+ glyphs.push(glyph);
+ if (charcode == 0x20)
+ glyphs.push(null);
+ }
+ }
+
+ // Enter the translated string into the cache
+ return (charsCache[chars] = glyphs);
+ }
+ };
+
+ return Font;
+})();
+
+/*
+ * Type1Parser encapsulate the needed code for parsing a Type1 font
+ * program. Some of its logic depends on the Type2 charstrings
+ * structure.
+ */
+var Type1Parser = function type1Parser() {
+ /*
+ * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
+ * of Plaintext Bytes. The function took a key as a parameter which can be
+ * for decrypting the eexec block of for decoding charStrings.
+ */
+ var kEexecEncryptionKey = 55665;
+ var kCharStringsEncryptionKey = 4330;
+
+ function decrypt(stream, key, discardNumber) {
+ var r = key, c1 = 52845, c2 = 22719;
+ var decryptedString = [];
+
+ var value = '';
+ var count = stream.length;
+ for (var i = 0; i < count; i++) {
+ value = stream[i];
+ decryptedString[i] = value ^ (r >> 8);
+ r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
+ }
+ return decryptedString.slice(discardNumber);
+ }
+
+ /*
+ * CharStrings are encoded following the the CharString Encoding sequence
+ * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
+ * The value in a byte indicates a command, a number, or subsequent bytes
+ * that are to be interpreted in a special way.
+ *
+ * CharString Number Encoding:
+ * A CharString byte containing the values from 32 through 255 inclusive
+ * indicate an integer. These values are decoded in four ranges.
+ *
+ * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
+ * indicate the integer v - 139. Thus, the integer values from -107 through
+ * 107 inclusive may be encoded in single byte.
+ *
+ * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * [(v - 247) x 256] + w + 108
+ *
+ * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * -[(v - 251) * 256] - w - 108
+ *
+ * 4. A CharString containing the value 255 indicates that the next 4 bytes
+ * are a two complement signed integer. The first of these bytes contains the
+ * highest order bits, the second byte contains the next higher order bits
+ * and the fourth byte contain the lowest order bits.
+ *
+ *
+ * CharString Command Encoding:
+ * CharStrings commands are encoded in 1 or 2 bytes.
+ *
+ * Single byte commands are encoded in 1 byte that contains a value between
+ * 0 and 31 inclusive.
+ * If a command byte contains the value 12, then the value in the next byte
+ * indicates a command. This "escape" mechanism allows many extra commands
+ * to be encoded and this encoding technique helps to minimize the length of
+ * the charStrings.
+ */
+ var charStringDictionary = {
+ '1': 'hstem',
+ '3': 'vstem',
+ '4': 'vmoveto',
+ '5': 'rlineto',
+ '6': 'hlineto',
+ '7': 'vlineto',
+ '8': 'rrcurveto',
+
+ // closepath is a Type1 command that do not take argument and is useless
+ // in Type2 and it can simply be ignored.
+ '9': null, // closepath
+
+ '10': 'callsubr',
+
+ // return is normally used inside sub-routines to tells to the execution
+ // flow that it can be back to normal.
+ // During the translation process Type1 charstrings will be flattened and
+ // sub-routines will be embedded directly into the charstring directly, so
+ // this can be ignored safely.
+ '11': 'return',
+
+ '12': {
+ // dotsection is a Type1 command to specify some hinting feature for dots
+ // that do not take a parameter and it can safely be ignored for Type2.
+ '0': null, // dotsection
+
+ // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple
+ // parameters, so instead of returning [vh]stem3 take a shortcut and
+ // return [vhstem] instead.
+ '1': 'vstem',
+ '2': 'hstem',
+
+ // Type1 only command with command not (yet) built-in ,throw an error
+ '6': -1, // seac
+ '7': -1, // sbw
+
+ '11': 'sub',
+ '12': 'div',
+
+ // callothersubr is a mechanism to make calls on the postscript
+ // interpreter, this is not supported by Type2 charstring but hopefully
+ // most of the default commands can be ignored safely.
+ '16': 'callothersubr',
+
+ '17': 'pop',
+
+ // setcurrentpoint sets the current point to x, y without performing a
+ // moveto (this is a one shot positionning command). This is used only
+ // with the return of an OtherSubrs call.
+ // TODO Implement the OtherSubrs charstring embedding and replace this
+ // call by a no-op, like 2 'pop' commands for example.
+ '33': null // setcurrentpoint
+ },
+ '13': 'hsbw',
+ '14': 'endchar',
+ '21': 'rmoveto',
+ '22': 'hmoveto',
+ '30': 'vhcurveto',
+ '31': 'hvcurveto'
+ };
+
+ var kEscapeCommand = 12;
+
+ function decodeCharString(array) {
+ var charstring = [];
+ var lsb = 0;
+ var width = 0;
+ var flexState = 0;
+
+ var value = '';
+ var count = array.length;
+ for (var i = 0; i < count; i++) {
+ value = array[i];
+
+ if (value < 32) {
+ var command = null;
+ if (value == kEscapeCommand) {
+ var escape = array[++i];
+
+ // TODO Clean this code
+ if (escape == 16) {
+ var index = charstring.pop();
+ var argc = charstring.pop();
+ for (var j = 0; j < argc; j++)
+ charstring.push('drop');
+
+ // If the flex mechanism is not used in a font program, Adobe
+ // states that entries 0, 1 and 2 can simply be replaced by
+ // {}, which means that we can simply ignore them.
+ if (index < 3) {
+ continue;
+ }
+
+ // This is the same things about hint replacement, if it is not used
+ // entry 3 can be replaced by {3}
+ // TODO support hint replacment
+ if (index == 3) {
+ charstring.push(3);
+ i++;
+ continue;
+ }
+ } else if (escape == 17 || escape == 33) {
+ // pop or setcurrentpoint commands can be ignored
+ // since we are not doing callothersubr
+ continue;
+ } else if (!kHintingEnabled && (escape == 1 || escape == 2)) {
+ charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
+ continue;
+ }
+
+ command = charStringDictionary['12'][escape];
+ } else {
+ // TODO Clean this code
+ if (value == 13) { // hsbw
+ if (charstring.length == 2) {
+ lsb = charstring[0];
+ width = charstring[1];
+ charstring.splice(0, 1);
+ } else if (charstring.length == 4 && charstring[3] == 'div') {
+ lsb = charstring[0];
+ width = charstring[1] / charstring[2];
+ charstring.splice(0, 1);
+ } else if (charstring.length == 4 && charstring[2] == 'div') {
+ lsb = charstring[0] / charstring[1];
+ width = charstring[3];
+ charstring.splice(0, 3);
+ } else {
+ error('Unsupported hsbw format: ' + charstring);
+ }
+
+ charstring.push(lsb, 'hmoveto');
+ continue;
+ } else if (value == 10) { // callsubr
+ if (charstring[charstring.length - 1] < 3) { // subr #0..2
+ var subrNumber = charstring.pop();
+ switch (subrNumber) {
+ case 1:
+ flexState = 1; // prepare for flex coordinates
+ break;
+ case 2:
+ flexState = 2; // flex in progress
+ break;
+ case 0:
+ // type2 flex command does not need final coords
+ charstring.push('exch', 'drop', 'exch', 'drop');
+ charstring.push('flex');
+ flexState = 0;
+ break;
+ }
+ continue;
+ }
+ } else if (value == 21 && flexState > 0) {
+ if (flexState > 1)
+ continue; // ignoring rmoveto
+ value = 5; // first segment replacing with rlineto
+ } else if (!kHintingEnabled && (value == 1 || value == 3)) {
+ charstring.push('drop', 'drop');
+ continue;
+ }
+ command = charStringDictionary[value];
+ }
+
+ // Some charstring commands are meaningless in Type2 and will return
+ // a null, let's just ignored them
+ if (!command && i < count) {
+ continue;
+ } else if (!command) {
+ break;
+ } else if (command == -1) {
+ warn('Support for Type1 command ' + value +
+ ' (' + escape + ') is not implemented in charstring: ' +
+ charstring);
+ if (value == 12) {
+ // we know how to ignore only some the Type1 commands
+ switch (escape) {
+ case 7:
+ charstring.push('drop', 'drop', 'drop', 'drop');
+ continue;
+ case 8:
+ charstring.push('drop');
+ continue;
+ }
+ }
+ }
+
+ value = command;
+ } else if (value <= 246) {
+ value = value - 139;
+ } else if (value <= 250) {
+ value = ((value - 247) * 256) + array[++i] + 108;
+ } else if (value <= 254) {
+ value = -((value - 251) * 256) - array[++i] - 108;
+ } else {
+ value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
+ (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
+ }
+
+ charstring.push(value);
+ }
+
+ return { charstring: charstring, width: width, lsb: lsb };
+ }
+
+ /*
+ * Returns an object containing a Subrs array and a CharStrings
+ * array extracted from and eexec encrypted block of data
+ */
+ function readNumberArray(str, index) {
+ var start = index;
+ while (str[index++] != '[')
+ start++;
+ start++;
+
+ var count = 0;
+ while (str[index++] != ']')
+ count++;
+
+ str = str.substr(start, count);
+
+ str = str.trim();
+ // Remove adjacent spaces
+ str = str.replace(/\s+/g, ' ');
+
+ var array = str.split(' ');
+ for (var i = 0, ii = array.length; i < ii; i++)
+ array[i] = parseFloat(array[i] || 0);
+ return array;
+ }
+
+ function readNumber(str, index) {
+ while (str[index] == ' ')
+ index++;
+
+ var start = index;
+
+ var count = 0;
+ while (str[index++] != ' ')
+ count++;
+
+ return parseFloat(str.substr(start, count) || 0);
+ }
+
+ function isSeparator(c) {
+ return c == ' ' || c == '\n' || c == '\x0d';
+ }
+
+ this.extractFontProgram = function Type1Parser_extractFontProgram(stream) {
+ var eexec = decrypt(stream, kEexecEncryptionKey, 4);
+ var eexecStr = '';
+ for (var i = 0, ii = eexec.length; i < ii; i++)
+ eexecStr += String.fromCharCode(eexec[i]);
+
+ var glyphsSection = false, subrsSection = false;
+ var program = {
+ subrs: [],
+ charstrings: [],
+ properties: {
+ 'privateData': {
+ 'lenIV': 4
+ }
+ }
+ };
+
+ var glyph = '';
+ var token = '';
+ var length = 0;
+
+ var c = '';
+ var count = eexecStr.length;
+ for (var i = 0; i < count; i++) {
+ var getToken = function getToken() {
+ while (i < count && isSeparator(eexecStr[i]))
+ ++i;
+
+ var token = '';
+ while (i < count && !isSeparator(eexecStr[i]))
+ token += eexecStr[i++];
+
+ return token;
+ };
+ var c = eexecStr[i];
+
+ if ((glyphsSection || subrsSection) &&
+ (token == 'RD' || token == '-|')) {
+ i++;
+ var data = eexec.slice(i, i + length);
+ var lenIV = program.properties.privateData['lenIV'];
+ var encoded = decrypt(data, kCharStringsEncryptionKey, lenIV);
+ var str = decodeCharString(encoded);
+
+ if (glyphsSection) {
+ program.charstrings.push({
+ glyph: glyph,
+ data: str.charstring,
+ lsb: str.lsb,
+ width: str.width
+ });
+ } else {
+ program.subrs.push(str.charstring);
+ }
+ i += length;
+ token = '';
+ } else if (isSeparator(c)) {
+ length = parseInt(token, 10);
+ token = '';
+ } else {
+ token += c;
+ if (!glyphsSection) {
+ switch (token) {
+ case '/CharString':
+ glyphsSection = true;
+ break;
+ case '/Subrs':
+ ++i;
+ var num = parseInt(getToken(), 10);
+ getToken(); // read in 'array'
+ for (var j = 0; j < num; ++j) {
+ var t = getToken(); // read in 'dup'
+ if (t == 'ND' || t == '|-' || t == 'noaccess')
+ break;
+ var index = parseInt(getToken(), 10);
+ if (index > j)
+ j = index;
+ var length = parseInt(getToken(), 10);
+ getToken(); // read in 'RD'
+ var data = eexec.slice(i + 1, i + 1 + length);
+ var lenIV = program.properties.privateData['lenIV'];
+ var encoded = decrypt(data, kCharStringsEncryptionKey, lenIV);
+ var str = decodeCharString(encoded);
+ i = i + 1 + length;
+ t = getToken(); // read in 'NP'
+ if (t == 'noaccess')
+ getToken(); // read in 'put'
+ program.subrs[index] = str.charstring;
+ }
+ break;
+ case '/BlueValues':
+ case '/OtherBlues':
+ case '/FamilyBlues':
+ case '/FamilyOtherBlues':
+ case '/StemSnapH':
+ case '/StemSnapV':
+ program.properties.privateData[token.substring(1)] =
+ readNumberArray(eexecStr, i + 1);
+ break;
+ case '/StdHW':
+ case '/StdVW':
+ program.properties.privateData[token.substring(1)] =
+ readNumberArray(eexecStr, i + 2)[0];
+ break;
+ case '/BlueShift':
+ case '/lenIV':
+ case '/BlueFuzz':
+ case '/BlueScale':
+ case '/LanguageGroup':
+ case '/ExpansionFactor':
+ program.properties.privateData[token.substring(1)] =
+ readNumber(eexecStr, i + 1);
+ break;
+ }
+ } else if (c == '/') {
+ token = glyph = '';
+ while ((c = eexecStr[++i]) != ' ')
+ glyph += c;
+ }
+ }
+ }
+
+ return program;
+ };
+
+ this.extractFontHeader = function Type1Parser_extractFontHeader(stream,
+ properties) {
+ var headerString = '';
+ for (var i = 0, ii = stream.length; i < ii; i++)
+ headerString += String.fromCharCode(stream[i]);
+
+ var token = '';
+ var count = headerString.length;
+ for (var i = 0; i < count; i++) {
+ var getToken = function getToken() {
+ var character = headerString[i];
+ while (i < count && (isSeparator(character) || character == '/'))
+ character = headerString[++i];
+
+ var token = '';
+ while (i < count && !(isSeparator(character) || character == '/')) {
+ token += character;
+ character = headerString[++i];
+ }
+
+ return token;
+ };
+
+ var c = headerString[i];
+ if (isSeparator(c)) {
+ switch (token) {
+ case '/FontMatrix':
+ var matrix = readNumberArray(headerString, i + 1);
+
+ // The FontMatrix is in unitPerEm, so make it pixels
+ for (var j = 0, jj = matrix.length; j < jj; j++)
+ matrix[j] *= 1000;
+
+ // Make the angle into the right direction
+ matrix[2] *= -1;
+
+ properties.fontMatrix = matrix;
+ break;
+ case '/Encoding':
+ var encodingArg = getToken();
+ var encoding;
+ if (!/^\d+$/.test(encodingArg)) {
+ // encoding name is specified
+ encoding = Encodings[encodingArg];
+ } else {
+ encoding = [];
+ var size = parseInt(encodingArg, 10);
+ getToken(); // read in 'array'
+
+ for (var j = 0; j < size; j++) {
+ var token = getToken();
+ if (token == 'dup') {
+ var index = parseInt(getToken(), 10);
+ var glyph = getToken();
+ encoding[index] = glyph;
+ getToken(); // read the in 'put'
+ }
+ }
+ }
+ if (!properties.hasEncoding && encoding) {
+ properties.baseEncoding = encoding;
+ break;
+ }
+ break;
+ }
+ token = '';
+ } else {
+ token += c;
+ }
+ }
+ };
+};
+
+/**
+ * The CFF class takes a Type1 file and wrap it into a
+ * 'Compact Font Format' which itself embed Type2 charstrings.
+ */
+var CFFStandardStrings = [
+ '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
+ 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus',
+ 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
+ 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum',
+ 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+ 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent',
+ 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
+ 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
+ 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl',
+ 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase',
+ 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown',
+ 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
+ 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash',
+ 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
+ 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
+ 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
+ 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
+ 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
+ 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
+ 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
+ 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
+ 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
+ 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
+ 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde',
+ 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute',
+ 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex',
+ 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex',
+ 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron', 'exclamsmall',
+ 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall',
+ 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff',
+ 'onedotenleader', 'zerooldstyle', 'oneoldstyle', 'twooldstyle',
+ 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
+ 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior',
+ 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior',
+ 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior',
+ 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
+ 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior',
+ 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall',
+ 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall',
+ 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
+ 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
+ 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah',
+ 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall',
+ 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall',
+ 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior',
+ 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'questiondownsmall', 'oneeighth',
+ 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
+ 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior',
+ 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
+ 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior',
+ 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior',
+ 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior',
+ 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall',
+ 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall',
+ 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall',
+ 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall',
+ 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall',
+ 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall',
+ 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall',
+ 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003',
+ 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'
+];
+
+var type1Parser = new Type1Parser();
+
+// Type1Font is also a CIDFontType0.
+var Type1Font = function Type1Font(name, file, properties) {
+ // Get the data block containing glyphs and subrs informations
+ var headerBlock = file.getBytes(properties.length1);
+ type1Parser.extractFontHeader(headerBlock, properties);
+
+ // Decrypt the data blocks and retrieve it's content
+ var eexecBlock = file.getBytes(properties.length2);
+ var data = type1Parser.extractFontProgram(eexecBlock);
+ for (var info in data.properties)
+ properties[info] = data.properties[info];
+
+ var charstrings = this.getOrderedCharStrings(data.charstrings, properties);
+ var type2Charstrings = this.getType2Charstrings(charstrings);
+ var subrs = this.getType2Subrs(data.subrs);
+
+ this.charstrings = charstrings;
+ this.data = this.wrap(name, type2Charstrings, this.charstrings,
+ subrs, properties);
+};
+
+Type1Font.prototype = {
+ createCFFIndexHeader: function Type1Font_createCFFIndexHeader(objects,
+ isByte) {
+ // First 2 bytes contains the number of objects contained into this index
+ var count = objects.length;
+
+ // If there is no object, just create an array saying that with another
+ // offset byte.
+ if (count == 0)
+ return '\x00\x00\x00';
+
+ var data = String.fromCharCode((count >> 8) & 0xFF, count & 0xff);
+
+ // Next byte contains the offset size use to reference object in the file
+ // Actually we're using 0x04 to be sure to be able to store everything
+ // without thinking of it while coding.
+ data += '\x04';
+
+ // Add another offset after this one because we need a new offset
+ var relativeOffset = 1;
+ for (var i = 0; i < count + 1; i++) {
+ data += String.fromCharCode((relativeOffset >>> 24) & 0xFF,
+ (relativeOffset >> 16) & 0xFF,
+ (relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+
+ if (objects[i])
+ relativeOffset += objects[i].length;
+ }
+
+ for (var i = 0; i < count; i++) {
+ for (var j = 0, jj = objects[i].length; j < jj; j++)
+ data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) :
+ objects[i][j];
+ }
+ return data;
+ },
+
+ encodeNumber: function Type1Font_encodeNumber(value) {
+ // some of the fonts has ouf-of-range values
+ // they are just arithmetic overflows
+ // make sanitizer happy
+ value |= 0;
+ if (value >= -32768 && value <= 32767) {
+ return '\x1c' +
+ String.fromCharCode((value >> 8) & 0xFF) +
+ String.fromCharCode(value & 0xFF);
+ } else {
+ return '\x1d' +
+ String.fromCharCode((value >> 24) & 0xFF) +
+ String.fromCharCode((value >> 16) & 0xFF) +
+ String.fromCharCode((value >> 8) & 0xFF) +
+ String.fromCharCode(value & 0xFF);
+ }
+ },
+
+ getOrderedCharStrings: function Type1Font_getOrderedCharStrings(glyphs,
+ properties) {
+ var charstrings = [];
+ var i, length, glyphName;
+ var unusedUnicode = kCmapGlyphOffset;
+ for (i = 0, length = glyphs.length; i < length; i++) {
+ var item = glyphs[i];
+ var glyphName = item.glyph;
+ var unicode = glyphName in GlyphsUnicode ?
+ GlyphsUnicode[glyphName] : unusedUnicode++;
+ charstrings.push({
+ glyph: glyphName,
+ unicode: unicode,
+ gid: i,
+ charstring: item.data,
+ width: item.width,
+ lsb: item.lsb
+ });
+ }
+
+ charstrings.sort(function charstrings_sort(a, b) {
+ return a.unicode - b.unicode;
+ });
+ return charstrings;
+ },
+
+ getType2Charstrings: function Type1Font_getType2Charstrings(
+ type1Charstrings) {
+ var type2Charstrings = [];
+ var count = type1Charstrings.length;
+ for (var i = 0; i < count; i++) {
+ var charstring = type1Charstrings[i].charstring;
+ type2Charstrings.push(this.flattenCharstring(charstring.slice(),
+ this.commandsMap));
+ }
+ return type2Charstrings;
+ },
+
+ getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
+ var bias = 0;
+ var count = type1Subrs.length;
+ if (count < 1240)
+ bias = 107;
+ else if (count < 33900)
+ bias = 1131;
+ else
+ bias = 32768;
+
+ // Add a bunch of empty subrs to deal with the Type2 bias
+ var type2Subrs = [];
+ for (var i = 0; i < bias; i++)
+ type2Subrs.push([0x0B]);
+
+ for (var i = 0; i < count; i++) {
+ var subr = type1Subrs[i];
+ if (!subr)
+ subr = [0x0B];
+
+ type2Subrs.push(this.flattenCharstring(subr, this.commandsMap));
+ }
+
+ return type2Subrs;
+ },
+
+ /*
+ * Flatten the commands by interpreting the postscript code and replacing
+ * every 'callsubr', 'callothersubr' by the real commands.
+ */
+ commandsMap: {
+ 'hstem': 1,
+ 'vstem': 3,
+ 'vmoveto': 4,
+ 'rlineto': 5,
+ 'hlineto': 6,
+ 'vlineto': 7,
+ 'rrcurveto': 8,
+ 'callsubr': 10,
+ 'return': 11,
+ 'sub': [12, 11],
+ 'div': [12, 12],
+ 'exch': [12, 28],
+ 'flex': [12, 35],
+ 'drop' : [12, 18],
+ 'endchar': 14,
+ 'rmoveto': 21,
+ 'hmoveto': 22,
+ 'vhcurveto': 30,
+ 'hvcurveto': 31
+ },
+
+ flattenCharstring: function Type1Font_flattenCharstring(charstring, map) {
+ // charstring changes size - can't cache .length in loop
+ for (var i = 0; i < charstring.length; i++) {
+ var command = charstring[i];
+ if (command.charAt) {
+ var cmd = map[command];
+ assert(cmd, 'Unknow command: ' + command);
+
+ if (isArray(cmd))
+ charstring.splice(i++, 1, cmd[0], cmd[1]);
+ else
+ charstring[i] = cmd;
+ } else {
+ // Type1 charstring use a division for number above 32000
+ if (command > 32000) {
+ var divisor = charstring[i + 1];
+ command /= divisor;
+ charstring.splice(i, 3, 28, command >> 8, command & 0xff);
+ } else {
+ charstring.splice(i, 1, 28, command >> 8, command & 0xff);
+ }
+ i += 2;
+ }
+ }
+ return charstring;
+ },
+
+ wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
+ var fields = {
+ // major version, minor version, header size, offset size
+ 'header': '\x01\x00\x04\x04',
+
+ 'names': this.createCFFIndexHeader([name]),
+
+ 'topDict': (function topDict(self) {
+ return function cffWrapTopDict() {
+ var header = '\x00\x01\x01\x01';
+ var dict =
+ '\xf8\x1b\x00' + // version
+ '\xf8\x1c\x01' + // Notice
+ '\xf8\x1d\x02' + // FullName
+ '\xf8\x1e\x03' + // FamilyName
+ '\xf8\x1f\x04' + // Weight
+ '\x1c\x00\x00\x10'; // Encoding
+
+ var boundingBox = properties.bbox;
+ for (var i = 0, ii = boundingBox.length; i < ii; i++)
+ dict += self.encodeNumber(boundingBox[i]);
+ dict += '\x05'; // FontBBox;
+
+ var offset = fields.header.length +
+ fields.names.length +
+ (header.length + 1) +
+ (dict.length + (4 + 4)) +
+ fields.strings.length +
+ fields.globalSubrs.length;
+
+ // If the offset if over 32767, encodeNumber is going to return
+ // 5 bytes to encode the position instead of 3.
+ if ((offset + fields.charstrings.length) > 32767) {
+ offset += 9;
+ } else {
+ offset += 7;
+ }
+
+ dict += self.encodeNumber(offset) + '\x0f'; // Charset
+
+ offset = offset + (glyphs.length * 2) + 1;
+ dict += self.encodeNumber(offset) + '\x11'; // Charstrings
+
+ offset = offset + fields.charstrings.length;
+ dict += self.encodeNumber(fields.privateData.length);
+ dict += self.encodeNumber(offset) + '\x12'; // Private
+
+ return header + String.fromCharCode(dict.length + 1) + dict;
+ };
+ })(this),
+
+ 'strings': (function strings(self) {
+ var strings = [
+ 'Version 0.11', // Version
+ 'See original notice', // Notice
+ name, // FullName
+ name, // FamilyName
+ 'Medium' // Weight
+ ];
+ return self.createCFFIndexHeader(strings);
+ })(this),
+
+ 'globalSubrs': this.createCFFIndexHeader([]),
+
+ 'charset': (function charset(self) {
+ var charsetString = '\x00'; // Encoding
+
+ var count = glyphs.length;
+ for (var i = 0; i < count; i++) {
+ var index = CFFStandardStrings.indexOf(charstrings[i].glyph);
+ // Some characters like asterikmath && circlecopyrt are
+ // missing from the original strings, for the moment let's
+ // map them to .notdef and see later if it cause any
+ // problems
+ if (index == -1)
+ index = 0;
+
+ charsetString += String.fromCharCode(index >> 8, index & 0xff);
+ }
+ return charsetString;
+ })(this),
+
+ 'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs),
+ true),
+
+ 'privateData': (function cffWrapPrivate(self) {
+ var data =
+ '\x8b\x14' + // defaultWidth
+ '\x8b\x15'; // nominalWidth
+ var fieldMap = {
+ BlueValues: '\x06',
+ OtherBlues: '\x07',
+ FamilyBlues: '\x08',
+ FamilyOtherBlues: '\x09',
+ StemSnapH: '\x0c\x0c',
+ StemSnapV: '\x0c\x0d',
+ BlueShift: '\x0c\x0a',
+ BlueFuzz: '\x0c\x0b',
+ BlueScale: '\x0c\x09',
+ LanguageGroup: '\x0c\x11',
+ ExpansionFactor: '\x0c\x18'
+ };
+ for (var field in fieldMap) {
+ if (!properties.privateData.hasOwnProperty(field))
+ continue;
+ var value = properties.privateData[field];
+
+ if (isArray(value)) {
+ data += self.encodeNumber(value[0]);
+ for (var i = 1, ii = value.length; i < ii; i++)
+ data += self.encodeNumber(value[i] - value[i - 1]);
+ } else {
+ data += self.encodeNumber(value);
+ }
+ data += fieldMap[field];
+ }
+
+ data += self.encodeNumber(data.length + 4) + '\x13'; // Subrs offset
+
+ return data;
+ })(this),
+
+ 'localSubrs': this.createCFFIndexHeader(subrs, true)
+ };
+ fields.topDict = fields.topDict();
+
+
+ var cff = [];
+ for (var index in fields) {
+ var field = fields[index];
+ for (var i = 0, ii = field.length; i < ii; i++)
+ cff.push(field.charCodeAt(i));
+ }
+
+ return cff;
+ }
+};
+
+var CFFFont = (function CFFFontClosure() {
+ function CFFFont(file, properties) {
+ this.properties = properties;
+
+ var parser = new CFFParser(file, properties);
+ var cff = parser.parse();
+ var compiler = new CFFCompiler(cff);
+ this.readExtra(cff);
+ try {
+ this.data = compiler.compile();
+ } catch (e) {
+ warn('Failed to compile font ' + properties.loadedName);
+ // There may have just been an issue with the compiler, set the data
+ // anyway and hope the font loaded.
+ this.data = file;
+ }
+ }
+
+ CFFFont.prototype = {
+ readExtra: function CFFFont_readExtra(cff) {
+ // charstrings contains info about glyphs (one element per glyph
+ // containing mappings for {unicode, width})
+ var charset = cff.charset.charset;
+ var encoding = cff.encoding ? cff.encoding.encoding : null;
+ var charstrings = this.getCharStrings(charset, encoding);
+
+ // create the mapping between charstring and glyph id
+ var glyphIds = [];
+ for (var i = 0, ii = charstrings.length; i < ii; i++)
+ glyphIds.push(charstrings[i].gid);
+
+ this.charstrings = charstrings;
+ this.glyphIds = glyphIds;
+ },
+ getCharStrings: function CFFFont_getCharStrings(charsets, encoding) {
+ var charstrings = [];
+ var unicodeUsed = [];
+ var unassignedUnicodeItems = [];
+ var inverseEncoding = [];
+ // CID fonts don't have an encoding.
+ if (encoding !== null)
+ for (var charcode in encoding)
+ inverseEncoding[encoding[charcode]] = charcode | 0;
+ else
+ inverseEncoding = charsets;
+ for (var i = 0, ii = charsets.length; i < ii; i++) {
+ var glyph = charsets[i];
+ if (glyph == '.notdef')
+ continue;
+
+ var code = inverseEncoding[i];
+ if (!code || isSpecialUnicode(code)) {
+ unassignedUnicodeItems.push(i);
+ continue;
+ }
+ charstrings.push({
+ unicode: code,
+ code: code,
+ gid: i,
+ glyph: glyph
+ });
+ unicodeUsed[code] = true;
+ }
+
+ var nextUnusedUnicode = kCmapGlyphOffset;
+ for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) {
+ var i = unassignedUnicodeItems[j];
+ // giving unicode value anyway
+ while (nextUnusedUnicode in unicodeUsed)
+ nextUnusedUnicode++;
+ var unicode = nextUnusedUnicode++;
+ charstrings.push({
+ unicode: unicode,
+ code: inverseEncoding[i] || 0,
+ gid: i,
+ glyph: charsets[i]
+ });
+ }
+
+ // sort the array by the unicode value (again)
+ charstrings.sort(function getCharStringsSort(a, b) {
+ return a.unicode - b.unicode;
+ });
+ return charstrings;
+ }
+ };
+
+ return CFFFont;
+})();
+
+var CFFParser = (function CFFParserClosure() {
+ function CFFParser(file, properties) {
+ this.bytes = file.getBytes();
+ this.properties = properties;
+ }
+ CFFParser.prototype = {
+ parse: function CFFParser_parse() {
+ var properties = this.properties;
+ var cff = new CFF();
+ this.cff = cff;
+
+ // The first five sections must be in order, all the others are reached
+ // via offsets contained in one of the below.
+ var header = this.parseHeader();
+ var nameIndex = this.parseIndex(header.endPos);
+ var topDictIndex = this.parseIndex(nameIndex.endPos);
+ var stringIndex = this.parseIndex(topDictIndex.endPos);
+ var globalSubrIndex = this.parseIndex(stringIndex.endPos);
+
+ var topDictParsed = this.parseDict(topDictIndex.obj.get(0));
+ var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
+
+ cff.header = header.obj;
+ cff.names = this.parseNameIndex(nameIndex.obj);
+ cff.strings = this.parseStringIndex(stringIndex.obj);
+ cff.topDict = topDict;
+ cff.globalSubrIndex = globalSubrIndex.obj;
+
+ this.parsePrivateDict(cff.topDict);
+
+ cff.isCIDFont = topDict.hasName('ROS');
+
+ var charStringOffset = topDict.getByName('CharStrings');
+ cff.charStrings = this.parseCharStrings(charStringOffset);
+
+ var charset, encoding;
+ if (cff.isCIDFont) {
+ var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj;
+ for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
+ var dictRaw = fdArrayIndex.get(i);
+ var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw),
+ cff.strings);
+ this.parsePrivateDict(fontDict);
+ cff.fdArray.push(fontDict);
+ }
+ // cid fonts don't have an encoding
+ encoding = null;
+ charset = this.parseCharsets(topDict.getByName('charset'),
+ cff.charStrings.count, cff.strings, true);
+ cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'),
+ cff.charStrings.count);
+ } else {
+ charset = this.parseCharsets(topDict.getByName('charset'),
+ cff.charStrings.count, cff.strings, false);
+ encoding = this.parseEncoding(topDict.getByName('Encoding'),
+ properties,
+ cff.strings, charset.charset);
+ }
+ cff.charset = charset;
+ cff.encoding = encoding;
+
+ return cff;
+ },
+ parseHeader: function CFFParser_parseHeader() {
+ var bytes = this.bytes;
+ var offset = 0;
+
+ while (bytes[offset] != 1)
+ ++offset;
+
+ if (offset != 0) {
+ warn('cff data is shifted');
+ bytes = bytes.subarray(offset);
+ this.bytes = bytes;
+ }
+ var major = bytes[0];
+ var minor = bytes[1];
+ var hdrSize = bytes[2];
+ var offSize = bytes[3];
+ var header = new CFFHeader(major, minor, hdrSize, offSize);
+ return {obj: header, endPos: hdrSize};
+ },
+ parseDict: function CFFParser_parseDict(dict) {
+ var pos = 0;
+
+ function parseOperand() {
+ var value = dict[pos++];
+ if (value === 30) {
+ return parseFloatOperand(pos);
+ } else if (value === 28) {
+ value = dict[pos++];
+ value = (value << 8) | dict[pos++];
+ return value;
+ } else if (value === 29) {
+ value = dict[pos++];
+ value = (value << 8) | dict[pos++];
+ value = (value << 8) | dict[pos++];
+ value = (value << 8) | dict[pos++];
+ return value;
+ } else if (value >= 32 && value <= 246) {
+ return value - 139;
+ } else if (value >= 247 && value <= 250) {
+ return ((value - 247) * 256) + dict[pos++] + 108;
+ } else if (value >= 251 && value <= 254) {
+ return -((value - 251) * 256) - dict[pos++] - 108;
+ } else {
+ error('255 is not a valid DICT command');
+ }
+ return -1;
+ }
+
+ function parseFloatOperand() {
+ var str = '';
+ var eof = 15;
+ var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8',
+ '9', '.', 'E', 'E-', null, '-'];
+ var length = dict.length;
+ while (pos < length) {
+ var b = dict[pos++];
+ var b1 = b >> 4;
+ var b2 = b & 15;
+
+ if (b1 == eof)
+ break;
+ str += lookup[b1];
+
+ if (b2 == eof)
+ break;
+ str += lookup[b2];
+ }
+ return parseFloat(str);
+ }
+
+ var operands = [];
+ var entries = [];
+
+ var pos = 0;
+ var end = dict.length;
+ while (pos < end) {
+ var b = dict[pos];
+ if (b <= 21) {
+ if (b === 12)
+ b = (b << 8) | dict[++pos];
+ entries.push([b, operands]);
+ operands = [];
+ ++pos;
+ } else {
+ operands.push(parseOperand());
+ }
+ }
+ return entries;
+ },
+ parseIndex: function CFFParser_parseIndex(pos) {
+ var cffIndex = new CFFIndex();
+ var bytes = this.bytes;
+ var count = (bytes[pos++] << 8) | bytes[pos++];
+ var offsets = [];
+ var start = pos;
+ var end = pos;
+
+ if (count != 0) {
+ var offsetSize = bytes[pos++];
+ // add 1 for offset to determine size of last object
+ var startPos = pos + ((count + 1) * offsetSize) - 1;
+
+ for (var i = 0, ii = count + 1; i < ii; ++i) {
+ var offset = 0;
+ for (var j = 0; j < offsetSize; ++j) {
+ offset <<= 8;
+ offset += bytes[pos++];
+ }
+ offsets.push(startPos + offset);
+ }
+ end = offsets[count];
+ }
+ for (var i = 0, ii = offsets.length - 1; i < ii; ++i) {
+ var offsetStart = offsets[i];
+ var offsetEnd = offsets[i + 1];
+ cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
+ }
+ return {obj: cffIndex, endPos: end};
+ },
+ parseNameIndex: function CFFParser_parseNameIndex(index) {
+ var names = [];
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var name = index.get(i);
+ // OTS doesn't allow names to be over 127 characters.
+ var length = Math.min(name.length, 127);
+ var data = [];
+ // OTS also only permits certain characters in the name.
+ for (var j = 0; j < length; ++j) {
+ var c = name[j];
+ if (j === 0 && c === 0) {
+ data[j] = c;
+ continue;
+ }
+ if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ ||
+ c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ ||
+ c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ ||
+ c === 47 /* / */ || c === 37 /* % */) {
+ data[j] = 95;
+ continue;
+ }
+ data[j] = c;
+ }
+ names.push(String.fromCharCode.apply(null, data));
+ }
+ return names;
+ },
+ parseStringIndex: function CFFParser_parseStringIndex(index) {
+ var strings = new CFFStrings();
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var data = index.get(i);
+ strings.add(String.fromCharCode.apply(null, data));
+ }
+ return strings;
+ },
+ createDict: function CFFParser_createDict(type, dict, strings) {
+ var cffDict = new type(strings);
+ var types = cffDict.types;
+
+ for (var i = 0, ii = dict.length; i < ii; ++i) {
+ var pair = dict[i];
+ var key = pair[0];
+ var value = pair[1];
+ cffDict.setByKey(key, value);
+ }
+ return cffDict;
+ },
+ parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) {
+ var charStrings = this.parseIndex(charStringOffset).obj;
+ // The CFF specification state that the 'dotsection' command
+ // (12, 0) is deprecated and treated as a no-op, but all Type2
+ // charstrings processors should support them. Unfortunately
+ // the font sanitizer don't. As a workaround the sequence (12, 0)
+ // is replaced by a useless (0, hmoveto).
+ var count = charStrings.count;
+ for (var i = 0; i < count; i++) {
+ var charstring = charStrings.get(i);
+
+ var data = charstring;
+ var length = data.length;
+ for (var j = 0; j <= length; j) {
+ var value = data[j++];
+ if (value == 12 && data[j++] == 0) {
+ data[j - 2] = 139;
+ data[j - 1] = 22;
+ } else if (value === 28) {
+ j += 2;
+ } else if (value >= 247 && value <= 254) {
+ j++;
+ } else if (value == 255) {
+ j += 4;
+ }
+ }
+ }
+ return charStrings;
+ },
+ parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
+ // no private dict, do nothing
+ if (!parentDict.hasName('Private'))
+ return;
+ var privateOffset = parentDict.getByName('Private');
+ // make sure the params are formatted correctly
+ if (!isArray(privateOffset) || privateOffset.length !== 2) {
+ parentDict.removeByName('Private');
+ return;
+ }
+ var size = privateOffset[0];
+ var offset = privateOffset[1];
+ // remove empty dicts or ones that refer to invalid location
+ if (size === 0 || offset >= this.bytes.length) {
+ parentDict.removeByName('Private');
+ return;
+ }
+
+ var privateDictEnd = offset + size;
+ var dictData = this.bytes.subarray(offset, privateDictEnd);
+ var dict = this.parseDict(dictData);
+ var privateDict = this.createDict(CFFPrivateDict, dict,
+ parentDict.strings);
+ parentDict.privateDict = privateDict;
+
+ // Parse the Subrs index also since it's relative to the private dict.
+ if (!privateDict.getByName('Subrs'))
+ return;
+ var subrsOffset = privateDict.getByName('Subrs');
+ var relativeOffset = offset + subrsOffset;
+ // Validate the offset.
+ if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
+ privateDict.removeByName('Subrs');
+ return;
+ }
+ var subrsIndex = this.parseIndex(relativeOffset);
+ privateDict.subrsIndex = subrsIndex.obj;
+ },
+ parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) {
+ if (pos == 0) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE,
+ ISOAdobeCharset);
+ } else if (pos == 1) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT,
+ ExpertCharset);
+ } else if (pos == 2) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET,
+ ExpertSubsetCharset);
+ }
+
+ var bytes = this.bytes;
+ var start = pos;
+ var format = bytes[pos++];
+ var charset = ['.notdef'];
+
+ // subtract 1 for the .notdef glyph
+ length -= 1;
+
+ switch (format) {
+ case 0:
+ for (var i = 0; i < length; i++) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ charset.push(cid ? id : strings.get(id));
+ }
+ break;
+ case 1:
+ while (charset.length <= length) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ var count = bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ break;
+ case 2:
+ while (charset.length <= length) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ var count = (bytes[pos++] << 8) | bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ break;
+ default:
+ error('Unknown charset format');
+ }
+ // Raw won't be needed if we actually compile the charset.
+ var end = pos;
+ var raw = bytes.subarray(start, end);
+
+ return new CFFCharset(false, format, charset, raw);
+ },
+ parseEncoding: function CFFParser_parseEncoding(pos,
+ properties,
+ strings,
+ charset) {
+ var encoding = {};
+ var bytes = this.bytes;
+ var predefined = false;
+ var hasSupplement = false;
+ var format;
+ var raw = null;
+
+ function readSupplement() {
+ var supplementsCount = bytes[pos++];
+ for (var i = 0; i < supplementsCount; i++) {
+ var code = bytes[pos++];
+ var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
+ encoding[code] = properties.differences.indexOf(strings.get(sid));
+ }
+ }
+
+ if (pos == 0 || pos == 1) {
+ predefined = true;
+ format = pos;
+ var gid = 1;
+ var baseEncoding = pos ? Encodings.ExpertEncoding :
+ Encodings.StandardEncoding;
+ for (var i = 0, ii = charset.length; i < ii; i++) {
+ var index = baseEncoding.indexOf(charset[i]);
+ if (index != -1)
+ encoding[index] = gid++;
+ }
+ } else {
+ var dataStart = pos;
+ var format = bytes[pos++];
+ switch (format & 0x7f) {
+ case 0:
+ var glyphsCount = bytes[pos++];
+ for (var i = 1; i <= glyphsCount; i++)
+ encoding[bytes[pos++]] = i;
+ break;
+
+ case 1:
+ var rangesCount = bytes[pos++];
+ var gid = 1;
+ for (var i = 0; i < rangesCount; i++) {
+ var start = bytes[pos++];
+ var left = bytes[pos++];
+ for (var j = start; j <= start + left; j++)
+ encoding[j] = gid++;
+ }
+ break;
+
+ default:
+ error('Unknow encoding format: ' + format + ' in CFF');
+ break;
+ }
+ var dataEnd = pos;
+ if (format & 0x80) {
+ // The font sanitizer does not support CFF encoding with a
+ // supplement, since the encoding is not really used to map
+ // between gid to glyph, let's overwrite what is declared in
+ // the top dictionary to let the sanitizer think the font use
+ // StandardEncoding, that's a lie but that's ok.
+ bytes[dataStart] &= 0x7f;
+ readSupplement();
+ hasSupplement = true;
+ }
+ raw = bytes.subarray(dataStart, dataEnd);
+ }
+ format = format & 0x7f;
+ return new CFFEncoding(predefined, format, encoding, raw);
+ },
+ parseFDSelect: function CFFParser_parseFDSelect(pos, length) {
+ var start = pos;
+ var bytes = this.bytes;
+ var format = bytes[pos++];
+ var fdSelect = [];
+ switch (format) {
+ case 0:
+ for (var i = 0; i < length; ++i) {
+ var id = bytes[pos++];
+ fdSelect.push(id);
+ }
+ break;
+ case 3:
+ var rangesCount = (bytes[pos++] << 8) | bytes[pos++];
+ for (var i = 0; i < rangesCount; ++i) {
+ var first = (bytes[pos++] << 8) | bytes[pos++];
+ var fdIndex = bytes[pos++];
+ var next = (bytes[pos] << 8) | bytes[pos + 1];
+ for (var j = first; j < next; ++j)
+ fdSelect.push(fdIndex);
+ }
+ // Advance past the sentinel(next).
+ pos += 2;
+ break;
+ default:
+ error('Unknown fdselect format ' + format);
+ break;
+ }
+ var end = pos;
+ return new CFFFDSelect(fdSelect, bytes.subarray(start, end));
+ }
+ };
+ return CFFParser;
+})();
+
+// Compact Font Format
+var CFF = (function CFFClosure() {
+ function CFF() {
+ this.header = null;
+ this.names = [];
+ this.topDict = null;
+ this.strings = new CFFStrings();
+ this.globalSubrIndex = null;
+
+ // The following could really be per font, but since we only have one font
+ // store them here.
+ this.encoding = null;
+ this.charset = null;
+ this.charStrings = null;
+ this.fdArray = [];
+ this.fdSelect = null;
+
+ this.isCIDFont = false;
+ }
+ return CFF;
+})();
+
+var CFFHeader = (function CFFHeaderClosure() {
+ function CFFHeader(major, minor, hdrSize, offSize) {
+ this.major = major;
+ this.minor = minor;
+ this.hdrSize = hdrSize;
+ this.offSize = offSize;
+ }
+ return CFFHeader;
+})();
+
+var CFFStrings = (function CFFStringsClosure() {
+ function CFFStrings() {
+ this.strings = [];
+ }
+ CFFStrings.prototype = {
+ get: function CFFStrings_get(index) {
+ if (index >= 0 && index <= 390)
+ return CFFStandardStrings[index];
+ if (index - 391 <= this.strings.length)
+ return this.strings[index - 391];
+ return CFFStandardStrings[0];
+ },
+ add: function CFFStrings_add(value) {
+ this.strings.push(value);
+ },
+ get count() {
+ return this.strings.length;
+ }
+ };
+ return CFFStrings;
+})();
+
+var CFFIndex = (function CFFIndexClosure() {
+ function CFFIndex() {
+ this.objects = [];
+ this.length = 0;
+ }
+ CFFIndex.prototype = {
+ add: function CFFIndex_add(data) {
+ this.length += data.length;
+ this.objects.push(data);
+ },
+ get: function CFFIndex_get(index) {
+ return this.objects[index];
+ },
+ get count() {
+ return this.objects.length;
+ }
+ };
+ return CFFIndex;
+})();
+
+var CFFDict = (function CFFDictClosure() {
+ function CFFDict(tables, strings) {
+ this.keyToNameMap = tables.keyToNameMap;
+ this.nameToKeyMap = tables.nameToKeyMap;
+ this.defaults = tables.defaults;
+ this.types = tables.types;
+ this.opcodes = tables.opcodes;
+ this.order = tables.order;
+ this.strings = strings;
+ this.values = {};
+ }
+ CFFDict.prototype = {
+ // value should always be an array
+ setByKey: function CFFDict_setByKey(key, value) {
+ if (!(key in this.keyToNameMap))
+ return false;
+ // ignore empty values
+ if (value.length === 0)
+ return true;
+ var type = this.types[key];
+ // remove the array wrapping these types of values
+ if (type === 'num' || type === 'sid' || type === 'offset')
+ value = value[0];
+ this.values[key] = value;
+ return true;
+ },
+ hasName: function CFFDict_hasName(name) {
+ return this.nameToKeyMap[name] in this.values;
+ },
+ getByName: function CFFDict_getByName(name) {
+ if (!(name in this.nameToKeyMap))
+ error('Invalid dictionary name "' + name + '"');
+ var key = this.nameToKeyMap[name];
+ if (!(key in this.values))
+ return this.defaults[key];
+ return this.values[key];
+ },
+ removeByName: function CFFDict_removeByName(name) {
+ delete this.values[this.nameToKeyMap[name]];
+ }
+ };
+ CFFDict.createTables = function CFFDict_createTables(layout) {
+ var tables = {
+ keyToNameMap: {},
+ nameToKeyMap: {},
+ defaults: {},
+ types: {},
+ opcodes: {},
+ order: []
+ };
+ for (var i = 0, ii = layout.length; i < ii; ++i) {
+ var entry = layout[i];
+ var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
+ tables.keyToNameMap[key] = entry[1];
+ tables.nameToKeyMap[entry[1]] = key;
+ tables.types[key] = entry[2];
+ tables.defaults[key] = entry[3];
+ tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]];
+ tables.order.push(key);
+ }
+ return tables;
+ };
+ return CFFDict;
+})();
+
+var CFFTopDict = (function CFFTopDictClosure() {
+ var layout = [
+ [[12, 30], 'ROS', ['sid', 'sid', 'num'], null],
+ [[12, 20], 'SyntheticBase', 'num', null],
+ [0, 'version', 'sid', null],
+ [1, 'Notice', 'sid', null],
+ [[12, 0], 'Copyright', 'sid', null],
+ [2, 'FullName', 'sid', null],
+ [3, 'FamilyName', 'sid', null],
+ [4, 'Weight', 'sid', null],
+ [[12, 1], 'isFixedPitch', 'num', 0],
+ [[12, 2], 'ItalicAngle', 'num', 0],
+ [[12, 3], 'UnderlinePosition', 'num', -100],
+ [[12, 4], 'UnderlineThickness', 'num', 50],
+ [[12, 5], 'PaintType', 'num', 0],
+ [[12, 6], 'CharstringType', 'num', 2],
+ [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'],
+ [.001, 0, 0, .001, 0, 0]],
+ [13, 'UniqueID', 'num', null],
+ [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]],
+ [[12, 8], 'StrokeWidth', 'num', 0],
+ [14, 'XUID', 'array', null],
+ [15, 'charset', 'offset', 0],
+ [16, 'Encoding', 'offset', 0],
+ [17, 'CharStrings', 'offset', 0],
+ [18, 'Private', ['offset', 'offset'], null],
+ [[12, 21], 'PostScript', 'sid', null],
+ [[12, 22], 'BaseFontName', 'sid', null],
+ [[12, 23], 'BaseFontBlend', 'delta', null],
+ [[12, 31], 'CIDFontVersion', 'num', 0],
+ [[12, 32], 'CIDFontRevision', 'num', 0],
+ [[12, 33], 'CIDFontType', 'num', 0],
+ [[12, 34], 'CIDCount', 'num', 8720],
+ [[12, 35], 'UIDBase', 'num', null],
+ [[12, 36], 'FDArray', 'offset', null],
+ [[12, 37], 'FDSelect', 'offset', null],
+ [[12, 38], 'FontName', 'sid', null]];
+ var tables = null;
+ function CFFTopDict(strings) {
+ if (tables === null)
+ tables = CFFDict.createTables(layout);
+ CFFDict.call(this, tables, strings);
+ this.privateDict = null;
+ }
+ CFFTopDict.prototype = Object.create(CFFDict.prototype);
+ return CFFTopDict;
+})();
+
+var CFFPrivateDict = (function CFFPrivateDictClosure() {
+ var layout = [
+ [6, 'BlueValues', 'delta', null],
+ [7, 'OtherBlues', 'delta', null],
+ [8, 'FamilyBlues', 'delta', null],
+ [9, 'FamilyOtherBlues', 'delta', null],
+ [[12, 9], 'BlueScale', 'num', 0.039625],
+ [[12, 10], 'BlueShift', 'num', 7],
+ [[12, 11], 'BlueFuzz', 'num', 1],
+ [10, 'StdHW', 'num', null],
+ [11, 'StdVW', 'num', null],
+ [[12, 12], 'StemSnapH', 'delta', null],
+ [[12, 13], 'StemSnapV', 'delta', null],
+ [[12, 14], 'ForceBold', 'num', 0],
+ [[12, 17], 'LanguageGroup', 'num', 0],
+ [[12, 18], 'ExpansionFactor', 'num', 0.06],
+ [[12, 19], 'initialRandomSeed', 'num', 0],
+ [19, 'Subrs', 'offset', null],
+ [20, 'defaultWidthX', 'num', 0],
+ [21, 'nominalWidthX', 'num', 0]
+ ];
+ var tables = null;
+ function CFFPrivateDict(strings) {
+ if (tables === null)
+ tables = CFFDict.createTables(layout);
+ CFFDict.call(this, tables, strings);
+ this.subrsIndex = null;
+ }
+ CFFPrivateDict.prototype = Object.create(CFFDict.prototype);
+ return CFFPrivateDict;
+})();
+
+var CFFCharsetPredefinedTypes = {
+ ISO_ADOBE: 0,
+ EXPERT: 1,
+ EXPERT_SUBSET: 2
+};
+var CFFCharsetEmbeddedTypes = {
+ FORMAT0: 0,
+ FORMAT1: 1,
+ FORMAT2: 2
+};
+var CFFCharset = (function CFFCharsetClosure() {
+ function CFFCharset(predefined, format, charset, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.charset = charset;
+ this.raw = raw;
+ }
+ return CFFCharset;
+})();
+
+var CFFEncodingPredefinedTypes = {
+ STANDARD: 0,
+ EXPERT: 1
+};
+var CFFCharsetEmbeddedTypes = {
+ FORMAT0: 0,
+ FORMAT1: 1
+};
+var CFFEncoding = (function CFFEncodingClosure() {
+ function CFFEncoding(predefined, format, encoding, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.encoding = encoding;
+ this.raw = raw;
+ }
+ return CFFEncoding;
+})();
+
+var CFFFDSelect = (function CFFFDSelectClosure() {
+ function CFFFDSelect(fdSelect, raw) {
+ this.fdSelect = fdSelect;
+ this.raw = raw;
+ }
+ return CFFFDSelect;
+})();
+
+// Helper class to keep track of where an offset is within the data and helps
+// filling in that offset once it's known.
+var CFFOffsetTracker = (function CFFOffsetTrackerClosure() {
+ function CFFOffsetTracker() {
+ this.offsets = {};
+ }
+ CFFOffsetTracker.prototype = {
+ isTracking: function CFFOffsetTracker_isTracking(key) {
+ return key in this.offsets;
+ },
+ track: function CFFOffsetTracker_track(key, location) {
+ if (key in this.offsets)
+ error('Already tracking location of ' + key);
+ this.offsets[key] = location;
+ },
+ offset: function CFFOffsetTracker_offset(value) {
+ for (var key in this.offsets) {
+ this.offsets[key] += value;
+ }
+ },
+ setEntryLocation: function CFFOffsetTracker_setEntryLocation(key,
+ values,
+ output) {
+ if (!(key in this.offsets))
+ error('Not tracking location of ' + key);
+ var data = output.data;
+ var dataOffset = this.offsets[key];
+ var size = 5;
+ for (var i = 0, ii = values.length; i < ii; ++i) {
+ var offset0 = i * size + dataOffset;
+ var offset1 = offset0 + 1;
+ var offset2 = offset0 + 2;
+ var offset3 = offset0 + 3;
+ var offset4 = offset0 + 4;
+ // It's easy to screw up offsets so perform this sanity check.
+ if (data[offset0] !== 0x1d || data[offset1] !== 0 ||
+ data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0)
+ error('writing to an offset that is not empty');
+ var value = values[i];
+ data[offset0] = 0x1d;
+ data[offset1] = (value >> 24) & 0xFF;
+ data[offset2] = (value >> 16) & 0xFF;
+ data[offset3] = (value >> 8) & 0xFF;
+ data[offset4] = value & 0xFF;
+ }
+ }
+ };
+ return CFFOffsetTracker;
+})();
+
+// Takes a CFF and converts it to the binary representation.
+var CFFCompiler = (function CFFCompilerClosure() {
+ function stringToArray(str) {
+ var array = [];
+ for (var i = 0, ii = str.length; i < ii; ++i)
+ array[i] = str.charCodeAt(i);
+
+ return array;
+ };
+ function CFFCompiler(cff) {
+ this.cff = cff;
+ }
+ CFFCompiler.prototype = {
+ compile: function CFFCompiler_compile() {
+ var cff = this.cff;
+ var output = {
+ data: [],
+ length: 0,
+ add: function CFFCompiler_add(data) {
+ this.data = this.data.concat(data);
+ this.length = this.data.length;
+ }
+ };
+
+ // Compile the five entries that must be in order.
+ var header = this.compileHeader(cff.header);
+ output.add(header);
+
+ var nameIndex = this.compileNameIndex(cff.names);
+ output.add(nameIndex);
+
+ var compiled = this.compileTopDicts([cff.topDict], output.length);
+ output.add(compiled.output);
+ var topDictTracker = compiled.trackers[0];
+
+ var stringIndex = this.compileStringIndex(cff.strings.strings);
+ output.add(stringIndex);
+
+ var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
+ output.add(globalSubrIndex);
+
+ // Now start on the other entries that have no specfic order.
+ if (cff.encoding && cff.topDict.hasName('Encoding')) {
+ if (cff.encoding.predefined) {
+ topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
+ output);
+ } else {
+ var encoding = this.compileEncoding(cff.encoding);
+ topDictTracker.setEntryLocation('Encoding', [output.length], output);
+ output.add(encoding);
+ }
+ }
+
+ if (cff.charset && cff.topDict.hasName('charset')) {
+ if (cff.charset.predefined) {
+ topDictTracker.setEntryLocation('charset', [cff.charset.format],
+ output);
+ } else {
+ var charset = this.compileCharset(cff.charset);
+ topDictTracker.setEntryLocation('charset', [output.length], output);
+ output.add(charset);
+ }
+ }
+
+ var charStrings = this.compileCharStrings(cff.charStrings);
+ topDictTracker.setEntryLocation('CharStrings', [output.length], output);
+ output.add(charStrings);
+
+ if (cff.isCIDFont) {
+ // For some reason FDSelect must be in front of FDArray on windows. OSX
+ // and linux don't seem to care.
+ topDictTracker.setEntryLocation('FDSelect', [output.length], output);
+ var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
+ output.add(fdSelect);
+
+ var compiled = this.compileTopDicts(cff.fdArray, output.length);
+ topDictTracker.setEntryLocation('FDArray', [output.length], output);
+ output.add(compiled.output);
+ var fontDictTrackers = compiled.trackers;
+
+ this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
+ }
+
+ this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
+
+ return output.data;
+ },
+ encodeNumber: function CFFCompiler_encodeNumber(value) {
+ if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt
+ return this.encodeInteger(value);
+ else
+ return this.encodeFloat(value);
+ },
+ encodeFloat: function CFFCompiler_encodeFloat(value) {
+ value = value.toString();
+ // Strip off the any leading zeros.
+ if (value.substr(0, 2) === '0.')
+ value = value.substr(1);
+ else if (value.substr(0, 3) === '-0.')
+ value = '-' + value.substr(2);
+ var nibbles = [];
+ for (var i = 0, ii = value.length; i < ii; ++i) {
+ var a = value.charAt(i), b = value.charAt(i + 1);
+ var nibble;
+ if (a === 'e' && b === '-') {
+ nibble = 0xc;
+ ++i;
+ } else if (a === '.') {
+ nibble = 0xa;
+ } else if (a === 'E') {
+ nibble = 0xb;
+ } else if (a === '-') {
+ nibble = 0xe;
+ } else {
+ nibble = a;
+ }
+ nibbles.push(nibble);
+ }
+ nibbles.push(0xf);
+ if (nibbles.length % 2)
+ nibbles.push(0xf);
+ var out = [30];
+ for (var i = 0, ii = nibbles.length; i < ii; i += 2)
+ out.push(nibbles[i] << 4 | nibbles[i + 1]);
+ return out;
+ },
+ encodeInteger: function CFFCompiler_encodeInteger(value) {
+ var code;
+ if (value >= -107 && value <= 107) {
+ code = [value + 139];
+ } else if (value >= 108 && value <= 1131) {
+ value = [value - 108];
+ code = [(value >> 8) + 247, value & 0xFF];
+ } else if (value >= -1131 && value <= -108) {
+ value = -value - 108;
+ code = [(value >> 8) + 251, value & 0xFF];
+ } else if (value >= -32768 && value <= 32767) {
+ code = [0x1c, (value >> 8) & 0xFF, value & 0xFF];
+ } else {
+ code = [0x1d,
+ (value >> 24) & 0xFF,
+ (value >> 16) & 0xFF,
+ (value >> 8) & 0xFF,
+ value & 0xFF];
+ }
+ return code;
+ },
+ compileHeader: function CFFCompiler_compileHeader(header) {
+ return [
+ header.major,
+ header.minor,
+ header.hdrSize,
+ header.offSize
+ ];
+ },
+ compileNameIndex: function CFFCompiler_compileNameIndex(names) {
+ var nameIndex = new CFFIndex();
+ for (var i = 0, ii = names.length; i < ii; ++i)
+ nameIndex.add(stringToArray(names[i]));
+ return this.compileIndex(nameIndex);
+ },
+ compileTopDicts: function CFFCompiler_compileTopDicts(dicts, length) {
+ var fontDictTrackers = [];
+ var fdArrayIndex = new CFFIndex();
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ var fontDictTracker = new CFFOffsetTracker();
+ var fontDictData = this.compileDict(fontDict, fontDictTracker);
+ fontDictTrackers.push(fontDictTracker);
+ fdArrayIndex.add(fontDictData);
+ fontDictTracker.offset(length);
+ }
+ fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
+ return {
+ trackers: fontDictTrackers,
+ output: fdArrayIndex
+ };
+ },
+ compilePrivateDicts: function CFFCompiler_compilePrivateDicts(dicts,
+ trackers,
+ output) {
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ if (!fontDict.privateDict || !fontDict.hasName('Private'))
+ continue;
+ var privateDict = fontDict.privateDict;
+ var privateDictTracker = new CFFOffsetTracker();
+ var privateDictData = this.compileDict(privateDict, privateDictTracker);
+
+ privateDictTracker.offset(output.length);
+ trackers[i].setEntryLocation('Private',
+ [privateDictData.length, output.length],
+ output);
+ output.add(privateDictData);
+
+ if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
+ var subrs = this.compileIndex(privateDict.subrsIndex);
+ privateDictTracker.setEntryLocation('Subrs', [privateDictData.length],
+ output);
+ output.add(subrs);
+ }
+ }
+ },
+ compileDict: function CFFCompiler_compileDict(dict, offsetTracker) {
+ var out = [];
+ // The dictionary keys must be in a certain order.
+ var order = dict.order;
+ for (var i = 0; i < order.length; ++i) {
+ var key = order[i];
+ if (!(key in dict.values))
+ continue;
+ var values = dict.values[key];
+ var types = dict.types[key];
+ if (!isArray(types)) types = [types];
+ if (!isArray(values)) values = [values];
+
+ // Remove any empty dict values.
+ if (values.length === 0)
+ continue;
+
+ for (var j = 0, jj = types.length; j < jj; ++j) {
+ var type = types[j];
+ var value = values[j];
+ switch (type) {
+ case 'num':
+ case 'sid':
+ out = out.concat(this.encodeNumber(value));
+ break;
+ case 'offset':
+ // For offsets we just insert a 32bit integer so we don't have to
+ // deal with figuring out the length of the offset when it gets
+ // replaced later on by the compiler.
+ var name = dict.keyToNameMap[key];
+ // Some offsets have the offset and the length, so just record the
+ // position of the first one.
+ if (!offsetTracker.isTracking(name))
+ offsetTracker.track(name, out.length);
+ out = out.concat([0x1d, 0, 0, 0, 0]);
+ break;
+ case 'array':
+ case 'delta':
+ out = out.concat(this.encodeNumber(value));
+ for (var k = 1, kk = values.length; k < kk; ++k)
+ out = out.concat(this.encodeNumber(values[k]));
+ break;
+ default:
+ error('Unknown data type of ' + type);
+ break;
+ }
+ }
+ out = out.concat(dict.opcodes[key]);
+ }
+ return out;
+ },
+ compileStringIndex: function CFFCompiler_compileStringIndex(strings) {
+ var stringIndex = new CFFIndex();
+ for (var i = 0, ii = strings.length; i < ii; ++i)
+ stringIndex.add(stringToArray(strings[i]));
+ return this.compileIndex(stringIndex);
+ },
+ compileGlobalSubrIndex: function CFFCompiler_compileGlobalSubrIndex() {
+ var globalSubrIndex = this.cff.globalSubrIndex;
+ this.out.writeByteArray(this.compileIndex(globalSubrIndex));
+ },
+ compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) {
+ return this.compileIndex(charStrings);
+ },
+ compileCharset: function CFFCompiler_compileCharset(charset) {
+ return this.compileTypedArray(charset.raw);
+ },
+ compileEncoding: function CFFCompiler_compileEncoding(encoding) {
+ return this.compileTypedArray(encoding.raw);
+ },
+ compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) {
+ return this.compileTypedArray(fdSelect);
+ },
+ compileTypedArray: function CFFCompiler_compileTypedArray(data) {
+ var out = [];
+ for (var i = 0, ii = data.length; i < ii; ++i)
+ out[i] = data[i];
+ return out;
+ },
+ compileIndex: function CFFCompiler_compileIndex(index, trackers) {
+ trackers = trackers || [];
+ var objects = index.objects;
+ // First 2 bytes contains the number of objects contained into this index
+ var count = objects.length;
+
+ // If there is no object, just create an index. This technically
+ // should just be [0, 0] but OTS has an issue with that.
+ if (count == 0)
+ return [0, 0, 0];
+
+ var data = [(count >> 8) & 0xFF, count & 0xff];
+
+ var lastOffset = 1;
+ for (var i = 0; i < count; ++i)
+ lastOffset += objects[i].length;
+
+ var offsetSize;
+ if (lastOffset < 0x100)
+ offsetSize = 1;
+ else if (lastOffset < 0x10000)
+ offsetSize = 2;
+ else if (lastOffset < 0x1000000)
+ offsetSize = 3;
+ else
+ offsetSize = 4;
+
+ // Next byte contains the offset size use to reference object in the file
+ data.push(offsetSize);
+
+ // Add another offset after this one because we need a new offset
+ var relativeOffset = 1;
+ for (var i = 0; i < count + 1; i++) {
+ if (offsetSize === 1) {
+ data.push(relativeOffset & 0xFF);
+ } else if (offsetSize === 2) {
+ data.push((relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ } else if (offsetSize === 3) {
+ data.push((relativeOffset >> 16) & 0xFF,
+ (relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ } else {
+ data.push((relativeOffset >>> 24) & 0xFF,
+ (relativeOffset >> 16) & 0xFF,
+ (relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ }
+
+ if (objects[i])
+ relativeOffset += objects[i].length;
+ }
+ var offset = data.length;
+
+ for (var i = 0; i < count; i++) {
+ // Notify the tracker where the object will be offset in the data.
+ if (trackers[i])
+ trackers[i].offset(data.length);
+ for (var j = 0, jj = objects[i].length; j < jj; j++)
+ data.push(objects[i][j]);
+ }
+ return data;
+ }
+ };
+ return CFFCompiler;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var GlyphsUnicode = {
+ A: 0x0041,
+ AE: 0x00C6,
+ AEacute: 0x01FC,
+ AEmacron: 0x01E2,
+ AEsmall: 0xF7E6,
+ Aacute: 0x00C1,
+ Aacutesmall: 0xF7E1,
+ Abreve: 0x0102,
+ Abreveacute: 0x1EAE,
+ Abrevecyrillic: 0x04D0,
+ Abrevedotbelow: 0x1EB6,
+ Abrevegrave: 0x1EB0,
+ Abrevehookabove: 0x1EB2,
+ Abrevetilde: 0x1EB4,
+ Acaron: 0x01CD,
+ Acircle: 0x24B6,
+ Acircumflex: 0x00C2,
+ Acircumflexacute: 0x1EA4,
+ Acircumflexdotbelow: 0x1EAC,
+ Acircumflexgrave: 0x1EA6,
+ Acircumflexhookabove: 0x1EA8,
+ Acircumflexsmall: 0xF7E2,
+ Acircumflextilde: 0x1EAA,
+ Acute: 0xF6C9,
+ Acutesmall: 0xF7B4,
+ Acyrillic: 0x0410,
+ Adblgrave: 0x0200,
+ Adieresis: 0x00C4,
+ Adieresiscyrillic: 0x04D2,
+ Adieresismacron: 0x01DE,
+ Adieresissmall: 0xF7E4,
+ Adotbelow: 0x1EA0,
+ Adotmacron: 0x01E0,
+ Agrave: 0x00C0,
+ Agravesmall: 0xF7E0,
+ Ahookabove: 0x1EA2,
+ Aiecyrillic: 0x04D4,
+ Ainvertedbreve: 0x0202,
+ Alpha: 0x0391,
+ Alphatonos: 0x0386,
+ Amacron: 0x0100,
+ Amonospace: 0xFF21,
+ Aogonek: 0x0104,
+ Aring: 0x00C5,
+ Aringacute: 0x01FA,
+ Aringbelow: 0x1E00,
+ Aringsmall: 0xF7E5,
+ Asmall: 0xF761,
+ Atilde: 0x00C3,
+ Atildesmall: 0xF7E3,
+ Aybarmenian: 0x0531,
+ B: 0x0042,
+ Bcircle: 0x24B7,
+ Bdotaccent: 0x1E02,
+ Bdotbelow: 0x1E04,
+ Becyrillic: 0x0411,
+ Benarmenian: 0x0532,
+ Beta: 0x0392,
+ Bhook: 0x0181,
+ Blinebelow: 0x1E06,
+ Bmonospace: 0xFF22,
+ Brevesmall: 0xF6F4,
+ Bsmall: 0xF762,
+ Btopbar: 0x0182,
+ C: 0x0043,
+ Caarmenian: 0x053E,
+ Cacute: 0x0106,
+ Caron: 0xF6CA,
+ Caronsmall: 0xF6F5,
+ Ccaron: 0x010C,
+ Ccedilla: 0x00C7,
+ Ccedillaacute: 0x1E08,
+ Ccedillasmall: 0xF7E7,
+ Ccircle: 0x24B8,
+ Ccircumflex: 0x0108,
+ Cdot: 0x010A,
+ Cdotaccent: 0x010A,
+ Cedillasmall: 0xF7B8,
+ Chaarmenian: 0x0549,
+ Cheabkhasiancyrillic: 0x04BC,
+ Checyrillic: 0x0427,
+ Chedescenderabkhasiancyrillic: 0x04BE,
+ Chedescendercyrillic: 0x04B6,
+ Chedieresiscyrillic: 0x04F4,
+ Cheharmenian: 0x0543,
+ Chekhakassiancyrillic: 0x04CB,
+ Cheverticalstrokecyrillic: 0x04B8,
+ Chi: 0x03A7,
+ Chook: 0x0187,
+ Circumflexsmall: 0xF6F6,
+ Cmonospace: 0xFF23,
+ Coarmenian: 0x0551,
+ Csmall: 0xF763,
+ D: 0x0044,
+ DZ: 0x01F1,
+ DZcaron: 0x01C4,
+ Daarmenian: 0x0534,
+ Dafrican: 0x0189,
+ Dcaron: 0x010E,
+ Dcedilla: 0x1E10,
+ Dcircle: 0x24B9,
+ Dcircumflexbelow: 0x1E12,
+ Dcroat: 0x0110,
+ Ddotaccent: 0x1E0A,
+ Ddotbelow: 0x1E0C,
+ Decyrillic: 0x0414,
+ Deicoptic: 0x03EE,
+ Delta: 0x2206,
+ Deltagreek: 0x0394,
+ Dhook: 0x018A,
+ Dieresis: 0xF6CB,
+ DieresisAcute: 0xF6CC,
+ DieresisGrave: 0xF6CD,
+ Dieresissmall: 0xF7A8,
+ Digammagreek: 0x03DC,
+ Djecyrillic: 0x0402,
+ Dlinebelow: 0x1E0E,
+ Dmonospace: 0xFF24,
+ Dotaccentsmall: 0xF6F7,
+ Dslash: 0x0110,
+ Dsmall: 0xF764,
+ Dtopbar: 0x018B,
+ Dz: 0x01F2,
+ Dzcaron: 0x01C5,
+ Dzeabkhasiancyrillic: 0x04E0,
+ Dzecyrillic: 0x0405,
+ Dzhecyrillic: 0x040F,
+ E: 0x0045,
+ Eacute: 0x00C9,
+ Eacutesmall: 0xF7E9,
+ Ebreve: 0x0114,
+ Ecaron: 0x011A,
+ Ecedillabreve: 0x1E1C,
+ Echarmenian: 0x0535,
+ Ecircle: 0x24BA,
+ Ecircumflex: 0x00CA,
+ Ecircumflexacute: 0x1EBE,
+ Ecircumflexbelow: 0x1E18,
+ Ecircumflexdotbelow: 0x1EC6,
+ Ecircumflexgrave: 0x1EC0,
+ Ecircumflexhookabove: 0x1EC2,
+ Ecircumflexsmall: 0xF7EA,
+ Ecircumflextilde: 0x1EC4,
+ Ecyrillic: 0x0404,
+ Edblgrave: 0x0204,
+ Edieresis: 0x00CB,
+ Edieresissmall: 0xF7EB,
+ Edot: 0x0116,
+ Edotaccent: 0x0116,
+ Edotbelow: 0x1EB8,
+ Efcyrillic: 0x0424,
+ Egrave: 0x00C8,
+ Egravesmall: 0xF7E8,
+ Eharmenian: 0x0537,
+ Ehookabove: 0x1EBA,
+ Eightroman: 0x2167,
+ Einvertedbreve: 0x0206,
+ Eiotifiedcyrillic: 0x0464,
+ Elcyrillic: 0x041B,
+ Elevenroman: 0x216A,
+ Emacron: 0x0112,
+ Emacronacute: 0x1E16,
+ Emacrongrave: 0x1E14,
+ Emcyrillic: 0x041C,
+ Emonospace: 0xFF25,
+ Encyrillic: 0x041D,
+ Endescendercyrillic: 0x04A2,
+ Eng: 0x014A,
+ Enghecyrillic: 0x04A4,
+ Enhookcyrillic: 0x04C7,
+ Eogonek: 0x0118,
+ Eopen: 0x0190,
+ Epsilon: 0x0395,
+ Epsilontonos: 0x0388,
+ Ercyrillic: 0x0420,
+ Ereversed: 0x018E,
+ Ereversedcyrillic: 0x042D,
+ Escyrillic: 0x0421,
+ Esdescendercyrillic: 0x04AA,
+ Esh: 0x01A9,
+ Esmall: 0xF765,
+ Eta: 0x0397,
+ Etarmenian: 0x0538,
+ Etatonos: 0x0389,
+ Eth: 0x00D0,
+ Ethsmall: 0xF7F0,
+ Etilde: 0x1EBC,
+ Etildebelow: 0x1E1A,
+ Euro: 0x20AC,
+ Ezh: 0x01B7,
+ Ezhcaron: 0x01EE,
+ Ezhreversed: 0x01B8,
+ F: 0x0046,
+ Fcircle: 0x24BB,
+ Fdotaccent: 0x1E1E,
+ Feharmenian: 0x0556,
+ Feicoptic: 0x03E4,
+ Fhook: 0x0191,
+ Fitacyrillic: 0x0472,
+ Fiveroman: 0x2164,
+ Fmonospace: 0xFF26,
+ Fourroman: 0x2163,
+ Fsmall: 0xF766,
+ G: 0x0047,
+ GBsquare: 0x3387,
+ Gacute: 0x01F4,
+ Gamma: 0x0393,
+ Gammaafrican: 0x0194,
+ Gangiacoptic: 0x03EA,
+ Gbreve: 0x011E,
+ Gcaron: 0x01E6,
+ Gcedilla: 0x0122,
+ Gcircle: 0x24BC,
+ Gcircumflex: 0x011C,
+ Gcommaaccent: 0x0122,
+ Gdot: 0x0120,
+ Gdotaccent: 0x0120,
+ Gecyrillic: 0x0413,
+ Ghadarmenian: 0x0542,
+ Ghemiddlehookcyrillic: 0x0494,
+ Ghestrokecyrillic: 0x0492,
+ Gheupturncyrillic: 0x0490,
+ Ghook: 0x0193,
+ Gimarmenian: 0x0533,
+ Gjecyrillic: 0x0403,
+ Gmacron: 0x1E20,
+ Gmonospace: 0xFF27,
+ Grave: 0xF6CE,
+ Gravesmall: 0xF760,
+ Gsmall: 0xF767,
+ Gsmallhook: 0x029B,
+ Gstroke: 0x01E4,
+ H: 0x0048,
+ H18533: 0x25CF,
+ H18543: 0x25AA,
+ H18551: 0x25AB,
+ H22073: 0x25A1,
+ HPsquare: 0x33CB,
+ Haabkhasiancyrillic: 0x04A8,
+ Hadescendercyrillic: 0x04B2,
+ Hardsigncyrillic: 0x042A,
+ Hbar: 0x0126,
+ Hbrevebelow: 0x1E2A,
+ Hcedilla: 0x1E28,
+ Hcircle: 0x24BD,
+ Hcircumflex: 0x0124,
+ Hdieresis: 0x1E26,
+ Hdotaccent: 0x1E22,
+ Hdotbelow: 0x1E24,
+ Hmonospace: 0xFF28,
+ Hoarmenian: 0x0540,
+ Horicoptic: 0x03E8,
+ Hsmall: 0xF768,
+ Hungarumlaut: 0xF6CF,
+ Hungarumlautsmall: 0xF6F8,
+ Hzsquare: 0x3390,
+ I: 0x0049,
+ IAcyrillic: 0x042F,
+ IJ: 0x0132,
+ IUcyrillic: 0x042E,
+ Iacute: 0x00CD,
+ Iacutesmall: 0xF7ED,
+ Ibreve: 0x012C,
+ Icaron: 0x01CF,
+ Icircle: 0x24BE,
+ Icircumflex: 0x00CE,
+ Icircumflexsmall: 0xF7EE,
+ Icyrillic: 0x0406,
+ Idblgrave: 0x0208,
+ Idieresis: 0x00CF,
+ Idieresisacute: 0x1E2E,
+ Idieresiscyrillic: 0x04E4,
+ Idieresissmall: 0xF7EF,
+ Idot: 0x0130,
+ Idotaccent: 0x0130,
+ Idotbelow: 0x1ECA,
+ Iebrevecyrillic: 0x04D6,
+ Iecyrillic: 0x0415,
+ Ifraktur: 0x2111,
+ Igrave: 0x00CC,
+ Igravesmall: 0xF7EC,
+ Ihookabove: 0x1EC8,
+ Iicyrillic: 0x0418,
+ Iinvertedbreve: 0x020A,
+ Iishortcyrillic: 0x0419,
+ Imacron: 0x012A,
+ Imacroncyrillic: 0x04E2,
+ Imonospace: 0xFF29,
+ Iniarmenian: 0x053B,
+ Iocyrillic: 0x0401,
+ Iogonek: 0x012E,
+ Iota: 0x0399,
+ Iotaafrican: 0x0196,
+ Iotadieresis: 0x03AA,
+ Iotatonos: 0x038A,
+ Ismall: 0xF769,
+ Istroke: 0x0197,
+ Itilde: 0x0128,
+ Itildebelow: 0x1E2C,
+ Izhitsacyrillic: 0x0474,
+ Izhitsadblgravecyrillic: 0x0476,
+ J: 0x004A,
+ Jaarmenian: 0x0541,
+ Jcircle: 0x24BF,
+ Jcircumflex: 0x0134,
+ Jecyrillic: 0x0408,
+ Jheharmenian: 0x054B,
+ Jmonospace: 0xFF2A,
+ Jsmall: 0xF76A,
+ K: 0x004B,
+ KBsquare: 0x3385,
+ KKsquare: 0x33CD,
+ Kabashkircyrillic: 0x04A0,
+ Kacute: 0x1E30,
+ Kacyrillic: 0x041A,
+ Kadescendercyrillic: 0x049A,
+ Kahookcyrillic: 0x04C3,
+ Kappa: 0x039A,
+ Kastrokecyrillic: 0x049E,
+ Kaverticalstrokecyrillic: 0x049C,
+ Kcaron: 0x01E8,
+ Kcedilla: 0x0136,
+ Kcircle: 0x24C0,
+ Kcommaaccent: 0x0136,
+ Kdotbelow: 0x1E32,
+ Keharmenian: 0x0554,
+ Kenarmenian: 0x053F,
+ Khacyrillic: 0x0425,
+ Kheicoptic: 0x03E6,
+ Khook: 0x0198,
+ Kjecyrillic: 0x040C,
+ Klinebelow: 0x1E34,
+ Kmonospace: 0xFF2B,
+ Koppacyrillic: 0x0480,
+ Koppagreek: 0x03DE,
+ Ksicyrillic: 0x046E,
+ Ksmall: 0xF76B,
+ L: 0x004C,
+ LJ: 0x01C7,
+ LL: 0xF6BF,
+ Lacute: 0x0139,
+ Lambda: 0x039B,
+ Lcaron: 0x013D,
+ Lcedilla: 0x013B,
+ Lcircle: 0x24C1,
+ Lcircumflexbelow: 0x1E3C,
+ Lcommaaccent: 0x013B,
+ Ldot: 0x013F,
+ Ldotaccent: 0x013F,
+ Ldotbelow: 0x1E36,
+ Ldotbelowmacron: 0x1E38,
+ Liwnarmenian: 0x053C,
+ Lj: 0x01C8,
+ Ljecyrillic: 0x0409,
+ Llinebelow: 0x1E3A,
+ Lmonospace: 0xFF2C,
+ Lslash: 0x0141,
+ Lslashsmall: 0xF6F9,
+ Lsmall: 0xF76C,
+ M: 0x004D,
+ MBsquare: 0x3386,
+ Macron: 0xF6D0,
+ Macronsmall: 0xF7AF,
+ Macute: 0x1E3E,
+ Mcircle: 0x24C2,
+ Mdotaccent: 0x1E40,
+ Mdotbelow: 0x1E42,
+ Menarmenian: 0x0544,
+ Mmonospace: 0xFF2D,
+ Msmall: 0xF76D,
+ Mturned: 0x019C,
+ Mu: 0x039C,
+ N: 0x004E,
+ NJ: 0x01CA,
+ Nacute: 0x0143,
+ Ncaron: 0x0147,
+ Ncedilla: 0x0145,
+ Ncircle: 0x24C3,
+ Ncircumflexbelow: 0x1E4A,
+ Ncommaaccent: 0x0145,
+ Ndotaccent: 0x1E44,
+ Ndotbelow: 0x1E46,
+ Nhookleft: 0x019D,
+ Nineroman: 0x2168,
+ Nj: 0x01CB,
+ Njecyrillic: 0x040A,
+ Nlinebelow: 0x1E48,
+ Nmonospace: 0xFF2E,
+ Nowarmenian: 0x0546,
+ Nsmall: 0xF76E,
+ Ntilde: 0x00D1,
+ Ntildesmall: 0xF7F1,
+ Nu: 0x039D,
+ O: 0x004F,
+ OE: 0x0152,
+ OEsmall: 0xF6FA,
+ Oacute: 0x00D3,
+ Oacutesmall: 0xF7F3,
+ Obarredcyrillic: 0x04E8,
+ Obarreddieresiscyrillic: 0x04EA,
+ Obreve: 0x014E,
+ Ocaron: 0x01D1,
+ Ocenteredtilde: 0x019F,
+ Ocircle: 0x24C4,
+ Ocircumflex: 0x00D4,
+ Ocircumflexacute: 0x1ED0,
+ Ocircumflexdotbelow: 0x1ED8,
+ Ocircumflexgrave: 0x1ED2,
+ Ocircumflexhookabove: 0x1ED4,
+ Ocircumflexsmall: 0xF7F4,
+ Ocircumflextilde: 0x1ED6,
+ Ocyrillic: 0x041E,
+ Odblacute: 0x0150,
+ Odblgrave: 0x020C,
+ Odieresis: 0x00D6,
+ Odieresiscyrillic: 0x04E6,
+ Odieresissmall: 0xF7F6,
+ Odotbelow: 0x1ECC,
+ Ogoneksmall: 0xF6FB,
+ Ograve: 0x00D2,
+ Ogravesmall: 0xF7F2,
+ Oharmenian: 0x0555,
+ Ohm: 0x2126,
+ Ohookabove: 0x1ECE,
+ Ohorn: 0x01A0,
+ Ohornacute: 0x1EDA,
+ Ohorndotbelow: 0x1EE2,
+ Ohorngrave: 0x1EDC,
+ Ohornhookabove: 0x1EDE,
+ Ohorntilde: 0x1EE0,
+ Ohungarumlaut: 0x0150,
+ Oi: 0x01A2,
+ Oinvertedbreve: 0x020E,
+ Omacron: 0x014C,
+ Omacronacute: 0x1E52,
+ Omacrongrave: 0x1E50,
+ Omega: 0x2126,
+ Omegacyrillic: 0x0460,
+ Omegagreek: 0x03A9,
+ Omegaroundcyrillic: 0x047A,
+ Omegatitlocyrillic: 0x047C,
+ Omegatonos: 0x038F,
+ Omicron: 0x039F,
+ Omicrontonos: 0x038C,
+ Omonospace: 0xFF2F,
+ Oneroman: 0x2160,
+ Oogonek: 0x01EA,
+ Oogonekmacron: 0x01EC,
+ Oopen: 0x0186,
+ Oslash: 0x00D8,
+ Oslashacute: 0x01FE,
+ Oslashsmall: 0xF7F8,
+ Osmall: 0xF76F,
+ Ostrokeacute: 0x01FE,
+ Otcyrillic: 0x047E,
+ Otilde: 0x00D5,
+ Otildeacute: 0x1E4C,
+ Otildedieresis: 0x1E4E,
+ Otildesmall: 0xF7F5,
+ P: 0x0050,
+ Pacute: 0x1E54,
+ Pcircle: 0x24C5,
+ Pdotaccent: 0x1E56,
+ Pecyrillic: 0x041F,
+ Peharmenian: 0x054A,
+ Pemiddlehookcyrillic: 0x04A6,
+ Phi: 0x03A6,
+ Phook: 0x01A4,
+ Pi: 0x03A0,
+ Piwrarmenian: 0x0553,
+ Pmonospace: 0xFF30,
+ Psi: 0x03A8,
+ Psicyrillic: 0x0470,
+ Psmall: 0xF770,
+ Q: 0x0051,
+ Qcircle: 0x24C6,
+ Qmonospace: 0xFF31,
+ Qsmall: 0xF771,
+ R: 0x0052,
+ Raarmenian: 0x054C,
+ Racute: 0x0154,
+ Rcaron: 0x0158,
+ Rcedilla: 0x0156,
+ Rcircle: 0x24C7,
+ Rcommaaccent: 0x0156,
+ Rdblgrave: 0x0210,
+ Rdotaccent: 0x1E58,
+ Rdotbelow: 0x1E5A,
+ Rdotbelowmacron: 0x1E5C,
+ Reharmenian: 0x0550,
+ Rfraktur: 0x211C,
+ Rho: 0x03A1,
+ Ringsmall: 0xF6FC,
+ Rinvertedbreve: 0x0212,
+ Rlinebelow: 0x1E5E,
+ Rmonospace: 0xFF32,
+ Rsmall: 0xF772,
+ Rsmallinverted: 0x0281,
+ Rsmallinvertedsuperior: 0x02B6,
+ S: 0x0053,
+ SF010000: 0x250C,
+ SF020000: 0x2514,
+ SF030000: 0x2510,
+ SF040000: 0x2518,
+ SF050000: 0x253C,
+ SF060000: 0x252C,
+ SF070000: 0x2534,
+ SF080000: 0x251C,
+ SF090000: 0x2524,
+ SF100000: 0x2500,
+ SF110000: 0x2502,
+ SF190000: 0x2561,
+ SF200000: 0x2562,
+ SF210000: 0x2556,
+ SF220000: 0x2555,
+ SF230000: 0x2563,
+ SF240000: 0x2551,
+ SF250000: 0x2557,
+ SF260000: 0x255D,
+ SF270000: 0x255C,
+ SF280000: 0x255B,
+ SF360000: 0x255E,
+ SF370000: 0x255F,
+ SF380000: 0x255A,
+ SF390000: 0x2554,
+ SF400000: 0x2569,
+ SF410000: 0x2566,
+ SF420000: 0x2560,
+ SF430000: 0x2550,
+ SF440000: 0x256C,
+ SF450000: 0x2567,
+ SF460000: 0x2568,
+ SF470000: 0x2564,
+ SF480000: 0x2565,
+ SF490000: 0x2559,
+ SF500000: 0x2558,
+ SF510000: 0x2552,
+ SF520000: 0x2553,
+ SF530000: 0x256B,
+ SF540000: 0x256A,
+ Sacute: 0x015A,
+ Sacutedotaccent: 0x1E64,
+ Sampigreek: 0x03E0,
+ Scaron: 0x0160,
+ Scarondotaccent: 0x1E66,
+ Scaronsmall: 0xF6FD,
+ Scedilla: 0x015E,
+ Schwa: 0x018F,
+ Schwacyrillic: 0x04D8,
+ Schwadieresiscyrillic: 0x04DA,
+ Scircle: 0x24C8,
+ Scircumflex: 0x015C,
+ Scommaaccent: 0x0218,
+ Sdotaccent: 0x1E60,
+ Sdotbelow: 0x1E62,
+ Sdotbelowdotaccent: 0x1E68,
+ Seharmenian: 0x054D,
+ Sevenroman: 0x2166,
+ Shaarmenian: 0x0547,
+ Shacyrillic: 0x0428,
+ Shchacyrillic: 0x0429,
+ Sheicoptic: 0x03E2,
+ Shhacyrillic: 0x04BA,
+ Shimacoptic: 0x03EC,
+ Sigma: 0x03A3,
+ Sixroman: 0x2165,
+ Smonospace: 0xFF33,
+ Softsigncyrillic: 0x042C,
+ Ssmall: 0xF773,
+ Stigmagreek: 0x03DA,
+ T: 0x0054,
+ Tau: 0x03A4,
+ Tbar: 0x0166,
+ Tcaron: 0x0164,
+ Tcedilla: 0x0162,
+ Tcircle: 0x24C9,
+ Tcircumflexbelow: 0x1E70,
+ Tcommaaccent: 0x0162,
+ Tdotaccent: 0x1E6A,
+ Tdotbelow: 0x1E6C,
+ Tecyrillic: 0x0422,
+ Tedescendercyrillic: 0x04AC,
+ Tenroman: 0x2169,
+ Tetsecyrillic: 0x04B4,
+ Theta: 0x0398,
+ Thook: 0x01AC,
+ Thorn: 0x00DE,
+ Thornsmall: 0xF7FE,
+ Threeroman: 0x2162,
+ Tildesmall: 0xF6FE,
+ Tiwnarmenian: 0x054F,
+ Tlinebelow: 0x1E6E,
+ Tmonospace: 0xFF34,
+ Toarmenian: 0x0539,
+ Tonefive: 0x01BC,
+ Tonesix: 0x0184,
+ Tonetwo: 0x01A7,
+ Tretroflexhook: 0x01AE,
+ Tsecyrillic: 0x0426,
+ Tshecyrillic: 0x040B,
+ Tsmall: 0xF774,
+ Twelveroman: 0x216B,
+ Tworoman: 0x2161,
+ U: 0x0055,
+ Uacute: 0x00DA,
+ Uacutesmall: 0xF7FA,
+ Ubreve: 0x016C,
+ Ucaron: 0x01D3,
+ Ucircle: 0x24CA,
+ Ucircumflex: 0x00DB,
+ Ucircumflexbelow: 0x1E76,
+ Ucircumflexsmall: 0xF7FB,
+ Ucyrillic: 0x0423,
+ Udblacute: 0x0170,
+ Udblgrave: 0x0214,
+ Udieresis: 0x00DC,
+ Udieresisacute: 0x01D7,
+ Udieresisbelow: 0x1E72,
+ Udieresiscaron: 0x01D9,
+ Udieresiscyrillic: 0x04F0,
+ Udieresisgrave: 0x01DB,
+ Udieresismacron: 0x01D5,
+ Udieresissmall: 0xF7FC,
+ Udotbelow: 0x1EE4,
+ Ugrave: 0x00D9,
+ Ugravesmall: 0xF7F9,
+ Uhookabove: 0x1EE6,
+ Uhorn: 0x01AF,
+ Uhornacute: 0x1EE8,
+ Uhorndotbelow: 0x1EF0,
+ Uhorngrave: 0x1EEA,
+ Uhornhookabove: 0x1EEC,
+ Uhorntilde: 0x1EEE,
+ Uhungarumlaut: 0x0170,
+ Uhungarumlautcyrillic: 0x04F2,
+ Uinvertedbreve: 0x0216,
+ Ukcyrillic: 0x0478,
+ Umacron: 0x016A,
+ Umacroncyrillic: 0x04EE,
+ Umacrondieresis: 0x1E7A,
+ Umonospace: 0xFF35,
+ Uogonek: 0x0172,
+ Upsilon: 0x03A5,
+ Upsilon1: 0x03D2,
+ Upsilonacutehooksymbolgreek: 0x03D3,
+ Upsilonafrican: 0x01B1,
+ Upsilondieresis: 0x03AB,
+ Upsilondieresishooksymbolgreek: 0x03D4,
+ Upsilonhooksymbol: 0x03D2,
+ Upsilontonos: 0x038E,
+ Uring: 0x016E,
+ Ushortcyrillic: 0x040E,
+ Usmall: 0xF775,
+ Ustraightcyrillic: 0x04AE,
+ Ustraightstrokecyrillic: 0x04B0,
+ Utilde: 0x0168,
+ Utildeacute: 0x1E78,
+ Utildebelow: 0x1E74,
+ V: 0x0056,
+ Vcircle: 0x24CB,
+ Vdotbelow: 0x1E7E,
+ Vecyrillic: 0x0412,
+ Vewarmenian: 0x054E,
+ Vhook: 0x01B2,
+ Vmonospace: 0xFF36,
+ Voarmenian: 0x0548,
+ Vsmall: 0xF776,
+ Vtilde: 0x1E7C,
+ W: 0x0057,
+ Wacute: 0x1E82,
+ Wcircle: 0x24CC,
+ Wcircumflex: 0x0174,
+ Wdieresis: 0x1E84,
+ Wdotaccent: 0x1E86,
+ Wdotbelow: 0x1E88,
+ Wgrave: 0x1E80,
+ Wmonospace: 0xFF37,
+ Wsmall: 0xF777,
+ X: 0x0058,
+ Xcircle: 0x24CD,
+ Xdieresis: 0x1E8C,
+ Xdotaccent: 0x1E8A,
+ Xeharmenian: 0x053D,
+ Xi: 0x039E,
+ Xmonospace: 0xFF38,
+ Xsmall: 0xF778,
+ Y: 0x0059,
+ Yacute: 0x00DD,
+ Yacutesmall: 0xF7FD,
+ Yatcyrillic: 0x0462,
+ Ycircle: 0x24CE,
+ Ycircumflex: 0x0176,
+ Ydieresis: 0x0178,
+ Ydieresissmall: 0xF7FF,
+ Ydotaccent: 0x1E8E,
+ Ydotbelow: 0x1EF4,
+ Yericyrillic: 0x042B,
+ Yerudieresiscyrillic: 0x04F8,
+ Ygrave: 0x1EF2,
+ Yhook: 0x01B3,
+ Yhookabove: 0x1EF6,
+ Yiarmenian: 0x0545,
+ Yicyrillic: 0x0407,
+ Yiwnarmenian: 0x0552,
+ Ymonospace: 0xFF39,
+ Ysmall: 0xF779,
+ Ytilde: 0x1EF8,
+ Yusbigcyrillic: 0x046A,
+ Yusbigiotifiedcyrillic: 0x046C,
+ Yuslittlecyrillic: 0x0466,
+ Yuslittleiotifiedcyrillic: 0x0468,
+ Z: 0x005A,
+ Zaarmenian: 0x0536,
+ Zacute: 0x0179,
+ Zcaron: 0x017D,
+ Zcaronsmall: 0xF6FF,
+ Zcircle: 0x24CF,
+ Zcircumflex: 0x1E90,
+ Zdot: 0x017B,
+ Zdotaccent: 0x017B,
+ Zdotbelow: 0x1E92,
+ Zecyrillic: 0x0417,
+ Zedescendercyrillic: 0x0498,
+ Zedieresiscyrillic: 0x04DE,
+ Zeta: 0x0396,
+ Zhearmenian: 0x053A,
+ Zhebrevecyrillic: 0x04C1,
+ Zhecyrillic: 0x0416,
+ Zhedescendercyrillic: 0x0496,
+ Zhedieresiscyrillic: 0x04DC,
+ Zlinebelow: 0x1E94,
+ Zmonospace: 0xFF3A,
+ Zsmall: 0xF77A,
+ Zstroke: 0x01B5,
+ a: 0x0061,
+ aabengali: 0x0986,
+ aacute: 0x00E1,
+ aadeva: 0x0906,
+ aagujarati: 0x0A86,
+ aagurmukhi: 0x0A06,
+ aamatragurmukhi: 0x0A3E,
+ aarusquare: 0x3303,
+ aavowelsignbengali: 0x09BE,
+ aavowelsigndeva: 0x093E,
+ aavowelsigngujarati: 0x0ABE,
+ abbreviationmarkarmenian: 0x055F,
+ abbreviationsigndeva: 0x0970,
+ abengali: 0x0985,
+ abopomofo: 0x311A,
+ abreve: 0x0103,
+ abreveacute: 0x1EAF,
+ abrevecyrillic: 0x04D1,
+ abrevedotbelow: 0x1EB7,
+ abrevegrave: 0x1EB1,
+ abrevehookabove: 0x1EB3,
+ abrevetilde: 0x1EB5,
+ acaron: 0x01CE,
+ acircle: 0x24D0,
+ acircumflex: 0x00E2,
+ acircumflexacute: 0x1EA5,
+ acircumflexdotbelow: 0x1EAD,
+ acircumflexgrave: 0x1EA7,
+ acircumflexhookabove: 0x1EA9,
+ acircumflextilde: 0x1EAB,
+ acute: 0x00B4,
+ acutebelowcmb: 0x0317,
+ acutecmb: 0x0301,
+ acutecomb: 0x0301,
+ acutedeva: 0x0954,
+ acutelowmod: 0x02CF,
+ acutetonecmb: 0x0341,
+ acyrillic: 0x0430,
+ adblgrave: 0x0201,
+ addakgurmukhi: 0x0A71,
+ adeva: 0x0905,
+ adieresis: 0x00E4,
+ adieresiscyrillic: 0x04D3,
+ adieresismacron: 0x01DF,
+ adotbelow: 0x1EA1,
+ adotmacron: 0x01E1,
+ ae: 0x00E6,
+ aeacute: 0x01FD,
+ aekorean: 0x3150,
+ aemacron: 0x01E3,
+ afii00208: 0x2015,
+ afii08941: 0x20A4,
+ afii10017: 0x0410,
+ afii10018: 0x0411,
+ afii10019: 0x0412,
+ afii10020: 0x0413,
+ afii10021: 0x0414,
+ afii10022: 0x0415,
+ afii10023: 0x0401,
+ afii10024: 0x0416,
+ afii10025: 0x0417,
+ afii10026: 0x0418,
+ afii10027: 0x0419,
+ afii10028: 0x041A,
+ afii10029: 0x041B,
+ afii10030: 0x041C,
+ afii10031: 0x041D,
+ afii10032: 0x041E,
+ afii10033: 0x041F,
+ afii10034: 0x0420,
+ afii10035: 0x0421,
+ afii10036: 0x0422,
+ afii10037: 0x0423,
+ afii10038: 0x0424,
+ afii10039: 0x0425,
+ afii10040: 0x0426,
+ afii10041: 0x0427,
+ afii10042: 0x0428,
+ afii10043: 0x0429,
+ afii10044: 0x042A,
+ afii10045: 0x042B,
+ afii10046: 0x042C,
+ afii10047: 0x042D,
+ afii10048: 0x042E,
+ afii10049: 0x042F,
+ afii10050: 0x0490,
+ afii10051: 0x0402,
+ afii10052: 0x0403,
+ afii10053: 0x0404,
+ afii10054: 0x0405,
+ afii10055: 0x0406,
+ afii10056: 0x0407,
+ afii10057: 0x0408,
+ afii10058: 0x0409,
+ afii10059: 0x040A,
+ afii10060: 0x040B,
+ afii10061: 0x040C,
+ afii10062: 0x040E,
+ afii10063: 0xF6C4,
+ afii10064: 0xF6C5,
+ afii10065: 0x0430,
+ afii10066: 0x0431,
+ afii10067: 0x0432,
+ afii10068: 0x0433,
+ afii10069: 0x0434,
+ afii10070: 0x0435,
+ afii10071: 0x0451,
+ afii10072: 0x0436,
+ afii10073: 0x0437,
+ afii10074: 0x0438,
+ afii10075: 0x0439,
+ afii10076: 0x043A,
+ afii10077: 0x043B,
+ afii10078: 0x043C,
+ afii10079: 0x043D,
+ afii10080: 0x043E,
+ afii10081: 0x043F,
+ afii10082: 0x0440,
+ afii10083: 0x0441,
+ afii10084: 0x0442,
+ afii10085: 0x0443,
+ afii10086: 0x0444,
+ afii10087: 0x0445,
+ afii10088: 0x0446,
+ afii10089: 0x0447,
+ afii10090: 0x0448,
+ afii10091: 0x0449,
+ afii10092: 0x044A,
+ afii10093: 0x044B,
+ afii10094: 0x044C,
+ afii10095: 0x044D,
+ afii10096: 0x044E,
+ afii10097: 0x044F,
+ afii10098: 0x0491,
+ afii10099: 0x0452,
+ afii10100: 0x0453,
+ afii10101: 0x0454,
+ afii10102: 0x0455,
+ afii10103: 0x0456,
+ afii10104: 0x0457,
+ afii10105: 0x0458,
+ afii10106: 0x0459,
+ afii10107: 0x045A,
+ afii10108: 0x045B,
+ afii10109: 0x045C,
+ afii10110: 0x045E,
+ afii10145: 0x040F,
+ afii10146: 0x0462,
+ afii10147: 0x0472,
+ afii10148: 0x0474,
+ afii10192: 0xF6C6,
+ afii10193: 0x045F,
+ afii10194: 0x0463,
+ afii10195: 0x0473,
+ afii10196: 0x0475,
+ afii10831: 0xF6C7,
+ afii10832: 0xF6C8,
+ afii10846: 0x04D9,
+ afii299: 0x200E,
+ afii300: 0x200F,
+ afii301: 0x200D,
+ afii57381: 0x066A,
+ afii57388: 0x060C,
+ afii57392: 0x0660,
+ afii57393: 0x0661,
+ afii57394: 0x0662,
+ afii57395: 0x0663,
+ afii57396: 0x0664,
+ afii57397: 0x0665,
+ afii57398: 0x0666,
+ afii57399: 0x0667,
+ afii57400: 0x0668,
+ afii57401: 0x0669,
+ afii57403: 0x061B,
+ afii57407: 0x061F,
+ afii57409: 0x0621,
+ afii57410: 0x0622,
+ afii57411: 0x0623,
+ afii57412: 0x0624,
+ afii57413: 0x0625,
+ afii57414: 0x0626,
+ afii57415: 0x0627,
+ afii57416: 0x0628,
+ afii57417: 0x0629,
+ afii57418: 0x062A,
+ afii57419: 0x062B,
+ afii57420: 0x062C,
+ afii57421: 0x062D,
+ afii57422: 0x062E,
+ afii57423: 0x062F,
+ afii57424: 0x0630,
+ afii57425: 0x0631,
+ afii57426: 0x0632,
+ afii57427: 0x0633,
+ afii57428: 0x0634,
+ afii57429: 0x0635,
+ afii57430: 0x0636,
+ afii57431: 0x0637,
+ afii57432: 0x0638,
+ afii57433: 0x0639,
+ afii57434: 0x063A,
+ afii57440: 0x0640,
+ afii57441: 0x0641,
+ afii57442: 0x0642,
+ afii57443: 0x0643,
+ afii57444: 0x0644,
+ afii57445: 0x0645,
+ afii57446: 0x0646,
+ afii57448: 0x0648,
+ afii57449: 0x0649,
+ afii57450: 0x064A,
+ afii57451: 0x064B,
+ afii57452: 0x064C,
+ afii57453: 0x064D,
+ afii57454: 0x064E,
+ afii57455: 0x064F,
+ afii57456: 0x0650,
+ afii57457: 0x0651,
+ afii57458: 0x0652,
+ afii57470: 0x0647,
+ afii57505: 0x06A4,
+ afii57506: 0x067E,
+ afii57507: 0x0686,
+ afii57508: 0x0698,
+ afii57509: 0x06AF,
+ afii57511: 0x0679,
+ afii57512: 0x0688,
+ afii57513: 0x0691,
+ afii57514: 0x06BA,
+ afii57519: 0x06D2,
+ afii57534: 0x06D5,
+ afii57636: 0x20AA,
+ afii57645: 0x05BE,
+ afii57658: 0x05C3,
+ afii57664: 0x05D0,
+ afii57665: 0x05D1,
+ afii57666: 0x05D2,
+ afii57667: 0x05D3,
+ afii57668: 0x05D4,
+ afii57669: 0x05D5,
+ afii57670: 0x05D6,
+ afii57671: 0x05D7,
+ afii57672: 0x05D8,
+ afii57673: 0x05D9,
+ afii57674: 0x05DA,
+ afii57675: 0x05DB,
+ afii57676: 0x05DC,
+ afii57677: 0x05DD,
+ afii57678: 0x05DE,
+ afii57679: 0x05DF,
+ afii57680: 0x05E0,
+ afii57681: 0x05E1,
+ afii57682: 0x05E2,
+ afii57683: 0x05E3,
+ afii57684: 0x05E4,
+ afii57685: 0x05E5,
+ afii57686: 0x05E6,
+ afii57687: 0x05E7,
+ afii57688: 0x05E8,
+ afii57689: 0x05E9,
+ afii57690: 0x05EA,
+ afii57694: 0xFB2A,
+ afii57695: 0xFB2B,
+ afii57700: 0xFB4B,
+ afii57705: 0xFB1F,
+ afii57716: 0x05F0,
+ afii57717: 0x05F1,
+ afii57718: 0x05F2,
+ afii57723: 0xFB35,
+ afii57793: 0x05B4,
+ afii57794: 0x05B5,
+ afii57795: 0x05B6,
+ afii57796: 0x05BB,
+ afii57797: 0x05B8,
+ afii57798: 0x05B7,
+ afii57799: 0x05B0,
+ afii57800: 0x05B2,
+ afii57801: 0x05B1,
+ afii57802: 0x05B3,
+ afii57803: 0x05C2,
+ afii57804: 0x05C1,
+ afii57806: 0x05B9,
+ afii57807: 0x05BC,
+ afii57839: 0x05BD,
+ afii57841: 0x05BF,
+ afii57842: 0x05C0,
+ afii57929: 0x02BC,
+ afii61248: 0x2105,
+ afii61289: 0x2113,
+ afii61352: 0x2116,
+ afii61573: 0x202C,
+ afii61574: 0x202D,
+ afii61575: 0x202E,
+ afii61664: 0x200C,
+ afii63167: 0x066D,
+ afii64937: 0x02BD,
+ agrave: 0x00E0,
+ agujarati: 0x0A85,
+ agurmukhi: 0x0A05,
+ ahiragana: 0x3042,
+ ahookabove: 0x1EA3,
+ aibengali: 0x0990,
+ aibopomofo: 0x311E,
+ aideva: 0x0910,
+ aiecyrillic: 0x04D5,
+ aigujarati: 0x0A90,
+ aigurmukhi: 0x0A10,
+ aimatragurmukhi: 0x0A48,
+ ainarabic: 0x0639,
+ ainfinalarabic: 0xFECA,
+ aininitialarabic: 0xFECB,
+ ainmedialarabic: 0xFECC,
+ ainvertedbreve: 0x0203,
+ aivowelsignbengali: 0x09C8,
+ aivowelsigndeva: 0x0948,
+ aivowelsigngujarati: 0x0AC8,
+ akatakana: 0x30A2,
+ akatakanahalfwidth: 0xFF71,
+ akorean: 0x314F,
+ alef: 0x05D0,
+ alefarabic: 0x0627,
+ alefdageshhebrew: 0xFB30,
+ aleffinalarabic: 0xFE8E,
+ alefhamzaabovearabic: 0x0623,
+ alefhamzaabovefinalarabic: 0xFE84,
+ alefhamzabelowarabic: 0x0625,
+ alefhamzabelowfinalarabic: 0xFE88,
+ alefhebrew: 0x05D0,
+ aleflamedhebrew: 0xFB4F,
+ alefmaddaabovearabic: 0x0622,
+ alefmaddaabovefinalarabic: 0xFE82,
+ alefmaksuraarabic: 0x0649,
+ alefmaksurafinalarabic: 0xFEF0,
+ alefmaksurainitialarabic: 0xFEF3,
+ alefmaksuramedialarabic: 0xFEF4,
+ alefpatahhebrew: 0xFB2E,
+ alefqamatshebrew: 0xFB2F,
+ aleph: 0x2135,
+ allequal: 0x224C,
+ alpha: 0x03B1,
+ alphatonos: 0x03AC,
+ amacron: 0x0101,
+ amonospace: 0xFF41,
+ ampersand: 0x0026,
+ ampersandmonospace: 0xFF06,
+ ampersandsmall: 0xF726,
+ amsquare: 0x33C2,
+ anbopomofo: 0x3122,
+ angbopomofo: 0x3124,
+ angbracketleft: 0x3008, // This glyph is missing from Adobe's original list.
+ angbracketright: 0x3009, // This glyph is missing from Adobe's original list.
+ angkhankhuthai: 0x0E5A,
+ angle: 0x2220,
+ anglebracketleft: 0x3008,
+ anglebracketleftvertical: 0xFE3F,
+ anglebracketright: 0x3009,
+ anglebracketrightvertical: 0xFE40,
+ angleleft: 0x2329,
+ angleright: 0x232A,
+ angstrom: 0x212B,
+ anoteleia: 0x0387,
+ anudattadeva: 0x0952,
+ anusvarabengali: 0x0982,
+ anusvaradeva: 0x0902,
+ anusvaragujarati: 0x0A82,
+ aogonek: 0x0105,
+ apaatosquare: 0x3300,
+ aparen: 0x249C,
+ apostrophearmenian: 0x055A,
+ apostrophemod: 0x02BC,
+ apple: 0xF8FF,
+ approaches: 0x2250,
+ approxequal: 0x2248,
+ approxequalorimage: 0x2252,
+ approximatelyequal: 0x2245,
+ araeaekorean: 0x318E,
+ araeakorean: 0x318D,
+ arc: 0x2312,
+ arighthalfring: 0x1E9A,
+ aring: 0x00E5,
+ aringacute: 0x01FB,
+ aringbelow: 0x1E01,
+ arrowboth: 0x2194,
+ arrowdashdown: 0x21E3,
+ arrowdashleft: 0x21E0,
+ arrowdashright: 0x21E2,
+ arrowdashup: 0x21E1,
+ arrowdblboth: 0x21D4,
+ arrowdbldown: 0x21D3,
+ arrowdblleft: 0x21D0,
+ arrowdblright: 0x21D2,
+ arrowdblup: 0x21D1,
+ arrowdown: 0x2193,
+ arrowdownleft: 0x2199,
+ arrowdownright: 0x2198,
+ arrowdownwhite: 0x21E9,
+ arrowheaddownmod: 0x02C5,
+ arrowheadleftmod: 0x02C2,
+ arrowheadrightmod: 0x02C3,
+ arrowheadupmod: 0x02C4,
+ arrowhorizex: 0xF8E7,
+ arrowleft: 0x2190,
+ arrowleftdbl: 0x21D0,
+ arrowleftdblstroke: 0x21CD,
+ arrowleftoverright: 0x21C6,
+ arrowleftwhite: 0x21E6,
+ arrowright: 0x2192,
+ arrowrightdblstroke: 0x21CF,
+ arrowrightheavy: 0x279E,
+ arrowrightoverleft: 0x21C4,
+ arrowrightwhite: 0x21E8,
+ arrowtableft: 0x21E4,
+ arrowtabright: 0x21E5,
+ arrowup: 0x2191,
+ arrowupdn: 0x2195,
+ arrowupdnbse: 0x21A8,
+ arrowupdownbase: 0x21A8,
+ arrowupleft: 0x2196,
+ arrowupleftofdown: 0x21C5,
+ arrowupright: 0x2197,
+ arrowupwhite: 0x21E7,
+ arrowvertex: 0xF8E6,
+ asciicircum: 0x005E,
+ asciicircummonospace: 0xFF3E,
+ asciitilde: 0x007E,
+ asciitildemonospace: 0xFF5E,
+ ascript: 0x0251,
+ ascriptturned: 0x0252,
+ asmallhiragana: 0x3041,
+ asmallkatakana: 0x30A1,
+ asmallkatakanahalfwidth: 0xFF67,
+ asterisk: 0x002A,
+ asteriskaltonearabic: 0x066D,
+ asteriskarabic: 0x066D,
+ asteriskmath: 0x2217,
+ asteriskmonospace: 0xFF0A,
+ asterisksmall: 0xFE61,
+ asterism: 0x2042,
+ asuperior: 0xF6E9,
+ asymptoticallyequal: 0x2243,
+ at: 0x0040,
+ atilde: 0x00E3,
+ atmonospace: 0xFF20,
+ atsmall: 0xFE6B,
+ aturned: 0x0250,
+ aubengali: 0x0994,
+ aubopomofo: 0x3120,
+ audeva: 0x0914,
+ augujarati: 0x0A94,
+ augurmukhi: 0x0A14,
+ aulengthmarkbengali: 0x09D7,
+ aumatragurmukhi: 0x0A4C,
+ auvowelsignbengali: 0x09CC,
+ auvowelsigndeva: 0x094C,
+ auvowelsigngujarati: 0x0ACC,
+ avagrahadeva: 0x093D,
+ aybarmenian: 0x0561,
+ ayin: 0x05E2,
+ ayinaltonehebrew: 0xFB20,
+ ayinhebrew: 0x05E2,
+ b: 0x0062,
+ babengali: 0x09AC,
+ backslash: 0x005C,
+ backslashmonospace: 0xFF3C,
+ badeva: 0x092C,
+ bagujarati: 0x0AAC,
+ bagurmukhi: 0x0A2C,
+ bahiragana: 0x3070,
+ bahtthai: 0x0E3F,
+ bakatakana: 0x30D0,
+ bar: 0x007C,
+ barmonospace: 0xFF5C,
+ bbopomofo: 0x3105,
+ bcircle: 0x24D1,
+ bdotaccent: 0x1E03,
+ bdotbelow: 0x1E05,
+ beamedsixteenthnotes: 0x266C,
+ because: 0x2235,
+ becyrillic: 0x0431,
+ beharabic: 0x0628,
+ behfinalarabic: 0xFE90,
+ behinitialarabic: 0xFE91,
+ behiragana: 0x3079,
+ behmedialarabic: 0xFE92,
+ behmeeminitialarabic: 0xFC9F,
+ behmeemisolatedarabic: 0xFC08,
+ behnoonfinalarabic: 0xFC6D,
+ bekatakana: 0x30D9,
+ benarmenian: 0x0562,
+ bet: 0x05D1,
+ beta: 0x03B2,
+ betasymbolgreek: 0x03D0,
+ betdagesh: 0xFB31,
+ betdageshhebrew: 0xFB31,
+ bethebrew: 0x05D1,
+ betrafehebrew: 0xFB4C,
+ bhabengali: 0x09AD,
+ bhadeva: 0x092D,
+ bhagujarati: 0x0AAD,
+ bhagurmukhi: 0x0A2D,
+ bhook: 0x0253,
+ bihiragana: 0x3073,
+ bikatakana: 0x30D3,
+ bilabialclick: 0x0298,
+ bindigurmukhi: 0x0A02,
+ birusquare: 0x3331,
+ blackcircle: 0x25CF,
+ blackdiamond: 0x25C6,
+ blackdownpointingtriangle: 0x25BC,
+ blackleftpointingpointer: 0x25C4,
+ blackleftpointingtriangle: 0x25C0,
+ blacklenticularbracketleft: 0x3010,
+ blacklenticularbracketleftvertical: 0xFE3B,
+ blacklenticularbracketright: 0x3011,
+ blacklenticularbracketrightvertical: 0xFE3C,
+ blacklowerlefttriangle: 0x25E3,
+ blacklowerrighttriangle: 0x25E2,
+ blackrectangle: 0x25AC,
+ blackrightpointingpointer: 0x25BA,
+ blackrightpointingtriangle: 0x25B6,
+ blacksmallsquare: 0x25AA,
+ blacksmilingface: 0x263B,
+ blacksquare: 0x25A0,
+ blackstar: 0x2605,
+ blackupperlefttriangle: 0x25E4,
+ blackupperrighttriangle: 0x25E5,
+ blackuppointingsmalltriangle: 0x25B4,
+ blackuppointingtriangle: 0x25B2,
+ blank: 0x2423,
+ blinebelow: 0x1E07,
+ block: 0x2588,
+ bmonospace: 0xFF42,
+ bobaimaithai: 0x0E1A,
+ bohiragana: 0x307C,
+ bokatakana: 0x30DC,
+ bparen: 0x249D,
+ bqsquare: 0x33C3,
+ braceex: 0xF8F4,
+ braceleft: 0x007B,
+ braceleftbt: 0xF8F3,
+ braceleftmid: 0xF8F2,
+ braceleftmonospace: 0xFF5B,
+ braceleftsmall: 0xFE5B,
+ bracelefttp: 0xF8F1,
+ braceleftvertical: 0xFE37,
+ braceright: 0x007D,
+ bracerightbt: 0xF8FE,
+ bracerightmid: 0xF8FD,
+ bracerightmonospace: 0xFF5D,
+ bracerightsmall: 0xFE5C,
+ bracerighttp: 0xF8FC,
+ bracerightvertical: 0xFE38,
+ bracketleft: 0x005B,
+ bracketleftbt: 0xF8F0,
+ bracketleftex: 0xF8EF,
+ bracketleftmonospace: 0xFF3B,
+ bracketlefttp: 0xF8EE,
+ bracketright: 0x005D,
+ bracketrightbt: 0xF8FB,
+ bracketrightex: 0xF8FA,
+ bracketrightmonospace: 0xFF3D,
+ bracketrighttp: 0xF8F9,
+ breve: 0x02D8,
+ brevebelowcmb: 0x032E,
+ brevecmb: 0x0306,
+ breveinvertedbelowcmb: 0x032F,
+ breveinvertedcmb: 0x0311,
+ breveinverteddoublecmb: 0x0361,
+ bridgebelowcmb: 0x032A,
+ bridgeinvertedbelowcmb: 0x033A,
+ brokenbar: 0x00A6,
+ bstroke: 0x0180,
+ bsuperior: 0xF6EA,
+ btopbar: 0x0183,
+ buhiragana: 0x3076,
+ bukatakana: 0x30D6,
+ bullet: 0x2022,
+ bulletinverse: 0x25D8,
+ bulletoperator: 0x2219,
+ bullseye: 0x25CE,
+ c: 0x0063,
+ caarmenian: 0x056E,
+ cabengali: 0x099A,
+ cacute: 0x0107,
+ cadeva: 0x091A,
+ cagujarati: 0x0A9A,
+ cagurmukhi: 0x0A1A,
+ calsquare: 0x3388,
+ candrabindubengali: 0x0981,
+ candrabinducmb: 0x0310,
+ candrabindudeva: 0x0901,
+ candrabindugujarati: 0x0A81,
+ capslock: 0x21EA,
+ careof: 0x2105,
+ caron: 0x02C7,
+ caronbelowcmb: 0x032C,
+ caroncmb: 0x030C,
+ carriagereturn: 0x21B5,
+ cbopomofo: 0x3118,
+ ccaron: 0x010D,
+ ccedilla: 0x00E7,
+ ccedillaacute: 0x1E09,
+ ccircle: 0x24D2,
+ ccircumflex: 0x0109,
+ ccurl: 0x0255,
+ cdot: 0x010B,
+ cdotaccent: 0x010B,
+ cdsquare: 0x33C5,
+ cedilla: 0x00B8,
+ cedillacmb: 0x0327,
+ cent: 0x00A2,
+ centigrade: 0x2103,
+ centinferior: 0xF6DF,
+ centmonospace: 0xFFE0,
+ centoldstyle: 0xF7A2,
+ centsuperior: 0xF6E0,
+ chaarmenian: 0x0579,
+ chabengali: 0x099B,
+ chadeva: 0x091B,
+ chagujarati: 0x0A9B,
+ chagurmukhi: 0x0A1B,
+ chbopomofo: 0x3114,
+ cheabkhasiancyrillic: 0x04BD,
+ checkmark: 0x2713,
+ checyrillic: 0x0447,
+ chedescenderabkhasiancyrillic: 0x04BF,
+ chedescendercyrillic: 0x04B7,
+ chedieresiscyrillic: 0x04F5,
+ cheharmenian: 0x0573,
+ chekhakassiancyrillic: 0x04CC,
+ cheverticalstrokecyrillic: 0x04B9,
+ chi: 0x03C7,
+ chieuchacirclekorean: 0x3277,
+ chieuchaparenkorean: 0x3217,
+ chieuchcirclekorean: 0x3269,
+ chieuchkorean: 0x314A,
+ chieuchparenkorean: 0x3209,
+ chochangthai: 0x0E0A,
+ chochanthai: 0x0E08,
+ chochingthai: 0x0E09,
+ chochoethai: 0x0E0C,
+ chook: 0x0188,
+ cieucacirclekorean: 0x3276,
+ cieucaparenkorean: 0x3216,
+ cieuccirclekorean: 0x3268,
+ cieuckorean: 0x3148,
+ cieucparenkorean: 0x3208,
+ cieucuparenkorean: 0x321C,
+ circle: 0x25CB,
+ circlecopyrt: 0x00A9, // This glyph is missing from Adobe's original list.
+ circlemultiply: 0x2297,
+ circleot: 0x2299,
+ circleplus: 0x2295,
+ circlepostalmark: 0x3036,
+ circlewithlefthalfblack: 0x25D0,
+ circlewithrighthalfblack: 0x25D1,
+ circumflex: 0x02C6,
+ circumflexbelowcmb: 0x032D,
+ circumflexcmb: 0x0302,
+ clear: 0x2327,
+ clickalveolar: 0x01C2,
+ clickdental: 0x01C0,
+ clicklateral: 0x01C1,
+ clickretroflex: 0x01C3,
+ club: 0x2663,
+ clubsuitblack: 0x2663,
+ clubsuitwhite: 0x2667,
+ cmcubedsquare: 0x33A4,
+ cmonospace: 0xFF43,
+ cmsquaredsquare: 0x33A0,
+ coarmenian: 0x0581,
+ colon: 0x003A,
+ colonmonetary: 0x20A1,
+ colonmonospace: 0xFF1A,
+ colonsign: 0x20A1,
+ colonsmall: 0xFE55,
+ colontriangularhalfmod: 0x02D1,
+ colontriangularmod: 0x02D0,
+ comma: 0x002C,
+ commaabovecmb: 0x0313,
+ commaaboverightcmb: 0x0315,
+ commaaccent: 0xF6C3,
+ commaarabic: 0x060C,
+ commaarmenian: 0x055D,
+ commainferior: 0xF6E1,
+ commamonospace: 0xFF0C,
+ commareversedabovecmb: 0x0314,
+ commareversedmod: 0x02BD,
+ commasmall: 0xFE50,
+ commasuperior: 0xF6E2,
+ commaturnedabovecmb: 0x0312,
+ commaturnedmod: 0x02BB,
+ compass: 0x263C,
+ congruent: 0x2245,
+ contourintegral: 0x222E,
+ control: 0x2303,
+ controlACK: 0x0006,
+ controlBEL: 0x0007,
+ controlBS: 0x0008,
+ controlCAN: 0x0018,
+ controlCR: 0x000D,
+ controlDC1: 0x0011,
+ controlDC2: 0x0012,
+ controlDC3: 0x0013,
+ controlDC4: 0x0014,
+ controlDEL: 0x007F,
+ controlDLE: 0x0010,
+ controlEM: 0x0019,
+ controlENQ: 0x0005,
+ controlEOT: 0x0004,
+ controlESC: 0x001B,
+ controlETB: 0x0017,
+ controlETX: 0x0003,
+ controlFF: 0x000C,
+ controlFS: 0x001C,
+ controlGS: 0x001D,
+ controlHT: 0x0009,
+ controlLF: 0x000A,
+ controlNAK: 0x0015,
+ controlRS: 0x001E,
+ controlSI: 0x000F,
+ controlSO: 0x000E,
+ controlSOT: 0x0002,
+ controlSTX: 0x0001,
+ controlSUB: 0x001A,
+ controlSYN: 0x0016,
+ controlUS: 0x001F,
+ controlVT: 0x000B,
+ copyright: 0x00A9,
+ copyrightsans: 0xF8E9,
+ copyrightserif: 0xF6D9,
+ cornerbracketleft: 0x300C,
+ cornerbracketlefthalfwidth: 0xFF62,
+ cornerbracketleftvertical: 0xFE41,
+ cornerbracketright: 0x300D,
+ cornerbracketrighthalfwidth: 0xFF63,
+ cornerbracketrightvertical: 0xFE42,
+ corporationsquare: 0x337F,
+ cosquare: 0x33C7,
+ coverkgsquare: 0x33C6,
+ cparen: 0x249E,
+ cruzeiro: 0x20A2,
+ cstretched: 0x0297,
+ curlyand: 0x22CF,
+ curlyor: 0x22CE,
+ currency: 0x00A4,
+ cyrBreve: 0xF6D1,
+ cyrFlex: 0xF6D2,
+ cyrbreve: 0xF6D4,
+ cyrflex: 0xF6D5,
+ d: 0x0064,
+ daarmenian: 0x0564,
+ dabengali: 0x09A6,
+ dadarabic: 0x0636,
+ dadeva: 0x0926,
+ dadfinalarabic: 0xFEBE,
+ dadinitialarabic: 0xFEBF,
+ dadmedialarabic: 0xFEC0,
+ dagesh: 0x05BC,
+ dageshhebrew: 0x05BC,
+ dagger: 0x2020,
+ daggerdbl: 0x2021,
+ dagujarati: 0x0AA6,
+ dagurmukhi: 0x0A26,
+ dahiragana: 0x3060,
+ dakatakana: 0x30C0,
+ dalarabic: 0x062F,
+ dalet: 0x05D3,
+ daletdagesh: 0xFB33,
+ daletdageshhebrew: 0xFB33,
+ dalethebrew: 0x05D3,
+ dalfinalarabic: 0xFEAA,
+ dammaarabic: 0x064F,
+ dammalowarabic: 0x064F,
+ dammatanaltonearabic: 0x064C,
+ dammatanarabic: 0x064C,
+ danda: 0x0964,
+ dargahebrew: 0x05A7,
+ dargalefthebrew: 0x05A7,
+ dasiapneumatacyrilliccmb: 0x0485,
+ dblGrave: 0xF6D3,
+ dblanglebracketleft: 0x300A,
+ dblanglebracketleftvertical: 0xFE3D,
+ dblanglebracketright: 0x300B,
+ dblanglebracketrightvertical: 0xFE3E,
+ dblarchinvertedbelowcmb: 0x032B,
+ dblarrowleft: 0x21D4,
+ dblarrowright: 0x21D2,
+ dbldanda: 0x0965,
+ dblgrave: 0xF6D6,
+ dblgravecmb: 0x030F,
+ dblintegral: 0x222C,
+ dbllowline: 0x2017,
+ dbllowlinecmb: 0x0333,
+ dbloverlinecmb: 0x033F,
+ dblprimemod: 0x02BA,
+ dblverticalbar: 0x2016,
+ dblverticallineabovecmb: 0x030E,
+ dbopomofo: 0x3109,
+ dbsquare: 0x33C8,
+ dcaron: 0x010F,
+ dcedilla: 0x1E11,
+ dcircle: 0x24D3,
+ dcircumflexbelow: 0x1E13,
+ dcroat: 0x0111,
+ ddabengali: 0x09A1,
+ ddadeva: 0x0921,
+ ddagujarati: 0x0AA1,
+ ddagurmukhi: 0x0A21,
+ ddalarabic: 0x0688,
+ ddalfinalarabic: 0xFB89,
+ dddhadeva: 0x095C,
+ ddhabengali: 0x09A2,
+ ddhadeva: 0x0922,
+ ddhagujarati: 0x0AA2,
+ ddhagurmukhi: 0x0A22,
+ ddotaccent: 0x1E0B,
+ ddotbelow: 0x1E0D,
+ decimalseparatorarabic: 0x066B,
+ decimalseparatorpersian: 0x066B,
+ decyrillic: 0x0434,
+ degree: 0x00B0,
+ dehihebrew: 0x05AD,
+ dehiragana: 0x3067,
+ deicoptic: 0x03EF,
+ dekatakana: 0x30C7,
+ deleteleft: 0x232B,
+ deleteright: 0x2326,
+ delta: 0x03B4,
+ deltaturned: 0x018D,
+ denominatorminusonenumeratorbengali: 0x09F8,
+ dezh: 0x02A4,
+ dhabengali: 0x09A7,
+ dhadeva: 0x0927,
+ dhagujarati: 0x0AA7,
+ dhagurmukhi: 0x0A27,
+ dhook: 0x0257,
+ dialytikatonos: 0x0385,
+ dialytikatonoscmb: 0x0344,
+ diamond: 0x2666,
+ diamondsuitwhite: 0x2662,
+ dieresis: 0x00A8,
+ dieresisacute: 0xF6D7,
+ dieresisbelowcmb: 0x0324,
+ dieresiscmb: 0x0308,
+ dieresisgrave: 0xF6D8,
+ dieresistonos: 0x0385,
+ dihiragana: 0x3062,
+ dikatakana: 0x30C2,
+ dittomark: 0x3003,
+ divide: 0x00F7,
+ divides: 0x2223,
+ divisionslash: 0x2215,
+ djecyrillic: 0x0452,
+ dkshade: 0x2593,
+ dlinebelow: 0x1E0F,
+ dlsquare: 0x3397,
+ dmacron: 0x0111,
+ dmonospace: 0xFF44,
+ dnblock: 0x2584,
+ dochadathai: 0x0E0E,
+ dodekthai: 0x0E14,
+ dohiragana: 0x3069,
+ dokatakana: 0x30C9,
+ dollar: 0x0024,
+ dollarinferior: 0xF6E3,
+ dollarmonospace: 0xFF04,
+ dollaroldstyle: 0xF724,
+ dollarsmall: 0xFE69,
+ dollarsuperior: 0xF6E4,
+ dong: 0x20AB,
+ dorusquare: 0x3326,
+ dotaccent: 0x02D9,
+ dotaccentcmb: 0x0307,
+ dotbelowcmb: 0x0323,
+ dotbelowcomb: 0x0323,
+ dotkatakana: 0x30FB,
+ dotlessi: 0x0131,
+ dotlessj: 0xF6BE,
+ dotlessjstrokehook: 0x0284,
+ dotmath: 0x22C5,
+ dottedcircle: 0x25CC,
+ doubleyodpatah: 0xFB1F,
+ doubleyodpatahhebrew: 0xFB1F,
+ downtackbelowcmb: 0x031E,
+ downtackmod: 0x02D5,
+ dparen: 0x249F,
+ dsuperior: 0xF6EB,
+ dtail: 0x0256,
+ dtopbar: 0x018C,
+ duhiragana: 0x3065,
+ dukatakana: 0x30C5,
+ dz: 0x01F3,
+ dzaltone: 0x02A3,
+ dzcaron: 0x01C6,
+ dzcurl: 0x02A5,
+ dzeabkhasiancyrillic: 0x04E1,
+ dzecyrillic: 0x0455,
+ dzhecyrillic: 0x045F,
+ e: 0x0065,
+ eacute: 0x00E9,
+ earth: 0x2641,
+ ebengali: 0x098F,
+ ebopomofo: 0x311C,
+ ebreve: 0x0115,
+ ecandradeva: 0x090D,
+ ecandragujarati: 0x0A8D,
+ ecandravowelsigndeva: 0x0945,
+ ecandravowelsigngujarati: 0x0AC5,
+ ecaron: 0x011B,
+ ecedillabreve: 0x1E1D,
+ echarmenian: 0x0565,
+ echyiwnarmenian: 0x0587,
+ ecircle: 0x24D4,
+ ecircumflex: 0x00EA,
+ ecircumflexacute: 0x1EBF,
+ ecircumflexbelow: 0x1E19,
+ ecircumflexdotbelow: 0x1EC7,
+ ecircumflexgrave: 0x1EC1,
+ ecircumflexhookabove: 0x1EC3,
+ ecircumflextilde: 0x1EC5,
+ ecyrillic: 0x0454,
+ edblgrave: 0x0205,
+ edeva: 0x090F,
+ edieresis: 0x00EB,
+ edot: 0x0117,
+ edotaccent: 0x0117,
+ edotbelow: 0x1EB9,
+ eegurmukhi: 0x0A0F,
+ eematragurmukhi: 0x0A47,
+ efcyrillic: 0x0444,
+ egrave: 0x00E8,
+ egujarati: 0x0A8F,
+ eharmenian: 0x0567,
+ ehbopomofo: 0x311D,
+ ehiragana: 0x3048,
+ ehookabove: 0x1EBB,
+ eibopomofo: 0x311F,
+ eight: 0x0038,
+ eightarabic: 0x0668,
+ eightbengali: 0x09EE,
+ eightcircle: 0x2467,
+ eightcircleinversesansserif: 0x2791,
+ eightdeva: 0x096E,
+ eighteencircle: 0x2471,
+ eighteenparen: 0x2485,
+ eighteenperiod: 0x2499,
+ eightgujarati: 0x0AEE,
+ eightgurmukhi: 0x0A6E,
+ eighthackarabic: 0x0668,
+ eighthangzhou: 0x3028,
+ eighthnotebeamed: 0x266B,
+ eightideographicparen: 0x3227,
+ eightinferior: 0x2088,
+ eightmonospace: 0xFF18,
+ eightoldstyle: 0xF738,
+ eightparen: 0x247B,
+ eightperiod: 0x248F,
+ eightpersian: 0x06F8,
+ eightroman: 0x2177,
+ eightsuperior: 0x2078,
+ eightthai: 0x0E58,
+ einvertedbreve: 0x0207,
+ eiotifiedcyrillic: 0x0465,
+ ekatakana: 0x30A8,
+ ekatakanahalfwidth: 0xFF74,
+ ekonkargurmukhi: 0x0A74,
+ ekorean: 0x3154,
+ elcyrillic: 0x043B,
+ element: 0x2208,
+ elevencircle: 0x246A,
+ elevenparen: 0x247E,
+ elevenperiod: 0x2492,
+ elevenroman: 0x217A,
+ ellipsis: 0x2026,
+ ellipsisvertical: 0x22EE,
+ emacron: 0x0113,
+ emacronacute: 0x1E17,
+ emacrongrave: 0x1E15,
+ emcyrillic: 0x043C,
+ emdash: 0x2014,
+ emdashvertical: 0xFE31,
+ emonospace: 0xFF45,
+ emphasismarkarmenian: 0x055B,
+ emptyset: 0x2205,
+ enbopomofo: 0x3123,
+ encyrillic: 0x043D,
+ endash: 0x2013,
+ endashvertical: 0xFE32,
+ endescendercyrillic: 0x04A3,
+ eng: 0x014B,
+ engbopomofo: 0x3125,
+ enghecyrillic: 0x04A5,
+ enhookcyrillic: 0x04C8,
+ enspace: 0x2002,
+ eogonek: 0x0119,
+ eokorean: 0x3153,
+ eopen: 0x025B,
+ eopenclosed: 0x029A,
+ eopenreversed: 0x025C,
+ eopenreversedclosed: 0x025E,
+ eopenreversedhook: 0x025D,
+ eparen: 0x24A0,
+ epsilon: 0x03B5,
+ epsilontonos: 0x03AD,
+ equal: 0x003D,
+ equalmonospace: 0xFF1D,
+ equalsmall: 0xFE66,
+ equalsuperior: 0x207C,
+ equivalence: 0x2261,
+ erbopomofo: 0x3126,
+ ercyrillic: 0x0440,
+ ereversed: 0x0258,
+ ereversedcyrillic: 0x044D,
+ escyrillic: 0x0441,
+ esdescendercyrillic: 0x04AB,
+ esh: 0x0283,
+ eshcurl: 0x0286,
+ eshortdeva: 0x090E,
+ eshortvowelsigndeva: 0x0946,
+ eshreversedloop: 0x01AA,
+ eshsquatreversed: 0x0285,
+ esmallhiragana: 0x3047,
+ esmallkatakana: 0x30A7,
+ esmallkatakanahalfwidth: 0xFF6A,
+ estimated: 0x212E,
+ esuperior: 0xF6EC,
+ eta: 0x03B7,
+ etarmenian: 0x0568,
+ etatonos: 0x03AE,
+ eth: 0x00F0,
+ etilde: 0x1EBD,
+ etildebelow: 0x1E1B,
+ etnahtafoukhhebrew: 0x0591,
+ etnahtafoukhlefthebrew: 0x0591,
+ etnahtahebrew: 0x0591,
+ etnahtalefthebrew: 0x0591,
+ eturned: 0x01DD,
+ eukorean: 0x3161,
+ euro: 0x20AC,
+ evowelsignbengali: 0x09C7,
+ evowelsigndeva: 0x0947,
+ evowelsigngujarati: 0x0AC7,
+ exclam: 0x0021,
+ exclamarmenian: 0x055C,
+ exclamdbl: 0x203C,
+ exclamdown: 0x00A1,
+ exclamdownsmall: 0xF7A1,
+ exclammonospace: 0xFF01,
+ exclamsmall: 0xF721,
+ existential: 0x2203,
+ ezh: 0x0292,
+ ezhcaron: 0x01EF,
+ ezhcurl: 0x0293,
+ ezhreversed: 0x01B9,
+ ezhtail: 0x01BA,
+ f: 0x0066,
+ fadeva: 0x095E,
+ fagurmukhi: 0x0A5E,
+ fahrenheit: 0x2109,
+ fathaarabic: 0x064E,
+ fathalowarabic: 0x064E,
+ fathatanarabic: 0x064B,
+ fbopomofo: 0x3108,
+ fcircle: 0x24D5,
+ fdotaccent: 0x1E1F,
+ feharabic: 0x0641,
+ feharmenian: 0x0586,
+ fehfinalarabic: 0xFED2,
+ fehinitialarabic: 0xFED3,
+ fehmedialarabic: 0xFED4,
+ feicoptic: 0x03E5,
+ female: 0x2640,
+ ff: 0xFB00,
+ ffi: 0xFB03,
+ ffl: 0xFB04,
+ fi: 0xFB01,
+ fifteencircle: 0x246E,
+ fifteenparen: 0x2482,
+ fifteenperiod: 0x2496,
+ figuredash: 0x2012,
+ filledbox: 0x25A0,
+ filledrect: 0x25AC,
+ finalkaf: 0x05DA,
+ finalkafdagesh: 0xFB3A,
+ finalkafdageshhebrew: 0xFB3A,
+ finalkafhebrew: 0x05DA,
+ finalmem: 0x05DD,
+ finalmemhebrew: 0x05DD,
+ finalnun: 0x05DF,
+ finalnunhebrew: 0x05DF,
+ finalpe: 0x05E3,
+ finalpehebrew: 0x05E3,
+ finaltsadi: 0x05E5,
+ finaltsadihebrew: 0x05E5,
+ firsttonechinese: 0x02C9,
+ fisheye: 0x25C9,
+ fitacyrillic: 0x0473,
+ five: 0x0035,
+ fivearabic: 0x0665,
+ fivebengali: 0x09EB,
+ fivecircle: 0x2464,
+ fivecircleinversesansserif: 0x278E,
+ fivedeva: 0x096B,
+ fiveeighths: 0x215D,
+ fivegujarati: 0x0AEB,
+ fivegurmukhi: 0x0A6B,
+ fivehackarabic: 0x0665,
+ fivehangzhou: 0x3025,
+ fiveideographicparen: 0x3224,
+ fiveinferior: 0x2085,
+ fivemonospace: 0xFF15,
+ fiveoldstyle: 0xF735,
+ fiveparen: 0x2478,
+ fiveperiod: 0x248C,
+ fivepersian: 0x06F5,
+ fiveroman: 0x2174,
+ fivesuperior: 0x2075,
+ fivethai: 0x0E55,
+ fl: 0xFB02,
+ florin: 0x0192,
+ fmonospace: 0xFF46,
+ fmsquare: 0x3399,
+ fofanthai: 0x0E1F,
+ fofathai: 0x0E1D,
+ fongmanthai: 0x0E4F,
+ forall: 0x2200,
+ four: 0x0034,
+ fourarabic: 0x0664,
+ fourbengali: 0x09EA,
+ fourcircle: 0x2463,
+ fourcircleinversesansserif: 0x278D,
+ fourdeva: 0x096A,
+ fourgujarati: 0x0AEA,
+ fourgurmukhi: 0x0A6A,
+ fourhackarabic: 0x0664,
+ fourhangzhou: 0x3024,
+ fourideographicparen: 0x3223,
+ fourinferior: 0x2084,
+ fourmonospace: 0xFF14,
+ fournumeratorbengali: 0x09F7,
+ fouroldstyle: 0xF734,
+ fourparen: 0x2477,
+ fourperiod: 0x248B,
+ fourpersian: 0x06F4,
+ fourroman: 0x2173,
+ foursuperior: 0x2074,
+ fourteencircle: 0x246D,
+ fourteenparen: 0x2481,
+ fourteenperiod: 0x2495,
+ fourthai: 0x0E54,
+ fourthtonechinese: 0x02CB,
+ fparen: 0x24A1,
+ fraction: 0x2044,
+ franc: 0x20A3,
+ g: 0x0067,
+ gabengali: 0x0997,
+ gacute: 0x01F5,
+ gadeva: 0x0917,
+ gafarabic: 0x06AF,
+ gaffinalarabic: 0xFB93,
+ gafinitialarabic: 0xFB94,
+ gafmedialarabic: 0xFB95,
+ gagujarati: 0x0A97,
+ gagurmukhi: 0x0A17,
+ gahiragana: 0x304C,
+ gakatakana: 0x30AC,
+ gamma: 0x03B3,
+ gammalatinsmall: 0x0263,
+ gammasuperior: 0x02E0,
+ gangiacoptic: 0x03EB,
+ gbopomofo: 0x310D,
+ gbreve: 0x011F,
+ gcaron: 0x01E7,
+ gcedilla: 0x0123,
+ gcircle: 0x24D6,
+ gcircumflex: 0x011D,
+ gcommaaccent: 0x0123,
+ gdot: 0x0121,
+ gdotaccent: 0x0121,
+ gecyrillic: 0x0433,
+ gehiragana: 0x3052,
+ gekatakana: 0x30B2,
+ geometricallyequal: 0x2251,
+ gereshaccenthebrew: 0x059C,
+ gereshhebrew: 0x05F3,
+ gereshmuqdamhebrew: 0x059D,
+ germandbls: 0x00DF,
+ gershayimaccenthebrew: 0x059E,
+ gershayimhebrew: 0x05F4,
+ getamark: 0x3013,
+ ghabengali: 0x0998,
+ ghadarmenian: 0x0572,
+ ghadeva: 0x0918,
+ ghagujarati: 0x0A98,
+ ghagurmukhi: 0x0A18,
+ ghainarabic: 0x063A,
+ ghainfinalarabic: 0xFECE,
+ ghaininitialarabic: 0xFECF,
+ ghainmedialarabic: 0xFED0,
+ ghemiddlehookcyrillic: 0x0495,
+ ghestrokecyrillic: 0x0493,
+ gheupturncyrillic: 0x0491,
+ ghhadeva: 0x095A,
+ ghhagurmukhi: 0x0A5A,
+ ghook: 0x0260,
+ ghzsquare: 0x3393,
+ gihiragana: 0x304E,
+ gikatakana: 0x30AE,
+ gimarmenian: 0x0563,
+ gimel: 0x05D2,
+ gimeldagesh: 0xFB32,
+ gimeldageshhebrew: 0xFB32,
+ gimelhebrew: 0x05D2,
+ gjecyrillic: 0x0453,
+ glottalinvertedstroke: 0x01BE,
+ glottalstop: 0x0294,
+ glottalstopinverted: 0x0296,
+ glottalstopmod: 0x02C0,
+ glottalstopreversed: 0x0295,
+ glottalstopreversedmod: 0x02C1,
+ glottalstopreversedsuperior: 0x02E4,
+ glottalstopstroke: 0x02A1,
+ glottalstopstrokereversed: 0x02A2,
+ gmacron: 0x1E21,
+ gmonospace: 0xFF47,
+ gohiragana: 0x3054,
+ gokatakana: 0x30B4,
+ gparen: 0x24A2,
+ gpasquare: 0x33AC,
+ gradient: 0x2207,
+ grave: 0x0060,
+ gravebelowcmb: 0x0316,
+ gravecmb: 0x0300,
+ gravecomb: 0x0300,
+ gravedeva: 0x0953,
+ gravelowmod: 0x02CE,
+ gravemonospace: 0xFF40,
+ gravetonecmb: 0x0340,
+ greater: 0x003E,
+ greaterequal: 0x2265,
+ greaterequalorless: 0x22DB,
+ greatermonospace: 0xFF1E,
+ greaterorequivalent: 0x2273,
+ greaterorless: 0x2277,
+ greateroverequal: 0x2267,
+ greatersmall: 0xFE65,
+ gscript: 0x0261,
+ gstroke: 0x01E5,
+ guhiragana: 0x3050,
+ guillemotleft: 0x00AB,
+ guillemotright: 0x00BB,
+ guilsinglleft: 0x2039,
+ guilsinglright: 0x203A,
+ gukatakana: 0x30B0,
+ guramusquare: 0x3318,
+ gysquare: 0x33C9,
+ h: 0x0068,
+ haabkhasiancyrillic: 0x04A9,
+ haaltonearabic: 0x06C1,
+ habengali: 0x09B9,
+ hadescendercyrillic: 0x04B3,
+ hadeva: 0x0939,
+ hagujarati: 0x0AB9,
+ hagurmukhi: 0x0A39,
+ haharabic: 0x062D,
+ hahfinalarabic: 0xFEA2,
+ hahinitialarabic: 0xFEA3,
+ hahiragana: 0x306F,
+ hahmedialarabic: 0xFEA4,
+ haitusquare: 0x332A,
+ hakatakana: 0x30CF,
+ hakatakanahalfwidth: 0xFF8A,
+ halantgurmukhi: 0x0A4D,
+ hamzaarabic: 0x0621,
+ hamzalowarabic: 0x0621,
+ hangulfiller: 0x3164,
+ hardsigncyrillic: 0x044A,
+ harpoonleftbarbup: 0x21BC,
+ harpoonrightbarbup: 0x21C0,
+ hasquare: 0x33CA,
+ hatafpatah: 0x05B2,
+ hatafpatah16: 0x05B2,
+ hatafpatah23: 0x05B2,
+ hatafpatah2f: 0x05B2,
+ hatafpatahhebrew: 0x05B2,
+ hatafpatahnarrowhebrew: 0x05B2,
+ hatafpatahquarterhebrew: 0x05B2,
+ hatafpatahwidehebrew: 0x05B2,
+ hatafqamats: 0x05B3,
+ hatafqamats1b: 0x05B3,
+ hatafqamats28: 0x05B3,
+ hatafqamats34: 0x05B3,
+ hatafqamatshebrew: 0x05B3,
+ hatafqamatsnarrowhebrew: 0x05B3,
+ hatafqamatsquarterhebrew: 0x05B3,
+ hatafqamatswidehebrew: 0x05B3,
+ hatafsegol: 0x05B1,
+ hatafsegol17: 0x05B1,
+ hatafsegol24: 0x05B1,
+ hatafsegol30: 0x05B1,
+ hatafsegolhebrew: 0x05B1,
+ hatafsegolnarrowhebrew: 0x05B1,
+ hatafsegolquarterhebrew: 0x05B1,
+ hatafsegolwidehebrew: 0x05B1,
+ hbar: 0x0127,
+ hbopomofo: 0x310F,
+ hbrevebelow: 0x1E2B,
+ hcedilla: 0x1E29,
+ hcircle: 0x24D7,
+ hcircumflex: 0x0125,
+ hdieresis: 0x1E27,
+ hdotaccent: 0x1E23,
+ hdotbelow: 0x1E25,
+ he: 0x05D4,
+ heart: 0x2665,
+ heartsuitblack: 0x2665,
+ heartsuitwhite: 0x2661,
+ hedagesh: 0xFB34,
+ hedageshhebrew: 0xFB34,
+ hehaltonearabic: 0x06C1,
+ heharabic: 0x0647,
+ hehebrew: 0x05D4,
+ hehfinalaltonearabic: 0xFBA7,
+ hehfinalalttwoarabic: 0xFEEA,
+ hehfinalarabic: 0xFEEA,
+ hehhamzaabovefinalarabic: 0xFBA5,
+ hehhamzaaboveisolatedarabic: 0xFBA4,
+ hehinitialaltonearabic: 0xFBA8,
+ hehinitialarabic: 0xFEEB,
+ hehiragana: 0x3078,
+ hehmedialaltonearabic: 0xFBA9,
+ hehmedialarabic: 0xFEEC,
+ heiseierasquare: 0x337B,
+ hekatakana: 0x30D8,
+ hekatakanahalfwidth: 0xFF8D,
+ hekutaarusquare: 0x3336,
+ henghook: 0x0267,
+ herutusquare: 0x3339,
+ het: 0x05D7,
+ hethebrew: 0x05D7,
+ hhook: 0x0266,
+ hhooksuperior: 0x02B1,
+ hieuhacirclekorean: 0x327B,
+ hieuhaparenkorean: 0x321B,
+ hieuhcirclekorean: 0x326D,
+ hieuhkorean: 0x314E,
+ hieuhparenkorean: 0x320D,
+ hihiragana: 0x3072,
+ hikatakana: 0x30D2,
+ hikatakanahalfwidth: 0xFF8B,
+ hiriq: 0x05B4,
+ hiriq14: 0x05B4,
+ hiriq21: 0x05B4,
+ hiriq2d: 0x05B4,
+ hiriqhebrew: 0x05B4,
+ hiriqnarrowhebrew: 0x05B4,
+ hiriqquarterhebrew: 0x05B4,
+ hiriqwidehebrew: 0x05B4,
+ hlinebelow: 0x1E96,
+ hmonospace: 0xFF48,
+ hoarmenian: 0x0570,
+ hohipthai: 0x0E2B,
+ hohiragana: 0x307B,
+ hokatakana: 0x30DB,
+ hokatakanahalfwidth: 0xFF8E,
+ holam: 0x05B9,
+ holam19: 0x05B9,
+ holam26: 0x05B9,
+ holam32: 0x05B9,
+ holamhebrew: 0x05B9,
+ holamnarrowhebrew: 0x05B9,
+ holamquarterhebrew: 0x05B9,
+ holamwidehebrew: 0x05B9,
+ honokhukthai: 0x0E2E,
+ hookabovecomb: 0x0309,
+ hookcmb: 0x0309,
+ hookpalatalizedbelowcmb: 0x0321,
+ hookretroflexbelowcmb: 0x0322,
+ hoonsquare: 0x3342,
+ horicoptic: 0x03E9,
+ horizontalbar: 0x2015,
+ horncmb: 0x031B,
+ hotsprings: 0x2668,
+ house: 0x2302,
+ hparen: 0x24A3,
+ hsuperior: 0x02B0,
+ hturned: 0x0265,
+ huhiragana: 0x3075,
+ huiitosquare: 0x3333,
+ hukatakana: 0x30D5,
+ hukatakanahalfwidth: 0xFF8C,
+ hungarumlaut: 0x02DD,
+ hungarumlautcmb: 0x030B,
+ hv: 0x0195,
+ hyphen: 0x002D,
+ hypheninferior: 0xF6E5,
+ hyphenmonospace: 0xFF0D,
+ hyphensmall: 0xFE63,
+ hyphensuperior: 0xF6E6,
+ hyphentwo: 0x2010,
+ i: 0x0069,
+ iacute: 0x00ED,
+ iacyrillic: 0x044F,
+ ibengali: 0x0987,
+ ibopomofo: 0x3127,
+ ibreve: 0x012D,
+ icaron: 0x01D0,
+ icircle: 0x24D8,
+ icircumflex: 0x00EE,
+ icyrillic: 0x0456,
+ idblgrave: 0x0209,
+ ideographearthcircle: 0x328F,
+ ideographfirecircle: 0x328B,
+ ideographicallianceparen: 0x323F,
+ ideographiccallparen: 0x323A,
+ ideographiccentrecircle: 0x32A5,
+ ideographicclose: 0x3006,
+ ideographiccomma: 0x3001,
+ ideographiccommaleft: 0xFF64,
+ ideographiccongratulationparen: 0x3237,
+ ideographiccorrectcircle: 0x32A3,
+ ideographicearthparen: 0x322F,
+ ideographicenterpriseparen: 0x323D,
+ ideographicexcellentcircle: 0x329D,
+ ideographicfestivalparen: 0x3240,
+ ideographicfinancialcircle: 0x3296,
+ ideographicfinancialparen: 0x3236,
+ ideographicfireparen: 0x322B,
+ ideographichaveparen: 0x3232,
+ ideographichighcircle: 0x32A4,
+ ideographiciterationmark: 0x3005,
+ ideographiclaborcircle: 0x3298,
+ ideographiclaborparen: 0x3238,
+ ideographicleftcircle: 0x32A7,
+ ideographiclowcircle: 0x32A6,
+ ideographicmedicinecircle: 0x32A9,
+ ideographicmetalparen: 0x322E,
+ ideographicmoonparen: 0x322A,
+ ideographicnameparen: 0x3234,
+ ideographicperiod: 0x3002,
+ ideographicprintcircle: 0x329E,
+ ideographicreachparen: 0x3243,
+ ideographicrepresentparen: 0x3239,
+ ideographicresourceparen: 0x323E,
+ ideographicrightcircle: 0x32A8,
+ ideographicsecretcircle: 0x3299,
+ ideographicselfparen: 0x3242,
+ ideographicsocietyparen: 0x3233,
+ ideographicspace: 0x3000,
+ ideographicspecialparen: 0x3235,
+ ideographicstockparen: 0x3231,
+ ideographicstudyparen: 0x323B,
+ ideographicsunparen: 0x3230,
+ ideographicsuperviseparen: 0x323C,
+ ideographicwaterparen: 0x322C,
+ ideographicwoodparen: 0x322D,
+ ideographiczero: 0x3007,
+ ideographmetalcircle: 0x328E,
+ ideographmooncircle: 0x328A,
+ ideographnamecircle: 0x3294,
+ ideographsuncircle: 0x3290,
+ ideographwatercircle: 0x328C,
+ ideographwoodcircle: 0x328D,
+ ideva: 0x0907,
+ idieresis: 0x00EF,
+ idieresisacute: 0x1E2F,
+ idieresiscyrillic: 0x04E5,
+ idotbelow: 0x1ECB,
+ iebrevecyrillic: 0x04D7,
+ iecyrillic: 0x0435,
+ ieungacirclekorean: 0x3275,
+ ieungaparenkorean: 0x3215,
+ ieungcirclekorean: 0x3267,
+ ieungkorean: 0x3147,
+ ieungparenkorean: 0x3207,
+ igrave: 0x00EC,
+ igujarati: 0x0A87,
+ igurmukhi: 0x0A07,
+ ihiragana: 0x3044,
+ ihookabove: 0x1EC9,
+ iibengali: 0x0988,
+ iicyrillic: 0x0438,
+ iideva: 0x0908,
+ iigujarati: 0x0A88,
+ iigurmukhi: 0x0A08,
+ iimatragurmukhi: 0x0A40,
+ iinvertedbreve: 0x020B,
+ iishortcyrillic: 0x0439,
+ iivowelsignbengali: 0x09C0,
+ iivowelsigndeva: 0x0940,
+ iivowelsigngujarati: 0x0AC0,
+ ij: 0x0133,
+ ikatakana: 0x30A4,
+ ikatakanahalfwidth: 0xFF72,
+ ikorean: 0x3163,
+ ilde: 0x02DC,
+ iluyhebrew: 0x05AC,
+ imacron: 0x012B,
+ imacroncyrillic: 0x04E3,
+ imageorapproximatelyequal: 0x2253,
+ imatragurmukhi: 0x0A3F,
+ imonospace: 0xFF49,
+ increment: 0x2206,
+ infinity: 0x221E,
+ iniarmenian: 0x056B,
+ integral: 0x222B,
+ integralbottom: 0x2321,
+ integralbt: 0x2321,
+ integralex: 0xF8F5,
+ integraltop: 0x2320,
+ integraltp: 0x2320,
+ intersection: 0x2229,
+ intisquare: 0x3305,
+ invbullet: 0x25D8,
+ invcircle: 0x25D9,
+ invsmileface: 0x263B,
+ iocyrillic: 0x0451,
+ iogonek: 0x012F,
+ iota: 0x03B9,
+ iotadieresis: 0x03CA,
+ iotadieresistonos: 0x0390,
+ iotalatin: 0x0269,
+ iotatonos: 0x03AF,
+ iparen: 0x24A4,
+ irigurmukhi: 0x0A72,
+ ismallhiragana: 0x3043,
+ ismallkatakana: 0x30A3,
+ ismallkatakanahalfwidth: 0xFF68,
+ issharbengali: 0x09FA,
+ istroke: 0x0268,
+ isuperior: 0xF6ED,
+ iterationhiragana: 0x309D,
+ iterationkatakana: 0x30FD,
+ itilde: 0x0129,
+ itildebelow: 0x1E2D,
+ iubopomofo: 0x3129,
+ iucyrillic: 0x044E,
+ ivowelsignbengali: 0x09BF,
+ ivowelsigndeva: 0x093F,
+ ivowelsigngujarati: 0x0ABF,
+ izhitsacyrillic: 0x0475,
+ izhitsadblgravecyrillic: 0x0477,
+ j: 0x006A,
+ jaarmenian: 0x0571,
+ jabengali: 0x099C,
+ jadeva: 0x091C,
+ jagujarati: 0x0A9C,
+ jagurmukhi: 0x0A1C,
+ jbopomofo: 0x3110,
+ jcaron: 0x01F0,
+ jcircle: 0x24D9,
+ jcircumflex: 0x0135,
+ jcrossedtail: 0x029D,
+ jdotlessstroke: 0x025F,
+ jecyrillic: 0x0458,
+ jeemarabic: 0x062C,
+ jeemfinalarabic: 0xFE9E,
+ jeeminitialarabic: 0xFE9F,
+ jeemmedialarabic: 0xFEA0,
+ jeharabic: 0x0698,
+ jehfinalarabic: 0xFB8B,
+ jhabengali: 0x099D,
+ jhadeva: 0x091D,
+ jhagujarati: 0x0A9D,
+ jhagurmukhi: 0x0A1D,
+ jheharmenian: 0x057B,
+ jis: 0x3004,
+ jmonospace: 0xFF4A,
+ jparen: 0x24A5,
+ jsuperior: 0x02B2,
+ k: 0x006B,
+ kabashkircyrillic: 0x04A1,
+ kabengali: 0x0995,
+ kacute: 0x1E31,
+ kacyrillic: 0x043A,
+ kadescendercyrillic: 0x049B,
+ kadeva: 0x0915,
+ kaf: 0x05DB,
+ kafarabic: 0x0643,
+ kafdagesh: 0xFB3B,
+ kafdageshhebrew: 0xFB3B,
+ kaffinalarabic: 0xFEDA,
+ kafhebrew: 0x05DB,
+ kafinitialarabic: 0xFEDB,
+ kafmedialarabic: 0xFEDC,
+ kafrafehebrew: 0xFB4D,
+ kagujarati: 0x0A95,
+ kagurmukhi: 0x0A15,
+ kahiragana: 0x304B,
+ kahookcyrillic: 0x04C4,
+ kakatakana: 0x30AB,
+ kakatakanahalfwidth: 0xFF76,
+ kappa: 0x03BA,
+ kappasymbolgreek: 0x03F0,
+ kapyeounmieumkorean: 0x3171,
+ kapyeounphieuphkorean: 0x3184,
+ kapyeounpieupkorean: 0x3178,
+ kapyeounssangpieupkorean: 0x3179,
+ karoriisquare: 0x330D,
+ kashidaautoarabic: 0x0640,
+ kashidaautonosidebearingarabic: 0x0640,
+ kasmallkatakana: 0x30F5,
+ kasquare: 0x3384,
+ kasraarabic: 0x0650,
+ kasratanarabic: 0x064D,
+ kastrokecyrillic: 0x049F,
+ katahiraprolongmarkhalfwidth: 0xFF70,
+ kaverticalstrokecyrillic: 0x049D,
+ kbopomofo: 0x310E,
+ kcalsquare: 0x3389,
+ kcaron: 0x01E9,
+ kcedilla: 0x0137,
+ kcircle: 0x24DA,
+ kcommaaccent: 0x0137,
+ kdotbelow: 0x1E33,
+ keharmenian: 0x0584,
+ kehiragana: 0x3051,
+ kekatakana: 0x30B1,
+ kekatakanahalfwidth: 0xFF79,
+ kenarmenian: 0x056F,
+ kesmallkatakana: 0x30F6,
+ kgreenlandic: 0x0138,
+ khabengali: 0x0996,
+ khacyrillic: 0x0445,
+ khadeva: 0x0916,
+ khagujarati: 0x0A96,
+ khagurmukhi: 0x0A16,
+ khaharabic: 0x062E,
+ khahfinalarabic: 0xFEA6,
+ khahinitialarabic: 0xFEA7,
+ khahmedialarabic: 0xFEA8,
+ kheicoptic: 0x03E7,
+ khhadeva: 0x0959,
+ khhagurmukhi: 0x0A59,
+ khieukhacirclekorean: 0x3278,
+ khieukhaparenkorean: 0x3218,
+ khieukhcirclekorean: 0x326A,
+ khieukhkorean: 0x314B,
+ khieukhparenkorean: 0x320A,
+ khokhaithai: 0x0E02,
+ khokhonthai: 0x0E05,
+ khokhuatthai: 0x0E03,
+ khokhwaithai: 0x0E04,
+ khomutthai: 0x0E5B,
+ khook: 0x0199,
+ khorakhangthai: 0x0E06,
+ khzsquare: 0x3391,
+ kihiragana: 0x304D,
+ kikatakana: 0x30AD,
+ kikatakanahalfwidth: 0xFF77,
+ kiroguramusquare: 0x3315,
+ kiromeetorusquare: 0x3316,
+ kirosquare: 0x3314,
+ kiyeokacirclekorean: 0x326E,
+ kiyeokaparenkorean: 0x320E,
+ kiyeokcirclekorean: 0x3260,
+ kiyeokkorean: 0x3131,
+ kiyeokparenkorean: 0x3200,
+ kiyeoksioskorean: 0x3133,
+ kjecyrillic: 0x045C,
+ klinebelow: 0x1E35,
+ klsquare: 0x3398,
+ kmcubedsquare: 0x33A6,
+ kmonospace: 0xFF4B,
+ kmsquaredsquare: 0x33A2,
+ kohiragana: 0x3053,
+ kohmsquare: 0x33C0,
+ kokaithai: 0x0E01,
+ kokatakana: 0x30B3,
+ kokatakanahalfwidth: 0xFF7A,
+ kooposquare: 0x331E,
+ koppacyrillic: 0x0481,
+ koreanstandardsymbol: 0x327F,
+ koroniscmb: 0x0343,
+ kparen: 0x24A6,
+ kpasquare: 0x33AA,
+ ksicyrillic: 0x046F,
+ ktsquare: 0x33CF,
+ kturned: 0x029E,
+ kuhiragana: 0x304F,
+ kukatakana: 0x30AF,
+ kukatakanahalfwidth: 0xFF78,
+ kvsquare: 0x33B8,
+ kwsquare: 0x33BE,
+ l: 0x006C,
+ labengali: 0x09B2,
+ lacute: 0x013A,
+ ladeva: 0x0932,
+ lagujarati: 0x0AB2,
+ lagurmukhi: 0x0A32,
+ lakkhangyaothai: 0x0E45,
+ lamaleffinalarabic: 0xFEFC,
+ lamalefhamzaabovefinalarabic: 0xFEF8,
+ lamalefhamzaaboveisolatedarabic: 0xFEF7,
+ lamalefhamzabelowfinalarabic: 0xFEFA,
+ lamalefhamzabelowisolatedarabic: 0xFEF9,
+ lamalefisolatedarabic: 0xFEFB,
+ lamalefmaddaabovefinalarabic: 0xFEF6,
+ lamalefmaddaaboveisolatedarabic: 0xFEF5,
+ lamarabic: 0x0644,
+ lambda: 0x03BB,
+ lambdastroke: 0x019B,
+ lamed: 0x05DC,
+ lameddagesh: 0xFB3C,
+ lameddageshhebrew: 0xFB3C,
+ lamedhebrew: 0x05DC,
+ lamfinalarabic: 0xFEDE,
+ lamhahinitialarabic: 0xFCCA,
+ laminitialarabic: 0xFEDF,
+ lamjeeminitialarabic: 0xFCC9,
+ lamkhahinitialarabic: 0xFCCB,
+ lamlamhehisolatedarabic: 0xFDF2,
+ lammedialarabic: 0xFEE0,
+ lammeemhahinitialarabic: 0xFD88,
+ lammeeminitialarabic: 0xFCCC,
+ largecircle: 0x25EF,
+ lbar: 0x019A,
+ lbelt: 0x026C,
+ lbopomofo: 0x310C,
+ lcaron: 0x013E,
+ lcedilla: 0x013C,
+ lcircle: 0x24DB,
+ lcircumflexbelow: 0x1E3D,
+ lcommaaccent: 0x013C,
+ ldot: 0x0140,
+ ldotaccent: 0x0140,
+ ldotbelow: 0x1E37,
+ ldotbelowmacron: 0x1E39,
+ leftangleabovecmb: 0x031A,
+ lefttackbelowcmb: 0x0318,
+ less: 0x003C,
+ lessequal: 0x2264,
+ lessequalorgreater: 0x22DA,
+ lessmonospace: 0xFF1C,
+ lessorequivalent: 0x2272,
+ lessorgreater: 0x2276,
+ lessoverequal: 0x2266,
+ lesssmall: 0xFE64,
+ lezh: 0x026E,
+ lfblock: 0x258C,
+ lhookretroflex: 0x026D,
+ lira: 0x20A4,
+ liwnarmenian: 0x056C,
+ lj: 0x01C9,
+ ljecyrillic: 0x0459,
+ ll: 0xF6C0,
+ lladeva: 0x0933,
+ llagujarati: 0x0AB3,
+ llinebelow: 0x1E3B,
+ llladeva: 0x0934,
+ llvocalicbengali: 0x09E1,
+ llvocalicdeva: 0x0961,
+ llvocalicvowelsignbengali: 0x09E3,
+ llvocalicvowelsigndeva: 0x0963,
+ lmiddletilde: 0x026B,
+ lmonospace: 0xFF4C,
+ lmsquare: 0x33D0,
+ lochulathai: 0x0E2C,
+ logicaland: 0x2227,
+ logicalnot: 0x00AC,
+ logicalnotreversed: 0x2310,
+ logicalor: 0x2228,
+ lolingthai: 0x0E25,
+ longs: 0x017F,
+ lowlinecenterline: 0xFE4E,
+ lowlinecmb: 0x0332,
+ lowlinedashed: 0xFE4D,
+ lozenge: 0x25CA,
+ lparen: 0x24A7,
+ lslash: 0x0142,
+ lsquare: 0x2113,
+ lsuperior: 0xF6EE,
+ ltshade: 0x2591,
+ luthai: 0x0E26,
+ lvocalicbengali: 0x098C,
+ lvocalicdeva: 0x090C,
+ lvocalicvowelsignbengali: 0x09E2,
+ lvocalicvowelsigndeva: 0x0962,
+ lxsquare: 0x33D3,
+ m: 0x006D,
+ mabengali: 0x09AE,
+ macron: 0x00AF,
+ macronbelowcmb: 0x0331,
+ macroncmb: 0x0304,
+ macronlowmod: 0x02CD,
+ macronmonospace: 0xFFE3,
+ macute: 0x1E3F,
+ madeva: 0x092E,
+ magujarati: 0x0AAE,
+ magurmukhi: 0x0A2E,
+ mahapakhhebrew: 0x05A4,
+ mahapakhlefthebrew: 0x05A4,
+ mahiragana: 0x307E,
+ maichattawalowleftthai: 0xF895,
+ maichattawalowrightthai: 0xF894,
+ maichattawathai: 0x0E4B,
+ maichattawaupperleftthai: 0xF893,
+ maieklowleftthai: 0xF88C,
+ maieklowrightthai: 0xF88B,
+ maiekthai: 0x0E48,
+ maiekupperleftthai: 0xF88A,
+ maihanakatleftthai: 0xF884,
+ maihanakatthai: 0x0E31,
+ maitaikhuleftthai: 0xF889,
+ maitaikhuthai: 0x0E47,
+ maitholowleftthai: 0xF88F,
+ maitholowrightthai: 0xF88E,
+ maithothai: 0x0E49,
+ maithoupperleftthai: 0xF88D,
+ maitrilowleftthai: 0xF892,
+ maitrilowrightthai: 0xF891,
+ maitrithai: 0x0E4A,
+ maitriupperleftthai: 0xF890,
+ maiyamokthai: 0x0E46,
+ makatakana: 0x30DE,
+ makatakanahalfwidth: 0xFF8F,
+ male: 0x2642,
+ mansyonsquare: 0x3347,
+ maqafhebrew: 0x05BE,
+ mars: 0x2642,
+ masoracirclehebrew: 0x05AF,
+ masquare: 0x3383,
+ mbopomofo: 0x3107,
+ mbsquare: 0x33D4,
+ mcircle: 0x24DC,
+ mcubedsquare: 0x33A5,
+ mdotaccent: 0x1E41,
+ mdotbelow: 0x1E43,
+ meemarabic: 0x0645,
+ meemfinalarabic: 0xFEE2,
+ meeminitialarabic: 0xFEE3,
+ meemmedialarabic: 0xFEE4,
+ meemmeeminitialarabic: 0xFCD1,
+ meemmeemisolatedarabic: 0xFC48,
+ meetorusquare: 0x334D,
+ mehiragana: 0x3081,
+ meizierasquare: 0x337E,
+ mekatakana: 0x30E1,
+ mekatakanahalfwidth: 0xFF92,
+ mem: 0x05DE,
+ memdagesh: 0xFB3E,
+ memdageshhebrew: 0xFB3E,
+ memhebrew: 0x05DE,
+ menarmenian: 0x0574,
+ merkhahebrew: 0x05A5,
+ merkhakefulahebrew: 0x05A6,
+ merkhakefulalefthebrew: 0x05A6,
+ merkhalefthebrew: 0x05A5,
+ mhook: 0x0271,
+ mhzsquare: 0x3392,
+ middledotkatakanahalfwidth: 0xFF65,
+ middot: 0x00B7,
+ mieumacirclekorean: 0x3272,
+ mieumaparenkorean: 0x3212,
+ mieumcirclekorean: 0x3264,
+ mieumkorean: 0x3141,
+ mieumpansioskorean: 0x3170,
+ mieumparenkorean: 0x3204,
+ mieumpieupkorean: 0x316E,
+ mieumsioskorean: 0x316F,
+ mihiragana: 0x307F,
+ mikatakana: 0x30DF,
+ mikatakanahalfwidth: 0xFF90,
+ minus: 0x2212,
+ minusbelowcmb: 0x0320,
+ minuscircle: 0x2296,
+ minusmod: 0x02D7,
+ minusplus: 0x2213,
+ minute: 0x2032,
+ miribaarusquare: 0x334A,
+ mirisquare: 0x3349,
+ mlonglegturned: 0x0270,
+ mlsquare: 0x3396,
+ mmcubedsquare: 0x33A3,
+ mmonospace: 0xFF4D,
+ mmsquaredsquare: 0x339F,
+ mohiragana: 0x3082,
+ mohmsquare: 0x33C1,
+ mokatakana: 0x30E2,
+ mokatakanahalfwidth: 0xFF93,
+ molsquare: 0x33D6,
+ momathai: 0x0E21,
+ moverssquare: 0x33A7,
+ moverssquaredsquare: 0x33A8,
+ mparen: 0x24A8,
+ mpasquare: 0x33AB,
+ mssquare: 0x33B3,
+ msuperior: 0xF6EF,
+ mturned: 0x026F,
+ mu: 0x00B5,
+ mu1: 0x00B5,
+ muasquare: 0x3382,
+ muchgreater: 0x226B,
+ muchless: 0x226A,
+ mufsquare: 0x338C,
+ mugreek: 0x03BC,
+ mugsquare: 0x338D,
+ muhiragana: 0x3080,
+ mukatakana: 0x30E0,
+ mukatakanahalfwidth: 0xFF91,
+ mulsquare: 0x3395,
+ multiply: 0x00D7,
+ mumsquare: 0x339B,
+ munahhebrew: 0x05A3,
+ munahlefthebrew: 0x05A3,
+ musicalnote: 0x266A,
+ musicalnotedbl: 0x266B,
+ musicflatsign: 0x266D,
+ musicsharpsign: 0x266F,
+ mussquare: 0x33B2,
+ muvsquare: 0x33B6,
+ muwsquare: 0x33BC,
+ mvmegasquare: 0x33B9,
+ mvsquare: 0x33B7,
+ mwmegasquare: 0x33BF,
+ mwsquare: 0x33BD,
+ n: 0x006E,
+ nabengali: 0x09A8,
+ nabla: 0x2207,
+ nacute: 0x0144,
+ nadeva: 0x0928,
+ nagujarati: 0x0AA8,
+ nagurmukhi: 0x0A28,
+ nahiragana: 0x306A,
+ nakatakana: 0x30CA,
+ nakatakanahalfwidth: 0xFF85,
+ napostrophe: 0x0149,
+ nasquare: 0x3381,
+ nbopomofo: 0x310B,
+ nbspace: 0x00A0,
+ ncaron: 0x0148,
+ ncedilla: 0x0146,
+ ncircle: 0x24DD,
+ ncircumflexbelow: 0x1E4B,
+ ncommaaccent: 0x0146,
+ ndotaccent: 0x1E45,
+ ndotbelow: 0x1E47,
+ nehiragana: 0x306D,
+ nekatakana: 0x30CD,
+ nekatakanahalfwidth: 0xFF88,
+ newsheqelsign: 0x20AA,
+ nfsquare: 0x338B,
+ ngabengali: 0x0999,
+ ngadeva: 0x0919,
+ ngagujarati: 0x0A99,
+ ngagurmukhi: 0x0A19,
+ ngonguthai: 0x0E07,
+ nhiragana: 0x3093,
+ nhookleft: 0x0272,
+ nhookretroflex: 0x0273,
+ nieunacirclekorean: 0x326F,
+ nieunaparenkorean: 0x320F,
+ nieuncieuckorean: 0x3135,
+ nieuncirclekorean: 0x3261,
+ nieunhieuhkorean: 0x3136,
+ nieunkorean: 0x3134,
+ nieunpansioskorean: 0x3168,
+ nieunparenkorean: 0x3201,
+ nieunsioskorean: 0x3167,
+ nieuntikeutkorean: 0x3166,
+ nihiragana: 0x306B,
+ nikatakana: 0x30CB,
+ nikatakanahalfwidth: 0xFF86,
+ nikhahitleftthai: 0xF899,
+ nikhahitthai: 0x0E4D,
+ nine: 0x0039,
+ ninearabic: 0x0669,
+ ninebengali: 0x09EF,
+ ninecircle: 0x2468,
+ ninecircleinversesansserif: 0x2792,
+ ninedeva: 0x096F,
+ ninegujarati: 0x0AEF,
+ ninegurmukhi: 0x0A6F,
+ ninehackarabic: 0x0669,
+ ninehangzhou: 0x3029,
+ nineideographicparen: 0x3228,
+ nineinferior: 0x2089,
+ ninemonospace: 0xFF19,
+ nineoldstyle: 0xF739,
+ nineparen: 0x247C,
+ nineperiod: 0x2490,
+ ninepersian: 0x06F9,
+ nineroman: 0x2178,
+ ninesuperior: 0x2079,
+ nineteencircle: 0x2472,
+ nineteenparen: 0x2486,
+ nineteenperiod: 0x249A,
+ ninethai: 0x0E59,
+ nj: 0x01CC,
+ njecyrillic: 0x045A,
+ nkatakana: 0x30F3,
+ nkatakanahalfwidth: 0xFF9D,
+ nlegrightlong: 0x019E,
+ nlinebelow: 0x1E49,
+ nmonospace: 0xFF4E,
+ nmsquare: 0x339A,
+ nnabengali: 0x09A3,
+ nnadeva: 0x0923,
+ nnagujarati: 0x0AA3,
+ nnagurmukhi: 0x0A23,
+ nnnadeva: 0x0929,
+ nohiragana: 0x306E,
+ nokatakana: 0x30CE,
+ nokatakanahalfwidth: 0xFF89,
+ nonbreakingspace: 0x00A0,
+ nonenthai: 0x0E13,
+ nonuthai: 0x0E19,
+ noonarabic: 0x0646,
+ noonfinalarabic: 0xFEE6,
+ noonghunnaarabic: 0x06BA,
+ noonghunnafinalarabic: 0xFB9F,
+ nooninitialarabic: 0xFEE7,
+ noonjeeminitialarabic: 0xFCD2,
+ noonjeemisolatedarabic: 0xFC4B,
+ noonmedialarabic: 0xFEE8,
+ noonmeeminitialarabic: 0xFCD5,
+ noonmeemisolatedarabic: 0xFC4E,
+ noonnoonfinalarabic: 0xFC8D,
+ notcontains: 0x220C,
+ notelement: 0x2209,
+ notelementof: 0x2209,
+ notequal: 0x2260,
+ notgreater: 0x226F,
+ notgreaternorequal: 0x2271,
+ notgreaternorless: 0x2279,
+ notidentical: 0x2262,
+ notless: 0x226E,
+ notlessnorequal: 0x2270,
+ notparallel: 0x2226,
+ notprecedes: 0x2280,
+ notsubset: 0x2284,
+ notsucceeds: 0x2281,
+ notsuperset: 0x2285,
+ nowarmenian: 0x0576,
+ nparen: 0x24A9,
+ nssquare: 0x33B1,
+ nsuperior: 0x207F,
+ ntilde: 0x00F1,
+ nu: 0x03BD,
+ nuhiragana: 0x306C,
+ nukatakana: 0x30CC,
+ nukatakanahalfwidth: 0xFF87,
+ nuktabengali: 0x09BC,
+ nuktadeva: 0x093C,
+ nuktagujarati: 0x0ABC,
+ nuktagurmukhi: 0x0A3C,
+ numbersign: 0x0023,
+ numbersignmonospace: 0xFF03,
+ numbersignsmall: 0xFE5F,
+ numeralsigngreek: 0x0374,
+ numeralsignlowergreek: 0x0375,
+ numero: 0x2116,
+ nun: 0x05E0,
+ nundagesh: 0xFB40,
+ nundageshhebrew: 0xFB40,
+ nunhebrew: 0x05E0,
+ nvsquare: 0x33B5,
+ nwsquare: 0x33BB,
+ nyabengali: 0x099E,
+ nyadeva: 0x091E,
+ nyagujarati: 0x0A9E,
+ nyagurmukhi: 0x0A1E,
+ o: 0x006F,
+ oacute: 0x00F3,
+ oangthai: 0x0E2D,
+ obarred: 0x0275,
+ obarredcyrillic: 0x04E9,
+ obarreddieresiscyrillic: 0x04EB,
+ obengali: 0x0993,
+ obopomofo: 0x311B,
+ obreve: 0x014F,
+ ocandradeva: 0x0911,
+ ocandragujarati: 0x0A91,
+ ocandravowelsigndeva: 0x0949,
+ ocandravowelsigngujarati: 0x0AC9,
+ ocaron: 0x01D2,
+ ocircle: 0x24DE,
+ ocircumflex: 0x00F4,
+ ocircumflexacute: 0x1ED1,
+ ocircumflexdotbelow: 0x1ED9,
+ ocircumflexgrave: 0x1ED3,
+ ocircumflexhookabove: 0x1ED5,
+ ocircumflextilde: 0x1ED7,
+ ocyrillic: 0x043E,
+ odblacute: 0x0151,
+ odblgrave: 0x020D,
+ odeva: 0x0913,
+ odieresis: 0x00F6,
+ odieresiscyrillic: 0x04E7,
+ odotbelow: 0x1ECD,
+ oe: 0x0153,
+ oekorean: 0x315A,
+ ogonek: 0x02DB,
+ ogonekcmb: 0x0328,
+ ograve: 0x00F2,
+ ogujarati: 0x0A93,
+ oharmenian: 0x0585,
+ ohiragana: 0x304A,
+ ohookabove: 0x1ECF,
+ ohorn: 0x01A1,
+ ohornacute: 0x1EDB,
+ ohorndotbelow: 0x1EE3,
+ ohorngrave: 0x1EDD,
+ ohornhookabove: 0x1EDF,
+ ohorntilde: 0x1EE1,
+ ohungarumlaut: 0x0151,
+ oi: 0x01A3,
+ oinvertedbreve: 0x020F,
+ okatakana: 0x30AA,
+ okatakanahalfwidth: 0xFF75,
+ okorean: 0x3157,
+ olehebrew: 0x05AB,
+ omacron: 0x014D,
+ omacronacute: 0x1E53,
+ omacrongrave: 0x1E51,
+ omdeva: 0x0950,
+ omega: 0x03C9,
+ omega1: 0x03D6,
+ omegacyrillic: 0x0461,
+ omegalatinclosed: 0x0277,
+ omegaroundcyrillic: 0x047B,
+ omegatitlocyrillic: 0x047D,
+ omegatonos: 0x03CE,
+ omgujarati: 0x0AD0,
+ omicron: 0x03BF,
+ omicrontonos: 0x03CC,
+ omonospace: 0xFF4F,
+ one: 0x0031,
+ onearabic: 0x0661,
+ onebengali: 0x09E7,
+ onecircle: 0x2460,
+ onecircleinversesansserif: 0x278A,
+ onedeva: 0x0967,
+ onedotenleader: 0x2024,
+ oneeighth: 0x215B,
+ onefitted: 0xF6DC,
+ onegujarati: 0x0AE7,
+ onegurmukhi: 0x0A67,
+ onehackarabic: 0x0661,
+ onehalf: 0x00BD,
+ onehangzhou: 0x3021,
+ oneideographicparen: 0x3220,
+ oneinferior: 0x2081,
+ onemonospace: 0xFF11,
+ onenumeratorbengali: 0x09F4,
+ oneoldstyle: 0xF731,
+ oneparen: 0x2474,
+ oneperiod: 0x2488,
+ onepersian: 0x06F1,
+ onequarter: 0x00BC,
+ oneroman: 0x2170,
+ onesuperior: 0x00B9,
+ onethai: 0x0E51,
+ onethird: 0x2153,
+ oogonek: 0x01EB,
+ oogonekmacron: 0x01ED,
+ oogurmukhi: 0x0A13,
+ oomatragurmukhi: 0x0A4B,
+ oopen: 0x0254,
+ oparen: 0x24AA,
+ openbullet: 0x25E6,
+ option: 0x2325,
+ ordfeminine: 0x00AA,
+ ordmasculine: 0x00BA,
+ orthogonal: 0x221F,
+ oshortdeva: 0x0912,
+ oshortvowelsigndeva: 0x094A,
+ oslash: 0x00F8,
+ oslashacute: 0x01FF,
+ osmallhiragana: 0x3049,
+ osmallkatakana: 0x30A9,
+ osmallkatakanahalfwidth: 0xFF6B,
+ ostrokeacute: 0x01FF,
+ osuperior: 0xF6F0,
+ otcyrillic: 0x047F,
+ otilde: 0x00F5,
+ otildeacute: 0x1E4D,
+ otildedieresis: 0x1E4F,
+ oubopomofo: 0x3121,
+ overline: 0x203E,
+ overlinecenterline: 0xFE4A,
+ overlinecmb: 0x0305,
+ overlinedashed: 0xFE49,
+ overlinedblwavy: 0xFE4C,
+ overlinewavy: 0xFE4B,
+ overscore: 0x00AF,
+ ovowelsignbengali: 0x09CB,
+ ovowelsigndeva: 0x094B,
+ ovowelsigngujarati: 0x0ACB,
+ p: 0x0070,
+ paampssquare: 0x3380,
+ paasentosquare: 0x332B,
+ pabengali: 0x09AA,
+ pacute: 0x1E55,
+ padeva: 0x092A,
+ pagedown: 0x21DF,
+ pageup: 0x21DE,
+ pagujarati: 0x0AAA,
+ pagurmukhi: 0x0A2A,
+ pahiragana: 0x3071,
+ paiyannoithai: 0x0E2F,
+ pakatakana: 0x30D1,
+ palatalizationcyrilliccmb: 0x0484,
+ palochkacyrillic: 0x04C0,
+ pansioskorean: 0x317F,
+ paragraph: 0x00B6,
+ parallel: 0x2225,
+ parenleft: 0x0028,
+ parenleftaltonearabic: 0xFD3E,
+ parenleftbt: 0xF8ED,
+ parenleftex: 0xF8EC,
+ parenleftinferior: 0x208D,
+ parenleftmonospace: 0xFF08,
+ parenleftsmall: 0xFE59,
+ parenleftsuperior: 0x207D,
+ parenlefttp: 0xF8EB,
+ parenleftvertical: 0xFE35,
+ parenright: 0x0029,
+ parenrightaltonearabic: 0xFD3F,
+ parenrightbt: 0xF8F8,
+ parenrightex: 0xF8F7,
+ parenrightinferior: 0x208E,
+ parenrightmonospace: 0xFF09,
+ parenrightsmall: 0xFE5A,
+ parenrightsuperior: 0x207E,
+ parenrighttp: 0xF8F6,
+ parenrightvertical: 0xFE36,
+ partialdiff: 0x2202,
+ paseqhebrew: 0x05C0,
+ pashtahebrew: 0x0599,
+ pasquare: 0x33A9,
+ patah: 0x05B7,
+ patah11: 0x05B7,
+ patah1d: 0x05B7,
+ patah2a: 0x05B7,
+ patahhebrew: 0x05B7,
+ patahnarrowhebrew: 0x05B7,
+ patahquarterhebrew: 0x05B7,
+ patahwidehebrew: 0x05B7,
+ pazerhebrew: 0x05A1,
+ pbopomofo: 0x3106,
+ pcircle: 0x24DF,
+ pdotaccent: 0x1E57,
+ pe: 0x05E4,
+ pecyrillic: 0x043F,
+ pedagesh: 0xFB44,
+ pedageshhebrew: 0xFB44,
+ peezisquare: 0x333B,
+ pefinaldageshhebrew: 0xFB43,
+ peharabic: 0x067E,
+ peharmenian: 0x057A,
+ pehebrew: 0x05E4,
+ pehfinalarabic: 0xFB57,
+ pehinitialarabic: 0xFB58,
+ pehiragana: 0x307A,
+ pehmedialarabic: 0xFB59,
+ pekatakana: 0x30DA,
+ pemiddlehookcyrillic: 0x04A7,
+ perafehebrew: 0xFB4E,
+ percent: 0x0025,
+ percentarabic: 0x066A,
+ percentmonospace: 0xFF05,
+ percentsmall: 0xFE6A,
+ period: 0x002E,
+ periodarmenian: 0x0589,
+ periodcentered: 0x00B7,
+ periodhalfwidth: 0xFF61,
+ periodinferior: 0xF6E7,
+ periodmonospace: 0xFF0E,
+ periodsmall: 0xFE52,
+ periodsuperior: 0xF6E8,
+ perispomenigreekcmb: 0x0342,
+ perpendicular: 0x22A5,
+ perthousand: 0x2030,
+ peseta: 0x20A7,
+ pfsquare: 0x338A,
+ phabengali: 0x09AB,
+ phadeva: 0x092B,
+ phagujarati: 0x0AAB,
+ phagurmukhi: 0x0A2B,
+ phi: 0x03C6,
+ phi1: 0x03D5,
+ phieuphacirclekorean: 0x327A,
+ phieuphaparenkorean: 0x321A,
+ phieuphcirclekorean: 0x326C,
+ phieuphkorean: 0x314D,
+ phieuphparenkorean: 0x320C,
+ philatin: 0x0278,
+ phinthuthai: 0x0E3A,
+ phisymbolgreek: 0x03D5,
+ phook: 0x01A5,
+ phophanthai: 0x0E1E,
+ phophungthai: 0x0E1C,
+ phosamphaothai: 0x0E20,
+ pi: 0x03C0,
+ pieupacirclekorean: 0x3273,
+ pieupaparenkorean: 0x3213,
+ pieupcieuckorean: 0x3176,
+ pieupcirclekorean: 0x3265,
+ pieupkiyeokkorean: 0x3172,
+ pieupkorean: 0x3142,
+ pieupparenkorean: 0x3205,
+ pieupsioskiyeokkorean: 0x3174,
+ pieupsioskorean: 0x3144,
+ pieupsiostikeutkorean: 0x3175,
+ pieupthieuthkorean: 0x3177,
+ pieuptikeutkorean: 0x3173,
+ pihiragana: 0x3074,
+ pikatakana: 0x30D4,
+ pisymbolgreek: 0x03D6,
+ piwrarmenian: 0x0583,
+ plus: 0x002B,
+ plusbelowcmb: 0x031F,
+ pluscircle: 0x2295,
+ plusminus: 0x00B1,
+ plusmod: 0x02D6,
+ plusmonospace: 0xFF0B,
+ plussmall: 0xFE62,
+ plussuperior: 0x207A,
+ pmonospace: 0xFF50,
+ pmsquare: 0x33D8,
+ pohiragana: 0x307D,
+ pointingindexdownwhite: 0x261F,
+ pointingindexleftwhite: 0x261C,
+ pointingindexrightwhite: 0x261E,
+ pointingindexupwhite: 0x261D,
+ pokatakana: 0x30DD,
+ poplathai: 0x0E1B,
+ postalmark: 0x3012,
+ postalmarkface: 0x3020,
+ pparen: 0x24AB,
+ precedes: 0x227A,
+ prescription: 0x211E,
+ primemod: 0x02B9,
+ primereversed: 0x2035,
+ product: 0x220F,
+ projective: 0x2305,
+ prolongedkana: 0x30FC,
+ propellor: 0x2318,
+ propersubset: 0x2282,
+ propersuperset: 0x2283,
+ proportion: 0x2237,
+ proportional: 0x221D,
+ psi: 0x03C8,
+ psicyrillic: 0x0471,
+ psilipneumatacyrilliccmb: 0x0486,
+ pssquare: 0x33B0,
+ puhiragana: 0x3077,
+ pukatakana: 0x30D7,
+ pvsquare: 0x33B4,
+ pwsquare: 0x33BA,
+ q: 0x0071,
+ qadeva: 0x0958,
+ qadmahebrew: 0x05A8,
+ qafarabic: 0x0642,
+ qaffinalarabic: 0xFED6,
+ qafinitialarabic: 0xFED7,
+ qafmedialarabic: 0xFED8,
+ qamats: 0x05B8,
+ qamats10: 0x05B8,
+ qamats1a: 0x05B8,
+ qamats1c: 0x05B8,
+ qamats27: 0x05B8,
+ qamats29: 0x05B8,
+ qamats33: 0x05B8,
+ qamatsde: 0x05B8,
+ qamatshebrew: 0x05B8,
+ qamatsnarrowhebrew: 0x05B8,
+ qamatsqatanhebrew: 0x05B8,
+ qamatsqatannarrowhebrew: 0x05B8,
+ qamatsqatanquarterhebrew: 0x05B8,
+ qamatsqatanwidehebrew: 0x05B8,
+ qamatsquarterhebrew: 0x05B8,
+ qamatswidehebrew: 0x05B8,
+ qarneyparahebrew: 0x059F,
+ qbopomofo: 0x3111,
+ qcircle: 0x24E0,
+ qhook: 0x02A0,
+ qmonospace: 0xFF51,
+ qof: 0x05E7,
+ qofdagesh: 0xFB47,
+ qofdageshhebrew: 0xFB47,
+ qofhebrew: 0x05E7,
+ qparen: 0x24AC,
+ quarternote: 0x2669,
+ qubuts: 0x05BB,
+ qubuts18: 0x05BB,
+ qubuts25: 0x05BB,
+ qubuts31: 0x05BB,
+ qubutshebrew: 0x05BB,
+ qubutsnarrowhebrew: 0x05BB,
+ qubutsquarterhebrew: 0x05BB,
+ qubutswidehebrew: 0x05BB,
+ question: 0x003F,
+ questionarabic: 0x061F,
+ questionarmenian: 0x055E,
+ questiondown: 0x00BF,
+ questiondownsmall: 0xF7BF,
+ questiongreek: 0x037E,
+ questionmonospace: 0xFF1F,
+ questionsmall: 0xF73F,
+ quotedbl: 0x0022,
+ quotedblbase: 0x201E,
+ quotedblleft: 0x201C,
+ quotedblmonospace: 0xFF02,
+ quotedblprime: 0x301E,
+ quotedblprimereversed: 0x301D,
+ quotedblright: 0x201D,
+ quoteleft: 0x2018,
+ quoteleftreversed: 0x201B,
+ quotereversed: 0x201B,
+ quoteright: 0x2019,
+ quoterightn: 0x0149,
+ quotesinglbase: 0x201A,
+ quotesingle: 0x0027,
+ quotesinglemonospace: 0xFF07,
+ r: 0x0072,
+ raarmenian: 0x057C,
+ rabengali: 0x09B0,
+ racute: 0x0155,
+ radeva: 0x0930,
+ radical: 0x221A,
+ radicalex: 0xF8E5,
+ radoverssquare: 0x33AE,
+ radoverssquaredsquare: 0x33AF,
+ radsquare: 0x33AD,
+ rafe: 0x05BF,
+ rafehebrew: 0x05BF,
+ ragujarati: 0x0AB0,
+ ragurmukhi: 0x0A30,
+ rahiragana: 0x3089,
+ rakatakana: 0x30E9,
+ rakatakanahalfwidth: 0xFF97,
+ ralowerdiagonalbengali: 0x09F1,
+ ramiddlediagonalbengali: 0x09F0,
+ ramshorn: 0x0264,
+ ratio: 0x2236,
+ rbopomofo: 0x3116,
+ rcaron: 0x0159,
+ rcedilla: 0x0157,
+ rcircle: 0x24E1,
+ rcommaaccent: 0x0157,
+ rdblgrave: 0x0211,
+ rdotaccent: 0x1E59,
+ rdotbelow: 0x1E5B,
+ rdotbelowmacron: 0x1E5D,
+ referencemark: 0x203B,
+ reflexsubset: 0x2286,
+ reflexsuperset: 0x2287,
+ registered: 0x00AE,
+ registersans: 0xF8E8,
+ registerserif: 0xF6DA,
+ reharabic: 0x0631,
+ reharmenian: 0x0580,
+ rehfinalarabic: 0xFEAE,
+ rehiragana: 0x308C,
+ rekatakana: 0x30EC,
+ rekatakanahalfwidth: 0xFF9A,
+ resh: 0x05E8,
+ reshdageshhebrew: 0xFB48,
+ reshhebrew: 0x05E8,
+ reversedtilde: 0x223D,
+ reviahebrew: 0x0597,
+ reviamugrashhebrew: 0x0597,
+ revlogicalnot: 0x2310,
+ rfishhook: 0x027E,
+ rfishhookreversed: 0x027F,
+ rhabengali: 0x09DD,
+ rhadeva: 0x095D,
+ rho: 0x03C1,
+ rhook: 0x027D,
+ rhookturned: 0x027B,
+ rhookturnedsuperior: 0x02B5,
+ rhosymbolgreek: 0x03F1,
+ rhotichookmod: 0x02DE,
+ rieulacirclekorean: 0x3271,
+ rieulaparenkorean: 0x3211,
+ rieulcirclekorean: 0x3263,
+ rieulhieuhkorean: 0x3140,
+ rieulkiyeokkorean: 0x313A,
+ rieulkiyeoksioskorean: 0x3169,
+ rieulkorean: 0x3139,
+ rieulmieumkorean: 0x313B,
+ rieulpansioskorean: 0x316C,
+ rieulparenkorean: 0x3203,
+ rieulphieuphkorean: 0x313F,
+ rieulpieupkorean: 0x313C,
+ rieulpieupsioskorean: 0x316B,
+ rieulsioskorean: 0x313D,
+ rieulthieuthkorean: 0x313E,
+ rieultikeutkorean: 0x316A,
+ rieulyeorinhieuhkorean: 0x316D,
+ rightangle: 0x221F,
+ righttackbelowcmb: 0x0319,
+ righttriangle: 0x22BF,
+ rihiragana: 0x308A,
+ rikatakana: 0x30EA,
+ rikatakanahalfwidth: 0xFF98,
+ ring: 0x02DA,
+ ringbelowcmb: 0x0325,
+ ringcmb: 0x030A,
+ ringhalfleft: 0x02BF,
+ ringhalfleftarmenian: 0x0559,
+ ringhalfleftbelowcmb: 0x031C,
+ ringhalfleftcentered: 0x02D3,
+ ringhalfright: 0x02BE,
+ ringhalfrightbelowcmb: 0x0339,
+ ringhalfrightcentered: 0x02D2,
+ rinvertedbreve: 0x0213,
+ rittorusquare: 0x3351,
+ rlinebelow: 0x1E5F,
+ rlongleg: 0x027C,
+ rlonglegturned: 0x027A,
+ rmonospace: 0xFF52,
+ rohiragana: 0x308D,
+ rokatakana: 0x30ED,
+ rokatakanahalfwidth: 0xFF9B,
+ roruathai: 0x0E23,
+ rparen: 0x24AD,
+ rrabengali: 0x09DC,
+ rradeva: 0x0931,
+ rragurmukhi: 0x0A5C,
+ rreharabic: 0x0691,
+ rrehfinalarabic: 0xFB8D,
+ rrvocalicbengali: 0x09E0,
+ rrvocalicdeva: 0x0960,
+ rrvocalicgujarati: 0x0AE0,
+ rrvocalicvowelsignbengali: 0x09C4,
+ rrvocalicvowelsigndeva: 0x0944,
+ rrvocalicvowelsigngujarati: 0x0AC4,
+ rsuperior: 0xF6F1,
+ rtblock: 0x2590,
+ rturned: 0x0279,
+ rturnedsuperior: 0x02B4,
+ ruhiragana: 0x308B,
+ rukatakana: 0x30EB,
+ rukatakanahalfwidth: 0xFF99,
+ rupeemarkbengali: 0x09F2,
+ rupeesignbengali: 0x09F3,
+ rupiah: 0xF6DD,
+ ruthai: 0x0E24,
+ rvocalicbengali: 0x098B,
+ rvocalicdeva: 0x090B,
+ rvocalicgujarati: 0x0A8B,
+ rvocalicvowelsignbengali: 0x09C3,
+ rvocalicvowelsigndeva: 0x0943,
+ rvocalicvowelsigngujarati: 0x0AC3,
+ s: 0x0073,
+ sabengali: 0x09B8,
+ sacute: 0x015B,
+ sacutedotaccent: 0x1E65,
+ sadarabic: 0x0635,
+ sadeva: 0x0938,
+ sadfinalarabic: 0xFEBA,
+ sadinitialarabic: 0xFEBB,
+ sadmedialarabic: 0xFEBC,
+ sagujarati: 0x0AB8,
+ sagurmukhi: 0x0A38,
+ sahiragana: 0x3055,
+ sakatakana: 0x30B5,
+ sakatakanahalfwidth: 0xFF7B,
+ sallallahoualayhewasallamarabic: 0xFDFA,
+ samekh: 0x05E1,
+ samekhdagesh: 0xFB41,
+ samekhdageshhebrew: 0xFB41,
+ samekhhebrew: 0x05E1,
+ saraaathai: 0x0E32,
+ saraaethai: 0x0E41,
+ saraaimaimalaithai: 0x0E44,
+ saraaimaimuanthai: 0x0E43,
+ saraamthai: 0x0E33,
+ saraathai: 0x0E30,
+ saraethai: 0x0E40,
+ saraiileftthai: 0xF886,
+ saraiithai: 0x0E35,
+ saraileftthai: 0xF885,
+ saraithai: 0x0E34,
+ saraothai: 0x0E42,
+ saraueeleftthai: 0xF888,
+ saraueethai: 0x0E37,
+ saraueleftthai: 0xF887,
+ sarauethai: 0x0E36,
+ sarauthai: 0x0E38,
+ sarauuthai: 0x0E39,
+ sbopomofo: 0x3119,
+ scaron: 0x0161,
+ scarondotaccent: 0x1E67,
+ scedilla: 0x015F,
+ schwa: 0x0259,
+ schwacyrillic: 0x04D9,
+ schwadieresiscyrillic: 0x04DB,
+ schwahook: 0x025A,
+ scircle: 0x24E2,
+ scircumflex: 0x015D,
+ scommaaccent: 0x0219,
+ sdotaccent: 0x1E61,
+ sdotbelow: 0x1E63,
+ sdotbelowdotaccent: 0x1E69,
+ seagullbelowcmb: 0x033C,
+ second: 0x2033,
+ secondtonechinese: 0x02CA,
+ section: 0x00A7,
+ seenarabic: 0x0633,
+ seenfinalarabic: 0xFEB2,
+ seeninitialarabic: 0xFEB3,
+ seenmedialarabic: 0xFEB4,
+ segol: 0x05B6,
+ segol13: 0x05B6,
+ segol1f: 0x05B6,
+ segol2c: 0x05B6,
+ segolhebrew: 0x05B6,
+ segolnarrowhebrew: 0x05B6,
+ segolquarterhebrew: 0x05B6,
+ segoltahebrew: 0x0592,
+ segolwidehebrew: 0x05B6,
+ seharmenian: 0x057D,
+ sehiragana: 0x305B,
+ sekatakana: 0x30BB,
+ sekatakanahalfwidth: 0xFF7E,
+ semicolon: 0x003B,
+ semicolonarabic: 0x061B,
+ semicolonmonospace: 0xFF1B,
+ semicolonsmall: 0xFE54,
+ semivoicedmarkkana: 0x309C,
+ semivoicedmarkkanahalfwidth: 0xFF9F,
+ sentisquare: 0x3322,
+ sentosquare: 0x3323,
+ seven: 0x0037,
+ sevenarabic: 0x0667,
+ sevenbengali: 0x09ED,
+ sevencircle: 0x2466,
+ sevencircleinversesansserif: 0x2790,
+ sevendeva: 0x096D,
+ seveneighths: 0x215E,
+ sevengujarati: 0x0AED,
+ sevengurmukhi: 0x0A6D,
+ sevenhackarabic: 0x0667,
+ sevenhangzhou: 0x3027,
+ sevenideographicparen: 0x3226,
+ seveninferior: 0x2087,
+ sevenmonospace: 0xFF17,
+ sevenoldstyle: 0xF737,
+ sevenparen: 0x247A,
+ sevenperiod: 0x248E,
+ sevenpersian: 0x06F7,
+ sevenroman: 0x2176,
+ sevensuperior: 0x2077,
+ seventeencircle: 0x2470,
+ seventeenparen: 0x2484,
+ seventeenperiod: 0x2498,
+ seventhai: 0x0E57,
+ sfthyphen: 0x00AD,
+ shaarmenian: 0x0577,
+ shabengali: 0x09B6,
+ shacyrillic: 0x0448,
+ shaddaarabic: 0x0651,
+ shaddadammaarabic: 0xFC61,
+ shaddadammatanarabic: 0xFC5E,
+ shaddafathaarabic: 0xFC60,
+ shaddakasraarabic: 0xFC62,
+ shaddakasratanarabic: 0xFC5F,
+ shade: 0x2592,
+ shadedark: 0x2593,
+ shadelight: 0x2591,
+ shademedium: 0x2592,
+ shadeva: 0x0936,
+ shagujarati: 0x0AB6,
+ shagurmukhi: 0x0A36,
+ shalshelethebrew: 0x0593,
+ shbopomofo: 0x3115,
+ shchacyrillic: 0x0449,
+ sheenarabic: 0x0634,
+ sheenfinalarabic: 0xFEB6,
+ sheeninitialarabic: 0xFEB7,
+ sheenmedialarabic: 0xFEB8,
+ sheicoptic: 0x03E3,
+ sheqel: 0x20AA,
+ sheqelhebrew: 0x20AA,
+ sheva: 0x05B0,
+ sheva115: 0x05B0,
+ sheva15: 0x05B0,
+ sheva22: 0x05B0,
+ sheva2e: 0x05B0,
+ shevahebrew: 0x05B0,
+ shevanarrowhebrew: 0x05B0,
+ shevaquarterhebrew: 0x05B0,
+ shevawidehebrew: 0x05B0,
+ shhacyrillic: 0x04BB,
+ shimacoptic: 0x03ED,
+ shin: 0x05E9,
+ shindagesh: 0xFB49,
+ shindageshhebrew: 0xFB49,
+ shindageshshindot: 0xFB2C,
+ shindageshshindothebrew: 0xFB2C,
+ shindageshsindot: 0xFB2D,
+ shindageshsindothebrew: 0xFB2D,
+ shindothebrew: 0x05C1,
+ shinhebrew: 0x05E9,
+ shinshindot: 0xFB2A,
+ shinshindothebrew: 0xFB2A,
+ shinsindot: 0xFB2B,
+ shinsindothebrew: 0xFB2B,
+ shook: 0x0282,
+ sigma: 0x03C3,
+ sigma1: 0x03C2,
+ sigmafinal: 0x03C2,
+ sigmalunatesymbolgreek: 0x03F2,
+ sihiragana: 0x3057,
+ sikatakana: 0x30B7,
+ sikatakanahalfwidth: 0xFF7C,
+ siluqhebrew: 0x05BD,
+ siluqlefthebrew: 0x05BD,
+ similar: 0x223C,
+ sindothebrew: 0x05C2,
+ siosacirclekorean: 0x3274,
+ siosaparenkorean: 0x3214,
+ sioscieuckorean: 0x317E,
+ sioscirclekorean: 0x3266,
+ sioskiyeokkorean: 0x317A,
+ sioskorean: 0x3145,
+ siosnieunkorean: 0x317B,
+ siosparenkorean: 0x3206,
+ siospieupkorean: 0x317D,
+ siostikeutkorean: 0x317C,
+ six: 0x0036,
+ sixarabic: 0x0666,
+ sixbengali: 0x09EC,
+ sixcircle: 0x2465,
+ sixcircleinversesansserif: 0x278F,
+ sixdeva: 0x096C,
+ sixgujarati: 0x0AEC,
+ sixgurmukhi: 0x0A6C,
+ sixhackarabic: 0x0666,
+ sixhangzhou: 0x3026,
+ sixideographicparen: 0x3225,
+ sixinferior: 0x2086,
+ sixmonospace: 0xFF16,
+ sixoldstyle: 0xF736,
+ sixparen: 0x2479,
+ sixperiod: 0x248D,
+ sixpersian: 0x06F6,
+ sixroman: 0x2175,
+ sixsuperior: 0x2076,
+ sixteencircle: 0x246F,
+ sixteencurrencydenominatorbengali: 0x09F9,
+ sixteenparen: 0x2483,
+ sixteenperiod: 0x2497,
+ sixthai: 0x0E56,
+ slash: 0x002F,
+ slashmonospace: 0xFF0F,
+ slong: 0x017F,
+ slongdotaccent: 0x1E9B,
+ smileface: 0x263A,
+ smonospace: 0xFF53,
+ sofpasuqhebrew: 0x05C3,
+ softhyphen: 0x00AD,
+ softsigncyrillic: 0x044C,
+ sohiragana: 0x305D,
+ sokatakana: 0x30BD,
+ sokatakanahalfwidth: 0xFF7F,
+ soliduslongoverlaycmb: 0x0338,
+ solidusshortoverlaycmb: 0x0337,
+ sorusithai: 0x0E29,
+ sosalathai: 0x0E28,
+ sosothai: 0x0E0B,
+ sosuathai: 0x0E2A,
+ space: 0x0020,
+ spacehackarabic: 0x0020,
+ spade: 0x2660,
+ spadesuitblack: 0x2660,
+ spadesuitwhite: 0x2664,
+ sparen: 0x24AE,
+ squarebelowcmb: 0x033B,
+ squarecc: 0x33C4,
+ squarecm: 0x339D,
+ squarediagonalcrosshatchfill: 0x25A9,
+ squarehorizontalfill: 0x25A4,
+ squarekg: 0x338F,
+ squarekm: 0x339E,
+ squarekmcapital: 0x33CE,
+ squareln: 0x33D1,
+ squarelog: 0x33D2,
+ squaremg: 0x338E,
+ squaremil: 0x33D5,
+ squaremm: 0x339C,
+ squaremsquared: 0x33A1,
+ squareorthogonalcrosshatchfill: 0x25A6,
+ squareupperlefttolowerrightfill: 0x25A7,
+ squareupperrighttolowerleftfill: 0x25A8,
+ squareverticalfill: 0x25A5,
+ squarewhitewithsmallblack: 0x25A3,
+ srsquare: 0x33DB,
+ ssabengali: 0x09B7,
+ ssadeva: 0x0937,
+ ssagujarati: 0x0AB7,
+ ssangcieuckorean: 0x3149,
+ ssanghieuhkorean: 0x3185,
+ ssangieungkorean: 0x3180,
+ ssangkiyeokkorean: 0x3132,
+ ssangnieunkorean: 0x3165,
+ ssangpieupkorean: 0x3143,
+ ssangsioskorean: 0x3146,
+ ssangtikeutkorean: 0x3138,
+ ssuperior: 0xF6F2,
+ sterling: 0x00A3,
+ sterlingmonospace: 0xFFE1,
+ strokelongoverlaycmb: 0x0336,
+ strokeshortoverlaycmb: 0x0335,
+ subset: 0x2282,
+ subsetnotequal: 0x228A,
+ subsetorequal: 0x2286,
+ succeeds: 0x227B,
+ suchthat: 0x220B,
+ suhiragana: 0x3059,
+ sukatakana: 0x30B9,
+ sukatakanahalfwidth: 0xFF7D,
+ sukunarabic: 0x0652,
+ summation: 0x2211,
+ sun: 0x263C,
+ superset: 0x2283,
+ supersetnotequal: 0x228B,
+ supersetorequal: 0x2287,
+ svsquare: 0x33DC,
+ syouwaerasquare: 0x337C,
+ t: 0x0074,
+ tabengali: 0x09A4,
+ tackdown: 0x22A4,
+ tackleft: 0x22A3,
+ tadeva: 0x0924,
+ tagujarati: 0x0AA4,
+ tagurmukhi: 0x0A24,
+ taharabic: 0x0637,
+ tahfinalarabic: 0xFEC2,
+ tahinitialarabic: 0xFEC3,
+ tahiragana: 0x305F,
+ tahmedialarabic: 0xFEC4,
+ taisyouerasquare: 0x337D,
+ takatakana: 0x30BF,
+ takatakanahalfwidth: 0xFF80,
+ tatweelarabic: 0x0640,
+ tau: 0x03C4,
+ tav: 0x05EA,
+ tavdages: 0xFB4A,
+ tavdagesh: 0xFB4A,
+ tavdageshhebrew: 0xFB4A,
+ tavhebrew: 0x05EA,
+ tbar: 0x0167,
+ tbopomofo: 0x310A,
+ tcaron: 0x0165,
+ tccurl: 0x02A8,
+ tcedilla: 0x0163,
+ tcheharabic: 0x0686,
+ tchehfinalarabic: 0xFB7B,
+ tchehinitialarabic: 0xFB7C,
+ tchehmedialarabic: 0xFB7D,
+ tcircle: 0x24E3,
+ tcircumflexbelow: 0x1E71,
+ tcommaaccent: 0x0163,
+ tdieresis: 0x1E97,
+ tdotaccent: 0x1E6B,
+ tdotbelow: 0x1E6D,
+ tecyrillic: 0x0442,
+ tedescendercyrillic: 0x04AD,
+ teharabic: 0x062A,
+ tehfinalarabic: 0xFE96,
+ tehhahinitialarabic: 0xFCA2,
+ tehhahisolatedarabic: 0xFC0C,
+ tehinitialarabic: 0xFE97,
+ tehiragana: 0x3066,
+ tehjeeminitialarabic: 0xFCA1,
+ tehjeemisolatedarabic: 0xFC0B,
+ tehmarbutaarabic: 0x0629,
+ tehmarbutafinalarabic: 0xFE94,
+ tehmedialarabic: 0xFE98,
+ tehmeeminitialarabic: 0xFCA4,
+ tehmeemisolatedarabic: 0xFC0E,
+ tehnoonfinalarabic: 0xFC73,
+ tekatakana: 0x30C6,
+ tekatakanahalfwidth: 0xFF83,
+ telephone: 0x2121,
+ telephoneblack: 0x260E,
+ telishagedolahebrew: 0x05A0,
+ telishaqetanahebrew: 0x05A9,
+ tencircle: 0x2469,
+ tenideographicparen: 0x3229,
+ tenparen: 0x247D,
+ tenperiod: 0x2491,
+ tenroman: 0x2179,
+ tesh: 0x02A7,
+ tet: 0x05D8,
+ tetdagesh: 0xFB38,
+ tetdageshhebrew: 0xFB38,
+ tethebrew: 0x05D8,
+ tetsecyrillic: 0x04B5,
+ tevirhebrew: 0x059B,
+ tevirlefthebrew: 0x059B,
+ thabengali: 0x09A5,
+ thadeva: 0x0925,
+ thagujarati: 0x0AA5,
+ thagurmukhi: 0x0A25,
+ thalarabic: 0x0630,
+ thalfinalarabic: 0xFEAC,
+ thanthakhatlowleftthai: 0xF898,
+ thanthakhatlowrightthai: 0xF897,
+ thanthakhatthai: 0x0E4C,
+ thanthakhatupperleftthai: 0xF896,
+ theharabic: 0x062B,
+ thehfinalarabic: 0xFE9A,
+ thehinitialarabic: 0xFE9B,
+ thehmedialarabic: 0xFE9C,
+ thereexists: 0x2203,
+ therefore: 0x2234,
+ theta: 0x03B8,
+ theta1: 0x03D1,
+ thetasymbolgreek: 0x03D1,
+ thieuthacirclekorean: 0x3279,
+ thieuthaparenkorean: 0x3219,
+ thieuthcirclekorean: 0x326B,
+ thieuthkorean: 0x314C,
+ thieuthparenkorean: 0x320B,
+ thirteencircle: 0x246C,
+ thirteenparen: 0x2480,
+ thirteenperiod: 0x2494,
+ thonangmonthothai: 0x0E11,
+ thook: 0x01AD,
+ thophuthaothai: 0x0E12,
+ thorn: 0x00FE,
+ thothahanthai: 0x0E17,
+ thothanthai: 0x0E10,
+ thothongthai: 0x0E18,
+ thothungthai: 0x0E16,
+ thousandcyrillic: 0x0482,
+ thousandsseparatorarabic: 0x066C,
+ thousandsseparatorpersian: 0x066C,
+ three: 0x0033,
+ threearabic: 0x0663,
+ threebengali: 0x09E9,
+ threecircle: 0x2462,
+ threecircleinversesansserif: 0x278C,
+ threedeva: 0x0969,
+ threeeighths: 0x215C,
+ threegujarati: 0x0AE9,
+ threegurmukhi: 0x0A69,
+ threehackarabic: 0x0663,
+ threehangzhou: 0x3023,
+ threeideographicparen: 0x3222,
+ threeinferior: 0x2083,
+ threemonospace: 0xFF13,
+ threenumeratorbengali: 0x09F6,
+ threeoldstyle: 0xF733,
+ threeparen: 0x2476,
+ threeperiod: 0x248A,
+ threepersian: 0x06F3,
+ threequarters: 0x00BE,
+ threequartersemdash: 0xF6DE,
+ threeroman: 0x2172,
+ threesuperior: 0x00B3,
+ threethai: 0x0E53,
+ thzsquare: 0x3394,
+ tihiragana: 0x3061,
+ tikatakana: 0x30C1,
+ tikatakanahalfwidth: 0xFF81,
+ tikeutacirclekorean: 0x3270,
+ tikeutaparenkorean: 0x3210,
+ tikeutcirclekorean: 0x3262,
+ tikeutkorean: 0x3137,
+ tikeutparenkorean: 0x3202,
+ tilde: 0x02DC,
+ tildebelowcmb: 0x0330,
+ tildecmb: 0x0303,
+ tildecomb: 0x0303,
+ tildedoublecmb: 0x0360,
+ tildeoperator: 0x223C,
+ tildeoverlaycmb: 0x0334,
+ tildeverticalcmb: 0x033E,
+ timescircle: 0x2297,
+ tipehahebrew: 0x0596,
+ tipehalefthebrew: 0x0596,
+ tippigurmukhi: 0x0A70,
+ titlocyrilliccmb: 0x0483,
+ tiwnarmenian: 0x057F,
+ tlinebelow: 0x1E6F,
+ tmonospace: 0xFF54,
+ toarmenian: 0x0569,
+ tohiragana: 0x3068,
+ tokatakana: 0x30C8,
+ tokatakanahalfwidth: 0xFF84,
+ tonebarextrahighmod: 0x02E5,
+ tonebarextralowmod: 0x02E9,
+ tonebarhighmod: 0x02E6,
+ tonebarlowmod: 0x02E8,
+ tonebarmidmod: 0x02E7,
+ tonefive: 0x01BD,
+ tonesix: 0x0185,
+ tonetwo: 0x01A8,
+ tonos: 0x0384,
+ tonsquare: 0x3327,
+ topatakthai: 0x0E0F,
+ tortoiseshellbracketleft: 0x3014,
+ tortoiseshellbracketleftsmall: 0xFE5D,
+ tortoiseshellbracketleftvertical: 0xFE39,
+ tortoiseshellbracketright: 0x3015,
+ tortoiseshellbracketrightsmall: 0xFE5E,
+ tortoiseshellbracketrightvertical: 0xFE3A,
+ totaothai: 0x0E15,
+ tpalatalhook: 0x01AB,
+ tparen: 0x24AF,
+ trademark: 0x2122,
+ trademarksans: 0xF8EA,
+ trademarkserif: 0xF6DB,
+ tretroflexhook: 0x0288,
+ triagdn: 0x25BC,
+ triaglf: 0x25C4,
+ triagrt: 0x25BA,
+ triagup: 0x25B2,
+ ts: 0x02A6,
+ tsadi: 0x05E6,
+ tsadidagesh: 0xFB46,
+ tsadidageshhebrew: 0xFB46,
+ tsadihebrew: 0x05E6,
+ tsecyrillic: 0x0446,
+ tsere: 0x05B5,
+ tsere12: 0x05B5,
+ tsere1e: 0x05B5,
+ tsere2b: 0x05B5,
+ tserehebrew: 0x05B5,
+ tserenarrowhebrew: 0x05B5,
+ tserequarterhebrew: 0x05B5,
+ tserewidehebrew: 0x05B5,
+ tshecyrillic: 0x045B,
+ tsuperior: 0xF6F3,
+ ttabengali: 0x099F,
+ ttadeva: 0x091F,
+ ttagujarati: 0x0A9F,
+ ttagurmukhi: 0x0A1F,
+ tteharabic: 0x0679,
+ ttehfinalarabic: 0xFB67,
+ ttehinitialarabic: 0xFB68,
+ ttehmedialarabic: 0xFB69,
+ tthabengali: 0x09A0,
+ tthadeva: 0x0920,
+ tthagujarati: 0x0AA0,
+ tthagurmukhi: 0x0A20,
+ tturned: 0x0287,
+ tuhiragana: 0x3064,
+ tukatakana: 0x30C4,
+ tukatakanahalfwidth: 0xFF82,
+ tusmallhiragana: 0x3063,
+ tusmallkatakana: 0x30C3,
+ tusmallkatakanahalfwidth: 0xFF6F,
+ twelvecircle: 0x246B,
+ twelveparen: 0x247F,
+ twelveperiod: 0x2493,
+ twelveroman: 0x217B,
+ twentycircle: 0x2473,
+ twentyhangzhou: 0x5344,
+ twentyparen: 0x2487,
+ twentyperiod: 0x249B,
+ two: 0x0032,
+ twoarabic: 0x0662,
+ twobengali: 0x09E8,
+ twocircle: 0x2461,
+ twocircleinversesansserif: 0x278B,
+ twodeva: 0x0968,
+ twodotenleader: 0x2025,
+ twodotleader: 0x2025,
+ twodotleadervertical: 0xFE30,
+ twogujarati: 0x0AE8,
+ twogurmukhi: 0x0A68,
+ twohackarabic: 0x0662,
+ twohangzhou: 0x3022,
+ twoideographicparen: 0x3221,
+ twoinferior: 0x2082,
+ twomonospace: 0xFF12,
+ twonumeratorbengali: 0x09F5,
+ twooldstyle: 0xF732,
+ twoparen: 0x2475,
+ twoperiod: 0x2489,
+ twopersian: 0x06F2,
+ tworoman: 0x2171,
+ twostroke: 0x01BB,
+ twosuperior: 0x00B2,
+ twothai: 0x0E52,
+ twothirds: 0x2154,
+ u: 0x0075,
+ uacute: 0x00FA,
+ ubar: 0x0289,
+ ubengali: 0x0989,
+ ubopomofo: 0x3128,
+ ubreve: 0x016D,
+ ucaron: 0x01D4,
+ ucircle: 0x24E4,
+ ucircumflex: 0x00FB,
+ ucircumflexbelow: 0x1E77,
+ ucyrillic: 0x0443,
+ udattadeva: 0x0951,
+ udblacute: 0x0171,
+ udblgrave: 0x0215,
+ udeva: 0x0909,
+ udieresis: 0x00FC,
+ udieresisacute: 0x01D8,
+ udieresisbelow: 0x1E73,
+ udieresiscaron: 0x01DA,
+ udieresiscyrillic: 0x04F1,
+ udieresisgrave: 0x01DC,
+ udieresismacron: 0x01D6,
+ udotbelow: 0x1EE5,
+ ugrave: 0x00F9,
+ ugujarati: 0x0A89,
+ ugurmukhi: 0x0A09,
+ uhiragana: 0x3046,
+ uhookabove: 0x1EE7,
+ uhorn: 0x01B0,
+ uhornacute: 0x1EE9,
+ uhorndotbelow: 0x1EF1,
+ uhorngrave: 0x1EEB,
+ uhornhookabove: 0x1EED,
+ uhorntilde: 0x1EEF,
+ uhungarumlaut: 0x0171,
+ uhungarumlautcyrillic: 0x04F3,
+ uinvertedbreve: 0x0217,
+ ukatakana: 0x30A6,
+ ukatakanahalfwidth: 0xFF73,
+ ukcyrillic: 0x0479,
+ ukorean: 0x315C,
+ umacron: 0x016B,
+ umacroncyrillic: 0x04EF,
+ umacrondieresis: 0x1E7B,
+ umatragurmukhi: 0x0A41,
+ umonospace: 0xFF55,
+ underscore: 0x005F,
+ underscoredbl: 0x2017,
+ underscoremonospace: 0xFF3F,
+ underscorevertical: 0xFE33,
+ underscorewavy: 0xFE4F,
+ union: 0x222A,
+ universal: 0x2200,
+ uogonek: 0x0173,
+ uparen: 0x24B0,
+ upblock: 0x2580,
+ upperdothebrew: 0x05C4,
+ upsilon: 0x03C5,
+ upsilondieresis: 0x03CB,
+ upsilondieresistonos: 0x03B0,
+ upsilonlatin: 0x028A,
+ upsilontonos: 0x03CD,
+ uptackbelowcmb: 0x031D,
+ uptackmod: 0x02D4,
+ uragurmukhi: 0x0A73,
+ uring: 0x016F,
+ ushortcyrillic: 0x045E,
+ usmallhiragana: 0x3045,
+ usmallkatakana: 0x30A5,
+ usmallkatakanahalfwidth: 0xFF69,
+ ustraightcyrillic: 0x04AF,
+ ustraightstrokecyrillic: 0x04B1,
+ utilde: 0x0169,
+ utildeacute: 0x1E79,
+ utildebelow: 0x1E75,
+ uubengali: 0x098A,
+ uudeva: 0x090A,
+ uugujarati: 0x0A8A,
+ uugurmukhi: 0x0A0A,
+ uumatragurmukhi: 0x0A42,
+ uuvowelsignbengali: 0x09C2,
+ uuvowelsigndeva: 0x0942,
+ uuvowelsigngujarati: 0x0AC2,
+ uvowelsignbengali: 0x09C1,
+ uvowelsigndeva: 0x0941,
+ uvowelsigngujarati: 0x0AC1,
+ v: 0x0076,
+ vadeva: 0x0935,
+ vagujarati: 0x0AB5,
+ vagurmukhi: 0x0A35,
+ vakatakana: 0x30F7,
+ vav: 0x05D5,
+ vavdagesh: 0xFB35,
+ vavdagesh65: 0xFB35,
+ vavdageshhebrew: 0xFB35,
+ vavhebrew: 0x05D5,
+ vavholam: 0xFB4B,
+ vavholamhebrew: 0xFB4B,
+ vavvavhebrew: 0x05F0,
+ vavyodhebrew: 0x05F1,
+ vcircle: 0x24E5,
+ vdotbelow: 0x1E7F,
+ vecyrillic: 0x0432,
+ veharabic: 0x06A4,
+ vehfinalarabic: 0xFB6B,
+ vehinitialarabic: 0xFB6C,
+ vehmedialarabic: 0xFB6D,
+ vekatakana: 0x30F9,
+ venus: 0x2640,
+ verticalbar: 0x007C,
+ verticallineabovecmb: 0x030D,
+ verticallinebelowcmb: 0x0329,
+ verticallinelowmod: 0x02CC,
+ verticallinemod: 0x02C8,
+ vewarmenian: 0x057E,
+ vhook: 0x028B,
+ vikatakana: 0x30F8,
+ viramabengali: 0x09CD,
+ viramadeva: 0x094D,
+ viramagujarati: 0x0ACD,
+ visargabengali: 0x0983,
+ visargadeva: 0x0903,
+ visargagujarati: 0x0A83,
+ vmonospace: 0xFF56,
+ voarmenian: 0x0578,
+ voicediterationhiragana: 0x309E,
+ voicediterationkatakana: 0x30FE,
+ voicedmarkkana: 0x309B,
+ voicedmarkkanahalfwidth: 0xFF9E,
+ vokatakana: 0x30FA,
+ vparen: 0x24B1,
+ vtilde: 0x1E7D,
+ vturned: 0x028C,
+ vuhiragana: 0x3094,
+ vukatakana: 0x30F4,
+ w: 0x0077,
+ wacute: 0x1E83,
+ waekorean: 0x3159,
+ wahiragana: 0x308F,
+ wakatakana: 0x30EF,
+ wakatakanahalfwidth: 0xFF9C,
+ wakorean: 0x3158,
+ wasmallhiragana: 0x308E,
+ wasmallkatakana: 0x30EE,
+ wattosquare: 0x3357,
+ wavedash: 0x301C,
+ wavyunderscorevertical: 0xFE34,
+ wawarabic: 0x0648,
+ wawfinalarabic: 0xFEEE,
+ wawhamzaabovearabic: 0x0624,
+ wawhamzaabovefinalarabic: 0xFE86,
+ wbsquare: 0x33DD,
+ wcircle: 0x24E6,
+ wcircumflex: 0x0175,
+ wdieresis: 0x1E85,
+ wdotaccent: 0x1E87,
+ wdotbelow: 0x1E89,
+ wehiragana: 0x3091,
+ weierstrass: 0x2118,
+ wekatakana: 0x30F1,
+ wekorean: 0x315E,
+ weokorean: 0x315D,
+ wgrave: 0x1E81,
+ whitebullet: 0x25E6,
+ whitecircle: 0x25CB,
+ whitecircleinverse: 0x25D9,
+ whitecornerbracketleft: 0x300E,
+ whitecornerbracketleftvertical: 0xFE43,
+ whitecornerbracketright: 0x300F,
+ whitecornerbracketrightvertical: 0xFE44,
+ whitediamond: 0x25C7,
+ whitediamondcontainingblacksmalldiamond: 0x25C8,
+ whitedownpointingsmalltriangle: 0x25BF,
+ whitedownpointingtriangle: 0x25BD,
+ whiteleftpointingsmalltriangle: 0x25C3,
+ whiteleftpointingtriangle: 0x25C1,
+ whitelenticularbracketleft: 0x3016,
+ whitelenticularbracketright: 0x3017,
+ whiterightpointingsmalltriangle: 0x25B9,
+ whiterightpointingtriangle: 0x25B7,
+ whitesmallsquare: 0x25AB,
+ whitesmilingface: 0x263A,
+ whitesquare: 0x25A1,
+ whitestar: 0x2606,
+ whitetelephone: 0x260F,
+ whitetortoiseshellbracketleft: 0x3018,
+ whitetortoiseshellbracketright: 0x3019,
+ whiteuppointingsmalltriangle: 0x25B5,
+ whiteuppointingtriangle: 0x25B3,
+ wihiragana: 0x3090,
+ wikatakana: 0x30F0,
+ wikorean: 0x315F,
+ wmonospace: 0xFF57,
+ wohiragana: 0x3092,
+ wokatakana: 0x30F2,
+ wokatakanahalfwidth: 0xFF66,
+ won: 0x20A9,
+ wonmonospace: 0xFFE6,
+ wowaenthai: 0x0E27,
+ wparen: 0x24B2,
+ wring: 0x1E98,
+ wsuperior: 0x02B7,
+ wturned: 0x028D,
+ wynn: 0x01BF,
+ x: 0x0078,
+ xabovecmb: 0x033D,
+ xbopomofo: 0x3112,
+ xcircle: 0x24E7,
+ xdieresis: 0x1E8D,
+ xdotaccent: 0x1E8B,
+ xeharmenian: 0x056D,
+ xi: 0x03BE,
+ xmonospace: 0xFF58,
+ xparen: 0x24B3,
+ xsuperior: 0x02E3,
+ y: 0x0079,
+ yaadosquare: 0x334E,
+ yabengali: 0x09AF,
+ yacute: 0x00FD,
+ yadeva: 0x092F,
+ yaekorean: 0x3152,
+ yagujarati: 0x0AAF,
+ yagurmukhi: 0x0A2F,
+ yahiragana: 0x3084,
+ yakatakana: 0x30E4,
+ yakatakanahalfwidth: 0xFF94,
+ yakorean: 0x3151,
+ yamakkanthai: 0x0E4E,
+ yasmallhiragana: 0x3083,
+ yasmallkatakana: 0x30E3,
+ yasmallkatakanahalfwidth: 0xFF6C,
+ yatcyrillic: 0x0463,
+ ycircle: 0x24E8,
+ ycircumflex: 0x0177,
+ ydieresis: 0x00FF,
+ ydotaccent: 0x1E8F,
+ ydotbelow: 0x1EF5,
+ yeharabic: 0x064A,
+ yehbarreearabic: 0x06D2,
+ yehbarreefinalarabic: 0xFBAF,
+ yehfinalarabic: 0xFEF2,
+ yehhamzaabovearabic: 0x0626,
+ yehhamzaabovefinalarabic: 0xFE8A,
+ yehhamzaaboveinitialarabic: 0xFE8B,
+ yehhamzaabovemedialarabic: 0xFE8C,
+ yehinitialarabic: 0xFEF3,
+ yehmedialarabic: 0xFEF4,
+ yehmeeminitialarabic: 0xFCDD,
+ yehmeemisolatedarabic: 0xFC58,
+ yehnoonfinalarabic: 0xFC94,
+ yehthreedotsbelowarabic: 0x06D1,
+ yekorean: 0x3156,
+ yen: 0x00A5,
+ yenmonospace: 0xFFE5,
+ yeokorean: 0x3155,
+ yeorinhieuhkorean: 0x3186,
+ yerahbenyomohebrew: 0x05AA,
+ yerahbenyomolefthebrew: 0x05AA,
+ yericyrillic: 0x044B,
+ yerudieresiscyrillic: 0x04F9,
+ yesieungkorean: 0x3181,
+ yesieungpansioskorean: 0x3183,
+ yesieungsioskorean: 0x3182,
+ yetivhebrew: 0x059A,
+ ygrave: 0x1EF3,
+ yhook: 0x01B4,
+ yhookabove: 0x1EF7,
+ yiarmenian: 0x0575,
+ yicyrillic: 0x0457,
+ yikorean: 0x3162,
+ yinyang: 0x262F,
+ yiwnarmenian: 0x0582,
+ ymonospace: 0xFF59,
+ yod: 0x05D9,
+ yoddagesh: 0xFB39,
+ yoddageshhebrew: 0xFB39,
+ yodhebrew: 0x05D9,
+ yodyodhebrew: 0x05F2,
+ yodyodpatahhebrew: 0xFB1F,
+ yohiragana: 0x3088,
+ yoikorean: 0x3189,
+ yokatakana: 0x30E8,
+ yokatakanahalfwidth: 0xFF96,
+ yokorean: 0x315B,
+ yosmallhiragana: 0x3087,
+ yosmallkatakana: 0x30E7,
+ yosmallkatakanahalfwidth: 0xFF6E,
+ yotgreek: 0x03F3,
+ yoyaekorean: 0x3188,
+ yoyakorean: 0x3187,
+ yoyakthai: 0x0E22,
+ yoyingthai: 0x0E0D,
+ yparen: 0x24B4,
+ ypogegrammeni: 0x037A,
+ ypogegrammenigreekcmb: 0x0345,
+ yr: 0x01A6,
+ yring: 0x1E99,
+ ysuperior: 0x02B8,
+ ytilde: 0x1EF9,
+ yturned: 0x028E,
+ yuhiragana: 0x3086,
+ yuikorean: 0x318C,
+ yukatakana: 0x30E6,
+ yukatakanahalfwidth: 0xFF95,
+ yukorean: 0x3160,
+ yusbigcyrillic: 0x046B,
+ yusbigiotifiedcyrillic: 0x046D,
+ yuslittlecyrillic: 0x0467,
+ yuslittleiotifiedcyrillic: 0x0469,
+ yusmallhiragana: 0x3085,
+ yusmallkatakana: 0x30E5,
+ yusmallkatakanahalfwidth: 0xFF6D,
+ yuyekorean: 0x318B,
+ yuyeokorean: 0x318A,
+ yyabengali: 0x09DF,
+ yyadeva: 0x095F,
+ z: 0x007A,
+ zaarmenian: 0x0566,
+ zacute: 0x017A,
+ zadeva: 0x095B,
+ zagurmukhi: 0x0A5B,
+ zaharabic: 0x0638,
+ zahfinalarabic: 0xFEC6,
+ zahinitialarabic: 0xFEC7,
+ zahiragana: 0x3056,
+ zahmedialarabic: 0xFEC8,
+ zainarabic: 0x0632,
+ zainfinalarabic: 0xFEB0,
+ zakatakana: 0x30B6,
+ zaqefgadolhebrew: 0x0595,
+ zaqefqatanhebrew: 0x0594,
+ zarqahebrew: 0x0598,
+ zayin: 0x05D6,
+ zayindagesh: 0xFB36,
+ zayindageshhebrew: 0xFB36,
+ zayinhebrew: 0x05D6,
+ zbopomofo: 0x3117,
+ zcaron: 0x017E,
+ zcircle: 0x24E9,
+ zcircumflex: 0x1E91,
+ zcurl: 0x0291,
+ zdot: 0x017C,
+ zdotaccent: 0x017C,
+ zdotbelow: 0x1E93,
+ zecyrillic: 0x0437,
+ zedescendercyrillic: 0x0499,
+ zedieresiscyrillic: 0x04DF,
+ zehiragana: 0x305C,
+ zekatakana: 0x30BC,
+ zero: 0x0030,
+ zeroarabic: 0x0660,
+ zerobengali: 0x09E6,
+ zerodeva: 0x0966,
+ zerogujarati: 0x0AE6,
+ zerogurmukhi: 0x0A66,
+ zerohackarabic: 0x0660,
+ zeroinferior: 0x2080,
+ zeromonospace: 0xFF10,
+ zerooldstyle: 0xF730,
+ zeropersian: 0x06F0,
+ zerosuperior: 0x2070,
+ zerothai: 0x0E50,
+ zerowidthjoiner: 0xFEFF,
+ zerowidthnonjoiner: 0x200C,
+ zerowidthspace: 0x200B,
+ zeta: 0x03B6,
+ zhbopomofo: 0x3113,
+ zhearmenian: 0x056A,
+ zhebrevecyrillic: 0x04C2,
+ zhecyrillic: 0x0436,
+ zhedescendercyrillic: 0x0497,
+ zhedieresiscyrillic: 0x04DD,
+ zihiragana: 0x3058,
+ zikatakana: 0x30B8,
+ zinorhebrew: 0x05AE,
+ zlinebelow: 0x1E95,
+ zmonospace: 0xFF5A,
+ zohiragana: 0x305E,
+ zokatakana: 0x30BE,
+ zparen: 0x24B5,
+ zretroflexhook: 0x0290,
+ zstroke: 0x01B6,
+ zuhiragana: 0x305A,
+ zukatakana: 0x30BA,
+ '.notdef': 0x0000
+};
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var PDFImage = (function PDFImageClosure() {
+ /**
+ * Decode the image in the main thread if it supported. Resovles the promise
+ * when the image data is ready.
+ */
+ function handleImageData(handler, xref, res, image, promise) {
+ if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) {
+ // For natively supported jpegs send them to the main thread for decoding.
+ var dict = image.dict;
+ var colorSpace = dict.get('ColorSpace', 'CS');
+ colorSpace = ColorSpace.parse(colorSpace, xref, res);
+ var numComps = colorSpace.numComps;
+ handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
+ var data = message.data;
+ var stream = new Stream(data, 0, data.length, image.dict);
+ promise.resolve(stream);
+ });
+ } else {
+ promise.resolve(image);
+ }
+ }
+ /**
+ * Decode and clamp a value. The formula is different from the spec because we
+ * don't decode to float range [0,1], we decode it in the [0,max] range.
+ */
+ function decodeAndClamp(value, addend, coefficient, max) {
+ value = addend + value * coefficient;
+ // Clamp the value to the range
+ return value < 0 ? 0 : value > max ? max : value;
+ }
+ function PDFImage(xref, res, image, inline, smask) {
+ this.image = image;
+ if (image.getParams) {
+ // JPX/JPEG2000 streams directly contain bits per component
+ // and color space mode information.
+ TODO('get params from actual stream');
+ // var bits = ...
+ // var colorspace = ...
+ }
+ // TODO cache rendered images?
+
+ var dict = image.dict;
+ this.width = dict.get('Width', 'W');
+ this.height = dict.get('Height', 'H');
+
+ if (this.width < 1 || this.height < 1)
+ error('Invalid image width: ' + this.width + ' or height: ' +
+ this.height);
+
+ this.interpolate = dict.get('Interpolate', 'I') || false;
+ this.imageMask = dict.get('ImageMask', 'IM') || false;
+
+ var bitsPerComponent = image.bitsPerComponent;
+ if (!bitsPerComponent) {
+ bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
+ if (!bitsPerComponent) {
+ if (this.imageMask)
+ bitsPerComponent = 1;
+ else
+ error('Bits per component missing in image: ' + this.imageMask);
+ }
+ }
+ this.bpc = bitsPerComponent;
+
+ if (!this.imageMask) {
+ var colorSpace = dict.get('ColorSpace', 'CS');
+ if (!colorSpace) {
+ TODO('JPX images (which don"t require color spaces');
+ colorSpace = new Name('DeviceRGB');
+ }
+ this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
+ this.numComps = this.colorSpace.numComps;
+ }
+
+ this.decode = dict.get('Decode', 'D');
+ this.needsDecode = false;
+ if (this.decode && this.colorSpace &&
+ !this.colorSpace.isDefaultDecode(this.decode)) {
+ this.needsDecode = true;
+ // Do some preprocessing to avoid more math.
+ var max = (1 << bitsPerComponent) - 1;
+ this.decodeCoefficients = [];
+ this.decodeAddends = [];
+ for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
+ var dmin = this.decode[i];
+ var dmax = this.decode[i + 1];
+ this.decodeCoefficients[j] = dmax - dmin;
+ this.decodeAddends[j] = max * dmin;
+ }
+ }
+
+ var mask = dict.get('Mask');
+
+ if (mask) {
+ TODO('masked images');
+ } else if (smask) {
+ this.smask = new PDFImage(xref, res, smask, false);
+ }
+ }
+ /**
+ * Handles processing of image data and calls the callback with an argument
+ * of a PDFImage when the image is ready to be used.
+ */
+ PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref,
+ res, image, inline) {
+ var imageDataPromise = new Promise();
+ var smaskPromise = new Promise();
+ // The image data and smask data may not be ready yet, wait till both are
+ // resolved.
+ Promise.all([imageDataPromise, smaskPromise]).then(function(results) {
+ var imageData = results[0], smaskData = results[1];
+ var image = new PDFImage(xref, res, imageData, inline, smaskData);
+ callback(image);
+ });
+
+ handleImageData(handler, xref, res, image, imageDataPromise);
+
+ var smask = image.dict.get('SMask');
+ if (smask)
+ handleImageData(handler, xref, res, smask, smaskPromise);
+ else
+ smaskPromise.resolve(null);
+ };
+
+ /**
+ * Resize an image using the nearest neighbor algorithm. Currently only
+ * supports one and three component images.
+ * @param {TypedArray} pixels The original image with one component.
+ * @param {Number} bpc Number of bits per component.
+ * @param {Number} components Number of color components, 1 or 3 is supported.
+ * @param {Number} w1 Original width.
+ * @param {Number} h1 Original height.
+ * @param {Number} w2 New width.
+ * @param {Number} h2 New height.
+ * @return {TypedArray} Resized image data.
+ */
+ PDFImage.resize = function PDFImage_resize(pixels, bpc, components,
+ w1, h1, w2, h2) {
+ var length = w2 * h2 * components;
+ var temp = bpc <= 8 ? new Uint8Array(length) :
+ bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
+ var xRatio = w1 / w2;
+ var yRatio = h1 / h2;
+ var px, py, newIndex, oldIndex;
+ for (var i = 0; i < h2; i++) {
+ for (var j = 0; j < w2; j++) {
+ px = Math.floor(j * xRatio);
+ py = Math.floor(i * yRatio);
+ newIndex = (i * w2) + j;
+ oldIndex = ((py * w1) + px);
+ if (components === 1) {
+ temp[newIndex] = pixels[oldIndex];
+ } else if (components === 3) {
+ newIndex *= 3;
+ oldIndex *= 3;
+ temp[newIndex] = pixels[oldIndex];
+ temp[newIndex + 1] = pixels[oldIndex + 1];
+ temp[newIndex + 2] = pixels[oldIndex + 2];
+ }
+ }
+ }
+ return temp;
+ };
+
+ PDFImage.prototype = {
+ get drawWidth() {
+ if (!this.smask)
+ return this.width;
+ return Math.max(this.width, this.smask.width);
+ },
+ get drawHeight() {
+ if (!this.smask)
+ return this.height;
+ return Math.max(this.height, this.smask.height);
+ },
+ getComponents: function PDFImage_getComponents(buffer) {
+ var bpc = this.bpc;
+ var needsDecode = this.needsDecode;
+ var decodeMap = this.decode;
+
+ // This image doesn't require any extra work.
+ if (bpc == 8 && !needsDecode)
+ return buffer;
+
+ var bufferLength = buffer.length;
+ var width = this.width;
+ var height = this.height;
+ var numComps = this.numComps;
+
+ var length = width * height * numComps;
+ var bufferPos = 0;
+ var output = bpc <= 8 ? new Uint8Array(length) :
+ bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
+ var rowComps = width * numComps;
+ var decodeAddends, decodeCoefficients;
+ if (needsDecode) {
+ decodeAddends = this.decodeAddends;
+ decodeCoefficients = this.decodeCoefficients;
+ }
+ var max = (1 << bpc) - 1;
+
+ if (bpc == 8) {
+ // Optimization for reading 8 bpc images that have a decode.
+ for (var i = 0, ii = length; i < ii; ++i) {
+ var compIndex = i % numComps;
+ var value = buffer[i];
+ value = decodeAndClamp(value, decodeAddends[compIndex],
+ decodeCoefficients[compIndex], max);
+ output[i] = value;
+ }
+ } else if (bpc == 1) {
+ // Optimization for reading 1 bpc images.
+ var valueZero = 0, valueOne = 1;
+ if (decodeMap) {
+ valueZero = decodeMap[0] ? 1 : 0;
+ valueOne = decodeMap[1] ? 1 : 0;
+ }
+ var mask = 0;
+ var buf = 0;
+
+ for (var i = 0, ii = length; i < ii; ++i) {
+ if (i % rowComps == 0) {
+ mask = 0;
+ buf = 0;
+ } else {
+ mask >>= 1;
+ }
+
+ if (mask <= 0) {
+ buf = buffer[bufferPos++];
+ mask = 128;
+ }
+
+ output[i] = !(buf & mask) ? valueZero : valueOne;
+ }
+ } else {
+ // The general case that handles all other bpc values.
+ var bits = 0, buf = 0;
+ for (var i = 0, ii = length; i < ii; ++i) {
+ if (i % rowComps == 0) {
+ buf = 0;
+ bits = 0;
+ }
+
+ while (bits < bpc) {
+ buf = (buf << 8) | buffer[bufferPos++];
+ bits += 8;
+ }
+
+ var remainingBits = bits - bpc;
+ var value = buf >> remainingBits;
+ if (needsDecode) {
+ var compIndex = i % numComps;
+ value = decodeAndClamp(value, decodeAddends[compIndex],
+ decodeCoefficients[compIndex], max);
+ }
+ output[i] = value;
+ buf = buf & ((1 << remainingBits) - 1);
+ bits = remainingBits;
+ }
+ }
+ return output;
+ },
+ getOpacity: function PDFImage_getOpacity(width, height) {
+ var smask = this.smask;
+ var originalWidth = this.width;
+ var originalHeight = this.height;
+ var buf;
+
+ if (smask) {
+ var sw = smask.width;
+ var sh = smask.height;
+ buf = new Uint8Array(sw * sh);
+ smask.fillGrayBuffer(buf);
+ if (sw != width || sh != height)
+ buf = PDFImage.resize(buf, smask.bps, 1, sw, sh, width, height);
+ } else {
+ buf = new Uint8Array(width * height);
+ for (var i = 0, ii = width * height; i < ii; ++i)
+ buf[i] = 255;
+ }
+ return buf;
+ },
+ applyStencilMask: function PDFImage_applyStencilMask(buffer,
+ inverseDecode) {
+ var width = this.width, height = this.height;
+ var bitStrideLength = (width + 7) >> 3;
+ var imgArray = this.getImageBytes(bitStrideLength * height);
+ var imgArrayPos = 0;
+ var i, j, mask, buf;
+ // removing making non-masked pixels transparent
+ var bufferPos = 3; // alpha component offset
+ for (i = 0; i < height; i++) {
+ mask = 0;
+ for (j = 0; j < width; j++) {
+ if (!mask) {
+ buf = imgArray[imgArrayPos++];
+ mask = 128;
+ }
+ if (!(buf & mask) == inverseDecode) {
+ buffer[bufferPos] = 0;
+ }
+ bufferPos += 4;
+ mask >>= 1;
+ }
+ }
+ },
+ fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
+ var numComps = this.numComps;
+ var originalWidth = this.width;
+ var originalHeight = this.height;
+ var bpc = this.bpc;
+
+ // rows start at byte boundary;
+ var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
+ var imgArray = this.getImageBytes(originalHeight * rowBytes);
+
+ var comps = this.colorSpace.getRgbBuffer(
+ this.getComponents(imgArray), bpc);
+ if (originalWidth != width || originalHeight != height)
+ comps = PDFImage.resize(comps, this.bpc, 3, originalWidth,
+ originalHeight, width, height);
+ var compsPos = 0;
+ var opacity = this.getOpacity(width, height);
+ var opacityPos = 0;
+ var length = width * height * 4;
+
+ for (var i = 0; i < length; i += 4) {
+ buffer[i] = comps[compsPos++];
+ buffer[i + 1] = comps[compsPos++];
+ buffer[i + 2] = comps[compsPos++];
+ buffer[i + 3] = opacity[opacityPos++];
+ }
+ },
+ fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
+ var numComps = this.numComps;
+ if (numComps != 1)
+ error('Reading gray scale from a color image: ' + numComps);
+
+ var width = this.width;
+ var height = this.height;
+ var bpc = this.bpc;
+
+ // rows start at byte boundary;
+ var rowBytes = (width * numComps * bpc + 7) >> 3;
+ var imgArray = this.getImageBytes(height * rowBytes);
+
+ var comps = this.getComponents(imgArray);
+ var length = width * height;
+ // we aren't using a colorspace so we need to scale the value
+ var scale = 255 / ((1 << bpc) - 1);
+ for (var i = 0; i < length; ++i)
+ buffer[i] = (scale * comps[i]) | 0;
+ },
+ getImageBytes: function PDFImage_getImageBytes(length) {
+ this.image.reset();
+ return this.image.getBytes(length);
+ }
+ };
+ return PDFImage;
+})();
+
+function loadJpegStream(id, imageData, objs) {
+ var img = new Image();
+ img.onload = (function loadJpegStream_onloadClosure() {
+ objs.resolve(id, img);
+ });
+ img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+}
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// The Metrics object contains glyph widths (in glyph space units).
+// As per PDF spec, for most fonts (Type 3 being an exception) a glyph
+// space unit corresponds to 1/1000th of text space unit.
+var Metrics = {
+ 'Courier': 600,
+ 'Courier-Bold': 600,
+ 'Courier-BoldOblique': 600,
+ 'Courier-Oblique': 600,
+ 'Helvetica' : {
+ 'space': 278,
+ 'exclam': 278,
+ 'quotedbl': 355,
+ 'numbersign': 556,
+ 'dollar': 556,
+ 'percent': 889,
+ 'ampersand': 667,
+ 'quoteright': 222,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 389,
+ 'plus': 584,
+ 'comma': 278,
+ 'hyphen': 333,
+ 'period': 278,
+ 'slash': 278,
+ 'zero': 556,
+ 'one': 556,
+ 'two': 556,
+ 'three': 556,
+ 'four': 556,
+ 'five': 556,
+ 'six': 556,
+ 'seven': 556,
+ 'eight': 556,
+ 'nine': 556,
+ 'colon': 278,
+ 'semicolon': 278,
+ 'less': 584,
+ 'equal': 584,
+ 'greater': 584,
+ 'question': 556,
+ 'at': 1015,
+ 'A': 667,
+ 'B': 667,
+ 'C': 722,
+ 'D': 722,
+ 'E': 667,
+ 'F': 611,
+ 'G': 778,
+ 'H': 722,
+ 'I': 278,
+ 'J': 500,
+ 'K': 667,
+ 'L': 556,
+ 'M': 833,
+ 'N': 722,
+ 'O': 778,
+ 'P': 667,
+ 'Q': 778,
+ 'R': 722,
+ 'S': 667,
+ 'T': 611,
+ 'U': 722,
+ 'V': 667,
+ 'W': 944,
+ 'X': 667,
+ 'Y': 667,
+ 'Z': 611,
+ 'bracketleft': 278,
+ 'backslash': 278,
+ 'bracketright': 278,
+ 'asciicircum': 469,
+ 'underscore': 556,
+ 'quoteleft': 222,
+ 'a': 556,
+ 'b': 556,
+ 'c': 500,
+ 'd': 556,
+ 'e': 556,
+ 'f': 278,
+ 'g': 556,
+ 'h': 556,
+ 'i': 222,
+ 'j': 222,
+ 'k': 500,
+ 'l': 222,
+ 'm': 833,
+ 'n': 556,
+ 'o': 556,
+ 'p': 556,
+ 'q': 556,
+ 'r': 333,
+ 's': 500,
+ 't': 278,
+ 'u': 556,
+ 'v': 500,
+ 'w': 722,
+ 'x': 500,
+ 'y': 500,
+ 'z': 500,
+ 'braceleft': 334,
+ 'bar': 260,
+ 'braceright': 334,
+ 'asciitilde': 584,
+ 'exclamdown': 333,
+ 'cent': 556,
+ 'sterling': 556,
+ 'fraction': 167,
+ 'yen': 556,
+ 'florin': 556,
+ 'section': 556,
+ 'currency': 556,
+ 'quotesingle': 191,
+ 'quotedblleft': 333,
+ 'guillemotleft': 556,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 500,
+ 'fl': 500,
+ 'endash': 556,
+ 'dagger': 556,
+ 'daggerdbl': 556,
+ 'periodcentered': 278,
+ 'paragraph': 537,
+ 'bullet': 350,
+ 'quotesinglbase': 222,
+ 'quotedblbase': 333,
+ 'quotedblright': 333,
+ 'guillemotright': 556,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 611,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 1000,
+ 'ordfeminine': 370,
+ 'Lslash': 556,
+ 'Oslash': 778,
+ 'OE': 1000,
+ 'ordmasculine': 365,
+ 'ae': 889,
+ 'dotlessi': 278,
+ 'lslash': 222,
+ 'oslash': 611,
+ 'oe': 944,
+ 'germandbls': 611,
+ 'Idieresis': 278,
+ 'eacute': 556,
+ 'abreve': 556,
+ 'uhungarumlaut': 556,
+ 'ecaron': 556,
+ 'Ydieresis': 667,
+ 'divide': 584,
+ 'Yacute': 667,
+ 'Acircumflex': 667,
+ 'aacute': 556,
+ 'Ucircumflex': 722,
+ 'yacute': 500,
+ 'scommaaccent': 500,
+ 'ecircumflex': 556,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 556,
+ 'Uacute': 722,
+ 'uogonek': 556,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 737,
+ 'Emacron': 667,
+ 'ccaron': 500,
+ 'aring': 556,
+ 'Ncommaaccent': 722,
+ 'lacute': 222,
+ 'agrave': 556,
+ 'Tcommaaccent': 611,
+ 'Cacute': 722,
+ 'atilde': 556,
+ 'Edotaccent': 667,
+ 'scaron': 500,
+ 'scedilla': 500,
+ 'iacute': 278,
+ 'lozenge': 471,
+ 'Rcaron': 722,
+ 'Gcommaaccent': 778,
+ 'ucircumflex': 556,
+ 'acircumflex': 556,
+ 'Amacron': 667,
+ 'rcaron': 333,
+ 'ccedilla': 500,
+ 'Zdotaccent': 611,
+ 'Thorn': 667,
+ 'Omacron': 778,
+ 'Racute': 722,
+ 'Sacute': 667,
+ 'dcaron': 643,
+ 'Umacron': 722,
+ 'uring': 556,
+ 'threesuperior': 333,
+ 'Ograve': 778,
+ 'Agrave': 667,
+ 'Abreve': 667,
+ 'multiply': 584,
+ 'uacute': 556,
+ 'Tcaron': 611,
+ 'partialdiff': 476,
+ 'ydieresis': 500,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 556,
+ 'edieresis': 556,
+ 'cacute': 500,
+ 'nacute': 556,
+ 'umacron': 556,
+ 'Ncaron': 722,
+ 'Iacute': 278,
+ 'plusminus': 584,
+ 'brokenbar': 260,
+ 'registered': 737,
+ 'Gbreve': 778,
+ 'Idotaccent': 278,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 333,
+ 'omacron': 556,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 722,
+ 'lcommaaccent': 222,
+ 'tcaron': 317,
+ 'eogonek': 556,
+ 'Uogonek': 722,
+ 'Aacute': 667,
+ 'Adieresis': 667,
+ 'egrave': 556,
+ 'zacute': 500,
+ 'iogonek': 222,
+ 'Oacute': 778,
+ 'oacute': 556,
+ 'amacron': 556,
+ 'sacute': 500,
+ 'idieresis': 278,
+ 'Ocircumflex': 778,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 556,
+ 'twosuperior': 333,
+ 'Odieresis': 778,
+ 'mu': 556,
+ 'igrave': 278,
+ 'ohungarumlaut': 556,
+ 'Eogonek': 667,
+ 'dcroat': 556,
+ 'threequarters': 834,
+ 'Scedilla': 667,
+ 'lcaron': 299,
+ 'Kcommaaccent': 667,
+ 'Lacute': 556,
+ 'trademark': 1000,
+ 'edotaccent': 556,
+ 'Igrave': 278,
+ 'Imacron': 278,
+ 'Lcaron': 556,
+ 'onehalf': 834,
+ 'lessequal': 549,
+ 'ocircumflex': 556,
+ 'ntilde': 556,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 556,
+ 'gbreve': 556,
+ 'onequarter': 834,
+ 'Scaron': 667,
+ 'Scommaaccent': 667,
+ 'Ohungarumlaut': 778,
+ 'degree': 400,
+ 'ograve': 556,
+ 'Ccaron': 722,
+ 'ugrave': 556,
+ 'radical': 453,
+ 'Dcaron': 722,
+ 'rcommaaccent': 333,
+ 'Ntilde': 722,
+ 'otilde': 556,
+ 'Rcommaaccent': 722,
+ 'Lcommaaccent': 556,
+ 'Atilde': 667,
+ 'Aogonek': 667,
+ 'Aring': 667,
+ 'Otilde': 778,
+ 'zdotaccent': 500,
+ 'Ecaron': 667,
+ 'Iogonek': 278,
+ 'kcommaaccent': 500,
+ 'minus': 584,
+ 'Icircumflex': 278,
+ 'ncaron': 556,
+ 'tcommaaccent': 278,
+ 'logicalnot': 584,
+ 'odieresis': 556,
+ 'udieresis': 556,
+ 'notequal': 549,
+ 'gcommaaccent': 556,
+ 'eth': 556,
+ 'zcaron': 500,
+ 'ncommaaccent': 556,
+ 'onesuperior': 333,
+ 'imacron': 278,
+ 'Euro': 556
+ },
+ 'Helvetica-Bold': {
+ 'space': 278,
+ 'exclam': 333,
+ 'quotedbl': 474,
+ 'numbersign': 556,
+ 'dollar': 556,
+ 'percent': 889,
+ 'ampersand': 722,
+ 'quoteright': 278,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 389,
+ 'plus': 584,
+ 'comma': 278,
+ 'hyphen': 333,
+ 'period': 278,
+ 'slash': 278,
+ 'zero': 556,
+ 'one': 556,
+ 'two': 556,
+ 'three': 556,
+ 'four': 556,
+ 'five': 556,
+ 'six': 556,
+ 'seven': 556,
+ 'eight': 556,
+ 'nine': 556,
+ 'colon': 333,
+ 'semicolon': 333,
+ 'less': 584,
+ 'equal': 584,
+ 'greater': 584,
+ 'question': 611,
+ 'at': 975,
+ 'A': 722,
+ 'B': 722,
+ 'C': 722,
+ 'D': 722,
+ 'E': 667,
+ 'F': 611,
+ 'G': 778,
+ 'H': 722,
+ 'I': 278,
+ 'J': 556,
+ 'K': 722,
+ 'L': 611,
+ 'M': 833,
+ 'N': 722,
+ 'O': 778,
+ 'P': 667,
+ 'Q': 778,
+ 'R': 722,
+ 'S': 667,
+ 'T': 611,
+ 'U': 722,
+ 'V': 667,
+ 'W': 944,
+ 'X': 667,
+ 'Y': 667,
+ 'Z': 611,
+ 'bracketleft': 333,
+ 'backslash': 278,
+ 'bracketright': 333,
+ 'asciicircum': 584,
+ 'underscore': 556,
+ 'quoteleft': 278,
+ 'a': 556,
+ 'b': 611,
+ 'c': 556,
+ 'd': 611,
+ 'e': 556,
+ 'f': 333,
+ 'g': 611,
+ 'h': 611,
+ 'i': 278,
+ 'j': 278,
+ 'k': 556,
+ 'l': 278,
+ 'm': 889,
+ 'n': 611,
+ 'o': 611,
+ 'p': 611,
+ 'q': 611,
+ 'r': 389,
+ 's': 556,
+ 't': 333,
+ 'u': 611,
+ 'v': 556,
+ 'w': 778,
+ 'x': 556,
+ 'y': 556,
+ 'z': 500,
+ 'braceleft': 389,
+ 'bar': 280,
+ 'braceright': 389,
+ 'asciitilde': 584,
+ 'exclamdown': 333,
+ 'cent': 556,
+ 'sterling': 556,
+ 'fraction': 167,
+ 'yen': 556,
+ 'florin': 556,
+ 'section': 556,
+ 'currency': 556,
+ 'quotesingle': 238,
+ 'quotedblleft': 500,
+ 'guillemotleft': 556,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 611,
+ 'fl': 611,
+ 'endash': 556,
+ 'dagger': 556,
+ 'daggerdbl': 556,
+ 'periodcentered': 278,
+ 'paragraph': 556,
+ 'bullet': 350,
+ 'quotesinglbase': 278,
+ 'quotedblbase': 500,
+ 'quotedblright': 500,
+ 'guillemotright': 556,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 611,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 1000,
+ 'ordfeminine': 370,
+ 'Lslash': 611,
+ 'Oslash': 778,
+ 'OE': 1000,
+ 'ordmasculine': 365,
+ 'ae': 889,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 611,
+ 'oe': 944,
+ 'germandbls': 611,
+ 'Idieresis': 278,
+ 'eacute': 556,
+ 'abreve': 556,
+ 'uhungarumlaut': 611,
+ 'ecaron': 556,
+ 'Ydieresis': 667,
+ 'divide': 584,
+ 'Yacute': 667,
+ 'Acircumflex': 722,
+ 'aacute': 556,
+ 'Ucircumflex': 722,
+ 'yacute': 556,
+ 'scommaaccent': 556,
+ 'ecircumflex': 556,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 556,
+ 'Uacute': 722,
+ 'uogonek': 611,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 737,
+ 'Emacron': 667,
+ 'ccaron': 556,
+ 'aring': 556,
+ 'Ncommaaccent': 722,
+ 'lacute': 278,
+ 'agrave': 556,
+ 'Tcommaaccent': 611,
+ 'Cacute': 722,
+ 'atilde': 556,
+ 'Edotaccent': 667,
+ 'scaron': 556,
+ 'scedilla': 556,
+ 'iacute': 278,
+ 'lozenge': 494,
+ 'Rcaron': 722,
+ 'Gcommaaccent': 778,
+ 'ucircumflex': 611,
+ 'acircumflex': 556,
+ 'Amacron': 722,
+ 'rcaron': 389,
+ 'ccedilla': 556,
+ 'Zdotaccent': 611,
+ 'Thorn': 667,
+ 'Omacron': 778,
+ 'Racute': 722,
+ 'Sacute': 667,
+ 'dcaron': 743,
+ 'Umacron': 722,
+ 'uring': 611,
+ 'threesuperior': 333,
+ 'Ograve': 778,
+ 'Agrave': 722,
+ 'Abreve': 722,
+ 'multiply': 584,
+ 'uacute': 611,
+ 'Tcaron': 611,
+ 'partialdiff': 494,
+ 'ydieresis': 556,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 556,
+ 'edieresis': 556,
+ 'cacute': 556,
+ 'nacute': 611,
+ 'umacron': 611,
+ 'Ncaron': 722,
+ 'Iacute': 278,
+ 'plusminus': 584,
+ 'brokenbar': 280,
+ 'registered': 737,
+ 'Gbreve': 778,
+ 'Idotaccent': 278,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 389,
+ 'omacron': 611,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 722,
+ 'lcommaaccent': 278,
+ 'tcaron': 389,
+ 'eogonek': 556,
+ 'Uogonek': 722,
+ 'Aacute': 722,
+ 'Adieresis': 722,
+ 'egrave': 556,
+ 'zacute': 500,
+ 'iogonek': 278,
+ 'Oacute': 778,
+ 'oacute': 611,
+ 'amacron': 556,
+ 'sacute': 556,
+ 'idieresis': 278,
+ 'Ocircumflex': 778,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 611,
+ 'twosuperior': 333,
+ 'Odieresis': 778,
+ 'mu': 611,
+ 'igrave': 278,
+ 'ohungarumlaut': 611,
+ 'Eogonek': 667,
+ 'dcroat': 611,
+ 'threequarters': 834,
+ 'Scedilla': 667,
+ 'lcaron': 400,
+ 'Kcommaaccent': 722,
+ 'Lacute': 611,
+ 'trademark': 1000,
+ 'edotaccent': 556,
+ 'Igrave': 278,
+ 'Imacron': 278,
+ 'Lcaron': 611,
+ 'onehalf': 834,
+ 'lessequal': 549,
+ 'ocircumflex': 611,
+ 'ntilde': 611,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 556,
+ 'gbreve': 611,
+ 'onequarter': 834,
+ 'Scaron': 667,
+ 'Scommaaccent': 667,
+ 'Ohungarumlaut': 778,
+ 'degree': 400,
+ 'ograve': 611,
+ 'Ccaron': 722,
+ 'ugrave': 611,
+ 'radical': 549,
+ 'Dcaron': 722,
+ 'rcommaaccent': 389,
+ 'Ntilde': 722,
+ 'otilde': 611,
+ 'Rcommaaccent': 722,
+ 'Lcommaaccent': 611,
+ 'Atilde': 722,
+ 'Aogonek': 722,
+ 'Aring': 722,
+ 'Otilde': 778,
+ 'zdotaccent': 500,
+ 'Ecaron': 667,
+ 'Iogonek': 278,
+ 'kcommaaccent': 556,
+ 'minus': 584,
+ 'Icircumflex': 278,
+ 'ncaron': 611,
+ 'tcommaaccent': 333,
+ 'logicalnot': 584,
+ 'odieresis': 611,
+ 'udieresis': 611,
+ 'notequal': 549,
+ 'gcommaaccent': 611,
+ 'eth': 611,
+ 'zcaron': 500,
+ 'ncommaaccent': 611,
+ 'onesuperior': 333,
+ 'imacron': 278,
+ 'Euro': 556
+ },
+ 'Helvetica-BoldOblique': {
+ 'space': 278,
+ 'exclam': 333,
+ 'quotedbl': 474,
+ 'numbersign': 556,
+ 'dollar': 556,
+ 'percent': 889,
+ 'ampersand': 722,
+ 'quoteright': 278,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 389,
+ 'plus': 584,
+ 'comma': 278,
+ 'hyphen': 333,
+ 'period': 278,
+ 'slash': 278,
+ 'zero': 556,
+ 'one': 556,
+ 'two': 556,
+ 'three': 556,
+ 'four': 556,
+ 'five': 556,
+ 'six': 556,
+ 'seven': 556,
+ 'eight': 556,
+ 'nine': 556,
+ 'colon': 333,
+ 'semicolon': 333,
+ 'less': 584,
+ 'equal': 584,
+ 'greater': 584,
+ 'question': 611,
+ 'at': 975,
+ 'A': 722,
+ 'B': 722,
+ 'C': 722,
+ 'D': 722,
+ 'E': 667,
+ 'F': 611,
+ 'G': 778,
+ 'H': 722,
+ 'I': 278,
+ 'J': 556,
+ 'K': 722,
+ 'L': 611,
+ 'M': 833,
+ 'N': 722,
+ 'O': 778,
+ 'P': 667,
+ 'Q': 778,
+ 'R': 722,
+ 'S': 667,
+ 'T': 611,
+ 'U': 722,
+ 'V': 667,
+ 'W': 944,
+ 'X': 667,
+ 'Y': 667,
+ 'Z': 611,
+ 'bracketleft': 333,
+ 'backslash': 278,
+ 'bracketright': 333,
+ 'asciicircum': 584,
+ 'underscore': 556,
+ 'quoteleft': 278,
+ 'a': 556,
+ 'b': 611,
+ 'c': 556,
+ 'd': 611,
+ 'e': 556,
+ 'f': 333,
+ 'g': 611,
+ 'h': 611,
+ 'i': 278,
+ 'j': 278,
+ 'k': 556,
+ 'l': 278,
+ 'm': 889,
+ 'n': 611,
+ 'o': 611,
+ 'p': 611,
+ 'q': 611,
+ 'r': 389,
+ 's': 556,
+ 't': 333,
+ 'u': 611,
+ 'v': 556,
+ 'w': 778,
+ 'x': 556,
+ 'y': 556,
+ 'z': 500,
+ 'braceleft': 389,
+ 'bar': 280,
+ 'braceright': 389,
+ 'asciitilde': 584,
+ 'exclamdown': 333,
+ 'cent': 556,
+ 'sterling': 556,
+ 'fraction': 167,
+ 'yen': 556,
+ 'florin': 556,
+ 'section': 556,
+ 'currency': 556,
+ 'quotesingle': 238,
+ 'quotedblleft': 500,
+ 'guillemotleft': 556,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 611,
+ 'fl': 611,
+ 'endash': 556,
+ 'dagger': 556,
+ 'daggerdbl': 556,
+ 'periodcentered': 278,
+ 'paragraph': 556,
+ 'bullet': 350,
+ 'quotesinglbase': 278,
+ 'quotedblbase': 500,
+ 'quotedblright': 500,
+ 'guillemotright': 556,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 611,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 1000,
+ 'ordfeminine': 370,
+ 'Lslash': 611,
+ 'Oslash': 778,
+ 'OE': 1000,
+ 'ordmasculine': 365,
+ 'ae': 889,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 611,
+ 'oe': 944,
+ 'germandbls': 611,
+ 'Idieresis': 278,
+ 'eacute': 556,
+ 'abreve': 556,
+ 'uhungarumlaut': 611,
+ 'ecaron': 556,
+ 'Ydieresis': 667,
+ 'divide': 584,
+ 'Yacute': 667,
+ 'Acircumflex': 722,
+ 'aacute': 556,
+ 'Ucircumflex': 722,
+ 'yacute': 556,
+ 'scommaaccent': 556,
+ 'ecircumflex': 556,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 556,
+ 'Uacute': 722,
+ 'uogonek': 611,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 737,
+ 'Emacron': 667,
+ 'ccaron': 556,
+ 'aring': 556,
+ 'Ncommaaccent': 722,
+ 'lacute': 278,
+ 'agrave': 556,
+ 'Tcommaaccent': 611,
+ 'Cacute': 722,
+ 'atilde': 556,
+ 'Edotaccent': 667,
+ 'scaron': 556,
+ 'scedilla': 556,
+ 'iacute': 278,
+ 'lozenge': 494,
+ 'Rcaron': 722,
+ 'Gcommaaccent': 778,
+ 'ucircumflex': 611,
+ 'acircumflex': 556,
+ 'Amacron': 722,
+ 'rcaron': 389,
+ 'ccedilla': 556,
+ 'Zdotaccent': 611,
+ 'Thorn': 667,
+ 'Omacron': 778,
+ 'Racute': 722,
+ 'Sacute': 667,
+ 'dcaron': 743,
+ 'Umacron': 722,
+ 'uring': 611,
+ 'threesuperior': 333,
+ 'Ograve': 778,
+ 'Agrave': 722,
+ 'Abreve': 722,
+ 'multiply': 584,
+ 'uacute': 611,
+ 'Tcaron': 611,
+ 'partialdiff': 494,
+ 'ydieresis': 556,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 556,
+ 'edieresis': 556,
+ 'cacute': 556,
+ 'nacute': 611,
+ 'umacron': 611,
+ 'Ncaron': 722,
+ 'Iacute': 278,
+ 'plusminus': 584,
+ 'brokenbar': 280,
+ 'registered': 737,
+ 'Gbreve': 778,
+ 'Idotaccent': 278,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 389,
+ 'omacron': 611,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 722,
+ 'lcommaaccent': 278,
+ 'tcaron': 389,
+ 'eogonek': 556,
+ 'Uogonek': 722,
+ 'Aacute': 722,
+ 'Adieresis': 722,
+ 'egrave': 556,
+ 'zacute': 500,
+ 'iogonek': 278,
+ 'Oacute': 778,
+ 'oacute': 611,
+ 'amacron': 556,
+ 'sacute': 556,
+ 'idieresis': 278,
+ 'Ocircumflex': 778,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 611,
+ 'twosuperior': 333,
+ 'Odieresis': 778,
+ 'mu': 611,
+ 'igrave': 278,
+ 'ohungarumlaut': 611,
+ 'Eogonek': 667,
+ 'dcroat': 611,
+ 'threequarters': 834,
+ 'Scedilla': 667,
+ 'lcaron': 400,
+ 'Kcommaaccent': 722,
+ 'Lacute': 611,
+ 'trademark': 1000,
+ 'edotaccent': 556,
+ 'Igrave': 278,
+ 'Imacron': 278,
+ 'Lcaron': 611,
+ 'onehalf': 834,
+ 'lessequal': 549,
+ 'ocircumflex': 611,
+ 'ntilde': 611,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 556,
+ 'gbreve': 611,
+ 'onequarter': 834,
+ 'Scaron': 667,
+ 'Scommaaccent': 667,
+ 'Ohungarumlaut': 778,
+ 'degree': 400,
+ 'ograve': 611,
+ 'Ccaron': 722,
+ 'ugrave': 611,
+ 'radical': 549,
+ 'Dcaron': 722,
+ 'rcommaaccent': 389,
+ 'Ntilde': 722,
+ 'otilde': 611,
+ 'Rcommaaccent': 722,
+ 'Lcommaaccent': 611,
+ 'Atilde': 722,
+ 'Aogonek': 722,
+ 'Aring': 722,
+ 'Otilde': 778,
+ 'zdotaccent': 500,
+ 'Ecaron': 667,
+ 'Iogonek': 278,
+ 'kcommaaccent': 556,
+ 'minus': 584,
+ 'Icircumflex': 278,
+ 'ncaron': 611,
+ 'tcommaaccent': 333,
+ 'logicalnot': 584,
+ 'odieresis': 611,
+ 'udieresis': 611,
+ 'notequal': 549,
+ 'gcommaaccent': 611,
+ 'eth': 611,
+ 'zcaron': 500,
+ 'ncommaaccent': 611,
+ 'onesuperior': 333,
+ 'imacron': 278,
+ 'Euro': 556
+ },
+ 'Helvetica-Oblique' : {
+ 'space': 278,
+ 'exclam': 278,
+ 'quotedbl': 355,
+ 'numbersign': 556,
+ 'dollar': 556,
+ 'percent': 889,
+ 'ampersand': 667,
+ 'quoteright': 222,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 389,
+ 'plus': 584,
+ 'comma': 278,
+ 'hyphen': 333,
+ 'period': 278,
+ 'slash': 278,
+ 'zero': 556,
+ 'one': 556,
+ 'two': 556,
+ 'three': 556,
+ 'four': 556,
+ 'five': 556,
+ 'six': 556,
+ 'seven': 556,
+ 'eight': 556,
+ 'nine': 556,
+ 'colon': 278,
+ 'semicolon': 278,
+ 'less': 584,
+ 'equal': 584,
+ 'greater': 584,
+ 'question': 556,
+ 'at': 1015,
+ 'A': 667,
+ 'B': 667,
+ 'C': 722,
+ 'D': 722,
+ 'E': 667,
+ 'F': 611,
+ 'G': 778,
+ 'H': 722,
+ 'I': 278,
+ 'J': 500,
+ 'K': 667,
+ 'L': 556,
+ 'M': 833,
+ 'N': 722,
+ 'O': 778,
+ 'P': 667,
+ 'Q': 778,
+ 'R': 722,
+ 'S': 667,
+ 'T': 611,
+ 'U': 722,
+ 'V': 667,
+ 'W': 944,
+ 'X': 667,
+ 'Y': 667,
+ 'Z': 611,
+ 'bracketleft': 278,
+ 'backslash': 278,
+ 'bracketright': 278,
+ 'asciicircum': 469,
+ 'underscore': 556,
+ 'quoteleft': 222,
+ 'a': 556,
+ 'b': 556,
+ 'c': 500,
+ 'd': 556,
+ 'e': 556,
+ 'f': 278,
+ 'g': 556,
+ 'h': 556,
+ 'i': 222,
+ 'j': 222,
+ 'k': 500,
+ 'l': 222,
+ 'm': 833,
+ 'n': 556,
+ 'o': 556,
+ 'p': 556,
+ 'q': 556,
+ 'r': 333,
+ 's': 500,
+ 't': 278,
+ 'u': 556,
+ 'v': 500,
+ 'w': 722,
+ 'x': 500,
+ 'y': 500,
+ 'z': 500,
+ 'braceleft': 334,
+ 'bar': 260,
+ 'braceright': 334,
+ 'asciitilde': 584,
+ 'exclamdown': 333,
+ 'cent': 556,
+ 'sterling': 556,
+ 'fraction': 167,
+ 'yen': 556,
+ 'florin': 556,
+ 'section': 556,
+ 'currency': 556,
+ 'quotesingle': 191,
+ 'quotedblleft': 333,
+ 'guillemotleft': 556,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 500,
+ 'fl': 500,
+ 'endash': 556,
+ 'dagger': 556,
+ 'daggerdbl': 556,
+ 'periodcentered': 278,
+ 'paragraph': 537,
+ 'bullet': 350,
+ 'quotesinglbase': 222,
+ 'quotedblbase': 333,
+ 'quotedblright': 333,
+ 'guillemotright': 556,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 611,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 1000,
+ 'ordfeminine': 370,
+ 'Lslash': 556,
+ 'Oslash': 778,
+ 'OE': 1000,
+ 'ordmasculine': 365,
+ 'ae': 889,
+ 'dotlessi': 278,
+ 'lslash': 222,
+ 'oslash': 611,
+ 'oe': 944,
+ 'germandbls': 611,
+ 'Idieresis': 278,
+ 'eacute': 556,
+ 'abreve': 556,
+ 'uhungarumlaut': 556,
+ 'ecaron': 556,
+ 'Ydieresis': 667,
+ 'divide': 584,
+ 'Yacute': 667,
+ 'Acircumflex': 667,
+ 'aacute': 556,
+ 'Ucircumflex': 722,
+ 'yacute': 500,
+ 'scommaaccent': 500,
+ 'ecircumflex': 556,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 556,
+ 'Uacute': 722,
+ 'uogonek': 556,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 737,
+ 'Emacron': 667,
+ 'ccaron': 500,
+ 'aring': 556,
+ 'Ncommaaccent': 722,
+ 'lacute': 222,
+ 'agrave': 556,
+ 'Tcommaaccent': 611,
+ 'Cacute': 722,
+ 'atilde': 556,
+ 'Edotaccent': 667,
+ 'scaron': 500,
+ 'scedilla': 500,
+ 'iacute': 278,
+ 'lozenge': 471,
+ 'Rcaron': 722,
+ 'Gcommaaccent': 778,
+ 'ucircumflex': 556,
+ 'acircumflex': 556,
+ 'Amacron': 667,
+ 'rcaron': 333,
+ 'ccedilla': 500,
+ 'Zdotaccent': 611,
+ 'Thorn': 667,
+ 'Omacron': 778,
+ 'Racute': 722,
+ 'Sacute': 667,
+ 'dcaron': 643,
+ 'Umacron': 722,
+ 'uring': 556,
+ 'threesuperior': 333,
+ 'Ograve': 778,
+ 'Agrave': 667,
+ 'Abreve': 667,
+ 'multiply': 584,
+ 'uacute': 556,
+ 'Tcaron': 611,
+ 'partialdiff': 476,
+ 'ydieresis': 500,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 556,
+ 'edieresis': 556,
+ 'cacute': 500,
+ 'nacute': 556,
+ 'umacron': 556,
+ 'Ncaron': 722,
+ 'Iacute': 278,
+ 'plusminus': 584,
+ 'brokenbar': 260,
+ 'registered': 737,
+ 'Gbreve': 778,
+ 'Idotaccent': 278,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 333,
+ 'omacron': 556,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 722,
+ 'lcommaaccent': 222,
+ 'tcaron': 317,
+ 'eogonek': 556,
+ 'Uogonek': 722,
+ 'Aacute': 667,
+ 'Adieresis': 667,
+ 'egrave': 556,
+ 'zacute': 500,
+ 'iogonek': 222,
+ 'Oacute': 778,
+ 'oacute': 556,
+ 'amacron': 556,
+ 'sacute': 500,
+ 'idieresis': 278,
+ 'Ocircumflex': 778,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 556,
+ 'twosuperior': 333,
+ 'Odieresis': 778,
+ 'mu': 556,
+ 'igrave': 278,
+ 'ohungarumlaut': 556,
+ 'Eogonek': 667,
+ 'dcroat': 556,
+ 'threequarters': 834,
+ 'Scedilla': 667,
+ 'lcaron': 299,
+ 'Kcommaaccent': 667,
+ 'Lacute': 556,
+ 'trademark': 1000,
+ 'edotaccent': 556,
+ 'Igrave': 278,
+ 'Imacron': 278,
+ 'Lcaron': 556,
+ 'onehalf': 834,
+ 'lessequal': 549,
+ 'ocircumflex': 556,
+ 'ntilde': 556,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 556,
+ 'gbreve': 556,
+ 'onequarter': 834,
+ 'Scaron': 667,
+ 'Scommaaccent': 667,
+ 'Ohungarumlaut': 778,
+ 'degree': 400,
+ 'ograve': 556,
+ 'Ccaron': 722,
+ 'ugrave': 556,
+ 'radical': 453,
+ 'Dcaron': 722,
+ 'rcommaaccent': 333,
+ 'Ntilde': 722,
+ 'otilde': 556,
+ 'Rcommaaccent': 722,
+ 'Lcommaaccent': 556,
+ 'Atilde': 667,
+ 'Aogonek': 667,
+ 'Aring': 667,
+ 'Otilde': 778,
+ 'zdotaccent': 500,
+ 'Ecaron': 667,
+ 'Iogonek': 278,
+ 'kcommaaccent': 500,
+ 'minus': 584,
+ 'Icircumflex': 278,
+ 'ncaron': 556,
+ 'tcommaaccent': 278,
+ 'logicalnot': 584,
+ 'odieresis': 556,
+ 'udieresis': 556,
+ 'notequal': 549,
+ 'gcommaaccent': 556,
+ 'eth': 556,
+ 'zcaron': 500,
+ 'ncommaaccent': 556,
+ 'onesuperior': 333,
+ 'imacron': 278,
+ 'Euro': 556
+ },
+ 'Symbol': {
+ 'space': 250,
+ 'exclam': 333,
+ 'universal': 713,
+ 'numbersign': 500,
+ 'existential': 549,
+ 'percent': 833,
+ 'ampersand': 778,
+ 'suchthat': 439,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asteriskmath': 500,
+ 'plus': 549,
+ 'comma': 250,
+ 'minus': 549,
+ 'period': 250,
+ 'slash': 278,
+ 'zero': 500,
+ 'one': 500,
+ 'two': 500,
+ 'three': 500,
+ 'four': 500,
+ 'five': 500,
+ 'six': 500,
+ 'seven': 500,
+ 'eight': 500,
+ 'nine': 500,
+ 'colon': 278,
+ 'semicolon': 278,
+ 'less': 549,
+ 'equal': 549,
+ 'greater': 549,
+ 'question': 444,
+ 'congruent': 549,
+ 'Alpha': 722,
+ 'Beta': 667,
+ 'Chi': 722,
+ 'Delta': 612,
+ 'Epsilon': 611,
+ 'Phi': 763,
+ 'Gamma': 603,
+ 'Eta': 722,
+ 'Iota': 333,
+ 'theta1': 631,
+ 'Kappa': 722,
+ 'Lambda': 686,
+ 'Mu': 889,
+ 'Nu': 722,
+ 'Omicron': 722,
+ 'Pi': 768,
+ 'Theta': 741,
+ 'Rho': 556,
+ 'Sigma': 592,
+ 'Tau': 611,
+ 'Upsilon': 690,
+ 'sigma1': 439,
+ 'Omega': 768,
+ 'Xi': 645,
+ 'Psi': 795,
+ 'Zeta': 611,
+ 'bracketleft': 333,
+ 'therefore': 863,
+ 'bracketright': 333,
+ 'perpendicular': 658,
+ 'underscore': 500,
+ 'radicalex': 500,
+ 'alpha': 631,
+ 'beta': 549,
+ 'chi': 549,
+ 'delta': 494,
+ 'epsilon': 439,
+ 'phi': 521,
+ 'gamma': 411,
+ 'eta': 603,
+ 'iota': 329,
+ 'phi1': 603,
+ 'kappa': 549,
+ 'lambda': 549,
+ 'mu': 576,
+ 'nu': 521,
+ 'omicron': 549,
+ 'pi': 549,
+ 'theta': 521,
+ 'rho': 549,
+ 'sigma': 603,
+ 'tau': 439,
+ 'upsilon': 576,
+ 'omega1': 713,
+ 'omega': 686,
+ 'xi': 493,
+ 'psi': 686,
+ 'zeta': 494,
+ 'braceleft': 480,
+ 'bar': 200,
+ 'braceright': 480,
+ 'similar': 549,
+ 'Euro': 750,
+ 'Upsilon1': 620,
+ 'minute': 247,
+ 'lessequal': 549,
+ 'fraction': 167,
+ 'infinity': 713,
+ 'florin': 500,
+ 'club': 753,
+ 'diamond': 753,
+ 'heart': 753,
+ 'spade': 753,
+ 'arrowboth': 1042,
+ 'arrowleft': 987,
+ 'arrowup': 603,
+ 'arrowright': 987,
+ 'arrowdown': 603,
+ 'degree': 400,
+ 'plusminus': 549,
+ 'second': 411,
+ 'greaterequal': 549,
+ 'multiply': 549,
+ 'proportional': 713,
+ 'partialdiff': 494,
+ 'bullet': 460,
+ 'divide': 549,
+ 'notequal': 549,
+ 'equivalence': 549,
+ 'approxequal': 549,
+ 'ellipsis': 1000,
+ 'arrowvertex': 603,
+ 'arrowhorizex': 1000,
+ 'carriagereturn': 658,
+ 'aleph': 823,
+ 'Ifraktur': 686,
+ 'Rfraktur': 795,
+ 'weierstrass': 987,
+ 'circlemultiply': 768,
+ 'circleplus': 768,
+ 'emptyset': 823,
+ 'intersection': 768,
+ 'union': 768,
+ 'propersuperset': 713,
+ 'reflexsuperset': 713,
+ 'notsubset': 713,
+ 'propersubset': 713,
+ 'reflexsubset': 713,
+ 'element': 713,
+ 'notelement': 713,
+ 'angle': 768,
+ 'gradient': 713,
+ 'registerserif': 790,
+ 'copyrightserif': 790,
+ 'trademarkserif': 890,
+ 'product': 823,
+ 'radical': 549,
+ 'dotmath': 250,
+ 'logicalnot': 713,
+ 'logicaland': 603,
+ 'logicalor': 603,
+ 'arrowdblboth': 1042,
+ 'arrowdblleft': 987,
+ 'arrowdblup': 603,
+ 'arrowdblright': 987,
+ 'arrowdbldown': 603,
+ 'lozenge': 494,
+ 'angleleft': 329,
+ 'registersans': 790,
+ 'copyrightsans': 790,
+ 'trademarksans': 786,
+ 'summation': 713,
+ 'parenlefttp': 384,
+ 'parenleftex': 384,
+ 'parenleftbt': 384,
+ 'bracketlefttp': 384,
+ 'bracketleftex': 384,
+ 'bracketleftbt': 384,
+ 'bracelefttp': 494,
+ 'braceleftmid': 494,
+ 'braceleftbt': 494,
+ 'braceex': 494,
+ 'angleright': 329,
+ 'integral': 274,
+ 'integraltp': 686,
+ 'integralex': 686,
+ 'integralbt': 686,
+ 'parenrighttp': 384,
+ 'parenrightex': 384,
+ 'parenrightbt': 384,
+ 'bracketrighttp': 384,
+ 'bracketrightex': 384,
+ 'bracketrightbt': 384,
+ 'bracerighttp': 494,
+ 'bracerightmid': 494,
+ 'bracerightbt': 494,
+ 'apple': 790
+ },
+ 'Times-Roman': {
+ 'space': 250,
+ 'exclam': 333,
+ 'quotedbl': 408,
+ 'numbersign': 500,
+ 'dollar': 500,
+ 'percent': 833,
+ 'ampersand': 778,
+ 'quoteright': 333,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 500,
+ 'plus': 564,
+ 'comma': 250,
+ 'hyphen': 333,
+ 'period': 250,
+ 'slash': 278,
+ 'zero': 500,
+ 'one': 500,
+ 'two': 500,
+ 'three': 500,
+ 'four': 500,
+ 'five': 500,
+ 'six': 500,
+ 'seven': 500,
+ 'eight': 500,
+ 'nine': 500,
+ 'colon': 278,
+ 'semicolon': 278,
+ 'less': 564,
+ 'equal': 564,
+ 'greater': 564,
+ 'question': 444,
+ 'at': 921,
+ 'A': 722,
+ 'B': 667,
+ 'C': 667,
+ 'D': 722,
+ 'E': 611,
+ 'F': 556,
+ 'G': 722,
+ 'H': 722,
+ 'I': 333,
+ 'J': 389,
+ 'K': 722,
+ 'L': 611,
+ 'M': 889,
+ 'N': 722,
+ 'O': 722,
+ 'P': 556,
+ 'Q': 722,
+ 'R': 667,
+ 'S': 556,
+ 'T': 611,
+ 'U': 722,
+ 'V': 722,
+ 'W': 944,
+ 'X': 722,
+ 'Y': 722,
+ 'Z': 611,
+ 'bracketleft': 333,
+ 'backslash': 278,
+ 'bracketright': 333,
+ 'asciicircum': 469,
+ 'underscore': 500,
+ 'quoteleft': 333,
+ 'a': 444,
+ 'b': 500,
+ 'c': 444,
+ 'd': 500,
+ 'e': 444,
+ 'f': 333,
+ 'g': 500,
+ 'h': 500,
+ 'i': 278,
+ 'j': 278,
+ 'k': 500,
+ 'l': 278,
+ 'm': 778,
+ 'n': 500,
+ 'o': 500,
+ 'p': 500,
+ 'q': 500,
+ 'r': 333,
+ 's': 389,
+ 't': 278,
+ 'u': 500,
+ 'v': 500,
+ 'w': 722,
+ 'x': 500,
+ 'y': 500,
+ 'z': 444,
+ 'braceleft': 480,
+ 'bar': 200,
+ 'braceright': 480,
+ 'asciitilde': 541,
+ 'exclamdown': 333,
+ 'cent': 500,
+ 'sterling': 500,
+ 'fraction': 167,
+ 'yen': 500,
+ 'florin': 500,
+ 'section': 500,
+ 'currency': 500,
+ 'quotesingle': 180,
+ 'quotedblleft': 444,
+ 'guillemotleft': 500,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 556,
+ 'fl': 556,
+ 'endash': 500,
+ 'dagger': 500,
+ 'daggerdbl': 500,
+ 'periodcentered': 250,
+ 'paragraph': 453,
+ 'bullet': 350,
+ 'quotesinglbase': 333,
+ 'quotedblbase': 444,
+ 'quotedblright': 444,
+ 'guillemotright': 500,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 444,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 889,
+ 'ordfeminine': 276,
+ 'Lslash': 611,
+ 'Oslash': 722,
+ 'OE': 889,
+ 'ordmasculine': 310,
+ 'ae': 667,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 500,
+ 'oe': 722,
+ 'germandbls': 500,
+ 'Idieresis': 333,
+ 'eacute': 444,
+ 'abreve': 444,
+ 'uhungarumlaut': 500,
+ 'ecaron': 444,
+ 'Ydieresis': 722,
+ 'divide': 564,
+ 'Yacute': 722,
+ 'Acircumflex': 722,
+ 'aacute': 444,
+ 'Ucircumflex': 722,
+ 'yacute': 500,
+ 'scommaaccent': 389,
+ 'ecircumflex': 444,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 444,
+ 'Uacute': 722,
+ 'uogonek': 500,
+ 'Edieresis': 611,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 760,
+ 'Emacron': 611,
+ 'ccaron': 444,
+ 'aring': 444,
+ 'Ncommaaccent': 722,
+ 'lacute': 278,
+ 'agrave': 444,
+ 'Tcommaaccent': 611,
+ 'Cacute': 667,
+ 'atilde': 444,
+ 'Edotaccent': 611,
+ 'scaron': 389,
+ 'scedilla': 389,
+ 'iacute': 278,
+ 'lozenge': 471,
+ 'Rcaron': 667,
+ 'Gcommaaccent': 722,
+ 'ucircumflex': 500,
+ 'acircumflex': 444,
+ 'Amacron': 722,
+ 'rcaron': 333,
+ 'ccedilla': 444,
+ 'Zdotaccent': 611,
+ 'Thorn': 556,
+ 'Omacron': 722,
+ 'Racute': 667,
+ 'Sacute': 556,
+ 'dcaron': 588,
+ 'Umacron': 722,
+ 'uring': 500,
+ 'threesuperior': 300,
+ 'Ograve': 722,
+ 'Agrave': 722,
+ 'Abreve': 722,
+ 'multiply': 564,
+ 'uacute': 500,
+ 'Tcaron': 611,
+ 'partialdiff': 476,
+ 'ydieresis': 500,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 611,
+ 'adieresis': 444,
+ 'edieresis': 444,
+ 'cacute': 444,
+ 'nacute': 500,
+ 'umacron': 500,
+ 'Ncaron': 722,
+ 'Iacute': 333,
+ 'plusminus': 564,
+ 'brokenbar': 200,
+ 'registered': 760,
+ 'Gbreve': 722,
+ 'Idotaccent': 333,
+ 'summation': 600,
+ 'Egrave': 611,
+ 'racute': 333,
+ 'omacron': 500,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 667,
+ 'lcommaaccent': 278,
+ 'tcaron': 326,
+ 'eogonek': 444,
+ 'Uogonek': 722,
+ 'Aacute': 722,
+ 'Adieresis': 722,
+ 'egrave': 444,
+ 'zacute': 444,
+ 'iogonek': 278,
+ 'Oacute': 722,
+ 'oacute': 500,
+ 'amacron': 444,
+ 'sacute': 389,
+ 'idieresis': 278,
+ 'Ocircumflex': 722,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 500,
+ 'twosuperior': 300,
+ 'Odieresis': 722,
+ 'mu': 500,
+ 'igrave': 278,
+ 'ohungarumlaut': 500,
+ 'Eogonek': 611,
+ 'dcroat': 500,
+ 'threequarters': 750,
+ 'Scedilla': 556,
+ 'lcaron': 344,
+ 'Kcommaaccent': 722,
+ 'Lacute': 611,
+ 'trademark': 980,
+ 'edotaccent': 444,
+ 'Igrave': 333,
+ 'Imacron': 333,
+ 'Lcaron': 611,
+ 'onehalf': 750,
+ 'lessequal': 549,
+ 'ocircumflex': 500,
+ 'ntilde': 500,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 611,
+ 'emacron': 444,
+ 'gbreve': 500,
+ 'onequarter': 750,
+ 'Scaron': 556,
+ 'Scommaaccent': 556,
+ 'Ohungarumlaut': 722,
+ 'degree': 400,
+ 'ograve': 500,
+ 'Ccaron': 667,
+ 'ugrave': 500,
+ 'radical': 453,
+ 'Dcaron': 722,
+ 'rcommaaccent': 333,
+ 'Ntilde': 722,
+ 'otilde': 500,
+ 'Rcommaaccent': 667,
+ 'Lcommaaccent': 611,
+ 'Atilde': 722,
+ 'Aogonek': 722,
+ 'Aring': 722,
+ 'Otilde': 722,
+ 'zdotaccent': 444,
+ 'Ecaron': 611,
+ 'Iogonek': 333,
+ 'kcommaaccent': 500,
+ 'minus': 564,
+ 'Icircumflex': 333,
+ 'ncaron': 500,
+ 'tcommaaccent': 278,
+ 'logicalnot': 564,
+ 'odieresis': 500,
+ 'udieresis': 500,
+ 'notequal': 549,
+ 'gcommaaccent': 500,
+ 'eth': 500,
+ 'zcaron': 444,
+ 'ncommaaccent': 500,
+ 'onesuperior': 300,
+ 'imacron': 278,
+ 'Euro': 500
+ },
+ 'Times-Bold': {
+ 'space': 250,
+ 'exclam': 333,
+ 'quotedbl': 555,
+ 'numbersign': 500,
+ 'dollar': 500,
+ 'percent': 1000,
+ 'ampersand': 833,
+ 'quoteright': 333,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 500,
+ 'plus': 570,
+ 'comma': 250,
+ 'hyphen': 333,
+ 'period': 250,
+ 'slash': 278,
+ 'zero': 500,
+ 'one': 500,
+ 'two': 500,
+ 'three': 500,
+ 'four': 500,
+ 'five': 500,
+ 'six': 500,
+ 'seven': 500,
+ 'eight': 500,
+ 'nine': 500,
+ 'colon': 333,
+ 'semicolon': 333,
+ 'less': 570,
+ 'equal': 570,
+ 'greater': 570,
+ 'question': 500,
+ 'at': 930,
+ 'A': 722,
+ 'B': 667,
+ 'C': 722,
+ 'D': 722,
+ 'E': 667,
+ 'F': 611,
+ 'G': 778,
+ 'H': 778,
+ 'I': 389,
+ 'J': 500,
+ 'K': 778,
+ 'L': 667,
+ 'M': 944,
+ 'N': 722,
+ 'O': 778,
+ 'P': 611,
+ 'Q': 778,
+ 'R': 722,
+ 'S': 556,
+ 'T': 667,
+ 'U': 722,
+ 'V': 722,
+ 'W': 1000,
+ 'X': 722,
+ 'Y': 722,
+ 'Z': 667,
+ 'bracketleft': 333,
+ 'backslash': 278,
+ 'bracketright': 333,
+ 'asciicircum': 581,
+ 'underscore': 500,
+ 'quoteleft': 333,
+ 'a': 500,
+ 'b': 556,
+ 'c': 444,
+ 'd': 556,
+ 'e': 444,
+ 'f': 333,
+ 'g': 500,
+ 'h': 556,
+ 'i': 278,
+ 'j': 333,
+ 'k': 556,
+ 'l': 278,
+ 'm': 833,
+ 'n': 556,
+ 'o': 500,
+ 'p': 556,
+ 'q': 556,
+ 'r': 444,
+ 's': 389,
+ 't': 333,
+ 'u': 556,
+ 'v': 500,
+ 'w': 722,
+ 'x': 500,
+ 'y': 500,
+ 'z': 444,
+ 'braceleft': 394,
+ 'bar': 220,
+ 'braceright': 394,
+ 'asciitilde': 520,
+ 'exclamdown': 333,
+ 'cent': 500,
+ 'sterling': 500,
+ 'fraction': 167,
+ 'yen': 500,
+ 'florin': 500,
+ 'section': 500,
+ 'currency': 500,
+ 'quotesingle': 278,
+ 'quotedblleft': 500,
+ 'guillemotleft': 500,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 556,
+ 'fl': 556,
+ 'endash': 500,
+ 'dagger': 500,
+ 'daggerdbl': 500,
+ 'periodcentered': 250,
+ 'paragraph': 540,
+ 'bullet': 350,
+ 'quotesinglbase': 333,
+ 'quotedblbase': 500,
+ 'quotedblright': 500,
+ 'guillemotright': 500,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 500,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 1000,
+ 'ordfeminine': 300,
+ 'Lslash': 667,
+ 'Oslash': 778,
+ 'OE': 1000,
+ 'ordmasculine': 330,
+ 'ae': 722,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 500,
+ 'oe': 722,
+ 'germandbls': 556,
+ 'Idieresis': 389,
+ 'eacute': 444,
+ 'abreve': 500,
+ 'uhungarumlaut': 556,
+ 'ecaron': 444,
+ 'Ydieresis': 722,
+ 'divide': 570,
+ 'Yacute': 722,
+ 'Acircumflex': 722,
+ 'aacute': 500,
+ 'Ucircumflex': 722,
+ 'yacute': 500,
+ 'scommaaccent': 389,
+ 'ecircumflex': 444,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 500,
+ 'Uacute': 722,
+ 'uogonek': 556,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 747,
+ 'Emacron': 667,
+ 'ccaron': 444,
+ 'aring': 500,
+ 'Ncommaaccent': 722,
+ 'lacute': 278,
+ 'agrave': 500,
+ 'Tcommaaccent': 667,
+ 'Cacute': 722,
+ 'atilde': 500,
+ 'Edotaccent': 667,
+ 'scaron': 389,
+ 'scedilla': 389,
+ 'iacute': 278,
+ 'lozenge': 494,
+ 'Rcaron': 722,
+ 'Gcommaaccent': 778,
+ 'ucircumflex': 556,
+ 'acircumflex': 500,
+ 'Amacron': 722,
+ 'rcaron': 444,
+ 'ccedilla': 444,
+ 'Zdotaccent': 667,
+ 'Thorn': 611,
+ 'Omacron': 778,
+ 'Racute': 722,
+ 'Sacute': 556,
+ 'dcaron': 672,
+ 'Umacron': 722,
+ 'uring': 556,
+ 'threesuperior': 300,
+ 'Ograve': 778,
+ 'Agrave': 722,
+ 'Abreve': 722,
+ 'multiply': 570,
+ 'uacute': 556,
+ 'Tcaron': 667,
+ 'partialdiff': 494,
+ 'ydieresis': 500,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 500,
+ 'edieresis': 444,
+ 'cacute': 444,
+ 'nacute': 556,
+ 'umacron': 556,
+ 'Ncaron': 722,
+ 'Iacute': 389,
+ 'plusminus': 570,
+ 'brokenbar': 220,
+ 'registered': 747,
+ 'Gbreve': 778,
+ 'Idotaccent': 389,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 444,
+ 'omacron': 500,
+ 'Zacute': 667,
+ 'Zcaron': 667,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 722,
+ 'lcommaaccent': 278,
+ 'tcaron': 416,
+ 'eogonek': 444,
+ 'Uogonek': 722,
+ 'Aacute': 722,
+ 'Adieresis': 722,
+ 'egrave': 444,
+ 'zacute': 444,
+ 'iogonek': 278,
+ 'Oacute': 778,
+ 'oacute': 500,
+ 'amacron': 500,
+ 'sacute': 389,
+ 'idieresis': 278,
+ 'Ocircumflex': 778,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 556,
+ 'twosuperior': 300,
+ 'Odieresis': 778,
+ 'mu': 556,
+ 'igrave': 278,
+ 'ohungarumlaut': 500,
+ 'Eogonek': 667,
+ 'dcroat': 556,
+ 'threequarters': 750,
+ 'Scedilla': 556,
+ 'lcaron': 394,
+ 'Kcommaaccent': 778,
+ 'Lacute': 667,
+ 'trademark': 1000,
+ 'edotaccent': 444,
+ 'Igrave': 389,
+ 'Imacron': 389,
+ 'Lcaron': 667,
+ 'onehalf': 750,
+ 'lessequal': 549,
+ 'ocircumflex': 500,
+ 'ntilde': 556,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 444,
+ 'gbreve': 500,
+ 'onequarter': 750,
+ 'Scaron': 556,
+ 'Scommaaccent': 556,
+ 'Ohungarumlaut': 778,
+ 'degree': 400,
+ 'ograve': 500,
+ 'Ccaron': 722,
+ 'ugrave': 556,
+ 'radical': 549,
+ 'Dcaron': 722,
+ 'rcommaaccent': 444,
+ 'Ntilde': 722,
+ 'otilde': 500,
+ 'Rcommaaccent': 722,
+ 'Lcommaaccent': 667,
+ 'Atilde': 722,
+ 'Aogonek': 722,
+ 'Aring': 722,
+ 'Otilde': 778,
+ 'zdotaccent': 444,
+ 'Ecaron': 667,
+ 'Iogonek': 389,
+ 'kcommaaccent': 556,
+ 'minus': 570,
+ 'Icircumflex': 389,
+ 'ncaron': 556,
+ 'tcommaaccent': 333,
+ 'logicalnot': 570,
+ 'odieresis': 500,
+ 'udieresis': 556,
+ 'notequal': 549,
+ 'gcommaaccent': 500,
+ 'eth': 500,
+ 'zcaron': 444,
+ 'ncommaaccent': 556,
+ 'onesuperior': 300,
+ 'imacron': 278,
+ 'Euro': 500
+ },
+ 'Times-BoldItalic': {
+ 'space': 250,
+ 'exclam': 389,
+ 'quotedbl': 555,
+ 'numbersign': 500,
+ 'dollar': 500,
+ 'percent': 833,
+ 'ampersand': 778,
+ 'quoteright': 333,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 500,
+ 'plus': 570,
+ 'comma': 250,
+ 'hyphen': 333,
+ 'period': 250,
+ 'slash': 278,
+ 'zero': 500,
+ 'one': 500,
+ 'two': 500,
+ 'three': 500,
+ 'four': 500,
+ 'five': 500,
+ 'six': 500,
+ 'seven': 500,
+ 'eight': 500,
+ 'nine': 500,
+ 'colon': 333,
+ 'semicolon': 333,
+ 'less': 570,
+ 'equal': 570,
+ 'greater': 570,
+ 'question': 500,
+ 'at': 832,
+ 'A': 667,
+ 'B': 667,
+ 'C': 667,
+ 'D': 722,
+ 'E': 667,
+ 'F': 667,
+ 'G': 722,
+ 'H': 778,
+ 'I': 389,
+ 'J': 500,
+ 'K': 667,
+ 'L': 611,
+ 'M': 889,
+ 'N': 722,
+ 'O': 722,
+ 'P': 611,
+ 'Q': 722,
+ 'R': 667,
+ 'S': 556,
+ 'T': 611,
+ 'U': 722,
+ 'V': 667,
+ 'W': 889,
+ 'X': 667,
+ 'Y': 611,
+ 'Z': 611,
+ 'bracketleft': 333,
+ 'backslash': 278,
+ 'bracketright': 333,
+ 'asciicircum': 570,
+ 'underscore': 500,
+ 'quoteleft': 333,
+ 'a': 500,
+ 'b': 500,
+ 'c': 444,
+ 'd': 500,
+ 'e': 444,
+ 'f': 333,
+ 'g': 500,
+ 'h': 556,
+ 'i': 278,
+ 'j': 278,
+ 'k': 500,
+ 'l': 278,
+ 'm': 778,
+ 'n': 556,
+ 'o': 500,
+ 'p': 500,
+ 'q': 500,
+ 'r': 389,
+ 's': 389,
+ 't': 278,
+ 'u': 556,
+ 'v': 444,
+ 'w': 667,
+ 'x': 500,
+ 'y': 444,
+ 'z': 389,
+ 'braceleft': 348,
+ 'bar': 220,
+ 'braceright': 348,
+ 'asciitilde': 570,
+ 'exclamdown': 389,
+ 'cent': 500,
+ 'sterling': 500,
+ 'fraction': 167,
+ 'yen': 500,
+ 'florin': 500,
+ 'section': 500,
+ 'currency': 500,
+ 'quotesingle': 278,
+ 'quotedblleft': 500,
+ 'guillemotleft': 500,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 556,
+ 'fl': 556,
+ 'endash': 500,
+ 'dagger': 500,
+ 'daggerdbl': 500,
+ 'periodcentered': 250,
+ 'paragraph': 500,
+ 'bullet': 350,
+ 'quotesinglbase': 333,
+ 'quotedblbase': 500,
+ 'quotedblright': 500,
+ 'guillemotright': 500,
+ 'ellipsis': 1000,
+ 'perthousand': 1000,
+ 'questiondown': 500,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 1000,
+ 'AE': 944,
+ 'ordfeminine': 266,
+ 'Lslash': 611,
+ 'Oslash': 722,
+ 'OE': 944,
+ 'ordmasculine': 300,
+ 'ae': 722,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 500,
+ 'oe': 722,
+ 'germandbls': 500,
+ 'Idieresis': 389,
+ 'eacute': 444,
+ 'abreve': 500,
+ 'uhungarumlaut': 556,
+ 'ecaron': 444,
+ 'Ydieresis': 611,
+ 'divide': 570,
+ 'Yacute': 611,
+ 'Acircumflex': 667,
+ 'aacute': 500,
+ 'Ucircumflex': 722,
+ 'yacute': 444,
+ 'scommaaccent': 389,
+ 'ecircumflex': 444,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 500,
+ 'Uacute': 722,
+ 'uogonek': 556,
+ 'Edieresis': 667,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 747,
+ 'Emacron': 667,
+ 'ccaron': 444,
+ 'aring': 500,
+ 'Ncommaaccent': 722,
+ 'lacute': 278,
+ 'agrave': 500,
+ 'Tcommaaccent': 611,
+ 'Cacute': 667,
+ 'atilde': 500,
+ 'Edotaccent': 667,
+ 'scaron': 389,
+ 'scedilla': 389,
+ 'iacute': 278,
+ 'lozenge': 494,
+ 'Rcaron': 667,
+ 'Gcommaaccent': 722,
+ 'ucircumflex': 556,
+ 'acircumflex': 500,
+ 'Amacron': 667,
+ 'rcaron': 389,
+ 'ccedilla': 444,
+ 'Zdotaccent': 611,
+ 'Thorn': 611,
+ 'Omacron': 722,
+ 'Racute': 667,
+ 'Sacute': 556,
+ 'dcaron': 608,
+ 'Umacron': 722,
+ 'uring': 556,
+ 'threesuperior': 300,
+ 'Ograve': 722,
+ 'Agrave': 667,
+ 'Abreve': 667,
+ 'multiply': 570,
+ 'uacute': 556,
+ 'Tcaron': 611,
+ 'partialdiff': 494,
+ 'ydieresis': 444,
+ 'Nacute': 722,
+ 'icircumflex': 278,
+ 'Ecircumflex': 667,
+ 'adieresis': 500,
+ 'edieresis': 444,
+ 'cacute': 444,
+ 'nacute': 556,
+ 'umacron': 556,
+ 'Ncaron': 722,
+ 'Iacute': 389,
+ 'plusminus': 570,
+ 'brokenbar': 220,
+ 'registered': 747,
+ 'Gbreve': 722,
+ 'Idotaccent': 389,
+ 'summation': 600,
+ 'Egrave': 667,
+ 'racute': 389,
+ 'omacron': 500,
+ 'Zacute': 611,
+ 'Zcaron': 611,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 667,
+ 'lcommaaccent': 278,
+ 'tcaron': 366,
+ 'eogonek': 444,
+ 'Uogonek': 722,
+ 'Aacute': 667,
+ 'Adieresis': 667,
+ 'egrave': 444,
+ 'zacute': 389,
+ 'iogonek': 278,
+ 'Oacute': 722,
+ 'oacute': 500,
+ 'amacron': 500,
+ 'sacute': 389,
+ 'idieresis': 278,
+ 'Ocircumflex': 722,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 500,
+ 'twosuperior': 300,
+ 'Odieresis': 722,
+ 'mu': 576,
+ 'igrave': 278,
+ 'ohungarumlaut': 500,
+ 'Eogonek': 667,
+ 'dcroat': 500,
+ 'threequarters': 750,
+ 'Scedilla': 556,
+ 'lcaron': 382,
+ 'Kcommaaccent': 667,
+ 'Lacute': 611,
+ 'trademark': 1000,
+ 'edotaccent': 444,
+ 'Igrave': 389,
+ 'Imacron': 389,
+ 'Lcaron': 611,
+ 'onehalf': 750,
+ 'lessequal': 549,
+ 'ocircumflex': 500,
+ 'ntilde': 556,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 667,
+ 'emacron': 444,
+ 'gbreve': 500,
+ 'onequarter': 750,
+ 'Scaron': 556,
+ 'Scommaaccent': 556,
+ 'Ohungarumlaut': 722,
+ 'degree': 400,
+ 'ograve': 500,
+ 'Ccaron': 667,
+ 'ugrave': 556,
+ 'radical': 549,
+ 'Dcaron': 722,
+ 'rcommaaccent': 389,
+ 'Ntilde': 722,
+ 'otilde': 500,
+ 'Rcommaaccent': 667,
+ 'Lcommaaccent': 611,
+ 'Atilde': 667,
+ 'Aogonek': 667,
+ 'Aring': 667,
+ 'Otilde': 722,
+ 'zdotaccent': 389,
+ 'Ecaron': 667,
+ 'Iogonek': 389,
+ 'kcommaaccent': 500,
+ 'minus': 606,
+ 'Icircumflex': 389,
+ 'ncaron': 556,
+ 'tcommaaccent': 278,
+ 'logicalnot': 606,
+ 'odieresis': 500,
+ 'udieresis': 556,
+ 'notequal': 549,
+ 'gcommaaccent': 500,
+ 'eth': 500,
+ 'zcaron': 389,
+ 'ncommaaccent': 556,
+ 'onesuperior': 300,
+ 'imacron': 278,
+ 'Euro': 500
+ },
+ 'Times-Italic': {
+ 'space': 250,
+ 'exclam': 333,
+ 'quotedbl': 420,
+ 'numbersign': 500,
+ 'dollar': 500,
+ 'percent': 833,
+ 'ampersand': 778,
+ 'quoteright': 333,
+ 'parenleft': 333,
+ 'parenright': 333,
+ 'asterisk': 500,
+ 'plus': 675,
+ 'comma': 250,
+ 'hyphen': 333,
+ 'period': 250,
+ 'slash': 278,
+ 'zero': 500,
+ 'one': 500,
+ 'two': 500,
+ 'three': 500,
+ 'four': 500,
+ 'five': 500,
+ 'six': 500,
+ 'seven': 500,
+ 'eight': 500,
+ 'nine': 500,
+ 'colon': 333,
+ 'semicolon': 333,
+ 'less': 675,
+ 'equal': 675,
+ 'greater': 675,
+ 'question': 500,
+ 'at': 920,
+ 'A': 611,
+ 'B': 611,
+ 'C': 667,
+ 'D': 722,
+ 'E': 611,
+ 'F': 611,
+ 'G': 722,
+ 'H': 722,
+ 'I': 333,
+ 'J': 444,
+ 'K': 667,
+ 'L': 556,
+ 'M': 833,
+ 'N': 667,
+ 'O': 722,
+ 'P': 611,
+ 'Q': 722,
+ 'R': 611,
+ 'S': 500,
+ 'T': 556,
+ 'U': 722,
+ 'V': 611,
+ 'W': 833,
+ 'X': 611,
+ 'Y': 556,
+ 'Z': 556,
+ 'bracketleft': 389,
+ 'backslash': 278,
+ 'bracketright': 389,
+ 'asciicircum': 422,
+ 'underscore': 500,
+ 'quoteleft': 333,
+ 'a': 500,
+ 'b': 500,
+ 'c': 444,
+ 'd': 500,
+ 'e': 444,
+ 'f': 278,
+ 'g': 500,
+ 'h': 500,
+ 'i': 278,
+ 'j': 278,
+ 'k': 444,
+ 'l': 278,
+ 'm': 722,
+ 'n': 500,
+ 'o': 500,
+ 'p': 500,
+ 'q': 500,
+ 'r': 389,
+ 's': 389,
+ 't': 278,
+ 'u': 500,
+ 'v': 444,
+ 'w': 667,
+ 'x': 444,
+ 'y': 444,
+ 'z': 389,
+ 'braceleft': 400,
+ 'bar': 275,
+ 'braceright': 400,
+ 'asciitilde': 541,
+ 'exclamdown': 389,
+ 'cent': 500,
+ 'sterling': 500,
+ 'fraction': 167,
+ 'yen': 500,
+ 'florin': 500,
+ 'section': 500,
+ 'currency': 500,
+ 'quotesingle': 214,
+ 'quotedblleft': 556,
+ 'guillemotleft': 500,
+ 'guilsinglleft': 333,
+ 'guilsinglright': 333,
+ 'fi': 500,
+ 'fl': 500,
+ 'endash': 500,
+ 'dagger': 500,
+ 'daggerdbl': 500,
+ 'periodcentered': 250,
+ 'paragraph': 523,
+ 'bullet': 350,
+ 'quotesinglbase': 333,
+ 'quotedblbase': 556,
+ 'quotedblright': 556,
+ 'guillemotright': 500,
+ 'ellipsis': 889,
+ 'perthousand': 1000,
+ 'questiondown': 500,
+ 'grave': 333,
+ 'acute': 333,
+ 'circumflex': 333,
+ 'tilde': 333,
+ 'macron': 333,
+ 'breve': 333,
+ 'dotaccent': 333,
+ 'dieresis': 333,
+ 'ring': 333,
+ 'cedilla': 333,
+ 'hungarumlaut': 333,
+ 'ogonek': 333,
+ 'caron': 333,
+ 'emdash': 889,
+ 'AE': 889,
+ 'ordfeminine': 276,
+ 'Lslash': 556,
+ 'Oslash': 722,
+ 'OE': 944,
+ 'ordmasculine': 310,
+ 'ae': 667,
+ 'dotlessi': 278,
+ 'lslash': 278,
+ 'oslash': 500,
+ 'oe': 667,
+ 'germandbls': 500,
+ 'Idieresis': 333,
+ 'eacute': 444,
+ 'abreve': 500,
+ 'uhungarumlaut': 500,
+ 'ecaron': 444,
+ 'Ydieresis': 556,
+ 'divide': 675,
+ 'Yacute': 556,
+ 'Acircumflex': 611,
+ 'aacute': 500,
+ 'Ucircumflex': 722,
+ 'yacute': 444,
+ 'scommaaccent': 389,
+ 'ecircumflex': 444,
+ 'Uring': 722,
+ 'Udieresis': 722,
+ 'aogonek': 500,
+ 'Uacute': 722,
+ 'uogonek': 500,
+ 'Edieresis': 611,
+ 'Dcroat': 722,
+ 'commaaccent': 250,
+ 'copyright': 760,
+ 'Emacron': 611,
+ 'ccaron': 444,
+ 'aring': 500,
+ 'Ncommaaccent': 667,
+ 'lacute': 278,
+ 'agrave': 500,
+ 'Tcommaaccent': 556,
+ 'Cacute': 667,
+ 'atilde': 500,
+ 'Edotaccent': 611,
+ 'scaron': 389,
+ 'scedilla': 389,
+ 'iacute': 278,
+ 'lozenge': 471,
+ 'Rcaron': 611,
+ 'Gcommaaccent': 722,
+ 'ucircumflex': 500,
+ 'acircumflex': 500,
+ 'Amacron': 611,
+ 'rcaron': 389,
+ 'ccedilla': 444,
+ 'Zdotaccent': 556,
+ 'Thorn': 611,
+ 'Omacron': 722,
+ 'Racute': 611,
+ 'Sacute': 500,
+ 'dcaron': 544,
+ 'Umacron': 722,
+ 'uring': 500,
+ 'threesuperior': 300,
+ 'Ograve': 722,
+ 'Agrave': 611,
+ 'Abreve': 611,
+ 'multiply': 675,
+ 'uacute': 500,
+ 'Tcaron': 556,
+ 'partialdiff': 476,
+ 'ydieresis': 444,
+ 'Nacute': 667,
+ 'icircumflex': 278,
+ 'Ecircumflex': 611,
+ 'adieresis': 500,
+ 'edieresis': 444,
+ 'cacute': 444,
+ 'nacute': 500,
+ 'umacron': 500,
+ 'Ncaron': 667,
+ 'Iacute': 333,
+ 'plusminus': 675,
+ 'brokenbar': 275,
+ 'registered': 760,
+ 'Gbreve': 722,
+ 'Idotaccent': 333,
+ 'summation': 600,
+ 'Egrave': 611,
+ 'racute': 389,
+ 'omacron': 500,
+ 'Zacute': 556,
+ 'Zcaron': 556,
+ 'greaterequal': 549,
+ 'Eth': 722,
+ 'Ccedilla': 667,
+ 'lcommaaccent': 278,
+ 'tcaron': 300,
+ 'eogonek': 444,
+ 'Uogonek': 722,
+ 'Aacute': 611,
+ 'Adieresis': 611,
+ 'egrave': 444,
+ 'zacute': 389,
+ 'iogonek': 278,
+ 'Oacute': 722,
+ 'oacute': 500,
+ 'amacron': 500,
+ 'sacute': 389,
+ 'idieresis': 278,
+ 'Ocircumflex': 722,
+ 'Ugrave': 722,
+ 'Delta': 612,
+ 'thorn': 500,
+ 'twosuperior': 300,
+ 'Odieresis': 722,
+ 'mu': 500,
+ 'igrave': 278,
+ 'ohungarumlaut': 500,
+ 'Eogonek': 611,
+ 'dcroat': 500,
+ 'threequarters': 750,
+ 'Scedilla': 500,
+ 'lcaron': 300,
+ 'Kcommaaccent': 667,
+ 'Lacute': 556,
+ 'trademark': 980,
+ 'edotaccent': 444,
+ 'Igrave': 333,
+ 'Imacron': 333,
+ 'Lcaron': 611,
+ 'onehalf': 750,
+ 'lessequal': 549,
+ 'ocircumflex': 500,
+ 'ntilde': 500,
+ 'Uhungarumlaut': 722,
+ 'Eacute': 611,
+ 'emacron': 444,
+ 'gbreve': 500,
+ 'onequarter': 750,
+ 'Scaron': 500,
+ 'Scommaaccent': 500,
+ 'Ohungarumlaut': 722,
+ 'degree': 400,
+ 'ograve': 500,
+ 'Ccaron': 667,
+ 'ugrave': 500,
+ 'radical': 453,
+ 'Dcaron': 722,
+ 'rcommaaccent': 389,
+ 'Ntilde': 667,
+ 'otilde': 500,
+ 'Rcommaaccent': 611,
+ 'Lcommaaccent': 556,
+ 'Atilde': 611,
+ 'Aogonek': 611,
+ 'Aring': 611,
+ 'Otilde': 722,
+ 'zdotaccent': 389,
+ 'Ecaron': 611,
+ 'Iogonek': 333,
+ 'kcommaaccent': 444,
+ 'minus': 675,
+ 'Icircumflex': 333,
+ 'ncaron': 500,
+ 'tcommaaccent': 278,
+ 'logicalnot': 675,
+ 'odieresis': 500,
+ 'udieresis': 500,
+ 'notequal': 549,
+ 'gcommaaccent': 500,
+ 'eth': 500,
+ 'zcaron': 389,
+ 'ncommaaccent': 500,
+ 'onesuperior': 300,
+ 'imacron': 278,
+ 'Euro': 500
+ },
+ 'ZapfDingbats': {
+ 'space': 278,
+ 'a1': 974,
+ 'a2': 961,
+ 'a202': 974,
+ 'a3': 980,
+ 'a4': 719,
+ 'a5': 789,
+ 'a119': 790,
+ 'a118': 791,
+ 'a117': 690,
+ 'a11': 960,
+ 'a12': 939,
+ 'a13': 549,
+ 'a14': 855,
+ 'a15': 911,
+ 'a16': 933,
+ 'a105': 911,
+ 'a17': 945,
+ 'a18': 974,
+ 'a19': 755,
+ 'a20': 846,
+ 'a21': 762,
+ 'a22': 761,
+ 'a23': 571,
+ 'a24': 677,
+ 'a25': 763,
+ 'a26': 760,
+ 'a27': 759,
+ 'a28': 754,
+ 'a6': 494,
+ 'a7': 552,
+ 'a8': 537,
+ 'a9': 577,
+ 'a10': 692,
+ 'a29': 786,
+ 'a30': 788,
+ 'a31': 788,
+ 'a32': 790,
+ 'a33': 793,
+ 'a34': 794,
+ 'a35': 816,
+ 'a36': 823,
+ 'a37': 789,
+ 'a38': 841,
+ 'a39': 823,
+ 'a40': 833,
+ 'a41': 816,
+ 'a42': 831,
+ 'a43': 923,
+ 'a44': 744,
+ 'a45': 723,
+ 'a46': 749,
+ 'a47': 790,
+ 'a48': 792,
+ 'a49': 695,
+ 'a50': 776,
+ 'a51': 768,
+ 'a52': 792,
+ 'a53': 759,
+ 'a54': 707,
+ 'a55': 708,
+ 'a56': 682,
+ 'a57': 701,
+ 'a58': 826,
+ 'a59': 815,
+ 'a60': 789,
+ 'a61': 789,
+ 'a62': 707,
+ 'a63': 687,
+ 'a64': 696,
+ 'a65': 689,
+ 'a66': 786,
+ 'a67': 787,
+ 'a68': 713,
+ 'a69': 791,
+ 'a70': 785,
+ 'a71': 791,
+ 'a72': 873,
+ 'a73': 761,
+ 'a74': 762,
+ 'a203': 762,
+ 'a75': 759,
+ 'a204': 759,
+ 'a76': 892,
+ 'a77': 892,
+ 'a78': 788,
+ 'a79': 784,
+ 'a81': 438,
+ 'a82': 138,
+ 'a83': 277,
+ 'a84': 415,
+ 'a97': 392,
+ 'a98': 392,
+ 'a99': 668,
+ 'a100': 668,
+ 'a89': 390,
+ 'a90': 390,
+ 'a93': 317,
+ 'a94': 317,
+ 'a91': 276,
+ 'a92': 276,
+ 'a205': 509,
+ 'a85': 509,
+ 'a206': 410,
+ 'a86': 410,
+ 'a87': 234,
+ 'a88': 234,
+ 'a95': 334,
+ 'a96': 334,
+ 'a101': 732,
+ 'a102': 544,
+ 'a103': 544,
+ 'a104': 910,
+ 'a106': 667,
+ 'a107': 760,
+ 'a108': 760,
+ 'a112': 776,
+ 'a111': 595,
+ 'a110': 694,
+ 'a109': 626,
+ 'a120': 788,
+ 'a121': 788,
+ 'a122': 788,
+ 'a123': 788,
+ 'a124': 788,
+ 'a125': 788,
+ 'a126': 788,
+ 'a127': 788,
+ 'a128': 788,
+ 'a129': 788,
+ 'a130': 788,
+ 'a131': 788,
+ 'a132': 788,
+ 'a133': 788,
+ 'a134': 788,
+ 'a135': 788,
+ 'a136': 788,
+ 'a137': 788,
+ 'a138': 788,
+ 'a139': 788,
+ 'a140': 788,
+ 'a141': 788,
+ 'a142': 788,
+ 'a143': 788,
+ 'a144': 788,
+ 'a145': 788,
+ 'a146': 788,
+ 'a147': 788,
+ 'a148': 788,
+ 'a149': 788,
+ 'a150': 788,
+ 'a151': 788,
+ 'a152': 788,
+ 'a153': 788,
+ 'a154': 788,
+ 'a155': 788,
+ 'a156': 788,
+ 'a157': 788,
+ 'a158': 788,
+ 'a159': 788,
+ 'a160': 894,
+ 'a161': 838,
+ 'a163': 1016,
+ 'a164': 458,
+ 'a196': 748,
+ 'a165': 924,
+ 'a192': 748,
+ 'a166': 918,
+ 'a167': 927,
+ 'a168': 928,
+ 'a169': 928,
+ 'a170': 834,
+ 'a171': 873,
+ 'a172': 828,
+ 'a173': 924,
+ 'a162': 924,
+ 'a174': 917,
+ 'a175': 930,
+ 'a176': 931,
+ 'a177': 463,
+ 'a178': 883,
+ 'a179': 836,
+ 'a193': 836,
+ 'a180': 867,
+ 'a199': 867,
+ 'a181': 696,
+ 'a200': 696,
+ 'a182': 874,
+ 'a201': 874,
+ 'a183': 760,
+ 'a184': 946,
+ 'a197': 771,
+ 'a185': 865,
+ 'a194': 771,
+ 'a198': 888,
+ 'a186': 967,
+ 'a195': 888,
+ 'a187': 831,
+ 'a188': 873,
+ 'a189': 927,
+ 'a190': 970,
+ 'a191': 918
+ }
+};
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var EOF = {};
+
+function isEOF(v) {
+ return v == EOF;
+}
+
+var Parser = (function ParserClosure() {
+ function Parser(lexer, allowStreams, xref) {
+ this.lexer = lexer;
+ this.allowStreams = allowStreams;
+ this.xref = xref;
+ this.inlineImg = 0;
+ this.refill();
+ }
+
+ Parser.prototype = {
+ refill: function Parser_refill() {
+ this.buf1 = this.lexer.getObj();
+ this.buf2 = this.lexer.getObj();
+ },
+ shift: function Parser_shift() {
+ if (isCmd(this.buf2, 'ID')) {
+ this.buf1 = this.buf2;
+ this.buf2 = null;
+ // skip byte after ID
+ this.lexer.skip();
+ } else {
+ this.buf1 = this.buf2;
+ this.buf2 = this.lexer.getObj();
+ }
+ },
+ getObj: function Parser_getObj(cipherTransform) {
+ if (isCmd(this.buf1, 'BI')) { // inline image
+ this.shift();
+ return this.makeInlineImage(cipherTransform);
+ }
+ if (isCmd(this.buf1, '[')) { // array
+ this.shift();
+ var array = [];
+ while (!isCmd(this.buf1, ']') && !isEOF(this.buf1))
+ array.push(this.getObj());
+ if (isEOF(this.buf1))
+ error('End of file inside array');
+ this.shift();
+ return array;
+ }
+ if (isCmd(this.buf1, '<<')) { // dictionary or stream
+ this.shift();
+ var dict = new Dict(this.xref);
+ while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
+ if (!isName(this.buf1))
+ error('Dictionary key must be a name object');
+
+ var key = this.buf1.name;
+ this.shift();
+ if (isEOF(this.buf1))
+ break;
+ dict.set(key, this.getObj(cipherTransform));
+ }
+ if (isEOF(this.buf1))
+ error('End of file inside dictionary');
+
+ // stream objects are not allowed inside content streams or
+ // object streams
+ if (isCmd(this.buf2, 'stream')) {
+ return this.allowStreams ?
+ this.makeStream(dict, cipherTransform) : dict;
+ }
+ this.shift();
+ return dict;
+ }
+ if (isInt(this.buf1)) { // indirect reference or integer
+ var num = this.buf1;
+ this.shift();
+ if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
+ var ref = new Ref(num, this.buf1);
+ this.shift();
+ this.shift();
+ return ref;
+ }
+ return num;
+ }
+ if (isString(this.buf1)) { // string
+ var str = this.buf1;
+ this.shift();
+ if (cipherTransform)
+ str = cipherTransform.decryptString(str);
+ return str;
+ }
+
+ // simple object
+ var obj = this.buf1;
+ this.shift();
+ return obj;
+ },
+ makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
+ var lexer = this.lexer;
+ var stream = lexer.stream;
+
+ // parse dictionary
+ var dict = new Dict();
+ while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
+ if (!isName(this.buf1))
+ error('Dictionary key must be a name object');
+
+ var key = this.buf1.name;
+ this.shift();
+ if (isEOF(this.buf1))
+ break;
+ dict.set(key, this.getObj(cipherTransform));
+ }
+
+ // parse image stream
+ var startPos = stream.pos;
+
+ // searching for the /EI\s/
+ var state = 0, ch;
+ while (state != 4 && (ch = stream.getByte()) != null) {
+ switch (ch) {
+ case 0x20:
+ case 0x0D:
+ case 0x0A:
+ state = state === 3 ? 4 : 0;
+ break;
+ case 0x45:
+ state = 2;
+ break;
+ case 0x49:
+ state = state === 2 ? 3 : 0;
+ break;
+ default:
+ state = 0;
+ break;
+ }
+ }
+
+ // TODO improve the small images performance to remove the limit
+ var inlineImgLimit = 500;
+ if (++this.inlineImg >= inlineImgLimit) {
+ if (this.inlineImg === inlineImgLimit)
+ warn('Too many inline images');
+ this.shift();
+ return null;
+ }
+
+ var length = (stream.pos - 4) - startPos;
+ var imageStream = stream.makeSubStream(startPos, length, dict);
+ if (cipherTransform)
+ imageStream = cipherTransform.createStream(imageStream);
+ imageStream = this.filter(imageStream, dict, length);
+ imageStream.parameters = dict;
+
+ this.buf2 = Cmd.get('EI');
+ this.shift();
+
+ return imageStream;
+ },
+ fetchIfRef: function Parser_fetchIfRef(obj) {
+ // not relying on the xref.fetchIfRef -- xref might not be set
+ return isRef(obj) ? this.xref.fetch(obj) : obj;
+ },
+ makeStream: function Parser_makeStream(dict, cipherTransform) {
+ var lexer = this.lexer;
+ var stream = lexer.stream;
+
+ // get stream start position
+ lexer.skipToNextLine();
+ var pos = stream.pos;
+
+ // get length
+ var length = this.fetchIfRef(dict.get('Length'));
+ if (!isInt(length))
+ error('Bad ' + length + ' attribute in stream');
+
+ // skip over the stream data
+ stream.pos = pos + length;
+ this.shift(); // '>>'
+ this.shift(); // 'stream'
+ if (!isCmd(this.buf1, 'endstream'))
+ error('Missing endstream');
+ this.shift();
+
+ stream = stream.makeSubStream(pos, length, dict);
+ if (cipherTransform)
+ stream = cipherTransform.createStream(stream);
+ stream = this.filter(stream, dict, length);
+ stream.parameters = dict;
+ return stream;
+ },
+ filter: function Parser_filter(stream, dict, length) {
+ var filter = this.fetchIfRef(dict.get('Filter', 'F'));
+ var params = this.fetchIfRef(dict.get('DecodeParms', 'DP'));
+ if (isName(filter))
+ return this.makeFilter(stream, filter.name, length, params);
+ if (isArray(filter)) {
+ var filterArray = filter;
+ var paramsArray = params;
+ for (var i = 0, ii = filterArray.length; i < ii; ++i) {
+ filter = filterArray[i];
+ if (!isName(filter))
+ error('Bad filter name: ' + filter);
+
+ params = null;
+ if (isArray(paramsArray) && (i in paramsArray))
+ params = paramsArray[i];
+ stream = this.makeFilter(stream, filter.name, length, params);
+ // after the first stream the length variable is invalid
+ length = null;
+ }
+ }
+ return stream;
+ },
+ makeFilter: function Parser_makeFilter(stream, name, length, params) {
+ if (name == 'FlateDecode' || name == 'Fl') {
+ if (params) {
+ return new PredictorStream(new FlateStream(stream), params);
+ }
+ return new FlateStream(stream);
+ }
+ if (name == 'LZWDecode' || name == 'LZW') {
+ var earlyChange = 1;
+ if (params) {
+ if (params.has('EarlyChange'))
+ earlyChange = params.get('EarlyChange');
+ return new PredictorStream(
+ new LZWStream(stream, earlyChange), params);
+ }
+ return new LZWStream(stream, earlyChange);
+ }
+ if (name == 'DCTDecode' || name == 'DCT') {
+ var bytes = stream.getBytes(length);
+ return new JpegStream(bytes, stream.dict, this.xref);
+ }
+ if (name == 'JPXDecode' || name == 'JPX') {
+ var bytes = stream.getBytes(length);
+ return new JpxStream(bytes, stream.dict);
+ }
+ if (name == 'ASCII85Decode' || name == 'A85') {
+ return new Ascii85Stream(stream);
+ }
+ if (name == 'ASCIIHexDecode' || name == 'AHx') {
+ return new AsciiHexStream(stream);
+ }
+ if (name == 'CCITTFaxDecode' || name == 'CCF') {
+ return new CCITTFaxStream(stream, params);
+ }
+ if (name == 'RunLengthDecode' || name == 'RL') {
+ return new RunLengthStream(stream);
+ }
+ if (name == 'JBIG2Decode') {
+ error('JBIG2 image format is not currently supprted.');
+ }
+ warn('filter "' + name + '" not supported yet');
+ return stream;
+ }
+ };
+
+ return Parser;
+})();
+
+var Lexer = (function LexerClosure() {
+ function Lexer(stream) {
+ this.stream = stream;
+ }
+
+ Lexer.isSpace = function Lexer_isSpace(ch) {
+ return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a';
+ };
+
+ // A '1' in this array means the character is white space. A '1' or
+ // '2' means the character ends a name or command.
+ var specialChars = [
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
+ 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
+ ];
+
+ function toHexDigit(ch) {
+ if (ch >= '0' && ch <= '9')
+ return ch.charCodeAt(0) - 48;
+ ch = ch.toUpperCase();
+ if (ch >= 'A' && ch <= 'F')
+ return ch.charCodeAt(0) - 55;
+ return -1;
+ }
+
+ Lexer.prototype = {
+ getNumber: function Lexer_getNumber(ch) {
+ var floating = false;
+ var str = ch;
+ var stream = this.stream;
+ for (;;) {
+ ch = stream.lookChar();
+ if (ch == '.' && !floating) {
+ str += ch;
+ floating = true;
+ } else if (ch == '-') {
+ // ignore minus signs in the middle of numbers to match
+ // Adobe's behavior
+ warn('Badly formated number');
+ } else if (ch >= '0' && ch <= '9') {
+ str += ch;
+ } else if (ch == 'e' || ch == 'E') {
+ floating = true;
+ } else {
+ // the last character doesn't belong to us
+ break;
+ }
+ stream.skip();
+ }
+ var value = parseFloat(str);
+ if (isNaN(value))
+ error('Invalid floating point number: ' + value);
+ return value;
+ },
+ getString: function Lexer_getString() {
+ var numParen = 1;
+ var done = false;
+ var str = '';
+ var stream = this.stream;
+ var ch;
+ do {
+ ch = stream.getChar();
+ switch (ch) {
+ case undefined:
+ warn('Unterminated string');
+ done = true;
+ break;
+ case '(':
+ ++numParen;
+ str += ch;
+ break;
+ case ')':
+ if (--numParen == 0) {
+ done = true;
+ } else {
+ str += ch;
+ }
+ break;
+ case '\\':
+ ch = stream.getChar();
+ switch (ch) {
+ case undefined:
+ warn('Unterminated string');
+ done = true;
+ break;
+ case 'n':
+ str += '\n';
+ break;
+ case 'r':
+ str += '\r';
+ break;
+ case 't':
+ str += '\t';
+ break;
+ case 'b':
+ str += '\b';
+ break;
+ case 'f':
+ str += '\f';
+ break;
+ case '\\':
+ case '(':
+ case ')':
+ str += ch;
+ break;
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ var x = ch - '0';
+ ch = stream.lookChar();
+ if (ch >= '0' && ch <= '7') {
+ stream.skip();
+ x = (x << 3) + (ch - '0');
+ ch = stream.lookChar();
+ if (ch >= '0' && ch <= '7') {
+ stream.skip();
+ x = (x << 3) + (ch - '0');
+ }
+ }
+
+ str += String.fromCharCode(x);
+ break;
+ case '\r':
+ ch = stream.lookChar();
+ if (ch == '\n')
+ stream.skip();
+ break;
+ case '\n':
+ break;
+ default:
+ str += ch;
+ }
+ break;
+ default:
+ str += ch;
+ }
+ } while (!done);
+ return str;
+ },
+ getName: function Lexer_getName(ch) {
+ var str = '';
+ var stream = this.stream;
+ while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
+ stream.skip();
+ if (ch == '#') {
+ ch = stream.lookChar();
+ var x = toHexDigit(ch);
+ if (x != -1) {
+ stream.skip();
+ var x2 = toHexDigit(stream.getChar());
+ if (x2 == -1)
+ error('Illegal digit in hex char in name: ' + x2);
+ str += String.fromCharCode((x << 4) | x2);
+ } else {
+ str += '#';
+ str += ch;
+ }
+ } else {
+ str += ch;
+ }
+ }
+ if (str.length > 128)
+ error('Warning: name token is longer than allowed by the spec: ' +
+ str.length);
+ return new Name(str);
+ },
+ getHexString: function Lexer_getHexString(ch) {
+ var str = '';
+ var stream = this.stream;
+ for (;;) {
+ ch = stream.getChar();
+ if (ch == '>') {
+ break;
+ }
+ if (!ch) {
+ warn('Unterminated hex string');
+ break;
+ }
+ if (specialChars[ch.charCodeAt(0)] != 1) {
+ var x, x2;
+ if ((x = toHexDigit(ch)) == -1)
+ error('Illegal character in hex string: ' + ch);
+
+ ch = stream.getChar();
+ while (specialChars[ch.charCodeAt(0)] == 1)
+ ch = stream.getChar();
+
+ if ((x2 = toHexDigit(ch)) == -1)
+ error('Illegal character in hex string: ' + ch);
+
+ str += String.fromCharCode((x << 4) | x2);
+ }
+ }
+ return str;
+ },
+ getObj: function Lexer_getObj() {
+ // skip whitespace and comments
+ var comment = false;
+ var stream = this.stream;
+ var ch;
+ while (true) {
+ if (!(ch = stream.getChar()))
+ return EOF;
+ if (comment) {
+ if (ch == '\r' || ch == '\n')
+ comment = false;
+ } else if (ch == '%') {
+ comment = true;
+ } else if (specialChars[ch.charCodeAt(0)] != 1) {
+ break;
+ }
+ }
+
+ // start reading token
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case '+': case '-': case '.':
+ return this.getNumber(ch);
+ case '(':
+ return this.getString();
+ case '/':
+ return this.getName(ch);
+ // array punctuation
+ case '[':
+ case ']':
+ return Cmd.get(ch);
+ // hex string or dict punctuation
+ case '<':
+ ch = stream.lookChar();
+ if (ch == '<') {
+ // dict punctuation
+ stream.skip();
+ return Cmd.get('<<');
+ }
+ return this.getHexString(ch);
+ // dict punctuation
+ case '>':
+ ch = stream.lookChar();
+ if (ch == '>') {
+ stream.skip();
+ return Cmd.get('>>');
+ }
+ case '{':
+ case '}':
+ return Cmd.get(ch);
+ // fall through
+ case ')':
+ error('Illegal character: ' + ch);
+ }
+
+ // command
+ var str = ch;
+ while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
+ stream.skip();
+ if (str.length == 128)
+ error('Command token too long: ' + str.length);
+
+ str += ch;
+ }
+ if (str == 'true')
+ return true;
+ if (str == 'false')
+ return false;
+ if (str == 'null')
+ return null;
+ return Cmd.get(str);
+ },
+ skipToNextLine: function Lexer_skipToNextLine() {
+ var stream = this.stream;
+ while (true) {
+ var ch = stream.getChar();
+ if (!ch || ch == '\n')
+ return;
+ if (ch == '\r') {
+ if ((ch = stream.lookChar()) == '\n')
+ stream.skip();
+ return;
+ }
+ }
+ },
+ skip: function Lexer_skip() {
+ this.stream.skip();
+ }
+ };
+
+ return Lexer;
+})();
+
+var Linearization = (function LinearizationClosure() {
+ function Linearization(stream) {
+ this.parser = new Parser(new Lexer(stream), false, null);
+ var obj1 = this.parser.getObj();
+ var obj2 = this.parser.getObj();
+ var obj3 = this.parser.getObj();
+ this.linDict = this.parser.getObj();
+ if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') &&
+ isDict(this.linDict)) {
+ var obj = this.linDict.get('Linearized');
+ if (!(isNum(obj) && obj > 0))
+ this.linDict = null;
+ }
+ }
+
+ Linearization.prototype = {
+ getInt: function Linearization_getInt(name) {
+ var linDict = this.linDict;
+ var obj;
+ if (isDict(linDict) &&
+ isInt(obj = linDict.get(name)) &&
+ obj > 0) {
+ return obj;
+ }
+ error('"' + name + '" field in linearization table is invalid');
+ },
+ getHint: function Linearization_getHint(index) {
+ var linDict = this.linDict;
+ var obj1, obj2;
+ if (isDict(linDict) &&
+ isArray(obj1 = linDict.get('H')) &&
+ obj1.length >= 2 &&
+ isInt(obj2 = obj1[index]) &&
+ obj2 > 0) {
+ return obj2;
+ }
+ error('Hints table in linearization table is invalid: ' + index);
+ },
+ get length() {
+ if (!isDict(this.linDict))
+ return 0;
+ return this.getInt('L');
+ },
+ get hintsOffset() {
+ return this.getHint(0);
+ },
+ get hintsLength() {
+ return this.getHint(1);
+ },
+ get hintsOffset2() {
+ return this.getHint(2);
+ },
+ get hintsLenth2() {
+ return this.getHint(3);
+ },
+ get objectNumberFirst() {
+ return this.getInt('O');
+ },
+ get endFirst() {
+ return this.getInt('E');
+ },
+ get numPages() {
+ return this.getInt('N');
+ },
+ get mainXRefEntriesOffset() {
+ return this.getInt('T');
+ },
+ get pageFirst() {
+ return this.getInt('P');
+ }
+ };
+
+ return Linearization;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var PatternType = {
+ AXIAL: 2,
+ RADIAL: 3
+};
+
+var Pattern = (function PatternClosure() {
+ // Constructor should define this.getPattern
+ function Pattern() {
+ error('should not call Pattern constructor');
+ }
+
+ Pattern.prototype = {
+ // Input: current Canvas context
+ // Output: the appropriate fillStyle or strokeStyle
+ getPattern: function Pattern_getPattern(ctx) {
+ error('Should not call Pattern.getStyle: ' + ctx);
+ }
+ };
+
+ Pattern.shadingFromIR = function Pattern_shadingFromIR(raw) {
+ return Shadings[raw[0]].fromIR(raw);
+ };
+
+ Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref,
+ res) {
+
+ var dict = isStream(shading) ? shading.dict : shading;
+ var type = dict.get('ShadingType');
+
+ switch (type) {
+ case PatternType.AXIAL:
+ case PatternType.RADIAL:
+ // Both radial and axial shadings are handled by RadialAxial shading.
+ return new Shadings.RadialAxial(dict, matrix, xref, res);
+ default:
+ return new Shadings.Dummy();
+ }
+ };
+ return Pattern;
+})();
+
+var Shadings = {};
+
+// Radial and axial shading have very similar implementations
+// If needed, the implementations can be broken into two classes
+Shadings.RadialAxial = (function RadialAxialClosure() {
+ function RadialAxial(dict, matrix, xref, res, ctx) {
+ this.matrix = matrix;
+ this.coordsArr = dict.get('Coords');
+ this.shadingType = dict.get('ShadingType');
+ this.type = 'Pattern';
+
+ this.ctx = ctx;
+ var cs = dict.get('ColorSpace', 'CS');
+ cs = ColorSpace.parse(cs, xref, res);
+ this.cs = cs;
+
+ var t0 = 0.0, t1 = 1.0;
+ if (dict.has('Domain')) {
+ var domainArr = dict.get('Domain');
+ t0 = domainArr[0];
+ t1 = domainArr[1];
+ }
+
+ var extendStart = false, extendEnd = false;
+ if (dict.has('Extend')) {
+ var extendArr = dict.get('Extend');
+ extendStart = extendArr[0];
+ extendEnd = extendArr[1];
+ TODO('Support extend');
+ }
+
+ this.extendStart = extendStart;
+ this.extendEnd = extendEnd;
+
+ var fnObj = dict.get('Function');
+ if (isArray(fnObj))
+ error('No support for array of functions');
+ if (!isPDFFunction(fnObj))
+ error('Invalid function');
+ var fn = PDFFunction.parse(xref, fnObj);
+
+ // 10 samples seems good enough for now, but probably won't work
+ // if there are sharp color changes. Ideally, we would implement
+ // the spec faithfully and add lossless optimizations.
+ var step = (t1 - t0) / 10;
+ var diff = t1 - t0;
+
+ var colorStops = [];
+ for (var i = t0; i <= t1; i += step) {
+ var rgbColor = cs.getRgb(fn([i]));
+ var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
+ colorStops.push([(i - t0) / diff, cssColor]);
+ }
+
+ this.colorStops = colorStops;
+ }
+
+ RadialAxial.fromIR = function RadialAxial_fromIR(raw) {
+ var type = raw[1];
+ var colorStops = raw[2];
+ var p0 = raw[3];
+ var p1 = raw[4];
+ var r0 = raw[5];
+ var r1 = raw[6];
+ return {
+ type: 'Pattern',
+ getPattern: function(ctx) {
+ var curMatrix = ctx.mozCurrentTransform;
+ if (curMatrix) {
+ var userMatrix = ctx.mozCurrentTransformInverse;
+
+ p0 = Util.applyTransform(p0, curMatrix);
+ p0 = Util.applyTransform(p0, userMatrix);
+
+ p1 = Util.applyTransform(p1, curMatrix);
+ p1 = Util.applyTransform(p1, userMatrix);
+ }
+
+ var grad;
+ if (type == PatternType.AXIAL)
+ grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
+ else if (type == PatternType.RADIAL)
+ grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
+
+ for (var i = 0, ii = colorStops.length; i < ii; ++i) {
+ var c = colorStops[i];
+ grad.addColorStop(c[0], c[1]);
+ }
+ return grad;
+ }
+ };
+ };
+
+ RadialAxial.prototype = {
+ getIR: function RadialAxial_getIR() {
+ var coordsArr = this.coordsArr;
+ var type = this.shadingType;
+ if (type == PatternType.AXIAL) {
+ var p0 = [coordsArr[0], coordsArr[1]];
+ var p1 = [coordsArr[2], coordsArr[3]];
+ var r0 = null;
+ var r1 = null;
+ } else if (type == PatternType.RADIAL) {
+ var p0 = [coordsArr[0], coordsArr[1]];
+ var p1 = [coordsArr[3], coordsArr[4]];
+ var r0 = coordsArr[2];
+ var r1 = coordsArr[5];
+ } else {
+ error('getPattern type unknown: ' + type);
+ }
+
+ var matrix = this.matrix;
+ if (matrix) {
+ p0 = Util.applyTransform(p0, matrix);
+ p1 = Util.applyTransform(p1, matrix);
+ }
+
+ return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1];
+ }
+ };
+
+ return RadialAxial;
+})();
+
+Shadings.Dummy = (function DummyClosure() {
+ function Dummy() {
+ this.type = 'Pattern';
+ }
+
+ Dummy.fromIR = function Dummy_fromIR() {
+ return 'hotpink';
+ };
+
+ Dummy.prototype = {
+ getIR: function Dummy_getIR() {
+ return ['Dummy'];
+ }
+ };
+ return Dummy;
+})();
+
+var TilingPattern = (function TilingPatternClosure() {
+ var PaintType = {
+ COLORED: 1,
+ UNCOLORED: 2
+ };
+ var MAX_PATTERN_SIZE = 512;
+
+ function TilingPattern(IR, color, ctx, objs) {
+ var operatorList = IR[2];
+ this.matrix = IR[3];
+ var bbox = IR[4];
+ var xstep = IR[5];
+ var ystep = IR[6];
+ var paintType = IR[7];
+
+ TODO('TilingType');
+
+ this.curMatrix = ctx.mozCurrentTransform;
+ this.invMatrix = ctx.mozCurrentTransformInverse;
+ this.ctx = ctx;
+ this.type = 'Pattern';
+
+ var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+
+ var topLeft = [x0, y0];
+ // we want the canvas to be as large as the step size
+ var botRight = [x0 + xstep, y0 + ystep];
+
+ var width = botRight[0] - topLeft[0];
+ var height = botRight[1] - topLeft[1];
+
+ // TODO: hack to avoid OOM, we would ideally compute the tiling
+ // pattern to be only as large as the acual size in device space
+ // This could be computed with .mozCurrentTransform, but still
+ // needs to be implemented
+ while (Math.abs(width) > MAX_PATTERN_SIZE ||
+ Math.abs(height) > MAX_PATTERN_SIZE) {
+ width = height = MAX_PATTERN_SIZE;
+ }
+
+ var tmpCanvas = createScratchCanvas(width, height);
+
+ // set the new canvas element context as the graphics context
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var graphics = new CanvasGraphics(tmpCtx, objs);
+
+ switch (paintType) {
+ case PaintType.COLORED:
+ tmpCtx.fillStyle = ctx.fillStyle;
+ tmpCtx.strokeStyle = ctx.strokeStyle;
+ break;
+ case PaintType.UNCOLORED:
+ var cssColor = Util.makeCssRgb(this, color[0], color[1], color[2]);
+ tmpCtx.fillStyle = cssColor;
+ tmpCtx.strokeStyle = cssColor;
+ break;
+ default:
+ error('Unsupported paint type: ' + paintType);
+ }
+
+ var scale = [width / xstep, height / ystep];
+ this.scale = scale;
+
+ // transform coordinates to pattern space
+ var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
+ var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
+ graphics.transform.apply(graphics, tmpScale);
+ graphics.transform.apply(graphics, tmpTranslate);
+
+ if (bbox && isArray(bbox) && 4 == bbox.length) {
+ var bboxWidth = x1 - x0;
+ var bboxHeight = y1 - y0;
+ graphics.rectangle(x0, y0, bboxWidth, bboxHeight);
+ graphics.clip();
+ graphics.endPath();
+ }
+
+ graphics.executeOperatorList(operatorList);
+
+ this.canvas = tmpCanvas;
+ }
+
+ TilingPattern.getIR = function TilingPattern_getIR(operatorList, dict, args) {
+ var matrix = dict.get('Matrix');
+ var bbox = dict.get('BBox');
+ var xstep = dict.get('XStep');
+ var ystep = dict.get('YStep');
+ var paintType = dict.get('PaintType');
+
+ return [
+ 'TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, paintType
+ ];
+ };
+
+ TilingPattern.prototype = {
+ getPattern: function TilingPattern_getPattern() {
+ var matrix = this.matrix;
+ var curMatrix = this.curMatrix;
+ var ctx = this.ctx;
+
+ if (curMatrix)
+ ctx.setTransform.apply(ctx, curMatrix);
+
+ if (matrix)
+ ctx.transform.apply(ctx, matrix);
+
+ var scale = this.scale;
+ ctx.scale(1 / scale[0], 1 / scale[1]);
+
+ return ctx.createPattern(this.canvas, 'repeat');
+ }
+ };
+
+ return TilingPattern;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var Stream = (function StreamClosure() {
+ function Stream(arrayBuffer, start, length, dict) {
+ this.bytes = new Uint8Array(arrayBuffer);
+ this.start = start || 0;
+ this.pos = this.start;
+ this.end = (start + length) || this.bytes.length;
+ this.dict = dict;
+ }
+
+ // required methods for a stream. if a particular stream does not
+ // implement these, an error should be thrown
+ Stream.prototype = {
+ get length() {
+ return this.end - this.start;
+ },
+ getByte: function Stream_getByte() {
+ if (this.pos >= this.end)
+ return null;
+ return this.bytes[this.pos++];
+ },
+ // returns subarray of original buffer
+ // should only be read
+ getBytes: function Stream_getBytes(length) {
+ var bytes = this.bytes;
+ var pos = this.pos;
+ var strEnd = this.end;
+
+ if (!length)
+ return bytes.subarray(pos, strEnd);
+
+ var end = pos + length;
+ if (end > strEnd)
+ end = strEnd;
+
+ this.pos = end;
+ return bytes.subarray(pos, end);
+ },
+ lookChar: function Stream_lookChar() {
+ if (this.pos >= this.end)
+ return null;
+ return String.fromCharCode(this.bytes[this.pos]);
+ },
+ getChar: function Stream_getChar() {
+ if (this.pos >= this.end)
+ return null;
+ return String.fromCharCode(this.bytes[this.pos++]);
+ },
+ skip: function Stream_skip(n) {
+ if (!n)
+ n = 1;
+ this.pos += n;
+ },
+ reset: function Stream_reset() {
+ this.pos = this.start;
+ },
+ moveStart: function Stream_moveStart() {
+ this.start = this.pos;
+ },
+ makeSubStream: function Stream_makeSubStream(start, length, dict) {
+ return new Stream(this.bytes.buffer, start, length, dict);
+ },
+ isStream: true
+ };
+
+ return Stream;
+})();
+
+var StringStream = (function StringStreamClosure() {
+ function StringStream(str) {
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var n = 0; n < length; ++n)
+ bytes[n] = str.charCodeAt(n);
+ Stream.call(this, bytes);
+ }
+
+ StringStream.prototype = Stream.prototype;
+
+ return StringStream;
+})();
+
+// super class for the decoding streams
+var DecodeStream = (function DecodeStreamClosure() {
+ function DecodeStream() {
+ this.pos = 0;
+ this.bufferLength = 0;
+ this.eof = false;
+ this.buffer = null;
+ }
+
+ DecodeStream.prototype = {
+ ensureBuffer: function DecodeStream_ensureBuffer(requested) {
+ var buffer = this.buffer;
+ var current = buffer ? buffer.byteLength : 0;
+ if (requested < current)
+ return buffer;
+ var size = 512;
+ while (size < requested)
+ size <<= 1;
+ var buffer2 = new Uint8Array(size);
+ for (var i = 0; i < current; ++i)
+ buffer2[i] = buffer[i];
+ return (this.buffer = buffer2);
+ },
+ getByte: function DecodeStream_getByte() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return this.buffer[this.pos++];
+ },
+ getBytes: function DecodeStream_getBytes(length) {
+ var end, pos = this.pos;
+
+ if (length) {
+ this.ensureBuffer(pos + length);
+ end = pos + length;
+
+ while (!this.eof && this.bufferLength < end)
+ this.readBlock();
+
+ var bufEnd = this.bufferLength;
+ if (end > bufEnd)
+ end = bufEnd;
+ } else {
+ while (!this.eof)
+ this.readBlock();
+
+ end = this.bufferLength;
+
+ // checking if bufferLength is still 0 then
+ // the buffer has to be initialized
+ if (!end)
+ this.buffer = new Uint8Array(0);
+ }
+
+ this.pos = end;
+ return this.buffer.subarray(pos, end);
+ },
+ lookChar: function DecodeStream_lookChar() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return String.fromCharCode(this.buffer[this.pos]);
+ },
+ getChar: function DecodeStream_getChar() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return String.fromCharCode(this.buffer[this.pos++]);
+ },
+ makeSubStream: function DecodeStream_makeSubStream(start, length, dict) {
+ var end = start + length;
+ while (this.bufferLength <= end && !this.eof)
+ this.readBlock();
+ return new Stream(this.buffer, start, length, dict);
+ },
+ skip: function DecodeStream_skip(n) {
+ if (!n)
+ n = 1;
+ this.pos += n;
+ },
+ reset: function DecodeStream_reset() {
+ this.pos = 0;
+ }
+ };
+
+ return DecodeStream;
+})();
+
+var FakeStream = (function FakeStreamClosure() {
+ function FakeStream(stream) {
+ this.dict = stream.dict;
+ DecodeStream.call(this);
+ }
+
+ FakeStream.prototype = Object.create(DecodeStream.prototype);
+ FakeStream.prototype.readBlock = function FakeStream_readBlock() {
+ var bufferLength = this.bufferLength;
+ bufferLength += 1024;
+ var buffer = this.ensureBuffer(bufferLength);
+ this.bufferLength = bufferLength;
+ };
+
+ FakeStream.prototype.getBytes = function FakeStream_getBytes(length) {
+ var end, pos = this.pos;
+
+ if (length) {
+ this.ensureBuffer(pos + length);
+ end = pos + length;
+
+ while (!this.eof && this.bufferLength < end)
+ this.readBlock();
+
+ var bufEnd = this.bufferLength;
+ if (end > bufEnd)
+ end = bufEnd;
+ } else {
+ this.eof = true;
+ end = this.bufferLength;
+ }
+
+ this.pos = end;
+ return this.buffer.subarray(pos, end);
+ };
+
+ return FakeStream;
+})();
+
+var StreamsSequenceStream = (function StreamsSequenceStreamClosure() {
+ function StreamsSequenceStream(streams) {
+ this.streams = streams;
+ DecodeStream.call(this);
+ }
+
+ StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype);
+
+ StreamsSequenceStream.prototype.readBlock =
+ function streamSequenceStreamReadBlock() {
+
+ var streams = this.streams;
+ if (streams.length == 0) {
+ this.eof = true;
+ return;
+ }
+ var stream = streams.shift();
+ var chunk = stream.getBytes();
+ var bufferLength = this.bufferLength;
+ var newLength = bufferLength + chunk.length;
+ var buffer = this.ensureBuffer(newLength);
+ buffer.set(chunk, bufferLength);
+ this.bufferLength = newLength;
+ };
+
+ return StreamsSequenceStream;
+})();
+
+var FlateStream = (function FlateStreamClosure() {
+ var codeLenCodeMap = new Uint32Array([
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ ]);
+
+ var lengthDecode = new Uint32Array([
+ 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a,
+ 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f,
+ 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073,
+ 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102
+ ]);
+
+ var distDecode = new Uint32Array([
+ 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d,
+ 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1,
+ 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01,
+ 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001
+ ]);
+
+ var fixedLitCodeTab = [new Uint32Array([
+ 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0,
+ 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0,
+ 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0,
+ 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0,
+ 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8,
+ 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8,
+ 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8,
+ 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8,
+ 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4,
+ 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4,
+ 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4,
+ 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4,
+ 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc,
+ 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec,
+ 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc,
+ 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc,
+ 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2,
+ 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2,
+ 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2,
+ 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2,
+ 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca,
+ 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea,
+ 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da,
+ 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa,
+ 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6,
+ 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6,
+ 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6,
+ 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6,
+ 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce,
+ 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee,
+ 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de,
+ 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe,
+ 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1,
+ 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1,
+ 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1,
+ 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1,
+ 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9,
+ 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9,
+ 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9,
+ 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9,
+ 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5,
+ 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5,
+ 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5,
+ 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5,
+ 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd,
+ 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed,
+ 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd,
+ 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd,
+ 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3,
+ 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3,
+ 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3,
+ 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3,
+ 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb,
+ 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb,
+ 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db,
+ 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb,
+ 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7,
+ 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7,
+ 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7,
+ 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7,
+ 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf,
+ 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef,
+ 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df,
+ 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff
+ ]), 9];
+
+ var fixedDistCodeTab = [new Uint32Array([
+ 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c,
+ 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000,
+ 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d,
+ 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
+ ]), 5];
+
+ function FlateStream(stream) {
+ var bytes = stream.getBytes();
+ var bytesPos = 0;
+
+ this.dict = stream.dict;
+ var cmf = bytes[bytesPos++];
+ var flg = bytes[bytesPos++];
+ if (cmf == -1 || flg == -1)
+ error('Invalid header in flate stream: ' + cmf + ', ' + flg);
+ if ((cmf & 0x0f) != 0x08)
+ error('Unknown compression method in flate stream: ' + cmf + ', ' + flg);
+ if ((((cmf << 8) + flg) % 31) != 0)
+ error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg);
+ if (flg & 0x20)
+ error('FDICT bit set in flate stream: ' + cmf + ', ' + flg);
+
+ this.bytes = bytes;
+ this.bytesPos = bytesPos;
+
+ this.codeSize = 0;
+ this.codeBuf = 0;
+
+ DecodeStream.call(this);
+ }
+
+ FlateStream.prototype = Object.create(DecodeStream.prototype);
+
+ FlateStream.prototype.getBits = function FlateStream_getBits(bits) {
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
+
+ var b;
+ while (codeSize < bits) {
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad encoding in flate stream');
+ codeBuf |= b << codeSize;
+ codeSize += 8;
+ }
+ b = codeBuf & ((1 << bits) - 1);
+ this.codeBuf = codeBuf >> bits;
+ this.codeSize = codeSize -= bits;
+ this.bytesPos = bytesPos;
+ return b;
+ };
+
+ FlateStream.prototype.getCode = function FlateStream_getCode(table) {
+ var codes = table[0];
+ var maxLen = table[1];
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
+
+ while (codeSize < maxLen) {
+ var b;
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad encoding in flate stream');
+ codeBuf |= (b << codeSize);
+ codeSize += 8;
+ }
+ var code = codes[codeBuf & ((1 << maxLen) - 1)];
+ var codeLen = code >> 16;
+ var codeVal = code & 0xffff;
+ if (codeSize == 0 || codeSize < codeLen || codeLen == 0)
+ error('Bad encoding in flate stream');
+ this.codeBuf = (codeBuf >> codeLen);
+ this.codeSize = (codeSize - codeLen);
+ this.bytesPos = bytesPos;
+ return codeVal;
+ };
+
+ FlateStream.prototype.generateHuffmanTable =
+ function flateStreamGenerateHuffmanTable(lengths) {
+ var n = lengths.length;
+
+ // find max code length
+ var maxLen = 0;
+ for (var i = 0; i < n; ++i) {
+ if (lengths[i] > maxLen)
+ maxLen = lengths[i];
+ }
+
+ // build the table
+ var size = 1 << maxLen;
+ var codes = new Uint32Array(size);
+ for (var len = 1, code = 0, skip = 2;
+ len <= maxLen;
+ ++len, code <<= 1, skip <<= 1) {
+ for (var val = 0; val < n; ++val) {
+ if (lengths[val] == len) {
+ // bit-reverse the code
+ var code2 = 0;
+ var t = code;
+ for (var i = 0; i < len; ++i) {
+ code2 = (code2 << 1) | (t & 1);
+ t >>= 1;
+ }
+
+ // fill the table entries
+ for (var i = code2; i < size; i += skip)
+ codes[i] = (len << 16) | val;
+
+ ++code;
+ }
+ }
+ }
+
+ return [codes, maxLen];
+ };
+
+ FlateStream.prototype.readBlock = function FlateStream_readBlock() {
+ // read block header
+ var hdr = this.getBits(3);
+ if (hdr & 1)
+ this.eof = true;
+ hdr >>= 1;
+
+ if (hdr == 0) { // uncompressed block
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
+ var b;
+
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad block header in flate stream');
+ var blockLen = b;
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad block header in flate stream');
+ blockLen |= (b << 8);
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad block header in flate stream');
+ var check = b;
+ if (typeof (b = bytes[bytesPos++]) == 'undefined')
+ error('Bad block header in flate stream');
+ check |= (b << 8);
+ if (check != (~blockLen & 0xffff))
+ error('Bad uncompressed block length in flate stream');
+
+ this.codeBuf = 0;
+ this.codeSize = 0;
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + blockLen);
+ var end = bufferLength + blockLen;
+ this.bufferLength = end;
+ for (var n = bufferLength; n < end; ++n) {
+ if (typeof (b = bytes[bytesPos++]) == 'undefined') {
+ this.eof = true;
+ break;
+ }
+ buffer[n] = b;
+ }
+ this.bytesPos = bytesPos;
+ return;
+ }
+
+ var litCodeTable;
+ var distCodeTable;
+ if (hdr == 1) { // compressed block, fixed codes
+ litCodeTable = fixedLitCodeTab;
+ distCodeTable = fixedDistCodeTab;
+ } else if (hdr == 2) { // compressed block, dynamic codes
+ var numLitCodes = this.getBits(5) + 257;
+ var numDistCodes = this.getBits(5) + 1;
+ var numCodeLenCodes = this.getBits(4) + 4;
+
+ // build the code lengths code table
+ var codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length);
+
+ for (var i = 0; i < numCodeLenCodes; ++i)
+ codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3);
+ var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
+
+ // build the literal and distance code tables
+ var len = 0;
+ var i = 0;
+ var codes = numLitCodes + numDistCodes;
+ var codeLengths = new Uint8Array(codes);
+ while (i < codes) {
+ var code = this.getCode(codeLenCodeTab);
+ if (code == 16) {
+ var bitsLength = 2, bitsOffset = 3, what = len;
+ } else if (code == 17) {
+ var bitsLength = 3, bitsOffset = 3, what = (len = 0);
+ } else if (code == 18) {
+ var bitsLength = 7, bitsOffset = 11, what = (len = 0);
+ } else {
+ codeLengths[i++] = len = code;
+ continue;
+ }
+
+ var repeatLength = this.getBits(bitsLength) + bitsOffset;
+ while (repeatLength-- > 0)
+ codeLengths[i++] = what;
+ }
+
+ litCodeTable =
+ this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
+ distCodeTable =
+ this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
+ } else {
+ error('Unknown block type in flate stream');
+ }
+
+ var buffer = this.buffer;
+ var limit = buffer ? buffer.length : 0;
+ var pos = this.bufferLength;
+ while (true) {
+ var code1 = this.getCode(litCodeTable);
+ if (code1 < 256) {
+ if (pos + 1 >= limit) {
+ buffer = this.ensureBuffer(pos + 1);
+ limit = buffer.length;
+ }
+ buffer[pos++] = code1;
+ continue;
+ }
+ if (code1 == 256) {
+ this.bufferLength = pos;
+ return;
+ }
+ code1 -= 257;
+ code1 = lengthDecode[code1];
+ var code2 = code1 >> 16;
+ if (code2 > 0)
+ code2 = this.getBits(code2);
+ var len = (code1 & 0xffff) + code2;
+ code1 = this.getCode(distCodeTable);
+ code1 = distDecode[code1];
+ code2 = code1 >> 16;
+ if (code2 > 0)
+ code2 = this.getBits(code2);
+ var dist = (code1 & 0xffff) + code2;
+ if (pos + len >= limit) {
+ buffer = this.ensureBuffer(pos + len);
+ limit = buffer.length;
+ }
+ for (var k = 0; k < len; ++k, ++pos)
+ buffer[pos] = buffer[pos - dist];
+ }
+ };
+
+ return FlateStream;
+})();
+
+var PredictorStream = (function PredictorStreamClosure() {
+ function PredictorStream(stream, params) {
+ var predictor = this.predictor = params.get('Predictor') || 1;
+
+ if (predictor <= 1)
+ return stream; // no prediction
+ if (predictor !== 2 && (predictor < 10 || predictor > 15))
+ error('Unsupported predictor: ' + predictor);
+
+ if (predictor === 2)
+ this.readBlock = this.readBlockTiff;
+ else
+ this.readBlock = this.readBlockPng;
+
+ this.stream = stream;
+ this.dict = stream.dict;
+
+ var colors = this.colors = params.get('Colors') || 1;
+ var bits = this.bits = params.get('BitsPerComponent') || 8;
+ var columns = this.columns = params.get('Columns') || 1;
+
+ this.pixBytes = (colors * bits + 7) >> 3;
+ this.rowBytes = (columns * colors * bits + 7) >> 3;
+
+ DecodeStream.call(this);
+ return this;
+ }
+
+ PredictorStream.prototype = Object.create(DecodeStream.prototype);
+
+ PredictorStream.prototype.readBlockTiff =
+ function predictorStreamReadBlockTiff() {
+ var rowBytes = this.rowBytes;
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+
+ var bits = this.bits;
+ var colors = this.colors;
+
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ var inbuf = 0, outbuf = 0;
+ var inbits = 0, outbits = 0;
+ var pos = bufferLength;
+
+ if (bits === 1) {
+ for (var i = 0; i < rowBytes; ++i) {
+ var c = rawBytes[i];
+ inbuf = (inbuf << 8) | c;
+ // bitwise addition is exclusive or
+ // first shift inbuf and then add
+ buffer[pos++] = (c ^ (inbuf >> colors)) & 0xFF;
+ // truncate inbuf (assumes colors < 16)
+ inbuf &= 0xFFFF;
+ }
+ } else if (bits === 8) {
+ for (var i = 0; i < colors; ++i)
+ buffer[pos++] = rawBytes[i];
+ for (; i < rowBytes; ++i) {
+ buffer[pos] = buffer[pos - colors] + rawBytes[i];
+ pos++;
+ }
+ } else {
+ var compArray = new Uint8Array(colors + 1);
+ var bitMask = (1 << bits) - 1;
+ var j = 0, k = bufferLength;
+ var columns = this.columns;
+ for (var i = 0; i < columns; ++i) {
+ for (var kk = 0; kk < colors; ++kk) {
+ if (inbits < bits) {
+ inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF);
+ inbits += 8;
+ }
+ compArray[kk] = (compArray[kk] +
+ (inbuf >> (inbits - bits))) & bitMask;
+ inbits -= bits;
+ outbuf = (outbuf << bits) | compArray[kk];
+ outbits += bits;
+ if (outbits >= 8) {
+ buffer[k++] = (outbuf >> (outbits - 8)) & 0xFF;
+ outbits -= 8;
+ }
+ }
+ }
+ if (outbits > 0) {
+ buffer[k++] = (outbuf << (8 - outbits)) +
+ (inbuf & ((1 << (8 - outbits)) - 1));
+ }
+ }
+ this.bufferLength += rowBytes;
+ };
+
+ PredictorStream.prototype.readBlockPng =
+ function predictorStreamReadBlockPng() {
+
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+
+ var predictor = this.stream.getByte();
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+
+ var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
+ if (prevRow.length == 0)
+ prevRow = new Uint8Array(rowBytes);
+
+ var j = bufferLength;
+ switch (predictor) {
+ case 0:
+ for (var i = 0; i < rowBytes; ++i)
+ buffer[j++] = rawBytes[i];
+ break;
+ case 1:
+ for (var i = 0; i < pixBytes; ++i)
+ buffer[j++] = rawBytes[i];
+ for (; i < rowBytes; ++i) {
+ buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xFF;
+ j++;
+ }
+ break;
+ case 2:
+ for (var i = 0; i < rowBytes; ++i)
+ buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xFF;
+ break;
+ case 3:
+ for (var i = 0; i < pixBytes; ++i)
+ buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
+ for (; i < rowBytes; ++i) {
+ buffer[j] = (((prevRow[i] + buffer[j - pixBytes]) >> 1) +
+ rawBytes[i]) & 0xFF;
+ j++;
+ }
+ break;
+ case 4:
+ // we need to save the up left pixels values. the simplest way
+ // is to create a new buffer
+ for (var i = 0; i < pixBytes; ++i) {
+ var up = prevRow[i];
+ var c = rawBytes[i];
+ buffer[j++] = up + c;
+ }
+ for (; i < rowBytes; ++i) {
+ var up = prevRow[i];
+ var upLeft = prevRow[i - pixBytes];
+ var left = buffer[j - pixBytes];
+ var p = left + up - upLeft;
+
+ var pa = p - left;
+ if (pa < 0)
+ pa = -pa;
+ var pb = p - up;
+ if (pb < 0)
+ pb = -pb;
+ var pc = p - upLeft;
+ if (pc < 0)
+ pc = -pc;
+
+ var c = rawBytes[i];
+ if (pa <= pb && pa <= pc)
+ buffer[j++] = left + c;
+ else if (pb <= pc)
+ buffer[j++] = up + c;
+ else
+ buffer[j++] = upLeft + c;
+ }
+ break;
+ default:
+ error('Unsupported predictor: ' + predictor);
+ }
+ this.bufferLength += rowBytes;
+ };
+
+ return PredictorStream;
+})();
+
+/**
+ * Depending on the type of JPEG a JpegStream is handled in different ways. For
+ * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
+ * data is stored and then loaded by the browser. For unsupported JPEG's we use
+ * a library to decode these images and the stream behaves like all the other
+ * DecodeStreams.
+ */
+var JpegStream = (function JpegStreamClosure() {
+ function isAdobeImage(bytes) {
+ var maxBytesScanned = Math.max(bytes.length - 16, 1024);
+ // Looking for APP14, 'Adobe'
+ for (var i = 0; i < maxBytesScanned; ++i) {
+ if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE &&
+ bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E &&
+ bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 &&
+ bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 &&
+ bytes[i + 8] == 0x65 && bytes[i + 9] == 0x00)
+ return true;
+ // scanning until frame tag
+ if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0)
+ break;
+ }
+ return false;
+ }
+
+ function fixAdobeImage(bytes) {
+ // Inserting 'EMBED' marker after JPEG signature
+ var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45,
+ 0x44, 0]);
+ var newBytes = new Uint8Array(bytes.length + embedMarker.length);
+ newBytes.set(bytes, embedMarker.length);
+ // copy JPEG header
+ newBytes[0] = bytes[0];
+ newBytes[1] = bytes[1];
+ newBytes.set(embedMarker, 2);
+ return newBytes;
+ }
+
+ function JpegStream(bytes, dict, xref) {
+ // TODO: per poppler, some images may have 'junk' before that
+ // need to be removed
+ this.dict = dict;
+
+ this.isAdobeImage = false;
+ this.colorTransform = dict.get('ColorTransform') || -1;
+
+ if (isAdobeImage(bytes)) {
+ this.isAdobeImage = true;
+ bytes = fixAdobeImage(bytes);
+ }
+
+ this.bytes = bytes;
+
+ DecodeStream.call(this);
+ }
+
+ JpegStream.prototype = Object.create(DecodeStream.prototype);
+
+ JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
+ if (this.bufferLength)
+ return;
+ try {
+ var jpegImage = new JpegImage();
+ if (this.colorTransform != -1)
+ jpegImage.colorTransform = this.colorTransform;
+ jpegImage.parse(this.bytes);
+ var width = jpegImage.width;
+ var height = jpegImage.height;
+ var data = jpegImage.getData(width, height);
+ this.buffer = data;
+ this.bufferLength = data.length;
+ } catch (e) {
+ error('JPEG error: ' + e);
+ }
+ };
+ JpegStream.prototype.getIR = function JpegStream_getIR() {
+ return bytesToString(this.bytes);
+ };
+ JpegStream.prototype.getChar = function JpegStream_getChar() {
+ error('internal error: getChar is not valid on JpegStream');
+ };
+ /**
+ * Checks if the image can be decoded and displayed by the browser without any
+ * further processing such as color space conversions.
+ */
+ JpegStream.prototype.isNativelySupported =
+ function JpegStream_isNativelySupported(xref, res) {
+ var cs = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res);
+ // when bug 674619 lands, let's check if browser can do
+ // normal cmyk and then we won't need to decode in JS
+ if (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB')
+ return true;
+ if (cs.name === 'DeviceCMYK' && !this.isAdobeImage &&
+ this.colorTransform < 1)
+ return true;
+ return false;
+ };
+ /**
+ * Checks if the image can be decoded by the browser.
+ */
+ JpegStream.prototype.isNativelyDecodable =
+ function JpegStream_isNativelyDecodable(xref, res) {
+ var cs = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res);
+ var numComps = cs.numComps;
+ if (numComps == 1 || numComps == 3)
+ return true;
+
+ return false;
+ };
+
+ return JpegStream;
+})();
+
+/**
+ * For JPEG 2000's we use a library to decode these images and
+ * the stream behaves like all the other DecodeStreams.
+ */
+var JpxStream = (function JpxStreamClosure() {
+ function JpxStream(bytes, dict) {
+ this.dict = dict;
+ this.bytes = bytes;
+
+ DecodeStream.call(this);
+ }
+
+ JpxStream.prototype = Object.create(DecodeStream.prototype);
+
+ JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) {
+ if (this.bufferLength)
+ return;
+
+ var jpxImage = new JpxImage();
+ jpxImage.parse(this.bytes);
+
+ var width = jpxImage.width;
+ var height = jpxImage.height;
+ var componentsCount = jpxImage.componentsCount;
+ if (componentsCount != 1 && componentsCount != 3 && componentsCount != 4)
+ error('JPX with ' + componentsCount + ' components is not supported');
+
+ var data = new Uint8Array(width * height * componentsCount);
+
+ for (var k = 0, kk = jpxImage.tiles.length; k < kk; k++) {
+ var tileCompoments = jpxImage.tiles[k];
+ var tileWidth = tileCompoments[0].width;
+ var tileHeight = tileCompoments[0].height;
+ var tileLeft = tileCompoments[0].left;
+ var tileTop = tileCompoments[0].top;
+
+ var dataPosition, sourcePosition, data0, data1, data2, data3, rowFeed;
+ switch (componentsCount) {
+ case 1:
+ data0 = tileCompoments[0].items;
+
+ dataPosition = width * tileTop + tileLeft;
+ rowFeed = width - tileWidth;
+ sourcePosition = 0;
+ for (var j = 0; j < tileHeight; j++) {
+ for (var i = 0; i < tileWidth; i++)
+ data[dataPosition++] = data0[sourcePosition++];
+ dataPosition += rowFeed;
+ }
+ break;
+ case 3:
+ data0 = tileCompoments[0].items;
+ data1 = tileCompoments[1].items;
+ data2 = tileCompoments[2].items;
+
+ dataPosition = (width * tileTop + tileLeft) * 3;
+ rowFeed = (width - tileWidth) * 3;
+ sourcePosition = 0;
+ for (var j = 0; j < tileHeight; j++) {
+ for (var i = 0; i < tileWidth; i++) {
+ data[dataPosition++] = data0[sourcePosition];
+ data[dataPosition++] = data1[sourcePosition];
+ data[dataPosition++] = data2[sourcePosition];
+ sourcePosition++;
+ }
+ dataPosition += rowFeed;
+ }
+ break;
+ case 4:
+ data0 = tileCompoments[0].items;
+ data1 = tileCompoments[1].items;
+ data2 = tileCompoments[2].items;
+ data3 = tileCompoments[3].items;
+
+ dataPosition = (width * tileTop + tileLeft) * 4;
+ rowFeed = (width - tileWidth) * 4;
+ sourcePosition = 0;
+ for (var j = 0; j < tileHeight; j++) {
+ for (var i = 0; i < tileWidth; i++) {
+ data[dataPosition++] = data0[sourcePosition];
+ data[dataPosition++] = data1[sourcePosition];
+ data[dataPosition++] = data2[sourcePosition];
+ data[dataPosition++] = data3[sourcePosition];
+ sourcePosition++;
+ }
+ dataPosition += rowFeed;
+ }
+ break;
+ }
+ }
+
+ this.buffer = data;
+ this.bufferLength = data.length;
+ };
+ JpxStream.prototype.getChar = function JpxStream_getChar() {
+ error('internal error: getChar is not valid on JpxStream');
+ };
+
+ return JpxStream;
+})();
+
+var DecryptStream = (function DecryptStreamClosure() {
+ function DecryptStream(str, decrypt) {
+ this.str = str;
+ this.dict = str.dict;
+ this.decrypt = decrypt;
+
+ DecodeStream.call(this);
+ }
+
+ var chunkSize = 512;
+
+ DecryptStream.prototype = Object.create(DecodeStream.prototype);
+
+ DecryptStream.prototype.readBlock = function DecryptStream_readBlock() {
+ var chunk = this.str.getBytes(chunkSize);
+ if (!chunk || chunk.length == 0) {
+ this.eof = true;
+ return;
+ }
+ var decrypt = this.decrypt;
+ chunk = decrypt(chunk);
+
+ var bufferLength = this.bufferLength;
+ var i, n = chunk.length;
+ var buffer = this.ensureBuffer(bufferLength + n);
+ for (i = 0; i < n; i++)
+ buffer[bufferLength++] = chunk[i];
+ this.bufferLength = bufferLength;
+ };
+
+ return DecryptStream;
+})();
+
+var Ascii85Stream = (function Ascii85StreamClosure() {
+ function Ascii85Stream(str) {
+ this.str = str;
+ this.dict = str.dict;
+ this.input = new Uint8Array(5);
+
+ DecodeStream.call(this);
+ }
+
+ Ascii85Stream.prototype = Object.create(DecodeStream.prototype);
+
+ Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() {
+ var tildaCode = '~'.charCodeAt(0);
+ var zCode = 'z'.charCodeAt(0);
+ var str = this.str;
+
+ var c = str.getByte();
+ while (Lexer.isSpace(String.fromCharCode(c)))
+ c = str.getByte();
+
+ if (!c || c === tildaCode) {
+ this.eof = true;
+ return;
+ }
+
+ var bufferLength = this.bufferLength, buffer;
+
+ // special code for z
+ if (c == zCode) {
+ buffer = this.ensureBuffer(bufferLength + 4);
+ for (var i = 0; i < 4; ++i)
+ buffer[bufferLength + i] = 0;
+ this.bufferLength += 4;
+ } else {
+ var input = this.input;
+ input[0] = c;
+ for (var i = 1; i < 5; ++i) {
+ c = str.getByte();
+ while (Lexer.isSpace(String.fromCharCode(c)))
+ c = str.getByte();
+
+ input[i] = c;
+
+ if (!c || c == tildaCode)
+ break;
+ }
+ buffer = this.ensureBuffer(bufferLength + i - 1);
+ this.bufferLength += i - 1;
+
+ // partial ending;
+ if (i < 5) {
+ for (; i < 5; ++i)
+ input[i] = 0x21 + 84;
+ this.eof = true;
+ }
+ var t = 0;
+ for (var i = 0; i < 5; ++i)
+ t = t * 85 + (input[i] - 0x21);
+
+ for (var i = 3; i >= 0; --i) {
+ buffer[bufferLength + i] = t & 0xFF;
+ t >>= 8;
+ }
+ }
+ };
+
+ return Ascii85Stream;
+})();
+
+var AsciiHexStream = (function AsciiHexStreamClosure() {
+ function AsciiHexStream(str) {
+ this.str = str;
+ this.dict = str.dict;
+
+ DecodeStream.call(this);
+ }
+
+ var hexvalueMap = {
+ 9: -1, // \t
+ 32: -1, // space
+ 48: 0,
+ 49: 1,
+ 50: 2,
+ 51: 3,
+ 52: 4,
+ 53: 5,
+ 54: 6,
+ 55: 7,
+ 56: 8,
+ 57: 9,
+ 65: 10,
+ 66: 11,
+ 67: 12,
+ 68: 13,
+ 69: 14,
+ 70: 15,
+ 97: 10,
+ 98: 11,
+ 99: 12,
+ 100: 13,
+ 101: 14,
+ 102: 15
+ };
+
+ AsciiHexStream.prototype = Object.create(DecodeStream.prototype);
+
+ AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() {
+ var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n,
+ decodeLength, buffer, bufferLength, i, length;
+
+ decodeLength = (bytes.length + 1) >> 1;
+ buffer = this.ensureBuffer(this.bufferLength + decodeLength);
+ bufferLength = this.bufferLength;
+
+ for (i = 0, length = bytes.length; i < length; i++) {
+ c = hexvalueMap[bytes[i]];
+ while (c == -1 && (i + 1) < length) {
+ c = hexvalueMap[bytes[++i]];
+ }
+
+ if ((i + 1) < length && (bytes[i + 1] !== gtCode)) {
+ n = hexvalueMap[bytes[++i]];
+ buffer[bufferLength++] = c * 16 + n;
+ } else {
+ // EOD marker at an odd number, behave as if a 0 followed the last
+ // digit.
+ if (bytes[i] !== gtCode) {
+ buffer[bufferLength++] = c * 16;
+ }
+ }
+ }
+
+ this.bufferLength = bufferLength;
+ this.eof = true;
+ };
+
+ return AsciiHexStream;
+})();
+
+var RunLengthStream = (function RunLengthStreamClosure() {
+ function RunLengthStream(str) {
+ this.str = str;
+ this.dict = str.dict;
+
+ DecodeStream.call(this);
+ }
+
+ RunLengthStream.prototype = Object.create(DecodeStream.prototype);
+
+ RunLengthStream.prototype.readBlock = function RunLengthStream_readBlock() {
+ // The repeatHeader has following format. The first byte defines type of run
+ // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes
+ // (in addition to the second byte from the header), n = 129 through 255 -
+ // duplicate the second byte from the header (257 - n) times, n = 128 - end.
+ var repeatHeader = this.str.getBytes(2);
+ if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] == 128) {
+ this.eof = true;
+ return;
+ }
+
+ var bufferLength = this.bufferLength;
+ var n = repeatHeader[0];
+ if (n < 128) {
+ // copy n bytes
+ var buffer = this.ensureBuffer(bufferLength + n + 1);
+ buffer[bufferLength++] = repeatHeader[1];
+ if (n > 0) {
+ var source = this.str.getBytes(n);
+ buffer.set(source, bufferLength);
+ bufferLength += n;
+ }
+ } else {
+ n = 257 - n;
+ var b = repeatHeader[1];
+ var buffer = this.ensureBuffer(bufferLength + n + 1);
+ for (var i = 0; i < n; i++)
+ buffer[bufferLength++] = b;
+ }
+ this.bufferLength = bufferLength;
+ };
+
+ return RunLengthStream;
+})();
+
+var CCITTFaxStream = (function CCITTFaxStreamClosure() {
+
+ var ccittEOL = -2;
+ var twoDimPass = 0;
+ var twoDimHoriz = 1;
+ var twoDimVert0 = 2;
+ var twoDimVertR1 = 3;
+ var twoDimVertL1 = 4;
+ var twoDimVertR2 = 5;
+ var twoDimVertL2 = 6;
+ var twoDimVertR3 = 7;
+ var twoDimVertL3 = 8;
+
+ var twoDimTable = [
+ [-1, -1], [-1, -1], // 000000x
+ [7, twoDimVertL3], // 0000010
+ [7, twoDimVertR3], // 0000011
+ [6, twoDimVertL2], [6, twoDimVertL2], // 000010x
+ [6, twoDimVertR2], [6, twoDimVertR2], // 000011x
+ [4, twoDimPass], [4, twoDimPass], // 0001xxx
+ [4, twoDimPass], [4, twoDimPass],
+ [4, twoDimPass], [4, twoDimPass],
+ [4, twoDimPass], [4, twoDimPass],
+ [3, twoDimHoriz], [3, twoDimHoriz], // 001xxxx
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimHoriz], [3, twoDimHoriz],
+ [3, twoDimVertL1], [3, twoDimVertL1], // 010xxxx
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertL1], [3, twoDimVertL1],
+ [3, twoDimVertR1], [3, twoDimVertR1], // 011xxxx
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [3, twoDimVertR1], [3, twoDimVertR1],
+ [1, twoDimVert0], [1, twoDimVert0], // 1xxxxxx
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0],
+ [1, twoDimVert0], [1, twoDimVert0]
+ ];
+
+ var whiteTable1 = [
+ [-1, -1], // 00000
+ [12, ccittEOL], // 00001
+ [-1, -1], [-1, -1], // 0001x
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 001xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 010xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 011xx
+ [11, 1792], [11, 1792], // 1000x
+ [12, 1984], // 10010
+ [12, 2048], // 10011
+ [12, 2112], // 10100
+ [12, 2176], // 10101
+ [12, 2240], // 10110
+ [12, 2304], // 10111
+ [11, 1856], [11, 1856], // 1100x
+ [11, 1920], [11, 1920], // 1101x
+ [12, 2368], // 11100
+ [12, 2432], // 11101
+ [12, 2496], // 11110
+ [12, 2560] // 11111
+ ];
+
+ var whiteTable2 = [
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx
+ [8, 29], [8, 29], // 00000010x
+ [8, 30], [8, 30], // 00000011x
+ [8, 45], [8, 45], // 00000100x
+ [8, 46], [8, 46], // 00000101x
+ [7, 22], [7, 22], [7, 22], [7, 22], // 0000011xx
+ [7, 23], [7, 23], [7, 23], [7, 23], // 0000100xx
+ [8, 47], [8, 47], // 00001010x
+ [8, 48], [8, 48], // 00001011x
+ [6, 13], [6, 13], [6, 13], [6, 13], // 000011xxx
+ [6, 13], [6, 13], [6, 13], [6, 13],
+ [7, 20], [7, 20], [7, 20], [7, 20], // 0001000xx
+ [8, 33], [8, 33], // 00010010x
+ [8, 34], [8, 34], // 00010011x
+ [8, 35], [8, 35], // 00010100x
+ [8, 36], [8, 36], // 00010101x
+ [8, 37], [8, 37], // 00010110x
+ [8, 38], [8, 38], // 00010111x
+ [7, 19], [7, 19], [7, 19], [7, 19], // 0001100xx
+ [8, 31], [8, 31], // 00011010x
+ [8, 32], [8, 32], // 00011011x
+ [6, 1], [6, 1], [6, 1], [6, 1], // 000111xxx
+ [6, 1], [6, 1], [6, 1], [6, 1],
+ [6, 12], [6, 12], [6, 12], [6, 12], // 001000xxx
+ [6, 12], [6, 12], [6, 12], [6, 12],
+ [8, 53], [8, 53], // 00100100x
+ [8, 54], [8, 54], // 00100101x
+ [7, 26], [7, 26], [7, 26], [7, 26], // 0010011xx
+ [8, 39], [8, 39], // 00101000x
+ [8, 40], [8, 40], // 00101001x
+ [8, 41], [8, 41], // 00101010x
+ [8, 42], [8, 42], // 00101011x
+ [8, 43], [8, 43], // 00101100x
+ [8, 44], [8, 44], // 00101101x
+ [7, 21], [7, 21], [7, 21], [7, 21], // 0010111xx
+ [7, 28], [7, 28], [7, 28], [7, 28], // 0011000xx
+ [8, 61], [8, 61], // 00110010x
+ [8, 62], [8, 62], // 00110011x
+ [8, 63], [8, 63], // 00110100x
+ [8, 0], [8, 0], // 00110101x
+ [8, 320], [8, 320], // 00110110x
+ [8, 384], [8, 384], // 00110111x
+ [5, 10], [5, 10], [5, 10], [5, 10], // 00111xxxx
+ [5, 10], [5, 10], [5, 10], [5, 10],
+ [5, 10], [5, 10], [5, 10], [5, 10],
+ [5, 10], [5, 10], [5, 10], [5, 10],
+ [5, 11], [5, 11], [5, 11], [5, 11], // 01000xxxx
+ [5, 11], [5, 11], [5, 11], [5, 11],
+ [5, 11], [5, 11], [5, 11], [5, 11],
+ [5, 11], [5, 11], [5, 11], [5, 11],
+ [7, 27], [7, 27], [7, 27], [7, 27], // 0100100xx
+ [8, 59], [8, 59], // 01001010x
+ [8, 60], [8, 60], // 01001011x
+ [9, 1472], // 010011000
+ [9, 1536], // 010011001
+ [9, 1600], // 010011010
+ [9, 1728], // 010011011
+ [7, 18], [7, 18], [7, 18], [7, 18], // 0100111xx
+ [7, 24], [7, 24], [7, 24], [7, 24], // 0101000xx
+ [8, 49], [8, 49], // 01010010x
+ [8, 50], [8, 50], // 01010011x
+ [8, 51], [8, 51], // 01010100x
+ [8, 52], [8, 52], // 01010101x
+ [7, 25], [7, 25], [7, 25], [7, 25], // 0101011xx
+ [8, 55], [8, 55], // 01011000x
+ [8, 56], [8, 56], // 01011001x
+ [8, 57], [8, 57], // 01011010x
+ [8, 58], [8, 58], // 01011011x
+ [6, 192], [6, 192], [6, 192], [6, 192], // 010111xxx
+ [6, 192], [6, 192], [6, 192], [6, 192],
+ [6, 1664], [6, 1664], [6, 1664], [6, 1664], // 011000xxx
+ [6, 1664], [6, 1664], [6, 1664], [6, 1664],
+ [8, 448], [8, 448], // 01100100x
+ [8, 512], [8, 512], // 01100101x
+ [9, 704], // 011001100
+ [9, 768], // 011001101
+ [8, 640], [8, 640], // 01100111x
+ [8, 576], [8, 576], // 01101000x
+ [9, 832], // 011010010
+ [9, 896], // 011010011
+ [9, 960], // 011010100
+ [9, 1024], // 011010101
+ [9, 1088], // 011010110
+ [9, 1152], // 011010111
+ [9, 1216], // 011011000
+ [9, 1280], // 011011001
+ [9, 1344], // 011011010
+ [9, 1408], // 011011011
+ [7, 256], [7, 256], [7, 256], [7, 256], // 0110111xx
+ [4, 2], [4, 2], [4, 2], [4, 2], // 0111xxxxx
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 2], [4, 2], [4, 2], [4, 2],
+ [4, 3], [4, 3], [4, 3], [4, 3], // 1000xxxxx
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [4, 3], [4, 3], [4, 3], [4, 3],
+ [5, 128], [5, 128], [5, 128], [5, 128], // 10010xxxx
+ [5, 128], [5, 128], [5, 128], [5, 128],
+ [5, 128], [5, 128], [5, 128], [5, 128],
+ [5, 128], [5, 128], [5, 128], [5, 128],
+ [5, 8], [5, 8], [5, 8], [5, 8], // 10011xxxx
+ [5, 8], [5, 8], [5, 8], [5, 8],
+ [5, 8], [5, 8], [5, 8], [5, 8],
+ [5, 8], [5, 8], [5, 8], [5, 8],
+ [5, 9], [5, 9], [5, 9], [5, 9], // 10100xxxx
+ [5, 9], [5, 9], [5, 9], [5, 9],
+ [5, 9], [5, 9], [5, 9], [5, 9],
+ [5, 9], [5, 9], [5, 9], [5, 9],
+ [6, 16], [6, 16], [6, 16], [6, 16], // 101010xxx
+ [6, 16], [6, 16], [6, 16], [6, 16],
+ [6, 17], [6, 17], [6, 17], [6, 17], // 101011xxx
+ [6, 17], [6, 17], [6, 17], [6, 17],
+ [4, 4], [4, 4], [4, 4], [4, 4], // 1011xxxxx
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 4], [4, 4], [4, 4], [4, 4],
+ [4, 5], [4, 5], [4, 5], [4, 5], // 1100xxxxx
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [4, 5], [4, 5], [4, 5], [4, 5],
+ [6, 14], [6, 14], [6, 14], [6, 14], // 110100xxx
+ [6, 14], [6, 14], [6, 14], [6, 14],
+ [6, 15], [6, 15], [6, 15], [6, 15], // 110101xxx
+ [6, 15], [6, 15], [6, 15], [6, 15],
+ [5, 64], [5, 64], [5, 64], [5, 64], // 11011xxxx
+ [5, 64], [5, 64], [5, 64], [5, 64],
+ [5, 64], [5, 64], [5, 64], [5, 64],
+ [5, 64], [5, 64], [5, 64], [5, 64],
+ [4, 6], [4, 6], [4, 6], [4, 6], // 1110xxxxx
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 6], [4, 6], [4, 6], [4, 6],
+ [4, 7], [4, 7], [4, 7], [4, 7], // 1111xxxxx
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7],
+ [4, 7], [4, 7], [4, 7], [4, 7]
+ ];
+
+ var blackTable1 = [
+ [-1, -1], [-1, -1], // 000000000000x
+ [12, ccittEOL], [12, ccittEOL], // 000000000001x
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000010xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000011xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000100xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000101xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000110xx
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000111xx
+ [11, 1792], [11, 1792], [11, 1792], [11, 1792], // 00000001000xx
+ [12, 1984], [12, 1984], // 000000010010x
+ [12, 2048], [12, 2048], // 000000010011x
+ [12, 2112], [12, 2112], // 000000010100x
+ [12, 2176], [12, 2176], // 000000010101x
+ [12, 2240], [12, 2240], // 000000010110x
+ [12, 2304], [12, 2304], // 000000010111x
+ [11, 1856], [11, 1856], [11, 1856], [11, 1856], // 00000001100xx
+ [11, 1920], [11, 1920], [11, 1920], [11, 1920], // 00000001101xx
+ [12, 2368], [12, 2368], // 000000011100x
+ [12, 2432], [12, 2432], // 000000011101x
+ [12, 2496], [12, 2496], // 000000011110x
+ [12, 2560], [12, 2560], // 000000011111x
+ [10, 18], [10, 18], [10, 18], [10, 18], // 0000001000xxx
+ [10, 18], [10, 18], [10, 18], [10, 18],
+ [12, 52], [12, 52], // 000000100100x
+ [13, 640], // 0000001001010
+ [13, 704], // 0000001001011
+ [13, 768], // 0000001001100
+ [13, 832], // 0000001001101
+ [12, 55], [12, 55], // 000000100111x
+ [12, 56], [12, 56], // 000000101000x
+ [13, 1280], // 0000001010010
+ [13, 1344], // 0000001010011
+ [13, 1408], // 0000001010100
+ [13, 1472], // 0000001010101
+ [12, 59], [12, 59], // 000000101011x
+ [12, 60], [12, 60], // 000000101100x
+ [13, 1536], // 0000001011010
+ [13, 1600], // 0000001011011
+ [11, 24], [11, 24], [11, 24], [11, 24], // 00000010111xx
+ [11, 25], [11, 25], [11, 25], [11, 25], // 00000011000xx
+ [13, 1664], // 0000001100100
+ [13, 1728], // 0000001100101
+ [12, 320], [12, 320], // 000000110011x
+ [12, 384], [12, 384], // 000000110100x
+ [12, 448], [12, 448], // 000000110101x
+ [13, 512], // 0000001101100
+ [13, 576], // 0000001101101
+ [12, 53], [12, 53], // 000000110111x
+ [12, 54], [12, 54], // 000000111000x
+ [13, 896], // 0000001110010
+ [13, 960], // 0000001110011
+ [13, 1024], // 0000001110100
+ [13, 1088], // 0000001110101
+ [13, 1152], // 0000001110110
+ [13, 1216], // 0000001110111
+ [10, 64], [10, 64], [10, 64], [10, 64], // 0000001111xxx
+ [10, 64], [10, 64], [10, 64], [10, 64]
+ ];
+
+ var blackTable2 = [
+ [8, 13], [8, 13], [8, 13], [8, 13], // 00000100xxxx
+ [8, 13], [8, 13], [8, 13], [8, 13],
+ [8, 13], [8, 13], [8, 13], [8, 13],
+ [8, 13], [8, 13], [8, 13], [8, 13],
+ [11, 23], [11, 23], // 00000101000x
+ [12, 50], // 000001010010
+ [12, 51], // 000001010011
+ [12, 44], // 000001010100
+ [12, 45], // 000001010101
+ [12, 46], // 000001010110
+ [12, 47], // 000001010111
+ [12, 57], // 000001011000
+ [12, 58], // 000001011001
+ [12, 61], // 000001011010
+ [12, 256], // 000001011011
+ [10, 16], [10, 16], [10, 16], [10, 16], // 0000010111xx
+ [10, 17], [10, 17], [10, 17], [10, 17], // 0000011000xx
+ [12, 48], // 000001100100
+ [12, 49], // 000001100101
+ [12, 62], // 000001100110
+ [12, 63], // 000001100111
+ [12, 30], // 000001101000
+ [12, 31], // 000001101001
+ [12, 32], // 000001101010
+ [12, 33], // 000001101011
+ [12, 40], // 000001101100
+ [12, 41], // 000001101101
+ [11, 22], [11, 22], // 00000110111x
+ [8, 14], [8, 14], [8, 14], [8, 14], // 00000111xxxx
+ [8, 14], [8, 14], [8, 14], [8, 14],
+ [8, 14], [8, 14], [8, 14], [8, 14],
+ [8, 14], [8, 14], [8, 14], [8, 14],
+ [7, 10], [7, 10], [7, 10], [7, 10], // 0000100xxxxx
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 10], [7, 10], [7, 10], [7, 10],
+ [7, 11], [7, 11], [7, 11], [7, 11], // 0000101xxxxx
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [7, 11], [7, 11], [7, 11], [7, 11],
+ [9, 15], [9, 15], [9, 15], [9, 15], // 000011000xxx
+ [9, 15], [9, 15], [9, 15], [9, 15],
+ [12, 128], // 000011001000
+ [12, 192], // 000011001001
+ [12, 26], // 000011001010
+ [12, 27], // 000011001011
+ [12, 28], // 000011001100
+ [12, 29], // 000011001101
+ [11, 19], [11, 19], // 00001100111x
+ [11, 20], [11, 20], // 00001101000x
+ [12, 34], // 000011010010
+ [12, 35], // 000011010011
+ [12, 36], // 000011010100
+ [12, 37], // 000011010101
+ [12, 38], // 000011010110
+ [12, 39], // 000011010111
+ [11, 21], [11, 21], // 00001101100x
+ [12, 42], // 000011011010
+ [12, 43], // 000011011011
+ [10, 0], [10, 0], [10, 0], [10, 0], // 0000110111xx
+ [7, 12], [7, 12], [7, 12], [7, 12], // 0000111xxxxx
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12],
+ [7, 12], [7, 12], [7, 12], [7, 12]
+ ];
+
+ var blackTable3 = [
+ [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx
+ [6, 9], // 000100
+ [6, 8], // 000101
+ [5, 7], [5, 7], // 00011x
+ [4, 6], [4, 6], [4, 6], [4, 6], // 0010xx
+ [4, 5], [4, 5], [4, 5], [4, 5], // 0011xx
+ [3, 1], [3, 1], [3, 1], [3, 1], // 010xxx
+ [3, 1], [3, 1], [3, 1], [3, 1],
+ [3, 4], [3, 4], [3, 4], [3, 4], // 011xxx
+ [3, 4], [3, 4], [3, 4], [3, 4],
+ [2, 3], [2, 3], [2, 3], [2, 3], // 10xxxx
+ [2, 3], [2, 3], [2, 3], [2, 3],
+ [2, 3], [2, 3], [2, 3], [2, 3],
+ [2, 3], [2, 3], [2, 3], [2, 3],
+ [2, 2], [2, 2], [2, 2], [2, 2], // 11xxxx
+ [2, 2], [2, 2], [2, 2], [2, 2],
+ [2, 2], [2, 2], [2, 2], [2, 2],
+ [2, 2], [2, 2], [2, 2], [2, 2]
+ ];
+
+ function CCITTFaxStream(str, params) {
+ this.str = str;
+ this.dict = str.dict;
+
+ params = params || new Dict();
+
+ this.encoding = params.get('K') || 0;
+ this.eoline = params.get('EndOfLine') || false;
+ this.byteAlign = params.get('EncodedByteAlign') || false;
+ this.columns = params.get('Columns') || 1728;
+ this.rows = params.get('Rows') || 0;
+ var eoblock = params.get('EndOfBlock');
+ if (eoblock == null)
+ eoblock = true;
+ this.eoblock = eoblock;
+ this.black = params.get('BlackIs1') || false;
+
+ this.codingLine = new Uint32Array(this.columns + 1);
+ this.refLine = new Uint32Array(this.columns + 2);
+
+ this.codingLine[0] = this.columns;
+ this.codingPos = 0;
+
+ this.row = 0;
+ this.nextLine2D = this.encoding < 0;
+ this.inputBits = 0;
+ this.inputBuf = 0;
+ this.outputBits = 0;
+ this.buf = EOF;
+
+ var code1;
+ while ((code1 = this.lookBits(12)) == 0) {
+ this.eatBits(1);
+ }
+ if (code1 == 1) {
+ this.eatBits(12);
+ }
+ if (this.encoding > 0) {
+ this.nextLine2D = !this.lookBits(1);
+ this.eatBits(1);
+ }
+
+ DecodeStream.call(this);
+ }
+
+ CCITTFaxStream.prototype = Object.create(DecodeStream.prototype);
+
+ CCITTFaxStream.prototype.readBlock = function CCITTFaxStream_readBlock() {
+ while (!this.eof) {
+ var c = this.lookChar();
+ this.buf = EOF;
+ this.ensureBuffer(this.bufferLength + 1);
+ this.buffer[this.bufferLength++] = c;
+ }
+ };
+
+ CCITTFaxStream.prototype.addPixels =
+ function ccittFaxStreamAddPixels(a1, blackPixels) {
+ var codingLine = this.codingLine;
+ var codingPos = this.codingPos;
+
+ if (a1 > codingLine[codingPos]) {
+ if (a1 > this.columns) {
+ warn('row is wrong length');
+ this.err = true;
+ a1 = this.columns;
+ }
+ if ((codingPos & 1) ^ blackPixels) {
+ ++codingPos;
+ }
+
+ codingLine[codingPos] = a1;
+ }
+ this.codingPos = codingPos;
+ };
+
+ CCITTFaxStream.prototype.addPixelsNeg =
+ function ccittFaxStreamAddPixelsNeg(a1, blackPixels) {
+ var codingLine = this.codingLine;
+ var codingPos = this.codingPos;
+
+ if (a1 > codingLine[codingPos]) {
+ if (a1 > this.columns) {
+ warn('row is wrong length');
+ this.err = true;
+ a1 = this.columns;
+ }
+ if ((codingPos & 1) ^ blackPixels)
+ ++codingPos;
+
+ codingLine[codingPos] = a1;
+ } else if (a1 < codingLine[codingPos]) {
+ if (a1 < 0) {
+ warn('invalid code');
+ this.err = true;
+ a1 = 0;
+ }
+ while (codingPos > 0 && a1 < codingLine[codingPos - 1])
+ --codingPos;
+ codingLine[codingPos] = a1;
+ }
+
+ this.codingPos = codingPos;
+ };
+
+ CCITTFaxStream.prototype.lookChar = function CCITTFaxStream_lookChar() {
+ if (this.buf != EOF)
+ return this.buf;
+
+ var refLine = this.refLine;
+ var codingLine = this.codingLine;
+ var columns = this.columns;
+
+ var refPos, blackPixels, bits;
+
+ if (this.outputBits == 0) {
+ if (this.eof)
+ return null;
+
+ this.err = false;
+
+ var code1, code2, code3;
+ if (this.nextLine2D) {
+ for (var i = 0; codingLine[i] < columns; ++i)
+ refLine[i] = codingLine[i];
+
+ refLine[i++] = columns;
+ refLine[i] = columns;
+ codingLine[0] = 0;
+ this.codingPos = 0;
+ refPos = 0;
+ blackPixels = 0;
+
+ while (codingLine[this.codingPos] < columns) {
+ code1 = this.getTwoDimCode();
+ switch (code1) {
+ case twoDimPass:
+ this.addPixels(refLine[refPos + 1], blackPixels);
+ if (refLine[refPos + 1] < columns)
+ refPos += 2;
+ break;
+ case twoDimHoriz:
+ code1 = code2 = 0;
+ if (blackPixels) {
+ do {
+ code1 += (code3 = this.getBlackCode());
+ } while (code3 >= 64);
+ do {
+ code2 += (code3 = this.getWhiteCode());
+ } while (code3 >= 64);
+ } else {
+ do {
+ code1 += (code3 = this.getWhiteCode());
+ } while (code3 >= 64);
+ do {
+ code2 += (code3 = this.getBlackCode());
+ } while (code3 >= 64);
+ }
+ this.addPixels(codingLine[this.codingPos] +
+ code1, blackPixels);
+ if (codingLine[this.codingPos] < columns) {
+ this.addPixels(codingLine[this.codingPos] + code2,
+ blackPixels ^ 1);
+ }
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ break;
+ case twoDimVertR3:
+ this.addPixels(refLine[refPos] + 3, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case twoDimVertR2:
+ this.addPixels(refLine[refPos] + 2, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertR1:
+ this.addPixels(refLine[refPos] + 1, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case twoDimVert0:
+ this.addPixels(refLine[refPos], blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case twoDimVertL3:
+ this.addPixelsNeg(refLine[refPos] - 3, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0)
+ --refPos;
+ else
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case twoDimVertL2:
+ this.addPixelsNeg(refLine[refPos] - 2, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0)
+ --refPos;
+ else
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case twoDimVertL1:
+ this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0)
+ --refPos;
+ else
+ ++refPos;
+
+ while (refLine[refPos] <= codingLine[this.codingPos] &&
+ refLine[refPos] < columns)
+ refPos += 2;
+ }
+ break;
+ case EOF:
+ this.addPixels(columns, 0);
+ this.eof = true;
+ break;
+ default:
+ warn('bad 2d code');
+ this.addPixels(columns, 0);
+ this.err = true;
+ }
+ }
+ } else {
+ codingLine[0] = 0;
+ this.codingPos = 0;
+ blackPixels = 0;
+ while (codingLine[this.codingPos] < columns) {
+ code1 = 0;
+ if (blackPixels) {
+ do {
+ code1 += (code3 = this.getBlackCode());
+ } while (code3 >= 64);
+ } else {
+ do {
+ code1 += (code3 = this.getWhiteCode());
+ } while (code3 >= 64);
+ }
+ this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
+ blackPixels ^= 1;
+ }
+ }
+
+ if (this.byteAlign)
+ this.inputBits &= ~7;
+
+ var gotEOL = false;
+
+ if (!this.eoblock && this.row == this.rows - 1) {
+ this.eof = true;
+ } else {
+ code1 = this.lookBits(12);
+ while (code1 == 0) {
+ this.eatBits(1);
+ code1 = this.lookBits(12);
+ }
+ if (code1 == 1) {
+ this.eatBits(12);
+ gotEOL = true;
+ } else if (code1 == EOF) {
+ this.eof = true;
+ }
+ }
+
+ if (!this.eof && this.encoding > 0) {
+ this.nextLine2D = !this.lookBits(1);
+ this.eatBits(1);
+ }
+
+ if (this.eoblock && gotEOL) {
+ code1 = this.lookBits(12);
+ if (code1 == 1) {
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.lookBits(1);
+ this.eatBits(1);
+ }
+ if (this.encoding >= 0) {
+ for (var i = 0; i < 4; ++i) {
+ code1 = this.lookBits(12);
+ if (code1 != 1)
+ warn('bad rtc code: ' + code1);
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.lookBits(1);
+ this.eatBits(1);
+ }
+ }
+ }
+ this.eof = true;
+ }
+ } else if (this.err && this.eoline) {
+ while (true) {
+ code1 = this.lookBits(13);
+ if (code1 == EOF) {
+ this.eof = true;
+ return null;
+ }
+ if ((code1 >> 1) == 1) {
+ break;
+ }
+ this.eatBits(1);
+ }
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.eatBits(1);
+ this.nextLine2D = !(code1 & 1);
+ }
+ }
+
+ if (codingLine[0] > 0)
+ this.outputBits = codingLine[this.codingPos = 0];
+ else
+ this.outputBits = codingLine[this.codingPos = 1];
+ this.row++;
+ }
+
+ if (this.outputBits >= 8) {
+ this.buf = (this.codingPos & 1) ? 0 : 0xFF;
+ this.outputBits -= 8;
+ if (this.outputBits == 0 && codingLine[this.codingPos] < columns) {
+ this.codingPos++;
+ this.outputBits = (codingLine[this.codingPos] -
+ codingLine[this.codingPos - 1]);
+ }
+ } else {
+ var bits = 8;
+ this.buf = 0;
+ do {
+ if (this.outputBits > bits) {
+ this.buf <<= bits;
+ if (!(this.codingPos & 1)) {
+ this.buf |= 0xFF >> (8 - bits);
+ }
+ this.outputBits -= bits;
+ bits = 0;
+ } else {
+ this.buf <<= this.outputBits;
+ if (!(this.codingPos & 1)) {
+ this.buf |= 0xFF >> (8 - this.outputBits);
+ }
+ bits -= this.outputBits;
+ this.outputBits = 0;
+ if (codingLine[this.codingPos] < columns) {
+ this.codingPos++;
+ this.outputBits = (codingLine[this.codingPos] -
+ codingLine[this.codingPos - 1]);
+ } else if (bits > 0) {
+ this.buf <<= bits;
+ bits = 0;
+ }
+ }
+ } while (bits);
+ }
+ if (this.black) {
+ this.buf ^= 0xFF;
+ }
+ return this.buf;
+ };
+
+ // This functions returns the code found from the table.
+ // The start and end parameters set the boundaries for searching the table.
+ // The limit parameter is optional. Function returns an array with three
+ // values. The first array element indicates whether a valid code is being
+ // returned. The second array element is the actual code. The third array
+ // element indicates whether EOF was reached.
+ CCITTFaxStream.prototype.findTableCode =
+ function ccittFaxStreamFindTableCode(start, end, table, limit) {
+
+ var limitValue = limit || 0;
+ for (var i = start; i <= end; ++i) {
+ var code = this.lookBits(i);
+ if (code == EOF)
+ return [true, 1, false];
+ if (i < end)
+ code <<= end - i;
+ if (!limitValue || code >= limitValue) {
+ var p = table[code - limitValue];
+ if (p[0] == i) {
+ this.eatBits(i);
+ return [true, p[1], true];
+ }
+ }
+ }
+ return [false, 0, false];
+ };
+
+ CCITTFaxStream.prototype.getTwoDimCode =
+ function ccittFaxStreamGetTwoDimCode() {
+
+ var code = 0;
+ var p;
+ if (this.eoblock) {
+ code = this.lookBits(7);
+ p = twoDimTable[code];
+ if (p && p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(1, 7, twoDimTable);
+ if (result[0] && result[2])
+ return result[1];
+ }
+ warn('Bad two dim code');
+ return EOF;
+ };
+
+ CCITTFaxStream.prototype.getWhiteCode =
+ function ccittFaxStreamGetWhiteCode() {
+
+ var code = 0;
+ var p;
+ var n;
+ if (this.eoblock) {
+ code = this.lookBits(12);
+ if (code == EOF)
+ return 1;
+
+ if ((code >> 5) == 0)
+ p = whiteTable1[code];
+ else
+ p = whiteTable2[code >> 3];
+
+ if (p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(1, 9, whiteTable2);
+ if (result[0])
+ return result[1];
+
+ result = this.findTableCode(11, 12, whiteTable1);
+ if (result[0])
+ return result[1];
+ }
+ warn('bad white code');
+ this.eatBits(1);
+ return 1;
+ };
+
+ CCITTFaxStream.prototype.getBlackCode =
+ function ccittFaxStreamGetBlackCode() {
+
+ var code, p;
+ if (this.eoblock) {
+ code = this.lookBits(13);
+ if (code == EOF)
+ return 1;
+ if ((code >> 7) == 0)
+ p = blackTable1[code];
+ else if ((code >> 9) == 0 && (code >> 7) != 0)
+ p = blackTable2[(code >> 1) - 64];
+ else
+ p = blackTable3[code >> 7];
+
+ if (p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(2, 6, blackTable3);
+ if (result[0])
+ return result[1];
+
+ result = this.findTableCode(7, 12, blackTable2, 64);
+ if (result[0])
+ return result[1];
+
+ result = this.findTableCode(10, 13, blackTable1);
+ if (result[0])
+ return result[1];
+ }
+ warn('bad black code');
+ this.eatBits(1);
+ return 1;
+ };
+
+ CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) {
+ var c;
+ while (this.inputBits < n) {
+ if ((c = this.str.getByte()) == null) {
+ if (this.inputBits == 0)
+ return EOF;
+ return ((this.inputBuf << (n - this.inputBits)) &
+ (0xFFFF >> (16 - n)));
+ }
+ this.inputBuf = (this.inputBuf << 8) + c;
+ this.inputBits += 8;
+ }
+ return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n));
+ };
+
+ CCITTFaxStream.prototype.eatBits = function CCITTFaxStream_eatBits(n) {
+ if ((this.inputBits -= n) < 0)
+ this.inputBits = 0;
+ };
+
+ return CCITTFaxStream;
+})();
+
+var LZWStream = (function LZWStreamClosure() {
+ function LZWStream(str, earlyChange) {
+ this.str = str;
+ this.dict = str.dict;
+ this.cachedData = 0;
+ this.bitsCached = 0;
+
+ var maxLzwDictionarySize = 4096;
+ var lzwState = {
+ earlyChange: earlyChange,
+ codeLength: 9,
+ nextCode: 258,
+ dictionaryValues: new Uint8Array(maxLzwDictionarySize),
+ dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
+ dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
+ currentSequence: new Uint8Array(maxLzwDictionarySize),
+ currentSequenceLength: 0
+ };
+ for (var i = 0; i < 256; ++i) {
+ lzwState.dictionaryValues[i] = i;
+ lzwState.dictionaryLengths[i] = 1;
+ }
+ this.lzwState = lzwState;
+
+ DecodeStream.call(this);
+ }
+
+ LZWStream.prototype = Object.create(DecodeStream.prototype);
+
+ LZWStream.prototype.readBits = function LZWStream_readBits(n) {
+ var bitsCached = this.bitsCached;
+ var cachedData = this.cachedData;
+ while (bitsCached < n) {
+ var c = this.str.getByte();
+ if (c == null) {
+ this.eof = true;
+ return null;
+ }
+ cachedData = (cachedData << 8) | c;
+ bitsCached += 8;
+ }
+ this.bitsCached = (bitsCached -= n);
+ this.cachedData = cachedData;
+ this.lastCode = null;
+ return (cachedData >>> bitsCached) & ((1 << n) - 1);
+ };
+
+ LZWStream.prototype.readBlock = function LZWStream_readBlock() {
+ var blockSize = 512;
+ var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize;
+ var i, j, q;
+
+ var lzwState = this.lzwState;
+ if (!lzwState)
+ return; // eof was found
+
+ var earlyChange = lzwState.earlyChange;
+ var nextCode = lzwState.nextCode;
+ var dictionaryValues = lzwState.dictionaryValues;
+ var dictionaryLengths = lzwState.dictionaryLengths;
+ var dictionaryPrevCodes = lzwState.dictionaryPrevCodes;
+ var codeLength = lzwState.codeLength;
+ var prevCode = lzwState.prevCode;
+ var currentSequence = lzwState.currentSequence;
+ var currentSequenceLength = lzwState.currentSequenceLength;
+
+ var decodedLength = 0;
+ var currentBufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
+
+ for (i = 0; i < blockSize; i++) {
+ var code = this.readBits(codeLength);
+ var hasPrev = currentSequenceLength > 0;
+ if (code < 256) {
+ currentSequence[0] = code;
+ currentSequenceLength = 1;
+ } else if (code >= 258) {
+ if (code < nextCode) {
+ currentSequenceLength = dictionaryLengths[code];
+ for (j = currentSequenceLength - 1, q = code; j >= 0; j--) {
+ currentSequence[j] = dictionaryValues[q];
+ q = dictionaryPrevCodes[q];
+ }
+ } else {
+ currentSequence[currentSequenceLength++] = currentSequence[0];
+ }
+ } else if (code == 256) {
+ codeLength = 9;
+ nextCode = 258;
+ currentSequenceLength = 0;
+ continue;
+ } else {
+ this.eof = true;
+ delete this.lzwState;
+ break;
+ }
+
+ if (hasPrev) {
+ dictionaryPrevCodes[nextCode] = prevCode;
+ dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
+ dictionaryValues[nextCode] = currentSequence[0];
+ nextCode++;
+ codeLength = (nextCode + earlyChange) & (nextCode + earlyChange - 1) ?
+ codeLength : Math.min(Math.log(nextCode + earlyChange) /
+ 0.6931471805599453 + 1, 12) | 0;
+ }
+ prevCode = code;
+
+ decodedLength += currentSequenceLength;
+ if (estimatedDecodedSize < decodedLength) {
+ do {
+ estimatedDecodedSize += decodedSizeDelta;
+ } while (estimatedDecodedSize < decodedLength);
+ buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
+ }
+ for (j = 0; j < currentSequenceLength; j++)
+ buffer[currentBufferLength++] = currentSequence[j];
+ }
+ lzwState.nextCode = nextCode;
+ lzwState.codeLength = codeLength;
+ lzwState.prevCode = prevCode;
+ lzwState.currentSequenceLength = currentSequenceLength;
+
+ this.bufferLength = currentBufferLength;
+ };
+
+ return LZWStream;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+function MessageHandler(name, comObj) {
+ this.name = name;
+ this.comObj = comObj;
+ this.callbackIndex = 1;
+ var callbacks = this.callbacks = {};
+ var ah = this.actionHandler = {};
+
+ ah['console_log'] = [function ahConsoleLog(data) {
+ console.log.apply(console, data);
+ }];
+ ah['console_error'] = [function ahConsoleError(data) {
+ console.error.apply(console, data);
+ }];
+
+ comObj.onmessage = function messageHandlerComObjOnMessage(event) {
+ var data = event.data;
+ if (data.isReply) {
+ var callbackId = data.callbackId;
+ if (data.callbackId in callbacks) {
+ var callback = callbacks[callbackId];
+ delete callbacks[callbackId];
+ callback(data.data);
+ } else {
+ error('Cannot resolve callback ' + callbackId);
+ }
+ } else if (data.action in ah) {
+ var action = ah[data.action];
+ if (data.callbackId) {
+ var promise = new Promise();
+ promise.then(function(resolvedData) {
+ comObj.postMessage({
+ isReply: true,
+ callbackId: data.callbackId,
+ data: resolvedData
+ });
+ });
+ action[0].call(action[1], data.data, promise);
+ } else {
+ action[0].call(action[1], data.data);
+ }
+ } else {
+ error('Unkown action from worker: ' + data.action);
+ }
+ };
+}
+
+MessageHandler.prototype = {
+ on: function messageHandlerOn(actionName, handler, scope) {
+ var ah = this.actionHandler;
+ if (ah[actionName]) {
+ error('There is already an actionName called "' + actionName + '"');
+ }
+ ah[actionName] = [handler, scope];
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {function} [callback] Optional callback that will handle a reply.
+ */
+ send: function messageHandlerSend(actionName, data, callback) {
+ var message = {
+ action: actionName,
+ data: data
+ };
+ if (callback) {
+ var callbackId = this.callbackIndex++;
+ this.callbacks[callbackId] = callback;
+ message.callbackId = callbackId;
+ }
+ this.comObj.postMessage(message);
+ }
+};
+
+var WorkerMessageHandler = {
+ setup: function wphSetup(handler) {
+ var pdfModel = null;
+
+ handler.on('test', function wphSetupTest(data) {
+ handler.send('test', data instanceof Uint8Array);
+ });
+
+ handler.on('GetDocRequest', function wphSetupDoc(data) {
+ // Create only the model of the PDFDoc, which is enough for
+ // processing the content of the pdf.
+ pdfModel = new PDFDocument(new Stream(data));
+ var doc = {
+ numPages: pdfModel.numPages,
+ fingerprint: pdfModel.getFingerprint(),
+ destinations: pdfModel.catalog.destinations,
+ outline: pdfModel.catalog.documentOutline,
+ info: pdfModel.getDocumentInfo(),
+ metadata: pdfModel.catalog.metadata
+ };
+ handler.send('GetDoc', {pdfInfo: doc});
+ });
+
+ handler.on('GetPageRequest', function wphSetupGetPage(data) {
+ var pageNumber = data.pageIndex + 1;
+ var pdfPage = pdfModel.getPage(pageNumber);
+ var page = {
+ pageIndex: data.pageIndex,
+ rotate: pdfPage.rotate,
+ ref: pdfPage.ref,
+ view: pdfPage.view
+ };
+ handler.send('GetPage', {pageInfo: page});
+ });
+
+ handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
+ var pdfPage = pdfModel.getPage(data.pageIndex + 1);
+ handler.send('GetAnnotations', {
+ pageIndex: data.pageIndex,
+ annotations: pdfPage.getAnnotations()
+ });
+ });
+
+ handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
+ var pageNum = data.pageIndex + 1;
+
+
+ // The following code does quite the same as
+ // Page.prototype.startRendering, but stops at one point and sends the
+ // result back to the main thread.
+ var gfx = new CanvasGraphics(null);
+
+ var start = Date.now();
+
+ var dependency = [];
+ var operatorList = null;
+ try {
+ var page = pdfModel.getPage(pageNum);
+ // Pre compile the pdf page and fetch the fonts/images.
+ operatorList = page.getOperatorList(handler, dependency);
+ } catch (e) {
+ var minimumStackMessage =
+ 'worker.js: while trying to getPage() and getOperatorList()';
+
+ // Turn the error into an obj that can be serialized
+ if (typeof e === 'string') {
+ e = {
+ message: e,
+ stack: minimumStackMessage
+ };
+ } else if (typeof e === 'object') {
+ e = {
+ message: e.message || e.toString(),
+ stack: e.stack || minimumStackMessage
+ };
+ } else {
+ e = {
+ message: 'Unknown exception type: ' + (typeof e),
+ stack: minimumStackMessage
+ };
+ }
+
+ handler.send('PageError', {
+ pageNum: pageNum,
+ error: e
+ });
+ return;
+ }
+
+ // Filter the dependecies for fonts.
+ var fonts = {};
+ for (var i = 0, ii = dependency.length; i < ii; i++) {
+ var dep = dependency[i];
+ if (dep.indexOf('font_') == 0) {
+ fonts[dep] = true;
+ }
+ }
+ handler.send('RenderPage', {
+ pageIndex: data.pageIndex,
+ operatorList: operatorList,
+ depFonts: Object.keys(fonts)
+ });
+ }, this);
+ }
+};
+
+var consoleTimer = {};
+
+var workerConsole = {
+ log: function log() {
+ var args = Array.prototype.slice.call(arguments);
+ postMessage({
+ action: 'console_log',
+ data: args
+ });
+ },
+
+ error: function error() {
+ var args = Array.prototype.slice.call(arguments);
+ postMessage({
+ action: 'console_error',
+ data: args
+ });
+ throw 'pdf.js execution error';
+ },
+
+ time: function time(name) {
+ consoleTimer[name] = Date.now();
+ },
+
+ timeEnd: function timeEnd(name) {
+ var time = consoleTimer[name];
+ if (time == null) {
+ error('Unkown timer name ' + name);
+ }
+ this.log('Timer:', name, Date.now() - time);
+ }
+};
+
+// Worker thread?
+if (typeof window === 'undefined') {
+ globalScope.console = workerConsole;
+
+ var handler = new MessageHandler('worker_processor', this);
+ WorkerMessageHandler.setup(handler);
+}
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
+// (www.w3.org/Graphics/JPEG/itu-t81.pdf)
+// - The JFIF specification can be found in the JPEG File Interchange Format
+// (www.w3.org/Graphics/JPEG/jfif3.pdf)
+// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
+// in PostScript Level 2, Technical Note #5116
+// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
+
+var JpegImage = (function jpegImage() {
+ "use strict";
+ var dctZigZag = new Int32Array([
+ 0,
+ 1, 8,
+ 16, 9, 2,
+ 3, 10, 17, 24,
+ 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40,
+ 48, 41, 34, 27, 20, 13, 6,
+ 7, 14, 21, 28, 35, 42, 49, 56,
+ 57, 50, 43, 36, 29, 22, 15,
+ 23, 30, 37, 44, 51, 58,
+ 59, 52, 45, 38, 31,
+ 39, 46, 53, 60,
+ 61, 54, 47,
+ 55, 62,
+ 63
+ ]);
+
+ var dctCos1 = 4017 // cos(pi/16)
+ var dctSin1 = 799 // sin(pi/16)
+ var dctCos3 = 3406 // cos(3*pi/16)
+ var dctSin3 = 2276 // sin(3*pi/16)
+ var dctCos6 = 1567 // cos(6*pi/16)
+ var dctSin6 = 3784 // sin(6*pi/16)
+ var dctSqrt2 = 5793 // sqrt(2)
+ var dctSqrt1d2 = 2896 // sqrt(2) / 2
+
+ function constructor() {
+ }
+
+ function buildHuffmanTable(codeLengths, values) {
+ var k = 0, code = [], i, j, length = 16;
+ while (length > 0 && !codeLengths[length - 1])
+ length--;
+ code.push({children: [], index: 0});
+ var p = code[0], q;
+ for (i = 0; i < length; i++) {
+ for (j = 0; j < codeLengths[i]; j++) {
+ p = code.pop();
+ p.children[p.index] = values[k];
+ while (p.index > 0) {
+ p = code.pop();
+ }
+ p.index++;
+ code.push(p);
+ while (code.length <= i) {
+ code.push(q = {children: [], index: 0});
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ k++;
+ }
+ if (i + 1 < length) {
+ // p here points to last code
+ code.push(q = {children: [], index: 0});
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ }
+ return code[0].children;
+ }
+
+ function decodeScan(data, offset,
+ frame, components, resetInterval,
+ spectralStart, spectralEnd,
+ successivePrev, successive) {
+ var precision = frame.precision;
+ var samplesPerLine = frame.samplesPerLine;
+ var scanLines = frame.scanLines;
+ var mcusPerLine = frame.mcusPerLine;
+ var progressive = frame.progressive;
+ var maxH = frame.maxH, maxV = frame.maxV;
+
+ var startOffset = offset, bitsData = 0, bitsCount = 0;
+ function readBit() {
+ if (bitsCount > 0) {
+ bitsCount--;
+ return (bitsData >> bitsCount) & 1;
+ }
+ bitsData = data[offset++];
+ if (bitsData == 0xFF) {
+ var nextByte = data[offset++];
+ if (nextByte) {
+ throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16);
+ }
+ // unstuff 0
+ }
+ bitsCount = 7;
+ return bitsData >>> 7;
+ }
+ function decodeHuffman(tree) {
+ var node = tree, bit;
+ while ((bit = readBit()) !== null) {
+ node = node[bit];
+ if (typeof node === 'number')
+ return node;
+ if (typeof node !== 'object')
+ throw "invalid huffman sequence";
+ }
+ return null;
+ }
+ function receive(length) {
+ var n = 0;
+ while (length > 0) {
+ var bit = readBit();
+ if (bit === null) return;
+ n = (n << 1) | bit;
+ length--;
+ }
+ return n;
+ }
+ function receiveAndExtend(length) {
+ var n = receive(length);
+ if (n >= 1 << (length - 1))
+ return n;
+ return n + (-1 << length) + 1;
+ }
+ function decodeBaseline(component, zz) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : receiveAndExtend(t);
+ zz[0]= (component.pred += diff);
+ var k = 1;
+ while (k < 64) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15)
+ break;
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ zz[z] = receiveAndExtend(s);
+ k++;
+ }
+ }
+ function decodeDCFirst(component, zz) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
+ zz[0] = (component.pred += diff);
+ }
+ function decodeDCSuccessive(component, zz) {
+ zz[0] |= readBit() << successive;
+ }
+ var eobrun = 0;
+ function decodeACFirst(component, zz) {
+ if (eobrun > 0) {
+ eobrun--;
+ return;
+ }
+ var k = spectralStart, e = spectralEnd;
+ while (k <= e) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r) - 1;
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ zz[z] = receiveAndExtend(s) * (1 << successive);
+ k++;
+ }
+ }
+ var successiveACState = 0, successiveACNextValue;
+ function decodeACSuccessive(component, zz) {
+ var k = spectralStart, e = spectralEnd, r = 0;
+ while (k <= e) {
+ var z = dctZigZag[k];
+ switch (successiveACState) {
+ case 0: // initial state
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r);
+ successiveACState = 4;
+ } else {
+ r = 16;
+ successiveACState = 1;
+ }
+ } else {
+ if (s !== 1)
+ throw "invalid ACn encoding";
+ successiveACNextValue = receiveAndExtend(s);
+ successiveACState = r ? 2 : 3;
+ }
+ continue;
+ case 1: // skipping r zero items
+ case 2:
+ if (zz[z])
+ zz[z] += (readBit() << successive);
+ else {
+ r--;
+ if (r === 0)
+ successiveACState = successiveACState == 2 ? 3 : 0;
+ }
+ break;
+ case 3: // set value for a zero item
+ if (zz[z])
+ zz[z] += (readBit() << successive);
+ else {
+ zz[z] = successiveACNextValue << successive;
+ successiveACState = 0;
+ }
+ break;
+ case 4: // eob
+ if (zz[z])
+ zz[z] += (readBit() << successive);
+ break;
+ }
+ k++;
+ }
+ if (successiveACState === 4) {
+ eobrun--;
+ if (eobrun === 0)
+ successiveACState = 0;
+ }
+ }
+ function decodeMcu(component, decode, mcu, row, col) {
+ var mcuRow = (mcu / mcusPerLine) | 0;
+ var mcuCol = mcu % mcusPerLine;
+ var blockRow = mcuRow * component.v + row;
+ var blockCol = mcuCol * component.h + col;
+ decode(component, component.blocks[blockRow][blockCol]);
+ }
+ function decodeBlock(component, decode, mcu) {
+ var blockRow = (mcu / component.blocksPerLine) | 0;
+ var blockCol = mcu % component.blocksPerLine;
+ decode(component, component.blocks[blockRow][blockCol]);
+ }
+
+ var componentsLength = components.length;
+ var component, i, j, k, n;
+ var decodeFn;
+ if (progressive) {
+ if (spectralStart === 0)
+ decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+ else
+ decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+ } else {
+ decodeFn = decodeBaseline;
+ }
+
+ var mcu = 0, marker;
+ var mcuExpected;
+ if (componentsLength == 1) {
+ mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+ } else {
+ mcuExpected = mcusPerLine * frame.mcusPerColumn;
+ }
+ if (!resetInterval) resetInterval = mcuExpected;
+
+ var h, v;
+ while (mcu < mcuExpected) {
+ // reset interval stuff
+ for (i = 0; i < componentsLength; i++)
+ components[i].pred = 0;
+ eobrun = 0;
+
+ if (componentsLength == 1) {
+ component = components[0];
+ for (n = 0; n < resetInterval; n++) {
+ decodeBlock(component, decodeFn, mcu);
+ mcu++;
+ }
+ } else {
+ for (n = 0; n < resetInterval; n++) {
+ for (i = 0; i < componentsLength; i++) {
+ component = components[i];
+ h = component.h;
+ v = component.v;
+ for (j = 0; j < v; j++) {
+ for (k = 0; k < h; k++) {
+ decodeMcu(component, decodeFn, mcu, j, k);
+ }
+ }
+ }
+ mcu++;
+ }
+ }
+
+ // find marker
+ bitsCount = 0;
+ marker = (data[offset] << 8) | data[offset + 1];
+ if (marker <= 0xFF00) {
+ throw "marker was not found";
+ }
+
+ if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
+ offset += 2;
+ }
+ else
+ break;
+ }
+
+ return offset - startOffset;
+ }
+
+ function buildComponentData(frame, component) {
+ var lines = [];
+ var blocksPerLine = component.blocksPerLine;
+ var blocksPerColumn = component.blocksPerColumn;
+ var samplesPerLine = blocksPerLine << 3;
+ var R = new Int32Array(64), r = new Uint8Array(64);
+
+ // A port of poppler's IDCT method which in turn is taken from:
+ // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+ // "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
+ // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+ // 988-991.
+ function quantizeAndInverse(zz, dataOut, dataIn) {
+ var qt = component.quantizationTable;
+ var v0, v1, v2, v3, v4, v5, v6, v7, t;
+ var p = dataIn;
+ var i;
+
+ // dequant
+ for (i = 0; i < 64; i++)
+ p[i] = zz[i] * qt[i];
+
+ // inverse DCT on rows
+ for (i = 0; i < 8; ++i) {
+ var row = 8 * i;
+
+ // check for all-zero AC coefficients
+ if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
+ p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
+ p[7 + row] == 0) {
+ t = (dctSqrt2 * p[0 + row] + 512) >> 10;
+ p[0 + row] = t;
+ p[1 + row] = t;
+ p[2 + row] = t;
+ p[3 + row] = t;
+ p[4 + row] = t;
+ p[5 + row] = t;
+ p[6 + row] = t;
+ p[7 + row] = t;
+ continue;
+ }
+
+ // stage 4
+ v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
+ v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
+ v2 = p[2 + row];
+ v3 = p[6 + row];
+ v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
+ v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
+ v5 = p[3 + row] << 4;
+ v6 = p[5 + row] << 4;
+
+ // stage 3
+ t = (v0 - v1+ 1) >> 1;
+ v0 = (v0 + v1 + 1) >> 1;
+ v1 = t;
+ t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+ v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+ v3 = t;
+ t = (v4 - v6 + 1) >> 1;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = t;
+ t = (v7 + v5 + 1) >> 1;
+ v5 = (v7 - v5 + 1) >> 1;
+ v7 = t;
+
+ // stage 2
+ t = (v0 - v3 + 1) >> 1;
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = t;
+ t = (v1 - v2 + 1) >> 1;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = t;
+ t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+ v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+ v7 = t;
+ t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+ v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ p[0 + row] = v0 + v7;
+ p[7 + row] = v0 - v7;
+ p[1 + row] = v1 + v6;
+ p[6 + row] = v1 - v6;
+ p[2 + row] = v2 + v5;
+ p[5 + row] = v2 - v5;
+ p[3 + row] = v3 + v4;
+ p[4 + row] = v3 - v4;
+ }
+
+ // inverse DCT on columns
+ for (i = 0; i < 8; ++i) {
+ var col = i;
+
+ // check for all-zero AC coefficients
+ if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
+ p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
+ p[7*8 + col] == 0) {
+ t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14;
+ p[0*8 + col] = t;
+ p[1*8 + col] = t;
+ p[2*8 + col] = t;
+ p[3*8 + col] = t;
+ p[4*8 + col] = t;
+ p[5*8 + col] = t;
+ p[6*8 + col] = t;
+ p[7*8 + col] = t;
+ continue;
+ }
+
+ // stage 4
+ v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
+ v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
+ v2 = p[2*8 + col];
+ v3 = p[6*8 + col];
+ v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
+ v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
+ v5 = p[3*8 + col];
+ v6 = p[5*8 + col];
+
+ // stage 3
+ t = (v0 - v1 + 1) >> 1;
+ v0 = (v0 + v1 + 1) >> 1;
+ v1 = t;
+ t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+ v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+ v3 = t;
+ t = (v4 - v6 + 1) >> 1;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = t;
+ t = (v7 + v5 + 1) >> 1;
+ v5 = (v7 - v5 + 1) >> 1;
+ v7 = t;
+
+ // stage 2
+ t = (v0 - v3 + 1) >> 1;
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = t;
+ t = (v1 - v2 + 1) >> 1;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = t;
+ t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+ v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+ v7 = t;
+ t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+ v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ p[0*8 + col] = v0 + v7;
+ p[7*8 + col] = v0 - v7;
+ p[1*8 + col] = v1 + v6;
+ p[6*8 + col] = v1 - v6;
+ p[2*8 + col] = v2 + v5;
+ p[5*8 + col] = v2 - v5;
+ p[3*8 + col] = v3 + v4;
+ p[4*8 + col] = v3 - v4;
+ }
+
+ // convert to 8-bit integers
+ for (i = 0; i < 64; ++i) {
+ var sample = 128 + ((p[i] + 8) >> 4);
+ dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample;
+ }
+ }
+
+ var i, j;
+ for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+ var scanLine = blockRow << 3;
+ for (i = 0; i < 8; i++)
+ lines.push(new Uint8Array(samplesPerLine));
+ for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+ quantizeAndInverse(component.blocks[blockRow][blockCol], r, R);
+
+ var offset = 0, sample = blockCol << 3;
+ for (j = 0; j < 8; j++) {
+ var line = lines[scanLine + j];
+ for (i = 0; i < 8; i++)
+ line[sample + i] = r[offset++];
+ }
+ }
+ }
+ return lines;
+ }
+
+ constructor.prototype = {
+ load: function load(path) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", path, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = (function() {
+ // TODO catch parse error
+ var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
+ this.parse(data);
+ if (this.onload)
+ this.onload();
+ }).bind(this);
+ xhr.send(null);
+ },
+ parse: function parse(data) {
+ var offset = 0, length = data.length;
+ function readUint16() {
+ var value = (data[offset] << 8) | data[offset + 1];
+ offset += 2;
+ return value;
+ }
+ function readDataBlock() {
+ var length = readUint16();
+ var array = data.subarray(offset, offset + length - 2);
+ offset += array.length;
+ return array;
+ }
+ function prepareComponents(frame) {
+ var maxH = 0, maxV = 0;
+ var component, componentId;
+ for (componentId in frame.components) {
+ if (frame.components.hasOwnProperty(componentId)) {
+ component = frame.components[componentId];
+ if (maxH < component.h) maxH = component.h;
+ if (maxV < component.v) maxV = component.v;
+ }
+ }
+ var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / maxH);
+ var mcusPerColumn = Math.ceil(frame.scanLines / 8 / maxV);
+ for (componentId in frame.components) {
+ if (frame.components.hasOwnProperty(componentId)) {
+ component = frame.components[componentId];
+ var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / maxH);
+ var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / maxV);
+ var blocksPerLineForMcu = mcusPerLine * component.h;
+ var blocksPerColumnForMcu = mcusPerColumn * component.v;
+ var blocks = [];
+ for (var i = 0; i < blocksPerColumnForMcu; i++) {
+ var row = [];
+ for (var j = 0; j < blocksPerLineForMcu; j++)
+ row.push(new Int32Array(64));
+ blocks.push(row);
+ }
+ component.blocksPerLine = blocksPerLine;
+ component.blocksPerColumn = blocksPerColumn;
+ component.blocks = blocks;
+ }
+ }
+ frame.maxH = maxH;
+ frame.maxV = maxV;
+ frame.mcusPerLine = mcusPerLine;
+ frame.mcusPerColumn = mcusPerColumn;
+ }
+ var jfif = null;
+ var adobe = null;
+ var pixels = null;
+ var frame, resetInterval;
+ var quantizationTables = [], frames = [];
+ var huffmanTablesAC = [], huffmanTablesDC = [];
+ var fileMarker = readUint16();
+ if (fileMarker != 0xFFD8) { // SOI (Start of Image)
+ throw "SOI not found";
+ }
+
+ fileMarker = readUint16();
+ while (fileMarker != 0xFFD9) { // EOI (End of image)
+ var i, j, l;
+ switch(fileMarker) {
+ case 0xFFE0: // APP0 (Application Specific)
+ case 0xFFE1: // APP1
+ case 0xFFE2: // APP2
+ case 0xFFE3: // APP3
+ case 0xFFE4: // APP4
+ case 0xFFE5: // APP5
+ case 0xFFE6: // APP6
+ case 0xFFE7: // APP7
+ case 0xFFE8: // APP8
+ case 0xFFE9: // APP9
+ case 0xFFEA: // APP10
+ case 0xFFEB: // APP11
+ case 0xFFEC: // APP12
+ case 0xFFED: // APP13
+ case 0xFFEE: // APP14
+ case 0xFFEF: // APP15
+ case 0xFFFE: // COM (Comment)
+ var appData = readDataBlock();
+
+ if (fileMarker === 0xFFE0) {
+ if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 &&
+ appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00'
+ jfif = {
+ version: { major: appData[5], minor: appData[6] },
+ densityUnits: appData[7],
+ xDensity: (appData[8] << 8) | appData[9],
+ yDensity: (appData[10] << 8) | appData[11],
+ thumbWidth: appData[12],
+ thumbHeight: appData[13],
+ thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
+ };
+ }
+ }
+ // TODO APP1 - Exif
+ if (fileMarker === 0xFFEE) {
+ if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F &&
+ appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+ adobe = {
+ version: appData[6],
+ flags0: (appData[7] << 8) | appData[8],
+ flags1: (appData[9] << 8) | appData[10],
+ transformCode: appData[11]
+ };
+ }
+ }
+ break;
+
+ case 0xFFDB: // DQT (Define Quantization Tables)
+ var quantizationTableCount = Math.floor((readUint16() - 2) / 65);
+ for (i = 0; i < quantizationTableCount; i++) {
+ var quantizationTableSpec = data[offset++];
+ var tableData = new Int32Array(64);
+ if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
+ for (j = 0; j < 64; j++) {
+ var z = dctZigZag[j];
+ tableData[z] = data[offset++];
+ }
+ } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
+ tableData[j] = readUint16();
+ } else
+ throw "DQT: invalid table spec";
+ quantizationTables[quantizationTableSpec & 15] = tableData;
+ }
+ break;
+
+ case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+ case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
+ readUint16(); // skip data length
+ frame = {};
+ frame.progressive = (fileMarker === 0xFFC2);
+ frame.precision = data[offset++];
+ frame.scanLines = readUint16();
+ frame.samplesPerLine = readUint16();
+ frame.components = [];
+ var componentsCount = data[offset++], componentId;
+ var maxH = 0, maxV = 0;
+ for (i = 0; i < componentsCount; i++) {
+ componentId = data[offset];
+ var h = data[offset + 1] >> 4;
+ var v = data[offset + 1] & 15;
+ var qId = data[offset + 2];
+ frame.components[componentId] = {
+ h: h,
+ v: v,
+ quantizationTable: quantizationTables[qId]
+ };
+ offset += 3;
+ }
+ prepareComponents(frame);
+ frames.push(frame);
+ break;
+
+ case 0xFFC4: // DHT (Define Huffman Tables)
+ var huffmanLength = readUint16();
+ for (i = 2; i < huffmanLength;) {
+ var huffmanTableSpec = data[offset++];
+ var codeLengths = new Uint8Array(16);
+ var codeLengthSum = 0;
+ for (j = 0; j < 16; j++, offset++)
+ codeLengthSum += (codeLengths[j] = data[offset]);
+ var huffmanValues = new Uint8Array(codeLengthSum);
+ for (j = 0; j < codeLengthSum; j++, offset++)
+ huffmanValues[j] = data[offset];
+ i += 17 + codeLengthSum;
+
+ ((huffmanTableSpec >> 4) === 0 ?
+ huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
+ buildHuffmanTable(codeLengths, huffmanValues);
+ }
+ break;
+
+ case 0xFFDD: // DRI (Define Restart Interval)
+ readUint16(); // skip data length
+ resetInterval = readUint16();
+ break;
+
+ case 0xFFDA: // SOS (Start of Scan)
+ var scanLength = readUint16();
+ var selectorsCount = data[offset++];
+ var components = [], component;
+ for (i = 0; i < selectorsCount; i++) {
+ component = frame.components[data[offset++]];
+ var tableSpec = data[offset++];
+ component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+ component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+ components.push(component);
+ }
+ var spectralStart = data[offset++];
+ var spectralEnd = data[offset++];
+ var successiveApproximation = data[offset++];
+ var processed = decodeScan(data, offset,
+ frame, components, resetInterval,
+ spectralStart, spectralEnd,
+ successiveApproximation >> 4, successiveApproximation & 15);
+ offset += processed;
+ break;
+ default:
+ throw "unknown JPEG marker " + fileMarker.toString(16);
+ }
+ fileMarker = readUint16();
+ }
+ if (frames.length != 1)
+ throw "only single frame JPEGs supported";
+
+ this.width = frame.samplesPerLine;
+ this.height = frame.scanLines;
+ this.jfif = jfif;
+ this.adobe = adobe;
+ this.components = [];
+ for (var id in frame.components) {
+ if (frame.components.hasOwnProperty(id)) {
+ this.components.push({
+ lines: buildComponentData(frame, frame.components[id]),
+ scaleX: frame.components[id].h / frame.maxH,
+ scaleY: frame.components[id].v / frame.maxV
+ });
+ }
+ }
+ },
+ getData: function getData(width, height) {
+ function clampTo8bit(a) {
+ return a < 0 ? 0 : a > 255 ? 255 : a;
+ }
+ var scaleX = this.width / width, scaleY = this.height / height;
+
+ var component1, component2, component3, component4;
+ var component1Line, component2Line, component3Line, component4Line;
+ var x, y;
+ var offset = 0;
+ var Y, Cb, Cr, K, C, M, Ye, R, G, B;
+ var colorTransform;
+ var dataLength = width * height * this.components.length;
+ var data = new Uint8Array(dataLength);
+ switch (this.components.length) {
+ case 1:
+ component1 = this.components[0];
+ for (y = 0; y < height; y++) {
+ component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+ for (x = 0; x < width; x++) {
+ Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+
+ data[offset++] = Y;
+ }
+ }
+ break;
+ case 3:
+ // The default transform for three components is true
+ colorTransform = true;
+ // The adobe transform marker overrides any previous setting
+ if (this.adobe && this.adobe.transformCode)
+ colorTransform = true;
+ else if (typeof this.colorTransform !== 'undefined')
+ colorTransform = !!this.colorTransform;
+
+ component1 = this.components[0];
+ component2 = this.components[1];
+ component3 = this.components[2];
+ for (y = 0; y < height; y++) {
+ component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+ component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
+ component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
+ for (x = 0; x < width; x++) {
+ if (!colorTransform) {
+ R = component1Line[0 | (x * component1.scaleX * scaleX)];
+ G = component2Line[0 | (x * component2.scaleX * scaleX)];
+ B = component3Line[0 | (x * component3.scaleX * scaleX)];
+ } else {
+ Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+ Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
+ Cr = component3Line[0 | (x * component3.scaleX * scaleX)];
+
+ R = clampTo8bit(Y + 1.402 * (Cr - 128));
+ G = clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
+ B = clampTo8bit(Y + 1.772 * (Cb - 128));
+ }
+
+ data[offset++] = R;
+ data[offset++] = G;
+ data[offset++] = B;
+ }
+ }
+ break;
+ case 4:
+ if (!this.adobe)
+ throw 'Unsupported color mode (4 components)';
+ // The default transform for four components is false
+ colorTransform = false;
+ // The adobe transform marker overrides any previous setting
+ if (this.adobe && this.adobe.transformCode)
+ colorTransform = true;
+ else if (typeof this.colorTransform !== 'undefined')
+ colorTransform = !!this.colorTransform;
+
+ component1 = this.components[0];
+ component2 = this.components[1];
+ component3 = this.components[2];
+ component4 = this.components[3];
+ for (y = 0; y < height; y++) {
+ component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+ component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
+ component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
+ component4Line = component4.lines[0 | (y * component4.scaleY * scaleY)];
+ for (x = 0; x < width; x++) {
+ if (!colorTransform) {
+ C = component1Line[0 | (x * component1.scaleX * scaleX)];
+ M = component2Line[0 | (x * component2.scaleX * scaleX)];
+ Ye = component3Line[0 | (x * component3.scaleX * scaleX)];
+ K = component4Line[0 | (x * component4.scaleX * scaleX)];
+ } else {
+ Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+ Cb = component2Line[0 | (x * component2.scaleX * scaleX)];
+ Cr = component3Line[0 | (x * component3.scaleX * scaleX)];
+ K = component4Line[0 | (x * component4.scaleX * scaleX)];
+
+ C = 255 - clampTo8bit(Y + 1.402 * (Cr - 128));
+ M = 255 - clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
+ Ye = 255 - clampTo8bit(Y + 1.772 * (Cb - 128));
+ }
+ data[offset++] = C;
+ data[offset++] = M;
+ data[offset++] = Ye;
+ data[offset++] = K;
+ }
+ }
+ break;
+ default:
+ throw 'Unsupported color mode';
+ }
+ return data;
+ },
+ copyToImageData: function copyToImageData(imageData) {
+ var width = imageData.width, height = imageData.height;
+ var imageDataArray = imageData.data;
+ var data = this.getData(width, height);
+ var i = 0, j = 0, x, y;
+ var Y, K, C, M, R, G, B;
+ switch (this.components.length) {
+ case 1:
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ Y = data[i++];
+
+ imageDataArray[j++] = Y;
+ imageDataArray[j++] = Y;
+ imageDataArray[j++] = Y;
+ imageDataArray[j++] = 255;
+ }
+ }
+ break;
+ case 3:
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ R = data[i++];
+ G = data[i++];
+ B = data[i++];
+
+ imageDataArray[j++] = R;
+ imageDataArray[j++] = G;
+ imageDataArray[j++] = B;
+ imageDataArray[j++] = 255;
+ }
+ }
+ break;
+ case 4:
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ C = data[i++];
+ M = data[i++];
+ Y = data[i++];
+ K = data[i++];
+
+ R = 255 - clampTo8bit(C * (1 - K / 255) + K);
+ G = 255 - clampTo8bit(M * (1 - K / 255) + K);
+ B = 255 - clampTo8bit(Y * (1 - K / 255) + K);
+
+ imageDataArray[j++] = R;
+ imageDataArray[j++] = G;
+ imageDataArray[j++] = B;
+ imageDataArray[j++] = 255;
+ }
+ }
+ break;
+ default:
+ throw 'Unsupported color mode';
+ }
+ }
+ };
+
+ return constructor;
+})();/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var JpxImage = (function JpxImageClosure() {
+ // Table E.1
+ var SubbandsGainLog2 = {
+ 'LL': 0,
+ 'LH': 1,
+ 'HL': 1,
+ 'HH': 2
+ };
+ function JpxImage() {
+ this.failOnCorruptedImage = false;
+ }
+ JpxImage.prototype = {
+ load: function JpxImage_load(url) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = (function() {
+ // TODO catch parse error
+ var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
+ this.parse(data);
+ if (this.onload)
+ this.onload();
+ }).bind(this);
+ xhr.send(null);
+ },
+ parse: function JpxImage_parse(data) {
+ function ReadUint(data, offset, bytes) {
+ var n = 0;
+ for (var i = 0; i < bytes; i++)
+ n = n * 256 + (data[offset + i] & 0xFF);
+ return n;
+ }
+ var position = 0, length = data.length;
+ while (position < length) {
+ var headerSize = 8;
+ var lbox = ReadUint(data, position, 4);
+ var tbox = ReadUint(data, position + 4, 4);
+ position += headerSize;
+ if (lbox == 1) {
+ lbox = ReadUint(data, position, 8);
+ position += 8;
+ headerSize += 8;
+ }
+ if (lbox == 0)
+ lbox = length - position + headerSize;
+ if (lbox < headerSize)
+ error('JPX error: Invalid box field size');
+ var dataLength = lbox - headerSize;
+ var jumpDataLength = true;
+ switch (tbox) {
+ case 0x6A501A1A: // 'jP\032\032'
+ // TODO
+ break;
+ case 0x6A703268: // 'jp2h'
+ jumpDataLength = false; // parsing child boxes
+ break;
+ case 0x636F6C72: // 'colr'
+ // TODO
+ break;
+ case 0x6A703263: // 'jp2c'
+ this.parseCodestream(data, position, position + dataLength);
+ break;
+ }
+ if (jumpDataLength)
+ position += dataLength;
+ }
+ },
+ parseCodestream: function JpxImage_parseCodestream(data, start, end) {
+ var context = {};
+ try {
+ var position = start;
+ while (position < end) {
+ var code = readUint16(data, position);
+ position += 2;
+
+ var length = 0, j;
+ switch (code) {
+ case 0xFF4F: // Start of codestream (SOC)
+ context.mainHeader = true;
+ break;
+ case 0xFFD9: // End of codestream (EOC)
+ break;
+ case 0xFF51: // Image and tile size (SIZ)
+ length = readUint16(data, position);
+ var siz = {};
+ siz.Xsiz = readUint32(data, position + 4);
+ siz.Ysiz = readUint32(data, position + 8);
+ siz.XOsiz = readUint32(data, position + 12);
+ siz.YOsiz = readUint32(data, position + 16);
+ siz.XTsiz = readUint32(data, position + 20);
+ siz.YTsiz = readUint32(data, position + 24);
+ siz.XTOsiz = readUint32(data, position + 28);
+ siz.YTOsiz = readUint32(data, position + 32);
+ var componentsCount = readUint16(data, position + 36);
+ siz.Csiz = componentsCount;
+ var components = [];
+ j = position + 38;
+ for (var i = 0; i < componentsCount; i++) {
+ var component = {
+ precision: (data[j] & 0x7F) + 1,
+ isSigned: !!(data[j] & 0x80),
+ XRsiz: data[j + 1],
+ YRsiz: data[j + 1]
+ };
+ calculateComponentDimensions(component, siz);
+ components.push(component);
+ }
+ context.SIZ = siz;
+ context.components = components;
+ calculateTileGrids(context, components);
+ context.QCC = [];
+ context.COC = [];
+ break;
+ case 0xFF5C: // Quantization default (QCD)
+ length = readUint16(data, position);
+ var qcd = {};
+ j = position + 2;
+ var sqcd = data[j++];
+ var spqcdSize, scalarExpounded;
+ switch (sqcd & 0x1F) {
+ case 0:
+ spqcdSize = 8;
+ scalarExpounded = true;
+ break;
+ case 1:
+ spqcdSize = 16;
+ scalarExpounded = false;
+ break;
+ case 2:
+ spqcdSize = 16;
+ scalarExpounded = true;
+ break;
+ default:
+ throw 'Invalid SQcd value ' + sqcd;
+ }
+ qcd.noQuantization = spqcdSize == 8;
+ qcd.scalarExpounded = scalarExpounded;
+ qcd.guardBits = sqcd >> 5;
+ var spqcds = [];
+ while (j < length + position) {
+ var spqcd = {};
+ if (spqcdSize == 8) {
+ spqcd.epsilon = data[j++] >> 3;
+ spqcd.mu = 0;
+ } else {
+ spqcd.epsilon = data[j] >> 3;
+ spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
+ j += 2;
+ }
+ spqcds.push(spqcd);
+ }
+ qcd.SPqcds = spqcds;
+ if (context.mainHeader)
+ context.QCD = qcd;
+ else {
+ context.currentTile.QCD = qcd;
+ context.currentTile.QCC = [];
+ }
+ break;
+ case 0xFF5D: // Quantization component (QCC)
+ length = readUint16(data, position);
+ var qcc = {};
+ j = position + 2;
+ var cqcc;
+ if (context.SIZ.Csiz < 257)
+ cqcc = data[j++];
+ else {
+ cqcc = readUint16(data, j);
+ j += 2;
+ }
+ var sqcd = data[j++];
+ var spqcdSize, scalarExpounded;
+ switch (sqcd & 0x1F) {
+ case 0:
+ spqcdSize = 8;
+ scalarExpounded = true;
+ break;
+ case 1:
+ spqcdSize = 16;
+ scalarExpounded = false;
+ break;
+ case 2:
+ spqcdSize = 16;
+ scalarExpounded = true;
+ break;
+ default:
+ throw 'Invalid SQcd value ' + sqcd;
+ }
+ qcc.noQuantization = spqcdSize == 8;
+ qcc.scalarExpounded = scalarExpounded;
+ qcc.guardBits = sqcd >> 5;
+ var spqcds = [];
+ while (j < length + position) {
+ var spqcd = {};
+ if (spqcdSize == 8) {
+ spqcd.epsilon = data[j++] >> 3;
+ spqcd.mu = 0;
+ } else {
+ spqcd.epsilon = data[j] >> 3;
+ spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
+ j += 2;
+ }
+ spqcds.push(spqcd);
+ }
+ qcc.SPqcds = spqcds;
+ if (context.mainHeader)
+ context.QCC[cqcc] = qcc;
+ else
+ context.currentTile.QCC[cqcc] = qcc;
+ break;
+ case 0xFF52: // Coding style default (COD)
+ length = readUint16(data, position);
+ var cod = {};
+ j = position + 2;
+ var scod = data[j++];
+ cod.entropyCoderWithCustomPrecincts = !!(scod & 1);
+ cod.sopMarkerUsed = !!(scod & 2);
+ cod.ephMarkerUsed = !!(scod & 4);
+ var codingStyle = {};
+ cod.progressionOrder = data[j++];
+ cod.layersCount = readUint16(data, j);
+ j += 2;
+ cod.multipleComponentTransform = data[j++];
+
+ cod.decompositionLevelsCount = data[j++];
+ cod.xcb = (data[j++] & 0xF) + 2;
+ cod.ycb = (data[j++] & 0xF) + 2;
+ var blockStyle = data[j++];
+ cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1);
+ cod.resetContextProbabilities = !!(blockStyle & 2);
+ cod.terminationOnEachCodingPass = !!(blockStyle & 4);
+ cod.verticalyStripe = !!(blockStyle & 8);
+ cod.predictableTermination = !!(blockStyle & 16);
+ cod.segmentationSymbolUsed = !!(blockStyle & 32);
+ cod.transformation = data[j++];
+ if (cod.entropyCoderWithCustomPrecincts) {
+ var precinctsSizes = {};
+ while (j < length + position) {
+ var precinctsSize = data[j];
+ precinctsSizes.push({
+ PPx: precinctsSize & 0xF,
+ PPy: precinctsSize >> 4
+ });
+ }
+ cod.precinctsSizes = precinctsSizes;
+ }
+
+ if (cod.sopMarkerUsed || cod.ephMarkerUsed ||
+ cod.selectiveArithmeticCodingBypass ||
+ cod.resetContextProbabilities ||
+ cod.terminationOnEachCodingPass ||
+ cod.verticalyStripe || cod.predictableTermination ||
+ cod.segmentationSymbolUsed)
+ throw 'Unsupported COD options: ' + uneval(cod);
+
+ if (context.mainHeader)
+ context.COD = cod;
+ else {
+ context.currentTile.COD = cod;
+ context.currentTile.COC = [];
+ }
+ break;
+ case 0xFF90: // Start of tile-part (SOT)
+ length = readUint16(data, position);
+ var tile = {};
+ tile.index = readUint16(data, position + 2);
+ tile.length = readUint32(data, position + 4);
+ tile.dataEnd = tile.length + position - 2;
+ tile.partIndex = data[position + 8];
+ tile.partsCount = data[position + 9];
+
+ context.mainHeader = false;
+ if (tile.partIndex == 0) {
+ // reset component specific settings
+ tile.COD = context.COD;
+ tile.COC = context.COC.slice(0); // clone of the global COC
+ tile.QCD = context.QCD;
+ tile.QCC = context.QCC.slice(0); // clone of the global COC
+ }
+ context.currentTile = tile;
+ break;
+ case 0xFF93: // Start of data (SOD)
+ var tile = context.currentTile;
+ if (tile.partIndex == 0) {
+ initializeTile(context, tile.index);
+ buildPackets(context);
+ }
+
+ // moving to the end of the data
+ length = tile.dataEnd - position;
+
+ parseTilePackets(context, data, position, length);
+ break;
+ case 0xFF64: // Comment (COM)
+ length = readUint16(data, position);
+ // skipping content
+ break;
+ default:
+ throw 'Unknown codestream code: ' + code.toString(16);
+ }
+ position += length;
+ }
+ } catch (e) {
+ if (this.failOnCorruptedImage)
+ error('JPX error: ' + e);
+ else
+ warn('JPX error: ' + e + '. Trying to recover');
+ }
+ this.tiles = transformComponents(context);
+ this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
+ this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
+ this.componentsCount = context.SIZ.Csiz;
+ }
+ };
+ function readUint32(data, offset) {
+ return (data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3];
+ }
+ function readUint16(data, offset) {
+ return (data[offset] << 8) | data[offset + 1];
+ }
+ function log2(x) {
+ var n = 1, i = 0;
+ while (x > n) {
+ n <<= 1;
+ i++;
+ }
+ return i;
+ }
+ function calculateComponentDimensions(component, siz) {
+ // Section B.2 Component mapping
+ component.x0 = Math.ceil(siz.XOsiz / component.XRsiz);
+ component.x1 = Math.ceil(siz.Xsiz / component.XRsiz);
+ component.y0 = Math.ceil(siz.YOsiz / component.YRsiz);
+ component.y1 = Math.ceil(siz.Ysiz / component.YRsiz);
+ component.width = component.x1 - component.x0;
+ component.height = component.y1 - component.y0;
+ }
+ function calculateTileGrids(context, components) {
+ var siz = context.SIZ;
+ // Section B.3 Division into tile and tile-components
+ var tiles = [];
+ var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz);
+ var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz);
+ for (var q = 0; q < numYtiles; q++) {
+ for (var p = 0; p < numXtiles; p++) {
+ var tile = {};
+ tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz);
+ tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz);
+ tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz);
+ tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz);
+ tile.width = tile.tx1 - tile.tx0;
+ tile.height = tile.ty1 - tile.ty0;
+ tile.components = [];
+ tiles.push(tile);
+ }
+ }
+ context.tiles = tiles;
+
+ var componentsCount = siz.Csiz;
+ for (var i = 0, ii = componentsCount; i < ii; i++) {
+ var component = components[i];
+ var tileComponents = [];
+ for (var j = 0, jj = tiles.length; j < jj; j++) {
+ var tileComponent = {}, tile = tiles[j];
+ tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
+ tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
+ tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
+ tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
+ tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
+ tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0;
+ tile.components[i] = tileComponent;
+ }
+ }
+ }
+ function getBlocksDimensions(context, component, r) {
+ var codOrCoc = component.codingStyleParameters;
+ var result = {};
+ if (!codOrCoc.entropyCoderWithCustomPrecincts) {
+ result.PPx = 15;
+ result.PPy = 15;
+ } else {
+ result.PPx = codOrCoc.precinctsSizes[r].PPx;
+ result.PPy = codOrCoc.precinctsSizes[r].PPy;
+ }
+ // calculate codeblock size as described in section B.7
+ result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) :
+ Math.min(codOrCoc.xcb, result.PPx);
+ result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) :
+ Math.min(codOrCoc.ycb, result.PPy);
+ return result;
+ }
+ function buildPrecincts(context, resolution, dimensions) {
+ // Section B.6 Division resolution to precincts
+ var precinctWidth = 1 << dimensions.PPx;
+ var precinctHeight = 1 << dimensions.PPy;
+ var numprecinctswide = resolution.trx1 > resolution.trx0 ?
+ Math.ceil(resolution.trx1 / precinctWidth) -
+ Math.floor(resolution.trx0 / precinctWidth) : 0;
+ var numprecinctshigh = resolution.try1 > resolution.try0 ?
+ Math.ceil(resolution.try1 / precinctHeight) -
+ Math.floor(resolution.try0 / precinctHeight) : 0;
+ var numprecincts = numprecinctswide * numprecinctshigh;
+ var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) *
+ precinctWidth;
+ var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) *
+ precinctHeight;
+ resolution.precinctParameters = {
+ precinctXOffset: precinctXOffset,
+ precinctYOffset: precinctYOffset,
+ precinctWidth: precinctWidth,
+ precinctHeight: precinctHeight,
+ numprecinctswide: numprecinctswide,
+ numprecinctshigh: numprecinctshigh,
+ numprecincts: numprecincts
+ };
+ }
+ function buildCodeblocks(context, subband, dimensions) {
+ // Section B.7 Division sub-band into code-blocks
+ var xcb_ = dimensions.xcb_;
+ var ycb_ = dimensions.ycb_;
+ var codeblockWidth = 1 << xcb_;
+ var codeblockHeight = 1 << ycb_;
+ var cbx0 = Math.floor(subband.tbx0 / codeblockWidth);
+ var cby0 = Math.floor(subband.tby0 / codeblockHeight);
+ var cbx1 = Math.ceil(subband.tbx1 / codeblockWidth);
+ var cby1 = Math.ceil(subband.tby1 / codeblockHeight);
+ var precinctParameters = subband.resolution.precinctParameters;
+ var codeblocks = [];
+ var precincts = [];
+ for (var j = cby0; j < cby1; j++) {
+ for (var i = cbx0; i < cbx1; i++) {
+ var codeblock = {
+ cbx: i,
+ cby: j,
+ tbx0: codeblockWidth * i,
+ tby0: codeblockHeight * j,
+ tbx1: codeblockWidth * (i + 1),
+ tby1: codeblockHeight * (j + 1)
+ };
+ // calculate precinct number
+ var pi = Math.floor((codeblock.tbx0 -
+ precinctParameters.precinctXOffset) /
+ precinctParameters.precinctWidth);
+ var pj = Math.floor((codeblock.tby0 -
+ precinctParameters.precinctYOffset) /
+ precinctParameters.precinctHeight);
+ var precinctNumber = pj +
+ pi * precinctParameters.numprecinctswide;
+ codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
+ codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
+ codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
+ codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
+ codeblock.precinctNumber = precinctNumber;
+ codeblock.subbandType = subband.type;
+ var coefficientsLength = (codeblock.tbx1_ - codeblock.tbx0_) *
+ (codeblock.tby1_ - codeblock.tby0_);
+ codeblock.Lblock = 3;
+ codeblocks.push(codeblock);
+ // building precinct for the sub-band
+ var precinct;
+ if (precinctNumber in precincts) {
+ precinct = precincts[precinctNumber];
+ precinct.cbxMin = Math.min(precinct.cbxMin, i);
+ precinct.cbyMin = Math.min(precinct.cbyMin, j);
+ precinct.cbxMax = Math.max(precinct.cbxMax, i);
+ precinct.cbyMax = Math.max(precinct.cbyMax, j);
+ } else {
+ precincts[precinctNumber] = precinct = {
+ cbxMin: i,
+ cbyMin: j,
+ cbxMax: i,
+ cbyMax: j
+ };
+ }
+ codeblock.precinct = precinct;
+ }
+ }
+ subband.codeblockParameters = {
+ codeblockWidth: xcb_,
+ codeblockHeight: ycb_,
+ numcodeblockwide: cbx1 - cbx0 + 1,
+ numcodeblockhigh: cby1 - cby1 + 1
+ };
+ subband.codeblocks = codeblocks;
+ for (var i = 0, ii = codeblocks.length; i < ii; i++) {
+ var codeblock = codeblocks[i];
+ var precinctNumber = codeblock.precinctNumber;
+ }
+ subband.precincts = precincts;
+ }
+ function createPacket(resolution, precinctNumber, layerNumber) {
+ var precinctCodeblocks = [];
+ // Section B.10.8 Order of info in packet
+ var subbands = resolution.subbands;
+ // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence
+ for (var i = 0, ii = subbands.length; i < ii; i++) {
+ var subband = subbands[i];
+ var codeblocks = subband.codeblocks;
+ for (var j = 0, jj = codeblocks.length; j < jj; j++) {
+ var codeblock = codeblocks[j];
+ if (codeblock.precinctNumber != precinctNumber)
+ continue;
+ precinctCodeblocks.push(codeblock);
+ }
+ }
+ return {
+ layerNumber: layerNumber,
+ codeblocks: precinctCodeblocks
+ };
+ }
+ function LayerResolutionComponentPositionIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var maxDecompositionLevelsCount = 0;
+ for (var q = 0; q < componentsCount; q++) {
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
+ tile.components[q].codingStyleParameters.decompositionLevelsCount);
+ }
+
+ var l = 0, r = 0, i = 0, k = 0;
+
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.1 Layer-resolution-component-position
+ for (; l < layersCount; l++) {
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; i < componentsCount; i++) {
+ var component = tile.components[i];
+ if (r > component.codingStyleParameters.decompositionLevelsCount)
+ continue;
+
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ for (; k < numprecincts;) {
+ var packet = createPacket(resolution, k, l);
+ k++;
+ return packet;
+ }
+ k = 0;
+ }
+ i = 0;
+ }
+ r = 0;
+ }
+ throw 'Out of packets';
+ };
+ }
+ function ResolutionLayerComponentPositionIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var maxDecompositionLevelsCount = 0;
+ for (var q = 0; q < componentsCount; q++) {
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
+ tile.components[q].codingStyleParameters.decompositionLevelsCount);
+ }
+
+ var r = 0, l = 0, i = 0, k = 0;
+
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.2 Resolution-layer-component-position
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; l < layersCount; l++) {
+ for (; i < componentsCount; i++) {
+ var component = tile.components[i];
+ if (r > component.codingStyleParameters.decompositionLevelsCount)
+ continue;
+
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ for (; k < numprecincts;) {
+ var packet = createPacket(resolution, k, l);
+ k++;
+ return packet;
+ }
+ k = 0;
+ }
+ i = 0;
+ }
+ l = 0;
+ }
+ throw 'Out of packets';
+ };
+ }
+ function buildPackets(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var componentsCount = siz.Csiz;
+ // Creating resolutions and sub-bands for each component
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount =
+ component.codingStyleParameters.decompositionLevelsCount;
+ // Section B.5 Resolution levels and sub-bands
+ var resolutions = [];
+ var subbands = [];
+ for (var r = 0; r <= decompositionLevelsCount; r++) {
+ var blocksDimensions = getBlocksDimensions(context, component, r);
+ var resolution = {};
+ var scale = 1 << (decompositionLevelsCount - r);
+ resolution.trx0 = Math.ceil(component.tcx0 / scale);
+ resolution.try0 = Math.ceil(component.tcy0 / scale);
+ resolution.trx1 = Math.ceil(component.tcx1 / scale);
+ resolution.try1 = Math.ceil(component.tcy1 / scale);
+ buildPrecincts(context, resolution, blocksDimensions);
+ resolutions.push(resolution);
+
+ var subband;
+ if (r == 0) {
+ // one sub-band (LL) with last decomposition
+ subband = {};
+ subband.type = 'LL';
+ subband.tbx0 = Math.ceil(component.tcx0 / scale);
+ subband.tby0 = Math.ceil(component.tcy0 / scale);
+ subband.tbx1 = Math.ceil(component.tcx1 / scale);
+ subband.tby1 = Math.ceil(component.tcy1 / scale);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolution.subbands = [subband];
+ } else {
+ var bscale = 1 << (decompositionLevelsCount - r + 1);
+ var resolutionSubbands = [];
+ // three sub-bands (HL, LH and HH) with rest of decompositions
+ subband = {};
+ subband.type = 'HL';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+
+ subband = {};
+ subband.type = 'LH';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+
+ subband = {};
+ subband.type = 'HH';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+
+ resolution.subbands = resolutionSubbands;
+ }
+ }
+ component.resolutions = resolutions;
+ component.subbands = subbands;
+ }
+ // Generate the packets sequence
+ var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder;
+ var packetsIterator;
+ switch (progressionOrder) {
+ case 0:
+ tile.packetsIterator =
+ new LayerResolutionComponentPositionIterator(context);
+ break;
+ case 1:
+ tile.packetsIterator =
+ new ResolutionLayerComponentPositionIterator(context);
+ break;
+ default:
+ throw 'Unsupported progression order ' + progressionOrder;
+ }
+ }
+ function parseTilePackets(context, data, offset, dataLength) {
+ var position = 0;
+ var buffer, bufferSize = 0, skipNextBit = false;
+ function readBits(count) {
+ while (bufferSize < count) {
+ var b = data[offset + position];
+ position++;
+ if (skipNextBit) {
+ buffer = (buffer << 7) | b;
+ bufferSize += 7;
+ skipNextBit = false;
+ } else {
+ buffer = (buffer << 8) | b;
+ bufferSize += 8;
+ }
+ if (b == 0xFF) {
+ skipNextBit = true;
+ }
+ }
+ bufferSize -= count;
+ return (buffer >>> bufferSize) & ((1 << count) - 1);
+ }
+ function alignToByte() {
+ bufferSize = 0;
+ if (skipNextBit) {
+ position++;
+ skipNextBit = false;
+ }
+ }
+ function readCodingpasses() {
+ var value = readBits(1);
+ if (value == 0)
+ return 1;
+ value = (value << 1) | readBits(1);
+ if (value == 0x02)
+ return 2;
+ value = (value << 2) | readBits(2);
+ if (value <= 0x0E)
+ return (value & 0x03) + 3;
+ value = (value << 5) | readBits(5);
+ if (value <= 0x1FE)
+ return (value & 0x1F) + 6;
+ value = (value << 7) | readBits(7);
+ return (value & 0x7F) + 37;
+ }
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var packetsIterator = tile.packetsIterator;
+ while (position < dataLength) {
+ var packet = packetsIterator.nextPacket();
+ if (!readBits(1)) {
+ alignToByte();
+ continue;
+ }
+ var layerNumber = packet.layerNumber;
+ var queue = [];
+ for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
+ var codeblock = packet.codeblocks[i];
+ var precinct = codeblock.precinct;
+ var codeblockColumn = codeblock.cbx - precinct.cbxMin;
+ var codeblockRow = codeblock.cby - precinct.cbyMin;
+ var codeblockIncluded = false;
+ var firstTimeInclusion = false;
+ if ('included' in codeblock) {
+ codeblockIncluded = !!readBits(1);
+ } else {
+ // reading inclusion tree
+ var precinct = codeblock.precinct;
+ var inclusionTree, zeroBitPlanesTree;
+ if ('inclusionTree' in precinct) {
+ inclusionTree = precinct.inclusionTree;
+ } else {
+ // building inclusion and zero bit-planes trees
+ var width = precinct.cbxMax - precinct.cbxMin + 1;
+ var height = precinct.cbyMax - precinct.cbyMin + 1;
+ inclusionTree = new InclusionTree(width, height, layerNumber);
+ zeroBitPlanesTree = new TagTree(width, height);
+ precinct.inclusionTree = inclusionTree;
+ precinct.zeroBitPlanesTree = zeroBitPlanesTree;
+ }
+
+ if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
+ while (true) {
+ if (readBits(1)) {
+ var valueReady = !inclusionTree.nextLevel();
+ if (valueReady) {
+ codeblock.included = true;
+ codeblockIncluded = firstTimeInclusion = true;
+ break;
+ }
+ } else {
+ inclusionTree.incrementValue(layerNumber);
+ break;
+ }
+ }
+ }
+ }
+ if (!codeblockIncluded)
+ continue;
+ if (firstTimeInclusion) {
+ zeroBitPlanesTree = precinct.zeroBitPlanesTree;
+ zeroBitPlanesTree.reset(codeblockColumn, codeblockRow);
+ while (true) {
+ if (readBits(1)) {
+ var valueReady = !zeroBitPlanesTree.nextLevel();
+ if (valueReady)
+ break;
+ } else
+ zeroBitPlanesTree.incrementValue();
+ }
+ codeblock.zeroBitPlanes = zeroBitPlanesTree.value;
+ }
+ var codingpasses = readCodingpasses();
+ while (readBits(1))
+ codeblock.Lblock++;
+ var codingpassesLog2 = log2(codingpasses);
+ // rounding down log2
+ var bits = ((codingpasses < (1 << codingpassesLog2)) ?
+ codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock;
+ var codedDataLength = readBits(bits);
+ queue.push({
+ codeblock: codeblock,
+ codingpasses: codingpasses,
+ dataLength: codedDataLength
+ });
+ }
+ alignToByte();
+ while (queue.length > 0) {
+ var packetItem = queue.shift();
+ var codeblock = packetItem.codeblock;
+ if (!('data' in codeblock))
+ codeblock.data = [];
+ codeblock.data.push({
+ data: data,
+ start: offset + position,
+ end: offset + position + packetItem.dataLength,
+ codingpasses: packetItem.codingpasses
+ });
+ position += packetItem.dataLength;
+ }
+ }
+ return position;
+ }
+ function copyCoefficients(coefficients, x0, y0, width, height,
+ delta, mb, codeblocks, transformation) {
+ var r = 0.5; // formula (E-6)
+ for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
+ var codeblock = codeblocks[i];
+ var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
+ var blockHeight = codeblock.tby1_ - codeblock.tby0_;
+ if (blockWidth == 0 || blockHeight == 0)
+ continue;
+ if (!('data' in codeblock))
+ continue;
+
+ var bitModel, currentCodingpassType;
+ bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType,
+ codeblock.zeroBitPlanes);
+ currentCodingpassType = 2; // first bit plane starts from cleanup
+
+ // collect data
+ var data = codeblock.data, totalLength = 0, codingpasses = 0;
+ for (var q = 0, qq = data.length; q < qq; q++) {
+ var dataItem = data[q];
+ totalLength += dataItem.end - dataItem.start;
+ codingpasses += dataItem.codingpasses;
+ }
+ var encodedData = new Uint8Array(totalLength), k = 0;
+ for (var q = 0, qq = data.length; q < qq; q++) {
+ var dataItem = data[q];
+ var chunk = dataItem.data.subarray(dataItem.start, dataItem.end);
+ encodedData.set(chunk, k);
+ k += chunk.length;
+ }
+ // decoding the item
+ var decoder = new ArithmeticDecoder(encodedData, 0, totalLength);
+ bitModel.setDecoder(decoder);
+
+ for (var q = 0; q < codingpasses; q++) {
+ switch (currentCodingpassType) {
+ case 0:
+ bitModel.runSignificancePropogationPass();
+ break;
+ case 1:
+ bitModel.runMagnitudeRefinementPass();
+ break;
+ case 2:
+ bitModel.runCleanupPass();
+ break;
+ }
+ currentCodingpassType = (currentCodingpassType + 1) % 3;
+ }
+
+ var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
+ var position = 0;
+ for (var j = 0; j < blockHeight; j++) {
+ for (var k = 0; k < blockWidth; k++) {
+ var n = (bitModel.coefficentsSign[position] ? -1 : 1) *
+ bitModel.coefficentsMagnitude[position];
+ var nb = bitModel.bitsDecoded[position], correction;
+ if (transformation == 0 || mb > nb) {
+ // use r only if transformation is irreversible or
+ // not all bitplanes were decoded for reversible transformation
+ n += n < 0 ? n - r : n > 0 ? n + r : 0;
+ correction = 1 << (mb - nb);
+ } else
+ correction = 1;
+ coefficients[offset++] = n * correction * delta;
+ position++;
+ }
+ offset += width - blockWidth;
+ }
+ }
+ }
+ function transformTile(context, tile, c) {
+ var component = tile.components[c];
+ var codingStyleParameters = component.codingStyleParameters;
+ var quantizationParameters = component.quantizationParameters;
+ var decompositionLevelsCount =
+ codingStyleParameters.decompositionLevelsCount;
+ var spqcds = quantizationParameters.SPqcds;
+ var scalarExpounded = quantizationParameters.scalarExpounded;
+ var guardBits = quantizationParameters.guardBits;
+ var transformation = codingStyleParameters.transformation;
+ var precision = context.components[c].precision;
+
+ var subbandCoefficients = [];
+ var k = 0, b = 0;
+ for (var i = 0; i <= decompositionLevelsCount; i++) {
+ var resolution = component.resolutions[i];
+
+ for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
+ var mu, epsilon;
+ if (!scalarExpounded) {
+ // formula E-5
+ mu = spqcds[0].mu;
+ epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
+ } else {
+ mu = spqcds[b].mu;
+ epsilon = spqcds[b].epsilon;
+ }
+
+ var subband = resolution.subbands[j];
+ var width = subband.tbx1 - subband.tbx0;
+ var height = subband.tby1 - subband.tby0;
+ var gainLog2 = SubbandsGainLog2[subband.type];
+
+ // calulate quantization coefficient (Section E.1.1.1)
+ var delta = Math.pow(2, (precision + gainLog2) - epsilon) *
+ (1 + mu / 2048);
+ var mb = (guardBits + epsilon - 1);
+
+ var coefficients = new Float32Array(width * height);
+ copyCoefficients(coefficients, subband.tbx0, subband.tby0,
+ width, height, delta, mb, subband.codeblocks, transformation);
+
+ subbandCoefficients.push({
+ width: width,
+ height: height,
+ items: coefficients
+ });
+
+ b++;
+ }
+ }
+
+ var transformation = codingStyleParameters.transformation;
+ var transform = transformation == 0 ? new IrreversibleTransform() :
+ new ReversibleTransform();
+ var result = transform.calculate(subbandCoefficients,
+ component.tcx0, component.tcy0);
+ return {
+ left: component.tcx0,
+ top: component.tcy0,
+ width: result.width,
+ height: result.height,
+ items: result.items
+ };
+ }
+ function transformComponents(context) {
+ var siz = context.SIZ;
+ var components = context.components;
+ var componentsCount = siz.Csiz;
+ var resultImages = [];
+ for (var i = 0, ii = context.tiles.length; i < ii; i++) {
+ var tile = context.tiles[i];
+ var result = [];
+ for (var c = 0; c < componentsCount; c++) {
+ var image = transformTile(context, tile, c);
+ result.push(image);
+ }
+
+ // Section G.2.2 Inverse multi component transform
+ if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
+ var y0items = result[0].items;
+ var y1items = result[1].items;
+ var y2items = result[2].items;
+ for (var j = 0, jj = y0items.length; j < jj; j++) {
+ var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j];
+ var i1 = y0 - ((y2 + y1) >> 2);
+ y1items[j] = i1;
+ y0items[j] = y2 + i1;
+ y2items[j] = y1 + i1;
+ }
+ }
+
+ // Section G.1 DC level shifting to unsigned component values
+ for (var c = 0; c < componentsCount; c++) {
+ var component = components[c];
+ if (component.isSigned)
+ continue;
+
+ var offset = 1 << (component.precision - 1);
+ var tileImage = result[c];
+ var items = tileImage.items;
+ for (var j = 0, jj = items.length; j < jj; j++)
+ items[j] += offset;
+ }
+
+ // To simplify things: shift and clamp output to 8 bit unsigned
+ for (var c = 0; c < componentsCount; c++) {
+ var component = components[c];
+ var offset = component.isSigned ? 128 : 0;
+ var shift = component.precision - 8;
+ var tileImage = result[c];
+ var items = tileImage.items;
+ var data = new Uint8Array(items.length);
+ for (var j = 0, jj = items.length; j < jj; j++) {
+ var value = (items[j] >> shift) + offset;
+ data[j] = value < 0 ? 0 : value > 255 ? 255 : value;
+ }
+ result[c].items = data;
+ }
+
+ resultImages.push(result);
+ }
+ return resultImages;
+ }
+ function initializeTile(context, tileIndex) {
+ var siz = context.SIZ;
+ var componentsCount = siz.Csiz;
+ var tile = context.tiles[tileIndex];
+ var resultTiles = [];
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var qcdOrQcc = c in context.currentTile.QCC ?
+ context.currentTile.QCC[c] : context.currentTile.QCD;
+ component.quantizationParameters = qcdOrQcc;
+ var codOrCoc = c in context.currentTile.COC ?
+ context.currentTile.COC[c] : context.currentTile.COD;
+ component.codingStyleParameters = codOrCoc;
+ }
+ tile.codingStyleDefaultParameters = context.currentTile.COD;
+ }
+
+ // Section B.10.2 Tag trees
+ var TagTree = (function TagTreeClosure() {
+ function TagTree(width, height) {
+ var levelsLength = log2(Math.max(width, height)) + 1;
+ this.levels = [];
+ for (var i = 0; i < levelsLength; i++) {
+ var level = {
+ width: width,
+ height: height,
+ items: []
+ };
+ this.levels.push(level);
+ width = Math.ceil(width / 2);
+ height = Math.ceil(height / 2);
+ }
+ }
+ TagTree.prototype = {
+ reset: function TagTree_reset(i, j) {
+ var currentLevel = 0, value = 0;
+ while (currentLevel < this.levels.length) {
+ var level = this.levels[currentLevel];
+ var index = i + j * level.width;
+ if (index in level.items) {
+ value = level.items[index];
+ break;
+ }
+ level.index = index;
+ i >>= 1;
+ j >>= 1;
+ currentLevel++;
+ }
+ currentLevel--;
+ var level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ this.currentLevel = currentLevel;
+ delete this.value;
+ },
+ incrementValue: function TagTree_incrementValue() {
+ var level = this.levels[this.currentLevel];
+ level.items[level.index]++;
+ },
+ nextLevel: function TagTree_nextLevel() {
+ var currentLevel = this.currentLevel;
+ var level = this.levels[currentLevel];
+ var value = level.items[level.index];
+ currentLevel--;
+ if (currentLevel < 0) {
+ this.value = value;
+ return false;
+ }
+
+ this.currentLevel = currentLevel;
+ var level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ return true;
+ }
+ };
+ return TagTree;
+ })();
+
+ var InclusionTree = (function InclusionTreeClosure() {
+ function InclusionTree(width, height, defaultValue) {
+ var levelsLength = log2(Math.max(width, height)) + 1;
+ this.levels = [];
+ for (var i = 0; i < levelsLength; i++) {
+ var items = new Uint8Array(width * height);
+ for (var j = 0, jj = items.length; j < jj; j++)
+ items[j] = defaultValue;
+
+ var level = {
+ width: width,
+ height: height,
+ items: items
+ };
+ this.levels.push(level);
+
+ width = Math.ceil(width / 2);
+ height = Math.ceil(height / 2);
+ }
+ }
+ InclusionTree.prototype = {
+ reset: function InclusionTree_reset(i, j, stopValue) {
+ var currentLevel = 0;
+ while (currentLevel < this.levels.length) {
+ var level = this.levels[currentLevel];
+ var index = i + j * level.width;
+ level.index = index;
+ var value = level.items[index];
+
+ if (value == 0xFF)
+ break;
+
+ if (value > stopValue) {
+ this.currentLevel = currentLevel;
+ // already know about this one, propagating the value to top levels
+ this.propagateValues();
+ return false;
+ }
+
+ i >>= 1;
+ j >>= 1;
+ currentLevel++;
+ }
+ this.currentLevel = currentLevel - 1;
+ return true;
+ },
+ incrementValue: function InclusionTree_incrementValue(stopValue) {
+ var level = this.levels[this.currentLevel];
+ level.items[level.index] = stopValue + 1;
+ this.propagateValues();
+ },
+ propagateValues: function InclusionTree_propagateValues() {
+ var levelIndex = this.currentLevel;
+ var level = this.levels[levelIndex];
+ var currentValue = level.items[level.index];
+ while (--levelIndex >= 0) {
+ var level = this.levels[levelIndex];
+ level.items[level.index] = currentValue;
+ }
+ },
+ nextLevel: function InclusionTree_nextLevel() {
+ var currentLevel = this.currentLevel;
+ var level = this.levels[currentLevel];
+ var value = level.items[level.index];
+ level.items[level.index] = 0xFF;
+ currentLevel--;
+ if (currentLevel < 0)
+ return false;
+
+ this.currentLevel = currentLevel;
+ var level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ return true;
+ }
+ };
+ return InclusionTree;
+ })();
+
+ // Implements C.3. Arithmetic decoding procedures
+ var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
+ var QeTable = [
+ {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
+ {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
+ {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
+ {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
+ {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
+ {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
+ {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
+ {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
+ {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
+ {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
+ {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
+ {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
+ {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
+ {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
+ {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
+ {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
+ {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
+ {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
+ {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
+ {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
+ {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
+ {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
+ {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
+ {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
+ {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
+ {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
+ {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
+ {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
+ {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
+ {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
+ {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
+ {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
+ {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
+ {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
+ {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
+ {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
+ {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
+ {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
+ {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
+ {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
+ {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
+ {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
+ {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
+ {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
+ {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
+ {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
+ {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
+ ];
+
+ function ArithmeticDecoder(data, start, end) {
+ this.data = data;
+ this.bp = start;
+ this.dataEnd = end;
+
+ this.chigh = data[start];
+ this.clow = 0;
+
+ this.byteIn();
+
+ this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
+ this.clow = (this.clow << 7) & 0xFFFF;
+ this.ct -= 7;
+ this.a = 0x8000;
+ }
+
+ ArithmeticDecoder.prototype = {
+ byteIn: function ArithmeticDecoder_byteIn() {
+ var data = this.data;
+ var bp = this.bp;
+ if (data[bp] == 0xFF) {
+ var b1 = data[bp + 1];
+ if (b1 > 0x8F) {
+ this.clow += 0xFF00;
+ this.ct = 8;
+ } else {
+ bp++;
+ this.clow += (data[bp] << 9);
+ this.ct = 7;
+ this.bp = bp;
+ }
+ } else {
+ bp++;
+ this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
+ this.ct = 8;
+ this.bp = bp;
+ }
+ if (this.clow > 0xFFFF) {
+ this.chigh += (this.clow >> 16);
+ this.clow &= 0xFFFF;
+ }
+ },
+ readBit: function ArithmeticDecoder_readBit(cx) {
+ var qeIcx = QeTable[cx.index].qe;
+ this.a -= qeIcx;
+
+ if (this.chigh < qeIcx) {
+ var d = this.exchangeLps(cx);
+ this.renormD();
+ return d;
+ } else {
+ this.chigh -= qeIcx;
+ if ((this.a & 0x8000) == 0) {
+ var d = this.exchangeMps(cx);
+ this.renormD();
+ return d;
+ } else {
+ return cx.mps;
+ }
+ }
+ },
+ renormD: function ArithmeticDecoder_renormD() {
+ do {
+ if (this.ct == 0)
+ this.byteIn();
+
+ this.a <<= 1;
+ this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
+ this.clow = (this.clow << 1) & 0xFFFF;
+ this.ct--;
+ } while ((this.a & 0x8000) == 0);
+ },
+ exchangeMps: function ArithmeticDecoder_exchangeMps(cx) {
+ var d;
+ var qeTableIcx = QeTable[cx.index];
+ if (this.a < qeTableIcx.qe) {
+ d = 1 - cx.mps;
+
+ if (qeTableIcx.switchFlag == 1) {
+ cx.mps = 1 - cx.mps;
+ }
+ cx.index = qeTableIcx.nlps;
+ } else {
+ d = cx.mps;
+ cx.index = qeTableIcx.nmps;
+ }
+ return d;
+ },
+ exchangeLps: function ArithmeticDecoder_exchangeLps(cx) {
+ var d;
+ var qeTableIcx = QeTable[cx.index];
+ if (this.a < qeTableIcx.qe) {
+ this.a = qeTableIcx.qe;
+ d = cx.mps;
+ cx.index = qeTableIcx.nmps;
+ } else {
+ this.a = qeTableIcx.qe;
+ d = 1 - cx.mps;
+
+ if (qeTableIcx.switchFlag == 1) {
+ cx.mps = 1 - cx.mps;
+ }
+ cx.index = qeTableIcx.nlps;
+ }
+ return d;
+ }
+ };
+
+ return ArithmeticDecoder;
+ })();
+
+ // Section D. Coefficient bit modeling
+ var BitModel = (function BitModelClosure() {
+ // Table D-1
+ // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4),
+ // vv - sum of Vi (0..2), and hh - sum of Hi (0..2)
+ var LLAndLHContextsLabel = new Uint8Array([
+ 0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4,
+ 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6,
+ 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8
+ ]);
+ var HLContextLabel = new Uint8Array([
+ 0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8,
+ 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3,
+ 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8
+ ]);
+ var HHContextLabel = new Uint8Array([
+ 0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5,
+ 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8,
+ 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8
+ ]);
+
+ // Table D-2
+ function calcSignContribution(significance0, sign0, significance1, sign1) {
+ if (significance1) {
+ if (!sign1)
+ return significance0 ? (!sign0 ? 1 : 0) : 1;
+ else
+ return significance0 ? (!sign0 ? 0 : -1) : -1;
+ } else
+ return significance0 ? (!sign0 ? 1 : -1) : 0;
+ }
+ // Table D-3
+ var SignContextLabels = [
+ {contextLabel: 13, xorBit: 0},
+ {contextLabel: 12, xorBit: 0},
+ {contextLabel: 11, xorBit: 0},
+ {contextLabel: 10, xorBit: 0},
+ {contextLabel: 9, xorBit: 0},
+ {contextLabel: 10, xorBit: 1},
+ {contextLabel: 11, xorBit: 1},
+ {contextLabel: 12, xorBit: 1},
+ {contextLabel: 13, xorBit: 1}
+ ];
+
+ function BitModel(width, height, subband, zeroBitPlanes) {
+ this.width = width;
+ this.height = height;
+
+ this.contextLabelTable = subband == 'HH' ? HHContextLabel :
+ subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel;
+
+ var coefficientCount = width * height;
+
+ // coefficients outside the encoding region treated as insignificant
+ // add border state cells for significanceState
+ this.neighborsSignificance = new Uint8Array(coefficientCount);
+ this.coefficentsSign = new Uint8Array(coefficientCount);
+ this.coefficentsMagnitude = new Uint32Array(coefficientCount);
+ this.processingFlags = new Uint8Array(coefficientCount);
+
+ var bitsDecoded = new Uint8Array(this.width * this.height);
+ for (var i = 0, ii = bitsDecoded.length; i < ii; i++)
+ bitsDecoded[i] = zeroBitPlanes;
+ this.bitsDecoded = bitsDecoded;
+
+ this.reset();
+ }
+
+ BitModel.prototype = {
+ setDecoder: function BitModel_setDecoder(decoder) {
+ this.decoder = decoder;
+ },
+ reset: function BitModel_reset() {
+ this.uniformContext = {index: 46, mps: 0};
+ this.runLengthContext = {index: 3, mps: 0};
+ this.contexts = [];
+ this.contexts.push({index: 4, mps: 0});
+ for (var i = 1; i <= 16; i++)
+ this.contexts.push({index: 0, mps: 0});
+ },
+ setNeighborsSignificance:
+ function BitModel_setNeighborsSignificance(row, column) {
+ var neighborsSignificance = this.neighborsSignificance;
+ var width = this.width, height = this.height;
+ var index = row * width + column;
+ if (row > 0) {
+ if (column > 0)
+ neighborsSignificance[index - width - 1] += 0x10;
+ if (column + 1 < width)
+ neighborsSignificance[index - width + 1] += 0x10;
+ neighborsSignificance[index - width] += 0x04;
+ }
+ if (row + 1 < height) {
+ if (column > 0)
+ neighborsSignificance[index + width - 1] += 0x10;
+ if (column + 1 < width)
+ neighborsSignificance[index + width + 1] += 0x10;
+ neighborsSignificance[index + width] += 0x04;
+ }
+ if (column > 0)
+ neighborsSignificance[index - 1] += 0x01;
+ if (column + 1 < width)
+ neighborsSignificance[index + 1] += 0x01;
+ neighborsSignificance[index] |= 0x80;
+ },
+ runSignificancePropogationPass:
+ function BitModel_runSignificancePropogationPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var contextLabels = this.contextLabels;
+ var neighborsSignificance = this.neighborsSignificance;
+ var processingFlags = this.processingFlags;
+ var contexts = this.contexts;
+ var labels = this.contextLabelTable;
+ var bitsDecoded = this.bitsDecoded;
+ // clear processed flag
+ var processedInverseMask = ~1;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ for (var q = 0, qq = width * height; q < qq; q++)
+ processingFlags[q] &= processedInverseMask;
+
+ for (var i0 = 0; i0 < height; i0 += 4) {
+ for (var j = 0; j < width; j++) {
+ var index = i0 * width + j;
+ for (var i1 = 0; i1 < 4; i1++, index += width) {
+ var i = i0 + i1;
+ if (i >= height)
+ break;
+
+ if (coefficentsMagnitude[index] || !neighborsSignificance[index])
+ continue;
+
+ var contextLabel = labels[neighborsSignificance[index]];
+ var cx = contexts[contextLabel];
+ var decision = decoder.readBit(cx);
+ if (decision) {
+ var sign = this.decodeSignBit(i, j);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j);
+ processingFlags[index] |= firstMagnitudeBitMask;
+ }
+ bitsDecoded[index]++;
+ processingFlags[index] |= processedMask;
+ }
+ }
+ }
+ },
+ decodeSignBit: function BitModel_decodeSignBit(row, column) {
+ var width = this.width, height = this.height;
+ var index = row * width + column;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var horizontalContribution = calcSignContribution(
+ column > 0 && coefficentsMagnitude[index - 1],
+ coefficentsSign[index - 1],
+ column + 1 < width && coefficentsMagnitude[index + 1],
+ coefficentsSign[index + 1]);
+ var verticalContribution = calcSignContribution(
+ row > 0 && coefficentsMagnitude[index - width],
+ coefficentsSign[index - width],
+ row + 1 < height && coefficentsMagnitude[index + width],
+ coefficentsSign[index + width]);
+
+ var contextLabelAndXor = SignContextLabels[
+ 3 * (1 - horizontalContribution) + (1 - verticalContribution)];
+ var contextLabel = contextLabelAndXor.contextLabel;
+ var cx = this.contexts[contextLabel];
+ var decoded = this.decoder.readBit(cx);
+ return decoded ^ contextLabelAndXor.xorBit;
+ },
+ runMagnitudeRefinementPass:
+ function BitModel_runMagnitudeRefinementPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var neighborsSignificance = this.neighborsSignificance;
+ var contexts = this.contexts;
+ var bitsDecoded = this.bitsDecoded;
+ var processingFlags = this.processingFlags;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ for (var i0 = 0; i0 < height; i0 += 4) {
+ for (var j = 0; j < width; j++) {
+ for (var i1 = 0; i1 < 4; i1++) {
+ var i = i0 + i1;
+ if (i >= height)
+ break;
+ var index = i * width + j;
+
+ // significant but not those that have just become
+ if (!coefficentsMagnitude[index] ||
+ (processingFlags[index] & processedMask) != 0)
+ continue;
+
+ var contextLabel = 16;
+ if ((processingFlags[index] &
+ firstMagnitudeBitMask) != 0) {
+ processingFlags[i * width + j] ^= firstMagnitudeBitMask;
+ // first refinement
+ var significance = neighborsSignificance[index];
+ var sumOfSignificance = (significance & 3) +
+ ((significance >> 2) & 3) + ((significance >> 4) & 7);
+ contextLabel = sumOfSignificance >= 1 ? 15 : 14;
+ }
+
+ var cx = contexts[contextLabel];
+ var bit = decoder.readBit(cx);
+ coefficentsMagnitude[index] =
+ (coefficentsMagnitude[index] << 1) | bit;
+ bitsDecoded[index]++;
+ processingFlags[index] |= processedMask;
+ }
+ }
+ }
+ },
+ runCleanupPass: function BitModel_runCleanupPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var neighborsSignificance = this.neighborsSignificance;
+ var significanceState = this.significanceState;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var contexts = this.contexts;
+ var labels = this.contextLabelTable;
+ var bitsDecoded = this.bitsDecoded;
+ var processingFlags = this.processingFlags;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ var oneRowDown = width;
+ var twoRowsDown = width * 2;
+ var threeRowsDown = width * 3;
+ for (var i0 = 0; i0 < height; i0 += 4) {
+ for (var j = 0; j < width; j++) {
+ var index0 = i0 * width + j;
+ // using the property: labels[neighborsSignificance[index]] == 0
+ // when neighborsSignificance[index] == 0
+ var allEmpty = i0 + 3 < height &&
+ processingFlags[index0] == 0 &&
+ processingFlags[index0 + oneRowDown] == 0 &&
+ processingFlags[index0 + twoRowsDown] == 0 &&
+ processingFlags[index0 + threeRowsDown] == 0 &&
+ neighborsSignificance[index0] == 0 &&
+ neighborsSignificance[index0 + oneRowDown] == 0 &&
+ neighborsSignificance[index0 + twoRowsDown] == 0 &&
+ neighborsSignificance[index0 + threeRowsDown] == 0;
+ var i1 = 0, index = index0;
+ var cx, i;
+ if (allEmpty) {
+ cx = this.runLengthContext;
+ var hasSignificantCoefficent = decoder.readBit(cx);
+ if (!hasSignificantCoefficent) {
+ bitsDecoded[index0]++;
+ bitsDecoded[index0 + oneRowDown]++;
+ bitsDecoded[index0 + twoRowsDown]++;
+ bitsDecoded[index0 + threeRowsDown]++;
+ continue; // next column
+ }
+ cx = this.uniformContext;
+ i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx);
+ i = i0 + i1;
+ index += i1 * width;
+
+ var sign = this.decodeSignBit(i, j);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j);
+ processingFlags[index] |= firstMagnitudeBitMask;
+
+ index = index0;
+ for (var i2 = i0; i2 <= i; i2++, index += width)
+ bitsDecoded[index]++;
+
+ i1++;
+ }
+ for (; i1 < 4; i1++, index += width) {
+ i = i0 + i1;
+ if (i >= height)
+ break;
+
+ if (coefficentsMagnitude[index] ||
+ (processingFlags[index] & processedMask) != 0)
+ continue;
+
+ var contextLabel = labels[neighborsSignificance[index]];
+ cx = contexts[contextLabel];
+ var decision = decoder.readBit(cx);
+ if (decision == 1) {
+ var sign = this.decodeSignBit(i, j);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j);
+ processingFlags[index] |= firstMagnitudeBitMask;
+ }
+ bitsDecoded[index]++;
+ }
+ }
+ }
+ }
+ };
+
+ return BitModel;
+ })();
+
+ // Section F, Discrete wavelet transofrmation
+ var Transform = (function TransformClosure() {
+ function Transform() {
+ }
+ Transform.prototype.calculate =
+ function transformCalculate(subbands, u0, v0) {
+ var ll = subbands[0];
+ for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) {
+ ll = this.iterate(ll, subbands[i], subbands[i + 1],
+ subbands[i + 2], u0, v0);
+ }
+ return ll;
+ };
+ Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh,
+ u0, v0) {
+ var llWidth = ll.width, llHeight = ll.height, llItems = ll.items;
+ var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items;
+ var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items;
+ var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items;
+
+ // Section F.3.3 interleave
+ var width = llWidth + hlWidth;
+ var height = llHeight + lhHeight;
+ var items = new Float32Array(width * height);
+ for (var i = 0, ii = llHeight; i < ii; i++) {
+ var k = i * llWidth, l = i * 2 * width;
+ for (var j = 0, jj = llWidth; j < jj; j++, k++, l += 2)
+ items[l] = llItems[k];
+ }
+ for (var i = 0, ii = hlHeight; i < ii; i++) {
+ var k = i * hlWidth, l = i * 2 * width + 1;
+ for (var j = 0, jj = hlWidth; j < jj; j++, k++, l += 2)
+ items[l] = hlItems[k];
+ }
+ for (var i = 0, ii = lhHeight; i < ii; i++) {
+ var k = i * lhWidth, l = (i * 2 + 1) * width;
+ for (var j = 0, jj = lhWidth; j < jj; j++, k++, l += 2)
+ items[l] = lhItems[k];
+ }
+ for (var i = 0, ii = hhHeight; i < ii; i++) {
+ var k = i * hhWidth, l = (i * 2 + 1) * width + 1;
+ for (var j = 0, jj = hhWidth; j < jj; j++, k++, l += 2)
+ items[l] = hhItems[k];
+ }
+
+ var bufferPadding = 4;
+ var bufferLength = new Float32Array(Math.max(width, height) +
+ 2 * bufferPadding);
+ var buffer = new Float32Array(bufferLength);
+ var bufferOut = new Float32Array(bufferLength);
+
+ // Section F.3.4 HOR_SR
+ for (var v = 0; v < height; v++) {
+ if (width == 1) {
+ // if width = 1, when u0 even keep items as is, when odd divide by 2
+ if ((u0 % 1) != 0) {
+ items[v * width] /= 2;
+ }
+ continue;
+ }
+
+ var k = v * width;
+ var l = bufferPadding;
+ for (var u = 0; u < width; u++, k++, l++)
+ buffer[l] = items[k];
+
+ // Section F.3.7 extending... using max extension of 4
+ var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
+ var i2 = bufferPadding + width - 2, j2 = bufferPadding + width;
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+
+ this.filter(buffer, bufferPadding, width, u0, bufferOut);
+
+ k = v * width;
+ l = bufferPadding;
+ for (var u = 0; u < width; u++, k++, l++)
+ items[k] = bufferOut[l];
+ }
+
+ // Section F.3.5 VER_SR
+ for (var u = 0; u < width; u++) {
+ if (height == 1) {
+ // if height = 1, when v0 even keep items as is, when odd divide by 2
+ if ((v0 % 1) != 0) {
+ items[u] /= 2;
+ }
+ continue;
+ }
+
+ var k = u;
+ var l = bufferPadding;
+ for (var v = 0; v < height; v++, k += width, l++)
+ buffer[l] = items[k];
+
+ // Section F.3.7 extending... using max extension of 4
+ var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
+ var i2 = bufferPadding + height - 2, j2 = bufferPadding + height;
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+
+ this.filter(buffer, bufferPadding, height, v0, bufferOut);
+
+ k = u;
+ l = bufferPadding;
+ for (var v = 0; v < height; v++, k += width, l++)
+ items[k] = bufferOut[l];
+ }
+ return {
+ width: width,
+ height: height,
+ items: items
+ };
+ };
+ return Transform;
+ })();
+
+ // Section 3.8.2 Irreversible 9-7 filter
+ var IrreversibleTransform = (function IrreversibleTransformClosure() {
+ function IrreversibleTransform() {
+ Transform.call(this);
+ }
+
+ IrreversibleTransform.prototype = Object.create(Transform.prototype);
+ IrreversibleTransform.prototype.filter =
+ function irreversibleTransformFilter(y, offset, length, i0, x) {
+ var i0_ = Math.floor(i0 / 2);
+ var i1_ = Math.floor((i0 + length) / 2);
+ var offset_ = offset - (i0 % 1);
+
+ var alpha = -1.586134342059924;
+ var beta = -0.052980118572961;
+ var gamma = 0.882911075530934;
+ var delta = 0.443506852043971;
+ var K = 1.230174104914001;
+ var K_ = 1 / K;
+
+ // step 1
+ var j = offset_ - 2;
+ for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
+ x[j] = K * y[j];
+
+ // step 2
+ var j = offset_ - 3;
+ for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2)
+ x[j] = K_ * y[j];
+
+ // step 3
+ var j = offset_ - 2;
+ for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
+ x[j] -= delta * (x[j - 1] + x[j + 1]);
+
+ // step 4
+ var j = offset_ - 1;
+ for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2)
+ x[j] -= gamma * (x[j - 1] + x[j + 1]);
+
+ // step 5
+ var j = offset_;
+ for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2)
+ x[j] -= beta * (x[j - 1] + x[j + 1]);
+
+ // step 6
+ var j = offset_ + 1;
+ for (var n = i0_, nn = i1_; n < nn; n++, j += 2)
+ x[j] -= alpha * (x[j - 1] + x[j + 1]);
+ };
+
+ return IrreversibleTransform;
+ })();
+
+ // Section 3.8.1 Reversible 5-3 filter
+ var ReversibleTransform = (function ReversibleTransformClosure() {
+ function ReversibleTransform() {
+ Transform.call(this);
+ }
+
+ ReversibleTransform.prototype = Object.create(Transform.prototype);
+ ReversibleTransform.prototype.filter =
+ function reversibleTransformFilter(y, offset, length, i0, x) {
+ var i0_ = Math.floor(i0 / 2);
+ var i1_ = Math.floor((i0 + length) / 2);
+ var offset_ = offset - (i0 % 1);
+
+ for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2)
+ x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4);
+
+ for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2)
+ x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2);
+ };
+
+ return ReversibleTransform;
+ })();
+
+ return JpxImage;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var bidi = PDFJS.bidi = (function bidiClosure() {
+ // Character types for symbols from 0000 to 00FF.
+ var baseTypes = [
+ 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS',
+ 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
+ 'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON',
+ 'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN',
+ 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON',
+ 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON',
+ 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN',
+ 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
+ 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
+ 'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON',
+ 'ON', 'ON', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON',
+ 'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
+ 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L'
+ ];
+
+ // Character types for symbols from 0600 to 06FF
+ var arabicTypes = [
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM',
+ 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN',
+ 'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM',
+ 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'ON', 'NSM',
+ 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
+ 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL'
+ ];
+
+ function isOdd(i) {
+ return (i & 1) != 0;
+ }
+
+ function isEven(i) {
+ return (i & 1) == 0;
+ }
+
+ function findUnequal(arr, start, value) {
+ var j;
+ for (var j = start, jj = arr.length; j < jj; ++j) {
+ if (arr[j] != value)
+ return j;
+ }
+ return j;
+ }
+
+ function setValues(arr, start, end, value) {
+ for (var j = start; j < end; ++j) {
+ arr[j] = value;
+ }
+ }
+
+ function reverseValues(arr, start, end) {
+ for (var i = start, j = end - 1; i < j; ++i, --j) {
+ var temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+ }
+
+ function mirrorGlyphs(c) {
+ /*
+ # BidiMirroring-1.txt
+ 0028; 0029 # LEFT PARENTHESIS
+ 0029; 0028 # RIGHT PARENTHESIS
+ 003C; 003E # LESS-THAN SIGN
+ 003E; 003C # GREATER-THAN SIGN
+ 005B; 005D # LEFT SQUARE BRACKET
+ 005D; 005B # RIGHT SQUARE BRACKET
+ 007B; 007D # LEFT CURLY BRACKET
+ 007D; 007B # RIGHT CURLY BRACKET
+ 00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ */
+ switch (c) {
+ case '(':
+ return ')';
+ case ')':
+ return '(';
+ case '<':
+ return '>';
+ case '>':
+ return '<';
+ case ']':
+ return '[';
+ case '[':
+ return ']';
+ case '}':
+ return '{';
+ case '{':
+ return '}';
+ case '\u00AB':
+ return '\u00BB';
+ case '\u00BB':
+ return '\u00AB';
+ default:
+ return c;
+ }
+ }
+
+ function bidi(text, startLevel) {
+ var str = text.str;
+ var strLength = str.length;
+ if (strLength == 0)
+ return str;
+
+ // get types, fill arrays
+
+ var chars = [];
+ var types = [];
+ var oldtypes = [];
+ var numBidi = 0;
+
+ for (var i = 0; i < strLength; ++i) {
+ chars[i] = str.charAt(i);
+
+ var charCode = str.charCodeAt(i);
+ var charType = 'L';
+ if (charCode <= 0x00ff)
+ charType = baseTypes[charCode];
+ else if (0x0590 <= charCode && charCode <= 0x05f4)
+ charType = 'R';
+ else if (0x0600 <= charCode && charCode <= 0x06ff)
+ charType = arabicTypes[charCode & 0xff];
+ else if (0x0700 <= charCode && charCode <= 0x08AC)
+ charType = 'AL';
+
+ if (charType == 'R' || charType == 'AL' || charType == 'AN')
+ numBidi++;
+
+ oldtypes[i] = types[i] = charType;
+ }
+
+ // detect the bidi method
+ // if there are no rtl characters then no bidi needed
+ // if less than 30% chars are rtl then string is primarily ltr
+ // if more than 30% chars are rtl then string is primarily rtl
+ if (numBidi == 0) {
+ text.direction = 'ltr';
+ return str;
+ }
+
+ if (startLevel == -1) {
+ if ((strLength / numBidi) < 0.3) {
+ text.direction = 'ltr';
+ startLevel = 0;
+ } else {
+ text.direction = 'rtl';
+ startLevel = 1;
+ }
+ }
+
+ var levels = [];
+
+ for (var i = 0; i < strLength; ++i) {
+ levels[i] = startLevel;
+ }
+
+ /*
+ X1-X10: skip most of this, since we are NOT doing the embeddings.
+ */
+
+ var e = isOdd(startLevel) ? 'R' : 'L';
+ var sor = e;
+ var eor = sor;
+
+ /*
+ W1. Examine each non-spacing mark (NSM) in the level run, and change the
+ type of the NSM to the type of the previous character. If the NSM is at the
+ start of the level run, it will get the type of sor.
+ */
+
+ var lastType = sor;
+ for (var i = 0; i < strLength; ++i) {
+ if (types[i] == 'NSM')
+ types[i] = lastType;
+ else
+ lastType = types[i];
+ }
+
+ /*
+ W2. Search backwards from each instance of a European number until the
+ first strong type (R, L, AL, or sor) is found. If an AL is found, change
+ the type of the European number to Arabic number.
+ */
+
+ var lastType = sor;
+ for (var i = 0; i < strLength; ++i) {
+ var t = types[i];
+ if (t == 'EN')
+ types[i] = (lastType == 'AL') ? 'AN' : 'EN';
+ else if (t == 'R' || t == 'L' || t == 'AL')
+ lastType = t;
+ }
+
+ /*
+ W3. Change all ALs to R.
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ var t = types[i];
+ if (t == 'AL')
+ types[i] = 'R';
+ }
+
+ /*
+ W4. A single European separator between two European numbers changes to a
+ European number. A single common separator between two numbers of the same
+ type changes to that type:
+ */
+
+ for (var i = 1; i < strLength - 1; ++i) {
+ if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN')
+ types[i] = 'EN';
+ if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') &&
+ types[i + 1] == types[i - 1])
+ types[i] = types[i - 1];
+ }
+
+ /*
+ W5. A sequence of European terminators adjacent to European numbers changes
+ to all European numbers:
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ if (types[i] == 'EN') {
+ // do before
+ for (var j = i - 1; j >= 0; --j) {
+ if (types[j] != 'ET')
+ break;
+ types[j] = 'EN';
+ }
+ // do after
+ for (var j = i + 1; j < strLength; --j) {
+ if (types[j] != 'ET')
+ break;
+ types[j] = 'EN';
+ }
+ }
+ }
+
+ /*
+ W6. Otherwise, separators and terminators change to Other Neutral:
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ var t = types[i];
+ if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS')
+ types[i] = 'ON';
+ }
+
+ /*
+ W7. Search backwards from each instance of a European number until the
+ first strong type (R, L, or sor) is found. If an L is found, then change
+ the type of the European number to L.
+ */
+
+ var lastType = sor;
+ for (var i = 0; i < strLength; ++i) {
+ var t = types[i];
+ if (t == 'EN')
+ types[i] = (lastType == 'L') ? 'L' : 'EN';
+ else if (t == 'R' || t == 'L')
+ lastType = t;
+ }
+
+ /*
+ N1. A sequence of neutrals takes the direction of the surrounding strong
+ text if the text on both sides has the same direction. European and Arabic
+ numbers are treated as though they were R. Start-of-level-run (sor) and
+ end-of-level-run (eor) are used at level run boundaries.
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ if (types[i] == 'ON') {
+ var end = findUnequal(types, i + 1, 'ON');
+ var before = sor;
+ if (i > 0)
+ before = types[i - 1];
+ var after = eor;
+ if (end + 1 < strLength)
+ after = types[end + 1];
+ if (before != 'L')
+ before = 'R';
+ if (after != 'L')
+ after = 'R';
+ if (before == after)
+ setValues(types, i, end, before);
+ i = end - 1; // reset to end (-1 so next iteration is ok)
+ }
+ }
+
+ /*
+ N2. Any remaining neutrals take the embedding direction.
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ if (types[i] == 'ON')
+ types[i] = e;
+ }
+
+ /*
+ I1. For all characters with an even (left-to-right) embedding direction,
+ those of type R go up one level and those of type AN or EN go up two
+ levels.
+ I2. For all characters with an odd (right-to-left) embedding direction,
+ those of type L, EN or AN go up one level.
+ */
+
+ for (var i = 0; i < strLength; ++i) {
+ var t = types[i];
+ if (isEven(levels[i])) {
+ if (t == 'R') {
+ levels[i] += 1;
+ } else if (t == 'AN' || t == 'EN') {
+ levels[i] += 2;
+ }
+ } else { // isOdd, so
+ if (t == 'L' || t == 'AN' || t == 'EN') {
+ levels[i] += 1;
+ }
+ }
+ }
+
+ /*
+ L1. On each line, reset the embedding level of the following characters to
+ the paragraph embedding level:
+
+ segment separators,
+ paragraph separators,
+ any sequence of whitespace characters preceding a segment separator or
+ paragraph separator, and any sequence of white space characters at the end
+ of the line.
+ */
+
+ // don't bother as text is only single line
+
+ /*
+ L2. From the highest level found in the text to the lowest odd level on
+ each line, reverse any contiguous sequence of characters that are at that
+ level or higher.
+ */
+
+ // find highest level & lowest odd level
+
+ var highestLevel = -1;
+ var lowestOddLevel = 99;
+ for (var i = 0, ii = levels.length; i < ii; ++i) {
+ var level = levels[i];
+ if (highestLevel < level)
+ highestLevel = level;
+ if (lowestOddLevel > level && isOdd(level))
+ lowestOddLevel = level;
+ }
+
+ // now reverse between those limits
+
+ for (var level = highestLevel; level >= lowestOddLevel; --level) {
+ // find segments to reverse
+ var start = -1;
+ for (var i = 0, ii = levels.length; i < ii; ++i) {
+ if (levels[i] < level) {
+ if (start >= 0) {
+ reverseValues(chars, start, i);
+ start = -1;
+ }
+ } else if (start < 0) {
+ start = i;
+ }
+ }
+ if (start >= 0) {
+ reverseValues(chars, start, levels.length);
+ }
+ }
+
+ /*
+ L3. Combining marks applied to a right-to-left base character will at this
+ point precede their base character. If the rendering engine expects them to
+ follow the base characters in the final display process, then the ordering
+ of the marks and the base character must be reversed.
+ */
+
+ // don't bother for now
+
+ /*
+ L4. A character that possesses the mirrored property as specified by
+ Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved
+ directionality of that character is R.
+ */
+
+ // don't mirror as characters are already mirrored in the pdf
+
+ // Finally, return string
+
+ var result = '';
+ for (var i = 0, ii = chars.length; i < ii; ++i) {
+ var ch = chars[i];
+ if (ch != '<' && ch != '>')
+ result += ch;
+ }
+ return result;
+ }
+
+ return bidi;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var Metadata = PDFJS.Metadata = (function MetadataClosure() {
+ function Metadata(meta) {
+ if (typeof meta === 'string') {
+ var parser = new DOMParser();
+ meta = parser.parseFromString(meta, 'application/xml');
+ } else if (!(meta instanceof Document)) {
+ error('Metadata: Invalid metadata object');
+ }
+
+ this.metaDocument = meta;
+ this.metadata = {};
+ this.parse();
+ }
+
+ Metadata.prototype = {
+ parse: function Metadata_parse() {
+ var doc = this.metaDocument;
+ var rdf = doc.documentElement;
+
+ if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in <xmpmeta>
+ rdf = rdf.firstChild;
+ while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf')
+ rdf = rdf.nextSibling;
+ }
+
+ var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null;
+ if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes())
+ return;
+
+ var childNodes = rdf.childNodes, desc, namespace, entries, entry;
+
+ for (var i = 0, length = childNodes.length; i < length; i++) {
+ desc = childNodes[i];
+ if (desc.nodeName.toLowerCase() !== 'rdf:description')
+ continue;
+
+ entries = [];
+ for (var ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
+ if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text')
+ entries.push(desc.childNodes[ii]);
+ }
+
+ for (ii = 0, iLength = entries.length; ii < iLength; ii++) {
+ var entry = entries[ii];
+ var name = entry.nodeName.toLowerCase();
+ this.metadata[name] = entry.textContent.trim();
+ }
+ }
+ },
+
+ get: function Metadata_get(name) {
+ return this.metadata[name] || null;
+ },
+
+ has: function Metadata_has(name) {
+ return typeof this.metadata[name] !== 'undefined';
+ }
+ };
+
+ return Metadata;
+})();
+
+}).call((typeof window === 'undefined') ? this : window);
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.min.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.min.js
new file mode 100644
index 0000000..abdac42
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/pdf.min.js
@@ -0,0 +1,1458 @@
+var PDFJS={};
+(function(){var zb,Ab,Ub,rb,Bb,Vb,Ua,Va,gb,hb,sb,Cb,Db;function Wa(d){console&&console.log?console.log(d):print&&print(d)}function Y(d){Eb>=Fb&&Wa("Warning: "+d)}function r(d){Wa("Error: "+d);var a;try{throw Error();}catch(b){a=b.stack?b.stack.split("\n").slice(2).join("\n"):""}Wa(a);throw Error(d);}function ea(d){Eb>=Bc&&Wa("TODO: "+d)}function fa(d,a){d||r("Malformed PDF: "+a)}function ca(d,a,b){Object.defineProperty(d,a,{value:b,enumerable:!0,configurable:!0,writable:!1});return b}fun [...]
+"",b=d.length,c=0;c<b;++c)a+=String.fromCharCode(d[c]);return a}function Ma(d){for(var a=d.length,b=new Uint8Array(a),c=0;c<a;++c)b[c]=d.charCodeAt(c)&255;return b}function ta(d){var a,b=d.length,c="";if("\u00fe"===d[0]&&"\u00ff"===d[1])for(a=2;a<b;a+=2)c+=String.fromCharCode(d.charCodeAt(a)<<8|d.charCodeAt(a+1));else for(a=0;a<b;++a)var e=Cc[d.charCodeAt(a)],c=c+(e?String.fromCharCode(e):d.charAt(a));return c}function ua(d){return"boolean"==typeof d}function E(d){return"number"==typeof [...]
+d}function sa(d){return"number"==typeof d}function Na(d){return"string"==typeof d}function T(d){return d instanceof wa}function aa(d,a){return d instanceof Ja&&(!a||d.cmd==a)}function U(d,a){return d instanceof xa&&(!a||d.get("Type").name==a)}function R(d){return d instanceof Array}function oa(d){return"object"==typeof d&&null!=d&&"getChar"in d}function ya(d){return d instanceof Gb}function ub(d,a){var b=document.createElement("canvas");b.width=d;b.height=a;return b}function Dc(d){d.mozC [...]
+(d._originalSave=d.save,d._originalRestore=d.restore,d._originalRotate=d.rotate,d._originalScale=d.scale,d._originalTranslate=d.translate,d._originalTransform=d.transform,d._transformMatrix=[1,0,0,1,0,0],d._transformStack=[],Object.defineProperty(d,"mozCurrentTransform",{get:function(){return this._transformMatrix}}),Object.defineProperty(d,"mozCurrentTransformInverse",{get:function(){var a=this._transformMatrix,b=a[0],c=a[1],e=a[2],f=a[3],g=a[4],a=a[5],i=b*f-c*e,k=c*e-b*f;return[f/i,c/k [...]
+(f*g-e*a)/k,(c*g-b*a)/i]}}),d.save=function(){var a=this._transformMatrix;this._transformStack.push(a);this._transformMatrix=a.slice(0,6);this._originalSave()},d.restore=function(){var a=this._transformStack.pop();a&&(this._transformMatrix=a,this._originalRestore())},d.translate=function(a,b){var c=this._transformMatrix;c[4]=c[0]*a+c[2]*b+c[4];c[5]=c[1]*a+c[3]*b+c[5];this._originalTranslate(a,b)},d.scale=function(a,b){var c=this._transformMatrix;c[0]*=a;c[1]*=a;c[2]*=b;c[3]*=b;this._orig [...]
+b)},d.transform=function(a,b,c,e,f,g){var i=this._transformMatrix;this._transformMatrix=[i[0]*a+i[2]*b,i[1]*a+i[3]*b,i[0]*c+i[2]*e,i[1]*c+i[3]*e,i[0]*f+i[2]*g+i[4],i[1]*f+i[3]*g+i[5]];d._originalTransform(a,b,c,e,f,g)},d.rotate=function(a){var b=Math.cos(a),c=Math.sin(a),e=this._transformMatrix;this._transformMatrix=[e[0]*b+e[2]*c,e[1]*b+e[3]*c,e[0]*-c+e[2]*b,e[1]*-c+e[3]*b,e[4],e[5]];this._originalRotate(a)})}function Hb(d){switch(d){case 63721:case 63193:return 169;default:return d}}fu [...]
+d||127<=d&&d<Ib||d>=la&&d<la+Ib}function Ec(d,a,b){var c=new Image;c.onload=function(){b.resolve(d,c)};c.src="data:image/jpeg;base64,"+window.btoa(a)}function vb(d,a){this.name=d;this.comObj=a;this.callbackIndex=1;var b=this.callbacks={},c=this.actionHandler={};c.console_log=[function(a){console.log.apply(console,a)}];c.console_error=[function(a){console.error.apply(console,a)}];a.onmessage=function(e){var f=e.data;if(f.isReply)if(e=f.callbackId,f.callbackId in b){var g=b[e];delete b[e]; [...]
+e);else f.action in c?(e=c[f.action],f.callbackId?(g=new pa,g.then(function(c){a.postMessage({isReply:!0,callbackId:f.callbackId,data:c})}),e[0].call(e[1],f.data,g)):e[0].call(e[1],f.data)):r("Unkown action from worker: "+f.action)}}PDFJS.build="c684dfc";"use strict";var za="undefined"===typeof window?this:window,Jb="undefined"==typeof window,Fb=1,Bc=5,Eb=Fb;za.PDFJS||(za.PDFJS={});za.PDFJS.getPdf=function(d,a){var b=d;"string"===typeof d&&(b={url:d});var c=new XMLHttpRequest;c.open("GET [...]
+c.mozResponseType=c.responseType="arraybuffer";var e=0>b.url.indexOf(":")?window.location.protocol:b.url.substring(0,b.url.indexOf(":")+1);c.expected="http:"===e||"https:"===e?200:0;"progress"in b&&(c.onprogress=b.progress||void 0);"error"in b&&(c.onerror=b.error||void 0);c.onreadystatechange=function(f){4===c.readyState&&(c.status===c.expected?a(c.mozResponseArrayBuffer||c.mozResponse||c.responseArrayBuffer||c.response):b.error&&b.error(f))};c.send(null)};za.PDFJS.pdfBug=!1;var Hc=funct [...]
+b,c,e){this.pageNumber=b;this.pageDict=c;this.xref=a;this.ref=e;this.displayReadyPromise=null}d.prototype={getPageProp:function(a){return this.pageDict.get(a)},inheritPageProp:function(a){for(var b=this.pageDict,c=b.get(a);void 0===c;){b=b.get("Parent");if(!b)break;c=b.get(a)}return c},get content(){return ca(this,"content",this.getPageProp("Contents"))},get resources(){return ca(this,"resources",this.inheritPageProp("Resources"))},get mediaBox(){var a=this.inheritPageProp("MediaBox");if [...]
+a.length)a=[0,0,612,792];return ca(this,"mediaBox",a)},get view(){var a=this.mediaBox,b=this.inheritPageProp("CropBox");if(!R(b)||4!==b.length)return ca(this,"view",a);b=Q.intersect(b,a);return!b?ca(this,"view",a):ca(this,"view",b)},get annotations(){return ca(this,"annotations",this.inheritPageProp("Annots"))},get rotate(){var a=this.inheritPageProp("Rotate")||0;0!=a%90?a=0:360<=a?a%=360:0>a&&(a=(a%360+360)%360);return ca(this,"rotate",a)},getOperatorList:function(a,b){var c=this.xref,e [...]
+f=this.resources;if(R(e)){var g=[],i,k=e.length;for(i=0;i<k;++i)g.push(c.fetchIfRef(e[i]));e=new Fc(g)}else oa(e)?e.reset():e||(e=new Aa(new Uint8Array(0)));return(this.pe=new Gc(c,a,"p"+this.pageNumber+"_")).getOperatorList(e,f,b)},getLinks:function(){var a=[],b=pageGetAnnotations(),c,e=b.length;for(c=0;c<e;++c)"Link"==b[c].type&&a.push(b[c]);return a},getAnnotations:function(){function a(a,c){for(var b=a;b&&!b.has(c);)b=b.get("Parent");return!b?null:b.get(c)}function b(a){if(!a)return! [...]
+if(0>c)return!1;switch(a.substr(0,c)){case "http":case "https":case "ftp":return!0;default:return!1}}var c=this.xref,e=this.annotations||[],f,g=e.length,i=[];for(f=0;f<g;++f){var k=e[f],j=c.fetch(k);if(U(j)){var h=j.get("Subtype");if(T(h)){var d=j.get("Rect"),l={};l.type=h.name;l.rect=d;switch(h.name){case "Link":if(h=j.get("A"))switch(h.get("S").name){case "URI":j=h.get("URI");b(j)||(j="");l.url=j;break;case "GoTo":l.dest=h.get("D");break;default:ea("other link types")}else j.has("Dest" [...]
+l.dest=T(j)?j.name:j);break;case "Widget":h=a(j,"FT");if(!T(h))break;l.fieldType=h.name;for(var h=[],n=j;n;){var d=n.get("Parent"),o=n.getRaw("Parent");if(n=n.get("T"))h.unshift(ta(n));else{var n=d.get("Kids"),p,q;for(p=0,q=n.length;p<q;p++){var u=n[p];if(u.num==k.num&&u.gen==k.gen)break}h.unshift("`"+p)}n=d;k=o}l.fullName=h.join(".");h=ta(j.get("TU")||"");l.alternativeText=h;if(h=/([\d\.]+)\sTf/.exec(a(j,"DA")||""))l.fontSize=parseFloat(h[1]);l.textAlignment=a(j,"Q");l.flags=a(j,"Ff")|| [...]
+j.get("Contents");k=j.get("T");l.content=ta(h||"");l.title=ta(k||"");l.name=!j.has("Name")?"Note":j.get("Name").name;break;default:ea("unimplemented annotation type: "+h.name)}i.push(l)}}}return i}};return d}(),Lc=function(){function d(c){oa(c)?a.call(this,c):"object"==typeof c&&null!=c&&"byteLength"in c?a.call(this,new Aa(c)):r("PDFDocument: Unknown argument type")}function a(a){fa(0<a.length,"stream must have data");this.stream=a;this.setup();this.acroForm=this.catalog.catDict.get("Acr [...]
+b,f,g){var i=a.pos,k=a.end,j="";i+f>k&&(f=k-i);for(k=0;k<f;++k)j+=a.getChar();a.pos=i;b=g?j.lastIndexOf(b):j.indexOf(b);if(-1==b)return!1;a.pos+=b;return!0}d.prototype={get linearization(){var a=this.stream.length,b=!1;a&&(b=new Ic(this.stream),b.length!=a&&(b=!1));return ca(this,"linearization",b)},get startXRef(){var a=this.stream,e=0;if(this.linearization)a.reset(),b(a,"endobj",1024)&&(e=a.pos+6);else{for(var f=!1,g=a.end;!f&&0<g;)g-=1015,0>g&&(g=0),a.pos=g,f=b(a,"startxref",1024,!0); [...]
+do e=a.getChar();while(va.isSpace(e));for(f="";9>=e-0;)f+=e,e=a.getChar();e=parseInt(f,10);isNaN(e)&&(e=0)}}return ca(this,"startXRef",e)},get mainXRefEntriesOffset(){var a=0,b=this.linearization;b&&(a=b.mainXRefEntriesOffset);return ca(this,"mainXRefEntriesOffset",a)},checkHeader:function(){var a=this.stream;a.reset();b(a,"%PDF-",1024)&&a.moveStart()},setup:function(){this.checkHeader();var a=new Jc(this.stream,this.startXRef,this.mainXRefEntriesOffset);this.xref=a;this.catalog=new Kc(a [...]
+this.linearization;return ca(this,"numPages",a?a.numPages:this.catalog.numPages)},getDocumentInfo:function(){var a;if(this.xref.trailer.has("Info")){var b=this.xref.trailer.get("Info");a={};b.forEach(function(b,e){a[b]="string"!==typeof e?e:ta(e)})}return ca(this,"getDocumentInfo",a)},getFingerprint:function(){var a=this.xref,b;if(a.trailer.has("ID"))b="",a.trailer.get("ID")[0].split("").forEach(function(a){b+=Number(a.charCodeAt(0)).toString(16)});else{a=this.stream.bytes.subarray(0,100 [...]
+0,a.length);b="";for(var f=0,g=a.length;f<g;f++)b+=Number(a[f]).toString(16)}return ca(this,"getFingerprint",b)},getPage:function(a){return this.catalog.getPage(a)}};return d}();"use strict";var Fa=[1,0,0,1,0,0],Q=PDFJS.Util=function(){function d(){}d.makeCssRgb=function(a,b,c){return"rgb("+(255*a|0)+","+(255*b|0)+","+(255*c|0)+")"};d.makeCssCmyk=function(a,b,c,e){a=(new Ba).getRgb([a,b,c,e]);return"rgb("+(255*a[0]|0)+","+(255*a[1]|0)+","+(255*a[2]|0)+")"};d.applyTransform=function(a,b){ [...]
+b[0]+a[1]*b[2]+b[4],a[0]*b[1]+a[1]*b[3]+b[5]]};d.applyInverseTransform=function(a,b){var c=b[0]*b[3]-b[1]*b[2];return[(a[0]*b[3]-a[1]*b[2]+b[2]*b[5]-b[4]*b[3])/c,(-a[0]*b[1]+a[1]*b[0]+b[4]*b[1]-b[5]*b[0])/c]};d.inverseTransform=function(a){var b=a[0]*a[3]-a[1]*a[2];return[a[3]/b,-a[1]/b,-a[2]/b,a[0]/b,(a[2]*a[5]-a[4]*a[3])/b,(a[4]*a[1]-a[5]*a[0])/b]};d.apply3dTransform=function(a,b){return[a[0]*b[0]+a[1]*b[1]+a[2]*b[2],a[3]*b[0]+a[4]*b[1]+a[5]*b[2],a[6]*b[0]+a[7]*b[1]+a[8]*b[2]]};d.norma [...]
+function(a){var b=a.slice(0);a[0]>a[2]&&(b[0]=a[2],b[2]=a[0]);a[1]>a[3]&&(b[1]=a[3],b[3]=a[1]);return b};d.intersect=function(a,b){function c(a,b){return a-b}var e=[a[0],a[2],b[0],b[2]].sort(c),f=[a[1],a[3],b[1],b[3]].sort(c),g=[],a=d.normalizeRect(a),b=d.normalizeRect(b);if(e[0]===a[0]&&e[1]===b[0]||e[0]===b[0]&&e[1]===a[0])g[0]=e[1],g[2]=e[2];else return!1;if(f[0]===a[1]&&f[1]===b[1]||f[0]===b[1]&&f[1]===a[1])g[1]=f[1],g[3]=f[2];else return!1;return g};d.sign=function(a){return 0>a?-1: [...]
+PDFJS.PageViewport=function(){function d(a,b,c,e,f){var g=(a[2]+a[0])/2,i=(a[3]+a[1])/2,k,j,h;switch(c){case -180:case 180:c=-1;j=k=0;h=1;break;case -270:case 90:c=0;j=k=1;h=0;break;case -90:case 270:c=0;j=k=-1;h=0;break;default:c=1,j=k=0,h=-1}var d,l,n;0==c?(d=Math.abs(i-a[1])*b+e,l=Math.abs(g-a[0])*b+f,n=Math.abs(a[3]-a[1])*b,a=Math.abs(a[2]-a[0])*b):(d=Math.abs(g-a[0])*b+e,l=Math.abs(i-a[1])*b+f,n=Math.abs(a[2]-a[0])*b,a=Math.abs(a[3]-a[1])*b);this.transform=[c*b,k*b,j*b,h*b,d-c*b*g-j [...]
+b*g-h*b*i];this.offsetX=e;this.offsetY=f;this.width=n;this.height=a;this.fontScale=b}d.prototype={convertToViewportPoint:function(a,b){return Q.applyTransform([a,b],this.transform)},convertToViewportRectangle:function(a){var b=Q.applyTransform([a[0],a[1]],this.transform),a=Q.applyTransform([a[2],a[3]],this.transform);return[b[0],b[1],a[0],a[1]]},convertToPdfPoint:function(a,b){return Q.applyInverseTransform([a,b],this.transform)}};return d}();var Cc=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 [...]
+0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],pa=PDFJS.Promise=function(){function d(b,c){this.name=b;this.isRejected=!1;this.error=null;null!=c?(this.isResol [...]
+c,this.hasData=!0):(this.isResolved=!1,this._data=a);this.callbacks=[];this.errbacks=[];this.progressbacks=[]}var a={};d.all=function(a){var c=new d,e=a.length,f=[];if(0===e)return c.resolve(f),c;for(var g=0,i=a.length;g<i;++g)a[g].then(function(a){return function(b){f[a]=b;e--;0===e&&c.resolve(f)}}(g));return c};d.prototype={hasData:!1,set data(b){if(void 0!==b&&(this._data!==a&&r("Promise "+this.name+": Cannot set the data of a promise twice"),this._data=b,this.hasData=!0,this.onDataCa [...]
+get data(){this._data===a&&r("Promise "+this.name+": Cannot get data that isn't set");return this._data},onData:function(b){this._data!==a?b(this._data):this.onDataCallback=b},resolve:function(a){this.isResolved&&r("A Promise can be resolved only once "+this.name);this.isRejected&&r("The Promise was already rejected "+this.name);this.isResolved=!0;this.data=a||null;for(var c=this.callbacks,e=0,f=c.length;e<f;e++)c[e].call(null,a)},progress:function(a){for(var c=this.progressbacks,e=0,f=c [...]
+f;e++)c[e].call(null,a)},reject:function(a){this.isRejected&&r("A Promise can be rejected only once "+this.name);this.isResolved&&r("The Promise was already resolved "+this.name);this.isRejected=!0;this.error=a||null;for(var c=this.errbacks,e=0,f=c.length;e<f;e++)c[e].call(null,a)},then:function(a,c,e){a||f("Requiring callback"+this.name);if(this.isResolved)a.call(null,this.data);else if(this.isRejected&&c){var f=this.error;c.call(null,f)}else this.callbacks.push(a),c&&this.errbacks.push [...]
+return d}(),Mc=function(){function d(){this.started={};this.times=[];this.enabled=!0}d.prototype={time:function(a){if(this.enabled){if(a in this.started)throw"Timer is already running for "+a;this.started[a]=Date.now()}},timeEnd:function(a){if(this.enabled){if(!(a in this.started))throw"Timer has not been started for "+a;this.times.push({name:a,start:this.started[a],end:Date.now()});delete this.started[a]}},toString:function(){for(var a=this.times,b="",c=0,e=0,f=a.length;e<f;++e){var g=a [...]
+g.length>c&&(c=g.length)}e=0;for(f=a.length;e<f;++e){for(var i=a[e],g=i.end-i.start,i=i.name;i.length<c;)i+=" ";b+=i+" "+g+"ms\n"}return b}};return d}();PDFJS.getDocument=function(d){var a=new PDFJS.Promise,b=new Nc(a);"string"===typeof d?PDFJS.getPdf({url:d,progress:function(b){b.lengthComputable&&a.progress({loaded:b.loaded,total:b.total})},error:function(b){a.reject("Unexpected server response of "+b.target.status+".")}},function(a){b.sendData(a)}):b.sendData(d);return a};var Oc=funct [...]
+b){this.pdfInfo=a;this.transport=b}d.prototype={get numPages(){return this.pdfInfo.numPages},get fingerprint(){return this.pdfInfo.fingerprint},getPage:function(a){return this.transport.getPage(a)},getDestinations:function(){var a=new PDFJS.Promise;a.resolve(this.pdfInfo.destinations);return a},getOutline:function(){var a=new PDFJS.Promise;a.resolve(this.pdfInfo.outline);return a},getMetadata:function(){var a=new PDFJS.Promise,b=this.pdfInfo.metadata;a.resolve({info:this.pdfInfo.info,met [...]
+null});return a},destroy:function(){this.transport.destroy()}};return d}(),Pc=function(){function d(a,b){this.pageInfo=a;this.transport=b;this.stats=new Mc;this.stats.enabled=!!za.PDFJS.enableStats;this.objs=b.objs;this.renderInProgress=!1}d.prototype={get pageNumber(){return this.pageInfo.pageIndex+1},get rotate(){return this.pageInfo.rotate},get ref(){return this.pageInfo.ref},get view(){return this.pageInfo.view},getViewport:function(a,b){2>arguments.length&&(b=this.rotate);return new [...]
+a,b,0,0)},getAnnotations:function(){if(this.annotationsPromise)return this.annotationsPromise;var a=new PDFJS.Promise;this.annotationsPromise=a;this.transport.getAnnotations(this.pageInfo.pageIndex);return a},render:function(a){function b(a){e.renderInProgress=!1;e.destroyed&&(delete e.operatorList,delete e.displayReadyPromise);a?c.reject(a):c.resolve()}this.renderInProgress=!0;var c=new pa;this.stats.time("Overall");this.displayReadyPromise||(this.displayReadyPromise=new pa,this.destroy [...]
+this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageNumber-1}));var e=this;this.displayReadyPromise.then(function(){if(e.destroyed)b();else{var c=new Oa(a.canvasContext,this.objs,a.textLayer);try{this.display(c,a.viewport,b)}catch(g){b(g)}}}.bind(this),function(a){b(a)});return c},startRenderingFromOperatorList:function(a,b){var c=this;this.operatorList=a;var e=function(){setTimeout(function(){c.displayReadyPromise.resolve()})};this.ensureFonts(b,function(){e()})}, [...]
+b){this.stats.time("Font Loading");for(var c=0,e=a.length;c<e;c++)a[c]=this.objs.objs[a[c]].data;Kb.bind(a,function(){this.stats.timeEnd("Font Loading");b.call(this)}.bind(this))},display:function(a,b,c){function e(){g=a.executeOperatorList(k,g,e,j);g==i&&(a.endDrawing(),f.timeEnd("Rendering"),f.timeEnd("Overall"),c&&c())}var f=this.stats;f.time("Rendering");a.beginDrawing(b);var g=0,i=this.operatorList.fnArray.length,k=this.operatorList,j=null;PDFJS.pdfBug&&StepperManager.enabled&&(j=St [...]
+1),j.init(k),j.nextBreakPoint=j.getNextBreakPoint());e()},getTextContent:function(){var a=new PDFJS.Promise;a.resolve("page text");return a},getOperationList:function(){var a=new PDFJS.Promise;a.resolve({dependencyFontsID:null,operatorList:null});return a},destroy:function(){this.destroyed=!0;this.renderInProgress||(delete this.operatorList,delete this.displayReadyPromise)}};return d}(),Nc=function(){function d(a){this.workerReadyPromise=a;this.objs=new Qc;this.pageCache=[];this.pageProm [...]
+{};if(!za.PDFJS.disableWorker&&"undefined"!==typeof Worker){a=PDFJS.workerSrc;"undefined"===typeof a&&r("No PDFJS.workerSrc specified");try{var b;if(PDFJS.isFirefoxExtension){var c=new MozBlobBuilder;c.append(document.querySelector("#PDFJS_SCRIPT_TAG").textContent);var e=window.URL.createObjectURL(c.getBlob());b=new Worker(e)}else b=new Worker(a);var f=new vb("main",b);this.messageHandler=f;f.on("test",function(a){a?(this.worker=b,this.setupMessageHandler(f)):(za.PDFJS.disableWorker=!0,t [...]
+var g=new Uint8Array(1);f.send("test",g);return}catch(i){Y("The worker has been disabled.")}}za.PDFJS.disableWorker=!0;this.setupFakeWorker()}d.prototype={destroy:function(){this.worker&&this.worker.terminate();this.pageCache=[];this.pagePromises=[]},setupFakeWorker:function(){var a={postMessage:function(b){a.onmessage({data:b})},terminate:function(){}},b=new vb("main",a);this.setupMessageHandler(b);Lb.setup(b)},setupMessageHandler:function(a){this.messageHandler=a;a.on("GetDoc",function [...]
+a=new Oc(a.pdfInfo,this);this.workerReadyPromise.resolve(a)},this);a.on("GetPage",function(a){var a=a.pageInfo,c=new Pc(a,this);this.pageCache[a.pageIndex]=c;this.pagePromises[a.pageIndex].resolve(c)},this);a.on("GetAnnotations",function(a){this.pageCache[a.pageIndex].annotationsPromise.resolve(a.annotations)},this);a.on("RenderPage",function(a){var c=this.pageCache[a.pageIndex],e=a.depFonts;c.stats.timeEnd("Page Request");c.startRenderingFromOperatorList(a.operatorList,e)},this);a.on("o [...]
+a[0],e=a[1];if(!this.objs.hasData(c))switch(e){case "JpegStream":e=a[2];Ec(c,e,this.objs);break;case "Image":e=a[2];this.objs.resolve(c,e);break;case "Font":var e=a[2],f=a[3],a=a[4];if(f)var g=new xa,f=new Aa(f,0,f.length,g);e=new Rc(e,f,a);this.objs.resolve(c,e);break;default:r("Got unkown object type "+e)}},this);a.on("PageError",function(a){var c=this.pageCache[a.pageNum-1];c.displayReadyPromise?c.displayReadyPromise.reject(a.error):r(a.error)},this);a.on("JpegDecode",function(a,c){va [...]
+f=a[1];3!=f&&1!=f&&r("Only 3 component or 1 component can be returned");var g=new Image;g.onload=function(){var a=g.width,b=g.height,e=a*b,h=4*e,e=new Uint8Array(e*f),d=ub(a,b).getContext("2d");d.drawImage(g,0,0);d=d.getImageData(0,0,a,b).data;if(3==f)for(var l=0,n=0;l<h;l+=4,n+=3)e[n]=d[l],e[n+1]=d[l+1],e[n+2]=d[l+2];else if(1==f)for(n=l=0;l<h;l+=4,n++)e[n]=d[l];c.resolve({data:e,width:a,height:b})}.bind(this);e="data:image/jpeg;base64,"+window.btoa(e);g.src=e})},sendData:function(a){th [...]
+a)},getPage:function(a,b){var c=a-1;if(c in this.pagePromises)return this.pagePromises[c];b=new PDFJS.Promise("Page "+a);this.pagePromises[c]=b;this.messageHandler.send("GetPageRequest",{pageIndex:c});return b},getAnnotations:function(a){this.messageHandler.send("GetAnnotationsRequest",{pageIndex:a})}};return d}();"use strict";Db=0;var Sc=function(){function d(a){this.alphaIsShape=!1;this.fontSize=0;this.fontSizeScale=1;this.fontMatrix=this.textMatrix=Fa;this.wordSpacing=this.charSpacing [...]
+this.lineX=this.y=this.x=this.leading=0;this.textHScale=1;this.textRenderingMode=Db;this.fillColorSpace=new Ca;this.fillColorSpaceObj=null;this.strokeColorSpace=new Ca;this.strokeColorObj=this.fillColorObj=this.strokeColorSpaceObj=null;this.strokeColor=this.fillColor="#000000";this.lineWidth=this.strokeAlpha=this.fillAlpha=1;this.old=a}d.prototype={clone:function(){return Object.create(this)},setCurrentPoint:function(a,b){this.x=a;this.y=b}};return d}(),Oa=function(){function d(a,b,c){th [...]
+this.current=new Sc;this.stateStack=[];this.xobjs=this.res=this.pendingClip=null;this.objs=b;this.textLayer=c;a&&Dc(a)}var a=["butt","round","square"],b=["miter","round","bevel"],c={},e={};d.prototype={slowCommands:{stroke:!0,closeStroke:!0,fill:!0,eoFill:!0,fillStroke:!0,eoFillStroke:!0,closeFillStroke:!0,closeEOFillStroke:!0,showText:!0,showSpacedText:!0,setStrokeColorSpace:!0,setFillColorSpace:!0,setStrokeColor:!0,setStrokeColorN:!0,setFillColor:!0,setFillColorN:!0,setStrokeGray:!0,se [...]
+setStrokeRGBColor:!0,setFillRGBColor:!0,setStrokeCMYKColor:!0,setFillCMYKColor:!0,paintJpegXObject:!0,paintImageXObject:!0,paintImageMaskXObject:!0,shadingFill:!0},beginDrawing:function(a){a=a.transform;this.ctx.save();this.ctx.transform.apply(this.ctx,a);this.textLayer&&this.textLayer.beginLayout()},executeOperatorList:function(a,b,c,e){var j=a.argsArray,a=a.fnArray,b=b||0,h=j.length;if(h==b)return b;for(var d=Date.now()+15,l=this.objs,n,o=this.slowCommands;;){if(e&&b===e.nextBreakPoint [...]
+c),b;n=a[b];if("dependency"!==n)this[n].apply(this,j[b]);else for(var p=j[b],q=0,u=p.length;q<u;q++){var O=p[q];if(!l.isResolved(O))return l.get(O,c),b}b++;if(b==h)return b;if(c&&o[n]&&Date.now()>d)return setTimeout(c,0),b}},endDrawing:function(){this.ctx.restore();this.textLayer&&this.textLayer.endLayout()},setLineWidth:function(a){this.current.lineWidth=a;this.ctx.lineWidth=a},setLineCap:function(b){this.ctx.lineCap=a[b]},setLineJoin:function(a){this.ctx.lineJoin=b[a]},setMiterLimit:fu [...]
+a},setDash:function(a,b){this.ctx.mozDash=a;this.ctx.mozDashOffset=b;this.ctx.webkitLineDash=a;this.ctx.webkitLineDashOffset=b},setRenderingIntent:function(a){ea("set rendering intent: "+a)},setFlatness:function(a){ea("set flatness: "+a)},setGState:function(a){for(var b=0,c=a.length;b<c;b++){var e=a[b],j=e[1];switch(e[0]){case "LW":this.setLineWidth(j);break;case "LC":this.setLineCap(j);break;case "LJ":this.setLineJoin(j);break;case "ML":this.setMiterLimit(j);break;case "D":this.setDash( [...]
+break;case "RI":this.setRenderingIntent(j);break;case "FL":this.setFlatness(j);break;case "Font":this.setFont(e[1],e[2]);break;case "CA":this.current.strokeAlpha=e[1];break;case "ca":this.current.fillAlpha=e[1],this.ctx.globalAlpha=e[1]}}},save:function(){this.ctx.save();var a=this.current;this.stateStack.push(a);this.current=a.clone()},restore:function(){var a=this.stateStack.pop();a&&(this.current=a,this.ctx.restore())},transform:function(a,b,c,e,j,h){this.ctx.transform(a,b,c,e,j,h)},m [...]
+b){this.ctx.moveTo(a,b);this.current.setCurrentPoint(a,b)},lineTo:function(a,b){this.ctx.lineTo(a,b);this.current.setCurrentPoint(a,b)},curveTo:function(a,b,c,e,j,h){this.ctx.bezierCurveTo(a,b,c,e,j,h);this.current.setCurrentPoint(j,h)},curveTo2:function(a,b,c,e){var j=this.current;this.ctx.bezierCurveTo(j.x,j.y,a,b,c,e);j.setCurrentPoint(c,e)},curveTo3:function(a,b,c,e){this.curveTo(a,b,c,e,c,e);this.current.setCurrentPoint(c,e)},closePath:function(){this.ctx.closePath()},rectangle:func [...]
+c,e){this.ctx.rect(a,b,c,e)},stroke:function(a){var a="undefined"!==typeof a?a:!0,b=this.ctx,c=this.current.strokeColor;0===this.current.lineWidth&&(b.lineWidth=this.getSinglePixelWidth());b.globalAlpha=this.current.strokeAlpha;c&&c.hasOwnProperty("type")&&"Pattern"===c.type?(b.save(),b.strokeStyle=c.getPattern(b),b.stroke(),b.restore()):b.stroke();a&&this.consumePath();b.globalAlpha=this.current.fillAlpha},closeStroke:function(){this.closePath();this.stroke()},fill:function(a){var a="un [...]
+typeof a?a:!0,b=this.ctx,c=this.current.fillColor;c&&c.hasOwnProperty("type")&&"Pattern"===c.type?(b.save(),b.fillStyle=c.getPattern(b),b.fill(),b.restore()):b.fill();a&&this.consumePath()},eoFill:function(){var a=this.setEOFillRule();this.fill();this.restoreFillRule(a)},fillStroke:function(){this.fill(!1);this.stroke(!1);this.consumePath()},eoFillStroke:function(){var a=this.setEOFillRule();this.fillStroke();this.restoreFillRule(a)},closeFillStroke:function(){this.closePath();this.fillS [...]
+closeEOFillStroke:function(){var a=this.setEOFillRule();this.closePath();this.fillStroke();this.restoreFillRule(a)},endPath:function(){this.consumePath()},clip:function(){this.pendingClip=c},eoClip:function(){this.pendingClip=e},beginText:function(){this.current.textMatrix=Fa;this.current.x=this.current.lineX=0;this.current.y=this.current.lineY=0},endText:function(){},setCharSpacing:function(a){this.current.charSpacing=a},setWordSpacing:function(a){this.current.wordSpacing=a},setHScale:f [...]
+a/100},setLeading:function(a){this.current.leading=-a},setFont:function(a,b){var c=this.objs.get(a),e=this.current;c||r("Can't find font for "+a);e.fontMatrix=c.fontMatrix?c.fontMatrix.slice(0):Fa.slice(0);(0===e.fontMatrix[0]||0===e.fontMatrix[3])&&Y("Invalid font matrix for font "+a);0>b&&(b=-b,e.fontMatrix[0]*=-1,e.fontMatrix[3]*=-1);this.current.font=c;this.current.fontSize=b;if(!c.coded){var e=c.black?c.bold?"bolder":"bold":c.bold?"bold":"normal",j=c.italic?"italic":"normal",c='"'+( [...]
+"sans-serif")+'", '+(c.isSerifFont?"serif":"sans-serif"),h=1<=b?b:1;this.current.fontSizeScale=1!=h?1:b/1;this.ctx.font=j+" "+e+" "+h+"px "+c}},setTextRenderingMode:function(a){4<=a&&ea("unsupported text rendering mode: "+a);this.current.textRenderingMode=a},setTextRise:function(a){ea("text rise: "+a)},moveText:function(a,b){this.current.x=this.current.lineX+=a;this.current.y=this.current.lineY+=b},setLeadingMoveText:function(a,b){this.setLeading(-b);this.moveText(a,b)},setTextMatrix:fun [...]
+c,e,j,h){this.current.textMatrix=[a,b,c,e,j,h];this.current.x=this.current.lineX=0;this.current.y=this.current.lineY=0},nextLine:function(){this.moveText(0,this.current.leading)},applyTextTransforms:function(){var a=this.ctx,b=this.current,c=b.textHScale,e=b.fontMatrix||Fa;a.transform.apply(a,b.textMatrix);a.scale(1,-1);a.translate(b.x,-1*b.y);a.transform.apply(a,e);a.scale(c,1)},getTextGeometry:function(){var a={},b=this.current.font,c=this.ctx.mozCurrentTransform;if(c){var e=Q.applyTra [...]
+0],c),c=Q.applyTransform([1,1],c);a.x=e[0];a.y=e[1];a.hScale=c[0]-e[0];a.vScale=c[1]-e[1]}a.spaceWidth=b.spaceWidth;return a},showText:function(a,b){var c=this.ctx,e=this.current,j=e.font,h=j.charsToGlyphs(a),d=e.fontSize,l=e.fontSizeScale,n=e.charSpacing,o=e.wordSpacing,p=e.textHScale,q=e.fontMatrix||Fa,u=p*q[0],O=h.length,N={str:"",length:0,canvasWidth:0,geom:{}},ba=this.textLayer&&!b?!0:!1,ha=e.textRenderingMode;if(j.coded){c.save();c.transform.apply(c,e.textMatrix);c.translate(e.x,e. [...]
+1);ba&&(this.save(),c.scale(1,-1),N.geom=this.getTextGeometry(),this.restore());for(var t=0;t<O;++t){var x=h[t];null===x?this.ctx.translate(o,0):(this.save(),c.scale(d,d),c.transform.apply(c,q),this.executeOperatorList(x.operatorList),this.restore(),l=Q.applyTransform([x.width,0],q)[0]*d+Q.sign(e.fontMatrix[0])*n,c.translate(l,0),e.x+=l*p,N.str+=x.unicode,N.length++,N.canvasWidth+=l)}}else{c.save();this.applyTextTransforms();t=e.lineWidth;q=Math.abs(e.textMatrix[0]*q[0]);t=0==q||0==t?thi [...]
+t/q;ba&&(N.geom=this.getTextGeometry());1!=l&&(c.scale(l,l),t/=l);c.lineWidth=t;for(t=q=0;t<O;++t)if(x=h[t],null===x)q+=Q.sign(e.fontMatrix[0])*o;else{var C=x.fontChar,p=0.001*x.width*d+Q.sign(e.fontMatrix[0])*n;if(!x.disabled){var F=q/l;switch(ha){default:case Db:case 4:c.fillText(C,F,0);break;case 1:case 5:c.strokeText(C,F,0);break;case 2:case 6:c.fillText(C,F,0),c.strokeText(C,F,0);case 3:}}q+=p;x=" "===x.unicode?"\u00a0":x.unicode;C=x.length;if(F=1<C){var F=x.charCodeAt(0),s=Ya[13];F [...]
+F<s.end?F=!0:(s=Ya[11],F=F>=s.begin&&F<s.end?!0:!1)}if(F)for(F=C-1;0<=F;F--)N.str+=x[F];else N.str+=x;N.length+=C;N.canvasWidth+=p}e.x+=q*u}c.restore();ba&&this.textLayer.appendText(N,j.loadedName,d);return N},showSpacedText:function(a){var b=this.ctx,c=this.current,e=c.font,j=c.fontSize,h=c.textHScale;e.coded||(h*=(c.fontMatrix||Fa)[0]);var d=a.length,l={str:"",length:0,canvasWidth:0,geom:{}},n=this.textLayer?!0:!1;n&&(b.save(),e.coded?(b.transform.apply(b,c.textMatrix),b.scale(1,-1),b. [...]
+-1*c.y),b.scale(h,1)):this.applyTextTransforms(),l.geom=this.getTextGeometry(),b.restore());for(b=0;b<d;++b){var o=a[b];if(sa(o)){var p=0.001*-o*j*h;c.x+=p;n&&(l.canvasWidth+=p,0>o&&0<l.geom.spaceWidth&&0<Math.round(-o/l.geom.spaceWidth)&&(l.str+="\u00a0",l.length++))}else Na(o)?(o=this.showText(o,!0),n&&(l.str=" "===o.str?l.str+"\u00a0":l.str+o.str,l.canvasWidth+=o.canvasWidth,l.length+=o.length)):r("Malformed PDF: "+("TJ array element "+o+" is not string or num"))}n&&this.textLayer.app [...]
+e.loadedName,j)},nextLineShowText:function(a){this.nextLine();this.showText(a)},nextLineSetSpacingShowText:function(a,b,c){this.setWordSpacing(a);this.setCharSpacing(b);this.nextLineShowText(c)},setCharWidth:function(){},setCharWidthAndBounds:function(a,b,c,e,j,h){this.rectangle(c,e,j-c,h-e);this.clip();this.endPath()},setStrokeColorSpace:function(a){this.current.strokeColorSpace=ia.fromIR(a)},setFillColorSpace:function(a){this.current.fillColorSpace=ia.fromIR(a)},setStrokeColor:function [...]
+this.current.strokeColorSpace.getRgb(arguments),a=Q.makeCssRgb(a[0],a[1],a[2]);this.ctx.strokeStyle=a;this.current.strokeColor=a},getColorN_Pattern:function(a,b){if("TilingPattern"==a[0]){var c=a[1],e=b.base,j;if(e){var h=e.numComps;j=[];for(var d=0;d<h;++d)j.push(c[d]);j=e.getRgb(j)}c=new Mb(a,j,this.ctx,this.objs)}else"RadialAxial"==a[0]||"Dummy"==a[0]?c=wb.shadingFromIR(a):r("Unkown IR type "+a[0]);return c},setStrokeColorN:function(){var a=this.current.strokeColorSpace;"Pattern"==a.n [...]
+this.getColorN_Pattern(arguments,a):this.setStrokeColor.apply(this,arguments)},setFillColor:function(){var a=this.current.fillColorSpace.getRgb(arguments),a=Q.makeCssRgb(a[0],a[1],a[2]);this.ctx.fillStyle=a;this.current.fillColor=a},setFillColorN:function(){var a=this.current.fillColorSpace;"Pattern"==a.name?this.current.fillColor=this.getColorN_Pattern(arguments,a):this.setFillColor.apply(this,arguments)},setStrokeGray:function(a){this.current.strokeColorSpace instanceof Ca||(this.curre [...]
+new Ca);a=Q.makeCssRgb(a,a,a);this.ctx.strokeStyle=a;this.current.strokeColor=a},setFillGray:function(a){this.current.fillColorSpace instanceof Ca||(this.current.fillColorSpace=new Ca);a=Q.makeCssRgb(a,a,a);this.ctx.fillStyle=a;this.current.fillColor=a},setStrokeRGBColor:function(a,b,c){this.current.strokeColorSpace instanceof ib||(this.current.strokeColorSpace=new ib);a=Q.makeCssRgb(a,b,c);this.ctx.strokeStyle=a;this.current.strokeColor=a},setFillRGBColor:function(a,b,c){this.current.fi [...]
+ib||(this.current.fillColorSpace=new ib);a=Q.makeCssRgb(a,b,c);this.ctx.fillStyle=a;this.current.fillColor=a},setStrokeCMYKColor:function(a,b,c,e){this.current.strokeColorSpace instanceof Ba||(this.current.strokeColorSpace=new Ba);a=Q.makeCssCmyk(a,b,c,e);this.ctx.strokeStyle=a;this.current.strokeColor=a},setFillCMYKColor:function(a,b,c,e){this.current.fillColorSpace instanceof Ba||(this.current.fillColorSpace=new Ba);a=Q.makeCssCmyk(a,b,c,e);this.ctx.fillStyle=a;this.current.fillColor=a [...]
+this.ctx;this.save();a=wb.shadingFromIR(a);b.fillStyle=a.getPattern(b);var c=b.mozCurrentTransformInverse;if(c){var b=b.canvas,e=b.width,j=b.height,b=Q.applyTransform([0,0],c),a=Q.applyTransform([0,j],c),h=Q.applyTransform([e,0],c),d=Q.applyTransform([e,j],c),c=Math.min(b[0],a[0],h[0],d[0]),e=Math.min(b[1],a[1],h[1],d[1]),j=Math.max(b[0],a[0],h[0],d[0]),b=Math.max(b[1],a[1],h[1],d[1]);this.ctx.fillRect(c,e,j-c,b-e)}else this.ctx.fillRect(-1E10,-1E10,2E10,2E10);this.restore()},beginInline [...]
+beginImageData:function(){r("Should not call beginImageData")},paintFormXObjectBegin:function(a,b){this.save();a&&R(a)&&6==a.length&&this.transform.apply(this,a);b&&R(b)&&4==b.length&&(this.rectangle(b[0],b[1],b[2]-b[0],b[3]-b[1]),this.clip(),this.endPath())},paintFormXObjectEnd:function(){this.restore()},paintJpegXObject:function(a,b,c){(a=this.objs.get(a))||r("Dependent image isn't ready yet");this.save();var e=this.ctx;e.scale(1/b,-1/c);e.drawImage(a,0,0,a.width,a.height,0,-c,b,c);thi [...]
+paintImageMaskXObject:function(a,b,c,e){this.save();var j=this.ctx;j.scale(1/c,-1/e);var h=ub(c,e),d=h.getContext("2d"),l=this.current.fillColor;d.fillStyle=l&&l.hasOwnProperty("type")&&"Pattern"===l.type?l.getPattern(d):l;d.fillRect(0,0,c,e);var l=d.getImageData(0,0,c,e),n=l.data,o=0,p,q,u,O,N=3;for(p=0;p<e;p++)for(q=u=0;q<c;q++)u||(O=a[o++],u=128),!(O&u)==b&&(n[N]=0),N+=4,u>>=1;d.putImageData(l,0,0);j.drawImage(h,0,-e);this.restore()},paintImageXObject:function(a){(a=this.objs.get(a))| [...]
+this.save();var b=this.ctx,c=a.width,e=a.height;b.scale(1/c,-1/e);var d=ub(c,e);this.putBinaryImageData(d.getContext("2d"),a,c,e);b.drawImage(d,0,-e);this.restore()},putBinaryImageData:function(){},markPoint:function(){ea("Marked content")},markPointProps:function(){ea("Marked content")},beginMarkedContent:function(){ea("Marked content")},beginMarkedContentProps:function(){ea("Marked content")},endMarkedContent:function(){ea("Marked content")},beginCompat:function(){ea("ignore undefined [...]
+endCompat:function(){ea("stop ignoring undefined operators")},consumePath:function(){if(this.pendingClip){var a=null;this.pendingClip==e&&(a=this.setEOFillRule());this.ctx.clip();this.pendingClip=null;null!==a&&this.restoreFillRule(a)}this.ctx.beginPath()},setEOFillRule:function(){var a=this.ctx.mozFillRule;this.ctx.mozFillRule="evenodd";return a},restoreFillRule:function(a){this.ctx.mozFillRule=a},getSinglePixelWidth:function(){var a=this.ctx.mozCurrentTransformInverse;return Math.abs(a [...]
+return d}();if(!Jb){var xb=document.createElement("canvas");xb.width=1;xb.height=1;var Tc=xb.getContext("2d");try{Tc.putImageData({width:1,height:1,data:new Uint8Array(4)},0,0),Oa.prototype.putBinaryImageData=function(d,a){d.putImageData(a,0,0)}}catch(Gd){Oa.prototype.putBinaryImageData=function(d,a,b,c){for(var b=d.getImageData(0,0,b,c),c=b.data,e=c.length;e--;)c[e]=a.data[e];d.putImageData(b,0,0)}}}"use strict";var wa=function(){function d(a){this.name=a}d.prototype={};return d}(),Ja=f [...]
+a}d.prototype={};var a={};d.get=function(b){var c=a[b];return c?c:a[b]=new d(b)};return d}(),xa=function(){function d(a){var b=Object.create(null);this.assignXref=function(b){a=b};this.get=function(c,e,f){var g;if("undefined"!=typeof(g=b[c])||c in b||"undefined"==typeof e||"undefined"!=typeof(g=b[e])||e in b||"undefined"==typeof f)return a?a.fetchIfRef(g):g;g=b[f]||null;return a?a.fetchIfRef(g):g};this.getRaw=function(a){return b[a]};this.getAll=function(){var a={},e;for(e in b){var f=th [...]
+a[e]=f instanceof d?f.getAll():f}return a};this.set=function(a,e){b[a]=e};this.has=function(a){return a in b};this.forEach=function(a){for(var e in b)a(e,this.get(e))}}return d}(),Gb=function(){function d(a,b){this.num=a;this.gen=b}d.prototype={};return d}(),Nb=function(){function d(){this.dict={}}d.prototype={has:function(a){return!!this.dict["R"+a.num+"."+a.gen]},put:function(a){this.dict["R"+a.num+"."+a.gen]=a}};return d}(),Kc=function(){function d(a){this.xref=a;a=a.getCatalogObj();f [...]
+this.catDict=a}d.prototype={get metadata(){var a=this.catDict.get("Metadata"),b;if(a&&U(a.dict)){var c=a.dict.get("Type"),e=a.dict.get("Subtype");T(c)&&T(e)&&"Metadata"===c.name&&"XML"===e.name&&(b=ta(tb(a.getBytes())))}return ca(this,"metadata",b)},get toplevelPagesDict(){var a=this.catDict.get("Pages");fa(U(a),"invalid top-level pages dictionary");return ca(this,"toplevelPagesDict",a)},get documentOutline(){var a=this.xref,b=this.catDict.get("Outlines"),c={items:[]};if(U(b)){var b=b.ge [...]
+e=new Nb;if(ya(b)){var f=[{obj:b,parent:c}];for(e.put(b);0<f.length;){var g=f.shift(),i=a.fetchIfRef(g.obj);if(null!==i){i.has("Title")||r("Invalid outline item");(b=i.get("A"))?b=b.get("D"):i.has("Dest")&&(b=i.getRaw("Dest"),T(b)&&(b=b.name));var k=i.get("Title"),k={dest:b,title:ta(k),color:i.get("C")||[0,0,0],count:i.get("Count"),bold:!!(i.get("F")&2),italic:!!(i.get("F")&1),items:[]};g.parent.items.push(k);b=i.getRaw("First");ya(b)&&!e.has(b)&&(f.push({obj:b,parent:k}),e.put(b));b=i.g [...]
+ya(b)&&!e.has(b)&&(f.push({obj:b,parent:g.parent}),e.put(b))}}}}b=0<c.items.length?c.items:null;return ca(this,"documentOutline",b)},get numPages(){var a=this.toplevelPagesDict.get("Count");fa(E(a),"page count in top level pages object is not an integer");return ca(this,"num",a)},traverseKids:function(a){var b=this.pageCache,a=a.get("Kids");fa(R(a),"page dictionary kids object is not an array");for(var c=0,e=a.length;c<e;++c){var f=a[c];fa(ya(f),"page dictionary kid is not a reference"); [...]
+U(g,"Page")||U(g)&&!g.has("Kids")?b.push(new Hc(this.xref,b.length,g,f)):(fa(U(g),"page dictionary kid reference points to wrong type of object"),this.traverseKids(g))}},get destinations(){function a(a){return U(a)?a.get("D"):a}var b=this.xref,c={},e,f,g=this.catDict.get("Names");g?e=g.getRaw("Dests"):this.catDict.has("Dests")&&(f=this.catDict.get("Dests"));f&&f.forEach(function(b,e){e&&(c[b]=a(e))});if(e){f=new Nb;f.put(e);for(e=[e];0<e.length;){var i,g=b.fetch(e.shift());if(g.has("Kids [...]
+g.get("Kids");for(g=0,i=k.length;g<i;g++){var d=k[g];f.has(d)&&r("invalid destinations");e.push(d);f.put(d)}}else{k=g.get("Names");for(g=0,i=k.length;g<i;g+=2)c[k[g]]=a(b.fetchIfRef(k[g+1]))}}}return ca(this,"destinations",c)},getPage:function(a){this.pageCache||(this.pageCache=[],this.traverseKids(this.toplevelPagesDict));return this.pageCache[a-1]}};return d}(),Jc=function(){function d(a,b){this.stream=a;this.entries=[];this.xrefstms={};var c=this.readXRef(b);c.assignXref(this);this.tr [...]
+[];var e=c.get("Encrypt");if(e){var f=c.get("ID");this.encrypt=new Uc(e,f[0])}(this.root=c.get("Root"))||r("Invalid root reference")}d.prototype={readXRefTable:function(a){for(var b;!aa(b=a.getObj(),"trailer");){var c=a.getObj();(!E(b)||!E(c))&&r("Invalid XRef table: wrong types in subsection header");for(var e=0;e<c;e++){var f={};f.offset=a.getObj();f.gen=a.getObj();var g=a.getObj();aa(g,"f")?f.free=!0:aa(g,"n")&&(f.uncompressed=!0);(!E(f.offset)||!E(f.gen)||!f.free&&!f.uncompressed)&&r [...]
+b+", "+c);this.entries[e+b]||(this.entries[e+b]=f)}}this.entries[0]&&!(65535===this.entries[0].gen&&this.entries[0].free)&&r("Invalid XRef table: unexpected first object");aa(b,"trailer")||r("Invalid XRef table: could not find trailer dictionary");a=a.getObj();U(a)||r("Invalid XRef table: could not parse trailer dictionary");return a},readXRefStream:function(a){var b=a.parameters,c=b.get("W"),e=b.get("Index");e||(e=[0,b.get("Size")]);for(var f,g;0<e.length;){var i=e[0],k=e[1];(!E(i)||!E( [...]
+i+", "+k);var d=c[0],h=c[1],m=c[2];(!E(d)||!E(h)||!E(m))&&r("Invalid XRef entry fields length: "+i+", "+k);for(f=0;f<k;++f){var l=0,n=0,o=0;for(g=0;g<d;++g)l=l<<8|a.getByte();0==d&&(l=1);for(g=0;g<h;++g)n=n<<8|a.getByte();for(g=0;g<m;++g)o=o<<8|a.getByte();g={};g.offset=n;g.gen=o;switch(l){case 0:g.free=!0;break;case 1:g.uncompressed=!0;break;case 2:break;default:r("Invalid XRef entry type: "+l)}this.entries[i+f]||(this.entries[i+f]=g)}e.splice(0,2)}return b},indexObjects:function(){func [...]
+b){for(var c="",e=a[b];13!==e&&10!==e&&!(++b>=a.length);){c+=String.fromCharCode(e);e=a[b]}return c}function b(a,b,c){for(var e=c.length,f=a.length,g=0;b<f;){for(var i=0;i<e&&a[b+i]==c[i];)++i;if(i>=e)break;b++;g++}return g}var c=new Uint8Array([116,114,97,105,108,101,114]),e=new Uint8Array([115,116,97,114,116,120,114,101,102]),f=new Uint8Array([101,110,100,111,98,106]),g=new Uint8Array([47,88,82,101,102]),i=this.stream;i.pos=0;for(var k=i.getBytes(),d=i.start,h=k.length,m=[],l=[];d<h;){ [...]
+if(32===n||9===n||13===n||10===n)++d;else if(37===n){do++d,n=k[d];while(13!==n&&10!==n)}else{var n=a(k,d),o;if("xref"===n)d+=b(k,d,c),m.push(d),d+=b(k,d,e);else if(o=/^(\d+)\s+(\d+)\s+obj\b/.exec(n)){this.entries[o[1]]={offset:d,gen:o[2]|0,uncompressed:!0};n=b(k,d,f)+7;o=k.subarray(d,d+n);var p=b(o,0,g);p<n&&64>o[p+5]&&(l.push(d),this.xrefstms[d]=1);d+=n}else d+=n.length+1}}c=0;for(e=l.length;c<e;++c)this.readXRef(l[c]);for(var q,c=0,e=m.length;c<e;++c)if(i.pos=m[c],l=new Pa(new va(i),!0 [...]
+l.getObj(),aa(f,"trailer")&&U(q=l.getObj())&&q.has("ID"))return q;if(q)return q;r("Invalid PDF structure")},readXRef:function(a){var b=this.stream;b.pos=a;try{var c=new Pa(new va(b),!0,null),e=c.getObj(),f;aa(e,"xref")?(f=this.readXRefTable(c),e=f.get("XRefStm"),E(e)&&(a=e,a in this.xrefstms||(this.xrefstms[a]=1,this.readXRef(a)))):E(e)&&((!E(c.getObj())||!aa(c.getObj(),"obj")||!oa(e=c.getObj()))&&r("Invalid XRef stream"),f=this.readXRefStream(e));e=f.get("Prev");E(e)?this.readXRef(e):ya [...]
+return f}catch(g){Wa("(while reading XRef): "+g)}Y("Indexing all PDF objects");return this.indexObjects()},getEntry:function(a){a=this.entries[a];return null===a?null:a.free?null:a},fetchIfRef:function(a){return!ya(a)?a:this.fetch(a)},fetch:function(a,b){fa(ya(a),"ref object is not a reference");var c=a.num;if(c in this.cache)return this.cache[c];var e=this.getEntry(c);if(null===e)return this.cache[c]=e;var f=a.gen,g;if(e.uncompressed){e.gen!=f&&r("inconsistent generation in XRef");g=thi [...]
+g=new Pa(new va(g),!0,this);var i=g.getObj(),k=g.getObj(),d=g.getObj();(!E(i)||i!=c||!E(k)||k!=f||!aa(d))&&r("bad XRef entry");if(!aa(d,"obj")){if(0==d.cmd.indexOf("obj")&&(c=parseInt(d.cmd.substring(3),10),!isNaN(c)))return c;r("bad XRef entry")}if(this.encrypt&&!b)try{e=g.getObj(this.encrypt.createCipherTransform(c,f))}catch(h){return this.fetch(a,!0)}else e=g.getObj();if(!oa(e)||e instanceof jb)this.cache[c]=e;return e}g=this.fetch(new Gb(e.offset,0));oa(g)||r("bad ObjStm stream");c=g [...]
+f=g.parameters.get("N");(!E(c)||!E(f))&&r("invalid first and n parameters for ObjStm stream");g=new Pa(new va(g),!1,this);k=[];d=[];for(i=0;i<f;++i)c=g.getObj(),E(c)||r("invalid object number in the ObjStm stream: "+c),d.push(c),c=g.getObj(),E(c)||r("invalid object offset in the ObjStm stream: "+c);for(i=0;i<f;++i)k.push(g.getObj()),this.cache[d[i]]=k[i];(e=k[e.gen])||r("bad XRef entry for compressed object");return e},getCatalogObj:function(){return this.root}};return d}(),Qc=function() [...]
+{}}d.prototype={objs:null,ensureObj:function(a,b){return this.objs[a]?this.objs[a]:this.objs[a]=new pa(a,b)},get:function(a,b){if(b)return this.ensureObj(a).then(b),null;var c=this.objs[a];(!c||!c.isResolved)&&r("Requesting object that isn't resolved yet "+a);return c.data},resolve:function(a,b){var c=this.objs;c[a]?c[a].resolve(b):this.ensureObj(a,b)},onData:function(a,b){this.ensureObj(a).onData(b)},isResolved:function(a){var b=this.objs;return b[a]?b[a].isResolved:!1},hasData:function [...]
+this.objs;return b[a]?b[a].hasData:!1},setData:function(a,b){this.ensureObj(a).data=b}};return d}();"use strict";var Za=function(){return{getSampleArray:function(d,a,b,c){for(var e=1,f=0,g=d.length;f<g;f++)e*=d[f];for(var e=e*a,d=[],g=a=0,i=1/(Math.pow(2,b)-1),c=c.getBytes((e*b+7)/8),k=0,f=0;f<e;f++){for(;a<b;)g<<=8,g|=c[k++],a+=8;a-=b;d.push((g>>a)*i);g&=(1<<a)-1}return d},getIR:function(d,a){var b=a.dict;b||(b=a);var c=[this.constructSampled,null,this.constructInterpolated,this.constru [...]
+this.constructPostScript],e=b.get("FunctionType");(c=c[e])||r("Unknown type of function");return c.call(this,a,b,d)},fromIR:function(d){switch(d[0]){case 0:return this.constructSampledFromIR(d);case 2:return this.constructInterpolatedFromIR(d);case 3:return this.constructStichedFromIR(d);default:return this.constructPostScriptFromIR(d)}},parse:function(d,a){return this.fromIR(this.getIR(d,a))},constructSampled:function(d,a){function b(a){for(var b=a.length,c=[],e=0,f=0;f<b;f+=2)c[e]=[a[f [...]
+++e;return c}var c=a.get("Domain"),e=a.get("Range");(!c||!e)&&r("No domain or range");var f=c.length/2,g=e.length/2,c=b(c),e=b(e),i=a.get("Size"),k=a.get("BitsPerSample"),j=a.get("Order");j||(j=1);1!==j&&r("No support for cubic spline interpolation: "+j);j=a.get("Encode");if(!j)for(var j=[],h=0;h<f;++h)j.push(0),j.push(i[h]-1);var j=b(j),h=(h=a.get("Decode"))?b(h):e,m=this.getSampleArray(i,g,k,d);return[0,f,c,j,h,m,i,g,Math.pow(2,k)-1,e]},constructSampledFromIR:function(d){return functio [...]
+d[1],c=d[2],e=d[3],f=d[4],g=d[5],i=d[6],k=d[7],j=d[9];b!=a.length&&r("Incorrect number of arguments: "+inputSize+" != "+a.length);for(var h=1<<b,m=new Float64Array(h),l=new Uint32Array(h),n=0;n<h;n++)m[n]=1;for(var o=k,p=1,q=0;q<b;++q){for(var n=c[q][0],u=c[q][1],O=Math.min(Math.max(a[q],n),u),N=e[q][0]+(O-n)*((e[q][1]-e[q][0])/(u-n)),u=i[q],N=Math.min(Math.max(N,0),u-1),n=N<u-1?Math.floor(N):N-1,O=n+1-N,N=N-n,ba=n*o,ha=ba+o,n=0;n<h;n++)n&p?(m[n]*=N,l[n]+=ha):(m[n]*=O,l[n]+=ba);o*=u;p<<= [...]
+for(n=0;n<k;++n){for(q=b=0;q<h;q++)b+=g[l[q]+n]*m[q];b=f[n][0]+(b-0)*((f[n][1]-f[n][0])/1);a[n]=Math.min(Math.max(b,j[n][0]),j[n][1])}return a}},constructInterpolated:function(d,a){var b=a.get("C0")||[0],c=a.get("C1")||[1],e=a.get("N");(!R(b)||!R(c))&&r("Illegal dictionary for interpolated function");for(var f=b.length,g=[],i=0;i<f;++i)g.push(c[i]-b[i]);return[2,b,g,e]},constructInterpolatedFromIR:function(d){var a=d[1],b=d[2],c=d[3],e=b.length;return function(f){for(var f=1==c?f[0]:Math [...]
+c),g=[],i=0;i<e;++i)g.push(a[i]+f*b[i]);return g}},constructStiched:function(d,a,b){(d=a.get("Domain"))||r("No domain");1!=d.length/2&&r("Bad domain for stiched function");for(var c=a.get("Functions"),e=[],f=0,g=c.length;f<g;++f)e.push(Za.getIR(b,b.fetchIfRef(c[f])));b=a.get("Bounds");a=a.get("Encode");return[3,d,b,a,e]},constructStichedFromIR:function(d){for(var a=d[1],b=d[2],c=d[3],d=d[4],e=[],f=0,g=d.length;f<g;f++)e.push(Za.fromIR(d[f]));return function(f){var f=f[0],g=a[0],d=a[1];f> [...]
+(f=g);g=0;for(d=b.length;g<d&&!(f<b[g]);++g);d=a[0];0<g&&(d=b[g-1]);var h=a[1];g<b.length&&(h=b[g]);var m=c[2*g];return e[g]([m+(f-d)*(c[2*g+1]-m)/(h-d)])}},constructPostScript:function(d,a){var b=a.get("Domain"),c=a.get("Range");b||r("No domain.");c||r("No range.");var e=new Vc(d),e=(new Wc(e)).parse();return[4,b,c,e]},constructPostScriptFromIR:function(d){var a=d[1],b=d[2],c=b.length/2,e=new Xc(d[3]),f=new Yc;return function(g){for(var i=[],d=0,j=a.length/2;d<j;++d)i.push(g[d]);g=i.joi [...]
+i=e.execute(i);j=[];for(d=c-1;0<=d;--d){var h=i.pop(),m=2*d;h<b[m]?h=b[m]:h>b[m+1]&&(h=b[m+1]);j[d]=h}f.set(g,j);return j}}}}(),Yc=function(){function d(){this.cache={};this.total=0}d.prototype={has:function(a){return a in this.cache},get:function(a){return this.cache[a]},set:function(a,b){1024>this.total&&(this.cache[a]=b,this.total++)}};return d}(),Zc=function(){function d(a){this.stack=a||[]}d.prototype={push:function(a){100<=this.stack.length&&r("PostScript function stack overflow.") [...]
+pop:function(){0>=this.stack.length&&r("PostScript function stack underflow.");return this.stack.pop()},copy:function(a){100<=this.stack.length+a&&r("PostScript function stack overflow.");for(var b=this.stack,c=b.length-a,a=a-1;0<=a;a--,c++)b.push(b[c])},index:function(a){this.push(this.stack[this.stack.length-a-1])},roll:function(a,b){var c=this.stack,e=c.length-a,f=c.length-1,g=e+(b-Math.floor(b/a)*a),i,d,j;for(i=e,d=f;i<d;i++,d--)j=c[i],c[i]=c[d],c[d]=j;for(i=e,d=g-1;i<d;i++,d--)j=c[i [...]
+c[d]=j;for(i=g,d=f;i<d;i++,d--)j=c[i],c[i]=c[d],c[d]=j}};return d}(),Xc=function(){function d(a,b){this.operators=a;this.operands=b}d.prototype={execute:function(a){for(var a=new Zc(a),b=0,c=this.operators,e=c.length,f,g;b<e;)if(f=c[b++],"number"==typeof f)a.push(f);else switch(f){case "jz":g=a.pop();(f=a.pop())||(b=g);break;case "j":b=f=a.pop();break;case "abs":f=a.pop();a.push(Math.abs(f));break;case "add":g=a.pop();f=a.pop();a.push(f+g);break;case "and":g=a.pop();f=a.pop();ua(f)&&ua(g [...]
+g):a.push(f&g);break;case "atan":f=a.pop();a.push(Math.atan(f));break;case "bitshift":g=a.pop();f=a.pop();0<f?a.push(f<<g):a.push(f>>g);break;case "ceiling":f=a.pop();a.push(Math.ceil(f));break;case "copy":f=a.pop();a.copy(f);break;case "cos":f=a.pop();a.push(Math.cos(f));break;case "cvi":f=a.pop()|0;a.push(f);break;case "cvr":break;case "div":g=a.pop();f=a.pop();a.push(f/g);break;case "dup":a.copy(1);break;case "eq":g=a.pop();f=a.pop();a.push(f==g);break;case "exch":a.roll(2,1);break;ca [...]
+a.pop();f=a.pop();a.push(Math.pow(f,g));break;case "false":a.push(!1);break;case "floor":f=a.pop();a.push(Math.floor(f));break;case "ge":g=a.pop();f=a.pop();a.push(f>=g);break;case "gt":g=a.pop();f=a.pop();a.push(f>g);break;case "idiv":g=a.pop();f=a.pop();a.push(f/g|0);break;case "index":f=a.pop();a.index(f);break;case "le":g=a.pop();f=a.pop();a.push(f<=g);break;case "ln":f=a.pop();a.push(Math.log(f));break;case "log":f=a.pop();a.push(Math.log(f)/Math.LN10);break;case "lt":g=a.pop();f=a. [...]
+g);break;case "mod":g=a.pop();f=a.pop();a.push(f%g);break;case "mul":g=a.pop();f=a.pop();a.push(f*g);break;case "ne":g=a.pop();f=a.pop();a.push(f!=g);break;case "neg":a.pop();a.push(-g);break;case "not":f=a.pop();ua(f)&&ua(g)?a.push(f&&g):a.push(f&g);break;case "or":g=a.pop();f=a.pop();ua(f)&&ua(g)?a.push(f||g):a.push(f|g);break;case "pop":a.pop();break;case "roll":g=a.pop();f=a.pop();a.roll(f,g);break;case "round":f=a.pop();a.push(Math.round(f));break;case "sin":f=a.pop();a.push(Math.si [...]
+case "sqrt":f=a.pop();a.push(Math.sqrt(f));break;case "sub":g=a.pop();f=a.pop();a.push(f-g);break;case "true":a.push(!0);break;case "truncate":f=a.pop();f=0>f?Math.ceil(f):Math.floor(f);a.push(f);break;case "xor":g=a.pop();f=a.pop();ua(f)&&ua(g)?a.push(f!=g):a.push(f^g);break;default:r("Unknown operator "+f)}return a.stack}};return d}(),Wc=function(){function d(a){this.lexer=a;this.operators=[];this.token;this.prev}d.prototype={nextToken:function(){this.prev=this.token;this.token=this.le [...]
+accept:function(a){return this.token.type==a?(this.nextToken(),!0):!1},expect:function(a){if(this.accept(a))return!0;r("Unexpected symbol: found "+this.token.type+" expected "+a+".")},parse:function(){this.nextToken();this.expect(Ua);this.parseBlock();this.expect(Va);return this.operators},parseBlock:function(){for(;;)if(this.accept(gb))this.operators.push(this.prev.value);else if(this.accept(hb))this.operators.push(this.prev.value);else if(this.accept(Ua))this.parseCondition();else brea [...]
+this.operators.length;this.operators.push(null,null);this.parseBlock();this.expect(Va);if(this.accept(sb))this.operators[a]=this.operators.length,this.operators[a+1]="jz";else if(this.accept(Ua)){var b=this.operators.length;this.operators.push(null,null);var c=this.operators.length;this.parseBlock();this.expect(Va);this.expect(Cb);this.operators[b]=this.operators.length;this.operators[b+1]="j";this.operators[a]=c;this.operators[a+1]="jz"}else r("PS Function: error parsing conditional.")} [...]
+Ua=0;Va=1;gb=2;hb=3;sb=4;Cb=5;var Qa=function(){function d(a,c){this.type=a;this.value=c}var a={};d.getOperator=function(b){var c=a[b];return c?c:a[b]=new d(hb,b)};d.LBRACE=new d(Ua,"{");d.RBRACE=new d(Va,"}");d.IF=new d(sb,"IF");d.IFELSE=new d(Cb,"IFELSE");return d}(),Vc=function(){function d(a){this.stream=a}d.prototype={getToken:function(){for(var a,b=!1,c=this.stream;;){if(!(a=c.getChar()))return Z;if(b){if("\n"==a||"\r"==a)b=!1}else if("%"==a)b=!0;else if(!va.isSpace(a))break}switch [...]
+this.getNumber(a));case "{":return Qa.LBRACE;case "}":return Qa.RBRACE}for(b=a.toLowerCase();;){a=c.lookChar();if(null===a)break;a=a.toLowerCase();if("a"<=a&&"z">=a)b+=a;else break;c.skip()}switch(b){case "if":return Qa.IF;case "ifelse":return Qa.IFELSE;default:return Qa.getOperator(b)}},getNumber:function(a){for(var b=a,c=this.stream;;){a=c.lookChar();if("0"<=a&&"9">=a||"-"==a||"."==a)b+=a;else break;c.skip()}a=parseFloat(b);isNaN(a)&&r("Invalid floating point number: "+a);return a}};re [...]
+"use strict";var $c=".notdef,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quoteright,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,quoteleft,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,braceleft,bar,braceright,asciitilde,exclamdown,cent,sterling,fra [...]
+ad=".notdef,space,exclamsmall,Hungarumlautsmall,dollaroldstyle,dollarsuperior,ampersandsmall,Acutesmall,parenleftsuperior,parenrightsuperior,twodotenleader,onedotenleader,comma,hyphen,period,fraction,zerooldstyle,oneoldstyle,twooldstyle,threeoldstyle,fouroldstyle,fiveoldstyle,sixoldstyle,sevenoldstyle,eightoldstyle,nineoldstyle,colon,semicolon,commasuperior,threequartersemdash,periodsuperior,questionsmall,asuperior,bsuperior,centsuperior,dsuperior,esuperior,isuperior,lsuperior,msuperior, [...]
+bd=".notdef,space,dollaroldstyle,dollarsuperior,parenleftsuperior,parenrightsuperior,twodotenleader,onedotenleader,comma,hyphen,period,fraction,zerooldstyle,oneoldstyle,twooldstyle,threeoldstyle,fouroldstyle,fiveoldstyle,sixoldstyle,sevenoldstyle,eightoldstyle,nineoldstyle,colon,semicolon,commasuperior,threequartersemdash,periodsuperior,asuperior,bsuperior,centsuperior,dsuperior,esuperior,isuperior,lsuperior,msuperior,nsuperior,osuperior,rsuperior,ssuperior,tsuperior,ff,fi,fl,ffi,ffl,par [...]
+"use strict";var cd={"Adobe-Japan1":[[32,160],{f:12,c:33},[45,8209],{f:46,c:46},165,{f:2,c:93},[95,818],[96,768],{f:27,c:97},166,125,[732,771],[700,8217],92,[699,8216],124,[126,8764],{f:3,c:161},8260,402,0,164,8220,171,{f:2,c:8249},{f:2,c:64257},[8210,8211],0,0,[183,8729],0,8226,8218,8222,8221,187,0,0,191,{f:2,c:769},[175,772],{f:3,c:774},778,[184,807],779,808,780,[822,8212],198,170,321,216,338,186,230,305,322,248,339,223,173,169,172,174,0,0,{f:2,c:178},181,185,{f:3,c:188},{f:6,c:192},{f [...]
+0,{f:6,c:217},{f:6,c:224},{f:16,c:231},0,{f:7,c:249},352,376,381,[773,8254],353,8482,382,0,8194,{s:91},65512,{s:3},{f:63,c:65377},{s:243},[8195,12288],{f:2,c:12289},65292,65294,12539,{f:2,c:65306},65311,65281,{f:2,c:12443},180,65344,168,65342,65507,65343,{f:2,c:12541},{f:2,c:12445},12291,20189,{f:3,c:12293},12540,8213,8208,65295,65340,[12316,65374],8214,65372,8230,8229,{s:4},{f:2,c:65288},{f:2,c:12308},65339,65341,65371,65373,{f:10,c:12296},65291,[8722,65293],177,215,247,65309,8800,65308 [...]
+c:8806},8734,8756,9794,9792,176,{f:2,c:8242},8451,65509,65284,{f:2,c:65504},65285,65283,65286,65290,65312,167,9734,9733,9675,9679,9678,9671,9670,9633,9632,9651,9650,9661,9660,8251,12306,8594,{f:2,c:8592},8595,12307,8712,8715,{f:2,c:8838},{f:2,c:8834},8746,8745,{f:2,c:8743},65506,8658,8660,8704,8707,8736,8869,8978,8706,8711,8801,8786,{f:2,c:8810},8730,8765,8733,8757,{f:2,c:8747},8491,8240,9839,9837,9834,{f:2,c:8224},182,9711,{f:10,c:65296},{f:26,c:65313},{f:26,c:65345},{f:83,c:12353},{f:8 [...]
+{f:17,c:913},{f:7,c:931},{f:17,c:945},{f:7,c:963},{f:6,c:1040},1025,{f:32,c:1046},1105,{f:26,c:1078},20124,21782,23043,38463,21696,24859,25384,23030,36898,33909,33564,31312,24746,25569,28197,26093,33894,33446,39925,26771,22311,26017,25201,23451,22992,34427,39156,32098,32190,39822,25110,31903,34999,23433,24245,25353,26263,26696,38343,38797,26447,20197,20234,20301,20381,20553,22258,22839,22996,23041,23561,24799,24847,24944,26131,26885,28858,30031,30064,31227,32173,32239,32963,33806,[12176, [...]
+36949,36986,21307,20117,20133,22495,32946,37057,30959,[12032,19968],22769,28322,36920,31282,33576,33419,39983,20801,21360,21693,21729,22240,23035,24341,39154,28139,32996,34093,38498,38512,38560,38907,21515,21491,23431,28879,[12155,32701],36802,[12204,38632],21359,40284,31418,19985,30867,[12165,33276],28198,22040,21764,27421,34074,39995,23013,21417,28006,[12128,29916],38287,22082,20113,36939,38642,33615,39180,21473,21942,23344,24433,26144,26355,26628,27704,27891,27945,29787,30408,31310,38 [...]
+34907,35424,37613,28082,30123,30410,39365,24742,35585,36234,38322,27022,21421,20870,22290,22576,22852,23476,24310,24616,25513,25588,27839,28436,28814,28948,29017,29141,29503,32257,33398,33489,34199,36960,37467,40219,22633,26044,27738,29989,20985,22830,22885,24448,24540,25276,26106,27178,27431,27572,29579,32705,35158,40236,40206,[12009,40644],23713,27798,33659,20740,23627,25014,33222,26742,29281,[12036,20057],20474,21368,24681,28201,31311,[12211,38899],19979,21270,20206,20309,20285,20385, [...]
+21487,22025,22799,23233,23478,23521,31185,26247,26524,26550,27468,27827,[12117,28779],29634,31117,[12146,31166],31292,31623,33457,33499,33540,33655,33775,33747,34662,35506,22057,36008,36838,36942,38686,34442,20420,23784,25105,[12123,29273],30011,33253,33469,34558,36032,38597,39187,39381,20171,20250,35299,22238,22602,22730,24315,24555,24618,24724,24674,25040,25106,25296,25913,39745,26214,26800,28023,28784,30028,30342,32117,33445,34809,38283,38542,[12185,35997],20977,21182,22806,21683,2347 [...]
+27010,28079,30861,33995,34903,35442,37799,39608,28012,39336,34521,22435,26623,34510,37390,21123,22151,21508,24275,25313,25785,26684,26680,27579,29554,30906,31339,35226,[12179,35282],36203,36611,37101,38307,38548,[12208,38761],23398,23731,27005,{f:2,c:38989},25499,31520,27179,27263,26806,39949,28511,21106,21917,24688,25324,27963,28167,28369,33883,35088,36676,19988,39993,21494,26907,27194,38788,26666,20828,31427,33970,37340,37772,22107,40232,26658,33541,33841,31909,21E3,33477,[12129,29926] [...]
+20896,23506,21002,21208,21223,24059,21914,22570,23014,23436,23448,23515,[12082,24178],24185,24739,24863,24931,25022,25563,25954,26577,26707,26874,27454,27475,27735,28450,28567,28485,29872,[12130,29976],30435,30475,31487,31649,31777,32233,[12152,32566],32752,32925,33382,33694,35251,35532,36011,36996,37969,38291,38289,38306,38501,38867,39208,33304,20024,21547,23736,24012,29609,30284,30524,23721,32747,36107,38593,38929,38996,39E3,20225,20238,21361,21916,22120,22522,22855,23305,23492,23696,2 [...]
+24524,25582,26426,26071,26082,26399,26827,26820,27231,24112,27589,27671,27773,30079,31048,23395,31232,32E3,24509,35215,35352,36020,36215,36556,36637,39138,39438,[12004,12225,39740],[12018,20096],20605,20736,22931,23452,25135,25216,25836,27450,29344,30097,31047,32681,34811,35516,35696,25516,33738,38816,21513,21507,21931,26708,27224,35440,30759,26485,[12233,40653],21364,23458,33050,34384,36870,19992,20037,20167,20241,21450,21560,23470,[12088,24339],24613,25937,26429,27714,27762,27875,28792 [...]
+31406,31496,32026,31998,32102,26087,[12124,29275],21435,23621,24040,25298,25312,25369,28192,34394,35377,36317,37624,28417,31142,[12226,39770],20136,{f:2,c:20139},20379,20384,20689,20807,31478,20849,20982,21332,21281,21375,21483,21932,22659,23777,24375,24394,24623,24656,24685,25375,25945,27211,27841,29378,29421,30703,33016,33029,33288,34126,37111,37857,38911,39255,39514,20208,20957,23597,26241,26989,23616,26354,26997,[12127,29577],26704,31873,20677,21220,22343,[12081,24062],37670,[12100,2 [...]
+27453,29748,31105,31165,31563,32202,33465,33740,34943,35167,35641,36817,[12198,37329],21535,37504,20061,20534,21477,21306,29399,29590,30697,33510,36527,39366,39368,39378,20855,24858,34398,21936,31354,20598,23507,36935,38533,20018,27355,37351,23633,23624,25496,31391,27795,38772,36705,31402,29066,38536,31874,26647,32368,26705,37740,21234,21531,34219,35347,32676,36557,37089,21350,34952,31041,20418,20670,21009,20804,21843,22317,29674,22411,22865,24418,24452,24693,24950,24935,25001,25522,2565 [...]
+26690,28179,30054,31293,31995,32076,32153,32331,32619,33550,33610,34509,35336,35427,35686,36605,38938,40335,33464,36814,39912,21127,25119,25731,28608,38553,26689,20625,[12107,27424],27770,28500,[12147,31348],32080,[12174,34880],35363,[12105,26376],20214,20537,20518,20581,20860,21048,21091,21927,22287,22533,23244,24314,25010,25080,25331,25458,26908,27177,29309,[12125,29356],29486,30740,30831,32121,30476,32937,[12178,35211],35609,36066,36562,36963,37749,38522,38997,39443,40568,20803,21407, [...]
+24358,28187,28304,[12126,29572],29694,32067,33335,[12180,35328],35578,38480,20046,20491,21476,21628,22266,22993,23396,[12080,24049],24235,24359,[12094,25144],25925,26543,28246,29392,31946,34996,32929,32993,33776,[11969,34382],35463,36328,37431,38599,39015,[12238,40723],20116,20114,20237,21320,21577,21566,23087,24460,24481,24735,26791,27278,29786,30849,35486,35492,35703,37264,20062,39881,20132,20348,20399,20505,20502,20809,20844,21151,21177,21246,21402,[12061,21475],21521,21518,21897,2235 [...]
+23380,23389,23439,[12079,24037],24039,24055,24184,24195,24218,24247,24344,24658,24908,25239,25304,25511,25915,26114,26179,26356,26477,26657,26775,27083,27743,27946,28009,28207,28317,30002,30343,30828,31295,31968,32005,32024,32094,32177,32789,32771,32943,32945,33108,33167,33322,33618,[12175,34892],34913,35611,36002,36092,37066,37237,37489,30783,37628,38308,38477,38917,[12217,39321],[12220,39640],40251,21083,21163,21495,21512,22741,25335,28640,35946,36703,40633,20811,21051,21578,22269,3129 [...]
+[12234,40658],29508,28425,33136,29969,24573,24794,[12219,39592],29403,36796,27492,38915,20170,22256,22372,22718,23130,24680,25031,26127,26118,26681,26801,28151,30165,32058,[12169,33390],39746,20123,20304,21449,21766,23919,24038,24046,26619,27801,29811,30722,35408,37782,35039,22352,24231,25387,20661,20652,20877,26368,21705,22622,22971,23472,24425,25165,25505,26685,27507,28168,28797,37319,29312,30741,30758,31085,25998,32048,33756,35009,36617,38555,21092,22312,26448,32618,36001,20916,22338, [...]
+27018,32948,21682,23822,22524,30869,40442,20316,21066,21643,25662,26152,26388,26613,31364,31574,32034,37679,26716,39853,31545,21273,20874,21047,23519,25334,25774,25830,26413,27578,34217,38609,30352,39894,25420,37638,39851,[12139,30399],26194,19977,20632,21442,[12077,23665],24808,25746,25955,26719,29158,29642,29987,31639,32386,34453,35715,36059,37240,39184,26028,26283,27531,20181,20180,20282,20351,21050,21496,21490,21987,22235,[12064,22763],22987,22985,23039,[12070,23376],23629,24066,2410 [...]
+25351,[12096,25903],23388,26031,26045,26088,26525,[12108,27490],27515,[12114,27663],29509,31049,31169,[12151,31992],32025,32043,32930,33026,[12164,33267],35222,35422,35433,35430,35468,35566,36039,36060,38604,39164,[12013,27503],20107,20284,20365,20816,23383,23546,24904,25345,26178,27425,28363,27835,29246,29885,30164,30913,[12144,31034],[12157,32780],[12159,32819],[12163,33258],33940,36766,27728,[12229,40575],24335,35672,40235,31482,36600,23437,38635,19971,21489,22519,22833,23241,23460,24 [...]
+28422,30142,36074,23455,34048,31712,20594,26612,33437,23649,34122,32286,33294,20889,23556,25448,36198,26012,29038,31038,32023,32773,35613,[12190,36554],36974,34503,37034,20511,21242,23610,26451,28796,29237,37196,37320,37675,33509,23490,24369,24825,20027,21462,23432,[12095,25163],26417,27530,29417,29664,31278,33131,36259,37202,[12216,39318],20754,21463,21610,23551,25480,27193,32172,38656,22234,21454,21608,23447,23601,24030,20462,24833,25342,27954,31168,31179,32066,32333,32722,33261,[12168 [...]
+34886,35186,35728,36468,36655,36913,37195,37228,38598,37276,20160,20303,20805,[12055,21313],24467,25102,26580,27713,28171,29539,32294,37325,37507,21460,22809,23487,28113,31069,32302,31899,22654,29087,20986,34899,36848,20426,23803,26149,30636,31459,33308,39423,20934,24490,26092,26991,27529,28147,28310,28516,30462,32020,24033,36981,37255,38918,20966,21021,25152,26257,26329,28186,24246,32210,32626,26360,34223,34295,35576,21161,21465,[12069,22899],24207,24464,24661,37604,38500,20663,20767,21 [...]
+21319,21484,21736,21830,21809,22039,22888,22974,23100,23477,23558,[12073,23567],23569,23578,24196,24202,24288,24432,25215,25220,25307,25484,25463,26119,26124,26157,26230,26494,26786,27167,27189,27836,28040,28169,28248,28988,28966,29031,30151,30465,30813,30977,31077,31216,31456,31505,31911,32057,32918,33750,33931,34121,34909,35059,35359,35388,35412,35443,35937,36062,37284,37478,37758,37912,38556,38808,19978,19976,19998,20055,20887,21104,22478,22580,22732,23330,24120,24773,25854,26465,2645 [...]
+30067,31331,33976,35698,37304,37664,22065,22516,39166,25325,26893,27542,29165,32340,32887,[12170,33394],35302,[12215,39135],34645,36785,23611,20280,20449,20405,21767,23072,23517,23529,[12092,24515],24910,25391,26032,26187,26862,27035,28024,28145,30003,30137,30495,31070,31206,32051,[12162,33251],33455,34218,35242,35386,[12189,36523],[12191,36763],36914,37341,38663,[12040,20154],20161,20995,22645,22764,23563,29978,23613,33102,35338,36805,38499,38765,31525,35535,38920,37218,22259,21416,3688 [...]
+24101,25512,[12116,27700],28810,30561,31883,32736,34928,36930,37204,37648,37656,38543,29790,39620,23815,23913,25968,26530,36264,38619,25454,26441,26905,33733,38935,38592,35070,28548,25722,[12072,23544],19990,28716,30045,26159,20932,21046,21218,22995,24449,24615,25104,25919,25972,26143,26228,26866,26646,27491,28165,29298,[12131,29983],30427,31934,32854,22768,35069,[11972,35199],35488,35475,35531,36893,37266,[11992,38738],38745,[12011,25993],31246,33030,38587,24109,24796,25114,26021,26132, [...]
+30707],31309,31821,32318,33034,36012,[12186,36196],36321,36447,30889,20999,25305,25509,25666,25240,35373,31363,31680,35500,38634,32118,[12166,33292],34633,20185,20808,21315,21344,23459,23554,23574,24029,25126,25159,25776,26643,26676,27849,27973,27927,26579,28508,29006,29053,26059,31359,31661,32218,32330,32680,33146,[12167,33307],33337,34214,35438,36046,36341,36984,36983,37549,37521,38275,39854,21069,21892,28472,28982,20840,31109,32341,33203,31950,22092,22609,23720,25514,26366,26365,26970 [...]
+30094,30990,31062,31199,31895,32032,32068,34311,35380,38459,36961,[12239,40736],20711,21109,21452,21474,20489,21930,22766,22863,29245,23435,23652,21277,24803,24819,25436,25475,25407,25531,25805,26089,26361,24035,27085,27133,28437,29157,20105,30185,30456,31379,31967,32207,32156,32865,33609,33624,33900,33980,34299,35013,[12187,36208],36865,36973,37783,38684,39442,20687,22679,24974,33235,34101,36104,36896,20419,20596,21063,21363,24687,25417,26463,28204,[12188,36275],36895,20439,23646,36042, [...]
+21330,34966,20854,25539,23384,23403,23562,25613,26449,36956,20182,22810,22826,27760,35409,21822,22549,22949,24816,25171,26561,33333,26965,38464,39364,39464,20307,22534,23550,32784,23729,24111,24453,24608,24907,25140,26367,27888,28382,32974,33151,33492,34955,36024,36864,36910,38538,40667,39899,20195,21488,[12068,22823],31532,37261,38988,40441,28381,28711,21331,21828,23429,25176,25246,25299,27810,28655,29730,35351,37944,28609,35582,33592,20967,34552,21482,21481,20294,36948,[12192,36784],22 [...]
+24061,31466,36799,26842,[12181,35895],29432,40008,27197,35504,20025,21336,22022,22374,25285,25506,26086,27470,28129,28251,28845,30701,31471,31658,32187,32829,32966,34507,35477,37723,22243,22727,24382,26029,26262,27264,27573,30007,35527,20516,30693,22320,24347,24677,26234,27744,30196,31258,32622,33268,34584,36933,39347,31689,30044,[12149,31481],31569,33988,36880,31209,31378,33590,23265,30528,20013,20210,23449,24544,25277,26172,26609,27880,[12173,34411],34935,35387,37198,37619,39376,27159, [...]
+33511,33879,36015,19969,20806,20939,21899,23541,24086,24115,24193,24340,24373,24427,24500,25074,25361,26274,26397,28526,29266,30010,30522,32884,33081,33144,34678,35519,35548,36229,36339,37530,[11985,12199,38263],38914,[12227,40165],21189,25431,30452,26389,27784,29645,36035,37806,38515,27941,22684,26894,27084,36861,37786,30171,36890,22618,26626,25524,27131,20291,28460,26584,36795,34086,32180,37716,26943,28528,22378,22775,23340,32044,[12118,29226],21514,37347,40372,20141,20302,20572,20597, [...]
+21576,22564,23450,24093,24213,24237,24311,24351,24716,25269,25402,25552,26799,27712,30855,31118,31243,32224,33351,35330,35558,36420,36883,37048,37165,37336,[12237,40718],27877,25688,25826,25973,28404,30340,31515,36969,37841,28346,21746,24505,25764,36685,36845,37444,20856,22635,22825,23637,24215,28155,32399,29980,36028,36578,39003,28857,20253,27583,28593,[12133,3E4],38651,20814,21520,22581,22615,22956,23648,24466,[12099,26007],26460,28193,30331,33759,36077,36884,37117,37709,30757,30778,21 [...]
+[12063,22303],22900,24594,20498,20826,20908,20941,[12049,20992],21776,22612,22616,22871,23445,23798,23947,24764,25237,25645,26481,26691,26812,26847,30423,28120,28271,28059,28783,29128,24403,30168,31095,31561,31572,31570,31958,32113,21040,33891,34153,34276,35342,35588,[12182,35910],36367,36867,36879,37913,38518,38957,39472,38360,20685,21205,21516,22530,23566,24999,25758,27934,30643,31461,33012,33796,36947,37509,23776,40199,21311,24471,24499,28060,29305,30563,31167,31716,27602,29420,35501, [...]
+20984,31361,26932,23626,40182,33515,23493,[12195,37193],28702,22136,23663,24775,25958,27788,35930,36929,38931,21585,26311,37389,22856,37027,20869,20045,20970,34201,35598,28760,25466,37707,26978,39348,32260,30071,21335,26976,36575,38627,27741,[12038,20108],23612,24336,36841,21250,36049,[12161,32905],34425,24319,[12103,26085],20083,[12042,20837],22914,23615,38894,20219,22922,24525,35469,28641,31152,31074,23527,33905,29483,29105,24180,24565,25467,25754,29123,31896,20035,24316,20043,22492,22 [...]
+28611,32013,33021,33075,33215,36786,35223,34468,24052,25226,25773,35207,26487,27874,27966,29750,30772,23110,32629,33453,[12218,39340],20467,24259,25309,25490,25943,26479,30403,29260,32972,32954,36649,37197,20493,22521,23186,26757,26995,29028,29437,36023,22770,36064,38506,36889,34687,31204,30695,33833,20271,21093,21338,25293,26575,27850,[12137,30333],31636,31893,33334,34180,36843,26333,28448,29190,32283,33707,39361,[12008,40614],20989,31665,30834,31672,32903,31560,27368,24161,32908,30033, [...]
+20843],37474,28300,30330,37271,39658,20240,32624,25244,31567,38309,40169,22138,22617,34532,38588,20276,21028,21322,21453,21467,24070,25644,26001,26495,27710,27726,29256,29359,29677,30036,32321,33324,34281,36009,31684,[12196,37318],29033,38930,39151,25405,26217,30058,30436,30928,34115,34542,21290,21329,21542,22915,24199,24444,24754,25161,25209,25259,26E3,[12112,27604],27852,30130,[12138,30382],30865,31192,32203,32631,32933,34987,35513,36027,36991,[12206,38750],[12214,39131],27147,31800,20 [...]
+24494,26503,27608,29749,30473,32654,[12240,40763],26570,31255,21305,[12134,30091],39661,24422,33181,33777,32920,24380,24517,30050,31558,36924,26727,23019,23195,32016,30334,35628,20469,24426,27161,27703,28418,29922,31080,34920,35413,35961,24287,25551,30149,31186,33495,37672,37618,33948,34541,39981,21697,24428,25996,27996,28693,36007,36051,38971,25935,29942,19981,20184,22496,22827,23142,23500,20904,24067,24220,24598,25206,25975,26023,26222,28014,[12119,29238],31526,33104,33178,33433,35676, [...]
+36212,[12201,38428],38468,20398,25771,27494,33310,33889,34154,37096,23553,26963,[12213,39080],33914,34135,20239,21103,24489,24133,26381,31119,33145,35079,35206,28149,24343,25173,27832,20175,29289,39826,20998,21563,22132,22707,24996,25198,28954,22894,31881,31966,32027,38640,[12098,25991],32862,19993,20341,20853,22592,24163,24179,24330,26564,20006,34109,38281,38491,[12150,31859],[12212,38913],20731,22721,30294,30887,21029,30629,34065,31622,20559,22793,[12122,29255],31687,32232,36794,36820, [...]
+21193,23081,24321,38829,20445,33303,37610,22275,25429,27497,29995,35036,36628,31298,21215,22675,24917,25098,26286,[11935,27597],31807,33769,20515,20472,21253,21574,22577,22857,23453,23792,23791,23849,24214,25265,25447,25918,[12101,26041],26379,27861,27873,28921,30770,32299,32990,33459,33804,34028,34562,35090,35370,35914,37030,37586,39165,40179,40300,20047,20129,20621,21078,22346,22952,24125,{f:2,c:24536},25151,26292,26395,26576,26834,20882,32033,32938,33192,35584,35980,36031,37502,38450, [...]
+21271,20693,[12056,21340],22696,25778,26420,29287,30566,31302,37350,21187,27809,27526,22528,24140,22868,26412,32763,20961,30406,25705,30952,39764,[12231,40635],22475,22969,26151,26522,27598,21737,27097,24149,33180,26517,39850,26622,40018,26717,20134,20451,[12060,21448],25273,26411,27819,36804,20397,32365,40639,19975,24930,28288,28459,34067,21619,26410,39749,[11922,24051],31637,23724,23494,34588,28234,34001,31252,33032,22937,31885,[11936,27665],30496,21209,22818,28961,29279,[12141,30683], [...]
+26891,23167,23064,20901,21517,21629,26126,30431,36855,37528,40180,23018,29277,28357,20813,26825,32191,32236,[12207,38754],40634,25720,27169,33538,22916,23391,[12113,27611],29467,30450,32178,32791,33945,20786,[12106,26408],40665,[12140,30446],26466,21247,39173,23588,25147,31870,36016,21839,24758,32011,[12200,38272],21249,20063,20918,22812,29242,32822,37326,24357,[12142,30690],21380,24441,32004,34220,35379,36493,38742,26611,34222,37971,24841,24840,27833,30290,35565,36664,21807,20305,20778, [...]
+23461,24189,24736,24962,25558,26377,26586,28263,28044,{f:2,c:29494},30001,31056,35029,35480,36938,[12194,37009],37109,38596,34701,[12067,22805],20104,20313,19982,35465,36671,38928,20653,24188,22934,23481,24248,25562,25594,25793,26332,26954,27096,27915,28342,29076,[12132,29992],31407,[12154,32650],32768,33865,33993,35201,35617,36362,36965,38525,39178,24958,25233,27442,27779,28020,32716,32764,28096,32645,34746,35064,26469,33713,38972,38647,27931,32097,33853,37226,20081,21365,23888,27396,28 [...]
+34349,35239,21033,21519,23653,26446,26792,29702,29827,30178,35023,35041,[12197,37324],38626,38520,24459,29575,[12148,31435],33870,25504,30053,21129,27969,28316,29705,30041,30827,31890,38534,[12015,31452],[12243,40845],20406,24942,26053,34396,20102,20142,20698,20001,20940,23534,26009,26753,28092,29471,30274,30637,31260,31975,33391,35538,36988,37327,38517,38936,[12050,21147],32209,20523,21400,26519,28107,29136,29747,33256,36650,38563,40023,40607,29792,22593,28057,32047,39006,20196,20278,20 [...]
+21169,23994,24604,29618,31036,33491,37428,38583,38646,38666,40599,40802,26278,27508,21015,21155,28872,35010,24265,24651,24976,28451,29001,31806,32244,32879,34030,36899,37676,21570,39791,27347,28809,36034,36335,38706,21172,23105,24266,24324,26391,27004,27028,28010,28431,29282,29436,31725,[12156,32769],32894,34635,37070,20845,40595,31108,32907,37682,35542,20525,21644,35441,27498,36036,33031,24785,26528,40434,20121,20120,39952,35435,34241,34152,26880,28286,30871,33109,24332,19984,19989,2001 [...]
+20022],20028,[12035,20031],20034,20054,20056,20098,[12037,20101],35947,20106,33298,24333,20110,{f:2,c:20126},[12039,20128],20130,20144,20147,20150,20174,20173,20164,20166,20162,20183,20190,20205,20191,20215,20233,20314,20272,20315,20317,20311,20295,20342,20360,20367,20376,20347,20329,20336,20369,20335,20358,20374,20760,20436,20447,20430,20440,20443,20433,20442,20432,{f:2,c:20452},20506,20520,20500,20522,20517,20485,20252,20470,20513,20521,20524,20478,20463,20497,20486,20547,20551,26371,2 [...]
+20552,20570,20566,20588,20600,20608,20634,20613,20660,20658,{f:2,c:20681},20659,20674,20694,20702,20709,20717,20707,20718,20729,20725,20745,{f:2,c:20737},20758,20757,20756,20762,20769,20794,20791,20796,20795,[12041,20799],[11918,20800],20818,20812,20820,20834,31480,{f:2,c:20841},20846,20864,[12044,20866],22232,20876,20873,20879,20881,20883,20885,[12045,20886],20900,20902,20898,{f:2,c:20905},[12046,20907],20915,{f:2,c:20913},20912,20917,20925,20933,20937,20955,[12047,20960],34389,20969,20 [...]
+[12048,20981],20990,20996,21003,21012,21006,21031,21034,21038,21043,21049,21071,21060,{f:2,c:21067},21086,21076,21098,21108,21097,21107,21119,21117,21133,21140,21138,21105,21128,21137,36776,36775,{f:2,c:21164},21180,21173,21185,21197,21207,21214,21219,21222,39149,21216,21235,21237,21240,[12051,21241],21254,21256,30008,21261,21264,21263,[12052,21269],[12053,21274],21283,21295,21297,21299,[12054,21304],21312,21318,21317,19991,21321,21325,20950,21342,[12057,21353],21358,22808,21371,21367,[1 [...]
+21398,21408,21414,21413,21422,21424,[12059,21430],21443,31762,38617,21471,26364,29166,21486,21480,21485,21498,21505,21565,21568,{f:2,c:21548},21564,21550,21558,21545,21533,21582,21647,21621,21646,21599,21617,21623,21616,21650,21627,21632,21622,21636,21648,21638,21703,21666,21688,21669,21676,21700,21704,21672,21675,21698,21668,21694,21692,21720,{f:2,c:21733},21775,21780,21757,21742,21741,21754,21730,21817,21824,21859,21836,21806,21852,21829,{f:2,c:21846},21816,21811,21853,21913,21888,2167 [...]
+21883,21886,21912,21918,21934,21884,21891,21929,21895,21928,21978,21957,21983,21956,21980,21988,21972,22036,22007,22038,22014,22013,22043,22009,22094,22096,29151,22068,22070,22066,22072,22123,22116,22063,22124,22122,22150,22144,22154,22176,22164,22159,22181,22190,22198,22196,22210,22204,22209,22211,22208,22216,22222,22225,22227,[12062,22231],22254,22265,22272,22271,22276,22281,22280,22283,22285,22291,22296,22294,21959,22300,22310,{f:2,c:22327},22350,22331,22336,22351,22377,22464,22408,22 [...]
+22409,22419,22432,22451,22436,22442,22448,22467,22470,22484,{f:2,c:22482},22538,22486,22499,22539,22553,22557,22642,22561,22626,22603,22640,27584,22610,22589,22649,22661,22713,22687,22699,22714,22750,22715,22712,22702,22725,22739,22737,22743,22745,22744,22757,22748,22756,22751,22767,22778,22777,{f:3,c:22779},[12065,22786],[12066,22794],22800,22811,26790,22821,{f:2,c:22828},22834,22840,22846,31442,22869,22864,22862,22874,22872,22882,22880,22887,22892,22889,22904,22913,22941,20318,20395,22 [...]
+22982,23016,23004,22925,{f:2,c:23001},23077,23071,23057,23068,23049,23066,23104,23148,23113,{f:2,c:23093},23138,23146,23194,23228,23230,23243,23234,23229,23267,23255,23270,23273,23254,{f:2,c:23290},23308,23307,23318,23346,23248,23338,23350,23358,23363,23365,23360,23377,23381,{f:2,c:23386},23397,23401,23408,23411,23413,23416,25992,23418,[12071,23424],23427,23462,23480,23491,23495,23497,23508,23504,23524,23526,23522,23518,23525,23531,23536,23542,23539,23557,{f:2,c:23559},23565,23571,23584, [...]
+23586],23592,[12075,23608],23609,23617,23622,23630,23635,23632,23631,23409,23660,[12076,23662],20066,23670,23673,23692,23697,23700,22939,23723,23739,23734,23740,23735,23749,23742,23751,23769,23785,23805,23802,23789,23948,23786,23819,23829,23831,23900,23839,23835,23825,23828,23842,23834,23833,23832,23884,23890,23886,23883,23916,23923,23926,23943,23940,23938,23970,23965,23980,23982,23997,23952,23991,23996,24009,24013,24019,24018,24022,[12078,24027],24043,24050,24053,24075,24090,24089,24081 [...]
+c:24118},24132,24131,24128,24142,24151,24148,24159,24162,24164,24135,{f:2,c:24181},[11923,12083,24186],40636,[12084,24191],24224,{f:2,c:24257},24264,24272,24271,24278,24291,24285,{f:2,c:24282},24290,24289,{f:2,c:24296},24300,24305,24307,24304,[12085,24308],24312,[12086,24318],24323,24329,24413,24412,[12087,24331],24337,24342,24361,24365,24376,24385,24392,24396,24398,24367,[11924,24401],{f:2,c:24406},24409,[12090,24417],24429,[12091,24435],24439,24451,24450,24447,24458,24456,24465,24455,2 [...]
+24472,24480,24488,24493,24508,24534,24571,24548,24568,24561,24541,24755,24575,24609,24672,24601,24592,24617,24590,24625,24603,24597,24619,24614,24591,24634,24666,24641,24682,24695,24671,24650,24646,24653,24675,24643,24676,24642,24684,24683,24665,24705,24717,24807,24707,24730,24708,24731,{f:2,c:24726},24722,24743,24715,24801,24760,24800,24787,24756,24560,24765,24774,24757,24792,24909,24853,24838,{f:2,c:24822},24832,24820,24826,24835,24865,24827,24817,{f:2,c:24845},24903,24894,24872,24871, [...]
+24892,24876,24884,24893,24898,24900,24947,24951,{f:3,c:24920},24939,24948,24943,24933,24945,24927,24925,24915,24949,24985,24982,24967,25004,24980,24986,24970,24977,25003,25006,25036,25034,25033,25079,25032,25027,25030,25018,25035,32633,25037,25062,25059,25078,25082,25076,25087,25085,25084,25086,25088,[12093,25096],25097,25101,25100,25108,25115,25118,25121,25130,25134,25136,{f:2,c:25138},25153,25166,25182,25187,25179,25184,25192,25212,25218,25225,25214,{f:2,c:25234},25238,25300,25219,2523 [...]
+25275,25295,25343,25286,25812,25288,25308,25292,25290,25282,25287,25243,25289,25356,25326,25329,25383,25346,25352,25327,25333,25424,25406,25421,25628,25423,25494,25486,25472,25515,25462,25507,25487,25481,25503,25525,25451,25449,25534,25577,25536,25542,25571,25545,25554,25590,25540,25622,25652,25606,25619,25638,25654,25885,25623,25640,25615,25703,25711,25718,25678,25898,25749,25747,25765,25769,25736,25788,25818,25810,25797,25799,25787,25816,25794,25841,25831,33289,{f:2,c:25824},25260,2582 [...]
+25846,25844,25842,25850,25856,25853,25880,25884,25861,25892,25891,25899,[12097,25908],[11929,25909],25911,25910,25912,30027,25928,25942,25941,25933,25944,25950,25949,25970,25976,{f:2,c:25986},35722,26011,26015,26027,26039,26051,26054,26049,26052,26060,26066,26075,26073,[12102,26080],[11931,26081],26097,26482,26122,26115,26107,26483,{f:2,c:26165},26164,26140,26191,26180,26185,26177,26206,26205,26212,{f:2,c:26215},26207,26210,26224,26243,26248,26254,26249,26244,26264,26269,26305,26297,2631 [...]
+26308,26296,26326,26330,26336,26175,26342,26345,[12104,26352],26357,26359,26383,26390,26398,{f:2,c:26406},38712,26414,26431,26422,26433,26424,26423,26438,26462,26464,26457,{f:2,c:26467},26505,26480,26537,26492,26474,26508,26507,26534,26529,26501,26551,26607,26548,26604,26547,26601,26552,26596,26590,26589,26594,26606,26553,26574,26566,26599,27292,26654,26694,26665,26688,26701,26674,26702,26803,26667,26713,26723,26743,26751,26783,26767,26797,26772,26781,26779,26755,27310,26809,26740,26805, [...]
+26895,26765,26750,26881,26826,26888,26840,26914,26918,26849,26892,26829,26836,26855,26837,26934,26898,26884,26839,26851,26917,26873,26848,26863,26920,26922,26906,26915,26913,26822,27001,26999,26972,27E3,26987,26964,27006,26990,26937,26996,26941,26969,26928,26977,26974,26973,27009,26986,27058,27054,27088,27071,27073,27091,27070,27086,23528,27082,27101,27067,27075,27047,27182,27025,27040,27036,27029,27060,27102,27112,27138,27163,27135,27402,27129,27122,27111,27141,27057,27166,27117,27156,2 [...]
+27154,27329,27171,27155,27204,27148,27250,27190,27256,27207,27234,27225,27238,27208,27192,27170,27280,27277,27296,27268,{f:2,c:27298},27287,34327,27323,27331,27330,27320,27315,27308,27358,27345,27359,27306,27354,27370,27387,27397,34326,27386,27410,27414,39729,27423,27448,27447,30428,27449,39150,27463,27459,27465,27472,27481,27476,27483,27487,27489,27512,[12109,27513],{f:2,c:27519},27524,27523,27533,27544,27541,27550,27556,{f:2,c:27562},27567,27570,27569,[12110,27571],27575,27580,27590,[1 [...]
+27603,27615,27628,27627,27635,27631,40638,27656,27667,[12115,27668],27675,27684,27683,27742,27733,27746,27754,27778,27789,27802,27777,27803,27774,27752,27763,27794,27792,27844,27889,27859,27837,27863,27845,27869,27822,27825,27838,27834,27867,27887,27865,27882,27935,34893,27958,27947,27965,27960,27929,27957,27955,27922,27916,28003,28051,28004,27994,28025,27993,28046,28053,28644,28037,28153,28181,28170,28085,28103,28134,28088,28102,28140,28126,28108,28136,28114,28101,28154,28121,28132,2811 [...]
+28205,28270,28206,28185,28274,28255,28222,28195,28267,28203,28278,28237,28191,28227,28218,28238,28196,28415,28189,28216,28290,28330,28312,28361,28343,28371,28349,28335,28356,28338,{f:2,c:28372},28303,28325,28354,28319,28481,28433,28748,28396,28408,28414,28479,28402,28465,28399,28466,28364,28478,28435,28407,28550,28538,28536,28545,28544,28527,28507,28659,28525,28546,28540,28504,28558,28561,28610,28518,28595,28579,28577,28580,28601,28614,28586,28639,28629,28652,28628,28632,28657,28654,2863 [...]
+28666,28689,28673,28687,28670,28699,28698,28532,28701,28696,28703,28720,28734,28722,28753,28771,28825,28818,28847,28913,28844,28856,28851,28846,28895,28875,28893,28889,28937,28925,28956,28953,29029,29013,29064,29030,29026,29004,29014,29036,29071,29179,29060,29077,29096,29100,29143,29113,29118,29138,29129,29140,29134,29152,29164,29159,29173,29180,29177,29183,29197,29200,29211,29224,29229,29228,29232,29234,[12120,29243],29244,[12121,29247],29248,29254,29259,29272,29300,29310,29314,29313,29 [...]
+29334,29346,29351,29369,29362,29379,29382,29380,29390,29394,29410,{f:2,c:29408},29433,29431,20495,29463,29450,29468,29462,29469,29492,29487,29481,29477,29502,{f:2,c:29518},40664,29527,29546,29544,29552,29560,29557,29563,29562,29640,29619,29646,29627,29632,29669,29678,29662,29858,29701,29807,29733,29688,29746,29754,29781,29759,29791,29785,29761,29788,29801,29808,29795,29802,29814,29822,29835,29854,29863,29898,29903,29908,29681,29920,29923,29927,29929,29934,29938,{f:2,c:29936},29944,29943, [...]
+29957,29964,29966,29965,29973,29971,29982,29990,29996,30012,30020,30029,30026,30025,30043,30022,30042,30057,30052,30055,30059,30061,30072,30070,{f:2,c:30086},30068,30090,30089,30082,30100,30106,30109,30117,30115,30146,30131,30147,30133,30141,30136,30140,30129,30157,30154,30162,30169,30179,30174,{f:2,c:30206},30204,30209,30192,30202,{f:2,c:30194},30219,30221,30217,30239,30247,{f:3,c:30240},30244,30260,30256,30267,{f:2,c:30279},30278,30300,30296,{f:2,c:30305},{f:3,c:30312},30311,30316,3032 [...]
+30326],30328,30332,30336,30339,30344,30347,30350,30358,30355,{f:2,c:30361},30384,30388,{f:3,c:30392},30402,30413,30422,30418,30430,30433,30437,30439,30442,34351,30459,30472,30471,30468,30505,30500,30494,{f:2,c:30501},30491,{f:2,c:30519},30535,30554,30568,30571,30555,30565,30591,30590,30585,30606,30603,30609,30624,30622,30640,30646,30649,30655,{f:2,c:30652},30651,30663,30669,30679,30682,30684,30691,30702,30716,30732,30738,31014,30752,31018,30789,30862,30836,30854,30844,30874,30860,30883,3 [...]
+30895,30929,30918,30923,30932,30910,30908,30917,30922,30956,30951,30938,30973,30964,30983,30994,30993,31001,31020,31019,31040,31072,31063,31071,31066,31061,31059,31098,31103,31114,31133,31143,40779,31146,31150,31155,{f:2,c:31161},31177,31189,31207,31212,31201,31203,31240,31245,{f:2,c:31256},31264,31263,31104,31281,31291,31294,31287,31299,31319,31305,{f:2,c:31329},31337,40861,31344,31353,31357,31368,31383,31381,31384,31382,31401,31432,31408,31414,31429,31428,31423,36995,31431,31434,31437, [...]
+31443,{f:2,c:31449},31453,{f:2,c:31457},31462,31469,31472,31490,31503,31498,31494,31539,{f:2,c:31512},31518,31541,31528,31542,31568,31610,31492,31565,31499,31564,31557,31605,31589,31604,31591,{f:2,c:31600},31596,31598,31645,31640,31647,31629,31644,31642,31627,31634,31631,31581,31641,31691,31681,31692,31695,31668,31686,31709,31721,31761,31764,31718,31717,31840,31744,31751,31763,31731,31735,31767,31757,31734,31779,31783,31786,31775,31799,31787,31805,31820,31811,31828,31823,31808,31824,3183 [...]
+31830,31845,31852,31861,31875,31888,31908,31917,31906,31915,31905,31912,31923,31922,31921,31918,31929,31933,31936,31941,31938,31960,31954,31964,31970,39739,31983,31986,31988,31990,31994,32006,32002,32028,32021,32010,32069,32075,32046,32050,32063,32053,32070,32115,32086,32078,32114,32104,32110,32079,32099,32147,32137,32091,32143,32125,32155,32186,32174,32163,32181,32199,32189,32171,32317,32162,32175,32220,32184,32159,32176,32216,32221,32228,32222,32251,32242,32225,32261,32266,32291,32289, [...]
+32287,32265,32267,32290,32326,32358,32315,32309,32313,32323,32311,32306,32314,32359,32349,32342,32350,{f:2,c:32345},32377,32362,32361,32380,32379,32387,32213,32381,36782,32383,{f:2,c:32392},32396,32402,32400,{f:2,c:32403},32406,32398,{f:2,c:32411},32568,32570,32581,{f:3,c:32588},32592,[12153,32593],32597,32596,32600,{f:2,c:32607},{f:2,c:32616},32615,32632,32642,32646,32643,32648,32647,32652,32660,32670,32669,32666,32675,32687,32690,32697,32686,32694,32696,35697,{f:2,c:32709},32714,32725, [...]
+32742,32745,32755,32761,39132,32774,32772,32779,[12158,32786],{f:2,c:32792},32796,32801,32808,32831,32827,32842,32838,32850,32856,32858,32863,32866,32872,32883,32882,32880,32886,32889,32893,[12160,32895],32900,32902,32901,32923,32915,32922,32941,20880,32940,32987,32997,32985,32989,32964,32986,32982,33033,33007,33009,33051,33065,33059,33071,33099,38539,33094,33086,33107,33105,33020,33137,33134,{f:2,c:33125},33140,33155,33160,33162,33152,33154,33184,33173,33188,33187,33119,33171,33193,3320 [...]
+33208,33213,33216,33218,33210,33225,33229,33233,33241,33240,33224,33242,{f:2,c:33247},33255,{f:2,c:33274},33278,{f:2,c:33281},33285,33287,33290,33293,33296,33302,33321,33323,33336,33331,33344,33369,33368,33373,33370,33375,33380,33378,33384,{f:2,c:33386},33326,33393,33399,[12171,33400],33406,33421,33426,33451,33439,33467,33452,33505,33507,33503,33490,33524,33523,33530,33683,33539,33531,33529,33502,33542,33500,33545,33497,33589,33588,33558,33586,33585,33600,33593,33616,33605,33583,33579,{f [...]
+33669,33690,33706,33695,33698,33686,33571,33678,33671,33674,33660,33717,33651,33653,33696,33673,33704,33780,33811,33771,33742,33789,33795,33752,33803,33729,33783,33799,33760,33778,33805,33826,33824,33725,33848,34054,33787,33901,33834,33852,34138,33924,33911,33899,33965,33902,33922,33897,33862,33836,33903,33913,33845,33994,33890,33977,33983,33951,34009,33997,33979,34010,34E3,33985,33990,34006,33953,34081,34047,34036,{f:2,c:34071},34092,34079,34069,34068,34044,34112,34147,34136,34120,34113 [...]
+34133,34176,34212,34184,34193,34186,34216,34157,34196,34203,34282,34183,34204,34167,34174,34192,34249,34234,34255,34233,34256,34261,34269,34277,34268,34297,34314,34323,34315,34302,34298,34310,34338,34330,34352,34367,[12172,34381],20053,34388,34399,34407,34417,34451,34467,{f:2,c:34473},{f:2,c:34443},34486,34479,34500,34502,34480,34505,34851,34475,34516,34526,34537,34540,34527,34523,34543,34578,34566,34568,34560,34563,34555,34577,34569,34573,34553,34570,34612,34623,34615,34619,34597,34601, [...]
+34655,34680,34636,34638,34676,34647,34664,34670,34649,34643,34659,34666,34821,34722,34719,34690,34735,34763,34749,34752,34768,38614,34731,34756,34739,34759,34758,34747,34799,34802,34784,34831,34829,34814,{f:2,c:34806},34830,34770,34833,34838,34837,34850,34849,34865,34870,34873,34855,34875,34884,34882,34898,34905,34910,34914,34923,34945,34942,34974,34933,34941,34997,34930,34946,34967,34962,34990,34969,34978,34957,34980,34992,35007,34993,{f:2,c:35011},35028,{f:2,c:35032},35037,35065,35074, [...]
+35048,35058,35076,35084,35082,35091,35139,35102,35109,{f:2,c:35114},35137,35140,35131,35126,35128,35148,35101,35168,35166,35174,35172,35181,35178,35183,35188,35191,[12177,35198],35203,35208,35210,35219,35224,35233,35241,35238,35244,35247,35250,35258,35261,{f:2,c:35263},35290,{f:2,c:35292},35303,35316,35320,35331,35350,35344,35340,35355,35357,35365,35382,35393,35419,35410,35398,35400,35452,35437,35436,35426,35461,35458,35460,35496,35489,35473,{f:2,c:35493},35482,35491,35524,35533,35522,35 [...]
+35571,35559,35556,35569,35604,35552,35554,35575,35550,35547,35596,35591,35610,35553,35606,35600,35607,35616,35635,38827,35622,35627,35646,35624,35649,35660,35663,35662,35657,35670,35675,35674,35691,35679,35692,35695,35700,35709,35712,35724,35726,{f:2,c:35730},35734,{f:2,c:35737},35898,35905,35903,35912,35916,35918,35920,[12183,35925],35938,35948,[12184,35960],35962,35970,35977,35973,35978,{f:2,c:35981},35988,35964,35992,25117,36013,36010,36029,{f:2,c:36018},36014,36022,36040,36033,36068, [...]
+36093,{f:2,c:36090},{f:2,c:36100},36106,36103,36111,36109,36112,40782,36115,36045,36116,36118,36199,36205,36209,36211,36225,36249,36290,36286,36282,36303,36314,36310,36300,36315,36299,{f:2,c:36330},36319,36323,36348,{f:2,c:36360},36351,{f:2,c:36381},36368,36383,36418,36405,36400,36404,36426,36423,36425,36428,36432,36424,36441,36452,36448,36394,36451,36437,36470,36466,36476,36481,36487,36485,36484,36491,36490,36499,36497,36500,36505,36522,36513,36524,36528,36550,36529,36542,36549,36552,36 [...]
+36579,36604,36603,36587,36606,36618,36613,36629,36626,36633,36627,36636,36639,36635,36620,36646,36659,36667,36665,36677,36674,36670,36684,36681,36678,36686,36695,36700,{f:3,c:36706},36764,36767,36771,36781,36783,36791,36826,36837,36834,36842,36847,36999,36852,36869,{f:2,c:36857},36881,36885,36897,36877,36894,36886,36875,36903,36918,36917,36921,36856,{f:4,c:36943},36878,36937,36926,36950,36952,36958,36968,36975,36982,38568,36978,36994,36989,36993,36992,37002,37001,37007,37032,37039,37041, [...]
+37092,25160,37083,37122,37138,37145,37170,37168,37194,37206,37208,37219,37221,37225,37235,37234,37259,37257,37250,37282,37291,37295,37290,37301,37300,37306,{f:2,c:37312},37321,37323,37328,37334,37343,37345,37339,37372,{f:2,c:37365},37406,37375,37396,37420,37397,37393,37470,37463,37445,37449,37476,37448,37525,37439,37451,37456,37532,37526,37523,37531,37466,37583,37561,37559,37609,37647,37626,37700,37678,37657,37666,37658,37667,37690,37685,37691,37724,37728,37756,37742,37718,37808,{f:2,c:3 [...]
+37817,{f:2,c:37846},37864,37861,37848,37827,37853,37840,37832,37860,37914,37908,37907,37891,37895,37904,37942,37931,37941,37921,37946,37953,37970,37956,37979,37984,37986,37982,37994,37417,38E3,38005,38007,38013,37978,38012,38014,38017,38015,38274,38279,38282,38292,38294,{f:2,c:38296},38304,38312,38311,38317,38332,38331,38329,38334,38346,28662,38339,38349,38348,38357,38356,38358,38364,38369,38373,38370,38433,38440,{f:2,c:38446},38466,38476,38479,38475,38519,38492,38494,38493,38495,38502,3 [...]
+38541,38552,38549,38551,38570,38567,{f:2,c:38577},38576,38580,[12202,38582],38584,[12203,38585],38606,38603,38601,38605,35149,38620,38669,38613,38649,38660,38662,38664,38675,38670,38673,38671,38678,38681,38692,38698,38704,38713,{f:2,c:38717},38724,38726,38728,38722,38729,38748,38752,38756,38758,38760,21202,38763,38769,38777,38789,38780,38785,38778,38790,38795,{f:2,c:38799},38812,38824,38822,38819,{f:2,c:38835},38851,38854,38856,[12209,38859],38876,[12210,38893],40783,38898,31455,38902,38 [...]
+38924,38968,38948,38945,38967,38973,38982,38991,38987,39019,{f:3,c:39023},39028,39027,39082,39087,39089,39094,39108,39107,39110,39145,39147,39171,39177,39186,39188,39192,39201,{f:2,c:39197},39204,39200,39212,39214,{f:2,c:39229},39234,39241,39237,39248,39243,{f:2,c:39249},39244,39253,{f:2,c:39319},39333,{f:2,c:39341},39356,39391,39387,39389,39384,39377,{f:2,c:39405},{f:2,c:39409},39419,39416,39425,39439,39429,39394,39449,39467,39479,39493,39490,39488,39491,39486,39509,39501,39515,39511,39 [...]
+39525,39524,39529,39531,39530,39597,39600,39612,39616,39631,39633,{f:2,c:39635},39646,[12221,39647],{f:2,c:39650},39654,39663,39659,39662,39668,39665,39671,39675,39686,39704,39706,39711,{f:2,c:39714},[12222,39717],{f:4,c:39719},39726,[12223,39727],[12224,39730],39748,39747,39759,{f:2,c:39757},39761,39768,39796,39827,39811,39825,{f:2,c:39830},{f:2,c:39839},39848,39860,39872,39882,39865,39878,39887,{f:2,c:39889},39907,39906,39908,39892,39905,39994,39922,39921,39920,39957,39956,39945,39955, [...]
+39944,39954,39946,39940,39982,39963,39973,39972,39969,39984,40007,39986,40006,39998,40026,40032,40039,40054,40056,40167,40172,40176,40201,40200,40171,40195,40198,40234,40230,40367,40227,40223,40260,40213,40210,40257,40255,40254,40262,40264,{f:2,c:40285},40292,40273,40272,40281,40306,40329,40327,40363,40303,40314,40346,40356,40361,40370,40388,40385,40379,40376,40378,40390,40399,40386,40409,40403,40440,40422,40429,40431,40445,{f:2,c:40474},40478,[12228,40565],40569,40573,40577,40584,{f:2,c [...]
+40597,40593,40605,[12230,40613],40617,40632,40618,40621,38753,40652,{f:3,c:40654},40660,40668,40670,40669,40672,40677,40680,40687,40692,{f:2,c:40694},[12235,40697],{f:2,c:40699},[12236,40701],{f:2,c:40711},30391,40725,40737,40748,40766,[12241,40778],[12242,40786],40788,40803,{f:3,c:40799},{f:2,c:40806},40812,40810,40823,40818,40822,40853,[12244,40860],[12245,40864],22575,27079,36953,29796,0,{f:76,c:9472},{f:20,c:9312},{f:10,c:8544},13129,13076,0,13133,0,13095,0,13110,13137,0,13069,13094, [...]
+0,{f:3,c:13212},{f:2,c:13198},13252,13217,12317,12319,8470,13261,0,{f:5,c:12964},{f:2,c:12849},12857,13182,13181,13180,8750,8721,{s:3},8735,8895,0,0,21854,{s:7},167133,0,0,28976,0,40407,{s:4},64054,0,0,22169,15694,{s:4},20448,0,0,36544,0,194797,{s:4},153716,32363,33606,167670,{s:3},40572,0,0,26171,0,40628,{s:4},26629,{s:5},23650,0,194780,0,32353,0,0,64070,{s:5},34083,37292,{s:7},34796,{s:8},25620,0,0,39506,{s:4},64074,0,194692,{s:4},31774,{s:6},64016,25681,0,0,63980,22625,39002,0,194679, [...]
+0,28678,{s:9},22218,{s:3},21085,0,28497,37297,{s:10},64106,{s:6},38960,0,40629,{s:9},33802,63939,{f:2,c:63890},63897,0,34847,194575,0,194771,194584,{s:7},137754,23643,{s:4},25890,0,0,26618,0,26766,0,148432,194848,{s:21},34110,{s:15},30562,{s:12},65075,0,{f:2,c:65073},{s:4},65072,{f:2,c:65077},{f:2,c:65081},0,0,{f:2,c:65079},{f:2,c:65087},{f:2,c:65085},{f:4,c:65089},{f:2,c:65083},{s:41},{f:3,c:12436},0,0,22099,{s:41},65508,65287,65282,0,9665,9655,8681,8679,8678,8680,9634,9831,9825,9828,98 [...]
+13218,{f:2,c:13220},13207,8467,13208,13235,13234,13233,13232,{f:3,c:13189},13259,13200,13268,13206,13090,13078,13080,13077,13059,13091,13143,13122,13113,13115,13056,13105,13127,13086,13098,0,13183,8481,9742,12342,12320,{s:3},{f:9,c:9352},{f:20,c:9332},12881,{f:10,c:8560},{f:10,c:12882},{f:26,c:9372},12867,12861,12863,12852,12856,12851,12860,12866,12862,12854,12853,12859,12864,12858,12976,12973,12969,12975,12948,12970,12952,12971,12946,12945,12947,12972,12974,12950,{s:8},{f:3,c:9131},0,{f [...]
+0,13260,13061,0,0,13215,13219,13222,0,0,12958,{f:2,c:13192},13256,8749,0,12848,{f:6,c:12842},12855,12865,10145,{s:3},9673,9824,9829,9827,9830,{f:4,c:9728},9758,{f:2,c:9756},9759,12953,9450,{f:2,c:8554},{s:3},{f:8,c:9601},9615,9614,9613,9612,9611,9610,9609,{f:2,c:9620},{f:2,c:9581},9584,9583,9552,9566,9578,9569,{f:2,c:9698},9701,9700,0,0,{f:3,c:9585},{s:20},20956,29081,{f:9,c:10102},{s:3},{f:2,c:8570},{s:3},8575,8458,8457,0,0,12292,8646,{f:2,c:8644},0,{f:4,c:12535},0,0,12957,{s:3},13179,{ [...]
+13134,{s:30},32394,35100,37704,37512,34012,20425,28859,26161,26824,37625,26363,24389,[12033,20008],20193,20220,20224,20227,20281,20310,20370,20362,20378,20372,20429,20544,20514,20479,20510,20550,20592,20546,20628,20724,20696,20810,20836,20893,20926,20972,21013,21148,21158,21184,21211,21248,0,21284,21362,21395,21426,21469,64014,21660,21642,21673,21759,21894,22361,22373,22444,22472,22471,64015,0,22686,22706,22795,22867,22875,22877,22883,22948,22970,23382,23488,29999,23512,0,23582,23718,237 [...]
+23847,23891,0,23874,23917,{f:2,c:23992},24016,24353,24372,24423,24503,24542,24669,24709,24714,24798,24789,24864,24818,24849,24887,24880,24984,25107,25254,25589,25696,25757,25806,25934,26112,26133,26121,26158,0,26148,26213,26199,26201,64018,26227,26265,26272,26290,26303,26362,26382,0,26470,26555,26706,26560,0,26692,26831,64019,26984,64020,27032,27106,27184,27243,27206,27251,27262,27362,27364,27606,27711,27740,27782,27759,27866,27908,28039,28015,28054,28076,28111,28152,28146,28156,28217,28 [...]
+28220,28351,28552,28597,28661,28677,28679,28712,28805,28843,28943,28932,29020,{f:2,c:28998},0,29121,29182,29361,29374,29476,64022,29559,29629,29641,29654,29667,29650,29703,29685,29734,29738,29737,29742,0,29833,29855,29953,30063,30338,30364,30366,30363,30374,64023,30534,21167,30753,30798,30820,30842,31024,{f:3,c:64024},31124,64027,31131,31441,31463,64028,31467,31646,64029,32072,0,32183,32160,32214,32338,32583,32673,64030,33537,33634,33663,33735,33782,33864,33972,34131,34137,34155,64031,34 [...]
+c:64032},34823,35061,35346,35383,35449,35495,35518,35551,64034,35574,35667,35711,36080,36084,36114,36214,64035,36559,0,64037,36967,37086,64038,37141,37159,37338,37335,37342,{f:2,c:37357},{f:2,c:37348},37382,37392,37386,37434,37440,37436,37454,37465,37457,37433,37479,37543,{f:2,c:37495},37607,37591,37593,37584,64039,37589,37600,37587,37669,37665,37627,64040,37662,37631,37661,37634,37744,37719,37796,37830,37854,37880,37937,37957,37960,38290,0,64041,38557,38575,38707,38715,38723,38733,38735 [...]
+0,38999,39013,{f:2,c:64042},39207,64044,39326,39502,39641,39644,39797,39794,39823,39857,39867,39936,40304,40299,64045,40473,40657,{s:636},8364,8486,0,0,64256,{f:2,c:64259},257,299,363,275,333,256,298,362,274,332,{f:4,c:8539},{f:2,c:8531},8304,{f:6,c:8308},{f:10,c:8320},461,282,0,7868,463,0,296,465,0,467,366,360,462,283,0,7869,464,0,297,466,0,468,367,361,593,8049,8048,509,0,596,0,0,601,0,0,602,0,0,603,8051,8050,0,331,629,652,0,0,658,643,720,{s:682},{f:10,c:12832},{s:108},{f:4,c:12892},{f: [...]
+{s:50},{f:26,c:9424},{f:26,c:9398},{s:48},{f:47,c:13008},0,{f:10,c:12928},12944,{f:6,c:12938},0,12959,{s:6},{f:2,c:12960},12955,12954,12963,12962,12951,0,12956,12949,{s:6},9676,{s:11},10111,{f:10,c:9451},{s:510},8414,{s:815},13274,{s:3},8448,13250,0,0,8453,0,13169,0,0,13197,13211,{s:3},{f:2,c:13271},{s:3},{f:2,c:13057},13060,13062,0,13064,0,13063,13066,0,13065,0,13067,0,13068,{f:6,c:13070},0,13079,0,13081,0,{f:4,c:13082},{f:3,c:13087},13092,0,13093,0,0,{f:2,c:13096},0,13101,0,0,{f:3,c:13 [...]
+0,0,{f:2,c:13108},13116,{s:3},13111,0,13112,13114,13117,13121,{f:3,c:13118},{f:4,c:13123},13128,{f:2,c:13131},{f:2,c:13135},0,0,13138,13140,0,0,13139,{f:2,c:13141},{s:132},8501,976,8714,8463,0,981,987,977,0,{f:2,c:9832},9836,{s:5},12347,0,{f:3,c:12339},8252,8265,{s:5},8723,0,8771,{f:2,c:8818},{s:6},{f:2,c:12312},{f:2,c:65375},{s:10},9115,{f:2,c:9117},9120,{s:4},9121,{f:2,c:9123},9126,{s:12},[9116,9119,9122,9125,9130],{s:8},9986,0,0,12349,0,12447,0,0,8709,8864,8854,8856,8853,8855,{s:4},96 [...]
+8656,8596,{f:2,c:8600},{f:2,c:8598},8652,8651,{s:10},12336,8967,{s:8},10048,10047,{s:7},9643,0,9642,0,10010,{s:12},9702,{s:4},10070,{s:379},{f:2,c:65093},{s:679},64103,64098,32227,[12232,40643],28331,64082,64061,64069,64062,27114,28212,64096,64071,64056,64066,64078,34395,64105,64052,64099,25581,25802,30799,64084,63856,64077,64097,64072,64076,{f:2,c:64091},64081,64067,64090,28041,29376,0,194885,64086,64080,64049,64059,24034,64063,64101,21373,64055,64095,24501,64064,0,64083,0,64085,64104,6 [...]
+26202,64053,64075,64100,64065,64048,0,64057,64051,27493,64058,27599,64050,25150,64079,63773,63964,63798,28122,63952,26310,27511,64087,37706,0,37636,{s:120},133390,{s:120},35999,11991,[11965,158033],{s:5},37555,38321,0,0,194812,{s:13},194965,{s:8},194794,0,26478,11974,0,194594,{s:13},13314,0,0,26083,{s:4},134071,{s:10},171339,0,194611,24378,{s:8},11945,0,20465,{s:7},63753,{s:7},11964,0,0,194732,26435,{s:3},133732,35329,25142,0,0,21555,23067,{s:3},25221,0,0,194819,{s:6},21567,{s:9},27506,{ [...]
+19256,0,0,24063,{s:6},194827,29626,134047,{s:3},194600,0,194849,{s:5},194623,{s:16},194675,{f:2,c:11916},23577,{s:3},131083,23426,194642,{s:5},11997,[11999,39136],[11998,169599],14221,0,[11927,14586],0,194887,0,[11909,20155],131490,{s:7},13599,0,194738,0,0,[11971,35200],{s:4},31237,{s:4},35498,0,32085,0,28568,{s:7},25591,30246,{s:4},[11978,163767],{s:5},146686,{s:5},13351,0,0,33067,0,0,194842,{s:5},11950,{s:5},194714,{s:3},194831,{s:19},22305,135741,194586,0,64003,{s:7},21534,15240,20839 [...]
+{s:9},20023,{s:13},[11946,150804],24421,23020,194658,0,24217,{s:46},13416,{s:8},21200,{s:9},26625,0,195024,195039,{s:5},153215,0,0,11959,{s:4},36534,63775,{s:3},63875,{s:5},31867,63906,0,63898,0,[11961,32770],157360,{s:4},[11911,132648],0,0,131210,194604,[11915,13630],{s:4},21589,0,22841,0,0,23414,194669,23572,14306,23782,0,20040,0,0,194742,{s:4},158105,25371,0,0,26211,0,194779,0,0,27126,27014,{s:3},27596,0,28183,0,0,27818,{s:3},[11942,20012],0,0,29935,30069,30188,30286,16305,30570,30633 [...]
+0,0,16996,{s:3},194924,0,0,32328,{s:5},11955,{s:4},33089,17491,0,[11966,33401],[11967,64094],[11968,64093],0,20857,33626,{s:3},17701,0,34292,131248,{s:4},34429,0,13358,35014,{s:6},18406,{s:8},36808,{s:19},166279,0,0,167447,0,0,38969,{s:6},39432,{s:4},39903,{s:10},148206,{s:5},21385,0,64017,194785,0,146622,132625,0,{f:2,c:19972},19999,20011,{f:2,c:20015},{f:2,c:20032},20036,[11907,20058],20095,20109,20118,20153,20176,20192,20221,20223,20235,20245,20320,20283,20297,20308,20346,{f:2,c:20349 [...]
+20431,20477,{f:2,c:20480},20496,20507,20519,20526,20567,20582,20586,20539,20623,20630,20636,20684,20710,20713,20719,20744,20747,20752,20763,20766,20831,20897,20924,0,20974,20980,20993,[11913,20994],21011,21065,21089,21094,21139,21192,21232,{f:2,c:21258},21310,21324,21323,21345,21356,21419,21466,21478,21493,21543,21581,21606,21611,21620,21645,21654,21665,21677,21689,21695,21702,21709,21774,21803,21813,21834,21856,0,21896,21902,22024,{f:2,c:22030},22071,22079,22089,22091,22095,22118,22121, [...]
+c:22129},22165,22170,{f:2,c:22188},22193,22217,22237,22244,22282,22293,22307,22319,{f:2,c:22323},22348,22384,22412,22428,22456,22502,22509,{f:2,c:22517},22527,22537,22560,22578,22652,22656,22697,22734,22736,22740,22746,22761,22796,22820,22831,22881,22893,22986,22994,23005,{f:2,c:23011},23044,23052,23075,23111,23125,23139,23149,23166,23198,23207,23212,23219,23264,23296,23321,23333,23341,23361,23420,{f:2,c:23422},23434,[11919,23587],23595,23600,23651,23657,23676,23755,23762,23796,23844,238 [...]
+23878,23882,23954,23956,23961,23968,24024,24032,24056,24064,24082,{f:2,c:24084},24088,24110,24152,{f:2,c:24171},24232,24234,{f:2,c:24254},0,24274,24327,24334,{f:2,c:24348},24354,24360,24374,24379,24384,[12089,24400],24408,24420,24457,24476,24487,24484,24495,24504,[11926,24516],24521,24545,24553,24557,24572,24599,24602,24627,24673,24703,24734,24740,24752,24779,24795,24824,{f:3,c:24850},24860,24956,24973,24991,25E3,25026,25055,25109,25129,25155,25158,[11928,25164],25169,25174,25284,25340,2 [...]
+25368,25401,{f:2,c:25410},25445,25460,25469,25476,25479,25488,25502,25553,25564,25609,25616,25634,25684,25691,25709,25723,{f:2,c:25790},25829,25847,25851,25860,25878,25881,25927,25959,25985,25989,26050,26096,26098,26156,26188,{f:2,c:26203},26209,26219,0,26276,26312,26348,26373,26387,26419,26440,26444,26486,26491,26544,26546,26617,26583,26585,26608,26668,{f:2,c:26672},26715,26738,26741,26746,26756,26789,26802,26832,26838,26856,26861,{f:2,c:26864},26876,26897,26899,26933,26939,26967,26979, [...]
+c:27007},27046,27053,27063,{f:2,c:27094},27137,27151,27157,27176,27188,27198,27205,{f:2,c:27216},27222,27227,27267,27273,27281,{f:3,c:27293},27356,27367,27372,27422,27428,27445,27462,27478,27488,27522,27582,27617,27633,27664,27699,[11937,27701],11938,27737,27766,27771,27781,27797,27804,27856,27860,27862,27872,{f:2,c:27883},27886,27914,27918,27921,27950,27991,27998,28005,28034,28095,28100,28106,28118,28137,28194,28241,28359,28362,28366,28413,28442,28458,28463,28467,28506,28510,28514,28541 [...]
+28562,28564,28570,{f:2,c:28583},28598,28634,28638,0,28729,28732,0,28756,{f:2,c:28765},28772,[11939,28780],28798,28801,28821,28855,{f:2,c:28883},28888,28892,28935,28960,28977,29002,29010,29024,29049,29074,0,29131,29139,29142,29184,29213,29227,29240,29249,29267,{f:2,c:29269},29276,29325,[11944,29357],29364,29383,29435,{f:2,c:29444},29480,29489,29507,29548,29564,29571,{f:2,c:29573},29589,{f:3,c:29598},29606,29611,29621,29623,29628,29647,29657,29673,29684,29693,29700,29706,{f:2,c:29722},2973 [...]
+{f:3,c:29743},29753,29764,29767,29771,29773,29777,29783,29798,29803,29809,29824,{f:3,c:29829},29840,29848,29852,29856,29859,29864,29867,29877,29887,29896,29914,29918,30030,30073,30081,30096,[12135,30098],30099,30132,30180,30201,30208,30218,{f:2,c:30229},30233,30238,30253,30261,30275,30283,30309,30317,30319,30321,30324,{f:2,c:30372},30405,30412,30444,30460,30516,30518,30556,{f:2,c:30559},30578,30589,30613,30634,30694,30704,30708,30726,30754,{f:2,c:30765},30768,30773,30824,30878,30920,3092 [...]
+{f:2,c:30944},30962,30967,30971,31025,0,[11949,31035],31037,31045,{f:2,c:31067},31115,31126,31128,[12145,31160],31163,31178,31194,31235,31241,31249,31262,31277,31289,31301,31308,31325,0,31341,31352,31392,31395,31411,{f:2,c:31419},31430,31495,31508,31527,31537,31559,31566,31584,31593,31597,31602,31633,31663,31703,31705,31755,31759,31776,31782,31793,31798,31825,31833,31847,31854,31856,31932,31935,{f:2,c:31944},31959,31961,31965,31979,{f:3,c:32007},32019,32029,32035,32065,32083,32089,32093, [...]
+{f:2,c:32139},32204,32235,32241,32249,32264,32273,32277,32288,32327,32354,32366,32371,32397,32401,32408,32580,32591,[11947,11954,32594],[11953,32595],32609,32657,32703,32718,32735,32741,32748,{f:2,c:32750},32762,32782,32785,32788,32804,32806,32826,32828,32864,32881,32885,32926,32934,32939,{f:2,c:32983},33046,33048,33082,33098,33100,33153,33156,33204,33231,33273,33283,33313,33330,33332,33350,33355,33359,33422,33454,33463,33470,33478,33534,33603,33617,33621,33670,33677,33682,33688,33705,{f [...]
+33770,33807,33809,33866,33910,33960,33967,33984,33986,34032,34045,34060,34100,34142,34191,34231,34254,34221,34322,34345,34386,34403,34412,34415,34426,34445,34449,34456,{f:2,c:34471},34554,34557,34571,34579,34585,34590,34600,34622,34673,34696,34713,{f:2,c:34732},34741,34774,34795,34797,34817,0,34822,34827,34836,34844,34902,34911,[11970,34916],34968,34986,{f:2,c:35005},35018,35026,35035,{f:2,c:35056},35078,{f:3,c:35096},35111,35120,35134,35195,35284,35286,35301,35313,35335,35343,35349,3536 [...]
+35572,35615,35639,{f:2,c:35651},35668,35740,35742,35911,35924,35955,36004,36057,36065,36088,36094,36123,36201,36204,36228,36237,36245,36262,36294,36302,36324,36332,36384,36427,36460,36464,36474,36498,36526,36531,36561,36564,36601,36631,36662,36774,[12193,36789],[11981,36790],0,36832,36836,36854,36866,36908,36932,37E3,37013,37017,37019,37026,37044,37079,37085,37108,37143,37148,37169,37178,37181,37192,37211,37217,37220,37262,37278,37288,{f:2,c:37293},37298,37308,37360,37367,37371,37383,374 [...]
+37432,37443,37447,37455,37472,37570,{f:2,c:37579},37599,37645,37653,37663,37671,37703,37714,0,37738,37741,37787,37818,37801,37825,37834,37858,37882,37885,37903,37940,37951,37973,37995,38002,[11986,38264],38310,38313,0,38324,38333,38362,[11983,11990,38429],38465,38488,38532,38564,38569,38610,195060,38622,38633,38641,38658,38665,38746,38755,38766,38771,38810,38818,{f:2,c:38837},38873,38878,38900,38922,38926,38942,38947,38955,38974,{f:2,c:38994},39001,39020,39096,39098,39103,39112,39141,{f: [...]
+39232,39245,39260,39263,39345,{f:2,c:39353},39369,39426,39446,39460,39463,{f:2,c:39469},39478,39480,39498,39510,{f:2,c:39605},39673,39683,39712,{f:2,c:39731},39795,39801,39847,39873,39879,39895,39911,39915,39927,39930,39933,39947,39975,39978,39990,40001,40019,40035,40048,40055,40194,40258,40263,40291,40297,40316,40318,40333,40369,40387,40391,40406,40415,40427,40436,40469,40477,40612,40616,40620,40679,40686,40720,40722,40727,40729,40751,40759,40761,40769,40773,40791,40808,40817,40821,4084 [...]
+0,13317,194564,22048,24267,11925,0,144954,0,28665,28390,29107,[11940,64073],{s:4},[11980,64102],0,23986,0,20435,20697,20720,20931,22134,27220,27905,28112,28226,28377,29668,29729,30060,30801,34805,144382,29608,15091,13531,17420,16010,0,0,19432,0,16090,15138,0,17786,16531,0,18021,16643,17043,18094,13448,140809,{f:3,c:63584},63610,63615,{s:23},{f:2,c:8836},{f:2,c:8842},8713,0,{f:2,c:8965},{s:9},{f:2,c:8741},{s:14},8802,0,8773,8776,{f:2,c:8822},{s:4},8487,{s:209},{f:2,c:8922},8533,8984,{f:2, [...]
+c:504},470,472,474,476,260,728,317,346,350,356,377,379,261,731,318,347,711,351,357,378,733,380,340,258,313,262,268,280,270,323,327,336,344,368,354,341,259,314,263,269,281,271,273,324,328,337,345,369,355,729,264,284,292,308,348,364,265,285,293,309,349,365,625,651,638,620,622,633,648,598,627,637,642,656,635,621,607,626,669,654,609,624,641,295,661,660,614,664,450,595,599,644,608,403,616,649,600,604,606,592,623,650,612,594,653,613,674,673,597,657,634,615,865,712,716,721,8255,783,{f:5,c:741}, [...]
+825,796,{f:2,c:799},829,809,815,734,804,816,828,820,{f:2,c:797},{f:2,c:792},810,{f:2,c:826},794,{s:3},{f:2,c:610},618,628,630,632,640,655,665,668,671,688,690,695,704,{f:2,c:736},{s:6},8862,{s:287},12348,12543,0,{f:2,c:12310},9838,9835,{f:2,c:10548},10687,0,12448,0,{f:2,c:10746},{s:13},962,{f:10,c:9461},{f:2,c:9750},9649,{f:10,c:12784},0,{f:6,c:12794},{f:15,c:9150},0,0,10003,0,9251,9166,{f:4,c:9680},{f:2,c:8263},0,8273,8258,{f:16,c:12688},{s:13},{f:2,c:9136},{f:12,c:9842},{f:2,c:12441},84 [...]
+20296,20319,20330,20332,20494,20504,20545,20722,20688,20742,20739,20789,20821,20823,13493,20938,20962,21079,21196,21206,21243,21276,21347,21405,21522,21631,21640,21840,21889,21933,21966,22075,22174,22185,22195,22391,22396,135963,22479,22500,22628,22665,136302,22738,22752,34369,22923,22930,22979,23059,23143,23159,23172,23236,137405,23421,23443,23570,64060,136884,23674,23695,23711,23715,23722,23760,138804,23821,23879,23937,23972,23975,24011,24158,24313,24320,24322,24355,24381,24404,24445,2 [...]
+24600,24629,24647,24733,24788,24797,24875,25020,25017,25122,25178,25199,25302,25468,25573,25721,25796,25808,25897,26013,26170,26146,26155,26160,26163,26184,143812,{f:2,c:26231},26253,26299,26331,26344,26439,26497,26515,26520,26523,26620,26653,26787,26890,26953,144836,26946,26980,27045,27087,15286,15299,27113,27125,145215,27195,145251,27284,27301,15375,27419,27436,27495,27561,27565,27607,27647,27653,27764,27800,27899,27846,27953,27961,27967,27992,28052,28074,28123,28125,28228,28254,28337, [...]
+28505,28513,28542,28556,28576,28604,28615,28618,28656,28750,28789,28836,28900,28971,28958,28974,29009,29032,29061,29063,29114,29124,29205,15935,29339,149489,29479,29520,29542,29602,29739,29766,29794,29805,29862,29865,29897,29951,29975,16242,30158,30210,30216,30308,30337,30365,30378,30390,30414,30420,30438,30449,30474,30489,{f:2,c:30541},30586,30592,30612,30688,152718,30787,30830,30896,152846,30893,30976,31004,31022,31028,31046,31097,31176,153457,31188,31198,31211,31213,31365,154052,31438 [...]
+31533,31547,31599,31745,31795,155041,31853,31865,31887,31892,31904,31957,32049,32092,32131,32166,32194,32296,32663,32731,32821,32823,32970,32992,33011,33120,{f:2,c:33127},33133,33211,33226,33239,17499,33376,33396,158463,33441,{f:2,c:33443},33449,33471,33493,33533,33536,33570,33581,33594,33607,33661,33703,33743,33745,33761,33793,33798,33887,33904,33907,33925,33950,33978,159296,34098,34078,34095,34148,34170,34188,34210,34251,34285,34303,{f:2,c:34308},34320,159988,34328,34360,34391,34402,17 [...]
+34488,34556,34695,17898,34826,34832,35022,161412,35122,35129,35136,35220,35318,35399,35421,35425,35445,35536,35654,35673,35689,35741,35913,35944,36271,36305,36311,36387,36413,36475,164471,18500,36602,36638,36653,36692,164813,36840,36846,36872,36909,37015,37043,37054,{f:2,c:37060},37063,37103,37140,37142,{f:2,c:37154},37167,37172,37251,37361,37705,{f:2,c:37732},37795,37855,37892,37939,37962,37987,38001,38286,38303,38316,38326,38347,38352,38355,18864,38366,38565,38639,38734,38805,38830,388 [...]
+38857,38875,38998,39143,39256,39427,39617,39619,39630,39638,39682,39688,19479,39725,39774,39782,39812,39818,39838,39886,39909,39928,39971,{f:2,c:40015},40037,{f:2,c:40221},40259,40274,40330,40342,40384,40364,40380,172432,40423,40455,40606,40623,40855,131209,19970,19983,19986,20009,20014,20039,131234,20049,13318,131236,20073,20125,13356,20156,20163,20168,20203,20186,20209,20213,20246,20324,20279,20286,20312,131603,{f:2,c:20343},20354,20357,20454,20402,20421,20427,20434,13418,20466,20499,2 [...]
+20563,20579,20643,20616,{f:2,c:20626},20629,20650,131883,20657,{f:2,c:20666},20676,20679,20723,131969,20686,131953,20692,20705,13458,132089,20759,132170,20832,132361,20851,20867,20875,13500,20888,20899,20909,13511,132566,20979,21010,21014,132943,21077,21084,21100,21111,21124,21122,133127,21144,133178,21156,{f:2,c:21178},21194,21201,133305,21239,21301,21314,133500,133533,21351,21370,21412,21428,133843,21431,21440,133917,{f:2,c:13661},21461,13667,21492,21540,21544,13678,21571,21602,21612,2 [...]
+21670,21678,21687,21690,21699,134469,21740,21743,21745,21747,{f:2,c:21760},21769,21820,21825,13734,21831,13736,21860,134625,21885,21890,21905,13765,21970,134805,134765,21951,21961,21964,21969,21981,13786,21986,134756,21993,22056,135007,22023,22032,22064,13812,22077,22080,22087,22110,22112,22125,13829,22152,22156,22173,22184,22194,22213,22221,22239,22248,{f:2,c:22262},135681,135765,22313,135803,{f:2,c:22341},22349,135796,22376,22383,{f:3,c:22387},22395,135908,135895,22426,{f:2,c:22429},22 [...]
+135933,22476,135990,136004,22494,22512,13898,22520,22523,22525,22532,22558,22567,22585,136132,22601,22604,22631,{f:2,c:22666},22669,{f:2,c:22671},22676,22685,22698,22705,136301,22723,22733,22754,{f:2,c:22771},{f:2,c:22789},22797,22804,136663,13969,22845,13977,22854,13974,158761,22879,136775,{f:2,c:22901},22908,22943,22958,22972,22984,22989,23006,23015,23022,136966,137026,14031,23053,23063,23079,23085,23141,23162,23179,23196,{f:2,c:23199},23202,23217,23221,23226,23231,23258,23260,23269,23 [...]
+23285,23304,23319,23348,23372,23378,23400,23407,23425,23428,137667,23446,23468,{f:2,c:14177},23502,23510,14188,14187,23537,23549,14197,23555,23593,138326,23647,{f:2,c:23655},23664,138541,138565,138616,138594,23688,23690,14273,138657,138652,23712,23714,23719,138642,23725,23733,138679,23753,138720,138803,23814,23824,23851,23837,23840,23857,23865,14312,23905,23914,14324,23920,139038,14333,23944,14336,23959,23984,23988,139126,24017,24023,139258,24036,24041,14383,14390,14400,24095,24126,24137 [...]
+14433,{f:2,c:24173},139643,24229,24236,24249,24262,24281,140062,24317,24328,140205,24350,24391,24419,24434,24446,24463,24482,24519,24523,{f:3,c:24530},24546,{f:2,c:24558},24563,14615,24610,24612,14618,24652,24725,24744,141043,24753,24766,24776,24793,24814,24821,24848,24857,24862,24890,14703,24897,24902,24928,141403,{f:2,c:24978},24983,24997,25005,141483,25045,25053,25077,141711,25123,25170,25185,25188,25211,25197,25203,25241,25301,142008,25341,25347,25360,{f:2,c:142159},25394,25397,{f:2, [...]
+25409,25412,25422,142150,25433,142365,142246,25452,25497,142372,25492,25533,{f:2,c:25556},25568,{f:2,c:25579},25586,25630,25637,25641,25647,25690,25693,25715,25725,25735,25745,25759,{f:2,c:25803},25813,25815,142817,25828,25855,14958,25871,25876,14963,25886,25906,25924,25940,25963,25978,25988,25994,26034,26037,26040,26047,26057,26068,15062,26105,26108,26116,26120,26145,26154,26181,26193,26190,15082,143811,143861,143798,26218,{f:2,c:26220},26235,26240,26256,26258,15118,26285,26289,26293,15 [...]
+15063,26369,26386,144242,26393,144339,144338,26445,26452,26461,144336,144356,144341,26484,144346,26514,144351,33635,26640,26563,26568,26578,26587,26615,144458,144465,144459,26648,26655,26669,144485,26675,26683,26686,26693,26697,26700,26709,26711,15223,26731,26734,26748,26754,26768,26774,15213,{f:3,c:26776},26780,{f:2,c:26794},26804,26811,26875,144612,144730,26819,26821,26828,26841,{f:2,c:26852},26860,26871,26883,26887,15239,144788,15245,26950,26985,26988,27002,27026,15268,27030,27056,270 [...]
+27072,27089,144953,144967,144952,27107,{f:2,c:27118},27123,15309,27124,27134,27153,27162,27165,145180,{f:2,c:27186},27199,27209,27258,27214,27218,27236,145164,27275,15344,27297,145252,27307,27325,27334,27348,27344,27357,145407,145383,{f:3,c:27377},27389,145444,27403,{f:3,c:27407},145469,27415,15398,27439,27466,27480,27500,27509,[11934,27514],27521,27547,27566,146072,27581,{f:3,c:27591},27610,{f:2,c:27622},27630,27650,27658,27662,27702,146559,27725,27739,27757,27780,27785,15555,27796,2779 [...]
+15570,27868,27881,27885,146688,27904,27940,{f:2,c:27942},27751,27951,27964,27995,28E3,28016,{f:2,c:28032},28042,28045,28049,28056,146752,146938,146937,146899,28075,28078,28084,28098,27956,28104,28110,28127,28150,28214,28190,15633,28210,{f:2,c:28232},{f:2,c:28235},28239,{f:2,c:28243},28247,28259,15646,28307,28327,28340,28355,28469,28395,28409,28411,28426,28428,28440,28453,28470,28476,147326,28498,28503,28512,28520,28560,28566,28606,28575,28581,28591,15716,{f:2,c:28616},28649,147606,28668, [...]
+28707,147715,28730,28739,28743,28747,15770,28773,28777,28782,28790,28806,28823,147910,28831,28849,147966,28908,28874,28881,28931,28934,28936,28940,15808,28975,29008,29011,29022,15828,29078,29056,29083,29088,29090,{f:2,c:29102},148412,29145,29148,29191,15877,29236,29241,29250,29271,29283,149033,{f:2,c:29294},29304,29311,29326,149157,29358,29360,29377,15968,29388,15974,15976,29427,29434,29447,29458,{f:2,c:29464},16003,29497,29484,29491,29501,29522,16020,29547,149654,{f:2,c:29550},29553,295 [...]
+29588,29592,29596,29605,29625,29631,29637,29643,29665,29671,29689,29715,29690,29697,29779,29760,29763,29778,29789,29825,29832,150093,29842,29847,29849,29857,29861,29866,29881,29883,29882,29910,29912,29931,150358,29946,150383,29984,29988,29994,16215,150550,{f:2,c:30013},30016,30024,30032,30034,30066,30065,30074,{f:2,c:30077},30092,16245,30114,16247,30128,30135,{f:2,c:30143},30150,30159,30163,30173,{f:2,c:30175},30183,30190,30193,30211,30232,30215,30223,16302,151054,30227,{f:2,c:30235},151 [...]
+30248,30268,30259,151146,16329,30273,151179,30281,30293,16343,30318,30357,30369,30368,{f:2,c:30375},30383,151626,30409,151637,30440,151842,30487,30490,30509,30517,151977,16441,152037,152013,30552,152094,30588,152140,16472,30618,30623,30626,30628,{f:2,c:30686},30692,30698,30700,30715,152622,30725,30729,30733,30745,30764,30791,30826,152793,30858,30868,30884,30877,30879,30907,30933,30950,{f:2,c:30969},30974,152999,30992,31003,31013,31050,31064,16645,31079,31090,31125,31137,31145,31156,31170 [...]
+c:31180},31190,16712,153513,153524,16719,31242,31253,31259,16739,31288,31303,31318,31321,31324,31327,31335,31338,31349,31362,31370,31376,31404,154068,16820,31417,31422,16831,31436,31464,31476,154340,154339,154353,31549,31530,{f:2,c:31534},16870,16883,31615,31553,16878,31573,31609,31588,31590,31603,154546,16903,31632,31643,16910,31669,31676,31685,31690,154699,154724,31700,31702,31706,31722,31728,31747,31758,31813,31818,31831,31838,31841,31849,31855,155182,155222,155237,31910,155234,{f:2,c [...]
+31940,155330,31949,155368,155427,31974,155484,31989,32003,17094,32018,32030,155616,155604,{f:2,c:32061},32064,32071,155660,155643,17110,32090,32106,32112,17117,32127,155671,32136,32151,155744,32157,32167,32170,32182,32192,32215,32217,32230,17154,155885,64088,32272,32279,32285,32295,32300,32325,32373,32382,{f:2,c:32390},17195,32410,17219,32572,32571,32574,32579,13505,156272,156294,{f:2,c:32611},32621,{f:2,c:32637},32656,20859,146702,32662,32668,32685,156674,32707,32719,32739,32754,32778,3 [...]
+32812,32816,32835,32870,32891,32921,32924,32932,32935,32952,157310,32965,32981,32998,33037,33013,33019,17390,33077,33054,17392,33060,33063,33068,157469,33085,17416,33129,17431,17436,33157,17442,33176,33202,33217,33219,33238,33243,157917,33252,157930,33260,33277,33279,158063,33284,158173,33305,33314,158238,33340,33353,33349,158296,17526,17530,33367,158348,33372,33379,158391,17553,33405,33407,33411,33418,33427,{f:2,c:33447},33458,33460,33466,33468,33506,33512,33527,{f:2,c:33543},33548,3362 [...]
+33584,33596,33604,33623,17598,17620,17587,{f:2,c:33684},33691,33693,33737,33744,33748,33757,33765,33785,33813,158835,33815,33849,33871,{f:2,c:33873},{f:2,c:33881},33884,158941,33893,33912,33916,33921,17677,33943,33958,33982,17672,{f:2,c:33998},34003,159333,34023,34026,34031,34033,34042,34075,{f:2,c:34084},34091,34127,34159,17731,34129,{f:2,c:34145},159636,34171,34173,34175,34177,34182,34195,34205,34207,159736,{f:2,c:159734},34236,34247,34250,{f:2,c:34264},34271,34273,34278,34294,34304,34 [...]
+34337,34340,34343,160013,34361,34364,160057,34368,34387,34390,34423,34439,34441,{f:2,c:34460},34481,34483,34497,34499,34513,34517,34519,34531,34534,17848,34565,34567,34574,34576,34591,34593,34595,34609,34618,34624,34627,34641,34648,{f:2,c:34660},34674,34684,160731,160730,34727,34697,34699,34707,34720,160766,17893,34750,160784,34753,34766,34783,160841,34787,{f:2,c:34789},34794,34835,34856,34862,34866,34876,17935,34890,34904,161301,161300,34921,161329,34927,34976,35004,35008,161427,35025,3 [...]
+35073,161550,35127,161571,35138,35141,35145,161618,35170,35209,35216,35231,35248,35255,35288,35307,18081,35315,35325,35327,18095,35345,35348,162181,35361,35381,35390,35397,35405,35416,35502,35472,35511,35543,35580,162436,35594,35589,35597,35612,35629,18188,35665,35678,35702,35713,35723,{f:2,c:35732},35897,162739,35901,162750,162759,35909,35919,35927,35945,35949,163E3,35987,35986,35993,18276,35995,36054,36053,163232,36081,163344,36105,36110,36296,36313,36364,18429,36349,36358,163978,36372 [...]
+c:36385},36391,164027,18454,36406,36409,36436,36450,36461,36463,36504,36510,36533,36539,164482,18510,164595,36608,36616,36651,36672,36682,36696,164876,36772,36788,164949,36801,36806,64036,36810,36813,36819,36821,36849,36853,36859,36876,36919,165227,36931,36957,{f:2,c:165320},36997,37004,37008,37025,18613,37040,37046,37059,37064,165591,37084,37087,165626,37110,37106,37120,37099,{f:2,c:37118},37124,37126,37144,37150,37175,37177,{f:2,c:37190},37207,37209,37236,37241,37253,37299,37302,{f:2,c [...]
+166214,37356,37377,{f:2,c:37398},166251,37442,37450,37462,37473,37477,37480,166280,{f:2,c:37500},37503,37513,37517,37527,37529,37535,37547,{f:2,c:166330},37554,{f:2,c:37567},37574,37582,37605,37649,166430,166441,37623,37673,166513,166467,37713,37722,37739,37745,37747,37793,166553,166605,37768,37771,37775,37790,37877,166628,166621,37873,37831,37852,37863,37897,{f:2,c:37910},37883,37938,37947,166849,166895,37997,37999,38265,38278,{f:2,c:38284},167184,167281,38344,167419,167455,38444,{f:2,c [...]
+38460,38497,167561,38530,167659,38554,167730,18919,38579,38586,38589,18938,167928,38616,38618,38621,18948,38676,38691,18985,38710,38721,38727,38743,38747,38762,168608,168625,38806,38814,{f:2,c:38833},38846,38860,38865,38868,38872,38881,38897,38916,38925,38932,38934,19132,169104,{f:2,c:38962},38949,38983,39014,39083,39085,39088,169423,39095,{f:2,c:39099},39106,39111,39115,39137,39139,39146,{f:2,c:39152},39155,39176,19259,169712,{f:2,c:39190},169753,{f:3,c:39194},169808,39217,{f:3,c:39226} [...]
+39246,39264,39331,39334,39357,39359,39363,39380,39385,39390,170182,39408,39417,39420,39434,39441,39450,39456,39473,39492,39500,39512,19394,39599,19402,39607,19410,39609,170610,39622,39632,39634,39637,39648,39653,39657,39692,39696,39698,39702,39708,39723,39741,19488,39755,39779,39781,{f:2,c:39787},{f:2,c:39798},39846,39852,171483,39858,39864,39870,39923,39896,39901,39914,39919,39918,171541,171658,171593,39958,{f:3,c:39960},39965,39970,39977,171716,39985,39991,40005,40028,171753,{f:2,c:400 [...]
+40020,40024,40027,40029,40031,{f:3,c:40041},{f:2,c:40045},40050,40053,40058,40166,40178,40203,[171982,171991],40209,{f:2,c:40215},172079,19652,172058,40242,19665,40266,40287,40290,172281,172162,40307,{f:2,c:40310},40324,40345,40353,40383,40373,40377,40381,40393,40410,40416,40419,19719,40458,40450,40461,40476,40571,139800,40576,40581,40603,172940,40637,173111,40671,40703,40706,19831,40707,40762,40765,40774,40787,40789,40792,173553,40797,173570,40809,40813,40816,173746,11948,13844,14509,15 [...]
+17854,17936,19326,19512,19681,19980,{f:2,c:20003},20089,20211,20236,20249,20267,20270,20273,20356,20382,20407,20484,20492,20556,20575,20578,20599,20622,20638,20642,20675,20712,20721,20734,20743,{f:3,c:20748},20787,20792,20852,20868,20920,20922,20936,20943,20945,{f:2,c:20947},20952,20959,20997,21030,21032,21035,{f:2,c:21041},21045,21052,21082,21088,21102,{f:2,c:21112},21130,21132,21217,21225,21233,21251,21265,21279,21293,21298,21309,21349,21357,21369,21374,21396,21401,21418,21423,21434,21 [...]
+c:21444},21472,21523,21546,21553,{f:2,c:21556},21580,21671,21674,21681,21691,21710,21738,21756,21765,21768,21781,21799,21802,21814,21841,21862,21903,21906,21908,21924,21938,21955,21958,21971,21979,21996,21998,22001,22006,22008,22021,22029,{f:2,c:22033},22060,22069,22073,22093,22100,22149,22175,22182,22199,22220,22223,22233,22241,22251,22253,22257,22279,22284,{f:2,c:22298},22301,22316,22318,{f:2,c:22333},22367,22379,22381,22394,22403,22423,22446,22485,22503,22541,22566,22605,22607,22623,2 [...]
+22657,22680,22716,22815,22819,22873,22905,22935,22959,22963,23007,23025,23032,23218,23224,23274,23286,23323,23325,23329,23352,23479,23511,23520,23583,23594,23596,23606,23641,23644,23661,23773,23809,23860,23869,23897,23934,23939,24007,24057,24104,24114,24117,24155,24168,24170,24183,24192,24203,24243,24253,24273,{f:2,c:24276},24397,24492,24554,24583,24649,24660,24679,24763,24772,24829,24842,24854,24874,24886,24926,24932,24955,24957,24959,24989,25016,25052,25058,25061,25064,25092,25095,2513 [...]
+25210,25232,25256,25306,25332,25366,25386,25398,25414,25419,25427,25457,25461,25471,25474,25482,{f:2,c:25518},25578,{f:2,c:25592},25618,25624,25632,25636,25642,25653,25661,25663,25682,25695,25716,25744,{f:2,c:25752},25772,25779,25837,25840,25883,25887,25902,25929,25952,26002,26005,26036,26046,26056,26062,26064,26079,26238,{f:2,c:26251},26291,26304,26319,26405,26421,26453,26496,26511,26513,26532,26545,26549,26558,26664,26758,26859,26869,26903,26931,26936,26971,26981,27048,27051,27055,2710 [...]
+27221,27239,27249,27311,{f:2,c:27336},27395,27451,27455,{f:2,c:27517},27568,27639,27641,27652,27657,27661,27692,27722,27730,27732,27769,27820,27828,27858,28001,28028,28089,28144,28229,28275,28283,28285,28297,28348,{f:2,c:28378},28454,28457,28464,28551,28573,28590,28599,28685,28704,28745,28824,28848,{f:2,c:28885},28997,29106,29172,29207,29215,29251,{f:2,c:29263},29274,29280,29288,29303,29316,29385,29413,29428,29442,29451,29470,29474,{f:2,c:29498},29517,29528,29543,29810,29871,29919,29924, [...]
+29974,29985,30015,30046,30105,30116,30145,30148,30156,30167,30172,30177,30191,30212,30220,30237,30258,30264,30277,30282,30303,30381,30397,30425,30443,30448,30457,30464,30478,30498,30504,30511,30521,30526,30533,30538,30543,30558,30564,30567,30572,30596,{f:2,c:30604},30614,30631,30639,30647,30654,30665,30673,30681,30705,30775,30812,30846,30872,30881,30897,30899,30921,30931,30988,31007,{f:2,c:31015},31039,31042,31060,31083,31100,31147,31172,31210,31234,31244,31280,31290,31300,31360,31366,31 [...]
+31421,31486,31531,31607,31648,31660,31664,31720,31730,31736,31740,31742,31753,31784,31791,31810,{f:2,c:31826},{f:3,c:31835},31858,31869,31879,31902,31930,31943,31955,31962,32060,32077,32130,32133,32141,32145,32158,32179,32185,32208,32229,{f:2,c:32245},32303,32310,32324,32367,32376,32385,32573,32603,32605,32613,32625,{f:2,c:32639},32651,32674,{f:3,c:32765},32775,32781,32798,32825,32904,32910,32975,32980,33005,33008,33015,33018,33022,33027,33047,33072,33111,33135,33139,33163,33168,33179,33 [...]
+33237,{f:2,c:33245},33249,33263,33270,33280,33291,{f:2,c:33299},33306,33338,33348,33389,33412,33417,33425,33450,33456,33488,33514,33519,33526,33622,33656,33784,33788,33880,33939,33969,33981,34043,34118,34134,34141,34181,34200,34370,34374,34496,34580,34594,34606,34617,34653,34683,34700,34702,{f:2,c:34711},34718,34723,34734,34751,34761,34778,34840,34843,34861,34874,34885,34891,34894,34901,34906,34926,{f:3,c:34970},35021,35040,35055,{f:2,c:35086},35110,35125,35162,35164,35179,35184,35196,35 [...]
+35260,35285,35401,35415,35431,35454,35462,35478,35510,35529,35537,35549,35564,35573,35590,35599,35601,35653,35666,35693,35704,35708,35710,35717,35743,35915,35923,35963,36026,36037,36041,36050,36076,36085,36087,36097,36099,36119,36124,36206,36241,36255,36267,36274,36309,36327,{f:2,c:36337},36340,36353,36363,36390,36401,{f:2,c:36416},36429,36431,36444,36449,36457,36465,36469,36471,36489,36496,36501,36506,36519,36521,36525,36584,36592,36615,36632,36645,36647,36652,36661,36666,36675,36679,36 [...]
+{f:3,c:36768},36773,36868,36891,36911,36940,36955,36976,36980,36985,37003,37016,37024,37042,37053,37065,37104,37125,37157,37210,37223,37242,37258,37265,37269,37296,37307,37309,37314,37317,37376,37385,37411,37494,37518,37551,{f:2,c:37563},37569,37571,37573,37576,37652,37683,37686,37720,37759,37762,37770,37819,37836,37862,37881,37890,{f:2,c:37901},37934,37964,38280,38305,38335,38342,38345,{f:2,c:38353},38368,38372,38374,38436,38449,38456,38461,38484,38516,38523,38527,38529,38531,38537,3855 [...]
+38683,{f:2,c:38689},38696,38705,38759,38774,38781,38783,38809,38815,38828,38841,38861,38880,38895,38919,38950,38958,{f:2,c:39010},39092,39109,39170,39185,39189,39221,39240,39252,39262,39393,39436,39440,39459,39489,39505,{f:2,c:39613},39681,39689,39691,{f:2,c:39693},39705,39733,39752,39765,39784,39808,39814,39824,39837,39856,39871,39880,39935,39938,39964,39989,40004,40022,40033,40040,40240,40253,40298,40315,40421,40425,40435,40570,{f:3,c:40578},40624,40676,40688,40690,40713,40719,40724,40 [...]
+40742,{f:2,c:40746},40756,40794,40815,40862,40869,131317,151044,151538,163187,194581,194630,194713,194726,194789,195038,13790,{s:4},172722,0,0,131416,{s:4},132529,0,0,132844,{s:6},134488,{s:21},154060,{s:9},14756,14776,142914,0,0,14940,0,0,143339,0,0,162228,0,15044,15051,{s:5},14981,{s:8},15347,27384,{s:5},15665,{s:9},147531,0,15936,14497,{s:34},158878,{s:12},18207,162876,{s:4},18462,{s:71},39709,39724,20482,20958,21255,23532,63784,26142,63785,28746,64021,21857,27706,31328,156492,34819,3 [...]
+171581,173594],"Adobe-Korea1":[{f:95,c:32},8361,8208,169,0,0,[12288,12644],{f:2,c:12289},12539,8229,[8230,8943],168,12291,{f:2,c:8211},8214,65340,65374,{f:2,c:8216},{f:2,c:8220},{f:2,c:12308},{f:10,c:12296},177,215,247,8800,{f:2,c:8804},8734,8756,176,{f:2,c:8242},8451,8491,{f:2,c:65504},65509,9794,9792,8736,8869,8978,8706,8711,8801,8786,167,8251,9734,9733,9675,9679,9678,9671,9670,9633,9632,9651,9650,9661,9660,8594,{f:2,c:8592},{f:2,c:8595},12307,171,187,8730,8765,8733,8757,{f:2,c:8747},8 [...]
+{f:2,c:8838},{f:2,c:8834},8746,8745,{f:2,c:8743},65506,8658,8660,8704,8707,180,732,711,728,733,730,729,184,731,161,191,8758,8750,8721,8719,164,8457,8240,9665,9664,9655,9654,9828,{f:2,c:9824},9829,9831,9827,9673,9672,9635,{f:2,c:9680},9618,{f:2,c:9636},9640,9639,9638,9641,9832,9743,9742,9756,9758,182,{f:2,c:8224},8597,8599,8601,8598,8600,9837,{f:2,c:9833},9836,12927,12828,8470,13255,8482,13250,13272,8481,{f:59,c:65281},65510,{f:33,c:65341},65507,{f:51,c:12593},{f:42,c:12645},{f:10,c:8560} [...]
+{f:17,c:913},{f:7,c:931},{f:17,c:945},{f:7,c:963},9472,9474,9484,9488,9496,9492,9500,9516,9508,9524,9532,9473,9475,9487,9491,9499,9495,9507,9523,9515,9531,9547,9504,9519,9512,9527,9535,9501,9520,9509,9528,9538,9490,9489,9498,9497,9494,9493,9486,9485,{f:2,c:9502},{f:2,c:9505},{f:2,c:9510},{f:2,c:9513},{f:2,c:9517},{f:2,c:9521},{f:2,c:9525},{f:2,c:9529},{f:2,c:9533},{f:2,c:9536},{f:8,c:9539},{f:3,c:13205},8467,13208,13252,{f:4,c:13219},{f:10,c:13209},13258,{f:3,c:13197},13263,{f:2,c:13192} [...]
+c:13223},{f:10,c:13232},{f:5,c:13184},{f:6,c:13242},{f:5,c:13200},8486,{f:2,c:13248},{f:3,c:13194},13270,13253,{f:3,c:13229},13275,{f:4,c:13225},13277,13264,13267,13251,13257,13276,13254,198,208,170,294,306,319,321,216,338,186,222,358,330,{f:28,c:12896},{f:26,c:9424},{f:15,c:9312},189,{f:2,c:8531},188,190,{f:4,c:8539},230,273,240,295,305,307,312,320,322,248,339,223,254,359,331,329,{f:28,c:12800},{f:26,c:9372},{f:15,c:9332},185,{f:2,c:178},8308,8319,{f:4,c:8321},{f:83,c:12353},{f:86,c:124 [...]
+c:1040},1025,{f:32,c:1046},1105,{f:26,c:1078},{f:2,c:44032},44036,{f:4,c:44039},{f:8,c:44048},{f:5,c:44057},44064,44068,{f:2,c:44076},{f:3,c:44079},{f:2,c:44088},44092,44096,44107,44109,44116,44120,44124,{f:2,c:44144},44148,{f:2,c:44151},44154,{f:2,c:44160},{f:4,c:44163},{f:4,c:44169},44176,44180,{f:2,c:44188},{f:3,c:44191},{f:3,c:44200},44204,{f:2,c:44207},{f:2,c:44216},{f:3,c:44219},44225,44228,44232,44236,44245,44247,{f:2,c:44256},44260,{f:2,c:44263},44266,44268,{f:3,c:44271},44275,{f [...]
+{f:2,c:44284},44288,44292,44294,{f:2,c:44300},44303,44305,44312,44316,44320,44329,{f:2,c:44332},{f:2,c:44340},44344,44348,{f:2,c:44356},44359,44361,44368,44372,44376,44385,44387,{f:2,c:44396},44400,{f:4,c:44403},{f:3,c:44411},44415,{f:2,c:44417},{f:2,c:44424},44428,44432,{f:2,c:44444},44452,44471,{f:2,c:44480},44484,44488,{f:2,c:44496},44499,44508,44512,44516,{f:2,c:44536},44540,{f:3,c:44543},{f:2,c:44552},44555,44557,44564,{f:2,c:44592},44596,{f:2,c:44599},44602,{f:2,c:44608},44611,{f:2 [...]
+44618,{f:3,c:44620},44624,44628,44630,{f:2,c:44636},{f:3,c:44639},44645,{f:2,c:44648},44652,44656,{f:2,c:44664},{f:3,c:44667},{f:2,c:44676},44684,{f:3,c:44732},44736,44740,{f:2,c:44748},{f:3,c:44751},{f:2,c:44760},44764,44776,44779,44781,44788,44792,44796,{f:2,c:44807},44813,44816,{f:2,c:44844},44848,44850,44852,{f:2,c:44860},44863,{f:3,c:44865},{f:2,c:44872},44880,{f:2,c:44892},{f:2,c:44900},44921,44928,44932,44936,{f:2,c:44944},44949,44956,{f:2,c:44984},44988,44992,{f:3,c:44999},45003, [...]
+45012,45020,{f:2,c:45032},{f:2,c:45040},45044,45048,{f:2,c:45056},45060,45068,45072,45076,{f:2,c:45084},45096,{f:2,c:45124},45128,45130,45132,45134,{f:3,c:45139},45143,45145,45149,{f:2,c:45180},45184,45188,{f:2,c:45196},45199,45201,{f:3,c:45208},45212,{f:4,c:45215},{f:2,c:45224},{f:5,c:45227},45233,{f:3,c:45235},45240,45244,{f:2,c:45252},{f:3,c:45255},{f:2,c:45264},45268,45272,45280,45285,{f:2,c:45320},{f:2,c:45323},45328,{f:2,c:45330},{f:2,c:45336},{f:3,c:45339},{f:3,c:45347},45352,4535 [...]
+{f:3,c:45367},{f:2,c:45376},45380,45384,{f:2,c:45392},{f:2,c:45396},45400,45404,45408,{f:2,c:45432},45436,45440,45442,{f:2,c:45448},45451,45453,{f:3,c:45458},45464,45468,45480,45516,45520,45524,{f:2,c:45532},45535,{f:2,c:45544},45548,45552,45561,45563,45565,{f:2,c:45572},45576,{f:2,c:45579},{f:2,c:45588},45591,45593,45600,45620,45628,45656,45660,45664,{f:2,c:45672},{f:2,c:45684},45692,{f:2,c:45700},45705,{f:2,c:45712},45716,{f:3,c:45720},{f:2,c:45728},45731,{f:2,c:45733},45738,45740,4574 [...]
+c:45768},45772,45776,45778,{f:2,c:45784},45787,45789,45794,{f:3,c:45796},45800,{f:5,c:45803},{f:3,c:45811},{f:5,c:45815},{f:3,c:45823},45828,45832,{f:2,c:45840},{f:3,c:45843},45852,{f:3,c:45908},45912,{f:2,c:45915},{f:2,c:45918},{f:2,c:45924},45927,45929,45931,45934,{f:2,c:45936},45940,45944,{f:2,c:45952},{f:3,c:45955},45964,45968,45972,{f:2,c:45984},45992,45996,{f:2,c:46020},46024,{f:2,c:46027},46030,46032,{f:2,c:46036},46039,46041,46043,46045,46048,46052,46056,46076,46096,46104,46108,4 [...]
+c:46120},46123,46132,{f:2,c:46160},46164,46168,{f:2,c:46176},46179,46181,46188,46208,46216,46237,46244,46248,46252,46261,46263,46265,46272,46276,46280,46288,46293,{f:2,c:46300},46304,{f:2,c:46307},46310,{f:2,c:46316},46319,46321,46328,{f:2,c:46356},46360,{f:2,c:46363},{f:2,c:46372},{f:4,c:46375},{f:2,c:46384},46388,46392,{f:2,c:46400},{f:3,c:46403},{f:3,c:46411},46416,46420,{f:2,c:46428},{f:3,c:46431},{f:2,c:46496},46500,46504,{f:2,c:46506},{f:2,c:46512},{f:3,c:46515},{f:3,c:46523},46528 [...]
+c:46540},{f:3,c:46543},46552,46572,{f:2,c:46608},46612,46616,46629,46636,46644,46664,46692,46696,{f:2,c:46748},46752,46756,{f:2,c:46763},46769,46804,46832,46836,46840,{f:2,c:46848},46853,{f:2,c:46888},46892,{f:2,c:46895},{f:2,c:46904},46907,46916,46920,46924,{f:2,c:46932},46944,46948,46952,{f:2,c:46960},46963,46965,{f:2,c:46972},46976,46980,{f:2,c:46988},{f:4,c:46991},{f:4,c:46998},47004,47008,{f:2,c:47016},{f:3,c:47019},{f:2,c:47028},47032,47047,47049,{f:2,c:47084},47088,47092,{f:2,c:47 [...]
+c:47103},{f:3,c:47111},47116,47120,{f:2,c:47128},47131,47133,{f:2,c:47140},47144,47148,{f:2,c:47156},{f:3,c:47159},47168,47172,47185,47187,{f:2,c:47196},47200,47204,{f:2,c:47212},47215,47217,47224,47228,47245,47272,47280,47284,47288,{f:2,c:47296},47299,47301,47308,47312,47316,47325,47327,47329,{f:2,c:47336},47340,47344,{f:2,c:47352},47355,47357,47364,47384,47392,{f:2,c:47420},47424,47428,47436,47439,47441,{f:2,c:47448},47452,47456,{f:2,c:47464},47467,47469,{f:2,c:47476},47480,47484,{f:2, [...]
+47495,{f:2,c:47497},{f:2,c:47501},{f:2,c:47532},47536,47540,{f:2,c:47548},47551,47553,{f:2,c:47560},47564,{f:5,c:47566},{f:2,c:47576},47579,{f:2,c:47581},47585,{f:3,c:47587},47592,47596,{f:2,c:47604},{f:4,c:47607},{f:2,c:47616},47624,47637,{f:2,c:47672},47676,47680,47682,{f:2,c:47688},47691,{f:2,c:47693},{f:3,c:47699},47704,47708,{f:2,c:47716},{f:3,c:47719},{f:2,c:47728},47732,47736,{f:3,c:47747},47751,47756,{f:2,c:47784},{f:2,c:47787},47792,47794,{f:2,c:47800},47803,47805,47812,47816,{f [...]
+47868,47872,47876,47885,47887,47889,47896,47900,47904,47913,47915,{f:3,c:47924},47928,{f:4,c:47931},{f:2,c:47940},47943,47945,47949,{f:2,c:47951},47956,47960,47969,47971,47980,48008,48012,48016,48036,48040,48044,48052,48055,48064,48068,48072,48080,48083,{f:2,c:48120},48124,{f:2,c:48127},48130,{f:2,c:48136},{f:3,c:48139},48143,48145,{f:5,c:48148},{f:5,c:48155},{f:2,c:48164},48167,48169,48173,{f:2,c:48176},48180,48184,{f:2,c:48192},{f:3,c:48195},48201,{f:2,c:48204},48208,48221,{f:2,c:48260 [...]
+c:48267},48270,{f:2,c:48276},48279,{f:2,c:48281},{f:2,c:48288},48292,{f:2,c:48295},{f:2,c:48304},{f:3,c:48307},{f:2,c:48316},48320,48324,48333,{f:3,c:48335},48341,48344,48348,{f:3,c:48372},48376,48380,{f:2,c:48388},48391,48393,48400,48404,48420,48428,48448,{f:2,c:48456},48460,48464,{f:2,c:48472},48484,48488,{f:2,c:48512},48516,{f:4,c:48519},{f:2,c:48528},48531,48533,{f:2,c:48537},48540,48548,48560,48568,{f:2,c:48596},48600,48604,48617,48624,48628,48632,48640,48643,48645,{f:2,c:48652},486 [...]
+{f:2,c:48668},48671,{f:2,c:48708},48712,48716,48718,{f:2,c:48724},48727,{f:3,c:48729},{f:2,c:48736},48740,48744,48746,{f:2,c:48752},{f:3,c:48755},{f:3,c:48763},48768,48772,{f:2,c:48780},{f:3,c:48783},{f:2,c:48792},48808,{f:2,c:48848},48852,{f:2,c:48855},48864,{f:3,c:48867},48876,48897,{f:2,c:48904},{f:2,c:48920},{f:3,c:48923},{f:2,c:48960},48964,48968,{f:2,c:48976},48981,49044,49072,49093,{f:2,c:49100},49104,49108,49116,49119,49121,49212,49233,49240,49244,49248,{f:2,c:49256},{f:2,c:49296 [...]
+{f:2,c:49312},49315,49317,{f:2,c:49324},{f:2,c:49327},{f:4,c:49331},{f:2,c:49340},{f:3,c:49343},49349,{f:2,c:49352},49356,49360,{f:2,c:49368},{f:3,c:49371},{f:2,c:49380},49384,49388,{f:2,c:49396},49399,49401,49408,49412,49416,49424,49429,{f:5,c:49436},{f:2,c:49443},{f:2,c:49446},{f:2,c:49452},{f:3,c:49455},49462,{f:2,c:49464},49468,49472,{f:2,c:49480},{f:3,c:49483},{f:2,c:49492},49496,49500,{f:2,c:49508},{f:3,c:49511},49520,49524,49528,49541,{f:3,c:49548},49552,49556,49558,{f:2,c:49564}, [...]
+49573,{f:2,c:49576},49580,49584,49597,49604,49608,49612,49620,{f:2,c:49623},49632,49636,49640,{f:2,c:49648},49651,{f:2,c:49660},49664,49668,{f:2,c:49676},49679,49681,{f:2,c:49688},49692,{f:2,c:49695},{f:2,c:49704},49707,49709,49711,{f:2,c:49713},49716,49736,{f:2,c:49744},49748,49752,49760,49765,{f:2,c:49772},49776,49780,{f:2,c:49788},49791,49793,{f:2,c:49800},49808,49816,49819,49821,{f:2,c:49828},49832,{f:2,c:49836},{f:2,c:49844},49847,49849,{f:2,c:49884},49888,{f:2,c:49891},{f:3,c:49899 [...]
+49910,{f:2,c:49912},{f:2,c:49915},49920,{f:2,c:49928},{f:2,c:49932},{f:3,c:49939},49944,49948,{f:2,c:49956},{f:2,c:49960},49989,{f:2,c:50024},50028,50032,50034,{f:2,c:50040},{f:2,c:50044},50052,50056,50060,50112,{f:2,c:50136},50140,{f:2,c:50143},50146,{f:2,c:50152},50157,{f:2,c:50164},50168,50184,50192,50212,50220,50224,50228,{f:2,c:50236},50248,{f:2,c:50276},50280,50284,{f:2,c:50292},50297,50304,50324,50332,50360,50364,50409,{f:2,c:50416},50420,50424,50426,{f:3,c:50431},50444,50448,5045 [...]
+c:50472},50476,50480,{f:2,c:50488},50491,50493,{f:2,c:50500},{f:3,c:50504},{f:3,c:50508},{f:3,c:50515},{f:3,c:50519},{f:2,c:50525},{f:2,c:50528},50532,50536,{f:2,c:50544},{f:3,c:50547},{f:2,c:50556},50560,50564,50567,{f:2,c:50572},50575,50577,50581,{f:2,c:50583},50588,50592,50601,{f:2,c:50612},{f:2,c:50616},{f:4,c:50619},{f:7,c:50628},50636,50638,{f:2,c:50640},50644,50648,{f:2,c:50656},50659,50661,{f:3,c:50668},50672,50676,{f:2,c:50678},{f:6,c:50684},{f:4,c:50693},50700,50704,{f:2,c:5071 [...]
+{f:2,c:50724},50728,{f:3,c:50732},50736,{f:3,c:50739},50743,50745,50747,{f:2,c:50752},50756,50760,{f:2,c:50768},{f:3,c:50771},{f:2,c:50780},50784,50796,50799,50801,{f:2,c:50808},50812,50816,{f:2,c:50824},50827,50829,{f:2,c:50836},50840,50844,{f:2,c:50852},50855,50857,{f:2,c:50864},50868,{f:3,c:50872},{f:2,c:50880},50883,50885,{f:2,c:50892},50896,50900,{f:2,c:50908},{f:2,c:50912},{f:2,c:50920},50924,50928,{f:2,c:50936},50941,{f:2,c:50948},50952,50956,{f:2,c:50964},50967,50969,{f:2,c:50976 [...]
+{f:2,c:50992},50995,50997,50999,{f:2,c:51004},51008,51012,51018,{f:2,c:51020},51023,{f:8,c:51025},51036,51040,51048,51051,{f:2,c:51060},51064,{f:3,c:51068},{f:3,c:51075},{f:4,c:51079},51086,{f:2,c:51088},51092,{f:3,c:51094},51098,{f:2,c:51104},{f:4,c:51107},{f:2,c:51116},51120,51124,{f:2,c:51132},{f:3,c:51135},{f:2,c:51144},51148,51150,51152,51160,51165,51172,51176,51180,{f:2,c:51200},51204,51208,51210,{f:2,c:51216},51219,{f:2,c:51221},{f:2,c:51228},51232,51236,{f:2,c:51244},51247,51249, [...]
+51264,{f:2,c:51272},{f:2,c:51276},51284,{f:2,c:51312},51316,51320,51322,{f:2,c:51328},51331,{f:3,c:51333},{f:3,c:51339},51348,51357,51359,51361,51368,{f:2,c:51388},51396,51400,51404,{f:2,c:51412},51415,51417,{f:2,c:51424},51428,51445,{f:2,c:51452},51456,{f:3,c:51460},{f:2,c:51468},51471,51473,51480,51500,51508,{f:2,c:51536},51540,51544,{f:2,c:51552},51555,51564,51568,51572,51580,{f:2,c:51592},51596,51600,{f:2,c:51608},51611,51613,{f:2,c:51648},51652,{f:2,c:51655},51658,{f:2,c:51664},5166 [...]
+{f:2,c:51673},{f:2,c:51676},51680,51682,51684,51687,{f:2,c:51692},{f:3,c:51695},{f:2,c:51704},51708,51712,{f:2,c:51720},{f:3,c:51723},51732,51736,51753,{f:2,c:51788},51792,51796,{f:2,c:51804},{f:3,c:51807},51816,51837,51844,51864,{f:2,c:51900},51904,51908,{f:2,c:51916},51919,51921,51923,{f:2,c:51928},51936,51948,51956,51976,51984,51988,51992,{f:2,c:52E3},52033,{f:2,c:52040},52044,52048,{f:2,c:52056},52061,52068,{f:2,c:52088},52124,52152,52180,52196,52199,52201,{f:2,c:52236},52240,52244,{ [...]
+{f:2,c:52257},{f:3,c:52263},52268,52270,52272,{f:2,c:52280},{f:4,c:52283},{f:2,c:52292},52296,52300,{f:2,c:52308},{f:3,c:52311},52320,52324,52326,52328,52336,52341,{f:2,c:52376},52380,52384,{f:2,c:52392},{f:3,c:52395},{f:2,c:52404},52408,52412,{f:2,c:52420},52423,52425,52432,52436,52452,52460,52464,52481,{f:2,c:52488},52492,52496,{f:2,c:52504},52507,52509,52516,52520,52524,52537,52572,52576,52580,{f:2,c:52588},52591,52593,52600,52616,{f:2,c:52628},52632,52636,{f:2,c:52644},52647,52649,52 [...]
+52684,52688,52712,52716,52720,{f:2,c:52728},52731,52733,52740,52744,52748,52756,52761,{f:2,c:52768},52772,52776,{f:2,c:52784},52787,52789,{f:2,c:52824},52828,{f:3,c:52831},{f:2,c:52840},52843,52845,{f:2,c:52852},52856,52860,{f:2,c:52868},52871,52873,{f:2,c:52880},52884,52888,{f:2,c:52896},{f:3,c:52899},{f:2,c:52908},52929,{f:2,c:52964},52968,{f:2,c:52971},{f:2,c:52980},{f:3,c:52983},{f:2,c:52992},52996,53E3,{f:2,c:53008},53011,53013,53020,53024,53028,{f:2,c:53036},{f:3,c:53039},53048,{f: [...]
+53080,53084,{f:2,c:53092},53095,53097,{f:2,c:53104},53108,53112,53120,53125,53132,53153,53160,53168,53188,{f:2,c:53216},53220,53224,{f:2,c:53232},53235,53237,53244,53248,53252,53265,53272,53293,{f:2,c:53300},53304,53308,{f:2,c:53316},53319,53321,53328,53332,53336,53344,{f:2,c:53356},53360,53364,{f:2,c:53372},53377,{f:2,c:53412},53416,53420,{f:2,c:53428},53431,53433,{f:2,c:53440},53444,{f:2,c:53448},{f:2,c:53456},{f:3,c:53459},{f:2,c:53468},53472,53476,{f:2,c:53484},{f:3,c:53487},53496,53 [...]
+c:53552},53556,53560,53562,{f:2,c:53568},{f:3,c:53571},{f:2,c:53580},53584,53588,{f:2,c:53596},53599,53601,53608,53612,53628,53636,53640,{f:2,c:53664},53668,53672,{f:2,c:53680},53683,53685,53690,53692,53696,53720,53748,53752,53767,53769,53776,{f:2,c:53804},53808,53812,{f:2,c:53820},53823,53825,53832,53852,53860,{f:2,c:53888},53892,53896,{f:2,c:53904},53909,53916,53920,53924,53932,53937,{f:2,c:53944},53948,{f:2,c:53951},53954,{f:2,c:53960},53963,53972,53976,53980,{f:2,c:53988},{f:2,c:54E3 [...]
+{f:2,c:54016},54019,54021,{f:3,c:54028},54032,54036,54038,{f:2,c:54044},{f:3,c:54047},54053,{f:2,c:54056},54060,54064,{f:2,c:54072},{f:3,c:54075},{f:2,c:54084},{f:2,c:54140},54144,54148,{f:2,c:54156},{f:3,c:54159},{f:2,c:54168},54172,54176,{f:2,c:54184},54187,54189,54196,54200,54204,{f:2,c:54212},{f:2,c:54216},54224,54232,54241,54243,{f:2,c:54252},54256,54260,{f:2,c:54268},54271,54273,54280,54301,54336,54340,54364,54368,54372,54381,54383,{f:2,c:54392},54396,{f:2,c:54399},54402,{f:2,c:544 [...]
+54413,54420,54441,54476,54480,54484,54492,54495,54504,54508,54512,54520,54523,54525,54532,54536,54540,{f:2,c:54548},54551,{f:2,c:54588},54592,54596,{f:2,c:54604},54607,54609,{f:2,c:54616},54620,54624,54629,{f:2,c:54632},54635,54637,{f:2,c:54644},54648,54652,{f:2,c:54660},{f:3,c:54663},54672,54693,{f:2,c:54728},54732,54736,54738,{f:2,c:54744},54747,54749,{f:2,c:54756},54760,54764,{f:2,c:54772},54775,54777,{f:2,c:54784},54788,54792,{f:2,c:54800},{f:3,c:54803},54812,54816,54820,54829,{f:2,c [...]
+54848,54853,{f:2,c:54856},54859,54861,54865,{f:2,c:54868},54872,54876,54887,54889,{f:2,c:54896},54900,54915,54917,{f:2,c:54924},54928,54932,54941,54943,54945,54952,54956,54960,54969,54971,{f:2,c:54980},54984,54988,54993,54996,54999,55001,55008,55012,55016,55024,55029,{f:2,c:55036},55040,55044,55057,{f:2,c:55064},55068,55072,{f:2,c:55080},55083,55085,{f:2,c:55092},55096,55100,55108,55111,55113,{f:2,c:55120},55124,{f:4,c:55126},{f:2,c:55136},55139,55141,55145,55148,55152,55156,{f:2,c:55164 [...]
+c:55176},55180,55184,{f:2,c:55192},55195,55197,20285,20339,20551,20729,21152,21487,21621,21733,22025,23233,23478,26247,{f:2,c:26550},26607,27468,29634,30146,31292,33499,33540,34903,34952,35382,[36040,63747],36303,36603,36838,39381,21051,21364,21508,24682,24932,27580,29647,33050,35258,[12179,35282],38307,20355,21002,22718,22904,23014,[12082,24178],24185,25031,25536,26438,26604,26751,28567,30286,30475,30965,31240,31487,31777,32925,[12169,33390],33393,35563,38291,20075,21917,26359,28212,308 [...]
+33883,35088,34638,38824,21208,22350,22570,23884,24863,25022,25121,25954,26577,27204,28187,[12130,29976],30131,30435,30640,32058,37039,{f:2,c:37969},40853,21283,23724,30002,32987,37440,38296,21083,22536,23004,23713,23831,24247,24378,24394,24951,27743,30074,30086,31968,32115,32177,32652,33108,33313,34193,35137,35611,37628,[38477,64009],40007,20171,20215,20491,20977,22607,24887,24894,24936,25913,27114,28433,30117,30342,30422,31623,33445,33995,37799,38283,21888,23458,22353,31923,32697,37301, [...]
+23621,24040,25298,25454,25818,25831,28192,28844,31067,36317,36382,36989,37445,37624,20094,20214,20581,[12081,24062],24314,24838,26967,33137,34388,36423,37749,39467,20062,20625,26480,26688,20745,21133,21138,27298,30652,37392,40660,21163,24623,36850,20552,25001,25581,25802,26684,27268,28608,33160,35233,38548,22533,29309,[12125,29356],29956,32121,32365,32937,[12178,35211,64010],35700,36963,40273,25225,27770,28500,32080,32570,35363,20860,24906,31645,35609,37463,37772,20140,20435,20510,20670, [...]
+21197,21375,22384,22659,24218,24465,24950,25004,25806,25964,26223,26299,[26356,63745],26775,28039,28805,28913,29855,29861,29898,30169,30828,30956,31455,31478,32069,32147,32789,32831,33051,33686,35686,36629,36885,37857,38915,38968,39514,39912,20418,21843,22586,[22865,63753],23395,23622,24760,25106,26690,26800,26856,28330,30028,30328,30926,31293,31995,32363,32380,35336,35489,35903,38542,40388,21476,21481,21578,21617,22266,22993,23396,23611,24235,25335,25911,25925,25970,26272,26543,27073,27 [...]
+30352,30590,31295,32660,32771,32929,33167,33510,33533,33776,34241,34865,34996,35493,36764,37678,38599,39015,[12220,39640],[12238,40723],21741,26011,26354,26767,31296,[12181,35895],40288,22256,22372,23825,26118,26801,26829,28414,29736,34974,39908,27752,[12219,39592],20379,20844,20849,21151,23380,[12079,24037],24656,24685,25329,25511,25915,29657,31354,34467,36002,38799,[20018,63749],23521,[12093,25096],26524,[12128,29916],31185,33747,35463,35506,36328,36942,37707,38982,[24275,64011],27112, [...]
+20896,23448,23532,24931,26874,27454,28748,29743,29912,31649,32592,33733,35264,36011,38364,39208,21038,24669,25324,36866,20362,20809,21281,22745,24291,26336,27960,28826,29378,29654,31568,33009,37979,21350,25499,32619,20054,20608,22602,22750,24618,24871,25296,27088,39745,23439,32024,32945,36703,20132,20689,21676,21932,23308,23968,24039,25898,25934,26657,27211,29409,30350,30703,32094,32761,33184,34126,34527,36611,36686,37066,39171,39509,39851,19992,20037,20061,20167,20465,20855,21246,21312, [...]
+[21477,63750],21646,22036,22389,22434,23495,23943,24272,25084,25304,25937,26552,26601,27083,27472,27590,27628,27714,28317,28792,29399,29590,29699,30655,30697,31350,32127,32777,[12165,33276],33285,33290,33503,34914,35635,36092,36544,36881,37041,37476,37558,39378,39493,40169,40407,[12244,40860,63751,63752],22283,23616,33738,38816,38827,40628,21531,31384,32676,35033,36557,37089,22528,23624,25496,31391,23470,[12088,24339],31353,31406,33422,36524,20518,21048,21240,21367,22280,25331,25458,2740 [...]
+21413,29527,34152,36470,38357,26426,27331,28528,35437,36556,39243,26231,27512,36020,[12225,39740],21483,22317,22862,25542,27131,29674,30789,31418,31429,31998,33909,35215,36211,36917,38312,21243,22343,30023,31584,33740,37406,27224,20811,21067,21127,25119,26840,26997,38553,20677,21156,21220,25027,[12100,26020],26681,27135,29822,31563,33465,33771,35250,35641,36817,39241,20170,22935,25810,26129,27278,29748,31105,31165,33449,{f:2,c:34942},35167,37670,20235,21450,24613,25201,27762,32026,32102, [...]
+30684,32943,20225,20238,20854,20864,21980,22120,22331,22522,22524,22804,22855,22931,23492,23696,23822,[12080,24049],24190,24524,25216,26071,26083,{f:2,c:26398},26462,26827,26820,27231,27450,27683,27773,27778,28103,29592,29734,29738,29826,29859,30072,30079,30849,30959,31041,{f:2,c:31047},31098,31637,32E3,32186,32648,32774,32813,32908,35352,35663,[35912,63744],36215,37665,37668,39138,39249,{f:2,c:39438},39525,40594,32202,20342,21513,25326,26708,[12198,37329,63754],21931,20794,23068,25062,[ [...]
+25343,37027,[35582,63837],26262,29014,38627,25423,25466,21335,26511,26976,28275,30007,32013,34930,22218,23064,20035,20839,[22856,63756],26608,32784,[12069,22899,63873],[24180,63886],[25754,63889],[31178,63893],[24565,63907],24684,25288,[25467,63908],[23527,63839,63914],23511,21162,22900,24361,[24594,63840],29785,39377,28611,33215,36786,24817,33126,[23615,63933],23273,35365,[26491,63944],[32016,63951],33021,23612,[27877,63971],[21311,63979],[28346,63980],22810,[33590,63998],[20025,63838], [...]
+21934,22296,22727,24406,26039,26086,27264,27573,28237,30701,31471,31774,32222,34507,34962,37170,37723,25787,28606,29562,30136,36948,21846,22349,25018,25812,26311,28129,28251,28525,28601,30192,32835,33213,34113,35203,35527,35674,37663,27795,30035,31572,36367,36957,21776,22530,22616,24162,25095,25758,26848,30070,[31958,64003],34739,40680,20195,22408,22382,[12068,22823],23565,23729,24118,24453,25140,25825,29619,33274,34955,36024,38538,40667,[23429,64004],24503,24755,20498,[12049,20992],2104 [...]
+22615,23566,23648,23798,23947,[24230,64001],24466,24764,25361,25481,25623,26691,26873,27330,28120,28193,28372,28644,29182,30428,30585,31153,31291,33796,35241,36077,36339,36424,36867,36884,36947,37117,37709,38518,38876,27602,28678,29272,29346,29544,30563,31167,31716,32411,[35712,63834],22697,24775,25958,26109,26302,27788,28958,29129,35930,38931,20077,31361,20189,20908,20941,21205,21516,24999,26481,26704,26847,[27934,64005],28540,30140,30643,31461,33012,33891,37509,20828,[12099,26007],2646 [...]
+31431,33651,[12182,35910],36887,38957,23663,33216,33434,36929,36975,37389,24471,23965,27225,29128,30331,31561,34276,35588,37159,39472,[21895,63755],[25078,63757],[30313,63758],[32645,63759],[34367,63760],[34746,63761],[35064,63762],[37007,63763],[27931,63765],[28889,63766],[29662,63767],32097,[33853,63768],[37226,63769],[39409,63770],[20098,63771],[21365,63772],[27396,63773],27410,28734,[29211,63774],[34349,63775],[40478,63776],21068,36771,[23888,63777],25829,25900,27414,[28651,63778],31 [...]
+[34253,63779],[35172,63780],35261,[25289,63781],[33240,63782],[34847,63783],[24266,63784],[26391,63785],[28010,63786],[29436,63787],29701,29807,34690,[37086,63788],[20358,63789],23821,24480,33802,[20919,63790],[25504,63861],[30053,63862],[20142,63863],20486,[20841,63864],[20937,63865],[26753,63866],27153,31918,31921,[31975,63867],[33391,63868],[35538,63869],36635,[37327,63870],20406,20791,[21237,63871],[21570,63872],[24300,63874],24942,25150,[26053,63875],27354,[28670,63876],[31018,63877 [...]
+[38317,63878],39522,[39530,63879],[40599,63880],[40654,63881],[12050,21147,63882],[26310,63883],[27511,63884],28701,31019,[36706,63885],38722,[24976,63887],[25088,63888],25891,[28451,63890],[29001,63891],[29833,63892],[32244,63894],[32879,63895],[34030,63897],[36646,63896],[36899,63898],[37706,63899],20925,[21015,63900],[21155,63901],27916,[28872,63903],[35010,63904],[24265,63906],25986,[27566,63909],28610,[31806,63910],[29557,63911],[20196,63912],20278,[22265,63913],23738,[23994,63915], [...]
+[29618,63917],31533,[32666,63919],32718,[32838,63920],36894,[37428,63921],[38646,63922],[38728,63923],[38936,63924],40801,[20363,63925],28583,[31150,63926],[37300,63927],[38583,63928],[21214,63791],25736,[25796,63792],[27347,63793],28510,28696,[29200,63794],[30439,63795],[12156,32769,63796],[34310,63797],[34396,63798],[36335,63799],36613,[38706,63800],[39791,63801],[40442,63802],[12228,40565],[30860,63803],[31103,63804],[32160,63805],[33737,63806],[37636,63807],[12229,40575,63808],40595, [...]
+[22751,63810],[24324,63811],26407,28711,29903,[31840,63812],[32894,63813],20769,28712,[29282,63814],[30922,63815],[36034,63816],36058,36084,[38647,63817],[20102,63930],[20698,63931],[23534,63932],24278,[26009,63934],[29134,63936],[30274,63937],30637,32842,[34044,63938],[36988,63939],39719,[12243,40845,63940],[22744,63818],23105,[23650,63819],[27155,63820],[28122,63821],[28431,63822],30267,[32047,63823],[32311,63824],34078,35128,37860,[38475,63825],[21129,63943],26066,[26611,63945],27060, [...]
+[28316,63947],28687,[29705,63948],29792,[30041,63949],30244,[30827,63950],35628,[39006,63952],[20845,63953],[25134,63954],[38520,63955],20374,[20523,63956],[23833,63957],[28138,63958],32184,[36650,63959],[24459,63960],[24900,63961],[26647,63962],[38534,63964],[21202,63826],[32907,63827],[20956,63828],[20940,63829],26974,[31260,63830],[32190,63831],[33777,63832],[38517,63833],20442,[21033,63965],21400,[21519,63966],21774,[23653,63967],24743,[26446,63969],[26792,63970],28012,29313,29432,[2 [...]
+29827,[30178,63973],31852,[32633,63974],32696,33673,[35023,63975],[35041,63976],[12197,37324,63977],37328,[38626,63978],39881,[21533,63981],28542,[29136,63982],[29848,63983],[34298,63984],36522,[38563,63985],[40023,63986],[40607,63987],[26519,63988],[28107,63989],29747,[33256,63990],38678,30764,[12148,31435,63991],[31520,63992],[31890,63993],25705,29802,30194,30908,30952,[12218,39340],39764,[12231,40635],23518,24149,28448,33180,33707,37E3,19975,21325,23081,24018,24398,24930,25405,26217,2 [...]
+28459,28771,30622,33836,34067,34875,36627,39237,39995,21788,25273,26411,27819,33545,35178,38778,20129,22916,{f:2,c:24536},26395,32178,32596,33426,33579,33725,36638,37017,22475,22969,23186,23504,26151,26522,26757,27599,29028,32629,36023,36067,36993,39749,33032,35978,38476,39488,[12230,40613],23391,27667,29467,30450,30431,33804,20906,35219,20813,20885,21193,26825,27796,30468,30496,32191,32236,[12207,38754],40629,28357,34065,20901,21517,21629,26126,26269,26919,28319,[12139,30399],30609,3355 [...]
+37225,37528,40180,34946,20398,20882,21215,22982,24125,24917,{f:2,c:25720},26286,26576,27169,27597,[12113,27611],29279,29281,29761,30520,[12141,30683],32791,33468,33541,35584,35624,35980,[12106,26408],27792,29287,[12140,30446],30566,31302,40361,27519,27794,22818,26406,33945,21359,22675,22937,24287,25551,26164,26483,28218,29483,31447,33495,37672,21209,24043,25006,25035,25098,25287,25771,[12102,26080],26969,27494,[12111,27595],28961,29687,30045,32326,33310,33538,34154,35491,36031,38695,4028 [...]
+20497,21006,21563,21839,[12098,25991],27766,{f:2,c:32010},32862,34442,[12200,38272],38639,21247,27797,29289,21619,23194,23614,23883,24396,24494,26410,26806,26979,28220,28228,30473,[12150,31859],32654,34183,35598,36855,38753,40692,23735,24758,24845,25003,25935,{f:2,c:26107},27665,27887,29599,29641,32225,38292,23494,34588,35600,21085,21338,25293,25615,25778,26420,27192,27850,29632,29854,31636,31893,32283,33162,33334,34180,36843,38649,39361,20276,21322,21453,21467,25292,25644,25856,26001,27 [...]
+28504,29677,30036,30242,30436,30460,30928,[30971,63844],31020,32070,33324,34784,36820,38930,39151,21187,25300,25765,28196,28497,30332,36299,37297,37474,39662,39747,20515,20621,22346,22952,23592,24135,24439,25151,25918,[12101,26041],26049,26121,26507,27036,28354,30917,32033,32938,33152,33323,33459,33953,34444,35370,35607,37030,38450,40848,20493,20467,22521,24472,25308,25490,26479,28227,28953,30403,32972,32986,{f:2,c:35060},35097,36064,36649,37197,38506,20271,20336,24091,26575,26658,[12137 [...]
+39748,24161,27146,29033,29140,30058,32321,34115,34281,39132,20240,31567,32624,38309,20961,24070,26805,27710,27726,27867,29359,31684,33539,27861,29754,20731,21128,22721,25816,27287,29863,30294,30887,34327,38370,38713,21342,24321,35722,36776,36783,37002,21029,30629,40009,40712,19993,20482,20853,23643,24183,26142,26170,26564,26821,28851,29953,30149,31177,31453,36647,39200,39432,20445,22561,22577,23542,26222,27493,27921,28282,28541,29668,29995,33769,35036,35091,35676,36628,20239,20693,21264, [...]
+23443,[24489,63846],26381,31119,33145,33583,34068,35079,35206,36665,[36667,64007],39333,39954,26412,20086,20472,22857,23553,{f:2,c:23791},25447,26834,28925,29090,29739,32299,34028,34562,36898,37586,40179,[19981,63847],20184,20463,20613,21078,21103,21542,21648,22496,22827,23142,23386,23413,23500,24220,25206,25975,26023,28014,28325,[12119,29238],31526,31807,[12152,32566],{f:2,c:33104},33178,33344,33433,33705,35331,36E3,36070,36091,36212,36282,37096,37340,[12201,38428],38468,39385,40167,[21 [...]
+20998,21545,22132,22707,22868,22894,24575,24996,25198,26128,27774,28954,30406,31881,31966,32027,33452,36033,38640,20315,24343,24447,25282,23849,26379,26842,30844,32323,40300,19989,20633,[12052,21269],21290,21329,22915,23138,24199,24754,24970,25161,25209,26E3,26503,27047,[12112,27604],{f:3,c:27606},27832,29749,30202,30738,30865,31189,31192,31875,32203,32737,32933,33086,33218,33778,34586,35048,35513,35692,36027,37145,[12206,38750],[12214,39131],[12240,40763],22188,23338,24428,25996,27315,2 [...]
+28657,28693,29277,29613,36007,36051,38971,24977,27703,32856,39425,20045,20107,20123,20181,20282,20284,20351,20447,20735,21490,21496,21766,21987,22235,[12064,22763],22882,23057,23531,23546,23556,24051,24107,24473,24605,25448,26012,26031,26614,26619,26797,27515,27801,27863,28195,28681,29509,30722,31038,31040,31072,31169,31721,32023,32114,32902,33293,33678,34001,34503,35039,35408,35422,35613,36060,36198,36781,37034,39164,39391,40605,21066,26388,20632,21034,[12077,23665],25955,27733,29642,29 [...]
+31639,33948,37240,38704,20087,25746,[27578,63856],29022,34217,19977,26441,26862,28183,33439,34072,34923,25591,28545,37394,39087,19978,20663,20687,20767,21830,21930,22039,23360,23577,23776,24120,24202,24224,24258,24819,26705,27233,28248,29245,29248,[29376,63994],30456,31077,31665,32724,35059,35316,35443,35937,36062,38684,[22622,63852],29885,36093,21959,31329,[32034,63850],[12170,33394],29298,[12131,29983],29989,31513,22661,22779,23996,24207,24246,24464,24661,25234,25471,25933,26257,26329, [...]
+26866,29312,29790,31598,32110,32214,32626,32997,33298,34223,35199,35475,36893,37604,[12233,40653],[12239,40736],[12067,22805],22893,24109,24796,26132,26227,26512,27728,28101,28511,[12143,30707],30889,33990,37323,37675,20185,20682,20808,21892,23307,23459,25159,25982,26059,28210,29053,29697,29764,29831,29887,30316,31146,32218,32341,32680,33146,33203,33337,34330,34796,35445,36323,36984,37521,37925,39245,39854,21352,23633,26964,27844,27945,28203,[12166,33292],34203,35131,35373,[35498,63855,6 [...]
+40807,21089,26297,27570,32406,34814,36109,38275,38493,25885,28041,29166,22478,22995,23468,24615,24826,25104,26143,26207,29481,29689,30427,[30465,63853],31596,32854,32882,33125,35488,37266,19990,21218,27506,27927,31237,31545,32048,36016,21484,22063,22609,23477,[12073,23567],23569,24034,25152,25475,25620,26157,26803,27836,28040,28335,28703,28836,29138,29990,30095,30094,30233,31505,31712,31787,32032,32057,34092,34157,34311,35380,36877,36961,37045,37559,38902,39479,20439,23660,26463,28049,31 [...]
+35606,36118,36895,23403,24061,25613,33984,36956,39137,[29575,63841,63963],23435,24730,26494,28126,35359,35494,36865,38924,21047,28753,30862,37782,34928,37335,20462,21463,22013,22234,22402,22781,23234,23432,23723,23744,24101,24833,25101,[12095,25163],25480,25628,25910,[25976,63849],27193,27530,[12116,27700],27929,28465,29159,29417,29560,29703,29874,30246,30561,31168,31319,31466,31929,32143,32172,32353,32670,33065,33585,33936,34010,34282,34966,35504,35728,36664,36930,36995,37228,37526,3756 [...]
+c:38567},38614,38656,38920,[12216,39318],39635,39706,21460,22654,22809,23408,23487,28113,28506,29087,29729,29881,32901,33789,24033,24455,24490,24642,26092,26642,26991,27219,27529,27957,28147,29667,30462,30636,31565,32020,33059,33308,33600,34036,34147,35426,35524,37255,37662,38918,39348,25100,34899,36848,37477,23815,23847,23913,29791,33181,34664,28629,[25342,63859],32722,35126,35186,19998,20056,20711,21213,21319,25215,26119,32361,34821,38494,20365,21273,22070,22987,23204,[12075,23608],236 [...]
+24066,24337,24643,26045,26159,26178,26558,26612,29468,[12142,30690],[12144,31034],32709,33940,33997,35222,35430,35433,35553,[12183,35925],35962,22516,23508,24335,24687,25325,26893,27542,28252,29060,31698,34645,[35672,63996],36606,[12215,39135],39166,20280,20353,20449,21627,23072,23480,24892,26032,26216,29180,30003,31070,32051,33102,[12162,33251],33688,34218,34254,34563,35338,[12189,36523],[12191,36763],36805,22833,23460,23526,24713,23529,23563,[12092,24515],27777,28145,28683,29978,33455, [...]
+63997],[12055,21313],38617,[12114,27663],20126,20420,20818,21854,23077,23784,25105,[12123,29273],33469,33706,34558,34905,35357,38463,38597,39187,40201,40285,22538,23731,23997,24132,[24801,63929],24853,25569,[27138,63764,63836,63935],28197,37122,37716,38990,39952,40823,23433,23736,25353,26191,26696,30524,38593,38797,38996,39839,26017,35585,36555,38332,21813,23721,24022,24245,26263,30284,33780,38343,22739,25276,29390,40232,20208,22830,24591,26171,27523,31207,40230,21395,21696,22467,23830,2 [...]
+28079,30861,33406,38552,38724,21380,25212,25494,28082,32266,33099,38989,27387,32588,40367,40474,20063,20539,20918,22812,24825,25590,26928,29242,32822,37326,24369,32004,[33509,63860],33903,33979,34277,36493,20335,22756,23363,24665,25562,25880,25965,26264,26954,27171,27915,28673,29036,30162,30221,31155,31344,[12154,32650],35140,35731,37312,38525,39178,22276,24481,26044,28417,30208,31142,35486,39341,[12226,39770],40812,20740,25014,25233,27277,33222,20547,22576,24422,28937,[12180,35328],3557 [...]
+20474,20796,22196,22852,25513,28153,23978,26989,20870,20104,20313,22914,27487,27741,29877,30998,33287,33349,33593,36671,36701,39192,20134,22495,24441,[26131,63968],30123,32377,35695,36870,39515,22181,22567,23032,23071,23476,24310,25424,25403,26941,27783,27839,28046,28051,28149,28436,28895,28982,29017,29123,29141,30799,30831,31605,32227,32303,34893,36575,37467,40182,24709,28037,29105,38321,21421,26579,28814,28976,29744,33398,33490,38331,39653,40573,26308,29121,[33865,63854],22603,23992,24 [...]
+26254,27001,27054,27704,27891,28214,28481,28634,28699,28719,29008,29151,29552,29787,29908,30408,31310,32403,33521,35424,36814,37704,38681,20034,20522,21E3,21473,26355,27757,28618,29450,30591,31330,33454,34269,34306,35028,35427,35709,35947,37555,38675,38928,20116,20237,20425,20658,21320,21566,21555,21978,22626,22714,22887,23067,23524,24735,25034,25942,26111,26212,26791,27738,28595,28879,29100,29522,31613,34568,35492,39986,40711,23627,27779,29508,[12127,29577],37434,28331,29797,30239,31337 [...]
+20800,22725,25793,29934,29973,30320,32705,37013,38605,39252,28198,[12129,29926],{f:2,c:31401},33253,34521,34680,35355,23113,23436,23451,26785,26880,28003,29609,29715,29740,30871,32233,32747,33048,33109,33694,35916,[38446,63942],38929,[12104,26352],24448,26106,26505,27754,29579,20525,23043,27498,30702,22806,23916,24013,29477,30031,20709,20985,22575,22829,22934,23002,23525,23970,25303,25622,25747,25854,26332,27208,29183,29796,31368,31407,32327,32350,32768,33136,34799,35201,35616,36953,3699 [...]
+27442,28020,32287,35109,36785,20433,20653,20887,21191,22471,22665,23481,24248,24898,27029,28044,28263,28342,29076,29794,[12132,29992],29996,32883,33592,33993,36362,37780,37854,20110,20305,20598,20778,[12060,21448],21451,21491,23431,23507,23588,24858,24962,26100,[12124,29275],29591,29760,30402,31056,31121,31161,32006,[12155,32701],33419,34261,34398,36802,36935,37109,37354,38533,[12204,38632],38633,21206,24423,26093,26161,26671,29020,31286,37057,38922,20113,27218,27550,28560,29065,32792,33 [...]
+36939,38549,38642,38907,34074,39729,20112,29066,38596,20803,21407,21729,22291,22290,22435,23195,23236,23491,24616,24895,25588,27781,27961,28274,28304,29232,29503,29783,33489,34945,36677,36960,38498,39E3,40219,[12105,26376],36234,37470,20301,20553,20702,21361,22285,22996,23041,23561,24944,26256,28205,29234,29771,32239,32963,33806,33894,34111,34655,34907,35096,35586,36949,[12209,38859],39759,20083,20369,20754,20842,21807,21929,23418,23461,{f:2,c:24188},24254,24736,24799,{f:2,c:24840},25540 [...]
+26580,26586,{f:2,c:26977},27833,27943,28216,28641,{f:2,c:29494},29788,30001,30290,32173,33278,33848,35029,35480,35547,35565,36400,36418,36938,36926,36986,[12195,37193],37321,37742,22537,27603,[12161,32905],32946,20801,22891,23609,28516,29607,32996,36103,37399,38287,[12160,32895],25102,28700,32104,34701,22432,24681,24903,27575,35518,37504,38577,[12036,20057],21535,28139,34093,38512,[12211,38899],39150,25558,27875,[12194,37009],20957,25033,33210,40441,20381,20506,20736,23452,24847,25087,25 [...]
+27589,30097,30691,32681,33380,34191,34811,[12176,34915],35516,35696,37291,[12038,20108],20197,20234,22839,23016,24050,24347,24411,24609,29246,29669,[30064,63842],30157,31227,[12157,32780],[12159,32819],32900,33505,33617,36029,36019,36999,39156,39180,28727,30410,32714,32716,32764,35610,[12040,20154],20161,20995,21360,[21693,63902],22240,23035,23493,24341,24525,28270,32106,33589,34451,35469,38765,38775,[12032,19968],20314,20350,22777,[12103,26085],28322,36920,37808,39353,20219,22764,22922, [...]
+31252,33615,36035,[12042,20837],21316,20173,21097,23381,33471,20180,[21050,63999],21672,22985,23039,[12070,23376],23383,23388,24675,24904,28363,[28825,63995],29038,29574,29943,30133,30913,32043,32773,[12163,33258],33576,34071,34249,35566,36039,38604,20316,21242,22204,26027,26152,28796,28856,29237,32189,33421,37196,38592,40306,23409,26855,27544,28538,30430,23697,26283,28507,31668,31786,34870,38620,19976,20183,21280,22580,22715,22767,22892,23559,24115,24196,24373,25484,26290,26454,27167,27 [...]
+28479,29254,29520,29835,31456,31911,33144,33247,33255,33674,33900,34083,34196,34255,35037,36115,37292,[12199,38263],38556,20877,21705,22312,23472,25165,26448,26685,26771,28221,28371,28797,32289,35009,36001,36617,40779,40782,29229,31631,35533,37658,20295,20302,20786,21632,22992,24213,25269,26485,26990,27159,27822,28186,29401,29482,30141,31672,32053,33511,33785,33879,34295,35419,36015,36487,36889,37048,38606,40799,21219,21514,23265,23490,25688,25973,28404,29380,30340,31309,31515,31821,3231 [...]
+35627,36042,[12186,36196],36321,36447,36842,36857,36969,37841,20291,20346,20659,20840,20856,21069,21098,22625,22652,22880,23560,23637,24283,24731,25136,26643,27583,27656,28593,29006,29728,[12133,3E4],30008,30033,30322,31564,31627,31661,31686,32399,35438,36670,36681,37439,37523,37666,37931,38651,39002,39019,39198,[20999,64E3],25130,25240,27993,30308,31434,31680,32118,21344,23742,24215,28472,28857,31896,38673,39822,40670,25509,25722,34678,19969,20117,20141,20572,20597,21576,22979,23450,241 [...]
+24311,24449,24773,25402,25919,25972,26060,26230,26232,26622,26984,27273,27491,27712,28096,28136,28191,28254,28702,28833,29582,29693,30010,30555,30855,31118,31243,31357,31934,32142,33351,35330,35562,35998,37165,37194,37336,37478,37580,37664,38662,38742,38748,38914,[12237,40718],21046,21137,21884,22564,24093,24351,24716,25552,26799,28639,31085,31532,33229,34234,35069,35576,36420,37261,38500,38555,38717,38988,[12241,40778],20430,20806,20939,21161,22066,24340,24427,25514,25805,26089,26177,26 [...]
+26397,26781,26839,27133,28437,28526,29031,29157,[12118,29226],29866,30522,31062,31066,31199,31264,31381,31895,31967,32068,32368,32903,34299,34468,35412,35519,36249,36481,36896,36973,37347,38459,38613,[12227,40165],26063,31751,[12188,36275],37827,23384,23562,21330,25305,29469,20519,23447,24478,24752,24939,26837,28121,29742,31278,32066,32156,32305,33131,36394,36405,37758,37912,20304,22352,24038,24231,25387,32618,20027,20303,20367,20570,23005,32964,21610,21608,22014,22863,23449,24030,24282, [...]
+26609,26666,27880,27954,28234,28557,28855,29664,30087,31820,32002,32044,32162,[12168,33311],34523,35387,35461,[12187,36208],36490,36659,36913,37198,37202,37956,39376,[12149,31481],31909,20426,20737,20934,22472,23535,23803,26201,27197,27994,28310,28652,28940,30063,31459,34850,36897,36981,38603,39423,33537,20013,20210,34886,37325,21373,27355,26987,27713,33914,22686,24974,26366,25327,28893,29969,30151,32338,33976,35657,36104,20043,21482,21675,22320,22336,24535,25345,25351,25711,[12096,25903 [...]
+26525,26547,[12108,27490],27744,27802,28460,30693,30757,31049,31063,32025,32930,33026,[12164,33267],33437,33463,34584,35468,36100,36286,36978,30452,31257,31287,32340,32887,21767,21972,22645,25391,25634,26185,26187,26733,27035,27524,27941,28337,29645,29800,29857,30043,30137,30433,30494,30603,31206,32265,32285,33275,34095,34967,35386,36049,36587,[12192,36784,63857],36914,37805,38499,38515,38663,20356,21489,23018,23241,24089,26702,29894,30142,31209,31378,33187,34541,36074,36300,36845,26015, [...]
+28503,32221,36655,37878,38598,24501,25074,28548,19988,20376,20511,21449,21983,23919,24046,27425,27492,30923,31642,36425,[12190,36554,63746],36974,25417,25662,30528,31364,37679,38015,40810,25776,28591,29158,29864,29914,31428,31762,32386,31922,32408,35738,36106,38013,39184,39244,21049,23519,25830,26413,32046,20717,[21443,63851],22649,{f:2,c:24920},25082,26028,31449,35730,35734,20489,20513,21109,21809,23100,24288,24432,24884,25950,26124,26166,26274,27085,28356,28466,29462,30241,31379,33081, [...]
+33980,20661,22512,23488,23528,24425,25505,30758,32181,33756,34081,37319,37365,20874,26613,31574,36012,20932,22971,24765,34389,20508,21076,23610,24957,25114,[25299,64002],25842,26021,28364,30240,33034,36448,38495,38587,20191,21315,21912,22825,24029,25797,27849,28154,29588,31359,[12167,33307],34214,36068,36368,36983,37351,38369,38433,38854,20984,21746,21894,24505,25764,28552,32180,36639,36685,37941,20681,23574,27838,28155,29979,30651,31805,31844,35449,35522,22558,22974,24086,25463,29266,30 [...]
+35548,36028,36626,24307,26228,28152,32893,33729,35531,[12205,38737],39894,21059,26367,28053,28399,32224,35558,36910,36958,39636,21021,21119,21736,24980,25220,25307,26786,26898,26970,27189,28818,28966,30813,30977,30990,31186,31245,32918,[12171,33400],33493,33609,34121,35970,36229,37218,37259,37294,20419,22225,29165,30679,34560,35320,[12072,23544],24534,26449,37032,21474,22618,23541,24740,24961,25696,32317,32880,34085,37507,25774,20652,23828,26368,22684,25277,25512,26894,27E3,27166,28267,3 [...]
+33467,33833,35535,36264,36861,37138,37195,37276,37648,37656,37786,38619,39478,39949,19985,30044,31069,31482,31569,31689,32302,33988,36441,36468,36600,36880,26149,26943,29763,20986,26414,40668,20805,24544,27798,34802,34909,34935,24756,33205,33795,36101,21462,21561,22068,23094,23601,28810,32736,32858,33030,33261,36259,37257,39519,40434,20596,20164,21408,24827,28204,23652,20360,20516,21988,23769,24159,24677,26772,27835,28100,29118,30164,30196,30305,31258,31305,32199,32251,32622,33268,34473, [...]
+39347,[12242,40786],21063,21189,39149,35242,19971,26578,28422,20405,23522,26517,[27784,63858],28024,29723,30759,37341,37756,34756,31204,31281,24555,20182,21668,21822,22702,22949,24816,25171,25302,26422,26965,33333,38464,39345,39389,20524,21331,21828,22396,25176,25826,26219,26589,28609,28655,29730,29752,35351,37944,21585,22022,22374,24392,24986,27470,28760,28845,32187,35477,22890,33067,25506,30472,32829,36010,22612,25645,27067,23445,24081,28271,34153,20812,21488,22826,24608,24907,27526,27 [...]
+31518,32974,33492,36294,37040,39089,25799,28580,25745,25860,20814,21520,[12063,22303],35342,24927,26742,30171,31570,32113,36890,22534,27084,33151,35114,36864,38969,20600,22871,22956,25237,36879,39722,24925,29305,38358,22369,23110,24052,25226,25773,25850,26487,27874,27966,29228,29750,30772,32631,33453,36315,38935,21028,22338,26495,29256,29923,36009,36774,37393,38442,[12043,20843],21485,25420,20329,21764,24726,25943,27803,28031,29260,29437,31255,35207,[12185,35997],24429,28558,28921,33192, [...]
+63845],20559,25153,[12122,29255],31687,32232,32745,36941,38829,39449,36022,22378,24179,26544,33805,35413,21536,23318,24163,24290,24330,25987,32954,34109,38281,38491,20296,21253,21261,21263,21638,21754,22275,24067,24598,25243,25265,25429,27873,28006,30129,30770,32990,33071,33502,33889,33970,34957,35090,36875,37610,39165,39825,24133,[26292,64006],26333,28689,29190,20469,21117,24426,24915,26451,27161,28418,29922,31080,34920,35961,39111,39108,39491,21697,31263,26963,35575,35914,[12213,39080] [...]
+25259,30130,[12138,30382],34987,36991,38466,21305,24380,24517,[27852,63848],29644,30050,[12134,30091],31558,33534,39325,20047,36924,19979,20309,21414,22799,24264,26160,27827,29781,33655,34662,36032,36944,38686,39957,22737,23416,34384,35604,40372,23506,24680,24717,26097,27735,28450,28579,28698,32597,32752,{f:2,c:38289},38480,38867,21106,36676,20989,21547,21688,21859,21898,27323,28085,32216,33382,37532,38519,40569,21512,21704,30418,34532,38308,38356,38492,20130,20233,23022,23270,24055,2465 [...]
+26689,27782,28207,32568,32923,33322,38917,20133,20565,21683,22419,22874,23401,23475,25032,26999,28023,28707,34809,35299,35442,35559,36994,39405,39608,21182,26680,20502,24184,26447,33607,[12175,34892,64008],20139,21521,22190,29670,37141,38911,39177,39255,[12217,39321],22099,22687,34395,35377,25010,27382,29563,36562,27463,38570,39511,22869,29184,36203,[12208,38761],20436,23796,24358,25080,26203,27883,28843,[12126,29572],29625,29694,30505,30541,32067,32098,32291,33335,34898,36066,37449,3902 [...]
+31348],[12174,34880],[12212,38913],23244,20448,21332,22846,23805,25406,28025,29433,33029,33031,33698,37583,38960,20136,20804,21009,22411,24418,27842,28366,28677,28752,28847,29074,29673,[29801,63918],33610,34722,34913,36872,37026,37795,39336,20846,24407,24800,24935,26291,34137,36426,37295,38795,20046,20114,21628,22741,22778,22909,23733,24359,[12094,25142],25160,26122,26215,27627,28009,28111,28246,28408,28564,28640,28649,28765,29392,29733,29786,29920,30355,31068,31946,32286,32993,33446,338 [...]
+34382,34399,34676,35703,35946,37804,38912,39013,24785,25110,37239,23130,26127,28151,28222,29759,39746,24573,24794,31503,21700,24344,27742,27859,27946,28888,32005,34425,35340,40251,21270,21644,23301,27194,[12117,28779],30069,31117,[12146,31166],33457,33775,35441,35649,36008,38772,25844,25899,{f:2,c:30906},31339,20024,21914,22864,23462,24187,24739,25563,27489,26213,26707,28185,29029,29872,32008,36996,39529,39973,27963,[28369,63748],29502,35905,38346,20976,24140,24488,24653,24822,24880,2490 [...]
+27045,27841,28255,28361,28514,29004,29852,30343,31681,31783,33618,34647,36945,38541,[12232,40643],21295,22238,24315,24458,24674,24724,25079,26214,26371,27292,28142,28590,28784,29546,32362,33214,33588,34516,35496,36036,21123,29554,23446,27243,37892,21742,22150,23389,25928,25989,26313,26783,28045,28102,[12120,29243],32948,37237,39501,20399,20505,21402,21518,21564,21897,21957,24127,24460,26429,29030,29661,36869,21211,21235,22628,22734,28932,29071,29179,34224,35347,[26248,63941],34216,21927, [...]
+33841,21321,21913,27585,24409,24509,25582,26249,28999,35569,36637,40638,20241,25658,28875,30054,34407,24676,35662,40440,20807,20982,21256,27958,33016,[12234,40657],26133,27427,28824,30165,21507,23673,32007,35350,[12107,27424],27453,27462,21560,24688,27965,32725,33288,20694,20958,21916,22123,22221,23020,23305,24076,24985,24984,25137,26206,26342,29081,{f:2,c:29113},29351,31143,31232,32690,35440,{s:163},{f:4,c:12310},{s:14},8223,8219,{f:2,c:8314},{s:7},8316,0,{f:2,c:8317},{s:23},700,{s:44}, [...]
+{s:20},{f:10,c:10122},{s:36},{f:26,c:9398},{s:61},{f:2,c:8826},{f:2,c:8910},{f:2,c:8832},{f:4,c:8816},0,8842,0,8843,{f:2,c:8822},8825,{f:2,c:8922},{s:5},8773,8771,8776,0,8868,{s:78},8244,{s:11},9839,{s:4},8258,{s:4},10045,0,0,8226,{s:4},{f:2,c:8249},{s:16},10010,10006,0,9711,{s:3},10070,0,9676,{s:24},9775,{s:6},12320,0,{f:10,c:10102},{s:17},12306,12342,{s:13},8710,0,8735,0,{f:2,c:8741},0,8787,8785,{f:2,c:8806},8723,{f:3,c:8853},0,8980,0,0,8802,0,9649,0,8738,8784,0,0,8867,0,0,{f:2,c:8814} [...]
+8713,8716,{f:2,c:8891},8794,8966,{s:6},12958,0,8252,{s:11},9702,{s:3},9663,9653,9657,9667,{s:4},9674,12849,12857,13259,{f:5,c:9327},{s:18},8656,8655,8653,{s:37},8657,8659,{s:8},8626,8625,0,8628,8624,8627,{s:14},8636,8640,{s:10},{f:2,c:8644},{s:144},{f:5,c:9347},{s:33},12948,{s:15},12965,{s:93},8672,8674,8673,8675,{s:4},8678,8680,8679,8681,{s:20},9757,9759,{s:76},12944,{f:6,c:12938},{s:15},{f:2,c:12318},8246,0,8245,{s:3},12540,0,0,{f:2,c:44034},{f:2,c:44037},{f:5,c:44043},44056,{f:2,c:440 [...]
+c:44065},{f:7,c:44069},44078,{f:6,c:44082},{f:2,c:44090},{f:3,c:44093},{f:10,c:44097},44108,{f:6,c:44110},{f:3,c:44117},{f:3,c:44121},{f:19,c:44125},{f:2,c:44146},{f:2,c:44149},44153,{f:5,c:44155},44162,{f:2,c:44167},{f:3,c:44173},{f:3,c:44177},{f:7,c:44181},44190,{f:6,c:44194},44203,{f:2,c:44205},{f:7,c:44209},44218,{f:3,c:44222},{f:2,c:44226},{f:3,c:44229},{f:3,c:44233},{f:8,c:44237},44246,{f:8,c:44248},{f:2,c:44258},{f:2,c:44261},44265,44267,{f:2,c:44269},44274,44276,{f:5,c:44279},{f: [...]
+{f:3,c:44289},44293,{f:5,c:44295},44302,44304,{f:6,c:44306},{f:3,c:44313},{f:3,c:44317},{f:8,c:44321},{f:2,c:44330},{f:6,c:44334},{f:2,c:44342},{f:3,c:44345},{f:7,c:44349},44358,44360,{f:6,c:44362},{f:3,c:44369},{f:3,c:44373},{f:8,c:44377},44386,{f:8,c:44388},{f:2,c:44398},{f:2,c:44401},{f:4,c:44407},44414,44416,{f:5,c:44419},{f:2,c:44426},{f:3,c:44429},{f:11,c:44433},{f:6,c:44446},{f:18,c:44453},{f:8,c:44472},{f:2,c:44482},{f:3,c:44485},{f:7,c:44489},44498,{f:8,c:44500},{f:3,c:44509},{f [...]
+{f:19,c:44517},{f:2,c:44538},{f:2,c:44541},{f:6,c:44546},44554,44556,{f:6,c:44558},{f:27,c:44565},{f:2,c:44594},{f:2,c:44597},44601,{f:5,c:44603},44610,44612,{f:3,c:44615},44619,44623,{f:3,c:44625},44629,{f:5,c:44631},44638,{f:3,c:44642},{f:2,c:44646},{f:2,c:44650},{f:3,c:44653},{f:7,c:44657},44666,{f:6,c:44670},{f:6,c:44678},{f:47,c:44685},44735,{f:3,c:44737},{f:7,c:44741},44750,{f:6,c:44754},{f:2,c:44762},{f:11,c:44765},{f:2,c:44777},44780,{f:6,c:44782},{f:3,c:44789},{f:3,c:44793},{f:1 [...]
+{f:4,c:44809},{f:2,c:44814},{f:27,c:44817},{f:2,c:44846},44849,44851,{f:7,c:44853},44862,44864,{f:4,c:44868},{f:6,c:44874},{f:11,c:44881},{f:6,c:44894},{f:19,c:44902},{f:6,c:44922},{f:3,c:44929},{f:3,c:44933},{f:7,c:44937},{f:3,c:44946},{f:6,c:44950},{f:27,c:44957},{f:2,c:44986},{f:3,c:44989},{f:6,c:44993},45002,45004,{f:5,c:45007},{f:7,c:45013},{f:11,c:45021},{f:6,c:45034},{f:2,c:45042},{f:3,c:45045},{f:7,c:45049},{f:2,c:45058},{f:7,c:45061},{f:3,c:45069},{f:3,c:45073},{f:7,c:45077},{f: [...]
+{f:27,c:45097},{f:2,c:45126},45129,45131,45133,{f:4,c:45135},45142,45144,{f:3,c:45146},{f:30,c:45150},{f:2,c:45182},{f:3,c:45185},{f:7,c:45189},45198,45200,{f:6,c:45202},45211,{f:2,c:45213},{f:5,c:45219},45226,45232,45234,{f:2,c:45238},{f:3,c:45241},{f:7,c:45245},45254,{f:6,c:45258},{f:2,c:45266},{f:3,c:45269},{f:7,c:45273},{f:4,c:45281},{f:34,c:45286},45322,{f:3,c:45325},45329,{f:4,c:45332},45338,{f:5,c:45342},{f:2,c:45350},{f:3,c:45353},{f:7,c:45357},45366,{f:6,c:45370},{f:2,c:45378},{ [...]
+{f:7,c:45385},{f:2,c:45394},{f:2,c:45398},{f:3,c:45401},{f:3,c:45405},{f:23,c:45409},{f:2,c:45434},{f:3,c:45437},45441,{f:5,c:45443},45450,45452,{f:4,c:45454},{f:3,c:45461},{f:3,c:45465},{f:11,c:45469},{f:35,c:45481},{f:3,c:45517},{f:3,c:45521},{f:7,c:45525},45534,{f:8,c:45536},{f:2,c:45546},{f:3,c:45549},{f:8,c:45553},45562,45564,{f:6,c:45566},{f:2,c:45574},{f:2,c:45577},{f:7,c:45581},45590,45592,{f:6,c:45594},{f:19,c:45601},{f:7,c:45621},{f:27,c:45629},{f:3,c:45657},{f:3,c:45661},{f:7, [...]
+{f:10,c:45674},{f:6,c:45686},{f:7,c:45693},{f:3,c:45702},{f:6,c:45706},{f:2,c:45714},{f:3,c:45717},{f:5,c:45723},45730,45732,{f:3,c:45735},45739,{f:3,c:45741},{f:3,c:45745},{f:19,c:45749},{f:2,c:45770},{f:3,c:45773},45777,{f:5,c:45779},45786,45788,{f:4,c:45790},45795,45799,{f:2,c:45801},{f:3,c:45808},45814,{f:3,c:45820},{f:2,c:45826},{f:3,c:45829},{f:7,c:45833},45842,{f:6,c:45846},{f:55,c:45853},45911,{f:2,c:45913},45917,{f:4,c:45920},45926,45928,45930,{f:2,c:45932},45935,{f:2,c:45938},{ [...]
+{f:7,c:45945},45954,{f:6,c:45958},{f:3,c:45965},{f:3,c:45969},{f:11,c:45973},{f:6,c:45986},{f:3,c:45993},{f:23,c:45997},{f:2,c:46022},{f:2,c:46025},46029,46031,{f:3,c:46033},46038,46040,46042,46044,{f:2,c:46046},{f:3,c:46049},{f:3,c:46053},{f:19,c:46057},{f:19,c:46077},{f:7,c:46097},{f:3,c:46105},{f:3,c:46109},{f:7,c:46113},46122,{f:8,c:46124},{f:27,c:46133},{f:2,c:46162},{f:3,c:46165},{f:7,c:46169},46178,46180,{f:6,c:46182},{f:19,c:46189},{f:7,c:46209},{f:20,c:46217},{f:6,c:46238},{f:3, [...]
+{f:3,c:46249},{f:8,c:46253},46262,46264,{f:6,c:46266},{f:3,c:46273},{f:3,c:46277},{f:7,c:46281},{f:4,c:46289},{f:6,c:46294},{f:2,c:46302},{f:2,c:46305},46309,{f:5,c:46311},46318,46320,{f:6,c:46322},{f:27,c:46329},{f:2,c:46358},{f:2,c:46361},{f:7,c:46365},46374,{f:5,c:46379},{f:2,c:46386},{f:3,c:46389},{f:7,c:46393},46402,{f:5,c:46406},{f:2,c:46414},{f:3,c:46417},{f:7,c:46421},46430,{f:62,c:46434},{f:2,c:46498},{f:3,c:46501},46505,{f:4,c:46508},46514,{f:5,c:46518},{f:2,c:46526},{f:3,c:465 [...]
+c:46533},46542,{f:6,c:46546},{f:19,c:46553},{f:35,c:46573},{f:2,c:46610},{f:3,c:46613},{f:12,c:46617},{f:6,c:46630},{f:7,c:46637},{f:19,c:46645},{f:27,c:46665},{f:3,c:46693},{f:51,c:46697},{f:2,c:46750},{f:3,c:46753},{f:6,c:46757},{f:4,c:46765},{f:34,c:46770},{f:27,c:46805},{f:3,c:46833},{f:3,c:46837},{f:7,c:46841},{f:3,c:46850},{f:34,c:46854},{f:2,c:46890},{f:2,c:46893},{f:7,c:46897},46906,{f:8,c:46908},{f:3,c:46917},{f:3,c:46921},{f:7,c:46925},{f:10,c:46934},{f:3,c:46945},{f:3,c:46949} [...]
+46962,46964,{f:6,c:46966},{f:2,c:46974},{f:3,c:46977},{f:7,c:46981},46990,{f:3,c:46995},{f:2,c:47002},{f:3,c:47005},{f:7,c:47009},47018,{f:6,c:47022},{f:2,c:47030},{f:14,c:47033},47048,{f:34,c:47050},{f:2,c:47086},{f:3,c:47089},{f:7,c:47093},47102,{f:5,c:47106},{f:2,c:47114},{f:3,c:47117},{f:7,c:47121},47130,47132,{f:6,c:47134},{f:2,c:47142},{f:3,c:47145},{f:7,c:47149},47158,{f:6,c:47162},{f:3,c:47169},{f:12,c:47173},47186,{f:8,c:47188},{f:2,c:47198},{f:3,c:47201},{f:7,c:47205},47214,472 [...]
+{f:3,c:47225},{f:16,c:47229},{f:26,c:47246},{f:7,c:47273},{f:3,c:47281},{f:3,c:47285},{f:7,c:47289},47298,47300,{f:6,c:47302},{f:3,c:47309},{f:3,c:47313},{f:8,c:47317},47326,47328,{f:6,c:47330},{f:2,c:47338},{f:3,c:47341},{f:7,c:47345},47354,47356,{f:6,c:47358},{f:19,c:47365},{f:7,c:47385},{f:27,c:47393},{f:2,c:47422},{f:3,c:47425},{f:7,c:47429},{f:2,c:47437},47440,{f:6,c:47442},{f:2,c:47450},{f:3,c:47453},{f:7,c:47457},47466,47468,{f:6,c:47470},{f:2,c:47478},{f:3,c:47481},{f:7,c:47485}, [...]
+{f:2,c:47499},{f:29,c:47503},{f:2,c:47534},{f:3,c:47537},{f:7,c:47541},47550,47552,{f:6,c:47554},{f:2,c:47562},47565,{f:5,c:47571},47578,47580,{f:2,c:47583},47586,{f:2,c:47590},{f:3,c:47593},{f:7,c:47597},47606,{f:5,c:47611},{f:6,c:47618},{f:12,c:47625},{f:34,c:47638},{f:2,c:47674},{f:3,c:47677},47681,{f:5,c:47683},47690,47692,{f:4,c:47695},{f:2,c:47702},{f:3,c:47705},{f:7,c:47709},47718,{f:6,c:47722},{f:2,c:47730},{f:3,c:47733},{f:10,c:47737},47750,{f:4,c:47752},{f:27,c:47757},47786,{f: [...]
+47793,{f:5,c:47795},47802,47804,{f:6,c:47806},{f:3,c:47813},{f:15,c:47817},{f:34,c:47834},{f:3,c:47869},{f:3,c:47873},{f:8,c:47877},47886,47888,{f:6,c:47890},{f:3,c:47897},{f:3,c:47901},{f:8,c:47905},47914,{f:8,c:47916},47927,{f:2,c:47929},{f:5,c:47935},47942,47944,{f:3,c:47946},47950,{f:3,c:47953},{f:3,c:47957},{f:8,c:47961},47970,{f:8,c:47972},{f:27,c:47981},{f:3,c:48009},{f:3,c:48013},{f:19,c:48017},{f:3,c:48037},{f:3,c:48041},{f:7,c:48045},{f:2,c:48053},{f:8,c:48056},{f:3,c:48065},{f [...]
+{f:7,c:48073},{f:2,c:48081},{f:36,c:48084},{f:2,c:48122},{f:2,c:48125},48129,{f:5,c:48131},48138,48142,48144,{f:2,c:48146},{f:2,c:48153},{f:4,c:48160},48166,48168,{f:3,c:48170},{f:2,c:48174},{f:2,c:48178},{f:3,c:48181},{f:7,c:48185},48194,{f:3,c:48198},{f:2,c:48202},{f:2,c:48206},{f:12,c:48209},{f:38,c:48222},{f:2,c:48262},{f:2,c:48265},48269,{f:5,c:48271},48278,48280,{f:5,c:48283},{f:2,c:48290},{f:2,c:48293},{f:7,c:48297},48306,{f:6,c:48310},{f:2,c:48318},{f:3,c:48321},{f:8,c:48325},483 [...]
+{f:2,c:48342},{f:3,c:48345},{f:23,c:48349},48375,{f:3,c:48377},{f:7,c:48381},48390,48392,{f:6,c:48394},{f:3,c:48401},{f:15,c:48405},{f:7,c:48421},{f:19,c:48429},{f:7,c:48449},{f:2,c:48458},{f:3,c:48461},{f:7,c:48465},{f:10,c:48474},{f:3,c:48485},{f:23,c:48489},{f:2,c:48514},{f:2,c:48517},{f:5,c:48523},48530,48532,{f:3,c:48534},48539,{f:7,c:48541},{f:11,c:48549},{f:7,c:48561},{f:27,c:48569},{f:2,c:48598},{f:3,c:48601},{f:12,c:48605},{f:6,c:48618},{f:3,c:48625},{f:3,c:48629},{f:7,c:48633}, [...]
+48644,{f:6,c:48646},{f:2,c:48654},{f:3,c:48657},{f:7,c:48661},48670,{f:36,c:48672},{f:2,c:48710},{f:3,c:48713},48717,{f:5,c:48719},48726,48728,{f:4,c:48732},{f:2,c:48738},{f:3,c:48741},48745,{f:5,c:48747},48754,{f:5,c:48758},{f:2,c:48766},{f:3,c:48769},{f:7,c:48773},48782,{f:6,c:48786},{f:14,c:48794},{f:39,c:48809},{f:2,c:48850},{f:2,c:48853},{f:7,c:48857},{f:2,c:48865},{f:6,c:48870},{f:20,c:48877},{f:6,c:48898},{f:14,c:48906},48922,{f:34,c:48926},{f:2,c:48962},{f:3,c:48965},{f:7,c:48969 [...]
+{f:62,c:48982},{f:27,c:49045},{f:20,c:49073},{f:6,c:49094},{f:2,c:49102},{f:3,c:49105},{f:7,c:49109},{f:2,c:49117},49120,{f:90,c:49122},{f:20,c:49213},{f:6,c:49234},{f:3,c:49241},{f:3,c:49245},{f:7,c:49249},{f:38,c:49258},{f:2,c:49298},{f:3,c:49301},{f:7,c:49305},49314,49316,{f:6,c:49318},49326,{f:2,c:49329},{f:5,c:49335},49342,{f:3,c:49346},{f:2,c:49350},{f:2,c:49354},{f:3,c:49357},{f:7,c:49361},49370,{f:6,c:49374},{f:2,c:49382},{f:3,c:49385},{f:7,c:49389},49398,49400,{f:6,c:49402},{f:3 [...]
+{f:3,c:49413},{f:7,c:49417},{f:4,c:49425},{f:6,c:49430},{f:2,c:49441},49445,{f:4,c:49448},49454,{f:4,c:49458},49463,{f:2,c:49466},{f:3,c:49469},{f:7,c:49473},49482,{f:6,c:49486},{f:2,c:49494},{f:3,c:49497},{f:7,c:49501},49510,{f:6,c:49514},{f:3,c:49521},{f:3,c:49525},{f:12,c:49529},{f:6,c:49542},49551,{f:3,c:49553},49557,{f:5,c:49559},49566,49568,{f:3,c:49570},{f:2,c:49574},{f:2,c:49578},{f:3,c:49581},{f:12,c:49585},{f:6,c:49598},{f:3,c:49605},{f:3,c:49609},{f:7,c:49613},{f:2,c:49621},{f [...]
+{f:3,c:49633},{f:3,c:49637},{f:7,c:49641},49650,{f:8,c:49652},{f:2,c:49662},{f:3,c:49665},{f:7,c:49669},49678,49680,{f:6,c:49682},{f:2,c:49690},{f:2,c:49693},{f:7,c:49697},49706,49708,49710,49712,49715,{f:19,c:49717},{f:7,c:49737},{f:2,c:49746},{f:3,c:49749},{f:7,c:49753},{f:4,c:49761},{f:6,c:49766},{f:2,c:49774},{f:3,c:49777},{f:7,c:49781},49790,49792,{f:6,c:49794},{f:6,c:49802},{f:7,c:49809},{f:2,c:49817},49820,{f:6,c:49822},{f:2,c:49830},{f:3,c:49833},{f:6,c:49838},49846,49848,{f:34,c [...]
+c:49886},{f:2,c:49889},{f:6,c:49893},49902,49904,{f:4,c:49906},49911,49914,{f:3,c:49917},{f:7,c:49921},{f:2,c:49930},{f:5,c:49934},{f:2,c:49942},{f:3,c:49945},{f:7,c:49949},{f:2,c:49958},{f:27,c:49962},{f:34,c:49990},{f:2,c:50026},{f:3,c:50029},50033,{f:5,c:50035},{f:2,c:50042},{f:6,c:50046},{f:3,c:50053},{f:3,c:50057},{f:51,c:50061},{f:23,c:50113},{f:2,c:50138},{f:2,c:50141},50145,{f:5,c:50147},{f:3,c:50154},{f:6,c:50158},{f:2,c:50166},{f:15,c:50169},{f:7,c:50185},{f:19,c:50193},{f:7,c: [...]
+c:50221},{f:3,c:50225},{f:7,c:50229},{f:10,c:50238},{f:27,c:50249},{f:2,c:50278},{f:3,c:50281},{f:7,c:50285},{f:3,c:50294},{f:6,c:50298},{f:19,c:50305},{f:7,c:50325},{f:27,c:50333},{f:3,c:50361},{f:44,c:50365},{f:6,c:50410},{f:2,c:50418},{f:3,c:50421},50425,{f:4,c:50427},{f:10,c:50434},{f:3,c:50445},{f:3,c:50449},{f:7,c:50453},{f:11,c:50461},{f:2,c:50474},{f:3,c:50477},{f:7,c:50481},50490,50492,{f:6,c:50494},{f:2,c:50502},50507,{f:4,c:50511},50518,{f:3,c:50522},50527,{f:2,c:50530},{f:3,c [...]
+c:50537},50546,{f:6,c:50550},{f:2,c:50558},{f:3,c:50561},{f:2,c:50565},{f:4,c:50568},50574,50576,{f:3,c:50578},50582,{f:3,c:50585},{f:3,c:50589},{f:8,c:50593},{f:10,c:50602},{f:2,c:50614},50618,{f:5,c:50623},50635,50637,50639,{f:2,c:50642},{f:3,c:50645},{f:7,c:50649},50658,50660,{f:6,c:50662},50671,{f:3,c:50673},50677,{f:4,c:50680},{f:3,c:50690},{f:3,c:50697},{f:3,c:50701},{f:7,c:50705},50714,{f:7,c:50717},{f:2,c:50726},{f:3,c:50729},50735,{f:2,c:50737},50742,50744,50746,{f:4,c:50748},{f [...]
+{f:3,c:50757},{f:7,c:50761},50770,{f:6,c:50774},{f:2,c:50782},{f:11,c:50785},{f:2,c:50797},50800,{f:6,c:50802},{f:2,c:50810},{f:3,c:50813},{f:7,c:50817},50826,50828,{f:6,c:50830},{f:2,c:50838},{f:3,c:50841},{f:7,c:50845},50854,50856,{f:6,c:50858},{f:2,c:50866},{f:3,c:50869},{f:5,c:50875},50882,50884,{f:6,c:50886},{f:2,c:50894},{f:3,c:50897},{f:7,c:50901},{f:2,c:50910},{f:6,c:50914},{f:2,c:50922},{f:3,c:50925},{f:7,c:50929},{f:3,c:50938},{f:6,c:50942},{f:2,c:50950},{f:3,c:50953},{f:7,c:50 [...]
+50968,{f:6,c:50970},{f:2,c:50978},{f:3,c:50981},{f:7,c:50985},50994,50996,50998,{f:4,c:51E3},{f:2,c:51006},{f:3,c:51009},{f:5,c:51013},51019,51022,51024,{f:3,c:51033},{f:3,c:51037},{f:7,c:51041},{f:2,c:51049},{f:8,c:51052},{f:2,c:51062},{f:3,c:51065},{f:4,c:51071},51078,{f:3,c:51083},51087,{f:2,c:51090},51093,51097,{f:5,c:51099},51106,{f:5,c:51111},{f:2,c:51118},{f:3,c:51121},{f:7,c:51125},51134,{f:6,c:51138},{f:2,c:51146},51149,51151,{f:7,c:51153},{f:4,c:51161},{f:6,c:51166},{f:3,c:5117 [...]
+{f:19,c:51181},{f:2,c:51202},{f:3,c:51205},51209,{f:5,c:51211},51218,51220,{f:5,c:51223},{f:2,c:51230},{f:3,c:51233},{f:7,c:51237},51246,51248,{f:6,c:51250},{f:3,c:51257},{f:3,c:51261},{f:7,c:51265},{f:2,c:51274},{f:6,c:51278},{f:27,c:51285},{f:2,c:51314},{f:3,c:51317},51321,{f:5,c:51323},51330,51332,{f:3,c:51336},{f:6,c:51342},{f:8,c:51349},51358,51360,{f:6,c:51362},{f:19,c:51369},{f:6,c:51390},{f:3,c:51397},{f:3,c:51401},{f:7,c:51405},51414,51416,{f:6,c:51418},{f:2,c:51426},{f:16,c:514 [...]
+c:51446},{f:2,c:51454},{f:3,c:51457},{f:5,c:51463},51470,51472,{f:6,c:51474},{f:19,c:51481},{f:7,c:51501},{f:27,c:51509},{f:2,c:51538},{f:3,c:51541},{f:7,c:51545},51554,{f:8,c:51556},{f:3,c:51565},{f:3,c:51569},{f:7,c:51573},{f:11,c:51581},{f:2,c:51594},{f:3,c:51597},{f:7,c:51601},51610,51612,{f:34,c:51614},{f:2,c:51650},{f:2,c:51653},51657,{f:5,c:51659},51666,51668,{f:2,c:51671},51675,{f:2,c:51678},51681,51683,{f:2,c:51685},{f:4,c:51688},51694,{f:6,c:51698},{f:2,c:51706},{f:3,c:51709},{ [...]
+51722,{f:6,c:51726},{f:3,c:51733},{f:16,c:51737},{f:34,c:51754},{f:2,c:51790},{f:3,c:51793},{f:7,c:51797},51806,{f:6,c:51810},{f:20,c:51817},{f:6,c:51838},{f:19,c:51845},{f:35,c:51865},{f:2,c:51902},{f:3,c:51905},{f:7,c:51909},51918,51920,51922,{f:4,c:51924},{f:6,c:51930},{f:11,c:51937},{f:7,c:51949},{f:19,c:51957},{f:7,c:51977},{f:3,c:51985},{f:3,c:51989},{f:7,c:51993},{f:31,c:52002},{f:6,c:52034},{f:2,c:52042},{f:3,c:52045},{f:7,c:52049},{f:3,c:52058},{f:6,c:52062},{f:19,c:52069},{f:34 [...]
+{f:27,c:52125},{f:27,c:52153},{f:15,c:52181},{f:2,c:52197},52200,{f:34,c:52202},{f:2,c:52238},{f:3,c:52241},{f:7,c:52245},{f:3,c:52254},{f:4,c:52259},{f:2,c:52266},52269,52271,{f:7,c:52273},52282,{f:5,c:52287},{f:2,c:52294},{f:3,c:52297},{f:7,c:52301},52310,{f:6,c:52314},{f:3,c:52321},52325,52327,{f:7,c:52329},{f:4,c:52337},{f:34,c:52342},{f:2,c:52378},{f:3,c:52381},{f:7,c:52385},52394,{f:6,c:52398},{f:2,c:52406},{f:3,c:52409},{f:7,c:52413},52422,52424,{f:6,c:52426},{f:3,c:52433},{f:15,c [...]
+c:52453},{f:3,c:52461},{f:16,c:52465},{f:6,c:52482},{f:2,c:52490},{f:3,c:52493},{f:7,c:52497},52506,52508,{f:6,c:52510},{f:3,c:52517},{f:3,c:52521},{f:12,c:52525},{f:34,c:52538},{f:3,c:52573},{f:3,c:52577},{f:7,c:52581},52590,52592,{f:6,c:52594},{f:15,c:52601},{f:11,c:52617},{f:2,c:52630},{f:3,c:52633},{f:7,c:52637},52646,52648,{f:6,c:52650},{f:19,c:52657},{f:7,c:52677},{f:3,c:52685},{f:23,c:52689},{f:3,c:52713},{f:3,c:52717},{f:7,c:52721},52730,52732,{f:6,c:52734},{f:3,c:52741},{f:3,c:5 [...]
+c:52749},{f:4,c:52757},{f:6,c:52762},{f:2,c:52770},{f:3,c:52773},{f:7,c:52777},52786,52788,{f:34,c:52790},{f:2,c:52826},{f:2,c:52829},{f:6,c:52834},52842,52844,{f:6,c:52846},{f:2,c:52854},{f:3,c:52857},{f:7,c:52861},52870,52872,{f:6,c:52874},{f:2,c:52882},{f:3,c:52885},{f:7,c:52889},52898,{f:6,c:52902},{f:19,c:52910},{f:34,c:52930},{f:2,c:52966},{f:2,c:52969},{f:7,c:52973},52982,{f:6,c:52986},{f:2,c:52994},{f:3,c:52997},{f:7,c:53001},53010,53012,{f:6,c:53014},{f:3,c:53021},{f:3,c:53025}, [...]
+53038,{f:6,c:53042},{f:27,c:53049},{f:2,c:53078},{f:3,c:53081},{f:7,c:53085},53094,53096,{f:6,c:53098},{f:2,c:53106},{f:3,c:53109},{f:7,c:53113},{f:4,c:53121},{f:6,c:53126},{f:20,c:53133},{f:6,c:53154},{f:7,c:53161},{f:19,c:53169},{f:27,c:53189},{f:2,c:53218},{f:3,c:53221},{f:7,c:53225},53234,53236,{f:6,c:53238},{f:3,c:53245},{f:3,c:53249},{f:12,c:53253},{f:6,c:53266},{f:20,c:53273},{f:6,c:53294},{f:2,c:53302},{f:3,c:53305},{f:7,c:53309},53318,53320,{f:6,c:53322},{f:3,c:53329},{f:3,c:533 [...]
+c:53337},{f:11,c:53345},{f:2,c:53358},{f:3,c:53361},{f:7,c:53365},{f:3,c:53374},{f:34,c:53378},{f:2,c:53414},{f:3,c:53417},{f:7,c:53421},53430,53432,{f:6,c:53434},{f:2,c:53442},{f:3,c:53445},{f:6,c:53450},53458,{f:6,c:53462},{f:2,c:53470},{f:3,c:53473},{f:7,c:53477},53486,{f:6,c:53490},{f:20,c:53497},{f:34,c:53518},{f:2,c:53554},{f:3,c:53557},53561,{f:5,c:53563},53570,{f:6,c:53574},{f:2,c:53582},{f:3,c:53585},{f:7,c:53589},53598,53600,{f:6,c:53602},{f:3,c:53609},{f:15,c:53613},{f:7,c:536 [...]
+c:53637},{f:23,c:53641},{f:2,c:53666},{f:3,c:53669},{f:7,c:53673},53682,53684,{f:4,c:53686},53691,{f:3,c:53693},{f:23,c:53697},{f:27,c:53721},{f:3,c:53749},{f:14,c:53753},53768,{f:6,c:53770},{f:27,c:53777},{f:2,c:53806},{f:3,c:53809},{f:7,c:53813},53822,53824,{f:6,c:53826},{f:19,c:53833},{f:7,c:53853},{f:27,c:53861},{f:2,c:53890},{f:3,c:53893},{f:7,c:53897},{f:3,c:53906},{f:6,c:53910},{f:3,c:53917},{f:3,c:53921},{f:7,c:53925},{f:4,c:53933},{f:6,c:53938},{f:2,c:53946},{f:2,c:53949},53953, [...]
+53962,{f:8,c:53964},{f:3,c:53973},{f:3,c:53977},{f:7,c:53981},{f:10,c:53990},{f:2,c:54002},{f:3,c:54005},{f:7,c:54009},54018,54020,{f:6,c:54022},54031,{f:3,c:54033},54037,{f:5,c:54039},54046,{f:3,c:54050},{f:2,c:54054},{f:2,c:54058},{f:3,c:54061},{f:7,c:54065},54074,{f:6,c:54078},{f:54,c:54086},{f:2,c:54142},{f:3,c:54145},{f:7,c:54149},54158,{f:6,c:54162},{f:2,c:54170},{f:3,c:54173},{f:7,c:54177},54186,54188,{f:6,c:54190},{f:3,c:54197},{f:3,c:54201},{f:7,c:54205},{f:2,c:54214},{f:6,c:542 [...]
+c:54225},{f:8,c:54233},54242,{f:8,c:54244},{f:2,c:54254},{f:3,c:54257},{f:7,c:54261},54270,54272,{f:6,c:54274},{f:20,c:54281},{f:34,c:54302},{f:3,c:54337},{f:23,c:54341},{f:3,c:54365},{f:3,c:54369},{f:8,c:54373},54382,{f:8,c:54384},{f:2,c:54394},{f:2,c:54397},54401,{f:5,c:54403},54410,54412,{f:6,c:54414},{f:20,c:54421},{f:34,c:54442},{f:3,c:54477},{f:3,c:54481},{f:7,c:54485},{f:2,c:54493},{f:8,c:54496},{f:3,c:54505},{f:3,c:54509},{f:7,c:54513},{f:2,c:54521},54524,{f:6,c:54526},{f:3,c:545 [...]
+c:54537},{f:7,c:54541},54550,{f:36,c:54552},{f:2,c:54590},{f:3,c:54593},{f:7,c:54597},54606,54608,{f:6,c:54610},{f:2,c:54618},{f:3,c:54621},{f:4,c:54625},{f:2,c:54630},54634,54636,{f:6,c:54638},{f:2,c:54646},{f:3,c:54649},{f:7,c:54653},54662,{f:6,c:54666},{f:20,c:54673},{f:34,c:54694},{f:2,c:54730},{f:3,c:54733},54737,{f:5,c:54739},54746,54748,{f:6,c:54750},{f:2,c:54758},{f:3,c:54761},{f:7,c:54765},54774,54776,{f:6,c:54778},{f:2,c:54786},{f:3,c:54789},{f:7,c:54793},54802,{f:6,c:54806},{f [...]
+{f:3,c:54817},{f:8,c:54821},{f:10,c:54830},{f:2,c:54842},{f:3,c:54845},{f:4,c:54849},{f:2,c:54854},54858,54860,{f:3,c:54862},{f:2,c:54866},{f:2,c:54870},{f:3,c:54873},{f:10,c:54877},54888,{f:6,c:54890},{f:2,c:54898},{f:14,c:54901},54916,{f:6,c:54918},{f:2,c:54926},{f:3,c:54929},{f:8,c:54933},54942,54944,{f:6,c:54946},{f:3,c:54953},{f:3,c:54957},{f:8,c:54961},54970,{f:8,c:54972},{f:2,c:54982},{f:3,c:54985},{f:4,c:54989},{f:2,c:54994},{f:2,c:54997},55E3,{f:6,c:55002},{f:3,c:55009},{f:3,c:5 [...]
+c:55017},{f:4,c:55025},{f:6,c:55030},{f:2,c:55038},{f:3,c:55041},{f:12,c:55045},{f:6,c:55058},{f:2,c:55066},{f:3,c:55069},{f:7,c:55073},55082,55084,{f:6,c:55086},{f:2,c:55094},{f:3,c:55097},{f:7,c:55101},{f:2,c:55109},55112,{f:6,c:55114},{f:2,c:55122},55125,{f:6,c:55130},55138,55140,{f:3,c:55142},{f:2,c:55146},{f:3,c:55149},{f:3,c:55153},{f:7,c:55157},{f:3,c:55166},{f:6,c:55170},{f:2,c:55178},{f:3,c:55181},{f:7,c:55185},55194,55196,{f:6,c:55198}],"Adobe-CNS1":[{f:95,c:32},{s:3},12288,652 [...]
+65294,8226,65307,65306,65311,65281,65072,8230,8229,65104,65380,65106,183,{f:4,c:65108},65372,8211,65073,8212,{s:4},{f:2,c:65288},{f:2,c:65077},65371,65373,{f:2,c:65079},{f:2,c:12308},{f:2,c:65081},{f:2,c:12304},{f:2,c:65083},{f:2,c:12298},{f:2,c:65085},{f:2,c:12296},{f:2,c:65087},{f:2,c:12300},{f:2,c:65089},{f:2,c:12302},{f:2,c:65091},{f:6,c:65113},{f:2,c:8216},{f:2,c:8220},{f:2,c:12317},8245,8242,65283,65286,65290,8251,167,12291,9675,9679,9651,9650,9678,9734,9733,9671,9670,9633,9632,966 [...]
+8453,8254,0,65343,0,{f:2,c:65097},{f:2,c:65101},{f:2,c:65099},{f:3,c:65119},65291,65293,215,247,177,8730,65308,65310,65309,{f:2,c:8806},8800,8734,8786,8801,{f:5,c:65122},8764,{f:2,c:8745},8869,8736,8735,8895,13266,13265,8747,8750,8757,8756,9792,9794,9793,9737,8593,8595,8594,8592,{f:2,c:8598},8601,8600,8741,8739,0,0,65295,65340,65284,165,12306,{f:2,c:162},65285,65312,8451,8457,{f:3,c:65129},13269,{f:3,c:13212},13262,13217,{f:2,c:13198},13252,176,[20825,58834],[20827,58835],[20830,58837],[ [...]
+20833,20835,21991,[29929,58044],[31950,58191],{f:8,c:9601},9615,9614,9613,9612,9611,9610,9609,9532,9524,9516,9508,9500,9620,9472,9474,9621,9484,9488,9492,9496,{f:2,c:9581},9584,9583,9552,9566,9578,9569,{f:2,c:9698},9701,9700,{f:3,c:9585},{f:10,c:65296},{f:10,c:8544},{f:9,c:12321},0,[21316,57443],0,{f:26,c:65313},{f:26,c:65345},{f:17,c:913},{f:7,c:931},{f:17,c:945},{f:7,c:963},{f:37,c:12549},729,714,711,715,[9312,63153],[9313,63154],[9314,63155],[9315,63156],[9316,63157],[9317,63158],[931 [...]
+[9319,63160],[9320,63161],[9321,63162],[9332,63163],[9333,63164],[9334,63165],[9335,63166],[9336,63167],[9337,63168],[9338,63169],[9339,63170],[9340,63171],[9341,63172],[8560,63173],[8561,63174],[8562,63175],[8563,63176],[8564,63177],[8565,63178],[8566,63179],[8567,63180],[8568,63181],[8569,63182],[12033,20008],[12034,20022,63183],[12035,20031,63184],[12037,20101,63185],[12039,20128,63186],[12044,20866,63187],[12045,20886,63188],[12046,20907,63189],[12051,21241,63190],[12054,21304,63191] [...]
+63192],[12059,21430,63193],[12065,12066,22786,22794,63194],[12071,23424,63195],[12078,24027,63196],[12083,24186,63197],[12084,24191,63198],[12085,24308],[12089,24400,63200],[12090,24417,63201],[12097,25908,63202],[12102,26080],[12135,30098,63204],[12136,30326],[12193,36789,63206],[12202,38582],{f:32,c:9216},9249,[12032,19968],[12036,20057],19969,19971,20035,20061,20102,[12038,20108],[12040,20154],[12041,20799],[12042,20837],[12043,20843],[12047,20960],[12049,20992],20993,[12050,21147],[1 [...]
+[12055,21313],[12056,21340],[12060,21448],19977,19979,19976,19978,20011,20024,20961,20037,20040,20063,20062,20110,20129,[20800,64012],20995,21242,21315,21449,[12061,21475],[12063,22303],[12064,22763],[12067,22805],[12068,22823],[12069,22899],[12070,23376],23377,23379,[12072,23544],[12073,23567],[12074,23586],[12075,23608],[12077,23665],24029,[12079,24037],[12080,24049],{f:2,c:24050},[12081,24062],[12082,24178],[12086,24318],[12087,24331],[12088,24339],25165,19985,19984,19981,20013,20016, [...]
+23609,20104,20113,20117,20114,20116,20130,20161,20160,20163,{f:2,c:20166},20173,{f:2,c:20170},20164,20803,20801,20839,{f:2,c:20845},20844,20887,20982,{f:3,c:20998},21243,{f:2,c:21246},21270,21305,21320,21319,21317,21342,21380,21451,21450,21453,22764,22825,22827,22826,22829,23380,23569,23588,23610,23663,24052,24187,24319,{f:2,c:24340},[12092,24515],[12093,25096],[12094,25142],[12095,25163],25166,[12096,25903],[12098,25991],[12099,26007],[12100,26020],[12101,26041],[12103,26085],[12104,263 [...]
+26376],[12106,26408],[12107,27424],[12108,27490],[12109,27513],[12111,27595],[12112,27604],[12113,27611],[12114,27663],[12116,27700],[12117,28779],[12118,29226],[12119,29238],[12120,29243],[12122,29255],[12123,29273],[12124,29275],[12125,29356],29579,19993,19990,19989,19988,19992,20027,20045,20047,20046,20197,20184,{f:4,c:20180},{f:2,c:20195},20185,20190,20805,20804,{f:2,c:20873},20908,{f:2,c:20985},20984,21002,21152,21151,[21253,57435],21254,21271,21277,20191,21322,21321,21345,21344,213 [...]
+21435,21487,21476,21491,21484,21486,21481,21480,21500,21496,21493,21483,21478,21482,21490,21489,21488,21477,21485,21499,22235,22234,22806,22830,22833,22900,22902,23381,23427,23612,24040,24039,24038,{f:2,c:24066},24179,24188,24321,24344,24343,24517,25098,{f:2,c:25171},25170,25169,26021,26086,26414,26412,{f:2,c:26410},26413,27491,27597,27665,27664,27704,27713,27712,27710,29359,[12126,29572],[12127,29577],[12128,29916],[12129,29926],[12130,29976],[12131,29983],[12132,29992],29993,[12133,3E4 [...]
+[12134,30091],[12137,30333],[12138,30382],[12139,30399],[12140,30446],[12141,30683],[12142,30690],[12143,30707],[12144,31034],[12146,31166],[12147,31348],[12148,31435],{f:2,c:19998},{f:2,c:20050},20073,20121,20132,20134,20133,20223,20233,20249,20234,20245,20237,{f:2,c:20240},20239,20210,20214,20219,20208,20211,20221,20225,20235,20809,20807,20806,20808,20840,20849,20877,20912,21015,{f:2,c:21009},21006,21014,21155,21256,21281,21280,{f:2,c:21360},21513,21519,21516,21514,21520,21505,21515,21 [...]
+21517,21512,21507,21518,21510,21522,22240,22238,22237,22323,22320,22312,22317,22316,22319,22313,{f:2,c:22809},{f:2,c:22839},22916,22904,22915,22909,22905,22914,22913,{f:2,c:23383},{f:2,c:23431},23429,23433,23546,23574,23673,24030,24070,24182,24180,24335,24347,24537,24534,25102,{f:2,c:25100},25104,25187,25179,25176,25910,26089,26088,{f:2,c:26092},{f:2,c:26354},26377,26429,26420,26417,26421,27425,27492,27515,27670,27741,27735,27737,{f:2,c:27743},27728,27733,27745,27739,{f:2,c:27725},28784, [...]
+30334,[12149,31481],[12150,31859],[12151,31992],[12152,32566],[12154,32650],[12155,32701],[12156,32769],32771,[12157,32780],[12158,32786],[12159,32819],[12160,32895],[12161,32905],{f:2,c:32907},[12162,33251],[12163,33258],[12164,33267],[12165,33276],[12166,33292],[12167,33307],[12168,33311],[12169,33390],[12170,33394],33406,[12173,34411],[12174,34880],[12175,34892],[12176,34915],35199,38433,20018,20136,20301,20303,20295,20311,20318,20276,20315,20309,20272,{f:2,c:20304},20285,20282,20280, [...]
+20284,20294,20323,20316,20320,20271,20302,20278,20313,20317,20296,20314,20812,20811,20813,20853,{f:2,c:20918},21029,21028,{f:2,c:21033},21032,21163,{f:2,c:21161},21164,21283,21363,21365,21533,21549,21534,21566,21542,21582,21543,21574,21571,21555,21576,21570,21531,21545,21578,21561,21563,21560,21550,{f:2,c:21557},21536,21564,21568,21553,21547,21535,21548,22250,22256,22244,22251,22346,22353,22336,22349,22343,22350,22334,22352,22351,22331,22767,22846,22941,22930,22952,22942,22947,22937,2293 [...]
+22931,22922,22949,23389,23388,{f:2,c:23386},23436,23435,23439,23596,{f:2,c:23616},23615,23614,{f:2,c:23696},23700,23692,24043,24076,24207,24199,24202,24311,24324,24351,24420,24418,24439,24441,24536,24524,24535,24525,24561,24555,24568,24554,25106,25105,25220,25239,25238,25216,25206,25225,25197,25226,25212,25214,25209,25203,25234,25199,25240,25198,25237,25235,25233,25222,25913,25915,25912,26097,26356,26463,{f:4,c:26446},26460,26454,[26462,57801],26441,26438,26464,26451,26455,27493,27599,27 [...]
+27801,27777,{f:2,c:27784},27781,27803,27754,27770,27792,27760,27788,27752,27798,27794,27773,27779,27762,27774,27764,27782,27766,27789,27796,27800,27778,28790,{f:2,c:28796},28792,29282,29281,29280,29380,29378,29590,29996,29995,{f:2,c:30007},30338,30447,30691,31169,31168,31167,31350,31995,32597,32918,32915,32925,32920,32923,32922,32946,33391,33426,33419,33421,[12178,35211],[12179,35282],[12180,35328],[12181,35895],[12182,35910],[12183,35925],[12185,35997],[12186,36196],[12187,36208],[12188 [...]
+36523],[12190,36554],[12191,36763],[12192,36784],36802,36806,36805,36804,24033,[12194,37009],37026,37034,37030,37027,[12195,37193],[12196,37318],[12197,37324],38450,38446,38449,38442,38444,20006,20054,20083,20107,20123,20126,{f:2,c:20139},20335,20381,20365,20339,20351,20332,20379,20363,20358,20355,20336,20341,20360,20329,20347,20374,20350,20367,20369,20346,20820,20818,20821,20841,20855,20854,20856,20925,20989,21051,21048,21047,21050,21040,21038,21046,21057,21182,21179,21330,21332,21331,2 [...]
+{f:3,c:21367},21462,21460,21463,21619,21621,21654,21624,21653,21632,21627,21623,21636,21650,21638,21628,21648,21617,21622,21644,21658,21602,21608,21643,21629,21646,22266,22403,22391,22378,22377,22369,22374,22372,22396,22812,22857,{f:2,c:22855},22852,22868,22974,22971,22996,22969,22958,22993,22982,22992,22989,22987,22995,22986,22959,22963,22994,22981,23391,23396,23395,23447,23450,23448,23452,23449,23451,23578,23624,{f:2,c:23621},23735,23713,23736,23721,23723,23729,23731,24088,24090,24086, [...]
+24081,24184,24218,24215,24220,{f:2,c:24213},24310,{f:2,c:24358},24361,{f:2,c:24448},24447,24444,24541,24544,24573,24565,24575,24591,24596,24623,24629,24598,24618,24597,24609,24615,24617,24619,24603,25110,25109,25151,25150,25152,25215,25289,25292,25284,25279,25282,25273,25298,25307,25259,{f:2,c:25299},25291,25288,25256,25277,25276,[25296,60582],25305,25287,25293,25269,25306,25265,25304,{f:2,c:25302},25286,25260,[25294,61010],25918,26023,26044,26106,26132,26131,26124,26118,26114,26126,2611 [...]
+26122,26119,26381,26379,26477,26507,26517,26481,26524,26483,26487,26503,26525,26519,{f:2,c:26479},26495,26505,26494,26512,26485,26522,26515,26492,26474,26482,27427,{f:2,c:27494},27519,27667,27675,27875,27880,27891,27825,27852,27877,27827,{f:2,c:27837},27836,27874,27819,27861,27859,27832,27844,27833,27841,27822,27863,27845,27889,27839,27835,27873,27867,27850,27820,27887,27868,27862,27872,28821,28814,28818,28810,28825,{f:2,c:29228},29240,29256,29287,29289,29376,29390,29401,29399,29392,2960 [...]
+29611,29605,30013,30109,{f:2,c:30105},30340,30402,30450,30452,30693,30717,31038,{f:2,c:31040},31177,31176,31354,31353,31482,31998,32596,32652,32651,[32773,58236],32954,32933,32930,32945,32929,32939,32937,32948,32938,32943,33253,33278,33293,33459,33437,33433,33453,33469,33439,33465,33457,33452,33445,33455,33464,33443,33456,33470,33463,34382,34417,21021,34920,36555,36814,36820,36817,37045,37048,37041,37046,37319,[12198,37329],[12199,38263],[12200,38272],[12201,38428],38464,38463,38459,3846 [...]
+38585],[12204,38632],38738,[12206,38750],20127,{f:2,c:20141},20449,20405,20399,20415,20448,20433,20431,20445,20419,20406,20440,20447,20426,20439,20398,20432,20420,20418,20442,20430,20446,20407,20823,20882,20881,20896,21070,21059,21066,21069,21068,21067,21063,21191,21193,21187,21185,21261,21335,21371,21402,21467,21676,21696,21672,21710,21705,21688,21670,21683,21703,21698,21693,21674,21697,21700,21704,21679,21675,21681,21691,21673,21671,21695,22271,22402,22411,22432,22435,22434,22478,22446 [...]
+22865,22863,22862,22864,23004,23E3,23039,23011,23016,23043,23013,23018,23002,23014,23041,23035,23401,23459,23462,23460,23458,23461,23553,{f:2,c:23630},23629,23627,23769,23762,24055,24093,24101,24095,24189,24224,24230,24314,24328,24365,24421,24456,24453,{f:2,c:24458},24455,24460,24457,24594,24605,24608,24613,24590,24616,24653,24688,24680,[24674,60712],24646,24643,24684,24683,24682,24676,25153,25308,25366,25353,25340,25325,25345,25326,25341,25351,25329,25335,25327,25324,25342,25332,25361,2 [...]
+25925,26027,26045,26082,26149,26157,26144,26151,26159,26143,26152,26161,26148,26359,26623,26579,26609,26580,26576,26604,26550,26543,26613,26601,26607,26564,26577,26548,26586,26597,26552,26575,26590,26611,26544,26585,26594,26589,26578,27498,27523,27526,27573,27602,27607,27679,27849,27915,27954,27946,27969,27941,27916,27953,27934,27927,27963,{f:2,c:27965},27958,27931,27893,27961,27943,27960,27945,27950,27957,27918,27947,28843,28858,28851,28844,28847,28845,28856,28846,28836,29232,29298,2929 [...]
+{f:2,c:29408},29623,29642,29627,29618,29645,29632,29619,29978,29997,30031,30028,30030,30027,30123,{f:2,c:30116},{f:2,c:30114},30328,{f:3,c:30342},30408,30406,30403,30405,30465,30457,30456,30473,30475,30462,30460,30471,30684,30722,30740,{f:2,c:30732},31046,31049,31048,31047,{f:2,c:31161},{f:2,c:31185},31179,31359,31361,31487,31485,31869,32002,32005,32E3,32009,32007,32004,32006,32568,32654,32703,32784,32781,32785,32822,32982,32997,32986,{f:2,c:32963},32972,32993,32987,32974,32990,32996,329 [...]
+33314,33511,33539,33541,33507,33499,33510,33540,33509,33538,33545,33490,33495,33521,33537,33500,33492,33489,33502,33491,33503,33519,33542,34384,34425,34427,34426,34893,34923,35201,35284,35336,{f:2,c:35330},35998,36E3,36212,36211,36276,36557,36556,36848,36838,36834,36842,36837,36845,36843,36836,36840,37066,37070,37057,37059,37195,37194,37325,38274,38480,{f:3,c:38475},[12207,38754],[12208,38761],[12209,38859],[12210,38893],[12211,38899],[12212,38913],[12213,39080],[12214,39131],[12215,3913 [...]
+39318],[12217,39321],20056,20147,{f:2,c:20492},20515,20463,20518,20517,20472,[20521,57375],20502,20486,20540,20511,20506,20498,20497,20474,20480,20500,20520,20465,20513,20491,20505,20504,20467,20462,20525,20522,20478,20523,20489,20860,{f:2,c:20900},20898,20941,20940,20934,20939,21078,21084,21076,21083,21085,21290,[21375,57459],21407,21405,21471,21736,21776,21761,21815,21756,21733,21746,21766,21754,21780,21737,21741,21729,21769,21742,21738,21734,21799,21767,21757,21775,{f:2,c:22275},22466 [...]
+22467,22537,22799,{f:2,c:22871},22874,23057,23064,23068,23071,23067,23059,23020,23072,23075,23081,23077,23052,23049,23403,23640,23472,23475,23478,23476,23470,23477,23481,23480,23556,23633,23637,23632,23789,23805,23803,23786,23784,23792,23798,23809,23796,24046,24109,24107,24235,24237,24231,24369,24466,24465,24464,24665,24675,24677,24656,24661,24685,24681,24687,24708,24735,24730,24717,24724,24716,24709,24726,25159,25331,25352,25343,25422,25406,25391,25429,25410,25414,25423,25417,25402,2542 [...]
+c:25386},25384,25421,25420,{f:2,c:25928},26009,26049,26053,26178,26185,26191,26179,26194,26188,26181,26177,26360,{f:2,c:26388},26391,26657,26680,26696,26694,26707,26681,26690,26708,26665,26803,26647,26700,26705,26685,26612,26704,26688,26684,26691,26666,26693,26643,26648,26689,27530,27529,27575,27683,{f:2,c:27687},27686,27684,27888,28010,28053,28040,28039,28006,28024,28023,27993,28051,28012,28041,28014,27994,28020,28009,28044,28042,28025,28037,28005,28052,28874,28888,28900,28889,28872,288 [...]
+29305,29436,29433,29437,29432,29431,29574,29677,29705,29678,29664,29674,29662,30036,30045,30044,30042,30041,30142,30149,30151,{f:2,c:30130},30141,30140,30137,30146,30136,30347,30384,30410,{f:2,c:30413},30505,{f:2,c:30495},30504,30697,30768,30759,30776,30749,30772,30775,30757,30765,30752,30751,30770,31061,31056,31072,31071,31062,31070,31069,31063,31066,31204,[31203,60418],31207,31199,31206,31209,31192,31364,31368,31449,31494,31505,31881,32033,32023,32011,32010,32032,32034,32020,32016,3202 [...]
+32013,32025,32027,32570,32607,32660,32709,32705,32774,32772,32792,32789,32793,32791,32829,32831,33009,33026,33008,33029,33005,33012,33030,33016,33011,33032,33021,33034,33020,33007,33261,33260,33280,33296,{f:2,c:33322},33320,33324,33467,33579,33618,33620,33610,33592,33616,33609,33589,33588,33615,33586,33593,33590,33559,33600,33585,33576,33603,34388,34442,34474,34451,34468,34473,34444,34467,34460,34928,34935,{f:2,c:34945},34941,34937,35352,35344,35342,35340,35349,35338,35351,35347,35350,35 [...]
+35912,35962,35961,{f:2,c:36001},[36215,58442],36524,36562,36564,36559,36785,36865,36870,36855,36864,36858,36852,36867,36861,36869,36856,37013,37089,37085,37090,37202,37197,37196,37336,37341,37335,37340,37337,38275,{f:2,c:38498},38497,38491,38493,38500,38488,38494,38587,39138,[12218,39340],[12219,39592],[12220,39640],[12222,39717],[12224,39730],[12225,39740],20094,20602,[20605,57382],20572,20551,20547,20556,20570,20553,20581,20598,20558,20565,20597,20596,20599,20559,20495,20591,20589,2082 [...]
+21098,21103,21202,21209,21208,21205,21264,21263,21273,{f:2,c:21311},21310,21443,26364,21830,21866,21862,21828,21854,21857,21827,21834,21809,21846,21839,21845,21807,21860,21816,21806,21852,21804,21859,21811,21825,21847,22280,22283,22281,22495,22533,22538,22534,22496,22500,22522,22530,22581,22519,22521,22816,22882,23094,23105,23113,23142,23146,23104,23100,23138,23130,23110,23114,23408,23495,23493,23492,23490,23487,23494,23561,23560,23559,23648,{f:2,c:23644},23815,23814,23822,23835,23830,23 [...]
+23849,23828,23833,23844,23847,23831,24034,24120,24118,24115,24119,{f:2,c:24247},24246,24245,24254,24373,24375,24407,24428,24425,24427,24471,24473,24478,24472,24481,24480,24476,24703,24739,24713,24736,24744,24779,24756,24806,24765,24773,24763,24757,24796,24764,24792,24789,24774,24799,24760,24794,24775,{f:2,c:25114},25160,25504,25511,25458,25494,25506,25509,25463,25447,25496,25514,25457,25513,25481,25475,25499,25451,25512,25476,25480,25497,25505,25516,25490,25487,25472,25467,25449,25448,25 [...]
+25942,25937,25945,25943,21855,25935,25944,25941,25940,26012,26011,26028,26063,{f:2,c:26059},26062,26205,26202,26212,26216,26214,26206,26361,21207,26395,26753,26799,26786,26771,26805,26751,26742,26801,26791,26775,26800,26755,26820,26797,26758,26757,26772,26781,26792,26783,26785,26754,27442,27578,{f:2,c:27627},27691,28046,28092,28147,28121,28082,28129,28108,28132,28155,28154,28165,28103,28107,28079,28113,28078,28126,28153,28088,28151,28149,28101,28114,28186,28085,28122,28139,28120,28138,28 [...]
+28136,28102,28100,28074,28140,28095,28134,28921,{f:2,c:28937},28925,28911,29245,29309,29313,29468,29467,29462,29459,29465,29575,29701,29706,29699,29702,29694,29709,29920,{f:2,c:29942},29980,29986,{f:2,c:30053},30050,30064,30095,{f:2,c:30164},30133,30154,30157,30350,30420,30418,30427,30519,30526,30524,30518,30520,30522,30827,30787,30798,31077,31080,31085,31227,31378,31381,31520,31528,31515,31532,31526,31513,31518,31534,31890,31895,31893,32070,32067,32113,32046,32057,32060,32064,32048,3205 [...]
+32066,32050,32049,32573,32670,32666,32716,32718,32722,32796,32842,32838,33071,33046,33059,33067,33065,33072,33060,33282,33333,33335,33334,33337,33678,33694,33688,33656,33698,33686,33725,33707,33682,33674,33683,33673,33696,33655,{f:2,c:33659},33670,33703,34389,24426,34503,34496,34486,34500,34485,34502,34507,34481,34479,34505,34899,34974,34952,34987,34962,34966,34957,34955,35219,35215,35370,35357,35363,35365,35377,35373,35359,35355,35362,35913,35930,36009,36012,36011,36008,36010,36007,3619 [...]
+36282,36571,36575,36889,36877,36890,36887,36899,36895,36893,36880,36885,36894,36896,36879,36898,36886,36891,36884,37096,37101,[37117,58488],37207,37326,37365,37350,37347,37351,37357,37353,38281,38506,38517,38515,38520,38512,38516,{f:2,c:38518},38508,38592,38634,38633,31456,31455,{f:2,c:38914},[12226,39770],[12227,40165],[12228,40565],[12229,40575],[12230,40613],[12231,40635],20642,20621,20613,20633,20625,20608,20630,20632,20634,26368,20977,21106,{f:2,c:21108},21097,21214,21213,21211,2133 [...]
+21888,21927,21884,21898,21917,21912,21890,21916,21930,21908,21895,21899,21891,21939,21934,21919,21822,21938,21914,21947,21932,21937,21886,21897,21931,21913,22285,22575,22570,22580,22564,{f:2,c:22576},22561,22557,22560,{f:2,c:22777},22880,[23159,57587],23194,23167,23186,23195,23207,23411,23409,23506,23500,23507,23504,{f:2,c:23562},23601,23884,23888,23860,23879,24061,24133,24125,24128,24131,24190,24266,{f:2,c:24257},24260,24380,24429,{f:2,c:24489},24488,24785,24801,24754,24758,24800,24860, [...]
+24853,24816,24827,24820,24936,24817,24846,24822,24841,24832,24850,25119,25161,25507,25484,25551,25536,25577,25545,25542,25549,25554,25571,25552,25569,25558,{f:2,c:25581},25462,25588,25578,25563,25682,25562,25593,25950,25958,{f:2,c:25954},26001,26E3,26031,26222,26224,[26228,57786],26230,26223,26257,26234,26238,26231,{f:2,c:26366},26399,26397,26874,26837,26848,26840,26839,26885,26847,26869,26862,26855,26873,26834,26866,26851,26827,26829,26893,26898,26894,26825,26842,26990,26875,27454,27450 [...]
+27542,27580,27631,{f:2,c:27694},27692,[28207,57904],28216,28244,28193,28210,28263,28234,28192,28197,28195,28187,28251,28248,28196,28246,28270,28205,28198,28271,28212,28237,28218,28204,28227,[28189,57901],28222,28363,28297,28185,28238,28259,28228,28274,28265,28255,{f:2,c:28953},28966,28976,28961,28982,[29038,57958],28956,29260,29316,29312,29494,29477,29492,29481,29754,29738,29747,29730,29733,{f:2,c:29749},29748,29743,29723,29734,29736,{f:2,c:29989},30059,30058,30178,30171,30179,30169,3016 [...]
+{f:2,c:30331},30358,30355,30388,30428,30543,30701,30813,30828,30831,31245,31240,31243,31237,31232,31384,31383,31382,31461,31459,31561,31574,31558,31568,31570,31572,31565,31563,31567,[31569,60510],31903,31909,32094,32080,32104,32085,32043,32110,32114,32097,32102,32098,32112,32115,21892,{f:2,c:32724},32779,32850,32901,33109,33108,33099,33105,33102,33081,33094,33086,33100,33107,33140,33298,33308,33769,33795,33784,33805,33760,33733,33803,[33729,58309],33775,33777,33780,33879,33802,33776,3380 [...]
+33778,33738,33848,33806,33796,33756,33799,33748,33759,34395,34527,34521,34541,34516,34523,34532,34512,34526,34903,{f:2,c:35009},34993,35203,35222,35387,35424,35413,35422,35388,35393,35412,35419,35408,35398,35380,35386,35382,35414,35937,35970,36015,36028,36019,36029,36033,36027,36032,36020,36023,36022,36031,36024,36234,36229,36225,36302,36317,36299,36314,36305,36300,36315,36294,36603,36600,36604,36764,36910,36917,36913,36920,36914,36918,37122,37109,37129,37118,37219,37221,37327,{f:2,c:373 [...]
+37385,37406,37389,37392,37383,37393,38292,38287,38283,38289,38291,38290,38286,38538,38542,38539,38525,{f:2,c:38533},38541,38514,38532,38593,38597,38596,{f:2,c:38598},38639,38642,38860,{f:2,c:38917},38920,39143,39146,39151,39145,39154,39149,39342,39341,[12232,40643],[12233,40653],[12234,40657],20098,20653,20661,{f:2,c:20658},20677,20670,20652,20663,20667,20655,20679,21119,21111,21117,21215,21222,21220,{f:2,c:21218},21295,21983,21992,21971,21990,21966,21980,21959,21969,{f:2,c:21987},21999, [...]
+{f:2,c:21957},21989,21961,{f:2,c:22290},22622,22609,22616,22615,22618,22612,22635,22604,22637,22602,22626,22610,22603,22887,23233,23241,23244,23230,23229,23228,23219,23234,23218,23913,23919,24140,24185,24265,24264,24338,24409,24492,24494,24858,24847,24904,24863,24819,24859,24825,24833,24840,24910,24908,24900,24909,24894,24884,24871,24845,24838,24887,{f:2,c:25121},25619,25662,25630,25642,25645,25661,25644,25615,25628,25620,25613,25654,{f:2,c:25622},25606,25964,26015,26032,26263,26249,{f:2 [...]
+26262,26244,26264,26253,26371,27028,26989,26970,26999,26976,26964,26997,26928,27010,26954,26984,26987,26974,26963,27001,27014,26973,26979,26971,27463,27506,27584,27583,27603,27645,28322,28335,28371,28342,28354,28304,28317,28359,28357,28325,28312,28348,28346,28331,28369,28310,28316,28356,28372,28330,28327,28340,29006,29017,29033,29028,29001,29031,29020,29036,29030,29004,29029,29022,28998,29032,29014,29242,29266,29495,29509,29503,29502,29807,29786,29781,29791,29790,29761,29759,29785,29787, [...]
+30070,30072,30208,30192,30209,30194,30193,30202,30207,30196,30195,{f:2,c:30430},30555,30571,30566,30558,30563,30585,30570,30572,30556,30565,30568,30562,30702,30862,30896,{f:2,c:30871},30860,30857,30844,30865,30867,30847,31098,31103,31105,33836,31165,31260,31258,31264,31252,31263,31262,{f:2,c:31391},31607,31680,31584,31598,31591,31921,31923,31925,32147,32121,32145,32129,32143,32091,32622,{f:2,c:32617},32626,32681,32680,32676,32854,32856,32902,32900,33137,33136,33144,33125,33134,33139,3313 [...]
+33126,33285,33351,33922,33911,33853,33841,33909,33894,33899,33865,33900,33883,33852,33845,33889,33891,33897,33901,33862,34398,34396,34399,34553,34579,34568,34567,34560,34558,34555,{f:2,c:34562},34566,34570,34905,35039,35028,35033,35036,35032,35037,35041,35018,35029,35026,35228,35299,35435,{f:2,c:35442},35430,35433,35440,35463,35452,35427,35488,35441,35461,35437,35426,35438,35436,35449,35451,35390,35432,35938,35978,35977,36042,{f:2,c:36039},36036,36018,36035,36034,36037,36321,36319,36328, [...]
+36346,36330,36324,36326,36530,36611,36617,36606,36618,36767,36786,36939,36938,36947,36930,36948,36924,36949,36944,36935,36943,36942,36941,36945,36926,36929,37138,37143,37228,37226,37225,37321,37431,37463,37432,37437,37440,37438,37467,37451,37476,37457,37428,37449,37453,37445,37433,37439,37466,38296,38552,{f:2,c:38548},38605,38603,{f:2,c:38601},38647,38651,38649,38646,38742,38772,38774,{f:2,c:38928},38931,38922,38930,38924,39164,39156,{f:2,c:39165},39347,39345,39348,39649,40169,40578,[122 [...]
+[12238,40723],[12239,40736],20711,20718,20709,20694,[20717,60903],20698,20693,20687,20689,20721,20686,20713,20834,20979,21123,21122,21297,21421,22014,22016,22043,22039,22013,22036,22022,22025,{f:2,c:22029},22007,22038,22047,22024,22032,22006,22296,22294,22645,22654,22659,22675,22666,22649,22661,22653,22781,22821,22818,22820,22890,22889,23265,23270,23273,23255,23254,23256,23267,23413,23518,23527,23521,{f:2,c:23525},23528,23522,23524,23519,23565,23650,23940,23943,24155,24163,24149,24151,24 [...]
+24278,24330,24390,24432,24505,24903,24895,24907,24951,{f:2,c:24930},24927,24922,24920,24949,25130,25735,25688,25684,25764,25720,25695,25722,25681,25703,25652,25709,25723,25970,26017,26071,26070,26274,26280,26269,27036,27048,27029,27073,27054,27091,27083,27035,27063,27067,27051,27060,27088,27085,27053,27084,27046,27075,27043,27465,27468,27699,28467,28436,28414,28435,28404,28457,28478,28448,28460,28431,28418,28450,28415,28399,28422,28465,28472,28466,28451,28437,28459,28463,28552,28458,2839 [...]
+28364,28407,29076,29081,29053,29066,29060,29074,29246,29330,29334,29508,29520,29796,29795,29802,29808,29805,29956,30097,30247,30221,30219,30217,30227,30433,30435,30596,30589,30591,30561,30913,30879,30887,30899,30889,30883,{f:2,c:31118},31117,31278,31281,31402,31401,31469,31471,31649,31637,31627,31605,31639,31645,31636,31631,[31672,58170],31623,31620,31929,{f:2,c:31933},32187,32176,32156,{f:2,c:32189},32160,32202,32180,32178,32177,32186,32162,32191,32181,32184,32173,[32210,58202],32199,32 [...]
+{f:2,c:32736},32735,32862,32858,32903,33104,33152,33167,33160,33162,33151,33154,33255,33274,33287,33300,33310,33355,33993,33983,33990,33988,33945,33950,33970,33948,33995,33976,33984,34003,33936,33980,34001,33994,34623,34588,34619,34594,34597,34612,34584,34645,34615,34601,35059,35074,35060,35065,35064,35069,35048,35098,35055,35494,35468,35486,35491,35469,35489,35475,35492,35498,35493,35496,35480,35473,35482,35495,35946,35981,35980,36051,{f:2,c:36049},36203,36249,36245,36348,36628,36626,36 [...]
+36771,36960,36952,36956,36963,36953,36958,36962,36957,36955,37145,37144,37150,37237,37240,37239,37236,37496,37548,37504,37509,37528,37526,37499,37523,37532,37544,37500,37521,38305,{f:2,c:38312},38307,38309,38308,38553,38556,38555,38604,38610,38656,38780,38789,38902,{f:2,c:38935},39087,39089,39171,39173,39180,39177,39361,{f:2,c:39599},39654,{f:2,c:39745},40180,40182,40179,40636,[12240,40763],[12241,40778],20740,20736,20731,20725,20729,20738,{f:2,c:20744},20741,20956,{f:3,c:21127},21133,21 [...]
+21426,22062,22075,22073,22066,22079,22068,22057,22099,22094,22103,22132,22070,{f:2,c:22063},22656,22687,22686,22707,22684,22702,22697,22694,22893,23305,23291,23307,23285,23308,23304,23534,23532,23529,23531,{f:2,c:23652},23965,23956,24162,24159,24161,24290,24282,24287,24285,24291,24288,24392,24433,24503,24501,24950,24935,24942,24925,24917,24962,24956,24944,24939,24958,24999,24976,25003,24974,25004,24986,24996,24980,25006,25134,25705,25711,25721,25758,25778,25736,[25744,57745],25776,25765, [...]
+25769,25746,25774,25773,25771,25754,25772,25753,25762,25779,25973,{f:2,c:25975},26286,26283,26292,26289,27171,27167,27112,27137,27166,27161,27133,27169,27155,27146,27123,27138,27141,27117,27153,27472,27470,27556,{f:2,c:27589},28479,28540,28548,28497,28518,28500,28550,28525,28507,28536,28526,28558,28538,28528,28516,28567,28504,28373,28527,28512,28511,29087,29100,29105,29096,29270,29339,29518,29527,29801,29835,29827,29822,29824,30079,30240,30249,30239,30244,30246,{f:2,c:30241},30362,30394, [...]
+30599,30604,30609,30603,30923,30917,30906,30922,30910,30933,30908,30928,31295,31292,31296,31293,31287,31291,31407,31406,31661,31665,31684,31668,{f:2,c:31686},31681,31648,31692,31946,32224,32244,32239,32251,32216,32236,32221,32232,32227,32218,32222,32233,32158,32217,32242,32249,32629,32631,32687,32745,32806,{f:3,c:33179},33184,33178,33176,34071,34109,34074,34030,{f:2,c:34092},34067,34065,34083,34081,34068,34028,34085,34047,34054,34690,34676,34678,34656,34662,34680,34664,34649,34647,34636, [...]
+34909,35088,35079,{f:2,c:35090},35093,35082,35516,35538,35527,35524,35477,35531,35576,35506,35529,35522,35519,35504,35542,35533,35510,35513,35547,35916,35918,35948,36064,36062,36070,36068,{f:2,c:36076},{f:2,c:36066},36060,36074,36065,36205,36255,36259,36395,36368,36381,36386,36367,36393,36383,36385,36382,36538,36637,36635,36639,36649,36646,36650,36636,36638,36645,36969,36974,36968,36973,36983,37168,37165,37159,37169,37255,37257,37259,37251,37573,37563,37559,37610,37604,37569,37555,37564, [...]
+37616,37554,38317,38321,38660,{f:2,c:38662},38665,38752,38797,38795,38799,38945,38955,38940,39091,39178,39187,39186,39192,39389,39376,39391,39387,39377,39381,39378,39385,39607,{f:2,c:39662},39719,39749,39748,39799,39791,40198,40201,40195,40617,40638,40654,22696,[12242,40786],20754,20760,20756,20752,20757,20864,20906,20957,21137,21139,21235,22105,22123,22137,22121,22116,22136,22122,22120,22117,22129,22127,22124,22114,22134,22721,22718,22727,22725,22894,23325,23348,23416,23536,23566,24394, [...]
+25001,24970,25037,25014,25022,25034,25032,25136,25797,25793,25803,{f:2,c:25787},25818,25796,25799,25794,25805,25791,25810,25812,25790,25972,26310,26313,26297,26308,26311,26296,27197,27192,27194,27225,27243,27224,27193,27204,27234,27233,27211,27207,27189,27231,27208,27481,27511,27653,28610,28593,28577,28611,28580,28609,28583,28595,28608,28601,[28598,60318],28582,28576,28596,29118,29129,29136,29138,29128,29141,29113,29134,29145,29148,{f:2,c:29123},29544,29852,29859,29848,29855,29854,29922, [...]
+30260,30264,30266,30439,30437,30624,{f:2,c:30622},30629,30952,30938,30956,30951,31142,{f:2,c:31309},31302,31308,31307,31418,31705,31761,31689,31716,31707,31713,31721,31718,{f:2,c:31957},32266,32273,32264,32283,32291,32286,[32285,58211],32265,32272,32633,32690,{f:2,c:32752},32750,[32808,58239],33203,33193,33192,33275,33288,{f:2,c:33368},34122,34137,34120,{f:2,c:34152},34115,34121,34157,34154,34142,34691,34719,34718,34722,34701,34913,35114,35122,35109,35115,35105,35242,[35238,58391],35558, [...]
+35569,35584,35548,35559,35566,35582,{f:2,c:35585},35575,35565,35571,35574,35580,35947,35949,35987,36084,36420,36401,36404,36418,36409,36405,36667,36655,36664,36659,36776,36774,36981,36980,36984,36978,36988,36986,37172,37266,37664,37686,37624,37683,37679,37666,37628,37675,37636,37658,37648,37670,37665,37653,37678,37657,38331,{f:2,c:38567},38570,38613,38670,38673,38678,38669,38675,38671,38747,[38748,58565],38758,38808,38960,38968,38971,38967,38957,38969,38948,39184,39208,39198,39195,39201, [...]
+39394,39409,39608,39612,39675,39661,39720,39825,40213,40227,40230,40232,40210,40219,40664,40660,[12243,40845],[12244,40860],20778,20767,20769,20786,21237,22158,22144,22160,22149,22151,22159,22741,22739,22737,22734,23344,23338,23332,23418,23607,23656,23996,23994,23997,23992,24171,24396,24509,25033,25026,25031,25062,25035,25138,25140,25806,25802,25816,25824,25840,25830,25836,25841,25826,25837,{f:2,c:25986},26329,26326,27264,27284,27268,27298,27292,27355,27299,27262,27287,27280,27296,27484, [...]
+27656,28632,28657,{f:2,c:28639},28635,28644,28651,28655,28544,28652,28641,28649,28629,28654,28656,29159,[29151,60361],29166,29158,29157,29165,29164,29172,29152,29237,29254,29552,29554,29865,29872,29862,29864,30278,30274,30284,30442,30643,30634,30640,30636,30631,30637,30703,30967,30970,30964,30959,30977,31143,31146,31319,31423,31751,31757,31742,31735,31756,31712,31968,31964,31966,31970,31967,31961,31965,32302,32318,32326,32311,32306,32323,32299,32317,32305,32325,32321,32308,32313,32328,32 [...]
+32303,32580,32755,32764,{f:2,c:32881},32880,32879,32883,33222,33219,33210,33218,33216,33215,33213,33225,33214,33256,33289,33393,34218,34180,34174,34204,34193,34196,34223,34203,34183,34216,34186,34214,34407,34752,34769,34739,34770,34758,34731,34747,34746,34760,34763,35131,35126,35140,35128,35133,35244,35598,35607,35609,35611,35594,35616,35613,35588,35600,35905,35903,35955,36090,36093,36092,36088,36091,36264,36425,36427,36424,36426,36676,36670,36674,36677,36671,36991,36989,36996,{f:2,c:369 [...]
+37177,37283,37278,37276,37709,37762,37672,37749,37706,37733,37707,37656,37758,37740,37723,37744,37722,37716,{f:3,c:38346},38344,38342,38577,38584,38614,38684,38686,38816,38867,38982,39094,39221,39425,39423,39854,39851,39850,39853,40251,40255,40587,40655,40670,{f:2,c:40668},40667,40766,40779,21474,22165,22190,22745,22744,23352,24413,25059,25139,25844,25842,25854,25862,{f:2,c:25850},25847,26039,26332,26406,27315,27308,27331,27323,27320,27330,{f:2,c:27310},27487,27512,27567,28681,28683,2867 [...]
+28689,28687,{f:2,c:29179},29182,29176,29559,29557,29863,29887,29973,30294,30296,30290,30653,30655,{f:2,c:30651},30990,31150,{f:2,c:31329},31328,{f:2,c:31428},31787,31783,31786,31774,31779,31777,31975,{f:2,c:32340},32350,32346,32353,32338,32345,32584,32761,32763,32887,32886,33229,33231,33290,34255,34217,34253,34256,34249,34224,34234,34233,34799,34796,34802,34784,35206,35250,35316,35624,35641,35628,35627,35920,36101,36441,36451,36454,36452,36447,36437,36544,36681,36685,36999,36995,37E3,{f: [...]
+37328,37780,37770,37782,37794,37811,37806,37804,37808,37784,37786,37783,38356,38358,38352,38357,38626,38620,38617,38619,38622,38692,38819,38822,38829,38905,38989,38991,38988,38990,38995,39098,{f:2,c:39230},39229,39214,39333,39438,39617,39683,39686,39759,39758,39757,39882,39881,39933,39880,39872,40273,40285,40288,40672,40725,40748,20787,22181,22184,{f:2,c:22750},22754,23541,40848,24300,25074,25079,25078,25077,25856,25871,26336,26333,27365,27357,27354,27347,28699,28703,28712,28698,28701,28 [...]
+29190,29197,29272,29346,29560,29562,29885,29898,29923,30087,30086,30303,30305,30663,31001,31153,31339,31337,{f:2,c:31806},31800,31805,31799,31808,32363,32365,32377,{f:2,c:32361},32371,32645,32694,32697,32696,33240,34281,34269,34282,34261,{f:2,c:34276},34295,34811,34821,34829,34809,34814,35168,35167,35158,35166,35649,35676,35672,35657,35674,{f:2,c:35662},35654,35673,36104,36106,36476,36466,36487,36470,36460,36474,36468,36692,36686,36781,{f:2,c:37002},37297,37294,37857,37841,37855,37827,37 [...]
+c:37852},37846,37858,37837,37848,37860,37847,37864,38364,38580,38627,38698,38695,38753,38876,38907,39006,39E3,39003,39100,39237,39241,39446,39449,39693,39912,39911,39894,39899,40329,40289,40306,40298,40300,40594,40599,40595,40628,21240,22199,22198,22196,22204,22756,23360,23363,23421,23542,24009,25080,25082,25880,25876,25881,26342,26407,27372,28734,28720,28722,29200,29563,29903,30306,30309,31014,31018,31020,31019,31431,31478,31820,31811,31821,{f:2,c:31983},36782,32381,32380,32386,32588,32 [...]
+33382,34299,34297,34321,34298,34310,34315,34311,34314,{f:2,c:34836},35172,35258,35320,35696,35692,35686,35695,35679,35691,36111,36109,36489,36481,36485,36482,37300,37323,37912,37891,37885,38369,38704,39108,39250,39249,39336,39467,39472,39479,39477,39955,39949,40569,40629,40680,40751,40799,40803,40801,{f:2,c:20791},22209,22208,22210,22804,23660,24013,25084,25086,25885,25884,26005,26345,27387,27396,27386,27570,28748,29211,29351,29910,29908,30313,30675,31824,32399,32396,32700,34327,34349,34 [...]
+34850,34849,34847,35178,35180,35261,35700,35703,35709,36115,36490,36493,36491,36703,36783,37306,37934,37939,37941,37946,37944,37938,37931,38370,{f:2,c:38712},38706,[38911,58586],39015,39013,39255,39493,39491,39488,39486,39631,39764,39761,39981,39973,40367,40372,40386,40376,40605,40687,40729,40796,{f:2,c:40806},20796,20795,22216,22218,22217,23423,24020,24018,24398,25087,25892,27402,27489,28753,28760,29568,29924,30090,30318,30316,31155,31840,31839,32894,32893,33247,35186,35183,35324,35712, [...]
+36497,36499,36705,37192,37956,{f:2,c:37969},{f:2,c:38717},38851,38849,39019,39253,39509,39501,39634,39706,40009,39985,39998,39995,40403,40407,40756,40812,40810,40852,22220,24022,25088,25891,25899,25898,26348,27408,29914,31434,31844,31843,31845,32403,32406,32404,33250,34360,34367,34865,35722,37008,37007,37987,37984,37988,38760,39023,39260,{f:2,c:39514},39511,{f:2,c:39635},39633,40020,40023,40022,40421,40607,40692,22225,22761,25900,28766,{f:2,c:30321},[30679,60226],32592,32648,34870,34873, [...]
+35730,35734,33399,36123,37312,37994,38722,38728,38724,38854,39024,39519,39714,39768,40031,{f:2,c:40441},{f:2,c:40572},40711,40823,40818,24307,27414,28771,31852,31854,34875,35264,36513,37313,38002,38E3,39025,39262,39638,39715,40652,28772,30682,35738,38007,38857,39522,39525,32412,35740,36522,37317,{f:2,c:38013},38012,{f:2,c:40055},40695,35924,38015,40474,29224,39530,39729,40475,40478,31858,20034,20060,[12048,20981],[12053,21274],[12058,21378],19975,19980,20039,20109,[12062,22231],[12076,23 [...]
+24435],19983,20871,19982,20014,20115,20162,20169,20168,20888,21244,21356,21433,22304,22787,22828,[23568,60417],24063,26081,[12110,27571],27596,[12115,27668],[12121,29247],20017,20028,20200,20188,20201,20193,20189,20186,21004,21001,21276,21324,{f:2,c:22306},22807,22831,23425,23428,23570,23611,23668,23667,24068,24192,24194,24521,25097,25168,27669,27702,27715,27711,27707,29358,29360,29578,[12145,31160],32906,38430,20238,20248,20268,20213,20244,20209,20224,20215,20232,20253,20226,20229,20258 [...]
+20212,20242,20913,21011,21008,21158,21282,21279,21325,21386,21511,22241,22239,22318,22314,22324,22844,22912,22908,22917,22907,22910,22903,22911,23382,23573,23589,23676,{f:2,c:23674},23678,24031,[24181,57646],24196,24322,24346,24436,24533,24532,24527,25180,25182,25188,25185,25190,25186,25177,25184,25178,25189,25911,26095,26094,26430,26425,26424,26427,26426,26431,26428,26419,27672,27718,27730,27740,27727,[27722,60796],27732,{f:2,c:27723},28785,29278,{f:2,c:29364},29582,29994,30335,31349,[1 [...]
+[12171,33400],33404,33408,33405,33407,[12172,34381],[12177,35198],37017,[37015,59347],37016,37019,37012,38434,38436,38432,38435,20310,20283,20322,20297,20307,20324,20286,20327,20306,20319,20289,20312,20269,20275,20287,20321,20879,20921,21020,21022,21025,{f:2,c:21165},21257,21347,21362,{f:2,c:21390},21552,21559,21546,21588,21573,21529,21532,21541,21528,21565,21583,21569,21544,21540,21575,22254,22247,22245,22337,22341,22348,22345,22347,22354,22790,22848,22950,22936,22944,22935,22926,22946, [...]
+22951,22945,23438,23442,23592,23594,23693,23695,23688,23691,23689,23698,23690,23686,23699,23701,24032,24074,24078,24203,24201,24204,24200,24205,24325,24349,24440,24438,24530,24529,24528,24557,24552,24558,24563,24545,24548,24547,24570,24559,24567,24571,24576,24564,25146,25219,25228,{f:2,c:25230},25236,25223,25201,25211,25210,25200,25217,25224,25207,25213,25202,25204,26096,26100,26099,26098,26101,26437,26439,26457,26453,26444,26440,26461,26445,26458,26443,27600,{f:2,c:27673},27768,27751,27 [...]
+27787,27791,27761,27759,27753,27802,27757,27783,27797,[27804,57900],27750,27763,27749,27771,27790,28788,28794,29283,29375,29373,29379,29382,29377,29370,29381,29589,29591,{f:2,c:29587},29586,30010,30009,{f:2,c:30100},30337,31037,32820,32917,32921,32912,32914,32924,33424,33423,33413,33422,33425,33427,33418,{f:2,c:33411},[12184,35960],36809,36799,37023,37025,37029,37022,37031,37024,38448,38440,38447,38445,20019,20376,20348,20357,20349,20352,20359,20342,20340,20361,20356,20343,20300,20375,20 [...]
+20345,20353,20344,20368,20380,20372,20382,20370,20354,20373,20331,20334,20894,20924,20926,21045,{f:2,c:21042},21062,21041,21180,{f:2,c:21258},21308,21394,21396,21639,21631,21633,21649,21634,21640,21611,21626,21630,21605,21612,21620,21606,21645,21615,21601,21600,21656,21603,21607,21604,22263,22265,22383,22386,22381,22379,22385,22384,22390,22400,22389,22395,{f:2,c:22387},22370,22376,22397,22796,22853,22965,22970,22991,22990,22962,22988,22977,22966,22972,22979,22998,22961,22973,22976,22984, [...]
+23394,23397,23443,23445,23620,23623,23726,23716,23712,23733,23727,23720,23724,23711,23715,23725,23714,23722,23719,23709,23717,23734,23728,23718,24087,24084,24089,24360,{f:3,c:24354},24404,24450,24446,24445,24542,24549,24621,24614,24601,24626,24587,24628,24586,24599,24627,24602,24606,24620,24610,24589,24592,24622,24595,24593,24588,24585,24604,25108,25149,25261,25268,25297,25278,25258,25270,25290,25262,25267,25263,25275,25257,25264,25272,25917,26024,26043,26121,26108,26116,26130,26120,2610 [...]
+26125,26117,26109,26129,26128,26358,26378,26501,26476,26510,26514,26486,26491,26520,26502,26500,26484,26509,26508,26490,26527,26513,26521,26499,26493,26497,{f:2,c:26488},26516,27429,27520,27518,27614,27677,27795,27884,27883,27886,27865,27830,27860,27821,27879,27831,27856,27842,27834,27843,27846,27885,27890,27858,27869,27828,27786,27805,27776,27870,27840,27952,27853,27847,27824,27897,27855,27881,27857,28820,28824,28805,28819,28806,28804,28817,28822,28802,28826,28803,29290,29398,29387,2940 [...]
+29394,29396,29402,29388,29393,29604,29601,29613,29606,29602,29600,29612,29597,29917,29928,{f:2,c:30015},30014,30092,30104,30383,30451,30449,30448,30453,30712,30716,30713,30715,30714,30711,31042,31039,31173,31352,31355,31483,31861,31997,32821,32911,32942,32931,32952,32949,32941,33312,33440,33472,33451,33434,33432,33435,33461,33447,33454,33468,33438,33466,33460,33448,33441,33449,33474,33444,33475,33462,33442,34416,34415,{f:2,c:34413},35926,36818,36811,36819,36813,36822,36821,36823,37042,37 [...]
+37043,37040,38457,38461,38460,38458,38467,20429,20421,20435,20402,20425,20427,20417,20436,20444,20441,[20411,60346],20403,20443,20423,20438,20410,20416,20409,20460,21060,21065,21184,21186,21309,21372,21399,21398,21401,21400,21690,21665,21677,21669,21711,21699,33549,21687,21678,21718,21686,{f:2,c:21701},21664,21616,21692,21666,21694,21618,21726,21680,22453,{f:2,c:22430},22436,22412,22423,22429,22427,22420,22424,22415,22425,22437,22426,22421,22772,22797,22867,23009,23006,23022,23040,23025, [...]
+23037,23036,23030,23012,23026,23031,23003,23017,23027,23029,23008,23038,23028,23021,23464,23628,23760,23768,23756,23767,23755,23771,23774,23770,23753,23751,23754,23766,{f:2,c:23763},23759,23752,23750,23758,23775,23800,24057,{f:3,c:24097},24096,24100,24240,24228,24226,24219,24227,24229,24327,24366,24406,24454,24631,24633,24660,24690,24670,24645,24659,24647,24649,24667,24652,24640,24642,24671,24612,24644,24664,24678,24686,{f:2,c:25154},25295,25357,25355,25333,25358,25347,25323,25337,25359, [...]
+25334,25344,{f:2,c:25363},25338,25365,25339,25328,25921,25923,26026,26047,26166,26145,26162,26165,26140,26150,26146,26163,26155,26170,26141,26164,26169,26158,{f:2,c:26383},26561,26610,26568,26554,26588,26555,26616,26584,26560,26551,26565,26603,26596,26591,26549,26573,26547,26615,26614,26606,26595,26562,26553,26574,26599,26608,26546,26620,26566,26605,26572,26542,26598,26587,26618,{f:2,c:26569},26563,26602,26571,27432,27522,27524,27574,27606,27608,27616,{f:2,c:27680},27944,27956,27949,2793 [...]
+27922,27914,27866,27955,27908,27929,27962,27930,27921,27904,27933,27970,27905,27928,27959,27907,27919,27968,27911,27936,27948,27912,27938,27913,27920,28855,28831,28862,28849,28848,28833,{f:2,c:28852},28841,29249,{f:2,c:29257},29292,29296,29299,29294,29386,29412,29416,29419,29407,29418,29414,29411,29573,29644,29634,29640,29637,29625,29622,29621,29620,29675,29631,29639,29630,29635,29638,29624,29643,29932,29934,29998,{f:2,c:30023},30119,30122,30329,30404,30472,{f:3,c:30467},30474,30455,3045 [...]
+c:30695},30726,{f:2,c:30737},30725,30736,30735,30734,[30729,58095],30723,30739,31050,31052,31051,31045,31044,31189,31181,31183,31190,31182,31360,31358,31441,{f:2,c:31488},31866,{f:2,c:31864},{f:3,c:31871},32003,32008,32001,32600,32657,32653,32702,32775,{f:2,c:32782},32788,32823,32984,32967,32992,32977,32968,32962,32976,32965,32995,32985,32988,32970,32981,32969,32975,32983,32998,32973,33279,33313,33428,33497,33534,33529,33543,33512,33536,33493,33594,33515,33494,33524,33516,33505,33522,335 [...]
+33531,33526,33520,33514,33508,33504,33530,33523,33517,34423,34420,34428,34419,34881,34894,34919,34922,34921,35283,35332,35335,36210,36835,36833,36846,36832,37105,37053,37055,37077,37061,37054,37063,37067,37064,[37332,60294],37331,38484,38479,38481,38483,38474,38478,20510,20485,20487,20499,20514,20528,20507,20469,20468,20531,20535,20524,{f:2,c:20470},20503,20508,20512,20519,20533,20527,20529,20494,20826,20884,20883,20938,{f:2,c:20932},20936,20942,21089,21082,21074,{f:2,c:21086},21077,2109 [...]
+21406,21798,21730,21783,21778,21735,21747,21732,21786,21759,21764,21768,21739,21777,21765,21745,21770,21755,{f:2,c:21751},21728,21774,21763,21771,{f:2,c:22273},22476,22578,22485,22482,22458,22470,22461,22460,22456,22454,22463,22471,22480,22457,22465,22798,22858,23065,23062,{f:2,c:23085},23061,23055,23063,23050,23070,23091,23404,23463,23469,23468,23555,23638,23636,23788,23807,23790,23793,23799,23808,23801,24105,24104,24232,24238,24234,24236,24371,24368,24423,24669,24666,24679,24641,24738, [...]
+24722,24705,24733,24707,24725,24731,24727,24711,24732,24718,25113,25158,25330,25360,25430,25388,{f:2,c:25412},25398,25411,25572,25401,25419,25418,25404,25385,25409,25396,25432,25428,25433,25389,25415,25395,25434,25425,25400,25431,25408,25416,25930,25926,26054,{f:2,c:26051},26050,26186,26207,26183,26193,{f:2,c:26386},26655,26650,26697,{f:2,c:26674},26683,26699,26703,26646,26673,26652,26677,26667,26669,26671,26702,26692,26676,26653,26642,26644,26662,26664,26670,26701,26682,26661,26656,2743 [...]
+27441,27444,27501,32898,27528,27622,27620,27624,27619,27618,27623,27685,28026,{f:2,c:28003},28022,27917,28001,28050,27992,28002,28013,28015,28049,28045,28143,28031,28038,27998,[28007,59078],28E3,28055,28016,28028,27999,28034,28056,27951,28008,28043,28030,28032,28036,27926,28035,28027,28029,28021,28048,28892,28883,28881,28893,28875,32569,28898,28887,28882,28894,28896,28884,28877,{f:3,c:28869},28890,28878,28897,29250,29304,29303,29302,29440,29434,29428,29438,29430,29427,29435,29441,29651,2 [...]
+29654,29628,29671,29667,29673,29660,29650,29659,29652,29661,29658,{f:2,c:29655},29672,{f:2,c:29918},{f:2,c:29940},29985,30043,30047,30128,30145,30139,30148,30144,30143,30134,30138,30346,30409,30493,30491,30480,30483,30482,30499,30481,30485,{f:2,c:30489},30498,30503,30755,30764,30754,30773,30767,30760,30766,30763,30753,30761,30771,30762,30769,31060,31067,31055,31068,31059,31058,31057,{f:2,c:31211},31200,31214,31213,31210,31196,31198,31197,31366,31369,31365,{f:2,c:31371},31370,31367,31448, [...]
+31507,31493,31503,31496,31498,31502,31497,31506,31876,31889,31882,31884,31880,31885,31877,32030,32029,32017,32014,32024,32022,32019,32031,32018,32015,32012,32604,32609,32606,32608,32605,32603,32662,32658,32707,32706,32704,32790,32830,32825,33018,33010,33017,33013,33025,33019,33024,33281,33327,33317,33587,33581,33604,33561,33617,33573,33622,33599,33601,33574,33564,33570,33602,33614,33563,33578,33544,33596,33613,33558,33572,33568,33591,33583,33577,33607,33605,33612,33619,33566,33580,33611, [...]
+34387,34386,34466,34472,34454,34445,34449,34462,34439,34455,34438,34443,34458,34437,34469,34457,34465,34471,34453,34456,34446,34461,34448,34452,{f:2,c:34883},34925,{f:2,c:34933},34930,34944,34929,34943,34927,34947,34942,34932,34940,35346,35911,35927,35963,36004,36003,36214,36216,36277,36279,36278,36561,36563,36862,36853,36866,36863,36859,36868,36860,36854,37078,37088,{f:2,c:37081},37091,37087,37093,37080,37083,37079,37084,37092,37200,{f:2,c:37198},37333,37346,37338,38492,38495,38588,3913 [...]
+39647],[12223,39727],20095,20592,20586,20577,20574,20576,20563,20555,20573,20594,20552,20557,20545,20571,20554,20578,20501,20549,20575,20585,20587,{f:2,c:20579},20550,20544,20590,20595,20567,20561,20944,21099,21101,21100,21102,21206,21203,21293,21404,{f:2,c:21877},21820,21837,21840,21812,21802,21841,21858,21814,21813,21808,21842,21829,21772,21810,21861,21838,21817,21832,21805,21819,21824,21835,22282,22279,22523,22548,22498,22518,22492,22516,22528,22509,22525,22536,22520,22539,22515,22479 [...]
+22499,22514,22501,22508,22497,22542,22524,22544,22503,22529,22540,22513,22505,22512,22541,22532,22876,23136,23128,23125,[23143,60437],23134,23096,23093,23149,23120,23135,23141,23148,23123,23140,23127,23107,23133,23122,23108,23131,23112,23182,23102,23117,23097,23116,23152,23145,23111,23121,23126,23106,23132,23410,23406,23489,23488,23641,23838,23819,23837,23834,23840,23820,23848,23821,23846,23845,23823,23856,23826,23843,23839,23854,24126,24116,24241,24244,24249,{f:2,c:24242},24374,24376,24 [...]
+24479,24714,24720,24710,24766,24752,24762,{f:2,c:24787},24783,24804,24793,24797,24776,24753,24795,24759,24778,24767,24771,24781,24768,25394,25445,25482,25474,25469,25533,25502,25517,25501,25495,25515,25486,25455,25479,25488,25454,25519,25461,25500,25453,25518,25468,25508,25403,25503,25464,25477,25473,25489,25485,25456,25939,26061,26213,26209,26203,26201,26204,26210,26392,26745,26759,26768,26780,{f:2,c:26733},26798,26795,26966,26735,26787,26796,26793,26741,26740,26802,26767,26743,26770,26 [...]
+26738,26794,26752,26737,26750,26779,26774,26763,26784,26761,26788,26744,26747,26769,26764,26762,26749,27446,27443,{f:2,c:27447},27537,27535,{f:2,c:27533},27532,27690,28096,28075,28084,28083,28276,28076,28137,28130,28087,28150,28116,28160,28104,28128,28127,28118,28094,28133,{f:2,c:28124},28123,28148,28106,28093,28141,28144,28090,28117,28098,28111,28105,28112,28146,28115,28157,28119,28109,28131,28091,28922,28941,28919,28951,28916,28940,28912,28932,28915,28944,28924,28927,28934,28947,28928, [...]
+28939,28930,28942,29310,{f:2,c:29307},29311,29469,29463,29447,29457,29464,29450,29448,29439,29455,29470,29576,29686,29688,29685,29700,29697,29693,29703,29696,29690,29692,29695,29708,29707,29684,29704,30052,30051,30158,30162,30159,{f:2,c:30155},30161,30160,30351,30345,30419,30521,30511,30509,{f:2,c:30513},30516,30515,30525,30501,30523,30517,30792,30802,30793,30797,30794,30796,30758,30789,30800,31076,31079,{f:2,c:31081},31075,31083,31073,31163,31226,31224,{f:2,c:31222},31375,31380,31376,31 [...]
+31540,31525,31536,31522,31524,31539,31512,31530,31517,31537,31531,31533,31535,31538,31544,31514,31523,31892,31896,31894,31907,32053,32061,32056,32054,32058,32069,32044,32041,32065,32071,{f:2,c:32062},32074,32059,32040,32611,32661,{f:2,c:32668},32667,{f:2,c:32714},32717,{f:2,c:32720},32711,32719,32713,32799,32798,32795,32839,32835,32840,33048,33061,33049,33051,33069,33055,33068,33054,33057,33045,33063,33053,33058,33297,33336,33331,33338,33332,33330,33396,33680,33699,33704,33677,33658,3365 [...]
+33679,33665,33685,33689,33653,33684,33705,33661,33667,33676,33693,33691,33706,33675,33662,33701,33711,33672,33687,33712,33663,33702,33671,33710,33654,34393,34390,34495,34487,34498,34497,34501,34490,34480,34504,34489,34483,34488,34508,34484,{f:2,c:34491},34499,{f:2,c:34493},34898,34953,34965,34984,34978,34986,34970,34961,34977,34975,34968,34983,34969,34971,34967,34980,34988,34956,34963,34958,35202,35286,35289,35285,35376,35367,35372,35358,35897,35899,{f:2,c:35932},35965,36005,36221,36219, [...]
+36290,36281,36287,36289,36568,36574,36573,36572,36567,{f:2,c:36576},36900,36875,36881,36892,36876,36897,37103,37098,37104,37108,{f:2,c:37106},37076,{f:2,c:37099},37097,37206,37208,37210,37203,37205,37356,37364,37361,37363,37368,37348,37369,{f:2,c:37354},37367,37352,37358,38266,38278,38280,38524,38509,38507,38513,38511,38591,38762,38916,39141,39319,20635,20629,20628,20638,20619,20643,20611,20620,20622,20637,20584,20636,20626,20610,20615,20831,20948,21266,21265,21412,21415,21905,21928,2192 [...]
+22085,21922,21907,21896,21903,21941,21889,21923,21906,21924,21885,21900,21926,21887,21909,21921,21902,22284,22569,22583,22553,22558,22567,22563,22568,22517,22600,22565,22556,22555,22579,22591,22582,22574,22585,22584,22573,22572,22587,22881,23215,23188,23199,23162,23202,23198,23160,23206,23164,23205,23212,23189,23214,23095,23172,23178,23191,23171,23179,23209,23163,23165,23180,23196,23183,23187,23197,23530,23501,23499,23508,23505,23498,23502,23564,23600,23863,23875,23915,23873,23883,23871, [...]
+23886,23893,23859,23866,23890,23869,23857,23897,23874,23865,23881,23864,23868,23858,23862,23872,23877,24132,24129,[24408,57673],24486,24485,24491,24777,24761,24780,24802,24782,24772,24852,24818,24842,24854,24837,24821,24851,24824,24828,24830,24769,24835,24856,24861,24848,24831,24836,24843,25162,25492,25521,25520,25550,25573,25576,25583,25539,25757,25587,25546,25568,25590,25557,25586,25589,25697,25567,25534,25565,25564,25540,25560,25555,25538,25543,25548,25547,25544,25584,25559,25561,2590 [...]
+25956,25948,25960,25957,25996,{f:2,c:26013},26030,26064,26066,26236,26220,26235,26240,26225,26233,26218,26226,26369,26892,26835,26884,26844,26922,26860,26858,26865,26895,26838,26871,26859,26852,26870,26899,26896,26867,26849,26887,26828,26888,26992,26804,26897,26863,26822,26900,26872,26832,26877,26876,26856,26891,26890,26903,26830,26824,{f:2,c:26845},26854,26868,26833,26886,26836,26857,26901,26917,26823,27449,27451,27455,27452,27540,27543,27545,27541,27581,27632,{f:2,c:27634},27696,28156, [...]
+28191,28233,28296,{f:2,c:28220},28229,28258,28203,28223,28225,28253,28275,28188,28211,28235,28224,28241,28219,28163,28206,28254,28264,28252,28257,28209,28200,28256,28273,28267,28217,28194,28208,28243,28261,28199,28280,28260,28279,28245,28281,28242,28262,{f:2,c:28213},28250,28960,28958,28975,28923,28974,28977,28963,28965,28962,28978,28959,28968,28986,28955,29259,29274,{f:2,c:29320},29318,29317,29323,29458,29451,29488,29474,29489,29491,29479,29490,29485,29478,29475,29493,29452,29742,29740, [...]
+29718,29722,29729,29741,29745,29732,29731,29725,29737,29728,29746,29947,29999,30063,30060,30183,30170,30177,30182,30173,30175,30180,30167,30357,30354,30426,{f:2,c:30534},30532,30541,30533,30538,30542,{f:2,c:30539},30686,30700,30816,{f:2,c:30820},30812,30829,30833,30826,30830,30832,30825,30824,30814,30818,31092,31091,31090,31088,31234,31242,31235,31244,31236,31385,31462,31460,31562,31559,31556,31560,31564,31566,31552,31576,31557,31906,31902,31912,31905,32088,32111,32099,32083,32086,32103, [...]
+32109,32092,32107,32082,32084,32105,32081,32095,32078,{f:2,c:32574},{f:2,c:32613},32674,{f:2,c:32672},32727,32849,{f:2,c:32847},33022,32980,33091,33098,33106,33103,33095,33085,33101,33082,33254,33262,{f:3,c:33271},33284,{f:2,c:33340},33343,33397,33595,[33743,60382],33785,33827,33728,33768,33810,33767,33764,33788,33782,33808,33734,33736,33771,33763,33727,33793,33757,33765,33752,33791,33761,33739,33742,33750,33781,33737,33801,[33807,58332],33758,33809,33798,33730,33779,33749,33786,33735,33 [...]
+33811,33690,33731,33772,33774,33732,33787,33751,33762,33819,33755,33790,34520,34530,34534,34515,34531,34522,34538,34525,34539,34524,34540,34537,34519,34536,34513,34888,34902,34901,35002,35031,35001,35E3,35008,35006,34998,35004,34999,35005,34994,35073,35017,35221,35224,35223,35293,{f:2,c:35290},35406,35405,35385,35417,35392,{f:2,c:35415},{f:2,c:35396},35410,35400,35409,35402,35404,35407,35935,35969,35968,36026,36030,36016,36025,36021,36228,36224,36233,36312,36307,36301,36295,36310,36316,3 [...]
+36313,36296,36311,36293,36591,36599,36602,36601,36582,36590,36581,36597,{f:2,c:36583},36598,36587,36593,36588,36596,36585,36909,36916,36911,37126,37164,[37124,60367],37119,37116,37128,37113,37115,37121,37120,37127,37125,37123,37217,37220,37215,37218,37216,37377,37386,37413,37379,37402,37414,37391,37388,37376,37394,37375,37373,37382,37380,37415,37378,37404,37412,37401,37399,37381,37398,38267,38285,38284,38288,38535,38526,{f:2,c:38536},38531,38528,38594,38600,38595,38641,38640,38764,38768, [...]
+39081,39147,40166,[12235,40697],{f:2,c:20099},20150,20669,20671,20678,20654,20676,20682,20660,20680,20674,20656,20673,20666,20657,20683,20681,20662,20664,20951,21114,21112,{f:2,c:21115},21955,21979,21964,21968,21963,21962,21981,[21952,64013],21972,21956,21993,21951,21970,21901,21967,21973,21986,21974,21960,22002,21965,21977,21954,22292,22611,22632,22628,22607,22605,22601,22639,22613,22606,22621,22617,22629,22619,22589,22627,22641,22780,23239,23236,23243,23226,23224,23217,23221,23216,2323 [...]
+23238,23223,23232,23242,23220,23222,23245,23225,23184,23510,{f:2,c:23512},23583,23603,23921,23907,23882,23909,23922,23916,23902,23912,23911,23906,24048,24143,24142,24138,24141,24139,24261,24268,24262,24267,24263,24384,24495,24493,24823,{f:2,c:24905},24875,24901,24886,24882,24878,24902,24879,24911,24873,24896,25120,37224,25123,25125,25124,25541,25585,25579,25616,25618,25609,25632,25636,25651,25667,25631,25621,25624,25657,25655,{f:2,c:25634},25612,25638,25648,25640,25665,25653,25647,25610, [...]
+25637,25639,25611,25575,25627,25646,25633,25614,25967,26002,26067,26246,26252,26261,26256,26251,26250,26265,26260,26232,26400,26982,26975,26936,26958,26978,26993,26943,26949,26986,26937,26946,26967,26969,27002,{f:2,c:26952},26933,26988,26931,26941,26981,26864,27E3,26932,26985,26944,26991,26948,26998,26968,26945,26996,26956,26939,26955,26935,26972,26959,26961,26930,26962,26927,27003,26940,27462,27461,27459,27458,27464,27457,27547,{f:2,c:27643},27641,{f:2,c:27639},28315,28374,28360,28303,2 [...]
+{f:2,c:28307},28320,28337,28345,28358,28370,28349,28353,28318,28361,28343,28336,28365,28326,28367,28338,28350,28355,28380,28376,28313,28306,28302,28301,28324,28321,28351,28339,28368,28362,28311,28334,28323,28999,29012,29010,29027,29024,28993,29021,[29026,61080],29042,29048,29034,29025,28994,29016,28995,29003,29040,29023,29008,29011,28996,29005,29018,29263,29325,29324,29329,29328,29326,29500,29506,29499,29498,29504,29514,29513,29764,{f:2,c:29770},29778,29777,29783,29760,{f:2,c:29775},2977 [...]
+29773,29780,29921,29951,29950,29949,29981,30073,30071,27011,30191,30223,30211,30199,30206,30204,[30201,60782],30200,30224,30203,30198,30189,30197,30205,30361,30389,30429,30549,{f:2,c:30559},30546,30550,30554,30569,30567,30548,30553,30573,30688,30855,30874,30868,30863,30852,30869,{f:2,c:30853},30881,30851,30841,30873,30848,30870,30843,31100,31106,31101,31097,31249,{f:2,c:31256},31250,31255,31253,31266,31251,31259,31248,31395,31394,31390,31467,31590,31588,31597,31604,31593,31602,31589,3160 [...]
+31585,31608,31606,31587,31922,31924,31919,32136,32134,32128,32141,32127,32133,32122,32142,32123,32131,32124,32140,32148,32132,32125,32146,32621,32619,{f:2,c:32615},32620,32678,32677,32679,{f:2,c:32731},32801,33124,33120,33143,33116,33129,33115,33122,33138,26401,33118,33142,33127,33135,33092,33121,33309,33353,33348,33344,33346,33349,34033,33855,33878,33910,33913,33935,33933,33893,33873,33856,33926,33895,33840,33869,33917,33882,33881,33908,33907,33885,34055,33886,33847,33850,33844,33914,33 [...]
+33842,33861,33833,33753,33867,33839,33858,33837,33887,33904,33849,33870,33868,33874,33903,33989,33934,33851,33863,33846,33843,33896,33918,33860,33835,33888,33876,33902,33872,34571,34564,34551,34572,34554,34518,34549,34637,34552,34574,34569,34561,34550,34573,34565,35030,35019,{f:2,c:35021},35038,35035,35034,35020,35024,35205,35227,35295,35301,35300,35297,35296,35298,35292,35302,35446,35462,35455,35425,35391,35447,35458,35460,35445,35459,35457,35444,35450,35900,35915,35914,35941,35940,3594 [...]
+c:35972},36044,{f:2,c:36200},36241,36236,{f:2,c:36238},36237,{f:2,c:36243},36240,36242,36336,36320,36332,36337,36334,36304,36329,36323,36322,36327,36338,36331,36340,36614,36607,36609,36608,36613,{f:2,c:36615},36610,[36619,60507],36946,36927,36932,36937,36925,37136,37133,37135,37137,37142,37140,37131,37134,{f:2,c:37230},37448,37458,37424,37434,37478,37427,37477,37470,37507,37422,37450,37446,37485,37484,37455,37472,37479,37487,37430,37473,37488,37425,37460,37475,37456,37490,37454,37459,374 [...]
+37426,38303,38300,38302,38299,{f:2,c:38546},38545,38551,38606,38650,38653,38648,38645,38771,{f:2,c:38775},38770,38927,{f:2,c:38925},39084,39158,39161,39343,39346,39344,39349,39597,39595,39771,40170,40173,40167,40576,[12236,40701],20710,20692,20695,20712,20723,20699,20714,20701,20708,20691,20716,20720,20719,20707,20704,20952,{f:2,c:21120},21225,21227,21296,21420,22055,22037,22028,22034,22012,22031,22044,22017,22035,22018,22010,22045,22020,22015,22009,22665,22652,22672,22680,22662,22657,22 [...]
+22667,22650,22663,22673,22670,22646,22658,22664,22651,22676,22671,22782,22891,23260,23278,23269,23253,23274,23258,23277,23275,23283,23266,23264,23259,23276,23262,23261,23257,23272,23263,23415,23520,23523,23651,23938,23936,23933,23942,23930,23937,23927,23946,23945,23944,23934,23932,23949,23929,23935,{f:2,c:24152},24147,24280,24273,24279,24270,24284,24277,24281,24274,24276,24388,24387,24431,24502,24876,24872,24897,24926,24945,24947,{f:2,c:24914},24946,24940,24960,24948,24916,24954,24923,24 [...]
+24938,24929,24918,25129,25127,25131,25643,25677,25691,25693,25716,25718,{f:2,c:25714},25725,25717,25702,25766,25678,25730,25694,25692,25675,25683,25696,25680,25727,25663,25708,25707,25689,25701,25719,25971,26016,26273,26272,26271,26373,26372,26402,27057,27062,27081,27040,27086,27030,27056,27052,27068,27025,27033,27022,27047,27021,27049,27070,27055,27071,27076,27069,27044,27092,27065,27082,27034,27087,27059,27027,27050,27041,27038,27097,27031,27024,27074,27061,27045,27078,27466,27469,2746 [...]
+{f:2,c:27587},27646,28366,28405,28401,28419,28453,28408,28471,28411,28462,28425,28494,{f:2,c:28441},28455,28440,28475,28434,28397,28426,28470,28531,28409,28398,28461,28480,28464,28476,28469,28395,28423,28430,28483,28421,28413,28406,28473,28444,28412,28474,28447,28429,28446,28424,28449,29063,29072,29065,29056,29061,29058,29071,29051,29062,29057,29079,29252,29267,29335,29333,29331,29507,29517,29521,29516,29794,29811,29809,29813,29810,29799,29806,29952,{f:2,c:29954},30077,30096,30230,30216, [...]
+30225,30218,30228,30392,30593,30588,30597,30594,30574,30592,30575,30590,30595,30898,30890,30900,30893,30888,30846,30891,30878,30885,30880,30892,30882,30884,31128,{f:2,c:31114},31126,31125,31124,31123,31127,31112,31122,31120,31275,31306,31280,31279,31272,31270,31400,{f:2,c:31403},31470,31624,31644,31626,31633,31632,31638,31629,31628,31643,31630,31621,31640,21124,31641,31652,31618,31931,31935,31932,31930,32167,32183,32194,32163,32170,32193,32192,32197,32157,32206,32196,32198,{f:2,c:32203}, [...]
+32150,32188,32159,32166,32174,32169,32161,32201,32627,{f:2,c:32738},32741,32734,32804,32861,32860,33161,33158,33155,33159,33165,33164,33163,33301,33943,33956,33953,33951,33978,33998,33986,33964,33966,33963,33977,33972,33985,33997,33962,33946,33969,34E3,33949,33959,33979,33954,33940,33991,33996,33947,33961,33967,[33960,58327],34006,33944,33974,33999,33952,34007,34004,34002,34011,33968,33937,34401,34611,34595,34600,34667,34624,34606,34590,34593,34585,34587,34627,34604,34625,34622,34630,345 [...]
+34602,34605,34620,34578,34618,34609,34613,34626,{f:2,c:34598},34616,34596,34586,34608,34577,35063,35047,{f:2,c:35057},35066,35070,35054,35068,35062,35067,35056,35052,35051,35229,35233,35231,35230,35305,35307,35304,35499,35481,35467,35474,35471,35478,35901,{f:2,c:35944},36053,36047,36055,36246,36361,36354,36351,36365,36349,36362,36355,36359,36358,36357,36350,36352,36356,{f:2,c:36624},36622,36621,37155,37148,37152,37154,37151,37149,37146,37156,37153,37147,37242,37234,37241,37235,37541,3754 [...]
+37498,37536,37524,37546,37517,37542,37530,37547,37497,37527,37503,37539,37614,37518,37506,37525,37538,37501,37512,37537,37514,37510,37516,37529,37543,37502,37511,37545,37533,37515,37421,38558,38561,38655,38744,38781,38778,38782,38787,38784,38786,38779,38788,38785,38783,38862,38861,38934,{f:2,c:39085},39170,39168,39175,39325,39324,39363,39353,39355,39354,39362,39357,39367,39601,39651,39655,{f:2,c:39742},{f:2,c:39776},39775,{f:2,c:40177},40181,40615,20735,20739,20784,20728,{f:2,c:20742},20 [...]
+{f:2,c:20747},20733,20746,{f:2,c:21131},21233,21231,22088,22082,22092,22069,22081,22090,22089,22086,22104,22106,22080,22067,22077,22060,22078,22072,22058,22074,22298,22699,22685,22705,22688,22691,22703,22700,22693,22689,22783,23295,23284,23293,23287,23286,23299,23288,23298,23289,23297,23303,23301,23311,23655,23961,23959,23967,23954,23970,23955,23957,23968,23964,23969,23962,23966,24169,24157,24160,24156,32243,24283,24286,24289,24393,24498,24971,24963,24953,25009,25008,24994,24969,24987,24 [...]
+25005,24991,24978,25002,24993,24973,24934,25011,25133,25710,25712,25750,25760,25733,25751,25756,25743,25739,25738,25740,25763,25759,25704,25777,25752,25974,25978,25977,25979,{f:2,c:26034},26293,26288,26281,26290,26295,26282,26287,27136,27142,27159,27109,27128,27157,27121,27108,27168,27135,27116,27106,27163,27165,27134,27175,27122,27118,27156,27127,27111,27200,27144,27110,27131,27149,27132,27115,27145,27140,27160,27173,27151,27126,27174,27143,27124,27158,27473,27557,27555,27554,27558,2764 [...]
+27650,28481,28454,28542,28551,28614,28562,28557,28553,28556,28514,28495,28549,28506,28566,28534,28524,28546,28501,28530,28498,28496,28503,28564,28563,28509,28416,28513,28523,28541,28519,28560,28499,28555,28521,28543,28565,28515,28535,28522,28539,29106,29103,29083,29104,29088,29082,29097,29109,29085,29093,29086,29092,29089,29098,29084,29095,29107,29336,29338,29528,29522,{f:3,c:29534},29533,29531,29537,29530,29529,29538,29831,{f:2,c:29833},29830,29825,29821,29829,29832,29820,[29817,58868], [...]
+30078,30245,30238,30233,30237,30236,30243,30234,30248,30235,{f:3,c:30364},30363,30605,30607,30601,30600,30925,30907,30927,30924,30929,30926,30932,30920,{f:2,c:30915},30921,31130,31137,31136,31132,31138,[31131,59175],27510,31289,31410,31412,31411,31671,31691,31678,31660,31694,31663,31673,31690,31669,31941,31944,31948,31947,32247,32219,32234,32231,32215,32225,32259,32250,32230,32246,32241,32240,32238,32223,32630,32684,32688,32685,32749,32747,32746,32748,32742,32744,32868,32871,33187,33183, [...]
+33186,33177,33175,33302,33359,33363,33362,33360,33358,33361,34084,34107,34063,34048,34089,34062,34057,34061,34079,34058,34087,34076,34043,34091,34042,34056,34060,34036,34090,34034,34069,34039,34027,34035,34044,34066,34026,34025,34070,34046,34088,34077,34094,34050,34045,34078,34038,34097,34086,{f:2,c:34023},34032,34031,34041,34072,34080,34096,34059,34073,34095,34402,34646,{f:2,c:34659},34679,34785,34675,34648,34644,34651,34642,34657,34650,34641,34654,34669,34666,34640,34638,34655,34653,34 [...]
+34682,34670,34652,34661,34639,34683,34677,34658,34663,34665,34906,35077,35084,35092,35083,{f:3,c:35095},35078,35094,35089,35086,35081,35234,35236,35235,35309,35312,35308,35535,35526,35512,35539,35537,{f:2,c:35540},35515,35543,35518,35520,35525,35544,35523,35514,35517,35545,35902,35917,35983,36069,36063,36057,36072,36058,36061,36071,36256,36252,36257,36251,36384,36387,36389,36388,36398,36373,36379,36374,36369,36377,{f:2,c:36390},36372,36370,36376,36371,36380,36375,36378,36652,36644,36632, [...]
+36643,{f:2,c:36630},36979,36976,36975,36967,36971,37167,37163,{f:2,c:37161},37170,37158,37166,{f:2,c:37253},37258,{f:2,c:37249},37252,37248,37584,{f:2,c:37571},37568,37593,37558,37583,37617,37599,37592,37609,37591,37597,37580,37615,37570,37608,37578,37576,37582,37606,37581,37589,37577,37600,37598,37607,37585,37587,37557,37601,37669,37574,37556,38268,38316,38315,38318,38320,38564,38562,38611,38661,38664,38658,38746,38794,38798,38792,38864,38863,38942,38941,38950,38953,38952,38944,38939,38 [...]
+39176,39162,39185,39188,{f:2,c:39190},39189,39388,39373,39375,{f:2,c:39379},39374,39369,[39382,60270],39384,39371,39383,39372,39603,39660,39659,39667,39666,39665,39750,39747,39783,39796,39793,39782,39798,39797,39792,39784,39780,39788,40188,40186,40189,40191,40183,40199,40192,40185,40187,40200,40197,40196,40579,40659,{f:2,c:40719},20764,20755,20759,20762,20753,20958,21300,21473,22128,22112,22126,22131,22118,22115,22125,22130,22110,22135,22300,22299,22728,22717,22729,22719,22714,22722,2271 [...]
+23321,23323,23329,23316,23315,23312,23318,[23336,59539],23322,23328,23326,23535,23980,23985,23977,23975,23989,23984,23982,23978,23976,23986,23981,23983,23988,{f:2,c:24167},24166,24175,24297,24295,24294,24296,24293,24395,24508,24507,24989,25E3,24982,25029,25012,25030,25025,25036,25018,25023,25016,24972,25815,25814,25808,25807,25801,25789,25737,25795,25819,25843,25817,25907,25983,25980,26018,26312,26302,26304,{f:2,c:26314},26319,26301,26299,26298,26316,26403,27188,27238,27209,27239,27186,2 [...]
+27229,27245,27254,27227,27217,27176,27226,27195,27199,27201,27242,27236,27216,27215,27220,27247,27241,27232,27196,27230,27222,27221,{f:2,c:27213},27206,27477,27476,27478,27559,{f:2,c:27562},27592,27591,27652,27651,27654,28589,28619,28579,28615,28604,28622,28616,28510,28612,28605,28574,28618,28584,28676,28581,28590,28602,28588,28586,28623,28607,28600,28578,28617,28587,28621,28591,28594,28592,29125,29122,29119,29112,29142,{f:2,c:29120},29131,29140,29130,29127,29135,29117,29144,29116,29126, [...]
+{f:2,c:29341},29545,{f:2,c:29542},29548,29541,29547,29546,29823,29850,29856,29844,29842,29845,29857,29963,30080,30255,30253,30257,30269,30259,30268,30261,30258,30256,30395,30438,30618,30621,30625,30620,30619,{f:2,c:30626},30613,30617,30615,30941,30953,30949,30954,30942,30947,30939,{f:2,c:30945},30957,{f:2,c:30943},31140,31300,31304,31303,31414,31416,31413,31409,31415,31710,31715,31719,31709,31701,31717,31706,31720,31737,31700,31722,31714,31708,31723,31704,31711,31954,31956,31959,{f:2,c:3 [...]
+32289,32279,32268,{f:2,c:32287},32275,32270,32284,32277,32282,32290,32267,32271,32278,32269,32276,32293,32292,32579,{f:2,c:32635},32634,32689,32751,32810,32809,32876,33201,33190,33198,33209,33205,33195,33200,33196,33204,33202,33207,33191,33266,{f:3,c:33365},34134,34117,34155,34125,34131,34145,34136,34112,34118,34148,34113,34146,34116,34129,34119,34147,34110,34139,34161,34126,34158,34165,34133,34151,34144,34188,34150,34141,34132,34149,34156,34403,34405,34404,34724,34715,34703,34711,34707, [...]
+34689,34710,34712,34681,34695,34723,34693,{f:2,c:34704},34717,34692,34708,34716,34714,34697,35102,35110,35120,{f:2,c:35117},35111,35121,35106,35113,35107,35119,35116,35103,35313,35552,35554,35570,{f:2,c:35572},35549,35604,35556,35551,35568,35528,35550,35553,35560,35583,35567,35579,{f:2,c:35985},35984,36085,36078,36081,36080,36083,36204,36206,36261,36263,36403,36414,36408,36416,36421,36406,{f:2,c:36412},36417,36400,36415,36541,[36662,60329],36654,36661,36658,36665,36663,36660,36982,36985, [...]
+37114,37171,{f:2,c:37173},37267,{f:2,c:37264},37261,37263,37671,37662,37640,37663,37638,37647,37754,37688,37692,37659,37667,37650,37633,37702,37677,37646,37645,37579,37661,37626,37651,37625,37623,37684,37634,37668,37631,37673,37689,37685,37674,37652,37644,37643,37630,37641,37632,37627,37654,38332,38349,38334,{f:2,c:38329},38326,38335,38325,38333,38569,38612,38667,38674,38672,38809,38807,38804,38896,38904,38965,38959,38962,39204,39199,39207,39209,39326,39406,39404,39397,39396,39408,39395, [...]
+39399,39609,39615,39604,39611,39670,39674,39673,39671,39731,39808,39813,39815,39804,39806,39803,39810,39827,39826,39824,39802,39829,39805,39816,40229,40215,40224,40222,40212,40233,40221,40216,40226,40208,40217,40223,40584,{f:2,c:40582},40622,40621,{f:2,c:40661},40698,40722,40765,20774,20773,20770,20772,20768,20777,21236,22163,{f:2,c:22156},22150,22148,22147,22142,22146,22143,22145,22742,22740,22735,22738,23341,23333,23346,23331,23340,23335,23334,23343,23342,23419,{f:2,c:23537},23991,2417 [...]
+25027,25013,25020,25063,25056,25061,25060,25064,25054,25839,25833,25827,25835,25828,25832,25985,25984,26038,26074,26322,27277,27286,27265,27301,27273,27295,27291,27297,27294,27271,27283,27278,27285,27267,27304,27300,27281,27263,27302,27290,27269,27276,27282,27483,27565,27657,28620,28585,28660,28628,28643,28636,28653,28647,28646,28638,28658,28637,28642,28648,29153,29169,29160,29170,29156,29168,29154,29555,{f:2,c:29550},29847,29874,29867,29840,29866,29869,29873,29861,29871,{f:3,c:29968},29 [...]
+30275,{f:2,c:30280},30279,30372,30441,30645,30635,30642,30647,30646,30644,30641,30632,30704,30963,30973,30978,{f:2,c:30971},30975,30962,30981,30969,30974,30980,31147,31144,31324,31323,31318,31320,31316,31322,31422,{f:2,c:31424},31749,31759,31730,31744,31743,31739,31758,31732,31755,31731,31746,31753,31747,31745,31736,31741,[31750,58176],{f:2,c:31728},31760,31754,31976,32301,32316,32322,32307,38984,32312,32298,32329,32320,32327,32297,32332,32304,32315,32310,32324,32314,32581,32639,32638,32 [...]
+32754,32812,33211,33220,33228,33226,33221,33223,33212,33257,33371,33370,33372,34179,34176,34191,34215,34197,34208,34187,34211,34171,34212,34202,34206,34167,34172,34185,34209,34170,34168,34135,34190,34198,34182,34189,34201,34205,34177,34210,34178,34184,34181,34169,34166,34200,34192,34207,34408,34750,34730,34733,34757,34736,34732,34745,34741,34748,34734,34761,34755,34754,34764,34743,34735,34756,34762,34740,34742,34751,34744,34749,34782,34738,35125,35123,35132,35134,35137,35154,35127,35138, [...]
+35246,{f:2,c:35314},35614,35608,35606,35601,35589,35595,35618,35599,35602,35605,35591,35597,35592,35590,35612,35603,35610,35919,35952,35954,35953,35951,35989,35988,36089,36207,36430,36429,36435,36432,36428,36423,36675,36672,36997,36990,37176,37274,37282,37275,37273,37279,37281,37277,37280,37793,37763,37807,37732,37718,37703,37756,37720,37724,37750,37705,{f:2,c:37712},37728,37741,37775,37708,37738,37753,37719,37717,37714,37711,37745,37751,37755,37729,37726,37731,37735,37710,37721,38343,38 [...]
+38339,38341,38327,38574,38576,38572,38688,38687,38680,38685,38681,38810,38817,38812,38814,38813,38869,38868,38897,38977,38980,38986,38985,38981,38979,39205,{f:2,c:39211},39210,39219,39218,39215,39213,39217,39216,39320,39331,39329,39426,39418,39412,39415,39417,39416,39414,39419,{f:2,c:39421},39420,39427,39614,39678,39677,39681,39676,39752,39834,39848,39838,39835,39846,39841,39845,39844,39814,39842,39840,39855,40243,40257,40295,40246,{f:2,c:40238},40241,40248,40240,40261,{f:2,c:40258},4025 [...]
+40253,32757,40237,40586,40585,40589,40624,40648,40666,40699,40703,40740,40739,40738,40788,[12245,40864],20785,{f:2,c:20781},22168,22172,22167,22170,22173,22169,22896,23356,{f:2,c:23657},24E3,{f:2,c:24173},25048,25055,{f:2,c:25069},25073,25066,25072,25067,25046,25065,25855,25860,25853,25848,25857,25859,25852,26004,26075,{f:2,c:26330},26328,27333,27321,27325,27361,27334,27322,{f:2,c:27318},27335,27316,27309,27486,27593,27659,28679,{f:2,c:28684},28673,28677,28692,28686,{f:2,c:28671},28667,2 [...]
+28663,28682,[29185,60224],29183,29177,29187,29181,29558,29880,29888,29877,29889,29886,29878,29883,29890,29972,29971,30300,30308,30297,30288,30291,30295,30298,30374,30397,30444,30658,30650,30988,{f:2,c:30995},30985,30992,30994,30993,31149,31148,31327,31772,31785,31769,31776,31775,31789,31773,31782,31784,31778,31781,31792,32348,32336,32342,32355,32344,32354,32351,32337,32352,32343,32339,32693,32691,{f:2,c:32759},32885,{f:2,c:33233},33232,33375,33374,34228,34246,34240,34243,34242,34227,3422 [...]
+34244,34239,34251,34254,34248,34245,34225,34230,34258,34340,34232,34231,34238,34409,34791,34790,34786,34779,34795,34794,34789,34783,34803,34788,34772,34780,34771,34797,34776,34787,34775,34777,34817,34804,34792,34781,35155,35147,35151,35148,35142,{f:2,c:35152},35145,35626,35623,35619,35635,35632,35637,35655,35631,35644,35646,35633,35621,35639,35622,35638,35630,35620,35643,35645,35642,35906,35957,35993,35992,35991,36094,36100,36098,36096,36444,36450,36448,36439,36438,36446,36453,36455,3644 [...]
+36445,36457,36436,{f:3,c:36678},36683,37160,{f:2,c:37178},37182,37288,37285,37287,37295,37290,37813,37772,37778,37815,37787,37789,37769,37799,37774,37802,37790,37798,37781,37768,37785,37791,37760,37773,37809,37777,37810,37796,37800,37812,37795,{f:2,c:38354},38353,38579,38615,38618,24002,38623,38616,38621,38691,38690,38693,38828,38830,38824,38827,38820,38826,38818,38821,38871,38873,38870,38872,38906,{f:3,c:38992},39096,39233,39228,39226,39439,39435,39433,39437,39428,39441,39434,39429,3943 [...]
+39644,39688,{f:2,c:39684},39721,39733,39754,39756,39755,39879,39878,39875,39871,39873,39861,39864,39891,39862,39876,39865,39869,40284,40275,40271,40266,40283,40267,40281,40278,40268,40279,40274,40276,40287,40280,40282,40590,40588,40671,40705,40704,[40726,58693],40741,40747,40746,40745,40744,40780,40789,{f:2,c:20788},21142,21239,21428,22187,22189,{f:2,c:22182},22186,22188,22746,22749,22747,22802,{f:3,c:23357},24003,24176,24511,25083,25863,25872,25869,25865,25868,25870,25988,26078,26077,26 [...]
+27360,27340,27345,27353,27339,27359,27356,27344,27371,27343,27341,27358,27488,27568,27660,28697,28711,28704,28694,28715,{f:3,c:28705},28713,28695,28708,28700,29196,29194,29191,29186,29189,{f:2,c:29349},29348,29347,29345,29899,29893,29879,29891,29974,30304,{f:2,c:30665},30660,30705,31005,31003,31009,31004,30999,31006,31152,{f:2,c:31335},31795,31804,31801,31788,31803,31980,31978,32374,32373,32376,32368,32375,32367,32378,32370,32372,32360,32587,32586,32643,32646,32695,{f:2,c:32765},32888,33 [...]
+33291,33380,33377,33379,34283,34289,34285,34265,34273,34280,34266,34263,34284,34290,34296,34264,34271,34275,34268,34257,34288,34278,34287,34270,34274,34816,34810,34819,{f:2,c:34806},34825,34828,34827,34822,34812,34824,34815,34826,34818,35170,{f:2,c:35162},35159,35169,35164,35160,35165,35161,35208,35255,35254,35318,35664,35656,35658,35648,35667,35670,35668,35659,35669,35665,35650,35666,35671,35907,35959,35958,35994,{f:2,c:36102},36105,36268,36266,36269,36267,36461,36472,36467,36458,36463, [...]
+36690,36689,{f:2,c:36687},36691,36788,37184,37183,37296,37293,37854,37831,37839,37826,37850,37840,37881,37868,37836,37849,37801,37862,37834,37844,37870,37859,37845,37828,37838,37824,37842,37797,37863,38269,{f:2,c:38362},38625,38697,{f:2,c:38699},38696,38694,38835,38839,38838,{f:3,c:38877},39004,39001,39005,38999,39103,39101,39099,39102,39240,39239,39235,{f:2,c:39334},39450,39445,39461,39453,39460,39451,39458,39456,39463,39459,39454,39452,39444,39618,39691,39690,39694,39692,39735,{f:2,c:3 [...]
+39902,39908,39910,39906,39920,39892,39895,39916,39900,39897,39909,39893,39905,39898,40311,40321,40330,40324,40328,40305,40320,40312,40326,{f:2,c:40331},40317,40299,{f:2,c:40308},40304,40297,40325,40307,40315,40322,40303,40313,40319,40327,40296,40596,40593,40640,40700,40749,{f:2,c:40768},40781,{f:3,c:40790},21303,22194,22197,22195,22755,23365,{f:2,c:24006},{f:2,c:24302},{f:2,c:24512},25081,25879,25878,25877,25875,26079,26344,{f:2,c:26339},27379,27376,27370,27368,27385,27377,{f:2,c:27374}, [...]
+28719,28727,28724,28721,28738,28728,28735,28730,28729,28714,28736,28731,28723,28737,{f:2,c:29203},29352,29565,29564,29882,30379,30378,30398,30445,30668,{f:2,c:30670},30669,30706,31013,31011,{f:2,c:31015},31012,31017,31154,31342,{f:2,c:31340},31479,31817,31816,31818,31815,31813,31982,32379,32382,32385,32384,32698,32767,32889,33243,33241,{f:2,c:33384},34338,34303,34305,34302,34331,34304,34294,34308,34313,34309,34316,34301,34841,{f:2,c:34832},34839,34835,34838,35171,35174,35257,35319,35680, [...]
+35688,35683,35685,35687,35693,36270,36486,36488,36484,36697,{f:2,c:36694},36693,36696,36698,37005,37187,37185,37303,37301,{f:2,c:37298},37899,37907,37883,37920,37903,37908,37886,37909,37904,37928,37913,37901,37877,37888,37879,37895,37902,37910,37906,37882,37897,37880,37948,37898,37887,37884,37900,37878,37905,37894,38366,38368,38367,{f:2,c:38702},38841,38843,{f:2,c:38909},39008,{f:2,c:39010},39007,{f:2,c:39105},39248,39246,39257,39244,39243,39251,39474,39476,39473,39468,39466,39478,39465, [...]
+39469,39623,39626,39622,39696,39698,39697,39947,39944,39927,39941,39954,39928,4E4,39943,39950,39942,39959,39956,39945,40351,40345,40356,40349,40338,40344,40336,40347,40352,40340,40348,40362,40343,40353,40346,40354,40360,40350,40355,40383,40361,40342,{f:2,c:40358},40601,40603,40602,40677,40676,40679,40678,40752,40750,40795,40800,40798,40797,40793,40849,20794,20793,21144,21143,22211,{f:2,c:22205},23368,23367,24011,24015,24305,25085,25883,27394,27388,27395,27384,27392,{f:2,c:28739},28746,{f [...]
+{f:2,c:28741},29213,29210,29209,29566,29975,30314,30672,31021,31025,31023,31828,31827,31986,32394,[32391,60229],32392,32395,32390,32397,32589,32699,32816,33245,34328,34346,34342,34335,34339,34332,34329,34343,34350,34337,34336,34345,34334,34341,34857,34845,34843,34848,34852,34844,34859,34890,35181,35177,35182,35179,35322,35705,35704,35653,{f:2,c:35706},36112,36116,36271,36494,36492,36702,36699,36701,37190,{f:2,c:37188},37305,37951,37947,37942,37929,37949,37936,37945,37930,37943,37932,3795 [...]
+38372,38371,38709,38714,38847,38881,39012,39113,39110,39104,39256,39254,39481,39485,39494,39492,39490,39489,39482,39487,39629,39701,{f:2,c:39703},39702,39738,39762,39979,39965,39964,39980,39971,{f:2,c:39976},39972,39969,40375,40374,40380,40385,40391,40394,40399,40382,40389,40387,40379,40373,40398,{f:2,c:40377},40364,40392,40369,40365,40396,40371,40397,40370,40570,40604,40683,40686,40685,40731,40728,40730,40753,40782,40805,40804,40850,20153,22214,22213,22219,22897,{f:2,c:23371},24021,2401 [...]
+25888,25894,25890,27403,{f:2,c:27400},27661,{f:3,c:28757},28754,{f:2,c:29214},29353,29567,29912,29909,29913,29911,30317,30381,31029,31156,{f:2,c:31344},31831,31836,31833,31835,31834,31988,31985,32401,32591,32647,33246,33387,{f:2,c:34356},34355,34348,34354,34358,34860,34856,34854,34858,34853,35185,35263,35262,35323,35710,35716,35714,35718,35717,35711,36117,36501,36500,36506,36498,36496,{f:2,c:36502},36704,36706,37191,37964,37968,{f:2,c:37962},37967,37959,37957,{f:2,c:37960},37958,38719,38 [...]
+39017,39115,39252,39259,39502,{f:2,c:39507},39500,39503,39496,39498,39497,39506,39504,39632,39705,39723,39739,39766,39765,40006,40008,39999,40004,39993,39987,40001,39996,39991,39988,39986,39997,39990,40411,40402,40414,40410,40395,40400,40412,40401,40415,40425,40409,40408,40406,40437,40405,40413,40630,40688,40757,40755,40754,40770,40811,40853,40866,20797,21145,22760,22759,22898,23373,24024,34863,24399,25089,{f:2,c:25091},25897,25893,26006,26347,{f:2,c:27409},27407,27594,28763,28762,29218, [...]
+29571,30320,30676,31847,31846,32405,33388,34362,34368,34361,34364,34353,34363,34366,34864,34866,34862,34867,35190,35188,35187,35326,35724,35726,35723,35720,35909,36121,36504,36708,36707,37308,37986,37973,37981,37975,37982,{f:2,c:38852},38912,39510,39513,{f:3,c:39710},40018,40024,40016,40010,40013,40011,40021,40025,40012,40014,40443,40439,40431,40419,40427,40440,40420,40438,40417,40430,40422,40434,[40432,60370],40418,40428,40436,40435,40424,40429,40642,40656,{f:2,c:40690},40710,40732,4076 [...]
+40771,40783,40817,40816,{f:2,c:40814},22227,22221,23374,23661,25901,{f:2,c:26349},27411,28767,28769,28765,28768,29219,29915,29925,30677,31032,31159,31158,31850,32407,32649,33389,34371,34872,34871,34869,34891,{f:2,c:35732},{f:3,c:36510},36509,37310,37309,37314,37995,{f:2,c:37992},38629,38726,38723,38727,38855,38885,39518,39637,39769,40035,40039,40038,40034,40030,40032,40450,40446,40455,40451,40454,40453,{f:2,c:40448},40457,40447,40445,40452,40608,40734,40774,{f:3,c:40820},22228,25902,2604 [...]
+27415,27418,28770,29222,29354,{f:2,c:30680},31033,31849,31851,31990,32410,32408,32411,32409,{f:2,c:33248},{f:3,c:34374},{f:2,c:35193},35196,35195,35327,{f:2,c:35736},36517,36516,36515,37998,37997,37999,38001,38003,38729,39026,39263,40040,40046,40045,40459,40461,40464,40463,40466,40465,40609,40693,40713,40775,40824,40827,40826,40825,22302,28774,31855,34876,36274,36518,37315,38004,38008,38006,38005,39520,[39726,60830],40052,40051,40049,40053,40468,40467,40694,40714,40868,28776,28773,31991, [...]
+34877,34879,35742,35996,36521,36553,38731,{f:2,c:39027},39116,39265,39339,39524,{f:2,c:39526},39716,40469,40471,40776,25095,27422,29223,34380,36520,38018,{f:2,c:38016},39529,39528,40473,34379,35743,38019,40057,40631,30325,39531,40058,40477,{f:2,c:28777},29225,40612,40830,40777,40856,{s:97},65075,0,65076,65103,[168,776,63208],[710,63209,65342],[12541,63210],[12542,63211],[12445,63212],[12446,63213],0,[12293,63216],[12294,63217],[12295,63218],[12540,63219],[63220,65339],[63221,65341],[1004 [...]
+[12353,63223],[12354,63224],[12355,63225],[12356,63226],[12357,63227],[12358,63228],[12359,63229],[12360,63230],[12361,63231],[12362,63232],[12363,63233],[12364,63234],[12365,63235],[12366,63236],[12367,63237],[12368,63238],[12369,63239],[12370,63240],[12371,63241],[12372,63242],[12373,63243],[12374,63244],[12375,63245],[12376,63246],[12377,63247],[12378,63248],[12379,63249],[12380,63250],[12381,63251],[12382,63252],[12383,63253],[12384,63254],[12385,63255],[12386,63256],[12387,63257],[1 [...]
+[12389,63259],[12390,63260],[12391,63261],[12392,63262],[12393,63263],[12394,63264],[12395,63265],[12396,63266],[12397,63267],[12398,63268],[12399,63269],[12400,63270],[12401,63271],[12402,63272],[12403,63273],[12404,63274],[12405,63275],[12406,63276],[12407,63277],[12408,63278],[12409,63279],[12410,63280],[12411,63281],[12412,63282],[12413,63283],[12414,63284],[12415,63285],[12416,63286],[12417,63287],[12418,63288],[12419,63289],[12420,63290],[12421,63291],[12422,63292],[12423,63293],[1 [...]
+[12425,63295],[12426,63296],[12427,63297],[12428,63298],[12429,63299],[12430,63300],[12431,63301],[12432,63302],[12433,63303],[12434,63304],[12435,63305],[12449,63306],[12450,63307],[12451,63308],[12452,63309],[12453,63310],[12454,63311],[12455,63312],[12456,63313],[12457,63314],[12458,63315],[12459,63316],[12460,63317],[12461,63318],[12462,63319],[12463,63320],[12464,63321],[12465,63322],[12466,63323],[12467,63324],[12468,63325],[12469,63326],[12470,63327],[12471,63328],[12472,63329],[1 [...]
+[12474,63331],[12475,63332],[12476,63333],[12477,63334],[12478,63335],[12479,63336],[12480,63337],[12481,63338],[12482,63339],[12483,63340],[12484,63341],[12485,63342],[12486,63343],[12487,63344],[12488,63345],[12489,63346],[12490,63347],[12491,63348],[12492,63349],[12493,63350],[12494,63351],[12495,63352],[12496,63353],[12497,63354],[12498,63355],[12499,63356],[12500,63357],[12501,63358],[12502,63359],[12503,63360],[12504,63361],[12505,63362],[12506,63363],[12507,63364],[12508,63365],[1 [...]
+[12510,63367],[12511,63368],[12512,63369],[12513,63370],[12514,63371],[12515,63372],[12516,63373],[12517,63374],[12518,63375],[12519,63376],[12520,63377],[12521,63378],[12522,63379],[12523,63380],[12524,63381],[12525,63382],[12526,63383],[12527,63384],[12528,63385],[12529,63386],[12530,63387],[12531,63388],[12532,63389],[12533,63390],[12534,63391],[1040,63392],[1041,63393],[1042,63394],[1043,63395],[1044,63396],[1045,63397],[1025,63398],[1046,63399],[1047,63400],[1048,63401],[1049,63402] [...]
+[1051,63404],[1052,63405],[1053,63406],[1054,63407],[1055,63408],[1056,63409],[1057,63410],[1058,63411],[1059,63412],[1060,63413],[1061,63414],[1062,63415],[1063,63416],[1064,63417],[1065,63418],[1066,63419],[1067,63420],[1068,63421],[1069,63422],[1070,63423],[1071,63424],[1072,63425],[1073,63426],[1074,63427],[1075,63428],[1076,63429],[1077,63430],[1105,63431],[1078,63432],[1079,63433],[1080,63434],[1081,63435],[1082,63436],[1083,63437],[1084,63438],[1085,63439],[1086,63440],[1087,63441 [...]
+[1089,63443],[1090,63444],[1091,63445],[1092,63446],[1093,63447],[1094,63448],[1095,63449],[1096,63450],[1097,63451],[1098,63452],[1099,63453],[1100,63454],[1101,63455],[1102,63456],[1103,63457],[8679,63458],[8632,63459],[8633,63460],[20033,63461],[63462,131276],[20058,63463],[63464,131210],[20994,63465],[17553,63466],63467,[20872,63468],[13853,63469],[63470,161287],{s:40},[172,63511,65506],[63512,65508],[63513,65287],[63514,65282],[12849,63515],[8470,63516],[8481,63517],30849,[37561,585 [...]
+22715,24658,31911,23290,9556,9574,9559,9568,9580,9571,9562,9577,9565,9554,9572,9557,{s:3},9560,9575,9563,9555,9573,9558,9567,9579,9570,9561,9576,9564,9553,{s:5},9619,{s:26},[58129,147159],[22462,58130],[58131,159443],[28990,58132],[58133,153568],[27042,58135],[58136,166889],[23412,58137],[31305,58138],[58139,153825],[58140,169177],[31333,58141],[31357,58142],[58143,154028],[31419,58144],[31408,58145],[31426,58146],[31427,58147],[29137,58148],[58149,156813],[16842,58150],[31450,58151],[31 [...]
+[31466,58153],[16879,58154],[21682,58155],[58156,154625],[31499,58157],[31573,58158],[31529,58159],[58160,152334],[58161,154878],[31650,58162],[31599,58163],[33692,58164],[58165,154548],[58166,158847],[31696,58167],[33825,58168],[31634,58169],0,[58171,154912],0,[33938,58174],[31738,58175],0,[31797,58177],[58178,154817],[31812,58179],[31875,58180],[58181,149634],[31910,58182],[58184,148856],[31945,58185],[31943,58186],[31974,58187],0,[31987,58189],[31989,58190],[32359,58192],[17693,58193] [...]
+[32093,58195],[58196,159446],[32137,58198],[32171,58199],[28981,58200],[32179,58201],32214,[58203,147543],[58204,155689],[32228,58205],[15635,58206],[32245,58207],[58208,137209],[32229,58209],[58210,164717],0,[58212,155937],[58213,155994],[32366,58214],0,[17195,58216],[37996,58217],[32295,58218],[32576,58219],[32577,58220],[32583,58221],[31030,58222],[58223,156368],[39393,58224],[32663,58225],[58226,156497],[32675,58227],[58228,136801],[58229,131176],[17756,58230],[58231,145254],[58233,1 [...]
+58234],[58235,156809],0,[32776,58237],[32797,58238],0,[32815,58240],[58241,172167],[58242,158915],[32827,58243],[32828,58244],[32865,58245],[58246,141076],[18825,58247],[58248,157222],[58249,146915],[58250,157416],[26405,58251],[32935,58252],[58253,166472],[33031,58254],[33050,58255],[22704,58256],[58257,141046],[27775,58258],[58259,156824],[25831,58261],[58262,136330],[33304,58263],[58264,137310],[27219,58265],[58266,150117],[58267,150165],[17530,58268],[33321,58269],[58271,158290],[582 [...]
+[20473,58273],[58274,136445],[34018,58275],[33634,58276],0,[58278,149927],[58279,144688],[58280,137075],[58281,146936],[33450,58282],[26907,58283],[58284,194964],[16859,58285],[34123,58286],[33488,58287],[33562,58288],[58289,134678],[58290,137140],[14017,58291],[58292,143741],[58293,144730],[33403,58294],[33506,58295],[33560,58296],[58297,147083],[58298,159139],[58299,158469],[58300,158615],[58301,144846],[15807,58302],[33565,58303],[21996,58304],[33669,58305],[17675,58306],[58307,159141 [...]
+58308],0,[33747,58310],[58312,159444],[27223,58313],[34138,58314],[13462,58315],[58316,159298],[33880,58318],[58319,154596],[33905,58320],[15827,58321],[17636,58322],[27303,58323],[33866,58324],[31064,58326],0,[58328,158614],[58329,159351],[58330,159299],[34014,58331],0,[33681,58333],[17568,58334],[33939,58335],[34020,58336],[58337,154769],[16960,58338],[58339,154816],[17731,58340],[34100,58341],[23282,58342],0,[17703,58344],[34163,58345],[17686,58346],[26559,58347],[34326,58348],[58349, [...]
+165435],[34241,58351],[58352,159880],[34306,58353],[58354,136578],[58355,159949],[58356,194994],[17770,58357],[34344,58358],[13896,58359],[58360,137378],[21495,58361],[58362,160666],[34430,58363],0,[58365,172280],[34798,58366],[58367,142375],[34737,58368],[34778,58369],[34831,58370,60990],[22113,58371],[34412,58372],[26710,58373],[17935,58374],[34885,58375],[34886,58376],[58377,161248],[58378,146873],[58379,161252],[34910,58380],[34972,58381],[18011,58382],[34996,58383],[34997,58384],[35 [...]
+[58388,161551],[35207,58389],{s:3},[35239,58393],[35260,58394],[58395,166437],[35303,58396],[58397,162084],[58398,162493],[35484,58399],[30611,58400],[37374,58401],[35472,58402],[58403,162393],[31465,58404],[58405,162618],[18195,58407],[58408,162616],[29052,58409],[35596,58410],[35615,58411],[58412,152624],[58413,152933],[35647,58414],0,[35661,58416],[35497,58417],[58418,150138],[35728,58419],[35739,58420],[35503,58421],[58422,136927],[17941,58423],[34895,58424],[35995,58425],[58426,1631 [...]
+163215],[58428,195028],[14117,58429],[58430,163155],[36054,58431],[58432,163224],[58433,163261],[36114,58434],[36099,58435],[58436,137488],[36059,58437],[28764,58438],[36113,58439],[16080,58441],0,[36265,58443],[58444,163842],[58445,135188],[58446,149898],[15228,58447],[58448,164284],[58449,160012],[31463,58450],[36525,58451],[36534,58452],[36547,58453],[37588,58454],[36633,58455],[36653,58456],[58457,164709],[58458,164882],[36773,58459],[37635,58460],[58461,172703],[58462,133712],[36787 [...]
+[58465,166366],[58466,165181],[58467,146875],[24312,58468],[58469,143970],[36857,58470],0,[58474,140069],[14720,58475],[58476,159447],[36919,58477],[58478,165180],[58479,162494],[36961,58480],[58481,165228],[58482,165387],[37032,58483],[58484,165651],[37060,58485],[58486,165606],[37038,58487],0,[37223,58489],[37289,58491],[37316,58492],[31916,58493],[58494,166195],[58495,138889],[37390,58496],[27807,58497],[37441,58498],[37474,58499],[58500,153017],[58502,166598],[58503,146587],[58504,16 [...]
+153051],[58506,134449],[37676,58507],[37739,58508],[58509,166625],[58510,166891],[23235,58512],[58513,166626],[58514,166629],[18789,58515],[37444,58516],[58517,166892],[58518,166969],[58519,166911],[37747,58520],[37979,58521],[36540,58522],[38277,58523],[38310,58524],[37926,58525],[38304,58526],[28662,58527],[17081,58528],[58530,165592],[58531,135804],[58532,146990],[18911,58533],[27676,58534],[38523,58535],[38550,58536],[16748,58537],[38563,58538],[58539,159445],[25050,58540],58541,[309 [...]
+[58543,166624],[38589,58544],[21452,58545],[18849,58546],[58547,158904],[58548,131700],[58549,156688],[58550,168111],[58551,168165],[58552,150225],[58553,137493],[58554,144138],[38705,58555],[34370,58556],[38710,58557],[18959,58558],[17725,58559],[17797,58560],[58561,150249],[28789,58562],[23361,58563],[38683,58564],0,[58566,168405],[38743,58567],[23370,58568],[58569,168427],[38751,58570],[37925,58571],[20688,58572],[58573,143543],[58574,143548],[38793,58575],[38815,58576],[38833,58577], [...]
+[38848,58579],[38866,58580],[38880,58581],[58582,152684],[38894,58583],[29724,58584],[58585,169011],0,[38901,58587],[58588,168989],[58589,162170],[19153,58590],[38964,58591],[38963,58592],[38987,58593],[39014,58594],[15118,58595],[58596,160117],[15697,58597],[58598,132656],[58599,147804],[58600,153350],[39114,58601],[39095,58602],[39112,58603],[39111,58604],[19199,58605],[58606,159015],[58607,136915],[21936,58608],[39137,58609],[39142,58610],[39148,58611],[37752,58612],[39225,58613],[586 [...]
+[19314,58615],[58616,170071],[58617,170245],[39413,58618],[39436,58619],[39483,58620],[39440,58621],[39512,58622],[58623,153381],[14020,58624],[58625,168113],[58626,170965],[39648,58627],[39650,58628],[58629,170757],[39668,58630],[19470,58631],[39700,58632],[39725,58633],[58634,165376],[20532,58635],[39732,58636],[14531,58638],[58639,143485],[39760,58640],[39744,58641],[58642,171326],[23109,58643],[58644,137315],[39822,58645],[39938,58647],[39935,58648],[39948,58649],[58650,171624],[4040 [...]
+[58652,171959],[58653,172434],[58654,172459],[58655,172257],[58656,172323],[58657,172511],[40318,58658],[40323,58659],[58660,172340],[40462,58661],[40388,58663],[58665,172435],[58666,172576],[58667,137531],[58668,172595],[40249,58669],[58670,172217],[58671,172724],[40592,58672],[40597,58673],[40606,58674],[40610,58675],[19764,58676],[40618,58677],[40623,58678],[58679,148324],[40641,58680],[15200,58681],[14821,58682],[15645,58683],[20274,58684],[14270,58685],[58686,166955],[40706,58687],[ [...]
+[19350,58689],[37924,58690],[58691,159138],[40727,58692,60836],0,[40761,58694],[22175,58695],[22154,58696],[40773,58697],[39352,58698],[58699,168075],[38898,58700],[33919,58701],0,[40809,58703],[31452,58704],[40846,58705],[29206,58706],[19390,58707],[58708,149877],[58709,149947],[29047,58710],[58711,150008],[58712,148296],[58713,150097],[29598,58714],[58715,166874],[58716,137466],[31135,58717],[58718,166270],[58719,167478],[37737,58720],[37875,58721],[58722,166468],[37612,58723],[37761,5 [...]
+58725],[58726,166252],[58727,148665],[29207,58728],[16107,58729],[30578,58730],[31299,58731],[28880,58732],[58733,148595],[58734,148472],[29054,58735],[58736,137199],[28835,58737],[58738,137406],[58739,144793],[16071,58740],[58741,137349],[58742,152623],[58743,137208],[14114,58744],[58745,136955],[58746,137273],[14049,58747],[58748,137076],[58749,137425],[58750,155467],[14115,58751],[58752,136896],[22363,58753],[58754,150053],[58755,136190],[58756,135848],[58757,136134],[58758,136374],[3 [...]
+58761],[58760,145062],0,[33877,58762],[58763,149908],[58764,160101],[58765,146993],[58766,152924],[58767,147195],[58768,159826],[17652,58769],[58770,145134],[58771,170397],[58772,159526],[26617,58773],[14131,58774],[15381,58775],[15847,58776],[22636,58777],[58778,137506],[26640,58779],[16471,58780],[58781,145215],[58782,147681],[58783,147595],[58784,147727],[58785,158753],[21707,58786],[22174,58787],[58788,157361],[22162,58789],[58790,135135],[58791,134056],[58792,134669],0,[58794,166675 [...]
+58795],[20216,58796],[20779,58797],[14361,58798],[58799,148534],[20156,58800],[58801,132197],0,[20299,58803],[20362,58804],[58805,153169],[23144,58806],[58807,131499],[58808,132043],[14745,58809],[58810,131850],[58811,132116],[13365,58812],[20265,58813],[58814,131776],[58815,167603],[58816,131701],[35546,58817],[58818,131596],[20120,58819],[20685,58820],[20749,58821],[20386,58822],[20227,58823],[58824,150030],[58825,147082],[20290,58826],[20526,58827],[20588,58828],[20609,58829],[20428,5 [...]
+58831],[20568,58832],[20732,58833],[28278,58838],[58839,144789],[58840,147001],[58841,147135],[28018,58842],[58843,137348],[58844,147081],[20904,58845],[20931,58846],[58847,132576],[17629,58848],[58849,132259],[58850,132242],[58851,132241],[36218,58852],[58853,166556],[58854,132878],[21081,58855],[21156,58856],[58857,133235],[21217,58858],0,[18042,58860],[29068,58861],[58862,148364],[58863,134176],[58864,149932],[58865,135396],[27089,58866],[58867,134685],0,[16094,58869],[29849,58870],[2 [...]
+[29782,58872],[29592,58873],[19342,58874],[58875,150204],[58876,147597],[21456,58877],[13700,58878],[29199,58879],[58880,147657],[21940,58881],[58882,131909],[21709,58883],[58884,134086],[22301,58885],[37469,58886],[38644,58887],[22493,58889],[22413,58890],[22399,58891],[13886,58892],[22731,58893],[23193,58894],[58895,166470],[58896,136954],[58897,137071],[58898,136976],[23084,58899],[22968,58900],[23166,58902],[23247,58903],[23058,58904],[58905,153926],[58906,137715],[58907,137313],[589 [...]
+[14069,58909],[27909,58910],[29763,58911],[23073,58912],[58913,155267],[23169,58914],[58915,166871],[58916,132115],[37856,58917],[29836,58918],[58919,135939],[28933,58920],[18802,58921],[37896,58922],[58923,166395],[37821,58924],[14240,58925],[23582,58926],[23710,58927],[24158,58928],[24136,58929],[58930,137622],[58931,137596],[58932,146158],[24269,58933],[23375,58934],[58935,137475],[58936,137476],[14081,58937],[58938,137376],[14045,58939],[58940,136958],[14035,58941],[33066,58942],[589 [...]
+[58944,138682],[58945,144498],[58946,166312],[24332,58947,60916],[24334,58948],[58949,137511],[58950,137131],[23147,58951],[58952,137019],[23364,58953],[58955,161277],[34912,58956],[24702,58957],[58958,141408],[58959,140843],[24539,58960],[16056,58961],[58962,140719],[58963,140734],[58964,168072],[58965,159603],[25024,58966],[58967,131134],[58968,131142],[58969,140827],[24985,58970],[24984,58971],[24693,58972],[58973,142491],[58974,142599],[58975,149204],[58976,168269],[25713,58977],[589 [...]
+[58979,142186],[14889,58980],[58981,142114],[58982,144464],[58983,170218],[58984,142968],[25399,58985],[25782,58987],[25393,58988],[25553,58989],[58990,149987],[58991,142695],[25252,58992],[58993,142497],[25659,58994],[25963,58995],[26994,58996],[15348,58997],[58998,143502],[58999,144045],[59E3,149897],[59001,144043],[21773,59002],[59003,144096],[59004,137433],[59005,169023],[26318,59006],[59007,144009],[59008,143795],[15072,59009],[59011,152964],[59012,166690],[59013,152975],[59014,1369 [...]
+152923],[59016,152613],[30958,59017],[59018,143619],[59019,137258],[59020,143924],[13412,59021],[59022,143887],[59023,143746],[59024,148169],[26254,59025],[59026,159012],[26219,59027],[19347,59028],[26160,59029],[59030,161904],[59031,138731],[26211,59032],[59033,144082],[59034,144097],[26142,59035],[59036,153714],[14545,59037],[59038,145466],[59039,145340],[15257,59040],[59041,145314],[59042,144382],[29904,59043],[15254,59044],[59046,149034],[26806,59047],0,[15300,59049],[27326,59050],[5 [...]
+[59053,148615],[27187,59054],[27218,59055],[27337,59056],[27397,59057],[59058,137490],[25873,59059],[26776,59060],[27212,59061],[15319,59062],[27258,59063],[27479,59064],[59065,147392],[59066,146586],[37792,59067],[37618,59068],[59069,166890],[59070,166603],[37513,59071],[59072,163870],[59073,166364],[37991,59074],[28069,59075],[28427,59076],0,[59079,147327],[15759,59080],[28164,59081],[59082,147516],[23101,59083],[28170,59084],[22599,59085],[27940,59086],[30786,59087],[28987,59088],[590 [...]
+[59090,148086],[28913,59091],[29264,59092,61085],[29319,59093],[29332,59094],[59095,149391],[59096,149285],[20857,59097],[59098,150180],[59099,132587],[29818,59100],[59101,147192],[59102,144991],[59103,150090],[59104,149783],[59105,155617],[16134,59106],[16049,59107],[59108,150239],[59109,166947],[59110,147253],[24743,59111],[16115,59112],[29900,59113],[29756,59114],[37767,59115],[29751,59116],[17567,59117],[59118,159210],[17745,59119],[30083,59120],[16227,59121],[59122,150745],[59123,15 [...]
+59124],[30037,59125],[30323,59126],[59127,173510],0,[29800,59129,61070],[59130,166604],[59131,149931],[59132,149902],[15099,59133],[15821,59134],[59135,150094],[16127,59136],[59137,149957],[59138,149747],[37370,59139],[22322,59140],[37698,59141],[59142,166627],[59143,137316],[20703,59144],[59145,152097],[59146,152039],[30584,59147],[59148,143922],[30478,59149],[30479,59150],[30587,59151],[59152,149143],[59153,145281],[14942,59154],[59155,149744],[29752,59156],[29851,59157],[16063,59158], [...]
+[59160,150215],[16584,59161],[59162,150166],[59163,156078],[37639,59164],[59165,152961],[30750,59166],[30861,59167],[30856,59168],[30930,59169],[29648,59170],[31065,59171],[59172,161601],[59173,153315],[16654,59174],0,0,[31141,59177],[27181,59178],[59179,147194],[31290,59180],[31220,59181],[16750,59182],[59183,136934],[16690,59184],[37429,59185],[31217,59186],[59187,134476],[59188,149900],[59189,131737],[59190,146874],[59191,137070],[13719,59192],[21867,59193],[13680,59194],[13994,59195] [...]
+[59197,134157],[31458,59198],[23129,59199],[59200,141045],[59201,154287],[59202,154268],[23053,59203],[59204,131675],[30960,59205],[23082,59206],[59207,154566],[31486,59208],[16889,59209],[31837,59210],[31853,59211],[16913,59212],[59213,154547],[59214,155324],[59215,155302],[31949,59216],[59217,150009],[59218,137136],[31886,59219],[31868,59220],[31918,59221],[27314,59222],[32220,59223],[32263,59224],[32211,59225],[32590,59226],[59227,156257],[59228,155996],[59229,162632],[32151,59230],[5 [...]
+[17002,59232],[59233,158581],[59234,133398],[26582,59235],[59236,131150],[59237,144847],[22468,59238],[59239,156690],[59240,156664],[32733,59242],[31527,59243],[59244,133164],[59245,154345],[59246,154947],[31500,59247],[59248,155150],[39398,59249],[34373,59250],[39523,59251],[27164,59252],[59253,144447],[59255,150007],[59256,157101],[39455,59257],[59258,157088],0,[59260,160039],[59261,158929],[17642,59262],[33079,59263],[17410,59264],[32966,59265],[33033,59266],[33090,59267],[59268,15762 [...]
+59269],[59270,158274],[33378,59271],[33381,59272],[59273,158289],[33875,59274],[59275,159143],[34320,59276],[59277,160283],[23174,59278],[16767,59279],[59280,137280],[23339,59281],[59282,137377],[23268,59283],[59284,137432],[34464,59285],[59286,195004],[59287,146831],[34861,59288],[59289,160802],[23042,59290],[34926,59291],[20293,59292],[34951,59293],[35007,59294],[35046,59295],[35173,59296],[35149,59297],[59298,153219],[35156,59299],[59300,161669],[59301,161668],[59302,166901],[59303,16 [...]
+166812],[59305,166393],[16045,59306],[33955,59307],[18165,59308],[18127,59309],[14322,59310],[35389,59311],[35356,59312],[59313,169032],[24397,59314],[37419,59315],[59316,148100],[26068,59317],[28969,59318],[28868,59319],[59320,137285],[40301,59321],[35999,59322],[36073,59323],[59324,163292],[22938,59325],[30659,59326],[23024,59327],[14036,59329],[36394,59330],[36519,59331],[59332,150537],[36656,59333],[36682,59334],[17140,59335],[27736,59336],[28603,59337],[59338,140065],[18587,59339],[ [...]
+[28299,59341],[59342,137178],[39913,59343],[14005,59344],[59345,149807],[37051,59346],0,[21873,59348],[18694,59349],[37307,59350],[37892,59351],[59352,166475],[16482,59353],[59354,166652],[37927,59355],[59356,166941],[59357,166971],[34021,59358],[35371,59359],[38297,59360],[38311,59361],[38295,59362],[38294,59363],[59364,167220],[29765,59365],[16066,59366],[59367,149759],[59368,150082],[59369,148458],[16103,59370],[59371,143909],[38543,59372],[59373,167655],[59374,167526],[59375,167525], [...]
+[59377,149997],[59378,150136],[59379,147438],[29714,59380],[29803,59381],[16124,59382],[38721,59383],[59384,168112],[26695,59385],[18973,59386],[59387,168083],[59388,153567],0,[37736,59390],[59391,166281],[59392,166950],[59393,166703],[59394,156606],[37562,59395],[23313,59396],[35689,59397],[18748,59398],[29689,59399],[59400,147995],[38811,59401],0,[39224,59403],[59404,134950],[24001,59405],[59406,166853],[59407,150194],[38943,59408],[59409,169178],[37622,59410],[59411,169431],[37349,594 [...]
+59413],[59414,166736],[59415,150119],[59416,166756],[39132,59417],[59418,166469],[16128,59419],[37418,59420],[18725,59421],[33812,59422],[39227,59423],[39245,59424],[59425,162566],[15869,59426],0,[19311,59428],[39338,59429],[39516,59430],[59431,166757],[59432,153800],[27279,59433],[39457,59434],[23294,59435],[39471,59436],[59437,170225],[19344,59438],[59439,170312],[39356,59440],[19389,59441],[19351,59442],[37757,59443],[22642,59444],[59445,135938],[22562,59446],[59447,149944],[59448,136 [...]
+59449],[59450,141087],[59451,146872],[26821,59452],[15741,59453],[37976,59454],[14631,59455],[24912,59456],[59457,141185],[59458,141675],[24839,59459],[40015,59460],[40019,59461],[40059,59462],[39989,59463],[39952,59464],[39807,59465],[39887,59466],[59467,171565],[39839,59468],[59469,172533],[59470,172286],[40225,59471],[19630,59472],[59473,147716],[40472,59474],[19632,59475],[40204,59476],[59477,172468],[59478,172269],[59479,172275],[59480,170287],[40357,59481],[33981,59482],[59483,1592 [...]
+159711],[59485,158594],[34300,59486],[17715,59487],[59488,159140],[59489,159364],[59490,159216],[33824,59491],[34286,59492],[59493,159232],[59494,145367],[59495,155748],[31202,59496],[59497,144796],[59498,144960],[59500,149982],[15714,59501],[37851,59502],[37566,59503],[37704,59504],[59505,131775],[30905,59506],[37495,59507],[37965,59508],[20452,59509],[13376,59510],[36964,59511],[59512,152925],[30781,59513],[30804,59514],[30902,59515],[30795,59516],[59517,137047],[59518,143817],[59519,1 [...]
+59520],[20338,59521],[28634,59522],[28633,59523],0,[28702,59524,59525],[21524,59526],[59527,147893],[22459,59528],[22771,59529],[22410,59530],[40214,59531],[22487,59532],[28980,59533],[13487,59534],[59535,147884],[29163,59536],[59537,158784],[59538,151447],0,[59540,137141],[59541,166473],[24844,59542],[23246,59543],[23051,59544],[17084,59545],[59546,148616],[14124,59547],[19323,59548],[59549,166396],[37819,59550],[37816,59551],[59552,137430],[59553,134941],[33906,59554],[59555,158912],[5 [...]
+[59557,148218],[59558,142374],[59559,148417],[22932,59560],[59561,146871],[59562,157505],[32168,59563],[59564,155995],[59565,155812],[59566,149945],[59567,149899],[59568,166394],[37605,59569],[29666,59570],[16105,59571],[29876,59572],[59573,166755],[59574,137375],[16097,59575],[59576,150195],[27352,59577],[29683,59578],[29691,59579],[16086,59580],[59581,150078],[59582,150164],[59583,137177],[59584,150118],[59585,132007],[59586,136228],[59587,149989],[29768,59588],[59589,149782],[28837,59 [...]
+149878],[37508,59592],[29670,59593],[37727,59594],[59595,132350],[37681,59596],[59597,166606],[59598,166422],[37766,59599],[59600,166887],[59601,153045],[18741,59602],[59603,166530],[29035,59604],[59605,149827],[59606,134399],[22180,59607],[59608,132634],[59609,134123],[59610,134328],[21762,59611],[31172,59612],[59613,137210],[32254,59614],[59615,136898],[59616,150096],[59617,137298],[17710,59618],[37889,59619],[14090,59620],[59621,166592],[59622,149933],[22960,59623],[59624,137407],[596 [...]
+[59626,160900],[23201,59627],[14050,59628],[59629,146779],[14E3,59630],[37471,59631],[23161,59632],[59633,166529],[59634,137314],[37748,59635],[15565,59636],[59637,133812],[19094,59638],[14730,59639],[20724,59640],[15721,59641],[15692,59642],[59643,136092],[29045,59644],[17147,59645],[59646,164376],[28175,59647],[59648,168164],[17643,59649],[27991,59650],[59651,163407],[28775,59652],[27823,59653],[15574,59654],[59655,147437],[59656,146989],[28162,59657],[28428,59658],[15727,59659],[59660 [...]
+[30033,59661],[14012,59662],[13512,59663],[18048,59664],[16090,59665],[18545,59666],[22980,59667],[37486,59668],[18750,59669],[36673,59670],[59671,166940],[59672,158656],[22546,59673],[22472,59674],[14038,59675],[59676,136274],[28926,59677],[59678,148322],[59679,150129],[59680,143331],[59681,135856],[59682,140221],[26809,59683],[26983,59684],[59685,136088],[59686,144613],[59687,162804],[59688,145119],[59689,166531],[59690,145366],[59691,144378],[59692,150687],[27162,59693],[59694,145069] [...]
+[33854,59696],[17631,59697],[17614,59698],[59699,159014],[59700,159057],[59701,158850],[59702,159710],0,0,[33597,59705],[59706,137018],[33773,59707],[59708,158848],[59709,159827],[59710,137179],[22921,59711],[23170,59712],[59713,137139],[23137,59714],[23153,59715],[59716,137477],[59717,147964],[14125,59718],[23023,59719],[59720,137020],[14023,59721],[29070,59722],[37776,59723],[26266,59724],[59725,148133],[23150,59726],[23083,59727],[59728,148115],[27179,59729],[59730,147193],[59731,1615 [...]
+148571],[59733,148170],[28957,59734],[59735,148057],[59736,166369],[20400,59737],[59738,159016],[23746,59739],[59740,148686],[59741,163405],[59742,148413],[27148,59743],[59744,148054],[59745,135940],0,[28979,59747],[59748,148457],[15781,59749],[27871,59750],[59751,194597],[23019,59754],[24412,59757],[59764,144128],[31955,59776],[59783,162548],[59786,153334],[59790,162584],[36972,59791],[33270,59795],[30476,59797],[27810,59799],[22269,59800],[22633,59828],[26465,59832],[23646,59838],[2277 [...]
+[28857,59843],[26627,59853],[36795,59859],[36796,59861],[20001,59871],[31545,59898],[15820,59902],[29482,57990,59909],[30048,59912],[22586,59920],[33446,59932],[27018,59940],[24803,59944],[20206,59984],[39364,60002],[40639,60023],[21249,60025],[26528,60038],[24808,60046],[20916,60053],[31363,60064],[39994,60075],[31432,60093],[26906,60098],[22956,60100],[22592,60102],[21610,60114],[24807,60123],[22138,60125],[26965,60132],[39983,60133],[34725,60134],[23584,60141],[24075,60143],[26398,601 [...]
+60157],[35713,60161],[20088,60166],[25283,60176],[26709,60180],0,[33533,60190],[35237,60194],[36768,60196],[38840,60198],[38983,60200],[39613,60201],[24497,60218],[26184,60219],[26303,60220],[60221,162425],0,[60225,149946],0,0,[60230,131910],[26382,60232],[26904,60233],[60235,161367],[60236,155618],[60239,161278],[60240,139418],[18640,60241],[19128,60242],[60244,166554],[60247,147515],[60250,150085],[60251,132554],[20946,60252],[60253,132625],[22943,60254],[60255,138920],[15294,60256],[6 [...]
+[14747,60262],[60264,165352],[60265,170441],[14178,60266],[60267,139715],[35678,60268],[60269,166734],0,[29193,60274],[60276,134264],[60280,132985],[36570,60281],[21135,60283],[29041,60285],[60288,147274],[60289,150183],[21948,60290],[60293,158546],[13427,60295],[60297,161330],[18200,60299],[60303,149823],[20582,60305],[13563,60306],[60307,144332],0,[18300,60310],[60311,166216],[60315,138640],0,[60320,162834],[36950,60321],[60323,151450],[35682,60324],[23899,60327],[60328,158711],0,[6033 [...]
+[35562,60332],[60333,150006],[60335,147439],[19392,60337],[60340,141083],[37989,60341],[60342,153569],[24981,60343],[23079,60344],[60345,194765],0,[60348,148769],[20074,60350],[60351,149812],[38486,60352],[28047,60353],[60354,158909],[35191,60356],[60359,156689],0,[31554,60363],[60364,168128],[60365,133649],0,[31301,60369],[39462,60372],[13919,60374],[60375,156777],[60376,131105],[31107,60377],[23852,60380],[60381,144665],0,[18128,60384],[30011,60386],[34917,60387],[22710,60389],[14108,6 [...]
+140685],[15444,60394],[37505,60397],[60398,139642],[37680,60400],[60402,149968],[27705,60403],[60406,134904],[34855,60407],[35061,60408],[60409,141606],[60410,164979],[60411,137137],[28344,60412],[60413,150058],[60414,137248],[14756,60415],0,0,[17727,60419],[26294,60420],[60421,171181],[60422,170148],[35139,60423],[16607,60427],[60428,136714],[14753,60429],[60430,145199],[60431,164072],[60432,136133],[29101,60433],[33638,60434],[60436,168360],0,[19639,60438],[60439,159919],[60440,166315] [...]
+[31555,60446],[31102,60447],[28597,60449],[60450,172767],[27139,60451],[60452,164632],[21410,60453],[60454,159239],[37823,60455],[26678,60456],[38749,59389,60457],[60458,164207],[60460,158133],[60461,136173],[60462,143919],[23941,60464],[60465,166960],[22293,60467],[38947,60468],[60469,166217],[23979,60470],[60471,149896],[26046,60472],[27093,60473],[21458,60474],[60475,150181],[60476,147329],[15377,60477],[26422,60478],[60482,139169],[13770,60490],[18682,60493],0,[30728,60496],[37461,60 [...]
+60499],[17375,60501],[23032,60505],0,[22155,60518],[60520,169449],[36882,60541],[21953,60546],[17673,60551],[32383,60552],[28502,60553],[27313,60554],[13540,60556],[60558,161949],[14138,60559],0,[60562,163876],[60565,162366],[15851,60567],[60569,146615],[60574,156248],[22207,60575],[36366,60577],[23405,60578],[25566,60581],0,[25904,60585],[22061,60586],[21530,60588],[60591,171416],[19581,60592],[22050,60593],[22046,60594],[32585,60595],[22901,60597],[60598,146752],[34672,60599],[33047,60 [...]
+60605],[36120,60606],[30267,60607],[40005,60608],[30286,60609],[30649,60610],[37701,60611],[21554,60612],[33096,60613],[33527,60614],[22053,60615],[33074,60616],[33816,60617],[32957,60618],[21994,60619],[31074,60620],[22083,60621],[21526,60622],[60623,134813],[13774,60624],[22021,57509,60625],[22001,60626],[26353,60627],[60628,164578],[13869,60629],[30004,60630],[22E3,60631],[21946,60632],[21655,60633],[21874,60634],[60635,134209],[60636,134294],[24272,57652,60637],[60639,134774],[60640, [...]
+134818],[40619,60642],[32090,60643],0,[60645,135285],[25245,60646],[38765,60647],[21652,60648],[36045,60649],[29174,60650],[37238,60651],[25596,60652],[25529,60653],[25598,60654],[21865,60655],[60656,142147],[40050,60657],[60658,143027],[20890,60659],[13535,60660],[60661,134567],[20903,60662],[21581,60663],[21790,60664],[21779,60665],[30310,60666],[36397,60667],[60668,157834],[30129,60669],[32950,60670],[34820,60671],0,[35015,60673],[33206,60674],[33820,60675],[17644,60677],[29444,60678] [...]
+[22139,60683],[37232,60690],[37384,60692],[60696,134905],[29286,60697],[18254,60699],[60701,163833],[16634,60703],[40029,60704],[25887,60705],[18675,60707],[60708,149472],[60709,171388],0,[60713,161187],60715,[60716,155720],[29091,60718],[32398,60719],[40272,60720],[13687,60723],[27826,60725],[21351,60726],[14812,60728],[60731,149016],[33325,60734],[21579,60735],60739,[14930,60740],[29556,60742],[60743,171692],[19721,60744],[39917,60745],0,[19547,60748],[60751,171998],[33884,60752],[6075 [...]
+[25390,60757],[32037,60758],[14890,60761],[36872,60762],[21196,60763],[15988,60764],[13946,60765],[17897,60766],[60767,132238],[30272,60768],[23280,60769],[60770,134838],[30842,60771],[18358,60772],[22695,60773],[16575,60774],[22140,60775],[39819,60776],[23924,60777],[30292,60778],[60779,173108],[40581,60780],[19681,60781],0,[14331,60783],[24857,60784],[60786,148466],60787,[22109,60788],[60792,171526],[21044,60793],[13741,60795],0,[40316,60797],[31830,60798],[39737,60799],[22494,60800],[ [...]
+[25811,60803],[60804,169168],[60805,156469],[34477,60807],[60808,134440],[60811,134513],60812,[20990,60813],[60814,139023],[23950,60815],[38659,60816],[60817,138705],[40577,60818],[36940,60819],[31519,60820],[39682,60821],[23761,60822],[31651,60823],[25192,60824],[25397,60825],[39679,60826],[31695,60827],[39722,60828],[31870,60829],0,[31810,60831],[31878,60832],[39957,60833],[31740,60834],[39689,60835],0,39982,[40794,60839],[21875,60840],[23491,60841],[20477,60842],[40600,60843],[20466,6 [...]
+60845],[21201,60847],[22375,60848],[20566,60849],[22967,60850],[24082,60851],[38856,60852],[40363,60853],[36700,60854],[21609,60855],[38836,60856],[39232,60857],[38842,60858],[21292,60859],[24880,60860],[26924,60861],[21466,60862],[39946,60863],[40194,60864],[19515,60865],[38465,60866],[27008,60867],[20646,60868],[30022,60869],[60870,137069],[39386,60871],[21107,60872],60873,[37209,60874],[38529,60875],[37212,60876],60877,[37201,60878],[60879,167575],[25471,60880],[27338,60882],[22033,60 [...]
+60884],[30074,60885],[25221,60886],[29519,60888],[31856,60889],[60890,154657],60892,[30422,60894],[39837,60895],[20010,60896],[60897,134356],[33726,60898],[34882,60899],60900,[23626,60901],[27072,60902],0,0,[21023,60905],[24053,60906],[20174,60907],[27697,60908],[60909,131570],[20281,60910],[21660,60911],0,[21146,60913],[36226,60914],[13822,60915],0,[13811,60917],60918,[27474,60919],[37244,60920],[40869,60921],[39831,60922],[38958,60923],[39092,60924],[39610,60925],[40616,60926],[40580,6 [...]
+60929],60930,[27642,60931],[34840,60932],[32632,60933],60934,[22048,60935],[60936,173642],[36471,60937],[40787,60938],60939,[36308,60940],[36431,60941],[40476,60942],[36353,60943],[25218,60944],[60945,164733],[36392,60946],[36469,60947],[31443,60948],[31294,60950],[30936,60951],[27882,60952],[35431,60953],[30215,60954],[40742,60956],[27854,60957],[34774,60958],[30147,60959],[60960,172722],[30803,60961],[36108,60963],[29410,60964],[29553,60965],[35629,60966],[29442,60967],[29937,60968],[3 [...]
+[60970,150203],[34351,60971],[24506,60972],[34976,60973],[17591,60974],60975,[60977,159237],60978,[35454,60979],[60980,140571],60981,[24829,60982],[30311,60983],[39639,60984],[40260,60985],[37742,58859,60986],[39823,60987],[34805,60988],60989,0,[36087,60991],[29484,60992],[38689,60993],[39856,60994],[13782,60995],[29362,60996],[19463,60997],[31825,60998],[39242,60999],[24921,61001],[19460,61002],[40598,61003],[24957,61004],61005,[22367,61006],[24943,61007],[25254,61008],[25145,61009],0,[ [...]
+[25058,61012],[21418,61013],[25444,61015],[26626,61016],[13778,61017],[23895,61018],[36826,61020],[61021,167481],61022,[20697,61023],[30982,61025],[21298,61026],[38456,61027],[61028,134971],[16485,61029],61030,[30718,61031],61032,[31938,61033],[61034,155418],[31962,61035],[31277,61036],[32870,61037],[32867,61038],[32077,61039],[29957,61040],[29938,61041],[35220,61042],[33306,61043],[26380,61044],[32866,61045],[61046,160902],[32859,61047],[29936,61048],[33027,61049],[30500,61050],[35209,6 [...]
+157644],[30035,61053],[34729,61055],[34766,61056],[33224,61057],[34700,61058],[35401,61059],[36013,61060],[35651,61061],[30507,61062],[29944,61063],[34010,61064],[27058,61066],[36262,61067],61068,[35241,58392,61069],0,[28089,61071],[34753,61072],[61073,147473],[29927,61074],[15835,61075],[29046,61076],[24740,57702,61077],[24988,61078],[15569,61079],0,[24695,61081],61082,[32625,61083],0,[24809,61086],[19326,61087],[57344,132423],[37595,57345],[57346,132575],[57347,147397],[34124,57348],[1 [...]
+[29679,57350],[20917,57351],[13897,57352],[57353,149826],[57354,166372],[37700,57355],[57356,137691],[33518,57357],[57358,146632],[30780,57359],[26436,57360],[25311,57361],[57362,149811],[57363,166314],[57364,131744],[57365,158643],[57366,135941],[20395,57367],[57368,140525],[20488,57369],[57370,159017],[57371,162436],[57372,144896],[57373,150193],[57374,140563],0,[57376,131966],[24484,57377],[57378,131968],[57379,131911],[28379,57380],[57381,132127],20702,[20737,57383],[13434,57384],[20 [...]
+[39020,57386],[14147,57387],[33814,57388],[57389,149924],[57390,132231],[20832,57391],[57392,144308],[20842,57393],[57394,134143],[57395,139516],[57396,131813],[57397,140592],[57398,132494],[57399,143923],[57400,137603],[23426,57401],[34685,57402],[57403,132531],[57404,146585],[20914,57405],[20920,57406],[40244,57407],[20937,57408],[20943,57409],[20945,57410],[15580,57411],[20947,57412],[57413,150182],[20915,57414],0,0,[20973,57417],[33741,57418],[26942,57419],[57420,145197],[24443,57421 [...]
+57422],[21030,57423],[21052,57424],[21173,57425],[21079,57426],[21140,57427],[21177,57428],[21189,57429],[31765,57430],[34114,57431],[21216,57432],[34317,57433],[57434,158483],0,[57436,166622],[21833,57437],[28377,57438],[57439,147328],[57440,133460],[57441,147436],[21299,57442],0,[57444,134114],[27851,57445],[57446,136998],[26651,57447],[29653,57448],[24650,57449],[16042,57450],[14540,57451],[57452,136936],[29149,57453],[17570,57454],[21357,57455],[21364,57456],[57457,165547],[21374,574 [...]
+136598],[57461,136723],[30694,57462],[21395,57463],[57464,166555],[21408,57465],[21419,57466],[21422,57467],[29607,57468],[57469,153458],[16217,57470],[29596,57471],[21441,57472],[21445,57473],[27721,57474],[20041,57475],[22526,57476],[21465,57477],[15019,57478],[57479,134031],[21472,57480],[57481,147435],[57482,142755],[21494,57483],[57484,134263],[21523,57485],[28793,57486],[21803,57487],[26199,57488],[27995,57489],[21613,57490],[57491,158547],[57492,134516],[21853,57493],[21647,57494] [...]
+[18342,57496],[57497,136973],[57498,134877],[15796,57499],[57500,134477],[57501,166332],[57502,140952],[21831,57503],[19693,57504],[21551,57505],[29719,57506],[21894,57507],[21929,57508],0,[57510,137431],[57511,147514],[17746,57512],[57513,148533],[26291,57514],[57515,135348],[22071,57516],[26317,57517],[57518,144010],[26276,57519],0,[22093,57521],[22095,57522],[30961,57523],[22257,57524],[38791,57525],[21502,57526],[22272,57527],[22255,57528],[22253,57529],[57530,166758],[13859,57531],[ [...]
+[22342,57533],[57534,147877],[27758,57535],[28811,57536],[22338,57537],[14001,57538],[57539,158846],[22502,57540],[57541,136214],[22531,57542],[57543,136276],[57544,148323],[22566,57545],[57546,150517],0,[22698,57548],[13665,57549],[22752,57550],[22748,57551],[57552,135740],[22779,57553],[23551,57554],[22339,57555],[57556,172368],[57557,148088],[37843,57558],[13729,57559],[22815,57560],[26790,57561],[14019,57562],[28249,57563],[57564,136766],[23076,57565],0,[57567,136850],[34053,57568],[ [...]
+[57570,134478],[57571,158849],[57572,159018],[57573,137180],[23001,57574],[57575,137211],[57576,137138],[57577,159142],[28017,57578],[57579,137256],[57580,136917],[23033,57581],[57582,159301],[23211,57583],[23139,57584],[14054,57585],[57586,149929],0,[14088,57588],[23190,57589],[29797,57590],[23251,57591],[57592,159649],[57593,140628],[57595,137489],[14130,57596],[57597,136888],[24195,57598],[21200,57599],[23414,57600],[25992,57601],[23420,57602],[57603,162318],[16388,57604],[18525,57605 [...]
+131588],[23509,57607],[57609,137780],[57610,154060],[57611,132517],[23539,57612],[23453,57613],[19728,57614],[23557,57615],[57616,138052],[23571,57617],[29646,57618],[23572,57619],[57620,138405],[57621,158504],[23625,57622],[18653,57623],[23685,57624],[23785,57625],[23791,57626],[23947,57627],[57628,138745],[57629,138807],[23824,57630],[23832,57631],[23878,57632],[57633,138916],[23738,57634],[24023,57635],[33532,57636],[14381,57637],[57638,149761],[57639,139337],[57640,139635],[33415,576 [...]
+57642],[15298,57643],[24110,57644],[27274,57645],0,57647,[57648,148668],[57649,134355],[21414,57650],[20151,57651],0,[21416,57653],[57654,137073],[24073,57655],57656,[57657,164994],[24313,57658],[24315,57659],[14496,57660],[24316,57661],[26686,57662],[37915,57663],[24333,57664],[57665,131521],[57666,194708],[15070,57667],[57669,135994],[24378,57670],[57671,157832],[57672,140240],[57674,140401],[24419,57675],[57677,159342],[24434,57678],[37696,57679],[57680,166454],[24487,57681],[23990,57 [...]
+57683],[57684,152144],[57685,139114],[57686,159992],[57687,140904],[37334,57688],[57689,131742],[57690,166441],[24625,57691],[26245,57692],[14691,57694],[15815,57695],[13881,57696],[22416,57697],[57698,141236],[31089,57699],[15936,57700],[24734,57701],0,0,[57704,149890],[57705,149903],[57706,162387],[29860,57707],[20705,57708],[23200,57709],[24932,57710],[24898,57712],[57713,194726],[57714,159442],[24961,57715],[20980,57716],[57717,132694],[24967,57718],[23466,57719],[57720,147383],[5772 [...]
+[25043,57722],[57723,166813],[57724,170333],[25040,57725],[14642,57726],[57727,141696],[57728,141505],[24611,57729],[24924,57730],[25886,57731],[25483,57732],[57733,131352],[25285,57734],[57735,137072],[25301,57736],[57737,142861],[25452,57738],[57739,149983],[14871,57740],[25656,57741],[25592,57742],[57743,136078],[57744,137212],[28554,57746],[57747,142902],0,[57750,153373],[25825,57751],[25829,57752],[38011,57753],[14950,57754],[25658,57755],[14935,57756],[25933,57757],[28438,57758],[5 [...]
+[57760,150051],[25989,57761],[25965,57762],[25951,57763],0,[26037,57765],[57766,149824],[19255,57767],[26065,57768],[16600,57769],[57770,137257],57771,[26083,57772],[24543,57773],[57774,144384],[26136,57775],[57776,143863],[57777,143864],[26180,57778],[57779,143780],[57780,143781],[26187,57781],[57782,134773],[26215,57783],[57784,152038],[26227,57785],0,[57788,143921],[57789,165364],[57790,143816],[57791,152339],[30661,57792],[57793,141559],[39332,57794],[26370,57795],[57796,148380],[577 [...]
+[27130,57799],[57800,145346],0,[26471,57802],[26466,57803],[57804,147917],[57805,168173],[26583,57806],[17641,57807],[26658,57808],[28240,57809],[37436,57810],[26625,57811],[57812,144358],[57813,159136],[26717,57814],[57815,144495],[27105,57816],[27147,57817],[57818,166623],[26995,57819],[26819,57820],[57821,144845],[26881,57822],[26880,57823],[14849,57825],[57826,144956],[15232,57827],[26540,57828],[26977,57829],[57830,166474],[17148,57831],[26934,57832],[27032,57833],[15265,57834],[578 [...]
+[33635,57836],[20624,57837],[27129,57838],[57839,144985],[57840,139562],[27205,57841],[57842,145155],[27293,57843],[15347,57844],[26545,57845],[27336,57846],[57847,168348],[15373,57848],[27421,57849],[57850,133411],[24798,57851,60308],[27445,57852],[27508,57853],[57854,141261],[28341,57855],[57856,146139],0,[57858,137560],[14144,57859],[21537,57860],[57861,146266],[27617,57862],[57863,147196],[27612,57864],[27703,57865],[57866,140427],[57867,149745],[57868,158545],[27738,57869],[33318,57 [...]
+57871],[57872,146876],[17605,57873],[57874,146877],[57875,147876],[57876,149772],[57877,149760],[57878,146633],[14053,57879],[15595,57880],[57881,134450],[39811,57882],[57883,143865],[57884,140433],[32655,57885],[26679,57886],[57887,159013],[57888,159137],[57889,159211],[28054,57890],[27996,57891],[28284,57892],[28420,57893],[57894,149887],[57895,147589],[57896,159346],[34099,57897],[57898,159604],[20935,57899],0,0,[33838,57902],[57903,166689],0,[57905,146991],[29779,57906],[57907,147330 [...]
+57908],[28239,57909],[23185,57910],[57911,143435],[28664,57912],[14093,57913],[28573,57914],[57915,146992],[28410,57916],[57917,136343],[57918,147517],[17749,57919],[37872,57920],[28484,57921],[28508,57922],[15694,57923],[28532,57924],[57925,168304],[15675,57926],[28575,57927],[57928,147780],[28627,57929],[57930,147601],[57931,147797],[57932,147513],[57933,147440],[57934,147380],[57935,147775],[20959,57936],[57937,147798],[57938,147799],[57939,147776],[57940,156125],[28747,57941],[28798, [...]
+57943],0,[28876,57945],[28885,57946],[28886,57947],[28895,57948],[16644,57949],[15848,57950],[29108,57951],[29078,57952],[57953,148087],[28971,57954],[28997,57955],[23176,57956],[29002,57957],0,[57960,148325],[29007,57961],[37730,57962],[57963,148161],[28972,57964],[57965,148570],[57966,150055],[57967,150050],[29114,57968],[57969,166888],[28861,57970],[29198,57971],[37954,57972],[29205,57973],[22801,57974],[37955,57975],[29220,57976],[37697,57977],[57978,153093],[29230,57979],[29248,5798 [...]
+149876],[26813,57982],[29269,57983],[29271,57984],[15957,57985],[57986,143428],[26637,57987],[28477,57988],[29314,57989],0,[29483,57991],[57992,149539],[57993,165931],[18669,57994],[57995,165892],[29480,57996],[29486,57997],[29647,57998],[29610,57999],[58E3,134202],[58001,158254],[29641,58002],[29769,58003],[58004,147938],[58005,136935],[58006,150052],[26147,58007],[14021,58008],[58009,149943],[58010,149901],[58011,150011],[29687,58012],[29717,58013],[26883,58014],[58015,150054],[29753,5 [...]
+58018],0,[58020,141485],[29792,58021],[58022,167602],[29767,58023],[29668,58024],[29814,58025],[33721,58026],[29804,58027],[29812,58029],[37873,58030],[27180,58031],[29826,58032],[18771,58033],[58034,150156],[58035,147807],[58036,150137],[58037,166799],[23366,58038],[58039,166915],[58040,137374],[29896,58041],[58042,137608],[29966,58043],[29982,58045],[58046,167641],[58047,137803],[23511,58048],[58049,167596],[37765,58050],[30029,58051],[30026,58052],[30055,58053],[30062,58054],[58055,15 [...]
+58056],[58057,150803],[30094,58058],[29789,58059],[30110,58060],[30132,58061],[30210,58062],[30252,58063],[30289,58064],[30287,58065],[30319,58066],58067,[58068,156661],[30352,58069],[33263,58070],[14328,58071],[58072,157969],[58073,157966],[30369,58074],[30373,58075],[30391,58076],[30412,58077],[58078,159647],[33890,58079],[58080,151709],[58081,151933],[58082,138780],[30494,58083],[30502,58084],[30528,58085],[25775,58086],[58087,152096],[30552,58088],[58089,144044],[30639,58090],[58091, [...]
+166248],[58093,136897],[30708,58094],0,[26826,58098],[30895,58099],[30919,58100],[30931,58101],[38565,58102],[31022,58103],[58104,153056],[30935,58105],[31028,58106],[30897,58107],[58108,161292],[36792,58109],[34948,58110],[58113,140828],[31110,58114],[35072,58115],[26882,58116],[31104,58117],[58118,153687],[31133,58119],[58120,162617],[31036,58121],[31145,58122],[28202,58123],[58124,160038],[16040,58125],[31174,58126],[58127,168205],[31188,58128],0,[21797,62526],0,[62528,134210],[62529, [...]
+151851],[21904,62531],[62532,142534],[14828,62533],[62534,131905],[36422,62535],[62536,150968],[62537,169189],0,[62539,164030],[30586,62540],[62541,142392],[14900,62542],[18389,62543],[62544,164189],[62545,158194],[62546,151018],[25821,62547],[62548,134524],[62549,135092],[62550,134357],0,[25741,62552],[36478,62553],[62554,134806],0,[62556,135012],[62557,142505],[62558,164438],[62559,148691],0,[62561,134470],[62562,170573],[62563,164073],[18420,62564],[62565,151207],[62566,142530],[39602 [...]
+62568],[62569,169460],[16365,62570],[13574,62571],[62572,152263],[62573,169940],0,[62575,142660],[40302,62576],[38933,62577],0,[17369,62579],0,[25780,62581],[21731,62582],0,[62584,142282],0,[14843,62586],0,[62588,157402],[62589,157462],[62590,162208],[25834,62591],[62592,151634],[62593,134211],[36456,62594],0,[62596,166732],[62597,132913],0,[18443,62599],[62600,131497],[16378,62601],[22643,62602],[62603,142733],0,[62605,148936],[62606,132348],[62607,155799],[62608,134988],0,[21881,62610] [...]
+62612],0,[19124,62614],[62615,141926],[62616,135325],[33194,62617],[39157,62618],[62619,134556],[25465,62620],[14846,62621],[62622,141173],[36288,62623],[22177,62624],[25724,62625],[15939,62626],0,[62628,173569],[62629,134665],[62630,142031],0,0,[62633,135368],[62634,145858],[14738,62635],[14854,62636],[62637,164507],[13688,62638],[62639,155209],[62640,139463],0,0,[62643,142514],[62644,169760],[13500,62645],[27709,62646],[62647,151099],0,0,[62650,161140],[62651,142987],[62652,139784],[62 [...]
+[62654,167117],[62655,134778],[62656,134196],[62683,161337],[62684,142286],[62687,142417],[14872,62689],[62691,135367],[62693,173618],[62695,167122],[62696,167321],[62697,167114],[38314,62698],0,[62706,161630],[28992,62708],0,[20822,62385],0,[20616,62487],0,[13459,62489],[20870,62491],[24130,63037],[20997,62495],[21031,62436],[21113,62497],0,[13651,62504],[21442,62505],[21343,62715],0,[21823,62520],0,[21976,59986],[13789,62722],[22049,63067],0,[22100,60044],[60148,135291],0,[60153,135379 [...]
+135934],0,0,[14265,60104],[23745,61099],[23829,63066],[23894,63030],[14392,63036],[20097,62477],[24253,63038],[14612,63042],[25017,63050],[25232,63054],[25368,63056],[25690,63063],[25745,62381],[33133,62709],[33156,59922],[33171,59924],[26624,63080],[15292,63093],[29327,60517],[29389,59781],0,[29497,59785],[30018,59811],[30172,59817],[16320,59818],[60278,151205],[16343,59820],0,30336,[30348,59824,151388],[16552,59845],[30777,59846],[16643,59855],[31377,59863],[31771,59876],[31981,59884], [...]
+[32686,59892],0,[33535,59936],[22623,59981],[34482,59960],0,[34699,59963],[35143,59969],0,[35369,59972],0,[36465,59988],[60484,164233],[36528,59990],0,[37214,62443],[37260,62441],[39182,60051],[39196,60054],0,0,[39809,60066],[40384,60080],[40339,60078],[40620,60085],[19857,60540],0,37818,[40571,60084],[28809,63148],[29512,59788],0,[31129,59858],[36791,59997],0,[39234,60056],{s:193},8364,{s:4},[12443,63518],[12444,63519],[11904,63520],{f:5,c:62211},[62216,131340],62217,[62218,131281],[622 [...]
+{f:2,c:62220},[62222,131275],[62223,139240],62224,[62225,131274],{f:4,c:62226},[62230,131342],{f:2,c:62231},{f:2,c:62776},[62778,138177],[62779,194680],[12205,38737,62780],[62781,131206],[20059,62782],[20155,62783],[13630,62784],[23587,62785],[24401,62786],[24516,62787],[14586,62788],[25164,62789],[25909,62790],[27514,62791],[27701,62792],[27706,62793],[28780,62794],[29227,62795],[20012,62796],[29357,62797],[62798,149737],[32594,62799],[31035,62800],[31993,62801],[32595,62802],[62803,156 [...]
+62804],[62806,156491],[32770,62807],[32896,62808],[62809,157202],[62810,158033],[21341,62811],[34916,62812],[35265,62813],[62814,161970],[35744,62815],[36125,62816],[38021,62817],[38264,62818],[38271,62819],[38376,62820],[62821,167439],[38886,62822],[39029,62823],[39118,62824],[39134,62825],[39267,62826],[62827,17E4],[40060,62828],[40479,62829],[40644,62830],[27503,62831],[62832,63751],[20023,62833],[62834,131207],[38429,62835],[25143,62836],[38050,62837],[11908,63521],[11910,63522],[119 [...]
+[11912,63524],[11914,63525],[11916,63526],[11917,63527],[11925,63528],[11932,63529],[11941,63531],[11943,63532],[11946,63533],[11948,63534],[11950,63535],[11958,63536],[11964,63537],[11966,63538],[11978,63540],[11980,63541],[11981,63542],[11983,63543],[11990,63544],[11991,63545],[11998,63546],[62368,172969],[62369,135493],[25866,62371],[20029,62374],[28381,62375],[40270,62376],[37343,62377],[62380,161589],[20250,62382],[20264,62383],[20392,62384],[20852,62386],[20892,62387],[20964,62388] [...]
+[21160,62390],[21307,62391],[21326,62392],[21457,62393],[21464,62394],[22242,62395],[22768,62396],[22788,62397],[22791,62398],[22834,62399],[22836,62400],[23398,62401],[23454,62402],[23455,62403],[23706,62404],[24198,62405],[24635,62406],[25993,62407],[26622,62408],[26628,62409],[26725,62410],[27982,62411],[28860,62412],[30005,62413],[32420,62414],[32428,62415],[32442,62416],[32455,62417],[32463,62418],[32479,62419],[32518,62420],[32567,62421],[33402,62422],[33487,62423],[33647,62424],[3 [...]
+[35774,62426],[35810,62427],[36710,62428],[36711,62429],[36718,62430],[29713,62431],[31996,62432],[32205,62433],[26950,62434],[31433,62435],[30904,62442],[32956,62444],[36107,62446],[33014,62447],[62448,133607],[32927,62451],[40647,62452],[19661,62453],[40393,62454],[40460,62455],[19518,62456],[62457,171510],[62458,159758],[40458,62459],[62460,172339],[13761,62461],[28314,62463],[33342,62464],[29977,62465],[18705,62467],[39532,62468],[39567,62469],[40857,62470],[31111,62471],[62472,16497 [...]
+138698],[62474,132560],[62475,142054],[20004,62476],[20096,62478],[20103,62479],[20159,62480],[20203,62481],[20279,62482],[13388,62483],[20413,62484],[15944,62485],[20483,62486],[13437,62488],[13477,62490],[22789,62492],[20955,62493],[20988,62494],[20105,62496],[21136,62498],[21287,62499],[13767,62500],[21417,62501],[13649,62502],[21424,62503],[21539,62506],[13677,62507],[13682,62508],[13953,62509],[21651,62510],[21667,62511],[21684,62512],[21689,62513],[21712,62514],[21743,62515],[21784 [...]
+62517],[21800,62518],[13720,62519],[13733,62521],[13759,62522],[21975,62523],[13765,62524],[62525,163204],[16467,62538],[62551,135412],[62555,134155],[62574,161992],[62580,155813],[62583,142668],[62585,135287],[62587,135279],[62595,139681],[62609,134550],[16571,62611],[62631,142537],[22098,62641],[62642,134961],[62657,157724],[62659,135375],[62660,141315],[62661,141625],[13819,62662],[62663,152035],[62664,134796],[62665,135053],[62666,134826],[16275,62667],[62668,134960],[62669,134471],[ [...]
+[62671,134732],[62673,134827],[62674,134057],[62675,134472],[62676,135360],[62677,135485],[16377,62678],[62679,140950],[25650,62680],[62681,135085],[62682,144372],[62685,134526],[62686,134527],[62688,142421],[62690,134808],[62692,134958],[62694,158544],[21708,62699],[33476,62700],[21945,62701],[62703,171715],[39974,62704],[39606,62705],[62707,142830],[33004,62710],[23580,62711],[62712,157042],[33076,62713],[14231,62714],[62716,164029],[37302,62717],[62718,134906],[62719,134671],[62720,13 [...]
+134907],[62723,151019],[13833,62724],[62725,134358],[22191,62726],[62727,141237],[62728,135369],[62729,134672],[62730,134776],[62731,135288],[62732,135496],[62733,164359],[62734,136277],[62735,134777],[62736,151120],[62737,142756],[23124,62738],[62739,135197],[62740,135198],[62741,135413],[62742,135414],[22428,62743],[62744,134673],[62745,161428],[62746,164557],[62747,135093],[62748,134779],[62749,151934],[14083,62750],[62751,135094],[62752,135552],[62753,152280],[62754,172733],[62755,14 [...]
+137274],[62757,147831],[62758,164476],[22681,62759],[21096,62760],[13850,62761],[62762,153405],[31666,62763],[23400,62764],[18432,62765],[19244,62766],[40743,62767],[18919,62768],[39967,62769],[39821,62770],[62771,154484],[62772,143677],[22011,62773],[13810,62774],[22153,62775],[23870,63028],[23880,63029],[15868,63031],[14351,63032],[23972,63033],[23993,63034],[14368,63035],[24357,63039],[24451,63040],[14600,63041],[14655,63043],[14669,63044],[24791,63045],[24893,63046],[23781,63047],[14 [...]
+[25015,63049],[25039,63051],[14776,63052],[25132,63053],[25317,63055],[14840,63057],[22193,63058],[14851,63059],[25570,63060],[25595,63061],[25607,63062],[14923,63064],[25792,63065],[40863,63068],[14999,63069],[25990,63070],[15037,63071],[26111,63072],[26195,63073],[15090,63074],[26258,63075],[15138,63076],[26390,63077],[15170,63078],[26532,63079],[15192,63081],[26698,63082],[26756,63083],[15218,63084],[15217,63085],[15227,63086],[26889,63087],[26947,63088],[29276,63089],[26980,63090],[2 [...]
+[27013,63092],[27094,63094],[15325,63095],[27237,63096],[27252,63097],[27249,63098],[27266,63099],[15340,63100],[27289,63101],[15346,63102],[27307,63103],[27317,63104],[27348,63105],[27382,63106],[27521,63107],[27585,63108],[27626,63109],[27765,63110],[27818,63111],[15563,63112],[27906,63113],[27910,63114],[27942,63115],[28033,63116],[15599,63117],[28068,63118],[28081,63119],[28181,63120],[28184,63121],[28201,63122],[28294,63123],[63124,166336],[28347,63125],[28386,63126],[28378,63127],[ [...]
+[28392,63129],[28393,63130],[28452,63131],[28468,63132],[15686,63133],[63134,147265],[28545,63135],[28606,63136],[15722,63137],[15733,63138],[29111,63139],[23705,63140],[15754,63141],[28716,63142],[15761,63143],[28752,63144],[28756,63145],[28783,63146],[28799,63147],[63149,131877],[17345,63150],[13809,63151],[63152,134872],[13902,58134],[15789,58172],[58173,154725],[26237,58183],[31860,58188],[29837,58197],[32402,58215],[17667,58232],[58260,151480],[58270,133901],[58277,158474],[13438,58 [...]
+143087],[58325,146613],[58343,159385],[34673,58364],[25537,58385],[30583,58387],[35210,58390],[58406,147343],[35660,58415],[58440,150729],[18730,58464],[58471,172052],[58472,165564],[58473,165121],[15088,58490],[28815,58511],[58529,140922],[58637,158120],[58646,148043],[26760,58662],[58664,139611],[40802,58702],[37830,58793],[58802,131967],[37734,58888],[37519,58901],[34324,58954],[58986,173147],[16784,59010],[26511,59045],[26654,59048],[14435,59051],[59077,149996],[15129,59128],[33942,5 [...]
+149858],[14818,59254],[33920,59259],[17262,59328],[38769,59402],[39323,59427],[18733,59499],[28439,59703],[59704,160009],[28838,59746],[59752,150095],[32357,59753],[23855,59755],[15859,59756],[59758,150109],[59759,137183],[32164,59760],[33830,59761],[21637,59762],[59763,146170],[59765,131604],[22398,59766],[59767,133333],[59768,132633],[16357,59769],[59770,139166],[59771,172726],[28675,59772],[59773,168283],[23920,59774],[29583,59775],[59777,166489],[59778,168992],[20424,59779],[32743,59 [...]
+59782],[29496,59784],[29505,59787],[16041,59789],[29173,59792],[59793,149746],[29665,59794],[16074,59796],[16081,59798],[29721,59801],[29726,59802],[29727,59803],[16098,59804],[16112,59805],[16116,59806],[16122,59807],[29907,59808],[16142,59809],[16211,59810],[30061,59812],[30066,59813],[30093,59814],[16252,59815],[30152,59816],[30285,59819],[30324,59821],[16348,59822],[30330,59823],[29064,59825],[22051,59826],[35200,59827],[16413,59829],[30531,59830],[16441,59831],[16453,59833],[13787,5 [...]
+59835],[16490,59836],[16495,59837],[30654,59839],[30667,59840],[30744,59842],[30748,59844],[30791,59847],[30801,59848],[30822,59849],[33864,59850],[59851,152885],[31027,59852],[31026,59854],[16649,59856],[31121,59857],[31238,59860],[16743,59862],[16818,59864],[31420,59865],[33401,59866],[16836,59867],[31439,59868],[31451,59869],[16847,59870],[31586,59872],[31596,59873],[31611,59874],[31762,59875],[16992,59877],[17018,59878],[31867,59879],[31900,59880],[17036,59881],[31928,59882],[17044,5 [...]
+59885],[28864,59886],[59887,134351],[32207,59888],[32212,59889],[32208,59890],[32253,59891],[32692,59893],[29343,59894],[17303,59895],[32800,59896],[32805,59897],[32814,59899],[32817,59900],[32852,59901],[22452,59903],[28832,59904],[32951,59905],[33001,59906],[17389,59907],[33036,59908],[33038,59910],[33042,59911],[33044,59913],[17409,59914],[15161,59915],[33110,59916],[33113,59917],[33114,59918],[17427,59919],[33148,59921],[17445,59923],[17453,59925],[33189,59926],[22511,59927],[33217,5 [...]
+59929],[33364,59930],[17551,59931],[33398,59933],[33482,59934],[33496,59935],[17584,59937],[33623,59938],[38505,59939],[33797,59941],[28917,59942],[33892,59943],[33928,59945],[17668,59946],[33982,59947],[34017,59948],[34040,59949],[34064,59950],[34104,59951],[34130,59952],[17723,59953],[34159,59954],[34160,59955],[34272,59956],[17783,59957],[34418,59958],[34450,59959],[34543,59961],[38469,59962],[17926,59964],[17943,59965],[34990,59966],[35071,59967],[35108,59968],[35217,59970],[59971,16 [...]
+59973],[35476,59974],[35508,59975],[35921,59976],[36052,59977],[36082,59978],[36124,59979],[18328,59980],[36291,59982],[18413,59983],[36410,59985],[22356,59987],[22005,59989],[18487,59991],[36558,59992],[36578,59993],[36580,59994],[36589,59995],[36594,59996],[36801,59998],[36810,59999],[36812,6E4],[36915,60001],[18605,60003],[39136,60004],[37395,60005],[18718,60006],[37416,60007],[37464,60008],[37483,60009],[37553,60010],[37550,60011],[37567,60012],[37603,60013],[37611,60014],[37619,6001 [...]
+60016],[37629,60017],[37699,60018],[37764,60019],[37805,60020],[18757,60021],[18769,60022],[37911,60024],[37917,60026],[37933,60027],[37950,60028],[18794,60029],[37972,60030],[38009,60031],[38189,60032],[38306,60033],[18855,60034],[38388,60035],[38451,60036],[18917,60037],[18980,60039],[38720,60040],[18997,60041],[38834,60042],[38850,60043],[19172,60045],[39097,60047],[19225,60048],[39153,60049],[22596,60050],[39193,60052],[39223,60055],[39261,60057],[39266,60058],[19312,60059],[39365,60 [...]
+60061],[39484,60062],[39695,60063],[39785,60065],[39901,60067],[39921,60068],[39924,60069],[19565,60070],[39968,60071],[14191,60072],[60073,138178],[40265,60074],[40702,60076],[22096,60077],[40381,60079],[40444,60081],[38134,60082],[36790,60083],[40625,60086],[40637,60087],[40646,60088],[38108,60089],[40674,60090],[40689,60091],[40696,60092],[40772,60094],[60095,131220],[60096,131767],[60097,132E3],[38083,60099],[60101,132311],[38081,60103],[60105,132565],[60106,132629],[60107,132726],[6 [...]
+[22359,60109],[29043,60110],[60111,133826],[60112,133837],[60113,134079],[60115,194619],[60116,134091],[21662,60117],[60118,134139],[60119,134203],[60120,134227],[60121,134245],[60122,134268],[60124,134285],[60126,134325],[60127,134365],[60128,134381],[60129,134511],[60130,134578],[60131,134600],[60135,134660],[60136,134670],[60137,134871],[60138,135056],[60139,134957],[60140,134771],[60142,135100],[60144,135260],[60145,135247],[60146,135286],[60149,135304],[60150,135318],[13895,60151],[ [...]
+[60154,135471],[60155,135483],[21348,60156],[60158,135907],[60159,136053],[60160,135990],[60162,136567],[60163,136729],[60164,137155],[60165,137159],[28859,60167],[60168,137261],[60169,137578],[60170,137773],[60171,137797],[60172,138282],[60173,138352],[60174,138412],[60175,138952],[60177,138965],[60178,139029],[29080,60179],[60181,139333],[27113,60182],[14024,60183],[60184,139900],[60185,140247],[60186,140282],[60187,141098],[60188,141425],[60189,141647],[60191,141671],[60192,141715],[6 [...]
+[60195,142056],[60197,142094],[60199,142143],[60202,142412],[60204,142472],[60205,142519],[60206,154600],[60207,142600],[60208,142610],[60209,142775],[60210,142741],[60211,142914],[60212,143220],[60213,143308],[60214,143411],[60215,143462],[60216,144159],[60217,144350],[60222,144743],[60223,144883],[60227,144922],[60228,145174],[22709,60231],[60234,146087],[60237,146961],[60238,147129],[60243,147737],[60245,148206],[60246,148237],[60248,148276],[60249,148374],[60258,148484],[60259,148694 [...]
+60260],[60261,149108],[60263,149295],[60271,149522],[60272,149755],[60273,150037],[60275,150208],[22885,60277],[60279,151430],[60282,151596],[22335,60284],[60286,152217],[60287,152601],[60291,152646],[60292,152686],[60296,152895],[60298,152926],[60300,152930],[60301,152934],[60302,153543],[60304,153693],[60309,153859],[60312,154286],[60313,154505],[60314,154630],[22433,60316],[29009,60317],[60319,155906],[60322,156082],[60325,156674],[60326,156746],[60330,156804],[60334,156808],[60336,15 [...]
+157119],[60339,157365],[22201,60347],[60349,157436],[13848,60355],[60357,157593],[60358,157806],[60360,157790],[60362,157895],[60366,157990],[60368,158009],[60371,158202],[60373,158253],[60378,158260],[60379,158555],[60383,158621],[60385,158884],[60388,159150],[60392,159819],[60393,160205],[60395,160384],[60396,160389],[60399,160395],[60401,160486],[38047,60404],[60405,160848],[14009,60416],[60424,161740],[60425,161880],[22230,60426],[60435,162269],[60441,162301],[60442,162314],[60443,16 [...]
+163174],[60448,163849],[60459,163875],[60463,163912],[60466,163971],[60479,163984],[60480,164084],[60481,164142],[60483,164175],[60485,164271],[60486,164378],[60487,164614],[60488,164655],[60489,164746],[60491,164968],[60492,165546],[25574,60494],[60495,166230],[60498,166328],[60500,166375],[60502,166376],[60503,166726],[60504,166868],[60506,166921],[60508,167877],[60509,168172],[60511,168208],[60512,168252],[15863,60513],[60514,168286],[60515,150218],[36816,60516],[60519,169191],[60521, [...]
+169400],[60523,169778],[60524,170193],[60525,170313],[60526,170346],[60527,170435],[60528,170536],[60529,170766],[60530,171354],[60531,171419],[32415,60532],[60533,171768],[60534,171811],[19620,60535],[38215,60536],[60537,172691],[29090,60538],[60539,172799],[60542,173515],[19868,60543],[60544,134300],[36798,60545],[36794,60547],[60548,140464],[36793,60549],[60550,150163],[20202,60555],[60557,166700],[36480,60560],[60561,137205],[60563,166764],[60564,166809],[60566,157359],[60568,161365] [...]
+[60571,153942],[20122,60572],[60573,155265],[60576,134765],[60579,147080],[60580,150686],[60583,137206],[60584,137339],[60587,154698],[60589,152337],[15814,60590],[60596,155352],[19996,60600],[60601,135146],[60602,134473],[60603,145082],[60638,151880],[21982,60644],[34694,60672],[60676,135361],[60679,149254],[23440,60680],[60682,157843],[60684,141044],[60685,163119],[60686,147875],[60687,163187],[60688,159440],[60689,160438],[60691,135641],[60693,146684],[60694,173737],[60695,134828],[60 [...]
+[60700,151490],[60702,135147],[60706,142752],[60710,135148],[60711,134666],[60714,135149],[60717,135559],[19994,60721],[19972,60722],[23309,60724],[13996,60727],[21373,60729],[13989,60730],[22682,60732],[60733,150382],[22442,60736],[60737,154261],[60738,133497],[60741,140389],[60746,146686],[60747,171824],[60749,151465],[60750,169374],[60753,146870],[60755,157619],[60756,145184],[60759,147191],[60760,146988],[60785,143578],[60789,135849],[22439,60790],[60791,149859],[60794,159918],[60801 [...]
+[60806,160100],[60809,159010],[60810,150242],[39963,60837],[60838,149822],[15878,60846],[60881,159011],[60887,132092],[60891,146685],[60893,149785],[22394,60904],[21722,60912],[29050,60928],[60949,150135],[60955,166490],[60962,194624],[60976,137275],[61E3,155993],[61014,144373],[61019,166850],[61024,138566],[61054,159441],[13877,61065],[61084,166701],[21024,61088],[15384,61089],[61090,146631],[61091,155351],[61092,161366],[61093,152881],[61094,137540],[61096,170243],[61097,159196],[61098 [...]
+[61100,156077],[61101,166415],[61102,145015],[61103,131310],[61104,157766],[61105,151310],[17762,61106],[23327,61107],[61108,156492],[40784,61109],[40614,61110],[61111,156267],[20962,57415],[21314,57416],[26285,57520],[22620,57547],[21843,57566],[15749,57594],[24928,57608],[18606,57668],[38845,57676],[57693,137335],[24755,57703],[33828,57711],[38932,57748],[57749,147596],[57764,143486],[57787,138813],[15147,57798],[15666,57824],[57857,132021],[28801,57944],[23708,57959],[58017,132547],[1 [...]
+[58096,136054],[58097,150034],[58111,166699],[58112,155779],[256,62233],[193,62234],[461,62235],[192,62236],[274,62237],[201,62238],[282,62239],[200,62240],[332,62241],[211,62242],[465,62243],[210,62244],62245,[7870,62246],62247,[7872,62248],[202,62249],[257,62250],[225,62251],[462,62252],[224,62253],[593,62254],[275,62255],[233,62256],[283,62257],[232,62258],[299,62259],[237,62260],[464,62261],[236,62262],[333,62263],[243,62264],[466,62265],[242,62266],[363,62267],[250,62268],[468,62269 [...]
+[470,62271],[472,62272],[474,62273],[476,62274],[252,62275],62276,[7871,62277],62278,[7873,62279],[234,62280],[609,62281],[643,63551],[592,63552],[603,63553],[596,63554],[629,63555],[339,63556],[248,63557],[331,63558],[650,63559],[618,63560],{f:2,c:62282},[11933,63530],[11974,63539],[12003,63547],20539,28158,[62841,171123],62842,[15817,62843],34959,[62845,147790],28791,23797,[19232,62848],[62849,152013],[13657,62850],[62851,154928],24866,[62853,166450],36775,37366,29073,26393,29626,[6285 [...]
+[62860,172295],[15499,62861],[62862,137600],[19216,62863],30948,29698,20910,[62867,165647],[16393,62868],27235,[62870,172730],[16931,62871],34319,31274,[62875,170311],[62876,166634],38741,28749,21284,[62880,139390],37876,30425,[62883,166371],62884,30685,20131,20464,20668,20015,20247,62891,21556,32139,22674,22736,[62896,138678],24210,24217,24514,[62900,141074],25995,[62902,144377],26905,27203,[62905,146531],27903,29184,[62909,148741],29580,[16091,62911],[62912,150035],23317,29881,35715,[6 [...]
+[62917,153237],31379,31724,31939,32364,33528,34199,62924,34960,62926,36537,62928,36815,34143,39392,37409,62933,[62934,167353],[62935,136255],[16497,62936],[17058,62937],23066,39016,26475,[17014,62944],22333,34262,[62948,149883],33471,[62950,160013],[19585,62951],[62952,159092],23931,[62954,158485],[62955,159678],{f:2,c:62956},23446,62959,32347],"Adobe-GB1":[{f:95,c:32},{f:3,c:12288},[183,12539],713,711,168,12291,12293,8212,65374,8214,[8230,8943],{f:2,c:8216},{f:2,c:8220},{f:2,c:12308},{f [...]
+{f:2,c:12310},{f:2,c:12304},177,215,247,8758,{f:2,c:8743},8721,8719,8746,8745,8712,8759,8730,8869,8741,8736,8978,8857,8747,8750,8801,8780,8776,8765,8733,8800,{f:2,c:8814},{f:2,c:8804},8734,8757,8756,9794,9792,176,{f:2,c:8242},8451,65284,164,{f:2,c:65504},8240,167,8470,9734,9733,9675,9679,9678,9671,9670,9633,9632,9651,9650,8251,8594,{f:2,c:8592},8595,12307,{f:20,c:9352},{f:20,c:9332},{f:10,c:9312},{f:10,c:12832},{f:12,c:8544},{f:3,c:65281},65509,{f:89,c:65285},65507,{f:83,c:12353},{f:86,c [...]
+c:913},{f:7,c:931},{f:17,c:945},{f:7,c:963},{f:7,c:59277},{f:2,c:65077},{f:2,c:65081},{f:2,c:65087},{f:2,c:65085},{f:4,c:65089},{f:2,c:59284},{f:2,c:65083},{f:2,c:65079},65073,59286,{f:2,c:65075},{f:6,c:1040},1025,{f:32,c:1046},1105,{f:26,c:1078},257,225,462,224,275,233,283,232,299,237,464,236,333,243,466,242,363,250,468,249,470,472,474,476,252,234,593,7743,324,328,505,609,{f:37,c:12549},0,{f:76,c:9472},{s:126},21834,38463,22467,25384,21710,21769,21696,30353,30284,34108,30702,33406,30861 [...]
+38797,27688,23433,20474,25353,26263,23736,33018,26696,32942,26114,30414,20985,25942,29100,32753,34948,20658,22885,25034,28595,33453,25420,25170,21485,21543,31494,[12043,20843],30116,24052,25300,36299,38774,25226,32793,22365,38712,32610,29240,[12137,30333],26575,30334,25670,20336,36133,25308,31255,26001,29677,25644,25203,33324,39041,26495,29256,25198,25292,20276,29923,21322,21150,32458,37030,24110,26758,27036,33152,32465,26834,30917,34444,38225,20621,35876,33502,32990,21253,35090,21093,34 [...]
+20445,22561,39281,23453,25265,25253,26292,35961,40077,29190,26479,30865,24754,21329,21271,36744,32972,36125,38049,20493,29384,22791,24811,28953,34987,22868,33519,26412,31528,23849,32503,29997,27893,36454,36856,36924,[12240,40763],[12112,27604],37145,31508,24444,30887,34006,34109,27605,27609,27606,24065,24199,30201,38381,25949,24330,24517,36767,22721,33218,36991,38491,38829,36793,32534,36140,25153,20415,21464,21342,{f:2,c:36776},36779,36941,26631,24426,33176,34920,40150,24971,21035,30250, [...]
+28626,28392,23486,25672,20853,20912,26564,19993,31177,39292,28851,30149,24182,29627,33760,25773,25320,38069,27874,21338,21187,25615,38082,31636,20271,24091,33334,33046,33162,28196,27850,39539,25429,[12056,21340],21754,34917,22496,19981,24067,27493,31807,37096,24598,25830,29468,35009,26448,25165,36130,30572,36393,37319,24425,33756,34081,39184,21442,34453,27531,24813,24808,28799,33485,33329,20179,27815,34255,25805,31961,27133,26361,33609,21397,31574,20391,20876,27979,23618,36461,25554,2144 [...]
+26597,30900,25661,23519,23700,24046,35815,25286,26612,35962,25600,25530,34633,39307,35863,32544,38130,20135,38416,39076,26124,29462,22330,23581,24120,38271,20607,32928,[12058,21378],25950,30021,21809,20513,36229,25220,38046,26397,22066,28526,24034,21557,28818,36710,25199,25764,25507,24443,28552,37108,[12162,33251],[12192,36784],23576,26216,24561,27785,38472,36225,34924,25745,31216,22478,27225,25104,21576,20056,31243,24809,28548,35802,25215,36894,39563,31204,21507,30196,25345,21273,27744, [...]
+39536,32827,40831,20360,23610,[12186,36196],32709,26021,28861,20805,20914,[12173,34411],23815,23456,25277,37228,30068,36364,31264,24833,31609,20167,32504,30597,19985,33261,21021,20986,27249,21416,36487,38148,38607,28353,38500,26970,30784,20648,30679,25616,35302,22788,25571,24029,31359,26941,20256,33337,21912,20018,30126,31383,24162,24202,38383,21019,21561,28810,25462,38180,22402,26149,26943,37255,21767,28147,32431,34850,25139,32496,30133,33576,30913,38604,36766,24904,29943,35789,27492,21 [...]
+27425,32874,33905,22257,21254,20174,19995,20945,31895,37259,31751,20419,36479,31713,31388,25703,23828,20652,33030,30209,31929,28140,32736,26449,23384,[12072,23544],30923,25774,25619,25514,25387,38169,25645,36798,31572,30249,25171,[12068,22823],21574,[12109,27513],20643,25140,24102,27526,20195,36151,34955,24453,36910,24608,32829,25285,20025,21333,37112,25528,32966,26086,27694,20294,24814,28129,35806,24377,34507,24403,25377,20826,33633,26723,[12049,20992],25443,36424,20498,23707,31095,2354 [...]
+24764,36947,30423,24503,24471,30340,36460,28783,30331,31561,30634,20979,37011,22564,20302,28404,36842,25932,31515,29380,28068,32735,23265,25269,24213,22320,33922,31532,24093,24351,36882,32532,39072,25474,28359,30872,28857,20856,38747,22443,30005,20291,30008,24215,24806,22880,28096,27583,30857,21500,38613,20939,20993,25481,21514,38035,35843,36300,29241,30879,34678,36845,35853,21472,19969,30447,21486,38025,39030,[12237,40718],38189,23450,35746,20002,19996,20908,33891,25026,21160,26635,2037 [...]
+27934,20828,25238,[12099,26007],38497,[12182,35910],36887,30168,37117,30563,27602,29322,29420,35835,22581,30585,36172,26460,38208,32922,24230,28193,22930,31471,30701,38203,27573,26029,32526,22534,20817,38431,23545,22697,21544,36466,25958,39039,22244,38045,30462,36929,25479,21702,22810,22842,22427,36530,26421,36346,33333,21057,24816,22549,34558,23784,40517,20420,39069,35769,23077,24694,21380,25212,36943,37122,39295,24681,[12157,32780],[12041,20799],[12159,32819],23572,39285,27953,[12038,2 [...]
+21457,32602,31567,20240,20047,38400,27861,29648,34281,24070,30058,32763,27146,30718,38034,32321,20961,28902,21453,36820,33539,36137,29359,39277,27867,22346,33459,[12101,26041],32938,25151,38450,22952,20223,35775,32442,25918,33778,[12206,38750],21857,39134,32933,21290,35837,21536,32954,24223,27832,36153,33452,37210,21545,27675,20998,32439,22367,28954,27774,31881,22859,20221,24575,24868,31914,20016,23553,26539,34562,23792,38155,39118,30127,28925,36898,20911,32541,35773,22857,20964,20315,21 [...]
+25975,32932,23413,25206,25282,36752,24133,27679,31526,20239,20440,26381,28014,28074,31119,34993,24343,29995,25242,36741,20463,37340,26023,33071,33105,24220,33104,36212,21103,35206,36171,22797,20613,20184,[12201,38428],[12119,29238],33145,36127,23500,35747,38468,22919,32538,21648,22134,22030,35813,25913,27010,38041,30422,28297,[12082,24178],[12130,29976],26438,26577,31487,32925,36214,24863,31174,25954,36195,20872,21018,38050,32568,32923,32434,23703,28207,26464,31705,30347,[12220,39640],33 [...]
+31957,25630,38224,31295,21578,21733,27468,25601,[12093,25096],40509,33011,30105,21106,[12208,38761],33883,26684,34532,38401,38548,38124,20010,21508,32473,26681,36319,32789,26356,24218,32697,22466,32831,26775,[12079,24037],25915,21151,24685,40858,20379,36524,20844,23467,[12088,24339],24041,27742,25329,36129,20849,38057,21246,27807,33503,29399,22434,26500,36141,22815,36764,33735,21653,31629,20272,27837,23396,22993,[12238,40723],21476,34506,[12219,39592],[12181,35895],32929,25925,39038,2226 [...]
+[12128,29916],21072,23521,25346,35074,20054,25296,24618,26874,20851,23448,20896,35266,31649,39302,32592,24815,28748,36143,20809,[12084,24191],36891,29808,35268,22317,30789,24402,40863,38394,36712,[12225,39740],35809,30328,26690,26588,36330,36149,21053,36746,28378,26829,38149,37101,22269,26524,35065,36807,21704,39608,23401,28023,27686,20133,23475,39559,37219,25E3,37039,38889,21547,28085,23506,20989,21898,32597,32752,25788,25421,26097,25022,24717,28938,27735,27721,22831,26477,33322,22741,2 [...]
+27627,37085,22909,32791,21495,28009,21621,21917,33655,33743,26680,[12146,31166],21644,20309,21512,30418,35977,38402,27827,28088,36203,35088,40548,36154,22079,[12234,40657],30165,24456,29408,24680,21756,20136,27178,34913,24658,36720,21700,28888,34425,40511,27946,23439,24344,32418,21897,20399,29492,21564,21402,20505,21518,21628,20046,24573,29786,22774,33899,32993,34676,29392,31946,28246,24359,34382,21804,25252,20114,27818,25143,33457,21719,21326,29502,28369,30011,21010,21270,35805,27088,24 [...]
+28142,22351,27426,29615,26707,36824,32531,25442,24739,21796,30186,35938,28949,28067,23462,24187,33618,24908,40644,30970,34647,31783,30343,20976,24822,29004,26179,24140,24653,35854,28784,25381,36745,24509,24674,34516,22238,27585,24724,24935,21321,24800,26214,36159,31229,20250,28905,27719,35763,35826,32472,33636,26127,23130,39746,27985,28151,35905,27963,20249,[12117,28779],33719,25110,24785,38669,36135,31096,20987,22334,22522,26426,30072,31293,31215,31637,32908,39269,36857,28608,35749,4048 [...]
+32521,21513,26497,26840,36753,31821,38598,21450,24613,30142,27762,21363,23241,32423,25380,[12047,20960],33034,[12080,24049],34015,25216,20864,23395,20238,31085,21058,24760,27982,23492,23490,35745,35760,26082,24524,38469,22931,32487,32426,22025,26551,22841,20339,23478,21152,33626,39050,36158,30002,38078,20551,31292,20215,26550,39550,23233,27516,30417,22362,23574,31546,38388,29006,20860,32937,33392,22904,32516,33575,26816,26604,30897,30839,25315,25441,31616,20461,21098,20943,33616,27099,37 [...]
+36145,35265,38190,31661,20214,20581,33328,21073,39279,28176,28293,28071,24314,20725,23004,23558,27974,27743,30086,33931,26728,22870,35762,21280,37233,38477,34121,26898,30977,28966,33014,20132,37066,27975,39556,23047,22204,25605,38128,30699,20389,33050,29409,[12179,35282],39290,32564,32478,21119,25945,37237,36735,36739,21483,31382,25581,25509,30342,31224,34903,38454,25130,21163,33410,26708,26480,25463,30571,31469,27905,32467,35299,22992,25106,34249,33445,30028,20511,20171,30117,35819,2362 [...]
+24062],31563,[12100,26020],[12198,37329],20170,27941,35167,32039,38182,20165,35880,36827,38771,26187,31105,36817,28908,28024,23613,21170,33606,20834,33550,30555,26230,40120,20140,24778,31934,31923,32463,20117,35686,26223,39048,38745,22659,25964,38236,24452,30153,38742,31455,31454,20928,28847,31384,25578,31350,32416,29590,[12210,38893],20037,28792,20061,37202,21417,25937,26087,[12165,33276],33285,21646,23601,30106,38816,25304,29401,30141,23621,39545,33738,23616,21632,30697,20030,27822,328 [...]
+25454,24040,20855,36317,36382,38191,20465,21477,24807,28844,21095,25424,40515,23071,20518,30519,21367,32482,25733,25899,25225,25496,20500,29237,35273,20915,35776,32477,22343,33740,38055,20891,21531,23803,20426,31459,27994,37089,39567,21888,21654,21345,21679,24320,25577,26999,20975,24936,21002,22570,21208,22350,30733,30475,24247,24951,31968,25179,25239,20130,28821,32771,25335,28900,38752,22391,33499,26607,26869,30933,39063,31185,22771,21683,21487,28212,20811,21051,23458,35838,32943,21827, [...]
+22353,21549,31354,24656,23380,25511,25248,[12061,21475],25187,23495,26543,21741,31391,33510,37239,24211,35044,22840,22446,25358,36328,33007,22359,31607,20393,24555,23485,27454,21281,31568,29378,26694,30719,30518,26103,20917,20111,30420,23743,31397,33909,22862,39745,20608,39304,24871,28291,22372,26118,25414,22256,25324,25193,24275,38420,22403,25289,21895,34593,33098,36771,21862,33713,26469,36182,34013,23146,26639,25318,31726,38417,20848,28572,35888,25597,35272,25042,32518,28866,28389,2970 [...]
+24266,37070,26391,28010,25438,21171,29282,[12156,32769],20332,23013,37226,28889,28061,21202,20048,38647,38253,34174,30922,32047,20769,22418,25794,32907,31867,27882,26865,26974,20919,21400,26792,29313,40654,31729,29432,31163,28435,29702,26446,[12197,37324],40100,31036,33673,33620,21519,26647,20029,21385,21169,30782,21382,21033,20616,20363,20432,30178,[12148,31435],31890,27813,[12202,38582],[12050,21147],29827,21737,20457,32852,33714,36830,38256,24265,24604,28063,24088,25947,33080,38142,24 [...]
+32451,31918,20937,26753,31921,33391,20004,36742,37327,26238,20142,35845,25769,32842,20698,30103,29134,23525,36797,28518,20102,25730,38243,24278,26009,21015,35010,28872,21155,29454,29747,26519,30967,38678,20020,37051,40158,28107,20955,36161,21533,25294,29618,33777,38646,40836,38083,20278,32666,20940,28789,38517,23725,39046,21478,20196,28316,29705,27060,30827,39311,30041,21016,30244,27969,26611,20845,40857,32843,21657,31548,31423,38534,22404,25314,38471,27004,23044,25602,31699,28431,38475, [...]
+39045,24208,28809,25523,21348,34383,40065,40595,30860,38706,36335,36162,[12229,40575],28510,31108,24405,38470,25134,39540,21525,38109,20387,26053,23653,23649,32533,34385,27695,24459,29575,28388,32511,23782,25371,23402,28390,21365,20081,25504,30053,25249,36718,20262,20177,27814,32438,35770,33821,34746,32599,36923,38179,31657,39585,35064,33853,27931,39558,32476,22920,[12231,40635],29595,30721,34434,39532,39554,22043,21527,22475,20080,40614,21334,36808,33033,30610,39314,34542,28385,34067,26 [...]
+28459,35881,33426,33579,30450,27667,24537,33725,29483,33541,38170,[12113,27611],[12141,30683],38086,21359,33538,20882,24125,35980,36152,20040,29611,26522,26757,37238,38665,29028,27809,30473,23186,38209,27599,32654,26151,23504,22969,23194,38376,38391,20204,33804,33945,27308,30431,38192,29467,26790,23391,30511,37274,38753,31964,36855,35868,24357,[12150,31859],31192,35269,27852,34588,23494,24130,26825,30496,32501,20885,20813,21193,23081,32517,[12207,38754],33495,25551,30596,34256,31186,2821 [...]
+34065,28781,27665,25279,[12139,30399],25935,24751,38397,26126,34719,40483,38125,21517,21629,35884,{f:2,c:25720},34321,27169,33180,30952,25705,39764,25273,26411,33707,22696,40664,27819,28448,23518,38476,35851,29279,26576,25287,29281,20137,22982,27597,22675,26286,24149,21215,24917,[12106,26408],[12140,30446],30566,29287,31302,25343,21738,21584,38048,37027,23068,32435,27670,20035,22902,32784,22856,21335,30007,38590,22218,25376,33041,24700,38393,28118,21602,39297,20869,23273,33021,22958,3867 [...]
+23612,25311,20320,21311,33147,36870,28346,34091,25288,24180,30910,25781,25467,24565,23064,37247,40479,23615,25423,32834,23421,21870,38218,38221,28037,24744,26592,29406,20957,23425,25319,27870,[12124,29275],25197,38062,32445,33043,27987,20892,24324,22900,21162,24594,[12069,22899],26262,34384,30111,25386,25062,31983,35834,21734,27431,40485,27572,34261,21589,20598,27812,21866,36276,29228,24085,24597,29750,25293,25490,29260,24472,28227,27966,25856,28504,30424,30928,30460,30036,21028,21467,20 [...]
+26049,32810,32982,25243,21638,21032,28846,34957,36305,27873,21624,32986,22521,35060,36180,38506,37197,20329,27803,21943,30406,30768,25256,28921,28558,24429,34028,26842,30844,31735,33192,26379,40527,25447,30896,22383,30738,38713,25209,25259,21128,29749,27607,21860,33086,30130,[12138,30382],21305,30174,20731,23617,35692,31687,20559,[12122,29255],39575,39128,28418,29922,31080,25735,30629,25340,39057,36139,21697,32856,20050,22378,33529,33805,24179,20973,29942,35780,23631,22369,27900,39047,23 [...]
+39748,36843,31893,21078,25169,38138,20166,33670,33889,33769,33970,22484,26420,22275,26222,28006,35889,26333,28689,26399,27450,26646,25114,22971,19971,20932,28422,26578,27791,20854,26827,22855,27495,30054,23822,33040,40784,26071,31048,31041,39569,36215,23682,20062,20225,21551,22865,30732,22120,[12115,27668],36804,24323,27773,27875,35755,25488,24688,27965,29301,25190,38030,38085,21315,36801,31614,20191,35878,20094,40660,38065,38067,21069,28508,36963,27973,35892,22545,23884,[12107,27424],27 [...]
+21595,33108,32652,22681,34103,24378,25250,27207,38201,25970,24708,26725,30631,20052,20392,24039,38808,25772,32728,23789,20431,31373,20999,33540,19988,24623,31363,38054,20405,20146,31206,29748,21220,33465,25810,31165,23517,27777,38738,36731,27682,20542,21375,28165,25806,26228,27696,24773,39031,35831,24198,29756,31351,31179,19992,37041,29699,27714,22234,37195,27845,36235,21306,34502,26354,36527,23624,39537,28192,21462,23094,40843,36259,21435,22280,39079,26435,37275,27849,20840,30154,25331, [...]
+21048,21149,32570,28820,30264,21364,40522,27063,30830,38592,35033,32676,28982,29123,20873,26579,29924,22756,25880,22199,35753,39286,25200,32469,24825,28909,22764,20161,[12040,20154],24525,38887,20219,35748,20995,22922,32427,25172,20173,[12103,26085],25102,33592,33993,33635,34701,29076,28342,23481,32466,20887,25545,26580,[12161,32905],33593,34837,20754,23418,22914,36785,20083,27741,[12042,20837],35109,36719,38446,34122,29790,38160,38384,28070,33509,24369,25746,27922,33832,33134,40131,2262 [...]
+21441,20254,25955,26705,21971,20007,25620,39578,25195,23234,29791,[12170,33394],28073,26862,20711,33678,30722,26432,21049,27801,32433,20667,21861,29022,31579,26194,29642,33515,26441,[12077,23665],21024,29053,34923,38378,38485,25797,36193,33203,21892,27733,25159,32558,22674,20260,21830,36175,26188,19978,23578,35059,26786,25422,31245,28903,33421,21242,38902,23569,21736,37045,32461,22882,36170,34503,[12166,33292],33293,36198,25668,23556,24913,28041,31038,35774,30775,30003,21627,20280,[12189 [...]
+23072,32453,31070,27784,23457,23158,29978,32958,24910,28183,22768,[12131,29983],29989,29298,21319,32499,30465,30427,21097,32988,22307,24072,22833,29422,26045,28287,35799,[12075,23608],34417,[12055,21313],[12143,30707],25342,26102,20160,[12215,39135],34432,23454,35782,21490,[12142,30690],20351,23630,39542,22987,24335,[12144,31034],[12064,22763],19990,26623,20107,25325,35475,36893,21183,26159,21980,22124,36866,20181,20365,37322,39280,[12114,27663],24066,24643,23460,35270,35797,25910,[12095 [...]
+39318],23432,23551,25480,21806,21463,30246,20861,34092,26530,26803,27530,25234,36755,21460,33298,28113,30095,20070,36174,23408,29087,34223,26257,26329,32626,34560,[12233,40653],[12239,40736],23646,26415,36848,26641,26463,25101,31446,22661,24246,25968,28465,24661,21047,32781,25684,34928,29993,24069,26643,25332,38684,21452,29245,35841,[12116,27700],30561,31246,21550,30636,39034,33308,35828,30805,26388,28865,26031,25749,22070,24605,31169,21496,19997,27515,32902,23546,21987,22235,20282,20284 [...]
+26494,32824,24578,39042,36865,23435,35772,35829,25628,33368,25822,22013,33487,37221,20439,32032,36895,31903,20723,22609,28335,23487,35785,32899,37240,33948,31639,34429,38539,38543,32485,39635,30862,23681,31319,36930,38567,31071,23385,25439,31499,34001,26797,21766,32553,29712,32034,38145,25152,22604,20182,23427,22905,22612,29549,25374,36427,36367,32974,33492,25260,21488,27888,37214,22826,24577,27760,22349,25674,36138,30251,28393,22363,27264,30192,28525,35885,35848,22374,27631,34962,30899, [...]
+28845,27748,22616,25642,22530,26848,33179,21776,31958,20504,36538,28108,36255,28907,25487,28059,28372,32486,33796,26691,36867,28120,38518,35752,22871,29305,34276,33150,30140,35466,26799,21076,36386,38161,25552,39064,36420,21884,20307,26367,22159,24789,28053,21059,23625,22825,28155,22635,[12133,3E4],29980,24684,33300,33094,25361,26465,36834,30522,36339,36148,38081,24086,21381,21548,28867,27712,24311,20572,20141,24237,25402,33351,36890,26704,37230,30643,21516,38108,24420,31461,26742,25413, [...]
+30171,20599,25237,22836,36879,20984,31171,31361,22270,24466,36884,28034,23648,[12063,22303],21520,20820,28237,22242,25512,39059,33151,34581,35114,36864,21534,23663,33216,25302,25176,33073,40501,38464,39534,39548,26925,22949,25299,21822,25366,21703,34521,27964,23043,[12129,29926],34972,27498,22806,35916,24367,28286,29609,39037,20024,28919,23436,30871,25405,26202,30358,24779,23451,23113,19975,33109,27754,29579,20129,26505,[12153,32593],24448,26106,26395,24536,22916,23041,24013,24494,21361, [...]
+26693,22260,21807,24799,20026,28493,32500,33479,33806,22996,20255,20266,23614,32428,26410,34074,21619,30031,32963,21890,39759,20301,28205,35859,23561,24944,21355,30239,28201,34442,[12098,25991],38395,32441,21563,31283,32010,38382,21985,32705,29934,25373,34583,28065,31389,25105,26017,21351,25569,27779,24043,21596,38056,20044,27745,35820,23627,[12102,26080],33436,26791,21566,21556,[12111,27595],27494,20116,25410,21320,33310,20237,20398,22366,25098,38654,26212,29289,21247,21153,24735,35823, [...]
+26512,35199,30802,30717,26224,22075,21560,38177,29306,31232,24687,24076,24713,33181,[12067,22805],24796,29060,28911,28330,27728,29312,27268,34989,24109,20064,23219,21916,38115,27927,31995,38553,25103,32454,30606,34430,21283,38686,36758,26247,23777,20384,29421,19979,21414,22799,21523,25472,38184,20808,20185,40092,32420,21688,36132,34900,33335,38386,28046,24358,23244,26174,38505,29616,29486,21439,33146,39301,32673,23466,38519,38480,32447,30456,21410,38262,[12217,39321],31665,35140,28248,20 [...]
+31077,35814,24819,21709,20139,39033,24055,27233,20687,21521,35937,33831,30813,38660,21066,21742,22179,38144,28040,23477,28102,26195,[12073,23567],23389,26657,32918,21880,31505,25928,26964,20123,27463,34638,38795,21327,25375,25658,37034,26012,32961,35856,20889,26800,21368,34809,25032,27844,27899,35874,23633,34218,33455,38156,27427,[12191,36763],26032,24571,[12092,24515],20449,34885,26143,33125,29481,24826,20852,21009,22411,24418,37026,[12175,34892],37266,24184,26447,24615,22995,20804,2098 [...]
+27769,38596,29066,20241,20462,32670,26429,21957,38152,31168,34966,32483,22687,25100,38656,34394,22040,39035,24464,35768,33988,37207,21465,26093,24207,30044,24676,32110,23167,32490,32493,36713,21927,23459,24748,26059,[12126,29572],36873,30307,30505,32474,38772,34203,23398,[12147,31348],38634,[12174,34880],21195,29071,24490,26092,35810,23547,39535,24033,27529,27739,35757,35759,36874,36805,21387,25276,40486,40493,21568,20011,33469,[12123,29273],34460,23830,34905,28079,38597,21713,20122,3576 [...]
+38409,28895,28153,30416,20005,30740,34578,23721,24310,[12180,35328],39068,38414,28814,27839,22852,25513,30524,34893,28436,33395,22576,29141,21388,30746,38593,21761,24422,28976,23476,35866,39564,27523,22830,40495,31207,26472,25196,20335,30113,[12154,32650],27915,38451,27687,20208,30162,20859,26679,28478,36992,33136,22934,29814,25671,23591,36965,31377,35875,23002,21676,33280,33647,35201,32768,26928,22094,32822,29239,37326,20918,20063,39029,25494,19994,21494,26355,33099,22812,28082,[12032,1 [...]
+21307,25558,38129,20381,20234,[12176,34915],39056,22839,36951,31227,20202,33008,30097,27778,23452,23016,24413,26885,34433,20506,24050,[12036,20057],30691,20197,33402,25233,26131,[12194,37009],23673,20159,24441,33222,36920,32900,30123,20134,35028,24847,27589,24518,20041,30410,28322,35811,35758,35850,35793,24322,32764,32716,32462,33589,33643,22240,27575,[12211,38899],38452,23035,21535,38134,28139,23493,39278,23609,24341,38544,21360,33521,27185,23156,40560,24212,32552,33721,{f:2,c:33828},33 [...]
+36814,36194,30408,24433,39062,30828,26144,21727,25317,20323,33219,30152,24248,38605,36362,34553,21647,27891,28044,27704,24703,21191,[12132,29992],24189,20248,24736,24551,23588,30001,37038,38080,29369,27833,28216,[12195,37193],26377,21451,21491,20305,37321,35825,[12060,21448],24188,36802,28132,20110,30402,27014,34398,24858,33286,20313,20446,36926,40060,24841,28189,28180,38533,20104,23089,[12204,38632],19982,23679,31161,23431,35821,[12155,32701],[12127,29577],22495,33419,37057,21505,36935, [...]
+24481,24840,27442,29425,32946,35465,28020,23507,35029,39044,35947,39533,40499,28170,20900,20803,22435,34945,21407,25588,36757,22253,21592,22278,29503,28304,32536,36828,33489,24895,24616,38498,[12104,26352],32422,36234,36291,38053,23731,31908,[12105,26376],24742,38405,32792,20113,37095,21248,38504,20801,36816,34164,37213,26197,38901,23381,21277,30776,26434,26685,21705,28798,23472,36733,20877,22312,21681,25874,26242,36190,36163,33039,33900,36973,31967,20991,34299,26531,26089,28577,34468,36 [...]
+36896,30338,28790,29157,36131,25321,21017,27901,36156,24590,22686,24974,26366,36192,25166,21939,28195,26413,36711,38113,38392,30504,26629,27048,21643,20045,28856,35784,25688,25995,23429,31364,20538,23528,30651,27617,35449,31896,27838,30415,26025,36759,23853,23637,34360,26632,21344,25112,31449,28251,32509,27167,31456,24432,28467,24352,25484,28072,26454,19976,24080,36134,20183,32960,30260,38556,25307,26157,25214,27836,36213,29031,32617,20806,32903,21484,36974,25240,21746,34544,36761,32773, [...]
+36825,27993,29645,26015,30495,29956,30759,33275,36126,38024,20390,26517,30137,35786,38663,25391,38215,38453,33976,25379,30529,24449,29424,20105,24596,25972,25327,27491,25919,24103,30151,37073,35777,33437,26525,[12096,25903],21553,34584,30693,32930,33026,27713,20043,32455,32844,30452,26893,27542,25191,20540,20356,22336,25351,[12108,27490],36286,21482,26088,32440,24535,25370,25527,[12164,33267],33268,32622,24092,23769,21046,26234,31209,31258,36136,28825,30164,28382,27835,31378,20013,30405, [...]
+34935,32456,31181,32959,37325,20210,20247,[12168,33311],21608,24030,27954,35788,31909,36724,32920,24090,21650,30385,23449,26172,39588,29664,26666,34523,26417,29482,35832,35803,36880,[12149,31481],28891,29038,25284,30633,22065,20027,33879,26609,21161,34496,36142,38136,31569,20303,27880,31069,39547,25235,[12118,29226],25341,19987,30742,36716,25776,36186,31686,26729,24196,35013,22918,25758,22766,29366,26894,38181,36861,36184,22368,32512,35846,20934,25417,25305,21331,26700,29730,33537,37196, [...]
+28796,27978,20857,21672,36164,23039,28363,28100,23388,32043,20180,31869,28371,[12070,23376],[12163,33258],28173,23383,39683,26837,36394,23447,32508,24635,32437,37049,[12187,36208],22863,25549,31199,[12188,36275],21330,26063,31062,35781,38459,32452,38075,32386,22068,37257,26368,32618,23562,36981,26152,24038,20304,26590,20570,20316,22352,24231,20109,19980,20800,19984,24319,21317,19989,20120,19998,[12224,39730],23404,22121,[12033,20008],31162,[12035,20031],[12052,21269],20039,22829,[12120,2 [...]
+27664,22239,32996,39319,27603,30590,40727,[12034,20022],20127,40720,20060,20073,20115,33416,23387,21868,22031,20164,21389,21405,21411,21413,21422,38757,36189,[12053,21274],21493,21286,21294,21310,36188,21350,21347,20994,21E3,21006,21037,21043,{f:2,c:21055},21068,21086,21089,21084,33967,21117,21122,21121,21136,21139,[12044,20866],32596,20155,20163,20169,20162,20200,20193,20203,20190,20251,20211,20258,20324,20213,20261,20263,20233,20267,20318,20327,25912,20314,20317,20319,20311,20274,20285 [...]
+20369,20361,20355,20367,20350,20347,20394,20348,20396,20372,20454,20456,20458,20421,20442,20451,20444,20433,20447,20472,20521,20556,20467,20524,20495,20526,20525,20478,20508,20492,20517,20520,20606,20547,20565,20552,20558,20588,20603,20645,20647,20649,20666,20694,20742,20717,20716,20710,20718,20743,20747,20189,27709,20312,20325,20430,[12245,40864],27718,31860,20846,24061,40649,39320,20865,22804,[12051,21241],21261,35335,21264,20971,22809,20821,[12039,20128],20822,20147,34926,34980,20149, [...]
+31104,23348,34819,32696,[12046,20907],20913,20925,20924,20935,[12045,20886],20898,20901,35744,{f:2,c:35750},35754,{f:2,c:35764},35767,{f:2,c:35778},35787,35791,35790,{f:3,c:35794},35798,{f:2,c:35800},35804,{f:2,c:35807},35812,{f:2,c:35816},35822,35824,35827,35830,35833,35836,{f:2,c:35839},35842,35844,35847,35852,35855,{f:2,c:35857},{f:3,c:35860},35865,35867,35864,35869,{f:3,c:35871},35877,35879,{f:2,c:35882},{f:2,c:35886},{f:2,c:35890},{f:2,c:35893},[12057,21353],21370,38429,38434,38433, [...]
+38461,38460,38466,38473,38484,38495,38503,38508,38514,38516,38536,38541,38551,38576,37015,37019,37021,37017,37036,37025,37044,37043,37046,37050,37048,37040,37071,37061,37054,37072,37060,37063,37075,37094,37090,37084,37079,37083,37099,37103,37118,37124,37154,37150,37155,37169,37167,37177,37187,37190,21005,22850,21154,{f:2,c:21164},21182,21759,21200,21206,21232,21471,29166,30669,[12085,24308],[12048,20981],20988,[12223,39727],[12059,21430],24321,30042,24047,22348,22441,22433,22654,22716,22 [...]
+22313,22316,22314,22323,22329,{f:2,c:22318},22364,22331,22338,22377,22405,22379,22406,22396,22395,22376,22381,22390,22387,22445,22436,22412,22450,22479,22439,22452,22419,22432,22485,22488,22490,22489,22482,22456,22516,22511,22520,22500,22493,22539,22541,22525,22509,22528,22558,22553,22596,22560,22629,22636,22657,22665,22682,22656,39336,40729,25087,33401,33405,33407,33423,33418,33448,33412,33422,33425,33431,33433,33451,33464,33470,33456,33480,33482,33507,33432,33463,33454,{f:2,c:33483},33 [...]
+33460,33441,33450,33439,33476,33486,33444,33505,33545,33527,33508,33551,33543,33500,33524,33490,33496,33548,33531,33491,33553,33562,33542,{f:2,c:33556},33504,33493,33564,33617,{f:2,c:33627},33544,33682,33596,33588,33585,33691,33630,33583,33615,33607,33603,33631,33600,33559,33632,33581,33594,33587,33638,33637,33640,33563,33641,33644,33642,{f:2,c:33645},33712,33656,{f:2,c:33715},33696,33706,33683,33692,33669,33660,33718,33705,33661,33720,33659,33688,33694,33704,33722,33724,33729,33793,3376 [...]
+33816,33803,33757,33789,33750,33820,33848,33809,33798,33748,33759,33807,33795,{f:2,c:33784},33770,33733,33728,33830,33776,33761,33884,33873,33882,33881,33907,{f:2,c:33927},33914,33929,33912,33852,33862,33897,33910,33932,33934,33841,33901,33985,33997,34E3,34022,33981,34003,33994,33983,33978,34016,33953,33977,33972,33943,34021,34019,34060,29965,34104,34032,34105,34079,34106,34134,34107,34047,34044,34137,34120,34152,34148,34142,34170,30626,34115,34162,34171,34212,34216,34183,34191,34169,342 [...]
+34181,34233,34231,34224,34259,34241,34268,34303,34343,34309,34345,34326,34364,[12086,24318],24328,22844,22849,32823,22869,22874,22872,21263,[12074,23586],23589,23596,23604,25164,25194,25247,25275,25290,25306,25303,25326,25378,25334,25401,25419,25411,25517,25590,25457,25466,25486,25524,25453,25516,25482,25449,25518,25532,25586,25592,25568,25599,25540,25566,25550,25682,25542,25534,25669,25665,25611,25627,25632,25612,25638,25633,25694,25732,25709,25750,25722,{f:2,c:25783},25753,25786,25792, [...]
+25828,25826,25865,25893,25902,[12087,24331],24530,29977,24337,21343,21489,21501,21481,21480,21499,21522,21526,21510,21579,{f:3,c:21586},21590,21571,21537,21591,21593,21539,21554,21634,21652,21623,21617,21604,{f:2,c:21658},21636,21622,21606,21661,21712,21677,21698,21684,21714,21671,21670,{f:2,c:21715},21618,21667,21717,21691,21695,21708,{f:2,c:21721},21724,{f:2,c:21673},21668,21725,21711,21726,21787,21735,21792,21757,21780,21747,{f:2,c:21794},21775,21777,21799,21802,21863,21903,21941,2183 [...]
+21845,21823,21840,21820,21815,21846,{f:3,c:21877},21811,21808,21852,21899,21970,21891,21937,21945,21896,21889,21919,21886,21974,21905,21883,21983,{f:2,c:21949},21908,21913,21994,22007,21961,22047,21969,{f:2,c:21995},21972,21990,21981,21956,21999,21989,{f:2,c:22002},{f:2,c:21964},21992,22005,21988,36756,22046,22024,22028,22017,22052,22051,22014,22016,22055,22061,22104,22073,22103,22060,22093,22114,22105,22108,22092,22100,22150,22116,22129,22123,{f:2,c:22139},22149,22163,22191,22228,[12062 [...]
+22241,22261,22251,22265,22271,22276,22282,22281,22300,24079,24089,24084,24081,24113,{f:2,c:24123},24119,24132,24148,24155,24158,24161,23692,23674,23693,23696,23702,23688,{f:2,c:23704},23697,23706,23708,23733,23714,23741,23724,23723,23729,23715,23745,23735,23748,23762,23780,23755,23781,{f:2,c:23810},23847,23846,23854,23844,23838,23814,23835,23896,23870,23860,23869,23916,23899,23919,23901,23915,23883,23882,23913,23924,23938,23961,23965,35955,23991,24005,[12091,24435],24439,24450,24455,2445 [...]
+24473,24476,24488,24493,24501,24508,34914,[12090,24417],29357,29360,29364,{f:2,c:29367},29379,29377,29390,29389,29394,29416,29423,29417,29426,29428,29431,29441,29427,29443,{f:2,c:29434},29463,29459,29473,29450,29470,29469,29461,29474,29497,29477,29484,29496,29489,29520,29517,29527,29536,29548,29551,29566,[12167,33307],22821,39143,22820,[12065,22786],39267,{f:6,c:39271},39284,39287,39293,39296,39300,39303,39306,39309,{f:2,c:39312},{f:3,c:39315},24192,24209,24203,24214,24229,24224,24249,24 [...]
+24243,36179,24274,24273,24283,24296,24298,33210,24516,24521,24534,24527,24579,24558,24580,24545,24548,24574,{f:2,c:24581},24554,24557,24568,24601,24629,24614,24603,24591,24589,24617,24619,24586,24639,24609,{f:2,c:24696},24699,24698,24642,24682,24701,24726,24730,24749,24733,24707,24722,24716,24731,24812,24763,24753,24797,24792,24774,24794,24756,24864,24870,24853,24867,24820,24832,24846,24875,24906,24949,25004,24980,24999,25015,25044,25077,24541,38579,38377,38379,38385,38387,{f:2,c:38389}, [...]
+{f:2,c:38403},38406,38408,{f:4,c:38410},38415,38418,{f:3,c:38421},{f:2,c:38425},20012,[12121,29247],25109,27701,27732,27740,27722,27811,27781,27792,27796,27788,{f:2,c:27752},27764,27766,27782,27817,27856,27860,27821,{f:2,c:27895},27889,27863,27826,27872,27862,27898,27883,27886,27825,27859,27887,27902,27961,27943,27916,27971,27976,27911,27908,27929,27918,27947,27981,27950,27957,27930,27983,27986,27988,27955,28049,28015,28062,28064,27998,{f:2,c:28051},27996,28E3,28028,28003,28186,28103,281 [...]
+28174,28095,28128,28177,28134,28125,28121,28182,28075,28172,28078,28203,28270,28238,28267,28338,28255,28294,{f:2,c:28243},28210,28197,28228,28383,28337,28312,28384,28461,28386,28325,28327,28349,28347,28343,28375,28340,28367,28303,28354,28319,28514,{f:2,c:28486},28452,28437,28409,28463,28470,28491,28532,28458,28425,28457,28553,28557,28556,28536,28530,28540,28538,28625,28617,28583,28601,28598,28610,28641,28654,28638,28640,28655,28698,28707,28699,28729,28725,28751,28766,[12071,23424],23428, [...]
+23461,23480,29999,39582,25652,23524,23534,35120,23536,36423,35591,36790,36819,36821,36837,36846,36836,36841,36838,36851,36840,36869,36868,36875,36902,36881,36877,36886,36897,{f:2,c:36917},36909,36911,36932,{f:2,c:36945},36944,36968,36952,36962,36955,26297,36980,36989,36994,37E3,36995,37003,[12089,24400],24407,24406,24408,23611,21675,23632,23641,23409,23651,23654,32700,24362,24361,24365,33396,24380,39739,[12076,23662],22913,22915,22925,{f:2,c:22953},22947,22935,22986,22955,22942,22948,229 [...]
+22959,22999,22974,{f:2,c:23045},23005,23048,23011,23E3,23033,23052,23049,23090,23092,23057,23075,23059,23104,23143,23114,23125,23100,23138,23157,33004,23210,23195,23159,23162,23230,23275,23218,23250,23252,23224,23264,23267,23281,23254,23270,23256,23260,23305,23319,23318,23346,23351,23360,23573,23580,23386,23397,23411,23377,23379,23394,39541,{f:2,c:39543},39546,39551,39549,{f:2,c:39552},39557,39560,39562,39568,{f:2,c:39570},39574,39576,{f:3,c:39579},{f:2,c:39583},{f:2,c:39586},39589,39591 [...]
+32419,32421,{f:2,c:32424},32429,32432,32446,{f:3,c:32448},32457,{f:2,c:32459},32464,32468,32471,32475,{f:2,c:32480},32488,32491,{f:2,c:32494},{f:2,c:32497},32525,32502,{f:2,c:32506},32510,{f:3,c:32513},{f:2,c:32519},{f:2,c:32523},32527,{f:2,c:32529},32535,32537,32540,32539,32543,{f:7,c:32545},{f:4,c:32554},{f:5,c:32559},32565,[12083,24186],30079,[12078,24027],30014,37013,29582,29585,29614,29602,29599,29647,29634,29649,29623,29619,29632,29641,29640,29669,29657,39036,29706,29673,29671,2966 [...]
+29711,29738,29787,29734,29733,29736,29744,29742,29740,29723,29722,29761,29788,29783,29781,29785,29815,29805,29822,29852,29838,{f:2,c:29824},29831,29835,29854,{f:2,c:29864},29840,29863,29906,29882,{f:3,c:38890},26444,26451,26462,26440,26473,26533,26503,26474,26483,26520,26535,26485,26536,26526,26541,26507,26487,26492,26608,26633,26584,26634,26601,26544,26636,26585,26549,26586,26547,26589,26624,26563,26552,26594,26638,26561,26621,{f:2,c:26674},{f:2,c:26720},26702,26722,26692,26724,26755,26 [...]
+26726,26689,26727,26688,26686,26698,26697,26665,26805,26767,26740,26743,26771,26731,26818,26990,26876,{f:2,c:26911},26873,26916,26864,26891,26881,26967,26851,26896,26993,26937,26976,26946,26973,27012,26987,27008,27032,27E3,26932,27084,{f:2,c:27015},27086,27017,26982,26979,27001,27035,27047,27067,27051,27053,27092,27057,27073,27082,27103,27029,27104,27021,27135,27183,27117,{f:2,c:27159},27237,27122,27204,27198,27296,27216,27227,27189,27278,27257,27197,27176,27224,27260,27281,27280,27305,2 [...]
+29495,29522,{f:2,c:27521},27527,27524,{f:2,c:27538},27533,{f:2,c:27546},27553,27562,36715,36717,{f:3,c:36721},{f:2,c:36725},36728,36727,{f:2,c:36729},36732,36734,{f:2,c:36737},36740,36743,36747,{f:3,c:36749},36760,36762,36558,25099,25111,25115,25119,25122,25121,25125,25124,25132,33255,29935,29940,29951,29967,29969,29971,[12097,25908],{f:3,c:26094},26122,26137,26482,26115,26133,26112,28805,26359,26141,26164,26161,26166,26165,32774,26207,26196,26177,26191,26198,26209,26199,26231,26244,2625 [...]
+26302,{f:2,c:26331},26342,26345,{f:2,c:36146},36150,36155,36157,36160,{f:2,c:36165},{f:2,c:36168},36167,36173,36181,36185,35271,{f:3,c:35274},{f:4,c:35278},29294,29343,29277,29286,29295,{f:2,c:29310},29316,29323,29325,29327,29330,25352,25394,25520,25663,25816,32772,27626,27635,27645,27637,27641,27653,27655,27654,27661,27669,{f:3,c:27672},27681,27689,27684,27690,27698,25909,25941,25963,29261,29266,29270,29232,34402,21014,32927,32924,32915,32956,26378,32957,32945,32939,32941,32948,32951,{f [...]
+32987,32962,32964,32985,32973,32983,26384,32989,33003,33009,33012,33005,{f:2,c:33037},33010,33020,26389,33042,35930,33078,33054,33068,33048,33074,33096,33100,33107,33140,{f:2,c:33113},33137,33120,33129,{f:2,c:33148},33133,33127,22605,23221,33160,33154,33169,28373,33187,33194,33228,26406,33226,33211,33217,33190,27428,27447,27449,27459,27462,27481,{f:3,c:39121},39125,{f:2,c:39129},[12110,27571],24384,27586,35315,26E3,40785,26003,26044,26054,26052,26051,26060,26062,26066,26070,28800,28828,2 [...]
+28859,28864,28855,28843,28849,28904,28874,28944,28947,28950,28975,28977,29043,29020,29032,28997,29042,29002,29048,29050,29080,29107,29109,29096,29088,29152,29140,29159,29177,29213,29224,28780,28952,29030,29113,25150,25149,25155,{f:2,c:25160},31035,31040,31046,31049,{f:2,c:31067},31059,31066,31074,31063,31072,31087,31079,31098,31109,31114,31130,31143,31155,24529,24528,24636,24669,24666,24679,24641,24665,24675,24747,24838,24845,24925,25001,24989,25035,25041,25094,32896,[12160,32895],27795, [...]
+30710,30712,30720,30729,{f:2,c:30743},30737,26027,30765,{f:2,c:30748},{f:3,c:30777},30751,30780,30757,30764,30755,30761,30798,30829,{f:2,c:30806},30758,30800,30791,30796,30826,30875,30867,30874,30855,30876,30881,30883,30898,30905,30885,30932,30937,30921,30956,30962,30981,30964,30995,31012,31006,31028,40859,[12235,40697],{f:2,c:40699},30449,30468,30477,30457,{f:2,c:30471},30490,30498,30489,30509,30502,30517,30520,{f:2,c:30544},30535,30531,30554,30568,30562,30565,30591,30605,30589,30592,30 [...]
+{f:2,c:30623},30640,30645,30653,30010,30016,30030,30027,30024,30043,30066,30073,30083,32600,32609,32607,35400,32616,32628,32625,32633,32641,32638,30413,30437,34866,{f:3,c:38021},38027,38026,{f:2,c:38028},{f:2,c:38031},38036,38039,38037,{f:3,c:38042},{f:2,c:38051},38059,38058,38061,38060,{f:2,c:38063},38066,38068,{f:5,c:38070},{f:2,c:38076},38079,38084,{f:7,c:38088},{f:3,c:38096},{f:3,c:38101},38105,38104,38107,{f:3,c:38110},38114,{f:2,c:38116},{f:2,c:38119},38122,38121,38123,{f:2,c:38126 [...]
+38135,38137,{f:2,c:38140},38143,38147,38146,{f:2,c:38150},{f:2,c:38153},{f:3,c:38157},{f:5,c:38162},38168,38171,{f:3,c:38173},38178,{f:2,c:38186},38185,38188,{f:2,c:38193},38196,{f:3,c:38198},38204,{f:2,c:38206},38210,38197,{f:3,c:38212},38217,38220,{f:2,c:38222},{f:3,c:38226},{f:4,c:38230},38235,{f:2,c:38238},38237,{f:2,c:38241},{f:9,c:38244},38255,{f:3,c:38257},38202,30695,30700,38601,31189,31213,31203,31211,31238,23879,31235,31234,31262,31252,31289,31287,31313,40655,39333,31344,30344, [...]
+30361,30372,29918,29920,29996,40480,40482,{f:5,c:40488},40498,40497,40502,40504,40503,{f:2,c:40505},40510,{f:2,c:40513},40516,{f:4,c:40518},{f:2,c:40523},40526,40529,40533,40535,{f:3,c:40538},40542,40547,{f:7,c:40550},40561,40557,40563,[12135,30098],30100,30102,30112,30109,30124,30115,{f:2,c:30131},30136,30148,30129,30128,30147,30146,30166,30157,30179,30184,30182,30180,30187,30183,30211,30193,30204,30207,30224,30208,30213,30220,30231,30218,30245,30232,30229,30233,30235,30268,30242,30240, [...]
+30256,30271,30261,30275,30270,30259,30285,30302,30292,30300,30294,30315,30319,32714,31462,{f:2,c:31352},31360,31366,31368,31381,31398,31392,31404,31400,31405,31411,34916,34921,34930,34941,34943,34946,34978,35014,34999,35004,35017,35042,35022,35043,35045,35057,35098,35068,35048,35070,35056,35105,35097,35091,35099,35082,35124,35115,35126,35137,35174,35195,[12134,30091],32997,30386,30388,30684,[12158,32786],32788,32790,32796,32800,32802,{f:3,c:32805},32809,32808,32817,32779,32821,32835,3283 [...]
+32873,32881,35203,39032,39040,39043,39049,{f:2,c:39052},39055,39060,{f:2,c:39066},{f:2,c:39070},{f:2,c:39073},{f:2,c:39077},[12172,34381],34388,34412,34414,34431,34426,34428,34427,34472,34445,34443,34476,34461,34471,34467,34474,34451,34473,34486,34500,34485,34510,34480,34490,34481,34479,34505,34511,34484,34537,{f:2,c:34545},34541,34547,34512,34579,34526,34548,34527,34520,34513,34563,34567,34552,34568,34570,34573,34569,34595,34619,34590,34597,34606,34586,34622,34632,34612,34609,34601,3461 [...]
+34594,{f:2,c:34685},34683,34656,34672,34636,34670,34699,34643,34659,34684,34660,34649,34661,34707,34735,34728,34770,34758,34696,34693,34733,34711,34691,34731,34789,34732,34741,34739,34763,34771,34749,34769,34752,34762,34779,34794,34784,34798,34838,34835,34814,34826,34843,34849,34873,34876,[12152,32566],32578,{f:2,c:32580},33296,31482,31485,31496,{f:2,c:31491},31509,31498,31531,31503,31559,31544,31530,31513,31534,31537,31520,31525,31524,31539,31550,31518,31576,31578,31557,31605,31564,3158 [...]
+31611,31586,31602,31601,31632,{f:2,c:31654},31672,31660,31645,31656,31621,31658,31644,31650,31659,31668,31697,31681,31692,31709,31706,{f:2,c:31717},31722,31756,31742,31740,31759,31766,31755,31775,31786,31782,31800,31809,31808,33278,{f:2,c:33281},33284,33260,34884,{f:3,c:33313},33325,33327,33320,33323,33336,33339,{f:2,c:33331},33342,33348,33353,33355,33359,33370,33375,33384,34942,34949,34952,35032,35039,35166,32669,32671,32679,{f:2,c:32687},32690,31868,25929,31889,31901,31900,31902,31906, [...]
+c:31932},31937,31943,{f:2,c:31948},31944,31941,31959,31976,[12169,33390],26280,32703,32718,32725,32741,32737,32742,32745,32750,32755,[12151,31992],32119,32166,32174,32327,32411,40632,40628,36211,36228,36244,36241,36273,36199,36205,35911,35913,37194,37200,{f:2,c:37198},37220,37218,37217,37232,37225,37231,{f:2,c:37245},37234,37236,37241,37260,37253,37264,37261,37265,{f:2,c:37282},37290,{f:3,c:37293},37301,37300,37306,[12183,35925],40574,36280,36331,36357,36441,36457,36277,36287,36284,36282 [...]
+c:36310},36314,36318,{f:2,c:36302},36315,36294,36332,{f:2,c:36343},36323,36345,36347,36324,36361,36349,36372,36381,36383,36396,36398,36387,36399,36410,36416,36409,36405,36413,36401,36425,{f:2,c:36417},{f:2,c:36433},36426,36464,36470,36476,36463,36468,36485,36495,36500,36496,36508,36510,[12184,35960],35970,35978,35973,35992,35988,26011,35286,35294,35290,35292,35301,35307,35311,35390,35622,38739,38633,38643,38639,38662,38657,38664,38671,38670,38698,38701,38704,38718,40832,40835,{f:6,c:4083 [...]
+40702,40715,40717,[12203,38585],{f:2,c:38588},38606,38610,30655,38624,37518,37550,37576,37694,37738,37834,37775,37950,37995,40063,40066,{f:4,c:40069},31267,40075,40078,{f:3,c:40080},{f:2,c:40084},{f:2,c:40090},{f:6,c:40094},{f:5,c:40101},40107,{f:2,c:40109},{f:8,c:40112},{f:4,c:40122},{f:4,c:40132},{f:7,c:40138},{f:3,c:40147},{f:3,c:40151},{f:2,c:40156},40159,40162,38780,38789,{f:2,c:38801},38804,38831,38827,38819,38834,38836,39601,39600,39607,40536,39606,39610,39612,39617,39616,39621,39 [...]
+c:39627},39633,39749,39747,39751,39753,39752,39757,39761,39144,39181,39214,39253,39252,[12221,39647],39649,39654,39663,39659,39675,39661,39673,39688,39695,39699,39711,39715,{f:2,c:40637},32315,40578,{f:2,c:40583},40587,40594,37846,40605,40607,{f:3,c:40667},40672,40671,40674,40681,40679,40677,40682,40687,40738,40748,40751,40761,40759,{f:2,c:40765},40772,12295,{s:13},30362,34297,31001,24859,39599,35158,22761,32631,25850,25943,38930,36774,32070,24171,32129,37770,35607,39165,23542,22577,3982 [...]
+35997],37575,29437,20633,24970,32179,31558,30050,25987,24163,38281,37002,32232,36022,35722,36783,36782,27161,40009,30303,28693,28657,36051,25839,39173,25765,37474,37457,39361,35036,36001,21443,34870,27544,24922,24920,29158,33980,33369,20489,28356,21408,20596,28204,23652,35435,25881,25723,34796,39262,35730,32399,37855,29987,38369,39019,22580,22039,[12199,38263],20767,33144,24288,26274,37396,[12190,36554],24505,22645,38515,35183,31281,25074,35488,39425,36978,39347,[12242,40786],29118,34909 [...]
+30087,36490,31820,32162,37276,37604,38619,30990,20786,35320,34389,20659,30241,38358,21109,37656,32020,32189,36781,35422,36060,32880,24478,21474,36517,31428,37679,36948,24118,36024,25812,21934,37170,25763,33213,24986,35477,24392,30070,25803,40680,34153,27284,25623,23798,31153,23566,29128,37159,25973,28364,36958,32224,39003,40670,22666,38651,28593,37347,35519,35548,37336,38914,37664,35330,26481,21205,26847,20941,[12222,39717],29346,29544,35712,36077,37709,37723,26039,32222,38538,23565,2213 [...]
+22890,22702,40285,38989,35355,24801,39187,20818,29246,39180,36019,30332,32624,38309,31020,37353,29033,31684,36009,39151,35370,32033,[12214,39131],35513,24290,36027,32027,22707,22894,24996,31966,35920,26963,37586,[12213,39080],30219,39342,32299,35575,40179,33178,36667,25771,36628,36070,24489,36E3,35331,23142,32283,35442,37411,33995,24185,36245,36123,23713,21083,37628,32177,23831,37804,25841,40255,38307,37499,20491,32102,40852,38799,36002,37390,28317,27083,36092,34865,39015,21102,38364,352 [...]
+24931,36011,24291,35215,27512,[12244,40860],38312,36556,35437,27331,36020,21130,36645,37707,22283,36942,39405,38867,28450,34399,38305,40372,36032,36703,40251,32005,22778,35703,28396,22057,33775,30059,21123,35441,25079,22750,27489,29872,36996,32233,35594,25582,36637,36036,31330,26371,29172,21295,35569,35496,32362,33911,28222,29554,36008,31117,25802,27231,31309,39249,35663,40388,32318,32221,26997,36655,32026,25824,24190,34186,21137,28639,35336,35352,38555,32380,32E3,22846,33698,38960,36040 [...]
+39381,27570,30435,22533,31627,38291,33393,32216,32365,27298,40572,25536,25791,31777,20745,34214,27323,37970,36368,36068,[12178,35211],37749,33382,21133,39198,28472,28666,28567,23559,28479,34083,27123,22892,35611,37292,33184,28550,39509,23308,25898,37496,30703,20709,39171,32371,32094,36686,36611,38542,31680,28500,32080,35489,32202,37670,20677,35641,36914,29180,30433,21185,33686,39912,39514,32147,38968,37857,24465,30169,31478,31998,33290,39378,33289,25818,37624,25084,21127,40273,32121,3525 [...]
+37406,36557,39423,38283,20977,38982,27579,35506,22718,25031,25715,24235,35122,35463,22602,20744,23532,31014,26336,34407,24011,31418,39243,28528,25844,38346,34847,33240,33802,20358,36084,34253,27396,25876,31811,38348,34349,28734,35733,25900,35261,25078,32412,29211,28651,25736,21214,28551,27138,37939,22744,39006,31852,38626,28757,35023,39881,31150,40599,21426,21237,31019,27511,28701,38584,20486,32879,34030,36899,37934,24976,28451,31806,25986,33225,37832,25088,29001,32244,31975,20841,36635, [...]
+36988,37904,29557,33256,37168,40023,36035,40801,37428,38728,23994,38936,39230,21129,[12243,40845],32894,22184,31840,22751,25871,38580,27155,23105,25695,31757,34310,30439,39025,24300,29200,25796,28407,34396,39791,36034,37682,38520,39522,37569,23650,32311,24942,28670,32209,24018,25891,23423,28772,20098,25476,36650,20523,20374,28138,32184,35542,34367,32645,37007,38012,31854,39486,39409,32097,23229,29802,30908,34718,[12218,39340],39393,21966,36023,[12230,40613],36067,36993,30622,39237,34875, [...]
+37672,37466,36031,37762,[12200,38272],24758,20497,37683,22818,35598,24396,35219,32191,32236,24287,28357,25003,38313,40180,37528,35628,35584,30045,37385,32013,38627,25747,33126,24817,39719,39186,25836,33193,25862,37312,[12227,40165],32886,22169,38007,37811,27320,29552,23527,25840,28632,37397,32016,33215,28611,36786,30247,35582,27472,40407,27590,22036,28442,30436,40848,36064,22132,40300,39449,39108,38971,36007,34315,24977,35413,28497,38935,25778,37610,20693,27192,35676,33229,[12241,40778], [...]
+21843,27683,35350,29309,37370,37467,36983,31805,35609,37666,37463,28154,35700,22649,27085,21958,22715,34196,25654,37740,27211,21932,20689,32761,31429,31434,27453,35242,23522,36629,27691,20670,38915,35531,24950,29898,31406,36264,21312,36544,39493,40818,39028,27402,21240,40306,30906,35731,39250,25854,32350,29105,38860,35469,32009,27054,32104,36575,37613,38287,28516,28753,34217,39955,36093,20632,21930,39479,25475,28544,27578,32023,31721,26348,38275,38493,36109,32341,20663,36062,29138,32057, [...]
+25885,25086,35373,32051,23529,23352,33102,28402,32882,32361,21213,32854,24107,29509,28629,35433,26178,34645,23526,35672,39387,21218,36969,37323,39166,35222,35430,22781,29560,27166,36664,26360,36118,23660,34899,27193,31466,25976,24101,38617,35504,38918,35500,30889,29197,32114,39164,39686,32883,24939,38924,35359,35494,25851,34311,35380,32901,38614,38568,32143,27506,23403,25613,32302,29795,37782,29562,25787,33274,24907,25892,36010,30321,28760,22727,35674,35527,22022,28271,29145,28644,32295, [...]
+35588,37563,38988,39636,26781,36028,37941,24307,32893,28916,37509,32113,38957,22294,22615,22296,38973,40213,39345,39389,27234,31402,35178,24398,28771,38929,33836,32178,[12209,38859],36949,22285,29234,28656,32173,33894,20553,20702,32239,35586,34907,32862,32011,31337,21839,25790,34680,28198,31401,21978,37794,28879,35491,28961,34154,22626,38695,21209,35492,37675,29351,35186,32722,37521,25138,32048,34662,36676,23805,20448,29433,22151,37697,39854,32406,36066,37532,38289,39023,38570,29694,2956 [...]
+25010,32171,38002,37129,35443,38911,38917,34157,22210,37559,26313,22063,21332,25406,33029,35559,23531,28681,35613,37573,37313,33288,37561,32137,38920,35377,32210,32396,36562,25080,36984,30316,32098,23416,21211,35426,23563,39348,35347,35338,36956,22739,40201,40232,21854,20126,35357,38329,40573,22196,38996,38331,33399,21421,30831,35578,39511,40230,26954,25562,30221,38525,30306,39178,27171,22575,35617,34277,29242,[12212,38913],26989,33865,37291,37541,38948,36986,20736,34811,34269,20740,2501 [...]
+35696,35516,35695,32377,34093,38512,37504,39154,38577,27387,23344,40441,25033,32403,29801,34722,29151,29074,34821,36111,31310,21938,25793,20653,30320,36404,20778,24962,37109,37438,29494,35480,36671,39192,[12226,39770],28417,33287,23996,35486,39729,29508,35709,38928,39341,40219,28149,36677,22290,21729,22291,32227,36960,39E3,32004,36493,38E3,38322,38642,37142,38549,36939,34292,37270,26248,38620,36617,25890,26283,36106,36124,33247,38015,26839,31432,36012,25799,21063,28580,36042,36104,36555, [...]
+35408,40779,20661,27656,30430,26028,36670,23940,26855,25136,32187,24373,28466,24115,36076,33081,36249,34756,36685,37754,36889,35998,37341,20597,35386,37806,38499,24128,30309,37165,35657,32340,32887,22519,34937,32025,25711,25842,24159,36074,28399,37912,32066,31278,33131,34886,35589,36600,30394,26205,39519,35576,35461,29165,30682,22225,36015,37956,31689,39376,23560,30938,36681,36090,27137,33674,35037,22941,22767,29376,37648,36101,22684,32180,35524,28310,28609,36039,28460,32156,32317,32305, [...]
+32068,38013,21959,21401,21428,38760,36107,21293,21297,36094,21060,21132,21108,20660,20480,20630,20757,20738,20756,20796,20791,20712,20674,20795,20752,20794,20681,31988,40652,22213,40172,35131,33248,35329,35344,35340,35349,35635,35406,35365,35393,35382,35398,35412,35416,35410,35462,35460,35455,35440,35452,35445,35436,35438,35533,35554,35425,35482,35493,{f:2,c:35473},35535,35537,35529,35547,35543,35522,35510,35574,35563,35604,35585,35556,35565,35580,35571,35558,35566,35550,35624,35740,3560 [...]
+35627,35629,35670,35673,35662,35742,35691,35734,38488,37178,37140,37172,37087,37174,37126,37192,33467,21233,24048,22538,22745,22754,22752,22746,22497,22607,22550,22610,22557,22628,34188,34131,34294,33703,33799,34031,33511,34338,34086,22603,29026,34136,34045,34126,34184,34234,29334,28366,34113,34254,34130,33984,33874,33892,33940,33845,34207,34133,40367,33939,32264,34118,34146,34078,39488,34362,37795,34167,34334,34298,34308,34282,34330,22889,23607,25451,25718,25759,25681,25692,25779,25860, [...]
+25852,25883,22064,22072,22216,22182,21764,21692,22144,22109,22112,22069,22006,22118,22130,22156,22117,22044,22062,21993,22038,22208,22029,22195,22209,22127,36705,22198,22165,22279,24131,24172,24152,24151,23943,23796,23888,23852,23975,23968,23959,23821,23992,23937,24020,24480,29559,29505,29546,29499,29547,29568,29564,39136,39219,39145,39228,{f:2,c:39146},39149,39156,39177,39185,39195,39223,39231,39235,{f:3,c:39240},39244,39266,24289,36065,25082,25006,24938,24894,24757,24884,25036,24927,25 [...]
+24887,24818,24947,24860,24978,38274,38278,38344,38286,38292,38284,38373,38317,38315,39726,38316,38334,38326,39721,38335,38333,38332,38339,38347,38356,38352,38357,38366,28739,28505,28711,28696,28668,28039,28025,28254,28590,28687,28408,28527,28150,28543,28678,28576,28683,28775,28740,28677,28535,28704,28703,28722,28712,28765,39467,36999,36885,37008,23656,24371,23285,23255,23296,23149,23304,23372,23207,23291,23307,23329,23338,23321,39380,39391,39385,39478,39515,39377,39384,39501,39498,39394, [...]
+39437,39429,39490,39469,39446,39489,39470,39480,{f:2,c:39491},39503,39525,39524,31993,32006,32002,{f:2,c:32007},32394,32028,32021,32019,32058,32050,32049,32272,32060,32064,32063,32093,32078,32115,32134,32131,32136,32190,32186,32203,32212,32196,32158,32172,32185,32163,32176,32199,32217,32215,32249,32242,32354,32230,32246,32241,32267,32225,32265,32285,32287,32286,32301,32266,32273,32381,32313,32309,32306,32326,32325,32392,32346,32338,32366,32382,32368,32367,32408,29859,29771,29903,38922,29 [...]
+29833,29862,29908,29914,38873,38878,38876,27050,27370,26776,26838,27141,26783,27355,27379,27368,27359,27273,26895,27208,26984,27071,27194,27292,27410,27422,27357,27111,27407,27414,27372,27354,27384,27315,27367,27299,27347,27358,27556,27550,27566,27563,27567,36564,36571,36594,36603,36708,36601,36604,36587,36580,36706,36602,36606,36618,36615,36613,36626,36646,{f:2,c:36638},36636,36659,36678,36692,25108,25127,29964,26311,26308,26249,26326,36033,36016,36026,36029,36100,36018,36037,36112,3604 [...]
+36075,36071,36091,35224,35244,35233,35263,35238,35247,35250,35255,27647,27660,27692,29272,26407,33110,33242,33051,33214,33121,33231,27487,{f:2,c:39086},39094,39100,39110,39112,36674,40783,26005,29036,29010,29079,29121,29148,29182,31152,31118,31146,25055,24932,25059,25095,28585,30959,30893,30824,30904,31018,31025,30820,30973,30951,30947,40853,30616,30558,30652,32646,32648,{f:3,c:37330},37337,37335,37333,37367,37351,37348,37702,37365,37369,37384,37414,37445,37393,37392,37377,37415,37380,37 [...]
+37434,37478,37431,37427,37461,37437,37432,37470,{f:2,c:37484},37439,37984,37424,37449,37448,37453,37422,37433,37944,37548,37536,37498,37546,37614,37583,37891,37603,37946,37553,37542,37799,37526,37580,37545,37877,37523,37503,37801,37530,37658,37547,37507,37899,37544,37539,37906,37688,37617,37847,37605,37616,37615,37608,37564,37597,37622,{f:2,c:37926},37571,37599,37606,37650,37638,37737,37659,37696,37633,37653,37678,37699,{f:2,c:37639},37663,37657,37733,37703,37750,37716,37732,37802,37744, [...]
+37848,37928,37767,37836,37784,37816,37823,37798,37808,37813,37964,37858,{f:2,c:37852},37837,37854,37827,37831,37841,37908,37917,37879,37989,37907,37997,37920,38009,37881,37913,37962,37938,37951,37972,37987,37758,31329,40169,40182,40199,40198,40227,40327,40469,40221,40223,40421,40239,40409,40240,40258,40478,40275,40477,40288,40274,40435,40284,40289,40339,40298,40303,40329,40344,40346,40384,40357,40361,40386,40380,40474,40403,40410,40431,40422,40434,40440,40460,40442,40475,30308,30296,3031 [...]
+c:30278},30281,30238,30267,{f:2,c:30317},30313,30322,31431,31414,35168,35123,35165,35143,35128,35172,30392,32814,32812,32889,32885,38919,{f:2,c:38926},38945,38940,28481,38950,38967,38990,38995,39027,39010,39001,39013,39020,39024,34787,34822,34566,34851,34806,34554,34799,34692,34832,34760,34833,34747,34766,32588,31716,31591,31849,31731,31744,31691,31836,31774,31787,31779,31850,31839,33380,33387,35018,32677,31986,31990,31965,32310,40617,36274,37317,37315,40570,36489,36428,36498,36474,36437 [...]
+36499,36497,36513,36451,36522,36518,35316,35318,38746,38722,38717,38724,40788,40799,40793,40800,40796,40806,40812,40810,40823,[12236,40701],40703,40713,35726,38014,37864,39799,39796,39809,39811,39822,40056,31308,39826,40031,39824,39853,39834,39850,39838,40045,39851,39837,40024,39873,40058,39985,39993,39971,39991,39872,39882,39879,39933,39894,{f:2,c:39914},39905,39908,39911,39901,39906,39920,39899,39924,39892,40029,39944,39952,39949,39954,39945,39935,39968,39986,39981,39976,39973,39977,39 [...]
+40008,39995,39989,40005,40022,40020,40018,40039,38851,38845,38857,40379,39631,39638,39637,39768,39758,39255,39260,39714,40695,40690,35180,38342,37686,24390,34068,32404,40803,22137,40725,22081,39662,35079,31296,39091,38308,39693,36852,24409,31339,39138,20642,34193,20760,25458,21067,30543,32397,26310,30637,[12228,40565],22217,40692,28635,25054,30663,28720,40629,34890,38370,38854,31844,32308,38822,40623,22220,39089,27311,32590,31984,20418,32363,40569,22190,39706,33903,31142,31858,39634,3858 [...]
+30787,{f:10,c:8560},{f:2,c:714},729,8211,8213,8229,8245,8453,8457,{f:4,c:8598},8725,8735,8739,8786,{f:2,c:8806},8895,{f:36,c:9552},{f:15,c:9601},{f:3,c:9619},{f:2,c:9660},{f:4,c:9698},9737,8853,12306,{f:2,c:12317},{f:9,c:12321},12963,{f:2,c:13198},{f:3,c:13212},13217,13252,13262,{f:2,c:13265},13269,65072,65506,65508,8481,12849,8208,12540,{f:2,c:12443},{f:2,c:12541},12294,{f:2,c:12445},{f:10,c:65097},{f:4,c:65108},{f:14,c:65113},{f:4,c:65128},12350,{f:12,c:12272},19970,{f:3,c:19972},19983 [...]
+{f:3,c:19999},20003,20006,20009,{f:2,c:20014},20017,20019,20021,20023,20028,{f:3,c:20032},20036,20038,20042,20049,20053,20055,{f:2,c:20058},{f:4,c:20066},{f:2,c:20071},{f:6,c:20074},20082,{f:10,c:20084},{f:3,c:20095},{f:2,c:20099},[12037,20101],20103,20106,20112,{f:2,c:20118},20121,{f:2,c:20124},20131,20138,{f:3,c:20143},20148,{f:4,c:20150},{f:3,c:20156},20168,20172,{f:2,c:20175},20178,{f:3,c:20186},20192,20194,{f:2,c:20198},20201,{f:3,c:20205},20209,20212,{f:3,c:20216},20220,20222,20224 [...]
+{f:2,c:20235},{f:5,c:20242},{f:2,c:20252},20257,20259,{f:2,c:20264},{f:3,c:20268},20273,20275,20277,20279,20281,20283,{f:5,c:20286},{f:2,c:20292},{f:6,c:20295},20306,20308,20310,{f:2,c:20321},20326,20328,{f:2,c:20330},{f:2,c:20333},{f:2,c:20337},20341,{f:4,c:20343},20349,{f:3,c:20352},20357,20359,20362,20364,20366,20368,{f:2,c:20370},20373,{f:3,c:20376},20380,{f:2,c:20382},{f:2,c:20385},20388,20395,20397,{f:5,c:20400},{f:9,c:20406},{f:2,c:20416},{f:4,c:20422},{f:3,c:20427},{f:5,c:20434}, [...]
+20450,{f:2,c:20452},20455,{f:2,c:20459},20464,20466,{f:4,c:20468},20473,{f:3,c:20475},20479,{f:5,c:20481},{f:2,c:20487},20490,20494,20496,20499,{f:3,c:20501},20507,{f:2,c:20509},20512,{f:3,c:20514},20519,{f:11,c:20527},20539,20541,{f:4,c:20543},{f:3,c:20548},{f:2,c:20554},20557,{f:5,c:20560},{f:4,c:20566},20571,{f:8,c:20573},{f:6,c:20582},{f:7,c:20589},{f:3,c:20600},{f:2,c:20604},{f:4,c:20609},{f:2,c:20614},{f:4,c:20617},{f:8,c:20622},20631,{f:8,c:20634},20644,20646,{f:2,c:20650},{f:4,c: [...]
+{f:2,c:20664},{f:2,c:20668},{f:3,c:20671},{f:2,c:20675},{f:3,c:20678},{f:5,c:20682},20688,{f:3,c:20690},{f:3,c:20695},{f:3,c:20699},{f:6,c:20703},{f:3,c:20713},{f:4,c:20719},20724,{f:3,c:20726},20730,{f:4,c:20732},20737,20739,20741,20746,{f:4,c:20748},20753,20755,{f:2,c:20758},{f:6,c:20761},20768,{f:8,c:20770},{f:7,c:20779},{f:4,c:20787},{f:2,c:20792},{f:2,c:20797},20802,20807,20810,20812,{f:3,c:20814},20819,{f:3,c:20823},20827,{f:5,c:20829},{f:2,c:20835},{f:2,c:20838},20842,20847,20850, [...]
+c:20862},{f:2,c:20867},{f:2,c:20870},{f:2,c:20874},{f:4,c:20878},{f:2,c:20883},20888,20890,{f:3,c:20893},20897,20899,{f:5,c:20902},{f:2,c:20909},20916,{f:3,c:20920},{f:2,c:20926},{f:3,c:20929},20933,20936,20938,20942,20944,{f:9,c:20946},20956,{f:2,c:20958},{f:2,c:20962},{f:6,c:20965},20972,20974,20978,20980,20983,20990,{f:2,c:20996},21001,{f:2,c:21003},{f:2,c:21007},{f:3,c:21011},21020,{f:2,c:21022},{f:3,c:21025},{f:3,c:21029},21034,21036,21039,{f:2,c:21041},{f:2,c:21044},21052,21054,{f: [...]
+{f:2,c:21064},{f:2,c:21070},{f:2,c:21074},21077,{f:4,c:21079},21085,{f:2,c:21087},{f:3,c:21090},21094,21096,{f:3,c:21099},{f:2,c:21104},21107,{f:7,c:21110},21118,21120,{f:3,c:21124},21131,{f:2,c:21134},21138,{f:7,c:21140},21148,{f:4,c:21156},{f:3,c:21166},{f:10,c:21172},21184,21186,{f:3,c:21188},21192,21194,{f:4,c:21196},21201,{f:2,c:21203},21207,21210,21212,{f:2,c:21216},21219,{f:11,c:21221},{f:3,c:21234},{f:2,c:21238},{f:3,c:21243},{f:4,c:21249},21255,{f:4,c:21257},21262,{f:4,c:21265}, [...]
+c:21275},{f:2,c:21278},21282,{f:2,c:21284},{f:3,c:21287},{f:2,c:21291},21296,{f:6,c:21298},[12054,21304],{f:2,c:21308},21314,21316,21318,{f:3,c:21323},21328,{f:2,c:21336},21339,21341,21349,21352,21354,{f:2,c:21356},21362,21366,21369,{f:4,c:21371},{f:2,c:21376},21379,{f:2,c:21383},21386,{f:7,c:21390},{f:2,c:21398},{f:2,c:21403},21406,21409,21412,21415,{f:3,c:21418},{f:3,c:21423},21427,21429,{f:4,c:21431},{f:3,c:21436},21440,{f:4,c:21444},{f:3,c:21454},{f:2,c:21458},21461,21466,{f:3,c:2146 [...]
+21479,21492,21498,{f:3,c:21502},21506,21509,21511,21515,21524,{f:3,c:21528},21532,21538,{f:2,c:21540},21546,21552,21555,{f:2,c:21558},21562,21565,21567,{f:2,c:21569},{f:2,c:21572},21575,21577,{f:4,c:21580},21585,21594,{f:5,c:21597},21603,21605,21607,{f:8,c:21609},21620,{f:2,c:21625},{f:2,c:21630},21633,21635,21637,{f:4,c:21639},21645,21649,21651,{f:2,c:21655},21660,{f:5,c:21662},21669,21678,21680,21682,{f:3,c:21685},{f:2,c:21689},21694,21699,21701,{f:2,c:21706},21718,21720,21723,21728,{f [...]
+{f:2,c:21739},{f:3,c:21743},{f:6,c:21748},21755,21758,21760,{f:2,c:21762},21765,21768,{f:5,c:21770},{f:2,c:21778},{f:6,c:21781},{f:4,c:21788},21793,{f:2,c:21797},{f:2,c:21800},21803,21805,21810,{f:3,c:21812},{f:4,c:21816},21821,21824,21826,21829,{f:2,c:21831},{f:4,c:21835},{f:2,c:21841},21844,{f:5,c:21847},21853,{f:2,c:21855},{f:2,c:21858},{f:2,c:21864},21867,{f:6,c:21871},{f:2,c:21881},21885,21887,{f:2,c:21893},{f:3,c:21900},21904,{f:2,c:21906},{f:3,c:21909},{f:2,c:21914},21918,{f:7,c:2 [...]
+c:21928},21931,21933,{f:2,c:21935},21940,21942,21944,21946,21948,{f:5,c:21951},21960,{f:2,c:21962},{f:2,c:21967},21973,{f:3,c:21975},21979,21982,21984,21986,21991,{f:2,c:21997},{f:2,c:22E3},22004,{f:5,c:22008},22015,{f:4,c:22018},22023,{f:2,c:22026},{f:4,c:22032},22037,{f:2,c:22041},22045,{f:3,c:22048},{f:2,c:22053},22056,{f:2,c:22058},22067,22071,22074,{f:3,c:22076},22080,{f:10,c:22082},{f:5,c:22095},{f:2,c:22101},{f:2,c:22106},{f:2,c:22110},22113,22115,22119,{f:2,c:22125},22128,22131,2 [...]
+22138,{f:3,c:22141},{f:4,c:22145},{f:4,c:22152},22157,{f:3,c:22160},22164,{f:3,c:22166},{f:9,c:22170},{f:2,c:22180},22183,{f:5,c:22185},{f:3,c:22192},22197,{f:4,c:22200},{f:3,c:22205},{f:2,c:22211},{f:2,c:22214},22219,{f:4,c:22221},{f:2,c:22226},{f:2,c:22229},{f:2,c:22232},22236,22243,{f:6,c:22245},22252,{f:2,c:22254},{f:2,c:22258},{f:3,c:22262},{f:2,c:22267},{f:3,c:22272},22277,22284,{f:4,c:22286},{f:2,c:22292},22295,{f:3,c:22297},{f:2,c:22301},{f:3,c:22304},{f:4,c:22308},22315,{f:2,c:2 [...]
+c:22324},{f:2,c:22332},22335,22337,{f:4,c:22339},{f:2,c:22344},22347,{f:5,c:22354},{f:2,c:22360},{f:2,c:22370},22373,22375,22380,22382,{f:3,c:22384},{f:2,c:22388},{f:3,c:22392},{f:5,c:22397},{f:4,c:22407},{f:5,c:22413},{f:7,c:22420},{f:4,c:22428},22437,22440,22442,22444,{f:3,c:22447},22451,{f:3,c:22453},{f:9,c:22457},{f:7,c:22468},{f:2,c:22476},{f:2,c:22480},22483,{f:2,c:22486},{f:2,c:22491},22494,{f:2,c:22498},{f:8,c:22501},22510,{f:4,c:22512},{f:2,c:22517},{f:2,c:22523},{f:2,c:22526},2 [...]
+c:22531},{f:2,c:22536},22540,{f:3,c:22542},{f:3,c:22546},{f:2,c:22551},{f:3,c:22554},22559,{f:2,c:22562},{f:5,c:22565},{f:4,c:22571},{f:2,c:22578},{f:14,c:22582},{f:5,c:22597},22606,22608,22611,{f:2,c:22613},{f:5,c:22617},{f:3,c:22623},22627,{f:5,c:22630},{f:8,c:22637},{f:3,c:22646},{f:4,c:22650},22655,22658,22660,{f:3,c:22662},{f:7,c:22667},{f:5,c:22676},22683,22685,{f:8,c:22688},{f:4,c:22698},{f:4,c:22703},{f:7,c:22708},22717,{f:2,c:22719},{f:3,c:22722},22726,{f:9,c:22728},22738,22740, [...]
+{f:3,c:22747},22753,22755,{f:4,c:22757},22762,22765,{f:2,c:22769},{f:2,c:22772},{f:2,c:22775},{f:2,c:22779},{f:4,c:22782},22787,{f:2,c:22789},{f:2,c:22792},[12066,22794],{f:2,c:22795},22798,{f:4,c:22800},{f:2,c:22807},22811,{f:2,c:22813},{f:2,c:22816},22819,22822,22824,22828,22832,{f:2,c:22834},{f:2,c:22837},22843,22845,{f:2,c:22847},22851,{f:2,c:22853},22858,{f:2,c:22860},22864,{f:2,c:22866},22873,{f:5,c:22875},22881,{f:2,c:22883},{f:3,c:22886},22891,22893,{f:4,c:22895},22901,22903,{f:3 [...]
+{f:3,c:22910},22917,22921,{f:2,c:22923},{f:4,c:22926},{f:2,c:22932},22936,{f:3,c:22938},{f:4,c:22943},{f:2,c:22950},{f:2,c:22956},{f:2,c:22960},{f:6,c:22963},22970,{f:2,c:22972},{f:7,c:22975},{f:3,c:22983},{f:4,c:22988},{f:2,c:22997},23001,23003,{f:5,c:23006},23012,{f:2,c:23014},{f:3,c:23017},{f:12,c:23021},23034,{f:3,c:23036},23040,23042,{f:2,c:23050},{f:4,c:23053},23058,{f:4,c:23060},{f:3,c:23065},{f:2,c:23069},{f:2,c:23073},23076,{f:3,c:23078},{f:7,c:23082},23091,23093,{f:5,c:23095},{ [...]
+{f:4,c:23106},{f:2,c:23111},{f:10,c:23115},{f:4,c:23126},{f:7,c:23131},{f:3,c:23139},{f:2,c:23144},{f:2,c:23147},{f:6,c:23150},{f:2,c:23160},{f:4,c:23163},{f:18,c:23168},{f:7,c:23187},{f:11,c:23196},{f:2,c:23208},{f:7,c:23211},23220,{f:2,c:23222},{f:4,c:23225},{f:2,c:23231},{f:6,c:23235},{f:2,c:23242},{f:5,c:23245},23251,23253,{f:3,c:23257},{f:3,c:23261},23266,{f:2,c:23268},{f:2,c:23271},23274,{f:5,c:23276},{f:3,c:23282},{f:5,c:23286},{f:4,c:23292},{f:7,c:23297},23306,{f:9,c:23309},23320 [...]
+{f:8,c:23330},{f:5,c:23339},23345,23347,{f:2,c:23349},{f:7,c:23353},{f:11,c:23361},{f:3,c:23373},23378,23382,23390,{f:2,c:23392},{f:2,c:23399},{f:3,c:23405},23410,23412,{f:2,c:23414},23417,{f:2,c:23419},23422,23426,23430,23434,{f:2,c:23437},{f:3,c:23440},23444,23446,23455,{f:3,c:23463},{f:4,c:23468},{f:2,c:23473},23479,{f:3,c:23482},{f:2,c:23488},23491,{f:4,c:23496},{f:3,c:23501},23505,{f:9,c:23508},23520,23523,23530,23533,23535,{f:4,c:23537},23543,{f:2,c:23549},23552,{f:2,c:23554},23557 [...]
+{f:2,c:23570},23575,23577,23579,{f:4,c:23582},23587,23590,{f:4,c:23592},{f:4,c:23597},{f:2,c:23602},{f:2,c:23605},{f:2,c:23619},{f:2,c:23622},{f:2,c:23628},{f:3,c:23634},{f:3,c:23638},{f:4,c:23642},23647,23655,{f:3,c:23657},23661,23664,{f:7,c:23666},{f:4,c:23675},23680,{f:5,c:23683},{f:3,c:23689},{f:2,c:23694},{f:2,c:23698},23701,{f:4,c:23709},{f:5,c:23716},23722,{f:3,c:23726},23730,23732,23734,{f:4,c:23737},23742,23744,{f:2,c:23746},{f:6,c:23749},{f:6,c:23756},{f:6,c:23763},{f:7,c:23770 [...]
+23783,23785,{f:2,c:23787},{f:2,c:23790},{f:3,c:23793},23797,{f:4,c:23799},23804,{f:4,c:23806},{f:2,c:23812},{f:5,c:23816},{f:5,c:23823},23829,{f:3,c:23832},{f:2,c:23836},{f:5,c:23839},23845,23848,{f:2,c:23850},{f:5,c:23855},{f:8,c:23861},{f:8,c:23871},{f:2,c:23880},{f:3,c:23885},{f:7,c:23889},{f:2,c:23897},23900,{f:11,c:23902},23914,{f:2,c:23917},{f:4,c:23920},{f:12,c:23925},23939,{f:2,c:23941},{f:15,c:23944},23960,{f:3,c:23962},{f:2,c:23966},{f:6,c:23969},{f:15,c:23976},23993,23995,{f:8 [...]
+{f:5,c:24006},24012,{f:4,c:24014},24019,{f:6,c:24021},24028,{f:2,c:24031},{f:2,c:24035},24042,{f:2,c:24044},{f:2,c:24053},{f:5,c:24056},{f:2,c:24063},24068,24071,{f:3,c:24073},{f:2,c:24077},{f:2,c:24082},24087,{f:7,c:24094},{f:3,c:24104},24108,{f:2,c:24111},24114,{f:2,c:24116},{f:2,c:24121},{f:2,c:24126},24129,{f:6,c:24134},{f:7,c:24141},24150,{f:2,c:24153},{f:2,c:24156},24160,{f:7,c:24164},{f:5,c:24173},24181,24183,{f:3,c:24193},24197,{f:2,c:24200},{f:3,c:24204},24210,24216,24219,24221, [...]
+{f:3,c:24232},24236,{f:5,c:24238},24244,{f:4,c:24250},{f:10,c:24255},{f:6,c:24267},{f:2,c:24276},{f:4,c:24279},{f:3,c:24284},{f:4,c:24292},24297,24299,{f:6,c:24301},24309,{f:2,c:24312},{f:3,c:24315},{f:3,c:24325},24329,{f:3,c:24332},24336,24338,24340,24342,{f:2,c:24345},{f:3,c:24348},{f:4,c:24353},24360,{f:2,c:24363},24366,24368,24370,24372,{f:3,c:24374},24379,{f:3,c:24381},{f:5,c:24385},24391,{f:3,c:24393},24397,24399,24401,24404,{f:3,c:24410},{f:3,c:24414},24419,24421,{f:2,c:24423},244 [...]
+24434,{f:3,c:24436},24440,24442,{f:3,c:24445},24451,24454,{f:3,c:24461},{f:2,c:24467},24470,{f:2,c:24474},24477,24479,{f:6,c:24482},{f:2,c:24491},{f:6,c:24495},24502,24504,{f:2,c:24506},{f:5,c:24510},{f:2,c:24519},{f:2,c:24522},24526,{f:3,c:24531},{f:3,c:24538},{f:2,c:24542},{f:2,c:24546},{f:2,c:24549},{f:2,c:24552},24556,{f:2,c:24559},{f:3,c:24562},{f:2,c:24566},{f:2,c:24569},24572,{f:3,c:24583},{f:2,c:24587},{f:2,c:24592},24595,{f:2,c:24599},24602,{f:2,c:24606},{f:3,c:24610},{f:3,c:246 [...]
+c:24624},{f:5,c:24630},{f:2,c:24637},24640,{f:7,c:24644},24652,{f:2,c:24654},24657,{f:2,c:24659},{f:3,c:24662},{f:2,c:24667},{f:4,c:24670},{f:2,c:24677},24686,{f:2,c:24689},{f:2,c:24692},24695,24702,{f:3,c:24704},{f:4,c:24709},{f:2,c:24714},{f:4,c:24718},24723,24725,{f:3,c:24727},24732,24734,{f:2,c:24737},{f:2,c:24740},24743,{f:2,c:24745},24750,24752,24755,24759,{f:2,c:24761},{f:8,c:24765},{f:3,c:24775},{f:5,c:24780},{f:3,c:24786},{f:2,c:24790},24793,24795,24798,{f:4,c:24802},24810,24821 [...]
+{f:4,c:24828},{f:4,c:24834},24839,{f:3,c:24842},{f:5,c:24848},{f:4,c:24854},{f:2,c:24861},{f:2,c:24865},24869,{f:3,c:24872},{f:8,c:24876},{f:2,c:24885},{f:6,c:24888},{f:8,c:24896},24905,24909,{f:2,c:24911},{f:3,c:24914},{f:2,c:24918},24921,{f:2,c:24923},24926,{f:2,c:24928},{f:2,c:24933},24937,{f:2,c:24940},24943,{f:2,c:24945},24948,{f:10,c:24952},{f:7,c:24963},{f:2,c:24972},24975,24979,{f:5,c:24981},{f:2,c:24987},{f:6,c:24990},{f:2,c:24997},25002,25005,{f:3,c:25007},{f:3,c:25011},{f:6,c: [...]
+c:25023},{f:4,c:25027},{f:4,c:25037},25043,{f:9,c:25045},{f:3,c:25056},{f:2,c:25060},25063,{f:9,c:25065},{f:2,c:25075},25081,25083,25085,{f:5,c:25089},25097,25107,25113,{f:3,c:25116},25120,25123,25126,{f:2,c:25128},25131,25133,25135,25137,25141,[12094,25142],{f:5,c:25144},25154,{f:3,c:25156},25162,{f:2,c:25167},{f:3,c:25173},{f:2,c:25177},{f:7,c:25180},{f:2,c:25188},25192,{f:2,c:25201},{f:2,c:25204},{f:2,c:25207},{f:2,c:25210},25213,{f:3,c:25217},{f:4,c:25221},{f:6,c:25227},25236,25241,{ [...]
+25251,{f:2,c:25254},{f:2,c:25257},{f:4,c:25261},{f:3,c:25266},{f:3,c:25270},25274,25278,{f:2,c:25280},25283,25291,25295,25297,25301,{f:2,c:25309},{f:2,c:25312},25316,{f:2,c:25322},25328,25330,25333,{f:4,c:25336},25344,{f:4,c:25347},{f:4,c:25354},{f:2,c:25359},{f:4,c:25362},{f:3,c:25367},25372,{f:2,c:25382},25385,{f:3,c:25388},{f:2,c:25392},{f:6,c:25395},{f:2,c:25403},{f:3,c:25407},25412,{f:2,c:25415},25418,{f:4,c:25425},{f:8,c:25430},25440,{f:3,c:25444},25450,25452,{f:2,c:25455},{f:3,c:2 [...]
+c:25464},{f:4,c:25468},25473,{f:2,c:25477},25483,25485,25489,{f:3,c:25491},25495,{f:7,c:25497},25505,25508,25510,25515,25519,{f:2,c:25521},{f:2,c:25525},25529,25531,25533,25535,{f:3,c:25537},25541,{f:2,c:25543},{f:3,c:25546},25553,{f:3,c:25555},{f:3,c:25559},{f:3,c:25563},25567,25570,{f:5,c:25572},{f:2,c:25579},{f:3,c:25583},25587,25589,25591,{f:4,c:25593},25598,{f:2,c:25603},{f:5,c:25606},25614,{f:2,c:25617},{f:2,c:25621},{f:3,c:25624},25629,25631,{f:4,c:25634},{f:3,c:25639},25643,{f:6, [...]
+25653,{f:3,c:25655},{f:2,c:25659},25662,25664,{f:2,c:25666},25673,{f:6,c:25675},25683,{f:3,c:25685},{f:3,c:25689},25693,{f:7,c:25696},25704,{f:3,c:25706},25710,{f:3,c:25712},{f:2,c:25716},25719,{f:6,c:25724},25731,25734,{f:8,c:25737},25748,{f:2,c:25751},{f:4,c:25754},{f:3,c:25760},{f:3,c:25766},25770,25775,25777,25780,25782,25785,25789,25795,25798,{f:2,c:25800},25804,25807,25809,25811,{f:2,c:25813},25817,{f:3,c:25819},25823,25825,25827,25829,{f:5,c:25831},{f:2,c:25837},25843,{f:2,c:25845 [...]
+25853,25855,{f:3,c:25857},25861,{f:2,c:25863},{f:5,c:25866},{f:2,c:25872},25875,25877,25879,25882,25884,{f:4,c:25886},{f:4,c:25894},25901,{f:4,c:25904},25911,25914,{f:2,c:25916},{f:5,c:25920},{f:2,c:25926},{f:2,c:25930},{f:2,c:25933},25936,{f:3,c:25938},25944,25946,25948,{f:3,c:25951},{f:2,c:25956},{f:4,c:25959},{f:3,c:25965},25969,25971,25974,{f:9,c:25977},{f:3,c:25988},{f:3,c:25992},{f:3,c:25997},26002,26004,26006,26008,26010,{f:2,c:26013},26016,{f:2,c:26018},26022,26024,26026,26030,{f [...]
+26040,{f:2,c:26042},{f:3,c:26046},26050,{f:4,c:26055},26061,{f:2,c:26064},{f:3,c:26067},{f:8,c:26072},26081,{f:2,c:26083},{f:2,c:26090},{f:4,c:26098},{f:2,c:26104},{f:5,c:26107},26113,{f:2,c:26116},{f:3,c:26119},26123,26125,{f:3,c:26128},{f:3,c:26134},{f:3,c:26138},26142,{f:4,c:26145},26150,{f:4,c:26153},26158,26160,{f:2,c:26162},{f:5,c:26167},26173,{f:2,c:26175},{f:7,c:26180},{f:2,c:26189},{f:2,c:26192},{f:2,c:26200},{f:2,c:26203},26206,26208,{f:2,c:26210},26213,26215,{f:5,c:26217},{f:3 [...]
+26229,{f:2,c:26232},{f:3,c:26235},{f:3,c:26239},26243,{f:2,c:26245},{f:2,c:26250},{f:4,c:26253},{f:4,c:26258},{f:5,c:26264},{f:4,c:26270},{f:4,c:26275},{f:2,c:26281},{f:2,c:26284},{f:5,c:26287},{f:4,c:26293},{f:4,c:26298},{f:5,c:26303},26309,26312,{f:12,c:26314},{f:2,c:26327},26330,{f:2,c:26334},{f:5,c:26337},{f:2,c:26343},{f:2,c:26346},{f:3,c:26349},26353,{f:2,c:26357},{f:2,c:26362},26365,{f:2,c:26369},{f:4,c:26372},26380,{f:2,c:26382},{f:3,c:26385},26390,{f:3,c:26392},26396,26398,{f:6, [...]
+26409,26414,26416,{f:2,c:26418},{f:4,c:26422},{f:2,c:26427},{f:2,c:26430},26433,{f:2,c:26436},26439,{f:2,c:26442},26445,26450,{f:2,c:26452},{f:5,c:26455},26461,{f:3,c:26466},{f:2,c:26470},{f:2,c:26475},26478,26484,26486,{f:4,c:26488},26493,26496,{f:2,c:26498},{f:2,c:26501},26504,26506,{f:4,c:26508},{f:4,c:26513},26518,26521,26523,{f:3,c:26527},26532,26534,26537,26540,26542,{f:2,c:26545},26548,{f:8,c:26553},26562,{f:10,c:26565},{f:3,c:26581},26587,26591,26593,{f:2,c:26595},{f:3,c:26598},{ [...]
+{f:2,c:26605},26610,{f:8,c:26613},26622,{f:4,c:26625},26630,26637,26640,26642,{f:2,c:26644},{f:5,c:26648},{f:3,c:26654},{f:7,c:26658},{f:7,c:26667},{f:3,c:26676},{f:2,c:26682},26687,26695,26699,26701,26703,26706,{f:10,c:26710},26730,{f:8,c:26732},26741,{f:9,c:26744},26754,26756,{f:8,c:26759},{f:3,c:26768},{f:3,c:26772},{f:4,c:26777},26782,{f:2,c:26784},{f:3,c:26787},{f:4,c:26793},26798,{f:2,c:26801},26804,{f:10,c:26806},26817,{f:6,c:26819},26826,26828,{f:4,c:26830},{f:2,c:26835},26841,{f [...]
+{f:2,c:26849},{f:3,c:26852},{f:6,c:26856},26863,{f:3,c:26866},{f:3,c:26870},26875,{f:4,c:26877},{f:3,c:26882},{f:5,c:26886},26892,26897,{f:12,c:26899},{f:3,c:26913},{f:8,c:26917},{f:2,c:26926},{f:3,c:26929},{f:4,c:26933},{f:3,c:26938},26942,{f:2,c:26944},{f:7,c:26947},{f:8,c:26955},{f:2,c:26965},{f:2,c:26968},{f:2,c:26971},26975,{f:2,c:26977},{f:2,c:26980},26983,{f:2,c:26985},26988,{f:2,c:26991},{f:3,c:26994},26998,{f:2,c:27002},{f:3,c:27005},27009,27011,27013,{f:3,c:27018},{f:6,c:27022} [...]
+{f:2,c:27033},{f:10,c:27037},27049,27052,{f:2,c:27055},{f:2,c:27058},{f:2,c:27061},{f:3,c:27064},{f:3,c:27068},27072,{f:8,c:27074},27087,{f:3,c:27089},{f:6,c:27093},{f:3,c:27100},{f:6,c:27105},{f:5,c:27112},{f:4,c:27118},{f:9,c:27124},27134,27136,{f:2,c:27139},{f:4,c:27142},{f:8,c:27147},{f:3,c:27156},{f:4,c:27162},27168,27170,{f:4,c:27172},27177,{f:4,c:27179},27184,{f:3,c:27186},{f:2,c:27190},{f:2,c:27195},{f:5,c:27199},{f:2,c:27205},{f:2,c:27209},{f:4,c:27212},{f:7,c:27217},27226,{f:3, [...]
+27232,{f:2,c:27235},{f:11,c:27238},{f:7,c:27250},{f:2,c:27258},{f:3,c:27261},{f:3,c:27265},{f:4,c:27269},{f:4,c:27274},27279,{f:2,c:27282},{f:2,c:27285},{f:4,c:27288},{f:3,c:27293},27297,{f:5,c:27300},27306,{f:2,c:27309},{f:3,c:27312},{f:4,c:27316},{f:2,c:27321},{f:7,c:27324},{f:15,c:27332},{f:6,c:27348},27356,{f:7,c:27360},27369,27371,{f:6,c:27373},{f:4,c:27380},{f:2,c:27385},{f:8,c:27388},{f:5,c:27397},{f:4,c:27403},{f:2,c:27408},{f:3,c:27411},{f:7,c:27415},27423,{f:2,c:27429},{f:10,c: [...]
+c:27443},27448,{f:2,c:27451},{f:4,c:27455},{f:2,c:27460},27464,{f:2,c:27466},{f:3,c:27469},{f:8,c:27473},{f:5,c:27482},27488,{f:2,c:27496},{f:7,c:27499},{f:4,c:27507},27514,{f:4,c:27517},27525,27528,27532,{f:4,c:27534},{f:2,c:27540},27543,27545,{f:2,c:27548},{f:2,c:27551},{f:2,c:27554},{f:5,c:27557},{f:2,c:27564},{f:2,c:27568},27574,{f:2,c:27576},{f:3,c:27580},27584,{f:2,c:27587},{f:4,c:27591},27596,27598,{f:2,c:27600},27608,27610,{f:5,c:27612},{f:8,c:27618},{f:3,c:27628},{f:3,c:27632},2 [...]
+c:27638},{f:3,c:27642},27646,{f:5,c:27648},{f:3,c:27657},27662,27666,27671,{f:3,c:27676},27680,27685,27693,27697,27699,{f:2,c:27702},{f:4,c:27705},{f:2,c:27710},{f:3,c:27715},27720,{f:5,c:27723},{f:3,c:27729},27734,{f:3,c:27736},{f:2,c:27746},{f:3,c:27749},{f:5,c:27755},27761,27763,27765,{f:2,c:27767},{f:3,c:27770},{f:2,c:27775},27780,27783,{f:2,c:27786},{f:2,c:27789},{f:2,c:27793},{f:4,c:27797},27802,{f:3,c:27804},27808,27810,27816,27820,{f:2,c:27823},{f:4,c:27828},27834,{f:4,c:27840},{ [...]
+27851,{f:3,c:27853},{f:2,c:27857},{f:3,c:27864},{f:2,c:27868},27871,27876,{f:2,c:27878},27881,{f:2,c:27884},27890,27892,27897,{f:2,c:27903},{f:2,c:27906},{f:2,c:27909},{f:3,c:27912},27917,{f:3,c:27919},{f:4,c:27923},27928,{f:2,c:27932},{f:6,c:27935},27942,{f:2,c:27944},{f:2,c:27948},{f:2,c:27951},27956,{f:3,c:27958},27962,{f:2,c:27967},27970,27972,27977,27980,27984,{f:4,c:27989},27995,27997,27999,{f:2,c:28001},{f:2,c:28004},{f:2,c:28007},{f:3,c:28011},{f:4,c:28016},{f:2,c:28021},{f:2,c:2 [...]
+c:28029},{f:2,c:28035},28038,{f:2,c:28042},28045,{f:2,c:28047},28050,{f:5,c:28054},28060,28066,28069,{f:2,c:28076},{f:2,c:28080},{f:2,c:28083},{f:2,c:28086},{f:6,c:28089},{f:3,c:28097},{f:3,c:28104},{f:4,c:28109},{f:4,c:28114},28119,{f:3,c:28122},28127,{f:2,c:28130},28133,{f:3,c:28135},28141,{f:2,c:28143},28146,28148,28152,{f:8,c:28157},{f:4,c:28166},28171,28175,{f:2,c:28178},28181,{f:2,c:28184},{f:2,c:28187},{f:2,c:28190},28194,{f:2,c:28199},28202,28206,{f:2,c:28208},28211,{f:3,c:28213} [...]
+c:28219},{f:4,c:28223},{f:8,c:28229},{f:4,c:28239},28245,28247,{f:2,c:28249},{f:2,c:28252},{f:11,c:28256},{f:2,c:28268},{f:14,c:28272},{f:3,c:28288},28292,{f:2,c:28295},{f:5,c:28298},{f:5,c:28305},28311,{f:3,c:28313},28318,{f:2,c:28320},{f:2,c:28323},28326,{f:2,c:28328},{f:4,c:28331},28336,28339,28341,{f:2,c:28344},28348,{f:3,c:28350},28355,28358,{f:3,c:28360},28365,28368,28370,28374,{f:2,c:28376},{f:3,c:28379},28387,28391,{f:2,c:28394},{f:2,c:28397},{f:2,c:28400},28403,{f:2,c:28405},{f: [...]
+28416,{f:3,c:28419},{f:2,c:28423},{f:5,c:28426},{f:3,c:28432},{f:4,c:28438},{f:5,c:28443},28449,{f:4,c:28453},28462,28464,{f:2,c:28468},28471,{f:5,c:28473},28480,{f:4,c:28482},{f:3,c:28488},28492,{f:3,c:28494},{f:2,c:28498},{f:3,c:28501},{f:2,c:28506},28509,{f:3,c:28511},28515,28517,{f:6,c:28519},28529,28531,{f:2,c:28533},28537,28539,{f:2,c:28541},{f:3,c:28545},28549,{f:2,c:28554},{f:8,c:28559},{f:4,c:28568},{f:3,c:28573},{f:2,c:28578},{f:2,c:28581},28584,{f:4,c:28586},{f:2,c:28591},2859 [...]
+{f:2,c:28599},{f:6,c:28602},{f:5,c:28612},{f:7,c:28618},{f:2,c:28627},{f:2,c:28630},{f:2,c:28633},{f:2,c:28636},{f:2,c:28642},{f:6,c:28645},{f:2,c:28652},{f:8,c:28658},28667,28669,{f:6,c:28671},{f:2,c:28679},28682,{f:3,c:28684},28688,{f:3,c:28690},{f:2,c:28694},28697,28700,28702,{f:2,c:28705},{f:3,c:28708},{f:7,c:28713},28721,{f:2,c:28723},{f:3,c:28726},{f:4,c:28730},{f:4,c:28735},{f:7,c:28741},{f:2,c:28749},28752,{f:3,c:28754},{f:2,c:28758},{f:4,c:28761},{f:4,c:28767},{f:2,c:28773},{f:3 [...]
+28782,{f:4,c:28785},28791,{f:3,c:28793},28797,{f:4,c:28801},{f:3,c:28806},{f:3,c:28811},{f:3,c:28815},28819,{f:2,c:28823},{f:2,c:28826},{f:13,c:28830},28848,28850,{f:3,c:28852},28858,{f:2,c:28862},{f:4,c:28868},28873,{f:4,c:28875},{f:8,c:28880},28890,{f:3,c:28892},{f:4,c:28896},28901,28906,28910,{f:4,c:28912},{f:2,c:28917},28920,{f:3,c:28922},{f:11,c:28926},{f:5,c:28939},{f:2,c:28945},28948,28951,{f:6,c:28955},{f:4,c:28962},{f:8,c:28967},{f:4,c:28978},{f:14,c:28983},{f:3,c:28998},29003,2 [...]
+c:29007},{f:9,c:29011},29021,{f:3,c:29023},29027,29029,{f:2,c:29034},29037,{f:3,c:29039},{f:4,c:29044},29049,{f:2,c:29051},{f:6,c:29054},{f:5,c:29061},{f:4,c:29067},{f:2,c:29072},29075,{f:2,c:29077},{f:5,c:29082},{f:7,c:29089},{f:3,c:29097},{f:4,c:29101},29106,29108,{f:3,c:29110},{f:4,c:29114},{f:2,c:29119},29122,{f:4,c:29124},{f:5,c:29129},{f:3,c:29135},29139,{f:3,c:29142},{f:2,c:29146},{f:2,c:29149},{f:4,c:29153},{f:5,c:29160},{f:5,c:29167},{f:4,c:29173},{f:2,c:29178},29181,{f:7,c:2918 [...]
+{f:2,c:29198},{f:10,c:29201},29212,{f:10,c:29214},29225,29227,{f:3,c:29229},{f:2,c:29235},29244,{f:7,c:29248},{f:3,c:29257},{f:4,c:29262},{f:3,c:29267},29271,29274,29276,29278,29280,{f:3,c:29283},29288,{f:4,c:29290},{f:2,c:29296},{f:2,c:29299},{f:3,c:29302},{f:2,c:29307},{f:2,c:29314},{f:5,c:29317},29324,29326,{f:2,c:29328},{f:3,c:29331},{f:8,c:29335},{f:2,c:29344},{f:4,c:29347},{f:4,c:29352},29358,{f:3,c:29361},29365,{f:6,c:29370},{f:3,c:29381},{f:4,c:29385},29391,29393,{f:4,c:29395},29 [...]
+c:29402},29407,{f:6,c:29410},{f:2,c:29418},{f:2,c:29429},{f:3,c:29438},29442,{f:6,c:29444},{f:3,c:29451},{f:4,c:29455},29460,{f:3,c:29464},{f:2,c:29471},{f:2,c:29475},{f:3,c:29478},29485,{f:2,c:29487},{f:2,c:29490},29493,29498,{f:2,c:29500},29504,{f:2,c:29506},{f:7,c:29510},{f:2,c:29518},29521,{f:4,c:29523},{f:8,c:29528},{f:7,c:29537},29545,29550,29553,{f:2,c:29555},29558,29561,29565,29567,{f:3,c:29569},{f:2,c:29573},29576,29578,{f:2,c:29580},{f:2,c:29583},{f:4,c:29586},{f:4,c:29591},{f: [...]
+{f:2,c:29600},{f:6,c:29603},29610,{f:2,c:29612},29617,{f:3,c:29620},{f:2,c:29624},{f:4,c:29628},29633,{f:5,c:29635},{f:2,c:29643},29646,{f:7,c:29650},{f:4,c:29658},29663,{f:4,c:29665},29670,29672,{f:3,c:29674},{f:4,c:29678},{f:11,c:29683},{f:4,c:29695},29700,{f:2,c:29703},{f:4,c:29707},{f:9,c:29713},{f:6,c:29724},{f:2,c:29731},29735,29737,29739,29741,29743,{f:2,c:29745},{f:5,c:29751},{f:2,c:29757},29760,{f:9,c:29762},{f:9,c:29772},29782,29784,29789,{f:3,c:29792},{f:5,c:29796},{f:2,c:2980 [...]
+{f:5,c:29809},{f:6,c:29816},29823,29826,{f:3,c:29828},29832,29834,{f:2,c:29836},29839,{f:11,c:29841},29853,{f:4,c:29855},{f:2,c:29860},{f:6,c:29866},{f:9,c:29873},{f:2,c:29883},{f:12,c:29886},{f:4,c:29899},{f:2,c:29904},29907,{f:5,c:29909},29915,29917,29919,29921,29925,{f:7,c:29927},{f:4,c:29936},29941,{f:7,c:29944},{f:4,c:29952},{f:7,c:29957},29966,29968,29970,{f:4,c:29972},29979,{f:2,c:29981},{f:3,c:29984},29988,{f:2,c:29990},29994,29998,30004,30006,30009,{f:2,c:30012},30015,{f:4,c:300 [...]
+c:30022},{f:2,c:30025},30029,{f:4,c:30032},{f:4,c:30037},{f:4,c:30046},{f:2,c:30051},{f:3,c:30055},{f:6,c:30060},30067,30069,30071,{f:5,c:30074},{f:3,c:30080},{f:2,c:30084},{f:3,c:30088},{f:3,c:30092},30096,30099,30101,30104,{f:2,c:30107},30110,30114,{f:5,c:30118},30125,{f:2,c:30134},{f:2,c:30138},{f:3,c:30143},30150,{f:2,c:30155},{f:4,c:30158},30163,30167,30170,{f:2,c:30172},{f:3,c:30175},30181,30185,{f:4,c:30188},{f:2,c:30194},{f:4,c:30197},{f:2,c:30202},{f:2,c:30205},30212,{f:4,c:3021 [...]
+{f:4,c:30225},30230,30234,{f:2,c:30236},30243,30248,30252,{f:2,c:30254},{f:2,c:30257},{f:2,c:30262},{f:2,c:30265},30269,30273,{f:2,c:30276},30280,{f:2,c:30282},{f:6,c:30286},30293,30295,{f:3,c:30297},30301,{f:2,c:30304},30310,30312,30314,{f:3,c:30323},[12136,30326],30327,{f:2,c:30329},{f:3,c:30335},30339,30341,{f:2,c:30345},{f:2,c:30348},{f:2,c:30351},30354,{f:2,c:30356},{f:2,c:30359},{f:9,c:30363},{f:9,c:30373},{f:2,c:30383},30387,{f:3,c:30389},30393,{f:4,c:30395},{f:2,c:30400},{f:2,c:3 [...]
+30409,{f:2,c:30411},30419,30421,{f:2,c:30425},{f:2,c:30428},30432,30434,30438,{f:6,c:30440},30448,30451,{f:3,c:30453},{f:2,c:30458},30461,{f:2,c:30463},{f:2,c:30466},{f:2,c:30469},30474,30476,{f:11,c:30478},{f:4,c:30491},30497,{f:3,c:30499},30503,{f:3,c:30506},30510,{f:5,c:30512},30521,30523,{f:3,c:30525},30530,{f:3,c:30532},{f:7,c:30536},{f:8,c:30546},{f:2,c:30556},{f:2,c:30559},30564,30567,{f:2,c:30569},{f:12,c:30573},{f:3,c:30586},{f:3,c:30593},{f:6,c:30598},{f:2,c:30607},{f:5,c:30611 [...]
+30625,{f:2,c:30627},30630,30632,30635,{f:2,c:30638},{f:2,c:30641},30644,{f:5,c:30646},30654,{f:7,c:30656},{f:5,c:30664},{f:9,c:30670},{f:2,c:30680},{f:5,c:30685},30692,30694,30696,30698,{f:3,c:30704},{f:2,c:30708},30711,{f:4,c:30713},{f:6,c:30723},{f:2,c:30730},{f:3,c:30734},30739,30741,30745,30747,30750,{f:3,c:30752},30756,30760,{f:2,c:30762},{f:2,c:30766},{f:3,c:30769},{f:2,c:30773},30781,30783,{f:2,c:30785},30788,30790,{f:4,c:30792},30797,30799,30801,{f:2,c:30803},{f:5,c:30808},{f:6,c [...]
+c:30821},30825,{f:7,c:30832},{f:4,c:30840},{f:10,c:30845},30856,{f:2,c:30858},{f:2,c:30863},30866,{f:3,c:30868},30873,{f:2,c:30877},30880,30882,30884,30886,30888,{f:3,c:30890},{f:2,c:30894},{f:3,c:30901},30907,30909,{f:2,c:30911},{f:3,c:30914},{f:3,c:30918},{f:4,c:30924},{f:3,c:30929},{f:3,c:30934},{f:8,c:30939},{f:3,c:30948},{f:3,c:30953},{f:2,c:30957},{f:2,c:30960},30963,{f:2,c:30965},{f:2,c:30968},{f:2,c:30971},{f:3,c:30974},{f:3,c:30978},{f:8,c:30982},{f:4,c:30991},{f:5,c:30996},{f:4 [...]
+{f:5,c:31007},31013,{f:3,c:31015},{f:4,c:31021},{f:2,c:31026},{f:5,c:31029},31037,31039,{f:4,c:31042},31047,{f:9,c:31050},{f:2,c:31060},{f:2,c:31064},31073,{f:2,c:31075},31078,{f:4,c:31081},31086,{f:7,c:31088},31097,{f:5,c:31099},{f:2,c:31106},{f:4,c:31110},{f:2,c:31115},{f:10,c:31120},{f:11,c:31131},{f:2,c:31144},{f:3,c:31147},31151,31154,{f:4,c:31156},[12145,31160],31164,31167,31170,{f:2,c:31172},{f:2,c:31175},31178,31180,{f:3,c:31182},{f:2,c:31187},{f:2,c:31190},{f:6,c:31193},{f:3,c:3 [...]
+31208,31210,31212,31214,{f:7,c:31217},{f:2,c:31225},31228,{f:2,c:31230},31233,{f:2,c:31236},{f:4,c:31239},31244,{f:5,c:31247},{f:2,c:31253},{f:2,c:31256},{f:3,c:31259},31263,{f:2,c:31265},{f:10,c:31268},{f:2,c:31279},31282,{f:3,c:31284},31288,31290,31294,{f:5,c:31297},{f:5,c:31303},{f:2,c:31311},{f:5,c:31314},{f:9,c:31320},{f:6,c:31331},31338,{f:4,c:31340},{f:3,c:31345},31349,{f:4,c:31355},31362,31365,31367,{f:4,c:31369},{f:3,c:31374},{f:2,c:31379},{f:3,c:31385},31390,{f:4,c:31393},31399 [...]
+c:31407},{f:2,c:31412},{f:3,c:31415},{f:4,c:31419},{f:4,c:31424},31430,31433,{f:10,c:31436},{f:2,c:31447},{f:4,c:31450},{f:2,c:31457},31460,{f:3,c:31463},{f:2,c:31467},31470,{f:6,c:31472},{f:2,c:31479},{f:2,c:31483},31486,{f:3,c:31488},31493,31495,31497,{f:3,c:31500},31504,{f:2,c:31506},{f:3,c:31510},31514,{f:2,c:31516},31519,{f:3,c:31521},31527,31529,31533,{f:2,c:31535},31538,{f:4,c:31540},31545,31547,31549,{f:6,c:31551},31560,31562,{f:2,c:31565},31571,31573,31575,31577,31580,{f:2,c:315 [...]
+{f:4,c:31587},{f:6,c:31592},{f:2,c:31599},{f:2,c:31603},31606,31608,31610,{f:2,c:31612},31615,{f:4,c:31617},{f:5,c:31622},31628,{f:2,c:31630},{f:3,c:31633},31638,{f:4,c:31640},{f:3,c:31646},{f:3,c:31651},{f:3,c:31662},{f:2,c:31666},{f:3,c:31669},{f:7,c:31673},{f:2,c:31682},31685,31688,31690,{f:4,c:31693},31698,{f:5,c:31700},{f:2,c:31707},{f:3,c:31710},{f:2,c:31714},{f:2,c:31719},{f:3,c:31723},{f:2,c:31727},31730,{f:3,c:31732},{f:4,c:31736},31741,31743,{f:6,c:31745},{f:3,c:31752},31758,{f [...]
+{f:7,c:31767},31776,31778,{f:2,c:31780},{f:2,c:31784},{f:12,c:31788},{f:4,c:31801},31810,{f:8,c:31812},{f:14,c:31822},{f:2,c:31837},{f:3,c:31841},{f:4,c:31845},31851,31853,{f:3,c:31855},{f:6,c:31861},{f:11,c:31870},{f:7,c:31882},{f:2,c:31891},31894,{f:3,c:31897},{f:2,c:31904},31907,{f:4,c:31910},{f:3,c:31915},{f:2,c:31919},{f:5,c:31924},{f:2,c:31930},{f:2,c:31935},{f:3,c:31938},31942,31945,31947,{f:7,c:31950},31960,{f:2,c:31962},{f:6,c:31969},{f:6,c:31977},31985,31987,31989,31991,31994,{ [...]
+31999,32001,32003,32012,{f:2,c:32014},{f:2,c:32017},32022,32024,{f:3,c:32029},{f:4,c:32035},{f:3,c:32040},{f:3,c:32044},{f:5,c:32052},32059,{f:2,c:32061},32065,32067,32069,{f:7,c:32071},32079,{f:12,c:32081},{f:2,c:32095},{f:3,c:32099},32103,{f:5,c:32105},{f:2,c:32111},{f:2,c:32116},32120,{f:7,c:32122},32130,{f:2,c:32132},32135,{f:5,c:32138},{f:3,c:32144},{f:8,c:32148},32157,{f:3,c:32159},{f:2,c:32164},{f:4,c:32167},32175,{f:3,c:32181},32188,{f:4,c:32192},{f:2,c:32197},{f:2,c:32200},{f:5, [...]
+32211,{f:2,c:32213},{f:3,c:32218},32223,32226,{f:2,c:32228},32231,{f:2,c:32234},{f:2,c:32237},32240,32243,32245,{f:2,c:32247},32250,{f:12,c:32252},{f:4,c:32268},{f:9,c:32274},32284,{f:3,c:32288},{f:3,c:32292},{f:3,c:32296},32300,{f:2,c:32303},32307,32312,32314,32316,{f:2,c:32319},{f:3,c:32322},{f:10,c:32328},32339,{f:4,c:32342},{f:3,c:32347},{f:3,c:32351},{f:6,c:32355},32364,{f:2,c:32369},{f:5,c:32372},{f:2,c:32378},{f:3,c:32383},{f:5,c:32387},32393,32395,32398,{f:3,c:32400},32405,32407, [...]
+{f:2,c:32413},32430,32436,{f:2,c:32443},32470,32484,32492,32505,32522,32528,32542,32567,32569,{f:7,c:32571},32579,{f:6,c:32582},32589,32591,{f:2,c:32594},32598,32601,{f:4,c:32603},32608,{f:5,c:32611},{f:3,c:32619},32623,32627,{f:2,c:32629},32632,{f:4,c:32634},{f:2,c:32639},{f:3,c:32642},32647,32649,32651,32653,{f:5,c:32655},{f:5,c:32661},{f:2,c:32667},32672,{f:2,c:32674},32678,32680,{f:5,c:32682},32689,{f:5,c:32691},{f:2,c:32698},32702,32704,{f:3,c:32706},{f:4,c:32710},32715,32717,{f:3,c [...]
+{f:2,c:32726},{f:6,c:32729},{f:3,c:32738},{f:2,c:32743},{f:4,c:32746},32751,32754,{f:5,c:32756},32762,{f:3,c:32765},32770,{f:4,c:32775},{f:2,c:32782},32785,32787,{f:2,c:32794},{f:3,c:32797},32801,{f:2,c:32803},32811,32813,{f:2,c:32815},32818,32820,{f:2,c:32825},32828,32830,{f:2,c:32832},{f:2,c:32836},{f:3,c:32839},{f:4,c:32846},32851,32853,32855,32857,{f:3,c:32859},{f:10,c:32863},{f:4,c:32875},32884,32888,{f:3,c:32890},{f:2,c:32897},32904,32906,{f:6,c:32909},{f:2,c:32916},32919,32921,329 [...]
+{f:3,c:32934},32940,32944,32947,{f:2,c:32949},{f:2,c:32952},32955,32965,{f:5,c:32967},{f:7,c:32975},32984,{f:2,c:32991},{f:2,c:32994},32998,33006,33013,33015,33017,33019,{f:4,c:33022},{f:2,c:33027},{f:2,c:33031},{f:2,c:33035},33045,33047,33049,{f:2,c:33052},{f:13,c:33055},{f:2,c:33069},33072,{f:3,c:33075},33079,{f:4,c:33082},{f:7,c:33087},33095,33097,33101,33103,33106,{f:2,c:33111},{f:5,c:33115},{f:3,c:33122},33128,33130,33132,33135,{f:2,c:33138},{f:3,c:33141},33153,{f:5,c:33155},33161,{ [...]
+33168,{f:6,c:33170},33177,{f:2,c:33182},{f:2,c:33185},{f:2,c:33188},33191,{f:8,c:33195},{f:6,c:33204},33212,{f:2,c:33220},{f:2,c:33223},33227,33230,{f:8,c:33232},33241,{f:4,c:33243},{f:2,c:33249},{f:3,c:33252},33257,33259,{f:5,c:33262},{f:5,c:33269},33277,33279,33283,33291,{f:2,c:33294},33297,33299,{f:6,c:33301},33309,33312,{f:4,c:33316},33321,33326,33330,33338,{f:2,c:33340},{f:5,c:33343},{f:2,c:33349},33352,33354,{f:3,c:33356},{f:8,c:33360},{f:4,c:33371},{f:4,c:33376},33381,33383,{f:2,c [...]
+c:33388},{f:2,c:33397},[12171,33400],{f:2,c:33403},{f:2,c:33408},33411,{f:3,c:33413},33417,33420,33424,{f:4,c:33427},{f:2,c:33434},33438,33440,{f:2,c:33442},33447,33458,{f:2,c:33461},33466,33468,{f:2,c:33471},{f:2,c:33474},{f:2,c:33477},33481,33488,33494,{f:2,c:33497},33501,33506,{f:3,c:33512},{f:3,c:33516},33520,{f:2,c:33522},{f:2,c:33525},33528,33530,{f:5,c:33532},{f:2,c:33546},33549,33552,{f:2,c:33554},33558,{f:2,c:33560},{f:10,c:33565},{f:2,c:33577},33582,33584,33586,33591,33595,{f:3 [...]
+{f:2,c:33601},{f:2,c:33604},33608,{f:5,c:33610},33619,{f:5,c:33621},33629,33634,{f:7,c:33648},{f:2,c:33657},{f:7,c:33662},{f:2,c:33671},{f:3,c:33675},{f:3,c:33679},{f:2,c:33684},33687,{f:2,c:33689},33693,33695,33697,{f:4,c:33699},{f:4,c:33708},33717,33723,{f:2,c:33726},{f:3,c:33730},33734,{f:2,c:33736},33739,{f:2,c:33741},{f:4,c:33744},33749,33751,{f:3,c:33753},33758,{f:3,c:33762},{f:3,c:33766},{f:4,c:33771},{f:5,c:33779},{f:3,c:33786},{f:3,c:33790},33794,33797,{f:2,c:33800},33808,{f:6,c [...]
+c:33817},{f:6,c:33822},{f:3,c:33833},{f:4,c:33837},{f:3,c:33842},{f:2,c:33846},{f:3,c:33849},{f:8,c:33854},{f:2,c:33863},{f:7,c:33866},{f:4,c:33875},33880,{f:4,c:33885},33890,33893,{f:2,c:33895},33898,33902,33904,33906,33908,33913,{f:7,c:33915},{f:4,c:33923},33930,33933,{f:4,c:33935},{f:2,c:33941},33944,{f:2,c:33946},{f:4,c:33949},{f:13,c:33954},{f:2,c:33968},33971,{f:3,c:33973},33979,33982,{f:2,c:33986},{f:4,c:33989},33996,{f:2,c:33998},34002,{f:2,c:34004},{f:6,c:34007},34014,{f:2,c:340 [...]
+{f:5,c:34023},34029,{f:11,c:34033},34046,{f:12,c:34048},{f:4,c:34061},34066,{f:2,c:34069},{f:2,c:34072},{f:3,c:34075},34080,34082,{f:2,c:34084},{f:4,c:34087},{f:9,c:34094},{f:3,c:34110},34114,{f:2,c:34116},34119,{f:3,c:34123},{f:3,c:34127},34132,34135,{f:4,c:34138},{f:3,c:34143},34147,{f:3,c:34149},{f:2,c:34155},{f:4,c:34158},34163,{f:2,c:34165},34168,{f:2,c:34172},{f:5,c:34175},34182,34185,34187,{f:2,c:34189},34192,{f:2,c:34194},{f:6,c:34197},{f:2,c:34205},{f:4,c:34208},34213,34215,{f:3 [...]
+{f:6,c:34225},34232,{f:6,c:34235},{f:7,c:34242},{f:3,c:34250},{f:2,c:34257},34260,{f:6,c:34262},{f:6,c:34270},{f:3,c:34278},{f:9,c:34283},34293,{f:2,c:34295},{f:3,c:34300},{f:4,c:34304},{f:3,c:34312},{f:5,c:34316},{f:4,c:34322},{f:3,c:34327},{f:3,c:34331},{f:3,c:34335},{f:4,c:34339},34344,{f:3,c:34346},{f:10,c:34350},34361,34363,{f:2,c:34365},{f:13,c:34368},{f:2,c:34386},{f:4,c:34390},34395,34397,{f:2,c:34400},{f:4,c:34403},{f:3,c:34408},34413,{f:2,c:34415},{f:7,c:34418},{f:7,c:34435},{f [...]
+34452,{f:6,c:34454},{f:5,c:34462},{f:2,c:34469},34475,{f:2,c:34477},{f:2,c:34482},{f:3,c:34487},{f:5,c:34491},{f:3,c:34497},34501,34504,{f:2,c:34508},{f:2,c:34514},{f:3,c:34517},34522,{f:2,c:34524},{f:4,c:34528},{f:4,c:34533},{f:3,c:34538},34543,{f:3,c:34549},{f:3,c:34555},34559,34561,{f:2,c:34564},{f:2,c:34571},{f:4,c:34574},34580,34582,34585,34587,34589,{f:2,c:34591},34596,{f:3,c:34598},{f:4,c:34602},{f:2,c:34607},{f:2,c:34610},{f:2,c:34613},{f:3,c:34616},{f:2,c:34620},{f:7,c:34624},{f [...]
+34637,{f:4,c:34639},34644,34646,34648,{f:6,c:34650},{f:2,c:34657},{f:7,c:34663},34671,{f:3,c:34673},34677,34679,{f:2,c:34681},{f:3,c:34687},{f:2,c:34694},{f:2,c:34697},34700,{f:5,c:34702},{f:3,c:34708},{f:6,c:34712},{f:2,c:34720},{f:5,c:34723},{f:2,c:34729},34734,{f:3,c:34736},34740,{f:4,c:34742},34748,{f:2,c:34750},{f:3,c:34753},34757,34759,34761,{f:2,c:34764},{f:2,c:34767},{f:7,c:34772},{f:4,c:34780},{f:2,c:34785},34788,{f:4,c:34790},34795,34797,{f:2,c:34800},{f:3,c:34803},{f:2,c:34807 [...]
+c:34812},{f:4,c:34815},34820,{f:3,c:34823},{f:5,c:34827},34834,34836,{f:4,c:34839},{f:3,c:34844},34848,{f:13,c:34852},{f:3,c:34867},{f:2,c:34871},34874,{f:3,c:34877},{f:3,c:34881},{f:3,c:34887},34891,{f:5,c:34894},{f:2,c:34901},34904,34906,34908,{f:3,c:34910},{f:2,c:34918},34922,34925,34927,34929,{f:4,c:34931},34936,{f:3,c:34938},34944,34947,{f:2,c:34950},{f:2,c:34953},34956,{f:4,c:34958},{f:3,c:34963},{f:5,c:34967},{f:5,c:34973},34979,{f:6,c:34981},34988,{f:3,c:34990},{f:5,c:34994},{f:4 [...]
+{f:4,c:35005},{f:2,c:35011},{f:2,c:35015},{f:3,c:35019},{f:2,c:35024},35027,{f:2,c:35030},{f:2,c:35034},35038,{f:2,c:35040},{f:2,c:35046},{f:7,c:35049},35058,{f:3,c:35061},{f:2,c:35066},{f:3,c:35071},{f:4,c:35075},{f:2,c:35080},{f:5,c:35083},35089,{f:5,c:35092},{f:5,c:35100},{f:3,c:35106},{f:4,c:35110},{f:4,c:35116},35121,35125,35127,{f:2,c:35129},{f:5,c:35132},{f:2,c:35138},{f:2,c:35141},{f:14,c:35144},{f:6,c:35159},{f:3,c:35169},35173,{f:3,c:35175},35179,{f:2,c:35181},{f:2,c:35184},{f: [...]
+{f:2,c:35196},[12177,35198],35200,35202,{f:2,c:35204},{f:4,c:35207},{f:3,c:35212},{f:3,c:35216},{f:2,c:35220},35223,{f:8,c:35225},{f:4,c:35234},{f:3,c:35239},35243,{f:2,c:35245},{f:2,c:35248},{f:4,c:35251},{f:2,c:35256},{f:2,c:35259},35262,35267,35277,{f:3,c:35283},{f:3,c:35287},35291,35293,{f:4,c:35295},35300,{f:4,c:35303},{f:3,c:35308},{f:3,c:35312},35317,35319,{f:7,c:35321},{f:3,c:35332},35337,35339,35341,35343,{f:2,c:35345},35348,35351,{f:2,c:35353},35356,35358,{f:3,c:35360},35364,{f [...]
+{f:2,c:35371},{f:3,c:35374},{f:2,c:35378},35381,{f:3,c:35383},{f:3,c:35387},{f:2,c:35391},{f:4,c:35394},35399,{f:5,c:35401},35407,35409,35411,{f:2,c:35414},{f:2,c:35417},{f:2,c:35420},{f:2,c:35423},{f:2,c:35428},{f:2,c:35431},35434,35439,35444,{f:3,c:35446},{f:2,c:35450},{f:2,c:35453},{f:4,c:35456},35464,{f:2,c:35467},{f:3,c:35470},35476,{f:2,c:35478},35481,{f:3,c:35483},35487,35490,35495,{f:3,c:35497},{f:3,c:35501},35505,{f:3,c:35507},{f:2,c:35511},{f:2,c:35514},{f:2,c:35517},{f:2,c:355 [...]
+{f:2,c:35525},35528,35530,35532,35534,35536,{f:3,c:35539},{f:3,c:35544},35549,{f:3,c:35551},35555,35557,{f:3,c:35560},35564,{f:2,c:35567},35570,{f:2,c:35572},35577,35579,35581,35583,35587,35590,{f:2,c:35592},{f:3,c:35595},35599,{f:3,c:35601},35605,35608,35612,{f:3,c:35614},{f:4,c:35618},35623,{f:2,c:35625},{f:5,c:35630},{f:5,c:35636},{f:4,c:35642},{f:10,c:35647},{f:4,c:35658},{f:6,c:35664},35671,35675,{f:9,c:35677},{f:4,c:35687},{f:2,c:35693},{f:3,c:35697},{f:2,c:35701},{f:5,c:35704},{f: [...]
+{f:9,c:35713},{f:3,c:35723},{f:3,c:35727},35732,{f:5,c:35735},35741,35743,35756,35761,35771,35783,35792,35818,35849,35870,{f:9,c:35896},{f:4,c:35906},{f:2,c:35914},{f:3,c:35917},{f:4,c:35921},{f:4,c:35926},{f:6,c:35931},{f:7,c:35939},{f:7,c:35948},{f:4,c:35956},{f:7,c:35963},{f:2,c:35971},{f:3,c:35974},35979,{f:7,c:35981},{f:3,c:35989},{f:4,c:35993},35999,{f:4,c:36003},{f:2,c:36013},36017,36021,36025,36030,36038,36041,{f:6,c:36043},36052,{f:4,c:36054},36059,36061,36063,36069,{f:2,c:36072 [...]
+{f:5,c:36085},{f:5,c:36095},{f:2,c:36102},36105,36108,36110,{f:5,c:36113},{f:4,c:36119},36128,{f:2,c:36177},36183,36191,36197,{f:3,c:36200},36204,{f:2,c:36206},{f:2,c:36209},{f:9,c:36216},{f:2,c:36226},{f:4,c:36230},{f:5,c:36236},{f:2,c:36242},{f:3,c:36246},{f:5,c:36250},{f:3,c:36256},{f:4,c:36260},{f:8,c:36265},{f:2,c:36278},36281,36283,36285,{f:3,c:36288},36293,{f:4,c:36295},36301,36304,{f:4,c:36306},{f:2,c:36312},36316,{f:3,c:36320},{f:3,c:36325},36329,{f:2,c:36333},{f:3,c:36336},3634 [...]
+{f:7,c:36350},{f:3,c:36358},36363,{f:2,c:36365},{f:3,c:36369},{f:8,c:36373},{f:2,c:36384},{f:5,c:36388},36395,36397,36400,{f:2,c:36402},{f:3,c:36406},{f:2,c:36411},{f:2,c:36414},36419,{f:2,c:36421},{f:4,c:36429},{f:2,c:36435},{f:3,c:36438},{f:9,c:36442},{f:2,c:36452},{f:2,c:36455},{f:2,c:36458},36462,36465,36467,36469,{f:3,c:36471},36475,{f:2,c:36477},36480,{f:3,c:36482},36486,36488,36492,36494,{f:5,c:36501},36507,36509,{f:2,c:36511},{f:3,c:36514},{f:3,c:36519},{f:2,c:36525},{f:2,c:36528 [...]
+{f:5,c:36539},{f:9,c:36545},{f:3,c:36559},36563,{f:6,c:36565},{f:3,c:36572},{f:4,c:36576},{f:6,c:36581},{f:6,c:36588},{f:5,c:36595},36605,{f:4,c:36607},36612,36614,36616,{f:7,c:36619},36627,{f:5,c:36630},{f:5,c:36640},{f:2,c:36647},{f:4,c:36651},{f:3,c:36656},{f:4,c:36660},{f:2,c:36665},{f:2,c:36668},{f:2,c:36672},36675,{f:2,c:36679},{f:3,c:36682},{f:5,c:36687},{f:10,c:36693},36704,36707,36709,36714,36736,36748,36754,36765,{f:3,c:36768},{f:2,c:36772},36775,36778,36780,{f:2,c:36787},[1219 [...]
+{f:2,c:36791},{f:3,c:36794},{f:2,c:36799},36803,36806,{f:5,c:36809},36815,36818,{f:2,c:36822},36826,{f:2,c:36832},36835,36839,36844,36847,{f:2,c:36849},{f:2,c:36853},{f:3,c:36858},{f:2,c:36862},{f:2,c:36871},36876,36878,36883,36888,36892,{f:2,c:36900},{f:6,c:36903},{f:2,c:36912},{f:2,c:36915},36919,{f:2,c:36921},36925,{f:2,c:36927},36931,{f:2,c:36933},{f:3,c:36936},36940,36950,{f:2,c:36953},36957,36959,36961,36964,{f:2,c:36966},{f:3,c:36970},{f:3,c:36975},36979,36982,36985,36987,36990,{f [...]
+37001,{f:3,c:37004},37010,37012,37014,37016,37018,37020,{f:3,c:37022},{f:2,c:37028},{f:3,c:37031},37035,37037,37042,37047,{f:2,c:37052},{f:2,c:37055},{f:2,c:37058},37062,{f:2,c:37064},{f:3,c:37067},37074,{f:3,c:37076},{f:3,c:37080},37086,37088,{f:3,c:37091},{f:2,c:37097},37100,37102,{f:4,c:37104},{f:2,c:37110},{f:4,c:37113},{f:3,c:37119},37123,37125,{f:2,c:37127},{f:8,c:37130},37139,37141,{f:2,c:37143},{f:4,c:37146},{f:3,c:37151},{f:3,c:37156},{f:5,c:37160},37166,37171,37173,{f:2,c:37175 [...]
+{f:2,c:37188},37191,37201,{f:4,c:37203},{f:2,c:37208},{f:2,c:37211},{f:2,c:37215},{f:3,c:37222},37227,37229,37235,{f:3,c:37242},{f:5,c:37248},37254,37256,37258,{f:2,c:37262},{f:3,c:37267},{f:3,c:37271},{f:5,c:37277},{f:6,c:37284},{f:4,c:37296},{f:4,c:37302},{f:5,c:37307},37314,37316,[12196,37318],37320,37328,37334,{f:2,c:37338},{f:5,c:37342},{f:2,c:37349},37352,{f:11,c:37354},37366,37368,{f:5,c:37371},{f:2,c:37378},{f:3,c:37381},{f:3,c:37386},37391,{f:2,c:37394},{f:8,c:37398},{f:4,c:3740 [...]
+{f:6,c:37416},37423,{f:2,c:37425},{f:2,c:37429},{f:2,c:37435},{f:4,c:37441},{f:2,c:37446},{f:3,c:37450},{f:3,c:37454},{f:3,c:37458},37462,{f:2,c:37464},{f:2,c:37468},{f:3,c:37471},{f:3,c:37475},{f:5,c:37479},{f:6,c:37486},{f:3,c:37493},37497,{f:3,c:37500},{f:2,c:37505},37508,{f:8,c:37510},{f:2,c:37519},37522,{f:2,c:37524},37527,37529,37531,{f:3,c:37533},{f:2,c:37537},37540,37543,37549,{f:2,c:37551},{f:5,c:37554},37560,37562,{f:4,c:37565},37570,37572,37574,{f:3,c:37577},{f:2,c:37581},{f:2 [...]
+{f:10,c:37587},37598,{f:3,c:37600},37607,37609,{f:2,c:37611},{f:4,c:37618},37623,{f:3,c:37625},{f:4,c:37629},{f:4,c:37634},{f:7,c:37641},37649,{f:2,c:37651},{f:2,c:37654},{f:3,c:37660},37665,{f:3,c:37667},37671,{f:2,c:37673},{f:2,c:37676},{f:2,c:37680},{f:2,c:37684},37687,{f:5,c:37689},37695,37698,{f:2,c:37700},{f:3,c:37704},37708,{f:6,c:37710},{f:3,c:37717},{f:2,c:37721},{f:8,c:37724},{f:3,c:37734},37739,{f:3,c:37741},{f:4,c:37745},{f:3,c:37751},{f:3,c:37755},{f:3,c:37759},37763,{f:2,c: [...]
+c:37768},{f:4,c:37771},{f:6,c:37776},37783,{f:9,c:37785},{f:2,c:37796},37800,37803,37805,37807,{f:2,c:37809},37812,{f:2,c:37814},{f:6,c:37817},{f:3,c:37824},{f:3,c:37828},37833,37835,{f:3,c:37838},{f:4,c:37842},{f:3,c:37849},37856,37859,{f:3,c:37861},{f:12,c:37865},37878,37880,{f:9,c:37882},{f:7,c:37892},{f:4,c:37900},37905,{f:3,c:37909},{f:3,c:37914},{f:2,c:37918},{f:5,c:37921},{f:5,c:37929},{f:3,c:37935},37940,{f:2,c:37942},37945,{f:3,c:37947},{f:4,c:37952},{f:5,c:37957},37963,{f:5,c:3 [...]
+{f:11,c:37973},{f:2,c:37985},37988,{f:5,c:37990},37996,{f:2,c:37998},38001,{f:4,c:38003},38008,{f:2,c:38010},{f:5,c:38016},38033,38038,38040,38087,38095,{f:2,c:38099},38106,38118,38139,38172,38176,38183,38195,38205,38211,38216,38219,38229,38234,38240,38254,{f:2,c:38260},{f:7,c:38264},38273,{f:2,c:38276},{f:2,c:38279},38282,38285,38288,38290,{f:3,c:38293},{f:8,c:38297},38306,{f:2,c:38310},38314,{f:4,c:38318},{f:3,c:38323},{f:2,c:38327},38330,{f:3,c:38336},{f:2,c:38340},38343,38345,{f:3,c: [...]
+c:38353},{f:5,c:38359},38365,{f:2,c:38367},{f:2,c:38371},{f:2,c:38374},38380,38399,38407,38419,38424,38427,38430,38432,{f:7,c:38435},{f:3,c:38443},{f:2,c:38447},{f:4,c:38455},38462,38465,38467,38474,{f:2,c:38478},{f:3,c:38481},{f:2,c:38486},{f:2,c:38489},38492,38494,38496,{f:2,c:38501},38507,{f:3,c:38509},38513,{f:4,c:38521},{f:7,c:38526},38535,38537,38540,{f:3,c:38545},38550,38554,{f:10,c:38557},38569,{f:5,c:38571},38578,38581,38583,38586,38591,{f:2,c:38594},38600,{f:2,c:38602},{f:2,c:3 [...]
+c:38611},{f:2,c:38615},38618,{f:3,c:38621},38625,{f:4,c:38628},{f:4,c:38635},{f:2,c:38640},{f:2,c:38644},38648,38650,{f:2,c:38652},38655,{f:2,c:38658},38661,{f:3,c:38666},{f:3,c:38672},{f:2,c:38676},{f:5,c:38679},38685,{f:8,c:38687},{f:2,c:38696},{f:2,c:38699},{f:2,c:38702},38705,{f:5,c:38707},{f:3,c:38714},{f:3,c:38719},38723,{f:3,c:38725},{f:8,c:38729},[12205,38737],{f:2,c:38740},{f:2,c:38743},{f:2,c:38748},38751,{f:2,c:38755},{f:2,c:38758},{f:9,c:38762},38773,{f:5,c:38775},{f:8,c:3878 [...]
+38796,38798,38800,38803,{f:3,c:38805},{f:7,c:38809},{f:2,c:38817},{f:2,c:38820},{f:4,c:38823},38828,38830,{f:2,c:38832},38835,{f:8,c:38837},{f:5,c:38846},{f:2,c:38852},{f:2,c:38855},38858,{f:6,c:38861},{f:5,c:38868},{f:2,c:38874},38877,{f:7,c:38879},38888,{f:5,c:38894},38900,{f:8,c:38903},38912,38916,38921,38923,38925,{f:3,c:38932},{f:3,c:38937},{f:4,c:38941},{f:2,c:38946},38949,{f:6,c:38951},{f:2,c:38958},{f:6,c:38961},{f:2,c:38969},38972,{f:8,c:38974},{f:5,c:38983},{f:4,c:38991},{f:3,c [...]
+{f:2,c:39004},{f:3,c:39007},{f:2,c:39011},39014,{f:3,c:39016},{f:2,c:39021},39026,39051,39054,39058,39061,39065,39075,{f:5,c:39081},39088,39090,{f:2,c:39092},{f:5,c:39095},{f:7,c:39101},39109,39111,{f:5,c:39113},{f:2,c:39119},39124,{f:2,c:39126},{f:2,c:39132},39137,{f:4,c:39139},39148,39150,{f:2,c:39152},39155,{f:7,c:39157},{f:4,c:39167},39172,{f:3,c:39174},39179,{f:2,c:39182},{f:4,c:39188},{f:2,c:39193},{f:2,c:39196},{f:2,c:39199},{f:6,c:39202},{f:5,c:39209},{f:4,c:39215},{f:3,c:39220}, [...]
+39229,{f:3,c:39232},39236,{f:2,c:39238},{f:4,c:39245},39251,39254,{f:4,c:39256},39261,{f:3,c:39263},39268,39270,39283,{f:2,c:39288},39291,39294,{f:2,c:39298},39305,39308,39310,{f:11,c:39322},{f:2,c:39334},{f:3,c:39337},{f:2,c:39343},39346,{f:12,c:39349},{f:14,c:39362},39379,{f:2,c:39382},39386,39388,39390,39392,{f:10,c:39395},{f:3,c:39406},{f:13,c:39410},39424,{f:3,c:39426},{f:7,c:39430},{f:6,c:39440},{f:2,c:39447},{f:17,c:39450},39468,39471,{f:5,c:39473},{f:5,c:39481},39487,{f:4,c:39494 [...]
+39502,{f:5,c:39504},39510,{f:2,c:39512},{f:3,c:39516},{f:2,c:39520},39523,{f:4,c:39526},39531,39538,39555,39561,{f:2,c:39565},{f:2,c:39572},39577,39590,{f:6,c:39593},{f:4,c:39602},39609,39611,{f:3,c:39613},{f:2,c:39619},{f:5,c:39622},{f:2,c:39629},39632,39639,{f:6,c:39641},39648,{f:4,c:39650},{f:4,c:39655},39660,{f:9,c:39664},39674,{f:7,c:39676},{f:2,c:39684},39687,{f:4,c:39689},39694,{f:3,c:39696},{f:6,c:39700},{f:4,c:39707},{f:2,c:39712},39716,39718,39720,{f:4,c:39722},39728,{f:8,c:397 [...]
+c:39741},39750,{f:3,c:39754},39760,{f:2,c:39762},{f:3,c:39765},39769,{f:20,c:39771},{f:4,c:39792},{f:2,c:39797},{f:9,c:39800},39810,{f:10,c:39812},39823,{f:7,c:39827},{f:2,c:39835},{f:11,c:39839},39852,{f:17,c:39855},{f:5,c:39874},39880,{f:9,c:39883},39893,{f:4,c:39895},39900,{f:3,c:39902},39907,{f:2,c:39909},39913,{f:4,c:39916},{f:3,c:39921},{f:8,c:39925},39934,{f:8,c:39936},{f:3,c:39946},{f:2,c:39950},39953,{f:12,c:39956},{f:2,c:39969},39972,{f:2,c:39974},{f:3,c:39978},{f:3,c:39982},39 [...]
+39992,39994,{f:2,c:39996},{f:6,c:39999},{f:2,c:40006},{f:8,c:40010},40019,40021,{f:4,c:40025},40030,{f:7,c:40032},{f:5,c:40040},{f:10,c:40046},40057,40059,{f:2,c:40061},40064,{f:2,c:40067},{f:2,c:40073},40076,40079,40083,{f:4,c:40086},40093,40106,40108,40111,40121,{f:5,c:40126},{f:2,c:40136},{f:2,c:40145},{f:2,c:40154},{f:2,c:40160},{f:2,c:40163},{f:3,c:40166},{f:2,c:40170},{f:6,c:40173},40181,{f:15,c:40183},40200,{f:11,c:40202},{f:5,c:40214},40220,40222,{f:3,c:40224},{f:2,c:40228},40231 [...]
+{f:10,c:40241},{f:3,c:40252},{f:2,c:40256},{f:14,c:40259},{f:8,c:40276},{f:2,c:40286},{f:8,c:40290},40299,{f:2,c:40301},{f:2,c:40304},{f:20,c:40307},40328,{f:9,c:40330},{f:4,c:40340},40345,{f:10,c:40347},{f:3,c:40358},{f:5,c:40362},{f:4,c:40368},{f:6,c:40373},{f:3,c:40381},40385,40387,{f:14,c:40389},{f:3,c:40404},40408,{f:10,c:40411},{f:8,c:40423},{f:2,c:40432},{f:4,c:40436},{f:17,c:40443},{f:8,c:40461},{f:4,c:40470},40476,40484,40487,40494,40496,40500,{f:2,c:40507},40512,40525,40528,{f: [...]
+40534,40537,40541,{f:4,c:40543},40549,{f:2,c:40558},40562,40564,{f:3,c:40566},40571,{f:2,c:40576},{f:4,c:40579},{f:2,c:40585},{f:6,c:40588},{f:3,c:40596},{f:5,c:40600},40606,{f:5,c:40608},{f:2,c:40615},{f:5,c:40618},{f:4,c:40624},{f:2,c:40630},{f:2,c:40633},40636,{f:4,c:40639},[12232,40643],{f:4,c:40645},{f:2,c:40650},40656,{f:2,c:40658},{f:3,c:40661},{f:2,c:40665},40673,{f:2,c:40675},40678,{f:4,c:40683},{f:2,c:40688},40691,{f:2,c:40693},40696,40698,{f:9,c:40704},40714,40716,40719,{f:2,c [...]
+40726,40728,{f:6,c:40730},40737,{f:9,c:40739},{f:2,c:40749},{f:7,c:40752},40760,40762,40764,{f:5,c:40767},{f:5,c:40773},{f:3,c:40780},40787,{f:4,c:40789},{f:2,c:40794},{f:2,c:40797},40802,{f:2,c:40804},{f:3,c:40807},40811,{f:5,c:40813},{f:4,c:40819},{f:7,c:40824},{f:2,c:40833},{f:2,c:40846},{f:3,c:40849},{f:3,c:40854},{f:2,c:40861},{f:5,c:40865},63788,{f:3,c:64013},64017,{f:2,c:64019},64024,{f:3,c:64031},{f:2,c:64035},{f:3,c:64039},11905,[59414,131207],[59415,131209],[59416,131276],11908 [...]
+11912,11915,59422,13726,13850,13838,11916,11927,14702,14616,59430,14799,14815,14963,14800,{f:2,c:59435},15182,15470,15584,11943,[59441,136663],59442,11946,16470,16735,11950,17207,11955,{f:2,c:11958},[59451,141711],17329,17324,11963,17373,17622,18017,17996,[59459,132361],18211,18217,18300,18317,11978,18759,18810,18813,{f:2,c:18818},{f:2,c:18821},18847,18843,18871,18870,[59476,133533],[59477,147966],19619,{f:3,c:19615},19575,19618,{f:7,c:19731},19886,59492,{s:226},8364,165,0,0,12351,{s:17} [...]
+12535,12537,12536,12538,0,{f:3,c:12339},{f:3,c:12344},{f:3,c:12586},{f:24,c:12704},11904,{f:2,c:11906},{f:3,c:11909},{f:2,c:11913},{f:10,c:11917},{f:2,c:11928},{f:12,c:11931},{f:2,c:11944},{f:3,c:11947},{f:4,c:11951},{f:2,c:11956},{f:3,c:11960},{f:14,c:11964},{f:41,c:11979},{f:71,c:13312},{f:43,c:13384},{f:298,c:13428},{f:111,c:13727},{f:11,c:13839},{f:765,c:13851},{f:85,c:14617},{f:96,c:14703},{f:14,c:14801},{f:147,c:14816},{f:218,c:14964},{f:287,c:15183},{f:113,c:15471},{f:885,c:15585} [...]
+{f:471,c:16736},{f:116,c:17208},{f:4,c:17325},{f:43,c:17330},{f:248,c:17374},{f:373,c:17623},{f:20,c:17997},{f:193,c:18018},{f:5,c:18212},{f:82,c:18218},{f:16,c:18301},{f:441,c:18318},{f:50,c:18760},{f:2,c:18811},{f:4,c:18814},18820,{f:20,c:18823},{f:3,c:18844},{f:22,c:18848},{f:703,c:18872},{f:39,c:19576},{f:111,c:19620},{f:148,c:19738},{f:7,c:19887}]};"use strict";var ia=function(){function d(){r("should not call ColorSpace constructor")}d.prototype={getRgb:function(a){r("Should not ca [...]
+a)},getRgbBuffer:function(a){r("Should not call ColorSpace.getRgbBuffer: "+a)}};d.parse=function(a,b,c){a=d.parseToIR(a,b,c);return a instanceof Ob?a:d.fromIR(a)};d.fromIR=function(a){var b=R(a)?a[0]:a;switch(b){case "DeviceGrayCS":return new Ca;case "DeviceRgbCS":return new ib;case "DeviceCmykCS":return new Ba;case "PatternCS":return(a=a[1])&&(a=d.fromIR(a)),new dd(a);case "IndexedCS":var b=a[2],c=a[3];return new ed(d.fromIR(a[1]),b,c);case "AlternateCS":return b=a[3],new Ob(a[1],d.from [...]
+Za.fromIR(b));case "LabCS":return new fd(a[1].WhitePoint,a[1].BlackPoint,a[1].Range);default:r("Unkown name "+b)}return null};d.parseToIR=function(a,b,c){if(T(a)){var e=c.get("ColorSpace");U(e)&&(e=e.get(a.name))&&(a=e)}a=b.fetchIfRef(a);if(T(a))switch(this.mode=e=a.name,e){case "DeviceGray":case "G":return"DeviceGrayCS";case "DeviceRGB":case "RGB":return"DeviceRgbCS";case "DeviceCMYK":case "CMYK":return"DeviceCmykCS";case "Pattern":return["PatternCS",null];default:r("unrecognized colors [...]
+e=a[0].name,e){case "DeviceGray":case "G":return"DeviceGrayCS";case "DeviceRGB":case "RGB":return"DeviceRgbCS";case "DeviceCMYK":case "CMYK":return"DeviceCmykCS";case "CalGray":return"DeviceGrayCS";case "CalRGB":return"DeviceRgbCS";case "ICCBased":e=b.fetchIfRef(a[1]).dict.get("N");if(1==e)return"DeviceGrayCS";if(3==e)return"DeviceRgbCS";if(4==e)return"DeviceCmykCS";break;case "Pattern":return(a=a[1])&&(a=d.parseToIR(a,b,c)),["PatternCS",a];case "Indexed":case "I":return c=d.parseToIR(a[ [...]
+a[2]+1,b=b.fetchIfRef(a[3]),["IndexedCS",c,e,b];case "Separation":case "DeviceN":var f=a[1],e=1;T(f)?e=1:R(f)&&(e=f.length);c=d.parseToIR(a[2],b,c);b=Za.getIR(b,b.fetchIfRef(a[3]));return["AlternateCS",e,c,b];case "Lab":return["LabCS",a[1].getAll()];default:r('unimplemented color space object "'+e+'"')}else r('unrecognized color space object: "'+a+'"');return null};d.isDefaultDecode=function(a,b){if(!a)return!0;if(2*b!==a.length)return warning("The decode map is not the correct length"), [...]
+0,e=a.length;c<e;c+=2)if(0!=a[c]||1!=a[c+1])return!1;return!0};return d}(),Ob=function(){function d(a,b,c){this.name="Alternate";this.numComps=a;this.defaultColor=[];for(var e=0;e<a;++e)this.defaultColor.push(1);this.base=b;this.tintFn=c}d.prototype={getRgb:function(a){return this.base.getRgb(this.tintFn(a))},getRgbBuffer:function(a,b){for(var c=this.tintFn,e=this.base,f=1/((1<<b)-1),g=a.length,i=0,d=e.numComps,j=new Uint8Array(d*g),h=this.numComps,m=[],l=0;l<g;l+=h){for(var n=0;n<h;++n) [...]
+n]*f;for(var n=c(m),o=0;o<d;++o)j[i++]=255*n[o]}return e.getRgbBuffer(j,8)},isDefaultDecode:function(a){return ia.isDefaultDecode(a,this.numComps)}};return d}(),dd=function(){function d(a){this.name="Pattern";this.base=a}d.prototype={};return d}(),ed=function(){function d(a,b,c){this.name="Indexed";this.numComps=1;this.defaultColor=[0];this.base=a;this.highVal=b;b*=a.numComps;a=new Uint8Array(b);if(oa(c))c=c.getBytes(b),a.set(c);else if(Na(c))for(var e=0;e<b;++e)a[e]=c.charCodeAt(e);else [...]
+c);this.lookup=a}d.prototype={getRgb:function(a){for(var b=this.base.numComps,c=a[0]*b,a=[],e=c,b=c+b;e<b;++e)a.push(this.lookup[e]);return this.base.getRgb(a)},getRgbBuffer:function(a){for(var b=this.base,c=b.numComps,e=this.lookup,f=a.length,g=new Uint8Array(f*c),i=0,d=0;d<f;++d)for(var j=a[d]*c,h=0;h<c;++h)g[i++]=e[j+h];return b.getRgbBuffer(g,8)},isDefaultDecode:function(){return!0}};return d}(),Ca=function(){function d(){this.name="DeviceGray";this.numComps=1;this.defaultColor=[0]}d [...]
+{getRgb:function(a){a=a[0];return[a,a,a]},getRgbBuffer:function(a,b){for(var c=255/((1<<b)-1),e=a.length,f=new Uint8Array(3*e),g=0,i=0;g<e;++g){var d=c*a[g]|0;f[i++]=d;f[i++]=d;f[i++]=d}return f},isDefaultDecode:function(a){return ia.isDefaultDecode(a,this.numComps)}};return d}(),ib=function(){function d(){this.name="DeviceRGB";this.numComps=3;this.defaultColor=[0,0,0]}d.prototype={getRgb:function(a){return a},getRgbBuffer:function(a,b){if(8==b)return a;var c=255/((1<<b)-1),e,f=a.length, [...]
+for(e=0;e<f;++e)g[e]=c*a[e]|0;return g},isDefaultDecode:function(a){return ia.isDefaultDecode(a,this.numComps)}};return d}(),Ba=function(){function d(){this.name="DeviceCMYK";this.numComps=4;this.defaultColor=[0,0,0,1]}d.prototype={getRgb:function(a){var b=a[0],c=a[1],e=a[2],a=a[3];return[1-(b*(1-a)+a),1-(c*(1-a)+a),1-(e*(1-a)+a)]},getRgbBuffer:function(a,b){for(var c=1/((1<<b)-1),e=a.length/4,f=new Uint8Array(3*e),g=0,i=0,d=0;d<e;d++){for(var j=[],h=0;4>h;++h)j.push(c*a[i++]);j=this.get [...]
+0;3>h;++h)f[g++]=Math.round(255*j[h])}return f},isDefaultDecode:function(a){return ia.isDefaultDecode(a,this.numComps)}};return d}(),fd=function(){function d(a,c,e){this.name="Lab";this.numComps=3;this.defaultColor=[0,0,0];a||r("WhitePoint missing - required for color space Lab");c=c||[0,0,0];e=e||[-100,100,-100,100];this.XW=a[0];this.YW=a[1];this.ZW=a[2];this.amin=e[0];this.amax=e[1];this.bmin=e[2];this.bmax=e[3];this.XB=c[0];this.YB=c[1];this.ZB=c[2];(0>this.XW||0>this.ZW||1!==this.YW) [...]
+if(0>this.XB||0>this.YB||0>this.ZB)Y("Invalid BlackPoint, falling back to default"),this.XB=this.YB=this.ZB=0;if(this.amin>this.amax||this.bmin>this.bmax)Y("Invalid Range, falling back to defaults"),this.amin=-100,this.amax=100,this.bmin=-100,this.bmax=100}function a(a){return a>=6/29?a*a*a:108/841*(a-4/29)}d.prototype={getRgb:function(b){var c=b[0],e=b[1],b=b[2],e=e>this.amax?this.amax:e,e=e<this.amin?this.amin:e,b=b>this.bmax?this.bmax:b,b=b<this.bmin?this.bmin:b,c=(c+16)/116,b=c-b/200 [...]
+a(c+e/500),c=this.YW*a(c),b=this.ZW*a(b);return Q.apply3dTransform([3.240479,-1.53715,-0.498535,-0.969256,1.875992,0.041556,0.055648,-0.204043,1.057311],[e,c,b])},getRgbBuffer:function(a,c){if(8==c)return a;var e,f=a.length/3,g=new Uint8Array(f),i=0;for(e=0;e<f;++e){var d=this.getRgb([a[e],a[e+1],a[e+2]]);g[i++]=d[0];g[i++]=d[1];g[i++]=d[2]}return g},isDefaultDecode:function(a){return 0===a[0]&&100===a[1]&&a[2]===this.amin&&a[3]===this.amax&&a[4]===this.bmin&&a[5]===this.bmax?!0:!1}};ret [...]
+"use strict";var Ra=function(){function d(a){this.b=this.a=0;var b=new Uint8Array(256),c,e=0,f,g=a.length;for(c=0;256>c;++c)b[c]=c;for(c=0;256>c;++c)f=b[c],e=e+f+a[c%g]&255,b[c]=b[e],b[e]=f;this.s=b}d.prototype={encryptBlock:function(a){var b,c=a.length,e,f,g=this.a,i=this.b,d=this.s,j=new Uint8Array(c);for(b=0;b<c;++b)g=g+1&255,e=d[g],i=i+e&255,f=d[i],d[g]=f,d[i]=e,j[b]=a[b]^d[e+f&255];this.a=g;this.b=i;return j}};d.prototype.decryptBlock=d.prototype.encryptBlock;return d}(),Xa=function [...]
+new Uint8Array([7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]),a=new Int32Array([-680876936,-389564586,606105819,-1044525330,-176418897,1200080426,-1473231341,-45705983,1770035416,-1958414417,-42063,-1990404162,1804603682,-40341101,-1502002290,1236535329,-165796510,-1069501632,643717713,-373897302,-701558691,38016083,-660478335,-405537848,568446438,-1019803690,-1 [...]
+1163531501,-1444681467,-51403784,1735328473,-1926607734,-378558,-2022574463,1839030562,-35309556,-1530992060,1272893353,-155497632,-1094730640,681279174,-358537222,-722521979,76029189,-640364487,-421815835,530742520,-995338651,-198630844,1126891415,-1416354905,-57434055,1700485571,-1894986606,-1051523,-2054922799,1873313359,-30611744,-1560198380,1309151649,-145523070,-1120210379,718787259,-343485551]);return function(b,c,e){var f=1732584193,g=-271733879,i=-1732584194,k=271733878,j=e+72&- [...]
+m;for(m=0;m<e;++m)h[m]=b[c++];h[m++]=128;for(b=j-8;m<b;)h[m++]=0;h[m++]=e<<3&255;h[m++]=e>>5&255;h[m++]=e>>13&255;h[m++]=e>>21&255;h[m++]=e>>>29&255;h[m++]=0;h[m++]=0;h[m++]=0;b=new Int32Array(16);for(m=0;m<j;){for(e=0;16>e;++e,m+=4)b[e]=h[m]|h[m+1]<<8|h[m+2]<<16|h[m+3]<<24;for(var l=f,c=g,n=i,o=k,p,q,e=0;64>e;++e){16>e?(p=c&n|~c&o,q=e):32>e?(p=o&c|~o&n,q=5*e+1&15):48>e?(p=c^n^o,q=3*e+5&15):(p=n^(c|~o),q=7*e&15);var u=o,l=l+p+a[e]+b[q]|0;p=d[e];o=n;n=c;c=c+(l<<p|l>>>32-p)|0;l=u}f=f+l|0;g [...]
+i+n|0;k=k+o|0}return new Uint8Array([f&255,f>>8&255,f>>16&255,f>>>24&255,g&255,g>>8&255,g>>16&255,g>>>24&255,i&255,i>>8&255,i>>16&255,i>>>24&255,k&255,k>>8&255,k>>16&255,k>>>24&255])}}(),gd=function(){function d(){}d.prototype={decryptBlock:function(a){return a}};return d}(),hd=function(){function d(a){var e=new Uint8Array(176);e.set(a);for(var a=16,f=1;176>a;++f)for(var d=e[a-3],h=e[a-2],m=e[a-1],l=e[a-4],d=c[d],h=c[h],m=c[m],l=c[l],d=d^b[f],n=0;4>n;++n)e[a]=d^=e[a-16],a++,e[a]=h^=e[a-1 [...]
+m^=e[a-16],a++,e[a]=l^=e[a-16],a++;this.key=e;this.buffer=new Uint8Array(16);this.bufferPosition=0}function a(a){var b,c,d=a.length,h=this.buffer;c=this.bufferPosition;var m=[],l=this.iv;for(b=0;b<d;++b)if(h[c]=a[b],++c,!(16>c)){var n,o=h;c=this.key;n=new Uint8Array(16);n.set(o);var p=o=void 0,q=void 0,u=p=q=void 0;for(p=0,q=160;16>p;++p,++q)n[p]^=c[q];for(o=9;1<=o;--o){q=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=q;q=n[14];p=n[10];n[14]=n[6];n[10]=n[2];n[6]=q;n[2]=p;q=n[15];p=n[11];u=n[7 [...]
+n[11]=q;n[7]=p;n[3]=u;for(p=0;16>p;++p)n[p]=e[n[p]];for(p=0,q=16*o;16>p;++p,++q)n[p]^=c[q];for(p=0;16>p;p+=4){var q=f[n[p+1]],u=f[n[p+2]],O=f[n[p+3]],q=f[n[p]]^q>>>8^q<<24^u>>>16^u<<16^O>>>24^O<<8;n[p]=q>>>24&255;n[p+1]=q>>16&255;n[p+2]=q>>8&255;n[p+3]=q&255}}q=n[13];n[13]=n[9];n[9]=n[5];n[5]=n[1];n[1]=q;q=n[14];p=n[10];n[14]=n[6];n[10]=n[2];n[6]=q;n[2]=p;q=n[15];p=n[11];u=n[7];n[15]=n[3];n[11]=q;n[7]=p;n[3]=u;for(p=0;16>p;++p)n[p]=e[n[p]],n[p]^=c[p];for(c=0;16>c;++c)n[c]^=l[c];l=h;m.pus [...]
+c=0}this.buffer=h;this.bufferLength=c;this.iv=l;if(0==m.length)return new Uint8Array([]);if(1==m.length)return m[0];d=new Uint8Array(16*m.length);for(b=0,c=0,a=m.length;b<a;++b,c+=16)d.set(m[b],c);return d}var b=new Uint8Array([141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179 [...]
+239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154,47,94,188,99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141,1,2,4,8,16,32,64,128,27,54,108,216,171,77,154 [...]
+99,198,151,53,106,212,179,125,250,239,197,145,57,114,228,211,189,97,194,159,37,74,148,51,102,204,131,29,58,116,232,203,141]),c=new Uint8Array([99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239 [...]
+67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85, [...]
+161,137,13,191,230,66,104,65,153,45,15,176,84,187,22]),e=new Uint8Array([82,9,106,213,48,54,165,56,191,64,163,158,129,243,215,251,124,227,57,130,155,47,255,135,52,142,67,68,196,222,233,203,84,123,148,50,166,194,35,61,238,76,149,11,66,250,195,78,8,46,161,102,40,217,36,178,118,91,162,73,109,139,209,37,114,248,246,100,134,104,152,22,212,164,92,204,93,101,182,146,108,112,72,80,253,237,185,218,94,21,70,87,167,141,157,132,144,216,171,0,140,188,211,10,247,228,88,5,184,179,69,6,208,44,30,143,202 [...]
+193,175,189,3,1,19,138,107,58,145,17,65,79,103,220,234,151,242,207,206,240,180,230,115,150,172,116,34,231,173,53,133,226,249,55,232,28,117,223,110,71,241,26,113,29,41,197,137,111,183,98,14,170,24,190,27,252,86,62,75,198,210,121,32,154,219,192,254,120,205,90,244,31,221,168,51,136,7,199,49,177,18,16,89,39,128,236,95,96,81,127,169,25,181,74,13,45,229,122,159,147,201,156,239,160,224,59,77,174,42,245,176,200,235,187,60,131,83,153,97,23,43,4,126,186,119,214,38,225,105,20,99,85,33,12,125]),f=ne [...]
+235474187,470948374,303765277,941896748,908933415,607530554,708780849,1883793496,2118214995,1817866830,1649639237,1215061108,1181045119,1417561698,1517767529,3767586992,4003061179,4236429990,4069246893,3635733660,3602770327,3299278474,3400528769,2430122216,2664543715,2362090238,2193862645,2835123396,2801107407,3035535058,3135740889,3678124923,3576870512,3341394285,3374361702,3810496343,3977675356,4279080257,4043610186,2876494627,2776292904,3076639029,3110650942,2472011535,2640243204,2403 [...]
+1001089995,899835584,666464733,699432150,59727847,226906860,530400753,294930682,1273168787,1172967064,1475418501,1509430414,1942435775,2110667444,1876241833,1641816226,2910219766,2743034109,2976151520,3211623147,2505202138,2606453969,2302690252,2269728455,3711829422,3543599269,3240894392,3475313331,3843699074,3943906441,4178062228,4144047775,1306967366,1139781709,1374988112,1610459739,1975683434,2076935265,1775276924,1742315127,1034867998,866637845,566021896,800440835,92987698,193195065, [...]
+395441711,1984812685,2017778566,1784663195,1683407248,1315562145,1080094634,1383856311,1551037884,101039829,135050206,437757123,337553864,1042385657,807962610,573804783,742039012,2531067453,2564033334,2328828971,2227573024,2935566865,2700099354,3001755655,3168937228,3868552805,3902563182,4203181171,4102977912,3736164937,3501741890,3265478751,3433712980,1106041591,1340463100,1576976609,1408749034,2043211483,2009195472,1708848333,1809054150,832877231,1068351396,766945465,599762354,15941798 [...]
+361929877,463180190,2709260871,2943682380,3178106961,3009879386,2572697195,2538681184,2236228733,2336434550,3509871135,3745345300,3441850377,3274667266,3910161971,3877198648,4110568485,4211818798,2597806476,2497604743,2261089178,2295101073,2733856160,2902087851,3202437046,2968011453,3936291284,3835036895,4136440770,4169408201,3535486456,3702665459,3467192302,3231722213,2051518780,1951317047,1716890410,1750902305,1113818384,1282050075,1584504582,1350078989,168810852,67556463,371049330,404 [...]
+1008918595,775550814,540080725,3969562369,3801332234,4035489047,4269907996,3569255213,3669462566,3366754619,3332740144,2631065433,2463879762,2160117071,2395588676,2767645557,2868897406,3102011747,3069049960,202008497,33778362,270040487,504459436,875451293,975658646,675039627,641025152,2084704233,1917518562,1615861247,1851332852,1147550661,1248802510,1484005843,1451044056,933301370,967311729,733156972,632953703,260388950,25965917,328671808,496906059,1206477858,1239443753,1543208500,144195 [...]
+1908694277,1675577880,1842759443,3610369226,3644379585,3408119516,3307916247,4011190502,3776767469,4077384432,4245618683,2809771154,2842737049,3144396420,3043140495,2673705150,2438237621,2203032232,2370213795]);d.prototype={decryptBlock:function(c){var b,e=c.length,f=this.buffer,d=this.bufferPosition;for(b=0;16>d&&b<e;++b,++d)f[d]=c[b];if(16>d)return this.bufferLength=d,new Uint8Array([]);this.iv=f;this.buffer=new Uint8Array(16);this.bufferLength=0;this.decryptBlock=a;return this.decrypt [...]
+return d}(),Xb=function(){function d(a,b){this.stringCipherConstructor=a;this.streamCipherConstructor=b}d.prototype={createStream:function(a){var b=new this.streamCipherConstructor;return new id(a,function(a){return b.decryptBlock(a)})},decryptString:function(a){var b=new this.stringCipherConstructor,a=Ma(a),a=b.decryptBlock(a);return tb(a)}};return d}(),Uc=function(){function d(a,b,g){var d=a.get("Filter");(!T(d)||"Standard"!=d.name)&&r("unknown encryption method");this.dict=a;d=a.get(" [...]
+1!=d&&2!=d&&4!=d)&&r("unsupported encryption algorithm");this.algorithm=d;var k=a.get("Length")||40;(!E(k)||40>k||0!=k%8)&&r("invalid key length");var j=Ma(a.get("O")),h=Ma(a.get("U")),m=a.get("P"),l=a.get("R"),n=!1!==a.get("EncryptMetadata"),b=Ma(b),o;g&&(o=Ma(g));var g=o,p=new Uint8Array([40,191,78,94,78,117,138,65,100,0,78,86,255,250,1,8,46,46,0,182,208,104,62,128,47,12,169,254,100,83,105,122]),q=new Uint8Array(100),u=0;if(g)for(o=Math.min(32,g.length);u<o;++u)q[u]=g[u];for(g=0;32>u;) [...]
+for(g=0,o=j.length;g<o;++g)q[u++]=j[g];q[u++]=m&255;q[u++]=m>>8&255;q[u++]=m>>16&255;q[u++]=m>>>24&255;for(g=0,o=b.length;g<o;++g)q[u++]=b[g];4<=l&&!n&&(q[u++]=255,q[u++]=255,q[u++]=255,q[u++]=255);o=Xa(q,0,u);k>>=3;if(3<=l)for(g=0;50>g;++g)o=Xa(o,0,k);k=o.subarray(0,k);if(3<=l){u=32;for(g=0,o=b.length;g<o;++g)q[u++]=b[g];l=new Ra(k);b=l.encryptBlock(Xa(q,0,u));o=k.length;q=new Uint8Array(o);for(g=1;19>=g;++g){for(l=0;l<o;++l)q[l]=k[l]^g;l=new Ra(q);b=l.encryptBlock(b)}}else l=new Ra(k), [...]
+32));for(g=0,o=b.length;g<o;++g)h[g]!=b[g]&&r("incorrect password");this.encryptionKey=k;4==d&&(this.cf=a.get("CF"),this.stmf=a.get("StmF")||c,this.strf=a.get("StrF")||c,this.eff=a.get("EFF")||this.strf)}function a(a,b,c,d){var k=new Uint8Array(c.length+9),j,h;for(j=0,h=c.length;j<h;++j)k[j]=c[j];k[j++]=a&255;k[j++]=a>>8&255;k[j++]=a>>16&255;k[j++]=b&255;k[j++]=b>>8&255;d&&(k[j++]=115,k[j++]=65,k[j++]=108,k[j++]=84);return Xa(k,0,j).subarray(0,Math.min(c.length+5,16))}function b(b,c,g,d, [...]
+b.get(c.name),j;null!=b&&(j=b.get("CFM"));if(!j||"None"==j.name)return function(){return new gd};if("V2"==j.name)return function(){return new Ra(a(g,d,k,!1))};if("AESV2"==j.name)return function(){return new hd(a(g,d,k,!0))};r("Unknown crypto method")}var c=new wa("Identity");d.prototype={createCipherTransform:function(c,f){if(4==this.algorithm)return new Xb(b(this.cf,this.stmf,c,f,this.encryptionKey),b(this.cf,this.strf,c,f,this.encryptionKey));var g=a(c,f,this.encryptionKey,!1),d=functi [...]
+return new Xb(d,d)}};return d}();"use strict";var Gc=function(){function d(a,b,f){this.state=new jd;this.stateStack=[];this.xref=a;this.handler=b;this.uniquePrefix=f;this.objIdCounter=0}function a(a){for(var e=a.length-1;0<e;e--){var f=a.substring(0,e),g=a.substring(e);if(f in b&&g in b)return[f,g]}return null}var b={w:"setLineWidth",J:"setLineCap",j:"setLineJoin",M:"setMiterLimit",d:"setDash",ri:"setRenderingIntent",i:"setFlatness",gs:"setGState",q:"save",Q:"restore",cm:"transform",m:"m [...]
+c:"curveTo",v:"curveTo2",y:"curveTo3",h:"closePath",re:"rectangle",S:"stroke",s:"closeStroke",f:"fill",F:"fill","f*":"eoFill",B:"fillStroke","B*":"eoFillStroke",b:"closeFillStroke","b*":"closeEOFillStroke",n:"endPath",W:"clip","W*":"eoClip",BT:"beginText",ET:"endText",Tc:"setCharSpacing",Tw:"setWordSpacing",Tz:"setHScale",TL:"setLeading",Tf:"setFont",Tr:"setTextRenderingMode",Ts:"setTextRise",Td:"moveText",TD:"setLeadingMoveText",Tm:"setTextMatrix","T*":"nextLine",Tj:"showText",TJ:"showS [...]
+"'":"nextLineShowText",'"':"nextLineSetSpacingShowText",d0:"setCharWidth",d1:"setCharWidthAndBounds",CS:"setStrokeColorSpace",cs:"setFillColorSpace",SC:"setStrokeColor",SCN:"setStrokeColorN",sc:"setFillColor",scn:"setFillColorN",G:"setStrokeGray",g:"setFillGray",RG:"setStrokeRGBColor",rg:"setFillRGBColor",K:"setStrokeCMYKColor",k:"setFillCMYKColor",sh:"shadingFill",BI:"beginInlineImage",ID:"beginImageData",EI:"endInlineImage",Do:"paintXObject",MP:"markPoint",DP:"markPointProps",BMC:"begi [...]
+BDC:"beginMarkedContentProps",EMC:"endMarkedContent",BX:"beginCompat",EX:"endCompat"};d.prototype={getOperatorList:function(c,e,f,g){function d(a){o.push("dependency");p.push(a);for(var b=0,c=a.length;b<c;b++)-1==f.indexOf(a[b])&&f.push(a[b])}function k(a,b){var c=null,g=e.get("Font");g||r("fontRes not available");b=m.fetchIfRef(b)||g.get(a);fa(U(b));++h.objIdCounter;b.translated||(b.translated=h.translateFont(b,m,e,f),b.translated&&(c="font_"+n+h.objIdCounter,b.translated.properties.loa [...]
+b.loadedName=c,g=b.translated,g.file&&(g.file=g.file.getBytes()),g.properties.file&&(g.properties.file=g.properties.file.getBytes()),l.send("obj",[c,"Font",g.name,g.file,g.properties])));c=c||b.loadedName;d([c]);return c}function j(a,b){var c=a.dict,f=c.get("Width","W"),g=c.get("Height","H");if(c.get("ImageMask","IM")){var f=c.get("Width","W"),g=c.get("Height","H"),k=a.getBytes((f+7>>3)*g),c=c.get("Decode","D"),c=!!c&&0<c[0];C="paintImageMaskXObject";t=[k,c,f,g]}else{var j="img_"+n+ ++h. [...]
+d([j]);t=[j,f,g];!c.get("SMask","IM")&&a instanceof jb&&a.isNativelySupported(m,e)?(C="paintJpegXObject",l.send("obj",[j,"JpegStream",a.getIR()])):(C="paintImageXObject",kd.buildImage(function(a){var b=a.drawWidth,c=a.drawHeight,e={width:b,height:c,data:new Uint8Array(4*b*c)};a.fillRgbaBuffer(e.data,b,c);l.send("obj",[j,"Image",e])},l,m,e,a,b))}}var h=this,m=this.xref,l=this.handler,n=this.uniquePrefix||"";g||(g={});g.argsArray||(g.argsArray=[]);g.fnArray||(g.fnArray=[]);for(var o=g.fnAr [...]
+q=f||[],e=e||new xa,u=e.get("XObject")||new xa,O=e.get("Pattern")||new xa,c=new Pa(new va(c),!1,m),N=e,ba=!1,ha,t=[],x;;){if(ba)x=ha,ba=!1;else if(x=c.getObj(),x==Z)break;if(aa(x)){x=x.cmd;var C=b[x];if(!C){var F=a(x);F&&(x=F[0],C=b[x],ba=!0,ha=Ja.get(F[1]))}fa(C,'Unknown command "'+x+'"');if(("SCN"==x||"scn"==x)&&!t[t.length-1].code){if(x=t[t.length-1],T(x)){var s=O.get(x.name);s&&(x=oa(s)?s.dict:s,F=x.get("PatternType"),1==F?(F=q.length,s=this.getOperatorList(s,x.get("Resources")||e,q) [...]
+t=Mb.getIR(s,x,t)):2==F?(s=x.get("Shading"),F=x.get("Matrix"),s=wb.parseShading(s,F,m,N),t=s.getIR()):r("Unkown PatternType "+F))}}else if("Do"==x&&!t[0].code){if(x=u.get(t[0].name))fa(oa(x),"XObject should be a stream"),F=x.dict.get("Subtype"),fa(T(F),"XObject should have a Name subtype"),"Form"==F.name?(F=x.dict.get("Matrix"),s=x.dict.get("BBox"),o.push("paintFormXObjectBegin"),p.push([F,s]),F=q.length,this.getOperatorList(x,x.dict.get("Resources")||e,q,g),d(q.slice(F)),C="paintFormXOb [...]
+t=[]):"Image"==F.name?j(x,!1):r("Unhandled XObject subtype "+F.name)}else"Tf"==x?t[0]=k(t[0].name):"EI"==x&&j(t[0],!0);switch(C){case "setFillColorSpace":case "setStrokeColorSpace":t=[ia.parseToIR(t[0],m,e)];break;case "shadingFill":(x=N.get("Shading"))||r("No shading resource found");(s=x.get(t[0].name))||r("No shading object found");t=[wb.parseShading(s,null,m,N).getIR()];C="shadingFill";break;case "setGState":x=t[0];F=e.get("ExtGState");if(!U(F)||!F.has(x.name))break;var Sa=[];F.get(x [...]
+b){switch(a){case "Type":break;case "LW":case "LC":case "LJ":case "ML":case "D":case "RI":case "FL":case "CA":case "ca":Sa.push([a,b]);break;case "Font":Sa.push(["Font",k(null,b[0]),b[1]]);break;case "OP":case "op":case "OPM":case "BG":case "BG2":case "UCR":case "UCR2":case "TR":case "TR2":case "HT":case "SM":case "SA":case "BM":case "SMask":case "AIS":case "TK":ea("graphic state operator "+a);break;default:Y("Unknown graphic state operator "+a)}});t=[Sa]}o.push(C);p.push(t);t=[]}else nu [...]
+t.length,"Too many arguments"),t.push(x instanceof xa?x.getAll():x))}return g},extractDataStructures:function(a,b,f,g){if(b=a.get("ToUnicode")||b.get("ToUnicode"))g.toUnicode=this.readToUnicode(b,f);g.composite&&(f=a.get("CIDSystemInfo"),U(f)&&(g.cidSystemInfo={registry:f.get("Registry"),ordering:f.get("Ordering"),supplement:f.get("Supplement")}),f=a.get("CIDToGIDMap"),oa(f)&&(g.cidToGidMap=this.readCidToGidMap(f)));var f=[],b=g.flags&rb?ja.symbolsEncoding:ja.StandardEncoding,d=a.has("En [...]
+if(d)if(a=a.get("Encoding"),U(a)){var k=a.get("BaseEncoding");k?b=ja[k.name]:d=!1;if(a.has("Differences"))for(var a=a.get("Differences"),j=k=0,h=a.length;j<h;j++){var m=a[j];sa(m)?k=m:f[k++]=m.name}}else T(a)?b=ja[a.name]:r("Encoding is not a Name nor a Dict");g.differences=f;g.baseEncoding=b;g.hasEncoding=d},readToUnicode:function(a){var b=[];if(T(a))"Identity-"==a.name.substr(0,9)||r("ToUnicode file cmap translation not implemented");else if(oa(a))for(var f=[],g="",d={},a=a.getBytes(a. [...]
+0,j=a.length;k<j;k++){var h=a[k];if(32==h||13==h||10==h||60==h||91==h||93==h){switch(g){case "usecmap":r("usecmap is not implemented");break;case "beginbfchar":case "beginbfrange":case "begincidchar":case "begincidrange":g="";f=[];break;case "endcidrange":case "endbfrange":for(var m=0,l=f.length;m<l;m+=3){var n=f[m],o=f[m+1],p=f[m+2];65535==p&&(p=n);if(R(p))for(var q=0;n<=o;)b[n]=p[q++],++n;else for(;n<=o;)b[n]=p++,++n}break;case "endcidchar":case "endbfchar":m=0;for(l=f.length;m<l;m+=2) [...]
+f[m+1],b[n]=p;break;case "":break;default:"0"<=g[0]&&"9">=g[0]&&(g=parseInt(g,10)),f.push(g),g=""}switch(h){case 91:f.push(d);break;case 93:for(var h=[],u;f.length&&(u=f.pop())!=d;)h.unshift(u);f.push(h)}}else if(62==h){if(g.length){if(4>=g.length)f.push(parseInt(g,16));else{h=[];m=0;for(l=g.length;m<l;m+=4){p=parseInt(g.substr(m,4),16);if(16>=p){m+=4;p=p<<16|parseInt(g.substr(m,4),16);p-=65536;h.push(55296|p>>10);h.push(56320|p&1023);break}h.push(p)}f.push(String.fromCharCode.apply(Stri [...]
+""}}else g+=String.fromCharCode(h)}return b},readCidToGidMap:function(a){for(var a=a.getBytes(),b=[],f=0,g=a.length;f<g;f++){var d=a[f++]<<8|a[f];0!=d&&(b[f>>1]=d)}return b},extractWidths:function(a,b,f,g){var b=[],d=0;if(g.composite){var d=a.get("DW")||1E3,k=a.get("W");if(k)for(var a=f=0,j=k.length;a<j;a++){var h=k[a];if(R(h)){for(var m=0,l=h.length;m<l;m++)b[f++]=h[m];f=0}else if(f){l=k[++a];for(m=f;m<=h;m++)b[m]=l;f=0}else f=h}}else if(j=g.firstChar,k=a.get("Widths")){m=j;a=0;for(j=k. [...]
+j;a++)b[m++]=k[a];d=parseFloat(f.get("MissingWidth"))||0}else k=a.get("BaseFont"),T(k)&&(d=this.getBaseFontMetrics(k.name),b=d.widths,d=d.defaultWidth);g.defaultWidth=d;g.widths=b},getBaseFontMetrics:function(a){var b=0,f=[],a=ld[Pb[a]||a];sa(a)?b=a:f=a;return{defaultWidth:b,widths:f}},translateFont:function(a,b,f,g){var d=a,k=a.get("Subtype");fa(T(k),"invalid font Subtype");var j=!1;if("Type0"==k.name){a=a.get("DescendantFonts");if(!a)return null;a=R(a)?b.fetchIfRef(a[0]):a;k=a.get("Sub [...]
+"invalid font Subtype");j=!0}var h=j?65535:255,m=a.get("FontDescriptor");if(!m)if("Type3"==k.name)m=new xa,m.set("FontName",new wa(k.name));else{g=a.get("BaseFont");if(!T(g))return null;var g=g.name.replace(/[,_]/g,"-"),l=this.getBaseFontMetrics(g),f=g.split("-")[0],f=(md[f]||-1!=f.search(/serif/gi)?Bb:0)|(nd[f]?rb:Vb),h={type:k.name,widths:l.widths,defaultWidth:l.defaultWidth,flags:f,firstChar:0,lastChar:h};this.extractDataStructures(a,a,b,h);return{name:g,dict:d,properties:h}}var n=a.g [...]
+0,o=a.get("LastChar")||h,p=m.get("FontName");Na(p)&&(p=new wa(p));fa(T(p),"invalid font name");var q=m.get("FontFile","FontFile2","FontFile3");if(q&&q.dict){var u=q.dict.get("Subtype");u&&(u=u.name);var O=q.dict.get("Length1"),N=q.dict.get("Length2")}h={type:k.name,subtype:u,file:q,length1:O,length2:N,composite:j,fixedPitch:!1,fontMatrix:a.get("FontMatrix")||Fa,firstChar:n||0,lastChar:o||h,bbox:m.get("FontBBox"),ascent:m.get("Ascent"),descent:m.get("Descent"),xHeight:m.get("XHeight"),cap [...]
+flags:m.get("Flags"),italicAngle:m.get("ItalicAngle"),coded:!1};this.extractWidths(a,b,m,h);this.extractDataStructures(a,d,b,h);if("Type3"===k.name)for(l in h.coded=!0,b=a.get("CharProcs").getAll(),a=a.get("Resources")||f,h.charProcOperatorList={},b)h.charProcOperatorList[l]=this.getOperatorList(b[l],a,g);return{name:p.name,dict:d,file:q,properties:h}}};return d}(),jd=function(){function d(){this.alphaIsShape=!1;this.fontSize=0;this.textMatrix=Fa;this.wordSpacing=this.charSpacing=this.li [...]
+this.leading=0;this.textHScale=1;this.strokeColorSpace=this.fillColorSpace=null}d.prototype={};return d}();"use strict";var la=57344,Ib=6400,$a=1E3,pc=!1;Bb=2;rb=4;Vb=32;var ja={ExpertEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclamsmall,Hungarumlautsmall,,dollaroldstyle,dollarsuperior,ampersandsmall,Acutesmall,parenleftsuperior,parenrightsuperior,twodotenleader,onedotenleader,comma,hyphen,period,fraction,zerooldstyle,oneoldstyle,twooldstyle,threeoldstyle,fouroldstyle,fiveoldstyle, [...]
+MacExpertEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclamsmall,Hungarumlautsmall,centoldstyle,dollaroldstyle,dollarsuperior,ampersandsmall,Acutesmall,parenleftsuperior,parenrightsuperior,twodotenleader,onedotenleader,comma,hyphen,period,fraction,zerooldstyle,oneoldstyle,twooldstyle,threeoldstyle,fouroldstyle,fiveoldstyle,sixoldstyle,sevenoldstyle,eightoldstyle,nineoldstyle,colon,semicolon,,threequartersemdash,,questionsmall,,,,,Ethsmall,,,onequarter,onehalf,threequarters,oneeighth, [...]
+MacRomanEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quotesingle,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,grave,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,braceleft,bar,braceright,asciitilde,,Adieresis [...]
+StandardEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quoteright,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,quoteleft,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,braceleft,bar,braceright,asciitilde,,,,,,,, [...]
+WinAnsiEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quotesingle,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,grave,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,braceleft,bar,braceright,asciitilde,bullet,Euro [...]
+symbolsEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,exclam,universal,numbersign,existential,percent,ampersand,suchthat,parenleft,parenright,asteriskmath,plus,comma,minus,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,congruent,Alpha,Beta,Chi,Delta,Epsilon,Phi,Gamma,Eta,Iota,theta1,Kappa,Lambda,Mu,Nu,Omicron,Pi,Theta,Rho,Sigma,Tau,Upsilon,sigma1,Omega,Xi,Psi,Zeta,bracketleft,therefore,bracketright,perpendicular,underscore,radi [...]
+zapfDingbatsEncoding:",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,space,a1,a2,a202,a3,a4,a5,a119,a118,a117,a11,a12,a13,a14,a15,a16,a105,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a6,a7,a8,a9,a10,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a203,a75,a204,a76,a77,a78,a79,a81,a82,a83,a84,a97,a98,a99,a100,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,a101,a102,a103,a104,a10 [...]
+Pb={ArialNarrow:"Helvetica","ArialNarrow-Bold":"Helvetica-Bold","ArialNarrow-BoldItalic":"Helvetica-BoldOblique","ArialNarrow-Italic":"Helvetica-Oblique",ArialBlack:"Helvetica","ArialBlack-Bold":"Helvetica-Bold","ArialBlack-BoldItalic":"Helvetica-BoldOblique","ArialBlack-Italic":"Helvetica-Oblique",Arial:"Helvetica","Arial-Bold":"Helvetica-Bold","Arial-BoldItalic":"Helvetica-BoldOblique","Arial-Italic":"Helvetica-Oblique","Arial-BoldItalicMT":"Helvetica-BoldOblique","Arial-BoldMT":"Helve [...]
+"Arial-ItalicMT":"Helvetica-Oblique",ArialMT:"Helvetica","Courier-Bold":"Courier-Bold","Courier-BoldItalic":"Courier-BoldOblique","Courier-Italic":"Courier-Oblique",CourierNew:"Courier","CourierNew-Bold":"Courier-Bold","CourierNew-BoldItalic":"Courier-BoldOblique","CourierNew-Italic":"Courier-Oblique","CourierNewPS-BoldItalicMT":"Courier-BoldOblique","CourierNewPS-BoldMT":"Courier-Bold","CourierNewPS-ItalicMT":"Courier-Oblique",CourierNewPSMT:"Courier","Helvetica-Bold":"Helvetica-Bold"," [...]
+"Helvetica-Italic":"Helvetica-Oblique","Symbol-Bold":"Symbol","Symbol-BoldItalic":"Symbol","Symbol-Italic":"Symbol",TimesNewRoman:"Times-Roman","TimesNewRoman-Bold":"Times-Bold","TimesNewRoman-BoldItalic":"Times-BoldItalic","TimesNewRoman-Italic":"Times-Italic",TimesNewRomanPS:"Times-Roman","TimesNewRomanPS-Bold":"Times-Bold","TimesNewRomanPS-BoldItalic":"Times-BoldItalic","TimesNewRomanPS-BoldItalicMT":"Times-BoldItalic","TimesNewRomanPS-BoldMT":"Times-Bold","TimesNewRomanPS-Italic":"Ti [...]
+"TimesNewRomanPS-ItalicMT":"Times-Italic",TimesNewRomanPSMT:"Times-Roman","TimesNewRomanPSMT-Bold":"Times-Bold","TimesNewRomanPSMT-BoldItalic":"Times-BoldItalic","TimesNewRomanPSMT-Italic":"Times-Italic"},od={ComicSansMS:"Comic Sans MS","ComicSansMS-Bold":"Comic Sans MS-Bold","ComicSansMS-BoldItalic":"Comic Sans MS-BoldItalic","ComicSansMS-Italic":"Comic Sans MS-Italic",LucidaConsole:"Courier","LucidaConsole-Bold":"Courier-Bold","LucidaConsole-BoldItalic":"Courier-BoldOblique","LucidaCon [...]
+md={"Adobe Jenson":!0,"Adobe Text":!0,Albertus:!0,Aldus:!0,Alexandria:!0,Algerian:!0,"American Typewriter":!0,Antiqua:!0,Apex:!0,Arno:!0,Aster:!0,Aurora:!0,Baskerville:!0,Bell:!0,Bembo:!0,"Bembo Schoolbook":!0,Benguiat:!0,"Berkeley Old Style":!0,"Bernhard Modern":!0,"Berthold City":!0,Bodoni:!0,"Bauer Bodoni":!0,"Book Antiqua":!0,Bookman:!0,"Bordeaux Roman":!0,"Californian FB":!0,Calisto:!0,Calvert:!0,Capitals:!0,Cambria:!0,Cartier:!0,Caslon:!0,Catull:!0,Centaur:!0,"Century Old Style":!0 [...]
+Chaparral:!0,"Charis SIL":!0,Cheltenham:!0,"Cholla Slab":!0,Clarendon:!0,Clearface:!0,Cochin:!0,Colonna:!0,"Computer Modern":!0,"Concrete Roman":!0,Constantia:!0,"Cooper Black":!0,Corona:!0,Ecotype:!0,Egyptienne:!0,Elephant:!0,Excelsior:!0,Fairfield:!0,"FF Scala":!0,Folkard:!0,Footlight:!0,FreeSerif:!0,"Friz Quadrata":!0,Garamond:!0,Gentium:!0,Georgia:!0,Gloucester:!0,"Goudy Old Style":!0,"Goudy Schoolbook":!0,"Goudy Pro Font":!0,Granjon:!0,"Guardian Egyptian":!0,Heather:!0,Hercules:!0," [...]
+Hiroshige:!0,"Hoefler Text":!0,"Humana Serif":!0,Imprint:!0,"Ionic No. 5":!0,Janson:!0,Joanna:!0,Korinna:!0,Lexicon:!0,"Liberation Serif":!0,"Linux Libertine":!0,Literaturnaya:!0,Lucida:!0,"Lucida Bright":!0,Melior:!0,Memphis:!0,Miller:!0,Minion:!0,Modern:!0,"Mona Lisa":!0,"Mrs Eaves":!0,"MS Serif":!0,"Museo Slab":!0,"New York":!0,"Nimbus Roman":!0,"NPS Rawlinson Roadway":!0,Palatino:!0,Perpetua:!0,Plantin:!0,"Plantin Schoolbook":!0,Playbill:!0,"Poor Richard":!0,"Rawlinson Roadway":!0,Re [...]
+Requiem:!0,Rockwell:!0,Roman:!0,"Rotis Serif":!0,Sabon:!0,Scala:!0,Seagull:!0,Sistina:!0,Souvenir:!0,STIX:!0,"Stone Informal":!0,"Stone Serif":!0,Sylfaen:!0,Times:!0,Trajan:!0,"Trinit\u00e9":!0,"Trump Mediaeval":!0,Utopia:!0,"Vale Type":!0,"Bitstream Vera":!0,"Vera Serif":!0,Versailles:!0,Wanted:!0,Weiss:!0,"Wide Latin":!0,Windsor:!0,XITS:!0},nd={Dingbats:!0,Symbol:!0,ZapfDingbats:!0},Kb={listeningForFontLoad:!1,bind:function(d,a){function b(){for(var c=0,e=d.length;c<e;c++)if(d[c].loadi [...]
+document.documentElement.removeEventListener("pdfjsFontLoad",b,!1);a();return!0}for(var c=[],e=[],f=[],g=0,i=d.length;g<i;g++){var k=d[g];if(!(k.attached||!1==k.loading)){k.attached=!0;f.push(k);var j="",h=k.data;if(h){for(var m=h.length,l=0;l<m;l++)j+=String.fromCharCode(h[l]);if(j=k.bindDOM(j))c.push(j),e.push(k.loadedName)}}}this.listeningForFontLoad=!1;!Jb&&c.length&&Kb.prepareFontLoadEvent(c,e,f);b()||document.documentElement.addEventListener("pdfjsFontLoad",b,!1)},prepareFontLoadEv [...]
+a,b){if(/^\w+$/.test(a.join(""))){var c=document.createElement("div");c.setAttribute("style","visibility: hidden;width: 10px; height: 10px;position: absolute; top: 0px; left: 0px;");for(var e="",f=0,g=a.length;f<g;++f)e+='<span style="font-family:'+a[f]+'">Hi</span>';c.innerHTML=e;document.body.appendChild(c);this.listeningForFontLoad||(window.addEventListener("message",function(a){JSON.parse(a.data);for(var a=0,c=b.length;a<c;++a)b[a].loading=!1;a=document.createEvent("Events");a.initEv [...]
+!0,!1);document.documentElement.dispatchEvent(a)},!1),this.listeningForFontLoad=!0);c='<!DOCTYPE HTML><html><head><style type="text/css">';f=0;for(g=d.length;f<g;++f)c+=d[f];d="";f=0;for(g=a.length;f<g;++f)d+='"'+a[f]+'", ';c=c+'</style><script type="application/javascript">'+(" var fontNames=["+d+"];\n")+' window.onload = function fontLoaderOnload() {\n parent.postMessage(JSON.stringify(fontNames), "*");\n }<\/script></head><body>';f=0;for(g=a.length;f<g;++f)c+="<p style=\"font-fa [...]
+"'\">Hi</p>";c+="</body></html>";a=document.createElement("iframe");a.src="data:text/html,"+c;a.setAttribute("style","visibility: hidden;width: 10px; height: 10px;position: absolute; top: 0px; left: 0px;");document.body.appendChild(a)}else r("Invalid font name(s): "+a.join())}},Ya=[{begin:0,end:127},{begin:128,end:255},{begin:256,end:383},{begin:384,end:591},{begin:592,end:687},{begin:688,end:767},{begin:768,end:879},{begin:880,end:1023},{begin:11392,end:11519},{begin:1024,end:1279},{beg [...]
+{begin:1424,end:1535},{begin:42240,end:42559},{begin:1536,end:1791},{begin:1984,end:2047},{begin:2304,end:2431},{begin:2432,end:2559},{begin:2560,end:2687},{begin:2688,end:2815},{begin:2816,end:2943},{begin:2944,end:3071},{begin:3072,end:3199},{begin:3200,end:3327},{begin:3328,end:3455},{begin:3584,end:3711},{begin:3712,end:3839},{begin:4256,end:4351},{begin:6912,end:7039},{begin:4352,end:4607},{begin:7680,end:7935},{begin:7936,end:8191},{begin:8192,end:8303},{begin:8304,end:8351},{begin [...]
+{begin:8400,end:8447},{begin:8448,end:8527},{begin:8528,end:8591},{begin:8592,end:8703},{begin:8704,end:8959},{begin:8960,end:9215},{begin:9216,end:9279},{begin:9280,end:9311},{begin:9312,end:9471},{begin:9472,end:9599},{begin:9600,end:9631},{begin:9632,end:9727},{begin:9728,end:9983},{begin:9984,end:10175},{begin:12288,end:12351},{begin:12352,end:12447},{begin:12448,end:12543},{begin:12544,end:12591},{begin:12592,end:12687},{begin:43072,end:43135},{begin:12800,end:13055},{begin:13056,en [...]
+{begin:44032,end:55215},{begin:55296,end:57343},{begin:67840,end:67871},{begin:19968,end:40959},{begin:57344,end:63743},{begin:12736,end:12783},{begin:64256,end:64335},{begin:64336,end:65023},{begin:65056,end:65071},{begin:65040,end:65055},{begin:65104,end:65135},{begin:65136,end:65279},{begin:65280,end:65519},{begin:65520,end:65535},{begin:3840,end:4095},{begin:1792,end:1871},{begin:1920,end:1983},{begin:3456,end:3583},{begin:4096,end:4255},{begin:4608,end:4991},{begin:5024,end:5119},{b [...]
+end:5759},{begin:5760,end:5791},{begin:5792,end:5887},{begin:6016,end:6143},{begin:6144,end:6319},{begin:10240,end:10495},{begin:40960,end:42127},{begin:5888,end:5919},{begin:66304,end:66351},{begin:66352,end:66383},{begin:66560,end:66639},{begin:118784,end:119039},{begin:119808,end:120831},{begin:1044480,end:1048573},{begin:65024,end:65039},{begin:917504,end:917631},{begin:6400,end:6479},{begin:6480,end:6527},{begin:6528,end:6623},{begin:6656,end:6687},{begin:11264,end:11359},{begin:115 [...]
+{begin:19904,end:19967},{begin:43008,end:43055},{begin:65536,end:65663},{begin:65856,end:65935},{begin:66432,end:66463},{begin:66464,end:66527},{begin:66640,end:66687},{begin:66688,end:66735},{begin:67584,end:67647},{begin:68096,end:68191},{begin:119552,end:119647},{begin:73728,end:74751},{begin:119648,end:119679},{begin:7040,end:7103},{begin:7168,end:7247},{begin:7248,end:7295},{begin:43136,end:43231},{begin:43264,end:43311},{begin:43312,end:43359},{begin:43520,end:43615},{begin:65936,e [...]
+{begin:66E3,end:66047},{begin:66208,end:66271},{begin:127024,end:127135}],qc=".notdef,.null,nonmarkingreturn,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quotesingle,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,grave,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t [...]
+Rc=function(){function d(a,b,c){this.name=a;this.coded=c.coded;this.charProcOperatorList=c.charProcOperatorList;this.sizes=[];var e=a.split("+"),e=1<e.length?e[1]:e[0];e.split(/[-,_]/g);this.isSerifFont=!!(c.flags&Bb);this.isSymbolicFont=!!(c.flags&rb);this.type=e=c.type;if(c.ignore)this.loadedName=this.isSerifFont?"serif":"sans-serif",this.loading=!1;else if(this.differences=c.differences,this.widths=c.widths,this.defaultWidth=c.defaultWidth,this.composite=c.composite,this.hasEncoding=c [...]
+this.fontMatrix=c.fontMatrix,this.widthMultiplier=1,"Type3"==c.type)this.encoding=c.baseEncoding;else if(this.loadCidToUnicode(c),c.toUnicode?this.toUnicode=c.toUnicode:this.rebuildToUnicode(c),this.toFontChar=this.buildToFontChar(this.toUnicode),b){var f;switch(e){case "Type1":case "CIDFontType0":this.mimetype="font/opentype";f=c.subtype;b="Type1C"==f||"CIDFontType0C"==f?new pd(b,c):new rc(a,b,c);f=this.convert(a,b,c);break;case "TrueType":case "CIDFontType2":this.mimetype="font/opentyp [...]
+b,c);break;default:Y("Font "+c.type+" is not supported")}this.data=f;this.fontMatrix=c.fontMatrix;this.widthMultiplier=!c.fontMatrix?1:1/c.fontMatrix[0];this.encoding=c.baseEncoding;this.loadedName=c.loadedName;this.loading=!0}else b=a.replace(/[,_]/g,"-"),b=Pb[b]||od[b]||b,this.bold=-1!=b.search(/bold/gi),this.italic=-1!=b.search(/oblique/gi)||-1!=b.search(/italic/gi),this.black=-1!=a.search(/Black/g),this.encoding=c.baseEncoding,this.noUnicodeAdaptation=!0,this.loadedName=b.split("-")[ [...]
+!1}function a(a){for(var b=[],c=0,e=a.length;c<e;++c)b[c]=a.charCodeAt(c);return b}function b(a){for(var b="",c=0,e=a.length;c<e;++c)b+=String.fromCharCode(a[c]);return b}function c(a){return(a[0]<<8)+(a[1]&255)}function e(a){return(a[0]<<24)+(a[1]<<16)+(a[2]<<8)+(a[3]&255)}function f(a){for(var b=0;2<=a;)a/=2,b++;for(var a=2,c=1;c<b;c++)a*=2;return a}function g(a){return String.fromCharCode(a>>8&255)+String.fromCharCode(a&255)}function i(a){a=32767<a?32767:-32768>a?-32768:a;return Strin [...]
+8&255)+String.fromCharCode(a&255)}function k(a){return String.fromCharCode(a>>24&255)+String.fromCharCode(a>>16&255)+String.fromCharCode(a>>8&255)+String.fromCharCode(a&255)}function j(a,b,c){"true"==a&&(a=k(65536));var a=a+g(c),e=f(c),d=16*e,a=a+g(d),a=a+g(Math.log(e)/Math.log(2)),a=a+g(16*c-d);b.file+=a;b.virtualOffset+=a.length}function h(a,b,c){for(var f=a.virtualOffset,g=c.length;c.length&3;)c.push(0);for(;a.virtualOffset&3;)a.virtualOffset++;for(var d=0,i=c.length,h=0;h<i;h+=4)d=d+ [...]
+1],c[h+2],c[h+3]])|0;b=b+k(d)+k(f)+k(g);a.file+=b;a.virtualOffset+=c.length}function m(a){for(var b=[],c=a.length,e=0;e<c;++e)b.push({unicode:a[e].unicode,code:e});b.sort(function(a,b){return a.unicode-b.unicode});a=[];for(e=0;e<c;){var f=b[e].unicode,g=[b[e].code];++e;for(var d=f;e<c&&d+1==b[e].unicode;)g.push(b[e].code),++d,++e;a.push([f,d,g])}return a}function l(b,c){var e=m(b),d="\x00\x00"+g(1)+"\x00\u0003\x00\u0001"+k(12),i=e.length+1,h=2*i,j=2*f(i),l=Math.log(i)/Math.log(2),n=2*i-j [...]
+p="",r="",H="",G=0;if(c)for(var I=0;I<i-1;I++)for(var A=e[I],J=A[0],w=A[1],M=2*(i-I)+2*G,G=G+(w-J+1),o=o+g(J),s=s+g(w),p=p+g(0),r=r+g(M),J=A[2],w=0,A=J.length;w<A;++w)H+=g(c[J[w]]);else for(I=0;I<i-1;I++)A=e[I],J=A[0],w=A[1],G=A[2][0],o+=g(J),s+=g(w),p+=g(G-J+1&65535),r+=g(0);e="\x00\x00"+g(h)+g(j)+g(l)+g(n)+(s+"\u00ff\u00ff")+"\x00\x00"+(o+"\u00ff\u00ff")+(p+"\x00\u0001")+(r+"\x00\x00")+H;return a(d+"\x00\u0004"+g(e.length+4)+e)}function n(a,b,c){var c=c||{unitsPerEm:0,yMax:0,yMin:0,asc [...]
+e=0,f=0,d=0,i=0,h=null,j=0;if(b)for(var m=0;m<b.length;++m){var l=b[m].unicode;if(h>l||!h)h=l;j<l&&(j=l);a:{for(var n=0,o=Ya.length;n<o;n++){var p=Ya[n];if(l>=p.begin&&l<p.end){l=n;break a}}l=-1}32>l?e|=1<<l:64>l?f|=1<<l-32:96>l?d|=1<<l-64:123>l?i|=1<<l-96:r("Unicode ranges Bits > 123 are reserved for internal usage")}else h=0,j=255;b=c.unitsPerEm||$a;m=c.ascent||a.ascent;l=c.descent||a.descent;n=c.yMax||m;o=-c.yMin||-l;b!=$a&&"undefined"==typeof c.ascent&&(m=Math.round(m*b/$a),l=Math.ro [...]
+n=m,o=-l);return"\x00\u0003\u0002$\u0001\u00f4\x00\u0005\x00\x00\u0002\u008a\u0002\u00bb\x00\x00\x00\u008c\u0002\u008a\u0002\u00bb\x00\x00\u0001\u00df\x001\u0001\u0002\x00\x00\x00\x00\u0006"+String.fromCharCode(a.fixedPitch?9:0)+"\x00\x00\x00\x00\x00\x00"+k(e)+k(f)+k(d)+k(i)+"*21*"+g(a.italicAngle?1:0)+g(h||a.firstChar)+g(j||a.lastChar)+g(m)+g(l)+"\x00d"+g(n)+g(o)+"\x00\x00\x00\x00\x00\x00\x00\x00"+g(a.xHeight)+g(a.capHeight)+g(0)+g(h||a.firstChar)+"\x00\u0003"}function o(a){var b=Math.f [...]
+Math.pow(2,16));return"\x00\u0003\x00\x00"+k(b)+"\x00\x00\x00\x00"+k(a.fixedPitch)+"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}function p(a){for(var a=["Original licence",a,"Unknown",a,a,"Version 0.11",a],b=[],c=0,e=a.length;c<e;c++){for(var f=a[c],d="",i=0,h=f.length;i<h;i++)d+=g(f.charCodeAt(i));b.push(d)}for(var d=[a,b],k=["\x00\u0001","\x00\u0003"],j=["\x00\x00","\x00\u0001"],m=["\x00\x00","\u0004\t"],c=a.length*k.length,l="\x00\x00"+g(c)+g(12*c+6),n=0,c=0,e=k. [...]
+e;c++)for(var o=d[c],i=0,h=o.length;i<h;i++)var f=o[i],p=k[c]+j[c]+m[c]+g(i)+g(f.length)+g(n),l=l+p,n=n+f.length;return l+=a.join("")+b.join("")}d.prototype={name:null,font:null,mimetype:null,encoding:null,checkAndRepair:function(f,d,i){function m(a){var b=a.getBytes(4),b=String.fromCharCode(b[0])+String.fromCharCode(b[1])+String.fromCharCode(b[2])+String.fromCharCode(b[3]),c=e(a.getBytes(4)),f=e(a.getBytes(4)),d=e(a.getBytes(4)),g=a.pos;a.pos=a.start?a.start:0;a.skip(f);var i=a.getBytes [...]
+g;"head"==b&&(i[8]=i[9]=i[10]=i[11]=0,i[17]|=32);return{tag:b,checksum:c,length:d,offset:f,data:i}}function ba(a,b,c){var e=c.glyphNames;if(e){for(var f=a.length,d={},g=[],i=0;i<f;++i){var h=e[b[i]];h&&(d[h]=a[i].unicode,g[a[i].code]=h)}c.glyphNameMap=d;c.hasEncoding||(c.baseEncoding=g)}else c.glyphNameMap={}}function ha(a,b){var f=(b.start?b.start:0)+a.offset;b.pos=f;for(var d=c(b.getBytes(2)),i=c(b.getBytes(2)),h=[],j=0;j<i;j++)h.push({platformID:c(b.getBytes(2)),encodingID:c(b.getByte [...]
+h.sort(function(a,b){return(a.platformID<<16)+a.encodingID-((b.platformID<<16)+b.encodingID)});for(var m=[h[0]],j=1;j<i;j++){var l=h[j],n=h[j-1];(l.platformID<<16)+l.encodingID<=(n.platformID<<16)+n.encodingID||m.push(l)}if(i-m.length){i=m.length;d=g(d)+g(i);for(j=0;j<i;j++)h=m[j],d+=g(h.platformID)+g(h.encodingID)+k(h.offset);j=0;for(h=d.length;j<h;j++)a.data[j]=d.charCodeAt(j)}for(j=0;j<i;j++){h=m[j];b.pos=f+h.offset;d=c(b.getBytes(2));c(b.getBytes(2));c(b.getBytes(2));if(0==d){i=[];j= [...]
+0;256>m;m++)if(f=b.getByte())i.push({unicode:m,code:m}),j.push(f);return{glyphs:i,ids:j,hasShortCmap:!0}}if(4==d){d=c(b.getBytes(2))>>1;b.getBytes(6);l=[];for(h=0;h<d;h++)l.push({end:c(b.getBytes(2))});b.getBytes(2);for(h=0;h<d;h++)l[h].start=c(b.getBytes(2));for(h=0;h<d;h++)l[h].delta=c(b.getBytes(2));for(h=f=0;h<d;h++)if(m=l[h],i=c(b.getBytes(2))){var u=(i>>1)-(d-h);m.offsetIndex=u;f=Math.max(f,u+m.end-m.start+1)}else m.offsetIndex=-1;for(var t=[],m=0;m<f;m++)t.push(c(b.getBytes(2)));i [...]
+for(h=0;h<d;h++)for(var m=l[h],f=m.start,q=m.end,O=m.delta,u=m.offsetIndex,m=f;m<=q;m++)65535!=m&&(n=0>u?m:t[u+m-f],n=n+O&65535,0!=n&&(i.push({unicode:m,code:m}),j.push(n)));return{glyphs:i,ids:j}}if(6==d){f=c(b.getBytes(2));d=c(b.getBytes(2));i=[];j=[];for(m=0;m<d;m++)n=c(b.getBytes(2)),h=f+m,i.push({unicode:h,code:h}),j.push(n);return{glyphs:i,ids:j}}}r("Unsupported cmap table format")}function t(b,e,f,d){if(e||f)if(!e&&f)f.data=null;else if(b.pos=(b.start?b.start:0)+e.offset,b.pos+=e. [...]
+e=c(b.getBytes(2)),d=d-e-(M.length-4*e>>1),0<d){b.pos=(b.start?b.start:0)+f.offset;for(var e="",g=0,i=M.length;g<i;g++)e+=String.fromCharCode(b.getByte());for(g=0;g<d;g++)e+="\x00\x00";f.data=a(e)}}function x(a,b,c,e,f){if(12>=c-b)return 0;var a=a.subarray(b,c),d=a[0]<<8|a[1];if(d&32768)return e.set(a,f),a.length;for(var b=10,g=c=0;g<d;g++)c=(a[b]<<8|a[b+1])+1,b+=2;b+=2+(a[b]<<8|a[b+1]);for(g=d=0;g<c;g++){var i=a[b++];if(i&192)return 0;var h=(i&2?1:i&16?0:2)+(i&4?1:i&32?0:2),d=d+h;i&8&&( [...]
+g+=i,d+=i*h)}b+=d;if(b>a.length)return 0;if(3<a.length-b)return b=b+3&-4,e.set(a.subarray(0,b),f),b;e.set(a,f);return a.length}function C(a,b,c,e){var f,d;e?(e=4,f=function(a,b){return a[b]<<24|a[b+1]<<16|a[b+2]<<8|a[b+3]},d=function(a,b,c){a[b]=c>>>24&255;a[b+1]=c>>16&255;a[b+2]=c>>8&255;a[b+3]=c&255}):(e=2,f=function(a,b){return a[b]<<9|a[b+1]<<1},d=function(a,b,c){a[b]=c>>9&255;a[b+1]=c>>1&255});var a=a.data,g=b.data,i=g.length,h=new Uint8Array(i),j=f(a,0),k=0;d(a,0,k);for(var m=0,l=e [...]
+l+=e){var n=f(a,l);n>i||(j=x(g,j,n,h,k),k+=j);d(a,l,k);j=n}if(0==k){f=new Uint8Array([0,1,0,0,0,0,0,0,0,0,0,0,0,0,49,0]);m=0;for(l=e;m<c;m++,l+=e)d(a,l,f.length);b.data=f}else b.data=h.subarray(0,k)}function F(a,b,c){var e;b?(b=4,e=function(a,b){return a[b]<<24|a[b+1]<<16|a[b+2]<<8|a[b+3]}):(b=2,e=function(a,b){return a[b]<<9|a[b+1]<<1});for(var a=a.data,f=a.length,d=e(a,0),g=b,i=0;g<f;g+=b,i++){var h=e(a,g);h==d&&(c[i]=!0);d=h}}function s(a,b){var f=(d.start?d.start:0)+a.offset;d.pos=f; [...]
+g=e(d.getBytes(4));d.getBytes(28);var i;switch(g){case 65536:i=qc;break;case 131072:for(var g=c(d.getBytes(2)),h=[],k=0;k<g;++k)h.push(c(d.getBytes(2)));for(var j=[];d.pos<f;){i=d.getByte();for(var m="",k=0;k<i;++k)m+=d.getChar();j.push(m)}i=[];for(k=0;k<g;++k)f=h[k],258>f?i.push(qc[f]):i.push(j[f-258]);break;case 196608:break;default:Y("Unknown/unsupported post table version "+g)}b.glyphNames=i}function Sa(a){a=a.data;return 0==(a[74]<<8|a[75])?!1:!0}var y="OS/2,cmap,head,hhea,hmtx,maxp [...]
+H=b(d.getBytes(4)),f=c(d.getBytes(2));c(d.getBytes(2));c(d.getBytes(2));c(d.getBytes(2));for(var G=f,I,A,J,w,M,B,z,ga,D,L,Q,f=[],v=0;v<G;v++){var K=m(d),E=y.indexOf(K.tag);-1!=E?("cmap"==K.tag?I=K:"post"==K.tag?A=K:"maxp"==K.tag?J=K:"hhea"==K.tag?w=K:"hmtx"==K.tag?M=K:"head"==K.tag?ga=K:"OS/2"==K.tag&&(Q=K),y.splice(E,1)):"vmtx"==K.tag?z=K:"vhea"==K.tag?B=K:"loca"==K.tag?D=K:"glyf"==K.tag&&(L=K);f.push(K)}var G=f.length+y.length,R={file:"",virtualOffset:16*G};j(H,R,G);Q&&!Sa(Q)&&(f.splic [...]
+1),Q=null);d.pos=(d.start||0)+J.offset;c(d.getBytes(4));K=c(d.getBytes(2));t(d,w,M,K);t(d,B,z,K);v=c([ga.data[50],ga.data[51]]);ga&&D&&L&&C(D,L,K,v);var $=[];L&&F(D,v,$);0==w.data[10]&&0==w.data[11]&&(w.data[10]=255,w.data[11]=255);A&&s(A,i);if("CIDFontType2"==i.type){I||(I={tag:"cmap",data:null},f.push(I));v=i.cidToGidMap||[];E=[0];if(0<v.length){for(var V=v.length-1;0<=V;V--)($=v[V])&&(E[$]=V);A=v.length;for(v=1;v<K;v++)E[v]||(E[v]=A++)}A=[];L=[];for(var qa=[],kb=[],v=1;v<K;v++){D=E[v] [...]
+this.toFontChar[D];!P||"number"!==typeof P||Wb(P)||P in qa?kb.push(v):(qa[P]=!0,A.push({unicode:P,code:D}),L.push(v))}B=la;for(var V=0,ab=kb.length;V<ab;V++){v=kb[V];for(D=E[v]||v;B in qa;)B++;if(B>=la+Ib)break;P=B++;this.toFontChar[D]=P;qa[P]=!0;A.push({unicode:P,code:D});L.push(v)}}else{v=ha(I,d);A=v.glyphs;L=v.ids;var sc=!!v.hasShortCmap;z=this.toFontChar;if(sc&&L.length==K){v=0;for(D=L.length;v<D;v++)L[v]=v}B=la;J=i.glyphNames||[];H=i.baseEncoding;G=i.differences;if(z&&0<z.length){P= [...]
+A.length;v<D;v++)if(A[v].unicode!=v+1){P=!1;break}if(P&&!this.isSymbolicFont){qa=[];kb=[];v=0;for(D=A.length;v<D;v++)P=z[v+1],!P||"number"!==typeof P||P in qa?kb.push(v):(A[v].unicode=P,qa[P]=!0);V=0;for(ab=kb.length;V<ab;V++){for(v=kb[V];B in qa;)B++;D=v+1;D in z||(z[D]=B);A[v].unicode=B++}this.useToFontChar=!0}}V=0;for(v=L.length-1;0<=v;v--)L[v]<K&&(!$[L[v]]||this.isSymbolicFont)||(L.splice(v,1),A.splice(v,1),V++);if(this.isSymbolicFont){K=65535;v=$=0;for(D=A.length;v<D;v++)P=A[v].unic [...]
+P),$=Math.max($,P);($&65280)!=(K&65280)&&(this.isSymbolicFont=!1)}2<V&&(Y("Switching TrueType encoding to MacRomanEncoding for "+this.name+" font"),H=ja.MacRomanEncoding);if(sc&&this.hasEncoding&&!this.isSymbolicFont){qa=[];v=0;for(D=A.length;v<D;v++)K=A[v].unicode,$=L[v],A[v].unicode+=la,z[K]=A[v].unicode,P=J[$]||H[K],P in na&&(P=na[P],P in qa||(qa[P]=!0,A.push({unicode:P,code:A[v].code}),L.push($),z[K]=P));this.useToFontChar=!0}else if(!this.isSymbolicFont&&(this.hasEncoding||i.glyphNa [...]
+[];v=0;for(D=A.length;v<D;v++)V[A[v].unicode]=v;qa=[];v=0;for(D=A.length;v<D;v++)if(K=A[v].unicode,ab=!1,$=L[v],P=J[$],P||(P=G[K]||H[K],ab=!0),P in na&&(P=na[P])&&V[P]!==v)qa[v]=P,ab&&(z[K]=P),delete V[K];for(E in qa)P=qa[E],V[P]?A[E].unicode=B++:(A[E].unicode=P,V[P]=E);this.useToFontChar=!0}if(this.isSymbolicFont){v=0;for(D=A.length;v<D;v++)K=A[v].unicode&255,A[v].unicode=z[K]=61440|K;this.useToFontChar=!0}ba(A,L,i);this.glyphNameMap=i.glyphNameMap}I.data=l(A,L);I=[];v=0;for(D=A.length; [...]
+!0;this.unicodeIsEnabled=I;Q||(w={unitsPerEm:c([ga.data[18],ga.data[19]]),yMax:c([ga.data[42],ga.data[43]]),yMin:c([ga.data[38],ga.data[39]])-65536,ascent:c([w.data[4],w.data[5]]),descent:c([w.data[6],w.data[7]])-65536},f.push({tag:"OS/2",data:a(n(i,A,w))}));-1!=y.indexOf("post")&&f.push({tag:"post",data:a(o(i))});-1!=y.indexOf("name")&&f.push({tag:"name",data:a(p(this.name))});f.sort(function(a,b){return(a.tag>b.tag)-(a.tag<b.tag)});v=0;for(D=f.length;v<D;v++){K=f[v];i=[];y=K.data;V=0;f [...]
+ab;V++)i.push(y[V]);h(R,K.tag,i)}v=0;for(D=f.length;v<D;v++){K=f[v];y=K.data;for(R.file+=b(y);R.file.length&3;)R.file+=String.fromCharCode(0)}return a(R.file)},convert:function(c,e,f){var d={file:"",virtualOffset:144};j("OTTO",d,9);var k=e.charstrings;f.fixedPitch=function(a){for(var b=0,c=a.length-1;b<c;b++)if(a[b]!=a[b+1])return!1;return!0}(k);for(var m={},t=0;t<k.length;++t){var x=k[t];m[x.glyph]=x.unicode}this.glyphNameMap=m;if(!f.hasEncoding&&("Type1C"==f.subtype||"CIDFontType0C"==f [...]
+[];for(t=0;t<k.length;++t)x=k[t],m[x.code]=x.glyph;f.baseEncoding=m}if("CIDFontType0C"==f.subtype){m=[];for(t=0;t<k.length;++t)x=k[t],m[x.code]=x.unicode;this.toFontChar=m}var c={"CFF ":e.data,"OS/2":a(n(f,k)),cmap:l(k.slice(),"glyphIds"in e?e.glyphIds:null),head:a("\x00\u0001\x00\x00\x00\x00\u0010\x00\x00\x00\x00\x00_\u000f<\u00f5\x00\x00\u0003\u00e8\x00\x00\x00\x00\u009e\x0B~'\x00\x00\x00\x00\u009e\x0B~'\x00\x00"+i(f.descent)+"\u000f\u00ff"+i(f.ascent)+g(f.italicAngle?2:0)+"\x00\u0011\ [...]
+hhea:a("\x00\u0001\x00\x00"+i(f.ascent)+i(f.descent)+"\x00\x00\u00ff\u00ff\x00\x00\x00\x00\x00\x00"+i(f.capHeight)+i(Math.tan(f.italicAngle)*f.xHeight)+"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+g(k.length+1)),hmtx:function(){for(var b="\x00\x00\x00\x00",c=0,e=k.length;c<e;c++)var f=k[c],b=b+(g("width"in f?f.width:0)+g(0));return a(b)}(),maxp:a("\x00\x00P\x00"+g(k.length+1)),name:a(p(c)),post:a(o(f))},C;for(C in c)h(d,C,c[C]);for(C in c)d.file+=b(c[C]);return a(d.file)},buildToFo [...]
+[],c=la,e=0,f=a.length;e<f;e++){var d=a[e],g="object"===typeof d?c++:d;"undefined"!==typeof d&&(b[e]=g)}return b},rebuildToUnicode:function(a){var b=a.firstChar,c=a.lastChar,e=[];if(a.composite)for(a=0==this.cidToUnicode.length;b<=c;b++){var f=b;e[b]=a?f:this.cidToUnicode[f]}else for(;b<=c;b++)(f=a.differences[b])||(f=a.baseEncoding[b]),f&&f in na&&(e[b]=na[f]);this.toUnicode=e},loadCidToUnicode:function(a){if(a.cidSystemInfo){var b=[],c=[];this.cidToUnicode=b;this.unicodeToCID=c;var a=a [...]
+e;a&&(e=cd[a.registry+"-"+a.ordering]);if(e){var a=1,f,d,g,i;for(f=0,i=e.length;f<i;++f)if(g=e[f],R(g)){var h=g.length;for(d=0;d<h;d++)b[a]=g[d],c[g[d]]=a;a++}else if("object"===typeof g)if(h=g.f){g=g.c;for(d=0;d<h;++d)b[a]=g,c[g]=a,a++,g++}else a+=g.s;else g&&(b[a]=g,c[g]=a),a++}}},bindDOM:function(a){var b=this.loadedName,a="url(data:"+this.mimetype+";base64,"+window.btoa(a)+");",b="@font-face { font-family:'"+b+"';src:"+a+"}",c=document.createElement("style");document.documentElement. [...]
+c=c.sheet;c.insertRule(b,c.cssRules.length);PDFJS.pdfBug&&FontInspector.enabled&&FontInspector.fontAdded(this,a);return b},get spaceWidth(){for(var a=["space","minus","one","i"],b,c=0,e=a.length;c<e;c++){b=a[c];if(b in this.widths){b=this.widths[b];break}b=na[b];var f=0;this.composite&&(f=this.unicodeToCID[b]);!f&&"toUnicode"in this&&(f=this.toUnicode.indexOf(b));0<f||(f=b);if(b=this.widths[f])break}b=(b||this.defaultWidth)*this.widthMultiplier;return ca(this,"spaceWidth",b)},charToGlyph [...]
+c,e;c=this.widths[a];switch(this.type){case "CIDFontType0":if(this.noUnicodeAdaptation){c=this.widths[this.unicodeToCID[a]||a];b=Hb(a);break}b=this.toFontChar[a]||a;break;case "CIDFontType2":if(this.noUnicodeAdaptation){c=this.widths[this.unicodeToCID[a]||a];b=Hb(a);break}b=this.toFontChar[a]||a;break;case "Type1":b=this.differences[a]||this.encoding[a];sa(c)||(c=this.widths[b]);if(this.noUnicodeAdaptation){b=Hb(na[b]||a);break}b=this.glyphNameMap[b]||na[b]||a;break;case "Type3":b=this.d [...]
+this.encoding[a];e=this.charProcOperatorList[b];b=a;break;case "TrueType":if(this.useToFontChar){b=this.toFontChar[a]||a;break}(b=this.differences[a]||this.encoding[a])||(b=ja.StandardEncoding[a]);sa(c)||(c=this.widths[b]);if(this.noUnicodeAdaptation){b=na[b]||a;break}if(!this.hasEncoding||this.isSymbolicFont){b=this.useToFontChar?this.toFontChar[a]:a;break}b=b in this.glyphNameMap?this.glyphNameMap[b]:na[b];break;default:Y("Unsupported font type: "+this.type)}var f=!("toUnicode"in this) [...]
+a;"number"===typeof f&&(f=String.fromCharCode(f));c=(sa(c)?c:this.defaultWidth)*this.widthMultiplier;a=this.unicodeIsEnabled?!this.unicodeIsEnabled[b]:!1;return{fontChar:String.fromCharCode(b),unicode:f,width:c,disabled:a,operatorList:e}},charsToGlyphs:function(a){var b=this.charsCache,e;if(b&&(e=b[a]))return e;b||(b=this.charsCache=Object.create(null));e=[];if(this.composite)for(var f=a.length-1,d=0;d<f;d++){var g=c([a.charCodeAt(d++),a.charCodeAt(d)]),i=this.charToGlyph(g);e.push(i);32 [...]
+0;for(f=a.length;d<f;++d)g=a.charCodeAt(d),i=this.charToGlyph(g),e.push(i),32==g&&e.push(null)}return b[a]=e}};return d}(),Qb=".notdef,space,exclam,quotedbl,numbersign,dollar,percent,ampersand,quoteright,parenleft,parenright,asterisk,plus,comma,hyphen,period,slash,zero,one,two,three,four,five,six,seven,eight,nine,colon,semicolon,less,equal,greater,question,at,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,bracketleft,backslash,bracketright,asciicircum,underscore,quoteleft,a,b,c,d,e, [...]
+tc=new function(){function d(a,b,c){for(var e=[],f="",d=a.length,g=0;g<d;g++)f=a[g],e[g]=f^b>>8,b=52845*(f+b)+22719&65535;return e.slice(c)}function a(a){for(var b=[],c=0,e=0,d=0,l="",n=a.length,o=0;o<n;o++){l=a[o];if(32>l){var p=null;if(l==g){var q=a[++o];if(16==q){for(var p=b.pop(),u=b.pop(),O=0;O<u;O++)b.push("drop");if(3>p)continue;if(3==p){b.push(3);o++;continue}}else if(17==q||33==q)continue;else if(!pc&&(1==q||2==q)){b.push("drop","drop","drop","drop","drop","drop");continue}p=f[" [...]
+l){2==b.length?(c=b[0],e=b[1],b.splice(0,1)):4==b.length&&"div"==b[3]?(c=b[0],e=b[1]/b[2],b.splice(0,1)):4==b.length&&"div"==b[2]?(c=b[0]/b[1],e=b[3],b.splice(0,3)):r("Unsupported hsbw format: "+b);b.push(c,"hmoveto");continue}else if(10==l){if(3>b[b.length-1]){switch(b.pop()){case 1:d=1;break;case 2:d=2;break;case 0:b.push("exch","drop","exch","drop"),b.push("flex"),d=0}continue}}else if(21==l&&0<d){if(1<d)continue;l=5}else if(!pc&&(1==l||3==l)){b.push("drop","drop");continue}p=f[l]}if( [...]
+else if(p){if(-1==p&&(Y("Support for Type1 command "+l+" ("+q+") is not implemented in charstring: "+b),12==l))switch(q){case 7:b.push("drop","drop","drop","drop");continue;case 8:b.push("drop");continue}}else break;l=p}else l=246>=l?l-139:250>=l?256*(l-247)+a[++o]+108:254>=l?-(256*(l-251))-a[++o]-108:(a[++o]&255)<<24|(a[++o]&255)<<16|(a[++o]&255)<<8|(a[++o]&255)<<0;b.push(l)}return{charstring:b,width:e,lsb:c}}function b(a,b){for(var c=b;"["!=a[b++];)c++;c++;for(var e=0;"]"!=a[b++];)e++; [...]
+a.substr(c,e),a=a.trim(),a=a.replace(/\s+/g," "),c=a.split(" "),e=0,f=c.length;e<f;e++)c[e]=parseFloat(c[e]||0);return c}function c(a,b){for(;" "==a[b];)b++;for(var c=b,e=0;" "!=a[b++];)e++;return parseFloat(a.substr(c,e)||0)}function e(a){return" "==a||"\n"==a||"\r"==a}var f={1:"hstem",3:"vstem",4:"vmoveto",5:"rlineto",6:"hlineto",7:"vlineto",8:"rrcurveto",9:null,10:"callsubr",11:"return",12:{"0":null,1:"vstem",2:"hstem",6:-1,7:-1,11:"sub",12:"div",16:"callothersubr",17:"pop",33:null},1 [...]
+14:"endchar",21:"rmoveto",22:"hmoveto",30:"vhcurveto",31:"hvcurveto"},g=12;this.extractFontProgram=function(f){for(var f=d(f,55665,4),g="",j=0,h=f.length;j<h;j++)g+=String.fromCharCode(f[j]);for(var h=!1,m={subrs:[],charstrings:[],properties:{privateData:{lenIV:4}}},l="",n="",o=0,p="",q=g.length,j=0;j<q;j++){var u=function(){for(;j<q&&e(g[j]);)++j;for(var a="";j<q&&!e(g[j]);)a+=g[j++];return a},p=g[j];if(h&&("RD"==n||"-|"==n)){j++;var O=f.slice(j,j+o),N=m.properties.privateData.lenIV,O=d [...]
+O=a(O);h?m.charstrings.push({glyph:l,data:O.charstring,lsb:O.lsb,width:O.width}):m.subrs.push(O.charstring);j+=o;n=""}else if(e(p))o=parseInt(n,10),n="";else if(n+=p,h){if("/"==p)for(n=l="";" "!=(p=g[++j]);)l+=p}else switch(n){case "/CharString":h=!0;break;case "/Subrs":++j;p=parseInt(u(),10);u();for(var ba=0;ba<p;++ba){N=u();if("ND"==N||"|-"==N||"noaccess"==N)break;var ha=parseInt(u(),10);ha>ba&&(ba=ha);o=parseInt(u(),10);u();O=f.slice(j+1,j+1+o);N=m.properties.privateData.lenIV;O=d(O,4 [...]
+a(O);j=j+1+o;N=u();"noaccess"==N&&u();m.subrs[ha]=O.charstring}break;case "/BlueValues":case "/OtherBlues":case "/FamilyBlues":case "/FamilyOtherBlues":case "/StemSnapH":case "/StemSnapV":m.properties.privateData[n.substring(1)]=b(g,j+1);break;case "/StdHW":case "/StdVW":m.properties.privateData[n.substring(1)]=b(g,j+2)[0];break;case "/BlueShift":case "/lenIV":case "/BlueFuzz":case "/BlueScale":case "/LanguageGroup":case "/ExpansionFactor":m.properties.privateData[n.substring(1)]=c(g,j+1 [...]
+this.extractFontHeader=function(a,c){for(var f="",d=0,g=a.length;d<g;d++)f+=String.fromCharCode(a[d]);for(var l="",n=f.length,d=0;d<n;d++){var g=function(){for(var a=f[d];d<n&&(e(a)||"/"==a);)a=f[++d];for(var b="";d<n&&!(e(a)||"/"==a);)b+=a,a=f[++d];return b},o=f[d];if(e(o)){switch(l){case "/FontMatrix":for(var g=b(f,d+1),o=0,p=g.length;o<p;o++)g[o]*=1E3;g[2]*=-1;c.fontMatrix=g;break;case "/Encoding":o=g();if(/^\d+$/.test(o)){var p=[],q=parseInt(o,10);g();for(o=0;o<q;o++)if(l=g(),"dup"== [...]
+parseInt(g(),10),u=g();p[l]=u;g()}}else p=ja[o];!c.hasEncoding&&p&&(c.baseEncoding=p)}l=""}else l+=o}}},rc=function(d,a,b){var c=a.getBytes(b.length1);tc.extractFontHeader(c,b);var a=a.getBytes(b.length2),a=tc.extractFontProgram(a),e;for(e in a.properties)b[e]=a.properties[e];e=this.getOrderedCharStrings(a.charstrings,b);c=this.getType2Charstrings(e);a=this.getType2Subrs(a.subrs);this.charstrings=e;this.data=this.wrap(d,c,this.charstrings,a,b)};rc.prototype={createCFFIndexHeader:function [...]
+d.length;if(0==b)return"\x00\x00\x00";for(var c=String.fromCharCode(b>>8&255,b&255),c=c+"\u0004",e=1,f=0;f<b+1;f++)c+=String.fromCharCode(e>>>24&255,e>>16&255,e>>8&255,e&255),d[f]&&(e+=d[f].length);for(f=0;f<b;f++)for(var e=0,g=d[f].length;e<g;e++)c+=a?String.fromCharCode(d[f][e]&255):d[f][e];return c},encodeNumber:function(d){d|=0;return-32768<=d&&32767>=d?"\u001c"+String.fromCharCode(d>>8&255)+String.fromCharCode(d&255):"\u001d"+String.fromCharCode(d>>24&255)+String.fromCharCode(d>>16& [...]
+8&255)+String.fromCharCode(d&255)},getOrderedCharStrings:function(d){var a=[],b,c,e,f=la;for(b=0,c=d.length;b<c;b++){var g=d[b];e=g.glyph;var i=e in na?na[e]:f++;a.push({glyph:e,unicode:i,gid:b,charstring:g.data,width:g.width,lsb:g.lsb})}a.sort(function(a,b){return a.unicode-b.unicode});return a},getType2Charstrings:function(d){for(var a=[],b=d.length,c=0;c<b;c++)a.push(this.flattenCharstring(d[c].charstring.slice(),this.commandsMap));return a},getType2Subrs:function(d){for(var a=0,b=d.l [...]
+b?107:33900>b?1131:32768,c=[],e=0;e<a;e++)c.push([11]);for(e=0;e<b;e++)(a=d[e])||(a=[11]),c.push(this.flattenCharstring(a,this.commandsMap));return c},commandsMap:{hstem:1,vstem:3,vmoveto:4,rlineto:5,hlineto:6,vlineto:7,rrcurveto:8,callsubr:10,"return":11,sub:[12,11],div:[12,12],exch:[12,28],flex:[12,35],drop:[12,18],endchar:14,rmoveto:21,hmoveto:22,vhcurveto:30,hvcurveto:31},flattenCharstring:function(d,a){for(var b=0;b<d.length;b++){var c=d[b];if(c.charAt){var e=a[c];e||r("Unknow comma [...]
+R(e)?d.splice(b++,1,e[0],e[1]):d[b]=e}else 32E3<c?(c/=d[b+1],d.splice(b,3,28,c>>8,c&255)):d.splice(b,1,28,c>>8,c&255),b+=2}return d},wrap:function(d,a,b,c,e){var f={header:"\u0001\x00\u0004\u0004",names:this.createCFFIndexHeader([d]),topDict:function(b){return function(){for(var c="\u00f8\u001b\x00\u00f8\u001c\u0001\u00f8\u001d\u0002\u00f8\u001e\u0003\u00f8\u001f\u0004\u001c\x00\x00\u0010",d=e.bbox,g=0,i=d.length;g<i;g++)c+=b.encodeNumber(d[g]);c+="\u0005";d=f.header.length+f.names.lengt [...]
+8)+f.strings.length+f.globalSubrs.length;d=32767<d+f.charstrings.length?d+9:d+7;c+=b.encodeNumber(d)+"\u000f";d=d+2*a.length+1;c+=b.encodeNumber(d)+"\u0011";d+=f.charstrings.length;c+=b.encodeNumber(f.privateData.length);c+=b.encodeNumber(d)+"\u0012";return"\x00\u0001\u0001\u0001"+String.fromCharCode(c.length+1)+c}}(this),strings:this.createCFFIndexHeader(["Version 0.11","See original notice",d,d,"Medium"]),globalSubrs:this.createCFFIndexHeader([]),charset:function(){for(var c="\x00",e=a [...]
+0;f<e;f++){var d=Qb.indexOf(b[f].glyph);-1==d&&(d=0);c+=String.fromCharCode(d>>8,d&255)}return c}(this),charstrings:this.createCFFIndexHeader([[139,14]].concat(a),!0),privateData:function(a){var b="\u008b\u0014\u008b\u0015",c={BlueValues:"\u0006",OtherBlues:"\u0007",FamilyBlues:"\u0008",FamilyOtherBlues:"\t",StemSnapH:"\u000c\u000c",StemSnapV:"\u000c\r",BlueShift:"\u000c\n",BlueFuzz:"\u000c\x0B",BlueScale:"\u000c\t",LanguageGroup:"\u000c\u0011",ExpansionFactor:"\u000c\u0018"},f;for(f in [...]
+e.privateData[f];if(R(d))for(var b=b+a.encodeNumber(d[0]),g=1,i=d.length;g<i;g++)b+=a.encodeNumber(d[g]-d[g-1]);else b+=a.encodeNumber(d);b+=c[f]}return b+=a.encodeNumber(b.length+4)+"\u0013"}(this),localSubrs:this.createCFFIndexHeader(c,!0)};f.topDict=f.topDict();var d=[],g;for(g in f)for(var c=f[g],i=0,k=c.length;i<k;i++)d.push(c.charCodeAt(i));return d}};var pd=function(){function d(a,b){this.properties=b;var c=(new qd(a,b)).parse(),e=new rd(c);this.readExtra(c);try{this.data=e.compil [...]
+b.loadedName),this.data=a}}d.prototype={readExtra:function(a){for(var a=this.getCharStrings(a.charset.charset,a.encoding?a.encoding.encoding:null),b=[],c=0,e=a.length;c<e;c++)b.push(a[c].gid);this.charstrings=a;this.glyphIds=b},getCharStrings:function(a,b){var c=[],e=[],f=[],d=[];if(null!==b)for(var i in b)d[b[i]]=i|0;else d=a;i=0;for(var k=a.length;i<k;i++){var j=a[i];if(".notdef"!=j){var h=d[i];!h||Wb(h)?f.push(i):(c.push({unicode:h,code:h,gid:i,glyph:j}),e[h]=!0)}}k=la;j=0;for(h=f.len [...]
+f[j];k in e;)k++;var m=k++;c.push({unicode:m,code:d[i]||0,gid:i,glyph:a[i]})}c.sort(function(a,b){return a.unicode-b.unicode});return c}};return d}(),qd=function(){function d(a,b){this.bytes=a.getBytes();this.properties=b}d.prototype={parse:function(){var a=this.properties,b=new sd;this.cff=b;var c=this.parseHeader(),e=this.parseIndex(c.endPos),f=this.parseIndex(e.endPos),d=this.parseIndex(f.endPos),i=this.parseIndex(d.endPos),f=this.parseDict(f.obj.get(0)),f=this.createDict(uc,f,b.strin [...]
+c.obj;b.names=this.parseNameIndex(e.obj);b.strings=this.parseStringIndex(d.obj);b.topDict=f;b.globalSubrIndex=i.obj;this.parsePrivateDict(b.topDict);b.isCIDFont=f.hasName("ROS");c=f.getByName("CharStrings");b.charStrings=this.parseCharStrings(c);if(b.isCIDFont){a=this.parseIndex(f.getByName("FDArray")).obj;c=0;for(e=a.count;c<e;++c)d=a.get(c),d=this.createDict(uc,this.parseDict(d),b.strings),this.parsePrivateDict(d),b.fdArray.push(d);a=null;c=this.parseCharsets(f.getByName("charset"),b.c [...]
+b.strings,!0);b.fdSelect=this.parseFDSelect(f.getByName("FDSelect"),b.charStrings.count)}else c=this.parseCharsets(f.getByName("charset"),b.charStrings.count,b.strings,!1),a=this.parseEncoding(f.getByName("Encoding"),a,b.strings,c.charset);b.charset=c;b.encoding=a;return b},parseHeader:function(){for(var a=this.bytes,b=0;1!=a[b];)++b;0!=b&&(Y("cff data is shifted"),this.bytes=a=a.subarray(b));b=a[2];return{obj:new td(a[0],a[1],b,a[3]),endPos:b}},parseDict:function(a){function b(){var b=a [...]
+b){for(var b="",e=["0","1","2","3","4","5","6","7","8","9",".","E","E-",null,"-"],f=a.length;c<f;){var d=a[c++],g=d>>4,d=d&15;if(15==g)break;b+=e[g];if(15==d)break;b+=e[d]}return parseFloat(b)}if(28===b)return b=a[c++],b=b<<8|a[c++];if(29===b)return b=a[c++],b=b<<8|a[c++],b=b<<8|a[c++],b=b<<8|a[c++];if(32<=b&&246>=b)return b-139;if(247<=b&&250>=b)return 256*(b-247)+a[c++]+108;if(251<=b&&254>=b)return-(256*(b-251))-a[c++]-108;r("255 is not a valid DICT command");return-1}for(var c=0,e=[], [...]
+d=a.length;c<d;){var i=a[c];21>=i?(12===i&&(i=i<<8|a[++c]),f.push([i,e]),e=[],++c):e.push(b())}return f},parseIndex:function(a){var b=new lb,c=this.bytes,e=c[a++]<<8|c[a++],f=[],d=a;if(0!=e){for(var d=c[a++],i=a+(e+1)*d-1,k=0,j=e+1;k<j;++k){for(var h=0,m=0;m<d;++m)h<<=8,h+=c[a++];f.push(i+h)}d=f[e]}k=0;for(j=f.length-1;k<j;++k)b.add(c.subarray(f[k],f[k+1]));return{obj:b,endPos:d}},parseNameIndex:function(a){for(var b=[],c=0,e=a.count;c<e;++c){for(var f=a.get(c),d=Math.min(f.length,127),i [...]
+d;++k){var j=f[k];i[k]=0===k&&0===j?j:33>j||126<j||91===j||93===j||40===j||41===j||123===j||125===j||60===j||62===j||47===j||37===j?95:j}b.push(String.fromCharCode.apply(null,i))}return b},parseStringIndex:function(a){for(var b=new vc,c=0,e=a.count;c<e;++c){var f=a.get(c);b.add(String.fromCharCode.apply(null,f))}return b},createDict:function(a,b,c){for(var a=new a(c),c=0,e=b.length;c<e;++c){var f=b[c];a.setByKey(f[0],f[1])}return a},parseCharStrings:function(a){for(var a=this.parseIndex( [...]
+a.count,c=0;c<b;c++)for(var e=a.get(c),f=e.length,d=0;d<=f;d){var i=e[d++];12==i&&0==e[d++]?(e[d-2]=139,e[d-1]=22):28===i?d+=2:247<=i&&254>=i?d++:255==i&&(d+=4)}return a},parsePrivateDict:function(a){if(a.hasName("Private")){var b=a.getByName("Private");if(!R(b)||2!==b.length)a.removeByName("Private");else{var c=b[0],b=b[1];0===c||b>=this.bytes.length?a.removeByName("Private"):(c=this.parseDict(this.bytes.subarray(b,b+c)),c=this.createDict(ud,c,a.strings),a.privateDict=c,c.getByName("Sub [...]
+c.getByName("Subrs"),b+=a,0===a||b>=this.bytes.length?c.removeByName("Subrs"):(a=this.parseIndex(b),c.subrsIndex=a.obj)))}}},parseCharsets:function(a,b,c,e){if(0==a)return new bb(!0,zb,$c);if(1==a)return new bb(!0,Ab,ad);if(2==a)return new bb(!0,Ub,bd);var f=this.bytes,d=a,i=f[a++],k=[".notdef"],b=b-1;switch(i){case 0:for(var j=0;j<b;j++){var h=f[a++]<<8|f[a++];k.push(e?h:c.get(h))}break;case 1:for(;k.length<=b;)for(var h=f[a++]<<8|f[a++],m=f[a++],j=0;j<=m;j++)k.push(e?h++:c.get(h++));br [...]
+b;){h=f[a++]<<8|f[a++];m=f[a++]<<8|f[a++];for(j=0;j<=m;j++)k.push(e?h++:c.get(h++))}break;default:r("Unknown charset format")}a=f.subarray(d,a);return new bb(!1,i,k,a)},parseEncoding:function(a,b,c,e){var f={},d=this.bytes,i=!1,k,j=null;if(0==a||1==a){i=!0;k=a;for(var h=1,a=a?ja.ExpertEncoding:ja.StandardEncoding,m=0,b=e.length;m<b;m++)c=a.indexOf(e[m]),-1!=c&&(f[c]=h++)}else{e=a;k=d[a++];switch(k&127){case 0:h=d[a++];for(m=1;m<=h;m++)f[d[a++]]=m;break;case 1:j=d[a++];h=1;for(m=0;m<j;m++ [...]
+d[a++],n=d[a++],o=l;o<=l+n;o++)f[o]=h++;break;default:r("Unknow encoding format: "+k+" in CFF")}m=a;if(k&128){d[e]&=127;h=d[a++];for(j=0;j<h;j++)l=d[a++],n=(d[a++]<<8)+(d[a++]&255),f[l]=b.differences.indexOf(c.get(n))}j=d.subarray(e,m)}return new vd(i,k&127,f,j)},parseFDSelect:function(a,b){var c=a,e=this.bytes,f=e[a++],d=[];switch(f){case 0:for(f=0;f<b;++f){var i=e[a++];d.push(i)}break;case 3:i=e[a++]<<8|e[a++];for(f=0;f<i;++f)for(var k=e[a++]<<8|e[a++],j=e[a++],h=e[a]<<8|e[a+1];k<h;++k [...]
+a+=2;break;default:r("Unknown fdselect format "+f)}return new wd(d,e.subarray(c,a))}};return d}(),sd=function(){return function(){this.header=null;this.names=[];this.topDict=null;this.strings=new vc;this.charStrings=this.charset=this.encoding=this.globalSubrIndex=null;this.fdArray=[];this.fdSelect=null;this.isCIDFont=!1}}(),td=function(){return function(d,a,b,c){this.major=d;this.minor=a;this.hdrSize=b;this.offSize=c}}(),vc=function(){function d(){this.strings=[]}d.prototype={get:functio [...]
+a&&390>=a?Qb[a]:a-391<=this.strings.length?this.strings[a-391]:Qb[0]},add:function(a){this.strings.push(a)},get count(){return this.strings.length}};return d}(),lb=function(){function d(){this.objects=[];this.length=0}d.prototype={add:function(a){this.length+=a.length;this.objects.push(a)},get:function(a){return this.objects[a]},get count(){return this.objects.length}};return d}(),Ga=function(){function d(a,b){this.keyToNameMap=a.keyToNameMap;this.nameToKeyMap=a.nameToKeyMap;this.default [...]
+this.types=a.types;this.opcodes=a.opcodes;this.order=a.order;this.strings=b;this.values={}}d.prototype={setByKey:function(a,b){if(!(a in this.keyToNameMap))return!1;if(0===b.length)return!0;var c=this.types[a];if("num"===c||"sid"===c||"offset"===c)b=b[0];this.values[a]=b;return!0},hasName:function(a){return this.nameToKeyMap[a]in this.values},getByName:function(a){a in this.nameToKeyMap||r('Invalid dictionary name "'+a+'"');a=this.nameToKeyMap[a];return!(a in this.values)?this.defaults[a [...]
+removeByName:function(a){delete this.values[this.nameToKeyMap[a]]}};d.createTables=function(a){for(var b={keyToNameMap:{},nameToKeyMap:{},defaults:{},types:{},opcodes:{},order:[]},c=0,e=a.length;c<e;++c){var f=a[c],d=R(f[0])?(f[0][0]<<8)+f[0][1]:f[0];b.keyToNameMap[d]=f[1];b.nameToKeyMap[f[1]]=d;b.types[d]=f[2];b.defaults[d]=f[3];b.opcodes[d]=R(f[0])?f[0]:[f[0]];b.order.push(d)}return b};return d}(),uc=function(){function d(c){null===b&&(b=Ga.createTables(a));Ga.call(this,b,c);this.priva [...]
+var a=[[[12,30],"ROS",["sid","sid","num"],null],[[12,20],"SyntheticBase","num",null],[0,"version","sid",null],[1,"Notice","sid",null],[[12,0],"Copyright","sid",null],[2,"FullName","sid",null],[3,"FamilyName","sid",null],[4,"Weight","sid",null],[[12,1],"isFixedPitch","num",0],[[12,2],"ItalicAngle","num",0],[[12,3],"UnderlinePosition","num",-100],[[12,4],"UnderlineThickness","num",50],[[12,5],"PaintType","num",0],[[12,6],"CharstringType","num",2],[[12,7],"FontMatrix","num,num,num,num,num,n [...]
+[0.001,0,0,0.001,0,0]],[13,"UniqueID","num",null],[5,"FontBBox",["num","num","num","num"],[0,0,0,0]],[[12,8],"StrokeWidth","num",0],[14,"XUID","array",null],[15,"charset","offset",0],[16,"Encoding","offset",0],[17,"CharStrings","offset",0],[18,"Private",["offset","offset"],null],[[12,21],"PostScript","sid",null],[[12,22],"BaseFontName","sid",null],[[12,23],"BaseFontBlend","delta",null],[[12,31],"CIDFontVersion","num",0],[[12,32],"CIDFontRevision","num",0],[[12,33],"CIDFontType","num",0], [...]
+"CIDCount","num",8720],[[12,35],"UIDBase","num",null],[[12,36],"FDArray","offset",null],[[12,37],"FDSelect","offset",null],[[12,38],"FontName","sid",null]],b=null;d.prototype=Object.create(Ga.prototype);return d}(),ud=function(){function d(c){null===b&&(b=Ga.createTables(a));Ga.call(this,b,c);this.subrsIndex=null}var a=[[6,"BlueValues","delta",null],[7,"OtherBlues","delta",null],[8,"FamilyBlues","delta",null],[9,"FamilyOtherBlues","delta",null],[[12,9],"BlueScale","num",0.039625],[[12,10 [...]
+"num",7],[[12,11],"BlueFuzz","num",1],[10,"StdHW","num",null],[11,"StdVW","num",null],[[12,12],"StemSnapH","delta",null],[[12,13],"StemSnapV","delta",null],[[12,14],"ForceBold","num",0],[[12,17],"LanguageGroup","num",0],[[12,18],"ExpansionFactor","num",0.06],[[12,19],"initialRandomSeed","num",0],[19,"Subrs","offset",null],[20,"defaultWidthX","num",0],[21,"nominalWidthX","num",0]],b=null;d.prototype=Object.create(Ga.prototype);return d}();zb=0;Ab=1;Ub=2;var bb=function(){return function(d [...]
+d;this.format=a;this.charset=b;this.raw=c}}(),vd=function(){return function(d,a,b,c){this.predefined=d;this.format=a;this.encoding=b;this.raw=c}}(),wd=function(){return function(d,a){this.fdSelect=d;this.raw=a}}(),wc=function(){function d(){this.offsets={}}d.prototype={isTracking:function(a){return a in this.offsets},track:function(a,b){a in this.offsets&&r("Already tracking location of "+a);this.offsets[a]=b},offset:function(a){for(var b in this.offsets)this.offsets[b]+=a},setEntryLocat [...]
+b,c){a in this.offsets||r("Not tracking location of "+a);for(var c=c.data,a=this.offsets[a],e=0,f=b.length;e<f;++e){var d=5*e+a,i=d+1,k=d+2,j=d+3,h=d+4;(29!==c[d]||0!==c[i]||0!==c[k]||0!==c[j]||0!==c[h])&&r("writing to an offset that is not empty");var m=b[e];c[d]=29;c[i]=m>>24&255;c[k]=m>>16&255;c[j]=m>>8&255;c[h]=m&255}}};return d}(),rd=function(){function d(a){for(var c=[],e=0,f=a.length;e<f;++e)c[e]=a.charCodeAt(e);return c}function a(a){this.cff=a}a.prototype={compile:function(){var [...]
+c={data:[],length:0,add:function(a){this.data=this.data.concat(a);this.length=this.data.length}},e=this.compileHeader(a.header);c.add(e);e=this.compileNameIndex(a.names);c.add(e);var f=this.compileTopDicts([a.topDict],c.length);c.add(f.output);e=f.trackers[0];f=this.compileStringIndex(a.strings.strings);c.add(f);f=this.compileIndex(a.globalSubrIndex);c.add(f);a.encoding&&a.topDict.hasName("Encoding")&&(a.encoding.predefined?e.setEntryLocation("Encoding",[a.encoding.format],c):(f=this.com [...]
+e.setEntryLocation("Encoding",[c.length],c),c.add(f)));a.charset&&a.topDict.hasName("charset")&&(a.charset.predefined?e.setEntryLocation("charset",[a.charset.format],c):(f=this.compileCharset(a.charset),e.setEntryLocation("charset",[c.length],c),c.add(f)));f=this.compileCharStrings(a.charStrings);e.setEntryLocation("CharStrings",[c.length],c);c.add(f);a.isCIDFont&&(e.setEntryLocation("FDSelect",[c.length],c),f=this.compileFDSelect(a.fdSelect.raw),c.add(f),f=this.compileTopDicts(a.fdArray [...]
+e.setEntryLocation("FDArray",[c.length],c),c.add(f.output),this.compilePrivateDicts(a.fdArray,f.trackers,c));this.compilePrivateDicts([a.topDict],[e],c);return c.data},encodeNumber:function(a){return parseFloat(a)==parseInt(a)&&!isNaN(a)?this.encodeInteger(a):this.encodeFloat(a)},encodeFloat:function(a){a=a.toString();"0."===a.substr(0,2)?a=a.substr(1):"-0."===a.substr(0,3)&&(a="-"+a.substr(2));for(var c=[],e=0,f=a.length;e<f;++e){var d=a.charAt(e),i=a.charAt(e+1);"e"===d&&"-"===i?(d=12, [...]
+d?10:"E"===d?11:"-"===d?14:d;c.push(d)}c.push(15);c.length%2&&c.push(15);a=[30];e=0;for(f=c.length;e<f;e+=2)a.push(c[e]<<4|c[e+1]);return a},encodeInteger:function(a){-107<=a&&107>=a?a=[a+139]:108<=a&&1131>=a?(a=[a-108],a=[(a>>8)+247,a&255]):-1131<=a&&-108>=a?(a=-a-108,a=[(a>>8)+251,a&255]):a=-32768<=a&&32767>=a?[28,a>>8&255,a&255]:[29,a>>24&255,a>>16&255,a>>8&255,a&255];return a},compileHeader:function(a){return[a.major,a.minor,a.hdrSize,a.offSize]},compileNameIndex:function(a){for(var [...]
+e=0,f=a.length;e<f;++e)c.add(d(a[e]));return this.compileIndex(c)},compileTopDicts:function(a,c){for(var e=[],f=new lb,d=0,i=a.length;d<i;++d){var k=a[d],j=new wc,k=this.compileDict(k,j);e.push(j);f.add(k);j.offset(c)}f=this.compileIndex(f,e);return{trackers:e,output:f}},compilePrivateDicts:function(a,c,e){for(var f=0,d=a.length;f<d;++f){var i=a[f];if(i.privateDict&&i.hasName("Private")){var k=i.privateDict,i=new wc,j=this.compileDict(k,i);i.offset(e.length);c[f].setEntryLocation("Privat [...]
+e.length],e);e.add(j);k.subrsIndex&&k.hasName("Subrs")&&(k=this.compileIndex(k.subrsIndex),i.setEntryLocation("Subrs",[j.length],e),e.add(k))}}},compileDict:function(a,c){for(var e=[],f=a.order,d=0;d<f.length;++d){var i=f[d];if(i in a.values){var k=a.values[i],j=a.types[i];R(j)||(j=[j]);R(k)||(k=[k]);if(0!==k.length){for(var h=0,m=j.length;h<m;++h){var l=j[h],n=k[h];switch(l){case "num":case "sid":e=e.concat(this.encodeNumber(n));break;case "offset":l=a.keyToNameMap[i];c.isTracking(l)||c [...]
+e.length);e=e.concat([29,0,0,0,0]);break;case "array":case "delta":e=e.concat(this.encodeNumber(n));l=1;for(n=k.length;l<n;++l)e=e.concat(this.encodeNumber(k[l]));break;default:r("Unknown data type of "+l)}}e=e.concat(a.opcodes[i])}}}return e},compileStringIndex:function(a){for(var c=new lb,e=0,f=a.length;e<f;++e)c.add(d(a[e]));return this.compileIndex(c)},compileGlobalSubrIndex:function(){this.out.writeByteArray(this.compileIndex(this.cff.globalSubrIndex))},compileCharStrings:function(a [...]
+compileCharset:function(a){return this.compileTypedArray(a.raw)},compileEncoding:function(a){return this.compileTypedArray(a.raw)},compileFDSelect:function(a){return this.compileTypedArray(a)},compileTypedArray:function(a){for(var c=[],e=0,f=a.length;e<f;++e)c[e]=a[e];return c},compileIndex:function(a,c){var c=c||[],e=a.objects,f=e.length;if(0==f)return[0,0,0];for(var d=[f>>8&255,f&255],i=1,k=0;k<f;++k)i+=e[k].length;i=256>i?1:65536>i?2:16777216>i?3:4;d.push(i);for(var j=1,k=0;k<f+1;k++) [...]
+255):2===i?d.push(j>>8&255,j&255):3===i?d.push(j>>16&255,j>>8&255,j&255):d.push(j>>>24&255,j>>16&255,j>>8&255,j&255),e[k]&&(j+=e[k].length);for(k=0;k<f;k++){c[k]&&c[k].offset(d.length);i=0;for(j=e[k].length;i<j;i++)d.push(e[k][i])}return d}};return a}();"use strict";var na={A:65,AE:198,AEacute:508,AEmacron:482,AEsmall:63462,Aacute:193,Aacutesmall:63457,Abreve:258,Abreveacute:7854,Abrevecyrillic:1232,Abrevedotbelow:7862,Abrevegrave:7856,Abrevehookabove:7858,Abrevetilde:7860,Acaron:461,Aci [...]
+Acircumflex:194,Acircumflexacute:7844,Acircumflexdotbelow:7852,Acircumflexgrave:7846,Acircumflexhookabove:7848,Acircumflexsmall:63458,Acircumflextilde:7850,Acute:63177,Acutesmall:63412,Acyrillic:1040,Adblgrave:512,Adieresis:196,Adieresiscyrillic:1234,Adieresismacron:478,Adieresissmall:63460,Adotbelow:7840,Adotmacron:480,Agrave:192,Agravesmall:63456,Ahookabove:7842,Aiecyrillic:1236,Ainvertedbreve:514,Alpha:913,Alphatonos:902,Amacron:256,Amonospace:65313,Aogonek:260,Aring:197,Aringacute:50 [...]
+Aringsmall:63461,Asmall:63329,Atilde:195,Atildesmall:63459,Aybarmenian:1329,B:66,Bcircle:9399,Bdotaccent:7682,Bdotbelow:7684,Becyrillic:1041,Benarmenian:1330,Beta:914,Bhook:385,Blinebelow:7686,Bmonospace:65314,Brevesmall:63220,Bsmall:63330,Btopbar:386,C:67,Caarmenian:1342,Cacute:262,Caron:63178,Caronsmall:63221,Ccaron:268,Ccedilla:199,Ccedillaacute:7688,Ccedillasmall:63463,Ccircle:9400,Ccircumflex:264,Cdot:266,Cdotaccent:266,Cedillasmall:63416,Chaarmenian:1353,Cheabkhasiancyrillic:1212,C [...]
+Chedescenderabkhasiancyrillic:1214,Chedescendercyrillic:1206,Chedieresiscyrillic:1268,Cheharmenian:1347,Chekhakassiancyrillic:1227,Cheverticalstrokecyrillic:1208,Chi:935,Chook:391,Circumflexsmall:63222,Cmonospace:65315,Coarmenian:1361,Csmall:63331,D:68,DZ:497,DZcaron:452,Daarmenian:1332,Dafrican:393,Dcaron:270,Dcedilla:7696,Dcircle:9401,Dcircumflexbelow:7698,Dcroat:272,Ddotaccent:7690,Ddotbelow:7692,Decyrillic:1044,Deicoptic:1006,Delta:8710,Deltagreek:916,Dhook:394,Dieresis:63179,Dieresi [...]
+DieresisGrave:63181,Dieresissmall:63400,Digammagreek:988,Djecyrillic:1026,Dlinebelow:7694,Dmonospace:65316,Dotaccentsmall:63223,Dslash:272,Dsmall:63332,Dtopbar:395,Dz:498,Dzcaron:453,Dzeabkhasiancyrillic:1248,Dzecyrillic:1029,Dzhecyrillic:1039,E:69,Eacute:201,Eacutesmall:63465,Ebreve:276,Ecaron:282,Ecedillabreve:7708,Echarmenian:1333,Ecircle:9402,Ecircumflex:202,Ecircumflexacute:7870,Ecircumflexbelow:7704,Ecircumflexdotbelow:7878,Ecircumflexgrave:7872,Ecircumflexhookabove:7874,Ecircumfle [...]
+Ecircumflextilde:7876,Ecyrillic:1028,Edblgrave:516,Edieresis:203,Edieresissmall:63467,Edot:278,Edotaccent:278,Edotbelow:7864,Efcyrillic:1060,Egrave:200,Egravesmall:63464,Eharmenian:1335,Ehookabove:7866,Eightroman:8551,Einvertedbreve:518,Eiotifiedcyrillic:1124,Elcyrillic:1051,Elevenroman:8554,Emacron:274,Emacronacute:7702,Emacrongrave:7700,Emcyrillic:1052,Emonospace:65317,Encyrillic:1053,Endescendercyrillic:1186,Eng:330,Enghecyrillic:1188,Enhookcyrillic:1223,Eogonek:280,Eopen:400,Epsilon: [...]
+Ercyrillic:1056,Ereversed:398,Ereversedcyrillic:1069,Escyrillic:1057,Esdescendercyrillic:1194,Esh:425,Esmall:63333,Eta:919,Etarmenian:1336,Etatonos:905,Eth:208,Ethsmall:63472,Etilde:7868,Etildebelow:7706,Euro:8364,Ezh:439,Ezhcaron:494,Ezhreversed:440,F:70,Fcircle:9403,Fdotaccent:7710,Feharmenian:1366,Feicoptic:996,Fhook:401,Fitacyrillic:1138,Fiveroman:8548,Fmonospace:65318,Fourroman:8547,Fsmall:63334,G:71,GBsquare:13191,Gacute:500,Gamma:915,Gammaafrican:404,Gangiacoptic:1002,Gbreve:286,G [...]
+Gcedilla:290,Gcircle:9404,Gcircumflex:284,Gcommaaccent:290,Gdot:288,Gdotaccent:288,Gecyrillic:1043,Ghadarmenian:1346,Ghemiddlehookcyrillic:1172,Ghestrokecyrillic:1170,Gheupturncyrillic:1168,Ghook:403,Gimarmenian:1331,Gjecyrillic:1027,Gmacron:7712,Gmonospace:65319,Grave:63182,Gravesmall:63328,Gsmall:63335,Gsmallhook:667,Gstroke:484,H:72,H18533:9679,H18543:9642,H18551:9643,H22073:9633,HPsquare:13259,Haabkhasiancyrillic:1192,Hadescendercyrillic:1202,Hardsigncyrillic:1066,Hbar:294,Hbrevebelo [...]
+Hcircle:9405,Hcircumflex:292,Hdieresis:7718,Hdotaccent:7714,Hdotbelow:7716,Hmonospace:65320,Hoarmenian:1344,Horicoptic:1E3,Hsmall:63336,Hungarumlaut:63183,Hungarumlautsmall:63224,Hzsquare:13200,I:73,IAcyrillic:1071,IJ:306,IUcyrillic:1070,Iacute:205,Iacutesmall:63469,Ibreve:300,Icaron:463,Icircle:9406,Icircumflex:206,Icircumflexsmall:63470,Icyrillic:1030,Idblgrave:520,Idieresis:207,Idieresisacute:7726,Idieresiscyrillic:1252,Idieresissmall:63471,Idot:304,Idotaccent:304,Idotbelow:7882,Iebre [...]
+Iecyrillic:1045,Ifraktur:8465,Igrave:204,Igravesmall:63468,Ihookabove:7880,Iicyrillic:1048,Iinvertedbreve:522,Iishortcyrillic:1049,Imacron:298,Imacroncyrillic:1250,Imonospace:65321,Iniarmenian:1339,Iocyrillic:1025,Iogonek:302,Iota:921,Iotaafrican:406,Iotadieresis:938,Iotatonos:906,Ismall:63337,Istroke:407,Itilde:296,Itildebelow:7724,Izhitsacyrillic:1140,Izhitsadblgravecyrillic:1142,J:74,Jaarmenian:1345,Jcircle:9407,Jcircumflex:308,Jecyrillic:1032,Jheharmenian:1355,Jmonospace:65322,Jsmall [...]
+KBsquare:13189,KKsquare:13261,Kabashkircyrillic:1184,Kacute:7728,Kacyrillic:1050,Kadescendercyrillic:1178,Kahookcyrillic:1219,Kappa:922,Kastrokecyrillic:1182,Kaverticalstrokecyrillic:1180,Kcaron:488,Kcedilla:310,Kcircle:9408,Kcommaaccent:310,Kdotbelow:7730,Keharmenian:1364,Kenarmenian:1343,Khacyrillic:1061,Kheicoptic:998,Khook:408,Kjecyrillic:1036,Klinebelow:7732,Kmonospace:65323,Koppacyrillic:1152,Koppagreek:990,Ksicyrillic:1134,Ksmall:63339,L:76,LJ:455,LL:63167,Lacute:313,Lambda:923,Lc [...]
+Lcedilla:315,Lcircle:9409,Lcircumflexbelow:7740,Lcommaaccent:315,Ldot:319,Ldotaccent:319,Ldotbelow:7734,Ldotbelowmacron:7736,Liwnarmenian:1340,Lj:456,Ljecyrillic:1033,Llinebelow:7738,Lmonospace:65324,Lslash:321,Lslashsmall:63225,Lsmall:63340,M:77,MBsquare:13190,Macron:63184,Macronsmall:63407,Macute:7742,Mcircle:9410,Mdotaccent:7744,Mdotbelow:7746,Menarmenian:1348,Mmonospace:65325,Msmall:63341,Mturned:412,Mu:924,N:78,NJ:458,Nacute:323,Ncaron:327,Ncedilla:325,Ncircle:9411,Ncircumflexbelow: [...]
+Ndotaccent:7748,Ndotbelow:7750,Nhookleft:413,Nineroman:8552,Nj:459,Njecyrillic:1034,Nlinebelow:7752,Nmonospace:65326,Nowarmenian:1350,Nsmall:63342,Ntilde:209,Ntildesmall:63473,Nu:925,O:79,OE:338,OEsmall:63226,Oacute:211,Oacutesmall:63475,Obarredcyrillic:1256,Obarreddieresiscyrillic:1258,Obreve:334,Ocaron:465,Ocenteredtilde:415,Ocircle:9412,Ocircumflex:212,Ocircumflexacute:7888,Ocircumflexdotbelow:7896,Ocircumflexgrave:7890,Ocircumflexhookabove:7892,Ocircumflexsmall:63476,Ocircumflextilde [...]
+Odblacute:336,Odblgrave:524,Odieresis:214,Odieresiscyrillic:1254,Odieresissmall:63478,Odotbelow:7884,Ogoneksmall:63227,Ograve:210,Ogravesmall:63474,Oharmenian:1365,Ohm:8486,Ohookabove:7886,Ohorn:416,Ohornacute:7898,Ohorndotbelow:7906,Ohorngrave:7900,Ohornhookabove:7902,Ohorntilde:7904,Ohungarumlaut:336,Oi:418,Oinvertedbreve:526,Omacron:332,Omacronacute:7762,Omacrongrave:7760,Omega:8486,Omegacyrillic:1120,Omegagreek:937,Omegaroundcyrillic:1146,Omegatitlocyrillic:1148,Omegatonos:911,Omicro [...]
+Omonospace:65327,Oneroman:8544,Oogonek:490,Oogonekmacron:492,Oopen:390,Oslash:216,Oslashacute:510,Oslashsmall:63480,Osmall:63343,Ostrokeacute:510,Otcyrillic:1150,Otilde:213,Otildeacute:7756,Otildedieresis:7758,Otildesmall:63477,P:80,Pacute:7764,Pcircle:9413,Pdotaccent:7766,Pecyrillic:1055,Peharmenian:1354,Pemiddlehookcyrillic:1190,Phi:934,Phook:420,Pi:928,Piwrarmenian:1363,Pmonospace:65328,Psi:936,Psicyrillic:1136,Psmall:63344,Q:81,Qcircle:9414,Qmonospace:65329,Qsmall:63345,R:82,Raarmeni [...]
+Rcaron:344,Rcedilla:342,Rcircle:9415,Rcommaaccent:342,Rdblgrave:528,Rdotaccent:7768,Rdotbelow:7770,Rdotbelowmacron:7772,Reharmenian:1360,Rfraktur:8476,Rho:929,Ringsmall:63228,Rinvertedbreve:530,Rlinebelow:7774,Rmonospace:65330,Rsmall:63346,Rsmallinverted:641,Rsmallinvertedsuperior:694,S:83,SF010000:9484,SF020000:9492,SF030000:9488,SF040000:9496,SF050000:9532,SF060000:9516,SF070000:9524,SF080000:9500,SF090000:9508,SF100000:9472,SF110000:9474,SF190000:9569,SF200000:9570,SF210000:9558,SF220 [...]
+SF230000:9571,SF240000:9553,SF250000:9559,SF260000:9565,SF270000:9564,SF280000:9563,SF360000:9566,SF370000:9567,SF380000:9562,SF390000:9556,SF400000:9577,SF410000:9574,SF420000:9568,SF430000:9552,SF440000:9580,SF450000:9575,SF460000:9576,SF470000:9572,SF480000:9573,SF490000:9561,SF500000:9560,SF510000:9554,SF520000:9555,SF530000:9579,SF540000:9578,Sacute:346,Sacutedotaccent:7780,Sampigreek:992,Scaron:352,Scarondotaccent:7782,Scaronsmall:63229,Scedilla:350,Schwa:399,Schwacyrillic:1240,Sch [...]
+Scircle:9416,Scircumflex:348,Scommaaccent:536,Sdotaccent:7776,Sdotbelow:7778,Sdotbelowdotaccent:7784,Seharmenian:1357,Sevenroman:8550,Shaarmenian:1351,Shacyrillic:1064,Shchacyrillic:1065,Sheicoptic:994,Shhacyrillic:1210,Shimacoptic:1004,Sigma:931,Sixroman:8549,Smonospace:65331,Softsigncyrillic:1068,Ssmall:63347,Stigmagreek:986,T:84,Tau:932,Tbar:358,Tcaron:356,Tcedilla:354,Tcircle:9417,Tcircumflexbelow:7792,Tcommaaccent:354,Tdotaccent:7786,Tdotbelow:7788,Tecyrillic:1058,Tedescendercyrilli [...]
+Tetsecyrillic:1204,Theta:920,Thook:428,Thorn:222,Thornsmall:63486,Threeroman:8546,Tildesmall:63230,Tiwnarmenian:1359,Tlinebelow:7790,Tmonospace:65332,Toarmenian:1337,Tonefive:444,Tonesix:388,Tonetwo:423,Tretroflexhook:430,Tsecyrillic:1062,Tshecyrillic:1035,Tsmall:63348,Twelveroman:8555,Tworoman:8545,U:85,Uacute:218,Uacutesmall:63482,Ubreve:364,Ucaron:467,Ucircle:9418,Ucircumflex:219,Ucircumflexbelow:7798,Ucircumflexsmall:63483,Ucyrillic:1059,Udblacute:368,Udblgrave:532,Udieresis:220,Udie [...]
+Udieresisbelow:7794,Udieresiscaron:473,Udieresiscyrillic:1264,Udieresisgrave:475,Udieresismacron:469,Udieresissmall:63484,Udotbelow:7908,Ugrave:217,Ugravesmall:63481,Uhookabove:7910,Uhorn:431,Uhornacute:7912,Uhorndotbelow:7920,Uhorngrave:7914,Uhornhookabove:7916,Uhorntilde:7918,Uhungarumlaut:368,Uhungarumlautcyrillic:1266,Uinvertedbreve:534,Ukcyrillic:1144,Umacron:362,Umacroncyrillic:1262,Umacrondieresis:7802,Umonospace:65333,Uogonek:370,Upsilon:933,Upsilon1:978,Upsilonacutehooksymbolgre [...]
+Upsilondieresis:939,Upsilondieresishooksymbolgreek:980,Upsilonhooksymbol:978,Upsilontonos:910,Uring:366,Ushortcyrillic:1038,Usmall:63349,Ustraightcyrillic:1198,Ustraightstrokecyrillic:1200,Utilde:360,Utildeacute:7800,Utildebelow:7796,V:86,Vcircle:9419,Vdotbelow:7806,Vecyrillic:1042,Vewarmenian:1358,Vhook:434,Vmonospace:65334,Voarmenian:1352,Vsmall:63350,Vtilde:7804,W:87,Wacute:7810,Wcircle:9420,Wcircumflex:372,Wdieresis:7812,Wdotaccent:7814,Wdotbelow:7816,Wgrave:7808,Wmonospace:65335,Wsm [...]
+X:88,Xcircle:9421,Xdieresis:7820,Xdotaccent:7818,Xeharmenian:1341,Xi:926,Xmonospace:65336,Xsmall:63352,Y:89,Yacute:221,Yacutesmall:63485,Yatcyrillic:1122,Ycircle:9422,Ycircumflex:374,Ydieresis:376,Ydieresissmall:63487,Ydotaccent:7822,Ydotbelow:7924,Yericyrillic:1067,Yerudieresiscyrillic:1272,Ygrave:7922,Yhook:435,Yhookabove:7926,Yiarmenian:1349,Yicyrillic:1031,Yiwnarmenian:1362,Ymonospace:65337,Ysmall:63353,Ytilde:7928,Yusbigcyrillic:1130,Yusbigiotifiedcyrillic:1132,Yuslittlecyrillic:112 [...]
+Z:90,Zaarmenian:1334,Zacute:377,Zcaron:381,Zcaronsmall:63231,Zcircle:9423,Zcircumflex:7824,Zdot:379,Zdotaccent:379,Zdotbelow:7826,Zecyrillic:1047,Zedescendercyrillic:1176,Zedieresiscyrillic:1246,Zeta:918,Zhearmenian:1338,Zhebrevecyrillic:1217,Zhecyrillic:1046,Zhedescendercyrillic:1174,Zhedieresiscyrillic:1244,Zlinebelow:7828,Zmonospace:65338,Zsmall:63354,Zstroke:437,a:97,aabengali:2438,aacute:225,aadeva:2310,aagujarati:2694,aagurmukhi:2566,aamatragurmukhi:2622,aarusquare:13059,aavowelsig [...]
+aavowelsigndeva:2366,aavowelsigngujarati:2750,abbreviationmarkarmenian:1375,abbreviationsigndeva:2416,abengali:2437,abopomofo:12570,abreve:259,abreveacute:7855,abrevecyrillic:1233,abrevedotbelow:7863,abrevegrave:7857,abrevehookabove:7859,abrevetilde:7861,acaron:462,acircle:9424,acircumflex:226,acircumflexacute:7845,acircumflexdotbelow:7853,acircumflexgrave:7847,acircumflexhookabove:7849,acircumflextilde:7851,acute:180,acutebelowcmb:791,acutecmb:769,acutecomb:769,acutedeva:2388,acutelowmo [...]
+acyrillic:1072,adblgrave:513,addakgurmukhi:2673,adeva:2309,adieresis:228,adieresiscyrillic:1235,adieresismacron:479,adotbelow:7841,adotmacron:481,ae:230,aeacute:509,aekorean:12624,aemacron:483,afii00208:8213,afii08941:8356,afii10017:1040,afii10018:1041,afii10019:1042,afii10020:1043,afii10021:1044,afii10022:1045,afii10023:1025,afii10024:1046,afii10025:1047,afii10026:1048,afii10027:1049,afii10028:1050,afii10029:1051,afii10030:1052,afii10031:1053,afii10032:1054,afii10033:1055,afii10034:1056 [...]
+afii10036:1058,afii10037:1059,afii10038:1060,afii10039:1061,afii10040:1062,afii10041:1063,afii10042:1064,afii10043:1065,afii10044:1066,afii10045:1067,afii10046:1068,afii10047:1069,afii10048:1070,afii10049:1071,afii10050:1168,afii10051:1026,afii10052:1027,afii10053:1028,afii10054:1029,afii10055:1030,afii10056:1031,afii10057:1032,afii10058:1033,afii10059:1034,afii10060:1035,afii10061:1036,afii10062:1038,afii10063:63172,afii10064:63173,afii10065:1072,afii10066:1073,afii10067:1074,afii10068: [...]
+afii10070:1077,afii10071:1105,afii10072:1078,afii10073:1079,afii10074:1080,afii10075:1081,afii10076:1082,afii10077:1083,afii10078:1084,afii10079:1085,afii10080:1086,afii10081:1087,afii10082:1088,afii10083:1089,afii10084:1090,afii10085:1091,afii10086:1092,afii10087:1093,afii10088:1094,afii10089:1095,afii10090:1096,afii10091:1097,afii10092:1098,afii10093:1099,afii10094:1100,afii10095:1101,afii10096:1102,afii10097:1103,afii10098:1169,afii10099:1106,afii10100:1107,afii10101:1108,afii10102:11 [...]
+afii10104:1111,afii10105:1112,afii10106:1113,afii10107:1114,afii10108:1115,afii10109:1116,afii10110:1118,afii10145:1039,afii10146:1122,afii10147:1138,afii10148:1140,afii10192:63174,afii10193:1119,afii10194:1123,afii10195:1139,afii10196:1141,afii10831:63175,afii10832:63176,afii10846:1241,afii299:8206,afii300:8207,afii301:8205,afii57381:1642,afii57388:1548,afii57392:1632,afii57393:1633,afii57394:1634,afii57395:1635,afii57396:1636,afii57397:1637,afii57398:1638,afii57399:1639,afii57400:1640, [...]
+afii57403:1563,afii57407:1567,afii57409:1569,afii57410:1570,afii57411:1571,afii57412:1572,afii57413:1573,afii57414:1574,afii57415:1575,afii57416:1576,afii57417:1577,afii57418:1578,afii57419:1579,afii57420:1580,afii57421:1581,afii57422:1582,afii57423:1583,afii57424:1584,afii57425:1585,afii57426:1586,afii57427:1587,afii57428:1588,afii57429:1589,afii57430:1590,afii57431:1591,afii57432:1592,afii57433:1593,afii57434:1594,afii57440:1600,afii57441:1601,afii57442:1602,afii57443:1603,afii57444:16 [...]
+afii57446:1606,afii57448:1608,afii57449:1609,afii57450:1610,afii57451:1611,afii57452:1612,afii57453:1613,afii57454:1614,afii57455:1615,afii57456:1616,afii57457:1617,afii57458:1618,afii57470:1607,afii57505:1700,afii57506:1662,afii57507:1670,afii57508:1688,afii57509:1711,afii57511:1657,afii57512:1672,afii57513:1681,afii57514:1722,afii57519:1746,afii57534:1749,afii57636:8362,afii57645:1470,afii57658:1475,afii57664:1488,afii57665:1489,afii57666:1490,afii57667:1491,afii57668:1492,afii57669:14 [...]
+afii57671:1495,afii57672:1496,afii57673:1497,afii57674:1498,afii57675:1499,afii57676:1500,afii57677:1501,afii57678:1502,afii57679:1503,afii57680:1504,afii57681:1505,afii57682:1506,afii57683:1507,afii57684:1508,afii57685:1509,afii57686:1510,afii57687:1511,afii57688:1512,afii57689:1513,afii57690:1514,afii57694:64298,afii57695:64299,afii57700:64331,afii57705:64287,afii57716:1520,afii57717:1521,afii57718:1522,afii57723:64309,afii57793:1460,afii57794:1461,afii57795:1462,afii57796:1467,afii577 [...]
+afii57799:1456,afii57800:1458,afii57801:1457,afii57802:1459,afii57803:1474,afii57804:1473,afii57806:1465,afii57807:1468,afii57839:1469,afii57841:1471,afii57842:1472,afii57929:700,afii61248:8453,afii61289:8467,afii61352:8470,afii61573:8236,afii61574:8237,afii61575:8238,afii61664:8204,afii63167:1645,afii64937:701,agrave:224,agujarati:2693,agurmukhi:2565,ahiragana:12354,ahookabove:7843,aibengali:2448,aibopomofo:12574,aideva:2320,aiecyrillic:1237,aigujarati:2704,aigurmukhi:2576,aimatragurmuk [...]
+ainfinalarabic:65226,aininitialarabic:65227,ainmedialarabic:65228,ainvertedbreve:515,aivowelsignbengali:2504,aivowelsigndeva:2376,aivowelsigngujarati:2760,akatakana:12450,akatakanahalfwidth:65393,akorean:12623,alef:1488,alefarabic:1575,alefdageshhebrew:64304,aleffinalarabic:65166,alefhamzaabovearabic:1571,alefhamzaabovefinalarabic:65156,alefhamzabelowarabic:1573,alefhamzabelowfinalarabic:65160,alefhebrew:1488,aleflamedhebrew:64335,alefmaddaabovearabic:1570,alefmaddaabovefinalarabic:65154 [...]
+alefmaksurafinalarabic:65264,alefmaksurainitialarabic:65267,alefmaksuramedialarabic:65268,alefpatahhebrew:64302,alefqamatshebrew:64303,aleph:8501,allequal:8780,alpha:945,alphatonos:940,amacron:257,amonospace:65345,ampersand:38,ampersandmonospace:65286,ampersandsmall:63270,amsquare:13250,anbopomofo:12578,angbopomofo:12580,angbracketleft:12296,angbracketright:12297,angkhankhuthai:3674,angle:8736,anglebracketleft:12296,anglebracketleftvertical:65087,anglebracketright:12297,anglebracketright [...]
+angleleft:9001,angleright:9002,angstrom:8491,anoteleia:903,anudattadeva:2386,anusvarabengali:2434,anusvaradeva:2306,anusvaragujarati:2690,aogonek:261,apaatosquare:13056,aparen:9372,apostrophearmenian:1370,apostrophemod:700,apple:63743,approaches:8784,approxequal:8776,approxequalorimage:8786,approximatelyequal:8773,araeaekorean:12686,araeakorean:12685,arc:8978,arighthalfring:7834,aring:229,aringacute:507,aringbelow:7681,arrowboth:8596,arrowdashdown:8675,arrowdashleft:8672,arrowdashright:8 [...]
+arrowdblboth:8660,arrowdbldown:8659,arrowdblleft:8656,arrowdblright:8658,arrowdblup:8657,arrowdown:8595,arrowdownleft:8601,arrowdownright:8600,arrowdownwhite:8681,arrowheaddownmod:709,arrowheadleftmod:706,arrowheadrightmod:707,arrowheadupmod:708,arrowhorizex:63719,arrowleft:8592,arrowleftdbl:8656,arrowleftdblstroke:8653,arrowleftoverright:8646,arrowleftwhite:8678,arrowright:8594,arrowrightdblstroke:8655,arrowrightheavy:10142,arrowrightoverleft:8644,arrowrightwhite:8680,arrowtableft:8676, [...]
+arrowup:8593,arrowupdn:8597,arrowupdnbse:8616,arrowupdownbase:8616,arrowupleft:8598,arrowupleftofdown:8645,arrowupright:8599,arrowupwhite:8679,arrowvertex:63718,asciicircum:94,asciicircummonospace:65342,asciitilde:126,asciitildemonospace:65374,ascript:593,ascriptturned:594,asmallhiragana:12353,asmallkatakana:12449,asmallkatakanahalfwidth:65383,asterisk:42,asteriskaltonearabic:1645,asteriskarabic:1645,asteriskmath:8727,asteriskmonospace:65290,asterisksmall:65121,asterism:8258,asuperior:63 [...]
+at:64,atilde:227,atmonospace:65312,atsmall:65131,aturned:592,aubengali:2452,aubopomofo:12576,audeva:2324,augujarati:2708,augurmukhi:2580,aulengthmarkbengali:2519,aumatragurmukhi:2636,auvowelsignbengali:2508,auvowelsigndeva:2380,auvowelsigngujarati:2764,avagrahadeva:2365,aybarmenian:1377,ayin:1506,ayinaltonehebrew:64288,ayinhebrew:1506,b:98,babengali:2476,backslash:92,backslashmonospace:65340,badeva:2348,bagujarati:2732,bagurmukhi:2604,bahiragana:12400,bahtthai:3647,bakatakana:12496,bar:1 [...]
+bbopomofo:12549,bcircle:9425,bdotaccent:7683,bdotbelow:7685,beamedsixteenthnotes:9836,because:8757,becyrillic:1073,beharabic:1576,behfinalarabic:65168,behinitialarabic:65169,behiragana:12409,behmedialarabic:65170,behmeeminitialarabic:64671,behmeemisolatedarabic:64520,behnoonfinalarabic:64621,bekatakana:12505,benarmenian:1378,bet:1489,beta:946,betasymbolgreek:976,betdagesh:64305,betdageshhebrew:64305,bethebrew:1489,betrafehebrew:64332,bhabengali:2477,bhadeva:2349,bhagujarati:2733,bhagurmu [...]
+bhook:595,bihiragana:12403,bikatakana:12499,bilabialclick:664,bindigurmukhi:2562,birusquare:13105,blackcircle:9679,blackdiamond:9670,blackdownpointingtriangle:9660,blackleftpointingpointer:9668,blackleftpointingtriangle:9664,blacklenticularbracketleft:12304,blacklenticularbracketleftvertical:65083,blacklenticularbracketright:12305,blacklenticularbracketrightvertical:65084,blacklowerlefttriangle:9699,blacklowerrighttriangle:9698,blackrectangle:9644,blackrightpointingpointer:9658,blackrigh [...]
+blacksmallsquare:9642,blacksmilingface:9787,blacksquare:9632,blackstar:9733,blackupperlefttriangle:9700,blackupperrighttriangle:9701,blackuppointingsmalltriangle:9652,blackuppointingtriangle:9650,blank:9251,blinebelow:7687,block:9608,bmonospace:65346,bobaimaithai:3610,bohiragana:12412,bokatakana:12508,bparen:9373,bqsquare:13251,braceex:63732,braceleft:123,braceleftbt:63731,braceleftmid:63730,braceleftmonospace:65371,braceleftsmall:65115,bracelefttp:63729,braceleftvertical:65079,bracerigh [...]
+bracerightmid:63741,bracerightmonospace:65373,bracerightsmall:65116,bracerighttp:63740,bracerightvertical:65080,bracketleft:91,bracketleftbt:63728,bracketleftex:63727,bracketleftmonospace:65339,bracketlefttp:63726,bracketright:93,bracketrightbt:63739,bracketrightex:63738,bracketrightmonospace:65341,bracketrighttp:63737,breve:728,brevebelowcmb:814,brevecmb:774,breveinvertedbelowcmb:815,breveinvertedcmb:785,breveinverteddoublecmb:865,bridgebelowcmb:810,bridgeinvertedbelowcmb:826,brokenbar: [...]
+bsuperior:63210,btopbar:387,buhiragana:12406,bukatakana:12502,bullet:8226,bulletinverse:9688,bulletoperator:8729,bullseye:9678,c:99,caarmenian:1390,cabengali:2458,cacute:263,cadeva:2330,cagujarati:2714,cagurmukhi:2586,calsquare:13192,candrabindubengali:2433,candrabinducmb:784,candrabindudeva:2305,candrabindugujarati:2689,capslock:8682,careof:8453,caron:711,caronbelowcmb:812,caroncmb:780,carriagereturn:8629,cbopomofo:12568,ccaron:269,ccedilla:231,ccedillaacute:7689,ccircle:9426,ccircumfle [...]
+cdot:267,cdotaccent:267,cdsquare:13253,cedilla:184,cedillacmb:807,cent:162,centigrade:8451,centinferior:63199,centmonospace:65504,centoldstyle:63394,centsuperior:63200,chaarmenian:1401,chabengali:2459,chadeva:2331,chagujarati:2715,chagurmukhi:2587,chbopomofo:12564,cheabkhasiancyrillic:1213,checkmark:10003,checyrillic:1095,chedescenderabkhasiancyrillic:1215,chedescendercyrillic:1207,chedieresiscyrillic:1269,cheharmenian:1395,chekhakassiancyrillic:1228,cheverticalstrokecyrillic:1209,chi:96 [...]
+chieuchaparenkorean:12823,chieuchcirclekorean:12905,chieuchkorean:12618,chieuchparenkorean:12809,chochangthai:3594,chochanthai:3592,chochingthai:3593,chochoethai:3596,chook:392,cieucacirclekorean:12918,cieucaparenkorean:12822,cieuccirclekorean:12904,cieuckorean:12616,cieucparenkorean:12808,cieucuparenkorean:12828,circle:9675,circlecopyrt:169,circlemultiply:8855,circleot:8857,circleplus:8853,circlepostalmark:12342,circlewithlefthalfblack:9680,circlewithrighthalfblack:9681,circumflex:710,c [...]
+circumflexcmb:770,clear:8999,clickalveolar:450,clickdental:448,clicklateral:449,clickretroflex:451,club:9827,clubsuitblack:9827,clubsuitwhite:9831,cmcubedsquare:13220,cmonospace:65347,cmsquaredsquare:13216,coarmenian:1409,colon:58,colonmonetary:8353,colonmonospace:65306,colonsign:8353,colonsmall:65109,colontriangularhalfmod:721,colontriangularmod:720,comma:44,commaabovecmb:787,commaaboverightcmb:789,commaaccent:63171,commaarabic:1548,commaarmenian:1373,commainferior:63201,commamonospace: [...]
+commareversedmod:701,commasmall:65104,commasuperior:63202,commaturnedabovecmb:786,commaturnedmod:699,compass:9788,congruent:8773,contourintegral:8750,control:8963,controlACK:6,controlBEL:7,controlBS:8,controlCAN:24,controlCR:13,controlDC1:17,controlDC2:18,controlDC3:19,controlDC4:20,controlDEL:127,controlDLE:16,controlEM:25,controlENQ:5,controlEOT:4,controlESC:27,controlETB:23,controlETX:3,controlFF:12,controlFS:28,controlGS:29,controlHT:9,controlLF:10,controlNAK:21,controlRS:30,controlS [...]
+controlSOT:2,controlSTX:1,controlSUB:26,controlSYN:22,controlUS:31,controlVT:11,copyright:169,copyrightsans:63721,copyrightserif:63193,cornerbracketleft:12300,cornerbracketlefthalfwidth:65378,cornerbracketleftvertical:65089,cornerbracketright:12301,cornerbracketrighthalfwidth:65379,cornerbracketrightvertical:65090,corporationsquare:13183,cosquare:13255,coverkgsquare:13254,cparen:9374,cruzeiro:8354,cstretched:663,curlyand:8911,curlyor:8910,currency:164,cyrBreve:63185,cyrFlex:63186,cyrbrev [...]
+d:100,daarmenian:1380,dabengali:2470,dadarabic:1590,dadeva:2342,dadfinalarabic:65214,dadinitialarabic:65215,dadmedialarabic:65216,dagesh:1468,dageshhebrew:1468,dagger:8224,daggerdbl:8225,dagujarati:2726,dagurmukhi:2598,dahiragana:12384,dakatakana:12480,dalarabic:1583,dalet:1491,daletdagesh:64307,daletdageshhebrew:64307,dalethebrew:1491,dalfinalarabic:65194,dammaarabic:1615,dammalowarabic:1615,dammatanaltonearabic:1612,dammatanarabic:1612,danda:2404,dargahebrew:1447,dargalefthebrew:1447,d [...]
+dblGrave:63187,dblanglebracketleft:12298,dblanglebracketleftvertical:65085,dblanglebracketright:12299,dblanglebracketrightvertical:65086,dblarchinvertedbelowcmb:811,dblarrowleft:8660,dblarrowright:8658,dbldanda:2405,dblgrave:63190,dblgravecmb:783,dblintegral:8748,dbllowline:8215,dbllowlinecmb:819,dbloverlinecmb:831,dblprimemod:698,dblverticalbar:8214,dblverticallineabovecmb:782,dbopomofo:12553,dbsquare:13256,dcaron:271,dcedilla:7697,dcircle:9427,dcircumflexbelow:7699,dcroat:273,ddabengal [...]
+ddagujarati:2721,ddagurmukhi:2593,ddalarabic:1672,ddalfinalarabic:64393,dddhadeva:2396,ddhabengali:2466,ddhadeva:2338,ddhagujarati:2722,ddhagurmukhi:2594,ddotaccent:7691,ddotbelow:7693,decimalseparatorarabic:1643,decimalseparatorpersian:1643,decyrillic:1076,degree:176,dehihebrew:1453,dehiragana:12391,deicoptic:1007,dekatakana:12487,deleteleft:9003,deleteright:8998,delta:948,deltaturned:397,denominatorminusonenumeratorbengali:2552,dezh:676,dhabengali:2471,dhadeva:2343,dhagujarati:2727,dha [...]
+dhook:599,dialytikatonos:901,dialytikatonoscmb:836,diamond:9830,diamondsuitwhite:9826,dieresis:168,dieresisacute:63191,dieresisbelowcmb:804,dieresiscmb:776,dieresisgrave:63192,dieresistonos:901,dihiragana:12386,dikatakana:12482,dittomark:12291,divide:247,divides:8739,divisionslash:8725,djecyrillic:1106,dkshade:9619,dlinebelow:7695,dlsquare:13207,dmacron:273,dmonospace:65348,dnblock:9604,dochadathai:3598,dodekthai:3604,dohiragana:12393,dokatakana:12489,dollar:36,dollarinferior:63203,dolla [...]
+dollaroldstyle:63268,dollarsmall:65129,dollarsuperior:63204,dong:8363,dorusquare:13094,dotaccent:729,dotaccentcmb:775,dotbelowcmb:803,dotbelowcomb:803,dotkatakana:12539,dotlessi:305,dotlessj:63166,dotlessjstrokehook:644,dotmath:8901,dottedcircle:9676,doubleyodpatah:64287,doubleyodpatahhebrew:64287,downtackbelowcmb:798,downtackmod:725,dparen:9375,dsuperior:63211,dtail:598,dtopbar:396,duhiragana:12389,dukatakana:12485,dz:499,dzaltone:675,dzcaron:454,dzcurl:677,dzeabkhasiancyrillic:1249,dze [...]
+dzhecyrillic:1119,e:101,eacute:233,earth:9793,ebengali:2447,ebopomofo:12572,ebreve:277,ecandradeva:2317,ecandragujarati:2701,ecandravowelsigndeva:2373,ecandravowelsigngujarati:2757,ecaron:283,ecedillabreve:7709,echarmenian:1381,echyiwnarmenian:1415,ecircle:9428,ecircumflex:234,ecircumflexacute:7871,ecircumflexbelow:7705,ecircumflexdotbelow:7879,ecircumflexgrave:7873,ecircumflexhookabove:7875,ecircumflextilde:7877,ecyrillic:1108,edblgrave:517,edeva:2319,edieresis:235,edot:279,edotaccent:2 [...]
+eegurmukhi:2575,eematragurmukhi:2631,efcyrillic:1092,egrave:232,egujarati:2703,eharmenian:1383,ehbopomofo:12573,ehiragana:12360,ehookabove:7867,eibopomofo:12575,eight:56,eightarabic:1640,eightbengali:2542,eightcircle:9319,eightcircleinversesansserif:10129,eightdeva:2414,eighteencircle:9329,eighteenparen:9349,eighteenperiod:9369,eightgujarati:2798,eightgurmukhi:2670,eighthackarabic:1640,eighthangzhou:12328,eighthnotebeamed:9835,eightideographicparen:12839,eightinferior:8328,eightmonospace [...]
+eightparen:9339,eightperiod:9359,eightpersian:1784,eightroman:8567,eightsuperior:8312,eightthai:3672,einvertedbreve:519,eiotifiedcyrillic:1125,ekatakana:12456,ekatakanahalfwidth:65396,ekonkargurmukhi:2676,ekorean:12628,elcyrillic:1083,element:8712,elevencircle:9322,elevenparen:9342,elevenperiod:9362,elevenroman:8570,ellipsis:8230,ellipsisvertical:8942,emacron:275,emacronacute:7703,emacrongrave:7701,emcyrillic:1084,emdash:8212,emdashvertical:65073,emonospace:65349,emphasismarkarmenian:137 [...]
+enbopomofo:12579,encyrillic:1085,endash:8211,endashvertical:65074,endescendercyrillic:1187,eng:331,engbopomofo:12581,enghecyrillic:1189,enhookcyrillic:1224,enspace:8194,eogonek:281,eokorean:12627,eopen:603,eopenclosed:666,eopenreversed:604,eopenreversedclosed:606,eopenreversedhook:605,eparen:9376,epsilon:949,epsilontonos:941,equal:61,equalmonospace:65309,equalsmall:65126,equalsuperior:8316,equivalence:8801,erbopomofo:12582,ercyrillic:1088,ereversed:600,ereversedcyrillic:1101,escyrillic:1 [...]
+esh:643,eshcurl:646,eshortdeva:2318,eshortvowelsigndeva:2374,eshreversedloop:426,eshsquatreversed:645,esmallhiragana:12359,esmallkatakana:12455,esmallkatakanahalfwidth:65386,estimated:8494,esuperior:63212,eta:951,etarmenian:1384,etatonos:942,eth:240,etilde:7869,etildebelow:7707,etnahtafoukhhebrew:1425,etnahtafoukhlefthebrew:1425,etnahtahebrew:1425,etnahtalefthebrew:1425,eturned:477,eukorean:12641,euro:8364,evowelsignbengali:2503,evowelsigndeva:2375,evowelsigngujarati:2759,exclam:33,excla [...]
+exclamdbl:8252,exclamdown:161,exclamdownsmall:63393,exclammonospace:65281,exclamsmall:63265,existential:8707,ezh:658,ezhcaron:495,ezhcurl:659,ezhreversed:441,ezhtail:442,f:102,fadeva:2398,fagurmukhi:2654,fahrenheit:8457,fathaarabic:1614,fathalowarabic:1614,fathatanarabic:1611,fbopomofo:12552,fcircle:9429,fdotaccent:7711,feharabic:1601,feharmenian:1414,fehfinalarabic:65234,fehinitialarabic:65235,fehmedialarabic:65236,feicoptic:997,female:9792,ff:64256,ffi:64259,ffl:64260,fi:64257,fifteenc [...]
+fifteenparen:9346,fifteenperiod:9366,figuredash:8210,filledbox:9632,filledrect:9644,finalkaf:1498,finalkafdagesh:64314,finalkafdageshhebrew:64314,finalkafhebrew:1498,finalmem:1501,finalmemhebrew:1501,finalnun:1503,finalnunhebrew:1503,finalpe:1507,finalpehebrew:1507,finaltsadi:1509,finaltsadihebrew:1509,firsttonechinese:713,fisheye:9673,fitacyrillic:1139,five:53,fivearabic:1637,fivebengali:2539,fivecircle:9316,fivecircleinversesansserif:10126,fivedeva:2411,fiveeighths:8541,fivegujarati:27 [...]
+fivehackarabic:1637,fivehangzhou:12325,fiveideographicparen:12836,fiveinferior:8325,fivemonospace:65301,fiveoldstyle:63285,fiveparen:9336,fiveperiod:9356,fivepersian:1781,fiveroman:8564,fivesuperior:8309,fivethai:3669,fl:64258,florin:402,fmonospace:65350,fmsquare:13209,fofanthai:3615,fofathai:3613,fongmanthai:3663,forall:8704,four:52,fourarabic:1636,fourbengali:2538,fourcircle:9315,fourcircleinversesansserif:10125,fourdeva:2410,fourgujarati:2794,fourgurmukhi:2666,fourhackarabic:1636,four [...]
+fourideographicparen:12835,fourinferior:8324,fourmonospace:65300,fournumeratorbengali:2551,fouroldstyle:63284,fourparen:9335,fourperiod:9355,fourpersian:1780,fourroman:8563,foursuperior:8308,fourteencircle:9325,fourteenparen:9345,fourteenperiod:9365,fourthai:3668,fourthtonechinese:715,fparen:9377,fraction:8260,franc:8355,g:103,gabengali:2455,gacute:501,gadeva:2327,gafarabic:1711,gaffinalarabic:64403,gafinitialarabic:64404,gafmedialarabic:64405,gagujarati:2711,gagurmukhi:2583,gahiragana:1 [...]
+gamma:947,gammalatinsmall:611,gammasuperior:736,gangiacoptic:1003,gbopomofo:12557,gbreve:287,gcaron:487,gcedilla:291,gcircle:9430,gcircumflex:285,gcommaaccent:291,gdot:289,gdotaccent:289,gecyrillic:1075,gehiragana:12370,gekatakana:12466,geometricallyequal:8785,gereshaccenthebrew:1436,gereshhebrew:1523,gereshmuqdamhebrew:1437,germandbls:223,gershayimaccenthebrew:1438,gershayimhebrew:1524,getamark:12307,ghabengali:2456,ghadarmenian:1394,ghadeva:2328,ghagujarati:2712,ghagurmukhi:2584,ghaina [...]
+ghainfinalarabic:65230,ghaininitialarabic:65231,ghainmedialarabic:65232,ghemiddlehookcyrillic:1173,ghestrokecyrillic:1171,gheupturncyrillic:1169,ghhadeva:2394,ghhagurmukhi:2650,ghook:608,ghzsquare:13203,gihiragana:12366,gikatakana:12462,gimarmenian:1379,gimel:1490,gimeldagesh:64306,gimeldageshhebrew:64306,gimelhebrew:1490,gjecyrillic:1107,glottalinvertedstroke:446,glottalstop:660,glottalstopinverted:662,glottalstopmod:704,glottalstopreversed:661,glottalstopreversedmod:705,glottalstopreve [...]
+glottalstopstroke:673,glottalstopstrokereversed:674,gmacron:7713,gmonospace:65351,gohiragana:12372,gokatakana:12468,gparen:9378,gpasquare:13228,gradient:8711,grave:96,gravebelowcmb:790,gravecmb:768,gravecomb:768,gravedeva:2387,gravelowmod:718,gravemonospace:65344,gravetonecmb:832,greater:62,greaterequal:8805,greaterequalorless:8923,greatermonospace:65310,greaterorequivalent:8819,greaterorless:8823,greateroverequal:8807,greatersmall:65125,gscript:609,gstroke:485,guhiragana:12368,guillemot [...]
+guillemotright:187,guilsinglleft:8249,guilsinglright:8250,gukatakana:12464,guramusquare:13080,gysquare:13257,h:104,haabkhasiancyrillic:1193,haaltonearabic:1729,habengali:2489,hadescendercyrillic:1203,hadeva:2361,hagujarati:2745,hagurmukhi:2617,haharabic:1581,hahfinalarabic:65186,hahinitialarabic:65187,hahiragana:12399,hahmedialarabic:65188,haitusquare:13098,hakatakana:12495,hakatakanahalfwidth:65418,halantgurmukhi:2637,hamzaarabic:1569,hamzalowarabic:1569,hangulfiller:12644,hardsigncyril [...]
+harpoonleftbarbup:8636,harpoonrightbarbup:8640,hasquare:13258,hatafpatah:1458,hatafpatah16:1458,hatafpatah23:1458,hatafpatah2f:1458,hatafpatahhebrew:1458,hatafpatahnarrowhebrew:1458,hatafpatahquarterhebrew:1458,hatafpatahwidehebrew:1458,hatafqamats:1459,hatafqamats1b:1459,hatafqamats28:1459,hatafqamats34:1459,hatafqamatshebrew:1459,hatafqamatsnarrowhebrew:1459,hatafqamatsquarterhebrew:1459,hatafqamatswidehebrew:1459,hatafsegol:1457,hatafsegol17:1457,hatafsegol24:1457,hatafsegol30:1457,ha [...]
+hatafsegolnarrowhebrew:1457,hatafsegolquarterhebrew:1457,hatafsegolwidehebrew:1457,hbar:295,hbopomofo:12559,hbrevebelow:7723,hcedilla:7721,hcircle:9431,hcircumflex:293,hdieresis:7719,hdotaccent:7715,hdotbelow:7717,he:1492,heart:9829,heartsuitblack:9829,heartsuitwhite:9825,hedagesh:64308,hedageshhebrew:64308,hehaltonearabic:1729,heharabic:1607,hehebrew:1492,hehfinalaltonearabic:64423,hehfinalalttwoarabic:65258,hehfinalarabic:65258,hehhamzaabovefinalarabic:64421,hehhamzaaboveisolatedarabic [...]
+hehinitialarabic:65259,hehiragana:12408,hehmedialaltonearabic:64425,hehmedialarabic:65260,heiseierasquare:13179,hekatakana:12504,hekatakanahalfwidth:65421,hekutaarusquare:13110,henghook:615,herutusquare:13113,het:1495,hethebrew:1495,hhook:614,hhooksuperior:689,hieuhacirclekorean:12923,hieuhaparenkorean:12827,hieuhcirclekorean:12909,hieuhkorean:12622,hieuhparenkorean:12813,hihiragana:12402,hikatakana:12498,hikatakanahalfwidth:65419,hiriq:1460,hiriq14:1460,hiriq21:1460,hiriq2d:1460,hiriqhe [...]
+hiriqnarrowhebrew:1460,hiriqquarterhebrew:1460,hiriqwidehebrew:1460,hlinebelow:7830,hmonospace:65352,hoarmenian:1392,hohipthai:3627,hohiragana:12411,hokatakana:12507,hokatakanahalfwidth:65422,holam:1465,holam19:1465,holam26:1465,holam32:1465,holamhebrew:1465,holamnarrowhebrew:1465,holamquarterhebrew:1465,holamwidehebrew:1465,honokhukthai:3630,hookabovecomb:777,hookcmb:777,hookpalatalizedbelowcmb:801,hookretroflexbelowcmb:802,hoonsquare:13122,horicoptic:1001,horizontalbar:8213,horncmb:795 [...]
+house:8962,hparen:9379,hsuperior:688,hturned:613,huhiragana:12405,huiitosquare:13107,hukatakana:12501,hukatakanahalfwidth:65420,hungarumlaut:733,hungarumlautcmb:779,hv:405,hyphen:45,hypheninferior:63205,hyphenmonospace:65293,hyphensmall:65123,hyphensuperior:63206,hyphentwo:8208,i:105,iacute:237,iacyrillic:1103,ibengali:2439,ibopomofo:12583,ibreve:301,icaron:464,icircle:9432,icircumflex:238,icyrillic:1110,idblgrave:521,ideographearthcircle:12943,ideographfirecircle:12939,ideographicallian [...]
+ideographiccallparen:12858,ideographiccentrecircle:12965,ideographicclose:12294,ideographiccomma:12289,ideographiccommaleft:65380,ideographiccongratulationparen:12855,ideographiccorrectcircle:12963,ideographicearthparen:12847,ideographicenterpriseparen:12861,ideographicexcellentcircle:12957,ideographicfestivalparen:12864,ideographicfinancialcircle:12950,ideographicfinancialparen:12854,ideographicfireparen:12843,ideographichaveparen:12850,ideographichighcircle:12964,ideographiciterationma [...]
+ideographiclaborcircle:12952,ideographiclaborparen:12856,ideographicleftcircle:12967,ideographiclowcircle:12966,ideographicmedicinecircle:12969,ideographicmetalparen:12846,ideographicmoonparen:12842,ideographicnameparen:12852,ideographicperiod:12290,ideographicprintcircle:12958,ideographicreachparen:12867,ideographicrepresentparen:12857,ideographicresourceparen:12862,ideographicrightcircle:12968,ideographicsecretcircle:12953,ideographicselfparen:12866,ideographicsocietyparen:12851,ideogr [...]
+ideographicspecialparen:12853,ideographicstockparen:12849,ideographicstudyparen:12859,ideographicsunparen:12848,ideographicsuperviseparen:12860,ideographicwaterparen:12844,ideographicwoodparen:12845,ideographiczero:12295,ideographmetalcircle:12942,ideographmooncircle:12938,ideographnamecircle:12948,ideographsuncircle:12944,ideographwatercircle:12940,ideographwoodcircle:12941,ideva:2311,idieresis:239,idieresisacute:7727,idieresiscyrillic:1253,idotbelow:7883,iebrevecyrillic:1239,iecyrillic [...]
+ieungaparenkorean:12821,ieungcirclekorean:12903,ieungkorean:12615,ieungparenkorean:12807,igrave:236,igujarati:2695,igurmukhi:2567,ihiragana:12356,ihookabove:7881,iibengali:2440,iicyrillic:1080,iideva:2312,iigujarati:2696,iigurmukhi:2568,iimatragurmukhi:2624,iinvertedbreve:523,iishortcyrillic:1081,iivowelsignbengali:2496,iivowelsigndeva:2368,iivowelsigngujarati:2752,ij:307,ikatakana:12452,ikatakanahalfwidth:65394,ikorean:12643,ilde:732,iluyhebrew:1452,imacron:299,imacroncyrillic:1251,imag [...]
+imatragurmukhi:2623,imonospace:65353,increment:8710,infinity:8734,iniarmenian:1387,integral:8747,integralbottom:8993,integralbt:8993,integralex:63733,integraltop:8992,integraltp:8992,intersection:8745,intisquare:13061,invbullet:9688,invcircle:9689,invsmileface:9787,iocyrillic:1105,iogonek:303,iota:953,iotadieresis:970,iotadieresistonos:912,iotalatin:617,iotatonos:943,iparen:9380,irigurmukhi:2674,ismallhiragana:12355,ismallkatakana:12451,ismallkatakanahalfwidth:65384,issharbengali:2554,is [...]
+isuperior:63213,iterationhiragana:12445,iterationkatakana:12541,itilde:297,itildebelow:7725,iubopomofo:12585,iucyrillic:1102,ivowelsignbengali:2495,ivowelsigndeva:2367,ivowelsigngujarati:2751,izhitsacyrillic:1141,izhitsadblgravecyrillic:1143,j:106,jaarmenian:1393,jabengali:2460,jadeva:2332,jagujarati:2716,jagurmukhi:2588,jbopomofo:12560,jcaron:496,jcircle:9433,jcircumflex:309,jcrossedtail:669,jdotlessstroke:607,jecyrillic:1112,jeemarabic:1580,jeemfinalarabic:65182,jeeminitialarabic:65183 [...]
+jeharabic:1688,jehfinalarabic:64395,jhabengali:2461,jhadeva:2333,jhagujarati:2717,jhagurmukhi:2589,jheharmenian:1403,jis:12292,jmonospace:65354,jparen:9381,jsuperior:690,k:107,kabashkircyrillic:1185,kabengali:2453,kacute:7729,kacyrillic:1082,kadescendercyrillic:1179,kadeva:2325,kaf:1499,kafarabic:1603,kafdagesh:64315,kafdageshhebrew:64315,kaffinalarabic:65242,kafhebrew:1499,kafinitialarabic:65243,kafmedialarabic:65244,kafrafehebrew:64333,kagujarati:2709,kagurmukhi:2581,kahiragana:12363,k [...]
+kakatakana:12459,kakatakanahalfwidth:65398,kappa:954,kappasymbolgreek:1008,kapyeounmieumkorean:12657,kapyeounphieuphkorean:12676,kapyeounpieupkorean:12664,kapyeounssangpieupkorean:12665,karoriisquare:13069,kashidaautoarabic:1600,kashidaautonosidebearingarabic:1600,kasmallkatakana:12533,kasquare:13188,kasraarabic:1616,kasratanarabic:1613,kastrokecyrillic:1183,katahiraprolongmarkhalfwidth:65392,kaverticalstrokecyrillic:1181,kbopomofo:12558,kcalsquare:13193,kcaron:489,kcedilla:311,kcircle:9 [...]
+kdotbelow:7731,keharmenian:1412,kehiragana:12369,kekatakana:12465,kekatakanahalfwidth:65401,kenarmenian:1391,kesmallkatakana:12534,kgreenlandic:312,khabengali:2454,khacyrillic:1093,khadeva:2326,khagujarati:2710,khagurmukhi:2582,khaharabic:1582,khahfinalarabic:65190,khahinitialarabic:65191,khahmedialarabic:65192,kheicoptic:999,khhadeva:2393,khhagurmukhi:2649,khieukhacirclekorean:12920,khieukhaparenkorean:12824,khieukhcirclekorean:12906,khieukhkorean:12619,khieukhparenkorean:12810,khokhait [...]
+khokhonthai:3589,khokhuatthai:3587,khokhwaithai:3588,khomutthai:3675,khook:409,khorakhangthai:3590,khzsquare:13201,kihiragana:12365,kikatakana:12461,kikatakanahalfwidth:65399,kiroguramusquare:13077,kiromeetorusquare:13078,kirosquare:13076,kiyeokacirclekorean:12910,kiyeokaparenkorean:12814,kiyeokcirclekorean:12896,kiyeokkorean:12593,kiyeokparenkorean:12800,kiyeoksioskorean:12595,kjecyrillic:1116,klinebelow:7733,klsquare:13208,kmcubedsquare:13222,kmonospace:65355,kmsquaredsquare:13218,kohi [...]
+kohmsquare:13248,kokaithai:3585,kokatakana:12467,kokatakanahalfwidth:65402,kooposquare:13086,koppacyrillic:1153,koreanstandardsymbol:12927,koroniscmb:835,kparen:9382,kpasquare:13226,ksicyrillic:1135,ktsquare:13263,kturned:670,kuhiragana:12367,kukatakana:12463,kukatakanahalfwidth:65400,kvsquare:13240,kwsquare:13246,l:108,labengali:2482,lacute:314,ladeva:2354,lagujarati:2738,lagurmukhi:2610,lakkhangyaothai:3653,lamaleffinalarabic:65276,lamalefhamzaabovefinalarabic:65272,lamalefhamzaaboveis [...]
+lamalefhamzabelowfinalarabic:65274,lamalefhamzabelowisolatedarabic:65273,lamalefisolatedarabic:65275,lamalefmaddaabovefinalarabic:65270,lamalefmaddaaboveisolatedarabic:65269,lamarabic:1604,lambda:955,lambdastroke:411,lamed:1500,lameddagesh:64316,lameddageshhebrew:64316,lamedhebrew:1500,lamfinalarabic:65246,lamhahinitialarabic:64714,laminitialarabic:65247,lamjeeminitialarabic:64713,lamkhahinitialarabic:64715,lamlamhehisolatedarabic:65010,lammedialarabic:65248,lammeemhahinitialarabic:64904 [...]
+largecircle:9711,lbar:410,lbelt:620,lbopomofo:12556,lcaron:318,lcedilla:316,lcircle:9435,lcircumflexbelow:7741,lcommaaccent:316,ldot:320,ldotaccent:320,ldotbelow:7735,ldotbelowmacron:7737,leftangleabovecmb:794,lefttackbelowcmb:792,less:60,lessequal:8804,lessequalorgreater:8922,lessmonospace:65308,lessorequivalent:8818,lessorgreater:8822,lessoverequal:8806,lesssmall:65124,lezh:622,lfblock:9612,lhookretroflex:621,lira:8356,liwnarmenian:1388,lj:457,ljecyrillic:1113,ll:63168,lladeva:2355,lla [...]
+llinebelow:7739,llladeva:2356,llvocalicbengali:2529,llvocalicdeva:2401,llvocalicvowelsignbengali:2531,llvocalicvowelsigndeva:2403,lmiddletilde:619,lmonospace:65356,lmsquare:13264,lochulathai:3628,logicaland:8743,logicalnot:172,logicalnotreversed:8976,logicalor:8744,lolingthai:3621,longs:383,lowlinecenterline:65102,lowlinecmb:818,lowlinedashed:65101,lozenge:9674,lparen:9383,lslash:322,lsquare:8467,lsuperior:63214,ltshade:9617,luthai:3622,lvocalicbengali:2444,lvocalicdeva:2316,lvocalicvowe [...]
+lvocalicvowelsigndeva:2402,lxsquare:13267,m:109,mabengali:2478,macron:175,macronbelowcmb:817,macroncmb:772,macronlowmod:717,macronmonospace:65507,macute:7743,madeva:2350,magujarati:2734,magurmukhi:2606,mahapakhhebrew:1444,mahapakhlefthebrew:1444,mahiragana:12414,maichattawalowleftthai:63637,maichattawalowrightthai:63636,maichattawathai:3659,maichattawaupperleftthai:63635,maieklowleftthai:63628,maieklowrightthai:63627,maiekthai:3656,maiekupperleftthai:63626,maihanakatleftthai:63620,maihan [...]
+maitaikhuleftthai:63625,maitaikhuthai:3655,maitholowleftthai:63631,maitholowrightthai:63630,maithothai:3657,maithoupperleftthai:63629,maitrilowleftthai:63634,maitrilowrightthai:63633,maitrithai:3658,maitriupperleftthai:63632,maiyamokthai:3654,makatakana:12510,makatakanahalfwidth:65423,male:9794,mansyonsquare:13127,maqafhebrew:1470,mars:9794,masoracirclehebrew:1455,masquare:13187,mbopomofo:12551,mbsquare:13268,mcircle:9436,mcubedsquare:13221,mdotaccent:7745,mdotbelow:7747,meemarabic:1605, [...]
+meeminitialarabic:65251,meemmedialarabic:65252,meemmeeminitialarabic:64721,meemmeemisolatedarabic:64584,meetorusquare:13133,mehiragana:12417,meizierasquare:13182,mekatakana:12513,mekatakanahalfwidth:65426,mem:1502,memdagesh:64318,memdageshhebrew:64318,memhebrew:1502,menarmenian:1396,merkhahebrew:1445,merkhakefulahebrew:1446,merkhakefulalefthebrew:1446,merkhalefthebrew:1445,mhook:625,mhzsquare:13202,middledotkatakanahalfwidth:65381,middot:183,mieumacirclekorean:12914,mieumaparenkorean:128 [...]
+mieumkorean:12609,mieumpansioskorean:12656,mieumparenkorean:12804,mieumpieupkorean:12654,mieumsioskorean:12655,mihiragana:12415,mikatakana:12511,mikatakanahalfwidth:65424,minus:8722,minusbelowcmb:800,minuscircle:8854,minusmod:727,minusplus:8723,minute:8242,miribaarusquare:13130,mirisquare:13129,mlonglegturned:624,mlsquare:13206,mmcubedsquare:13219,mmonospace:65357,mmsquaredsquare:13215,mohiragana:12418,mohmsquare:13249,mokatakana:12514,mokatakanahalfwidth:65427,molsquare:13270,momathai:3 [...]
+moverssquaredsquare:13224,mparen:9384,mpasquare:13227,mssquare:13235,msuperior:63215,mturned:623,mu:181,mu1:181,muasquare:13186,muchgreater:8811,muchless:8810,mufsquare:13196,mugreek:956,mugsquare:13197,muhiragana:12416,mukatakana:12512,mukatakanahalfwidth:65425,mulsquare:13205,multiply:215,mumsquare:13211,munahhebrew:1443,munahlefthebrew:1443,musicalnote:9834,musicalnotedbl:9835,musicflatsign:9837,musicsharpsign:9839,mussquare:13234,muvsquare:13238,muwsquare:13244,mvmegasquare:13241,mvs [...]
+mwmegasquare:13247,mwsquare:13245,n:110,nabengali:2472,nabla:8711,nacute:324,nadeva:2344,nagujarati:2728,nagurmukhi:2600,nahiragana:12394,nakatakana:12490,nakatakanahalfwidth:65413,napostrophe:329,nasquare:13185,nbopomofo:12555,nbspace:160,ncaron:328,ncedilla:326,ncircle:9437,ncircumflexbelow:7755,ncommaaccent:326,ndotaccent:7749,ndotbelow:7751,nehiragana:12397,nekatakana:12493,nekatakanahalfwidth:65416,newsheqelsign:8362,nfsquare:13195,ngabengali:2457,ngadeva:2329,ngagujarati:2713,ngagu [...]
+ngonguthai:3591,nhiragana:12435,nhookleft:626,nhookretroflex:627,nieunacirclekorean:12911,nieunaparenkorean:12815,nieuncieuckorean:12597,nieuncirclekorean:12897,nieunhieuhkorean:12598,nieunkorean:12596,nieunpansioskorean:12648,nieunparenkorean:12801,nieunsioskorean:12647,nieuntikeutkorean:12646,nihiragana:12395,nikatakana:12491,nikatakanahalfwidth:65414,nikhahitleftthai:63641,nikhahitthai:3661,nine:57,ninearabic:1641,ninebengali:2543,ninecircle:9320,ninecircleinversesansserif:10130,nined [...]
+ninegujarati:2799,ninegurmukhi:2671,ninehackarabic:1641,ninehangzhou:12329,nineideographicparen:12840,nineinferior:8329,ninemonospace:65305,nineoldstyle:63289,nineparen:9340,nineperiod:9360,ninepersian:1785,nineroman:8568,ninesuperior:8313,nineteencircle:9330,nineteenparen:9350,nineteenperiod:9370,ninethai:3673,nj:460,njecyrillic:1114,nkatakana:12531,nkatakanahalfwidth:65437,nlegrightlong:414,nlinebelow:7753,nmonospace:65358,nmsquare:13210,nnabengali:2467,nnadeva:2339,nnagujarati:2723,nn [...]
+nnnadeva:2345,nohiragana:12398,nokatakana:12494,nokatakanahalfwidth:65417,nonbreakingspace:160,nonenthai:3603,nonuthai:3609,noonarabic:1606,noonfinalarabic:65254,noonghunnaarabic:1722,noonghunnafinalarabic:64415,nooninitialarabic:65255,noonjeeminitialarabic:64722,noonjeemisolatedarabic:64587,noonmedialarabic:65256,noonmeeminitialarabic:64725,noonmeemisolatedarabic:64590,noonnoonfinalarabic:64653,notcontains:8716,notelement:8713,notelementof:8713,notequal:8800,notgreater:8815,notgreaterno [...]
+notgreaternorless:8825,notidentical:8802,notless:8814,notlessnorequal:8816,notparallel:8742,notprecedes:8832,notsubset:8836,notsucceeds:8833,notsuperset:8837,nowarmenian:1398,nparen:9385,nssquare:13233,nsuperior:8319,ntilde:241,nu:957,nuhiragana:12396,nukatakana:12492,nukatakanahalfwidth:65415,nuktabengali:2492,nuktadeva:2364,nuktagujarati:2748,nuktagurmukhi:2620,numbersign:35,numbersignmonospace:65283,numbersignsmall:65119,numeralsigngreek:884,numeralsignlowergreek:885,numero:8470,nun:1 [...]
+nundageshhebrew:64320,nunhebrew:1504,nvsquare:13237,nwsquare:13243,nyabengali:2462,nyadeva:2334,nyagujarati:2718,nyagurmukhi:2590,o:111,oacute:243,oangthai:3629,obarred:629,obarredcyrillic:1257,obarreddieresiscyrillic:1259,obengali:2451,obopomofo:12571,obreve:335,ocandradeva:2321,ocandragujarati:2705,ocandravowelsigndeva:2377,ocandravowelsigngujarati:2761,ocaron:466,ocircle:9438,ocircumflex:244,ocircumflexacute:7889,ocircumflexdotbelow:7897,ocircumflexgrave:7891,ocircumflexhookabove:7893 [...]
+ocyrillic:1086,odblacute:337,odblgrave:525,odeva:2323,odieresis:246,odieresiscyrillic:1255,odotbelow:7885,oe:339,oekorean:12634,ogonek:731,ogonekcmb:808,ograve:242,ogujarati:2707,oharmenian:1413,ohiragana:12362,ohookabove:7887,ohorn:417,ohornacute:7899,ohorndotbelow:7907,ohorngrave:7901,ohornhookabove:7903,ohorntilde:7905,ohungarumlaut:337,oi:419,oinvertedbreve:527,okatakana:12458,okatakanahalfwidth:65397,okorean:12631,olehebrew:1451,omacron:333,omacronacute:7763,omacrongrave:7761,omdeva [...]
+omega1:982,omegacyrillic:1121,omegalatinclosed:631,omegaroundcyrillic:1147,omegatitlocyrillic:1149,omegatonos:974,omgujarati:2768,omicron:959,omicrontonos:972,omonospace:65359,one:49,onearabic:1633,onebengali:2535,onecircle:9312,onecircleinversesansserif:10122,onedeva:2407,onedotenleader:8228,oneeighth:8539,onefitted:63196,onegujarati:2791,onegurmukhi:2663,onehackarabic:1633,onehalf:189,onehangzhou:12321,oneideographicparen:12832,oneinferior:8321,onemonospace:65297,onenumeratorbengali:25 [...]
+oneparen:9332,oneperiod:9352,onepersian:1777,onequarter:188,oneroman:8560,onesuperior:185,onethai:3665,onethird:8531,oogonek:491,oogonekmacron:493,oogurmukhi:2579,oomatragurmukhi:2635,oopen:596,oparen:9386,openbullet:9702,option:8997,ordfeminine:170,ordmasculine:186,orthogonal:8735,oshortdeva:2322,oshortvowelsigndeva:2378,oslash:248,oslashacute:511,osmallhiragana:12361,osmallkatakana:12457,osmallkatakanahalfwidth:65387,ostrokeacute:511,osuperior:63216,otcyrillic:1151,otilde:245,otildeacu [...]
+oubopomofo:12577,overline:8254,overlinecenterline:65098,overlinecmb:773,overlinedashed:65097,overlinedblwavy:65100,overlinewavy:65099,overscore:175,ovowelsignbengali:2507,ovowelsigndeva:2379,ovowelsigngujarati:2763,p:112,paampssquare:13184,paasentosquare:13099,pabengali:2474,pacute:7765,padeva:2346,pagedown:8671,pageup:8670,pagujarati:2730,pagurmukhi:2602,pahiragana:12401,paiyannoithai:3631,pakatakana:12497,palatalizationcyrilliccmb:1156,palochkacyrillic:1216,pansioskorean:12671,paragrap [...]
+parenleft:40,parenleftaltonearabic:64830,parenleftbt:63725,parenleftex:63724,parenleftinferior:8333,parenleftmonospace:65288,parenleftsmall:65113,parenleftsuperior:8317,parenlefttp:63723,parenleftvertical:65077,parenright:41,parenrightaltonearabic:64831,parenrightbt:63736,parenrightex:63735,parenrightinferior:8334,parenrightmonospace:65289,parenrightsmall:65114,parenrightsuperior:8318,parenrighttp:63734,parenrightvertical:65078,partialdiff:8706,paseqhebrew:1472,pashtahebrew:1433,pasquare [...]
+patah11:1463,patah1d:1463,patah2a:1463,patahhebrew:1463,patahnarrowhebrew:1463,patahquarterhebrew:1463,patahwidehebrew:1463,pazerhebrew:1441,pbopomofo:12550,pcircle:9439,pdotaccent:7767,pe:1508,pecyrillic:1087,pedagesh:64324,pedageshhebrew:64324,peezisquare:13115,pefinaldageshhebrew:64323,peharabic:1662,peharmenian:1402,pehebrew:1508,pehfinalarabic:64343,pehinitialarabic:64344,pehiragana:12410,pehmedialarabic:64345,pekatakana:12506,pemiddlehookcyrillic:1191,perafehebrew:64334,percent:37, [...]
+percentmonospace:65285,percentsmall:65130,period:46,periodarmenian:1417,periodcentered:183,periodhalfwidth:65377,periodinferior:63207,periodmonospace:65294,periodsmall:65106,periodsuperior:63208,perispomenigreekcmb:834,perpendicular:8869,perthousand:8240,peseta:8359,pfsquare:13194,phabengali:2475,phadeva:2347,phagujarati:2731,phagurmukhi:2603,phi:966,phi1:981,phieuphacirclekorean:12922,phieuphaparenkorean:12826,phieuphcirclekorean:12908,phieuphkorean:12621,phieuphparenkorean:12812,philat [...]
+phisymbolgreek:981,phook:421,phophanthai:3614,phophungthai:3612,phosamphaothai:3616,pi:960,pieupacirclekorean:12915,pieupaparenkorean:12819,pieupcieuckorean:12662,pieupcirclekorean:12901,pieupkiyeokkorean:12658,pieupkorean:12610,pieupparenkorean:12805,pieupsioskiyeokkorean:12660,pieupsioskorean:12612,pieupsiostikeutkorean:12661,pieupthieuthkorean:12663,pieuptikeutkorean:12659,pihiragana:12404,pikatakana:12500,pisymbolgreek:982,piwrarmenian:1411,plus:43,plusbelowcmb:799,pluscircle:8853,pl [...]
+plusmod:726,plusmonospace:65291,plussmall:65122,plussuperior:8314,pmonospace:65360,pmsquare:13272,pohiragana:12413,pointingindexdownwhite:9759,pointingindexleftwhite:9756,pointingindexrightwhite:9758,pointingindexupwhite:9757,pokatakana:12509,poplathai:3611,postalmark:12306,postalmarkface:12320,pparen:9387,precedes:8826,prescription:8478,primemod:697,primereversed:8245,product:8719,projective:8965,prolongedkana:12540,propellor:8984,propersubset:8834,propersuperset:8835,proportion:8759,pr [...]
+psi:968,psicyrillic:1137,psilipneumatacyrilliccmb:1158,pssquare:13232,puhiragana:12407,pukatakana:12503,pvsquare:13236,pwsquare:13242,q:113,qadeva:2392,qadmahebrew:1448,qafarabic:1602,qaffinalarabic:65238,qafinitialarabic:65239,qafmedialarabic:65240,qamats:1464,qamats10:1464,qamats1a:1464,qamats1c:1464,qamats27:1464,qamats29:1464,qamats33:1464,qamatsde:1464,qamatshebrew:1464,qamatsnarrowhebrew:1464,qamatsqatanhebrew:1464,qamatsqatannarrowhebrew:1464,qamatsqatanquarterhebrew:1464,qamatsqa [...]
+qamatsquarterhebrew:1464,qamatswidehebrew:1464,qarneyparahebrew:1439,qbopomofo:12561,qcircle:9440,qhook:672,qmonospace:65361,qof:1511,qofdagesh:64327,qofdageshhebrew:64327,qofhebrew:1511,qparen:9388,quarternote:9833,qubuts:1467,qubuts18:1467,qubuts25:1467,qubuts31:1467,qubutshebrew:1467,qubutsnarrowhebrew:1467,qubutsquarterhebrew:1467,qubutswidehebrew:1467,question:63,questionarabic:1567,questionarmenian:1374,questiondown:191,questiondownsmall:63423,questiongreek:894,questionmonospace:65 [...]
+quotedbl:34,quotedblbase:8222,quotedblleft:8220,quotedblmonospace:65282,quotedblprime:12318,quotedblprimereversed:12317,quotedblright:8221,quoteleft:8216,quoteleftreversed:8219,quotereversed:8219,quoteright:8217,quoterightn:329,quotesinglbase:8218,quotesingle:39,quotesinglemonospace:65287,r:114,raarmenian:1404,rabengali:2480,racute:341,radeva:2352,radical:8730,radicalex:63717,radoverssquare:13230,radoverssquaredsquare:13231,radsquare:13229,rafe:1471,rafehebrew:1471,ragujarati:2736,ragurm [...]
+rahiragana:12425,rakatakana:12521,rakatakanahalfwidth:65431,ralowerdiagonalbengali:2545,ramiddlediagonalbengali:2544,ramshorn:612,ratio:8758,rbopomofo:12566,rcaron:345,rcedilla:343,rcircle:9441,rcommaaccent:343,rdblgrave:529,rdotaccent:7769,rdotbelow:7771,rdotbelowmacron:7773,referencemark:8251,reflexsubset:8838,reflexsuperset:8839,registered:174,registersans:63720,registerserif:63194,reharabic:1585,reharmenian:1408,rehfinalarabic:65198,rehiragana:12428,rekatakana:12524,rekatakanahalfwid [...]
+resh:1512,reshdageshhebrew:64328,reshhebrew:1512,reversedtilde:8765,reviahebrew:1431,reviamugrashhebrew:1431,revlogicalnot:8976,rfishhook:638,rfishhookreversed:639,rhabengali:2525,rhadeva:2397,rho:961,rhook:637,rhookturned:635,rhookturnedsuperior:693,rhosymbolgreek:1009,rhotichookmod:734,rieulacirclekorean:12913,rieulaparenkorean:12817,rieulcirclekorean:12899,rieulhieuhkorean:12608,rieulkiyeokkorean:12602,rieulkiyeoksioskorean:12649,rieulkorean:12601,rieulmieumkorean:12603,rieulpansiosko [...]
+rieulparenkorean:12803,rieulphieuphkorean:12607,rieulpieupkorean:12604,rieulpieupsioskorean:12651,rieulsioskorean:12605,rieulthieuthkorean:12606,rieultikeutkorean:12650,rieulyeorinhieuhkorean:12653,rightangle:8735,righttackbelowcmb:793,righttriangle:8895,rihiragana:12426,rikatakana:12522,rikatakanahalfwidth:65432,ring:730,ringbelowcmb:805,ringcmb:778,ringhalfleft:703,ringhalfleftarmenian:1369,ringhalfleftbelowcmb:796,ringhalfleftcentered:723,ringhalfright:702,ringhalfrightbelowcmb:825,ri [...]
+rinvertedbreve:531,rittorusquare:13137,rlinebelow:7775,rlongleg:636,rlonglegturned:634,rmonospace:65362,rohiragana:12429,rokatakana:12525,rokatakanahalfwidth:65435,roruathai:3619,rparen:9389,rrabengali:2524,rradeva:2353,rragurmukhi:2652,rreharabic:1681,rrehfinalarabic:64397,rrvocalicbengali:2528,rrvocalicdeva:2400,rrvocalicgujarati:2784,rrvocalicvowelsignbengali:2500,rrvocalicvowelsigndeva:2372,rrvocalicvowelsigngujarati:2756,rsuperior:63217,rtblock:9616,rturned:633,rturnedsuperior:692,r [...]
+rukatakana:12523,rukatakanahalfwidth:65433,rupeemarkbengali:2546,rupeesignbengali:2547,rupiah:63197,ruthai:3620,rvocalicbengali:2443,rvocalicdeva:2315,rvocalicgujarati:2699,rvocalicvowelsignbengali:2499,rvocalicvowelsigndeva:2371,rvocalicvowelsigngujarati:2755,s:115,sabengali:2488,sacute:347,sacutedotaccent:7781,sadarabic:1589,sadeva:2360,sadfinalarabic:65210,sadinitialarabic:65211,sadmedialarabic:65212,sagujarati:2744,sagurmukhi:2616,sahiragana:12373,sakatakana:12469,sakatakanahalfwidth [...]
+samekh:1505,samekhdagesh:64321,samekhdageshhebrew:64321,samekhhebrew:1505,saraaathai:3634,saraaethai:3649,saraaimaimalaithai:3652,saraaimaimuanthai:3651,saraamthai:3635,saraathai:3632,saraethai:3648,saraiileftthai:63622,saraiithai:3637,saraileftthai:63621,saraithai:3636,saraothai:3650,saraueeleftthai:63624,saraueethai:3639,saraueleftthai:63623,sarauethai:3638,sarauthai:3640,sarauuthai:3641,sbopomofo:12569,scaron:353,scarondotaccent:7783,scedilla:351,schwa:601,schwacyrillic:1241,schwadier [...]
+schwahook:602,scircle:9442,scircumflex:349,scommaaccent:537,sdotaccent:7777,sdotbelow:7779,sdotbelowdotaccent:7785,seagullbelowcmb:828,second:8243,secondtonechinese:714,section:167,seenarabic:1587,seenfinalarabic:65202,seeninitialarabic:65203,seenmedialarabic:65204,segol:1462,segol13:1462,segol1f:1462,segol2c:1462,segolhebrew:1462,segolnarrowhebrew:1462,segolquarterhebrew:1462,segoltahebrew:1426,segolwidehebrew:1462,seharmenian:1405,sehiragana:12379,sekatakana:12475,sekatakanahalfwidth:6 [...]
+semicolonarabic:1563,semicolonmonospace:65307,semicolonsmall:65108,semivoicedmarkkana:12444,semivoicedmarkkanahalfwidth:65439,sentisquare:13090,sentosquare:13091,seven:55,sevenarabic:1639,sevenbengali:2541,sevencircle:9318,sevencircleinversesansserif:10128,sevendeva:2413,seveneighths:8542,sevengujarati:2797,sevengurmukhi:2669,sevenhackarabic:1639,sevenhangzhou:12327,sevenideographicparen:12838,seveninferior:8327,sevenmonospace:65303,sevenoldstyle:63287,sevenparen:9338,sevenperiod:9358,se [...]
+sevenroman:8566,sevensuperior:8311,seventeencircle:9328,seventeenparen:9348,seventeenperiod:9368,seventhai:3671,sfthyphen:173,shaarmenian:1399,shabengali:2486,shacyrillic:1096,shaddaarabic:1617,shaddadammaarabic:64609,shaddadammatanarabic:64606,shaddafathaarabic:64608,shaddakasraarabic:64610,shaddakasratanarabic:64607,shade:9618,shadedark:9619,shadelight:9617,shademedium:9618,shadeva:2358,shagujarati:2742,shagurmukhi:2614,shalshelethebrew:1427,shbopomofo:12565,shchacyrillic:1097,sheenara [...]
+sheenfinalarabic:65206,sheeninitialarabic:65207,sheenmedialarabic:65208,sheicoptic:995,sheqel:8362,sheqelhebrew:8362,sheva:1456,sheva115:1456,sheva15:1456,sheva22:1456,sheva2e:1456,shevahebrew:1456,shevanarrowhebrew:1456,shevaquarterhebrew:1456,shevawidehebrew:1456,shhacyrillic:1211,shimacoptic:1005,shin:1513,shindagesh:64329,shindageshhebrew:64329,shindageshshindot:64300,shindageshshindothebrew:64300,shindageshsindot:64301,shindageshsindothebrew:64301,shindothebrew:1473,shinhebrew:1513, [...]
+shinshindothebrew:64298,shinsindot:64299,shinsindothebrew:64299,shook:642,sigma:963,sigma1:962,sigmafinal:962,sigmalunatesymbolgreek:1010,sihiragana:12375,sikatakana:12471,sikatakanahalfwidth:65404,siluqhebrew:1469,siluqlefthebrew:1469,similar:8764,sindothebrew:1474,siosacirclekorean:12916,siosaparenkorean:12820,sioscieuckorean:12670,sioscirclekorean:12902,sioskiyeokkorean:12666,sioskorean:12613,siosnieunkorean:12667,siosparenkorean:12806,siospieupkorean:12669,siostikeutkorean:12668,six: [...]
+sixbengali:2540,sixcircle:9317,sixcircleinversesansserif:10127,sixdeva:2412,sixgujarati:2796,sixgurmukhi:2668,sixhackarabic:1638,sixhangzhou:12326,sixideographicparen:12837,sixinferior:8326,sixmonospace:65302,sixoldstyle:63286,sixparen:9337,sixperiod:9357,sixpersian:1782,sixroman:8565,sixsuperior:8310,sixteencircle:9327,sixteencurrencydenominatorbengali:2553,sixteenparen:9347,sixteenperiod:9367,sixthai:3670,slash:47,slashmonospace:65295,slong:383,slongdotaccent:7835,smileface:9786,smonos [...]
+sofpasuqhebrew:1475,softhyphen:173,softsigncyrillic:1100,sohiragana:12381,sokatakana:12477,sokatakanahalfwidth:65407,soliduslongoverlaycmb:824,solidusshortoverlaycmb:823,sorusithai:3625,sosalathai:3624,sosothai:3595,sosuathai:3626,space:32,spacehackarabic:32,spade:9824,spadesuitblack:9824,spadesuitwhite:9828,sparen:9390,squarebelowcmb:827,squarecc:13252,squarecm:13213,squarediagonalcrosshatchfill:9641,squarehorizontalfill:9636,squarekg:13199,squarekm:13214,squarekmcapital:13262,squareln: [...]
+squaremg:13198,squaremil:13269,squaremm:13212,squaremsquared:13217,squareorthogonalcrosshatchfill:9638,squareupperlefttolowerrightfill:9639,squareupperrighttolowerleftfill:9640,squareverticalfill:9637,squarewhitewithsmallblack:9635,srsquare:13275,ssabengali:2487,ssadeva:2359,ssagujarati:2743,ssangcieuckorean:12617,ssanghieuhkorean:12677,ssangieungkorean:12672,ssangkiyeokkorean:12594,ssangnieunkorean:12645,ssangpieupkorean:12611,ssangsioskorean:12614,ssangtikeutkorean:12600,ssuperior:6321 [...]
+sterlingmonospace:65505,strokelongoverlaycmb:822,strokeshortoverlaycmb:821,subset:8834,subsetnotequal:8842,subsetorequal:8838,succeeds:8827,suchthat:8715,suhiragana:12377,sukatakana:12473,sukatakanahalfwidth:65405,sukunarabic:1618,summation:8721,sun:9788,superset:8835,supersetnotequal:8843,supersetorequal:8839,svsquare:13276,syouwaerasquare:13180,t:116,tabengali:2468,tackdown:8868,tackleft:8867,tadeva:2340,tagujarati:2724,tagurmukhi:2596,taharabic:1591,tahfinalarabic:65218,tahinitialarab [...]
+tahiragana:12383,tahmedialarabic:65220,taisyouerasquare:13181,takatakana:12479,takatakanahalfwidth:65408,tatweelarabic:1600,tau:964,tav:1514,tavdages:64330,tavdagesh:64330,tavdageshhebrew:64330,tavhebrew:1514,tbar:359,tbopomofo:12554,tcaron:357,tccurl:680,tcedilla:355,tcheharabic:1670,tchehfinalarabic:64379,tchehinitialarabic:64380,tchehmedialarabic:64381,tcircle:9443,tcircumflexbelow:7793,tcommaaccent:355,tdieresis:7831,tdotaccent:7787,tdotbelow:7789,tecyrillic:1090,tedescendercyrillic: [...]
+tehfinalarabic:65174,tehhahinitialarabic:64674,tehhahisolatedarabic:64524,tehinitialarabic:65175,tehiragana:12390,tehjeeminitialarabic:64673,tehjeemisolatedarabic:64523,tehmarbutaarabic:1577,tehmarbutafinalarabic:65172,tehmedialarabic:65176,tehmeeminitialarabic:64676,tehmeemisolatedarabic:64526,tehnoonfinalarabic:64627,tekatakana:12486,tekatakanahalfwidth:65411,telephone:8481,telephoneblack:9742,telishagedolahebrew:1440,telishaqetanahebrew:1449,tencircle:9321,tenideographicparen:12841,te [...]
+tenperiod:9361,tenroman:8569,tesh:679,tet:1496,tetdagesh:64312,tetdageshhebrew:64312,tethebrew:1496,tetsecyrillic:1205,tevirhebrew:1435,tevirlefthebrew:1435,thabengali:2469,thadeva:2341,thagujarati:2725,thagurmukhi:2597,thalarabic:1584,thalfinalarabic:65196,thanthakhatlowleftthai:63640,thanthakhatlowrightthai:63639,thanthakhatthai:3660,thanthakhatupperleftthai:63638,theharabic:1579,thehfinalarabic:65178,thehinitialarabic:65179,thehmedialarabic:65180,thereexists:8707,therefore:8756,theta: [...]
+thetasymbolgreek:977,thieuthacirclekorean:12921,thieuthaparenkorean:12825,thieuthcirclekorean:12907,thieuthkorean:12620,thieuthparenkorean:12811,thirteencircle:9324,thirteenparen:9344,thirteenperiod:9364,thonangmonthothai:3601,thook:429,thophuthaothai:3602,thorn:254,thothahanthai:3607,thothanthai:3600,thothongthai:3608,thothungthai:3606,thousandcyrillic:1154,thousandsseparatorarabic:1644,thousandsseparatorpersian:1644,three:51,threearabic:1635,threebengali:2537,threecircle:9314,threecirc [...]
+threedeva:2409,threeeighths:8540,threegujarati:2793,threegurmukhi:2665,threehackarabic:1635,threehangzhou:12323,threeideographicparen:12834,threeinferior:8323,threemonospace:65299,threenumeratorbengali:2550,threeoldstyle:63283,threeparen:9334,threeperiod:9354,threepersian:1779,threequarters:190,threequartersemdash:63198,threeroman:8562,threesuperior:179,threethai:3667,thzsquare:13204,tihiragana:12385,tikatakana:12481,tikatakanahalfwidth:65409,tikeutacirclekorean:12912,tikeutaparenkorean: [...]
+tikeutkorean:12599,tikeutparenkorean:12802,tilde:732,tildebelowcmb:816,tildecmb:771,tildecomb:771,tildedoublecmb:864,tildeoperator:8764,tildeoverlaycmb:820,tildeverticalcmb:830,timescircle:8855,tipehahebrew:1430,tipehalefthebrew:1430,tippigurmukhi:2672,titlocyrilliccmb:1155,tiwnarmenian:1407,tlinebelow:7791,tmonospace:65364,toarmenian:1385,tohiragana:12392,tokatakana:12488,tokatakanahalfwidth:65412,tonebarextrahighmod:741,tonebarextralowmod:745,tonebarhighmod:742,tonebarlowmod:744,toneba [...]
+tonefive:445,tonesix:389,tonetwo:424,tonos:900,tonsquare:13095,topatakthai:3599,tortoiseshellbracketleft:12308,tortoiseshellbracketleftsmall:65117,tortoiseshellbracketleftvertical:65081,tortoiseshellbracketright:12309,tortoiseshellbracketrightsmall:65118,tortoiseshellbracketrightvertical:65082,totaothai:3605,tpalatalhook:427,tparen:9391,trademark:8482,trademarksans:63722,trademarkserif:63195,tretroflexhook:648,triagdn:9660,triaglf:9668,triagrt:9658,triagup:9650,ts:678,tsadi:1510,tsadidag [...]
+tsadidageshhebrew:64326,tsadihebrew:1510,tsecyrillic:1094,tsere:1461,tsere12:1461,tsere1e:1461,tsere2b:1461,tserehebrew:1461,tserenarrowhebrew:1461,tserequarterhebrew:1461,tserewidehebrew:1461,tshecyrillic:1115,tsuperior:63219,ttabengali:2463,ttadeva:2335,ttagujarati:2719,ttagurmukhi:2591,tteharabic:1657,ttehfinalarabic:64359,ttehinitialarabic:64360,ttehmedialarabic:64361,tthabengali:2464,tthadeva:2336,tthagujarati:2720,tthagurmukhi:2592,tturned:647,tuhiragana:12388,tukatakana:12484,tuka [...]
+tusmallhiragana:12387,tusmallkatakana:12483,tusmallkatakanahalfwidth:65391,twelvecircle:9323,twelveparen:9343,twelveperiod:9363,twelveroman:8571,twentycircle:9331,twentyhangzhou:21316,twentyparen:9351,twentyperiod:9371,two:50,twoarabic:1634,twobengali:2536,twocircle:9313,twocircleinversesansserif:10123,twodeva:2408,twodotenleader:8229,twodotleader:8229,twodotleadervertical:65072,twogujarati:2792,twogurmukhi:2664,twohackarabic:1634,twohangzhou:12322,twoideographicparen:12833,twoinferior:8 [...]
+twonumeratorbengali:2549,twooldstyle:63282,twoparen:9333,twoperiod:9353,twopersian:1778,tworoman:8561,twostroke:443,twosuperior:178,twothai:3666,twothirds:8532,u:117,uacute:250,ubar:649,ubengali:2441,ubopomofo:12584,ubreve:365,ucaron:468,ucircle:9444,ucircumflex:251,ucircumflexbelow:7799,ucyrillic:1091,udattadeva:2385,udblacute:369,udblgrave:533,udeva:2313,udieresis:252,udieresisacute:472,udieresisbelow:7795,udieresiscaron:474,udieresiscyrillic:1265,udieresisgrave:476,udieresismacron:470 [...]
+ugrave:249,ugujarati:2697,ugurmukhi:2569,uhiragana:12358,uhookabove:7911,uhorn:432,uhornacute:7913,uhorndotbelow:7921,uhorngrave:7915,uhornhookabove:7917,uhorntilde:7919,uhungarumlaut:369,uhungarumlautcyrillic:1267,uinvertedbreve:535,ukatakana:12454,ukatakanahalfwidth:65395,ukcyrillic:1145,ukorean:12636,umacron:363,umacroncyrillic:1263,umacrondieresis:7803,umatragurmukhi:2625,umonospace:65365,underscore:95,underscoredbl:8215,underscoremonospace:65343,underscorevertical:65075,underscorewa [...]
+union:8746,universal:8704,uogonek:371,uparen:9392,upblock:9600,upperdothebrew:1476,upsilon:965,upsilondieresis:971,upsilondieresistonos:944,upsilonlatin:650,upsilontonos:973,uptackbelowcmb:797,uptackmod:724,uragurmukhi:2675,uring:367,ushortcyrillic:1118,usmallhiragana:12357,usmallkatakana:12453,usmallkatakanahalfwidth:65385,ustraightcyrillic:1199,ustraightstrokecyrillic:1201,utilde:361,utildeacute:7801,utildebelow:7797,uubengali:2442,uudeva:2314,uugujarati:2698,uugurmukhi:2570,uumatragur [...]
+uuvowelsignbengali:2498,uuvowelsigndeva:2370,uuvowelsigngujarati:2754,uvowelsignbengali:2497,uvowelsigndeva:2369,uvowelsigngujarati:2753,v:118,vadeva:2357,vagujarati:2741,vagurmukhi:2613,vakatakana:12535,vav:1493,vavdagesh:64309,vavdagesh65:64309,vavdageshhebrew:64309,vavhebrew:1493,vavholam:64331,vavholamhebrew:64331,vavvavhebrew:1520,vavyodhebrew:1521,vcircle:9445,vdotbelow:7807,vecyrillic:1074,veharabic:1700,vehfinalarabic:64363,vehinitialarabic:64364,vehmedialarabic:64365,vekatakana: [...]
+verticalbar:124,verticallineabovecmb:781,verticallinebelowcmb:809,verticallinelowmod:716,verticallinemod:712,vewarmenian:1406,vhook:651,vikatakana:12536,viramabengali:2509,viramadeva:2381,viramagujarati:2765,visargabengali:2435,visargadeva:2307,visargagujarati:2691,vmonospace:65366,voarmenian:1400,voicediterationhiragana:12446,voicediterationkatakana:12542,voicedmarkkana:12443,voicedmarkkanahalfwidth:65438,vokatakana:12538,vparen:9393,vtilde:7805,vturned:652,vuhiragana:12436,vukatakana:1 [...]
+wacute:7811,waekorean:12633,wahiragana:12431,wakatakana:12527,wakatakanahalfwidth:65436,wakorean:12632,wasmallhiragana:12430,wasmallkatakana:12526,wattosquare:13143,wavedash:12316,wavyunderscorevertical:65076,wawarabic:1608,wawfinalarabic:65262,wawhamzaabovearabic:1572,wawhamzaabovefinalarabic:65158,wbsquare:13277,wcircle:9446,wcircumflex:373,wdieresis:7813,wdotaccent:7815,wdotbelow:7817,wehiragana:12433,weierstrass:8472,wekatakana:12529,wekorean:12638,weokorean:12637,wgrave:7809,whitebu [...]
+whitecircle:9675,whitecircleinverse:9689,whitecornerbracketleft:12302,whitecornerbracketleftvertical:65091,whitecornerbracketright:12303,whitecornerbracketrightvertical:65092,whitediamond:9671,whitediamondcontainingblacksmalldiamond:9672,whitedownpointingsmalltriangle:9663,whitedownpointingtriangle:9661,whiteleftpointingsmalltriangle:9667,whiteleftpointingtriangle:9665,whitelenticularbracketleft:12310,whitelenticularbracketright:12311,whiterightpointingsmalltriangle:9657,whiterightpointi [...]
+whitesmallsquare:9643,whitesmilingface:9786,whitesquare:9633,whitestar:9734,whitetelephone:9743,whitetortoiseshellbracketleft:12312,whitetortoiseshellbracketright:12313,whiteuppointingsmalltriangle:9653,whiteuppointingtriangle:9651,wihiragana:12432,wikatakana:12528,wikorean:12639,wmonospace:65367,wohiragana:12434,wokatakana:12530,wokatakanahalfwidth:65382,won:8361,wonmonospace:65510,wowaenthai:3623,wparen:9394,wring:7832,wsuperior:695,wturned:653,wynn:447,x:120,xabovecmb:829,xbopomofo:12 [...]
+xdieresis:7821,xdotaccent:7819,xeharmenian:1389,xi:958,xmonospace:65368,xparen:9395,xsuperior:739,y:121,yaadosquare:13134,yabengali:2479,yacute:253,yadeva:2351,yaekorean:12626,yagujarati:2735,yagurmukhi:2607,yahiragana:12420,yakatakana:12516,yakatakanahalfwidth:65428,yakorean:12625,yamakkanthai:3662,yasmallhiragana:12419,yasmallkatakana:12515,yasmallkatakanahalfwidth:65388,yatcyrillic:1123,ycircle:9448,ycircumflex:375,ydieresis:255,ydotaccent:7823,ydotbelow:7925,yeharabic:1610,yehbarreea [...]
+yehbarreefinalarabic:64431,yehfinalarabic:65266,yehhamzaabovearabic:1574,yehhamzaabovefinalarabic:65162,yehhamzaaboveinitialarabic:65163,yehhamzaabovemedialarabic:65164,yehinitialarabic:65267,yehmedialarabic:65268,yehmeeminitialarabic:64733,yehmeemisolatedarabic:64600,yehnoonfinalarabic:64660,yehthreedotsbelowarabic:1745,yekorean:12630,yen:165,yenmonospace:65509,yeokorean:12629,yeorinhieuhkorean:12678,yerahbenyomohebrew:1450,yerahbenyomolefthebrew:1450,yericyrillic:1099,yerudieresiscyril [...]
+yesieungkorean:12673,yesieungpansioskorean:12675,yesieungsioskorean:12674,yetivhebrew:1434,ygrave:7923,yhook:436,yhookabove:7927,yiarmenian:1397,yicyrillic:1111,yikorean:12642,yinyang:9775,yiwnarmenian:1410,ymonospace:65369,yod:1497,yoddagesh:64313,yoddageshhebrew:64313,yodhebrew:1497,yodyodhebrew:1522,yodyodpatahhebrew:64287,yohiragana:12424,yoikorean:12681,yokatakana:12520,yokatakanahalfwidth:65430,yokorean:12635,yosmallhiragana:12423,yosmallkatakana:12519,yosmallkatakanahalfwidth:6539 [...]
+yoyaekorean:12680,yoyakorean:12679,yoyakthai:3618,yoyingthai:3597,yparen:9396,ypogegrammeni:890,ypogegrammenigreekcmb:837,yr:422,yring:7833,ysuperior:696,ytilde:7929,yturned:654,yuhiragana:12422,yuikorean:12684,yukatakana:12518,yukatakanahalfwidth:65429,yukorean:12640,yusbigcyrillic:1131,yusbigiotifiedcyrillic:1133,yuslittlecyrillic:1127,yuslittleiotifiedcyrillic:1129,yusmallhiragana:12421,yusmallkatakana:12517,yusmallkatakanahalfwidth:65389,yuyekorean:12683,yuyeokorean:12682,yyabengali: [...]
+z:122,zaarmenian:1382,zacute:378,zadeva:2395,zagurmukhi:2651,zaharabic:1592,zahfinalarabic:65222,zahinitialarabic:65223,zahiragana:12374,zahmedialarabic:65224,zainarabic:1586,zainfinalarabic:65200,zakatakana:12470,zaqefgadolhebrew:1429,zaqefqatanhebrew:1428,zarqahebrew:1432,zayin:1494,zayindagesh:64310,zayindageshhebrew:64310,zayinhebrew:1494,zbopomofo:12567,zcaron:382,zcircle:9449,zcircumflex:7825,zcurl:657,zdot:380,zdotaccent:380,zdotbelow:7827,zecyrillic:1079,zedescendercyrillic:1177, [...]
+zehiragana:12380,zekatakana:12476,zero:48,zeroarabic:1632,zerobengali:2534,zerodeva:2406,zerogujarati:2790,zerogurmukhi:2662,zerohackarabic:1632,zeroinferior:8320,zeromonospace:65296,zerooldstyle:63280,zeropersian:1776,zerosuperior:8304,zerothai:3664,zerowidthjoiner:65279,zerowidthnonjoiner:8204,zerowidthspace:8203,zeta:950,zhbopomofo:12563,zhearmenian:1386,zhebrevecyrillic:1218,zhecyrillic:1078,zhedescendercyrillic:1175,zhedieresiscyrillic:1245,zihiragana:12376,zikatakana:12472,zinorheb [...]
+zlinebelow:7829,zmonospace:65370,zohiragana:12382,zokatakana:12478,zparen:9397,zretroflexhook:656,zstroke:438,zuhiragana:12378,zukatakana:12474,".notdef":0};"use strict";var kd=function(){function d(a,c,e,f,d){if(f instanceof jb&&f.isNativelyDecodable(c,e)){var i=f.dict.get("ColorSpace","CS"),i=ia.parse(i,c,e),c=i.numComps;a.send("JpegDecode",[f.getIR(),c],function(a){a=a.data;a=new Aa(a,0,a.length,f.dict);d.resolve(a)})}else d.resolve(f)}function a(b,c,e,f,d){this.image=e;e.getParams&&e [...]
+f=e.dict;this.width=f.get("Width","W");this.height=f.get("Height","H");(1>this.width||1>this.height)&&r("Invalid image width: "+this.width+" or height: "+this.height);this.interpolate=f.get("Interpolate","I")||!1;this.imageMask=f.get("ImageMask","IM")||!1;e=e.bitsPerComponent;e||(e=f.get("BitsPerComponent","BPC"))||(this.imageMask?e=1:r("Bits per component missing in image: "+this.imageMask));this.bpc=e;if(!this.imageMask){var i=f.get("ColorSpace","CS");i||(ea('JPX images (which don"t re [...]
+i=new wa("DeviceRGB"));this.colorSpace=ia.parse(i,b,c);this.numComps=this.colorSpace.numComps}this.decode=f.get("Decode","D");this.needsDecode=!1;if(this.decode&&this.colorSpace&&!this.colorSpace.isDefaultDecode(this.decode)){this.needsDecode=!0;e=(1<<e)-1;this.decodeCoefficients=[];this.decodeAddends=[];for(var k=i=0;i<this.decode.length;i+=2,++k){var j=this.decode[i];this.decodeCoefficients[k]=this.decode[i+1]-j;this.decodeAddends[k]=e*j}}f.get("Mask")?ea("masked images"):d&&(this.smas [...]
+c,d,!1))}a.buildImage=function(b,c,e,f,g,i){var k=new pa,j=new pa;pa.all([k,j]).then(function(c){c=new a(e,f,c[0],i,c[1]);b(c)});d(c,e,f,g,k);(g=g.dict.get("SMask"))?d(c,e,f,g,j):j.resolve(null)};a.resize=function(a,c,e,f,d,i,k){for(var j=i*k*e,c=8>=c?new Uint8Array(j):16>=c?new Uint16Array(j):new Uint32Array(j),j=f/i,d=d/k,h,m,l,n=0;n<k;n++)for(var o=0;o<i;o++)h=Math.floor(o*j),m=Math.floor(n*d),l=n*i+o,h=m*f+h,1===e?c[l]=a[h]:3===e&&(l*=3,h*=3,c[l]=a[h],c[l+1]=a[h+1],c[l+2]=a[h+2]);ret [...]
+{get drawWidth(){return!this.smask?this.width:Math.max(this.width,this.smask.width)},get drawHeight(){return!this.smask?this.height:Math.max(this.height,this.smask.height)},getComponents:function(a){var c=this.bpc,e=this.needsDecode,f=this.decode;if(8==c&&!e)return a;var d=this.width,i=this.numComps,k=d*this.height*i,j=0,h=8>=c?new Uint8Array(k):16>=c?new Uint16Array(k):new Uint32Array(k),d=d*i,m,l;e&&(m=this.decodeAddends,l=this.decodeCoefficients);var n=(1<<c)-1;if(8==c)for(f=0;f<k;++f [...]
+i,p=a[f],j=p,j=m[o]+j*l[o],p=0>j?0:j>n?n:j;h[f]=p}else if(1==c){i=0;m=1;f&&(i=f[0]?1:0,m=f[1]?1:0);for(var q=l=0,f=0;f<k;++f)0==f%d?q=l=0:l>>=1,0>=l&&(q=a[j++],l=128),h[f]=!(q&l)?i:m}else for(f=q=o=0;f<k;++f){0==f%d&&(o=q=0);for(;o<c;)q=q<<8|a[j++],o+=8;var u=o-c,p=q>>u;e&&(o=f%i,p=m[o]+p*l[o],p=0>p?0:p>n?n:p);h[f]=p;q&=(1<<u)-1;o=u}return h},getOpacity:function(b,c){var e=this.smask,f;if(e){var d=e.width,i=e.height;f=new Uint8Array(d*i);e.fillGrayBuffer(f);if(d!=b||i!=c)f=a.resize(f,e.b [...]
+b,c)}else{f=new Uint8Array(b*c);e=0;for(d=b*c;e<d;++e)f[e]=255}return f},applyStencilMask:function(a,c){var e=this.width,f=this.height,d=this.getImageBytes((e+7>>3)*f),i=0,k,j,h,m,l=3;for(k=0;k<f;k++)for(j=h=0;j<e;j++)h||(m=d[i++],h=128),!(m&h)==c&&(a[l]=0),l+=4,h>>=1},fillRgbaBuffer:function(b,c,e){var f=this.width,d=this.height,i=this.bpc,i=this.colorSpace.getRgbBuffer(this.getComponents(this.getImageBytes(d*(f*this.numComps*i+7>>3))),i);if(f!=c||d!=e)i=a.resize(i,this.bpc,3,f,d,c,e);f [...]
+0,d=this.getOpacity(c,e),k=0,c=4*c*e,e=0;e<c;e+=4)b[e]=i[f++],b[e+1]=i[f++],b[e+2]=i[f++],b[e+3]=d[k++]},fillGrayBuffer:function(a){var c=this.numComps;1!=c&&r("Reading gray scale from a color image: "+c);for(var e=this.width,f=this.height,d=this.bpc,c=this.getComponents(this.getImageBytes(f*(e*c*d+7>>3))),e=e*f,d=255/((1<<d)-1),f=0;f<e;++f)a[f]=d*c[f]|0},getImageBytes:function(a){this.image.reset();return this.image.getBytes(a)}};return a}();"use strict";var ld={Courier:600,"Courier-Bol [...]
+"Courier-Oblique":600,Helvetica:{space:278,exclam:278,quotedbl:355,numbersign:556,dollar:556,percent:889,ampersand:667,quoteright:222,parenleft:333,parenright:333,asterisk:389,plus:584,comma:278,hyphen:333,period:278,slash:278,zero:556,one:556,two:556,three:556,four:556,five:556,six:556,seven:556,eight:556,nine:556,colon:278,semicolon:278,less:584,equal:584,greater:584,question:556,at:1015,A:667,B:667,C:722,D:722,E:667,F:611,G:778,H:722,I:278,J:500,K:667,L:556,M:833,N:722,O:778,P:667,Q:7 [...]
+S:667,T:611,U:722,V:667,W:944,X:667,Y:667,Z:611,bracketleft:278,backslash:278,bracketright:278,asciicircum:469,underscore:556,quoteleft:222,a:556,b:556,c:500,d:556,e:556,f:278,g:556,h:556,i:222,j:222,k:500,l:222,m:833,n:556,o:556,p:556,q:556,r:333,s:500,t:278,u:556,v:500,w:722,x:500,y:500,z:500,braceleft:334,bar:260,braceright:334,asciitilde:584,exclamdown:333,cent:556,sterling:556,fraction:167,yen:556,florin:556,section:556,currency:556,quotesingle:191,quotedblleft:333,guillemotleft:556 [...]
+guilsinglright:333,fi:500,fl:500,endash:556,dagger:556,daggerdbl:556,periodcentered:278,paragraph:537,bullet:350,quotesinglbase:222,quotedblbase:333,quotedblright:333,guillemotright:556,ellipsis:1E3,perthousand:1E3,questiondown:611,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:1E3,ordfeminine:370,Lslash:556,Oslash:778,OE:1E3,ordmasculine:365,ae:889,dotlessi:278,lslash:2 [...]
+oe:944,germandbls:611,Idieresis:278,eacute:556,abreve:556,uhungarumlaut:556,ecaron:556,Ydieresis:667,divide:584,Yacute:667,Acircumflex:667,aacute:556,Ucircumflex:722,yacute:500,scommaaccent:500,ecircumflex:556,Uring:722,Udieresis:722,aogonek:556,Uacute:722,uogonek:556,Edieresis:667,Dcroat:722,commaaccent:250,copyright:737,Emacron:667,ccaron:500,aring:556,Ncommaaccent:722,lacute:222,agrave:556,Tcommaaccent:611,Cacute:722,atilde:556,Edotaccent:667,scaron:500,scedilla:500,iacute:278,lozenge [...]
+Gcommaaccent:778,ucircumflex:556,acircumflex:556,Amacron:667,rcaron:333,ccedilla:500,Zdotaccent:611,Thorn:667,Omacron:778,Racute:722,Sacute:667,dcaron:643,Umacron:722,uring:556,threesuperior:333,Ograve:778,Agrave:667,Abreve:667,multiply:584,uacute:556,Tcaron:611,partialdiff:476,ydieresis:500,Nacute:722,icircumflex:278,Ecircumflex:667,adieresis:556,edieresis:556,cacute:500,nacute:556,umacron:556,Ncaron:722,Iacute:278,plusminus:584,brokenbar:260,registered:737,Gbreve:778,Idotaccent:278,sum [...]
+Egrave:667,racute:333,omacron:556,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:722,lcommaaccent:222,tcaron:317,eogonek:556,Uogonek:722,Aacute:667,Adieresis:667,egrave:556,zacute:500,iogonek:222,Oacute:778,oacute:556,amacron:556,sacute:500,idieresis:278,Ocircumflex:778,Ugrave:722,Delta:612,thorn:556,twosuperior:333,Odieresis:778,mu:556,igrave:278,ohungarumlaut:556,Eogonek:667,dcroat:556,threequarters:834,Scedilla:667,lcaron:299,Kcommaaccent:667,Lacute:556,trademark:1E3,edotacce [...]
+Imacron:278,Lcaron:556,onehalf:834,lessequal:549,ocircumflex:556,ntilde:556,Uhungarumlaut:722,Eacute:667,emacron:556,gbreve:556,onequarter:834,Scaron:667,Scommaaccent:667,Ohungarumlaut:778,degree:400,ograve:556,Ccaron:722,ugrave:556,radical:453,Dcaron:722,rcommaaccent:333,Ntilde:722,otilde:556,Rcommaaccent:722,Lcommaaccent:556,Atilde:667,Aogonek:667,Aring:667,Otilde:778,zdotaccent:500,Ecaron:667,Iogonek:278,kcommaaccent:500,minus:584,Icircumflex:278,ncaron:556,tcommaaccent:278,logicalnot [...]
+udieresis:556,notequal:549,gcommaaccent:556,eth:556,zcaron:500,ncommaaccent:556,onesuperior:333,imacron:278,Euro:556},"Helvetica-Bold":{space:278,exclam:333,quotedbl:474,numbersign:556,dollar:556,percent:889,ampersand:722,quoteright:278,parenleft:333,parenright:333,asterisk:389,plus:584,comma:278,hyphen:333,period:278,slash:278,zero:556,one:556,two:556,three:556,four:556,five:556,six:556,seven:556,eight:556,nine:556,colon:333,semicolon:333,less:584,equal:584,greater:584,question:611,at:9 [...]
+B:722,C:722,D:722,E:667,F:611,G:778,H:722,I:278,J:556,K:722,L:611,M:833,N:722,O:778,P:667,Q:778,R:722,S:667,T:611,U:722,V:667,W:944,X:667,Y:667,Z:611,bracketleft:333,backslash:278,bracketright:333,asciicircum:584,underscore:556,quoteleft:278,a:556,b:611,c:556,d:611,e:556,f:333,g:611,h:611,i:278,j:278,k:556,l:278,m:889,n:611,o:611,p:611,q:611,r:389,s:556,t:333,u:611,v:556,w:778,x:556,y:556,z:500,braceleft:389,bar:280,braceright:389,asciitilde:584,exclamdown:333,cent:556,sterling:556,fract [...]
+florin:556,section:556,currency:556,quotesingle:238,quotedblleft:500,guillemotleft:556,guilsinglleft:333,guilsinglright:333,fi:611,fl:611,endash:556,dagger:556,daggerdbl:556,periodcentered:278,paragraph:556,bullet:350,quotesinglbase:278,quotedblbase:500,quotedblright:500,guillemotright:556,ellipsis:1E3,perthousand:1E3,questiondown:611,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emd [...]
+ordfeminine:370,Lslash:611,Oslash:778,OE:1E3,ordmasculine:365,ae:889,dotlessi:278,lslash:278,oslash:611,oe:944,germandbls:611,Idieresis:278,eacute:556,abreve:556,uhungarumlaut:611,ecaron:556,Ydieresis:667,divide:584,Yacute:667,Acircumflex:722,aacute:556,Ucircumflex:722,yacute:556,scommaaccent:556,ecircumflex:556,Uring:722,Udieresis:722,aogonek:556,Uacute:722,uogonek:611,Edieresis:667,Dcroat:722,commaaccent:250,copyright:737,Emacron:667,ccaron:556,aring:556,Ncommaaccent:722,lacute:278,agr [...]
+Cacute:722,atilde:556,Edotaccent:667,scaron:556,scedilla:556,iacute:278,lozenge:494,Rcaron:722,Gcommaaccent:778,ucircumflex:611,acircumflex:556,Amacron:722,rcaron:389,ccedilla:556,Zdotaccent:611,Thorn:667,Omacron:778,Racute:722,Sacute:667,dcaron:743,Umacron:722,uring:611,threesuperior:333,Ograve:778,Agrave:722,Abreve:722,multiply:584,uacute:611,Tcaron:611,partialdiff:494,ydieresis:556,Nacute:722,icircumflex:278,Ecircumflex:667,adieresis:556,edieresis:556,cacute:556,nacute:611,umacron:611 [...]
+Iacute:278,plusminus:584,brokenbar:280,registered:737,Gbreve:778,Idotaccent:278,summation:600,Egrave:667,racute:389,omacron:611,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:722,lcommaaccent:278,tcaron:389,eogonek:556,Uogonek:722,Aacute:722,Adieresis:722,egrave:556,zacute:500,iogonek:278,Oacute:778,oacute:611,amacron:556,sacute:556,idieresis:278,Ocircumflex:778,Ugrave:722,Delta:612,thorn:611,twosuperior:333,Odieresis:778,mu:611,igrave:278,ohungarumlaut:611,Eogonek:667,dcroat:61 [...]
+Scedilla:667,lcaron:400,Kcommaaccent:722,Lacute:611,trademark:1E3,edotaccent:556,Igrave:278,Imacron:278,Lcaron:611,onehalf:834,lessequal:549,ocircumflex:611,ntilde:611,Uhungarumlaut:722,Eacute:667,emacron:556,gbreve:611,onequarter:834,Scaron:667,Scommaaccent:667,Ohungarumlaut:778,degree:400,ograve:611,Ccaron:722,ugrave:611,radical:549,Dcaron:722,rcommaaccent:389,Ntilde:722,otilde:611,Rcommaaccent:722,Lcommaaccent:611,Atilde:722,Aogonek:722,Aring:722,Otilde:778,zdotaccent:500,Ecaron:667,I [...]
+kcommaaccent:556,minus:584,Icircumflex:278,ncaron:611,tcommaaccent:333,logicalnot:584,odieresis:611,udieresis:611,notequal:549,gcommaaccent:611,eth:611,zcaron:500,ncommaaccent:611,onesuperior:333,imacron:278,Euro:556},"Helvetica-BoldOblique":{space:278,exclam:333,quotedbl:474,numbersign:556,dollar:556,percent:889,ampersand:722,quoteright:278,parenleft:333,parenright:333,asterisk:389,plus:584,comma:278,hyphen:333,period:278,slash:278,zero:556,one:556,two:556,three:556,four:556,five:556,si [...]
+eight:556,nine:556,colon:333,semicolon:333,less:584,equal:584,greater:584,question:611,at:975,A:722,B:722,C:722,D:722,E:667,F:611,G:778,H:722,I:278,J:556,K:722,L:611,M:833,N:722,O:778,P:667,Q:778,R:722,S:667,T:611,U:722,V:667,W:944,X:667,Y:667,Z:611,bracketleft:333,backslash:278,bracketright:333,asciicircum:584,underscore:556,quoteleft:278,a:556,b:611,c:556,d:611,e:556,f:333,g:611,h:611,i:278,j:278,k:556,l:278,m:889,n:611,o:611,p:611,q:611,r:389,s:556,t:333,u:611,v:556,w:778,x:556,y:556, [...]
+bar:280,braceright:389,asciitilde:584,exclamdown:333,cent:556,sterling:556,fraction:167,yen:556,florin:556,section:556,currency:556,quotesingle:238,quotedblleft:500,guillemotleft:556,guilsinglleft:333,guilsinglright:333,fi:611,fl:611,endash:556,dagger:556,daggerdbl:556,periodcentered:278,paragraph:556,bullet:350,quotesinglbase:278,quotedblbase:500,quotedblright:500,guillemotright:556,ellipsis:1E3,perthousand:1E3,questiondown:611,grave:333,acute:333,circumflex:333,tilde:333,macron:333,bre [...]
+dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:1E3,ordfeminine:370,Lslash:611,Oslash:778,OE:1E3,ordmasculine:365,ae:889,dotlessi:278,lslash:278,oslash:611,oe:944,germandbls:611,Idieresis:278,eacute:556,abreve:556,uhungarumlaut:611,ecaron:556,Ydieresis:667,divide:584,Yacute:667,Acircumflex:722,aacute:556,Ucircumflex:722,yacute:556,scommaaccent:556,ecircumflex:556,Uring:722,Udieresis:722,aogonek:556,Uacute:722,uogonek:611,Edieresis:667,Dcroat:722,comm [...]
+copyright:737,Emacron:667,ccaron:556,aring:556,Ncommaaccent:722,lacute:278,agrave:556,Tcommaaccent:611,Cacute:722,atilde:556,Edotaccent:667,scaron:556,scedilla:556,iacute:278,lozenge:494,Rcaron:722,Gcommaaccent:778,ucircumflex:611,acircumflex:556,Amacron:722,rcaron:389,ccedilla:556,Zdotaccent:611,Thorn:667,Omacron:778,Racute:722,Sacute:667,dcaron:743,Umacron:722,uring:611,threesuperior:333,Ograve:778,Agrave:722,Abreve:722,multiply:584,uacute:611,Tcaron:611,partialdiff:494,ydieresis:556,N [...]
+icircumflex:278,Ecircumflex:667,adieresis:556,edieresis:556,cacute:556,nacute:611,umacron:611,Ncaron:722,Iacute:278,plusminus:584,brokenbar:280,registered:737,Gbreve:778,Idotaccent:278,summation:600,Egrave:667,racute:389,omacron:611,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:722,lcommaaccent:278,tcaron:389,eogonek:556,Uogonek:722,Aacute:722,Adieresis:722,egrave:556,zacute:500,iogonek:278,Oacute:778,oacute:611,amacron:556,sacute:556,idieresis:278,Ocircumflex:778,Ugrave:722,De [...]
+twosuperior:333,Odieresis:778,mu:611,igrave:278,ohungarumlaut:611,Eogonek:667,dcroat:611,threequarters:834,Scedilla:667,lcaron:400,Kcommaaccent:722,Lacute:611,trademark:1E3,edotaccent:556,Igrave:278,Imacron:278,Lcaron:611,onehalf:834,lessequal:549,ocircumflex:611,ntilde:611,Uhungarumlaut:722,Eacute:667,emacron:556,gbreve:611,onequarter:834,Scaron:667,Scommaaccent:667,Ohungarumlaut:778,degree:400,ograve:611,Ccaron:722,ugrave:611,radical:549,Dcaron:722,rcommaaccent:389,Ntilde:722,otilde:61 [...]
+Lcommaaccent:611,Atilde:722,Aogonek:722,Aring:722,Otilde:778,zdotaccent:500,Ecaron:667,Iogonek:278,kcommaaccent:556,minus:584,Icircumflex:278,ncaron:611,tcommaaccent:333,logicalnot:584,odieresis:611,udieresis:611,notequal:549,gcommaaccent:611,eth:611,zcaron:500,ncommaaccent:611,onesuperior:333,imacron:278,Euro:556},"Helvetica-Oblique":{space:278,exclam:278,quotedbl:355,numbersign:556,dollar:556,percent:889,ampersand:667,quoteright:222,parenleft:333,parenright:333,asterisk:389,plus:584,co [...]
+period:278,slash:278,zero:556,one:556,two:556,three:556,four:556,five:556,six:556,seven:556,eight:556,nine:556,colon:278,semicolon:278,less:584,equal:584,greater:584,question:556,at:1015,A:667,B:667,C:722,D:722,E:667,F:611,G:778,H:722,I:278,J:500,K:667,L:556,M:833,N:722,O:778,P:667,Q:778,R:722,S:667,T:611,U:722,V:667,W:944,X:667,Y:667,Z:611,bracketleft:278,backslash:278,bracketright:278,asciicircum:469,underscore:556,quoteleft:222,a:556,b:556,c:500,d:556,e:556,f:278,g:556,h:556,i:222,j:2 [...]
+l:222,m:833,n:556,o:556,p:556,q:556,r:333,s:500,t:278,u:556,v:500,w:722,x:500,y:500,z:500,braceleft:334,bar:260,braceright:334,asciitilde:584,exclamdown:333,cent:556,sterling:556,fraction:167,yen:556,florin:556,section:556,currency:556,quotesingle:191,quotedblleft:333,guillemotleft:556,guilsinglleft:333,guilsinglright:333,fi:500,fl:500,endash:556,dagger:556,daggerdbl:556,periodcentered:278,paragraph:537,bullet:350,quotesinglbase:222,quotedblbase:333,quotedblright:333,guillemotright:556,e [...]
+perthousand:1E3,questiondown:611,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:1E3,ordfeminine:370,Lslash:556,Oslash:778,OE:1E3,ordmasculine:365,ae:889,dotlessi:278,lslash:222,oslash:611,oe:944,germandbls:611,Idieresis:278,eacute:556,abreve:556,uhungarumlaut:556,ecaron:556,Ydieresis:667,divide:584,Yacute:667,Acircumflex:667,aacute:556,Ucircumflex:722,yacute:500,scommaac [...]
+ecircumflex:556,Uring:722,Udieresis:722,aogonek:556,Uacute:722,uogonek:556,Edieresis:667,Dcroat:722,commaaccent:250,copyright:737,Emacron:667,ccaron:500,aring:556,Ncommaaccent:722,lacute:222,agrave:556,Tcommaaccent:611,Cacute:722,atilde:556,Edotaccent:667,scaron:500,scedilla:500,iacute:278,lozenge:471,Rcaron:722,Gcommaaccent:778,ucircumflex:556,acircumflex:556,Amacron:667,rcaron:333,ccedilla:500,Zdotaccent:611,Thorn:667,Omacron:778,Racute:722,Sacute:667,dcaron:643,Umacron:722,uring:556,t [...]
+Ograve:778,Agrave:667,Abreve:667,multiply:584,uacute:556,Tcaron:611,partialdiff:476,ydieresis:500,Nacute:722,icircumflex:278,Ecircumflex:667,adieresis:556,edieresis:556,cacute:500,nacute:556,umacron:556,Ncaron:722,Iacute:278,plusminus:584,brokenbar:260,registered:737,Gbreve:778,Idotaccent:278,summation:600,Egrave:667,racute:333,omacron:556,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:722,lcommaaccent:222,tcaron:317,eogonek:556,Uogonek:722,Aacute:667,Adieresis:667,egrave:556,za [...]
+iogonek:222,Oacute:778,oacute:556,amacron:556,sacute:500,idieresis:278,Ocircumflex:778,Ugrave:722,Delta:612,thorn:556,twosuperior:333,Odieresis:778,mu:556,igrave:278,ohungarumlaut:556,Eogonek:667,dcroat:556,threequarters:834,Scedilla:667,lcaron:299,Kcommaaccent:667,Lacute:556,trademark:1E3,edotaccent:556,Igrave:278,Imacron:278,Lcaron:556,onehalf:834,lessequal:549,ocircumflex:556,ntilde:556,Uhungarumlaut:722,Eacute:667,emacron:556,gbreve:556,onequarter:834,Scaron:667,Scommaaccent:667,Ohun [...]
+degree:400,ograve:556,Ccaron:722,ugrave:556,radical:453,Dcaron:722,rcommaaccent:333,Ntilde:722,otilde:556,Rcommaaccent:722,Lcommaaccent:556,Atilde:667,Aogonek:667,Aring:667,Otilde:778,zdotaccent:500,Ecaron:667,Iogonek:278,kcommaaccent:500,minus:584,Icircumflex:278,ncaron:556,tcommaaccent:278,logicalnot:584,odieresis:556,udieresis:556,notequal:549,gcommaaccent:556,eth:556,zcaron:500,ncommaaccent:556,onesuperior:333,imacron:278,Euro:556},Symbol:{space:250,exclam:333,universal:713,numbersig [...]
+percent:833,ampersand:778,suchthat:439,parenleft:333,parenright:333,asteriskmath:500,plus:549,comma:250,minus:549,period:250,slash:278,zero:500,one:500,two:500,three:500,four:500,five:500,six:500,seven:500,eight:500,nine:500,colon:278,semicolon:278,less:549,equal:549,greater:549,question:444,congruent:549,Alpha:722,Beta:667,Chi:722,Delta:612,Epsilon:611,Phi:763,Gamma:603,Eta:722,Iota:333,theta1:631,Kappa:722,Lambda:686,Mu:889,Nu:722,Omicron:722,Pi:768,Theta:741,Rho:556,Sigma:592,Tau:611, [...]
+sigma1:439,Omega:768,Xi:645,Psi:795,Zeta:611,bracketleft:333,therefore:863,bracketright:333,perpendicular:658,underscore:500,radicalex:500,alpha:631,beta:549,chi:549,delta:494,epsilon:439,phi:521,gamma:411,eta:603,iota:329,phi1:603,kappa:549,lambda:549,mu:576,nu:521,omicron:549,pi:549,theta:521,rho:549,sigma:603,tau:439,upsilon:576,omega1:713,omega:686,xi:493,psi:686,zeta:494,braceleft:480,bar:200,braceright:480,similar:549,Euro:750,Upsilon1:620,minute:247,lessequal:549,fraction:167,infi [...]
+florin:500,club:753,diamond:753,heart:753,spade:753,arrowboth:1042,arrowleft:987,arrowup:603,arrowright:987,arrowdown:603,degree:400,plusminus:549,second:411,greaterequal:549,multiply:549,proportional:713,partialdiff:494,bullet:460,divide:549,notequal:549,equivalence:549,approxequal:549,ellipsis:1E3,arrowvertex:603,arrowhorizex:1E3,carriagereturn:658,aleph:823,Ifraktur:686,Rfraktur:795,weierstrass:987,circlemultiply:768,circleplus:768,emptyset:823,intersection:768,union:768,propersuperse [...]
+notsubset:713,propersubset:713,reflexsubset:713,element:713,notelement:713,angle:768,gradient:713,registerserif:790,copyrightserif:790,trademarkserif:890,product:823,radical:549,dotmath:250,logicalnot:713,logicaland:603,logicalor:603,arrowdblboth:1042,arrowdblleft:987,arrowdblup:603,arrowdblright:987,arrowdbldown:603,lozenge:494,angleleft:329,registersans:790,copyrightsans:790,trademarksans:786,summation:713,parenlefttp:384,parenleftex:384,parenleftbt:384,bracketlefttp:384,bracketleftex: [...]
+bracelefttp:494,braceleftmid:494,braceleftbt:494,braceex:494,angleright:329,integral:274,integraltp:686,integralex:686,integralbt:686,parenrighttp:384,parenrightex:384,parenrightbt:384,bracketrighttp:384,bracketrightex:384,bracketrightbt:384,bracerighttp:494,bracerightmid:494,bracerightbt:494,apple:790},"Times-Roman":{space:250,exclam:333,quotedbl:408,numbersign:500,dollar:500,percent:833,ampersand:778,quoteright:333,parenleft:333,parenright:333,asterisk:500,plus:564,comma:250,hyphen:333 [...]
+slash:278,zero:500,one:500,two:500,three:500,four:500,five:500,six:500,seven:500,eight:500,nine:500,colon:278,semicolon:278,less:564,equal:564,greater:564,question:444,at:921,A:722,B:667,C:667,D:722,E:611,F:556,G:722,H:722,I:333,J:389,K:722,L:611,M:889,N:722,O:722,P:556,Q:722,R:667,S:556,T:611,U:722,V:722,W:944,X:722,Y:722,Z:611,bracketleft:333,backslash:278,bracketright:333,asciicircum:469,underscore:500,quoteleft:333,a:444,b:500,c:444,d:500,e:444,f:333,g:500,h:500,i:278,j:278,k:500,l:2 [...]
+n:500,o:500,p:500,q:500,r:333,s:389,t:278,u:500,v:500,w:722,x:500,y:500,z:444,braceleft:480,bar:200,braceright:480,asciitilde:541,exclamdown:333,cent:500,sterling:500,fraction:167,yen:500,florin:500,section:500,currency:500,quotesingle:180,quotedblleft:444,guillemotleft:500,guilsinglleft:333,guilsinglright:333,fi:556,fl:556,endash:500,dagger:500,daggerdbl:500,periodcentered:250,paragraph:453,bullet:350,quotesinglbase:333,quotedblbase:444,quotedblright:444,guillemotright:500,ellipsis:1E3, [...]
+questiondown:444,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:889,ordfeminine:276,Lslash:611,Oslash:722,OE:889,ordmasculine:310,ae:667,dotlessi:278,lslash:278,oslash:500,oe:722,germandbls:500,Idieresis:333,eacute:444,abreve:444,uhungarumlaut:500,ecaron:444,Ydieresis:722,divide:564,Yacute:722,Acircumflex:722,aacute:444,Ucircumflex:722,yacute:500,scommaaccent:389,ecircum [...]
+Uring:722,Udieresis:722,aogonek:444,Uacute:722,uogonek:500,Edieresis:611,Dcroat:722,commaaccent:250,copyright:760,Emacron:611,ccaron:444,aring:444,Ncommaaccent:722,lacute:278,agrave:444,Tcommaaccent:611,Cacute:667,atilde:444,Edotaccent:611,scaron:389,scedilla:389,iacute:278,lozenge:471,Rcaron:667,Gcommaaccent:722,ucircumflex:500,acircumflex:444,Amacron:722,rcaron:333,ccedilla:444,Zdotaccent:611,Thorn:556,Omacron:722,Racute:667,Sacute:556,dcaron:588,Umacron:722,uring:500,threesuperior:300 [...]
+Agrave:722,Abreve:722,multiply:564,uacute:500,Tcaron:611,partialdiff:476,ydieresis:500,Nacute:722,icircumflex:278,Ecircumflex:611,adieresis:444,edieresis:444,cacute:444,nacute:500,umacron:500,Ncaron:722,Iacute:333,plusminus:564,brokenbar:200,registered:760,Gbreve:722,Idotaccent:333,summation:600,Egrave:611,racute:333,omacron:500,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:667,lcommaaccent:278,tcaron:326,eogonek:444,Uogonek:722,Aacute:722,Adieresis:722,egrave:444,zacute:444,io [...]
+Oacute:722,oacute:500,amacron:444,sacute:389,idieresis:278,Ocircumflex:722,Ugrave:722,Delta:612,thorn:500,twosuperior:300,Odieresis:722,mu:500,igrave:278,ohungarumlaut:500,Eogonek:611,dcroat:500,threequarters:750,Scedilla:556,lcaron:344,Kcommaaccent:722,Lacute:611,trademark:980,edotaccent:444,Igrave:333,Imacron:333,Lcaron:611,onehalf:750,lessequal:549,ocircumflex:500,ntilde:500,Uhungarumlaut:722,Eacute:611,emacron:444,gbreve:500,onequarter:750,Scaron:556,Scommaaccent:556,Ohungarumlaut:72 [...]
+ograve:500,Ccaron:667,ugrave:500,radical:453,Dcaron:722,rcommaaccent:333,Ntilde:722,otilde:500,Rcommaaccent:667,Lcommaaccent:611,Atilde:722,Aogonek:722,Aring:722,Otilde:722,zdotaccent:444,Ecaron:611,Iogonek:333,kcommaaccent:500,minus:564,Icircumflex:333,ncaron:500,tcommaaccent:278,logicalnot:564,odieresis:500,udieresis:500,notequal:549,gcommaaccent:500,eth:500,zcaron:444,ncommaaccent:500,onesuperior:300,imacron:278,Euro:500},"Times-Bold":{space:250,exclam:333,quotedbl:555,numbersign:500, [...]
+percent:1E3,ampersand:833,quoteright:333,parenleft:333,parenright:333,asterisk:500,plus:570,comma:250,hyphen:333,period:250,slash:278,zero:500,one:500,two:500,three:500,four:500,five:500,six:500,seven:500,eight:500,nine:500,colon:333,semicolon:333,less:570,equal:570,greater:570,question:500,at:930,A:722,B:667,C:722,D:722,E:667,F:611,G:778,H:778,I:389,J:500,K:778,L:667,M:944,N:722,O:778,P:611,Q:778,R:722,S:556,T:667,U:722,V:722,W:1E3,X:722,Y:722,Z:667,bracketleft:333,backslash:278,bracket [...]
+asciicircum:581,underscore:500,quoteleft:333,a:500,b:556,c:444,d:556,e:444,f:333,g:500,h:556,i:278,j:333,k:556,l:278,m:833,n:556,o:500,p:556,q:556,r:444,s:389,t:333,u:556,v:500,w:722,x:500,y:500,z:444,braceleft:394,bar:220,braceright:394,asciitilde:520,exclamdown:333,cent:500,sterling:500,fraction:167,yen:500,florin:500,section:500,currency:500,quotesingle:278,quotedblleft:500,guillemotleft:500,guilsinglleft:333,guilsinglright:333,fi:556,fl:556,endash:500,dagger:500,daggerdbl:500,periodc [...]
+paragraph:540,bullet:350,quotesinglbase:333,quotedblbase:500,quotedblright:500,guillemotright:500,ellipsis:1E3,perthousand:1E3,questiondown:500,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:1E3,ordfeminine:300,Lslash:667,Oslash:778,OE:1E3,ordmasculine:330,ae:722,dotlessi:278,lslash:278,oslash:500,oe:722,germandbls:556,Idieresis:389,eacute:444,abreve:500,uhungarumlaut:55 [...]
+Ydieresis:722,divide:570,Yacute:722,Acircumflex:722,aacute:500,Ucircumflex:722,yacute:500,scommaaccent:389,ecircumflex:444,Uring:722,Udieresis:722,aogonek:500,Uacute:722,uogonek:556,Edieresis:667,Dcroat:722,commaaccent:250,copyright:747,Emacron:667,ccaron:444,aring:500,Ncommaaccent:722,lacute:278,agrave:500,Tcommaaccent:667,Cacute:722,atilde:500,Edotaccent:667,scaron:389,scedilla:389,iacute:278,lozenge:494,Rcaron:722,Gcommaaccent:778,ucircumflex:556,acircumflex:500,Amacron:722,rcaron:444 [...]
+Zdotaccent:667,Thorn:611,Omacron:778,Racute:722,Sacute:556,dcaron:672,Umacron:722,uring:556,threesuperior:300,Ograve:778,Agrave:722,Abreve:722,multiply:570,uacute:556,Tcaron:667,partialdiff:494,ydieresis:500,Nacute:722,icircumflex:278,Ecircumflex:667,adieresis:500,edieresis:444,cacute:444,nacute:556,umacron:556,Ncaron:722,Iacute:389,plusminus:570,brokenbar:220,registered:747,Gbreve:778,Idotaccent:389,summation:600,Egrave:667,racute:444,omacron:500,Zacute:667,Zcaron:667,greaterequal:549,E [...]
+lcommaaccent:278,tcaron:416,eogonek:444,Uogonek:722,Aacute:722,Adieresis:722,egrave:444,zacute:444,iogonek:278,Oacute:778,oacute:500,amacron:500,sacute:389,idieresis:278,Ocircumflex:778,Ugrave:722,Delta:612,thorn:556,twosuperior:300,Odieresis:778,mu:556,igrave:278,ohungarumlaut:500,Eogonek:667,dcroat:556,threequarters:750,Scedilla:556,lcaron:394,Kcommaaccent:778,Lacute:667,trademark:1E3,edotaccent:444,Igrave:389,Imacron:389,Lcaron:667,onehalf:750,lessequal:549,ocircumflex:500,ntilde:556, [...]
+Eacute:667,emacron:444,gbreve:500,onequarter:750,Scaron:556,Scommaaccent:556,Ohungarumlaut:778,degree:400,ograve:500,Ccaron:722,ugrave:556,radical:549,Dcaron:722,rcommaaccent:444,Ntilde:722,otilde:500,Rcommaaccent:722,Lcommaaccent:667,Atilde:722,Aogonek:722,Aring:722,Otilde:778,zdotaccent:444,Ecaron:667,Iogonek:389,kcommaaccent:556,minus:570,Icircumflex:389,ncaron:556,tcommaaccent:333,logicalnot:570,odieresis:500,udieresis:556,notequal:549,gcommaaccent:500,eth:500,zcaron:444,ncommaaccent [...]
+imacron:278,Euro:500},"Times-BoldItalic":{space:250,exclam:389,quotedbl:555,numbersign:500,dollar:500,percent:833,ampersand:778,quoteright:333,parenleft:333,parenright:333,asterisk:500,plus:570,comma:250,hyphen:333,period:250,slash:278,zero:500,one:500,two:500,three:500,four:500,five:500,six:500,seven:500,eight:500,nine:500,colon:333,semicolon:333,less:570,equal:570,greater:570,question:500,at:832,A:667,B:667,C:667,D:722,E:667,F:667,G:722,H:778,I:389,J:500,K:667,L:611,M:889,N:722,O:722,P [...]
+R:667,S:556,T:611,U:722,V:667,W:889,X:667,Y:611,Z:611,bracketleft:333,backslash:278,bracketright:333,asciicircum:570,underscore:500,quoteleft:333,a:500,b:500,c:444,d:500,e:444,f:333,g:500,h:556,i:278,j:278,k:500,l:278,m:778,n:556,o:500,p:500,q:500,r:389,s:389,t:278,u:556,v:444,w:667,x:500,y:444,z:389,braceleft:348,bar:220,braceright:348,asciitilde:570,exclamdown:389,cent:500,sterling:500,fraction:167,yen:500,florin:500,section:500,currency:500,quotesingle:278,quotedblleft:500,guillemotle [...]
+guilsinglright:333,fi:556,fl:556,endash:500,dagger:500,daggerdbl:500,periodcentered:250,paragraph:500,bullet:350,quotesinglbase:333,quotedblbase:500,quotedblright:500,guillemotright:500,ellipsis:1E3,perthousand:1E3,questiondown:500,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emdash:1E3,AE:944,ordfeminine:266,Lslash:611,Oslash:722,OE:944,ordmasculine:300,ae:722,dotlessi:278,lslash:2 [...]
+oe:722,germandbls:500,Idieresis:389,eacute:444,abreve:500,uhungarumlaut:556,ecaron:444,Ydieresis:611,divide:570,Yacute:611,Acircumflex:667,aacute:500,Ucircumflex:722,yacute:444,scommaaccent:389,ecircumflex:444,Uring:722,Udieresis:722,aogonek:500,Uacute:722,uogonek:556,Edieresis:667,Dcroat:722,commaaccent:250,copyright:747,Emacron:667,ccaron:444,aring:500,Ncommaaccent:722,lacute:278,agrave:500,Tcommaaccent:611,Cacute:667,atilde:500,Edotaccent:667,scaron:389,scedilla:389,iacute:278,lozenge [...]
+Gcommaaccent:722,ucircumflex:556,acircumflex:500,Amacron:667,rcaron:389,ccedilla:444,Zdotaccent:611,Thorn:611,Omacron:722,Racute:667,Sacute:556,dcaron:608,Umacron:722,uring:556,threesuperior:300,Ograve:722,Agrave:667,Abreve:667,multiply:570,uacute:556,Tcaron:611,partialdiff:494,ydieresis:444,Nacute:722,icircumflex:278,Ecircumflex:667,adieresis:500,edieresis:444,cacute:444,nacute:556,umacron:556,Ncaron:722,Iacute:389,plusminus:570,brokenbar:220,registered:747,Gbreve:722,Idotaccent:389,sum [...]
+Egrave:667,racute:389,omacron:500,Zacute:611,Zcaron:611,greaterequal:549,Eth:722,Ccedilla:667,lcommaaccent:278,tcaron:366,eogonek:444,Uogonek:722,Aacute:667,Adieresis:667,egrave:444,zacute:389,iogonek:278,Oacute:722,oacute:500,amacron:500,sacute:389,idieresis:278,Ocircumflex:722,Ugrave:722,Delta:612,thorn:500,twosuperior:300,Odieresis:722,mu:576,igrave:278,ohungarumlaut:500,Eogonek:667,dcroat:500,threequarters:750,Scedilla:556,lcaron:382,Kcommaaccent:667,Lacute:611,trademark:1E3,edotacce [...]
+Imacron:389,Lcaron:611,onehalf:750,lessequal:549,ocircumflex:500,ntilde:556,Uhungarumlaut:722,Eacute:667,emacron:444,gbreve:500,onequarter:750,Scaron:556,Scommaaccent:556,Ohungarumlaut:722,degree:400,ograve:500,Ccaron:667,ugrave:556,radical:549,Dcaron:722,rcommaaccent:389,Ntilde:722,otilde:500,Rcommaaccent:667,Lcommaaccent:611,Atilde:667,Aogonek:667,Aring:667,Otilde:722,zdotaccent:389,Ecaron:667,Iogonek:389,kcommaaccent:500,minus:606,Icircumflex:389,ncaron:556,tcommaaccent:278,logicalnot [...]
+udieresis:556,notequal:549,gcommaaccent:500,eth:500,zcaron:389,ncommaaccent:556,onesuperior:300,imacron:278,Euro:500},"Times-Italic":{space:250,exclam:333,quotedbl:420,numbersign:500,dollar:500,percent:833,ampersand:778,quoteright:333,parenleft:333,parenright:333,asterisk:500,plus:675,comma:250,hyphen:333,period:250,slash:278,zero:500,one:500,two:500,three:500,four:500,five:500,six:500,seven:500,eight:500,nine:500,colon:333,semicolon:333,less:675,equal:675,greater:675,question:500,at:920 [...]
+C:667,D:722,E:611,F:611,G:722,H:722,I:333,J:444,K:667,L:556,M:833,N:667,O:722,P:611,Q:722,R:611,S:500,T:556,U:722,V:611,W:833,X:611,Y:556,Z:556,bracketleft:389,backslash:278,bracketright:389,asciicircum:422,underscore:500,quoteleft:333,a:500,b:500,c:444,d:500,e:444,f:278,g:500,h:500,i:278,j:278,k:444,l:278,m:722,n:500,o:500,p:500,q:500,r:389,s:389,t:278,u:500,v:444,w:667,x:444,y:444,z:389,braceleft:400,bar:275,braceright:400,asciitilde:541,exclamdown:389,cent:500,sterling:500,fraction:16 [...]
+florin:500,section:500,currency:500,quotesingle:214,quotedblleft:556,guillemotleft:500,guilsinglleft:333,guilsinglright:333,fi:500,fl:500,endash:500,dagger:500,daggerdbl:500,periodcentered:250,paragraph:523,bullet:350,quotesinglbase:333,quotedblbase:556,quotedblright:556,guillemotright:500,ellipsis:889,perthousand:1E3,questiondown:500,grave:333,acute:333,circumflex:333,tilde:333,macron:333,breve:333,dotaccent:333,dieresis:333,ring:333,cedilla:333,hungarumlaut:333,ogonek:333,caron:333,emd [...]
+ordfeminine:276,Lslash:556,Oslash:722,OE:944,ordmasculine:310,ae:667,dotlessi:278,lslash:278,oslash:500,oe:667,germandbls:500,Idieresis:333,eacute:444,abreve:500,uhungarumlaut:500,ecaron:444,Ydieresis:556,divide:675,Yacute:556,Acircumflex:611,aacute:500,Ucircumflex:722,yacute:444,scommaaccent:389,ecircumflex:444,Uring:722,Udieresis:722,aogonek:500,Uacute:722,uogonek:500,Edieresis:611,Dcroat:722,commaaccent:250,copyright:760,Emacron:611,ccaron:444,aring:500,Ncommaaccent:667,lacute:278,agr [...]
+Cacute:667,atilde:500,Edotaccent:611,scaron:389,scedilla:389,iacute:278,lozenge:471,Rcaron:611,Gcommaaccent:722,ucircumflex:500,acircumflex:500,Amacron:611,rcaron:389,ccedilla:444,Zdotaccent:556,Thorn:611,Omacron:722,Racute:611,Sacute:500,dcaron:544,Umacron:722,uring:500,threesuperior:300,Ograve:722,Agrave:611,Abreve:611,multiply:675,uacute:500,Tcaron:556,partialdiff:476,ydieresis:444,Nacute:667,icircumflex:278,Ecircumflex:611,adieresis:500,edieresis:444,cacute:444,nacute:500,umacron:500 [...]
+Iacute:333,plusminus:675,brokenbar:275,registered:760,Gbreve:722,Idotaccent:333,summation:600,Egrave:611,racute:389,omacron:500,Zacute:556,Zcaron:556,greaterequal:549,Eth:722,Ccedilla:667,lcommaaccent:278,tcaron:300,eogonek:444,Uogonek:722,Aacute:611,Adieresis:611,egrave:444,zacute:389,iogonek:278,Oacute:722,oacute:500,amacron:500,sacute:389,idieresis:278,Ocircumflex:722,Ugrave:722,Delta:612,thorn:500,twosuperior:300,Odieresis:722,mu:500,igrave:278,ohungarumlaut:500,Eogonek:611,dcroat:50 [...]
+Scedilla:500,lcaron:300,Kcommaaccent:667,Lacute:556,trademark:980,edotaccent:444,Igrave:333,Imacron:333,Lcaron:611,onehalf:750,lessequal:549,ocircumflex:500,ntilde:500,Uhungarumlaut:722,Eacute:611,emacron:444,gbreve:500,onequarter:750,Scaron:500,Scommaaccent:500,Ohungarumlaut:722,degree:400,ograve:500,Ccaron:667,ugrave:500,radical:453,Dcaron:722,rcommaaccent:389,Ntilde:667,otilde:500,Rcommaaccent:611,Lcommaaccent:556,Atilde:611,Aogonek:611,Aring:611,Otilde:722,zdotaccent:389,Ecaron:611,I [...]
+kcommaaccent:444,minus:675,Icircumflex:333,ncaron:500,tcommaaccent:278,logicalnot:675,odieresis:500,udieresis:500,notequal:549,gcommaaccent:500,eth:500,zcaron:389,ncommaaccent:500,onesuperior:300,imacron:278,Euro:500},ZapfDingbats:{space:278,a1:974,a2:961,a202:974,a3:980,a4:719,a5:789,a119:790,a118:791,a117:690,a11:960,a12:939,a13:549,a14:855,a15:911,a16:933,a105:911,a17:945,a18:974,a19:755,a20:846,a21:762,a22:761,a23:571,a24:677,a25:763,a26:760,a27:759,a28:754,a6:494,a7:552,a8:537,a9:57 [...]
+a29:786,a30:788,a31:788,a32:790,a33:793,a34:794,a35:816,a36:823,a37:789,a38:841,a39:823,a40:833,a41:816,a42:831,a43:923,a44:744,a45:723,a46:749,a47:790,a48:792,a49:695,a50:776,a51:768,a52:792,a53:759,a54:707,a55:708,a56:682,a57:701,a58:826,a59:815,a60:789,a61:789,a62:707,a63:687,a64:696,a65:689,a66:786,a67:787,a68:713,a69:791,a70:785,a71:791,a72:873,a73:761,a74:762,a203:762,a75:759,a204:759,a76:892,a77:892,a78:788,a79:784,a81:438,a82:138,a83:277,a84:415,a97:392,a98:392,a99:668,a100:668,a [...]
+a93:317,a94:317,a91:276,a92:276,a205:509,a85:509,a206:410,a86:410,a87:234,a88:234,a95:334,a96:334,a101:732,a102:544,a103:544,a104:910,a106:667,a107:760,a108:760,a112:776,a111:595,a110:694,a109:626,a120:788,a121:788,a122:788,a123:788,a124:788,a125:788,a126:788,a127:788,a128:788,a129:788,a130:788,a131:788,a132:788,a133:788,a134:788,a135:788,a136:788,a137:788,a138:788,a139:788,a140:788,a141:788,a142:788,a143:788,a144:788,a145:788,a146:788,a147:788,a148:788,a149:788,a150:788,a151:788,a152:78 [...]
+a154:788,a155:788,a156:788,a157:788,a158:788,a159:788,a160:894,a161:838,a163:1016,a164:458,a196:748,a165:924,a192:748,a166:918,a167:927,a168:928,a169:928,a170:834,a171:873,a172:828,a173:924,a162:924,a174:917,a175:930,a176:931,a177:463,a178:883,a179:836,a193:836,a180:867,a199:867,a181:696,a200:696,a182:874,a201:874,a183:760,a184:946,a197:771,a185:865,a194:771,a198:888,a186:967,a195:888,a187:831,a188:873,a189:927,a190:970,a191:918}};"use strict";var Z={},Pa=function(){function d(a,b,c){thi [...]
+this.allowStreams=b;this.xref=c;this.inlineImg=0;this.refill()}d.prototype={refill:function(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()},shift:function(){aa(this.buf2,"ID")?(this.buf1=this.buf2,this.buf2=null,this.lexer.skip()):(this.buf1=this.buf2,this.buf2=this.lexer.getObj())},getObj:function(a){if(aa(this.buf1,"BI"))return this.shift(),this.makeInlineImage(a);if(aa(this.buf1,"[")){this.shift();for(a=[];!aa(this.buf1,"]")&&this.buf1!=Z;)a.push(this.getObj());this.buf [...]
+this.shift();return a}if(aa(this.buf1,"<<")){this.shift();for(var b=new xa(this.xref);!aa(this.buf1,">>")&&this.buf1!=Z;){T(this.buf1)||r("Dictionary key must be a name object");var c=this.buf1.name;this.shift();if(this.buf1==Z)break;b.set(c,this.getObj(a))}this.buf1==Z&&r("End of file inside dictionary");if(aa(this.buf2,"stream"))return this.allowStreams?this.makeStream(b,a):b;this.shift();return b}if(E(this.buf1))return a=this.buf1,this.shift(),E(this.buf1)&&aa(this.buf2,"R")&&(a=new G [...]
+this.shift(),this.shift()),a;if(Na(this.buf1))return b=this.buf1,this.shift(),a&&(b=a.decryptString(b)),b;a=this.buf1;this.shift();return a},makeInlineImage:function(a){for(var b=this.lexer.stream,c=new xa;!aa(this.buf1,"ID")&&this.buf1!=Z;){T(this.buf1)||r("Dictionary key must be a name object");var e=this.buf1.name;this.shift();if(this.buf1==Z)break;c.set(e,this.getObj(a))}for(var e=b.pos,f=0,d;4!=f&&null!=(d=b.getByte());)switch(d){case 32:case 13:case 10:f=3===f?4:0;break;case 69:f=2 [...]
+2===f?3:0;break;default:f=0}if(500<=++this.inlineImg)return 500===this.inlineImg&&Y("Too many inline images"),this.shift(),null;d=b.pos-4-e;b=b.makeSubStream(e,d,c);a&&(b=a.createStream(b));b=this.filter(b,c,d);b.parameters=c;this.buf2=Ja.get("EI");this.shift();return b},fetchIfRef:function(a){return ya(a)?this.xref.fetch(a):a},makeStream:function(a,b){var c=this.lexer,e=c.stream;c.skipToNextLine();var c=e.pos,d=this.fetchIfRef(a.get("Length"));E(d)||r("Bad "+d+" attribute in stream");e. [...]
+this.shift();aa(this.buf1,"endstream")||r("Missing endstream");this.shift();e=e.makeSubStream(c,d,a);b&&(e=b.createStream(e));e=this.filter(e,a,d);e.parameters=a;return e},filter:function(a,b,c){var e=this.fetchIfRef(b.get("Filter","F")),b=this.fetchIfRef(b.get("DecodeParms","DP"));if(T(e))return this.makeFilter(a,e.name,c,b);if(R(e))for(var d=e,g=b,i=0,k=d.length;i<k;++i)e=d[i],T(e)||r("Bad filter name: "+e),b=null,R(g)&&i in g&&(b=g[i]),a=this.makeFilter(a,e.name,c,b),c=null;return a}, [...]
+b,c,e){if("FlateDecode"==b||"Fl"==b)return e?new xc(new yc(a),e):new yc(a);if("LZWDecode"==b||"LZW"==b){b=1;return e?(e.has("EarlyChange")&&(b=e.get("EarlyChange")),new xc(new zc(a,b),e)):new zc(a,b)}if("DCTDecode"==b||"DCT"==b)return e=a.getBytes(c),new jb(e,a.dict,this.xref);if("JPXDecode"==b||"JPX"==b)return e=a.getBytes(c),new xd(e,a.dict);if("ASCII85Decode"==b||"A85"==b)return new yd(a);if("ASCIIHexDecode"==b||"AHx"==b)return new zd(a);if("CCITTFaxDecode"==b||"CCF"==b)return new Ad( [...]
+b||"RL"==b)return new Bd(a);"JBIG2Decode"==b&&r("JBIG2 image format is not currently supprted.");Y('filter "'+b+'" not supported yet');return a}};return d}(),va=function(){function d(a){this.stream=a}function a(a){if("0"<=a&&"9">=a)return a.charCodeAt(0)-48;a=a.toUpperCase();return"A"<=a&&"F">=a?a.charCodeAt(0)-55:-1}d.isSpace=function(a){return" "==a||"\t"==a||"\r"==a||"\n"==a};var b=[1,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,2,2,0,0,0,0,0,2,0,0,0,0 [...]
+0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];d.prototype={getNumber:function(a){for(var b=!1,d=a,g=this.stream;;){a=g.lookChar();if("."= [...]
+a,b=!0;else if("-"==a)Y("Badly formated number");else if("0"<=a&&"9">=a)d+=a;else if("e"==a||"E"==a)b=!0;else break;g.skip()}a=parseFloat(d);isNaN(a)&&r("Invalid floating point number: "+a);return a},getString:function(){var a=1,b=!1,d="",g=this.stream,i;do switch(i=g.getChar(),i){case void 0:Y("Unterminated string");b=!0;break;case "(":++a;d+=i;break;case ")":0==--a?b=!0:d+=i;break;case "\\":i=g.getChar();switch(i){case void 0:Y("Unterminated string");b=!0;break;case "n":d+="\n";break;c [...]
+"\r";break;case "t":d+="\t";break;case "b":d+="\u0008";break;case "f":d+="\u000c";break;case "\\":case "(":case ")":d+=i;break;case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":var k=i-0;i=g.lookChar();"0"<=i&&"7">=i&&(g.skip(),k=(k<<3)+(i-0),i=g.lookChar(),"0"<=i&&"7">=i&&(g.skip(),k=(k<<3)+(i-0)));d+=String.fromCharCode(k);break;case "\r":i=g.lookChar();"\n"==i&&g.skip();break;case "\n":break;default:d+=i}break;default:d+=i}while(!b);return d},getName:function(c){ [...]
+"",d=this.stream;(c=d.lookChar())&&!b[c.charCodeAt(0)];)if(d.skip(),"#"==c){var c=d.lookChar(),g=a(c);if(-1!=g){d.skip();var i=a(d.getChar());-1==i&&r("Illegal digit in hex char in name: "+i);e+=String.fromCharCode(g<<4|i)}else e+="#",e+=c}else e+=c;128<e.length&&r("Warning: name token is longer than allowed by the spec: "+e.length);return new wa(e)},getHexString:function(c){for(var e="",d=this.stream;;){c=d.getChar();if(">"==c)break;if(!c){Y("Unterminated hex string");break}if(1!=b[c.ch [...]
+i;-1==(g=a(c))&&r("Illegal character in hex string: "+c);for(c=d.getChar();1==b[c.charCodeAt(0)];)c=d.getChar();-1==(i=a(c))&&r("Illegal character in hex string: "+c);e+=String.fromCharCode(g<<4|i)}}return e},getObj:function(){for(var a=!1,e=this.stream,d;;){if(!(d=e.getChar()))return Z;if(a){if("\r"==d||"\n"==d)a=!1}else if("%"==d)a=!0;else if(1!=b[d.charCodeAt(0)])break}switch(d){case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":case "8":case "9":case "+":case "-" [...]
+case "(":return this.getString();case "/":return this.getName(d);case "[":case "]":return Ja.get(d);case "<":d=e.lookChar();return"<"==d?(e.skip(),Ja.get("<<")):this.getHexString(d);case ">":if(d=e.lookChar(),">"==d)return e.skip(),Ja.get(">>");case "{":case "}":return Ja.get(d);case ")":r("Illegal character: "+d)}for(a=d;(d=e.lookChar())&&!b[d.charCodeAt(0)];)e.skip(),128==a.length&&r("Command token too long: "+a.length),a+=d;return"true"==a?!0:"false"==a?!1:"null"==a?null:Ja.get(a)},sk [...]
+this.stream;;){var b=a.getChar();if(!b||"\n"==b)break;if("\r"==b){"\n"==a.lookChar()&&a.skip();break}}},skip:function(){this.stream.skip()}};return d}(),Ic=function(){function d(a){this.parser=new Pa(new va(a),!1,null);var a=this.parser.getObj(),b=this.parser.getObj(),c=this.parser.getObj();this.linDict=this.parser.getObj();E(a)&&E(b)&&aa(c,"obj")&&U(this.linDict)&&(a=this.linDict.get("Linearized"),sa(a)&&0<a||(this.linDict=null))}d.prototype={getInt:function(a){var b=this.linDict,c;if(U [...]
+b.get(a))&&0<c)return c;r('"'+a+'" field in linearization table is invalid')},getHint:function(a){var b=this.linDict,c,e;if(U(b)&&R(c=b.get("H"))&&2<=c.length&&E(e=c[a])&&0<e)return e;r("Hints table in linearization table is invalid: "+a)},get length(){return!U(this.linDict)?0:this.getInt("L")},get hintsOffset(){return this.getHint(0)},get hintsLength(){return this.getHint(1)},get hintsOffset2(){return this.getHint(2)},get hintsLenth2(){return this.getHint(3)},get objectNumberFirst(){ret [...]
+get endFirst(){return this.getInt("E")},get numPages(){return this.getInt("N")},get mainXRefEntriesOffset(){return this.getInt("T")},get pageFirst(){return this.getInt("P")}};return d}();"use strict";var wb=function(){function d(){r("should not call Pattern constructor")}d.prototype={getPattern:function(a){r("Should not call Pattern.getStyle: "+a)}};d.shadingFromIR=function(a){return Ta[a[0]].fromIR(a)};d.parseShading=function(a,b,c,e){a=oa(a)?a.dict:a;switch(a.get("ShadingType")){case 2 [...]
+b,c,e);default:return new Ta.Dummy}};return d}(),Ta={};Ta.RadialAxial=function(){function d(a,b,c,e,d){this.matrix=b;this.coordsArr=a.get("Coords");this.shadingType=a.get("ShadingType");this.type="Pattern";this.ctx=d;b=a.get("ColorSpace","CS");this.cs=b=ia.parse(b,c,e);e=0;d=1;a.has("Domain")&&(d=a.get("Domain"),e=d[0],d=d[1]);var g=!1,i=!1;a.has("Extend")&&(i=a.get("Extend"),g=i[0],i=i[1],ea("Support extend"));this.extendStart=g;this.extendEnd=i;a=a.get("Function");R(a)&&r("No support f [...]
+a:if("object"!=typeof a)g=!1;else{if(U(a))g=a;else if(oa(a))g=a.dict;else{g=!1;break a}g=g.has("FunctionType")}g||r("Invalid function");for(var c=Za.parse(c,a),a=(d-e)/10,g=d-e,i=[],k=e;k<=d;k+=a){var j=b.getRgb(c([k])),j=Q.makeCssRgb(j[0],j[1],j[2]);i.push([(k-e)/g,j])}this.colorStops=i}d.fromIR=function(a){var b=a[1],c=a[2],e=a[3],d=a[4],g=a[5],i=a[6];return{type:"Pattern",getPattern:function(a){var j=a.mozCurrentTransform;if(j){var h=a.mozCurrentTransformInverse;e=Q.applyTransform(e,j [...]
+h);d=Q.applyTransform(d,j);d=Q.applyTransform(d,h)}var m;2==b?m=a.createLinearGradient(e[0],e[1],d[0],d[1]):3==b&&(m=a.createRadialGradient(e[0],e[1],g,d[0],d[1],i));a=0;for(j=c.length;a<j;++a)h=c[a],m.addColorStop(h[0],h[1]);return m}}};d.prototype={getIR:function(){var a=this.coordsArr,b=this.shadingType;if(2==b)var c=[a[0],a[1]],e=[a[2],a[3]],d=null,g=null;else 3==b?(c=[a[0],a[1]],e=[a[3],a[4]],d=a[2],g=a[5]):r("getPattern type unknown: "+b);if(a=this.matrix)c=Q.applyTransform(c,a),e= [...]
+a);return["RadialAxial",b,this.colorStops,c,e,d,g]}};return d}();Ta.Dummy=function(){function d(){this.type="Pattern"}d.fromIR=function(){return"hotpink"};d.prototype={getIR:function(){return["Dummy"]}};return d}();var Mb=function(){var d,a;function b(b,f,g,i){var k=b[2];this.matrix=b[3];var j=b[4],h=b[5],m=b[6],l=b[7];ea("TilingType");this.curMatrix=g.mozCurrentTransform;this.invMatrix=g.mozCurrentTransformInverse;this.ctx=g;this.type="Pattern";for(var b=j[0],n=j[1],o=j[2],p=j[3],q=[b,n [...]
+n+m],O=u[0]-q[0],N=u[1]-q[1];Math.abs(O)>c||Math.abs(N)>c;)O=N=c;var u=ub(O,N),ba=u.getContext("2d"),i=new Oa(ba,i);switch(l){case d:ba.fillStyle=g.fillStyle;ba.strokeStyle=g.strokeStyle;break;case a:f=Q.makeCssRgb(this,f[0],f[1],f[2]);ba.fillStyle=f;ba.strokeStyle=f;break;default:r("Unsupported paint type: "+l)}this.scale=h=[O/h,N/m];m=[1,0,0,1,-q[0],-q[1]];i.transform.apply(i,[h[0],0,0,h[1],0,0]);i.transform.apply(i,m);j&&R(j)&&4==j.length&&(i.rectangle(b,n,o-b,p-n),i.clip(),i.endPath( [...]
+this.canvas=u}d=1;a=2;var c=512;b.getIR=function(a,b,c){var d=b.get("Matrix"),k=b.get("BBox"),j=b.get("XStep"),h=b.get("YStep"),b=b.get("PaintType");return["TilingPattern",c,a,d,k,j,h,b]};b.prototype={getPattern:function(){var a=this.matrix,b=this.curMatrix,c=this.ctx;b&&c.setTransform.apply(c,b);a&&c.transform.apply(c,a);a=this.scale;c.scale(1/a[0],1/a[1]);return c.createPattern(this.canvas,"repeat")}};return b}();"use strict";var Aa=function(){function d(a,b,c,d){this.bytes=new Uint8Ar [...]
+this.start=b||0;this.end=b+c||this.bytes.length;this.dict=d}d.prototype={get length(){return this.end-this.start},getByte:function(){return this.pos>=this.end?null:this.bytes[this.pos++]},getBytes:function(a){var b=this.bytes,c=this.pos,d=this.end;if(!a)return b.subarray(c,d);a=c+a;a>d&&(a=d);this.pos=a;return b.subarray(c,a)},lookChar:function(){return this.pos>=this.end?null:String.fromCharCode(this.bytes[this.pos])},getChar:function(){return this.pos>=this.end?null:String.fromCharCode [...]
+skip:function(a){a||(a=1);this.pos+=a},reset:function(){this.pos=this.start},moveStart:function(){this.start=this.pos},makeSubStream:function(a,b,c){return new d(this.bytes.buffer,a,b,c)},isStream:!0};return d}();(function(){function d(a){for(var b=a.length,c=new Uint8Array(b),d=0;d<b;++d)c[d]=a.charCodeAt(d);Aa.call(this,c)}d.prototype=Aa.prototype;return d})();var W=function(){function d(){this.bufferLength=this.pos=0;this.eof=!1;this.buffer=null}d.prototype={ensureBuffer:function(a){v [...]
+c=b?b.byteLength:0;if(a<c)return b;for(var d=512;d<a;)d<<=1;a=new Uint8Array(d);for(d=0;d<c;++d)a[d]=b[d];return this.buffer=a},getByte:function(){for(var a=this.pos;this.bufferLength<=a;){if(this.eof)return null;this.readBlock()}return this.buffer[this.pos++]},getBytes:function(a){var b=this.pos;if(a){this.ensureBuffer(b+a);for(a=b+a;!this.eof&&this.bufferLength<a;)this.readBlock();var c=this.bufferLength;a>c&&(a=c)}else{for(;!this.eof;)this.readBlock();a=this.bufferLength;a||(this.buff [...]
+a;return this.buffer.subarray(b,a)},lookChar:function(){for(var a=this.pos;this.bufferLength<=a;){if(this.eof)return null;this.readBlock()}return String.fromCharCode(this.buffer[this.pos])},getChar:function(){for(var a=this.pos;this.bufferLength<=a;){if(this.eof)return null;this.readBlock()}return String.fromCharCode(this.buffer[this.pos++])},makeSubStream:function(a,b,c){for(var d=a+b;this.bufferLength<=d&&!this.eof;)this.readBlock();return new Aa(this.buffer,a,b,c)},skip:function(a){a| [...]
+a},reset:function(){this.pos=0}};return d}();(function(){function d(a){this.dict=a.dict;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){var a=this.bufferLength,a=a+1024;this.ensureBuffer(a);this.bufferLength=a};d.prototype.getBytes=function(a){var b=this.pos;if(a){this.ensureBuffer(b+a);for(a=b+a;!this.eof&&this.bufferLength<a;)this.readBlock();var c=this.bufferLength;a>c&&(a=c)}else this.eof=!0,a=this.bufferLength;this.pos=a;return this.buffer.subarr [...]
+return d})();var Fc=function(){function d(a){this.streams=a;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){var a=this.streams;if(0==a.length)this.eof=!0;else{var a=a.shift().getBytes(),b=this.bufferLength,c=b+a.length;this.ensureBuffer(c).set(a,b);this.bufferLength=c}};return d}(),yc=function(){function d(a){var b=a.getBytes(),c=0;this.dict=a.dict;var a=b[c++],d=b[c++];(-1==a||-1==d)&&r("Invalid header in flate stream: "+a+", "+d);8!=(a&15)&&r("Unkno [...]
+a+", "+d);0!=((a<<8)+d)%31&&r("Bad FCHECK in flate stream: "+a+", "+d);d&32&&r("FDICT bit set in flate stream: "+a+", "+d);this.bytes=b;this.bytesPos=c;this.codeBuf=this.codeSize=0;W.call(this)}var a=new Uint32Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),b=new Uint32Array([3,4,5,6,7,8,9,10,65547,65549,65551,65553,131091,131095,131099,131103,196643,196651,196659,196667,262211,262227,262243,262259,327811,327843,327875,327907,258,258,258]),c=new Uint32Array([1,2,3,4,65541,65543,1 [...]
+196625,196633,262177,262193,327745,327777,393345,393409,459009,459137,524801,525057,590849,591361,657409,658433,724993,727041,794625,798721,868353,876545]),e=[new Uint32Array([459008,524368,524304,524568,459024,524400,524336,590016,459016,524384,524320,589984,524288,524416,524352,590048,459012,524376,524312,589968,459028,524408,524344,590032,459020,524392,524328,59E4,524296,524424,524360,590064,459010,524372,524308,524572,459026,524404,524340,590024,459018,524388,524324,589992,524292,524 [...]
+590056,459014,524380,524316,589976,459030,524412,524348,590040,459022,524396,524332,590008,524300,524428,524364,590072,459009,524370,524306,524570,459025,524402,524338,590020,459017,524386,524322,589988,524290,524418,524354,590052,459013,524378,524314,589972,459029,524410,524346,590036,459021,524394,524330,590004,524298,524426,524362,590068,459011,524374,524310,524574,459027,524406,524342,590028,459019,524390,524326,589996,524294,524422,524358,590060,459015,524382,524318,589980,459031,52 [...]
+590044,459023,524398,524334,590012,524302,524430,524366,590076,459008,524369,524305,524569,459024,524401,524337,590018,459016,524385,524321,589986,524289,524417,524353,590050,459012,524377,524313,589970,459028,524409,524345,590034,459020,524393,524329,590002,524297,524425,524361,590066,459010,524373,524309,524573,459026,524405,524341,590026,459018,524389,524325,589994,524293,524421,524357,590058,459014,524381,524317,589978,459030,524413,524349,590042,459022,524397,524333,590010,524301,52 [...]
+590074,459009,524371,524307,524571,459025,524403,524339,590022,459017,524387,524323,589990,524291,524419,524355,590054,459013,524379,524315,589974,459029,524411,524347,590038,459021,524395,524331,590006,524299,524427,524363,590070,459011,524375,524311,524575,459027,524407,524343,590030,459019,524391,524327,589998,524295,524423,524359,590062,459015,524383,524319,589982,459031,524415,524351,590046,459023,524399,524335,590014,524303,524431,524367,590078,459008,524368,524304,524568,459024,52 [...]
+590017,459016,524384,524320,589985,524288,524416,524352,590049,459012,524376,524312,589969,459028,524408,524344,590033,459020,524392,524328,590001,524296,524424,524360,590065,459010,524372,524308,524572,459026,524404,524340,590025,459018,524388,524324,589993,524292,524420,524356,590057,459014,524380,524316,589977,459030,524412,524348,590041,459022,524396,524332,590009,524300,524428,524364,590073,459009,524370,524306,524570,459025,524402,524338,590021,459017,524386,524322,589989,524290,52 [...]
+590053,459013,524378,524314,589973,459029,524410,524346,590037,459021,524394,524330,590005,524298,524426,524362,590069,459011,524374,524310,524574,459027,524406,524342,590029,459019,524390,524326,589997,524294,524422,524358,590061,459015,524382,524318,589981,459031,524414,524350,590045,459023,524398,524334,590013,524302,524430,524366,590077,459008,524369,524305,524569,459024,524401,524337,590019,459016,524385,524321,589987,524289,524417,524353,590051,459012,524377,524313,589971,459028,52 [...]
+590035,459020,524393,524329,590003,524297,524425,524361,590067,459010,524373,524309,524573,459026,524405,524341,590027,459018,524389,524325,589995,524293,524421,524357,590059,459014,524381,524317,589979,459030,524413,524349,590043,459022,524397,524333,590011,524301,524429,524365,590075,459009,524371,524307,524571,459025,524403,524339,590023,459017,524387,524323,589991,524291,524419,524355,590055,459013,524379,524315,589975,459029,524411,524347,590039,459021,524395,524331,590007,524299,52 [...]
+590071,459011,524375,524311,524575,459027,524407,524343,590031,459019,524391,524327,589999,524295,524423,524359,590063,459015,524383,524319,589983,459031,524415,524351,590047,459023,524399,524335,590015,524303,524431,524367,590079]),9],f=[new Uint32Array([327680,327696,327688,327704,327684,327700,327692,327708,327682,327698,327690,327706,327686,327702,327694,0,327681,327697,327689,327705,327685,327701,327693,327709,327683,327699,327691,327707,327687,327703,327695,0]),5];d.prototype=Objec [...]
+d.prototype.getBits=function(a){for(var b=this.codeSize,c=this.codeBuf,d=this.bytes,e=this.bytesPos,f;b<a;)"undefined"==typeof(f=d[e++])&&r("Bad encoding in flate stream"),c|=f<<b,b+=8;this.codeBuf=c>>a;this.codeSize=b-a;this.bytesPos=e;return c&(1<<a)-1};d.prototype.getCode=function(a){for(var b=a[0],c=a[1],a=this.codeSize,d=this.codeBuf,e=this.bytes,f=this.bytesPos;a<c;){var l;"undefined"==typeof(l=e[f++])&&r("Bad encoding in flate stream");d|=l<<a;a+=8}c=b[d&(1<<c)-1];b=c>>16;c&=65535 [...]
+b||0==b)&&r("Bad encoding in flate stream");this.codeBuf=d>>b;this.codeSize=a-b;this.bytesPos=f;return c};d.prototype.generateHuffmanTable=function(a){for(var b=a.length,c=0,d=0;d<b;++d)a[d]>c&&(c=a[d]);for(var e=1<<c,f=new Uint32Array(e),l=1,n=0,o=2;l<=c;++l,n<<=1,o<<=1)for(var p=0;p<b;++p)if(a[p]==l){for(var q=0,u=n,d=0;d<l;++d)q=q<<1|u&1,u>>=1;for(d=q;d<e;d+=o)f[d]=l<<16|p;++n}return[f,c]};d.prototype.readBlock=function(){var d=this.getBits(3);d&1&&(this.eof=!0);d>>=1;if(0==d){var d=t [...]
+i=this.bytesPos,k;"undefined"==typeof(k=d[i++])&&r("Bad block header in flate stream");var j=k;"undefined"==typeof(k=d[i++])&&r("Bad block header in flate stream");j|=k<<8;"undefined"==typeof(k=d[i++])&&r("Bad block header in flate stream");var h=k;"undefined"==typeof(k=d[i++])&&r("Bad block header in flate stream");(h|k<<8)!=(~j&65535)&&r("Bad uncompressed block length in flate stream");this.codeSize=this.codeBuf=0;k=this.bufferLength;h=this.ensureBuffer(k+j);this.bufferLength=j=k+j;for [...]
+j;++m){if("undefined"==typeof(k=d[i++])){this.eof=!0;break}h[m]=k}this.bytesPos=i}else{if(1==d)i=e,k=f;else if(2==d){h=this.getBits(5)+257;j=this.getBits(5)+1;d=this.getBits(4)+4;k=new Uint8Array(a.length);for(i=0;i<d;++i)k[a[i]]=this.getBits(3);k=this.generateHuffmanTable(k);i=d=0;j=h+j;for(m=new Uint8Array(j);i<j;){var l=this.getCode(k);if(16==l)var n=2,o=3,l=d;else if(17==l)o=n=3,l=d=0;else if(18==l)n=7,o=11,l=d=0;else{m[i++]=d=l;continue}for(n=this.getBits(n)+o;0<n--;)m[i++]=l}i=this [...]
+h));k=this.generateHuffmanTable(m.subarray(h,j))}else r("Unknown block type in flate stream");j=(h=this.buffer)?h.length:0;for(m=this.bufferLength;;)if(l=this.getCode(i),256>l)m+1>=j&&(h=this.ensureBuffer(m+1),j=h.length),h[m++]=l;else{if(256==l){this.bufferLength=m;break}l-=257;l=b[l];n=l>>16;0<n&&(n=this.getBits(n));d=(l&65535)+n;l=this.getCode(k);l=c[l];n=l>>16;0<n&&(n=this.getBits(n));l=(l&65535)+n;m+d>=j&&(h=this.ensureBuffer(m+d),j=h.length);for(n=0;n<d;++n,++m)h[m]=h[m-l]}}};retur [...]
+function(){function d(a,b){var c=this.predictor=b.get("Predictor")||1;if(1>=c)return a;2!==c&&(10>c||15<c)&&r("Unsupported predictor: "+c);this.readBlock=2===c?this.readBlockTiff:this.readBlockPng;this.stream=a;this.dict=a.dict;var c=this.colors=b.get("Colors")||1,d=this.bits=b.get("BitsPerComponent")||8,f=this.columns=b.get("Columns")||1;this.pixBytes=c*d+7>>3;this.rowBytes=f*c*d+7>>3;W.call(this);return this}d.prototype=Object.create(W.prototype);d.prototype.readBlockTiff=function(){va [...]
+b=this.bufferLength,c=this.ensureBuffer(b+a),d=this.bits,f=this.colors,g=this.stream.getBytes(a),i=0,k=0,j=0,h=0,m=b;if(1===d)for(b=0;b<a;++b)d=g[b],i=i<<8|d,c[m++]=(d^i>>f)&255,i&=65535;else if(8===d){for(b=0;b<f;++b)c[m++]=g[b];for(;b<a;++b)c[m]=c[m-f]+g[b],m++}else{for(var m=new Uint8Array(f+1),l=(1<<d)-1,n=0,o=b,p=this.columns,b=0;b<p;++b)for(var q=0;q<f;++q)j<d&&(i=i<<8|g[n++]&255,j+=8),m[q]=m[q]+(i>>j-d)&l,j-=d,k=k<<d|m[q],h+=d,8<=h&&(c[o++]=k>>h-8&255,h-=8);0<h&&(c[o++]=(k<<8-h)+( [...]
+1))}this.bufferLength+=a};d.prototype.readBlockPng=function(){var a=this.rowBytes,b=this.pixBytes,c=this.stream.getByte(),d=this.stream.getBytes(a),f=this.bufferLength,g=this.ensureBuffer(f+a),i=g.subarray(f-a,f);0==i.length&&(i=new Uint8Array(a));switch(c){case 0:for(c=0;c<a;++c)g[f++]=d[c];break;case 1:for(c=0;c<b;++c)g[f++]=d[c];for(;c<a;++c)g[f]=g[f-b]+d[c]&255,f++;break;case 2:for(c=0;c<a;++c)g[f++]=i[c]+d[c]&255;break;case 3:for(c=0;c<b;++c)g[f++]=(i[c]>>1)+d[c];for(;c<a;++c)g[f]=( [...]
+b]>>1)+d[c]&255,f++;break;case 4:for(c=0;c<b;++c){var k=i[c],j=d[c];g[f++]=k+j}for(;c<a;++c){var k=i[c],h=i[c-b],m=g[f-b],j=m+k-h,l=j-m;0>l&&(l=-l);var n=j-k;0>n&&(n=-n);var o=j-h;0>o&&(o=-o);j=d[c];l<=n&&l<=o?g[f++]=m+j:n<=o?g[f++]=k+j:g[f++]=h+j}break;default:r("Unsupported predictor: "+c)}this.bufferLength+=a};return d}(),jb=function(){function d(a,b){this.dict=b;this.isAdobeImage=!1;this.colorTransform=b.get("ColorTransform")||-1;var c;a:{c=a;for(var d=Math.max(c.length-16,1024),f=0; [...]
+c[f]&&238==c[f+1]&&0==c[f+2]&&14==c[f+3]&&65==c[f+4]&&100==c[f+5]&&111==c[f+6]&&98==c[f+7]&&101==c[f+8]&&0==c[f+9]){c=!0;break a}if(255==c[f]&&192==c[f+1])break}c=!1}c&&(this.isAdobeImage=!0,c=a,d=new Uint8Array([255,236,0,8,69,77,66,69,68,0]),f=new Uint8Array(c.length+d.length),f.set(c,d.length),f[0]=c[0],f[1]=c[1],f.set(d,2),a=f);this.bytes=a;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.ensureBuffer=function(){if(!this.bufferLength)try{var a=new Cd;-1!=this.colorTran [...]
+this.colorTransform);a.parse(this.bytes);var b=a.getData(a.width,a.height);this.buffer=b;this.bufferLength=b.length}catch(c){r("JPEG error: "+c)}};d.prototype.getIR=function(){return tb(this.bytes)};d.prototype.getChar=function(){r("internal error: getChar is not valid on JpegStream")};d.prototype.isNativelySupported=function(a,b){var c=ia.parse(this.dict.get("ColorSpace"),a,b);return"DeviceGray"===c.name||"DeviceRGB"===c.name||"DeviceCMYK"===c.name&&!this.isAdobeImage&&1>this.colorTrans [...]
+d.prototype.isNativelyDecodable=function(a,b){var c=ia.parse(this.dict.get("ColorSpace"),a,b).numComps;return 1==c||3==c?!0:!1};return d}(),xd=function(){function d(a,b){this.dict=b;this.bytes=a;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.ensureBuffer=function(){if(!this.bufferLength){var a=new Dd;a.parse(this.bytes);var b=a.width,c=a.height,d=a.componentsCount;1!=d&&3!=d&&4!=d&&r("JPX with "+d+" components is not supported");for(var c=new Uint8Array(b*c*d),f=0,g=a.ti [...]
+g;f++){var i=a.tiles[f],k=i[0].width,j=i[0].height,h=i[0].left,m=i[0].top,l,n,o,p;switch(d){case 1:l=i[0].items;h=b*m+h;p=b-k;for(var q=m=0;q<j;q++){for(var u=0;u<k;u++)c[h++]=l[m++];h+=p}break;case 3:l=i[0].items;n=i[1].items;o=i[2].items;h=3*(b*m+h);p=3*(b-k);for(q=m=0;q<j;q++){for(u=0;u<k;u++)c[h++]=l[m],c[h++]=n[m],c[h++]=o[m],m++;h+=p}break;case 4:l=i[0].items;n=i[1].items;o=i[2].items;i=i[3].items;h=4*(b*m+h);p=4*(b-k);for(q=m=0;q<j;q++){for(u=0;u<k;u++)c[h++]=l[m],c[h++]=n[m],c[h+ [...]
+i[m],m++;h+=p}}}this.buffer=c;this.bufferLength=c.length}};d.prototype.getChar=function(){r("internal error: getChar is not valid on JpxStream")};return d}(),id=function(){function d(a,b){this.str=a;this.dict=a.dict;this.decrypt=b;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){var a=this.str.getBytes(512);if(!a||0==a.length)this.eof=!0;else{var b=this.decrypt,a=b(a),b=this.bufferLength,c,d=a.length,f=this.ensureBuffer(b+d);for(c=0;c<d;c++)f[b++]=a[c] [...]
+b}};return d}(),yd=function(){function d(a){this.str=a;this.dict=a.dict;this.input=new Uint8Array(5);W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){for(var a=this.str,b=a.getByte();va.isSpace(String.fromCharCode(b));)b=a.getByte();if(!b||126===b)this.eof=!0;else{var c=this.bufferLength;if(122==b){for(var a=this.ensureBuffer(c+4),d=0;4>d;++d)a[c+d]=0;this.bufferLength+=4}else{var f=this.input;f[0]=b;for(d=1;5>d;++d){for(b=a.getByte();va.isSpace(String [...]
+a.getByte();f[d]=b;if(!b||126==b)break}a=this.ensureBuffer(c+d-1);this.bufferLength+=d-1;if(5>d){for(;5>d;++d)f[d]=117;this.eof=!0}for(d=b=0;5>d;++d)b=85*b+(f[d]-33);for(d=3;0<=d;--d)a[c+d]=b&255,b>>=8}}};return d}(),zd=function(){function d(a){this.str=a;this.dict=a.dict;W.call(this)}var a={9:-1,32:-1,48:0,49:1,50:2,51:3,52:4,53:5,54:6,55:7,56:8,57:9,65:10,66:11,67:12,68:13,69:14,70:15,97:10,98:11,99:12,100:13,101:14,102:15};d.prototype=Object.create(W.prototype);d.prototype.readBlock=f [...]
+this.str.getBytes(),c,d,f,g,i,k;f=this.ensureBuffer(this.bufferLength+(b.length+1>>1));g=this.bufferLength;for(i=0,k=b.length;i<k;i++){for(c=a[b[i]];-1==c&&i+1<k;)c=a[b[++i]];i+1<k&&62!==b[i+1]?(d=a[b[++i]],f[g++]=16*c+d):62!==b[i]&&(f[g++]=16*c)}this.bufferLength=g;this.eof=!0};return d}(),Bd=function(){function d(a){this.str=a;this.dict=a.dict;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){var a=this.str.getBytes(2);if(!a||2>a.length||128==a[0])thi [...]
+else{var b=this.bufferLength,c=a[0];if(128>c){var d=this.ensureBuffer(b+c+1);d[b++]=a[1];0<c&&(a=this.str.getBytes(c),d.set(a,b),b+=c)}else for(var c=257-c,a=a[1],d=this.ensureBuffer(b+c+1),f=0;f<c;f++)d[b++]=a;this.bufferLength=b}};return d}(),Ad=function(){function d(a,b){this.str=a;this.dict=a.dict;b=b||new xa;this.encoding=b.get("K")||0;this.eoline=b.get("EndOfLine")||!1;this.byteAlign=b.get("EncodedByteAlign")||!1;this.columns=b.get("Columns")||1728;this.rows=b.get("Rows")||0;var c= [...]
+null==c&&(c=!0);this.eoblock=c;this.black=b.get("BlackIs1")||!1;this.codingLine=new Uint32Array(this.columns+1);this.refLine=new Uint32Array(this.columns+2);this.codingLine[0]=this.columns;this.row=this.codingPos=0;this.nextLine2D=0>this.encoding;this.outputBits=this.inputBuf=this.inputBits=0;for(this.buf=Z;0==(c=this.lookBits(12));)this.eatBits(1);1==c&&this.eatBits(12);0<this.encoding&&(this.nextLine2D=!this.lookBits(1),this.eatBits(1));W.call(this)}var a=[[-1,-1],[-1,-1],[7,8],[7,7],[ [...]
+[6,5],[6,5],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[3,3],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2], [...]
+2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2]],b=[[-1,-1],[12,-2],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[12,1984],[12,2048],[12,2112],[12,2176],[12,2240],[12,2304],[11,1856],[11,1856],[11,1920],[11,1920],[12,2368] [...]
+[12,2496],[12,2560]],c=[[-1,-1],[-1,-1],[-1,-1],[-1,-1],[8,29],[8,29],[8,30],[8,30],[8,45],[8,45],[8,46],[8,46],[7,22],[7,22],[7,22],[7,22],[7,23],[7,23],[7,23],[7,23],[8,47],[8,47],[8,48],[8,48],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[6,13],[7,20],[7,20],[7,20],[7,20],[8,33],[8,33],[8,34],[8,34],[8,35],[8,35],[8,36],[8,36],[8,37],[8,37],[8,38],[8,38],[7,19],[7,19],[7,19],[7,19],[8,31],[8,31],[8,32],[8,32],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,1],[6,12],[6,12],[6,12],[6, [...]
+[6,12],[6,12],[6,12],[8,53],[8,53],[8,54],[8,54],[7,26],[7,26],[7,26],[7,26],[8,39],[8,39],[8,40],[8,40],[8,41],[8,41],[8,42],[8,42],[8,43],[8,43],[8,44],[8,44],[7,21],[7,21],[7,21],[7,21],[7,28],[7,28],[7,28],[7,28],[8,61],[8,61],[8,62],[8,62],[8,63],[8,63],[8,0],[8,0],[8,320],[8,320],[8,384],[8,384],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,10],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11],[5,11], [...]
+11],[5,11],[5,11],[5,11],[7,27],[7,27],[7,27],[7,27],[8,59],[8,59],[8,60],[8,60],[9,1472],[9,1536],[9,1600],[9,1728],[7,18],[7,18],[7,18],[7,18],[7,24],[7,24],[7,24],[7,24],[8,49],[8,49],[8,50],[8,50],[8,51],[8,51],[8,52],[8,52],[7,25],[7,25],[7,25],[7,25],[8,55],[8,55],[8,56],[8,56],[8,57],[8,57],[8,58],[8,58],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,192],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[6,1664],[8,448],[8,448],[8,512],[8,512],[9,704],[9, [...]
+[8,640],[8,576],[8,576],[9,832],[9,896],[9,960],[9,1024],[9,1088],[9,1152],[9,1216],[9,1280],[9,1344],[9,1408],[7,256],[7,256],[7,256],[7,256],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[ [...]
+[4,3],[4,3],[4,3],[4,3],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,128],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,8],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[5,9],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,16],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[6,17],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4], [...]
+4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,4],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[4,5],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,14],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[6,15],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64] [...]
+64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[5,64],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,6],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7],[4,7]],e=[[-1,-1],[-1,-1],[12,-2],[12,-2],[-1,-1],[-1, [...]
+[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[-1,-1],[11,1792],[11,1792],[11,1792],[11,1792],[12,1984],[12,1984],[12,2048],[12,2048],[12,2112],[12,2112],[12,2176],[12,2176],[12,2240],[12,2240],[12,2304],[12,2304],[11,1856],[11,1856],[11,1856],[11,1856],[11,1920],[11,1920],[11,1920],[11,1920],[12,2368],[12,2368],[12,2432],[12,2432],[12,2496],[1 [...]
+2560],[12,2560],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[10,18],[12,52],[12,52],[13,640],[13,704],[13,768],[13,832],[12,55],[12,55],[12,56],[12,56],[13,1280],[13,1344],[13,1408],[13,1472],[12,59],[12,59],[12,60],[12,60],[13,1536],[13,1600],[11,24],[11,24],[11,24],[11,24],[11,25],[11,25],[11,25],[11,25],[13,1664],[13,1728],[12,320],[12,320],[12,384],[12,384],[12,448],[12,448],[13,512],[13,576],[12,53],[12,53],[12,54],[12,54],[13,896],[13,960],[13,1024],[13,1088],[13,1152], [...]
+[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64],[10,64]],f=[[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[8,13],[11,23],[11,23],[12,50],[12,51],[12,44],[12,45],[12,46],[12,47],[12,57],[12,58],[12,61],[12,256],[10,16],[10,16],[10,16],[10,16],[10,17],[10,17],[10,17],[10,17],[12,48],[12,49],[12,62],[12,63],[12,30],[12,31],[12,32],[12,33],[12,40],[12,41],[11,22],[11,22],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14] [...]
+14],[8,14],[8,14],[8,14],[8,14],[8,14],[8,14],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,10],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11],[7,11 [...]
+[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[9,15],[12,128],[12,192],[12,26],[12,27],[12,28],[12,29],[11,19],[11,19],[11,20],[11,20],[12,34],[12,35],[12,36],[12,37],[12,38],[12,39],[11,21],[11,21],[12,42],[12,43],[10,0],[10,0],[10,0],[10,0],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12],[7,12]],g=[[-1,-1],[-1,-1],[-1,-1],[ [...]
+9],[6,8],[5,7],[5,7],[4,6],[4,6],[4,6],[4,6],[4,5],[4,5],[4,5],[4,5],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[3,4],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,3],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2],[2,2]];d.prototype=Object.create(W.prototype);d.prototype.readBlock=function(){for(;!this.eof;){var a=this.lookChar();this.buf=Z;this.ensureB [...]
+1);this.buffer[this.bufferLength++]=a}};d.prototype.addPixels=function(a,b){var c=this.codingLine,d=this.codingPos;a>c[d]&&(a>this.columns&&(Y("row is wrong length"),this.err=!0,a=this.columns),d&1^b&&++d,c[d]=a);this.codingPos=d};d.prototype.addPixelsNeg=function(a,b){var c=this.codingLine,d=this.codingPos;if(a>c[d])a>this.columns&&(Y("row is wrong length"),this.err=!0,a=this.columns),d&1^b&&++d,c[d]=a;else if(a<c[d]){0>a&&(Y("invalid code"),this.err=!0,a=0);for(;0<d&&a<c[d-1];)--d;c[d] [...]
+d};d.prototype.lookChar=function(){if(this.buf!=Z)return this.buf;var a=this.refLine,b=this.codingLine,c=this.columns,d,e,f;if(0==this.outputBits){if(this.eof)return null;this.err=!1;var g,o;if(this.nextLine2D){for(d=0;b[d]<c;++d)a[d]=b[d];a[d++]=c;a[d]=c;for(e=d=this.codingPos=b[0]=0;b[this.codingPos]<c;)switch(f=this.getTwoDimCode(),f){case 0:this.addPixels(a[d+1],e);a[d+1]<c&&(d+=2);break;case 1:f=g=0;if(e){do f+=o=this.getBlackCode();while(64<=o);do g+=o=this.getWhiteCode();while(64< [...]
+o=this.getWhiteCode();while(64<=o);do g+=o=this.getBlackCode();while(64<=o)}this.addPixels(b[this.codingPos]+f,e);for(b[this.codingPos]<c&&this.addPixels(b[this.codingPos]+g,e^1);a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 7:this.addPixels(a[d]+3,e);e^=1;if(b[this.codingPos]<c)for(++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 5:this.addPixels(a[d]+2,e);e^=1;if(b[this.codingPos]<c)for(++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 3:this.addPixels(a[d]+1,e);e^=1;if(b[thi [...]
+c)for(++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 2:this.addPixels(a[d],e);e^=1;if(b[this.codingPos]<c)for(++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 8:this.addPixelsNeg(a[d]-3,e);e^=1;if(b[this.codingPos]<c)for(0<d?--d:++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 6:this.addPixelsNeg(a[d]-2,e);e^=1;if(b[this.codingPos]<c)for(0<d?--d:++d;a[d]<=b[this.codingPos]&&a[d]<c;)d+=2;break;case 4:this.addPixelsNeg(a[d]-1,e);e^=1;if(b[this.codingPos]<c)for(0<d?--d:++d;a[d [...]
+a[d]<c;)d+=2;break;case Z:this.addPixels(c,0);this.eof=!0;break;default:Y("bad 2d code"),this.addPixels(c,0),this.err=!0}}else for(e=this.codingPos=b[0]=0;b[this.codingPos]<c;){f=0;if(e){do f+=o=this.getBlackCode();while(64<=o)}else{do f+=o=this.getWhiteCode();while(64<=o)}this.addPixels(b[this.codingPos]+f,e);e^=1}this.byteAlign&&(this.inputBits&=-8);a=!1;if(!this.eoblock&&this.row==this.rows-1)this.eof=!0;else{for(f=this.lookBits(12);0==f;)this.eatBits(1),f=this.lookBits(12);1==f?(this [...]
+a=!0):f==Z&&(this.eof=!0)}!this.eof&&0<this.encoding&&(this.nextLine2D=!this.lookBits(1),this.eatBits(1));if(this.eoblock&&a){if(f=this.lookBits(12),1==f){this.eatBits(12);0<this.encoding&&(this.lookBits(1),this.eatBits(1));if(0<=this.encoding)for(d=0;4>d;++d)f=this.lookBits(12),1!=f&&Y("bad rtc code: "+f),this.eatBits(12),0<this.encoding&&(this.lookBits(1),this.eatBits(1));this.eof=!0}}else if(this.err&&this.eoline){for(;;){f=this.lookBits(13);if(f==Z)return this.eof=!0,null;if(1==f>>1) [...]
+0<this.encoding&&(this.eatBits(1),this.nextLine2D=!(f&1))}this.outputBits=0<b[0]?b[this.codingPos=0]:b[this.codingPos=1];this.row++}if(8<=this.outputBits)this.buf=this.codingPos&1?0:255,this.outputBits-=8,0==this.outputBits&&b[this.codingPos]<c&&(this.codingPos++,this.outputBits=b[this.codingPos]-b[this.codingPos-1]);else{f=8;this.buf=0;do this.outputBits>f?(this.buf<<=f,this.codingPos&1||(this.buf|=255>>8-f),this.outputBits-=f,f=0):(this.buf<<=this.outputBits,this.codingPos&1||(this.buf [...]
+this.outputBits),f-=this.outputBits,this.outputBits=0,b[this.codingPos]<c?(this.codingPos++,this.outputBits=b[this.codingPos]-b[this.codingPos-1]):0<f&&(this.buf<<=f,f=0));while(f)}this.black&&(this.buf^=255);return this.buf};d.prototype.findTableCode=function(a,b,c,d){for(d=d||0;a<=b;++a){var e=this.lookBits(a);if(e==Z)return[!0,1,!1];a<b&&(e<<=b-a);if(!d||e>=d)if(e=c[e-d],e[0]==a)return this.eatBits(a),[!0,e[1],!0]}return[!1,0,!1]};d.prototype.getTwoDimCode=function(){var b=0;if(this.e [...]
+this.lookBits(7),(b=a[b])&&0<b[0])return this.eatBits(b[0]),b[1]}else if(b=this.findTableCode(1,7,a),b[0]&&b[2])return b[1];Y("Bad two dim code");return Z};d.prototype.getWhiteCode=function(){var a=0;if(this.eoblock){a=this.lookBits(12);if(a==Z)return 1;a=0==a>>5?b[a]:c[a>>3];if(0<a[0])return this.eatBits(a[0]),a[1]}else{a=this.findTableCode(1,9,c);if(a[0])return a[1];a=this.findTableCode(11,12,b);if(a[0])return a[1]}Y("bad white code");this.eatBits(1);return 1};d.prototype.getBlackCode= [...]
+if(this.eoblock){a=this.lookBits(13);if(a==Z)return 1;a=0==a>>7?e[a]:0==a>>9&&0!=a>>7?f[(a>>1)-64]:g[a>>7];if(0<a[0])return this.eatBits(a[0]),a[1]}else{a=this.findTableCode(2,6,g);if(a[0])return a[1];a=this.findTableCode(7,12,f,64);if(a[0])return a[1];a=this.findTableCode(10,13,e);if(a[0])return a[1]}Y("bad black code");this.eatBits(1);return 1};d.prototype.lookBits=function(a){for(var b;this.inputBits<a;){if(null==(b=this.str.getByte()))return 0==this.inputBits?Z:this.inputBuf<<a-this. [...]
+65535>>16-a;this.inputBuf=(this.inputBuf<<8)+b;this.inputBits+=8}return this.inputBuf>>this.inputBits-a&65535>>16-a};d.prototype.eatBits=function(a){if(0>(this.inputBits-=a))this.inputBits=0};return d}(),zc=function(){function d(a,b){this.str=a;this.dict=a.dict;this.bitsCached=this.cachedData=0;for(var c={earlyChange:b,codeLength:9,nextCode:258,dictionaryValues:new Uint8Array(4096),dictionaryLengths:new Uint16Array(4096),dictionaryPrevCodes:new Uint16Array(4096),currentSequence:new Uint8 [...]
+currentSequenceLength:0},d=0;256>d;++d)c.dictionaryValues[d]=d,c.dictionaryLengths[d]=1;this.lzwState=c;W.call(this)}d.prototype=Object.create(W.prototype);d.prototype.readBits=function(a){for(var b=this.bitsCached,c=this.cachedData;b<a;){var d=this.str.getByte();if(null==d)return this.eof=!0,null;c=c<<8|d;b+=8}this.bitsCached=b-=a;this.cachedData=c;this.lastCode=null;return c>>>b&(1<<a)-1};d.prototype.readBlock=function(){var a=1024,b,c,d,f=this.lzwState;if(f){var g=f.earlyChange,i=f.ne [...]
+f.dictionaryValues,j=f.dictionaryLengths,h=f.dictionaryPrevCodes,m=f.codeLength,l=f.prevCode,n=f.currentSequence,o=f.currentSequenceLength,p=0,q=this.bufferLength,u=this.ensureBuffer(this.bufferLength+a);for(b=0;512>b;b++){var O=this.readBits(m),N=0<o;if(256>O)n[0]=O,o=1;else if(258<=O)if(O<i){o=j[O];for(c=o-1,d=O;0<=c;c--)n[c]=k[d],d=h[d]}else n[o++]=n[0];else if(256==O){m=9;i=258;o=0;continue}else{this.eof=!0;delete this.lzwState;break}N&&(h[i]=l,j[i]=j[l]+1,k[i]=n[0],i++,m=i+g&i+g-1?m [...]
+g)/0.6931471805599453+1,12)|0);l=O;p+=o;if(a<p){do a+=512;while(a<p);u=this.ensureBuffer(this.bufferLength+a)}for(c=0;c<o;c++)u[q++]=n[c]}f.nextCode=i;f.codeLength=m;f.prevCode=l;f.currentSequenceLength=o;this.bufferLength=q}};return d}();"use strict";vb.prototype={on:function(d,a,b){var c=this.actionHandler;c[d]&&r('There is already an actionName called "'+d+'"');c[d]=[a,b]},send:function(d,a,b){d={action:d,data:a};b&&(a=this.callbackIndex++,this.callbacks[a]=b,d.callbackId=a);this.comO [...]
+var Lb={setup:function(d){var a=null;d.on("test",function(a){d.send("test",a instanceof Uint8Array)});d.on("GetDocRequest",function(b){a=new Lc(new Aa(b));b={numPages:a.numPages,fingerprint:a.getFingerprint(),destinations:a.catalog.destinations,outline:a.catalog.documentOutline,info:a.getDocumentInfo(),metadata:a.catalog.metadata};d.send("GetDoc",{pdfInfo:b})});d.on("GetPageRequest",function(b){var c=a.getPage(b.pageIndex+1);d.send("GetPage",{pageInfo:{pageIndex:b.pageIndex,rotate:c.rota [...]
+view:c.view}})});d.on("GetAnnotationsRequest",function(b){var c=a.getPage(b.pageIndex+1);d.send("GetAnnotations",{pageIndex:b.pageIndex,annotations:c.getAnnotations()})});d.on("RenderPageRequest",function(b){var c=b.pageIndex+1;new Oa(null);Date.now();var e=[],f=null;try{f=a.getPage(c).getOperatorList(d,e)}catch(g){g="string"===typeof g?{message:g,stack:"worker.js: while trying to getPage() and getOperatorList()"}:"object"===typeof g?{message:g.message||g.toString(),stack:g.stack||"worke [...]
+{message:"Unknown exception type: "+typeof g,stack:"worker.js: while trying to getPage() and getOperatorList()"};d.send("PageError",{pageNum:c,error:g});return}for(var c={},i=0,k=e.length;i<k;i++){var j=e[i];0==j.indexOf("font_")&&(c[j]=!0)}d.send("RenderPage",{pageIndex:b.pageIndex,operatorList:f,depFonts:Object.keys(c)})},this)}},Ac={},Ed={log:function(){var d=Array.prototype.slice.call(arguments);postMessage({action:"console_log",data:d})},error:function(){var d=Array.prototype.slice. [...]
+postMessage({action:"console_error",data:d});throw"pdf.js execution error";},time:function(d){Ac[d]=Date.now()},timeEnd:function(d){var a=Ac[d];null==a&&r("Unkown timer name "+d);this.log("Timer:",d,Date.now()-a)}};if("undefined"===typeof window){za.console=Ed;var Fd=new vb("worker_processor",this);Lb.setup(Fd)}var Cd=function(){function d(){}function a(a,b){for(var c=0,d=[],e,f,g=16;0<g&&!a[g-1];)g--;d.push({children:[],index:0});var i=d[0],h;for(e=0;e<g;e++){for(f=0;f<a[e];f++){i=d.pop [...]
+b[c];0<i.index;)i=d.pop();i.index++;for(d.push(i);d.length<=e;)d.push(h={children:[],index:0}),i.children[i.index]=h.children,i=h;c++}e+1<g&&(d.push(h={children:[],index:0}),i.children[i.index]=h.children,i=h)}return d[0].children}function b(a,b,c,d,f,g,i,h,j){function k(){if(0<w)return w--,J>>w&1;J=a[b++];if(255==J){var c=a[b++];if(c)throw"unexpected marker: "+(J<<8|c).toString(16);}w=7;return J>>>7}function l(a){for(var b;null!==(b=k());){a=a[b];if("number"===typeof a)return a;if("obje [...]
+}return null}function m(a){for(var b=0;0<a;){var c=k();if(null===c)return;b=b<<1|c;a--}return b}function F(a){var b=m(a);return b>=1<<a-1?b:b+(-1<<a)+1}function s(a,b){var c=l(a.huffmanTableDC),c=0===c?0:F(c);b[0]=a.pred+=c;for(c=1;64>c;){var d=l(a.huffmanTableAC),f=d&15,d=d>>4;if(0===f){if(15>d)break;c+=16}else c+=d,b[e[c]]=F(f),c++}}function r(a,b){var c=l(a.huffmanTableDC),c=0===c?0:F(c)<<j;b[0]=a.pred+=c}function y(a,b){b[0]|=k()<<j}function H(a,b){if(0<M)M--;else for(var c=g,d=i;c<= [...]
+l(a.huffmanTableAC),h=f&15,f=f>>4;if(0===h){if(15>f){M=m(f)+(1<<f)-1;break}c+=16}else c+=f,b[e[c]]=F(h)*(1<<j),c++}}function G(a,b){for(var c=g,d=i,f=0;c<=d;){var h=e[c];switch(B){case 0:f=l(a.huffmanTableAC);h=f&15;f>>=4;if(0===h)15>f?(M=m(f)+(1<<f),B=4):(f=16,B=1);else{if(1!==h)throw"invalid ACn encoding";z=F(h);B=f?2:3}continue;case 1:case 2:b[h]?b[h]+=k()<<j:(f--,0===f&&(B=2==B?3:0));break;case 3:b[h]?b[h]+=k()<<j:(b[h]=z<<j,B=0);break;case 4:b[h]&&(b[h]+=k()<<j)}c++}4===B&&(M--,0=== [...]
+var I=c.mcusPerLine,A=b,J=0,w=0,M=0,B=0,z,ga=d.length,D,L,E,v,K,h=c.progressive?0===g?0===h?r:y:0===h?H:G:s,Q=0,c=1==ga?d[0].blocksPerLine*d[0].blocksPerColumn:I*c.mcusPerColumn;f||(f=c);for(var R,$;Q<c;){for(L=0;L<ga;L++)d[L].pred=0;M=0;if(1==ga){D=d[0];for(K=0;K<f;K++)h(D,D.blocks[Q/D.blocksPerLine|0][Q%D.blocksPerLine]),Q++}else for(K=0;K<f;K++){for(L=0;L<ga;L++){D=d[L];R=D.h;$=D.v;for(E=0;E<$;E++)for(v=0;v<R;v++)h(D,D.blocks[(Q/I|0)*D.v+E][Q%I*D.h+v])}Q++}w=0;D=a[b]<<8|a[b+1];if(6528 [...]
+if(65488<=D&&65495>=D)b+=2;else break}return b-A}function c(a,b){for(var c=[],d=b.blocksPerLine,e=b.blocksPerColumn,O=d<<3,N=new Int32Array(64),ba=new Uint8Array(64),ha,t,x=0;x<e;x++){var C=x<<3;for(ha=0;8>ha;ha++)c.push(new Uint8Array(O));for(var F=0;F<d;F++){var s=b.blocks[x][F];ha=ba;t=N;for(var r=b.quantizationTable,y=void 0,H=void 0,G=void 0,I=void 0,A=void 0,J=void 0,w=void 0,M=void 0,B=void 0,z=void 0,z=0;64>z;z++)t[z]=s[z]*r[z];for(z=0;8>z;++z)s=8*z,0==t[1+s]&&0==t[2+s]&&0==t[3+s [...]
+s]&&0==t[5+s]&&0==t[6+s]&&0==t[7+s]?(B=m*t[0+s]+512>>10,t[0+s]=B,t[1+s]=B,t[2+s]=B,t[3+s]=B,t[4+s]=B,t[5+s]=B,t[6+s]=B,t[7+s]=B):(y=m*t[0+s]+128>>8,H=m*t[4+s]+128>>8,G=t[2+s],I=t[6+s],A=l*(t[1+s]-t[7+s])+128>>8,M=l*(t[1+s]+t[7+s])+128>>8,J=t[3+s]<<4,w=t[5+s]<<4,B=y-H+1>>1,y=y+H+1>>1,H=B,B=G*h+I*j+128>>8,G=G*j-I*h+128>>8,I=B,B=A-w+1>>1,A=A+w+1>>1,w=B,B=M+J+1>>1,J=M-J+1>>1,M=B,B=y-I+1>>1,y=y+I+1>>1,I=B,B=H-G+1>>1,H=H+G+1>>1,G=B,B=A*k+M*i+2048>>12,A=A*i-M*k+2048>>12,M=B,B=J*g+w*f+2048>>12,J [...]
+2048>>12,w=B,t[0+s]=y+M,t[7+s]=y-M,t[1+s]=H+w,t[6+s]=H-w,t[2+s]=G+J,t[5+s]=G-J,t[3+s]=I+A,t[4+s]=I-A);for(z=0;8>z;++z)s=z,0==t[8+s]&&0==t[16+s]&&0==t[24+s]&&0==t[32+s]&&0==t[40+s]&&0==t[48+s]&&0==t[56+s]?(B=m*t[z+0]+8192>>14,t[0+s]=B,t[8+s]=B,t[16+s]=B,t[24+s]=B,t[32+s]=B,t[40+s]=B,t[48+s]=B,t[56+s]=B):(y=m*t[0+s]+2048>>12,H=m*t[32+s]+2048>>12,G=t[16+s],I=t[48+s],A=l*(t[8+s]-t[56+s])+2048>>12,M=l*(t[8+s]+t[56+s])+2048>>12,J=t[24+s],w=t[40+s],B=y-H+1>>1,y=y+H+1>>1,H=B,B=G*h+I*j+2048>>12,G [...]
+2048>>12,I=B,B=A-w+1>>1,A=A+w+1>>1,w=B,B=M+J+1>>1,J=M-J+1>>1,M=B,B=y-I+1>>1,y=y+I+1>>1,I=B,B=H-G+1>>1,H=H+G+1>>1,G=B,B=A*k+M*i+2048>>12,A=A*i-M*k+2048>>12,M=B,B=J*g+w*f+2048>>12,J=J*f-w*g+2048>>12,w=B,t[0+s]=y+M,t[56+s]=y-M,t[8+s]=H+w,t[48+s]=H-w,t[16+s]=G+J,t[40+s]=G-J,t[24+s]=I+A,t[32+s]=I-A);for(z=0;64>z;++z)y=128+(t[z]+8>>4),ha[z]=0>y?0:255<y?255:y;z=0;y=F<<3;for(t=0;8>t;t++){H=c[C+t];for(ha=0;8>ha;ha++)H[y+ha]=ba[z++]}}}return c}var e=new Int32Array([0,1,8,16,9,2,3,10,17,24,32,25,18 [...]
+19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),f=4017,g=799,i=3406,k=2276,j=1567,h=3784,m=5793,l=2896;d.prototype={load:function(a){var b=new XMLHttpRequest;b.open("GET",a,!0);b.responseType="arraybuffer";b.onload=function(){this.parse(new Uint8Array(b.response||b.mozResponseArrayBuffer));if(this.onload)this.onload()}.bind(this);b.send(null)},parse:function(d){function f(){var a=d[h]<<8|d[h+1];h [...]
+function g(){var a=f(),a=d.subarray(h,h+a-2);h+=a.length;return a}function i(a){var b=0,c=0,d,e;for(e in a.components)a.components.hasOwnProperty(e)&&(d=a.components[e],b<d.h&&(b=d.h),c<d.v&&(c=d.v));var f=Math.ceil(a.samplesPerLine/8/b),g=Math.ceil(a.scanLines/8/c);for(e in a.components)if(a.components.hasOwnProperty(e)){d=a.components[e];for(var h=Math.ceil(Math.ceil(a.samplesPerLine/8)*d.h/b),j=Math.ceil(Math.ceil(a.scanLines/8)*d.v/c),k=f*d.h,l=g*d.v,m=[],n=0;n<l;n++){for(var t=[],C= [...]
+m.push(t)}d.blocksPerLine=h;d.blocksPerColumn=j;d.blocks=m}a.maxH=b;a.maxV=c;a.mcusPerLine=f;a.mcusPerColumn=g}var h=0,j=null,k=null,l,m,t=[],x=[],C=[],F=[],s=f();if(65496!=s)throw"SOI not found";for(s=f();65497!=s;){var r;switch(s){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:var y=g();65504===s&&74===y[0]&&70===y[1]&&73===y[2]&&70===y[3]&&0===y[4 [...]
+minor:y[6]},densityUnits:y[7],xDensity:y[8]<<8|y[9],yDensity:y[10]<<8|y[11],thumbWidth:y[12],thumbHeight:y[13],thumbData:y.subarray(14,14+3*y[12]*y[13])});65518===s&&65===y[0]&&100===y[1]&&111===y[2]&&98===y[3]&&101===y[4]&&0===y[5]&&(k={version:y[6],flags0:y[7]<<8|y[8],flags1:y[9]<<8|y[10],transformCode:y[11]});break;case 65499:y=Math.floor((f()-2)/65);for(s=0;s<y;s++){var H=d[h++],G=new Int32Array(64);if(0===H>>4)for(r=0;64>r;r++)G[e[r]]=d[h++];else if(1===H>>4)G[r]=f();else throw"DQT: [...]
+t[H&15]=G}break;case 65472:case 65474:f();l={};l.progressive=65474===s;l.precision=d[h++];l.scanLines=f();l.samplesPerLine=f();l.components=[];y=d[h++];for(s=0;s<y;s++)H=d[h],l.components[H]={h:d[h+1]>>4,v:d[h+1]&15,quantizationTable:t[d[h+2]]},h+=3;i(l);x.push(l);break;case 65476:y=f();for(s=2;s<y;){var H=d[h++],G=new Uint8Array(16),I=0;for(r=0;16>r;r++,h++)I+=G[r]=d[h];var A=new Uint8Array(I);for(r=0;r<I;r++,h++)A[r]=d[h];s+=17+I;(0===H>>4?F:C)[H&15]=a(G,A)}break;case 65501:f();m=f();b [...]
+H=d[h++];y=[];for(s=0;s<H;s++)G=l.components[d[h++]],I=d[h++],G.huffmanTableDC=F[I>>4],G.huffmanTableAC=C[I&15],y.push(G);s=d[h++];H=d[h++];G=d[h++];s=b(d,h,l,y,m,s,H,G>>4,G&15);h+=s;break;default:throw"unknown JPEG marker "+s.toString(16);}s=f()}if(1!=x.length)throw"only single frame JPEGs supported";this.width=l.samplesPerLine;this.height=l.scanLines;this.jfif=j;this.adobe=k;this.components=[];for(var J in l.components)l.components.hasOwnProperty(J)&&this.components.push({lines:c(l,l.c [...]
+scaleX:l.components[J].h/l.maxH,scaleY:l.components[J].v/l.maxV})},getData:function(a,b){function c(a){return 0>a?0:255<a?255:a}var d=this.width/a,e=this.height/b,f,g,i,h,j,k,l,m,s,r,y=0,H,G,I,A,J,w,M=new Uint8Array(a*b*this.components.length);switch(this.components.length){case 1:f=this.components[0];for(r=0;r<b;r++){j=f.lines[0|r*f.scaleY*e];for(s=0;s<a;s++)H=j[0|s*f.scaleX*d],M[y++]=H}break;case 3:w=!0;this.adobe&&this.adobe.transformCode?w=!0:"undefined"!==typeof this.colorTransform& [...]
+f=this.components[0];g=this.components[1];i=this.components[2];for(r=0;r<b;r++){j=f.lines[0|r*f.scaleY*e];k=g.lines[0|r*g.scaleY*e];l=i.lines[0|r*i.scaleY*e];for(s=0;s<a;s++)w?(H=j[0|s*f.scaleX*d],G=k[0|s*g.scaleX*d],I=l[0|s*i.scaleX*d],h=c(H+1.402*(I-128)),m=c(H-0.3441363*(G-128)-0.71413636*(I-128)),H=c(H+1.772*(G-128))):(h=j[0|s*f.scaleX*d],m=k[0|s*g.scaleX*d],H=l[0|s*i.scaleX*d]),M[y++]=h,M[y++]=m,M[y++]=H}break;case 4:if(!this.adobe)throw"Unsupported color mode (4 components)";w=!1;t [...]
+this.adobe.transformCode?w=!0:"undefined"!==typeof this.colorTransform&&(w=!!this.colorTransform);f=this.components[0];g=this.components[1];i=this.components[2];h=this.components[3];for(r=0;r<b;r++){j=f.lines[0|r*f.scaleY*e];k=g.lines[0|r*g.scaleY*e];l=i.lines[0|r*i.scaleY*e];m=h.lines[0|r*h.scaleY*e];for(s=0;s<a;s++)w?(H=j[0|s*f.scaleX*d],G=k[0|s*g.scaleX*d],I=l[0|s*i.scaleX*d],A=m[0|s*h.scaleX*d],J=255-c(H+1.402*(I-128)),I=255-c(H-0.3441363*(G-128)-0.71413636*(I-128)),H=255-c(H+1.772*( [...]
+(J=j[0|s*f.scaleX*d],I=k[0|s*g.scaleX*d],H=l[0|s*i.scaleX*d],A=m[0|s*h.scaleX*d]),M[y++]=J,M[y++]=I,M[y++]=H,M[y++]=A}break;default:throw"Unsupported color mode";}return M},copyToImageData:function(a){var b=a.width,c=a.height,a=a.data,d=this.getData(b,c),e=0,f=0,g,i,h,j,k,l;switch(this.components.length){case 1:for(i=0;i<c;i++)for(g=0;g<b;g++)h=d[e++],a[f++]=h,a[f++]=h,a[f++]=h,a[f++]=255;break;case 3:for(i=0;i<c;i++)for(g=0;g<b;g++)k=d[e++],l=d[e++],h=d[e++],a[f++]=k,a[f++]=l,a[f++]=h,a [...]
+break;case 4:for(i=0;i<c;i++)for(g=0;g<b;g++)k=d[e++],l=d[e++],h=d[e++],j=d[e++],k=255-clampTo8bit(k*(1-j/255)+j),l=255-clampTo8bit(l*(1-j/255)+j),h=255-clampTo8bit(h*(1-j/255)+j),a[f++]=k,a[f++]=l,a[f++]=h,a[f++]=255;break;default:throw"Unsupported color mode";}}};return d}();"use strict";var Dd=function(){function d(){this.failOnCorruptedImage=!1}function a(a,b){return a[b]<<24|a[b+1]<<16|a[b+2]<<8|a[b+3]}function b(a,b){return a[b]<<8|a[b+1]}function c(a){for(var b=1,c=0;a>b;)b<<=1,c+ [...]
+function e(a,b,c){for(var a=c.xcb_,d=c.ycb_,e=1<<a,f=1<<d,g=Math.floor(b.tbx0/e),i=Math.floor(b.tby0/f),h=Math.ceil(b.tbx1/e),j=Math.ceil(b.tby1/f),k=b.resolution.precinctParameters,l=[],c=[],m=i;m<j;m++)for(i=g;i<h;i++){var n={cbx:i,cby:m,tbx0:e*i,tby0:f*m,tbx1:e*(i+1),tby1:f*(m+1)},o=Math.floor((n.tby0-k.precinctYOffset)/k.precinctHeight)+Math.floor((n.tbx0-k.precinctXOffset)/k.precinctWidth)*k.numprecinctswide;n.tbx0_=Math.max(b.tbx0,n.tbx0);n.tby0_=Math.max(b.tby0,n.tby0);n.tbx1_=Mat [...]
+n.tbx1);n.tby1_=Math.min(b.tby1,n.tby1);n.precinctNumber=o;n.subbandType=b.type;n.Lblock=3;l.push(n);o in c?(o=c[o],o.cbxMin=Math.min(o.cbxMin,i),o.cbyMin=Math.min(o.cbyMin,m),o.cbxMax=Math.max(o.cbxMax,i),o.cbyMax=Math.max(o.cbyMax,m)):c[o]=o={cbxMin:i,cbyMin:m,cbxMax:i,cbyMax:m};n.precinct=o}b.codeblockParameters={codeblockWidth:a,codeblockHeight:d,numcodeblockwide:h-g+1,numcodeblockhigh:j-j+1};b.codeblocks=l;i=0;for(a=l.length;i<a;i++);b.precincts=c}function f(a,b,c){for(var d=[],a=a. [...]
+e=0,f=a.length;e<f;e++)for(var g=a[e].codeblocks,i=0,h=g.length;i<h;i++){var j=g[i];j.precinctNumber==b&&d.push(j)}return{layerNumber:c,codeblocks:d}}function g(a){for(var b=a.tiles[a.currentTile.index],c=b.codingStyleDefaultParameters.layersCount,d=a.SIZ.Csiz,e=0,a=0;a<d;a++)e=Math.max(e,b.components[a].codingStyleParameters.decompositionLevelsCount);var g=0,i=0,h=0,j=0;this.nextPacket=function(){for(;g<c;g++){for(;i<=e;i++){for(;h<d;h++){var a=b.components[h];if(!(i>a.codingStyleParame [...]
+a.resolutions[i],k=a.precinctParameters.numprecincts;j<k;)return a=f(a,j,g),j++,a;j=0}}h=0}i=0}throw"Out of packets";}}function i(a){for(var b=a.tiles[a.currentTile.index],c=b.codingStyleDefaultParameters.layersCount,d=a.SIZ.Csiz,e=0,a=0;a<d;a++)e=Math.max(e,b.components[a].codingStyleParameters.decompositionLevelsCount);var g=0,i=0,h=0,j=0;this.nextPacket=function(){for(;g<=e;g++){for(;i<c;i++){for(;h<d;h++){var a=b.components[h];if(!(g>a.codingStyleParameters.decompositionLevelsCount)) [...]
+a.resolutions[g],k=a.precinctParameters.numprecincts;j<k;)return a=f(a,j,i),j++,a;j=0}}h=0}i=0}throw"Out of packets";}}function k(a,b,d,e){function f(a){for(;l<a;){var c=b[d+j];j++;n?(k=k<<7|c,l+=7,n=!1):(k=k<<8|c,l+=8);255==c&&(n=!0)}l-=a;return k>>>l&(1<<a)-1}function g(){l=0;n&&(j++,n=!1)}function i(){var a=f(1);if(0==a)return 1;a=a<<1|f(1);if(2==a)return 2;a=a<<2|f(2);if(14>=a)return(a&3)+3;a=a<<5|f(5);if(510>=a)return(a&31)+6;a=a<<7|f(7);return(a&127)+37}for(var j=0,k,l=0,n=!1,a=a.t [...]
+e;){var o=a.nextPacket();if(f(1)){for(var r=o.layerNumber,p=[],q=0,A=o.codeblocks.length;q<A;q++){var J=o.codeblocks[q],w=J.precinct,M=J.cbx-w.cbxMin,B=J.cby-w.cbyMin,z=!1,ga=!1;if("included"in J)z=!!f(1);else{var w=J.precinct,D,L;if("inclusionTree"in w)D=w.inclusionTree;else{L=w.cbxMax-w.cbxMin+1;var E=w.cbyMax-w.cbyMin+1;D=new m(L,E,r);L=new h(L,E);w.inclusionTree=D;w.zeroBitPlanesTree=L}if(D.reset(M,B,r))for(;;)if(f(1)){if(E=!D.nextLevel()){z=ga=J.included=!0;break}}else{D.incrementVa [...]
+w.zeroBitPlanesTree;for(L.reset(M,B);;)if(f(1)){if(E=!L.nextLevel())break}else L.incrementValue();J.zeroBitPlanes=L.value}for(w=i();f(1);)J.Lblock++;M=c(w);M=f((w<1<<M?M-1:M)+J.Lblock);p.push({codeblock:J,codingpasses:w,dataLength:M})}}for(g();0<p.length;)o=p.shift(),J=o.codeblock,"data"in J||(J.data=[]),J.data.push({data:b,start:d+j,end:d+j+o.dataLength,codingpasses:o.codingpasses}),j+=o.dataLength}else g()}return j}var j={LL:0,LH:1,HL:1,HH:2};d.prototype={load:function(a){var b=new XML [...]
+b.open("GET",a,!0);b.responseType="arraybuffer";b.onload=function(){this.parse(new Uint8Array(b.response||b.mozResponseArrayBuffer));if(this.onload)this.onload()}.bind(this);b.send(null)},parse:function(a){function b(a,c,d){for(var e=0,f=0;f<d;f++)e=256*e+(a[c+f]&255);return e}for(var c=0,d=a.length;c<d;){var e=8,f=b(a,c,4),g=b(a,c+4,4),c=c+e;1==f&&(f=b(a,c,8),c+=8,e+=8);0==f&&(f=d-c+e);f<e&&r("JPX error: Invalid box field size");e=f-e;f=!0;switch(g){case 1785737832:f=!1;break;case 17857 [...]
+c,c+e)}f&&(c+=e)}},parseCodestream:function(c,d,f){var h={};try{for(var m=d;m<f;){var o=b(c,m),m=m+2,x=0,C;switch(o){case 65359:h.mainHeader=!0;break;case 65497:break;case 65361:var x=b(c,m),F={};F.Xsiz=a(c,m+4);F.Ysiz=a(c,m+8);F.XOsiz=a(c,m+12);F.YOsiz=a(c,m+16);F.XTsiz=a(c,m+20);F.YTsiz=a(c,m+24);F.XTOsiz=a(c,m+28);F.YTOsiz=a(c,m+32);var s=b(c,m+36);F.Csiz=s;var Sa=[];C=m+38;for(var y=0;y<s;y++){var H={precision:(c[C]&127)+1,isSigned:!!(c[C]&128),XRsiz:c[C+1],YRsiz:c[C+1]},G=H,I=F;G.x0 [...]
+G.XRsiz);G.x1=Math.ceil(I.Xsiz/G.XRsiz);G.y0=Math.ceil(I.YOsiz/G.YRsiz);G.y1=Math.ceil(I.Ysiz/G.YRsiz);G.width=G.x1-G.x0;G.height=G.y1-G.y0;Sa.push(H)}h.SIZ=F;h.components=Sa;for(var A=h,J=Sa,w=A.SIZ,M=[],B=Math.ceil((w.Xsiz-w.XTOsiz)/w.XTsiz),z=Math.ceil((w.Ysiz-w.YTOsiz)/w.YTsiz),ga=0;ga<z;ga++)for(var D=0;D<B;D++){var L={};L.tx0=Math.max(w.XTOsiz+D*w.XTsiz,w.XOsiz);L.ty0=Math.max(w.YTOsiz+ga*w.YTsiz,w.YOsiz);L.tx1=Math.min(w.XTOsiz+(D+1)*w.XTsiz,w.Xsiz);L.ty1=Math.min(w.YTOsiz+(ga+1)* [...]
+L.width=L.tx1-L.tx0;L.height=L.ty1-L.ty0;L.components=[];M.push(L)}A.tiles=M;for(var E=0,v=w.Csiz;E<v;E++)for(var K=J[E],Q=0,R=M.length;Q<R;Q++){var $={},L=M[Q];$.tcx0=Math.ceil(L.tx0/K.XRsiz);$.tcy0=Math.ceil(L.ty0/K.YRsiz);$.tcx1=Math.ceil(L.tx1/K.XRsiz);$.tcy1=Math.ceil(L.ty1/K.YRsiz);$.width=$.tcx1-$.tcx0;$.height=$.tcy1-$.tcy0;L.components[E]=$}h.QCC=[];h.COC=[];break;case 65372:var x=b(c,m),V={};C=m+2;var W=c[C++],T,P;switch(W&31){case 0:T=8;P=!0;break;case 1:T=16;P=!1;break;case 2 [...]
+break;default:throw"Invalid SQcd value "+W;}V.noQuantization=8==T;V.scalarExpounded=P;V.guardBits=W>>5;for(var aa=[];C<x+m;){var U={};8==T?(U.epsilon=c[C++]>>3,U.mu=0):(U.epsilon=c[C]>>3,U.mu=(c[C]&7)<<8|c[C+1],C+=2);aa.push(U)}V.SPqcds=aa;h.mainHeader?h.QCD=V:(h.currentTile.QCD=V,h.currentTile.QCC=[]);break;case 65373:var x=b(c,m),Z={};C=m+2;var ca;257>h.SIZ.Csiz?ca=c[C++]:(ca=b(c,C),C+=2);W=c[C++];switch(W&31){case 0:T=8;P=!0;break;case 1:T=16;P=!1;break;case 2:T=16;P=!0;break;default: [...]
+W;}Z.noQuantization=8==T;Z.scalarExpounded=P;Z.guardBits=W>>5;for(aa=[];C<x+m;)U={},8==T?(U.epsilon=c[C++]>>3,U.mu=0):(U.epsilon=c[C]>>3,U.mu=(c[C]&7)<<8|c[C+1],C+=2),aa.push(U);Z.SPqcds=aa;h.mainHeader?h.QCC[ca]=Z:h.currentTile.QCC[ca]=Z;break;case 65362:var x=b(c,m),X={};C=m+2;var ea=c[C++];X.entropyCoderWithCustomPrecincts=!!(ea&1);X.sopMarkerUsed=!!(ea&2);X.ephMarkerUsed=!!(ea&4);X.progressionOrder=c[C++];X.layersCount=b(c,C);C+=2;X.multipleComponentTransform=c[C++];X.decompositionLe [...]
+c[C++];X.xcb=(c[C++]&15)+2;X.ycb=(c[C++]&15)+2;var fa=c[C++];X.selectiveArithmeticCodingBypass=!!(fa&1);X.resetContextProbabilities=!!(fa&2);X.terminationOnEachCodingPass=!!(fa&4);X.verticalyStripe=!!(fa&8);X.predictableTermination=!!(fa&16);X.segmentationSymbolUsed=!!(fa&32);X.transformation=c[C++];if(X.entropyCoderWithCustomPrecincts){for(var na={};C<x+m;){var oa=c[C];na.push({PPx:oa&15,PPy:oa>>4})}X.precinctsSizes=na}if(X.sopMarkerUsed||X.ephMarkerUsed||X.selectiveArithmeticCodingBypa [...]
+X.terminationOnEachCodingPass||X.verticalyStripe||X.predictableTermination||X.segmentationSymbolUsed)throw"Unsupported COD options: "+uneval(X);h.mainHeader?h.COD=X:(h.currentTile.COD=X,h.currentTile.COC=[]);break;case 65424:var x=b(c,m),ma={};ma.index=b(c,m+2);ma.length=a(c,m+4);ma.dataEnd=ma.length+m-2;ma.partIndex=c[m+8];ma.partsCount=c[m+9];h.mainHeader=!1;0==ma.partIndex&&(ma.COD=h.COD,ma.COC=h.COC.slice(0),ma.QCD=h.QCD,ma.QCC=h.QCC.slice(0));h.currentTile=ma;break;case 65427:ma=h.c [...]
+if(0==ma.partIndex){for(var ia=h,xa=ia.SIZ.Csiz,va=ia.tiles[ma.index],la=0;la<xa;la++){var za=va.components[la];za.quantizationParameters=la in ia.currentTile.QCC?ia.currentTile.QCC[la]:ia.currentTile.QCD;za.codingStyleParameters=la in ia.currentTile.COC?ia.currentTile.COC[la]:ia.currentTile.COD}va.codingStyleDefaultParameters=ia.currentTile.COD;for(var ja=h,ua=ja.tiles[ja.currentTile.index],Ja=ja.SIZ.Csiz,ya=0;ya<Ja;ya++){for(var da=ua.components[ya],Aa=da.codingStyleParameters.decompos [...]
+Fa=[],ta=[],sa=0;sa<=Aa;sa++){var pa,Ca=sa,mb=da.codingStyleParameters,Ha={};mb.entropyCoderWithCustomPrecincts?(Ha.PPx=mb.precinctsSizes[Ca].PPx,Ha.PPy=mb.precinctsSizes[Ca].PPy):(Ha.PPx=15,Ha.PPy=15);Ha.xcb_=0<Ca?Math.min(mb.xcb,Ha.PPx-1):Math.min(mb.xcb,Ha.PPx);Ha.ycb_=0<Ca?Math.min(mb.ycb,Ha.PPy-1):Math.min(mb.ycb,Ha.PPy);pa=Ha;var Da={},cb=1<<Aa-sa;Da.trx0=Math.ceil(da.tcx0/cb);Da.try0=Math.ceil(da.tcy0/cb);Da.trx1=Math.ceil(da.tcx1/cb);Da.try1=Math.ceil(da.tcy1/cb);var Ia=Da,wa=1<< [...]
+1<<pa.PPy,Pa=Ia.trx1>Ia.trx0?Math.ceil(Ia.trx1/wa)-Math.floor(Ia.trx0/wa):0,Qa=Ia.try1>Ia.try0?Math.ceil(Ia.try1/Ba)-Math.floor(Ia.try0/Ba):0;Ia.precinctParameters={precinctXOffset:Math.floor(Ia.trx0/wa)*wa,precinctYOffset:Math.floor(Ia.try0/Ba)*Ba,precinctWidth:wa,precinctHeight:Ba,numprecinctswide:Pa,numprecinctshigh:Qa,numprecincts:Pa*Qa};Fa.push(Da);var S;if(0==sa)S={type:"LL"},S.tbx0=Math.ceil(da.tcx0/cb),S.tby0=Math.ceil(da.tcy0/cb),S.tbx1=Math.ceil(da.tcx1/cb),S.tby1=Math.ceil(da. [...]
+S.resolution=Da,e(ja,S,pa),ta.push(S),Da.subbands=[S];else{var Ea=1<<Aa-sa+1,Ga=[];S={type:"HL"};S.tbx0=Math.ceil(da.tcx0/Ea-0.5);S.tby0=Math.ceil(da.tcy0/Ea);S.tbx1=Math.ceil(da.tcx1/Ea-0.5);S.tby1=Math.ceil(da.tcy1/Ea);S.resolution=Da;e(ja,S,pa);ta.push(S);Ga.push(S);S={type:"LH"};S.tbx0=Math.ceil(da.tcx0/Ea);S.tby0=Math.ceil(da.tcy0/Ea-0.5);S.tbx1=Math.ceil(da.tcx1/Ea);S.tby1=Math.ceil(da.tcy1/Ea-0.5);S.resolution=Da;e(ja,S,pa);ta.push(S);Ga.push(S);S={type:"HH"};S.tbx0=Math.ceil(da.t [...]
+S.tby0=Math.ceil(da.tcy0/Ea-0.5);S.tbx1=Math.ceil(da.tcx1/Ea-0.5);S.tby1=Math.ceil(da.tcy1/Ea-0.5);S.resolution=Da;e(ja,S,pa);ta.push(S);Ga.push(S);Da.subbands=Ga}}da.resolutions=Fa;da.subbands=ta}var Ua=ua.codingStyleDefaultParameters.progressionOrder;switch(Ua){case 0:ua.packetsIterator=new g(ja);break;case 1:ua.packetsIterator=new i(ja);break;default:throw"Unsupported progression order "+Ua;}}x=ma.dataEnd-m;k(h,c,m,x);break;case 65380:x=b(c,m);break;default:throw"Unknown codestream co [...]
+}m+=x}}catch(Va){this.failOnCorruptedImage?r("JPX error: "+Va):Y("JPX error: "+Va+". Trying to recover")}for(var Wa=h.components,Ma=h.SIZ.Csiz,Xa=[],Oa=0,ib=h.tiles.length;Oa<ib;Oa++){for(var Za=h.tiles[Oa],db=[],ra=0;ra<Ma;ra++){for(var nb=Za.components[ra],Ra=nb.codingStyleParameters,Ta=nb.quantizationParameters,rb=Ra.decompositionLevelsCount,Na=Ta.SPqcds,ub=Ta.scalarExpounded,vb=Ta.guardBits,Ya=Ra.transformation,wb=h.components[ra].precision,jb=[],$a=0,Rb=0;Rb<=rb;Rb++)for(var lb=nb.r [...]
+bb=0,Bb=lb.subbands.length;bb<Bb;bb++){var gb,Yb;ub?(gb=Na[$a].mu,Yb=Na[$a].epsilon):(gb=Na[0].mu,Yb=Na[0].epsilon+(0<Rb?1-Rb:0));for(var eb=lb.subbands[bb],hb=eb.tbx1-eb.tbx0,sb=eb.tby1-eb.tby0,Cb=Math.pow(2,wb+j[eb.type]-Yb)*(1+gb/2048),Db=vb+Yb-1,tb=new Float32Array(hb*sb),Gb=tb,Hb=eb.tbx0,Ib=eb.tby0,xb=hb,Qb=Cb,zb=Db,Ab=eb.codeblocks,Ub=Ya,dc=0,Vb=Ab.length;dc<Vb;++dc){var Ka=Ab[dc],Zb=Ka.tbx1_-Ka.tbx0_,ec=Ka.tby1_-Ka.tby0_;if(!(0==Zb||0==ec)&&"data"in Ka){var fb,$b;fb=new n(Zb,ec,Ka [...]
+Ka.zeroBitPlanes);$b=2;for(var ac=Ka.data,bc=0,fc=0,La=0,gc=ac.length;La<gc;La++)var ob=ac[La],bc=bc+(ob.end-ob.start),fc=fc+ob.codingpasses;for(var Eb=new Uint8Array(bc),yb=0,La=0,gc=ac.length;La<gc;La++){var ob=ac[La],Fb=ob.data.subarray(ob.start,ob.end);Eb.set(Fb,yb);yb+=Fb.length}var Wb=new l(Eb,0,bc);fb.setDecoder(Wb);for(La=0;La<fc;La++){switch($b){case 0:fb.runSignificancePropogationPass();break;case 1:fb.runMagnitudeRefinementPass();break;case 2:fb.runCleanupPass()}$b=($b+1)%3}fo [...]
+Ka.tbx0_-Hb+(Ka.tby0_-Ib)*xb,cc=0,Jb=0;Jb<ec;Jb++){for(yb=0;yb<Zb;yb++){var pb=(fb.coefficentsSign[cc]?-1:1)*fb.coefficentsMagnitude[cc],Kb=fb.bitsDecoded[cc],ic;0==Ub||zb>Kb?(pb+=0>pb?pb-0.5:0<pb?pb+0.5:0,ic=1<<zb-Kb):ic=1;Gb[hc++]=pb*ic*Qb;cc++}hc+=xb-Zb}}}jb.push({width:hb,height:sb,items:tb});$a++}var Ya=Ra.transformation,jc=(0==Ya?new p:new q).calculate(jb,nb.tcx0,nb.tcy0);db.push({left:nb.tcx0,top:nb.tcy0,width:jc.width,height:jc.height,items:jc.items})}if(Za.codingStyleDefaultPara [...]
+db[0].items,Lb=db[1].items,Mb=db[2].items,ka=0,Sb=kc.length;ka<Sb;ka++){var Nb=Lb[ka],Ob=Mb[ka],lc=kc[ka]-(Ob+Nb>>2);Lb[ka]=lc;kc[ka]=Ob+lc;Mb[ka]=Nb+lc}for(ra=0;ra<Ma;ra++){var Tb=Wa[ra];if(!Tb.isSigned)for(var mc=1<<Tb.precision-1,nc=db[ra],qb=nc.items,ka=0,Sb=qb.length;ka<Sb;ka++)qb[ka]+=mc}for(ra=0;ra<Ma;ra++){for(var Tb=Wa[ra],mc=Tb.isSigned?128:0,Xb=Tb.precision-8,nc=db[ra],qb=nc.items,Pb=new Uint8Array(qb.length),ka=0,Sb=qb.length;ka<Sb;ka++){var oc=(qb[ka]>>Xb)+mc;Pb[ka]=0>oc?0:2 [...]
+oc}db[ra].items=Pb}Xa.push(db)}this.tiles=Xa;this.width=h.SIZ.Xsiz-h.SIZ.XOsiz;this.height=h.SIZ.Ysiz-h.SIZ.YOsiz;this.componentsCount=h.SIZ.Csiz}};var h=function(){function a(b,d){var e=c(Math.max(b,d))+1;this.levels=[];for(var f=0;f<e;f++)this.levels.push({width:b,height:d,items:[]}),b=Math.ceil(b/2),d=Math.ceil(d/2)}a.prototype={reset:function(a,b){for(var c=0,d=0;c<this.levels.length;){var e=this.levels[c],f=a+b*e.width;if(f in e.items){d=e.items[f];break}e.index=f;a>>=1;b>>=1;c++}c- [...]
+e.items[e.index]=d;this.currentLevel=c;delete this.value},incrementValue:function(){var a=this.levels[this.currentLevel];a.items[a.index]++},nextLevel:function(){var a=this.currentLevel,b=this.levels[a],c=b.items[b.index];a--;if(0>a)return this.value=c,!1;this.currentLevel=a;b=this.levels[a];b.items[b.index]=c;return!0}};return a}(),m=function(){function a(b,d,e){var f=c(Math.max(b,d))+1;this.levels=[];for(var g=0;g<f;g++){for(var h=new Uint8Array(b*d),i=0,j=h.length;i<j;i++)h[i]=e;this. [...]
+height:d,items:h});b=Math.ceil(b/2);d=Math.ceil(d/2)}}a.prototype={reset:function(a,b,c){for(var d=0;d<this.levels.length;){var e=this.levels[d],f=a+b*e.width;e.index=f;e=e.items[f];if(255==e)break;if(e>c)return this.currentLevel=d,this.propagateValues(),!1;a>>=1;b>>=1;d++}this.currentLevel=d-1;return!0},incrementValue:function(a){var b=this.levels[this.currentLevel];b.items[b.index]=a+1;this.propagateValues()},propagateValues:function(){for(var a=this.currentLevel,b=this.levels[a],c=b.i [...]
+--a;)b=this.levels[a],b.items[b.index]=c},nextLevel:function(){var a=this.currentLevel,b=this.levels[a],c=b.items[b.index];b.items[b.index]=255;a--;if(0>a)return!1;this.currentLevel=a;b=this.levels[a];b.items[b.index]=c;return!0}};return a}(),l=function(){function a(b,c,d){this.data=b;this.bp=c;this.dataEnd=d;this.chigh=b[c];this.clow=0;this.byteIn();this.chigh=this.chigh<<7&65535|this.clow>>9&127;this.clow=this.clow<<7&65535;this.ct-=7;this.a=32768}var b=[{qe:22017,nmps:1,nlps:1,switchF [...]
+nmps:2,nlps:6,switchFlag:0},{qe:6145,nmps:3,nlps:9,switchFlag:0},{qe:2753,nmps:4,nlps:12,switchFlag:0},{qe:1313,nmps:5,nlps:29,switchFlag:0},{qe:545,nmps:38,nlps:33,switchFlag:0},{qe:22017,nmps:7,nlps:6,switchFlag:1},{qe:21505,nmps:8,nlps:14,switchFlag:0},{qe:18433,nmps:9,nlps:14,switchFlag:0},{qe:14337,nmps:10,nlps:14,switchFlag:0},{qe:12289,nmps:11,nlps:17,switchFlag:0},{qe:9217,nmps:12,nlps:18,switchFlag:0},{qe:7169,nmps:13,nlps:20,switchFlag:0},{qe:5633,nmps:29,nlps:21,switchFlag:0}, [...]
+nmps:15,nlps:14,switchFlag:1},{qe:21505,nmps:16,nlps:14,switchFlag:0},{qe:20737,nmps:17,nlps:15,switchFlag:0},{qe:18433,nmps:18,nlps:16,switchFlag:0},{qe:14337,nmps:19,nlps:17,switchFlag:0},{qe:13313,nmps:20,nlps:18,switchFlag:0},{qe:12289,nmps:21,nlps:19,switchFlag:0},{qe:10241,nmps:22,nlps:19,switchFlag:0},{qe:9217,nmps:23,nlps:20,switchFlag:0},{qe:8705,nmps:24,nlps:21,switchFlag:0},{qe:7169,nmps:25,nlps:22,switchFlag:0},{qe:6145,nmps:26,nlps:23,switchFlag:0},{qe:5633,nmps:27,nlps:24,s [...]
+{qe:5121,nmps:28,nlps:25,switchFlag:0},{qe:4609,nmps:29,nlps:26,switchFlag:0},{qe:4353,nmps:30,nlps:27,switchFlag:0},{qe:2753,nmps:31,nlps:28,switchFlag:0},{qe:2497,nmps:32,nlps:29,switchFlag:0},{qe:2209,nmps:33,nlps:30,switchFlag:0},{qe:1313,nmps:34,nlps:31,switchFlag:0},{qe:1089,nmps:35,nlps:32,switchFlag:0},{qe:673,nmps:36,nlps:33,switchFlag:0},{qe:545,nmps:37,nlps:34,switchFlag:0},{qe:321,nmps:38,nlps:35,switchFlag:0},{qe:273,nmps:39,nlps:36,switchFlag:0},{qe:133,nmps:40,nlps:37,swit [...]
+{qe:73,nmps:41,nlps:38,switchFlag:0},{qe:37,nmps:42,nlps:39,switchFlag:0},{qe:21,nmps:43,nlps:40,switchFlag:0},{qe:9,nmps:44,nlps:41,switchFlag:0},{qe:5,nmps:45,nlps:42,switchFlag:0},{qe:1,nmps:45,nlps:43,switchFlag:0},{qe:22017,nmps:46,nlps:46,switchFlag:0}];a.prototype={byteIn:function(){var a=this.data,b=this.bp;255==a[b]?143<a[b+1]?(this.clow+=65280,this.ct=8):(b++,this.clow+=a[b]<<9,this.ct=7,this.bp=b):(b++,this.clow+=b<this.dataEnd?a[b]<<8:65280,this.ct=8,this.bp=b);65535<this.clo [...]
+this.clow>>16,this.clow&=65535)},readBit:function(a){var c=b[a.index].qe;this.a-=c;if(this.chigh<c)return a=this.exchangeLps(a),this.renormD(),a;this.chigh-=c;return 0==(this.a&32768)?(a=this.exchangeMps(a),this.renormD(),a):a.mps},renormD:function(){do 0==this.ct&&this.byteIn(),this.a<<=1,this.chigh=this.chigh<<1&65535|this.clow>>15&1,this.clow=this.clow<<1&65535,this.ct--;while(0==(this.a&32768))},exchangeMps:function(a){var c,d=b[a.index];this.a<d.qe?(c=1-a.mps,1==d.switchFlag&&(a.mps [...]
+a.index=d.nlps):(c=a.mps,a.index=d.nmps);return c},exchangeLps:function(a){var c,d=b[a.index];this.a<d.qe?(this.a=d.qe,c=a.mps,a.index=d.nmps):(this.a=d.qe,c=1-a.mps,1==d.switchFlag&&(a.mps=1-a.mps),a.index=d.nlps);return c}};return a}(),n=function(){function a(e,f,g,h){this.width=e;this.height=f;this.contextLabelTable="HH"==g?d:"HL"==g?c:b;e*=f;this.neighborsSignificance=new Uint8Array(e);this.coefficentsSign=new Uint8Array(e);this.coefficentsMagnitude=new Uint32Array(e);this.processing [...]
+e=new Uint8Array(this.width*this.height);f=0;for(g=e.length;f<g;f++)e[f]=h;this.bitsDecoded=e;this.reset()}var b=new Uint8Array([0,5,8,0,3,7,8,0,4,7,8,0,0,0,0,0,1,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8,0,0,0,0,0,2,6,8,0,3,7,8,0,4,7,8]),c=new Uint8Array([0,3,4,0,5,7,7,0,8,8,8,0,0,0,0,0,1,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8,0,0,0,0,0,2,3,4,0,6,7,7,0,8,8,8]),d=new Uint8Array([0,1,2,0,1,2,2,0,2,2,2,0 [...]
+3,4,5,0,4,5,5,0,5,5,5,0,0,0,0,0,6,7,7,0,7,7,7,0,7,7,7,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8,0,0,0,0,0,8,8,8,0,8,8,8,0,8,8,8]),e=[{contextLabel:13,xorBit:0},{contextLabel:12,xorBit:0},{contextLabel:11,xorBit:0},{contextLabel:10,xorBit:0},{contextLabel:9,xorBit:0},{contextLabel:10,xorBit:1},{contextLabel:11,xorBit:1},{contextLabel:12,xorBit:1},{contextLabel:13,xorBit:1}];a.prototype={setDecoder:function(a){this.decoder=a},reset:function(){this.uniformContext={index:46,mps:0};this.runLengthContex [...]
+mps:0};this.contexts=[];this.contexts.push({index:4,mps:0});for(var a=1;16>=a;a++)this.contexts.push({index:0,mps:0})},setNeighborsSignificance:function(a,b){var c=this.neighborsSignificance,d=this.width,e=this.height,f=a*d+b;0<a&&(0<b&&(c[f-d-1]+=16),b+1<d&&(c[f-d+1]+=16),c[f-d]+=4);a+1<e&&(0<b&&(c[f+d-1]+=16),b+1<d&&(c[f+d+1]+=16),c[f+d]+=4);0<b&&(c[f-1]+=1);b+1<d&&(c[f+1]+=1);c[f]|=128},runSignificancePropogationPass:function(){for(var a=this.decoder,b=this.width,c=this.height,d=this. [...]
+e=this.coefficentsSign,f=this.neighborsSignificance,g=this.processingFlags,h=this.contexts,i=this.contextLabelTable,j=this.bitsDecoded,k=0,l=b*c;k<l;k++)g[k]&=-2;for(k=0;k<c;k+=4)for(l=0;l<b;l++)for(var m=k*b+l,n=0;4>n;n++,m+=b){var o=k+n;if(o>=c)break;if(!d[m]&&f[m]){if(a.readBit(h[i[f[m]]])){var r=this.decodeSignBit(o,l);e[m]=r;d[m]=1;this.setNeighborsSignificance(o,l);g[m]|=2}j[m]++;g[m]|=1}}},decodeSignBit:function(a,b){var c=this.width,d=a*c+b,f=this.coefficentsMagnitude,g=this.coef [...]
+c=e[3*(1-(b+1<c&&f[d+1]?g[d+1]?0<b&&f[d-1]?!g[d-1]?0:-1:-1:0<b&&f[d-1]?!g[d-1]?1:0:1:0<b&&f[d-1]?!g[d-1]?1:-1:0))+(1-(a+1<this.height&&f[d+c]?g[d+c]?0<a&&f[d-c]?!g[d-c]?0:-1:-1:0<a&&f[d-c]?!g[d-c]?1:0:1:0<a&&f[d-c]?!g[d-c]?1:-1:0))];return this.decoder.readBit(this.contexts[c.contextLabel])^c.xorBit},runMagnitudeRefinementPass:function(){for(var a=this.decoder,b=this.width,c=this.height,d=this.coefficentsMagnitude,e=this.neighborsSignificance,f=this.contexts,g=this.bitsDecoded,h=this.pro [...]
+i=0;i<c;i+=4)for(var j=0;j<b;j++)for(var k=0;4>k;k++){var m=i+k;if(m>=c)break;var l=m*b+j;if(d[l]&&0==(h[l]&1)){var n=16;0!=(h[l]&2)&&(h[m*b+j]^=2,m=e[l],n=1<=(m&3)+(m>>2&3)+(m>>4&7)?15:14);m=a.readBit(f[n]);d[l]=d[l]<<1|m;g[l]++;h[l]|=1}}},runCleanupPass:function(){for(var a=this.decoder,b=this.width,c=this.height,d=this.neighborsSignificance,e=this.coefficentsMagnitude,f=this.coefficentsSign,g=this.contexts,h=this.contextLabelTable,i=this.bitsDecoded,j=this.processingFlags,k=2*b,m=3*b, [...]
+4)for(var n=0;n<b;n++){var o=l*b+n,r=0,p=o,q,u;if(l+3<c&&0==j[o]&&0==j[o+b]&&0==j[o+k]&&0==j[o+m]&&0==d[o]&&0==d[o+b]&&0==d[o+k]&&0==d[o+m]){q=this.runLengthContext;if(!a.readBit(q)){i[o]++;i[o+b]++;i[o+k]++;i[o+m]++;continue}q=this.uniformContext;r=a.readBit(q)<<1|a.readBit(q);u=l+r;p+=r*b;q=this.decodeSignBit(u,n);f[p]=q;e[p]=1;this.setNeighborsSignificance(u,n);j[p]|=2;p=o;for(o=l;o<=u;o++,p+=b)i[p]++;r++}for(;4>r;r++,p+=b){u=l+r;if(u>=c)break;e[p]||0!=(j[p]&1)||(q=g[h[d[p]]],1==a.rea [...]
+(q=this.decodeSignBit(u,n),f[p]=q,e[p]=1,this.setNeighborsSignificance(u,n),j[p]|=2),i[p]++)}}}};return a}(),o=function(){function a(){}a.prototype.calculate=function(a,b,c){for(var d=a[0],e=1,f=a.length,g=1;e<f;e+=3,g++)d=this.iterate(d,a[e],a[e+1],a[e+2],b,c);return d};a.prototype.iterate=function(a,b,c,d,e,f){for(var g=a.width,h=a.height,a=a.items,i=b.width,j=b.height,k=b.items,l=c.width,m=c.height,n=c.items,o=d.width,p=d.height,r=d.items,d=g+i,c=h+m,b=new Float32Array(d*c),q=0,u=h;q< [...]
+q*g,z=2*q*d,E=0,D=g;E<D;E++,h++,z+=2)b[z]=a[h];q=0;for(u=j;q<u;q++){h=q*i;z=2*q*d+1;E=0;for(D=i;E<D;E++,h++,z+=2)b[z]=k[h]}q=0;for(u=m;q<u;q++){h=q*l;z=(2*q+1)*d;E=0;for(D=l;E<D;E++,h++,z+=2)b[z]=n[h]}q=0;for(u=p;q<u;q++){h=q*o;z=(2*q+1)*d+1;E=0;for(D=o;E<D;E++,h++,z+=2)b[z]=r[h]}a=new Float32Array(Math.max(d,c)+8);g=new Float32Array(a);a=new Float32Array(a);for(i=0;i<c;i++)if(1==d)0!=e%1&&(b[i*d]/=2);else{h=i*d;z=4;for(j=0;j<d;j++,h++,z++)g[z]=b[h];h=3;z=5;k=4+d-2;l=4+d;g[h--]=g[z++];g[ [...]
+g[h--]=g[z++];g[l++]=g[k--];g[h--]=g[z++];g[l++]=g[k--];g[h--]=g[z++];g[l++]=g[k--];this.filter(g,4,d,e,a);h=i*d;z=4;for(j=0;j<d;j++,h++,z++)b[h]=a[z]}for(j=0;j<d;j++)if(1==c)0!=f%1&&(b[j]/=2);else{h=j;z=4;for(i=0;i<c;i++,h+=d,z++)g[z]=b[h];h=3;z=5;k=4+c-2;l=4+c;g[h--]=g[z++];g[l++]=g[k--];g[h--]=g[z++];g[l++]=g[k--];g[h--]=g[z++];g[l++]=g[k--];g[h--]=g[z++];g[l++]=g[k--];this.filter(g,4,c,f,a);h=j;z=4;for(i=0;i<c;i++,h+=d,z++)b[h]=a[z]}return{width:d,height:c,items:b}};return a}(),p=fun [...]
+a.prototype=Object.create(o.prototype);a.prototype.filter=function(a,b,c,d,e){for(var f=Math.floor(d/2),c=Math.floor((d+c)/2),b=b-d%1,d=b-2,g=f-1,h=c+2;g<h;g++,d+=2)e[d]=1.230174104914001*a[d];d=b-3;g=f-2;for(h=c+2;g<h;g++,d+=2)e[d]=0.8128930661159609*a[d];d=b-2;g=f-1;for(h=c+2;g<h;g++,d+=2)e[d]-=0.443506852043971*(e[d-1]+e[d+1]);d=b-1;g=f-1;for(h=c+1;g<h;g++,d+=2)e[d]-=0.882911075530934*(e[d-1]+e[d+1]);d=b;g=f;for(h=c+1;g<h;g++,d+=2)e[d]-=-0.052980118572961*(e[d-1]+e[d+1]);d=b+1;g=f;for [...]
+d+=2)e[d]-=-1.586134342059924*(e[d-1]+e[d+1])};return a}(),q=function(){function a(){o.call(this)}a.prototype=Object.create(o.prototype);a.prototype.filter=function(a,b,c,d,e){for(var f=Math.floor(d/2),c=Math.floor((d+c)/2),b=b-d%1,d=f,g=c+1,h=b;d<g;d++,h+=2)e[h]=a[h]-Math.floor((a[h-1]+a[h+1]+2)/4);d=f;g=c;for(h=b+1;d<g;d++,h+=2)e[h]=a[h]+Math.floor((e[h-1]+e[h+1])/2)};return a}();return d}();"use strict";PDFJS.bidi=function(){function d(a,b,d){for(d-=1;b<d;++b,--d){var g=a[b];a[b]=a[d] [...]
+var a="BN,BN,BN,BN,BN,BN,BN,BN,BN,S,B,S,WS,B,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,B,B,B,S,WS,ON,ON,ET,ET,ET,ON,ON,ON,ON,ON,ON,CS,ON,CS,ON,EN,EN,EN,EN,EN,EN,EN,EN,EN,EN,ON,ON,ON,ON,ON,ON,ON,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,ON,ON,ON,ON,ON,ON,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,L,ON,ON,ON,ON,BN,BN,BN,BN,BN,BN,B,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,BN,CS,ON,ET,ET,ET,ET,ON,ON,ON,ON,L,ON,ON,ON,ON,ON,ET,ET,EN,EN,ON,L,ON [...]
+b="AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CS,AL,ON,ON,NSM,NSM,NSM,NSM,NSM,NSM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,NSM,AL,AL,AL,AL,AL,AL,AL,AN,AN,AN,AN,AN,AN,AN,AN,AN,AN,ET,AN,AN,AL,AL,AL,NSM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL, [...]
+return function(c,e){var f=c.str,g=f.length;if(0==g)return f;for(var i=[],k=[],j=0,h=0;h<g;++h){i[h]=f.charAt(h);var m=f.charCodeAt(h),l="L";255>=m?l=a[m]:1424<=m&&1524>=m?l="R":1536<=m&&1791>=m?l=b[m&255]:1792<=m&&2220>=m&&(l="AL");("R"==l||"AL"==l||"AN"==l)&&j++;k[h]=l}if(0==j)return c.direction="ltr",f;-1==e&&(0.3>g/j?(c.direction="ltr",e=0):(c.direction="rtl",e=1));f=[];for(h=0;h<g;++h)f[h]=e;m=j=0!=(e&1)?"R":"L";for(h=0;h<g;++h)"NSM"==k[h]?k[h]=m:m=k[h];m=j;for(h=0;h<g;++h)if(l=k[h] [...]
+"AL"==m?"AN":"EN";else if("R"==l||"L"==l||"AL"==l)m=l;for(h=0;h<g;++h)l=k[h],"AL"==l&&(k[h]="R");for(h=1;h<g-1;++h)if("ES"==k[h]&&"EN"==k[h-1]&&"EN"==k[h+1]&&(k[h]="EN"),"CS"==k[h]&&("EN"==k[h-1]||"AN"==k[h-1])&&k[h+1]==k[h-1])k[h]=k[h-1];for(h=0;h<g;++h)if("EN"==k[h]){for(m=h-1;0<=m&&!("ET"!=k[m]);--m)k[m]="EN";for(m=h+1;m<g&&!("ET"!=k[m]);--m)k[m]="EN"}for(h=0;h<g;++h)if(l=k[h],"WS"==l||"ES"==l||"ET"==l||"CS"==l)k[h]="ON";m=j;for(h=0;h<g;++h)if(l=k[h],"EN"==l)k[h]="L"==m?"L":"EN";else [...]
+"L"==l)m=l;for(h=0;h<g;++h)if("ON"==k[h]){m=void 0;m=h+1;for(l=k.length;m<l&&!("ON"!=k[m]);++m);var n=j;0<h&&(n=k[h-1]);l=j;m+1<g&&(l=k[m+1]);"L"!=n&&(n="R");"L"!=l&&(l="R");if(n==l)for(var l=k,o=m;h<o;++h)l[h]=n;h=m-1}for(h=0;h<g;++h)"ON"==k[h]&&(k[h]=j);for(h=0;h<g;++h)if(l=k[h],0==(f[h]&1))if("R"==l)f[h]+=1;else{if("AN"==l||"EN"==l)f[h]+=2}else if("L"==l||"AN"==l||"EN"==l)f[h]+=1;m=-1;k=99;h=0;for(g=f.length;h<g;++h)j=f[h],m<j&&(m=j),k>j&&0!=(j&1)&&(k=j);for(j=m;j>=k;--j){m=-1;h=0;for [...]
+g;++h)f[h]<j?0<=m&&(d(i,m,h),m=-1):0>m&&(m=h);0<=m&&d(i,m,f.length)}f="";h=0;for(g=i.length;h<g;++h)k=i[h],"<"!=k&&">"!=k&&(f+=k);return f}}();"use strict";PDFJS.Metadata=function(){function d(a){"string"===typeof a?a=(new DOMParser).parseFromString(a,"application/xml"):a instanceof Document||r("Metadata: Invalid metadata object");this.metaDocument=a;this.metadata={};this.parse()}d.prototype={parse:function(){var a=this.metaDocument.documentElement;if("rdf:rdf"!==a.nodeName.toLowerCase() [...]
+"rdf:rdf"!==a.nodeName.toLowerCase();)a=a.nextSibling;var b=a?a.nodeName.toLowerCase():null;if(a&&"rdf:rdf"===b&&a.hasChildNodes())for(var a=a.childNodes,c,d=0,f=a.length;d<f;d++)if(c=a[d],"rdf:description"===c.nodeName.toLowerCase()){for(var b=[],g=0,i=c.childNodes.length;g<i;g++)"#text"!==c.childNodes[g].nodeName.toLowerCase()&&b.push(c.childNodes[g]);for(g=0,i=b.length;g<i;g++)c=b[g],this.metadata[c.nodeName.toLowerCase()]=c.textContent.trim()}},get:function(a){return this.metadata[a] [...]
+typeof this.metadata[a]}};return d}()}).call("undefined"===typeof window?this:window);
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.css b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.css
new file mode 100644
index 0000000..4cc4e5c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.css
@@ -0,0 +1,486 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+body {
+ background-color: rgb(135, 137, 140) !important;
+ background-image: none !important;
+ margin: 0 0 0 200px !important;
+ padding: 0px;
+ transition: margin-left 0.25s ease-in-out;
+ -o-transition: margin-left 0.25s ease-in-out;
+ -moz-transition: margin-left 0.25s ease-in-out;
+ -webkit-transition: margin-left 0.25s ease-in-out;
+}
+
+body.nosidebar {
+ margin-left: 0 !important;
+}
+
+[hidden] {
+ display: none !important;
+}
+
+/* === Toolbar === */
+#controls {
+ background-color: #eee;
+ background: -o-linear-gradient(bottom,#eee 0%,#fff 100%);
+ background: -moz-linear-gradient(center bottom, #eee 0%, #fff 100%);
+ background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff));
+ border-bottom: 1px solid #666;
+ padding: 3px;
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ height: 24px;
+ width: 100%;
+ z-index: 1;
+ white-space:nowrap;
+ overflow: hidden;
+}
+
+.separator {
+ display: inline;
+ border-left: 1px solid #d3d3d3;
+ border-right: 1px solid #fff;
+ height: 16px;
+ width:0px;
+ margin: 4px;
+}
+
+#controls > a > img {
+ margin: 4px;
+ height: 16px;
+}
+
+#controls > button {
+ line-height: 16px;
+}
+
+#controls > button > img {
+ width: 16px;
+ height: 16px;
+}
+
+#controls > button[disabled] > img {
+ opacity: 0.5;
+}
+
+#pageNumber {
+ text-align: right;
+}
+
+#fileInput {
+ line-height: 16px;
+}
+
+/* === Sidebar === */
+#sidebar {
+ position: fixed;
+ width: 200px;
+ top: 31px;
+ bottom: 0;
+ left: 0;
+ transition: left 0.25s ease-in-out;
+ -o-transition: left 0.25s ease-in-out;
+ -moz-transition: left 0.25s ease-in-out;
+ -webkit-transition: left 0.25s ease-in-out;
+ z-index: 1;
+}
+
+body.nosidebar #sidebar {
+ left: -200px;
+}
+
+#pinIcon {
+ position: absolute;
+ top: 4px;
+ right: 55px;
+ width: 15px;
+ height: 15px;
+ background: center no-repeat;
+ background-image: url('images/pin-up.svg');
+ background-size: 15px 15px;
+}
+
+#pinIcon:hover {
+ background-color: rgba(255,255,255,0.35);
+}
+
+#sidebar.pinned #pinIcon {
+ background-image: url('images/pin-down.svg');
+ background-size: 15px 15px;
+}
+
+#sidebarBox {
+ background-color: rgb(224, 229, 234);
+ width: 100%;
+ height: 100%;
+}
+
+#sidebarScrollView {
+ position: absolute;
+ overflow: hidden;
+ overflow-y: scroll;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding-top: 15px;
+}
+
+body.nosidebar #sidebarScrollView {
+ overflow-y: hidden;
+}
+
+#sidebarScrollView a {
+ text-decoration: none;
+ outline: none !important;
+}
+
+.thumbnail {
+ width: 134px;
+ height: 134px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ margin-left:auto;
+ margin-right:auto;
+ line-height: 134px;
+ text-align: center;
+ overflow: visible;
+}
+
+.thumbnail[data-loaded] > canvas {
+ box-shadow: 0px 2px 4px #333;
+ -moz-box-shadow: 0px 2px 4px #333;
+ -webkit-box-shadow: 0px 2px 4px #333;
+}
+
+.thumbnail:not([data-loaded]) {
+ background-color: rgb(224, 229, 234);
+}
+
+.thumbnail > canvas {
+ vertical-align: middle;
+ display: inline-block;
+}
+
+.thumbnailLabelContainer {
+ font-size: 11px;
+ text-align: center;
+ color: #444;
+ margin-top: 7px;
+ margin-bottom: 12px;
+}
+.thumbnailLabelContainer.selected .thumbnailLabel {
+ background-color: #1a7ad8;
+ border-radius: 10px;
+ padding: 1px 7px;
+ color: white;
+}
+
+#outlineScrollView {
+ position: absolute;
+ background-color: #fff;
+ overflow: auto;
+ top: 20px;
+ bottom: 10px;
+ left: 10px;
+ width: 280px;
+}
+
+#outlineView {
+ padding-top: 4px;
+ padding-bottom: 100px;
+ padding-left: 6px;
+ padding-right: 6px;
+ font-size: smaller;
+}
+
+.outlineItem > .outlineItems {
+ margin-left: 20px;
+}
+
+.outlineItem > a {
+ text-decoration: none;
+ color: black;
+}
+
+#sidebarControls {
+ position:absolute;
+ width: 120px;
+ height: 32px;
+ left: 15px;
+ bottom: 35px;
+}
+
+#sidebarControls > button {
+ box-shadow: 0px 4px 10px #000;
+ -moz-box-shadow: 0px 4px 10px #000;
+ -webkit-box-shadow: 0px 4px 10px #000;
+}
+
+#sidebarControls > button > img {
+ width: 32px;
+ height: 32px;
+}
+
+#sidebarControls > button[disabled] > img {
+ opacity: 0.5;
+}
+
+#sidebarControls > button[data-selected] {
+ box-shadow: 0px 4px 10px #ff0;
+ -moz-box-shadow: 0px 4px 10px #ff0;
+ -webkit-box-shadow: 0px 4px 10px #ff0;
+}
+
+/* === Content view === */
+canvas {
+ margin: auto;
+ display: block;
+}
+
+.page {
+ width: 816px;
+ height: 1056px;
+ margin: 10px auto;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0px 4px 10px #000;
+ -moz-box-shadow: 0px 4px 10px #000;
+ -webkit-box-shadow: 0px 4px 10px #000;
+ background-color: white;
+}
+
+.page > a {
+ display: block;
+ position: absolute;
+}
+
+.page > a:hover {
+ opacity: 0.2;
+ background: #ff0;
+ box-shadow: 0px 2px 10px #ff0;
+ -moz-box-shadow: 0px 2px 10px #ff0;
+ -webkit-box-shadow: 0px 2px 10px #ff0;
+}
+
+.loadingIcon {
+ position: absolute;
+ display: block;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background: url('images/loading-icon.gif') center no-repeat;
+}
+
+.textLayer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ color: #000;
+ font-family: sans-serif;
+}
+
+.textLayer > div {
+ color: transparent;
+ position: absolute;
+ line-height:1.3;
+}
+
+.annotComment > div {
+ position: absolute;
+}
+
+.annotComment > img {
+ position: absolute;
+}
+
+.annotComment > img:hover {
+ cursor: pointer;
+ opacity: 0.7;
+}
+
+.annotComment > div {
+ padding: 0.2em;
+ max-width: 20em;
+ background-color: #F1E47B;
+ box-shadow: 0px 2px 10px #333;
+ -moz-box-shadow: 0px 2px 10px #333;
+ -webkit-box-shadow: 0px 2px 10px #333;
+}
+
+.annotComment > div > h1 {
+ font-weight: normal;
+ font-size: 1.2em;
+ border-bottom: 1px solid #000000;
+ margin: 0px;
+}
+
+/* TODO: file FF bug to support ::-moz-selection:window-inactive
+ so we can override the opaque grey background when the window is inactive;
+ see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
+::selection { background:rgba(0,0,255,0.3); }
+::-moz-selection { background:rgba(0,0,255,0.3); }
+
+#viewer {
+ margin: 44px 0px 0px;
+ padding: 8px 0px;
+}
+
+#pageWidthOption {
+ border-top: 1px solid black;
+}
+
+#customScaleOption {
+ display: none;
+}
+
+#errorWrapper {
+ background: none repeat scroll 0 0 #FF5555;
+ color: white;
+ left: 0;
+ position: fixed;
+ right: 0;
+ top: 30px;
+ z-index: 1000;
+ padding: 3px;
+ font-size: 0.8em;
+}
+
+#errorMessageLeft {
+ float: left;
+}
+
+#errorMessageRight {
+ float: right;
+}
+
+#errorMoreInfo {
+ background-color: #FFFFFF;
+ color: black;
+ padding: 3px;
+ margin: 3px;
+ width: 98%;
+}
+
+.clearBoth {
+ clear: both;
+}
+
+/* === Printed media overrides === */
+ at media print {
+ #sidebar {
+ display: none;
+ }
+
+ #controls {
+ display: none;
+ }
+
+ #viewer {
+ margin: 0;
+ padding: 0;
+ }
+
+ .page {
+ display: none;
+ margin: 0;
+ }
+
+ .page canvas {
+ box-shadow: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ }
+
+ .page[data-loaded] {
+ display: block;
+ page-break-after: always;
+ }
+}
+
+#loadingBox {
+ margin: 100px 0;
+ text-align: center;
+}
+
+#loadingBar {
+ background-color: #333;
+ display: inline-block;
+ border: 1px solid black;
+ clear: both;
+ margin:0px;
+ line-height: 0;
+ border-radius: 4px;
+ width: 15em;
+ height: 1.5em;
+}
+
+#loadingBar .progress {
+ background-color: green;
+ display: inline-block;
+ float: left;
+
+ background: #b4e391;
+ background: -moz-linear-gradient(top, #b4e391 0%, #61c419 50%, #b4e391 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b4e391), color-stop(50%,#61c419), color-stop(100%,#b4e391));
+ background: -webkit-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%);
+ background: -o-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%);
+ background: -ms-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%);
+ background: linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%);
+
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+
+ width: 0%;
+ height: 100%;
+}
+
+#PDFBug {
+ font-size: 10px;
+ position: fixed;
+ top: 35px;
+ bottom: 5px;
+ right: 2px;
+ width: 300px;
+ background: white;
+ border: 1px solid #666;
+ padding: 0;
+}
+#PDFBug .controls {
+ border-bottom: 1px solid #666;
+ padding: 3px;
+ background: -moz-linear-gradient(center bottom, #eee 0%, #fff 100%);
+}
+#PDFBug .panels {
+ overflow: auto;
+ position: absolute;
+ top: 27px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+#PDFBug button.active {
+ font-weight: bold;
+}
+.debuggerShowText {
+ background: yellow;
+ color: blue;
+ opacity: 0.3;
+}
+.debuggerHideText:hover {
+ background: yellow;
+ opacity: 0.3;
+}
+#PDFBug .stats {
+ font-size: 10px;
+ white-space: pre;
+ font-family: courier;
+}
+#PDFBug .stats .title {
+ font-weight: bold;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.js
new file mode 100644
index 0000000..76bac82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.js
@@ -0,0 +1,1616 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
+var kDefaultScale = 'auto';
+var kDefaultScaleDelta = 1.1;
+var kUnknownScale = 0;
+var kCacheSize = 20;
+var kCssUnits = 96.0 / 72.0;
+var kScrollbarPadding = 40;
+var kMinScale = 0.25;
+var kMaxScale = 4.0;
+var kImageDirectory = './images/';
+var kSettingsMemory = 20;
+
+function getFileName(url) {
+ var anchor = url.indexOf('#');
+ var query = url.indexOf('?');
+ var end = Math.min(
+ anchor > 0 ? anchor : url.length,
+ query > 0 ? query : url.length);
+ return url.substring(url.lastIndexOf('/', end) + 1, end);
+}
+
+var Cache = function cacheCache(size) {
+ var data = [];
+ this.push = function cachePush(view) {
+ var i = data.indexOf(view);
+ if (i >= 0)
+ data.splice(i);
+ data.push(view);
+ if (data.length > size)
+ data.shift().destroy();
+ };
+};
+
+var ProgressBar = (function ProgressBarClosure() {
+
+ function clamp(v, min, max) {
+ return Math.min(Math.max(v, min), max);
+ }
+
+ function ProgressBar(id, opts) {
+
+ // Fetch the sub-elements for later
+ this.div = document.querySelector(id + ' .progress');
+
+ // Get options, with sensible defaults
+ this.height = opts.height || 100;
+ this.width = opts.width || 100;
+ this.units = opts.units || '%';
+ this.percent = opts.percent || 0;
+
+ // Initialize heights
+ this.div.style.height = this.height + this.units;
+ }
+
+ ProgressBar.prototype = {
+
+ updateBar: function ProgressBar_updateBar() {
+ var progressSize = this.width * this._percent / 100;
+
+ this.div.style.width = progressSize + this.units;
+ },
+
+ get percent() {
+ return this._percent;
+ },
+
+ set percent(val) {
+ this._percent = clamp(val, 0, 100);
+ this.updateBar();
+ }
+ };
+
+ return ProgressBar;
+})();
+
+var RenderingQueue = (function RenderingQueueClosure() {
+ function RenderingQueue() {
+ this.items = [];
+ }
+
+ RenderingQueue.prototype = {
+ enqueueDraw: function RenderingQueueEnqueueDraw(item) {
+ if (!item.drawingRequired())
+ return; // as no redraw required, no need for queueing.
+
+ this.items.push(item);
+ if (this.items.length > 1)
+ return; // not first item
+
+ item.draw(this.continueExecution.bind(this));
+ },
+ continueExecution: function RenderingQueueContinueExecution() {
+ var item = this.items.shift();
+
+ if (this.items.length == 0)
+ return; // queue is empty
+
+ item = this.items[0];
+ item.draw(this.continueExecution.bind(this));
+ }
+ };
+
+ return RenderingQueue;
+})();
+
+var FirefoxCom = (function FirefoxComClosure() {
+ return {
+ /**
+ * Creates an event that hopefully the extension is listening for and will
+ * synchronously respond to.
+ * @param {String} action The action to trigger.
+ * @param {String} data Optional data to send.
+ * @return {*} The response.
+ */
+ request: function(action, data) {
+ var request = document.createTextNode('');
+ request.setUserData('action', action, null);
+ request.setUserData('data', data, null);
+ document.documentElement.appendChild(request);
+
+ var sender = document.createEvent('Events');
+ sender.initEvent('pdf.js.message', true, false);
+ request.dispatchEvent(sender);
+ var response = request.getUserData('response');
+ document.documentElement.removeChild(request);
+ return response;
+ }
+ };
+})();
+
+// Settings Manager - This is a utility for saving settings
+// First we see if localStorage is available
+// If not, we use FUEL in FF
+var Settings = (function SettingsClosure() {
+ var isLocalStorageEnabled = (function localStorageEnabledTest() {
+ // Feature test as per http://diveintohtml5.info/storage.html
+ // The additional localStorage call is to get around a FF quirk, see
+ // bug #495747 in bugzilla
+ try {
+ return 'localStorage' in window && window['localStorage'] !== null &&
+ localStorage;
+ } catch (e) {
+ return false;
+ }
+ })();
+
+ var isFirefoxExtension = PDFJS.isFirefoxExtension;
+
+ function Settings(fingerprint) {
+ var database = null;
+ var index;
+ if (isFirefoxExtension)
+ database = FirefoxCom.request('getDatabase', null) || '{}';
+ else if (isLocalStorageEnabled)
+ database = localStorage.getItem('database') || '{}';
+ else
+ return false;
+
+ database = JSON.parse(database);
+ if (!('files' in database))
+ database.files = [];
+ if (database.files.length >= kSettingsMemory)
+ database.files.shift();
+ for (var i = 0, length = database.files.length; i < length; i++) {
+ var branch = database.files[i];
+ if (branch.fingerprint == fingerprint) {
+ index = i;
+ break;
+ }
+ }
+ if (typeof index != 'number')
+ index = database.files.push({fingerprint: fingerprint}) - 1;
+ this.file = database.files[index];
+ this.database = database;
+ }
+
+ Settings.prototype = {
+ set: function settingsSet(name, val) {
+ if (!('file' in this))
+ return false;
+
+ var file = this.file;
+ file[name] = val;
+ var database = JSON.stringify(this.database);
+ if (isFirefoxExtension)
+ FirefoxCom.request('setDatabase', database);
+ else if (isLocalStorageEnabled)
+ localStorage.setItem('database', database);
+ },
+
+ get: function settingsGet(name, defaultValue) {
+ if (!('file' in this))
+ return defaultValue;
+
+ return this.file[name] || defaultValue;
+ }
+ };
+
+ return Settings;
+})();
+
+var cache = new Cache(kCacheSize);
+var renderingQueue = new RenderingQueue();
+var currentPageNumber = 1;
+
+var kSidebarWidth = 200;
+var PDFView = {
+ pages: [],
+ thumbnails: [],
+ currentScale: kUnknownScale,
+ currentScaleValue: null,
+ initialBookmark: document.location.hash.substring(1),
+ sidebarWidth: kSidebarWidth,
+
+ toggleSidebar: function() {
+ if (document.body.className.match(/\bnosidebar\b/)) {
+ document.body.className = document.body.className.replace(/ ?nosidebar\b/, '');
+ // The sidebar is going to appear, so immediately shrink the view area
+ this.sidebarWidth = kSidebarWidth;
+ this.parseScale(this.currentScaleValue);
+ }
+ else {
+ // The sidebar is hiding, don't enlarge the view area yet, but wait for
+ // the transition to end first
+ document.body.className = document.body.className + ' nosidebar';
+ }
+ },
+
+ setScale: function pdfViewSetScale(val, resetAutoSettings) {
+ if (val == this.currentScale)
+ return;
+
+ var pages = this.pages;
+ for (var i = 0; i < pages.length; i++)
+ pages[i].update(val * kCssUnits);
+
+ if (this.currentScale != val)
+ this.pages[this.page - 1].scrollIntoView();
+ this.currentScale = val;
+
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('scalechange', false, false, window, 0);
+ event.scale = val;
+ event.resetAutoSettings = resetAutoSettings;
+ window.dispatchEvent(event);
+ },
+
+ parseScale: function pdfViewParseScale(value, resetAutoSettings) {
+ if ('custom' == value)
+ return;
+
+ var scale = parseFloat(value);
+ this.currentScaleValue = value;
+ if (scale) {
+ this.setScale(scale, true);
+ return;
+ }
+
+ var currentPage = this.pages[this.page - 1];
+ if (!currentPage)
+ return; // document isn't loaded yet
+ var pageWidthScale = (window.innerWidth - this.sidebarWidth - kScrollbarPadding) /
+ currentPage.width * currentPage.scale / kCssUnits;
+ var pageHeightScale = (window.innerHeight - kScrollbarPadding) /
+ currentPage.height * currentPage.scale / kCssUnits;
+ if ('page-width' == value)
+ this.setScale(pageWidthScale, resetAutoSettings);
+ if ('page-height' == value)
+ this.setScale(pageHeightScale, resetAutoSettings);
+ if ('page-fit' == value) {
+ this.setScale(
+ Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
+ }
+ if ('auto' == value)
+ this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
+
+ selectScaleOption(value);
+ },
+
+ zoomIn: function pdfViewZoomIn() {
+ var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta);
+ this.parseScale(newScale, true);
+ },
+
+ zoomOut: function pdfViewZoomOut() {
+ var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta);
+ this.parseScale(newScale, true);
+ },
+
+ set page(val) {
+ var pages = this.pages;
+ var input = document.getElementById('pageNumber');
+ if (!(0 < val && val <= pages.length)) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', false, false, window, 0);
+ event.pageNumber = this.page;
+ window.dispatchEvent(event);
+ return;
+ }
+
+ pages[val - 1].updateStats();
+ currentPageNumber = val;
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', false, false, window, 0);
+ event.pageNumber = val;
+ window.dispatchEvent(event);
+
+ // checking if the this.page was called from the updateViewarea function:
+ // avoiding the creation of two "set page" method (internal and public)
+ if (updateViewarea.inProgress)
+ return;
+
+ // Avoid scrolling the first page during loading
+ if (this.loading && val == 1)
+ return;
+
+ pages[val - 1].scrollIntoView();
+ },
+
+ get page() {
+ return currentPageNumber;
+ },
+
+ open: function pdfViewOpen(url, scale) {
+ this.url = url;
+
+ if (!PDFView.loadingBar) {
+ PDFView.loadingBar = new ProgressBar('#loadingBar', {});
+ }
+
+ var self = this;
+ self.loading = true;
+ PDFJS.getDocument(url).then(
+ function getDocumentCallback(pdfDocument) {
+ self.load(pdfDocument, scale);
+ self.loading = false;
+ },
+ function getDocumentError(message, exception) {
+ var loadingIndicator = document.getElementById('loading');
+ loadingIndicator.textContent = 'Error';
+ var moreInfo = {
+ message: message
+ };
+ self.error('An error occurred while loading the PDF.', moreInfo);
+ self.loading = false;
+ },
+ function getDocumentProgress(progressData) {
+ self.progress(progressData.loaded / progressData.total);
+ }
+ );
+ },
+
+ download: function pdfViewDownload() {
+ var url = this.url.split('#')[0];
+ if (PDFJS.isFirefoxExtension) {
+ FirefoxCom.request('download', url);
+ } else {
+ url += '#pdfjs.action=download', '_parent';
+ window.open(url, '_parent');
+ }
+ },
+
+ navigateTo: function pdfViewNavigateTo(dest) {
+ if (typeof dest === 'string')
+ dest = this.destinations[dest];
+ if (!(dest instanceof Array))
+ return; // invalid destination
+ // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+ var destRef = dest[0];
+ var pageNumber = destRef instanceof Object ?
+ this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
+ if (pageNumber > this.pages.length)
+ pageNumber = this.pages.length;
+ if (pageNumber) {
+ this.page = pageNumber;
+ var currentPage = this.pages[pageNumber - 1];
+ currentPage.scrollIntoView(dest);
+ }
+ },
+
+ getDestinationHash: function pdfViewGetDestinationHash(dest) {
+ if (typeof dest === 'string')
+ return PDFView.getAnchorUrl('#' + escape(dest));
+ if (dest instanceof Array) {
+ var destRef = dest[0]; // see navigateTo method for dest format
+ var pageNumber = destRef instanceof Object ?
+ this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+ (destRef + 1);
+ if (pageNumber) {
+ var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
+ var destKind = dest[1];
+ if (typeof destKind === 'object' && 'name' in destKind &&
+ destKind.name == 'XYZ') {
+ var scale = (dest[4] || this.currentScale);
+ pdfOpenParams += '&zoom=' + (scale * 100);
+ if (dest[2] || dest[3]) {
+ pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+ }
+ }
+ return pdfOpenParams;
+ }
+ }
+ return '';
+ },
+
+ /**
+ * For the firefox extension we prefix the full url on anchor links so they
+ * don't come up as resource:// urls and so open in new tab/window works.
+ * @param {String} anchor The anchor hash include the #.
+ */
+ getAnchorUrl: function getAnchorUrl(anchor) {
+ if (PDFJS.isFirefoxExtension)
+ return this.url.split('#')[0] + anchor;
+ return anchor;
+ },
+
+ /**
+ * Show the error box.
+ * @param {String} message A message that is human readable.
+ * @param {Object} moreInfo (optional) Further information about the error
+ * that is more technical. Should have a 'message'
+ * and optionally a 'stack' property.
+ */
+ error: function pdfViewError(message, moreInfo) {
+ var errorWrapper = document.getElementById('errorWrapper');
+ errorWrapper.removeAttribute('hidden');
+
+ var errorMessage = document.getElementById('errorMessage');
+ errorMessage.textContent = message;
+
+ var closeButton = document.getElementById('errorClose');
+ closeButton.onclick = function() {
+ errorWrapper.setAttribute('hidden', 'true');
+ };
+
+ var errorMoreInfo = document.getElementById('errorMoreInfo');
+ var moreInfoButton = document.getElementById('errorShowMore');
+ var lessInfoButton = document.getElementById('errorShowLess');
+ moreInfoButton.onclick = function() {
+ errorMoreInfo.removeAttribute('hidden');
+ moreInfoButton.setAttribute('hidden', 'true');
+ lessInfoButton.removeAttribute('hidden');
+ };
+ lessInfoButton.onclick = function() {
+ errorMoreInfo.setAttribute('hidden', 'true');
+ moreInfoButton.removeAttribute('hidden');
+ lessInfoButton.setAttribute('hidden', 'true');
+ };
+ moreInfoButton.removeAttribute('hidden');
+ lessInfoButton.setAttribute('hidden', 'true');
+ errorMoreInfo.value = 'PDF.JS Build: ' + PDFJS.build + '\n';
+
+ if (moreInfo) {
+ errorMoreInfo.value += 'Message: ' + moreInfo.message;
+ if (moreInfo.stack) {
+ errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack;
+ } else {
+ if (moreInfo.filename)
+ errorMoreInfo.value += '\n' + 'File: ' + moreInfo.filename;
+ if (moreInfo.lineNumber)
+ errorMoreInfo.value += '\n' + 'Line: ' + moreInfo.lineNumber;
+ }
+ }
+ errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1;
+ },
+
+ progress: function pdfViewProgress(level) {
+ var percent = Math.round(level * 100);
+ var loadingIndicator = document.getElementById('loading');
+ loadingIndicator.textContent = 'Loading... ' + percent + '%';
+
+ PDFView.loadingBar.percent = percent;
+ },
+
+ load: function pdfViewLoad(pdfDocument, scale) {
+ function bindOnAfterDraw(pageView, thumbnailView) {
+ // when page is painted, using the image as thumbnail base
+ pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+ thumbnailView.setImage(pageView.canvas);
+ preDraw();
+ };
+ }
+
+ var errorWrapper = document.getElementById('errorWrapper');
+ errorWrapper.setAttribute('hidden', 'true');
+
+ var loadingBox = document.getElementById('loadingBox');
+ loadingBox.setAttribute('hidden', 'true');
+
+ var sidebar = document.getElementById('sidebarView');
+ sidebar.parentNode.scrollTop = 0;
+
+ while (sidebar.hasChildNodes())
+ sidebar.removeChild(sidebar.lastChild);
+
+ if ('_loadingInterval' in sidebar)
+ clearInterval(sidebar._loadingInterval);
+
+ var container = document.getElementById('viewer');
+ while (container.hasChildNodes())
+ container.removeChild(container.lastChild);
+
+ var pagesCount = pdfDocument.numPages;
+ var id = pdfDocument.fingerprint;
+ var storedHash = null;
+ document.getElementById('numPages').textContent = pagesCount;
+ document.getElementById('pageNumber').max = pagesCount;
+ PDFView.documentFingerprint = id;
+ var store = PDFView.store = new Settings(id);
+ if (store.get('exists', false)) {
+ var page = store.get('page', '1');
+ var zoom = store.get('zoom', PDFView.currentScale);
+ var left = store.get('scrollLeft', '0');
+ var top = store.get('scrollTop', '0');
+
+ storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
+ }
+
+ var pages = this.pages = [];
+ var pagesRefMap = {};
+ var thumbnails = this.thumbnails = [];
+ var pagePromises = [];
+ for (var i = 1; i <= pagesCount; i++)
+ pagePromises.push(pdfDocument.getPage(i));
+ var self = this;
+ var pagesPromise = PDFJS.Promise.all(pagePromises);
+ pagesPromise.then(function(promisedPages) {
+ for (var i = 1; i <= pagesCount; i++) {
+ var page = promisedPages[i - 1];
+ var pageView = new PageView(container, page, i, scale,
+ page.stats, self.navigateTo.bind(self));
+ var thumbnailView = new ThumbnailView(sidebar, page, i);
+ bindOnAfterDraw(pageView, thumbnailView);
+
+ pages.push(pageView);
+ thumbnails.push(thumbnailView);
+ var pageRef = page.ref;
+ pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
+ }
+
+ self.pagesRefMap = pagesRefMap;
+ });
+
+ var destinationsPromise = pdfDocument.getDestinations();
+ destinationsPromise.then(function(destinations) {
+ self.destinations = destinations;
+ });
+
+ // outline and initial view depends on destinations and pagesRefMap
+ PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
+ pdfDocument.getOutline().then(function(outline) {
+ if (!outline)
+ return;
+
+ self.outline = new DocumentOutlineView(outline);
+ var outlineSwitchButton = document.getElementById('outlineSwitch');
+ outlineSwitchButton.removeAttribute('disabled');
+ self.switchSidebarView('outline');
+ });
+
+ self.setInitialView(storedHash, scale);
+ });
+
+ pdfDocument.getMetadata().then(function(data) {
+ var info = data.info, metadata = data.metadata;
+ self.documentInfo = info;
+ self.metadata = metadata;
+ });
+ },
+
+ setInitialView: function pdfViewSetInitialView(storedHash, scale) {
+ // Reset the current scale, as otherwise the page's scale might not get
+ // updated if the zoom level stayed the same.
+ this.currentScale = 0;
+ this.currentScaleValue = null;
+ if (this.initialBookmark) {
+ this.setHash(this.initialBookmark);
+ this.initialBookmark = null;
+ }
+ else if (storedHash)
+ this.setHash(storedHash);
+ else if (scale) {
+ this.parseScale(scale, true);
+ this.page = 1;
+ }
+
+ // Don't let us be past the end of the document--bad things happen
+ if (this.page > this.pages.length)
+ this.page = this.pages.length;
+
+ if (PDFView.currentScale === kUnknownScale) {
+ // Scale was not initialized: invalid bookmark or scale was not specified.
+ // Setting the default one.
+ this.parseScale(kDefaultScale, true);
+ }
+
+ // Need to do this explicitly because our sidebar does not start out hidden
+ updateThumbViewArea();
+ },
+
+ setHash: function pdfViewSetHash(hash) {
+ if (!hash)
+ return;
+
+ if (hash.indexOf('=') >= 0) {
+ var params = PDFView.parseQueryString(hash);
+ // borrowing syntax from "Parameters for Opening PDF Files"
+ if ('nameddest' in params) {
+ PDFView.navigateTo(params.nameddest);
+ return;
+ }
+ if ('page' in params) {
+ var pageNumber = (params.page | 0) || 1;
+ this.page = pageNumber;
+ if ('zoom' in params) {
+ var zoomArgs = params.zoom.split(','); // scale,left,top
+ // building destination array
+
+ // If the zoom value, it has to get divided by 100. If it is a string,
+ // it should stay as it is.
+ var zoomArg = zoomArgs[0];
+ var zoomArgNumber = parseFloat(zoomArg);
+ if (zoomArgNumber)
+ zoomArg = zoomArgNumber / 100;
+
+ var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
+ (zoomArgs[2] | 0), zoomArg];
+ var currentPage = this.pages[pageNumber - 1];
+ currentPage.scrollIntoView(dest);
+ } else
+ this.page = params.page; // simple page
+ return;
+ }
+ } else if (/^\d+$/.test(hash)) // page number
+ this.page = hash;
+ else // named destination
+ PDFView.navigateTo(unescape(hash));
+ },
+
+ switchSidebarView: function pdfViewSwitchSidebarView(view) {
+ var thumbsScrollView = document.getElementById('sidebarScrollView');
+ var outlineScrollView = document.getElementById('outlineScrollView');
+ var thumbsSwitchButton = document.getElementById('thumbsSwitch');
+ var outlineSwitchButton = document.getElementById('outlineSwitch');
+ switch (view) {
+ case 'thumbs':
+ thumbsScrollView.removeAttribute('hidden');
+ outlineScrollView.setAttribute('hidden', 'true');
+ thumbsSwitchButton.setAttribute('data-selected', true);
+ outlineSwitchButton.removeAttribute('data-selected');
+ updateThumbViewArea();
+ break;
+ case 'outline':
+ thumbsScrollView.setAttribute('hidden', 'true');
+ outlineScrollView.removeAttribute('hidden');
+ thumbsSwitchButton.removeAttribute('data-selected');
+ outlineSwitchButton.setAttribute('data-selected', true);
+ break;
+ }
+ },
+
+ pinSidebar: function pdfViewPinSidebar() {
+ document.getElementById('sidebar').classList.toggle('pinned');
+ },
+
+ getVisiblePages: function pdfViewGetVisiblePages() {
+ var pages = this.pages;
+ var kBottomMargin = 10;
+ var visiblePages = [];
+
+ var currentHeight = kBottomMargin;
+ var windowTop = window.pageYOffset;
+ for (var i = 1; i <= pages.length; ++i) {
+ var page = pages[i - 1];
+ var pageHeight = page.height + kBottomMargin;
+ if (currentHeight + pageHeight > windowTop)
+ break;
+
+ currentHeight += pageHeight;
+ }
+
+ var windowBottom = window.pageYOffset + window.innerHeight;
+ for (; i <= pages.length && currentHeight < windowBottom; ++i) {
+ var singlePage = pages[i - 1];
+ visiblePages.push({ id: singlePage.id, y: currentHeight,
+ view: singlePage });
+ currentHeight += singlePage.height * singlePage.scale + kBottomMargin;
+ }
+ return visiblePages;
+ },
+
+ getVisibleThumbs: function pdfViewGetVisibleThumbs() {
+ var thumbs = this.thumbnails;
+ var kBottomMargin = 5;
+ var visibleThumbs = [];
+
+ var view = document.getElementById('sidebarScrollView');
+ var currentHeight = kBottomMargin;
+ var top = view.scrollTop;
+ for (var i = 1; i <= thumbs.length; ++i) {
+ var thumb = thumbs[i - 1];
+ var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
+ if (currentHeight + thumbHeight > top)
+ break;
+
+ currentHeight += thumbHeight;
+ }
+
+ var bottom = top + view.clientHeight;
+ for (; i <= thumbs.length && currentHeight < bottom; ++i) {
+ var singleThumb = thumbs[i - 1];
+ visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
+ view: singleThumb });
+ currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
+ }
+
+ return visibleThumbs;
+ },
+
+ // Helper function to parse query string (e.g. ?param1=value&parm2=...).
+ parseQueryString: function pdfViewParseQueryString(query) {
+ var parts = query.split('&');
+ var params = {};
+ for (var i = 0, ii = parts.length; i < parts.length; ++i) {
+ var param = parts[i].split('=');
+ var key = param[0];
+ var value = param.length > 1 ? param[1] : null;
+ params[unescape(key)] = unescape(value);
+ }
+ return params;
+ }
+};
+
+var PageView = function pageView(container, pdfPage, id, scale,
+ stats, navigateTo) {
+ this.id = id;
+ this.pdfPage = pdfPage;
+
+ this.scale = scale || 1.0;
+ this.viewport = this.pdfPage.getViewport(this.scale);
+
+ var anchor = document.createElement('a');
+ anchor.name = '' + this.id;
+
+ var div = document.createElement('div');
+ div.id = 'pageContainer' + this.id;
+ div.className = 'page';
+
+ container.appendChild(anchor);
+ container.appendChild(div);
+
+ this.destroy = function pageViewDestroy() {
+ this.update();
+ this.pdfPage.destroy();
+ };
+
+ this.update = function pageViewUpdate(scale) {
+ this.scale = scale || this.scale;
+ var viewport = this.pdfPage.getViewport(this.scale);
+
+ this.viewport = viewport;
+ div.style.width = viewport.width + 'px';
+ div.style.height = viewport.height + 'px';
+
+ while (div.hasChildNodes())
+ div.removeChild(div.lastChild);
+ div.removeAttribute('data-loaded');
+
+ delete this.canvas;
+
+ this.loadingIconDiv = document.createElement('div');
+ this.loadingIconDiv.className = 'loadingIcon';
+ div.appendChild(this.loadingIconDiv);
+ };
+
+ Object.defineProperty(this, 'width', {
+ get: function PageView_getWidth() {
+ return this.viewport.width;
+ },
+ enumerable: true
+ });
+
+ Object.defineProperty(this, 'height', {
+ get: function PageView_getHeight() {
+ return this.viewport.height;
+ },
+ enumerable: true
+ });
+
+ function setupAnnotations(pdfPage, viewport) {
+ function bindLink(link, dest) {
+ link.href = PDFView.getDestinationHash(dest);
+ link.onclick = function pageViewSetupLinksOnclick() {
+ if (dest)
+ PDFView.navigateTo(dest);
+ return false;
+ };
+ }
+ function createElementWithStyle(tagName, item) {
+ var rect = viewport.convertToViewportRectangle(item.rect);
+ rect = PDFJS.Util.normalizeRect(rect);
+ var element = document.createElement(tagName);
+ element.style.left = Math.floor(rect[0]) + 'px';
+ element.style.top = Math.floor(rect[1]) + 'px';
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+ return element;
+ }
+ function createCommentAnnotation(type, item) {
+ var container = document.createElement('section');
+ container.className = 'annotComment';
+
+ var image = createElementWithStyle('img', item);
+ var type = item.type;
+ var rect = viewport.convertToViewportRectangle(item.rect);
+ rect = PDFJS.Util.normalizeRect(rect);
+ image.src = kImageDirectory + type.toLowerCase() + '.svg';
+ image.alt = '[' + type + ' Annotation]';
+ var content = document.createElement('div');
+ content.setAttribute('hidden', true);
+ var title = document.createElement('h1');
+ var text = document.createElement('p');
+ content.style.left = Math.floor(rect[2]) + 'px';
+ content.style.top = Math.floor(rect[1]) + 'px';
+ title.textContent = item.title;
+
+ if (!item.content && !item.title) {
+ content.setAttribute('hidden', true);
+ } else {
+ var e = document.createElement('span');
+ var lines = item.content.split('\n');
+ for (var i = 0, ii = lines.length; i < ii; ++i) {
+ var line = lines[i];
+ e.appendChild(document.createTextNode(line));
+ if (i < (ii - 1))
+ e.appendChild(document.createElement('br'));
+ }
+ text.appendChild(e);
+ image.addEventListener('mouseover', function annotationImageOver() {
+ content.removeAttribute('hidden');
+ }, false);
+
+ image.addEventListener('mouseout', function annotationImageOut() {
+ content.setAttribute('hidden', true);
+ }, false);
+ }
+
+ content.appendChild(title);
+ content.appendChild(text);
+ container.appendChild(image);
+ container.appendChild(content);
+
+ return container;
+ }
+
+ pdfPage.getAnnotations().then(function(items) {
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ switch (item.type) {
+ case 'Link':
+ var link = createElementWithStyle('a', item);
+ link.href = item.url || '';
+ if (!item.url)
+ bindLink(link, ('dest' in item) ? item.dest : null);
+ div.appendChild(link);
+ break;
+ case 'Text':
+ var comment = createCommentAnnotation(item.name, item);
+ if (comment)
+ div.appendChild(comment);
+ break;
+ }
+ }
+ });
+ }
+
+ this.getPagePoint = function pageViewGetPagePoint(x, y) {
+ return this.viewport.convertToPdfPoint(x, y);
+ };
+
+ this.scrollIntoView = function pageViewScrollIntoView(dest) {
+ if (!dest) {
+ div.scrollIntoView(true);
+ return;
+ }
+
+ var x = 0, y = 0;
+ var width = 0, height = 0, widthScale, heightScale;
+ var scale = 0;
+ switch (dest[1].name) {
+ case 'XYZ':
+ x = dest[2];
+ y = dest[3];
+ scale = dest[4];
+ break;
+ case 'Fit':
+ case 'FitB':
+ scale = 'page-fit';
+ break;
+ case 'FitH':
+ case 'FitBH':
+ y = dest[2];
+ scale = 'page-width';
+ break;
+ case 'FitV':
+ case 'FitBV':
+ x = dest[2];
+ scale = 'page-height';
+ break;
+ case 'FitR':
+ x = dest[2];
+ y = dest[3];
+ width = dest[4] - x;
+ height = dest[5] - y;
+ widthScale = (window.innerWidth - kScrollbarPadding) /
+ width / kCssUnits;
+ heightScale = (window.innerHeight - kScrollbarPadding) /
+ height / kCssUnits;
+ scale = Math.min(widthScale, heightScale);
+ break;
+ default:
+ return;
+ }
+
+ var boundingRect = [
+ this.viewport.convertToViewportPoint(x, y),
+ this.viewport.convertToViewportPoint(x + width, y + height)
+ ];
+
+ if (scale && scale !== PDFView.currentScale)
+ PDFView.parseScale(scale, true);
+ else if (PDFView.currentScale === kUnknownScale)
+ PDFView.parseScale(kDefaultScale, true);
+
+ setTimeout(function pageViewScrollIntoViewRelayout() {
+ // letting page to re-layout before scrolling
+ var scale = PDFView.currentScale;
+ var x = Math.min(boundingRect[0][0], boundingRect[1][0]);
+ var y = Math.min(boundingRect[0][1], boundingRect[1][1]);
+ var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
+ var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
+
+ // using temporary div to scroll it into view
+ var tempDiv = document.createElement('div');
+ tempDiv.style.position = 'absolute';
+ tempDiv.style.left = Math.floor(x) + 'px';
+ tempDiv.style.top = Math.floor(y) + 'px';
+ tempDiv.style.width = Math.ceil(width) + 'px';
+ tempDiv.style.height = Math.ceil(height) + 'px';
+ div.appendChild(tempDiv);
+ tempDiv.scrollIntoView(true);
+ div.removeChild(tempDiv);
+ }, 0);
+ };
+
+ this.drawingRequired = function() {
+ return !div.querySelector('canvas');
+ };
+
+ this.draw = function pageviewDraw(callback) {
+ if (!this.drawingRequired()) {
+ this.updateStats();
+ callback();
+ return;
+ }
+
+ var canvas = document.createElement('canvas');
+ canvas.id = 'page' + this.id;
+ canvas.mozOpaque = true;
+ div.appendChild(canvas);
+ this.canvas = canvas;
+
+ var textLayerDiv = null;
+ if (!PDFJS.disableTextLayer) {
+ textLayerDiv = document.createElement('div');
+ textLayerDiv.className = 'textLayer';
+ div.appendChild(textLayerDiv);
+ }
+ var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
+
+ var scale = this.scale, viewport = this.viewport;
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+
+ // Rendering area
+
+ var self = this;
+ function pageViewDrawCallback(error) {
+ if (self.loadingIconDiv) {
+ div.removeChild(self.loadingIconDiv);
+ delete self.loadingIconDiv;
+ }
+
+ if (error)
+ PDFView.error('An error occurred while rendering the page.', error);
+
+ self.stats = pdfPage.stats;
+ self.updateStats();
+ if (self.onAfterDraw)
+ self.onAfterDraw();
+
+ cache.push(self);
+ callback();
+ }
+
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: this.viewport,
+ textLayer: textLayer
+ };
+ this.pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ pageViewDrawCallback(null);
+ },
+ function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ }
+ );
+
+ setupAnnotations(this.pdfPage, this.viewport);
+ div.setAttribute('data-loaded', true);
+ };
+
+ this.updateStats = function pageViewUpdateStats() {
+ if (PDFJS.pdfBug && Stats.enabled) {
+ var stats = this.stats;
+ Stats.add(this.id, stats);
+ }
+ };
+};
+
+var ThumbnailView = function thumbnailView(container, pdfPage, id) {
+ var anchor = document.createElement('a');
+ anchor.href = PDFView.getAnchorUrl('#page=' + id);
+ anchor.onclick = function stopNivigation() {
+ PDFView.page = id;
+ return false;
+ };
+
+ var viewport = pdfPage.getViewport(1);
+ var pageWidth = viewport.width;
+ var pageHeight = viewport.height;
+ var pageRatio = pageWidth / pageHeight;
+ this.id = id;
+
+ var maxThumbSize = 134;
+ var canvasWidth = this.width = pageRatio >= 1 ? maxThumbSize :
+ maxThumbSize * pageRatio;
+ var canvasHeight = this.height = pageRatio <= 1 ? maxThumbSize :
+ maxThumbSize / pageRatio;
+ var scaleX = this.scaleX = (canvasWidth / pageWidth);
+ var scaleY = this.scaleY = (canvasHeight / pageHeight);
+
+ var div = document.createElement('div');
+ div.id = 'thumbnailContainer' + id;
+ div.className = 'thumbnail';
+
+ var labelDiv = document.createElement('div');
+ labelDiv.id = 'thumbnailLabel' + id;
+ labelDiv.className = 'thumbnailLabelContainer';
+ var labelSpan = document.createElement('span');
+ labelSpan.innerHTML = id;
+ labelSpan.className = 'thumbnailLabel';
+ labelDiv.appendChild(labelSpan);
+ labelDiv.style.visibility = 'hidden';
+
+ anchor.appendChild(div);
+ anchor.appendChild(labelDiv);
+ container.appendChild(anchor);
+
+ this.hasImage = false;
+
+ function getPageDrawContext() {
+ var canvas = document.createElement('canvas');
+ canvas.id = 'thumbnail' + id;
+ canvas.mozOpaque = true;
+
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+
+ div.setAttribute('data-loaded', true);
+ div.appendChild(canvas);
+
+ labelDiv.style.visibility = 'visible';
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+ ctx.restore();
+ return ctx;
+ }
+
+ this.drawingRequired = function thumbnailViewDrawingRequired() {
+ return !this.hasImage;
+ };
+
+ this.draw = function thumbnailViewDraw(callback) {
+ if (this.hasImage) {
+ callback();
+ return;
+ }
+
+ var ctx = getPageDrawContext();
+ var drawViewport = pdfPage.getViewport(scaleX);
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport
+ };
+ pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ callback();
+ },
+ function pdfPageRenderError(error) {
+ callback();
+ }
+ );
+ this.hasImage = true;
+ };
+
+ this.setImage = function thumbnailViewSetImage(img) {
+ if (this.hasImage || !img)
+ return;
+
+ var ctx = getPageDrawContext();
+ ctx.drawImage(img, 0, 0, img.width, img.height,
+ 0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ this.hasImage = true;
+ };
+};
+
+var DocumentOutlineView = function documentOutlineView(outline) {
+ var outlineView = document.getElementById('outlineView');
+
+ function bindItemLink(domObj, item) {
+ domObj.href = PDFView.getDestinationHash(item.dest);
+ domObj.onclick = function documentOutlineViewOnclick(e) {
+ PDFView.navigateTo(item.dest);
+ return false;
+ };
+ }
+
+ var queue = [{parent: outlineView, items: outline}];
+ while (queue.length > 0) {
+ var levelData = queue.shift();
+ var i, n = levelData.items.length;
+ for (i = 0; i < n; i++) {
+ var item = levelData.items[i];
+ var div = document.createElement('div');
+ div.className = 'outlineItem';
+ var a = document.createElement('a');
+ bindItemLink(a, item);
+ a.textContent = item.title;
+ div.appendChild(a);
+
+ if (item.items.length > 0) {
+ var itemsDiv = document.createElement('div');
+ itemsDiv.className = 'outlineItems';
+ div.appendChild(itemsDiv);
+ queue.push({parent: itemsDiv, items: item.items});
+ }
+
+ levelData.parent.appendChild(div);
+ }
+ }
+};
+
+// optimised CSS custom property getter/setter
+var CustomStyle = (function CustomStyleClosure() {
+
+ // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+ // animate-css-transforms-firefox-webkit.html
+ // in some versions of IE9 it is critical that ms appear in this list
+ // before Moz
+ var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
+ var _cache = { };
+
+ function CustomStyle() {
+ }
+
+ CustomStyle.getProp = function get(propName, element) {
+ // check cache only when no element is given
+ if (arguments.length == 1 && typeof _cache[propName] == 'string') {
+ return _cache[propName];
+ }
+
+ element = element || document.documentElement;
+ var style = element.style, prefixed, uPropName;
+
+ // test standard property first
+ if (typeof style[propName] == 'string') {
+ return (_cache[propName] = propName);
+ }
+
+ // capitalize
+ uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+
+ // test vendor specific properties
+ for (var i = 0, l = prefixes.length; i < l; i++) {
+ prefixed = prefixes[i] + uPropName;
+ if (typeof style[prefixed] == 'string') {
+ return (_cache[propName] = prefixed);
+ }
+ }
+
+ //if all fails then set to undefined
+ return (_cache[propName] = 'undefined');
+ }
+
+ CustomStyle.setProp = function set(propName, element, str) {
+ var prop = this.getProp(propName);
+ if (prop != 'undefined')
+ element.style[prop] = str;
+ }
+
+ return CustomStyle;
+})();
+
+var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
+ this.textLayerDiv = textLayerDiv;
+
+ this.beginLayout = function textLayerBuilderBeginLayout() {
+ this.textDivs = [];
+ this.textLayerQueue = [];
+ };
+
+ this.endLayout = function textLayerBuilderEndLayout() {
+ var self = this;
+ var textDivs = this.textDivs;
+ var textLayerDiv = this.textLayerDiv;
+ var renderTimer = null;
+ var renderingDone = false;
+ var renderInterval = 0;
+ var resumeInterval = 500; // in ms
+
+ // Render the text layer, one div at a time
+ function renderTextLayer() {
+ if (textDivs.length === 0) {
+ clearInterval(renderTimer);
+ renderingDone = true;
+ return;
+ }
+ var textDiv = textDivs.shift();
+ if (textDiv.dataset.textLength > 0) {
+ textLayerDiv.appendChild(textDiv);
+
+ if (textDiv.dataset.textLength > 1) { // avoid div by zero
+ // Adjust div width to match canvas text
+ // Due to the .offsetWidth calls, this is slow
+ // This needs to come after appending to the DOM
+ var textScale = textDiv.dataset.canvasWidth / textDiv.offsetWidth;
+ CustomStyle.setProp('transform' , textDiv,
+ 'scale(' + textScale + ', 1)');
+ CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
+ }
+ } // textLength > 0
+ }
+ renderTimer = setInterval(renderTextLayer, renderInterval);
+
+ // Stop rendering when user scrolls. Resume after XXX milliseconds
+ // of no scroll events
+ var scrollTimer = null;
+ function textLayerOnScroll() {
+ if (renderingDone) {
+ window.removeEventListener('scroll', textLayerOnScroll, false);
+ return;
+ }
+
+ // Immediately pause rendering
+ clearInterval(renderTimer);
+
+ clearTimeout(scrollTimer);
+ scrollTimer = setTimeout(function textLayerScrollTimer() {
+ // Resume rendering
+ renderTimer = setInterval(renderTextLayer, renderInterval);
+ }, resumeInterval);
+ }; // textLayerOnScroll
+
+ window.addEventListener('scroll', textLayerOnScroll, false);
+ }; // endLayout
+
+ this.appendText = function textLayerBuilderAppendText(text,
+ fontName, fontSize) {
+ var textDiv = document.createElement('div');
+
+ // vScale and hScale already contain the scaling to pixel units
+ var fontHeight = fontSize * text.geom.vScale;
+ textDiv.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
+ textDiv.dataset.fontName = fontName;
+
+ textDiv.style.fontSize = fontHeight + 'px';
+ textDiv.style.left = text.geom.x + 'px';
+ textDiv.style.top = (text.geom.y - fontHeight) + 'px';
+ textDiv.textContent = PDFJS.bidi(text, -1);
+ textDiv.dir = text.direction;
+ textDiv.dataset.textLength = text.length;
+ this.textDivs.push(textDiv);
+ };
+};
+
+window.addEventListener('load', function webViewerLoad(evt) {
+/*
+ var params = PDFView.parseQueryString(document.location.search.substring(1));
+
+ var file = PDFJS.isFirefoxExtension ?
+ window.location.toString() : params.file || kDefaultURL;
+
+ if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
+ !window.FileList || !window.Blob) {
+ document.getElementById('fileInput').setAttribute('hidden', 'true');
+ document.getElementById('fileInputSeperator')
+ .setAttribute('hidden', 'true');
+ } else {
+ document.getElementById('fileInput').value = null;
+ }
+*/
+
+ // Special debugging flags in the hash section of the URL.
+ var hash = document.location.hash.substring(1);
+ var hashParams = PDFView.parseQueryString(hash);
+
+ if ('disableWorker' in hashParams)
+ PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
+
+ if ('disableTextLayer' in hashParams)
+ PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true');
+
+ if ('pdfBug' in hashParams &&
+ (!PDFJS.isFirefoxExtension || FirefoxCom.request('pdfBugEnabled'))) {
+ PDFJS.pdfBug = true;
+ var pdfBug = hashParams['pdfBug'];
+ var enabled = pdfBug.split(',');
+ PDFBug.enable(enabled);
+ PDFBug.init();
+ }
+
+ var sidebarScrollView = document.getElementById('sidebarScrollView');
+ sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true);
+ PDFView.open(file, 0);
+}, true);
+
+var sidebarHideListener = function(evt) {
+ PDFView.sidebarWidth = document.body.className.match(/\bnosidebar\b/)
+ ? 0
+ : kSidebarWidth;
+ PDFView.parseScale(PDFView.currentScaleValue);
+};
+document.body.addEventListener('transitionend', sidebarHideListener, false);
+document.body.addEventListener('oTransitionEnd', sidebarHideListener, false);
+document.body.addEventListener('webkitTransitionEnd', sidebarHideListener, false);
+
+/**
+ * Render the next not yet visible page already such that it is
+ * hopefully ready once the user scrolls to it.
+ */
+function preDraw() {
+ var pages = PDFView.pages;
+ var visible = PDFView.getVisiblePages();
+ var last = visible[visible.length - 1];
+ // PageView.id is the actual page number, which is + 1 compared
+ // to the index in `pages`. That means, pages[last.id] is the next
+ // PageView instance.
+ if (pages[last.id] && pages[last.id].drawingRequired()) {
+ renderingQueue.enqueueDraw(pages[last.id]);
+ return;
+ }
+ // If there is nothing to draw on the next page, maybe the user
+ // is scrolling up, so, let's try to render the next page *before*
+ // the first visible page
+ if (pages[visible[0].id - 2]) {
+ renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
+ }
+}
+
+function updateViewarea() {
+ var visiblePages = PDFView.getVisiblePages();
+ var pageToDraw;
+ for (var i = 0; i < visiblePages.length; i++) {
+ var page = visiblePages[i];
+ var pageObj = PDFView.pages[page.id - 1];
+
+ pageToDraw |= pageObj.drawingRequired();
+ renderingQueue.enqueueDraw(pageObj);
+ }
+
+ if (!visiblePages.length)
+ return;
+
+ // If there is no need to draw a page that is currenlty visible, preDraw the
+ // next page the user might scroll to.
+ if (!pageToDraw) {
+ preDraw();
+ }
+
+ updateViewarea.inProgress = true; // used in "set page"
+ var currentId = PDFView.page;
+ var firstPage = visiblePages[0];
+ PDFView.page = firstPage.id;
+ updateViewarea.inProgress = false;
+
+ var currentScale = PDFView.currentScale;
+ var currentScaleValue = PDFView.currentScaleValue;
+ var normalizedScaleValue = currentScaleValue == currentScale ?
+ currentScale * 100 : currentScaleValue;
+
+ var kViewerTopMargin = 52;
+ var pageNumber = firstPage.id;
+ var pdfOpenParams = '#page=' + pageNumber;
+ pdfOpenParams += '&zoom=' + normalizedScaleValue;
+ var currentPage = PDFView.pages[pageNumber - 1];
+ var topLeft = currentPage.getPagePoint(window.pageXOffset,
+ window.pageYOffset - firstPage.y - kViewerTopMargin);
+ pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
+
+ var store = PDFView.store;
+ store.set('exists', true);
+ store.set('page', pageNumber);
+ store.set('zoom', normalizedScaleValue);
+ store.set('scrollLeft', Math.round(topLeft[0]));
+ store.set('scrollTop', Math.round(topLeft[1]));
+ var href = PDFView.getAnchorUrl(pdfOpenParams);
+ document.getElementById('viewBookmark').href = href;
+}
+
+window.addEventListener('scroll', function webViewerScroll(evt) {
+ updateViewarea();
+}, true);
+
+var thumbnailTimer;
+
+function updateThumbViewArea() {
+ // Only render thumbs after pausing scrolling for this amount of time
+ // (makes UI more responsive)
+ var delay = 50; // in ms
+
+ if (thumbnailTimer)
+ clearTimeout(thumbnailTimer);
+
+ thumbnailTimer = setTimeout(function() {
+ var visibleThumbs = PDFView.getVisibleThumbs();
+ for (var i = 0; i < visibleThumbs.length; i++) {
+ var thumb = visibleThumbs[i];
+ renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
+ }
+ }, delay);
+}
+
+window.addEventListener('transitionend', updateThumbViewArea, true);
+window.addEventListener('webkitTransitionEnd', updateThumbViewArea, true);
+
+window.addEventListener('resize', function webViewerResize(evt) {
+ if (document.getElementById('pageWidthOption').selected ||
+ document.getElementById('pageFitOption').selected ||
+ document.getElementById('pageAutoOption').selected)
+ PDFView.parseScale(document.getElementById('scaleSelect').value);
+ updateViewarea();
+});
+
+window.addEventListener('hashchange', function webViewerHashchange(evt) {
+ PDFView.setHash(document.location.hash.substring(1));
+});
+
+window.addEventListener('change', function webViewerChange(evt) {
+ var files = evt.target.files;
+ if (!files || files.length == 0)
+ return;
+
+ // Read the local file into a Uint8Array.
+ var fileReader = new FileReader();
+ fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
+ var data = evt.target.result;
+ var buffer = new ArrayBuffer(data.length);
+ var uint8Array = new Uint8Array(buffer);
+
+ for (var i = 0; i < data.length; i++)
+ uint8Array[i] = data.charCodeAt(i);
+
+ // TODO using blob instead?
+ PDFJS.getDocument(uint8Array).then(function(pdfDocument) {
+ PDFView.load(pdfDocument);
+ });
+ };
+
+ // Read as a binary string since "readAsArrayBuffer" is not yet
+ // implemented in Firefox.
+ var file = files[0];
+ fileReader.readAsBinaryString(file);
+
+ // URL does not reflect proper document location - hiding some icons.
+ document.getElementById('viewBookmark').setAttribute('hidden', 'true');
+ document.getElementById('download').setAttribute('hidden', 'true');
+}, true);
+
+function selectScaleOption(value) {
+ var options = document.getElementById('scaleSelect').options;
+ var predefinedValueFound = false;
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+ if (option.value != value) {
+ option.selected = false;
+ continue;
+ }
+ option.selected = true;
+ predefinedValueFound = true;
+ }
+ return predefinedValueFound;
+}
+
+window.addEventListener('scalechange', function scalechange(evt) {
+ var customScaleOption = document.getElementById('customScaleOption');
+ customScaleOption.selected = false;
+
+ if (!evt.resetAutoSettings &&
+ (document.getElementById('pageWidthOption').selected ||
+ document.getElementById('pageFitOption').selected ||
+ document.getElementById('pageAutoOption').selected)) {
+ updateViewarea();
+ return;
+ }
+
+ var predefinedValueFound = selectScaleOption('' + evt.scale);
+ if (!predefinedValueFound) {
+ customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
+ customScaleOption.selected = true;
+ }
+
+ updateViewarea();
+}, true);
+
+window.addEventListener('pagechange', function pagechange(evt) {
+ var page = evt.pageNumber;
+ if (document.getElementById('pageNumber').value != page)
+ document.getElementById('pageNumber').value = page;
+ document.getElementById('previous').disabled = (page <= 1);
+ document.getElementById('next').disabled = (page >= PDFView.pages.length);
+}, true);
+
+window.addEventListener('keydown', function keydown(evt) {
+ var handled = false;
+ var cmd = (evt.ctrlKey ? 1 : 0) |
+ (evt.altKey ? 2 : 0) |
+ (evt.shiftKey ? 4 : 0) |
+ (evt.metaKey ? 8 : 0);
+
+ // First, handle the key bindings that are independent whether an input
+ // control is selected or not.
+ if (cmd == 1 || cmd == 8) { // either CTRL or META key.
+ switch (evt.keyCode) {
+ case 61: // FF/Mac '='
+ case 107: // FF '+' and '='
+ case 187: // Chrome '+'
+ PDFView.zoomIn();
+ handled = true;
+ break;
+ case 109: // FF '-'
+ case 189: // Chrome '-'
+ PDFView.zoomOut();
+ handled = true;
+ break;
+ case 48: // '0'
+ PDFView.parseScale(kDefaultScale, true);
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled) {
+ evt.preventDefault();
+ return;
+ }
+
+ // Some shortcuts should not get handled if a control/input element
+ // is selected.
+ var curElement = document.activeElement;
+ if (curElement && curElement.tagName == 'INPUT')
+ return;
+ var controlsElement = document.getElementById('controls');
+ while (curElement) {
+ if (curElement === controlsElement)
+ return; // ignoring if the 'controls' element is focused
+ curElement = curElement.parentNode;
+ }
+
+ if (cmd == 0) { // no control key pressed at all.
+ switch (evt.keyCode) {
+ case 37: // left arrow
+ case 75: // 'k'
+ case 80: // 'p'
+ PDFView.page--;
+ handled = true;
+ break;
+ case 39: // right arrow
+ case 74: // 'j'
+ case 78: // 'n'
+ PDFView.page++;
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled) {
+ evt.preventDefault();
+ }
+});
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.min.js b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.min.js
new file mode 100644
index 0000000..6379ffb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.min.js
@@ -0,0 +1,50 @@
+var kDefaultURL="compressed.tracemonkey-pldi-09.pdf",kDefaultScale="auto",kDefaultScaleDelta=1.1,kUnknownScale=0,kCacheSize=20,kCssUnits=96/72,kScrollbarPadding=40,kMinScale=0.25,kMaxScale=4,kImageDirectory="./images/",kSettingsMemory=20;function getFileName(a){var b=a.indexOf("#"),c=a.indexOf("?"),b=Math.min(0<b?b:a.length,0<c?c:a.length);return a.substring(a.lastIndexOf("/",b)+1,b)}
+var Cache=function(a){var b=[];this.push=function(c){var d=b.indexOf(c);0<=d&&b.splice(d);b.push(c);b.length>a&&b.shift().destroy()}},ProgressBar=function(){function a(a,c){this.div=document.querySelector(a+" .progress");this.height=c.height||100;this.width=c.width||100;this.units=c.units||"%";this.percent=c.percent||0;this.div.style.height=this.height+this.units}a.prototype={updateBar:function(){this.div.style.width=this.width*this._percent/100+this.units},get percent(){return this._per [...]
+Math.min(Math.max(a,0),100);this.updateBar()}};return a}(),RenderingQueue=function(){function a(){this.items=[]}a.prototype={enqueueDraw:function(a){a.drawingRequired()&&(this.items.push(a),1<this.items.length||a.draw(this.continueExecution.bind(this)))},continueExecution:function(){var a=this.items.shift();0!=this.items.length&&(a=this.items[0],a.draw(this.continueExecution.bind(this)))}};return a}(),FirefoxCom=function(){return{request:function(a,b){var c=document.createTextNode("");c. [...]
+a,null);c.setUserData("data",b,null);document.documentElement.appendChild(c);var d=document.createEvent("Events");d.initEvent("pdf.js.message",!0,!1);c.dispatchEvent(d);d=c.getUserData("response");document.documentElement.removeChild(c);return d}}}(),Settings=function(){function a(a){var c=null,f;if(d)c=FirefoxCom.request("getDatabase",null)||"{}";else if(b)c=localStorage.getItem("database")||"{}";else return!1;c=JSON.parse(c);"files"in c||(c.files=[]);c.files.length>=kSettingsMemory&&c. [...]
+for(var h=0,i=c.files.length;h<i;h++)if(c.files[h].fingerprint==a){f=h;break}"number"!=typeof f&&(f=c.files.push({fingerprint:a})-1);this.file=c.files[f];this.database=c}var b;try{b="localStorage"in window&&null!==window.localStorage&&localStorage}catch(c){b=!1}var d=PDFJS.isFirefoxExtension;a.prototype={set:function(a,c){if(!("file"in this))return!1;this.file[a]=c;var f=JSON.stringify(this.database);d?FirefoxCom.request("setDatabase",f):b&&localStorage.setItem("database",f)},get:functio [...]
+this)?b:this.file[a]||b}};return a}(),cache=new Cache(kCacheSize),renderingQueue=new RenderingQueue,currentPageNumber=1,kSidebarWidth=200,PDFView={pages:[],thumbnails:[],currentScale:kUnknownScale,currentScaleValue:null,initialBookmark:document.location.hash.substring(1),sidebarWidth:kSidebarWidth,toggleSidebar:function(){document.body.className.match(/\bnosidebar\b/)?(document.body.className=document.body.className.replace(/ ?nosidebar\b/,""),this.sidebarWidth=kSidebarWidth,this.parseSc [...]
+document.body.className+=" nosidebar"},setScale:function(a,b){if(a!=this.currentScale){for(var c=this.pages,d=0;d<c.length;d++)c[d].update(a*kCssUnits);this.currentScale!=a&&this.pages[this.page-1].scrollIntoView();this.currentScale=a;c=document.createEvent("UIEvents");c.initUIEvent("scalechange",!1,!1,window,0);c.scale=a;c.resetAutoSettings=b;window.dispatchEvent(c)}},parseScale:function(a,b){if("custom"!=a){var c=parseFloat(a);this.currentScaleValue=a;if(c)this.setScale(c,!0);else{var [...]
+1];d&&(c=(window.innerWidth-this.sidebarWidth-kScrollbarPadding)/d.width*d.scale/kCssUnits,d=(window.innerHeight-kScrollbarPadding)/d.height*d.scale/kCssUnits,"page-width"==a&&this.setScale(c,b),"page-height"==a&&this.setScale(d,b),"page-fit"==a&&this.setScale(Math.min(c,d),b),"auto"==a&&this.setScale(Math.min(1,c),b),selectScaleOption(a))}}},zoomIn:function(){this.parseScale(Math.min(kMaxScale,this.currentScale*kDefaultScaleDelta),!0)},zoomOut:function(){this.parseScale(Math.max(kMinSca [...]
+kDefaultScaleDelta),!0)},set page(a){var b=this.pages;document.getElementById("pageNumber");if(0<a&&a<=b.length)b[a-1].updateStats(),currentPageNumber=a,c=document.createEvent("UIEvents"),c.initUIEvent("pagechange",!1,!1,window,0),c.pageNumber=a,window.dispatchEvent(c),updateViewarea.inProgress||this.loading&&1==a||b[a-1].scrollIntoView();else{var c=document.createEvent("UIEvents");c.initUIEvent("pagechange",!1,!1,window,0);c.pageNumber=this.page;window.dispatchEvent(c)}},get page(){retu [...]
+open:function(a,b){this.url=a;PDFView.loadingBar||(PDFView.loadingBar=new ProgressBar("#loadingBar",{}));var c=this;c.loading=!0;PDFJS.getDocument(a).then(function(a){c.load(a,b);c.loading=!1},function(a){document.getElementById("loading").textContent="Error";c.error("An error occurred while loading the PDF.",{message:a});c.loading=!1},function(a){c.progress(a.loaded/a.total)})},download:function(){var a=this.url.split("#")[0];PDFJS.isFirefoxExtension?FirefoxCom.request("download",a):(a+ [...]
+"_parent",window.open(a,"_parent"))},navigateTo:function(a){"string"===typeof a&&(a=this.destinations[a]);if(a instanceof Array){var b=a[0],b=b instanceof Object?this.pagesRefMap[b.num+" "+b.gen+" R"]:b+1;b>this.pages.length&&(b=this.pages.length);b&&(this.page=b,this.pages[b-1].scrollIntoView(a))}},getDestinationHash:function(a){if("string"===typeof a)return PDFView.getAnchorUrl("#"+escape(a));if(a instanceof Array){var b=a[0];if(b=b instanceof Object?this.pagesRefMap[b.num+" "+b.gen+" [...]
+PDFView.getAnchorUrl("#page="+b),c=a[1];if("object"===typeof c&&"name"in c&&"XYZ"==c.name&&(b+="&zoom="+100*(a[4]||this.currentScale),a[2]||a[3]))b+=","+(a[2]||0)+","+(a[3]||0);return b}}return""},getAnchorUrl:function(a){return PDFJS.isFirefoxExtension?this.url.split("#")[0]+a:a},error:function(a,b){var c=document.getElementById("errorWrapper");c.removeAttribute("hidden");document.getElementById("errorMessage").textContent=a;document.getElementById("errorClose").onclick=function(){c.set [...]
+"true")};var d=document.getElementById("errorMoreInfo"),e=document.getElementById("errorShowMore"),g=document.getElementById("errorShowLess");e.onclick=function(){d.removeAttribute("hidden");e.setAttribute("hidden","true");g.removeAttribute("hidden")};g.onclick=function(){d.setAttribute("hidden","true");e.removeAttribute("hidden");g.setAttribute("hidden","true")};e.removeAttribute("hidden");g.setAttribute("hidden","true");d.value="PDF.JS Build: "+PDFJS.build+"\n";b&&(d.value+="Message: " [...]
+b.stack?d.value+="\nStack: "+b.stack:(b.filename&&(d.value+="\nFile: "+b.filename),b.lineNumber&&(d.value+="\nLine: "+b.lineNumber)));d.rows=d.value.split("\n").length-1},progress:function(a){a=Math.round(100*a);document.getElementById("loading").textContent="Loading... "+a+"%";PDFView.loadingBar.percent=a},load:function(a,b){function c(a,b){a.onAfterDraw=function(){b.setImage(a.canvas);preDraw()}}document.getElementById("errorWrapper").setAttribute("hidden","true");document.getElementBy [...]
+"true");var d=document.getElementById("sidebarView");for(d.parentNode.scrollTop=0;d.hasChildNodes();)d.removeChild(d.lastChild);"_loadingInterval"in d&&clearInterval(d._loadingInterval);for(var e=document.getElementById("viewer");e.hasChildNodes();)e.removeChild(e.lastChild);var g=a.numPages,f=a.fingerprint,h=null;document.getElementById("numPages").textContent=g;document.getElementById("pageNumber").max=g;PDFView.documentFingerprint=f;var i=PDFView.store=new Settings(f);if(i.get("exists [...]
+i.get("page","1"),j=i.get("zoom",PDFView.currentScale),l=i.get("scrollLeft","0"),i=i.get("scrollTop","0"),h="page="+f+"&zoom="+j+","+l+","+i;for(var k=this.pages=[],m={},t=this.thumbnails=[],f=[],j=1;j<=g;j++)f.push(a.getPage(j));var n=this,f=PDFJS.Promise.all(f);f.then(function(a){for(var f=1;f<=g;f++){var h=a[f-1],i=new PageView(e,h,f,b,h.stats,n.navigateTo.bind(n)),j=new ThumbnailView(d,h,f);c(i,j);k.push(i);t.push(j);h=h.ref;m[h.num+" "+h.gen+" R"]=f}n.pagesRefMap=m});j=a.getDestinat [...]
+a});PDFJS.Promise.all([f,j]).then(function(){a.getOutline().then(function(a){a&&(n.outline=new DocumentOutlineView(a),document.getElementById("outlineSwitch").removeAttribute("disabled"),n.switchSidebarView("outline"))});n.setInitialView(h,b)});a.getMetadata().then(function(a){var b=a.metadata;n.documentInfo=a.info;n.metadata=b})},setInitialView:function(a,b){this.currentScale=0;this.currentScaleValue=null;this.initialBookmark?(this.setHash(this.initialBookmark),this.initialBookmark=null [...]
+b&&(this.parseScale(b,!0),this.page=1);this.page>this.pages.length&&(this.page=this.pages.length);PDFView.currentScale===kUnknownScale&&this.parseScale(kDefaultScale,!0);updateThumbViewArea()},setHash:function(a){if(a)if(0<=a.indexOf("=")){var b=PDFView.parseQueryString(a);if("nameddest"in b)PDFView.navigateTo(b.nameddest);else if("page"in b)if(this.page=a=b.page|0||1,"zoom"in b){var b=b.zoom.split(","),c=b[0],d=parseFloat(c);d&&(c=d/100);this.pages[a-1].scrollIntoView([null,{name:"XYZ"} [...]
+0,c])}else this.page=b.page}else/^\d+$/.test(a)?this.page=a:PDFView.navigateTo(unescape(a))},switchSidebarView:function(a){var b=document.getElementById("sidebarScrollView"),c=document.getElementById("outlineScrollView"),d=document.getElementById("thumbsSwitch"),e=document.getElementById("outlineSwitch");switch(a){case "thumbs":b.removeAttribute("hidden");c.setAttribute("hidden","true");d.setAttribute("data-selected",!0);e.removeAttribute("data-selected");updateThumbViewArea();break;case [...]
+"true"),c.removeAttribute("hidden"),d.removeAttribute("data-selected"),e.setAttribute("data-selected",!0)}},pinSidebar:function(){document.getElementById("sidebar").classList.toggle("pinned")},getVisiblePages:function(){for(var a=this.pages,b=[],c=10,d=window.pageYOffset,e=1;e<=a.length;++e){var g=a[e-1].height+10;if(c+g>d)break;c+=g}for(d=window.pageYOffset+window.innerHeight;e<=a.length&&c<d;++e)g=a[e-1],b.push({id:g.id,y:c,view:g}),c+=g.height*g.scale+10;return b},getVisibleThumbs:fun [...]
+this.thumbnails,b=[],c=document.getElementById("sidebarScrollView"),d=5,e=c.scrollTop,g=1;g<=a.length;++g){var f=a[g-1],f=f.height*f.scaleY+5;if(d+f>e)break;d+=f}for(c=e+c.clientHeight;g<=a.length&&d<c;++g)e=a[g-1],b.push({id:e.id,y:d,view:e}),d+=e.height*e.scaleY+5;return b},parseQueryString:function(a){for(var a=a.split("&"),b={},c=0;c<a.length;++c){var d=a[c].split("="),e=1<d.length?d[1]:null;b[unescape(d[0])]=unescape(e)}return b}},PageView=function(a,b,c,d){function e(a,b){function [...]
+PDFView.getDestinationHash(b);a.onclick=function(){b&&PDFView.navigateTo(b);return!1}}function d(a,c){var e=b.convertToViewportRectangle(c.rect),e=PDFJS.Util.normalizeRect(e),f=document.createElement(a);f.style.left=Math.floor(e[0])+"px";f.style.top=Math.floor(e[1])+"px";f.style.width=Math.ceil(e[2]-e[0])+"px";f.style.height=Math.ceil(e[3]-e[1])+"px";return f}function e(a,c){var f=document.createElement("section");f.className="annotComment";var g=d("img",c),a=c.type,i=b.convertToViewport [...]
+i=PDFJS.Util.normalizeRect(i);g.src=kImageDirectory+a.toLowerCase()+".svg";g.alt="["+a+" Annotation]";var l=document.createElement("div");l.setAttribute("hidden",!0);var p=document.createElement("h1"),q=document.createElement("p");l.style.left=Math.floor(i[2])+"px";l.style.top=Math.floor(i[1])+"px";p.textContent=c.title;if(!c.content&&!c.title)l.setAttribute("hidden",!0);else{for(var i=document.createElement("span"),r=c.content.split("\n"),o=0,s=r.length;o<s;++o)i.appendChild(document.cr [...]
+o<s-1&&i.appendChild(document.createElement("br"));q.appendChild(i);g.addEventListener("mouseover",function(){l.removeAttribute("hidden")},!1);g.addEventListener("mouseout",function(){l.setAttribute("hidden",!0)},!1)}l.appendChild(p);l.appendChild(q);f.appendChild(g);f.appendChild(l);return f}a.getAnnotations().then(function(a){for(var b=0;b<a.length;b++){var f=a[b];switch(f.type){case "Link":var h=d("a",f);h.href=f.url||"";f.url||c(h,"dest"in f?f.dest:null);g.appendChild(h);break;case " [...]
+e(f.name,f))&&g.appendChild(f)}}})}this.id=c;this.pdfPage=b;this.scale=d||1;this.viewport=this.pdfPage.getViewport(this.scale);c=document.createElement("a");c.name=""+this.id;var g=document.createElement("div");g.id="pageContainer"+this.id;g.className="page";a.appendChild(c);a.appendChild(g);this.destroy=function(){this.update();this.pdfPage.destroy()};this.update=function(a){this.scale=a||this.scale;this.viewport=a=this.pdfPage.getViewport(this.scale);g.style.width=a.width+"px";for(g.st [...]
+a.height+"px";g.hasChildNodes();)g.removeChild(g.lastChild);g.removeAttribute("data-loaded");delete this.canvas;this.loadingIconDiv=document.createElement("div");this.loadingIconDiv.className="loadingIcon";g.appendChild(this.loadingIconDiv)};Object.defineProperty(this,"width",{get:function(){return this.viewport.width},enumerable:!0});Object.defineProperty(this,"height",{get:function(){return this.viewport.height},enumerable:!0});this.getPagePoint=function(a,b){return this.viewport.conve [...]
+b)};this.scrollIntoView=function(a){if(a){var b=0,c=0,d=0,e=0,k;k=0;switch(a[1].name){case "XYZ":b=a[2];c=a[3];k=a[4];break;case "Fit":case "FitB":k="page-fit";break;case "FitH":case "FitBH":c=a[2];k="page-width";break;case "FitV":case "FitBV":b=a[2];k="page-height";break;case "FitR":b=a[2];c=a[3];d=a[4]-b;e=a[5]-c;a=(window.innerWidth-kScrollbarPadding)/d/kCssUnits;k=(window.innerHeight-kScrollbarPadding)/e/kCssUnits;k=Math.min(a,k);break;default:return}var m=[this.viewport.convertToVie [...]
+c),this.viewport.convertToViewportPoint(b+d,c+e)];k&&k!==PDFView.currentScale?PDFView.parseScale(k,!0):PDFView.currentScale===kUnknownScale&&PDFView.parseScale(kDefaultScale,!0);setTimeout(function(){var a=Math.min(m[0][0],m[1][0]),b=Math.min(m[0][1],m[1][1]),c=Math.abs(m[0][0]-m[1][0]),d=Math.abs(m[0][1]-m[1][1]),e=document.createElement("div");e.style.position="absolute";e.style.left=Math.floor(a)+"px";e.style.top=Math.floor(b)+"px";e.style.width=Math.ceil(c)+"px";e.style.height=Math.c [...]
+g.appendChild(e);e.scrollIntoView(!0);g.removeChild(e)},0)}else g.scrollIntoView(!0)};this.drawingRequired=function(){return!g.querySelector("canvas")};this.draw=function(a){function c(d){k.loadingIconDiv&&(g.removeChild(k.loadingIconDiv),delete k.loadingIconDiv);d&&PDFView.error("An error occurred while rendering the page.",d);k.stats=b.stats;k.updateStats();if(k.onAfterDraw)k.onAfterDraw();cache.push(k);a()}if(this.drawingRequired()){var d=document.createElement("canvas");d.id="page"+t [...]
+!0;g.appendChild(d);this.canvas=d;var j=null;PDFJS.disableTextLayer||(j=document.createElement("div"),j.className="textLayer",g.appendChild(j));var j=j?new TextLayerBuilder(j):null,l=this.viewport;d.width=l.width;d.height=l.height;l=d.getContext("2d");l.save();l.fillStyle="rgb(255, 255, 255)";l.fillRect(0,0,d.width,d.height);l.restore();var k=this;this.pdfPage.render({canvasContext:l,viewport:this.viewport,textLayer:j}).then(function(){c(null)},function(a){c(a)});e(this.pdfPage,this.view [...]
+!0)}else this.updateStats(),a()};this.updateStats=function(){PDFJS.pdfBug&&Stats.enabled&&Stats.add(this.id,this.stats)}},ThumbnailView=function(a,b,c){function d(){var a=document.createElement("canvas");a.id="thumbnail"+c;a.mozOpaque=!0;a.width=i;a.height=j;k.setAttribute("data-loaded",!0);k.appendChild(a);m.style.visibility="visible";a=a.getContext("2d");a.save();a.fillStyle="rgb(255, 255, 255)";a.fillRect(0,0,i,j);a.restore();return a}var e=document.createElement("a");e.href=PDFView.g [...]
+c);e.onclick=function(){PDFView.page=c;return!1};var g=b.getViewport(1),f=g.width,g=g.height,h=f/g;this.id=c;var i=this.width=1<=h?134:134*h,j=this.height=1>=h?134:134/h,l=this.scaleX=i/f;this.scaleY=j/g;var k=document.createElement("div");k.id="thumbnailContainer"+c;k.className="thumbnail";var m=document.createElement("div");m.id="thumbnailLabel"+c;m.className="thumbnailLabelContainer";f=document.createElement("span");f.innerHTML=c;f.className="thumbnailLabel";m.appendChild(f);m.style.v [...]
+"hidden";e.appendChild(k);e.appendChild(m);a.appendChild(e);this.hasImage=!1;this.drawingRequired=function(){return!this.hasImage};this.draw=function(a){if(this.hasImage)a();else{var c=d(),e=b.getViewport(l);b.render({canvasContext:c,viewport:e}).then(function(){a()},function(){a()});this.hasImage=!0}};this.setImage=function(a){if(!this.hasImage&&a){var b=d();b.drawImage(a,0,0,a.width,a.height,0,0,b.canvas.width,b.canvas.height);this.hasImage=!0}}},DocumentOutlineView=function(a){functio [...]
+PDFView.getDestinationHash(b.dest);a.onclick=function(){PDFView.navigateTo(b.dest);return!1}}for(a=[{parent:document.getElementById("outlineView"),items:a}];0<a.length;){var c=a.shift(),d,e=c.items.length;for(d=0;d<e;d++){var g=c.items[d],f=document.createElement("div");f.className="outlineItem";var h=document.createElement("a");b(h,g);h.textContent=g.title;f.appendChild(h);0<g.items.length&&(h=document.createElement("div"),h.className="outlineItems",f.appendChild(h),a.push({parent:h,ite [...]
+c.parent.appendChild(f)}}},CustomStyle=function(){function a(){}var b=["ms","Moz","Webkit","O"],c={};a.getProp=function(a,e){if(1==arguments.length&&"string"==typeof c[a])return c[a];var e=e||document.documentElement,g=e.style,f,h;if("string"==typeof g[a])return c[a]=a;h=a.charAt(0).toUpperCase()+a.slice(1);for(var i=0,j=b.length;i<j;i++)if(f=b[i]+h,"string"==typeof g[f])return c[a]=f;return c[a]="undefined"};a.setProp=function(a,b,c){a=this.getProp(a);"undefined"!=a&&(b.style[a]=c)};ret [...]
+TextLayerBuilder=function(a){this.textLayerDiv=a;this.beginLayout=function(){this.textDivs=[];this.textLayerQueue=[]};this.endLayout=function(){function a(){if(0===d.length)clearInterval(g),f=!0;else{var b=d.shift();0<b.dataset.textLength&&(e.appendChild(b),1<b.dataset.textLength&&(CustomStyle.setProp("transform",b,"scale("+b.dataset.canvasWidth/b.offsetWidth+", 1)"),CustomStyle.setProp("transformOrigin",b,"0% 0%")))}}function c(){f?window.removeEventListener("scroll",c,!1):(clearInterva [...]
+j=setTimeout(function(){g=setInterval(a,h)},i))}var d=this.textDivs,e=this.textLayerDiv,g=null,f=!1,h=0,i=500,g=setInterval(a,h),j=null;window.addEventListener("scroll",c,!1)};this.appendText=function(a,c,d){var e=document.createElement("div"),d=d*a.geom.vScale;e.dataset.canvasWidth=a.canvasWidth*a.geom.hScale;e.dataset.fontName=c;e.style.fontSize=d+"px";e.style.left=a.geom.x+"px";e.style.top=a.geom.y-d+"px";e.textContent=PDFJS.bidi(a,-1);e.dir=a.direction;e.dataset.textLength=a.length;t [...]
+window.addEventListener("load",function(){var a=document.location.hash.substring(1),a=PDFView.parseQueryString(a);"disableWorker"in a&&(PDFJS.disableWorker="true"===a.disableWorker);"disableTextLayer"in a&&(PDFJS.disableTextLayer="true"===a.disableTextLayer);if("pdfBug"in a&&(!PDFJS.isFirefoxExtension||FirefoxCom.request("pdfBugEnabled")))PDFJS.pdfBug=!0,a=a.pdfBug.split(","),PDFBug.enable(a),PDFBug.init();document.getElementById("sidebarScrollView").addEventListener("scroll",updateThumb [...]
+!0);PDFView.open(file,0)},!0);var sidebarHideListener=function(){PDFView.sidebarWidth=document.body.className.match(/\bnosidebar\b/)?0:kSidebarWidth;PDFView.parseScale(PDFView.currentScaleValue)};document.body.addEventListener("transitionend",sidebarHideListener,!1);document.body.addEventListener("oTransitionEnd",sidebarHideListener,!1);document.body.addEventListener("webkitTransitionEnd",sidebarHideListener,!1);
+function preDraw(){var a=PDFView.pages,b=PDFView.getVisiblePages(),c=b[b.length-1];a[c.id]&&a[c.id].drawingRequired()?renderingQueue.enqueueDraw(a[c.id]):a[b[0].id-2]&&renderingQueue.enqueueDraw(a[b[0].id-2])}
+function updateViewarea(){for(var a=PDFView.getVisiblePages(),b,c=0;c<a.length;c++){var d=PDFView.pages[a[c].id-1];b|=d.drawingRequired();renderingQueue.enqueueDraw(d)}if(a.length){b||preDraw();updateViewarea.inProgress=!0;a=a[0];PDFView.page=a.id;updateViewarea.inProgress=!1;b=PDFView.currentScale;c=PDFView.currentScaleValue;b=c==b?100*b:c;var c=a.id,d="#page="+c+("&zoom="+b),a=PDFView.pages[c-1].getPagePoint(window.pageXOffset,window.pageYOffset-a.y-52),d=d+(","+Math.round(a[0])+","+Ma [...]
+e=PDFView.store;e.set("exists",!0);e.set("page",c);e.set("zoom",b);e.set("scrollLeft",Math.round(a[0]));e.set("scrollTop",Math.round(a[1]));a=PDFView.getAnchorUrl(d);document.getElementById("viewBookmark").href=a}}window.addEventListener("scroll",function(){updateViewarea()},!0);var thumbnailTimer;
+function updateThumbViewArea(){thumbnailTimer&&clearTimeout(thumbnailTimer);thumbnailTimer=setTimeout(function(){for(var a=PDFView.getVisibleThumbs(),b=0;b<a.length;b++)renderingQueue.enqueueDraw(PDFView.thumbnails[a[b].id-1])},50)}window.addEventListener("transitionend",updateThumbViewArea,!0);window.addEventListener("webkitTransitionEnd",updateThumbViewArea,!0);
+window.addEventListener("resize",function(){(document.getElementById("pageWidthOption").selected||document.getElementById("pageFitOption").selected||document.getElementById("pageAutoOption").selected)&&PDFView.parseScale(document.getElementById("scaleSelect").value);updateViewarea()});window.addEventListener("hashchange",function(){PDFView.setHash(document.location.hash.substring(1))});
+window.addEventListener("change",function(a){if((a=a.target.files)&&0!=a.length){var b=new FileReader;b.onload=function(a){for(var a=a.target.result,b=new ArrayBuffer(a.length),b=new Uint8Array(b),e=0;e<a.length;e++)b[e]=a.charCodeAt(e);PDFJS.getDocument(b).then(function(a){PDFView.load(a)})};b.readAsBinaryString(a[0]);document.getElementById("viewBookmark").setAttribute("hidden","true");document.getElementById("download").setAttribute("hidden","true")}},!0);
+function selectScaleOption(a){for(var b=document.getElementById("scaleSelect").options,c=!1,d=0;d<b.length;d++){var e=b[d];e.value!=a?e.selected=!1:c=e.selected=!0}return c}
+window.addEventListener("scalechange",function(a){var b=document.getElementById("customScaleOption");b.selected=!1;if((a.resetAutoSettings||!document.getElementById("pageWidthOption").selected&&!document.getElementById("pageFitOption").selected&&!document.getElementById("pageAutoOption").selected)&&!selectScaleOption(""+a.scale))b.textContent=Math.round(1E4*a.scale)/100+"%",b.selected=!0;updateViewarea()},!0);
+window.addEventListener("pagechange",function(a){a=a.pageNumber;document.getElementById("pageNumber").value!=a&&(document.getElementById("pageNumber").value=a);document.getElementById("previous").disabled=1>=a;document.getElementById("next").disabled=a>=PDFView.pages.length},!0);
+window.addEventListener("keydown",function(a){var b=!1,c=(a.ctrlKey?1:0)|(a.altKey?2:0)|(a.shiftKey?4:0)|(a.metaKey?8:0);if(1==c||8==c)switch(a.keyCode){case 61:case 107:case 187:PDFView.zoomIn();b=!0;break;case 109:case 189:PDFView.zoomOut();b=!0;break;case 48:PDFView.parseScale(kDefaultScale,!0),b=!0}if(b)a.preventDefault();else{var d=document.activeElement;if(!(d&&"INPUT"==d.tagName)){for(var e=document.getElementById("controls");d;){if(d===e)return;d=d.parentNode}if(0==c)switch(a.key [...]
+b=!0;break;case 39:case 74:case 78:PDFView.page++,b=!0}b&&a.preventDefault()}}});
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationView.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationView.java
new file mode 100644
index 0000000..d5f82fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationView.java
@@ -0,0 +1,24 @@
+/*
+ * PDFViewerApplicationView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.common.satellite.SatelliteApplicationView;
+import org.rstudio.studio.client.pdfviewer.events.InitCompleteEvent;
+
+public interface PDFViewerApplicationView extends SatelliteApplicationView
+{
+ HandlerRegistration addInitCompleteHandler(InitCompleteEvent.Handler handler);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationWindow.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationWindow.java
new file mode 100644
index 0000000..eed33b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerApplicationWindow.java
@@ -0,0 +1,119 @@
+/*
+ * PDFViewerApplicationWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.satellite.SatelliteWindow;
+import org.rstudio.studio.client.pdfviewer.PDFViewerPresenter;
+import org.rstudio.studio.client.pdfviewer.events.InitCompleteEvent;
+import org.rstudio.studio.client.pdfviewer.model.PDFViewerParams;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+
+ at Singleton
+public class PDFViewerApplicationWindow extends SatelliteWindow
+ implements PDFViewerApplicationView
+{
+
+ @Inject
+ public PDFViewerApplicationWindow(Provider<PDFViewerPresenter> pPresenter,
+ Provider<EventBus> pEventBus,
+ Provider<FontSizeManager> pFontSizeManager)
+ {
+ super(pEventBus, pFontSizeManager);
+ pPresenter_ = pPresenter;
+ }
+
+ @Override
+ protected void onInitialize(LayoutPanel mainPanel, JavaScriptObject params)
+ {
+ Window.setTitle("RStudio: Compile PDF");
+
+ // create the presenter and activate it with the passed params
+ PDFViewerParams pdfParams = params.<PDFViewerParams>cast();
+ presenter_ = pPresenter_.get();
+ presenter_.addInitCompleteHandler(new InitCompleteEvent.Handler()
+ {
+ @Override
+ public void onInitComplete(InitCompleteEvent event)
+ {
+ initCompleted_ = true;
+ fireEvent(new InitCompleteEvent());
+ }
+ });
+ presenter_.onActivated(pdfParams);
+
+ // PDF.js doesn't work correctly unless the viewer takes its natural
+ // height and the window is allowed to scroll. So get rid of the main
+ // panel and add the PDFViewer directly to the root panel.
+ mainPanel.setVisible(false);
+ RootPanel.get().add(presenter_);
+ }
+
+ @Override
+ protected boolean allowScrolling()
+ {
+ return true;
+ }
+
+ @Override
+ public void reactivate(JavaScriptObject params)
+ {
+ if (params != null)
+ {
+ PDFViewerParams pdfParams = params.<PDFViewerParams>cast();
+ presenter_.onActivated(pdfParams);
+ }
+ }
+
+ @Override
+ public Widget getWidget()
+ {
+ return this;
+ }
+
+ @Override
+ public HandlerRegistration addInitCompleteHandler(
+ final InitCompleteEvent.Handler handler)
+ {
+ if (initCompleted_)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ handler.onInitComplete(new InitCompleteEvent());
+ }
+ });
+ }
+ return addHandler(handler, InitCompleteEvent.TYPE);
+ }
+
+ private boolean initCompleted_;
+ private final Provider<PDFViewerPresenter> pPresenter_;
+ private PDFViewerPresenter presenter_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.java
new file mode 100644
index 0000000..98f242c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.java
@@ -0,0 +1,361 @@
+/*
+ * PDFViewerPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.studio.client.common.Value;
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+import org.rstudio.studio.client.pdfviewer.PDFViewerPresenter;
+import org.rstudio.studio.client.pdfviewer.events.InitCompleteEvent;
+import org.rstudio.studio.client.pdfviewer.events.PageClickEvent;
+import org.rstudio.studio.client.pdfviewer.model.SyncTexCoordinates;
+import org.rstudio.studio.client.pdfviewer.pdfjs.PDFView;
+import org.rstudio.studio.client.pdfviewer.pdfjs.PdfJs;
+
+public class PDFViewerPanel extends Composite
+ implements PDFViewerPresenter.Display
+{
+ interface Binder extends UiBinder<Widget, PDFViewerPanel>
+ {}
+
+ @Inject
+ public PDFViewerPanel(PDFViewerToolbar toolbar)
+ {
+ toolbar_ = toolbar;
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+ viewer_.getElement().setId("viewer");
+ Document.get().getBody().getStyle().setMarginLeft(200, Style.Unit.PX);
+
+ new WidgetHandlerRegistration(this)
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return Event.addNativePreviewHandler(new Event.NativePreviewHandler()
+ {
+ @Override
+ public void onPreviewNativeEvent(Event.NativePreviewEvent event)
+ {
+ if ( (event.getTypeInt() == Event.ONCLICK) &&
+ DomUtils.isCommandClick(event.getNativeEvent()))
+ {
+ EventTarget target =
+ event.getNativeEvent().getEventTarget();
+
+ if (Element.is(target))
+ {
+ Element el = Element.as(target);
+ if (viewer_.getElement().isOrHasChild(el))
+ {
+ fireClickEvent(event.getNativeEvent(), el);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+ }
+
+ private void fireClickEvent(NativeEvent nativeEvent, Element el)
+ {
+ Element pageEl = el;
+ while (pageEl != null)
+ {
+ if (pageEl.getId().matches("^pageContainer([\\d]+)$"))
+ {
+ break;
+ }
+
+ pageEl = pageEl.getParentElement();
+ }
+
+ if (pageEl == null)
+ return;
+
+ int page = getContainerPageNum(pageEl);
+
+ int pageX = nativeEvent.getClientX() +
+ Document.get().getDocumentElement().getScrollLeft() +
+ Document.get().getBody().getScrollLeft() -
+ pageEl.getAbsoluteLeft();
+ int pageY = nativeEvent.getClientY() +
+ Document.get().getDocumentElement().getScrollTop() +
+ Document.get().getBody().getScrollTop() -
+ pageEl.getAbsoluteTop();
+
+ fireEvent(new PageClickEvent(new SyncTexCoordinates(
+ page,
+ (int) ((pageX / PDFView.currentScale() / 96) * 72),
+ (int) ((pageY / PDFView.currentScale() / 96) * 72))));
+ }
+
+ private int getContainerPageNum(Element container)
+ {
+ return Integer.parseInt(
+ container.getId().substring("pageContainer".length()));
+ }
+
+ @Override
+ public PDFViewerToolbarDisplay getToolbarDisplay()
+ {
+ return toolbar_;
+ }
+
+ @Override
+ public void toggleThumbnails()
+ {
+ PDFView.toggleSidebar();
+ }
+
+ @Override
+ public void updateSelectedPage(int pageNumber)
+ {
+ Element pageLabel =
+ Document.get().getElementById("thumbnailLabel" + pageNumber);
+ if (pageLabel != null && pageLabel.getClassName().contains("selected"))
+ return;
+
+ if (selectedPageLabel_ != null)
+ selectedPageLabel_.removeClassName("selected");
+
+ selectedPageLabel_ = pageLabel;
+
+ if (selectedPageLabel_ != null)
+ {
+ selectedPageLabel_.addClassName("selected");
+
+ Element scroller = Document.get().getElementById("sidebarScrollView");
+ Element page =
+ Document.get().getElementById("thumbnailContainer" + pageNumber);
+ DomUtils.ensureVisibleVert(scroller, page, 30);
+ }
+ }
+
+ @Override
+ public void setStatusText(String text)
+ {
+ lblStatus_.setText(text);
+ }
+
+ @Override
+ public SyncTexCoordinates getTopCoordinates()
+ {
+ return getBoundaryCoordinates(true);
+ }
+
+ @Override
+ public SyncTexCoordinates getBottomCoordinates()
+ {
+ return getBoundaryCoordinates(false);
+ }
+
+ private SyncTexCoordinates getBoundaryCoordinates(boolean top)
+ {
+ int scrollY = Document.get().getScrollTop() + toolbar_.getOffsetHeight();
+
+ // linear probe our way to the current page
+ Element viewerEl = viewer_.getElement();
+ for (int i = 1; i < viewerEl.getChildCount(); i+=2)
+ {
+ Node childNode = viewerEl.getChild(i);
+ if (Element.is(childNode))
+ {
+ Element el = Element.as(childNode);
+
+ if (el.getAbsoluteBottom() > scrollY)
+ {
+ int pageNum = getContainerPageNum(el);
+ int pageY = scrollY - el.getAbsoluteTop();
+ if (pageY < 0)
+ pageY = 0;
+
+ if (!top)
+ {
+ final int kStatusBarHeight = 16;
+ pageY += Document.get().getClientHeight() - kStatusBarHeight;
+ }
+
+ return new SyncTexCoordinates(
+ pageNum,
+ 0,
+ (int) ((pageY / PDFView.currentScale() / 96) * 72));
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void navigateTo(final PdfLocation pdfLocation)
+ {
+ double factor = PDFView.currentScale() * 96 / 72;
+
+ final double x = pdfLocation.getX() * factor;
+ final double y = pdfLocation.getY() * factor;
+ final double w = pdfLocation.getWidth() * factor;
+ final double h = pdfLocation.getHeight() * factor;
+
+ final Value<Integer> retries = new Value<Integer>(0);
+
+ // Sometimes pageContainer is null during load, so retry every 100ms
+ // until it's not, or we've tried 40 times.
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ @Override
+ public boolean execute()
+ {
+ Element pageContainer = Document.get().getElementById(
+ "pageContainer" + pdfLocation.getPage());
+
+ if (pageContainer == null)
+ {
+ retries.setValue(retries.getValue() + 1);
+ return retries.getValue() < 40;
+ }
+
+ if (pdfLocation.isFromClick())
+ {
+ final DivElement div = Document.get().createDivElement();
+ div.getStyle().setPosition(Style.Position.ABSOLUTE);
+ div.getStyle().setTop(y, Unit.PX);
+ div.getStyle().setLeft(x, Unit.PX);
+ div.getStyle().setWidth(w, Unit.PX);
+ div.getStyle().setHeight(h, Unit.PX);
+ div.getStyle().setBackgroundColor("rgba(0, 126, 246, 0.1)");
+ div.getStyle().setProperty("transition", "opacity 4s");
+ // use DomUtils to set transition styles so gwt doesn't assert
+ // an invalid style name (no camelCase) in debug mode
+ DomUtils.setStyle(div, "-moz-transition", "opacity 4s");
+ DomUtils.setStyle(div, "-webkit-transition", "opacity 4s");
+
+ pageContainer.appendChild(div);
+
+ Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand()
+ {
+ @Override
+ public boolean execute()
+ {
+ div.getStyle().setOpacity(0.0);
+ return false;
+ }
+ }, 2000);
+ }
+
+ // scroll to the page
+ PDFView.goToPage(pdfLocation.getPage());
+
+ // if the target isn't on-screen then scroll to it
+ if (pdfLocation.getY() > getBottomCoordinates().getY())
+ {
+ Window.scrollTo(
+ Window.getScrollLeft(),
+ Math.max(0, pageContainer.getAbsoluteTop() + (int) y - 180));
+ }
+
+ return false;
+ }
+ }, 100);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ if (!once_)
+ {
+ once_ = true;
+ PdfJs.load(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ loaded_ = true;
+ if (initialUrl_ != null)
+ open(initialUrl_);
+
+ fireEvent(new InitCompleteEvent());
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setURL(String url)
+ {
+ PDFView.setLoadingVisible(true);
+
+ if (loaded_)
+ open(url);
+ else
+ initialUrl_ = url;
+ }
+
+ @Override
+ public HandlerRegistration addInitCompleteHandler(
+ InitCompleteEvent.Handler handler)
+ {
+ return addHandler(handler, InitCompleteEvent.TYPE);
+ }
+
+ @Override
+ public void closeWindow()
+ {
+ WindowEx.get().close();
+ }
+
+ private native void open(String url) /*-{
+ $wnd.PDFView.open(url, 0);
+ }-*/;
+
+ @Override
+ public HandlerRegistration addPageClickHandler(PageClickEvent.Handler handler)
+ {
+ return addHandler(handler, PageClickEvent.TYPE);
+ }
+
+ private boolean loaded_;
+ private String initialUrl_;
+ private boolean once_;
+
+ @UiField(provided = true)
+ PDFViewerToolbar toolbar_;
+ @UiField
+ FlowPanel viewer_;
+ @UiField
+ Label lblStatus_;
+
+ private Element selectedPageLabel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.ui.xml
new file mode 100644
index 0000000..2ebe32c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerPanel.ui.xml
@@ -0,0 +1,175 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget"
+ xmlns:pdf="urn:import:org.rstudio.studio.client.pdfviewer.ui">
+
+
+ <ui:image field='StatusBarTile' src='images/StatusBarTile.png' />
+
+ <ui:style>
+ .pdfViewerPanel {
+ -webkit-user-select: auto;
+ }
+
+ .toolbarShadow {
+ position: fixed;
+ top: 0;
+ height: 30px;
+ left: -30px;
+ right: -30px;
+ z-index: 90;
+ box-shadow: 4px 0px 8px #222;
+ -moz-box-shadow: 4px 0px 8px #222;
+ -webkit-box-shadow: 4px 0px 8px #222;
+ }
+
+ @sprite .statusBar {
+ gwt-image: 'StatusBarTile';
+ background-repeat: repeat-x;
+ width: 100%;
+ font-size: 11px;
+ color: #3c474d;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 16px;
+ z-index: 90;
+ }
+ .statusBar div {
+ display: inline;
+ height: 16px;
+ white-space: nowrap;
+ }
+
+ .statusCaption {
+ position: relative;
+ top: 1px;
+ left: 3px;
+ }
+
+ </ui:style>
+ <ui:with field="res" type="org.rstudio.core.client.theme.res.ThemeResources"/>
+
+ <g:HTMLPanel styleName="{style.pdfViewerPanel}">
+ <pdf:PDFViewerToolbar ui:field="toolbar_" />
+ <div class="{style.toolbarShadow}"></div>
+ <div id="controls" style="display: none">
+ <button id="previous" onclick="PDFView.page--;" oncontextmenu="return false;">
+ <img src="images/go-up.svg" align="top" height="16"/>
+ Previous
+ </button>
+
+ <button id="next" onclick="PDFView.page++;" oncontextmenu="return false;">
+ <img src="images/go-down.svg" align="top" height="16"/>
+ Next
+ </button>
+
+ <div class="separator"></div>
+
+ <input type="number" id="pageNumber" onchange="PDFView.page = this.value;" value="1" size="4" min="1" />
+
+ <span>/</span>
+ <span id="numPages">--</span>
+
+ <div class="separator"></div>
+
+ <button id="zoomOut" title="Zoom Out" onclick="PDFView.zoomOut();" oncontextmenu="return false;">
+ <img src="images/zoom-out.svg" align="top" height="16"/>
+ </button>
+ <button id="zoomIn" title="Zoom In" onclick="PDFView.zoomIn();" oncontextmenu="return false;">
+ <img src="images/zoom-in.svg" align="top" height="16"/>
+ </button>
+
+ <div class="separator"></div>
+
+<!--
+ <select id="scaleSelect" onchange="PDFView.parseScale(this.value);" oncontextmenu="return false;">
+ <option id="customScaleOption" value="custom"></option>
+ <option value="0.5">50%</option>
+ <option value="0.75">75%</option>
+ <option value="1">100%</option>
+ <option value="1.25">125%</option>
+ <option value="1.5">150%</option>
+ <option value="2">200%</option>
+ <option id="pageWidthOption" value="page-width">Page Width</option>
+ <option id="pageFitOption" value="page-fit">Page Fit</option>
+ <option id="pageAutoOption" value="auto" selected="selected">Auto</option>
+ </select>
+-->
+
+ <div class="separator"></div>
+
+ <button id="print" onclick="window.print();" oncontextmenu="return false;">
+ <img src="images/document-print.svg" align="top" height="16"/>
+ Print
+ </button>
+
+ <button id="download" title="Download" onclick="PDFView.download();" oncontextmenu="return false;">
+ <img src="images/download.svg" align="top" height="16"/>
+ Download
+ </button>
+
+ <div class="separator"></div>
+
+ <input id="fileInput" type="file" oncontextmenu="return false;"/>
+
+ <div id="fileInputSeperator" class="separator"></div>
+
+ <a href="#" id="viewBookmark" title="Bookmark (or copy) current location">
+ <img src="images/bookmark.svg" alt="Bookmark" align="top" height="16"/>
+ </a>
+
+ <span id="info">--</span>
+ </div>
+ <div id="errorWrapper" hidden='true'>
+ <div id="errorMessageLeft">
+ <span id="errorMessage"></span>
+ <button id="errorShowMore" onclick="" oncontextmenu="return false;">
+ More Information
+ </button>
+ <button id="errorShowLess" onclick="" oncontextmenu="return false;" hidden='true'>
+ Less Information
+ </button>
+ </div>
+ <div id="errorMessageRight">
+ <button id="errorClose" oncontextmenu="return false;">
+ Close
+ </button>
+ </div>
+ <div class="clearBoth"></div>
+ <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
+ </div>
+
+ <div id="sidebar" style="top: 30px; bottom: 16px">
+ <div id="sidebarBox">
+ <div id="pinIcon" onClick="PDFView.pinSidebar()"></div>
+ <div id="sidebarScrollView">
+ <div id="sidebarView"></div>
+ </div>
+ <div id="outlineScrollView" hidden='true'>
+ <div id="outlineView"></div>
+ </div>
+ <div id="sidebarControls" style="display: none">
+ <button id="thumbsSwitch" title="Show Thumbnails" onclick="PDFView.switchSidebarView('thumbs')" data-selected="data-selected">
+ <img src="images/nav-thumbs.svg" align="top" height="16" alt="Thumbs" />
+ </button>
+ <button id="outlineSwitch" title="Show Document Outline" onclick="PDFView.switchSidebarView('outline')" disabled="disabled">
+ <img src="images/nav-outline.svg" align="top" height="16" alt="Document Outline" />
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <div id="loadingBox" hidden="hidden">
+ <div id="loading" hidden="hidden">Loading... 0%</div>
+ <div id="loadingBar" hidden="hidden"><div class="progress"></div></div>
+ </div>
+ <g:FlowPanel ui:field="viewer_"/>
+
+ <g:HorizontalPanel styleName="{style.statusBar}">
+ <g:Label ui:field="lblStatus_" styleName="{style.statusCaption}"></g:Label>
+ </g:HorizontalPanel>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.java
new file mode 100644
index 0000000..c25f808
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.java
@@ -0,0 +1,184 @@
+/*
+ * PDFViewerToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.HasButtonMethods;
+import org.rstudio.core.client.widget.InlineToolbarButton;
+import org.rstudio.core.client.widget.NumericTextBox;
+import org.rstudio.studio.client.pdfviewer.ui.images.Resources;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class PDFViewerToolbar extends Composite
+ implements PDFViewerToolbarDisplay
+{
+ interface Binder extends UiBinder<Widget, PDFViewerToolbar>
+ {}
+
+ @Inject
+ public PDFViewerToolbar(Commands commands)
+ {
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ if (BrowseCap.isMacintosh())
+ {
+ btnJumpToSource_.setTitle(
+ btnJumpToSource_.getTitle().replace("Ctrl+", "Cmd+"));
+ }
+
+ final Resources resources = GWT.create(Resources.class);
+
+ zoomOut_.addMouseDownHandler(new MouseDownHandler()
+ {
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ zoomOut_.setResource(resources.zoomButtonLeftPressed());
+ }
+ });
+ zoomOut_.addMouseUpHandler(new MouseUpHandler()
+ {
+ @Override
+ public void onMouseUp(MouseUpEvent event)
+ {
+ zoomOut_.setResource(resources.zoomButtonLeft());
+ }
+ });
+ zoomOut_.addMouseOutHandler(new MouseOutHandler()
+ {
+ @Override
+ public void onMouseOut(MouseOutEvent event)
+ {
+ zoomOut_.setResource(resources.zoomButtonLeft());
+ }
+ });
+ zoomIn_.addMouseDownHandler(new MouseDownHandler()
+ {
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ zoomIn_.setResource(resources.zoomButtonRightPressed());
+ }
+ });
+ zoomIn_.addMouseUpHandler(new MouseUpHandler()
+ {
+ @Override
+ public void onMouseUp(MouseUpEvent event)
+ {
+ zoomIn_.setResource(resources.zoomButtonRight());
+ }
+ });
+ zoomIn_.addMouseOutHandler(new MouseOutHandler()
+ {
+ @Override
+ public void onMouseOut(MouseOutEvent event)
+ {
+ zoomIn_.setResource(resources.zoomButtonRight());
+ }
+ });
+ }
+
+ @Override
+ public HasButtonMethods getViewPdfExternal()
+ {
+ return btnViewExternal_;
+ }
+
+ @Override
+ public HasButtonMethods getJumpToSource()
+ {
+ return btnJumpToSource_;
+ }
+
+ @Override
+ public HasButtonMethods getPrevButton()
+ {
+ return btnPrevious_;
+ }
+
+ @Override
+ public HasButtonMethods getNextButton()
+ {
+ return btnNext_;
+ }
+
+ @Override
+ public HasButtonMethods getThumbnailsButton()
+ {
+ return btnThumbnails_;
+ }
+
+ @Override
+ public void setPdfFile(FileSystemItem pdfFile)
+ {
+ filename_.setText(pdfFile.getName());
+ filename_.setTitle(pdfFile.getPath());
+ }
+
+ @Override
+ public HasClickHandlers getZoomOut()
+ {
+ return zoomOut_;
+ }
+
+ @Override
+ public HasClickHandlers getZoomIn()
+ {
+ return zoomIn_;
+ }
+
+ @Override
+ public HasValue<String> getPageNumber()
+ {
+ return pageNumber_;
+ }
+
+ @Override
+ public void selectPageNumber()
+ {
+ pageNumber_.selectAll();
+ }
+
+ @UiField
+ InlineToolbarButton btnViewExternal_;
+ @UiField
+ InlineToolbarButton btnJumpToSource_;
+ @UiField
+ InlineToolbarButton btnPrevious_;
+ @UiField
+ InlineToolbarButton btnNext_;
+ @UiField
+ InlineToolbarButton btnThumbnails_;
+ @UiField
+ Anchor filename_;
+ @UiField
+ Image zoomOut_;
+ @UiField
+ Image zoomIn_;
+ @UiField
+ NumericTextBox pageNumber_;
+ @UiField
+ Image fileIcon_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.ui.xml b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.ui.xml
new file mode 100644
index 0000000..452dfb1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbar.ui.xml
@@ -0,0 +1,164 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget"
+ xmlns:pdfviewer="urn:import:org.rstudio.studio.client.pdfviewer.ui">
+
+ <ui:with field="res"
+ type="org.rstudio.studio.client.pdfviewer.ui.images.Resources"/>
+ <ui:with field="fileIconRes"
+ type="org.rstudio.studio.client.common.filetypes.FileIconResources"/>
+ <ui:style>
+ .wrapper {
+ position: fixed;
+ top: 0;
+ height: 30px;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ cursor: default;
+ font-size: 12px;
+ padding: 0 6px 0 6px;
+ border-bottom: 1px solid #777;
+ border-top: 1px solid #fff;
+ background: #f1f1f5; /* Old browsers */
+ background: -moz-linear-gradient(top, #f1f1f5 1%, #eeeff3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(1%, #f1f1f5), color-stop(100%, #eeeff3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f1f1f5 1%, #eeeff3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f1f1f5 1%, #eeeff3 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #f1f1f5 1%, #eeeff3 100%); /* IE10+ */
+ background: literal("linear-gradient(top, #f1f1f5 1%,#eeeff3 100%)"); /* W3C */
+ filter: literal("progid:DXImageTransform.Microsoft.gradient( startColorstr='#f1f1f5', endColorstr='#eeeff3',GradientType=0 )"); /* IE6-9 */
+ }
+
+ .alignTable {
+ width: 100%;
+ height: 30px;
+ }
+
+ td.alignCell {
+ overflow: visible;
+ vertical-align: middle;
+ white-space: nowrap;
+ width: 33%;
+ }
+
+ td.alignCell * {
+ white-space: nowrap;
+ }
+
+ .filename {
+ font-weight: bold;
+ color: #838994;
+ margin-right: 6px;
+ text-decoration: none;
+ }
+
+ .inlineBlock {
+ display: inline;
+ }
+
+ .zoomButton {
+ cursor: pointer;
+ }
+
+ .pageNumber {
+ border: 1px solid rgb(153, 153, 153);
+ outline: none;
+ width: 30px;
+ text-align: right;
+ }
+
+ .pageNumber:focus {
+ outline: none;
+ }
+
+ .padLeft3 {
+ margin-left: 2px;
+ }
+
+ .fileIcon {
+ cursor: pointer;
+ }
+
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.wrapper}">
+ <table class="{style.alignTable}" cellpadding="0" cellspacing="0">
+ <tr>
+ <td class="{style.alignCell}">
+ <widget:ZeroHeightPanel yOffset="3" height="16" width="16">
+ <widget:child>
+ <g:Image resource="{fileIconRes.iconPdf}"
+ styleName="{style.fileIcon}"
+ ui:field="fileIcon_"/>
+ </widget:child>
+ </widget:ZeroHeightPanel>
+ <g:Anchor ui:field="filename_" styleName="{style.filename}" />
+ <widget:InlineToolbarSeparator />
+ <widget:InlineToolbarButton ui:field="btnViewExternal_"
+ icon="{res.openPdfExternalIcon}"
+ label=""
+ description="Open PDF in external viewer"/>
+ <widget:InlineToolbarSeparator />
+ <widget:InlineToolbarButton ui:field="btnJumpToSource_"
+ icon="{res.jumpToSourceIcon}"
+ label=""
+ description="Sync editor location to PDF view (Ctrl+Click) (Ctrl+F8)"/>
+
+ </td>
+ <td class="{style.alignCell}" align="center" nowrap="nowrap" style="min-width: 400px">
+ <widget:InlineToolbarButton ui:field="btnPrevious_"
+ icon="{res.previousPageIcon}"
+ label="Previous"
+ description=""/>
+ <widget:InlineToolbarSeparator />
+ <widget:InlineToolbarButton ui:field="btnNext_"
+ icon="{res.nextPageIcon}"
+ label="Next"
+ description=""/>
+ <widget:InlineToolbarSeparator />
+ <div class="{style.inlineBlock} {style.padLeft3}">
+ Page
+ <widget:NumericTextBox styleName="{style.pageNumber}"
+ ui:field="pageNumber_"/>
+ of <span id="numPages"/>
+ </div>
+ <widget:InlineToolbarSeparator />
+ <widget:InlineToolbarButton ui:field="btnThumbnails_"
+ icon="{res.thumbnailsIcon}"
+ label="Thumbnails"
+ description="Show/hide page thumbnails"/>
+ </td>
+ <td class="{style.alignCell}" align="right">
+ <widget:ZeroHeightPanel yOffset="9" height="24" width="61">
+ <widget:child>
+ <g:Image resource="{res.zoomButtonLeft}"
+ ui:field="zoomOut_"
+ styleName="{style.zoomButton}"/>
+ </widget:child>
+ <widget:child>
+ <g:Image resource="{res.zoomButtonRight}"
+ ui:field="zoomIn_"
+ styleName="{style.zoomButton}"/>
+ </widget:child>
+ </widget:ZeroHeightPanel>
+ <div class="{style.inlineBlock}">
+ <select id="scaleSelect" onchange="PDFView.parseScale(this.value);" oncontextmenu="return false;">
+ <option id="customScaleOption" value="custom"></option>
+ <option value="0.5">50%</option>
+ <option value="0.75">75%</option>
+ <option value="1">100%</option>
+ <option value="1.25">125%</option>
+ <option value="1.5">150%</option>
+ <option value="2">200%</option>
+ <option id="pageWidthOption" value="page-width">Page Width</option>
+ <option id="pageFitOption" value="page-fit">Page Fit</option>
+ <option id="pageAutoOption" value="auto" selected="selected">Auto</option>
+ </select>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbarDisplay.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbarDisplay.java
new file mode 100644
index 0000000..33d48b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFViewerToolbarDisplay.java
@@ -0,0 +1,35 @@
+/*
+ * PDFViewerToolbarDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.HasButtonMethods;
+
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.HasValue;
+
+public interface PDFViewerToolbarDisplay
+{
+ HasButtonMethods getViewPdfExternal();
+ HasButtonMethods getJumpToSource();
+ HasButtonMethods getPrevButton();
+ HasButtonMethods getNextButton();
+ HasButtonMethods getThumbnailsButton();
+ HasClickHandlers getZoomOut();
+ HasClickHandlers getZoomIn();
+ HasValue<String> getPageNumber();
+ void selectPageNumber();
+ void setPdfFile(FileSystemItem pdfFile);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFWidget.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFWidget.java
new file mode 100644
index 0000000..dcf6453
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PDFWidget.java
@@ -0,0 +1,76 @@
+/*
+ * PDFWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.canvas.client.Canvas;
+import com.google.gwt.canvas.dom.client.Context2d;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Composite;
+
+import org.rstudio.studio.client.pdfviewer.pdfjs.PdfJs;
+
+public class PDFWidget extends Composite
+{
+ public PDFWidget(String pdfUrl)
+ {
+ pdfUrl_ = pdfUrl;
+ canvas_ = Canvas.createIfSupported();
+ initWidget(canvas_);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ if (!initialized_)
+ {
+ initialized_ = true;
+
+ PdfJs.load(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ loadPdf(pdfUrl_, canvas_.getContext2d());
+ }
+ });
+ }
+ }
+
+ private native void loadPdf(String pdfUrl, Context2d context2d) /*-{
+ $wnd.PDFJS.getPdf(pdfUrl, function(data) {
+ try
+ {
+ //
+ // Instantiate PDFDoc with PDF data
+ //
+ var pdf = new $wnd.PDFJS.PDFDoc(data);
+ var page = pdf.getPage(1);
+
+ //
+ // Render PDF page into canvas context
+ //
+ page.startRendering(context2d);
+ }
+ catch (e)
+ {
+ $wnd.alert(e);
+ }
+ });
+ }-*/;
+
+ private final Canvas canvas_;
+ private boolean initialized_;
+ private final String pdfUrl_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PageNumberListBox.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PageNumberListBox.java
new file mode 100644
index 0000000..2d8b36c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/PageNumberListBox.java
@@ -0,0 +1,84 @@
+/*
+ * PageNumberListBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.ListBox;
+
+public class PageNumberListBox extends ListBox
+ implements HasValue<Integer>
+{
+ public PageNumberListBox()
+ {
+ super(false);
+ }
+
+ public void setPageCount(int value)
+ {
+ while (value > getItemCount())
+ addItem((getItemCount() + 1) + "");
+
+ while (value < getItemCount())
+ removeItem(getItemCount() - 1);
+ }
+
+ @Override
+ public Integer getValue()
+ {
+ if (getSelectedIndex() < 0)
+ return null;
+ return getSelectedIndex() + 1;
+ }
+
+ @Override
+ public void setValue(Integer value)
+ {
+ setValue(value, true);
+ }
+
+ @Override
+ public void setValue(Integer value, boolean fireEvents)
+ {
+ if (value == null)
+ value = 0;
+ Integer other = getValue();
+ if (other == null)
+ other = 0;
+
+ if (other.equals(value))
+ return;
+
+ if (value < 0)
+ throw new ArrayIndexOutOfBoundsException();
+ if (value > getItemCount())
+ setPageCount(value);
+
+ // If value is 0 (originally null) then this will be setSelectedIndex(-1),
+ // which is desired.
+ setSelectedIndex(value - 1);
+
+ if (fireEvents)
+ ValueChangeEvent.fire(this, value == 0 ? null : value);
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/JumpToSourceIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/JumpToSourceIcon.png
new file mode 100644
index 0000000..86a1c86
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/JumpToSourceIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/NextPageIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/NextPageIcon.png
new file mode 100755
index 0000000..d878c63
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/NextPageIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/OpenPdfExternalIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/OpenPdfExternalIcon.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/OpenPdfExternalIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/PreviousPageIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/PreviousPageIcon.png
new file mode 100755
index 0000000..e115931
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/PreviousPageIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/Resources.java b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/Resources.java
new file mode 100644
index 0000000..df6675a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/Resources.java
@@ -0,0 +1,60 @@
+/*
+ * Resources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.pdfviewer.ui.images;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface Resources extends ClientBundle
+{
+ @Source("OpenPdfExternalIcon.png")
+ ImageResource openPdfExternalIcon();
+
+ @Source("JumpToSourceIcon.png")
+ ImageResource jumpToSourceIcon();
+
+ @Source("NextPageIcon.png")
+ ImageResource nextPageIcon();
+
+ @Source("PreviousPageIcon.png")
+ ImageResource previousPageIcon();
+
+ @Source("SizeButton.png")
+ ImageResource sizeButton();
+
+ @Source("SizeButtonPressed.png")
+ ImageResource sizeButtonPressed();
+
+ @Source("ZoomButtonLeft.png")
+ ImageResource zoomButtonLeft();
+
+ @Source("ZoomButtonLeftPressed.png")
+ ImageResource zoomButtonLeftPressed();
+
+ @Source("ZoomButtonRight.png")
+ ImageResource zoomButtonRight();
+
+ @Source("ZoomButtonRightPressed.png")
+ ImageResource zoomButtonRightPressed();
+
+ @Source("ZoomInIcon.png")
+ ImageResource zoomInIcon();
+
+ @Source("ZoomOutIcon.png")
+ ImageResource zoomOutIcon();
+
+ @Source("ThumbnailsIcon.png")
+ ImageResource thumbnailsIcon();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButton.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButton.png
new file mode 100755
index 0000000..25d04af
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButtonPressed.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButtonPressed.png
new file mode 100755
index 0000000..5d3b8f4
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/SizeButtonPressed.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/StatusBarTile.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/StatusBarTile.png
new file mode 100644
index 0000000..3b5380b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/StatusBarTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ThumbnailsIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ThumbnailsIcon.png
new file mode 100644
index 0000000..40dc5c4
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ThumbnailsIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeft.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeft.png
new file mode 100644
index 0000000..d309e51
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeftPressed.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeftPressed.png
new file mode 100644
index 0000000..0607cfb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonLeftPressed.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRight.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRight.png
new file mode 100644
index 0000000..b752b17
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRightPressed.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRightPressed.png
new file mode 100644
index 0000000..a3a137f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomButtonRightPressed.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomInIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomInIcon.png
new file mode 100755
index 0000000..a236232
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomInIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomOutIcon.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomOutIcon.png
new file mode 100755
index 0000000..7731165
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/ZoomOutIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbar.html b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbar.html
new file mode 100644
index 0000000..4297372
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbar.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css">
+ body {
+ font-family: Lucida Grande;
+ font-size: 11px;
+ }
+ .wrapper {
+ height: 30px;
+ border: 1px solid silver;
+ cursor: default;
+ }
+ table {
+ width: 100%;
+ height: 30px;
+ }
+ td {
+ overflow: visible;
+ }
+ .filename {
+ font-weight: bold;
+ }
+ .toolbarButton, .otherWidget {
+ display: inline;
+ margin-right: 8px;
+ }
+ .toolbarButton {
+ cursor: pointer;
+ }
+ .icon {
+ position: relative;
+ top: -2px;
+ vertical-align: middle;
+ margin-right: 3px;
+ }
+ .sepOuter {
+ position: relative;
+ display: inline-block;
+ width: 2px;
+ height: 2px;
+ margin-right: 4px;
+ overflow: visible;
+ }
+ .sepInner {
+ background-image: url(toolbarSeparator.png);
+ height: 20px;
+ width: 2px;
+ position: absolute;
+ top: -11px;
+ }
+ .zoomButton {
+ overflow: visible;
+ display: inline-block;
+ position: relative;
+ width: 61px;
+ height: 8px;
+ margin-right: 4px;
+ }
+ .zoomInner {
+ display: block;
+ position: absolute;
+ top: -7px;
+ left: 0;
+ width: 61px;
+ height: 8px;
+ cursor: pointer;
+ }
+ .zoomLeft {
+ display: inline-block;
+ background-image: url(ZoomButtonLeft.png);
+ width: 31px;
+ height: 24px;
+ text-align: left;
+ }
+ .zoomRight {
+ display: inline-block;
+ background-image: url(ZoomButtonRight.png);
+ width: 30px;
+ height: 24px;
+ text-align: right;
+ }
+ .zoomOut, .zoomIn {
+ display: inline-block;
+ width: 20px;
+ height: 18px;
+ margin-top: 3px;
+ }
+ .zoomOut {
+ background-image: url(ZoomOutIcon.png);
+ margin-left: 6px;
+ }
+ .zoomIn {
+ background-image: url(ZoomInIcon.png);
+ margin-right: 7px;
+ }
+ .outer {
+ display: inline-block;
+ height: 0;
+ background-color: blue;
+ position: relative;
+ }
+ .inner {
+ display: inline-block;
+ position: absolute;
+ left: 0;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="wrapper">
+ <table cellpadding="0" cellspacing="0">
+ <tr>
+ <td width="33%" valign="middle">
+ <span class="filename">Project.Rnw</span>
+ <div class="outer" style="width: 20px">
+ <div class="inner" style="bottom: -6px; height: 18px; width: 20px">
+ <div class="toolbarButton">
+ <img class="icon" src="FileOptionsIcon.png"/>
+ </div>
+ </div>
+ </div>
+ </td>
+ <td width="33%" valign="middle" align="center">
+ <div class="toolbarButton">
+ <img class="icon" src="PreviousPageIcon.png"/>Previous
+ </div>
+ <div class="sepOuter">
+ <div class="sepInner">
+ </div>
+ </div>
+ <div class="toolbarButton">
+ <img class="icon" src="NextPageIcon.png"/>Next
+ </div>
+ <div class="sepOuter"><div class="sepInner"></div></div>
+ <div class="otherWidget">
+ Page <select><option>1</option></select> of 5
+ </div>
+ </td>
+ <td width="33%" valign="middle" align="right">
+ <div class="zoomButton">
+ <div class="zoomInner">
+ <div class="zoomLeft"><div class="zoomOut"></div></div><div class="zoomRight"><div class="zoomIn"></div></div>
+ </div>
+ </div>
+ <div class="otherWidget">
+ <select><option>100%</option></select>
+ </div>
+ </td>
+ </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbarSeparator.png b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbarSeparator.png
new file mode 100644
index 0000000..6efd66c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/pdfviewer/ui/images/toolbarSeparator.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ProjectMRUList.java b/src/gwt/src/org/rstudio/studio/client/projects/ProjectMRUList.java
new file mode 100644
index 0000000..623b498
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ProjectMRUList.java
@@ -0,0 +1,68 @@
+/*
+ * ProjectMRUList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.projects.events.SwitchToProjectEvent;
+import org.rstudio.studio.client.workbench.MRUList;
+import org.rstudio.studio.client.workbench.WorkbenchListManager;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ProjectMRUList extends MRUList
+{
+ @Inject
+ public ProjectMRUList(Commands commands,
+ WorkbenchListManager listManager,
+ final EventBus eventBus)
+ {
+ super(listManager.getProjectMruList(),
+ new AppCommand[] {
+ commands.projectMru0(),
+ commands.projectMru1(),
+ commands.projectMru2(),
+ commands.projectMru3(),
+ commands.projectMru4(),
+ commands.projectMru5(),
+ commands.projectMru6(),
+ commands.projectMru7(),
+ commands.projectMru8(),
+ commands.projectMru9()
+ },
+ commands.clearRecentProjects(),
+ false,
+ new OperationWithInput<String>()
+ {
+ @Override
+ public void execute(String file)
+ {
+ eventBus.fireEvent(new SwitchToProjectEvent(file));
+ }
+ });
+ }
+
+ @Override
+ protected String transformMruEntryPath(String entryPath)
+ {
+ return FileSystemItem.createFile(entryPath).getParentPathString();
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/Projects.java b/src/gwt/src/org/rstudio/studio/client/projects/Projects.java
new file mode 100644
index 0000000..cada49a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/Projects.java
@@ -0,0 +1,664 @@
+/*
+ * Projects.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.SerializedCommand;
+import org.rstudio.core.client.SerializedCommandQueue;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.ApplicationQuit;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.console.ProcessExitEvent;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.common.vcs.VcsCloneOptions;
+import org.rstudio.studio.client.projects.events.OpenProjectFileEvent;
+import org.rstudio.studio.client.projects.events.OpenProjectFileHandler;
+import org.rstudio.studio.client.projects.events.OpenProjectErrorEvent;
+import org.rstudio.studio.client.projects.events.OpenProjectErrorHandler;
+import org.rstudio.studio.client.projects.events.SwitchToProjectEvent;
+import org.rstudio.studio.client.projects.events.SwitchToProjectHandler;
+import org.rstudio.studio.client.projects.model.NewProjectContext;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.projects.model.ProjectsServerOperations;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.ui.newproject.NewProjectWizard;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesDialog;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+
+ at Singleton
+public class Projects implements OpenProjectFileHandler,
+ SwitchToProjectHandler,
+ OpenProjectErrorHandler
+{
+ public interface Binder extends CommandBinder<Commands, Projects> {}
+
+ @Inject
+ public Projects(GlobalDisplay globalDisplay,
+ final Session session,
+ Provider<ProjectMRUList> pMRUList,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fsContext,
+ ApplicationQuit applicationQuit,
+ ProjectsServerOperations server,
+ GitServerOperations gitServer,
+ EventBus eventBus,
+ Binder binder,
+ final Commands commands,
+ Provider<ProjectPreferencesDialog> pPrefDialog,
+ Provider<UIPrefs> pUIPrefs)
+ {
+ globalDisplay_ = globalDisplay;
+ pMRUList_ = pMRUList;
+ applicationQuit_ = applicationQuit;
+ server_ = server;
+ gitServer_ = gitServer;
+ fileDialogs_ = fileDialogs;
+ fsContext_ = fsContext;
+ session_ = session;
+ pPrefDialog_ = pPrefDialog;
+ pUIPrefs_ = pUIPrefs;
+
+ binder.bind(commands, this);
+
+ eventBus.addHandler(OpenProjectErrorEvent.TYPE, this);
+ eventBus.addHandler(SwitchToProjectEvent.TYPE, this);
+ eventBus.addHandler(OpenProjectFileEvent.TYPE, this);
+
+ eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ SessionInfo sessionInfo = session.getSessionInfo();
+
+ // ensure mru is initialized
+ ProjectMRUList mruList = pMRUList_.get();
+
+ // enable/disable commands
+ String activeProjectFile = sessionInfo.getActiveProjectFile();
+ boolean hasProject = activeProjectFile != null;
+ commands.closeProject().setEnabled(hasProject);
+ commands.projectOptions().setEnabled(hasProject);
+
+ // remove version control commands if necessary
+ if (!sessionInfo.isVcsEnabled())
+ {
+ commands.activateVcs().remove();
+ commands.vcsCommit().remove();
+ commands.vcsShowHistory().remove();
+ commands.vcsPull().remove();
+ commands.vcsPush().remove();
+ commands.vcsCleanup().remove();
+ }
+ else
+ {
+ commands.activateVcs().setMenuLabel(
+ "Show _" + sessionInfo.getVcsName());
+
+ // customize for svn if necessary
+ if (sessionInfo.getVcsName().equals(VCSConstants.SVN_ID))
+ {
+ commands.vcsPush().remove();
+ commands.vcsPull().setButtonLabel("Update");
+ commands.vcsPull().setMenuLabel("_Update");
+ }
+
+ // customize for git if necessary
+ if (sessionInfo.getVcsName().equals(VCSConstants.GIT_ID))
+ {
+ commands.vcsCleanup().remove();
+ }
+ }
+
+ // disable the open project in new window command in web mode
+ if (!Desktop.isDesktop())
+ commands.openProjectInNewWindow().remove();
+
+ // maintain mru
+ if (hasProject)
+ mruList.add(activeProjectFile);
+ }
+ });
+ }
+
+
+ @Handler
+ public void onNewProject()
+ {
+ // first resolve the quit context (potentially saving edited documents
+ // and determining whether to save the R environment on exit)
+ applicationQuit_.prepareForQuit("Save Current Workspace",
+ new ApplicationQuit.QuitContext() {
+ @Override
+ public void onReadyToQuit(final boolean saveChanges)
+ {
+ server_.getNewProjectContext(
+ new SimpleRequestCallback<NewProjectContext>() {
+ @Override
+ public void onResponseReceived(NewProjectContext context)
+ {
+ NewProjectWizard wiz = new NewProjectWizard(
+ session_.getSessionInfo(),
+ pUIPrefs_.get(),
+ new NewProjectInput(
+ FileSystemItem.createDir(
+ pUIPrefs_.get().defaultProjectLocation().getValue()),
+ context
+ ),
+
+ new ProgressOperationWithInput<NewProjectResult>() {
+
+ @Override
+ public void execute(NewProjectResult newProject,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ createNewProject(newProject, saveChanges);
+ }
+ });
+ wiz.showModal();
+ }
+ });
+ }
+ });
+
+
+ }
+
+
+ private void createNewProject(final NewProjectResult newProject,
+ final boolean saveChanges)
+ {
+
+
+ // This gets a little crazy. We have several pieces of asynchronous logic
+ // that each may or may not need to be executed, depending on the type
+ // of project being created and on whether the previous pieces of logic
+ // succeed. Plus we have this ProgressIndicator that needs to be fed
+ // properly.
+
+ final ProgressIndicator indicator = globalDisplay_.getProgressIndicator(
+ "Error Creating Project");
+
+ // Here's the command queue that will hold the various operations.
+ final SerializedCommandQueue createProjectCmds =
+ new SerializedCommandQueue();
+
+ // WARNING: When calling addCommand, BE SURE TO PASS FALSE as the second
+ // argument, to delay running of the commands until they are all
+ // scheduled.
+
+ // First, attempt to update the default project location pref
+ createProjectCmds.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(final Command continuation)
+ {
+ UIPrefs uiPrefs = pUIPrefs_.get();
+
+ // update default project location pref if necessary
+ if ((newProject.getNewDefaultProjectLocation() != null) ||
+ (newProject.getCreateGitRepo() !=
+ uiPrefs.newProjGitInit().getValue()))
+ {
+ indicator.onProgress("Saving defaults...");
+
+ if (newProject.getNewDefaultProjectLocation() != null)
+ {
+ uiPrefs.defaultProjectLocation().setGlobalValue(
+ newProject.getNewDefaultProjectLocation());
+ }
+
+ if (newProject.getCreateGitRepo() !=
+ uiPrefs.newProjGitInit().getValue())
+ {
+ uiPrefs.newProjGitInit().setGlobalValue(
+ newProject.getCreateGitRepo());
+ }
+
+ // call the server -- in all cases continue on with
+ // creating the project (swallow errors updating the pref)
+ server_.setUiPrefs(
+ session_.getSessionInfo().getUiPrefs(),
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ continuation.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ super.onError(error);
+ continuation.execute();
+ }
+ });
+ }
+ else
+ {
+ continuation.execute();
+ }
+ }
+ }, false);
+
+ // Next, if necessary, clone a repo
+ if (newProject.getVcsCloneOptions() != null)
+ {
+ createProjectCmds.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(final Command continuation)
+ {
+ VcsCloneOptions cloneOptions = newProject.getVcsCloneOptions();
+
+ if (cloneOptions.getVcsName().equals((VCSConstants.GIT_ID)))
+ indicator.onProgress("Cloning Git repoistory...");
+ else
+ indicator.onProgress("Checking out SVN repository...");
+
+ gitServer_.vcsClone(
+ cloneOptions,
+ new ServerRequestCallback<ConsoleProcess>() {
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ final ConsoleProgressDialog consoleProgressDialog =
+ new ConsoleProgressDialog(proc, gitServer_);
+ consoleProgressDialog.showModal();
+
+ proc.addProcessExitHandler(new ProcessExitEvent.Handler()
+ {
+ @Override
+ public void onProcessExit(ProcessExitEvent event)
+ {
+ if (event.getExitCode() == 0)
+ {
+ consoleProgressDialog.hide();
+ continuation.execute();
+ }
+ else
+ {
+ indicator.onCompleted();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+ }, false);
+ }
+
+ // Next, create the project file
+ createProjectCmds.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(final Command continuation)
+ {
+ indicator.onProgress("Creating project...");
+
+ server_.createProject(
+ newProject.getProjectFile(),
+ newProject.getNewPackageOptions(),
+ newProject.getNewShinyAppOptions(),
+ new VoidServerRequestCallback(indicator)
+ {
+ @Override
+ public void onSuccess()
+ {
+ if (!newProject.getOpenInNewWindow())
+ {
+ applicationQuit_.performQuit(
+ saveChanges,
+ newProject.getProjectFile());
+ }
+
+ continuation.execute();
+ }
+ });
+ }
+ }, false);
+
+ // Next, initialize a git repo if requested
+ if (newProject.getCreateGitRepo())
+ {
+ createProjectCmds.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(final Command continuation)
+ {
+ indicator.onProgress("Initializing git repository...");
+
+ String projDir = FileSystemItem.createFile(
+ newProject.getProjectFile()).getParentPathString();
+
+ gitServer_.gitInitRepo(
+ projDir,
+ new VoidServerRequestCallback(indicator)
+ {
+ @Override
+ public void onSuccess()
+ {
+ continuation.execute();
+ }
+
+ @Override
+ public void onFailure()
+ {
+ continuation.execute();
+ }
+ });
+ }
+ }, false);
+ }
+
+ if (newProject.getOpenInNewWindow())
+ {
+ createProjectCmds.addCommand(new SerializedCommand() {
+
+ @Override
+ public void onExecute(Command continuation)
+ {
+ Desktop.getFrame().openProjectInNewWindow(
+ newProject.getProjectFile());
+ continuation.execute();
+
+ }
+ });
+ }
+
+ // If we get here, dismiss the progress indicator
+ createProjectCmds.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(Command continuation)
+ {
+ indicator.onCompleted();
+ continuation.execute();
+ }
+ }, false);
+
+ // Now set it all in motion!
+ createProjectCmds.run();
+ }
+
+
+ @Handler
+ public void onOpenProject()
+ {
+ // first resolve the quit context (potentially saving edited documents
+ // and determining whether to save the R environment on exit)
+ applicationQuit_.prepareForQuit("Switch Projects",
+ new ApplicationQuit.QuitContext() {
+ public void onReadyToQuit(final boolean saveChanges)
+ {
+ showOpenProjectDialog(
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ @Override
+ public void execute(final FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+
+ if (input == null)
+ return;
+
+ // perform quit
+ applicationQuit_.performQuit(saveChanges, input.getPath());
+ }
+ });
+
+ }
+ });
+ }
+
+ @Handler
+ public void onOpenProjectInNewWindow()
+ {
+ showOpenProjectDialog(
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ @Override
+ public void execute(final FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+
+ if (input == null)
+ return;
+
+ // call the desktop to open the project (since it is
+ // a conventional foreground gui application it has
+ // less chance of running afowl of desktop app creation
+ // & activation restrictions)
+ Desktop.getFrame().openProjectInNewWindow(input.getPath());
+ }
+ });
+ }
+
+
+
+ @Handler
+ public void onCloseProject()
+ {
+ // first resolve the quit context (potentially saving edited documents
+ // and determining whether to save the R environment on exit)
+ applicationQuit_.prepareForQuit("Close Project",
+ new ApplicationQuit.QuitContext() {
+ public void onReadyToQuit(final boolean saveChanges)
+ {
+ applicationQuit_.performQuit(saveChanges, NONE);
+ }});
+ }
+
+ @Handler
+ public void onProjectOptions()
+ {
+ showProjectOptions(ProjectPreferencesDialog.GENERAL);
+ }
+
+ @Handler
+ public void onProjectSweaveOptions()
+ {
+ showProjectOptions(ProjectPreferencesDialog.SWEAVE);
+ }
+
+ @Handler
+ public void onBuildToolsProjectSetup()
+ {
+ // check whether there is a project active
+ if (!hasActiveProject())
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.INFO,
+ "No Active Project",
+ "Build tools can only be configured from within an " +
+ "RStudio project.");
+
+ }
+ else
+ {
+ showProjectOptions(ProjectPreferencesDialog.BUILD);
+ }
+ }
+
+
+ @Handler
+ public void onVersionControlProjectSetup()
+ {
+ // check whether there is a project active
+ if (!hasActiveProject())
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.INFO,
+ "No Active Project",
+ "Version control features can only be accessed from within an " +
+ "RStudio project. Note that if you have an existing directory " +
+ "under version control you can associate an RStudio project " +
+ "with that directory using the New Project dialog.");
+
+ }
+ else
+ {
+ showProjectOptions(ProjectPreferencesDialog.VCS);
+ }
+ }
+
+ private void showProjectOptions(final int initialPane)
+ {
+ final ProgressIndicator indicator = globalDisplay_.getProgressIndicator(
+ "Error Reading Options");
+ indicator.onProgress("Reading options...");
+
+ server_.readProjectOptions(new SimpleRequestCallback<RProjectOptions>() {
+
+ @Override
+ public void onResponseReceived(RProjectOptions options)
+ {
+ indicator.onCompleted();
+
+ ProjectPreferencesDialog dlg = pPrefDialog_.get();
+ dlg.initialize(options);
+ dlg.activatePane(initialPane);
+ dlg.showModal();
+ }});
+ }
+
+ @Override
+ public void onOpenProjectFile(final OpenProjectFileEvent event)
+ {
+ // project options for current project
+ FileSystemItem projFile = event.getFile();
+ if (projFile.getPath().equals(
+ session_.getSessionInfo().getActiveProjectFile()))
+ {
+ onProjectOptions();
+ return;
+ }
+
+ // prompt to confirm
+ String projectPath = projFile.getParentPathString();
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+ "Confirm Open Project",
+ "Do you want to open the project " + projectPath + "?",
+ new Operation()
+ {
+ public void execute()
+ {
+ switchToProject(event.getFile().getPath());
+ }
+ },
+ true);
+ }
+
+
+ @Override
+ public void onSwitchToProject(final SwitchToProjectEvent event)
+ {
+ switchToProject(event.getProject());
+ }
+
+ @Override
+ public void onOpenProjectError(OpenProjectErrorEvent event)
+ {
+ // show error dialog
+ String msg = "Project '" + event.getProject() + "' " +
+ "could not be opened: " + event.getMessage();
+ globalDisplay_.showErrorMessage("Error Opening Project", msg);
+
+ // remove from mru list
+ pMRUList_.get().remove(event.getProject());
+ }
+
+ private boolean hasActiveProject()
+ {
+ return session_.getSessionInfo().getActiveProjectFile() != null;
+ }
+
+
+ private void switchToProject(final String projectFilePath)
+ {
+ applicationQuit_.prepareForQuit("Switch Projects",
+ new ApplicationQuit.QuitContext() {
+ public void onReadyToQuit(final boolean saveChanges)
+ {
+ applicationQuit_.performQuit(saveChanges, projectFilePath);
+ }});
+ }
+
+ private void showOpenProjectDialog(
+ ProgressOperationWithInput<FileSystemItem> onCompleted)
+ {
+ // choose project file
+ fileDialogs_.openFile(
+ "Open Project",
+ fsContext_,
+ FileSystemItem.createDir(
+ pUIPrefs_.get().defaultProjectLocation().getValue()),
+ "R Projects (*.Rproj)",
+ onCompleted);
+ }
+
+ private final Provider<ProjectMRUList> pMRUList_;
+ private final ApplicationQuit applicationQuit_;
+ private final ProjectsServerOperations server_;
+ private final GitServerOperations gitServer_;
+ private final FileDialogs fileDialogs_;
+ private final RemoteFileSystemContext fsContext_;
+ private final GlobalDisplay globalDisplay_;
+ private final Session session_;
+ private final Provider<ProjectPreferencesDialog> pPrefDialog_;
+ private final Provider<UIPrefs> pUIPrefs_;
+
+ public static final String NONE = "none";
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorEvent.java b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorEvent.java
new file mode 100644
index 0000000..d15441f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorEvent.java
@@ -0,0 +1,54 @@
+/*
+ * OpenProjectErrorEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import org.rstudio.studio.client.projects.model.OpenProjectError;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class OpenProjectErrorEvent extends GwtEvent<OpenProjectErrorHandler>
+{
+ public static final GwtEvent.Type<OpenProjectErrorHandler> TYPE =
+ new GwtEvent.Type<OpenProjectErrorHandler>();
+
+ public OpenProjectErrorEvent(OpenProjectError error)
+ {
+ error_ = error;
+ }
+
+ public String getProject()
+ {
+ return error_.getProject();
+ }
+
+ public String getMessage()
+ {
+ return error_.getMessage();
+ }
+
+ @Override
+ protected void dispatch(OpenProjectErrorHandler handler)
+ {
+ handler.onOpenProjectError(this);
+ }
+
+ @Override
+ public GwtEvent.Type<OpenProjectErrorHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final OpenProjectError error_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorHandler.java b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorHandler.java
new file mode 100644
index 0000000..26e534f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectErrorHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenProjectErrorHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenProjectErrorHandler extends EventHandler
+{
+ void onOpenProjectError(OpenProjectErrorEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileEvent.java b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileEvent.java
new file mode 100644
index 0000000..46cb3fe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileEvent.java
@@ -0,0 +1,48 @@
+/*
+ * OpenProjectFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class OpenProjectFileEvent extends GwtEvent<OpenProjectFileHandler>
+{
+ public static final GwtEvent.Type<OpenProjectFileHandler> TYPE =
+ new GwtEvent.Type<OpenProjectFileHandler>();
+
+ public OpenProjectFileEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ protected void dispatch(OpenProjectFileHandler handler)
+ {
+ handler.onOpenProjectFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<OpenProjectFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileSystemItem file_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileHandler.java b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileHandler.java
new file mode 100644
index 0000000..f16e919
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/OpenProjectFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * OpenProjectFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface OpenProjectFileHandler extends EventHandler
+{
+ void onOpenProjectFile(OpenProjectFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectEvent.java b/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectEvent.java
new file mode 100644
index 0000000..ede2ada
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectEvent.java
@@ -0,0 +1,47 @@
+/*
+ * SwitchToProjectEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SwitchToProjectEvent extends GwtEvent<SwitchToProjectHandler>
+{
+ public static final GwtEvent.Type<SwitchToProjectHandler> TYPE =
+ new GwtEvent.Type<SwitchToProjectHandler>();
+
+ public SwitchToProjectEvent(String project)
+ {
+ project_ = project;
+ }
+
+ public String getProject()
+ {
+ return project_;
+ }
+
+ @Override
+ protected void dispatch(SwitchToProjectHandler handler)
+ {
+ handler.onSwitchToProject(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SwitchToProjectHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String project_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectHandler.java b/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectHandler.java
new file mode 100644
index 0000000..8a19e7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/events/SwitchToProjectHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SwitchToProjectHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SwitchToProjectHandler extends EventHandler
+{
+ void onSwitchToProject(SwitchToProjectEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/NewPackageOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/NewPackageOptions.java
new file mode 100644
index 0000000..e3fffdf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/NewPackageOptions.java
@@ -0,0 +1,42 @@
+/*
+ * NewPackageOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class NewPackageOptions extends JavaScriptObject
+{
+ protected NewPackageOptions()
+ {
+ }
+
+ public native final static NewPackageOptions create(boolean usingRcpp,
+ JsArrayString codeFiles)
+ /*-{
+ var options = new Object();
+ options.using_rcpp = usingRcpp;
+ options.code_files = codeFiles;
+ return options;
+ }-*/;
+
+ public native final boolean getUsingRcpp() /*-{
+ return this.using_rcpp;
+ }-*/;
+
+ public native final JsArrayString getCodeFiles() /*-{
+ return this.code_files;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectContext.java b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectContext.java
new file mode 100644
index 0000000..9d2c939
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectContext.java
@@ -0,0 +1,28 @@
+/*
+ * NewProjectContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class NewProjectContext extends JavaScriptObject
+{
+ protected NewProjectContext()
+ {
+ }
+
+ public native final boolean isRcppAvailable() /*-{
+ return this.rcpp_available;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectInput.java b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectInput.java
new file mode 100644
index 0000000..84db0c5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectInput.java
@@ -0,0 +1,40 @@
+/*
+ * NewProjectInput.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class NewProjectInput
+{
+ public NewProjectInput(FileSystemItem defaultNewProjectLocation,
+ NewProjectContext context)
+ {
+ defaultNewProjectLocation_ = defaultNewProjectLocation;
+ context_ = context;
+ }
+
+ public FileSystemItem getDefaultNewProjectLocation()
+ {
+ return defaultNewProjectLocation_;
+ }
+
+ public NewProjectContext getContext()
+ {
+ return context_;
+ }
+
+ private final FileSystemItem defaultNewProjectLocation_;
+ private final NewProjectContext context_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectResult.java b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectResult.java
new file mode 100644
index 0000000..3e25b47
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/NewProjectResult.java
@@ -0,0 +1,84 @@
+/*
+ * NewProjectResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import org.rstudio.studio.client.common.vcs.VcsCloneOptions;
+
+public class NewProjectResult
+{
+ public NewProjectResult(String projectFile,
+ boolean createGitRepo,
+ String newDefaultProjectLocation,
+ VcsCloneOptions vcsCloneOptions,
+ NewPackageOptions newPackageOptions,
+ NewShinyAppOptions newShinyAppOptions)
+ {
+ projectFile_ = projectFile;
+ createGitRepo_ = createGitRepo;
+ openInNewWindow_ = false;
+ newDefaultProjectLocation_ = newDefaultProjectLocation;
+ vcsCloneOptions_ = vcsCloneOptions;
+ newPackageOptions_ = newPackageOptions;
+ newShinyAppOptions_ = newShinyAppOptions;
+ }
+
+ public String getProjectFile()
+ {
+ return projectFile_;
+ }
+
+ public boolean getCreateGitRepo()
+ {
+ return createGitRepo_;
+ }
+
+ public boolean getOpenInNewWindow()
+ {
+ return openInNewWindow_;
+ }
+
+ public void setOpenInNewWindow(boolean openInNewWindow)
+ {
+ openInNewWindow_ = openInNewWindow;
+ }
+
+ public String getNewDefaultProjectLocation()
+ {
+ return newDefaultProjectLocation_;
+ }
+
+ public VcsCloneOptions getVcsCloneOptions()
+ {
+ return vcsCloneOptions_;
+ }
+
+ public NewPackageOptions getNewPackageOptions()
+ {
+ return newPackageOptions_;
+ }
+
+ public NewShinyAppOptions getNewShinyAppOptions()
+ {
+ return newShinyAppOptions_;
+ }
+
+ private final boolean createGitRepo_;
+ private boolean openInNewWindow_;
+ private final String projectFile_;
+ private final String newDefaultProjectLocation_;
+ private final VcsCloneOptions vcsCloneOptions_;
+ private final NewPackageOptions newPackageOptions_;
+ private final NewShinyAppOptions newShinyAppOptions_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/NewShinyAppOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/NewShinyAppOptions.java
new file mode 100644
index 0000000..2e330f6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/NewShinyAppOptions.java
@@ -0,0 +1,30 @@
+/*
+ * NewShinyAppOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class NewShinyAppOptions extends JavaScriptObject
+{
+ protected NewShinyAppOptions()
+ {
+ }
+
+ public native final static NewShinyAppOptions create()
+ /*-{
+ var options = new Object();
+ return options;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/OpenProjectError.java b/src/gwt/src/org/rstudio/studio/client/projects/model/OpenProjectError.java
new file mode 100644
index 0000000..3e5a001
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/OpenProjectError.java
@@ -0,0 +1,38 @@
+/*
+ * OpenProjectError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class OpenProjectError extends JavaScriptObject
+{
+
+ protected OpenProjectError()
+ {
+
+ }
+
+ public native final String getProject() /*-{
+ return this.project;
+ }-*/;
+
+
+ public native final String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/ProjectsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/projects/model/ProjectsServerOperations.java
new file mode 100644
index 0000000..04e95ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/ProjectsServerOperations.java
@@ -0,0 +1,39 @@
+/*
+ * ProjectsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.prefs.model.PrefsServerOperations;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+public interface ProjectsServerOperations extends PrefsServerOperations,
+ SourceServerOperations
+{
+ void getNewProjectContext(ServerRequestCallback<NewProjectContext> callback);
+
+ void createProject(String projectFile,
+ NewPackageOptions newPackageOptions,
+ NewShinyAppOptions newShinyAppOptions,
+ ServerRequestCallback<Void> callback);
+
+ void readProjectOptions(ServerRequestCallback<RProjectOptions> callback);
+
+ void writeProjectOptions(RProjectOptions options,
+ ServerRequestCallback<Void> callback);
+
+ void writeProjectVcsOptions(RProjectVcsOptions options,
+ ServerRequestCallback<Void> callback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectAutoRoxygenizeOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectAutoRoxygenizeOptions.java
new file mode 100644
index 0000000..5fba9b7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectAutoRoxygenizeOptions.java
@@ -0,0 +1,59 @@
+/*
+ * RProjectAutoRoxygenizeOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectAutoRoxygenizeOptions extends JavaScriptObject
+{
+ protected RProjectAutoRoxygenizeOptions()
+ {
+ }
+
+ public native static final RProjectAutoRoxygenizeOptions create(
+ boolean runOnCheck,
+ boolean runOnBuilds,
+ boolean runOnBuildAndReload) /*-{
+ var options = new Object();
+ options.run_on_check = runOnCheck;
+ options.run_on_package_builds = runOnBuilds;
+ options.run_on_build_and_reload = runOnBuildAndReload;
+ return options;
+ }-*/;
+
+ public native final boolean getRunOnCheck() /*-{
+ return this.run_on_check;
+ }-*/;
+
+ public native final void setRunOnCheck(boolean runOnCheck) /*-{
+ this.run_on_check = runOnCheck;
+ }-*/;
+
+ public native final boolean getRunOnPackageBuilds() /*-{
+ return this.run_on_package_builds;
+ }-*/;
+
+ public native final void setRunOnPackageBuilds(boolean runOnBuilds) /*-{
+ this.run_on_package_builds = runOnBuilds;
+ }-*/;
+
+ public native final boolean getRunOnBuildAndReload() /*-{
+ return this.run_on_build_and_reload;
+ }-*/;
+
+ public native final void setRunOnBuildAndReload(boolean runOnBuildAndReload) /*-{
+ this.run_on_build_and_reload = runOnBuildAndReload;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildContext.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildContext.java
new file mode 100644
index 0000000..dc9b3ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildContext.java
@@ -0,0 +1,32 @@
+/*
+ * RProjectBuildContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectBuildContext extends JavaScriptObject
+{
+ protected RProjectBuildContext()
+ {
+ }
+
+ public native final boolean isRoxygen2Installed() /*-{
+ return this.roxygen2_installed;
+ }-*/;
+
+ public native final boolean isDevtoolsInstalled() /*-{
+ return this.devtools_installed;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildOptions.java
new file mode 100644
index 0000000..79ea488
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectBuildOptions.java
@@ -0,0 +1,46 @@
+/*
+ * RProjectBuildOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectBuildOptions extends JavaScriptObject
+{
+ protected RProjectBuildOptions()
+ {
+ }
+
+ public native static final RProjectBuildOptions createEmpty() /*-{
+ var options = new Object();
+ return options;
+ }-*/;
+
+ public native final String getMakefileArgs() /*-{
+ return this.makefile_args;
+ }-*/;
+
+ public native final void setMakefileArgs(String makefileArgs) /*-{
+ this.makefile_args = makefileArgs;
+ }-*/;
+
+ public native final RProjectAutoRoxygenizeOptions getAutoRogyginizeOptions() /*-{
+ return this.auto_roxygenize_options;
+ }-*/;
+
+ public native final void setAutoRoxyginizeOptions(
+ RProjectAutoRoxygenizeOptions options) /*-{
+ this.auto_roxygenize_options = options;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java
new file mode 100644
index 0000000..cd3550f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java
@@ -0,0 +1,285 @@
+/*
+ * RProjectConfig.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.StringUtil;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectConfig extends JavaScriptObject
+{
+ public static final int DEFAULT_VALUE = 0;
+ public static final int YES_VALUE = 1;
+ public static final int NO_VALUE = 2;
+ public static final int ASK_VALUE = 3;
+
+ protected RProjectConfig()
+ {
+ }
+
+ public native static final RProjectConfig createEmpty() /*-{
+ var config = new Object();
+ config.version = 1.0;
+ return config;
+ }-*/;
+
+ public native final double getVersion() /*-{
+ return this.version;
+ }-*/;
+
+ public native final int getRestoreWorkspace() /*-{
+ return this.restore_workspace;
+ }-*/;
+
+ public native final void setRestoreWorkspace(int restoreWorkspace) /*-{
+ this.restore_workspace = restoreWorkspace;
+ }-*/;
+
+ public native final int getSaveWorkspace() /*-{
+ return this.save_workspace;
+ }-*/;
+
+ public native final void setSaveWorkspace(int saveWorkspace) /*-{
+ this.save_workspace = saveWorkspace;
+ }-*/;
+
+ public native final int getAlwaysSaveHistory() /*-{
+ return this.always_save_history;
+ }-*/;
+
+ public native final void setAlwaysSaveHistory(int alwaysSaveHistory) /*-{
+ this.always_save_history = alwaysSaveHistory;
+ }-*/;
+
+ public native final boolean getEnableCodeIndexing() /*-{
+ return this.enable_code_indexing;
+ }-*/;
+
+ public native final void setEnableCodeIndexing(boolean enableCodeIndexing) /*-{
+ this.enable_code_indexing = enableCodeIndexing;
+ }-*/;
+
+ public native final boolean getUseSpacesForTab() /*-{
+ return this.use_spaces_for_tab;
+ }-*/;
+
+ public native final void setUseSpacesForTab(boolean useSpacesForTab) /*-{
+ this.use_spaces_for_tab = useSpacesForTab;
+ }-*/;
+
+ public native final int getNumSpacesForTab() /*-{
+ return this.num_spaces_for_tab;
+ }-*/;
+
+ public native final void setNumSpacesForTab(int numSpacesForTab) /*-{
+ this.num_spaces_for_tab = numSpacesForTab;
+ }-*/;
+
+ public native final boolean getAutoAppendNewline() /*-{
+ return this.auto_append_newline;
+ }-*/;
+
+ public native final void setAutoAppendNewline(boolean autoAppendNewline) /*-{
+ this.auto_append_newline = autoAppendNewline;
+ }-*/;
+
+ public native final boolean getStripTrailingWhitespace() /*-{
+ return this.strip_trailing_whitespace;
+ }-*/;
+
+ public native final void setStripTrailingWhitespace(boolean stripTrailingWhitespace) /*-{
+ this.strip_trailing_whitespace = stripTrailingWhitespace;
+ }-*/;
+
+ public native final String getEncoding() /*-{
+ return this.default_encoding;
+ }-*/;
+
+ public native final void setEncoding(String defaultEncoding) /*-{
+ this.default_encoding = defaultEncoding;
+ }-*/;
+
+ public native final String getDefaultSweaveEngine() /*-{
+ return this.default_sweave_engine;
+ }-*/;
+
+ public native final void setDefaultSweaveEngine(String defaultSweaveEngine) /*-{
+ this.default_sweave_engine = defaultSweaveEngine;
+ }-*/;
+
+ public native final String getDefaultLatexProgram() /*-{
+ return this.default_latex_program;
+ }-*/;
+
+ public native final void setDefaultLatexProgram(String defaultLatexProgram) /*-{
+ this.default_latex_program = defaultLatexProgram;
+ }-*/;
+
+ public native final String getRootDocument() /*-{
+ return this.root_document;
+ }-*/;
+
+ public native final void setRootDocument(String rootDocument) /*-{
+ this.root_document = rootDocument;
+ }-*/;
+
+ public static final String BUILD_TYPE_NONE = "None";
+ public static final String BUILD_TYPE_PACKAGE = "Package";
+ public static final String BUILD_TYPE_MAKEFILE = "Makefile";
+ public static final String BUILD_TYPE_CUSTOM = "Custom";
+
+ public native final String getBuildType() /*-{
+ return this.build_type;
+ }-*/;
+
+ public native final void setBuildType(String buildType) /*-{
+ this.build_type = buildType;
+ }-*/;
+
+ public native final boolean getPackageUseDevtools() /*-{
+ return this.package_use_devtools;
+ }-*/;
+
+ public native final void setPackageUseDevtools(boolean useDevtools) /*-{
+ this.package_use_devtools = useDevtools;
+ }-*/;
+
+ public native final String getPackagePath() /*-{
+ return this.package_path;
+ }-*/;
+
+ public native final void setPackagePath(String packagePath) /*-{
+ this.package_path = packagePath;
+ }-*/;
+
+ public native final String getPackageInstallArgs() /*-{
+ return this.package_install_args;
+ }-*/;
+
+ public native final void setPackageInstallArgs(String installArgs) /*-{
+ this.package_install_args = installArgs;
+ }-*/;
+
+ public native final String getPackageBuildArgs() /*-{
+ return this.package_build_args;
+ }-*/;
+
+ public native final void setPackageBuildArgs(String buildArgs) /*-{
+ this.package_build_args = buildArgs;
+ }-*/;
+
+
+ public native final String getPackageBuildBinaryArgs() /*-{
+ return this.package_build_binary_args;
+ }-*/;
+
+ public native final void setPackageBuildBinaryArgs(String buildArgs) /*-{
+ this.package_build_binary_args = buildArgs;
+ }-*/;
+
+ public native final String getPackageCheckArgs() /*-{
+ return this.package_check_args;
+ }-*/;
+
+ public native final void setPackageCheckArgs(String checkArgs) /*-{
+ this.package_check_args = checkArgs;
+ }-*/;
+
+
+ public final boolean hasPackageRoxygenize()
+ {
+ return !StringUtil.isNullOrEmpty(getPackageRoxygenizeNative());
+ }
+
+ public final boolean getPackageRoxygenzieRd()
+ {
+ return getPackageRoxygenize(ROXYGENIZE_RD);
+ }
+
+ public final boolean getPackageRoxygenizeNamespace()
+ {
+ return getPackageRoxygenize(ROXYGENIZE_NAMESPACE);
+ }
+
+ public final boolean getPackageRoxygenizeCollate()
+ {
+ return getPackageRoxygenize(ROXYGENIZE_COLLATE);
+ }
+
+ public final void setPackageRoxygenize(boolean rd,
+ boolean collate,
+ boolean namespace)
+ {
+ ArrayList<String> roclets = new ArrayList<String>();
+ if (rd)
+ roclets.add(ROXYGENIZE_RD);
+ if (collate)
+ roclets.add(ROXYGENIZE_COLLATE);
+ if (namespace)
+ roclets.add(ROXYGENIZE_NAMESPACE);
+
+ String roxygenize = StringUtil.join(roclets, ROXYGENIZE_DELIM);
+ setPackageRoxygenizeNative(roxygenize);
+ }
+
+ private static final String ROXYGENIZE_RD = "rd";
+ private static final String ROXYGENIZE_COLLATE = "collate";
+ private static final String ROXYGENIZE_NAMESPACE = "namespace";
+ private static final String ROXYGENIZE_DELIM = ",";
+
+ private final boolean getPackageRoxygenize(String roclet)
+ {
+ String[] roclets = getPackageRoxygenizeNative().split(ROXYGENIZE_DELIM);
+ for (int i=0; i<roclets.length; i++)
+ if (roclets[i].equals(roclet))
+ return true;
+
+ return false;
+ }
+
+ private native final String getPackageRoxygenizeNative() /*-{
+ return this.package_roxygenize;
+ }-*/;
+
+ private native final void setPackageRoxygenizeNative(String roxygenize) /*-{
+ this.package_roxygenize = roxygenize;
+ }-*/;
+
+ public native final String getMakefilePath() /*-{
+ return this.makefile_path;
+ }-*/;
+
+ public native final void setMakefilePath(String makefilePath) /*-{
+ this.makefile_path = makefilePath;
+ }-*/;
+
+ public native final String getCustomScriptPath() /*-{
+ return this.custom_script_path;
+ }-*/;
+
+ public native final void setCustomScriptPath(String customScriptPath) /*-{
+ this.custom_script_path = customScriptPath;
+ }-*/;
+
+ public native final String getTutorialPath() /*-{
+ return this.tutorial_path;
+ }-*/;
+
+ public native final void setTutorialPath(String tutorialPath) /*-{
+ this.tutorial_path = tutorialPath;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectOptions.java
new file mode 100644
index 0000000..08d35ca
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectOptions.java
@@ -0,0 +1,63 @@
+/*
+ * RProjectOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectOptions extends JavaScriptObject
+{
+ protected RProjectOptions()
+ {
+ }
+
+ public static final RProjectOptions createEmpty()
+ {
+ return create(RProjectConfig.createEmpty(),
+ RProjectVcsOptions.createEmpty(),
+ RProjectBuildOptions.createEmpty());
+ }
+
+ public native static final RProjectOptions create(
+ RProjectConfig config,
+ RProjectVcsOptions vcsOptions,
+ RProjectBuildOptions buildOptions) /*-{
+ var options = new Object();
+ options.config = config;
+ options.vcs_options = vcsOptions;
+ options.vcs_options_default = new Object();
+ options.build_options = buildOptions;
+ return options;
+ }-*/;
+
+ public native final RProjectConfig getConfig() /*-{
+ return this.config;
+ }-*/;
+
+ public native final RProjectVcsOptions getVcsOptions() /*-{
+ return this.vcs_options;
+ }-*/;
+
+ public native final RProjectBuildOptions getBuildOptions() /*-{
+ return this.build_options;
+ }-*/;
+
+ public native final RProjectVcsContext getVcsContext() /*-{
+ return this.vcs_context;
+ }-*/;
+
+ public native final RProjectBuildContext getBuildContext() /*-{
+ return this.build_context;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsContext.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsContext.java
new file mode 100644
index 0000000..b3d8dcb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsContext.java
@@ -0,0 +1,41 @@
+/*
+ * RProjectVcsContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class RProjectVcsContext extends JavaScriptObject
+{
+ protected RProjectVcsContext()
+ {
+ }
+
+ public native final String getDetectedVcs() /*-{
+ return this.detected_vcs;
+ }-*/;
+
+ public native final JsArrayString getApplicableVcs() /*-{
+ return this.applicable_vcs;
+ }-*/;
+
+ public native final String getSvnRepositoryRoot() /*-{
+ return this.svn_repository_root;
+ }-*/;
+
+ public native final String getGitRemoteOriginUrl() /*-{
+ return this.git_remote_origin_url;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsOptions.java
new file mode 100644
index 0000000..6ed01dc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectVcsOptions.java
@@ -0,0 +1,37 @@
+/*
+ * RProjectVcsOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RProjectVcsOptions extends JavaScriptObject
+{
+ protected RProjectVcsOptions()
+ {
+ }
+
+ public native static final RProjectVcsOptions createEmpty() /*-{
+ var options = new Object();
+ return options;
+ }-*/;
+
+ public native final String getActiveVcsOverride() /*-{
+ return this.active_vcs_override;
+ }-*/;
+
+ public native final void setActiveVcsOverride(String override) /*-{
+ this.active_vcs_override = override;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/CodeFilesList.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/CodeFilesList.java
new file mode 100644
index 0000000..e920598
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/CodeFilesList.java
@@ -0,0 +1,142 @@
+/*
+ * CodeFilesList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+
+
+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.inject.Inject;
+
+public class CodeFilesList extends Composite
+{
+ public CodeFilesList()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ VerticalPanel panel = new VerticalPanel();
+ panel.addStyleName(RES.styles().wizardMainColumn());
+
+ HorizontalPanel labelPanel = new HorizontalPanel();
+ Label label = new Label("Create package based on source files:");
+ label.addStyleName(RES.styles().wizardTextEntryLabel());
+ labelPanel.add(label);
+ panel.add(labelPanel);
+
+ HorizontalPanel dictionariesPanel = new HorizontalPanel();
+ listBox_ = new ListBox(false);
+ listBox_.addStyleName(RES.styles().codeFilesListBox());
+ listBox_.getElement().<SelectElement>cast().setSize(3);
+ dictionariesPanel.add(listBox_);
+
+ VerticalPanel buttonPanel = new VerticalPanel();
+ SmallButton buttonAdd = createButton("Add...");
+ buttonAdd.addClickHandler(addButtonClicked_);
+ buttonPanel.add(buttonAdd);
+ SmallButton buttonRemove = createButton("Remove");
+ buttonRemove.addClickHandler(removeButtonClicked_);
+ buttonPanel.add(buttonRemove);
+ dictionariesPanel.add(buttonPanel);
+
+ panel.add(dictionariesPanel);
+
+ initWidget(panel);
+ }
+
+ @Inject
+ void initialize(FileDialogs fileDialogs,
+ RemoteFileSystemContext fileSystemContext)
+ {
+ fileDialogs_ = fileDialogs;
+ fileSystemContext_ = fileSystemContext;
+ }
+
+ public ArrayList<String> getCodeFiles()
+ {
+ ArrayList<String> codeFiles = new ArrayList<String>();
+ for (int i=0; i<listBox_.getItemCount(); i++)
+ codeFiles.add(listBox_.getItemText(i));
+ return codeFiles;
+ }
+
+
+
+ private ClickHandler addButtonClicked_ = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.openFile(
+ "Add Source File",
+ fileSystemContext_,
+ FileSystemItem.home(),
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ @Override
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ if (input == null)
+ return;
+
+ listBox_.addItem(input.getPath());
+ }
+
+ });
+ }
+ };
+
+
+ private ClickHandler removeButtonClicked_ = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ // get selected index
+ int index = listBox_.getSelectedIndex();
+ if (index != -1)
+ listBox_.removeItem(index);
+ }
+ };
+
+
+ private SmallButton createButton(String caption)
+ {
+ SmallButton button = new SmallButton(caption);
+ button.addStyleName(RES.styles().codeFilesListButton());
+ button.fillWidth();
+ return button;
+ }
+
+ private final ListBox listBox_;
+ private FileDialogs fileDialogs_;
+ private RemoteFileSystemContext fileSystemContext_;
+
+ static final NewProjectResources RES = NewProjectResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/ExistingDirectoryPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/ExistingDirectoryPage.java
new file mode 100644
index 0000000..ae75b68
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/ExistingDirectoryPage.java
@@ -0,0 +1,91 @@
+/*
+ * ExistingDirectoryPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+
+
+
+public class ExistingDirectoryPage extends NewProjectWizardPage
+{
+ public ExistingDirectoryPage()
+ {
+ super("Existing Directory",
+ "Associate a project with an existing working directory",
+ "Create Project from Existing Directory",
+ NewProjectResources.INSTANCE.existingDirectoryIcon(),
+ NewProjectResources.INSTANCE.existingDirectoryIconLarge());
+
+
+ }
+
+ @Override
+ protected void onAddWidgets()
+ {
+
+ existingProjectDir_ = new DirectoryChooserTextBox(
+ "Project working directory:", null);
+
+ addWidget(existingProjectDir_);
+
+ }
+
+
+ @Override
+ protected NewProjectResult collectInput()
+ {
+ String dir = existingProjectDir_.getText();
+ if (dir.length() > 0)
+ {
+ return new NewProjectResult(
+ projFileFromDir(dir), false, null, null, null, null);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean validate(NewProjectResult input)
+ {
+ if (input == null)
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.WARNING,
+ "Error",
+ "You must specify an existing working directory to " +
+ "create the new project within.");
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+
+ }
+
+ @Override
+ public void focus()
+ {
+ existingProjectDir_.focusButton();
+ }
+
+
+ private DirectoryChooserTextBox existingProjectDir_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/GitPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/GitPage.java
new file mode 100644
index 0000000..3ad5d7d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/GitPage.java
@@ -0,0 +1,66 @@
+/*
+ * GitPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+
+public class GitPage extends VersionControlPage
+{
+ public GitPage()
+ {
+ super("Git",
+ "Clone a project from a Git repository",
+ "Clone Git Repository",
+ NewProjectResources.INSTANCE.gitIcon(),
+ NewProjectResources.INSTANCE.gitIconLarge());
+ }
+
+ @Override
+ protected String getVcsId()
+ {
+ return VCSConstants.GIT_ID;
+ }
+
+ @Override
+ protected String guessRepoDir(String url)
+ {
+ /*
+ * Strip trailing spaces, slashes and /.git
+ */
+ while (url.endsWith("/") || url.endsWith(" ") || url.endsWith("\t"))
+ url = url.substring(0, url.length() - 1);
+ if (url.endsWith("/.git"))
+ {
+ url = url.substring(0, url.length() - 5);
+ while (url.endsWith("/"))
+ url = url.substring(0, url.length() - 1);
+ }
+
+ /*
+ * Find last component, but be prepared that repo could have
+ * the form "remote.example.com:foo.git", i.e. no slash
+ * in the directory part.
+ */
+ url = url.replaceFirst(".*[:/]", ""); // greedy
+
+ /*
+ * Strip .{bundle,git}.
+ */
+ url = url.replaceAll(".(bundle|git)$", "");
+ url = url.replaceAll("[\u0000-\u0020]+", " ");
+ url = url.trim();
+ return url;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryNavigationPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryNavigationPage.java
new file mode 100644
index 0000000..c82a092
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryNavigationPage.java
@@ -0,0 +1,53 @@
+/*
+ * NewDirectoryNavigationPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.widget.WizardNavigationPage;
+import org.rstudio.core.client.widget.WizardPage;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+public class NewDirectoryNavigationPage
+ extends WizardNavigationPage<NewProjectInput,NewProjectResult>
+{
+
+ public NewDirectoryNavigationPage(SessionInfo sessionInfo)
+ {
+ super("New Directory",
+ "Start a project in a brand new working directory",
+ "Project Type",
+ NewProjectResources.INSTANCE.newProjectDirectoryIcon(),
+ NewProjectResources.INSTANCE.newProjectDirectoryIconLarge(),
+ createPages(sessionInfo));
+ }
+
+
+ private static ArrayList<WizardPage<NewProjectInput, NewProjectResult>>
+ createPages(SessionInfo sessionInfo)
+ {
+ ArrayList<WizardPage<NewProjectInput, NewProjectResult>> pages =
+ new ArrayList<WizardPage<NewProjectInput, NewProjectResult>>();
+
+ pages.add(new NewDirectoryPage());
+ pages.add(new NewPackagePage());
+ pages.add(new NewShinyAppPage());
+ return pages;
+ }
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryPage.java
new file mode 100644
index 0000000..ed55f78
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewDirectoryPage.java
@@ -0,0 +1,190 @@
+/*
+ * NewDirectoryPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.projects.model.NewPackageOptions;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.projects.model.NewShinyAppOptions;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+public class NewDirectoryPage extends NewProjectWizardPage
+{
+ public NewDirectoryPage()
+ {
+ this("Empty Project",
+ "Create a new project in an empty directory",
+ "Create New Project",
+ NewProjectResources.INSTANCE.newProjectDirectoryIcon(),
+ NewProjectResources.INSTANCE.newProjectDirectoryIconLarge());
+ }
+
+ public NewDirectoryPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage)
+ {
+ super(title, subTitle, pageCaption, image, largeImage);
+ }
+
+
+ @Override
+ protected void onAddWidgets()
+ {
+ NewProjectResources.Styles styles = NewProjectResources.INSTANCE.styles();
+
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.addStyleName(styles.wizardMainColumn());
+
+ // create the dir name label
+ dirNameLabel_ = new Label("Directory name:");
+ dirNameLabel_.addStyleName(styles.wizardTextEntryLabel());
+
+ // top panel widgets
+ onAddTopPanelWidgets(panel);
+
+ // dir name
+ VerticalPanel namePanel = new VerticalPanel();
+ namePanel.addStyleName(styles.newProjectDirectoryName());
+ namePanel.add(dirNameLabel_);
+ txtProjectName_ = new TextBox();
+ txtProjectName_.setWidth("100%");
+ namePanel.add(txtProjectName_);
+ panel.add(namePanel);
+ addWidget(panel);
+
+ onAddBodyWidgets();
+
+ addSpacer();
+
+ // project dir
+ newProjectParent_ = new DirectoryChooserTextBox(
+ "Create project as subdirectory of:", txtProjectName_);
+ addWidget(newProjectParent_);
+
+ // if git is available then add git init
+ SessionInfo sessionInfo =
+ RStudioGinjector.INSTANCE.getSession().getSessionInfo();
+ chkGitInit_ = new CheckBox("Create a git repository for this project");
+ chkGitInit_.addStyleName(styles.wizardCheckbox());
+ if (sessionInfo.isVcsAvailable(VCSConstants.GIT_ID))
+ {
+ UIPrefs uiPrefs = RStudioGinjector.INSTANCE.getUIPrefs();
+ chkGitInit_.setValue(uiPrefs.newProjGitInit().getValue());
+
+ addWidget(chkGitInit_);
+ }
+ }
+
+ protected void onAddTopPanelWidgets(HorizontalPanel panel)
+ {
+ }
+
+ protected void onAddBodyWidgets()
+ {
+ }
+
+ protected NewPackageOptions getNewPackageOptions()
+ {
+ return null;
+ }
+
+ protected NewShinyAppOptions getNewShinyAppOptions()
+ {
+ return null;
+ }
+
+ @Override
+ protected void initialize(NewProjectInput input)
+ {
+ super.initialize(input);
+
+ newProjectParent_.setText(input.getDefaultNewProjectLocation().getPath());
+ }
+
+
+ @Override
+ protected boolean validate(NewProjectResult input)
+ {
+ if (input == null)
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.WARNING,
+ "Error",
+ "You must specify a name for the new project directory.",
+ txtProjectName_);
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ @Override
+ protected NewProjectResult collectInput()
+ {
+ String name = txtProjectName_.getText().trim();
+ String dir = newProjectParent_.getText();
+ if (name.length() > 0 && dir.length() > 0)
+ {
+ String projDir = FileSystemItem.createDir(dir).completePath(name);
+ String projFile = projFileFromDir(projDir);
+ String newDefaultLocation = null;
+ if (!dir.equals(defaultNewProjectLocation_))
+ newDefaultLocation = dir;
+
+ return new NewProjectResult(projFile,
+ chkGitInit_.getValue(),
+ newDefaultLocation,
+ null,
+ getNewPackageOptions(),
+ getNewShinyAppOptions());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+ @Override
+ public void focus()
+ {
+ txtProjectName_.setFocus(true);
+ }
+
+ protected Label dirNameLabel_;
+ protected TextBox txtProjectName_;
+ private CheckBox chkGitInit_;
+
+ private DirectoryChooserTextBox newProjectParent_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewPackagePage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewPackagePage.java
new file mode 100644
index 0000000..4109a01
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewPackagePage.java
@@ -0,0 +1,103 @@
+/*
+ * NewDirectoryPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.projects.model.NewPackageOptions;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+
+
+public class NewPackagePage extends NewDirectoryPage
+{
+ public NewPackagePage()
+ {
+ super("R Package",
+ "Create a new R package",
+ "Create R Package",
+ NewProjectResources.INSTANCE.packageIcon(),
+ NewProjectResources.INSTANCE.packageIconLarge());
+ }
+
+ @Override
+ protected void onAddTopPanelWidgets(HorizontalPanel panel)
+ {
+ dirNameLabel_.setText("Package name:");
+
+ String[] labels = {"Package"};
+ String[] values = {"package"};
+ listProjectType_ = new SelectWidget("Type:",
+ labels,
+ values,
+ false);
+ listProjectType_.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ txtProjectName_.setFocus(true);
+ }
+ });
+ panel.add(listProjectType_);
+ }
+
+ @Override
+ protected void onAddBodyWidgets()
+ {
+ // code files panel
+ listCodeFiles_ = new CodeFilesList();
+ addWidget(listCodeFiles_);
+ }
+
+
+ @Override
+ protected void initialize(NewProjectInput input)
+ {
+ super.initialize(input);
+
+ if (input.getContext().isRcppAvailable())
+ listProjectType_.addChoice("Package w/ Rcpp", "package-rcpp");
+ }
+
+ @Override
+ public void focus()
+ {
+ super.focus();
+
+ // workaround qt crash on mac desktop
+ if (BrowseCap.isMacintoshDesktop())
+ {
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ listProjectType_.getListBox());
+ }
+ }
+
+ @Override
+ protected NewPackageOptions getNewPackageOptions()
+ {
+ return NewPackageOptions.create(
+ listProjectType_.getValue().equals("package-rcpp"),
+ JsUtil.toJsArrayString(listCodeFiles_.getCodeFiles()));
+ }
+
+ private SelectWidget listProjectType_;
+ private CodeFilesList listCodeFiles_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectResources.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectResources.java
new file mode 100644
index 0000000..5bfa7ca
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectResources.java
@@ -0,0 +1,64 @@
+/*
+ * NewProjectResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+
+public interface NewProjectResources extends ClientBundle
+{
+ static NewProjectResources INSTANCE =
+ (NewProjectResources)GWT.create(NewProjectResources.class);
+
+
+ ImageResource newProjectDirectoryIcon();
+ ImageResource newProjectDirectoryIconLarge();
+ ImageResource packageIcon();
+ ImageResource packageIconLarge();
+ ImageResource shinyAppIcon();
+ ImageResource shinyAppIconLarge();
+ ImageResource existingDirectoryIcon();
+ ImageResource existingDirectoryIconLarge();
+ ImageResource projectFromRepositoryIcon();
+ ImageResource projectFromRepositoryIconLarge();
+
+ ImageResource gitIcon();
+ ImageResource gitIconLarge();
+ ImageResource svnIcon();
+ ImageResource svnIconLarge();
+
+ static interface Styles extends CssResource
+ {
+ String wizardWidget();
+ String wizardMainColumn();
+ String wizardTextEntryLabel();
+ String wizardSpacer();
+ String vcsSelectorDesktop();
+ String wizardCheckbox();
+ String vcsNotInstalledWidget();
+ String vcsHelpLink();
+ String newProjectDirectoryName();
+ String codeFilesListButton();
+ String codeFilesListBox();
+ }
+
+ @Source("NewProjectWizard.css")
+ Styles styles();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.css b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.css
new file mode 100644
index 0000000..ba221f7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.css
@@ -0,0 +1,55 @@
+
+.wizardWidget {
+ margin-left: 2px;
+}
+
+.wizardTextEntryLabel {
+ margin-bottom: 2px;
+}
+
+.wizardMainColumn {
+ width: 296px;
+}
+
+.wizardSpacer {
+ height: 12px;
+}
+
+
+.newProjectDirectoryName {
+ margin-left: 5px;
+ width: 97%;
+}
+
+.codeFilesListButton {
+ width: 60px;
+}
+
+.codeFilesListBox {
+ width: 236px;
+ margin-right: 4px;
+}
+
+.vcsSelectorDesktop {
+ margin-top: -2px;
+ margin-left: -2px;
+}
+
+ at if user.agent gecko1_8 { .wizardCheckbox {
+ margin-left: -3px;
+ }
+}
+ at else { .wizardCheckbox {
+ }
+}
+
+.vcsNotInstalledWidget {
+ width: 325px;
+ margin-left: 10px;
+}
+
+.vcsHelpLink {
+ margin-left: -2px;
+ margin-top: 8px;
+ margin-bottom: 20px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.java
new file mode 100644
index 0000000..993decb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizard.java
@@ -0,0 +1,85 @@
+/*
+ * NewProjectWizard.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.Wizard;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.core.client.widget.WizardPage;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import com.google.gwt.user.client.ui.CheckBox;
+
+
+public class NewProjectWizard extends Wizard<NewProjectInput,NewProjectResult>
+{
+ public NewProjectWizard(
+ SessionInfo sessionInfo,
+ UIPrefs uiPrefs,
+ NewProjectInput input,
+ ProgressOperationWithInput<NewProjectResult> operation)
+ {
+ super("New Project",
+ "Create project from:",
+ input,
+ operation);
+
+ setOkButtonCaption("Create Project");
+
+
+ openInNewWindow_ = new CheckBox("Open in new window");
+ addLeftWidget(openInNewWindow_);
+ openInNewWindow_.setVisible(false);
+
+
+ addPage(new NewDirectoryNavigationPage(sessionInfo));
+ addPage(new ExistingDirectoryPage());
+
+ if (sessionInfo.getAllowVcs())
+ addPage(new VersionControlNavigationPage(sessionInfo));
+ }
+
+ @Override
+ protected void onPageActivated(
+ WizardPage<NewProjectInput,NewProjectResult> page,
+ boolean okButtonVisible)
+ {
+ openInNewWindow_.setVisible(Desktop.isDesktop() && okButtonVisible);
+ }
+
+ @Override
+ protected void onSelectorActivated()
+ {
+ openInNewWindow_.setVisible(false);
+ }
+
+ @Override
+ protected NewProjectResult ammendInput(NewProjectResult result)
+ {
+ if (result != null)
+ {
+ result.setOpenInNewWindow(openInNewWindow_.getValue());
+ return result;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private final CheckBox openInNewWindow_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizardPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizardPage.java
new file mode 100644
index 0000000..0b72d20
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewProjectWizardPage.java
@@ -0,0 +1,96 @@
+/*
+ * NewProjectWizardPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.WizardPage;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class NewProjectWizardPage
+ extends WizardPage<NewProjectInput,NewProjectResult>
+{
+ public NewProjectWizardPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage)
+ {
+ super(title, subTitle, pageCaption, image, largeImage);
+
+ }
+
+ @Override
+ protected Widget createWidget()
+ {
+ flowPanel_ = new FlowPanel();
+ flowPanel_.setWidth("100%");
+
+ onAddWidgets();
+
+ return flowPanel_;
+ }
+
+ protected abstract void onAddWidgets();
+
+
+
+ @Override
+ protected void initialize(NewProjectInput input)
+ {
+ defaultNewProjectLocation_ = input.getDefaultNewProjectLocation();
+ }
+
+ protected void addWidget(Widget widget)
+ {
+ widget.addStyleName(NewProjectResources.INSTANCE.styles().wizardWidget());
+ flowPanel_.add(widget);
+ }
+
+ protected SessionInfo getSessionInfo()
+ {
+ return RStudioGinjector.INSTANCE.getSession().getSessionInfo();
+ }
+
+ protected void addSpacer()
+ {
+ Label spacerLabel = new Label();
+ spacerLabel.addStyleName(
+ NewProjectResources.INSTANCE.styles().wizardSpacer());
+ flowPanel_.add(spacerLabel);
+ }
+
+ protected String projFileFromDir(String dir)
+ {
+ FileSystemItem dirItem = FileSystemItem.createDir(dir);
+ return FileSystemItem.createFile(
+ dirItem.completePath(dirItem.getName() + ".Rproj")).getPath();
+ }
+
+ protected FileSystemItem defaultNewProjectLocation_;
+
+ protected final GlobalDisplay globalDisplay_ =
+ RStudioGinjector.INSTANCE.getGlobalDisplay();
+
+ private FlowPanel flowPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewShinyAppPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewShinyAppPage.java
new file mode 100644
index 0000000..4cad993
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/NewShinyAppPage.java
@@ -0,0 +1,58 @@
+/*
+ * NewShinyAppPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewShinyAppOptions;
+
+import com.google.gwt.user.client.ui.HorizontalPanel;
+
+
+public class NewShinyAppPage extends NewDirectoryPage
+{
+ public NewShinyAppPage()
+ {
+ super("Shiny Web Application",
+ "Create a new Shiny web application",
+ "Create Shiny Web Application",
+ NewProjectResources.INSTANCE.shinyAppIcon(),
+ NewProjectResources.INSTANCE.shinyAppIconLarge());
+ }
+
+ @Override
+ protected void onAddTopPanelWidgets(HorizontalPanel panel)
+ {
+
+ }
+
+ @Override
+ protected void onAddBodyWidgets()
+ {
+
+ }
+
+ @Override
+ protected void initialize(NewProjectInput input)
+ {
+ super.initialize(input);
+
+ }
+
+ @Override
+ protected NewShinyAppOptions getNewShinyAppOptions()
+ {
+ return NewShinyAppOptions.create();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/SvnPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/SvnPage.java
new file mode 100644
index 0000000..d6d740d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/SvnPage.java
@@ -0,0 +1,64 @@
+/*
+ * SvnPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+
+public class SvnPage extends VersionControlPage
+{
+ public SvnPage()
+ {
+ super("Subversion",
+ "Checkout a project from a Subversion repository",
+ "Checkout Subversion Repository",
+ NewProjectResources.INSTANCE.svnIcon(),
+ NewProjectResources.INSTANCE.svnIconLarge());
+ }
+
+
+ @Override
+ protected String getVcsId()
+ {
+ return VCSConstants.SVN_ID;
+ }
+
+ @Override
+ protected boolean includeCredentials()
+ {
+ return true;
+ }
+
+
+ @Override
+ protected String guessRepoDir(String url)
+ {
+ // Strip trailing spaces and slashes
+ while (url.endsWith("/") || url.endsWith(" ") || url.endsWith("\t"))
+ url = url.substring(0, url.length() - 1);
+
+ // Find last component
+ url = url.replaceFirst(".*[/]", ""); // greedy
+
+ url = url.replaceAll("[\u0000-\u0020]+", " ");
+ url = url.trim();
+
+ // Suppress if it is "trunk"
+ if (url.equals("trunk"))
+ url = "";
+
+ return url;
+
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlNavigationPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlNavigationPage.java
new file mode 100644
index 0000000..d8681db
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlNavigationPage.java
@@ -0,0 +1,53 @@
+/*
+ * VersionControlNavigationPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.widget.WizardNavigationPage;
+import org.rstudio.core.client.widget.WizardPage;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+public class VersionControlNavigationPage
+ extends WizardNavigationPage<NewProjectInput,NewProjectResult>
+{
+
+ public VersionControlNavigationPage(SessionInfo sessionInfo)
+ {
+ super("Version Control",
+ "Checkout a project from a version control repository",
+ "Create Project from Version Control",
+ NewProjectResources.INSTANCE.projectFromRepositoryIcon(),
+ NewProjectResources.INSTANCE.projectFromRepositoryIconLarge(),
+ createPages(sessionInfo));
+ }
+
+
+ private static ArrayList<WizardPage<NewProjectInput, NewProjectResult>>
+ createPages(SessionInfo sessionInfo)
+ {
+ ArrayList<WizardPage<NewProjectInput, NewProjectResult>> pages =
+ new ArrayList<WizardPage<NewProjectInput, NewProjectResult>>();
+
+ pages.add(new GitPage());
+ pages.add(new SvnPage());
+
+ return pages;
+ }
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlPage.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlPage.java
new file mode 100644
index 0000000..32050f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/VersionControlPage.java
@@ -0,0 +1,291 @@
+/*
+ * VersionControlPage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.newproject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.HelpLink;
+import org.rstudio.studio.client.common.vcs.VcsCloneOptions;
+import org.rstudio.studio.client.common.vcs.VcsHelpLink;
+import org.rstudio.studio.client.projects.model.NewProjectInput;
+import org.rstudio.studio.client.projects.model.NewProjectResult;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+public abstract class VersionControlPage extends NewProjectWizardPage
+{
+
+ public VersionControlPage(String title,
+ String subTitle,
+ String pageCaption,
+ ImageResource image,
+ ImageResource largeImage)
+ {
+ super(title, subTitle, pageCaption, image, largeImage);
+ }
+
+
+ @Override
+ protected boolean acceptNavigation()
+ {
+ SessionInfo sessionInfo =
+ RStudioGinjector.INSTANCE.getSession().getSessionInfo();
+ if (!sessionInfo.isVcsAvailable(getVcsId()))
+ {
+ NewProjectResources.Styles styles =
+ NewProjectResources.INSTANCE.styles();
+
+ VerticalPanel verticalPanel = new VerticalPanel();
+ verticalPanel.addStyleName(styles.vcsNotInstalledWidget());
+
+ if (Desktop.isDesktop())
+ {
+ HTML msg = new HTML(
+ "<p>" + getTitle() + " was not detected " +
+ "on the system path.</p>" +
+ "<p>To create projects from " + getTitle() + " " +
+ "repositories you should install " + getTitle() + " " +
+ "and then restart RStudio.</p>" +
+ "<p>Note that if " + getTitle() + " is installed " +
+ "and not on the path, then you can specify its location using " +
+ "the " + (BrowseCap.isMacintosh() ? "Preferences" : "Options") +
+ " dialog.</p>");
+ msg.setWidth("100%");
+
+ verticalPanel.add(msg);
+
+ HelpLink vcsHelpLink = new VcsHelpLink();
+ vcsHelpLink.setCaption("Using " + getTitle() + " with RStudio");
+ vcsHelpLink.addStyleName(styles.vcsHelpLink());
+ verticalPanel.add(vcsHelpLink);
+ }
+ else
+ {
+ HTML msg = new HTML(
+ "<p>An installation of " + getTitle() + " was not detected " +
+ "on this system.</p>" +
+ "<p>To create projects from " + getTitle() + " " +
+ "repositories you should request that your server " +
+ "administrator install the " + getTitle() + " package.</p>");
+ msg.setWidth("100%");
+
+ verticalPanel.add(msg);
+ }
+
+ MessageDialog dlg = new MessageDialog(MessageDialog.INFO,
+ getTitle() + " Not Found",
+ verticalPanel);
+
+
+ dlg.addButton("OK", (Operation)null, true, false);
+ dlg.showModal();
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+
+ @Override
+ protected void onAddWidgets()
+ {
+ NewProjectResources.Styles styles = NewProjectResources.INSTANCE.styles();
+
+ VerticalPanel urlPanel = new VerticalPanel();
+ urlPanel.addStyleName(styles.wizardMainColumn());
+ Label urlLabel = new Label("Repository URL:");
+ urlLabel.addStyleName(styles.wizardTextEntryLabel());
+ urlPanel.add(urlLabel);
+ txtRepoUrl_ = new TextBox();
+ txtRepoUrl_.addDomHandler(new KeyDownHandler() {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ handleAutoFillCheckoutDir();
+ }
+ }, KeyDownEvent.getType());
+
+ txtRepoUrl_.setWidth("100%");
+ urlPanel.add(txtRepoUrl_);
+
+ addWidget(urlPanel);
+
+ addSpacer();
+
+ txtUsername_ = new TextBox();
+ txtUsername_.setWidth("100%");
+
+ if (includeCredentials())
+ {
+ VerticalPanel usernamePanel = new VerticalPanel();
+ usernamePanel.addStyleName(styles.wizardMainColumn());
+ Label usernameLabel = new Label("Username (if required for this repository URL):");
+ usernameLabel.addStyleName(styles.wizardTextEntryLabel());
+ usernamePanel.add(usernameLabel);
+ usernamePanel.add(txtUsername_);
+ addWidget(usernamePanel);
+
+ addSpacer();
+ }
+
+ Label dirNameLabel = new Label("Project directory name:");
+ dirNameLabel.addStyleName(styles.wizardTextEntryLabel());
+ addWidget(dirNameLabel);
+ txtDirName_ = new TextBox();
+ txtDirName_.addValueChangeHandler(new ValueChangeHandler<String>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ if (!event.getValue().equals(guessRepoDir()))
+ suppressDirNameDetection_ = true;
+ }
+
+ });
+ txtDirName_.addStyleName(styles.wizardMainColumn());
+ addWidget(txtDirName_);
+
+ addSpacer();
+
+ existingRepoDestDir_ = new DirectoryChooserTextBox(
+ "Create project as subdirectory of:", txtRepoUrl_);
+ addWidget(existingRepoDestDir_);
+ }
+
+ @Override
+ protected void initialize(NewProjectInput input)
+ {
+ super.initialize(input);
+ existingRepoDestDir_.setText(
+ input.getDefaultNewProjectLocation().getPath());
+ }
+
+ @Override
+ protected NewProjectResult collectInput()
+ {
+ if (txtDirName_.getText().trim().length() == 0)
+ autoFillCheckoutDir();
+
+ String url = txtRepoUrl_.getText().trim();
+ String username = txtUsername_.getText().trim();
+ String checkoutDir = txtDirName_.getText().trim();
+ String dir = existingRepoDestDir_.getText().trim();
+ if (url.length() > 0 && checkoutDir.length() > 0 && dir.length() > 0)
+ {
+ String projFile = projFileFromDir(
+ FileSystemItem.createDir(dir).completePath(checkoutDir));
+
+ VcsCloneOptions options = VcsCloneOptions.create(getVcsId(),
+ url,
+ username,
+ checkoutDir,
+ dir);
+
+ return new NewProjectResult(projFile, false, dir, options, null, null);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean validate(NewProjectResult input)
+ {
+ if (input == null)
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.WARNING,
+ "Error",
+ "You must specify a repository URL and " +
+ "directory to create the new project within.",
+ txtRepoUrl_);
+
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+
+ }
+
+
+ @Override
+ public void focus()
+ {
+ txtRepoUrl_.setFocus(true);
+
+ }
+
+ private void handleAutoFillCheckoutDir()
+ {
+ if (suppressDirNameDetection_)
+ return;
+
+ // delay so the text has a chance to populate
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ autoFillCheckoutDir();
+ }
+ });
+ }
+
+ private void autoFillCheckoutDir()
+ {
+ txtDirName_.setText(guessRepoDir());
+ }
+
+ private String guessRepoDir()
+ {
+ return guessRepoDir(txtRepoUrl_.getText().trim());
+ }
+
+ protected abstract String getVcsId();
+
+ protected boolean includeCredentials()
+ {
+ return false;
+ }
+
+ protected abstract String guessRepoDir(String url);
+
+ private TextBox txtRepoUrl_;
+ private TextBox txtUsername_;
+ private TextBox txtDirName_;
+ private DirectoryChooserTextBox existingRepoDestDir_;
+ private boolean suppressDirNameDetection_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIcon.png
new file mode 100755
index 0000000..635fff8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIconLarge.png
new file mode 100755
index 0000000..366312a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/existingDirectoryIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIcon.png
new file mode 100644
index 0000000..42bb8aa
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIconLarge.png
new file mode 100644
index 0000000..0ab6238
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/gitIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIcon.png
new file mode 100755
index 0000000..e38a44f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIconLarge.png
new file mode 100755
index 0000000..e40b53e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/newProjectDirectoryIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIcon.png
new file mode 100644
index 0000000..2e1467e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIconLarge.png
new file mode 100644
index 0000000..c6a81e7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/packageIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIcon.png
new file mode 100755
index 0000000..cc54a63
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIconLarge.png
new file mode 100755
index 0000000..60de427
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/projectFromRepositoryIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIcon.png
new file mode 100644
index 0000000..3d44e8e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIconLarge.png
new file mode 100644
index 0000000..34f0501
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/shinyAppIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIcon.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIcon.png
new file mode 100644
index 0000000..a8d0bd6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIconLarge.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIconLarge.png
new file mode 100644
index 0000000..9e64332
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/newproject/svnIconLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectCompilePdfPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectCompilePdfPreferencesPane.java
new file mode 100644
index 0000000..bfbd442
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectCompilePdfPreferencesPane.java
@@ -0,0 +1,182 @@
+/*
+ * ProjectCompilePdfPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.latex.LatexProgramSelectWidget;
+import org.rstudio.studio.client.common.rnw.RnwWeaveSelectWidget;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+public class ProjectCompilePdfPreferencesPane extends ProjectPreferencesPane
+{
+ @Inject
+ public ProjectCompilePdfPreferencesPane()
+ {
+ addHeader("Program defaults");
+
+ defaultSweaveEngine_ = new RnwWeaveSelectWidget();
+ add(defaultSweaveEngine_);
+
+ defaultLatexProgram_ = new LatexProgramSelectWidget();
+ add(defaultLatexProgram_);
+
+ addHeader("PDF preview");
+
+ rootDoc_ = new RootDocumentChooser();
+ nudgeRight(rootDoc_);
+ add(rootDoc_);
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconCompilePdf();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Sweave";
+ }
+
+ @Override
+ protected void initialize(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ defaultSweaveEngine_.setValue(config.getDefaultSweaveEngine());
+ defaultLatexProgram_.setValue(config.getDefaultLatexProgram());
+ rootDoc_.setText(config.getRootDocument());
+
+ // workaround qt crash on mac desktop
+ if (BrowseCap.isMacintoshDesktop())
+ {
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ defaultSweaveEngine_.getListBox());
+
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ defaultLatexProgram_.getListBox());
+ }
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean onApply(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setDefaultSweaveEngine(defaultSweaveEngine_.getValue());
+ config.setDefaultLatexProgram(defaultLatexProgram_.getValue());
+ config.setRootDocument(rootDoc_.getText().trim());
+ return false;
+ }
+
+ private void addHeader(String caption)
+ {
+ PreferencesDialogBaseResources baseRes =
+ PreferencesDialogBaseResources.INSTANCE;
+ Label pdfCompilationLabel = new Label(caption);
+ pdfCompilationLabel.addStyleName(baseRes.styles().headerLabel());
+ nudgeRight(pdfCompilationLabel);
+ add(pdfCompilationLabel);
+ }
+
+ private class RootDocumentChooser extends TextBoxWithButton
+ {
+ public RootDocumentChooser()
+ {
+ super("Compile PDF root document",
+ "(Current Document)",
+ "Browse...",
+ new HelpButton("pdf_root_document"),
+ null);
+
+ // allow user to set the value to empty string
+ setReadOnly(false);
+
+ addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ final FileSystemItem projDir = RStudioGinjector.INSTANCE.
+ getSession().getSessionInfo().getActiveProjectDir();
+
+ RStudioGinjector.INSTANCE.getFileDialogs().openFile(
+ "Choose File",
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext(),
+ projDir,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ String proj = projDir.getPath();
+ if (input.getPath().startsWith(proj + "/"))
+ {
+ String projRelative =
+ input.getPath().substring(proj.length() + 1);
+ setText(projRelative);
+ }
+ else
+ {
+
+ }
+ }
+ });
+ }
+ });
+
+ }
+
+ // allow user to set the value to empty string
+ @Override
+ public String getText()
+ {
+ if (getTextBox().getText().trim().isEmpty())
+ return "";
+ else
+ return super.getText();
+ }
+ }
+
+ private RnwWeaveSelectWidget defaultSweaveEngine_;
+ private LatexProgramSelectWidget defaultLatexProgram_;
+ private TextBoxWithButton rootDoc_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectEditingPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectEditingPreferencesPane.java
new file mode 100644
index 0000000..6a359c0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectEditingPreferencesPane.java
@@ -0,0 +1,173 @@
+/*
+ * ProjectEditingPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.NumericValueWidget;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.workbench.views.source.editors.text.IconvListResult;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ui.ChooseEncodingDialog;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.inject.Inject;
+
+public class ProjectEditingPreferencesPane extends ProjectPreferencesPane
+{
+ @Inject
+ public ProjectEditingPreferencesPane(final SourceServerOperations server)
+ {
+ // source editing options
+ enableCodeIndexing_ = new CheckBox("Index R source files (for code search/navigation)", false);
+ enableCodeIndexing_.addStyleName(RESOURCES.styles().enableCodeIndexing());
+ add(enableCodeIndexing_);
+
+ chkSpacesForTab_ = new CheckBox("Insert spaces for tab", false);
+ chkSpacesForTab_.addStyleName(RESOURCES.styles().useSpacesForTab());
+ add(chkSpacesForTab_);
+
+ numSpacesForTab_ = new NumericValueWidget("Tab width");
+ numSpacesForTab_.addStyleName(RESOURCES.styles().numberOfTabs());
+ add(numSpacesForTab_);
+
+ chkAutoAppendNewline_ = new CheckBox("Ensure that source files end with newline");
+ chkAutoAppendNewline_.addStyleName(RESOURCES.styles().editingOption());
+ add(chkAutoAppendNewline_);
+
+ chkStripTrailingWhitespace_ = new CheckBox("Strip trailing horizontal whitespace when saving");
+ chkStripTrailingWhitespace_.addStyleName(RESOURCES.styles().editingOption());
+ add(chkStripTrailingWhitespace_);
+
+ encoding_ = new TextBoxWithButton(
+ "Text encoding:",
+ "Change...",
+ new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ server.iconvlist(new SimpleRequestCallback<IconvListResult>()
+ {
+ @Override
+ public void onResponseReceived(IconvListResult response)
+ {
+ new ChooseEncodingDialog(
+ response.getCommon(),
+ response.getAll(),
+ encodingValue_,
+ false,
+ false,
+ new OperationWithInput<String>()
+ {
+ public void execute(String encoding)
+ {
+ if (encoding == null)
+ return;
+
+ setEncoding(encoding);
+ }
+ }).showModal();
+ }
+ });
+
+ }
+ });
+ encoding_.setWidth("250px");
+ encoding_.addStyleName(RESOURCES.styles().encodingChooser());
+
+ add(encoding_);
+
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconCodeEditing();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Code Editing";
+ }
+
+ @Override
+ protected void initialize(RProjectOptions options)
+ {
+ initialConfig_ = options.getConfig();
+
+ enableCodeIndexing_.setValue(initialConfig_.getEnableCodeIndexing());
+ chkSpacesForTab_.setValue(initialConfig_.getUseSpacesForTab());
+ numSpacesForTab_.setValue(initialConfig_.getNumSpacesForTab() + "");
+ chkAutoAppendNewline_.setValue(initialConfig_.getAutoAppendNewline());
+ chkStripTrailingWhitespace_.setValue(initialConfig_.getStripTrailingWhitespace());
+ setEncoding(initialConfig_.getEncoding());
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return numSpacesForTab_.validate("Tab width");
+ }
+
+ @Override
+ public boolean onApply(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setEnableCodeIndexing(enableCodeIndexing_.getValue());
+ config.setUseSpacesForTab(chkSpacesForTab_.getValue());
+ config.setNumSpacesForTab(getTabWidth());
+ config.setAutoAppendNewline(chkAutoAppendNewline_.getValue());
+ config.setStripTrailingWhitespace(chkStripTrailingWhitespace_.getValue());
+ config.setEncoding(encodingValue_);
+ return false;
+ }
+
+ private void setEncoding(String encoding)
+ {
+ encodingValue_ = encoding;
+ encoding_.setText(encoding);
+ }
+
+ private int getTabWidth()
+ {
+ try
+ {
+ return Integer.parseInt(numSpacesForTab_.getValue());
+ }
+ catch (Exception e)
+ {
+ // should never happen since validate would have been called
+ // prior to exiting the dialog. revert to original setting
+ return initialConfig_.getNumSpacesForTab();
+ }
+ }
+
+ private CheckBox enableCodeIndexing_;
+ private CheckBox chkSpacesForTab_;
+ private NumericValueWidget numSpacesForTab_;
+ private CheckBox chkAutoAppendNewline_;
+ private CheckBox chkStripTrailingWhitespace_;
+ private TextBoxWithButton encoding_;
+ private String encodingValue_;
+ private RProjectConfig initialConfig_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectGeneralPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectGeneralPreferencesPane.java
new file mode 100644
index 0000000..68016ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectGeneralPreferencesPane.java
@@ -0,0 +1,127 @@
+/*
+ * ProjectGeneralPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.inject.Inject;
+
+public class ProjectGeneralPreferencesPane extends ProjectPreferencesPane
+{
+ @Inject
+ public ProjectGeneralPreferencesPane()
+ {
+ Grid grid = new Grid(4, 2);
+ grid.addStyleName(RESOURCES.styles().workspaceGrid());
+ grid.setCellSpacing(8);
+
+ Label infoLabel = new Label("Use (Default) to inherit the global default setting");
+ infoLabel.addStyleName(PreferencesDialogBaseResources.INSTANCE.styles().infoLabel());
+ grid.setWidget(0, 0, infoLabel);
+
+ // restore workspace
+ grid.setWidget(1, 0, new Label("Restore .RData into workspace at startup"));
+ grid.setWidget(1, 1, restoreWorkspace_ = new YesNoAskDefault(false));
+
+ // save workspace
+ grid.setWidget(2, 0, new Label("Save workspace to .RData on exit"));
+ grid.setWidget(2, 1, saveWorkspace_ = new YesNoAskDefault(true));
+
+ // always save history
+ grid.setWidget(3, 0, new Label("Always save history (even if not saving .RData)"));
+ grid.setWidget(3, 1, alwaysSaveHistory_ = new YesNoAskDefault(false));
+
+ add(grid);
+
+
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconR();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "General";
+ }
+
+ @Override
+ protected void initialize(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ restoreWorkspace_.setSelectedValue(config.getRestoreWorkspace());
+ saveWorkspace_.setSelectedValue(config.getSaveWorkspace());
+ alwaysSaveHistory_.setSelectedValue(config.getAlwaysSaveHistory());
+ tutorialPath_ = config.getTutorialPath();
+ }
+
+ @Override
+ public boolean onApply(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setRestoreWorkspace(restoreWorkspace_.getSelectedValue());
+ config.setSaveWorkspace(saveWorkspace_.getSelectedValue());
+ config.setAlwaysSaveHistory(alwaysSaveHistory_.getSelectedValue());
+ config.setTutorialPath(tutorialPath_);
+ return false;
+ }
+
+ private class YesNoAskDefault extends ListBox
+ {
+ public YesNoAskDefault(boolean includeAsk)
+ {
+ super(false);
+
+ String[] items = includeAsk ? new String[] {USE_DEFAULT, YES, NO, ASK}:
+ new String[] {USE_DEFAULT, YES, NO};
+
+ for (int i=0; i<items.length; i++)
+ addItem(items[i]);
+ }
+
+ public void setSelectedValue(int value)
+ {
+ if (value < getItemCount())
+ setSelectedIndex(value);
+ else
+ setSelectedIndex(0);
+ }
+
+ public int getSelectedValue()
+ {
+ return getSelectedIndex();
+ }
+ }
+
+ private static final String USE_DEFAULT = "(Default)";
+ private static final String YES = "Yes";
+ private static final String NO = "No";
+ private static final String ASK ="Ask";
+
+ private YesNoAskDefault restoreWorkspace_;
+ private YesNoAskDefault saveWorkspace_;
+ private YesNoAskDefault alwaysSaveHistory_;
+
+ private String tutorialPath_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.css b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.css
new file mode 100644
index 0000000..7406fe5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.css
@@ -0,0 +1,93 @@
+
+ at external gwt-Label, gwt-TextBox;
+
+.panelContainer {
+ width: 525px;
+ height: 425px;
+}
+
+.buildToolsPanel {
+ width: 100%;
+}
+
+.workspaceGrid {
+ margin-bottom: 10px;
+ width: 375px;
+}
+
+.enableCodeIndexing {
+ margin-left: 5px;
+}
+
+.enableCodeIndexing input[type=checkbox] {
+ margin-bottom: 13px;
+}
+
+.useSpacesForTab {
+ margin-left: 5px;
+}
+
+.numberOfTabs {
+ margin-top: 5px;
+ margin-left: 25px;
+}
+
+.editingOption {
+ margin-top: 10px;
+ margin-left: 5px;
+}
+
+.encodingChooser {
+ margin-top: 13px;
+ margin-left: 10px;
+ margin-bottom: 15px;
+}
+
+.vcsSelectExtraSpaced {
+ margin-bottom: 20px;
+}
+
+.vcsOriginLabel {
+ margin-left: 5px;
+}
+
+.vcsOriginUrl {
+ margin-top: -3px;
+ margin-left: 5px;
+ border: 1px solid #BBB;
+ padding: 2px;
+ padding-right: 10px;
+}
+
+.vcsNoOriginUrl {
+ width: 150px;
+ color: #494949;
+}
+
+.buildToolsAdditionalArguments {
+ width: 375px;
+ margin-bottom: 12px;
+}
+
+.buildToolsAdditionalArguments .gwt-Label {
+ margin-bottom: 2px;
+}
+
+.buildToolsAdditionalArguments .gwt-TextBox {
+ width: 100%;
+}
+
+.buildToolsCheckBox {
+ margin-left: -5px;
+}
+
+.buildToolsRoxygenize {
+ width: 100%;
+ margin-bottom: 12px;
+}
+
+.buildToolsDevtools {
+ margin-bottom: 10px;
+}
+
+
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.java
new file mode 100644
index 0000000..6465248
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialog.java
@@ -0,0 +1,131 @@
+/*
+ * ProjectPreferencesDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import org.rstudio.core.client.prefs.PreferencesDialogBase;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.projects.model.ProjectsServerOperations;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.ui.prefs.buildtools.ProjectBuildToolsPreferencesPane;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class ProjectPreferencesDialog extends PreferencesDialogBase<RProjectOptions>
+{
+ public static final int GENERAL = 0;
+ public static final int EDITING = 1;
+ public static final int SWEAVE = 2;
+ public static final int BUILD = 3;
+ public static final int VCS = 4;
+
+ @Inject
+ public ProjectPreferencesDialog(ProjectsServerOperations server,
+ Provider<UIPrefs> pUIPrefs,
+ Session session,
+ ProjectGeneralPreferencesPane general,
+ ProjectEditingPreferencesPane editing,
+ ProjectCompilePdfPreferencesPane compilePdf,
+ ProjectSourceControlPreferencesPane source,
+ ProjectBuildToolsPreferencesPane build)
+ {
+ super("Project Options",
+ RES.styles().panelContainer(),
+ false,
+ new ProjectPreferencesPane[] {general, editing, compilePdf, build, source});
+
+ server_ = server;
+ pUIPrefs_ = pUIPrefs;
+
+ if (!session.getSessionInfo().getAllowVcs())
+ hidePane(4);
+ }
+
+ @Override
+ protected RProjectOptions createEmptyPrefs()
+ {
+ return RProjectOptions.createEmpty();
+ }
+
+
+ @Override
+ protected void doSaveChanges(final RProjectOptions options,
+ final Operation onCompleted,
+ final ProgressIndicator indicator,
+ final boolean reload)
+ {
+
+ server_.writeProjectOptions(
+ options,
+ new ServerRequestCallback<Void>() {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ indicator.onCompleted();
+
+ // update project ui prefs
+ RProjectConfig config = options.getConfig();
+ UIPrefs uiPrefs = pUIPrefs_.get();
+ uiPrefs.useSpacesForTab().setProjectValue(
+ config.getUseSpacesForTab());
+ uiPrefs.numSpacesForTab().setProjectValue(
+ config.getNumSpacesForTab());
+ uiPrefs.autoAppendNewline().setProjectValue(
+ config.getAutoAppendNewline());
+ uiPrefs.stripTrailingWhitespace().setProjectValue(
+ config.getStripTrailingWhitespace());
+ uiPrefs.defaultEncoding().setProjectValue(
+ config.getEncoding());
+ uiPrefs.defaultSweaveEngine().setProjectValue(
+ config.getDefaultSweaveEngine());
+ uiPrefs.defaultLatexProgram().setProjectValue(
+ config.getDefaultLatexProgram());
+ uiPrefs.rootDocument().setProjectValue(
+ config.getRootDocument());
+ uiPrefs.useRoxygen().setProjectValue(
+ config.hasPackageRoxygenize());
+
+ if (onCompleted != null)
+ onCompleted.execute();
+ if (reload)
+ reload();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+
+ }
+
+
+ private final ProjectsServerOperations server_;
+ private final Provider<UIPrefs> pUIPrefs_;
+
+ private static final ProjectPreferencesDialogResources RES =
+ ProjectPreferencesDialogResources.INSTANCE;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialogResources.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialogResources.java
new file mode 100644
index 0000000..474174d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesDialogResources.java
@@ -0,0 +1,50 @@
+/*
+ * ProjectPreferencesDialogResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface ProjectPreferencesDialogResources extends ClientBundle
+{
+ static interface Styles extends CssResource
+ {
+ String panelContainer();
+ String buildToolsPanel();
+ String workspaceGrid();
+ String enableCodeIndexing();
+ String useSpacesForTab();
+ String numberOfTabs();
+ String editingOption();
+ String encodingChooser();
+ String vcsSelectExtraSpaced();
+ String vcsOriginLabel();
+ String vcsOriginUrl();
+ String vcsNoOriginUrl();
+ String buildToolsAdditionalArguments();
+ String buildToolsRoxygenize();
+ String buildToolsCheckBox();
+ String buildToolsDevtools();
+ }
+
+ @Source("ProjectPreferencesDialog.css")
+ Styles styles();
+
+ ImageResource iconBuild();
+
+ static ProjectPreferencesDialogResources INSTANCE = (ProjectPreferencesDialogResources)GWT.create(ProjectPreferencesDialogResources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesPane.java
new file mode 100644
index 0000000..1335082
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectPreferencesPane.java
@@ -0,0 +1,79 @@
+/*
+ * ProjectPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+import org.rstudio.core.client.prefs.PreferencesDialogPaneBase;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.projects.events.SwitchToProjectEvent;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+
+public abstract class ProjectPreferencesPane
+ extends PreferencesDialogPaneBase<RProjectOptions>
+{
+
+ @Inject
+ void injectMembers(GlobalDisplay globalDisplay,
+ Session session,
+ EventBus eventBus)
+ {
+ globalDisplay_ = globalDisplay;
+ session_ = session;
+ eventBus_ = eventBus;
+ }
+
+ protected void promptToRestart()
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.QUESTION,
+ "Confirm Restart RStudio",
+ "You need to restart RStudio in order for this change to take " +
+ "effect. Do you want to do this now?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ forceClosed(new Command() {
+
+ @Override
+ public void execute()
+ {
+ SwitchToProjectEvent event = new SwitchToProjectEvent(
+ session_.getSessionInfo().getActiveProjectFile());
+ eventBus_.fireEvent(event);
+
+ }
+
+ });
+ }
+ },
+ true);
+ }
+
+ protected static final ProjectPreferencesDialogResources RESOURCES =
+ ProjectPreferencesDialogResources.INSTANCE;
+
+ private GlobalDisplay globalDisplay_;
+ private Session session_;
+ private EventBus eventBus_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectSourceControlPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectSourceControlPreferencesPane.java
new file mode 100644
index 0000000..41da7bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/ProjectSourceControlPreferencesPane.java
@@ -0,0 +1,325 @@
+/*
+ * ProjectSourceControlPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs;
+
+
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.HelpLink;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.common.vcs.VcsHelpLink;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.model.RProjectVcsOptions;
+import org.rstudio.studio.client.projects.model.RProjectVcsContext;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+public class ProjectSourceControlPreferencesPane extends ProjectPreferencesPane
+{
+ @Inject
+ public ProjectSourceControlPreferencesPane(final Session session,
+ GlobalDisplay globalDisplay,
+ GitServerOperations server)
+ {
+ session_ = session;
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+
+ vcsSelect_ = new SelectWidget("Version control system:", new String[]{});
+ spaced(vcsSelect_);
+ add(vcsSelect_);
+ vcsSelect_.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ updateOriginLabel();
+
+ if (vcsSelect_.getValue().equals(VCSConstants.GIT_ID))
+ {
+ confirmGitRepo(new Command() {
+ @Override
+ public void execute()
+ {
+ promptToRestart();
+ }
+ });
+ }
+ else
+ {
+ promptToRestart();
+ }
+ }
+ });
+
+ lblOrigin_ = new OriginLabel();
+ lblOrigin_.addStyleName(RES.styles().vcsOriginLabel());
+ lblOrigin_.addStyleName(ThemeStyles.INSTANCE.selectableText());
+ extraSpaced(lblOrigin_);
+ add(lblOrigin_);
+
+ HelpLink vcsHelpLink = new VcsHelpLink();
+ nudgeRight(vcsHelpLink);
+ add(vcsHelpLink);
+
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconSourceControl();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Git/SVN";
+ }
+
+ @Override
+ protected void initialize(RProjectOptions options)
+ {
+ // save the context
+ vcsContext_ = options.getVcsContext();
+
+ // populate the vcs selections list
+ String[] vcsSelections = new String[] { NONE };
+ JsArrayString applicableVcs = vcsContext_.getApplicableVcs();
+ if (applicableVcs.length() > 0)
+ {
+ vcsSelections = new String[applicableVcs.length() + 1];
+ vcsSelections[0] = NONE;
+ for (int i=0; i<applicableVcs.length(); i++)
+ vcsSelections[i+1] = applicableVcs.get(i);
+ }
+ vcsSelect_.setChoices(vcsSelections);
+
+ // set override or default
+ RProjectVcsOptions vcsOptions = options.getVcsOptions();
+ if (vcsOptions.getActiveVcsOverride().length() > 0)
+ setVcsSelection(vcsOptions.getActiveVcsOverride());
+ else
+ setVcsSelection(vcsContext_.getDetectedVcs());
+ }
+
+ @Override
+ public boolean onApply(RProjectOptions options)
+ {
+ RProjectVcsOptions vcsOptions = options.getVcsOptions();
+ setVcsOptions(vcsOptions);
+ return false;
+ }
+
+ private void setVcsOptions(RProjectVcsOptions vcsOptions)
+ {
+ String vcsSelection = getVcsSelection();
+ if (!vcsSelection.equals(vcsContext_.getDetectedVcs()))
+ vcsOptions.setActiveVcsOverride(vcsSelection);
+ else
+ vcsOptions.setActiveVcsOverride("");
+ }
+
+
+ private String getVcsSelection()
+ {
+ String value = vcsSelect_.getValue();
+ if (value.equals(NONE))
+ return VCSConstants.NO_ID;
+ else
+ return value;
+ }
+
+ private void setVcsSelection(String vcs)
+ {
+ // set value
+ if (vcs.equals(VCSConstants.NO_ID))
+ vcsSelect_.setValue(NONE);
+ else if (!vcsSelect_.setValue(vcs))
+ {
+ vcsSelect_.setValue(NONE);
+ }
+
+ updateOriginLabel();
+
+
+ }
+
+ private void updateOriginLabel()
+ {
+ String vcs = getVcsSelection();
+ if (vcs.equals(VCSConstants.GIT_ID))
+ {
+ StringBuilder label = new StringBuilder();
+ label.append("Origin: ");
+ String originUrl = vcsContext_.getGitRemoteOriginUrl();
+ if (originUrl.length() == 0)
+ originUrl = NO_REMOTE_ORIGIN;
+ lblOrigin_.setOrigin("Origin:", originUrl);
+ lblOrigin_.setVisible(true);
+ vcsSelect_.removeStyleName(RES.styles().vcsSelectExtraSpaced());
+
+ }
+ else if (vcs.equals(VCSConstants.SVN_ID))
+ {
+ lblOrigin_.setOrigin("Repo:",
+ vcsContext_.getSvnRepositoryRoot());
+ lblOrigin_.setVisible(true);
+ vcsSelect_.removeStyleName(RES.styles().vcsSelectExtraSpaced());
+ }
+ else // vcs.equals(VCSConstants.NO_ID)
+ {
+ lblOrigin_.setOrigin("", "");
+ lblOrigin_.setVisible(false);
+ vcsSelect_.addStyleName(RES.styles().vcsSelectExtraSpaced());
+ }
+ }
+
+
+ private void confirmGitRepo(final Command onConfirmed)
+ {
+ final ProgressIndicator indicator = getProgressIndicator();
+ indicator.onProgress("Checking for git repository...");
+
+ final String projDir =
+ session_.getSessionInfo().getActiveProjectDir().getPath();
+
+ server_.gitHasRepo(projDir, new ServerRequestCallback<Boolean>() {
+
+ @Override
+ public void onResponseReceived(Boolean result)
+ {
+ indicator.onCompleted();
+
+ if (result)
+ {
+ onConfirmed.execute();
+ }
+ else
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.QUESTION,
+ "Confirm New Git Repository",
+ "Do you want to initialize a new git repository " +
+ "for this project?",
+ false,
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ server_.gitInitRepo(
+ projDir,
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ onConfirmed.execute();
+ }
+ @Override
+ public void onFailure()
+ {
+ setVcsSelection(VCSConstants.NO_ID);
+ }
+ });
+
+ }
+ },
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ setVcsSelection(VCSConstants.NO_ID);
+ indicator.onCompleted();
+ }
+
+ },
+ true);
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ setVcsSelection(VCSConstants.NO_ID);
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+
+ }
+
+ private class OriginLabel extends Composite
+ {
+ public OriginLabel()
+ {
+ HorizontalPanel panel = new HorizontalPanel();
+ lblCaption_ = new Label();
+ panel.add(lblCaption_);
+
+ lblOrigin_ = new Label();
+ lblOrigin_.addStyleName(RES.styles().vcsOriginUrl());
+ panel.add(lblOrigin_);
+
+ initWidget(panel);
+
+
+ }
+
+ public void setOrigin(String caption, String origin)
+ {
+ lblCaption_.setText(caption);
+ lblOrigin_.setText(origin);
+
+ if (origin.equals(NO_REMOTE_ORIGIN))
+ lblOrigin_.addStyleName(RES.styles().vcsNoOriginUrl());
+ else
+ lblOrigin_.removeStyleName(RES.styles().vcsNoOriginUrl());
+ }
+
+ private Label lblCaption_;
+ private Label lblOrigin_;
+ }
+
+ private final Session session_;
+ private final GlobalDisplay globalDisplay_;
+ private final GitServerOperations server_;
+
+ private SelectWidget vcsSelect_;
+ private OriginLabel lblOrigin_;
+ private RProjectVcsContext vcsContext_;
+
+ private static final String NONE = "(None)";
+
+ private static final String NO_REMOTE_ORIGIN ="None";
+
+ private static final ProjectPreferencesDialogResources RES =
+ ProjectPreferencesDialogResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsCustomPanel.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsCustomPanel.java
new file mode 100644
index 0000000..603417f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsCustomPanel.java
@@ -0,0 +1,62 @@
+/*
+ * BuildToolsCustomPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+
+
+public class BuildToolsCustomPanel extends BuildToolsPanel
+{
+ public BuildToolsCustomPanel()
+ {
+ pathSelector_ = new FileSelector("Custom build script:");
+ pathSelector_.setTextWidth("250px");
+ add(pathSelector_);
+ }
+
+ @Override
+ void load(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ pathSelector_.setText(config.getCustomScriptPath());
+ }
+
+ @Override
+ void save(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setCustomScriptPath(pathSelector_.getText());
+ }
+
+ @Override
+ boolean validate()
+ {
+ boolean valid = pathSelector_.getText().length() != 0;
+ if (!valid)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Script Not Specified",
+ "You must specify a path to the custom build script.");
+ }
+
+ return valid;
+ }
+
+ private PathSelector pathSelector_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsMakefilePanel.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsMakefilePanel.java
new file mode 100644
index 0000000..c8576ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsMakefilePanel.java
@@ -0,0 +1,62 @@
+/*
+ * BuildToolsMakefilePanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+
+
+public class BuildToolsMakefilePanel extends BuildToolsPanel
+{
+ public BuildToolsMakefilePanel()
+ {
+ pathSelector_ = new DirectorySelector("Makefile directory:");
+ add(pathSelector_);
+
+ txtMakefileArgs_ = new AdditionalArguments("Additional arguments:");
+ Style style = txtMakefileArgs_.getElement().getStyle();
+ style.setMarginTop(2, Unit.PX);
+ add(txtMakefileArgs_);
+
+ }
+
+ @Override
+ void load(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ pathSelector_.setText(config.getMakefilePath());
+
+ txtMakefileArgs_.setText(options.getBuildOptions().getMakefileArgs());
+
+ }
+
+ @Override
+ void save(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setMakefilePath(pathSelector_.getText());
+
+ options.getBuildOptions().setMakefileArgs(
+ txtMakefileArgs_.getText().trim());
+ }
+
+ private PathSelector pathSelector_;
+ private AdditionalArguments txtMakefileArgs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPackagePanel.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPackagePanel.java
new file mode 100644
index 0000000..d53d338
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPackagePanel.java
@@ -0,0 +1,213 @@
+/*
+ * BuildToolsPackagePanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.HelpLink;
+import org.rstudio.studio.client.common.PackagesHelpLink;
+import org.rstudio.studio.client.projects.model.RProjectBuildOptions;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesDialogResources;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.inject.Inject;
+
+
+public class BuildToolsPackagePanel extends BuildToolsPanel
+{
+ public BuildToolsPackagePanel()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ ProjectPreferencesDialogResources RES =
+ ProjectPreferencesDialogResources.INSTANCE;
+
+ pathSelector_ = new DirectorySelector("Package directory:");
+ pathSelector_.getElement().getStyle().setMarginBottom(10, Unit.PX);
+ add(pathSelector_);
+ pathSelector_.addValueChangeHandler(new ValueChangeHandler<String>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ if (pathSelector_.getText().equals(
+ workbenchContext_.getActiveProjectDir().getPath()))
+ {
+ pathSelector_.setText("");
+ }
+ }
+
+ });
+
+ chkUseDevtools_ = checkBox(
+ "Use devtools package functions if available");
+ chkUseDevtools_.addStyleName(RES.styles().buildToolsDevtools());
+ add(chkUseDevtools_);
+
+ roxygenizePanel_ = new VerticalPanel();
+ roxygenizePanel_.addStyleName(RES.styles().buildToolsRoxygenize());
+ HorizontalPanel rocletPanel = new HorizontalPanel();
+ chkUseRoxygen_ = checkBox("Generate documentation with Roxygen");
+ rocletPanel.add(chkUseRoxygen_);
+ btnConfigureRoxygen_ = new ThemedButton("Configure...");
+ btnConfigureRoxygen_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ new BuildToolsRoxygenOptionsDialog(
+ roxygenOptions_,
+ new OperationWithInput<BuildToolsRoxygenOptions>() {
+
+ @Override
+ public void execute(BuildToolsRoxygenOptions input)
+ {
+ roxygenOptions_ = input;
+ chkUseRoxygen_.setValue(input.getRocletRd() ||
+ input.getRocletCollate() ||
+ input.getRocletNamespace());
+
+ }
+
+ }).showModal();
+
+ }
+ });
+ rocletPanel.add(btnConfigureRoxygen_);
+ roxygenizePanel_.add(rocletPanel);
+ add(roxygenizePanel_);
+
+
+ add(installAdditionalArguments_ = new AdditionalArguments(
+ new SafeHtmlBuilder().appendHtmlConstant(
+ "Build and Reload — R CMD INSTALL additional options:").toSafeHtml()));
+
+ add(checkAdditionalArguments_ = new AdditionalArguments(
+ new SafeHtmlBuilder().appendHtmlConstant(
+ "Check Package — R CMD check additional options:").toSafeHtml()));
+
+ add(buildAdditionalArguments_ = new AdditionalArguments(
+ new SafeHtmlBuilder().appendHtmlConstant(
+ "Build Source Package — R CMD build additional options:").toSafeHtml()));
+
+ add(buildBinaryAdditionalArguments_ = new AdditionalArguments(
+ new SafeHtmlBuilder().appendHtmlConstant(
+ "Build Binary Package — R CMD INSTALL additional options:").toSafeHtml()));
+
+
+ HelpLink packagesHelpLink = new PackagesHelpLink();
+ packagesHelpLink.getElement().getStyle().setMarginTop(7, Unit.PX);
+ add(packagesHelpLink);
+ }
+
+ @Inject
+ public void initialize(WorkbenchContext workbenchContext)
+ {
+ workbenchContext_ = workbenchContext;
+ }
+
+ @Override
+ protected void provideDefaults()
+ {
+ installAdditionalArguments_.setText("--no-multiarch --with-keep.source");
+ chkUseDevtools_.setValue(true);
+ }
+
+ @Override
+ void load(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ pathSelector_.setText(config.getPackagePath());
+ installAdditionalArguments_.setText(config.getPackageInstallArgs());
+ buildAdditionalArguments_.setText(config.getPackageBuildArgs());
+ buildBinaryAdditionalArguments_.setText(config.getPackageBuildBinaryArgs());
+ checkAdditionalArguments_.setText(config.getPackageCheckArgs());
+
+ roxygenOptions_ = new BuildToolsRoxygenOptions(
+ config.getPackageRoxygenzieRd(),
+ config.getPackageRoxygenizeCollate(),
+ config.getPackageRoxygenizeNamespace(),
+ options.getBuildOptions().getAutoRogyginizeOptions());
+
+ boolean showRoxygenize = config.hasPackageRoxygenize() ||
+ options.getBuildContext().isRoxygen2Installed();
+ roxygenizePanel_.setVisible(showRoxygenize);
+ chkUseDevtools_.setValue(config.getPackageUseDevtools());
+ chkUseRoxygen_.setValue(config.hasPackageRoxygenize());
+ chkUseRoxygen_.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ if (event.getValue())
+ {
+ if (!roxygenOptions_.hasActiveRoclet())
+ roxygenOptions_.setRocletRd(true);
+ btnConfigureRoxygen_.click();
+ }
+ else
+ {
+ roxygenOptions_.clearRoclets();
+ }
+ }
+ });
+ }
+
+ @Override
+ void save(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+ config.setPackageUseDevtools(chkUseDevtools_.getValue());
+ config.setPackagePath(pathSelector_.getText());
+ config.setPackageInstallArgs(installAdditionalArguments_.getText());
+ config.setPackageBuildArgs(buildAdditionalArguments_.getText());
+ config.setPackageBuildBinaryArgs(buildBinaryAdditionalArguments_.getText());
+ config.setPackageCheckArgs(checkAdditionalArguments_.getText());
+ config.setPackageRoxygenize(roxygenOptions_.getRocletRd(),
+ roxygenOptions_.getRocletCollate(),
+ roxygenOptions_.getRocletNamespace());
+ RProjectBuildOptions buildOptions = options.getBuildOptions();
+ buildOptions.setAutoRoxyginizeOptions(
+ roxygenOptions_.getAutoRoxygenize());
+ }
+
+ private PathSelector pathSelector_;
+
+ private AdditionalArguments installAdditionalArguments_;
+ private AdditionalArguments buildAdditionalArguments_;
+ private AdditionalArguments buildBinaryAdditionalArguments_;
+ private AdditionalArguments checkAdditionalArguments_;
+
+ private BuildToolsRoxygenOptions roxygenOptions_;
+
+ private VerticalPanel roxygenizePanel_;
+ private CheckBox chkUseRoxygen_;
+ private CheckBox chkUseDevtools_;
+ private ThemedButton btnConfigureRoxygen_;
+
+ private WorkbenchContext workbenchContext_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPanel.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPanel.java
new file mode 100644
index 0000000..884da3e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsPanel.java
@@ -0,0 +1,227 @@
+/*
+ * BuildToolsPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesDialogResources;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class BuildToolsPanel extends VerticalPanel
+{
+ public BuildToolsPanel()
+ {
+ }
+
+ abstract void load(RProjectOptions options);
+ abstract void save(RProjectOptions options);
+
+ protected void provideDefaults()
+ {
+ }
+
+ boolean validate()
+ {
+ return true;
+ }
+
+ protected abstract class PathSelector extends TextBoxWithButton
+ {
+ public PathSelector(String label,
+ final String emptyLabel)
+ {
+ super(label, emptyLabel, "Browse...", null);
+ }
+
+
+ protected String getProjectPath(FileSystemItem projDir,
+ FileSystemItem path)
+ {
+ String proj = projDir.getPath();
+ if (path.getPath().startsWith(proj + "/"))
+ {
+ return path.getPath().substring(proj.length() + 1);
+ }
+ else
+ {
+ return path.getPath();
+ }
+ }
+ }
+
+ protected class DirectorySelector extends PathSelector
+ {
+ public DirectorySelector(String label)
+ {
+ super(label, "(Project Root)");
+
+ // allow user to clear out a value
+ setReadOnly(false);
+
+ getTextBox().addChangeHandler(new ChangeHandler() {
+
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ if (getTextBox().getText().length() == 0)
+ getTextBox().setText("(Project Root)");
+ }
+
+ });
+
+ addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ final FileSystemItem projDir = RStudioGinjector.INSTANCE.
+ getSession().getSessionInfo().getActiveProjectDir();
+
+ RStudioGinjector.INSTANCE.getFileDialogs().chooseFolder(
+ "Choose Directory",
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext(),
+ projDir,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ setText(getProjectPath(projDir, input));
+ }
+ });
+ }
+ });
+ }
+
+ // allow user to set the value to empty string
+ @Override
+ public String getText()
+ {
+ if (getTextBox().getText().trim().isEmpty())
+ return "";
+ else
+ return super.getText();
+ }
+ }
+
+ protected class FileSelector extends PathSelector
+ {
+ public FileSelector(String label)
+ {
+ super(label, "(None)");
+
+ addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ final FileSystemItem projDir = RStudioGinjector.INSTANCE.
+ getSession().getSessionInfo().getActiveProjectDir();
+
+ RStudioGinjector.INSTANCE.getFileDialogs().openFile(
+ "Choose File",
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext(),
+ projDir,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ setText(getProjectPath(projDir, input));
+ }
+ });
+ }
+ });
+ }
+
+
+ }
+
+ protected class AdditionalArguments extends Composite
+ {
+ AdditionalArguments(String label)
+ {
+ this(new Label(label));
+ }
+
+ AdditionalArguments(SafeHtml label)
+ {
+ this(new HTML(label));
+ }
+
+ AdditionalArguments(Widget captionWidget)
+ {
+ VerticalPanel panel = new VerticalPanel();
+ panel.addStyleName(RES.styles().buildToolsAdditionalArguments());
+
+
+ panel.add(captionWidget);
+
+ textBox_ = new TextBox();
+ panel.add(textBox_);
+
+ initWidget(panel);
+ }
+
+ public void setText(String text)
+ {
+ textBox_.setText(text);
+ }
+
+ public String getText()
+ {
+ return textBox_.getText().trim();
+ }
+
+ private final TextBox textBox_;
+ }
+
+ protected CheckBox checkBox(String caption)
+ {
+ CheckBox chk = new CheckBox(caption);
+ chk.addStyleName(RES.styles().buildToolsCheckBox());
+ return chk;
+ }
+
+ protected static ProjectPreferencesDialogResources RES =
+ ProjectPreferencesDialogResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptions.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptions.java
new file mode 100644
index 0000000..88c8f1b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptions.java
@@ -0,0 +1,84 @@
+/*
+ * BuildToolsRoxygenOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import org.rstudio.studio.client.projects.model.RProjectAutoRoxygenizeOptions;
+
+public class BuildToolsRoxygenOptions
+{
+ public BuildToolsRoxygenOptions(boolean rocletRd,
+ boolean rocletCollate,
+ boolean rocletNamespace,
+ RProjectAutoRoxygenizeOptions autoRoxygenize)
+ {
+ rocletRd_ = rocletRd;
+ rocletCollate_ = rocletCollate;
+ rocletNamespace_ = rocletNamespace;
+ autoRoxygenize_ = autoRoxygenize;
+ }
+
+ public boolean hasActiveRoclet()
+ {
+ return getRocletRd() || getRocletCollate() || getRocletNamespace();
+ }
+
+ public void clearRoclets()
+ {
+ setRocletRd(false);
+ setRocletCollate(false);
+ setRocletNamespace(false);
+ }
+
+ public boolean getRocletRd()
+ {
+ return rocletRd_;
+ }
+
+ public void setRocletRd(boolean rocletRd)
+ {
+ rocletRd_ = rocletRd;
+ }
+
+ public boolean getRocletCollate()
+ {
+ return rocletCollate_;
+ }
+
+ public void setRocletCollate(boolean rocletCollate)
+ {
+ rocletCollate_ = rocletCollate;
+ }
+
+ public boolean getRocletNamespace()
+ {
+ return rocletNamespace_;
+ }
+
+ public void setRocletNamespace(boolean rocletNamespace)
+ {
+ rocletNamespace_ = rocletNamespace;
+ }
+
+ public RProjectAutoRoxygenizeOptions getAutoRoxygenize()
+ {
+ return autoRoxygenize_;
+ }
+
+ private boolean rocletRd_;
+ private boolean rocletCollate_;
+ private boolean rocletNamespace_;
+ private final RProjectAutoRoxygenizeOptions autoRoxygenize_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.java
new file mode 100644
index 0000000..ef267e4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.java
@@ -0,0 +1,95 @@
+/*
+ * BuildToolsRoxygenOptionsDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.projects.model.RProjectAutoRoxygenizeOptions;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class BuildToolsRoxygenOptionsDialog extends ModalDialog<BuildToolsRoxygenOptions>
+{
+ public interface Binder extends UiBinder<Widget,
+ BuildToolsRoxygenOptionsDialog>
+ {
+ }
+
+ public BuildToolsRoxygenOptionsDialog(
+ BuildToolsRoxygenOptions options,
+ OperationWithInput<BuildToolsRoxygenOptions> operation)
+ {
+ super("Roxygen Options", operation);
+ mainWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ chkRocletRd_.setValue(options.getRocletRd());
+ chkRocletCollate_.setValue(options.getRocletCollate());
+ chkRocletNamespace_.setValue(options.getRocletNamespace());
+ RProjectAutoRoxygenizeOptions runOptions = options.getAutoRoxygenize();
+ chkRunForCheckPackage_.setValue(runOptions.getRunOnCheck());
+ chkRunForBuildPackage_.setValue(runOptions.getRunOnPackageBuilds());
+ chkRunForBuildAndReload_.setValue(runOptions.getRunOnBuildAndReload());
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return mainWidget_;
+ }
+
+ @Override
+ protected BuildToolsRoxygenOptions collectInput()
+ {
+ return new BuildToolsRoxygenOptions(
+ chkRocletRd_.getValue(),
+ chkRocletCollate_.getValue(),
+ chkRocletNamespace_.getValue(),
+ RProjectAutoRoxygenizeOptions.create(
+ chkRunForCheckPackage_.getValue(),
+ chkRunForBuildPackage_.getValue(),
+ chkRunForBuildAndReload_.getValue()));
+ }
+
+
+ @Override
+ protected boolean validate(BuildToolsRoxygenOptions input)
+ {
+ return true;
+ }
+
+
+ @UiField
+ CheckBox chkRocletRd_;
+ @UiField
+ CheckBox chkRocletCollate_;
+ @UiField
+ CheckBox chkRocletNamespace_;
+ @UiField
+ CheckBox chkRunForCheckPackage_;
+ @UiField
+ CheckBox chkRunForBuildPackage_;
+ @UiField
+ CheckBox chkRunForBuildAndReload_;
+
+
+ private Widget mainWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.ui.xml
new file mode 100644
index 0000000..64ec965
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/BuildToolsRoxygenOptionsDialog.ui.xml
@@ -0,0 +1,33 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .panel {
+ width: 300px;
+ }
+
+ .topParagraph {
+ margin-top: 0px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.panel}">
+
+ <g:Label text="Use roxygen to generate:"/>
+ <g:CheckBox ui:field="chkRocletRd_" text="Rd files"/> <br/>
+ <g:CheckBox ui:field="chkRocletCollate_" text="Collate field"/> <br/>
+ <g:CheckBox ui:field="chkRocletNamespace_" text="NAMESPACE file"/> <br/>
+
+ <br/>
+
+ <g:Label text="Automatically roxygenize when running:"/>
+ <g:CheckBox ui:field="chkRunForCheckPackage_" text="R CMD check"/> <br/>
+ <g:CheckBox ui:field="chkRunForBuildPackage_" text="Source and binary package builds"/> <br/>
+ <g:CheckBox ui:field="chkRunForBuildAndReload_" text="Build & Reload"/> <br/>
+
+ <br/>
+
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/ProjectBuildToolsPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/ProjectBuildToolsPreferencesPane.java
new file mode 100644
index 0000000..c890f98
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/buildtools/ProjectBuildToolsPreferencesPane.java
@@ -0,0 +1,184 @@
+/*
+ * ProjectBuildToolsPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.projects.ui.prefs.buildtools;
+
+import java.util.HashMap;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.projects.model.RProjectConfig;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesDialogResources;
+import org.rstudio.studio.client.projects.ui.prefs.ProjectPreferencesPane;
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+import com.google.inject.Inject;
+
+public class ProjectBuildToolsPreferencesPane extends ProjectPreferencesPane
+{
+ @Inject
+ public ProjectBuildToolsPreferencesPane(final Session session,
+ GlobalDisplay globalDisplay)
+ {
+ session_ = session;
+ globalDisplay_ = globalDisplay;
+
+ buildTypeSelect_ = new BuildTypeSelectWidget();
+ spaced(buildTypeSelect_);
+ add(buildTypeSelect_);
+
+ buildToolsPanel_ = new SimplePanel();
+ buildToolsPanel_.addStyleName(RES.styles().buildToolsPanel());
+ buildToolsPanel_.getElement().getStyle().setMarginLeft(
+ BrowseCap.isFirefox() ? 1 : 4, Unit.PX);
+ add(buildToolsPanel_);
+
+ packagePanel_ = new BuildToolsPackagePanel();
+ buildToolsPanels_.put(RProjectConfig.BUILD_TYPE_PACKAGE,
+ packagePanel_);
+
+ buildToolsPanels_.put(RProjectConfig.BUILD_TYPE_MAKEFILE,
+ new BuildToolsMakefilePanel());
+
+ buildToolsPanels_.put(RProjectConfig.BUILD_TYPE_CUSTOM,
+ new BuildToolsCustomPanel());
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return RES.iconBuild();
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Build Tools";
+ }
+
+ @Override
+ protected void initialize(RProjectOptions options)
+ {
+ initialConfig_ = options.getConfig();
+
+ String buildType = initialConfig_.getBuildType();
+ buildTypeSelect_.setValue(initialConfig_.getBuildType());
+
+ for (BuildToolsPanel panel : buildToolsPanels_.values())
+ panel.load(options);
+
+ manageBuildToolsPanel(buildType);
+
+ // if the initial type isn't package then we need to provide package
+ // defaults if the user switches to a package
+ providePackageBuildTypeDefaults_ =
+ !buildType.equals(RProjectConfig.BUILD_TYPE_PACKAGE);
+ }
+
+ @Override
+ public boolean onApply(RProjectOptions options)
+ {
+ RProjectConfig config = options.getConfig();
+
+ config.setBuildType(buildTypeSelect_.getValue());
+
+ for (BuildToolsPanel panel : buildToolsPanels_.values())
+ panel.save(options);
+
+ // require reload if the build type or roxygen settings changed
+ return !initialConfig_.getBuildType().equals(buildTypeSelect_.getValue());
+ }
+
+
+ @Override
+ public boolean validate()
+ {
+ String buildType = buildTypeSelect_.getValue();
+ if (buildToolsPanels_.containsKey(buildType))
+ return buildToolsPanels_.get(buildType).validate();
+ else
+ return true;
+ }
+
+ private void manageBuildToolsPanel(String buildType)
+ {
+ if (buildToolsPanels_.containsKey(buildType))
+ buildToolsPanel_.setWidget(buildToolsPanels_.get(buildType));
+ else
+ buildToolsPanel_.setWidget(null);
+ }
+
+
+ private class BuildTypeSelectWidget extends SelectWidget
+ {
+ public BuildTypeSelectWidget()
+ {
+ super("Project build tools:",
+ new String[]{"(" + RProjectConfig.BUILD_TYPE_NONE + ")",
+ RProjectConfig.BUILD_TYPE_PACKAGE,
+ RProjectConfig.BUILD_TYPE_MAKEFILE,
+ RProjectConfig.BUILD_TYPE_CUSTOM},
+ new String[]{RProjectConfig.BUILD_TYPE_NONE,
+ RProjectConfig.BUILD_TYPE_PACKAGE,
+ RProjectConfig.BUILD_TYPE_MAKEFILE,
+ RProjectConfig.BUILD_TYPE_CUSTOM},
+ false,
+ true,
+ false);
+
+ addChangeHandler(new ChangeHandler() {
+
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ String buildType = getValue();
+ if (buildType.equals(RProjectConfig.BUILD_TYPE_PACKAGE) &&
+ providePackageBuildTypeDefaults_)
+ {
+ providePackageBuildTypeDefaults_ = false;
+ packagePanel_.provideDefaults();
+ }
+
+ manageBuildToolsPanel(buildType);
+ }
+ });
+ }
+ }
+
+ private boolean providePackageBuildTypeDefaults_ = false;
+ private final BuildToolsPanel packagePanel_;
+
+ @SuppressWarnings("unused")
+ private final Session session_;
+ @SuppressWarnings("unused")
+ private final GlobalDisplay globalDisplay_;
+
+ private RProjectConfig initialConfig_;
+ private BuildTypeSelectWidget buildTypeSelect_;
+
+ private SimplePanel buildToolsPanel_;
+ private HashMap<String, BuildToolsPanel> buildToolsPanels_ =
+ new HashMap<String, BuildToolsPanel>();
+
+ private static final ProjectPreferencesDialogResources RES =
+ ProjectPreferencesDialogResources.INSTANCE;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/iconBuild.png b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/iconBuild.png
new file mode 100644
index 0000000..0c7aee8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/projects/ui/prefs/iconBuild.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/server/Bool.java b/src/gwt/src/org/rstudio/studio/client/server/Bool.java
new file mode 100644
index 0000000..4c8d196
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/Bool.java
@@ -0,0 +1,29 @@
+/*
+ * Bool.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Bool extends JavaScriptObject
+{
+ protected Bool()
+ {
+ }
+
+ public final native boolean getValue() /*-{
+ return this.value;
+ }-*/;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/server/ClientException.java b/src/gwt/src/org/rstudio/studio/client/server/ClientException.java
new file mode 100644
index 0000000..11bb238
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/ClientException.java
@@ -0,0 +1,105 @@
+/*
+ * ClientException.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+import org.rstudio.core.client.StringUtil;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class ClientException extends JavaScriptObject
+{
+ public static final ClientException create(Throwable e)
+ {
+ JsArray<StackElement> stack = JsArray.createArray().cast();
+ for (StackTraceElement element : e.getStackTrace())
+ stack.push(StackElement.create(element));
+
+ return create(e.getMessage(),
+ GWT.getPermutationStrongName(),
+ stack);
+ }
+
+ public static native final ClientException create(
+ String message,
+ String strongName,
+ JsArray<StackElement> stack) /*-{
+ var ex = new Object();
+ ex.message = message;
+ ex.strong_name = strongName;
+ ex.stack = stack;
+ return ex;
+ }-*/;
+
+ protected ClientException()
+ {
+ }
+
+ public native final String getMessage() /*-{
+ return this.message;
+ }-*/;
+
+ public native final String getStrongName() /*-{
+ return this.strong_name;
+ }-*/;
+
+ public native final JsArray<StackElement> getStack() /*-{
+ return this.stack;
+ }-*/;
+
+ public static class StackElement extends JavaScriptObject
+ {
+ public static final StackElement create(StackTraceElement element)
+ {
+ return create(StringUtil.notNull(element.getFileName()),
+ StringUtil.notNull(element.getClassName()),
+ StringUtil.notNull(element.getMethodName()),
+ element.getLineNumber());
+ }
+
+ public static native final StackElement create(String fileName,
+ String className,
+ String methodName,
+ int lineNumber) /*-{
+ var item = new Object();
+ item.file_name = fileName;
+ item.class_name = className;
+ item.method_name = methodName;
+ item.line_number = lineNumber;
+ return item;
+ }-*/;
+
+ protected StackElement()
+ {
+ }
+
+ public native final String getFileName() /*-{
+ return this.file_name;
+ }-*/;
+
+ public native final String getClassName() /*-{
+ return this.class_name;
+ }-*/;
+
+ public native final String getMethodName() /*-{
+ return this.method_name;
+ }-*/;
+
+ public native final String getLineNumber() /*-{
+ return this.line_number;
+ }-*/;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/LogEntryType.java b/src/gwt/src/org/rstudio/studio/client/server/LogEntryType.java
new file mode 100644
index 0000000..01faa76
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/LogEntryType.java
@@ -0,0 +1,22 @@
+/*
+ * LogEntryType.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+public class LogEntryType
+{
+ public static final int ERROR = 0;
+ public static final int WARNING = 1;
+ public static final int INFO = 2;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/Server.java b/src/gwt/src/org/rstudio/studio/client/server/Server.java
new file mode 100644
index 0000000..d5ca557
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/Server.java
@@ -0,0 +1,45 @@
+/*
+ * Server.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server;
+
+import org.rstudio.studio.client.application.model.ApplicationServerOperations;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.common.shiny.model.ShinyServerOperations;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewServerOperations;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+
+public interface Server extends ApplicationServerOperations,
+ WorkbenchServerOperations,
+ HTMLPreviewServerOperations,
+ ShinyServerOperations,
+ ShinyAppsServerOperations
+
+
+{
+ /**
+ * Add an entry to the server's log.
+ *
+ * @param logEntryType see LogEntryType.ERROR, ec.
+ * @param logEntry log entry (should not include newlines)
+ * @param requestCallback handle errors
+ */
+ void log(int logEntryType,
+ String logEntry,
+ ServerRequestCallback<Void> requestCallback);
+
+ void logException(ClientException e,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/ServerDataSource.java b/src/gwt/src/org/rstudio/studio/client/server/ServerDataSource.java
new file mode 100644
index 0000000..5d3272c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/ServerDataSource.java
@@ -0,0 +1,20 @@
+/*
+ * ServerDataSource.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+public interface ServerDataSource<T>
+{
+ void requestData(ServerRequestCallback<T> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/ServerError.java b/src/gwt/src/org/rstudio/studio/client/server/ServerError.java
new file mode 100644
index 0000000..037cae2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/ServerError.java
@@ -0,0 +1,57 @@
+/*
+ * ServerError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server;
+
+import com.google.gwt.json.client.JSONValue;
+
+public interface ServerError
+{
+ // method succeeded
+ public static final int SUCCESS = 0 ;
+
+ // couldn't connect (method did not execute)
+ public static final int CONNECTION = 1 ;
+
+ // unavailable (method did not execute)
+ public static final int UNAVAILABLE = 2;
+
+ // unauthorized (method did not execute)
+ public static final int UNAUTHORIZED = 3;
+
+ // protocol (method did not executne)
+ public static final int PROTOCOL = 4 ;
+
+ // error during processing (method failed in known state)
+ public static final int EXECUTION = 5 ;
+
+ // rpc transmission errors (method may have executed)
+ public static final int TRANSMISSION = 6;
+
+ // error type
+ int getCode() ;
+
+ // error message
+ String getMessage();
+
+ // underlying error
+ ServerErrorCause getCause() ;
+
+ // message to display to the end-user
+ String getUserMessage();
+
+ JSONValue getClientInfo();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/ServerErrorCause.java b/src/gwt/src/org/rstudio/studio/client/server/ServerErrorCause.java
new file mode 100644
index 0000000..a552e2f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/ServerErrorCause.java
@@ -0,0 +1,39 @@
+/*
+ * ServerErrorCause.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+public class ServerErrorCause
+{
+ public ServerErrorCause(int code, String category, String message)
+ {
+ code_ = code;
+ category_ = category;
+ message_ = message;
+ }
+
+ public int getCode() { return code_; }
+ public String getCategory() { return category_; }
+ public String getMessage() { return message_; }
+
+ @Override
+ public String toString()
+ {
+ return code_ + ": [" + category_ + "] " + message_;
+ }
+
+ private final int code_;
+ private final String category_;
+ private final String message_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/ServerRequestCallback.java b/src/gwt/src/org/rstudio/studio/client/server/ServerRequestCallback.java
new file mode 100644
index 0000000..a584898
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/ServerRequestCallback.java
@@ -0,0 +1,31 @@
+/*
+ * ServerRequestCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server;
+
+public abstract class ServerRequestCallback<T>
+{
+ public void onResponseReceived(T response)
+ {
+ }
+
+ public abstract void onError(ServerError error);
+
+ public void cancel() { cancelled_ = true; }
+ public boolean cancelled() { return cancelled_; }
+
+ private boolean cancelled_ = false;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/server/Void.java b/src/gwt/src/org/rstudio/studio/client/server/Void.java
new file mode 100644
index 0000000..4f338c9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/Void.java
@@ -0,0 +1,32 @@
+/*
+ * Void.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+// Type returned to ServerRequestCallback::onResponseReceived when
+// the remote method conceptually has a "void" return type
+
+public class Void extends JavaScriptObject
+{
+ public static final native Void create() /*-{
+ return new Object();
+ }-*/;
+
+ protected Void()
+ {
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/VoidServerRequestCallback.java b/src/gwt/src/org/rstudio/studio/client/server/VoidServerRequestCallback.java
new file mode 100644
index 0000000..5765596
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/VoidServerRequestCallback.java
@@ -0,0 +1,61 @@
+/*
+ * VoidServerRequestCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.widget.ProgressIndicator;
+
+public class VoidServerRequestCallback extends ServerRequestCallback<Void>
+{
+ public VoidServerRequestCallback()
+ {
+ this(null);
+ }
+
+ public VoidServerRequestCallback(ProgressIndicator progress)
+ {
+ progress_ = progress ;
+ }
+
+ public void onResponseReceived(Void response)
+ {
+ if (progress_ != null)
+ progress_.onCompleted();
+
+ onSuccess();
+ }
+
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+
+ if (progress_ != null)
+ progress_.onError(error.getUserMessage());
+
+ onFailure();
+ }
+
+ protected void onSuccess()
+ {
+
+ }
+
+ protected void onFailure()
+ {
+
+ }
+
+ private ProgressIndicator progress_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/AsyncCompletion.java b/src/gwt/src/org/rstudio/studio/client/server/remote/AsyncCompletion.java
new file mode 100644
index 0000000..c453531
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/AsyncCompletion.java
@@ -0,0 +1,33 @@
+/*
+ * AsyncCompletion.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.jsonrpc.RpcResponse;
+
+public class AsyncCompletion extends JavaScriptObject
+{
+ protected AsyncCompletion()
+ {
+ }
+
+ public final native String getHandle() /*-{
+ return this.handle;
+ }-*/;
+
+ public final native RpcResponse getResponse() /*-{
+ return this.response;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEvent.java b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEvent.java
new file mode 100644
index 0000000..8c2bf81
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEvent.java
@@ -0,0 +1,118 @@
+/*
+ * ClientEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+class ClientEvent extends JavaScriptObject
+{
+ public static final String Busy = "busy";
+ public static final String ConsolePrompt = "console_prompt";
+ public static final String ConsoleOutput = "console_output" ;
+ public static final String ConsoleError = "console_error";
+ public static final String ConsoleWritePrompt = "console_write_prompt";
+ public static final String ConsoleWriteInput = "console_write_input";
+ public static final String ShowErrorMessage = "show_error_message";
+ public static final String ShowHelp = "show_help" ;
+ public static final String BrowseUrl = "browse_url";
+ public static final String ShowEditor = "show_editor";
+ public static final String ChooseFile = "choose_file";
+ public static final String AbendWarning = "abend_warning";
+ public static final String Quit = "quit";
+ public static final String Suicide = "suicide";
+ public static final String FileChanged = "file_changed";
+ public static final String WorkingDirChanged = "working_dir_changed";
+ public static final String PlotsStateChanged = "plots_state_changed";
+ public static final String ViewData = "view_data";
+ public static final String PackageStatusChanged = "package_status_changed";
+ public static final String InstalledPackagesChanged = "installed_packages_changed";
+ public static final String Locator = "locator";
+ public static final String ConsoleResetHistory = "console_reset_history";
+ public static final String SessionSerialization = "session_serialization";
+ public static final String HistoryEntriesAdded = "history_entries_added";
+ public static final String QuotaStatus = "quota_status";
+ public static final String FileEdit = "file_edit";
+ public static final String ShowContent = "show_content";
+ public static final String ShowData = "show_data";
+ public static final String AsyncCompletion = "async_completion";
+ public static final String SaveActionChanged = "save_action_changed";
+ public static final String ShowWarningBar = "show_warning_bar";
+ public static final String OpenProjectError = "open_project_error";
+ public static final String VcsRefresh = "vcs_refresh";
+ public static final String AskPass = "ask_pass";
+ public static final String ConsoleProcessOutput = "console_process_output";
+ public static final String ConsoleProcessExit = "console_process_exit";
+ public static final String ListChanged = "list_changed";
+ public static final String UiPrefsChanged = "ui_prefs_changed";
+ public static final String HandleUnsavedChanges = "handle_unsaved_changes";
+ public static final String PosixShellOutput = "posix_shell_output";
+ public static final String PosixShellExit = "posix_shell_exit";
+ public static final String ConsoleProcessPrompt = "console_process_prompt";
+ public static final String ConsoleProcessCreated = "console_process_created";
+ public static final String HTMLPreviewStartedEvent = "html_preview_started_event";
+ public static final String HTMLPreviewOutputEvent = "html_preview_output_event";
+ public static final String HTMLPreviewCompletedEvent = "html_preview_completed_event";
+ public static final String CompilePdfStartedEvent = "compile_pdf_started_event";
+ public static final String CompilePdfOutputEvent = "compile_pdf_output_event";
+ public static final String CompilePdfErrorsEvent = "compile_pdf_errors_event";
+ public static final String CompilePdfCompletedEvent = "compile_pdf_completed_event";
+ public static final String SynctexEditFile = "synctex_edit_file";
+ public static final String FindResult = "find_result";
+ public static final String FindOperationEnded = "find_operation_ended";
+ public static final String RPubsUploadStatus = "rpubs_upload_status";
+ public static final String BuildStarted = "build_started";
+ public static final String BuildOutput = "build_output";
+ public static final String BuildCompleted = "build_completed";
+ public static final String BuildErrors = "build_errors";
+ public static final String DirectoryNavigate = "directory_navigate";
+ public static final String DeferredInitCompleted = "deferred_init_completed";
+ public static final String PlotsZoomSizeChanged = "plots_zoom_size_changed";
+ public static final String SourceCppStarted = "source_cpp_started";
+ public static final String SourceCppCompleted = "source_cpp_completed";
+ public static final String LoadedPackageUpdates = "loaded_package_updates";
+ public static final String ActivatePane = "activate_pane";
+ public static final String ShowPresentationPane = "show_presentation_pane";
+ public static final String EnvironmentRefresh = "environment_refresh";
+ public static final String ContextDepthChanged = "context_depth_changed";
+ public static final String EnvironmentAssigned = "environment_assigned";
+ public static final String EnvironmentRemoved = "environment_removed";
+ public static final String BrowserLineChanged = "browser_line_changed";
+ public static final String PackageLoaded = "package_loaded";
+ public static final String PackageUnloaded = "package_unloaded";
+ public static final String PresentationPaneRequestCompleted = "presentation_pane_request_completed";
+ public static final String UnhandledError = "unhandled_error";
+ public static final String ErrorHandlerChanged = "error_handler_changed";
+ public static final String ViewerNavigate = "viewer_navigate";
+ public static final String UpdateCheck = "update_check";
+ public static final String SourceExtendedTypeDetected = "source_extended_type_detected";
+ public static final String ShinyViewer = "shiny_viewer";
+ public static final String DebugSourceCompleted = "debug_source_completed";
+
+ protected ClientEvent()
+ {
+ }
+
+ public final native int getId() /*-{
+ return this.id;
+ }-*/;
+
+ public final native String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native <T> T getData() /*-{
+ return this.data;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventDispatcher.java b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventDispatcher.java
new file mode 100644
index 0000000..feea2ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventDispatcher.java
@@ -0,0 +1,555 @@
+/*
+ * ClientEventDispatcher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.application.events.*;
+import org.rstudio.studio.client.application.model.SaveAction;
+import org.rstudio.studio.client.application.model.SessionSerializationAction;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfErrorsEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfOutputEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfStartedEvent;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfResult;
+import org.rstudio.studio.client.common.console.ConsoleProcessCreatedEvent;
+import org.rstudio.studio.client.common.console.ServerConsoleOutputEvent;
+import org.rstudio.studio.client.common.console.ServerConsolePromptEvent;
+import org.rstudio.studio.client.common.console.ServerProcessExitEvent;
+import org.rstudio.studio.client.common.debugging.events.ErrorHandlerChangedEvent;
+import org.rstudio.studio.client.common.debugging.events.PackageLoadedEvent;
+import org.rstudio.studio.client.common.debugging.events.PackageUnloadedEvent;
+import org.rstudio.studio.client.common.debugging.events.UnhandledErrorEvent;
+import org.rstudio.studio.client.common.debugging.model.ErrorHandlerType;
+import org.rstudio.studio.client.common.debugging.model.UnhandledError;
+import org.rstudio.studio.client.common.rpubs.events.RPubsUploadStatusEvent;
+import org.rstudio.studio.client.common.synctex.events.SynctexEditFileEvent;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewCompletedEvent;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewOutputEvent;
+import org.rstudio.studio.client.htmlpreview.events.HTMLPreviewStartedEvent;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewResult;
+import org.rstudio.studio.client.projects.events.OpenProjectErrorEvent;
+import org.rstudio.studio.client.projects.model.OpenProjectError;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.shiny.events.ShinyApplicationStatusEvent;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.workbench.events.*;
+import org.rstudio.studio.client.workbench.model.*;
+import org.rstudio.studio.client.workbench.prefs.events.UiPrefsChangedEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildCompletedEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildErrorsEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildOutputEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildStartedEvent;
+import org.rstudio.studio.client.workbench.views.choosefile.events.ChooseFileEvent;
+import org.rstudio.studio.client.workbench.views.console.events.*;
+import org.rstudio.studio.client.workbench.views.console.model.ConsolePrompt;
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleResetHistory;
+import org.rstudio.studio.client.workbench.views.data.events.ViewDataEvent;
+import org.rstudio.studio.client.workbench.views.data.model.DataView;
+import org.rstudio.studio.client.workbench.views.edit.events.ShowEditorEvent;
+import org.rstudio.studio.client.workbench.views.edit.model.ShowEditorData;
+import org.rstudio.studio.client.workbench.views.environment.events.*;
+import org.rstudio.studio.client.workbench.views.environment.model.DebugSourceResult;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
+import org.rstudio.studio.client.workbench.views.history.events.HistoryEntriesAddedEvent;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindOperationEndedEvent;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindResultEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppCompletedEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppStartedEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.model.SourceCppState;
+import org.rstudio.studio.client.workbench.views.packages.events.InstalledPackagesChangedEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.LoadedPackageUpdatesEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.PackageStatusChangedEvent;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageStatus;
+import org.rstudio.studio.client.workbench.views.plots.events.LocatorEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsZoomSizeChangedEvent;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsState;
+import org.rstudio.studio.client.workbench.views.presentation.events.PresentationPaneRequestCompletedEvent;
+import org.rstudio.studio.client.workbench.views.presentation.events.ShowPresentationPaneEvent;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+import org.rstudio.studio.client.workbench.views.source.events.FileEditEvent;
+import org.rstudio.studio.client.workbench.views.source.events.ShowContentEvent;
+import org.rstudio.studio.client.workbench.views.source.events.ShowDataEvent;
+import org.rstudio.studio.client.workbench.views.source.events.SourceExtendedTypeDetectedEvent;
+import org.rstudio.studio.client.workbench.views.source.model.ContentItem;
+import org.rstudio.studio.client.workbench.views.source.model.DataItem;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.AskPassEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.viewer.events.ViewerNavigateEvent;
+
+import java.util.ArrayList;
+
+public class ClientEventDispatcher
+{
+ public ClientEventDispatcher(EventBus eventBus)
+ {
+ eventBus_ = eventBus;
+ }
+
+ public void enqueEventAsJso(JavaScriptObject event)
+ {
+ ClientEvent clientEvent = event.<ClientEvent>cast();
+ enqueEvent(clientEvent);
+ }
+
+ public void enqueEvent(ClientEvent event)
+ {
+ pendingEvents_.add(event);
+ if (pendingEvents_.size() == 1)
+ {
+ Scheduler.get().scheduleIncremental(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ final int MAX_EVENTS_AT_ONCE = 200;
+ for (int i = 0;
+ i < MAX_EVENTS_AT_ONCE && pendingEvents_.size() > 0;
+ i++)
+ {
+ ClientEvent currentEvent = pendingEvents_.remove(0);
+ dispatchEvent(currentEvent);
+ }
+ return pendingEvents_.size() > 0;
+ }
+ });
+ }
+ }
+
+ private void dispatchEvent(ClientEvent event)
+ {
+ String type = event.getType();
+ try
+ {
+ if (type.equals(ClientEvent.Busy))
+ {
+ boolean busy = event.<Bool>getData().getValue();
+ eventBus_.fireEvent(new BusyEvent(busy));
+ }
+ else if (type.equals(ClientEvent.ConsoleOutput))
+ {
+ String output = event.getData();
+ eventBus_.fireEvent(new ConsoleWriteOutputEvent(output));
+ }
+ else if (type.equals(ClientEvent.ConsoleError))
+ {
+ String error = event.getData();
+ eventBus_.fireEvent(new ConsoleWriteErrorEvent(error));
+ }
+ else if (type.equals(ClientEvent.ConsoleWritePrompt))
+ {
+ String prompt = event.getData();
+ eventBus_.fireEvent(new ConsoleWritePromptEvent(prompt));
+ }
+ else if (type.equals(ClientEvent.ConsoleWriteInput))
+ {
+ String input = event.getData();
+ eventBus_.fireEvent(new ConsoleWriteInputEvent(input));
+ }
+ else if (type.equals(ClientEvent.ConsolePrompt))
+ {
+ ConsolePrompt prompt = event.getData();
+ eventBus_.fireEvent(new ConsolePromptEvent(prompt));
+ }
+ else if (type.equals(ClientEvent.ShowEditor))
+ {
+ ShowEditorData data = event.getData();
+ eventBus_.fireEvent(new ShowEditorEvent(data));
+ }
+ else if (type.equals(ClientEvent.FileChanged))
+ {
+ FileChange fileChange = event.getData();
+ eventBus_.fireEvent(new FileChangeEvent(fileChange));
+ }
+ else if (type.equals(ClientEvent.WorkingDirChanged))
+ {
+ String path = event.getData();
+ eventBus_.fireEvent(new WorkingDirChangedEvent(path));
+ }
+ else if (type.equals(ClientEvent.ShowHelp))
+ {
+ String helpUrl = event.getData();
+ eventBus_.fireEvent(new ShowHelpEvent(helpUrl));
+ }
+ else if (type.equals(ClientEvent.ShowErrorMessage))
+ {
+ ErrorMessage errorMessage = event.getData();
+ eventBus_.fireEvent(new ShowErrorMessageEvent(errorMessage));
+ }
+ else if (type.equals(ClientEvent.ChooseFile))
+ {
+ boolean newFile = event.<Bool>getData().getValue();
+ eventBus_.fireEvent(new ChooseFileEvent(newFile));
+ }
+ else if (type.equals(ClientEvent.BrowseUrl))
+ {
+ BrowseUrlInfo urlInfo = event.getData();
+ eventBus_.fireEvent(new BrowseUrlEvent(urlInfo));
+ }
+ else if (type.equals(ClientEvent.PlotsStateChanged))
+ {
+ PlotsState plotsState = event.getData();
+ eventBus_.fireEvent(new PlotsChangedEvent(plotsState));
+ }
+ else if (type.equals(ClientEvent.ViewData))
+ {
+ DataView dataView = event.getData();
+ eventBus_.fireEvent(new ViewDataEvent(dataView));
+ }
+ else if (type.equals(ClientEvent.InstalledPackagesChanged))
+ {
+ eventBus_.fireEvent(new InstalledPackagesChangedEvent());
+ }
+ else if (type.equals(ClientEvent.PackageStatusChanged))
+ {
+ PackageStatus status = event.getData();
+ eventBus_.fireEvent(new PackageStatusChangedEvent(status));
+ }
+ else if (type.equals(ClientEvent.Locator))
+ {
+ eventBus_.fireEvent(new LocatorEvent());
+ }
+ else if (type.equals(ClientEvent.ConsoleResetHistory))
+ {
+ ConsoleResetHistory reset = event.getData();
+ eventBus_.fireEvent(new ConsoleResetHistoryEvent(reset));
+ }
+ else if (type.equals(ClientEvent.SessionSerialization))
+ {
+ SessionSerializationAction action = event.getData();
+ eventBus_.fireEvent(new SessionSerializationEvent(action));
+ }
+ else if (type.equals(ClientEvent.HistoryEntriesAdded))
+ {
+ RpcObjectList<HistoryEntry> entries = event.getData();
+ eventBus_.fireEvent(new HistoryEntriesAddedEvent(entries));
+ }
+ else if (type.equals(ClientEvent.QuotaStatus))
+ {
+ QuotaStatus quotaStatus = event.getData();
+ eventBus_.fireEvent(new QuotaStatusEvent(quotaStatus));
+ }
+ else if (type.equals(ClientEvent.FileEdit))
+ {
+ FileSystemItem file = event.getData();
+ eventBus_.fireEvent(new FileEditEvent(file));
+ }
+ else if (type.equals(ClientEvent.ShowContent))
+ {
+ ContentItem content = event.getData();
+ eventBus_.fireEvent(new ShowContentEvent(content));
+ }
+ else if (type.equals(ClientEvent.ShowData))
+ {
+ DataItem data = event.getData();
+ eventBus_.fireEvent(new ShowDataEvent(data));
+ }
+ else if (type.equals(ClientEvent.AbendWarning))
+ {
+ eventBus_.fireEvent(new SessionAbendWarningEvent());
+ }
+ else if (type.equals(ClientEvent.ShowWarningBar))
+ {
+ WarningBarMessage message = event.getData();
+ eventBus_.fireEvent(new ShowWarningBarEvent(message));
+ }
+ else if (type.equals(ClientEvent.OpenProjectError))
+ {
+ OpenProjectError error = event.getData();
+ eventBus_.fireEvent(new OpenProjectErrorEvent(error));
+ }
+ else if (type.equals(ClientEvent.VcsRefresh))
+ {
+ JsObject data = event.getData();
+ eventBus_.fireEvent(new VcsRefreshEvent(Reason.NA,
+ data.getInteger("delay")));
+ }
+ else if (type.equals(ClientEvent.AskPass))
+ {
+ AskPassEvent.Data data = event.getData();
+ eventBus_.fireEvent(new AskPassEvent(data));
+ }
+ else if (type.equals(ClientEvent.ConsoleProcessOutput))
+ {
+ ServerConsoleOutputEvent.Data data = event.getData();
+ eventBus_.fireEvent(new ServerConsoleOutputEvent(data.getHandle(),
+ data.getOutput(),
+ data.isError()));
+ }
+ else if (type.equals(ClientEvent.ConsoleProcessPrompt))
+ {
+ ServerConsolePromptEvent.Data data = event.getData();
+ eventBus_.fireEvent(new ServerConsolePromptEvent(data.getHandle(),
+ data.getPrompt()));
+ }
+ else if (type.equals(ClientEvent.ConsoleProcessCreated))
+ {
+ ConsoleProcessCreatedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new ConsoleProcessCreatedEvent(data));
+ }
+ else if (type.equals(ClientEvent.ConsoleProcessExit))
+ {
+ ServerProcessExitEvent.Data data = event.getData();
+ eventBus_.fireEvent(new ServerProcessExitEvent(data.getHandle(),
+ data.getExitCode()));
+ }
+ else if (type.equals(ClientEvent.HTMLPreviewStartedEvent))
+ {
+ HTMLPreviewStartedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new HTMLPreviewStartedEvent(data));
+ }
+ else if (type.equals(ClientEvent.HTMLPreviewOutputEvent))
+ {
+ String output = event.getData();
+ eventBus_.fireEvent(new HTMLPreviewOutputEvent(output));
+ }
+ else if (type.equals(ClientEvent.HTMLPreviewCompletedEvent))
+ {
+ HTMLPreviewResult result = event.getData();
+ eventBus_.fireEvent(new HTMLPreviewCompletedEvent(result));
+ }
+ else if (type.equals(ClientEvent.CompilePdfStartedEvent))
+ {
+ CompilePdfStartedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new CompilePdfStartedEvent(data));
+ }
+ else if (type.equals(ClientEvent.CompilePdfOutputEvent))
+ {
+ String output = event.getData();
+ eventBus_.fireEvent(new CompilePdfOutputEvent(output));
+ }
+ else if (type.equals(ClientEvent.CompilePdfErrorsEvent))
+ {
+ JsArray<CompileError> data = event.getData();
+ eventBus_.fireEvent(new CompilePdfErrorsEvent(data));
+ }
+ else if (type.equals(ClientEvent.CompilePdfCompletedEvent))
+ {
+ CompilePdfResult result = event.getData();
+ eventBus_.fireEvent(new CompilePdfCompletedEvent(result));
+ }
+ else if (type.equals(ClientEvent.SynctexEditFile))
+ {
+ SourceLocation sourceLocation = event.getData();
+ eventBus_.fireEvent(new SynctexEditFileEvent(sourceLocation));
+ }
+ else if (type.equals(ClientEvent.FindResult))
+ {
+ FindResultEvent.Data data = event.getData();
+ eventBus_.fireEvent(new FindResultEvent(
+ data.getHandle(), data.getResults().toArrayList()));
+ }
+ else if (type.equals(ClientEvent.FindOperationEnded))
+ {
+ String data = event.getData();
+ eventBus_.fireEvent(new FindOperationEndedEvent(data));
+ }
+ else if (type.equals(ClientEvent.RPubsUploadStatus))
+ {
+ RPubsUploadStatusEvent.Status status = event.getData();
+ eventBus_.fireEvent(new RPubsUploadStatusEvent(status));
+ }
+ else if (type.equals(ClientEvent.BuildStarted))
+ {
+ eventBus_.fireEvent(new BuildStartedEvent());
+ }
+ else if (type.equals(ClientEvent.BuildOutput))
+ {
+ CompileOutput data = event.getData();
+ eventBus_.fireEvent(new BuildOutputEvent(data));
+ }
+ else if (type.equals(ClientEvent.BuildCompleted))
+ {
+ BuildCompletedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new BuildCompletedEvent(data));
+ }
+ else if (type.equals(ClientEvent.BuildErrors))
+ {
+ BuildErrorsEvent.Data data = event.getData();
+ eventBus_.fireEvent(new BuildErrorsEvent(data));
+ }
+ else if (type.equals(ClientEvent.DirectoryNavigate))
+ {
+ DirectoryNavigateEvent.Data data = event.getData();
+ eventBus_.fireEvent(new DirectoryNavigateEvent(data));
+ }
+ else if (type.equals(ClientEvent.DeferredInitCompleted))
+ {
+ eventBus_.fireEvent(new DeferredInitCompletedEvent());
+ }
+ else if (type.equals(ClientEvent.PlotsZoomSizeChanged))
+ {
+ PlotsZoomSizeChangedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new PlotsZoomSizeChangedEvent(data));
+ }
+ else if (type.equals(ClientEvent.SourceCppStarted))
+ {
+ eventBus_.fireEvent(new SourceCppStartedEvent());
+ }
+ else if (type.equals(ClientEvent.SourceCppCompleted))
+ {
+ SourceCppState state = event.getData();
+ eventBus_.fireEvent(new SourceCppCompletedEvent(state));
+ }
+ else if (type.equals(ClientEvent.LoadedPackageUpdates))
+ {
+ String installCmd = event.getData();
+ eventBus_.fireEvent(new LoadedPackageUpdatesEvent(installCmd));
+ }
+ else if (type.equals(ClientEvent.ActivatePane))
+ {
+ String pane = event.getData();
+ eventBus_.fireEvent(new ActivatePaneEvent(pane));
+ }
+ else if (type.equals(ClientEvent.ShowPresentationPane))
+ {
+ PresentationState state = event.getData();
+ eventBus_.fireEvent(new ShowPresentationPaneEvent(state));
+ }
+ else if (type.equals(ClientEvent.EnvironmentRefresh))
+ {
+ eventBus_.fireEvent(new EnvironmentRefreshEvent());
+ }
+ else if (type.equals(ClientEvent.ListChanged))
+ {
+ eventBus_.fireEvent(new ListChangedEvent(event.<JsObject>getData()));
+ }
+ else if (type.equals(ClientEvent.UiPrefsChanged))
+ {
+ UiPrefsChangedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new UiPrefsChangedEvent(data));
+ }
+ else if (type.equals(ClientEvent.ContextDepthChanged)) {
+ EnvironmentContextData data = event.getData();
+ eventBus_.fireEvent(new ContextDepthChangedEvent(data, true));
+ }
+ else if (type.equals(ClientEvent.HandleUnsavedChanges))
+ {
+ eventBus_.fireEvent(new HandleUnsavedChangesEvent());
+ }
+ else if (type.equals(ClientEvent.Quit))
+ {
+ boolean switchProjects = event.<Bool>getData().getValue();
+ eventBus_.fireEvent(new QuitEvent(switchProjects));
+ }
+ else if (type.equals(ClientEvent.Suicide))
+ {
+ // NOTE: we don't explicitly stop listening for events here
+ // for the reasons cited above in ClientEvent.Quit
+
+ // fire event
+ String message = event.getData();
+ eventBus_.fireEvent(new SuicideEvent(message));
+ }
+ else if (type.equals(ClientEvent.SaveActionChanged))
+ {
+ SaveAction action = event.getData();
+ eventBus_.fireEvent(new SaveActionChangedEvent(action));
+ }
+ else if (type.equals(ClientEvent.EnvironmentAssigned))
+ {
+ RObject objectInfo = event.getData();
+ eventBus_.fireEvent(new EnvironmentObjectAssignedEvent(objectInfo));
+ }
+ else if (type.equals(ClientEvent.EnvironmentRemoved))
+ {
+ String objectName = event.getData();
+ eventBus_.fireEvent(new EnvironmentObjectRemovedEvent(objectName));
+ }
+ else if (type.equals(ClientEvent.BrowserLineChanged))
+ {
+ LineData lineData = event.getData();
+ eventBus_.fireEvent(new BrowserLineChangedEvent(lineData));
+ }
+ else if (type.equals(ClientEvent.PackageLoaded))
+ {
+ eventBus_.fireEvent(new PackageLoadedEvent(
+ (String)event.getData()));
+ }
+ else if (type.equals(ClientEvent.PackageUnloaded))
+ {
+ eventBus_.fireEvent(new PackageUnloadedEvent(
+ (String)event.getData()));
+ }
+ else if (type.equals(ClientEvent.PresentationPaneRequestCompleted))
+ {
+ eventBus_.fireEvent(new PresentationPaneRequestCompletedEvent());
+ }
+ else if (type.equals(ClientEvent.UnhandledError))
+ {
+ UnhandledError err = event.getData();
+ eventBus_.fireEvent(new UnhandledErrorEvent(err));
+ }
+ else if (type.equals(ClientEvent.ErrorHandlerChanged))
+ {
+ ErrorHandlerType handlerType = event.getData();
+ eventBus_.fireEvent(new ErrorHandlerChangedEvent(handlerType));
+ }
+ else if (type.equals(ClientEvent.ViewerNavigate))
+ {
+ ViewerNavigateEvent.Data data = event.getData();
+ eventBus_.fireEvent(new ViewerNavigateEvent(data));
+ }
+ else if (type.equals(ClientEvent.SourceExtendedTypeDetected))
+ {
+ SourceExtendedTypeDetectedEvent.Data data = event.getData();
+ eventBus_.fireEvent(new SourceExtendedTypeDetectedEvent(data));
+ }
+ else if (type.equals(ClientEvent.ShinyViewer))
+ {
+ ShinyApplicationParams data = event.getData();
+ eventBus_.fireEvent(new ShinyApplicationStatusEvent(data));
+ }
+ else if (type.equals(ClientEvent.DebugSourceCompleted))
+ {
+ DebugSourceResult result = (DebugSourceResult)event.getData();
+ eventBus_.fireEvent(new DebugSourceCompletedEvent(result));
+ }
+ else
+ {
+ GWT.log("WARNING: Server event not dispatched: " + type, null);
+ }
+ }
+ catch(Throwable e)
+ {
+ GWT.log("WARNING: Exception occured dispatching event: " + type, e);
+ }
+ }
+
+
+ private final EventBus eventBus_;
+
+ private final ArrayList<ClientEvent> pendingEvents_ = new ArrayList<ClientEvent>();
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventHandler.java b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventHandler.java
new file mode 100644
index 0000000..039602c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/ClientEventHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ClientEventHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public interface ClientEventHandler
+{
+ void onClientEvent(JavaScriptObject clientEvent);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServer.java b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServer.java
new file mode 100644
index 0000000..976cb06
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServer.java
@@ -0,0 +1,3486 @@
+/*
+ * RemoteServer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.*;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.json.client.*;
+import com.google.gwt.user.client.Random;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.core.client.jsonrpc.*;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.*;
+import org.rstudio.studio.client.application.model.ProductInfo;
+import org.rstudio.studio.client.application.model.SuspendOptions;
+import org.rstudio.studio.client.application.model.UpdateCheckResult;
+import org.rstudio.studio.client.common.JSONUtils;
+import org.rstudio.studio.client.common.codetools.Completions;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.console.ConsoleProcess.ConsoleProcessFactory;
+import org.rstudio.studio.client.common.console.ConsoleProcessInfo;
+import org.rstudio.studio.client.common.crypto.PublicKeyInfo;
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.debugging.model.FunctionState;
+import org.rstudio.studio.client.common.debugging.model.FunctionSteps;
+import org.rstudio.studio.client.common.debugging.model.TopLevelLineData;
+import org.rstudio.studio.client.common.mirrors.model.CRANMirror;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.shell.ShellInput;
+import org.rstudio.studio.client.common.shiny.model.ShinyCapabilities;
+import org.rstudio.studio.client.common.synctex.model.PdfLocation;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.common.vcs.*;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
+import org.rstudio.studio.client.notebook.CompileNotebookOptions;
+import org.rstudio.studio.client.notebook.CompileNotebookResult;
+import org.rstudio.studio.client.projects.model.NewPackageOptions;
+import org.rstudio.studio.client.projects.model.NewProjectContext;
+import org.rstudio.studio.client.projects.model.NewShinyAppOptions;
+import org.rstudio.studio.client.projects.model.RProjectOptions;
+import org.rstudio.studio.client.projects.model.RProjectVcsOptions;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.shiny.model.ShinyAppsApplicationInfo;
+import org.rstudio.studio.client.shiny.model.ShinyAppsDeploymentRecord;
+import org.rstudio.studio.client.shiny.model.ShinyRunCmd;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchResults;
+import org.rstudio.studio.client.workbench.codesearch.model.FunctionDefinition;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+import org.rstudio.studio.client.workbench.model.Agreement;
+import org.rstudio.studio.client.workbench.model.HTMLCapabilities;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.model.TerminalOptions;
+import org.rstudio.studio.client.workbench.model.TexCapabilities;
+import org.rstudio.studio.client.workbench.model.WorkbenchMetrics;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.SpellingPrefsContext;
+import org.rstudio.studio.client.workbench.views.environment.model.DataPreviewResult;
+import org.rstudio.studio.client.workbench.views.environment.model.DownloadInfo;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.ObjectContents;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+import org.rstudio.studio.client.workbench.views.files.model.FileUploadToken;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInfo;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageUpdate;
+import org.rstudio.studio.client.workbench.views.plots.model.Point;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageContext;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationRPubsSource;
+import org.rstudio.studio.client.workbench.views.presentation.model.SlideNavigation;
+import org.rstudio.studio.client.workbench.views.source.editors.text.IconvListResult;
+import org.rstudio.studio.client.workbench.views.source.model.CheckForExternalEditResult;
+import org.rstudio.studio.client.workbench.views.source.model.CppCapabilities;
+import org.rstudio.studio.client.workbench.views.source.model.RdShellResult;
+import org.rstudio.studio.client.workbench.views.source.model.RnwChunkOptions;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitCount;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+ at Singleton
+public class RemoteServer implements Server
+{
+ @Inject
+ public RemoteServer(Session session,
+ EventBus eventBus,
+ Satellite satellite,
+ final SatelliteManager satelliteManager,
+ Provider<ConsoleProcessFactory> pConsoleProcessFactory)
+ {
+ pConsoleProcessFactory_ = pConsoleProcessFactory;
+ clientId_ = null;
+ disconnected_ = false;
+ listeningForEvents_ = false;
+ session_ = session;
+ eventBus_ = eventBus;
+ satellite_ = satellite;
+ serverAuth_ = new RemoteServerAuth(this);
+
+ // define external event listener if we are the main window
+ // (so we can forward to the satellites)
+ ClientEventHandler externalListener = null;
+ if (!satellite.isCurrentWindowSatellite())
+ {
+ externalListener = new ClientEventHandler() {
+ @Override
+ public void onClientEvent(JavaScriptObject clientEvent)
+ {
+ satelliteManager.dispatchEvent(clientEvent);
+ }
+ };
+ }
+
+ // create server event listener
+ serverEventListener_ = new RemoteServerEventListener(this,
+ externalListener);
+ }
+
+ // complete initialization now that the workbench is ready
+ public void initializeForMainWorkbench()
+ {
+ // satellite windows should never call onWorkbenchReady
+ if (satellite_.isCurrentWindowSatellite())
+ {
+ Debug.log("Satellite window cannot call onWorkbenchReady!");
+ assert false;
+ }
+
+ // update state
+ listeningForEvents_ = true;
+
+ // only check credentials if we are in server mode
+ if (session_.getSessionInfo().getMode().equals(SessionInfo.SERVER_MODE))
+ serverAuth_.schedulePeriodicCredentialsUpdate();
+
+ // start event listener
+ serverEventListener_.start();
+
+ // register satallite callback
+ registerSatelliteCallback();
+ }
+
+ public void disconnect()
+ {
+ disconnected_ = true;
+ serverEventListener_.stop();
+ }
+
+ public void log(int logEntryType,
+ String logEntry,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(logEntryType));
+ params.set(1, new JSONString(logEntry));
+ sendRequest(LOG_SCOPE , LOG, params, requestCallback);
+ }
+
+ public void logException(ClientException e,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(LOG_SCOPE, LOG_EXCEPTION, e, requestCallback);
+ }
+
+ public void clientInit(
+ final ServerRequestCallback<SessionInfo> requestCallback)
+ {
+ // send init request (record clientId and version contained in response)
+ sendRequest(RPC_SCOPE,
+ CLIENT_INIT,
+ new ServerRequestCallback<SessionInfo>() {
+
+ public void onResponseReceived(SessionInfo sessionInfo)
+ {
+ clientId_ = sessionInfo.getClientId();
+ clientVersion_ = sessionInfo.getClientVersion();
+ requestCallback.onResponseReceived(sessionInfo);
+ }
+
+ public void onError(ServerError error)
+ {
+ requestCallback.onError(error);
+ }
+ });
+ }
+
+ // accept application agreement
+ public void acceptAgreement(Agreement agreement,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ ACCEPT_AGREEMENT,
+ agreement.getHash(),
+ requestCallback);
+ }
+
+
+ public void suspendSession(boolean force,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SUSPEND_SESSION, force, requestCallback);
+ }
+
+
+ public void handleUnsavedChangesCompleted(
+ boolean handled,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ HANDLE_UNSAVED_CHANGES_COMPLETED,
+ handled,
+ requestCallback);
+ }
+
+ public void quitSession(boolean saveWorkspace,
+ String switchToProject,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONBoolean.getInstance(saveWorkspace));
+ params.set(1, new JSONString(StringUtil.notNull(switchToProject)));
+ sendRequest(RPC_SCOPE, QUIT_SESSION, params, requestCallback);
+ }
+
+ public void updateCredentials()
+ {
+ serverAuth_.attemptToUpdateCredentials();
+ }
+
+ public String getApplicationURL(String pathName)
+ {
+ // if accessing a URL is the first thing we do after being
+ // suspended ensure that events flow right away
+ ensureListeningForEvents();
+
+ // return the url
+ return GWT.getHostPageBaseURL() + pathName;
+ }
+
+ public void suspendForRestart(SuspendOptions options,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SUSPEND_FOR_RESTART, options, requestCallback);
+ }
+
+ public void ping(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PING, requestCallback);
+ }
+
+
+ public void setWorkbenchMetrics(WorkbenchMetrics metrics,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ SET_WORKBENCH_METRICS,
+ metrics,
+ requestCallback);
+ }
+
+ public void setPrefs(RPrefs rPrefs,
+ JavaScriptObject uiPrefs,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONObject(rPrefs));
+ params.set(1, new JSONObject(uiPrefs));
+ sendRequest(RPC_SCOPE, SET_PREFS, params, requestCallback);
+}
+
+ public void setUiPrefs(JavaScriptObject uiPrefs,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ SET_UI_PREFS,
+ uiPrefs,
+ requestCallback);
+ }
+
+ public void getRPrefs(ServerRequestCallback<RPrefs> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_R_PREFS,
+ requestCallback);
+ }
+
+ public void updateClientState(JavaScriptObject temporary,
+ JavaScriptObject persistent,
+ JavaScriptObject projectPersistent,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONObject(temporary));
+ params.set(1, new JSONObject(persistent));
+ params.set(2, new JSONObject(projectPersistent));
+ sendRequest(RPC_SCOPE,
+ SET_CLIENT_STATE,
+ params,
+ requestCallback);
+ }
+
+ public void userPromptCompleted(int response,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, USER_PROMPT_COMPLETED, response, requestCallback);
+ }
+
+ public void getTerminalOptions(
+ ServerRequestCallback<TerminalOptions> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_TERMINAL_OPTIONS, requestCallback);
+ }
+
+ public void startShellDialog(
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ START_SHELL_DIALOG,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ public void getInitMessages(ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(META_SCOPE,
+ GET_INIT_MESSAGES,
+ requestCallback);
+ }
+
+ public void searchCode(
+ String term,
+ int maxResults,
+ ServerRequestCallback<CodeSearchResults> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(term));
+ params.set(1, new JSONNumber(maxResults));
+ sendRequest(RPC_SCOPE, SEARCH_CODE, params, requestCallback);
+ }
+
+ public void getFunctionDefinition(
+ String line,
+ int pos,
+ ServerRequestCallback<FunctionDefinition> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(line));
+ params.set(1, new JSONNumber(pos));
+ sendRequest(RPC_SCOPE,
+ GET_FUNCTION_DEFINITION,
+ params,
+ requestCallback);
+ }
+
+ public void findFunctionInSearchPath(
+ String line,
+ int pos,
+ String fromWhere,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(line));
+ params.set(1, new JSONNumber(pos));
+ params.set(2, fromWhere != null ? new JSONString(fromWhere) :
+ JSONNull.getInstance());
+ sendRequest(RPC_SCOPE,
+ FIND_FUNCTION_IN_SEARCH_PATH,
+ params,
+ requestCallback);
+ }
+
+
+ public void getSearchPathFunctionDefinition(
+ String name,
+ String namespace,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(name));
+ params.set(1, new JSONString(namespace));
+ sendRequest(RPC_SCOPE,
+ GET_SEARCH_PATH_FUNCTION_DEFINITION,
+ params,
+ requestCallback);
+ }
+
+ public void getMethodDefinition(
+ String name,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_METHOD_DEFINITION, name, requestCallback);
+ }
+
+ public void consoleInput(String consoleInput,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CONSOLE_INPUT, consoleInput, requestCallback);
+ }
+
+ public void resetConsoleActions(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, RESET_CONSOLE_ACTIONS, requestCallback);
+ }
+
+ public void processStart(String handle,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PROCESS_START, handle, requestCallback);
+ }
+
+ @Override
+ public void processInterrupt(String handle,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PROCESS_INTERRUPT, handle, requestCallback);
+ }
+
+ @Override
+ public void processReap(String handle,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PROCESS_REAP, handle, requestCallback);
+ }
+
+ @Override
+ public void processWriteStdin(String handle,
+ ShellInput input,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(handle));
+ params.set(1, new JSONObject(input));
+ sendRequest(RPC_SCOPE, PROCESS_WRITE_STDIN, params, requestCallback);
+ }
+
+
+ public void interrupt(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, INTERRUPT, requestCallback);
+ }
+
+ public void abort(String nextProj,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(StringUtil.notNull(nextProj)));
+ sendRequest(RPC_SCOPE, ABORT, params, requestCallback);
+ }
+
+ public void getCompletions(String line, int cursorPos,
+ ServerRequestCallback<Completions> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(line));
+ params.set(1, new JSONNumber(cursorPos));
+ sendRequest(RPC_SCOPE,
+ GET_COMPLETIONS,
+ params,
+ requestCallback) ;
+ }
+
+ public void getHelpAtCursor(String line, int cursorPos,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(line));
+ params.set(1, new JSONNumber(cursorPos));
+ sendRequest(RPC_SCOPE,
+ GET_HELP_AT_CURSOR,
+ params,
+ requestCallback) ;
+ }
+
+ public void removeAllObjects(boolean includeHidden,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ REMOVE_ALL_OBJECTS,
+ includeHidden,
+ requestCallback);
+ }
+
+ @Override
+ public void removeObjects(List<String> objectNames,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONUtils.toJSONStringArray(objectNames));
+ sendRequest(RPC_SCOPE,
+ REMOVE_OBJECTS,
+ params,
+ requestCallback);
+ }
+
+ public void downloadDataFile(
+ String dataFileUrl,
+ ServerRequestCallback<DownloadInfo> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ DOWNLOAD_DATA_FILE,
+ dataFileUrl,
+ requestCallback);
+ }
+
+ public void getDataPreview(String dataFilePath,
+ ServerRequestCallback<DataPreviewResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_DATA_PREVIEW,
+ dataFilePath,
+ requestCallback);
+ }
+
+ public void getOutputPreview(String dataFilePath,
+ boolean heading,
+ String separator,
+ String decimal,
+ String quote,
+ ServerRequestCallback<DataPreviewResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(dataFilePath));
+ params.set(1, JSONBoolean.getInstance(heading));
+ params.set(2, new JSONString(separator));
+ params.set(3, new JSONString(decimal));
+ params.set(4, new JSONString(quote));
+
+ sendRequest(RPC_SCOPE,
+ GET_OUTPUT_PREVIEW,
+ params,
+ requestCallback);
+ }
+
+ public void editCompleted(String text,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, EDIT_COMPLETED, text, requestCallback);
+ }
+
+ public void chooseFileCompleted(String file,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CHOOSE_FILE_COMPLETED, file, requestCallback);
+ }
+
+
+ public void listPackages(
+ ServerRequestCallback<JsArray<PackageInfo>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, LIST_PACKAGES, requestCallback);
+ }
+
+ public void getPackageInstallContext(
+ ServerRequestCallback<PackageInstallContext> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_PACKAGE_INSTALL_CONTEXT, requestCallback);
+ }
+
+ public void isPackageLoaded(
+ String packageName,
+ String libName,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(packageName));
+ params.set(1, new JSONString(libName));
+ sendRequest(RPC_SCOPE, IS_PACKAGE_LOADED, params, requestCallback);
+ }
+
+ public void availablePackages(
+ String repository,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, AVAILABLE_PACKAGES, repository, requestCallback);
+ }
+
+ public void checkForPackageUpdates(
+ ServerRequestCallback<JsArray<PackageUpdate>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CHECK_FOR_PACKAGE_UPDATES, requestCallback);
+ }
+
+ public void initDefaultUserLibrary(
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, INIT_DEFAULT_USER_LIBRARY, requestCallback);
+ }
+
+ public void loadedPackageUpdatesRequired(
+ List<String> packages,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONArray(JsUtil.toJsArrayString(packages)));
+ sendRequest(RPC_SCOPE,
+ LOADED_PACKAGE_UPDATES_REQUIRED,
+ params,
+ requestCallback);
+ }
+
+ public void ignoreNextLoadedPackageCheck(
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, IGNORE_NEXT_LOADED_PACKAGE_CHECK, requestCallback);
+ }
+
+ public void setCRANMirror(CRANMirror mirror,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SET_CRAN_MIRROR, mirror, requestCallback);
+ }
+
+ public void getCRANMirrors(
+ ServerRequestCallback<JsArray<CRANMirror>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_CRAN_MIRRORS, requestCallback);
+ }
+
+ public void suggestTopics(String prefix,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "suggest_topics", prefix, requestCallback);
+ }
+
+ public void getHelp(String topic,
+ String packageName,
+ int options,
+ ServerRequestCallback<HelpInfo> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(topic));
+ if (packageName != null)
+ params.set(1, new JSONString(packageName));
+ else
+ params.set(1, JSONNull.getInstance());
+ params.set(2, new JSONNumber(options));
+
+ sendRequest(RPC_SCOPE, GET_HELP, params, requestCallback);
+ }
+
+ public void showHelpTopic(String topic, String pkgName)
+ {
+ JSONArray params = new JSONArray() ;
+ params.set(0, new JSONString(topic)) ;
+ params.set(1, pkgName != null
+ ? new JSONString(pkgName)
+ : JSONNull.getInstance()) ;
+
+ sendRequest(RPC_SCOPE,
+ SHOW_HELP_TOPIC,
+ params,
+ null) ;
+ }
+
+ public void search(String query,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ SEARCH,
+ query,
+ requestCallback) ;
+ }
+
+ @Override
+ public void stat(String path,
+ ServerRequestCallback<FileSystemItem> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, STAT, path, requestCallback);
+ }
+
+ @Override
+ public void isTextFile(String path,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, IS_TEXT_FILE, path, requestCallback);
+ }
+
+ public void listFiles(
+ FileSystemItem directory,
+ boolean monitor,
+ ServerRequestCallback<JsArray<FileSystemItem>> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+ paramArray.set(0, new JSONString(directory.getPath()));
+ paramArray.set(1, JSONBoolean.getInstance(monitor));
+
+ sendRequest(RPC_SCOPE,
+ LIST_FILES,
+ paramArray,
+ requestCallback);
+ }
+
+ public void listAllFiles(String path,
+ String pattern,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONString(pattern));
+ sendRequest(RPC_SCOPE,
+ LIST_ALL_FILES,
+ params,
+ requestCallback);
+ }
+
+ public void createFolder(FileSystemItem folder,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ CREATE_FOLDER,
+ folder.getPath(),
+ requestCallback);
+ }
+
+ public void deleteFiles(ArrayList<FileSystemItem> files,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+ JSONArray pathArray = new JSONArray();
+ for (int i=0; i<files.size(); i++)
+ pathArray.set(i, new JSONString(files.get(i).getPath()));
+ paramArray.set(0, pathArray);
+
+ sendRequest(RPC_SCOPE, DELETE_FILES, paramArray, requestCallback);
+ }
+
+ public void copyFile(FileSystemItem sourceFile,
+ FileSystemItem targetFile,
+ boolean ovewrite,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+ paramArray.set(0, new JSONString(sourceFile.getPath()));
+ paramArray.set(1, new JSONString(targetFile.getPath()));
+ paramArray.set(2, JSONBoolean.getInstance(ovewrite));
+
+ sendRequest(RPC_SCOPE, COPY_FILE, paramArray, requestCallback);
+ }
+
+
+ public void moveFiles(ArrayList<FileSystemItem> files,
+ FileSystemItem targetDirectory,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+
+ JSONArray pathArray = new JSONArray();
+ for (int i=0; i<files.size(); i++)
+ pathArray.set(i, new JSONString(files.get(i).getPath()));
+
+ paramArray.set(0, pathArray);
+ paramArray.set(1, new JSONString(targetDirectory.getPath()));
+
+ sendRequest(RPC_SCOPE, MOVE_FILES, paramArray, requestCallback);
+ }
+
+ public void renameFile(FileSystemItem file,
+ FileSystemItem targetFile,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+ paramArray.set(0, new JSONString(file.getPath()));
+ paramArray.set(1, new JSONString(targetFile.getPath()));
+
+ sendRequest(RPC_SCOPE, RENAME_FILE, paramArray, requestCallback);
+ }
+
+ public String getFileUrl(FileSystemItem file)
+ {
+ if (Desktop.isDesktop())
+ {
+ return Desktop.getFrame().getUriForPath(file.getPath());
+ }
+
+ if (!file.isDirectory())
+ {
+ if (file.isWithinHome())
+ {
+ return getApplicationURL(FILES_SCOPE) + "/" + file.homeRelativePath();
+ }
+ else
+ {
+ String url = getApplicationURL(FILE_SHOW);
+ url += "?path=" + URL.encodeQueryString(file.getPath());
+ return url;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // get file upload base url
+ public String getFileUploadUrl()
+ {
+ return getApplicationURL(UPLOAD_SCOPE);
+ }
+
+ public void completeUpload(FileUploadToken token,
+ boolean commit,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray paramArray = new JSONArray();
+ paramArray.set(0, new JSONObject(token));
+ paramArray.set(1, JSONBoolean.getInstance(commit));
+ sendRequest(RPC_SCOPE, COMPLETE_UPLOAD, paramArray, requestCallback);
+ }
+
+ public String getFileExportUrl(String name, FileSystemItem file)
+ {
+ return getApplicationURL(EXPORT_SCOPE) + "/" + URL.encodePathSegment(name) + "?" +
+ "name=" + URL.encodeQueryString(name) + "&" +
+ "file=" + URL.encodeQueryString(file.getPath());
+ }
+
+
+ public String getFileExportUrl(String name,
+ FileSystemItem parentDirectory,
+ ArrayList<String> filenames)
+ {
+ // build url params for files
+ StringBuilder files = new StringBuilder();
+ for (int i = 0; i<filenames.size(); i++)
+ {
+ files.append("file").append(i).append("=");
+ files.append(URL.encodeQueryString(filenames.get(i)));
+ files.append("&");
+ }
+
+ // return url
+ return getApplicationURL(EXPORT_SCOPE) + "/" + URL.encodePathSegment(name) + "?" +
+ "name=" + URL.encodeQueryString(name) + "&" +
+ "parent=" + URL.encodeQueryString(parentDirectory.getPath()) + "&" +
+ files.toString();
+ }
+
+
+ // get graphics url
+ public String getGraphicsUrl(String filename)
+ {
+ return getApplicationURL(GRAPHICS_SCOPE) + "/" + filename;
+ }
+
+ public String getPlotExportUrl(String type,
+ int width,
+ int height,
+ boolean attachment)
+ {
+ // build preview URL
+ String previewURL = getGraphicsUrl("plot." + type);
+ previewURL += "?";
+ previewURL += "width=" + width;
+ previewURL += "&";
+ previewURL += "height=" + height;
+ // append random number to default over-aggressive image caching
+ // by browsers
+ previewURL += "&randomizer=" + Random.nextInt();
+ if (attachment)
+ previewURL += "&attachment=1";
+
+ return previewURL;
+ }
+
+ public void nextPlot(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, NEXT_PLOT, requestCallback);
+ }
+
+ public void previousPlot(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PREVIOUS_PLOT, requestCallback);
+ }
+
+ public void removePlot(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, REMOVE_PLOT, requestCallback);
+ }
+
+ public void clearPlots(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLEAR_PLOTS, requestCallback);
+ }
+
+ public void refreshPlot(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, REFRESH_PLOT, requestCallback);
+ }
+
+ public void savePlotAs(FileSystemItem file,
+ String format,
+ int width,
+ int height,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(file.getPath()));
+ params.set(1, new JSONString(format));
+ params.set(2, new JSONNumber(width));
+ params.set(3, new JSONNumber(height));
+ params.set(4, JSONBoolean.getInstance(overwrite));
+ sendRequest(RPC_SCOPE, SAVE_PLOT_AS, params, requestCallback);
+ }
+
+ public void savePlotAsPdf(FileSystemItem file,
+ double widthInches,
+ double heightInches,
+ boolean useCairoPdf,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(file.getPath()));
+ params.set(1, new JSONNumber(widthInches));
+ params.set(2, new JSONNumber(heightInches));
+ params.set(3, JSONBoolean.getInstance(useCairoPdf));
+ params.set(4, JSONBoolean.getInstance(overwrite));
+ sendRequest(RPC_SCOPE, SAVE_PLOT_AS_PDF, params, requestCallback);
+ }
+
+ public void copyPlotToClipboardMetafile(
+ int width,
+ int height,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(width));
+ params.set(1, new JSONNumber(height));
+ sendRequest(RPC_SCOPE,
+ COPY_PLOT_TO_CLIPBOARD_METAFILE,
+ params,
+ requestCallback);
+ }
+
+ public void getUniqueSavePlotStem(String directory,
+ ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_UNIQUE_SAVE_PLOT_STEM, directory, requestCallback);
+ }
+
+ public void getSavePlotContext(
+ String directory,
+ ServerRequestCallback<SavePlotAsImageContext> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_SAVE_PLOT_CONTEXT,
+ directory,
+ requestCallback);
+ }
+
+ public void locatorCompleted(Point point,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, LOCATOR_COMPLETED, point, requestCallback);
+ }
+
+ public void setManipulatorValues(JSONObject values,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, values);
+ sendRequest(RPC_SCOPE, SET_MANIPULATOR_VALUES, params, requestCallback);
+ }
+
+ public void manipulatorPlotClicked(
+ int x,
+ int y,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(x));
+ params.set(1, new JSONNumber(y));
+ sendRequest(RPC_SCOPE, MANIPULATOR_PLOT_CLICKED, params, requestCallback);
+ }
+
+ public void getNewProjectContext(
+ ServerRequestCallback<NewProjectContext> callback)
+ {
+ sendRequest(RPC_SCOPE, GET_NEW_PROJECT_CONTEXT, callback);
+ }
+
+ public void createProject(String projectFile,
+ NewPackageOptions newPackageOptions,
+ NewShinyAppOptions newShinyAppOptions,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(projectFile));
+ params.set(1, newPackageOptions != null ?
+ new JSONObject(newPackageOptions) : JSONNull.getInstance());
+ params.set(2, newShinyAppOptions != null ?
+ new JSONObject(newShinyAppOptions) : JSONNull.getInstance());
+ sendRequest(RPC_SCOPE, CREATE_PROJECT, params, requestCallback);
+ }
+
+ public void readProjectOptions(ServerRequestCallback<RProjectOptions> callback)
+ {
+ sendRequest(RPC_SCOPE, READ_PROJECT_OPTIONS, callback);
+ }
+
+ public void writeProjectOptions(RProjectOptions options,
+ ServerRequestCallback<Void> callback)
+ {
+ sendRequest(RPC_SCOPE, WRITE_PROJECT_OPTIONS, options, callback);
+ }
+
+ public void writeProjectVcsOptions(RProjectVcsOptions options,
+ ServerRequestCallback<Void> callback)
+ {
+ sendRequest(RPC_SCOPE, WRITE_PROJECT_VCS_OPTIONS, options, callback);
+ }
+
+ public void newDocument(String filetype,
+ String contents,
+ JsObject properties,
+ ServerRequestCallback<SourceDocument> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(filetype));
+ params.set(1, contents != null ? new JSONString(contents) :
+ JSONNull.getInstance());
+ params.set(2, new JSONObject(properties));
+ sendRequest(RPC_SCOPE, NEW_DOCUMENT, params, requestCallback);
+ }
+
+ public void openDocument(String path,
+ String filetype,
+ String encoding,
+ ServerRequestCallback<SourceDocument> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONString(filetype));
+ params.set(2, encoding != null ? new JSONString(encoding)
+ : JSONNull.getInstance());
+ sendRequest(RPC_SCOPE, OPEN_DOCUMENT, params, requestCallback);
+ }
+
+ public void saveDocument(String id,
+ String path,
+ String fileType,
+ String encoding,
+ String foldSpec,
+ String contents,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, path == null ? JSONNull.getInstance() : new JSONString(path));
+ params.set(2, fileType == null ? JSONNull.getInstance() : new JSONString(fileType));
+ params.set(3, encoding == null ? JSONNull.getInstance() : new JSONString(encoding));
+ params.set(4, new JSONString(StringUtil.notNull(foldSpec)));
+ params.set(5, new JSONString(contents));
+ sendRequest(RPC_SCOPE, SAVE_DOCUMENT, params, requestCallback);
+ }
+
+ public void saveDocumentDiff(String id,
+ String path,
+ String fileType,
+ String encoding,
+ String foldSpec,
+ String replacement,
+ int offset,
+ int length,
+ String hash,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, path == null ? JSONNull.getInstance() : new JSONString(path));
+ params.set(2, fileType == null ? JSONNull.getInstance() : new JSONString(fileType));
+ params.set(3, encoding == null ? JSONNull.getInstance() : new JSONString(encoding));
+ params.set(4, new JSONString(StringUtil.notNull(foldSpec)));
+ params.set(5, new JSONString(replacement));
+ params.set(6, new JSONNumber(offset));
+ params.set(7, new JSONNumber(length));
+ params.set(8, new JSONString(hash));
+ sendRequest(RPC_SCOPE, SAVE_DOCUMENT_DIFF, params, requestCallback);
+ }
+
+ public void checkForExternalEdit(
+ String id,
+ ServerRequestCallback<CheckForExternalEditResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CHECK_FOR_EXTERNAL_EDIT, id, requestCallback);
+ }
+
+ public void ignoreExternalEdit(String id,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, IGNORE_EXTERNAL_EDIT, id, requestCallback);
+ }
+
+ public void closeDocument(String id,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLOSE_DOCUMENT, id, requestCallback);
+ }
+
+ public void closeAllDocuments(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLOSE_ALL_DOCUMENTS, requestCallback);
+ }
+
+ public void getSourceTemplate(String name,
+ String template,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(name));
+ params.set(1, new JSONString(template));
+ sendRequest(RPC_SCOPE, GET_SOURCE_TEMPLATE, params, requestCallback);
+ }
+
+ public void createRdShell(
+ String name,
+ String type,
+ ServerRequestCallback<RdShellResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(name));
+ params.set(1, new JSONString(type));
+ sendRequest(RPC_SCOPE, CREATE_RD_SHELL, params, requestCallback);
+ }
+
+ public void setSourceDocumentOnSave(String id,
+ boolean shouldSourceOnSave,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, JSONBoolean.getInstance(shouldSourceOnSave));
+ sendRequest(RPC_SCOPE,
+ SET_SOURCE_DOCUMENT_ON_SAVE,
+ params,
+ requestCallback);
+ }
+
+ public void getTexCapabilities(
+ ServerRequestCallback<TexCapabilities> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_TEX_CAPABILITIES,
+ requestCallback);
+ }
+
+ public void getChunkOptions(
+ String weaveType,
+ ServerRequestCallback<RnwChunkOptions> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_CHUNK_OPTIONS, weaveType, requestCallback);
+ }
+
+ public String getProgressUrl(String message)
+ {
+ String url = getApplicationURL(SOURCE_SCOPE + "/" + "progress");
+ url += "?message=" + URL.encodeQueryString(message);
+ return url;
+ }
+
+
+ public void saveActiveDocument(String contents,
+ boolean sweave,
+ String rnwWeave,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(contents));
+ params.set(1, JSONBoolean.getInstance(sweave));
+ params.set(2, new JSONString(rnwWeave));
+
+ sendRequest(RPC_SCOPE,
+ SAVE_ACTIVE_DOCUMENT,
+ params,
+ requestCallback);
+ }
+
+ public void modifyDocumentProperties(
+ String id,
+ HashMap<String, String> properties,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONObject obj = new JSONObject();
+ for (Map.Entry<String, String> entry : properties.entrySet())
+ {
+ obj.put(entry.getKey(), entry.getValue() == null
+ ? JSONNull.getInstance()
+ : new JSONString(entry.getValue()));
+ }
+
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, obj);
+
+ sendRequest(RPC_SCOPE, MODIFY_DOCUMENT_PROPERTIES, params, requestCallback);
+ }
+
+ public void revertDocument(String id,
+ String fileType,
+ ServerRequestCallback<SourceDocument> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, new JSONString(fileType));
+ sendRequest(RPC_SCOPE, REVERT_DOCUMENT, params, requestCallback);
+ }
+
+ public void reopenWithEncoding(String id,
+ String encoding,
+ ServerRequestCallback<SourceDocument> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(id));
+ params.set(1, new JSONString(encoding));
+ sendRequest(RPC_SCOPE, REOPEN_WITH_ENCODING, params, requestCallback);
+ }
+
+ public void removeContentUrl(String contentUrl,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, REMOVE_CONTENT_URL, contentUrl, requestCallback);
+ }
+
+ public void detectFreeVars(String code,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, DETECT_FREE_VARS, code, requestCallback);
+ }
+
+ public void iconvlist(ServerRequestCallback<IconvListResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, ICONVLIST, requestCallback);
+ }
+
+ @Override
+ public void createNotebook(
+ CompileNotebookOptions options,
+ ServerRequestCallback<CompileNotebookResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "create_notebook", options, requestCallback);
+ }
+
+ @Override
+ public void isReadOnlyFile(String path,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "is_read_only_file", path, requestCallback);
+ }
+
+ @Override
+ public void getShinyCapabilities(
+ ServerRequestCallback<ShinyCapabilities> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "get_shiny_capabilities", requestCallback);
+ }
+
+ public void getRecentHistory(
+ long maxItems,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_RECENT_HISTORY, maxItems, requestCallback);
+ }
+
+ public void getHistoryItems(
+ long startIndex, // inclusive
+ long endIndex, // exclusive
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(startIndex));
+ params.set(1, new JSONNumber(endIndex));
+ sendRequest(RPC_SCOPE, GET_HISTORY_ITEMS, params, requestCallback);
+ }
+
+
+ public void removeHistoryItems(JsArrayNumber itemIndexes,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ REMOVE_HISTORY_ITEMS,
+ itemIndexes,
+ requestCallback);
+ }
+
+
+ public void clearHistory(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLEAR_HISTORY, requestCallback);
+ }
+
+
+ public void getHistoryArchiveItems(
+ long startIndex, // inclusive
+ long endIndex, // exclusive
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(startIndex));
+ params.set(1, new JSONNumber(endIndex));
+ sendRequest(RPC_SCOPE, GET_HISTORY_ARCHIVE_ITEMS, params, requestCallback);
+ }
+
+
+ public void searchHistoryArchive(
+ String query,
+ long maxEntries,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(query));
+ params.set(1, new JSONNumber(maxEntries));
+ sendRequest(RPC_SCOPE, SEARCH_HISTORY_ARCHIVE, params, requestCallback);
+ }
+
+ public void searchHistoryArchiveByPrefix(
+ String prefix,
+ long maxEntries,
+ boolean uniqueOnly,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(prefix));
+ params.set(1, new JSONNumber(maxEntries));
+ params.set(2, JSONBoolean.getInstance(uniqueOnly));
+ sendRequest(RPC_SCOPE, SEARCH_HISTORY_ARCHIVE_BY_PREFIX, params, requestCallback);
+ }
+
+ public void gitAdd(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_ADD, params, requestCallback);
+ }
+
+ public void gitRemove(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_REMOVE, params, requestCallback);
+ }
+
+ public void gitDiscard(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_DISCARD, params, requestCallback);
+ }
+
+ public void gitRevert(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_REVERT, params, requestCallback);
+ }
+
+ public void gitStage(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_STAGE, params, requestCallback);
+ }
+
+ public void gitUnstage(ArrayList<String> paths,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray jsonPaths = JSONUtils.toJSONStringArray(paths);
+
+ JSONArray params = new JSONArray();
+ params.set(0, jsonPaths);
+ sendRequest(RPC_SCOPE, GIT_UNSTAGE, params, requestCallback);
+ }
+
+ @Override
+ public void gitAllStatus(ServerRequestCallback<AllStatus> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_ALL_STATUS, requestCallback);
+ }
+
+ @Override
+ public void gitFullStatus(ServerRequestCallback<JsArray<StatusAndPathInfo>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_FULL_STATUS, requestCallback);
+ }
+
+ @Override
+ public void gitListBranches(ServerRequestCallback<BranchesInfo> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_LIST_BRANCHES, requestCallback);
+ }
+
+ @Override
+ public void gitCheckout(String id,
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_CHECKOUT, id,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ public void gitCommit(String message,
+ boolean amend,
+ boolean signOff,
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(message));
+ params.set(1, JSONBoolean.getInstance(amend));
+ params.set(2, JSONBoolean.getInstance(signOff));
+ sendRequest(RPC_SCOPE, GIT_COMMIT, params,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ private class ConsoleProcessCallbackAdapter
+ extends ServerRequestCallback<ConsoleProcessInfo>
+ {
+ private ConsoleProcessCallbackAdapter(
+ ServerRequestCallback<ConsoleProcess> callback)
+ {
+ callback_ = callback;
+ }
+
+ @Override
+ public void onResponseReceived(ConsoleProcessInfo response)
+ {
+ pConsoleProcessFactory_.get().connectToProcess(response,
+ callback_);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback_.onError(error);
+ }
+
+ private final ServerRequestCallback<ConsoleProcess> callback_;
+ }
+
+ public void gitPush(ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_PUSH,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ @Override
+ public void vcsClone(VcsCloneOptions options,
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ VCS_CLONE,
+ options,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ public void gitPull(ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_PULL,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ @Override
+ public void askpassCompleted(String value, boolean remember,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, value == null ? JSONNull.getInstance()
+ : new JSONString(value));
+ params.set(1, JSONBoolean.getInstance(remember));
+ sendRequest(RPC_SCOPE, ASKPASS_COMPLETED, params, true, requestCallback);
+ }
+
+ @Override
+ public void createSshKey(CreateKeyOptions options,
+ ServerRequestCallback<CreateKeyResult> request)
+ {
+ sendRequest(RPC_SCOPE, CREATE_SSH_KEY, options, request);
+ }
+
+
+ @Override
+ public void gitSshPublicKey(String privateKeyPath,
+ ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GIT_SSH_PUBLIC_KEY,
+ privateKeyPath,
+ requestCallback);
+ }
+
+ @Override
+ public void gitHasRepo(String directory,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_HAS_REPO, directory, requestCallback);
+ }
+
+ @Override
+ public void gitInitRepo(String directory,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GIT_INIT_REPO, directory, requestCallback);
+ }
+
+ @Override
+ public void gitGetIgnores(String path,
+ ServerRequestCallback<ProcessResult> callback)
+ {
+ sendRequest(RPC_SCOPE, GIT_GET_IGNORES, path, callback);
+ }
+
+ @Override
+ public void gitSetIgnores(String path,
+ String ignores,
+ ServerRequestCallback<ProcessResult> callback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONString(ignores));
+ sendRequest(RPC_SCOPE, GIT_SET_IGNORES, params, callback);
+ }
+
+ @Override
+ public void gitGithubRemoteUrl(String view,
+ String path,
+ ServerRequestCallback<String> callback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(view));
+ params.set(1, new JSONString(path));
+ sendRequest(RPC_SCOPE, GIT_GITHUB_REMOTE_URL, params, callback);
+ }
+
+ @Override
+ public void gitDiffFile(String path,
+ PatchMode mode,
+ int contextLines,
+ boolean noSizeWarning,
+ ServerRequestCallback<DiffResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONNumber(mode.getValue()));
+ params.set(2, new JSONNumber(contextLines));
+ params.set(3, JSONBoolean.getInstance(noSizeWarning));
+ sendRequest(RPC_SCOPE, GIT_DIFF_FILE, params, requestCallback);
+ }
+
+ @Override
+ public void gitApplyPatch(String patch,
+ PatchMode mode,
+ String sourceEncoding,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(patch));
+ params.set(1, new JSONNumber(mode.getValue()));
+ params.set(2, new JSONString(sourceEncoding));
+ sendRequest(RPC_SCOPE, GIT_APPLY_PATCH, params, requestCallback);
+ }
+
+ public void gitHistoryCount(String spec,
+ FileSystemItem fileFilter,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(spec));
+ params.set(1, fileFilter != null ?
+ new JSONString(fileFilter.getPath()) : JSONNull.getInstance());
+ params.set(2, new JSONString(searchText));
+ sendRequest(RPC_SCOPE, GIT_HISTORY_COUNT, params, requestCallback);
+ }
+
+ @Override
+ public void gitHistory(String spec,
+ FileSystemItem fileFilter,
+ int skip,
+ int maxentries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(spec));
+ params.set(1, fileFilter != null ?
+ new JSONString(fileFilter.getPath()) : JSONNull.getInstance());
+ params.set(2, new JSONNumber(skip));
+ params.set(3, new JSONNumber(maxentries));
+ params.set(4, new JSONString(StringUtil.notNull(searchText)));
+ sendRequest(RPC_SCOPE, GIT_HISTORY, params, requestCallback);
+ }
+
+ @Override
+ public void gitShow(String rev,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(rev));
+ params.set(1, JSONBoolean.getInstance(noSizeWarning));
+
+ sendRequest(RPC_SCOPE, GIT_SHOW, params, requestCallback);
+ }
+
+ @Override
+ public void gitShowFile(String rev,
+ String filename,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(rev));
+ params.set(1, new JSONString(filename));
+ sendRequest(RPC_SCOPE, GIT_SHOW_FILE, params, requestCallback);
+ }
+
+ @Override
+ public void gitExportFile(String rev,
+ String filename,
+ String targetPath,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(rev));
+ params.set(1, new JSONString(filename));
+ params.set(2, new JSONString(targetPath));
+ sendRequest(RPC_SCOPE, GIT_EXPORT_FILE, params, requestCallback);
+ }
+
+
+ @Override
+ public void getPublicKey(ServerRequestCallback<PublicKeyInfo> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_PUBLIC_KEY, requestCallback);
+ }
+
+ @Override
+ public void listGet(String listName,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, LIST_GET, listName, requestCallback);
+ }
+
+ @Override
+ public void listSetContents(String listName,
+ ArrayList<String> list,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(listName));
+ params.set(1, new JSONArray(JsUtil.toJsArrayString(list)));
+
+ sendRequest(RPC_SCOPE, LIST_SET_CONTENTS, params, requestCallback);
+ }
+
+ @Override
+ public void listPrependItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ LIST_PREPEND_ITEM,
+ listName,
+ value,
+ requestCallback);
+ }
+
+ @Override
+ public void listAppendItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ LIST_APPEND_ITEM,
+ listName,
+ value,
+ requestCallback);
+ }
+
+ @Override
+ public void listRemoveItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ LIST_REMOVE_ITEM,
+ listName,
+ value,
+ requestCallback);
+ }
+
+ @Override
+ public void listClear(String listName,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, LIST_CLEAR, listName, requestCallback);
+ }
+
+ // package-visible methods for peer classes RemoteServerAuth and
+ // RemoveServerEventListener
+
+
+ boolean isDisconnected()
+ {
+ return disconnected_;
+ }
+
+ EventBus getEventBus()
+ {
+ return eventBus_;
+ }
+
+ RpcRequest getEvents(
+ int lastEventId,
+ ServerRequestCallback<JsArray<ClientEvent>> requestCallback,
+ RetryHandler retryHandler)
+ {
+ // satellite windows should never call getEvents directly!
+ if (satellite_.isCurrentWindowSatellite())
+ {
+ Debug.log("Satellite window shoudl not call getEvents!");
+ assert false;
+ }
+
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(lastEventId));
+ return sendRequest(EVENTS_SCOPE,
+ "get_events",
+ params,
+ false,
+ requestCallback,
+ retryHandler);
+ }
+
+ void handleUnauthorizedError()
+ {
+ // disconnect
+ disconnect();
+
+ // fire event
+ UnauthorizedEvent event = new UnauthorizedEvent();
+ eventBus_.fireEvent(event);
+ }
+
+ private <T> void sendRequest(String scope,
+ String method,
+ ServerRequestCallback<T> requestCallback)
+ {
+ sendRequest(scope, method, new JSONArray(), requestCallback);
+ }
+
+ private <T> void sendRequest(String scope,
+ String method,
+ boolean param,
+ ServerRequestCallback<T> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONBoolean.getInstance(param));
+ sendRequest(scope, method, params, requestCallback);
+ }
+
+ private <T> void sendRequest(String scope,
+ String method,
+ long param,
+ ServerRequestCallback<T> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(param));
+ sendRequest(scope, method, params, requestCallback);
+ }
+
+ private <T> void sendRequest(String scope,
+ String method,
+ String param,
+ ServerRequestCallback<T> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+
+ // pass JSONNull if the string is null
+ params.set(0, param != null ?
+ new JSONString(param) :
+ JSONNull.getInstance());
+
+ sendRequest(scope, method, params, requestCallback);
+ }
+
+ private <T> void sendRequest(String scope,
+ String method,
+ String param1,
+ String param2,
+ ServerRequestCallback<T> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+
+ // pass JSONNull if the string is null
+ params.set(0, param1 != null ? new JSONString(param1) :
+ JSONNull.getInstance());
+ params.set(1, param2 != null ? new JSONString(param2) :
+ JSONNull.getInstance());
+
+
+ sendRequest(scope, method, params, requestCallback);
+ }
+
+
+ private <T> void sendRequest(String scope,
+ String method,
+ JavaScriptObject param,
+ ServerRequestCallback<T> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+
+ // pass JSONNull if the object is null
+ params.set(0, param != null ? new JSONObject(param) :
+ JSONNull.getInstance());
+
+ sendRequest(scope, method, params, requestCallback);
+ }
+
+
+ private <T> void sendRequest(final String scope,
+ final String method,
+ final JSONArray params,
+ final ServerRequestCallback<T> requestCallback)
+ {
+ sendRequest(scope, method, params, false, requestCallback);
+ }
+
+ private <T> void sendRequest(final String scope,
+ final String method,
+ final JSONArray params,
+ final boolean redactLog,
+ final ServerRequestCallback<T> cb)
+ {
+ // if this is a satellite window then we handle this by proxying
+ // back through the main workbench window
+ if (satellite_.isCurrentWindowSatellite())
+ {
+ sendRequestViaMainWorkbench(scope, method, params, redactLog, cb);
+
+ }
+ // otherwise just a standard request with single retry
+ else
+ {
+ sendRequestWithRetry(scope, method, params, redactLog, cb);
+ }
+
+ }
+
+ private <T> void sendRequestWithRetry(
+ final String scope,
+ final String method,
+ final JSONArray params,
+ final boolean redactLog,
+ final ServerRequestCallback<T> requestCallback)
+ {
+ // retry handler (make the same call with the same params. ensure that
+ // only one retry occurs by passing null as the retryHandler)
+ RetryHandler retryHandler = new RetryHandler() {
+
+ public void onRetry()
+ {
+ // retry one time (passing null as last param ensures there
+ // is no retry handler installed)
+ sendRequest(scope,
+ method,
+ params,
+ redactLog,
+ requestCallback,
+ null);
+ }
+
+ public void onError(RpcError error)
+ {
+ // propagate error which caused the retry to the caller
+ requestCallback.onError(new RemoteServerError(error));
+ }
+ };
+
+ // submit request (retry same request up to one time)
+ sendRequest(scope,
+ method,
+ params,
+ redactLog,
+ requestCallback,
+ retryHandler);
+ }
+
+ // sendRequest method called for internal calls from main workbench
+ // (as opposed to proxied calls from satellites)
+ private <T> RpcRequest sendRequest(
+ String scope,
+ String method,
+ JSONArray params,
+ boolean redactLog,
+ final ServerRequestCallback<T> requestCallback,
+ RetryHandler retryHandler)
+ {
+ return sendRequest(
+ null,
+ scope,
+ method,
+ params,
+ redactLog,
+ new RpcResponseHandler()
+ {
+ @Override
+ public void onResponseReceived(RpcResponse response)
+ {
+ // ignore response if no request callback or
+ // if it was cancelled
+ if (requestCallback == null ||
+ requestCallback.cancelled())
+ return;
+
+ if (response.getError() != null)
+ {
+ requestCallback.onError(
+ new RemoteServerError(response.getError()));
+ }
+ else
+ {
+ T result = response.<T> getResult();
+ requestCallback.onResponseReceived(result);
+ }
+ }
+ },
+ retryHandler);
+
+ }
+
+ // lowest level sendRequest method -- called from the main workbench
+ // in two scenarios: direct internal call and servicing a proxied
+ // request from a satellite window
+ private RpcRequest sendRequest(String sourceWindow,
+ String scope,
+ String method,
+ JSONArray params,
+ boolean redactLog,
+ final RpcResponseHandler responseHandler,
+ final RetryHandler retryHandler)
+ {
+ // ensure we are listening for events. note that we do this here
+ // because we are no longer so aggressive about retrying on failed
+ // get_events calls. therefore, if we retry and fail a few times
+ // we may need to restart event listening.
+ ensureListeningForEvents();
+
+ // create request
+ String rserverURL = getApplicationURL(scope) + "/" + method;
+ RpcRequest rpcRequest = new RpcRequest(rserverURL,
+ method,
+ params,
+ null,
+ redactLog,
+ sourceWindow,
+ clientId_,
+ clientVersion_);
+
+ if (isDisconnected())
+ return rpcRequest;
+
+ // send the request
+ rpcRequest.send(new RpcRequestCallback() {
+ public void onError(RpcRequest request, RpcError error)
+ {
+ // ignore errors if we are disconnected
+ if (isDisconnected())
+ return;
+
+ // if we have a retry handler then see if we can resolve the
+ // error and then retry
+ if ( resolveRpcErrorAndRetry(error, retryHandler) )
+ return ;
+
+ // first crack goes to globally registered rpc error handlers
+ if (!handleRpcErrorInternally(error))
+ {
+ // no global handlers processed it, send on to caller
+ responseHandler.onResponseReceived(RpcResponse.create(error));
+ }
+ }
+
+ public void onResponseReceived(final RpcRequest request,
+ RpcResponse response)
+ {
+ // ignore response if we are disconnected
+ // - handler was cancelled
+ if (isDisconnected())
+ return;
+
+ // check for error
+ if (response.getError() != null)
+ {
+ // ERROR: explicit error returned by server
+ RpcError error = response.getError();
+
+ // if we have a retry handler then see if we can resolve the
+ // error and then retry
+ if ( resolveRpcErrorAndRetry(error, retryHandler) )
+ return ;
+
+ // give first crack to internal handlers, then forward to caller
+ if (!handleRpcErrorInternally(error))
+ responseHandler.onResponseReceived(response);
+ }
+ else if (response.getAsyncHandle() != null)
+ {
+ serverEventListener_.registerAsyncHandle(
+ response.getAsyncHandle(),
+ request,
+ this);
+ }
+ // no error, process the result
+ else
+ {
+ // no error, forward to caller
+ responseHandler.onResponseReceived(response);
+
+ // always ensure that the event source receives events unless
+ // the server specifically flags us that no events are likely
+ // to be pending (e.g. an rpc call where no events were added
+ // to the queue by the call)
+ if (eventsPending(response))
+ serverEventListener_.ensureEvents();
+ }
+ }
+ });
+
+ // return the request
+ return rpcRequest;
+ }
+
+ private void ensureListeningForEvents()
+ {
+ // don't do this if we are disconnected
+ if (isDisconnected())
+ return;
+
+ // if we are in a mode where we are listening for events (running
+ // as the main workbench) then ensure we are listening
+
+ // we need the listeningForEvents_ flag because we don't want to cause
+ // events to flow prior to the workbench being instantiated and fully
+ // initialized. since this method can be called at any time we need to
+ // protect ourselves against this "pre-workbench initialization" state
+
+ // the retries are there to work around the fact that when we execute a
+ // network request which causes us to resume from a suspended session
+ // the first query for events often returns ServiceUnavailable because
+ // the process isn't alive yet. by retrying we make certain that if
+ // the first attempts to listen fail we eventually get synced up
+
+ if (listeningForEvents_)
+ serverEventListener_.ensureListening(10);
+ }
+
+ private boolean eventsPending(RpcResponse response)
+ {
+ String eventsPending = response.getField("ep");
+ if (eventsPending == null)
+ return true ; // default to true for json-rpc compactness
+ else
+ return Boolean.parseBoolean(eventsPending);
+ }
+
+ private boolean resolveRpcErrorAndRetry(final RpcError error,
+ final RetryHandler retryHandler)
+ {
+ // won't even attempt resolve if we don't have a retryHandler
+ if (retryHandler == null)
+ return false;
+
+ // can attempt to resolve UNAUTHORIZED by updating credentials
+ if (error.getCode() == RpcError.UNAUTHORIZED)
+ {
+ // check credentials
+ serverAuth_.updateCredentials(new ServerRequestCallback<Integer>() {
+
+ @Override
+ public void onResponseReceived(Integer response)
+ {
+ // allow retry on success, otherwise handle unauthorized error
+ if (response.intValue() ==
+ RemoteServerAuth.CREDENTIALS_UPDATE_SUCCESS)
+ {
+ retryHandler.onRetry();
+ }
+ else
+ {
+ handleUnauthorizedError();
+ }
+ }
+
+ @Override
+ public void onError(ServerError serverError)
+ {
+ // log the auth sequence error
+ Debug.logError(serverError);
+
+ // unable to resolve unauthorized error through a
+ // credentials check -- treat as unauthorized
+ handleUnauthorizedError();
+ }
+ });
+
+ // attempting to resolve
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private boolean handleRpcErrorInternally(RpcError error)
+ {
+ if (error.getCode() == RpcError.UNAUTHORIZED)
+ {
+ handleUnauthorizedError();
+ return true;
+ }
+ else if (error.getCode() == RpcError.INVALID_CLIENT_ID)
+ {
+ // disconnect
+ disconnect();
+
+ // fire event
+ ClientDisconnectedEvent event = new ClientDisconnectedEvent();
+ eventBus_.fireEvent(event);
+
+ // handled
+ return true;
+ }
+ else if (error.getCode() == RpcError.INVALID_CLIENT_VERSION)
+ {
+ // disconnect
+ disconnect();
+
+ // fire event
+ InvalidClientVersionEvent event = new InvalidClientVersionEvent();
+ eventBus_.fireEvent(event);
+
+ // handled
+ return true;
+ }
+ else if (error.getCode() == RpcError.SERVER_OFFLINE)
+ {
+ // disconnect
+ disconnect();
+
+ // fire event
+ ServerOfflineEvent event = new ServerOfflineEvent();
+ eventBus_.fireEvent(event);
+
+ // handled
+ return true;
+
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ // the following sequence of calls enables marsahlling of remote server
+ // requests from satellite windows back into the main workbench window
+
+ // this code sets up the sendRemoteServerRequest global callback within
+ // the main workbench
+ private native void registerSatelliteCallback() /*-{
+ var server = this;
+ $wnd.sendRemoteServerRequest = $entry(
+ function(sourceWindow, scope, method, params, redactLog, responseCallback) {
+ server. at org.rstudio.studio.client.server.remote.RemoteServer::sendRemoteServerRequest(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLcom/google/gwt/core/client/JavaScriptObject;)(sourceWindow, scope, method, params, redactLog, responseCallback);
+ }
+ );
+ }-*/;
+
+ // this code runs in the main workbench and implements the server request
+ // and then calls back the satellite on the provided js responseCallback
+ private void sendRemoteServerRequest(final JavaScriptObject sourceWindow,
+ final String scope,
+ final String method,
+ final String params,
+ final boolean redactLog,
+ final JavaScriptObject responseCallback)
+ {
+ // get the WindowEx from the sourceWindow
+ final WindowEx srcWnd = sourceWindow.<WindowEx>cast();
+
+ // get the json array from the string
+ final JSONArray jsonParams = JSONParser.parseStrict(params).isArray();
+
+ // setup an rpc response handler that proxies back to the js object
+ class ResponseHandler extends RpcResponseHandler
+ {
+ @Override
+ public void onResponseReceived(RpcResponse response)
+ {
+ if (!srcWnd.isClosed())
+ performCallback(responseCallback, response);
+ }
+
+ public void onError(RpcError error)
+ {
+ RpcResponse errorResponse = RpcResponse.create(error);
+ if (!srcWnd.isClosed())
+ performCallback(responseCallback, errorResponse);
+ }
+
+ private native void performCallback(JavaScriptObject responseCallback,
+ RpcResponse response) /*-{
+ responseCallback.onResponse(response);
+ }-*/;
+ };
+ final ResponseHandler responseHandler = new ResponseHandler();
+
+ // setup a retry handler which will call back the second time with
+ // the same args (but no retryHandler, ensurin at most 1 retry)
+ RetryHandler retryHandler = new RetryHandler() {
+
+ public void onRetry()
+ {
+ // retry one time (passing null as last param ensures there
+ // is no retry handler installed)
+ sendRequest(getSourceWindowName(sourceWindow),
+ scope,
+ method,
+ jsonParams,
+ redactLog,
+ responseHandler,
+ null);
+ }
+
+ public void onError(RpcError error)
+ {
+ // propagate error which caused the retry to the caller
+ responseHandler.onError(error);
+ }
+ };
+
+ // submit request (retry same request up to one time)
+ sendRequest(getSourceWindowName(sourceWindow),
+ scope,
+ method,
+ jsonParams,
+ redactLog,
+ responseHandler,
+ retryHandler);
+ }
+
+ private native String getSourceWindowName(JavaScriptObject sourceWindow) /*-{
+ return sourceWindow.RStudioSatelliteName;
+ }-*/;
+
+ // call made from satellite -- this delegates to a native method which
+ // sets up a javascript callback and then calls the main workbench
+ private <T> void sendRequestViaMainWorkbench(
+ String scope,
+ String method,
+ JSONArray params,
+ boolean redactLog,
+ final ServerRequestCallback<T> requestCallback)
+ {
+ sendRequestViaMainWorkbench(
+ scope,
+ method,
+ params.toString(),
+ redactLog,
+ new RpcResponseHandler() {
+ @Override
+ public void onResponseReceived(RpcResponse response)
+ {
+ if (response.getError() != null)
+ {
+ RpcError error = response.getError();
+ requestCallback.onError(new RemoteServerError(error));
+ }
+ else
+ {
+ T result = response.<T> getResult();
+ requestCallback.onResponseReceived(result);
+ }
+
+ }
+ });
+ }
+
+ // call from satellite to sendRemoteServerRequest method made available
+ // by main workbench
+ private native void sendRequestViaMainWorkbench(
+ String scope,
+ String method,
+ String params,
+ boolean redactLog,
+ RpcResponseHandler handler) /*-{
+
+ var responseCallback = new Object();
+ responseCallback.onResponse = $entry(function(response) {
+ handler. at org.rstudio.core.client.jsonrpc.RpcResponseHandler::onResponseReceived(Lorg/rstudio/core/client/jsonrpc/RpcResponse;)(response);
+ });
+
+ $wnd.opener.sendRemoteServerRequest($wnd,
+ scope,
+ method,
+ params,
+ redactLog,
+ responseCallback);
+ }-*/;
+
+ @Override
+ public void svnAdd(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONUtils.toJSONStringArray(paths));
+ sendRequest(RPC_SCOPE, SVN_ADD, params, requestCallback);
+ }
+
+ @Override
+ public void svnDelete(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONUtils.toJSONStringArray(paths));
+ sendRequest(RPC_SCOPE, SVN_DELETE, params, requestCallback);
+ }
+
+ @Override
+ public void svnRevert(ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONUtils.toJSONStringArray(paths));
+ sendRequest(RPC_SCOPE, SVN_REVERT, params, requestCallback);
+ }
+
+ @Override
+ public void svnResolve(String accept,
+ ArrayList<String> paths,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(accept));
+ params.set(1, JSONUtils.toJSONStringArray(paths));
+ sendRequest(RPC_SCOPE, SVN_RESOLVE, params, requestCallback);
+ }
+
+ @Override
+ public void svnStatus(ServerRequestCallback<JsArray<StatusAndPathInfo>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SVN_STATUS, requestCallback);
+ }
+
+ @Override
+ public void svnUpdate(ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SVN_UPDATE,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ @Override
+ public void svnCleanup( ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SVN_CLEANUP, requestCallback);
+ }
+
+
+ @Override
+ public void svnCommit(ArrayList<String> paths,
+ String message,
+ ServerRequestCallback<ConsoleProcess> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONUtils.toJSONStringArray(paths));
+ params.set(1, new JSONString(message));
+
+ sendRequest(RPC_SCOPE, SVN_COMMIT, params,
+ new ConsoleProcessCallbackAdapter(requestCallback));
+ }
+
+ @Override
+ public void svnDiffFile(String path,
+ Integer contextLines,
+ boolean noSizeWarning,
+ ServerRequestCallback<DiffResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONNumber(contextLines));
+ params.set(2, JSONBoolean.getInstance(noSizeWarning));
+ sendRequest(RPC_SCOPE, SVN_DIFF_FILE, params, requestCallback);
+ }
+
+ @Override
+ public void svnApplyPatch(String path,
+ String patch,
+ String sourceEncoding,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONString(patch));
+ params.set(2, new JSONString(sourceEncoding));
+ sendRequest(RPC_SCOPE, SVN_APPLY_PATCH, params, requestCallback);
+ }
+
+ @Override
+ public void svnHistoryCount(int revision,
+ FileSystemItem path,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(revision));
+ params.set(1, path == null ? JSONNull.getInstance()
+ : new JSONString(path.getPath()));
+ params.set(2, new JSONString(StringUtil.notNull(searchText)));
+
+ sendRequest(RPC_SCOPE, SVN_HISTORY_COUNT, params, requestCallback);
+ }
+
+ @Override
+ public void svnHistory(int revision,
+ FileSystemItem path,
+ int skip,
+ int maxentries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(revision));
+ params.set(1, path == null ? JSONNull.getInstance()
+ : new JSONString(path.getPath()));
+ params.set(2, new JSONNumber(skip));
+ params.set(3, new JSONNumber(maxentries));
+ params.set(4, new JSONString(StringUtil.notNull(searchText)));
+
+ sendRequest(RPC_SCOPE, SVN_HISTORY, params, requestCallback);
+ }
+
+ @Override
+ public void svnShow(int rev,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(rev));
+ params.set(1, JSONBoolean.getInstance(noSizeWarning));
+
+ sendRequest(RPC_SCOPE, SVN_SHOW, params, requestCallback);
+ }
+
+ @Override
+ public void svnShowFile(int rev,
+ String filename,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(rev));
+ params.set(1, new JSONString(filename));
+ sendRequest(RPC_SCOPE, SVN_SHOW_FILE, params, requestCallback);
+ }
+
+ public void svnGetIgnores(
+ String path,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SVN_GET_IGNORES, path, requestCallback);
+ }
+
+ public void svnSetIgnores(String path,
+ String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(path));
+ params.set(1, new JSONString(ignores));
+ sendRequest(RPC_SCOPE, SVN_SET_IGNORES, params, requestCallback);
+ }
+
+ @Override
+ public void viewerStopped(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "viewer_stopped", requestCallback);
+ }
+
+
+ public void previewHTML(HTMLPreviewParams params,
+ ServerRequestCallback<Boolean> callback)
+ {
+ sendRequest(RPC_SCOPE, PREVIEW_HTML, params, callback);
+ }
+
+ public void terminatePreviewHTML(ServerRequestCallback<Void> callback)
+ {
+ sendRequest(RPC_SCOPE, TERMINATE_PREVIEW_HTML, callback);
+ }
+
+ public void getHTMLCapabilities(
+ ServerRequestCallback<HTMLCapabilities> callback)
+ {
+ sendRequest(RPC_SCOPE, GET_HTML_CAPABILITIES, callback);
+ }
+
+ public void rpubsIsPublished(String htmlFile,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "rpubs_is_published", htmlFile, requestCallback);
+ }
+
+ public void rpubsUpload(String contextId,
+ String title,
+ String htmlFile,
+ boolean isUpdate,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(contextId));
+ params.set(1, new JSONString(title));
+ params.set(2, new JSONString(htmlFile));
+ params.set(3, JSONBoolean.getInstance(isUpdate));
+ sendRequest(RPC_SCOPE, RPUBS_UPLOAD, params, requestCallback);
+ }
+
+ public void rpubsTerminateUpload(String contextId,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ RPUBS_TERMINATE_UPLOAD,
+ contextId,
+ requestCallback);
+ }
+
+ @Override
+ public void setPresentationSlideIndex(
+ int index,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SET_PRESENTATION_SLIDE_INDEX, index, requestCallback);
+ }
+
+ @Override
+ public void setWorkingDirectory(String path,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ SET_WORKING_DIRECTORY,
+ path,
+ requestCallback);
+ }
+
+ @Override
+ public void createStandalonePresentation(
+ String targetFile,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ CREATE_STANDALONE_PRESENTATION,
+ StringUtil.notNull(targetFile),
+ requestCallback);
+ }
+
+ @Override
+ public void createDesktopViewInBrowserPresentation(
+ ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ CREATE_DESKTOP_VIEW_IN_BROWSER_PRESENTATION,
+ requestCallback);
+ }
+
+
+ @Override
+ public void createPresentationRPubsSource(
+ ServerRequestCallback<PresentationRPubsSource> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ CREATE_PRESENTATION_RPUBS_SOURCE,
+ requestCallback);
+ }
+
+ @Override
+ public void presentationExecuteCode(
+ String code,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, PRESENTATION_EXECUTE_CODE, code, requestCallback);
+ }
+
+ @Override
+ public void createNewPresentation(
+ String filePath,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CREATE_NEW_PRESENTATION, filePath, requestCallback);
+ }
+
+ @Override
+ public void showPresentationPane(String filePath,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, SHOW_PRESENTATION_PANE, filePath, requestCallback);
+ }
+
+ @Override
+ public void closePresentationPane(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLOSE_PRESENTATION_PANE, requestCallback);
+ }
+
+ @Override
+ public void tutorialFeedback(String feedback,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, TUTORIAL_FEEDBACK, feedback, requestCallback);
+ }
+
+ @Override
+ public void tutorialQuizResponse(
+ int slideIndex, int answer, boolean correct,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(slideIndex));
+ params.set(1, new JSONNumber(answer));
+ params.set(2, JSONBoolean.getInstance(correct));
+ sendRequest(RPC_SCOPE, TUTORIAL_QUIZ_RESPONSE, params, requestCallback);
+ }
+
+
+ @Override
+ public void getSlideNavigationForFile(
+ String filePath,
+ ServerRequestCallback<SlideNavigation> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_SLIDE_NAVIGATION_FOR_FILE,
+ filePath,
+ requestCallback);
+ }
+
+ @Override
+ public void getSlideNavigationForCode(
+ String code,
+ String baseDir,
+ ServerRequestCallback<SlideNavigation> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(code));
+ params.set(1, new JSONString(baseDir));
+ sendRequest(RPC_SCOPE,
+ GET_SLIDE_NAVIGATION_FOR_CODE,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void clearPresentationCache(
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, CLEAR_PRESENTATION_CACHE, requestCallback);
+ }
+
+
+ public void compilePdf(FileSystemItem targetFile,
+ String encoding,
+ SourceLocation sourceLocation,
+ String completedAction,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(targetFile.getPath()));
+ params.set(1, new JSONString(encoding));
+ params.set(2, new JSONObject(sourceLocation));
+ params.set(3, new JSONString(completedAction));
+ sendRequest(RPC_SCOPE, COMPILE_PDF, params, requestCallback);
+ }
+
+ public void isCompilePdfRunning(ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, IS_COMPILE_PDF_RUNNING, requestCallback);
+ }
+
+ public void terminateCompilePdf(
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, TERMINATE_COMPILE_PDF, requestCallback);
+ }
+
+ public void compilePdfClosed(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, COMPILE_PDF_CLOSED, requestCallback);
+ }
+
+ @Override
+ public void synctexForwardSearch(String rootDocument,
+ SourceLocation sourceLocation,
+ ServerRequestCallback<PdfLocation> callback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(rootDocument));
+ params.set(1, new JSONObject(sourceLocation));
+ sendRequest(RPC_SCOPE, SYNCTEX_FORWARD_SEARCH, params, callback);
+ }
+
+ @Override
+ public void applyForwardConcordance(
+ String rootDocument,
+ SourceLocation sourceLocation,
+ ServerRequestCallback<SourceLocation> callback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(rootDocument));
+ params.set(1, new JSONObject(sourceLocation));
+ sendRequest(RPC_SCOPE, APPLY_FORWARD_CONCORDANCE, params, callback);
+ }
+
+ @Override
+ public void synctexInverseSearch(PdfLocation pdfLocation,
+ ServerRequestCallback<SourceLocation> callback)
+ {
+ sendRequest(RPC_SCOPE, SYNCTEX_INVERSE_SEARCH, pdfLocation, callback);
+ }
+
+ @Override
+ public void applyInverseConcordance(
+ SourceLocation sourceLocation,
+ ServerRequestCallback<SourceLocation> callback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONObject(sourceLocation));
+ sendRequest(RPC_SCOPE, APPLY_INVERSE_CONCORDANCE, params, callback);
+ }
+
+
+ public void checkSpelling(
+ JsArrayString words,
+ ServerRequestCallback<JsArrayInteger> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONArray(words));
+ sendRequest(RPC_SCOPE, CHECK_SPELLING, params, requestCallback);
+ }
+
+ public void suggestionList(
+ String word,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(word));
+ sendRequest(RPC_SCOPE, SUGGESTION_LIST, params, requestCallback);
+ }
+
+ @Override
+ public void getWordChars(ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "get_word_chars", requestCallback);
+ }
+
+ public void addCustomDictionary(
+ String dictPath,
+ ServerRequestCallback<JsArrayString> callback)
+ {
+ sendRequest(RPC_SCOPE, ADD_CUSTOM_DICTIONARY, dictPath, callback);
+ }
+
+ public void removeCustomDictionary(
+ String name,
+ ServerRequestCallback<JsArrayString> callback)
+ {
+ sendRequest(RPC_SCOPE, REMOVE_CUSTOM_DICTIONARY, name, callback);
+ }
+
+
+ public void installAllDictionaries(
+ ServerRequestCallback<SpellingPrefsContext> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, INSTALL_ALL_DICTIONARIES, requestCallback);
+ }
+
+ @Override
+ public void beginFind(String searchString,
+ boolean regex,
+ boolean ignoreCase,
+ FileSystemItem directory,
+ JsArrayString filePatterns,
+ ServerRequestCallback<String> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(searchString));
+ params.set(1, JSONBoolean.getInstance(regex));
+ params.set(2, JSONBoolean.getInstance(ignoreCase));
+ params.set(3, new JSONString(directory == null ? ""
+ : directory.getPath()));
+ params.set(4, new JSONArray(filePatterns));
+ sendRequest(RPC_SCOPE, BEGIN_FIND, params, requestCallback);
+ }
+
+ @Override
+ public void stopFind(String findOperationHandle,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, STOP_FIND, findOperationHandle, requestCallback);
+ }
+
+ @Override
+ public void clearFindResults(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, "clear_find_results", requestCallback);
+ }
+
+ @Override
+ public void getCppCapabilities(
+ ServerRequestCallback<CppCapabilities> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, GET_CPP_CAPABILITIES, requestCallback);
+ }
+
+ @Override
+ public void startBuild(String type,
+ ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, START_BUILD, type, requestCallback);
+ }
+
+ @Override
+ public void terminateBuild(ServerRequestCallback<Boolean> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, TERMINATE_BUILD, requestCallback);
+ }
+
+ @Override
+ public void devtoolsLoadAllPath(
+ ServerRequestCallback<String> requestCallback)
+ {
+ sendRequest(RPC_SCOPE, DEVTOOLS_LOAD_ALL_PATH, requestCallback);
+ }
+
+ @Override
+ public void listEnvironment(ServerRequestCallback<JsArray<RObject>> callback)
+ {
+ sendRequest(RPC_SCOPE, LIST_ENVIRONMENT, callback);
+ }
+
+ @Override
+ public void setContextDepth(int newContextDepth,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ SET_CONTEXT_DEPTH,
+ newContextDepth,
+ requestCallback);
+ }
+
+ @Override
+ public void setEnvironment(String environmentName,
+ ServerRequestCallback<Void> requestCallback)
+ {
+
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(environmentName));
+ sendRequest(RPC_SCOPE,
+ SET_ENVIRONMENT,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void setEnvironmentFrame(int frame,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(frame));
+ sendRequest(RPC_SCOPE,
+ SET_ENVIRONMENT_FRAME,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getEnvironmentNames(
+ ServerRequestCallback<JsArray<EnvironmentFrame>> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_ENVIRONMENT_NAMES,
+ requestCallback);
+ }
+
+ @Override
+ public void getEnvironmentState(
+ ServerRequestCallback<EnvironmentContextData> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_ENVIRONMENT_STATE,
+ requestCallback);
+ }
+
+ @Override
+ public void requeryContext(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ REQUERY_CONTEXT,
+ requestCallback);
+ }
+
+ @Override
+ public void getObjectContents(
+ String objectName,
+ ServerRequestCallback<ObjectContents> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(objectName));
+ sendRequest(RPC_SCOPE,
+ GET_OBJECT_CONTENTS,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getFunctionSteps(
+ String functionName,
+ String fileName,
+ String packageName,
+ int[] lineNumbers,
+ ServerRequestCallback<JsArray<FunctionSteps>> requestCallback)
+ {
+ JSONArray lineNums = new JSONArray();
+ for (int idx = 0; idx < lineNumbers.length; idx++)
+ {
+ lineNums.set(idx, new JSONNumber(lineNumbers[idx]));
+ }
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(functionName));
+ params.set(1, new JSONString(fileName));
+ params.set(2, new JSONString(packageName));
+ params.set(3, lineNums);
+ sendRequest(RPC_SCOPE,
+ GET_FUNCTION_STEPS,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void setFunctionBreakpoints(
+ String functionName,
+ String fileName,
+ String packageName,
+ ArrayList<String> steps,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray breakSteps = new JSONArray();
+ for (int idx = 0; idx < steps.size(); idx++)
+ {
+ breakSteps.set(idx, new JSONString(steps.get(idx)));
+ }
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(functionName));
+ params.set(1, new JSONString(fileName));
+ params.set(2, new JSONString(packageName));
+ params.set(3, breakSteps);
+ sendRequest(RPC_SCOPE,
+ SET_FUNCTION_BREAKPOINTS,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getFunctionState(
+ String functionName,
+ String fileName,
+ int lineNumber,
+ ServerRequestCallback<FunctionState> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(functionName));
+ params.set(1, new JSONString(fileName));
+ params.set(2, new JSONNumber(lineNumber));
+ sendRequest(RPC_SCOPE,
+ GET_FUNCTION_STATE,
+ params,
+ requestCallback);
+ }
+
+ public void executeDebugSource(
+ String fileName,
+ ArrayList<Integer> topBreakLines,
+ ArrayList<Integer> debugBreakLines,
+ int step,
+ int mode,
+ ServerRequestCallback<TopLevelLineData> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(fileName));
+ params.set(1, JSONUtils.toJSONNumberArray(topBreakLines));
+ params.set(2, JSONUtils.toJSONNumberArray(debugBreakLines));
+ params.set(3, new JSONNumber(step));
+ params.set(4, new JSONNumber(mode));
+
+ sendRequest(RPC_SCOPE,
+ EXECUTE_DEBUG_SOURCE,
+ params,
+ requestCallback);
+ }
+
+ public void setErrorManagementType(
+ int type,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(type));
+
+ sendRequest(RPC_SCOPE,
+ SET_ERROR_MANAGEMENT_TYPE,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void updateBreakpoints(ArrayList<Breakpoint> breakpoints,
+ boolean set, boolean arm, ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray bps = new JSONArray();
+ for (int i = 0; i < breakpoints.size(); i++)
+ {
+ bps.set(i, new JSONObject(breakpoints.get(i)));
+ }
+
+ JSONArray params = new JSONArray();
+ params.set(0, bps);
+ params.set(1, JSONBoolean.getInstance(set));
+ params.set(2, JSONBoolean.getInstance(arm));
+ sendRequest(RPC_SCOPE,
+ UPDATE_BREAKPOINTS,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void removeAllBreakpoints(ServerRequestCallback<Void> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ REMOVE_ALL_BREAKPOINTS,
+ requestCallback);
+ }
+
+ @Override
+ public void checkForUpdates(
+ boolean manual,
+ ServerRequestCallback<UpdateCheckResult> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, JSONBoolean.getInstance(manual));
+ sendRequest(RPC_SCOPE,
+ CHECK_FOR_UPDATES,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getProductInfo(ServerRequestCallback<ProductInfo> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_PRODUCT_INFO,
+ requestCallback);
+ }
+
+ @Override
+ public void getShinyViewerType(ServerRequestCallback<ShinyViewerType> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_SHINY_VIEWER_TYPE,
+ requestCallback);
+ }
+
+ @Override
+ public void setShinyViewerType(int viewerType,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONNumber(viewerType));
+ sendRequest(RPC_SCOPE,
+ SET_SHINY_VIEWER_TYPE,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getShinyRunCmd(String shinyAppDir,
+ ServerRequestCallback<ShinyRunCmd> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(shinyAppDir));
+ sendRequest(RPC_SCOPE,
+ GET_SHINY_RUN_CMD,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getShinyAppsAccountList(
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ sendRequest(RPC_SCOPE,
+ GET_SHINYAPPS_ACCOUNT_LIST,
+ requestCallback);
+ }
+
+ @Override
+ public void removeShinyAppsAccount(String accountName,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(accountName));
+ sendRequest(RPC_SCOPE,
+ REMOVE_SHINYAPPS_ACCOUNT,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void connectShinyAppsAccount(String command,
+ ServerRequestCallback<Void> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(command));
+ sendRequest(RPC_SCOPE,
+ CONNECT_SHINYAPPS_ACCOUNT,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getShinyAppsAppList(
+ String accountName,
+ ServerRequestCallback<JsArray<ShinyAppsApplicationInfo>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(accountName));
+ sendRequest(RPC_SCOPE,
+ GET_SHINYAPPS_APP_LIST,
+ params,
+ requestCallback);
+ }
+
+ @Override
+ public void getShinyAppsDeployments(
+ String dir,
+ ServerRequestCallback<JsArray<ShinyAppsDeploymentRecord>> requestCallback)
+ {
+ JSONArray params = new JSONArray();
+ params.set(0, new JSONString(dir));
+ sendRequest(RPC_SCOPE,
+ GET_SHINYAPPS_DEPLOYMENTS,
+ params,
+ requestCallback);
+ }
+
+ private String clientId_;
+ private double clientVersion_ = 0;
+ private boolean listeningForEvents_;
+ private boolean disconnected_;
+
+ private final RemoteServerAuth serverAuth_;
+ private final RemoteServerEventListener serverEventListener_ ;
+
+ private final Provider<ConsoleProcessFactory> pConsoleProcessFactory_;
+
+ private final Session session_;
+ private final EventBus eventBus_;
+ private final Satellite satellite_;
+
+ // url scopes
+ private static final String RPC_SCOPE = "rpc";
+ private static final String FILES_SCOPE = "files";
+ private static final String EVENTS_SCOPE = "events";
+ private static final String UPLOAD_SCOPE = "upload";
+ private static final String EXPORT_SCOPE = "export";
+ private static final String GRAPHICS_SCOPE = "graphics";
+ private static final String SOURCE_SCOPE = "source";
+ private static final String LOG_SCOPE = "log";
+ private static final String META_SCOPE = "meta";
+ private static final String FILE_SHOW = "file_show";
+
+ // session methods
+ private static final String CLIENT_INIT = "client_init";
+ private static final String ACCEPT_AGREEMENT = "accept_agreement";
+ private static final String SUSPEND_SESSION = "suspend_session";
+ private static final String HANDLE_UNSAVED_CHANGES_COMPLETED = "handle_unsaved_changes_completed";
+ private static final String QUIT_SESSION = "quit_session";
+ private static final String SUSPEND_FOR_RESTART = "suspend_for_restart";
+ private static final String PING="ping";
+
+ private static final String SET_WORKBENCH_METRICS = "set_workbench_metrics";
+ private static final String SET_PREFS = "set_prefs";
+ private static final String SET_UI_PREFS = "set_ui_prefs";
+ private static final String GET_R_PREFS = "get_r_prefs";
+ private static final String SET_CLIENT_STATE = "set_client_state";
+ private static final String USER_PROMPT_COMPLETED = "user_prompt_completed";
+ private static final String GET_TERMINAL_OPTIONS = "get_terminal_options";
+ private static final String START_SHELL_DIALOG = "start_shell_dialog";
+ private static final String SEARCH_CODE = "search_code";
+ private static final String GET_SEARCH_PATH_FUNCTION_DEFINITION = "get_search_path_function_definition";
+ private static final String GET_METHOD_DEFINITION = "get_method_definition";
+ private static final String GET_FUNCTION_DEFINITION = "get_function_definition";
+ private static final String FIND_FUNCTION_IN_SEARCH_PATH = "find_function_in_search_path";
+
+ private static final String CONSOLE_INPUT = "console_input";
+ private static final String RESET_CONSOLE_ACTIONS = "reset_console_actions";
+ private static final String INTERRUPT = "interrupt";
+ private static final String ABORT = "abort";
+ private static final String GET_COMPLETIONS = "get_completions";
+ private static final String GET_HELP_AT_CURSOR = "get_help_at_cursor";
+
+ private static final String PROCESS_START = "process_start";
+ private static final String PROCESS_INTERRUPT = "process_interrupt";
+ private static final String PROCESS_REAP = "process_reap";
+ private static final String PROCESS_WRITE_STDIN = "process_write_stdin";
+
+ private static final String REMOVE_ALL_OBJECTS = "remove_all_objects";
+ private static final String REMOVE_OBJECTS = "remove_objects";
+ private static final String DOWNLOAD_DATA_FILE = "download_data_file";
+ private static final String GET_DATA_PREVIEW = "get_data_preview";
+ private static final String GET_OUTPUT_PREVIEW = "get_output_preview";
+
+ private static final String EDIT_COMPLETED = "edit_completed";
+ private static final String CHOOSE_FILE_COMPLETED = "choose_file_completed";
+
+ private static final String LIST_PACKAGES = "list_packages";
+ private static final String AVAILABLE_PACKAGES = "available_packages";
+ private static final String CHECK_FOR_PACKAGE_UPDATES = "check_for_package_updates";
+ private static final String INIT_DEFAULT_USER_LIBRARY = "init_default_user_library";
+ private static final String LOADED_PACKAGE_UPDATES_REQUIRED = "loaded_package_updates_required";
+ private static final String IGNORE_NEXT_LOADED_PACKAGE_CHECK = "ignore_next_loaded_package_check";
+ private static final String GET_PACKAGE_INSTALL_CONTEXT = "get_package_install_context";
+ private static final String IS_PACKAGE_LOADED = "is_package_loaded";
+ private static final String SET_CRAN_MIRROR = "set_cran_mirror";
+ private static final String GET_CRAN_MIRRORS = "get_cran_mirrors";
+
+ private static final String GET_HELP = "get_help";
+ private static final String SHOW_HELP_TOPIC = "show_help_topic" ;
+ private static final String SEARCH = "search" ;
+
+ private static final String STAT = "stat";
+ private static final String IS_TEXT_FILE = "is_text_file";
+ private static final String LIST_FILES = "list_files";
+ private static final String LIST_ALL_FILES = "list_all_files";
+ private static final String CREATE_FOLDER = "create_folder";
+ private static final String DELETE_FILES = "delete_files";
+ private static final String COPY_FILE = "copy_file";
+ private static final String MOVE_FILES = "move_files";
+ private static final String RENAME_FILE = "rename_file";
+ private static final String COMPLETE_UPLOAD = "complete_upload";
+
+ private static final String NEXT_PLOT = "next_plot";
+ private static final String PREVIOUS_PLOT = "previous_plot";
+ private static final String REMOVE_PLOT = "remove_plot";
+ private static final String CLEAR_PLOTS = "clear_plots";
+ private static final String REFRESH_PLOT = "refresh_plot";
+ private static final String SAVE_PLOT_AS = "save_plot_as";
+ private static final String SAVE_PLOT_AS_PDF = "save_plot_as_pdf";
+ private static final String COPY_PLOT_TO_CLIPBOARD_METAFILE = "copy_plot_to_clipboard_metafile";
+ private static final String GET_UNIQUE_SAVE_PLOT_STEM = "get_unique_save_plot_stem";
+ private static final String GET_SAVE_PLOT_CONTEXT = "get_save_plot_context";
+ private static final String LOCATOR_COMPLETED = "locator_completed";
+ private static final String SET_MANIPULATOR_VALUES = "set_manipulator_values";
+ private static final String MANIPULATOR_PLOT_CLICKED = "manipulator_plot_clicked";
+
+ private static final String GET_NEW_PROJECT_CONTEXT = "get_new_project_context";
+ private static final String CREATE_PROJECT = "create_project";
+ private static final String READ_PROJECT_OPTIONS = "read_project_options";
+ private static final String WRITE_PROJECT_OPTIONS = "write_project_options";
+ private static final String WRITE_PROJECT_VCS_OPTIONS = "write_project_vcs_options";
+
+ private static final String NEW_DOCUMENT = "new_document";
+ private static final String OPEN_DOCUMENT = "open_document";
+ private static final String SAVE_DOCUMENT = "save_document";
+ private static final String SAVE_DOCUMENT_DIFF = "save_document_diff";
+ private static final String CHECK_FOR_EXTERNAL_EDIT = "check_for_external_edit";
+ private static final String IGNORE_EXTERNAL_EDIT = "ignore_external_edit";
+ private static final String CLOSE_DOCUMENT = "close_document";
+ private static final String CLOSE_ALL_DOCUMENTS = "close_all_documents";
+ private static final String GET_SOURCE_TEMPLATE = "get_source_template";
+ private static final String CREATE_RD_SHELL = "create_rd_shell";
+ private static final String SET_SOURCE_DOCUMENT_ON_SAVE = "set_source_document_on_save";
+ private static final String SAVE_ACTIVE_DOCUMENT = "save_active_document";
+ private static final String MODIFY_DOCUMENT_PROPERTIES = "modify_document_properties";
+ private static final String REVERT_DOCUMENT = "revert_document";
+ private static final String REOPEN_WITH_ENCODING = "reopen_with_encoding";
+ private static final String REMOVE_CONTENT_URL = "remove_content_url";
+ private static final String DETECT_FREE_VARS = "detect_free_vars";
+ private static final String ICONVLIST = "iconvlist";
+ private static final String GET_TEX_CAPABILITIES = "get_tex_capabilities";
+ private static final String GET_CHUNK_OPTIONS = "get_chunk_options";
+
+ private static final String GET_RECENT_HISTORY = "get_recent_history";
+ private static final String GET_HISTORY_ITEMS = "get_history_items";
+ private static final String REMOVE_HISTORY_ITEMS = "remove_history_items";
+ private static final String CLEAR_HISTORY = "clear_history";
+ private static final String GET_HISTORY_ARCHIVE_ITEMS = "get_history_archive_items";
+ private static final String SEARCH_HISTORY_ARCHIVE = "search_history_archive";
+ private static final String SEARCH_HISTORY_ARCHIVE_BY_PREFIX = "search_history_archive_by_prefix";
+
+ private static final String VCS_CLONE = "vcs_clone";
+
+ private static final String GIT_ADD = "git_add";
+ private static final String GIT_REMOVE = "git_remove";
+ private static final String GIT_DISCARD = "git_discard";
+ private static final String GIT_REVERT = "git_revert";
+ private static final String GIT_STAGE = "git_stage";
+ private static final String GIT_UNSTAGE = "git_unstage";
+ private static final String GIT_ALL_STATUS = "git_all_status";
+ private static final String GIT_FULL_STATUS = "git_full_status";
+ private static final String GIT_LIST_BRANCHES = "git_list_branches";
+ private static final String GIT_CHECKOUT = "git_checkout";
+ private static final String GIT_COMMIT = "git_commit";
+ private static final String GIT_PUSH = "git_push";
+ private static final String GIT_PULL = "git_pull";
+ private static final String ASKPASS_COMPLETED = "askpass_completed";
+ private static final String CREATE_SSH_KEY = "create_ssh_key";
+ private static final String GIT_SSH_PUBLIC_KEY = "git_ssh_public_key";
+ private static final String GIT_HAS_REPO = "git_has_repo";
+ private static final String GIT_INIT_REPO = "git_init_repo";
+ private static final String GIT_GET_IGNORES = "git_get_ignores";
+ private static final String GIT_SET_IGNORES = "git_set_ignores";
+ private static final String GIT_GITHUB_REMOTE_URL = "git_github_remote_url";
+ private static final String GIT_DIFF_FILE = "git_diff_file";
+ private static final String GIT_APPLY_PATCH = "git_apply_patch";
+ private static final String GIT_HISTORY_COUNT = "git_history_count";
+ private static final String GIT_HISTORY = "git_history";
+ private static final String GIT_SHOW = "git_show";
+ private static final String GIT_SHOW_FILE = "git_show_file";
+ private static final String GIT_EXPORT_FILE = "git_export_file";
+
+ private static final String SVN_ADD = "svn_add";
+ private static final String SVN_DELETE = "svn_delete";
+ private static final String SVN_REVERT = "svn_revert";
+ private static final String SVN_RESOLVE = "svn_resolve";
+ private static final String SVN_STATUS = "svn_status";
+ private static final String SVN_UPDATE = "svn_update";
+ private static final String SVN_CLEANUP = "svn_cleanup";
+ private static final String SVN_COMMIT = "svn_commit";
+ private static final String SVN_DIFF_FILE = "svn_diff_file";
+ private static final String SVN_APPLY_PATCH = "svn_apply_patch";
+ private static final String SVN_HISTORY_COUNT = "svn_history_count";
+ private static final String SVN_HISTORY = "svn_history";
+ private static final String SVN_SHOW = "svn_show";
+ private static final String SVN_SHOW_FILE = "svn_show_file";
+ private static final String SVN_GET_IGNORES = "svn_get_ignores";
+ private static final String SVN_SET_IGNORES = "svn_set_ignores";
+
+ private static final String GET_PUBLIC_KEY = "get_public_key";
+
+ private static final String LIST_GET = "list_get";
+ private static final String LIST_SET_CONTENTS = "list_set_contents";
+ private static final String LIST_PREPEND_ITEM = "list_prepend_item";
+ private static final String LIST_APPEND_ITEM = "list_append_item";
+ private static final String LIST_REMOVE_ITEM = "list_remove_item";
+ private static final String LIST_CLEAR = "list_clear";
+
+ private static final String PREVIEW_HTML = "preview_html";
+ private static final String TERMINATE_PREVIEW_HTML = "terminate_preview_html";
+ private static final String GET_HTML_CAPABILITIES = "get_html_capabilities";
+ private static final String RPUBS_UPLOAD = "rpubs_upload";
+ private static final String RPUBS_TERMINATE_UPLOAD = "terminate_rpubs_upload";
+
+ private static final String SET_WORKING_DIRECTORY = "set_working_directory";
+ private static final String CREATE_STANDALONE_PRESENTATION = "create_standalone_presentation";
+ private static final String CREATE_DESKTOP_VIEW_IN_BROWSER_PRESENTATION = "create_desktop_view_in_browser_presentation";
+ private static final String CREATE_PRESENTATION_RPUBS_SOURCE = "create_presentation_rpubs_source";
+ private static final String SET_PRESENTATION_SLIDE_INDEX = "set_presentation_slide_index";
+ private static final String PRESENTATION_EXECUTE_CODE = "presentation_execute_code";
+ private static final String CREATE_NEW_PRESENTATION = "create_new_presentation";
+ private static final String SHOW_PRESENTATION_PANE = "show_presentation_pane";
+ private static final String CLOSE_PRESENTATION_PANE = "close_presentation_pane";
+
+ private static final String TUTORIAL_FEEDBACK = "tutorial_feedback";
+ private static final String TUTORIAL_QUIZ_RESPONSE = "tutorial_quiz_response";
+
+ private static final String GET_SLIDE_NAVIGATION_FOR_FILE = "get_slide_navigation_for_file";
+ private static final String GET_SLIDE_NAVIGATION_FOR_CODE = "get_slide_navigation_for_code";
+ private static final String CLEAR_PRESENTATION_CACHE = "clear_presentation_cache";
+
+ private static final String COMPILE_PDF = "compile_pdf";
+ private static final String IS_COMPILE_PDF_RUNNING = "is_compile_pdf_running";
+ private static final String TERMINATE_COMPILE_PDF = "terminate_compile_pdf";
+ private static final String COMPILE_PDF_CLOSED = "compile_pdf_closed";
+
+ private static final String SYNCTEX_FORWARD_SEARCH = "synctex_forward_search";
+ private static final String SYNCTEX_INVERSE_SEARCH = "synctex_inverse_search";
+ private static final String APPLY_FORWARD_CONCORDANCE = "apply_forward_concordance";
+ private static final String APPLY_INVERSE_CONCORDANCE = "apply_inverse_concordance";
+
+ private static final String CHECK_SPELLING = "check_spelling";
+ private static final String SUGGESTION_LIST = "suggestion_list";
+ private static final String ADD_CUSTOM_DICTIONARY = "add_custom_dictionary";
+ private static final String REMOVE_CUSTOM_DICTIONARY = "remove_custom_dictionary";
+ private static final String INSTALL_ALL_DICTIONARIES = "install_all_dictionaries";
+
+ private static final String BEGIN_FIND = "begin_find";
+ private static final String STOP_FIND = "stop_find";
+
+ private static final String GET_CPP_CAPABILITIES = "get_cpp_capabilities";
+ private static final String START_BUILD = "start_build";
+ private static final String TERMINATE_BUILD = "terminate_build";
+ private static final String DEVTOOLS_LOAD_ALL_PATH = "devtools_load_all_path";
+
+ private static final String LIST_ENVIRONMENT = "list_environment";
+ private static final String SET_CONTEXT_DEPTH = "set_context_depth";
+ private static final String SET_ENVIRONMENT = "set_environment";
+ private static final String SET_ENVIRONMENT_FRAME = "set_environment_frame";
+ private static final String GET_ENVIRONMENT_NAMES = "get_environment_names";
+ private static final String GET_ENVIRONMENT_STATE = "get_environment_state";
+ private static final String GET_OBJECT_CONTENTS = "get_object_contents";
+ private static final String REQUERY_CONTEXT = "requery_context";
+
+ private static final String GET_FUNCTION_STEPS = "get_function_steps";
+ private static final String SET_FUNCTION_BREAKPOINTS = "set_function_breakpoints";
+ private static final String GET_FUNCTION_STATE = "get_function_state";
+ private static final String EXECUTE_DEBUG_SOURCE = "execute_debug_source";
+ private static final String SET_ERROR_MANAGEMENT_TYPE = "set_error_management_type";
+ private static final String UPDATE_BREAKPOINTS = "update_breakpoints";
+ private static final String REMOVE_ALL_BREAKPOINTS = "remove_all_breakpoints";
+
+ private static final String LOG = "log";
+ private static final String LOG_EXCEPTION = "log_exception";
+
+ private static final String GET_INIT_MESSAGES = "get_init_messages";
+
+ private static final String CHECK_FOR_UPDATES = "check_for_updates";
+ private static final String GET_PRODUCT_INFO = "get_product_info";
+
+ private static final String GET_SHINY_VIEWER_TYPE = "get_shiny_viewer_type";
+ private static final String GET_SHINY_RUN_CMD = "get_shiny_run_cmd";
+ private static final String SET_SHINY_VIEWER_TYPE = "set_shiny_viewer_type";
+
+ private static final String GET_SHINYAPPS_ACCOUNT_LIST = "get_shinyapps_account_list";
+ private static final String REMOVE_SHINYAPPS_ACCOUNT = "remove_shinyapps_account";
+ private static final String CONNECT_SHINYAPPS_ACCOUNT = "connect_shinyapps_account";
+ private static final String GET_SHINYAPPS_APP_LIST = "get_shinyapps_app_list";
+ private static final String GET_SHINYAPPS_DEPLOYMENTS = "get_shinyapps_deployments";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerAuth.java b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerAuth.java
new file mode 100644
index 0000000..edb6b1f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerAuth.java
@@ -0,0 +1,258 @@
+/*
+ * RemoteServerAuth.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.user.client.Random;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
+import com.google.gwt.user.client.ui.RootPanel;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.jsonrpc.RequestLog;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry;
+import org.rstudio.core.client.jsonrpc.RequestLogEntry.ResponseType;
+import org.rstudio.core.client.jsonrpc.RpcError;
+import org.rstudio.core.client.jsonrpc.RpcResponse;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import java.util.ArrayList;
+
+class RemoteServerAuth
+{
+ public static final int CREDENTIALS_UPDATE_SUCCESS = 1;
+ public static final int CREDENTIALS_UPDATE_FAILURE = 2;
+ public static final int CREDENTIALS_UPDATE_UNSUPPORTED = 3;
+
+ public RemoteServerAuth(RemoteServer remoteServer)
+ {
+ remoteServer_ = remoteServer;
+ }
+
+ private Timer periodicUpdateTimer_ = null;
+
+ public void schedulePeriodicCredentialsUpdate()
+ {
+ // create the callback
+ periodicUpdateTimer_ = new Timer() {
+ @Override
+ public void run()
+ {
+ updateCredentials(new ServerRequestCallback<Integer>() {
+
+ @Override
+ public void onResponseReceived(Integer response)
+ {
+ switch(response)
+ {
+ case CREDENTIALS_UPDATE_SUCCESS:
+ // do nothing (we just successfully updated our
+ // credentials)
+ break;
+
+ case CREDENTIALS_UPDATE_FAILURE:
+ // we are not authorized, blow the client away
+ remoteServer_.handleUnauthorizedError();
+ break;
+
+ case CREDENTIALS_UPDATE_UNSUPPORTED:
+ // not supported by the back end so cancel the timer
+ periodicUpdateTimer_.cancel();
+ break;
+ }
+ }
+
+ @Override
+ public void onError(ServerError serverError)
+ {
+ // if method is not supported then cancel the timer
+ Debug.logError(serverError);
+ }
+ });
+
+
+ }
+ };
+
+ // schedule for every 5 minutes
+ final int kMinutes = 5;
+ int milliseconds = kMinutes * 60 * 1000;
+ periodicUpdateTimer_.scheduleRepeating(milliseconds);
+ }
+
+ public void attemptToUpdateCredentials()
+ {
+ updateCredentials(new ServerRequestCallback<Integer>() {
+
+ @Override
+ public void onResponseReceived(Integer response)
+ {
+ // this method does nothing in the case of both successfully
+ // updating credentails and method not found. however, if
+ // the credentials update fails then it needs to blow
+ // away the client
+
+ if (response.intValue() == CREDENTIALS_UPDATE_FAILURE)
+ {
+ remoteServer_.handleUnauthorizedError();
+ }
+ }
+
+ @Override
+ public void onError(ServerError serverError)
+ {
+ Debug.logError(serverError);
+ }
+ });
+ }
+
+ // save previous form as a precaution against forms which are not
+ // cleaned up due to the submit handler not being called
+ private static ArrayList<FormPanel> previousUpdateCredentialsForms_ =
+ new ArrayList<FormPanel>();
+
+ private void safeCleanupPreviousUpdateCredentials()
+ {
+ try
+ {
+ for (int i=0; i<previousUpdateCredentialsForms_.size(); i++)
+ {
+ FormPanel formPanel = previousUpdateCredentialsForms_.get(i);
+ RootPanel.get().remove(formPanel);
+ }
+
+ previousUpdateCredentialsForms_.clear();
+ }
+ catch(Throwable e)
+ {
+ }
+ }
+
+ public void updateCredentials(
+ final ServerRequestCallback<Integer> requestCallback)
+ {
+ // safely cleanup any previously active update credentials forms
+ safeCleanupPreviousUpdateCredentials();
+
+ // create a hidden form panel to submit the update credentials to
+ // (we do this so GWT manages the trickiness associated with
+ // managing and reading the contents of a hidden iframe)
+ final FormPanel updateCredentialsForm = new FormPanel();
+ updateCredentialsForm.setMethod(FormPanel.METHOD_GET);
+ updateCredentialsForm.setEncoding(FormPanel.ENCODING_URLENCODED);
+
+ // form url
+ String url = remoteServer_.getApplicationURL("auth-update-credentials");
+ updateCredentialsForm.setAction(url);
+
+ // request log entry (fake up a json rpc method call to conform
+ // to the data format expected by RequestLog
+ String requestId = Integer.toString(Random.nextInt());
+ String requestData = createRequestData();
+ final RequestLogEntry logEntry = RequestLog.log(requestId, requestData);
+
+ // form submit complete handler
+ updateCredentialsForm.addSubmitCompleteHandler(new SubmitCompleteHandler(){
+
+ public void onSubmitComplete(SubmitCompleteEvent event)
+ {
+ // parse the results
+ String results = event.getResults();
+ RpcResponse response = RpcResponse.parse(event.getResults());
+ if (response != null)
+ {
+ logEntry.logResponse(ResponseType.Normal, results);
+
+ // check for error
+ RpcError rpcError = response.getError();
+ if (rpcError != null)
+ {
+ if (rpcError.getCode() == RpcError.METHOD_NOT_FOUND)
+ {
+ requestCallback.onResponseReceived(
+ new Integer(CREDENTIALS_UPDATE_UNSUPPORTED));
+ }
+ else
+ {
+ requestCallback.onError(new RemoteServerError(rpcError));
+ }
+ }
+ else // must be a valid response
+ {
+ Bool authenticated = response.getResult();
+ if (authenticated.getValue())
+ {
+ requestCallback.onResponseReceived(
+ new Integer(CREDENTIALS_UPDATE_SUCCESS));
+ }
+ else
+ {
+ requestCallback.onResponseReceived(
+ new Integer(CREDENTIALS_UPDATE_FAILURE));
+ }
+ }
+ }
+ else // error parsing results
+ {
+ logEntry.logResponse(ResponseType.Error, results);
+
+ // form message
+ String msg = "Error parsing results: " +
+ (results != null ? results : "(null)");
+
+ // we don't expect this so debug log to flag our attention
+ Debug.log("UPDATE CREDENTIALS: " + msg);
+
+ // return the error
+ RpcError rpcError = RpcError.create(RpcError.PARSE_ERROR, msg);
+ requestCallback.onError(new RemoteServerError(rpcError));
+ }
+
+ // remove the hidden form (from both last-ditch list and DOM)
+ previousUpdateCredentialsForms_.remove(updateCredentialsForm);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ RootPanel.get().remove(updateCredentialsForm);
+ }
+ });
+ }
+ });
+
+ // add the (hidden) form panel to the document and last ditch list
+ RootPanel.get().add(updateCredentialsForm, -1000, -1000);
+ previousUpdateCredentialsForms_.add(updateCredentialsForm);
+
+ // submit the form
+ updateCredentialsForm.submit();
+ }
+
+ private String createRequestData()
+ {
+ JSONObject request = new JSONObject() ;
+ request.put("method", new JSONString("update_credentials"));
+ request.put("params", new JSONArray());
+ return request.toString();
+ }
+
+ private final RemoteServer remoteServer_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerError.java b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerError.java
new file mode 100644
index 0000000..4be069c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerError.java
@@ -0,0 +1,124 @@
+/*
+ * RemoteServerError.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.json.client.JSONNull;
+import com.google.gwt.json.client.JSONValue;
+import org.rstudio.core.client.jsonrpc.RpcError;
+import org.rstudio.core.client.jsonrpc.RpcUnderlyingError;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerErrorCause;
+
+class RemoteServerError implements ServerError
+{
+ public RemoteServerError(RpcError rpcError)
+ {
+ code_ = codeFromRpcErrorCode(rpcError.getCode());
+ message_ = rpcError.getMessage();
+ RpcUnderlyingError rpcErrorCause = rpcError.getError();
+ if (rpcErrorCause != null)
+ {
+ cause_ = new ServerErrorCause(rpcErrorCause.getCode(),
+ rpcErrorCause.getCategory(),
+ rpcErrorCause.getMessage());
+ }
+ else
+ {
+ cause_ = null;
+ }
+ clientInfo_ = rpcError.getClientInfo();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append(code_ + ": " + message_ + "\n");
+ if (cause_ != null)
+ sb.append(cause_.toString());
+ return sb.toString();
+ }
+
+ public int getCode()
+ {
+ return code_;
+ }
+
+ public String getMessage()
+ {
+ return message_ ;
+ }
+
+ public ServerErrorCause getCause()
+ {
+ return cause_ ;
+ }
+
+ public String getUserMessage()
+ {
+ if (cause_ != null)
+ return cause_.getMessage();
+ else
+ return message_ ;
+ }
+
+ @Override
+ public JSONValue getClientInfo()
+ {
+ return clientInfo_ == null ? JSONNull.getInstance() : clientInfo_;
+ }
+
+ private int codeFromRpcErrorCode(int code)
+ {
+ switch(code)
+ {
+ case RpcError.SUCCESS:
+ return ServerError.SUCCESS;
+
+ case RpcError.CONNECTION_ERROR:
+ return ServerError.CONNECTION;
+
+ case RpcError.UNAVAILABLE:
+ return ServerError.UNAVAILABLE;
+
+ case RpcError.UNAUTHORIZED:
+ return ServerError.UNAUTHORIZED;
+
+ case RpcError.PARSE_ERROR:
+ case RpcError.INVALID_REQUEST:
+ case RpcError.METHOD_NOT_FOUND:
+ case RpcError.PARAM_MISSING:
+ case RpcError.PARAM_TYPE_MISMATCH:
+ case RpcError.PARAM_INVALID:
+ case RpcError.METHOD_UNEXEPECTED:
+ return ServerError.PROTOCOL;
+
+ case RpcError.EXECUTION_ERROR:
+ return ServerError.EXECUTION;
+
+ case RpcError.TRANSMISSION_ERROR:
+ return ServerError.TRANSMISSION;
+
+ default:
+ return ServerError.SUCCESS;
+ }
+ }
+
+ private int code_ ;
+ private String message_ ;
+ private ServerErrorCause cause_ ;
+ private JSONValue clientInfo_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerEventListener.java b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerEventListener.java
new file mode 100644
index 0000000..d674c0e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/RemoteServerEventListener.java
@@ -0,0 +1,508 @@
+/*
+ * RemoteServerEventListener.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import org.rstudio.core.client.jsonrpc.RpcError;
+import org.rstudio.core.client.jsonrpc.RpcRequest;
+import org.rstudio.core.client.jsonrpc.RpcRequestCallback;
+import org.rstudio.core.client.jsonrpc.RpcResponse;
+import org.rstudio.studio.client.application.events.*;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import java.util.HashMap;
+
+
+class RemoteServerEventListener
+{
+ /**
+ * Stores the context needed to complete an async request.
+ */
+ static class AsyncRequestInfo
+ {
+ AsyncRequestInfo(RpcRequest request, RpcRequestCallback callback)
+ {
+ this.request = request;
+ this.callback = callback;
+ }
+
+ public final RpcRequest request;
+ public final RpcRequestCallback callback;
+ }
+
+ public RemoteServerEventListener(RemoteServer server,
+ ClientEventHandler externalEventHandler)
+ {
+ server_ = server;
+ externalEventHandler_ = externalEventHandler;
+ eventDispatcher_ = new ClientEventDispatcher(server_.getEventBus());
+ lastEventId_ = -1;
+ listenCount_ = 0;
+ listenErrorCount_ = 0;
+ isListening_ = false;
+ sessionWasQuit_ = false;
+
+ // we take the liberty of stopping ourselves if the window is on
+ // the verge of being closed. this allows us to prevent the scenario:
+ //
+ // - window closes and the browser terminates the listener connection
+ // - onError is called when the connection is terminated -- this results
+ // in another call to listen() which starts a new connection
+ // - now we have a "leftover" connection still active with the server
+ // even after the user has left the page
+ //
+ // we can't use Window CloseEvent because this occurs *after* the
+ // connection is terminated and restarted in onError. we currently
+ // don't handle the ClosingEvent elsewhere in the application so calling
+ // stop() here is as good as calling it in CloseEvent. however, even
+ // if we did handle ClosingEvent and show a prompt which resulted in
+ // the window NOT closing this would still be OK as the event listener
+ // would still be restarted as necessary by the call to ensureEvents
+ //
+ // note that in the future if we need to make sure event listening
+ // is preserved even in the close cancelled case described above
+ // (e.g. for multi-user cases) then we would need to make sure there
+ // is another way to restart the listener (perhaps a global timer
+ // that checks for isListening every few seconds, or perhaps some
+ // abstraction over addWindowClosingHandler that allows "undo" of
+ // things which were closed or shutdown during closing
+ Window.addWindowClosingHandler(new ClosingHandler() {
+ public void onWindowClosing(ClosingEvent event)
+ {
+ stop();
+ }
+ });
+ }
+
+ public void start()
+ {
+ // start should never be called on a running event listener!
+ // (need to protect against extra requests going to the server
+ // and starving the browser of its 2 connections)
+ if (isListening_)
+ stop();
+
+ // maintain flag indicating that we *should* be listening (allows us to
+ // know when to restart in the case that we are unexpectedly cutoff)
+ isListening_ = true;
+
+ // reset listen count. this will allow us to delay listening on the
+ // second listen (to prevent the "perpetual loading" problem)
+ listenCount_ = 0;
+
+ // reset our lastEventId to make sure we get all events which are
+ // currently pending on the server. note in the case of "restarting"
+ // the event listener setting this to -1 could in theory cause us to
+ // receive an event twice (because the reset to -1 causes us to never
+ // confirm receipt of the event with the server). in practice this
+ // would a) be very unlikely; b) not be that big of a deal; and c) is
+ // judged preferrable than doing something more complex in this code
+ // which might avoid dupes but cause other bugs (such as missing events
+ // from the server). note also that when we go multi-user we'll be
+ // revisiting this mechanism again so there will be an opportunity to
+ // eliminate this scenario then
+ lastEventId_ = -1;
+
+ // start listening
+ listen();
+ }
+
+ public void stop()
+ {
+ isListening_ = false;
+ listenCount_ = 0;
+ if (activeRequestCallback_ != null)
+ {
+ activeRequestCallback_.cancel();
+ activeRequestCallback_ = null;
+ }
+ if (activeRequest_ != null)
+ {
+ activeRequest_.cancel();
+ activeRequest_ = null;
+ }
+ }
+
+ // ensure that we are actively listening for events (used to make
+ // sure that we restart listening when the session is about to resume
+ // after a suspension)
+ public void ensureListening(final int attempts)
+ {
+ // exit if we are now listening
+ if (isListening_)
+ return;
+
+ // exit if we have already quit or been disconnected
+ if (sessionWasQuit_ || server_.isDisconnected())
+ return;
+
+ // attempt to start the service
+ start();
+
+ // if appropriate, schedule another attempt in 250ms
+ final int attemptsRemaining = attempts - 1;
+ if (attemptsRemaining > 0)
+ {
+ new Timer() {
+ public void run()
+ {
+ ensureListening(attemptsRemaining);
+ }
+ }.schedule(250);
+ }
+ }
+
+ // ensure that events are received during the next short time interval.
+ // this not only starts listening if we aren't currently listening but
+ // also ensures (via a Watchdog) that events are received (and if they
+ // are not received restarts the event listener)
+ public void ensureEvents()
+ {
+ // if we aren't listening then start us up
+ if (!isListening_)
+ {
+ start();
+ }
+
+ // if we are listening then use the Watchdog to still make sure we
+ // receive the events even if it requires restarting
+ else
+ {
+ // NOTE: Watchdog is required to work around pathological cases
+ // where the browser has terminated our request for events but
+ // we have not been notified nor can we programmatically detect it.
+ // we need a way to recover and this is it. we have observed this
+ // behavior in webkit if:
+ //
+ // 1) we do not use DeferredCommand/doListen (see below); and
+ //
+ // 2) the user navigates Back within a Frame
+ //
+ // can only imagine that it could happen in other scenarios!
+
+ if (!watchdog_.isRunning())
+ watchdog_.run(kWatchdogIntervalMs);
+ }
+ }
+
+ private void restart()
+ {
+ stop();
+ start();
+ }
+
+ private void listen()
+ {
+ // bounce listen to ensure it is never added to the browser's internal
+ // list of requests bound to the current page load. being on this list
+ // (at least in webkit, perhaps in others) results in at least 2 and
+ // perhaps other problems:
+ //
+ // 1) perpetual "Loading..." indicator displayed to user (user can
+ // also then "cancel" the event request!); and
+ //
+ // 2) terimation of the request without warning by the browser when
+ // the user hits the Back button within a frame hosted on the page
+ // (note in this case we get no error so think the request is still
+ // running -- see Watchdog for workaround to this general class of
+ // issues)
+
+ // determine bounce ms (do a bigger bounce for the second listen
+ // request as this is the one which gets us stuck in "perpetual loading")
+ int bounceMs = 1;
+ if (++listenCount_ == 2)
+ bounceMs = kSecondListenBounceMs;
+
+ Timer listenTimer = new Timer() {
+ @Override
+ public void run()
+ {
+ doListen();
+ }
+ };
+ listenTimer.schedule(bounceMs);
+ }
+
+ private void doListen()
+ {
+ // abort if we are no longer running
+ if (!isListening_)
+ return;
+
+ // setup request callback (save reference for cancellation)
+ activeRequestCallback_ = new ServerRequestCallback<JsArray<ClientEvent>>()
+ {
+ @Override
+ public void onResponseReceived(JsArray<ClientEvent> events)
+ {
+ // keep watchdog appraised of successful receipt of events
+ watchdog_.notifyResponseReceived();
+
+ try
+ {
+ // only processs events if we are still listening
+ if (isListening_ && (events != null))
+ {
+ for (int i=0; i<events.length(); i++)
+ {
+ // we can stop listening in the middle of dispatching
+ // events (e.g. if we dispatch a Suicide event) so we
+ // need to check the listening_ flag before each event
+ // is dispatched
+ if (!isListening_)
+ return;
+
+ // disppatch event
+ ClientEvent event = events.get(i);
+ dispatchEvent(event);
+ lastEventId_ = event.getId();
+ }
+ }
+ }
+ // catch all here to make sure that in all cases we call
+ // listen() again after processing
+ catch(Throwable e)
+ {
+ GWT.log("ERROR: Processing client events", e);
+ }
+
+ // listen for more events
+ listen();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // stop listening for events
+ stop();
+
+ // if this was server unavailable then signal event and return
+ if (error.getCode() == ServerError.UNAVAILABLE)
+ {
+ ServerUnavailableEvent event = new ServerUnavailableEvent();
+ server_.getEventBus().fireEvent(event);
+ return;
+ }
+
+ // attempt to restart listening, but throttle restart attempts
+ // in both timing (500ms delay) and quantity (no more than 5
+ // attempts). We do this because unthrottled restart attempts could
+ // result in our server getting hammered with requests)
+ if (listenErrorCount_++ <= 5)
+ {
+ Timer startTimer = new Timer() {
+ @Override
+ public void run()
+ {
+ // only start again if we haven't been started
+ // by some other means (e.g. ensureListening, etc)
+ if (!isListening_)
+ start();
+ }
+ };
+ startTimer.schedule(500);
+ }
+ // otherwise reset the listen error count and remain stopped
+ else
+ {
+ listenErrorCount_ = 0;
+ }
+ }
+ };
+
+ // retry handler (restart listener)
+ RetryHandler retryHandler = new RetryHandler() {
+
+ public void onRetry()
+ {
+ // need to do a full restart to ensure that the existing
+ // activeRequest_ and activeRequestCallback_ are cleaned up
+ // and all state is reset correctly
+ restart();
+ }
+
+ public void onError(RpcError error)
+ {
+ // error while attempting to recover, to be on the safe side
+ // we simply stop listening for events. if rather than stopping
+ // we restarted we would open ourselves up to a situation
+ // where we keep hitting the same error over and over again.
+ stop();
+ }
+ };
+
+ // send request
+ activeRequest_ = server_.getEvents(lastEventId_,
+ activeRequestCallback_,
+ retryHandler);
+ }
+
+
+ private void dispatchEvent(ClientEvent event)
+ {
+ // do some special handling before calling the standard dispatcher
+ String type = event.getType();
+
+ // we handle async completions directly
+ if (type.equals(ClientEvent.AsyncCompletion))
+ {
+ AsyncCompletion completion = event.getData();
+ String handle = completion.getHandle();
+ AsyncRequestInfo req = asyncRequests_.remove(handle);
+ if (req != null)
+ {
+ req.callback.onResponseReceived(req.request,
+ completion.getResponse());
+ }
+ else
+ {
+ // We haven't seen this request yet. Store it for later,
+ // maybe it's just taking a long time for the request
+ // to complete.
+ asyncResponses_.put(handle, completion.getResponse());
+ }
+ }
+ else
+ {
+ // if there is a quit event then we set an internal flag to avoid
+ // ensureListening/ensureEvents calls trying to spark the event
+ // stream back up after the user has quit
+ if (type.equals(ClientEvent.Quit))
+ sessionWasQuit_ = true;
+
+ // perform standard handling
+ eventDispatcher_.enqueEvent(event);
+
+ // allow any external handler registered to see the event
+ if (externalEventHandler_ != null)
+ externalEventHandler_.onClientEvent(event);
+ }
+
+ }
+
+ // NOTE: the design of the Watchdog likely results in more restarts of
+ // the event service than is optimal. when an rpc call reports that
+ // events are pending and the Watchdog is invoked it is very likely
+ // that the events have already been delievered in response to the
+ // previous poll. In this case the Watchdog "misses" those events which
+ // were already delivered and subsequently assumes that the service
+ // needs to be restarted
+
+ private class Watchdog
+ {
+ public void run(int waitMs)
+ {
+ isRunning_ = true;
+ responseReceived_ = false ;
+
+ Timer timer = new Timer() {
+ public void run()
+ {
+ try
+ {
+ if (!responseReceived_)
+ {
+ // ensure that the workbench wasn't closed while we
+ // were waiting for the timer to run
+ if (!sessionWasQuit_)
+ restart();
+ }
+ }
+ catch(Throwable e)
+ {
+ GWT.log("Error restarting event source", e);
+ }
+
+ isRunning_ = false;
+ responseReceived_ = false ;
+ }
+
+ };
+ timer.schedule(waitMs);
+ }
+
+ public boolean isRunning()
+ {
+ return isRunning_ ;
+ }
+
+ public void notifyResponseReceived()
+ {
+ responseReceived_ = true;
+ }
+
+ private boolean isRunning_ = false;
+ private boolean responseReceived_ = false;
+ }
+
+ public void registerAsyncHandle(String asyncHandle,
+ RpcRequest request,
+ RpcRequestCallback callback)
+ {
+ RpcResponse response = asyncResponses_.remove(asyncHandle);
+ if (response == null)
+ {
+ // We don't have the response for this request--this is
+ // the normal case.
+ asyncRequests_.put(asyncHandle,
+ new AsyncRequestInfo(request, callback));
+ }
+ else
+ {
+ // We already have the response--the request must've taken
+ // a long time to return.
+ callback.onResponseReceived(request, response);
+ }
+ }
+
+ private final RemoteServer server_;
+
+ // note: kSecondListenDelayMs must be less than kWatchdogIntervalMs
+ // (by a reasonable margin) to void the watchdog getting involved
+ // unnecessarily during a listen delay
+ private final int kWatchdogIntervalMs = 1000;
+ private final int kSecondListenBounceMs = 250;
+
+ private boolean isListening_;
+ private int lastEventId_ ;
+ private int listenCount_ ;
+ private int listenErrorCount_ ;
+ private boolean sessionWasQuit_ ;
+
+ private RpcRequest activeRequest_ ;
+ private ServerRequestCallback<JsArray<ClientEvent>> activeRequestCallback_;
+
+ private final ClientEventDispatcher eventDispatcher_;
+
+ private final ClientEventHandler externalEventHandler_;
+
+ private Watchdog watchdog_ = new Watchdog();
+
+ // Stores async requests that expect to be completed later.
+ private final HashMap<String, AsyncRequestInfo> asyncRequests_
+ = new HashMap<String, AsyncRequestInfo>();
+
+ // Stores any async responses that didn't have matching requests at the
+ // time they were received. This is to deal with any race conditions where
+ // the completion occurs before we even finished making the request.
+ private final HashMap<String, RpcResponse> asyncResponses_
+ = new HashMap<String, RpcResponse>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/server/remote/RetryHandler.java b/src/gwt/src/org/rstudio/studio/client/server/remote/RetryHandler.java
new file mode 100644
index 0000000..4558604
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/server/remote/RetryHandler.java
@@ -0,0 +1,28 @@
+/*
+ * RetryHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.server.remote;
+
+import org.rstudio.core.client.jsonrpc.RpcError;
+
+interface RetryHandler
+{
+ // perform the retry
+ void onRetry();
+
+ // will be called with the original error that caused the retry attempt
+ // if there is an error involving the retry mechanism itself (e.g. error
+ // occurs during attempt to authenticate in response to UNAUTHORIZED)
+ void onError(RpcError error);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplication.java b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplication.java
new file mode 100644
index 0000000..9a5b977
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplication.java
@@ -0,0 +1,260 @@
+/*
+ * ShinyApplication.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.ApplicationInterrupt;
+import org.rstudio.studio.client.application.ApplicationInterrupt.InterruptHandler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.shiny.model.ShinyServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.shiny.events.ShinyApplicationStatusEvent;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.shiny.model.ShinyRunCmd;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleBusyEvent;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ShinyApplication implements ShinyApplicationStatusEvent.Handler,
+ ConsoleBusyEvent.Handler,
+ DebugModeChangedEvent.Handler,
+ RestartStatusEvent.Handler
+{
+ public interface Binder
+ extends CommandBinder<Commands, ShinyApplication> {}
+
+ @Inject
+ public ShinyApplication(EventBus eventBus,
+ Commands commands,
+ Binder binder,
+ Provider<UIPrefs> pPrefs,
+ final SatelliteManager satelliteManager,
+ ShinyServerOperations server,
+ GlobalDisplay display,
+ ApplicationInterrupt interrupt)
+ {
+ eventBus_ = eventBus;
+ satelliteManager_ = satelliteManager;
+ commands_ = commands;
+ pPrefs_ = pPrefs;
+ server_ = server;
+ display_ = display;
+ isBusy_ = false;
+ currentViewType_ = ShinyViewerType.SHINY_VIEWER_NONE;
+ interrupt_ = interrupt;
+
+ eventBus_.addHandler(ShinyApplicationStatusEvent.TYPE, this);
+ eventBus_.addHandler(ConsoleBusyEvent.TYPE, this);
+ eventBus_.addHandler(DebugModeChangedEvent.TYPE, this);
+ eventBus_.addHandler(RestartStatusEvent.TYPE, this);
+
+ binder.bind(commands, this);
+ exportShinyAppClosedCallback();
+ }
+
+ @Override
+ public void onShinyApplicationStatus(ShinyApplicationStatusEvent event)
+ {
+ if (event.getParams().getState() == ShinyApplicationParams.STATE_STARTED)
+ {
+ currentViewType_ = event.getParams().getViewerType();
+
+ // open the window to view the application if needed
+ if (currentViewType_ == ShinyViewerType.SHINY_VIEWER_WINDOW)
+ {
+ satelliteManager_.openSatellite(ShinyApplicationSatellite.NAME,
+ event.getParams(),
+ new Size(960,1100));
+ }
+ currentAppFilePath_ = event.getParams().getPath();
+ }
+ else if (event.getParams().getState() == ShinyApplicationParams.STATE_STOPPED)
+ {
+ currentAppFilePath_ = null;
+ }
+ }
+
+ @Override
+ public void onConsoleBusy(ConsoleBusyEvent event)
+ {
+ isBusy_ = event.isBusy();
+ }
+
+ @Override
+ public void onDebugModeChanged(DebugModeChangedEvent event)
+ {
+ // When leaving debug mode while the Shiny application is open in a
+ // browser, automatically return to the app by activating the window.
+ if (!event.debugging() &&
+ currentAppFilePath_ != null &&
+ currentViewType_ == ShinyViewerType.SHINY_VIEWER_WINDOW)
+ {
+ satelliteManager_.activateSatelliteWindow(
+ ShinyApplicationSatellite.NAME);
+ }
+ }
+
+ @Override
+ public void onRestartStatus(RestartStatusEvent event)
+ {
+ // Close the satellite window when R restarts, since this leads to the
+ // Shiny server being terminated. Closing the window triggers a
+ // ShinyApplicationStatusEvent that allows the rest of the UI a chance
+ // to react to the app's termination.
+ if (event.getStatus() == RestartStatusEvent.RESTART_INITIATED &&
+ currentViewType_ == ShinyViewerType.SHINY_VIEWER_WINDOW)
+ {
+ satelliteManager_.closeSatelliteWindow(
+ ShinyApplicationSatellite.NAME);
+ }
+ }
+
+ @Handler
+ public void onShinyRunInPane()
+ {
+ setShinyViewerType(ShinyViewerType.SHINY_VIEWER_PANE);
+ }
+
+ @Handler
+ public void onShinyRunInViewer()
+ {
+ setShinyViewerType(ShinyViewerType.SHINY_VIEWER_WINDOW);
+ }
+
+ @Handler
+ public void onShinyRunInBrowser()
+ {
+ setShinyViewerType(ShinyViewerType.SHINY_VIEWER_BROWSER);
+ }
+
+ public void launchShinyApplication(String filePath)
+ {
+ final String dir = filePath.substring(0, filePath.lastIndexOf("/"));
+ if (dir.equals(currentAppFilePath_))
+ {
+ // The app being launched is the one already running; open and
+ // reload the app.
+ if (currentViewType_ == ShinyViewerType.SHINY_VIEWER_WINDOW)
+ {
+ satelliteManager_.dispatchCommand(commands_.reloadShinyApp());
+ satelliteManager_.activateSatelliteWindow(
+ ShinyApplicationSatellite.NAME);
+ }
+ else if (currentViewType_ == ShinyViewerType.SHINY_VIEWER_PANE &&
+ commands_.viewerRefresh().isEnabled())
+ {
+ commands_.viewerRefresh().execute();
+ }
+ return;
+ }
+ else if (currentAppFilePath_ != null && isBusy_)
+ {
+ // There's another app running. Interrupt it and then start this one.
+ interrupt_.interruptR(new InterruptHandler() {
+ @Override
+ public void onInterruptFinished()
+ {
+ launchShinyAppDir(dir);
+ }
+ });
+ }
+ else
+ {
+ // Nothing else running, start this app.
+ launchShinyAppDir(dir);
+ }
+ }
+
+ private void notifyShinyAppClosed(JavaScriptObject params)
+ {
+ ShinyApplicationParams appState = params.cast();
+ // If the application is stopping, then the user initiated the stop by
+ // closing the app window. Interrupt R to stop the Shiny app.
+ if (appState.getState().equals(ShinyApplicationParams.STATE_STOPPING))
+ {
+ if (commands_.interruptR().isEnabled())
+ commands_.interruptR().execute();
+ appState.setState(ShinyApplicationParams.STATE_STOPPED);
+ }
+ eventBus_.fireEvent(new ShinyApplicationStatusEvent(
+ (ShinyApplicationParams) params.cast()));
+ }
+
+ private final native void exportShinyAppClosedCallback()/*-{
+ var registry = this;
+ $wnd.notifyShinyAppClosed = $entry(
+ function(params) {
+ registry. at org.rstudio.studio.client.shiny.ShinyApplication::notifyShinyAppClosed(Lcom/google/gwt/core/client/JavaScriptObject;)(params);
+ }
+ );
+ }-*/;
+
+ private void setShinyViewerType(int viewerType)
+ {
+ UIPrefs prefs = pPrefs_.get();
+ prefs.shinyViewerType().setGlobalValue(viewerType);
+ prefs.writeUIPrefs();
+ server_.setShinyViewerType(viewerType, new VoidServerRequestCallback());
+ }
+
+ private void launchShinyAppDir(String dir)
+ {
+ server_.getShinyRunCmd(dir,
+ new ServerRequestCallback<ShinyRunCmd>()
+ {
+ @Override
+ public void onResponseReceived(ShinyRunCmd cmd)
+ {
+ eventBus_.fireEvent(
+ new SendToConsoleEvent(cmd.getRunCmd(), true));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Shiny App Launch Failed",
+ error.getMessage());
+ }
+ });
+ }
+
+ private final EventBus eventBus_;
+ private final SatelliteManager satelliteManager_;
+ private final Commands commands_;
+ private final Provider<UIPrefs> pPrefs_;
+ private final ShinyServerOperations server_;
+ private final GlobalDisplay display_;
+ private final ApplicationInterrupt interrupt_;
+
+ private String currentAppFilePath_;
+ private boolean isBusy_;
+ private int currentViewType_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationPresenter.java b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationPresenter.java
new file mode 100644
index 0000000..b9151ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationPresenter.java
@@ -0,0 +1,157 @@
+/*
+ * ShinyApplicationPresenter.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.shiny.events.ShinyApplicationStatusEvent;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class ShinyApplicationPresenter
+ implements IsWidget, ShinyApplicationStatusEvent.Handler
+{
+ public interface Binder
+ extends CommandBinder<Commands, ShinyApplicationPresenter>
+ {}
+
+ public interface Display extends IsWidget
+ {
+ String getDocumentTitle();
+ String getUrl();
+ String getAbsoluteUrl();
+ void showApp(ShinyApplicationParams params);
+ void reloadApp();
+ }
+
+ @Inject
+ public ShinyApplicationPresenter(Display view,
+ GlobalDisplay globalDisplay,
+ Binder binder,
+ final Commands commands,
+ EventBus eventBus,
+ Satellite satellite)
+ {
+ view_ = view;
+ satellite_ = satellite;
+ events_ = eventBus;
+ globalDisplay_ = globalDisplay;
+
+ binder.bind(commands, this);
+
+ initializeEvents();
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Override
+ public void onShinyApplicationStatus(ShinyApplicationStatusEvent event)
+ {
+ if (event.getParams().getState() == ShinyApplicationParams.STATE_RELOADING)
+ {
+ view_.reloadApp();
+ }
+ }
+
+ @Handler
+ public void onReloadShinyApp()
+ {
+ view_.reloadApp();
+ }
+
+ @Handler
+ public void onViewerPopout()
+ {
+ globalDisplay_.openWindow(params_.getUrl());
+ }
+
+ public void loadApp(ShinyApplicationParams params)
+ {
+ params_ = params;
+ view_.showApp(params);
+ }
+
+ private native void initializeEvents() /*-{
+ var thiz = this;
+ $wnd.addEventListener(
+ "message",
+ $entry(function(e) {
+ thiz. at org.rstudio.studio.client.shiny.ShinyApplicationPresenter::onMessage(Ljava/lang/String;Ljava/lang/String;)(e.data, e.origin);
+ }),
+ true);
+
+ $wnd.addEventListener(
+ "unload",
+ $entry(function() {
+ thiz. at org.rstudio.studio.client.shiny.ShinyApplicationPresenter::onClose()();
+ }),
+ true);
+ }-*/;
+
+ private void onMessage(String data, String origin)
+ {
+ if ("disconnected".equals(data))
+ {
+ // ensure the frame url starts with the specified origin; we need to
+ // use the absolute url since 'origin' includes the hostname and
+ // portmapped urls may be relative (i.e. just p/XXXX).
+ if (view_.getAbsoluteUrl().startsWith(origin))
+ {
+ appStopped_ = true;
+ closeShinyApp();
+ }
+ }
+ }
+
+ private void onClose()
+ {
+ ShinyApplicationParams params = ShinyApplicationParams.create(
+ params_.getPath(),
+ params_.getUrl(),
+ appStopped_ ?
+ ShinyApplicationParams.STATE_STOPPED :
+ ShinyApplicationParams.STATE_STOPPING);
+ notifyShinyAppClosed(params);
+ }
+
+ private final native void closeShinyApp() /*-{
+ $wnd.close();
+ }-*/;
+
+ private final native void notifyShinyAppClosed(JavaScriptObject params) /*-{
+ $wnd.opener.notifyShinyAppClosed(params);
+ }-*/;
+
+ private final Display view_;
+ private final Satellite satellite_;
+ private final EventBus events_;
+ private final GlobalDisplay globalDisplay_;
+
+ private ShinyApplicationParams params_;
+ private boolean appStopped_ = false;
+ private boolean popoutToBrowser_ = false;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationSatellite.java b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationSatellite.java
new file mode 100644
index 0000000..f24d5a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApplicationSatellite.java
@@ -0,0 +1,40 @@
+/*
+ * ShinyApplicationSatellite.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny;
+
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteApplication;
+import org.rstudio.studio.client.shiny.ui.ShinyApplicationView;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ShinyApplicationSatellite extends SatelliteApplication
+{
+ public static final String NAME = "shiny";
+
+ @Inject
+ public ShinyApplicationSatellite(ShinyApplicationView view,
+ Satellite satellite,
+ Provider<AceThemes> pAceThemes,
+ ApplicationUncaughtExceptionHandler exHandler)
+ {
+ super(NAME, view, satellite, pAceThemes, exHandler);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApps.java b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApps.java
new file mode 100644
index 0000000..07a98a0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ShinyApps.java
@@ -0,0 +1,295 @@
+/*
+ * ShinyApps.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FilePathUtils;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.shiny.events.ShinyAppsActionEvent;
+import org.rstudio.studio.client.shiny.events.ShinyAppsDeployInitiatedEvent;
+import org.rstudio.studio.client.shiny.model.ShinyAppsApplicationInfo;
+import org.rstudio.studio.client.shiny.model.ShinyAppsDeploymentRecord;
+import org.rstudio.studio.client.shiny.model.ShinyAppsDirectoryState;
+import org.rstudio.studio.client.shiny.ui.ShinyAppsAccountManagerDialog;
+import org.rstudio.studio.client.shiny.ui.ShinyAppsDeployDialog;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ShinyApps implements SessionInitHandler,
+ ShinyAppsActionEvent.Handler,
+ ShinyAppsDeployInitiatedEvent.Handler
+{
+ public interface Binder
+ extends CommandBinder<Commands, ShinyApps> {}
+
+ @Inject
+ public ShinyApps(EventBus events,
+ Commands commands,
+ Session session,
+ GlobalDisplay display,
+ Binder binder,
+ ShinyAppsServerOperations server)
+ {
+ commands_ = commands;
+ display_ = display;
+ session_ = session;
+ server_ = server;
+ events_ = events;
+
+ binder.bind(commands, this);
+
+ events.addHandler(SessionInitEvent.TYPE, this);
+ events.addHandler(ShinyAppsActionEvent.TYPE, this);
+ events.addHandler(ShinyAppsDeployInitiatedEvent.TYPE, this);
+ }
+
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ // "Manage accounts" can be invoked any time the package is available
+ commands_.shinyAppsManageAccounts().setVisible(
+ session_.getSessionInfo().getShinyappsInstalled());
+
+ // This object keeps track of the most recent deployment we made of each
+ // directory, and is used to default directory deployments to last-used
+ // settings.
+ new JSObjectStateValue(
+ "shinyapps",
+ "shinyAppsDirectories",
+ ClientState.PERSISTENT,
+ session_.getSessionInfo().getClientState(),
+ false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ dirState_ = (ShinyAppsDirectoryState) (value == null ?
+ ShinyAppsDirectoryState.create() :
+ value.cast());
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ dirStateDirty_ = false;
+ return (JsObject) (dirState_ == null ?
+ ShinyAppsDirectoryState.create().cast() :
+ dirState_.cast());
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ return dirStateDirty_;
+ }
+ };
+ }
+
+ @Override
+ public void onShinyAppsAction(ShinyAppsActionEvent event)
+ {
+ if (event.getAction() == ShinyAppsActionEvent.ACTION_TYPE_DEPLOY)
+ {
+ String dir = FilePathUtils.dirFromFile(event.getPath());
+ ShinyAppsDeploymentRecord record = dirState_.getLastDeployment(dir);
+ String lastAccount = null;
+ String lastAppName = null;
+ if (record != null)
+ {
+ lastAccount = record.getAccount();
+ lastAppName = record.getName();
+ }
+ ShinyAppsDeployDialog dialog =
+ new ShinyAppsDeployDialog(
+ server_, display_, events_,
+ dir, lastAccount, lastAppName);
+ dialog.showModal();
+ }
+ else if (event.getAction() == ShinyAppsActionEvent.ACTION_TYPE_TERMINATE)
+ {
+ terminateShinyApp(FilePathUtils.dirFromFile(event.getPath()));
+ }
+ }
+
+ @Override
+ public void onShinyAppsDeployInitiated(ShinyAppsDeployInitiatedEvent event)
+ {
+ dirState_.addDeployment(event.getPath(), event.getRecord());
+ dirStateDirty_ = true;
+ }
+
+ @Handler
+ public void onShinyAppsManageAccounts()
+ {
+ ShinyAppsAccountManagerDialog dialog =
+ new ShinyAppsAccountManagerDialog(server_, display_);
+ dialog.showModal();
+ }
+
+ // Terminate, step 1: create a list of apps deployed from this directory
+ private void terminateShinyApp(final String dir)
+ {
+ server_.getShinyAppsDeployments(dir,
+ new ServerRequestCallback<JsArray<ShinyAppsDeploymentRecord>>()
+ {
+ @Override
+ public void onResponseReceived(
+ JsArray<ShinyAppsDeploymentRecord> records)
+ {
+ terminateShinyApp(dir, records);
+ }
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error Terminating Application",
+ "Could not determine application deployments for '" +
+ dir + "':" + error.getMessage());
+ }
+ });
+ }
+
+ // Terminate, step 2: Get the status of the applications from the server
+ private void terminateShinyApp(final String dir,
+ JsArray<ShinyAppsDeploymentRecord> records)
+ {
+ if (records.length() == 0)
+ {
+ display_.showMessage(GlobalDisplay.MSG_INFO, "No Deployments Found",
+ "No application deployments were found for '" + dir + "'");
+ return;
+ }
+
+ // If we know the most recent deployment of the directory, act on that
+ // deployment by default
+ final ArrayList<ShinyAppsDeploymentRecord> recordList =
+ new ArrayList<ShinyAppsDeploymentRecord>();
+ ShinyAppsDeploymentRecord lastRecord = dirState_.getLastDeployment(dir);
+ if (lastRecord != null)
+ {
+ recordList.add(lastRecord);
+ }
+ for (int i = 0; i < records.length(); i++)
+ {
+ ShinyAppsDeploymentRecord record = records.get(i);
+ if (lastRecord == null)
+ {
+ recordList.add(record);
+ }
+ else
+ {
+ if (record.getUrl().equals(lastRecord.getUrl()))
+ recordList.set(0, record);
+ }
+ }
+
+ // We need to further filter the list by deployments that are
+ // eligible for termination (i.e. are currently running)
+ server_.getShinyAppsAppList(recordList.get(0).getAccount(),
+ new ServerRequestCallback<JsArray<ShinyAppsApplicationInfo>>()
+ {
+ @Override
+ public void onResponseReceived(JsArray<ShinyAppsApplicationInfo> apps)
+ {
+ terminateShinyApp(dir, apps, recordList);
+ }
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error Listing Applications",
+ error.getMessage());
+ }
+ });
+ }
+
+ // Terminate, step 3: compare the deployments and apps active on the server
+ // until we find a running app from the current directory
+ private void terminateShinyApp(String dir,
+ JsArray<ShinyAppsApplicationInfo> apps,
+ List<ShinyAppsDeploymentRecord> records)
+ {
+ for (int i = 0; i < records.size(); i++)
+ {
+ for (int j = 0; j < apps.length(); j++)
+ {
+ ShinyAppsApplicationInfo candidate = apps.get(j);
+ if (candidate.getName().equals(records.get(i).getName()) &&
+ candidate.getStatus().equals("running"))
+ {
+ terminateShinyApp(records.get(i).getAccount(), candidate);
+ return;
+ }
+ }
+ }
+ display_.showMessage(GlobalDisplay.MSG_INFO,
+ "No Running Deployments Found", "No applications deployed from '" +
+ dir + "' appear to be running.");
+ }
+
+ // Terminate, step 4: confirm that we've selected the right app for
+ // termination
+ private void terminateShinyApp(final String accountName,
+ final ShinyAppsApplicationInfo target)
+ {
+ display_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+ "Confirm Terminate Application",
+ "Terminate the application '" + target.getName() + "' running " +
+ "at " + target.getUrl() + "?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ terminateShinyApp(accountName, target.getName());
+ }
+ },
+ true
+ );
+ }
+
+ // Terminate, step 5: perform the termination
+ private void terminateShinyApp(String accountName, final String appName)
+ {
+ events_.fireEvent(new SendToConsoleEvent("shinyapps::terminateApp(\"" +
+ appName + "\", \"" + accountName + "\")", true));
+ }
+
+ private final Commands commands_;
+ private final GlobalDisplay display_;
+ private final Session session_;
+ private final ShinyAppsServerOperations server_;
+ private final EventBus events_;
+
+ private ShinyAppsDirectoryState dirState_;
+ private boolean dirStateDirty_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyApplicationStatusEvent.java b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyApplicationStatusEvent.java
new file mode 100644
index 0000000..e597475
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyApplicationStatusEvent.java
@@ -0,0 +1,55 @@
+/*
+ * ShinyApplicationStatusEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.events;
+
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShinyApplicationStatusEvent extends GwtEvent<ShinyApplicationStatusEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShinyApplicationStatus(ShinyApplicationStatusEvent event);
+ }
+
+ public static final GwtEvent.Type<ShinyApplicationStatusEvent.Handler> TYPE =
+ new GwtEvent.Type<ShinyApplicationStatusEvent.Handler>();
+
+ public ShinyApplicationStatusEvent(ShinyApplicationParams params)
+ {
+ params_ = params;
+ }
+
+ public ShinyApplicationParams getParams()
+ {
+ return params_;
+ }
+
+ @Override
+ protected void dispatch(ShinyApplicationStatusEvent.Handler handler)
+ {
+ handler.onShinyApplicationStatus(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShinyApplicationStatusEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private ShinyApplicationParams params_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsActionEvent.java b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsActionEvent.java
new file mode 100644
index 0000000..d2ff037
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsActionEvent.java
@@ -0,0 +1,63 @@
+/*
+ * ShinyAppsActionEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShinyAppsActionEvent extends GwtEvent<ShinyAppsActionEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShinyAppsAction(ShinyAppsActionEvent event);
+ }
+
+ public static final GwtEvent.Type<ShinyAppsActionEvent.Handler> TYPE =
+ new GwtEvent.Type<ShinyAppsActionEvent.Handler>();
+
+ public ShinyAppsActionEvent(int action, String path)
+ {
+ action_ = action;
+ path_ = path;
+ }
+
+ public String getPath()
+ {
+ return path_;
+ }
+
+ public int getAction()
+ {
+ return action_;
+ }
+
+ @Override
+ protected void dispatch(ShinyAppsActionEvent.Handler handler)
+ {
+ handler.onShinyAppsAction(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShinyAppsActionEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ public static int ACTION_TYPE_DEPLOY = 0;
+ public static int ACTION_TYPE_TERMINATE = 1;
+
+ private String path_;
+ private int action_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsDeployInitiatedEvent.java b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsDeployInitiatedEvent.java
new file mode 100644
index 0000000..8ab6e94
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/events/ShinyAppsDeployInitiatedEvent.java
@@ -0,0 +1,62 @@
+/*
+ * ShinyAppsDeployInitiatedEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.events;
+
+import org.rstudio.studio.client.shiny.model.ShinyAppsDeploymentRecord;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShinyAppsDeployInitiatedEvent extends GwtEvent<ShinyAppsDeployInitiatedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShinyAppsDeployInitiated(ShinyAppsDeployInitiatedEvent event);
+ }
+
+ public static final GwtEvent.Type<ShinyAppsDeployInitiatedEvent.Handler> TYPE =
+ new GwtEvent.Type<ShinyAppsDeployInitiatedEvent.Handler>();
+
+ public ShinyAppsDeployInitiatedEvent(String path,
+ ShinyAppsDeploymentRecord record)
+ {
+ path_ = path;
+ record_ = record;
+ }
+
+ public ShinyAppsDeploymentRecord getRecord()
+ {
+ return record_;
+ }
+
+ public String getPath()
+ {
+ return path_; }
+
+ @Override
+ protected void dispatch(ShinyAppsDeployInitiatedEvent.Handler handler)
+ {
+ handler.onShinyAppsDeployInitiated(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShinyAppsDeployInitiatedEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private ShinyAppsDeploymentRecord record_;
+ private String path_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyApplicationParams.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyApplicationParams.java
new file mode 100644
index 0000000..f93b386
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyApplicationParams.java
@@ -0,0 +1,58 @@
+/*
+ * ShinyApplicationParams.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyApplicationParams extends JavaScriptObject
+{
+ protected ShinyApplicationParams() {}
+
+ public final static String STATE_STARTED = "started";
+ public final static String STATE_STOPPING = "stopping";
+ public final static String STATE_STOPPED = "stopped";
+ public final static String STATE_RELOADING = "reloading";
+
+ public native static ShinyApplicationParams create(String path,
+ String url,
+ String state) /*-{
+ return {
+ path: path,
+ url: url,
+ state: state,
+ viewer: 0
+ };
+ }-*/;
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public final native String getUrl() /*-{
+ return this.url;
+ }-*/;
+
+ public final native int getViewerType() /*-{
+ return this.viewer;
+ }-*/;
+
+ public final native String getState() /*-{
+ return this.state;
+ }-*/;
+
+ public final native void setState(String state) /*-{
+ this.state = state;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsApplicationInfo.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsApplicationInfo.java
new file mode 100644
index 0000000..10d1e6b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsApplicationInfo.java
@@ -0,0 +1,44 @@
+/*
+ * ShinyAppsApplicationInfo.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyAppsApplicationInfo extends JavaScriptObject
+{
+ protected ShinyAppsApplicationInfo()
+ {
+ }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getUrl() /*-{
+ return this.url;
+ }-*/;
+
+ public final native String getStatus() /*-{
+ return this.status;
+ }-*/;
+
+ public final native String getCreatedTime() /*-{
+ return this.created_time;
+ }-*/;
+
+ public final native String getUpdatedTime() /*-{
+ return this.updated_time;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDeploymentRecord.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDeploymentRecord.java
new file mode 100644
index 0000000..843bed4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDeploymentRecord.java
@@ -0,0 +1,47 @@
+/*
+ * ShinyAppsDeploymentRecord.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyAppsDeploymentRecord extends JavaScriptObject
+{
+ protected ShinyAppsDeploymentRecord()
+ {
+ }
+
+ public static final native ShinyAppsDeploymentRecord create(
+ String name,
+ String account,
+ String url) /*-{
+ return {
+ 'name': name,
+ 'account': account,
+ 'url': url
+ };
+ }-*/;
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getAccount() /*-{
+ return this.account;
+ }-*/;
+
+ public final native String getUrl() /*-{
+ return this.url;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDirectoryState.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDirectoryState.java
new file mode 100644
index 0000000..6b3374b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyAppsDirectoryState.java
@@ -0,0 +1,40 @@
+/*
+ * ShinyAppsDirectoryState.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyAppsDirectoryState extends JavaScriptObject
+{
+ protected ShinyAppsDirectoryState()
+ {
+ }
+
+ public final native static ShinyAppsDirectoryState create() /*-{
+ return { dir_map: {} };
+ }-*/;
+
+ public final native void addDeployment (
+ String dir,
+ ShinyAppsDeploymentRecord record) /*-{
+ this.dir_map[dir] = record;
+ }-*/;
+
+ public final native ShinyAppsDeploymentRecord getLastDeployment(String dir) /*-{
+ return this.dir_map[dir] ?
+ this.dir_map[dir] :
+ null;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyRunCmd.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyRunCmd.java
new file mode 100644
index 0000000..6365065
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyRunCmd.java
@@ -0,0 +1,26 @@
+/*
+ * ShinyRunCmd.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyRunCmd extends JavaScriptObject
+{
+ protected ShinyRunCmd() {}
+
+ public final native String getRunCmd() /*-{
+ return this.run_cmd;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyViewerType.java b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyViewerType.java
new file mode 100644
index 0000000..5eb6cae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/model/ShinyViewerType.java
@@ -0,0 +1,32 @@
+/*
+ * ShinyViewerType.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShinyViewerType extends JavaScriptObject
+{
+ protected ShinyViewerType() {}
+
+ public final static int SHINY_VIEWER_USER = 0;
+ public final static int SHINY_VIEWER_NONE = 1;
+ public final static int SHINY_VIEWER_PANE = 2;
+ public final static int SHINY_VIEWER_WINDOW = 3;
+ public final static int SHINY_VIEWER_BROWSER = 4;
+
+ public final native int getViewerType() /*-{
+ return this.viewerType;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/DeployArrow.png b/src/gwt/src/org/rstudio/studio/client/shiny/ui/DeployArrow.png
new file mode 100644
index 0000000..16f916b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/shiny/ui/DeployArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationPanel.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationPanel.java
new file mode 100644
index 0000000..b32e308
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationPanel.java
@@ -0,0 +1,143 @@
+/*
+ * ShinyApplicationPanel.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.ResizeComposite;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.RStudioFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.shiny.ShinyApplicationPresenter;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+
+public class ShinyApplicationPanel extends ResizeComposite
+ implements ShinyApplicationPresenter.Display
+{
+ @Inject
+ public ShinyApplicationPanel(Commands commands)
+ {
+ commands_ = commands;
+ rootPanel_ = new LayoutPanel();
+
+ toolbar_ = createToolbar(commands_);
+ rootPanel_.add(toolbar_);
+ rootPanel_.setWidgetLeftRight(toolbar_, 0, Unit.PX, 0, Unit.PX);
+ rootPanel_.setWidgetTopHeight(toolbar_, 0, Unit.PX, toolbar_.getHeight(), Unit.PX);
+
+ initWidget(rootPanel_);
+ }
+
+ private Toolbar createToolbar(Commands commands)
+ {
+ Toolbar toolbar = new Toolbar();
+ ToolbarButton refreshButton =
+ commands_.reloadShinyApp().createToolbarButton();
+ refreshButton.setLeftImage(commands_.viewerRefresh().getImageResource());
+ refreshButton.getElement().getStyle().setMarginTop(2, Unit.PX);
+ toolbar.addLeftWidget(refreshButton);
+ if (Desktop.isDesktop())
+ {
+ toolbar.addLeftSeparator();
+ urlBox_ = new Label("");
+ Style style = urlBox_.getElement().getStyle();
+ style.setColor("#606060");
+ urlBox_.addStyleName(ThemeStyles.INSTANCE.selectableText());
+ toolbar.addLeftWidget(urlBox_);
+ }
+ ToolbarButton popout = commands_.viewerPopout().createToolbarButton();
+ popout.setText("Open in Browser");
+ toolbar.addRightWidget(popout);
+ return toolbar;
+ }
+
+ @Override
+ public void showApp(ShinyApplicationParams params)
+ {
+ appParams_ = params;
+
+ if (appFrame_ != null)
+ {
+ rootPanel_.remove(appFrame_);
+ appFrame_ = null;
+ }
+
+ // We don't show the URL in server mode
+ if (urlBox_ != null)
+ urlBox_.setText(params.getUrl());
+
+ appFrame_ = new RStudioFrame(appParams_.getUrl());
+ appFrame_.setSize("100%", "100%");
+ rootPanel_.add(appFrame_);
+ rootPanel_.setWidgetLeftRight(appFrame_, 0, Unit.PX, 0, Unit.PX);
+ rootPanel_.setWidgetTopBottom(appFrame_, toolbar_.getHeight()+1, Unit.PX, 0, Unit.PX);
+ }
+
+ @Override
+ public void reloadApp()
+ {
+ // appFrame_.getWindow().reload() would be better, but won't work here
+ // due to same-origin policy restrictions
+ appFrame_.setUrl(appFrame_.getUrl());
+ }
+
+ @Override
+ public String getDocumentTitle()
+ {
+ return appFrame_.getWindow().getDocument().getTitle();
+ }
+
+ @Override
+ public String getUrl()
+ {
+ return appParams_.getUrl();
+ }
+
+ @Override
+ public String getAbsoluteUrl()
+ {
+ // always return a full URL; if the app URL appears to be relative,
+ // make it absolute by prepending the relevant portion of this window's
+ // URL
+ String url = appParams_.getUrl();
+ if (!(url.startsWith("http://") || url.startsWith("https://")))
+ {
+ String thisUrl = Window.Location.getProtocol() + "//" +
+ Window.Location.getHost() + "/" +
+ Window.Location.getPath();
+ if (!thisUrl.endsWith("/"))
+ thisUrl += "/";
+ url = thisUrl + url;
+ }
+ return url;
+ }
+
+ private final Commands commands_;
+
+ private LayoutPanel rootPanel_;
+ private Toolbar toolbar_;
+ private Label urlBox_;
+
+ private RStudioFrame appFrame_;
+ private ShinyApplicationParams appParams_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationView.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationView.java
new file mode 100644
index 0000000..2e2c2d8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationView.java
@@ -0,0 +1,22 @@
+/*
+ * ShinyApplicationView.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import org.rstudio.studio.client.common.satellite.SatelliteApplicationView;
+
+public interface ShinyApplicationView extends SatelliteApplicationView
+{
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationWindow.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationWindow.java
new file mode 100644
index 0000000..9dba9d4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyApplicationWindow.java
@@ -0,0 +1,73 @@
+/*
+ * ShinyApplicationWindow.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.satellite.SatelliteWindow;
+import org.rstudio.studio.client.shiny.ShinyApplicationPresenter;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+
+ at Singleton
+public class ShinyApplicationWindow extends SatelliteWindow
+ implements ShinyApplicationView
+{
+
+ @Inject
+ public ShinyApplicationWindow(Provider<EventBus> pEventBus,
+ Provider<FontSizeManager> pFSManager,
+ Provider<ShinyApplicationPresenter> pPresenter)
+ {
+ super(pEventBus, pFSManager);
+ pPresenter_ = pPresenter;
+ }
+
+ @Override
+ protected void onInitialize(LayoutPanel mainPanel, JavaScriptObject params)
+ {
+ ShinyApplicationParams appParams = params.cast();
+ Window.setTitle(appParams.getPath() + " - " + "Shiny");
+ ShinyApplicationPresenter appPresenter = pPresenter_.get();
+ appPresenter.loadApp(appParams);
+
+ // make it fill the containing layout panel
+ Widget presWidget = appPresenter.asWidget();
+ mainPanel.add(presWidget);
+ mainPanel.setWidgetLeftRight(presWidget, 0, Unit.PX, 0, Unit.PX);
+ mainPanel.setWidgetTopBottom(presWidget, 0, Unit.PX, 0, Unit.PX);
+ }
+
+ @Override
+ public void reactivate(JavaScriptObject params)
+ {
+ }
+
+ @Override
+ public Widget getWidget()
+ {
+ return this;
+ }
+
+ private Provider<ShinyApplicationPresenter> pPresenter_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.java
new file mode 100644
index 0000000..3ca4293
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.java
@@ -0,0 +1,78 @@
+/*
+ * ShinyAppsAccountManager.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ShinyAppsAccountManager extends Composite
+{
+
+ private static ShinyAppsAccountManagerUiBinder uiBinder = GWT
+ .create(ShinyAppsAccountManagerUiBinder.class);
+
+ interface ShinyAppsAccountManagerUiBinder extends
+ UiBinder<Widget, ShinyAppsAccountManager>
+ {
+ }
+
+ public ShinyAppsAccountManager()
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ public void setAccountList(JsArrayString accounts)
+ {
+ accountList.clear();
+ for (int i = 0; i < accounts.length(); i++)
+ {
+ accountList.addItem(accounts.get(i));
+ }
+ }
+
+ public String getSelectedAccount()
+ {
+ int idx = accountList.getSelectedIndex();
+ return idx >= 0 ? accountList.getItemText(idx) : null;
+ }
+
+ public void removeAccount(String accountName)
+ {
+ for (int i = 0; i < accountList.getItemCount(); i++)
+ {
+ if (accountList.getItemText(i).equals(accountName))
+ {
+ accountList.removeItem(i);
+ return;
+ }
+ }
+ }
+
+ public HandlerRegistration addAccountSelectionChangeHandler(
+ ChangeHandler handler)
+ {
+ return accountList.addChangeHandler(handler);
+ }
+
+ @UiField
+ ListBox accountList;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.ui.xml b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.ui.xml
new file mode 100644
index 0000000..92e290a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManager.ui.xml
@@ -0,0 +1,24 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:r="urn:import:org.rstudio.core.client.widget">
+ <ui:style>
+ .accountList
+ {
+ width: 100%;
+ padding: 5px;
+ }
+
+ .accountHeader
+ {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+ </ui:style>
+ <g:HTMLPanel>
+ <g:Label styleName="{style.accountHeader}"
+ text="Connected Accounts"></g:Label>
+ <g:ListBox styleName="{style.accountList}"
+ ui:field="accountList" visibleItemCount="5"></g:ListBox>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManagerDialog.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManagerDialog.java
new file mode 100644
index 0000000..3268508
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsAccountManagerDialog.java
@@ -0,0 +1,183 @@
+/*
+ * ShinyAppsAccountManagerDialog.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+public class ShinyAppsAccountManagerDialog
+ extends ShinyAppsDialog<ShinyAppsAccountManager>
+{
+ public ShinyAppsAccountManagerDialog(ShinyAppsServerOperations server,
+ final GlobalDisplay display)
+ {
+ super(server, display, new ShinyAppsAccountManager());
+ setText("Manage ShinyApps Accounts");
+ setWidth("300px");
+
+ connectButton_ = new ThemedButton("Connect...");
+ connectButton_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onConnect();
+ }
+ });
+ disconnectButton_ = new ThemedButton("Disconnect");
+ disconnectButton_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onDisconnect();
+ }
+ });
+ disconnectButton_.setEnabled(false);
+ doneButton_ = new ThemedButton("Done");
+ doneButton_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onDone();
+ }
+ });
+ addLeftButton(disconnectButton_);
+ addLeftButton(connectButton_);
+ addOkButton(doneButton_);
+
+ contents_.addAccountSelectionChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ setDisconnectButtonEnabledState();
+ }
+ });
+
+ refreshAccountList();
+ }
+
+ private void onDisconnect()
+ {
+ final String account = contents_.getSelectedAccount();
+ if (account == null)
+ {
+ display_.showErrorMessage("Error Disconnection Account",
+ "Please select an account to disconnect.");
+ return;
+ }
+ display_.showYesNoMessage(
+ GlobalDisplay.MSG_QUESTION,
+ "Confirm Remove Account",
+ "Are you sure you want to disconnect the '" +
+ account +
+ "' account? This won't delete the account on ShinyApps.",
+ false,
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ onConfirmDisconnect(account);
+ }
+ }, null, null, "Disconnect Account", "Cancel", false);
+ }
+
+ private void onConfirmDisconnect(final String accountName)
+ {
+ server_.removeShinyAppsAccount(accountName, new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ contents_.removeAccount(accountName);
+ setDisconnectButtonEnabledState();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error Disconnecting Account",
+ error.getMessage());
+ }
+ });
+ }
+
+ private void onConnect()
+ {
+ ShinyAppsConnectAccountDialog dialog =
+ new ShinyAppsConnectAccountDialog(server_, display_);
+ dialog.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ refreshAccountList();
+ }
+ });
+ dialog.showModal();
+ }
+
+ private void onDone()
+ {
+ closeDialog();
+ }
+
+ private void setDisconnectButtonEnabledState()
+ {
+ disconnectButton_.setEnabled(
+ contents_.getSelectedAccount() != null);
+ }
+
+ private void refreshAccountList()
+ {
+ server_.getShinyAppsAccountList(new ServerRequestCallback<JsArrayString>()
+ {
+ @Override
+ public void onResponseReceived(JsArrayString accounts)
+ {
+ contents_.setAccountList(accounts);
+ setDisconnectButtonEnabledState();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error retrieving ShinyApps accounts",
+ error.getMessage());
+ }
+ });
+ }
+
+ private ThemedButton connectButton_;
+ private ThemedButton disconnectButton_;
+ private ThemedButton doneButton_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.java
new file mode 100644
index 0000000..52933a0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.java
@@ -0,0 +1,79 @@
+/*
+ * ShinyAppsConnectAccount.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ShinyAppsConnectAccount extends Composite
+{
+
+ private static ShinyAppsConnectAccountUiBinder uiBinder = GWT
+ .create(ShinyAppsConnectAccountUiBinder.class);
+
+ interface ShinyAppsConnectAccountUiBinder extends
+ UiBinder<Widget, ShinyAppsConnectAccount>
+ {
+ }
+
+ interface ConnectStyle extends CssResource
+ {
+ String spaced();
+ }
+
+ public ShinyAppsConnectAccount()
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+ accountInfo.addKeyUpHandler(new KeyUpHandler()
+ {
+ @Override
+ public void onKeyUp(KeyUpEvent event)
+ {
+ if (onAccountInfoChanged_ != null)
+ {
+ onAccountInfoChanged_.execute();
+ }
+ }
+ });
+ }
+
+ public void setOnAccountInfoChanged(Command cmd)
+ {
+ onAccountInfoChanged_ = cmd;
+ }
+
+ public String getAccountInfo()
+ {
+ return accountInfo.getText();
+ }
+
+ public ConnectStyle getStyle()
+ {
+ return style;
+ }
+
+ @UiField TextArea accountInfo;
+ @UiField ConnectStyle style;
+
+ private Command onAccountInfoChanged_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.ui.xml b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.ui.xml
new file mode 100644
index 0000000..0f26e18
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccount.ui.xml
@@ -0,0 +1,44 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style type="org.rstudio.studio.client.shiny.ui.ShinyAppsConnectAccount.ConnectStyle">
+ @eval fixedFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ .accountInfo
+ {
+ font-family: fixedFont;
+ padding: 5px;
+ margin-left: 40px;
+ width: 325px;
+ }
+
+ .connectHeader
+ {
+ font-weight: bold;
+ }
+
+ .spaced
+ {
+ margin-bottom: 5px;
+ }
+ </ui:style>
+ <g:HTMLPanel>
+ <g:Label styleName="{style.connectHeader}"
+ text="Connecting your ShinyApps account"></g:Label>
+ <g:HTML>
+ <ol>
+ <li><p>Go to <a href="http://my.shinyapps.io/" target="_blank">your
+ account on ShinyApps</a> and log in.</p></li>
+ <li><p>Click your name, then choose <b>Tokens</b> from your account menu.
+ </p></li>
+ <li><p>Use the Copy command under <b>Actions</b> to copy the token,
+ account, and secret information to the clipboard, and paste the
+ result here or at the R console. (It will start with
+ <tt>shinyapps::setAccountInfo</tt>.)</p></li>
+ </ol>
+ </g:HTML>
+ <g:TextArea styleName="{style.accountInfo} {style.spaced}"
+ ui:field="accountInfo" visibleLines="5">
+ </g:TextArea>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccountDialog.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccountDialog.java
new file mode 100644
index 0000000..1d5d7aa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsConnectAccountDialog.java
@@ -0,0 +1,102 @@
+/*
+ * ShinyAppsConnectAccountDialog.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HTML;
+
+public class ShinyAppsConnectAccountDialog
+ extends ShinyAppsDialog<ShinyAppsConnectAccount>
+{
+ public ShinyAppsConnectAccountDialog(ShinyAppsServerOperations server,
+ final GlobalDisplay display)
+ {
+ super(server, display, new ShinyAppsConnectAccount());
+ display_ = display;
+ server_ = server;
+
+ setText("Connect ShinyApps account");
+ setWidth("450px");
+ HTML createLink = new HTML("<small>Need a ShinyApps account?<br />" +
+ "Get started at <a href=\"http://www.shinyapps.io/\"" +
+ "target=\"blank\">http://www.shinyapps.io</a></small>");
+ createLink.setStyleName(contents_.getStyle().spaced());
+ addLeftWidget(createLink);
+ connectButton_ = new ThemedButton("Connect");
+ connectButton_.setEnabled(false);
+ connectButton_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onConnect();
+ }
+ });
+ addCancelButton();
+ addOkButton(connectButton_);
+ contents_.setOnAccountInfoChanged(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ connectButton_.setEnabled(contents_.getAccountInfo().length() > 0);
+ }
+ });
+ }
+
+ private void onConnect()
+ {
+ final String cmd = contents_.getAccountInfo();
+ if (!cmd.startsWith("shinyapps::setAccountInfo"))
+ {
+ display_.showErrorMessage("Error Connecting Account",
+ "The pasted command should start with " +
+ "shinyapps::setAccountInfo. If you're having trouble, try " +
+ "connecting your account manually; type " +
+ "?shinyapps::setAccountInfo at the R console for help.");
+ return;
+ }
+ server_.connectShinyAppsAccount(cmd,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ closeDialog();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error Connecting Account",
+ "The command '" + cmd + "' failed. You can set up an " +
+ "account manually by using shinyapps::setAccountInfo; " +
+ "type ?shinyapps::setAccountInfo at the R console for " +
+ "more information.");
+ }
+ });
+ }
+
+ private ThemedButton connectButton_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.java
new file mode 100644
index 0000000..facf8b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.java
@@ -0,0 +1,223 @@
+/*
+ * ShinyAppsDeploy.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import java.util.List;
+
+import org.rstudio.studio.client.common.FilePathUtils;
+import org.rstudio.studio.client.shiny.model.ShinyAppsApplicationInfo;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ShinyAppsDeploy extends Composite
+{
+ private static ShinyAppsDeployUiBinder uiBinder = GWT
+ .create(ShinyAppsDeployUiBinder.class);
+
+ interface ShinyAppsDeployUiBinder extends UiBinder<Widget, ShinyAppsDeploy>
+ {
+ }
+
+ public interface DeployStyle extends CssResource
+ {
+ String statusLabel();
+ String normalStatus();
+ String otherStatus();
+ String launchCheck();
+ }
+
+ public interface DeployResources extends ClientBundle
+ {
+ @Source("DeployArrow.png")
+ ImageResource deployArrow();
+ }
+
+ public ShinyAppsDeploy()
+ {
+ initWidget(uiBinder.createAndBindUi(this));
+
+ // Validate the application name on every keystroke
+ appName.addKeyUpHandler(new KeyUpHandler()
+ {
+ @Override
+ public void onKeyUp(KeyUpEvent event)
+ {
+ validateAppName();
+ }
+ });
+ }
+
+ public void setSourceDir(String dir)
+ {
+ sourceDir.setText(dir);
+ appName.setText(FilePathUtils.friendlyFileName(dir));
+ }
+
+ public void setAccountList(JsArrayString accounts, String selected)
+ {
+ accountList.clear();
+ int selectedIdx = 0;
+ for (int i = 0; i < accounts.length(); i++)
+ {
+ String account = accounts.get(i);
+ accountList.addItem(account);
+ if (account.equals(selected))
+ selectedIdx = i;
+ }
+ accountList.setSelectedIndex(selectedIdx);
+ }
+
+ public String getSelectedAccount()
+ {
+ int idx = accountList.getSelectedIndex();
+ return idx >= 0 ?
+ accountList.getItemText(idx) :
+ null;
+ }
+
+ public String getSelectedApp()
+ {
+ int idx = appList.getSelectedIndex();
+ return idx >= 0 ?
+ appList.getItemText(idx) :
+ null;
+ }
+
+ public void setAppList(List<String> apps, String selected)
+ {
+ appList.clear();
+ int selectedIdx = Math.max(0, apps.size() - 1);
+ if (apps != null)
+ {
+ for (int i = 0; i < apps.size(); i++)
+ {
+ String app = apps.get(i);
+ appList.addItem(app);
+ if (app.equals(selected))
+ {
+ selectedIdx = i;
+ }
+ }
+ }
+ appList.addItem("Create New");
+ appList.setSelectedIndex(selectedIdx);
+ }
+
+ public String getNewAppName()
+ {
+ return appName.getText();
+ }
+
+ public void showAppInfo(ShinyAppsApplicationInfo info)
+ {
+ if (info == null)
+ {
+ appInfoPanel.setVisible(false);
+ nameLabel.setVisible(true);
+ appName.setVisible(true);
+ validateAppName();
+ return;
+ }
+
+ setAppNameValid(true);
+ urlAnchor.setText(info.getUrl());
+ urlAnchor.setHref(info.getUrl());
+ String status = info.getStatus();
+ statusLabel.setText(status);
+ statusLabel.setStyleName(style.statusLabel() + " " +
+ (status.equals("running") ?
+ style.normalStatus() :
+ style.otherStatus()));
+
+ appInfoPanel.setVisible(true);
+ nameLabel.setVisible(false);
+ appName.setVisible(false);
+ nameValidatePanel.setVisible(false);
+ }
+
+ public HandlerRegistration addAccountChangeHandler(ChangeHandler handler)
+ {
+ return accountList.addChangeHandler(handler);
+ }
+
+ public HandlerRegistration addAppChangeHandler(ChangeHandler handler)
+ {
+ return appList.addChangeHandler(handler);
+ }
+
+ public void setOnDeployEnabled(Command cmd)
+ {
+ onDeployEnabled_ = cmd;
+ }
+
+ public void setOnDeployDisabled(Command cmd)
+ {
+ onDeployDisabled_ = cmd;
+ }
+
+ public DeployStyle getStyle()
+ {
+ return style;
+ }
+
+ private void validateAppName()
+ {
+ String app = appName.getText();
+ RegExp validReg = RegExp.compile("^[A-Za-z0-9_-]{4,63}$");
+ setAppNameValid(validReg.test(app));
+ }
+
+ private void setAppNameValid(boolean isValid)
+ {
+ nameValidatePanel.setVisible(!isValid);
+ if (isValid && onDeployEnabled_ != null)
+ onDeployEnabled_.execute();
+ else if (!isValid && onDeployDisabled_ != null)
+ onDeployDisabled_.execute();
+ }
+
+ @UiField Anchor urlAnchor;
+ @UiField Label sourceDir;
+ @UiField Label nameLabel;
+ @UiField Label statusLabel;
+ @UiField ListBox accountList;
+ @UiField ListBox appList;
+ @UiField TextBox appName;
+ @UiField HTMLPanel appInfoPanel;
+ @UiField HTMLPanel nameValidatePanel;
+ @UiField DeployStyle style;
+
+ private Command onDeployEnabled_;
+ private Command onDeployDisabled_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.ui.xml b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.ui.xml
new file mode 100644
index 0000000..49c7c3e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeploy.ui.xml
@@ -0,0 +1,117 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:with field="res" type="org.rstudio.studio.client.shiny.ui.ShinyAppsDeploy.DeployResources" />
+ <ui:with field="projRes" type="org.rstudio.studio.client.projects.ui.newproject.NewProjectResources" />
+ <ui:style type="org.rstudio.studio.client.shiny.ui.ShinyAppsDeploy.DeployStyle">
+ .sourceDestLabels
+ {
+ color: #808080;
+ }
+
+ .dropListControl
+ {
+ width: 250px;
+ }
+
+ .gridControl
+ {
+ display: block;
+ width: 244px;
+ padding: 3px;
+ }
+
+ .source
+ {
+ font-weight: bold;
+ padding-top: 10px;
+ }
+
+ .statusLabel
+ {
+ font-size: 140%;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 15px;
+ }
+
+ .urlAnchor
+ {
+ display: block;
+ text-align: center;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ }
+
+ .transferArrow
+ {
+ margin-top: 10px;
+ margin-left: 50px;
+ }
+
+ .normalStatus
+ {
+ color: green;
+ }
+
+ .otherStatus
+ {
+ color: red;
+ }
+
+ .validateError
+ {
+ margin-left: 25px;
+ color: red;
+ }
+
+ .shinyLogo
+ {
+ float: left;
+ }
+
+ .launchCheck
+ {
+ display: block;
+ margin-top: 7px;
+ }
+ </ui:style>
+ <g:HTMLPanel>
+ <g:Image styleName="{style.shinyLogo}" resource="{projRes.shinyAppIcon}"></g:Image>
+ <g:Label styleName="{style.source}" ui:field="sourceDir"></g:Label>
+ <g:Image styleName="{style.transferArrow}" resource="{res.deployArrow}"></g:Image>
+ <g:Grid>
+ <g:row>
+ <g:customCell>
+ <g:Label text="Account"></g:Label>
+ </g:customCell>
+ <g:customCell>
+ <g:ListBox styleName="{style.dropListControl}" ui:field="accountList"></g:ListBox>
+ </g:customCell>
+ </g:row>
+ <g:row>
+ <g:customCell>
+ <g:Label text="Application"></g:Label>
+ </g:customCell>
+ <g:customCell>
+ <g:ListBox styleName="{style.dropListControl}" ui:field="appList"></g:ListBox>
+ </g:customCell>
+ </g:row>
+ <g:row>
+ <g:customCell>
+ <g:Label ui:field="nameLabel" text="Name"></g:Label>
+ </g:customCell>
+ <g:customCell>
+ <g:TextBox styleName="{style.gridControl}" ui:field="appName" maxLength="63"></g:TextBox>
+ </g:customCell>
+ </g:row>
+ </g:Grid>
+ <g:HTMLPanel visible="false" ui:field="appInfoPanel">
+ <g:Anchor styleName="{style.urlAnchor}" ui:field="urlAnchor" target="_blank"></g:Anchor>
+ <g:Label styleName="{style.statusLabel}" ui:field="statusLabel"></g:Label>
+ </g:HTMLPanel>
+ <g:HTMLPanel visible="false" ui:field="nameValidatePanel">
+ <g:Label styleName="{style.validateError}" text="Application names must be between 4 and 63 characters, and can contain only alphanumeric characters, dashes, and underscores."></g:Label>
+ </g:HTMLPanel>
+ </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeployDialog.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeployDialog.java
new file mode 100644
index 0000000..0d0369d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDeployDialog.java
@@ -0,0 +1,363 @@
+/*
+ * ShinyAppsDeployDialog.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.shiny.events.ShinyAppsDeployInitiatedEvent;
+import org.rstudio.studio.client.shiny.model.ShinyAppsApplicationInfo;
+import org.rstudio.studio.client.shiny.model.ShinyAppsDeploymentRecord;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.PopupPanel;
+
+public class ShinyAppsDeployDialog
+ extends ShinyAppsDialog<ShinyAppsDeploy>
+{
+ public ShinyAppsDeployDialog(ShinyAppsServerOperations server,
+ final GlobalDisplay display,
+ EventBus events,
+ String sourceDir,
+ final String lastAccount,
+ String lastAppName)
+
+ {
+ super(server, display, new ShinyAppsDeploy());
+ setText("Deploy to ShinyApps");
+ setWidth("350px");
+ deployButton_ = new ThemedButton("Deploy");
+ addCancelButton();
+ addOkButton(deployButton_);
+ sourceDir_ = sourceDir;
+ events_ = events;
+ lastAppName_ = lastAppName;
+
+ launchCheck_ = new CheckBox("Launch");
+ launchCheck_.setValue(true);
+ launchCheck_.setStyleName(contents_.getStyle().launchCheck());
+ addLeftWidget(launchCheck_);
+
+ contents_.setSourceDir(sourceDir);
+
+ deployButton_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ onDeploy();
+ }
+ });
+
+ indicator_ = addProgressIndicator(false);
+
+ // Get the deployments of this directory from any account (should be fast,
+ // since this information is stored locally in the directory).
+ server_.getShinyAppsDeployments(sourceDir,
+ new ServerRequestCallback<JsArray<ShinyAppsDeploymentRecord>>()
+ {
+ @Override
+ public void onResponseReceived(
+ JsArray<ShinyAppsDeploymentRecord> records)
+ {
+ processDeploymentRecords(records);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // If an error occurs we won't have any local deployment records,
+ // but the user can still create new deployments.
+ }
+ });
+
+ server_.getShinyAppsAccountList(new ServerRequestCallback<JsArrayString>()
+ {
+ @Override
+ public void onResponseReceived(JsArrayString accounts)
+ {
+ if (accounts.length() == 0)
+ {
+ // The user has no accounts connected--hide ourselves and
+ // ask the user to connect an account before we continue.
+ hide();
+ ShinyAppsConnectAccountDialog dialog =
+ new ShinyAppsConnectAccountDialog(server_, display_);
+ dialog.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ onConnectAccountFinished();
+ }
+ });
+ dialog.showModal();
+ }
+ else
+ {
+ // pre-select the last account used to deploy this app, or
+ // the first account if we don't have any deployment records
+ String initialAccount = lastAccount == null ?
+ accounts.get(0) : lastAccount;
+ contents_.setAccountList(accounts, initialAccount);
+ updateApplicationList();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error retrieving ShinyApps accounts",
+ error.getMessage());
+ closeDialog();
+ }
+ });
+
+ // Update the list of applications when the account is changed
+ contents_.addAccountChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ updateApplicationList();
+ }
+ });
+
+ // Update app info when the application is changed
+ contents_.addAppChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ updateApplicationInfo();
+ }
+ });
+
+ contents_.setOnDeployDisabled(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ deployButton_.setEnabled(false);
+ }
+ });
+
+ contents_.setOnDeployEnabled(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ deployButton_.setEnabled(true);
+ }
+ });
+ }
+
+ // Runs when the selected application changes; shows the cached information
+ // (URL and status) for the selected application
+ private void updateApplicationInfo()
+ {
+ String appName = contents_.getSelectedApp();
+ if (appName == "Create New")
+ {
+ contents_.showAppInfo(null);
+ }
+ else if (apps_.containsKey(contents_.getSelectedAccount()))
+ {
+ JsArray<ShinyAppsApplicationInfo> apps =
+ apps_.get(contents_.getSelectedAccount());
+ for (int i = 0; i < apps.length(); i++)
+ {
+ if (apps.get(i).getName().equals(appName))
+ {
+ contents_.showAppInfo(apps.get(i));
+ }
+ }
+ }
+ }
+
+ private void updateApplicationList()
+ {
+ final String accountName = contents_.getSelectedAccount();
+ if (accountName == null)
+ return;
+
+ // Check to see if the app list is already in our cache
+ if (apps_.containsKey(accountName))
+ {
+ setAppList(apps_.get(accountName));
+ return;
+ }
+
+ // This operation hits the ShinyApps service, so show some progress if
+ // it takes more than a few ms
+ final Timer t = new Timer() {
+ @Override
+ public void run()
+ {
+ indicator_.onProgress("Contacting ShinyApps...");
+ }
+ };
+ t.schedule(500);
+
+ // Not already in our cache, fetch it and populate the cache
+ server_.getShinyAppsAppList(accountName,
+ new ServerRequestCallback<JsArray<ShinyAppsApplicationInfo>>()
+ {
+ @Override
+ public void onResponseReceived(
+ JsArray<ShinyAppsApplicationInfo> apps)
+ {
+
+ t.cancel();
+ indicator_.onCompleted();
+ apps_.put(accountName, apps);
+ setAppList(apps);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ t.cancel();
+ indicator_.onCompleted();
+ // we can always create a new app
+ contents_.setAppList(null, null);
+ }
+ });
+ }
+
+ private void setAppList(JsArray<ShinyAppsApplicationInfo> apps)
+ {
+ ArrayList<String> appNames = new ArrayList<String>();
+ for (int i = 0; i < apps.length(); i++)
+ {
+ ShinyAppsApplicationInfo appInfo = apps.get(i);
+ // Filter the app list by URLs deployed from this directory
+ // specifically
+ if (deployments_.containsKey(appInfo.getUrl()))
+ {
+ appNames.add(apps.get(i).getName());
+ }
+ }
+ contents_.setAppList(appNames, lastAppName_);
+ updateApplicationInfo();
+ }
+
+ // Runs when we've finished doing a just-in-time account connection
+ private void onConnectAccountFinished()
+ {
+ server_.getShinyAppsAccountList(new ServerRequestCallback<JsArrayString>()
+ {
+ @Override
+ public void onResponseReceived(JsArrayString accounts)
+ {
+ if (accounts.length() == 0)
+ {
+ // The user didn't successfully connect an account--just close
+ // ourselves
+ closeDialog();
+ }
+ else
+ {
+ // We have an account, show it and re-display ourselves
+ contents_.setAccountList(accounts, accounts.get(0));
+ updateApplicationList();
+ showModal();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ display_.showErrorMessage("Error retrieving ShinyApps accounts",
+ error.getMessage());
+ closeDialog();
+ }
+ });
+ }
+
+ private void onDeploy()
+ {
+ String appName = contents_.getSelectedApp();
+ if (appName == null || appName == "Create New")
+ appName = contents_.getNewAppName();
+
+ String account = contents_.getSelectedAccount();
+ String launch = launchCheck_.getValue() ? "TRUE" : "FALSE";
+
+ // send the deployment command to the console
+ String cmd = "shinyapps::deployApp(appDir=\"" + sourceDir_ + "\", " +
+ "account=\"" + account + "\", " +
+ "appName=\"" + appName + "\", " +
+ "launch.browser=" + launch + ")";
+
+ events_.fireEvent(new SendToConsoleEvent(cmd, true));
+
+ // let everyone know a deployment has started (this triggers the
+ // deployment record to be cached for this directory, so we can
+ // issue an identical deployment next time)
+ events_.fireEvent(new ShinyAppsDeployInitiatedEvent(
+ sourceDir_,
+ ShinyAppsDeploymentRecord.create(appName, account, "")));
+
+ closeDialog();
+ }
+
+ // Create a lookup from app URL to deployments made of this directory
+ // to that URL
+ private void processDeploymentRecords(
+ JsArray<ShinyAppsDeploymentRecord> records)
+ {
+ for (int i = 0; i < records.length(); i++)
+ {
+ ShinyAppsDeploymentRecord record = records.get(i);
+ deployments_.put(record.getUrl(), record);
+ }
+ }
+
+ private final EventBus events_;
+
+ private String sourceDir_;
+ private String lastAppName_;
+ private ThemedButton deployButton_;
+ private ProgressIndicator indicator_;
+ private CheckBox launchCheck_;
+
+ // Map of account name to a list of applications owned by that account
+ private Map<String, JsArray<ShinyAppsApplicationInfo>> apps_ =
+ new HashMap<String, JsArray<ShinyAppsApplicationInfo>>();
+
+ // Map of app URL to the deployment made to that URL
+ private Map<String, ShinyAppsDeploymentRecord> deployments_ =
+ new HashMap<String, ShinyAppsDeploymentRecord>();
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDialog.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDialog.java
new file mode 100644
index 0000000..b5998c6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyAppsDialog.java
@@ -0,0 +1,45 @@
+/*
+ * ShinyAppsDialog.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.shiny.model.ShinyAppsServerOperations;
+
+import com.google.gwt.user.client.ui.Widget;
+
+public abstract class ShinyAppsDialog<W extends Widget>
+ extends ModalDialogBase
+{
+ public ShinyAppsDialog(ShinyAppsServerOperations server,
+ final GlobalDisplay display,
+ W contents)
+ {
+ server_ = server;
+ display_ = display;
+ contents_ = contents;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return contents_;
+ }
+
+ protected W contents_;
+
+ protected ShinyAppsServerOperations server_;
+ protected GlobalDisplay display_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyViewerTypePopupMenu.java b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyViewerTypePopupMenu.java
new file mode 100644
index 0000000..d6bf68a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/shiny/ui/ShinyViewerTypePopupMenu.java
@@ -0,0 +1,76 @@
+/*
+ * ShinyViewerTypePopupMenu.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.shiny.ui;
+
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.shiny.model.ShinyServerOperations;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.inject.Inject;
+
+// An extension of the toolbar popup menu that gets the current Shiny viewer
+// type and checks the appropriate command before showing the menu
+public class ShinyViewerTypePopupMenu extends ToolbarPopupMenu
+{
+ @Inject
+ public ShinyViewerTypePopupMenu(Commands commands,
+ ShinyServerOperations server)
+ {
+ commands_ = commands;
+ server_ = server;
+ addItem(commands.shinyRunInViewer().createMenuItem(false));
+ addItem(commands.shinyRunInPane().createMenuItem(false));
+ addSeparator();
+ addItem(commands.shinyRunInBrowser().createMenuItem(false));
+ }
+
+ @Override
+ public void getDynamicPopupMenu
+ (final ToolbarPopupMenu.DynamicPopupMenuCallback callback)
+ {
+ final ToolbarPopupMenu menu = this;
+ server_.getShinyViewerType(
+ new ServerRequestCallback<ShinyViewerType>()
+ {
+ @Override
+ public void onResponseReceived(ShinyViewerType response)
+ {
+ int viewerType = response.getViewerType();
+ commands_.shinyRunInPane().setChecked(false);
+ commands_.shinyRunInViewer().setChecked(false);
+ commands_.shinyRunInBrowser().setChecked(false);
+ if (ShinyViewerType.SHINY_VIEWER_PANE == viewerType)
+ commands_.shinyRunInPane().setChecked(true);
+ if (ShinyViewerType.SHINY_VIEWER_WINDOW == viewerType)
+ commands_.shinyRunInViewer().setChecked(true);
+ if (ShinyViewerType.SHINY_VIEWER_BROWSER == viewerType)
+ commands_.shinyRunInBrowser().setChecked(true);
+ callback.onPopupMenu(menu);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onPopupMenu(menu);
+ }
+ });
+ }
+
+ private final ShinyServerOperations server_;
+ private final Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplication.java b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplication.java
new file mode 100644
index 0000000..884c421
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplication.java
@@ -0,0 +1,39 @@
+/*
+ * VCSApplication.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.vcs;
+
+import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteApplication;
+import org.rstudio.studio.client.common.vcs.AskPassManager;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class VCSApplication extends SatelliteApplication
+{
+ @Inject
+ public VCSApplication(VCSApplicationView view,
+ Satellite satellite,
+ Provider<AceThemes> pAceThemes,
+ ApplicationUncaughtExceptionHandler uncaughtExHandler,
+ AskPassManager askPassManager) // force gin to create
+ {
+ super("review_changes", view, satellite, pAceThemes, uncaughtExHandler);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationParams.java b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationParams.java
new file mode 100644
index 0000000..f77f816
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationParams.java
@@ -0,0 +1,79 @@
+/*
+ * VCSApplicationParams.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.vcs;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.studio.client.common.vcs.StatusAndPathInfo;
+
+public class VCSApplicationParams extends JavaScriptObject
+{
+ protected VCSApplicationParams()
+ {
+ }
+
+ public final static VCSApplicationParams create(
+ boolean showHistory,
+ FileSystemItem historyFileFilter,
+ ArrayList<StatusAndPath> selected)
+ {
+ JsArray<StatusAndPathInfo> jsSelected =
+ JavaScriptObject.createArray().cast();
+
+ jsSelected.setLength(selected.size());
+
+ for (int i=0; i<selected.size(); i++)
+ jsSelected.set(i, selected.get(i).toInfo());
+
+ return createNative(showHistory, historyFileFilter, jsSelected);
+ }
+
+ private final static native VCSApplicationParams createNative(
+ boolean showHistory,
+ FileSystemItem historyFileFilter,
+ JsArray<StatusAndPathInfo> selected) /*-{
+ var params = new Object();
+ params.show_history = showHistory;
+ params.history_file_filter = historyFileFilter;
+ params.selected = selected;
+ return params;
+ }-*/;
+
+
+
+ public final native boolean getShowHistory() /*-{
+ return this.show_history;
+ }-*/;
+
+ public final native FileSystemItem getHistoryFileFilter() /*-{
+ return this.history_file_filter;
+ }-*/;
+
+ public final ArrayList<StatusAndPath> getSelected()
+ {
+ return StatusAndPath.fromInfos(getSelectedNative());
+ }
+
+ private final native JsArray<StatusAndPathInfo> getSelectedNative() /*-{
+ return this.selected;
+ }-*/;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationView.java b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationView.java
new file mode 100644
index 0000000..f4d197d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/vcs/VCSApplicationView.java
@@ -0,0 +1,21 @@
+/*
+ * VCSApplicationView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.vcs;
+
+import org.rstudio.studio.client.common.satellite.SatelliteApplicationView;
+
+public interface VCSApplicationView extends SatelliteApplicationView
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/vcs/ui/VCSApplicationWindow.java b/src/gwt/src/org/rstudio/studio/client/vcs/ui/VCSApplicationWindow.java
new file mode 100644
index 0000000..288e98c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/vcs/ui/VCSApplicationWindow.java
@@ -0,0 +1,120 @@
+/*
+ * VCSApplicationWindow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.vcs.ui;
+
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.StyleUtils;
+import org.rstudio.studio.client.common.satellite.SatelliteWindow;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.vcs.VCSApplicationParams;
+import org.rstudio.studio.client.vcs.VCSApplicationView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.frame.VCSPopup;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+
+ at Singleton
+public class VCSApplicationWindow extends SatelliteWindow
+ implements VCSApplicationView
+{
+ @Inject
+ public VCSApplicationWindow(Provider<ReviewPresenter> pReviewPresenter,
+ Provider<HistoryPresenter> pHistoryPresenter,
+ Provider<Commands> pCommands,
+ Provider<EventBus> pEventBus,
+ Provider<FontSizeManager> pFontSizeManager)
+ {
+ super(pEventBus, pFontSizeManager);
+ pReviewPresenter_ = pReviewPresenter;
+ pHistoryPresenter_ = pHistoryPresenter;
+ pCommands_ = pCommands;
+ }
+
+
+ @Override
+ protected void onInitialize(LayoutPanel mainPanel,
+ JavaScriptObject params)
+ {
+ // set our window title
+ Window.setTitle("RStudio: Review Changes");
+
+ // always show scrollbars on the mac
+ StyleUtils.forceMacScrollbars(mainPanel);
+
+ // show the vcs ui in our main panel
+ VCSApplicationParams vcsParams = params.<VCSApplicationParams>cast();
+ ReviewPresenter rpres = pReviewPresenter_.get();
+ ArrayList<StatusAndPath> selected = vcsParams.getSelected();
+ if (selected.size() > 0)
+ rpres.setSelectedPaths(selected);
+ HistoryPresenter hpres = pHistoryPresenter_.get();
+ if (vcsParams.getHistoryFileFilter() != null)
+ hpres.setFileFilter(vcsParams.getHistoryFileFilter());
+
+ vcsPopupController_ = VCSPopup.show(mainPanel,
+ rpres,
+ hpres,
+ vcsParams.getShowHistory());
+ }
+
+ @Override
+ public void reactivate(JavaScriptObject params)
+ {
+ // respect parameters if they were passed
+ if (params != null)
+ {
+ VCSApplicationParams vcsParams = params.<VCSApplicationParams>cast();
+ if (vcsParams.getShowHistory())
+ {
+ vcsPopupController_.switchToHistory(
+ vcsParams.getHistoryFileFilter());
+ }
+ else
+ {
+ vcsPopupController_.switchToReview(vcsParams.getSelected());
+ }
+ }
+ // for no parameters passed we still want to do a refresh
+ else
+ {
+ pCommands_.get().vcsRefreshNoError().execute();
+ }
+ }
+
+ @Override
+ public Widget getWidget()
+ {
+ return this;
+ }
+
+ private final Provider<ReviewPresenter> pReviewPresenter_;
+ private final Provider<HistoryPresenter> pHistoryPresenter_;
+ private final Provider<Commands> pCommands_;
+ private VCSPopup.Controller vcsPopupController_ = null;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ClientStateUpdater.java b/src/gwt/src/org/rstudio/studio/client/workbench/ClientStateUpdater.java
new file mode 100644
index 0000000..6739454
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ClientStateUpdater.java
@@ -0,0 +1,134 @@
+/*
+ * ClientStateUpdater.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.Barrier.Token;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.events.*;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+
+public class ClientStateUpdater extends TimeBufferedCommand
+{
+ /**
+ * "Client state" collectively refers to the set of stuff that the
+ * client would like to remember for the next time we connect.
+ *
+ * Components that would like to participate in saving client state should
+ * add a handler to SaveClientStateEvent on EventBus, and in
+ * that handler, add values to the ClientState if they have changed.
+ * Values that are unchanged since the last update need not be added,
+ * as the values that are put in ClientState will be merged with previous
+ * values.
+ *
+ * The SaveClientStateEvent fires either passively (on a timer) or actively
+ * (on request [though in fact also on a timer, just a shorter one]). Any
+ * component can request that SaveClientStateEvent be fired: simply fire
+ * PushClientStateEvent on the EventBus.
+ */
+ @Inject
+ public ClientStateUpdater(EventBus events,
+ WorkbenchServerOperations server)
+ {
+ super(INITIAL_INTERVAL_MILLIS, PASSIVE_INTERVAL_MILLIS, ACTIVE_INTERVAL_MILLIS);
+ events_ = events;
+ server_ = server;
+
+ events_.addHandler(PushClientStateEvent.TYPE, new PushClientStateHandler()
+ {
+ public void onPushClientState(PushClientStateEvent event)
+ {
+ // Don't allow active pushes until after the initial interval
+ // has elapsed. This lets us avoid storms of requests during
+ // startup.
+ if (lastExecuted_ != null)
+ nudge();
+ }
+ });
+
+ events_.addHandler(LastChanceSaveEvent.TYPE, new LastChanceSaveHandler()
+ {
+ public void onLastChanceSave(LastChanceSaveEvent event)
+ {
+ // We're quitting. Save client state one more time.
+
+ barrierToken_ = event.acquire();
+ nudge();
+ }
+ });
+ }
+
+ @Override
+ protected void performAction(final boolean shouldSchedulePassive)
+ {
+ ClientState state = ClientState.create();
+ try
+ {
+ events_.fireEvent(new SaveClientStateEvent(state));
+ }
+ catch (Exception e)
+ {
+ onComplete(shouldSchedulePassive);
+ return;
+ }
+
+ if (state.isEmpty())
+ {
+ onComplete(shouldSchedulePassive);
+ return;
+ }
+
+ server_.updateClientState(
+ state.getTemporaryData(),
+ state.getPersistentData(),
+ state.getProjectPersistentData(),
+ new ServerRequestCallback<Void>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ onComplete(shouldSchedulePassive);
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ onComplete(shouldSchedulePassive);
+ }
+ });
+ }
+
+ private void onComplete(boolean shouldSchedulePassive)
+ {
+ if (barrierToken_ != null)
+ barrierToken_.release();
+ if (shouldSchedulePassive)
+ schedulePassive();
+ }
+
+ private static final int INITIAL_INTERVAL_MILLIS = 2000;
+ private static final int PASSIVE_INTERVAL_MILLIS = 5000;
+ private static final int ACTIVE_INTERVAL_MILLIS = Desktop.isDesktop()
+ ? 100
+ : 350;
+ private final EventBus events_;
+ private final WorkbenchServerOperations server_;
+ private Token barrierToken_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/FileMRUList.java b/src/gwt/src/org/rstudio/studio/client/workbench/FileMRUList.java
new file mode 100644
index 0000000..67e22ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/FileMRUList.java
@@ -0,0 +1,75 @@
+/*
+ * FileMRUList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+ at Singleton
+public class FileMRUList extends MRUList
+{
+ @Inject
+ public FileMRUList(Commands commands,
+ WorkbenchListManager listManager,
+ final FileTypeRegistry fileTypeRegistry,
+ final FilesServerOperations server)
+ {
+ super(listManager.getFileMruList(),
+ new AppCommand[] {
+ commands.mru0(),
+ commands.mru1(),
+ commands.mru2(),
+ commands.mru3(),
+ commands.mru4(),
+ commands.mru5(),
+ commands.mru6(),
+ commands.mru7(),
+ commands.mru8(),
+ commands.mru9()
+ },
+ commands.clearRecentFiles(),
+ true,
+ new OperationWithInput<String>()
+ {
+ @Override
+ public void execute(final String file)
+ {
+ server.stat(file, new ServerRequestCallback<FileSystemItem>()
+ {
+ @Override
+ public void onResponseReceived(FileSystemItem response)
+ {
+ fileTypeRegistry.editFile(response);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ fileTypeRegistry.editFile(FileSystemItem.createFile(file));
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/MRUList.java b/src/gwt/src/org/rstudio/studio/client/workbench/MRUList.java
new file mode 100644
index 0000000..5282de0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/MRUList.java
@@ -0,0 +1,161 @@
+/*
+ * MRUList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+
+import org.rstudio.core.client.DuplicateHelper;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.AppMenuItem;
+import org.rstudio.core.client.command.CommandHandler;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.workbench.events.ListChangedEvent;
+import org.rstudio.studio.client.workbench.events.ListChangedHandler;
+
+import java.util.*;
+
+public class MRUList
+{
+ public MRUList(WorkbenchList mruList,
+ AppCommand[] mruCmds,
+ AppCommand clearCommand,
+ boolean includeExt,
+ OperationWithInput<String> operation)
+ {
+ clearCommand_ = clearCommand;
+ mruList_ = mruList;
+ mruCmds_ = mruCmds;
+ includeExt_ = includeExt;
+ operation_ = operation;
+
+
+ for (int i = 0; i < mruCmds_.length; i++)
+ bindCommand(i);
+
+ clearCommand_.addHandler(new CommandHandler()
+ {
+ public void onCommand(AppCommand command)
+ {
+ clear();
+ }
+ });
+
+
+ mruList_.addListChangedHandler(new ListChangedHandler() {
+ @Override
+ public void onListChanged(ListChangedEvent event)
+ {
+ mruEntries_.clear();
+ mruEntries_.addAll(event.getList());
+ updateCommands();
+ }
+ });
+ }
+
+ private void bindCommand(final int i)
+ {
+ mruCmds_[i].addHandler(new CommandHandler()
+ {
+ public void onCommand(AppCommand command)
+ {
+ if (i < mruEntries_.size())
+ operation_.execute(mruEntries_.get(i));
+ }
+ });
+ }
+
+ public void add(String entry)
+ {
+ assert entry.indexOf("\n") < 0;
+
+ mruList_.prepend(entry);
+ }
+
+ public void remove(String entry)
+ {
+ mruList_.remove(entry);
+ }
+
+ public void clear()
+ {
+ mruList_.clear();
+ }
+
+ public String getQualifiedLabel(String mruEntry)
+ {
+ // make a copy of the existing mru entries and prepend the specified
+ // entry if it doesn't exist. we need to do this because at startup
+ // the most recently loaded project may not be in the list yet
+ @SuppressWarnings("unchecked")
+ ArrayList<String> mruEntries = (ArrayList<String>)mruEntries_.clone();
+ if (!mruEntries.contains(mruEntry))
+ mruEntries.add(mruEntry);
+
+ // save the index of the entry
+ int index = mruEntries.indexOf(mruEntry);
+
+ // transform paths
+ for (int i=0; i<mruEntries.size(); i++)
+ mruEntries.set(i, transformMruEntryPath(mruEntries.get(i)));
+
+ // generate labels
+ mruEntries = DuplicateHelper.getPathLabels(mruEntries, includeExt_);
+
+ // return the label
+ return mruEntries.get(index);
+ }
+
+ private void updateCommands()
+ {
+ while (mruEntries_.size() > mruCmds_.length)
+ mruEntries_.remove(mruEntries_.size() - 1);
+
+ clearCommand_.setEnabled(mruEntries_.size() > 0);
+
+ // optionally transform paths
+ ArrayList<String> entries = new ArrayList<String>();
+ for (String entry : mruEntries_)
+ entries.add(transformMruEntryPath(entry));
+
+ // generate labels
+ ArrayList<String> labels = DuplicateHelper.getPathLabels(entries,
+ includeExt_);
+
+ for (int i = 0; i < mruCmds_.length; i++)
+ {
+ if (i >= mruEntries_.size())
+ mruCmds_[i].setVisible(false);
+ else
+ {
+ mruCmds_[i].setVisible(true);
+ mruCmds_[i].setMenuLabel(
+ AppMenuItem.escapeMnemonics(labels.get(i)));
+ mruCmds_[i].setDesc(mruEntries_.get(i));
+ }
+ }
+ }
+
+
+ protected String transformMruEntryPath(String entryPath)
+ {
+ return entryPath;
+ }
+
+ private final ArrayList<String> mruEntries_ = new ArrayList<String>();
+ private final AppCommand[] mruCmds_;
+ private final AppCommand clearCommand_;
+ private final WorkbenchList mruList_;
+ private final boolean includeExt_;
+ private final OperationWithInput<String> operation_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/Workbench.java b/src/gwt/src/org/rstudio/studio/client/workbench/Workbench.java
new file mode 100644
index 0000000..212e07f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/Workbench.java
@@ -0,0 +1,406 @@
+/*
+ * Workbench.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import com.google.gwt.core.client.GWT;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.ConsoleDispatcher;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.vcs.AskPassManager;
+import org.rstudio.studio.client.common.vcs.ShowPublicKeyDialog;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.htmlpreview.HTMLPreview;
+import org.rstudio.studio.client.pdfviewer.PDFViewer;
+import org.rstudio.studio.client.server.Server;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.shiny.ShinyApplication;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.*;
+import org.rstudio.studio.client.workbench.model.*;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.choosefile.ChooseFile;
+import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+public class Workbench implements BusyHandler,
+ ShowErrorMessageHandler,
+ ShowWarningBarHandler,
+ BrowseUrlHandler,
+ QuotaStatusHandler,
+ WorkbenchLoadedHandler,
+ WorkbenchMetricsChangedHandler
+{
+ interface Binder extends CommandBinder<Commands, Workbench> {}
+
+ @Inject
+ public Workbench(WorkbenchMainView view,
+ WorkbenchContext workbenchContext,
+ GlobalDisplay globalDisplay,
+ Commands commands,
+ EventBus eventBus,
+ Session session,
+ Provider<UIPrefs> pPrefs,
+ Server server,
+ RemoteFileSystemContext fsContext,
+ FileDialogs fileDialogs,
+ FileTypeRegistry fileTypeRegistry,
+ ConsoleDispatcher consoleDispatcher,
+ Provider<GitState> pGitState,
+ ChooseFile chooseFile, // required to force gin to create
+ AskPassManager askPass, // required to force gin to create
+ PDFViewer pdfViewer, // required to force gin to create
+ HTMLPreview htmlPreview, // required to force gin to create
+ ProfilerPresenter prof, // required to force gin to create
+ ShinyApplication sApp) // required to force gin to create
+ {
+ view_ = view;
+ workbenchContext_ = workbenchContext;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+ eventBus_ = eventBus;
+ session_ = session;
+ pPrefs_ = pPrefs;
+ server_ = server;
+ fsContext_ = fsContext;
+ fileDialogs_ = fileDialogs;
+ fileTypeRegistry_ = fileTypeRegistry;
+ consoleDispatcher_ = consoleDispatcher;
+ pGitState_ = pGitState;
+
+ ((Binder)GWT.create(Binder.class)).bind(commands, this);
+
+ // edit
+ eventBus.addHandler(BusyEvent.TYPE, this);
+ eventBus.addHandler(ShowErrorMessageEvent.TYPE, this);
+ eventBus.addHandler(ShowWarningBarEvent.TYPE, this);
+ eventBus.addHandler(BrowseUrlEvent.TYPE, this);
+ eventBus.addHandler(QuotaStatusEvent.TYPE, this);
+ eventBus.addHandler(WorkbenchLoadedEvent.TYPE, this);
+ eventBus.addHandler(WorkbenchMetricsChangedEvent.TYPE, this);
+
+ // We don't want to send setWorkbenchMetrics more than once per 1/2-second
+ metricsChangedCommand_ = new TimeBufferedCommand(-1, -1, 500)
+ {
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ assert !shouldSchedulePassive;
+
+ server_.setWorkbenchMetrics(lastWorkbenchMetrics_,
+ new VoidServerRequestCallback());
+ }
+ };
+ }
+
+ public WorkbenchMainView getMainView()
+ {
+ return view_ ;
+ }
+
+ public void onWorkbenchLoaded(WorkbenchLoadedEvent event)
+ {
+ server_.initializeForMainWorkbench();
+
+ FileSystemItem defaultDialogDir =
+ session_.getSessionInfo().getActiveProjectDir();
+ if (defaultDialogDir != null)
+ workbenchContext_.setDefaultFileDialogDir(defaultDialogDir);
+
+ // check for init messages
+ checkForInitMessages();
+
+ if (Desktop.isDesktop() &&
+ session_.getSessionInfo().getVcsName().equals(VCSConstants.GIT_ID))
+ {
+ pGitState_.get().addVcsRefreshHandler(new VcsRefreshHandler() {
+
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ FileSystemItem projDir = workbenchContext_.getActiveProjectDir();
+ if (projDir != null)
+ {
+ String title = projDir.getPath();
+ String branch = pGitState_.get().getBranchInfo()
+ .getActiveBranch();
+ if (branch != null)
+ title = title + " - " + branch;
+ Desktop.getFrame().setWindowTitle(title);
+ }
+ }
+ });
+ }
+
+ }
+
+ public void onBusy(BusyEvent event)
+ {
+ }
+
+ public void onShowErrorMessage(ShowErrorMessageEvent event)
+ {
+ ErrorMessage errorMessage = event.getErrorMessage();
+ globalDisplay_.showErrorMessage(errorMessage.getTitle(),
+ errorMessage.getMessage());
+
+ }
+
+ @Override
+ public void onShowWarningBar(ShowWarningBarEvent event)
+ {
+ WarningBarMessage message = event.getMessage();
+ globalDisplay_.showWarningBar(message.isSevere(), message.getMessage());
+ }
+
+ public void onBrowseUrl(BrowseUrlEvent event)
+ {
+ BrowseUrlInfo urlInfo = event.getUrlInfo();
+ NewWindowOptions newWindowOptions = new NewWindowOptions();
+ newWindowOptions.setName(urlInfo.getWindow());
+ globalDisplay_.openWindow(urlInfo.getUrl(), newWindowOptions);
+ }
+
+ public void onWorkbenchMetricsChanged(WorkbenchMetricsChangedEvent event)
+ {
+ lastWorkbenchMetrics_ = event.getWorkbenchMetrics();
+ metricsChangedCommand_.nudge();
+ }
+
+ public void onQuotaStatus(QuotaStatusEvent event)
+ {
+ QuotaStatus quotaStatus = event.getQuotaStatus();
+
+ // always show warning if the user is over quota
+ if (quotaStatus.isOverQuota())
+ {
+ long over = quotaStatus.getUsed() - quotaStatus.getQuota();
+ StringBuilder msg = new StringBuilder();
+ msg.append("You are ");
+ msg.append(StringUtil.formatFileSize(over));
+ msg.append(" over your ");
+ msg.append(StringUtil.formatFileSize(quotaStatus.getQuota()));
+ msg.append(" file storage limit. Please remove files to ");
+ msg.append("continue working.");
+ globalDisplay_.showWarningBar(false, msg.toString());
+ }
+
+ // show a warning if the user is near their quota (but no more
+ // than one time per instantiation of the application)
+ else if (quotaStatus.isNearQuota() && !nearQuotaWarningShown_)
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("You are nearly over your ");
+ msg.append(StringUtil.formatFileSize(quotaStatus.getQuota()));
+ msg.append(" file storage limit.");
+ globalDisplay_.showWarningBar(false, msg.toString());
+
+ nearQuotaWarningShown_ = true;
+ }
+ }
+
+ @Handler
+ public void onSetWorkingDir()
+ {
+ fileDialogs_.chooseFolder(
+ "Choose Working Directory",
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ // set console
+ consoleDispatcher_.executeSetWd(input, true);
+
+ // set files pane
+ eventBus_.fireEvent(new DirectoryNavigateEvent(input));
+
+ indicator.onCompleted();
+ }
+ });
+ }
+
+ @Handler
+ public void onSourceFile()
+ {
+ fileDialogs_.openFile(
+ "Source File",
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input, ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ consoleDispatcher_.executeSourceCommand(
+ input.getPath(),
+ fileTypeRegistry_.getTextTypeForFile(input),
+ pPrefs_.get().defaultEncoding().getValue(),
+ false,
+ false,
+ true,
+ false);
+
+ commands_.activateConsole().execute();
+ }
+ });
+ }
+
+ @Handler
+ public void onVersionControlShowRsaKey()
+ {
+ final ProgressIndicator indicator = new GlobalProgressDelayer(
+ globalDisplay_, 500, "Reading RSA public key...").getIndicator();
+
+ // compute path to public key
+ String sshDir = session_.getSessionInfo().getDefaultSSHKeyDir();
+ final String keyPath = FileSystemItem.createDir(sshDir).completePath(
+ "id_rsa.pub");
+
+ // read it
+ server_.gitSshPublicKey(keyPath, new ServerRequestCallback<String> () {
+
+ @Override
+ public void onResponseReceived(String publicKeyContents)
+ {
+ indicator.onCompleted();
+
+ new ShowPublicKeyDialog("RSA Public Key",
+ publicKeyContents).showModal();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ String msg = "Error attempting to read key '" + keyPath + "' (" +
+ error.getUserMessage() + ")";
+ indicator.onError(msg);
+ }
+ });
+ }
+
+ @Handler
+ public void onShowShellDialog()
+ {
+ if (Desktop.isDesktop())
+ {
+ server_.getTerminalOptions(new SimpleRequestCallback<TerminalOptions>()
+ {
+ @Override
+ public void onResponseReceived(TerminalOptions options)
+ {
+ Desktop.getFrame().openTerminal(options.getTerminalPath(),
+ options.getWorkingDirectory(),
+ options.getExtraPathEntries());
+ }
+ });
+ }
+ else
+ {
+ final ProgressIndicator indicator = new GlobalProgressDelayer(
+ globalDisplay_, 500, "Starting shell...").getIndicator();
+
+ server_.startShellDialog(new ServerRequestCallback<ConsoleProcess>() {
+
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ indicator.onCompleted();
+ new ConsoleProgressDialog(proc, server_).showModal();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+ }
+
+ @Handler
+ public void onToggleFullScreen()
+ {
+ if (Desktop.isDesktop() && Desktop.getFrame().supportsFullscreenMode())
+ Desktop.getFrame().toggleFullscreenMode();
+ }
+
+ private void checkForInitMessages()
+ {
+ server_.getInitMessages(new ServerRequestCallback<String>() {
+ @Override
+ public void onResponseReceived(String message)
+ {
+ if (message != null)
+ globalDisplay_.showWarningBar(false, message);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // ignore
+ }
+ });
+ }
+
+
+ private final Server server_;
+ private final EventBus eventBus_;
+ private final Session session_;
+ private final Provider<UIPrefs> pPrefs_;
+ private final WorkbenchMainView view_;
+ private final GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+ private final RemoteFileSystemContext fsContext_;
+ private final FileDialogs fileDialogs_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final WorkbenchContext workbenchContext_;
+ private final ConsoleDispatcher consoleDispatcher_;
+ private final Provider<GitState> pGitState_;
+ private final TimeBufferedCommand metricsChangedCommand_;
+ private WorkbenchMetrics lastWorkbenchMetrics_;
+ private boolean nearQuotaWarningShown_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchContext.java
new file mode 100644
index 0000000..691a068
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchContext.java
@@ -0,0 +1,187 @@
+/*
+ * WorkbenchContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.workbench.events.BusyEvent;
+import org.rstudio.studio.client.workbench.events.BusyHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedEvent;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedHandler;
+
+import com.google.gwt.user.client.Timer;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class WorkbenchContext
+{
+
+ @Inject
+ public WorkbenchContext(Session session, EventBus eventBus)
+ {
+ session_ = session;
+
+ // track current working dir
+ currentWorkingDir_ = FileSystemItem.home();
+ defaultFileDialogDir_ = FileSystemItem.home();
+ eventBus.addHandler(WorkingDirChangedEvent.TYPE,
+ new WorkingDirChangedHandler() {
+ @Override
+ public void onWorkingDirChanged(WorkingDirChangedEvent event)
+ {
+ currentWorkingDir_ = FileSystemItem.createDir(event.getPath());
+ defaultFileDialogDir_ = FileSystemItem.createDir(event.getPath());;
+ }
+ });
+
+ eventBus.addHandler(BusyEvent.TYPE, new BusyHandler() {
+ @Override
+ public void onBusy(BusyEvent event)
+ {
+ isServerBusy_ = event.isBusy();
+
+ if (Desktop.isDesktop())
+ Desktop.getFrame().setBusy(isServerBusy_);
+ }
+ });
+
+ eventBus.addHandler(RestartStatusEvent.TYPE,
+ new RestartStatusEvent.Handler()
+ {
+ @Override
+ public void onRestartStatus(RestartStatusEvent event)
+ {
+ if (event.getStatus() == RestartStatusEvent.RESTART_INITIATED)
+ isRestartInProgress_ = true;
+ else if (event.getStatus() == RestartStatusEvent.RESTART_COMPLETED)
+ {
+ // clear the restart in progress event after a delay
+ // (it basically just controls whether errors are displayed
+ // from get_environment_state, list_packages, etc.). we've
+ // seen issues where the flag is cleared too soon -- this
+ // is likely an underlying logic problem in sendPing, but
+ // we don't want to make a change to that late in the v0.98
+ // cycle so instead we just delay the setting of the flag
+ // finding a better solution is tracked in bug #3651
+ new Timer() {
+ @Override
+ public void run()
+ {
+ isRestartInProgress_ = false;
+ }
+ }.schedule(500);
+ }
+ }
+ });
+ }
+
+
+
+ public FileSystemItem getCurrentWorkingDir()
+ {
+ return currentWorkingDir_;
+ }
+
+ public FileSystemItem getDefaultFileDialogDir()
+ {
+ if (defaultFileDialogDir_ != null)
+ return defaultFileDialogDir_;
+ else
+ return getCurrentWorkingDir();
+ }
+
+ public void setDefaultFileDialogDir(FileSystemItem dir)
+ {
+ defaultFileDialogDir_ = dir;
+ }
+
+ // NOTE: mirrors behavior of rEnvironmentDir in SessionMain.cpp
+ public String getREnvironmentPath()
+ {
+ SessionInfo sessionInfo = session_.getSessionInfo();
+ if (sessionInfo != null)
+ {
+ FileSystemItem rEnvDir = null;
+
+ if (getActiveProjectDir() != null)
+ {
+ rEnvDir = getActiveProjectDir();
+ }
+ if (sessionInfo.getMode().equals(SessionInfo.DESKTOP_MODE))
+ {
+ rEnvDir = currentWorkingDir_;
+ }
+ else
+ {
+ rEnvDir = FileSystemItem.createDir(
+ sessionInfo.getInitialWorkingDir());
+ }
+ return rEnvDir.completePath(".RData");
+ }
+ else
+ {
+ return FileSystemItem.home().completePath(".RData");
+ }
+ }
+
+ public String getActiveProjectFile()
+ {
+ return session_.getSessionInfo().getActiveProjectFile();
+ }
+
+ public FileSystemItem getActiveProjectDir()
+ {
+ if (activeProjectDir_ == null)
+ {
+ SessionInfo sessionInfo = session_.getSessionInfo();
+ if (sessionInfo != null &&
+ sessionInfo.getActiveProjectFile() != null)
+ {
+ activeProjectDir_ = FileSystemItem.createFile(
+ sessionInfo.getActiveProjectFile()).getParentPath();
+ }
+ }
+ return activeProjectDir_;
+ }
+
+ public boolean isProjectActive()
+ {
+ SessionInfo sessionInfo = session_.getSessionInfo();
+ return sessionInfo != null && sessionInfo.getActiveProjectFile() != null;
+ }
+
+ public boolean isServerBusy()
+ {
+ return isServerBusy_;
+ }
+
+ public boolean isRestartInProgress()
+ {
+ return isRestartInProgress_;
+ }
+
+ private boolean isServerBusy_ = false;
+ private boolean isRestartInProgress_ = false;
+ private FileSystemItem currentWorkingDir_ = FileSystemItem.home();
+ private FileSystemItem defaultFileDialogDir_ = FileSystemItem.home();
+ private FileSystemItem activeProjectDir_ = null;
+ private Session session_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchList.java b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchList.java
new file mode 100644
index 0000000..3e1c011
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchList.java
@@ -0,0 +1,39 @@
+/*
+ * WorkbenchList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.workbench.events.ListChangedHandler;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+/*
+ * Interface to workbench lists. The contract is that the mutating functions
+ * call the server and then an updated copy of the list is (eventually)
+ * returned via the ListChangedHandler
+ */
+public interface WorkbenchList
+{
+ // mutating operations
+ void setContents(ArrayList<String> list);
+ void append(String item);
+ void prepend(String item);
+ void remove(String item);
+ void clear();
+
+ // change handler
+ HandlerRegistration addListChangedHandler(ListChangedHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchListManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchListManager.java
new file mode 100644
index 0000000..9ea9226
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchListManager.java
@@ -0,0 +1,181 @@
+/*
+ * WorkbenchListManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.events.ListChangedEvent;
+import org.rstudio.studio.client.workbench.events.ListChangedHandler;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.WorkbenchLists;
+import org.rstudio.studio.client.workbench.model.WorkbenchListsServerOperations;
+
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+
+
+public class WorkbenchListManager
+{
+
+ @Inject
+ public WorkbenchListManager(EventBus events,
+ Session session,
+ WorkbenchListsServerOperations server)
+ {
+ session_ = session;
+ server_ = server;
+
+ listContexts_.put(FILE_MRU, new ListContext(FILE_MRU));
+ listContexts_.put(PROJECT_MRU, new ListContext(PROJECT_MRU));
+ listContexts_.put(HELP_HISTORY, new ListContext(HELP_HISTORY));
+ listContexts_.put(USER_DICTIONARY, new ListContext(USER_DICTIONARY));
+
+ events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ WorkbenchLists lists = session_.getSessionInfo().getLists();
+ updateList(FILE_MRU, lists);
+ updateList(PROJECT_MRU, lists);
+ updateList(HELP_HISTORY, lists);
+ updateList(USER_DICTIONARY, lists);
+ }
+ });
+
+ events.addHandler(ListChangedEvent.TYPE, new ListChangedHandler() {
+ @Override
+ public void onListChanged(ListChangedEvent event)
+ {
+ updateList(event.getName(), event.getList());
+ }
+ });
+ }
+
+ public WorkbenchList getFileMruList()
+ {
+ return listContexts_.get(FILE_MRU);
+ }
+
+
+ public WorkbenchList getProjectMruList()
+ {
+ return listContexts_.get(PROJECT_MRU);
+ }
+
+ public WorkbenchList getHelpHistoryList()
+ {
+ return listContexts_.get(HELP_HISTORY);
+ }
+
+ public WorkbenchList getUserDictionaryList()
+ {
+ return listContexts_.get(USER_DICTIONARY);
+ }
+
+
+ private void updateList(String name, WorkbenchLists lists)
+ {
+ updateList(name, lists.getList(name));
+ }
+
+ private void updateList(String name, ArrayList<String> list)
+ {
+ listContexts_.get(name).setList(list);
+ }
+
+ private class ListContext implements WorkbenchList
+ {
+ public ListContext(String name)
+ {
+ name_ = name;
+ }
+
+ public void setList(ArrayList<String> list)
+ {
+ list_ = list;
+ handlers_.fireEvent(new ListChangedEvent(name_, list_));
+ }
+
+ @Override
+ public void setContents(ArrayList<String> list)
+ {
+ server_.listSetContents(name_, list, new ListRequestCallback());
+ }
+
+ @Override
+ public void append(String item)
+ {
+ server_.listAppendItem(name_, item, new ListRequestCallback());
+ }
+
+ @Override
+ public void prepend(String item)
+ {
+ server_.listPrependItem(name_, item, new ListRequestCallback());
+ }
+
+ @Override
+ public void remove(String item)
+ {
+ server_.listRemoveItem(name_, item, new ListRequestCallback());
+ }
+
+ @Override
+ public void clear()
+ {
+ server_.listClear(name_, new ListRequestCallback());
+ }
+
+ @Override
+ public HandlerRegistration addListChangedHandler(
+ ListChangedHandler handler)
+ {
+ HandlerRegistration hreg = handlers_.addHandler(ListChangedEvent.TYPE,
+ handler);
+
+ if (list_ != null)
+ handler.onListChanged(new ListChangedEvent(name_, list_));
+
+ return hreg;
+ }
+
+ // for now we have a no-op stub for server request callbacks
+ private class ListRequestCallback extends VoidServerRequestCallback
+ {
+ }
+
+ private final String name_;
+ private ArrayList<String> list_ = null;
+ private final HandlerManager handlers_ = new HandlerManager(this);
+
+ }
+
+ private HashMap<String,ListContext> listContexts_ =
+ new HashMap<String,ListContext>();
+
+ private final Session session_;
+ private final WorkbenchListsServerOperations server_;
+
+ private static final String FILE_MRU = "file_mru";
+ private static final String PROJECT_MRU = "project_mru";
+ private static final String HELP_HISTORY = "help_history_links";
+ private static final String USER_DICTIONARY = "user_dictionary";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchMainView.java b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchMainView.java
new file mode 100644
index 0000000..aea7b16
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchMainView.java
@@ -0,0 +1,22 @@
+/*
+ * WorkbenchMainView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface WorkbenchMainView extends IsWidget
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchView.java b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchView.java
new file mode 100644
index 0000000..60a30a4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/WorkbenchView.java
@@ -0,0 +1,27 @@
+/*
+ * WorkbenchView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench;
+
+
+public interface WorkbenchView
+{
+ void bringToFront();
+ void maximize();
+ void ensureHeight(int height);
+ void setProgress(boolean showProgress);
+ void onBeforeUnselected();
+ void onBeforeSelected();
+ void onSelected();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearch.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearch.java
new file mode 100644
index 0000000..ef6fa0d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearch.java
@@ -0,0 +1,222 @@
+/*
+ * CodeSearch.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.SearchDisplay;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class CodeSearch
+{
+ public interface Observer
+ {
+ String getCueText();
+ void onCompleted();
+ void onCancel();
+ }
+
+
+ public interface Display
+ {
+ SearchDisplay getSearchDisplay();
+
+ void setCueText(String text);
+
+ SuggestBox.DefaultSuggestionDisplay getSuggestionDisplay();
+
+ CodeSearchOracle getSearchOracle();
+
+ }
+
+ @Inject
+ public CodeSearch(Display display,
+ final FileTypeRegistry fileTypeRegistry,
+ final EventBus eventBus)
+ {
+ display_ = display;
+
+ SearchDisplay searchDisplay = display_.getSearchDisplay();
+ searchDisplay.setAutoSelectEnabled(true);
+
+ searchDisplay.addSelectionHandler(new SelectionHandler<Suggestion>() {
+
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event)
+ {
+ // map back to a code search result
+ CodeNavigationTarget target =
+ display_.getSearchOracle().navigationTargetFromSuggestion(
+ event.getSelectedItem());
+
+ // create full file path and position
+ String srcFile = target.getFile();
+ final FileSystemItem srcItem = FileSystemItem.createFile(srcFile);
+ final FilePosition pos = target.getPosition();
+
+ // fire editing event (delayed so the Enter keystroke
+ // doesn't get routed into the source editor)
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ display_.getSearchDisplay().clear();
+ display_.getSearchOracle().clear();
+
+ if (observer_ != null)
+ observer_.onCompleted();
+
+ fileTypeRegistry.editFile(srcItem, pos);
+ }
+ });
+ }
+ });
+
+ searchDisplay.addCloseHandler(new CloseHandler<SearchDisplay>() {
+ @Override
+ public void onClose(CloseEvent<SearchDisplay> event)
+ {
+ display_.getSearchDisplay().clear();
+
+ if (observer_ != null)
+ observer_.onCancel();
+ }
+ });
+
+ // various conditions invalidate the search oracle's cache
+
+ searchDisplay.addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event)
+ {
+ display_.getSearchOracle().clear();
+ }
+ });
+
+
+ searchDisplay.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event)
+ {
+ display_.getSearchOracle().clear();
+ }
+ });
+
+ eventBusHandlers_.add(
+ eventBus.addHandler(FileChangeEvent.TYPE, new FileChangeHandler() {
+ @Override
+ public void onFileChange(FileChangeEvent event)
+ {
+ // if this was an R file then invalide the cache
+ CodeSearchOracle oracle = display_.getSearchOracle();
+ if (oracle.hasCachedResults())
+ {
+ FileSystemItem fsi = event.getFileChange().getFile();
+ if (fsi.getExtension().toLowerCase().equals(".r"))
+ oracle.clear();
+ }
+ }
+ }));
+
+ searchDisplay.addValueChangeHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ boolean hasSearch = event.getValue().length() != 0;
+ if (!hasSearch)
+ {
+ display_.getSearchOracle().invalidateSearches();
+ display_.getSuggestionDisplay().hideSuggestions();
+ }
+ }
+ });
+
+ searchDisplay.addKeyDownHandler(new KeyDownHandler() {
+
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // eat key-up if the suggestions are showing (since the
+ // suggestions menu is taking these and if we take it
+ // the cursor will go to the beginning of the selection)
+ if (display_.getSuggestionDisplay().isSuggestionListShowing() &&
+ (event.getNativeKeyCode() == KeyCodes.KEY_UP))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ });
+ }
+
+ public Widget getSearchWidget()
+ {
+ return (Widget) display_.getSearchDisplay();
+ }
+
+ public void setObserver(Observer observer)
+ {
+ observer_ = observer;
+
+ if (observer_ != null)
+ {
+ String cueText = observer_.getCueText();
+ if (cueText != null)
+ display_.setCueText(cueText);
+ }
+ }
+
+ // notify the CodeSearch that the search is completed so that it
+ // can un-hook from EventBus events
+ public void detachEventBusHandlers()
+ {
+ for (int i=0; i<eventBusHandlers_.size(); i++)
+ eventBusHandlers_.get(i).removeHandler();
+ eventBusHandlers_.clear();
+ }
+
+ private final Display display_;
+ private Observer observer_ = null;
+ private ArrayList<HandlerRegistration> eventBusHandlers_ =
+ new ArrayList<HandlerRegistration>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchOracle.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchOracle.java
new file mode 100644
index 0000000..220bf5d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchOracle.java
@@ -0,0 +1,301 @@
+/*
+ * CodeSearchOracle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.DuplicateHelper;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchResults;
+import org.rstudio.studio.client.workbench.codesearch.model.RFileItem;
+import org.rstudio.studio.client.workbench.codesearch.model.RSourceItem;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchServerOperations;
+
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.inject.Inject;
+
+public class CodeSearchOracle extends SuggestOracle
+{
+ @Inject
+ public CodeSearchOracle(CodeSearchServerOperations server,
+ WorkbenchContext workbenchContext)
+ {
+ server_ = server;
+ workbenchContext_ = workbenchContext;
+ }
+
+
+ @Override
+ public void requestSuggestions(final Request request,
+ final Callback callback)
+ {
+ // invalidate any outstanding search
+ searchInvalidation_.invalidate();
+
+ // first see if we can serve the request from the cache
+ for (int i=resultCache_.size() - 1; i >= 0; i--)
+ {
+ // get the previous result
+ SearchResult res = resultCache_.get(i);
+
+ // exact match of previous query
+ if (request.getQuery().equals(res.getQuery()))
+ {
+ callback.onSuggestionsReady(request,
+ new Response(res.getSuggestions()));
+ return;
+ }
+
+ // if this query is a further refinement of a non-overflowed
+ // previous query then satisfy it by filtering the previous results
+ if (!res.getMoreAvailable() &&
+ request.getQuery().startsWith(res.getQuery()))
+ {
+ Pattern pattern = null;
+ String queryLower = request.getQuery().toLowerCase();
+ if (queryLower.indexOf('*') != -1)
+ pattern = patternForTerm(queryLower);
+
+ ArrayList<CodeSearchSuggestion> suggestions =
+ new ArrayList<CodeSearchSuggestion>();
+ for (int s=0; s<res.getSuggestions().size(); s++)
+ {
+ CodeSearchSuggestion sugg = res.getSuggestions().get(s);
+
+ String name = sugg.getMatchedString().toLowerCase();
+ if (pattern != null)
+ {
+ Match match = pattern.match(name, 0);
+ if (match != null && match.getIndex() == 0)
+ suggestions.add(sugg);
+ }
+ else
+ {
+ if (name.startsWith(queryLower))
+ suggestions.add(sugg);
+ }
+ }
+
+
+
+ // process and cache suggestions. note that this adds an item to
+ // the end of the resultCache_ (which we are currently iterating
+ // over) no biggie because we are about to return from the loop
+ suggestions = processSuggestions(request, suggestions, false);
+
+ // return suggestions
+ callback.onSuggestionsReady(request, new Response(suggestions));
+
+ return;
+ }
+ }
+
+ // failed to short-circuit via the cache, hit the server
+ codeSearch_.enqueRequest(request, callback);
+ }
+
+ public CodeNavigationTarget navigationTargetFromSuggestion(Suggestion sugg)
+ {
+ return ((CodeSearchSuggestion)sugg).getNavigationTarget();
+ }
+
+ public void invalidateSearches()
+ {
+ searchInvalidation_.invalidate();
+ }
+
+ public boolean hasCachedResults()
+ {
+ return !resultCache_.isEmpty();
+ }
+
+ public void clear()
+ {
+ resultCache_.clear();
+ }
+
+ @Override
+ public boolean isDisplayStringHTML()
+ {
+ return true;
+ }
+
+ private Pattern patternForTerm(String term)
+ {
+ // split the term on *
+ StringBuilder regex = new StringBuilder();
+ String[] components = term.split("\\*", -1);
+ for (int i=0; i<components.length; i++)
+ {
+ if (i > 0)
+ regex.append(".*");
+ regex.append(Pattern.escape(components[i]));
+ }
+ return Pattern.create(regex.toString());
+ }
+
+ private class CodeSearchCommand extends TimeBufferedCommand
+ {
+ public CodeSearchCommand()
+ {
+ super(300);
+ }
+
+ public void enqueRequest(Request request, Callback callback)
+ {
+ request_ = request;
+ callback_ = callback;
+ invalidationToken_ = searchInvalidation_.getInvalidationToken();
+ nudge();
+ }
+
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ // failed to short-circuit via the cache, hit the server
+ server_.searchCode(
+ request_.getQuery(),
+ request_.getLimit(),
+ new SimpleRequestCallback<CodeSearchResults>() {
+
+ @Override
+ public void onResponseReceived(CodeSearchResults response)
+ {
+ ArrayList<CodeSearchSuggestion> suggestions =
+ new ArrayList<CodeSearchSuggestion>();
+
+ // file results
+ ArrayList<RFileItem> fileResults =
+ response.getRFileItems().toArrayList();
+ for (int i = 0; i<fileResults.size(); i++)
+ suggestions.add(new CodeSearchSuggestion(fileResults.get(i)));
+
+
+ // src results
+ FileSystemItem context = workbenchContext_.getActiveProjectDir();
+ ArrayList<RSourceItem> srcResults =
+ response.getRSourceItems().toArrayList();
+ for (int i = 0; i<srcResults.size(); i++)
+ {
+ suggestions.add(
+ new CodeSearchSuggestion(srcResults.get(i), context));
+ }
+
+ // process suggestions (disambiguate paths & cache)
+ suggestions = processSuggestions(request_,
+ suggestions,
+ response.getMoreAvailable());
+
+ // return suggestions
+ if (!invalidationToken_.isInvalid())
+ {
+ callback_.onSuggestionsReady(request_,
+ new Response(suggestions));
+ }
+ }
+ });
+
+ }
+
+ private Request request_;
+ private Callback callback_;
+ private Invalidation.Token invalidationToken_;
+ };
+
+
+ private ArrayList<CodeSearchSuggestion> processSuggestions(
+ Request request,
+ ArrayList<CodeSearchSuggestion> suggestions,
+ boolean moreAvailable)
+ {
+ // get file paths for file targets (which are always at the beginning)
+ ArrayList<String> filePaths = new ArrayList<String>();
+ for(CodeSearchSuggestion suggestion : suggestions)
+ {
+ if (!suggestion.isFileTarget())
+ break;
+
+ filePaths.add(suggestion.getNavigationTarget().getFile());
+ }
+
+ // disambiguate them
+ ArrayList<String> displayLabels = DuplicateHelper.getPathLabels(filePaths,
+ true);
+ ArrayList<CodeSearchSuggestion> newSuggestions =
+ new ArrayList<CodeSearchSuggestion>(suggestions);
+ for (int i=0; i<displayLabels.size(); i++)
+ newSuggestions.get(i).setFileDisplayString(filePaths.get(i),
+ displayLabels.get(i));
+
+ // cache the suggestions (up to 15 active result sets cached)
+ // NOTE: the cache is cleared on gain focus, lost focus, and
+ // the search term reverting back to empty)
+ if (resultCache_.size() > 15)
+ resultCache_.remove(0);
+ resultCache_.add(new SearchResult(request.getQuery(),
+ newSuggestions,
+ moreAvailable));
+
+ return newSuggestions;
+ }
+
+ private final Invalidation searchInvalidation_ = new Invalidation();
+
+ private final CodeSearchServerOperations server_ ;
+ private final WorkbenchContext workbenchContext_;
+ private final CodeSearchCommand codeSearch_ = new CodeSearchCommand();
+
+ private final ArrayList<SearchResult> resultCache_ =
+ new ArrayList<SearchResult>();
+
+ private class SearchResult
+ {
+ public SearchResult(String query,
+ ArrayList<CodeSearchSuggestion> suggestions,
+ boolean moreAvailable)
+ {
+ query_ = query;
+ suggestions_ = suggestions;
+ moveAvailable_ = moreAvailable;
+ }
+
+ public String getQuery()
+ {
+ return query_;
+ }
+
+ public ArrayList<CodeSearchSuggestion> getSuggestions()
+ {
+ return suggestions_;
+ }
+
+ public boolean getMoreAvailable()
+ {
+ return moveAvailable_;
+ }
+
+ private final String query_;
+ private final ArrayList<CodeSearchSuggestion> suggestions_;
+ private final boolean moveAvailable_;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchSuggestion.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchSuggestion.java
new file mode 100644
index 0000000..941254c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/CodeSearchSuggestion.java
@@ -0,0 +1,172 @@
+/*
+ * CodeSearchSuggestion.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.SafeHtmlUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.codesearch.model.RFileItem;
+import org.rstudio.studio.client.workbench.codesearch.model.RS4MethodParam;
+import org.rstudio.studio.client.workbench.codesearch.model.RSourceItem;
+import org.rstudio.studio.client.workbench.codesearch.ui.CodeSearchResources;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+
+class CodeSearchSuggestion implements Suggestion
+{
+ public CodeSearchSuggestion(RFileItem fileItem)
+ {
+ isFileTarget_ = true;
+ navigationTarget_ = new CodeNavigationTarget(fileItem.getPath());
+ matchedString_ = fileItem.getFilename();
+
+ // compute display string
+ ImageResource image =
+ fileTypeRegistry_.getIconForFilename(fileItem.getFilename());
+
+ displayString_ = createDisplayString(image,
+ RES.styles().fileImage(),
+ fileItem.getFilename(),
+ null,
+ null);
+ }
+
+ public CodeSearchSuggestion(RSourceItem sourceItem, FileSystemItem fsContext)
+ {
+ isFileTarget_ = false;
+ navigationTarget_ = sourceItem.toCodeNavigationTarget();
+ matchedString_ = sourceItem.getFunctionName();
+
+ // compute display string
+ ImageResource image = StandardIcons.INSTANCE.function();
+ if (sourceItem.getType() == RSourceItem.METHOD)
+ image = RES.method();
+ else if (sourceItem.getType() == RSourceItem.CLASS)
+ image = RES.cls();
+
+ // adjust context for parent context
+ String context = sourceItem.getContext();
+ if (fsContext != null)
+ {
+ String fsContextPath = fsContext.getPath();
+ if (!fsContextPath.endsWith("/"))
+ fsContextPath = fsContextPath + "/";
+
+ if (context.startsWith(fsContextPath) &&
+ (context.length() > fsContextPath.length()))
+ {
+ context = context.substring(fsContextPath.length());
+ }
+ }
+
+ // create display string
+ displayString_ = createDisplayString(image,
+ RES.styles().itemImage(),
+ sourceItem.getFunctionName(),
+ sourceItem.getSignature(),
+ context);
+ }
+
+ public String getMatchedString()
+ {
+ return matchedString_;
+ }
+
+ @Override
+ public String getDisplayString()
+ {
+ return displayString_;
+ }
+
+ public void setFileDisplayString(String file, String displayString)
+ {
+ // compute display string
+ ImageResource image = fileTypeRegistry_.getIconForFilename(file);
+ displayString_ = createDisplayString(image,
+ RES.styles().fileImage(),
+ displayString,
+ null,
+ null);
+
+ }
+
+ @Override
+ public String getReplacementString()
+ {
+ return "" ;
+ }
+
+ public CodeNavigationTarget getNavigationTarget()
+ {
+ return navigationTarget_;
+ }
+
+ public boolean isFileTarget()
+ {
+ return isFileTarget_;
+ }
+
+ private String createDisplayString(ImageResource image,
+ String imageStyle,
+ String name,
+ JsArray<RS4MethodParam> signature,
+ String context)
+ {
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ SafeHtmlUtil.appendImage(sb, imageStyle, image);
+ SafeHtmlUtil.appendSpan(sb, RES.styles().itemName(), name);
+
+ // check for signature
+ if (signature != null && signature.length() > 0)
+ {
+ StringBuilder sigBuilder = new StringBuilder();
+ sigBuilder.append("{");
+ for (int i=0; i<signature.length(); i++)
+ {
+ if (i>0)
+ sigBuilder.append(", ");
+ sigBuilder.append(signature.get(i).getType());
+ }
+ sigBuilder.append("}");
+ SafeHtmlUtil.appendSpan(sb,
+ RES.styles().itemName(),
+ sigBuilder.toString());
+ }
+
+ // check for context
+ if (context != null)
+ {
+ SafeHtmlUtil.appendSpan(sb,
+ RES.styles().itemContext(),
+ "(" + context + ")");
+ }
+ return sb.toSafeHtml().asString();
+ }
+
+
+ private final boolean isFileTarget_;
+ private final CodeNavigationTarget navigationTarget_ ;
+ private final String matchedString_;
+ private String displayString_;
+ private static final FileTypeRegistry fileTypeRegistry_ =
+ RStudioGinjector.INSTANCE.getFileTypeRegistry();
+ private static final CodeSearchResources RES = CodeSearchResources.INSTANCE;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchResults.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchResults.java
new file mode 100644
index 0000000..03841fc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchResults.java
@@ -0,0 +1,40 @@
+/*
+ * CodeSearchResults.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CodeSearchResults extends JavaScriptObject
+{
+ protected CodeSearchResults()
+ {
+
+ }
+
+ public final native RpcObjectList<RFileItem> getRFileItems() /*-{
+ return this.file_items;
+ }-*/;
+
+ public final native RpcObjectList<RSourceItem> getRSourceItems() /*-{
+ return this.source_items;
+ }-*/;
+
+ public final native boolean getMoreAvailable() /*-{
+ return this.more_available;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchServerOperations.java
new file mode 100644
index 0000000..99a5955
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/CodeSearchServerOperations.java
@@ -0,0 +1,94 @@
+/*
+ * CodeSearchServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface CodeSearchServerOperations
+{
+ /*
+ * Search all currently managed code (project or open source docs
+ * for a file or function matching the specified term)
+ */
+ void searchCode(
+ String term,
+ int maxResults,
+ ServerRequestCallback<CodeSearchResults> requestCallback);
+
+ /*
+ * Get the definition of the specified function (if known).
+ * We pass a line and pos rather than a function name because that is
+ * the level of fidelity we have in the editor -- we use R on the server
+ * to sort out the name of the token to lookup. Fields in the
+ * FunctionDefinition will be as follows on return
+ * - getFunctionName -- the parsed token from line/pos or
+ * null if no token could be parsed
+ *
+ * - getFile/getPosition -- file location if one of the files we
+ * are managing has the definition, else null
+ *
+ * - getSearchPathFunctionDefinition
+ * -- name/namespace/code if the function was
+ * found on the search path, else null. this
+ * is mutually exclusive with getFile
+ */
+ void getFunctionDefinition(
+ String line,
+ int pos,
+ ServerRequestCallback<FunctionDefinition> requestCallback);
+
+ /*
+ * Find the passed function in the search path (searching starting
+ * at the environment specified by fromWhere, or the global env
+ * if fromWhere is null). SearchPathFunctionDefinition will be as
+ * follows on return:
+ * - getName -- the parsed token from line/pos or null if no
+ * token could be parsed
+ *
+ * - getNamespace -- if the name was found in a namespace on the
+ * search path then this is the namespace, else null
+ *
+ * - getCode -- printed variation of the function if it was
+ * found within a namespace
+ */
+ void findFunctionInSearchPath(
+ String line,
+ int pos,
+ String fromWhere,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback);
+
+ /*
+ * Get the function identified by the following name/namespace.
+ * SearchPathFunctionDefinition will be as follows on return:
+ * - getName -- the passed name
+ *
+ * - getNamespace -- the passed namespace
+ *
+ * - getCode -- printed variation of the function (or error
+ * message if it wasn't found
+ */
+ void getSearchPathFunctionDefinition(
+ String name,
+ String namespace,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback);
+
+ /*
+ * Get a function which is known to be an S3 or S4 method. returns null
+ * if no such method could be located
+ */
+ void getMethodDefinition(
+ String name,
+ ServerRequestCallback<SearchPathFunctionDefinition> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/FunctionDefinition.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/FunctionDefinition.java
new file mode 100644
index 0000000..9adb768
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/FunctionDefinition.java
@@ -0,0 +1,57 @@
+/*
+ * FunctionDefinition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FunctionDefinition extends JavaScriptObject
+{
+ protected FunctionDefinition()
+ {
+
+ }
+
+ /*
+ * Name of function -- can be null if no function token could be
+ * ascertained from the line and pos passed to the server
+ */
+ public final native String getFunctionName() /*-{
+ return this.function_name;
+ }-*/;
+
+ /*
+ * File position where the definition of the function is. Can be null
+ * if no indexed file defining the function was found
+ */
+ public final native FileSystemItem getFile() /*-{
+ return this.file;
+ }-*/;
+ public final native FilePosition getPosition()/*-{
+ return this.position;
+ }-*/;
+
+ /*
+ * A definition of the function from the current search path. Can be null
+ * if the function was found in an indexed file
+ */
+ public final native SearchPathFunctionDefinition
+ getSearchPathFunctionDefinition() /*-{
+ return this.search_path_definition;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RFileItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RFileItem.java
new file mode 100644
index 0000000..5739c1f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RFileItem.java
@@ -0,0 +1,36 @@
+/*
+ * RFileItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class RFileItem extends JavaScriptObject
+{
+ protected RFileItem()
+ {
+ }
+
+
+ public final native String getFilename() /*-{
+ return this.filename;
+ }-*/;
+
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RS4MethodParam.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RS4MethodParam.java
new file mode 100644
index 0000000..4c3bdf6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RS4MethodParam.java
@@ -0,0 +1,36 @@
+/*
+ * RS4MethodParam.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class RS4MethodParam extends JavaScriptObject
+{
+ protected RS4MethodParam()
+ {
+ }
+
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+
+ public final native String getType() /*-{
+ return this.type;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RSourceItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RSourceItem.java
new file mode 100644
index 0000000..2ec0812
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/RSourceItem.java
@@ -0,0 +1,65 @@
+/*
+ * RSourceItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.FilePosition;
+
+
+public class RSourceItem extends JavaScriptObject
+{
+ protected RSourceItem()
+ {
+ }
+
+ public static final int NONE = 0;
+ public static final int FUNCTION = 1;
+ public static final int METHOD = 2;
+ public static final int CLASS = 3;
+
+ public final native int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native String getFunctionName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native JsArray<RS4MethodParam> getSignature() /*-{
+ return this.signature;
+ }-*/;
+
+ // project-relative filename
+ public final native String getContext() /*-{
+ return this.context;
+ }-*/;
+
+ public final native int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public final native int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public final CodeNavigationTarget toCodeNavigationTarget()
+ {
+ return new CodeNavigationTarget(getContext(),
+ FilePosition.create(getLine(),
+ getColumn()));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/SearchPathFunctionDefinition.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/SearchPathFunctionDefinition.java
new file mode 100644
index 0000000..ded7312
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/model/SearchPathFunctionDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * SearchPathFunctionDefinition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+
+public class SearchPathFunctionDefinition extends JavaScriptObject
+{
+ protected SearchPathFunctionDefinition()
+ {
+ }
+
+ public final static native SearchPathFunctionDefinition create(String name,
+ String namespace, String code, boolean debugCode)
+ /*-{
+ return { name: name,
+ namespace: namespace,
+ code: code,
+ methods: [],
+ from_src_attrib: true,
+ active_debug_code: debugCode };
+ }-*/;
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getNamespace() /*-{
+ return this.namespace;
+ }-*/;
+
+ public final native String getCode() /*-{
+ return this.code;
+ }-*/;
+
+ public final native JsArrayString getMethods() /*-{
+ return this.methods;
+ }-*/;
+
+ public final native boolean isCodeFromSrcAttrib() /*-{
+ return this.from_src_attrib;
+ }-*/;
+
+ public final native boolean isActiveDebugCode() /*-{
+ return this.active_debug_code ? true : false;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearch.css b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearch.css
new file mode 100644
index 0000000..a068eb5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearch.css
@@ -0,0 +1,38 @@
+
+ at external search;
+
+.codeSearchWidget .search {
+ width: 145px;
+}
+
+.fileImage {
+ position: relative;
+ top: 2px;
+ margin-right: 5px;
+}
+
+.itemImage {
+ position: relative;
+ top: 2px;
+ margin-left: 2px;
+ margin-right: 8px;
+ padding-top: 4px;
+ padding-bottom: 2px;
+
+}
+
+.itemName {
+ margin-right: 7px;
+}
+
+.itemContext {
+ color: #888;
+}
+
+.codeSearchDialogMainWidget {
+ width: 250px;
+}
+
+.codeSearchDialogMainWidget .codeSearchWidget .search {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchDialog.java
new file mode 100644
index 0000000..75a96a2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchDialog.java
@@ -0,0 +1,117 @@
+/*
+ * CodeSearchDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.ui;
+
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Provider;
+
+public class CodeSearchDialog extends ModalDialogBase
+ implements CodeSearch.Observer
+
+{
+ public CodeSearchDialog(Provider<CodeSearch> pCodeSearch)
+ {
+ super();
+
+ setGlassEnabled(false);
+ setAutoHideEnabled(true);
+
+ setText("Go to File/Function");
+
+ pCodeSearch_ = pCodeSearch;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel mainPanel = new VerticalPanel();
+ mainPanel.addStyleName(
+ CodeSearchResources.INSTANCE.styles().codeSearchDialogMainWidget());
+ codeSearch_ = pCodeSearch_.get();
+ codeSearch_.setObserver(this);
+ mainPanel.add(codeSearch_.getSearchWidget());
+ return mainPanel;
+ }
+
+ @Override
+ protected void positionAndShowDialog(final Command onCompleted)
+ {
+ setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ int left = (Window.getClientWidth()/2) - (offsetWidth/2);
+ setPopupPosition(left, 15);
+ onCompleted.execute();
+
+ }
+
+ });
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ ((CanFocus)codeSearch_.getSearchWidget()).focus();
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ super.onUnload();
+ if (codeSearch_ != null)
+ codeSearch_.detachEventBusHandlers();
+ }
+
+ @Override
+ public void onCompleted()
+ {
+ closeDialog();
+ }
+
+ @Override
+ public void onCancel()
+ {
+ // delay to prevent ESC key from ever getting into the editor
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ closeDialog();
+ }
+ });
+
+ }
+
+ @Override
+ public String getCueText()
+ {
+ return "";
+ }
+
+ Provider<CodeSearch> pCodeSearch_;
+ CodeSearch codeSearch_;
+
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchResources.java
new file mode 100644
index 0000000..195c8c7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchResources.java
@@ -0,0 +1,46 @@
+/*
+ * CodeSearchResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.ui;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+
+public interface CodeSearchResources extends ClientBundle
+{
+ public static interface Styles extends CssResource
+ {
+ String codeSearchWidget();
+ String fileImage();
+ String itemImage();
+ String itemName();
+ String itemContext();
+ String codeSearchDialogMainWidget();
+ }
+
+
+ @Source("CodeSearch.css")
+ Styles styles();
+
+ ImageResource method();
+ ImageResource cls();
+ ImageResource gotoFunction();
+
+ public static CodeSearchResources INSTANCE =
+ (CodeSearchResources)GWT.create(CodeSearchResources.class) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchWidget.java
new file mode 100644
index 0000000..86b02bb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/CodeSearchWidget.java
@@ -0,0 +1,71 @@
+/*
+ * CodeSearchWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.codesearch.ui;
+
+
+
+import org.rstudio.core.client.widget.SearchDisplay;
+import org.rstudio.core.client.widget.SearchWidget;
+import org.rstudio.core.client.widget.TextBoxWithCue;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
+import org.rstudio.studio.client.workbench.codesearch.CodeSearchOracle;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.inject.Inject;
+
+
+public class CodeSearchWidget extends SearchWidget
+ implements CodeSearch.Display
+{
+ @Inject
+ public CodeSearchWidget(CodeSearchOracle oracle,
+ final Commands commands)
+ {
+ super(oracle,
+ new TextBoxWithCue("Go to file/function"),
+ new SuggestBox.DefaultSuggestionDisplay());
+
+ oracle_ = oracle;
+
+ CodeSearchResources res = CodeSearchResources.INSTANCE;
+
+ setIcon(res.gotoFunction());
+
+ addStyleName(res.styles().codeSearchWidget());
+ }
+
+ @Override
+ public SearchDisplay getSearchDisplay()
+ {
+ return this;
+ }
+
+ @Override
+ public void setCueText(String text)
+ {
+ ((TextBoxWithCue)getTextBox()).setCueText(text);
+ }
+
+ @Override
+ public CodeSearchOracle getSearchOracle()
+ {
+ return oracle_;
+ }
+
+ private final CodeSearchOracle oracle_;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/cls.png b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/cls.png
new file mode 100644
index 0000000..f50de27
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/cls.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/gotoFunction.png b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/gotoFunction.png
new file mode 100644
index 0000000..bd2a70d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/gotoFunction.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/method.png b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/method.png
new file mode 100644
index 0000000..41ee61a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/codesearch/ui/method.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.cmd.xml b/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.cmd.xml
new file mode 100644
index 0000000..63c9679
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.cmd.xml
@@ -0,0 +1,1418 @@
+<!--
+
+This file is intended to contain definitions for all commands in RStudio, as
+well as menu structures (for main menu and popup menus).
+
+<menu>
+ Contains <menu>, <cmd>, <separator>
+ @id (required): The method name for this menu on the command bundle interface
+ @vertical: Whether the menu should be vertical. Defaults to true
+ @label: For submenus, indicate the label in the parent menu for this submenu
+
+<cmd> (in menu context)
+ @refid (required): The id of the command that should appear here
+
+<cmd> (in command context)
+ @id (required): The method name for this command on the command bundle iface
+ @label: Will be used for buttonLabel or menuLabel if they are not provided
+ @buttonLabel: Label that should be used on toolbar button face. Use empty
+ string if no label should be used
+ @menuLabel: Label that should be used in a menu.
+ @desc: Extended description (e.g. for tooltip)
+
+-->
+<commands>
+ <menu id="mainMenu" vertical="false">
+
+ <separator/>
+
+ <menu label="_File">
+ <menu label="New _File">
+ <cmd refid="newSourceDoc"/>
+ <separator/>
+ <cmd refid="newTextDoc"/>
+ <cmd refid="newCppDoc"/>
+ <separator/>
+ <cmd refid="newSweaveDoc"/>
+ <cmd refid="newRMarkdownDoc"/>
+ <cmd refid="newRHTMLDoc"/>
+ <cmd refid="newRPresentationDoc"/>
+ <separator/>
+ <cmd refid="newRDocumentationDoc"/>
+ </menu>
+ <cmd refid="newProject"/>
+ <separator/>
+ <cmd refid="openSourceDoc"/>
+ <cmd refid="reopenSourceDocWithEncoding"/>
+ <menu label="_Recent Files">
+ <cmd refid="mru0"/>
+ <cmd refid="mru1"/>
+ <cmd refid="mru2"/>
+ <cmd refid="mru3"/>
+ <cmd refid="mru4"/>
+ <cmd refid="mru5"/>
+ <cmd refid="mru6"/>
+ <cmd refid="mru7"/>
+ <cmd refid="mru8"/>
+ <cmd refid="mru9"/>
+ <separator/>
+ <cmd refid="clearRecentFiles"/>
+ </menu>
+ <separator/>
+ <cmd refid="openProject"/>
+ <cmd refid="openProjectInNewWindow"/>
+ <menu label="Recent Pro_jects">
+ <cmd refid="projectMru0"/>
+ <cmd refid="projectMru1"/>
+ <cmd refid="projectMru2"/>
+ <cmd refid="projectMru3"/>
+ <cmd refid="projectMru4"/>
+ <cmd refid="projectMru5"/>
+ <cmd refid="projectMru6"/>
+ <cmd refid="projectMru7"/>
+ <cmd refid="projectMru8"/>
+ <cmd refid="projectMru9"/>
+ <separator/>
+ <cmd refid="clearRecentProjects"/>
+ </menu>
+ <separator/>
+ <cmd refid="saveSourceDoc"/>
+ <cmd refid="saveSourceDocAs"/>
+ <cmd refid="saveSourceDocWithEncoding"/>
+ <cmd refid="saveAllSourceDocs"/>
+ <separator/>
+ <cmd refid="knitDocument"/>
+ <cmd refid="previewHTML"/>
+ <cmd refid="compilePDF"/>
+ <cmd refid="compileNotebook"/>
+ <separator/>
+ <cmd refid="printSourceDoc"/>
+ <separator/>
+ <cmd refid="closeSourceDoc"/>
+ <cmd refid="closeAllSourceDocs"/>
+ <separator/>
+ <cmd refid="closeProject"/>
+ <separator/>
+ <cmd refid="quitSession"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Edit">
+ <cmd refid="undoDummy"/>
+ <cmd refid="redoDummy"/>
+ <separator/>
+ <cmd refid="cutDummy"/>
+ <cmd refid="copyDummy"/>
+ <cmd refid="pasteDummy"/>
+ <separator/>
+ <menu label="_Folding">
+ <cmd refid="fold"/>
+ <cmd refid="unfold"/>
+ <separator/>
+ <cmd refid="foldAll"/>
+ <cmd refid="unfoldAll"/>
+ </menu>
+ <separator/>
+ <cmd refid="goToLine"/>
+ <separator/>
+ <cmd refid="findReplace"/>
+ <cmd refid="findNext"/>
+ <cmd refid="findPrevious"/>
+ <cmd refid="findFromSelection"/>
+ <cmd refid="replaceAndFind"/>
+ <separator/>
+ <cmd refid="findInFiles"/>
+ <separator/>
+ <cmd refid="checkSpelling"/>
+ <separator/>
+ <cmd refid="consoleClear"/>
+ <separator/>
+ <cmd refid="macPreferences"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Code">
+ <cmd refid="sourceNavigateBack"/>
+ <cmd refid="sourceNavigateForward"/>
+ <separator/>
+ <cmd refid="insertChunk"/>
+ <cmd refid="insertSection"/>
+ <separator/>
+ <cmd refid="jumpTo"/>
+ <cmd refid="goToFileFunction"/>
+ <separator/>
+ <cmd refid="goToHelp"/>
+ <cmd refid="goToFunctionDefinition"/>
+ <separator/>
+ <cmd refid="extractFunction"/>
+ <cmd refid="extractLocalVariable"/>
+ <separator/>
+ <cmd refid="reindent"/>
+ <cmd refid="reflowComment"/>
+ <cmd refid="commentUncomment"/>
+ <separator/>
+ <cmd refid="executeCode"/>
+ <cmd refid="executeLastCode"/>
+ <menu label="Run Regi_on">
+ <cmd refid="executeCurrentChunk"/>
+ <cmd refid="executeNextChunk"/>
+ <cmd refid="executeToCurrentLine"/>
+ <cmd refid="executeFromCurrentLine"/>
+ <cmd refid="executeCurrentFunction"/>
+ <cmd refid="executeCurrentSection"/>
+ <separator/>
+ <cmd refid="executeAllCode"/>
+ </menu>
+
+ <separator/>
+ <cmd refid="sourceActiveDocument"/>
+ <cmd refid="sourceActiveDocumentWithEcho"/>
+ <separator/>
+ <cmd refid="sourceFile"/>
+ <separator/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_View">
+ <cmd refid="showToolbar"/>
+ <cmd refid="hideToolbar"/>
+ <separator/>
+ <cmd refid="zoomActualSize"/>
+ <cmd refid="zoomIn"/>
+ <cmd refid="zoomOut"/>
+ <separator/>
+ <cmd refid="toggleFullScreen"/>
+ <separator/>
+ <cmd refid="switchToTab"/>
+ <cmd refid="nextTab"/>
+ <cmd refid="previousTab"/>
+ <cmd refid="firstTab"/>
+ <cmd refid="lastTab"/>
+ <separator/>
+ <cmd refid="activateSource"/>
+ <cmd refid="activateConsole"/>
+ <cmd refid="activateHelp"/>
+ <separator/>
+ <cmd refid="activateHistory"/>
+ <cmd refid="activateFiles"/>
+ <cmd refid="activatePlots"/>
+ <cmd refid="activatePackages"/>
+ <cmd refid="activateEnvironment"/>
+ <cmd refid="activateVcs"/>
+ <cmd refid="activateBuild"/>
+ <separator/>
+ <cmd refid="synctexSearch"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Plots">
+ <cmd refid="nextPlot"/>
+ <cmd refid="previousPlot"/>
+ <separator/>
+ <cmd refid="zoomPlot"/>
+ <separator/>
+ <cmd refid="savePlotAsImage"/>
+ <cmd refid="savePlotAsPdf"/>
+ <separator/>
+ <cmd refid="copyPlotToClipboard"/>
+ <separator/>
+ <cmd refid="removePlot"/>
+ <separator/>
+ <cmd refid="clearPlots"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Session">
+ <cmd refid="interruptR"/>
+ <cmd refid="restartR"/>
+ <separator/>
+ <cmd refid="terminateR"/>
+ <separator/>
+ <menu label="Set _Working Directory">
+ <cmd refid="setWorkingDirToActiveDoc"/>
+ <cmd refid="setWorkingDirToFilesPane"/>
+ <separator/>
+ <cmd refid="setWorkingDir"/>
+ </menu>
+ <separator/>
+ <cmd refid="loadWorkspace"/>
+ <cmd refid="saveWorkspace"/>
+ <separator/>
+ <cmd refid="clearWorkspace"/>
+ </menu>
+
+ <menu label="_Build">
+ <cmd refid="devtoolsLoadAll"/>
+ <cmd refid="buildAll"/>
+ <cmd refid="rebuildAll"/>
+ <cmd refid="cleanAll"/>
+ <separator/>
+ <cmd refid="testPackage"/>
+ <separator/>
+ <cmd refid="checkPackage"/>
+ <separator/>
+ <cmd refid="buildSourcePackage"/>
+ <cmd refid="buildBinaryPackage"/>
+ <separator/>
+ <cmd refid="roxygenizePackage"/>
+ <separator/>
+ <cmd refid="stopBuild"/>
+ <separator/>
+ <cmd refid="buildToolsProjectSetup"/>
+ </menu>
+
+ <menu label="_Debug">
+ <cmd refid="debugBreakpoint"/>
+ <cmd refid="debugClearBreakpoints"/>
+ <separator/>
+ <cmd refid="debugStep"/>
+ <cmd refid="debugStepInto"/>
+ <cmd refid="debugFinish"/>
+ <cmd refid="debugContinue"/>
+ <cmd refid="debugStop"/>
+ <separator/>
+ <menu label="_On Error">
+ <cmd refid="errorsMessage"/>
+ <cmd refid="errorsTraceback"/>
+ <cmd refid="errorsBreak"/>
+ </menu>
+ <separator/>
+ <cmd refid="debugHelp"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Tools">
+ <menu label="_Import Dataset">
+ <cmd refid="importDatasetFromFile"/>
+ <cmd refid="importDatasetFromURL"/>
+ </menu>
+ <separator/>
+ <cmd refid="installPackage"/>
+ <cmd refid="updatePackages"/>
+ <separator/>
+ <menu label="_Version Control">
+ <cmd refid="vcsFileDiff"/>
+ <cmd refid="vcsFileLog"/>
+ <cmd refid="vcsFileRevert"/>
+ <separator/>
+ <cmd refid="vcsViewOnGitHub"/>
+ <cmd refid="vcsBlameOnGitHub"/>
+ <separator/>
+ <cmd refid="vcsCommit"/>
+ <separator/>
+ <cmd refid="vcsPull"/>
+ <cmd refid="vcsCleanup"/>
+ <cmd refid="vcsPush"/>
+ <separator/>
+ <cmd refid="vcsShowHistory"/>
+ <separator/>
+ <cmd refid="versionControlProjectSetup"/>
+ </menu>
+ <separator/>
+ <menu label="_ShinyApps">
+ <cmd refid="shinyAppsDeploy"/>
+ <cmd refid="shinyAppsTerminate"/>
+ <separator/>
+ <cmd refid="shinyAppsManageAccounts"/>
+ </menu>
+ <separator/>
+ <cmd refid="showShellDialog"/>
+ <separator/>
+ <cmd refid="projectOptions"/>
+ <separator/>
+ <cmd refid="showOptions"/>
+ </menu>
+
+ <separator/>
+
+ <menu label="_Help">
+ <cmd refid="helpHome"/>
+ <cmd refid="showAboutDialog"/>
+ <cmd refid="checkForUpdates"/>
+ <separator/>
+ <cmd refid="helpUsingRStudio"/>
+ <cmd refid="rstudioSupport"/>
+ <separator/>
+ <cmd refid="markdownHelp"/>
+ <cmd refid="helpKeyboardShortcuts"/>
+ <separator/>
+ <cmd refid="rstudioAgreement"/>
+ <separator/>
+ <menu label="Dia_gnostics">
+ <cmd refid="showRequestLog"/>
+ <cmd refid="showLogFiles"/>
+ <cmd refid="updateCredentials"/>
+ <cmd refid="diagnosticsReport"/>
+ <separator/>
+ <cmd refid="debugDumpContents"/>
+ <cmd refid="debugImportDump"/>
+ <cmd refid="raiseException"/>
+- <cmd refid="raiseException2"/>
+ </menu>
+ </menu>
+
+ <separator/>
+
+ </menu>
+
+ <shortcuts>
+ <!--
+ Only the following keys may be used:
+ Alphanumerics
+ /
+ Enter
+ Left, Right, Up, Down
+ Tab
+ PageUp, PageDown
+ F1-F12
+ =
+ <
+ `
+ .
+ -->
+ <!-- Valid modifiers are Ctrl, Alt, Meta, Shift, and Cmd -->
+ <!-- "Cmd" means Ctrl OR Meta -->
+ <shortcutgroup name="Source Editor">
+ <shortcut refid="insertChunk" value="Cmd+Alt+I"/>
+ <shortcut refid="insertSection" value="Cmd+Shift+R"/>
+ <shortcut refid="extractFunction" value="Cmd+Alt+T"/>
+ <shortcut refid="extractLocalVariable" value="Cmd+Alt+V"/>
+ <shortcut refid="commentUncomment" value="Cmd+Shift+C"/>
+ <shortcut refid="reindent" value="Cmd+I"/>
+ <shortcut refid="reflowComment" value="Cmd+Shift+/"/>
+ <shortcut refid="fold" value="Alt+L" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="fold" value="Cmd+Alt+L" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="unfold" value="Shift+Alt+L" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="unfold" value="Cmd+Shift+Alt+L" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="foldAll" value="Alt+O" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="foldAll" value="Cmd+Alt+O" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="unfoldAll" value="Shift+Alt+O" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="unfoldAll" value="Cmd+Shift+Alt+O" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut value="Ctrl+K" title="Delete to Line End" if="org.rstudio.core.client.BrowseCap.isMacintosh()" />
+ <shortcut value="Alt+Up" title="Move Lines Up" />
+ <shortcut value="Alt+Down" title="Move Lines Down" />
+ <shortcut value="Ctrl+D" title="Delete Line" if="!org.rstudio.core.client.BrowseCap.isMacintosh()" />
+ <shortcut value="Cmd+D" title="Delete Line" if="org.rstudio.core.client.BrowseCap.isMacintosh()" />
+ <shortcut value="Ctrl+U" title="Yank Line Up to Cursor" />
+ <shortcut value="Ctrl+K" title="Yank Line After Cursor" />
+ <shortcut value="Ctrl+Y" title="Insert Yanked Text" />
+ <shortcut value="Cmd+Shift+Alt+G" title="Go to Line" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut value="Shift+Alt+G" title="Go to Line" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut value="Ctrl+T" title="Transpose Letters" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ </shortcutgroup>
+ <shortcutgroup name="Source Navigation">
+ <shortcut refid="sourceNavigateBack" value="Cmd+F9"/>
+ <shortcut refid="sourceNavigateForward" value="Cmd+F10"/>
+ <shortcut refid="findFromSelection" value="Meta+E" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="findFromSelection" value="Ctrl+F3" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="findReplace" value="Meta+F" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="findReplace" value="Cmd+F" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="findNext" value="Cmd+G" if="!org.rstudio.core.client.BrowseCap.isWindows()"/>
+ <shortcut refid="findNext" value="F3" if="org.rstudio.core.client.BrowseCap.isWindows()"/>
+ <shortcut refid="findPrevious" value="Cmd+Shift+G" if="!org.rstudio.core.client.BrowseCap.isWindows()"/>
+ <shortcut refid="findPrevious" value="Shift+F3" if="org.rstudio.core.client.BrowseCap.isWindows()"/>
+ <shortcut refid="replaceAndFind" value="Cmd+Shift+J"/>
+ <shortcut refid="goToFileFunction" value="Ctrl+."/>
+ <shortcut refid="goToLine" value="Shift+Alt+G" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="goToLine" value="Cmd+Shift+Alt+G" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="jumpTo" value="Shift+Alt+J" if="!org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="jumpTo" value="Cmd+Shift+Alt+J" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="jumpToMatching" value="Ctrl+P"/>
+ </shortcutgroup>
+ <shortcutgroup name="Tabs/Panes">
+ <shortcut refid="activateSource" value="Ctrl+1"/>
+ <shortcut refid="activateConsole" value="Ctrl+2"/>
+ <shortcut refid="activateHelp" value="Ctrl+3"/>
+ <shortcut refid="activateHistory" value="Ctrl+4"/>
+ <shortcut refid="activateFiles" value="Ctrl+5"/>
+ <shortcut refid="activatePlots" value="Ctrl+6"/>
+ <shortcut refid="activatePackages" value="Ctrl+7"/>
+ <shortcut refid="activateEnvironment" value="Ctrl+8"/>
+ <shortcut refid="activateVcs" value="Ctrl+9"/>
+ <shortcut refid="activateBuild" value="Ctrl+0"/>
+ <shortcut refid="switchToTab" value="Ctrl+Alt+Down"/>
+ <shortcut refid="previousTab" value="Ctrl+Alt+Left" if="!org.rstudio.core.client.BrowseCap.isLinux()"/>
+ <shortcut refid="nextTab" value="Ctrl+Alt+Right" if="!org.rstudio.core.client.BrowseCap.isLinux()"/>
+ <shortcut refid="previousTab" value="Ctrl+PageUp" if="org.rstudio.core.client.BrowseCap.isLinux()"/>
+ <shortcut refid="nextTab" value="Ctrl+PageDown" if="org.rstudio.core.client.BrowseCap.isLinux()"/>
+ <shortcut refid="firstTab" value="Ctrl+Alt+Shift+Left"/>
+ <shortcut refid="lastTab" value="Ctrl+Alt+Shift+Right"/>
+ </shortcutgroup>
+ <shortcutgroup name="Files">
+ <shortcut refid="saveSourceDoc" value="Cmd+S"/>
+ <shortcut refid="saveAllSourceDocs" value= "Cmd+Alt+S"/>
+ <shortcut refid="newSourceDoc" value="Cmd+Shift+N" if="!org.rstudio.core.client.BrowseCap.isChrome()" title="New R Script"/>
+ <shortcut refid="newSourceDoc" value="Cmd+Alt+I" if="org.rstudio.core.client.BrowseCap.isChrome()" title="New R Script"/>
+ <shortcut refid="openSourceDoc" value="Cmd+O"/>
+ <shortcut refid="closeSourceDoc" value="Cmd+W"/>
+ <shortcut refid="closeAllSourceDocs" value="Cmd+Shift+W"/>
+ <shortcut refid="findInFiles" value="Cmd+Shift+F"/>
+ </shortcutgroup>
+ <shortcutgroup name="Build">
+ <shortcut refid="compilePDF" value="Cmd+Shift+I"/>
+ <shortcut refid="compileNotebook" value="Cmd+Shift+Alt+H" />
+ <shortcut refid="previewHTML" value="Cmd+Shift+Y" />
+ <shortcut refid="knitDocument" value="Cmd+Shift+K" />
+ <shortcut refid="buildAll" value="Cmd+Shift+B"/>
+ <shortcut refid="devtoolsLoadAll" value="Cmd+Shift+L"/>
+ <shortcut refid="checkPackage" value="Cmd+Shift+E"/>
+ <shortcut refid="testPackage" value="Cmd+Shift+T" if="org.rstudio.studio.client.application.Desktop.isDesktop()"/>
+ <shortcut refid="testPackage" value="Cmd+Alt+F7" if="!org.rstudio.studio.client.application.Desktop.isDesktop()"/>
+ <shortcut refid="roxygenizePackage" value="Cmd+Shift+D"/>
+ </shortcutgroup>
+ <shortcutgroup name="Execute">
+ <shortcut refid="sourceFile" value="Cmd+Shift+O"/>
+ <shortcut refid="sourceActiveDocument" value="Cmd+Shift+S"/>
+ <shortcut refid="sourceActiveDocumentWithEcho" value="Cmd+Shift+Enter"/>;
+ <shortcut refid="executeLastCode" value="Cmd+Shift+P"/>
+ <shortcut refid="executeCode" value="Cmd+R" if="org.rstudio.core.client.BrowseCap.isWindowsDesktop()"/>
+ <shortcut refid="executeCode" value="Cmd+Enter"/>
+ <shortcut refid="executeAllCode" value="Cmd+Alt+R"/>
+ <shortcut refid="executeToCurrentLine" value="Cmd+Alt+B"/>
+ <shortcut refid="executeFromCurrentLine" value="Cmd+Alt+E"/>
+ <shortcut refid="executeCurrentFunction" value="Cmd+Alt+F"/>
+ <shortcut refid="executeCurrentSection" value="Cmd+Alt+T"/>
+ <shortcut refid="executeCurrentChunk" value="Cmd+Alt+C"/>
+ <shortcut refid="executeNextChunk" value="Cmd+Alt+N"/>
+ </shortcutgroup>
+ <shortcutgroup name="Debug">
+ <shortcut refid="debugBreakpoint" value="Shift+F9"/>
+ <shortcut refid="debugStep" value="F10"/>
+ <shortcut refid="debugContinue" value="Shift+F5"/>
+ <shortcut refid="debugStop" value="Shift+F8"/>
+ </shortcutgroup>
+ <shortcutgroup name="Source Control">
+ <shortcut refid="vcsFileDiff" value="Ctrl+Alt+D" title="Diff Files"/>
+ <shortcut refid="vcsCommit" value="Ctrl+Alt+M" />
+ </shortcutgroup>
+ <shortcutgroup name="Other">
+ <shortcut value="F1" title="Show Function Help" />
+ <shortcut value="F2" title="Show Function Code" />
+ <shortcut value="Tab" title="Complete Code" />
+ <shortcut refid="quitSession" value="Cmd+Q" if="org.rstudio.studio.client.application.Desktop.isDesktop()"/>
+ <shortcut refid="restartR" value="Cmd+Shift+0" if="org.rstudio.core.client.BrowseCap.isMacintosh()"/>
+ <shortcut refid="restartR" value="Cmd+Shift+F10"/>
+ <shortcut refid="previousPlot" value="Cmd+Shift+PageUp" />
+ <shortcut refid="nextPlot" value="Cmd+Shift+PageDown" />
+ <shortcut refid="showRequestLog" value="Ctrl+`" />
+ <shortcut refid="logFocusedElement" value="Ctrl+Shift+`" />
+ <shortcut refid="setWorkingDir" value="Ctrl+Shift+H" />
+ <shortcut refid="synctexSearch" value="Cmd+F8" />
+ <shortcut refid="checkSpelling" value="F7" />
+ <shortcut refid="refreshSuperDevMode" value="Cmd+Shift+F5"/>
+ <shortcut refid="helpKeyboardShortcuts" value="Alt+Shift+K"/>
+ <shortcut refid="toggleFullScreen" value="Ctrl+Meta+F" if="org.rstudio.core.client.BrowseCap.isMacintoshDesktop()"/>
+ </shortcutgroup>
+ <shortcutgroup name="Console">
+ <shortcut refid="consoleClear" value="Ctrl+L"/>
+ <shortcut value="Ctrl+Up" title="Popup Command History"/>
+ </shortcutgroup>
+ </shortcuts>
+
+ <!--
+ Valid cmd attributes:
+ id The unique name for this command. There must be a matching
+ method with this name on the CommandBundle subinterface.
+
+ label The user-visible name for this command. Will be used on
+ buttons and menus unless overridden by menuLabel or
+ buttonLabel.
+
+ menuLabel The name that will be displayed in the main menu and/or
+ popup menus.
+
+ buttonLabel The name that will be displayed on toolbar buttons.
+
+ desc A short description to be used as a tooltip.
+
+ enabled [true|false] Whether the command should start out as enabled
+ or disabled. Defaults to true.
+
+ visible [true|false] Whether the command should start out as visible
+ or hidden. Defaults to true.
+ -->
+
+ <cmd id="setWorkingDirToActiveDoc"
+ buttonLabel=""
+ menuLabel="To Source File Location"
+ desc="Change working directory to path of active document"/>
+ <cmd id="setWorkingDirToFilesPane"
+ buttonLabel=""
+ menuLabel="To _Files Pane Location"
+ desc="Change working directory to location of Files pane"/>
+ <cmd id="setWorkingDir"
+ buttonLabel=""
+ menuLabel="_Choose Directory..."
+ desc="Select and change to a new working directory"/>
+
+ <cmd id="newSourceDoc"
+ buttonLabel=""
+ menuLabel="_R Script"
+ desc="Create a new R script"/>
+
+ <cmd id="newTextDoc"
+ menuLabel="_Text File"
+ desc="Create a new text file"/>
+
+ <cmd id="newCppDoc"
+ menuLabel="_C++ File"
+ desc="Create a new C++ file"/>
+
+ <cmd id="rcppHelp"
+ desc="Help on using Rcpp"/>
+
+ <cmd id="newSweaveDoc"
+ menuLabel="R _Sweave"
+ desc="Create a new R Sweave document"/>
+
+ <cmd id="newRMarkdownDoc"
+ menuLabel="R _Markdown"
+ desc="Create a new R Markdown document"/>
+
+ <cmd id="newRHTMLDoc"
+ menuLabel="R _HTML"
+ desc="Create a new R HTML document"/>
+
+ <cmd id="newRPresentationDoc"
+ menuLabel="R _Presentation"
+ desc="Create a new R presentation"/>
+
+ <cmd id="newRDocumentationDoc"
+ menuLabel="R_d File"
+ desc="Create a new Rd documentation file"/>
+
+ <cmd id="openSourceDoc"
+ menuLabel="_Open File..."
+ buttonLabel=""
+ desc="Open an existing file"/>
+ <cmd id="reopenSourceDocWithEncoding"
+ menuLabel="Reopen with _Encoding..."
+ buttonLabel=""
+ desc="Reopen the current file with a different encoding"/>
+ <cmd id="saveSourceDoc"
+ menuLabel="_Save"
+ buttonLabel=""
+ desc="Save current document"/>
+ <cmd id="saveSourceDocAs"
+ menuLabel="Save _As..."
+ buttonLabel="Save as"
+ desc="Save current file to a specific path" />
+ <cmd id="saveAllSourceDocs"
+ menuLabel="Sa_ve All"
+ buttonLabel=""
+ desc="Save all open documents"/>
+ <cmd id="saveSourceDocWithEncoding"
+ menuLabel="Save wit_h Encoding..."
+ desc="Save the current file with a different encoding"/>
+ <cmd id="closeSourceDoc"
+ menuLabel="_Close"
+ enabled="false"
+ preventShortcutWhenDisabled="org.rstudio.studio.client.application.Desktop.isDesktop()"/>
+ <cmd id="closeAllSourceDocs"
+ menuLabel="C_lose All"/>
+ <cmd id="vcsFileDiff"
+ menuLabel="_Diff of"
+ desc="Show differences for the file"/>
+ <cmd id="vcsFileLog"
+ menuLabel="_Log of"
+ desc="Show log of changes to the file"/>
+ <cmd id="vcsFileRevert"
+ menuLabel="_Revert"
+ desc="Revert changes to the file"/>
+ <cmd id="vcsViewOnGitHub"
+ menuLabel="_View FILE on GitHub"
+ desc="View this file on Github"/>
+ <cmd id="vcsBlameOnGitHub"
+ menuLabel="_Blame FILE on GitHub"
+ desc="Blame view for this file on Github"/>
+ <cmd id="printSourceDoc"
+ menuLabel="Pr_int..."
+ buttonLabel=""
+ desc="Print the current file"/>
+ <cmd id="popoutDoc"
+ desc="Show in new window"/>
+
+ <cmd id="mru0" visible="false"/>
+ <cmd id="mru1" visible="false"/>
+ <cmd id="mru2" visible="false"/>
+ <cmd id="mru3" visible="false"/>
+ <cmd id="mru4" visible="false"/>
+ <cmd id="mru5" visible="false"/>
+ <cmd id="mru6" visible="false"/>
+ <cmd id="mru7" visible="false"/>
+ <cmd id="mru8" visible="false"/>
+ <cmd id="mru9" visible="false"/>
+ <cmd id="clearRecentFiles" menuLabel="Clear List"/>
+
+ <cmd id="newProject"
+ menuLabel="New _Project..."
+ buttonLabel=""
+ desc="Create a project"/>
+ <cmd id="openProject"
+ menuLabel="Ope_n Project..."
+ buttonLabel=""
+ desc="Open a project"/>
+ <cmd id="openProjectInNewWindow"
+ menuLabel="Open Project in New _Window..."
+ buttonLabel=""
+ desc="Open a project in a new window"/>
+
+ <cmd id="projectMru0" visible="false"/>
+ <cmd id="projectMru1" visible="false"/>
+ <cmd id="projectMru2" visible="false"/>
+ <cmd id="projectMru3" visible="false"/>
+ <cmd id="projectMru4" visible="false"/>
+ <cmd id="projectMru5" visible="false"/>
+ <cmd id="projectMru6" visible="false"/>
+ <cmd id="projectMru7" visible="false"/>
+ <cmd id="projectMru8" visible="false"/>
+ <cmd id="projectMru9" visible="false"/>
+ <cmd id="clearRecentProjects" menuLabel="Clear List"/>
+
+ <cmd id="closeProject"
+ menuLabel="Close Projec_t"
+ buttonLabel=""
+ desc="Close the currently open project"/>
+
+ <cmd id="projectOptions"
+ menuLabel="_Project Options..."
+ buttonLabel=""
+ desc="Edit options for the current project"/>
+
+ <cmd id="projectSweaveOptions"
+ menuLabel=""
+ buttonLabel=""
+ desc=""/>
+
+ <cmd id="showToolbar"
+ menuLabel="Show _Toolbar"/>
+ <cmd id="hideToolbar"
+ menuLabel="Hide _Toolbar"/>
+ <cmd id="zoomActualSize"
+ menuLabel="Actual Size"/>
+ <cmd id="zoomIn"
+ menuLabel="_Zoom In"/>
+ <cmd id="zoomOut"
+ menuLabel="Zoom O_ut"/>
+ <cmd id="goToFileFunction"
+ menuLabel="Go To File/F_unction..."/>
+ <cmd id="activateSource"
+ menuLabel="Move Focus to Sou_rce"/>
+ <cmd id="activateConsole"
+ menuLabel="Move Focus to _Console"/>
+ <cmd id="activateEnvironment"
+ menuLabel="Show _Environment"/>
+ <cmd id="activateData"
+ menuLabel="Show _Data"/>
+ <cmd id="activateHistory"
+ menuLabel="Show Histor_y"/>
+ <cmd id="activateFiles"
+ menuLabel="Show F_iles"/>
+ <cmd id="activatePlots"
+ menuLabel="Show Pl_ots"/>
+ <cmd id="activatePackages"
+ menuLabel="Show P_ackages"/>
+ <cmd id="activateHelp"
+ menuLabel="Move Focus to _Help"/>
+ <cmd id="activateVcs"
+ menuLabel="Show _Vcs"/>
+ <cmd id="activateBuild"
+ menuLabel="Show _Build"/>
+ <cmd id="activatePresentation"
+ menuLabel="Show Presentation"/>
+ <cmd id="jumpTo"
+ menuLabel="_Jump To..."/>
+ <cmd id="switchToTab"
+ menuLabel="Switch to Ta_b..."/>
+ <cmd id="previousTab"
+ menuLabel="_Previous Tab"/>
+ <cmd id="nextTab"
+ menuLabel="_Next Tab"/>
+ <cmd id="firstTab"
+ menuLabel="_First Tab"/>
+ <cmd id="lastTab"
+ menuLabel="_Last Tab"/>
+ <cmd id="goToLine"
+ menuLabel="_Go to Line..."/>
+ <cmd id="toggleFullScreen"
+ menuLabel="Toggle Full Screen"/>
+
+ <cmd id="findFromSelection"
+ menuLabel="_Use Selection for Find"/>
+ <cmd id="findReplace"
+ menuLabel="_Find..."/>
+ <cmd id="findNext"
+ buttonLabel="Next"
+ menuLabel="Find _Next"
+ desc="Find next occurrence"/>
+ <cmd id="findPrevious"
+ buttonLabel="Prev"
+ menuLabel="Find Pre_vious"
+ desc="Find previous occurrence"/>
+ <cmd id="replaceAndFind"
+ buttonLabel="Replace"
+ menuLabel="_Replace and Find"
+ desc="Replace and find next occurrence"/>
+ <cmd id="findInFiles"
+ menuLabel="Find _in Files..."/>
+ <cmd id="fold"
+ menuLabel="Collapse"/>
+ <cmd id="unfold"
+ menuLabel="Expand"/>
+ <cmd id="foldAll"
+ menuLabel="Collapse All"/>
+ <cmd id="unfoldAll"
+ menuLabel="Expand All"/>
+ <cmd id="jumpToMatching"
+ menuLabel="Jump To _Matching"
+ desc="Jump to matching brace or paren"/>
+ <cmd id="executeAllCode"
+ buttonLabel=""
+ menuLabel="Run _All"
+ desc="Run all of the code in the source file"/>
+ <cmd id="executeCode"
+ buttonLabel="Run"
+ menuLabel="Run _Line(s)"
+ desc="Run the current line or selection"/>
+ <cmd id="executeCodeWithoutFocus"/>
+ <cmd id="executeToCurrentLine"
+ menuLabel="Run From _Beginning To Line"
+ desc="Run from the beginning of the source file up through the current line"/>
+ <cmd id="executeFromCurrentLine"
+ menuLabel="Run From Line to _End"
+ desc="Run from the current line through the end of the source file"/>
+ <cmd id="executeCurrentFunction"
+ menuLabel="Run _Function Definition"
+ desc="Run the top-level function definition, if any, that contains the cursor"/>
+ <cmd id="executeCurrentSection"
+ buttonLabel="Run Section"
+ menuLabel="Run Code _Section"
+ desc="Run the code section that contains the cursor"/>
+ <cmd id="executeLastCode"
+ menuLabel="Re-Run _Previous"
+ desc="Re-run the previous code region"/>
+ <cmd id="insertChunk"
+ menuLabel="_Insert Chunk"
+ desc="Insert a new code chunk"/>
+ <cmd id="insertSection"
+ menuLabel="_Insert Section..."
+ desc="Insert a new code section"/>
+ <cmd id="executeCurrentChunk"
+ menuLabel="Run _Current Chunk"
+ desc="Run the current code chunk"/>
+ <cmd id="executeNextChunk"
+ menuLabel="Run _Next Chunk"
+ desc="Run the next code chunk"/>
+ <cmd id ="goToHelp"
+ menuLabel="Go To _Help"
+ desc="Go to help for the currently selected function"/>
+ <cmd id ="goToFunctionDefinition"
+ menuLabel="_Go To Function Definition"
+ desc="Go to to the definition of the currently selected function"/>
+ <cmd id="codeCompletion"
+ menuLabel="Code Completion"
+ desc="Show code completions at the current cursor location"/>
+ <cmd id = "sourceNavigateBack"
+ menuLabel="Bac_k"
+ desc="Go back to the previous source location"/>
+ <cmd id = "sourceNavigateForward"
+ menuLabel="For_ward"
+ desc="Go forward to the next source location"/>
+ <cmd id="extractFunction"
+ menuLabel="E_xtract Function"
+ desc="Turn the current selection into a function"/>
+ <cmd id="extractLocalVariable"
+ menuLabel="Extract _Variable"
+ desc="Extract a variable out of the current selection"/>
+ <cmd id="sourceFile"
+ buttonLabel=""
+ menuLabel="Source _File..."
+ desc="Source the contents of an R file"/>
+ <cmd id="sourceActiveDocument"
+ buttonLabel="Source"
+ menuLabel="_Source"
+ desc="Source the contents of the active document"/>
+ <cmd id="sourceActiveDocumentWithEcho"
+ buttonLabel=""
+ menuLabel="Source with _Echo"
+ desc="Source the contents of the active document (with echo)"/>
+ <cmd id="commentUncomment"
+ menuLabel="_Comment/Uncomment Lines"
+ desc="Comment or uncomment the current line/selection"/>
+ <cmd id="reindent"
+ menuLabel="_Reindent Lines"
+ desc="Reindent the current line/selection"/>
+ <cmd id="reflowComment"
+ menuLabel="Reflow Co_mment"
+ desc="Reflow selected comment lines so they wrap evenly"/>
+
+ <cmd id="markdownHelp"
+ menuLabel="_Markdown Quick Reference"
+ desc="Markdown quick reference"/>
+
+ <cmd id="usingRMarkdownHelp"
+ menuLabel="_Using R Markdown"
+ desc="Guide to using R Markdown"/>
+
+ <cmd id="authoringRPresentationsHelp"
+ menuLabel="_Authoring R Presentations"
+ desc="Guide to using R Markdown"/>
+
+ <cmd id="knitDocument"
+ buttonLabel="Knit"
+ menuLabel="_Knit"
+ desc="Knit the current document"/>
+
+ <cmd id="previewHTML"
+ buttonLabel="Preview"
+ menuLabel="Preview"
+ desc="Show a preview of the current document as HTML"/>
+
+ <cmd id="publishHTML"
+ buttonLabel="Publish"
+ menuLabel="P_ublish to RPubs..."
+ desc="Publish the current document"/>
+
+ <cmd id="compilePDF"
+ buttonLabel="Compile PDF"
+ menuLabel="_Compile PDF"
+ desc="Compile a PDF from the current LaTeX or Sweave document"/>
+
+ <cmd id="compileNotebook"
+ menuLabel="_Compile Notebook..."
+ desc="Compile an HTML notebook from the current R script"/>
+
+ <cmd id="synctexSearch"
+ menuLabel="S_ync PDF View to Editor"
+ buttonLabel=""
+ desc="Sync PDF view to editor location (Ctrl+Click)"/>
+
+ <cmd id="checkSpelling"
+ menuLabel="Check _Spelling..."/>
+
+ <cmd id="newFolder"
+ menuLabel="Folder..."
+ buttonLabel="New Folder"
+ desc="Create a new folder"/>
+ <cmd id="uploadFile"
+ menuLabel="Upload Files..."
+ buttonLabel="Upload"
+ desc="Upload files to server"/>
+ <cmd id="copyFile"
+ menuLabel="Copy..."
+ buttonLabel="Copy"
+ desc="Copy selected file or folder"/>
+ <cmd id="moveFiles"
+ menuLabel="Move..."
+ buttonLabel="Move"
+ desc="Move selected files or folders"/>
+ <cmd id="exportFiles"
+ menuLabel="Export..."
+ buttonLabel="Export"
+ desc="Export selected files or folders"/>
+ <cmd id="renameFile"
+ buttonLabel="Rename"
+ desc="Rename selected file or folder"/>
+ <cmd id="deleteFiles"
+ buttonLabel="Delete"
+ desc="Delete selected files or folders"/>
+ <cmd id="refreshFiles"
+ menuLabel="Refresh"
+ desc="Refresh file listing"/>
+ <cmd id="goToWorkingDir"
+ buttonLabel = ""
+ menuLabel="Go To Working Directory"
+ desc="View the current working directory"/>
+ <cmd id="setAsWorkingDir"
+ label="Set As Working Directory"/>
+ <cmd id="showFolder"
+ label="Show Folder in New Window"
+ visible="false"/>
+ <cmd id="vcsAddFiles"
+ menuLabel="Add"
+ buttonLabel="Add"
+ desc="Add the selected files or folders"/>
+ <cmd id="vcsRemoveFiles"
+ menuLabel="Delete"
+ buttonLabel="Delete"
+ desc="Delete the selected files or folders"/>
+
+ <!-- VCS pane -->
+ <cmd id="vcsDiff"
+ buttonLabel="Diff"
+ menuLabel="Diff"
+ desc="Diff selected file(s)"/>
+ <cmd id="vcsCommit"
+ buttonLabel="Commit"
+ menuLabel="_Commit..."
+ desc="Commit pending changes"/>
+ <cmd id="vcsRevert"
+ buttonLabel="Revert"
+ menuLabel="Revert..."
+ desc="Revert selected changes"/>
+ <cmd id="vcsShowHistory"
+ menuLabel="_History"
+ buttonLabel="History"
+ desc="View history of previous commits"/>
+ <cmd id="vcsRefresh"
+ desc="Refresh listing"/>
+ <cmd id="vcsRefreshNoError"/>
+ <cmd id="vcsOpen"
+ menuLabel="Open File"
+ desc="Open selected file(s)"/>
+ <cmd id="vcsIgnore"
+ menuLabel="Ignore..."
+ buttonLabel="Ignore"
+ desc="Ignore the selected files or folders"/>
+ <cmd id="vcsPull"
+ menuLabel="_Pull Branches"
+ buttonLabel="Pull"/>
+ <cmd id="vcsPush"
+ menuLabel="P_ush Branch"
+ buttonLabel="Push"/>
+ <cmd id="vcsCleanup"
+ menuLabel="Cleanu_p"
+ buttonLabel="Cleanup"
+ desc="Recursively clean up the working copy (removing locks, etc)"/>
+ <cmd id="consoleClear"
+ menuLabel="Cle_ar Console"/>
+ <cmd id="interruptR"
+ menuLabel="_Interrupt R"/>
+ <cmd id="restartR"
+ menuLabel="_Restart R"
+ desc="Restart R"/>
+ <cmd id="terminateR"
+ menuLabel="_Terminate R..."
+ desc="Forcibly terminate R session"/>
+ <cmd id="vcsResolve"
+ menuLabel="Resolve..."
+ buttonLabel="Resolve"
+ desc="Resolve conflicts in the selected files or folders"/>
+
+ <!-- PDF Viewer -->
+ <cmd id="showPdfExternal"
+ menuLabel="Show PDF in External Viewer"
+ desc="Show in an external PDF viewer window"/>
+
+ <!-- HTML Preview -->
+ <cmd id="openHtmlExternal"
+ buttonLabel=""
+ desc="View the page with the system web browser"/>
+
+ <cmd id="saveHtmlPreviewAsLocalFile"
+ menuLabel="File on Local Computer..."
+ desc="Download the page to a local file"/>
+
+ <cmd id="saveHtmlPreviewAs"
+ menuLabel="File on RStudio Server..."
+ buttonLabel="Save As"
+ desc="Save the page to another location"/>
+
+ <cmd id="showHtmlPreviewLog"
+ buttonLabel="Log"
+ desc="Show the compilation log for this document"/>
+
+ <cmd id="refreshHtmlPreview"
+ desc="Refresh the preview"/>
+
+ <!-- Presentation -->
+ <cmd id="refreshPresentation"
+ desc="Refresh the presentation"/>
+ <cmd id="presentationFullscreen"
+ desc="Show presentation in full screen mode"/>
+
+ <cmd id="presentationHome"
+ desc="Go to the first slide"/>
+ <cmd id="presentationNext"
+ desc="Go to the next slide"/>
+ <cmd id="presentationPrev"
+ desc="Go to the previous slide"/>
+ <cmd id="presentationEdit"
+ desc="Edit this slide of the presentation"/>
+
+ <cmd id="presentationViewInBrowser"
+ menuLabel="_View in Browser"
+ desc="View the presentation in an external web browser"/>
+ <cmd id="presentationSaveAsStandalone"
+ menuLabel="_Save As Web Page..."
+ desc="Save the presentation as a standalone web page"/>
+ <cmd id="presentationPublishToRpubs"
+ menuLabel="Publish to RPubs..."
+ desc="Publish the presentation to RPubs"/>
+
+ <cmd id="tutorialFeedback"
+ desc="Provide feedback on this tutorial"/>
+
+ <cmd id="clearPresentationCache"
+ menuLabel="Clear Knitr Cache..."
+ desc="Clear knitr cache for this presentation"/>
+
+ <cmd id="historySendToSource"
+ menuLabel="Insert into _Source"
+ buttonLabel="To Source"
+ desc="Insert the selected commands into the current document (Shift+Enter)"/>
+ <cmd id="historySendToConsole"
+ menuLabel="Send to _Console"
+ buttonLabel="To Console"
+ desc="Send the selected commands to the R console (Enter)"/>
+ <cmd id="searchHistory"
+ menuLabel="Search History"
+ buttonLabel=""
+ desc="Search history for commands matching a pattern"/>
+ <cmd id="loadHistory"
+ menuLabel="_Load History..."
+ buttonLabel=""
+ desc="Load history from an existing file"/>
+ <cmd id="saveHistory"
+ menuLabel="Sa_ve History As..."
+ buttonLabel=""
+ desc="Save history into a file"/>
+ <cmd id="historyRemoveEntries"
+ menuLabel="_Remove Entries..."
+ buttonLabel=""
+ desc="Remove the selected history entries"/>
+ <cmd id="clearHistory"
+ menuLabel="Clear _All..."
+ buttonLabel=""
+ desc="Clear all history entries"/>
+ <cmd id="historyDismissResults"
+ label="Done" />
+ <cmd id="historyShowContext"
+ label="Show In Context" />
+ <cmd id="historyDismissContext"
+ label="« Back" />
+
+ <cmd id="nextPlot"
+ buttonLabel=""
+ menuLabel="_Next Plot"
+ desc="Next plot"/>
+ <cmd id="previousPlot"
+ buttonLabel=""
+ menuLabel="_Previous Plot"
+ desc="Previous plot"/>
+ <cmd id="savePlotAsImage"
+ menuLabel="Save Plot as _Image..."
+ desc="Save the current plot as an image file"/>
+ <cmd id="savePlotAsPdf"
+ menuLabel="Save Plot as P_DF..."
+ desc="Save the current plot as a PDF file"/>
+ <cmd id="copyPlotToClipboard"
+ menuLabel="Cop_y Plot to Clipboard..."
+ desc="Copy the current plot to the clipboard"/>
+ <cmd id ="zoomPlot"
+ menuLabel="_Zoom Plot..."
+ buttonLabel="Zoom"
+ desc="View a larger version of the plot in a new window"/>
+ <cmd id="removePlot"
+ buttonLabel=""
+ menuLabel="_Remove Plot..."
+ desc="Remove the current plot"/>
+ <cmd id="clearPlots"
+ buttonLabel="Clear All"
+ menuLabel="_Clear All..."
+ desc="Clear all Plots"/>
+ <cmd id="refreshPlot"
+ buttonLabel=""
+ menuLabel="Refresh"
+ desc="Refresh current plot"/>
+ <cmd id="showManipulator"
+ buttonLabel=""
+ menuLabel="Show _Manipulator"
+ desc="Show the manipulator for this plot"/>
+
+ <cmd id="clearWorkspace"
+ menuLabel="_Clear Workspace..."
+ buttonLabel="Clear"
+ desc="Clear objects from the workspace"/>
+ <cmd id="loadWorkspace"
+ menuLabel="_Load Workspace..."/>
+ <cmd id="saveWorkspace"
+ menuLabel="_Save Workspace As..."/>
+ <cmd id="importDatasetFromFile"
+ menuLabel="From _Text File..."/>
+ <cmd id="importDatasetFromURL"
+ menuLabel="From _Web URL..."/>
+
+ <cmd id="refreshWorkspace"
+ buttonLabel=""
+ menuLabel="Refresh"
+ desc="Refresh Workspace"/>
+
+ <cmd id="installPackage"
+ menuLabel="Install Pac_kages..."
+ buttonLabel="Install Packages"
+ desc="Install R packages" />
+
+ <cmd id="updatePackages"
+ menuLabel="Check for Package _Updates..."
+ buttonLabel="Check for Updates"
+ desc="Check for package updates" />
+
+ <cmd id ="refreshPackages"
+ desc="Refresh Package listing"/>
+
+ <cmd id="versionControlOptions"
+ menuLabel="_Options..."
+ desc="Configure version control options"/>
+
+ <cmd id="versionControlHelp"
+ menuLabel="_Using Version Control"
+ desc="Help on using version control with RStudio"/>
+
+ <cmd id="versionControlShowRsaKey"
+ menuLabel="Show Public Key..."
+ desc="Show RSA public key"/>
+
+ <cmd id="versionControlProjectSetup"
+ menuLabel="Project _Setup..."
+ desc="Setup version control for the current project"/>
+
+ <cmd id="showShellDialog"
+ menuLabel="_Shell..."
+ desc="Execute shell commands(s)"/>
+
+ <cmd id="macPreferences"
+ menuLabel="_Preferences..."/>
+
+ <cmd id="showOptions"
+ menuLabel="_Global Options..."/>
+
+ <cmd id="checkForUpdates"
+ menuLabel="Check for _Updates"
+ visible="false"/>
+
+ <cmd id="helpUsingRStudio"
+ menuLabel="RStudio _Docs"/>
+
+ <cmd id="helpKeyboardShortcuts"
+ menuLabel="_Keyboard Shortcuts" />
+
+ <cmd id="helpBack"
+ buttonLabel=""
+ desc="Previous topic"/>
+ <cmd id="helpForward"
+ buttonLabel=""
+ desc="Next topic"/>
+ <cmd id="helpHome"
+ menuLabel="R _Help"
+ buttonLabel=""
+ desc="Help home"/>
+ <cmd id="printHelp"
+ buttonLabel=""
+ desc="Print topic"/>
+ <cmd id="clearHelpHistory"
+ menuLabel="Clear history"
+ desc="Clear history"/>
+ <cmd id="helpPopout"
+ buttonLabel=""
+ desc="Show in new window"/>
+ <cmd id="refreshHelp"
+ menuLabel="Refresh"
+ desc="Refresh topic"/>
+
+ <cmd id="viewerPopout"
+ buttonLabel=""
+ desc="Show in new window"/>
+ <cmd id="viewerRefresh"
+ desc="Refresh page"/>
+ <cmd id="viewerStop"
+ buttonLabel=""
+ desc="Stop application"/>
+ <cmd id="viewerClear"
+ buttonLabel=""
+ desc="Clear viewer"/>
+
+ <cmd id="raiseException"
+ menuLabel="Raise Exception"/>
+ <cmd id="raiseException2"
+ menuLabel="Raise Exception JS"/>
+ <cmd id="showWarningBar"
+ menuLabel="Show warning bar"/>
+ <cmd id="showRequestLog"
+ menuLabel="_Request Log"/>
+ <cmd id="diagnosticsReport"
+ menuLabel="_Write Diagnostics Report"
+ visible="false"/>
+ <cmd id="logFocusedElement"
+ menuLabel="Log focused element"/>
+ <cmd id="debugDumpContents"
+ menuLabel="_Dump Editor Contents..."/>
+ <cmd id="debugImportDump"
+ menuLabel="_Import Editor Contents..."/>
+ <cmd id="refreshSuperDevMode"/>
+
+ <cmd id="quitSession"
+ menuLabel="_Quit RStudio..."/>
+
+ <cmd id="showAboutDialog"
+ menuLabel="_About RStudio"/>
+ <cmd id="showLogFiles"
+ menuLabel="_Show Log Files"
+ visible="false"/>
+ <cmd id="updateCredentials"
+ menuLabel="_Update Credentials"/>
+
+ <cmd id="rstudioSupport"
+ menuLabel="RStudio _Support" />
+
+ <cmd id="rstudioAgreement"
+ menuLabel="RStudio Agreement" />
+
+ <cmd id="rstudioLicense"
+ menuLabel="RStudio _License"
+ visible="false"/>
+
+ <cmd id="buildAll"
+ buttonLabel="Build & Reload"
+ menuLabel="_Build and Reload"
+ desc="Build and reload the package"/>
+
+ <cmd id="rebuildAll"
+ menuLabel="Clean and _Rebuild"
+ desc="Clean prevoius output and rebuild all"/>
+
+ <cmd id="cleanAll"
+ menuLabel="_Clean All"
+ buttonLabel="Clean"
+ desc="Clean all"/>
+
+ <cmd id="buildSourcePackage"
+ menuLabel="Build _Source Package"
+ desc="Build a source package"/>
+
+ <cmd id="buildBinaryPackage"
+ menuLabel="Build Binar_y Package"
+ desc="Build a binary package"/>
+
+ <cmd id="devtoolsLoadAll"
+ menuLabel="_Load All"
+ desc="Execute devtools::load_all"/>
+
+ <cmd id="roxygenizePackage"
+ menuLabel="_Document"
+ desc="Run roxygen2 to document this package"/>
+
+ <cmd id="checkPackage"
+ buttonLabel="Check"
+ menuLabel="_Check Package"
+ desc="R CMD check"/>
+
+ <cmd id="testPackage"
+ menuLabel="_Test Package"
+ desc="Run tests for package"/>
+
+ <cmd id="stopBuild"
+ menuLabel="Sto_p Build"
+ desc="Stop the current build"/>
+
+ <cmd id="buildToolsProjectSetup"
+ menuLabel="Configure Build _Tools..."
+ desc="Configure build tools"/>
+
+ <cmd id="refreshEnvironment"
+ menuLabel="_Refresh Environment"
+ desc="Refresh the list of objects in the environment"/>
+
+ <cmd id="undoDummy"
+ menuLabel="_Undo"/>
+ <cmd id="redoDummy"
+ menuLabel="Re_do"/>
+ <cmd id="cutDummy"
+ menuLabel="Cu_t"/>
+ <cmd id="copyDummy"
+ menuLabel="_Copy"/>
+ <cmd id="pasteDummy"
+ menuLabel="_Paste"/>
+
+ <cmd id="maximizeConsole"
+ menuLabel="Maximize Console"/>
+
+ <cmd id="debugBreakpoint"
+ menuLabel="Toggle _Breakpoint"
+ desc="Set or remove a breakpoint on the current line of code"/>
+ <cmd id="debugClearBreakpoints"
+ menuLabel="Clear _All Breakpoints..."
+ desc="Remove all the breakpoints in the current project"/>
+ <cmd id="debugContinue"
+ menuLabel="_Continue"
+ buttonLabel="Continue"
+ desc="Continue execution until the next breakpoint is encountered"/>
+ <cmd id="debugStop"
+ menuLabel="_Stop Debugging"
+ buttonLabel="Stop"
+ desc="Exit debug mode"/>
+ <cmd id="debugStep"
+ menuLabel="E_xecute Next Line"
+ buttonLabel="Next"
+ desc="Execute the next line of code"/>
+ <cmd id="debugStepInto"
+ menuLabel="Step _Into Function"
+ buttonLabel=""
+ desc="Step into the current function call"/>
+ <cmd id="debugFinish"
+ menuLabel="_Finish Function/Loop"
+ buttonLabel=""
+ desc="Execute the remainer of the current function or loop"/>
+ <cmd id="debugHelp"
+ menuLabel="Debugging _Help"
+ desc="Guide to debugging features"/>
+
+ <cmd id="errorsMessage"
+ menuLabel="_Message Only"
+ desc="Print the error message when an unhandled error occurs"
+ checkable="true"/>
+ <cmd id="errorsTraceback"
+ menuLabel="_Error Inspector"
+ desc="Show the error inspector when an unhandled error occurs"
+ checkable="true"/>
+ <cmd id="errorsBreak"
+ menuLabel="_Break in Code"
+ desc="Break when any unhandled error occurs"
+ checkable="true"/>
+
+ <cmd id="showProfiler"
+ menuLabel= "_Profiler"
+ desc="Show the R code profiler"/>
+ <cmd id="startProfiler"
+ menuLabel="Start Profiling"
+ desc="Start profiling R code"/>
+ <cmd id="stopProfiler"
+ menuLabel="Stop Profiling"
+ desc="Stop profiling R code"/>
+
+ <cmd id="reloadShinyApp"
+ menuLabel="Reload"
+ desc="Reload the Shiny application"/>
+ <cmd id="shinyRunInPane"
+ menuLabel="Run in Viewer Pane"
+ desc="Run the Shiny application in an RStudio pane"
+ checkable="true"/>
+ <cmd id="shinyRunInViewer"
+ menuLabel="Run in Window"
+ desc="Run the Shiny application in an RStudio viewer window"
+ checkable="true"/>
+ <cmd id="shinyRunInBrowser"
+ menuLabel="Run External"
+ desc="Run the Shiny application in the system's default Web browser"
+ checkable="true"/>
+
+ <cmd id="shinyAppsDeploy"
+ menuLabel="Deploy App..."
+ desc="Deploy an application to the ShinyApps service"
+ visible="false"/>
+ <cmd id="shinyAppsTerminate"
+ menuLabel="Terminate App..."
+ desc="Terminate a ShinyApps application"
+ visible="false"/>
+ <cmd id="shinyAppsManageAccounts"
+ menuLabel="Manage Accounts..."
+ desc="Connect or disconnect ShinyApps accounts"
+ visible="false"/>
+
+</commands>
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.java b/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.java
new file mode 100644
index 0000000..07d1eb0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/commands/Commands.java
@@ -0,0 +1,357 @@
+/*
+ * Commands.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.commands;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandBundle;
+import org.rstudio.core.client.command.MenuCallback;
+
+public abstract class
+ Commands extends CommandBundle
+{
+ public abstract void mainMenu(MenuCallback callback);
+
+ // Workbench
+ public abstract AppCommand setWorkingDir();
+
+ // Source
+ public abstract AppCommand newSourceDoc();
+ public abstract AppCommand newTextDoc();
+ public abstract AppCommand newCppDoc();
+ public abstract AppCommand newSweaveDoc();
+ public abstract AppCommand newRMarkdownDoc();
+ public abstract AppCommand newRHTMLDoc();
+ public abstract AppCommand newRDocumentationDoc();
+ public abstract AppCommand newRPresentationDoc();
+ public abstract AppCommand openSourceDoc();
+ public abstract AppCommand reopenSourceDocWithEncoding();
+ public abstract AppCommand saveSourceDoc();
+ public abstract AppCommand saveSourceDocAs();
+ public abstract AppCommand saveSourceDocWithEncoding();
+ public abstract AppCommand saveAllSourceDocs();
+ public abstract AppCommand closeSourceDoc();
+ public abstract AppCommand closeAllSourceDocs();
+ public abstract AppCommand executeAllCode();
+ public abstract AppCommand sourceFile();
+ public abstract AppCommand sourceActiveDocument();
+ public abstract AppCommand sourceActiveDocumentWithEcho();
+ public abstract AppCommand executeCode();
+ public abstract AppCommand executeCodeWithoutFocus();
+ public abstract AppCommand executeToCurrentLine();
+ public abstract AppCommand executeFromCurrentLine();
+ public abstract AppCommand executeCurrentFunction();
+ public abstract AppCommand executeCurrentSection();
+ public abstract AppCommand executeLastCode();
+ public abstract AppCommand insertChunk();
+ public abstract AppCommand insertSection();
+ public abstract AppCommand executeCurrentChunk();
+ public abstract AppCommand executeNextChunk();
+ public abstract AppCommand goToHelp();
+ public abstract AppCommand rcppHelp();
+ public abstract AppCommand goToFunctionDefinition();
+ public abstract AppCommand sourceNavigateBack();
+ public abstract AppCommand sourceNavigateForward();
+ public abstract AppCommand markdownHelp();
+ public abstract AppCommand usingRMarkdownHelp();
+ public abstract AppCommand authoringRPresentationsHelp();
+ public abstract AppCommand knitDocument();
+ public abstract AppCommand previewHTML();
+ public abstract AppCommand publishHTML();
+ public abstract AppCommand compilePDF();
+ public abstract AppCommand compileNotebook();
+ public abstract AppCommand synctexSearch();
+ public abstract AppCommand activateSource();
+ public abstract AppCommand printSourceDoc();
+ public abstract AppCommand vcsFileLog();
+ public abstract AppCommand vcsFileDiff();
+ public abstract AppCommand vcsFileRevert();
+ public abstract AppCommand popoutDoc();
+ public abstract AppCommand findReplace();
+ public abstract AppCommand findNext();
+ public abstract AppCommand findPrevious();
+ public abstract AppCommand findFromSelection();
+ public abstract AppCommand replaceAndFind();
+ public abstract AppCommand findInFiles();
+ public abstract AppCommand fold();
+ public abstract AppCommand unfold();
+ public abstract AppCommand foldAll();
+ public abstract AppCommand unfoldAll();
+ public abstract AppCommand jumpToMatching();
+ public abstract AppCommand extractFunction();
+ public abstract AppCommand extractLocalVariable();
+ public abstract AppCommand commentUncomment();
+ public abstract AppCommand reindent();
+ public abstract AppCommand reflowComment();
+ public abstract AppCommand setWorkingDirToActiveDoc();
+ public abstract AppCommand codeCompletion();
+
+ // Projects
+ public abstract AppCommand newProject();
+ public abstract AppCommand openProject();
+ public abstract AppCommand openProjectInNewWindow();
+ public abstract AppCommand projectMru0();
+ public abstract AppCommand projectMru1();
+ public abstract AppCommand projectMru2();
+ public abstract AppCommand projectMru3();
+ public abstract AppCommand projectMru4();
+ public abstract AppCommand projectMru5();
+ public abstract AppCommand projectMru6();
+ public abstract AppCommand projectMru7();
+ public abstract AppCommand projectMru8();
+ public abstract AppCommand projectMru9();
+ public abstract AppCommand clearRecentProjects();
+ public abstract AppCommand closeProject();
+ public abstract AppCommand projectOptions();
+ public abstract AppCommand projectSweaveOptions();
+
+ // Console
+ public abstract AppCommand consoleClear();
+ public abstract AppCommand interruptR();
+ public abstract AppCommand restartR();
+ public abstract AppCommand terminateR();
+ public abstract AppCommand activateConsole();
+
+ // Files
+ public abstract AppCommand newFolder();
+ public abstract AppCommand uploadFile();
+ public abstract AppCommand copyFile();
+ public abstract AppCommand moveFiles();
+ public abstract AppCommand exportFiles();
+ public abstract AppCommand renameFile();
+ public abstract AppCommand deleteFiles();
+ public abstract AppCommand refreshFiles();
+ public abstract AppCommand activateFiles();
+ public abstract AppCommand goToWorkingDir();
+ public abstract AppCommand setAsWorkingDir();
+ public abstract AppCommand setWorkingDirToFilesPane();
+ public abstract AppCommand showFolder();
+
+ // VCS
+ public abstract AppCommand vcsDiff();
+ public abstract AppCommand vcsCommit();
+ public abstract AppCommand vcsRevert();
+ public abstract AppCommand vcsViewOnGitHub();
+ public abstract AppCommand vcsBlameOnGitHub();
+ public abstract AppCommand vcsShowHistory();
+ public abstract AppCommand vcsRefresh();
+ public abstract AppCommand vcsRefreshNoError();
+ public abstract AppCommand vcsOpen();
+ public abstract AppCommand vcsIgnore();
+ public abstract AppCommand vcsPull();
+ public abstract AppCommand vcsPush();
+ public abstract AppCommand vcsCleanup();
+ public abstract AppCommand vcsAddFiles();
+ public abstract AppCommand vcsRemoveFiles();
+ public abstract AppCommand activateVcs();
+ public abstract AppCommand vcsResolve();
+
+ // PDF
+ public abstract AppCommand showPdfExternal();
+
+ // HTML preview
+ public abstract AppCommand openHtmlExternal();
+ public abstract AppCommand saveHtmlPreviewAsLocalFile();
+ public abstract AppCommand saveHtmlPreviewAs();
+ public abstract AppCommand refreshHtmlPreview();
+ public abstract AppCommand showHtmlPreviewLog();
+
+ // Presentation
+ public abstract AppCommand refreshPresentation();
+ public abstract AppCommand presentationFullscreen();
+ public abstract AppCommand presentationHome();
+ public abstract AppCommand presentationNext();
+ public abstract AppCommand presentationPrev();
+ public abstract AppCommand presentationEdit();
+ public abstract AppCommand presentationViewInBrowser();
+ public abstract AppCommand presentationSaveAsStandalone();
+ public abstract AppCommand presentationPublishToRpubs();
+ public abstract AppCommand activatePresentation();
+ public abstract AppCommand tutorialFeedback();
+ public abstract AppCommand clearPresentationCache();
+
+ // View
+ public abstract AppCommand showToolbar();
+ public abstract AppCommand hideToolbar();
+ public abstract AppCommand zoomActualSize();
+ public abstract AppCommand zoomIn();
+ public abstract AppCommand zoomOut();
+ public abstract AppCommand jumpTo();
+ public abstract AppCommand goToFileFunction();
+ public abstract AppCommand switchToTab();
+ public abstract AppCommand previousTab();
+ public abstract AppCommand nextTab();
+ public abstract AppCommand firstTab();
+ public abstract AppCommand lastTab();
+ public abstract AppCommand goToLine();
+ public abstract AppCommand toggleFullScreen();
+
+ // History
+ public abstract AppCommand historySendToSource();
+ public abstract AppCommand historySendToConsole();
+ public abstract AppCommand searchHistory();
+ public abstract AppCommand loadHistory();
+ public abstract AppCommand saveHistory();
+ public abstract AppCommand historyRemoveEntries();
+ public abstract AppCommand clearHistory();
+ public abstract AppCommand historyDismissResults();
+ public abstract AppCommand historyShowContext();
+ public abstract AppCommand historyDismissContext();
+ public abstract AppCommand activateHistory();
+
+ // Workspace
+ public abstract AppCommand clearWorkspace();
+ public abstract AppCommand refreshWorkspace();
+ public abstract AppCommand saveWorkspace();
+ public abstract AppCommand loadWorkspace();
+ public abstract AppCommand importDatasetFromFile();
+ public abstract AppCommand importDatasetFromURL();
+
+ // Environment
+ public abstract AppCommand activateEnvironment();
+ public abstract AppCommand refreshEnvironment();
+
+ // Plots
+ public abstract AppCommand nextPlot();
+ public abstract AppCommand previousPlot();
+ public abstract AppCommand savePlotAsImage();
+ public abstract AppCommand savePlotAsPdf();
+ public abstract AppCommand copyPlotToClipboard();
+ public abstract AppCommand zoomPlot();
+ public abstract AppCommand removePlot();
+ public abstract AppCommand clearPlots();
+ public abstract AppCommand refreshPlot();
+ public abstract AppCommand activatePlots();
+ public abstract AppCommand showManipulator();
+
+ // Packages
+ public abstract AppCommand installPackage();
+ public abstract AppCommand updatePackages();
+ public abstract AppCommand refreshPackages();
+ public abstract AppCommand activatePackages();
+
+ // Version control
+ public abstract AppCommand versionControlHelp();
+ public abstract AppCommand versionControlShowRsaKey();
+ public abstract AppCommand versionControlProjectSetup();
+
+ // Profiler
+ public abstract AppCommand showProfiler();
+ public abstract AppCommand startProfiler();
+ public abstract AppCommand stopProfiler();
+
+ // Tools
+ public abstract AppCommand showShellDialog();
+ public abstract AppCommand macPreferences();
+ public abstract AppCommand showOptions();
+
+ // Help
+ public abstract AppCommand helpBack();
+ public abstract AppCommand helpForward();
+ public abstract AppCommand helpHome();
+ public abstract AppCommand printHelp();
+ public abstract AppCommand clearHelpHistory();
+ public abstract AppCommand helpPopout();
+ public abstract AppCommand refreshHelp();
+ public abstract AppCommand raiseException();
+ public abstract AppCommand raiseException2();
+ public abstract AppCommand activateHelp();
+ public abstract AppCommand showAboutDialog();
+ public abstract AppCommand checkForUpdates();
+ public abstract AppCommand helpUsingRStudio();
+ public abstract AppCommand helpKeyboardShortcuts();
+ public abstract AppCommand showRequestLog();
+ public abstract AppCommand logFocusedElement();
+ public abstract AppCommand debugDumpContents();
+ public abstract AppCommand debugImportDump();
+ public abstract AppCommand refreshSuperDevMode();
+ public abstract AppCommand viewShortcuts();
+
+ // Viewer
+ public abstract AppCommand viewerPopout();
+ public abstract AppCommand viewerRefresh();
+ public abstract AppCommand viewerStop();
+ public abstract AppCommand viewerClear();
+
+ // Application
+ public abstract AppCommand quitSession();
+ public abstract AppCommand updateCredentials();
+ public abstract AppCommand diagnosticsReport();
+ public abstract AppCommand showLogFiles();
+ public abstract AppCommand rstudioSupport();
+ public abstract AppCommand rstudioAgreement();
+
+ public abstract AppCommand showWarningBar();
+
+ // Build
+ public abstract AppCommand buildAll();
+ public abstract AppCommand devtoolsLoadAll();
+ public abstract AppCommand rebuildAll();
+ public abstract AppCommand cleanAll();
+ public abstract AppCommand buildSourcePackage();
+ public abstract AppCommand buildBinaryPackage();
+ public abstract AppCommand roxygenizePackage();
+ public abstract AppCommand checkPackage();
+ public abstract AppCommand testPackage();
+ public abstract AppCommand stopBuild();
+ public abstract AppCommand buildToolsProjectSetup();
+ public abstract AppCommand activateBuild();
+
+ // Clipboard placeholders
+ public abstract AppCommand undoDummy();
+ public abstract AppCommand redoDummy();
+ public abstract AppCommand cutDummy();
+ public abstract AppCommand copyDummy();
+ public abstract AppCommand pasteDummy();
+
+ public abstract AppCommand mru0();
+ public abstract AppCommand mru1();
+ public abstract AppCommand mru2();
+ public abstract AppCommand mru3();
+ public abstract AppCommand mru4();
+ public abstract AppCommand mru5();
+ public abstract AppCommand mru6();
+ public abstract AppCommand mru7();
+ public abstract AppCommand mru8();
+ public abstract AppCommand mru9();
+ public abstract AppCommand clearRecentFiles();
+
+ // Debugging
+ public abstract AppCommand debugBreakpoint();
+ public abstract AppCommand debugClearBreakpoints();
+ public abstract AppCommand debugContinue();
+ public abstract AppCommand debugStop();
+ public abstract AppCommand debugStep();
+ public abstract AppCommand debugStepInto();
+ public abstract AppCommand debugFinish();
+ public abstract AppCommand debugHelp();
+ public abstract AppCommand errorsMessage();
+ public abstract AppCommand errorsTraceback();
+ public abstract AppCommand errorsBreak();
+
+ // Shiny IDE features
+ public abstract AppCommand reloadShinyApp();
+ public abstract AppCommand shinyRunInPane();
+ public abstract AppCommand shinyRunInViewer();
+ public abstract AppCommand shinyRunInBrowser();
+
+ // ShinyApps connectivity
+ public abstract AppCommand shinyAppsDeploy();
+ public abstract AppCommand shinyAppsManageAccounts();
+ public abstract AppCommand shinyAppsTerminate();
+
+ // Other
+ public abstract AppCommand checkSpelling();
+ public abstract AppCommand maximizeConsole();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildAll.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildAll.png
new file mode 100644
index 0000000..0e7a12c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildAll.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildToolsProjectSetup.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildToolsProjectSetup.png
new file mode 100644
index 0000000..3405d7f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/buildToolsProjectSetup.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkPackage.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkPackage.png
new file mode 100644
index 0000000..8c5a72c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkPackage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkSpelling.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkSpelling.png
new file mode 100644
index 0000000..2bf0547
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/checkSpelling.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearHistory.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearHistory.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearHistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPlots.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPlots.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPlots.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPresentationCache.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPresentationCache.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearPresentationCache.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearWorkspace.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearWorkspace.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/clearWorkspace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/compileNotebook.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/compileNotebook.png
new file mode 100644
index 0000000..02a9a7c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/compileNotebook.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/compilePDF.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/compilePDF.png
new file mode 100644
index 0000000..37f3401
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/compilePDF.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugBreakpoint.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugBreakpoint.png
new file mode 100644
index 0000000..60538a4
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugBreakpoint.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugClearBreakpoints.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugClearBreakpoints.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugClearBreakpoints.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugContinue.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugContinue.png
new file mode 100644
index 0000000..5924863
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugContinue.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugFinish.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugFinish.png
new file mode 100644
index 0000000..dd166fc
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugFinish.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugHelp.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugHelp.png
new file mode 100644
index 0000000..844d378
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugHelp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStep.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStep.png
new file mode 100644
index 0000000..b4a5ed5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStep.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStepInto.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStepInto.png
new file mode 100644
index 0000000..51e2841
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStepInto.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStop.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStop.png
new file mode 100644
index 0000000..2f32e4b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/debugStop.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/deleteFiles.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/deleteFiles.png
new file mode 100644
index 0000000..1822dda
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/deleteFiles.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/devtoolsLoadAll.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/devtoolsLoadAll.png
new file mode 100644
index 0000000..0df48ee
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/devtoolsLoadAll.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/editVariable.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/editVariable.png
new file mode 100644
index 0000000..df06a15
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/editVariable.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsBreak.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsBreak.png
new file mode 100644
index 0000000..c628365
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsBreak.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsMessage.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsMessage.png
new file mode 100644
index 0000000..084b437
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsMessage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsTraceback.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsTraceback.png
new file mode 100644
index 0000000..ac8edf3
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/errorsTraceback.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeCode.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeCode.png
new file mode 100755
index 0000000..15ff10e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeCode.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeLastCode.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeLastCode.png
new file mode 100755
index 0000000..613a211
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/executeLastCode.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/find.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/find.png
new file mode 100644
index 0000000..6adab66
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/find.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/findReplace.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/findReplace.png
new file mode 100644
index 0000000..6adab66
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/findReplace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/goToWorkingDir.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/goToWorkingDir.png
new file mode 100644
index 0000000..4178a0b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/goToWorkingDir.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpBack.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpBack.png
new file mode 100644
index 0000000..905c109
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpBack.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpForward.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpForward.png
new file mode 100644
index 0000000..871f326
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpForward.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpHome.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpHome.png
new file mode 100644
index 0000000..206be5f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpHome.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpPopout.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpPopout.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/helpPopout.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissContext.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissContext.png
new file mode 100644
index 0000000..fc2713a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissContext.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissResults.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissResults.png
new file mode 100644
index 0000000..fc2713a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyDismissResults.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyRemoveEntries.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyRemoveEntries.png
new file mode 100644
index 0000000..1822dda
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyRemoveEntries.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToConsole.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToConsole.png
new file mode 100644
index 0000000..b23cefb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToConsole.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToSource.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToSource.png
new file mode 100644
index 0000000..b90fb14
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historySendToSource.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyShowContext.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyShowContext.png
new file mode 100644
index 0000000..8f0cd5c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/historyShowContext.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertChunk.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertChunk.png
new file mode 100755
index 0000000..cd115d3
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertChunk.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertSection.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertSection.png
new file mode 100644
index 0000000..e358cc7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/insertSection.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/installPackage.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/installPackage.png
new file mode 100644
index 0000000..f1c2248
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/installPackage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/interruptR.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/interruptR.png
new file mode 100644
index 0000000..7faab56
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/interruptR.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/knitDocument.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/knitDocument.png
new file mode 100644
index 0000000..fa1e993
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/knitDocument.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadHistory.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadHistory.png
new file mode 100644
index 0000000..564828e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadHistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadWorkspace.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadWorkspace.png
new file mode 100644
index 0000000..564828e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/loadWorkspace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newCppDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newCppDoc.png
new file mode 100644
index 0000000..75a5a15
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newCppDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newFolder.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newFolder.png
new file mode 100644
index 0000000..7548194
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newFolder.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newProject.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newProject.png
new file mode 100755
index 0000000..1de45d7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newProject.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRDocumentationDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRDocumentationDoc.png
new file mode 100644
index 0000000..957d47e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRDocumentationDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRHTMLDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRHTMLDoc.png
new file mode 100644
index 0000000..1fabb60
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRHTMLDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRMarkdownDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRMarkdownDoc.png
new file mode 100644
index 0000000..2ba2caf
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRMarkdownDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRPresentationDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRPresentationDoc.png
new file mode 100644
index 0000000..70b79c8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newRPresentationDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSourceDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSourceDoc.png
new file mode 100644
index 0000000..2f6e4aa
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSourceDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSweaveDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSweaveDoc.png
new file mode 100755
index 0000000..c07c2be
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newSweaveDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/newTextDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newTextDoc.png
new file mode 100644
index 0000000..1361a4e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/newTextDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/nextPlot.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/nextPlot.png
new file mode 100644
index 0000000..871f326
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/nextPlot.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/openHtmlExternal.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openHtmlExternal.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openHtmlExternal.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/openProject.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openProject.png
new file mode 100755
index 0000000..3f2fafb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openProject.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/openSourceDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openSourceDoc.png
new file mode 100644
index 0000000..564828e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/openSourceDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/popoutDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/popoutDoc.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/popoutDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationEdit.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationEdit.png
new file mode 100644
index 0000000..df06a15
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationEdit.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationFullscreen.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationFullscreen.png
new file mode 100644
index 0000000..a83fe55
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationFullscreen.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationHome.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationHome.png
new file mode 100644
index 0000000..206be5f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationHome.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationNext.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationNext.png
new file mode 100644
index 0000000..871f326
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationNext.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPrev.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPrev.png
new file mode 100644
index 0000000..905c109
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPrev.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPublishToRpubs.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPublishToRpubs.png
new file mode 100644
index 0000000..a1d6102
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationPublishToRpubs.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationSaveAsStandalone.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationSaveAsStandalone.png
new file mode 100644
index 0000000..7bb40b5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationSaveAsStandalone.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationViewInBrowser.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationViewInBrowser.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/presentationViewInBrowser.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/previewHTML.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/previewHTML.png
new file mode 100644
index 0000000..e9b1403
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/previewHTML.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/previousPlot.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/previousPlot.png
new file mode 100644
index 0000000..905c109
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/previousPlot.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHelp.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHelp.png
new file mode 100644
index 0000000..0db056f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHelp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHtmlPreview.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHtmlPreview.png
new file mode 100644
index 0000000..0db056f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printHtmlPreview.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/printSourceDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printSourceDoc.png
new file mode 100644
index 0000000..0db056f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/printSourceDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/publishHTML.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/publishHTML.png
new file mode 100644
index 0000000..a1d6102
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/publishHTML.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/quitSession.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/quitSession.png
new file mode 100644
index 0000000..89c39a0
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/quitSession.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/rcppHelp.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/rcppHelp.png
new file mode 100644
index 0000000..844d378
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/rcppHelp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshEnvironment.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshEnvironment.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshEnvironment.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshFiles.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshFiles.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshFiles.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHelp.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHelp.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHelp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHtmlPreview.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHtmlPreview.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshHtmlPreview.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPackages.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPackages.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPackages.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPlot.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPlot.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPlot.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPresentation.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPresentation.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshPresentation.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshWorkspace.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshWorkspace.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/refreshWorkspace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/reloadShinyApp.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/reloadShinyApp.png
new file mode 100644
index 0000000..4eafd81
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/reloadShinyApp.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/removePlot.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/removePlot.png
new file mode 100644
index 0000000..1822dda
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/removePlot.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/renameFile.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/renameFile.png
new file mode 100644
index 0000000..45bd12f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/renameFile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/restartR.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/restartR.png
new file mode 100644
index 0000000..08c6f6a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/restartR.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveAllSourceDocs.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveAllSourceDocs.png
new file mode 100644
index 0000000..f052600
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveAllSourceDocs.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHistory.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHistory.png
new file mode 100644
index 0000000..7bb40b5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAs.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAs.png
new file mode 100644
index 0000000..7bb40b5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAs.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAsLocalFile.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAsLocalFile.png
new file mode 100644
index 0000000..d883882
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveHtmlPreviewAsLocalFile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsImage.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsImage.png
new file mode 100644
index 0000000..af44346
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsImage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsPdf.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsPdf.png
new file mode 100644
index 0000000..37f3401
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/savePlotAsPdf.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveSourceDoc.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveSourceDoc.png
new file mode 100644
index 0000000..7bb40b5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveSourceDoc.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveWorkspace.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveWorkspace.png
new file mode 100644
index 0000000..7bb40b5
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/saveWorkspace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/searchHistory.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/searchHistory.png
new file mode 100644
index 0000000..6adab66
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/searchHistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/shinyAppsDeploy.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/shinyAppsDeploy.png
new file mode 100644
index 0000000..653198d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/shinyAppsDeploy.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/showHtmlPreviewLog.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/showHtmlPreviewLog.png
new file mode 100644
index 0000000..5f6afa1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/showHtmlPreviewLog.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/showPdfExternal.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/showPdfExternal.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/showPdfExternal.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceActiveDocument.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceActiveDocument.png
new file mode 100644
index 0000000..896595b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceActiveDocument.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateBack.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateBack.png
new file mode 100755
index 0000000..7d23fd4
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateBack.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateForward.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateForward.png
new file mode 100755
index 0000000..7bdfc8b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/sourceNavigateForward.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/startProfiler.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/startProfiler.png
new file mode 100644
index 0000000..5924863
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/startProfiler.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopBuild.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopBuild.png
new file mode 100644
index 0000000..7faab56
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopBuild.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopProfiler.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopProfiler.png
new file mode 100644
index 0000000..2f32e4b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/stopProfiler.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/synctexSearch.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/synctexSearch.png
new file mode 100644
index 0000000..9ce6d05
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/synctexSearch.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/tutorialFeedback.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/tutorialFeedback.png
new file mode 100644
index 0000000..0967ae7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/tutorialFeedback.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/updatePackages.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/updatePackages.png
new file mode 100755
index 0000000..ac6852b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/updatePackages.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/uploadFile.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/uploadFile.png
new file mode 100644
index 0000000..9aaa534
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/uploadFile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsAddFiles.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsAddFiles.png
new file mode 100755
index 0000000..c209ce6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsAddFiles.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsCommit.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsCommit.png
new file mode 100755
index 0000000..ecdca86
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsCommit.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsDiff.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsDiff.png
new file mode 100755
index 0000000..c6290eb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsDiff.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsFileDiff.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsFileDiff.png
new file mode 100755
index 0000000..c6290eb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsFileDiff.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsIgnore.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsIgnore.png
new file mode 100644
index 0000000..c39060d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsIgnore.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPull.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPull.png
new file mode 100644
index 0000000..d883882
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPull.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPush.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPush.png
new file mode 100644
index 0000000..c5c663f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsPush.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRefresh.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRefresh.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRefresh.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRemoveFiles.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRemoveFiles.png
new file mode 100755
index 0000000..f7ff70d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRemoveFiles.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsResolve.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsResolve.png
new file mode 100644
index 0000000..6752341
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsResolve.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRevert.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRevert.png
new file mode 100755
index 0000000..7163e2a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsRevert.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsShowHistory.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsShowHistory.png
new file mode 100755
index 0000000..e4c1261
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsShowHistory.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsUnstage.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsUnstage.png
new file mode 100644
index 0000000..871f326
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsUnstage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsViewOnGitHub.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsViewOnGitHub.png
new file mode 100644
index 0000000..b8cf2db
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/vcsViewOnGitHub.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/versionControlProjectSetup.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/versionControlProjectSetup.png
new file mode 100644
index 0000000..8547bb1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/versionControlProjectSetup.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerClear.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerClear.png
new file mode 100644
index 0000000..2b0af75
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerClear.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerPopout.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerPopout.png
new file mode 100644
index 0000000..a443655
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerPopout.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerRefresh.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerRefresh.png
new file mode 100644
index 0000000..c664695
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerRefresh.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerStop.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerStop.png
new file mode 100644
index 0000000..7faab56
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/viewerStop.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/commands/zoomPlot.png b/src/gwt/src/org/rstudio/studio/client/workbench/commands/zoomPlot.png
new file mode 100644
index 0000000..b45f9ec
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/commands/zoomPlot.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneEvent.java
new file mode 100644
index 0000000..dc47534
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ActivatePaneEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class ActivatePaneEvent extends GwtEvent<ActivatePaneHandler>
+{
+ public static final GwtEvent.Type<ActivatePaneHandler> TYPE =
+ new GwtEvent.Type<ActivatePaneHandler>();
+
+ public ActivatePaneEvent(String pane)
+ {
+ pane_ = pane;
+ }
+
+ public String getPane()
+ {
+ return pane_;
+ }
+
+ @Override
+ protected void dispatch(ActivatePaneHandler handler)
+ {
+ handler.onActivatePane(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ActivatePaneHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private String pane_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneHandler.java
new file mode 100644
index 0000000..8d387f3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ActivatePaneHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ActivatePaneHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ActivatePaneHandler extends EventHandler
+{
+ void onActivatePane(ActivatePaneEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlEvent.java
new file mode 100644
index 0000000..3844c7c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlEvent.java
@@ -0,0 +1,48 @@
+/*
+ * BrowseUrlEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.model.BrowseUrlInfo;
+
+public class BrowseUrlEvent extends GwtEvent<BrowseUrlHandler>
+{
+ public static final GwtEvent.Type<BrowseUrlHandler> TYPE =
+ new GwtEvent.Type<BrowseUrlHandler>();
+
+ public BrowseUrlEvent(BrowseUrlInfo urlInfo)
+ {
+ urlInfo_ = urlInfo;
+ }
+
+ public BrowseUrlInfo getUrlInfo()
+ {
+ return urlInfo_;
+ }
+
+ @Override
+ protected void dispatch(BrowseUrlHandler handler)
+ {
+ handler.onBrowseUrl(this);
+ }
+
+ @Override
+ public GwtEvent.Type<BrowseUrlHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final BrowseUrlInfo urlInfo_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlHandler.java
new file mode 100644
index 0000000..c88b943
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/BrowseUrlHandler.java
@@ -0,0 +1,22 @@
+/*
+ * BrowseUrlHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface BrowseUrlHandler extends EventHandler
+{
+ void onBrowseUrl(BrowseUrlEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyEvent.java
new file mode 100644
index 0000000..6a4baf5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyEvent.java
@@ -0,0 +1,47 @@
+/*
+ * BusyEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BusyEvent extends GwtEvent<BusyHandler>
+{
+ public static final GwtEvent.Type<BusyHandler> TYPE =
+ new GwtEvent.Type<BusyHandler>();
+
+ public BusyEvent(boolean isBusy)
+ {
+ isBusy_ = isBusy;
+ }
+
+ public boolean isBusy()
+ {
+ return isBusy_;
+ }
+
+ @Override
+ protected void dispatch(BusyHandler handler)
+ {
+ handler.onBusy(this);
+ }
+
+ @Override
+ public GwtEvent.Type<BusyHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final boolean isBusy_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyHandler.java
new file mode 100644
index 0000000..7b4695e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/BusyHandler.java
@@ -0,0 +1,22 @@
+/*
+ * BusyHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface BusyHandler extends EventHandler
+{
+ void onBusy(BusyEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveEvent.java
new file mode 100644
index 0000000..5721096
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveEvent.java
@@ -0,0 +1,64 @@
+/*
+ * LastChanceSaveEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.Barrier;
+import org.rstudio.core.client.Barrier.Token;
+
+/**
+ * This event provides components a chance to save their state before the
+ * session is terminated. It applies only for Desktop mode, since we can't
+ * stop web browser windows from closing.
+ *
+ * To use, subscribe to this event on the global event bus, and when it
+ * fires call acquire() to get a token. The process will not quit until
+ * all acquired tokens are released by calling release().
+ *
+ * IMPORTANT NOTE: You MUST call release on the token eventually--this
+ * mechanism is not intended to provide quit cancellation functionality,
+ * but only to momentarily delay quitting while state is saved!
+ */
+public class LastChanceSaveEvent extends GwtEvent<LastChanceSaveHandler>
+{
+ public static final Type<LastChanceSaveHandler> TYPE = new Type<LastChanceSaveHandler>();
+
+ public LastChanceSaveEvent(Barrier barrier)
+ {
+ barrier_ = barrier;
+ }
+
+ /**
+ * Delay quitting until the returned barrier token is released.
+ */
+ public Token acquire()
+ {
+ return barrier_.acquire();
+ }
+
+ @Override
+ public Type<LastChanceSaveHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(LastChanceSaveHandler handler)
+ {
+ handler.onLastChanceSave(this);
+ }
+
+ private final Barrier barrier_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveHandler.java
new file mode 100644
index 0000000..2ac1f1c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/LastChanceSaveHandler.java
@@ -0,0 +1,22 @@
+/*
+ * LastChanceSaveHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface LastChanceSaveHandler extends EventHandler
+{
+ void onLastChanceSave(LastChanceSaveEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedEvent.java
new file mode 100644
index 0000000..6a81043
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedEvent.java
@@ -0,0 +1,69 @@
+/*
+ * ListChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.js.JsObject;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ListChangedEvent extends GwtEvent<ListChangedHandler>
+{
+ public static final GwtEvent.Type<ListChangedHandler> TYPE =
+ new GwtEvent.Type<ListChangedHandler>();
+
+ public ListChangedEvent(String name, ArrayList<String> list)
+ {
+ name_ = name;
+ list_ = list;
+ }
+
+ public ListChangedEvent(JsObject eventData)
+ {
+ name_ = eventData.getString("name");
+
+ JsArrayString list = eventData.getObject("list");
+ list_ = new ArrayList<String>();
+ for (int i=0; i<list.length(); i++)
+ list_.add(list.get(i));
+ }
+
+ public String getName()
+ {
+ return name_;
+ }
+
+ public ArrayList<String> getList()
+ {
+ return list_;
+ }
+
+ @Override
+ protected void dispatch(ListChangedHandler handler)
+ {
+ handler.onListChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ListChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String name_;
+ private final ArrayList<String> list_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedHandler.java
new file mode 100644
index 0000000..8fc3687
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ListChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ListChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ListChangedHandler extends EventHandler
+{
+ void onListChanged(ListChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateEvent.java
new file mode 100644
index 0000000..f810d67
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateEvent.java
@@ -0,0 +1,39 @@
+/*
+ * PushClientStateEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PushClientStateEvent extends GwtEvent<PushClientStateHandler>
+{
+ public static final GwtEvent.Type<PushClientStateHandler> TYPE =
+ new GwtEvent.Type<PushClientStateHandler>();
+
+ public PushClientStateEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(PushClientStateHandler handler)
+ {
+ handler.onPushClientState(this);
+ }
+
+ @Override
+ public GwtEvent.Type<PushClientStateHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateHandler.java
new file mode 100644
index 0000000..4ce7608
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/PushClientStateHandler.java
@@ -0,0 +1,22 @@
+/*
+ * PushClientStateHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface PushClientStateHandler extends EventHandler
+{
+ void onPushClientState(PushClientStateEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusEvent.java
new file mode 100644
index 0000000..d15dbb1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusEvent.java
@@ -0,0 +1,48 @@
+/*
+ * QuotaStatusEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.model.QuotaStatus;
+
+public class QuotaStatusEvent extends GwtEvent<QuotaStatusHandler>
+{
+ public static final GwtEvent.Type<QuotaStatusHandler> TYPE =
+ new GwtEvent.Type<QuotaStatusHandler>();
+
+ public QuotaStatusEvent(QuotaStatus quotaStatus)
+ {
+ quotaStatus_ = quotaStatus;
+ }
+
+ public QuotaStatus getQuotaStatus()
+ {
+ return quotaStatus_;
+ }
+
+ @Override
+ protected void dispatch(QuotaStatusHandler handler)
+ {
+ handler.onQuotaStatus(this);
+ }
+
+ @Override
+ public GwtEvent.Type<QuotaStatusHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final QuotaStatus quotaStatus_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusHandler.java
new file mode 100644
index 0000000..da5be3f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/QuotaStatusHandler.java
@@ -0,0 +1,22 @@
+/*
+ * QuotaStatusHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface QuotaStatusHandler extends EventHandler
+{
+ void onQuotaStatus(QuotaStatusEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateEvent.java
new file mode 100644
index 0000000..d5bb47f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateEvent.java
@@ -0,0 +1,53 @@
+/*
+ * SaveClientStateEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.model.ClientState;
+
+public class SaveClientStateEvent extends GwtEvent<SaveClientStateHandler>
+{
+ public static final GwtEvent.Type<SaveClientStateHandler> TYPE =
+ new GwtEvent.Type<SaveClientStateHandler>();
+
+ public SaveClientStateEvent()
+ {
+ this(ClientState.create());
+ }
+
+ public SaveClientStateEvent(ClientState state)
+ {
+ state_ = state;
+ }
+
+ public ClientState getState()
+ {
+ return state_;
+ }
+
+ @Override
+ protected void dispatch(SaveClientStateHandler handler)
+ {
+ handler.onSaveClientState(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SaveClientStateHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final ClientState state_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateHandler.java
new file mode 100644
index 0000000..16a3dc4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/SaveClientStateHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SaveClientStateHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SaveClientStateHandler extends EventHandler
+{
+ void onSaveClientState(SaveClientStateEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitEvent.java
new file mode 100644
index 0000000..52f0524
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitEvent.java
@@ -0,0 +1,39 @@
+/*
+ * SessionInitEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SessionInitEvent extends GwtEvent<SessionInitHandler>
+{
+ public static final GwtEvent.Type<SessionInitHandler> TYPE =
+ new GwtEvent.Type<SessionInitHandler>();
+
+ public SessionInitEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(SessionInitHandler handler)
+ {
+ handler.onSessionInit(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SessionInitHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitHandler.java
new file mode 100644
index 0000000..8ef9faa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/SessionInitHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SessionInitHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SessionInitHandler extends EventHandler
+{
+ void onSessionInit(SessionInitEvent sie);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageEvent.java
new file mode 100644
index 0000000..95e7c08
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageEvent.java
@@ -0,0 +1,48 @@
+/*
+ * ShowErrorMessageEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.model.ErrorMessage;
+
+public class ShowErrorMessageEvent extends GwtEvent<ShowErrorMessageHandler>
+{
+ public static final GwtEvent.Type<ShowErrorMessageHandler> TYPE =
+ new GwtEvent.Type<ShowErrorMessageHandler>();
+
+ public ShowErrorMessageEvent(ErrorMessage message)
+ {
+ errorMessage_ = message;
+ }
+
+ public ErrorMessage getErrorMessage()
+ {
+ return errorMessage_;
+ }
+
+ @Override
+ protected void dispatch(ShowErrorMessageHandler handler)
+ {
+ handler.onShowErrorMessage(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowErrorMessageHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private ErrorMessage errorMessage_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageHandler.java
new file mode 100644
index 0000000..ba03704
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowErrorMessageHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowErrorMessageHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowErrorMessageHandler extends EventHandler
+{
+ void onShowErrorMessage(ShowErrorMessageEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarEvent.java
new file mode 100644
index 0000000..136f257
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarEvent.java
@@ -0,0 +1,50 @@
+/*
+ * ShowWarningBarEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import org.rstudio.studio.client.workbench.model.WarningBarMessage;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+
+public class ShowWarningBarEvent extends GwtEvent<ShowWarningBarHandler>
+{
+ public static final GwtEvent.Type<ShowWarningBarHandler> TYPE =
+ new GwtEvent.Type<ShowWarningBarHandler>();
+
+ public ShowWarningBarEvent(WarningBarMessage message)
+ {
+ message_ = message;
+ }
+
+ public WarningBarMessage getMessage()
+ {
+ return message_;
+ }
+
+ @Override
+ protected void dispatch(ShowWarningBarHandler handler)
+ {
+ handler.onShowWarningBar(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowWarningBarHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private WarningBarMessage message_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarHandler.java
new file mode 100644
index 0000000..0f1747e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/ShowWarningBarHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowWarningBarHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowWarningBarHandler extends EventHandler
+{
+ void onShowWarningBar(ShowWarningBarEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedEvent.java
new file mode 100644
index 0000000..6d512d9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * WorkbenchLoadedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class WorkbenchLoadedEvent extends GwtEvent<WorkbenchLoadedHandler>
+{
+ public static final GwtEvent.Type<WorkbenchLoadedHandler> TYPE =
+ new GwtEvent.Type<WorkbenchLoadedHandler>();
+
+ @Override
+ protected void dispatch(WorkbenchLoadedHandler handler)
+ {
+ handler.onWorkbenchLoaded(this);
+ }
+
+ @Override
+ public GwtEvent.Type<WorkbenchLoadedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedHandler.java
new file mode 100644
index 0000000..33981ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchLoadedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * WorkbenchLoadedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface WorkbenchLoadedHandler extends EventHandler
+{
+ void onWorkbenchLoaded(WorkbenchLoadedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedEvent.java
new file mode 100644
index 0000000..07098d5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedEvent.java
@@ -0,0 +1,49 @@
+/*
+ * WorkbenchMetricsChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.model.WorkbenchMetrics;
+
+public class WorkbenchMetricsChangedEvent extends
+ GwtEvent<WorkbenchMetricsChangedHandler>
+{
+ public static final GwtEvent.Type<WorkbenchMetricsChangedHandler> TYPE =
+ new GwtEvent.Type<WorkbenchMetricsChangedHandler>();
+
+ public WorkbenchMetricsChangedEvent(WorkbenchMetrics clientMetrics)
+ {
+ clientMetrics_ = clientMetrics ;
+ }
+
+ public WorkbenchMetrics getWorkbenchMetrics()
+ {
+ return clientMetrics_ ;
+ }
+
+ @Override
+ protected void dispatch(WorkbenchMetricsChangedHandler handler)
+ {
+ handler.onWorkbenchMetricsChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<WorkbenchMetricsChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final WorkbenchMetrics clientMetrics_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedHandler.java
new file mode 100644
index 0000000..a86492c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/events/WorkbenchMetricsChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * WorkbenchMetricsChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface WorkbenchMetricsChangedHandler extends EventHandler
+{
+ void onWorkbenchMetricsChanged(WorkbenchMetricsChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/Agreement.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/Agreement.java
new file mode 100644
index 0000000..1012c28
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/Agreement.java
@@ -0,0 +1,41 @@
+/*
+ * Agreement.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Agreement extends JavaScriptObject
+{
+ protected Agreement()
+ {
+ }
+
+ public final native String getTitle() /*-{
+ return this.title;
+ }-*/;
+
+ public final native String getContents() /*-{
+ return this.contents;
+ }-*/;
+
+ public final native String getHash() /*-{
+ return this.hash;
+ }-*/;
+
+ public final native boolean getUpdated() /*-{
+ return this.updated;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/BrowseUrlInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/BrowseUrlInfo.java
new file mode 100644
index 0000000..468cbfc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/BrowseUrlInfo.java
@@ -0,0 +1,33 @@
+/*
+ * BrowseUrlInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class BrowseUrlInfo extends JavaScriptObject
+{
+ protected BrowseUrlInfo()
+ {
+ }
+
+ public final native String getUrl() /*-{
+ return this.url;
+ }-*/;
+
+ public final native String getWindow() /*-{
+ return this.window;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ChangeTracker.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ChangeTracker.java
new file mode 100644
index 0000000..f1b571a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ChangeTracker.java
@@ -0,0 +1,28 @@
+/*
+ * ChangeTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+public interface ChangeTracker
+{
+ boolean hasChanged();
+
+ /**
+ * Causes hasChanged() to return false until the thing you're tracking
+ * changes again.
+ */
+ void reset();
+
+ ChangeTracker fork();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientInitState.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientInitState.java
new file mode 100644
index 0000000..0dcc129
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientInitState.java
@@ -0,0 +1,35 @@
+/*
+ * ClientInitState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ClientInitState extends JavaScriptObject
+{
+ protected ClientInitState()
+ {
+ }
+
+ public final native <T extends JavaScriptObject> T take(String group) /*-{
+ var grp = this[group];
+ if (grp)
+ delete this[group];
+ return grp || {};
+ }-*/;
+
+ public final native <T extends JavaScriptObject> T peek(String group) /*-{
+ return this[group] || {};
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientState.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientState.java
new file mode 100644
index 0000000..f1dd68f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ClientState.java
@@ -0,0 +1,105 @@
+/*
+ * ClientState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public final class ClientState extends JavaScriptObject
+{
+ public static final int TEMPORARY = 0;
+ public static final int PERSISTENT = 1;
+ public static final int PROJECT_PERSISTENT = 2;
+
+ protected ClientState()
+ {
+ }
+
+ public static native ClientState create() /*-{
+ return {
+ temporary: {},
+ persistent: {},
+ project_persistent: {},
+ set: function(group, name, value, persist) {
+ var base = this.temporary;
+ if (persist == 1)
+ base = this.persistent;
+ else if (persist == 2)
+ base = this.project_persistent;
+ var grp = base[group];
+ if (!grp)
+ grp = base[group] = {};
+ grp[name] = value;
+ this.isEmpty = false;
+ },
+ isEmpty: true
+ };
+ }-*/;
+
+ public native final JavaScriptObject getTemporaryData() /*-{
+ return this.temporary;
+ }-*/;
+
+ public native final JavaScriptObject getPersistentData() /*-{
+ return this.persistent;
+ }-*/;
+
+ public native final JavaScriptObject getProjectPersistentData() /*-{
+ return this.project_persistent;
+ }-*/;
+
+ public native final boolean isEmpty() /*-{
+ return this.isEmpty;
+ }-*/;
+
+ public native final void putObject(String group,
+ String name,
+ JavaScriptObject value,
+ int persist) /*-{
+ this.set(group, name, value, persist);
+ }-*/;
+
+ public native final void putString(String group,
+ String name,
+ String value,
+ int persist) /*-{
+ this.set(group, name, value, persist);
+ }-*/;
+
+ public native final void putInt(String group,
+ String name,
+ int value,
+ int persist) /*-{
+ this.set(group, name, value, persist);
+ }-*/;
+
+ public native final void putBoolean(String group,
+ String name,
+ boolean value,
+ int persist) /*-{
+ this.set(group, name, value, persist);
+ }-*/;
+
+ public void putStrings(String group,
+ String name,
+ String[] value,
+ int persist)
+ {
+ JsArrayString array = JsArrayString.createArray().cast();
+ for (String v : value)
+ array.push(v);
+ this.putObject(group, name, array, persist);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ConsoleAction.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ConsoleAction.java
new file mode 100644
index 0000000..6c6c3a1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ConsoleAction.java
@@ -0,0 +1,35 @@
+/*
+ * ConsoleAction.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ConsoleAction extends JavaScriptObject
+{
+ protected ConsoleAction() {}
+
+ public static final int PROMPT = 0;
+ public static final int INPUT = 1;
+ public static final int OUTPUT = 2;
+ public static final int ERROR = 3;
+
+ public native final int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public native final String getData() /*-{
+ return this.data;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ErrorMessage.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ErrorMessage.java
new file mode 100644
index 0000000..bf1068e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ErrorMessage.java
@@ -0,0 +1,33 @@
+/*
+ * ErrorMessage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class ErrorMessage extends JavaScriptObject
+{
+ protected ErrorMessage()
+ {
+ }
+
+ public final native String getTitle() /*-{
+ return this.title;
+ }-*/;
+
+ public final native String getMessage() /*-{
+ return this.message;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/EventBasedChangeTracker.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/EventBasedChangeTracker.java
new file mode 100644
index 0000000..bada03a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/EventBasedChangeTracker.java
@@ -0,0 +1,54 @@
+/*
+ * EventBasedChangeTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+
+public class EventBasedChangeTracker<T> implements ChangeTracker
+{
+ public EventBasedChangeTracker(HasValueChangeHandlers<T> source)
+ {
+ source_ = source;
+ source.addValueChangeHandler(new ValueChangeHandler<T>()
+ {
+ public void onValueChange(ValueChangeEvent<T> valueChangeEvent)
+ {
+ changed_ = true;
+ }
+ });
+ }
+
+ public boolean hasChanged()
+ {
+ return changed_;
+ }
+
+ public void reset()
+ {
+ changed_ = false;
+ }
+
+ public ChangeTracker fork()
+ {
+ EventBasedChangeTracker<T> ebct = new EventBasedChangeTracker<T>(source_);
+ ebct.changed_ = changed_;
+ return ebct;
+ }
+
+ protected boolean changed_ = false;
+ private final HasValueChangeHandlers<T> source_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/HTMLCapabilities.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/HTMLCapabilities.java
new file mode 100644
index 0000000..41e71bd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/HTMLCapabilities.java
@@ -0,0 +1,30 @@
+/*
+ * HTMLCapabilities.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class HTMLCapabilities extends JavaScriptObject
+{
+ protected HTMLCapabilities() {}
+
+ public native final boolean isRMarkdownSupported() /*-{
+ return this.r_markdown_supported;
+ }-*/;
+
+ public native final boolean isStitchSupported() /*-{
+ return this.stitch_supported;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/MetaServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/MetaServerOperations.java
new file mode 100644
index 0000000..3d8b80f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/MetaServerOperations.java
@@ -0,0 +1,22 @@
+/*
+ * MetaServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface MetaServerOperations
+{
+ void getInitMessages(ServerRequestCallback<String> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/QuotaStatus.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/QuotaStatus.java
new file mode 100644
index 0000000..7ec1407
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/QuotaStatus.java
@@ -0,0 +1,78 @@
+/*
+ * QuotaStatus.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class QuotaStatus extends JavaScriptObject
+{
+ protected QuotaStatus()
+ {
+ }
+
+ public final long getUsed()
+ {
+ return getLongValue("used");
+ }
+
+ public final long getQuota()
+ {
+ return getLongValue("quota");
+ }
+
+ public final long getLimit()
+ {
+ return getLongValue("limit");
+ }
+
+ public final boolean isNearQuota()
+ {
+ return isNear(getQuota());
+ }
+
+ public final boolean isOverQuota()
+ {
+ return isOver(getQuota());
+ }
+
+ public final boolean isNearLimit()
+ {
+ return isNear(getLimit());
+ }
+
+ private final boolean isOver(double threshold)
+ {
+ return getUsed() > threshold;
+ }
+
+ private final boolean isNear(double threshold)
+ {
+ // defend against dbz
+ if (threshold == 0)
+ return false;
+
+ return (getUsed() / threshold) > 0.90;
+ }
+
+ private final long getLongValue(String value)
+ {
+ return new Double(getValueNative(value)).longValue();
+ }
+
+ private final native double getValueNative(String value) /*-{
+ return this[value];
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/RemoteFileSystemContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/RemoteFileSystemContext.java
new file mode 100644
index 0000000..4cdafa2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/RemoteFileSystemContext.java
@@ -0,0 +1,131 @@
+/*
+ * RemoteFileSystemContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.inject.Inject;
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.files.PosixFileSystemContext;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+import java.util.ArrayList;
+
+public class RemoteFileSystemContext extends PosixFileSystemContext
+{
+ @Inject
+ public RemoteFileSystemContext(FilesServerOperations server,
+ FileTypeRegistry fileTypeRegistry,
+ GlobalDisplay globalDisplay)
+ {
+ super();
+ server_ = server;
+ fileTypeRegistry_ = fileTypeRegistry;
+ globalDisplay_ = globalDisplay;
+ }
+
+ public MessageDisplay messageDisplay()
+ {
+ return globalDisplay_;
+ }
+
+ public void cd(String relativeOrAbsolutePath)
+ {
+ final String newPath = combine(workingDir_, relativeOrAbsolutePath);
+
+ final FileSystemItem newPathEntry = FileSystemItem.createDir(newPath);
+
+ final ArrayList<FileSystemItem> fsi = new ArrayList<FileSystemItem>();
+
+ server_.listFiles(
+ newPathEntry,
+ false, // since this is used for the file dialog don't
+ // cause the call to reset the server monitoring state
+ new ServerRequestCallback<JsArray<FileSystemItem>>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ callbacks_.onError(error.getUserMessage());
+ }
+
+ @Override
+ public void onResponseReceived(final JsArray<FileSystemItem> response)
+ {
+ for (int i = 0; i < response.length(); i++)
+ fsi.add(response.get(i));
+
+ workingDir_ = newPath;
+ contents_ = fsi.toArray(new FileSystemItem[0]);
+ callbacks_.onNavigated();
+ }
+ });
+ }
+
+ public void refresh()
+ {
+ cd(workingDir_);
+ }
+
+ public void mkdir(final String directoryName, final ProgressIndicator progress)
+ {
+ String error;
+ if (null != (error = validatePathElement(directoryName, true)))
+ {
+ progress.onError(error);
+ return;
+ }
+
+ final String baseDir = workingDir_;
+ String newPath = combine(baseDir, directoryName);
+ final FileSystemItem newFolder = FileSystemItem.createDir(newPath);
+ server_.createFolder(
+ newFolder,
+ new ServerRequestCallback<org.rstudio.studio.client.server.Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ progress.onError(error.getUserMessage());
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ if (baseDir.equals(workingDir_))
+ {
+ progress.onCompleted();
+ callbacks_.onDirectoryCreated(newFolder);
+ }
+ }
+ });
+ }
+
+ public ImageResource getIcon(FileSystemItem item)
+ {
+ return fileTypeRegistry_.getIconForFile(item);
+ }
+
+ private final FilesServerOperations server_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final GlobalDisplay globalDisplay_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/Session.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/Session.java
new file mode 100644
index 0000000..3c9de89
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/Session.java
@@ -0,0 +1,46 @@
+/*
+ * Session.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.inject.Inject;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.events.PushClientStateEvent;
+
+public class Session
+{
+ @Inject
+ public Session(EventBus events)
+ {
+ events_ = events;
+ }
+
+ public SessionInfo getSessionInfo()
+ {
+ return sessionInfo_;
+ }
+
+ public void setSessionInfo(SessionInfo sessionInfo)
+ {
+ sessionInfo_ = sessionInfo;
+ }
+
+ public void persistClientState()
+ {
+ events_.fireEvent(new PushClientStateEvent());
+ }
+
+ private SessionInfo sessionInfo_;
+ private final EventBus events_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/SessionInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/SessionInfo.java
new file mode 100644
index 0000000..3267936
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/SessionInfo.java
@@ -0,0 +1,362 @@
+/*
+ * SessionInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfState;
+import org.rstudio.studio.client.common.console.ConsoleProcessInfo;
+import org.rstudio.studio.client.common.debugging.model.ErrorManagerState;
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildState;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesState;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+
+public class SessionInfo extends JavaScriptObject
+{
+ protected SessionInfo()
+ {
+ }
+
+ public final native String getClientId() /*-{
+ return this.clientId;
+ }-*/;
+
+ public final native double getClientVersion() /*-{
+ return this.version;
+ }-*/;
+
+ public final native String getUserIdentity() /*-{
+ return this.userIdentity;
+ }-*/;
+
+ public final native JsArray<RnwWeave> getRnwWeaveTypes() /*-{
+ return this.rnw_weave_types;
+ }-*/;
+
+ public final native JsArrayString getLatexProgramTypes() /*-{
+ return this.latex_program_types;
+ }-*/;
+
+ public final native TexCapabilities getTexCapabilities() /*-{
+ return this.tex_capabilities;
+ }-*/;
+
+ public final native CompilePdfState getCompilePdfState() /*-{
+ return this.compile_pdf_state;
+ }-*/;
+
+ public final native FindInFilesState getFindInFilesState() /*-{
+ return this.find_in_files_state;
+ }-*/;
+
+ public final native String getLogDir() /*-{
+ return this.log_dir;
+ }-*/;
+
+ public final native String getScratchDir() /*-{
+ return this.scratch_dir;
+ }-*/;
+
+ public final native String getTempDir() /*-{
+ return this.temp_dir;
+ }-*/;
+
+ public final native JsObject getUiPrefs() /*-{
+ if (!this.ui_prefs)
+ this.ui_prefs = {};
+ return this.ui_prefs;
+ }-*/;
+
+ public final static String DESKTOP_MODE = "desktop";
+ public final static String SERVER_MODE = "server";
+
+ public final native String getMode() /*-{
+ return this.mode;
+ }-*/;
+
+ public final native boolean getResumed() /*-{
+ return this.resumed;
+ }-*/;
+
+ public final native String getDefaultPrompt() /*-{
+ return this.prompt;
+ }-*/;
+
+ public final native JsArrayString getConsoleHistory() /*-{
+ return this.console_history;
+ }-*/;
+
+ public final native int getConsoleHistoryCapacity() /*-{
+ return this.console_history_capacity;
+ }-*/;
+
+ public final native RpcObjectList<ConsoleAction> getConsoleActions() /*-{
+ return this.console_actions;
+ }-*/;
+
+ public final native int getConsoleActionsLimit() /*-{
+ return this.console_actions_limit;
+ }-*/;
+
+ public final native ClientInitState getClientState() /*-{
+ return this.client_state;
+ }-*/;
+
+ public final native JsArray<SourceDocument> getSourceDocuments() /*-{
+ return this.source_documents;
+ }-*/;
+
+ public final native WorkbenchLists getLists() /*-{
+ return this.lists;
+ }-*/;
+
+ public final native boolean hasAgreement() /*-{
+ return this.hasAgreement;
+ }-*/;
+
+ public final native Agreement pendingAgreement() /*-{
+ return this.pendingAgreement;
+ }-*/;
+
+ public final native String docsURL() /*-{
+ return this.docsURL;
+ }-*/;
+
+ public final native String getRstudioVersion() /*-{
+ return this.rstudio_version;
+ }-*/;
+
+ public final native String getSystemEncoding() /*-{
+ return this.system_encoding;
+ }-*/;
+
+ public final boolean isVcsEnabled()
+ {
+ return !StringUtil.isNullOrEmpty(getVcsName());
+ }
+
+ public final boolean isVcsAvailable()
+ {
+ String[] availableVcs = getAvailableVCS();
+ return availableVcs.length > 0 && availableVcs[0].length() > 0;
+ }
+
+ public final String[] getAvailableVCS()
+ {
+ return this.<JsObject>cast().getString("vcs_available", true).split(",");
+ }
+
+ public final native String getVcsName() /*-{
+ return this.vcs;
+ }-*/;
+
+ public final boolean isVcsAvailable(String id)
+ {
+ String[] availableVcs = getAvailableVCS();
+ for (int i=0; i<availableVcs.length; i++)
+ {
+ if (availableVcs[i].equals(id))
+ return true;
+ }
+
+ return false;
+ }
+
+ public native final String getDefaultSSHKeyDir() /*-{
+ return this.default_ssh_key_dir;
+ }-*/;
+
+ public native final boolean isGithubRepository() /*-{
+ return this.is_github_repo;
+ }-*/;
+
+ // TODO: The check for null was for migration in the presence of
+ // sessions that couldn't suspend (3/21/2011). Remove this check
+ // once we are sufficiently clear of this date window.
+ public final native String getInitialWorkingDir() /*-{
+ if (!this.initial_working_dir)
+ this.initial_working_dir = "~/";
+ return this.initial_working_dir;
+ }-*/;
+
+ public final native String getActiveProjectFile() /*-{
+ return this.active_project_file;
+ }-*/;
+
+ public final FileSystemItem getActiveProjectDir()
+ {
+ String projFile = getActiveProjectFile();
+ if (projFile != null)
+ {
+ return FileSystemItem.createFile(projFile).getParentPath();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public final native JsObject getProjectUIPrefs() /*-{
+ if (!this.project_ui_prefs)
+ this.project_ui_prefs = {};
+ return this.project_ui_prefs;
+ }-*/;
+
+ public final native JsArrayString getProjectOpenDocs() /*-{
+ if (!this.project_open_docs)
+ this.project_open_docs = {};
+ return this.project_open_docs;
+ }-*/;
+
+ public final native JsArray<ConsoleProcessInfo> getConsoleProcesses() /*-{
+ return this.console_processes;
+ }-*/;
+
+ public final native boolean isInternalPdfPreviewEnabled() /*-{
+ return this.internal_pdf_preview_enabled;
+ }-*/;
+
+
+ public final native String getSumatraPdfExePath() /*-{
+ return this.sumatra_pdf_exe_path;
+ }-*/;
+
+ public final native HTMLCapabilities getHTMLCapabilities() /*-{
+ return this.html_capabilities;
+ }-*/;
+
+ public final static String BUILD_TOOLS_NONE = "None";
+ public final static String BUILD_TOOLS_PACKAGE = "Package";
+ public final static String BUILD_TOOLS_MAKEFILE = "Makefile";
+ public final static String BUILD_TOOLS_CUSTOM = "Custom";
+
+ public final native String getBuildToolsType() /*-{
+ return this.build_tools_type;
+ }-*/;
+
+ public final native String getBuildTargetDir() /*-{
+ return this.build_target_dir;
+ }-*/;
+
+ public final native boolean getHasPackageSrcDir() /*-{
+ return this.has_pkg_src;
+ }-*/;
+
+ public final native boolean getHasPackageVignetteDir() /*-{
+ return this.has_pkg_vig;
+ }-*/;
+
+ public final String getPresentationName()
+ {
+ PresentationState state = getPresentationState();
+ if (state != null)
+ return state.getPaneCaption();
+ else
+ return "Presentation";
+ }
+
+ public final native PresentationState getPresentationState() /*-{
+ return this.presentation_state;
+ }-*/;
+
+ public final native BuildState getBuildState() /*-{
+ return this.build_state;
+ }-*/;
+
+ public final native boolean isDevtoolsInstalled() /*-{
+ return this.devtools_installed;
+ }-*/;
+
+ public final native boolean isCairoPdfAvailable() /*-{
+ return this.have_cairo_pdf;
+ }-*/;
+
+ public final native boolean getAllowVcsExeEdit() /*-{
+ return this.allow_vcs_exe_edit;
+ }-*/;
+
+ public final native boolean getAllowCRANReposEdit() /*-{
+ return this.allow_cran_repos_edit;
+ }-*/;
+
+ public final native boolean getAllowVcs() /*-{
+ return this.allow_vcs;
+ }-*/;
+
+ public final native boolean getAllowPackageInstallation() /*-{
+ return this.allow_pkg_install;
+ }-*/;
+
+ public final native boolean getAllowShell() /*-{
+ return this.allow_shell;
+ }-*/;
+
+ public final native boolean getAllowFileDownloads() /*-{
+ return this.allow_file_download;
+ }-*/;
+
+ public final native boolean getAllowRemovePublicFolder() /*-{
+ return this.allow_remove_public_folder;
+ }-*/;
+
+ public final native boolean getAllowRpubsPublish() /*-{
+ return this.allow_rpubs_publish;
+ }-*/;
+
+ public final native String getSwitchToProject() /*-{
+ return this.switch_to_project;
+ }-*/;
+
+ public final native EnvironmentContextData getEnvironmentState() /*-{
+ return this.environment_state;
+ }-*/;
+
+ public final native boolean getDisablePackages() /*-{
+ return this.disable_packages;
+ }-*/;
+
+ public final native boolean getHaveSrcrefAttribute() /*-{
+ return this.have_srcref_attribute;
+ }-*/;
+
+ public final native ErrorManagerState getErrorState() /*-{
+ return this.error_state;
+ }-*/;
+
+ public final native boolean getDisableCheckForUpdates() /*-{
+ return this.disable_check_for_updates;
+ }-*/;
+
+ public final native boolean getShowIdentity() /*-{
+ return this.show_identity;
+ }-*/;
+
+ public final native boolean getHaveAdvancedStepCommands() /*-{
+ return this.have_advanced_step_commands;
+ }-*/;
+
+ public final native boolean getShinyappsInstalled() /*-{
+ return this.shinyapps_installed;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/TerminalOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/TerminalOptions.java
new file mode 100644
index 0000000..e553ef6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/TerminalOptions.java
@@ -0,0 +1,34 @@
+/*
+ * TerminalOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class TerminalOptions extends JavaScriptObject
+{
+ protected TerminalOptions() {}
+
+ public native final String getTerminalPath() /*-{
+ return this.terminal_path;
+ }-*/;
+
+ public native final String getWorkingDirectory() /*-{
+ return this.working_directory;
+ }-*/;
+
+ public native final String getExtraPathEntries() /*-{
+ return this.extra_path_entries;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/TexCapabilities.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/TexCapabilities.java
new file mode 100644
index 0000000..b0a6da4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/TexCapabilities.java
@@ -0,0 +1,40 @@
+/*
+ * TexCapabilities.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class TexCapabilities extends JavaScriptObject
+{
+ protected TexCapabilities()
+ {
+ }
+
+ public final native boolean isTexInstalled() /*-{
+ return this.tex_installed;
+ }-*/;
+
+ public final boolean isRnwWeaveAvailable(RnwWeave rnwWeave)
+ {
+ return isPackageInstalledNative(
+ rnwWeave.getPackageName().toLowerCase() + "_installed");
+ }
+
+ private final native boolean isPackageInstalledNative(String attrib) /*-{
+ return this[attrib];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/UnsavedChangesTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/UnsavedChangesTarget.java
new file mode 100644
index 0000000..7a8940c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/UnsavedChangesTarget.java
@@ -0,0 +1,26 @@
+/*
+ * UnsavedChangesTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.resources.client.ImageResource;
+
+public interface UnsavedChangesTarget
+{
+ String getId();
+ ImageResource getIcon();
+ String getTitle();
+ String getPath();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/ValueChangeTracker.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/ValueChangeTracker.java
new file mode 100644
index 0000000..111eff7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/ValueChangeTracker.java
@@ -0,0 +1,46 @@
+/*
+ * ValueChangeTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+public class ValueChangeTracker<TValue>
+{
+ public ValueChangeTracker(TValue value)
+ {
+ value_ = value;
+ }
+
+ public TValue getValue()
+ {
+ return value_;
+ }
+
+ public boolean checkForChange(TValue newValue)
+ {
+ boolean equal;
+ if (value_ == null ^ newValue == null)
+ equal = false;
+ else if (value_ == null)
+ equal = true;
+ else
+ equal = value_.equals(newValue);
+
+ if (!equal)
+ value_ = newValue;
+
+ return !equal;
+ }
+
+ private TValue value_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/WarningBarMessage.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/WarningBarMessage.java
new file mode 100644
index 0000000..662c901
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/WarningBarMessage.java
@@ -0,0 +1,33 @@
+/*
+ * WarningBarMessage.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class WarningBarMessage extends JavaScriptObject
+{
+ protected WarningBarMessage()
+ {
+
+ }
+
+ public native final boolean isSevere() /*-{
+ return this.severe;
+ }-*/;
+
+ public native final String getMessage() /*-{
+ return this.message;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchLists.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchLists.java
new file mode 100644
index 0000000..66d5e4c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchLists.java
@@ -0,0 +1,48 @@
+/*
+ * WorkbenchLists.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class WorkbenchLists extends JavaScriptObject
+{
+ protected WorkbenchLists()
+ {
+
+ }
+
+ public final ArrayList<String> getList(String name)
+ {
+ JsArrayString jsList = getListNative(name);
+ return convertList(jsList);
+ }
+
+
+ private native final JsArrayString getListNative(String name) /*-{
+ return this[name];
+ }-*/;
+
+ private ArrayList<String> convertList(JsArrayString jsList)
+ {
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i=0; i<jsList.length(); i++)
+ list.add(jsList.get(i));
+ return list;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchListsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchListsServerOperations.java
new file mode 100644
index 0000000..91c01f1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchListsServerOperations.java
@@ -0,0 +1,49 @@
+/*
+ * WorkbenchListsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import com.google.gwt.core.client.JsArrayString;
+
+
+
+public interface WorkbenchListsServerOperations
+{
+ void listGet(String listName,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void listSetContents(String listName,
+ ArrayList<String> list,
+ ServerRequestCallback<Void> requestCallback);
+
+ void listPrependItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback);
+
+ void listAppendItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback);
+
+ void listRemoveItem(String listName,
+ String value,
+ ServerRequestCallback<Void> requestCallback);
+
+ void listClear(String listName,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchMetrics.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchMetrics.java
new file mode 100644
index 0000000..c0a499f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchMetrics.java
@@ -0,0 +1,77 @@
+/*
+ * WorkbenchMetrics.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class WorkbenchMetrics extends JavaScriptObject
+{
+ protected WorkbenchMetrics()
+ {
+ }
+
+ public static final native WorkbenchMetrics create(int consoleWidth,
+ int graphicsWidth,
+ int graphicsHeight) /*-{
+ var clientMetrics = new Object();
+ clientMetrics.consoleWidth = consoleWidth ;
+ clientMetrics.graphicsWidth = graphicsWidth ;
+ clientMetrics.graphicsHeight = graphicsHeight ;
+ return clientMetrics ;
+ }-*/;
+
+ public final native int getConsoleWidth() /*-{
+ return this.consoleWidth;
+ }-*/;
+
+ public final native int getGraphicsWidth() /*-{
+ return this.graphicsWidth ;
+ }-*/;
+
+ public final native int getGraphicsHeight() /*-{
+ return this.graphicsHeight;
+ }-*/;
+
+ public final boolean equalTo(WorkbenchMetrics other)
+ {
+ return (other != null &&
+ getConsoleWidth() == other.getConsoleWidth() &&
+ getGraphicsWidth() == other.getGraphicsWidth() &&
+ getGraphicsHeight() == other.getGraphicsHeight());
+ }
+
+ // are the metrics "close enough"to the previous ones such that we don't
+ // need to send an update? graphics always have to be equal but if the
+ // console width has gotten 1 or 2 characters wider then we can
+ // suppress sending. note we do this to avoid chatty set_workbench_metrics
+ // calls (failing to increase the width of the console merely results
+ // in some extra whitespace at the right margin)
+ public final boolean closeEnoughToPrevious(WorkbenchMetrics previous)
+ {
+ // if previous is null then we're not close enough
+ if (previous == null)
+ return false;
+
+ // new width offset
+ int newWidthOffset = getConsoleWidth() - previous.getConsoleWidth();
+
+ // if it's 0, 1, or 2 larger then it's close enough
+ boolean consoleCloseEnough = newWidthOffset >= 0 && newWidthOffset <= 2;
+
+ return (consoleCloseEnough &&
+ (getGraphicsWidth() == previous.getGraphicsWidth()) &&
+ (getGraphicsHeight() == previous.getGraphicsHeight()));
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchServerOperations.java
new file mode 100644
index 0000000..6fc32ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/WorkbenchServerOperations.java
@@ -0,0 +1,103 @@
+/*
+ * WorkbenchServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfServerOperations;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.common.debugging.DebuggingServerOperations;
+import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations;
+import org.rstudio.studio.client.common.spelling.model.SpellingServerOperations;
+import org.rstudio.studio.client.common.synctex.model.SynctexServerOperations;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.projects.model.ProjectsServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchServerOperations;
+import org.rstudio.studio.client.workbench.prefs.model.PrefsServerOperations;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildServerOperations;
+import org.rstudio.studio.client.workbench.views.choosefile.model.ChooseFileServerOperations;
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleServerOperations;
+import org.rstudio.studio.client.workbench.views.data.model.DataServerOperations;
+import org.rstudio.studio.client.workbench.views.edit.model.EditServerOperations;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesServerOperations;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryServerOperations;
+import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerServerOperations;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+import org.rstudio.studio.client.workbench.views.viewer.model.ViewerServerOperations;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentServerOperations;
+
+public interface WorkbenchServerOperations extends ConsoleServerOperations,
+ FilesServerOperations,
+ PackagesServerOperations,
+ HelpServerOperations,
+ PlotsServerOperations,
+ EditServerOperations,
+ SourceServerOperations,
+ DataServerOperations,
+ ChooseFileServerOperations,
+ HistoryServerOperations,
+ MirrorsServerOperations,
+ GitServerOperations,
+ SVNServerOperations,
+ PrefsServerOperations,
+ ProjectsServerOperations,
+ CodeSearchServerOperations,
+ CryptoServerOperations,
+ WorkbenchListsServerOperations,
+ SpellingServerOperations,
+ CompilePdfServerOperations,
+ FindInFilesServerOperations,
+ SynctexServerOperations,
+ BuildServerOperations,
+ PresentationServerOperations,
+ EnvironmentServerOperations,
+ DebuggingServerOperations,
+ MetaServerOperations,
+ ViewerServerOperations,
+ ProfilerServerOperations
+{
+ void initializeForMainWorkbench();
+ void disconnect();
+
+ void setWorkbenchMetrics(WorkbenchMetrics clientMetrics,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getRPrefs(ServerRequestCallback<RPrefs> requestCallback);
+
+ void updateClientState(JavaScriptObject temporary,
+ JavaScriptObject persistent,
+ JavaScriptObject projectPersistent,
+ ServerRequestCallback<Void> requestCallback);
+
+
+ void userPromptCompleted(int response,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getTerminalOptions(
+ ServerRequestCallback<TerminalOptions> requestCallback);
+
+
+ void startShellDialog(ServerRequestCallback<ConsoleProcess> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/BoolStateValue.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/BoolStateValue.java
new file mode 100644
index 0000000..98c1a9d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/BoolStateValue.java
@@ -0,0 +1,49 @@
+/*
+ * BoolStateValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model.helper;
+
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+
+public abstract class BoolStateValue extends ClientStateValue<Boolean>
+{
+ public BoolStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state)
+ {
+ super(group,
+ name,
+ persist,
+ state);
+ }
+
+ @Override
+ protected final Boolean doGet(JsObject group, String name)
+ {
+ return group.getBoolean(name);
+ }
+
+ @Override
+ protected final void doSet(ClientState state,
+ String group,
+ String name,
+ Boolean value,
+ int persist)
+ {
+ state.putBoolean(group, name, value, persist);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/ClientStateValue.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/ClientStateValue.java
new file mode 100644
index 0000000..faf9dba
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/ClientStateValue.java
@@ -0,0 +1,118 @@
+/*
+ * ClientStateValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model.helper;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.events.SaveClientStateEvent;
+import org.rstudio.studio.client.workbench.events.SaveClientStateHandler;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.ValueChangeTracker;
+
+/**
+ * ClientStateValue makes it easy to hook a single value up to the ClientState
+ * system. Subclass one of the type-specific abstract subclasses, and provide
+ * implementations for onInit(T) and getValue().
+ *
+ * By default, getValue() will be called periodically to check for changes.
+ * This will be called quite often so if it will be expensive and there's a
+ * cheaper way to determine if the value changed, you can override hasChanged()
+ * and have different logic.
+ *
+ * @param <T>
+ */
+public abstract class ClientStateValue<T> implements SaveClientStateHandler
+{
+ protected ClientStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state)
+ {
+ this(group, name, persist, state, false);
+ }
+
+ /**
+ * If delayedInit == true, finishInit(ClientInitState) must be called
+ * by the subclasser or caller, in order to complete object creation.
+ */
+ protected ClientStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state,
+ boolean delayedInit)
+ {
+ group_ = group;
+ name_ = name;
+ persist_ = persist;
+
+ if (!delayedInit)
+ finishInit(state);
+ }
+
+ protected void finishInit(ClientInitState state)
+ {
+ JsObject grp = state.peek(group_);
+ T obj = doGet(grp, name_);
+ valueTracker_ = new ValueChangeTracker<T>(obj);
+ onInit(obj);
+
+ EventBus evt = RStudioGinjector.INSTANCE.getEventBus();
+ evt.addHandler(SaveClientStateEvent.TYPE, this);
+ }
+
+ protected abstract T doGet(JsObject group, String name);
+ protected abstract void doSet(ClientState state,
+ String group,
+ String name,
+ T value,
+ int persist);
+
+ protected abstract void onInit(T value);
+ protected abstract T getValue();
+
+ public final void onSaveClientState(SaveClientStateEvent event)
+ {
+ try
+ {
+ if (hasChanged())
+ {
+ ClientState clientState = event.getState();
+ put(clientState, getValue());
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.log(e.toString());
+ }
+ }
+
+ protected boolean hasChanged()
+ {
+ return valueTracker_.checkForChange(getValue());
+ }
+
+ protected void put(ClientState clientState, T newValue)
+ {
+ doSet(clientState, group_, name_, newValue, persist_);
+ }
+
+ private ValueChangeTracker<T> valueTracker_;
+ private String group_;
+ private String name_;
+ private int persist_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/IntStateValue.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/IntStateValue.java
new file mode 100644
index 0000000..917e4d7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/IntStateValue.java
@@ -0,0 +1,54 @@
+/*
+ * IntStateValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model.helper;
+
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+
+public abstract class IntStateValue extends ClientStateValue<Integer>
+{
+ public IntStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state)
+ {
+ super(group, name, persist, state);
+ }
+
+ protected IntStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state, boolean delayedInit)
+ {
+ super(group, name, persist, state, delayedInit);
+ }
+
+ @Override
+ protected final Integer doGet(JsObject group, String name)
+ {
+ return group.getInteger(name);
+ }
+
+ @Override
+ protected final void doSet(ClientState state,
+ String group,
+ String name,
+ Integer value,
+ int persist)
+ {
+ state.putInt(group, name, value, persist);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/JSObjectStateValue.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/JSObjectStateValue.java
new file mode 100644
index 0000000..60a00a0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/JSObjectStateValue.java
@@ -0,0 +1,52 @@
+/*
+ * JSObjectStateValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model.helper;
+
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+
+public abstract class JSObjectStateValue extends ClientStateValue<JsObject>
+
+{
+ public JSObjectStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state,
+ boolean delayedInit)
+ {
+ super(group,
+ name,
+ persist,
+ state,
+ delayedInit);
+ }
+
+ @Override
+ protected final JsObject doGet(JsObject group, String name)
+ {
+ return group.getObject(name);
+ }
+
+ @Override
+ protected final void doSet(ClientState state,
+ String group,
+ String name,
+ JsObject value,
+ int persist)
+ {
+ state.putObject(group, name, value, persist);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/StringStateValue.java b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/StringStateValue.java
new file mode 100644
index 0000000..393e1f7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/model/helper/StringStateValue.java
@@ -0,0 +1,49 @@
+/*
+ * StringStateValue.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.model.helper;
+
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+
+public abstract class StringStateValue extends ClientStateValue<String>
+{
+ public StringStateValue(String group,
+ String name,
+ int persist,
+ ClientInitState state)
+ {
+ super(group,
+ name,
+ persist,
+ state);
+ }
+
+ @Override
+ protected final String doGet(JsObject group, String name)
+ {
+ return group.getString(name);
+ }
+
+ @Override
+ protected final void doSet(ClientState state,
+ String group,
+ String name,
+ String value,
+ int persist)
+ {
+ state.putString(group, name, value, persist);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedEvent.java
new file mode 100644
index 0000000..4c5e7ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedEvent.java
@@ -0,0 +1,46 @@
+/*
+ * PreferenceChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PreferenceChangedEvent extends GwtEvent<PreferenceChangedHandler>
+{
+ public static final Type<PreferenceChangedHandler> TYPE = new Type<PreferenceChangedHandler>();
+
+ public PreferenceChangedEvent(String prefName)
+ {
+ prefName_ = prefName;
+ }
+
+ public String getName()
+ {
+ return prefName_;
+ }
+
+ @Override
+ public Type<PreferenceChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(PreferenceChangedHandler handler)
+ {
+ handler.onPreferenceChanged(this);
+ }
+
+ private final String prefName_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedHandler.java
new file mode 100644
index 0000000..d1536b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/PreferenceChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * PreferenceChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface PreferenceChangedHandler extends EventHandler
+{
+ void onPreferenceChanged(PreferenceChangedEvent e);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedEvent.java
new file mode 100644
index 0000000..13e4380
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedEvent.java
@@ -0,0 +1,72 @@
+/*
+ * UiPrefsChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.events;
+
+import org.rstudio.core.client.js.JsObject;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class UiPrefsChangedEvent extends GwtEvent<UiPrefsChangedHandler>
+{
+ public static final String GLOBAL_TYPE = "global";
+ public static final String PROJECT_TYPE = "project";
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public final native String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native JsObject getPrefs() /*-{
+ return this.prefs;
+ }-*/;
+ }
+
+ public static final Type<UiPrefsChangedHandler> TYPE = new Type<UiPrefsChangedHandler>();
+
+ public UiPrefsChangedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getType()
+ {
+ return data_.getType();
+ }
+
+ public JsObject getUIPrefs()
+ {
+ return data_.getPrefs();
+ }
+
+ @Override
+ public Type<UiPrefsChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(UiPrefsChangedHandler handler)
+ {
+ handler.onUiPrefsChanged(this);
+ }
+
+ private final Data data_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedHandler.java
new file mode 100644
index 0000000..427539a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/events/UiPrefsChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * UiPrefsChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface UiPrefsChangedHandler extends EventHandler
+{
+ void onUiPrefsChanged(UiPrefsChangedEvent e);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/CompilePdfPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/CompilePdfPrefs.java
new file mode 100644
index 0000000..306ee6b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/CompilePdfPrefs.java
@@ -0,0 +1,38 @@
+/*
+ * CompilePdfPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CompilePdfPrefs extends JavaScriptObject
+{
+ protected CompilePdfPrefs() {}
+
+ public static final native CompilePdfPrefs create(boolean cleanOutput,
+ boolean enableShellEscape) /*-{
+ var prefs = new Object();
+ prefs.clean_output = cleanOutput;
+ prefs.enable_shell_escape = enableShellEscape;
+ return prefs ;
+ }-*/;
+
+ public native final boolean getCleanOutput() /*-{
+ return this.clean_output;
+ }-*/;
+
+ public native final boolean getEnableShellEscape() /*-{
+ return this.enable_shell_escape;
+ }-*/;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/GeneralPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/GeneralPrefs.java
new file mode 100644
index 0000000..64649b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/GeneralPrefs.java
@@ -0,0 +1,51 @@
+/*
+ * GeneralPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class GeneralPrefs extends JavaScriptObject
+{
+ protected GeneralPrefs() {}
+
+ public static final native GeneralPrefs create(int saveAction,
+ boolean loadRData,
+ boolean rProfileOnResume,
+ String initialWorkingDir) /*-{
+ var prefs = new Object();
+ prefs.save_action = saveAction;
+ prefs.load_rdata = loadRData;
+ prefs.rprofile_on_resume = rProfileOnResume;
+ prefs.initial_working_dir = initialWorkingDir;
+ return prefs ;
+ }-*/;
+
+
+ public native final int getSaveAction() /*-{
+ return this.save_action;
+ }-*/;
+
+ public native final boolean getLoadRData() /*-{
+ return this.load_rdata;
+ }-*/;
+
+ public native final boolean getRprofileOnResume() /*-{
+ return this.rprofile_on_resume;
+ }-*/;
+
+ public native final String getInitialWorkingDirectory() /*-{
+ return this.initial_working_dir;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/HistoryPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/HistoryPrefs.java
new file mode 100644
index 0000000..698b514
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/HistoryPrefs.java
@@ -0,0 +1,42 @@
+/*
+ * HistoryPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class HistoryPrefs extends JavaScriptObject
+{
+ protected HistoryPrefs() {}
+
+ public static final native HistoryPrefs create(boolean alwaysSave,
+ boolean removeDuplicates) /*-{
+ var prefs = new Object();
+ prefs.always_save = alwaysSave;
+ prefs.remove_duplicates = removeDuplicates;
+ return prefs ;
+ }-*/;
+
+
+ public native final boolean getAlwaysSave() /*-{
+ return this.always_save;
+ }-*/;
+
+ public native final boolean getRemoveDuplicates() /*-{
+ if (this.remove_duplicates === undefined)
+ return false;
+ else
+ return this.remove_duplicates;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PackagesPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PackagesPrefs.java
new file mode 100644
index 0000000..0541aa5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PackagesPrefs.java
@@ -0,0 +1,66 @@
+/*
+ * PackagesPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import org.rstudio.studio.client.common.mirrors.model.BioconductorMirror;
+import org.rstudio.studio.client.common.mirrors.model.CRANMirror;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PackagesPrefs extends JavaScriptObject
+{
+ protected PackagesPrefs() {}
+
+ public static final native PackagesPrefs create(
+ CRANMirror cranMirror,
+ boolean useInternet2,
+ BioconductorMirror bioconductorMirror,
+ boolean cleanupAfterCheckSuccess,
+ boolean viewDirAfterCheckFailure,
+ boolean hideObjectFiles) /*-{
+ var prefs = new Object();
+ prefs.cran_mirror = cranMirror;
+ prefs.use_internet2 = useInternet2;
+ prefs.bioconductor_mirror = bioconductorMirror;
+ prefs.cleanup_after_check_success = cleanupAfterCheckSuccess;
+ prefs.viewdir_after_check_failure = viewDirAfterCheckFailure;
+ prefs.hide_object_files = hideObjectFiles;
+ return prefs ;
+ }-*/;
+
+ public native final CRANMirror getCRANMirror() /*-{
+ return this.cran_mirror;
+ }-*/;
+
+ public native final boolean getUseInternet2() /*-{
+ return this.use_internet2;
+ }-*/;
+
+ public native final boolean getCleanupAfterCheckSuccess() /*-{
+ return this.cleanup_after_check_success;
+ }-*/;
+
+ public native final boolean getViewDirAfterCheckFailure() /*-{
+ return this.viewdir_after_check_failure;
+ }-*/;
+
+ public native final boolean getHideObjectFiles() /*-{
+ return this.hide_object_files;
+ }-*/;
+
+ public native final BioconductorMirror getBioconductorMirror() /*-{
+ return this.bioconductor_mirror;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/Prefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/Prefs.java
new file mode 100644
index 0000000..22faaa5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/Prefs.java
@@ -0,0 +1,336 @@
+/*
+ * Prefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.js.JsObject;
+
+import java.util.HashMap;
+
+public abstract class Prefs
+{
+ public interface PrefValue<T> extends HasValueChangeHandlers<T>
+ {
+ // get accessor for prefs -- this automatically checks the project
+ // prefs, then the global prefs, then returns the default. this should
+ // be called by user code that wants to depend on prefs
+ T getValue();
+
+ // explicit get and set of global pref values -- these should be used by
+ // preferences UI and be followed by a call to server.setUiPrefs to
+ // make sure they are persisted
+ T getGlobalValue();
+ void setGlobalValue(T value);
+ void setGlobalValue(T value, boolean fireEvents);
+
+ // explicit set for project values -- these are here so that the project
+ // options dialog can notify other modules that preferences have changed
+ // these values are not persisted by this module (rather, the project
+ // options dialog has its own codepath to read and write them along with
+ // the other non-uipref project options)
+ void setProjectValue(T value);
+ void setProjectValue(T value, boolean fireEvents);
+
+ HandlerRegistration bind(CommandWithArg<T> handler);
+ }
+
+ private abstract class JsonValue<T> implements PrefValue<T>
+ {
+ public JsonValue(String name, T defaultValue)
+ {
+ name_ = name;
+ defaultValue_ = defaultValue;
+ }
+
+ public HandlerRegistration bind(final CommandWithArg<T> handler)
+ {
+ HandlerRegistration reg = addValueChangeHandler(new ValueChangeHandler<T>()
+ {
+ public void onValueChange(ValueChangeEvent<T> e)
+ {
+ handler.execute(e.getValue());
+ }
+ });
+ handler.execute(getValue());
+ return reg;
+ }
+
+ public T getValue()
+ {
+ if (projectRoot_.hasKey(name_))
+ return doGetValue(projectRoot_);
+ else
+ return getGlobalValue();
+ }
+
+ public T getGlobalValue()
+ {
+ if (!globalRoot_.hasKey(name_))
+ return defaultValue_;
+ return doGetValue(globalRoot_);
+ }
+
+ public abstract T doGetValue(JsObject root);
+
+ public void setGlobalValue(T value)
+ {
+ setGlobalValue(value, true);
+ }
+
+ public void setGlobalValue(T value, boolean fireEvents)
+ {
+ setValue(globalRoot_, value, fireEvents);
+ }
+
+ public void setProjectValue(T value)
+ {
+ setProjectValue(value, true);
+ }
+
+ public void setProjectValue(T value, boolean fireEvents)
+ {
+ setValue(projectRoot_, value, fireEvents);
+ }
+
+ protected abstract void doSetValue(JsObject root, String name, T value);
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<T> handler)
+ {
+ return handlerManager_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlerManager_.fireEvent(event);
+ }
+
+ private void setValue(JsObject root, T value, boolean fireEvents)
+ {
+ T val = doGetValue(root);
+
+ if (value == null && val == null)
+ return;
+ if (value != null && val != null && value.equals(val))
+ return;
+
+ doSetValue(root, name_, value);
+ if (fireEvents)
+ ValueChangeEvent.fire(this, getValue());
+
+ }
+
+ protected final String name_;
+ private final T defaultValue_;
+ private final HandlerManager handlerManager_ = new HandlerManager(this);
+ }
+
+ private class BooleanValue extends JsonValue<Boolean>
+ {
+ private BooleanValue(String name, Boolean defaultValue)
+ {
+ super(name, defaultValue);
+ }
+
+ @Override
+ public Boolean doGetValue(JsObject root)
+ {
+ return root.getBoolean(name_);
+ }
+
+ @Override
+ protected void doSetValue(JsObject root, String name, Boolean value)
+ {
+ root.setBoolean(name, value);
+ }
+ }
+
+ private class IntValue extends JsonValue<Integer>
+ {
+ private IntValue(String name, Integer defaultValue)
+ {
+ super(name, defaultValue);
+ }
+
+ @Override
+ public Integer doGetValue(JsObject root)
+ {
+ return root.getInteger(name_);
+ }
+
+ @Override
+ protected void doSetValue(JsObject root, String name, Integer value)
+ {
+ root.setInteger(name, value);
+ }
+ }
+
+ private class DoubleValue extends JsonValue<Double>
+ {
+ private DoubleValue(String name, Double defaultValue)
+ {
+ super(name, defaultValue);
+ }
+
+ @Override
+ public Double doGetValue(JsObject root)
+ {
+ return root.getDouble(name_);
+ }
+
+ @Override
+ protected void doSetValue(JsObject root, String name, Double value)
+ {
+ root.setDouble(name, value);
+ }
+ }
+
+ private class StringValue extends JsonValue<String>
+ {
+ private StringValue(String name, String defaultValue)
+ {
+ super(name, defaultValue);
+ }
+
+ @Override
+ public String doGetValue(JsObject root)
+ {
+ return root.getString(name_);
+ }
+
+ @Override
+ protected void doSetValue(JsObject root, String name, String value)
+ {
+ root.setString(name, value);
+ }
+ }
+
+ private class ObjectValue<T extends JavaScriptObject> extends JsonValue<T>
+ {
+ private ObjectValue(String name)
+ {
+ super(name, null);
+ }
+
+ private ObjectValue(String name, T defaultValue)
+ {
+ super(name, defaultValue);
+ }
+
+ @Override
+ public T doGetValue(JsObject root)
+ {
+ return root.<T>getObject(name_);
+ }
+
+ @Override
+ protected void doSetValue(JsObject root, String name, T value)
+ {
+ root.setObject(name, value);
+ }
+ }
+
+ public Prefs(JsObject root, JsObject projectRoot)
+ {
+ globalRoot_ = root;
+ projectRoot_ = projectRoot;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrefValue<Boolean> bool(String name, boolean defaultValue)
+ {
+ PrefValue<Boolean> val = (PrefValue<Boolean>) values_.get(name);
+ if (val == null)
+ {
+ val = new BooleanValue(name, defaultValue);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrefValue<Integer> integer(String name, Integer defaultValue)
+ {
+ PrefValue<Integer> val = (PrefValue<Integer>) values_.get(name);
+ if (val == null)
+ {
+ val = new IntValue(name, defaultValue);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrefValue<Double> dbl(String name, Double defaultValue)
+ {
+ PrefValue<Double> val = (PrefValue<Double>) values_.get(name);
+ if (val == null)
+ {
+ val = new DoubleValue(name, defaultValue);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected PrefValue<String> string(String name, String defaultValue)
+ {
+ PrefValue<String> val = (PrefValue<String>) values_.get(name);
+ if (val == null)
+ {
+ val = new StringValue(name, defaultValue);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected <T> PrefValue<T> object(String name)
+ {
+ PrefValue<T> val = (PrefValue<T>) values_.get(name);
+ if (val == null)
+ {
+ val = new ObjectValue(name);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ protected <T extends JavaScriptObject> PrefValue<T> object(String name,
+ T defaultValue)
+ {
+ PrefValue<T> val = (PrefValue<T>) values_.get(name);
+ if (val == null)
+ {
+ val = new ObjectValue<T>(name, defaultValue);
+ values_.put(name, val);
+ }
+ return val;
+ }
+
+
+ private final JsObject globalRoot_;
+ private final JsObject projectRoot_;
+ private final HashMap<String, PrefValue<?>> values_ =
+ new HashMap<String, PrefValue<?>>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PrefsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PrefsServerOperations.java
new file mode 100644
index 0000000..797d752
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/PrefsServerOperations.java
@@ -0,0 +1,30 @@
+/*
+ * PrefsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public interface PrefsServerOperations
+{
+ void setPrefs(RPrefs rPrefs,
+ JavaScriptObject uiPrefs,
+ ServerRequestCallback<Void> requestCallback);
+
+ void setUiPrefs(JavaScriptObject uiPrefs,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/ProjectsPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/ProjectsPrefs.java
new file mode 100644
index 0000000..d68c3b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/ProjectsPrefs.java
@@ -0,0 +1,33 @@
+/*
+ * ProjectsPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ProjectsPrefs extends JavaScriptObject
+{
+ protected ProjectsPrefs() {}
+
+ public static final native ProjectsPrefs create(boolean restoreLastProject)
+ /*-{
+ var prefs = new Object();
+ prefs.restore_last_project = restoreLastProject;
+ return prefs ;
+ }-*/;
+
+ public native final boolean getRestoreLastProject() /*-{
+ return this.restore_last_project;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/RPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/RPrefs.java
new file mode 100644
index 0000000..17268e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/RPrefs.java
@@ -0,0 +1,86 @@
+/*
+ * RPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class RPrefs extends JavaScriptObject
+{
+ protected RPrefs() {}
+
+ public static native final RPrefs createEmpty() /*-{
+ var prefs = new Object();
+ this.general_prefs = new Object();
+ this.history_prefs = new Object();
+ this.packages_prefs = new Object();
+ this.projects_prefs = new Object();
+ this.source_control_prefs = new Object();
+ this.compile_pdf_prefs = new Object();
+ return prefs;
+ }-*/;
+
+ public native final GeneralPrefs getGeneralPrefs() /*-{
+ return this.general_prefs;
+ }-*/;
+
+ public native final void setGeneralPrefs(GeneralPrefs generalPrefs) /*-{
+ this.general_prefs = generalPrefs;
+ }-*/;
+
+ public native final HistoryPrefs getHistoryPrefs() /*-{
+ return this.history_prefs;
+ }-*/;
+
+ public native final void setHistoryPrefs(HistoryPrefs historyPrefs) /*-{
+ this.history_prefs = historyPrefs;
+ }-*/;
+
+ public native final PackagesPrefs getPackagesPrefs() /*-{
+ return this.packages_prefs;
+ }-*/;
+
+ public native final void setPackagesPrefs(PackagesPrefs packagesPrefs) /*-{
+ this.packages_prefs = packagesPrefs;
+ }-*/;
+
+ public native final ProjectsPrefs getProjectsPrefs() /*-{
+ return this.projects_prefs;
+ }-*/;
+
+ public native final void setProjectsPrefs(ProjectsPrefs projectsPrefs) /*-{
+ this.projects_prefs = projectsPrefs;
+ }-*/;
+
+ public native final SourceControlPrefs getSourceControlPrefs() /*-{
+ return this.source_control_prefs;
+ }-*/;
+
+ public native final void setSourceControlPrefs(
+ SourceControlPrefs sourceControlPrefs) /*-{
+ this.source_control_prefs = sourceControlPrefs;
+ }-*/;
+
+ public native final CompilePdfPrefs getCompilePdfPrefs() /*-{
+ return this.compile_pdf_prefs;
+ }-*/;
+
+ public native final void setCompilePdfPrefs(CompilePdfPrefs compilePdfPrefs) /*-{
+ this.compile_pdf_prefs = compilePdfPrefs;
+ }-*/;
+
+ public native final SpellingPrefsContext getSpellingPrefsContext() /*-{
+ return this.spelling_prefs_context;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SourceControlPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SourceControlPrefs.java
new file mode 100644
index 0000000..c6141cf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SourceControlPrefs.java
@@ -0,0 +1,68 @@
+/*
+ * SourceControlPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SourceControlPrefs extends JavaScriptObject
+{
+ protected SourceControlPrefs() {}
+
+ // create source control prefs -- don't pass the rsa_public_key_path
+ // and have_rsa_public_key parameters because they are read-only riders
+ // for the prefs ui
+ public static final native SourceControlPrefs create(boolean vcsEnabled,
+ String gitExePath,
+ String svnExePath,
+ String terminalPath,
+ boolean useGitBash)
+ /*-{
+ var prefs = new Object();
+ prefs.vcs_enabled = vcsEnabled;
+ prefs.git_exe_path = gitExePath;
+ prefs.svn_exe_path = svnExePath;
+ prefs.terminal_path = terminalPath;
+ prefs.use_git_bash = useGitBash;
+ return prefs ;
+ }-*/;
+
+ public native final boolean getVcsEnabled() /*-{
+ return this.vcs_enabled;
+ }-*/;
+
+ public native final String getGitExePath() /*-{
+ return this.git_exe_path;
+ }-*/;
+
+ public native final String getSvnExePath() /*-{
+ return this.svn_exe_path;
+ }-*/;
+
+ public native final String getTerminalPath() /*-{
+ return this.terminal_path;
+ }-*/;
+
+ public native final boolean getUseGitBash() /*-{
+ return this.use_git_bash;
+ }-*/;
+
+ public native final String getRsaKeyPath() /*-{
+ return this.rsa_key_path;
+ }-*/;
+
+ public native final boolean getHaveRsaKey() /*-{
+ return this.have_rsa_key;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SpellingPrefsContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SpellingPrefsContext.java
new file mode 100644
index 0000000..05346ec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/SpellingPrefsContext.java
@@ -0,0 +1,39 @@
+/*
+ * SpellingPrefsContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import org.rstudio.studio.client.common.spelling.model.SpellingLanguage;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+public class SpellingPrefsContext extends JavaScriptObject
+{
+ protected SpellingPrefsContext() {}
+
+ public native final boolean getAllLanguagesInstalled() /*-{
+ return this.all_languages_installed;
+ }-*/;
+
+ public native final JsArray<SpellingLanguage> getAvailableLanguages() /*-{
+ return this.available_languages;
+ }-*/;
+
+ public native final JsArrayString getCustomDictionaries() /*-{
+ return this.custom_dictionaries;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java
new file mode 100644
index 0000000..3347de2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java
@@ -0,0 +1,320 @@
+/*
+ * UIPrefs.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.notebook.CompileNotebookPrefs;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.events.UiPrefsChangedEvent;
+import org.rstudio.studio.client.workbench.prefs.events.UiPrefsChangedHandler;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
+
+ at Singleton
+public class UIPrefs extends UIPrefsAccessor implements UiPrefsChangedHandler
+{
+ @Inject
+ public UIPrefs(Session session,
+ EventBus eventBus,
+ PrefsServerOperations server)
+ {
+ super(session.getSessionInfo().getUiPrefs(),
+ session.getSessionInfo().getProjectUIPrefs());
+
+ session_ = session;
+ server_ = server;
+
+ eventBus.addHandler(UiPrefsChangedEvent.TYPE, this);
+ }
+
+ public void writeUIPrefs()
+ {
+ server_.setUiPrefs(
+ session_.getSessionInfo().getUiPrefs(),
+ new VoidServerRequestCallback());
+ }
+
+ @Override
+ public void onUiPrefsChanged(UiPrefsChangedEvent e)
+ {
+ if (e.getType().equals(UiPrefsChangedEvent.GLOBAL_TYPE))
+ {
+ // get prefs accessor
+ UIPrefsAccessor newUiPrefs = new UIPrefsAccessor(
+ e.getUIPrefs(),
+ JsObject.createJsObject());
+
+ // show line numbers
+ showLineNumbers().setGlobalValue(
+ newUiPrefs.showLineNumbers().getGlobalValue());
+
+ // highlight selected word
+ highlightSelectedWord().setGlobalValue(
+ newUiPrefs.highlightSelectedWord().getGlobalValue());
+
+ // highlight selected line
+ highlightSelectedLine().setGlobalValue(
+ newUiPrefs.highlightSelectedLine().getGlobalValue());
+
+ // pane config
+ if (!newUiPrefs.paneConfig().getGlobalValue().isEqualTo(
+ paneConfig().getGlobalValue()))
+ {
+ paneConfig().setGlobalValue(
+ newUiPrefs.paneConfig().getGlobalValue());
+ }
+
+ // use spaces for tab
+ useSpacesForTab().setGlobalValue(
+ newUiPrefs.useSpacesForTab().getGlobalValue());
+
+ // num spacers for tab
+ numSpacesForTab().setGlobalValue(
+ newUiPrefs.numSpacesForTab().getGlobalValue());
+
+ // blinking cursor
+ blinkingCursor().setGlobalValue(
+ newUiPrefs.blinkingCursor().getGlobalValue());
+
+ // show margin
+ showMargin().setGlobalValue(
+ newUiPrefs.showMargin().getGlobalValue());
+
+ // print margin column
+ printMarginColumn().setGlobalValue(
+ newUiPrefs.printMarginColumn().getGlobalValue());
+
+ // show invisibles
+ showInvisibles().setGlobalValue(
+ newUiPrefs.showInvisibles().getGlobalValue());
+
+ // show indent guides
+ showIndentGuides().setGlobalValue(
+ newUiPrefs.showIndentGuides().getGlobalValue());
+
+ // use vim mode
+ useVimMode().setGlobalValue(
+ newUiPrefs.useVimMode().getGlobalValue());
+
+ // insert matching
+ insertMatching().setGlobalValue(
+ newUiPrefs.insertMatching().getGlobalValue());
+
+ autoAppendNewline().setGlobalValue(
+ newUiPrefs.autoAppendNewline().getGlobalValue());
+
+ stripTrailingWhitespace().setGlobalValue(
+ newUiPrefs.stripTrailingWhitespace().getGlobalValue());
+
+ // soft wrap R files
+ softWrapRFiles().setGlobalValue(
+ newUiPrefs.softWrapRFiles().getGlobalValue());
+
+ // focus console after exec
+ focusConsoleAfterExec().setGlobalValue(
+ newUiPrefs.focusConsoleAfterExec().getGlobalValue());
+
+ // syntax color console
+ syntaxColorConsole().setGlobalValue(
+ newUiPrefs.syntaxColorConsole().getGlobalValue());
+
+ // save all before build
+ saveAllBeforeBuild().setGlobalValue(
+ newUiPrefs.saveAllBeforeBuild().getGlobalValue());
+
+ // font size
+ fontSize().setGlobalValue(
+ newUiPrefs.fontSize().getGlobalValue());
+
+ // theme
+ theme().setGlobalValue(newUiPrefs.theme().getGlobalValue());
+
+ // default encoding
+ defaultEncoding().setGlobalValue(
+ newUiPrefs.defaultEncoding().getGlobalValue());
+
+ // default project location
+ defaultProjectLocation().setGlobalValue(
+ newUiPrefs.defaultProjectLocation().getGlobalValue());
+
+ // toolbar visible
+ toolbarVisible().setGlobalValue(
+ newUiPrefs.toolbarVisible().getGlobalValue());
+
+ // source with echo
+ sourceWithEcho().setGlobalValue(
+ newUiPrefs.sourceWithEcho().getGlobalValue());
+
+ // clear hidden values in workspace
+ clearHidden().setGlobalValue(
+ newUiPrefs.clearHidden().getGlobalValue());
+
+ // export plot options
+ if (!ExportPlotOptions.areEqual(
+ newUiPrefs.exportPlotOptions().getGlobalValue(),
+ exportPlotOptions().getGlobalValue()))
+ {
+ exportPlotOptions().setGlobalValue(
+ newUiPrefs.exportPlotOptions().getGlobalValue());
+ }
+
+ // save plot as pdf options
+ if (!SavePlotAsPdfOptions.areEqual(
+ newUiPrefs.savePlotAsPdfOptions().getGlobalValue(),
+ savePlotAsPdfOptions().getGlobalValue()))
+ {
+ savePlotAsPdfOptions().setGlobalValue(
+ newUiPrefs.savePlotAsPdfOptions().getGlobalValue());
+ }
+
+ // compile notebook options
+ if (!CompileNotebookPrefs.areEqual(
+ newUiPrefs.compileNotebookOptions().getGlobalValue(),
+ compileNotebookOptions().getGlobalValue()))
+ {
+ compileNotebookOptions().setGlobalValue(
+ newUiPrefs.compileNotebookOptions().getGlobalValue());
+ }
+
+ // default sweave engine
+ defaultSweaveEngine().setGlobalValue(
+ newUiPrefs.defaultSweaveEngine().getGlobalValue());
+
+ // default latex program
+ defaultLatexProgram().setGlobalValue(
+ newUiPrefs.defaultLatexProgram().getGlobalValue());
+
+ // root document
+ rootDocument().setGlobalValue(
+ newUiPrefs.rootDocument().getGlobalValue());
+
+ // use roxygen
+ useRoxygen().setGlobalValue(
+ newUiPrefs.useRoxygen().getGlobalValue());
+
+ // pdf preview
+ pdfPreview().setGlobalValue(
+ newUiPrefs.pdfPreview().getGlobalValue());
+
+ // always enable rnw concordance
+ alwaysEnableRnwConcordance().setGlobalValue(
+ newUiPrefs.alwaysEnableRnwConcordance().getGlobalValue());
+
+ // insert numbered latex sections
+ insertNumberedLatexSections().setGlobalValue(
+ newUiPrefs.insertNumberedLatexSections().getGlobalValue());
+
+ // spelling dictionary language
+ spellingDictionaryLanguage().setGlobalValue(
+ newUiPrefs.spellingDictionaryLanguage().getGlobalValue());
+
+ // spelling custom dictionaries
+ if (!JsUtil.areEqual(
+ spellingCustomDictionaries().getGlobalValue(),
+ newUiPrefs.spellingCustomDictionaries().getGlobalValue()))
+ {
+ spellingCustomDictionaries().setGlobalValue(
+ newUiPrefs.spellingCustomDictionaries().getGlobalValue());
+ }
+
+ // ignore words in uppercase
+ ignoreWordsInUppercase().setGlobalValue(
+ newUiPrefs.ignoreWordsInUppercase().getGlobalValue());
+
+ // ignore words with numbers
+ ignoreWordsWithNumbers().setGlobalValue(
+ newUiPrefs.ignoreWordsWithNumbers().getGlobalValue());
+
+ // navigate to build error
+ navigateToBuildError().setGlobalValue(
+ newUiPrefs.navigateToBuildError().getGlobalValue());
+
+ // enable packages pane
+ packagesPaneEnabled().setGlobalValue(
+ newUiPrefs.packagesPaneEnabled().getGlobalValue());
+
+ // use rcpp template
+ useRcppTemplate().setGlobalValue(
+ newUiPrefs.useRcppTemplate().getGlobalValue());
+
+ // restore source documents
+ restoreSourceDocuments().setGlobalValue(
+ newUiPrefs.restoreSourceDocuments().getGlobalValue());
+
+ // break in user code only on unhandled errors
+ handleErrorsInUserCodeOnly().setGlobalValue(
+ newUiPrefs.handleErrorsInUserCodeOnly().getGlobalValue());
+
+ // auto expand error tracebacks
+ autoExpandErrorTracebacks().setGlobalValue(
+ newUiPrefs.autoExpandErrorTracebacks().getGlobalValue());
+ }
+ else if (e.getType().equals(UiPrefsChangedEvent.PROJECT_TYPE))
+ {
+ // get prefs accessor
+ UIPrefsAccessor newUiPrefs = new UIPrefsAccessor(
+ JsObject.createJsObject(),
+ e.getUIPrefs());
+
+ // use spaces for tab
+ useSpacesForTab().setProjectValue(
+ newUiPrefs.useSpacesForTab().getValue());
+
+ // num spaces for tab
+ numSpacesForTab().setProjectValue(
+ newUiPrefs.numSpacesForTab().getValue());
+
+ // auto-append newline
+ autoAppendNewline().setProjectValue(
+ newUiPrefs.autoAppendNewline().getValue());
+
+ // strip trailing whitespace
+ stripTrailingWhitespace().setProjectValue(
+ newUiPrefs.stripTrailingWhitespace().getValue());
+
+ // default encoding
+ defaultEncoding().setProjectValue(
+ newUiPrefs.defaultEncoding().getValue());
+
+ // default sweave engine
+ defaultSweaveEngine().setProjectValue(
+ newUiPrefs.defaultSweaveEngine().getValue());
+
+ // default latex program
+ defaultLatexProgram().setProjectValue(
+ newUiPrefs.defaultLatexProgram().getValue());
+
+ // root document
+ rootDocument().setProjectValue(newUiPrefs.rootDocument().getValue());
+
+ // use roxygen
+ useRoxygen().setProjectValue(newUiPrefs.useRoxygen().getValue());
+ }
+ else
+ {
+ Debug.log("Unexpected uiPrefs type: " + e.getType());
+ }
+ }
+
+ private final Session session_;
+ private final PrefsServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefsAccessor.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefsAccessor.java
new file mode 100644
index 0000000..4766b79
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefsAccessor.java
@@ -0,0 +1,367 @@
+/*
+ * UIPrefsAccessor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.model;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.notebook.CompileNotebookPrefs;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+import org.rstudio.studio.client.workbench.ui.PaneConfig;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+import com.google.gwt.core.client.JsArrayString;
+
+public class UIPrefsAccessor extends Prefs
+{
+ public UIPrefsAccessor(JsObject uiPrefs, JsObject projectUiPrefs)
+ {
+ super(uiPrefs, projectUiPrefs);
+ }
+
+ public PrefValue<Boolean> showLineNumbers()
+ {
+ return bool("show_line_numbers", true);
+ }
+
+ public PrefValue<Boolean> highlightSelectedWord()
+ {
+ return bool("highlight_selected_word", true);
+ }
+
+ public PrefValue<Boolean> highlightSelectedLine()
+ {
+ return bool("highlight_selected_line", false);
+ }
+
+ public PrefValue<PaneConfig> paneConfig()
+ {
+ return object("pane_config");
+ }
+
+ // NOTE: UserSettings.cpp depends on the name of this value
+ public PrefValue<Boolean> useSpacesForTab()
+ {
+ return bool("use_spaces_for_tab", true);
+ }
+
+ // NOTE: UserSettings.cpp depends on the name of this value
+ public PrefValue<Integer> numSpacesForTab()
+ {
+ return integer("num_spaces_for_tab", 2);
+ }
+
+ public PrefValue<Boolean> showMargin()
+ {
+ return bool("show_margin", false);
+ }
+
+ public PrefValue<Boolean> blinkingCursor()
+ {
+ return bool("blinking_cursor", true);
+ }
+
+ public PrefValue<Integer> printMarginColumn()
+ {
+ return integer("print_margin_column", 80);
+ }
+
+ public PrefValue<Boolean> showInvisibles()
+ {
+ return bool("show_invisibles", false);
+ }
+
+ public PrefValue<Boolean> showIndentGuides()
+ {
+ return bool("show_indent_guides", false);
+ }
+
+ public PrefValue<Boolean> useVimMode()
+ {
+ return bool("use_vim_mode", false);
+ }
+
+ public PrefValue<Boolean> insertMatching()
+ {
+ return bool("insert_matching", true);
+ }
+
+ public PrefValue<Boolean> autoAppendNewline()
+ {
+ return bool("auto_append_newline", false);
+ }
+
+ public PrefValue<Boolean> stripTrailingWhitespace()
+ {
+ return bool("strip_trailing_whitespace", false);
+ }
+
+ public PrefValue<Boolean> reindentOnPaste()
+ {
+ return bool("reindent_on_paste", true);
+ }
+
+ public PrefValue<Boolean> verticallyAlignArgumentIndent()
+ {
+ return bool("valign_argument_indent", true);
+ }
+
+ public PrefValue<Boolean> softWrapRFiles()
+ {
+ return bool("soft_wrap_r_files", false);
+ }
+
+ public PrefValue<Boolean> focusConsoleAfterExec()
+ {
+ return bool("focus_console_after_exec", false);
+ }
+
+ public PrefValue<Boolean> syntaxColorConsole()
+ {
+ return bool("syntax_color_console", false);
+ }
+
+ public PrefValue<Boolean> saveAllBeforeBuild()
+ {
+ return bool("save_files_before_build", false);
+ }
+
+ public PrefValue<Double> fontSize()
+ {
+ return dbl("font_size_points", 10.0);
+ }
+
+ public PrefValue<String> theme()
+ {
+ return string("theme", null);
+ }
+
+ public String getThemeErrorClass()
+ {
+ if ((theme().getValue() == null) ||
+ AceThemes.TEXTMATE.equals(theme().getValue()))
+ return " ace_constant";
+ else
+ return " ace_constant ace_language";
+ }
+
+ // NOTE: UserSettings.cpp depends on the name of this value
+ public PrefValue<String> defaultEncoding()
+ {
+ return string("default_encoding", "");
+ }
+
+ public PrefValue<String> defaultProjectLocation()
+ {
+ return string("default_project_location", FileSystemItem.HOME_PATH);
+ }
+
+ public PrefValue<Boolean> toolbarVisible()
+ {
+ return bool("toolbar_visible", true);
+ }
+
+ public PrefValue<Boolean> sourceWithEcho()
+ {
+ return bool("source_with_echo", false);
+ }
+
+ public PrefValue<Boolean> clearHidden()
+ {
+ return bool("clear_hidden", false);
+ }
+
+ public PrefValue<ExportPlotOptions> exportPlotOptions()
+ {
+ return object("export_plot_options", ExportPlotOptions.createDefault());
+ }
+
+ public PrefValue<SavePlotAsPdfOptions> savePlotAsPdfOptions()
+ {
+ return object("save_plot_as_pdf_options",
+ SavePlotAsPdfOptions.createDefault());
+ }
+
+ public PrefValue<CompileNotebookPrefs> compileNotebookOptions()
+ {
+ return object("compile_notebook_options",
+ CompileNotebookPrefs.createDefault());
+ }
+
+ public PrefValue<Boolean> newProjGitInit()
+ {
+ return bool("new_proj_git_init", false);
+ }
+
+ public PrefValue<String> defaultSweaveEngine()
+ {
+ return string("default_sweave_engine", "Sweave");
+ }
+
+ public PrefValue<String> defaultLatexProgram()
+ {
+ return string("default_latex_program", "pdfLaTeX");
+ }
+
+ public PrefValue<String> rootDocument()
+ {
+ return string("root_document", "");
+ }
+
+ public PrefValue<Boolean> useRoxygen()
+ {
+ return bool("use_roxygen", false);
+ }
+
+ public static final String PDF_PREVIEW_NONE = "none";
+ public static final String PDF_PREVIEW_RSTUDIO = "rstudio";
+ public static final String PDF_PREVIEW_DESKTOP_SYNCTEX = "desktop-synctex";
+ public static final String PDF_PREVIEW_SYSTEM = "system";
+
+ public PrefValue<String> pdfPreview()
+ {
+ return string("pdf_previewer", getDefaultPdfPreview());
+ }
+
+ // provide a straight value accessor for pdfPreview which will
+ // automatically prevent the use of the internal viewer on osx
+ public String getPdfPreviewValue()
+ {
+ // get the underlying value
+ String pdfPreview = pdfPreview().getValue();
+
+ // the internal viewer has stability issues on the mac
+ // so re-route to system viewer
+ if (BrowseCap.isMacintoshDesktop())
+ {
+ if (pdfPreview.equals(PDF_PREVIEW_RSTUDIO))
+ pdfPreview = PDF_PREVIEW_SYSTEM;
+ }
+
+ // return the (potentially) adjusted value
+ return pdfPreview;
+ }
+
+ public PrefValue<Boolean> alwaysEnableRnwConcordance()
+ {
+ return bool("always_enable_concordance", true);
+ }
+
+ public PrefValue<Boolean> insertNumberedLatexSections()
+ {
+ return bool("insert_numbered_latex_sections", false);
+ }
+
+ public PrefValue<String> spellingDictionaryLanguage()
+ {
+ return string("spelling_dictionary_language", "en_US");
+ }
+
+ public PrefValue<JsArrayString> spellingCustomDictionaries()
+ {
+ return object("spelling_custom_dictionaries",
+ JsArrayString.createArray().<JsArrayString>cast());
+ }
+
+ public PrefValue<Boolean> ignoreWordsInUppercase()
+ {
+ return bool("ignore_uppercase_words", true);
+ }
+
+ public PrefValue<Boolean> ignoreWordsWithNumbers()
+ {
+ return bool("ignore_words_with_numbers", true);
+ }
+
+ public PrefValue<Boolean> navigateToBuildError()
+ {
+ return bool("navigate_to_build_error", true);
+ }
+
+ public PrefValue<Boolean> packagesPaneEnabled()
+ {
+ return bool("packages_pane_enabled", true);
+ }
+
+ public PrefValue<Boolean> useRcppTemplate()
+ {
+ return bool("use_rcpp_template", true);
+ }
+
+ public PrefValue<Boolean> restoreSourceDocuments()
+ {
+ return bool("restore_source_documents", true);
+ }
+
+ public PrefValue<Boolean> handleErrorsInUserCodeOnly()
+ {
+ return bool("handle_errors_in_user_code_only", true);
+ }
+
+ public PrefValue<Boolean> autoExpandErrorTracebacks()
+ {
+ return bool("auto_expand_error_tracebacks", false);
+ }
+
+ public PrefValue<Boolean> checkForUpdates()
+ {
+ return bool("check_for_updates", true);
+ }
+
+ public PrefValue<Boolean> showInternalFunctionsInTraceback()
+ {
+ return bool("show_internal_functions", false);
+ }
+
+ public PrefValue<Integer> shinyViewerType()
+ {
+ return integer("shiny_viewer_type", ShinyViewerType.SHINY_VIEWER_WINDOW);
+ }
+
+ private String getDefaultPdfPreview()
+ {
+ if (Desktop.isDesktop())
+ {
+ // if there is a desktop synctex viewer available then default to it
+ if (Desktop.getFrame().getDesktopSynctexViewer().length() > 0)
+ {
+ return PDF_PREVIEW_DESKTOP_SYNCTEX;
+ }
+
+ // otherwise default to the system viewer on linux and the internal
+ // viewer on mac (windows will always have a desktop synctex viewer)
+ else
+ {
+ if (BrowseCap.isLinux())
+ {
+ return PDF_PREVIEW_SYSTEM;
+ }
+ else
+ {
+ return PDF_PREVIEW_RSTUDIO;
+ }
+ }
+ }
+
+ // web mode -- always default to internal viewer
+ else
+ {
+ return PDF_PREVIEW_RSTUDIO;
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AceEditorPreview.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AceEditorPreview.java
new file mode 100644
index 0000000..5e972ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AceEditorPreview.java
@@ -0,0 +1,184 @@
+/*
+ * AceEditorPreview.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Unit;
+import org.rstudio.core.client.ExternalJavaScriptLoader;
+import org.rstudio.core.client.ExternalJavaScriptLoader.Callback;
+import org.rstudio.core.client.theme.ThemeFonts;
+import org.rstudio.core.client.widget.DynamicIFrame;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceResources;
+
+public class AceEditorPreview extends DynamicIFrame
+{
+ public AceEditorPreview(String code)
+ {
+ code_ = code;
+ Style style = getStyleElement().getStyle();
+ style.setBorderColor("#CCC");
+ style.setBorderWidth(1, Unit.PX);
+ style.setBorderStyle(BorderStyle.SOLID);
+ }
+
+ @Override
+ protected void onFrameLoaded()
+ {
+ isFrameLoaded_ = true;
+ if (themeUrl_ != null)
+ setTheme(themeUrl_);
+ if (fontSize_ != null)
+ setFontSize(fontSize_);
+ if (zoomLevel_ != null)
+ setZoomLevel(zoomLevel_);
+
+ final Document doc = getDocument();
+ final BodyElement body = doc.getBody();
+ body.getStyle().setMargin(0, Unit.PX);
+ body.getStyle().setBackgroundColor("white");
+
+ StyleElement style = doc.createStyleElement();
+ style.setType("text/css");
+ style.setInnerText(
+ ".ace_editor {\n" +
+ "border: none !important;\n" +
+ "}");
+ setFont(ThemeFonts.getFixedWidthFont());
+ body.appendChild(style);
+
+ DivElement div = doc.createDivElement();
+ div.setId("editor");
+ div.getStyle().setWidth(100, Unit.PCT);
+ div.getStyle().setHeight(100, Unit.PCT);
+ div.setInnerText(code_);
+ body.appendChild(div);
+
+ FontSizer.injectStylesIntoDocument(doc);
+ FontSizer.applyNormalFontSize(div);
+
+ new ExternalJavaScriptLoader(doc, AceResources.INSTANCE.acejs().getSafeUri().asString())
+ .addCallback(new Callback()
+ {
+ public void onLoaded()
+ {
+ new ExternalJavaScriptLoader(doc, AceResources.INSTANCE.acesupportjs().getSafeUri().asString())
+ .addCallback(new Callback()
+ {
+ public void onLoaded()
+ {
+ body.appendChild(doc.createScriptElement(
+ "var editor = ace.edit('editor');\n" +
+ "editor.renderer.setHScrollBarAlwaysVisible(false);\n" +
+ "editor.renderer.setTheme({});\n" +
+ "editor.setHighlightActiveLine(false);\n" +
+ "editor.renderer.setShowGutter(false);\n" +
+ "editor.renderer.setDisplayIndentGuides(false);\n" +
+ "var RMode = require('mode/r').Mode;\n" +
+ "editor.getSession().setMode(new RMode(false, editor.getSession().getDocument()));"));
+ }
+ });
+ }
+ });
+ }
+
+ public void setTheme(String themeUrl)
+ {
+ themeUrl_ = themeUrl;
+ if (!isFrameLoaded_)
+ return;
+
+ if (currentStyleLink_ != null)
+ currentStyleLink_.removeFromParent();
+
+ Document doc = getDocument();
+ currentStyleLink_ = doc.createLinkElement();
+ currentStyleLink_.setRel("stylesheet");
+ currentStyleLink_.setType("text/css");
+ currentStyleLink_.setHref(themeUrl);
+ doc.getBody().appendChild(currentStyleLink_);
+ }
+
+ public void setFontSize(double fontSize)
+ {
+ fontSize_ = fontSize;
+ if (!isFrameLoaded_)
+ return;
+
+ FontSizer.setNormalFontSize(getDocument(), fontSize);
+ }
+
+ public void setFont(String font)
+ {
+ final String STYLE_EL_ID = "__rstudio_font_family";
+ Document document = getDocument();
+
+ Element oldStyle = document.getElementById(STYLE_EL_ID);
+
+ StyleElement style = document.createStyleElement();
+ style.setAttribute("type", "text/css");
+ style.setInnerText(".ace_editor, .ace_text-layer {\n" +
+ "font-family: " + font + " !important;\n" +
+ "}");
+
+ document.getBody().appendChild(style);
+
+ if (oldStyle != null)
+ oldStyle.removeFromParent();
+
+ style.setId(STYLE_EL_ID);
+ }
+
+ public void setZoomLevel(double zoomLevel)
+ {
+ zoomLevel_ = zoomLevel;
+ if (!isFrameLoaded_)
+ return;
+
+ final String STYLE_EL_ID = "__rstudio_zoom_level";
+ Document document = getDocument();
+
+ Element oldStyle = document.getElementById(STYLE_EL_ID);
+
+ StyleElement style = document.createStyleElement();
+ style.setAttribute("type", "text/css");
+ String zoom = Double.toString(zoomLevel);
+ style.setInnerText(".ace_line {\n" +
+ " -webkit-transform: scale(" + zoom + ");\n" +
+ "}");
+
+ document.getBody().appendChild(style);
+
+ if (oldStyle != null)
+ oldStyle.removeFromParent();
+
+ style.setId(STYLE_EL_ID);
+ }
+
+ public void reload()
+ {
+ getWindow().reload();
+ }
+
+
+
+ private LinkElement currentStyleLink_;
+ private boolean isFrameLoaded_;
+ private String themeUrl_;
+ private Double fontSize_;
+ private Double zoomLevel_;
+ private final String code_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AppearancePreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AppearancePreferencesPane.java
new file mode 100644
index 0000000..96d1a9b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/AppearancePreferencesPane.java
@@ -0,0 +1,276 @@
+/*
+ * AppearancePreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.theme.ThemeFonts;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.editors.text.themes.AceThemes;
+
+public class AppearancePreferencesPane extends PreferencesPane
+{
+ @Inject
+ public AppearancePreferencesPane(PreferencesDialogResources res,
+ UIPrefs uiPrefs,
+ final AceThemes themes)
+ {
+ res_ = res;
+ uiPrefs_ = uiPrefs;
+
+ VerticalPanel leftPanel = new VerticalPanel();
+
+ if (Desktop.isDesktop())
+ {
+ // no zoom level on cocoa desktop
+ if (!BrowseCap.isCocoaDesktop())
+ {
+ int initialIndex = -1;
+ int normalIndex = -1;
+ String[] zoomValues =
+ Desktop.getFrame().getZoomLevels().split("\\n");
+ String[] zoomLabels = new String[zoomValues.length];
+ for (int i=0; i<zoomValues.length; i++)
+ {
+ double zoomValue = Double.parseDouble(zoomValues[i]);
+
+ if (zoomValue == 1.0)
+ normalIndex = i;
+
+ if (zoomValue == Desktop.getFrame().getZoomLevel())
+ initialIndex = i;
+
+ zoomLabels[i] = StringUtil.formatPercent(zoomValue);
+ }
+
+ if (initialIndex == -1)
+ initialIndex = normalIndex;
+
+ zoomLevel_ = new SelectWidget("Zoom:",
+ zoomLabels,
+ zoomValues,
+ false);
+ zoomLevel_.getListBox().setSelectedIndex(initialIndex);
+ initialZoomLevel_ = zoomValues[initialIndex];
+
+ leftPanel.add(zoomLevel_);
+
+ zoomLevel_.getListBox().addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ updatePreviewZoomLevel();
+ preview_.reload();
+ }
+ });
+ }
+
+ String[] fonts = Desktop.getFrame().getFixedWidthFontList().split("\\n");
+
+ fontFace_ = new SelectWidget("Editor font:", fonts, fonts, false, false, false);
+
+ String value = Desktop.getFrame().getFixedWidthFont();
+ String label = Desktop.getFrame().getFixedWidthFont().replaceAll("\\\"",
+ "");
+ if (!fontFace_.setValue(label))
+ {
+ fontFace_.insertValue(0, label, value);
+ fontFace_.setValue(value);
+ }
+ initialFontFace_ = StringUtil.notNull(fontFace_.getValue());
+ leftPanel.add(fontFace_);
+ fontFace_.addChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ String font = fontFace_.getValue();
+ if (font != null)
+ preview_.setFont(font);
+ else
+ preview_.setFont(ThemeFonts.getFixedWidthFont());
+ }
+ });
+ }
+
+ String[] labels = {"7", "8", "9", "10", "11", "12", "13", "14", "16", "18", "24", "36"};
+ String[] values = new String[labels.length];
+ for (int i = 0; i < labels.length; i++)
+ values[i] = Double.parseDouble(labels[i]) + "";
+
+ fontSize_ = new SelectWidget("Font size:",
+ labels,
+ values,
+ false);
+ if (!fontSize_.setValue(uiPrefs.fontSize().getGlobalValue() + ""))
+ fontSize_.getListBox().setSelectedIndex(3);
+ fontSize_.getListBox().addChangeHandler(new ChangeHandler()
+ {
+ public void onChange(ChangeEvent event)
+ {
+ preview_.setFontSize(Double.parseDouble(fontSize_.getValue()));
+ }
+ });
+
+ leftPanel.add(fontSize_);
+
+ theme_ = new SelectWidget("Editor theme:",
+ themes.getThemeNames(),
+ themes.getThemeNames(),
+ true);
+ theme_.getListBox().addChangeHandler(new ChangeHandler()
+ {
+ public void onChange(ChangeEvent event)
+ {
+ preview_.setTheme(themes.getThemeUrl(theme_.getValue()));
+ }
+ });
+ theme_.getListBox().getElement().<SelectElement>cast().setSize(
+ themes.getThemeNames().length);
+ theme_.addStyleName(res.styles().themeChooser());
+ leftPanel.add(theme_);
+ theme_.setValue(themes.getEffectiveThemeName(uiPrefs_.theme().getGlobalValue()));
+
+ FlowPanel previewPanel = new FlowPanel();
+ previewPanel.setSize("100%", "100%");
+ preview_ = new AceEditorPreview(CODE_SAMPLE);
+ preview_.setHeight("375px");
+ preview_.setWidth("278px");
+ preview_.setTheme(themes.getThemeUrl(uiPrefs_.theme().getGlobalValue()));
+ preview_.setFontSize(Double.parseDouble(fontSize_.getValue()));
+ updatePreviewZoomLevel();
+ previewPanel.add(preview_);
+
+ HorizontalPanel hpanel = new HorizontalPanel();
+ hpanel.setWidth("100%");
+ hpanel.add(leftPanel);
+ hpanel.setCellWidth(leftPanel, "160px");
+ hpanel.add(previewPanel);
+
+ add(hpanel);
+ }
+
+
+ private void updatePreviewZoomLevel()
+ {
+ // no zoom preview on desktop
+ if (Desktop.isDesktop() && !Desktop.getFrame().isCocoa())
+ {
+ preview_.setZoomLevel(Double.parseDouble(zoomLevel_.getValue()) /
+ Desktop.getFrame().getZoomLevel());
+ }
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return res_.iconAppearance();
+ }
+
+ @Override
+ protected void initialize(RPrefs prefs)
+ {
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean restartRequired = super.onApply(rPrefs);
+
+ double fontSize = Double.parseDouble(fontSize_.getValue());
+ uiPrefs_.fontSize().setGlobalValue(fontSize);
+ uiPrefs_.theme().setGlobalValue(theme_.getValue());
+ if (Desktop.isDesktop())
+ {
+ if (!initialFontFace_.equals(fontFace_.getValue()))
+ {
+ Desktop.getFrame().setFixedWidthFont(fontFace_.getValue());
+ restartRequired = true;
+ }
+
+ if (!Desktop.getFrame().isCocoa())
+ {
+ if (!initialZoomLevel_.equals(zoomLevel_.getValue()))
+ {
+ double zoomLevel = Double.parseDouble(zoomLevel_.getValue());
+ Desktop.getFrame().setZoomLevel(zoomLevel);
+ restartRequired = true;
+ }
+ }
+ }
+
+ return restartRequired;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Appearance";
+ }
+
+ private final PreferencesDialogResources res_;
+ private final UIPrefs uiPrefs_;
+ private SelectWidget fontSize_;
+ private SelectWidget theme_;
+ private AceEditorPreview preview_;
+ private SelectWidget fontFace_;
+ private String initialFontFace_;
+ private SelectWidget zoomLevel_;
+ private String initialZoomLevel_;
+
+ private static final String CODE_SAMPLE =
+ "# plotting of R objects\n" +
+ "plot <- function (x, y, ...)\n" +
+ "{\n" +
+ " if (is.function(x) && \n" +
+ " is.null(attr(x, \"class\")))\n" +
+ " {\n" +
+ " if (missing(y))\n" +
+ " y <- NULL\n" +
+ " \n" +
+ " # check for ylab argument\n" +
+ " hasylab <- function(...) \n" +
+ " !all(is.na(\n" +
+ " pmatch(names(list(...)),\n" +
+ " \"ylab\")))\n" +
+ " \n" +
+ " if (hasylab(...))\n" +
+ " plot.function(x, y, ...)\n" +
+ " \n" +
+ " else \n" +
+ " plot.function(\n" +
+ " x, y, \n" +
+ " ylab = paste(\n" +
+ " deparse(substitute(x)),\n" +
+ " \"(x)\"), \n" +
+ " ...)\n" +
+ " }\n" +
+ " else \n" +
+ " UseMethod(\"plot\")\n" +
+ "}\n";
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CheckBoxPrefView.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CheckBoxPrefView.java
new file mode 100644
index 0000000..857936a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CheckBoxPrefView.java
@@ -0,0 +1,21 @@
+/*
+ * CheckBoxPrefView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.user.client.ui.CheckBox;
+
+public class CheckBoxPrefView extends CheckBox
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CompilePdfPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CompilePdfPreferencesPane.java
new file mode 100644
index 0000000..ce0c787
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/CompilePdfPreferencesPane.java
@@ -0,0 +1,210 @@
+/*
+ * CompilePdfPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.HelpButton;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.studio.client.common.latex.LatexProgramSelectWidget;
+import org.rstudio.studio.client.common.rnw.RnwWeaveSelectWidget;
+import org.rstudio.studio.client.common.synctex.SynctexUtils;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.CompilePdfPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefsAccessor;
+
+public class CompilePdfPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public CompilePdfPreferencesPane(UIPrefs prefs,
+ PreferencesDialogResources res)
+ {
+ prefs_ = prefs;
+ res_ = res;
+ PreferencesDialogBaseResources baseRes = PreferencesDialogBaseResources.INSTANCE;
+
+ Label programDefaultsLabel = new Label(
+ "Program defaults (when not in a project)");
+ programDefaultsLabel.addStyleName(baseRes.styles().headerLabel());
+ nudgeRight(programDefaultsLabel);
+ add(programDefaultsLabel);
+
+ defaultSweaveEngine_ = new RnwWeaveSelectWidget();
+ defaultSweaveEngine_.setValue(
+ prefs.defaultSweaveEngine().getGlobalValue());
+ add(defaultSweaveEngine_);
+
+ defaultLatexProgram_ = new LatexProgramSelectWidget();
+ defaultLatexProgram_.setValue(
+ prefs.defaultLatexProgram().getGlobalValue());
+ add(defaultLatexProgram_);
+
+ Label perProjectLabel = new Label(
+ "NOTE: The Rnw weave and LaTeX compilation options are also set on a " +
+ "per-project (and optionally per-file) basis. Click the help " +
+ "icons above for more details.");
+
+ perProjectLabel.addStyleName(baseRes.styles().infoLabel());
+ nudgeRight(perProjectLabel);
+ spaced(perProjectLabel);
+ add(perProjectLabel);
+
+ Label compilationOptionsLabel = new Label("LaTeX editing and compilation");
+ compilationOptionsLabel.addStyleName(baseRes.styles().headerLabel());
+ nudgeRight(compilationOptionsLabel);
+ add(compilationOptionsLabel);
+ chkCleanTexi2DviOutput_ = new CheckBox(
+ "Clean auxiliary output after compile");
+ spaced(chkCleanTexi2DviOutput_);
+ add(chkCleanTexi2DviOutput_);
+
+ chkEnableShellEscape_ = new CheckBox("Enable shell escape commands");
+ spaced(chkEnableShellEscape_);
+ add(chkEnableShellEscape_);
+
+ CheckBox chkNumberedSections = checkboxPref(
+ "Insert numbered sections and subsections",
+ prefs_.insertNumberedLatexSections());
+ spaced(chkNumberedSections);
+ add(chkNumberedSections);
+
+ Label previwingOptionsLabel = new Label("PDF preview");
+ previwingOptionsLabel.addStyleName(baseRes.styles().headerLabel());
+ previwingOptionsLabel.getElement().getStyle().setMarginTop(8, Unit.PX);
+ nudgeRight(previwingOptionsLabel);
+ add(previwingOptionsLabel);
+
+ pdfPreview_ = new PdfPreviewSelectWidget();
+ add(pdfPreview_);
+
+ CheckBox chkConcordance = checkboxPref(
+ "Always enable Rnw concordance (required for synctex)",
+ prefs_.alwaysEnableRnwConcordance());
+ spaced(chkConcordance);
+ add(chkConcordance);
+ }
+
+ private class PdfPreviewSelectWidget extends SelectWidget
+ {
+ public PdfPreviewSelectWidget()
+ {
+ super(
+ "Preview PDF after compile using:",
+ new String[]{},
+ new String[]{},
+ false,
+ true,
+ false);
+
+ HelpButton.addHelpButton(this, "pdf_preview");
+ }
+ }
+
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconCompilePdf();
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Sweave";
+ }
+
+ @Override
+ protected void initialize(RPrefs prefs)
+ {
+ CompilePdfPrefs compilePdfPrefs = prefs.getCompilePdfPrefs();
+ chkCleanTexi2DviOutput_.setValue(compilePdfPrefs.getCleanOutput());
+ chkEnableShellEscape_.setValue(compilePdfPrefs.getEnableShellEscape());
+
+ pdfPreview_.addChoice("(No Preview)", UIPrefsAccessor.PDF_PREVIEW_NONE);
+
+ String desktopSynctexViewer = SynctexUtils.getDesktopSynctexViewer();
+ if (desktopSynctexViewer.length() > 0)
+ {
+ pdfPreview_.addChoice(desktopSynctexViewer + " (Recommended)",
+ UIPrefsAccessor.PDF_PREVIEW_DESKTOP_SYNCTEX);
+ }
+
+ pdfPreview_.addChoice("RStudio Viewer",
+ UIPrefsAccessor.PDF_PREVIEW_RSTUDIO);
+
+ pdfPreview_.addChoice("System Viewer",
+ UIPrefsAccessor.PDF_PREVIEW_SYSTEM);
+
+ pdfPreview_.setValue(prefs_.pdfPreview().getValue());
+
+ // workaround qt crash on mac desktop
+ if (BrowseCap.isMacintoshDesktop())
+ {
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ defaultSweaveEngine_.getListBox());
+
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ defaultLatexProgram_.getListBox());
+ }
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean requiresRestart = super.onApply(rPrefs);
+
+ prefs_.defaultSweaveEngine().setGlobalValue(
+ defaultSweaveEngine_.getValue());
+ prefs_.defaultLatexProgram().setGlobalValue(
+ defaultLatexProgram_.getValue());
+
+ prefs_.pdfPreview().setGlobalValue(pdfPreview_.getValue());
+
+ CompilePdfPrefs prefs = CompilePdfPrefs.create(
+ chkCleanTexi2DviOutput_.getValue(),
+ chkEnableShellEscape_.getValue());
+ rPrefs.setCompilePdfPrefs(prefs);
+
+ return requiresRestart;
+ }
+
+ private final UIPrefs prefs_;
+
+ @SuppressWarnings("unused")
+ private final PreferencesDialogResources res_;
+
+ private RnwWeaveSelectWidget defaultSweaveEngine_;
+ private LatexProgramSelectWidget defaultLatexProgram_;
+ private CheckBox chkCleanTexi2DviOutput_;
+ private CheckBox chkEnableShellEscape_;
+ private PdfPreviewSelectWidget pdfPreview_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/EditingPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/EditingPreferencesPane.java
new file mode 100644
index 0000000..23d675c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/EditingPreferencesPane.java
@@ -0,0 +1,85 @@
+/*
+ * EditingPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.inject.Inject;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.NumericValueWidget;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+public class EditingPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public EditingPreferencesPane(UIPrefs prefs)
+ {
+ prefs_ = prefs;
+
+ add(checkboxPref("Highlight selected word", prefs.highlightSelectedWord()));
+ add(checkboxPref("Highlight selected line", prefs.highlightSelectedLine()));
+ add(checkboxPref("Show line numbers", prefs.showLineNumbers()));
+ add(tight(spacesForTab_ = checkboxPref("Insert spaces for tab", prefs.useSpacesForTab())));
+ add(indent(tabWidth_ = numericPref("Tab width", prefs.numSpacesForTab())));
+ add(tight(showMargin_ = checkboxPref("Show margin", prefs.showMargin())));
+ add(indent(marginCol_ = numericPref("Margin column", prefs.printMarginColumn())));
+ add(checkboxPref("Show whitespace characters", prefs_.showInvisibles()));
+ add(checkboxPref("Show indent guides", prefs_.showIndentGuides()));
+ add(checkboxPref("Blinking cursor", prefs_.blinkingCursor()));
+ add(checkboxPref("Insert matching parens/quotes", prefs_.insertMatching()));
+ add(checkboxPref("Auto-indent code after paste", prefs_.reindentOnPaste()));
+ add(checkboxPref("Vertically align arguments in auto-indent", prefs_.verticallyAlignArgumentIndent()));
+ add(checkboxPref("Soft-wrap R source files", prefs_.softWrapRFiles()));
+ add(checkboxPref("Ensure that source files end with newline", prefs_.autoAppendNewline()));
+ add(checkboxPref("Strip trailing horizontal whitespace when saving", prefs_.stripTrailingWhitespace()));
+ add(checkboxPref("Focus console after executing from source", prefs_.focusConsoleAfterExec()));
+ add(checkboxPref("Show syntax highlighting in console input", prefs_.syntaxColorConsole()));
+ add(checkboxPref("Enable vim editing mode", prefs_.useVimMode()));
+ }
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconCodeEditing();
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return (!spacesForTab_.getValue() || tabWidth_.validatePositive("Tab width")) &&
+ (!showMargin_.getValue() || marginCol_.validate("Margin column"));
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Code Editing";
+ }
+
+ @Override
+ protected void initialize(RPrefs prefs)
+ {
+ }
+
+
+ private final UIPrefs prefs_;
+ private final NumericValueWidget tabWidth_;
+ private final NumericValueWidget marginCol_;
+ private final CheckBox spacesForTab_;
+ private final CheckBox showMargin_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/GeneralPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/GeneralPreferencesPane.java
new file mode 100644
index 0000000..fc1464a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/GeneralPreferencesPane.java
@@ -0,0 +1,344 @@
+/*
+ * GeneralPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.SelectWidget;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.model.SaveAction;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.GeneralPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.HistoryPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.ProjectsPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.editors.text.IconvListResult;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ui.ChooseEncodingDialog;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+public class GeneralPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public GeneralPreferencesPane(RemoteFileSystemContext fsContext,
+ FileDialogs fileDialogs,
+ UIPrefs prefs,
+ Session session,
+ final GlobalDisplay globalDisplay,
+ SourceServerOperations server)
+ {
+ fsContext_ = fsContext;
+ fileDialogs_ = fileDialogs;
+ prefs_ = prefs;
+ server_ = server;
+ session_ = session;
+
+ if (Desktop.isDesktop())
+ {
+ if (Desktop.getFrame().canChooseRVersion())
+ {
+ rVersion_ = new TextBoxWithButton(
+ "R version:",
+ "Change...",
+ new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ String ver = Desktop.getFrame().chooseRVersion();
+ if (!StringUtil.isNullOrEmpty(ver))
+ {
+ rVersion_.setText(ver);
+
+ globalDisplay.showMessage(MessageDialog.INFO,
+ "Change R Version",
+ "You need to quit and re-open RStudio " +
+ "in order for this change to take effect.");
+ }
+ }
+ });
+ rVersion_.setWidth("100%");
+ rVersion_.setText(Desktop.getFrame().getRVersion());
+ spaced(rVersion_);
+ add(rVersion_);
+ }
+ }
+
+ Label defaultLabel = new Label("Default working directory (when not in a project):");
+ nudgeRight(defaultLabel);
+ add(tight(defaultLabel));
+ add(dirChooser_ = new DirectoryChooserTextBox(null,
+ null,
+ fileDialogs_,
+ fsContext_));
+ spaced(dirChooser_);
+ nudgeRight(dirChooser_);
+ textBoxWithChooser(dirChooser_);
+
+ restoreLastProject_ = new CheckBox("Restore most recently opened project at startup");
+ lessSpaced(restoreLastProject_);
+ add(restoreLastProject_);
+
+ add(checkboxPref("Restore previously open source documents at startup", prefs_.restoreSourceDocuments()));
+
+ add(loadRData_ = new CheckBox("Restore .RData into workspace at startup"));
+ lessSpaced(loadRData_);
+
+ saveWorkspace_ = new SelectWidget(
+ "Save workspace to .RData on exit:",
+ new String[] {
+ "Always",
+ "Never",
+ "Ask"
+ });
+ spaced(saveWorkspace_);
+ add(saveWorkspace_);
+
+ alwaysSaveHistory_ = new CheckBox(
+ "Always save history (even when not saving .RData)");
+ lessSpaced(alwaysSaveHistory_);
+ add(alwaysSaveHistory_);
+
+ removeHistoryDuplicates_ = new CheckBox(
+ "Remove duplicate entries in history");
+ spaced(removeHistoryDuplicates_);
+ add(removeHistoryDuplicates_);
+
+ rProfileOnResume_ = new CheckBox("Run Rprofile when resuming suspended session");
+ spaced(rProfileOnResume_);
+ if (!Desktop.isDesktop())
+ add(rProfileOnResume_);
+
+ // The error handler features require source references; if this R
+ // version doesn't support them, don't show these options.
+ if (session_.getSessionInfo().getHaveSrcrefAttribute())
+ {
+ add(checkboxPref(
+ "Use debug error handler only when my code contains errors",
+ prefs_.handleErrorsInUserCodeOnly()));
+ CheckBox chkTracebacks = checkboxPref(
+ "Automatically expand tracebacks in error inspector",
+ prefs_.autoExpandErrorTracebacks());
+ chkTracebacks.getElement().getStyle().setMarginBottom(15, Unit.PX);
+ add(chkTracebacks);
+ }
+
+ encodingValue_ = prefs_.defaultEncoding().getGlobalValue();
+ add(encoding_ = new TextBoxWithButton(
+ "Default text encoding:",
+ "Change...",
+ new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ server_.iconvlist(new SimpleRequestCallback<IconvListResult>()
+ {
+ @Override
+ public void onResponseReceived(IconvListResult response)
+ {
+ new ChooseEncodingDialog(
+ response.getCommon(),
+ response.getAll(),
+ encodingValue_,
+ true,
+ false,
+ new OperationWithInput<String>()
+ {
+ public void execute(String encoding)
+ {
+ if (encoding == null)
+ return;
+
+ setEncoding(encoding);
+ }
+ }).showModal();
+ }
+ });
+
+ }
+ }));
+ nudgeRight(encoding_);
+ textBoxWithChooser(encoding_);
+ spaced(encoding_);
+ setEncoding(prefs.defaultEncoding().getGlobalValue());
+
+ // provide check for updates option in desktop mode when not
+ // already globally disabled
+ if (Desktop.isDesktop() &&
+ !session.getSessionInfo().getDisableCheckForUpdates())
+ {
+ add(checkboxPref("Automatically notify me of updates to RStudio",
+ prefs_.checkForUpdates()));
+ }
+
+ saveWorkspace_.setEnabled(false);
+ loadRData_.setEnabled(false);
+ dirChooser_.setEnabled(false);
+ alwaysSaveHistory_.setEnabled(false);
+ removeHistoryDuplicates_.setEnabled(false);
+ rProfileOnResume_.setEnabled(false);
+ restoreLastProject_.setEnabled(false);
+ }
+
+ @Override
+ protected void initialize(RPrefs rPrefs)
+ {
+ // general prefs
+ GeneralPrefs generalPrefs = rPrefs.getGeneralPrefs();
+
+ saveWorkspace_.setEnabled(true);
+ loadRData_.setEnabled(true);
+ dirChooser_.setEnabled(true);
+
+ int saveWorkspaceIndex;
+ switch (generalPrefs.getSaveAction())
+ {
+ case SaveAction.NOSAVE:
+ saveWorkspaceIndex = 1;
+ break;
+ case SaveAction.SAVE:
+ saveWorkspaceIndex = 0;
+ break;
+ case SaveAction.SAVEASK:
+ default:
+ saveWorkspaceIndex = 2;
+ break;
+ }
+ saveWorkspace_.getListBox().setSelectedIndex(saveWorkspaceIndex);
+
+ loadRData_.setValue(generalPrefs.getLoadRData());
+ dirChooser_.setText(generalPrefs.getInitialWorkingDirectory());
+
+ // history prefs
+ HistoryPrefs historyPrefs = rPrefs.getHistoryPrefs();
+
+ alwaysSaveHistory_.setEnabled(true);
+ removeHistoryDuplicates_.setEnabled(true);
+
+ alwaysSaveHistory_.setValue(historyPrefs.getAlwaysSave());
+ removeHistoryDuplicates_.setValue(historyPrefs.getRemoveDuplicates());
+
+ rProfileOnResume_.setValue(generalPrefs.getRprofileOnResume());
+ rProfileOnResume_.setEnabled(true);
+
+ // projects prefs
+ ProjectsPrefs projectsPrefs = rPrefs.getProjectsPrefs();
+ restoreLastProject_.setEnabled(true);
+ restoreLastProject_.setValue(projectsPrefs.getRestoreLastProject());
+ }
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconR();
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean restartRequired = super.onApply(rPrefs);
+
+ prefs_.defaultEncoding().setGlobalValue(encodingValue_);
+
+ if (saveWorkspace_.isEnabled())
+ {
+ int saveAction;
+ switch (saveWorkspace_.getListBox().getSelectedIndex())
+ {
+ case 0:
+ saveAction = SaveAction.SAVE;
+ break;
+ case 1:
+ saveAction = SaveAction.NOSAVE;
+ break;
+ case 2:
+ default:
+ saveAction = SaveAction.SAVEASK;
+ break;
+ }
+
+ // set general prefs
+ GeneralPrefs generalPrefs = GeneralPrefs.create(saveAction,
+ loadRData_.getValue(),
+ rProfileOnResume_.getValue(),
+ dirChooser_.getText());
+ rPrefs.setGeneralPrefs(generalPrefs);
+
+ // set history prefs
+ HistoryPrefs historyPrefs = HistoryPrefs.create(
+ alwaysSaveHistory_.getValue(),
+ removeHistoryDuplicates_.getValue());
+ rPrefs.setHistoryPrefs(historyPrefs);
+
+
+ // set projects prefs
+ ProjectsPrefs projectsPrefs = ProjectsPrefs.create(
+ restoreLastProject_.getValue());
+ rPrefs.setProjectsPrefs(projectsPrefs);
+ }
+
+ return restartRequired;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "General";
+ }
+
+ private void setEncoding(String encoding)
+ {
+ encodingValue_ = encoding;
+ if (StringUtil.isNullOrEmpty(encoding))
+ encoding_.setText(ChooseEncodingDialog.ASK_LABEL);
+ else
+ encoding_.setText(encoding);
+ }
+
+
+
+ private final FileSystemContext fsContext_;
+ private final FileDialogs fileDialogs_;
+ private SelectWidget saveWorkspace_;
+ private TextBoxWithButton rVersion_;
+ private TextBoxWithButton dirChooser_;
+ private CheckBox loadRData_;
+ private final CheckBox alwaysSaveHistory_;
+ private final CheckBox removeHistoryDuplicates_;
+ private CheckBox restoreLastProject_;
+ private CheckBox rProfileOnResume_;
+ private final SourceServerOperations server_;
+ private final UIPrefs prefs_;
+ private final TextBoxWithButton encoding_;
+ private String encodingValue_;
+ private final Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PackagesPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PackagesPreferencesPane.java
new file mode 100644
index 0000000..84d129b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PackagesPreferencesPane.java
@@ -0,0 +1,236 @@
+/*
+ * PackagesPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.HelpLink;
+import org.rstudio.studio.client.common.PackagesHelpLink;
+import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror;
+import org.rstudio.studio.client.common.mirrors.model.CRANMirror;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.PackagesPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+public class PackagesPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public PackagesPreferencesPane(PreferencesDialogResources res,
+ GlobalDisplay globalDisplay,
+ UIPrefs uiPrefs,
+ Session session,
+ final DefaultCRANMirror defaultCRANMirror)
+ {
+ res_ = res;
+ globalDisplay_ = globalDisplay;
+ PreferencesDialogBaseResources baseRes = PreferencesDialogBaseResources.INSTANCE;
+
+ Label installationLabel = new Label("Package management");
+ installationLabel.addStyleName(baseRes.styles().headerLabel());
+ nudgeRight(installationLabel);
+ add(installationLabel);
+
+ cranMirrorTextBox_ = new TextBoxWithButton(
+ "CRAN mirror:",
+ "Change...",
+ new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ defaultCRANMirror.choose(new OperationWithInput<CRANMirror>(){
+ @Override
+ public void execute(CRANMirror cranMirror)
+ {
+ cranMirror_ = cranMirror;
+ cranMirrorTextBox_.setText(cranMirror_.getDisplay());
+ }
+ });
+
+ }
+ });
+ nudgeRight(cranMirrorTextBox_);
+ textBoxWithChooser(cranMirrorTextBox_);
+ cranMirrorTextBox_.setText("");
+ if (session.getSessionInfo().getAllowCRANReposEdit())
+ {
+ lessSpaced(cranMirrorTextBox_);
+ add(cranMirrorTextBox_);
+ }
+
+ CheckBox chkEnablePackages = checkboxPref("Enable packages pane",
+ uiPrefs.packagesPaneEnabled());
+ chkEnablePackages.addValueChangeHandler(new ValueChangeHandler<Boolean>(){
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ reloadRequired_ = true;
+ }
+ });
+ if (!session.getSessionInfo().getDisablePackages())
+ add(chkEnablePackages);
+
+
+ useInternet2_ = new CheckBox(
+ "Use Internet Explorer library/proxy for HTTP",
+ true);
+ if (BrowseCap.isWindowsDesktop())
+ {
+ lessSpaced(chkEnablePackages);
+ spaced(useInternet2_);
+ add(useInternet2_);
+ }
+ else
+ {
+ spaced(chkEnablePackages);
+ chkEnablePackages.getElement().getStyle().setMarginBottom(12, Unit.PX);
+ }
+
+ Label developmentLabel = new Label("Package development");
+ developmentLabel.addStyleName(baseRes.styles().headerLabel());
+ nudgeRight(developmentLabel);
+ add(developmentLabel);
+
+ add(checkboxPref("Save all files prior to building packages", uiPrefs.saveAllBeforeBuild()));
+ add(checkboxPref("Automatically navigate editor to build errors", uiPrefs.navigateToBuildError()));
+
+ hideObjectFiles_ = new CheckBox("Hide object files in package src directory");
+ lessSpaced(hideObjectFiles_);
+ add(hideObjectFiles_);
+
+ cleanupAfterCheckSuccess_ = new CheckBox("Cleanup output after successful R CMD check");
+ lessSpaced(cleanupAfterCheckSuccess_);
+ add(cleanupAfterCheckSuccess_);
+
+ viewDirAfterCheckFailure_ = new CheckBox("View Rcheck directory after failed R CMD check");
+ lessSpaced(viewDirAfterCheckFailure_);
+ add(viewDirAfterCheckFailure_);
+
+ add(checkboxPref("Use Rcpp template when creating C++ files", uiPrefs.useRcppTemplate()));
+
+ HelpLink packagesHelpLink = new PackagesHelpLink();
+ packagesHelpLink.getElement().getStyle().setMarginTop(12, Unit.PX);
+ nudgeRight(packagesHelpLink);
+ add(packagesHelpLink);
+
+ cranMirrorTextBox_.setEnabled(false);
+ useInternet2_.setEnabled(false);
+ cleanupAfterCheckSuccess_.setEnabled(false);
+ viewDirAfterCheckFailure_.setEnabled(false);
+ hideObjectFiles_.setEnabled(false);
+ }
+
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return res_.iconPackages();
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Packages";
+ }
+
+ @Override
+ protected void initialize(RPrefs prefs)
+ {
+ // packages prefs
+ PackagesPrefs packagesPrefs = prefs.getPackagesPrefs();
+
+ cranMirrorTextBox_.setEnabled(true);
+ if (!packagesPrefs.getCRANMirror().isEmpty())
+ {
+ cranMirror_ = packagesPrefs.getCRANMirror();
+ cranMirrorTextBox_.setText(cranMirror_.getDisplay());
+ }
+ useInternet2_.setEnabled(true);
+ useInternet2_.setValue(packagesPrefs.getUseInternet2());
+ useInternet2_.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.INFO,
+ "Restart R Required",
+ "You must restart your R session for this setting " +
+ "to take effect.");
+ }
+ });
+
+ cleanupAfterCheckSuccess_.setEnabled(true);
+ cleanupAfterCheckSuccess_.setValue(packagesPrefs.getCleanupAfterCheckSuccess());
+
+ viewDirAfterCheckFailure_.setEnabled(true);
+ viewDirAfterCheckFailure_.setValue(packagesPrefs.getViewDirAfterCheckFailure());
+
+ hideObjectFiles_.setEnabled(true);
+ hideObjectFiles_.setValue(packagesPrefs.getHideObjectFiles());
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean reload = super.onApply(rPrefs);
+
+ // set packages prefs
+ PackagesPrefs packagesPrefs = PackagesPrefs.create(
+ cranMirror_,
+ useInternet2_.getValue(),
+ null,
+ cleanupAfterCheckSuccess_.getValue(),
+ viewDirAfterCheckFailure_.getValue(),
+ hideObjectFiles_.getValue());
+ rPrefs.setPackagesPrefs(packagesPrefs);
+
+ return reload || reloadRequired_;
+ }
+
+ private final PreferencesDialogResources res_;
+ private final GlobalDisplay globalDisplay_;
+
+
+ private CRANMirror cranMirror_ = CRANMirror.empty();
+ private CheckBox useInternet2_;
+ private TextBoxWithButton cranMirrorTextBox_;
+ private CheckBox cleanupAfterCheckSuccess_;
+ private CheckBox viewDirAfterCheckFailure_;
+ private CheckBox hideObjectFiles_;
+ private boolean reloadRequired_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PaneLayoutPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PaneLayoutPreferencesPane.java
new file mode 100644
index 0000000..fa139b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PaneLayoutPreferencesPane.java
@@ -0,0 +1,377 @@
+/*
+ * PaneLayoutPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.PaneConfig;
+
+import java.util.ArrayList;
+
+public class PaneLayoutPreferencesPane extends PreferencesPane
+{
+ class ExclusiveSelectionMaintainer
+ {
+ class ListChangeHandler implements ChangeHandler
+ {
+ ListChangeHandler(int whichList)
+ {
+ whichList_ = whichList;
+ }
+
+ public void onChange(ChangeEvent event)
+ {
+ int selectedIndex = lists_[whichList_].getSelectedIndex();
+
+ for (int i = 0; i < lists_.length; i++)
+ {
+ if (i != whichList_
+ && lists_[i].getSelectedIndex() == selectedIndex)
+ {
+ lists_[i].setSelectedIndex(notSelectedIndex());
+ }
+ }
+
+ updateTabSetPositions();
+ }
+
+ private Integer notSelectedIndex()
+ {
+ boolean[] seen = new boolean[4];
+ for (ListBox listBox : lists_)
+ seen[listBox.getSelectedIndex()] = true;
+ for (int i = 0; i < seen.length; i++)
+ if (!seen[i])
+ return i;
+ return null;
+ }
+
+ private final int whichList_;
+ }
+
+ ExclusiveSelectionMaintainer(ListBox[] lists)
+ {
+ lists_ = lists;
+ for (int i = 0; i < lists.length; i++)
+ lists[i].addChangeHandler(new ListChangeHandler(i));
+ }
+
+ private final ListBox[] lists_;
+ }
+
+ class ModuleList extends Composite implements ValueChangeHandler<Boolean>,
+ HasValueChangeHandlers<ArrayList<Boolean>>
+ {
+ ModuleList()
+ {
+ checkBoxes_ = new ArrayList<CheckBox>();
+ VerticalPanel panel = new VerticalPanel();
+ for (String module : PaneConfig.getAllTabs())
+ {
+ CheckBox checkBox = new CheckBox(module, false);
+ checkBox.addValueChangeHandler(this);
+ checkBoxes_.add(checkBox);
+ panel.add(checkBox);
+ if (module.equals("Presentation"))
+ checkBox.setVisible(false);
+ }
+ initWidget(panel);
+ }
+
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ ValueChangeEvent.fire(this, getSelectedIndices());
+ }
+
+ public ArrayList<Boolean> getSelectedIndices()
+ {
+ ArrayList<Boolean> results = new ArrayList<Boolean>();
+ for (CheckBox checkBox : checkBoxes_)
+ results.add(checkBox.getValue());
+ return results;
+ }
+
+ public void setSelectedIndices(ArrayList<Boolean> selected)
+ {
+ for (int i = 0; i < selected.size(); i++)
+ checkBoxes_.get(i).setValue(selected.get(i), false);
+ }
+
+ public ArrayList<String> getValue()
+ {
+ ArrayList<String> value = new ArrayList<String>();
+ for (CheckBox checkBox : checkBoxes_)
+ {
+ if (checkBox.getValue())
+ value.add(checkBox.getText());
+ }
+ return value;
+ }
+
+ public void setValue(ArrayList<String> tabs)
+ {
+ for (CheckBox checkBox : checkBoxes_)
+ checkBox.setValue(tabs.contains(checkBox.getText()), false);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<ArrayList<Boolean>> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ private final ArrayList<CheckBox> checkBoxes_;
+ }
+
+
+ @Inject
+ public PaneLayoutPreferencesPane(PreferencesDialogResources res,
+ UIPrefs uiPrefs)
+ {
+ res_ = res;
+ uiPrefs_ = uiPrefs;
+
+ add(new Label("Choose the layout of the panes in RStudio by selecting from the controls in each quadrant.", true));
+
+ String[] allPanes = PaneConfig.getAllPanes();
+
+ leftTop_ = new ListBox();
+ leftBottom_ = new ListBox();
+ rightTop_ = new ListBox();
+ rightBottom_ = new ListBox();
+ allPanes_ = new ListBox[]{leftTop_, leftBottom_, rightTop_, rightBottom_};
+ for (ListBox lb : allPanes_)
+ {
+ for (String value : allPanes)
+ lb.addItem(value);
+ }
+
+ PaneConfig value = uiPrefs.paneConfig().getGlobalValue();
+ if (value == null || !value.validateAndAutoCorrect())
+ uiPrefs.paneConfig().setGlobalValue(PaneConfig.createDefault(), false);
+
+ JsArrayString origPanes = uiPrefs.paneConfig().getGlobalValue().getPanes();
+ for (int i = 0; i < 4; i++)
+ {
+ boolean success = selectByValue(allPanes_[i], origPanes.get(i));
+ if (!success)
+ {
+ Debug.log("Bad config! Falling back to a reasonable default");
+ leftTop_.setSelectedIndex(0);
+ leftBottom_.setSelectedIndex(1);
+ rightTop_.setSelectedIndex(2);
+ rightBottom_.setSelectedIndex(3);
+ break;
+ }
+ }
+
+ new ExclusiveSelectionMaintainer(allPanes_);
+
+ for (ListBox lb : allPanes_)
+ lb.addChangeHandler(new ChangeHandler()
+ {
+ public void onChange(ChangeEvent event)
+ {
+ dirty_ = true;
+ }
+ });
+
+ Grid grid = new Grid(2, 2);
+ grid.addStyleName(res.styles().paneLayoutTable());
+ grid.setCellSpacing(8);
+ grid.setCellPadding(6);
+ grid.setWidget(0, 0, leftTopPanel_ = createPane(leftTop_));
+ grid.setWidget(1, 0, leftBottomPanel_ = createPane(leftBottom_));
+ grid.setWidget(0, 1, rightTopPanel_ = createPane(rightTop_));
+ grid.setWidget(1, 1, rightBottomPanel_ = createPane(rightBottom_));
+ for (int row = 0; row < 2; row++)
+ for (int col = 0; col < 2; col++)
+ grid.getCellFormatter().setStyleName(row, col,
+ res.styles().paneLayoutTable());
+ add(grid);
+
+ allPanePanels_ = new VerticalPanel[] {leftTopPanel_, leftBottomPanel_,
+ rightTopPanel_, rightBottomPanel_};
+
+ tabSet1ModuleList_ = new ModuleList();
+ tabSet1ModuleList_.setValue(toArrayList(uiPrefs.paneConfig().getGlobalValue().getTabSet1()));
+ tabSet2ModuleList_ = new ModuleList();
+ tabSet2ModuleList_.setValue(toArrayList(uiPrefs.paneConfig().getGlobalValue().getTabSet2()));
+
+ ValueChangeHandler<ArrayList<Boolean>> vch = new ValueChangeHandler<ArrayList<Boolean>>()
+ {
+ public void onValueChange(ValueChangeEvent<ArrayList<Boolean>> e)
+ {
+ dirty_ = true;
+
+ ModuleList source = (ModuleList) e.getSource();
+ ModuleList other = (source == tabSet1ModuleList_)
+ ? tabSet2ModuleList_
+ : tabSet1ModuleList_;
+
+ if (!PaneConfig.isValidConfig(source.getValue()))
+ {
+ ArrayList<Boolean> indices = source.getSelectedIndices();
+ ArrayList<Boolean> otherIndices = other.getSelectedIndices();
+ for (int i = 0; i < indices.size(); i++)
+ {
+ indices.set(i, !otherIndices.get(i));
+ }
+ source.setSelectedIndices(indices);
+ }
+ else
+ {
+ ArrayList<Boolean> indices = source.getSelectedIndices();
+ ArrayList<Boolean> otherIndices = new ArrayList<Boolean>();
+ for (Boolean b : indices)
+ otherIndices.add(!b);
+ other.setSelectedIndices(otherIndices);
+
+ updateTabSetLabels();
+ }
+ }
+ };
+ tabSet1ModuleList_.addValueChangeHandler(vch);
+ tabSet2ModuleList_.addValueChangeHandler(vch);
+
+ updateTabSetPositions();
+ updateTabSetLabels();
+ }
+
+ private VerticalPanel createPane(ListBox listBox)
+ {
+ VerticalPanel vp = new VerticalPanel();
+ vp.add(listBox);
+ return vp;
+ }
+
+ private static boolean selectByValue(ListBox listBox, String value)
+ {
+ for (int i = 0; i < listBox.getItemCount(); i++)
+ {
+ if (listBox.getValue(i).equals(value))
+ {
+ listBox.setSelectedIndex(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return res_.iconPanes();
+ }
+
+ @Override
+ protected void initialize(RPrefs prefs)
+ {
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean restartRequired = super.onApply(rPrefs);
+
+ if (dirty_)
+ {
+ JsArrayString panes = JsArrayString.createArray().cast();
+ panes.push(leftTop_.getValue(leftTop_.getSelectedIndex()));
+ panes.push(leftBottom_.getValue(leftBottom_.getSelectedIndex()));
+ panes.push(rightTop_.getValue(rightTop_.getSelectedIndex()));
+ panes.push(rightBottom_.getValue(rightBottom_.getSelectedIndex()));
+
+ JsArrayString tabSet1 = JsArrayString.createArray().cast();
+ for (String tab : tabSet1ModuleList_.getValue())
+ tabSet1.push(tab);
+
+ JsArrayString tabSet2 = JsArrayString.createArray().cast();
+ for (String tab : tabSet2ModuleList_.getValue())
+ tabSet2.push(tab);
+
+ uiPrefs_.paneConfig().setGlobalValue(PaneConfig.create(panes, tabSet1, tabSet2));
+
+ dirty_ = false;
+ }
+
+ return restartRequired;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Pane Layout";
+ }
+
+ private void updateTabSetPositions()
+ {
+ for (int i = 0; i < allPanes_.length; i++)
+ {
+ String value = allPanes_[i].getValue(allPanes_[i].getSelectedIndex());
+ if (value.equals("TabSet1"))
+ allPanePanels_[i].add(tabSet1ModuleList_);
+ else if (value.equals("TabSet2"))
+ allPanePanels_[i].add(tabSet2ModuleList_);
+ }
+ }
+
+ private void updateTabSetLabels()
+ {
+ for (ListBox pane : allPanes_)
+ {
+ pane.setItemText(2, StringUtil.join(tabSet1ModuleList_.getValue(), ", "));
+ pane.setItemText(3, StringUtil.join(tabSet2ModuleList_.getValue(), ", "));
+ }
+ }
+
+ private ArrayList<String> toArrayList(JsArrayString strings)
+ {
+ ArrayList<String> results = new ArrayList<String>();
+ for (int i = 0; i < strings.length(); i++)
+ results.add(strings.get(i));
+ return results;
+ }
+
+ private final PreferencesDialogResources res_;
+ private final UIPrefs uiPrefs_;
+ private final ListBox leftTop_;
+ private final ListBox leftBottom_;
+ private final ListBox rightTop_;
+ private final ListBox rightBottom_;
+ private final ListBox[] allPanes_;
+ private final VerticalPanel leftTopPanel_;
+ private final VerticalPanel leftBottomPanel_;
+ private final VerticalPanel rightTopPanel_;
+ private final VerticalPanel rightBottomPanel_;
+ private final VerticalPanel[] allPanePanels_;
+ private final ModuleList tabSet1ModuleList_;
+ private final ModuleList tabSet2ModuleList_;
+ private boolean dirty_ = false;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.css
new file mode 100644
index 0000000..834a75c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.css
@@ -0,0 +1,46 @@
+
+ at external gwt-CheckBox;
+
+.panelContainer {
+ width: 550px;
+ height: 460px;
+}
+
+.sshKeyWidget {
+ margin-top: 20px;
+}
+
+.usingVcsHelp {
+ margin-top: 30px;
+}
+
+.newSection {
+ margin-top: 25px;
+}
+
+
+.paneLayoutTable td.paneLayoutTable {
+ background-color: white;
+ height: 200px;
+ vertical-align: top;
+ padding-top: 0px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.paneLayoutTable .gwt-CheckBox {
+ display: block;
+ margin: 0 0 0 8px;
+ max-height: 18px;
+ font-size: 11px;
+}
+
+.paneLayoutTable select {
+ width: 195px;
+ margin-bottom: 5px;
+}
+
+.themeChooser select {
+ width: 148px;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.java
new file mode 100644
index 0000000..3d1a462
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialog.java
@@ -0,0 +1,120 @@
+/*
+ * PreferencesDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.core.client.GWT;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.prefs.PreferencesDialogBase;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+
+public class PreferencesDialog extends PreferencesDialogBase<RPrefs>
+{
+ @Inject
+ public PreferencesDialog(WorkbenchServerOperations server,
+ Session session,
+ PreferencesDialogResources res,
+ Provider<GeneralPreferencesPane> pR,
+ EditingPreferencesPane source,
+ CompilePdfPreferencesPane compilePdf,
+ AppearancePreferencesPane appearance,
+ PaneLayoutPreferencesPane paneLayout,
+ PackagesPreferencesPane packages,
+ SourceControlPreferencesPane sourceControl,
+ SpellingPreferencesPane spelling)
+ {
+ super("Options",
+ res.styles().panelContainer(),
+ true,
+ new PreferencesPane[] {pR.get(),
+ source,
+ appearance,
+ paneLayout,
+ packages,
+ compilePdf,
+ spelling,
+ sourceControl});
+
+ session_ = session;
+ server_ = server;
+
+ if (!session.getSessionInfo().getAllowVcs())
+ hidePane(7);
+ }
+
+
+
+ public void activateSourceControl()
+ {
+ activatePane(4);
+ }
+
+ @Override
+ protected RPrefs createEmptyPrefs()
+ {
+ return RPrefs.createEmpty();
+ }
+
+
+ @Override
+ protected void doSaveChanges(final RPrefs rPrefs,
+ final Operation onCompleted,
+ final ProgressIndicator progressIndicator,
+ final boolean reload)
+ {
+ // save changes
+ server_.setPrefs(
+ rPrefs,
+ session_.getSessionInfo().getUiPrefs(),
+ new SimpleRequestCallback<Void>() {
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ progressIndicator.onCompleted();
+ if (onCompleted != null)
+ onCompleted.execute();
+ if (reload)
+ reload();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progressIndicator.onError(error.getUserMessage());
+ }
+ });
+
+ }
+
+ public static void ensureStylesInjected()
+ {
+ GWT.<PreferencesDialogResources>create(PreferencesDialogResources.class).styles().ensureInjected();
+ }
+
+
+
+ private final WorkbenchServerOperations server_;
+ private final Session session_;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialogResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialogResources.java
new file mode 100644
index 0000000..daab86d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesDialogResources.java
@@ -0,0 +1,39 @@
+/*
+ * PreferencesDialogResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface PreferencesDialogResources extends ClientBundle
+{
+ public interface Styles extends CssResource
+ {
+ String panelContainer();
+ String paneLayoutTable();
+ String themeChooser();
+ String sshKeyWidget();
+ String usingVcsHelp();
+ String newSection();
+ }
+
+ @Source("PreferencesDialog.css")
+ Styles styles();
+
+ ImageResource iconAppearance();
+ ImageResource iconPanes();
+ ImageResource iconPackages();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesPane.java
new file mode 100644
index 0000000..ff97c59
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/PreferencesPane.java
@@ -0,0 +1,79 @@
+/*
+ * PreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.CheckBox;
+import org.rstudio.core.client.prefs.PreferencesDialogPaneBase;
+import org.rstudio.core.client.widget.NumericValueWidget;
+import org.rstudio.studio.client.workbench.prefs.model.Prefs.PrefValue;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+
+import java.util.ArrayList;
+
+public abstract class PreferencesPane extends PreferencesDialogPaneBase<RPrefs>
+{
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ for (Command cmd : onApplyCommands_)
+ cmd.execute();
+ return false;
+ }
+
+ protected CheckBox checkboxPref(String label,
+ final PrefValue<Boolean> prefValue)
+ {
+ final CheckBox checkBox = new CheckBox(label, false);
+ lessSpaced(checkBox);
+ checkBox.setValue(prefValue.getGlobalValue());
+ onApplyCommands_.add(new Command()
+ {
+ public void execute()
+ {
+ prefValue.setGlobalValue(checkBox.getValue());
+ }
+ });
+ return checkBox;
+ }
+
+
+ protected NumericValueWidget numericPref(String label,
+ final PrefValue<Integer> prefValue)
+ {
+ final NumericValueWidget widget = new NumericValueWidget(label);
+ lessSpaced(widget);
+ registerEnsureVisibleHandler(widget);
+ widget.setValue(prefValue.getGlobalValue() + "");
+ onApplyCommands_.add(new Command()
+ {
+ public void execute()
+ {
+ try
+ {
+ prefValue.setGlobalValue(Integer.parseInt(widget.getValue()));
+ }
+ catch (Exception e)
+ {
+ // It's OK for this to be invalid if we got past validation--
+ // that means the associated checkbox wasn't checked
+ }
+ }
+ });
+ return widget;
+ }
+
+ protected final ArrayList<Command> onApplyCommands_ = new ArrayList<Command>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SourceControlPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SourceControlPreferencesPane.java
new file mode 100644
index 0000000..295bc85
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SourceControlPreferencesPane.java
@@ -0,0 +1,259 @@
+/*
+ * SourceControlPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.FileChooserTextBox;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.HelpLink;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.SshKeyWidget;
+import org.rstudio.studio.client.common.vcs.VcsHelpLink;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.SourceControlPrefs;
+
+public class SourceControlPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public SourceControlPreferencesPane(PreferencesDialogResources res,
+ Session session,
+ GitServerOperations server,
+ final GlobalDisplay globalDisplay,
+ final Commands commands,
+ RemoteFileSystemContext fsContext,
+ FileDialogs fileDialogs)
+ {
+ res_ = res;
+
+ chkVcsEnabled_ = new CheckBox(
+ "Enable version control interface for RStudio projects");
+ extraSpaced(chkVcsEnabled_);
+ add(chkVcsEnabled_);
+ chkVcsEnabled_.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ manageControlVisibility();
+
+ globalDisplay.showMessage(
+ MessageDialog.INFO,
+ (event.getValue() ? "Enable" : "Disable") + " Version Control",
+ "You must restart RStudio for this change to take effect.");
+ }
+ });
+
+
+ // git exe path chooser
+ gitExePathChooser_ = new FileChooserTextBox("",
+ "(Not Found)",
+ null,
+ null);
+ gitExePathLabel_ = new Label("Git executable:");
+ SessionInfo sessionInfo = session.getSessionInfo();
+ if (sessionInfo.getAllowVcsExeEdit())
+ addTextBoxChooser(gitExePathLabel_, null, null, gitExePathChooser_);
+
+ // use git bash
+ chkUseGitBash_ = new CheckBox("Use Git Bash as shell for Git projects");
+ if (haveGitBashPref())
+ {
+ extraSpaced(chkUseGitBash_);
+ add(chkUseGitBash_);
+ }
+
+
+ // svn exe path chooser
+ svnExePathLabel_ = new Label("SVN executable:");
+ svnExePathChooser_ = new FileChooserTextBox("",
+ "(Not Found)",
+ null,
+ null);
+ if (sessionInfo.getAllowVcsExeEdit())
+ addTextBoxChooser(svnExePathLabel_, null, null, svnExePathChooser_);
+
+ // terminal path
+ terminalPathLabel_ = new Label("Terminal executable:");
+ terminalPathChooser_ = new FileChooserTextBox("",
+ "(Not Found)",
+ null,
+ null);
+ if (haveTerminalPathPref())
+ addTextBoxChooser(terminalPathLabel_, null, null, terminalPathChooser_);
+
+ // ssh key widget
+ sshKeyWidget_ = new SshKeyWidget(server, "330px");
+ sshKeyWidget_.addStyleName(res_.styles().sshKeyWidget());
+ nudgeRight(sshKeyWidget_);
+ add(sshKeyWidget_);
+
+ HelpLink vcsHelpLink = new VcsHelpLink();
+ nudgeRight(vcsHelpLink);
+ vcsHelpLink.addStyleName(res_.styles().newSection());
+ add(vcsHelpLink);
+
+ chkVcsEnabled_.setEnabled(false);
+ gitExePathChooser_.setEnabled(false);
+ svnExePathChooser_.setEnabled(false);
+ terminalPathChooser_.setEnabled(false);
+ chkUseGitBash_.setEnabled(false);
+ }
+
+ @Override
+ protected void initialize(RPrefs rPrefs)
+ {
+ // source control prefs
+ SourceControlPrefs prefs = rPrefs.getSourceControlPrefs();
+
+ chkVcsEnabled_.setEnabled(true);
+ gitExePathChooser_.setEnabled(true);
+ svnExePathChooser_.setEnabled(true);
+ terminalPathChooser_.setEnabled(true);
+ chkUseGitBash_.setEnabled(true);
+
+ chkVcsEnabled_.setValue(prefs.getVcsEnabled());
+ gitExePathChooser_.setText(prefs.getGitExePath());
+ svnExePathChooser_.setText(prefs.getSvnExePath());
+ terminalPathChooser_.setText(prefs.getTerminalPath());
+ chkUseGitBash_.setValue(prefs.getUseGitBash());
+
+ sshKeyWidget_.setRsaSshKeyPath(prefs.getRsaKeyPath(),
+ prefs.getHaveRsaKey());
+ sshKeyWidget_.setProgressIndicator(getProgressIndicator());
+
+ manageControlVisibility();
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconSourceControl();
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Git/SVN";
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ boolean restartRequired = super.onApply(rPrefs);
+
+ SourceControlPrefs prefs = SourceControlPrefs.create(
+ chkVcsEnabled_.getValue(), gitExePathChooser_.getText(),
+ svnExePathChooser_.getText(), terminalPathChooser_.getText(),
+ chkUseGitBash_.getValue());
+
+ rPrefs.setSourceControlPrefs(prefs);
+
+ return restartRequired;
+ }
+
+ private boolean haveTerminalPathPref()
+ {
+ return Desktop.isDesktop() && BrowseCap.isLinux();
+ }
+
+ private boolean haveGitBashPref()
+ {
+ return Desktop.isDesktop() && BrowseCap.isWindows();
+ }
+
+ private void addTextBoxChooser(Label captionLabel, HyperlinkLabel link,
+ String captionPanelStyle, TextBoxWithButton chooser)
+ {
+ String textWidth = "250px";
+
+ HorizontalPanel captionPanel = new HorizontalPanel();
+ captionPanel.setWidth(textWidth);
+ nudgeRight(captionPanel);
+ if (captionPanelStyle != null)
+ captionPanel.addStyleName(captionPanelStyle);
+
+ captionPanel.add(captionLabel);
+ captionPanel.setCellHorizontalAlignment(captionLabel,
+ HasHorizontalAlignment.ALIGN_LEFT);
+
+ if (link != null)
+ {
+ HorizontalPanel linkPanel = new HorizontalPanel();
+ linkPanel.add(link);
+ captionPanel.add(linkPanel);
+ captionPanel.setCellHorizontalAlignment(linkPanel,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+
+ }
+
+ add(tight(captionPanel));
+
+ chooser.setTextWidth(textWidth);
+ nudgeRight(chooser);
+ textBoxWithChooser(chooser);
+ add(chooser);
+ }
+
+ private void manageControlVisibility()
+ {
+ boolean vcsEnabled = chkVcsEnabled_.getValue();
+ gitExePathLabel_.setVisible(vcsEnabled);
+ gitExePathChooser_.setVisible(vcsEnabled);
+ svnExePathLabel_.setVisible(vcsEnabled);
+ svnExePathChooser_.setVisible(vcsEnabled);
+ terminalPathLabel_.setVisible(vcsEnabled);
+ terminalPathChooser_.setVisible(vcsEnabled && haveTerminalPathPref());
+ chkUseGitBash_.setVisible(vcsEnabled && haveGitBashPref());
+ sshKeyWidget_.setVisible(vcsEnabled);
+ }
+
+ private final PreferencesDialogResources res_;
+
+ private final CheckBox chkVcsEnabled_;
+
+ private Label svnExePathLabel_;
+ private Label gitExePathLabel_;
+ private TextBoxWithButton gitExePathChooser_;
+ private TextBoxWithButton svnExePathChooser_;
+ private Label terminalPathLabel_;
+ private TextBoxWithButton terminalPathChooser_;
+ private CheckBox chkUseGitBash_;
+ private SshKeyWidget sshKeyWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SpellingPreferencesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SpellingPreferencesPane.java
new file mode 100644
index 0000000..c9e22d2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/SpellingPreferencesPane.java
@@ -0,0 +1,164 @@
+/*
+ * SpellingPreferencesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.prefs.views;
+
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.spelling.SpellingService;
+import org.rstudio.studio.client.common.spelling.ui.SpellingCustomDictionariesWidget;
+import org.rstudio.studio.client.common.spelling.ui.SpellingLanguageSelectWidget;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.SpellingPrefsContext;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+public class SpellingPreferencesPane extends PreferencesPane
+{
+ @Inject
+ public SpellingPreferencesPane(GlobalDisplay globalDisplay,
+ PreferencesDialogResources res,
+ SpellingService spellingService,
+ UIPrefs prefs)
+ {
+ globalDisplay_ = globalDisplay;
+ res_ = res;
+ spellingService_ = spellingService;
+ uiPrefs_ = prefs;
+
+ languageWidget_ = new SpellingLanguageSelectWidget(onInstallLanguages_);
+ spaced(languageWidget_);
+ add(languageWidget_);
+
+ customDictsWidget_ = new SpellingCustomDictionariesWidget();
+ spaced(customDictsWidget_);
+ nudgeRight(customDictsWidget_);
+ add(customDictsWidget_);
+
+ add(checkboxPref("Ignore words in UPPERCASE",
+ prefs.ignoreWordsInUppercase()));
+
+ add(checkboxPref("Ignore words with numbers",
+ prefs.ignoreWordsInUppercase()));
+ }
+
+
+ private CommandWithArg<String> onInstallLanguages_
+ = new CommandWithArg<String>()
+ {
+ @Override
+ public void execute(String progress)
+ {
+ // show progress
+ final ProgressIndicator indicator = getProgressIndicator();
+ indicator.onProgress(progress);
+
+ // save current selection for restoring
+ final String currentLang = languageWidget_.getSelectedLanguage();
+
+ spellingService_.installAllDictionaries(
+ new ServerRequestCallback<SpellingPrefsContext> () {
+
+ @Override
+ public void onResponseReceived(SpellingPrefsContext context)
+ {
+ indicator.onCompleted();
+ languageWidget_.setLanguages(
+ context.getAllLanguagesInstalled(),
+ context.getAvailableLanguages());
+ languageWidget_.setSelectedLanguage(currentLang);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ JSONString userMessage = error.getClientInfo().isString();
+ if (userMessage != null)
+ {
+ indicator.onCompleted();
+ globalDisplay_.showErrorMessage(
+ "Error Downloading Dictionaries",
+ userMessage.stringValue());
+ }
+ else
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ }
+
+ });
+ }
+
+ };
+
+ @Override
+ protected void initialize(RPrefs rPrefs)
+ {
+ SpellingPrefsContext context = rPrefs.getSpellingPrefsContext();
+ languageWidget_.setLanguages(context.getAllLanguagesInstalled(),
+ context.getAvailableLanguages());
+
+ languageWidget_.setSelectedLanguage(
+ uiPrefs_.spellingDictionaryLanguage().getValue());
+
+ customDictsWidget_.setDictionaries(context.getCustomDictionaries());
+ customDictsWidget_.setProgressIndicator(getProgressIndicator());
+ }
+
+ @Override
+ public boolean onApply(RPrefs rPrefs)
+ {
+ uiPrefs_.spellingDictionaryLanguage().setGlobalValue(
+ languageWidget_.getSelectedLanguage());
+
+ return super.onApply(rPrefs);
+ }
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return PreferencesDialogBaseResources.INSTANCE.iconSpelling();
+ }
+
+ @Override
+ public boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Spelling";
+ }
+
+
+ @SuppressWarnings("unused")
+ private final PreferencesDialogResources res_;
+
+ private final GlobalDisplay globalDisplay_;
+ private final UIPrefs uiPrefs_;
+ private final SpellingService spellingService_;
+ private final SpellingLanguageSelectWidget languageWidget_;
+ private final SpellingCustomDictionariesWidget customDictsWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconAppearance.png b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconAppearance.png
new file mode 100755
index 0000000..7c72b8a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconAppearance.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPackages.png b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPackages.png
new file mode 100644
index 0000000..64f4254
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPackages.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPanes.png b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPanes.png
new file mode 100755
index 0000000..f28374d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/views/iconPanes.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/ConsoleTabPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/ConsoleTabPanel.java
new file mode 100644
index 0000000..8e6d2ad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/ConsoleTabPanel.java
@@ -0,0 +1,192 @@
+/*
+ * ConsoleTabPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.theme.PrimaryWindowFrame;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.views.console.ConsoleInterruptButton;
+import org.rstudio.studio.client.workbench.views.console.ConsolePane;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedEvent;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedHandler;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputTab;
+
+import java.util.ArrayList;
+
+public class ConsoleTabPanel extends WorkbenchTabPanel
+{
+ public ConsoleTabPanel(final PrimaryWindowFrame owner,
+ ConsolePane consolePane,
+ WorkbenchTab compilePdfTab,
+ FindOutputTab findResultsTab,
+ WorkbenchTab sourceCppTab,
+ EventBus events,
+ ConsoleInterruptButton consoleInterrupt,
+ ToolbarButton goToWorkingDirButton)
+ {
+ super(owner);
+ owner_ = owner;
+ consolePane_ = consolePane;
+ compilePdfTab_ = compilePdfTab;
+ findResultsTab_ = findResultsTab;
+ sourceCppTab_ = sourceCppTab;
+ consoleInterrupt_ = consoleInterrupt;
+ goToWorkingDirButton_ = goToWorkingDirButton;
+
+ compilePdfTab.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ @Override
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ compilePdfTabVisible_ = true;
+ managePanels();
+ if (event.getActivate())
+ selectTab(compilePdfTab_);
+ }
+ });
+ compilePdfTab.addEnsureHiddenHandler(new EnsureHiddenHandler()
+ {
+ @Override
+ public void onEnsureHidden(EnsureHiddenEvent event)
+ {
+ compilePdfTabVisible_ = false;
+ managePanels();
+ if (!consoleOnly_)
+ selectTab(0);
+ }
+ });
+
+ findResultsTab.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ @Override
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ findResultsTabVisible_ = true;
+ managePanels();
+ if (event.getActivate())
+ selectTab(findResultsTab_);
+ }
+ });
+ findResultsTab.addEnsureHiddenHandler(new EnsureHiddenHandler()
+ {
+ @Override
+ public void onEnsureHidden(EnsureHiddenEvent event)
+ {
+ findResultsTab_.onDismiss();
+ findResultsTabVisible_ = false;
+ managePanels();
+ if (!consoleOnly_)
+ selectTab(0);
+ }
+ });
+
+ sourceCppTab.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ @Override
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ sourceCppTabVisible_ = true;
+ managePanels();
+ if (event.getActivate())
+ selectTab(sourceCppTab_);
+ }
+ });
+ sourceCppTab.addEnsureHiddenHandler(new EnsureHiddenHandler()
+ {
+ @Override
+ public void onEnsureHidden(EnsureHiddenEvent event)
+ {
+ sourceCppTabVisible_ = false;
+ managePanels();
+ if (!consoleOnly_)
+ selectTab(0);
+ }
+ });
+
+ events.addHandler(WorkingDirChangedEvent.TYPE, new WorkingDirChangedHandler()
+ {
+ @Override
+ public void onWorkingDirChanged(WorkingDirChangedEvent event)
+ {
+ String path = event.getPath();
+ if (!path.endsWith("/"))
+ path += "/";
+ consolePane_.setWorkingDirectory(path);
+ owner.setSubtitle(path);
+ }
+ });
+
+ consoleOnly_ = false;
+ managePanels();
+ }
+
+ private void managePanels()
+ {
+ boolean consoleOnly = !compilePdfTabVisible_ &&
+ !findResultsTabVisible_ &&
+ !sourceCppTabVisible_;
+
+ if (!consoleOnly)
+ {
+ ArrayList<WorkbenchTab> tabs = new ArrayList<WorkbenchTab>();
+ tabs.add(consolePane_);
+ if (compilePdfTabVisible_)
+ tabs.add(compilePdfTab_);
+ if (findResultsTabVisible_)
+ tabs.add(findResultsTab_);
+ if (sourceCppTabVisible_)
+ tabs.add(sourceCppTab_);
+
+ setTabs(tabs);
+ }
+
+ if (consoleOnly != consoleOnly_)
+ {
+ consoleOnly_ = consoleOnly;
+
+ consolePane_.setMainToolbarVisible(!consoleOnly);
+ if (consoleOnly)
+ {
+ owner_.setMainWidget(consolePane_);
+ owner_.addLeftWidget(goToWorkingDirButton_);
+ owner_.setContextButton(consoleInterrupt_,
+ consoleInterrupt_.getWidth(),
+ consoleInterrupt_.getHeight());
+ consolePane_.onBeforeSelected();
+ consolePane_.onSelected();
+ consolePane_.setVisible(true);
+ }
+ else
+ {
+ consolePane_.onBeforeUnselected();
+ owner_.setFillWidget(this);
+ owner_.setContextButton(null, 0, 0);
+ }
+ }
+ }
+
+ private final PrimaryWindowFrame owner_;
+ private final ConsolePane consolePane_;
+ private final WorkbenchTab compilePdfTab_;
+ private boolean compilePdfTabVisible_;
+ private final FindOutputTab findResultsTab_;
+ private final WorkbenchTab sourceCppTab_;
+ private boolean sourceCppTabVisible_;
+ private final ConsoleInterruptButton consoleInterrupt_;
+ private final ToolbarButton goToWorkingDirButton_;
+ private boolean findResultsTabVisible_;
+ private boolean consoleOnly_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadTabShim.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadTabShim.java
new file mode 100644
index 0000000..feafbac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadTabShim.java
@@ -0,0 +1,89 @@
+/*
+ * DelayLoadTabShim.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.AsyncShim;
+import org.rstudio.core.client.events.*;
+
+public abstract class DelayLoadTabShim<T extends IsWidget,
+ TParentTab extends DelayLoadWorkbenchTab<T>> extends AsyncShim<T>
+{
+
+ protected final TParentTab getParentTab()
+ {
+ return parentTab_;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setParentTab(DelayLoadWorkbenchTab<T> parentTab)
+ {
+ parentTab_ = (TParentTab) parentTab;
+ }
+
+ @Override
+ protected void onDelayLoadSuccess(T obj)
+ {
+ super.onDelayLoadSuccess(obj);
+ final Widget child = obj.asWidget();
+
+ if (child instanceof HasEnsureVisibleHandlers)
+ {
+ ((HasEnsureVisibleHandlers)child).addEnsureVisibleHandler(
+ new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ parentTab_.ensureVisible(event.getActivate());
+ }
+ });
+ }
+
+ if (child instanceof HasEnsureHeightHandlers)
+ {
+ ((HasEnsureHeightHandlers)child).addEnsureHeightHandler(
+ new EnsureHeightHandler()
+ {
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ parentTab_.ensureHeight(event.getHeight());
+ }
+ });
+ }
+
+ if (child instanceof HasEnsureHiddenHandlers)
+ {
+ ((HasEnsureHiddenHandlers)child).addEnsureHiddenHandler(
+ new EnsureHiddenHandler()
+ {
+ public void onEnsureHidden(EnsureHiddenEvent event)
+ {
+ parentTab_.ensureHidden();
+ }
+ });
+ }
+
+ child.setSize("100%", "100%");
+ parentTab_.getPanel().add(child);
+ }
+
+ public abstract void onBeforeUnselected();
+ public abstract void onBeforeSelected();
+ public abstract void onSelected();
+
+ private TParentTab parentTab_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadWorkbenchTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadWorkbenchTab.java
new file mode 100644
index 0000000..eb0e0f6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/DelayLoadWorkbenchTab.java
@@ -0,0 +1,200 @@
+/*
+ * DelayLoadWorkbenchTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.SerializedCommandQueue;
+import org.rstudio.core.client.events.EnsureHiddenEvent;
+import org.rstudio.core.client.events.EnsureHiddenHandler;
+import org.rstudio.core.client.events.EnsureHeightEvent;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.studio.client.RStudioGinjector;
+
+public abstract class DelayLoadWorkbenchTab<T extends IsWidget>
+ implements WorkbenchTab
+{
+ protected DelayLoadWorkbenchTab(
+ String title,
+ DelayLoadTabShim<T, ? extends DelayLoadWorkbenchTab<T>> shimmed)
+ {
+ title_ = title;
+ panel_ = new DockLayoutPanel(Style.Unit.PX);
+ shimmed_ = shimmed;
+ shimmed_.setParentTab(this);
+ }
+
+ public Widget asWidget()
+ {
+ return panel_;
+ }
+
+ public String getTitle()
+ {
+ return title_;
+ }
+
+ public Panel getPanel()
+ {
+ return panel_;
+ }
+
+ public final void onBeforeSelected()
+ {
+ shimmed_.onBeforeSelected();
+ }
+
+ public void onBeforeUnselected()
+ {
+ shimmed_.onBeforeUnselected();
+ }
+
+ public void prefetch(final Command continuation)
+ {
+ shimmed_.forceLoad(true, continuation);
+ }
+
+ public final void onSelected()
+ {
+ shimmed_.onSelected();
+ }
+
+ public boolean isSuppressed()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return false;
+ }
+
+ @Override
+ public void confirmClose(Command onConfirmed)
+ {
+ onConfirmed.execute();
+ }
+
+ public void ensureVisible(boolean activate)
+ {
+ handlers_.fireEvent(new EnsureVisibleEvent(activate));
+ }
+
+ public void ensureHidden()
+ {
+ handlers_.fireEvent(new EnsureHiddenEvent());
+ }
+
+ public void ensureHeight(int height)
+ {
+ handlers_.fireEvent(new EnsureHeightEvent(height));
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return handlers_.addHandler(EnsureVisibleEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addEnsureHiddenHandler(EnsureHiddenHandler handler)
+ {
+ return handlers_.addHandler(EnsureHiddenEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return handlers_.addHandler(EnsureHeightEvent.TYPE, handler);
+ }
+
+ protected void setInternalCallbacks(InternalCallbacks callbacks)
+ {
+ callbacks_ = callbacks;
+ }
+
+ protected interface InternalCallbacks
+ {
+
+ void onBeforeSelected();
+
+ void onSelected();
+
+ }
+
+ protected void initialize(final WorkbenchPane pane, Panel panel)
+ {
+ assert !initialized_;
+
+ initialized_ = true;
+
+ pane.ensureWidget();
+ panel.add(pane);
+ pane.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ ensureVisible(event.getActivate());
+ }
+ });
+ pane.addEnsureHiddenHandler(new EnsureHiddenHandler()
+ {
+ @Override
+ public void onEnsureHidden(EnsureHiddenEvent event)
+ {
+ ensureHidden();
+ }
+ });
+
+ setInternalCallbacks(new InternalCallbacks()
+ {
+ public void onBeforeSelected()
+ {
+ pane.onBeforeSelected();
+ }
+
+ public void onSelected()
+ {
+ pane.onSelected();
+ }
+ });
+
+ pane.onBeforeSelected();
+ pane.onSelected();
+ }
+
+ protected void handleCodeLoadFailure(Throwable reason)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Code Failed to Load",
+ reason == null ? "(Unknown error)" : reason.getMessage());
+ }
+
+ private final HandlerManager handlers_ = new HandlerManager(null);
+ @SuppressWarnings("unused")
+ private SerializedCommandQueue initQueue = new SerializedCommandQueue();
+ private boolean initialized_;
+ protected final DockLayoutPanel panel_;
+ private final String title_;
+ @SuppressWarnings("unused")
+ private InternalCallbacks callbacks_;
+ private DelayLoadTabShim<T, ?> shimmed_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/FontSizeManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/FontSizeManager.java
new file mode 100644
index 0000000..7a9dd7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/FontSizeManager.java
@@ -0,0 +1,58 @@
+/*
+ * FontSizeManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.studio.client.application.events.ChangeFontSizeEvent;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+ at Singleton
+public class FontSizeManager
+{
+ @Inject
+ public FontSizeManager(final EventBus events,
+ UIPrefs prefs)
+ {
+ prefs.fontSize().bind(new CommandWithArg<Double>()
+ {
+ public void execute(Double value)
+ {
+ final int DEFAULT_SIZE = 9;
+ try
+ {
+ if (value != null)
+ size_ = value;
+ else
+ size_ = DEFAULT_SIZE;
+ }
+ catch (Exception e)
+ {
+ size_ = DEFAULT_SIZE;
+ }
+ events.fireEvent(new ChangeFontSizeEvent(size_));
+ }
+ });
+ }
+
+ public double getSize()
+ {
+ return size_;
+ }
+
+ private double size_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/MainSplitPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/MainSplitPanel.java
new file mode 100644
index 0000000..903f201
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/MainSplitPanel.java
@@ -0,0 +1,267 @@
+/*
+ * MainSplitPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.SplitterResizedEvent;
+import com.google.gwt.user.client.ui.SplitterResizedHandler;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.NotifyingSplitLayoutPanel;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+
+public class MainSplitPanel extends NotifyingSplitLayoutPanel
+ implements SplitterResizedHandler
+{
+ private static class State extends JavaScriptObject
+ {
+ protected State() {}
+
+ public native final boolean hasSplitterPos() /*-{
+ return typeof(this.splitterpos) != 'undefined';
+ }-*/;
+
+ public native final int getSplitterPos() /*-{
+ return this.splitterpos;
+ }-*/;
+
+ public native final void setSplitterPos(int pos) /*-{
+ this.splitterpos = pos;
+ }-*/;
+
+ public native final boolean hasPanelWidth() /*-{
+ return typeof(this.panelwidth) != 'undefined';
+ }-*/;
+
+ public native final int getPanelWidth() /*-{
+ return this.panelwidth;
+ }-*/;
+
+ public native final void setPanelWidth(int width) /*-{
+ this.panelwidth = width;
+ }-*/;
+
+ public native final boolean hasWindowWidth() /*-{
+ return typeof(this.windowwidth) != 'undefined';
+ }-*/;
+
+ public native final int getWindowWidth() /*-{
+ return this.windowwidth;
+ }-*/;
+
+ public native final void setWindowWidth(int width) /*-{
+ this.windowwidth = width;
+ }-*/;
+
+ public static boolean equals(State a, State b)
+ {
+ if (a == null ^ b == null)
+ return false;
+ if (a == null)
+ return true;
+
+ if (a.hasSplitterPos() ^ b.hasSplitterPos())
+ return false;
+ if (a.hasSplitterPos() && a.getSplitterPos() != b.getSplitterPos())
+ return false;
+
+ if (a.hasPanelWidth() ^ b.hasPanelWidth())
+ return false;
+ if (a.hasPanelWidth() && a.getPanelWidth() != b.getPanelWidth())
+ return false;
+
+ if (a.hasWindowWidth() ^ b.hasWindowWidth())
+ return false;
+ if (a.hasWindowWidth() && a.getWindowWidth() != b.getWindowWidth())
+ return false;
+
+ return true;
+ }
+ }
+
+ @Inject
+ public MainSplitPanel(EventBus events,
+ Session session)
+ {
+ super(3, events);
+ session_ = session;
+ addSplitterResizedHandler(this);
+ }
+
+ public void initialize(Widget left, Widget right)
+ {
+ left_ = left;
+ right_ = right;
+
+ new JSObjectStateValue(GROUP_WORKBENCH,
+ KEY_RIGHTPANESIZE,
+ ClientState.PERSISTENT,
+ session_.getSessionInfo().getClientState(),
+ false) {
+
+ @Override
+ protected void onInit(JsObject value)
+ {
+ State state = value == null ? null : (State)value.cast();
+ if (state != null && state.hasSplitterPos())
+ {
+ if (state.hasPanelWidth() && state.hasWindowWidth()
+ && state.getWindowWidth() != Window.getClientWidth())
+ {
+ int delta = state.getWindowWidth() - state.getPanelWidth();
+ int offsetWidth = Window.getClientWidth() - delta;
+ double pct = (double)state.getSplitterPos()
+ / state.getPanelWidth();
+ addEast(right_, pct * offsetWidth);
+ }
+ else
+ {
+ addEast(right_, state.getSplitterPos());
+ }
+ }
+ else
+ {
+ addEast(right_, Window.getClientWidth() * 0.45);
+ }
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ enforceBoundaries();
+ }
+ });
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ State state = JavaScriptObject.createObject().cast();
+ state.setPanelWidth(getOffsetWidth());
+ state.setWindowWidth(Window.getClientWidth());
+ state.setSplitterPos(right_.getOffsetWidth());
+ return state.cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ JsObject newValue = getValue();
+ if (!State.equals(lastKnownValue_, (State) newValue.cast()))
+ {
+ lastKnownValue_ = newValue.cast();
+ return true;
+ }
+ return false;
+ }
+
+ private State lastKnownValue_;
+ };
+
+ add(left);
+ setWidgetMinSize(right_, 0);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ deferredSaveWidthPercent();
+ }
+
+ public void onSplitterResized(SplitterResizedEvent event)
+ {
+ enforceBoundaries();
+ deferredSaveWidthPercent();
+ }
+
+ private void enforceBoundaries()
+ {
+ LayoutData layoutData = (LayoutData) right_.getLayoutData();
+ if (layoutData != null
+ && getOffsetWidth() != 0
+ && layoutData.size > getOffsetWidth() - 3)
+ {
+ layoutData.size = getOffsetWidth() - 3;
+ forceLayout();
+ }
+ }
+
+ private void deferredSaveWidthPercent()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ splitPercent_ = null;
+ int panelWidth = getOffsetWidth();
+ assert panelWidth > 0;
+ assert isVisible() && isAttached();
+ if (panelWidth > 0)
+ splitPercent_ = (double)right_.getOffsetWidth() / panelWidth;
+ previousOffsetWidth_ = panelWidth;
+ }
+ });
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+
+ int offsetWidth = getOffsetWidth();
+ if ((previousOffsetWidth_ == null || offsetWidth != previousOffsetWidth_.intValue())
+ && splitPercent_ != null)
+ {
+ LayoutData layoutData = (LayoutData) right_.getLayoutData();
+ if (layoutData == null)
+ return;
+ layoutData.size = splitPercent_ * offsetWidth;
+
+ previousOffsetWidth_ = offsetWidth;
+
+ // Defer actually updating the layout, so that if we receive many
+ // mouse events before layout/paint occurs, we'll only update once.
+ if (layoutCommand_ == null) {
+ layoutCommand_ = new Command() {
+ public void execute() {
+ layoutCommand_ = null;
+ forceLayout();
+ }
+ };
+ Scheduler.get().scheduleDeferred(layoutCommand_);
+ }
+ }
+ }
+
+ private Double splitPercent_ = null;
+ private Integer previousOffsetWidth_ = null;
+
+ private final Session session_;
+ @SuppressWarnings("unused")
+ private Widget left_;
+ private Widget right_;
+ private static final String GROUP_WORKBENCH = "workbenchp";
+ private static final String KEY_RIGHTPANESIZE = "rightpanesize";
+ private Command layoutCommand_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/OptionsLoader.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/OptionsLoader.java
new file mode 100644
index 0000000..d0523c2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/OptionsLoader.java
@@ -0,0 +1,180 @@
+/*
+ * OptionsLoader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.AsyncShim;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+import org.rstudio.studio.client.workbench.prefs.model.Prefs.PrefValue;
+import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.prefs.views.PreferencesDialog;
+
+public class OptionsLoader
+{
+ public abstract static class Shim extends AsyncShim<OptionsLoader>
+ {
+ public abstract void showOptions();
+ public abstract void showVersionControlOptions();
+ }
+
+
+ @Inject
+ OptionsLoader(GlobalDisplay globalDisplay,
+ UIPrefs uiPrefs,
+ Commands commands,
+ WorkbenchServerOperations server,
+ Provider<PreferencesDialog> pPrefDialog)
+ {
+ globalDisplay_ = globalDisplay;
+ uiPrefs_ = uiPrefs;
+ commands_ = commands;
+ server_ = server;
+ pPrefDialog_ = pPrefDialog;
+ }
+
+ public void showOptions()
+ {
+ showOptions(false);
+ }
+
+ public void showVersionControlOptions()
+ {
+ showOptions(true);
+ }
+
+ private void showOptions(final boolean activateSourceControl)
+ {
+ final ProgressIndicator indicator = globalDisplay_.getProgressIndicator(
+ "Error Reading Options");
+ indicator.onProgress("Reading options...");
+
+ server_.getRPrefs(
+ new SimpleRequestCallback<RPrefs>() {
+
+ @Override
+ public void onResponseReceived(RPrefs rPrefs)
+ {
+ indicator.onCompleted();
+ PreferencesDialog prefDialog = pPrefDialog_.get();
+ prefDialog.initialize(rPrefs);
+ if (activateSourceControl)
+ prefDialog.activateSourceControl();
+ prefDialog.showModal();
+
+ // if the user changes global sweave or latex options notify
+ // them if this results in the current project being out
+ // of sync with the global settings
+ new SweaveProjectOptionsNotifier(prefDialog);
+
+ // activate main window if we are in desktop mode (because on
+ // the mac you can actually show prefs from a satellite window)
+ if (Desktop.isDesktop())
+ Desktop.getFrame().bringMainFrameToFront();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+
+ private class SweaveProjectOptionsNotifier
+ {
+ public SweaveProjectOptionsNotifier(PreferencesDialog prefsDialog)
+ {
+ previousRnwWeaveMethod_ =
+ uiPrefs_.defaultSweaveEngine().getGlobalValue();
+ previousLatexProgram_ =
+ uiPrefs_.defaultLatexProgram().getGlobalValue();
+
+ prefsDialog.addCloseHandler(new CloseHandler<PopupPanel>() {
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ boolean notified = notifyIfNecessary(
+ "weaving Rnw files",
+ previousRnwWeaveMethod_,
+ uiPrefs_.defaultSweaveEngine());
+
+ if (!notified)
+ {
+ notifyIfNecessary("LaTeX typesetting",
+ previousLatexProgram_,
+ uiPrefs_.defaultLatexProgram());
+ }
+ }
+
+ });
+ }
+
+ private boolean notifyIfNecessary(String valueName,
+ String previousValue,
+ PrefValue<String> pref)
+ {
+ if (!previousValue.equals(pref.getGlobalValue()) &&
+ !pref.getValue().equals(pref.getGlobalValue()))
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Project Option Unchanged",
+ "You changed the global option for " + valueName + " to " +
+ pref.getGlobalValue() + ", however the current project is " +
+ "still configured to use " + pref.getValue() + ".\n\n" +
+ "Do you want to edit the options for the current " +
+ "project as well?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ commands_.projectSweaveOptions().execute();
+ }
+ },
+ true);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private final String previousRnwWeaveMethod_;
+ private final String previousLatexProgram_;
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private final WorkbenchServerOperations server_;
+ private final Commands commands_;
+ private final UIPrefs uiPrefs_;
+ private final Provider<PreferencesDialog> pPrefDialog_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneConfig.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneConfig.java
new file mode 100644
index 0000000..99e67c5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneConfig.java
@@ -0,0 +1,298 @@
+/*
+ * PaneConfig.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.js.JsUtil;
+
+import java.util.*;
+
+public class PaneConfig extends JavaScriptObject
+{
+ public native static PaneConfig create(JsArrayString panes,
+ JsArrayString tabSet1,
+ JsArrayString tabSet2) /*-{
+ return { panes: panes, tabSet1: tabSet1, tabSet2: tabSet2 };
+ }-*/;
+
+ public static PaneConfig createDefault()
+ {
+ JsArrayString panes = createArray().cast();
+ panes.push("Source");
+ panes.push("Console");
+ panes.push("TabSet1");
+ panes.push("TabSet2");
+
+ JsArrayString tabSet1 = createArray().cast();
+ tabSet1.push("Environment");
+ tabSet1.push("History");
+ tabSet1.push("Build");
+ tabSet1.push("VCS");
+ tabSet1.push("Presentation");
+
+ JsArrayString tabSet2 = createArray().cast();
+ tabSet2.push("Files");
+ tabSet2.push("Plots");
+ tabSet2.push("Packages");
+ tabSet2.push("Help");
+ tabSet2.push("Viewer");
+
+ return create(panes, tabSet1, tabSet2);
+ }
+
+ public static String[] getAllPanes()
+ {
+ return new String[] {"Source", "Console", "TabSet1", "TabSet2"};
+ }
+
+ public static String[] getAllTabs()
+ {
+ return new String[] {"Environment", "History", "Files", "Plots",
+ "Packages", "Help", "Build", "VCS", "Presentation",
+ "Viewer"};
+ }
+
+ public static String[] getAlwaysVisibleTabs()
+ {
+ return new String[] {"Environment", "History", "Files", "Plots",
+ "Help", "Viewer"};
+ }
+
+ public static String[] getHideableTabs()
+ {
+ return new String[] {"Build", "VCS", "Presentation", "Packages" };
+ }
+
+ // Any tabs that were added after our first public release.
+ public static String[] getAddableTabs()
+ {
+ return new String[] {"Build", "VCS", "Presentation", "Viewer" };
+ }
+
+ // Tabs that have been replaced by newer versions/replaceable supersets
+ public static String[] getReplacedTabs()
+ {
+ return new String[] {"Workspace"};
+ }
+
+ // The tabs that replace those in getReplacedTabs(), order-matched
+ public static String[] getReplacementTabs()
+ {
+ return new String[] {"Environment"};
+ }
+
+ // Given the name of a tab, return the index of the tab that it should
+ // replace it, or -1 if the tab doesn't have a replacement
+ public static int indexOfReplacedTab(String tab)
+ {
+ String[] replacedTabs = getReplacedTabs();
+ int idx;
+ for (idx = 0; idx < replacedTabs.length; idx++)
+ {
+ if (tab.equals(replacedTabs[idx]))
+ {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ // Given an array of tabs, replace any obsolete entries with their
+ // replacements
+ public static void replaceObsoleteTabs(JsArrayString tabs)
+ {
+ for (int idx = 0; idx < tabs.length(); idx++)
+ {
+ if (indexOfReplacedTab(tabs.get(idx)) >= 0)
+ {
+ tabs.set(idx, getReplacementTabs()[idx]);
+ }
+ }
+ }
+
+ protected PaneConfig()
+ {
+ }
+
+ public native final JsArrayString getPanes() /*-{
+ return this.panes;
+ }-*/;
+
+ public native final void setPanes(JsArrayString panes) /*-{
+ this.panes = panes;
+ }-*/;
+
+ public native final JsArrayString getTabSet1() /*-{
+ return this.tabSet1;
+ }-*/;
+
+ public native final void setTabSet1(JsArrayString tabSet) /*-{
+ this.tabSet1 = tabSet;
+ }-*/;
+
+ public native final JsArrayString getTabSet2() /*-{
+ return this.tabSet2;
+ }-*/;
+
+ public native final void setTabSet2(JsArrayString tabSet) /*-{
+ this.tabSet2 = tabSet;
+ }-*/;
+
+ public final boolean validateAndAutoCorrect()
+ {
+ JsArrayString panes = getPanes();
+ if (panes == null)
+ return false;
+ if (!sameElements(panes, new String[] {"Source", "Console", "TabSet1", "TabSet2"}))
+ return false;
+
+ JsArrayString ts1 = getTabSet1();
+ JsArrayString ts2 = getTabSet2();
+ if (ts1.length() == 0 || ts2.length() == 0)
+ return false;
+
+ // Replace any obsoleted tabs in the config
+ replaceObsoleteTabs(ts1);
+ replaceObsoleteTabs(ts2);
+
+ // If any of these tabs are missing, then they can be added
+ Set<String> addableTabs = makeSet(getAddableTabs());
+
+ // If any of these tabs are missing, then the whole config is invalid
+ Set<String> baseTabs = makeSet(getAllTabs());
+ baseTabs.removeAll(addableTabs);
+
+ for (String tab : JsUtil.asIterable(concat(ts1, ts2)))
+ {
+ if (!baseTabs.remove(tab) && !addableTabs.remove(tab))
+ return false; // unknown tab
+ }
+
+ // If any baseTabs are still present, they weren't part of the tabsets
+ if (baseTabs.size() > 0)
+ return false;
+
+ // Were any addable tabs missing? Add them the appropriate tabset
+ // (Iterate over original array instead of addableTabs set so that order
+ // is well-defined)
+ for (String tab : getAddableTabs())
+ if (addableTabs.contains(tab))
+ if (tab.equals("Viewer"))
+ ts2.push(tab);
+ else
+ ts1.push(tab);
+
+ // These tabs can be hidden sometimes; they can't stand alone in a tabset
+ Set<String> hideableTabs = makeSet(getHideableTabs());
+ if (isSubset(hideableTabs, JsUtil.asIterable(ts1))
+ || isSubset(hideableTabs, JsUtil.asIterable(ts2)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean isSubset(Set<String> set, Iterable<String> possibleSubset)
+ {
+ for (String el : possibleSubset)
+ if (!set.contains(el))
+ return false;
+ return true;
+ }
+
+ private static Set<String> makeSet(String... values)
+ {
+ TreeSet<String> set = new TreeSet<String>();
+ for (String val : values)
+ set.add(val);
+ return set;
+ }
+
+ public final PaneConfig copy()
+ {
+ return create(copy(getPanes()),
+ copy(getTabSet1()),
+ copy(getTabSet2()));
+ }
+
+ public final native boolean isEqualTo(PaneConfig other) /*-{
+ return other != null &&
+ this.panes.toString() == other.panes.toString() &&
+ this.tabSet1.toString() == other.tabSet1.toString() &&
+ this.tabSet2.toString() == other.tabSet2.toString();
+ }-*/;
+
+ private boolean sameElements(JsArrayString a, String[] b)
+ {
+ if (a.length() != b.length)
+ return false;
+
+ ArrayList<String> a1 = new ArrayList<String>();
+ for (int i = 0; i < a.length(); i++)
+ a1.add(a.get(i));
+ Collections.sort(a1);
+
+ Arrays.sort(b);
+
+ for (int i = 0; i < b.length; i++)
+ if (!a1.get(i).equals(b[i]))
+ return false;
+
+ return true;
+ }
+
+ private JsArrayString concat(JsArrayString a, JsArrayString b)
+ {
+ JsArrayString ab = createArray().cast();
+ for (int i = 0; i < a.length(); i++)
+ ab.push(a.get(i));
+ for (int i = 0; i < b.length(); i++)
+ ab.push(b.get(i));
+ return ab;
+ }
+
+ private static JsArrayString copy(JsArrayString array)
+ {
+ if (array == null)
+ return null;
+
+ JsArrayString copy = JsArrayString.createArray().cast();
+ for (int i = 0; i < array.length(); i++)
+ copy.push(array.get(i));
+ return copy;
+ }
+
+ public static boolean isValidConfig(ArrayList<String> tabs)
+ {
+ if (isSubset(makeSet(getHideableTabs()), tabs))
+ {
+ // The proposed tab config only contains hideable tabs (or possibly
+ // no tabs at all). Reject.
+ return false;
+ }
+ else if (isSubset(makeSet(tabs.toArray(new String[tabs.size()])),
+ makeSet(getAlwaysVisibleTabs())))
+ {
+ // The proposed tab config contains all the always-visible tabs,
+ // which implies that the other tab config only contains hideable
+ // tabs (or possibly no tabs at all). Reject.
+ return false;
+ }
+ else
+ return true;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneManager.java
new file mode 100644
index 0000000..6a0d60c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/PaneManager.java
@@ -0,0 +1,508 @@
+/*
+ * PaneManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Triad;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.layout.DualWindowLayoutPanel;
+import org.rstudio.core.client.layout.LogicalWindow;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.MinimizedModuleTabLayoutPanel;
+import org.rstudio.core.client.theme.MinimizedWindowFrame;
+import org.rstudio.core.client.theme.PrimaryWindowFrame;
+import org.rstudio.core.client.theme.WindowFrame;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
+import org.rstudio.studio.client.workbench.model.helper.IntStateValue;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.ConsoleInterruptButton;
+import org.rstudio.studio.client.workbench.views.console.ConsolePane;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputTab;
+import org.rstudio.studio.client.workbench.views.source.SourceShim;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/*
+ * TODO: Push client state when selected tab or layout changes
+ */
+
+public class PaneManager
+{
+ public interface Binder extends CommandBinder<Commands, PaneManager> {}
+
+ public enum Tab {
+ History, Files, Plots, Packages, Help, VCS, Build,
+ Presentation, Environment, Viewer
+ }
+
+ class SelectedTabStateValue extends IntStateValue
+ {
+ SelectedTabStateValue(String name,
+ WorkbenchTabPanel tabPanel)
+ {
+ super("workbench-pane", name, ClientState.PROJECT_PERSISTENT,
+ session_.getSessionInfo().getClientState(), true);
+ tabPanel_ = tabPanel;
+ finishInit(session_.getSessionInfo().getClientState());
+ }
+
+ @Override
+ protected void onInit(Integer value)
+ {
+ if (value != null)
+ tabPanel_.selectTab(value);
+ }
+
+ @Override
+ protected Integer getValue() { return tabPanel_.getSelectedIndex(); }
+
+ private final WorkbenchTabPanel tabPanel_;
+ }
+
+ @Inject
+ public PaneManager(Provider<MainSplitPanel> pSplitPanel,
+ WorkbenchServerOperations server,
+ EventBus eventBus,
+ Session session,
+ Binder binder,
+ Commands commands,
+ UIPrefs uiPrefs,
+ @Named("Console") final Widget consolePane,
+ ConsoleInterruptButton consoleInterrupt,
+ SourceShim source,
+ @Named("History") final WorkbenchTab historyTab,
+ @Named("Files") final WorkbenchTab filesTab,
+ @Named("Plots") final WorkbenchTab plotsTab,
+ @Named("Packages") final WorkbenchTab packagesTab,
+ @Named("Help") final WorkbenchTab helpTab,
+ @Named("VCS") final WorkbenchTab vcsTab,
+ @Named("Build") final WorkbenchTab buildTab,
+ @Named("Presentation") final WorkbenchTab presentationTab,
+ @Named("Environment") final WorkbenchTab environmentTab,
+ @Named("Viewer") final WorkbenchTab viewerTab,
+ @Named("Compile PDF") final WorkbenchTab compilePdfTab,
+ @Named("Source Cpp") final WorkbenchTab sourceCppTab,
+ final FindOutputTab findOutputTab)
+ {
+ eventBus_ = eventBus;
+ session_ = session;
+ commands_ = commands;
+ consolePane_ = (ConsolePane)consolePane;
+ consoleInterrupt_ = consoleInterrupt;
+ source_ = source;
+ historyTab_ = historyTab;
+ filesTab_ = filesTab;
+ plotsTab_ = plotsTab;
+ packagesTab_ = packagesTab;
+ helpTab_ = helpTab;
+ vcsTab_ = vcsTab;
+ buildTab_ = buildTab;
+ presentationTab_ = presentationTab;
+ environmentTab_ = environmentTab;
+ viewerTab_ = viewerTab;
+ compilePdfTab_ = compilePdfTab;
+ findOutputTab_ = findOutputTab;
+ sourceCppTab_ = sourceCppTab;
+
+ binder.bind(commands, this);
+
+ PaneConfig config = validateConfig(uiPrefs.paneConfig().getValue());
+ initPanes(config);
+
+ panes_ = createPanes(config);
+ left_ = createSplitWindow(panes_.get(0), panes_.get(1), "left", 0.4);
+ right_ = createSplitWindow(panes_.get(2), panes_.get(3), "right", 0.6);
+
+ panel_ = pSplitPanel.get();
+ panel_.initialize(left_, right_);
+
+ if (session_.getSessionInfo().getSourceDocuments().length() == 0
+ && sourceLogicalWindow_.getState() != WindowState.HIDE)
+ {
+ sourceLogicalWindow_.onWindowStateChange(
+ new WindowStateChangeEvent(WindowState.HIDE));
+ }
+ else if (session_.getSessionInfo().getSourceDocuments().length() > 0
+ && sourceLogicalWindow_.getState() == WindowState.HIDE)
+ {
+ sourceLogicalWindow_.onWindowStateChange(
+ new WindowStateChangeEvent(WindowState.NORMAL));
+ }
+
+ uiPrefs.paneConfig().addValueChangeHandler(new ValueChangeHandler<PaneConfig>()
+ {
+ public void onValueChange(ValueChangeEvent<PaneConfig> evt)
+ {
+ ArrayList<LogicalWindow> newPanes = createPanes(validateConfig(evt.getValue()));
+ panes_ = newPanes;
+ left_.replaceWindows(newPanes.get(0), newPanes.get(1));
+ right_.replaceWindows(newPanes.get(2), newPanes.get(3));
+
+ tabSet1TabPanel_.clear();
+ tabSet2TabPanel_.clear();
+ populateTabPanel(tabNamesToTabs(evt.getValue().getTabSet1()),
+ tabSet1TabPanel_, tabSet1MinPanel_);
+ populateTabPanel(tabNamesToTabs(evt.getValue().getTabSet2()),
+ tabSet2TabPanel_, tabSet2MinPanel_);
+ }
+ });
+ }
+
+ @Handler
+ public void onMaximizeConsole()
+ {
+ LogicalWindow consoleWindow = panesByName_.get("Console");
+ if (consoleWindow.getState() != WindowState.MAXIMIZE)
+ {
+ consoleWindow.onWindowStateChange(
+ new WindowStateChangeEvent(WindowState.MAXIMIZE));
+ }
+ }
+
+ private ArrayList<LogicalWindow> createPanes(PaneConfig config)
+ {
+ ArrayList<LogicalWindow> results = new ArrayList<LogicalWindow>();
+
+ JsArrayString panes = config.getPanes();
+ for (int i = 0; i < 4; i++)
+ {
+ results.add(panesByName_.get(panes.get(i)));
+ }
+ return results;
+ }
+
+ private void initPanes(PaneConfig config)
+ {
+ panesByName_ = new HashMap<String, LogicalWindow>();
+ panesByName_.put("Console", createConsole());
+ panesByName_.put("Source", createSource());
+
+ Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel> ts1 = createTabSet(
+ "TabSet1",
+ tabNamesToTabs(config.getTabSet1()));
+ panesByName_.put("TabSet1", ts1.first);
+ tabSet1TabPanel_ = ts1.second;
+ tabSet1MinPanel_ = ts1.third;
+
+ Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel> ts2 = createTabSet(
+ "TabSet2",
+ tabNamesToTabs(config.getTabSet2()));
+ panesByName_.put("TabSet2", ts2.first);
+ tabSet2TabPanel_ = ts2.second;
+ tabSet2MinPanel_ = ts2.third;
+ }
+
+ private ArrayList<Tab> tabNamesToTabs(JsArrayString tabNames)
+ {
+ ArrayList<Tab> tabList = new ArrayList<Tab>();
+ for (int j = 0; j < tabNames.length(); j++)
+ tabList.add(Enum.valueOf(Tab.class, tabNames.get(j)));
+ return tabList;
+ }
+
+ private PaneConfig validateConfig(PaneConfig config)
+ {
+ if (config == null)
+ config = PaneConfig.createDefault();
+ if (!config.validateAndAutoCorrect())
+ {
+ Debug.log("Pane config is not valid");
+ config = PaneConfig.createDefault();
+ }
+ return config;
+ }
+
+ public MainSplitPanel getPanel()
+ {
+ return panel_;
+ }
+
+ public WorkbenchTab getTab(Tab tab)
+ {
+ switch (tab)
+ {
+ case History:
+ return historyTab_;
+ case Files:
+ return filesTab_;
+ case Plots:
+ return plotsTab_;
+ case Packages:
+ return packagesTab_;
+ case Help:
+ return helpTab_;
+ case VCS:
+ return vcsTab_;
+ case Build:
+ return buildTab_;
+ case Presentation:
+ return presentationTab_;
+ case Environment:
+ return environmentTab_;
+ case Viewer:
+ return viewerTab_;
+ }
+ throw new IllegalArgumentException("Unknown tab");
+ }
+
+ public WorkbenchTab[] getAllTabs()
+ {
+ return new WorkbenchTab[] { historyTab_, filesTab_,
+ plotsTab_, packagesTab_, helpTab_,
+ vcsTab_, buildTab_, presentationTab_,
+ environmentTab_, viewerTab_};
+ }
+
+ public void activateTab(Tab tab)
+ {
+ tabToPanel_.get(tab).selectTab(tabToIndex_.get(tab));
+ }
+
+ public void activateTab(String tabName)
+ {
+ Tab tab = tabForName(tabName);
+ if (tab != null)
+ activateTab(tab);
+ }
+
+ public ConsolePane getConsole()
+ {
+ return consolePane_;
+ }
+
+ public WorkbenchTabPanel getOwnerTabPanel(Tab tab)
+ {
+ return tabToPanel_.get(tab);
+ }
+
+ public LogicalWindow getSourceLogicalWindow()
+ {
+ return sourceLogicalWindow_;
+ }
+
+ private DualWindowLayoutPanel createSplitWindow(LogicalWindow top,
+ LogicalWindow bottom,
+ String name,
+ double bottomDefaultPct)
+ {
+ return new DualWindowLayoutPanel(
+ eventBus_,
+ top,
+ bottom,
+ session_,
+ name,
+ WindowState.NORMAL,
+ (int) (Window.getClientHeight()*bottomDefaultPct));
+ }
+
+ private LogicalWindow createConsole()
+ {
+ PrimaryWindowFrame frame = new PrimaryWindowFrame("Console", null);
+
+ ToolbarButton goToWorkingDirButton =
+ commands_.goToWorkingDir().createToolbarButton();
+ goToWorkingDirButton.addStyleName(
+ ThemeResources.INSTANCE.themeStyles().windowFrameToolbarButton());
+
+ @SuppressWarnings("unused")
+ ConsoleTabPanel consoleTabPanel = new ConsoleTabPanel(frame,
+ consolePane_,
+ compilePdfTab_,
+ findOutputTab_,
+ sourceCppTab_,
+ eventBus_,
+ consoleInterrupt_,
+ goToWorkingDirButton);
+
+ return new LogicalWindow(frame, new MinimizedWindowFrame("Console"));
+ }
+
+ private LogicalWindow createSource()
+ {
+ WindowFrame sourceFrame = new WindowFrame();
+ sourceFrame.setFillWidget(source_.asWidget());
+ source_.forceLoad();
+ return sourceLogicalWindow_ = new LogicalWindow(
+ sourceFrame,
+ new MinimizedWindowFrame("Source"));
+ }
+
+ private
+ Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel>
+ createTabSet(String persisterName, ArrayList<Tab> tabs)
+ {
+ final WindowFrame frame = new WindowFrame();
+ final WorkbenchTabPanel tabPanel = new WorkbenchTabPanel(frame);
+ MinimizedModuleTabLayoutPanel minimized = new MinimizedModuleTabLayoutPanel();
+
+ populateTabPanel(tabs, tabPanel, minimized);
+
+ frame.setFillWidget(tabPanel);
+
+ minimized.addSelectionHandler(new SelectionHandler<Integer>()
+ {
+ public void onSelection(SelectionEvent<Integer> integerSelectionEvent)
+ {
+ int tab = integerSelectionEvent.getSelectedItem();
+ tabPanel.selectTab(tab);
+ }
+ });
+
+ tabPanel.addSelectionHandler(new SelectionHandler<Integer>()
+ {
+ public void onSelection(SelectionEvent<Integer> integerSelectionEvent)
+ {
+ session_.persistClientState();
+ }
+ });
+
+ new SelectedTabStateValue(persisterName, tabPanel);
+
+ return new Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel>(
+ new LogicalWindow(frame, minimized),
+ tabPanel,
+ minimized);
+ }
+
+ private void populateTabPanel(ArrayList<Tab> tabs,
+ WorkbenchTabPanel tabPanel,
+ MinimizedModuleTabLayoutPanel minimized)
+ {
+ ArrayList<WorkbenchTab> tabList = new ArrayList<WorkbenchTab>();
+ for (int i = 0; i < tabs.size(); i++)
+ {
+ Tab tab = tabs.get(i);
+ tabList.add(getTab(tab));
+ tabToPanel_.put(tab, tabPanel);
+ tabToIndex_.put(tab, i);
+ }
+ tabPanel.setTabs(tabList);
+
+ ArrayList<String> labels = new ArrayList<String>();
+ for (Tab tab : tabs)
+ {
+ if (!getTab(tab).isSuppressed())
+ labels.add(getTabLabel(tab));
+ }
+ minimized.setTabs(labels.toArray(new String[labels.size()]));
+ }
+
+ private String getTabLabel(Tab tab)
+ {
+ switch (tab)
+ {
+ case History:
+ return "History";
+ case Files:
+ return "Files";
+ case Plots:
+ return "Plots";
+ case Packages:
+ return "Packages";
+ case Help:
+ return "Help";
+ case VCS:
+ return getTab(tab).getTitle();
+ case Build:
+ return "Build";
+ case Presentation:
+ return getTab(tab).getTitle();
+ case Environment:
+ return "Environment";
+ case Viewer:
+ return "Viewer";
+ }
+ return "??";
+ }
+
+ private Tab tabForName(String name)
+ {
+ if (name.equalsIgnoreCase("history"))
+ return Tab.History;
+ if (name.equalsIgnoreCase("files"))
+ return Tab.Files;
+ if (name.equalsIgnoreCase("plots"))
+ return Tab.Plots;
+ if (name.equalsIgnoreCase("packages"))
+ return Tab.Packages;
+ if (name.equalsIgnoreCase("help"))
+ return Tab.Help;
+ if (name.equalsIgnoreCase("vcs"))
+ return Tab.VCS;
+ if (name.equalsIgnoreCase("build"))
+ return Tab.Build;
+ if (name.equalsIgnoreCase("presentation"))
+ return Tab.Presentation;
+ if (name.equalsIgnoreCase("environment"))
+ return Tab.Environment;
+ if (name.equalsIgnoreCase("viewer"))
+ return Tab.Viewer;
+
+ return null;
+ }
+
+ private final EventBus eventBus_;
+ private final Session session_;
+ private final Commands commands_;
+ private final FindOutputTab findOutputTab_;
+ private final WorkbenchTab compilePdfTab_;
+ private final WorkbenchTab sourceCppTab_;
+ private final ConsolePane consolePane_;
+ private final ConsoleInterruptButton consoleInterrupt_;
+ private final SourceShim source_;
+ private final WorkbenchTab historyTab_;
+ private final WorkbenchTab filesTab_;
+ private final WorkbenchTab plotsTab_;
+ private final WorkbenchTab packagesTab_;
+ private final WorkbenchTab helpTab_;
+ private final WorkbenchTab vcsTab_;
+ private final WorkbenchTab buildTab_;
+ private final WorkbenchTab presentationTab_;
+ private final WorkbenchTab environmentTab_;
+ private final WorkbenchTab viewerTab_;
+ private MainSplitPanel panel_;
+ private LogicalWindow sourceLogicalWindow_;
+ private final HashMap<Tab, WorkbenchTabPanel> tabToPanel_ =
+ new HashMap<Tab, WorkbenchTabPanel>();
+ private final HashMap<Tab, Integer> tabToIndex_ =
+ new HashMap<Tab, Integer>();
+ private HashMap<String, LogicalWindow> panesByName_;
+ private DualWindowLayoutPanel left_;
+ private DualWindowLayoutPanel right_;
+ private ArrayList<LogicalWindow> panes_;
+ private WorkbenchTabPanel tabSet1TabPanel_;
+ private MinimizedModuleTabLayoutPanel tabSet1MinPanel_;
+ private WorkbenchTabPanel tabSet2TabPanel_;
+ private MinimizedModuleTabLayoutPanel tabSet2MinPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/ToolbarPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/ToolbarPane.java
new file mode 100644
index 0000000..f4b41c7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/ToolbarPane.java
@@ -0,0 +1,188 @@
+/*
+ * ToolbarPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.LazyPanel;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.widget.SecondaryToolbar;
+import org.rstudio.core.client.widget.SimplePanelWithProgress;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.common.AutoGlassPanel;
+
+public abstract class ToolbarPane extends LazyPanel implements RequiresResize,
+ HasEnsureVisibleHandlers,
+ HasEnsureHeightHandlers
+{
+ public Widget asWidget()
+ {
+ ensureWidget();
+ return this;
+ }
+
+ public void setProgress(boolean progress)
+ {
+ ensureWidget();
+
+ if (progress)
+ progressPanel_.showProgress(progressDelayMs_);
+ else
+ {
+ progressPanel_.setWidget(mainWidget_);
+ dockPanel_.forceLayout();
+ }
+ }
+
+ @Override
+ protected Widget createWidget()
+ {
+ dockPanel_ = new DockLayoutPanel(Style.Unit.PX);
+ dockPanel_.setSize("100%", "100%");
+
+ mainToolbar_ = createMainToolbar();
+ if (mainToolbar_ != null)
+ addToolbar(mainToolbar_);
+
+ secondaryToolbar_ = createSecondaryToolbar();
+ if (secondaryToolbar_ !=null)
+ addToolbar(secondaryToolbar_);
+
+ mainWidget_ = createMainWidget() ;
+ mainWidget_.setSize("100%", "100%");
+
+ progressPanel_ = new SimplePanelWithProgress();
+ progressPanel_.setSize("100%", "100%");
+ progressPanel_.setWidget(mainWidget_);
+
+ dockPanel_.add(progressPanel_);
+
+ AutoGlassPanel glassPanel = new AutoGlassPanel(dockPanel_);
+ glassPanel.setSize("100%", "100%");
+ return glassPanel;
+ }
+
+ public void onResize()
+ {
+ Widget child = getWidget();
+ if (child != null && child instanceof RequiresResize)
+ ((RequiresResize) child).onResize();
+ }
+
+ protected abstract Widget createMainWidget();
+
+ protected Toolbar createMainToolbar()
+ {
+ return null ;
+ }
+
+ protected SecondaryToolbar createSecondaryToolbar()
+ {
+ return null ;
+ }
+
+ public int getToolbarsHeight()
+ {
+ return (mainToolbar_ != null ? mainToolbar_.getOffsetHeight() : 0)
+ + (secondaryToolbar_ != null ? secondaryToolbar_.getOffsetHeight() : 0);
+ }
+
+ protected void setProgressDelay(int delayMs)
+ {
+ progressDelayMs_ = delayMs;
+ }
+
+ private void addToolbar(Toolbar toolbar)
+ {
+ dockPanel_.addNorth(toolbar, toolbar.getHeight());
+ }
+
+ public void bringToFront()
+ {
+ ensureWidget();
+ fireEvent(new EnsureVisibleEvent());
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ public HandlerRegistration addEnsureHiddenHandler(EnsureHiddenHandler handler)
+ {
+ return addHandler(handler, EnsureHiddenEvent.TYPE);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ public void ensureVisible()
+ {
+ fireEvent(new EnsureVisibleEvent());
+ }
+
+ public void ensureHidden()
+ {
+ fireEvent(new EnsureHiddenEvent());
+ }
+
+ public void ensureHeight(int height)
+ {
+ fireEvent(new EnsureHeightEvent(height));
+ }
+
+ public void maximize()
+ {
+ fireEvent(new EnsureHeightEvent(EnsureHeightEvent.MAXIMIZED));
+ }
+
+ public boolean isMainToolbarVisible()
+ {
+ return mainToolbar_.isVisible();
+ }
+
+ public void setMainToolbarVisible(boolean visible)
+ {
+ setToolbarVisibility(visible, mainToolbar_);
+ }
+
+ public void setSecondaryToolbarVisible(boolean visible)
+ {
+ setToolbarVisibility(visible, secondaryToolbar_);
+ }
+
+ private void setToolbarVisibility(final boolean visible,
+ final Toolbar toolbar)
+ {
+ if (visible == toolbar.isVisible())
+ return;
+
+ toolbar.setVisible(visible);
+ dockPanel_.setWidgetSize(toolbar, visible ? toolbar.getHeight()
+ : 0);
+ }
+
+ private DockLayoutPanel dockPanel_;
+ protected Toolbar mainToolbar_ ;
+ protected Toolbar secondaryToolbar_ ;
+ private Widget mainWidget_ ;
+ private SimplePanelWithProgress progressPanel_ ;
+ private int progressDelayMs_ = 200 ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchPane.java
new file mode 100644
index 0000000..2a01146
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchPane.java
@@ -0,0 +1,69 @@
+/*
+ * WorkbenchPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.user.client.Command;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+
+public abstract class WorkbenchPane extends ToolbarPane
+ implements WorkbenchView,
+ WorkbenchTab
+{
+ protected WorkbenchPane(String title)
+ {
+ title_ = title ;
+ }
+
+ public void prefetch(Command continuation)
+ {
+ continuation.execute();
+ }
+
+ public String getTitle()
+ {
+ return title_ ;
+ }
+
+ // hook for subclasses to be notified right before & after they are selected
+ public void onBeforeUnselected()
+ {
+ }
+ public void onBeforeSelected()
+ {
+ }
+ public void onSelected()
+ {
+ }
+
+ @Override
+ public boolean isSuppressed()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return false;
+ }
+
+ @Override
+ public void confirmClose(Command onConfirmed)
+ {
+ onConfirmed.execute();
+ }
+
+ private String title_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchScreen.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchScreen.java
new file mode 100644
index 0000000..0f72878
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchScreen.java
@@ -0,0 +1,354 @@
+/*
+ * WorkbenchScreen.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.SerializedCommand;
+import org.rstudio.core.client.SerializedCommandQueue;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.WindowStateChangeEvent;
+import org.rstudio.core.client.layout.WindowState;
+import org.rstudio.core.client.theme.ModuleTabLayoutPanel;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.ChangeFontSizeEvent;
+import org.rstudio.studio.client.application.events.ChangeFontSizeHandler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.ui.appended.ApplicationEndedPopupPanel;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.FileMRUList;
+import org.rstudio.studio.client.workbench.WorkbenchMainView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.*;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.WorkbenchMetrics;
+import org.rstudio.studio.client.workbench.ui.PaneManager.Tab;
+import org.rstudio.studio.client.workbench.views.edit.Edit;
+import org.rstudio.studio.client.workbench.views.edit.Edit.Shim;
+import org.rstudio.studio.client.workbench.views.edit.events.ShowEditorEvent;
+import org.rstudio.studio.client.workbench.views.help.events.ActivateHelpEvent;
+import org.rstudio.studio.client.workbench.views.plots.PlotsTab;
+import org.rstudio.studio.client.workbench.views.source.events.LastSourceDocClosedEvent;
+import org.rstudio.studio.client.workbench.views.source.events.LastSourceDocClosedHandler;
+
+public class WorkbenchScreen extends Composite
+ implements WorkbenchMainView,
+ SelectionHandler<Integer>,
+ ActivatePaneHandler,
+ RequiresResize
+{
+ interface MyCommandBinder extends CommandBinder<Commands, WorkbenchScreen>{}
+ static MyCommandBinder commandBinder = GWT.create(MyCommandBinder.class);
+
+ @Inject
+ public WorkbenchScreen(GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ Session session,
+ Provider<PaneManager> pPaneManager,
+ final Edit.Shim edit,
+ Commands commands,
+ final Provider<FileMRUList> mruList,
+ FontSizeManager fontSizeManager,
+ OptionsLoader.Shim optionsLoader)
+ {
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ session_ = session;
+ edit_ = edit;
+ optionsLoader_ = optionsLoader;
+
+ if (!BrowseCap.isMacintoshDesktop() || BrowseCap.isCocoaDesktop())
+ commands.macPreferences().remove();
+
+ if (!Desktop.isDesktop() ||
+ !Desktop.getFrame().supportsFullscreenMode())
+ {
+ commands.toggleFullScreen().remove();
+ }
+
+ eventBus_.addHandler(ActivatePaneEvent.TYPE, this);
+ eventBus_.addHandler(ShowEditorEvent.TYPE, edit);
+ eventBus_.addHandler(ChangeFontSizeEvent.TYPE, new ChangeFontSizeHandler()
+ {
+ public void onChangeFontSize(ChangeFontSizeEvent event)
+ {
+ FontSizer.setNormalFontSize(Document.get(), event.getFontSize());
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ // Causes the console width to be remeasured
+ doOnPaneSizesChanged();
+ }
+ });
+ }
+ });
+ FontSizer.setNormalFontSize(Document.get(), fontSizeManager.getSize());
+
+
+ paneManager_ = pPaneManager.get();
+ tabsPanel_ = paneManager_.getPanel();
+ tabsPanel_.setSize("100%", "100%");
+ tabsPanel_.addStyleDependentName("Workbench");
+
+ // Prevent doOnPaneSizesChanged() from being called more than once
+ // every N milliseconds. Note that the act of sending the client metrics
+ // to the server also has its own buffer, so this one only needs to
+ // minimize the expense of doing the calculation, not of sending data to
+ // the server.
+ paneSizesChangedCommand_ = new TimeBufferedCommand(200)
+ {
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ assert !shouldSchedulePassive;
+ doOnPaneSizesChanged();
+ }
+ };
+
+ eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler()
+ {
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ prefetch();
+ mruList.get();
+ }
+ });
+
+ eventBus.addHandler(LastSourceDocClosedEvent.TYPE,
+ new LastSourceDocClosedHandler()
+ {
+ public void onLastSourceDocClosed(LastSourceDocClosedEvent event)
+ {
+ paneManager_.getSourceLogicalWindow().onWindowStateChange(
+ new WindowStateChangeEvent(WindowState.HIDE));
+ }
+ });
+
+ ((PlotsTab) paneManager_.getTab(Tab.Plots)).addResizeHandler(new ResizeHandler()
+ {
+ public void onResize(ResizeEvent event)
+ {
+ onPaneSizesChanged();
+ }
+ });
+ tabsPanel_.addSplitterResizedHandler(new SplitterResizedHandler()
+ {
+ public void onSplitterResized(SplitterResizedEvent event)
+ {
+ onPaneSizesChanged();
+ }
+ });
+
+ // init widget
+ initWidget(tabsPanel_);
+
+ commandBinder.bind(commands, this);
+ }
+
+ private void prefetch()
+ {
+ final SerializedCommandQueue prefetchQueue = new SerializedCommandQueue();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ onPaneSizesChanged();
+ }
+ });
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ for (final WorkbenchTab tab : paneManager_.getAllTabs())
+ prefetchQueue.addCommand(new SerializedCommand()
+ {
+ public void onExecute(Command continuation)
+ {
+ tab.prefetch(continuation);
+ }
+ });
+ prefetchQueue.addCommand(new SerializedCommand()
+ {
+ public void onExecute(Command continuation)
+ {
+ ApplicationEndedPopupPanel.prefetch(continuation);
+ }
+ });
+ prefetchQueue.addCommand(new SerializedCommand()
+ {
+ public void onExecute(Command continuation)
+ {
+ edit_.forceLoad(true, continuation);
+ }
+ });
+ prefetchQueue.addCommand(new SerializedCommand()
+ {
+ public void onExecute(Command continuation)
+ {
+ optionsLoader_.forceLoad(true, continuation);
+ }
+ });
+ }
+ });
+ }
+
+ public void onResize()
+ {
+ tabsPanel_.onResize();
+ onPaneSizesChanged();
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ eventBus_.fireEvent(new WorkbenchLoadedEvent());
+ }
+
+
+ private void onPaneSizesChanged()
+ {
+ paneSizesChangedCommand_.nudge();
+ }
+
+ private void doOnPaneSizesChanged()
+ {
+ // console width
+ int consoleWidth = paneManager_.getConsole().getCharacterWidth();
+
+ // if the console is hidden then just use its last value. if there
+ // has never been a valid value then then lastMetrics_ console width
+ // will be 0, which the server will know to ignore
+ if (paneManager_.getConsole().getOffsetWidth() <= 0)
+ consoleWidth = lastMetrics_.getConsoleWidth();
+
+ // plots size (don't allow negative metrics)
+ WorkbenchTabPanel plotPanel = paneManager_.getOwnerTabPanel(Tab.Plots);
+ Size deckPanelSize = new Size(
+ plotPanel.getOffsetWidth(),
+ plotPanel.getOffsetHeight() - ModuleTabLayoutPanel.BAR_HEIGHT);
+
+ Size plotsSize = new Size(
+ Math.max(deckPanelSize.width, 0),
+ Math.max(deckPanelSize.height - Toolbar.DEFAULT_HEIGHT, 0));
+
+ WorkbenchMetrics metrics = WorkbenchMetrics.create(consoleWidth,
+ plotsSize.width,
+ plotsSize.height);
+
+ // make sure we don't send very similar metrics values twice (it is
+ // an expensive operation since it involves at least 2 http requests)
+ if (!metrics.closeEnoughToPrevious(lastMetrics_))
+ {
+ lastMetrics_ = metrics;
+ eventBus_.fireEvent(new WorkbenchMetricsChangedEvent(metrics));
+ }
+
+ session_.persistClientState();
+ }
+
+ public void onSelection(SelectionEvent<Integer> integerSelectionEvent)
+ {
+ eventBus_.fireEvent(new PushClientStateEvent());
+ }
+
+ @Override
+ public void onActivatePane(ActivatePaneEvent event)
+ {
+ paneManager_.activateTab(event.getPane());
+ }
+
+ @Handler
+ void onActivateEnvironment() { paneManager_.activateTab(Tab.Environment); }
+ @Handler
+ void onActivateHistory() { paneManager_.activateTab(Tab.History); }
+ @Handler
+ void onActivateFiles() { paneManager_.activateTab(Tab.Files); }
+ @Handler
+ void onActivatePlots() { paneManager_.activateTab(Tab.Plots); }
+ @Handler
+ void onActivatePackages() { paneManager_.activateTab(Tab.Packages); }
+ @Handler
+ void onActivateHelp()
+ {
+ paneManager_.activateTab(Tab.Help);
+ eventBus_.fireEvent(new ActivateHelpEvent());
+ }
+ @Handler
+ void onActivateVcs() { paneManager_.activateTab(Tab.VCS); }
+ @Handler
+ void onActivateBuild() { paneManager_.activateTab(Tab.Build); }
+ @Handler
+ void onActivatePresentation() { paneManager_.activateTab(Tab.Presentation);}
+
+ @Handler
+ void onMacPreferences()
+ {
+ onShowOptions();
+ }
+
+ @Handler
+ void onShowOptions()
+ {
+ optionsLoader_.showOptions();
+ }
+
+
+ @Handler
+ void onVersionControlHelp()
+ {
+ globalDisplay_.openRStudioLink("using_version_control");
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ private TimeBufferedCommand paneSizesChangedCommand_;
+
+ private WorkbenchMetrics lastMetrics_ = WorkbenchMetrics.create(0,0,0);
+
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus eventBus_;
+ private final Session session_;
+ private final Shim edit_;
+ private final org.rstudio.studio.client.workbench.ui.OptionsLoader.Shim optionsLoader_;
+
+ private final MainSplitPanel tabsPanel_ ;
+ private PaneManager paneManager_;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTab.java
new file mode 100644
index 0000000..ca5e144
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTab.java
@@ -0,0 +1,37 @@
+/*
+ * WorkbenchTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.IsWidget;
+import org.rstudio.core.client.events.HasEnsureHiddenHandlers;
+import org.rstudio.core.client.events.HasEnsureHeightHandlers;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+
+public interface WorkbenchTab extends IsWidget,
+ HasEnsureVisibleHandlers,
+ HasEnsureHiddenHandlers,
+ HasEnsureHeightHandlers
+{
+ String getTitle();
+ void onBeforeUnselected();
+ void onBeforeSelected();
+ void onSelected();
+ void prefetch(Command continuation);
+ boolean isSuppressed();
+
+ boolean closeable();
+ void confirmClose(Command onConfirmed);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTabPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTabPanel.java
new file mode 100644
index 0000000..4296170
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/WorkbenchTabPanel.java
@@ -0,0 +1,268 @@
+/*
+ * WorkbenchTabPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.ProvidesResize;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.theme.ModuleTabLayoutPanel;
+import org.rstudio.core.client.theme.WindowFrame;
+
+import java.util.ArrayList;
+
+class WorkbenchTabPanel
+ extends Composite
+ implements RequiresResize,
+ ProvidesResize,
+ HasSelectionHandlers<Integer>,
+ HasEnsureVisibleHandlers,
+ HasEnsureHeightHandlers
+{
+ public WorkbenchTabPanel(WindowFrame owner)
+ {
+ tabPanel_ = new ModuleTabLayoutPanel(owner);
+ tabPanel_.setSize("100%", "100%");
+ tabPanel_.addStyleDependentName("Workbench");
+ initWidget(tabPanel_);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ releaseOnUnload_.add(tabPanel_.addBeforeSelectionHandler(new BeforeSelectionHandler<Integer>()
+ {
+ public void onBeforeSelection(BeforeSelectionEvent<Integer> event)
+ {
+ if (clearing_)
+ return;
+
+ if (getSelectedIndex() >= 0)
+ {
+ int unselectedTab = getSelectedIndex();
+ if (unselectedTab < tabs_.size())
+ {
+ WorkbenchTab lastTab = tabs_.get(unselectedTab);
+ lastTab.onBeforeUnselected();
+ }
+ }
+
+ int selectedTab = event.getItem().intValue();
+ if (selectedTab < tabs_.size())
+ {
+ WorkbenchTab tab = tabs_.get(selectedTab);
+ tab.onBeforeSelected();
+ }
+ }
+ }));
+ releaseOnUnload_.add(tabPanel_.addSelectionHandler(new SelectionHandler<Integer>()
+ {
+ public void onSelection(SelectionEvent<Integer> event)
+ {
+ if (clearing_)
+ return;
+
+ WorkbenchTab pane = tabs_.get(event.getSelectedItem().intValue());
+ pane.onSelected();
+ }
+ }));
+
+ int selectedIndex = tabPanel_.getSelectedIndex();
+ if (selectedIndex >= 0)
+ {
+ WorkbenchTab tab = tabs_.get(selectedIndex);
+ tab.onBeforeSelected();
+ tab.onSelected();
+ }
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ releaseOnUnload_.removeHandler();
+
+ super.onUnload();
+ }
+
+ public void setTabs(ArrayList<WorkbenchTab> tabs)
+ {
+ if (areTabsIdentical(tabs))
+ return;
+
+ tabPanel_.clear();
+ tabs_.clear();
+
+ for (WorkbenchTab tab : tabs)
+ add(tab);
+ }
+
+ private boolean areTabsIdentical(ArrayList<WorkbenchTab> tabs)
+ {
+ if (tabs_.size() != tabs.size())
+ return false;
+
+ // In case tab panels were removed implicitly (such as Console)
+ if (tabPanel_.getWidgetCount() != tabs.size())
+ return false;
+
+ for (int i = 0; i < tabs.size(); i++)
+ if (tabs_.get(i) != tabs.get(i))
+ return false;
+
+ return true;
+ }
+
+ private void add(final WorkbenchTab tab)
+ {
+ if (tab.isSuppressed())
+ return;
+
+ tabs_.add(tab);
+ final Widget widget = tab.asWidget();
+ tabPanel_.add(widget, tab.getTitle(), false, !tab.closeable() ? null : new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ tab.confirmClose(new Command() {
+ @Override
+ public void execute()
+ {
+ tab.ensureHidden();
+ }
+ });
+ }
+ });
+
+ tab.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ // First ensure that we ourselves are visible
+ fireEvent(new EnsureVisibleEvent(event.getActivate()));
+ if (event.getActivate())
+ tabPanel_.selectTab(widget);
+ }
+ });
+
+ tab.addEnsureHeightHandler(new EnsureHeightHandler() {
+
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ fireEvent(event);
+ }
+ });
+ }
+
+ public void selectTab(int tabIndex)
+ {
+ if (tabPanel_.getSelectedIndex() == tabIndex)
+ {
+ // if it's already selected then we still want to fire the
+ // onBeforeSelected and onSelected methods (so that actions
+ // like auto-focus are always taken)
+ int selected = getSelectedIndex();
+ if (selected != -1)
+ {
+ WorkbenchTab tab = tabs_.get(selected);
+ tab.onBeforeSelected();
+ tab.onSelected();
+ }
+
+ return;
+ }
+
+ // deal with migrating from n+1 to n tabs, and with -1 values
+ int safeIndex = Math.min(Math.max(0, tabIndex), tabs_.size() - 1);
+
+ tabPanel_.selectTab(safeIndex);
+ }
+
+ public void selectTab(WorkbenchTab pane)
+ {
+ int index = tabs_.indexOf(pane);
+ if (index != -1)
+ selectTab(index);
+ else
+ {
+ String title = pane.getTitle();
+ for (int i = 0; i < tabs_.size(); i++)
+ {
+ WorkbenchTab tab = tabs_.get(i);
+ if (tab.getTitle().equals(title))
+ {
+ selectTab(i);
+ return;
+ }
+ }
+ }
+ }
+
+ public int getSelectedIndex()
+ {
+ return tabPanel_.getSelectedIndex();
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<Integer> integerSelectionHandler)
+ {
+ return tabPanel_.addSelectionHandler(integerSelectionHandler);
+ }
+
+ public void onResize()
+ {
+ Widget w = getWidget();
+ if (w instanceof RequiresResize)
+ ((RequiresResize)w).onResize();
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(
+ EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addEnsureHeightHandler(
+ EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ public void clear()
+ {
+ clearing_ = true;
+ tabPanel_.clear();
+ tabs_.clear();
+ clearing_ = false;
+ }
+
+ private ModuleTabLayoutPanel tabPanel_;
+ private ArrayList<WorkbenchTab> tabs_ = new ArrayList<WorkbenchTab>();
+ private final HandlerRegistrations releaseOnUnload_ = new HandlerRegistrations();
+ private boolean clearing_ = false;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableResources.java
new file mode 100644
index 0000000..d7b4b08
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableResources.java
@@ -0,0 +1,32 @@
+/*
+ * UnsavedChangesCellTableResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui.unsaved;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.cellview.client.CellTable;
+
+public interface UnsavedChangesCellTableResources extends CellTable.Resources
+{
+ static UnsavedChangesCellTableResources INSTANCE =
+ (UnsavedChangesCellTableResources)GWT.create(UnsavedChangesCellTableResources.class) ;
+
+ interface UnsavedChangesCellTableStyle extends CellTable.Style
+ {
+ }
+
+ @Source("UnsavedChangesCellTableStyle.css")
+ UnsavedChangesCellTableStyle cellTableStyle();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableStyle.css
new file mode 100644
index 0000000..b46a132
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesCellTableStyle.css
@@ -0,0 +1,84 @@
+
+/* Based on:
+http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/gwt/user/cellview/client/CellTable.css
+*/
+
+
+ at def selectionBorderWidth 2px;
+.cellTableWidget {
+
+}
+
+.cellTableFirstColumn {
+}
+
+
+.cellTableLastColumn {
+}
+
+.cellTableFooter {
+}
+
+.cellTableHeader {
+}
+
+.cellTableCell {
+ padding: 3px 1px;
+ overflow: hidden;
+}
+
+.cellTableCell input[type=checkbox] {
+ margin-top: 7px;
+ margin-bottom: 0;
+}
+
+
+.cellTableFirstColumnFooter {
+
+}
+
+.cellTableFirstColumnHeader {
+
+}
+
+.cellTableLastColumnFooter {
+
+}
+
+.cellTableLastColumnHeader {
+}
+
+.cellTableSortableHeader {
+}
+
+.cellTableSortableHeader:hover {
+}
+
+.cellTableSortedHeaderAscending {
+
+}
+
+.cellTableSortedHeaderDescending {
+
+}
+
+.cellTableEvenRow, .cellTableHoveredRow, .cellTableSelectedRow, .cellTableKeyboardSelectedRow {
+ background: #ffffff;
+}
+
+.cellTableEvenRowCell, .cellTableHoveredRowCell, .cellTableSelectedRowCell, .cellTableKeyboardSelectedRowCell, .cellTableKeyboardSelectedCell {
+ border: selectionBorderWidth solid #ffffff;
+}
+
+.cellTableOddRow {
+ background-color: #eef4fb;
+}
+
+.cellTableOddRowCell {
+ border: selectionBorderWidth solid #eef4fb;;
+}
+
+.cellTableLoading {
+ margin: 30px;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.css
new file mode 100644
index 0000000..7ca2d42
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.css
@@ -0,0 +1,33 @@
+
+
+.captionLabel {
+ margin-bottom: 4px;
+ width: 400px;
+}
+
+.targetName {
+
+}
+
+.targetPath {
+ color: #606060;
+ font-size: 0.9em;
+ word-wrap: break-word;
+}
+
+.targetUntitled {
+ line-height: 2.2ex;
+ height: 4.4ex; /* 2.2ex for each visible line */
+ vertical-align: middle;
+ display: table-cell;
+
+}
+
+
+.targetScrollPanel {
+ width: 400px;
+ height: 170px;
+ border: 1px solid #BBB;
+ background-color: white;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.java
new file mode 100644
index 0000000..1cd36b6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/ui/unsaved/UnsavedChangesDialog.java
@@ -0,0 +1,309 @@
+/*
+ * UnsavedChangesDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.ui.unsaved;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.SafeHtmlUtil;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.ImageResourceCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.IdentityColumn;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.DefaultSelectionEventManager;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.ProvidesKey;
+
+public class UnsavedChangesDialog extends ModalDialog<UnsavedChangesDialog.Result>
+{
+ public class Result
+ {
+ public Result(ArrayList<UnsavedChangesTarget> saveTargets,
+ boolean alwaysSave)
+ {
+ saveTargets_ = saveTargets;
+ alwaysSave_ = alwaysSave;
+ }
+
+ public ArrayList<UnsavedChangesTarget> getSaveTargets()
+ {
+ return saveTargets_;
+ }
+
+ public boolean getAlwaysSave()
+ {
+ return alwaysSave_;
+ }
+
+ private ArrayList<UnsavedChangesTarget> saveTargets_;
+ private boolean alwaysSave_;
+ }
+
+ public UnsavedChangesDialog(
+ String title,
+ ArrayList<UnsavedChangesTarget> dirtyTargets,
+ final OperationWithInput<Result> saveOperation,
+ final Command onCancelled)
+ {
+ this(title, null, dirtyTargets, saveOperation, onCancelled);
+ }
+
+ public UnsavedChangesDialog(
+ String title,
+ String alwaysSaveOption,
+ ArrayList<UnsavedChangesTarget> dirtyTargets,
+ final OperationWithInput<Result> saveOperation,
+ final Command onCancelled)
+ {
+ super(title,
+ saveOperation,
+ onCancelled != null ? new Operation() {
+ @Override
+ public void execute()
+ {
+ onCancelled.execute();
+ }} :
+ null);
+ alwaysSaveOption_ = StringUtil.notNull(alwaysSaveOption);
+ targets_ = dirtyTargets;
+
+ setOkButtonCaption("Save Selected");
+
+ addLeftButton(new ThemedButton("Don't Save", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ closeDialog();
+ saveOperation.execute(new Result(
+ new ArrayList<UnsavedChangesTarget>(),
+ false));
+ }
+ }));
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // create cell table
+ targetsCellTable_ = new CellTable<UnsavedChangesTarget>(
+ 15,
+ UnsavedChangesCellTableResources.INSTANCE,
+ KEY_PROVIDER);
+ selectionModel_ = new MultiSelectionModel<UnsavedChangesTarget>(KEY_PROVIDER);
+ targetsCellTable_.setSelectionModel(
+ selectionModel_,
+ DefaultSelectionEventManager.<UnsavedChangesTarget> createCheckboxManager());
+ targetsCellTable_.setWidth("100%", true);
+
+ // add columns
+ addSelectionColumn();
+ addIconColumn();
+ addNameAndPathColumn();
+
+ // hook-up data provider
+ dataProvider_ = new ListDataProvider<UnsavedChangesTarget>();
+ dataProvider_.setList(targets_);
+ dataProvider_.addDataDisplay(targetsCellTable_);
+ targetsCellTable_.setPageSize(targets_.size());
+
+ // select all by default
+ for (UnsavedChangesTarget editingTarget : dataProvider_.getList())
+ selectionModel_.setSelected(editingTarget, true);
+
+ // enclose cell table in scroll panel
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setStylePrimaryName(RESOURCES.styles().targetScrollPanel());
+ scrollPanel.setWidget(targetsCellTable_);
+ if (dataProvider_.getList().size() > 4)
+ scrollPanel.setHeight("280px");
+
+ // always save check box (may not be shown)
+ chkAlwaysSave_ = new CheckBox(alwaysSaveOption_);
+
+ // main widget
+ VerticalPanel panel = new VerticalPanel();
+ Label captionLabel = new Label(
+ "The following files have unsaved changes:");
+ captionLabel.setStylePrimaryName(RESOURCES.styles().captionLabel());
+ panel.add(captionLabel);
+ panel.add(scrollPanel);
+ if (!StringUtil.isNullOrEmpty(alwaysSaveOption_))
+ {
+ panel.add(chkAlwaysSave_);
+ panel.setCellHeight(chkAlwaysSave_, "30px");
+ panel.setCellVerticalAlignment(chkAlwaysSave_,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+
+ }
+
+ return panel;
+ }
+
+ private Column<UnsavedChangesTarget, Boolean> addSelectionColumn()
+ {
+ Column<UnsavedChangesTarget, Boolean> checkColumn =
+ new Column<UnsavedChangesTarget, Boolean>(new CheckboxCell(true, false))
+ {
+ @Override
+ public Boolean getValue(UnsavedChangesTarget object)
+ {
+ return selectionModel_.isSelected(object);
+ }
+ };
+ checkColumn.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
+ targetsCellTable_.addColumn(checkColumn);
+ targetsCellTable_.setColumnWidth(checkColumn, 25, Unit.PX);
+
+ return checkColumn;
+ }
+
+
+ private Column<UnsavedChangesTarget, ImageResource> addIconColumn()
+ {
+ Column<UnsavedChangesTarget, ImageResource> iconColumn =
+ new Column<UnsavedChangesTarget, ImageResource>(new ImageResourceCell()) {
+
+ @Override
+ public ImageResource getValue(UnsavedChangesTarget object)
+ {
+ return object.getIcon();
+ }
+ };
+ targetsCellTable_.addColumn(iconColumn);
+ targetsCellTable_.setColumnWidth(iconColumn, 20, Unit.PX);
+
+ return iconColumn;
+ }
+
+ private class NameAndPathCell extends AbstractCell<UnsavedChangesTarget>
+ {
+
+ @Override
+ public void render(
+ com.google.gwt.cell.client.Cell.Context context,
+ UnsavedChangesTarget value, SafeHtmlBuilder sb)
+ {
+ if (value != null)
+ {
+ Styles styles = RESOURCES.styles();
+
+ String path = value.getPath();
+ if (path != null)
+ {
+ SafeHtmlUtil.appendDiv(sb, styles.targetName(), value.getTitle());
+ SafeHtmlUtil.appendDiv(sb, styles.targetPath(), path);
+ }
+ else
+ {
+ SafeHtmlUtil.appendDiv(sb,
+ styles.targetUntitled(),
+ value.getTitle());
+ }
+ }
+
+ }
+
+ }
+
+ private IdentityColumn<UnsavedChangesTarget> addNameAndPathColumn()
+ {
+ IdentityColumn<UnsavedChangesTarget> nameAndPathColumn =
+ new IdentityColumn<UnsavedChangesTarget>(new NameAndPathCell());
+
+ targetsCellTable_.addColumn(nameAndPathColumn);
+ targetsCellTable_.setColumnWidth(nameAndPathColumn, 350, Unit.PX);
+ return nameAndPathColumn;
+ }
+
+ @Override
+ protected Result collectInput()
+ {
+ return new Result(new ArrayList<UnsavedChangesTarget>(
+ selectionModel_.getSelectedSet()),
+ chkAlwaysSave_.getValue());
+ }
+
+ @Override
+ protected boolean validate(Result input)
+ {
+ return true;
+ }
+
+ static interface Styles extends CssResource
+ {
+ String targetScrollPanel();
+ String captionLabel();
+ String targetName();
+ String targetPath();
+ String targetUntitled();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("UnsavedChangesDialog.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources) GWT.create(Resources.class);
+
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private static final ProvidesKey<UnsavedChangesTarget> KEY_PROVIDER =
+ new ProvidesKey<UnsavedChangesTarget>() {
+ @Override
+ public Object getKey(UnsavedChangesTarget item)
+ {
+ return item.getId();
+ }
+ };
+
+ private final ArrayList<UnsavedChangesTarget> targets_;
+
+ private CellTable<UnsavedChangesTarget> targetsCellTable_;
+ private ListDataProvider<UnsavedChangesTarget> dataProvider_;
+ private MultiSelectionModel<UnsavedChangesTarget> selectionModel_;
+
+ private final String alwaysSaveOption_;
+ private CheckBox chkAlwaysSave_;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/BasePresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/BasePresenter.java
new file mode 100644
index 0000000..df79b0a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/BasePresenter.java
@@ -0,0 +1,49 @@
+/*
+ * BasePresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+
+public abstract class BasePresenter implements IsWidget
+{
+ protected BasePresenter(WorkbenchView view)
+ {
+ view_ = view;
+ }
+
+ public Widget asWidget()
+ {
+ return (Widget) view_;
+ }
+
+ public void onBeforeUnselected()
+ {
+ view_.onBeforeUnselected();
+ }
+
+ public void onBeforeSelected()
+ {
+ view_.onBeforeSelected();
+ }
+
+ public void onSelected()
+ {
+ view_.onSelected();
+ }
+
+ private final WorkbenchView view_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPane.java
new file mode 100644
index 0000000..e7916d6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPane.java
@@ -0,0 +1,163 @@
+/*
+ * BuildPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+import org.rstudio.studio.client.common.compile.CompileOutputBufferWithHighlight;
+import org.rstudio.studio.client.common.compile.CompilePanel;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+
+public class BuildPane extends WorkbenchPane implements BuildPresenter.Display
+{
+ @Inject
+ public BuildPane(Commands commands,
+ Session session)
+ {
+ super("Build");
+ commands_ = commands;
+ session_ = session;
+ compilePanel_ = new CompilePanel(new CompileOutputBufferWithHighlight());
+ ensureWidget();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ // always include build all
+ toolbar.addLeftWidget(commands_.buildAll().createToolbarButton());
+ toolbar.addLeftSeparator();
+
+ // packages get check package
+ String type = session_.getSessionInfo().getBuildToolsType();
+ if (type.equals(SessionInfo.BUILD_TOOLS_PACKAGE))
+ {
+ toolbar.addLeftWidget(commands_.checkPackage().createToolbarButton());
+ toolbar.addLeftSeparator();
+ }
+
+ // create more menu
+ ToolbarPopupMenu moreMenu = new ToolbarPopupMenu();
+ if (type.equals(SessionInfo.BUILD_TOOLS_MAKEFILE))
+ {
+ moreMenu.addItem(commands_.rebuildAll().createMenuItem(false));
+ moreMenu.addItem(commands_.cleanAll().createMenuItem(false));
+ moreMenu.addSeparator();
+ }
+
+ // packages get additional commands
+ else if (type.equals(SessionInfo.BUILD_TOOLS_PACKAGE))
+ {
+ moreMenu.addItem(commands_.devtoolsLoadAll().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.rebuildAll().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.buildSourcePackage().createMenuItem(false));
+ moreMenu.addItem(commands_.buildBinaryPackage().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.roxygenizePackage().createMenuItem(false));
+ moreMenu.addSeparator();
+ }
+ moreMenu.addItem(commands_.buildToolsProjectSetup().createMenuItem(false));
+
+ // add more menu
+ ToolbarButton moreButton = new ToolbarButton(
+ "More",
+ StandardIcons.INSTANCE.more_actions(),
+ moreMenu);
+ toolbar.addLeftWidget(moreButton);
+
+ // connect compile panel
+ compilePanel_.connectToolbar(toolbar);
+
+
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return compilePanel_;
+ }
+
+ @Override
+ public void buildStarted()
+ {
+ compilePanel_.compileStarted(null);
+ }
+
+ @Override
+ public void showOutput(CompileOutput output)
+ {
+ compilePanel_.showOutput(output);
+ }
+
+ @Override
+ public void showErrors(String basePath,
+ JsArray<CompileError> errors,
+ boolean ensureVisible,
+ int autoSelect)
+ {
+ compilePanel_.showErrors(basePath, errors, autoSelect);
+
+ if (ensureVisible && CompileError.showErrorList(errors))
+ ensureVisible();
+ }
+
+ @Override
+ public void buildCompleted()
+ {
+ compilePanel_.compileCompleted();
+ }
+
+ @Override
+ public HasClickHandlers stopButton()
+ {
+ return compilePanel_.stopButton();
+ }
+
+ @Override
+ public HasSelectionCommitHandlers<CodeNavigationTarget> errorList()
+ {
+ return compilePanel_.errorList();
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ compilePanel_.scrollToBottom();
+ }
+
+ private Commands commands_;
+ private Session session_;
+
+ CompilePanel compilePanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPresenter.java
new file mode 100644
index 0000000..24481a3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildPresenter.java
@@ -0,0 +1,376 @@
+/*
+ * BuildPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.SuspendAndRestartEvent;
+import org.rstudio.studio.client.common.DelayedProgressRequestCallback;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorList;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.prefs.events.UiPrefsChangedEvent;
+import org.rstudio.studio.client.workbench.prefs.events.UiPrefsChangedHandler;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildCompletedEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildErrorsEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildOutputEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.events.BuildStartedEvent;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildServerOperations;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildState;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedEvent;
+import org.rstudio.studio.client.workbench.views.console.events.WorkingDirChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.SourceBuildHelper;
+
+public class BuildPresenter extends BasePresenter
+{
+ public interface Display extends WorkbenchView
+ {
+ void buildStarted();
+
+ void showOutput(CompileOutput output);
+ void scrollToBottom();
+
+ void showErrors(String basePath,
+ JsArray<CompileError> errors,
+ boolean ensureVisible,
+ int autoSelect);
+ void buildCompleted();
+
+ HasSelectionCommitHandlers<CodeNavigationTarget> errorList();
+
+ HasClickHandlers stopButton();
+ }
+
+ @Inject
+ public BuildPresenter(Display display,
+ GlobalDisplay globalDisplay,
+ UIPrefs uiPrefs,
+ BuildServerOperations server,
+ final Commands commands,
+ EventBus eventBus,
+ FileTypeRegistry fileTypeRegistry,
+ SourceBuildHelper sourceBuildHelper)
+ {
+ super(display);
+ view_ = display;
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ uiPrefs_ = uiPrefs;
+ eventBus_ = eventBus;
+ commands_ = commands;
+ fileTypeRegistry_ = fileTypeRegistry;
+ sourceBuildHelper_ = sourceBuildHelper;
+
+ eventBus.addHandler(BuildStartedEvent.TYPE,
+ new BuildStartedEvent.Handler()
+ {
+ @Override
+ public void onBuildStarted(BuildStartedEvent event)
+ {
+ commands.stopBuild().setEnabled(true);
+
+ view_.bringToFront();
+ view_.buildStarted();
+
+ }
+ });
+
+ eventBus.addHandler(BuildOutputEvent.TYPE,
+ new BuildOutputEvent.Handler()
+ {
+ @Override
+ public void onBuildOutput(BuildOutputEvent event)
+ {
+ view_.showOutput(event.getOutput());
+ }
+ });
+
+ eventBus.addHandler(BuildErrorsEvent.TYPE,
+ new BuildErrorsEvent.Handler()
+ {
+ @Override
+ public void onBuildErrors(BuildErrorsEvent event)
+ {
+ view_.showErrors(event.getBaseDirectory(),
+ event.getErrors(),
+ true,
+ uiPrefs_.navigateToBuildError().getValue() ?
+ CompileErrorList.AUTO_SELECT_FIRST_ERROR :
+ CompileErrorList.AUTO_SELECT_NONE);
+
+ if (uiPrefs_.navigateToBuildError().getValue())
+ {
+ CompileError error = CompileError.getFirstError(event.getErrors());
+ if (error != null)
+ {
+ fileTypeRegistry_.editFile(
+ FileSystemItem.createFile(error.getPath()),
+ FilePosition.create(error.getLine(), error.getColumn()),
+ true);
+ }
+ }
+ }
+ });
+
+ eventBus.addHandler(BuildCompletedEvent.TYPE,
+ new BuildCompletedEvent.Handler()
+ {
+ @Override
+ public void onBuildCompleted(BuildCompletedEvent event)
+ {
+ commands.stopBuild().setEnabled(false);
+
+ view_.bringToFront();
+ view_.buildCompleted();
+ if (event.getRestartR())
+ {
+ eventBus_.fireEvent(
+ new SuspendAndRestartEvent(event.getAfterRestartCommand()));
+ }
+ }
+ });
+
+ // invalidate devtools load all path whenever the project ui prefs
+ // or working directory changes
+ eventBus.addHandler(UiPrefsChangedEvent.TYPE, new UiPrefsChangedHandler()
+ {
+ @Override
+ public void onUiPrefsChanged(UiPrefsChangedEvent e)
+ {
+ if (e.getType().equals(UiPrefsChangedEvent.PROJECT_TYPE))
+ devtoolsLoadAllPath_ = null;
+ }
+ });
+ eventBus.addHandler(WorkingDirChangedEvent.TYPE,
+ new WorkingDirChangedHandler() {
+ @Override
+ public void onWorkingDirChanged(WorkingDirChangedEvent event)
+ {
+ devtoolsLoadAllPath_ = null;
+ }
+ });
+
+ view_.errorList().addSelectionCommitHandler(
+ new SelectionCommitHandler<CodeNavigationTarget>() {
+ @Override
+ public void onSelectionCommit(
+ SelectionCommitEvent<CodeNavigationTarget> event)
+ {
+ CodeNavigationTarget target = event.getSelectedItem();
+ FileSystemItem fsi = FileSystemItem.createFile(target.getFile());
+ fileTypeRegistry_.editFile(fsi, target.getPosition());
+ }
+ });
+
+ view_.stopButton().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ commands.stopBuild().execute();
+ }
+ });
+ }
+
+ public void initialize(BuildState buildState)
+ {
+ view_.buildStarted();
+
+ JsArray<CompileOutput> outputs = buildState.getOutputs();
+ for (int i = 0; i<outputs.length(); i++)
+ view_.showOutput(outputs.get(i));
+
+ if (buildState.getErrors().length() > 0)
+ view_.showErrors(buildState.getErrorsBaseDir(),
+ buildState.getErrors(),
+ false,
+ CompileErrorList.AUTO_SELECT_NONE);
+
+ if (!buildState.isRunning())
+ view_.buildCompleted();
+ else
+ commands_.stopBuild().setEnabled(true);
+
+
+ }
+
+ private void sendLoadCommandToConsole(String loadCommand)
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(loadCommand, true, true));
+ }
+
+
+ void onBuildAll()
+ {
+ startBuild("build-all");
+ }
+
+ void onDevtoolsLoadAll()
+ {
+ sourceBuildHelper_.withSaveFilesBeforeCommand(new Command() {
+ @Override
+ public void execute()
+ {
+ withDevtoolsLoadAllPath(new CommandWithArg<String>() {
+ @Override
+ public void execute(String loadAllPath)
+ {
+ sendLoadCommandToConsole(
+ "devtools::load_all(\"" + loadAllPath + "\")");
+ }
+ });
+ }
+ }, "Build");
+ }
+
+ void onBuildSourcePackage()
+ {
+ startBuild("build-source-package");
+ }
+
+ void onBuildBinaryPackage()
+ {
+ startBuild("build-binary-package");
+ }
+
+ void onRoxygenizePackage()
+ {
+ startBuild("roxygenize-package");
+ }
+
+ void onCheckPackage()
+ {
+ startBuild("check-package");
+ }
+
+ void onTestPackage()
+ {
+ startBuild("test-package");
+ }
+
+ void onRebuildAll()
+ {
+ startBuild("rebuild-all");
+ }
+
+ void onCleanAll()
+ {
+ startBuild("clean-all");
+ }
+
+
+ private void startBuild(final String type)
+ {
+ // attempt to start a build (this will be a silent no-op if there
+ // is already a build running)
+ sourceBuildHelper_.withSaveFilesBeforeCommand(new Command() {
+ @Override
+ public void execute()
+ {
+ server_.startBuild(type, new SimpleRequestCallback<Boolean>() {
+ @Override
+ public void onResponseReceived(Boolean response)
+ {
+
+ }
+ });
+ }
+ }, "Build");
+ }
+
+ void onStopBuild()
+ {
+ server_.terminateBuild(new DelayedProgressRequestCallback<Boolean>(
+ "Terminating Build..."){
+ @Override
+ protected void onSuccess(Boolean response)
+ {
+ if (!response)
+ {
+ globalDisplay_.showErrorMessage(
+ "Error Terminating Build",
+ "Unable to terminate build. Please try again.");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+ Scheduler.get().scheduleDeferred(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ view_.scrollToBottom();
+ }
+ });
+ }
+
+ private void withDevtoolsLoadAllPath(
+ final CommandWithArg<String> onAvailable)
+ {
+ if (devtoolsLoadAllPath_ != null)
+ {
+ onAvailable.execute(devtoolsLoadAllPath_);
+ }
+ else
+ {
+ server_.devtoolsLoadAllPath(new SimpleRequestCallback<String>() {
+ @Override
+ public void onResponseReceived(String loadAllPath)
+ {
+ devtoolsLoadAllPath_ = loadAllPath;
+ onAvailable.execute(devtoolsLoadAllPath_);
+ }
+ });
+ }
+
+ }
+
+ private String devtoolsLoadAllPath_ = null;
+
+ private final GlobalDisplay globalDisplay_;
+ private final UIPrefs uiPrefs_;
+ private final BuildServerOperations server_;
+ private final Display view_ ;
+ private final EventBus eventBus_;
+ private final Commands commands_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final SourceBuildHelper sourceBuildHelper_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildTab.java
new file mode 100644
index 0000000..e38535f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/BuildTab.java
@@ -0,0 +1,161 @@
+/*
+ * BuildTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildState;
+import org.rstudio.studio.client.workbench.views.buildtools.ui.BuildPaneResources;
+
+
+public class BuildTab extends DelayLoadWorkbenchTab<BuildPresenter>
+{
+ public interface Binder extends CommandBinder<Commands, Shim> {}
+
+ public abstract static class Shim extends DelayLoadTabShim<BuildPresenter, BuildTab>
+ {
+ @Handler
+ public abstract void onBuildAll();
+ @Handler
+ public abstract void onDevtoolsLoadAll();
+ @Handler
+ public abstract void onRebuildAll();
+ @Handler
+ public abstract void onCleanAll();
+ @Handler
+ public abstract void onBuildSourcePackage();
+ @Handler
+ public abstract void onBuildBinaryPackage();
+ @Handler
+ public abstract void onRoxygenizePackage();
+ @Handler
+ public abstract void onStopBuild();
+ @Handler
+ public abstract void onCheckPackage();
+ @Handler
+ public abstract void onTestPackage();
+
+ abstract void initialize(BuildState buildState);
+ }
+
+ @Inject
+ public BuildTab(final Shim shim,
+ final Session session,
+ Binder binder,
+ final Commands commands,
+ EventBus eventBus,
+ UIPrefs uiPrefs)
+ {
+ super("Build", shim);
+ session_ = session;
+ binder.bind(commands, shim);
+
+ // stop build always starts out disabled
+ commands.stopBuild().setEnabled(false);
+
+ // manage roxygen command
+ commands.roxygenizePackage().setVisible(uiPrefs.useRoxygen().getValue());
+ uiPrefs.useRoxygen().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ commands.roxygenizePackage().setVisible(event.getValue());
+ }
+ });
+
+ eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ SessionInfo sessionInfo = session.getSessionInfo();
+
+ // remove devtools commands if it isn't installed
+ if (!sessionInfo.isDevtoolsInstalled())
+ {
+ commands.devtoolsLoadAll().remove();
+ commands.checkPackage().remove();
+ }
+
+ // adapt or remove package commands if this isn't a package
+ String type = sessionInfo.getBuildToolsType();
+ if (!type.equals(SessionInfo.BUILD_TOOLS_PACKAGE))
+ {
+ commands.devtoolsLoadAll().remove();
+ commands.buildSourcePackage().remove();
+ commands.buildBinaryPackage().remove();
+ commands.roxygenizePackage().remove();
+ commands.checkPackage().remove();
+ commands.testPackage().remove();
+ commands.buildAll().setImageResource(
+ BuildPaneResources.INSTANCE.iconBuild());
+ commands.buildAll().setMenuLabel("_Build All");
+ commands.buildAll().setButtonLabel("Build All");
+ commands.buildAll().setDesc("Build all");
+
+ }
+
+ // remove makefile commands if this isn't a makefile
+ if (type.equals(SessionInfo.BUILD_TOOLS_CUSTOM))
+ {
+ commands.rebuildAll().remove();
+ }
+
+ if (!type.equals(SessionInfo.BUILD_TOOLS_MAKEFILE))
+ {
+ commands.cleanAll().remove();
+ }
+
+ // remove all other commands if there are no build tools
+ if (type.equals(SessionInfo.BUILD_TOOLS_NONE))
+ {
+ commands.buildAll().remove();
+ commands.rebuildAll().remove();
+ commands.cleanAll().remove();
+ commands.stopBuild().remove();
+ commands.activateBuild().remove();
+ }
+
+ // initialize from build state if necessary
+ BuildState buildState = sessionInfo.getBuildState();
+ if (buildState != null)
+ shim.initialize(buildState);
+ }
+ });
+
+
+ }
+
+ @Override
+ public boolean isSuppressed()
+ {
+ return session_.getSessionInfo().getBuildToolsType().equals(
+ SessionInfo.BUILD_TOOLS_NONE);
+ }
+
+ private Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildCompletedEvent.java
new file mode 100644
index 0000000..cbc51b1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildCompletedEvent.java
@@ -0,0 +1,75 @@
+/*
+ * BuildCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools.events;
+
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BuildCompletedEvent extends GwtEvent<BuildCompletedEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+
+ }
+
+ public native final boolean getRestartR() /*-{
+ return this.restart_r;
+ }-*/;
+
+ public native final String getAfterRestartCommand() /*-{
+ return this.after_restart_command;
+ }-*/;
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onBuildCompleted(BuildCompletedEvent event);
+ }
+
+ public BuildCompletedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public boolean getRestartR()
+ {
+ return data_.getRestartR();
+ }
+
+ public String getAfterRestartCommand()
+ {
+ return data_.getAfterRestartCommand();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBuildCompleted(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildErrorsEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildErrorsEvent.java
new file mode 100644
index 0000000..5b21851
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildErrorsEvent.java
@@ -0,0 +1,77 @@
+/*
+ * BuildErrorsEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools.events;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BuildErrorsEvent extends GwtEvent<BuildErrorsEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public final native String getBaseDirectory() /*-{
+ return this.base_dir;
+ }-*/;
+
+ public final native JsArray<CompileError> getErrors() /*-{
+ return this.errors;
+ }-*/;
+ }
+
+
+ public interface Handler extends EventHandler
+ {
+ void onBuildErrors(BuildErrorsEvent event);
+ }
+
+ public BuildErrorsEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getBaseDirectory()
+ {
+ return data_.getBaseDirectory();
+ }
+
+ public JsArray<CompileError> getErrors()
+ {
+ return data_.getErrors();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBuildErrors(this);
+ }
+
+ private Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildOutputEvent.java
new file mode 100644
index 0000000..b6bec57
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildOutputEvent.java
@@ -0,0 +1,54 @@
+/*
+ * BuildOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools.events;
+
+import org.rstudio.studio.client.common.compile.CompileOutput;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BuildOutputEvent extends GwtEvent<BuildOutputEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onBuildOutput(BuildOutputEvent event);
+ }
+
+ public BuildOutputEvent(CompileOutput output)
+ {
+ output_ = output;
+ }
+
+ public CompileOutput getOutput()
+ {
+ return output_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBuildOutput(this);
+ }
+
+ private final CompileOutput output_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildStartedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildStartedEvent.java
new file mode 100644
index 0000000..09f6949
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/events/BuildStartedEvent.java
@@ -0,0 +1,44 @@
+/*
+ * BuildStartedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BuildStartedEvent extends GwtEvent<BuildStartedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onBuildStarted(BuildStartedEvent event);
+ }
+
+ public BuildStartedEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBuildStarted(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildRestartContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildRestartContext.java
new file mode 100644
index 0000000..f14acbb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildRestartContext.java
@@ -0,0 +1,33 @@
+/*
+ * BuildRestartContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.buildtools.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class BuildRestartContext extends JavaScriptObject
+{
+ protected BuildRestartContext()
+ {
+ }
+
+ public final native String getPackageName() /*-{
+ return this.package_name;
+ }-*/;
+
+ public final native String getBuildOutput() /*-{
+ return this.build_output;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildServerOperations.java
new file mode 100644
index 0000000..fe2cc83
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildServerOperations.java
@@ -0,0 +1,39 @@
+/*
+ * BuildServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.buildtools.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.source.model.CppCapabilities;
+
+public interface BuildServerOperations
+{
+ // check if we can build C/C++ code
+ void getCppCapabilities(
+ ServerRequestCallback<CppCapabilities> requestCallback);
+
+ // returns true to indicate that the build has started, returns false
+ // to indicate that the build could not be started because another
+ // build is currently in progress
+ void startBuild(String type,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ // terminate any running build
+ void terminateBuild(ServerRequestCallback<Boolean> requestCallback);
+
+
+ // get the devtools::load_all path
+ void devtoolsLoadAllPath(ServerRequestCallback<String> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildState.java
new file mode 100644
index 0000000..fbde9ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/model/BuildState.java
@@ -0,0 +1,45 @@
+/*
+ * BuildState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.buildtools.model;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class BuildState extends JavaScriptObject
+{
+ protected BuildState()
+ {
+ }
+
+ public final native boolean isRunning() /*-{
+ return this.running;
+ }-*/;
+
+ public final native String getErrorsBaseDir() /*-{
+ return this.errors_base_dir;
+ }-*/;
+
+ public final native JsArray<CompileError> getErrors() /*-{
+ return this.errors;
+ }-*/;
+
+ public final native JsArray<CompileOutput> getOutputs() /*-{
+ return this.outputs;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/BuildPane.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/BuildPane.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/BuildPaneResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/BuildPaneResources.java
new file mode 100644
index 0000000..ee664e7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/BuildPaneResources.java
@@ -0,0 +1,39 @@
+/*
+ * BuildPaneResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.buildtools.ui;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface BuildPaneResources extends ClientBundle
+{
+ public static interface Styles extends CssResource
+ {
+
+ }
+
+
+ @Source("BuildPane.css")
+ Styles styles();
+
+ ImageResource iconBuild();
+
+ public static BuildPaneResources INSTANCE =
+ (BuildPaneResources)GWT.create(BuildPaneResources.class) ;
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/iconBuild.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/iconBuild.png
new file mode 100644
index 0000000..51a5b8a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/buildtools/ui/iconBuild.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/ChooseFile.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/ChooseFile.java
new file mode 100644
index 0000000..9842476
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/ChooseFile.java
@@ -0,0 +1,87 @@
+/*
+ * ChooseFile.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.choosefile;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.views.choosefile.events.ChooseFileEvent;
+import org.rstudio.studio.client.workbench.views.choosefile.events.ChooseFileHandler;
+import org.rstudio.studio.client.workbench.views.choosefile.model.ChooseFileServerOperations;
+
+ at Singleton
+public class ChooseFile implements ChooseFileHandler
+{
+ @Inject
+ public ChooseFile(EventBus events,
+ ChooseFileServerOperations server,
+ RemoteFileSystemContext fsContext,
+ WorkbenchContext workbenchContext,
+ FileDialogs fileDialogs)
+ {
+ server_ = server;
+ fsContext_ = fsContext;
+ workbenchContext_ = workbenchContext;
+ fileDialogs_ = fileDialogs;
+
+ events.addHandler(ChooseFileEvent.TYPE, this);
+
+ }
+
+ public void onChooseFile(ChooseFileEvent event)
+ {
+ fileDialogs_.openFile(
+ "Choose File",
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator progress)
+ {
+ String message, path;
+ if (input != null)
+ {
+ message = "Saving...";
+ path = input.getPath();
+ }
+ else
+ {
+ message = "Cancelling...";
+ path = null;
+ }
+
+ progress.onProgress(message);
+ server_.chooseFileCompleted(
+ path,
+ new VoidServerRequestCallback(
+ progress));
+ }
+ });
+ }
+
+ private final ChooseFileServerOperations server_;
+ private final RemoteFileSystemContext fsContext_;
+ private final WorkbenchContext workbenchContext_;
+ private final FileDialogs fileDialogs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileEvent.java
new file mode 100644
index 0000000..aefae72
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileEvent.java
@@ -0,0 +1,47 @@
+/*
+ * ChooseFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.choosefile.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ChooseFileEvent extends GwtEvent<ChooseFileHandler>
+{
+ public static final GwtEvent.Type<ChooseFileHandler> TYPE =
+ new GwtEvent.Type<ChooseFileHandler>();
+
+ public ChooseFileEvent(boolean newFile)
+ {
+ newFile_ = newFile;
+ }
+
+ public boolean getNewFile()
+ {
+ return newFile_;
+ }
+
+ @Override
+ protected void dispatch(ChooseFileHandler handler)
+ {
+ handler.onChooseFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ChooseFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final boolean newFile_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileHandler.java
new file mode 100644
index 0000000..d364083
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/events/ChooseFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ChooseFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.choosefile.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ChooseFileHandler extends EventHandler
+{
+ void onChooseFile(ChooseFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/model/ChooseFileServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/model/ChooseFileServerOperations.java
new file mode 100644
index 0000000..c5c6296
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/choosefile/model/ChooseFileServerOperations.java
@@ -0,0 +1,25 @@
+/*
+ * ChooseFileServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.choosefile.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+public interface ChooseFileServerOperations extends FilesServerOperations
+{
+ void chooseFileCompleted(String file,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/Console.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/Console.java
new file mode 100644
index 0000000..4c3954a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/Console.java
@@ -0,0 +1,111 @@
+/*
+ * Console.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.layout.DelayFadeInHelper;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.BusyEvent;
+import org.rstudio.studio.client.workbench.events.BusyHandler;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleHandler;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;
+
+public class Console
+{
+ interface Binder extends CommandBinder<Commands, Console> {}
+
+ public interface Display
+ {
+ void bringToFront();
+ void focus();
+ void ensureCursorVisible();
+ IsWidget getConsoleInterruptButton();
+ void setDebugMode(boolean debugMode);
+ }
+
+ @Inject
+ public Console(final Display view, EventBus events, Commands commands)
+ {
+ view_ = view;
+
+ events.addHandler(SendToConsoleEvent.TYPE, new SendToConsoleHandler()
+ {
+ public void onSendToConsole(SendToConsoleEvent event)
+ {
+ view.bringToFront();
+ }
+ });
+
+ ((Binder) GWT.create(Binder.class)).bind(commands, this);
+
+ fadeInHelper_ = new DelayFadeInHelper(
+ view_.getConsoleInterruptButton().asWidget());
+ events.addHandler(BusyEvent.TYPE, new BusyHandler()
+ {
+ @Override
+ public void onBusy(BusyEvent event)
+ {
+ if (event.isBusy())
+ fadeInHelper_.beginShow();
+ }
+ });
+
+ events.addHandler(ConsolePromptEvent.TYPE, new ConsolePromptHandler()
+ {
+ @Override
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ fadeInHelper_.hide();
+ }
+ });
+
+ events.addHandler(DebugModeChangedEvent.TYPE,
+ new DebugModeChangedEvent.Handler()
+ {
+ @Override
+ public void onDebugModeChanged(DebugModeChangedEvent event)
+ {
+ view.setDebugMode(event.debugging());
+ }
+ });
+ }
+
+ @Handler
+ void onActivateConsole()
+ {
+ WindowEx.get().focus();
+ view_.bringToFront();
+ view_.focus();
+ view_.ensureCursorVisible();
+ }
+
+ public Display getDisplay()
+ {
+ return view_ ;
+ }
+
+ private final DelayFadeInHelper fadeInHelper_;
+ private final Display view_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleInterruptButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleInterruptButton.java
new file mode 100644
index 0000000..a3e3632
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleInterruptButton.java
@@ -0,0 +1,103 @@
+/*
+ * ConsoleInterruptButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console;
+
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.layout.DelayFadeInHelper;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.BusyEvent;
+import org.rstudio.studio.client.workbench.events.BusyHandler;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleBusyEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
+
+public class ConsoleInterruptButton extends Composite
+{
+ @Inject
+ public ConsoleInterruptButton(final EventBus events,
+ Commands commands)
+ {
+ fadeInHelper_ = new DelayFadeInHelper(this);
+
+ // The SimplePanel wrapper is necessary for the toolbar button's "pushed"
+ // effect to work.
+ SimplePanel panel = new SimplePanel();
+ panel.getElement().getStyle().setPosition(Position.RELATIVE);
+
+ commands_ = commands;
+ ImageResource icon = commands_.interruptR().getImageResource();
+ ToolbarButton button = new ToolbarButton(icon,
+ commands.interruptR());
+ width_ = icon.getWidth() + 6;
+ height_ = icon.getHeight();
+ panel.setWidget(button);
+
+ initWidget(panel);
+ setVisible(false);
+
+ events.addHandler(BusyEvent.TYPE, new BusyHandler()
+ {
+ public void onBusy(BusyEvent event)
+ {
+ if (event.isBusy())
+ {
+ fadeInHelper_.beginShow();
+ commands_.interruptR().setEnabled(true);
+ events.fireEvent(new ConsoleBusyEvent(true));
+ }
+ }
+ });
+
+ /*
+ JJ says:
+ It is possible that the client could miss the busy = false event (if the
+ client goes out of network coverage and then the server suspends before
+ it can come back into coverage). For this reason I think that the icon's
+ controller logic should subscribe to the ConsolePromptEvent and clear it
+ whenenver a prompt occurs.
+ */
+ events.addHandler(ConsolePromptEvent.TYPE, new ConsolePromptHandler()
+ {
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ fadeInHelper_.hide();
+ commands_.interruptR().setEnabled(false);
+ events.fireEvent(new ConsoleBusyEvent(false));
+ }
+ });
+ }
+
+ public int getWidth()
+ {
+ return width_;
+ }
+
+ public int getHeight()
+ {
+ return height_;
+ }
+
+ private final DelayFadeInHelper fadeInHelper_;
+ private final int width_;
+ private final int height_;
+ private final Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsolePane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsolePane.java
new file mode 100644
index 0000000..004c005
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsolePane.java
@@ -0,0 +1,172 @@
+/*
+ * ConsolePane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.SecondaryToolbar;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.console.shell.Shell;
+
+public class ConsolePane extends WorkbenchPane
+ implements Console.Display, CanFocus
+{
+ @Inject
+ public ConsolePane(Provider<Shell> consoleProvider,
+ final EventBus events,
+ Commands commands,
+ Session session)
+ {
+ super("Console");
+
+ consoleProvider_ = consoleProvider ;
+ commands_ = commands;
+ session_ = session;
+ debugMode_ = false;
+
+ // console is interacted with immediately so we make sure it
+ // is always created during startup
+ ensureWidget();
+
+ new Console(this, events, commands);
+ }
+
+ public void setWorkingDirectory(String directory)
+ {
+ workingDir_.setText(directory);
+ }
+
+ public void focus()
+ {
+ shell_.getDisplay().focus();
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ shell_.getDisplay().ensureInputVisible();
+ }
+
+ @Override
+ public IsWidget getConsoleInterruptButton()
+ {
+ return consoleInterruptButton_;
+ }
+
+ public int getCharacterWidth()
+ {
+ return shell_.getDisplay().getCharacterWidth();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+ workingDir_ = new Label();
+ workingDir_.setStyleName(ThemeStyles.INSTANCE.subtitle());
+ toolbar.addLeftWidget(workingDir_);
+ toolbar.addLeftWidget(commands_.goToWorkingDir().createToolbarButton());
+ consoleInterruptButton_ = commands_.interruptR().createToolbarButton();
+ toolbar.addRightWidget(consoleInterruptButton_);
+ return toolbar;
+ }
+
+ @Override
+ protected SecondaryToolbar createSecondaryToolbar()
+ {
+ SecondaryToolbar toolbar = new SecondaryToolbar(true);
+ toolbar.addLeftWidget(commands_.debugStep().createToolbarButton());
+ if (session_.getSessionInfo().getHaveAdvancedStepCommands())
+ {
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.debugStepInto().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.debugFinish().createToolbarButton());
+ }
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.debugContinue().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.debugStop().createToolbarButton());
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // initialize the debug toolbar--generally this hides it until debug state
+ // is entered.
+ setDebugMode(debugMode_);
+
+ shell_ = consoleProvider_.get() ;
+ return (Widget) shell_.getDisplay() ;
+ }
+
+ @Override
+ public void onBeforeUnselected()
+ {
+ shell_.onBeforeUnselected();
+ }
+
+ @Override
+ public void onBeforeSelected()
+ {
+ shell_.onBeforeSelected();
+ }
+
+ @Override
+ public void onSelected()
+ {
+ shell_.onSelected();
+ }
+
+ @Override
+ public void setDebugMode(boolean debugMode)
+ {
+ debugMode_ = debugMode;
+ setSecondaryToolbarVisible(debugMode_);
+
+ if (debugMode_)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ if (shell_ != null)
+ shell_.getDisplay().ensureInputVisible();
+ }
+ });
+ }
+ }
+
+ private Provider<Shell> consoleProvider_ ;
+ private final Commands commands_;
+ private Shell shell_;
+ private Session session_;
+ private Label workingDir_;
+ private ToolbarButton consoleInterruptButton_;
+ private boolean debugMode_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleResources.java
new file mode 100644
index 0000000..cca41e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/ConsoleResources.java
@@ -0,0 +1,48 @@
+/*
+ * ConsoleResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+
+public interface ConsoleResources extends ClientBundle
+{
+ public static final ConsoleResources INSTANCE = GWT.create(ConsoleResources.class);
+
+ @CssResource.NotStrict
+ ConsoleStyles consoleStyles();
+
+ public interface ConsoleStyles extends CssResource
+ {
+ String console();
+ String input();
+ String prompt();
+ String output();
+ String command();
+ String completionPopup();
+ String completionGrid();
+ String functionInfo();
+ String functionInfoSignature();
+ String functionInfoSummary();
+ String paramInfoName();
+ String paramInfoDesc();
+ String promptFullHelp();
+ String error();
+ String selected();
+ }
+
+ public static final String KEYWORD_CLASS_NAME = " ace_keyword";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/consoleStyles.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/consoleStyles.css
new file mode 100644
index 0000000..2ae32e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/consoleStyles.css
@@ -0,0 +1,117 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external popupContent;
+ at external ace_sb, ace_cursor, ace_hidden;
+
+.console {
+ font-family: fixedWidthFont;
+ padding-left: 6px;
+ padding-bottom: 8px;
+ line-height: 16px;
+ border: none;
+ outline: none;
+ word-wrap: break-word;
+ white-space: pre-wrap !important;
+ cursor: text;
+}
+.console td {
+ font-family: fixedWidthFont;
+}
+
+.output {
+ outline: none;
+ border: none;
+ white-space: pre-wrap !important;
+ word-break: break-all;
+ margin: 0;
+ -webkit-user-select: text;
+}
+.output .command, .input {
+}
+
+.error {}
+
+.input.ace_editor {
+ position: relative !important;
+}
+.input .ace_sb {
+ display: none;
+ width: 0 !important;
+}
+
+.console .prompt {
+ white-space: pre;
+ -webkit-user-select: text;
+}
+
+.completionPopup {
+ padding: 0;
+ margin-left: -8px !important;
+ margin-top: 4px !important;
+ z-index: 1002;
+}
+.completionPopup .popupContent {
+ margin: -4px;
+}
+
+.completionGrid {
+ cursor: default;
+ padding: 0;
+}
+
+.completionGrid td {
+ font-family: fixedWidthFont;
+ font-size: 12px;
+ padding-left: 3px;
+ padding-right: 3px;
+ margin: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.completionGrid td span.packageName {
+ color: #999;
+}
+
+.selected {
+ background-color: rgb(146, 193, 240);
+}
+
+.functionInfo, .paramInfoName {
+ background-color: #fff;
+ padding: 2px 2px 2px 3px;
+
+}
+
+.paramInfoName {
+ font-weight: bold;
+}
+
+.functionInfoSignature {
+ font-family: fixedWidthFont;
+ font-size: 12px;
+}
+
+.functionInfoSignature,
+.paramInfoDesc {
+ padding-left: 2em;
+ text-indent: -2em;
+ padding-bottom: 3px;
+}
+
+.functionInfoSummary {
+ color: black;
+}
+
+.functionInfoSummary>a {
+ text-decoration: none;
+ color: black;
+}
+
+.promptFullHelp {
+ height: 16px;
+ background: #f0f0f4;
+ font-size: 9px;
+ padding: 3px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleBusyEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleBusyEvent.java
new file mode 100644
index 0000000..ddfc42f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleBusyEvent.java
@@ -0,0 +1,54 @@
+/*
+ * ConsoleBusyEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleBusyEvent
+ extends GwtEvent<ConsoleBusyEvent.Handler>
+{
+ public static final GwtEvent.Type<ConsoleBusyEvent.Handler> TYPE =
+ new GwtEvent.Type<ConsoleBusyEvent.Handler>();
+
+ public interface Handler extends EventHandler
+ {
+ void onConsoleBusy(ConsoleBusyEvent event);
+ }
+
+ public ConsoleBusyEvent(boolean busy)
+ {
+ busy_ = busy;
+ }
+
+ public boolean isBusy()
+ {
+ return busy_;
+ }
+
+ @Override
+ public Type<ConsoleBusyEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onConsoleBusy(this);
+ }
+
+ private boolean busy_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleExecutePendingInputEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleExecutePendingInputEvent.java
new file mode 100644
index 0000000..06f25be
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleExecutePendingInputEvent.java
@@ -0,0 +1,44 @@
+/*
+ * ConsoleExecutePendingInputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleExecutePendingInputEvent extends GwtEvent<ConsoleExecutePendingInputEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onExecutePendingInput(ConsoleExecutePendingInputEvent event);
+ }
+
+ public ConsoleExecutePendingInputEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onExecutePendingInput(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputEvent.java
new file mode 100644
index 0000000..29757ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputEvent.java
@@ -0,0 +1,47 @@
+/*
+ * ConsoleInputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleInputEvent extends GwtEvent<ConsoleInputHandler>
+{
+ public static final GwtEvent.Type<ConsoleInputHandler> TYPE =
+ new GwtEvent.Type<ConsoleInputHandler>();
+
+ public ConsoleInputEvent(String input)
+ {
+ input_ = input;
+ }
+
+ public String getInput()
+ {
+ return input_;
+ }
+
+ @Override
+ protected void dispatch(ConsoleInputHandler handler)
+ {
+ handler.onConsoleInput(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ConsoleInputHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String input_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputHandler.java
new file mode 100644
index 0000000..e477162
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleInputHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleInputHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleInputHandler extends EventHandler
+{
+ void onConsoleInput(ConsoleInputEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptEvent.java
new file mode 100644
index 0000000..8ae25b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ConsolePromptEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.console.model.ConsolePrompt;
+
+public class ConsolePromptEvent extends GwtEvent<ConsolePromptHandler>
+{
+ public static final GwtEvent.Type<ConsolePromptHandler> TYPE =
+ new GwtEvent.Type<ConsolePromptHandler>();
+
+ public ConsolePromptEvent(ConsolePrompt prompt)
+ {
+ prompt_ = prompt;
+ }
+
+ public ConsolePrompt getPrompt()
+ {
+ return prompt_;
+ }
+
+ @Override
+ protected void dispatch(ConsolePromptHandler handler)
+ {
+ handler.onConsolePrompt(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ConsolePromptHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final ConsolePrompt prompt_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptHandler.java
new file mode 100644
index 0000000..23a89e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsolePromptHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsolePromptHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsolePromptHandler extends EventHandler
+{
+ void onConsolePrompt(ConsolePromptEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryEvent.java
new file mode 100644
index 0000000..8105f51
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryEvent.java
@@ -0,0 +1,55 @@
+/*
+ * ConsoleResetHistoryEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleResetHistory;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleResetHistoryEvent extends GwtEvent<ConsoleResetHistoryHandler>
+{
+ public static final GwtEvent.Type<ConsoleResetHistoryHandler> TYPE =
+ new GwtEvent.Type<ConsoleResetHistoryHandler>();
+
+ public ConsoleResetHistoryEvent(ConsoleResetHistory reset)
+ {
+ reset_ = reset;
+ }
+
+ public JsArrayString getHistory()
+ {
+ return reset_.getHistory();
+ }
+
+ public boolean getPreserveUIContext()
+ {
+ return reset_.getPreserveUIContext();
+ }
+
+ @Override
+ protected void dispatch(ConsoleResetHistoryHandler handler)
+ {
+ handler.onConsoleResetHistory(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ConsoleResetHistoryHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final ConsoleResetHistory reset_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryHandler.java
new file mode 100644
index 0000000..73685c7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleResetHistoryHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleResetHistoryHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleResetHistoryHandler extends EventHandler
+{
+ void onConsoleResetHistory(ConsoleResetHistoryEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleRestartRCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleRestartRCompletedEvent.java
new file mode 100644
index 0000000..a150aa0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleRestartRCompletedEvent.java
@@ -0,0 +1,44 @@
+/*
+ * ConsoleRestartRCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleRestartRCompletedEvent extends GwtEvent<ConsoleRestartRCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onRestartRCompleted(ConsoleRestartRCompletedEvent event);
+ }
+
+ public ConsoleRestartRCompletedEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onRestartRCompleted(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorEvent.java
new file mode 100644
index 0000000..ff2150c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorEvent.java
@@ -0,0 +1,47 @@
+/*
+ * ConsoleWriteErrorEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleWriteErrorEvent extends GwtEvent<ConsoleWriteErrorHandler>
+{
+ public static final GwtEvent.Type<ConsoleWriteErrorHandler> TYPE =
+ new GwtEvent.Type<ConsoleWriteErrorHandler>();
+
+ public ConsoleWriteErrorEvent(String error)
+ {
+ error_ = error;
+ }
+
+ public String getError()
+ {
+ return error_;
+ }
+
+ @Override
+ protected void dispatch(ConsoleWriteErrorHandler handler)
+ {
+ handler.onConsoleWriteError(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ConsoleWriteErrorHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String error_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorHandler.java
new file mode 100644
index 0000000..c133fcb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteErrorHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleWriteErrorHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleWriteErrorHandler extends EventHandler
+{
+ void onConsoleWriteError(ConsoleWriteErrorEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputEvent.java
new file mode 100644
index 0000000..2fea6eb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputEvent.java
@@ -0,0 +1,46 @@
+/*
+ * ConsoleWriteInputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleWriteInputEvent extends GwtEvent<ConsoleWriteInputHandler>
+{
+ public static final Type<ConsoleWriteInputHandler> TYPE = new Type<ConsoleWriteInputHandler>();
+
+ public ConsoleWriteInputEvent(String input)
+ {
+ this.input_ = input;
+ }
+
+ public String getInput()
+ {
+ return input_;
+ }
+
+ private String input_;
+
+ @Override
+ public Type<ConsoleWriteInputHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ConsoleWriteInputHandler handler)
+ {
+ handler.onConsoleWriteInput(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputHandler.java
new file mode 100644
index 0000000..071dadf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteInputHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleWriteInputHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleWriteInputHandler extends EventHandler
+{
+ void onConsoleWriteInput(ConsoleWriteInputEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputEvent.java
new file mode 100644
index 0000000..714881d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputEvent.java
@@ -0,0 +1,48 @@
+/*
+ * ConsoleWriteOutputEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleWriteOutputEvent extends GwtEvent<ConsoleWriteOutputHandler>
+{
+ public static final GwtEvent.Type<ConsoleWriteOutputHandler> TYPE =
+ new GwtEvent.Type<ConsoleWriteOutputHandler>();
+
+ public ConsoleWriteOutputEvent(String output)
+ {
+ output_ = output;
+ }
+
+ public String getOutput()
+ {
+ return output_;
+ }
+
+ @Override
+ protected void dispatch(ConsoleWriteOutputHandler handler)
+ {
+ handler.onConsoleWriteOutput(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ConsoleWriteOutputHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String output_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputHandler.java
new file mode 100644
index 0000000..4e5bef8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWriteOutputHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleWriteOutputHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleWriteOutputHandler extends EventHandler
+{
+ void onConsoleWriteOutput(ConsoleWriteOutputEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptEvent.java
new file mode 100644
index 0000000..ec8815e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptEvent.java
@@ -0,0 +1,46 @@
+/*
+ * ConsoleWritePromptEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ConsoleWritePromptEvent extends GwtEvent<ConsoleWritePromptHandler>
+{
+ public static final Type<ConsoleWritePromptHandler> TYPE = new Type<ConsoleWritePromptHandler>();
+
+ public ConsoleWritePromptEvent(String prompt)
+ {
+ prompt_ = prompt;
+ }
+
+ public String getPrompt()
+ {
+ return prompt_;
+ }
+
+ private String prompt_;
+
+ @Override
+ public Type<ConsoleWritePromptHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ConsoleWritePromptHandler handler)
+ {
+ handler.onConsoleWritePrompt(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptHandler.java
new file mode 100644
index 0000000..abd61a8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/ConsoleWritePromptHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ConsoleWritePromptHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ConsoleWritePromptHandler extends EventHandler
+{
+ void onConsoleWritePrompt(ConsoleWritePromptEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/RunCommandWithDebugEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/RunCommandWithDebugEvent.java
new file mode 100644
index 0000000..bce8e1a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/RunCommandWithDebugEvent.java
@@ -0,0 +1,54 @@
+/*
+ * RerunLastCommandEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class RunCommandWithDebugEvent
+ extends GwtEvent<RunCommandWithDebugEvent.Handler>
+{
+ public static final GwtEvent.Type<RunCommandWithDebugEvent.Handler> TYPE =
+ new GwtEvent.Type<RunCommandWithDebugEvent.Handler>();
+
+ public interface Handler extends EventHandler
+ {
+ void onRunCommandWithDebug(RunCommandWithDebugEvent event);
+ }
+
+ public RunCommandWithDebugEvent(String command)
+ {
+ command_ = command;
+ }
+
+ public String getCommand()
+ {
+ return command_;
+ }
+
+ @Override
+ public Type<RunCommandWithDebugEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onRunCommandWithDebug(this);
+ }
+
+ private String command_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleEvent.java
new file mode 100644
index 0000000..f0614ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleEvent.java
@@ -0,0 +1,81 @@
+/*
+ * SendToConsoleEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SendToConsoleEvent extends GwtEvent<SendToConsoleHandler>
+{
+ public static final GwtEvent.Type<SendToConsoleHandler> TYPE =
+ new GwtEvent.Type<SendToConsoleHandler>();
+
+ public SendToConsoleEvent(String code, boolean execute)
+ {
+ this(code, execute, false);
+ }
+
+ public SendToConsoleEvent(String code, boolean execute, boolean focus)
+ {
+ this(code, execute, focus, false);
+ }
+
+ public SendToConsoleEvent(String code,
+ boolean execute,
+ boolean focus,
+ boolean animate)
+ {
+ code_ = code;
+ execute_ = execute;
+ focus_ = focus;
+ animate_ = animate;
+ }
+
+ public String getCode()
+ {
+ return code_;
+ }
+
+ public boolean shouldExecute()
+ {
+ return execute_;
+ }
+
+ public boolean shouldFocus()
+ {
+ return focus_;
+ }
+
+ public boolean shouldAnimate()
+ {
+ return animate_;
+ }
+
+ @Override
+ public Type<SendToConsoleHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SendToConsoleHandler sendToConsoleHandler)
+ {
+ sendToConsoleHandler.onSendToConsole(this);
+ }
+
+ private final String code_;
+ private final boolean execute_;
+ private final boolean focus_;
+ private final boolean animate_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleHandler.java
new file mode 100644
index 0000000..d232a29
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/SendToConsoleHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SendToConsoleHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SendToConsoleHandler extends EventHandler
+{
+ void onSendToConsole(SendToConsoleEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedEvent.java
new file mode 100644
index 0000000..7aaee99
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedEvent.java
@@ -0,0 +1,47 @@
+/*
+ * WorkingDirChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class WorkingDirChangedEvent extends GwtEvent<WorkingDirChangedHandler>
+{
+ public static final GwtEvent.Type<WorkingDirChangedHandler> TYPE =
+ new GwtEvent.Type<WorkingDirChangedHandler>();
+
+ public WorkingDirChangedEvent(String path)
+ {
+ path_ = path;
+ }
+
+ public String getPath()
+ {
+ return path_;
+ }
+
+ @Override
+ protected void dispatch(WorkingDirChangedHandler handler)
+ {
+ handler.onWorkingDirChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<WorkingDirChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String path_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedHandler.java
new file mode 100644
index 0000000..d97cd9b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/events/WorkingDirChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * WorkingDirChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface WorkingDirChangedHandler extends EventHandler
+{
+ void onWorkingDirChanged(WorkingDirChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsolePrompt.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsolePrompt.java
new file mode 100644
index 0000000..e86adff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsolePrompt.java
@@ -0,0 +1,33 @@
+/*
+ * ConsolePrompt.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+public class ConsolePrompt extends JavaScriptObject
+{
+ protected ConsolePrompt()
+ {
+ }
+
+ public final native String getPromptText() /*-{
+ return this.prompt;
+ }-*/;
+
+ public final native boolean getAddToHistory() /*-{
+ return this.history;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleResetHistory.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleResetHistory.java
new file mode 100644
index 0000000..8654e3f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleResetHistory.java
@@ -0,0 +1,34 @@
+/*
+ * ConsoleResetHistory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+
+public class ConsoleResetHistory extends JavaScriptObject
+{
+ protected ConsoleResetHistory()
+ {
+ }
+
+ public final native JsArrayString getHistory() /*-{
+ return this.history;
+ }-*/;
+
+ public final native boolean getPreserveUIContext() /*-{
+ return this.preserve_ui_context;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleServerOperations.java
new file mode 100644
index 0000000..d8d4d81
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/model/ConsoleServerOperations.java
@@ -0,0 +1,47 @@
+/*
+ * ConsoleServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.model;
+
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.shell.ShellInput;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryServerOperations;
+
+public interface ConsoleServerOperations extends CodeToolsServerOperations,
+ HistoryServerOperations
+{
+ // interrupt the current session
+ void interrupt(ServerRequestCallback<Void> requestCallback);
+
+ // send console input
+ void consoleInput(String consoleInput,
+ ServerRequestCallback<Void> requestCallback);
+
+ void resetConsoleActions(ServerRequestCallback<Void> requestCallback);
+
+ void processStart(String handle,
+ ServerRequestCallback<Void> requestCallback);
+
+ void processInterrupt(String handle,
+ ServerRequestCallback<Void> requestCallback);
+
+ void processReap(String handle,
+ ServerRequestCallback<Void> requestCallback);
+
+ void processWriteStdin(String handle,
+ ShellInput input,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyDownPreviewHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyDownPreviewHandler.java
new file mode 100644
index 0000000..6626d07
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyDownPreviewHandler.java
@@ -0,0 +1,22 @@
+/*
+ * KeyDownPreviewHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell;
+
+import com.google.gwt.dom.client.NativeEvent;
+
+public interface KeyDownPreviewHandler
+{
+ boolean previewKeyDown(NativeEvent event) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyPressPreviewHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyPressPreviewHandler.java
new file mode 100644
index 0000000..ad992a4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/KeyPressPreviewHandler.java
@@ -0,0 +1,20 @@
+/*
+ * KeyPressPreviewHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell;
+
+public interface KeyPressPreviewHandler
+{
+ boolean previewKeyPress(char charCode) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/Shell.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/Shell.java
new file mode 100644
index 0000000..3507d44
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/Shell.java
@@ -0,0 +1,647 @@
+/*
+ * Shell.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.console.shell;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.CommandLineHistory;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.debugging.ErrorManager;
+import org.rstudio.studio.client.common.debugging.events.UnhandledErrorEvent;
+import org.rstudio.studio.client.common.debugging.model.ErrorHandlerType;
+import org.rstudio.studio.client.common.shell.ShellDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.ConsoleAction;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.model.helper.StringStateValue;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.events.*;
+import org.rstudio.studio.client.workbench.views.console.model.ConsoleServerOperations;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionPopupPanel;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.HistoryCompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.RCompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
+
+import java.util.ArrayList;
+
+public class Shell implements ConsoleInputHandler,
+ ConsoleWriteOutputHandler,
+ ConsoleWriteErrorHandler,
+ ConsoleWritePromptHandler,
+ ConsoleWriteInputHandler,
+ ConsolePromptHandler,
+ ConsoleResetHistoryHandler,
+ ConsoleRestartRCompletedEvent.Handler,
+ ConsoleExecutePendingInputEvent.Handler,
+ SendToConsoleHandler,
+ DebugModeChangedEvent.Handler,
+ RunCommandWithDebugEvent.Handler,
+ UnhandledErrorEvent.Handler
+{
+ static interface Binder extends CommandBinder<Commands, Shell>
+ {
+ }
+
+ public interface Display extends ShellDisplay
+ {
+ void onBeforeUnselected();
+ void onBeforeSelected();
+ void onSelected();
+ }
+
+ @Inject
+ public Shell(ConsoleServerOperations server,
+ EventBus eventBus,
+ Display display,
+ Session session,
+ GlobalDisplay globalDisplay,
+ Commands commands,
+ UIPrefs uiPrefs,
+ ErrorManager errorManager)
+ {
+ super() ;
+
+ ((Binder)GWT.create(Binder.class)).bind(commands, this);
+
+ server_ = server ;
+ eventBus_ = eventBus ;
+ view_ = display ;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+ errorManager_ = errorManager;
+ input_ = view_.getInputEditorDisplay() ;
+ historyManager_ = new CommandLineHistory(input_);
+ browseHistoryManager_ = new CommandLineHistory(input_);
+ prefs_ = uiPrefs;
+
+ inputAnimator_ = new ShellInputAnimator(view_.getInputEditorDisplay());
+
+ view_.setMaxOutputLines(session.getSessionInfo().getConsoleActionsLimit());
+
+ keyDownPreviewHandlers_ = new ArrayList<KeyDownPreviewHandler>() ;
+ keyPressPreviewHandlers_ = new ArrayList<KeyPressPreviewHandler>() ;
+
+ InputKeyDownHandler handler = new InputKeyDownHandler() ;
+ // This needs to be a capturing key down handler or else Ace will have
+ // handled the event before we had a chance to prevent it
+ view_.addCapturingKeyDownHandler(handler) ;
+ view_.addKeyPressHandler(handler) ;
+
+ eventBus.addHandler(ConsoleInputEvent.TYPE, this);
+ eventBus.addHandler(ConsoleWriteOutputEvent.TYPE, this);
+ eventBus.addHandler(ConsoleWriteErrorEvent.TYPE, this);
+ eventBus.addHandler(ConsoleWritePromptEvent.TYPE, this);
+ eventBus.addHandler(ConsoleWriteInputEvent.TYPE, this);
+ eventBus.addHandler(ConsolePromptEvent.TYPE, this);
+ eventBus.addHandler(ConsoleResetHistoryEvent.TYPE, this);
+ eventBus.addHandler(ConsoleRestartRCompletedEvent.TYPE, this);
+ eventBus.addHandler(ConsoleExecutePendingInputEvent.TYPE, this);
+ eventBus.addHandler(SendToConsoleEvent.TYPE, this);
+ eventBus.addHandler(DebugModeChangedEvent.TYPE, this);
+ eventBus.addHandler(RunCommandWithDebugEvent.TYPE, this);
+ eventBus.addHandler(UnhandledErrorEvent.TYPE, this);
+
+ final CompletionManager completionManager
+ = new RCompletionManager(view_.getInputEditorDisplay(),
+ null,
+ new CompletionPopupPanel(),
+ server,
+ null,
+ null) ;
+ addKeyDownPreviewHandler(completionManager) ;
+ addKeyPressPreviewHandler(completionManager) ;
+
+ addKeyDownPreviewHandler(new HistoryCompletionManager(
+ view_.getInputEditorDisplay(), server));
+
+ uiPrefs.insertMatching().bind(new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ AceEditorNative.setInsertMatching(arg);
+ }});
+
+ sessionInit(session);
+ }
+
+ private void sessionInit(Session session)
+ {
+ SessionInfo sessionInfo = session.getSessionInfo();
+ ClientInitState clientState = sessionInfo.getClientState();
+
+ new StringStateValue(GROUP_CONSOLE, STATE_INPUT, ClientState.TEMPORARY, clientState) {
+ @Override
+ protected void onInit(String value)
+ {
+ initialInput_ = value;
+ }
+ @Override
+ protected String getValue()
+ {
+ return view_.getInputEditorDisplay().getText();
+ }
+ };
+
+ JsArrayString history = sessionInfo.getConsoleHistory();
+ if (history != null)
+ setHistory(history);
+
+ RpcObjectList<ConsoleAction> actions = sessionInfo.getConsoleActions();
+ if (actions != null)
+ {
+ view_.playbackActions(actions);
+ }
+
+ if (sessionInfo.getResumed())
+ {
+ // no special UI for this (resuming session with all console
+ // history and other UI state preserved deemed adequate feedback)
+ }
+ }
+
+ public Display getDisplay()
+ {
+ return view_ ;
+ }
+
+ @Handler
+ void onConsoleClear()
+ {
+ // clear output
+ view_.clearOutput();
+
+ // notify server
+ server_.resetConsoleActions(new VoidServerRequestCallback());
+
+ // if we don't bounce setFocus the menu retains focus
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute()
+ {
+ view_.getInputEditorDisplay().setFocus(true);
+ }
+ });
+
+ }
+
+ public void addKeyDownPreviewHandler(KeyDownPreviewHandler handler)
+ {
+ keyDownPreviewHandlers_.add(handler) ;
+ }
+
+ public void addKeyPressPreviewHandler(KeyPressPreviewHandler handler)
+ {
+ keyPressPreviewHandlers_.add(handler) ;
+ }
+
+ public void onConsoleInput(final ConsoleInputEvent event)
+ {
+ server_.consoleInput(event.getInput(),
+ new ServerRequestCallback<Void>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ // show the error in the console then re-prompt
+ view_.consoleWriteError("Error: " + error.getUserMessage() + "\n");
+ if (lastPromptText_ != null)
+ consolePrompt(lastPromptText_, false);
+ }
+ });
+ }
+
+ public void onConsoleWriteOutput(ConsoleWriteOutputEvent event)
+ {
+ view_.consoleWriteOutput(event.getOutput()) ;
+ }
+
+ public void onConsoleWriteError(final ConsoleWriteErrorEvent event)
+ {
+ view_.consoleWriteError(event.getError());
+ }
+
+ public void onUnhandledError(UnhandledErrorEvent event)
+ {
+ if (!debugging_)
+ {
+ view_.consoleWriteExtendedError(
+ event.getError().getErrorMessage(),
+ event.getError(),
+ prefs_.autoExpandErrorTracebacks().getValue(),
+ getHistoryEntry(0));
+ }
+ }
+
+ public void onConsoleWriteInput(ConsoleWriteInputEvent event)
+ {
+ view_.consoleWriteInput(event.getInput());
+ }
+
+ public void onConsoleWritePrompt(ConsoleWritePromptEvent event)
+ {
+ view_.consoleWritePrompt(event.getPrompt());
+ }
+
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ String prompt = event.getPrompt().getPromptText() ;
+ boolean addToHistory = event.getPrompt().getAddToHistory() ;
+
+ consolePrompt(prompt, addToHistory) ;
+ }
+
+ private void consolePrompt(String prompt, boolean addToHistory)
+ {
+ view_.consolePrompt(prompt, true) ;
+
+ if (lastPromptText_ == null
+ && initialInput_ != null
+ && initialInput_.length() > 0)
+ {
+ view_.getInputEditorDisplay().setText(initialInput_);
+ view_.ensureInputVisible();
+ }
+
+ addToHistory_ = addToHistory;
+ resetHistoryPosition();
+ lastPromptText_ = prompt ;
+
+ if (restoreFocus_)
+ {
+ restoreFocus_ = false;
+ view_.getInputEditorDisplay().setFocus(true);
+ }
+ }
+
+ public void onConsoleResetHistory(ConsoleResetHistoryEvent event)
+ {
+ setHistory(event.getHistory());
+ }
+
+ @Override
+ public void onRestartRCompleted(ConsoleRestartRCompletedEvent event)
+ {
+ if (view_.isPromptEmpty())
+ eventBus_.fireEvent(new SendToConsoleEvent("", true));
+
+ focus();
+ }
+
+ private void processCommandEntry()
+ {
+ String commandText = view_.processCommandEntry() ;
+ if (addToHistory_ && (commandText.length() > 0))
+ addToHistory(commandText);
+
+ // fire event
+ eventBus_.fireEvent(new ConsoleInputEvent(commandText));
+ }
+
+ public void onSendToConsole(final SendToConsoleEvent event)
+ {
+ final InputEditorDisplay display = view_.getInputEditorDisplay();
+
+ // get anything already at the console
+ final String previousInput = StringUtil.notNull(display.getText());
+
+ // define code block we execute at finish
+ Command finishSendToConsole = new Command() {
+ @Override
+ public void execute()
+ {
+ if (event.shouldExecute())
+ {
+ processCommandEntry();
+ if (previousInput.length() > 0)
+ display.setText(previousInput);
+ }
+
+ if (!event.shouldExecute() || event.shouldFocus())
+ {
+ display.setFocus(true);
+ display.collapseSelection(false);
+ }
+ }
+ };
+
+ // do standrd finish if we aren't animating
+ if (!event.shouldAnimate())
+ {
+ display.clear();
+ display.setText(event.getCode());
+ finishSendToConsole.execute();
+ }
+ else
+ {
+ inputAnimator_.enque(event.getCode(), finishSendToConsole);
+ }
+ }
+
+ @Override
+ public void onExecutePendingInput(ConsoleExecutePendingInputEvent event)
+ {
+ // if the source view is delegating a Cmd+Enter to us then
+ // take it if we are focused and we have a command to enter
+ if (view_.getInputEditorDisplay().isFocused() &&
+ (view_.getInputEditorDisplay().getText().length() > 0))
+ {
+ processCommandEntry();
+ }
+ // otherwise delegate back to the source view. we do this via
+ // executing a command which is a bit of hack but it's a clean
+ // way to call code within the "current editor" (an event would
+ // go to all editors). another alternative would be to
+ // call a method on the SourceShim
+ else
+ {
+ commands_.executeCodeWithoutFocus().execute();
+ }
+ }
+
+ @Override
+ public void onDebugModeChanged(DebugModeChangedEvent event)
+ {
+ if (event.debugging())
+ {
+ view_.ensureInputVisible();
+ }
+ debugging_ = event.debugging();
+ }
+
+ @Override
+ public void onRunCommandWithDebug(final RunCommandWithDebugEvent event)
+ {
+ // Invoked from the "Rerun with Debug" command in the ConsoleError widget.
+ errorManager_.setDebugSessionHandlerType(
+ ErrorHandlerType.ERRORS_BREAK,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ eventBus_.fireEvent(new SendToConsoleEvent(
+ event.getCommand(), true));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // if we failed to set debug mode, don't rerun the command
+ }
+ });
+ }
+
+ private final class InputKeyDownHandler implements KeyDownHandler,
+ KeyPressHandler
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ int keyCode = event.getNativeKeyCode();
+
+ for (KeyDownPreviewHandler handler : keyDownPreviewHandlers_)
+ {
+ if (handler.previewKeyDown(event.getNativeEvent()))
+ {
+ event.preventDefault() ;
+ event.stopPropagation() ;
+ return;
+ }
+ }
+
+ if (event.getNativeKeyCode() == KeyCodes.KEY_TAB)
+ event.preventDefault();
+
+ int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+
+ if (event.isUpArrow() && modifiers == 0)
+ {
+ if ((input_.getCurrentLineNum() == 0) || input_.isCursorAtEnd())
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ navigateHistory(-1);
+ }
+ }
+ else if (event.isDownArrow() && modifiers == 0)
+ {
+ if ((input_.getCurrentLineNum() == input_.getCurrentLineCount() - 1)
+ || input_.isCursorAtEnd())
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ navigateHistory(1);
+ }
+ }
+ else if (keyCode == KeyCodes.KEY_ENTER && modifiers == 0)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ restoreFocus_ = true;
+ processCommandEntry();
+ }
+ else if (keyCode == KeyCodes.KEY_ESCAPE && modifiers == 0)
+ {
+ event.preventDefault();
+
+ if (input_.getText().length() == 0)
+ {
+ // view_.isPromptEmpty() is to check for cases where the
+ // server is prompting but not at the top level. Escape
+ // needs to send null in those cases.
+ // For example, try "scan()" function
+ if (view_.isPromptEmpty())
+ {
+ // interrupt server
+ server_.interrupt(new VoidServerRequestCallback() {
+ @Override
+ public void onError(ServerError error)
+ {
+ super.onError(error);
+ globalDisplay_.showErrorMessage(
+ "Error Interrupting Server",
+ error.getUserMessage());
+ }
+ });
+ }
+ else
+ {
+ // if the input is already empty then send a console reset
+ // which will jump us back to the main prompt
+ eventBus_.fireEvent(new ConsoleInputEvent(null));
+ }
+ }
+
+ input_.clear();
+ }
+ else
+ {
+ int mod = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ if (mod == KeyboardShortcut.CTRL)
+ {
+ switch (keyCode)
+ {
+ case 'L':
+ Shell.this.onConsoleClear() ;
+ event.preventDefault() ;
+ break;
+ }
+ }
+ else if (mod == KeyboardShortcut.ALT)
+ {
+ switch (keyCode)
+ {
+ case 189: // hyphen
+ event.preventDefault();
+ event.stopPropagation();
+ input_.replaceSelection(" <- ", true);
+ break;
+ }
+ }
+ }
+ }
+
+ public void onKeyPress(KeyPressEvent event)
+ {
+ for (KeyPressPreviewHandler handler : keyPressPreviewHandlers_)
+ {
+ if (handler.previewKeyPress(event.getCharCode()))
+ {
+ event.preventDefault() ;
+ event.stopPropagation() ;
+ return;
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private boolean lastKeyCodeWasZero_;
+ }
+
+ private boolean isBrowsePrompt()
+ {
+ return lastPromptText_ != null && (lastPromptText_.startsWith("Browse"));
+ }
+
+ private void resetHistoryPosition()
+ {
+ historyManager_.resetPosition();
+ browseHistoryManager_.resetPosition();
+ }
+
+ private void addToHistory(String commandText)
+ {
+ if (isBrowsePrompt())
+ browseHistoryManager_.addToHistory(commandText);
+ else
+ historyManager_.addToHistory(commandText);
+ }
+
+ private String getHistoryEntry(int offset)
+ {
+ if (isBrowsePrompt())
+ return browseHistoryManager_.getHistoryEntry(offset);
+ else
+ return historyManager_.getHistoryEntry(offset);
+ }
+
+ private void navigateHistory(int offset)
+ {
+ if (isBrowsePrompt())
+ browseHistoryManager_.navigateHistory(offset);
+ else
+ historyManager_.navigateHistory(offset);
+
+ view_.ensureInputVisible();
+ }
+
+ public void focus()
+ {
+ input_.setFocus(true);
+ }
+
+ private void setHistory(JsArrayString history)
+ {
+ ArrayList<String> historyList = new ArrayList<String>(history.length());
+ for (int i = 0; i < history.length(); i++)
+ historyList.add(history.get(i));
+ historyManager_.setHistory(historyList);
+ browseHistoryManager_.resetPosition();
+ }
+
+ public void onBeforeUnselected()
+ {
+ view_.onBeforeUnselected();
+
+ }
+
+ public void onBeforeSelected()
+ {
+ view_.onBeforeSelected();
+ }
+
+ public void onSelected()
+ {
+ view_.onSelected();
+ }
+
+ private final ConsoleServerOperations server_ ;
+ private final EventBus eventBus_ ;
+ private final Display view_ ;
+ private final GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+ private final ErrorManager errorManager_;
+ private final InputEditorDisplay input_ ;
+ private final ArrayList<KeyDownPreviewHandler> keyDownPreviewHandlers_ ;
+ private final ArrayList<KeyPressPreviewHandler> keyPressPreviewHandlers_ ;
+ // indicates whether the next command should be added to history
+ private boolean addToHistory_ ;
+ private String lastPromptText_ ;
+ private final UIPrefs prefs_;
+
+ private final CommandLineHistory historyManager_;
+ private final CommandLineHistory browseHistoryManager_;
+
+ private final ShellInputAnimator inputAnimator_;
+
+ private String initialInput_ ;
+
+ private static final String GROUP_CONSOLE = "console";
+ private static final String STATE_INPUT = "input";
+
+ private boolean restoreFocus_ = true;
+ private boolean debugging_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellInputAnimator.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellInputAnimator.java
new file mode 100644
index 0000000..e507964
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellInputAnimator.java
@@ -0,0 +1,130 @@
+/*
+ * ShellInputAnimator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.workbench.views.console.shell;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.user.client.Command;
+
+public class ShellInputAnimator
+{
+ public ShellInputAnimator(InputEditorDisplay display)
+ {
+ display_ = display;
+ }
+
+ public void enque(String code, final Command onFinished)
+ {
+ // wrap onFinished with a check of the queue for additional commands
+ Command onAnimatedInputFinished = new Command() {
+ @Override
+ public void execute()
+ {
+ // perform stock finished action
+ onFinished.execute();
+
+ // remove from queue
+ pendingAnimatedInput_.remove(0);
+
+ // execute any pending inputs
+ executePendingAnimatedInput();
+ }
+ };
+
+ // create the input and add it to the queue
+ InputAnimator inputAnimator = new InputAnimator(
+ code,
+ onAnimatedInputFinished);
+ pendingAnimatedInput_.add(inputAnimator);
+
+ // if we are the only one in the queue then we need to manually
+ // force execution (otherwise we'll just get execute when the
+ // currently executing command completes)
+ if (pendingAnimatedInput_.size() == 1)
+ executePendingAnimatedInput();
+ }
+
+
+ private void executePendingAnimatedInput()
+ {
+ if (pendingAnimatedInput_.size() > 0)
+ {
+ // get the input animaator
+ InputAnimator inputAnimator = pendingAnimatedInput_.get(0);
+
+ // calculate the period (make sure the command takes no longer
+ // than 1600ms to input)
+ final int kMaxMs = 1600;
+ String code = inputAnimator.getCode();
+ int period = Math.min( kMaxMs / code.length(), 75);
+
+ // schedule it
+ Scheduler.get().scheduleFixedPeriod(inputAnimator, period);
+ }
+ }
+
+
+ private class InputAnimator implements RepeatingCommand
+ {
+ public InputAnimator(String code, Command onFinished)
+ {
+ code_ = code;
+ onFinished_ = onFinished;
+ }
+
+ public String getCode()
+ {
+ return code_;
+ }
+
+ @Override
+ public boolean execute()
+ {
+ // termination condition
+ if ((nextChar_ + 1) > code_.length())
+ {
+ onFinished_.execute();
+ return false;
+ }
+
+ // clear before first char
+ if (nextChar_ == 0)
+ display_.clear();
+
+ display_.insertCode(code_.substring(nextChar_, nextChar_+1));
+
+ nextChar_++;
+
+ return true;
+ }
+
+ private int nextChar_ = 0;
+ private final String code_;
+ private final Command onFinished_;
+
+ }
+
+ private InputEditorDisplay display_;
+
+ private ArrayList<InputAnimator> pendingAnimatedInput_ =
+ new ArrayList<InputAnimator>();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellPane.java
new file mode 100644
index 0000000..18cb38e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/ShellPane.java
@@ -0,0 +1,88 @@
+/*
+ * ShellPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.shell.ShellWidget;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+
+public class ShellPane extends ShellWidget implements Shell.Display
+{
+ @Inject
+ public ShellPane(final AceEditor editor, UIPrefs uiPrefs, EventBus events)
+ {
+ super(editor, events);
+
+ editor.setDisableOverwrite(true);
+
+ editor.setFileType(FileTypeRegistry.R, true);
+ // Setting file type to R changes the wrap mode to false. We want it to
+ // be true so that the console input can wrap.
+ editor.setUseWrapMode(true);
+
+ uiPrefs.syntaxColorConsole().bind(new CommandWithArg<Boolean>()
+ {
+ public void execute(Boolean arg)
+ {
+ Widget inputWidget = editor.getWidget();
+ if (arg)
+ inputWidget.removeStyleName("nocolor");
+ else
+ inputWidget.addStyleName("nocolor");
+ }
+ });
+
+ uiPrefs.blinkingCursor().bind(new CommandWithArg<Boolean>()
+ {
+ public void execute(Boolean arg)
+ {
+ editor.setBlinkingCursor(arg);
+ }
+ });
+ }
+
+ @Override
+ public void onBeforeUnselected()
+ {
+ scrollPanel_.saveScrollPosition();
+ }
+
+ @Override
+ public void onBeforeSelected()
+ {
+ }
+
+ @Override
+ public void onSelected()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ doOnLoad();
+ scrollPanel_.restoreScrollPosition();
+ input_.focus();
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionList.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionList.java
new file mode 100644
index 0000000..61c0a03
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionList.java
@@ -0,0 +1,304 @@
+/*
+ * CompletionList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLTable.Cell;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+
+class CompletionList<TItem> extends Composite
+ implements HasSelectionCommitHandlers<TItem>,
+ HasSelectionHandlers<TItem>
+{
+ public class GridMouseHandler implements ClickHandler, MouseMoveHandler
+ {
+ public void onClick(ClickEvent event)
+ {
+ Cell cell = grid_.getCellForEvent(event) ;
+ if (cell != null)
+ {
+ int rowClicked = cell.getRowIndex() ;
+ SelectionCommitEvent.fire(CompletionList.this, items_[rowClicked]) ;
+ }
+ }
+
+ public void onMouseMove(MouseMoveEvent event)
+ {
+ if (lastMouseMoveCoordinates_ != null)
+ {
+ if (event.getScreenX() == lastMouseMoveCoordinates_.getX()
+ && event.getScreenY() == lastMouseMoveCoordinates_.getY())
+ {
+ return;
+ }
+ }
+ lastMouseMoveCoordinates_ = new Point(event.getScreenX(),
+ event.getScreenY());
+
+ if (firstEvent_)
+ {
+ // Want to avoid the bug where the cursor happens to be positioned
+ // where the popup shows up and thus fires a mouse move event;
+ // so even though the user isn't touching the mouse, the selection
+ // changes.
+ firstEvent_ = false;
+ return;
+ }
+
+ int mousedOverRow = grid_.getRowForEvent(event) ;
+ if (mousedOverRow >= 0)
+ {
+ setSelectedIndex(mousedOverRow) ;
+ }
+ }
+
+ private boolean firstEvent_ = true;
+ private Point lastMouseMoveCoordinates_;
+ }
+
+ public CompletionList(TItem[] items,
+ int visibleItems,
+ boolean asHtml,
+ boolean allowVerticalShrink)
+ {
+ allowVerticalShrink_ = allowVerticalShrink;
+ styles_ = ConsoleResources.INSTANCE.consoleStyles();
+ GridEx grid = new GridEx(items.length, 1) ;
+ for (int i = 0; i < items.length; i++)
+ {
+ if (asHtml)
+ grid.setHTML(i, 0, items[i].toString()) ;
+ else
+ grid.setText(i, 0, items[i].toString()) ;
+ }
+ grid.addClickHandler(new GridMouseHandler()) ;
+ grid.addMouseMoveHandler(new GridMouseHandler()) ;
+ grid.setStylePrimaryName(styles_.completionGrid()) ;
+
+ FontSizer.applyNormalFontSize(grid);
+
+ scrollPanel_ = new ScrollPanel() ;
+ scrollPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
+ scrollPanel_.add(grid) ;
+ scrollPanel_.setHeight((visibleItems * 20) + "px") ;
+
+ initWidget(scrollPanel_) ;
+ grid_ = grid ;
+ items_ = items ;
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad() ;
+ int width = grid_.getOffsetWidth() + 20;
+ if (maxWidthInPixels_ != null
+ && maxWidthInPixels_ > 0
+ && maxWidthInPixels_ < width)
+ width = maxWidthInPixels_;
+ scrollPanel_.setWidth(width + "px") ;
+ if (allowVerticalShrink_ &&
+ grid_.getOffsetHeight() < scrollPanel_.getOffsetHeight())
+ {
+ scrollPanel_.setHeight("") ;
+ }
+ grid_.setWidth("100%") ;
+ selectNext() ;
+ }
+
+ public int getItemCount()
+ {
+ if (grid_ != null)
+ return grid_.getRowCount() ;
+ else
+ return 0;
+ }
+
+ public TItem getSelectedItem()
+ {
+ int index = getSelectedIndex() ;
+ if (index < 0)
+ return null ;
+ return items_[index] ;
+ }
+
+ public boolean selectNext()
+ {
+ return moveSelection(1, true) ;
+ }
+
+ public boolean selectPrev()
+ {
+ return moveSelection(-1, true) ;
+ }
+
+ public boolean selectNextPage()
+ {
+ return moveSelection(4, false) ;
+ }
+
+ public boolean selectPrevPage()
+ {
+ return moveSelection(-4, false) ;
+ }
+
+ public boolean selectFirst()
+ {
+ return moveSelection(-getItemCount(), false) ;
+ }
+
+ public boolean selectLast()
+ {
+ return moveSelection(getItemCount(), false) ;
+ }
+
+ private boolean moveSelection(int offset, boolean allowWrap)
+ {
+ if (getItemCount() == 0)
+ return false ;
+
+ int index = getSelectedIndex() + offset ;
+ if (allowWrap)
+ index = (index + getItemCount()) % getItemCount() ;
+ else
+ index = Math.min(getItemCount() - 1, Math.max(0, index)) ;
+
+ setSelectedIndex(index) ;
+
+ return true ;
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<TItem> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType()) ;
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<TItem> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType()) ;
+ }
+
+ public HTML getDetailedInfoPane()
+ {
+ return null ;
+ }
+
+ public int getSelectedIndex()
+ {
+ return selectedIndex_ ;
+ }
+
+ public void setSelectedIndex(int index)
+ {
+ if (selectedIndex_ != index)
+ {
+ CellFormatter cf = grid_.getCellFormatter() ;
+ if (selectedIndex_ >= 0)
+ cf.removeStyleName(selectedIndex_, 0, styles_.selected()) ;
+
+ selectedIndex_ = index ;
+
+ if (index >= 0)
+ {
+ cf.addStyleName(selectedIndex_, 0, styles_.selected()) ;
+ com.google.gwt.dom.client.Element el =
+ DomUtils.getTableCell(grid_.getElement(), index, 0) ;
+ DomUtils.ensureVisibleVert(scrollPanel_.getElement(), el, 2) ;
+ SelectionEvent.fire(this, getSelectedItem()) ;
+ }
+ }
+ }
+
+ /**
+ * Gets the rectangle of the selected row in absolute (document-relative)
+ * coordinates, or null if nothing is selected.
+ */
+ public Rectangle getSelectionRect()
+ {
+ int index = getSelectedIndex() ;
+ if (index < 0)
+ return null;
+
+ com.google.gwt.dom.client.Element el =
+ DomUtils.getTableCell(grid_.getElement(), index, 0) ;
+ return new Rectangle(el.getAbsoluteLeft(),
+ el.getAbsoluteTop(),
+ el.getOffsetWidth(),
+ el.getOffsetHeight()) ;
+ }
+
+ private class GridEx extends Grid implements HasMouseMoveHandlers
+ {
+ public GridEx(int rows, int cols)
+ {
+ super(rows, cols) ;
+ sinkEvents(Event.ONMOUSEMOVE) ;
+ }
+
+ public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler)
+ {
+ return addHandler(handler, MouseMoveEvent.getType()) ;
+ }
+
+ public int getRowForEvent(MouseMoveEvent event)
+ {
+ Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
+ if (td == null) {
+ return -1;
+ }
+
+ Element tr = DOM.getParent(td);
+ Element body = DOM.getParent(tr);
+ int row = DOM.getChildIndex(body, tr);
+
+ return row ;
+ }
+ }
+
+ public void setMaxWidth(int maxWidthInPixels)
+ {
+ maxWidthInPixels_ = maxWidthInPixels;
+ }
+
+ private int selectedIndex_ = -1 ;
+
+ private final GridEx grid_ ;
+ private final TItem[] items_ ;
+ private final ScrollPanel scrollPanel_ ;
+ private final ConsoleResources.ConsoleStyles styles_;
+ private final boolean allowVerticalShrink_;
+ private Integer maxWidthInPixels_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionListPopupPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionListPopupPanel.java
new file mode 100644
index 0000000..68be694
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionListPopupPanel.java
@@ -0,0 +1,90 @@
+/*
+ * CompletionListPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.HTML;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.ThemedPopupPanel;
+
+public class CompletionListPopupPanel extends ThemedPopupPanel
+ implements HasSelectionCommitHandlers<String>
+{
+ public CompletionListPopupPanel(String[] entries)
+ {
+ super(true);
+ list_ = new CompletionList<String>(entries, 10, false, true);
+ setWidget(list_);
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<String> handler)
+ {
+ return list_.addSelectionCommitHandler(handler);
+ }
+
+ public String getSelectedValue()
+ {
+ if (list_ == null || !list_.isAttached())
+ return null ;
+
+ return list_.getSelectedItem() ;
+ }
+
+ public boolean selectNext()
+ {
+ return list_.selectNext() ;
+ }
+
+ public boolean selectPrev()
+ {
+ return list_.selectPrev() ;
+ }
+
+ public boolean selectPrevPage()
+ {
+ return list_.selectPrevPage() ;
+ }
+
+ public boolean selectNextPage()
+ {
+ return list_.selectNextPage() ;
+ }
+
+ public boolean selectFirst()
+ {
+ return list_.selectFirst() ;
+ }
+
+ public boolean selectLast()
+ {
+ return list_.selectLast() ;
+ }
+
+ public void setMaxWidth(int pixels)
+ {
+ list_.setMaxWidth(pixels);
+ }
+
+ public void setText(String s)
+ {
+ HTML html = new HTML();
+ html.setText(s);
+ setWidget(html);
+ }
+
+ private final CompletionList<String> list_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionManager.java
new file mode 100644
index 0000000..66e16b1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionManager.java
@@ -0,0 +1,36 @@
+/*
+ * CompletionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.dom.client.NativeEvent;
+import org.rstudio.studio.client.workbench.views.console.shell.KeyDownPreviewHandler;
+import org.rstudio.studio.client.workbench.views.console.shell.KeyPressPreviewHandler;
+
+public interface CompletionManager extends KeyDownPreviewHandler,
+ KeyPressPreviewHandler
+{
+ interface InitCompletionFilter
+ {
+ boolean shouldComplete(NativeEvent keyDownEvent) ;
+ }
+
+ void goToHelp();
+ void goToFunctionDefinition();
+
+ void codeCompletion();
+
+ void close();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupDisplay.java
new file mode 100644
index 0000000..dca8b30
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupDisplay.java
@@ -0,0 +1,63 @@
+/*
+ * CompletionPopupDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.event.dom.client.HasMouseDownHandlers;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionRequester.QualifiedName;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo.ParsedInfo;
+
+public interface CompletionPopupDisplay
+ extends HasSelectionCommitHandlers<QualifiedName>,
+ HasSelectionHandlers<QualifiedName>,
+ HasCloseHandlers<PopupPanel>,
+ HasMouseDownHandlers
+{
+ void showCompletionValues(QualifiedName[] results,
+ PositionCallback callback,
+ boolean showHelpPane) ;
+ void showErrorMessage(String userMessage, PositionCallback callback) ;
+ void hide() ;
+ boolean isShowing() ;
+
+ void setPopupPosition(int x, int y) ;
+ int getOffsetHeight() ;
+
+ QualifiedName getSelectedValue() ;
+ Rectangle getSelectionRect() ;
+
+ boolean selectPrev() ;
+ boolean selectNext() ;
+ boolean selectPrevPage() ;
+ boolean selectNextPage() ;
+ boolean selectFirst() ;
+ boolean selectLast() ;
+
+ void displayFunctionHelp(HelpInfo.ParsedInfo help) ;
+ void displayParameterHelp(ParsedInfo helpInfo, String parameter) ;
+ /**
+ * Clear out the current help info
+ * @param downloadOperationPending If true, the current value is being
+ * cleared in preparation for a new value that is being downloaded.
+ * Implementations may choose to show a progress indicator in this case.
+ */
+ void clearHelp(boolean downloadOperationPending) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupPanel.java
new file mode 100644
index 0000000..0d1e62c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionPopupPanel.java
@@ -0,0 +1,226 @@
+/*
+ * CompletionPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.ThemedPopupPanel;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionRequester.QualifiedName;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo.ParsedInfo;
+
+public class CompletionPopupPanel extends ThemedPopupPanel
+ implements CompletionPopupDisplay
+{
+ public CompletionPopupPanel()
+ {
+ super() ;
+ styles_ = ConsoleResources.INSTANCE.consoleStyles();
+ setStylePrimaryName(styles_.completionPopup()) ;
+ }
+
+ public void showProgress(String progress, PositionCallback callback)
+ {
+ setText(progress) ;
+ show(callback) ;
+ }
+
+ public void showErrorMessage(String error, PositionCallback callback)
+ {
+ setText(error) ;
+ show(callback) ;
+ }
+
+ public void showCompletionValues(QualifiedName[] values,
+ PositionCallback callback,
+ boolean showHelpPane)
+ {
+ CompletionList<QualifiedName> list = new CompletionList<QualifiedName>(
+ values,
+ 7,
+ true,
+ false) ;
+
+ list.addSelectionCommitHandler(new SelectionCommitHandler<QualifiedName>() {
+ public void onSelectionCommit(SelectionCommitEvent<QualifiedName> event)
+ {
+ SelectionCommitEvent.fire(CompletionPopupPanel.this,
+ event.getSelectedItem()) ;
+ }
+ }) ;
+ list.addSelectionHandler(new SelectionHandler<QualifiedName>() {
+ public void onSelection(SelectionEvent<QualifiedName> event)
+ {
+ SelectionEvent.fire(CompletionPopupPanel.this,
+ event.getSelectedItem()) ;
+ }
+ }) ;
+ list_ = list ;
+
+ help_ = new HelpInfoPane() ;
+ help_.setWidth("400px") ;
+
+ HorizontalPanelWithMouseEvents horiz
+ = new HorizontalPanelWithMouseEvents() ;
+ horiz.add(list_) ;
+ if (showHelpPane)
+ horiz.add(help_) ;
+
+ setWidget(horiz) ;
+ ElementIds.assignElementId(horiz.getElement(),
+ ElementIds.POPUP_COMPLETIONS);
+
+ show(callback) ;
+ }
+
+ private void show(PositionCallback callback)
+ {
+ if (callback != null)
+ setPopupPositionAndShow(callback) ;
+ else
+ show() ;
+ }
+
+ public QualifiedName getSelectedValue()
+ {
+ if (list_ == null || !list_.isAttached())
+ return null ;
+
+ return list_.getSelectedItem() ;
+ }
+
+ public Rectangle getSelectionRect()
+ {
+ return list_.getSelectionRect() ;
+ }
+
+ public boolean selectNext()
+ {
+ return list_.selectNext() ;
+ }
+
+ public boolean selectPrev()
+ {
+ return list_.selectPrev() ;
+ }
+
+ public boolean selectPrevPage()
+ {
+ return list_.selectPrevPage() ;
+ }
+
+ public boolean selectNextPage()
+ {
+ return list_.selectNextPage() ;
+ }
+
+ public boolean selectFirst()
+ {
+ return list_.selectFirst() ;
+ }
+
+ public boolean selectLast()
+ {
+ return list_.selectLast() ;
+ }
+
+ public void displayFunctionHelp(ParsedInfo help)
+ {
+ help_.displayFunctionHelp(help) ;
+ help_.setHeight(list_.getOffsetHeight() + "px") ;
+ }
+
+ public void displayParameterHelp(ParsedInfo help, String parameterName)
+ {
+ help_.displayParameterHelp(help, parameterName) ;
+ help_.setHeight(list_.getOffsetHeight() + "px") ;
+ }
+
+ public void clearHelp(boolean downloadOperationPending)
+ {
+ help_.clearHelp(downloadOperationPending) ;
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<QualifiedName> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType()) ;
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<QualifiedName> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType()) ;
+ }
+
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addDomHandler(handler, MouseDownEvent.getType()) ;
+ }
+
+ private HTML setText(String text)
+ {
+ HTML contents = new HTML() ;
+ contents.setText(text) ;
+ setWidget(contents) ;
+ return contents ;
+ }
+
+ private static class HorizontalPanelWithMouseEvents
+ extends HorizontalPanel
+ implements HasMouseOverHandlers,
+ HasMouseOutHandlers
+ {
+ public HorizontalPanelWithMouseEvents()
+ {
+ super() ;
+ sinkEvents(Event.ONMOUSEOVER
+ | Event.ONMOUSEOUT
+ | Event.ONMOUSEDOWN
+ | Event.ONFOCUS
+ | Event.ONBLUR) ;
+ }
+
+ public HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
+ {
+ return addHandler(handler, MouseOverEvent.getType()) ;
+ }
+
+ public HandlerRegistration addMouseOutHandler(MouseOutHandler handler)
+ {
+ return addHandler(handler, MouseOutEvent.getType()) ;
+ }
+
+ @SuppressWarnings("unused")
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return addHandler(handler, MouseDownEvent.getType()) ;
+ }
+ }
+
+ private CompletionList<QualifiedName> list_ ;
+ private HelpInfoPane help_ ;
+ private final ConsoleResources.ConsoleStyles styles_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionRequester.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionRequester.java
new file mode 100644
index 0000000..a38216a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionRequester.java
@@ -0,0 +1,272 @@
+/*
+ * CompletionRequester.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.codetools.Completions;
+import org.rstudio.studio.client.common.r.RToken;
+import org.rstudio.studio.client.common.r.RTokenizer;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.source.model.RnwChunkOptions;
+import org.rstudio.studio.client.workbench.views.source.model.RnwChunkOptions.RnwOptionCompletionResult;
+import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
+
+import java.util.ArrayList;
+
+
+public class CompletionRequester
+{
+ private final CodeToolsServerOperations server_ ;
+
+ private String cachedLinePrefix_ ;
+ private CompletionResult cachedResult_ ;
+ private RnwCompletionContext rnwContext_ ;
+
+ public CompletionRequester(CodeToolsServerOperations server,
+ RnwCompletionContext rnwContext)
+ {
+ server_ = server ;
+ rnwContext_ = rnwContext;
+ }
+
+ public void getCompletions(
+ final String line,
+ final int pos,
+ final boolean implicit,
+ final ServerRequestCallback<CompletionResult> callback)
+ {
+ if (cachedResult_ != null && cachedResult_.guessedFunctionName == null)
+ {
+ if (line.substring(0, pos).startsWith(cachedLinePrefix_))
+ {
+ String diff = line.substring(cachedLinePrefix_.length(), pos) ;
+ if (diff.length() > 0)
+ {
+ ArrayList<RToken> tokens = RTokenizer.asTokens("a" + diff) ;
+
+ // when we cross a :: the list may actually grow, not shrink
+ if (!diff.endsWith("::"))
+ {
+ while (tokens.size() > 0
+ && tokens.get(tokens.size()-1).getContent().equals(":"))
+ {
+ tokens.remove(tokens.size()-1) ;
+ }
+
+ if (tokens.size() == 1
+ && tokens.get(0).getTokenType() == RToken.ID)
+ {
+ callback.onResponseReceived(narrow(diff)) ;
+ return ;
+ }
+ }
+ }
+ }
+ }
+
+ doGetCompletions(line, pos, new ServerRequestCallback<Completions>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ callback.onError(error);
+ }
+
+ @Override
+ public void onResponseReceived(Completions response)
+ {
+ cachedLinePrefix_ = line.substring(0, pos);
+
+ JsArrayString comp = response.getCompletions();
+ JsArrayString pkgs = response.getPackages();
+ ArrayList<QualifiedName> newComp = new ArrayList<QualifiedName>();
+
+ for (int i = 0; i < comp.length(); i++)
+ newComp.add(new QualifiedName(comp.get(i), pkgs.get(i)));
+
+ CompletionResult result = new CompletionResult(
+ response.getToken(),
+ newComp,
+ response.getGuessedFunctionName(),
+ response.getSuggestOnAccept());
+
+ cachedResult_ = response.isCacheable() ? result : null;
+
+ if (!implicit || result.completions.size() != 0)
+ callback.onResponseReceived(result);
+ }
+ }) ;
+ }
+
+ private void doGetCompletions(
+ String line,
+ int pos,
+ ServerRequestCallback<Completions> requestCallback)
+ {
+ int optionsStartOffset;
+ if (rnwContext_ != null &&
+ (optionsStartOffset = rnwContext_.getRnwOptionsStart(line, pos)) >= 0)
+ {
+ doGetSweaveCompletions(line, optionsStartOffset, pos, requestCallback);
+ }
+ else
+ {
+ server_.getCompletions(line, pos, requestCallback);
+ }
+ }
+
+ private void doGetSweaveCompletions(
+ final String line,
+ final int optionsStartOffset,
+ final int cursorPos,
+ final ServerRequestCallback<Completions> requestCallback)
+ {
+ rnwContext_.getChunkOptions(new ServerRequestCallback<RnwChunkOptions>()
+ {
+ @Override
+ public void onResponseReceived(RnwChunkOptions options)
+ {
+ RnwOptionCompletionResult result = options.getCompletions(
+ line,
+ optionsStartOffset,
+ cursorPos,
+ rnwContext_ == null ? null : rnwContext_.getActiveRnwWeave());
+
+ Completions response = Completions.createCompletions(
+ result.token,
+ result.completions,
+ JsUtil.createEmptyArray(result.completions.length())
+ .<JsArrayString>cast(),
+ null);
+ // Unlike other completion types, Sweave completions are not
+ // guaranteed to narrow the candidate list (in particular
+ // true/false).
+ response.setCacheable(false);
+ if (result.completions.length() > 0 &&
+ result.completions.get(0).endsWith("="))
+ {
+ response.setSuggestOnAccept(true);
+ }
+ requestCallback.onResponseReceived(response);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ requestCallback.onError(error);
+ }
+ });
+ }
+
+ public void flushCache()
+ {
+ cachedLinePrefix_ = null ;
+ cachedResult_ = null ;
+ }
+
+ private CompletionResult narrow(String diff)
+ {
+ assert cachedResult_.guessedFunctionName == null ;
+
+ String token = cachedResult_.token + diff ;
+ ArrayList<QualifiedName> newCompletions = new ArrayList<QualifiedName>() ;
+ for (QualifiedName qname : cachedResult_.completions)
+ if (qname.name.startsWith(token))
+ newCompletions.add(qname) ;
+
+ return new CompletionResult(token, newCompletions, null,
+ cachedResult_.suggestOnAccept) ;
+ }
+
+ public static class CompletionResult
+ {
+ public CompletionResult(String token, ArrayList<QualifiedName> completions,
+ String guessedFunctionName,
+ boolean suggestOnAccept)
+ {
+ this.token = token ;
+ this.completions = completions ;
+ this.guessedFunctionName = guessedFunctionName ;
+ this.suggestOnAccept = suggestOnAccept ;
+ }
+
+ public final String token ;
+ public final ArrayList<QualifiedName> completions ;
+ public final String guessedFunctionName ;
+ public final boolean suggestOnAccept ;
+ }
+
+ public static class QualifiedName implements Comparable<QualifiedName>
+ {
+ public QualifiedName(String name, String pkgName)
+ {
+ this.name = name ;
+ this.pkgName = pkgName ;
+ }
+
+ @Override
+ public String toString()
+ {
+ return DomUtils.textToHtml(name) + getFormattedPackageName();
+ }
+
+ private String getFormattedPackageName()
+ {
+ return pkgName == null || pkgName.length() == 0
+ ? ""
+ : " <span class=\"packageName\">{"
+ + DomUtils.textToHtml(pkgName)
+ + "}</span>";
+ }
+
+ public static QualifiedName parseFromText(String val)
+ {
+ String name, pkgName = null;
+ int idx = val.indexOf('{') ;
+ if (idx < 0)
+ {
+ name = val ;
+ }
+ else
+ {
+ name = val.substring(0, idx).trim() ;
+ pkgName = val.substring(idx + 1, val.length() - 1) ;
+ }
+
+ return new QualifiedName(name, pkgName) ;
+ }
+
+ public int compareTo(QualifiedName o)
+ {
+ if (name.endsWith("=") ^ o.name.endsWith("="))
+ return name.endsWith("=") ? -1 : 1 ;
+
+ int result = String.CASE_INSENSITIVE_ORDER.compare(name, o.name) ;
+ if (result != 0)
+ return result ;
+
+ String pkg = pkgName == null ? "" : pkgName ;
+ String opkg = o.pkgName == null ? "" : o.pkgName ;
+ return pkg.compareTo(opkg) ;
+ }
+
+ public final String name ;
+ public final String pkgName ;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionUtils.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionUtils.java
new file mode 100644
index 0000000..ec13a7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/CompletionUtils.java
@@ -0,0 +1,21 @@
+/*
+ * CompletionUtils.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+public class CompletionUtils
+{
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpInfoPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpInfoPane.java
new file mode 100644
index 0000000..0667713
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpInfoPane.java
@@ -0,0 +1,116 @@
+/*
+ * HelpInfoPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo;
+
+public class HelpInfoPane extends Composite
+{
+ public HelpInfoPane()
+ {
+ styles_ = ConsoleResources.INSTANCE.consoleStyles();
+
+ DockLayoutPanel outer = new DockLayoutPanel(Unit.PX);
+
+ f1prompt_ = new Label("Press F1 for additional help");
+ f1prompt_.setStylePrimaryName(styles_.promptFullHelp());
+ outer.addSouth(f1prompt_, 16);
+
+ scrollPanel_.add(vpanel_) ;
+ vpanel_.setStylePrimaryName(styles_.functionInfo()) ;
+ vpanel_.setWidth("100%") ;
+ outer.add(scrollPanel_);
+
+ initWidget(outer) ;
+
+ timer_ = new Timer() {
+ public void run()
+ {
+ scrollPanel_.setVisible(false) ;
+ vpanel_.clear() ;
+ }
+ };
+ }
+
+ public void displayFunctionHelp(HelpInfo.ParsedInfo help)
+ {
+ timer_.cancel() ;
+ vpanel_.clear() ;
+
+ f1prompt_.setVisible(true);
+
+ if (help.getFunctionSignature() != null)
+ {
+ Label lblSig = new Label(help.getFunctionSignature()) ;
+ lblSig.setStylePrimaryName(styles_.functionInfoSignature()) ;
+ vpanel_.add(lblSig);
+ }
+
+ HTML htmlDesc = new HTML(help.getDescription()) ;
+ htmlDesc.setStylePrimaryName(styles_.functionInfoSummary()) ;
+ vpanel_.add(htmlDesc) ;
+
+ scrollPanel_.setVisible(true) ;
+ }
+
+ public void displayParameterHelp(HelpInfo.ParsedInfo help, String paramName)
+ {
+ String desc = help.getArgs().get(paramName) ;
+ if (desc == null)
+ {
+ clearHelp(false) ;
+ return ;
+ }
+
+ timer_.cancel() ;
+ vpanel_.clear() ;
+
+ f1prompt_.setVisible(true);
+
+ if (paramName != null)
+ {
+ Label lblSig = new Label(paramName) ;
+ lblSig.setStylePrimaryName(styles_.paramInfoName()) ;
+ vpanel_.add(lblSig);
+ }
+
+ HTML htmlDesc = new HTML(desc) ;
+ htmlDesc.setStylePrimaryName(styles_.paramInfoDesc()) ;
+ vpanel_.add(htmlDesc) ;
+
+ scrollPanel_.setVisible(true) ;
+ }
+
+ public void clearHelp(boolean downloadOperationPending)
+ {
+ f1prompt_.setVisible(false);
+
+ timer_.cancel() ;
+ if (downloadOperationPending)
+ timer_.schedule(170) ;
+ else
+ timer_.run() ;
+ }
+
+ private final ScrollPanel scrollPanel_ = new ScrollPanel() ;
+ private final VerticalPanel vpanel_ = new VerticalPanel() ;
+ private final Timer timer_;
+ private final ConsoleResources.ConsoleStyles styles_;
+ private Label f1prompt_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpStrategy.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpStrategy.java
new file mode 100644
index 0000000..f99990a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HelpStrategy.java
@@ -0,0 +1,213 @@
+/*
+ * HelpStrategy.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionRequester.QualifiedName;
+import org.rstudio.studio.client.workbench.views.help.model.HelpInfo;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+
+public abstract class HelpStrategy
+{
+ public abstract void showHelp(QualifiedName selectedItem,
+ CompletionPopupDisplay display) ;
+
+ public abstract void showHelpTopic(QualifiedName selectedItem) ;
+
+ public abstract boolean isNull();
+
+ public static HelpStrategy createFunctionStrategy(
+ HelpServerOperations server)
+ {
+ return new FunctionStrategy(server) ;
+ }
+
+ public static HelpStrategy createParameterStrategy(
+ HelpServerOperations server,
+ String functionName)
+ {
+ return new ParameterStrategy(server, functionName) ;
+ }
+
+ public static HelpStrategy createNullStrategy()
+ {
+ return new NullStrategy();
+ }
+
+ static class NullStrategy extends HelpStrategy
+ {
+ public NullStrategy() {}
+
+ @Override
+ public void showHelp(QualifiedName selectedItem, CompletionPopupDisplay display)
+ {
+ }
+
+ @Override
+ public void showHelpTopic(QualifiedName selectedItem)
+ {
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return true;
+ }
+ }
+
+ static class FunctionStrategy extends HelpStrategy
+ {
+ public FunctionStrategy(HelpServerOperations server)
+ {
+ super() ;
+ server_ = server ;
+ }
+
+ @Override
+ public void showHelp(final QualifiedName selectedItem,
+ final CompletionPopupDisplay display)
+ {
+ server_.getHelp(selectedItem.name, selectedItem.pkgName, 0,
+ new ServerRequestCallback<HelpInfo>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Error Retrieving Help", error.getUserMessage());
+ display.clearHelp(false) ;
+ }
+
+ public void onResponseReceived(HelpInfo result)
+ {
+ if (result != null)
+ {
+ HelpInfo.ParsedInfo help = result.parse(selectedItem.name) ;
+ if (help.hasInfo())
+ {
+ display.displayFunctionHelp(help) ;
+ return;
+ }
+ }
+
+ display.clearHelp(false) ;
+ }
+ }) ;
+
+ }
+
+ @Override
+ public void showHelpTopic(QualifiedName selectedItem)
+ {
+ server_.showHelpTopic(selectedItem.name, selectedItem.pkgName) ;
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return false;
+ }
+
+ protected final HelpServerOperations server_ ;
+ }
+
+ static class ParameterStrategy extends FunctionStrategy
+ {
+ public ParameterStrategy(HelpServerOperations server, String functionName)
+ {
+ super(server) ;
+ functionName_ = functionName ;
+ }
+
+ @Override
+ public void showHelp(QualifiedName qname,
+ final CompletionPopupDisplay display)
+ {
+ String selectedItem = qname.name ;
+
+ if (selectedItem.endsWith("="))
+ {
+ assert StringUtil.isNullOrEmpty(qname.pkgName)
+ : "Completion parameter had a package name!? " +
+ qname.pkgName + "::" + qname.name;
+ selectedItem = selectedItem.substring(0, selectedItem.length() - 1) ;
+
+ parameter_ = selectedItem ;
+ if (helpInfo_ != null)
+ {
+ doShow(display) ;
+ }
+ else
+ {
+ server_.getHelp(functionName_, null, 0,
+ new ServerRequestCallback<HelpInfo>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ display.clearHelp(false) ;
+ }
+
+ @Override
+ public void onResponseReceived(HelpInfo response)
+ {
+ if (response != null)
+ helpInfo_ = response.parse(functionName_) ;
+ else
+ helpInfo_ = null;
+
+ if (helpInfo_ != null)
+ doShow(display) ;
+ else
+ display.clearHelp(false);
+ }
+ }) ;
+ }
+ }
+ else
+ {
+ super.showHelp(qname, display) ;
+ }
+ }
+
+ @Override
+ public void showHelpTopic(QualifiedName selectedItem)
+ {
+ server_.showHelpTopic(functionName_, null) ;
+ }
+
+ private void doShow(CompletionPopupDisplay display)
+ {
+ assert helpInfo_ != null && parameter_ != null && display != null ;
+ String desc = helpInfo_.getArgs().get(parameter_) ;
+ if (desc == null)
+ {
+ display.clearHelp(false) ;
+ }
+ else
+ {
+ display.displayParameterHelp(helpInfo_, parameter_) ;
+ }
+ }
+
+ private final String functionName_ ;
+
+ private HelpInfo.ParsedInfo helpInfo_ ;
+ private String parameter_ ;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HistoryCompletionManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HistoryCompletionManager.java
new file mode 100644
index 0000000..77e064e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/HistoryCompletionManager.java
@@ -0,0 +1,198 @@
+/*
+ * HistoryCompletionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.workbench.views.console.shell.KeyDownPreviewHandler;
+import org.rstudio.studio.client.workbench.views.console.shell.KeyPressPreviewHandler;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryServerOperations;
+
+public class HistoryCompletionManager implements KeyDownPreviewHandler,
+ KeyPressPreviewHandler
+{
+ public HistoryCompletionManager(InputEditorDisplay input,
+ HistoryServerOperations server)
+ {
+ input_ = input;
+ server_ = server;
+ }
+
+ public boolean previewKeyDown(NativeEvent event)
+ {
+ if (popup_ == null)
+ {
+ if (event.getKeyCode() == KeyCodes.KEY_UP
+ && (event.getCtrlKey() || event.getMetaKey()))
+ {
+ beginSuggest();
+ return true;
+ }
+ }
+ else
+ {
+ switch (event.getKeyCode())
+ {
+ case KeyCodes.KEY_SHIFT:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_ALT:
+ return false ; // bare modifiers should do nothing
+ }
+
+ if (event.getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ dismiss();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ input_.setText(popup_.getSelectedValue());
+ dismiss();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_UP)
+ {
+ popup_.selectPrev();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_DOWN)
+ {
+ popup_.selectNext();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_UP)
+ return popup_.selectPrev() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_DOWN)
+ return popup_.selectNext() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEUP)
+ return popup_.selectPrevPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEDOWN)
+ return popup_.selectNextPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_HOME)
+ return popup_.selectFirst() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_END)
+ return popup_.selectLast() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_LEFT)
+ {
+ dismiss();
+ return true ;
+ }
+
+ dismiss();
+ return false;
+ }
+
+ return false;
+ }
+
+ private void dismiss()
+ {
+ if (popup_ != null)
+ {
+ popup_.hide();
+ popup_ = null;
+ }
+ }
+
+ private void beginSuggest()
+ {
+ historyRequestInvalidation_.invalidate();
+ final Token token = historyRequestInvalidation_.getInvalidationToken();
+
+ String value = input_.getText();
+ server_.searchHistoryArchiveByPrefix(
+ value, 20, true,
+ new SimpleRequestCallback<RpcObjectList<HistoryEntry>>()
+ {
+ @Override
+ public void onResponseReceived(RpcObjectList<HistoryEntry> resp)
+ {
+ if (token.isInvalid())
+ return;
+
+ if (resp.length() == 0)
+ {
+ popup_ = new CompletionListPopupPanel(new String[0]);
+ popup_.setText("(No matching commands)");
+ }
+ else
+ {
+ String[] entries = new String[resp.length()];
+ for (int i = 0; i < entries.length; i++)
+ entries[i] = resp.get(entries.length - i - 1).getCommand();
+ popup_ = new CompletionListPopupPanel(entries);
+ }
+
+ popup_.setMaxWidth(input_.getBounds().getWidth());
+ popup_.setPopupPositionAndShow(new PositionCallback()
+ {
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ Rectangle bounds = input_.getBounds();
+
+ int top = bounds.getTop() - offsetHeight;
+ if (top < 20)
+ top = bounds.getBottom();
+
+ popup_.selectLast();
+ popup_.setPopupPosition(bounds.getLeft() - 6, top);
+ }
+ });
+
+ popup_.addSelectionCommitHandler(new SelectionCommitHandler<String>()
+ {
+ public void onSelectionCommit(SelectionCommitEvent<String> e)
+ {
+ input_.setText(e.getSelectedItem());
+ dismiss();
+ }
+ });
+
+ popup_.addCloseHandler(new CloseHandler<PopupPanel>() {
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ popup_ = null;
+ }
+
+ });
+ }
+ });
+ }
+
+ public boolean previewKeyPress(char charCode)
+ {
+ return false;
+ }
+
+ private CompletionListPopupPanel popup_;
+ private final InputEditorDisplay input_;
+ private final HistoryServerOperations server_;
+ private final Invalidation historyRequestInvalidation_ = new Invalidation();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/NullCompletionManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/NullCompletionManager.java
new file mode 100644
index 0000000..c8efbeb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/NullCompletionManager.java
@@ -0,0 +1,46 @@
+/*
+ * NullCompletionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.dom.client.NativeEvent;
+
+public class NullCompletionManager implements CompletionManager
+{
+ public void close()
+ {
+ }
+
+ public void goToHelp()
+ {
+ }
+
+ public void goToFunctionDefinition()
+ {
+ }
+
+ public void codeCompletion()
+ {
+ }
+
+ public boolean previewKeyDown(NativeEvent event)
+ {
+ return false;
+ }
+
+ public boolean previewKeyPress(char charCode)
+ {
+ return false;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/PopupPositioner.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/PopupPositioner.java
new file mode 100644
index 0000000..0aaa60e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/PopupPositioner.java
@@ -0,0 +1,49 @@
+/*
+ * PopupPositioner.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import org.rstudio.core.client.Rectangle;
+
+public class PopupPositioner implements PositionCallback
+{
+ private Rectangle cursorBounds_ ;
+ private CompletionPopupDisplay popup_ ;
+
+ public PopupPositioner(Rectangle cursorBounds, CompletionPopupDisplay popup)
+ {
+ this.cursorBounds_ = cursorBounds ;
+ popup_ = popup ;
+ }
+
+ public void setPosition(int popupWidth, int popupHeight)
+ {
+ if (cursorBounds_ == null)
+ {
+ assert false : "Positioning popup but no cursor bounds available" ;
+ return;
+ }
+
+ int windowBottom = Window.getScrollTop() + Window.getClientHeight() ;
+ int cursorBottom = cursorBounds_.getBottom() ;
+
+ if (windowBottom - cursorBottom >= popupHeight)
+ popup_.setPopupPosition(cursorBounds_.getLeft(), cursorBottom) ;
+ else
+ popup_.setPopupPosition(cursorBounds_.getLeft(),
+ cursorBounds_.getTop() - popupHeight) ;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/RCompletionManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/RCompletionManager.java
new file mode 100644
index 0000000..780c1d7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/assist/RCompletionManager.java
@@ -0,0 +1,720 @@
+/*
+ * RCompletionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.assist;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.codesearch.model.FunctionDefinition;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionRequester.CompletionResult;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionRequester.QualifiedName;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorLineWithCursorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorUtil;
+import org.rstudio.studio.client.workbench.views.source.editors.text.NavigableSourceEditor;
+import org.rstudio.studio.client.workbench.views.source.events.CodeBrowserNavigationEvent;
+import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import java.util.ArrayList;
+
+
+public class RCompletionManager implements CompletionManager
+{
+ // globally suppress F1 and F2 so no default browser behavior takes those
+ // keystrokes (e.g. Help in Chrome)
+ static
+ {
+ Event.addNativePreviewHandler(new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ int keyCode = event.getNativeEvent().getKeyCode();
+ if ((keyCode == 112 || keyCode == 113) &&
+ KeyboardShortcut.NONE ==
+ KeyboardShortcut.getModifierValue(event.getNativeEvent()))
+ {
+ event.getNativeEvent().preventDefault();
+ }
+ }
+ }
+ });
+ }
+
+ public RCompletionManager(InputEditorDisplay input,
+ NavigableSourceEditor navigableSourceEditor,
+ CompletionPopupDisplay popup,
+ CodeToolsServerOperations server,
+ InitCompletionFilter initFilter,
+ RnwCompletionContext rnwContext)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ input_ = input ;
+ navigableSourceEditor_ = navigableSourceEditor;
+ popup_ = popup ;
+ server_ = server ;
+ requester_ = new CompletionRequester(server_, rnwContext);
+ initFilter_ = initFilter ;
+ rnwContext_ = rnwContext;
+
+ input_.addBlurHandler(new BlurHandler() {
+ public void onBlur(BlurEvent event)
+ {
+ if (!ignoreNextInputBlur_)
+ invalidatePendingRequests() ;
+ ignoreNextInputBlur_ = false ;
+ }
+ }) ;
+
+ input_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ invalidatePendingRequests();
+ }
+ });
+
+ popup_.addSelectionCommitHandler(new SelectionCommitHandler<QualifiedName>() {
+ public void onSelectionCommit(SelectionCommitEvent<QualifiedName> event)
+ {
+ assert context_ != null : "onSelection called but handler is null" ;
+ if (context_ != null)
+ context_.onSelection(event.getSelectedItem()) ;
+ }
+ }) ;
+
+ popup_.addSelectionHandler(new SelectionHandler<QualifiedName>() {
+ public void onSelection(SelectionEvent<QualifiedName> event)
+ {
+ popup_.clearHelp(true) ;
+ context_.showHelp(event.getSelectedItem()) ;
+ }
+ }) ;
+
+ popup_.addMouseDownHandler(new MouseDownHandler() {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ ignoreNextInputBlur_ = true ;
+ }
+ }) ;
+ }
+
+ @Inject
+ public void initialize(GlobalDisplay globalDisplay,
+ FileTypeRegistry fileTypeRegistry,
+ EventBus eventBus)
+ {
+ globalDisplay_ = globalDisplay;
+ fileTypeRegistry_ = fileTypeRegistry;
+ eventBus_ = eventBus;
+ }
+
+ public void close()
+ {
+ popup_.hide();
+ }
+
+ public void codeCompletion()
+ {
+ if (initFilter_ == null || initFilter_.shouldComplete(null))
+ beginSuggest(true, false);
+ }
+
+ public void goToHelp()
+ {
+ InputEditorLineWithCursorPosition linePos =
+ InputEditorUtil.getLineWithCursorPosition(input_);
+
+ server_.getHelpAtCursor(
+ linePos.getLine(), linePos.getPosition(),
+ new SimpleRequestCallback<Void>("Help"));
+ }
+
+ public void goToFunctionDefinition()
+ {
+ // determine current line and cursor position
+ InputEditorLineWithCursorPosition lineWithPos =
+ InputEditorUtil.getLineWithCursorPosition(input_);
+
+ // lookup function definition at this location
+
+ // delayed progress indicator
+ final GlobalProgressDelayer progress = new GlobalProgressDelayer(
+ globalDisplay_, 1000, "Searching for function definition...");
+
+ server_.getFunctionDefinition(
+ lineWithPos.getLine(),
+ lineWithPos.getPosition(),
+ new ServerRequestCallback<FunctionDefinition>() {
+ @Override
+ public void onResponseReceived(FunctionDefinition def)
+ {
+ // dismiss progress
+ progress.dismiss();
+
+ // if we got a hit
+ if (def.getFunctionName() != null)
+ {
+ // search locally if a function navigator was provided
+ if (navigableSourceEditor_ != null)
+ {
+ // try to search for the function locally
+ SourcePosition position =
+ navigableSourceEditor_.findFunctionPositionFromCursor(
+ def.getFunctionName());
+ if (position != null)
+ {
+ navigableSourceEditor_.navigateToPosition(position,
+ true);
+ return; // we're done
+ }
+
+ }
+
+ // if we didn't satisfy the request using a function
+ // navigator and we got a file back from the server then
+ // navigate to the file/loc
+ if (def.getFile() != null)
+ {
+ fileTypeRegistry_.editFile(def.getFile(),
+ def.getPosition());
+ }
+
+ // if we didn't get a file back see if we got a
+ // search path definition
+ else if (def.getSearchPathFunctionDefinition() != null)
+ {
+ eventBus_.fireEvent(new CodeBrowserNavigationEvent(
+ def.getSearchPathFunctionDefinition()));
+
+ }
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progress.dismiss();
+
+ globalDisplay_.showErrorMessage("Error Searching for Function",
+ error.getUserMessage());
+ }
+ });
+ }
+
+
+ public boolean previewKeyDown(NativeEvent event)
+ {
+ /**
+ * KEYS THAT MATTER
+ *
+ * When popup not showing:
+ * Tab - attempt completion (handled in Console.java)
+ *
+ * When popup showing:
+ * Esc - dismiss popup
+ * Enter/Tab/Right-arrow - accept current selection
+ * Up-arrow/Down-arrow - change selected item
+ * Left-arrow - dismiss popup
+ * [identifier] - narrow suggestions--or if we're lame, just dismiss
+ * All others - dismiss popup
+ */
+
+ int modifier = KeyboardShortcut.getModifierValue(event);
+
+ if (!popup_.isShowing())
+ {
+ if ((event.getKeyCode() == KeyCodes.KEY_TAB && modifier == KeyboardShortcut.NONE)
+ || (event.getKeyCode() == ' ' && modifier == KeyboardShortcut.CTRL))
+ {
+ if (initFilter_ == null || initFilter_.shouldComplete(event))
+ {
+ return beginSuggest(true, false) ;
+ }
+ }
+ else if (event.getKeyCode() == 112 // F1
+ && modifier == KeyboardShortcut.NONE)
+ {
+ goToHelp();
+ }
+ else if (event.getKeyCode() == 113 // F2
+ && modifier == KeyboardShortcut.NONE)
+ {
+ goToFunctionDefinition();
+ }
+ }
+ else
+ {
+ switch (event.getKeyCode())
+ {
+ case KeyCodes.KEY_SHIFT:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_ALT:
+ return false ; // bare modifiers should do nothing
+ }
+
+ if (modifier == KeyboardShortcut.NONE)
+ {
+ if (event.getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ invalidatePendingRequests() ;
+ return true ;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_TAB
+ || event.getKeyCode() == KeyCodes.KEY_ENTER
+ || event.getKeyCode() == KeyCodes.KEY_RIGHT)
+ {
+ QualifiedName value = popup_.getSelectedValue() ;
+ if (value != null)
+ {
+ context_.onSelection(value) ;
+ return true ;
+ }
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_UP)
+ return popup_.selectPrev() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_DOWN)
+ return popup_.selectNext() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEUP)
+ return popup_.selectPrevPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEDOWN)
+ return popup_.selectNextPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_HOME)
+ return popup_.selectFirst() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_END)
+ return popup_.selectLast() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_LEFT)
+ {
+ invalidatePendingRequests() ;
+ return true ;
+ }
+ else if (event.getKeyCode() == 112) // F1
+ {
+ context_.showHelpTopic() ;
+ return true ;
+ }
+ else if (event.getKeyCode() == 113) // F2
+ {
+ goToFunctionDefinition();
+ return true;
+ }
+ }
+
+ if (isIdentifierKey(event))
+ return false ;
+
+ invalidatePendingRequests() ;
+ return false ;
+ }
+
+ return false ;
+ }
+
+ public boolean previewKeyPress(char c)
+ {
+ if (popup_.isShowing())
+ {
+ if ((c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9')
+ || c == '.' || c == '_'
+ || c == ':')
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ beginSuggest(false, false) ;
+ }
+ });
+ }
+ }
+ else
+ {
+ if ((c == '@' && isRoxygenTagValidHere()) || isSweaveCompletion(c))
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ beginSuggest(true, true) ;
+ }
+ });
+ }
+ else if (!input_.isSelectionCollapsed())
+ {
+ switch(c)
+ {
+ case '"':
+ case '\'':
+ encloseSelection(c, c);
+ return true;
+ case '(':
+ encloseSelection('(', ')');
+ return true;
+ case '{':
+ encloseSelection('{', '}');
+ return true;
+ case '[':
+ encloseSelection('[', ']');
+ return true;
+ }
+ }
+ }
+ return false ;
+ }
+
+ private void encloseSelection(char beginChar, char endChar)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(beginChar);
+ builder.append(input_.getSelectionValue());
+ builder.append(endChar);
+ input_.replaceSelection(builder.toString(), true);
+ }
+
+ private boolean isRoxygenTagValidHere()
+ {
+ if (input_.getText().matches("\\s*#+'.*"))
+ {
+ String linePart = input_.getText().substring(0, input_.getSelection().getStart().getPosition());
+ if (linePart.matches("\\s*#+'\\s*"))
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSweaveCompletion(char c)
+ {
+ if (rnwContext_ == null || (c != ',' && c != ' ' && c != '='))
+ return false;
+
+ int optionsStart = rnwContext_.getRnwOptionsStart(
+ input_.getText(),
+ input_.getSelection().getStart().getPosition());
+
+ if (optionsStart < 0)
+ {
+ return false;
+ }
+
+ String linePart = input_.getText().substring(
+ optionsStart,
+ input_.getSelection().getStart().getPosition());
+
+ return c != ' ' || linePart.matches(".*,\\s*");
+ }
+
+ private static boolean isIdentifierKey(NativeEvent event)
+ {
+ if (event.getAltKey()
+ || event.getCtrlKey()
+ || event.getMetaKey())
+ {
+ return false ;
+ }
+
+ int keyCode = event.getKeyCode() ;
+ if (keyCode >= 'a' && keyCode <= 'z')
+ return true ;
+ if (keyCode >= 'A' && keyCode <= 'Z')
+ return true ;
+ if (keyCode == 189 && event.getShiftKey()) // underscore
+ return true ;
+ if (keyCode == 186 && event.getShiftKey()) // colon
+ return true ;
+
+ if (event.getShiftKey())
+ return false ;
+
+ if (keyCode >= '0' && keyCode <= '9')
+ return true ;
+ if (keyCode == 190) // period
+ return true ;
+
+ return false ;
+ }
+
+ private void invalidatePendingRequests()
+ {
+ invalidatePendingRequests(true) ;
+ }
+
+ private void invalidatePendingRequests(boolean flushCache)
+ {
+ invalidation_.invalidate();
+ if (popup_.isShowing())
+ popup_.hide() ;
+ if (flushCache)
+ requester_.flushCache() ;
+ }
+
+ /**
+ * If false, the suggest operation was aborted
+ */
+ private boolean beginSuggest(boolean flushCache, boolean implicit)
+ {
+ if (!input_.isSelectionCollapsed())
+ return false ;
+
+ invalidatePendingRequests(flushCache) ;
+
+ String line = input_.getText() ;
+ if (!input_.hasSelection())
+ {
+ Debug.log("Cursor wasn't in input box or was in subelement");
+ return false ;
+ }
+ InputEditorSelection selection = input_.getSelection() ;
+ if (selection == null)
+ return false;
+
+ String linePart = line.substring(0, selection.getStart().getPosition());
+
+ if (line.matches("\\s*#.*") && !linePart.matches("\\s*#+'\\s*[^\\s].*"))
+ {
+ // No completion inside comments (except Roxygen). For the Roxygen
+ // case, only do completion if we're past the first non-whitespace
+ // character (to allow for easy indenting).
+ return false;
+ }
+
+ boolean canAutoAccept = flushCache;
+ context_ = new CompletionRequestContext(invalidation_.getInvalidationToken(),
+ selection,
+ canAutoAccept) ;
+ requester_.getCompletions(line,
+ selection.getStart().getPosition(),
+ implicit,
+ context_);
+
+ return true ;
+ }
+
+ /**
+ * It's important that we create a new instance of this each time.
+ * It maintains state that is associated with a completion request.
+ */
+ private final class CompletionRequestContext extends
+ ServerRequestCallback<CompletionResult>
+ {
+ public CompletionRequestContext(Invalidation.Token token,
+ InputEditorSelection selection,
+ boolean canAutoAccept)
+ {
+ invalidationToken_ = token ;
+ selection_ = selection ;
+ canAutoAccept_ = canAutoAccept;
+ }
+
+ public void showHelp(QualifiedName selectedItem)
+ {
+ helpStrategy_.showHelp(selectedItem, popup_) ;
+ }
+
+ public void showHelpTopic()
+ {
+ helpStrategy_.showHelpTopic(popup_.getSelectedValue()) ;
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (invalidationToken_.isInvalid())
+ return ;
+
+ RCompletionManager.this.popup_.showErrorMessage(
+ error.getUserMessage(),
+ new PopupPositioner(input_.getCursorBounds(), popup_)) ;
+ }
+
+ @Override
+ public void onResponseReceived(CompletionResult completions)
+ {
+ if (invalidationToken_.isInvalid())
+ return ;
+
+ final QualifiedName[] results
+ = completions.completions.toArray(new QualifiedName[0]) ;
+
+ if (results.length == 0)
+ {
+ popup_.showErrorMessage(
+ "(No matches)",
+ new PopupPositioner(input_.getCursorBounds(), popup_)) ;
+ return ;
+ }
+
+ initializeHelpStrategy(completions) ;
+
+ // Move range to beginning of token; we want to place the popup there.
+ final String token = completions.token ;
+
+ Rectangle rect = input_.getPositionBounds(
+ selection_.getStart().movePosition(-token.length(), true));
+
+ token_ = token ;
+ suggestOnAccept_ = completions.suggestOnAccept;
+
+ if (results.length == 1
+ && canAutoAccept_
+ && StringUtil.isNullOrEmpty(results[0].pkgName))
+ {
+ onSelection(results[0]);
+ }
+ else
+ {
+ if (results.length == 1 && canAutoAccept_)
+ applyValue(results[0].name);
+
+ popup_.showCompletionValues(
+ results,
+ new PopupPositioner(rect, popup_),
+ !helpStrategy_.isNull()) ;
+ }
+ }
+
+ private void initializeHelpStrategy(CompletionResult completions)
+ {
+ if (completions.guessedFunctionName != null)
+ {
+ helpStrategy_ = HelpStrategy.createParameterStrategy(
+ server_, completions.guessedFunctionName) ;
+ return;
+ }
+
+ boolean anyPackages = false;
+ ArrayList<QualifiedName> qnames = completions.completions;
+ for (QualifiedName qname : qnames)
+ {
+ if (!StringUtil.isNullOrEmpty(qname.pkgName))
+ anyPackages = true;
+ }
+
+ if (anyPackages)
+ helpStrategy_ = HelpStrategy.createFunctionStrategy(server_) ;
+ else
+ helpStrategy_ = HelpStrategy.createNullStrategy();
+ }
+
+ private void onSelection(QualifiedName qname)
+ {
+ final String value = qname.name ;
+
+ if (invalidationToken_.isInvalid())
+ return ;
+
+ popup_.hide() ;
+ requester_.flushCache() ;
+
+ if (value == null)
+ {
+ assert false : "Selected comp value is null" ;
+ return ;
+ }
+
+ applyValue(value);
+
+ if (suggestOnAccept_)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ beginSuggest(true, true);
+ }
+ });
+ }
+ }
+
+ private void applyValue(final String value)
+ {
+ // Move range to beginning of token
+ input_.setFocus(true) ;
+ input_.setSelection(new InputEditorSelection(
+ selection_.getStart().movePosition(-token_.length(), true),
+ input_.getSelection().getEnd()));
+
+ // Replace the token with the full completion
+ input_.replaceSelection(value, true) ;
+
+ /* In some cases, applyValue can be called more than once
+ * as part of the same completion instance--specifically,
+ * if there's only one completion candidate and it is in
+ * a package. To make sure that the selection movement
+ * logic works the second time, we need to reset the
+ * selection.
+ */
+ token_ = value;
+ selection_ = input_.getSelection();
+ }
+
+ private final Invalidation.Token invalidationToken_ ;
+ private InputEditorSelection selection_ ;
+ private final boolean canAutoAccept_;
+ private HelpStrategy helpStrategy_ ;
+ private boolean suggestOnAccept_;
+ }
+
+ private GlobalDisplay globalDisplay_;
+ private FileTypeRegistry fileTypeRegistry_;
+ private EventBus eventBus_;
+
+ private final CodeToolsServerOperations server_;
+ private final InputEditorDisplay input_ ;
+ private final NavigableSourceEditor navigableSourceEditor_;
+ private final CompletionPopupDisplay popup_ ;
+ private final CompletionRequester requester_ ;
+ private final InitCompletionFilter initFilter_ ;
+ // Prevents completion popup from being dismissed when you merely
+ // click on it to scroll.
+ private boolean ignoreNextInputBlur_ = false;
+ private String token_ ;
+
+ private final Invalidation invalidation_ = new Invalidation();
+ private CompletionRequestContext context_ ;
+ private final RnwCompletionContext rnwContext_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorDisplay.java
new file mode 100644
index 0000000..5a640e6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorDisplay.java
@@ -0,0 +1,58 @@
+/*
+ * InputEditorDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.editor;
+
+import com.google.gwt.event.dom.client.HasAllFocusHandlers;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.ui.HasText;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+public interface InputEditorDisplay extends HasAllFocusHandlers,
+ HasClickHandlers,
+ HasText
+{
+ boolean hasSelection() ;
+ InputEditorSelection getSelection() ;
+ void setSelection(InputEditorSelection selection) ;
+ String getSelectionValue();
+ Rectangle getCursorBounds() ;
+ Rectangle getPositionBounds(InputEditorPosition position);
+ Rectangle getBounds() ;
+ void setFocus(boolean focused) ;
+ boolean isFocused();
+ /**
+ * @param value New value
+ * @return Original value
+ */
+ String replaceSelection(String value, boolean collapseSelection) ;
+ boolean isSelectionCollapsed() ;
+ void clear() ;
+
+ void insertCode(String code);
+
+ void collapseSelection(boolean collapseToStart);
+
+ InputEditorSelection getStart();
+ InputEditorSelection getEnd();
+
+ int getCurrentLineNum();
+ int getCurrentLineCount();
+
+ boolean isCursorAtEnd();
+
+ Position getCursorPosition();
+ String getLanguageMode(Position position);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorLineWithCursorPosition.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorLineWithCursorPosition.java
new file mode 100644
index 0000000..c157a8e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorLineWithCursorPosition.java
@@ -0,0 +1,37 @@
+/*
+ * InputEditorLineWithCursorPosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.editor;
+
+public class InputEditorLineWithCursorPosition
+{
+ public InputEditorLineWithCursorPosition(String line, int position)
+ {
+ line_ = line;
+ position_ = position;
+ }
+
+ public String getLine()
+ {
+ return line_;
+ }
+
+ public int getPosition()
+ {
+ return position_;
+ }
+
+ private final String line_;
+ private final int position_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorPosition.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorPosition.java
new file mode 100644
index 0000000..0adb2fe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorPosition.java
@@ -0,0 +1,92 @@
+/*
+ * InputEditorPosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.editor;
+
+public abstract class InputEditorPosition implements Comparable<InputEditorPosition>
+{
+ public InputEditorPosition(Object line, int position)
+ {
+ line_ = line;
+ position_ = position;
+ }
+
+ public Object getLine()
+ {
+ return line_;
+ }
+
+ public int getPosition()
+ {
+ return position_;
+ }
+
+ public int compareTo(InputEditorPosition o)
+ {
+ if (o == null)
+ return 1;
+
+ int result = compareLineTo(o.getLine());
+ if (result == 0)
+ result = getPosition() - o.getPosition();
+
+ return result;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hashCode = 0;
+ if (line_ != null)
+ hashCode = line_.hashCode() * 31;
+ hashCode += position_;
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ return false;
+ if (!(obj instanceof InputEditorPosition))
+ return false;
+ return compareTo((InputEditorPosition) obj) == 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Position " + getPosition();
+ }
+
+ protected abstract int compareLineTo(Object other);
+
+ public abstract InputEditorPosition movePosition(
+ int position, boolean relative);
+
+ public abstract InputEditorPosition moveToNextLine();
+ public abstract InputEditorPosition moveToPreviousLine();
+
+ public abstract int getLineLength();
+
+ public abstract InputEditorPosition skipEmptyLines(
+ boolean upwards,
+ InputEditorPosition boundary);
+
+ public abstract InputEditorPosition growToIncludeLines(String pattern,
+ boolean upwards);
+
+ private final Object line_;
+ private final int position_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorSelection.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorSelection.java
new file mode 100644
index 0000000..26bf3bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorSelection.java
@@ -0,0 +1,99 @@
+/*
+ * InputEditorSelection.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.editor;
+
+public final class InputEditorSelection
+ implements Comparable<InputEditorSelection>
+{
+ public InputEditorSelection(InputEditorPosition start,
+ InputEditorPosition end)
+ {
+ start_ = start;
+ end_ = end;
+ }
+
+ public InputEditorSelection(InputEditorPosition at)
+ {
+ start_ = at;
+ end_ = at;
+ }
+
+ public InputEditorPosition getStart()
+ {
+ return start_;
+ }
+
+ public InputEditorPosition getEnd()
+ {
+ return end_;
+ }
+
+ public int compareTo(InputEditorSelection o)
+ {
+ if (o == null)
+ return 1 ;
+
+ int result = getStart().compareTo(o.getStart());
+ if (result == 0)
+ result = getEnd().compareTo(o.getEnd());
+ return result;
+ }
+
+ public boolean isEmpty()
+ {
+ return start_.equals(end_);
+ }
+
+ public InputEditorSelection extendToLineStart()
+ {
+ return new InputEditorSelection(
+ start_.movePosition(0, false),
+ end_);
+ }
+
+ public InputEditorSelection extendToLineEnd()
+ {
+ return new InputEditorSelection(
+ start_,
+ end_.movePosition(end_.getLineLength(), false));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Start: " + (start_ == null ? "null" : start_) +
+ ", End: " + (end_ == null ? "null" : end_);
+ }
+
+ public InputEditorSelection shrinkToNonEmptyLines()
+ {
+ InputEditorPosition newEnd = end_.skipEmptyLines(true, start_);
+ if (newEnd == null || newEnd.compareTo(start_) <= 0)
+ return new InputEditorSelection(start_, start_);
+ InputEditorPosition newStart = start_.skipEmptyLines(false, end_);
+ assert newStart != null;
+ return new InputEditorSelection(newStart, newEnd);
+ }
+
+ public InputEditorSelection growToIncludeLines(String pattern)
+ {
+ return new InputEditorSelection(
+ start_.growToIncludeLines(pattern, true),
+ end_.growToIncludeLines(pattern, false));
+ }
+
+ private final InputEditorPosition start_;
+ private final InputEditorPosition end_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorUtil.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorUtil.java
new file mode 100644
index 0000000..5cb0f96
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/editor/InputEditorUtil.java
@@ -0,0 +1,90 @@
+/*
+ * InputEditorUtil.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.editor;
+
+public class InputEditorUtil
+{
+ public static void yankAfterCursor(final InputEditorDisplay editor,
+ final boolean saveValue)
+ {
+ InputEditorSelection selection = editor.getSelection() ;
+ if (selection != null)
+ {
+ selection = selection.extendToLineEnd();
+
+ editor.setSelection(selection);
+
+ editor.setFocus(true) ;
+ String yanked = editor.replaceSelection("", true);
+ if (saveValue)
+ lastYanked = yanked;
+ }
+ }
+
+ public static void yankBeforeCursor(final InputEditorDisplay editor,
+ final boolean saveValue)
+ {
+ InputEditorSelection selection = editor.getSelection() ;
+ if (selection != null)
+ {
+ selection = selection.extendToLineStart();
+
+ editor.setSelection(selection);
+
+ editor.setFocus(true) ;
+ String yanked = editor.replaceSelection("", true);
+ if (saveValue)
+ lastYanked = yanked;
+ }
+ }
+
+ public static void pasteYanked(InputEditorDisplay editor)
+ {
+ if (lastYanked != null)
+ {
+ editor.replaceSelection(lastYanked, true);
+ }
+ }
+
+ public static InputEditorLineWithCursorPosition getLineWithCursorPosition(
+ InputEditorDisplay editor)
+ {
+ String line;
+ int pos;
+ if (editor.getSelection().isEmpty())
+ {
+ line = editor.getText();
+ pos = editor.getSelection().getStart().getPosition();
+ // Move pos to the right until we get to a break
+ for (; pos < line.length() && isRIdentifierChar(line.charAt(pos)); pos++)
+ {
+ }
+ }
+ else
+ {
+ line = editor.getSelectionValue();
+ pos = line.length();
+ }
+
+ return new InputEditorLineWithCursorPosition(line, pos);
+ }
+
+ private static boolean isRIdentifierChar(char ch)
+ {
+ return Character.isLetterOrDigit(ch) || ch == '.' || ch == '_';
+ }
+
+ private static String lastYanked;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImpl.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImpl.java
new file mode 100644
index 0000000..2d76a3a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImpl.java
@@ -0,0 +1,49 @@
+/*
+ * PlainTextEditorImpl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.impl;
+
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import org.rstudio.core.client.dom.ElementEx;
+
+public class PlainTextEditorImpl
+{
+ /**
+ * Webkit doesn't like empty editable SPANs--it is not possible to
+ * drive the focus into one. However, empty editable DIVs are fine.
+ *
+ * Firefox likes empty editable DIVs only slightly more--they put
+ * themselves about 0.5em too high and sometimes collapse their height
+ * to just a few pixels. However, a DIV that contains two SPANs--one to
+ * set the height with a zero-width space and the other being editable--
+ * works great.
+ *
+ * This method takes one or the other approach, and returns the actual
+ * contentEditable element.
+ */
+ public ElementEx setupTextContainer(Element element)
+ {
+ DOM.setElementPropertyBoolean(element, "contentEditable", true) ;
+ return (ElementEx) element;
+ }
+
+ public void relayFocusEvents(HasHandlers handlers)
+ {
+ }
+
+ public void poll()
+ {}
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplFirefox.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplFirefox.java
new file mode 100644
index 0000000..ed63ba5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplFirefox.java
@@ -0,0 +1,120 @@
+/*
+ * PlainTextEditorImplFirefox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Text;
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.ElementEx;
+
+public class PlainTextEditorImplFirefox extends PlainTextEditorImpl
+{
+ /**
+ * @see org.rstudio.studio.client.workbench.views.console.shell.impl.PlainTextEditorImpl#setupTextContainer(Element)
+ */
+ @Override
+ public ElementEx setupTextContainer(Element element)
+ {
+ ElementEx zwspSpan = (ElementEx) DOM.createSpan() ;
+ zwspSpan.setInnerText("\u200B") ;
+
+ ElementEx textContainer = (ElementEx) DOM.createDiv() ;
+ textContainer.getStyle().setDisplay(Display.INLINE);
+
+ element.appendChild(zwspSpan) ;
+ element.appendChild(textContainer) ;
+
+ DOM.setElementPropertyBoolean(textContainer, "contentEditable", true) ;
+
+ textContainer_ = textContainer;
+ return textContainer_;
+ }
+
+ @Override
+ public void relayFocusEvents(HasHandlers handlers)
+ {
+ addDomListener(textContainer_, "focus", handlers);
+ addDomListener(textContainer_, "blur", handlers);
+ }
+
+ private native static JavaScriptObject addDomListener(
+ com.google.gwt.dom.client.Element element,
+ String eventName,
+ HasHandlers hasHandlers) /*-{
+ var listener = $entry(function(e) {
+ @com.google.gwt.event.dom.client.DomEvent::fireNativeEvent(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/event/shared/HasHandlers;Lcom/google/gwt/dom/client/Element;)(e, hasHandlers, element);
+ });
+ element.addEventListener(eventName, listener, false);
+ }-*/;
+
+ @Override
+ public void poll()
+ {
+ // This doesn't work all that well
+ /*
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ manageZwsp();
+ }
+ });
+ */
+ }
+
+ private void manageZwsp()
+ {
+ String val = textContainer_.getInnerText();
+ if (val.length() == 0)
+ {
+ textContainer_.appendChild(Document.get().createTextNode("\u200B"));
+ }
+ else if (val.length() > 1 && val.indexOf('\u200B') >= 0)
+ {
+ stripZwsp(textContainer_);
+ }
+ }
+
+ private void stripZwsp(Node node)
+ {
+ if (node.getNodeType() == Node.TEXT_NODE)
+ {
+ while (true)
+ {
+ String text = node.getNodeValue();
+ int index = text.indexOf('\u200B');
+ if (index >= 0)
+ DomUtils.deleteTextData((Text) node, index, 1);
+ else
+ break;
+ }
+ }
+ else if (node.getNodeType() == Node.ELEMENT_NODE)
+ {
+ NodeList<Node> nodes = node.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++)
+ stripZwsp(nodes.getItem(i));
+ }
+ }
+
+ private ElementEx textContainer_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplIE8.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplIE8.java
new file mode 100644
index 0000000..a9c0bea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/console/shell/impl/PlainTextEditorImplIE8.java
@@ -0,0 +1,20 @@
+/*
+ * PlainTextEditorImplIE8.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.console.shell.impl;
+
+
+public class PlainTextEditorImplIE8 extends PlainTextEditorImpl
+{
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/Data.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/Data.java
new file mode 100644
index 0000000..4d25ee7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/Data.java
@@ -0,0 +1,45 @@
+/*
+ * Data.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data;
+
+import com.google.inject.Inject;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.data.model.DataServerOperations;
+
+public class Data extends BasePresenter
+{
+ public interface Display extends WorkbenchView
+ {
+
+ }
+
+ @Inject
+ public Data(Display display,
+ EventBus eventBus,
+ DataServerOperations server)
+ {
+ super(display);
+ display_ = display;
+ }
+
+
+
+ @SuppressWarnings("unused")
+ private final Display display_ ;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataPane.java
new file mode 100644
index 0000000..8450267
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataPane.java
@@ -0,0 +1,49 @@
+/*
+ * DataPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data;
+
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.HorizontalCenterPanel;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+
+public class DataPane extends WorkbenchPane implements Data.Display
+{
+ @Inject
+ public DataPane()
+ {
+ super("Data");
+ ensureWidget();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ Label label = new Label("Under Construction");
+ label.getElement().getStyle().setColor("#888");
+ return new HorizontalCenterPanel(label, 100);
+ }
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataTab.java
new file mode 100644
index 0000000..a08eb42
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/DataTab.java
@@ -0,0 +1,30 @@
+/*
+ * DataTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data;
+
+import com.google.inject.Inject;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+
+public class DataTab extends DelayLoadWorkbenchTab<Data>
+{
+ public abstract static class Shim extends DelayLoadTabShim<Data, DataTab> {}
+
+ @Inject
+ public DataTab(Shim shim)
+ {
+ super("Data", shim);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataEvent.java
new file mode 100644
index 0000000..44e3084
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ViewDataEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.data.model.DataView;
+
+public class ViewDataEvent extends GwtEvent<ViewDataHandler>
+{
+ public static final GwtEvent.Type<ViewDataHandler> TYPE =
+ new GwtEvent.Type<ViewDataHandler>();
+
+ public ViewDataEvent(DataView dataView)
+ {
+ dataView_ = dataView;
+ }
+
+ public DataView getDataView()
+ {
+ return dataView_;
+ }
+
+ @Override
+ protected void dispatch(ViewDataHandler handler)
+ {
+ handler.onViewData(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ViewDataHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private DataView dataView_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataHandler.java
new file mode 100644
index 0000000..f15ba6e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/events/ViewDataHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ViewDataHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ViewDataHandler extends EventHandler
+{
+ void onViewData(ViewDataEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataServerOperations.java
new file mode 100644
index 0000000..2cabcf5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataServerOperations.java
@@ -0,0 +1,20 @@
+/*
+ * DataServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.data.model;
+
+public interface DataServerOperations
+{
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataView.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataView.java
new file mode 100644
index 0000000..6e1e997
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/data/model/DataView.java
@@ -0,0 +1,66 @@
+/*
+ * DataView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package
+org.rstudio.studio.client.workbench.views.data.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayNumber;
+import com.google.gwt.core.client.JsArrayString;
+
+public class DataView extends JavaScriptObject
+{
+ protected DataView()
+ {
+ }
+
+ public static class ColumnInfo extends JavaScriptObject
+ {
+ protected ColumnInfo()
+ {
+ }
+
+ public static final int STRING = 0;
+ public static final int NUMBER = 1;
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native int getType() /*-{
+ return this.type;
+ }-*/;
+ }
+
+ public final native String getTitle() /*-{
+ return this.title;
+ }-*/;
+
+ public final native JsArray<ColumnInfo> getColumnInfo() /*-{
+ return this.columnInfo;
+ }-*/;
+
+ public final native int getRowCount() /*-{
+ return this.rowCount;
+ }-*/;
+
+ public final native JsArrayString getStringColumn(int index) /*-{
+ return this.columns[index];
+ }-*/;
+
+ public final native JsArrayNumber getNumberColumn(int index) /*-{
+ return this.columns[index];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/Edit.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/Edit.java
new file mode 100644
index 0000000..23269da
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/Edit.java
@@ -0,0 +1,81 @@
+/*
+ * Edit.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.AsyncShim;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.edit.events.ShowEditorEvent;
+import org.rstudio.studio.client.workbench.views.edit.events.ShowEditorHandler;
+import org.rstudio.studio.client.workbench.views.edit.model.EditServerOperations;
+
+
+public class Edit implements ShowEditorHandler
+{
+ public abstract static class Shim extends AsyncShim<Edit>
+ implements ShowEditorHandler
+ {
+ public abstract void onShowEditor(ShowEditorEvent event);
+ }
+
+ public static interface Display
+ {
+ void show(String text,
+ boolean isRCode,
+ boolean lineWrapping,
+ ProgressOperationWithInput<String> operation);
+ }
+
+ @Inject
+ public Edit(Display view,
+ EditServerOperations server)
+ {
+ view_ = view ;
+ server_ = server;
+ }
+
+ public void onShowEditor(ShowEditorEvent event)
+ {
+ view_.show(event.getContent(),
+ event.isRCode(),
+ event.getLineWrapping(),
+ new ProgressOperationWithInput<String>() {
+
+ public void execute(final String input,
+ final ProgressIndicator progress)
+ {
+ if (input != null)
+ {
+ progress.onProgress("Saving...");
+ server_.editCompleted(input,
+ new VoidServerRequestCallback(progress));
+ }
+ else
+ {
+ progress.onProgress("Cancelling...");
+ server_.editCompleted(null,
+ new VoidServerRequestCallback(progress));
+ }
+ }
+ });
+
+ }
+
+ private final Display view_;
+ private final EditServerOperations server_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorEvent.java
new file mode 100644
index 0000000..545b383
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorEvent.java
@@ -0,0 +1,63 @@
+/*
+ * ShowEditorEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.events;
+
+import org.rstudio.studio.client.workbench.views.edit.model.ShowEditorData;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowEditorEvent extends GwtEvent<ShowEditorHandler>
+{
+ public static final GwtEvent.Type<ShowEditorHandler> TYPE =
+ new GwtEvent.Type<ShowEditorHandler>();
+
+ public ShowEditorEvent(ShowEditorData data)
+ {
+ content_ = data.getContent();
+ isRCode_ = data.isRCode();
+ lineWrapping_ = data.getLineWrapping();
+ }
+
+ public String getContent()
+ {
+ return content_;
+ }
+
+ public boolean isRCode()
+ {
+ return isRCode_;
+ }
+
+ public boolean getLineWrapping()
+ {
+ return lineWrapping_;
+ }
+
+ @Override
+ protected void dispatch(ShowEditorHandler handler)
+ {
+ handler.onShowEditor(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowEditorHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String content_;
+ private final boolean isRCode_;
+ private final boolean lineWrapping_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorHandler.java
new file mode 100644
index 0000000..2067518
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/events/ShowEditorHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowEditorHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowEditorHandler extends EventHandler
+{
+ void onShowEditor(ShowEditorEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/EditServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/EditServerOperations.java
new file mode 100644
index 0000000..f8c6bf3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/EditServerOperations.java
@@ -0,0 +1,24 @@
+/*
+ * EditServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+public interface EditServerOperations
+{
+ // indicate that modal editing of an object has completed successfully
+ void editCompleted(String text, ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/ShowEditorData.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/ShowEditorData.java
new file mode 100644
index 0000000..73e5ae0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/model/ShowEditorData.java
@@ -0,0 +1,36 @@
+/*
+ * ShowEditorData.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ShowEditorData extends JavaScriptObject
+{
+ protected ShowEditorData()
+ {
+ }
+
+ public final native String getContent() /*-{
+ return this.content;
+ }-*/;
+
+ public final native boolean isRCode() /*-{
+ return this.is_r_code;
+ }-*/;
+
+ public final native boolean getLineWrapping() /*-{
+ return this.line_wrapping;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditDialog.java
new file mode 100644
index 0000000..131c8b6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditDialog.java
@@ -0,0 +1,129 @@
+/*
+ * EditDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.dom.DomMetrics;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+
+public class EditDialog extends ModalDialogBase
+{
+ public EditDialog(String text,
+ boolean isRCode,
+ boolean lineWrapping,
+ final ProgressOperationWithInput<String> operation)
+ {
+ this("Edit",
+ "Save",
+ text,
+ isRCode,
+ lineWrapping,
+ new Size(0,0),
+ operation);
+ }
+
+ public EditDialog(String caption,
+ String saveCaption,
+ String text,
+ boolean isRCode,
+ boolean lineWrapping,
+ Size minimumSize,
+ final ProgressOperationWithInput<String> operation)
+ {
+ editor_ = new AceEditor();
+ setText(caption);
+ sourceText_ = text;
+ isRCode_ = isRCode;
+ lineWrapping_ = lineWrapping;
+ minimumSize_ = minimumSize;
+
+ final ProgressIndicator progressIndicator = addProgressIndicator();
+
+ ThemedButton saveButton = new ThemedButton(saveCaption,
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ operation.execute(editor_.getCode(), progressIndicator);
+ }
+ });
+ addButton(saveButton);
+
+ ThemedButton cancelButton = new ThemedButton("Cancel", new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ operation.execute(null, progressIndicator);
+ }
+ });
+ addCancelButton(cancelButton);
+
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // create widget and set size
+ Widget editWidget = editor_.getWidget();
+ Size size = DomMetrics.adjustedCodeElementSize(sourceText_, 25, 100);
+ if (!minimumSize_.isEmpty())
+ {
+ size = new Size(Math.max(size.width, minimumSize_.width),
+ Math.max(size.height, minimumSize_.height));
+ }
+ editWidget.setSize(size.width + "px", size.height + "px");
+
+ editor_.setCode(sourceText_, false);
+ if (isRCode_)
+ {
+ // NOTE: line wrapping is ignored for R code since it has its
+ // own localized setting for enabled/disable of line wrapping
+
+ editor_.setFileType(FileTypeRegistry.R);
+
+ setEscapeDisabled(true);
+ }
+ else
+ {
+ editor_.setUseWrapMode(lineWrapping_);
+ editor_.setShowLineNumbers(false);
+ }
+
+ // return the widget
+ SimplePanel panel = new SimplePanel();
+ panel.addStyleName("EditDialog");
+ panel.setSize(size.width + "px", size.height + "px");
+ panel.setWidget(editWidget);
+ return panel;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ editor_.focus();
+ }
+
+ private final String sourceText_ ;
+ private final boolean isRCode_;
+ private final boolean lineWrapping_;
+ private final AceEditor editor_ ;
+ private Size minimumSize_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditView.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditView.java
new file mode 100644
index 0000000..e0903ac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/edit/ui/EditView.java
@@ -0,0 +1,37 @@
+/*
+ * EditView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.edit.ui;
+
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.workbench.views.edit.Edit.Display;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+
+public class EditView implements Display
+{
+ public void show(final String text,
+ final boolean isRCode,
+ final boolean lineWrapping,
+ final ProgressOperationWithInput<String> operation)
+ {
+ AceEditor.load(new Command()
+ {
+ public void execute()
+ {
+ new EditDialog(text, isRCode, lineWrapping, operation).showModal();
+ }
+ });
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/ClearAllDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/ClearAllDialog.java
new file mode 100644
index 0000000..4754b66
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/ClearAllDialog.java
@@ -0,0 +1,128 @@
+/*
+ * ClearAllDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.MultiLineLabel;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.core.client.widget.images.MessageDialogImages;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+public class ClearAllDialog extends ModalDialogBase
+{
+ public ClearAllDialog(int numObjects,
+ final ProgressOperationWithInput<Boolean> okOperation)
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ numObjects_ = numObjects;
+
+ setText("Confirm Remove Objects");
+ setButtonAlignment(HasHorizontalAlignment.ALIGN_CENTER);
+
+ ThemedButton yesButton = new ThemedButton("Yes", new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (okOperation != null)
+ okOperation.execute(chkIncludeHidden_.getValue(), progress_);
+ closeDialog();
+ }
+ });
+ addOkButton(yesButton);
+
+ addCancelButton().setText("No");
+ }
+
+ @Inject
+ void initialize(UIPrefs prefs)
+ {
+ prefs_ = prefs;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ progress_ = addProgressIndicator();
+
+ VerticalPanel panel = new VerticalPanel();
+
+ HorizontalPanel horizontalPanel = new HorizontalPanel();
+ horizontalPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
+
+ // add image
+ MessageDialogImages images = MessageDialogImages.INSTANCE;
+ Image image = new Image(images.dialog_warning());
+ horizontalPanel.add(image);
+
+ // add message widget
+ String objects;
+ if (numObjects_ == 0)
+ objects = "all objects";
+ else if (numObjects_ == 1)
+ objects = "1 object";
+ else
+ objects = numObjects_ + " objects";
+ Label label = new MultiLineLabel(
+ "Are you sure you want to remove " + objects + " from the " +
+ "environment? This operation cannot be undone.");
+ label.setStylePrimaryName(
+ ThemeResources.INSTANCE.themeStyles().dialogMessage());
+ horizontalPanel.add(label);
+ panel.add(horizontalPanel);
+
+ // add include hidden option
+ HorizontalPanel optionPanel = new HorizontalPanel();
+ Style optionStyle = optionPanel.getElement().getStyle();
+ optionStyle.setMarginLeft(image.getWidth(), Unit.PX);
+ optionStyle.setMarginBottom(10, Unit.PX);
+
+ chkIncludeHidden_ = new CheckBox("Include hidden objects");
+ chkIncludeHidden_.setValue(prefs_.clearHidden().getValue());
+
+ if (numObjects_ == 0)
+ {
+ chkIncludeHidden_.addValueChangeHandler(new ValueChangeHandler<Boolean>(){
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ prefs_.clearHidden().setGlobalValue(event.getValue());
+ prefs_.writeUIPrefs();
+ }
+ });
+ optionPanel.add(chkIncludeHidden_);
+ }
+ panel.add(optionPanel);
+
+ return panel;
+ }
+
+ private ProgressIndicator progress_ ;
+ private CheckBox chkIncludeHidden_;
+ private UIPrefs prefs_;
+ private int numObjects_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.css
new file mode 100644
index 0000000..de6842b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.css
@@ -0,0 +1,5 @@
+.environmentNameLabel
+{
+ font-weight: bold;
+ margin-right: 0.5em;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.java
new file mode 100644
index 0000000..76e28ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPane.java
@@ -0,0 +1,592 @@
+/*
+ * EnvironmentPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.DebugFilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.SearchWidget;
+import org.rstudio.core.client.widget.SecondaryToolbar;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.ImageMenuItem;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentServerOperations;
+import org.rstudio.studio.client.workbench.views.environment.model.ObjectContents;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+import org.rstudio.studio.client.workbench.views.environment.view.EnvironmentObjects;
+import org.rstudio.studio.client.workbench.views.environment.view.EnvironmentObjectsObserver;
+import org.rstudio.studio.client.workbench.views.environment.view.EnvironmentResources;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class EnvironmentPane extends WorkbenchPane
+ implements EnvironmentPresenter.Display,
+ EnvironmentObjectsObserver
+{
+ @Inject
+ public EnvironmentPane(Commands commands,
+ EventBus eventBus,
+ GlobalDisplay globalDisplay,
+ EnvironmentServerOperations serverOperations,
+ Session session,
+ UIPrefs prefs)
+ {
+ super("Environment");
+
+ commands_ = commands;
+ eventBus_ = eventBus;
+ server_ = serverOperations;
+ globalDisplay_ = globalDisplay;
+ prefs_ = prefs;
+
+ expandedObjects_ = new ArrayList<String>();
+ scrollPosition_ = 0;
+ isClientStateDirty_ = false;
+ environments_ = null;
+ EnvironmentContextData environmentState =
+ session.getSessionInfo().getEnvironmentState();
+ environmentName_ = environmentState.environmentName();
+ environmentIsLocal_ = environmentState.environmentIsLocal();
+
+ EnvironmentPaneResources.INSTANCE.environmentPaneStyle().ensureInjected();
+
+ ensureWidget();
+ }
+
+ // WorkbenchPane overrides -------------------------------------------------
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+ toolbar.addLeftWidget(commands_.loadWorkspace().createToolbarButton());
+ toolbar.addLeftWidget(commands_.saveWorkspace().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(createImportMenu());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.clearWorkspace().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.refreshEnvironment().createToolbarButton());
+
+ ToolbarPopupMenu menu = new ToolbarPopupMenu();
+ menu.addItem(createViewMenuItem(EnvironmentObjects.OBJECT_LIST_VIEW));
+ menu.addItem(createViewMenuItem(EnvironmentObjects.OBJECT_GRID_VIEW));
+ viewButton_ = new ToolbarButton(
+ nameOfViewType(EnvironmentObjects.OBJECT_LIST_VIEW),
+ imageOfViewType(EnvironmentObjects.OBJECT_LIST_VIEW),
+ menu);
+ toolbar.addRightWidget(viewButton_);
+
+ return toolbar;
+ }
+
+ @Override
+ protected SecondaryToolbar createSecondaryToolbar()
+ {
+ SecondaryToolbar toolbar = new SecondaryToolbar();
+
+ environmentMenu_ = new EnvironmentPopupMenu();
+ environmentButton_ = new ToolbarButton(
+ friendlyEnvironmentName(),
+ imageOfEnvironment(environmentName_, environmentIsLocal_),
+ environmentMenu_);
+ toolbar.addLeftWidget(environmentButton_);
+
+ SearchWidget searchWidget = new SearchWidget(new SuggestOracle() {
+ @Override
+ public void requestSuggestions(Request request, Callback callback)
+ {
+ // no suggestions
+ callback.onSuggestionsReady(
+ request,
+ new Response(new ArrayList<Suggestion>()));
+ }
+ });
+ searchWidget.addValueChangeHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ objects_.setFilterText(event.getValue());
+ }
+ });
+
+ searchWidget.getElement().getStyle().setMarginTop(1, Unit.PX);
+ toolbar.addRightWidget(searchWidget);
+
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ objects_ = new EnvironmentObjects(this);
+ return objects_;
+ }
+
+ // EnviromentPresenter.Display implementation ------------------------------
+
+ @Override
+ public void addObject(RObject object)
+ {
+ objects_.addObject(object);
+ }
+
+ @Override
+ public void addObjects(JsArray<RObject> objects)
+ {
+ objects_.addObjects(objects);
+ }
+
+ @Override
+ public void removeObject(String objectName)
+ {
+ objects_.removeObject(objectName);
+ }
+
+ @Override
+ public void setContextDepth(int contextDepth)
+ {
+ objects_.setContextDepth(contextDepth);
+
+ // if the environment we're about to show is nested, turn off the toolbar
+ // commands that act on the global environment
+ Boolean commandsEnabled = contextDepth == 0;
+ commands_.loadWorkspace().setEnabled(commandsEnabled);
+ commands_.saveWorkspace().setEnabled(commandsEnabled);
+ commands_.importDatasetFromFile().setEnabled(commandsEnabled);
+ commands_.importDatasetFromURL().setEnabled(commandsEnabled);
+ dataImportButton_.setEnabled(commandsEnabled);
+ }
+
+ @Override
+ public void clearObjects()
+ {
+ objects_.clearObjects();
+ expandedObjects_.clear();
+ scrollPosition_ = 0;
+ isClientStateDirty_ = true;
+ }
+
+ @Override
+ public void setEnvironmentName(String environmentName, boolean local)
+ {
+ environmentName_ = environmentName;
+ environmentButton_.setText(friendlyEnvironmentName());
+ environmentButton_.setLeftImage(imageOfEnvironment(environmentName,
+ local));
+ objects_.setEnvironmentName(friendlyEnvironmentName());
+ if (environmentName.equals("R_GlobalEnv"))
+ commands_.clearWorkspace().setEnabled(true);
+ else
+ commands_.clearWorkspace().setEnabled(false);
+ }
+
+ @Override
+ public void setCallFrames(JsArray<CallFrame> frameList)
+ {
+ objects_.setCallFrames(frameList);
+ }
+
+ @Override
+ public int getScrollPosition()
+ {
+ return scrollPosition_;
+ }
+
+ @Override
+ public void setScrollPosition(int scrollPosition)
+ {
+ objects_.setScrollPosition(scrollPosition);
+ }
+
+ @Override
+ public void setExpandedObjects(JsArrayString objects)
+ {
+ objects_.setExpandedObjects(objects);
+ expandedObjects_.clear();
+ for (int idx = 0; idx < objects.length(); idx++)
+ {
+ expandedObjects_.add(objects.get(idx));
+ }
+ }
+
+ @Override
+ public String[] getExpandedObjects()
+ {
+ return expandedObjects_.toArray(new String[0]);
+ }
+
+ @Override
+ public List<String> getSelectedObjects()
+ {
+ return objects_.getSelectedObjects();
+ }
+
+ @Override
+ public void clearSelection()
+ {
+ objects_.clearSelection();
+ }
+
+ @Override
+ public void changeContextDepth(int newDepth)
+ {
+ server_.setContextDepth(newDepth, new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error opening call frame", error.getUserMessage());
+ }
+ });
+ }
+
+ public boolean clientStateDirty()
+ {
+ return isClientStateDirty_;
+ }
+
+ public void setClientStateClean()
+ {
+ isClientStateDirty_ = false;
+ }
+
+ @Override
+ public void resize()
+ {
+ objects_.onResize();
+ }
+
+ @Override
+ public void setBrowserRange(DebugFilePosition range)
+ {
+ objects_.updateLineNumber(range.getLine());
+ }
+
+ @Override
+ public void setObjectDisplayType(int type)
+ {
+ viewButton_.setText(nameOfViewType(type));
+ viewButton_.setLeftImage(imageOfViewType(type));
+ objects_.setObjectDisplay(type);
+ }
+
+ @Override
+ public int getObjectDisplayType()
+ {
+ return objects_.getObjectDisplay();
+ }
+
+ @Override
+ public int getSortColumn()
+ {
+ return objects_.getSortColumn();
+ }
+
+ @Override
+ public boolean getAscendingSort()
+ {
+ return objects_.getAscendingSort();
+ }
+
+ @Override
+ public void setSort(int sortColumn, boolean sortAscending)
+ {
+ objects_.setSort(sortColumn, sortAscending);
+ }
+
+ @Override
+ public void setViewDirty()
+ {
+ isClientStateDirty_ = true;
+ }
+
+ // EnviromentObjects.Observer implementation -------------------------------
+
+ public void setPersistedScrollPosition(int scrollPosition)
+ {
+ scrollPosition_ = scrollPosition;
+ isClientStateDirty_ = true;
+ }
+
+ public void setObjectExpanded(String objectName)
+ {
+ expandedObjects_.add(objectName);
+ isClientStateDirty_ = true;
+ }
+
+ public void setObjectCollapsed(String objectName)
+ {
+ expandedObjects_.remove(objectName);
+ isClientStateDirty_ = true;
+ }
+
+ public void viewObject(String objectName)
+ {
+ executeFunctionForObject("View", objectName);
+ }
+
+ @Override
+ public boolean getShowInternalFunctions()
+ {
+ return prefs_.showInternalFunctionsInTraceback().getValue();
+ }
+
+ @Override
+ public void setShowInternalFunctions(boolean show)
+ {
+ prefs_.showInternalFunctionsInTraceback().setProjectValue(show);
+ }
+
+ public void fillObjectContents(final RObject object,
+ final Operation onCompleted)
+ {
+ server_.getObjectContents(object.getName(),
+ new ServerRequestCallback<ObjectContents>()
+ {
+ @Override
+ public void onResponseReceived(ObjectContents contents)
+ {
+ object.setDeferredContents(contents.getContents());
+ onCompleted.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ onCompleted.execute();
+ }
+ });
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private void executeFunctionForObject(String function, String objectName)
+ {
+ String editCode =
+ function + "(" + StringUtil.toRSymbolName(objectName) + ")";
+ SendToConsoleEvent event = new SendToConsoleEvent(editCode, true);
+ eventBus_.fireEvent(event);
+ }
+
+ private Widget createImportMenu()
+ {
+ ToolbarPopupMenu menu = new ToolbarPopupMenu();
+ menu.addItem(commands_.importDatasetFromFile().createMenuItem(false));
+ menu.addItem(commands_.importDatasetFromURL().createMenuItem(false));
+ dataImportButton_ = new ToolbarButton(
+ "Import Dataset",
+ StandardIcons.INSTANCE.import_dataset(),
+ menu);
+ return dataImportButton_;
+
+ }
+
+ private String friendlyEnvironmentName()
+ {
+ return friendlyNameOfEnvironment(environmentName_);
+ }
+
+ private String friendlyNameOfEnvironment(String name)
+ {
+ if (name.equals("R_GlobalEnv"))
+ return GLOBAL_ENVIRONMENT_NAME;
+ else if (name.equals("base"))
+ return "package:base";
+ else
+ return name;
+ }
+
+ private ImageResource imageOfEnvironment(String name, boolean local)
+ {
+ if (name.endsWith("()"))
+ return EnvironmentResources.INSTANCE.functionEnvironment();
+ else if (name.equals("R_GlobalEnv"))
+ return EnvironmentResources.INSTANCE.globalEnvironment();
+ else if (name.startsWith("package:") ||
+ name.equals("base") ||
+ local)
+ return EnvironmentResources.INSTANCE.packageEnvironment();
+ else
+ return EnvironmentResources.INSTANCE.attachedEnvironment();
+ }
+
+ private void setEnvironments(JsArray<EnvironmentFrame> environments)
+ {
+ environments_ = environments;
+ rebuildEnvironmentMenu();
+ }
+
+ private void rebuildEnvironmentMenu()
+ {
+ environmentMenu_.clearItems();
+ if (environments_ == null)
+ {
+ return;
+ }
+ for (int i = 0; i < environments_.length(); i++)
+ {
+ final EnvironmentFrame frame = environments_.get(i);
+ ImageResource img = imageOfEnvironment(frame.getName(),
+ frame.isLocal());
+ environmentMenu_.addItem(ImageMenuItem.create(img,
+ friendlyNameOfEnvironment(frame.getName()),
+ new Scheduler.ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ loadEnvironmentFrame(frame);
+ }
+ }));
+ }
+ }
+
+ // Called to load a new environment into the environment pane.
+ private void loadEnvironmentFrame(final EnvironmentFrame frame)
+ {
+ ServerRequestCallback<Void> callback = new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void v)
+ {
+ setEnvironmentName(frame.getName(), frame.isLocal());
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+
+ }
+ };
+ // If the frame's an active call frame, set it by its index
+ if (frame.getFrame() > 0)
+ server_.setEnvironmentFrame(frame.getFrame(), callback);
+ // Otherwise, set it by its name
+ else
+ server_.setEnvironment(frame.getName(), callback);
+ }
+
+ private String nameOfViewType(int type)
+ {
+ if (type == EnvironmentObjects.OBJECT_LIST_VIEW)
+ return "List";
+ else if (type == EnvironmentObjects.OBJECT_GRID_VIEW)
+ return "Grid";
+ return "";
+ }
+
+ private ImageResource imageOfViewType(int type)
+ {
+ if (type == EnvironmentObjects.OBJECT_LIST_VIEW)
+ return EnvironmentResources.INSTANCE.objectListView();
+ else if (type == EnvironmentObjects.OBJECT_GRID_VIEW)
+ return EnvironmentResources.INSTANCE.objectGridView();
+ return null;
+ }
+
+ private MenuItem createViewMenuItem(final int type)
+ {
+ return ImageMenuItem.create(
+ imageOfViewType(type),
+ nameOfViewType(type),
+ new Scheduler.ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ setObjectDisplayType(type);
+ }
+ });
+ }
+
+ // An extension of the toolbar popup menu that gets environment names from
+ // the server when the menu is invoked.
+ private class EnvironmentPopupMenu extends ToolbarPopupMenu
+ {
+ @Override
+ public void getDynamicPopupMenu
+ (final ToolbarPopupMenu.DynamicPopupMenuCallback callback)
+ {
+ server_.getEnvironmentNames(
+ new ServerRequestCallback<JsArray<EnvironmentFrame>>()
+ {
+ @Override
+ public void onResponseReceived(JsArray<EnvironmentFrame> response)
+ {
+ setEnvironments(response);
+ callback.onPopupMenu(environmentMenu_);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // Just live with a stale list.
+ callback.onPopupMenu(environmentMenu_);
+ }
+ });
+ }
+ }
+
+ public static final String GLOBAL_ENVIRONMENT_NAME = "Global Environment";
+
+ private final Commands commands_;
+ private final EventBus eventBus_;
+ private final GlobalDisplay globalDisplay_;
+ private final EnvironmentServerOperations server_;
+ private final UIPrefs prefs_;
+
+ private ToolbarButton dataImportButton_;
+ private ToolbarPopupMenu environmentMenu_;
+ private ToolbarButton environmentButton_;
+ private ToolbarButton viewButton_;
+ private EnvironmentObjects objects_;
+
+ private ArrayList<String> expandedObjects_;
+ private int scrollPosition_;
+ private boolean isClientStateDirty_;
+ private JsArray<EnvironmentFrame> environments_;
+ private String environmentName_;
+ private boolean environmentIsLocal_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneResources.java
new file mode 100644
index 0000000..b14b6c5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneResources.java
@@ -0,0 +1,29 @@
+/*
+ * EnvironmentPaneResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+
+public interface EnvironmentPaneResources extends ClientBundle
+{
+ public static final EnvironmentPaneResources INSTANCE =
+ GWT.create(EnvironmentPaneResources.class);
+
+ @Source("EnvironmentPane.css")
+ EnvironmentPaneStyle environmentPaneStyle();
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneStyle.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneStyle.java
new file mode 100644
index 0000000..b0338e8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPaneStyle.java
@@ -0,0 +1,23 @@
+/*
+ * EnvironmentPaneStyle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment;
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface EnvironmentPaneStyle extends CssResource
+{
+ String environmentNameLabel();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPresenter.java
new file mode 100644
index 0000000..fe58a32
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentPresenter.java
@@ -0,0 +1,855 @@
+/*
+ * EnvironmentPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment;
+
+import com.google.gwt.core.client.JsArrayString;
+
+import org.rstudio.core.client.DebugFilePosition;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperation;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.RestartStatusEvent;
+import org.rstudio.studio.client.common.ConsoleDispatcher;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.debugging.DebugCommander;
+import org.rstudio.studio.client.common.debugging.DebugCommander.DebugMode;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.events.OpenDataFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenDataFileHandler;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.ActivatePaneEvent;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.model.helper.IntStateValue;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputHandler;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Timer;
+import com.google.inject.Inject;
+
+import org.rstudio.studio.client.workbench.views.environment.dataimport.ImportFileSettings;
+import org.rstudio.studio.client.workbench.views.environment.dataimport.ImportFileSettingsDialog;
+import org.rstudio.studio.client.workbench.views.environment.dataimport.ImportFileSettingsDialogResult;
+import org.rstudio.studio.client.workbench.views.environment.events.BrowserLineChangedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.ContextDepthChangedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.EnvironmentObjectAssignedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.EnvironmentObjectRemovedEvent;
+import org.rstudio.studio.client.workbench.views.environment.events.EnvironmentRefreshEvent;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.DownloadInfo;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentServerOperations;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+import org.rstudio.studio.client.workbench.views.environment.view.EnvironmentClientState;
+import org.rstudio.studio.client.workbench.views.source.SourceShim;
+import org.rstudio.studio.client.workbench.views.source.events.CodeBrowserFinishedEvent;
+import org.rstudio.studio.client.workbench.views.source.events.CodeBrowserHighlightEvent;
+import org.rstudio.studio.client.workbench.views.source.events.CodeBrowserNavigationEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class EnvironmentPresenter extends BasePresenter
+ implements OpenDataFileHandler
+{
+ public interface Binder
+ extends CommandBinder<Commands, EnvironmentPresenter> {}
+
+ public interface Display extends WorkbenchView
+ {
+ void addObject(RObject object);
+ void addObjects(JsArray<RObject> objects);
+ void clearObjects();
+ void clearSelection();
+ void setContextDepth(int contextDepth);
+ void removeObject(String object);
+ void setEnvironmentName(String name, boolean local);
+ void setCallFrames(JsArray<CallFrame> frames);
+ int getScrollPosition();
+ void setScrollPosition(int scrollPosition);
+ void setObjectDisplayType(int type);
+ int getObjectDisplayType();
+ int getSortColumn();
+ boolean getAscendingSort();
+ void setSort(int sortColumn, boolean sortAscending);
+ void setExpandedObjects(JsArrayString objects);
+ String[] getExpandedObjects();
+ boolean clientStateDirty();
+ void setClientStateClean();
+ void resize();
+ void setBrowserRange(DebugFilePosition filePosition);
+ List<String> getSelectedObjects();
+ }
+
+ @Inject
+ public EnvironmentPresenter(Display view,
+ EnvironmentServerOperations server,
+ Binder binder,
+ Commands commands,
+ GlobalDisplay globalDisplay,
+ EventBus eventBus,
+ FileDialogs fileDialogs,
+ WorkbenchContext workbenchContext,
+ ConsoleDispatcher consoleDispatcher,
+ RemoteFileSystemContext fsContext,
+ Session session,
+ SourceShim sourceShim,
+ DebugCommander debugCommander)
+ {
+ super(view);
+ binder.bind(commands, this);
+
+ view_ = view;
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ consoleDispatcher_ = consoleDispatcher;
+ fsContext_ = fsContext;
+ fileDialogs_ = fileDialogs;
+ workbenchContext_ = workbenchContext;
+ eventBus_ = eventBus;
+ refreshingView_ = false;
+ initialized_ = false;
+ currentBrowseFile_ = "";
+ currentBrowsePosition_ = null;
+ sourceShim_ = sourceShim;
+ debugCommander_ = debugCommander;
+ session_ = session;
+ requeryContextTimer_ = new Timer()
+ {
+ @Override
+ public void run()
+ {
+ server_.requeryContext(new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ }
+ });
+ }
+ };
+
+ eventBus.addHandler(EnvironmentRefreshEvent.TYPE,
+ new EnvironmentRefreshEvent.Handler()
+ {
+ @Override
+ public void onEnvironmentRefresh(EnvironmentRefreshEvent event)
+ {
+ refreshView();
+ }
+ });
+
+ eventBus.addHandler(RestartStatusEvent.TYPE,
+ new RestartStatusEvent.Handler()
+ {
+ @Override
+ public void onRestartStatus(RestartStatusEvent event)
+ {
+ if (event.getStatus() == RestartStatusEvent.RESTART_COMPLETED)
+ {
+ refreshView();
+ }
+ }
+ });
+
+ eventBus.addHandler(ContextDepthChangedEvent.TYPE,
+ new ContextDepthChangedEvent.Handler()
+ {
+ @Override
+ public void onContextDepthChanged(ContextDepthChangedEvent event)
+ {
+ loadNewContextState(event.getContextDepth(),
+ event.getEnvironmentName(),
+ event.environmentIsLocal(),
+ event.getCallFrames(),
+ event.useProvidedSource(),
+ event.getFunctionCode());
+ setViewFromEnvironmentList(event.getEnvironmentList());
+ requeryContextTimer_.cancel();
+ }
+ });
+
+ eventBus.addHandler(EnvironmentObjectAssignedEvent.TYPE,
+ new EnvironmentObjectAssignedEvent.Handler()
+ {
+ @Override
+ public void onEnvironmentObjectAssigned(EnvironmentObjectAssignedEvent event)
+ {
+ view_.addObject(event.getObjectInfo());
+ }
+ });
+
+ eventBus.addHandler(EnvironmentObjectRemovedEvent.TYPE,
+ new EnvironmentObjectRemovedEvent.Handler()
+ {
+ @Override
+ public void onEnvironmentObjectRemoved(EnvironmentObjectRemovedEvent event)
+ {
+ view_.removeObject(event.getObjectName());
+ }
+ });
+
+ eventBus.addHandler(BrowserLineChangedEvent.TYPE,
+ new BrowserLineChangedEvent.Handler()
+ {
+ @Override
+ public void onBrowserLineChanged(BrowserLineChangedEvent event)
+ {
+ if (currentBrowsePosition_.compareTo(event.getRange()) != 0)
+ {
+ currentBrowsePosition_ = event.getRange();
+ view_.setBrowserRange(currentBrowsePosition_);
+ openOrUpdateFileBrowsePoint(true, false);
+ }
+ requeryContextTimer_.cancel();
+ }
+ });
+
+ eventBus.addHandler(ConsoleWriteInputEvent.TYPE,
+ new ConsoleWriteInputHandler()
+ {
+ @Override
+ public void onConsoleWriteInput(ConsoleWriteInputEvent event)
+ {
+ String input = event.getInput().trim();
+ if (input.equals(DebugCommander.STOP_COMMAND) ||
+ input.equals(DebugCommander.NEXT_COMMAND) ||
+ input.equals(DebugCommander.CONTINUE_COMMAND))
+ {
+ // When a debug command is issued, we expect to hear back from
+ // the server--either a context depth change or browser line
+ // change event. If neither has occurred after some reasonable
+ // time, poll the server once for its current status.
+ requeryContextTimer_.schedule(500);
+ }
+ }
+ });
+
+ new JSObjectStateValue(
+ "environment-panel",
+ "environmentPanelSettings",
+ ClientState.TEMPORARY,
+ session.getSessionInfo().getClientState(),
+ false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ if (value != null)
+ {
+ EnvironmentClientState clientState = value.cast();
+ view_.setScrollPosition(clientState.getScrollPosition());
+ view_.setExpandedObjects(clientState.getExpandedObjects());
+ view_.setSort(clientState.getSortColumn(),
+ clientState.getAscendingSort());
+ }
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ // the state object we're about to create will be persisted, so
+ // our state is clean until the user makes more changes.
+ view_.setClientStateClean();
+ return EnvironmentClientState.create(view_.getScrollPosition(),
+ view_.getExpandedObjects(),
+ view_.getSortColumn(),
+ view_.getAscendingSort())
+ .cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ return view_.clientStateDirty();
+ }
+ };
+
+ // Store the object display type more permanently than the other
+ // client state settings; it's likely to be a user preference.
+ new IntStateValue(
+ "environment-grid",
+ "objectDisplayType",
+ ClientState.PERSISTENT,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(Integer value)
+ {
+ if (value != null)
+ view_.setObjectDisplayType(value);
+ }
+
+ @Override
+ protected Integer getValue()
+ {
+ return view_.getObjectDisplayType();
+ }
+ };
+ }
+
+ @Handler
+ void onRefreshEnvironment()
+ {
+ refreshView();
+ }
+
+ void onClearWorkspace()
+ {
+ view_.bringToFront();
+ final List<String> objectNames = view_.getSelectedObjects();
+
+ new ClearAllDialog(objectNames.size(),
+ new ProgressOperationWithInput<Boolean>() {
+
+ @Override
+ public void execute(Boolean includeHidden, ProgressIndicator indicator)
+ {
+ indicator.onProgress("Removing objects...");
+ if (objectNames.size() == 0)
+ {
+ server_.removeAllObjects(
+ includeHidden,
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ view_.clearSelection();
+ view_.clearObjects();
+ }
+ });
+ }
+ else
+ {
+ server_.removeObjects(
+ objectNames,
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ view_.clearSelection();
+ for (String obj: objectNames)
+ {
+ view_.removeObject(obj);
+ }
+ }
+ });
+ }
+ }
+ }).showModal();
+ }
+
+ void onSaveWorkspace()
+ {
+ view_.bringToFront();
+
+ consoleDispatcher_.saveFileAsThenExecuteCommand("Save Workspace As",
+ ".RData",
+ true,
+ "save.image");
+ }
+
+ void onLoadWorkspace()
+ {
+ view_.bringToFront();
+ consoleDispatcher_.chooseFileThenExecuteCommand("Load Workspace", "load");
+ }
+
+ void onImportDatasetFromFile()
+ {
+ view_.bringToFront();
+ fileDialogs_.openFile(
+ "Select File to Import",
+ fsContext_,
+ workbenchContext_.getCurrentWorkingDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(
+ FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ showImportFileDialog(input, null);
+ }
+ });
+ }
+
+ void onImportDatasetFromURL()
+ {
+ view_.bringToFront();
+ globalDisplay_.promptForText(
+ "Import from Web URL" ,
+ "Please enter the URL to import data from:",
+ "",
+ new ProgressOperationWithInput<String>(){
+ public void execute(String input, final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Downloading data...");
+ server_.downloadDataFile(input.trim(),
+ new ServerRequestCallback<DownloadInfo>(){
+
+ @Override
+ public void onResponseReceived(DownloadInfo downloadInfo)
+ {
+ indicator.onCompleted();
+ showImportFileDialog(
+ FileSystemItem.createFile(downloadInfo.getPath()),
+ downloadInfo.getVarname());
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+
+ });
+ }
+ });
+ }
+
+ public void onOpenDataFile(OpenDataFileEvent event)
+ {
+ final String dataFilePath = event.getFile().getPath();
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+ "Confirm Load RData",
+
+ "Do you want to load the R data file \"" + dataFilePath + "\" " +
+ "into the global environment?",
+
+ new ProgressOperation() {
+ public void execute(ProgressIndicator indicator)
+ {
+ consoleDispatcher_.executeCommand(
+ "load",
+ FileSystemItem.createFile(dataFilePath));
+
+ indicator.onCompleted();
+ }
+ },
+
+ true);
+ }
+
+ @Override
+ public void onBeforeSelected()
+ {
+ super.onBeforeSelected();
+
+ // if the view isn't yet initialized, initialize it with the list of
+ // objects in the environment
+ if (!initialized_)
+ {
+ // we may have a cached list of objects in the session info--if
+ // we do, use that list; otherwise, refresh the view to get a new one.
+ // (the list may be empty e.g. on cold session startup when the
+ // environment was loaded from .RData and therefore not available
+ // during session init; we also want to fetch a fresh list in this
+ // case).
+ JsArray<RObject> environmentList =
+ session_.getSessionInfo().getEnvironmentState().environmentList();
+ if (environmentList == null ||
+ environmentList.length() == 0)
+ {
+ refreshView();
+ }
+ else
+ {
+ setViewFromEnvironmentList(environmentList);
+ }
+ initialized_ = true;
+ }
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+
+ // GWT sometimes gets a 0-height layout cached on tab switch; resize the
+ // tab after selection to ensure it's filling its space.
+ if (initialized_)
+ {
+ view_.resize();
+ }
+ }
+
+ public void initialize(EnvironmentContextData environmentState)
+ {
+ loadNewContextState(environmentState.contextDepth(),
+ environmentState.environmentName(),
+ environmentState.environmentIsLocal(),
+ environmentState.callFrames(),
+ environmentState.useProvidedSource(),
+ environmentState.functionCode());
+ setViewFromEnvironmentList(environmentState.environmentList());
+ initialized_ = true;
+ }
+
+ public void setContextDepth(int contextDepth)
+ {
+ // if entering debug state, activate this tab
+ if (contextDepth > 0 &&
+ contextDepth_ == 0)
+ {
+ eventBus_.fireEvent(new ActivatePaneEvent("Environment"));
+ debugCommander_.enterDebugMode(DebugMode.Function);
+ }
+ // if leaving debug mode, let everyone know
+ else if (contextDepth == 0 &&
+ contextDepth_ > 0)
+ {
+ debugCommander_.leaveDebugMode();
+ }
+ contextDepth_ = contextDepth;
+ view_.setContextDepth(contextDepth_);
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private void loadNewContextState(int contextDepth,
+ String environmentName,
+ boolean isLocalEvironment,
+ JsArray<CallFrame> callFrames,
+ boolean useBrowseSources,
+ String functionCode)
+ {
+ setContextDepth(contextDepth);
+ environmentName_ = environmentName;
+ view_.setEnvironmentName(environmentName_, isLocalEvironment);
+ if (callFrames != null &&
+ callFrames.length() > 0 &&
+ contextDepth > 0)
+ {
+ view_.setCallFrames(callFrames);
+ CallFrame browseFrame = callFrames.get(
+ contextDepth_ - 1);
+ String newBrowseFile = browseFrame.getFileName().trim();
+ boolean sourceChanged = false;
+
+ // check to see if the file we're about to switch to contains unsaved
+ // changes. if it does, use the source supplied by the server, even if
+ // the server thinks the document is clean.
+ if (fileContainsUnsavedChanges(newBrowseFile))
+ {
+ useBrowseSources = true;
+ }
+
+ // if the file is different or we're swapping into or out of the source
+ // viewer, turn off highlighting in the old file before turning it on
+ // in the new one. avoid this in the case where the file is different
+ // but both frames are viewed from source, since in this case an
+ // unnecessary close and reopen of the source viewer would be
+ // triggered.
+ if ((!newBrowseFile.equals(currentBrowseFile_) ||
+ useBrowseSources != useCurrentBrowseSource_) &&
+ !(useBrowseSources && useCurrentBrowseSource_))
+ {
+ openOrUpdateFileBrowsePoint(false, false);
+ }
+
+ useCurrentBrowseSource_ = useBrowseSources;
+ if (!currentBrowseSource_.equals(functionCode))
+ {
+ currentBrowseSource_ = functionCode;
+ sourceChanged = true;
+ }
+
+ // highlight the active line in the file now being debugged
+ currentBrowseFile_ = newBrowseFile;
+ currentBrowsePosition_ = browseFrame.getRange();
+ currentFunctionLineNumber_ = browseFrame.getFunctionLineNumber();
+ openOrUpdateFileBrowsePoint(true, sourceChanged);
+ }
+ else
+ {
+ openOrUpdateFileBrowsePoint(false, false);
+ useCurrentBrowseSource_ = false;
+ currentBrowseSource_ = "";
+ currentBrowseFile_ = "";
+ currentBrowsePosition_ = null;
+ currentFunctionLineNumber_ = 0;
+ }
+ }
+
+ // given a path, indicate whether it corresponds to a file that currently
+ // contains unsaved changes.
+ private boolean fileContainsUnsavedChanges(String path)
+ {
+ ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
+ sourceShim_.getUnsavedChanges();
+
+ for (UnsavedChangesTarget target: unsavedSourceDocs)
+ {
+ if (target.getPath().equals(path))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void openOrUpdateFileBrowsePoint(boolean debugging,
+ boolean sourceChanged)
+ {
+ String file = currentBrowseFile_;
+
+ // if we have no file and no source code, we can do no navigation
+ if (!CallFrame.isNavigableFilename(file) &&
+ !useCurrentBrowseSource_)
+ {
+ return;
+ }
+
+ // if we have a real filename and sign from the server that the file
+ // is in sync with the actual copy of the function, navigate to the
+ // file itself
+ if (currentBrowsePosition_ != null &&
+ !useCurrentBrowseSource_)
+ {
+ FileSystemItem sourceFile = FileSystemItem.createFile(file);
+ eventBus_.fireEvent(new OpenSourceFileEvent(sourceFile,
+ (FilePosition) currentBrowsePosition_.cast(),
+ FileTypeRegistry.R,
+ debugging ?
+ (contextDepth_ == 1 ?
+ NavigationMethod.DebugStep :
+ NavigationMethod.DebugFrame)
+ :
+ NavigationMethod.DebugEnd));
+
+ }
+ // otherwise, if we have a copy of the source from the server, load
+ // the copy from the server into the code browser window
+ else if (useCurrentBrowseSource_ &&
+ currentBrowseSource_.length() > 0)
+ {
+ if (debugging)
+ {
+ if (sourceChanged)
+ {
+ // create the function name for the code browser by removing the
+ // () indicator supplied by the server
+ String functionName = environmentName_;
+ int idx = functionName.indexOf('(');
+ if (idx > 0)
+ {
+ functionName = functionName.substring(0, idx);
+ }
+ // if this is a different source file than we already have open,
+ // open it
+ eventBus_.fireEvent(new CodeBrowserNavigationEvent(
+ SearchPathFunctionDefinition.create(
+ functionName,
+ "debugging",
+ currentBrowseSource_,
+ true),
+ currentBrowsePosition_.functionRelativePosition(
+ currentFunctionLineNumber_),
+ contextDepth_ == 1));
+ }
+ else if (currentBrowsePosition_.getLine() > 0)
+ {
+ // if this is the same one currently open, just move the
+ // highlight
+ eventBus_.fireEvent(new CodeBrowserHighlightEvent(
+ currentBrowsePosition_.functionRelativePosition(
+ currentFunctionLineNumber_)));
+ }
+ }
+ else
+ {
+ eventBus_.fireEvent(new CodeBrowserFinishedEvent());
+ }
+ }
+ }
+
+ private void setViewFromEnvironmentList(JsArray<RObject> objects)
+ {
+ view_.clearObjects();
+ view_.addObjects(objects);
+ }
+
+ private void refreshView()
+ {
+ // if we're currently waiting for a view refresh to come back, don't
+ // queue another server request
+ if (refreshingView_)
+ {
+ return;
+ }
+ // start showing the progress spinner and initiate the request
+ view_.setProgress(true);
+ refreshingView_ = true;
+ server_.getEnvironmentState(
+ new ServerRequestCallback<EnvironmentContextData>()
+ {
+
+ @Override
+ public void onResponseReceived(EnvironmentContextData data)
+ {
+ view_.setProgress(false);
+ refreshingView_ = false;
+ initialized_ = true;
+ eventBus_.fireEvent(new ContextDepthChangedEvent(data, false));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (!workbenchContext_.isRestartInProgress())
+ {
+ globalDisplay_.showErrorMessage("Error Listing Objects",
+ error.getUserMessage());
+ }
+ view_.setProgress(false);
+ refreshingView_ = false;
+ }
+ });
+ }
+
+ private void showImportFileDialog(FileSystemItem input, String varname)
+ {
+ ImportFileSettingsDialog dialog = new ImportFileSettingsDialog(
+ server_,
+ input,
+ varname,
+ "Import Dataset",
+ new OperationWithInput<ImportFileSettingsDialogResult>()
+ {
+ public void execute(
+ ImportFileSettingsDialogResult result)
+ {
+ ImportFileSettings input = result.getSettings();
+ String var = StringUtil.toRSymbolName(input.getVarname());
+ String code =
+ var +
+ " <- " +
+ makeCommand(input,
+ result.getDefaultStringsAsFactors()) +
+ "\n View(" + var + ")";
+ eventBus_.fireEvent(new SendToConsoleEvent(code, true));
+ }
+ },
+ globalDisplay_);
+ dialog.showModal();
+ }
+
+ private String makeCommand(ImportFileSettings input,
+ boolean defaultStringsAsFactors)
+ {
+ HashMap<String, ImportFileSettings> commandDefaults_ =
+ new HashMap<String, ImportFileSettings>();
+
+ commandDefaults_.put("read.table", new ImportFileSettings(
+ null, null, false, "", ".", "\"'", "NA", defaultStringsAsFactors));
+ commandDefaults_.put("read.csv", new ImportFileSettings(
+ null, null, true, ",", ".", "\"", "NA", defaultStringsAsFactors));
+ commandDefaults_.put("read.delim", new ImportFileSettings(
+ null, null, true, "\t", ".", "\"", "NA", defaultStringsAsFactors));
+ commandDefaults_.put("read.csv2", new ImportFileSettings(
+ null, null, true, ";", ",", "\"", "NA", defaultStringsAsFactors));
+ commandDefaults_.put("read.delim2", new ImportFileSettings(
+ null, null, true, "\t", ",", "\"", "NA", defaultStringsAsFactors));
+
+ String command = "read.table";
+ ImportFileSettings settings = commandDefaults_.get("read.table");
+ int score = settings.calculateSimilarity(input);
+ for (String cmd : new String[] {"read.csv", "read.delim"})
+ {
+ ImportFileSettings theseSettings = commandDefaults_.get(cmd);
+ int thisScore = theseSettings.calculateSimilarity(input);
+ if (thisScore > score)
+ {
+ score = thisScore;
+ command = cmd;
+ settings = theseSettings;
+ }
+ }
+
+ StringBuilder code = new StringBuilder(command);
+ code.append("(");
+ code.append(StringUtil.textToRLiteral(input.getFile().getPath()));
+ if (input.isHeader() != settings.isHeader())
+ code.append(", header=" + (input.isHeader() ? "TRUE" : "FALSE"));
+ if (!input.getSep().equals(settings.getSep()))
+ code.append(", sep=" + StringUtil.textToRLiteral(input.getSep()));
+ if (!input.getDec().equals(settings.getDec()))
+ code.append(", dec=" + StringUtil.textToRLiteral(input.getDec()));
+ if (!input.getQuote().equals(settings.getQuote()))
+ code.append(", quote=" + StringUtil.textToRLiteral(input.getQuote()));
+ if (!input.getNAStrings().equals(settings.getNAStrings()))
+ code.append(", na.strings=" + StringUtil.textToRLiteral(input.getNAStrings()));
+ if (input.getStringsAsFactors() != settings.getStringsAsFactors())
+ code.append(", stringsAsFactors=" + (input.getStringsAsFactors() ? "TRUE" : "FALSE"));
+
+ code.append(")");
+
+ return code.toString();
+ }
+
+ private final Display view_;
+ private final EnvironmentServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final ConsoleDispatcher consoleDispatcher_;
+ private final RemoteFileSystemContext fsContext_;
+ private final WorkbenchContext workbenchContext_;
+ private final FileDialogs fileDialogs_;
+ private final EventBus eventBus_;
+ private final SourceShim sourceShim_;
+ private final DebugCommander debugCommander_;
+ private final Session session_;
+
+ private int contextDepth_;
+ private boolean refreshingView_;
+ private boolean initialized_;
+ private DebugFilePosition currentBrowsePosition_;
+ private int currentFunctionLineNumber_;
+ private String currentBrowseFile_;
+ private boolean useCurrentBrowseSource_;
+ private String currentBrowseSource_;
+ private String environmentName_;
+ private Timer requeryContextTimer_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentTab.java
new file mode 100644
index 0000000..436fd79
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/EnvironmentTab.java
@@ -0,0 +1,79 @@
+/*
+ * EnvironmentTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenDataFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenDataFileHandler;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import com.google.inject.Inject;
+
+public class EnvironmentTab extends DelayLoadWorkbenchTab<EnvironmentPresenter>
+{
+ public interface Binder extends CommandBinder<Commands, EnvironmentTab.Shim> {}
+
+ public abstract static class Shim
+ extends DelayLoadTabShim<EnvironmentPresenter, EnvironmentTab>
+ implements OpenDataFileHandler
+ {
+ @Handler
+ public abstract void onLoadWorkspace();
+ @Handler
+ public abstract void onSaveWorkspace();
+ @Handler
+ public abstract void onImportDatasetFromFile();
+ @Handler
+ public abstract void onImportDatasetFromURL();
+ @Handler
+ public abstract void onClearWorkspace();
+
+ abstract void initialize(EnvironmentContextData environmentState);
+ }
+
+ @Inject
+ public EnvironmentTab(final Shim shim,
+ Binder binder,
+ EventBus events,
+ Commands commands,
+ Session session)
+ {
+ super("Environment", shim);
+ binder.bind(commands, shim);
+ events.addHandler(OpenDataFileEvent.TYPE, shim);
+
+ session_ = session;
+
+ events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ EnvironmentContextData environmentState =
+ session_.getSessionInfo().getEnvironmentState();
+ shim.initialize(environmentState);
+ }
+ });
+ }
+
+ private final Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettings.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettings.java
new file mode 100644
index 0000000..354e918
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettings.java
@@ -0,0 +1,102 @@
+/*
+ * ImportFileSettings.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.dataimport;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class ImportFileSettings
+{
+ public ImportFileSettings(FileSystemItem file,
+ String varname,
+ boolean header,
+ String sep,
+ String decimal,
+ String quote,
+ String naStrings,
+ boolean stringsAsFactors)
+ {
+ file_ = file;
+ varname_ = varname;
+ header_ = header;
+ sep_ = sep;
+ decimal_ = decimal;
+ quote_ = quote;
+ naStrings_ = naStrings;
+ stringsAsFactors_ = stringsAsFactors;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ public String getVarname()
+ {
+ return varname_;
+ }
+
+ public boolean isHeader()
+ {
+ return header_;
+ }
+
+ public String getSep()
+ {
+ return sep_;
+ }
+
+ public String getDec()
+ {
+ return decimal_;
+ }
+
+ public String getQuote()
+ {
+ return quote_;
+ }
+
+ public String getNAStrings()
+ {
+ return naStrings_;
+ }
+
+ public boolean getStringsAsFactors()
+ {
+ return stringsAsFactors_;
+ }
+
+ public int calculateSimilarity(ImportFileSettings other)
+ {
+ int score = 0;
+ if (isHeader() == other.isHeader())
+ score++;
+ if (getSep().equals(other.getSep()))
+ score += 2;
+ if (getDec().equals(other.getDec()))
+ score += 2;
+ if (getQuote().equals(other.getQuote()))
+ score++;
+ return score;
+ }
+
+ private final FileSystemItem file_;
+ private final String varname_;
+ private final boolean header_;
+ private final String sep_;
+ private final String decimal_;
+ private final String quote_;
+ private final String naStrings_;
+ private final boolean stringsAsFactors_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.css
new file mode 100644
index 0000000..d3d116e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.css
@@ -0,0 +1,46 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+.input, .output {
+ resize: none;
+ font-family: fixedWidthFont;
+ font-size: 11px;
+ white-space: pre;
+ width: 400px;
+ height: 200px;
+ overflow: auto;
+ background-color: white;
+ border: 1px solid #888;
+ padding: 4px;
+}
+.output td {
+ padding-right: 16px;
+}
+.inputLabel, .outputLabel {
+ margin-bottom: 3px;
+}
+.outputLabel {
+ margin-top: 12px;
+}
+.header {
+ font-weight: bold;
+}
+.leftPanel {
+ position: relative;
+ top: -5px;
+ margin-right: 10px;
+}
+.list {
+ width: 150px;
+}
+.nastrings {
+ width: 75px;
+}
+.varname {
+ width: 100%;
+ margin-bottom: 1.5em;
+ border: #cfd2d4 solid 1px;
+ font-size: 12px;
+ font-family: proportionalFont;
+ outline: none;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.java
new file mode 100644
index 0000000..0860fcc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.java
@@ -0,0 +1,375 @@
+/*
+ * ImportFileSettingsDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.dataimport;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.TextAreaElement;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.environment.model.DataPreviewResult;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentServerOperations;
+
+public class ImportFileSettingsDialog extends ModalDialog<ImportFileSettingsDialogResult>
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("ImportFileSettingsDialog.css")
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String varname();
+ String nastrings();
+ String input();
+ String output();
+ String inputLabel();
+ String outputLabel();
+ String header();
+ String leftPanel();
+ String list();
+ }
+
+ interface MyBinder extends UiBinder<Widget, ImportFileSettingsDialog> {}
+
+
+ public ImportFileSettingsDialog(
+ EnvironmentServerOperations server,
+ FileSystemItem dataFile,
+ String varname,
+ String caption,
+ OperationWithInput<ImportFileSettingsDialogResult> operation,
+ GlobalDisplay globalDisplay)
+ {
+ super(caption, operation);
+ server_ = server;
+ dataFile_ = dataFile;
+ globalDisplay_ = globalDisplay;
+
+ Resources res = GWT.create(Resources.class);
+ styles_ = res.styles();
+
+ MyBinder binder = GWT.create(MyBinder.class);
+ widget_ = binder.createAndBindUi(this);
+
+ if (varname != null)
+ varname_.setText(varname);
+ else
+ varname_.setText(dataFile.getStem()
+ .replace(" ", ".")
+ .replace("-", "."));
+
+ separator_.addItem("Whitespace", "");
+ separator_.addItem("Comma", ",");
+ separator_.addItem("Semicolon", ";");
+ separator_.addItem("Tab", "\t");
+
+ decimal_.addItem("Period", ".");
+ decimal_.addItem("Comma", ",");
+
+ quote_.addItem("Double quote (\")", "\"");
+ quote_.addItem("Single quote (')", "'");
+ quote_.addItem("None", "");
+
+ hookChangeEvents();
+
+ ((TextAreaElement) input_.getElement().cast()).setReadOnly(true);
+ ((TextAreaElement) outputPanel_.getElement().cast()).setReadOnly(true);
+
+ progress_ = addProgressIndicator();
+
+ setOkButtonCaption("Import");
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ separator_.setSelectedIndex(-1);
+ quote_.setSelectedIndex(-1);
+ naStrings_.setText("NA");
+ loadData();
+ }
+
+ private void hookChangeEvents()
+ {
+ ValueChangeHandler<Boolean> valueChangeHandler = new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> booleanValueChangeEvent)
+ {
+ updateOutput();
+ }
+ };
+ headingYes_.addValueChangeHandler(valueChangeHandler);
+ headingNo_.addValueChangeHandler(valueChangeHandler);
+
+ ChangeHandler changeHandler = new ChangeHandler()
+ {
+ public void onChange(ChangeEvent event)
+ {
+ updateOutput();
+ }
+ };
+ separator_.addChangeHandler(changeHandler);
+ decimal_.addChangeHandler(changeHandler);
+ quote_.addChangeHandler(changeHandler);
+ }
+
+ private void updateOutput()
+ {
+ if (separator_.getSelectedIndex() < 0
+ || quote_.getSelectedIndex() < 0
+ || decimal_.getSelectedIndex() < 0)
+ {
+ return;
+ }
+
+ updateRequest_.invalidate();
+ final Token invalidationToken = updateRequest_.getInvalidationToken();
+
+ progress_.onProgress("Updating preview");
+ server_.getOutputPreview(
+ dataFile_.getPath(),
+ headingYes_.getValue().booleanValue(),
+ separator_.getValue(separator_.getSelectedIndex()),
+ decimal_.getValue(decimal_.getSelectedIndex()),
+ quote_.getValue(quote_.getSelectedIndex()),
+ new ServerRequestCallback<DataPreviewResult>()
+ {
+ @Override
+ public void onResponseReceived(DataPreviewResult response)
+ {
+ if (invalidationToken.isInvalid())
+ return;
+
+ progress_.onProgress(null);
+ populateOutput(response);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (invalidationToken.isInvalid())
+ return;
+
+ progress_.onProgress(null);
+ globalDisplay_.showErrorMessage(
+ "Error",
+ error.getUserMessage());
+ }
+ });
+ }
+
+ private void loadData()
+ {
+ final Token invalidationToken = updateRequest_.getInvalidationToken();
+
+ progress_.onProgress("Detecting data format");
+ server_.getDataPreview(
+ dataFile_.getPath(),
+ new ServerRequestCallback<DataPreviewResult>()
+ {
+ @Override
+ public void onResponseReceived(DataPreviewResult response)
+ {
+ input_.setHTML(toInputHtml(response));
+
+ if (invalidationToken.isInvalid())
+ return;
+
+ progress_.onProgress(null);
+ populateOutput(response);
+ if (response.hasHeader())
+ headingYes_.setValue(true);
+ else
+ headingNo_.setValue(true);
+
+ selectByValue(separator_, response.getSeparator());
+ selectByValue(decimal_, response.getDecimal());
+ selectByValue(quote_, response.getQuote());
+
+ defaultStringsAsFactors_ = response.getDefaultStringsAsFactors();
+ stringsAsFactors_.setValue(defaultStringsAsFactors_);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (invalidationToken.isInvalid())
+ return;
+
+ progress_.onProgress(null);
+ globalDisplay_.showErrorMessage(
+ "Error",
+ error.getUserMessage());
+ }
+ });
+ }
+
+ private void selectByValue(ListBox listBox, String value)
+ {
+ for (int i = 0; i < listBox.getItemCount(); i++)
+ {
+ if (equal(listBox.getValue(i), value))
+ {
+ listBox.setSelectedIndex(i);
+ return;
+ }
+ }
+ listBox.setSelectedIndex(-1);
+ }
+
+ private boolean equal(String v1, String v2)
+ {
+ if (v1 == null ^ v2 == null)
+ return false;
+ if (v1 == null)
+ return true;
+ return v1.equals(v2);
+ }
+
+ private void populateOutput(DataPreviewResult result)
+ {
+ JsArray<JsObject> output = result.getOutput();
+ JsArrayString names = result.getOutputNames();
+
+ int rows = output.length();
+ int cols = names.length();
+ Grid grid = new Grid(rows + 1, cols);
+ grid.setCellPadding(0);
+ grid.setCellSpacing(0);
+ grid.getRowFormatter().addStyleName(0, styles_.header());
+ for (int col = 0; col < cols; col++)
+ grid.setText(0, col, names.get(col));
+
+ for (int row = 0; row < rows; row++)
+ {
+ for (int col = 0; col < cols; col++)
+ {
+ String val = output.get(row).getString(names.get(col), true);
+ if (val == null)
+ val = "NA";
+ grid.setText(row + 1, col, val);
+ }
+ }
+
+ outputPanel_.setWidget(grid);
+ }
+
+ private String toInputHtml(DataPreviewResult response)
+ {
+ String input = response.getInputLines();
+ return input.replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll("^ ", " ")
+ .replaceAll(" $", " ")
+ .replaceAll("\\t", "<span style=\"background-color: #EEE; color: #888; padding: 0 10px 0 10px\">⇥</span>");
+ }
+
+ @Override
+ protected ImportFileSettingsDialogResult collectInput()
+ {
+ return new ImportFileSettingsDialogResult(
+ new ImportFileSettings(
+ dataFile_,
+ varname_.getText().trim(),
+ headingYes_.getValue(),
+ separator_.getValue(separator_.getSelectedIndex()),
+ decimal_.getValue(decimal_.getSelectedIndex()),
+ quote_.getValue(quote_.getSelectedIndex()),
+ naStrings_.getText().trim(),
+ stringsAsFactors_.getValue()),
+ defaultStringsAsFactors_);
+ }
+
+ @Override
+ protected boolean validate(ImportFileSettingsDialogResult input)
+ {
+ if (varname_.getText().trim().length() == 0)
+ {
+ varname_.setFocus(true);
+ globalDisplay_.showErrorMessage("Variable Name Is Required",
+ "Please provide a variable name.");
+ return false;
+ }
+
+ return (headingYes_.getValue() || headingNo_.getValue())
+ && separator_.getSelectedIndex() >= 0
+ && quote_.getSelectedIndex() >= 0;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return widget_;
+ }
+
+ public static void ensureStylesInjected()
+ {
+ Resources res = GWT.create(Resources.class);
+ res.styles().ensureInjected();
+ }
+
+ @UiField
+ ListBox separator_;
+ @UiField
+ ListBox decimal_;
+ @UiField
+ ListBox quote_;
+ @UiField
+ HTML input_;
+ @UiField
+ SimplePanel outputPanel_;
+ @UiField
+ RadioButton headingYes_;
+ @UiField
+ RadioButton headingNo_;
+ @UiField
+ TextBox varname_;
+ @UiField
+ TextBox naStrings_;
+ @UiField
+ CheckBox stringsAsFactors_;
+
+ private final Widget widget_;
+ private final EnvironmentServerOperations server_;
+ private final FileSystemItem dataFile_;
+ private boolean defaultStringsAsFactors_ = true;
+ private final GlobalDisplay globalDisplay_;
+ private ProgressIndicator progress_;
+ private final Invalidation updateRequest_ = new Invalidation();
+ private final Styles styles_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.ui.xml
new file mode 100644
index 0000000..6fc58df
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialog.ui.xml
@@ -0,0 +1,53 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.workbench.views.environment.dataimport.ImportFileSettingsDialog.Resources"/>
+
+ <g:HTMLPanel>
+ <g:HorizontalPanel>
+ <g:HTMLPanel styleName="{res.styles.leftPanel}">
+ <table>
+ <tr>
+ <td colspan="2">
+ <g:Label text="Name" />
+ <g:TextBox ui:field="varname_" styleName="{res.styles.varname}" />
+ </td>
+ </tr>
+ <tr>
+ <td>Heading</td>
+ <td>
+ <g:RadioButton ui:field="headingYes_" name="importFileSettingsHeading" text="Yes"/>
+ <g:RadioButton ui:field="headingNo_" name="importFileSettingsHeading" text="No"/>
+ </td>
+ </tr>
+ <tr>
+ <td>Separator</td>
+ <td><g:ListBox ui:field="separator_" styleName="{res.styles.list}" /></td>
+ </tr>
+ <tr>
+ <td>Decimal</td>
+ <td><g:ListBox ui:field="decimal_" styleName="{res.styles.list}" /></td>
+ </tr>
+ <tr>
+ <td>Quote</td>
+ <td><g:ListBox ui:field="quote_" styleName="{res.styles.list}" /></td>
+ </tr>
+ <tr>
+ <td>na.strings</td>
+ <td><g:TextBox ui:field="naStrings_" styleName="{res.styles.nastrings}"></g:TextBox></td>
+ </tr>
+ <tr>
+ <td><g:CheckBox ui:field="stringsAsFactors_" text="Strings as factors"></g:CheckBox></td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+ <g:VerticalPanel>
+ <g:Label text="Input File" styleName="{res.styles.inputLabel}"/>
+ <g:HTML ui:field="input_" styleName="{res.styles.input}"/>
+ <g:Label text="Data Frame" styleName="{res.styles.outputLabel}"/>
+ <g:SimplePanel ui:field="outputPanel_" styleName="{res.styles.output}"/>
+ </g:VerticalPanel>
+ </g:HorizontalPanel>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialogResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialogResult.java
new file mode 100644
index 0000000..0195b67
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportFileSettingsDialogResult.java
@@ -0,0 +1,39 @@
+/*
+ * ImportFileSettingsDialogResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.dataimport;
+
+public class ImportFileSettingsDialogResult
+{
+ public ImportFileSettingsDialogResult(ImportFileSettings settings,
+ boolean defaultStringsAsFactors)
+ {
+ settings_ = settings;
+ defaultStringsAsFactors_ = defaultStringsAsFactors;
+ }
+
+ public ImportFileSettings getSettings()
+ {
+ return settings_;
+ }
+
+ public boolean getDefaultStringsAsFactors()
+ {
+ return defaultStringsAsFactors_;
+ }
+
+ private final ImportFileSettings settings_;
+ private final boolean defaultStringsAsFactors_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportGoogleSpreadsheetDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportGoogleSpreadsheetDialog.css
new file mode 100644
index 0000000..c68efdd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/ImportGoogleSpreadsheetDialog.css
@@ -0,0 +1,64 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external search;
+
+.progressPanel {
+ background-color: white;
+ border: 1px solid #cfd2d4;
+}
+
+.scrollPanel {
+}
+
+.table td {
+ overflow: hidden;
+ padding: 4px;
+}
+
+.selected {
+ background-color: #ccc;
+}
+*:focus .selected {
+ background-color: rgb(146, 193, 240);
+}
+
+.date {
+ color: #AAA;
+ width: 175px;
+}
+
+.selected .date {
+ color: black;
+}
+
+.googleSpreadsheetSearch .search {
+ margin-right: 0;
+ margin-bottom: 3px;
+ width: 200px;
+}
+
+.overflowWarning {
+ font-size: 10px;
+ margin-top: 3px;
+}
+
+.varNameLabel {
+ display: inline;
+ margin-right: 4px;
+}
+.varName {
+ width: 175px;
+ border: #cfd2d4 solid 1px;
+ font-size: 12px;
+ font-family: proportionalFont;
+ outline: none;
+}
+
+ at sprite .spreadsheetIcon {
+ gwt-image: 'spreadsheet';
+}
+
+td.iconCell {
+ padding-right: 0;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/spreadsheet.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/spreadsheet.png
new file mode 100644
index 0000000..049a806
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/dataimport/spreadsheet.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/BrowserLineChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/BrowserLineChangedEvent.java
new file mode 100644
index 0000000..d28f568
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/BrowserLineChangedEvent.java
@@ -0,0 +1,79 @@
+/*
+ * BrowserLineChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import org.rstudio.core.client.DebugFilePosition;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BrowserLineChangedEvent
+ extends GwtEvent<BrowserLineChangedEvent.Handler>
+{
+
+ public interface Handler extends EventHandler
+ {
+ void onBrowserLineChanged(BrowserLineChangedEvent event);
+ }
+
+ public BrowserLineChangedEvent(LineData data)
+ {
+ lineData_ = data;
+ }
+
+ public int getLineNumber()
+ {
+ return lineData_.getLineNumber();
+ }
+
+ public int getEndLineNumber()
+ {
+ return lineData_.getEndLineNumber();
+ }
+
+ public int getCharacterNumber()
+ {
+ return lineData_.getCharacterNumber();
+ }
+
+ public int getEndCharacterNumber()
+ {
+ return lineData_.getEndCharacterNumber();
+ }
+
+ public DebugFilePosition getRange()
+ {
+ return DebugFilePosition.create(
+ getLineNumber(),
+ getEndLineNumber(),
+ getCharacterNumber(),
+ getEndCharacterNumber());
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBrowserLineChanged(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+ private final LineData lineData_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/ContextDepthChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/ContextDepthChangedEvent.java
new file mode 100644
index 0000000..7729d34
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/ContextDepthChangedEvent.java
@@ -0,0 +1,103 @@
+/*
+ * ContextDepthChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.EnvironmentContextData;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ContextDepthChangedEvent extends
+ GwtEvent<ContextDepthChangedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onContextDepthChanged(ContextDepthChangedEvent event);
+ }
+
+ // This event is fired both as an event dispatched from the server and as
+ // an event from the client to set up state after requesting it from the
+ // server. The serverInitiated flag distinguishes between these states.
+ public ContextDepthChangedEvent(EnvironmentContextData data,
+ boolean serverInitiated)
+ {
+ contextData_ = data;
+ serverInitiated_ = serverInitiated;
+ }
+
+ public int getContextDepth()
+ {
+ return contextData_.contextDepth();
+ }
+
+ public JsArray<RObject> getEnvironmentList()
+ {
+ return contextData_.environmentList();
+ }
+
+ public JsArray<CallFrame> getCallFrames()
+ {
+ return contextData_.callFrames();
+ }
+
+ public String getFunctionName()
+ {
+ return contextData_.functionName();
+ }
+
+ public String getFunctionCode()
+ {
+ return contextData_.functionCode();
+ }
+
+ public boolean useProvidedSource()
+ {
+ return contextData_.useProvidedSource();
+ }
+
+ public String getEnvironmentName()
+ {
+ return contextData_.environmentName();
+ }
+
+ public boolean environmentIsLocal()
+ {
+ return contextData_.environmentIsLocal();
+ }
+
+ public boolean isServerInitiated()
+ {
+ return serverInitiated_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onContextDepthChanged(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+ private final EnvironmentContextData contextData_;
+ private final boolean serverInitiated_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugModeChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugModeChangedEvent.java
new file mode 100644
index 0000000..f8cf196
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugModeChangedEvent.java
@@ -0,0 +1,52 @@
+/*
+ * DebugModeChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class DebugModeChangedEvent
+ extends GwtEvent<DebugModeChangedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onDebugModeChanged(DebugModeChangedEvent event);
+ }
+
+ public DebugModeChangedEvent(boolean debugging)
+ {
+ debugging_ = debugging;
+ }
+
+ public boolean debugging()
+ {
+ return debugging_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onDebugModeChanged(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+ private final boolean debugging_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugSourceCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugSourceCompletedEvent.java
new file mode 100644
index 0000000..2a99a57
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/DebugSourceCompletedEvent.java
@@ -0,0 +1,62 @@
+/*
+ * DebugSourceCompletedEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import org.rstudio.studio.client.workbench.views.environment.model.DebugSourceResult;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class DebugSourceCompletedEvent
+ extends GwtEvent<DebugSourceCompletedEvent.Handler>
+{
+
+ public interface Handler extends EventHandler
+ {
+ void onDebugSourceCompleted(DebugSourceCompletedEvent event);
+ }
+
+ public static final GwtEvent.Type<DebugSourceCompletedEvent.Handler> TYPE =
+ new GwtEvent.Type<DebugSourceCompletedEvent.Handler>();
+
+ public DebugSourceCompletedEvent(DebugSourceResult result)
+ {
+ result_ = result;
+ }
+
+ public String getPath()
+ {
+ return result_.getPath();
+ }
+
+ public boolean getSucceeded()
+ {
+ return result_.getSucceeded();
+ }
+
+ @Override
+ protected void dispatch(DebugSourceCompletedEvent.Handler handler)
+ {
+ handler.onDebugSourceCompleted(this);
+ }
+
+ @Override
+ public GwtEvent.Type<DebugSourceCompletedEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private DebugSourceResult result_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectAssignedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectAssignedEvent.java
new file mode 100644
index 0000000..4d47486
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectAssignedEvent.java
@@ -0,0 +1,57 @@
+/*
+ * EnvironmentObjectAssignedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+
+public class EnvironmentObjectAssignedEvent
+ extends GwtEvent<EnvironmentObjectAssignedEvent.Handler>
+{
+
+ public interface Handler extends EventHandler
+ {
+ void onEnvironmentObjectAssigned(EnvironmentObjectAssignedEvent event);
+ }
+
+ public static final GwtEvent.Type<EnvironmentObjectAssignedEvent.Handler> TYPE =
+ new GwtEvent.Type<EnvironmentObjectAssignedEvent.Handler>();
+
+ public EnvironmentObjectAssignedEvent(RObject objectInfo)
+ {
+ objectInfo_ = objectInfo ;
+ }
+
+ public RObject getObjectInfo()
+ {
+ return objectInfo_;
+ }
+
+ @Override
+ protected void dispatch(EnvironmentObjectAssignedEvent.Handler handler)
+ {
+ handler.onEnvironmentObjectAssigned(this);
+ }
+
+ @Override
+ public GwtEvent.Type<EnvironmentObjectAssignedEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final RObject objectInfo_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectRemovedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectRemovedEvent.java
new file mode 100644
index 0000000..0d5a658
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentObjectRemovedEvent.java
@@ -0,0 +1,54 @@
+/*
+ * EnvironmentObjectRemovedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnvironmentObjectRemovedEvent
+ extends GwtEvent<EnvironmentObjectRemovedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onEnvironmentObjectRemoved(EnvironmentObjectRemovedEvent event);
+ }
+
+ public static final GwtEvent.Type<EnvironmentObjectRemovedEvent.Handler> TYPE =
+ new GwtEvent.Type<EnvironmentObjectRemovedEvent.Handler>();
+
+ public EnvironmentObjectRemovedEvent(String objectName)
+ {
+ objectName_ = objectName;
+ }
+
+ public String getObjectName()
+ {
+ return objectName_;
+ }
+
+ @Override
+ protected void dispatch(EnvironmentObjectRemovedEvent.Handler handler)
+ {
+ handler.onEnvironmentObjectRemoved(this);
+ }
+
+ @Override
+ public GwtEvent.Type<EnvironmentObjectRemovedEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String objectName_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentRefreshEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentRefreshEvent.java
new file mode 100644
index 0000000..8ebf0f0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/EnvironmentRefreshEvent.java
@@ -0,0 +1,44 @@
+/*
+ * EnvironmentRefreshEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EnvironmentRefreshEvent extends GwtEvent<EnvironmentRefreshEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onEnvironmentRefresh(EnvironmentRefreshEvent event);
+ }
+
+ public EnvironmentRefreshEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onEnvironmentRefresh(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/LineData.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/LineData.java
new file mode 100644
index 0000000..a45cabf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/events/LineData.java
@@ -0,0 +1,40 @@
+/*
+ * LineData.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class LineData extends JavaScriptObject
+{
+ protected LineData()
+ {
+ }
+
+ public final native int getLineNumber() /*-{
+ return this.line_number;
+ }-*/;
+
+ public final native int getEndLineNumber() /*-{
+ return this.end_line_number;
+ }-*/;
+
+ public final native int getCharacterNumber() /*-{
+ return this.character_number;
+ }-*/;
+
+ public final native int getEndCharacterNumber() /*-{
+ return this.end_character_number;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/CallFrame.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/CallFrame.java
new file mode 100644
index 0000000..277543d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/CallFrame.java
@@ -0,0 +1,113 @@
+/*
+ * CallFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import org.rstudio.core.client.DebugFilePosition;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CallFrame extends JavaScriptObject
+{
+ protected CallFrame()
+ {
+ }
+
+ public final native String getFunctionName() /*-{
+ return this.function_name;
+ }-*/;
+
+ public final native int getContextDepth() /*-{
+ return this.context_depth;
+ }-*/;
+
+ public final native String getFileName() /*-{
+ return this.file_name;
+ }-*/;
+
+ public final native int getLineNumber() /*-{
+ return this.line_number;
+ }-*/;
+
+ public final native int getEndLineNumber() /*-{
+ return this.end_line_number;
+ }-*/;
+
+ public final native int getCharacterNumber() /*-{
+ return this.character_number;
+ }-*/;
+
+ public final native int getEndCharacterNumber() /*-{
+ return this.end_character_number;
+ }-*/;
+
+ public final native String getArgumentList() /*-{
+ return this.argument_list;
+ }-*/;
+
+ public final native int getFunctionLineNumber() /*-{
+ return this.function_line_number;
+ }-*/;
+
+ public final native int getFunctionCharacterNumber() /*-{
+ return this.function_character_number;
+ }-*/;
+
+ public final native String getShinyFunctionLabel() /*-{
+ return this.shiny_function_label;
+ }-*/;
+
+ public final native boolean hasRealSrcref() /*-{
+ return this.real_sourceref;
+ }-*/;
+
+ public final native boolean isErrorHandler() /*-{
+ return this.is_error_handler;
+ }-*/;
+
+ public final native boolean isHidden() /*-{
+ return this.is_hidden;
+ }-*/;
+
+ public final boolean isSourceEquiv()
+ {
+ return getFunctionName() == "eval" && hasRealSrcref();
+ }
+
+ public final DebugFilePosition getRange()
+ {
+ return DebugFilePosition.create(
+ getLineNumber(),
+ getEndLineNumber(),
+ getCharacterNumber(),
+ getEndCharacterNumber());
+ };
+
+ public final boolean isNavigable()
+ {
+ return hasRealSrcref();
+ }
+
+ public final static boolean isNavigableFilename(String fileName)
+ {
+ if (fileName.length() > 0 &&
+ !fileName.equalsIgnoreCase("NULL") &&
+ !fileName.equalsIgnoreCase("<tmp>"))
+ {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DataPreviewResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DataPreviewResult.java
new file mode 100644
index 0000000..717c9fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DataPreviewResult.java
@@ -0,0 +1,59 @@
+/*
+ * DataPreviewResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.js.JsObject;
+
+public class DataPreviewResult extends JavaScriptObject
+{
+ protected DataPreviewResult()
+ {
+ }
+
+ public final native String getInputLines() /*-{
+ return this.inputLines[0];
+ }-*/;
+
+ public final native JsArray<JsObject> getOutput() /*-{
+ return this.output;
+ }-*/;
+
+ public final native JsArrayString getOutputNames() /*-{
+ return this.outputNames;
+ }-*/;
+
+ public final native boolean hasHeader() /*-{
+ return this.header[0];
+ }-*/;
+
+ public final native String getSeparator() /*-{
+ return this.separator[0];
+ }-*/;
+
+ public final native String getDecimal() /*-{
+ return this.decimal[0];
+ }-*/;
+
+ public final native String getQuote() /*-{
+ return this.quote[0];
+ }-*/;
+
+ public final native boolean getDefaultStringsAsFactors() /*-{
+ return this.defaultStringsAsFactors[0];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DebugSourceResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DebugSourceResult.java
new file mode 100644
index 0000000..85519b3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DebugSourceResult.java
@@ -0,0 +1,30 @@
+/*
+ * DebugSourceResult.java
+ *
+ * Copyright (C) 2009-2014 RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DebugSourceResult extends JavaScriptObject
+{
+ protected DebugSourceResult() {}
+
+ public final native String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public final native boolean getSucceeded() /*-{
+ return this.succeeded;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DownloadInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DownloadInfo.java
new file mode 100644
index 0000000..18dbd08
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/DownloadInfo.java
@@ -0,0 +1,32 @@
+/*
+ * DownloadInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DownloadInfo extends JavaScriptObject
+{
+ protected DownloadInfo()
+ {
+ }
+
+ public final native String getPath() /*-{
+ return this.path[0];
+ }-*/;
+
+ public final native String getVarname() /*-{
+ return this.varname[0];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentContextData.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentContextData.java
new file mode 100644
index 0000000..df47793
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentContextData.java
@@ -0,0 +1,55 @@
+/*
+ * EnvironmentContextData.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class EnvironmentContextData extends JavaScriptObject
+{
+ protected EnvironmentContextData() { }
+
+ public final native int contextDepth() /*-{
+ return this.context_depth;
+ }-*/;
+
+ public final native String functionName() /*-{
+ return this.function_name;
+ }-*/;
+
+ public final native JsArray<CallFrame> callFrames() /*-{
+ return this.call_frames;
+ }-*/;
+
+ public final native boolean useProvidedSource() /*-{
+ return this.use_provided_source;
+ }-*/;
+
+ public final native String functionCode() /*-{
+ return this.function_code;
+ }-*/;
+
+ public final native JsArray<RObject> environmentList() /*-{
+ return this.environment_list;
+ }-*/;
+
+ public final native String environmentName() /*-{
+ return this.environment_name;
+ }-*/;
+
+ public final native boolean environmentIsLocal() /*-{
+ return this.environment_is_local;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentFrame.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentFrame.java
new file mode 100644
index 0000000..a65f85c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentFrame.java
@@ -0,0 +1,35 @@
+/*
+ * EnvironmentFrame.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class EnvironmentFrame extends JavaScriptObject
+{
+ protected EnvironmentFrame() { }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native int getFrame() /*-{
+ return this.frame;
+ }-*/;
+
+ public final native boolean isLocal() /*-{
+ return this.local;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentServerOperations.java
new file mode 100644
index 0000000..6816d9f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/EnvironmentServerOperations.java
@@ -0,0 +1,69 @@
+/*
+ * EnvironmentServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import java.util.List;
+
+import com.google.gwt.core.client.JsArray;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+public interface EnvironmentServerOperations
+{
+ void listEnvironment(ServerRequestCallback<JsArray<RObject> > callback);
+
+ void removeAllObjects(boolean includeHidden,
+ ServerRequestCallback<Void> requestCallback);
+
+ void removeObjects(List<String> objectNames,
+ ServerRequestCallback<Void> requestCallback);
+
+ void downloadDataFile(String dataFileURL,
+ ServerRequestCallback<DownloadInfo> requestCallback);
+
+ void getDataPreview(
+ String dataFilePath,
+ ServerRequestCallback<DataPreviewResult> requestCallback);
+
+ void getOutputPreview(
+ String dataFilePath,
+ boolean heading,
+ String separator,
+ String decimal,
+ String quote,
+ ServerRequestCallback<DataPreviewResult> requestCallback);
+
+ void setContextDepth(int newContextDepth,
+ ServerRequestCallback<Void> requestCallback);
+
+ void setEnvironment(String environmentName,
+ ServerRequestCallback<Void> requestCallback);
+
+ void setEnvironmentFrame(int frame,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getEnvironmentNames(
+ ServerRequestCallback<JsArray<EnvironmentFrame>> requestCallback);
+
+ void getEnvironmentState(
+ ServerRequestCallback<EnvironmentContextData> requestCallback);
+
+ void getObjectContents(
+ String objectName,
+ ServerRequestCallback<ObjectContents> requestCallback);
+
+ void requeryContext(ServerRequestCallback<Void> requestCallback);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/ObjectContents.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/ObjectContents.java
new file mode 100644
index 0000000..bd2c09e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/ObjectContents.java
@@ -0,0 +1,27 @@
+/*
+ * ObjectContents.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class ObjectContents extends JavaScriptObject
+{
+ protected ObjectContents() {}
+
+ public native final JsArrayString getContents() /*-{
+ return this.contents;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/RObject.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/RObject.java
new file mode 100644
index 0000000..820898a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/model/RObject.java
@@ -0,0 +1,68 @@
+/*
+ * RObject.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class RObject extends JavaScriptObject
+{
+ protected RObject()
+ {
+ }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native boolean isData() /*-{
+ return this.is_data;
+ }-*/;
+
+ public final native String getValue() /*-{
+ return this.value ? this.value : "NO_VALUE";
+ }-*/;
+
+ public final native String getDescription() /*-{
+ return this.description ? this.description : "";
+ }-*/;
+
+ public final native JsArrayString getContents() /*-{
+ return this.contents;
+ }-*/;
+
+ public final native int getLength() /*-{
+ return this.length;
+ }-*/;
+
+ public final native int getSize() /*-{
+ return this.size;
+ }-*/;
+
+ public final native boolean getContentsDeferred() /*-{
+ return this.contents_deferred;
+ }-*/;
+
+ public final native void setDeferredContents(JsArrayString contents) /*-{
+ this.contents_deferred = false;
+ this.contents = contents;
+ }-*/;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/AttachedEnvironment.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/AttachedEnvironment.png
new file mode 100644
index 0000000..0add4b3
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/AttachedEnvironment.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.css
new file mode 100644
index 0000000..e8deb49
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.css
@@ -0,0 +1,30 @@
+.callFrame
+{
+ padding-left: 5px;
+ padding-right: 5px;
+ cursor: pointer;
+ border: 1px solid transparent;
+ font-size: 90%;
+ margin-bottom: 2px;
+ width: 100%;
+ box-sizing: border-box;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.activeFrame
+{
+ background-color: #FFEE9B;
+ cursor: default;
+}
+
+.noSourceFrame
+{
+ color: #A0A0A0;
+}
+
+.hiddenFrame
+{
+ display: none;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.java
new file mode 100644
index 0000000..e8dc321
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.java
@@ -0,0 +1,171 @@
+/*
+ * CallFrameItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.common.FilePathUtils;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+
+public class CallFrameItem extends Composite
+ implements ClickHandler
+{
+ public interface Binder extends UiBinder<Widget, CallFrameItem>
+ {
+ }
+
+ interface Style extends CssResource
+ {
+ String callFrame();
+ String activeFrame();
+ String noSourceFrame();
+ String hiddenFrame();
+ }
+
+ public CallFrameItem(CallFrame frame,
+ EnvironmentObjectsObserver observer, boolean hidden)
+ {
+ isActive_ = false;
+ isVisible_ = true;
+ observer_ = observer;
+ frame_ = frame;
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+ functionName.addClickHandler(this);
+ if (!frame.isNavigable() || hidden)
+ {
+ functionName.addStyleName(style.noSourceFrame());
+
+ // hide call frames for which we don't have usable sources--but leave
+ // them in the DOM (we may want to easily show/hide these at the user's
+ // request)
+ if (hidden)
+ {
+ functionName.addStyleName(style.hiddenFrame());
+ isVisible_ = false;
+ }
+ }
+ setDisplayText(frame_.getLineNumber());
+
+ FontSizer.applyNormalFontSize(this);
+ }
+
+ public void setActive()
+ {
+ functionName.addStyleName(style.activeFrame());
+ isActive_ = true;
+ }
+
+ public void updateLineNumber(int newLineNumber)
+ {
+ setDisplayText(newLineNumber);
+ }
+
+ public void onClick(ClickEvent event)
+ {
+ if (!isActive_)
+ {
+ observer_.changeContextDepth(frame_.getContextDepth());
+ }
+ }
+
+ public void setVisible(boolean visible)
+ {
+ if (visible != isVisible_)
+ {
+ if (visible)
+ {
+ functionName.removeStyleName(style.hiddenFrame());
+ }
+ else
+ {
+ functionName.addStyleName(style.hiddenFrame());
+ }
+ isVisible_ = visible;
+ }
+ }
+
+ public boolean isNavigable()
+ {
+ return frame_.isNavigable();
+ }
+
+ public boolean isHidden()
+ {
+ return frame_.isHidden();
+ }
+
+ // Private functions -------------------------------------------------------
+
+ private void setDisplayText(int lineNumber)
+ {
+ if (frame_.getContextDepth() > 0)
+ {
+ String fileLocation = "";
+ if (hasFileLocation())
+ {
+ fileLocation = " at " +
+ FilePathUtils.friendlyFileName(
+ frame_.getFileName()) + ":" +
+ lineNumber;
+ }
+ functionName.setText(
+ getFrameLabel() +
+ fileLocation);
+ }
+ else
+ {
+ functionName.setText(getFrameLabel());
+ }
+ }
+
+ private boolean hasFileLocation()
+ {
+ return CallFrame.isNavigableFilename(frame_.getFileName());
+ }
+
+ private String getFrameLabel()
+ {
+ if (frame_.isSourceEquiv())
+ {
+ return "[Debug source]";
+ }
+ if (frame_.getShinyFunctionLabel().isEmpty())
+ {
+ return frame_.getFunctionName() + "(" + frame_.getArgumentList() + ")";
+ }
+ else
+ {
+ return "[Shiny: " + frame_.getShinyFunctionLabel() + "]";
+ }
+ }
+
+ @UiField Label functionName;
+ @UiField Style style;
+
+ EnvironmentObjectsObserver observer_;
+ CallFrame frame_;
+ boolean isActive_;
+ boolean isVisible_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.ui.xml
new file mode 100644
index 0000000..b13b4c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFrameItem.ui.xml
@@ -0,0 +1,6 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style src="CallFrameItem.css" type="org.rstudio.studio.client.workbench.views.environment.view.CallFrameItem.Style" />
+ <g:Label ui:field="functionName" styleName="{style.callFrame}"></g:Label>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.css
new file mode 100644
index 0000000..9d42b49
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.css
@@ -0,0 +1,36 @@
+ at def callFramePanelMargin 12px;
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+
+.frameList
+{
+ box-sizing: border-box;
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+.framePanel
+{
+ padding-bottom: callFramePanelMargin;
+ padding-top: 3px;
+}
+
+.executionArrow
+{
+ position: absolute;
+ left: 3px;
+ top: 0.5em;
+}
+
+.tracebackHeader
+{
+ margin-top: 2px;
+ margin-left: 10px;
+}
+
+.toggleHide
+{
+ font-family: proportionalFont;
+ text-align: right;
+ font-weight: normal;
+ margin-right: 10px;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.java
new file mode 100644
index 0000000..066cde4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.java
@@ -0,0 +1,209 @@
+/*
+ * CallFramePanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.ResizeComposite;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class CallFramePanel extends ResizeComposite
+{
+ public interface Binder extends UiBinder<Widget, CallFramePanel>
+ {
+ }
+
+ public interface CallFramePanelHost
+ {
+ void minimizeCallFramePanel();
+ void restoreCallFramePanel();
+ boolean getShowInternalFunctions();
+ void setShowInternalFunctions(boolean hide);
+ }
+
+ public CallFramePanel(EnvironmentObjectsObserver observer, CallFramePanelHost panelHost)
+ {
+ final ThemeStyles globalStyles = ThemeResources.INSTANCE.themeStyles();
+ panelHost_ = panelHost;
+
+ // import the minimize button from the global theme resources
+ HTML minimize = new HTML();
+ minimize.setStylePrimaryName(globalStyles.minimize());
+ minimize.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (isMinimized_)
+ {
+ callFramePanelHeader.removeStyleName(globalStyles.minimizedWindow());
+ panelHost_.restoreCallFramePanel();
+ isMinimized_ = false;
+ }
+ else
+ {
+ callFramePanelHeader.addStyleName(globalStyles.minimizedWindow());
+ panelHost_.minimizeCallFramePanel();
+ isMinimized_ = true;
+ }
+ }
+ });
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ Label tracebackTitle = new Label("Traceback");
+ tracebackTitle.addStyleName(style.tracebackHeader());
+
+ callFramePanelHeader.addStyleName(globalStyles.windowframe());
+ callFramePanelHeader.add(tracebackTitle);
+ CheckBox showInternals = new CheckBox("Show internals");
+ showInternals.setValue(panelHost_.getShowInternalFunctions());
+ showInternals.addValueChangeHandler(
+ new ValueChangeHandler<Boolean>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ panelHost_.setShowInternalFunctions(event.getValue());
+ // Ignore the function on the top of the stack; we always
+ // want to show it since it's the execution point
+ for (int i = 1; i < callFrameItems_.size(); i++)
+ {
+ CallFrameItem item = callFrameItems_.get(i);
+ if (!item.isNavigable() && !item.isHidden())
+ {
+ item.setVisible(event.getValue());
+ }
+ }
+ }
+ }
+ );
+ showInternals.setStylePrimaryName(style.toggleHide());
+
+ callFramePanelHeader.add(showInternals);
+ callFramePanelHeader.setWidgetRightWidth(
+ showInternals, 28, Style.Unit.PX,
+ 30, Style.Unit.PCT);
+ callFramePanelHeader.add(minimize);
+ callFramePanelHeader.setWidgetRightWidth(minimize, 14, Style.Unit.PX,
+ 14, Style.Unit.PX);
+
+ observer_ = observer;
+ callFrameItems_ = new ArrayList<CallFrameItem>();
+ }
+
+ public void setCallFrames(JsArray<CallFrame> frameList, int contextDepth)
+ {
+ clearCallFrames();
+
+ // Check to see whether every function on the stack is internal.
+ // If it is, the traceback window may appear empty, so show everything
+ // to give the user some context.
+ boolean allInternal = true;
+ int idxSourceEquiv = Integer.MAX_VALUE;
+
+ for (int idx = 0; idx < frameList.length(); idx++)
+ {
+ CallFrame frame = frameList.get(idx);
+ if (frame.isNavigable()) {
+ allInternal = false;
+ }
+ if (frame.isSourceEquiv())
+ idxSourceEquiv = idx;
+ }
+
+ for (int idx = frameList.length() - 1; idx >= 0; idx--)
+ {
+ CallFrame frame = frameList.get(idx);
+ // Always show the first frame, since that's where execution is
+ // actually halted. From the remaining frames, show them if they are
+ // "navigable" (user) frames, or if the user has elected to show all
+ // frames.
+ CallFrameItem item = new CallFrameItem(
+ frame,
+ observer_,
+ frame.isHidden() ||
+ (!panelHost_.getShowInternalFunctions() &&
+ ((!frame.isNavigable()) || idx > idxSourceEquiv) &&
+ !allInternal &&
+ idx > 0));
+ if (contextDepth == frame.getContextDepth())
+ {
+ item.setActive();
+ }
+ callFrameItems_.add(item);
+ }
+
+ // now walk forwards through the frames and add each to the UI
+ Collections.reverse(callFrameItems_);
+ for (CallFrameItem item: callFrameItems_)
+ {
+ callFramePanel.add(item);
+ }
+ }
+
+ public void updateLineNumber(int newLineNumber)
+ {
+ if (callFrameItems_.size() > 0)
+ {
+ callFrameItems_.get(0).updateLineNumber(newLineNumber);
+ }
+ }
+
+ public void clearCallFrames()
+ {
+ callFramePanel.clear();
+ callFrameItems_.clear();
+ }
+
+ public int getDesiredPanelHeight()
+ {
+ return callFramePanelHeader.getOffsetHeight() +
+ callFramePanel.getOffsetHeight();
+ }
+
+ public boolean isMinimized()
+ {
+ return isMinimized_;
+ }
+
+ @UiField HTMLPanel callFramePanel;
+ @UiField CallFramePanelStyle style;
+ @UiField LayoutPanel callFramePanelHeader;
+
+ EnvironmentObjectsObserver observer_;
+ CallFramePanelHost panelHost_;
+ ArrayList<CallFrameItem> callFrameItems_;
+ boolean isMinimized_ = false;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.ui.xml
new file mode 100644
index 0000000..c0ce38e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanel.ui.xml
@@ -0,0 +1,18 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui">
+ <ui:style src="EnvironmentObjects.css" type="org.rstudio.studio.client.workbench.views.environment.view.EnvironmentStyle" field="environmentStyle" />
+ <ui:style src="CallFramePanel.css" type="org.rstudio.studio.client.workbench.views.environment.view.CallFramePanelStyle"></ui:style>
+ <ui:image src="ExecutionArrow.png" field="executionArrow"></ui:image>
+ <g:HeaderPanel styleName="{environmentStyle.environmentPanel}">
+ <g:LayoutPanel styleName="{environmentStyle.categoryHeaderRow}" ui:field="callFramePanelHeader" />
+ <g:ResizeLayoutPanel styleName="{environmentStyle.fillHeight}">
+ <g:ScrollPanel styleName="{environmentStyle.fillHeight}">
+ <g:HTMLPanel>
+ <g:Image resource="{executionArrow}" styleName="{style.executionArrow}"></g:Image>
+ <g:HTMLPanel styleName="{style.frameList} {style.framePanel}" ui:field="callFramePanel"></g:HTMLPanel>
+ </g:HTMLPanel>
+ </g:ScrollPanel>
+ </g:ResizeLayoutPanel>
+ </g:HeaderPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanelStyle.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanelStyle.java
new file mode 100644
index 0000000..a577c50
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CallFramePanelStyle.java
@@ -0,0 +1,24 @@
+/*
+ * CallFramePanelStyle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface CallFramePanelStyle extends CssResource
+{
+ String tracebackHeader();
+ String toggleHide();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CollapseIcon.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CollapseIcon.png
new file mode 100644
index 0000000..4fc6762
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/CollapseIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentClientState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentClientState.java
new file mode 100644
index 0000000..056c371
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentClientState.java
@@ -0,0 +1,55 @@
+/*
+ * EnvironmentClientState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class EnvironmentClientState extends JavaScriptObject
+{
+ protected EnvironmentClientState()
+ {
+ }
+
+ public static final native EnvironmentClientState create(
+ int scrollPosition,
+ String[] expandedObjects,
+ int sortColumn,
+ boolean ascendingSort) /*-{
+ var options = new Object();
+ options.scroll_position = scrollPosition;
+ options.expanded_objects = expandedObjects;
+ options.sort_column = sortColumn;
+ options.ascending_sort = ascendingSort;
+ return options;
+ }-*/;
+
+ public final native int getScrollPosition() /*-{
+ return this.scroll_position;
+ }-*/;
+
+ public final native JsArrayString getExpandedObjects() /*-{
+ return this.expanded_objects ? this.expanded_objects : [];
+ }-*/;
+
+ public final native int getSortColumn() /*-{
+ return this.sort_column;
+ }-*/;
+
+ public final native boolean getAscendingSort() /*-{
+ return this.ascending_sort;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectDisplay.java
new file mode 100644
index 0000000..b13bef2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectDisplay.java
@@ -0,0 +1,127 @@
+/*
+ * EnvironmentObjectDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import java.util.List;
+
+import org.rstudio.core.client.cellview.ScrollingDataGrid;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.workbench.views.environment.EnvironmentPane;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.Column;
+
+public abstract class EnvironmentObjectDisplay
+ extends ScrollingDataGrid<RObjectEntry>
+{
+ public interface Host
+ {
+ public boolean enableClickableObjects();
+ public boolean useStatePersistence();
+ public String getFilterText();
+ public int getSortColumn();
+ public void setSortColumn(int col);
+ public void toggleAscendingSort();
+ boolean getAscendingSort();
+ boolean getShowInternalFunctions();
+ void setShowInternalFunctions(boolean hide);
+ public void fillObjectContents(RObject object, Operation onCompleted);
+ }
+
+ public EnvironmentObjectDisplay(Host host,
+ EnvironmentObjectsObserver observer,
+ String environmentName)
+ {
+ super(1024, RObjectEntry.KEY_PROVIDER);
+
+ observer_ = observer;
+ host_ = host;
+ environmentStyle_ = EnvironmentResources.INSTANCE.environmentStyle();
+ environmentStyle_.ensureInjected();
+ environmentName_ = environmentName;
+ filterRenderer_ = new AbstractSafeHtmlRenderer<String>()
+ {
+ @Override
+ public SafeHtml render(String str)
+ {
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ boolean hasMatch = false;
+ String filterText = host_.getFilterText();
+ if (filterText.length() > 0)
+ {
+ int idx = str.toLowerCase().indexOf(filterText);
+ if (idx >= 0)
+ {
+ hasMatch = true;
+ sb.appendEscaped(str.substring(0, idx));
+ sb.appendHtmlConstant(
+ "<span class=\"" +
+ environmentStyle_.filterMatch() +
+ "\">");
+ sb.appendEscaped(str.substring(idx,
+ idx + filterText.length()));
+ sb.appendHtmlConstant("</span>");
+ sb.appendEscaped(str.substring(idx + filterText.length(),
+ str.length()));
+ }
+ }
+ if (!hasMatch)
+ sb.appendEscaped(str);
+ return sb.toSafeHtml();
+ }
+ };
+ }
+
+ public abstract List<String> getSelectedObjects();
+ public abstract void clearSelection();
+
+ public void setEnvironmentName(String environmentName)
+ {
+ environmentName_ = environmentName;
+ }
+
+ // attaches a handler to a column that invokes the associated object
+ protected void attachClickToInvoke(Column<RObjectEntry, String> column)
+ {
+ column.setFieldUpdater(new FieldUpdater<RObjectEntry, String>()
+ {
+ @Override
+ public void update(int index, RObjectEntry object, String value)
+ {
+ if (object.getCategory() == RObjectEntry.Categories.Data &&
+ host_.enableClickableObjects())
+ {
+ observer_.viewObject(object.rObject.getName());
+ }
+ }
+ });
+ }
+
+ protected boolean selectionEnabled()
+ {
+ return environmentName_.equals(EnvironmentPane.GLOBAL_ENVIRONMENT_NAME);
+ }
+
+ protected AbstractSafeHtmlRenderer<String> filterRenderer_;
+ protected EnvironmentObjectsObserver observer_;
+ protected Host host_;
+ protected EnvironmentStyle environmentStyle_;
+ protected String environmentName_ = "";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.css
new file mode 100644
index 0000000..79e9cb2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.css
@@ -0,0 +1,53 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+
+.objectGridColumn,
+.objectGridHeader
+{
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-left: 4px;
+}
+
+th.objectGridHeader
+{
+ font-family: proportionalFont;
+ border-bottom: 1px solid #d6dadc;
+ border-right: 1px solid #d6dadc;
+ border-top: 1px solid white;
+ border-left: 1px solid white;
+ padding: 1px 2px;
+ color: #3c474d;
+ background-color: #eeeff1;
+ font-weight: normal;
+ text-align: left;
+ border-collapse: separate;
+ font-size: 11px;
+}
+
+.valueColumn
+{
+ padding-right: 15px;
+}
+
+.objectGrid table
+{
+ border-collapse: separate;
+}
+
+.checkColumn,
+th.checkColumn
+{
+ text-align: center;
+}
+
+.checkColumn input
+{
+ margin: 0px;
+}
+
+.dataFrameValueCol
+{
+ padding-right: 35px;
+ cursor: pointer;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.java
new file mode 100644
index 0000000..31c73b4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectGrid.java
@@ -0,0 +1,376 @@
+/*
+ * EnvironmentObjectGrid.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.studio.client.workbench.views.environment.view.RObjectEntry.Categories;
+
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.dom.builder.shared.TableCellBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.cellview.client.AbstractCellTable;
+import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
+import com.google.gwt.user.cellview.client.AbstractHeaderOrFooterBuilder;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.Header;
+import com.google.gwt.view.client.DefaultSelectionEventManager;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.SelectionModel;
+
+public class EnvironmentObjectGrid extends EnvironmentObjectDisplay
+{
+ public interface Style extends CssResource
+ {
+ String objectGridColumn();
+ String objectGridHeader();
+ String checkColumn();
+ String objectGrid();
+ String valueColumn();
+ String dataFrameValueCol();
+ }
+
+ public interface Resources extends ClientBundle
+ {
+ @Source("EnvironmentObjectGrid.css")
+ Style style();
+ }
+
+ public EnvironmentObjectGrid(EnvironmentObjectDisplay.Host host,
+ EnvironmentObjectsObserver observer,
+ String environmentName)
+ {
+ super(host, observer, environmentName);
+ style_ = ((Resources)GWT.create(Resources.class)).style();
+ style_.ensureInjected();
+ selection_ = new MultiSelectionModel<RObjectEntry>(
+ RObjectEntry.KEY_PROVIDER);
+
+ createColumns();
+ setTableBuilder(new EnvironmentObjectGridBuilder(this));
+ setHeaderBuilder(new GridHeaderBuilder(this, false));
+ setSkipRowHoverCheck(true);
+ setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+ setSelectionModel(selection_,
+ DefaultSelectionEventManager.<RObjectEntry>createCheckboxManager(0));
+ addStyleName(style_.objectGrid());
+ }
+
+ // Returns the objects that should be considered selected.
+ // - If one or more objects are manually selected, that set of objects is
+ // returned.
+ // - If no objects are manually selected but the list is filtered, the
+ // the objects that match the filter are returned.
+ // - If no selection or filter is present, an empty list is returned
+ // (generally this causes operations to act on the whole list)
+ @Override
+ public List<String> getSelectedObjects()
+ {
+ boolean hasFilter = !host_.getFilterText().isEmpty();
+ ArrayList<String> selectedObjectNames = new ArrayList<String>();
+ ArrayList<String> filteredObjectNames = new ArrayList<String>();
+ List<RObjectEntry> objects = getVisibleItems();
+ for (RObjectEntry object: objects)
+ {
+ if (object.visible)
+ {
+ if (hasFilter)
+ {
+ filteredObjectNames.add(object.rObject.getName());
+ }
+ if (selection_.isSelected(object))
+ {
+ selectedObjectNames.add(object.rObject.getName());
+ }
+ }
+ }
+ return selectedObjectNames.size() == 0 ? filteredObjectNames :
+ selectedObjectNames;
+ }
+
+ @Override
+ public void clearSelection()
+ {
+ setSelectAll(false);
+ redrawHeaders();
+ }
+
+ @Override
+ public void setEnvironmentName(String environmentName)
+ {
+ // When the environment changes, we need to redraw the headers to
+ // (possibly) adjust for the presence or absence of the selection
+ // column.
+ super.setEnvironmentName(environmentName);
+ if (columns_.size() > 0)
+ {
+ columns_.get(0).setWidth(selectionEnabled() ? 20 : 25);
+ }
+ setColumnWidths();
+ }
+
+ // Private methods ---------------------------------------------------------
+
+ private void createColumns()
+ {
+ checkColumn_ = new Column<RObjectEntry, Boolean>(
+ new CheckboxCell(false, false))
+ {
+ @Override
+ public Boolean getValue(RObjectEntry value)
+ {
+ return selection_.isSelected(value);
+ }
+ };
+ addColumn(checkColumn_);
+ checkHeader_ = new Header<Boolean>(new CheckboxCell())
+ {
+ @Override
+ public Boolean getValue()
+ {
+ return selectAll_;
+ }
+ };
+ checkHeader_.setUpdater(new ValueUpdater<Boolean>()
+ {
+ @Override
+ public void update(Boolean value)
+ {
+ if (selectAll_ != value)
+ {
+ setSelectAll(value);
+ }
+ }
+ });
+
+ columns_.add(new ObjectGridColumn(
+ new ClickableTextCell(filterRenderer_), "Name",
+ selectionEnabled() ? 20 : 25,
+ ObjectGridColumn.COLUMN_NAME, host_)
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return object.rObject.getName();
+ }
+ });
+ columns_.add(new ObjectGridColumn(
+ new ClickableTextCell(), "Type", 15,
+ ObjectGridColumn.COLUMN_TYPE, host_)
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return object.rObject.getType();
+ }
+ });
+ columns_.add(new ObjectGridColumn(
+ new ClickableTextCell(), "Length", 10,
+ ObjectGridColumn.COLUMN_LENGTH, host_)
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return (new Integer(object.rObject.getLength())).toString();
+ }
+ });
+ columns_.add(new ObjectGridColumn(
+ new ClickableTextCell(), "Size", 12,
+ ObjectGridColumn.COLUMN_SIZE, host_)
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return StringUtil.formatFileSize(object.rObject.getSize());
+ }
+ });
+ columns_.add(new ObjectGridColumn(
+ new ClickableTextCell(filterRenderer_),
+ "Value", 38,
+ ObjectGridColumn.COLUMN_VALUE, host_)
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return object.getDisplayValue();
+ }
+ });
+ for (ObjectGridColumn column: columns_)
+ {
+ if (column.getType() == ObjectGridColumn.COLUMN_VALUE)
+ {
+ attachClickToInvoke(column);
+ }
+ addColumn(column);
+ }
+ setColumnWidths();
+ }
+
+ private void setSelectAll(boolean selected)
+ {
+ List<RObjectEntry> objects = getVisibleItems();
+ for (RObjectEntry object: objects)
+ {
+ if (object.visible)
+ {
+ selection_.setSelected(object, selected);
+ }
+ }
+ selectAll_ = selected;
+ }
+
+ private class GridHeaderBuilder
+ extends AbstractHeaderOrFooterBuilder<RObjectEntry>
+ {
+ public GridHeaderBuilder(AbstractCellTable<RObjectEntry> table,
+ boolean isFooter)
+ {
+ super(table, isFooter);
+ setSortIconStartOfLine(false);
+ }
+
+ @Override
+ protected boolean buildHeaderOrFooterImpl()
+ {
+ // Render the "select all" checkbox header cell
+ TableRowBuilder row = startRow();
+
+ if (selectionEnabled())
+ {
+ TableCellBuilder selectAll = row.startTH();
+ selectAll.className(style_.objectGridHeader() + " " +
+ style_.checkColumn());
+ renderHeader(selectAll, new Cell.Context(0, 0, null), checkHeader_);
+ selectAll.end();
+ }
+
+ // Render a header for each column
+ for (int i = 0; i < columns_.size(); i++)
+ {
+ ObjectGridColumn col = columns_.get(i);
+ TableCellBuilder cell = row.startTH();
+ cell.className(style_.objectGridHeader());
+ Cell.Context context = new Cell.Context(0, i, null);
+ renderSortableHeader(cell, context, col.getHeader(),
+ i == host_.getSortColumn(),
+ host_.getAscendingSort());
+ cell.endTH();
+ }
+ row.end();
+ return true;
+ }
+ }
+
+ private void setColumnWidths()
+ {
+ int start = 0;
+ if (selectionEnabled())
+ {
+ setColumnWidth(start++, "5%");
+ }
+ else
+ {
+ // Clear the width of the last column (it's going to go away entirely
+ // if we're dropping the selection column).
+ clearColumnWidth(columns_.size());
+ }
+ for (int i = 0; i < columns_.size(); i++)
+ {
+ setColumnWidth(
+ start + i,
+ new Integer(columns_.get(i).getWidth()).toString() + "%");
+ }
+ }
+
+ // builds individual rows of the object table
+ private class EnvironmentObjectGridBuilder
+ extends AbstractCellTableBuilder<RObjectEntry>
+ {
+
+ public EnvironmentObjectGridBuilder(
+ AbstractCellTable<RObjectEntry> cellTable)
+ {
+ super(cellTable);
+ }
+
+ @Override
+ protected void buildRowImpl(RObjectEntry rowValue, int absRowIndex)
+ {
+ if (!rowValue.visible)
+ return;
+
+ TableRowBuilder row = startRow();
+
+ if (selectionEnabled())
+ {
+ TableCellBuilder check = row.startTD();
+ check.className(style_.checkColumn());
+ check.style().width(5, Unit.PCT);
+ renderCell(check, createContext(0), checkColumn_, rowValue);
+ check.endTD();
+ }
+
+ for (int i = 0; i < columns_.size(); i++)
+ {
+ ObjectGridColumn col = columns_.get(i);
+ TableCellBuilder td = row.startTD();
+ String className = style_.objectGridColumn();
+ if (col.getType() == ObjectGridColumn.COLUMN_VALUE)
+ {
+ className += " " + style_.valueColumn();
+ if (host_.enableClickableObjects() &&
+ rowValue.getCategory() == Categories.Data)
+ {
+ className += " " + style_.dataFrameValueCol() +
+ " " +
+ ThemeStyles.INSTANCE.environmentDataFrameCol();
+ }
+ if (rowValue.isPromise())
+ {
+ className += " " + environmentStyle_.unevaluatedPromise();
+ }
+ td.title(rowValue.getDisplayValue());
+ }
+ if (col.getType() == ObjectGridColumn.COLUMN_NAME)
+ {
+ td.title(rowValue.rObject.getName());
+ }
+ td.className(className);
+ renderCell(td, createContext(i+1), col, rowValue);
+ td.endTD();
+ }
+
+ row.end();
+ }
+ }
+
+ private Column<RObjectEntry, Boolean> checkColumn_;
+ private Header<Boolean> checkHeader_;
+ private ArrayList<ObjectGridColumn> columns_ =
+ new ArrayList<ObjectGridColumn>();
+ private Style style_;
+ private SelectionModel<RObjectEntry> selection_;
+ private boolean selectAll_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.css
new file mode 100644
index 0000000..0b6e11d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.css
@@ -0,0 +1,107 @@
+ at def headerRowHeight 20px;
+
+.categoryHeaderRow
+{
+ background-color: #f0f0f0;
+ font-weight: bold;
+ height: headerRowHeight;
+}
+
+.expandIcon
+{
+ position: relative;
+ top: 1px;
+ left: 3px;
+ height: 14px;
+ width: 14px;
+}
+
+.unclickableIcon
+{
+ cursor: default;
+}
+
+.unevaluatedPromise
+{
+ color: #a0a0a0;
+}
+
+.widthSettingRow
+{
+ margin: 0px;
+ padding: 0px;
+}
+
+td.nameCol
+{
+ width: 25%;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ border: 1px solid #f0f0f0;
+ border-left: none;
+ background: none;
+}
+
+td.expandCol
+{
+ border: 1px solid #f0f0f0;
+ border-right: none;
+ width: 20px;
+}
+
+td.valueCol
+{
+ border: 1px solid #f0f0f0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ padding-right: 20px;
+ background-color: #ffffff;
+}
+
+.widthSettingRow td.expandCol,
+.widthSettingRow td.nameCol,
+.widthSettingRow td.valueCol
+{
+ margin: 0px;
+ padding: 0px;
+ height: 0px;
+ border: none;
+}
+
+.categoryHeaderText
+{
+ padding-left: 5px;
+}
+
+.clickableCol
+{
+ cursor: pointer;
+}
+
+td.dataFrameValueCol
+{
+ padding-right: 35px;
+}
+
+.detailRow
+{
+ border: 1px solid #f0f0f0;
+ font-size: 90%;
+}
+
+.detailRow td
+{
+ padding-left: 10px;
+ padding-right: 20px;
+ border: none;
+ white-space: nowrap;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+.objectList table,
+.objectList td
+{
+ border-collapse: collapse;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.java
new file mode 100644
index 0000000..3697566
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectList.java
@@ -0,0 +1,434 @@
+/*
+ * EnvironmentObjectList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.resources.CoreResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.workbench.views.environment.view.RObjectEntry.Categories;
+
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.dom.builder.shared.TableCellBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.AbstractCellTable;
+import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.view.client.NoSelectionModel;
+
+public class EnvironmentObjectList extends EnvironmentObjectDisplay
+{
+ public interface Style extends CssResource
+ {
+ String categoryHeaderRow();
+ String expandIcon();
+ String unclickableIcon();
+ String unevaluatedPromise();
+ String widthSettingRow();
+ String expandCol();
+ String nameCol();
+ String valueCol();
+ String categoryHeaderText();
+ String clickableCol();
+ String dataFrameValueCol();
+ String detailRow();
+ String objectList();
+ }
+
+ public interface Resources extends ClientBundle
+ {
+ @Source("EnvironmentObjectList.css")
+ Style style();
+ }
+
+ public EnvironmentObjectList(EnvironmentObjectDisplay.Host host,
+ EnvironmentObjectsObserver observer,
+ String environmentName)
+ {
+ super(host, observer, environmentName);
+ setTableBuilder(new EnvironmentObjectTableBuilder(this));
+
+ // disable persistent and transient row selection (currently necessary
+ // because we emit more than one row per object and the DataGrid selection
+ // behaviors aren't designed to work that way)
+ setSelectionModel(new NoSelectionModel<RObjectEntry>(
+ RObjectEntry.KEY_PROVIDER));
+ setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+
+ createColumns();
+ addColumn(objectExpandColumn_);
+ addColumn(objectNameColumn_);
+ addColumn(objectDescriptionColumn_);
+ setSkipRowHoverCheck(true);
+ style_ = ((Resources)GWT.create(Resources.class)).style();
+ style_.ensureInjected();
+ addStyleName(style_.objectList());
+ }
+
+ @Override
+ public List<String> getSelectedObjects()
+ {
+ // If the view is unfiltered, return nothing.
+ if (host_.getFilterText().isEmpty())
+ {
+ return new ArrayList<String>();
+ }
+
+ // If the view is filtered, return items that are visible.
+ ArrayList<String> objectNames = new ArrayList<String>();
+ List<RObjectEntry> objects = getVisibleItems();
+ for (RObjectEntry object: objects)
+ {
+ if (object.visible)
+ {
+ objectNames.add(object.rObject.getName());
+ }
+ }
+ return objectNames;
+ }
+
+ @Override
+ public void clearSelection()
+ {
+ // No selection to clear in list view
+ }
+
+ private void createColumns()
+ {
+ createExpandColumn();
+ createNameColumn(filterRenderer_);
+ createDescriptionColumn(filterRenderer_);
+ }
+
+ private void createNameColumn(SafeHtmlRenderer<String> renderer)
+ {
+ // the name of the object (simple text column)
+ objectNameColumn_ = new Column<RObjectEntry, String>(
+ new ClickableTextCell(renderer))
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return object.rObject.getName();
+ }
+ };
+ attachClickToInvoke(objectNameColumn_);
+ }
+
+ private void createDescriptionColumn(SafeHtmlRenderer<String> renderer)
+ {
+ // the description *or* value of the object; when clicked, we'll view
+ // or edit the data inside the object.
+ objectDescriptionColumn_ = new Column<RObjectEntry, String>(
+ new ClickableTextCell(renderer))
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ return object.getDisplayValue();
+ }
+ };
+ attachClickToInvoke(objectDescriptionColumn_);
+ }
+
+ private void createExpandColumn()
+ {
+ // the column containing the expand command; available only on objects
+ // with contents (such as lists and data frames).
+ SafeHtmlRenderer<String> expanderRenderer =
+ new AbstractSafeHtmlRenderer<String>()
+ {
+ @Override
+ public SafeHtml render(String object)
+ {
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ sb.appendHtmlConstant(object);
+ return sb.toSafeHtml();
+ }
+ };
+ objectExpandColumn_ = new Column<RObjectEntry, String>(
+ new ClickableTextCell(expanderRenderer))
+ {
+ @Override
+ public String getValue(RObjectEntry object)
+ {
+ String imageUri = "";
+ String imageStyle = style_.expandIcon();
+ if (object.canExpand())
+ {
+ ImageResource expandImage =
+ object.isExpanding ?
+ CoreResources.INSTANCE.progress() :
+ object.expanded ?
+ EnvironmentResources.INSTANCE.collapseIcon() :
+ EnvironmentResources.INSTANCE.expandIcon();
+
+ imageUri = expandImage.getSafeUri().asString();
+ }
+ else if (object.hasTraceInfo())
+ {
+ imageUri = EnvironmentResources.INSTANCE
+ .tracedFunction().getSafeUri().asString();
+ imageStyle += (" " + style_.unclickableIcon());
+ }
+ if (imageUri.length() > 0)
+ {
+ return "<input type=\"image\" src=\"" + imageUri + "\" " +
+ "class=\"" + imageStyle + "\" />";
+ }
+ return "";
+ }
+ };
+ objectExpandColumn_.setFieldUpdater(
+ new FieldUpdater<RObjectEntry, String>()
+ {
+ @Override
+ public void update(int index,
+ RObjectEntry object,
+ String value)
+ {
+ if (!object.canExpand())
+ return;
+ expandObject(index, object);
+ }
+ });
+ }
+
+ private void expandObject(final int index, final RObjectEntry object)
+ {
+ if (!object.expanded &&
+ !object.isExpanding &&
+ object.rObject.getContentsDeferred())
+ {
+ // Prevent reentry
+ object.isExpanding = true;
+ redrawRow(index);
+
+ // If the contents are deferred, we'll need to go to the server to
+ // get them.
+ host_.fillObjectContents(object.rObject, new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ object.expanded = true;
+ object.isExpanding = false;
+ redrawRow(index);
+ }
+ });
+ }
+ else if (!object.rObject.getContentsDeferred())
+ {
+ object.expanded = !object.expanded;
+
+ // Tell the observer this happened, so it can persist. Don't persist
+ // expansion state for deferred-content objects, since we don't want
+ // those to try to expand at app init.
+ if (host_.useStatePersistence() &&
+ !object.contentsAreDeferred)
+ {
+ if (object.expanded)
+ observer_.setObjectExpanded(object.rObject.getName());
+ else
+ observer_.setObjectCollapsed(object.rObject.getName());
+ }
+ redrawRow(index);
+ }
+ }
+
+ // builds individual rows of the object table
+ private class EnvironmentObjectTableBuilder
+ extends AbstractCellTableBuilder<RObjectEntry>
+ {
+ public EnvironmentObjectTableBuilder(
+ AbstractCellTable<RObjectEntry> cellTable)
+ {
+ super(cellTable);
+ }
+
+ // (re)build the given row
+ public void buildRowImpl(RObjectEntry rowValue, int absRowIndex)
+ {
+ // build nothing for invisible rows
+ if (!rowValue.visible)
+ return;
+
+ // build the header for the row (if any)
+ buildRowHeader(rowValue, absRowIndex);
+
+ TableRowBuilder row = startRow();
+
+ // build the columns
+ buildExpandColumn(rowValue, row);
+ buildNameColumn(rowValue, row);
+ buildDescriptionColumn(rowValue, row);
+
+ row.endTR();
+
+ // if the row is expanded, draw its content
+ if (rowValue.expanded)
+ {
+ buildExpandedContentRow(rowValue);
+ }
+ }
+
+ private void buildExpandColumn(RObjectEntry rowValue, TableRowBuilder row)
+ {
+ TableCellBuilder expandCol = row.startTD();
+ expandCol.className(style_.expandCol());
+ renderCell(expandCol, createContext(0), objectExpandColumn_, rowValue);
+ expandCol.endTD();
+ }
+
+ private void buildNameColumn(RObjectEntry rowValue, TableRowBuilder row)
+ {
+ TableCellBuilder nameCol = row.startTD();
+ String styleName = style_.nameCol();
+ if (rowValue.getCategory() == Categories.Data &&
+ host_.enableClickableObjects())
+ {
+ styleName += (" " + style_.clickableCol());
+ }
+ String size = rowValue.rObject.getSize() > 0 ?
+ ", " + rowValue.rObject.getSize() + " bytes" :
+ "";
+ nameCol.className(styleName);
+ nameCol.title(
+ rowValue.rObject.getName() +
+ " (" + rowValue.rObject.getType() + size + ")");
+ renderCell(nameCol, createContext(1), objectNameColumn_, rowValue);
+ nameCol.endTD();
+ }
+
+ private void buildDescriptionColumn(RObjectEntry rowValue,
+ TableRowBuilder row)
+ {
+ // build the column containing the description of the object
+ TableCellBuilder descCol = row.startTD();
+ String title = rowValue.rObject.getValue();
+ if ((!title.equals(RObjectEntry.NO_VALUE)) &&
+ title != null)
+ {
+ if (rowValue.isPromise())
+ {
+ title += " (unevaluated promise)";
+ }
+ descCol.title(title);
+ }
+ String descriptionStyle = style_.valueCol();
+ if (rowValue.isPromise())
+ {
+ descriptionStyle += (" " + style_.unevaluatedPromise());
+ }
+ else if (rowValue.getCategory() == RObjectEntry.Categories.Data &&
+ host_.enableClickableObjects())
+ {
+ descriptionStyle += (" " +
+ style_.dataFrameValueCol() + " " +
+ style_.clickableCol());
+ }
+ if (rowValue.getCategory() == RObjectEntry.Categories.Data)
+ {
+ descriptionStyle += (" " +
+ ThemeStyles.INSTANCE.environmentDataFrameCol());
+ }
+ descCol.className(descriptionStyle);
+ renderCell(descCol, createContext(2), objectDescriptionColumn_, rowValue);
+ descCol.endTD();
+ }
+
+ private void buildRowHeader(RObjectEntry rowValue, int absRowIndex)
+ {
+ // if building the first row, we need to add a dummy row to the top.
+ // since the grid uses a fixed table layout, the first row sets the
+ // column widths, so we can't let the first row be a spanning header.
+ if (rowValue.isFirstObject)
+ {
+ TableRowBuilder widthSettingRow = startRow().className(
+ style_.widthSettingRow());
+ widthSettingRow.startTD().className(style_.expandCol()).endTD();
+ widthSettingRow.startTD().className(style_.nameCol()).endTD();
+ widthSettingRow.startTD().className(style_.valueCol()).endTD();
+ widthSettingRow.endTR();
+ }
+
+ // if this row is the first of its category, draw the category header
+ if (rowValue.isCategoryLeader)
+ {
+ String categoryTitle;
+ switch (rowValue.getCategory())
+ {
+ case RObjectEntry.Categories.Data:
+ categoryTitle = "Data";
+ break;
+ case RObjectEntry.Categories.Function:
+ categoryTitle = "Functions";
+ break;
+ default:
+ categoryTitle = "Values";
+ break;
+ }
+ TableRowBuilder leaderRow = startRow().className(
+ style_.categoryHeaderRow());
+ TableCellBuilder objectHeader = leaderRow.startTD();
+ objectHeader.colSpan(3)
+ .className(style_.categoryHeaderText())
+ .text(categoryTitle)
+ .endTD();
+ leaderRow.endTR();
+ }
+ }
+
+ // draw additional rows when the row has been expanded
+ private void buildExpandedContentRow(RObjectEntry rowValue)
+ {
+ JsArrayString contents = rowValue.rObject.getContents();
+
+ for (int idx = 0; idx < contents.length(); idx++)
+ {
+ TableRowBuilder detail = startRow().className(style_.detailRow());
+ detail.startTD().endTD();
+ TableCellBuilder objectDetail = detail.startTD();
+ String content = contents.get(idx);
+ // ignore the first two characters of output
+ // ("$ value:" becomes "value:")
+ content = content.substring(2, content.length()).trim();
+ objectDetail.colSpan(2)
+ .title(content)
+ .text(content)
+ .endTD();
+ detail.endTR();
+ }
+ }
+ }
+
+ private Style style_;
+
+ private Column<RObjectEntry, String> objectExpandColumn_;
+ private Column<RObjectEntry, String> objectNameColumn_;
+ private Column<RObjectEntry, String> objectDescriptionColumn_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.css
new file mode 100644
index 0000000..a1cd45f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.css
@@ -0,0 +1,143 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at def headerRowHeight 20px;
+
+.environmentPane
+{
+ font-family: fixedWidthFont;
+ background-color: #e0e0e0;
+}
+
+.environmentPanel
+{
+ background-color: #ffffff;
+}
+
+.objectGrid
+{
+ height: 100%;
+ width: 100%;
+ cursor: default;
+}
+
+.objectGrid td:focus
+{
+ outline: none;
+}
+
+.objectGrid td.nameCol
+{
+ width: 25%;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ border: 1px solid #f0f0f0;
+ border-left: none;
+ background: none;
+}
+
+.objectGrid td.expandCol
+{
+ border: 1px solid #f0f0f0;
+ border-right: none;
+ width: 20px;
+}
+
+.objectGrid td.valueCol
+{
+ border: 1px solid #f0f0f0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow-x: hidden;
+ padding-right: 20px;
+ background-color: #ffffff;
+}
+
+.objectGrid td.clickableCol
+{
+ cursor: pointer;
+}
+
+.objectGrid td.dataFrameValueCol
+{
+ padding-right: 35px;
+}
+
+.objectGrid .detailRow
+{
+ border: 1px solid #f0f0f0;
+ font-size: 90%;
+}
+
+.objectGrid .detailRow td
+{
+ padding-left: 10px;
+ padding-right: 20px;
+ border: none;
+ white-space: nowrap;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+.categoryHeaderRow
+{
+ background-color: #f0f0f0;
+ font-weight: bold;
+ height: headerRowHeight;
+}
+
+.categoryHeaderText
+{
+ padding-left: 5px;
+}
+
+.emptyEnvironmentPanel
+{
+ text-align: center;
+ margin-top: 30%;
+}
+
+.emptyEnvironmentMessage
+{
+ font-family: proportionalFont;
+ font-size: 90%;
+ color: #909090;
+}
+
+.unclickableIcon
+{
+ cursor: default;
+}
+
+.unevaluatedPromise
+{
+ color: #a0a0a0;
+}
+
+.widthSettingRow
+{
+ margin: 0px;
+ padding: 0px;
+}
+
+.widthSettingRow td.expandCol,
+.widthSettingRow td.nameCol,
+.widthSettingRow td.valueCol
+{
+ margin: 0px;
+ padding: 0px;
+ height: 0px;
+ border: none;
+}
+
+.fillHeight
+{
+ height: 100%;
+}
+
+.filterMatch
+{
+ background-color: #e8e8ff;
+}
+
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.java
new file mode 100644
index 0000000..2b70011
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.java
@@ -0,0 +1,696 @@
+/*
+ * EnvironmentObjects.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.view.client.ListDataProvider;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.cellview.AutoHidingSplitLayoutPanel;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.common.SuperDevMode;
+import org.rstudio.studio.client.workbench.views.environment.EnvironmentPane;
+import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+import org.rstudio.studio.client.workbench.views.environment.view.CallFramePanel.CallFramePanelHost;
+
+public class EnvironmentObjects extends ResizeComposite
+ implements CallFramePanelHost,
+ EnvironmentObjectDisplay.Host
+{
+ // Public interfaces -------------------------------------------------------
+
+ public interface Binder extends UiBinder<Widget, EnvironmentObjects>
+ {
+ }
+
+ // Constructor -------------------------------------------------------------
+
+ public EnvironmentObjects(EnvironmentObjectsObserver observer)
+ {
+ observer_ = observer;
+ contextDepth_ = 0;
+ environmentName_ = EnvironmentPane.GLOBAL_ENVIRONMENT_NAME;
+
+ objectDisplayType_ = OBJECT_LIST_VIEW;
+ objectDataProvider_ = new ListDataProvider<RObjectEntry>();
+ objectSort_ = new RObjectEntrySort();
+
+ // set up the call frame panel
+ callFramePanel_ = new CallFramePanel(observer_, this);
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ splitPanel.addSouth(callFramePanel_, 150);
+ splitPanel.setWidgetMinSize(callFramePanel_, style.headerRowHeight());
+
+ setObjectDisplay(objectDisplayType_);
+
+ FontSizer.applyNormalFontSize(this);
+ }
+
+ // Public methods ----------------------------------------------------------
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+ if (pendingCallFramePanelSize_)
+ {
+ autoSizeCallFramePanel();
+ }
+ }
+
+ public void setContextDepth(int contextDepth)
+ {
+ if (contextDepth > 0)
+ {
+ splitPanel.setWidgetHidden(callFramePanel_, false);
+ splitPanel.onResize();
+ }
+ else if (contextDepth == 0)
+ {
+ callFramePanel_.clearCallFrames();
+ splitPanel.setWidgetHidden(callFramePanel_, true);
+ }
+ contextDepth_ = contextDepth;
+ }
+
+ public void addObject(RObject obj)
+ {
+ int idx = indexOfExistingObject(obj.getName());
+ RObjectEntry newEntry = entryFromRObject(obj);
+ boolean added = false;
+
+ // if the object is already in the environment, just update the value
+ if (idx >= 0)
+ {
+ final RObjectEntry oldEntry = objectDataProvider_.getList().get(idx);
+
+ if (oldEntry.rObject.getType().equals(obj.getType()))
+ {
+ // type did not change; update in-place and preserve expansion flag
+ newEntry.expanded = oldEntry.expanded;
+ objectDataProvider_.getList().set(idx, newEntry);
+ added = true;
+ }
+ else
+ {
+ // types did change, do a full add/remove
+ objectDataProvider_.getList().remove(idx);
+ }
+
+ }
+ if (!added)
+ {
+ RObjectEntry entry = entryFromRObject(obj);
+ idx = indexOfNewObject(entry);
+ objectDataProvider_.getList().add(idx, entry);
+ }
+ updateCategoryLeaders(true);
+ objectDisplay_.getRowElement(idx).scrollIntoView();
+ }
+
+ public void removeObject(String objName)
+ {
+ int idx = indexOfExistingObject(objName);
+ if (idx >= 0)
+ {
+ objectDataProvider_.getList().remove(idx);
+ }
+
+ updateCategoryLeaders(true);
+ }
+
+ public void clearObjects()
+ {
+ objectDataProvider_.getList().clear();
+ }
+
+ public void clearSelection()
+ {
+ objectDisplay_.clearSelection();
+ }
+
+ // bulk add for objects--used on init or environment switch
+ public void addObjects(JsArray<RObject> objects)
+ {
+ // create an entry for each object and sort the array
+ int numObjects = objects.length();
+ ArrayList<RObjectEntry> objectEntryList = new ArrayList<RObjectEntry>();
+ for (int i = 0; i < numObjects; i++)
+ {
+ RObjectEntry entry = entryFromRObject(objects.get(i));
+ objectEntryList.add(entry);
+ }
+ Collections.sort(objectEntryList, objectSort_);
+
+ // push the list into the UI and update category leaders
+ objectDataProvider_.getList().addAll(objectEntryList);
+ updateCategoryLeaders(false);
+
+ if (useStatePersistence())
+ {
+ setDeferredState();
+ }
+ }
+
+ public List<String> getSelectedObjects()
+ {
+ return objectDisplay_.getSelectedObjects();
+ }
+
+ public void setCallFrames(JsArray<CallFrame> frameList)
+ {
+ callFramePanel_.setCallFrames(frameList, contextDepth_);
+
+ // if the parent panel has layout information, auto-size the call frame
+ // panel (let GWT go first so the call frame panel visibility has
+ // taken effect)
+ if (splitPanel.getOffsetHeight() > 0)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ autoSizeCallFramePanel();
+ }
+ });
+ }
+ else
+ {
+ // wait until the split panel has layout information to compute the
+ // correct size of the call frame panel
+ pendingCallFramePanelSize_ = true;
+ }
+ }
+
+ public void setEnvironmentName(String environmentName)
+ {
+ environmentName_ = environmentName;
+ if (objectDisplay_ != null)
+ objectDisplay_.setEnvironmentName(environmentName);
+ }
+
+ public int getScrollPosition()
+ {
+ return objectDisplay_.getScrollPanel().getVerticalScrollPosition();
+ }
+
+ public void setScrollPosition(int scrollPosition)
+ {
+ deferredScrollPosition_ = scrollPosition;
+ }
+
+ public void setExpandedObjects(JsArrayString objects)
+ {
+ deferredExpandedObjects_ = objects;
+ }
+
+ public void updateLineNumber (int newLineNumber)
+ {
+ callFramePanel_.updateLineNumber(newLineNumber);
+ }
+
+ public void setFilterText (String filterText)
+ {
+ filterText_ = filterText.toLowerCase();
+
+ // Iterate over each entry in the list, and toggle its visibility based
+ // on whether it matches the current filter text.
+ List<RObjectEntry> objects = objectDataProvider_.getList();
+ for (int i = 0; i < objects.size(); i++)
+ {
+ RObjectEntry entry = objects.get(i);
+ boolean visible = matchesFilter(entry.rObject);
+ // Redraw the object if its visibility status has changed, or if it's
+ // visible (for visible entries we need to update the search highlight)
+ if (visible != entry.visible || visible)
+ {
+ entry.visible = visible;
+ objectDisplay_.redrawRow(i);
+ }
+ }
+
+ updateCategoryLeaders(true);
+ }
+
+ public int getObjectDisplay()
+ {
+ return objectDisplayType_;
+ }
+
+ // Sets the object display type. Waits for the event loop to finish because
+ // of an apparent timing bug triggered by superdevmode (see case 3745).
+ public void setObjectDisplay(int type)
+ {
+ deferredObjectDisplayType_ = new Integer(type);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ setDeferredObjectDisplay();
+ }
+ });
+ }
+
+ private void setDeferredObjectDisplay()
+ {
+ if (deferredObjectDisplayType_ == null)
+ {
+ return;
+ }
+
+ final int type = deferredObjectDisplayType_;
+
+ // if we already have an active display of this type, do nothing
+ if (type == objectDisplayType_ &&
+ objectDisplay_ != null)
+ {
+ return;
+ }
+
+ // clean up previous object display, if we had one
+ if (objectDisplay_ != null)
+ {
+ objectDataProvider_.removeDataDisplay(objectDisplay_);
+ splitPanel.remove(objectDisplay_);
+ }
+
+ try
+ {
+ // create the new object display and wire it to the data source
+ if (type == OBJECT_LIST_VIEW)
+ {
+ objectDisplay_ = new EnvironmentObjectList(
+ this, observer_, environmentName_);
+ objectSort_.setSortType(RObjectEntrySort.SORT_AUTO);
+ }
+ else if (type == OBJECT_GRID_VIEW)
+ {
+ objectDisplay_ = new EnvironmentObjectGrid(
+ this, observer_, environmentName_);
+ objectSort_.setSortType(RObjectEntrySort.SORT_COLUMN);
+ }
+ }
+ catch (Throwable e)
+ {
+ // For reasons that are unclear, GWT sometimes barfs when trying to
+ // create the virtual scrollbars in the DataGrid that drives the
+ // environment list (it computes, and then tries to apply, a negative
+ // height). This appears to only happen during superdevmode boot,
+ // so try again (up to 5 times) if we're in superdevmode.
+
+ if (SuperDevMode.isActive())
+ {
+ if (gridRenderRetryCount_ >= 5)
+ {
+ Debug.log("WARNING: Failed to render environment pane data grid");
+ }
+ gridRenderRetryCount_++;
+ Debug.log("WARNING: Retrying environment data grid render (" +
+ gridRenderRetryCount_ + ")");
+ Timer t = new Timer() {
+ @Override
+ public void run()
+ {
+ setObjectDisplay(type);
+ }
+ };
+ t.schedule(5);
+ }
+
+ return;
+ }
+
+ objectDisplayType_ = type;
+ Collections.sort(objectDataProvider_.getList(), objectSort_);
+ updateCategoryLeaders(false);
+ objectDataProvider_.addDataDisplay(objectDisplay_);
+
+ objectDisplay_.getScrollPanel().addScrollHandler(new ScrollHandler()
+ {
+ @Override
+ public void onScroll(ScrollEvent event)
+ {
+ if (useStatePersistence())
+ {
+ deferredScrollPosition_ = getScrollPosition();
+ observer_.setPersistedScrollPosition(deferredScrollPosition_);
+ }
+ }
+ });
+
+ objectDisplay_.setEmptyTableWidget(buildEmptyGridMessage());
+ objectDisplay_.addStyleName(style.objectGrid());
+ objectDisplay_.addStyleName(style.environmentPanel());
+ splitPanel.add(objectDisplay_);
+ deferredObjectDisplayType_ = null;
+ }
+
+ // CallFramePanelHost implementation ---------------------------------------
+
+ @Override
+ public void minimizeCallFramePanel()
+ {
+ callFramePanelHeight_ = splitPanel.getWidgetSize(callFramePanel_).intValue();
+ splitPanel.setWidgetSize(callFramePanel_, style.headerRowHeight());
+ }
+
+ @Override
+ public void restoreCallFramePanel()
+ {
+ splitPanel.setWidgetSize(callFramePanel_, callFramePanelHeight_);
+ callFramePanel_.onResize();
+ }
+
+ @Override
+ public boolean getShowInternalFunctions()
+ {
+ return observer_.getShowInternalFunctions();
+ }
+
+ @Override
+ public void setShowInternalFunctions(boolean show)
+ {
+ observer_.setShowInternalFunctions(show);
+ }
+
+ // EnvironmentObjectsDisplay.Host implementation ---------------------------
+
+ @Override
+ public boolean enableClickableObjects()
+ {
+ return contextDepth_ < 2;
+ }
+
+ // we currently only set and/or get persisted state at the root context
+ // level.
+ @Override
+ public boolean useStatePersistence()
+ {
+ return environmentName_.equals(EnvironmentPane.GLOBAL_ENVIRONMENT_NAME);
+ }
+
+ @Override
+ public String getFilterText()
+ {
+ return filterText_;
+ }
+
+ @Override
+ public int getSortColumn()
+ {
+ return objectSort_.getSortColumn();
+ }
+
+ @Override
+ public void setSortColumn(int col)
+ {
+ objectSort_.setSortColumn(col);
+ observer_.setViewDirty();
+ Collections.sort(objectDataProvider_.getList(), objectSort_);
+ }
+
+ @Override
+ public void toggleAscendingSort()
+ {
+ setAscendingSort(!objectSort_.getAscending());
+ }
+
+ @Override
+ public boolean getAscendingSort()
+ {
+ return objectSort_.getAscending();
+ }
+
+ public void setAscendingSort(boolean ascending)
+ {
+ objectSort_.setAscending(ascending);
+ observer_.setViewDirty();
+ Collections.sort(objectDataProvider_.getList(), objectSort_);
+ }
+
+ public void setSort(int column, boolean ascending)
+ {
+ objectSort_.setSortColumn(column);
+ objectSort_.setAscending(ascending);
+ Collections.sort(objectDataProvider_.getList(), objectSort_);
+ }
+
+ @Override
+ public void fillObjectContents(RObject object, Operation onCompleted)
+ {
+ observer_.fillObjectContents(object, onCompleted);
+ }
+
+ // Private methods: object management --------------------------------------
+
+ private int indexOfExistingObject(String objectName)
+ {
+ List<RObjectEntry> objects = objectDataProvider_.getList();
+
+ // find the position of the object in the list--we can't use binary
+ // search here since we're matching on names and the list isn't sorted
+ // by name (it's sorted by type, then name)
+ int index;
+ boolean foundObject = false;
+ for (index = 0; index < objects.size(); index++)
+ {
+ if (objects.get(index).rObject.getName() == objectName)
+ {
+ foundObject = true;
+ break;
+ }
+ }
+
+ return foundObject ? index : -1;
+ }
+
+ // returns the position a new object entry should occupy in the table
+ private int indexOfNewObject(RObjectEntry obj)
+ {
+ List<RObjectEntry> objects = objectDataProvider_.getList();
+ int numObjects = objects.size();
+ int idx;
+ // consider: can we use binary search here?
+ for (idx = 0; idx < numObjects; idx++)
+ {
+ if (objectSort_.compare(obj, objects.get(idx)) < 0)
+ {
+ break;
+ }
+ }
+ return idx;
+ }
+
+ // after adds or removes, we need to tag the new category-leading objects
+ private void updateCategoryLeaders(boolean redrawUpdatedRows)
+ {
+ // no need to do these model updates if we're not in the mode that
+ // displays them
+ if (objectDisplayType_ != OBJECT_LIST_VIEW)
+ return;
+
+ List<RObjectEntry> objects = objectDataProvider_.getList();
+
+ // whether or not we've found a leader for each category
+ Boolean[] leaders = { false, false, false };
+ boolean foundFirstObject = false;
+
+ for (int i = 0; i < objects.size(); i++)
+ {
+ RObjectEntry entry = objects.get(i);
+ if (!entry.visible)
+ continue;
+ if (!foundFirstObject)
+ {
+ entry.isFirstObject = true;
+ foundFirstObject = true;
+ }
+ else
+ {
+ entry.isFirstObject = false;
+ }
+ int category = entry.getCategory();
+ Boolean leader = entry.isCategoryLeader;
+ // if we haven't found a leader for this category yet, make this object
+ // the leader if it isn't already
+ if (!leaders[category])
+ {
+ leaders[category] = true;
+ if (!leader)
+ {
+ entry.isCategoryLeader = true;
+ }
+ }
+ // if this object is marked as the leader but we've already found a
+ // leader, unmark it
+ else if (leader)
+ {
+ entry.isCategoryLeader = false;
+ }
+
+ // if we changed the leader flag, redraw the row
+ if (leader != entry.isCategoryLeader
+ && redrawUpdatedRows)
+ {
+ objectDisplay_.redrawRow(i);
+ }
+ }
+ }
+
+ private Widget buildEmptyGridMessage()
+ {
+ HTMLPanel messagePanel = new HTMLPanel("");
+ messagePanel.setStyleName(style.emptyEnvironmentPanel());
+ environmentEmptyMessage_ = new Label(EMPTY_ENVIRONMENT_MESSAGE);
+ environmentEmptyMessage_.setStyleName(style.emptyEnvironmentMessage());
+ messagePanel.add(environmentEmptyMessage_);
+ return messagePanel;
+ }
+
+ private void autoSizeCallFramePanel()
+ {
+ // after setting the frames, resize the call frame panel to neatly
+ // wrap the new list, up to a maximum of 2/3 of the height of the
+ // split panel.
+ int desiredCallFramePanelSize =
+ callFramePanel_.getDesiredPanelHeight();
+
+ if (splitPanel.getOffsetHeight() > 0)
+ {
+ desiredCallFramePanelSize = Math.min(
+ desiredCallFramePanelSize,
+ (int)(0.66 * splitPanel.getOffsetHeight()));
+ }
+
+ // if the panel is minimized, just update the cached height so it'll
+ // get set to what we want when/if the panel is restored
+ if (callFramePanel_.isMinimized())
+ {
+ callFramePanelHeight_ = desiredCallFramePanelSize;
+ }
+ else
+ {
+ splitPanel.setWidgetSize(
+ callFramePanel_, desiredCallFramePanelSize);
+ callFramePanel_.onResize();
+ objectDisplay_.onResize();
+ }
+
+ pendingCallFramePanelSize_ = false;
+ }
+
+
+ // Private methods: state persistence --------------------------------------
+
+ private void setDeferredState()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ if (deferredExpandedObjects_ != null)
+ {
+ // loop through the objects in the list and check to see if each
+ // is marked expanded in the persisted list of expanded objects
+ List<RObjectEntry> objects = objectDataProvider_.getList();
+ for (int idxObj = 0; idxObj < objects.size(); idxObj++)
+ {
+ for (int idxExpanded = 0;
+ idxExpanded < deferredExpandedObjects_.length();
+ idxExpanded++)
+ {
+ if (objects.get(idxObj).rObject.getName() ==
+ deferredExpandedObjects_.get(idxExpanded))
+ {
+ objects.get(idxObj).expanded = true;
+ objectDisplay_.redrawRow(idxObj);
+ }
+ }
+ }
+ }
+
+ // set the cached scroll position
+ objectDisplay_.getScrollPanel().setVerticalScrollPosition(
+ deferredScrollPosition_);
+
+ }
+ });
+ }
+
+ private boolean matchesFilter(RObject obj)
+ {
+ if (filterText_.isEmpty())
+ return true;
+ return obj.getName().toLowerCase().contains(filterText_) ||
+ obj.getValue().toLowerCase().contains(filterText_);
+ }
+
+ private RObjectEntry entryFromRObject(RObject obj)
+ {
+ return new RObjectEntry(obj, matchesFilter(obj));
+ }
+
+ private final static String EMPTY_ENVIRONMENT_MESSAGE =
+ "Environment is empty";
+
+ public static final int OBJECT_LIST_VIEW = 0;
+ public static final int OBJECT_GRID_VIEW = 1;
+
+ @UiField EnvironmentStyle style;
+ @UiField AutoHidingSplitLayoutPanel splitPanel;
+
+ EnvironmentObjectDisplay objectDisplay_;
+ CallFramePanel callFramePanel_;
+ Label environmentEmptyMessage_;
+
+ private ListDataProvider<RObjectEntry> objectDataProvider_;
+ private RObjectEntrySort objectSort_;
+
+ private EnvironmentObjectsObserver observer_;
+ private int contextDepth_;
+ private int callFramePanelHeight_;
+ private int objectDisplayType_ = OBJECT_LIST_VIEW;
+ private String filterText_ = "";
+ private String environmentName_;
+
+ // deferred settings--set on load but not applied until we have data.
+ private int deferredScrollPosition_ = 0;
+ private JsArrayString deferredExpandedObjects_;
+ private boolean pendingCallFramePanelSize_ = false;
+ private Integer deferredObjectDisplayType_ = new Integer(OBJECT_LIST_VIEW);
+ private int gridRenderRetryCount_ = 0;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.ui.xml
new file mode 100644
index 0000000..2b8c0c2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjects.ui.xml
@@ -0,0 +1,6 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:rcv="urn:import:org.rstudio.core.client.cellview">
+ <ui:style src="EnvironmentObjects.css" type="org.rstudio.studio.client.workbench.views.environment.view.EnvironmentStyle" />
+ <rcv:AutoHidingSplitLayoutPanel ui:field="splitPanel" styleName="{style.environmentPane}"></rcv:AutoHidingSplitLayoutPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectsObserver.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectsObserver.java
new file mode 100644
index 0000000..9a58c81
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentObjectsObserver.java
@@ -0,0 +1,31 @@
+/*
+ * EnvironmentObjectsObserver.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+
+public interface EnvironmentObjectsObserver
+{
+ void viewObject(String objectName);
+ void setObjectExpanded(String objectName);
+ void setObjectCollapsed(String objectName);
+ void setPersistedScrollPosition(int scrollPosition);
+ void changeContextDepth(int newDepth);
+ void setViewDirty();
+ boolean getShowInternalFunctions();
+ void setShowInternalFunctions(boolean show);
+ void fillObjectContents(RObject object, Operation onCompleted);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentResources.java
new file mode 100644
index 0000000..d3c4257
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentResources.java
@@ -0,0 +1,57 @@
+/*
+ * EnvironmentResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface EnvironmentResources extends ClientBundle
+{
+ public static final EnvironmentResources INSTANCE =
+ GWT.create(EnvironmentResources.class);
+
+ @Source("ExpandIcon.png")
+ ImageResource expandIcon();
+
+ @Source("CollapseIcon.png")
+ ImageResource collapseIcon();
+
+ @Source("TracedFunction.png")
+ ImageResource tracedFunction();
+
+ @Source("GlobalEnvironment.png")
+ ImageResource globalEnvironment();
+
+ @Source("PackageEnvironment.png")
+ ImageResource packageEnvironment();
+
+ @Source("AttachedEnvironment.png")
+ ImageResource attachedEnvironment();
+
+ @Source("FunctionEnvironment.png")
+ ImageResource functionEnvironment();
+
+ @Source("ObjectListView.png")
+ ImageResource objectListView();
+
+ @Source("ObjectGridView.png")
+ ImageResource objectGridView();
+
+ @Source("EnvironmentObjects.css")
+ EnvironmentStyle environmentStyle();
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentStyle.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentStyle.java
new file mode 100644
index 0000000..e5e3ec1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/EnvironmentStyle.java
@@ -0,0 +1,42 @@
+/*
+ * EnvironmentStyle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.resources.client.CssResource;
+
+interface EnvironmentStyle extends CssResource
+{
+ int headerRowHeight();
+ String expandCol();
+ String nameCol();
+ String valueCol();
+ String clickableCol();
+ String detailRow();
+ String categoryHeaderRow();
+ String categoryHeaderText();
+ String emptyEnvironmentPanel();
+ String emptyEnvironmentMessage();
+ String unclickableIcon();
+ String unevaluatedPromise();
+ String objectGrid();
+ String widthSettingRow();
+ String dataFrameValueCol();
+ String environmentPanel();
+ String filterMatch();
+ String environmentPane();
+ String fillHeight();
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExecutionArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExecutionArrow.png
new file mode 100644
index 0000000..9e1c2e2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExecutionArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExpandIcon.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExpandIcon.png
new file mode 100644
index 0000000..aa37428
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ExpandIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/FunctionEnvironment.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/FunctionEnvironment.png
new file mode 100644
index 0000000..00c65fd
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/FunctionEnvironment.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/GlobalEnvironment.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/GlobalEnvironment.png
new file mode 100644
index 0000000..df2ba93
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/GlobalEnvironment.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridColumn.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridColumn.java
new file mode 100644
index 0000000..53d0865
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridColumn.java
@@ -0,0 +1,96 @@
+/*
+ * ObjectGridColumn.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.Header;
+
+public abstract class ObjectGridColumn extends Column<RObjectEntry, String>
+{
+ public ObjectGridColumn(Cell<String> cell,
+ String columnName,
+ int columnWidth,
+ int columnType,
+ final EnvironmentObjectDisplay.Host host)
+ {
+ super(cell);
+ columnName_ = columnName;
+ columnWidth_ = columnWidth;
+ columnType_ = columnType;
+ setSortable(true);
+ header_ = new Header<String>(new ClickableTextCell())
+ {
+ @Override
+ public String getValue()
+ {
+ return columnName_;
+ }
+ };
+ header_.setUpdater(new ValueUpdater<String>()
+ {
+ @Override
+ public void update(String value)
+ {
+ if (host.getSortColumn() == columnType_)
+ {
+ host.toggleAscendingSort();
+ }
+ else
+ {
+ host.setSortColumn(columnType_);
+ }
+ }
+ });
+ }
+
+ public String getName()
+ {
+ return columnName_;
+ }
+
+ public int getWidth()
+ {
+ return columnWidth_;
+ }
+
+ public void setWidth(int width)
+ {
+ columnWidth_ = width;
+ }
+
+ public int getType()
+ {
+ return columnType_;
+ }
+
+ public Header<String> getHeader()
+ {
+ return header_;
+ }
+
+ public static final int COLUMN_NAME = 0;
+ public static final int COLUMN_TYPE = 1;
+ public static final int COLUMN_LENGTH = 2;
+ public static final int COLUMN_SIZE = 3;
+ public static final int COLUMN_VALUE = 4;
+
+ private String columnName_;
+ private int columnWidth_;
+ private int columnType_;
+ private Header<String> header_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridView.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridView.png
new file mode 100644
index 0000000..e870400
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectGridView.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectListView.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectListView.png
new file mode 100644
index 0000000..94b9d6e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/ObjectListView.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/PackageEnvironment.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/PackageEnvironment.png
new file mode 100644
index 0000000..de2dcfe
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/PackageEnvironment.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntry.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntry.java
new file mode 100644
index 0000000..1d94c22
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntry.java
@@ -0,0 +1,108 @@
+/*
+ * RObjectEntry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import com.google.gwt.view.client.ProvidesKey;
+import org.rstudio.studio.client.workbench.views.environment.model.RObject;
+
+// represents an R object's entry in the environment pane view
+public class RObjectEntry
+{
+ public static final ProvidesKey<RObjectEntry> KEY_PROVIDER =
+ new ProvidesKey<RObjectEntry>() {
+ public Object getKey(RObjectEntry item) {
+ return item.rObject.getName();
+ }
+ };
+
+ // the classification of data in the pane
+ public class Categories
+ {
+ public static final int Data = 0;
+ public static final int Value = 1;
+ public static final int Function = 2;
+ }
+
+ // make a new entry in the pane from an R object
+ RObjectEntry(RObject obj, boolean isVisible)
+ {
+ rObject = obj;
+ expanded = false;
+ isCategoryLeader = false;
+ visible = isVisible;
+ isFirstObject = false;
+ isExpanding = false;
+ contentsAreDeferred = obj.getContentsDeferred();
+ }
+
+ // show expander for objects that have contents
+ public boolean canExpand()
+ {
+ return rObject.getLength() > 0 &&
+ (rObject.getContentsDeferred() ||
+ (rObject.getContents().length() > 0 &&
+ !rObject.getContents().get(0).equals(NO_VALUE))) &&
+ !hasTraceInfo();
+ }
+
+ public boolean hasTraceInfo()
+ {
+ return rObject.getType().equals("functionWithTrace");
+ }
+
+ public int getCategory()
+ {
+ String type = rObject.getType();
+ if (rObject.isData() ||
+ type.equals("matrix") ||
+ type.equals("data.table") ||
+ type.equals("cast_df") ||
+ type.equals("xts"))
+ {
+ return Categories.Data;
+ }
+ else if (type.equals("function") ||
+ hasTraceInfo())
+ {
+ return Categories.Function;
+ }
+
+ return Categories.Value;
+ }
+
+ public boolean isPromise()
+ {
+ return rObject.getType() == "promise";
+ }
+
+ public String getDisplayValue()
+ {
+ String val = rObject.getValue().trim();
+ return val == RObjectEntry.NO_VALUE ?
+ rObject.getDescription().trim() :
+ val;
+ }
+
+ public static final String NO_VALUE = "NO_VALUE";
+
+ RObject rObject;
+ boolean expanded;
+ boolean isCategoryLeader;
+ boolean visible;
+ boolean isFirstObject;
+ boolean isExpanding;
+ boolean contentsAreDeferred;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntrySort.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntrySort.java
new file mode 100644
index 0000000..3c73521
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/RObjectEntrySort.java
@@ -0,0 +1,123 @@
+/*
+ * RObjectEntrySort.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.environment.view;
+
+import java.util.Comparator;
+
+class RObjectEntrySort implements Comparator<RObjectEntry>
+{
+ public RObjectEntrySort()
+ {
+ sortType_ = SORT_AUTO;
+ sortColumn_ = ObjectGridColumn.COLUMN_NAME;
+ ascending_ = true;
+ }
+
+ public void setSortType(int sortType)
+ {
+ sortType_ = sortType;
+ }
+
+ public void setSortColumn(int sortColumn)
+ {
+ sortColumn_ = sortColumn;
+ // default to descending sort for size (largest objects on top--the most
+ // likely desired ordering)
+ if (sortColumn == ObjectGridColumn.COLUMN_SIZE)
+ ascending_ = false;
+ else
+ ascending_ = true;
+ }
+
+ public int getSortColumn()
+ {
+ return sortColumn_;
+ }
+
+ public boolean getAscending()
+ {
+ return ascending_;
+ }
+
+ public void setAscending(boolean ascending)
+ {
+ ascending_ = ascending;
+ }
+
+ public int compare(RObjectEntry first, RObjectEntry second)
+ {
+ if (ascending_ || sortType_ == SORT_AUTO)
+ return compareAscending(first, second);
+ else
+ return compareAscending(second, first);
+ }
+
+ public int compareAscending(RObjectEntry first, RObjectEntry second)
+ {
+ int result = 0;
+ if (sortType_ == SORT_AUTO)
+ {
+ result = first.getCategory() - second.getCategory();
+ if (result == 0)
+ {
+ result = localeCompare(first.rObject.getName(),
+ second.rObject.getName());
+ }
+ }
+ else if (sortType_ == SORT_COLUMN)
+ {
+ switch (sortColumn_)
+ {
+ case ObjectGridColumn.COLUMN_NAME:
+ result = localeCompare(first.rObject.getName(),
+ second.rObject.getName());
+ break;
+ case ObjectGridColumn.COLUMN_TYPE:
+ result = localeCompare(first.rObject.getType(),
+ second.rObject.getType());
+ break;
+ case ObjectGridColumn.COLUMN_LENGTH:
+ result = new Long(first.rObject.getLength())
+ .compareTo(new Long(second.rObject.getLength()));
+ break;
+ case ObjectGridColumn.COLUMN_SIZE:
+ result = new Long(first.rObject.getSize())
+ .compareTo(new Long(second.rObject.getSize()));
+ break;
+ case ObjectGridColumn.COLUMN_VALUE:
+ result = localeCompare(first.getDisplayValue(),
+ second.getDisplayValue());
+ break;
+ }
+ }
+ return result;
+ }
+
+ // Gets sort order of two strings. Coerces from undefined/null values to
+ // empty strings.
+ private native int localeCompare(String first, String second) /*-{
+ firstVal = first ? first : "";
+ secondVal = second ? second : "";
+ return firstVal.localeCompare(secondVal);
+ }-*/;
+
+ public static final int SORT_AUTO = 0;
+ public static final int SORT_COLUMN = 1;
+
+ private int sortType_;
+ private int sortColumn_;
+ private boolean ascending_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/TracedFunction.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/TracedFunction.png
new file mode 100644
index 0000000..dec8c55
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/environment/view/TracedFunction.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/Files.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/Files.java
new file mode 100644
index 0000000..1ddaa11
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/Files.java
@@ -0,0 +1,653 @@
+/*
+ * Files.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.cellview.ColumnSortInfo;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.ConsoleDispatcher;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.fileexport.FileExport;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.events.OpenFileInBrowserEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenFileInBrowserHandler;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientInitState;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.model.helper.StringStateValue;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.files.events.*;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+import org.rstudio.studio.client.workbench.views.files.model.PendingFileUpload;
+
+import java.util.ArrayList;
+
+public class Files
+ extends BasePresenter
+ implements FileChangeHandler,
+ OpenFileInBrowserHandler,
+ DirectoryNavigateHandler
+{
+ interface Binder extends CommandBinder<Commands, Files> {}
+
+
+ public interface Display extends WorkbenchView
+ {
+ public interface NavigationObserver
+ {
+ void onFileNavigation(FileSystemItem file);
+ void onSelectAllValueChanged(boolean value);
+ }
+
+ public interface Observer extends NavigationObserver
+ {
+ void onFileSelectionChanged();
+ void onColumnSortOrderChanaged(JsArray<ColumnSortInfo> sortOrder);
+ }
+
+ void setObserver(Observer observer);
+
+
+ void setColumnSortOrder(JsArray<ColumnSortInfo> sortOrder);
+
+ void listDirectory(FileSystemItem directory,
+ ServerDataSource<JsArray<FileSystemItem>> filesDS);
+
+ void updateDirectoryListing(FileChange action);
+
+ void renameFile(FileSystemItem from, FileSystemItem to);
+
+ void selectAll();
+ void selectNone();
+
+ ArrayList<FileSystemItem> getSelectedFiles();
+
+ void showFolderPicker(
+ String title,
+ RemoteFileSystemContext context,
+ FileSystemItem initialDir,
+ ProgressOperationWithInput<FileSystemItem> operation);
+
+ void showFileUpload(
+ String targetURL,
+ FileSystemItem targetDirectory,
+ RemoteFileSystemContext fileSystemContext,
+ OperationWithInput<PendingFileUpload> completedOperation);
+
+
+ void showHtmlFileChoice(FileSystemItem file,
+ Command onEdit,
+ Command onBrowse);
+ }
+
+ @Inject
+ public Files(Display view,
+ EventBus eventBus,
+ FilesServerOperations server,
+ RemoteFileSystemContext fileSystemContext,
+ GlobalDisplay globalDisplay,
+ Session session,
+ Commands commands,
+ Provider<FilesCopy> pFilesCopy,
+ Provider<FilesUpload> pFilesUpload,
+ Provider<FileExport> pFileExport,
+ FileTypeRegistry fileTypeRegistry,
+ ConsoleDispatcher consoleDispatcher,
+ WorkbenchContext workbenchContext)
+ {
+ super(view);
+ view_ = view ;
+ view_.setObserver(new DisplayObserver());
+ fileTypeRegistry_ = fileTypeRegistry;
+ consoleDispatcher_ = consoleDispatcher;
+ workbenchContext_ = workbenchContext;
+
+ eventBus_ = eventBus;
+ server_ = server;
+ fileSystemContext_ = fileSystemContext;
+ globalDisplay_ = globalDisplay ;
+ session_ = session;
+ pFilesCopy_ = pFilesCopy;
+ pFilesUpload_ = pFilesUpload;
+ pFileExport_ = pFileExport;
+
+ ((Binder)GWT.create(Binder.class)).bind(commands, this);
+
+
+ eventBus_.addHandler(FileChangeEvent.TYPE, this);
+
+ initSession();
+ }
+
+ private void initSession()
+ {
+ final SessionInfo sessionInfo = session_.getSessionInfo();
+ ClientInitState state = sessionInfo.getClientState();
+
+ // make the column sort order persistent
+ new JSObjectStateValue(MODULE_FILES, KEY_SORT_ORDER, ClientState.PROJECT_PERSISTENT, state, false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ if (value != null)
+ columnSortOrder_ = value.cast();
+ else
+ columnSortOrder_ = null;
+
+ lastKnownState_ = columnSortOrder_;
+
+ view_.setColumnSortOrder(columnSortOrder_);
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ if (columnSortOrder_ != null)
+ return columnSortOrder_.cast();
+ else
+ return null;
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ if (lastKnownState_ != columnSortOrder_)
+ {
+ lastKnownState_ = columnSortOrder_;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private JsArray<ColumnSortInfo> lastKnownState_ = null;
+ };
+
+
+ // navigate to previous directory (works for resumed case)
+ new StringStateValue(MODULE_FILES, KEY_PATH, ClientState.PROJECT_PERSISTENT, state) {
+ @Override
+ protected void onInit(final String value)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ // if we've already navigated to a directory then
+ // we don't need to initialize from client state
+ if (hasNavigatedToDirectory_)
+ return;
+
+ // compute start dir
+ String path = transformPathStateValue(value);
+ FileSystemItem start = path != null
+ ? FileSystemItem.createDir(path)
+ : FileSystemItem.createDir(
+ sessionInfo.getInitialWorkingDir());
+ navigateToDirectory(start);
+ }
+ });
+ }
+
+ @Override
+ protected String getValue()
+ {
+ return currentPath_.getPath();
+ }
+
+ private String transformPathStateValue(String value)
+ {
+ // if the value is null then return null
+ if (value == null)
+ return null;
+
+ // only respect the value for projects
+ String projectFile = session_.getSessionInfo().getActiveProjectFile();
+ if (projectFile == null)
+ return null;
+
+ // ensure that the value is within the project dir (it wouldn't
+ // be if the project directory has been moved or renamed)
+ String projectDirPath =
+ FileSystemItem.createFile(projectFile).getParentPathString();
+ if (value.startsWith(projectDirPath))
+ return value;
+ else
+ return null;
+ }
+ };
+ }
+
+
+
+ public Display getDisplay()
+ {
+ return view_ ;
+ }
+
+ // observer for display
+ public class DisplayObserver implements Display.Observer {
+
+ public void onFileSelectionChanged()
+ {
+ }
+
+ public void onFileNavigation(FileSystemItem file)
+ {
+ if (file.isDirectory())
+ {
+ navigateToDirectory(file);
+ }
+ else
+ {
+ navigateToFile(file);
+ }
+ }
+
+ public void onSelectAllValueChanged(boolean value)
+ {
+ if (value)
+ view_.selectAll();
+ else
+ view_.selectNone();
+ }
+
+ public void onColumnSortOrderChanaged(
+ JsArray<ColumnSortInfo> sortOrder)
+ {
+ columnSortOrder_ = sortOrder;
+ }
+ };
+
+
+ @Handler
+ void onRefreshFiles()
+ {
+ view_.listDirectory(currentPath_, currentPathFilesDS_);
+ }
+
+ @Handler
+ void onNewFolder()
+ {
+ globalDisplay_.promptForText(
+ "New Folder",
+ "Please enter the new folder name",
+ null,
+ new ProgressOperationWithInput<String>()
+ {
+ public void execute(String input,
+ final ProgressIndicator progress)
+ {
+ progress.onProgress("Creating folder...");
+
+ String folderPath = currentPath_.completePath(input);
+ FileSystemItem folder = FileSystemItem.createDir(folderPath);
+
+ server_.createFolder(
+ folder,
+ new VoidServerRequestCallback(progress));
+ }
+ });
+ }
+
+ void onUploadFile()
+ {
+ pFilesUpload_.get().execute(currentPath_, fileSystemContext_);
+ }
+
+ @Handler
+ void onCopyFile()
+ {
+ // copy selected files then clear the selection
+ pFilesCopy_.get().execute(view_.getSelectedFiles(),
+ currentPath_,
+ new Command() {
+ public void execute()
+ {
+ view_.selectNone();
+ }});
+ }
+
+
+ @Handler
+ void onMoveFiles()
+ {
+ // get currently selected files
+ final ArrayList<FileSystemItem> selectedFiles = view_.getSelectedFiles();
+
+ // validation: some selection exists
+ if (selectedFiles.size() == 0)
+ return ;
+
+ // validation -- not prohibited move of public folder
+ if (!validateNotRestrictedFolder(selectedFiles, "moved"))
+ return ;
+
+ view_.showFolderPicker(
+ "Choose Folder",
+ fileSystemContext_,
+ currentPath_,
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ public void execute(final FileSystemItem targetDir,
+ final ProgressIndicator progress)
+ {
+ if (targetDir == null)
+ return;
+
+ // check to make sure that we aren't moving any folders
+ // onto themselves or moving any files into the directory
+ // where they currently reside
+ for (int i=0; i<selectedFiles.size(); i++)
+ {
+ FileSystemItem file = selectedFiles.get(i);
+ FileSystemItem fileParent = file.getParentPath();
+
+ if (file.getPath().equals(targetDir.getPath()) ||
+ fileParent.getPath().equals(targetDir.getPath()))
+ {
+ progress.onError("Invalid target folder");
+ return ;
+ }
+ }
+
+ progress.onProgress("Moving files...");
+
+ view_.selectNone();
+
+ server_.moveFiles(selectedFiles,
+ targetDir,
+ new VoidServerRequestCallback(progress));
+ }
+ });
+ }
+
+
+ @Handler
+ void onExportFiles()
+ {
+ pFileExport_.get().export("Export Files",
+ "selected file(s)",
+ currentPath_,
+ view_.getSelectedFiles());
+ }
+
+ @Handler
+ void onRenameFile()
+ {
+ // get currently selected files
+ ArrayList<FileSystemItem> selectedFiles = view_.getSelectedFiles();
+
+ // validation: some selection exists
+ if (selectedFiles.size() == 0)
+ return ;
+
+ // validation: no more than one file selected
+ if (selectedFiles.size() > 1)
+ {
+ globalDisplay_.showErrorMessage(
+ "Invalid Selection",
+ "Please select only one file to rename");
+ return ;
+ }
+
+ // validation -- not prohibited move of public folder
+ if (!validateNotRestrictedFolder(selectedFiles, "renamed"))
+ return ;
+
+ // prompt for new file name then execute the rename
+ final FileSystemItem file = selectedFiles.get(0);
+ globalDisplay_.promptForText("Rename File",
+ "Please enter the new file name:",
+ file.getName(),
+ 0,
+ file.getStem().length(),
+ null,
+ new ProgressOperationWithInput<String>() {
+
+ public void execute(String input,
+ final ProgressIndicator progress)
+ {
+ progress.onProgress("Renaming file...");
+
+ String path = file.getParentPath().completePath(input);
+ FileSystemItem target ;
+ if (file.isDirectory())
+ target = FileSystemItem.createDir(path);
+ else
+ target = FileSystemItem.createFile(path);
+
+ // clear selection
+ view_.selectNone();
+
+ // premptively rename in the UI then fallback to refreshing
+ // the view if there is an error
+ view_.renameFile(file, target);
+
+ // execute on the server
+ server_.renameFile(file,
+ target,
+ new VoidServerRequestCallback(progress) {
+ @Override
+ protected void onFailure()
+ {
+ onRefreshFiles();
+ }
+ });
+ }
+ });
+ }
+
+ @Handler
+ void onDeleteFiles()
+ {
+ // get currently selected files
+ final ArrayList<FileSystemItem> selectedFiles = view_.getSelectedFiles();
+
+ // validation: some selection exists
+ if (selectedFiles.size() == 0)
+ return ;
+
+ // validation -- not prohibited move of public folder
+ if (!validateNotRestrictedFolder(selectedFiles, "deleted"))
+ return ;
+
+ // confirm delete then execute it
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_QUESTION,
+ "Confirm Delete",
+ "Are you sure you want to delete the selected files?",
+ new ProgressOperation() {
+ public void execute(final ProgressIndicator progress)
+ {
+ progress.onProgress("Deleting files...");
+
+ view_.selectNone();
+
+ server_.deleteFiles(
+ selectedFiles,
+ new VoidServerRequestCallback(progress));
+ }
+ },
+ true);
+ }
+
+ private boolean validateNotRestrictedFolder(ArrayList<FileSystemItem> files,
+ String verb)
+ {
+ if (!session_.getSessionInfo().getAllowRemovePublicFolder())
+ {
+ for (FileSystemItem file : files)
+ {
+ if (file.isPublicFolder())
+ {
+ globalDisplay_.showErrorMessage(
+ "Error",
+ "The Public folder cannot be " + verb + ".");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void onGoToWorkingDir()
+ {
+ view_.bringToFront();
+ navigateToDirectory(workbenchContext_.getCurrentWorkingDir());
+ }
+
+ @Handler
+ void onSetAsWorkingDir()
+ {
+ consoleDispatcher_.executeSetWd(currentPath_, true);
+ }
+
+ void onSetWorkingDirToFilesPane()
+ {
+ onSetAsWorkingDir();
+ }
+
+
+ @Handler
+ void onShowFolder()
+ {
+ eventBus_.fireEvent(new ShowFolderEvent(currentPath_));
+ }
+
+ public void onFileChange(FileChangeEvent event)
+ {
+ view_.updateDirectoryListing(event.getFileChange());
+ }
+
+ public void onOpenFileInBrowser(OpenFileInBrowserEvent event)
+ {
+ showFileInBrowser(event.getFile());
+
+ }
+
+ public void onDirectoryNavigate(DirectoryNavigateEvent event)
+ {
+ navigateToDirectory(event.getDirectory());
+ if (event.getActivate())
+ view_.bringToFront();
+ }
+
+ private void navigateToDirectory(FileSystemItem directoryEntry)
+ {
+ hasNavigatedToDirectory_ = true;
+ currentPath_ = directoryEntry;
+ view_.listDirectory(currentPath_, currentPathFilesDS_);
+ session_.persistClientState();
+ }
+
+
+ private void navigateToFile(final FileSystemItem file)
+ {
+ String ext = file.getExtension().toLowerCase();
+ if (ext.equals(".htm") || ext.equals(".html"))
+ {
+ view_.showHtmlFileChoice(
+ file,
+ new Command() {
+
+ @Override
+ public void execute()
+ {
+ fileTypeRegistry_.openFile(file);
+ }
+ },
+ new Command() {
+
+ @Override
+ public void execute()
+ {
+ showFileInBrowser(file);
+ }
+ });
+ }
+ else
+ {
+ fileTypeRegistry_.openFile(file);
+ }
+
+ }
+
+ private void showFileInBrowser(FileSystemItem file)
+ {
+ // show the file in a new window if we can get a file url for it
+ String fileURL = server_.getFileUrl(file);
+ if (fileURL != null)
+ {
+ globalDisplay_.openWindow(fileURL);
+ }
+ }
+
+ // data source for listing files on the current path which can
+ // be passed to the files view
+ ServerDataSource<JsArray<FileSystemItem>> currentPathFilesDS_ =
+ new ServerDataSource<JsArray<FileSystemItem>>()
+ {
+ public void requestData(
+ ServerRequestCallback<JsArray<FileSystemItem>> requestCallback)
+ {
+ // pass true to enable monitoring for all calls to list_files
+ server_.listFiles(currentPath_, true, requestCallback);
+ }
+ };
+
+ private final Display view_ ;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final ConsoleDispatcher consoleDispatcher_;
+ private final WorkbenchContext workbenchContext_;
+ private final FilesServerOperations server_;
+ private final EventBus eventBus_;
+ private final GlobalDisplay globalDisplay_ ;
+ private final RemoteFileSystemContext fileSystemContext_;
+ private final Session session_;
+ private FileSystemItem currentPath_ = FileSystemItem.home();
+ private boolean hasNavigatedToDirectory_ = false;
+ private final Provider<FilesCopy> pFilesCopy_;
+ private final Provider<FilesUpload> pFilesUpload_;
+ private final Provider<FileExport> pFileExport_;
+ private static final String MODULE_FILES = "files-pane";
+ private static final String KEY_PATH = "path";
+ private static final String KEY_SORT_ORDER = "sortOrder";
+ private JsArray<ColumnSortInfo> columnSortOrder_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesCopy.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesCopy.java
new file mode 100644
index 0000000..6ffa004
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesCopy.java
@@ -0,0 +1,111 @@
+/*
+ * FilesCopy.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+
+import java.util.ArrayList;
+
+public class FilesCopy
+{
+ @Inject
+ public FilesCopy(FilesServerOperations server,
+ GlobalDisplay globalDisplay)
+ {
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ }
+
+ public void execute(ArrayList<FileSystemItem> files,
+ FileSystemItem targetDirectory,
+ Command completedCommand)
+ {
+ // copy list so we don't modify passed list
+ ArrayList<FileSystemItem> filesQueue = new ArrayList<FileSystemItem>(
+ files);
+
+ // begin copy sequence (this method keeps calling itself until
+ // the queue of files is empty)
+ copyNextFile(filesQueue, targetDirectory, completedCommand);
+ }
+
+ private void copyNextFile(final ArrayList<FileSystemItem> filesQueue,
+ final FileSystemItem targetDirectory,
+ final Command completedCommand)
+ {
+ // terminate if there are no files left
+ if (filesQueue.size() == 0)
+ {
+ if (completedCommand != null)
+ completedCommand.execute();
+
+ return;
+ }
+
+ // remove the first file from the list
+ final FileSystemItem sourceFile = filesQueue.remove(0);
+
+ // determine the default name and default selection
+ final String COPY_PREFIX = "CopyOf";
+ String defaultName = COPY_PREFIX + sourceFile.getName();
+ int defaultSelectionLength = COPY_PREFIX.length()
+ + sourceFile.getStem().length();
+
+ // show prompt for new filename
+ final String objectName = sourceFile.isDirectory() ? "Folder" : "File";
+ globalDisplay_.promptForText(
+ "Copy " + objectName,
+ "Enter a name for the copy of '" + sourceFile.getName() + "':",
+ defaultName,
+ 0,
+ defaultSelectionLength,
+ null,
+ new ProgressOperationWithInput<String>() {
+
+ public void execute(String input, ProgressIndicator progress)
+ {
+ progress.onProgress("Copying " + objectName.toLowerCase() + "...");
+
+ String targetFilePath = targetDirectory.completePath(input);
+ final FileSystemItem targetFile = FileSystemItem.createFile(
+ targetFilePath);
+
+ server_.copyFile(sourceFile,
+ targetFile,
+ false,
+ new VoidServerRequestCallback(progress) {
+ @Override
+ protected void onSuccess()
+ {
+ // copy the next file in the queue
+ copyNextFile(filesQueue,
+ targetDirectory,
+ completedCommand);
+ }
+ });
+ }
+ });
+ }
+
+ private final FilesServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesPane.java
new file mode 100644
index 0000000..279f771
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesPane.java
@@ -0,0 +1,260 @@
+/*
+ * FilesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.cellview.ColumnSortInfo;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.ServerDataSource;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+import org.rstudio.studio.client.workbench.views.files.model.PendingFileUpload;
+import org.rstudio.studio.client.workbench.views.files.ui.*;
+
+import java.util.ArrayList;
+
+public class FilesPane extends WorkbenchPane implements Files.Display
+{
+ @Inject
+ public FilesPane(GlobalDisplay globalDisplay,
+ FileDialogs fileDialogs,
+ Commands commands,
+ FileTypeRegistry fileTypeRegistry,
+ Provider<FileCommandToolbar> pFileCommandToolbar)
+ {
+ super("Files");
+ globalDisplay_ = globalDisplay ;
+ commands_ = commands;
+ fileDialogs_ = fileDialogs;
+ fileTypeRegistry_ = fileTypeRegistry;
+ pFileCommandToolbar_ = pFileCommandToolbar;
+ ensureWidget();
+ }
+
+ public void setObserver(Files.Display.Observer observer)
+ {
+ observer_ = observer;
+ }
+
+ // implement forwarding (and null-safe) observer for sub-components
+ private class DisplayObserverProxy implements Files.Display.Observer
+ {
+ public void onFileSelectionChanged()
+ {
+ if (observer_ != null)
+ observer_.onFileSelectionChanged();
+
+ }
+ public void onFileNavigation(FileSystemItem file)
+ {
+ if (observer_ != null)
+ observer_.onFileNavigation(file);
+ }
+
+ public void onSelectAllValueChanged(boolean value)
+ {
+ if (observer_ != null)
+ observer_.onSelectAllValueChanged(value);
+ }
+
+ public void onColumnSortOrderChanaged(
+ JsArray<ColumnSortInfo> sortOrder)
+ {
+ if (observer_ != null)
+ observer_.onColumnSortOrderChanaged(sortOrder);
+ }
+ }
+
+ @Override
+ public void setColumnSortOrder(JsArray<ColumnSortInfo> sortOrder)
+ {
+ filesList_.setColumnSortOrder(sortOrder);
+ }
+
+ public void listDirectory(final FileSystemItem directory,
+ ServerDataSource<JsArray<FileSystemItem>> dataSource)
+ {
+ setProgress(true);
+
+ dataSource.requestData(new ServerRequestCallback<JsArray<FileSystemItem>>(){
+ public void onResponseReceived(JsArray<FileSystemItem> response)
+ {
+ setProgress(false);
+ filePathToolbar_.setPath(directory.getPath());
+ filesList_.displayFiles(directory, response);
+ }
+ public void onError(ServerError error)
+ {
+ setProgress(false);
+ globalDisplay_.showErrorMessage("File Listing Error",
+ "Error navigating to " +
+ directory.getPath() + ":\n\n" +
+ error.getUserMessage());
+
+ if (!directory.equalTo(FileSystemItem.home()))
+ {
+ observer_.onFileNavigation(FileSystemItem.home());
+ }
+ }
+ });
+ }
+
+ public void updateDirectoryListing(FileChange fileAction)
+ {
+ if (filesList_ != null) // can be called by file_changed event
+ // prior to widget creation
+ {
+ filesList_.updateWithAction(fileAction);
+ }
+ }
+
+ public void renameFile(FileSystemItem from, FileSystemItem to)
+ {
+ filesList_.renameFile(from, to);
+ }
+
+ public void showFolderPicker(
+ String caption,
+ RemoteFileSystemContext fileSystemContext,
+ FileSystemItem initialDir,
+ ProgressOperationWithInput<FileSystemItem> operation)
+ {
+ fileDialogs_.chooseFolder(caption,
+ fileSystemContext,
+ initialDir,
+ operation);
+ }
+
+ public void showFileUpload(
+ String targetURL,
+ FileSystemItem targetDirectory,
+ RemoteFileSystemContext fileSystemContext,
+ OperationWithInput<PendingFileUpload> completedOperation)
+ {
+ FileUploadDialog dlg = new FileUploadDialog(targetURL,
+ targetDirectory,
+ fileDialogs_,
+ fileSystemContext,
+ completedOperation);
+ dlg.showModal();
+ }
+
+ public void selectAll()
+ {
+ filesList_.selectAll();
+ }
+
+ public void selectNone()
+ {
+ filesList_.selectNone();
+ }
+
+ public ArrayList<FileSystemItem> getSelectedFiles()
+ {
+ return filesList_.getSelectedFiles();
+ }
+
+ @Override
+ public void showHtmlFileChoice(FileSystemItem file,
+ Command onEdit,
+ Command onBrowse)
+ {
+ final ToolbarPopupMenu menu = new ToolbarPopupMenu();
+
+ String editLabel = AppCommand.formatMenuLabel(
+ commands_.renameFile().getImageResource(), "Open in Editor", null);
+ String openLabel = AppCommand.formatMenuLabel(
+ commands_.openHtmlExternal().getImageResource(),
+ "View in Web Browser",
+ null);
+
+ menu.addItem(new MenuItem(editLabel, true, onEdit));
+ menu.addItem(new MenuItem(openLabel, true, onBrowse));
+
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ Event event = Event.getCurrentEvent();
+ menu.setPopupPosition(event.getClientX(), event.getClientY());
+ }
+ });
+ }
+
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ filePathToolbar_ = new FilePathToolbar(new DisplayObserverProxy());
+
+ // create file list and file progress
+ filesList_ = new FilesList(new DisplayObserverProxy(), fileTypeRegistry_);
+
+ DockLayoutPanel dockPanel = new DockLayoutPanel(Unit.PX);
+ dockPanel.addNorth(filePathToolbar_, filePathToolbar_.getHeight());
+ dockPanel.add(filesList_);
+ // return container
+ return dockPanel;
+ }
+
+ @Override
+ public void onBeforeSelected()
+ {
+ if (needsInit)
+ {
+ needsInit = false;
+ FileSystemItem home = FileSystemItem.home();
+ observer_.onFileNavigation(home);
+ }
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ return pFileCommandToolbar_.get();
+ }
+
+ private boolean needsInit = false;
+ private FilesList filesList_ ;
+ private FilePathToolbar filePathToolbar_;
+ private final GlobalDisplay globalDisplay_ ;
+ private final FileDialogs fileDialogs_;
+ private Files.Display.Observer observer_;
+
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final Commands commands_;
+ private final Provider<FileCommandToolbar> pFileCommandToolbar_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesTab.java
new file mode 100644
index 0000000..6e740f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesTab.java
@@ -0,0 +1,56 @@
+/*
+ * FilesTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenFileInBrowserEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenFileInBrowserHandler;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateEvent;
+import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateHandler;
+
+public class FilesTab extends DelayLoadWorkbenchTab<Files>
+{
+ public interface Binder extends CommandBinder<Commands, FilesTab.Shim> {}
+
+ public abstract static class Shim
+ extends DelayLoadTabShim<Files, FilesTab>
+ implements OpenFileInBrowserHandler, DirectoryNavigateHandler
+ {
+ @Handler
+ public abstract void onUploadFile();
+ @Handler
+ public abstract void onSetWorkingDirToFilesPane();
+ @Handler
+ public abstract void onGoToWorkingDir();
+ }
+
+ @Inject
+ public FilesTab(Shim shim,
+ Binder binder,
+ EventBus events,
+ Commands commands)
+ {
+ super("Files", shim);
+ binder.bind(commands, shim);
+ events.addHandler(OpenFileInBrowserEvent.TYPE, shim);
+ events.addHandler(DirectoryNavigateEvent.TYPE, shim);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesUpload.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesUpload.java
new file mode 100644
index 0000000..29620b9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/FilesUpload.java
@@ -0,0 +1,139 @@
+/*
+ * FilesUpload.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.views.files.model.FileUploadToken;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+import org.rstudio.studio.client.workbench.views.files.model.PendingFileUpload;
+
+public class FilesUpload
+{
+ @Inject
+ public FilesUpload(Files.Display display,
+ GlobalDisplay globalDisplay,
+ FilesServerOperations server)
+ {
+ display_ = display;
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ }
+
+ void execute(FileSystemItem targetDirectory,
+ RemoteFileSystemContext fileSystemContext)
+ {
+ // this can be invoked from the other side of the async shim
+ // so make sure we come to the front whenever this is called
+ display_.bringToFront();
+
+ display_.showFileUpload(
+ server_.getFileUploadUrl(),
+ targetDirectory,
+ fileSystemContext,
+ new OperationWithInput<PendingFileUpload>() {
+ public void execute(PendingFileUpload pendingUpload)
+ {
+ // confirm overwrites if necessary
+ FileUploadToken token = pendingUpload.getToken();
+ if (pendingUpload.getOverwrites().length() > 0)
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Confirm Overwrite",
+ confirmFileUploadOverwriteMessage(pendingUpload),
+ false,
+ completeFileUploadOperation(token, true),
+ completeFileUploadOperation(token, false),
+ false);
+ }
+ else
+ {
+ completeFileUploadOperation(token, true).execute();
+ }
+ }
+ });
+ }
+
+
+ private String confirmFileUploadOverwriteMessage(
+ PendingFileUpload pendingUpload)
+ {
+ JsArray<FileSystemItem> overwrites = pendingUpload.getOverwrites();
+ FileSystemItem firstFile = overwrites.get(0);
+ boolean multiple = overwrites.length() > 1 ;
+ StringBuilder msg = new StringBuilder();
+ msg.append("The upload will overwrite ");
+ if (multiple)
+ msg.append("multiple files including ");
+ else
+ msg.append("the file ");
+ msg.append("\"" + firstFile.getPath() + "\". ");
+
+ msg.append("Are you sure you want to overwrite ");
+ if (multiple)
+ msg.append("these files?");
+ else
+ msg.append("this file?");
+ return msg.toString();
+ }
+
+ private Operation completeFileUploadOperation(final FileUploadToken token,
+ final boolean commit)
+ {
+ return new Operation()
+ {
+ public void execute()
+ {
+ String msg = (commit ? "Completing" : "Cancelling") +
+ " file upload...";
+ final Command dismissProgress = globalDisplay_.showProgress(msg);
+
+ server_.completeUpload(token,
+ commit,
+ new ServerRequestCallback<Void>() {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ dismissProgress.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ dismissProgress.execute();
+ globalDisplay_.showErrorMessage("File Upload Error",
+ error.getUserMessage());
+ }
+ });
+ }
+ };
+ }
+
+
+ private final Files.Display display_;
+ private final GlobalDisplay globalDisplay_ ;
+ private final FilesServerOperations server_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateEvent.java
new file mode 100644
index 0000000..5a9e1b4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateEvent.java
@@ -0,0 +1,83 @@
+/*
+ * DirectoryNavigateEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class DirectoryNavigateEvent extends GwtEvent<DirectoryNavigateHandler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public final native String getDirectory() /*-{
+ return this.directory;
+ }-*/;
+
+ public final native boolean getActivate() /*-{
+ return this.activate;
+ }-*/;
+ }
+
+ public static final GwtEvent.Type<DirectoryNavigateHandler> TYPE =
+ new GwtEvent.Type<DirectoryNavigateHandler>();
+
+ public DirectoryNavigateEvent(Data data)
+ {
+ this(FileSystemItem.createDir(data.getDirectory()), data.getActivate());
+ }
+
+ public DirectoryNavigateEvent(FileSystemItem directory)
+ {
+ this(directory, false);
+ }
+
+ public DirectoryNavigateEvent(FileSystemItem directory,
+ boolean activate)
+ {
+ directory_ = directory;
+ activate_ = activate;
+ }
+
+ public FileSystemItem getDirectory()
+ {
+ return directory_;
+ }
+
+ public boolean getActivate()
+ {
+ return activate_;
+ }
+
+ @Override
+ protected void dispatch(DirectoryNavigateHandler handler)
+ {
+ handler.onDirectoryNavigate(this);
+ }
+
+ @Override
+ public GwtEvent.Type<DirectoryNavigateHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileSystemItem directory_;
+ private final boolean activate_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateHandler.java
new file mode 100644
index 0000000..02d5050
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/DirectoryNavigateHandler.java
@@ -0,0 +1,23 @@
+/*
+ * DirectoryNavigateHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface DirectoryNavigateHandler extends EventHandler
+{
+ void onDirectoryNavigate(DirectoryNavigateEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeEvent.java
new file mode 100644
index 0000000..1e1de49
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeEvent.java
@@ -0,0 +1,48 @@
+/*
+ * FileChangeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+
+public class FileChangeEvent extends GwtEvent<FileChangeHandler>
+{
+ public static final GwtEvent.Type<FileChangeHandler> TYPE =
+ new GwtEvent.Type<FileChangeHandler>();
+
+ public FileChangeEvent(FileChange fileChange)
+ {
+ fileChange_ = fileChange;
+ }
+
+ public FileChange getFileChange()
+ {
+ return fileChange_;
+ }
+
+ @Override
+ protected void dispatch(FileChangeHandler handler)
+ {
+ handler.onFileChange(this);
+ }
+
+ @Override
+ public GwtEvent.Type<FileChangeHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final FileChange fileChange_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeHandler.java
new file mode 100644
index 0000000..2851922
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/FileChangeHandler.java
@@ -0,0 +1,22 @@
+/*
+ * FileChangeHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface FileChangeHandler extends EventHandler
+{
+ void onFileChange(FileChangeEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderEvent.java
new file mode 100644
index 0000000..6a0d092
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderEvent.java
@@ -0,0 +1,48 @@
+/*
+ * ShowFolderEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class ShowFolderEvent extends GwtEvent<ShowFolderHandler>
+{
+ public static final Type<ShowFolderHandler> TYPE =
+ new Type<ShowFolderHandler>();
+
+ public ShowFolderEvent(FileSystemItem path)
+ {
+ path_ = path;
+ }
+
+ public FileSystemItem getPath()
+ {
+ return path_;
+ }
+
+ @Override
+ public Type<ShowFolderHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ShowFolderHandler handler)
+ {
+ handler.onShowFolder(this);
+ }
+
+ private final FileSystemItem path_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderHandler.java
new file mode 100644
index 0000000..6cd0795
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/events/ShowFolderHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowFolderHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowFolderHandler extends EventHandler
+{
+ void onShowFolder(ShowFolderEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileChange.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileChange.java
new file mode 100644
index 0000000..481ef1b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileChange.java
@@ -0,0 +1,61 @@
+/*
+ * FileChange.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class FileChange extends JavaScriptObject
+{
+ // NOTE: missing 2 because we got rid of RENAME
+ public final static int ADD = 1;
+ public final static int DELETE = 3;
+ public final static int MODIFIED = 4;
+
+ public static final FileChange createAdd(FileSystemItem file)
+ {
+ return create(ADD, file);
+ }
+
+ public static final FileChange createDelete(FileSystemItem file)
+ {
+ return create(DELETE, file);
+ }
+
+ public static final FileChange createModified(FileSystemItem file)
+ {
+ return create(MODIFIED, file);
+ }
+
+ private static final native FileChange create(int type,
+ FileSystemItem file) /*-{
+ var fileViewAction = new Object();
+ fileViewAction.type = type ;
+ fileViewAction.file = file ;
+ return fileViewAction ;
+ }-*/;
+
+ protected FileChange()
+ {
+ }
+
+ public final native int getType() /*-{
+ return this.type;
+ }-*/;
+
+ public final native FileSystemItem getFile() /*-{
+ return this.file;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileSystemItemAction.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileSystemItemAction.java
new file mode 100644
index 0000000..292117b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileSystemItemAction.java
@@ -0,0 +1,22 @@
+/*
+ * FileSystemItemAction.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public interface FileSystemItemAction
+{
+ void execute(FileSystemItem file);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileUploadToken.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileUploadToken.java
new file mode 100644
index 0000000..2957286
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FileUploadToken.java
@@ -0,0 +1,24 @@
+/*
+ * FileUploadToken.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FileUploadToken extends JavaScriptObject
+{
+ protected FileUploadToken()
+ {
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FilesServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FilesServerOperations.java
new file mode 100644
index 0000000..c5c1106
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/FilesServerOperations.java
@@ -0,0 +1,81 @@
+/*
+ * FilesServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.model;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+import java.util.ArrayList;
+
+public interface FilesServerOperations
+{
+ void stat(String path,
+ ServerRequestCallback<FileSystemItem> requestCallback);
+
+ void isTextFile(String path,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ // get a file listing
+ void listFiles(FileSystemItem directory,
+ boolean monitor,
+ ServerRequestCallback<JsArray<FileSystemItem>> requestCallback);
+
+ void listAllFiles(String path,
+ String pattern,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ // create a folder
+ void createFolder(FileSystemItem folder,
+ ServerRequestCallback<Void> requestCallback);
+
+ // delete files
+ void deleteFiles(ArrayList<FileSystemItem> files,
+ ServerRequestCallback<Void> requestCallback);
+
+ // copy file
+ void copyFile(FileSystemItem sourceFile,
+ FileSystemItem targetFile,
+ boolean overwrite,
+ ServerRequestCallback<Void> requestCallback);
+
+ // move files
+ void moveFiles(ArrayList<FileSystemItem> files,
+ FileSystemItem targetDirectory,
+ ServerRequestCallback<Void> requestCallback);
+
+ // rename file
+ void renameFile(FileSystemItem file,
+ FileSystemItem targetFile,
+ ServerRequestCallback<Void> serverRequestCallback);
+
+
+ String getFileUrl(FileSystemItem file);
+
+ String getFileUploadUrl();
+
+ void completeUpload(FileUploadToken token,
+ boolean commit,
+ ServerRequestCallback<Void> requestCallback);
+
+ String getFileExportUrl(String name,
+ FileSystemItem file);
+
+ String getFileExportUrl(String name,
+ FileSystemItem parentDirectory,
+ ArrayList<String> filenames);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/PendingFileUpload.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/PendingFileUpload.java
new file mode 100644
index 0000000..3783bb4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/model/PendingFileUpload.java
@@ -0,0 +1,34 @@
+/*
+ * PendingFileUpload.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class PendingFileUpload extends JavaScriptObject
+{
+ protected PendingFileUpload()
+ {
+ }
+
+ public final native FileUploadToken getToken() /*-{
+ return this.token;
+ }-*/;
+
+ public final native JsArray<FileSystemItem> getOverwrites() /*-{
+ return this.overwrites;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileCommandToolbar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileCommandToolbar.java
new file mode 100644
index 0000000..23e5e22
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileCommandToolbar.java
@@ -0,0 +1,61 @@
+/*
+ * FileCommandToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.ui;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+
+public class FileCommandToolbar extends Toolbar
+{
+ @Inject
+ public FileCommandToolbar(Commands commands, Session session)
+ {
+ StandardIcons icons = StandardIcons.INSTANCE;
+
+ addLeftWidget(commands.newFolder().createToolbarButton());
+ addLeftSeparator();
+ addLeftWidget(commands.uploadFile().createToolbarButton());
+ addLeftSeparator();
+ addLeftWidget(commands.deleteFiles().createToolbarButton());
+ addLeftWidget(commands.renameFile().createToolbarButton());
+ addLeftSeparator();
+
+ // More
+ ToolbarPopupMenu moreMenu = new ToolbarPopupMenu();
+ moreMenu.addItem(commands.copyFile().createMenuItem(false));
+ moreMenu.addItem(commands.moveFiles().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands.exportFiles().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands.setAsWorkingDir().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands.showFolder().createMenuItem(false));
+
+ ToolbarButton moreButton = new ToolbarButton("More",
+ icons.more_actions(),
+ moreMenu);
+ addLeftWidget(moreButton);
+
+
+ // Refresh
+ ToolbarButton refreshButton = commands.refreshFiles().createToolbarButton();
+ addRightWidget(refreshButton);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilePathToolbar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilePathToolbar.java
new file mode 100644
index 0000000..a8aeefc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilePathToolbar.java
@@ -0,0 +1,146 @@
+/*
+ * FilePathToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.ui;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemContext.Callbacks;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.files.PosixFileSystemContext;
+import org.rstudio.core.client.files.filedialog.PathBreadcrumbWidget;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.views.files.Files;
+
+public class FilePathToolbar extends Composite
+ implements RequiresResize, ProvidesResize
+{
+ private class FileSystemContextImpl extends PosixFileSystemContext
+ {
+ public MessageDisplay messageDisplay()
+ {
+ return RStudioGinjector.INSTANCE.getGlobalDisplay();
+ }
+
+ public void cd(String relativeOrAbsolutePath)
+ {
+ workingDir_ = combine(pwd(), relativeOrAbsolutePath);
+ callbacks_.onNavigated();
+ }
+
+ public void refresh()
+ {
+ throw new UnsupportedOperationException("refresh not supported");
+ }
+
+ public void mkdir(String folderName, ProgressIndicator progress)
+ {
+ throw new UnsupportedOperationException("mkdir not supported");
+ }
+
+ public ImageResource getIcon(FileSystemItem item)
+ {
+ throw new UnsupportedOperationException("getIcon not supported");
+ }
+ }
+
+ public FilePathToolbar(Files.Display.NavigationObserver navigationObserver)
+ {
+ LayoutPanel layout = new LayoutPanel();
+ layout.setSize("100%", "21px");
+ initWidget(layout);
+ setStyleName(ThemeStyles.INSTANCE.secondaryToolbar());
+
+ navigationObserver_ = navigationObserver;
+
+ // select all check box
+ CheckBox selectAllCheckBox = new CheckBox();
+ selectAllCheckBox.addStyleDependentName("FilesSelectAll");
+ selectAllCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>(){
+
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ navigationObserver_.onSelectAllValueChanged(
+ event.getValue().booleanValue());
+ }
+ });
+
+ layout.add(selectAllCheckBox);
+ layout.setWidgetTopBottom(selectAllCheckBox, 0, Unit.PX, 0, Unit.PX);
+ layout.setWidgetLeftWidth(selectAllCheckBox, 0, Unit.PX, 20, Unit.PX);
+
+ // breadcrumb widget
+ fileSystemContext_ = new FileSystemContextImpl();
+ fileSystemContext_.setCallbacks(new Callbacks()
+ {
+ public void onNavigated()
+ {
+ navigationObserver_.onFileNavigation(fileSystemContext_.pwdItem());
+ }
+
+ public void onError(String errorMessage)
+ {
+ assert false : "Not implemented";
+ }
+
+ public void onDirectoryCreated(FileSystemItem directory)
+ {
+ assert false : "Not implemented";
+ }
+ });
+ pathBreadcrumbWidget_ = new PathBreadcrumbWidget(fileSystemContext_);
+ pathBreadcrumbWidget_.addStyleDependentName("filepane");
+ pathBreadcrumbWidget_.addSelectionCommitHandler(
+ new SelectionCommitHandler<FileSystemItem>()
+ {
+ public void onSelectionCommit(SelectionCommitEvent<FileSystemItem> e)
+ {
+ fileSystemContext_.cd(e.getSelectedItem().getPath());
+ }
+ });
+
+ layout.add(pathBreadcrumbWidget_);
+ layout.setWidgetTopBottom(pathBreadcrumbWidget_, 0, Unit.PX, 0, Unit.PX);
+ layout.setWidgetLeftRight(pathBreadcrumbWidget_, 21, Unit.PX, 0, Unit.PX);
+ }
+
+ public void setPath(String path)
+ {
+ assert fileSystemContext_.isAbsolute(path);
+ pathBreadcrumbWidget_.setDirectory(fileSystemContext_.parseDir(path));
+ }
+
+ public int getHeight()
+ {
+ return 21;
+ }
+
+ public void onResize()
+ {
+ if (getWidget() instanceof RequiresResize)
+ ((RequiresResize)getWidget()).onResize();
+ }
+
+ private final Files.Display.NavigationObserver navigationObserver_;
+ private FileSystemContextImpl fileSystemContext_;
+ private PathBreadcrumbWidget pathBreadcrumbWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileUploadDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileUploadDialog.java
new file mode 100644
index 0000000..cbc4eac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FileUploadDialog.java
@@ -0,0 +1,240 @@
+/*
+ * FileUploadDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.files.filedialog.FileDialogResources;
+import org.rstudio.core.client.jsonrpc.RpcError;
+import org.rstudio.core.client.jsonrpc.RpcResponse;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.HtmlFormModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.views.files.model.PendingFileUpload;
+
+public class FileUploadDialog extends HtmlFormModalDialog<PendingFileUpload>
+{
+ public FileUploadDialog(
+ String actionURL,
+ FileSystemItem targetDirectory,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileSystemContext,
+ OperationWithInput<PendingFileUpload> completedOperation)
+ {
+ super("Upload Files",
+ "Uploading file...",
+ actionURL,
+ completedOperation);
+ fileDialogs_ = fileDialogs;
+ fileSystemContext_ = fileSystemContext;
+ targetDirectory_ = targetDirectory;
+ }
+
+ @Override
+ protected void positionAndShowDialog(final Command onCompleted)
+ {
+ final PopupPanel thisPanel = this;
+ setPopupPositionAndShow(new PopupPanel.PositionCallback() {
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ int left = (Window.getClientWidth()/2) - (offsetWidth/2);
+ int top = (Window.getClientHeight()/2) - (offsetHeight/2);
+ // clip the top so the choose file dialog always appears
+ // over the file upload dialog (mostly a problem on osx)
+ top = Math.min(top, 200);
+
+ thisPanel.setPopupPosition(left, top);
+
+ onCompleted.execute();
+ }
+ });
+ }
+
+ @Override
+ protected void setFormPanelEncodingAndMethod(FormPanel formPanel)
+ {
+ formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
+ formPanel.setMethod(FormPanel.METHOD_POST);
+ }
+
+ @Override
+ protected PendingFileUpload parseResults(String results) throws Exception
+ {
+ RpcResponse response = RpcResponse.parse(results);
+ if (response == null)
+ throw new Exception("Unexpected response from server");
+
+ // check for errors
+ RpcError error = response.getError();
+ if (error != null)
+ {
+ // special error message if we know the user failed to
+ // select a directory
+ if (error.getCode() == RpcError.PARAM_INVALID &&
+ fileUpload_.getFilename().length() == 0)
+ {
+ throw new Exception("You must specify a file to upload.");
+ }
+ else
+ {
+ throw new Exception(error.getEndUserMessage());
+ }
+ }
+
+ // return PendingFileUpload
+ PendingFileUpload pendingFileUpload = response.getResult();
+ return pendingFileUpload;
+ }
+
+ // NOTE: discovered that GWT was always submitting the form whether
+ // or not we cancelled the SubmitEvent. Perhaps their bug? Anyway, the
+ // solution was to always return true for validation and then to check
+ // for the empty fileUpload filename above in parseResults (knowing that
+ // the server would always return an error if no file was specified)
+ @Override
+ protected boolean validate()
+ {
+ return true;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel panel = new VerticalPanel();
+ panel.setStyleName(ThemeStyles.INSTANCE.fileUploadPanel());
+
+ // directory panel
+ HorizontalPanel directoryPanel = new HorizontalPanel();
+ directoryPanel.setWidth("100%");
+ directoryPanel.setStyleName(ThemeStyles.INSTANCE.fileUploadField());
+
+ // directory name (informational field)
+ panel.add(new Label("Target directory:"));
+ directoryNameWidget_ = new DirectoryNameWidget();
+ directoryNameWidget_.setDirectory(targetDirectory_);
+ directoryPanel.add(directoryNameWidget_);
+
+ // browse directory button
+ // JJA: removed browse button (was causing confusion for users who
+ // thought it was what they should press to browse local files)
+ /*
+ Button browseButton = new Button("Browse...",
+ new BrowseDirectoryClickHandler());
+ browseButton.getElement().getStyle().setMarginRight(5, Unit.PX);
+ directoryPanel.add(browseButton);
+ directoryPanel.setCellHorizontalAlignment(
+ browseButton,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+ */
+ panel.add(directoryPanel);
+
+ // filename field
+ panel.add(new Label("File to upload:"));
+ fileUpload_ = new FileUpload();
+ fileUpload_.setStyleName(ThemeStyles.INSTANCE.fileUploadField());
+ fileUpload_.setName("file");
+ panel.add(fileUpload_);
+
+ // zip file tip field
+ HTML tip = new HTML("<b>TIP</b>: To upload multiple files or a " +
+ "directory, create a zip file. The zip file will " +
+ "be automatically expanded after upload.");
+ tip.addStyleName(ThemeStyles.INSTANCE.fileUploadField());
+ tip.addStyleName(ThemeStyles.INSTANCE.fileUploadTipLabel());
+ panel.add(tip);
+
+ // target directory hidden field
+ targetDirectoryHidden_ = new Hidden("targetDirectory",
+ targetDirectory_.getPath());
+ panel.add(targetDirectoryHidden_);
+
+ return panel;
+ }
+
+ // JJA: used by currently commented out browse directory button
+ @SuppressWarnings("unused")
+ private class BrowseDirectoryClickHandler implements ClickHandler
+ {
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.chooseFolder(
+ "Choose Target Directory",
+ fileSystemContext_,
+ targetDirectory_,
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+ targetDirectory_ = input;
+ targetDirectoryHidden_.setValue(input.getPath());
+ directoryNameWidget_.setDirectory(input);
+ }
+ });
+ }
+ }
+
+ private class DirectoryNameWidget extends HorizontalPanel
+ {
+ public DirectoryNameWidget()
+ {
+ setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
+
+ image_ = new Image();
+ image_.setStyleName(
+ FileDialogResources.INSTANCE.styles().columnIcon());
+ this.add(image_);
+ name_ = new HTML();
+ this.add(name_);
+ }
+
+ public void setDirectory(FileSystemItem directoryItem)
+ {
+ if (directoryItem.equalTo(FileSystemItem.home()))
+ {
+ image_.setResource(FileDialogResources.INSTANCE.homeImage());
+ name_.setHTML("Home");
+ }
+ else
+ {
+ image_.setResource(FileIconResources.INSTANCE.iconFolder());
+ name_.setHTML(" " + directoryItem.getPath());
+ }
+ }
+
+ Image image_ ;
+ HTML name_ ;
+ }
+
+ private FileUpload fileUpload_;
+ private FileSystemItem targetDirectory_;
+ private Hidden targetDirectoryHidden_;
+ private DirectoryNameWidget directoryNameWidget_;
+ private final FileDialogs fileDialogs_;
+ private RemoteFileSystemContext fileSystemContext_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesList.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesList.java
new file mode 100644
index 0000000..85bd434
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesList.java
@@ -0,0 +1,579 @@
+/*
+ * FilesList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.files.ui;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.cellview.ColumnSortInfo;
+import org.rstudio.core.client.cellview.LinkColumn;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.workbench.views.files.Files;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.ImageResourceCell;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.ColumnSortEvent;
+import com.google.gwt.user.cellview.client.ColumnSortList;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.view.client.DefaultSelectionEventManager;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.ProvidesKey;
+
+public class FilesList extends Composite
+{
+ public FilesList(final Files.Display.Observer observer,
+ final FileTypeRegistry fileTypeRegistry)
+ {
+ observer_ = observer;
+
+ // create data provider and sort handler
+ dataProvider_ = new ListDataProvider<FileSystemItem>();
+ sortHandler_ = new ColumnSortEvent.ListHandler<FileSystemItem>(
+ dataProvider_.getList());
+
+ // create cell table
+ filesCellTable_ = new CellTable<FileSystemItem>(
+ 15,
+ FilesListCellTableResources.INSTANCE,
+ KEY_PROVIDER);
+ selectionModel_ = new MultiSelectionModel<FileSystemItem>(KEY_PROVIDER);
+ filesCellTable_.setSelectionModel(
+ selectionModel_,
+ DefaultSelectionEventManager.<FileSystemItem> createCheckboxManager());
+ filesCellTable_.setWidth("100%", false);
+
+ // hook-up data provider
+ dataProvider_.addDataDisplay(filesCellTable_);
+
+ // add columns
+ addSelectionColumn();
+ addIconColumn(fileTypeRegistry);
+ nameColumn_ = addNameColumn();
+ sizeColumn_ = addSizeColumn();
+ modifiedColumn_ = addModifiedColumn();
+
+ // initialize sorting
+ addColumnSortHandler();
+
+ // enclose in scroll panel
+ scrollPanel_ = new ScrollPanel();
+ initWidget(scrollPanel_);
+ scrollPanel_.setWidget(filesCellTable_);
+ }
+
+ private Column<FileSystemItem, Boolean> addSelectionColumn()
+ {
+ Column<FileSystemItem, Boolean> checkColumn =
+ new Column<FileSystemItem, Boolean>(new CheckboxCell(true, false) {
+ @Override
+ public void render(Context context, Boolean value, SafeHtmlBuilder sb)
+ {
+ // don't render the check box if its for the parent path
+ if (parentPath_ == null || context.getIndex() > 0)
+ super.render(context, value, sb);
+ }
+ })
+ {
+ @Override
+ public Boolean getValue(FileSystemItem item)
+ {
+ return selectionModel_.isSelected(item);
+ }
+
+
+ };
+ checkColumn.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
+ filesCellTable_.addColumn(checkColumn);
+ filesCellTable_.setColumnWidth(checkColumn, 20, Unit.PX);
+
+ return checkColumn;
+ }
+
+
+ private Column<FileSystemItem, ImageResource> addIconColumn(
+ final FileTypeRegistry fileTypeRegistry)
+ {
+ Column<FileSystemItem, ImageResource> iconColumn =
+ new Column<FileSystemItem, ImageResource>(new ImageResourceCell()) {
+
+ @Override
+ public ImageResource getValue(FileSystemItem object)
+ {
+ if (object == parentPath_)
+ return FileIconResources.INSTANCE.iconUpFolder();
+ else
+ return fileTypeRegistry.getIconForFile(object);
+ }
+ };
+ iconColumn.setSortable(true);
+ filesCellTable_.addColumn(iconColumn,
+ SafeHtmlUtils.fromSafeConstant("<br/>"));
+ filesCellTable_.setColumnWidth(iconColumn, 20, Unit.PX);
+
+ sortHandler_.setComparator(iconColumn, new FilesListComparator() {
+ @Override
+ public int doCompare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ if (arg0.isDirectory() && !arg1.isDirectory())
+ return 1;
+ else if (arg1.isDirectory() && !arg0.isDirectory())
+ return -1;
+ else
+ return arg0.getExtension().compareTo(arg1.getExtension());
+ }
+ });
+
+ return iconColumn;
+ }
+
+ private LinkColumn<FileSystemItem> addNameColumn()
+ {
+ LinkColumn<FileSystemItem> nameColumn = new LinkColumn<FileSystemItem>(
+ dataProvider_,
+ new OperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input)
+ {
+ observer_.onFileNavigation(input);
+ }
+ })
+ {
+ @Override
+ public String getValue(FileSystemItem item)
+ {
+ if (item == parentPath_)
+ return "..";
+ else
+ return item.getName();
+ }
+ };
+ nameColumn.setSortable(true);
+ filesCellTable_.addColumn(nameColumn, "Name");
+
+ sortHandler_.setComparator(nameColumn, new FilesListComparator() {
+ @Override
+ public int doCompare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ return arg0.getName().compareToIgnoreCase(arg1.getName());
+ }
+ });
+
+ return nameColumn;
+ }
+
+
+ private TextColumn<FileSystemItem> addSizeColumn()
+ {
+ TextColumn<FileSystemItem> sizeColumn = new TextColumn<FileSystemItem>() {
+ public String getValue(FileSystemItem file)
+ {
+ if (!file.isDirectory())
+ return StringUtil.formatFileSize(file.getLength());
+ else
+ return new String();
+ }
+ };
+ sizeColumn.setSortable(true);
+ filesCellTable_.addColumn(sizeColumn, "Size");
+ filesCellTable_.setColumnWidth(sizeColumn, 80, Unit.PX);
+
+ sortHandler_.setComparator(sizeColumn, new FoldersOnBottomComparator() {
+ @Override
+ public int doItemCompare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ return new Long(arg0.getLength()).compareTo(
+ new Long(arg1.getLength()));
+ }
+ });
+
+ return sizeColumn;
+ }
+
+
+ private TextColumn<FileSystemItem> addModifiedColumn()
+ {
+ TextColumn<FileSystemItem> modColumn = new TextColumn<FileSystemItem>() {
+ public String getValue(FileSystemItem file)
+ {
+ if (!file.isDirectory())
+ return StringUtil.formatDate(file.getLastModified());
+ else
+ return new String();
+ }
+ };
+ modColumn.setSortable(true);
+ filesCellTable_.addColumn(modColumn, "Modified");
+ filesCellTable_.setColumnWidth(modColumn, 160, Unit.PX);
+
+ sortHandler_.setComparator(modColumn, new FoldersOnBottomComparator() {
+ @Override
+ public int doItemCompare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ return arg0.getLastModified().compareTo(arg1.getLastModified());
+ }
+ });
+
+ return modColumn;
+ }
+
+ private void addColumnSortHandler()
+ {
+ filesCellTable_.addColumnSortHandler(new Handler() {
+ @Override
+ public void onColumnSort(ColumnSortEvent event)
+ {
+ ColumnSortList sortList = event.getColumnSortList();
+
+ // insert the default initial sort order for size and modified
+ if (!applyingProgrammaticSort_)
+ {
+ if (event.getColumn().equals(sizeColumn_) &&
+ forceSizeSortDescending)
+ {
+ forceSizeSortDescending = false;
+ forceModifiedSortDescending = true;
+ sortList.insert(0,
+ new com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo(event.getColumn(), false));
+ }
+ else if (event.getColumn().equals(modifiedColumn_) &&
+ forceModifiedSortDescending)
+ {
+ forceModifiedSortDescending = false;
+ forceSizeSortDescending = true;
+ sortList.insert(0,
+ new com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo(event.getColumn(), false));
+ }
+ else
+ {
+ forceModifiedSortDescending = true;
+ forceSizeSortDescending = true;
+ }
+ }
+
+ // record sort order and fire event to observer
+ JsArray<ColumnSortInfo> sortOrder = newSortOrderArray();
+ for (int i=0; i<sortList.size(); i++)
+ {
+ // match the column index
+ com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo sortInfo = sortList.get(i);
+ Object column = sortInfo.getColumn();
+
+ for (int c=0; c<filesCellTable_.getColumnCount(); c++)
+ {
+ if (filesCellTable_.getColumn(c).equals(column))
+ {
+ boolean ascending = sortInfo.isAscending();
+ sortOrder.push(ColumnSortInfo.create(c, ascending));
+ break;
+ }
+ }
+ }
+ observer_.onColumnSortOrderChanaged(sortOrder);
+
+ // record active sort column ascending state
+ activeSortColumnAscending_ = event.isSortAscending();
+
+ // delegate the sort
+ sortHandler_.onColumnSort(event);
+ }
+
+ private native final JsArray<ColumnSortInfo> newSortOrderArray()
+ /*-{
+ return [];
+ }-*/;
+ private boolean forceSizeSortDescending = true;
+ private boolean forceModifiedSortDescending = true;
+ });
+ }
+
+
+
+ public void setColumnSortOrder(JsArray<ColumnSortInfo> sortOrder)
+ {
+ if (sortOrder != null)
+ {
+ ColumnSortInfo.setSortList(filesCellTable_, sortOrder);
+ }
+ else
+ {
+ ColumnSortList columnSortList = filesCellTable_.getColumnSortList();
+ columnSortList.clear();
+ columnSortList.push(nameColumn_);
+ }
+ }
+
+
+ public void displayFiles(FileSystemItem containingPath,
+ JsArray<FileSystemItem> files)
+ {
+ // clear the selection
+ selectNone();
+
+ // set containing path
+ containingPath_ = containingPath;
+ parentPath_ = containingPath_.getParentPath();
+
+ // set page size (+1 for parent path)
+ filesCellTable_.setPageSize(files.length() + 1);
+
+ // get underlying list
+ List<FileSystemItem> fileList = dataProvider_.getList();
+ fileList.clear();
+
+ // add entry for parent path if we have one
+ if (parentPath_ != null)
+ fileList.add(parentPath_);
+
+ // add files to table
+ for (int i=0; i<files.length(); i++)
+ fileList.add(files.get(i));
+
+ // apply sort list
+ applyColumnSortList();
+
+ // fire selection changed
+ observer_.onFileSelectionChanged();
+ }
+
+ public void selectAll()
+ {
+ for (FileSystemItem item : dataProvider_.getList())
+ {
+ if (item != parentPath_)
+ selectionModel_.setSelected(item, true);
+ }
+ }
+
+ public void selectNone()
+ {
+ selectionModel_.clear();
+ }
+
+
+ public ArrayList<FileSystemItem> getSelectedFiles()
+ {
+ // first make sure there are no leftover items in the selected set
+ Set<FileSystemItem> selectedSet = selectionModel_.getSelectedSet();
+ selectedSet.retainAll(dataProvider_.getList());
+
+ return new ArrayList<FileSystemItem>(selectedSet);
+ }
+
+ public void updateWithAction(FileChange viewAction)
+ {
+ final FileSystemItem file = viewAction.getFile();
+ final List<FileSystemItem> files = getFiles();
+ switch(viewAction.getType())
+ {
+ case FileChange.ADD:
+ if (file.getParentPath().equalTo(containingPath_))
+ {
+ int row = rowForFile(file);
+ if (row == -1)
+ {
+ files.add(file);
+ filesCellTable_.setPageSize(files.size() + 1);
+ }
+ else
+ {
+ // since we eagerly perform renames at the client UI
+ // layer then sometimes an "added" file is really just
+ // a rename. in this case the file already exists due
+ // to the eager rename in the client but still needs its
+ // metadata updated
+ files.set(row, file);
+ }
+ }
+ break;
+
+ case FileChange.MODIFIED:
+ {
+ int row = rowForFile(file);
+ if (row != -1)
+ files.set(row, file);
+ }
+ break;
+
+ case FileChange.DELETE:
+ {
+ int row = rowForFile(file);
+ if (row != -1)
+ {
+ files.remove(row);
+
+ // if a file is deleted and then re-added within the same
+ // event loop (as occurs when gedit saves a text file) the
+ // table doesn't always update correctly (it has a duplicate
+ // of the item deleted / re-added). the call to flush overcomes
+ // this issue
+ dataProvider_.flush();
+ }
+ }
+ break;
+
+ default:
+ Debug.log("Unexpected file change type: " + viewAction.getType());
+
+ break;
+ }
+ }
+
+ public void renameFile(FileSystemItem from, FileSystemItem to)
+ {
+ int index = getFiles().indexOf(from);
+ if (index != -1)
+ {
+ selectNone();
+ getFiles().set(index, to);
+ }
+ }
+
+ private List<FileSystemItem> getFiles()
+ {
+ return dataProvider_.getList();
+ }
+
+ private int rowForFile(FileSystemItem file)
+ {
+ List<FileSystemItem> files = getFiles();
+ for (int i=0; i<files.size(); i++)
+ if (files.get(i).equalTo(file))
+ return i ;
+
+ return -1;
+ }
+
+ private void applyColumnSortList()
+ {
+ applyingProgrammaticSort_ = true;
+ ColumnSortEvent.fire(filesCellTable_,
+ filesCellTable_.getColumnSortList());
+ applyingProgrammaticSort_ = false;
+ }
+
+ private static final ProvidesKey<FileSystemItem> KEY_PROVIDER =
+ new ProvidesKey<FileSystemItem>() {
+ @Override
+ public Object getKey(FileSystemItem item)
+ {
+ return item.getPath();
+ }
+ };
+
+ // comparator which ensures that the parent path is always on top
+ private abstract class FilesListComparator implements Comparator<FileSystemItem>
+ {
+ @Override
+ public int compare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ int ascendingFactor = activeSortColumnAscending_ ? -1 : 1;
+
+ if (arg0 == parentPath_)
+ return 1 * ascendingFactor;
+ else if (arg1 == parentPath_)
+ return -1 * ascendingFactor;
+ else
+ return doCompare(arg0, arg1);
+ }
+
+ protected abstract int doCompare(FileSystemItem arg0, FileSystemItem arg1);
+ }
+
+ private abstract class SeparateFoldersComparator extends FilesListComparator
+ {
+ public SeparateFoldersComparator(boolean foldersOnBottom)
+ {
+ if (foldersOnBottom)
+ sortFactor_ = 1;
+ else
+ sortFactor_ = -1;
+ }
+
+ protected int doCompare(FileSystemItem arg0, FileSystemItem arg1)
+ {
+ int ascendingResult = activeSortColumnAscending_ ? 1 : -1;
+
+ if (arg0.isDirectory() && !arg1.isDirectory())
+ return ascendingResult * sortFactor_;
+ else if (arg1.isDirectory() && !arg0.isDirectory())
+ return -ascendingResult * sortFactor_;
+ else
+ return doItemCompare(arg0, arg1);
+ }
+
+ protected abstract int doItemCompare(FileSystemItem arg0, FileSystemItem arg1);
+
+ private final int sortFactor_ ;
+ }
+
+ private abstract class FoldersOnBottomComparator extends SeparateFoldersComparator
+ {
+ public FoldersOnBottomComparator()
+ {
+ super(true);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private abstract class FoldersOnTopComparator extends SeparateFoldersComparator
+ {
+ public FoldersOnTopComparator()
+ {
+ super(false);
+ }
+ }
+
+
+ private FileSystemItem containingPath_ = null;
+ private FileSystemItem parentPath_ = null;
+
+ private final CellTable<FileSystemItem> filesCellTable_;
+ private final LinkColumn<FileSystemItem> nameColumn_;
+ private final TextColumn<FileSystemItem> sizeColumn_;
+ private final TextColumn<FileSystemItem> modifiedColumn_;
+ private boolean activeSortColumnAscending_ = true;
+ private boolean applyingProgrammaticSort_ = false;
+
+
+ private final MultiSelectionModel<FileSystemItem> selectionModel_;
+ private final ListDataProvider<FileSystemItem> dataProvider_;
+ private final ColumnSortEvent.ListHandler<FileSystemItem> sortHandler_;
+
+ private final Files.Display.Observer observer_ ;
+ private final ScrollPanel scrollPanel_ ;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableResources.java
new file mode 100644
index 0000000..f8bfabe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableResources.java
@@ -0,0 +1,46 @@
+/*
+ * FilesListCellTableResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.files.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.user.cellview.client.CellTable;
+import org.rstudio.core.client.theme.RStudioCellTableStyle;
+
+public interface FilesListCellTableResources extends CellTable.Resources
+{
+ static FilesListCellTableResources INSTANCE =
+ (FilesListCellTableResources)GWT.create(FilesListCellTableResources.class);
+
+ @Source("ascendingArrow.png")
+ @ImageOptions(flipRtl = true)
+ ImageResource cellTableSortAscending();
+
+ /**
+ * Icon used when a column is sorted in descending order.
+ */
+ @Source("descendingArrow.png")
+ @ImageOptions(flipRtl = true)
+ ImageResource cellTableSortDescending();
+
+ interface FilesListCellTableStyle extends CellTable.Style
+ {
+ }
+
+ @Source({RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS, "FilesListCellTableStyle.css"})
+ FilesListCellTableStyle cellTableStyle();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableStyle.css
new file mode 100644
index 0000000..57f8587
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/FilesListCellTableStyle.css
@@ -0,0 +1,24 @@
+ at def selectionBorderWidth 2px;
+
+.cellTableCell {
+ padding: 0 1px;
+}
+
+.cellTableCell input[type=checkbox] {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.cellTableLastColumnHeader {
+ border-right: none;
+}
+
+.cellTableEvenRow, .cellTableOddRow, .cellTableHoveredRow, .cellTableSelectedRow, .cellTableKeyboardSelectedRow {
+ background: #ffffff;
+ color: #606060;
+}
+
+.cellTableEvenRowCell, .cellTableOddRowCell, .cellTableHoveredRowCell, .cellTableSelectedRowCell, .cellTableKeyboardSelectedRowCell, .cellTableKeyboardSelectedCell {
+ border: selectionBorderWidth solid #ffffff;
+ color: #606060;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/ascendingArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/ascendingArrow.png
new file mode 100755
index 0000000..b88c48b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/ascendingArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/descendingArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/descendingArrow.png
new file mode 100755
index 0000000..37a7582
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/files/ui/descendingArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/Help.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/Help.java
new file mode 100644
index 0000000..7f9f47a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/Help.java
@@ -0,0 +1,215 @@
+/*
+ * Help.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help;
+
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.CsvReader;
+import org.rstudio.core.client.CsvWriter;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.workbench.WorkbenchList;
+import org.rstudio.studio.client.workbench.WorkbenchListManager;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.ListChangedEvent;
+import org.rstudio.studio.client.workbench.events.ListChangedHandler;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.help.events.*;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+import org.rstudio.studio.client.workbench.views.help.model.Link;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class Help extends BasePresenter implements ShowHelpHandler
+{
+ public interface Binder extends CommandBinder<Commands, Help> {}
+
+ public interface Display extends WorkbenchView,
+ HasHelpNavigateHandlers
+ {
+ String getUrl() ;
+ String getDocTitle() ;
+ void showHelp(String helpURL);
+ void back() ;
+ void forward() ;
+ void print() ;
+ void popout() ;
+ void refresh() ;
+ void focus();
+
+ LinkMenu getHistory() ;
+
+ /**
+ * Returns true if this Help pane has ever been navigated.
+ */
+ boolean navigated();
+ }
+
+ public interface LinkMenu extends HasSelectionHandlers<String>
+ {
+ void addLink(Link link) ;
+ void removeLink(Link link) ;
+ boolean containsLink(Link link) ;
+ void clearLinks() ;
+ ArrayList<Link> getLinks() ;
+ }
+
+ @Inject
+ public Help(Display view,
+ HelpServerOperations server,
+ WorkbenchListManager listManager,
+ Commands commands,
+ Binder binder)
+ {
+ super(view);
+ server_ = server ;
+ helpHistoryList_ = listManager.getHelpHistoryList();
+ view_ = view;
+
+ binder.bind(commands, this);
+
+ view_.addHelpNavigateHandler(new HelpNavigateHandler() {
+ public void onNavigate(HelpNavigateEvent event)
+ {
+ if (!historyInitialized_)
+ return;
+
+ CsvWriter csvWriter = new CsvWriter();
+ csvWriter.writeValue(getApplicationRelativeHelpUrl(event.getUrl()));
+ csvWriter.writeValue(event.getTitle());
+ helpHistoryList_.append(csvWriter.getValue());
+
+ }
+ }) ;
+ SelectionHandler<String> navigator = new SelectionHandler<String>() {
+ public void onSelection(SelectionEvent<String> event)
+ {
+ showHelp(event.getSelectedItem()) ;
+ }
+ } ;
+ view_.getHistory().addSelectionHandler(navigator) ;
+
+ // initialize help history
+ helpHistoryList_.addListChangedHandler(new ListChangedHandler() {
+ @Override
+ public void onListChanged(ListChangedEvent event)
+ {
+ // clear existing
+ final LinkMenu history = view_.getHistory() ;
+ history.clearLinks();
+
+ // intialize from the list
+ ArrayList<String> list = event.getList();
+ for (int i=0; i<list.size(); i++)
+ {
+ // parse the two fields out
+ CsvReader csvReader = new CsvReader(list.get(i));
+ Iterator<String[]> it = csvReader.iterator();
+ if (!it.hasNext())
+ continue;
+ String[] fields = it.next();
+ if (fields.length != 2)
+ continue;
+
+ // add the link
+ Link link = new Link(fields[0], fields[1]);
+ history.addLink(link);
+ }
+
+ // one time init
+ if (!historyInitialized_)
+ {
+ // mark us initialized
+ historyInitialized_ = true ;
+
+ if (!view_.navigated())
+ {
+ ArrayList<Link> links = history.getLinks();
+ if (links.size() > 0)
+ showHelp(links.get(0).getUrl());
+ else
+ home();
+ }
+ }
+ }
+ });
+
+ }
+
+ // Home handled by Shim for activation from main menu context
+ public void onHelpHome() { view_.bringToFront(); home(); }
+
+
+
+ @Handler public void onHelpBack() { view_.back(); }
+ @Handler public void onHelpForward() { view_.forward(); }
+ @Handler public void onPrintHelp() { view_.print(); }
+ @Handler public void onHelpPopout() { view_.popout(); }
+ @Handler public void onRefreshHelp() { view_.refresh(); }
+ @Handler
+ public void onClearHelpHistory()
+ {
+ if (!historyInitialized_)
+ return;
+
+ helpHistoryList_.clear();
+ }
+
+ public void onShowHelp(ShowHelpEvent event)
+ {
+ showHelp(event.getTopicUrl());
+ view_.bringToFront();
+ }
+
+ public void onActivateHelp(ActivateHelpEvent event)
+ {
+ view_.bringToFront();
+ view_.focus();
+ }
+
+ private void home()
+ {
+ showHelp("help/doc/html/index.html");
+ }
+
+ public Display getDisplay()
+ {
+ return view_ ;
+ }
+
+ private void showHelp(String topicUrl)
+ {
+ view_.showHelp(server_.getApplicationURL(topicUrl));
+ }
+
+ private String getApplicationRelativeHelpUrl(String helpUrl)
+ {
+ String appUrl = server_.getApplicationURL("");
+ if (helpUrl.startsWith(appUrl) && !helpUrl.equals(appUrl))
+ return helpUrl.substring(appUrl.length());
+ else
+ return helpUrl;
+ }
+
+ private Display view_ ;
+ private HelpServerOperations server_ ;
+ private WorkbenchList helpHistoryList_;
+ private boolean historyInitialized_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpPane.java
new file mode 100644
index 0000000..07b5c32
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpPane.java
@@ -0,0 +1,647 @@
+/*
+ * HelpPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.help;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.command.ShortcutManager;
+import org.rstudio.core.client.dom.ElementEx;
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.events.NativeKeyDownEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.FindTextBox;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.RStudioFrame;
+import org.rstudio.core.client.widget.SecondaryToolbar;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.common.AutoGlassPanel;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.help.Help.LinkMenu;
+import org.rstudio.studio.client.workbench.views.help.events.HelpNavigateEvent;
+import org.rstudio.studio.client.workbench.views.help.events.HelpNavigateHandler;
+import org.rstudio.studio.client.workbench.views.help.model.VirtualHistory;
+import org.rstudio.studio.client.workbench.views.help.search.HelpSearch;
+
+public class HelpPane extends WorkbenchPane
+ implements Help.Display
+{
+ @Inject
+ public HelpPane(Provider<HelpSearch> searchProvider,
+ GlobalDisplay globalDisplay,
+ Commands commands)
+ {
+ super("Help") ;
+
+ searchProvider_ = searchProvider ;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+
+ MenuItem clear = commands.clearHelpHistory().createMenuItem(false);
+ history_ = new ToolbarLinkMenu(12, true, null, new MenuItem[] { clear }) ;
+
+ Window.addResizeHandler(new ResizeHandler()
+ {
+ public void onResize(ResizeEvent event)
+ {
+ history_.getMenu().hide();
+ }
+ });
+
+ ensureWidget();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ frame_ = new RStudioFrame() ;
+ frame_.setSize("100%", "100%");
+ frame_.setStylePrimaryName("rstudio-HelpFrame") ;
+ ElementIds.assignElementId(frame_.getElement(), ElementIds.HELP_FRAME);
+
+ return new AutoGlassPanel(frame_);
+ }
+
+ @Override
+ public void onResize()
+ {
+ manageTitleLabelMaxSize();
+
+ super.onResize();
+ }
+
+ private void manageTitleLabelMaxSize()
+ {
+ if (title_ != null)
+ {
+ int offsetWidth = getOffsetWidth();
+ if (offsetWidth > 0)
+ {
+ int newWidth = offsetWidth - 25;
+ if (newWidth > 0)
+ title_.getElement().getStyle().setPropertyPx("maxWidth", newWidth);
+ }
+ }
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad() ;
+
+ if (!initialized_)
+ {
+ initialized_ = true;
+
+ initHelpCallbacks() ;
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ manageTitleLabelMaxSize();
+ }
+ });
+ }
+ }
+
+ public final native void initHelpCallbacks() /*-{
+ function addEventHandler(subject, eventName, handler) {
+ if (subject.addEventListener) {
+ subject.addEventListener(eventName, handler, false);
+ }
+ else {
+ subject.attachEvent(eventName, handler);
+ }
+ }
+
+ var thiz = this ;
+ $wnd.helpNavigated = function(document, win) {
+ thiz. at org.rstudio.studio.client.workbench.views.help.HelpPane::helpNavigated(Lcom/google/gwt/dom/client/Document;)(document);
+ addEventHandler(win, "unload", function () {
+ thiz. at org.rstudio.studio.client.workbench.views.help.HelpPane::unload()();
+ });
+ } ;
+ $wnd.helpNavigate = function(url) {
+ thiz. at org.rstudio.studio.client.workbench.views.help.HelpPane::showHelp(Ljava/lang/String;)(url);
+ } ;
+
+ $wnd.helpKeydown = function(e) {
+ thiz. at org.rstudio.studio.client.workbench.views.help.HelpPane::handleKeyDown(Lcom/google/gwt/dom/client/NativeEvent;)(e);
+ } ;
+ }-*/;
+
+
+
+ // delegate shortcuts which occur while Help has focus
+
+ private void handleKeyDown(NativeEvent e)
+ {
+ // determine whether this key-combination means we should focus find
+ int mod = KeyboardShortcut.getModifierValue(e);
+ if ((mod == (BrowseCap.hasMetaKey() ? KeyboardShortcut.META
+ : KeyboardShortcut.CTRL)) &&
+ e.getKeyCode() == 'F')
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ WindowEx.get().focus();
+ findTextBox_.focus();
+ findTextBox_.selectAll();
+ }
+
+ // delegate to the shortcut manager
+ else
+ {
+ NativeKeyDownEvent evt = new NativeKeyDownEvent(e);
+ ShortcutManager.INSTANCE.onKeyDown(evt);
+ if (evt.isCanceled())
+ {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // since this is a shortcut handled by the main window
+ // we set focus to it
+ WindowEx.get().focus();
+ }
+
+ }
+ }
+
+ private void helpNavigated(Document doc)
+ {
+ NodeList<Element> elements = doc.getElementsByTagName("a") ;
+ for (int i = 0; i < elements.getLength(); i++)
+ {
+ ElementEx a = (ElementEx) elements.getItem(i) ;
+ String href = a.getAttribute("href", 2) ;
+ if (href == null)
+ continue ;
+
+ if (href.contains(":") || href.endsWith(".pdf"))
+ {
+ // external links
+ AnchorElement aElement = a.cast();
+ aElement.setTarget("_blank") ;
+ }
+ else
+ {
+ // Internal links need to be handled in JavaScript so that
+ // they can participate in virtual session history. This
+ // won't have any effect for right-click > Show in New Window
+ // but that's a good thing.
+ a.setAttribute("onclick",
+ "window.parent.helpNavigate(this.href);return false") ;
+ }
+ }
+
+ String effectiveTitle = getDocTitle(doc);
+ title_.setText(effectiveTitle) ;
+ this.fireEvent(new HelpNavigateEvent(doc.getURL(), effectiveTitle)) ;
+ }
+
+ private String getDocTitle(Document doc)
+ {
+ String docUrl = StringUtil.notNull(doc.getURL());
+ String docTitle = doc.getTitle();
+
+ String previewPrefix = new String("/help/preview?file=");
+ int previewLoc = docUrl.indexOf(previewPrefix);
+ if (previewLoc != -1)
+ {
+ String file = docUrl.substring(previewLoc + previewPrefix.length());
+ file = URL.decodeQueryString(file);
+ FileSystemItem fsi = FileSystemItem.createFile(file);
+ docTitle = fsi.getName();
+ }
+ else if (StringUtil.isNullOrEmpty(docTitle))
+ {
+ String url = new String(docUrl);
+ url = url.split("\\?")[0];
+ url = url.split("#")[0];
+ String[] chunks = url.split("/");
+ docTitle = chunks[chunks.length - 1];
+ }
+
+ return docTitle;
+ }
+
+ private void unload()
+ {
+ title_.setText("") ;
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ toolbar.addLeftWidget(commands_.helpBack().createToolbarButton());
+ toolbar.addLeftWidget(commands_.helpForward().createToolbarButton());
+ toolbar.addLeftWidget(commands_.helpHome().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.printHelp().createToolbarButton());
+ toolbar.addLeftWidget(commands_.helpPopout().createToolbarButton());
+
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.refreshHelp().createToolbarButton());
+
+ toolbar.addRightWidget(searchProvider_.get().getSearchWidget());
+
+ return toolbar;
+ }
+
+ @Override
+ protected SecondaryToolbar createSecondaryToolbar()
+ {
+ SecondaryToolbar toolbar = new SecondaryToolbar() ;
+ toolbar.addLeftPopupMenu(title_ = new Label(), history_.getMenu());
+
+ if (isFindSupported())
+ {
+ final SmallButton btnNext = new SmallButton(">", true);
+ btnNext.setTitle("Find next (Enter)");
+ btnNext.setVisible(false);
+ btnNext.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ findNext();
+ }
+ });
+
+ final SmallButton btnPrev = new SmallButton("<", true);
+ btnPrev.setTitle("Find previous");
+ btnPrev.setVisible(false);
+ btnPrev.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ findPrev();
+ }
+ });
+
+
+ findTextBox_ = new FindTextBox("Find in Topic");
+ findTextBox_.setOverrideWidth(90);
+ toolbar.addLeftWidget(findTextBox_);
+ findTextBox_.addKeyUpHandler(new KeyUpHandler() {
+
+ @Override
+ public void onKeyUp(KeyUpEvent event)
+ {
+ WindowEx contentWindow = getContentWindow();
+ if (contentWindow != null)
+ {
+ // escape or tab means exit find mode and put focus
+ // into the main content window
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE ||
+ event.getNativeKeyCode() == KeyCodes.KEY_TAB)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
+ clearTerm();
+ contentWindow.focus();
+ }
+ else
+ {
+ // prevent two enter keys in rapid succession from
+ // minimizing or maximizing the help pane
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ // check for term
+ String term = findTextBox_.getValue().trim();
+
+ // if there is a term then search for it
+ if (term.length() > 0)
+ {
+ // make buttons visible
+ setButtonVisibility(true);
+
+ // perform the find (check for incremental)
+ if (isIncrementalFindSupported())
+ {
+ boolean incremental =
+ !event.isAnyModifierKeyDown() &&
+ (event.getNativeKeyCode() != KeyCodes.KEY_ENTER);
+
+ performFind(term, true, incremental);
+ }
+ else
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ performFind(term, true, false);
+ }
+ }
+
+ // no term means clear term and remove selection
+ else
+ {
+ if (isIncrementalFindSupported())
+ {
+ clearTerm();
+ contentWindow.removeSelection();
+ }
+ }
+ }
+ }
+ }
+
+ private void clearTerm()
+ {
+ findTextBox_.setValue("");
+ setButtonVisibility(false);
+ }
+
+ private void setButtonVisibility(final boolean visible)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ btnNext.setVisible(visible);
+ btnPrev.setVisible(visible);
+ }
+ });
+ }
+ });
+
+ findTextBox_.addKeyDownHandler(new KeyDownHandler() {
+
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // we handle these directly so prevent the browser
+ // from handling them
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE ||
+ event.getNativeKeyCode() == KeyCodes.KEY_TAB ||
+ event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ });
+
+ if (isIncrementalFindSupported())
+ {
+ btnPrev.getElement().getStyle().setMarginRight(3, Unit.PX);
+ toolbar.addLeftWidget(btnPrev);
+ toolbar.addLeftWidget(btnNext);
+ }
+
+ }
+
+ return toolbar ;
+ }
+
+ private String getTerm()
+ {
+ return findTextBox_.getValue().trim();
+ }
+
+ private void findNext()
+ {
+ String term = getTerm();
+ if (term.length() > 0)
+ performFind(term, true, false);
+ }
+
+ private void findPrev()
+ {
+ String term = getTerm();
+ if (term.length() > 0)
+ performFind(term, false, false);
+ }
+
+ private void performFind(String term,
+ boolean forwards,
+ boolean incremental)
+ {
+ WindowEx contentWindow = getContentWindow();
+ if (contentWindow == null)
+ return;
+
+ // if this is an incremental search then reset the selection first
+ if (incremental)
+ contentWindow.removeSelection();
+
+ contentWindow.find(term, false, !forwards, true, false);
+ }
+
+ private boolean isFindSupported()
+ {
+ return BrowseCap.INSTANCE.hasWindowFind();
+ }
+
+ // Firefox changes focus during our typeahead search (it must take
+ // focus when you set the selection into the iframe) which breaks
+ // typeahead entirely. rather than code around this we simply
+ // disable it for Firefox
+ private boolean isIncrementalFindSupported()
+ {
+ return isFindSupported() && !BrowseCap.isFirefox();
+ }
+
+ public String getUrl()
+ {
+ if (getIFrameEx() != null)
+ return getIFrameEx().getContentWindow().getLocationHref() ;
+ else
+ return null;
+ }
+
+ public String getDocTitle()
+ {
+ return getIFrameEx().getContentDocument().getTitle() ;
+ }
+
+ public void showHelp(String url)
+ {
+ ensureWidget();
+ bringToFront();
+ navStack_.navigate(url) ;
+ setLocation(url);
+ navigated_ = true;
+ }
+
+ private void setLocation(final String url)
+ {
+ // allow subsequent calls to setLocation to override any previous
+ // call (necessary so two consecutive calls like we get during
+ // some startup scenarios don't result in the first url displaying
+ // rather than the second)
+ targetUrl_ = url;
+
+ RepeatingCommand navigateCommand = new RepeatingCommand() {
+ @Override
+ public boolean execute()
+ {
+ if (getIFrameEx() != null &&
+ getIFrameEx().getContentWindow() != null)
+ {
+ if (targetUrl_.equals(getUrl()))
+ {
+ getIFrameEx().getContentWindow().reload();
+ }
+ else
+ {
+ getIFrameEx().getContentWindow().replaceLocationHref(targetUrl_);
+ frame_.setUrl(targetUrl_);
+ }
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ };
+
+ if (navigateCommand.execute())
+ Scheduler.get().scheduleFixedDelay(navigateCommand, 100);
+ }
+
+ public void refresh()
+ {
+ String url = getUrl();
+ if (url != null)
+ setLocation(url);
+ }
+
+ private WindowEx getContentWindow()
+ {
+ return getIFrameEx() != null ? getIFrameEx().getContentWindow() : null ;
+ }
+
+ public void back()
+ {
+ String backUrl = navStack_.back() ;
+ if (backUrl != null)
+ setLocation(backUrl) ;
+ }
+
+ public void forward()
+ {
+ String fwdUrl = navStack_.forward() ;
+ if (fwdUrl != null)
+ setLocation(fwdUrl) ;
+ }
+
+ public void print()
+ {
+ getContentWindow().focus() ;
+ getContentWindow().print() ;
+ }
+
+ public void popout()
+ {
+ String href = getContentWindow().getLocationHref() ;
+ globalDisplay_.openWindow(href);
+ }
+
+ @Override
+ public void focus()
+ {
+ WindowEx contentWindow = getContentWindow();
+ if (contentWindow != null)
+ contentWindow.focus();
+ }
+
+ public HandlerRegistration addHelpNavigateHandler(HelpNavigateHandler handler)
+ {
+ return addHandler(handler, HelpNavigateEvent.TYPE) ;
+ }
+
+
+ public LinkMenu getHistory()
+ {
+ return history_ ;
+ }
+
+ public boolean navigated()
+ {
+ return navigated_;
+ }
+
+ private IFrameElementEx getIFrameEx()
+ {
+ return frame_.getElement().cast();
+ }
+
+ private void findInTopic(String term, CanFocus findInputSource)
+ {
+ // get content window
+ WindowEx contentWindow = getContentWindow();
+ if (contentWindow == null)
+ return;
+
+ if (!contentWindow.find(term, false, false, true, false))
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "Find in Topic",
+ "No occurences found",
+ findInputSource);
+ }
+ }
+
+
+ private final VirtualHistory navStack_ = new VirtualHistory() ;
+ private final ToolbarLinkMenu history_ ;
+
+ private Label title_ ;
+ private RStudioFrame frame_ ;
+ private FindTextBox findTextBox_;
+ private final Provider<HelpSearch> searchProvider_ ;
+ private GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+ private boolean navigated_;
+ private boolean initialized_;
+ private String targetUrl_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpTab.java
new file mode 100644
index 0000000..682d3ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/HelpTab.java
@@ -0,0 +1,51 @@
+/*
+ * HelpTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.help.events.ActivateHelpEvent;
+import org.rstudio.studio.client.workbench.views.help.events.ActivateHelpHandler;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpHandler;
+
+public class HelpTab extends DelayLoadWorkbenchTab<Help>
+{
+ public abstract static class Shim extends DelayLoadTabShim<Help, HelpTab>
+ implements ShowHelpHandler,
+ ActivateHelpHandler
+ {
+ @Handler public abstract void onHelpHome();
+ }
+
+ public interface Binder extends CommandBinder<Commands, HelpTab.Shim> {}
+
+ @Inject
+ public HelpTab(Shim shim,
+ Binder binder,
+ Commands commands,
+ EventBus events)
+ {
+ super("Help", shim);
+ binder.bind(commands, shim);
+ events.addHandler(ShowHelpEvent.TYPE, shim);
+ events.addHandler(ActivateHelpEvent.TYPE, shim);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/ToolbarLinkMenu.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/ToolbarLinkMenu.java
new file mode 100644
index 0000000..a8da676
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/ToolbarLinkMenu.java
@@ -0,0 +1,179 @@
+/*
+ * ToolbarLinkMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help;
+
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.workbench.views.help.Help.LinkMenu;
+import org.rstudio.studio.client.workbench.views.help.model.Link;
+
+import java.util.ArrayList;
+
+public class ToolbarLinkMenu implements LinkMenu
+{
+ public ToolbarLinkMenu(int maxLinks,
+ boolean addFromTop,
+ MenuItem[] pre,
+ MenuItem[] post)
+ {
+ maxLinks_ = maxLinks;
+ top_ = addFromTop;
+ pre_ = pre != null ? pre : new MenuItem[0] ;
+ post_ = post != null ? post : new MenuItem[0] ;
+ menu_ = new ToolbarPopupMenu() ;
+ clearLinks() ;
+ }
+
+ public ToolbarPopupMenu getMenu()
+ {
+ return menu_ ;
+ }
+
+ public void addLink(Link link)
+ {
+ LinkMenuItem menuItem = new LinkMenuItem(link, this) ;
+ int beforeIndex ;
+ if (top_)
+ {
+ beforeIndex = pre_.length == 0 ? 0 : pre_.length + 1 ;
+ }
+ else
+ {
+ beforeIndex = menu_.getItemCount() ;
+ if (pre_.length > 0)
+ beforeIndex++ ; // initial separator isn't counted in getItemCount()
+ if (post_.length > 0)
+ beforeIndex -= post_.length + 1 ;
+
+ // some weird race condition causes beforeIndex to go negative
+ beforeIndex = Math.max(0, beforeIndex) ;
+ }
+
+ try
+ {
+ menu_.insertItem(menuItem, beforeIndex) ;
+ }
+ catch (RuntimeException e)
+ {
+ Debug.log("beforeIndex: " + beforeIndex + ", length: " + menu_.getItemCount()) ;
+ throw e ;
+ }
+
+ links_.add(top_ ? 0 : links_.size(), link) ;
+
+ while (links_.size() > maxLinks_)
+ removeLink(links_.get(top_ ? links_.size() - 1 : 0));
+ }
+
+ public void removeLink(Link link)
+ {
+ menu_.removeItem(new LinkMenuItem(link, this)) ;
+ links_.remove(link) ;
+ }
+
+ public boolean containsLink(Link link)
+ {
+ return menu_.containsItem(new LinkMenuItem(link, this)) ;
+ }
+
+ public void clearLinks()
+ {
+ menu_.clearItems() ;
+ for (MenuItem mi : pre_)
+ menu_.addItem(mi) ;
+ if (pre_.length > 0)
+ menu_.addSeparator() ;
+ if (post_.length > 0)
+ menu_.addSeparator() ;
+ for (MenuItem mi : post_)
+ menu_.addItem(mi) ;
+
+ links_.clear() ;
+ }
+
+ public ArrayList<Link> getLinks()
+ {
+ return new ArrayList<Link>(links_) ;
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<String> handler)
+ {
+ return handlers_.addHandler(SelectionEvent.getType(), handler) ;
+ }
+
+ private class LinkMenuItem extends MenuItem
+ {
+ public LinkMenuItem(final Link link,
+ final ToolbarLinkMenu thiz)
+ {
+ super(link.getTitle(), new Command() {
+ public void execute()
+ {
+ SelectionEvent.fire(thiz, link.getUrl()) ;
+ }
+ }) ;
+
+ link_ = link ;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return link_.hashCode() ;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ LinkMenuItem other = (LinkMenuItem) obj ;
+ if (link_ == null)
+ {
+ if (other.link_ != null)
+ return false ;
+ } else if (!link_.equals(other.link_))
+ return false ;
+ return true ;
+ }
+
+ private final Link link_ ;
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event) ;
+ }
+
+ private final HandlerManager handlers_ = new HandlerManager(null);
+ private final ToolbarPopupMenu menu_ ;
+ private final MenuItem[] pre_ ;
+ private final MenuItem[] post_ ;
+ private final ArrayList<Link> links_ = new ArrayList<Link>() ;
+ private final int maxLinks_;
+ private boolean top_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpEvent.java
new file mode 100644
index 0000000..17fc74e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpEvent.java
@@ -0,0 +1,39 @@
+/*
+ * ActivateHelpEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ActivateHelpEvent extends GwtEvent<ActivateHelpHandler>
+{
+ public static final GwtEvent.Type<ActivateHelpHandler> TYPE =
+ new GwtEvent.Type<ActivateHelpHandler>();
+
+ public ActivateHelpEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(ActivateHelpHandler handler)
+ {
+ handler.onActivateHelp(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ActivateHelpHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpHandler.java
new file mode 100644
index 0000000..e26ae4a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ActivateHelpHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ActivateHelpHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ActivateHelpHandler extends EventHandler
+{
+ void onActivateHelp(ActivateHelpEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HasHelpNavigateHandlers.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HasHelpNavigateHandlers.java
new file mode 100644
index 0000000..11d56de
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HasHelpNavigateHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasHelpNavigateHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+
+public interface HasHelpNavigateHandlers extends HasHandlers
+{
+ HandlerRegistration addHelpNavigateHandler(HelpNavigateHandler handler) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateEvent.java
new file mode 100644
index 0000000..c3b9aea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateEvent.java
@@ -0,0 +1,54 @@
+/*
+ * HelpNavigateEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class HelpNavigateEvent extends GwtEvent<HelpNavigateHandler>
+{
+ public static final GwtEvent.Type<HelpNavigateHandler> TYPE =
+ new GwtEvent.Type<HelpNavigateHandler>();
+
+ public HelpNavigateEvent(String url, String title)
+ {
+ url_ = url ;
+ title_ = title ;
+ }
+
+ public String getUrl()
+ {
+ return url_ ;
+ }
+
+ public String getTitle()
+ {
+ return title_ ;
+ }
+
+ @Override
+ protected void dispatch(HelpNavigateHandler handler)
+ {
+ handler.onNavigate(this);
+ }
+
+ @Override
+ public GwtEvent.Type<HelpNavigateHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String url_ ;
+ private final String title_ ;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateHandler.java
new file mode 100644
index 0000000..20ea4c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/HelpNavigateHandler.java
@@ -0,0 +1,22 @@
+/*
+ * HelpNavigateHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface HelpNavigateHandler extends EventHandler
+{
+ public void onNavigate(HelpNavigateEvent event) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpEvent.java
new file mode 100644
index 0000000..a807325
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpEvent.java
@@ -0,0 +1,47 @@
+/*
+ * ShowHelpEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowHelpEvent extends GwtEvent<ShowHelpHandler>
+{
+ public static final GwtEvent.Type<ShowHelpHandler> TYPE =
+ new GwtEvent.Type<ShowHelpHandler>();
+
+ public ShowHelpEvent(String topicUrl)
+ {
+ topicUrl_ = topicUrl;
+ }
+
+ public String getTopicUrl()
+ {
+ return topicUrl_;
+ }
+
+ @Override
+ protected void dispatch(ShowHelpHandler handler)
+ {
+ handler.onShowHelp(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowHelpHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final String topicUrl_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpHandler.java
new file mode 100644
index 0000000..116f32e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/events/ShowHelpHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowHelpHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowHelpHandler extends EventHandler
+{
+ void onShowHelp(ShowHelpEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpInfo.java
new file mode 100644
index 0000000..614ea75
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpInfo.java
@@ -0,0 +1,205 @@
+/*
+ * HelpInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.model ;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.*;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.DomUtils.NodePredicate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class HelpInfo extends JavaScriptObject
+{
+ protected HelpInfo()
+ {
+
+ }
+
+ public final ParsedInfo parse(String defaultSignature)
+ {
+ HashMap<String, String> values = new HashMap<String, String>() ;
+ HashMap<String, String> args = null ;
+
+ String html = getHTML() ;
+ if (html != null)
+ {
+ DivElement div = Document.get().createDivElement() ;
+ div.setInnerHTML(html) ;
+
+ // disable all links
+ NodeList<Element> anchors = div.getElementsByTagName("a") ;
+ for (int i = 0; i < anchors.getLength(); i++)
+ {
+ Element anchor = anchors.getItem(i) ;
+ Element parent = anchor.getParentElement() ;
+ Node child = anchor.getFirstChild() ;
+ while (child != null)
+ {
+ parent.insertBefore(child, anchor) ;
+ child = child.getNextSibling() ;
+ }
+ }
+
+ // get all h2 and h3 headings
+ NodeList<Element> h2headings = div.getElementsByTagName("h2") ;
+ NodeList<Element> h3headings = div.getElementsByTagName("h3") ;
+ ArrayList<Element> headings = new ArrayList<Element>();
+ for (int i = 0; i<h2headings.getLength(); i++)
+ headings.add(h2headings.getItem(i));
+ for (int i = 0; i<h3headings.getLength(); i++)
+ headings.add(h3headings.getItem(i));
+
+ // iterate through them
+ for (int i = 0; i < headings.size(); i++)
+ {
+ Element heading = headings.get(i) ;
+ String name = heading.getInnerText() ;
+ if (name.equals("Arguments"))
+ {
+ args = parseArguments(heading) ;
+ }
+ StringBuffer value = new StringBuffer() ;
+ Node sibling = heading.getNextSibling() ;
+ while (sibling != null
+ && !sibling.getNodeName().toLowerCase().equals("h2")
+ && !sibling.getNodeName().toLowerCase().equals("h3"))
+ {
+ value.append(DomUtils.getHtml(sibling)) ;
+ sibling = sibling.getNextSibling() ;
+ }
+ values.put(name, value.toString()) ;
+ }
+ }
+
+ String signature = getSignature();
+ if (signature == null)
+ signature = defaultSignature;
+ return new ParsedInfo(getPackageName(), signature, values, args) ;
+ }
+
+ private HashMap<String, String> parseArguments(Element heading)
+ {
+ Element table = (Element) DomUtils.findNode(heading, true, true,
+ new NodePredicate() {
+ public boolean test(Node n)
+ {
+ if (n.getNodeType() != Node.ELEMENT_NODE)
+ return false ;
+
+ Element el = (Element) n ;
+
+ return el.getTagName().toUpperCase().equals("TABLE")
+ && "R argblock".equals(el.getAttribute("summary")) ;
+ }
+ });
+
+ if (table == null)
+ {
+ assert false : "Unexpected help format, no argblock table found" ;
+ return null ;
+ }
+
+ HashMap<String, String> result = new HashMap<String, String>() ;
+
+ TableElement t = (TableElement) table ;
+ NodeList<TableRowElement> rows = t.getRows() ;
+ for (int i = 0; i < rows.getLength(); i++)
+ {
+ TableRowElement row = rows.getItem(i) ;
+ NodeList<TableCellElement> cells = row.getCells() ;
+ TableCellElement argNameCell = cells.getItem(0) ;
+ TableCellElement argValueCell = cells.getItem(1) ;
+
+ String argNameText = argNameCell.getInnerText() ;
+ String argValueHtml = argValueCell.getInnerHTML() ;
+
+ result.put(argNameText, argValueHtml) ;
+ }
+
+ return result ;
+ }
+
+ private final native String getHTML() /*-{
+ return this.html ? this.html[0] : null ;
+ }-*/;
+
+ private final native String getSignature() /*-{
+ return this.signature ? this.signature[0] : null ;
+ }-*/;
+
+ private final native String getPackageName() /*-{
+ return this.pkgname ? this.pkgname[0] : null ;
+ }-*/;
+
+ public static class ParsedInfo
+ {
+ private String pkgName ;
+ private String signature ;
+ private HashMap<String, String> values ;
+ private HashMap<String, String> args ;
+
+ public ParsedInfo(String pkgName, String signature, HashMap<String, String> values,
+ HashMap<String, String> args)
+ {
+ super() ;
+ this.pkgName = pkgName ;
+ this.signature = signature ;
+ this.values = values != null ? values : new HashMap<String, String>();
+ this.args = args ;
+ }
+
+ public String getPackageName()
+ {
+ return pkgName ;
+ }
+
+ public String getFunctionSignature()
+ {
+ return signature ;
+ }
+
+ public String getDescription()
+ {
+ return values.get("Description") ;
+ }
+
+ public String getUsage()
+ {
+ return values.get("Usage") ;
+ }
+
+ public String getDetails()
+ {
+ return values.get("Details") ;
+ }
+
+ /**
+ * Returns null if no args section was present in the docs.
+ */
+ public HashMap<String, String> getArgs()
+ {
+ return args ;
+ }
+
+ public boolean hasInfo()
+ {
+ return signature != null
+ || args != null
+ || getDescription() != null;
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpServerOperations.java
new file mode 100644
index 0000000..cdbab8d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/HelpServerOperations.java
@@ -0,0 +1,36 @@
+/*
+ * HelpServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.model;
+
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface HelpServerOperations
+{
+ void suggestTopics(String prefix,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void getHelp(String topic,
+ String packageName,
+ int options,
+ ServerRequestCallback<HelpInfo> requestCallback);
+
+ String getApplicationURL(String topicURI);
+
+ void showHelpTopic(String topic, String pkgName) ;
+
+ void search(String query,
+ ServerRequestCallback<JsArrayString> requestCallback) ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/Link.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/Link.java
new file mode 100644
index 0000000..4249c9b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/Link.java
@@ -0,0 +1,106 @@
+/*
+ * Link.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.model;
+
+import com.google.gwt.dom.client.Document;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+
+public class Link
+{
+ public Link(String url, String title)
+ {
+ this(url, title, false);
+ }
+
+ public Link(String url, String title, boolean preserveHost)
+ {
+ if (!preserveHost)
+ url = removeHost(url);
+ url_ = url ;
+ title_ = title ;
+ id_ = normalizeUrl(url_) ;
+ }
+
+ /**
+ * If the URL has the same scheme, hostname, and port as the current page,
+ * then drop them from the URL.
+ *
+ * For example,
+ * http://rstudio.com/help/base/ls
+ * becomes
+ * /help/base/ls
+ */
+ private String removeHost(String url)
+ {
+ String pageUrl = Document.get().getURL();
+ Pattern p = Pattern.create("^http(s?)://[^/]+");
+ Match m = p.match(pageUrl, 0);
+ if (m == null)
+ {
+ assert false : "Couldn't parse page URL: " + url;
+ return url;
+ }
+ String prefix = m.getValue();
+ if (!url.startsWith(prefix))
+ return url;
+ else
+ return url.substring(prefix.length());
+ }
+
+ public String getUrl()
+ {
+ return url_ ;
+ }
+
+ public String getTitle()
+ {
+ return title_ ;
+ }
+
+ private static String normalizeUrl(String url)
+ {
+ return url.indexOf('#') >= 0 ? url.substring(0, url.indexOf('#')) : url ;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (id_ == null) ? 0 : id_.hashCode() ;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true ;
+ if (obj == null)
+ return false ;
+ if (getClass() != obj.getClass())
+ return false ;
+ Link other = (Link) obj ;
+ if (id_ == null)
+ {
+ if (other.id_ != null)
+ return false ;
+ } else if (!id_.equals(other.id_))
+ return false ;
+ return true ;
+ }
+
+ private final String url_ ;
+ private final String title_ ;
+ private final String id_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/VirtualHistory.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/VirtualHistory.java
new file mode 100644
index 0000000..5ed919d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/model/VirtualHistory.java
@@ -0,0 +1,72 @@
+/*
+ * VirtualHistory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.model;
+
+import java.util.ArrayList;
+
+public class VirtualHistory
+{
+ public void navigate(String url)
+ {
+ // truncate the stack to the current pos
+ while (stack_.size() > pos_ + 1)
+ stack_.remove(stack_.size() - 1) ;
+
+ stack_.add(url) ;
+ pos_ = stack_.size() - 1;
+
+ dump() ;
+ }
+
+ public String back()
+ {
+ if (pos_ <= 0)
+ return null ;
+ pos_-- ;
+
+ dump() ;
+
+ return stack_.get(pos_) ;
+ }
+
+ public String forward()
+ {
+ if (pos_ >= stack_.size() - 1)
+ return null ;
+ pos_++ ;
+
+ dump() ;
+
+ return stack_.get(pos_) ;
+ }
+
+ private void dump()
+ {
+ /*
+ StringBuffer out = new StringBuffer() ;
+ for (int i = stack_.size() - 1; i >= 0; i--)
+ {
+ if (i == pos_)
+ out.append('*') ;
+ out.append(stack_.get(i)) ;
+ out.append('\n') ;
+ }
+ Debug.log(out.toString()) ;
+ */
+ }
+
+ private ArrayList<String> stack_ = new ArrayList<String>() ;
+ private int pos_ = -1 ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearch.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearch.java
new file mode 100644
index 0000000..bc5d23b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearch.java
@@ -0,0 +1,86 @@
+/*
+ * HelpSearch.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.search;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.SearchDisplay;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+
+public class HelpSearch
+{
+ public interface Display
+ {
+ SearchDisplay getSearchDisplay();
+ }
+
+ @Inject
+ public HelpSearch(Display display,
+ HelpServerOperations server,
+ EventBus eventBus)
+ {
+ display_ = display ;
+ eventBus_ = eventBus ;
+ server_ = server ;
+
+ display_.getSearchDisplay().addSelectionHandler(
+ new SelectionHandler<Suggestion>() {
+
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event)
+ {
+ fireShowHelpEvent(event.getSelectedItem().getDisplayString());
+ }
+ });
+
+ display_.getSearchDisplay().addSelectionCommitHandler(
+ new SelectionCommitHandler<String>() {
+ public void onSelectionCommit(SelectionCommitEvent<String> event)
+ {
+ fireShowHelpEvent(event.getSelectedItem());
+ }
+ }) ;
+ }
+
+ public Widget getSearchWidget()
+ {
+ return (Widget) display_.getSearchDisplay();
+ }
+
+ private void fireShowHelpEvent(String topic)
+ {
+ server_.search(topic, new SimpleRequestCallback<JsArrayString>() {
+ public void onResponseReceived(JsArrayString url)
+ {
+ if (url != null && url.length() > 0)
+ eventBus_.fireEvent(new ShowHelpEvent(url.get(0))) ;
+ }
+ }) ;
+ }
+
+ private final HelpServerOperations server_ ;
+ private final EventBus eventBus_ ;
+ private final Display display_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchOracle.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchOracle.java
new file mode 100644
index 0000000..154d9cd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchOracle.java
@@ -0,0 +1,82 @@
+/*
+ * HelpSearchOracle.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.search;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.inject.Inject;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.help.model.HelpServerOperations;
+
+import java.util.ArrayList;
+
+public class HelpSearchOracle extends SuggestOracle
+{
+ @Inject
+ public HelpSearchOracle(HelpServerOperations server)
+ {
+ server_ = server ;
+ }
+
+ @Override
+ public void requestSuggestions(final Request request,
+ final Callback callback)
+ {
+ String query = request.getQuery() ;
+ server_.suggestTopics(query,
+ new ServerRequestCallback<JsArrayString>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ }
+
+ @Override
+ public void onResponseReceived(JsArrayString suggestions)
+ {
+ int maxCount = Math.min(suggestions.length(), request.getLimit());
+
+ ArrayList<SearchSuggestion> results =
+ new ArrayList<SearchSuggestion>() ;
+ for (int i = 0; i< maxCount; i++)
+ results.add(new SearchSuggestion(suggestions.get(i))) ;
+
+ callback.onSuggestionsReady(request, new Response(results)) ;
+ }
+ }); ;
+ }
+
+ private class SearchSuggestion implements Suggestion
+ {
+ public SearchSuggestion(String value)
+ {
+ value_ = value ;
+ }
+
+ public String getDisplayString()
+ {
+ return value_ ;
+ }
+
+ public String getReplacementString()
+ {
+ return value_ ;
+ }
+
+ private final String value_ ;
+ }
+
+ private final HelpServerOperations server_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchWidget.java
new file mode 100644
index 0000000..546e0a5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/help/search/HelpSearchWidget.java
@@ -0,0 +1,38 @@
+/*
+ * HelpSearchWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.help.search;
+
+
+import org.rstudio.core.client.widget.SearchDisplay;
+import org.rstudio.core.client.widget.SearchWidget;
+
+import com.google.inject.Inject;
+
+
+public class HelpSearchWidget extends SearchWidget
+ implements HelpSearch.Display
+{
+ @Inject
+ public HelpSearchWidget(HelpSearchOracle oracle)
+ {
+ super(oracle);
+ }
+
+ @Override
+ public SearchDisplay getSearchDisplay()
+ {
+ return this;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HasHistory.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HasHistory.java
new file mode 100644
index 0000000..35790e6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HasHistory.java
@@ -0,0 +1,29 @@
+/*
+ * HasHistory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.HasAllKeyHandlers;
+
+import java.util.ArrayList;
+
+public interface HasHistory
+{
+ ArrayList<String> getSelectedValues();
+ ArrayList<Long> getSelectedCommandIndexes();
+ HasAllKeyHandlers getKeyTarget();
+
+ Element getFocusTarget();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/History.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/History.java
new file mode 100644
index 0000000..99106c4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/History.java
@@ -0,0 +1,683 @@
+/*
+ * History.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history;
+
+import com.google.gwt.core.client.JsArrayNumber;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.ConsoleDispatcher;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.StringStateValue;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleResetHistoryEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleResetHistoryHandler;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.history.History.Display.Mode;
+import org.rstudio.studio.client.workbench.views.history.events.FetchCommandsEvent;
+import org.rstudio.studio.client.workbench.views.history.events.FetchCommandsHandler;
+import org.rstudio.studio.client.workbench.views.history.events.HistoryEntriesAddedEvent;
+import org.rstudio.studio.client.workbench.views.history.events.HistoryEntriesAddedHandler;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryServerOperations;
+import org.rstudio.studio.client.workbench.views.source.events.InsertSourceEvent;
+
+import java.util.ArrayList;
+
+public class History extends BasePresenter implements SelectionCommitHandler<Void>,
+ FetchCommandsHandler
+{
+ public interface SearchBoxDisplay extends HasValueChangeHandlers<String>
+ {
+ String getText();
+ public void setText(String text);
+ }
+
+ public interface Display extends WorkbenchView,
+ HasSelectionCommitHandlers<Void>
+ {
+ public enum Mode
+ {
+ Recent(0),
+ SearchResults(1),
+ CommandContext(2);
+
+ Mode(int value)
+ {
+ value_ = value;
+ }
+
+ public int getValue()
+ {
+ return value_;
+ }
+
+ private final int value_;
+ }
+
+ void setRecentCommands(ArrayList<HistoryEntry> commands,
+ boolean scrollToBottom);
+ void addRecentCommands(ArrayList<HistoryEntry> entries, boolean top);
+
+ int getRecentCommandsScrollPosition();
+ void setRecentCommandsScrollPosition(int scrollPosition);
+
+ ArrayList<Integer> getRecentCommandsSelectedRowIndexes();
+ int getRecentCommandsRowsDisplayed();
+
+ void truncateRecentCommands(int maxCommands);
+
+ ArrayList<String> getSelectedCommands();
+ ArrayList<Long> getSelectedCommandIndexes();
+ HandlerRegistration addFetchCommandsHandler(FetchCommandsHandler handler);
+ void setMoreCommands(long moreCommands);
+ SearchBoxDisplay getSearchBox();
+ Mode getMode();
+ void scrollToBottom();
+ void focusSearch();
+
+ void dismissSearchResults();
+ void showSearchResults(String query,
+ ArrayList<HistoryEntry> entries);
+ void showContext(String command,
+ ArrayList<HistoryEntry> entries,
+ long highlightOffset,
+ long highlightLength);
+ void dismissContext();
+
+ HasHistory getRecentCommandsWidget();
+ HasHistory getSearchResultsWidget();
+ HasHistory getCommandContextWidget();
+
+ boolean isCommandTableFocused();
+
+
+ }
+
+ public interface Binder extends CommandBinder<Commands, History>
+ {}
+
+
+ class SearchCommand extends TimeBufferedCommand implements ValueChangeHandler<String>
+ {
+ SearchCommand(Session session)
+ {
+ super(200);
+ }
+
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ final String query = searchQuery_;
+ if (searchQuery_ != null && searchQuery_.length() > 0)
+ {
+ server_.searchHistoryArchive(
+ searchQuery_, COMMAND_CHUNK_SIZE,
+ new SimpleRequestCallback<RpcObjectList<HistoryEntry>>()
+ {
+ @Override
+ public void onResponseReceived(
+ RpcObjectList<HistoryEntry> response)
+ {
+ if (!query.equals(searchQuery_))
+ return;
+
+ ArrayList<HistoryEntry> entries = toList(response);
+ view_.showSearchResults(query, entries);
+ }
+ });
+ }
+ }
+
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ String query = event.getValue();
+ searchQuery_ = query;
+ if (searchQuery_.equals(""))
+ {
+ view_.dismissSearchResults();
+ }
+ else
+ {
+ nudge();
+ }
+ }
+
+ public void dismissResults()
+ {
+ view_.dismissSearchResults();
+ searchQuery_ = null;
+ }
+
+ private String searchQuery_;
+ }
+
+ @Inject
+ public History(final Display view,
+ HistoryServerOperations server,
+ final GlobalDisplay globalDisplay,
+ ConsoleDispatcher consoleDispatcher,
+ EventBus events,
+ final Session session,
+ Commands commands,
+ Binder binder)
+ {
+ super(view);
+ view_ = view;
+ events_ = events;
+ globalDisplay_ = globalDisplay;
+ consoleDispatcher_ = consoleDispatcher;
+ searchCommand_ = new SearchCommand(session);
+ session_ = session;
+
+ binder.bind(commands, this);
+
+ view_.addSelectionCommitHandler(this);
+ view_.addFetchCommandsHandler(this);
+
+ server_ = server;
+ events_.addHandler(ConsoleResetHistoryEvent.TYPE, new ConsoleResetHistoryHandler()
+ {
+ @Override
+ public void onConsoleResetHistory(ConsoleResetHistoryEvent event)
+ {
+ view_.bringToFront();
+
+ // convert to HistoryEntry
+ ArrayList<HistoryEntry> commands = toRecentCommandsList(
+ event.getHistory());
+
+ // determine entries to add
+ int preservedScrollPos = -1;
+ int startIndex = Math.max(0, commands.size() - COMMAND_CHUNK_SIZE);
+
+ // if we are updating an existing context then preserve the
+ // history position and the scroll position
+ if (event.getPreserveUIContext())
+ {
+ preservedScrollPos = view_.getRecentCommandsScrollPosition();
+
+ if (historyPosition_ < commands.size())
+ startIndex = new Long(historyPosition_).intValue();
+ }
+
+ // set recent commands
+ ArrayList<HistoryEntry> subList = new ArrayList<HistoryEntry>();
+ subList.addAll(commands.subList(startIndex, commands.size()));
+ boolean scrollToBottom = preservedScrollPos == -1;
+ setRecentCommands(subList, scrollToBottom);
+
+ // restore scroll position if requested
+ if (preservedScrollPos != -1)
+ {
+ final int scrollPos = preservedScrollPos;
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ view_.setRecentCommandsScrollPosition(scrollPos);
+ }
+ });
+ }
+ }
+ });
+
+ events_.addHandler(HistoryEntriesAddedEvent.TYPE, new HistoryEntriesAddedHandler()
+ {
+ public void onHistoryEntriesAdded(HistoryEntriesAddedEvent event)
+ {
+ view_.addRecentCommands(toList(event.getEntries()), false);
+ view_.truncateRecentCommands(
+ session_.getSessionInfo().getConsoleHistoryCapacity());
+ }
+ });
+
+ view_.getSearchBox().addValueChangeHandler(searchCommand_);
+
+ view_.getRecentCommandsWidget().getKeyTarget().addKeyDownHandler(
+ new KeyHandler(commands.historySendToConsole(),
+ commands.historySendToSource(),
+ null,
+ null));
+ view_.getSearchResultsWidget().getKeyTarget().addKeyDownHandler(
+ new KeyHandler(commands.historySendToConsole(),
+ commands.historySendToSource(),
+ commands.historyDismissResults(),
+ commands.historyShowContext()));
+ view_.getCommandContextWidget().getKeyTarget().addKeyDownHandler(
+ new KeyHandler(commands.historySendToConsole(),
+ commands.historySendToSource(),
+ commands.historyDismissContext(),
+ null));
+
+ new StringStateValue("history", "query", ClientState.TEMPORARY,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(String value)
+ {
+ if (value != null && value.length() != 0)
+ {
+ view_.getSearchBox().setText(value);
+ }
+ }
+
+ @Override
+ protected String getValue()
+ {
+ return view_.getSearchBox().getText();
+ }
+ };
+
+ server_.getRecentHistory(
+ COMMAND_CHUNK_SIZE,
+ new ServerRequestCallback<RpcObjectList<HistoryEntry>>()
+ {
+ @Override
+ public void onResponseReceived(RpcObjectList<HistoryEntry> response)
+ {
+ ArrayList<HistoryEntry> result = toRecentCommandsList(response);
+ setRecentCommands(result, true);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error While Retrieving History",
+ error.getUserMessage());
+ }
+ });
+ }
+
+
+ private void setRecentCommands(ArrayList<HistoryEntry> commands,
+ boolean scrollToBottom)
+ {
+ view_.setRecentCommands(commands, scrollToBottom);
+
+ if (commands.size() > 0)
+ historyPosition_ = commands.get(0).getIndex();
+ else
+ historyPosition_ = 0;
+
+ view_.setMoreCommands(Math.min(historyPosition_, COMMAND_CHUNK_SIZE));
+ }
+
+
+ private class KeyHandler implements KeyDownHandler
+ {
+ private KeyHandler(Command accept,
+ Command shiftAccept,
+ Command left,
+ Command right)
+ {
+ this.accept_ = accept;
+ this.shiftAccept_ = shiftAccept;
+ this.left_ = left;
+ this.right_ = right;
+ }
+
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (!view_.isCommandTableFocused())
+ return;
+
+ boolean handled = false;
+
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ if (event.isShiftKeyDown())
+ {
+ if (shiftAccept_ != null)
+ shiftAccept_.execute();
+ handled = true;
+ }
+ else if (!event.isAnyModifierKeyDown())
+ {
+ if (accept_ != null)
+ accept_.execute();
+ handled = true;
+ }
+ }
+ else if (!event.isAnyModifierKeyDown())
+ {
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_ESCAPE:
+ case KeyCodes.KEY_LEFT:
+ if (left_ != null)
+ left_.execute();
+ handled = true;
+ break;
+ case KeyCodes.KEY_RIGHT:
+ if (right_ != null)
+ right_.execute();
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ private final Command accept_;
+ private final Command shiftAccept_;
+ private final Command left_;
+ private final Command right_;
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+ if (view_.getMode() == Mode.Recent)
+ {
+ view_.scrollToBottom();
+ view_.focusSearch();
+ }
+
+ }
+
+ private String getSelectedCommands()
+ {
+ ArrayList<String> commands = view_.getSelectedCommands();
+ StringBuilder cmd = new StringBuilder();
+ for (String command : commands)
+ {
+ cmd.append(command);
+ cmd.append("\n");
+ }
+ String commandString = cmd.toString();
+ return commandString;
+ }
+
+ @Handler
+ void onHistorySendToConsole()
+ {
+ String commandString = getSelectedCommands();
+ commandString = StringUtil.chomp(commandString);
+ if (commandString.length() > 0 )
+ events_.fireEvent(new SendToConsoleEvent(commandString, false));
+ }
+
+ @Handler
+ void onHistorySendToSource()
+ {
+ String commandString = getSelectedCommands();
+ if (commandString.length() > 0)
+ events_.fireEvent(new InsertSourceEvent(commandString, true));
+ }
+
+ void onSearchHistory()
+ {
+ globalDisplay_.showErrorMessage("Message", "onSearchHistory");
+ }
+
+ void onLoadHistory()
+ {
+ view_.bringToFront();
+
+ consoleDispatcher_.chooseFileThenExecuteCommand("Load History",
+ "loadhistory");
+ }
+
+ void onSaveHistory()
+ {
+ view_.bringToFront();
+
+ consoleDispatcher_.saveFileAsThenExecuteCommand("Save History As",
+ ".Rhistory",
+ false,
+ "savehistory");
+ }
+
+ @Handler
+ void onHistoryRemoveEntries()
+ {
+ // get selected indexes (bail if there is no selection)
+ final ArrayList<Integer> selectedRowIndexes =
+ view_.getRecentCommandsSelectedRowIndexes();
+ if (selectedRowIndexes.size() < 1)
+ {
+ globalDisplay_.showErrorMessage(
+ "Error",
+ "No history entries currently selected.");
+ return;
+ }
+
+ // bring view to front
+ view_.bringToFront();
+
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_QUESTION,
+ "Confirm Remove Entries",
+ "Are you sure you want to remove the selected entries from " +
+ "the history?",
+
+ new ProgressOperation() {
+ public void execute(final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Removing items...");
+
+ // for each selected row index we need to calculate
+ // the offset from the bottom
+ int rowCount = view_.getRecentCommandsRowsDisplayed();
+ JsArrayNumber bottomIndexes = (JsArrayNumber)
+ JsArrayNumber.createArray();
+ for (int i = 0; i<selectedRowIndexes.size(); i++)
+ bottomIndexes.push(rowCount - selectedRowIndexes.get(i) - 1);
+
+ server_.removeHistoryItems(
+ bottomIndexes,
+ new VoidServerRequestCallback(indicator));
+ }
+ },
+
+ true
+ );
+
+
+ }
+
+ @Handler
+ void onClearHistory()
+ {
+ view_.bringToFront();
+
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Confirm Clear History",
+ "Are you sure you want to clear all history entries?",
+
+ new ProgressOperation() {
+ public void execute(final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Clearing history...");
+ server_.clearHistory(
+ new VoidServerRequestCallback(indicator));
+ }
+ },
+
+ true
+ );
+ }
+
+ @Handler
+ void onHistoryDismissResults()
+ {
+ searchCommand_.dismissResults();
+ }
+
+ @Handler
+ void onHistoryDismissContext()
+ {
+ view_.dismissContext();
+ }
+
+ @Handler
+ void onHistoryShowContext()
+ {
+ ArrayList<Long> indexes = view_.getSelectedCommandIndexes();
+ if (indexes.size() != 1)
+ return;
+
+ final String command = view_.getSelectedCommands().get(0);
+ final Long min = indexes.get(0);
+ final long max = indexes.get(indexes.size() - 1) + 1;
+ final long start = Math.max(0, min - CONTEXT_LINES);
+ final long end = max + CONTEXT_LINES;
+
+ server_.getHistoryArchiveItems(
+ start,
+ end,
+ new SimpleRequestCallback<RpcObjectList<HistoryEntry>>() {
+ @Override
+ public void onResponseReceived(RpcObjectList<HistoryEntry> response)
+ {
+ ArrayList<HistoryEntry> entries = toList(response);
+ view_.showContext(command,
+ entries,
+ min - start,
+ max - min);
+ }
+ });
+ }
+
+ private ArrayList<HistoryEntry> toList(RpcObjectList<HistoryEntry> response)
+ {
+ ArrayList<HistoryEntry> entries = new ArrayList<HistoryEntry>();
+ for (int i = 0; i < response.length(); i++)
+ entries.add(response.get(i));
+ return entries;
+ }
+
+ private ArrayList<HistoryEntry> toRecentCommandsList(
+ JsArrayString jsCommands)
+ {
+ ArrayList<HistoryEntry> commands = new ArrayList<HistoryEntry>();
+ for (int i=0; i<jsCommands.length(); i++)
+ commands.add(HistoryEntry.create(i, jsCommands.get(i)));
+ return commands;
+ }
+
+ private ArrayList<HistoryEntry> toRecentCommandsList(
+ RpcObjectList<HistoryEntry> response)
+ {
+ ArrayList<HistoryEntry> entries = new ArrayList<HistoryEntry>();
+ for (int i = 0; i < response.length(); i++)
+ entries.add(response.get(i));
+ return entries;
+ }
+
+
+
+ public void onSelectionCommit(SelectionCommitEvent<Void> e)
+ {
+ onHistorySendToConsole();
+ }
+
+ public void onFetchCommands(FetchCommandsEvent event)
+ {
+ if (fetchingMoreCommands_)
+ return;
+
+ if (historyPosition_ == 0)
+ {
+ // This should rarely/never happen
+ return;
+ }
+
+ long startIndex = Math.max(0, historyPosition_ - COMMAND_CHUNK_SIZE);
+ long endIndex = historyPosition_;
+ server_.getHistoryItems(startIndex, endIndex,
+ new SimpleRequestCallback<RpcObjectList<HistoryEntry>>()
+ {
+ @Override
+ public void onResponseReceived(RpcObjectList<HistoryEntry> response)
+ {
+ ArrayList<HistoryEntry> entries =
+ toRecentCommandsList(response);
+ view_.addRecentCommands(entries, true);
+ fetchingMoreCommands_ = false;
+
+ if (response.length() > 0)
+ historyPosition_ = response.get(0).getIndex();
+ else
+ historyPosition_ = 0; // this shouldn't happen
+
+ view_.setMoreCommands(Math.min(historyPosition_,
+ COMMAND_CHUNK_SIZE));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ super.onError(error);
+ fetchingMoreCommands_ = false;
+ }
+ });
+ }
+
+
+
+ // This field indicates how far into the history stream we have reached.
+ // When this value becomes 0, that means there is no more history to go
+ // fetch.
+ private long historyPosition_ = 0;
+
+ private static final int COMMAND_CHUNK_SIZE = 300;
+ private static final int CONTEXT_LINES = 50;
+ private boolean fetchingMoreCommands_ = false;
+ private final Display view_;
+ private final EventBus events_;
+ private final GlobalDisplay globalDisplay_;
+ private final SearchCommand searchCommand_;
+ private HistoryServerOperations server_;
+ private final Session session_;
+ private final ConsoleDispatcher consoleDispatcher_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HistoryTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HistoryTab.java
new file mode 100644
index 0000000..b2eca9d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/HistoryTab.java
@@ -0,0 +1,48 @@
+/*
+ * HistoryTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history;
+
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+
+public class HistoryTab extends DelayLoadWorkbenchTab<History>
+{
+ public interface Binder extends CommandBinder<Commands, HistoryTab.Shim> {}
+
+ public abstract static class Shim
+ extends DelayLoadTabShim<History, HistoryTab>
+ {
+ @Handler
+ public abstract void onSearchHistory();
+
+ @Handler
+ public abstract void onLoadHistory();
+
+ @Handler
+ public abstract void onSaveHistory();
+ }
+
+ @Inject
+ public HistoryTab(Shim shim, Binder binder, Commands commands)
+ {
+ super("History", shim);
+ binder.bind(commands, shim);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsEvent.java
new file mode 100644
index 0000000..9247092
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsEvent.java
@@ -0,0 +1,35 @@
+/*
+ * FetchCommandsEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FetchCommandsEvent extends GwtEvent<FetchCommandsHandler>
+{
+ public static final Type<FetchCommandsHandler> TYPE =
+ new Type<FetchCommandsHandler>();
+
+ @Override
+ public Type<FetchCommandsHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(FetchCommandsHandler handler)
+ {
+ handler.onFetchCommands(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsHandler.java
new file mode 100644
index 0000000..b663405
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/FetchCommandsHandler.java
@@ -0,0 +1,22 @@
+/*
+ * FetchCommandsHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface FetchCommandsHandler extends EventHandler
+{
+ void onFetchCommands(FetchCommandsEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedEvent.java
new file mode 100644
index 0000000..0f4e2dd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedEvent.java
@@ -0,0 +1,49 @@
+/*
+ * HistoryEntriesAddedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+
+public class HistoryEntriesAddedEvent extends GwtEvent<HistoryEntriesAddedHandler>
+{
+ public static final GwtEvent.Type<HistoryEntriesAddedHandler> TYPE =
+ new GwtEvent.Type<HistoryEntriesAddedHandler>();
+
+ public HistoryEntriesAddedEvent(RpcObjectList<HistoryEntry> entries)
+ {
+ entries_ = entries;
+ }
+
+ public RpcObjectList<HistoryEntry> getEntries()
+ {
+ return entries_;
+ }
+
+ @Override
+ protected void dispatch(HistoryEntriesAddedHandler handler)
+ {
+ handler.onHistoryEntriesAdded(this);
+ }
+
+ @Override
+ public GwtEvent.Type<HistoryEntriesAddedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private final RpcObjectList<HistoryEntry> entries_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedHandler.java
new file mode 100644
index 0000000..40a9d73
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/events/HistoryEntriesAddedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * HistoryEntriesAddedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface HistoryEntriesAddedHandler extends EventHandler
+{
+ void onHistoryEntriesAdded(HistoryEntriesAddedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryEntry.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryEntry.java
new file mode 100644
index 0000000..aa4a490
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryEntry.java
@@ -0,0 +1,64 @@
+/*
+ * HistoryEntry.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.Date;
+
+public class HistoryEntry extends JavaScriptObject
+{
+ protected HistoryEntry()
+ {
+ }
+
+ public static final native HistoryEntry create(int index, String command) /*-{
+ var entry = new Object();
+ entry.index = index;
+ entry.timestamp = 0;
+ entry.command = command;
+ return entry;
+ }-*/;
+
+ public final long getIndex()
+ {
+ return new Double(getIndexNative()).longValue();
+ }
+
+ public final Date getTimestamp()
+ {
+ Double lastModified = new Double(getTimestampNative());
+ return new Date(lastModified.longValue());
+ }
+
+ public final native String getCommand() /*-{
+ return this.command;
+ }-*/;
+
+ public final String asString()
+ {
+ return getIndex() + " - " +
+ getTimestamp().toString() + " - " +
+ getCommand();
+ }
+
+ private final native double getIndexNative() /*-{
+ return this.index;
+ }-*/;
+
+ private final native double getTimestampNative() /*-{
+ return this.timestamp;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryServerOperations.java
new file mode 100644
index 0000000..e40129c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/model/HistoryServerOperations.java
@@ -0,0 +1,85 @@
+/*
+ * HistoryServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.model;
+
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import com.google.gwt.core.client.JsArrayNumber;
+
+public interface HistoryServerOperations
+{
+ /*
+ * getRecentHistory -- return all of the available history items (up to max)
+ */
+ void getRecentHistory(
+ long maxItems,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback);
+
+ /*
+ * getHistoryItems -- get a subset of history items
+ */
+ void getHistoryItems(
+ long startIndex, // inclusive
+ long endIndex, // exclusive
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback);
+
+ /*
+ * removeHistoryItems -- indexes of items to remove
+ */
+ void removeHistoryItems(JsArrayNumber itemIndexes,
+ ServerRequestCallback<Void> requestCallback);
+
+
+ /*
+ * clearHistory -- clear the entire history
+ */
+ void clearHistory(ServerRequestCallback<Void> requestCallback);
+
+
+ /*
+ * getHistoryArchiveItems -- return a range of history archive items
+ * (note that startIndex is inclusive and endIndex is exclusive)
+ */
+ void getHistoryArchiveItems(
+ long startIndex, // inclusive
+ long endIndex, // exclusive
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback);
+
+
+ /*
+ * searchHistoryDatabase - search the history archive for the query
+ * (return up to maxEntries). the search is conducted beginning with the
+ * most recent history items and returned in index decsending order i.e. newest
+ * ones first)
+ */
+ void searchHistoryArchive(
+ String query,
+ long maxEntries,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback);
+
+ /*
+ * searchHistoryArchiveByPrefix - search the history for items with the
+ * specified prefix (return up to maxEntries. the search is conducted
+ * beginning with the most recent history items and returned in index
+ * descending order i.e. newest ones first)
+ */
+ void searchHistoryArchiveByPrefix(
+ String prefix,
+ long maxEntries,
+ boolean uniqueOnly,
+ ServerRequestCallback<RpcObjectList<HistoryEntry>> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryEntryItemCodec.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryEntryItemCodec.java
new file mode 100644
index 0000000..735a663
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryEntryItemCodec.java
@@ -0,0 +1,187 @@
+/*
+ * HistoryEntryItemCodec.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.*;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.widget.HeaderBreaksItemCodec;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryPane.Resources;
+
+import java.util.Date;
+
+public class HistoryEntryItemCodec extends HeaderBreaksItemCodec<HistoryEntry, String, Long>
+{
+ public enum TimestampMode { GROUP, ITEM, NONE }
+
+ public HistoryEntryItemCodec(String commandClass,
+ String timestampClass,
+ TimestampMode timestampMode,
+ boolean disclosureButton)
+ {
+ commandClass_ = commandClass;
+ timestampClass_ = timestampClass;
+ timestampMode_ = timestampMode;
+ disclosureButton_ = disclosureButton;
+
+ res_ = GWT.create(HistoryPane.Resources.class);
+ }
+
+ public TableRowElement getRowForItem(HistoryEntry entry)
+ {
+ TableRowElement tr = Document.get().createTRElement();
+ tr.setAttribute("data-entry-id",
+ entry.getIndex() + "");
+ tr.setAttribute("data-timestamp",
+ entry.getTimestamp().getTime() + "");
+
+ TableCellElement td = Document.get().createTDElement();
+ td.setColSpan(2);
+ td.setClassName(commandClass_);
+
+ DivElement div = Document.get().createDivElement();
+ div.setInnerText(addBreaks(entry.getCommand()));
+
+ td.appendChild(div);
+ tr.appendChild(td);
+ TableCellElement tdDiscButton = maybeCreateDisclosureButton(entry);
+ if (tdDiscButton != null)
+ tr.appendChild(tdDiscButton);
+
+ return tr;
+ }
+
+ protected TableCellElement maybeCreateDisclosureButton(HistoryEntry entry)
+ {
+ if (!disclosureButton_)
+ return null;
+
+ TableCellElement td = Document.get().createTDElement();
+ td.setClassName(res_.styles().disclosure());
+ td.setVAlign("middle");
+
+ DivElement div = Document.get().createDivElement();
+ div.setTitle("Show command in original context");
+ div.setClassName(res_.styles().disclosure());
+
+ td.appendChild(div);
+ return td;
+ }
+
+ protected String addBreaks(String val)
+ {
+ // This causes an extra space on Ubuntu desktop--disabling until
+ // we can figure out a different way
+ //return val.replace("(", "(\u200b");
+ return val;
+ }
+
+ protected String stripBreaks(String val)
+ {
+ //return val.replace("\u200b", "");
+ return val;
+ }
+
+ public String getOutputForRow(TableRowElement row)
+ {
+ return stripBreaks(row.getCells().getItem(0).getInnerText());
+ }
+
+ public Long getOutputForRow2(TableRowElement row)
+ {
+ return Long.parseLong(row.getAttribute("data-entry-id"));
+ }
+
+ private long getTimestampForRow(TableRowElement row)
+ {
+ return Long.parseLong(row.getAttribute("data-timestamp"));
+ }
+
+ public boolean isValueRow(TableRowElement row)
+ {
+ return !timestampClass_.equals(row.getClassName());
+ }
+
+ public boolean hasNonValueRows()
+ {
+ return timestampMode_ == TimestampMode.GROUP;
+ }
+
+ @Override
+ protected boolean needsBreak(TableRowElement prevRow, TableRowElement row)
+ {
+ if (timestampMode_ == TimestampMode.ITEM)
+ return true;
+
+ if (timestampMode_ == TimestampMode.GROUP)
+ {
+ long lastTime = prevRow == null ? Long.MIN_VALUE
+ : getTimestampForRow(prevRow);
+ long time = getTimestampForRow(row);
+ return Math.abs(time - lastTime) > 1000*60*15;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected int addBreak(TableRowElement row)
+ {
+ String formatted = formatDate(getTimestampForRow(row));
+ if (timestampMode_ == TimestampMode.ITEM)
+ return addTimestampCell(row, formatted);
+ else
+ return addTimestampRow(row, formatted);
+ }
+
+ private int addTimestampRow(TableRowElement row, String formatted)
+ {
+ Element tsRow = Document.get().createElement("tr");
+ tsRow.setClassName(timestampClass_);
+ tsRow.setInnerHTML("<td colspan='2'>" +
+ DomUtils.textToHtml(formatted) +
+ "</td>");
+ row.getParentElement().insertBefore(tsRow, row);
+ return 1;
+ }
+
+ private int addTimestampCell(TableRowElement row, String formatted)
+ {
+ TableCellElement cell = row.getCells().getItem(0);
+ cell.setColSpan(1);
+ TableCellElement tsCell = Document.get().createElement("td").cast();
+ tsCell.setClassName(timestampClass_);
+ tsCell.setInnerText(formatted);
+ row.insertAfter(tsCell, cell);
+ return 0;
+ }
+
+ private String formatDate(long time)
+ {
+ if (time == 0)
+ return null;
+ Date date = new Date(time);
+ return DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_SHORT).format(date);
+ }
+
+ private final String commandClass_;
+ private final String timestampClass_;
+ private final TimestampMode timestampMode_;
+ private final boolean disclosureButton_;
+ private Resources res_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.css
new file mode 100644
index 0000000..d01c1b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.css
@@ -0,0 +1,87 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+.selected {
+ background-color: #ccc;
+}
+*:focus .selected, .inboundFocus .selected, .fakeFocus .selected {
+ background-color: rgb(146, 193, 240)
+}
+ at if user.agent ie8 {
+ .selected {
+ background-color: rgb(146, 193, 240)
+ }
+}
+
+.historyTable:focus {
+ outline: none;
+}
+
+.loadMore {
+ display: block;
+ text-decoration: none;
+ text-align: center;
+ font-weight: bold;
+ padding: 2px 0 2px 0;
+}
+
+.historyTable {
+ font-family: fixedWidthFont;
+ font-size: 12px;
+ overflow-x: hidden;
+ table-layout: fixed;
+}
+
+.historyTable td:first-child div {
+ width: 100%;
+ padding-left: 3px;
+ padding-right: 3px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.historyTable .selected td:first-child div {
+ width: auto;
+ overflow: visible;
+ margin-left: 28px;
+ text-indent: -28px;
+}
+
+.command {
+ width: 20%;
+ overflow-x: hidden;
+ white-space: nowrap;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.selected .command {
+ white-space: normal;
+}
+
+.timestamp {
+ color: #999;
+ font-family: proportionalFont;
+}
+.historyTable td.timestamp {
+ padding-right: 3px;
+ white-space: nowrap;
+ overflow-x: hidden;
+ font-size: 11px !important;
+}
+.historyTable tr.timestamp td {
+ padding-top: 6px;
+ font-size: 11px !important;
+}
+.selected td.timestamp {
+ color: black;
+}
+
+ at sprite div.disclosure {
+ gwt-image: 'searchResultsContextButton2';
+ cursor: pointer;
+ margin-top: 1px;
+}
+ at sprite .selected div.disclosure {
+ gwt-image: 'searchResultsContextButton';
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.java
new file mode 100644
index 0000000..84abccb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryPane.java
@@ -0,0 +1,640 @@
+/*
+ * HistoryPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.layout.client.Layout.AnimationCallback;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.history.HasHistory;
+import org.rstudio.studio.client.workbench.views.history.History.SearchBoxDisplay;
+import org.rstudio.studio.client.workbench.views.history.events.FetchCommandsEvent;
+import org.rstudio.studio.client.workbench.views.history.events.FetchCommandsHandler;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryEntryItemCodec.TimestampMode;
+
+import java.util.ArrayList;
+
+public class HistoryPane extends WorkbenchPane
+ implements org.rstudio.studio.client.workbench.views.history.History.Display,
+ HasSelectionCommitHandlers<Void>
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("HistoryPane.css")
+ Styles styles();
+
+ ImageResource searchResultsContextButton();
+ ImageResource searchResultsContextButton2();
+ }
+
+ interface Styles extends CssResource
+ {
+ String selected();
+ String loadMore();
+ String historyTable();
+
+ String command();
+ String timestamp();
+ String disclosure();
+
+ String inboundFocus();
+ String fakeFocus();
+ }
+
+ public static void ensureStylesInjected()
+ {
+ ((Resources) GWT.create(Resources.class)).styles().ensureInjected();
+ }
+
+ @Inject
+ public HistoryPane(Commands commands)
+ {
+ super("History");
+ commands_ = commands;
+ ensureWidget();
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ scrollToBottom();
+ }
+
+ @Override
+ public void onBeforeUnselected()
+ {
+ super.onBeforeUnselected();
+ recentScrollPanel_.saveScrollPosition();
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ recentScrollPanel_.restoreScrollPosition();
+ }
+ });
+ }
+
+ @Override
+ public void focusSearch()
+ {
+ FocusHelper.setFocusDeferred(searchWidget_);
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ mainPanel_ = new LayoutPanel();
+
+ VerticalPanel vpanel = new VerticalPanel();
+ vpanel.setSize("100%", "100%");
+
+ loadMore_ = new Anchor("Load more entries...", "javascript:return false");
+ loadMore_.setWidth("100%");
+ loadMore_.setVisible(false);
+ loadMore_.setStyleName(styles_.loadMore());
+ vpanel.add(loadMore_);
+ loadMore_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ fireEvent(new FetchCommandsEvent());
+ }
+ });
+
+ commandList_ = createHistoryTable(TimestampMode.NONE);
+ vpanel.add(commandList_);
+
+ recentScrollPanel_ = new BottomScrollPanel() {
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ }
+ };
+ recentScrollPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
+ recentScrollPanel_.setWidget(vpanel);
+ commandList_.setOwningScrollPanel(recentScrollPanel_);
+
+ mainPanel_.add(recentScrollPanel_);
+ mainPanel_.setWidgetTopBottom(recentScrollPanel_, 0, Unit.PX, 0, Unit.PX);
+ mainPanel_.setWidgetLeftRight(recentScrollPanel_, 0, Unit.PX, 0, Unit.PX);
+
+ searchLabel_ = new Label();
+ searchLabel_.setHeight("");
+ searchResults_ = new HistoryTableWithToolbar(
+ createHistoryTable(TimestampMode.ITEM),
+ new Widget[] {
+ searchLabel_
+ },
+ new Widget[] {
+ new SmallButton(commands_.historyDismissResults())
+ });
+ mainPanel_.add(searchResults_);
+ mainPanel_.setWidgetTopBottom(searchResults_, 0, Unit.PX, 0, Unit.PX);
+ mainPanel_.setWidgetLeftRight(searchResults_, 0, Unit.PX, 0, Unit.PX);
+
+ contextLabel_ = new Label();
+ contextResults_ = new HistoryTableWithToolbar(
+ createHistoryTable(TimestampMode.GROUP),
+ new Widget[] {
+ new SmallButton(commands_.historyDismissContext()),
+ contextLabel_
+ },
+ new Widget[] {
+ new SmallButton(commands_.historyDismissResults())
+ });
+ mainPanel_.add(contextResults_);
+ mainPanel_.setWidgetTopBottom(contextResults_, 0, Unit.PX, 0, Unit.PX);
+ mainPanel_.setWidgetLeftRight(contextResults_, 0, Unit.PX, 0, Unit.PX);
+
+ setMode(Mode.Recent);
+ setVisible(searchResults_, searchResults_.getFocusTarget(), false);
+ setVisible(contextResults_, contextResults_.getFocusTarget(), false);
+
+ return mainPanel_;
+ }
+
+ public Mode getMode()
+ {
+ return mode_;
+ }
+
+ protected void setMode(Mode mode)
+ {
+ if (mode != mode_)
+ {
+ Widget current = getWidgetForMode(mode_);
+ Widget target = getWidgetForMode(mode);
+
+ boolean focus = DomUtils.hasFocus(getActiveHistory().getFocusTarget());
+ HasHistory focusCurrent = getHistoryForMode(mode_);
+ HasHistory focusTarget = getHistoryForMode(mode);
+
+ boolean rightToLeft = mode_.getValue() < mode.getValue();
+
+ if (mode != Mode.Recent && mode_ != Mode.Recent)
+ animate(current,
+ target,
+ rightToLeft,
+ focusCurrent,
+ focusTarget,
+ focus);
+ else
+ {
+ setVisible(current, focusCurrent.getFocusTarget(), false);
+ setVisible(target, focusTarget.getFocusTarget(), true);
+ if (focus)
+ {
+ DomUtils.setActive(focusTarget.getFocusTarget());
+ }
+ }
+ mode_ = mode;
+
+ // enable/disable commands
+ boolean enableRemoveCommands = mode_ == Mode.Recent;
+ commands_.historyRemoveEntries().setEnabled(enableRemoveCommands);
+ commands_.clearHistory().setEnabled(enableRemoveCommands);
+ }
+ }
+
+ private HasHistory getHistoryForMode(Mode mode)
+ {
+ return (mode == Mode.Recent) ? commandList_ :
+ (mode == Mode.SearchResults) ? searchResults_ :
+ (mode == Mode.CommandContext) ? contextResults_ : null;
+ }
+
+ private Widget getWidgetForMode(Mode mode)
+ {
+ return (mode == Mode.Recent) ? recentScrollPanel_ :
+ (mode == Mode.SearchResults) ? searchResults_ :
+ (mode == Mode.CommandContext) ? contextResults_ : null;
+ }
+
+ private void animate(final Widget from,
+ final Widget to,
+ boolean rightToLeft,
+ final HasHistory fromFocus,
+ final HasHistory toFocus,
+ final boolean focus)
+ {
+ assert from != to;
+
+ if (focus)
+ toFocus.getFocusTarget().addClassName(styles_.inboundFocus());
+
+ setVisible(to, toFocus.getFocusTarget(), true);
+
+ int width = getOffsetWidth();
+
+ mainPanel_.setWidgetLeftWidth(from,
+ 0, Unit.PX,
+ width, Unit.PX);
+ mainPanel_.setWidgetLeftWidth(to,
+ rightToLeft ? width : -width, Unit.PX,
+ width, Unit.PX);
+ mainPanel_.forceLayout();
+
+ mainPanel_.setWidgetLeftWidth(from,
+ rightToLeft ? -width : width, Unit.PX,
+ width, Unit.PX);
+ mainPanel_.setWidgetLeftWidth(to,
+ 0, Unit.PX,
+ width, Unit.PX);
+
+ mainPanel_.animate(300, new AnimationCallback()
+ {
+ public void onAnimationComplete()
+ {
+ setVisible(from, fromFocus.getFocusTarget(), false);
+ mainPanel_.setWidgetLeftRight(to, 0, Unit.PX, 0, Unit.PX);
+ mainPanel_.forceLayout();
+ if (focus)
+ {
+ DomUtils.setActive(toFocus.getFocusTarget());
+ toFocus.getFocusTarget().removeClassName(styles_.inboundFocus());
+ }
+ }
+
+ public void onLayout(Layer layer, double progress)
+ {
+ }
+ });
+ }
+
+ private void setVisible(Widget widget,
+ Element focusTarget,
+ boolean visible)
+ {
+ //mainPanel_.getWidgetContainerElement(widget).getStyle().setDisplay(
+ // visible ? Display.BLOCK : Display.NONE);
+ if (visible)
+ {
+ if (focusTarget.getTabIndex() != 0)
+ {
+ focusTarget.setTabIndex(0);
+ mainPanel_.setWidgetLeftRight(widget, 0, Unit.PX, 0, Unit.PX);
+ }
+ }
+ else
+ {
+ if (focusTarget.getTabIndex() != -1)
+ {
+ focusTarget.setTabIndex(-1);
+ if (DomUtils.hasFocus(focusTarget))
+ focusTarget.blur();
+ mainPanel_.setWidgetLeftRight(widget, -5000, Unit.PX, 5000, Unit.PX);
+ }
+ }
+ mainPanel_.forceLayout();
+ }
+
+ public void scrollToBottom()
+ {
+ assert mode_ == Mode.Recent;
+ recentScrollPanel_.scrollToBottom();
+ }
+
+ private HistoryTable createHistoryTable(TimestampMode timestampMode)
+ {
+ HistoryTable table = new HistoryTable(
+ styles_.command(),
+ styles_.timestamp(),
+ styles_.selected(),
+ timestampMode,
+ commands_);
+ table.setSize("100%", "100%");
+ table.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (doubleClick_.checkForDoubleClick(event.getNativeEvent()))
+ {
+ if (event.getNativeEvent().getShiftKey())
+ commands_.historySendToSource().execute();
+ else
+ commands_.historySendToConsole().execute();
+ }
+ }
+ private final DoubleClickState doubleClick_ = new DoubleClickState();
+ });
+
+ return table;
+ }
+
+ public void setRecentCommands(ArrayList<HistoryEntry> entries,
+ boolean scrollToBottom)
+ {
+ commandList_.clear();
+ commandList_.addItems(entries, true);
+ if (scrollToBottom)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ recentScrollPanel_.scrollToBottom();
+ }
+ });
+ }
+ }
+
+ public void truncateRecentCommands(int maxCommands)
+ {
+ commandList_.removeTopRows(
+ Math.max(0, commandList_.getRowCount() - maxCommands));
+ }
+
+ public ArrayList<Integer> getRecentCommandsSelectedRowIndexes()
+ {
+ return commandList_.getSelectedRowIndexes();
+ }
+
+ public int getRecentCommandsRowsDisplayed()
+ {
+ return commandList_.getRowCount();
+ }
+
+ public void setMoreCommands(long moreCommands)
+ {
+ if (moreCommands <= 0)
+ loadMore_.setVisible(false);
+ else
+ {
+ loadMore_.setVisible(true);
+ loadMore_.setText("Load " + moreCommands + " more entries");
+ }
+ }
+
+ public void addRecentCommands(ArrayList<HistoryEntry> entries, boolean top)
+ {
+ TableRowElement topRow = null;
+ if (top)
+ topRow = commandList_.getTopRow();
+
+ commandList_.addItems(entries, top);
+
+ if (top)
+ {
+ if (topRow == null)
+ recentScrollPanel_.scrollToBottom();
+ else
+ recentScrollPanel_.setVerticalScrollPosition(topRow.getOffsetTop());
+ }
+ else
+ recentScrollPanel_.onContentSizeChanged();
+ }
+
+ public ArrayList<String> getSelectedCommands()
+ {
+ return getActiveHistory().getSelectedValues();
+ }
+
+ public ArrayList<Long> getSelectedCommandIndexes()
+ {
+ return getActiveHistory().getSelectedCommandIndexes();
+ }
+
+ public SearchBoxDisplay getSearchBox()
+ {
+ return new SearchBoxDisplay()
+ {
+ public String getText()
+ {
+ return searchWidget_.getText();
+ }
+
+ public void setText(String text)
+ {
+ searchWidget_.setText(text, true);
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return searchWidget_.addValueChangeHandler(handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ searchWidget_.fireEvent(event);
+ }
+ };
+ }
+
+ public int getRecentCommandsScrollPosition()
+ {
+ return recentScrollPanel_.getVerticalScrollPosition();
+ }
+
+ public void setRecentCommandsScrollPosition(int scrollPosition)
+ {
+ recentScrollPanel_.setVerticalScrollPosition(scrollPosition);
+ }
+
+ private HasHistory getActiveHistory()
+ {
+ switch (mode_)
+ {
+ case Recent:
+ return commandList_;
+ case SearchResults:
+ return searchResults_;
+ case CommandContext:
+ return contextResults_;
+ default:
+ assert false : "Unknown mode";
+ return null;
+ }
+ }
+
+ public void dismissSearchResults()
+ {
+ setMode(Mode.Recent);
+ //searchResults_.clear();
+ //contextResults_.clear();
+ searchWidget_.setText("");
+ }
+
+ public void showSearchResults(String query,
+ ArrayList<HistoryEntry> entries)
+ {
+ searchLabel_.setText("Search results: " + query);
+ setMode(Mode.SearchResults);
+ contextResults_.clear();
+ searchResults_.clear();
+ searchResults_.addItems(entries, true);
+ if (entries.size() > 0)
+ searchResults_.highlightRows(0, 1);
+ }
+
+ public void dismissContext()
+ {
+ setMode(Mode.SearchResults);
+ DomUtils.setActive(searchResults_.getFocusTarget());
+ //contextResults_.clear();
+ }
+
+ public HasHistory getRecentCommandsWidget()
+ {
+ return commandList_;
+ }
+
+ public HasHistory getSearchResultsWidget()
+ {
+ return searchResults_;
+ }
+
+ public HasHistory getCommandContextWidget()
+ {
+ return contextResults_;
+ }
+
+ public boolean isCommandTableFocused()
+ {
+ return DomUtils.hasFocus(getActiveHistory().getFocusTarget());
+ }
+
+ public void showContext(String command,
+ ArrayList<HistoryEntry> entries,
+ long highlightOffset,
+ long highlightLength)
+ {
+ contextLabel_.setText("Showing command in context");
+ setMode(Mode.CommandContext);
+ contextResults_.clear();
+ contextResults_.addItems(entries, true);
+ contextResults_.highlightRows((int)highlightOffset, (int)highlightLength);
+ }
+
+ public HandlerRegistration addFetchCommandsHandler(FetchCommandsHandler handler)
+ {
+ return addHandler(handler, FetchCommandsEvent.TYPE);
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ searchWidget_ = new SearchWidget(new SuggestOracle()
+ {
+ @Override
+ public void requestSuggestions(Request request,
+ Callback callback)
+ {
+ callback.onSuggestionsReady(
+ request,
+ new Response(new ArrayList<Suggestion>()));
+ }
+ });
+ searchWidget_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ getActiveHistory().getKeyTarget().fireEvent(event);
+ }
+ });
+ searchWidget_.addSelectionCommitHandler(new SelectionCommitHandler<String>()
+ {
+ public void onSelectionCommit(SelectionCommitEvent<String> event)
+ {
+ if (mode_ == Mode.SearchResults)
+ fireEvent(event);
+ }
+ });
+ searchWidget_.addFocusHandler(new FocusHandler()
+ {
+ public void onFocus(FocusEvent event)
+ {
+ commandList_.getFocusTarget().addClassName(styles_.fakeFocus());
+ searchResults_.getFocusTarget().addClassName(styles_.fakeFocus());
+ contextResults_.getFocusTarget().addClassName(styles_.fakeFocus());
+ }
+ });
+ searchWidget_.addBlurHandler(new BlurHandler()
+ {
+ public void onBlur(BlurEvent event)
+ {
+ commandList_.getFocusTarget().removeClassName(styles_.fakeFocus());
+ searchResults_.getFocusTarget().removeClassName(styles_.fakeFocus());
+ contextResults_.getFocusTarget().removeClassName(styles_.fakeFocus());
+ }
+ });
+
+ Toolbar toolbar = new Toolbar();
+ toolbar.addLeftWidget(commands_.loadHistory().createToolbarButton());
+ toolbar.addLeftWidget(commands_.saveHistory().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.historySendToConsole().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.historySendToSource().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.historyRemoveEntries().createToolbarButton());
+ toolbar.addLeftWidget(commands_.clearHistory().createToolbarButton());
+
+ toolbar.addRightWidget(searchWidget_);
+
+ return toolbar;
+ }
+
+ public HandlerRegistration addSelectionCommitHandler(
+ SelectionCommitHandler<Void> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType());
+ }
+
+ private HistoryTable commandList_;
+ private BottomScrollPanel recentScrollPanel_;
+
+ private Label searchLabel_;
+ private Label contextLabel_;
+ private HistoryTableWithToolbar contextResults_;
+ private HistoryTableWithToolbar searchResults_;
+ private final Commands commands_;
+ private Anchor loadMore_;
+ private SearchWidget searchWidget_;
+ private Styles styles_ = ((Resources) GWT.create(Resources.class)).styles();
+ private LayoutPanel mainPanel_;
+ private Mode mode_ = Mode.Recent;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTable.java
new file mode 100644
index 0000000..17bbda0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTable.java
@@ -0,0 +1,128 @@
+/*
+ * HistoryTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.TableColElement;
+import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.event.dom.client.HasAllKeyHandlers;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import org.rstudio.core.client.widget.FastSelectTable;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.history.HasHistory;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryEntryItemCodec.TimestampMode;
+import org.rstudio.studio.client.workbench.views.history.view.HistoryPane.Resources;
+
+import java.util.ArrayList;
+
+public class HistoryTable extends FastSelectTable<HistoryEntry, String, Long>
+ implements HasHistory
+{
+ public HistoryTable(String commandClassName,
+ String timestampClassName,
+ String selectedClassName,
+ TimestampMode timestampMode,
+ final Commands commands)
+ {
+ super(new HistoryEntryItemCodec(commandClassName,
+ timestampClassName,
+ timestampMode,
+ timestampMode == TimestampMode.ITEM),
+ selectedClassName,
+ true,
+ true);
+
+ searchResult_ = timestampMode == TimestampMode.ITEM;
+
+ applyWidthConstraints();
+
+ final Resources res = GWT.create(Resources.class);
+ setStyleName(res.styles().historyTable());
+ FontSizer.applyNormalFontSize(this);
+
+ if (searchResult_)
+ {
+ addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ Element el = DOM.eventGetTarget((Event) event.getNativeEvent());
+ if (el != null
+ && el.getTagName().equalsIgnoreCase("div")
+ && el.getClassName().equals(res.styles().disclosure()))
+ {
+ // disclosure click
+ commands.historyShowContext().execute();
+ }
+ }
+ });
+ }
+ }
+
+ private void applyWidthConstraints()
+ {
+ createAndAppendCol("100%");
+ createAndAppendCol("105");
+ if (searchResult_)
+ createAndAppendCol("23");
+ }
+
+ private TableColElement createAndAppendCol(String width)
+ {
+ TableColElement col = Document.get().createColElement();
+ col.setWidth(width);
+ getElement().appendChild(col);
+ lastCol_ = col;
+ return col;
+ }
+
+ @Override
+ protected void addToTop(TableSectionElement tbody)
+ {
+ getElement().insertAfter(tbody, lastCol_);
+ }
+
+ @Override
+ public void clear()
+ {
+ super.clear();
+ applyWidthConstraints();
+ }
+
+ public ArrayList<Long> getSelectedCommandIndexes()
+ {
+ return getSelectedValues2();
+ }
+
+ public HasAllKeyHandlers getKeyTarget()
+ {
+ return this;
+ }
+
+ public com.google.gwt.dom.client.Element getFocusTarget()
+ {
+ return getElement();
+ }
+
+ private TableColElement lastCol_;
+ private boolean searchResult_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTableWithToolbar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTableWithToolbar.java
new file mode 100644
index 0000000..6e2e7d7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/HistoryTableWithToolbar.java
@@ -0,0 +1,146 @@
+/*
+ * HistoryTableWithToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.view;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.HasAllKeyHandlers;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.studio.client.workbench.views.history.HasHistory;
+import org.rstudio.studio.client.workbench.views.history.model.HistoryEntry;
+
+import java.util.ArrayList;
+
+public class HistoryTableWithToolbar extends Composite
+ implements HasHistory, HasAllKeyHandlers
+{
+ public HistoryTableWithToolbar(HistoryTable table,
+ Widget[] leftWidgets,
+ Widget[] rightWidgets)
+ {
+ DockLayoutPanel panel = new DockLayoutPanel(Unit.PX);
+
+ Shelf toolbar = new Shelf();
+ toolbar.setWidth("100%");
+
+ if (leftWidgets != null)
+ {
+ for (int i = 0; i < leftWidgets.length; i++)
+ {
+ toolbar.addLeftWidget(leftWidgets[i]);
+ }
+ }
+ if (rightWidgets != null)
+ {
+ for (int i = 0; i < rightWidgets.length; i++)
+ {
+ toolbar.addRightWidget(rightWidgets[i]);
+ }
+ }
+ panel.addNorth(toolbar, toolbar.getHeight());
+
+ historyTable_ = table;
+ scrollPanel_ = new ScrollPanel(historyTable_);
+ scrollPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
+ scrollPanel_.setSize("100%", "100%");
+ panel.add(scrollPanel_);
+
+ historyTable_.setOwningScrollPanel(scrollPanel_);
+
+ initWidget(panel);
+ }
+
+ public void clear()
+ {
+ historyTable_.clear();
+ }
+
+ public void addItems(ArrayList<HistoryEntry> entries, boolean top)
+ {
+ historyTable_.addItems(entries, top);
+ }
+
+ public ArrayList<String> getSelectedValues()
+ {
+ return historyTable_.getSelectedValues();
+ }
+
+ public ArrayList<Long> getSelectedCommandIndexes()
+ {
+ return historyTable_.getSelectedValues2();
+ }
+
+ public boolean moveSelectionUp()
+ {
+ return historyTable_.moveSelectionUp();
+ }
+
+ public boolean moveSelectionDown()
+ {
+ return historyTable_.moveSelectionDown();
+ }
+
+ public HasAllKeyHandlers getKeyTarget()
+ {
+ return historyTable_;
+ }
+
+ public void highlightRows(int offset, int length)
+ {
+ historyTable_.clearSelection();
+ historyTable_.setSelected(offset, length, true);
+
+ Rectangle rect = historyTable_.getSelectionRect();
+ if (rect == null)
+ return;
+ int height = scrollPanel_.getOffsetHeight();
+ if (rect.getHeight() > height)
+ scrollPanel_.setVerticalScrollPosition(rect.getTop());
+ else
+ scrollPanel_.setVerticalScrollPosition(
+ rect.getTop() - (height - rect.getHeight())/2);
+ }
+
+ public HandlerRegistration addKeyUpHandler(KeyUpHandler handler)
+ {
+ return historyTable_.addKeyUpHandler(handler);
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return historyTable_.addKeyDownHandler(handler);
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
+ {
+ return historyTable_.addKeyPressHandler(handler);
+ }
+
+ public Element getFocusTarget()
+ {
+ return historyTable_.getElement();
+ }
+
+ private final HistoryTable historyTable_;
+ private ScrollPanel scrollPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.css
new file mode 100644
index 0000000..a93609e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.css
@@ -0,0 +1,27 @@
+ at external gwt-Label;
+
+ at sprite .shelf {
+ gwt-image: 'shelfbg';
+ padding: 0 8px 1px 8px;
+}
+
+ at sprite .largeShelf {
+ gwt-image: 'shelfbgLarge';
+ padding: 0 8px 1px 8px;
+}
+
+.shelf .gwt-Label {
+ font-size: 10px;
+}
+
+.largeShelf .gwt-Label {
+ font-size: 10px;
+}
+
+.left button {
+ margin-right: 4px;
+}
+
+.right button {
+ margin-left: 4px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.java
new file mode 100644
index 0000000..635fcee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.java
@@ -0,0 +1,125 @@
+/*
+ * Shelf.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.history.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class Shelf extends Composite
+{
+ interface Binder extends UiBinder<HorizontalPanel, Shelf> {}
+ interface Resources extends ClientBundle
+ {
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource shelfbg();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource shelfbgLarge();
+
+ @Source("Shelf.css")
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String shelf();
+ String largeShelf();
+ String left();
+ String right();
+ }
+
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ public Shelf()
+ {
+ this(false);
+ }
+
+ public Shelf(boolean large)
+ {
+ large_ = large;
+ HorizontalPanel mainPanel = binder.createAndBindUi(this);
+ if (large_)
+ mainPanel.setStyleName(RES.styles().largeShelf());
+
+ initWidget(mainPanel);
+
+
+ left_.setHeight("100%");
+ right_.setHeight("100%");
+
+ left_.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
+ right_.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
+
+ mainPanel.setCellHorizontalAlignment(right_,
+ HorizontalPanel.ALIGN_RIGHT);
+ }
+
+ public void addLeftWidget(Widget w)
+ {
+ left_.add(w);
+ }
+
+ public void addRightWidget(Widget w)
+ {
+ right_.add(w);
+ }
+
+ public void setRightVerticalAlignment(VerticalAlignmentConstant alignment)
+ {
+ right_.setVerticalAlignment(alignment);
+ }
+
+ public int getHeight()
+ {
+
+ if (large_)
+ return RES.shelfbgLarge().getHeight();
+ else
+ return RES.shelfbg().getHeight();
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ private static final Binder binder = GWT.create(Binder.class);
+
+ @UiField
+ HorizontalPanel left_;
+ @UiField
+ HorizontalPanel right_;
+
+ private boolean large_ = false;
+
+ private static final Resources RES = (Resources)GWT.create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.ui.xml
new file mode 100644
index 0000000..70c0446
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/Shelf.ui.xml
@@ -0,0 +1,10 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.workbench.views.history.view.Shelf.Resources"/>
+
+ <g:HorizontalPanel styleName="{res.styles.shelf}">
+ <g:HorizontalPanel styleName="{res.styles.left}" ui:field="left_"/>
+ <g:HorizontalPanel styleName="{res.styles.right}" ui:field="right_"/>
+ </g:HorizontalPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton.png
new file mode 100644
index 0000000..7508802
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton2.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton2.png
new file mode 100644
index 0000000..7d1f559
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/searchResultsContextButton2.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbg.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbg.png
new file mode 100644
index 0000000..213fbb7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbg.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbgLarge.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbgLarge.png
new file mode 100644
index 0000000..a082e80
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/history/view/shelfbgLarge.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPane.java
new file mode 100644
index 0000000..3a7748b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPane.java
@@ -0,0 +1,165 @@
+/*
+ * CompilePdfOutputPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.compilepdf;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompilePanel;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorList;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+
+public class CompilePdfOutputPane extends WorkbenchPane
+ implements CompilePdfOutputPresenter.Display
+{
+ @Inject
+ public CompilePdfOutputPane()
+ {
+ super("Compile PDF");
+ compilePanel_ = new CompilePanel();
+ ensureWidget();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return compilePanel_;
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ fileLabel_ = new ToolbarFileLabel(toolbar, 200);
+
+ ImageResource showLogImage = StandardIcons.INSTANCE.show_log();
+ showLogButton_ = new ToolbarButton("View Log",
+ showLogImage,
+ (ClickHandler) null);
+ showLogButton_.getElement().getStyle().setMarginBottom(3, Unit.PX);
+ showLogButton_.setTitle("View the LaTeX compilation log");
+ showLogSeparator_ = toolbar.addLeftSeparator();
+ setShowLogVisible(false);
+ toolbar.addLeftWidget(showLogButton_);
+
+ compilePanel_.connectToolbar(toolbar);
+
+ return toolbar;
+ }
+
+ @Override
+ public void ensureVisible(boolean activate)
+ {
+ fireEvent(new EnsureVisibleEvent(activate));
+ }
+
+ @Override
+ public void compileStarted(String fileName)
+ {
+ clearAll();
+
+ fileLabel_.setFileName(fileName);
+ setShowLogVisible(false);
+
+ compilePanel_.compileStarted(fileName);
+ }
+
+ @Override
+ public void clearAll()
+ {
+ setShowLogVisible(false);
+
+ compilePanel_.clearAll();
+
+ }
+
+ @Override
+ public void showOutput(String output)
+ {
+ compilePanel_.showOutput(output);
+ }
+
+
+ @Override
+ public void showErrors(JsArray<CompileError> errors)
+ {
+ compilePanel_.showErrors(null, errors, CompileErrorList.AUTO_SELECT_FIRST);
+
+ if (CompileError.showErrorList(errors))
+ ensureVisible(true);
+ }
+
+ @Override
+ public boolean isEffectivelyVisible()
+ {
+ return DomUtils.isEffectivelyVisible(getElement());
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ compilePanel_.scrollToBottom();
+ }
+
+ @Override
+ public void compileCompleted()
+ {
+ compilePanel_.compileCompleted();
+
+ setShowLogVisible(true);
+ }
+
+ @Override
+ public HasClickHandlers stopButton()
+ {
+ return compilePanel_.stopButton();
+ }
+
+ @Override
+ public HasClickHandlers showLogButton()
+ {
+ return showLogButton_;
+ }
+
+ @Override
+ public HasSelectionCommitHandlers<CodeNavigationTarget> errorList()
+ {
+ return compilePanel_.errorList();
+ }
+
+ private void setShowLogVisible(boolean visible)
+ {
+ showLogSeparator_.setVisible(visible);
+ showLogButton_.setVisible(visible);
+ }
+
+ private ToolbarFileLabel fileLabel_;
+ private Widget showLogSeparator_;
+ private ToolbarButton showLogButton_;
+
+
+ private CompilePanel compilePanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPresenter.java
new file mode 100644
index 0000000..349096b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputPresenter.java
@@ -0,0 +1,310 @@
+/*
+ * CompilePdfOutputPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.compilepdf;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.CommandUtil;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.common.DelayedProgressRequestCallback;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfErrorsEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfOutputEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfStartedEvent;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfServerOperations;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfState;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.output.compilepdf.events.CompilePdfEvent;
+
+
+public class CompilePdfOutputPresenter extends BasePresenter
+ implements CompilePdfEvent.Handler,
+ CompilePdfStartedEvent.Handler,
+ CompilePdfOutputEvent.Handler,
+ CompilePdfErrorsEvent.Handler,
+ CompilePdfCompletedEvent.Handler
+{
+ public interface Display extends WorkbenchView, HasEnsureHiddenHandlers
+ {
+ void ensureVisible(boolean activate);
+ void compileStarted(String text);
+ void showOutput(String output);
+ void showErrors(JsArray<CompileError> errors);
+ void clearAll();
+ void compileCompleted();
+ HasClickHandlers stopButton();
+ HasClickHandlers showLogButton();
+ HasSelectionCommitHandlers<CodeNavigationTarget> errorList();
+ boolean isEffectivelyVisible();
+ void scrollToBottom();
+ }
+
+ @Inject
+ public CompilePdfOutputPresenter(Display view,
+ GlobalDisplay globalDisplay,
+ CompilePdfServerOperations server,
+ FileTypeRegistry fileTypeRegistry,
+ Commands commands)
+ {
+ super(view);
+ view_ = view;
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ fileTypeRegistry_ = fileTypeRegistry;
+ commands_ = commands;
+
+ view_.stopButton().addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ terminateCompilePdf(null);
+ }
+
+ });
+
+ view_.showLogButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ FileSystemItem logFile = FileSystemItem.createFile(
+ targetFile_.getParentPath().completePath(
+ targetFile_.getStem() + ".log"));
+ fileTypeRegistry_.editFile(logFile);
+ }
+
+ });
+
+ view_.errorList().addSelectionCommitHandler(
+ new SelectionCommitHandler<CodeNavigationTarget>() {
+
+ @Override
+ public void onSelectionCommit(
+ SelectionCommitEvent<CodeNavigationTarget> event)
+ {
+ CodeNavigationTarget target = event.getSelectedItem();
+ FileSystemItem fsi = FileSystemItem.createFile(target.getFile());
+ fileTypeRegistry_.editFile(fsi, target.getPosition());
+ }
+ });
+ }
+
+ public void initialize(CompilePdfState compilePdfState)
+ {
+ view_.ensureVisible(false);
+
+ view_.clearAll();
+
+ compileStarted(compilePdfState.getTargetFile());
+
+ view_.showOutput(compilePdfState.getOutput());
+
+ if (compilePdfState.getErrors().length() > 0)
+ view_.showErrors(compilePdfState.getErrors());
+
+ if (!compilePdfState.isRunning())
+ view_.compileCompleted();
+ }
+
+ public void confirmClose(Command onConfirmed)
+ {
+ // wrap the onConfirmed in another command which notifies the server
+ // that we've closed the tab
+ final Command confirmedCommand = CommandUtil.join(onConfirmed,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ server_.compilePdfClosed(new VoidServerRequestCallback());
+ }
+ });
+
+ server_.isCompilePdfRunning(
+ new DelayedProgressRequestCallback<Boolean>("Closing Compile PDF...") {
+ @Override
+ public void onSuccess(Boolean isRunning)
+ {
+ if (isRunning)
+ {
+ confirmTerminateRunningCompile("close the Compile PDF tab",
+ confirmedCommand);
+ }
+ else
+ {
+ confirmedCommand.execute();
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public void onCompilePdf(CompilePdfEvent event)
+ {
+ // switch back to the console after compile unless the compile pdf
+ // tab was already visible
+ switchToConsoleOnSuccessfulCompile_ = !view_.isEffectivelyVisible();
+
+ // activate the compile pdf tab if we aren't using the internal previewer
+ boolean activate = !event.useInternalPreview();
+ view_.ensureVisible(activate);
+
+ // run the compile
+ compilePdf(event.getTargetFile(),
+ event.getEncoding(),
+ event.getSourceLocation(),
+ event.getCompletedAction());
+ }
+
+ @Override
+ public void onCompilePdfOutput(CompilePdfOutputEvent event)
+ {
+ view_.showOutput(event.getOutput());
+ }
+
+ @Override
+ public void onCompilePdfErrors(CompilePdfErrorsEvent event)
+ {
+ view_.showErrors(event.getErrors());
+ }
+
+ @Override
+ public void onCompilePdfStarted(CompilePdfStartedEvent event)
+ {
+ compileStarted(event.getTargetFile());
+ }
+
+ @Override
+ public void onCompilePdfCompleted(CompilePdfCompletedEvent event)
+ {
+ view_.compileCompleted();
+
+ if (event.getResult().getSucceeded() &&
+ switchToConsoleOnSuccessfulCompile_)
+ {
+ commands_.activateConsole().execute();
+ }
+ else if (!event.getResult().getSucceeded())
+ {
+ view_.ensureVisible(true);
+ }
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+ Scheduler.get().scheduleDeferred(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ view_.scrollToBottom();
+ }
+ });
+ }
+
+ private void compileStarted(String targetFile)
+ {
+ targetFile_ = FileSystemItem.createFile(targetFile);
+ view_.compileStarted(targetFile);
+ }
+
+ private void compilePdf(FileSystemItem targetFile,
+ String encoding,
+ SourceLocation sourceLocation,
+ String completedAction)
+ {
+ // attempt to start a compilation (this might not actually work
+ // if there is already a compile running)
+ server_.compilePdf(
+ targetFile,
+ encoding,
+ sourceLocation,
+ completedAction,
+ new DelayedProgressRequestCallback<Boolean>("Compiling PDF...")
+ {
+ @Override
+ protected void onSuccess(Boolean started)
+ {
+ }
+ });
+ }
+
+ private void confirmTerminateRunningCompile(String operation,
+ final Command onTerminated)
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Stop Running Compile",
+ "There is a PDF compilation currently running. If you " +
+ operation + " it will be terminated. Are you " +
+ "sure you want to stop the running PDF compilation?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ terminateCompilePdf(onTerminated);
+ }},
+ false);
+ }
+
+
+ private void terminateCompilePdf(final Command onTerminated)
+ {
+ server_.terminateCompilePdf(new DelayedProgressRequestCallback<Boolean>(
+ "Terminating PDF compilation...") {
+ @Override
+ protected void onSuccess(Boolean wasTerminated)
+ {
+ if (wasTerminated)
+ {
+ if (onTerminated != null)
+ onTerminated.execute();
+ }
+ else
+ {
+ globalDisplay_.showErrorMessage(
+ "Compile PDF",
+ "Unable to terminate PDF compilation. Please try again.");
+ }
+ }
+ });
+ }
+
+ private FileSystemItem targetFile_ = null;
+ private final Display view_;
+ private final GlobalDisplay globalDisplay_;
+ private final CompilePdfServerOperations server_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final Commands commands_;
+ private boolean switchToConsoleOnSuccessfulCompile_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputTab.java
new file mode 100644
index 0000000..ac4a9e0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/CompilePdfOutputTab.java
@@ -0,0 +1,90 @@
+/*
+ * CompilePdfOutputTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.compilepdf;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfCompletedEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfErrorsEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfOutputEvent;
+import org.rstudio.studio.client.common.compilepdf.events.CompilePdfStartedEvent;
+import org.rstudio.studio.client.common.compilepdf.model.CompilePdfState;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+
+import org.rstudio.studio.client.workbench.views.output.compilepdf.events.CompilePdfEvent;
+
+public class CompilePdfOutputTab extends DelayLoadWorkbenchTab<CompilePdfOutputPresenter>
+{
+ public abstract static class Shim extends
+ DelayLoadTabShim<CompilePdfOutputPresenter, CompilePdfOutputTab>
+ implements CompilePdfEvent.Handler,
+ CompilePdfStartedEvent.Handler,
+ CompilePdfOutputEvent.Handler,
+ CompilePdfErrorsEvent.Handler,
+ CompilePdfCompletedEvent.Handler
+ {
+ abstract void initialize(CompilePdfState compilePdfState);
+ abstract void confirmClose(Command onConfirmed);
+ }
+
+ @Inject
+ public CompilePdfOutputTab(Shim shim,
+ EventBus events,
+ final Session session)
+ {
+ super("Compile PDF", shim);
+ shim_ = shim;
+
+ events.addHandler(CompilePdfEvent.TYPE, shim);
+ events.addHandler(CompilePdfOutputEvent.TYPE, shim);
+ events.addHandler(CompilePdfErrorsEvent.TYPE, shim);
+ events.addHandler(CompilePdfStartedEvent.TYPE, shim);
+ events.addHandler(CompilePdfCompletedEvent.TYPE, shim);
+
+ events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ SessionInfo sessionInfo = session.getSessionInfo();
+ CompilePdfState compilePdfState = sessionInfo.getCompilePdfState();
+ if (compilePdfState.isTabVisible())
+ shim_.initialize(compilePdfState);
+ }
+ });
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return true;
+ }
+
+ @Override
+ public void confirmClose(Command onConfirmed)
+ {
+ shim_.confirmClose(onConfirmed);
+ }
+
+
+ private Shim shim_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/events/CompilePdfEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/events/CompilePdfEvent.java
new file mode 100644
index 0000000..e112f21
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/compilepdf/events/CompilePdfEvent.java
@@ -0,0 +1,87 @@
+/*
+ * CompilePdfEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.compilepdf.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CompilePdfEvent extends GwtEvent<CompilePdfEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCompilePdf(CompilePdfEvent event);
+ }
+
+ public CompilePdfEvent(FileSystemItem targetFile,
+ String encoding,
+ SourceLocation sourceLocation,
+ String completedAction,
+ boolean useInternalPreview)
+ {
+ targetFile_ = targetFile;
+ encoding_ = encoding;
+ sourceLocation_ = sourceLocation;
+ completedAction_ = completedAction;
+ useInternalPreview_ = useInternalPreview;
+ }
+
+ public FileSystemItem getTargetFile()
+ {
+ return targetFile_;
+ }
+
+ public String getEncoding()
+ {
+ return encoding_;
+ }
+
+ public SourceLocation getSourceLocation()
+ {
+ return sourceLocation_;
+ }
+
+ public String getCompletedAction()
+ {
+ return completedAction_;
+ }
+
+ public boolean useInternalPreview()
+ {
+ return useInternalPreview_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCompilePdf(this);
+ }
+
+ private final FileSystemItem targetFile_;
+ private final String encoding_;
+ private final SourceLocation sourceLocation_;
+ private final String completedAction_;
+ private final boolean useInternalPreview_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.java
new file mode 100644
index 0000000..27d7fc5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.java
@@ -0,0 +1,245 @@
+/*
+ * FindInFilesDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.core.client.widget.DirectoryChooserTextBox;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class FindInFilesDialog extends ModalDialog<FindInFilesDialog.State>
+{
+ public interface Binder extends UiBinder<Widget, FindInFilesDialog>
+ {
+ }
+
+ public static class State extends JavaScriptObject
+ {
+ public static native State createState(String query,
+ String path,
+ boolean regex,
+ boolean caseSensitive,
+ JsArrayString filePatterns) /*-{
+ return {
+ query: query,
+ path: path,
+ regex: regex,
+ caseSensitive: caseSensitive,
+ filePatterns: filePatterns
+ };
+ }-*/;
+
+ protected State() {}
+
+ public native final String getQuery() /*-{
+ return this.query;
+ }-*/;
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final boolean isRegex() /*-{
+ return this.regex;
+ }-*/;
+
+ public native final boolean isCaseSensitive() /*-{
+ return this.caseSensitive;
+ }-*/;
+
+ public final String[] getFilePatterns()
+ {
+ return JsUtil.toStringArray(getFilePatternsNative());
+ }
+
+ private native JsArrayString getFilePatternsNative() /*-{
+ return this.filePatterns;
+ }-*/;
+ }
+
+ public FindInFilesDialog(OperationWithInput<State> operation)
+ {
+ super("Find in Files", operation);
+
+ dirChooser_ = new DirectoryChooserTextBox("Search in:", null);
+ dirChooser_.setText("");
+ mainWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ setOkButtonCaption("Find");
+
+ listPresetFilePatterns_.addChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ manageFilePattern();
+ }
+ });
+ manageFilePattern();
+
+ txtSearchPattern_.addKeyUpHandler(new KeyUpHandler()
+ {
+ @Override
+ public void onKeyUp(KeyUpEvent event)
+ {
+ enableOkButton(txtSearchPattern_.getText().trim().length() > 0);
+ }
+ });
+ }
+
+ public void setDirectory(FileSystemItem directory)
+ {
+ dirChooser_.setText(directory.getPath());
+ }
+
+ private void manageFilePattern()
+ {
+ divCustomFilter_.getStyle().setDisplay(
+ listPresetFilePatterns_.getSelectedIndex() == 2
+ ? Style.Display.BLOCK
+ : Style.Display.NONE);
+ }
+
+ @Override
+ protected State collectInput()
+ {
+ String filePatterns =
+ listPresetFilePatterns_.getValue(
+ listPresetFilePatterns_.getSelectedIndex());
+ if (filePatterns.equals("custom"))
+ filePatterns = txtFilePattern_.getText();
+
+ ArrayList<String> list = new ArrayList<String>();
+ for (String pattern : filePatterns.split(","))
+ {
+ String trimmedPattern = pattern.trim();
+ if (trimmedPattern.length() > 0)
+ list.add(trimmedPattern);
+ }
+
+ return State.createState(txtSearchPattern_.getText(),
+ getEffectivePath().getPath(),
+ checkboxRegex_.getValue(),
+ checkboxCaseSensitive_.getValue(),
+ JsUtil.toJsArrayString(list));
+ }
+
+ private FileSystemItem getEffectivePath()
+ {
+ if (StringUtil.notNull(dirChooser_.getText()).trim().length() == 0)
+ return null;
+ return FileSystemItem.createDir(dirChooser_.getText());
+ }
+
+ @Override
+ protected boolean validate(State input)
+ {
+ if (StringUtil.isNullOrEmpty(input.getQuery().trim()))
+ {
+ // TODO: Show an error message here? Or disable Find button until there
+ // is something to search for?
+ return false;
+ }
+
+ if (StringUtil.isNullOrEmpty(input.getPath().trim()))
+ {
+ globalDisplay_.showErrorMessage(
+ "Error", "You must specify a directory to search.");
+
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return mainWidget_;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ super.onDialogShown();
+
+ txtSearchPattern_.setFocus(true);
+ txtSearchPattern_.selectAll();
+ }
+
+ public void setSearchPattern(String searchPattern)
+ {
+ txtSearchPattern_.setText(searchPattern);
+ }
+
+ public void setState(State dialogState)
+ {
+ if (txtSearchPattern_.getText().isEmpty())
+ txtSearchPattern_.setText(dialogState.getQuery());
+ checkboxCaseSensitive_.setValue(dialogState.isCaseSensitive());
+ checkboxRegex_.setValue(dialogState.isRegex());
+ dirChooser_.setText(dialogState.getPath());
+
+ String filePatterns = StringUtil.join(
+ Arrays.asList(dialogState.getFilePatterns()), ", ");
+ if (listPresetFilePatterns_.getValue(0).equals(filePatterns))
+ listPresetFilePatterns_.setSelectedIndex(0);
+ else if (listPresetFilePatterns_.getValue(1).equals(filePatterns))
+ listPresetFilePatterns_.setSelectedIndex(1);
+ else
+ listPresetFilePatterns_.setSelectedIndex(2);
+ txtFilePattern_.setText(filePatterns);
+ manageFilePattern();
+ }
+
+ @UiField
+ TextBox txtSearchPattern_;
+ @UiField
+ CheckBox checkboxRegex_;
+ @UiField
+ CheckBox checkboxCaseSensitive_;
+ @UiField(provided = true)
+ DirectoryChooserTextBox dirChooser_;
+ @UiField
+ TextBox txtFilePattern_;
+ @UiField
+ ListBox listPresetFilePatterns_;
+ @UiField
+ DivElement divCustomFilter_;
+ private Widget mainWidget_;
+ private GlobalDisplay globalDisplay_ = RStudioGinjector.INSTANCE.getGlobalDisplay();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.ui.xml
new file mode 100644
index 0000000..0fd774f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindInFilesDialog.ui.xml
@@ -0,0 +1,56 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .panel input[type=checkbox] {
+ margin-left: 0;
+ }
+
+ .panel input:focus {
+ outline: none;
+ }
+
+ .pattern, .filePattern {
+ width: 375px;
+ }
+
+ .example {
+ color: #999;
+ }
+
+ .presetFilePatterns {
+ width: 100%;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.panel}">
+ <p>
+ <g:Label text="Find:"/>
+ <g:TextBox ui:field="txtSearchPattern_" styleName="{style.pattern}"/>
+ <br/>
+ <g:CheckBox ui:field="checkboxCaseSensitive_" text="Case sensitive"/>
+     
+ <g:CheckBox ui:field="checkboxRegex_" text="Regular expression"/>
+ </p>
+
+ <p>
+ <widget:DirectoryChooserTextBox ui:field="dirChooser_"/>
+ </p>
+
+ <p><g:Label text="Search these files:"/>
+ <g:ListBox ui:field="listPresetFilePatterns_"
+ styleName="{style.presetFilePatterns}">
+ <g:item value="">All Files</g:item>
+ <g:item value="*.r, *.R, *.rnw, *.Rnw, *.rmd, *.Rmd, *.rmarkdown, *.Rmarkdown, *.rhtml, *.Rhtml, *.rd, *.Rd, *.h, *.hpp, *.c, *.cpp">Common R source files (R, C/C++, Rnw, Rmd, Rhtml, Rd)</g:item>
+ <g:item value="custom">Custom Filter</g:item>
+ </g:ListBox>
+ <div ui:field="divCustomFilter_">
+ <g:TextBox ui:field="txtFilePattern_" styleName="{style.filePattern}"/>
+ <br/>
+ <span class="{style.example}">Example: *.R, *.r, *.csv. Separate multiple types with commas.</span>
+ </div>
+ </p>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutput.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutput.css
new file mode 100644
index 0000000..40ab5f6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutput.css
@@ -0,0 +1,56 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+.findOutput {
+ width: 100%;
+ height: 100%;
+}
+
+.findOutput tr {
+ height: 20px;
+}
+
+.findOutput .headerRow td {
+ font-weight: bold;
+ font-size: 12px !important;
+ padding: 2px 2px 2px 3px;
+}
+
+.line {
+ color: #777;
+ font-family: fixedWidthFont;
+ text-align: right;
+ padding-left: 12px;
+}
+
+.lineValue {
+ font-family: fixedWidthFont;
+ padding-right: 2px;
+ white-space: nowrap;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+.selectedRow {
+ background-color: #ccc;
+}
+*:focus .selectedRow {
+ background-color: rgb(146, 193, 240)
+}
+ at if user.agent ie8 {
+ .selectedRow {
+ background-color: rgb(146, 193, 240)
+ }
+}
+
+.overflowWarning {
+ font-weight: bold;
+ padding-left: 2px;
+ background-color: #900;
+ color: white;
+}
+
+.findOutput strong {
+ font-weight: normal;
+ color: #66A;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputCodec.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputCodec.java
new file mode 100644
index 0000000..1759671
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputCodec.java
@@ -0,0 +1,123 @@
+/*
+ * FindOutputCodec.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableRowElement;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.widget.HeaderBreaksItemCodec;
+import org.rstudio.studio.client.workbench.views.output.find.FindOutputResources.Styles;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;
+
+public class FindOutputCodec
+ extends HeaderBreaksItemCodec<FindResult, CodeNavigationTarget, Object>
+{
+ public FindOutputCodec(FindOutputResources resources)
+ {
+ styles_ = resources.styles();
+ }
+
+ @Override
+ public TableRowElement getRowForItem(FindResult entry)
+ {
+ if (entry == null)
+ {
+ // Overflow message
+ TableRowElement tr = Document.get().createTRElement();
+ TableCellElement td = Document.get().createTDElement();
+ td.setClassName(styles_.overflowWarning());
+ td.setColSpan(2);
+ td.setInnerText("More than 1000 matching lines were found. " +
+ "Only the first 1000 lines are shown.");
+ tr.appendChild(td);
+ return tr;
+ }
+
+ TableRowElement tr = Document.get().createTRElement();
+ tr.setAttribute(DATA_FILE, entry.getFile());
+ tr.setAttribute(DATA_LINE, entry.getLine() + "");
+
+ TableCellElement td1 = Document.get().createTDElement();
+ td1.setClassName(styles_.line());
+ td1.setInnerText(entry.getLine() + ":\u00A0");
+ tr.appendChild(td1);
+
+ TableCellElement td2 = Document.get().createTDElement();
+ td2.setClassName(styles_.lineValue());
+ td2.setInnerHTML(entry.getLineHTML().asString());
+ tr.appendChild(td2);
+
+ return tr;
+ }
+
+ @Override
+ protected boolean needsBreak(TableRowElement prevRow, TableRowElement row)
+ {
+ if (!row.hasAttribute(DATA_FILE))
+ return false;
+
+ return prevRow == null ||
+ !prevRow.getAttribute(DATA_FILE).equals(row.getAttribute(DATA_FILE));
+ }
+
+ @Override
+ protected int addBreak(TableRowElement row)
+ {
+ TableRowElement tr = Document.get().createTRElement();
+ tr.setClassName(styles_.headerRow());
+
+ TableCellElement td = Document.get().createTDElement();
+ td.setColSpan(2);
+ td.setInnerText(row.getAttribute(DATA_FILE));
+ tr.appendChild(td);
+
+ row.getParentElement().insertBefore(tr, row);
+ return 1;
+ }
+
+ @Override
+ public CodeNavigationTarget getOutputForRow(TableRowElement row)
+ {
+ String file = row.getAttribute(DATA_FILE);
+ int line = Integer.parseInt(row.getAttribute(DATA_LINE));
+
+ return new CodeNavigationTarget(file, FilePosition.create(line, 1));
+ }
+
+ @Override
+ public Object getOutputForRow2(TableRowElement row)
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isValueRow(TableRowElement row)
+ {
+ return row.hasAttribute(DATA_FILE);
+ }
+
+ @Override
+ public boolean hasNonValueRows()
+ {
+ return true;
+ }
+
+ private Styles styles_;
+
+ private static final String DATA_FILE = "data-file";
+ private static final String DATA_LINE = "data-line";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPane.java
new file mode 100644
index 0000000..33f04c6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPane.java
@@ -0,0 +1,274 @@
+/*
+ * FindOutputPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.core.client.widget.events.SelectionChangedHandler;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;
+
+import java.util.ArrayList;
+
+
+public class FindOutputPane extends WorkbenchPane
+ implements FindOutputPresenter.Display,
+ HasSelectionCommitHandlers<CodeNavigationTarget>
+{
+ @Inject
+ public FindOutputPane(Commands commands)
+ {
+ super("Find Results");
+ commands_ = commands;
+ ensureWidget();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ searchLabel_ = new Label();
+ toolbar.addLeftWidget(searchLabel_);
+
+ stopSearch_ = new ToolbarButton(
+ commands_.interruptR().getImageResource(),
+ (ClickHandler) null);
+ stopSearch_.setVisible(false);
+
+ toolbar.addRightWidget(stopSearch_);
+
+
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ context_ = new FindResultContext();
+
+ FindOutputResources resources = GWT.create(FindOutputResources.class);
+ resources.styles().ensureInjected();
+
+ table_ = new FastSelectTable<FindResult, CodeNavigationTarget, Object>(
+ new FindOutputCodec(resources),
+ resources.styles().selectedRow(),
+ true,
+ false);
+ FontSizer.applyNormalFontSize(table_);
+ table_.addStyleName(resources.styles().findOutput());
+ table_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT)
+ return;
+
+ if (dblClick_.checkForDoubleClick(event.getNativeEvent()))
+ fireSelectionCommitted();
+ }
+
+ private final DoubleClickState dblClick_ = new DoubleClickState();
+ });
+
+ table_.addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ fireSelectionCommitted();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ });
+
+ container_ = new SimplePanel();
+ container_.setSize("100%", "100%");
+ statusPanel_ = new StatusPanel();
+ statusPanel_.setSize("100%", "100%");
+ scrollPanel_ = new ScrollPanel(table_);
+ scrollPanel_.setSize("100%", "100%");
+ container_.setWidget(scrollPanel_);
+ return container_;
+ }
+
+ private void fireSelectionCommitted()
+ {
+ ArrayList<CodeNavigationTarget> values = table_.getSelectedValues();
+ if (values.size() == 1)
+ SelectionCommitEvent.fire(this, values.get(0));
+ }
+
+ @Override
+ public void addMatches(ArrayList<FindResult> findResults)
+ {
+ int matchesToAdd = Math.min(findResults.size(), MAX_COUNT - matchCount_);
+
+ if (matchesToAdd > 0)
+ {
+ matchCount_ += matchesToAdd;
+
+ if (matchCount_ > 0 && container_.getWidget() != scrollPanel_)
+ container_.setWidget(scrollPanel_);
+
+ table_.addItems(findResults.subList(0, matchesToAdd), false);
+ }
+
+ if (matchesToAdd != findResults.size())
+ showOverflow();
+ }
+
+ @Override
+ public void clearMatches()
+ {
+ context_.reset();
+ table_.clear();
+ overflow_ = false;
+ matchCount_ = 0;
+ statusPanel_.setStatusText("");
+ container_.setWidget(statusPanel_);
+ }
+
+ @Override
+ public void showSearchCompleted()
+ {
+ if (matchCount_ == 0)
+ statusPanel_.setStatusText("(No results found)");
+ }
+
+ @Override
+ public void ensureVisible(boolean activate)
+ {
+ fireEvent(new EnsureVisibleEvent(activate));
+ }
+
+ @Override
+ public HasClickHandlers getStopSearchButton()
+ {
+ return stopSearch_;
+ }
+
+ @Override
+ public void setStopSearchButtonVisible(boolean visible)
+ {
+ stopSearch_.setVisible(visible);
+ }
+
+ @Override
+ public void ensureSelectedRowIsVisible()
+ {
+ ArrayList<TableRowElement> rows = table_.getSelectedRows();
+ if (rows.size() > 0)
+ {
+ DomUtils.ensureVisibleVert(scrollPanel_.getElement(),
+ rows.get(0),
+ 20);
+ }
+ }
+
+ @Override
+ public HandlerRegistration addSelectionChangedHandler(SelectionChangedHandler handler)
+ {
+ return table_.addSelectionChangedHandler(handler);
+ }
+
+ @Override
+ public void showOverflow()
+ {
+ if (overflow_)
+ return;
+ overflow_ = true;
+ ArrayList<FindResult> items = new ArrayList<FindResult>();
+ items.add(null);
+ table_.addItems(items, false);
+ }
+
+ @Override
+ public void updateSearchLabel(String query, String path)
+ {
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ builder.appendEscaped("Results for ")
+ .appendHtmlConstant("<strong>")
+ .appendEscaped(query)
+ .appendHtmlConstant("</strong>")
+ .appendEscaped(" in ")
+ .appendEscaped(path);
+ searchLabel_.getElement().setInnerHTML(builder.toSafeHtml().asString());
+ }
+
+ @Override
+ public void clearSearchLabel()
+ {
+ searchLabel_.setText("");
+ }
+
+ @Override
+ public HandlerRegistration addSelectionCommitHandler(SelectionCommitHandler<CodeNavigationTarget> handler)
+ {
+ return addHandler(handler, SelectionCommitEvent.getType());
+ }
+
+ private class StatusPanel extends HorizontalCenterPanel
+ {
+ public StatusPanel()
+ {
+ super(new Label(), 50);
+ label_ = (Label)getWidget();
+
+ }
+
+ public void setStatusText(String status)
+ {
+ label_.setText(status);
+ }
+
+
+ private final Label label_;
+
+ }
+
+ private FastSelectTable<FindResult, CodeNavigationTarget, Object> table_;
+ private FindResultContext context_;
+ private final Commands commands_;
+ private Label searchLabel_;
+ private ToolbarButton stopSearch_;
+ private SimplePanel container_;
+ private ScrollPanel scrollPanel_;
+ private StatusPanel statusPanel_;
+ private boolean overflow_ = false;
+ private int matchCount_;
+
+ // This must be the same as MAX_COUNT in SessionFind.cpp
+ private static final int MAX_COUNT = 1000;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPresenter.java
new file mode 100644
index 0000000..dbe7763
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputPresenter.java
@@ -0,0 +1,292 @@
+/*
+ * FindOutputPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.events.HasEnsureHiddenHandlers;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.events.SelectionChangedEvent;
+import org.rstudio.core.client.widget.events.SelectionChangedHandler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindInFilesEvent;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindOperationEndedEvent;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindResultEvent;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesServerOperations;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesState;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;
+
+import java.util.ArrayList;
+
+public class FindOutputPresenter extends BasePresenter
+{
+ public interface Display extends WorkbenchView,
+ HasSelectionCommitHandlers<CodeNavigationTarget>,
+ HasEnsureHiddenHandlers
+ {
+ void addMatches(ArrayList<FindResult> findResults);
+ void clearMatches();
+ void ensureVisible(boolean activate);
+
+ HasClickHandlers getStopSearchButton();
+ void setStopSearchButtonVisible(boolean visible);
+
+ void ensureSelectedRowIsVisible();
+
+ HandlerRegistration addSelectionChangedHandler(SelectionChangedHandler handler);
+
+ void showOverflow();
+
+ void showSearchCompleted();
+
+ void updateSearchLabel(String query, String path);
+ void clearSearchLabel();
+ }
+
+ @Inject
+ public FindOutputPresenter(Display view,
+ EventBus events,
+ FindInFilesServerOperations server,
+ final FileTypeRegistry ftr,
+ Session session,
+ WorkbenchContext workbenchContext)
+ {
+ super(view);
+ view_ = view;
+ events_ = events;
+ server_ = server;
+ session_ = session;
+ workbenchContext_ = workbenchContext;
+
+ view_.addSelectionChangedHandler(new SelectionChangedHandler()
+ {
+ @Override
+ public void onSelectionChanged(SelectionChangedEvent e)
+ {
+ view_.ensureSelectedRowIsVisible();
+ }
+ });
+
+ view_.addSelectionCommitHandler(new SelectionCommitHandler<CodeNavigationTarget>()
+ {
+ @Override
+ public void onSelectionCommit(SelectionCommitEvent<CodeNavigationTarget> event)
+ {
+ CodeNavigationTarget target = event.getSelectedItem();
+ if (target == null)
+ return;
+
+ ftr.editFile(FileSystemItem.createFile(target.getFile()),
+ target.getPosition());
+ }
+ });
+
+ view_.getStopSearchButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ stop();
+ }
+ });
+
+ events_.addHandler(FindResultEvent.TYPE, new FindResultEvent.Handler()
+ {
+ @Override
+ public void onFindResult(FindResultEvent event)
+ {
+ if (!event.getHandle().equals(currentFindHandle_))
+ return;
+ view_.addMatches(event.getResults());
+ }
+ });
+
+ events_.addHandler(FindOperationEndedEvent.TYPE, new FindOperationEndedEvent.Handler()
+ {
+ @Override
+ public void onFindOperationEnded(
+ FindOperationEndedEvent event)
+ {
+ if (event.getHandle().equals(currentFindHandle_))
+ {
+ currentFindHandle_ = null;
+ view_.setStopSearchButtonVisible(false);
+ view_.showSearchCompleted();
+ }
+ }
+ });
+
+ new JSObjectStateValue(GROUP_FIND_IN_FILES, KEY_DIALOG_STATE,
+ ClientState.PROJECT_PERSISTENT,
+ session.getSessionInfo().getClientState(),
+ false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ dialogState_ = value == null
+ ? null
+ : value.<FindInFilesDialog.State>cast();
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ return dialogState_.cast();
+ }
+ };
+ }
+
+ public void initialize(FindInFilesState state)
+ {
+ view_.ensureVisible(false);
+
+ currentFindHandle_ = state.getHandle();
+ view_.addMatches(state.getResults().toArrayList());
+ updateSearchLabel(state.getInput(), state.getPath(), state.isRegex());
+
+ if (state.isRunning())
+ view_.setStopSearchButtonVisible(true);
+ else
+ events_.fireEvent(new FindOperationEndedEvent(state.getHandle()));
+ }
+
+ public void onFindInFiles(FindInFilesEvent event)
+ {
+ FindInFilesDialog dialog = new FindInFilesDialog(new OperationWithInput<FindInFilesDialog.State>()
+ {
+ @Override
+ public void execute(final FindInFilesDialog.State input)
+ {
+ dialogState_ = input;
+
+ stopAndClear();
+
+ FileSystemItem searchPath =
+ FileSystemItem.createDir(input.getPath());
+
+ JsArrayString filePatterns = JsArrayString.createArray().cast();
+ for (String pattern : input.getFilePatterns())
+ filePatterns.push(pattern);
+
+ server_.beginFind(input.getQuery(),
+ input.isRegex(),
+ !input.isCaseSensitive(),
+ searchPath,
+ filePatterns,
+ new SimpleRequestCallback<String>()
+ {
+ @Override
+ public void onResponseReceived(String handle)
+ {
+ currentFindHandle_ = handle;
+ updateSearchLabel(input.getQuery(),
+ input.getPath(),
+ input.isRegex());
+ view_.setStopSearchButtonVisible(true);
+
+ super.onResponseReceived(handle);
+
+ view_.ensureVisible(true);
+ }
+ });
+ }
+ });
+
+ if (!StringUtil.isNullOrEmpty(event.getSearchPattern()))
+ dialog.setSearchPattern(event.getSearchPattern());
+
+ if (dialogState_ == null)
+ {
+ dialog.setDirectory(
+ session_.getSessionInfo().getActiveProjectDir() != null ?
+ session_.getSessionInfo().getActiveProjectDir() :
+ workbenchContext_.getCurrentWorkingDir());
+ }
+ else
+ {
+ dialog.setState(dialogState_);
+ }
+
+ dialog.showModal();
+ }
+
+ public void onDismiss()
+ {
+ stopAndClear();
+ server_.clearFindResults(new VoidServerRequestCallback());
+ }
+
+ private void updateSearchLabel(String query, String path, boolean regex)
+ {
+ if (regex)
+ query = "/" + query + "/";
+ else
+ query = "\"" + query + "\"";
+
+ view_.updateSearchLabel(query, path);
+ }
+
+ private void stopAndClear()
+ {
+ stop();
+ view_.clearMatches();
+ view_.clearSearchLabel();
+ }
+
+ private void stop()
+ {
+ if (currentFindHandle_ != null)
+ {
+ server_.stopFind(currentFindHandle_,
+ new VoidServerRequestCallback());
+ currentFindHandle_ = null;
+ }
+ view_.setStopSearchButtonVisible(false);
+ }
+
+ private String currentFindHandle_;
+
+ private FindInFilesDialog.State dialogState_;
+
+ private final Display view_;
+ private final FindInFilesServerOperations server_;
+ private final Session session_;
+ private final WorkbenchContext workbenchContext_;
+ private EventBus events_;
+
+ private static final String GROUP_FIND_IN_FILES = "find-in-files";
+ private static final String KEY_DIALOG_STATE = "dialog-state";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputResources.java
new file mode 100644
index 0000000..95b5861
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputResources.java
@@ -0,0 +1,34 @@
+/*
+ * FindOutputResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+
+public interface FindOutputResources extends ClientBundle
+{
+ public interface Styles extends CssResource
+ {
+ String findOutput();
+ String headerRow();
+ String line();
+ String lineValue();
+ String selectedRow();
+ String overflowWarning();
+ }
+
+ @Source("FindOutput.css")
+ Styles styles();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputTab.java
new file mode 100644
index 0000000..11b584d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindOutputTab.java
@@ -0,0 +1,80 @@
+/*
+ * FindOutputTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.core.client.GWT;
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindInFilesEvent;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesState;
+
+public class FindOutputTab extends DelayLoadWorkbenchTab<FindOutputPresenter>
+{
+ public abstract static class Shim extends DelayLoadTabShim<FindOutputPresenter, FindOutputTab>
+ implements FindInFilesEvent.Handler
+ {
+ abstract void initialize(FindInFilesState state);
+ public abstract void onDismiss();
+ }
+
+ static interface Binder extends CommandBinder<Commands, Shim>
+ {}
+
+ @Inject
+ public FindOutputTab(final Shim shim,
+ EventBus events,
+ Commands commands,
+ final Session session)
+ {
+ super("Find in Files", shim);
+ shim_ = shim;
+
+ events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler()
+ {
+ @Override
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ FindInFilesState state =
+ session.getSessionInfo().getFindInFilesState();
+ if (state.isTabVisible())
+ shim.initialize(state);
+ }
+ });
+
+ GWT.<Binder>create(Binder.class).bind(commands, shim);
+
+ events.addHandler(FindInFilesEvent.TYPE, shim);
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return true;
+ }
+
+ public void onDismiss()
+ {
+ shim_.onDismiss();
+ }
+
+ private final Shim shim_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindResultContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindResultContext.java
new file mode 100644
index 0000000..d1f0406
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/FindResultContext.java
@@ -0,0 +1,174 @@
+/*
+ * FindResultContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find;
+
+import com.google.gwt.view.client.AbstractDataProvider;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.ProvidesKey;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;
+
+import java.util.HashMap;
+
+public class FindResultContext
+{
+ public class File
+ {
+ public File(String path)
+ {
+ path_ = path;
+ }
+
+ public String getPath()
+ {
+ return path_;
+ }
+
+ public void addMatch(int line, int column, String value)
+ {
+ int lineWidth = (line + "").length();
+ maxLineWidth_ = Math.max(lineWidth, maxLineWidth_);
+ matchData_.getList().add(new Match(this, line, column, value));
+ }
+
+ public int getCount()
+ {
+ return matchData_.getList().size();
+ }
+
+ public AbstractDataProvider<Match> getDataProvider()
+ {
+ return matchData_;
+ }
+
+ public void refresh()
+ {
+ matchData_.refresh();
+ }
+
+ public void clear()
+ {
+ matchData_.getList().clear();
+ }
+
+ private final String path_;
+ private final ListDataProvider<Match> matchData_ =
+ new ListDataProvider<Match>(new ProvidesKey<Match>()
+ {
+ @Override
+ public Object getKey(Match item)
+ {
+ return item.getParent().getPath() + ":" + item.getLine();
+ }
+ });
+ }
+
+ public class Match
+ {
+ public Match(File parent, int line, int column, String value)
+ {
+ parent_ = parent;
+ line_ = line;
+ column_ = column;
+ value_ = value;
+ }
+
+ public File getParent()
+ {
+ return parent_;
+ }
+
+ public int getLine()
+ {
+ return line_;
+ }
+
+ public int getColumn()
+ {
+ return column_;
+ }
+
+ public String getValue()
+ {
+ return value_;
+ }
+
+ private final File parent_;
+ private final int line_;
+ private final int column_;
+ private final String value_;
+ }
+
+ private File getFile(String path)
+ {
+ File file = filesByName_.get(path);
+ if (file == null)
+ {
+ file = new File(path);
+ data_.getList().add(file);
+ filesByName_.put(path, file);
+ }
+ return file;
+ }
+
+ public int getMaxLineWidth()
+ {
+ return maxLineWidth_;
+ }
+
+ public AbstractDataProvider<File> getDataProvider()
+ {
+ return data_;
+ }
+
+ public void reset()
+ {
+ data_.getList().clear();
+ filesByName_.clear();
+ maxLineWidth_ = 0;
+ }
+
+ public void addMatches(Iterable<FindResult> findResults)
+ {
+ int origMaxLineWidth = maxLineWidth_;
+
+ for (FindResult fr : findResults)
+ {
+ File file = getFile(fr.getFile());
+
+ file.addMatch(fr.getLine(), 0, fr.getLineValue());
+
+ int index = data_.getList().indexOf(file);
+ if (index >= 0) // not that we are expecting otherwise...
+ data_.getList().set(index, file);
+ }
+
+ if (maxLineWidth_ != origMaxLineWidth)
+ {
+ for (File aFile : data_.getList())
+ aFile.refresh();
+ }
+ }
+
+ private final ListDataProvider<File> data_ = new ListDataProvider<File>(new ProvidesKey<File>()
+ {
+ @Override
+ public Object getKey(File item)
+ {
+ return item.getPath();
+ }
+ });
+ private final HashMap<String, File> filesByName_ = new HashMap<String, File>();
+ private int maxLineWidth_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindInFilesEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindInFilesEvent.java
new file mode 100644
index 0000000..d0f945f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindInFilesEvent.java
@@ -0,0 +1,52 @@
+/*
+ * FindInFilesEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FindInFilesEvent extends GwtEvent<FindInFilesEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onFindInFiles(FindInFilesEvent event);
+ }
+
+ public FindInFilesEvent(String searchPattern)
+ {
+ searchPattern_ = searchPattern;
+ }
+
+ public String getSearchPattern()
+ {
+ return searchPattern_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onFindInFiles(this);
+ }
+
+ private final String searchPattern_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindOperationEndedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindOperationEndedEvent.java
new file mode 100644
index 0000000..ea7304c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindOperationEndedEvent.java
@@ -0,0 +1,52 @@
+/*
+ * FindOperationEndedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FindOperationEndedEvent extends GwtEvent<FindOperationEndedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onFindOperationEnded(FindOperationEndedEvent event);
+ }
+
+ public FindOperationEndedEvent(String handle)
+ {
+ handle_ = handle;
+ }
+
+ public String getHandle()
+ {
+ return handle_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onFindOperationEnded(this);
+ }
+
+ private final String handle_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindResultEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindResultEvent.java
new file mode 100644
index 0000000..22074ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/events/FindResultEvent.java
@@ -0,0 +1,79 @@
+/*
+ * FindResultEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;
+import java.util.ArrayList;
+
+public class FindResultEvent extends GwtEvent<FindResultEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onFindResult(FindResultEvent event);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+
+ public native final String getHandle() /*-{
+ return this.handle;
+ }-*/;
+
+ public native final RpcObjectList<FindResult> getResults() /*-{
+ return this.results;
+ }-*/;
+ }
+
+ public FindResultEvent(String handle, ArrayList<FindResult> results)
+ {
+ handle_ = handle;
+ results_ = results;
+ }
+
+ public String getHandle()
+ {
+ return handle_;
+ }
+
+ public ArrayList<FindResult> getResults()
+ {
+ return results_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onFindResult(this);
+ }
+
+ private final String handle_;
+ private final ArrayList<FindResult> results_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesServerOperations.java
new file mode 100644
index 0000000..44b4ac9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesServerOperations.java
@@ -0,0 +1,35 @@
+/*
+ * FindInFilesServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.model;
+
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+
+public interface FindInFilesServerOperations
+{
+ void beginFind(String searchString,
+ boolean regex,
+ boolean ignoreCase,
+ FileSystemItem directory,
+ JsArrayString filePatterns,
+ ServerRequestCallback<String> requestCallback);
+
+ void stopFind(String findOperationHandle,
+ ServerRequestCallback<Void> requestCallback);
+
+ void clearFindResults(ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesState.java
new file mode 100644
index 0000000..1c92f99
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindInFilesState.java
@@ -0,0 +1,56 @@
+/*
+ * FindInFilesState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+
+public class FindInFilesState extends JavaScriptObject
+{
+ protected FindInFilesState()
+ {
+ }
+
+ public final boolean isTabVisible()
+ {
+ return !StringUtil.isNullOrEmpty(getHandle());
+ }
+
+ public native final String getHandle() /*-{
+ return this.handle;
+ }-*/;
+
+
+ public native final RpcObjectList<FindResult> getResults() /*-{
+ return this.results;
+ }-*/;
+
+ public native final boolean isRunning() /*-{
+ return this.running;
+ }-*/;
+
+ public native final String getInput() /*-{
+ return this.input;
+ }-*/;
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final boolean isRegex() /*-{
+ return this.regex;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindResult.java
new file mode 100644
index 0000000..3ce3d2f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/find/model/FindResult.java
@@ -0,0 +1,125 @@
+/*
+ * FindResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.find.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import org.rstudio.core.client.Pair;
+
+import java.util.ArrayList;
+
+public class FindResult extends JavaScriptObject
+{
+ public static native FindResult create(String file,
+ int line,
+ String lineValue) /*-{
+ return ({
+ file: file,
+ line: line,
+ lineValue: lineValue
+ });
+ }-*/;
+
+ protected FindResult() {}
+
+ public native final String getFile() /*-{
+ return this.file;
+ }-*/;
+
+ public native final int getLine() /*-{
+ return this.line;
+ }-*/;
+
+ public native final String getLineValue() /*-{
+ return this.lineValue;
+ }-*/;
+
+ public final ArrayList<Integer> getMatchOns()
+ {
+ return getJavaArray("matchOn");
+ }
+
+ public final ArrayList<Integer> getMatchOffs()
+ {
+ return getJavaArray("matchOff");
+ }
+
+ public final SafeHtml getLineHTML()
+ {
+ SafeHtmlBuilder out = new SafeHtmlBuilder();
+
+ ArrayList<Integer> on = getMatchOns();
+ ArrayList<Integer> off = getMatchOffs();
+ ArrayList<Pair<Boolean, Integer>> parts
+ = new ArrayList<Pair<Boolean, Integer>>();
+ while (on.size() + off.size() > 0)
+ {
+ int onVal = on.size() == 0 ? Integer.MAX_VALUE : on.get(0);
+ int offVal = off.size() == 0 ? Integer.MAX_VALUE : off.get(0);
+ if (onVal <= offVal)
+ parts.add(new Pair<Boolean, Integer>(true, on.remove(0)));
+ else
+ parts.add(new Pair<Boolean, Integer>(false, off.remove(0)));
+ }
+
+ String line = getLineValue();
+
+ // Use a counter to ensure tags are balanced.
+ int openTags = 0;
+
+ for (int i = 0; i < line.length(); i++)
+ {
+ while (parts.size() > 0 && parts.get(0).second == i)
+ {
+ if (parts.remove(0).first)
+ {
+ out.appendHtmlConstant("<strong>");
+ openTags++;
+ }
+ else if (openTags > 0)
+ {
+ out.appendHtmlConstant("</strong>");
+ openTags--;
+ }
+ }
+ out.append(line.charAt(i));
+ }
+
+ while (openTags > 0)
+ {
+ openTags--;
+ out.appendHtmlConstant("</strong>");
+ }
+
+ return out.toSafeHtml();
+ }
+
+ private ArrayList<Integer> getJavaArray(String property)
+ {
+ JsArrayInteger array = getArray(property);
+ ArrayList<Integer> ints = new ArrayList<Integer>();
+ for (int i = 0; i < array.length(); i++)
+ ints.add(array.get(i));
+ return ints;
+ }
+
+ private native final JsArrayInteger getArray(String property) /*-{
+ if (this == null)
+ return [];
+ return this[property] || [];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPane.java
new file mode 100644
index 0000000..4cd9dac
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPane.java
@@ -0,0 +1,102 @@
+/*
+ * SourceCppOutputPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.sourcecpp;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.HasSelectionCommitHandlers;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+import org.rstudio.studio.client.common.compile.CompileOutputBufferWithHighlight;
+import org.rstudio.studio.client.common.compile.CompilePanel;
+import org.rstudio.studio.client.common.compile.errorlist.CompileErrorList;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.model.SourceCppState;
+
+public class SourceCppOutputPane extends WorkbenchPane
+ implements SourceCppOutputPresenter.Display
+{
+ @Inject
+ public SourceCppOutputPane()
+ {
+ super("Source Cpp");
+ compilePanel_ = new CompilePanel(new CompileOutputBufferWithHighlight());
+ ensureWidget();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return compilePanel_;
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+ fileLabel_ = new ToolbarFileLabel(toolbar, 200);
+ compilePanel_.connectToolbar(toolbar);
+ return toolbar;
+ }
+
+ @Override
+ public void ensureVisible(boolean activate)
+ {
+ fireEvent(new EnsureVisibleEvent(activate));
+ }
+
+ @Override
+ public void clearAll()
+ {
+ compilePanel_.clearAll();
+ }
+
+ @Override
+ public void showResults(SourceCppState state)
+ {
+ fileLabel_.setFileName(state.getTargetFile());
+
+ JsArray<CompileOutput> outputs = state.getOutputs();
+ for (int i=0; i<outputs.length(); i++)
+ compilePanel_.showOutput(outputs.get(i));
+
+ if (state.getErrors().length() > 0)
+ {
+ compilePanel_.showErrors(null,
+ state.getErrors(),
+ CompileErrorList.AUTO_SELECT_FIRST,
+ true);
+ }
+ }
+
+ @Override
+ public void scrollToBottom()
+ {
+ compilePanel_.scrollToBottom();
+ }
+
+
+ @Override
+ public HasSelectionCommitHandlers<CodeNavigationTarget> errorList()
+ {
+ return compilePanel_.errorList();
+ }
+
+ private ToolbarFileLabel fileLabel_;
+ private CompilePanel compilePanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPresenter.java
new file mode 100644
index 0000000..f092a72
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputPresenter.java
@@ -0,0 +1,123 @@
+/*
+ * SourceCppOutputPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.sourcecpp;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import org.rstudio.core.client.CodeNavigationTarget;
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppCompletedEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppStartedEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.model.SourceCppState;
+
+
+public class SourceCppOutputPresenter extends BasePresenter
+ implements SourceCppStartedEvent.Handler,
+ SourceCppCompletedEvent.Handler
+{
+ public interface Display extends WorkbenchView, HasEnsureHiddenHandlers
+ {
+ void ensureVisible(boolean activate);
+ void clearAll();
+ void scrollToBottom();
+ void showResults(SourceCppState state);
+ HasSelectionCommitHandlers<CodeNavigationTarget> errorList();
+ }
+
+ @Inject
+ public SourceCppOutputPresenter(Display view,
+ FileTypeRegistry fileTypeRegistry,
+ UIPrefs uiPrefs)
+ {
+ super(view);
+ view_ = view;
+ fileTypeRegistry_ = fileTypeRegistry;
+ uiPrefs_ = uiPrefs;
+
+ view_.errorList().addSelectionCommitHandler(
+ new SelectionCommitHandler<CodeNavigationTarget>() {
+
+ @Override
+ public void onSelectionCommit(
+ SelectionCommitEvent<CodeNavigationTarget> event)
+ {
+ CodeNavigationTarget target = event.getSelectedItem();
+ FileSystemItem fsi = FileSystemItem.createFile(target.getFile());
+ fileTypeRegistry_.editFile(fsi, target.getPosition());
+ }
+ });
+ }
+
+ @Override
+ public void onSourceCppStarted(SourceCppStartedEvent event)
+ {
+ view_.clearAll();
+ }
+
+ @Override
+ public void onSourceCppCompleted(SourceCppCompletedEvent event)
+ {
+ updateView(event.getState(), true);
+ }
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+ Scheduler.get().scheduleDeferred(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ view_.scrollToBottom();
+ }
+ });
+ }
+
+ private void updateView(SourceCppState state, boolean activate)
+ {
+ if (state.getErrors().length() > 0)
+ view_.ensureVisible(activate);
+
+ // show results
+ view_.showResults(state);
+
+ // navigate to the first error
+ if (uiPrefs_.navigateToBuildError().getValue())
+ {
+ CompileError error = CompileError.getFirstError(state.getErrors());
+ if (error != null)
+ {
+ fileTypeRegistry_.editFile(
+ FileSystemItem.createFile(error.getPath()),
+ FilePosition.create(error.getLine(), error.getColumn()),
+ true);
+ }
+ }
+
+ }
+
+ private final Display view_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final UIPrefs uiPrefs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputTab.java
new file mode 100644
index 0000000..29e8037
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/SourceCppOutputTab.java
@@ -0,0 +1,48 @@
+/*
+ * SourceCppOutputTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.sourcecpp;
+
+import com.google.inject.Inject;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppCompletedEvent;
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.events.SourceCppStartedEvent;
+
+public class SourceCppOutputTab extends DelayLoadWorkbenchTab<SourceCppOutputPresenter>
+{
+ public abstract static class Shim extends
+ DelayLoadTabShim<SourceCppOutputPresenter, SourceCppOutputTab>
+ implements SourceCppStartedEvent.Handler,
+ SourceCppCompletedEvent.Handler
+ {
+ }
+
+ @Inject
+ public SourceCppOutputTab(Shim shim, EventBus events)
+ {
+ super("Source Cpp", shim);
+ events.addHandler(SourceCppStartedEvent.TYPE, shim);
+ events.addHandler(SourceCppCompletedEvent.TYPE, shim);
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return true;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppCompletedEvent.java
new file mode 100644
index 0000000..0c82f85
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppCompletedEvent.java
@@ -0,0 +1,53 @@
+/*
+ * SourceCppCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.sourcecpp.events;
+
+import org.rstudio.studio.client.workbench.views.output.sourcecpp.model.SourceCppState;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceCppCompletedEvent extends GwtEvent<SourceCppCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSourceCppCompleted(SourceCppCompletedEvent event);
+ }
+
+ public SourceCppCompletedEvent(SourceCppState state)
+ {
+ state_ = state;
+ }
+
+ public SourceCppState getState()
+ {
+ return state_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSourceCppCompleted(this);
+ }
+
+ private final SourceCppState state_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppStartedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppStartedEvent.java
new file mode 100644
index 0000000..64ec49b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/events/SourceCppStartedEvent.java
@@ -0,0 +1,45 @@
+/*
+ * SourceCppStartedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.output.sourcecpp.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceCppStartedEvent extends GwtEvent<SourceCppStartedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSourceCppStarted(SourceCppStartedEvent event);
+ }
+
+ public SourceCppStartedEvent()
+ {
+ }
+
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSourceCppStarted(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/model/SourceCppState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/model/SourceCppState.java
new file mode 100644
index 0000000..c40a588
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/output/sourcecpp/model/SourceCppState.java
@@ -0,0 +1,41 @@
+/*
+ * SourceCppState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.output.sourcecpp.model;
+
+import org.rstudio.studio.client.common.compile.CompileError;
+import org.rstudio.studio.client.common.compile.CompileOutput;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class SourceCppState extends JavaScriptObject
+{
+ protected SourceCppState()
+ {
+ }
+
+ public final native String getTargetFile() /*-{
+ return this.target_file;
+ }-*/;
+
+ public final native JsArray<CompileOutput> getOutputs() /*-{
+ return this.outputs;
+ }-*/;
+
+ public final native JsArray<CompileError> getErrors() /*-{
+ return this.errors;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/Packages.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/Packages.java
new file mode 100644
index 0000000..74e937c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/Packages.java
@@ -0,0 +1,833 @@
+/*
+ * Packages.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.SuspendAndRestartEvent;
+import org.rstudio.studio.client.application.model.SuspendOptions;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror;
+import org.rstudio.studio.client.server.ServerDataSource;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.InstalledPackagesChangedEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.InstalledPackagesChangedHandler;
+import org.rstudio.studio.client.workbench.views.packages.events.LoadedPackageUpdatesEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.PackageStatusChangedEvent;
+import org.rstudio.studio.client.workbench.views.packages.events.PackageStatusChangedHandler;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInfo;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallOptions;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallRequest;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageStatus;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageUpdate;
+import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
+import org.rstudio.studio.client.workbench.views.packages.ui.CheckForUpdatesDialog;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public class Packages
+ extends BasePresenter
+ implements InstalledPackagesChangedHandler,
+ PackageStatusChangedHandler,
+ DeferredInitCompletedEvent.Handler,
+ PackagesDisplayObserver
+{
+ public interface Binder extends CommandBinder<Commands, Packages> {}
+
+ public interface Display extends WorkbenchView
+ {
+ void listPackages(List<PackageInfo> packagesDS);
+
+ void installPackage(PackageInstallContext installContext,
+ PackageInstallOptions defaultInstallOptions,
+ PackagesServerOperations server,
+ GlobalDisplay globalDisplay,
+ OperationWithInput<PackageInstallRequest> operation);
+
+ void setPackageStatus(PackageStatus status);
+
+ void setObserver(PackagesDisplayObserver observer) ;
+ void setProgress(boolean showProgress);
+ }
+
+ @Inject
+ public Packages(Display view,
+ EventBus events,
+ PackagesServerOperations server,
+ GlobalDisplay globalDisplay,
+ Session session,
+ Binder binder,
+ Commands commands,
+ WorkbenchContext workbenchContext,
+ DefaultCRANMirror defaultCRANMirror)
+ {
+ super(view);
+ view_ = view;
+ server_ = server;
+ globalDisplay_ = globalDisplay ;
+ view_.setObserver(this) ;
+ events_ = events ;
+ defaultCRANMirror_ = defaultCRANMirror;
+ workbenchContext_ = workbenchContext;
+ binder.bind(commands, this);
+
+ events.addHandler(InstalledPackagesChangedEvent.TYPE, this);
+ events.addHandler(PackageStatusChangedEvent.TYPE, this);
+ events.addHandler(DeferredInitCompletedEvent.TYPE, this);
+
+ // make the install options persistent
+ new JSObjectStateValue("packages-pane", "installOptions", ClientState.PROJECT_PERSISTENT,
+ session.getSessionInfo().getClientState(), false)
+ {
+ @Override
+ protected void onInit(JsObject value)
+ {
+ if (value != null)
+ installOptions_ = value.cast();
+ lastKnownState_ = installOptions_;
+ }
+
+ @Override
+ protected JsObject getValue()
+ {
+ return installOptions_.cast();
+ }
+
+ @Override
+ protected boolean hasChanged()
+ {
+ if (!PackageInstallOptions.areEqual(lastKnownState_, installOptions_))
+ {
+ lastKnownState_ = installOptions_;
+ return true;
+ }
+
+ return false;
+ }
+
+ private PackageInstallOptions lastKnownState_;
+ };
+
+ listPackages();
+ }
+
+ void onInstallPackage()
+ {
+ withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){
+
+ @Override
+ public void execute(final PackageInstallContext installContext)
+ {
+ if (installContext.isDefaultLibraryWriteable())
+ {
+ continueInstallPackage(installContext);
+ }
+ else
+ {
+ globalDisplay_.showYesNoMessage(MessageDialog.QUESTION,
+ "Create Package Library",
+ "Would you like to create a personal library '" +
+ installContext.getDefaultUserLibraryPath() + "' " +
+ "to install packages into?",
+ false,
+ new Operation() // Yes operation
+ {
+ @Override
+ public void execute()
+ {
+ ProgressIndicator indicator =
+ globalDisplay_.getProgressIndicator(
+ "Error Creating Library");
+ server_.initDefaultUserLibrary(
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ protected void onSuccess()
+ {
+ // call this function back recursively
+ // so we can retrieve the updated
+ // PackageInstallContext from the server
+ onInstallPackage();
+ }
+ });
+ }
+ },
+ new Operation() // No operation
+ {
+ @Override
+ public void execute()
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.WARNING,
+ "Install Packages",
+ "Unable to install packages (default library '" +
+ installContext.getDefaultLibraryPath() + "' is " +
+ "not writeable)");
+
+ }
+ },
+ true);
+ }
+ }
+
+ });
+ }
+
+
+ private void continueInstallPackage(
+ final PackageInstallContext installContext)
+ {
+ // if CRAN needs to be configured then do it
+ if (!installContext.isCRANMirrorConfigured())
+ {
+ defaultCRANMirror_.configure(new Command() {
+ public void execute()
+ {
+ doInstallPackage(installContext);
+ }
+ });
+ }
+ else
+ {
+ doInstallPackage(installContext);
+ }
+ }
+
+ private void doInstallPackage(final PackageInstallContext installContext)
+ {
+ // if install options have not yet initialized the default library
+ // path then set it now from the context
+ if (StringUtil.isNullOrEmpty(installOptions_.getLibraryPath()))
+ {
+ installOptions_ = PackageInstallOptions.create(
+ installOptions_.getInstallFromRepository(),
+ installContext.getDefaultLibraryPath(),
+ installOptions_.getInstallDependencies());
+ }
+
+ view_.installPackage(
+ installContext,
+ installOptions_,
+ server_,
+ globalDisplay_,
+ new OperationWithInput<PackageInstallRequest>()
+ {
+ public void execute(PackageInstallRequest request)
+ {
+ installOptions_ = request.getOptions();
+
+ boolean usingDefaultLibrary =
+ request.getOptions().getLibraryPath().equals(
+ installContext.getDefaultLibraryPath());
+
+ StringBuilder command = new StringBuilder();
+ command.append("install.packages(");
+
+ List<String> packages = request.getPackages();
+ if (packages != null)
+ {
+ if (packages.size() > 1)
+ command.append("c(");
+ for (int i=0; i<packages.size(); i++)
+ {
+ if (i > 0)
+ command.append(", ");
+ command.append("\"");
+ command.append(packages.get(i));
+ command.append("\"");
+ }
+ if (packages.size() > 1)
+ command.append(")");
+
+ // dependencies
+ if (!request.getOptions().getInstallDependencies())
+ command.append(", dependencies = FALSE");
+ }
+ // must be a local package
+ else
+ {
+ // get path
+ FileSystemItem localPackage = request.getLocalPackage();
+
+ // convert to string
+ String path = localPackage.getPath();
+
+ // append command
+ command.append("\"" + path + "\", repos = NULL");
+
+ // append type = source if needed
+ if (path.endsWith(".tar.gz"))
+ command.append(", type = \"source\"");
+ }
+
+ if (!usingDefaultLibrary)
+ {
+ command.append(", lib=\"");
+ command.append(request.getOptions().getLibraryPath());
+ command.append("\"");
+ }
+
+ command.append(")");
+ String cmd = command.toString();
+ executeWithLoadedPackageCheck(new InstallCommand(packages, cmd));
+ }
+ });
+ }
+
+
+ void onUpdatePackages()
+ {
+ withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){
+
+ @Override
+ public void execute(final PackageInstallContext installContext)
+ {
+ // if there are no writeable library paths then we just
+ // short circuit to all packages are up to date message
+ if (installContext.getWriteableLibraryPaths().length() == 0)
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "Check for Updates",
+ "All packages are up to date.");
+
+ }
+
+ // if CRAN needs to be configured then do it
+ else if (!installContext.isCRANMirrorConfigured())
+ {
+ defaultCRANMirror_.configure(new Command() {
+ public void execute()
+ {
+ doUpdatePackages(installContext);
+ }
+ });
+ }
+
+ // otherwise we are good to go!
+ else
+ {
+ doUpdatePackages(installContext);
+ }
+ }
+
+ });
+ }
+
+ private void doUpdatePackages(final PackageInstallContext installContext)
+ {
+ new CheckForUpdatesDialog(
+ globalDisplay_,
+ new ServerDataSource<JsArray<PackageUpdate>>() {
+ public void requestData(
+ ServerRequestCallback<JsArray<PackageUpdate>> requestCallback)
+ {
+ server_.checkForPackageUpdates(requestCallback);
+ }
+ },
+ new OperationWithInput<ArrayList<PackageUpdate>>() {
+ @Override
+ public void execute(ArrayList<PackageUpdate> updates)
+ {
+ InstallCommand cmd = buildUpdatePackagesCommand(updates,
+ installContext);
+ executeWithLoadedPackageCheck(cmd);
+ }
+ },
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ // cancel emits an empty console input line to clear
+ // the busy indicator
+ events_.fireEvent(new SendToConsoleEvent("", true));
+ }
+ }).showModal();
+ }
+
+
+ private InstallCommand buildUpdatePackagesCommand(
+ ArrayList<PackageUpdate> updates,
+ final PackageInstallContext installContext)
+ {
+ // split the updates into their respective target libraries
+ List<String> packages = new ArrayList<String>();
+ LinkedHashMap<String, ArrayList<PackageUpdate>> updatesByLibPath =
+ new LinkedHashMap<String, ArrayList<PackageUpdate>>();
+ for (PackageUpdate update : updates)
+ {
+ // auto-create target list if necessary
+ String libPath = update.getLibPath();
+ if (!updatesByLibPath.containsKey(libPath))
+ updatesByLibPath.put(libPath, new ArrayList<PackageUpdate>());
+
+ // insert into list
+ updatesByLibPath.get(libPath).add(update);
+
+ // track global list of packages
+ packages.add(update.getPackageName());
+ }
+
+ // generate an install packages command for each targeted library
+ StringBuilder command = new StringBuilder();
+ for (String libPath : updatesByLibPath.keySet())
+ {
+ if (command.length() > 0)
+ command.append("\n");
+
+ ArrayList<PackageUpdate> libPathUpdates = updatesByLibPath.get(libPath);
+ command.append("install.packages(");
+ if (libPathUpdates.size() > 1)
+ command.append("c(");
+ for (int i=0; i<libPathUpdates.size(); i++)
+ {
+ PackageUpdate update = libPathUpdates.get(i);
+ if (i > 0)
+ command.append(", ");
+ command.append("\"");
+ command.append(update.getPackageName());
+ command.append("\"");
+ }
+ if (libPathUpdates.size() > 1)
+ command.append(")");
+
+ if (!libPath.equals(installContext.getDefaultLibraryPath()))
+ {
+ command.append(", lib=\"");
+ command.append(libPath);
+ command.append("\"");
+ }
+
+ command.append(")");
+
+ }
+
+ return new InstallCommand(packages, command.toString());
+ }
+
+
+ @Handler
+ public void onRefreshPackages()
+ {
+ listPackages();
+ }
+
+ public void removePackage(final PackageInfo packageInfo)
+ {
+ withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){
+
+ @Override
+ public void execute(final PackageInstallContext installContext)
+ {
+ final boolean usingDefaultLibrary = packageInfo.getLibrary().equals(
+ installContext.getDefaultLibraryPath());
+
+ StringBuilder message = new StringBuilder();
+ message.append("Are you sure you wish to permanently uninstall the '");
+ message.append(packageInfo.getName() + "' package");
+ if (!usingDefaultLibrary)
+ {
+ message.append(" from library '");
+ message.append(packageInfo.getLibrary());
+ message.append("'");
+ }
+ message.append("? This action cannot be undone.");
+
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Uninstall Package ",
+ message.toString(),
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ StringBuilder command = new StringBuilder();
+ command.append("remove.packages(\"");
+ command.append(packageInfo.getName());
+ command.append("\"");
+ if (!usingDefaultLibrary)
+ {
+ command.append(", lib=\"");
+ command.append(packageInfo.getLibrary());
+ command.append("\"");
+ }
+ command.append(")");
+ String cmd = command.toString();
+ events_.fireEvent(new SendToConsoleEvent(cmd, true));
+ }
+ },
+ true);
+ }
+ });
+ }
+
+ public void listPackages()
+ {
+ view_.setProgress(true);
+ server_.listPackages(
+ new SimpleRequestCallback<JsArray<PackageInfo>>("Error Listing Packages")
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ // don't show errors during restart
+ if (!workbenchContext_.isRestartInProgress())
+ super.onError(error);
+
+ view_.setProgress(false);
+ }
+
+ @Override
+ public void onResponseReceived(JsArray<PackageInfo> response)
+ {
+ // sort the packages
+ allPackages_ = new ArrayList<PackageInfo>();
+ for (int i=0; i<response.length(); i++)
+ allPackages_.add(response.get(i));
+ Collections.sort(allPackages_, new Comparator<PackageInfo>() {
+ public int compare(PackageInfo o1, PackageInfo o2)
+ {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+
+ view_.setProgress(false);
+ setViewPackageList();
+ }
+ });
+ }
+
+ public void loadPackage(final String packageName, final String libName)
+ {
+ // check status to make sure the package was unloaded
+ checkPackageStatusOnNextConsolePrompt(packageName, libName);
+
+ // send the command
+ StringBuilder command = new StringBuilder();
+ command.append("library(\"");
+ command.append(packageName);
+ command.append("\"");
+ command.append(", lib.loc=\"");
+ command.append(libName);
+ command.append("\"");
+ command.append(")");
+ events_.fireEvent(new SendToConsoleEvent(command.toString(), true));
+
+ }
+
+ public void unloadPackage(String packageName, String libName)
+ {
+ // check status to make sure the package was unloaded
+ checkPackageStatusOnNextConsolePrompt(packageName, libName);
+
+ StringBuilder command = new StringBuilder();
+ command.append("detach(\"package:");
+ command.append(packageName);
+ command.append("\", unload=TRUE)");
+ events_.fireEvent(new SendToConsoleEvent(command.toString(), true));
+ }
+
+ public void showHelp(PackageInfo packageInfo)
+ {
+ events_.fireEvent(new ShowHelpEvent(packageInfo.getUrl())) ;
+ }
+
+
+ public void onInstalledPackagesChanged(InstalledPackagesChangedEvent event)
+ {
+ listPackages() ;
+ }
+
+ @Override
+ public void onDeferredInitCompleted(DeferredInitCompletedEvent event)
+ {
+ listPackages();
+ }
+
+ public void onPackageFilterChanged(String filter)
+ {
+ packageFilter_ = filter.toLowerCase();
+ setViewPackageList();
+ }
+
+ public void onPackageStatusChanged(PackageStatusChangedEvent event)
+ {
+ PackageStatus status = event.getPackageStatus();
+ view_.setPackageStatus(status);
+
+ // also update the list of allPackages_
+ for (int i = 0; i<allPackages_.size(); i++)
+ {
+ PackageInfo packageInfo = allPackages_.get(i);
+ if (packageInfo.getName().equals(status.getName()) &&
+ packageInfo.getLibrary().equals(status.getLib()))
+ {
+ allPackages_.set(i, status.isLoaded() ? packageInfo.asLoaded() :
+ packageInfo.asUnloaded());
+ }
+ }
+ }
+
+ private void setViewPackageList()
+ {
+ ArrayList<PackageInfo> packages = null; ;
+
+ // apply filter (if any)
+ if (packageFilter_.length() > 0)
+ {
+ packages = new ArrayList<PackageInfo>();
+
+ // first do prefix search
+ for (PackageInfo pkgInfo : allPackages_)
+ {
+ if (pkgInfo.getName().toLowerCase().startsWith(packageFilter_))
+ packages.add(pkgInfo);
+ }
+
+ // then do contains search on name & desc
+ for (PackageInfo pkgInfo : allPackages_)
+ {
+ if (pkgInfo.getName().toLowerCase().contains(packageFilter_) ||
+ pkgInfo.getDesc().toLowerCase().contains(packageFilter_))
+ {
+ if (!packages.contains(pkgInfo))
+ packages.add(pkgInfo);
+ }
+ }
+ }
+ else
+ {
+ packages = allPackages_;
+ }
+
+ view_.listPackages(packages);
+ }
+
+ private void checkPackageStatusOnNextConsolePrompt(
+ final String packageName,
+ final String libName)
+ {
+ // remove any existing handler
+ removeConsolePromptHandler();
+
+ consolePromptHandlerReg_ = events_.addHandler(ConsolePromptEvent.TYPE,
+ new ConsolePromptHandler() {
+ @Override
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ // remove handler so it is only called once
+ removeConsolePromptHandler();
+
+ // check status and set it
+ server_.isPackageLoaded(
+ packageName,
+ libName,
+ new ServerRequestCallback<Boolean>() {
+ @Override
+ public void onResponseReceived(Boolean status)
+ {
+ PackageStatus pkgStatus = PackageStatus.create(packageName,
+ libName,
+ status);
+ view_.setPackageStatus(pkgStatus);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // ignore errors
+ }
+ });
+ }
+ });
+ }
+
+ private void removeConsolePromptHandler()
+ {
+ if (consolePromptHandlerReg_ != null)
+ {
+ consolePromptHandlerReg_.removeHandler();
+ consolePromptHandlerReg_ = null;
+ }
+ }
+
+ private void withPackageInstallContext(
+ final OperationWithInput<PackageInstallContext> operation)
+ {
+ final ProgressIndicator indicator =
+ globalDisplay_.getProgressIndicator("Error");
+ indicator.onProgress("Retrieving package installation context...");
+
+ server_.getPackageInstallContext(
+ new SimpleRequestCallback<PackageInstallContext>() {
+
+ @Override
+ public void onResponseReceived(PackageInstallContext context)
+ {
+ indicator.onCompleted();
+ operation.execute(context);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+ public void onLoadedPackageUpdates(LoadedPackageUpdatesEvent event)
+ {
+ restartForInstallWithConfirmation(event.getInstallCmd());
+ }
+
+ private class InstallCommand
+ {
+ public InstallCommand(List<String> packages, String cmd)
+ {
+ this.packages = packages;
+ this.cmd = cmd;
+ }
+ public final List<String> packages;
+ public final String cmd;
+ }
+
+ private void executeWithLoadedPackageCheck(final InstallCommand command)
+ {
+ // check if we are potentially going to be overwriting an
+ // already installed package. if so then prompt for restart
+ if ((command.packages != null))
+ {
+ server_.loadedPackageUpdatesRequired(
+ command.packages,
+ new ServerRequestCallback<Boolean>() {
+
+ @Override
+ public void onResponseReceived(Boolean required)
+ {
+ if (required)
+ {
+ restartForInstallWithConfirmation(command.cmd);
+ }
+ else
+ {
+ executePkgCommand(command.cmd);
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ executePkgCommand(command.cmd);
+ }
+
+ });
+ }
+ else
+ {
+ executePkgCommand(command.cmd);
+ }
+ }
+
+ private void executePkgCommand(String cmd)
+ {
+ events_.fireEvent(new SendToConsoleEvent(cmd, true));
+ }
+
+ private void restartForInstallWithConfirmation(final String installCmd)
+ {
+ String msg = "One or more of the packages that will be updated by this " +
+ "installation are currently loaded. Restarting R prior " +
+ "to updating these packages is strongly recommended.\n\n" +
+ "RStudio can restart R and then automatically continue " +
+ "the installation after restarting (all work and " +
+ "data will be preserved during the restart).\n\n" +
+ "Do you want to restart R prior to installing?";
+
+ final boolean haveInstallCmd = installCmd.startsWith("install.packages");
+
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Updating Loaded Packages",
+ msg,
+ true,
+ new Operation() { public void execute()
+ {
+ events_.fireEvent(new SuspendAndRestartEvent(
+ SuspendOptions.createSaveAll(true), installCmd));
+
+ }},
+ new Operation() { public void execute()
+ {
+ server_.ignoreNextLoadedPackageCheck(
+ new VoidServerRequestCallback() {
+ @Override
+ public void onSuccess()
+ {
+ if (haveInstallCmd)
+ executePkgCommand(installCmd);
+ }
+ });
+ }},
+ true);
+ }
+
+
+ private final Display view_;
+ private final PackagesServerOperations server_;
+ private ArrayList<PackageInfo> allPackages_ = new ArrayList<PackageInfo>();
+ private String packageFilter_ = new String();
+ private HandlerRegistration consolePromptHandlerReg_ = null;
+ private final EventBus events_ ;
+ private final GlobalDisplay globalDisplay_ ;
+ private final WorkbenchContext workbenchContext_;
+ private final DefaultCRANMirror defaultCRANMirror_;
+ private PackageInstallOptions installOptions_ =
+ PackageInstallOptions.create(true, "", true);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesDisplayObserver.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesDisplayObserver.java
new file mode 100644
index 0000000..333bcb3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesDisplayObserver.java
@@ -0,0 +1,27 @@
+/*
+ * PackagesDisplayObserver.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages;
+
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInfo;
+
+public interface PackagesDisplayObserver
+{
+ void listPackages() ;
+ void loadPackage(String pkgName, String libName) ;
+ void unloadPackage(String pkgName, String libName) ;
+ void showHelp(PackageInfo packageInfo) ;
+ void removePackage(PackageInfo packageInfo);
+ void onPackageFilterChanged(String filter);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesPane.java
new file mode 100644
index 0000000..046b27c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesPane.java
@@ -0,0 +1,307 @@
+/*
+ * PackagesPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages;
+
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.NoSelectionModel;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.cellview.ImageButtonColumn;
+import org.rstudio.core.client.cellview.LinkColumn;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.SearchWidget;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInfo;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallOptions;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallRequest;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageStatus;
+import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
+import org.rstudio.studio.client.workbench.views.packages.ui.InstallPackageDialog;
+import org.rstudio.studio.client.workbench.views.packages.ui.PackagesCellTableResources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PackagesPane extends WorkbenchPane implements Packages.Display
+{
+ @Inject
+ public PackagesPane(Commands commands)
+ {
+ super("Packages");
+ commands_ = commands;
+
+ ensureWidget();
+ }
+
+ public void setObserver(PackagesDisplayObserver observer)
+ {
+ observer_ = observer ;
+ }
+
+ public void listPackages(List<PackageInfo> packages)
+ {
+ packagesTable_.setPageSize(packages.size());
+ packagesDataProvider_.setList(packages);
+ }
+
+ public void installPackage(PackageInstallContext installContext,
+ PackageInstallOptions defaultInstallOptions,
+ PackagesServerOperations server,
+ GlobalDisplay globalDisplay,
+ OperationWithInput<PackageInstallRequest> operation)
+ {
+ new InstallPackageDialog(installContext,
+ defaultInstallOptions,
+ server,
+ globalDisplay,
+ operation).showModal();
+ }
+
+ public void setPackageStatus(PackageStatus status)
+ {
+ int row = packageRow(status.getName(), status.getLib()) ;
+
+ if (row != -1)
+ {
+ List<PackageInfo> packages = packagesDataProvider_.getList();
+
+ packages.set(row, status.isLoaded() ? packages.get(row).asLoaded() :
+ packages.get(row).asUnloaded());
+ }
+
+ // go through any duplicates to reconcile their status
+ List<PackageInfo> packages = packagesDataProvider_.getList();
+ for (int i=0; i<packages.size(); i++)
+ {
+ if (packages.get(i).getName().equals(status.getName()) &&
+ i != row)
+ {
+ packages.set(i, packages.get(i).asUnloaded());
+ }
+ }
+ }
+
+ private int packageRow(String packageName, String packageLib)
+ {
+ // if we haven't retreived packages yet then return not found
+ if (packagesDataProvider_ == null)
+ return -1;
+
+ List<PackageInfo> packages = packagesDataProvider_.getList();
+
+ // figure out which row of the table includes this package
+ int row = -1;
+ for (int i=0; i<packages.size(); i++)
+ {
+ PackageInfo packageInfo = packages.get(i);
+ if (packageInfo.getName().equals(packageName) &&
+ packageInfo.getLibrary().equals(packageLib))
+ {
+ row = i ;
+ break;
+ }
+ }
+ return row ;
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ toolbar.addLeftWidget(commands_.installPackage().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.updatePackages().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.refreshPackages().createToolbarButton());
+
+ searchWidget_ = new SearchWidget(new SuggestOracle() {
+ @Override
+ public void requestSuggestions(Request request, Callback callback)
+ {
+ // no suggestions
+ callback.onSuggestionsReady(
+ request,
+ new Response(new ArrayList<Suggestion>()));
+ }
+ });
+ searchWidget_.addValueChangeHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ observer_.onPackageFilterChanged(event.getValue().trim());
+ }
+ });
+ toolbar.addRightWidget(searchWidget_);
+
+ return toolbar;
+ }
+
+ private class VersionCell extends AbstractCell<PackageInfo>
+ {
+ @Override
+ public void render(Context context, PackageInfo value, SafeHtmlBuilder sb)
+ {
+ sb.appendHtmlConstant("<div title=\"");
+ sb.appendEscaped(value.getLibrary());
+ sb.appendHtmlConstant("\"");
+ sb.appendHtmlConstant(" class=\"");
+ sb.appendEscaped(ThemeStyles.INSTANCE.adornedText());
+ sb.appendHtmlConstant("\"");
+ sb.appendHtmlConstant(">");
+ sb.appendEscaped(value.getVersion());
+ sb.appendHtmlConstant("</div>");
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ packagesDataProvider_ = new ListDataProvider<PackageInfo>();
+
+ packagesTable_ = new CellTable<PackageInfo>(
+ 15,
+ PackagesCellTableResources.INSTANCE);
+ packagesTable_.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+ packagesTable_.setSelectionModel(new NoSelectionModel<PackageInfo>());
+ packagesTable_.setWidth("100%", false);
+
+ LoadedColumn loadedColumn = new LoadedColumn();
+ packagesTable_.addColumn(loadedColumn);
+
+ NameColumn nameColumn = new NameColumn();
+ packagesTable_.addColumn(nameColumn);
+
+ TextColumn<PackageInfo> descColumn = new TextColumn<PackageInfo>() {
+ public String getValue(PackageInfo packageInfo)
+ {
+ return packageInfo.getDesc();
+ }
+ };
+
+ packagesTable_.addColumn(descColumn);
+
+ Column<PackageInfo, PackageInfo> versionColumn =
+ new Column<PackageInfo, PackageInfo>(new VersionCell()) {
+
+ @Override
+ public PackageInfo getValue(PackageInfo object)
+ {
+ return object;
+ }
+ };
+
+ packagesTable_.addColumn(versionColumn);
+
+ ImageButtonColumn<PackageInfo> removeColumn =
+ new ImageButtonColumn<PackageInfo>(
+ AbstractImagePrototype.create(ThemeResources.INSTANCE.removePackage()),
+ new OperationWithInput<PackageInfo>() {
+ public void execute(PackageInfo packageInfo)
+ {
+ observer_.removePackage(packageInfo);
+ }
+ },
+ "Remove package");
+ packagesTable_.addColumn(removeColumn);
+ packagesTable_.setColumnWidth(removeColumn, 30, Unit.PX);
+
+
+ packagesDataProvider_.addDataDisplay(packagesTable_);
+
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setWidget(packagesTable_);
+ return scrollPanel;
+ }
+
+
+ class LoadedColumn extends Column<PackageInfo, Boolean>
+ {
+ public LoadedColumn()
+ {
+ super(new CheckboxCell(false, false));
+
+ setFieldUpdater(new FieldUpdater<PackageInfo,Boolean>() {
+ public void update(int index, PackageInfo packageInfo, Boolean value)
+ {
+ if (value.booleanValue())
+ observer_.loadPackage(packageInfo.getName(),
+ packageInfo.getLibrary()) ;
+ else
+ observer_.unloadPackage(packageInfo.getName(),
+ packageInfo.getLibrary()) ;
+
+ }
+ });
+ }
+
+ @Override
+ public Boolean getValue(PackageInfo packageInfo)
+ {
+ return packageInfo.isLoaded();
+ }
+
+ }
+
+ // package name column which includes a hyperlink to package docs
+ class NameColumn extends LinkColumn<PackageInfo>
+ {
+ public NameColumn()
+ {
+ super(packagesDataProvider_,
+ new OperationWithInput<PackageInfo>()
+ {
+ @Override
+ public void execute(PackageInfo packageInfo)
+ {
+ observer_.showHelp(packageInfo);
+ }
+ },
+ true);
+ }
+
+ @Override
+ public String getValue(PackageInfo packageInfo)
+ {
+ return packageInfo.getName();
+ }
+ }
+
+
+
+ private CellTable<PackageInfo> packagesTable_;
+ private ListDataProvider<PackageInfo> packagesDataProvider_;
+ private SearchWidget searchWidget_;
+ private PackagesDisplayObserver observer_ ;
+ private final Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesTab.java
new file mode 100644
index 0000000..22664df
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/PackagesTab.java
@@ -0,0 +1,67 @@
+/*
+ * PackagesTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages;
+
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.packages.events.LoadedPackageUpdatesEvent;
+
+public class PackagesTab extends DelayLoadWorkbenchTab<Packages>
+{
+ public interface Binder extends CommandBinder<Commands, Shim> {}
+
+ public abstract static class Shim
+ extends DelayLoadTabShim<Packages, PackagesTab>
+ implements LoadedPackageUpdatesEvent.Handler
+ {
+ @Handler
+ public abstract void onInstallPackage();
+ @Handler
+ public abstract void onUpdatePackages();
+ }
+
+ @Inject
+ public PackagesTab(Shim shim,
+ Binder binder,
+ EventBus events,
+ Commands commands,
+ UIPrefs uiPrefs,
+ Session session)
+ {
+ super("Packages", shim);
+ binder.bind(commands, shim);
+ events.addHandler(LoadedPackageUpdatesEvent.TYPE, shim);
+ uiPrefs_ = uiPrefs;
+ session_ = session;
+ }
+
+ @Override
+ public boolean isSuppressed()
+ {
+ return session_.getSessionInfo().getDisablePackages() ||
+ !uiPrefs_.packagesPaneEnabled().getValue();
+ }
+
+ private final UIPrefs uiPrefs_;
+ private final Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedEvent.java
new file mode 100644
index 0000000..5465926
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedEvent.java
@@ -0,0 +1,41 @@
+/*
+ * InstalledPackagesChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class InstalledPackagesChangedEvent extends
+ GwtEvent<InstalledPackagesChangedHandler>
+{
+ public static final GwtEvent.Type<InstalledPackagesChangedHandler> TYPE =
+ new GwtEvent.Type<InstalledPackagesChangedHandler>();
+
+ public InstalledPackagesChangedEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(InstalledPackagesChangedHandler handler)
+ {
+ handler.onInstalledPackagesChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<InstalledPackagesChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedHandler.java
new file mode 100644
index 0000000..922ab80
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/InstalledPackagesChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * InstalledPackagesChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface InstalledPackagesChangedHandler extends EventHandler
+{
+ void onInstalledPackagesChanged(InstalledPackagesChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/LoadedPackageUpdatesEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/LoadedPackageUpdatesEvent.java
new file mode 100644
index 0000000..3045d89
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/LoadedPackageUpdatesEvent.java
@@ -0,0 +1,52 @@
+/*
+ * LoadedPackageUpdatesEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class LoadedPackageUpdatesEvent extends GwtEvent<LoadedPackageUpdatesEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onLoadedPackageUpdates(LoadedPackageUpdatesEvent event);
+ }
+
+ public LoadedPackageUpdatesEvent(String installCmd)
+ {
+ installCmd_ = installCmd;
+ }
+
+ public String getInstallCmd()
+ {
+ return installCmd_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onLoadedPackageUpdates(this);
+ }
+
+ private final String installCmd_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedEvent.java
new file mode 100644
index 0000000..e034a30
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedEvent.java
@@ -0,0 +1,49 @@
+/*
+ * PackageStatusChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageStatus;
+
+public class PackageStatusChangedEvent
+ extends GwtEvent<PackageStatusChangedHandler>
+{
+ public static final GwtEvent.Type<PackageStatusChangedHandler> TYPE =
+ new GwtEvent.Type<PackageStatusChangedHandler>();
+
+ public PackageStatusChangedEvent(PackageStatus packageStatus)
+ {
+ packageStatus_ = packageStatus ;
+ }
+
+ public PackageStatus getPackageStatus()
+ {
+ return packageStatus_;
+ }
+
+ @Override
+ protected void dispatch(PackageStatusChangedHandler handler)
+ {
+ handler.onPackageStatusChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<PackageStatusChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private PackageStatus packageStatus_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedHandler.java
new file mode 100644
index 0000000..705cbf1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/events/PackageStatusChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * PackageStatusChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface PackageStatusChangedHandler extends EventHandler
+{
+ void onPackageStatusChanged(PackageStatusChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInfo.java
new file mode 100644
index 0000000..01c053b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInfo.java
@@ -0,0 +1,71 @@
+/*
+ * PackageInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PackageInfo extends JavaScriptObject
+{
+ protected PackageInfo()
+ {
+ }
+
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getLibrary() /*-{
+ return this.library;
+ }-*/;
+
+ public final native String getVersion() /*-{
+ return this.version;
+ }-*/;
+
+ public final native String getDesc() /*-{
+ return this.desc;
+ }-*/;
+
+ public final native String getUrl() /*-{
+ return this.url;
+ }-*/;
+
+ public final native boolean isLoaded() /*-{
+ return this.loaded;
+ }-*/;
+
+ public final PackageInfo asLoaded()
+ {
+ return asLoadedState(true);
+ }
+
+ public final PackageInfo asUnloaded()
+ {
+ return asLoadedState(false);
+ }
+
+ private final native PackageInfo asLoadedState(boolean loaded) /*-{
+ var packageInfo = new Object();
+ packageInfo.name = this.name;
+ packageInfo.library = this.library;
+ packageInfo.version = this.version;
+ packageInfo.desc = this.desc;
+ packageInfo.url = this.url;
+ packageInfo.loaded = loaded;
+ return packageInfo;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallContext.java
new file mode 100644
index 0000000..c86bafd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallContext.java
@@ -0,0 +1,57 @@
+/*
+ * PackageInstallContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class PackageInstallContext extends JavaScriptObject
+{
+ protected PackageInstallContext()
+ {
+ }
+
+ public final native boolean isCRANMirrorConfigured() /*-{
+ return this.cranMirrorConfigured[0];
+ }-*/;
+
+ public final native JsArrayString selectedRepositoryNames() /*-{
+ return this.selectedRepositoryNames;
+ }-*/;
+
+ public final native String packageArchiveExtension() /*-{
+ return this.packageArchiveExtension[0];
+ }-*/;
+
+ public final native String getDefaultLibraryPath() /*-{
+ return this.defaultLibraryPath[0];
+ }-*/;
+
+ public final native boolean isDefaultLibraryWriteable() /*-{
+ return this.defaultLibraryWriteable[0];
+ }-*/;
+
+ public final native JsArrayString getWriteableLibraryPaths() /*-{
+ return this.writeableLibraryPaths;
+ }-*/;
+
+ public final native String getDefaultUserLibraryPath() /*-{
+ return this.defaultUserLibraryPath[0];
+ }-*/;
+
+ public final native boolean isDevModeOn() /*-{
+ return this.devModeOn[0];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallOptions.java
new file mode 100644
index 0000000..289c69e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallOptions.java
@@ -0,0 +1,65 @@
+/*
+ * PackageInstallOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PackageInstallOptions extends JavaScriptObject
+{
+ protected PackageInstallOptions()
+ {
+
+ }
+
+ public static final native PackageInstallOptions create(
+ boolean installFromRepository,
+ String libraryPath,
+ boolean installDependencies) /*-{
+ var options = new Object();
+ options.installFromRepository = installFromRepository;
+ options.libraryPath = libraryPath ;
+ options.installDependencies = installDependencies ;
+ return options ;
+ }-*/;
+
+
+ public final native boolean getInstallFromRepository() /*-{
+ if (typeof this.installFromRepository != 'undefined')
+ return this.installFromRepository;
+ else
+ return true;
+ }-*/;
+
+ public final native String getLibraryPath() /*-{
+ return this.libraryPath;
+ }-*/;
+
+ public final native boolean getInstallDependencies() /*-{
+ return this.installDependencies;
+ }-*/;
+
+ public static native boolean areEqual(PackageInstallOptions a,
+ PackageInstallOptions b) /*-{
+ if (a === null ^ b === null)
+ return false;
+ if (a === null)
+ return true;
+ return a.libraryPath === b.libraryPath &&
+ a.installDependencies === b.installDependencies &&
+ a.installFromRepository === b.installFromRepository;
+ }-*/;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallRequest.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallRequest.java
new file mode 100644
index 0000000..dd104d2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageInstallRequest.java
@@ -0,0 +1,65 @@
+/*
+ * PackageInstallRequest.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import java.util.List;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class PackageInstallRequest
+{
+ public PackageInstallRequest(List<String> packages,
+ PackageInstallOptions options)
+ {
+ this(packages, null, options);
+ }
+
+ public PackageInstallRequest(FileSystemItem localPackage,
+ PackageInstallOptions options)
+ {
+ this(null, localPackage, options);
+ }
+
+ private PackageInstallRequest(List<String> packages,
+ FileSystemItem localPackage,
+ PackageInstallOptions options)
+ {
+ packages_ = packages;
+ localPackage_ = localPackage;
+ options_ = options;
+ }
+
+ public List<String> getPackages()
+ {
+ return packages_;
+ }
+
+ public FileSystemItem getLocalPackage()
+ {
+ return localPackage_;
+ }
+
+ public PackageInstallOptions getOptions()
+ {
+ return options_;
+ }
+
+
+ private final List<String> packages_;
+ private final FileSystemItem localPackage_;
+ private final PackageInstallOptions options_;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageStatus.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageStatus.java
new file mode 100644
index 0000000..6d89575
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageStatus.java
@@ -0,0 +1,68 @@
+/*
+ * PackageStatus.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+
+
+public class PackageStatus extends JavaScriptObject
+{
+ protected PackageStatus()
+ {
+ }
+
+ public static final native PackageStatus create(String name,
+ String lib,
+ boolean loaded) /*-{
+ var status = new Object();
+ status.name = name ;
+ status.lib = lib ;
+ status.loaded = loaded;
+ return status ;
+ }-*/;
+
+ public final native String getName() /*-{
+ return this.name[0];
+ }-*/;
+
+ public final String getLib()
+ {
+ String lib = getLibNative();
+ if (lib != null)
+ {
+ return lib;
+ }
+ else
+ {
+ FileSystemItem path = FileSystemItem.createDir(getPathNative());
+ return path.getParentPathString();
+ }
+ }
+
+ public final native boolean isLoaded() /*-{
+ return this.loaded[0];
+ }-*/;
+
+ private final native String getPathNative() /*-{
+ return this.path[0];
+ }-*/;
+
+ private final native String getLibNative() /*-{
+ return this.lib;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageUpdate.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageUpdate.java
new file mode 100644
index 0000000..2e762ad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackageUpdate.java
@@ -0,0 +1,46 @@
+/*
+ * PackageUpdate.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PackageUpdate extends JavaScriptObject
+{
+ protected PackageUpdate()
+ {
+ }
+
+ public final native String getPackageName() /*-{
+ return this.packageName;
+ }-*/;
+
+ public final native String getLibPath() /*-{
+ return this.libPath;
+ }-*/;
+
+ public final native String getInstalled() /*-{
+ return this.installed;
+ }-*/;
+
+ public final native String getAvailable() /*-{
+ return this.available;
+ }-*/;
+
+
+
+ public final native String getNewsUrl() /*-{
+ return this.newsUrl;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackagesServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackagesServerOperations.java
new file mode 100644
index 0000000..27e69af
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/model/PackagesServerOperations.java
@@ -0,0 +1,53 @@
+/*
+ * PackagesServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.model;
+
+import java.util.List;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
+
+public interface PackagesServerOperations
+{
+ // list installed packages
+ void listPackages(
+ ServerRequestCallback<JsArray<PackageInfo>> requestCallback);
+
+ void availablePackages(
+ String repository,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void isPackageLoaded(String packageName, String libName,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void checkForPackageUpdates(
+ ServerRequestCallback<JsArray<PackageUpdate>> requestCallback);
+
+ void getPackageInstallContext(
+ ServerRequestCallback<PackageInstallContext> requestCallback);
+
+ void initDefaultUserLibrary(ServerRequestCallback<Void> requestCallback);
+
+ void loadedPackageUpdatesRequired(
+ List<String> packages,
+ ServerRequestCallback<Boolean> requestCallback);
+
+ void ignoreNextLoadedPackageCheck(
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.css
new file mode 100644
index 0000000..b16efd1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.css
@@ -0,0 +1,8 @@
+
+
+.mainWidget {
+ width: 450px;
+ height: 300px;
+ border: 1px solid #BBB;
+ background-color: white;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.java
new file mode 100644
index 0000000..4adb487
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/CheckForUpdatesDialog.java
@@ -0,0 +1,307 @@
+/*
+ * CheckForUpdatesDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.cellview.ImageButtonColumn;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerDataSource;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageUpdate;
+
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.NoSelectionModel;
+
+public class CheckForUpdatesDialog extends ModalDialog<ArrayList<PackageUpdate>>
+{
+ public CheckForUpdatesDialog(
+ GlobalDisplay globalDisplay,
+ ServerDataSource<JsArray<PackageUpdate>> updatesDS,
+ OperationWithInput<ArrayList<PackageUpdate>> checkOperation,
+ Operation cancelOperation)
+ {
+ super("Update Packages", checkOperation, cancelOperation);
+ globalDisplay_ = globalDisplay;
+ updatesDS_ = updatesDS;
+
+ setOkButtonCaption("Install Updates");
+
+ addLeftButton(selectAllButton_ = new ThemedButton("Select All",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ setGlobalApplyUpdate(true);
+ }
+ }));
+
+ addLeftButton(selectNoneButton_ = new ThemedButton("Select None",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ setGlobalApplyUpdate(false);
+ }
+ }));
+
+ enableOkButton(false);
+ enableCancelButton(false);
+ selectAllButton_.setEnabled(false);
+ selectNoneButton_.setEnabled(false);
+ }
+
+ @Override
+ protected ArrayList<PackageUpdate> collectInput()
+ {
+ ArrayList<PackageUpdate> updates = new ArrayList<PackageUpdate>();
+ for (PendingUpdate update : updatesDataProvider_.getList())
+ {
+ if (update.getApplyUpdate())
+ updates.add(update.getUpdateInfo());
+ }
+ return updates;
+ }
+
+ @Override
+ protected boolean validate(ArrayList<PackageUpdate> input)
+ {
+ return input.size() > 0;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ updatesTable_ = new CellTable<PendingUpdate>(
+ 15,
+ GWT.<PackagesCellTableResources> create(PackagesCellTableResources.class));
+ updatesTable_.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+ updatesTable_.setSelectionModel(new NoSelectionModel<PendingUpdate>());
+ updatesTable_.setWidth("100%", true);
+
+ UpdateColumn updateColumn = new UpdateColumn();
+ updatesTable_.addColumn(updateColumn);
+ updatesTable_.setColumnWidth(updateColumn, 30, Unit.PX);
+
+
+ TextColumn<PendingUpdate> nameColumn = new TextColumn<PendingUpdate>() {
+ public String getValue(PendingUpdate update)
+ {
+ return update.getUpdateInfo().getPackageName();
+ }
+ };
+ updatesTable_.addColumn(nameColumn, "Package");
+ updatesTable_.setColumnWidth(nameColumn, 28, Unit.PCT);
+
+ TextColumn<PendingUpdate> installedColumn = new TextColumn<PendingUpdate>() {
+ public String getValue(PendingUpdate update)
+ {
+ return update.getUpdateInfo().getInstalled();
+ }
+ };
+ updatesTable_.addColumn(installedColumn, "Installed");
+ updatesTable_.setColumnWidth(installedColumn, 28, Unit.PCT);
+
+ TextColumn<PendingUpdate> availableColumn = new TextColumn<PendingUpdate>() {
+ public String getValue(PendingUpdate update)
+ {
+ return update.getUpdateInfo().getAvailable();
+ }
+ };
+ updatesTable_.addColumn(availableColumn, "Available");
+ updatesTable_.setColumnWidth(availableColumn, 28, Unit.PCT);
+
+ ImageButtonColumn<PendingUpdate> newsColumn =
+ new ImageButtonColumn<PendingUpdate>(
+ AbstractImagePrototype.create(ThemeResources.INSTANCE.newsButton()),
+ new OperationWithInput<PendingUpdate>() {
+ public void execute(PendingUpdate update)
+ {
+ GlobalDisplay.NewWindowOptions options =
+ new GlobalDisplay.NewWindowOptions();
+ options.setName("_rstudio_package_news");
+ globalDisplay_.openWindow(update.getUpdateInfo().getNewsUrl(),
+ options);
+ }
+ },
+ "Show package NEWS")
+ {
+ @Override
+ protected boolean showButton(PendingUpdate update)
+ {
+ return !StringUtil.isNullOrEmpty(
+ update.getUpdateInfo().getNewsUrl());
+ }
+ };
+ updatesTable_.addColumn(newsColumn, "NEWS");
+ updatesTable_.setColumnWidth(newsColumn, 16, Unit.PCT);
+
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setStylePrimaryName(RESOURCES.styles().mainWidget());
+ scrollPanel.setWidget(updatesTable_);
+
+ // query for updates
+ updatesDS_.requestData(new SimpleRequestCallback<JsArray<PackageUpdate>>() {
+
+ @Override
+ public void onResponseReceived(JsArray<PackageUpdate> packageUpdates)
+ {
+ if (packageUpdates.length() > 0)
+ {
+ ArrayList<PendingUpdate> updates = new ArrayList<PendingUpdate>();
+ for (int i=0; i<packageUpdates.length(); i++)
+ updates.add(new PendingUpdate(packageUpdates.get(i), false));
+ updatesTable_.setPageSize(updates.size());
+ updatesDataProvider_ = new ListDataProvider<PendingUpdate>();
+ updatesDataProvider_.setList(updates);
+ updatesDataProvider_.addDataDisplay(updatesTable_);
+
+ enableCancelButton(true);
+ selectAllButton_.setEnabled(true);
+ selectNoneButton_.setEnabled(true);
+ }
+ else
+ {
+ closeDialog();
+ globalDisplay_.showMessage(
+ MessageDialog.INFO,
+ "Check for Updates",
+ "All packages are up to date.");
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ closeDialog();
+ super.onError(error);
+ }
+ });
+
+ return scrollPanel;
+ }
+
+ class UpdateColumn extends Column<PendingUpdate, Boolean>
+ {
+ public UpdateColumn()
+ {
+ super(new CheckboxCell(false, false));
+
+ setFieldUpdater(new FieldUpdater<PendingUpdate,Boolean>() {
+ public void update(int index, PendingUpdate update, Boolean value)
+ {
+ List<PendingUpdate> updates = updatesDataProvider_.getList();
+ updates.set(updates.indexOf(update),
+ new PendingUpdate(update.getUpdateInfo(), value));
+ manageUIState();
+ }
+ });
+ }
+
+ @Override
+ public Boolean getValue(PendingUpdate update)
+ {
+ return update.getApplyUpdate();
+ }
+ }
+
+ private class PendingUpdate
+ {
+ public PendingUpdate(PackageUpdate updateInfo, boolean applyUpdate)
+ {
+ updateInfo_ = updateInfo;
+ applyUpdate_ = applyUpdate;
+ }
+
+ public PackageUpdate getUpdateInfo()
+ {
+ return updateInfo_;
+ }
+
+ public boolean getApplyUpdate()
+ {
+ return applyUpdate_;
+ }
+
+ private final PackageUpdate updateInfo_;
+ private final boolean applyUpdate_;
+ }
+
+ private void setGlobalApplyUpdate(Boolean applyUpdate)
+ {
+ List<PendingUpdate> updates = updatesDataProvider_.getList();
+ ArrayList<PendingUpdate> newUpdates = new ArrayList<PendingUpdate>();
+ for(PendingUpdate update : updates)
+ newUpdates.add(new PendingUpdate(update.getUpdateInfo(), applyUpdate));
+ updatesDataProvider_.setList(newUpdates);
+ manageUIState();
+ }
+
+ private void manageUIState()
+ {
+ enableOkButton(collectInput().size() > 0);
+ }
+
+ static interface Styles extends CssResource
+ {
+ String mainWidget();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("CheckForUpdatesDialog.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources) GWT.create(Resources.class);
+
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private CellTable<PendingUpdate> updatesTable_;
+ private ServerDataSource<JsArray<PackageUpdate>> updatesDS_;
+ private ListDataProvider<PendingUpdate> updatesDataProvider_;
+ private ThemedButton selectAllButton_;
+ private ThemedButton selectNoneButton_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.css
new file mode 100644
index 0000000..7bdd675
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.css
@@ -0,0 +1,46 @@
+
+
+.mainWidget {
+ width: 360px;
+}
+
+.packageSourceListBox {
+ width: 100%;
+}
+
+.packageSourcePanel {
+ width: 99%;
+ height: 50px;
+}
+
+.configureRepositoriesImage {
+ margin-right: 4px;
+ margin-bottom: -4px;
+}
+
+.configureRepositoriesLink {
+ font-size: 0.8em;
+ margin-top: 2px;
+}
+
+.packagesLabel {
+ margin-bottom: 3px;
+}
+
+.packageFileTextBox {
+ margin-top: 0px;
+ margin-right: 5px;
+ width: 290px;
+}
+
+.packageFileBrowseButton {
+
+}
+
+.extraBottomPad {
+ margin-bottom: 15px;
+}
+
+.installDependenciesCheckBox {
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.java
new file mode 100644
index 0000000..5130652
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/InstallPackageDialog.java
@@ -0,0 +1,412 @@
+/*
+ * InstallPackageDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.ui;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.CaptionWithHelp;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.MultipleItemSuggestTextBox;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.TextBoxWithButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallOptions;
+import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallRequest;
+import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+
+public class InstallPackageDialog extends ModalDialog<PackageInstallRequest>
+{
+ public InstallPackageDialog(
+ PackageInstallContext installContext,
+ PackageInstallOptions defaultInstallOptions,
+ PackagesServerOperations server,
+ GlobalDisplay globalDisplay,
+ OperationWithInput<PackageInstallRequest> operation)
+{
+ super("Install Packages", operation);
+
+ installContext_ = installContext;
+ defaultInstallOptions_ = defaultInstallOptions;
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+
+ setOkButtonCaption("Install");
+}
+
+
+ @Override
+ protected PackageInstallRequest collectInput()
+ {
+ // package install options
+ String libraryPath = installContext_.getWriteableLibraryPaths().get(
+ libraryListBox_.getSelectedIndex());
+ boolean installDependencies = installDependenciesCheckBox_.getValue();
+ PackageInstallOptions options = PackageInstallOptions.create(
+ installFromRepository(),
+ libraryPath,
+ installDependencies);
+
+ if (installFromRepository())
+ {
+ return new PackageInstallRequest(packagesTextBox_.getItems(), options);
+ }
+ else
+ {
+ return new PackageInstallRequest(archiveFilePath_, options);
+ }
+ }
+
+
+ @Override
+ protected boolean validate(PackageInstallRequest request)
+ {
+ // check for package name
+ if (installFromRepository() && (request.getPackages().size() == 0) ||
+ !installFromRepository() && request.getLocalPackage() == null)
+ {
+ globalDisplay_.showErrorMessage(
+ "No Package Selected",
+ "You must specify the package to install.",
+ getPackageInputWidget());
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ // vertical panel
+ VerticalPanel mainPanel = new VerticalPanel();
+ mainPanel.setSpacing(2);
+ mainPanel.setStylePrimaryName(RESOURCES.styles().mainWidget());
+
+ // source type
+ reposCaption_ = new CaptionWithHelp("Install from:",
+ "Configuring Repositories",
+ "configuring_repositories");
+ reposCaption_.setIncludeVersionInfo(false);
+ reposCaption_.setWidth("100%");
+ mainPanel.add(reposCaption_);
+
+ packageSourceListBox_ = new ListBox();
+ packageSourceListBox_.setStylePrimaryName(
+ RESOURCES.styles().packageSourceListBox());
+ packageSourceListBox_.addStyleName(RESOURCES.styles().extraBottomPad());
+ JsArrayString repos = installContext_.selectedRepositoryNames();
+ if (repos.length() == 1)
+ {
+ packageSourceListBox_.addItem("Repository (" + repos.get(0) + ")");
+ }
+ else
+ {
+ StringBuilder reposItem = new StringBuilder();
+ reposItem.append("Repository (");
+ for (int i=0; i<repos.length(); i++)
+ {
+ if (i != 0)
+ reposItem.append(", ");
+ reposItem.append(repos.get(i));
+ }
+ reposItem.append(")");
+ packageSourceListBox_.addItem(reposItem.toString());
+ }
+ packageSourceListBox_.addItem("Package Archive File (" +
+ installContext_.packageArchiveExtension() +
+ ")");
+ mainPanel.add(packageSourceListBox_);
+
+ // source panel container
+ sourcePanel_ = new SimplePanel();
+ sourcePanel_.setStylePrimaryName(RESOURCES.styles().packageSourcePanel());
+
+ // repos source panel
+ reposSourcePanel_ = new FlowPanel();
+ Label packagesLabel = new Label(
+ "Packages (separate multiple with space or comma):");
+ packagesLabel.setStylePrimaryName(RESOURCES.styles().packagesLabel());
+ reposSourcePanel_.add(packagesLabel);
+
+ packagesTextBox_ = new MultipleItemSuggestTextBox();
+ packagesSuggestBox_ = new SuggestBox(new PackageOracle(),
+ packagesTextBox_);
+ packagesSuggestBox_.setWidth("100%");
+ packagesSuggestBox_.setLimit(20);
+ packagesSuggestBox_.addStyleName(RESOURCES.styles().extraBottomPad());
+ reposSourcePanel_.add(packagesSuggestBox_);
+ sourcePanel_.setWidget(reposSourcePanel_);
+ mainPanel.add(sourcePanel_);
+
+ // archive source panel
+ packageArchiveFile_ = new TextBoxWithButton(
+ "Package archive:",
+ "Browse...",
+ browseForArchiveClickHandler_);
+
+ // create check box here because manageUIState accesses it
+ installDependenciesCheckBox_ = new CheckBox();
+
+ if (defaultInstallOptions_.getInstallFromRepository())
+ packageSourceListBox_.setSelectedIndex(0);
+ else
+ packageSourceListBox_.setSelectedIndex(1);
+ manageUIState();
+
+ packageSourceListBox_.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ manageUIState();
+
+ if (!installFromRepository())
+ packageArchiveFile_.click();
+ }
+ });
+
+
+ mainPanel.add(new Label("Install to Library:"));
+
+ // library list box
+ libraryListBox_ = new ListBox();
+ libraryListBox_.setWidth("100%");
+ libraryListBox_.addStyleName(RESOURCES.styles().extraBottomPad());
+ JsArrayString libPaths = installContext_.getWriteableLibraryPaths();
+ int selectedIndex = 0;
+ for (int i=0; i<libPaths.length(); i++)
+ {
+ String libPath = libPaths.get(i);
+
+ if (!installContext_.isDevModeOn())
+ {
+ if (defaultInstallOptions_.getLibraryPath().equals(libPath))
+ selectedIndex = i;
+ }
+
+ if (libPath.equals(installContext_.getDefaultLibraryPath()))
+ libPath = libPath + " [Default]";
+
+ libraryListBox_.addItem(libPath);
+
+ }
+ libraryListBox_.setSelectedIndex(selectedIndex);
+ mainPanel.add(libraryListBox_);
+
+ // install dependencies check box
+ installDependenciesCheckBox_.addStyleName(RESOURCES.styles().installDependenciesCheckBox());
+ installDependenciesCheckBox_.setText("Install dependencies");
+ installDependenciesCheckBox_.setValue(
+ defaultInstallOptions_.getInstallDependencies());
+ mainPanel.add(installDependenciesCheckBox_);
+
+ mainPanel.add(new HTML("<br/>"));
+
+ return mainPanel;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ if (installFromRepository())
+ FocusHelper.setFocusDeferred(packagesSuggestBox_);
+ else
+ FocusHelper.setFocusDeferred(packageSourceListBox_);
+ }
+
+ private void manageUIState()
+ {
+ if (installFromRepository())
+ {
+ reposCaption_.setHelpVisible(true);
+ sourcePanel_.setWidget(reposSourcePanel_);
+ FocusHelper.setFocusDeferred(packagesSuggestBox_);
+ installDependenciesCheckBox_.setVisible(true);
+ }
+ else
+ {
+ reposCaption_.setHelpVisible(false);
+ sourcePanel_.setWidget(packageArchiveFile_);
+ FocusHelper.setFocusDeferred(packageArchiveFile_);
+ installDependenciesCheckBox_.setVisible(false);
+ }
+ }
+
+ private boolean installFromRepository()
+ {
+ return packageSourceListBox_.getSelectedIndex() == 0;
+ }
+
+ private CanFocus getPackageInputWidget()
+ {
+ if (installFromRepository())
+ return new CanFocus() {
+ @Override
+ public void focus()
+ {
+ packagesSuggestBox_.setFocus(true);
+
+ }};
+ else
+ return packageArchiveFile_;
+ }
+
+ private ClickHandler browseForArchiveClickHandler_ = new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.openFile(
+ "Select Package Archive",
+ fileSystemContext_,
+ defaultArchiveDir_,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+
+ if (input == null)
+ return;
+
+ // update default archive dir
+ defaultArchiveDir_ = input.getParentPath();
+
+ // set archive file path
+ archiveFilePath_ = input;
+
+ // update UI
+ packageArchiveFile_.setText(
+ StringUtil.shortPathName(input, "gwt-TextBox", 280));
+ }
+ });
+
+ }
+
+ };
+
+ private class PackageOracle extends MultiWordSuggestOracle
+ {
+ PackageOracle()
+ {
+ // no separators (strict prefix match)
+ super("");
+
+ server_.availablePackages(null,
+ new ServerRequestCallback<JsArrayString>() {
+ @Override
+ public void onResponseReceived(JsArrayString packages)
+ {
+ for (int i=0; i<packages.length(); i++)
+ add(packages.get(i));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.log("Error querying for packages: " +
+ error.getUserMessage());
+ }
+ });
+ }
+ }
+
+ static interface Styles extends CssResource
+ {
+ String mainWidget();
+ String packageSourcePanel();
+ String configureRepositoriesImage();
+ String configureRepositoriesLink();
+ String packagesLabel();
+ String extraBottomPad();
+ String installDependenciesCheckBox();
+ String packageSourceListBox();
+ String packageFileTextBox();
+ String packageFileBrowseButton();
+ }
+
+ static interface Resources extends ClientBundle
+ {
+ @Source("InstallPackageDialog.css")
+ Styles styles();
+ }
+
+ static Resources RESOURCES = (Resources)GWT.create(Resources.class) ;
+ public static void ensureStylesInjected()
+ {
+ RESOURCES.styles().ensureInjected();
+ }
+
+ private final PackageInstallContext installContext_;
+ private final PackageInstallOptions defaultInstallOptions_;
+ private final PackagesServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+
+ private CaptionWithHelp reposCaption_;
+ private ListBox packageSourceListBox_;
+
+ private SimplePanel sourcePanel_;
+ private FlowPanel reposSourcePanel_;
+ private TextBoxWithButton packageArchiveFile_ = null;
+
+ private MultipleItemSuggestTextBox packagesTextBox_ = null;
+ private SuggestBox packagesSuggestBox_ = null;
+ private ListBox libraryListBox_ = null;
+ private CheckBox installDependenciesCheckBox_ = null;
+
+ FileSystemItem archiveFilePath_ = null;
+
+ private static FileSystemItem defaultArchiveDir_ = FileSystemItem.home();
+
+ private final FileSystemContext fileSystemContext_ =
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext();
+
+ private final FileDialogs fileDialogs_ =
+ RStudioGinjector.INSTANCE.getFileDialogs();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableResources.java
new file mode 100644
index 0000000..9913665
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableResources.java
@@ -0,0 +1,33 @@
+/*
+ * PackagesCellTableResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.packages.ui;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.cellview.client.CellTable;
+import org.rstudio.core.client.theme.RStudioCellTableStyle;
+
+public interface PackagesCellTableResources extends CellTable.Resources
+{
+ static PackagesCellTableResources INSTANCE =
+ (PackagesCellTableResources)GWT.create(PackagesCellTableResources.class) ;
+
+ interface PackagesCellTableStyle extends CellTable.Style
+ {
+ }
+
+ @Source({RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS, "PackagesCellTableStyle.css"})
+ PackagesCellTableStyle cellTableStyle();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableStyle.css
new file mode 100644
index 0000000..a74068a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/packages/ui/PackagesCellTableStyle.css
@@ -0,0 +1,22 @@
+ at def selectionBorderWidth 2px;
+
+.cellTableCell input[type=checkbox] {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.cellTableEvenRow {
+ background: #ffffff;
+}
+
+.cellTableEvenRowCell {
+ border: selectionBorderWidth solid #ffffff;
+}
+
+.cellTableOddRow {
+ background-color: #eef4fb;
+}
+
+.cellTableOddRowCell {
+ border: selectionBorderWidth solid #eef4fb;;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Locator.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Locator.java
new file mode 100644
index 0000000..50a7994
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Locator.java
@@ -0,0 +1,103 @@
+/*
+ * Locator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots;
+
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.Size;
+
+public class Locator implements HasSelectionHandlers<Point>
+{
+ public interface Display extends HasSelectionHandlers<Point>
+ {
+ void showLocator(Plots.Parent parent);
+ void hideLocator();
+ boolean isVisible();
+ }
+
+ public Locator(Plots.Parent parent)
+ {
+ parent_ = parent;
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<Point> handler)
+ {
+ return handlers_.addHandler(SelectionEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ public boolean isActive()
+ {
+ return display_ != null;
+ }
+
+ public void locate(String url, Size size)
+ {
+ if (currentUrl_ == null || !currentUrl_.equals(url) ||
+ currentSize_ == null || !currentSize_.equals(size) ||
+ display_ == null || !display_.isVisible())
+ {
+ clearDisplay();
+
+ display_ = new LocatorPanel();
+ hreg_ = display_.addSelectionHandler(new SelectionHandler<Point>()
+ {
+ public void onSelection(SelectionEvent<Point> event)
+ {
+ SelectionEvent.fire(Locator.this, event.getSelectedItem());
+ }
+ });
+ currentUrl_ = url;
+ currentSize_ = size;
+ display_.showLocator(parent_);
+ }
+ }
+
+ public void clearDisplay()
+ {
+ currentUrl_ = null;
+ currentSize_ = null;
+
+ if (display_ != null)
+ {
+ display_.hideLocator();
+ display_ = null;
+ }
+
+ if (hreg_ != null)
+ {
+ hreg_.removeHandler();
+ hreg_ = null;
+ }
+ }
+
+
+ private HandlerRegistration hreg_;
+ private Display display_;
+ private Plots.Parent parent_;
+ private String currentUrl_;
+ private Size currentSize_;
+ private final HandlerManager handlers_ = new HandlerManager(null);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/LocatorPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/LocatorPanel.java
new file mode 100644
index 0000000..5f1f39c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/LocatorPanel.java
@@ -0,0 +1,237 @@
+/*
+ * LocatorPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.layout.FadeOutAnimation;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.HasCustomizableToolbar.Customizer;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+
+import java.util.ArrayList;
+
+public class LocatorPanel extends LayoutPanel
+ implements Locator.Display, HasSelectionHandlers<Point>
+{
+ public LocatorPanel()
+ {
+ setStylePrimaryName(ThemeStyles.INSTANCE.locatorPanel());
+
+ feedbackImage_ = new Image(StandardIcons.INSTANCE.click_feedback());
+ feedbackImage_.setVisible(false);
+ add(feedbackImage_);
+ setWidgetTopHeight(feedbackImage_, 0, Unit.PX, FB_HEIGHT, Unit.PX);
+ setWidgetLeftWidth(feedbackImage_, 0, Unit.PX, FB_WIDTH, Unit.PX);
+
+ inputPanel_ = new InputPanel();
+ inputPanel_.setSize("100%", "100%");
+ add(inputPanel_);
+ setWidgetLeftRight(inputPanel_, 0, Unit.PX, 0, Unit.PX);
+ setWidgetTopBottom(inputPanel_, 0, Unit.PX, 0, Unit.PX);
+ inputPanel_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ Element el = inputPanel_.getElement();
+ int x = event.getNativeEvent().getClientX();
+ int y = event.getNativeEvent().getClientY();
+
+ Point p = new Point(
+ x - el.getAbsoluteLeft(), y - el.getAbsoluteTop());
+
+ showFeedbackAt(p);
+
+ SelectionEvent.fire(LocatorPanel.this, p);
+ }
+ });
+
+ // fill entire area of parent container
+ setSize("100%", "100%");
+ }
+
+ public void showLocator(Plots.Parent parent)
+ {
+ // set parent
+ parent_ = parent ;
+
+ // add to parent
+ parent_.add(this);
+
+ // add custom locator toolbar to parent
+ installCustomToolbar();
+
+ // subscribe to ESC key browser-wide for dismissal of the Locator UI
+ // (unhook any existing subscription first)
+ unhookNativePreviewHandler();
+ escHandlerReg_ = Event.addNativePreviewHandler(new NativePreviewHandler(){
+
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ event.cancel();
+ SelectionEvent.fire(LocatorPanel.this, null);
+ }
+ }
+
+ });
+ }
+
+ public void hideLocator()
+ {
+ // make sure we unsubscribe from events
+ unhookNativePreviewHandler();
+
+ // ice custom toolbar
+ parent_.removeCustomToolbar();
+
+ // remove from parent
+ removeFromParent();
+ }
+
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<Point> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+
+ @Override
+ protected void onUnload()
+ {
+ // make sure we unsubscribe from events
+ unhookNativePreviewHandler();
+
+ // nix any existing feedback
+ cancelFeedback();
+
+ // super
+ super.onUnload();
+ }
+
+ private void installCustomToolbar()
+ {
+ parent_.installCustomToolbar(new Customizer() {
+ public void setToolbarContents(Toolbar toolbar)
+ {
+ toolbar.addLeftWidget(
+ new Label("Locator active (Esc to finish)"));
+
+ SmallButton doneButton = new SmallButton("Finish");
+ doneButton.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ SelectionEvent.fire(LocatorPanel.this, null);
+ }
+ });
+ toolbar.addRightWidget(doneButton);
+ toolbar.addRightWidget(new HTML(" "));
+ }
+ });
+ }
+
+ private void showFeedbackAt(Point p)
+ {
+ cancelFeedback();
+
+ setWidgetTopHeight(feedbackImage_, p.getY() - FB_OFFSET_Y,
+ Unit.PX, FB_HEIGHT, Unit.PX);
+ setWidgetLeftWidth(feedbackImage_, p.getX() - FB_OFFSET_X,
+ Unit.PX, FB_WIDTH, Unit.PX);
+ forceLayout();
+ feedbackImage_.setVisible(true);
+ feedbackImage_.getElement().getStyle().setOpacity(1.0);
+ feedbackTimer_ = new Timer()
+ {
+ @Override
+ public void run()
+ {
+ feedbackTimer_ = null;
+ ArrayList<Widget> widgets = new ArrayList<Widget>();
+ widgets.add(feedbackImage_);
+ feedbackAnimation_ = new FadeOutAnimation(widgets,
+ new Command() {
+ public void execute()
+ {
+ feedbackAnimation_ = null;
+ }
+ });
+ feedbackAnimation_.run(300);
+ }
+ };
+ feedbackTimer_.schedule(700);
+ }
+
+ private void cancelFeedback()
+ {
+ if (feedbackTimer_ != null)
+ {
+ feedbackTimer_.cancel();
+ feedbackTimer_ = null;
+ }
+ if (feedbackAnimation_ != null)
+ {
+ feedbackAnimation_.cancel();
+ feedbackAnimation_ = null;
+ }
+ }
+
+ private void unhookNativePreviewHandler()
+ {
+ if (escHandlerReg_ != null)
+ {
+ escHandlerReg_.removeHandler();
+ escHandlerReg_ = null;
+ }
+ }
+
+ private static class InputPanel extends SimplePanel
+ {
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+ }
+
+ private Plots.Parent parent_;
+ private Timer feedbackTimer_;
+ private Animation feedbackAnimation_;
+ private Image feedbackImage_;
+ private static final int FB_HEIGHT = 24;
+ private static final int FB_WIDTH = 24;
+ private static final int FB_OFFSET_Y = 23;
+ private static final int FB_OFFSET_X = 0;
+ private InputPanel inputPanel_;
+ private HandlerRegistration escHandlerReg_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Plots.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Plots.java
new file mode 100644
index 0000000..395e3f1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/Plots.java
@@ -0,0 +1,647 @@
+/*
+ * Plots.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.dom.NativeScreen;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.HasCustomizableToolbar;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperation;
+import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
+import org.rstudio.studio.client.workbench.views.plots.events.LocatorEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.LocatorHandler;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedHandler;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsZoomSizeChangedEvent;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageContext;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsState;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot;
+import org.rstudio.studio.client.workbench.views.plots.ui.manipulator.ManipulatorChangedHandler;
+import org.rstudio.studio.client.workbench.views.plots.ui.manipulator.ManipulatorManager;
+
+public class Plots extends BasePresenter implements PlotsChangedHandler,
+ LocatorHandler,
+ ConsolePromptHandler,
+ DeferredInitCompletedEvent.Handler,
+ PlotsZoomSizeChangedEvent.Handler
+{
+ public interface Parent extends HasWidgets, HasCustomizableToolbar
+ {
+ }
+
+
+ public interface Display extends WorkbenchView, HasResizeHandlers
+ {
+ void showEmptyPlot();
+ void showPlot(String plotUrl);
+ String getPlotUrl();
+
+ void refresh();
+
+ Panel getPlotsSurface();
+
+ Parent getPlotsParent();
+ Size getPlotFrameSize();
+ }
+
+
+ @Inject
+ public Plots(final Display view,
+ GlobalDisplay globalDisplay,
+ WorkbenchContext workbenchContext,
+ Provider<UIPrefs> uiPrefs,
+ Commands commands,
+ EventBus events,
+ final PlotsServerOperations server,
+ Session session)
+ {
+ super(view);
+ view_ = view;
+ globalDisplay_ = globalDisplay;
+ workbenchContext_ = workbenchContext;
+ uiPrefs_ = uiPrefs;
+ server_ = server;
+ session_ = session;
+ exportPlot_ = GWT.create(ExportPlot.class);
+ zoomWindow_ = null;
+ zoomWindowDefaultSize_ = null;
+
+ locator_ = new Locator(view.getPlotsParent());
+ locator_.addSelectionHandler(new SelectionHandler<Point>()
+ {
+ public void onSelection(SelectionEvent<Point> e)
+ {
+ org.rstudio.studio.client.workbench.views.plots.model.Point p = null;
+ if (e.getSelectedItem() != null)
+ p = org.rstudio.studio.client.workbench.views.plots.model.Point.create(
+ e.getSelectedItem().getX(),
+ e.getSelectedItem().getY()
+ );
+ server.locatorCompleted(p, new SimpleRequestCallback<Void>());
+ }
+ });
+
+ // manipulator
+ manipulatorManager_ = new ManipulatorManager(
+ view_.getPlotsSurface(),
+ commands,
+
+ new ManipulatorChangedHandler()
+ {
+ @Override
+ public void onManipulatorChanged(JSONObject values)
+ {
+ server_.setManipulatorValues(values,
+ new ManipulatorRequestCallback());
+ }
+ },
+
+ new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ server_.manipulatorPlotClicked(new Double(event.getX()).intValue(),
+ new Double(event.getY()).intValue(),
+ new ManipulatorRequestCallback());
+
+ }
+ }
+ );
+
+ events.addHandler(DeferredInitCompletedEvent.TYPE, this);
+ events.addHandler(PlotsZoomSizeChangedEvent.TYPE, this);
+}
+
+ public void onPlotsChanged(PlotsChangedEvent event)
+ {
+ // get the event
+ PlotsState plotsState = event.getPlotsState();
+
+ // clear progress
+ view_.setProgress(false);
+ manipulatorManager_.setProgress(false);
+
+ // if this is the empty plot then clear the display
+ // NOTE: we currently return a zero byte PNG as our "empty.png" from
+ // the server. this is shown as a blank pane by Webkit, however
+ // firefox shows the full URI of the empty.png rather than a blank
+ // pane. therefore, we put in this workaround.
+ if (plotsState.getFilename().startsWith("empty."))
+ {
+ view_.showEmptyPlot();
+ }
+ else
+ {
+ String url = server_.getGraphicsUrl(plotsState.getFilename());
+ view_.showPlot(url);
+ }
+
+ // activate the plots tab if requested
+ if (plotsState.getActivatePlots())
+ view_.bringToFront();
+
+ // update plot size
+ plotSize_ = new Size(plotsState.getWidth(), plotsState.getHeight());
+
+ // manipulator
+ manipulatorManager_.setManipulator(plotsState.getManipulator(),
+ plotsState.getShowManipulator());
+
+ // locator
+ if (locator_.isActive())
+ locate();
+
+
+ // reload zoom window if we have one
+ if (Desktop.isDesktop())
+ Desktop.getFrame().reloadZoomWindow();
+ else if ((zoomWindow_ != null) && !zoomWindow_.isClosed())
+ zoomWindow_.reload();
+ }
+
+ void onNextPlot()
+ {
+ view_.bringToFront();
+ setChangePlotProgress();
+ server_.nextPlot(new PlotRequestCallback());
+ }
+
+ void onPreviousPlot()
+ {
+ view_.bringToFront();
+ setChangePlotProgress();
+ server_.previousPlot(new PlotRequestCallback());
+ }
+
+ void onRemovePlot()
+ {
+ // delete plot gesture indicates we are done with locator
+ safeClearLocator();
+
+ // confirm
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+
+ "Remove Plot",
+
+ "Are you sure you want to remove the current plot?",
+
+ new ProgressOperation() {
+ public void execute(final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Removing plot...");
+ server_.removePlot(new VoidServerRequestCallback(indicator));
+ }
+ },
+
+ true
+
+ );
+
+ view_.bringToFront();
+ }
+
+ void onClearPlots()
+ {
+ // clear plots gesture indicates we are done with locator
+ safeClearLocator();
+
+ // confirm
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
+
+ "Clear Plots",
+
+ "Are you sure you want to clear all of the plots in the history?",
+
+ new ProgressOperation() {
+ public void execute(final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Clearing plots...");
+ server_.clearPlots(new VoidServerRequestCallback(indicator));
+ }
+ },
+
+ true
+
+ );
+ }
+
+
+ void onSavePlotAsImage()
+ {
+ view_.bringToFront();
+
+ final ProgressIndicator indicator =
+ globalDisplay_.getProgressIndicator("Error");
+ indicator.onProgress("Preparing to export plot...");
+
+ // get the default directory
+ FileSystemItem defaultDir = ExportPlot.getDefaultSaveDirectory(
+ workbenchContext_.getCurrentWorkingDir());
+
+ // get context
+ server_.getSavePlotContext(
+ defaultDir.getPath(),
+ new SimpleRequestCallback<SavePlotAsImageContext>() {
+
+ @Override
+ public void onResponseReceived(SavePlotAsImageContext context)
+ {
+ indicator.onCompleted();
+
+ exportPlot_.savePlotAsImage(globalDisplay_,
+ server_,
+ context,
+ ExportPlotOptions.adaptToSize(
+ uiPrefs_.get().exportPlotOptions().getValue(),
+ getPlotSize()),
+ saveExportOptionsOperation_);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+ void onSavePlotAsPdf()
+ {
+ view_.bringToFront();
+
+ final ProgressIndicator indicator =
+ globalDisplay_.getProgressIndicator("Error");
+ indicator.onProgress("Preparing to export plot...");
+
+ // get the default directory
+ final FileSystemItem defaultDir = ExportPlot.getDefaultSaveDirectory(
+ workbenchContext_.getCurrentWorkingDir());
+
+ // get context
+ server_.getUniqueSavePlotStem(
+ defaultDir.getPath(),
+ new SimpleRequestCallback<String>() {
+
+ @Override
+ public void onResponseReceived(String stem)
+ {
+ indicator.onCompleted();
+
+ Size size = getPlotSize();
+ final SavePlotAsPdfOptions currentOptions =
+ SavePlotAsPdfOptions.adaptToSize(
+ uiPrefs_.get().savePlotAsPdfOptions().getValue(),
+ pixelsToInches(size.width),
+ pixelsToInches(size.height));
+
+
+ exportPlot_.savePlotAsPdf(
+ globalDisplay_,
+ server_,
+ session_.getSessionInfo(),
+ defaultDir,
+ stem,
+ currentOptions,
+ new OperationWithInput<SavePlotAsPdfOptions>() {
+ @Override
+ public void execute(SavePlotAsPdfOptions options)
+ {
+ if (!SavePlotAsPdfOptions.areEqual(
+ options,
+ currentOptions))
+ {
+ UIPrefs prefs = uiPrefs_.get();
+ prefs.savePlotAsPdfOptions().setGlobalValue(options);
+ prefs.writeUIPrefs();
+ }
+ }
+ }) ;
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+
+
+ void onCopyPlotToClipboard()
+ {
+ view_.bringToFront();
+
+ exportPlot_.copyPlotToClipboard(
+ server_,
+ ExportPlotOptions.adaptToSize(
+ uiPrefs_.get().exportPlotOptions().getValue(),
+ getPlotSize()),
+ saveExportOptionsOperation_);
+ }
+
+ private double pixelsToInches(int pixels)
+ {
+ return (double)pixels / 96.0;
+ }
+
+ private OperationWithInput<ExportPlotOptions> saveExportOptionsOperation_ =
+ new OperationWithInput<ExportPlotOptions>()
+ {
+ public void execute(ExportPlotOptions options)
+ {
+ UIPrefs uiPrefs = uiPrefs_.get();
+ if (!ExportPlotOptions.areEqual(
+ options,
+ uiPrefs.exportPlotOptions().getValue()))
+ {
+ uiPrefs.exportPlotOptions().setGlobalValue(options);
+ uiPrefs.writeUIPrefs();
+ }
+ }
+ };
+
+
+ void onZoomPlot()
+ {
+ int width, height;
+ if (zoomWindowDefaultSize_ != null)
+ {
+ // trim based on available screen size
+ NativeScreen screen = NativeScreen.get();
+ width = Math.min(screen.getAvailWidth(),
+ zoomWindowDefaultSize_.width);
+ height = Math.min(screen.getAvailHeight(),
+ zoomWindowDefaultSize_.height);
+ }
+ else
+ {
+ final int PADDING = 20;
+
+ Size currentPlotSize = view_.getPlotFrameSize();
+
+ // calculate ideal heigh and width. try to be as large as possible
+ // within the bounds of the current client size
+ Size bounds = new Size(Window.getClientWidth() - PADDING,
+ Window.getClientHeight() - PADDING);
+
+ float widthRatio = bounds.width / ((float)currentPlotSize.width);
+ float heightRatio = bounds.height / ((float)currentPlotSize.height);
+ float ratio = Math.min(widthRatio, heightRatio);
+
+ // constrain initial width to between 300 and 1,200 pixels
+ width = Math.max(300, (int) (ratio * currentPlotSize.width));
+ width = Math.min(1200, width);
+
+ // constrain initial height to between 300 and 900 pixels
+ height = Math.max(300, (int) (ratio * currentPlotSize.height));
+ height = Math.min(900, height);
+ }
+
+
+ // determine whether we should scale (see comment in ImageFrame.onLoad
+ // for why we wouldn't want to scale)
+ int scale = 1;
+ if (Desktop.isDesktop() && BrowseCap.isMacintosh())
+ scale = 0;
+
+ // compose url string
+ String url = server_.getGraphicsUrl("plot_zoom?" +
+ "width=" + width + "&" +
+ "height=" + height + "&" +
+ "scale=" + scale);
+
+
+ // open and activate window
+ NewWindowOptions options = new NewWindowOptions();
+ options.setName("_rstudio_zoom");
+ options.setFocus(true);
+ options.setCallback(new OperationWithInput<WindowEx>() {
+ @Override
+ public void execute(WindowEx input)
+ {
+ zoomWindow_ = input;
+ }
+ });
+ globalDisplay_.openMinimalWindow(url,
+ false,
+ width,
+ height,
+ options);
+ }
+
+ void onRefreshPlot()
+ {
+ view_.bringToFront();
+ view_.setProgress(true);
+ server_.refreshPlot(new PlotRequestCallback());
+ }
+
+ @Override
+ public void onDeferredInitCompleted(DeferredInitCompletedEvent event)
+ {
+ boolean showErrors = !workbenchContext_.isRestartInProgress();
+ server_.refreshPlot(new PlotRequestCallback(showErrors));
+ }
+
+ void onShowManipulator()
+ {
+ manipulatorManager_.showManipulator();
+ }
+
+ public Display getView()
+ {
+ return view_;
+ }
+
+ private void safeClearLocator()
+ {
+ if (locator_.isActive())
+ {
+ server_.locatorCompleted(null, new SimpleRequestCallback<Void>() {
+ @Override
+ public void onError(ServerError error)
+ {
+ // ignore errors (this method is meant to be used "quietly"
+ // so that if the server has a problem with clearing
+ // locator state (e.g. because it has already exited the
+ // locator state) we don't bother the user with it. worst
+ // case if this fails then the user will see that the console
+ // is still pending the locator command and the Done and Esc
+ // gestures will still be available to clear the Locator
+ }
+ });
+ }
+ }
+
+ private void setChangePlotProgress()
+ {
+ if (!Desktop.isDesktop())
+ view_.setProgress(true);
+ }
+
+ private class PlotRequestCallback extends ServerRequestCallback<Void>
+ {
+ public PlotRequestCallback()
+ {
+ this(true);
+ }
+
+ public PlotRequestCallback(boolean showErrors)
+ {
+ showErrors_ = showErrors;
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ // we don't clear the progress until the GraphicsOutput
+ // event is received (enables us to wait for rendering
+ // to complete before clearing progress)
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ view_.setProgress(false);
+
+ if (showErrors_)
+ {
+ globalDisplay_.showErrorMessage("Server Error",
+ error.getUserMessage());
+ }
+ }
+
+ private final boolean showErrors_;
+ }
+
+ public void onLocator(LocatorEvent event)
+ {
+ view_.bringToFront();
+ locate();
+ }
+
+ private void locate()
+ {
+ locator_.locate(view_.getPlotUrl(), getPlotSize());
+ }
+
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ locator_.clearDisplay();
+ }
+
+ @Override
+ public void onPlotsZoomSizeChanged(PlotsZoomSizeChangedEvent event)
+ {
+ zoomWindowDefaultSize_ = new Size(event.getWidth(), event.getHeight());
+ }
+
+ private Size getPlotSize()
+ {
+ // NOTE: the reason we capture the plotSize_ from the PlotChangedEvent
+ // is that the server can actually change the size of the plot
+ // (e.g. for CairoSVG the width and height must be multiples of 4)
+ // in order for locator to work properly we need to use this size
+ // rather than size of our current plot frame
+
+ if (plotSize_ != null) // first try to use the last size reported
+ return plotSize_ ;
+ else // then fallback to frame size
+ return view_.getPlotFrameSize();
+ }
+
+ private class ManipulatorRequestCallback extends ServerRequestCallback<Void>
+ {
+ public ManipulatorRequestCallback()
+ {
+ manipulatorManager_.setProgress(true);
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ // we don't clear the progress until the GraphicsOutput
+ // event is received (enables us to wait for rendering
+ // to complete before clearing progress)
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ manipulatorManager_.setProgress(false);
+ globalDisplay_.showErrorMessage("Server Error",
+ error.getUserMessage());
+
+ }
+
+ }
+
+ private final Display view_;
+ private final GlobalDisplay globalDisplay_;
+ private final PlotsServerOperations server_;
+ private final WorkbenchContext workbenchContext_;
+ private final Session session_;
+ private final Provider<UIPrefs> uiPrefs_;
+ private final Locator locator_;
+ private final ManipulatorManager manipulatorManager_;
+ private WindowEx zoomWindow_;
+ private Size zoomWindowDefaultSize_;
+
+ // export plot impl
+ private final ExportPlot exportPlot_ ;
+
+ // size of most recently rendered plot
+ Size plotSize_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsPane.java
new file mode 100644
index 0000000..5019c35
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsPane.java
@@ -0,0 +1,192 @@
+/*
+ * PlotsPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.plots;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.widget.ImageFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.plots.ui.PlotsToolbar;
+
+import java.util.Iterator;
+
+public class PlotsPane extends WorkbenchPane implements Plots.Display,
+ HasResizeHandlers
+{
+ @Inject
+ public PlotsPane(Commands commands)
+ {
+ super("Plots");
+ commands_ = commands;
+ ensureWidget();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ plotsToolbar_ = new PlotsToolbar(commands_);
+ return plotsToolbar_;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ panel_ = new LayoutPanel();
+ panel_.setSize("100%", "100%");
+
+ frame_ = new ImageFrame();
+ frame_.setStyleName("rstudio-HelpFrame");
+ frame_.setMarginWidth(0);
+ frame_.setMarginHeight(0);
+ frame_.setUrl("about:blank");
+ frame_.setSize("100%", "100%");
+ ElementIds.assignElementId(frame_.getElement(),
+ ElementIds.PLOT_IMAGE_FRAME);
+
+ panel_.add(frame_);
+ panel_.setWidgetTopBottom(frame_, 0, Unit.PX, 0, Unit.PX);
+ panel_.setWidgetLeftRight(frame_, 0, Unit.PX, 0, Unit.PX);
+
+ // Stops mouse events from being routed to the iframe, which would
+ // interfere with dragging the workbench pane sizer. also provide
+ // a widget container where adornments can be added on top fo the
+ // plots panel (e.g. manipulator button)
+ plotsSurface_ = new FlowPanel();
+ plotsSurface_.setSize("100%", "100%");
+ panel_.add(plotsSurface_);
+ panel_.setWidgetTopBottom(plotsSurface_, 0, Unit.PX, 0, Unit.PX);
+ panel_.setWidgetLeftRight(plotsSurface_, 0, Unit.PX, 0, Unit.PX);
+
+ // return the panel
+ return panel_;
+ }
+
+ @Override
+ public void setProgress(boolean enabled)
+ {
+ // also set frame to about:blank during progress
+ if (enabled)
+ frame_.setImageUrl(null);
+
+ super.setProgress(enabled);
+ }
+
+ public void showEmptyPlot()
+ {
+ frame_.setImageUrl(null);
+ }
+
+ public void showPlot(String plotUrl)
+ {
+ // save plot url for refresh
+ plotUrl_ = plotUrl;
+
+ // use frame.contentWindow.location.replace to avoid having the plot
+ // enter the browser's history
+ frame_.setImageUrl(plotUrl);
+ }
+
+ public String getPlotUrl()
+ {
+ return plotUrl_;
+ }
+
+
+ public void refresh()
+ {
+ if (plotUrl_ != null)
+ frame_.setImageUrl(plotUrl_);
+ }
+
+ public Panel getPlotsSurface()
+ {
+ return plotsSurface_;
+ }
+
+ public Plots.Parent getPlotsParent()
+ {
+ return plotsParent_;
+ }
+
+ public Size getPlotFrameSize()
+ {
+ return new Size(frame_.getOffsetWidth(), frame_.getOffsetHeight());
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+ ResizeEvent.fire(this, getOffsetWidth(), getOffsetHeight());
+ }
+
+ public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler)
+ {
+ return addHandler(resizeHandler, ResizeEvent.getType());
+ }
+
+ private LayoutPanel panel_;
+ private ImageFrame frame_;
+ private String plotUrl_;
+ private final Commands commands_;
+ private PlotsToolbar plotsToolbar_ = null;
+ private FlowPanel plotsSurface_ = null;
+ private Plots.Parent plotsParent_ = new Plots.Parent() {
+ public void add(Widget w)
+ {
+ panel_.add(w);
+ }
+
+ public boolean remove(Widget w)
+ {
+ return panel_.remove(w);
+ }
+
+ public Iterator<Widget> iterator()
+ {
+ return panel_.iterator();
+ }
+
+ public void clear()
+ {
+ panel_.clear();
+ }
+
+ public void installCustomToolbar(Customizer customizer)
+ {
+ plotsToolbar_.installCustomToolbar(customizer);
+ }
+
+ public void removeCustomToolbar()
+ {
+ plotsToolbar_.removeCustomToolbar();
+ }
+ };
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsTab.java
new file mode 100644
index 0000000..f9b4c7d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/PlotsTab.java
@@ -0,0 +1,192 @@
+/*
+ * PlotsTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots;
+
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
+import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
+import org.rstudio.studio.client.workbench.views.plots.events.LocatorEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.LocatorHandler;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedEvent;
+import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedHandler;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsState;
+
+public class PlotsTab extends DelayLoadWorkbenchTab<Plots>
+ implements HasResizeHandlers, PlotsChangedHandler
+{
+ public interface Binder extends CommandBinder<Commands, PlotsShim> {}
+
+ public abstract static class PlotsShim extends DelayLoadTabShim<Plots, PlotsTab>
+ implements PlotsChangedHandler, LocatorHandler, ConsolePromptHandler
+ {
+ boolean loaded = false;
+ PlotsState delayLoadPlotsState;
+
+ @Handler
+ public abstract void onNextPlot();
+ @Handler
+ public abstract void onPreviousPlot();
+ @Handler
+ public abstract void onRemovePlot();
+ @Handler
+ public abstract void onClearPlots();
+ @Handler
+ public abstract void onZoomPlot();
+ @Handler
+ public abstract void onSavePlotAsImage();
+ @Handler
+ public abstract void onSavePlotAsPdf();
+ @Handler
+ public abstract void onCopyPlotToClipboard();
+ @Handler
+ public abstract void onRefreshPlot();
+ @Handler
+ public abstract void onShowManipulator();
+
+ @Override
+ protected void onDelayLoadSuccess(Plots plots)
+ {
+ loaded = true;
+ super.onDelayLoadSuccess(plots);
+
+ Widget child = plots.asWidget();
+ ((HasResizeHandlers)child).addResizeHandler(new ResizeHandler()
+ {
+ public void onResize(ResizeEvent event)
+ {
+ ResizeEvent.fire(getParentTab(),
+ event.getWidth(),
+ event.getHeight());
+ }
+ });
+
+ if (delayLoadPlotsState != null)
+ {
+ plots.onPlotsChanged(new PlotsChangedEvent(delayLoadPlotsState));
+ delayLoadPlotsState = null;
+ }
+ }
+ }
+
+ @Inject
+ public PlotsTab(PlotsShim shim,
+ EventBus events,
+ Binder binder,
+ Commands commands,
+ PlotsServerOperations server)
+ {
+ super("Plots", shim);
+ binder.bind(commands, shim);
+ commands_ = commands;
+ shim_ = shim;
+ events.addHandler(PlotsChangedEvent.TYPE, this);
+ events.addHandler(LocatorEvent.TYPE, shim);
+ events.addHandler(ConsolePromptEvent.TYPE, shim);
+
+ // disable all commands
+ commands_.nextPlot().setEnabled(false);
+ commands_.previousPlot().setEnabled(false);
+ commands_.savePlotAsImage().setEnabled(false);
+ commands_.savePlotAsPdf().setEnabled(false);
+ commands_.copyPlotToClipboard().setEnabled(false);
+ commands_.zoomPlot().setEnabled(false);
+ commands_.removePlot().setEnabled(false);
+ commands_.clearPlots().setEnabled(false);
+ commands_.refreshPlot().setEnabled(false);
+ commands_.showManipulator().setEnabled(false);
+ }
+
+ public HandlerRegistration addResizeHandler(ResizeHandler handler)
+ {
+ return handlers_.addHandler(ResizeEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ public void onPlotsChanged(PlotsChangedEvent event)
+ {
+ shim_.delayLoadPlotsState = null;
+
+ PlotsState plotsState = event.getPlotsState();
+
+ // zero or one plot -- next and previous disabled
+ if (plotsState.getPlotCount() <= 1)
+ {
+ commands_.nextPlot().setEnabled(false);
+ commands_.previousPlot().setEnabled(false);
+ }
+ // first plot (only next is enabled)
+ else if (plotsState.getPlotIndex() == 0)
+ {
+ commands_.nextPlot().setEnabled(true);
+ commands_.previousPlot().setEnabled(false);
+ }
+ // last plot (only back is enabled)
+ else if (plotsState.getPlotIndex() ==
+ (plotsState.getPlotCount() - 1))
+ {
+ commands_.nextPlot().setEnabled(false);
+ commands_.previousPlot().setEnabled(true);
+ }
+ // both enabled
+ else
+ {
+ commands_.nextPlot().setEnabled(true);
+ commands_.previousPlot().setEnabled(true);
+ }
+
+ // other commands which are only enabled if there is at least
+ // one plot alive
+ boolean hasPlots = plotsState.getPlotCount() >= 1;
+ commands_.savePlotAsImage().setEnabled(hasPlots);
+ commands_.savePlotAsPdf().setEnabled(hasPlots);
+ commands_.copyPlotToClipboard().setEnabled(hasPlots);
+ commands_.zoomPlot().setEnabled(hasPlots);
+ commands_.removePlot().setEnabled(hasPlots);
+ commands_.clearPlots().setEnabled(hasPlots);
+ commands_.refreshPlot().setEnabled(hasPlots);
+ commands_.showManipulator().setEnabled(hasPlots);
+
+ if (plotsState.getActivatePlots() || shim_.loaded)
+ {
+ shim_.onPlotsChanged(event);
+ }
+ else
+ {
+ shim_.delayLoadPlotsState = plotsState;
+ }
+ }
+
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ private final PlotsShim shim_;
+ private Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorEvent.java
new file mode 100644
index 0000000..d727b5e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorEvent.java
@@ -0,0 +1,40 @@
+/*
+ * LocatorEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class LocatorEvent extends GwtEvent<LocatorHandler>
+{
+ public static final GwtEvent.Type<LocatorHandler> TYPE =
+ new GwtEvent.Type<LocatorHandler>();
+
+ public LocatorEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(LocatorHandler handler)
+ {
+ handler.onLocator(this);
+ }
+
+ @Override
+ public GwtEvent.Type<LocatorHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorHandler.java
new file mode 100644
index 0000000..73ae83d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/LocatorHandler.java
@@ -0,0 +1,22 @@
+/*
+ * LocatorHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface LocatorHandler extends EventHandler
+{
+ void onLocator(LocatorEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedEvent.java
new file mode 100644
index 0000000..e88c38b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedEvent.java
@@ -0,0 +1,48 @@
+/*
+ * PlotsChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsState;
+
+public class PlotsChangedEvent extends GwtEvent<PlotsChangedHandler>
+{
+ public static final GwtEvent.Type<PlotsChangedHandler> TYPE =
+ new GwtEvent.Type<PlotsChangedHandler>();
+
+ public PlotsChangedEvent(PlotsState plotsState)
+ {
+ plotsState_ = plotsState;
+ }
+
+ public PlotsState getPlotsState()
+ {
+ return plotsState_;
+ }
+
+ @Override
+ protected void dispatch(PlotsChangedHandler handler)
+ {
+ handler.onPlotsChanged(this);
+ }
+
+ @Override
+ public GwtEvent.Type<PlotsChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private PlotsState plotsState_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedHandler.java
new file mode 100644
index 0000000..39925b6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * PlotsChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface PlotsChangedHandler extends EventHandler
+{
+ void onPlotsChanged(PlotsChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsZoomSizeChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsZoomSizeChangedEvent.java
new file mode 100644
index 0000000..a70edad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/events/PlotsZoomSizeChangedEvent.java
@@ -0,0 +1,74 @@
+/*
+ * PlotsZoomSizeChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PlotsZoomSizeChangedEvent extends GwtEvent<PlotsZoomSizeChangedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPlotsZoomSizeChanged(PlotsZoomSizeChangedEvent event);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+
+ public native final int getWidth() /*-{
+ return this.width;
+ }-*/;
+
+ public native final int getHeight() /*-{
+ return this.height;
+ }-*/;
+ }
+
+ public PlotsZoomSizeChangedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public int getWidth()
+ {
+ return data_.getWidth();
+ }
+
+ public int getHeight()
+ {
+ return data_.getHeight();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPlotsZoomSizeChanged(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/ExportPlotOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/ExportPlotOptions.java
new file mode 100644
index 0000000..08a7dfc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/ExportPlotOptions.java
@@ -0,0 +1,104 @@
+/*
+ * ExportPlotOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import org.rstudio.core.client.Size;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ExportPlotOptions extends JavaScriptObject
+{
+ protected ExportPlotOptions()
+ {
+ }
+
+ public static final ExportPlotOptions createDefault()
+ {
+ return create(550, 450, false, "PNG", false, false);
+ }
+
+ public static final native ExportPlotOptions create(int width,
+ int height,
+ boolean keepRatio,
+ String format,
+ boolean viewAfterSave,
+ boolean copyAsMetafile)
+ /*-{
+ var options = new Object();
+ options.width = width ;
+ options.height = height ;
+ options.format = format;
+ options.keepRatio = keepRatio;
+ options.viewAfterSave = viewAfterSave;
+ options.copyAsMetafile = copyAsMetafile;
+ return options ;
+ }-*/;
+
+ public static final ExportPlotOptions adaptToSize(ExportPlotOptions options,
+ Size size)
+ {
+ return ExportPlotOptions.create(size.width,
+ size.height,
+ options.getKeepRatio(),
+ options.getFormat(),
+ options.getViewAfterSave(),
+ options.getCopyAsMetafile());
+ }
+
+ public static native boolean areEqual(ExportPlotOptions a, ExportPlotOptions b) /*-{
+ if (a === null ^ b === null)
+ return false;
+ if (a === null)
+ return true;
+ return a.format === b.format &&
+ a.width === b.width &&
+ a.height === b.height &&
+ a.keepRatio === b.keepRatio &&
+ a.viewAfterSave === b.viewAfterSave &&
+ a.copyAsMetafile === b.copyAsMetafile;
+ }-*/;
+
+ public final native String getFormat() /*-{
+ return this.format;
+ }-*/;
+
+ public final native int getWidth() /*-{
+ return this.width;
+ }-*/;
+ public final native int getHeight() /*-{
+ return this.height;
+ }-*/;
+
+ public final native boolean getKeepRatio() /*-{
+ if (this.keepRatio)
+ return this.keepRatio;
+ else
+ return false;
+ }-*/;
+
+ public final native boolean getViewAfterSave() /*-{
+ if (this.viewAfterSave)
+ return this.viewAfterSave;
+ else
+ return false;
+ }-*/;
+
+ public final native boolean getCopyAsMetafile() /*-{
+ if (this.copyAsMetafile)
+ return this.copyAsMetafile;
+ else
+ return false;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Manipulator.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Manipulator.java
new file mode 100644
index 0000000..5906930
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Manipulator.java
@@ -0,0 +1,145 @@
+/*
+ * Manipulator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class Manipulator extends JavaScriptObject
+{
+ protected Manipulator()
+ {
+ }
+
+ public static class Control extends JavaScriptObject
+ {
+ public final static int SLIDER = 0;
+ public final static int PICKER = 1;
+ public final static int CHECKBOX = 2;
+ public final static int BUTTON = 3;
+
+ protected Control()
+ {
+ }
+
+ public final native int getType() /*-{
+ return this.type[0];
+ }-*/;
+
+ public final native String getLabel() /*-{
+ if (this.label)
+ return this.label[0];
+ else
+ return null;
+ }-*/;
+ }
+
+ public static class Slider extends Control
+ {
+ protected Slider()
+ {
+ }
+
+ public final native double getMin() /*-{
+ return this.min[0];
+ }-*/;
+
+ public final native double getMax() /*-{
+ return this.max[0];
+ }-*/;
+
+ public final native double getStep() /*-{
+ return this.step[0];
+ }-*/;
+
+ public final native boolean getTicks() /*-{
+ return this.ticks[0];
+ }-*/;
+
+ public final native double getInitialValue() /*-{
+ return this.initialValue[0];
+ }-*/;
+
+ }
+
+ public static class Picker extends Control
+ {
+ protected Picker()
+ {
+ }
+
+ public final native JsArrayString getChoices() /*-{
+ return this.choices;
+ }-*/;
+
+ public final native String getInitialValue() /*-{
+ return this.initialValue[0];
+ }-*/;
+
+ }
+
+ public static class CheckBox extends Control
+ {
+ protected CheckBox()
+ {
+ }
+
+ public final native boolean getInitialValue() /*-{
+ return this.initialValue[0];
+ }-*/;
+ }
+
+ public static class Button extends Control
+ {
+ protected Button()
+ {
+ }
+ }
+
+
+ public final native String getID() /*-{
+ return this.id[0];
+ }-*/;
+
+ public final native JsArrayString getVariables() /*-{
+ return this.variables;
+ }-*/;
+
+ public final native double getDoubleValue(String name) /*-{
+ var valueArray = this.values[name];
+ return valueArray[0];
+ }-*/;
+
+ public final native String getStringValue(String name) /*-{
+ var valueArray = this.values[name];
+ return valueArray[0];
+ }-*/;
+
+ public final native boolean getBooleanValue(String name) /*-{
+ var valueArray = this.values[name];
+ return valueArray[0];
+ }-*/;
+
+ public final native Control getControl(String name) /*-{
+ var control = this.controls[name];
+ return control;
+ }-*/;
+
+ public final boolean hasControls()
+ {
+ return getVariables() != null &&
+ getVariables().length() > 0;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsServerOperations.java
new file mode 100644
index 0000000..ba88615
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsServerOperations.java
@@ -0,0 +1,80 @@
+/*
+ * PlotsServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import com.google.gwt.json.client.JSONObject;
+
+
+public interface PlotsServerOperations
+{
+ String getGraphicsUrl(String filename);
+
+ String getFileUrl(FileSystemItem file);
+
+ String getPlotExportUrl(String type,
+ int width,
+ int height,
+ boolean attachment);
+
+ void nextPlot(ServerRequestCallback<Void> requestCallback);
+ void previousPlot(ServerRequestCallback<Void> requestCallback);
+
+ void removePlot(ServerRequestCallback<Void> requestCallback);
+
+ void clearPlots(ServerRequestCallback<Void> requestCallback);
+
+ void refreshPlot(ServerRequestCallback<Void> requestCallback);
+
+ void setManipulatorValues(JSONObject values,
+ ServerRequestCallback<Void> requestCallback);
+
+ void manipulatorPlotClicked(int x,
+ int y,
+ ServerRequestCallback<Void> requestCallback);
+
+ void locatorCompleted(Point point,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getUniqueSavePlotStem(String directory,
+ ServerRequestCallback<String> requestCallback);
+
+ void getSavePlotContext(
+ String directory,
+ ServerRequestCallback<SavePlotAsImageContext> requestCallback);
+
+ void savePlotAs(FileSystemItem file,
+ String format,
+ int width,
+ int height,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback);
+
+ void savePlotAsPdf(FileSystemItem file,
+ double widthInches,
+ double heightInches,
+ boolean useCairoPdf,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback);
+
+
+ void copyPlotToClipboardMetafile(
+ int width,
+ int height,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsState.java
new file mode 100644
index 0000000..015fd13
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/PlotsState.java
@@ -0,0 +1,56 @@
+/*
+ * PlotsState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PlotsState extends JavaScriptObject
+{
+ protected PlotsState()
+ {
+ }
+
+ public final native String getFilename() /*-{
+ return this.filename;
+ }-*/;
+
+ public final native Manipulator getManipulator() /*-{
+ return this.manipulator;
+ }-*/;
+
+ public final native int getWidth() /*-{
+ return this.width;
+ }-*/;
+
+ public final native int getHeight() /*-{
+ return this.height;
+ }-*/;
+
+ public final native int getPlotIndex() /*-{
+ return this.plotIndex;
+ }-*/;
+
+ public final native int getPlotCount() /*-{
+ return this.plotCount;
+ }-*/;
+
+ public final native boolean getActivatePlots() /*-{
+ return this.activatePlots;
+ }-*/;
+
+ public final native boolean getShowManipulator() /*-{
+ return this.showManipulator;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Point.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Point.java
new file mode 100644
index 0000000..636a40b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/Point.java
@@ -0,0 +1,42 @@
+/*
+ * Point.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Point extends JavaScriptObject
+{
+ protected Point()
+ {
+ }
+
+ public static final native Point create(double x, double y) /*-{
+ var point = new Object();
+ point.x = x ;
+ point.y = y ;
+ return point ;
+ }-*/;
+
+ // size
+ public final native double getX() /*-{
+ return this.x;
+ }-*/;
+ public final native double getY() /*-{
+ return this.y;
+ }-*/;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageContext.java
new file mode 100644
index 0000000..6c6bc6b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageContext.java
@@ -0,0 +1,39 @@
+/*
+ * SavePlotAsImageContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class SavePlotAsImageContext extends JavaScriptObject
+{
+ protected SavePlotAsImageContext()
+ {
+ }
+
+ public final native JsArray<SavePlotAsImageFormat> getFormats() /*-{
+ return this.formats;
+ }-*/;
+
+ public final native FileSystemItem getDirectory() /*-{
+ return this.directory;
+ }-*/;
+
+ public final native String getUniqueFileStem() /*-{
+ return this.uniqueFileStem;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageFormat.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageFormat.java
new file mode 100644
index 0000000..3db6807
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsImageFormat.java
@@ -0,0 +1,32 @@
+/*
+ * SavePlotAsImageFormat.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SavePlotAsImageFormat extends JavaScriptObject
+{
+ protected SavePlotAsImageFormat()
+ {
+ }
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getExtension() /*-{
+ return this.extension;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsPdfOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsPdfOptions.java
new file mode 100644
index 0000000..b41cdbe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/model/SavePlotAsPdfOptions.java
@@ -0,0 +1,95 @@
+/*
+ * SavePlotAsPdfOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SavePlotAsPdfOptions extends JavaScriptObject
+{
+ protected SavePlotAsPdfOptions()
+ {
+ }
+
+ public static final SavePlotAsPdfOptions createDefault()
+ {
+ return SavePlotAsPdfOptions.create(8.5, 11);
+ }
+
+ public static final SavePlotAsPdfOptions create(double width, double height)
+ {
+ return create(width, height, true, false, false);
+ }
+
+ public static final native SavePlotAsPdfOptions create(
+ double width,
+ double height,
+ boolean portrait,
+ boolean cairoPdf,
+ boolean viewAfterSave) /*-{
+ var options = new Object();
+ options.width = width ;
+ options.height = height ;
+ options.portrait = portrait;
+ options.cairo_pdf = cairoPdf;
+ options.viewAfterSave = viewAfterSave;
+ return options ;
+ }-*/;
+
+ public static final SavePlotAsPdfOptions adaptToSize(
+ SavePlotAsPdfOptions options,
+ double width,
+ double height)
+ {
+ return SavePlotAsPdfOptions.create(width,
+ height,
+ options.getPortrait(),
+ options.getCairoPdf(),
+ options.getViewAfterSave());
+ }
+
+ public static native boolean areEqual(SavePlotAsPdfOptions a, SavePlotAsPdfOptions b) /*-{
+ if (a === null ^ b === null)
+ return false;
+ if (a === null)
+ return true;
+ return a.width === b.width &&
+ a.height === b.height &&
+ a.portrait === b.portrait &&
+ a.cairo_pdf === b.cairo_pdf &&
+ a.viewAfterSave === b.viewAfterSave;
+ }-*/;
+
+ public final native double getWidth() /*-{
+ return this.width;
+ }-*/;
+
+ public final native double getHeight() /*-{
+ return this.height;
+ }-*/;
+
+ public final native boolean getPortrait() /*-{
+ return this.portrait;
+ }-*/;
+
+ public final native boolean getCairoPdf() /*-{
+ return this.cairo_pdf;
+ }-*/;
+
+
+ public final native boolean getViewAfterSave() /*-{
+ return this.viewAfterSave;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/PlotsToolbar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/PlotsToolbar.java
new file mode 100644
index 0000000..fa82731
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/PlotsToolbar.java
@@ -0,0 +1,77 @@
+/*
+ * PlotsToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui;
+
+import org.rstudio.core.client.widget.HasCustomizableToolbar;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+public class PlotsToolbar extends Toolbar implements HasCustomizableToolbar
+{
+ public PlotsToolbar(Commands commands)
+ {
+ commands_ = commands ;
+ installStandardUI();
+ }
+
+ public void installCustomToolbar(Customizer customizer)
+ {
+ removeAllWidgets();
+ customizer.setToolbarContents(this);
+ }
+
+ public void removeCustomToolbar()
+ {
+ removeAllWidgets();
+ installStandardUI();
+ }
+
+ private void installStandardUI()
+ {
+ // plot history navigation
+ addLeftWidget(commands_.previousPlot().createToolbarButton());
+ addLeftWidget(commands_.nextPlot().createToolbarButton());
+ addLeftSeparator();
+
+ // popout current plot
+ addLeftWidget(commands_.zoomPlot().createToolbarButton());
+ addLeftSeparator();
+
+ // export commands
+ ToolbarPopupMenu exportMenu = new ToolbarPopupMenu();
+ exportMenu.addItem(commands_.savePlotAsImage().createMenuItem(false));
+ exportMenu.addItem(commands_.savePlotAsPdf().createMenuItem(false));
+ exportMenu.addSeparator();
+ exportMenu.addItem(commands_.copyPlotToClipboard().createMenuItem(false));
+ ToolbarButton exportButton = new ToolbarButton(
+ "Export", commands_.savePlotAsImage().getImageResource(),
+ exportMenu);
+ addLeftWidget(exportButton);
+ addLeftSeparator();
+
+ addLeftWidget(commands_.removePlot().createToolbarButton());
+ addLeftSeparator();
+
+ // clear all plots
+ addLeftWidget(commands_.clearPlots().createToolbarButton());
+
+ // refresh
+ addRightWidget(commands_.refreshPlot().createToolbarButton());
+ }
+
+ private Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.css
new file mode 100644
index 0000000..ada59b5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.css
@@ -0,0 +1,150 @@
+
+.exportTargetLabel {
+ margin-right: 7px;
+}
+
+.imageFormatListBox {
+ width: 170px;
+}
+
+.fileNameLabel {
+ margin-top: -3px;
+}
+
+.fileNameTextBox {
+ margin-top: -5px;
+ width: 165px;
+}
+
+.directoryButton {
+ margin-left: -2px;
+}
+
+.directoryLabel {
+ margin-top: -2px;
+ width: 250px;
+ cursor: default;
+}
+}
+
+.imageOptionLabel {
+ margin-right: 5px;
+}
+
+.imageSizeTextBox {
+ text-align: left;
+ width: 35px;
+}
+
+.imagePreview {
+ border: 1px solid #BBB;
+ outline: 0;
+ background-color: white;
+}
+
+.horizontalSizeOptions {
+ margin-bottom: 4px;
+}
+
+.horizontalSizeOptions .widthAndHeightEntry {
+ margin-right: 10px;
+ margin-bottom: 4px;
+}
+
+.horizontalSizeOptions .maintainAspectRatioCheckBox {
+ margin-left: 10px;
+ margin-right: 10px;
+ margin-bottom: 4px;
+}
+
+.horizontalSizeOptions .updateImageSizeButton {
+ padding-top: 5px;
+
+}
+
+
+
+.verticalSizeOptions {
+
+}
+
+.verticalSizeOptions .widthAndHeightEntry {
+ margin-bottom: 4px;
+}
+
+.verticalSizeOptions .maintainAspectRatioCheckBox {
+ margin-left: -3px;
+}
+
+.verticalSizeOptions .updateImageSizeButton {
+ margin-left: -2px;
+ margin-top: 6px;
+ margin-bottom: 4px;
+}
+
+.rightClickCopyLabel {
+ margin-left: 8px;
+ margin-top: 3px;
+ width: 220px;
+}
+
+.copyFormatLabel {
+ margin-top: 2px;
+ margin-right: 6px;
+
+}
+
+.copyFormatBitmap {
+ margin-right: 8px;
+}
+
+.copyFormatMetafile {
+}
+
+
+.savePdfMainWidget {
+ width: 360px;
+ margin-bottom: 15px;
+}
+
+.savePdfSizeListBox {
+ width: 120px;
+ margin-right: 10px;
+}
+
+.savePdfSizeLabel {
+ color: gray;
+}
+
+.savePdfFileNameLabel {
+ margin-top: 0px;
+}
+
+
+.savePdfFileNameTextBox {
+ margin-left: 10px;
+ width: 280px;
+}
+
+.savePdfDirectoryLabel {
+ margin-left: 10px;
+}
+
+.savePdfViewAfterCheckbox {
+ margin-left: 7px;
+}
+
+.savePdfViewAfterCheckbox input[type=checkbox] {
+ margin-top: 7px;
+}
+
+.savePdfPaperSizeTextBox {
+ width: 40px;
+}
+
+.savePdfPaperSizeX {
+ margin-top: 3px;
+ margin-left: 5px;
+ margin-right: 5px;
+ color: gray;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.java
new file mode 100644
index 0000000..7fe7ff7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlot.java
@@ -0,0 +1,120 @@
+/*
+ * ExportPlot.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageContext;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
+
+import com.google.gwt.user.client.ui.TextBox;
+
+public class ExportPlot
+{
+ public void savePlotAsImage(GlobalDisplay globalDisplay,
+ PlotsServerOperations server,
+ SavePlotAsImageContext context,
+ ExportPlotOptions options,
+ OperationWithInput<ExportPlotOptions> onClose)
+ {
+ new SavePlotAsImageDialog(globalDisplay,
+ server,
+ context,
+ options,
+ onClose).showModal();
+ }
+
+ public void savePlotAsPdf(GlobalDisplay globalDisplay,
+ PlotsServerOperations server,
+ SessionInfo sessionInfo,
+ FileSystemItem defaultDirectory,
+ String defaultPlotName,
+ final SavePlotAsPdfOptions options,
+ final OperationWithInput<SavePlotAsPdfOptions> onClose)
+ {
+ new SavePlotAsPdfDialog(globalDisplay,
+ server,
+ sessionInfo,
+ defaultDirectory,
+ defaultPlotName,
+ options,
+ onClose).showModal();
+ }
+
+
+ public void copyPlotToClipboard(
+ PlotsServerOperations server,
+ ExportPlotOptions options,
+ OperationWithInput<ExportPlotOptions> onClose)
+ {
+ }
+
+
+ // utility for calculating display of directory
+ public static String shortDirectoryName(FileSystemItem directory,
+ int maxWidth)
+ {
+ return StringUtil.shortPathName(directory, "gwt-Label", maxWidth);
+ }
+
+ public static FileSystemItem composeTargetPath(String ext,
+ TextBox fileNameTextBox,
+ FileSystemItem directory)
+ {
+ // get the filename
+ String filename = fileNameTextBox.getText().trim();
+ if (filename.length() == 0)
+ return null;
+
+ // compute the target path
+ FileSystemItem targetPath = FileSystemItem.createFile(
+ directory.completePath(filename));
+
+ // if the extension isn't already correct then append it
+ if (!targetPath.getExtension().equalsIgnoreCase(ext))
+ targetPath = FileSystemItem.createFile(targetPath.getPath() + ext);
+
+ // return the path
+ return targetPath;
+ }
+
+
+ // track which directory to suggest as the default for various
+ // working directories
+
+
+ public static FileSystemItem getDefaultSaveDirectory(
+ FileSystemItem defaultDir)
+ {
+ if (defaultSaveDirectory_ == null)
+ defaultSaveDirectory_ = defaultDir;
+
+ return defaultSaveDirectory_;
+ }
+
+ public static void setDefaultSaveDirectory(
+ FileSystemItem defaultSaveDirectory)
+ {
+ defaultSaveDirectory_ = defaultSaveDirectory;
+ }
+
+ // remember last save directory
+ static private FileSystemItem defaultSaveDirectory_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotDialog.java
new file mode 100644
index 0000000..18d2b85
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotDialog.java
@@ -0,0 +1,117 @@
+/*
+ * ExportPlotDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ExportPlotDialog extends ModalDialogBase
+{
+ public ExportPlotDialog(PlotsServerOperations server,
+ ExportPlotOptions options)
+ {
+ server_ = server;
+ options_ = options;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ VerticalPanel mainPanel = new VerticalPanel();
+
+ // enforce maximum initial dimensions based on screen size
+ Size maxSize = new Size(Window.getClientWidth() - 100,
+ Window.getClientHeight() - 200);
+
+ int width = Math.min(options_.getWidth(), maxSize.width);
+ int height = Math.min(options_.getHeight(), maxSize.height);
+
+ sizeEditor_ = new ExportPlotSizeEditor(
+ width,
+ height,
+ options_.getKeepRatio(),
+ createTopLeftWidget(),
+ server_,
+ new ExportPlotSizeEditor.Observer() {
+ public void onPlotResized(boolean withMouse)
+ {
+ if (!withMouse)
+ center();
+ }
+ });
+ mainPanel.add(sizeEditor_);
+
+ Widget bottomWidget = createBottomWidget();
+ if (bottomWidget != null)
+ mainPanel.add(bottomWidget);
+
+ return mainPanel;
+
+ }
+
+
+ protected Widget createTopLeftWidget()
+ {
+ return null;
+ }
+
+ protected Widget createBottomWidget()
+ {
+ return null;
+ }
+
+ protected ExportPlotSizeEditor getSizeEditor()
+ {
+ return sizeEditor_;
+ }
+
+ protected ExportPlotOptions getCurrentOptions(ExportPlotOptions previous)
+ {
+ ExportPlotSizeEditor sizeEditor = getSizeEditor();
+ return ExportPlotOptions.create(sizeEditor.getImageWidth(),
+ sizeEditor.getImageHeight(),
+ sizeEditor.getKeepRatio(),
+ previous.getFormat(),
+ previous.getViewAfterSave(),
+ previous.getCopyAsMetafile());
+ }
+
+
+ @Override
+ protected void onDialogShown()
+ {
+ super.onDialogShown();
+ sizeEditor_.onSizerShown();
+ }
+
+
+ protected final PlotsServerOperations server_;
+
+
+ private final ExportPlotOptions options_;
+
+ private ExportPlotSizeEditor sizeEditor_;
+
+
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotResources.java
new file mode 100644
index 0000000..3f6c854
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotResources.java
@@ -0,0 +1,71 @@
+/*
+ * ExportPlotResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface ExportPlotResources extends ClientBundle
+{
+ public static interface Styles extends CssResource
+ {
+ String exportTargetLabel();
+ String imageFormatListBox();
+ String fileNameLabel();
+ String fileNameTextBox();
+ String directoryButton();
+ String directoryLabel();
+
+ String imagePreview();
+ String imageOptionLabel();
+ String imageSizeTextBox();
+
+ String verticalSizeOptions();
+ String horizontalSizeOptions();
+
+ String widthAndHeightEntry();
+ String maintainAspectRatioCheckBox();
+ String updateImageSizeButton();
+
+ String rightClickCopyLabel();
+
+ String copyFormatLabel();
+ String copyFormatBitmap();
+ String copyFormatMetafile();
+
+ String savePdfMainWidget();
+ String savePdfSizeListBox();
+ String savePdfSizeLabel();
+ String savePdfDirectoryLabel();
+ String savePdfFileNameLabel();
+ String savePdfFileNameTextBox();
+ String savePdfViewAfterCheckbox();
+ String savePdfPaperSizeTextBox();
+ String savePdfPaperSizeX();
+ }
+
+
+ @Source("ExportPlot.css")
+ Styles styles();
+
+ ImageResource rightMouse();
+
+ public static ExportPlotResources INSTANCE =
+ (ExportPlotResources)GWT.create(ExportPlotResources.class) ;
+
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotSizeEditor.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotSizeEditor.java
new file mode 100644
index 0000000..c8de8c3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/ExportPlotSizeEditor.java
@@ -0,0 +1,475 @@
+/*
+ * ExportPlotSizeEditor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.CellPanel;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ExportPlotSizeEditor extends Composite
+{
+ public interface Observer
+ {
+ void onPlotResized(boolean withMouse);
+ }
+
+ public ExportPlotSizeEditor(int initialWidth,
+ int initialHeight,
+ boolean keepRatio,
+ PlotsServerOperations server,
+ Observer observer)
+ {
+ this(initialWidth, initialHeight, keepRatio, null, server, observer);
+ }
+
+ public ExportPlotSizeEditor(int initialWidth,
+ int initialHeight,
+ boolean keepRatio,
+ Widget extraWidget,
+ PlotsServerOperations server,
+ final Observer observer)
+ {
+ // alias objects and resources
+ server_ = server;
+ ExportPlotResources resources = ExportPlotResources.INSTANCE;
+
+ // main widget
+ VerticalPanel verticalPanel = new VerticalPanel();
+
+ // if we have an extra widget then enclose it within a horizontal
+ // panel with it on the left and the options on the right
+ HorizontalPanel topPanel = new HorizontalPanel();
+ CellPanel optionsPanel = null;
+ HorizontalPanel widthAndHeightPanel = null;
+ if (extraWidget != null)
+ {
+ topPanel.setWidth("100%");
+
+ topPanel.add(extraWidget);
+ topPanel.setCellHorizontalAlignment(extraWidget,
+ HasHorizontalAlignment.ALIGN_LEFT);
+
+ optionsPanel = new VerticalPanel();
+ optionsPanel.setStylePrimaryName(
+ resources.styles().verticalSizeOptions());
+ optionsPanel.setSpacing(0);
+ topPanel.add(optionsPanel);
+ topPanel.setCellHorizontalAlignment(
+ optionsPanel,
+ HasHorizontalAlignment.ALIGN_RIGHT);
+
+ widthAndHeightPanel = new HorizontalPanel();
+ widthAndHeightPanel.setStylePrimaryName(
+ resources.styles().widthAndHeightEntry());
+ configureHorizontalOptionsPanel(widthAndHeightPanel);
+ optionsPanel.add(widthAndHeightPanel);
+ }
+ else
+ {
+ optionsPanel = topPanel ;
+ optionsPanel.setStylePrimaryName(
+ resources.styles().horizontalSizeOptions());
+ widthAndHeightPanel = topPanel;
+ configureHorizontalOptionsPanel(topPanel);
+ }
+
+ // image width
+ widthAndHeightPanel.add(createImageOptionLabel("Width:"));
+ widthTextBox_ = createImageSizeTextBox();
+ widthTextBox_.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ // screen out programmatic sets
+ if (settingDimenensionInProgress_)
+ return;
+
+ // enforce min size
+ int width = constrainWidth(getImageWidth());
+
+ // preserve aspect ratio if requested
+ if (getKeepRatio())
+ {
+ double ratio = (double)lastHeight_ / (double)lastWidth_;
+ int height = constrainHeight((int) (ratio * (double)width));
+ setHeightTextBox(height);
+ }
+
+ // set width
+ setWidthTextBox(width);
+ }
+
+ });
+ widthAndHeightPanel.add(widthTextBox_);
+
+ // image height
+ widthAndHeightPanel.add(new HTML(" "));
+ widthAndHeightPanel.add(createImageOptionLabel("Height:"));
+ heightTextBox_ = createImageSizeTextBox();
+ heightTextBox_.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ // screen out programmatic sets
+ if (settingDimenensionInProgress_)
+ return;
+
+ // enforce min size
+ int height = constrainHeight(getImageHeight());
+
+ // preserve aspect ratio if requested
+ if (getKeepRatio())
+ {
+ double ratio = (double)lastWidth_ / (double)lastHeight_;
+ int width = constrainWidth((int) (ratio * (double)height));
+ setWidthTextBox(width);
+ }
+
+ // always set height
+ setHeightTextBox(height);
+ }
+
+ });
+ widthAndHeightPanel.add(heightTextBox_);
+
+ // add width and height panel to options panel container if necessary
+ if (widthAndHeightPanel != optionsPanel)
+ optionsPanel.add(widthAndHeightPanel);
+
+ // lock ratio check box
+ keepRatioCheckBox_ = new CheckBox();
+ keepRatioCheckBox_.setStylePrimaryName(
+ resources.styles().maintainAspectRatioCheckBox());
+ keepRatioCheckBox_.setValue(keepRatio);
+ keepRatioCheckBox_.setText("Maintain aspect ratio");
+ optionsPanel.add(keepRatioCheckBox_);
+
+ // image and sizer in layout panel (create now so we can call
+ // setSize in update button click handler)
+ previewPanel_ = new LayoutPanel();
+
+
+ // update button
+ ThemedButton updateButton = new ThemedButton("Update Preview",
+ new ClickHandler(){
+ public void onClick(ClickEvent event)
+ {
+ setPreviewPanelSize(getImageWidth(), getImageHeight());
+
+ updateImage();
+
+ observer.onPlotResized(false);
+ }
+ });
+ updateButton.setStylePrimaryName(
+ resources.styles().updateImageSizeButton());
+ optionsPanel.add(updateButton);
+
+ // add top panel
+ verticalPanel.add(topPanel);
+
+ // image frame
+ imageFrame_ = new ImageFrame();
+ imageFrame_.setUrl("about:blank");
+ imageFrame_.setSize("100%", "100%");
+ imageFrame_.setMarginHeight(0);
+ imageFrame_.setMarginWidth(0);
+ imageFrame_.setStylePrimaryName(resources.styles().imagePreview());
+
+ // Stops mouse events from being routed to the iframe, which would
+ // interfere with resizing
+ final GlassPanel glassPanel = new GlassPanel(imageFrame_);
+ glassPanel.getChildContainerElement().getStyle().setOverflow(
+ Overflow.VISIBLE);
+ glassPanel.setSize("100%", "100%");
+
+
+
+ previewPanel_.add(glassPanel);
+ previewPanel_.setWidgetLeftRight(glassPanel,
+ 0, Unit.PX,
+ IMAGE_INSET, Unit.PX);
+ previewPanel_.setWidgetTopBottom(glassPanel,
+ 0, Unit.PX,
+ IMAGE_INSET, Unit.PX);
+ previewPanel_.getWidgetContainerElement(
+ glassPanel).getStyle().setOverflow(Overflow.VISIBLE);
+
+ // resize gripper
+ ResizeGripper gripper = new ResizeGripper(new ResizeGripper.Observer()
+ {
+ @Override
+ public void onResizingStarted()
+ {
+ int startWidth = getImageWidth();
+ int startHeight = getImageHeight();
+
+ widthAspectRatio_ = (double)startWidth / (double)startHeight;
+ heightAspectRatio_ = (double)startHeight / (double)startWidth;
+
+ glassPanel.setGlass(true);
+ }
+
+ @Override
+ public void onResizing(int xDelta, int yDelta)
+ {
+ // get start width and height
+ int startWidth = getImageWidth();
+ int startHeight = getImageHeight();
+
+ // calculate new height and width
+ int newWidth = constrainWidth(startWidth + xDelta);
+ int newHeight = constrainHeight(startHeight + yDelta);
+
+ // preserve aspect ratio if requested
+ if (getKeepRatio())
+ {
+ if (Math.abs(xDelta) > Math.abs(yDelta))
+ newHeight = (int) (heightAspectRatio_ * (double)newWidth);
+ else
+ newWidth = (int) (widthAspectRatio_ * (double)newHeight);
+ }
+
+ // set text boxes
+ setWidthTextBox(newWidth);
+ setHeightTextBox(newHeight);
+
+ // set image preview size
+ setPreviewPanelSize(newWidth, newHeight);
+ }
+
+ @Override
+ public void onResizingCompleted()
+ {
+ glassPanel.setGlass(false);
+ updateImage();
+ observer.onPlotResized(true);
+ }
+
+ private double widthAspectRatio_ = 1.0;
+ private double heightAspectRatio_ = 1.0;
+ });
+
+ // layout gripper
+ previewPanel_.add(gripper);
+ previewPanel_.setWidgetRightWidth(gripper,
+ 0, Unit.PX,
+ gripper.getImageWidth(), Unit.PX);
+ previewPanel_.setWidgetBottomHeight(gripper,
+ 0, Unit.PX,
+ gripper.getImageHeight(), Unit.PX);
+
+ // constrain dimensions
+ initialWidth = constrainWidth(initialWidth);
+ initialHeight = constrainHeight(initialHeight);
+
+ // initialie text boxes
+ setWidthTextBox(initialWidth);
+ setHeightTextBox(initialHeight);
+
+ // initialize preview
+ setPreviewPanelSize(initialWidth, initialHeight);
+
+ verticalPanel.add(previewPanel_);
+
+ // set initial focus widget
+ if (extraWidget == null)
+ initialFocusWidget_ = widthTextBox_;
+ else
+ initialFocusWidget_ = null;
+
+ initWidget(verticalPanel);
+
+ }
+
+ public void onSizerShown()
+ {
+ updateImage();
+
+ if (initialFocusWidget_ != null)
+ FocusHelper.setFocusDeferred(initialFocusWidget_);
+ }
+
+
+ public int getImageWidth()
+ {
+ try
+ {
+ return Integer.parseInt(widthTextBox_.getText().trim());
+ }
+ catch(NumberFormatException ex)
+ {
+ setWidthTextBox(lastWidth_);
+ return lastWidth_;
+ }
+ }
+
+ public int getImageHeight()
+ {
+ try
+ {
+ return Integer.parseInt(heightTextBox_.getText().trim());
+ }
+ catch(NumberFormatException ex)
+ {
+ setHeightTextBox(lastHeight_);
+ return lastHeight_;
+ }
+ }
+
+ public boolean getKeepRatio()
+ {
+ return keepRatioCheckBox_.getValue();
+ }
+
+ public ImageFrame getImageFrame()
+ {
+ return imageFrame_;
+ }
+
+ private void setWidthTextBox(int width)
+ {
+ settingDimenensionInProgress_ = true;
+ lastWidth_ = width;
+ widthTextBox_.setText(Integer.toString(width));
+ settingDimenensionInProgress_ = false;
+ }
+
+
+ private void setHeightTextBox(int height)
+ {
+ settingDimenensionInProgress_ = true;
+ lastHeight_ = height;
+ heightTextBox_.setText(Integer.toString(height));
+ settingDimenensionInProgress_ = false;
+ }
+
+ private int constrainWidth(int width)
+ {
+ if (width < MIN_SIZE)
+ {
+ keepRatioCheckBox_.setValue(false);
+ return MIN_SIZE;
+ }
+ else
+ {
+ return width;
+ }
+ }
+
+ private int constrainHeight(int height)
+ {
+ if (height < MIN_SIZE)
+ {
+ keepRatioCheckBox_.setValue(false);
+ return MIN_SIZE;
+ }
+ else
+ {
+ return height;
+ }
+ }
+
+ private void setPreviewPanelSize(int width, int height)
+ {
+ Size maxSize = new Size(Window.getClientWidth() - 100,
+ Window.getClientHeight() - 200);
+
+ if (width <= maxSize.width && height <= maxSize.height)
+ {
+ previewPanel_.setVisible(true);
+ previewPanel_.setSize((width + IMAGE_INSET) + "px",
+ (height + IMAGE_INSET) + "px");
+ }
+ else
+ {
+ previewPanel_.setVisible(false);
+ }
+ }
+
+ private Label createImageOptionLabel(String text)
+ {
+ Label label = new Label(text);
+ label.setStylePrimaryName(
+ ExportPlotResources.INSTANCE.styles().imageOptionLabel());
+ return label;
+ }
+
+ private TextBox createImageSizeTextBox()
+ {
+ TextBox textBox = new TextBox();
+ textBox.setStylePrimaryName(
+ ExportPlotResources.INSTANCE.styles().imageSizeTextBox());
+ return textBox;
+ }
+
+
+
+ private void updateImage()
+ {
+ imageFrame_.setImageUrl(server_.getPlotExportUrl("png",
+ getImageWidth(),
+ getImageHeight(),
+ false));
+ }
+
+ private void configureHorizontalOptionsPanel(HorizontalPanel panel)
+ {
+ panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ panel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
+ }
+
+
+
+ private static final int IMAGE_INSET = 6;
+
+ private final ImageFrame imageFrame_;
+ private final TextBox widthTextBox_;
+ private final TextBox heightTextBox_;
+ private final CheckBox keepRatioCheckBox_;
+
+ private final Focusable initialFocusWidget_;
+
+ private final PlotsServerOperations server_;
+
+ private int lastWidth_;
+ private int lastHeight_ ;
+
+ private boolean settingDimenensionInProgress_ = false;
+
+ private final int MIN_SIZE = 100;
+ private LayoutPanel previewPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsHandler.java
new file mode 100644
index 0000000..9ec050d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsHandler.java
@@ -0,0 +1,224 @@
+/*
+ * SavePlotAsHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+
+public class SavePlotAsHandler
+{
+ public interface ServerOperations
+ {
+ void savePlot(FileSystemItem targetPath,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback);
+
+ String getFileUrl(FileSystemItem path);
+ }
+
+
+ public SavePlotAsHandler(GlobalDisplay globalDisplay,
+ ProgressIndicator progressIndicator,
+ ServerOperations server)
+ {
+ globalDisplay_ = globalDisplay;
+ progressIndicator_ = progressIndicator;
+ server_ = server;
+ }
+
+
+ public void attemptSave(FileSystemItem targetPath,
+ boolean overwrite,
+ boolean viewAfterSave,
+ final Operation onCompleted)
+ {
+ if (Desktop.isDesktop() || !viewAfterSave)
+ desktopSavePlotAs(targetPath, overwrite, viewAfterSave, onCompleted);
+ else
+ webSavePlotAs(targetPath, overwrite, viewAfterSave, onCompleted);
+
+ }
+
+
+ private void desktopSavePlotAs(final FileSystemItem targetPath,
+ boolean overwrite,
+ final boolean viewAfterSave,
+ Operation onCompleted)
+ {
+ progressIndicator_.onProgress("Converting Plot...");
+
+ savePlotAs(
+ targetPath,
+ overwrite,
+ viewAfterSave,
+ onCompleted,
+ new PlotSaveAsUIHandler() {
+ @Override
+ public void onSuccess()
+ {
+ progressIndicator_.clearProgress();
+
+ if (viewAfterSave)
+ {
+ RStudioGinjector.INSTANCE.getFileTypeRegistry().openFile(
+ targetPath);
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progressIndicator_.onError(error.getUserMessage());
+
+ }
+
+ @Override
+ public void onOverwritePrompt()
+ {
+ progressIndicator_.clearProgress();
+ }
+ });
+ }
+
+ private void webSavePlotAs(final FileSystemItem targetPath,
+ final boolean overwrite,
+ final boolean viewAfterSave,
+ final Operation onCompleted)
+ {
+ globalDisplay_.openProgressWindow("_blank",
+ "Converting Plot...",
+ new OperationWithInput<WindowEx>() {
+ public void execute(final WindowEx window)
+ {
+ savePlotAs(
+ targetPath,
+ overwrite,
+ viewAfterSave,
+ onCompleted,
+ new PlotSaveAsUIHandler() {
+ @Override
+ public void onSuccess()
+ {
+ // redirect window to view file
+ final String url = server_.getFileUrl(targetPath);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand(){
+ @Override
+ public void execute()
+ {
+ window.replaceLocationHref(url);
+ }
+ });
+
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ window.close();
+
+ globalDisplay_.showErrorMessage("Error Saving Plot",
+ error.getUserMessage());
+ }
+
+ @Override
+ public void onOverwritePrompt()
+ {
+ window.close();
+ }
+ });
+ }
+ });
+ }
+
+ private interface PlotSaveAsUIHandler
+ {
+ void onSuccess();
+ void onError(ServerError error);
+ void onOverwritePrompt();
+ }
+
+
+ private void savePlotAs(final FileSystemItem targetPath,
+ boolean overwrite,
+ final boolean viewAfterSave,
+ final Operation onCompleted,
+ final PlotSaveAsUIHandler uiHandler)
+ {
+ server_.savePlot(
+ targetPath,
+ overwrite,
+ new ServerRequestCallback<Bool>() {
+
+ @Override
+ public void onResponseReceived(Bool saved)
+ {
+ if (saved.getValue())
+ {
+ uiHandler.onSuccess();
+
+ // fire onCompleted
+ if (onCompleted != null)
+ onCompleted.execute();
+ }
+ else
+ {
+ uiHandler.onOverwritePrompt();
+
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "File Exists",
+ "The specified file name already exists. " +
+ "Do you want to overwrite it?",
+ new Operation() {
+ @Override
+ public void execute()
+ {
+ attemptSave(targetPath,
+ true,
+ viewAfterSave,
+ onCompleted);
+ }
+ },
+ true);
+
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ uiHandler.onError(error);
+ }
+
+ });
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private ProgressIndicator progressIndicator_;
+ private final ServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsImageDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsImageDialog.java
new file mode 100644
index 0000000..8bb71bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsImageDialog.java
@@ -0,0 +1,164 @@
+/*
+ * SavePlotAsImageDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageContext;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class SavePlotAsImageDialog extends ExportPlotDialog
+{
+ public SavePlotAsImageDialog(
+ GlobalDisplay globalDisplay,
+ PlotsServerOperations server,
+ SavePlotAsImageContext context,
+ final ExportPlotOptions options,
+ final OperationWithInput<ExportPlotOptions> onClose)
+ {
+ super(server, options);
+
+ setText("Save Plot as Image");
+
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ progressIndicator_ = addProgressIndicator();
+
+ ThemedButton saveButton = new ThemedButton("Save",
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ attemptSavePlot(false, new Operation() {
+ @Override
+ public void execute()
+ {
+ onClose.execute(getCurrentOptions(options));
+
+ closeDialog();
+ }
+ });
+ }
+ });
+ addOkButton(saveButton);
+ addCancelButton();
+
+ // file type and target path
+ saveAsTarget_ = new SavePlotAsTargetEditor(options.getFormat(),
+ context);
+
+ // view after size
+ viewAfterSaveCheckBox_ = new CheckBox("View plot after saving");
+ viewAfterSaveCheckBox_.setValue(options.getViewAfterSave());
+ addLeftWidget(viewAfterSaveCheckBox_);
+
+ }
+
+ @Override
+ protected Widget createTopLeftWidget()
+ {
+ return saveAsTarget_;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ super.onDialogShown();
+ saveAsTarget_.focus();
+ }
+
+ @Override
+ protected ExportPlotOptions getCurrentOptions(ExportPlotOptions previous)
+ {
+ ExportPlotSizeEditor sizeEditor = getSizeEditor();
+ return ExportPlotOptions.create(sizeEditor.getImageWidth(),
+ sizeEditor.getImageHeight(),
+ sizeEditor.getKeepRatio(),
+ saveAsTarget_.getFormat(),
+ viewAfterSaveCheckBox_.getValue(),
+ previous.getCopyAsMetafile());
+ }
+
+ private void attemptSavePlot(boolean overwrite,
+ final Operation onCompleted)
+ {
+ // get plot format
+ final String format = saveAsTarget_.getFormat();
+
+ // validate path
+ FileSystemItem targetPath = saveAsTarget_.getTargetPath();
+ if (targetPath == null)
+ {
+ globalDisplay_.showErrorMessage(
+ "File Name Required",
+ "You must provide a file name for the plot image.",
+ saveAsTarget_);
+ return;
+ }
+
+ // create handler
+ SavePlotAsHandler handler = new SavePlotAsHandler(
+ globalDisplay_,
+ progressIndicator_,
+ new SavePlotAsHandler.ServerOperations()
+ {
+ @Override
+ public void savePlot(
+ FileSystemItem targetPath,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback)
+ {
+ ExportPlotSizeEditor sizeEditor = getSizeEditor();
+ server_.savePlotAs(targetPath,
+ format,
+ sizeEditor.getImageWidth(),
+ sizeEditor.getImageHeight(),
+ overwrite,
+ requestCallback);
+ }
+
+ @Override
+ public String getFileUrl(FileSystemItem path)
+ {
+ return server_.getFileUrl(path);
+ }
+ });
+
+ // invoke handler
+ handler.attemptSave(targetPath,
+ overwrite,
+ viewAfterSaveCheckBox_.getValue(),
+ onCompleted);
+ }
+
+ private final GlobalDisplay globalDisplay_;
+ private ProgressIndicator progressIndicator_;
+ private final PlotsServerOperations server_;
+ private SavePlotAsTargetEditor saveAsTarget_;
+ private CheckBox viewAfterSaveCheckBox_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsPdfDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsPdfDialog.java
new file mode 100644
index 0000000..fc7e34d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsPdfDialog.java
@@ -0,0 +1,547 @@
+/*
+ * SavePlotAsPdfDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.server.Bool;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class SavePlotAsPdfDialog extends ModalDialogBase
+{
+ public SavePlotAsPdfDialog(GlobalDisplay globalDisplay,
+ PlotsServerOperations server,
+ final SessionInfo sessionInfo,
+ FileSystemItem defaultDirectory,
+ String defaultPlotName,
+ final SavePlotAsPdfOptions options,
+ final OperationWithInput<SavePlotAsPdfOptions> onClose)
+ {
+ setText("Save Plot as PDF");
+
+ globalDisplay_ = globalDisplay;
+ sessionInfo_ = sessionInfo;
+ server_ = server;
+ defaultDirectory_ = defaultDirectory;
+ defaultPlotName_ = defaultPlotName;
+ options_ = options;
+
+ progressIndicator_ = addProgressIndicator();
+
+ ThemedButton saveButton = new ThemedButton("Save",
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ attemptSavePdf(false, new Operation() {
+ @Override
+ public void execute()
+ {
+ // get options to send back to caller for persistence
+ PaperSize paperSize = paperSizeEditor_.selectedPaperSize();
+ SavePlotAsPdfOptions pdfOptions = SavePlotAsPdfOptions.create(
+ paperSize.getWidth(),
+ paperSize.getHeight(),
+ isPortraitOrientation(),
+ useCairoPdf(),
+ viewAfterSaveCheckBox_.getValue());
+
+ onClose.execute(pdfOptions);
+
+ closeDialog();
+ }
+ });
+ }
+ });
+ addOkButton(saveButton);
+ addCancelButton();
+
+
+ ThemedButton previewButton = new ThemedButton("Preview",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ // get temp file for preview
+ FileSystemItem tempDir =
+ FileSystemItem.createDir(sessionInfo.getTempDir());
+ FileSystemItem previewPath =
+ FileSystemItem.createFile(tempDir.completePath("preview.pdf"));
+
+ // invoke handler
+ SavePlotAsHandler handler = createSavePlotAsHandler();
+ handler.attemptSave(previewPath, true, true, null);
+ }
+ });
+ addLeftButton(previewButton);
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ fileNameTextBox_.setFocus(true);
+ fileNameTextBox_.selectAll();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ ExportPlotResources.Styles styles = ExportPlotResources.INSTANCE.styles();
+
+ Grid grid = new Grid(7, 2);
+ grid.setStylePrimaryName(styles.savePdfMainWidget());
+
+ // paper size
+ grid.setWidget(0, 0, new Label("PDF Size:"));
+
+ // paper size label
+ paperSizeEditor_ = new PaperSizeEditor();
+ grid.setWidget(0, 1, paperSizeEditor_);
+
+ // orientation
+ grid.setWidget(1, 0, new Label("Orientation:"));
+ HorizontalPanel orientationPanel = new HorizontalPanel();
+ orientationPanel.setSpacing(kComponentSpacing);
+ VerticalPanel orientationGroupPanel = new VerticalPanel();
+ final String kOrientationGroup = new String("Orientation");
+ portraitRadioButton_ = new RadioButton(kOrientationGroup, "Portrait");
+ orientationGroupPanel.add(portraitRadioButton_);
+ landscapeRadioButton_ = new RadioButton(kOrientationGroup, "Landscape");
+ orientationGroupPanel.add(landscapeRadioButton_);
+ orientationPanel.add(orientationGroupPanel);
+ grid.setWidget(1, 1, orientationPanel);
+
+ boolean haveCairoPdf = sessionInfo_.isCairoPdfAvailable();
+ if (haveCairoPdf)
+ grid.setWidget(2, 0, new Label("Options:"));
+ HorizontalPanel cairoPdfPanel = new HorizontalPanel();
+ String label = "Use cairo_pdf device";
+ if (BrowseCap.isMacintoshDesktop())
+ label = label + " (requires X11)";
+ chkCairoPdf_ = new CheckBox(label);
+ chkCairoPdf_.getElement().getStyle().setMarginLeft(kComponentSpacing,
+ Unit.PX);
+ cairoPdfPanel.add(chkCairoPdf_);
+ chkCairoPdf_.setValue(haveCairoPdf && options_.getCairoPdf());
+ if (haveCairoPdf)
+ grid.setWidget(2, 1, cairoPdfPanel);
+
+ grid.setWidget(3, 0, new HTML(" "));
+
+ ThemedButton directoryButton = new ThemedButton("Directory...");
+ directoryButton.setStylePrimaryName(styles.directoryButton());
+ directoryButton.getElement().getStyle().setMarginLeft(-2, Unit.PX);
+ grid.setWidget(4, 0, directoryButton);
+ directoryButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.chooseFolder(
+ "Choose Directory",
+ fileSystemContext_,
+ FileSystemItem.createDir(directoryLabel_.getTitle().trim()),
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ // update default
+ ExportPlot.setDefaultSaveDirectory(input);
+
+ // set display
+ setDirectory(input);
+ }
+ });
+ }
+ });
+
+
+ directoryLabel_ = new Label();
+ setDirectory(defaultDirectory_);
+ directoryLabel_.setStylePrimaryName(styles.savePdfDirectoryLabel());
+ grid.setWidget(4, 1, directoryLabel_);
+
+ Label fileNameLabel = new Label("File name:");
+ fileNameLabel.setStylePrimaryName(styles.savePdfFileNameLabel());
+ grid.setWidget(5, 0, fileNameLabel);
+ fileNameTextBox_ = new TextBox();
+ fileNameTextBox_.setText(defaultPlotName_);
+ fileNameTextBox_.setStylePrimaryName(styles.savePdfFileNameTextBox());
+ grid.setWidget(5, 1, fileNameTextBox_);
+
+
+ // view after size
+ viewAfterSaveCheckBox_ = new CheckBox("View plot after saving");
+ viewAfterSaveCheckBox_.setStylePrimaryName(
+ styles.savePdfViewAfterCheckbox());
+ viewAfterSaveCheckBox_.setValue(options_.getViewAfterSave());
+ grid.setWidget(6, 1, viewAfterSaveCheckBox_);
+
+ // set default value
+ if (options_.getPortrait())
+ portraitRadioButton_.setValue(true);
+ else
+ landscapeRadioButton_.setValue(true);
+
+ // return the widget
+ return grid;
+ }
+
+ private void attemptSavePdf(boolean overwrite,
+ final Operation onCompleted)
+ {
+ // validate file name
+ FileSystemItem targetPath = getTargetPath();
+ if (targetPath == null)
+ {
+ globalDisplay_.showErrorMessage(
+ "File Name Required",
+ "You must provide a file name for the plot pdf.",
+ fileNameTextBox_);
+ return;
+ }
+
+ // invoke handler
+ SavePlotAsHandler handler = createSavePlotAsHandler();
+ handler.attemptSave(targetPath,
+ overwrite,
+ viewAfterSaveCheckBox_.getValue(),
+ onCompleted);
+ }
+
+
+
+ private FileSystemItem getTargetPath()
+ {
+ return ExportPlot.composeTargetPath(".pdf", fileNameTextBox_, directory_);
+ }
+
+ private void setDirectory(FileSystemItem directory)
+ {
+ // set directory
+ directory_ = directory;
+
+ // set label
+ String dirLabel = ExportPlot.shortDirectoryName(directory, 250);
+ directoryLabel_.setText(dirLabel);
+
+ // set tooltip
+ directoryLabel_.setTitle(directory.getPath());
+ }
+
+
+
+
+ private boolean isPortraitOrientation()
+ {
+ return portraitRadioButton_.getValue();
+ }
+
+ private boolean useCairoPdf()
+ {
+ return chkCairoPdf_.getValue();
+ }
+
+ private class PaperSize
+ {
+ public PaperSize(String name, double width, double height)
+ {
+ name_ = name;
+ width_ = width;
+ height_ = height;
+ }
+
+ public String getName() { return name_; }
+ public double getWidth() { return width_; }
+ public double getHeight() { return height_; }
+
+ private final String name_ ;
+ private final double width_ ;
+ private final double height_ ;
+ }
+
+ private SavePlotAsHandler createSavePlotAsHandler()
+ {
+ return new SavePlotAsHandler(
+ globalDisplay_,
+ progressIndicator_,
+ new SavePlotAsHandler.ServerOperations()
+ {
+ @Override
+ public void savePlot(
+ FileSystemItem targetPath,
+ boolean overwrite,
+ ServerRequestCallback<Bool> requestCallback)
+ {
+ PaperSize paperSize = paperSizeEditor_.selectedPaperSize();
+ double width = paperSize.getWidth();
+ double height = paperSize.getHeight();
+ if (!isPortraitOrientation())
+ {
+ width = paperSize.getHeight();
+ height = paperSize.getWidth();
+ }
+
+ server_.savePlotAsPdf(targetPath,
+ width,
+ height,
+ chkCairoPdf_.getValue(),
+ overwrite,
+ requestCallback);
+ }
+
+ @Override
+ public String getFileUrl(FileSystemItem path)
+ {
+ return server_.getFileUrl(path);
+ }
+ });
+ }
+
+ private class PaperSizeEditor extends Composite
+ {
+ public PaperSizeEditor()
+ {
+ ExportPlotResources.Styles styles =
+ ExportPlotResources.INSTANCE.styles();
+
+ paperSizes_.add(new PaperSize("US Letter", 8.5, 11));
+ paperSizes_.add(new PaperSize("US Legal", 8.5, 14));
+ paperSizes_.add(new PaperSize("A4", 8.27, 11.69));
+ paperSizes_.add(new PaperSize("A5", 5.83, 8.27));
+ paperSizes_.add(new PaperSize("A6", 4.13, 5.83));
+ paperSizes_.add(new PaperSize("4 x 6 in.", 4, 6));
+ paperSizes_.add(new PaperSize("5 x 7 in.", 5, 7));
+ paperSizes_.add(new PaperSize("6 x 8 in.", 6, 8));
+
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ panel.setSpacing(kComponentSpacing);
+
+ // paper size list box
+ int selectedPaperSize = -1;
+ paperSizeListBox_ = new ListBox();
+ paperSizeListBox_.setStylePrimaryName(styles.savePdfSizeListBox());
+ for (int i=0; i<paperSizes_.size(); i++)
+ {
+ PaperSize paperSize = paperSizes_.get(i);
+ paperSizeListBox_.addItem(paperSize.getName());
+ if (paperSize.getWidth() == options_.getWidth() &&
+ paperSize.getHeight() == options_.getHeight())
+ {
+ selectedPaperSize = i;
+ }
+ }
+ PaperSize customPaperSize = new PaperSize("(Custom)", 8.5, 11);
+ paperSizes_.add(customPaperSize);
+ paperSizeListBox_.addItem(customPaperSize.getName());
+
+ if (selectedPaperSize == -1)
+ {
+ setCustomPaperSize(options_.getWidth(), options_.getHeight());
+ selectedPaperSize = paperSizes_.size() - 1;
+ }
+
+ paperSizeListBox_.addChangeHandler(new ChangeHandler() {
+ public void onChange(ChangeEvent event)
+ {
+ updateSizeDescription();
+ }
+ });
+ panel.add(paperSizeListBox_);
+
+ HorizontalPanel editPanel = new HorizontalPanel();
+ widthTextBox_ = new TextBox();
+ widthTextBox_.setStylePrimaryName(styles.savePdfPaperSizeTextBox());
+ widthTextBox_.addChangeHandler(sizeTextBoxChangeHandler_);
+ editPanel.add(widthTextBox_);
+
+ Label label = new Label("x");
+ label.setStylePrimaryName(styles.savePdfPaperSizeX());
+ editPanel.add(label);
+
+ heightTextBox_ = new TextBox();
+ heightTextBox_.setStylePrimaryName(styles.savePdfPaperSizeTextBox());
+ heightTextBox_.addChangeHandler(sizeTextBoxChangeHandler_);
+ editPanel.add(heightTextBox_);
+ panel.add(editPanel);
+
+ Label inchesLabel = new Label("inches");
+ inchesLabel.setStylePrimaryName(styles.savePdfPaperSizeX());
+ editPanel.add(inchesLabel);
+
+ paperSizeListBox_.setSelectedIndex(selectedPaperSize);
+ updateSizeDescription();
+
+ initWidget(panel);
+ }
+
+ public PaperSize selectedPaperSize()
+ {
+ int selectedSize = paperSizeListBox_.getSelectedIndex();
+ return paperSizes_.get(selectedSize);
+ }
+
+ private void updateSizeDescription()
+ {
+ setPaperSize(selectedPaperSize());
+ }
+
+ private void setPaperSize(PaperSize paperSize)
+ {
+ widthTextBox_.setText(sizeFormat_.format(paperSize.getWidth()));
+ heightTextBox_.setText(sizeFormat_.format(paperSize.getHeight()));
+ }
+
+ private void setCustomPaperSize(double width, double height)
+ {
+ paperSizes_.remove(paperSizes_.size() - 2);
+ paperSizes_.add(new PaperSize("(Custom)", width, height));
+ }
+
+ private ChangeHandler sizeTextBoxChangeHandler_ = new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ // read width and height
+ PaperSize defaultSize = selectedPaperSize();
+ double width = readSizeEntry(widthTextBox_, defaultSize.getWidth());
+ double height = readSizeEntry(heightTextBox_,
+ defaultSize.getHeight());
+
+ // see if it matches an existing size
+ int sizeIndex = -1;
+ for (int i=0; i<paperSizes_.size(); i++)
+ {
+ PaperSize paperSize = paperSizes_.get(i);
+ if (paperSize.getWidth() == width &&
+ paperSize.getHeight() == height)
+ {
+ sizeIndex = i;
+ break;
+ }
+ }
+
+ // if it doesn't then update custom
+ if (sizeIndex == -1)
+ {
+ setCustomPaperSize(width, height);
+ sizeIndex = paperSizes_.size() - 1;
+ }
+
+ // select
+ paperSizeListBox_.setSelectedIndex(sizeIndex);
+ }
+ };
+
+ private double readSizeEntry(TextBox textBox, double defaultValue)
+ {
+ double size = defaultValue;
+ try
+ {
+ size = Double.parseDouble(textBox.getText().trim());
+
+ if (size < kMimimumSize)
+ size = defaultValue;
+ else if (size > kMaximumSize)
+ size = defaultValue;
+ }
+ catch(NumberFormatException e)
+ {
+ }
+ textBox.setText(sizeFormat_.format(size));
+ return size;
+ }
+
+
+ private ListBox paperSizeListBox_;
+ private final TextBox widthTextBox_;
+ private final TextBox heightTextBox_;
+ private final List<PaperSize> paperSizes_ = new ArrayList<PaperSize>();
+ private final NumberFormat sizeFormat_ = NumberFormat.getFormat("##0.00");
+
+ private final double kMimimumSize = 3.0;
+ private final double kMaximumSize = 100.0;
+ }
+
+
+
+ private final GlobalDisplay globalDisplay_;
+ private final SessionInfo sessionInfo_;
+ private final PlotsServerOperations server_;
+ private final SavePlotAsPdfOptions options_;
+ private final FileSystemItem defaultDirectory_;
+ private final String defaultPlotName_;
+ private final ProgressIndicator progressIndicator_;
+
+ private TextBox fileNameTextBox_;
+ private FileSystemItem directory_;
+ private Label directoryLabel_;
+ private PaperSizeEditor paperSizeEditor_ ;
+
+ private RadioButton portraitRadioButton_ ;
+ private RadioButton landscapeRadioButton_;
+
+ private CheckBox chkCairoPdf_;
+ private CheckBox viewAfterSaveCheckBox_;
+
+ final int kComponentSpacing = 7;
+
+ private final FileSystemContext fileSystemContext_ =
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext();
+
+ private final FileDialogs fileDialogs_ =
+ RStudioGinjector.INSTANCE.getFileDialogs();
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsTargetEditor.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsTargetEditor.java
new file mode 100644
index 0000000..4730b77
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/SavePlotAsTargetEditor.java
@@ -0,0 +1,182 @@
+/*
+ * SavePlotAsTargetEditor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export;
+
+import java.util.HashMap;
+
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageContext;
+import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsImageFormat;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+
+public class SavePlotAsTargetEditor extends Composite implements CanFocus
+{
+ public SavePlotAsTargetEditor(String defaultFormat,
+ SavePlotAsImageContext context)
+ {
+ context_ = context;
+
+ ExportPlotResources.Styles styles = ExportPlotResources.INSTANCE.styles();
+
+ Grid grid = new Grid(3, 2);
+ grid.setCellPadding(0);
+
+ Label imageFormatLabel = new Label("Image format:");
+ imageFormatLabel.setStylePrimaryName(styles.exportTargetLabel());
+
+ grid.setWidget(0, 0, imageFormatLabel);
+ imageFormatListBox_ = new ListBox();
+ JsArray<SavePlotAsImageFormat> formats = context.getFormats();
+ int selectedIndex = 0;
+ for (int i=0; i<formats.length(); i++)
+ {
+ SavePlotAsImageFormat format = formats.get(i);
+ if (format.getExtension().equals(defaultFormat))
+ selectedIndex = i;
+ imageFormatListBox_.addItem(format.getName(), format.getExtension());
+ }
+ imageFormatListBox_.setSelectedIndex(selectedIndex);
+ imageFormatListBox_.setStylePrimaryName(styles.imageFormatListBox());
+ grid.setWidget(0, 1, imageFormatListBox_);
+
+ ThemedButton directoryButton = new ThemedButton("Directory...");
+ directoryButton.setStylePrimaryName(styles.directoryButton());
+ directoryButton.getElement().getStyle().setMarginLeft(-2, Unit.PX);
+ grid.setWidget(1, 0, directoryButton);
+ directoryButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fileDialogs_.chooseFolder(
+ "Choose Directory",
+ fileSystemContext_,
+ FileSystemItem.createDir(directoryLabel_.getTitle().trim()),
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ indicator.onCompleted();
+
+ // update default
+ ExportPlot.setDefaultSaveDirectory(input);
+
+ // set display
+ setDirectory(input);
+ }
+ });
+ }
+ });
+
+ directoryLabel_ = new Label();
+ setDirectory(context_.getDirectory());
+ directoryLabel_.setStylePrimaryName(styles.directoryLabel());
+ grid.setWidget(1, 1, directoryLabel_);
+
+ Label fileNameLabel = new Label("File name:");
+ fileNameLabel.setStylePrimaryName(styles.fileNameLabel());
+ grid.setWidget(2, 0, fileNameLabel);
+ fileNameTextBox_ = new TextBox();
+ fileNameTextBox_.setText(context.getUniqueFileStem());
+ fileNameTextBox_.setStylePrimaryName(styles.fileNameTextBox());
+ grid.setWidget(2, 1, fileNameTextBox_);
+
+ initWidget(grid);
+ }
+
+ public void focus()
+ {
+ fileNameTextBox_.setFocus(true);
+ fileNameTextBox_.selectAll();
+ }
+
+ public String getFormat()
+ {
+ return imageFormatListBox_.getValue(
+ imageFormatListBox_.getSelectedIndex());
+ }
+
+ public FileSystemItem getTargetPath()
+ {
+ // first determine format extension
+ String ext = "." + imageFormatListBox_.getValue(
+ imageFormatListBox_.getSelectedIndex());
+
+ return ExportPlot.composeTargetPath(ext, fileNameTextBox_, directory_);
+ }
+
+ public FileSystemItem getTargetDirectory()
+ {
+ return directory_;
+ }
+
+
+ private void setDirectory(FileSystemItem directory)
+ {
+ // set directory
+ directory_ = directory;
+
+ // set label
+ String dirLabel = ExportPlot.shortDirectoryName(directory, 250);
+ directoryLabel_.setText(dirLabel);
+
+ // set tooltip
+ directoryLabel_.setTitle(directory.getPath());
+ }
+
+
+ private ListBox imageFormatListBox_;
+ private TextBox fileNameTextBox_;
+ private FileSystemItem directory_;
+ private Label directoryLabel_;
+
+
+ private final SavePlotAsImageContext context_;
+
+ private final FileSystemContext fileSystemContext_ =
+ RStudioGinjector.INSTANCE.getRemoteFileSystemContext();
+
+ private final FileDialogs fileDialogs_ =
+ RStudioGinjector.INSTANCE.getFileDialogs();
+
+ // remember what directory was chosen for plot export for various
+ // working directories
+ static HashMap<String, FileSystemItem> initialDirectories_ =
+ new HashMap<String,FileSystemItem>();
+
+
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopDialog.java
new file mode 100644
index 0000000..5aa42eb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopDialog.java
@@ -0,0 +1,104 @@
+/*
+ * CopyPlotToClipboardDesktopDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export.impl;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.ElementEx;
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.widget.ImageFrame;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotDialog;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+
+public class CopyPlotToClipboardDesktopDialog extends ExportPlotDialog
+{
+
+ public CopyPlotToClipboardDesktopDialog(
+ PlotsServerOperations server,
+ final ExportPlotOptions options,
+ final OperationWithInput<ExportPlotOptions> onClose)
+ {
+ super(server, options);
+
+ setText("Copy Plot to Clipboard");
+
+ ThemedButton copyButton = new ThemedButton("Copy Plot",
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ // do the copy
+ performCopy(new Operation() {
+
+ @Override
+ public void execute()
+ {
+ // save options
+ onClose.execute(getCurrentOptions(options));
+
+ // close dialog
+ closeDialog();
+ }
+ });
+ }
+ });
+
+ addOkButton(copyButton);
+ addCancelButton();
+ }
+
+
+ protected void performCopy(Operation onCompleted)
+ {
+ copyAsBitmap(onCompleted);
+ }
+
+ protected void copyAsBitmap(Operation onCompleted)
+ {
+ ImageFrame imageFrame = getSizeEditor().getImageFrame();
+ final WindowEx win = imageFrame.getElement().<IFrameElementEx>cast()
+ .getContentWindow();
+
+ Document doc = win.getDocument();
+ NodeList<Element> images = doc.getElementsByTagName("img");
+ if (images.getLength() > 0)
+ {
+ ElementEx img = images.getItem(0).cast();
+
+ if (BrowseCap.isCocoaDesktop()) {
+ win.focus();
+ DomUtils.selectElement(img);
+ }
+
+ Desktop.getFrame().copyImageToClipboard(img.getClientLeft(),
+ img.getClientTop(),
+ img.getClientWidth(),
+ img.getClientHeight());
+ }
+
+ onCompleted.execute();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopMetafileDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopMetafileDialog.java
new file mode 100644
index 0000000..455fb7f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardDesktopMetafileDialog.java
@@ -0,0 +1,115 @@
+/*
+ * CopyPlotToClipboardDesktopMetafileDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export.impl;
+
+
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotResources;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotSizeEditor;
+
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RadioButton;
+
+public class CopyPlotToClipboardDesktopMetafileDialog extends CopyPlotToClipboardDesktopDialog
+{
+
+ public CopyPlotToClipboardDesktopMetafileDialog(
+ PlotsServerOperations server,
+ ExportPlotOptions options,
+ OperationWithInput<ExportPlotOptions> onClose)
+ {
+ super(server, options, onClose);
+
+
+ ExportPlotResources.Styles styles = ExportPlotResources.INSTANCE.styles();
+
+ Label label = new Label();
+ label.setStylePrimaryName(styles.copyFormatLabel());
+ label.setText("Copy as:");
+ addLeftWidget(label);
+
+ copyAsBitmapRadioButton_ = new RadioButton(
+ "Format",
+ SafeHtmlUtils.fromString("Bitmap"));
+ copyAsBitmapRadioButton_.setStylePrimaryName(styles.copyFormatBitmap());
+ addLeftWidget(copyAsBitmapRadioButton_);
+
+ copyAsMetafileRadioButton_ = new RadioButton(
+ "Format",
+ SafeHtmlUtils.fromString("Metafile"));
+ copyAsMetafileRadioButton_.setStylePrimaryName(styles.copyFormatMetafile());
+ addLeftWidget(copyAsMetafileRadioButton_);
+
+ if (options.getCopyAsMetafile())
+ copyAsMetafileRadioButton_.setValue(true);
+ else
+ copyAsBitmapRadioButton_.setValue(true);
+ }
+
+
+ @Override
+ protected void performCopy(Operation onCompleted)
+ {
+ if (getCopyAsMetafile())
+ copyAsMetafile(onCompleted);
+ else
+ copyAsBitmap(onCompleted);
+ }
+
+
+ @Override
+ protected ExportPlotOptions getCurrentOptions(ExportPlotOptions previous)
+ {
+ ExportPlotSizeEditor sizeEditor = getSizeEditor();
+ return ExportPlotOptions.create(sizeEditor.getImageWidth(),
+ sizeEditor.getImageHeight(),
+ sizeEditor.getKeepRatio(),
+ previous.getFormat(),
+ previous.getViewAfterSave(),
+ getCopyAsMetafile());
+ }
+
+
+ private boolean getCopyAsMetafile()
+ {
+ return copyAsMetafileRadioButton_.getValue();
+ }
+
+ private void copyAsMetafile(final Operation onCompleted)
+ {
+ ExportPlotSizeEditor sizeEditor = getSizeEditor();
+ server_.copyPlotToClipboardMetafile(
+ sizeEditor.getImageWidth(),
+ sizeEditor.getImageHeight(),
+ new SimpleRequestCallback<Void>()
+ {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ onCompleted.execute();
+ }
+ });
+ }
+
+ private RadioButton copyAsBitmapRadioButton_;
+ private RadioButton copyAsMetafileRadioButton_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardWebDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardWebDialog.java
new file mode 100644
index 0000000..18c8bec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/CopyPlotToClipboardWebDialog.java
@@ -0,0 +1,72 @@
+/*
+ * CopyPlotToClipboardWebDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export.impl;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotDialog;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlotResources;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+
+public class CopyPlotToClipboardWebDialog extends ExportPlotDialog
+{
+
+ public CopyPlotToClipboardWebDialog(
+ PlotsServerOperations server,
+ final ExportPlotOptions options,
+ final OperationWithInput<ExportPlotOptions> onClose)
+ {
+ super(server, options);
+
+ setText("Copy Plot to Clipboard");
+
+ ExportPlotResources resources = ExportPlotResources.INSTANCE;
+
+ ThemedButton closeButton = new ThemedButton("Close",
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ // save options
+ onClose.execute(getCurrentOptions(options));
+
+ // close dialog
+ closeDialog();
+ }
+ });
+ addCancelButton(closeButton);
+
+
+ HorizontalPanel infoPanel = new HorizontalPanel();
+
+ Image rightMouseImage = new Image(resources.rightMouse());
+ infoPanel.add(rightMouseImage);
+
+ Label label = new Label("Right click on the plot image above to " +
+ "copy to the clipboard.");
+ label.setStylePrimaryName(resources.styles().rightClickCopyLabel());
+ infoPanel.add(label);
+
+ addLeftWidget(infoPanel);
+
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotDesktop.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotDesktop.java
new file mode 100644
index 0000000..f224c42
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotDesktop.java
@@ -0,0 +1,51 @@
+/*
+ * ExportPlotDesktop.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export.impl;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot;
+
+public class ExportPlotDesktop extends ExportPlot
+{
+
+ @Override
+ public void copyPlotToClipboard(
+ PlotsServerOperations server,
+ ExportPlotOptions options,
+ OperationWithInput<ExportPlotOptions> onClose)
+ {
+ if (Desktop.getFrame().supportsClipboardMetafile())
+ {
+ new CopyPlotToClipboardDesktopMetafileDialog(server,
+ options,
+ onClose).showModal();
+ }
+ else if (!BrowseCap.isCocoaDesktop())
+ {
+ new CopyPlotToClipboardDesktopDialog(server,
+ options,
+ onClose).showModal();
+ }
+ else
+ {
+ new CopyPlotToClipboardWebDialog(server, options, onClose).showModal();
+ }
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotWeb.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotWeb.java
new file mode 100644
index 0000000..da2178f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/impl/ExportPlotWeb.java
@@ -0,0 +1,33 @@
+/*
+ * ExportPlotWeb.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.export.impl;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.workbench.views.plots.model.ExportPlotOptions;
+import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
+import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot;
+
+public class ExportPlotWeb extends ExportPlot
+{
+ @Override
+ public void copyPlotToClipboard(
+ PlotsServerOperations server,
+ ExportPlotOptions options,
+ OperationWithInput<ExportPlotOptions> onClose)
+ {
+ new CopyPlotToClipboardWebDialog(server, options, onClose).showModal();
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/rightMouse.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/rightMouse.png
new file mode 100644
index 0000000..6d405ae
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/export/rightMouse.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorChangedHandler.java
new file mode 100644
index 0000000..943a1eb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ManipulatorChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import com.google.gwt.json.client.JSONObject;
+
+public interface ManipulatorChangedHandler
+{
+ void onManipulatorChanged(JSONObject values);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControl.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControl.java
new file mode 100644
index 0000000..a73cec5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControl.java
@@ -0,0 +1,61 @@
+/*
+ * ManipulatorControl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.core.client.widget.CanFocus;
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.client.ui.Composite;
+
+public abstract class ManipulatorControl extends Composite implements CanFocus
+{
+ public ManipulatorControl(String variable,
+ Manipulator.Control control,
+ ManipulatorChangedHandler changedHandler)
+ {
+ super();
+ variable_ = variable;
+ if (control.getLabel() != null)
+ label_ = control.getLabel();
+ else
+ label_ = variable;
+ changedHandler_ = changedHandler;
+ }
+
+ protected void addControlStyle(String derivedStyleName)
+ {
+ addStyleName(ManipulatorResources.INSTANCE.manipulatorStyles().control());
+ addStyleName(derivedStyleName);
+ }
+
+ protected String getLabel()
+ {
+ return label_;
+ }
+
+ protected void onValueChanged(JSONValue value)
+ {
+ JSONObject values = new JSONObject();
+ values.put(variable_, value);
+ changedHandler_.onManipulatorChanged(values);
+ }
+
+
+ private final String variable_;
+ private final String label_;
+ private final ManipulatorChangedHandler changedHandler_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlButton.java
new file mode 100644
index 0000000..c729a50
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlButton.java
@@ -0,0 +1,59 @@
+/*
+ * ManipulatorControlButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.json.client.JSONBoolean;
+
+
+public class ManipulatorControlButton extends ManipulatorControl
+{
+ public ManipulatorControlButton(
+ String variable,
+ Manipulator.Button button,
+ final ManipulatorChangedHandler changedHandler)
+ {
+ super(variable, button, changedHandler);
+
+ // button
+ button_ = new ThemedButton(button.getLabel());
+ button_.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ ManipulatorControlButton.this.onValueChanged(
+ JSONBoolean.getInstance(true));
+ }
+ });
+
+ initWidget(button_);
+
+ addControlStyle(
+ ManipulatorResources.INSTANCE.manipulatorStyles().button());
+ }
+
+ @Override
+ public void focus()
+ {
+
+ }
+
+ private ThemedButton button_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlCheckBox.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlCheckBox.java
new file mode 100644
index 0000000..6421b3d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlCheckBox.java
@@ -0,0 +1,78 @@
+/*
+ * ManipulatorControlCheckBox.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.json.client.JSONBoolean;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+
+public class ManipulatorControlCheckBox extends ManipulatorControl
+{
+
+ public ManipulatorControlCheckBox(String variable,
+ boolean value,
+ Manipulator.CheckBox checkBox,
+ final ManipulatorChangedHandler changedHandler)
+ {
+ super(variable, checkBox, changedHandler);
+
+ // get manipulator styles
+ ManipulatorStyles styles = ManipulatorResources.INSTANCE.manipulatorStyles();
+
+ // main control
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ panel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
+
+ // checkbox
+ checkBox_ = new CheckBox();
+ checkBox_.setValue(value);
+ checkBox_.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ ManipulatorControlCheckBox.this.onValueChanged(
+ JSONBoolean.getInstance(checkBox_.getValue()));
+
+ }
+ });
+ panel.add(checkBox_);
+
+ // label
+ Label label = new Label(getLabel());
+ panel.add(label);
+
+
+ initWidget(panel);
+ addControlStyle(styles.checkBox());
+ }
+
+ @Override
+ public void focus()
+ {
+ checkBox_.setFocus(true);
+ }
+
+ private CheckBox checkBox_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlPicker.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlPicker.java
new file mode 100644
index 0000000..7cd667b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlPicker.java
@@ -0,0 +1,89 @@
+/*
+ * ManipulatorControlPicker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+
+public class ManipulatorControlPicker extends ManipulatorControl
+{
+
+ public ManipulatorControlPicker(
+ String variable,
+ String value,
+ Manipulator.Picker picker,
+ final ManipulatorChangedHandler changedHandler)
+ {
+ super(variable, picker, changedHandler);
+
+ // get manipulator styles
+ ManipulatorStyles styles = ManipulatorResources.INSTANCE.manipulatorStyles();
+
+ // main control
+ HorizontalPanel panel = new HorizontalPanel();
+ panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+ panel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
+
+ // caption
+ Label captionLabel = new Label();
+ captionLabel.setStyleName(styles.captionLabel());
+ captionLabel.setText(getLabel() + ":");
+ panel.add(captionLabel);
+
+ // picker
+ listBox_ = new ListBox();
+ listBox_.setVisibleItemCount(1);
+ JsArrayString choices = picker.getChoices();
+ int selectedIndex = 0;
+ for (int i=0; i<choices.length(); i++)
+ {
+ String choice = choices.get(i);
+ listBox_.addItem(choice);
+ if (choice.equals(value))
+ selectedIndex = i;
+ }
+ listBox_.setSelectedIndex(selectedIndex);
+ listBox_.addChangeHandler(new ChangeHandler(){
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ ManipulatorControlPicker.this.onValueChanged(
+ new JSONString(listBox_.getItemText(listBox_.getSelectedIndex())));
+ }
+ });
+ panel.add(listBox_);
+
+ initWidget(panel);
+ addControlStyle(styles.picker());
+ }
+
+ @Override
+ public void focus()
+ {
+ listBox_.setFocus(true);
+ }
+
+ private ListBox listBox_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlSlider.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlSlider.java
new file mode 100644
index 0000000..1b2efc3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorControlSlider.java
@@ -0,0 +1,137 @@
+/*
+ * ManipulatorControlSlider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.widgetideas.client.SliderBar;
+
+ at SuppressWarnings("deprecation")
+public class ManipulatorControlSlider extends ManipulatorControl
+ implements SliderBar.LabelFormatter
+{
+ public ManipulatorControlSlider(String variable,
+ double value,
+ Manipulator.Slider slider,
+ ManipulatorChangedHandler changedHandler)
+ {
+ super(variable, slider, changedHandler);
+
+ // get manipulator styles
+ ManipulatorStyles styles = ManipulatorResources.INSTANCE.manipulatorStyles();
+
+ // containing panel
+ VerticalPanel panel = new VerticalPanel();
+
+ // setup caption panel and add it
+ HorizontalPanel captionPanel = new HorizontalPanel();
+
+ Label captionLabel = new Label();
+ captionLabel.setStyleName(styles.captionLabel());
+ captionLabel.setText(getLabel() + ":");
+ captionPanel.add(captionLabel);
+ final Label valueLabel = new Label();
+ valueLabel.setStyleName(styles.sliderValueLabel());
+ captionPanel.add(valueLabel);
+ panel.add(captionPanel);
+
+ // create with range and custom formatter
+ final double min = slider.getMin();
+ final double max = slider.getMax();
+ final double range = max - min;
+ sliderBar_ = new SliderBar(min, max, this);
+
+ // show labels only at the beginning and end
+ sliderBar_.setNumLabels(1);
+
+ // set step size (default to 1 or continuous decimal as appropriate)
+ double step = slider.getStep();
+ if (step == -1)
+ {
+ // short range or decimals means continous decimal
+ if (range < 2 || hasDecimals(max) || hasDecimals(min) )
+ step = range / 250; // ~ one step per pixel
+ else
+ step = 1;
+ }
+ sliderBar_.setStepSize(step);
+
+ // optional tick marks
+ if (slider.getTicks())
+ {
+ double numTicks = range / step;
+ if (numTicks <= 25) // no more than 25 ticks
+ sliderBar_.setNumTicks(new Double(numTicks).intValue());
+ else
+ sliderBar_.setNumTicks(1);
+ }
+ else
+ {
+ // always at beginning and end
+ sliderBar_.setNumTicks(1);
+ }
+
+ // update label on change
+ sliderBar_.addChangeListener(new ChangeListener() {
+ @Override
+ public void onChange(Widget sender)
+ {
+ valueLabel.setText(formatLabel(sliderBar_,
+ sliderBar_.getCurrentValue()));
+ }
+ });
+ sliderBar_.setCurrentValue(value);
+
+ // fire changed even on slide completed
+ sliderBar_.addSlideCompletedListener(new ChangeListener() {
+ @Override
+ public void onChange(Widget sender)
+ {
+ ManipulatorControlSlider.this.onValueChanged(
+ new JSONNumber(sliderBar_.getCurrentValue()));
+ }
+
+ });
+
+ // add slider bar and fully initialize widget
+ panel.add(sliderBar_);
+ initWidget(panel);
+ addControlStyle(styles.slider());
+ }
+
+ @Override
+ public void focus()
+ {
+ sliderBar_.setFocus(true);
+ }
+
+
+ @Override
+ public String formatLabel(SliderBar slider, double value)
+ {
+ return StringUtil.prettyFormatNumber(value);
+ }
+
+ private static boolean hasDecimals(double value)
+ {
+ double truncatedValue = (double)(Math.round(value));
+ return value != truncatedValue;
+ }
+
+ private SliderBar sliderBar_ ;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorManager.java
new file mode 100644
index 0000000..1bc88cb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorManager.java
@@ -0,0 +1,190 @@
+/*
+ * ManipulatorManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.core.client.widget.ProgressImage;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+
+public class ManipulatorManager
+{
+ public ManipulatorManager(Panel plotsSurface,
+ Commands commands,
+ ManipulatorChangedHandler changedHandler,
+ final ClickHandler plotsClickHandler)
+ {
+ ManipulatorResources resources = ManipulatorResources.INSTANCE;
+ ManipulatorStyles styles = ManipulatorStyles.INSTANCE;
+
+ // references
+ plotsSurface_ = plotsSurface;
+
+ // no manipulator to start
+ manipulator_ = null;
+ manipulatorPopup_ = null;
+
+ // create manipulator button
+ manipulatorButton_ = new ToolbarButton(
+ resources.manipulateButton(),
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ showManipulatorPopup();
+ }
+ });
+ manipulatorButton_.addStyleName(styles.manipulateButton());
+ manipulatorButton_.setTitle(commands.showManipulator().getTooltip());
+ plotsSurface_.add(manipulatorButton_);
+ manipulatorButton_.setVisible(false);
+ manipulatorProgress_ = new ProgressImage(resources.manipulateProgress());
+ manipulatorProgress_.addStyleName(styles.manipulateProgress());
+ plotsSurface_.add(manipulatorProgress_);
+ manipulatorProgress_.setVisible(false);
+
+ // create manipulator popup panel
+ manipulatorPopup_ = new ManipulatorPopupPanel(changedHandler);
+ manipulatorPopup_.addAutoHidePartner(plotsSurface_.getElement());
+
+ // forward click event to caller
+ plotsSurface_.addDomHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (manipulator_ != null)
+ plotsClickHandler.onClick(event);
+ }
+
+ }, ClickEvent.getType());
+ }
+
+
+
+
+ public void setManipulator(Manipulator manipulator, boolean show)
+ {
+ if (isNewManipulatorState(manipulator))
+ {
+ // set active manipulator
+ manipulator_ = manipulator;
+
+ // set visibility of manipulator button
+ manipulatorButton_.setVisible(manipulator_ != null);
+
+ // update UI
+ manipulatorPopup_.update(manipulator_);
+
+ // if we have a manipulator then show if requested, otherwise hide
+ if (manipulator_ != null)
+ {
+ // show if requested and there are controls
+ if (show && manipulator_.hasControls())
+ showManipulatorPopup();
+ }
+ else
+ {
+ manipulatorPopup_.hide();
+ }
+ }
+ }
+
+ public void showManipulator()
+ {
+ if (manipulator_ != null)
+ showManipulatorPopup();
+ }
+
+ public void setProgress(boolean showProgress)
+ {
+ if (showProgress)
+ {
+ manipulatorButton_.setVisible(false);
+ manipulatorProgress_.show(true);
+ }
+ else
+ {
+ manipulatorProgress_.show(false);
+ manipulatorButton_.setVisible(manipulator_ != null);
+ }
+ }
+
+
+ private boolean isNewManipulatorState(Manipulator manipulator)
+ {
+ if (manipulator_ == null && manipulator == null)
+ return false;
+ if (manipulator_ == null && manipulator != null)
+ return true;
+ else if (manipulator == null && manipulator_ != null)
+ return true;
+ else if (!manipulator_.getID().equals(manipulator.getID()))
+ return true;
+ else
+ return false;
+ }
+
+ private void showManipulatorPopup()
+ {
+ // show it if necessary
+ if (!manipulatorPopup_.isShowing())
+ {
+ // don't do it if the plots tab is currently hidden
+ // NOTE: in this case we should really post back to show the
+ // manipulator after the plots tab finishes animating to full
+ // visibility. this fix at least gets us out of the bizzaro UI
+ // state of the manipulator existing below the workbench
+ if (plotsSurface_.getOffsetHeight() == 0)
+ return;
+
+ manipulatorPopup_.recordPreviouslyFocusedElement();
+
+ manipulatorPopup_.setPopupPositionAndShow(new PositionCallback(){
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ // position the manipulator to the right if necessary
+ int xPos = plotsSurface_.getAbsoluteLeft() - offsetWidth + 20;
+ if (xPos < 0)
+ {
+ xPos = plotsSurface_.getAbsoluteLeft() +
+ plotsSurface_.getOffsetWidth() - 20;
+ }
+
+ manipulatorPopup_.setPopupPosition(
+ xPos,
+ plotsSurface_.getAbsoluteTop() - 6);
+
+ manipulatorPopup_.focusFirstControl();
+ }
+
+ }) ;
+
+
+ }
+ }
+
+
+ private final Panel plotsSurface_;
+ private Manipulator manipulator_;
+ private ToolbarButton manipulatorButton_;
+ private ProgressImage manipulatorProgress_;
+ private ManipulatorPopupPanel manipulatorPopup_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorPopupPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorPopupPanel.java
new file mode 100644
index 0000000..20dac2b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorPopupPanel.java
@@ -0,0 +1,185 @@
+/*
+ * ManipulatorPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.widget.FocusHelper;
+import org.rstudio.core.client.widget.MiniDialogPopupPanel;
+import org.rstudio.studio.client.workbench.views.plots.model.Manipulator;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class ManipulatorPopupPanel extends MiniDialogPopupPanel
+
+{
+ public ManipulatorPopupPanel(final ManipulatorChangedHandler changedHandler)
+ {
+ super(true, false);
+
+ changedHandler_ = changedHandler;
+
+
+ setCaption("Manipulate");
+
+
+
+
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ mainPanel_ = new VerticalPanel();
+ mainPanel_.setWidth("250px");
+ return mainPanel_;
+ }
+
+ public void update(Manipulator manipulator)
+ {
+ mainPanel_.clear();
+
+ if (manipulator != null && manipulator.getVariables() != null)
+ {
+ // iterate over the variables
+ JsArrayString variables = manipulator.getVariables();
+ for (int i=0; i<variables.length(); i++)
+ {
+ String variable = variables.get(i);
+ try
+ {
+ ManipulatorControl addedControl = null;
+ Manipulator.Control control = manipulator.getControl(variable);
+ switch(control.getType())
+ {
+ case Manipulator.Control.SLIDER:
+ Manipulator.Slider slider = control.cast();
+ addedControl = addSliderControl(
+ variable,
+ manipulator.getDoubleValue(variable),
+ slider);
+ break;
+ case Manipulator.Control.PICKER:
+ Manipulator.Picker picker = control.cast();
+ addedControl = addPickerControl(
+ variable,
+ manipulator.getStringValue(variable),
+ picker);
+ break;
+ case Manipulator.Control.CHECKBOX:
+ Manipulator.CheckBox checkBox = control.cast();
+ addedControl = addCheckBoxControl(
+ variable,
+ manipulator.getBooleanValue(variable),
+ checkBox);
+ break;
+ case Manipulator.Control.BUTTON:
+ Manipulator.Button button = control.cast();
+ addedControl = addButtonControl(variable, button);
+ break;
+ }
+
+ // save reference to first control (for setting focus)
+ if (i == 0)
+ firstControl_ = addedControl;
+ }
+ catch(Throwable e)
+ {
+ Debug.log("WARNING: exception occurred during addition of " +
+ "variable " + variable + ", " + e.getMessage());
+ }
+
+ }
+ }
+ }
+
+ public void focusFirstControl()
+ {
+ if (firstControl_ != null) // defensive
+ {
+ FocusHelper.setFocusDeferred(firstControl_);
+ }
+ }
+
+ private ManipulatorControl addSliderControl(String variable,
+ double value,
+ Manipulator.Slider slider)
+ {
+ ManipulatorControlSlider sliderControl =
+ new ManipulatorControlSlider(variable, value, slider, changedHandler_);
+ mainPanel_.add(sliderControl);
+ return sliderControl;
+ }
+
+ private ManipulatorControl addPickerControl(String variable,
+ String value,
+ Manipulator.Picker picker)
+ {
+ ManipulatorControlPicker pickerControl =
+ new ManipulatorControlPicker(variable, value, picker, changedHandler_);
+ mainPanel_.add(pickerControl);
+ return pickerControl;
+ }
+
+ private ManipulatorControl addCheckBoxControl(String variable,
+ boolean value,
+ Manipulator.CheckBox checkBox)
+ {
+ ManipulatorControlCheckBox checkBoxControl =
+ new ManipulatorControlCheckBox(variable, value, checkBox, changedHandler_);
+ mainPanel_.add(checkBoxControl);
+ return checkBoxControl;
+ }
+
+ private ManipulatorControl addButtonControl(String variable,
+ Manipulator.Button button)
+{
+ ManipulatorControlButton buttonControl =
+ new ManipulatorControlButton(variable, button, changedHandler_);
+ mainPanel_.add(buttonControl);
+ return buttonControl;
+}
+
+
+
+ @Override
+ public void onPreviewNativeEvent(Event.NativePreviewEvent event)
+ {
+
+ if (event.getTypeInt() == Event.ONKEYDOWN)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ switch (nativeEvent.getKeyCode())
+ {
+ case KeyCodes.KEY_ESCAPE:
+ nativeEvent.preventDefault();
+ nativeEvent.stopPropagation();
+ event.cancel();
+ hideMiniDialog();
+ break;
+ }
+ }
+ }
+
+ private VerticalPanel mainPanel_;
+ private ManipulatorControl firstControl_;
+ private final ManipulatorChangedHandler changedHandler_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorResources.java
new file mode 100644
index 0000000..a8ce5aa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorResources.java
@@ -0,0 +1,34 @@
+/*
+ * ManipulatorResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+
+public interface ManipulatorResources extends ClientBundle
+{
+ public static final ManipulatorResources INSTANCE = GWT.create(ManipulatorResources.class);
+
+ @Source("ManipulatorStyles.css")
+ ManipulatorStyles manipulatorStyles();
+
+ ImageResource manipulateButton();
+ ImageResource manipulateProgress();
+
+ @Source("manipulateSliderBar.png")
+ DataResource manipulateSliderBar();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.css
new file mode 100644
index 0000000..a86511c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.css
@@ -0,0 +1,109 @@
+
+ at external .gwt-SliderBar-shell;
+ at external .gwt-SliderBar-line;
+ at external .gwt-SliderBar-knob;
+ at external .gwt-SliderBar-label;
+ at external .gwt-SliderBar-line-sliding;
+ at external .gwt-SliderBar-shell-focused;
+ at external .gwt-SliderBar-tick;
+ at external .gwt-SliderBar-knob;
+ at external .gwt-ListBox;
+
+ at url SLIDERBAR manipulateSliderBar;
+
+.captionLabel {
+ font-weight: bold;
+ margin-right: 8px;
+}
+
+.control {
+ padding-bottom: 15px;
+}
+
+.slider {
+ width: 100%;
+}
+
+
+.slider .sliderValueLabel {
+ margin-bottom: 10px;
+}
+
+.slider .gwt-SliderBar-shell {
+ height: 34pt;
+ width: 100%;
+}
+
+.slider .gwt-SliderBar-shell .gwt-SliderBar-line {
+ height: 3px;
+ background: SLIDERBAR repeat-x top;
+ width: 95%;
+ top: 25pt;
+ overflow: hidden;
+}
+
+.slider .gwt-SliderBar-shell .gwt-SliderBar-knob {
+ top: 19pt;
+ width: 13px;
+ height: 18px;
+ z-index: 1;
+ cursor: pointer;
+}
+
+.slider .gwt-SliderBar-shell .gwt-SliderBar-tick {
+ top: 16pt;
+ width: 1px;
+ height: 6pt;
+ background-color: #919599;
+ overflow: hidden;
+}
+
+.slider .gwt-SliderBar-shell .gwt-SliderBar-label {
+ top: 2pt;
+ font-size: 8pt;
+ cursor: default;
+}
+
+.slider .gwt-SliderBar-shell-focused {
+}
+
+.slider .gwt-SliderBar-shell .gwt-SliderBar-line-sliding {
+ cursor: pointer;
+}
+
+.picker {
+}
+
+.picker .gwt-ListBox {
+ min-width: 100px;
+}
+
+.checkBox {
+ margin-left: -5px;
+}
+
+.button {
+ position: relative;
+ left: -12px;
+
+}
+
+.manipulateButton {
+ margin-left: 4px;
+ margin-top: 3px;
+}
+
+.manipulateProgress {
+ margin-left: 5px;
+ margin-top: 5px;
+}
+
+ at if user.agent gecko1_8 {
+ .manipulateProgress {
+ margin-left: 6px;
+ }
+}
+
+
+
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.java
new file mode 100644
index 0000000..1af89f4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/ManipulatorStyles.java
@@ -0,0 +1,43 @@
+/*
+ * ManipulatorStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.plots.ui.manipulator;
+
+
+import com.google.gwt.resources.client.CssResource;
+
+public interface ManipulatorStyles extends CssResource
+{
+ public static ManipulatorStyles INSTANCE = ManipulatorResources.INSTANCE.manipulatorStyles();
+
+ String captionLabel();
+
+ String control();
+
+ String slider();
+ String sliderValueLabel();
+
+ String picker();
+
+ String checkBox();
+
+ String button();
+
+ String manipulateButton();
+
+ String manipulateProgress();
+
+}
+
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateButton.png
new file mode 100755
index 0000000..146c0b0
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateProgress.gif b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateProgress.gif
new file mode 100755
index 0000000..81bfbcb
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateProgress.gif differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateSliderBar.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateSliderBar.png
new file mode 100644
index 0000000..e289788
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/plots/ui/manipulator/manipulateSliderBar.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/Presentation.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/Presentation.java
new file mode 100644
index 0000000..0f44a91
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/Presentation.java
@@ -0,0 +1,757 @@
+/*
+ * Presentation.java
+
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation;
+
+import java.util.Iterator;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.Barrier;
+import org.rstudio.core.client.MessageDisplay;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.Barrier.Token;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.BarrierReleasedEvent;
+import org.rstudio.core.client.events.BarrierReleasedHandler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperation;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.application.events.ReloadEvent;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;
+
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.edit.ui.EditDialog;
+import org.rstudio.studio.client.workbench.views.presentation.events.PresentationPaneRequestCompletedEvent;
+import org.rstudio.studio.client.workbench.views.presentation.events.ShowPresentationPaneEvent;
+import org.rstudio.studio.client.workbench.views.presentation.events.SourceFileSaveCompletedEvent;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationRPubsSource;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+import org.rstudio.studio.client.workbench.views.presentation.model.SlideNavigation;
+import org.rstudio.studio.client.workbench.views.presentation.model.SlideNavigationItem;
+import org.rstudio.studio.client.workbench.views.source.events.EditPresentationSourceEvent;
+
+public class Presentation extends BasePresenter
+{
+ public interface Binder extends CommandBinder<Commands, Presentation> {}
+
+ public interface Display extends WorkbenchView
+ {
+ void load(String url);
+ void zoom(String title, String url, Command onClosed);
+ void clear();
+ boolean hasSlides();
+
+ void home();
+ void slide(int index);
+ void next();
+ void prev();
+
+ void pauseMedia();
+
+ SlideMenu getSlideMenu();
+
+ String getPresentationTitle();
+
+ void showBusy();
+ void hideBusy();
+ }
+
+ public interface SlideMenu
+ {
+ void setCaption(String caption);
+ void setDropDownVisible(boolean visible);
+ void addItem(MenuItem menu);
+ void clear();
+ }
+
+ @Inject
+ public Presentation(Display display,
+ PresentationServerOperations server,
+ GlobalDisplay globalDisplay,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileSystemContext,
+ EventBus eventBus,
+ FileTypeRegistry fileTypeRegistry,
+ Session session,
+ Binder binder,
+ Commands commands,
+ PresentationDispatcher dispatcher)
+ {
+ super(display);
+ view_ = display;
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ fileDialogs_ = fileDialogs;
+ fileSystemContext_ = fileSystemContext;
+ eventBus_ = eventBus;
+ commands_ = commands;
+ fileTypeRegistry_ = fileTypeRegistry;
+ session_ = session;
+ dispatcher_ = dispatcher;
+ dispatcher_.setContext(new PresentationDispatcher.Context()
+ {
+ @Override
+ public void pauseMedia()
+ {
+ view_.pauseMedia();
+ }
+
+ @Override
+ public String getPresentationFilePath()
+ {
+ return currentState_.getFilePath();
+ }
+ });
+
+ binder.bind(commands, this);
+
+ // auto-refresh for presentation files saved
+ eventBus.addHandler(SourceFileSaveCompletedEvent.TYPE,
+ new SourceFileSaveCompletedEvent.Handler() {
+ @Override
+ public void onSourceFileSaveCompleted(SourceFileSaveCompletedEvent event)
+ {
+ if (currentState_ != null)
+ {
+ FileSystemItem file = event.getSourceFile();
+ if (file.getPath().equals(currentState_.getFilePath()))
+ {
+ int index = detectSlideIndex(event.getContents(),
+ event.getCursorPos().getRow());
+ if (index != -1)
+ currentState_.setSlideIndex(index);
+
+ refreshPresentation();
+ }
+ else if (file.getParentPathString().equals(getCurrentPresDir())
+ &&
+ file.getExtension().toLowerCase().equals(".css"))
+ {
+ refreshPresentation();
+ }
+ }
+ }
+ });
+
+ eventBus.addHandler(PresentationPaneRequestCompletedEvent.TYPE,
+ new PresentationPaneRequestCompletedEvent.Handler()
+ {
+ @Override
+ public void onPresentationRequestCompleted(
+ PresentationPaneRequestCompletedEvent event)
+ {
+ view_.hideBusy();
+ }
+ });
+
+ initPresentationCallbacks();
+ }
+
+ public void initialize(PresentationState state)
+ {
+ if ((state.getSlideIndex() == 0) || state.isTutorial())
+ view_.bringToFront();
+
+ init(state);
+ }
+
+ public void onShowPresentationPane(ShowPresentationPaneEvent event)
+ {
+ globalDisplay_.showProgress("Opening Presentation...");
+ reloadWorkbench();
+ }
+
+ @Handler
+ void onPresentationHome()
+ {
+ view_.home();
+ }
+
+ @Handler
+ void onPresentationNext()
+ {
+ view_.next();
+ }
+
+ @Handler
+ void onPresentationPrev()
+ {
+ view_.prev();
+ }
+
+ @Handler
+ void onPresentationEdit()
+ {
+ eventBus_.fireEvent(new EditPresentationSourceEvent(
+ FileSystemItem.createFile(currentState_.getFilePath()),
+ currentState_.getSlideIndex()));
+ }
+
+ @Handler
+ void onPresentationFullscreen()
+ {
+ // clear the internal iframe so there is no conflict over handling
+ // presentation events (we'll restore it on zoom close)
+ view_.clear();
+
+ // show the zoomed version of the presentation. after it closes
+ // restore the inline version
+ view_.zoom(session_.getSessionInfo().getPresentationName(),
+ buildPresentationUrl("zoom"),
+ new Command() {
+ @Override
+ public void execute()
+ {
+ view_.load(buildPresentationUrl());
+ }
+ });
+ }
+
+ @Handler
+ void onPresentationViewInBrowser()
+ {
+ if (Desktop.isDesktop())
+ {
+ server_.createDesktopViewInBrowserPresentation(
+ new SimpleRequestCallback<String>() {
+ @Override
+ public void onResponseReceived(String path)
+ {
+ Desktop.getFrame().showFile(path);
+ }
+ });
+ }
+ else
+ {
+ globalDisplay_.openWindow(
+ server_.getApplicationURL("presentation/view"));
+ }
+ }
+
+ @Handler
+ void onPresentationSaveAsStandalone()
+ {
+ // determine the default file name
+ if (saveAsStandaloneDefaultPath_ == null)
+ {
+ FileSystemItem presFilePath = FileSystemItem.createFile(
+ currentState_.getFilePath());
+ saveAsStandaloneDefaultPath_ = FileSystemItem.createFile(
+ presFilePath.getParentPath().completePath(presFilePath.getStem()
+ + ".html"));
+ }
+
+ fileDialogs_.saveFile(
+ "Save Presentation As",
+ fileSystemContext_,
+ saveAsStandaloneDefaultPath_,
+ ".html",
+ false,
+ new ProgressOperationWithInput<FileSystemItem>(){
+
+ @Override
+ public void execute(final FileSystemItem targetFile,
+ ProgressIndicator indicator)
+ {
+ if (targetFile == null)
+ {
+ indicator.onCompleted();
+ return;
+ }
+
+ indicator.onProgress("Saving Presentation...");
+
+ server_.createStandalonePresentation(
+ targetFile.getPath(),
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ saveAsStandaloneDefaultPath_ = targetFile;
+ }
+ });
+ }
+ });
+ }
+
+ private void saveAsStandalone(String targetFile,
+ final ProgressIndicator indicator,
+ final Command onSuccess)
+ {
+ server_.createStandalonePresentation(
+ targetFile, new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ onSuccess.execute();
+ }
+ });
+ }
+
+ @Handler
+ void onPresentationPublishToRpubs()
+ {
+ server_.createPresentationRPubsSource(
+ new SimpleRequestCallback<PresentationRPubsSource>() {
+
+ @Override
+ public void onResponseReceived(PresentationRPubsSource source)
+ {
+ RPubsUploadDialog dlg = new RPubsUploadDialog(
+ "Presentation",
+ view_.getPresentationTitle(),
+ source.getSourceFilePath(),
+ source.isPublished());
+ dlg.showModal();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showErrorMessage("Error Saving Presentation",
+ getErrorMessage(error));
+ }
+ });
+ }
+
+ @Handler
+ void onClearPresentationCache()
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.INFO,
+ "Clear Knitr Cache",
+ "Clearing the Knitr cache will discard previously cached " +
+ "output and re-run all of the R code chunks within the " +
+ "presentation.\n\n" +
+ "Are you sure you want to clear the cache now?",
+ false,
+ new ProgressOperation() {
+
+ @Override
+ public void execute(final ProgressIndicator indicator)
+ {
+ indicator.onProgress("Clearing Knitr Cache...");
+ server_.clearPresentationCache(
+ new ServerRequestCallback<Void>() {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ indicator.onCompleted();
+ refreshPresentation();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onCompleted();
+ globalDisplay_.showErrorMessage(
+ "Error Clearing Cache",
+ getErrorMessage(error));
+ }
+ });
+ }
+
+ },
+ new ProgressOperation() {
+
+ @Override
+ public void execute(ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ }
+ },
+ true);
+ }
+
+
+ @Handler
+ void onRefreshPresentation()
+ {
+ if (Event.getCurrentEvent().getShiftKey())
+ currentState_.setSlideIndex(0);
+
+ refreshPresentation();
+ }
+
+ private void refreshPresentation()
+ {
+ view_.showBusy();
+ view_.load(buildPresentationUrl());
+ }
+
+ @Handler
+ void onTutorialFeedback()
+ {
+ EditDialog editDialog = new EditDialog("Provide Feedback",
+ "Submit",
+ "",
+ false,
+ true,
+ new Size(450,300),
+ new ProgressOperationWithInput<String>() {
+ @Override
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ if (input == null)
+ {
+ indicator.onCompleted();
+ return;
+ }
+
+ indicator.onProgress("Saving feedback...");
+
+ server_.tutorialFeedback(input,
+ new VoidServerRequestCallback(indicator));
+
+ }
+ });
+
+ editDialog.showModal();
+
+ }
+
+
+ @Override
+ public void onSelected()
+ {
+ super.onSelected();
+
+ // after doing a pane reconfig the frame gets wiped (no idea why)
+ // workaround this by doing a check for an active state with
+ // no slides currently displayed
+ if (currentState_ != null &&
+ currentState_.isActive() &&
+ !view_.hasSlides())
+ {
+ init(currentState_);
+ }
+ }
+
+
+ public void confirmClose(Command onConfirmed)
+ {
+ // don't allow close if this is a tutorial
+ if (currentState_.isTutorial())
+ {
+ globalDisplay_.showMessage(
+ MessageDisplay.MSG_WARNING,
+ "Unable to Close",
+ "Tutorials cannot be closed");
+ return;
+ }
+
+ final ProgressIndicator progress = new GlobalProgressDelayer(
+ globalDisplay_,
+ 0,
+ "Closing Presentation...").getIndicator();
+
+ server_.closePresentationPane(new ServerRequestCallback<Void>(){
+ @Override
+ public void onResponseReceived(Void resp)
+ {
+ reloadWorkbench();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progress.onError(error.getUserMessage());
+
+ }
+ });
+ }
+
+ private void reloadWorkbench()
+ {
+ Barrier barrier = new Barrier();
+ barrier.addBarrierReleasedHandler(new BarrierReleasedHandler() {
+
+ @Override
+ public void onBarrierReleased(BarrierReleasedEvent event)
+ {
+ eventBus_.fireEvent(new ReloadEvent());
+ }
+ });
+
+ Token token = barrier.acquire();
+ try
+ {
+ eventBus_.fireEvent(new LastChanceSaveEvent(barrier));
+ }
+ finally
+ {
+ token.release();
+ }
+ }
+
+
+ private void init(PresentationState state)
+ {
+ currentState_ = state;
+ view_.load(buildPresentationUrl());
+ }
+
+ private String buildPresentationUrl()
+ {
+ return buildPresentationUrl(null);
+ }
+
+ private String buildPresentationUrl(String extraPath)
+ {
+ String url = server_.getApplicationURL("presentation/");
+ if (extraPath != null)
+ url = url + extraPath;
+ url = url + "#/" + currentState_.getSlideIndex();
+ return url;
+ }
+
+ private boolean isPresentationActive()
+ {
+ return (currentState_ != null) &&
+ (currentState_.isActive())&&
+ view_.hasSlides();
+ }
+
+ private String getCurrentPresDir()
+ {
+ if (currentState_ == null)
+ return "";
+
+ FileSystemItem presFilePath = FileSystemItem.createFile(
+ currentState_.getFilePath());
+ return presFilePath.getParentPathString();
+ }
+
+ private void onPresentationSlideChanged(final int index,
+ final JavaScriptObject jsCmds)
+ {
+ // note the slide index and save it
+ currentState_.setSlideIndex(index);
+ indexPersister_.setIndex(index);
+
+
+ // find the first navigation item that is <= to the index
+ if (slideNavigation_ != null)
+ {
+ JsArray<SlideNavigationItem> items = slideNavigation_.getItems();
+ for (int i=(items.length()-1); i>=0; i--)
+ {
+ if (items.get(i).getIndex() <= index)
+ {
+ String caption = items.get(i).getTitle();
+ caption += " (" + (index+1) + "/" +
+ slideNavigation_.getTotalSlides() + ")";
+
+
+ view_.getSlideMenu().setCaption(caption);
+ break;
+ }
+ }
+ }
+
+ // execute commands if we stay on the slide for > 500ms
+ new Timer() {
+ @Override
+ public void run()
+ {
+ // execute commands if we're still on the same slide
+ if (index == currentState_.getSlideIndex())
+ {
+ JsArray<JavaScriptObject> cmds = jsCmds.cast();
+ for (int i=0; i<cmds.length(); i++)
+ dispatchCommand(cmds.get(i));
+ }
+ }
+ }.schedule(500);
+ }
+
+ private void dispatchCommand(JavaScriptObject jsCommand)
+ {
+ dispatcher_.dispatchCommand(jsCommand);
+ }
+
+ private void initPresentationNavigator(JavaScriptObject jsNavigator)
+ {
+ // record current slides
+ slideNavigation_ = jsNavigator.cast();
+ JsArray<SlideNavigationItem> items = slideNavigation_.getItems();
+
+ // reset the slides menu
+ SlideMenu slideMenu = view_.getSlideMenu();
+ slideMenu.clear();
+ for (int i=0; i<items.length(); i++)
+ {
+ // get slide
+ final SlideNavigationItem item = items.get(i);
+
+ // build html
+ SafeHtmlBuilder menuHtml = new SafeHtmlBuilder();
+ for (int j=0; j<item.getIndent(); j++)
+ menuHtml.appendHtmlConstant(" ");
+ menuHtml.appendEscaped(item.getTitle());
+
+
+ slideMenu.addItem(new MenuItem(menuHtml.toSafeHtml(),
+ new Command() {
+ @Override
+ public void execute()
+ {
+ view_.slide(item.getIndex());
+ }
+ }));
+ }
+
+ slideMenu.setDropDownVisible(slideNavigation_.getItems().length() > 1);
+ }
+
+ private void recordPresentationQuizAnswer(int slideIndex,
+ int answer,
+ boolean correct)
+ {
+ server_.tutorialQuizResponse(slideIndex,
+ answer,
+ correct,
+ new VoidServerRequestCallback());
+ }
+
+ private final native void initPresentationCallbacks() /*-{
+ var thiz = this;
+ $wnd.presentationSlideChanged = $entry(function(index, cmds) {
+ thiz. at org.rstudio.studio.client.workbench.views.presentation.Presentation::onPresentationSlideChanged(ILcom/google/gwt/core/client/JavaScriptObject;)(index, cmds);
+ });
+ $wnd.dispatchPresentationCommand = $entry(function(cmd) {
+ thiz. at org.rstudio.studio.client.workbench.views.presentation.Presentation::dispatchCommand(Lcom/google/gwt/core/client/JavaScriptObject;)(cmd);
+ });
+ $wnd.initPresentationNavigator = $entry(function(slides) {
+ thiz. at org.rstudio.studio.client.workbench.views.presentation.Presentation::initPresentationNavigator(Lcom/google/gwt/core/client/JavaScriptObject;)(slides);
+ });
+ $wnd.recordPresentationQuizAnswer = $entry(function(index, answer, correct) {
+ thiz. at org.rstudio.studio.client.workbench.views.presentation.Presentation::recordPresentationQuizAnswer(IIZ)(index, answer, correct);
+ });
+ }-*/;
+
+ private class IndexPersister extends TimeBufferedCommand
+ {
+ public IndexPersister()
+ {
+ super(500);
+ }
+
+ public void setIndex(int index)
+ {
+ index_ = index;
+ nudge();
+ }
+
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ server_.setPresentationSlideIndex(index_,
+ new VoidServerRequestCallback());
+ }
+
+ private int index_ = 0;
+ };
+ private IndexPersister indexPersister_ = new IndexPersister();
+
+
+
+
+
+ private static int detectSlideIndex(String contents, int cursorLine)
+ {
+ int currentLine = 0;
+ int slideIndex = -1;
+ String slideRegex = "^\\={3,}\\s*$";
+
+ Iterator<String> it = StringUtil.getLineIterator(contents).iterator();
+ while (it.hasNext())
+ {
+ String line = it.next();
+ if (line.matches(slideRegex))
+ slideIndex++;
+
+ if (currentLine++ >= cursorLine)
+ {
+ // bump the slide index if the next line is a header
+ if (it.hasNext() && it.next().matches(slideRegex))
+ slideIndex++;
+
+ return slideIndex;
+ }
+ }
+
+
+ return -1;
+ }
+
+
+ private String getErrorMessage(ServerError error)
+ {
+ String message = error.getUserMessage();
+ JSONString userMessage = error.getClientInfo().isString();
+ if (userMessage != null)
+ message = userMessage.stringValue();
+ return message;
+ }
+
+ private final Display view_ ;
+ private final PresentationServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus eventBus_;
+ private final Commands commands_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final FileDialogs fileDialogs_;
+ private final RemoteFileSystemContext fileSystemContext_;
+ private final Session session_;
+ private final PresentationDispatcher dispatcher_;
+ private PresentationState currentState_ = null;
+ private SlideNavigation slideNavigation_ = null;
+ private boolean usingRmd_ = false;
+
+ private FileSystemItem saveAsStandaloneDefaultPath_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationDispatcher.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationDispatcher.java
new file mode 100644
index 0000000..9713d48
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationDispatcher.java
@@ -0,0 +1,141 @@
+/*
+ * PresentationDispatcher.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.presentation;
+
+import org.rstudio.core.client.FilePosition;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.filetypes.events.OpenPresentationSourceFileEvent;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationCommand;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.inject.Inject;
+
+public class PresentationDispatcher
+{
+ @Inject
+ public PresentationDispatcher(EventBus eventBus,
+ FileTypeRegistry fileTypeRegistry)
+ {
+ eventBus_ = eventBus;
+ fileTypeRegistry_ = fileTypeRegistry;
+ }
+
+ public interface Context
+ {
+ void pauseMedia();
+ String getPresentationFilePath();
+ }
+
+
+ public void setContext(Context context)
+ {
+ context_ = context;
+ }
+
+ public void dispatchCommand(JavaScriptObject jsCommand)
+ {
+ // cast
+ PresentationCommand command = jsCommand.cast();
+
+ // crack parameters
+ String param1 = null, param2 = null;
+ String params = command.getParams();
+ if (params.length() > 0)
+ {
+ // find the first space and split on that
+ int spaceLoc = params.indexOf(' ');
+ if (spaceLoc == -1)
+ {
+ param1 = params;
+ }
+ else
+ {
+ param1 = params.substring(0, spaceLoc);
+ param2 = params.substring(spaceLoc+1);
+ }
+ }
+
+ String cmdName = command.getName().toLowerCase();
+
+ if (cmdName.equals("source"))
+ performSourceCommand(param1, param2);
+ else
+ performOtherCommand(cmdName, params, param1, param2);
+ }
+
+ private void performSourceCommand(String param1, String param2)
+ {
+ if (param1 != null)
+ {
+ // get filename and type
+ FileSystemItem file = FileSystemItem.createFile(
+ getPresentationPath(param1));
+ TextFileType fileType = fileTypeRegistry_.getTextTypeForFile(file);
+
+ // check for a file position and/or pattern
+ FilePosition pos = null;
+ String pattern = null;
+ if (param2 != null)
+ {
+ if (param2.length() > 2 &&
+ param2.startsWith("/") && param2.endsWith("/"))
+ {
+ pattern = param2.substring(1, param2.length()-1);
+ }
+ else
+ {
+ int line = StringUtil.parseInt(param2, 0);
+ if (line > 0)
+ pos = FilePosition.create(line, 1);
+ }
+ }
+
+ // dispatch
+ fireOpenSourceFileEvent(new OpenPresentationSourceFileEvent(
+ file,
+ fileType,
+ pos,
+ pattern));
+ }
+ }
+
+ protected void performOtherCommand(String cmdName,
+ String params,
+ String param1,
+ String param2)
+ {
+ }
+
+ protected void fireOpenSourceFileEvent(OpenPresentationSourceFileEvent event)
+ {
+ eventBus_.fireEvent(event);
+ }
+
+ protected String getPresentationPath(String file)
+ {
+ FileSystemItem presentationFile = FileSystemItem.createFile(
+ context_.getPresentationFilePath());
+ return presentationFile.getParentPath().completePath(file);
+ }
+
+ protected Context context_;
+ protected final EventBus eventBus_;
+ protected final FileTypeRegistry fileTypeRegistry_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationFrame.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationFrame.java
new file mode 100644
index 0000000..02ea1b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationFrame.java
@@ -0,0 +1,131 @@
+/*
+ * PresentationFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.presentation;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.widget.AnchorableFrame;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.user.client.ui.HasText;
+
+public class PresentationFrame extends AnchorableFrame
+{
+ public PresentationFrame(boolean autoFocus)
+ {
+ this(autoFocus, false);
+ }
+
+ public PresentationFrame(boolean autoFocus,
+ boolean allowFullScreen)
+ {
+ this(autoFocus, allowFullScreen, null);
+ }
+
+ public PresentationFrame(boolean autoFocus,
+ boolean allowFullScreen,
+ final HasText titleWidget)
+ {
+ super(autoFocus);
+
+ // allow full-screen view of iframe
+ if (allowFullScreen)
+ {
+ Element el = getElement();
+ el.setAttribute("webkitallowfullscreen", "");
+ el.setAttribute("mozallowfullscreen", "");
+ el.setAttribute("allowfullscreen", "");
+ }
+
+ addLoadHandler(new LoadHandler() {
+
+ @Override
+ public void onLoad(LoadEvent event)
+ {
+ // set title
+ title_ = StringUtil.notNull(
+ getWindow().getDocument().getTitle());
+
+ if (titleWidget != null)
+ titleWidget.setText(title_);
+ }
+ });
+ }
+
+ public String getFrameTitle()
+ {
+ return title_;
+ }
+
+ public void clear()
+ {
+ getWindow().replaceLocationHref("about:blank");
+ }
+
+ public void home()
+ {
+ Reveal.fromWindow(getWindow()).home();
+ }
+
+ public void slide(int index)
+ {
+ Reveal.fromWindow(getWindow()).slide(index);
+ }
+
+ public void next()
+ {
+ Reveal.fromWindow(getWindow()).next();
+ }
+
+ public void prev()
+ {
+ Reveal.fromWindow(getWindow()).prev();
+ }
+
+ private static class Reveal extends JavaScriptObject
+ {
+ protected Reveal()
+ {
+ }
+
+ public static final native Reveal fromWindow(WindowEx window) /*-{
+ return window.Reveal;
+ }-*/;
+
+ public final native void home() /*-{
+ this.slide(0);
+ }-*/;
+
+ public final native void slide(int index) /*-{
+ this.slide(index);
+ }-*/;
+
+ public final native void next() /*-{
+ this.next();
+ }-*/;
+
+ public final native void prev() /*-{
+ this.prev();
+ }-*/;
+ }
+
+
+ private String title_ = "";
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationPane.java
new file mode 100644
index 0000000..4beecfd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationPane.java
@@ -0,0 +1,355 @@
+/*
+ * PresentationPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+
+import org.rstudio.core.client.command.ShortcutManager;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.events.NativeKeyDownEvent;
+import org.rstudio.core.client.resources.CoreResources;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.FullscreenPopupPanel;
+import org.rstudio.core.client.widget.AnchorableFrame;
+import org.rstudio.core.client.widget.ScrollableToolbarPopupMenu;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+
+import org.rstudio.studio.client.common.AutoGlassPanel;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.presentation.Presentation.SlideMenu;
+
+public class PresentationPane extends WorkbenchPane implements Presentation.Display
+{
+ @Inject
+ public PresentationPane(Commands commands, Session session)
+ {
+ super("Presentation");
+ commands_ = commands;
+ session_ = session;
+ ensureWidget();
+
+ initPresentationCallbacks();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ boolean isTutorial =
+ session_.getSessionInfo().getPresentationState().isTutorial();
+
+ Toolbar toolbar = new Toolbar();
+
+ toolbar.addLeftWidget(commands_.presentationHome().createToolbarButton());
+ toolbar.addLeftSeparator();
+
+ titleLabel_.addStyleName(ThemeResources.INSTANCE.themeStyles()
+ .presentationNavigatorLabel());
+ menuWidget_ = toolbar.addLeftPopupMenu(titleLabel_, slidesMenu_);
+ getSlideMenu().setDropDownVisible(false);
+ toolbar.addLeftSeparator();
+
+ if (!isTutorial)
+ {
+ toolbar.addLeftWidget(commands_.presentationEdit().createToolbarButton());
+ toolbar.addLeftSeparator();
+ }
+
+ toolbar.addLeftWidget(commands_.presentationFullscreen().createToolbarButton());
+
+ // More
+ if (!isTutorial)
+ {
+ ToolbarPopupMenu moreMenu = new ToolbarPopupMenu();
+ moreMenu.addItem(commands_.clearPresentationCache().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.presentationViewInBrowser().createMenuItem(false));
+ moreMenu.addItem(commands_.presentationSaveAsStandalone().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.presentationPublishToRpubs().createMenuItem(false));
+
+ ToolbarButton moreButton = new ToolbarButton("More",
+ StandardIcons.INSTANCE.more_actions(),
+ moreMenu);
+ toolbar.addRightWidget(moreButton);
+
+ }
+ else
+ {
+ toolbar.addRightWidget(commands_.tutorialFeedback().createToolbarButton());
+ }
+
+ toolbar.addRightSeparator();
+ toolbar.addRightWidget(refreshButton_ =
+ commands_.refreshPresentation().createToolbarButton());
+ progressButton_ = new ToolbarButton(
+ CoreResources.INSTANCE.progress_gray(),
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent e)
+ {
+ }
+ });
+ toolbar.addRightWidget(progressButton_);
+ progressButton_.setVisible(false);
+
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ frame_ = new PresentationFrame(false) ;
+ frame_.setUrl("about:blank");
+ frame_.setSize("100%", "100%");
+ return new AutoGlassPanel(frame_);
+ }
+
+ @Override
+ public void load(String url)
+ {
+ frame_.navigate(url);
+ }
+
+ @Override
+ public void zoom(String title, String url, final Command onClosed)
+ {
+ // create the titlebar (no title for now)
+ HorizontalPanel titlePanel = new HorizontalPanel();
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+ Label titleLabel = new Label(title);
+ titleLabel.addStyleName(styles.fullscreenCaptionLabel());
+ titlePanel.add(titleLabel);
+
+ // create the frame
+ AnchorableFrame frame = new PresentationFrame(true);
+ frame.setSize("100%", "100%");
+
+ // create the popup panel & add close handler
+ activeZoomPanel_ = new FullscreenPopupPanel(titlePanel, frame, false);
+ activeZoomPanel_.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ activeZoomPanel_ = null;
+ onClosed.execute();
+ }
+ });
+
+ // load the frame and show the zoom panel
+ frame.navigate(url);
+ activeZoomPanel_.center();
+ }
+
+ @Override
+ public void clear()
+ {
+ frame_.clear();
+ }
+
+ @Override
+ public boolean hasSlides()
+ {
+ String href = frame_.getWindow().getLocationHref();
+ return !"about:blank".equals(href) &&
+ !"javascript:void(0)".equals(href);
+ }
+
+ @Override
+ public void pauseMedia()
+ {
+ pausePlayers(frame_.getWindow());
+ }
+
+ private static final native void pausePlayers(WindowEx window) /*-{
+ window.pauseAllPlayers();
+ }-*/;
+
+ @Override
+ public void home()
+ {
+ frame_.home();
+ }
+
+ @Override
+ public void slide(int index)
+ {
+ frame_.slide(index);
+ }
+
+ @Override
+ public void next()
+ {
+ frame_.next();
+ }
+
+ @Override
+ public void prev()
+ {
+ frame_.prev();
+ }
+
+ @Override
+ public SlideMenu getSlideMenu()
+ {
+ return slideMenu_;
+ }
+
+ private SlideMenu slideMenu_ = new SlideMenu() {
+
+ @Override
+ public void setCaption(String caption)
+ {
+ titleLabel_.setText(caption);
+ }
+
+ @Override
+ public void addItem(MenuItem menu)
+ {
+ slidesMenu_.addItem(menu);
+ }
+
+ @Override
+ public void clear()
+ {
+ slidesMenu_.clearItems();
+ }
+
+ @Override
+ public void setDropDownVisible(boolean visible)
+ {
+ menuWidget_.setVisible(visible);
+ }
+ };
+
+ @Override
+ public String getPresentationTitle()
+ {
+ return frame_.getFrameTitle();
+ }
+
+ @Override
+ public void showBusy()
+ {
+ busyPending_ = true;
+
+ new Timer() {
+ @Override
+ public void run()
+ {
+ if (busyPending_)
+ {
+ refreshButton_.setVisible(false);
+ progressButton_.setVisible(true);
+ busyPending_ = false;
+ }
+ }
+ }.schedule(750);
+ }
+
+ @Override
+ public void hideBusy()
+ {
+ busyPending_ = false;
+ refreshButton_.setVisible(true);
+ progressButton_.setVisible(false);
+ }
+
+
+ private final native void initPresentationCallbacks() /*-{
+ var thiz = this;
+ $wnd.presentationKeydown = $entry(function(e) {
+ thiz. at org.rstudio.studio.client.workbench.views.presentation.PresentationPane::handleKeyDown(Lcom/google/gwt/dom/client/NativeEvent;)(e);
+ });
+ }-*/;
+
+ private void handleKeyDown(NativeEvent e)
+ {
+ // get the event
+ NativeKeyDownEvent evt = new NativeKeyDownEvent(e);
+
+ // if there is a zoom panel then ignore other shortcuts
+ // (only handle Esc)
+ if (activeZoomPanel_ != null)
+ {
+ if (e.getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ activeZoomPanel_.close();
+ }
+ }
+ else
+ {
+ ShortcutManager.INSTANCE.onKeyDown(evt);
+ if (evt.isCanceled())
+ {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // since this is a shortcut handled by the main window
+ // we set focus to it
+ WindowEx.get().focus();
+ }
+ }
+ }
+
+ private class SlidesPopupMenu extends ScrollableToolbarPopupMenu
+ {
+ public SlidesPopupMenu()
+ {
+ addStyleName(ThemeStyles.INSTANCE.statusBarMenu());
+ }
+
+ @Override
+ protected int getMaxHeight()
+ {
+ return Window.getClientHeight() - titleLabel_.getAbsoluteTop() -
+ titleLabel_.getOffsetHeight() - 300;
+ }
+ }
+
+ private Label titleLabel_ = new Label();
+ private SlidesPopupMenu slidesMenu_ = new SlidesPopupMenu();
+ private Widget menuWidget_;
+ private ToolbarButton refreshButton_;
+ private ToolbarButton progressButton_;
+ private boolean busyPending_ = false;
+ private PresentationFrame frame_ ;
+ private final Commands commands_;
+ private final Session session_;
+
+ private FullscreenPopupPanel activeZoomPanel_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationTab.java
new file mode 100644
index 0000000..7b37f12
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/PresentationTab.java
@@ -0,0 +1,102 @@
+/*
+ * PresentationTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation;
+
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.events.SessionInitEvent;
+import org.rstudio.studio.client.workbench.events.SessionInitHandler;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.presentation.events.ShowPresentationPaneEvent;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+
+
+public class PresentationTab extends DelayLoadWorkbenchTab<Presentation>
+{
+ public interface Binder extends CommandBinder<Commands, Shim> {}
+
+ public abstract static class Shim extends DelayLoadTabShim<Presentation, PresentationTab>
+ implements ShowPresentationPaneEvent.Handler
+ {
+ abstract void initialize(PresentationState state);
+ abstract void confirmClose(Command onConfirmed);
+ }
+
+ @Inject
+ public PresentationTab(final Shim shim,
+ Binder binder,
+ final Commands commands,
+ EventBus eventBus,
+ Session session)
+ {
+ super(session.getSessionInfo().getPresentationName(), shim);
+ binder.bind(commands, shim);
+ shim_ = shim;
+ session_ = session;
+
+ eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
+
+ public void onSessionInit(SessionInitEvent sie)
+ {
+ PresentationState state =
+ session_.getSessionInfo().getPresentationState();
+ if (state.isActive())
+ shim.initialize(state);
+
+ if (state.isTutorial())
+ {
+ commands.clearPresentationCache().remove();
+ commands.presentationViewInBrowser().remove();
+ commands.presentationSaveAsStandalone().remove();
+ commands.presentationPublishToRpubs().remove();
+ }
+ else
+ {
+ commands.tutorialFeedback().remove();
+ }
+ }
+ });
+
+ eventBus.addHandler(ShowPresentationPaneEvent.TYPE, shim);
+ }
+
+ @Override
+ public boolean closeable()
+ {
+ return true;
+ }
+
+ @Override
+ public void confirmClose(Command onConfirmed)
+ {
+ shim_.confirmClose(onConfirmed);
+ }
+
+ @Override
+ public boolean isSuppressed()
+ {
+ return !session_.getSessionInfo().getPresentationState().isActive();
+ }
+
+ private Session session_;
+ private Shim shim_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/PresentationPaneRequestCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/PresentationPaneRequestCompletedEvent.java
new file mode 100644
index 0000000..7b528da
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/PresentationPaneRequestCompletedEvent.java
@@ -0,0 +1,45 @@
+/*
+ * PresentationPaneRequestCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class PresentationPaneRequestCompletedEvent extends GwtEvent<PresentationPaneRequestCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onPresentationRequestCompleted(PresentationPaneRequestCompletedEvent event);
+ }
+
+ public PresentationPaneRequestCompletedEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPresentationRequestCompleted(this);
+ }
+
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/ShowPresentationPaneEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/ShowPresentationPaneEvent.java
new file mode 100644
index 0000000..23ae169
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/ShowPresentationPaneEvent.java
@@ -0,0 +1,54 @@
+/*
+ * ShowPresentationPaneEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.events;
+
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowPresentationPaneEvent extends GwtEvent<ShowPresentationPaneEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShowPresentationPane(ShowPresentationPaneEvent event);
+ }
+
+ public ShowPresentationPaneEvent(PresentationState presentationState)
+ {
+ presentationState_ = presentationState;
+ }
+
+ public PresentationState getPresentationState()
+ {
+ return presentationState_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onShowPresentationPane(this);
+ }
+
+ private final PresentationState presentationState_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/SourceFileSaveCompletedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/SourceFileSaveCompletedEvent.java
new file mode 100644
index 0000000..4125e22
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/events/SourceFileSaveCompletedEvent.java
@@ -0,0 +1,71 @@
+/*
+ * SourceFileSaveCompletedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceFileSaveCompletedEvent extends GwtEvent<SourceFileSaveCompletedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSourceFileSaveCompleted(SourceFileSaveCompletedEvent event);
+ }
+
+ public SourceFileSaveCompletedEvent(FileSystemItem sourceFile,
+ String contents,
+ Position cursorPos)
+ {
+ sourceFile_ = sourceFile;
+ contents_ = contents;
+ cursorPos_ = cursorPos;
+ }
+
+ public FileSystemItem getSourceFile()
+ {
+ return sourceFile_;
+ }
+
+ public String getContents()
+ {
+ return contents_;
+ }
+
+ public Position getCursorPos()
+ {
+ return cursorPos_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSourceFileSaveCompleted(this);
+ }
+
+ private final FileSystemItem sourceFile_;
+ private final String contents_;
+ private final Position cursorPos_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationCommand.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationCommand.java
new file mode 100644
index 0000000..b04afef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationCommand.java
@@ -0,0 +1,40 @@
+/*
+ * PresentationCommand.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PresentationCommand extends JavaScriptObject
+{
+ protected PresentationCommand()
+ {
+ }
+
+ public final static native PresentationCommand create(String name,
+ String params) /*-{
+ var obj = new Object();
+ obj.name = name;
+ obj.params = params;
+ return obj;
+ }-*/;
+
+ public final native String getName() /*-{
+ return this.name;
+ }-*/;
+
+ public final native String getParams() /*-{
+ return this.params;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationRPubsSource.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationRPubsSource.java
new file mode 100644
index 0000000..4632fbc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationRPubsSource.java
@@ -0,0 +1,32 @@
+/*
+ * PresentationRPubsSource.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PresentationRPubsSource extends JavaScriptObject
+{
+ protected PresentationRPubsSource()
+ {
+ }
+
+ public final native boolean isPublished() /*-{
+ return this.published;
+ }-*/;
+
+ public final native String getSourceFilePath() /*-{
+ return this.source_file_path;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationServerOperations.java
new file mode 100644
index 0000000..a8b054f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationServerOperations.java
@@ -0,0 +1,71 @@
+/*
+ * PresentationServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import org.rstudio.studio.client.common.rpubs.model.RPubsServerOperations;
+import org.rstudio.studio.client.server.*;
+import org.rstudio.studio.client.server.Void;
+
+public interface PresentationServerOperations extends RPubsServerOperations
+{
+ String getApplicationURL(String url);
+
+ void showHelpTopic(String topic, String pkgName) ;
+
+ void setWorkingDirectory(String path,
+ ServerRequestCallback<Void> requestCallback);
+
+ void createStandalonePresentation(
+ String targetFile,
+ ServerRequestCallback<Void> requestCallback);
+
+ void createDesktopViewInBrowserPresentation(
+ ServerRequestCallback<String> requestCallback);
+
+ void createPresentationRPubsSource(
+ ServerRequestCallback<PresentationRPubsSource> requestCallback);
+
+ void setPresentationSlideIndex(int index,
+ ServerRequestCallback<Void> requestCallback);
+
+ void presentationExecuteCode(String code,
+ ServerRequestCallback<Void> requestCallback);
+
+ void createNewPresentation(String filePath,
+ ServerRequestCallback<Void> requestCallback);
+
+ void showPresentationPane(String filePath,
+ ServerRequestCallback<Void> requestCallback);
+
+ void closePresentationPane(ServerRequestCallback<Void> requestCallaback);
+
+ void tutorialFeedback(String feedback,
+ ServerRequestCallback<Void> requestCallback);
+
+ void tutorialQuizResponse(int slideIndex, int answer, boolean correct,
+ ServerRequestCallback<Void> requestCallback);
+
+ void getSlideNavigationForFile(
+ String filePath,
+ ServerRequestCallback<SlideNavigation> requestCallback);
+
+ void getSlideNavigationForCode(
+ String code,
+ String baseDir,
+ ServerRequestCallback<SlideNavigation> requestCallback);
+
+ void clearPresentationCache(ServerRequestCallback<Void> requestCallback);
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationState.java
new file mode 100644
index 0000000..d00f1f1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/PresentationState.java
@@ -0,0 +1,48 @@
+/*
+ * PresentationState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class PresentationState extends JavaScriptObject
+{
+ protected PresentationState()
+ {
+ }
+
+ public final native boolean isActive() /*-{
+ return this.active;
+ }-*/;
+
+ public final native String getPaneCaption() /*-{
+ return this.pane_caption;
+ }-*/;
+
+ public final native boolean isTutorial() /*-{
+ return this.is_tutorial;
+ }-*/;
+
+ public final native String getFilePath() /*-{
+ return this.file_path;
+ }-*/;
+
+ public final native void setSlideIndex(int index) /*-{
+ this.slide_index = index;
+ }-*/;
+
+ public final native int getSlideIndex() /*-{
+ return this.slide_index;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigation.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigation.java
new file mode 100644
index 0000000..cdf1a53
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigation.java
@@ -0,0 +1,34 @@
+/*
+ * SlideNavigation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class SlideNavigation extends JavaScriptObject
+{
+ protected SlideNavigation()
+ {
+ }
+
+ public final native int getTotalSlides() /*-{
+ return this.total_slides;
+ }-*/;
+
+
+ public final native JsArray<SlideNavigationItem> getItems() /*-{
+ return this.items;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigationItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigationItem.java
new file mode 100644
index 0000000..7959268
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/presentation/model/SlideNavigationItem.java
@@ -0,0 +1,40 @@
+/*
+ * SlideNavigationItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.presentation.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SlideNavigationItem extends JavaScriptObject
+{
+ protected SlideNavigationItem()
+ {
+ }
+
+ public final native String getTitle() /*-{
+ return this.title;
+ }-*/;
+
+ public final native int getIndent() /*-{
+ return this.indent;
+ }-*/;
+
+ public final native int getIndex() /*-{
+ return this.index;
+ }-*/;
+
+ public final native int getLine() /*-{
+ return this.line;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/DocsMenu.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/DocsMenu.java
new file mode 100644
index 0000000..5de0c47
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/DocsMenu.java
@@ -0,0 +1,116 @@
+/*
+ * DocsMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.inject.Inject;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.AppMenuBar;
+import org.rstudio.core.client.command.DisabledMenuItem;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.views.source.events.DocTabsChangedEvent;
+import org.rstudio.studio.client.workbench.views.source.events.DocTabsChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.events.SwitchToDocEvent;
+
+import java.util.ArrayList;
+
+public class DocsMenu extends AppMenuBar
+{
+ public DocsMenu()
+ {
+ super(true);
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(EventBus events)
+ {
+ assert events_ == null : "DocsMenu.initialize was called more than once";
+ events_ = events;
+ events_.addHandler(
+ DocTabsChangedEvent.TYPE,
+ new DocTabsChangedHandler()
+ {
+ public void onDocTabsChanged(DocTabsChangedEvent event)
+ {
+ setDocs(event.getIcons(), event.getNames(), event.getPaths());
+ }
+ }) ;
+ }
+
+ public void setOwnerPopupPanel(PopupPanel panel)
+ {
+ panel_ = panel;
+ }
+
+ public void setDocs(ImageResource[] icons, String[] names, String[] paths)
+ {
+ clearItems();
+ names_.clear();
+ menuItems_.clear();
+
+ assert icons.length == names.length && names.length == paths.length;
+
+ if (icons.length == 0)
+ {
+ addItem(new DisabledMenuItem("(No documents)"));
+ }
+
+ for (int i = 0; i < icons.length; i++)
+ {
+ String label = AppCommand.formatMenuLabel(icons[i],
+ names[i] + "\u00A0\u00A0\u00A0",
+ null);
+ final int tabIndex = i;
+ MenuItem item = addItem(label, true, new Command()
+ {
+ public void execute()
+ {
+ if (panel_ != null)
+ panel_.hide(false);
+ events_.fireEvent(new SwitchToDocEvent(tabIndex));
+ }
+ });
+ item.setTitle(paths[i]);
+
+ names_.add(names[i]);
+ menuItems_.add(item);
+ }
+ }
+
+ public void filter(String criteria)
+ {
+ for (int i = 0; i < names_.size(); i++)
+ {
+ menuItems_.get(i).setVisible(shouldShow(criteria, names_.get(i)));
+ }
+ }
+
+ private boolean shouldShow(String filterCriteria, String value)
+ {
+ if (filterCriteria == null)
+ return true;
+ return value.toLowerCase().startsWith(filterCriteria.toLowerCase());
+ }
+
+ private ArrayList<String> names_ = new ArrayList<String>();
+ private ArrayList<MenuItem> menuItems_ = new ArrayList<MenuItem>();
+ private EventBus events_;
+ private PopupPanel panel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/PanelWithToolbars.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/PanelWithToolbars.java
new file mode 100644
index 0000000..0ce75b9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/PanelWithToolbars.java
@@ -0,0 +1,75 @@
+/*
+ * PanelWithToolbars.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.ResizeComposite;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.IsWidgetWithHeight;
+
+public class PanelWithToolbars extends ResizeComposite
+{
+ public PanelWithToolbars(Toolbar toolbar, Widget mainWidget)
+ {
+ this(toolbar, mainWidget, null);
+ }
+
+ public PanelWithToolbars(Toolbar toolbar,
+ Widget mainWidget,
+ IsWidgetWithHeight statusBar)
+ {
+ this(toolbar, null, mainWidget, statusBar);
+ }
+
+ public PanelWithToolbars(Toolbar toolbar,
+ Toolbar secondaryToolbar,
+ Widget mainWidget,
+ IsWidgetWithHeight statusBar)
+ {
+ mainWidget_ = mainWidget;
+
+ panel_ = new DockLayoutPanel(Unit.PX);
+ panel_.addNorth(toolbar, toolbar.getHeight());
+
+ if (secondaryToolbar != null)
+ panel_.addNorth(secondaryToolbar, secondaryToolbar.getHeight());
+
+ if (statusBar != null)
+ panel_.addSouth(statusBar.asWidget(), statusBar.getHeight());
+
+ panel_.add(mainWidget_);
+
+ initWidget(panel_);
+ }
+
+ public void insertNorth(Widget widget, double size, Widget before)
+ {
+ if (before == null)
+ before = mainWidget_;
+ panel_.insertNorth(widget, size, before);
+ panel_.forceLayout();
+ }
+
+ public void remove(Widget widget)
+ {
+ panel_.remove(widget);
+ panel_.forceLayout();
+ }
+
+ private DockLayoutPanel panel_;
+ private Widget mainWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/Source.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/Source.java
new file mode 100644
index 0000000..37e6cf6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/Source.java
@@ -0,0 +1,2461 @@
+/*
+ * Source.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.*;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.command.ShortcutManager;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.filetypes.EditableFileType;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.filetypes.events.OpenPresentationSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent.NavigationMethod;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileHandler;
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+import org.rstudio.studio.client.common.rnw.RnwWeaveRegistry;
+import org.rstudio.studio.client.common.synctex.Synctex;
+import org.rstudio.studio.client.common.synctex.events.SynctexStatusChangedEvent;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.FileMRUList;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.model.helper.IntStateValue;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
+import org.rstudio.studio.client.workbench.views.data.events.ViewDataEvent;
+import org.rstudio.studio.client.workbench.views.data.events.ViewDataHandler;
+import org.rstudio.studio.client.workbench.views.output.find.events.FindInFilesEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetSource;
+import org.rstudio.studio.client.workbench.views.source.editors.codebrowser.CodeBrowserEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.data.DataEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerContents;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetPresentationHelper;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FileTypeChangedEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FileTypeChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.SourceOnSaveChangedEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.SourceOnSaveChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ui.NewRdDialog;
+import org.rstudio.studio.client.workbench.views.source.events.*;
+import org.rstudio.studio.client.workbench.views.source.model.ContentItem;
+import org.rstudio.studio.client.workbench.views.source.model.DataItem;
+import org.rstudio.studio.client.workbench.views.source.model.RdShellResult;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.source.model.SourceNavigation;
+import org.rstudio.studio.client.workbench.views.source.model.SourceNavigationHistory;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class Source implements InsertSourceHandler,
+ IsWidget,
+ OpenSourceFileHandler,
+ TabClosingHandler,
+ TabCloseHandler,
+ SelectionHandler<Integer>,
+ TabClosedHandler,
+ FileEditHandler,
+ ShowContentHandler,
+ ShowDataHandler,
+ CodeBrowserNavigationHandler,
+ CodeBrowserFinishedHandler,
+ CodeBrowserHighlightEvent.Handler,
+ SourceExtendedTypeDetectedEvent.Handler,
+ BeforeShowHandler
+{
+ public interface Display extends IsWidget,
+ HasTabClosingHandlers,
+ HasTabCloseHandlers,
+ HasTabClosedHandlers,
+ HasBeforeSelectionHandlers<Integer>,
+ HasSelectionHandlers<Integer>
+ {
+ void addTab(Widget widget,
+ ImageResource icon,
+ String name,
+ String tooltip,
+ boolean switchToTab);
+ void selectTab(int tabIndex);
+ void selectTab(Widget widget);
+ int getTabCount();
+ int getActiveTabIndex();
+ void closeTab(Widget widget, boolean interactive);
+ void closeTab(Widget widget, boolean interactive, Command onClosed);
+ void closeTab(int index, boolean interactive);
+ void closeTab(int index, boolean interactive, Command onClosed);
+ void setDirty(Widget widget, boolean dirty);
+ void manageChevronVisibility();
+ void showOverflowPopup();
+
+ void showUnsavedChangesDialog(
+ String title,
+ ArrayList<UnsavedChangesTarget> dirtyTargets,
+ OperationWithInput<UnsavedChangesDialog.Result> saveOperation,
+ Command onCancelled);
+
+ void ensureVisible();
+
+ void renameTab(Widget child,
+ ImageResource icon,
+ String value,
+ String tooltip);
+
+ HandlerRegistration addBeforeShowHandler(BeforeShowHandler handler);
+ }
+
+ public interface CPSEditingTargetCommand
+ {
+ void execute(EditingTarget editingTarget, Command continuation);
+ }
+
+ @Inject
+ public Source(Commands commands,
+ Display view,
+ SourceServerOperations server,
+ EditingTargetSource editingTargetSource,
+ FileTypeRegistry fileTypeRegistry,
+ GlobalDisplay globalDisplay,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileContext,
+ EventBus events,
+ final Session session,
+ Synctex synctex,
+ WorkbenchContext workbenchContext,
+ Provider<FileMRUList> pMruList,
+ UIPrefs uiPrefs,
+ RnwWeaveRegistry rnwWeaveRegistry)
+ {
+ commands_ = commands;
+ view_ = view;
+ server_ = server;
+ editingTargetSource_ = editingTargetSource;
+ fileTypeRegistry_ = fileTypeRegistry;
+ globalDisplay_ = globalDisplay;
+ fileDialogs_ = fileDialogs;
+ fileContext_ = fileContext;
+ events_ = events;
+ session_ = session;
+ synctex_ = synctex;
+ workbenchContext_ = workbenchContext;
+ pMruList_ = pMruList;
+ uiPrefs_ = uiPrefs;
+ rnwWeaveRegistry_ = rnwWeaveRegistry;
+
+ view_.addTabClosingHandler(this);
+ view_.addTabCloseHandler(this);
+ view_.addTabClosedHandler(this);
+ view_.addSelectionHandler(this);
+ view_.addBeforeShowHandler(this);
+
+ dynamicCommands_ = new HashSet<AppCommand>();
+ dynamicCommands_.add(commands.saveSourceDoc());
+ dynamicCommands_.add(commands.reopenSourceDocWithEncoding());
+ dynamicCommands_.add(commands.saveSourceDocAs());
+ dynamicCommands_.add(commands.saveSourceDocWithEncoding());
+ dynamicCommands_.add(commands.printSourceDoc());
+ dynamicCommands_.add(commands.vcsFileLog());
+ dynamicCommands_.add(commands.vcsFileDiff());
+ dynamicCommands_.add(commands.vcsFileRevert());
+ dynamicCommands_.add(commands.executeCode());
+ dynamicCommands_.add(commands.executeCodeWithoutFocus());
+ dynamicCommands_.add(commands.executeAllCode());
+ dynamicCommands_.add(commands.executeToCurrentLine());
+ dynamicCommands_.add(commands.executeFromCurrentLine());
+ dynamicCommands_.add(commands.executeCurrentFunction());
+ dynamicCommands_.add(commands.executeCurrentSection());
+ dynamicCommands_.add(commands.executeLastCode());
+ dynamicCommands_.add(commands.insertChunk());
+ dynamicCommands_.add(commands.insertSection());
+ dynamicCommands_.add(commands.executeCurrentChunk());
+ dynamicCommands_.add(commands.executeNextChunk());
+ dynamicCommands_.add(commands.sourceActiveDocument());
+ dynamicCommands_.add(commands.sourceActiveDocumentWithEcho());
+ dynamicCommands_.add(commands.markdownHelp());
+ dynamicCommands_.add(commands.usingRMarkdownHelp());
+ dynamicCommands_.add(commands.authoringRPresentationsHelp());
+ dynamicCommands_.add(commands.knitDocument());
+ dynamicCommands_.add(commands.previewHTML());
+ dynamicCommands_.add(commands.compilePDF());
+ dynamicCommands_.add(commands.compileNotebook());
+ dynamicCommands_.add(commands.synctexSearch());
+ dynamicCommands_.add(commands.popoutDoc());
+ dynamicCommands_.add(commands.findReplace());
+ dynamicCommands_.add(commands.findNext());
+ dynamicCommands_.add(commands.findPrevious());
+ dynamicCommands_.add(commands.findFromSelection());
+ dynamicCommands_.add(commands.replaceAndFind());
+ dynamicCommands_.add(commands.extractFunction());
+ dynamicCommands_.add(commands.extractLocalVariable());
+ dynamicCommands_.add(commands.commentUncomment());
+ dynamicCommands_.add(commands.reindent());
+ dynamicCommands_.add(commands.reflowComment());
+ dynamicCommands_.add(commands.jumpTo());
+ dynamicCommands_.add(commands.jumpToMatching());
+ dynamicCommands_.add(commands.goToHelp());
+ dynamicCommands_.add(commands.goToFunctionDefinition());
+ dynamicCommands_.add(commands.setWorkingDirToActiveDoc());
+ dynamicCommands_.add(commands.debugDumpContents());
+ dynamicCommands_.add(commands.debugImportDump());
+ dynamicCommands_.add(commands.goToLine());
+ dynamicCommands_.add(commands.checkSpelling());
+ dynamicCommands_.add(commands.codeCompletion());
+ dynamicCommands_.add(commands.rcppHelp());
+ dynamicCommands_.add(commands.debugBreakpoint());
+ dynamicCommands_.add(commands.vcsViewOnGitHub());
+ dynamicCommands_.add(commands.vcsBlameOnGitHub());
+ for (AppCommand command : dynamicCommands_)
+ {
+ command.setVisible(false);
+ command.setEnabled(false);
+ }
+
+ // fake shortcuts for commands which we handle at a lower level
+ commands.goToHelp().setShortcut(new KeyboardShortcut(112));
+ commands.goToFunctionDefinition().setShortcut(new KeyboardShortcut(113));
+ commands.codeCompletion().setShortcut(
+ new KeyboardShortcut(KeyCodes.KEY_TAB));
+
+ // See bug 3673 and https://bugs.webkit.org/show_bug.cgi?id=41016
+ if (BrowseCap.isMacintosh())
+ {
+ ShortcutManager.INSTANCE.register(
+ KeyboardShortcut.META | KeyboardShortcut.ALT,
+ 192,
+ commands.executeNextChunk(),
+ "Execute",
+ commands.executeNextChunk().getMenuLabel(false));
+ }
+
+ events.addHandler(ShowContentEvent.TYPE, this);
+ events.addHandler(ShowDataEvent.TYPE, this);
+
+ events.addHandler(ViewDataEvent.TYPE, new ViewDataHandler()
+ {
+ public void onViewData(ViewDataEvent event)
+ {
+ server_.newDocument(
+ FileTypeRegistry.DATAFRAME.getTypeId(),
+ null,
+ JsObject.createJsObject(),
+ new SimpleRequestCallback<SourceDocument>("Edit Data Frame") {
+ public void onResponseReceived(SourceDocument response)
+ {
+ addTab(response);
+ }
+ });
+ }
+ });
+
+ events.addHandler(CodeBrowserNavigationEvent.TYPE, this);
+
+ events.addHandler(CodeBrowserFinishedEvent.TYPE, this);
+
+ events.addHandler(CodeBrowserHighlightEvent.TYPE, this);
+
+ events.addHandler(FileTypeChangedEvent.TYPE, new FileTypeChangedHandler()
+ {
+ public void onFileTypeChanged(FileTypeChangedEvent event)
+ {
+ manageCommands();
+ }
+ });
+
+ events.addHandler(SourceOnSaveChangedEvent.TYPE,
+ new SourceOnSaveChangedHandler() {
+ @Override
+ public void onSourceOnSaveChanged(SourceOnSaveChangedEvent event)
+ {
+ manageSaveCommands();
+ }
+ });
+
+ events.addHandler(SwitchToDocEvent.TYPE, new SwitchToDocHandler()
+ {
+ public void onSwitchToDoc(SwitchToDocEvent event)
+ {
+ ensureVisible(false);
+ view_.selectTab(event.getSelectedIndex());
+ }
+ });
+
+ events.addHandler(SourceFileSavedEvent.TYPE, new SourceFileSavedHandler()
+ {
+ public void onSourceFileSaved(SourceFileSavedEvent event)
+ {
+ pMruList_.get().add(event.getPath());
+ }
+ });
+
+ events.addHandler(SourceNavigationEvent.TYPE,
+ new SourceNavigationHandler() {
+ @Override
+ public void onSourceNavigation(SourceNavigationEvent event)
+ {
+ if (!suspendSourceNavigationAdding_)
+ {
+ sourceNavigationHistory_.add(event.getNavigation());
+ }
+ }
+ });
+
+ events.addHandler(SourceExtendedTypeDetectedEvent.TYPE, this);
+
+ sourceNavigationHistory_.addChangeHandler(new ChangeHandler()
+ {
+
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ manageSourceNavigationCommands();
+ }
+ });
+
+ events.addHandler(SynctexStatusChangedEvent.TYPE,
+ new SynctexStatusChangedEvent.Handler()
+ {
+ @Override
+ public void onSynctexStatusChanged(SynctexStatusChangedEvent event)
+ {
+ manageSynctexCommands();
+ }
+ });
+
+ restoreDocuments(session);
+
+ new IntStateValue(MODULE_SOURCE, KEY_ACTIVETAB, ClientState.PROJECT_PERSISTENT,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(Integer value)
+ {
+ if (value == null)
+ return;
+ if (value >= 0 && view_.getTabCount() > value)
+ view_.selectTab(value);
+
+ if (view_.getTabCount() > 0 && view_.getActiveTabIndex() >= 0)
+ {
+ editors_.get(view_.getActiveTabIndex()).onInitiallyLoaded();
+ }
+
+ // clear the history manager
+ sourceNavigationHistory_.clear();
+ }
+
+ @Override
+ protected Integer getValue()
+ {
+ return view_.getActiveTabIndex();
+ }
+ };
+
+ uiPrefs_.verticallyAlignArgumentIndent().bind(new CommandWithArg<Boolean>()
+ {
+ @Override
+ public void execute(Boolean arg)
+ {
+ AceEditorNative.setVerticallyAlignFunctionArgs(arg);
+ }
+ });
+
+ initialized_ = true;
+ // As tabs were added before, manageCommands() was suppressed due to
+ // initialized_ being false, so we need to run it explicitly
+ manageCommands();
+ // Same with this event
+ fireDocTabsChanged();
+
+ // open project docs
+ openProjectDocs(session);
+ }
+
+ /**
+ * @param isNewTabPending True if a new tab is about to be created. (If
+ * false and there are no tabs already, then a new source doc might
+ * be created to make sure we don't end up with a source pane showing
+ * with no tabs in it.)
+ */
+ private void ensureVisible(boolean isNewTabPending)
+ {
+ newTabPending_++;
+ try
+ {
+ view_.ensureVisible();
+ }
+ finally
+ {
+ newTabPending_--;
+ }
+ }
+
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ private void restoreDocuments(final Session session)
+ {
+ final JsArray<SourceDocument> docs =
+ session.getSessionInfo().getSourceDocuments();
+
+ for (int i = 0; i < docs.length(); i++)
+ {
+ addTab(docs.get(i));
+ }
+ }
+
+ private void openProjectDocs(final Session session)
+ {
+ JsArrayString openDocs = session.getSessionInfo().getProjectOpenDocs();
+ if (openDocs.length() > 0)
+ {
+ // set new tab pending for the duration of the continuation
+ newTabPending_++;
+
+ // create a continuation for opening the source docs
+ SerializedCommandQueue openCommands = new SerializedCommandQueue();
+
+ for (int i=0; i<openDocs.length(); i++)
+ {
+ String doc = openDocs.get(i);
+ final FileSystemItem fsi = FileSystemItem.createFile(doc);
+
+ openCommands.addCommand(new SerializedCommand() {
+
+ @Override
+ public void onExecute(final Command continuation)
+ {
+ openFile(fsi,
+ fileTypeRegistry_.getTextTypeForFile(fsi),
+ new CommandWithArg<EditingTarget>() {
+ @Override
+ public void execute(EditingTarget arg)
+ {
+ continuation.execute();
+ }
+ });
+ }
+ });
+ }
+
+ // decrement newTabPending and select first tab when done
+ openCommands.addCommand(new SerializedCommand() {
+
+ @Override
+ public void onExecute(Command continuation)
+ {
+ newTabPending_--;
+ onFirstTab();
+ continuation.execute();
+ }
+
+ });
+
+ // execute the continuation
+ openCommands.run();
+ }
+ }
+
+ public void onShowContent(ShowContentEvent event)
+ {
+ ensureVisible(true);
+ ContentItem content = event.getContent();
+ server_.newDocument(
+ FileTypeRegistry.URLCONTENT.getTypeId(),
+ null,
+ (JsObject) content.cast(),
+ new SimpleRequestCallback<SourceDocument>("Show")
+ {
+ @Override
+ public void onResponseReceived(SourceDocument response)
+ {
+ addTab(response);
+ }
+ });
+ }
+
+ public void onShowData(ShowDataEvent event)
+ {
+ DataItem data = event.getData();
+
+ for (int i = 0; i < editors_.size(); i++)
+ {
+ String path = editors_.get(i).getPath();
+ if (path != null && path.equals(data.getURI()))
+ {
+ ((DataEditingTarget)editors_.get(i)).updateData(data);
+
+ ensureVisible(false);
+ view_.selectTab(i);
+ return;
+ }
+ }
+
+ ensureVisible(true);
+ server_.newDocument(
+ FileTypeRegistry.DATAFRAME.getTypeId(),
+ null,
+ (JsObject) data.cast(),
+ new SimpleRequestCallback<SourceDocument>("Show Data Frame")
+ {
+ @Override
+ public void onResponseReceived(SourceDocument response)
+ {
+ addTab(response);
+ }
+ });
+ }
+
+ @Handler
+ public void onShowProfiler()
+ {
+ // first try to activate existing
+ for (int idx = 0; idx < editors_.size(); idx++)
+ {
+ String path = editors_.get(idx).getPath();
+ if (ProfilerEditingTarget.PATH.equals(path))
+ {
+ ensureVisible(false);
+ view_.selectTab(idx);
+ return;
+ }
+ }
+
+ // create new profiler
+ ensureVisible(true);
+ server_.newDocument(
+ FileTypeRegistry.PROFILER.getTypeId(),
+ null,
+ (JsObject) ProfilerContents.createDefault().cast(),
+ new SimpleRequestCallback<SourceDocument>("Show Profiler")
+ {
+ @Override
+ public void onResponseReceived(SourceDocument response)
+ {
+ addTab(response);
+ }
+ });
+ }
+
+
+ @Handler
+ public void onNewSourceDoc()
+ {
+ newDoc(FileTypeRegistry.R, null);
+ }
+
+ @Handler
+ public void onNewTextDoc()
+ {
+ newDoc(FileTypeRegistry.TEXT, null);
+ }
+
+ @Handler
+ public void onNewCppDoc()
+ {
+ if (uiPrefs_.useRcppTemplate().getValue())
+ {
+ newSourceDocWithTemplate(
+ FileTypeRegistry.CPP,
+ "",
+ "rcpp.cpp",
+ Position.create(0, 0),
+ new CommandWithArg<EditingTarget> () {
+ @Override
+ public void execute(EditingTarget target)
+ {
+ target.verifyCppPrerequisites();
+ }
+ }
+ );
+ }
+ else
+ {
+ newDoc(FileTypeRegistry.CPP,
+ new ResultCallback<EditingTarget, ServerError> () {
+ @Override
+ public void onSuccess(EditingTarget target)
+ {
+ target.verifyCppPrerequisites();
+ }
+ });
+ }
+ }
+
+ @Handler
+ public void onNewSweaveDoc()
+ {
+ // set concordance value if we need to
+ String concordance = new String();
+ if (uiPrefs_.alwaysEnableRnwConcordance().getValue())
+ {
+ RnwWeave activeWeave = rnwWeaveRegistry_.findTypeIgnoreCase(
+ uiPrefs_.defaultSweaveEngine().getValue());
+ if (activeWeave.getInjectConcordance())
+ concordance = "\\SweaveOpts{concordance=TRUE}\n";
+ }
+ final String concordanceValue = concordance;
+
+ // show progress
+ final ProgressIndicator indicator = new GlobalProgressDelayer(
+ globalDisplay_, 500, "Creating new document...").getIndicator();
+
+ // get the template
+ server_.getSourceTemplate("",
+ "sweave.Rnw",
+ new ServerRequestCallback<String>() {
+ @Override
+ public void onResponseReceived(String templateContents)
+ {
+ indicator.onCompleted();
+
+ // add in concordance if necessary
+ final boolean hasConcordance = concordanceValue.length() > 0;
+ if (hasConcordance)
+ {
+ String beginDoc = "\\begin{document}\n";
+ templateContents = templateContents.replace(
+ beginDoc,
+ beginDoc + concordanceValue);
+ }
+
+ newDoc(FileTypeRegistry.SWEAVE,
+ templateContents,
+ new ResultCallback<EditingTarget, ServerError> () {
+ @Override
+ public void onSuccess(EditingTarget target)
+ {
+ int startRow = 4 + (hasConcordance ? 1 : 0);
+ target.setCursorPosition(Position.create(startRow, 0));
+ }
+ });
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+ @Handler
+ public void onNewRMarkdownDoc()
+ {
+ newSourceDocWithTemplate(FileTypeRegistry.RMARKDOWN,
+ "",
+ "r_markdown.Rmd",
+ Position.create(3, 0));
+ }
+
+ @Handler
+ public void onNewRHTMLDoc()
+ {
+ newSourceDocWithTemplate(FileTypeRegistry.RHTML,
+ "",
+ "r_html.Rhtml");
+ }
+
+
+ @Handler
+ public void onNewRDocumentationDoc()
+ {
+ new NewRdDialog(
+ new OperationWithInput<NewRdDialog.Result>() {
+
+ @Override
+ public void execute(final NewRdDialog.Result result)
+ {
+ final Command createEmptyDoc = new Command() {
+ @Override
+ public void execute()
+ {
+ newSourceDocWithTemplate(FileTypeRegistry.RD,
+ result.name,
+ "r_documentation_empty.Rd",
+ Position.create(3, 7));
+ }
+ };
+
+ if (!result.type.equals(NewRdDialog.Result.TYPE_NONE))
+ {
+ server_.createRdShell(
+ result.name,
+ result.type,
+ new SimpleRequestCallback<RdShellResult>() {
+ @Override
+ public void onResponseReceived(RdShellResult result)
+ {
+ if (result.getPath() != null)
+ {
+ fileTypeRegistry_.openFile(
+ FileSystemItem.createFile(result.getPath()));
+ }
+ else if (result.getContents() != null)
+ {
+ newDoc(FileTypeRegistry.RD,
+ result.getContents(),
+ null);
+ }
+ else
+ {
+ createEmptyDoc.execute();
+ }
+ }
+ });
+
+ }
+ else
+ {
+ createEmptyDoc.execute();
+ }
+
+ }
+ }).showModal();
+ }
+
+ @Handler
+ public void onNewRPresentationDoc()
+ {
+ fileDialogs_.saveFile(
+ "New R Presentation",
+ fileContext_,
+ workbenchContext_.getDefaultFileDialogDir(),
+ ".Rpres",
+ true,
+ new ProgressOperationWithInput<FileSystemItem>() {
+
+ @Override
+ public void execute(final FileSystemItem input,
+ final ProgressIndicator indicator)
+ {
+ if (input == null)
+ {
+ indicator.onCompleted();
+ return;
+ }
+
+ indicator.onProgress("Creating Presentation...");
+
+ server_.createNewPresentation(
+ input.getPath(),
+ new VoidServerRequestCallback(indicator) {
+ @Override
+ public void onSuccess()
+ {
+ openFile(input,
+ FileTypeRegistry.RPRESENTATION,
+ new CommandWithArg<EditingTarget>() {
+
+ @Override
+ public void execute(EditingTarget arg)
+ {
+ server_.showPresentationPane(
+ input.getPath(),
+ new VoidServerRequestCallback());
+
+ }
+
+ });
+ }
+ });
+
+ }
+
+ });
+ }
+
+ private void newSourceDocWithTemplate(final TextFileType fileType,
+ String name,
+ String template)
+ {
+ newSourceDocWithTemplate(fileType, name, template, null);
+ }
+
+ private void newSourceDocWithTemplate(final TextFileType fileType,
+ String name,
+ String template,
+ final Position cursorPosition)
+ {
+ newSourceDocWithTemplate(fileType, name, template, cursorPosition, null);
+ }
+
+ private void newSourceDocWithTemplate(
+ final TextFileType fileType,
+ String name,
+ String template,
+ final Position cursorPosition,
+ final CommandWithArg<EditingTarget> onSuccess)
+ {
+ final ProgressIndicator indicator = new GlobalProgressDelayer(
+ globalDisplay_, 500, "Creating new document...").getIndicator();
+
+ server_.getSourceTemplate(name,
+ template,
+ new ServerRequestCallback<String>() {
+ @Override
+ public void onResponseReceived(String templateContents)
+ {
+ indicator.onCompleted();
+
+ newDoc(fileType,
+ templateContents,
+ new ResultCallback<EditingTarget, ServerError> () {
+ @Override
+ public void onSuccess(EditingTarget target)
+ {
+ if (cursorPosition != null)
+ target.setCursorPosition(cursorPosition);
+
+ if (onSuccess != null)
+ onSuccess.execute(target);
+ }
+ });
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ });
+ }
+
+
+ private void newDoc(EditableFileType fileType,
+ ResultCallback<EditingTarget, ServerError> callback)
+ {
+ newDoc(fileType, null, callback);
+ }
+
+ private void newDoc(EditableFileType fileType,
+ String contents,
+ final ResultCallback<EditingTarget, ServerError> resultCallback)
+ {
+ ensureVisible(true);
+ server_.newDocument(
+ fileType.getTypeId(),
+ contents,
+ JsObject.createJsObject(),
+ new SimpleRequestCallback<SourceDocument>(
+ "Error Creating New Document")
+ {
+ @Override
+ public void onResponseReceived(SourceDocument newDoc)
+ {
+ EditingTarget target = addTab(newDoc);
+ if (resultCallback != null)
+ resultCallback.onSuccess(target);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (resultCallback != null)
+ resultCallback.onFailure(error);
+ }
+ });
+ }
+
+ @Handler
+ public void onFindInFiles()
+ {
+ String searchPattern = "";
+ if (activeEditor_ != null && activeEditor_ instanceof TextEditingTarget)
+ {
+ TextEditingTarget textEditor = (TextEditingTarget) activeEditor_;
+ String selection = textEditor.getSelectedText();
+ boolean multiLineSelection = selection.indexOf('\n') != -1;
+
+ if ((selection.length() != 0) && !multiLineSelection)
+ searchPattern = selection;
+ }
+
+ events_.fireEvent(new FindInFilesEvent(searchPattern));
+ }
+
+ @Handler
+ public void onActivateSource()
+ {
+ ensureVisible(false);
+ if (activeEditor_ != null)
+ {
+ activeEditor_.focus();
+ activeEditor_.ensureCursorVisible();
+ }
+ }
+
+ @Handler
+ public void onSwitchToTab()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ ensureVisible(false);
+
+ view_.showOverflowPopup();
+ }
+
+ @Handler
+ public void onFirstTab()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ ensureVisible(false);
+ if (view_.getTabCount() > 0)
+ view_.selectTab(0);
+ }
+
+ @Handler
+ public void onPreviousTab()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ ensureVisible(false);
+ int index = view_.getActiveTabIndex();
+ if (index >= 1)
+ view_.selectTab(index - 1);
+ }
+
+ @Handler
+ public void onNextTab()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ ensureVisible(false);
+ int index = view_.getActiveTabIndex();
+ if (index < view_.getTabCount() - 1)
+ view_.selectTab(index + 1);
+ }
+
+ @Handler
+ public void onLastTab()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ ensureVisible(false);
+ if (view_.getTabCount() > 0)
+ view_.selectTab(view_.getTabCount() - 1);
+ }
+
+ @Handler
+ public void onCloseSourceDoc()
+ {
+ if (view_.getTabCount() == 0)
+ return;
+
+ view_.closeTab(view_.getActiveTabIndex(), true);
+ }
+
+ /**
+ * Execute the given command for each editor, using continuation-passing
+ * style. When executed, the CPSEditingTargetCommand needs to execute its
+ * own Command parameter to continue the iteration.
+ * @param command The command to run on each EditingTarget
+ */
+ private void cpsExecuteForEachEditor(ArrayList<EditingTarget> editors,
+ final CPSEditingTargetCommand command,
+ final Command completedCommand)
+ {
+ SerializedCommandQueue queue = new SerializedCommandQueue();
+
+ // Clone editors_, since the original may be mutated during iteration
+ for (final EditingTarget editor : new ArrayList<EditingTarget>(editors))
+ {
+ queue.addCommand(new SerializedCommand()
+ {
+ @Override
+ public void onExecute(Command continuation)
+ {
+ command.execute(editor, continuation);
+ }
+ });
+ }
+
+ if (completedCommand != null)
+ {
+ queue.addCommand(new SerializedCommand() {
+
+ public void onExecute(Command continuation)
+ {
+ completedCommand.execute();
+ continuation.execute();
+ }
+ });
+ }
+ }
+
+ private void cpsExecuteForEachEditor(ArrayList<EditingTarget> editors,
+ final CPSEditingTargetCommand command)
+ {
+ cpsExecuteForEachEditor(editors, command, null);
+ }
+
+
+ @Handler
+ public void onSaveAllSourceDocs()
+ {
+ cpsExecuteForEachEditor(editors_, new CPSEditingTargetCommand()
+ {
+ @Override
+ public void execute(EditingTarget target, Command continuation)
+ {
+ if (target.dirtyState().getValue())
+ {
+ target.save(continuation);
+ }
+ else
+ {
+ continuation.execute();
+ }
+ }
+ });
+ }
+
+
+ private void saveEditingTargetsWithPrompt(
+ String title,
+ ArrayList<EditingTarget> editingTargets,
+ final Command onCompleted,
+ final Command onCancelled)
+ {
+ // execute on completed right away if the list is empty
+ if (editingTargets.size() == 0)
+ {
+ onCompleted.execute();
+ }
+
+ // if there is just one thing dirty then go straight to the save dialog
+ else if (editingTargets.size() == 1)
+ {
+ editingTargets.get(0).saveWithPrompt(onCompleted, onCancelled);
+ }
+
+ // otherwise use the multi save changes dialog
+ else
+ {
+ // convert to UnsavedChangesTarget collection
+ ArrayList<UnsavedChangesTarget> unsavedTargets =
+ new ArrayList<UnsavedChangesTarget>();
+ unsavedTargets.addAll(editingTargets);
+
+ // show dialog
+ view_.showUnsavedChangesDialog(
+ title,
+ unsavedTargets,
+ new OperationWithInput<UnsavedChangesDialog.Result>()
+ {
+ @Override
+ public void execute(UnsavedChangesDialog.Result result)
+ {
+ saveChanges(result.getSaveTargets(), onCompleted);
+ }
+ },
+ onCancelled);
+ }
+ }
+
+ private void saveChanges(ArrayList<UnsavedChangesTarget> targets,
+ Command onCompleted)
+ {
+ // convert back to editing targets
+ ArrayList<EditingTarget> saveTargets =
+ new ArrayList<EditingTarget>();
+ for (UnsavedChangesTarget target: targets)
+ {
+ EditingTarget saveTarget =
+ getEditingTargetForId(target.getId());
+ if (saveTarget != null)
+ saveTargets.add(saveTarget);
+ }
+
+ // execute the save
+ cpsExecuteForEachEditor(
+
+ // targets the user chose to save
+ saveTargets,
+
+ // save each editor
+ new CPSEditingTargetCommand()
+ {
+ @Override
+ public void execute(EditingTarget saveTarget,
+ Command continuation)
+ {
+ saveTarget.save(continuation);
+ }
+ },
+
+ // onCompleted at the end
+ onCompleted
+ );
+ }
+
+
+ private EditingTarget getEditingTargetForId(String id)
+ {
+ for (EditingTarget target : editors_)
+ if (id.equals(target.getId()))
+ return target;
+
+ return null;
+ }
+
+ @Handler
+ public void onCloseAllSourceDocs()
+ {
+ closeAllSourceDocs("Close All", null);
+ }
+
+ public void closeAllSourceDocs(String caption, Command onCompleted)
+ {
+ // collect up a list of dirty documents
+ ArrayList<EditingTarget> dirtyTargets = new ArrayList<EditingTarget>();
+ for (EditingTarget target : editors_)
+ if (target.dirtyState().getValue())
+ dirtyTargets.add(target);
+
+ // create a command used to close all tabs
+ final Command closeAllTabsCommand = new Command()
+ {
+ @Override
+ public void execute()
+ {
+ cpsExecuteForEachEditor(editors_, new CPSEditingTargetCommand()
+ {
+ @Override
+ public void execute(EditingTarget target, Command continuation)
+ {
+ view_.closeTab(target.asWidget(), false, continuation);
+ }
+ });
+
+ }
+ };
+
+ // save targets
+ saveEditingTargetsWithPrompt(caption,
+ dirtyTargets,
+ CommandUtil.join(closeAllTabsCommand,
+ onCompleted),
+ null);
+
+ }
+
+ private boolean isUnsavedFileBackedTarget(EditingTarget target)
+ {
+ return target.dirtyState().getValue() && (target.getPath() != null);
+ }
+
+ public ArrayList<UnsavedChangesTarget> getUnsavedChanges()
+ {
+ ArrayList<UnsavedChangesTarget> targets =
+ new ArrayList<UnsavedChangesTarget>();
+ for (EditingTarget target : editors_)
+ if (isUnsavedFileBackedTarget(target))
+ targets.add(target);
+
+ return targets;
+ }
+
+ public void saveAllUnsaved(Command onCompleted)
+ {
+ saveChanges(getUnsavedChanges(), onCompleted);
+ }
+
+ public void saveWithPrompt(UnsavedChangesTarget target,
+ Command onCompleted,
+ Command onCancelled)
+ {
+ EditingTarget editingTarget = getEditingTargetForId(target.getId());
+ if (editingTarget != null)
+ editingTarget.saveWithPrompt(onCompleted, onCancelled);
+ }
+
+ public void handleUnsavedChangesBeforeExit(
+ ArrayList<UnsavedChangesTarget> saveTargets,
+ final Command onCompleted)
+ {
+ // first handle saves, then revert unsaved, then callback on completed
+ saveChanges(saveTargets, new Command() {
+
+ @Override
+ public void execute()
+ {
+ // revert unsaved
+ revertUnsavedTargets(onCompleted);
+ }
+ });
+ }
+
+ private void revertUnsavedTargets(Command onCompleted)
+ {
+ // collect up unsaved targets
+ ArrayList<EditingTarget> unsavedTargets = new ArrayList<EditingTarget>();
+ for (EditingTarget target : editors_)
+ if (isUnsavedFileBackedTarget(target))
+ unsavedTargets.add(target);
+
+ // revert all of them
+ cpsExecuteForEachEditor(
+
+ // targets the user chose not to save
+ unsavedTargets,
+
+ // save each editor
+ new CPSEditingTargetCommand()
+ {
+ @Override
+ public void execute(EditingTarget saveTarget,
+ Command continuation)
+ {
+ if (saveTarget.getPath() != null)
+ {
+ // file backed document -- revert it
+ saveTarget.revertChanges(continuation);
+ }
+ else
+ {
+ // untitled document -- just close the tab non-interactively
+ view_.closeTab(saveTarget.asWidget(), false, continuation);
+ }
+ }
+ },
+
+ // onCompleted at the end
+ onCompleted
+ );
+
+ }
+
+
+ @Handler
+ public void onOpenSourceDoc()
+ {
+ fileDialogs_.openFile(
+ "Open File",
+ fileContext_,
+ workbenchContext_.getDefaultFileDialogDir(),
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(final FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ return;
+
+ workbenchContext_.setDefaultFileDialogDir(
+ input.getParentPath());
+
+ indicator.onCompleted();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ fileTypeRegistry_.openFile(input);
+ }
+ });
+ }
+ });
+ }
+
+
+ public void onOpenSourceFile(OpenSourceFileEvent event)
+ {
+ doOpenSourceFile(event.getFile(),
+ event.getFileType(),
+ event.getPosition(),
+ null,
+ event.getNavigationMethod(),
+ false);
+ }
+
+
+
+ public void onOpenPresentationSourceFile(OpenPresentationSourceFileEvent event)
+ {
+ // don't do the navigation if the active document is a source
+ // file from this presentation module
+
+ doOpenSourceFile(event.getFile(),
+ event.getFileType(),
+ event.getPosition(),
+ event.getPattern(),
+ NavigationMethod.HighlightLine,
+ true);
+
+ }
+
+ public void onEditPresentationSource(final EditPresentationSourceEvent event)
+ {
+ openFile(
+ event.getSourceFile(),
+ FileTypeRegistry.RPRESENTATION,
+ new CommandWithArg<EditingTarget>() {
+ @Override
+ public void execute(final EditingTarget editor)
+ {
+ TextEditingTargetPresentationHelper.navigateToSlide(
+ editor,
+ event.getSlideIndex());
+ }
+ });
+ }
+
+
+ private void doOpenSourceFile(final FileSystemItem file,
+ final TextFileType fileType,
+ final FilePosition position,
+ final String pattern,
+ final NavigationMethod navMethod,
+ final boolean forceHighlightMode)
+ {
+ final boolean isDebugNavigation =
+ navMethod == NavigationMethod.DebugStep ||
+ navMethod == NavigationMethod.DebugFrame ||
+ navMethod == NavigationMethod.DebugEnd;
+ if (isDebugNavigation)
+ {
+ setPendingDebugSelection();
+ }
+
+ final CommandWithArg<FileSystemItem> action = new CommandWithArg<FileSystemItem>()
+ {
+ @Override
+ public void execute(FileSystemItem file)
+ {
+ openFile(file,
+ fileType,
+ new CommandWithArg<EditingTarget>() {
+
+ @Override
+ public void execute(EditingTarget target)
+ {
+ if (position != null)
+ {
+ SourcePosition endPosition = null;
+ if (isDebugNavigation)
+ {
+ DebugFilePosition filePos =
+ (DebugFilePosition) position.cast();
+ endPosition = SourcePosition.create(
+ filePos.getEndLine() - 1,
+ filePos.getEndColumn() + 1);
+
+ if (Desktop.isDesktop() &&
+ navMethod != NavigationMethod.DebugEnd)
+ Desktop.getFrame().bringMainFrameToFront();
+ }
+ navigate(target,
+ SourcePosition.create(position.getLine() - 1,
+ position.getColumn() - 1),
+ endPosition);
+ }
+ else if (pattern != null)
+ {
+ Position pos = target.search(pattern);
+ if (pos != null)
+ {
+ navigate(target,
+ SourcePosition.create(pos.getRow(), 0),
+ null);
+ }
+ }
+ }
+
+ });
+ }
+
+ private void navigate(final EditingTarget target,
+ final SourcePosition srcPosition,
+ final SourcePosition srcEndPosition)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ if (navMethod == NavigationMethod.DebugStep)
+ {
+ target.highlightDebugLocation(
+ srcPosition,
+ srcEndPosition,
+ true);
+ }
+ else if (navMethod == NavigationMethod.DebugFrame)
+ {
+ target.highlightDebugLocation(
+ srcPosition,
+ srcEndPosition,
+ false);
+ }
+ else if (navMethod == NavigationMethod.DebugEnd)
+ {
+ target.endDebugHighlighting();
+ }
+ else
+ {
+ // force highlight mode if requested
+ if (forceHighlightMode)
+ target.forceLineHighlighting();
+
+ // now navigate to the new position
+ boolean highlight =
+ navMethod == NavigationMethod.HighlightLine &&
+ !uiPrefs_.highlightSelectedLine().getValue();
+ target.navigateToPosition(srcPosition,
+ false,
+ highlight);
+ }
+ }
+ });
+ }
+ };
+
+ // Warning: event.getFile() can be null (e.g. new Sweave document)
+ if (file != null && file.getLength() < 0)
+ {
+ // If the file has no size info, stat the file from the server. This
+ // is to prevent us from opening large files accidentally.
+
+ server_.stat(file.getPath(), new ServerRequestCallback<FileSystemItem>()
+ {
+ @Override
+ public void onResponseReceived(FileSystemItem response)
+ {
+ action.execute(response);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // Couldn't stat the file? Proceed anyway. If the file doesn't
+ // exist, we'll let the downstream code be the one to show the
+ // error.
+ action.execute(file);
+ }
+ });
+ }
+ else
+ {
+ action.execute(file);
+ }
+ }
+
+
+ private void openFile(FileSystemItem file)
+ {
+ openFile(file, fileTypeRegistry_.getTextTypeForFile(file));
+ }
+
+ private void openFile(FileSystemItem file, TextFileType fileType)
+ {
+ openFile(file,
+ fileType,
+ new CommandWithArg<EditingTarget>() {
+ @Override
+ public void execute(EditingTarget arg)
+ {
+
+ }
+ });
+ }
+
+ private void openFile(final FileSystemItem file,
+ final TextFileType fileType,
+ final CommandWithArg<EditingTarget> executeOnSuccess)
+ {
+ openFile(file,
+ fileType,
+ new ResultCallback<EditingTarget, ServerError>() {
+ @Override
+ public void onSuccess(EditingTarget target)
+ {
+ if (executeOnSuccess != null)
+ executeOnSuccess.execute(target);
+ }
+
+ @Override
+ public void onFailure(ServerError error)
+ {
+ String message = error.getUserMessage();
+
+ // see if a special message was provided
+ JSONValue errValue = error.getClientInfo();
+ if (errValue != null)
+ {
+ JSONString errMsg = errValue.isString();
+ if (errMsg != null)
+ message = errMsg.stringValue();
+ }
+
+ globalDisplay_.showMessage(GlobalDisplay.MSG_ERROR,
+ "Error while opening file",
+ message);
+
+ }
+ });
+ }
+
+ // top-level wrapper for opening files. takes care of:
+ // - making sure the view is visible
+ // - checking whether it is already open and re-selecting its tab
+ // - prohibit opening very large files (>500KB)
+ // - confirmation of opening large files (>100KB)
+ // - finally, actually opening the file from the server
+ // via the call to the lower level openFile method
+ private void openFile(final FileSystemItem file,
+ final TextFileType fileType,
+ final ResultCallback<EditingTarget, ServerError> resultCallback)
+ {
+ ensureVisible(true);
+
+ if (file == null)
+ {
+ newDoc(fileType, resultCallback);
+ return;
+ }
+
+ for (int i = 0; i < editors_.size(); i++)
+ {
+ EditingTarget target = editors_.get(i);
+ String thisPath = target.getPath();
+ if (thisPath != null
+ && thisPath.equalsIgnoreCase(file.getPath()))
+ {
+ view_.selectTab(i);
+ pMruList_.get().add(thisPath);
+ if (resultCallback != null)
+ resultCallback.onSuccess(target);
+ return;
+ }
+ }
+
+ EditingTarget target = editingTargetSource_.getEditingTarget(fileType);
+
+ if (file.getLength() > target.getFileSizeLimit())
+ {
+ if (resultCallback != null)
+ resultCallback.onCancelled();
+ showFileTooLargeWarning(file, target.getFileSizeLimit());
+ }
+ else if (file.getLength() > target.getLargeFileSize())
+ {
+ confirmOpenLargeFile(file, new Operation() {
+ public void execute()
+ {
+ openFileFromServer(file, fileType, resultCallback);
+ }
+ }, new Operation() {
+ public void execute()
+ {
+ // user (wisely) cancelled
+ if (resultCallback != null)
+ resultCallback.onCancelled();
+ }
+ });
+ }
+ else
+ {
+ openFileFromServer(file, fileType, resultCallback);
+ }
+ }
+
+ private void showFileTooLargeWarning(FileSystemItem file,
+ long sizeLimit)
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("The file '" + file.getName() + "' is too ");
+ msg.append("large to open in the source editor (the file is ");
+ msg.append(StringUtil.formatFileSize(file.getLength()) + " and the ");
+ msg.append("maximum file size is ");
+ msg.append(StringUtil.formatFileSize(sizeLimit) + ")");
+
+ globalDisplay_.showMessage(GlobalDisplay.MSG_WARNING,
+ "Selected File Too Large",
+ msg.toString());
+ }
+
+ private void confirmOpenLargeFile(FileSystemItem file,
+ Operation openOperation,
+ Operation cancelOperation)
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("The source file '" + file.getName() + "' is large (");
+ msg.append(StringUtil.formatFileSize(file.getLength()) + ") ");
+ msg.append("and may take some time to open. ");
+ msg.append("Are you sure you want to continue opening it?");
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_WARNING,
+ "Confirm Open",
+ msg.toString(),
+ openOperation,
+ false); // 'No' is default
+ }
+
+ private void openFileFromServer(
+ final FileSystemItem file,
+ final TextFileType fileType,
+ final ResultCallback<EditingTarget, ServerError> resultCallback)
+ {
+ final Command dismissProgress = globalDisplay_.showProgress(
+ "Opening file...");
+
+ server_.openDocument(
+ file.getPath(),
+ fileType.getTypeId(),
+ uiPrefs_.defaultEncoding().getValue(),
+ new ServerRequestCallback<SourceDocument>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ dismissProgress.execute();
+ pMruList_.get().remove(file.getPath());
+ Debug.logError(error);
+ if (resultCallback != null)
+ resultCallback.onFailure(error);
+ }
+
+ @Override
+ public void onResponseReceived(SourceDocument document)
+ {
+ dismissProgress.execute();
+ pMruList_.get().add(document.getPath());
+ EditingTarget target = addTab(document);
+ if (resultCallback != null)
+ resultCallback.onSuccess(target);
+ }
+ });
+ }
+
+
+ private EditingTarget addTab(SourceDocument doc)
+ {
+ final EditingTarget target = editingTargetSource_.getEditingTarget(
+ doc, fileContext_, new Provider<String>()
+ {
+ public String get()
+ {
+ return getNextDefaultName();
+ }
+ });
+
+ final Widget widget = target.asWidget();
+
+ editors_.add(target);
+ view_.addTab(widget,
+ target.getIcon(),
+ target.getName().getValue(),
+ target.getTabTooltip(), // used as tooltip, if non-null
+ true);
+ fireDocTabsChanged();
+
+ target.getName().addValueChangeHandler(new ValueChangeHandler<String>()
+ {
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ view_.renameTab(widget,
+ target.getIcon(),
+ event.getValue(),
+ target.getPath());
+ fireDocTabsChanged();
+ }
+ });
+
+ view_.setDirty(widget, target.dirtyState().getValue());
+ target.dirtyState().addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ view_.setDirty(widget, event.getValue());
+ manageCommands();
+ }
+ });
+
+ target.addEnsureVisibleHandler(new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ view_.selectTab(widget);
+ }
+ });
+
+ target.addCloseHandler(new CloseHandler<Void>()
+ {
+ public void onClose(CloseEvent<Void> voidCloseEvent)
+ {
+ view_.closeTab(widget, false);
+ }
+ });
+
+ return target;
+ }
+
+ private String getNextDefaultName()
+ {
+ int max = 0;
+ for (EditingTarget target : editors_)
+ {
+ String name = target.getName().getValue();
+ max = Math.max(max, getUntitledNum(name));
+ }
+
+ return "Untitled" + (max + 1);
+ }
+
+ private native final int getUntitledNum(String name) /*-{
+ var match = /^Untitled([0-9]{1,5})$/.exec(name);
+ if (!match)
+ return 0;
+ return parseInt(match[1]);
+ }-*/;
+
+ public void onInsertSource(final InsertSourceEvent event)
+ {
+ if (activeEditor_ != null
+ && activeEditor_ instanceof TextEditingTarget
+ && commands_.executeCode().isEnabled())
+ {
+ TextEditingTarget textEditor = (TextEditingTarget) activeEditor_;
+ textEditor.insertCode(event.getCode(), event.isBlock());
+ }
+ else
+ {
+ newDoc(FileTypeRegistry.R,
+ new ResultCallback<EditingTarget, ServerError>()
+ {
+ public void onSuccess(EditingTarget arg)
+ {
+ ((TextEditingTarget)arg).insertCode(event.getCode(),
+ event.isBlock());
+ }
+ });
+ }
+ }
+
+ public void onTabClosing(final TabClosingEvent event)
+ {
+ EditingTarget target = editors_.get(event.getTabIndex());
+ if (!target.onBeforeDismiss())
+ event.cancel();
+ }
+
+ @Override
+ public void onTabClose(TabCloseEvent event)
+ {
+ // can't proceed if there is no active editor
+ if (activeEditor_ == null)
+ return;
+
+ if (event.getTabIndex() >= editors_.size())
+ return; // Seems like this should never happen...?
+
+ final String activeEditorId = activeEditor_.getId();
+
+ if (editors_.get(event.getTabIndex()).getId().equals(activeEditorId))
+ {
+ // scan the source navigation history for an entry that can
+ // be used as the next active tab (anything that doesn't have
+ // the same document id as the currently active tab)
+ SourceNavigation srcNav = sourceNavigationHistory_.scanBack(
+ new SourceNavigationHistory.Filter()
+ {
+ public boolean includeEntry(SourceNavigation navigation)
+ {
+ return !navigation.getDocumentId().equals(activeEditorId);
+ }
+ });
+
+ // see if the source navigation we found corresponds to an active
+ // tab -- if it does then set this on the event
+ if (srcNav != null)
+ {
+ for (int i=0; i<editors_.size(); i++)
+ {
+ if (srcNav.getDocumentId().equals(editors_.get(i).getId()))
+ {
+ view_.selectTab(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public void onTabClosed(TabClosedEvent event)
+ {
+ EditingTarget target = editors_.remove(event.getTabIndex());
+ target.onDismiss();
+ if (activeEditor_ == target)
+ {
+ activeEditor_.onDeactivate();
+ activeEditor_ = null;
+ }
+ server_.closeDocument(target.getId(),
+ new VoidServerRequestCallback());
+
+ manageCommands();
+ fireDocTabsChanged();
+
+ if (view_.getTabCount() == 0)
+ {
+ sourceNavigationHistory_.clear();
+ events_.fireEvent(new LastSourceDocClosedEvent());
+ }
+ }
+
+ private void fireDocTabsChanged()
+ {
+ if (!initialized_)
+ return;
+
+ String[] ids = new String[editors_.size()];
+ ImageResource[] icons = new ImageResource[editors_.size()];
+ String[] names = new String[editors_.size()];
+ String[] paths = new String[editors_.size()];
+ for (int i = 0; i < ids.length; i++)
+ {
+ EditingTarget target = editors_.get(i);
+ ids[i] = target.getId();
+ icons[i] = target.getIcon();
+ names[i] = target.getName().getValue();
+ paths[i] = target.getPath();
+ }
+
+ events_.fireEvent(new DocTabsChangedEvent(ids, icons, names, paths));
+
+ view_.manageChevronVisibility();
+ }
+
+ public void onSelection(SelectionEvent<Integer> event)
+ {
+ if (activeEditor_ != null)
+ activeEditor_.onDeactivate();
+
+ activeEditor_ = null;
+
+ if (event.getSelectedItem() >= 0)
+ {
+ activeEditor_ = editors_.get(event.getSelectedItem());
+ activeEditor_.onActivate();
+ // don't send focus to the tab if we're expecting a debug selection
+ // event
+ if (initialized_ && !isDebugSelectionPending())
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ if (activeEditor_ != null)
+ activeEditor_.focus();
+ }
+ });
+ }
+ else if (isDebugSelectionPending())
+ {
+ clearPendingDebugSelection();
+ }
+ }
+
+ if (initialized_)
+ manageCommands();
+ }
+
+ private void manageCommands()
+ {
+ boolean hasDocs = editors_.size() > 0;
+
+ commands_.closeSourceDoc().setEnabled(hasDocs);
+ commands_.closeAllSourceDocs().setEnabled(hasDocs);
+ commands_.nextTab().setEnabled(hasDocs);
+ commands_.previousTab().setEnabled(hasDocs);
+ commands_.firstTab().setEnabled(hasDocs);
+ commands_.lastTab().setEnabled(hasDocs);
+ commands_.switchToTab().setEnabled(hasDocs);
+ commands_.activateSource().setEnabled(hasDocs);
+ commands_.setWorkingDirToActiveDoc().setEnabled(hasDocs);
+
+ HashSet<AppCommand> newCommands =
+ activeEditor_ != null ? activeEditor_.getSupportedCommands()
+ : new HashSet<AppCommand>();
+
+ HashSet<AppCommand> commandsToEnable = new HashSet<AppCommand>(newCommands);
+ commandsToEnable.removeAll(activeCommands_);
+
+ HashSet<AppCommand> commandsToDisable = new HashSet<AppCommand>(activeCommands_);
+ commandsToDisable.removeAll(newCommands);
+
+ for (AppCommand command : commandsToEnable)
+ {
+ command.setEnabled(true);
+ command.setVisible(true);
+ }
+
+ for (AppCommand command : commandsToDisable)
+ {
+ command.setEnabled(false);
+ command.setVisible(false);
+ }
+
+ // commands which should always be visible even when disabled
+ commands_.saveSourceDoc().setVisible(true);
+ commands_.saveSourceDocAs().setVisible(true);
+ commands_.printSourceDoc().setVisible(true);
+ commands_.setWorkingDirToActiveDoc().setVisible(true);
+ commands_.debugBreakpoint().setVisible(true);
+
+ // manage synctex commands
+ manageSynctexCommands();
+
+ // manage vcs commands
+ manageVcsCommands();
+
+ // manage save and save all
+ manageSaveCommands();
+
+ // manage source navigation
+ manageSourceNavigationCommands();
+
+ // manage ShinyApps commands
+ manageShinyAppsCommands();
+
+ activeCommands_ = newCommands;
+
+ assert verifyNoUnsupportedCommands(newCommands)
+ : "Unsupported commands detected (please add to Source.dynamicCommands_)";
+ }
+
+ private void manageSynctexCommands()
+ {
+ // synctex commands are enabled if we have synctex for the active editor
+ boolean synctexAvailable = synctex_.isSynctexAvailable();
+ if (synctexAvailable)
+ {
+ if ((activeEditor_ != null) &&
+ (activeEditor_.getPath() != null) &&
+ activeEditor_.canCompilePdf())
+ {
+ synctexAvailable = synctex_.isSynctexAvailable();
+ }
+ else
+ {
+ synctexAvailable = false;
+ }
+ }
+
+ synctex_.enableCommands(synctexAvailable);
+ }
+
+ private void manageVcsCommands()
+ {
+ // manage availablity of vcs commands
+ boolean vcsCommandsEnabled =
+ session_.getSessionInfo().isVcsEnabled() &&
+ (activeEditor_ != null) &&
+ (activeEditor_.getPath() != null) &&
+ activeEditor_.getPath().startsWith(
+ session_.getSessionInfo().getActiveProjectDir().getPath());
+
+ commands_.vcsFileLog().setVisible(vcsCommandsEnabled);
+ commands_.vcsFileLog().setEnabled(vcsCommandsEnabled);
+ commands_.vcsFileDiff().setVisible(vcsCommandsEnabled);
+ commands_.vcsFileDiff().setEnabled(vcsCommandsEnabled);
+ commands_.vcsFileRevert().setVisible(vcsCommandsEnabled);
+ commands_.vcsFileRevert().setEnabled(vcsCommandsEnabled);
+
+ if (vcsCommandsEnabled)
+ {
+ String name = FileSystemItem.getNameFromPath(activeEditor_.getPath());
+ commands_.vcsFileDiff().setMenuLabel("_Diff \"" + name + "\"");
+ commands_.vcsFileLog().setMenuLabel("_Log of \"" + name +"\"");
+ commands_.vcsFileRevert().setMenuLabel("_Revert \"" + name + "\"...");
+ }
+
+ boolean isGithubRepo = session_.getSessionInfo().isGithubRepository();
+ if (vcsCommandsEnabled && isGithubRepo)
+ {
+ String name = FileSystemItem.getNameFromPath(activeEditor_.getPath());
+
+ commands_.vcsViewOnGitHub().setVisible(true);
+ commands_.vcsViewOnGitHub().setEnabled(true);
+ commands_.vcsViewOnGitHub().setMenuLabel(
+ "_View \"" + name + "\" on GitHub");
+
+ commands_.vcsBlameOnGitHub().setVisible(true);
+ commands_.vcsBlameOnGitHub().setEnabled(true);
+ commands_.vcsBlameOnGitHub().setMenuLabel(
+ "_Blame \"" + name + "\" on GitHub");
+ }
+ else
+ {
+ commands_.vcsViewOnGitHub().setVisible(false);
+ commands_.vcsViewOnGitHub().setEnabled(false);
+ commands_.vcsBlameOnGitHub().setVisible(false);
+ commands_.vcsBlameOnGitHub().setEnabled(false);
+ }
+ }
+
+ private void manageShinyAppsCommands()
+ {
+ boolean shinyCommandsAvailable =
+ session_.getSessionInfo().getShinyappsInstalled() &&
+ (activeEditor_ != null) &&
+ (activeEditor_.getPath() != null) &&
+ activeEditor_.getExtendedFileType() == "shiny";
+ commands_.shinyAppsDeploy().setVisible(shinyCommandsAvailable);
+ commands_.shinyAppsTerminate().setVisible(shinyCommandsAvailable);
+ }
+
+ private void manageSaveCommands()
+ {
+ boolean saveEnabled = (activeEditor_ != null) &&
+ activeEditor_.isSaveCommandActive();
+ commands_.saveSourceDoc().setEnabled(saveEnabled);
+ manageSaveAllCommand();
+ }
+
+
+ private void manageSaveAllCommand()
+ {
+ // if one document is dirty then we are enabled
+ for (EditingTarget target : editors_)
+ {
+ if (target.isSaveCommandActive())
+ {
+ commands_.saveAllSourceDocs().setEnabled(true);
+ return;
+ }
+ }
+
+ // not one was dirty, disabled
+ commands_.saveAllSourceDocs().setEnabled(false);
+ }
+
+
+ private boolean verifyNoUnsupportedCommands(HashSet<AppCommand> commands)
+ {
+ HashSet<AppCommand> temp = new HashSet<AppCommand>(commands);
+ temp.removeAll(dynamicCommands_);
+ return temp.size() == 0;
+ }
+
+ public void onFileEdit(FileEditEvent event)
+ {
+ fileTypeRegistry_.editFile(event.getFile());
+ }
+
+ public void onBeforeShow(BeforeShowEvent event)
+ {
+ if (view_.getTabCount() == 0 && newTabPending_ == 0)
+ {
+ // Avoid scenarios where the Source tab comes up but no tabs are
+ // in it. (But also avoid creating an extra source tab when there
+ // were already new tabs about to be created!)
+ onNewSourceDoc();
+ }
+ }
+
+ @Handler
+ public void onSourceNavigateBack()
+ {
+ if (!sourceNavigationHistory_.isForwardEnabled())
+ {
+ if (activeEditor_ != null)
+ activeEditor_.recordCurrentNavigationPosition();
+ }
+
+ SourceNavigation navigation = sourceNavigationHistory_.goBack();
+ if (navigation != null)
+ attemptSourceNavigation(navigation, commands_.sourceNavigateBack());
+ }
+
+ @Handler
+ public void onSourceNavigateForward()
+ {
+ SourceNavigation navigation = sourceNavigationHistory_.goForward();
+ if (navigation != null)
+ attemptSourceNavigation(navigation, commands_.sourceNavigateForward());
+ }
+
+ private void attemptSourceNavigation(final SourceNavigation navigation,
+ final AppCommand retryCommand)
+ {
+ // see if we can navigate by id
+ String docId = navigation.getDocumentId();
+ final EditingTarget target = getEditingTargetForId(docId);
+ if (target != null)
+ {
+ // check for navigation to the current position -- in this
+ // case execute the retry command
+ if ( (target == activeEditor_) &&
+ target.isAtSourceRow(navigation.getPosition()))
+ {
+ if (retryCommand.isEnabled())
+ retryCommand.execute();
+ }
+ else
+ {
+ suspendSourceNavigationAdding_ = true;
+ try
+ {
+ view_.selectTab(target.asWidget());
+ target.restorePosition(navigation.getPosition());
+ }
+ finally
+ {
+ suspendSourceNavigationAdding_ = false;
+ }
+ }
+ }
+
+ // check for code browser navigation
+ else if ((navigation.getPath() != null) &&
+ navigation.getPath().equals(CodeBrowserEditingTarget.PATH))
+ {
+ activateCodeBrowser(
+ new SourceNavigationResultCallback<CodeBrowserEditingTarget>(
+ navigation.getPosition(),
+ retryCommand));
+ }
+
+ // check for file path navigation
+ else if ((navigation.getPath() != null) &&
+ !navigation.getPath().startsWith(DataItem.URI_PREFIX))
+ {
+ FileSystemItem file = FileSystemItem.createFile(navigation.getPath());
+ TextFileType fileType = fileTypeRegistry_.getTextTypeForFile(file);
+
+ // open the file and restore the position
+ openFile(file,
+ fileType,
+ new SourceNavigationResultCallback<EditingTarget>(
+ navigation.getPosition(),
+ retryCommand));
+ }
+ else
+ {
+ // couldn't navigate to this item, retry
+ if (retryCommand.isEnabled())
+ retryCommand.execute();
+ }
+ }
+
+ private void manageSourceNavigationCommands()
+ {
+ commands_.sourceNavigateBack().setEnabled(
+ sourceNavigationHistory_.isBackEnabled());
+
+ commands_.sourceNavigateForward().setEnabled(
+ sourceNavigationHistory_.isForwardEnabled());
+ }
+
+
+ @Override
+ public void onCodeBrowserNavigation(final CodeBrowserNavigationEvent event)
+ {
+ if (event.getDebugPosition() != null)
+ {
+ setPendingDebugSelection();
+ }
+
+ activateCodeBrowser(new ResultCallback<CodeBrowserEditingTarget,ServerError>() {
+ @Override
+ public void onSuccess(CodeBrowserEditingTarget target)
+ {
+ target.showFunction(event.getFunction());
+ if (event.getDebugPosition() != null)
+ {
+ highlightDebugBrowserPosition(target, event.getDebugPosition(),
+ event.getExecuting());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onCodeBrowserFinished(final CodeBrowserFinishedEvent event)
+ {
+ int codeBrowserTabIndex = indexOfCodeBrowserTab();
+ if (codeBrowserTabIndex >= 0)
+ {
+ view_.closeTab(codeBrowserTabIndex, false);
+ return;
+ }
+ }
+
+
+ @Override
+ public void onCodeBrowserHighlight(final CodeBrowserHighlightEvent event)
+ {
+ // no need to highlight if we don't have a code browser tab to highlight
+ if (indexOfCodeBrowserTab() < 0)
+ return;
+
+ setPendingDebugSelection();
+ activateCodeBrowser(new ResultCallback<CodeBrowserEditingTarget,ServerError>() {
+ @Override
+ public void onSuccess(CodeBrowserEditingTarget target)
+ {
+ highlightDebugBrowserPosition(target, event.getDebugPosition(), true);
+ }
+ });
+ }
+
+ private void highlightDebugBrowserPosition(CodeBrowserEditingTarget target,
+ DebugFilePosition pos,
+ boolean executing)
+ {
+ target.highlightDebugLocation(SourcePosition.create(
+ pos.getLine(),
+ pos.getColumn() - 1),
+ SourcePosition.create(
+ pos.getEndLine(),
+ pos.getEndColumn() + 1),
+ executing);
+ }
+
+ // returns the index of the tab currently containing the code browser, or
+ // -1 if the code browser tab isn't currently open;
+ private int indexOfCodeBrowserTab()
+ {
+ // see if there is an existing target to use
+ for (int idx = 0; idx < editors_.size(); idx++)
+ {
+ String path = editors_.get(idx).getPath();
+ if (CodeBrowserEditingTarget.PATH.equals(path))
+ {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ private void activateCodeBrowser(
+ final ResultCallback<CodeBrowserEditingTarget,ServerError> callback)
+ {
+ int codeBrowserTabIndex = indexOfCodeBrowserTab();
+ if (codeBrowserTabIndex >= 0)
+ {
+ ensureVisible(false);
+ view_.selectTab(codeBrowserTabIndex);
+
+ // callback
+ callback.onSuccess( (CodeBrowserEditingTarget)
+ editors_.get(codeBrowserTabIndex));
+
+ // satisfied request
+ return;
+ }
+
+ // create a new one
+ newDoc(FileTypeRegistry.CODEBROWSER,
+ new ResultCallback<EditingTarget, ServerError>()
+ {
+ @Override
+ public void onSuccess(EditingTarget arg)
+ {
+ callback.onSuccess( (CodeBrowserEditingTarget)arg);
+ }
+
+ @Override
+ public void onFailure(ServerError error)
+ {
+ callback.onFailure(error);
+ }
+
+ @Override
+ public void onCancelled()
+ {
+ callback.onCancelled();
+ }
+
+ });
+ }
+
+ private boolean isDebugSelectionPending()
+ {
+ return debugSelectionTimer_ != null;
+ }
+
+ private void clearPendingDebugSelection()
+ {
+ if (debugSelectionTimer_ != null)
+ {
+ debugSelectionTimer_.cancel();
+ debugSelectionTimer_ = null;
+ }
+ }
+
+ private void setPendingDebugSelection()
+ {
+ if (!isDebugSelectionPending())
+ {
+ debugSelectionTimer_ = new Timer()
+ {
+ public void run()
+ {
+ debugSelectionTimer_ = null;
+ }
+ };
+ debugSelectionTimer_.schedule(250);
+ }
+ }
+
+ private class SourceNavigationResultCallback<T extends EditingTarget>
+ extends ResultCallback<T,ServerError>
+ {
+ public SourceNavigationResultCallback(SourcePosition restorePosition,
+ AppCommand retryCommand)
+ {
+ suspendSourceNavigationAdding_ = true;
+ restorePosition_ = restorePosition;
+ retryCommand_ = retryCommand;
+ }
+
+ @Override
+ public void onSuccess(final T target)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ try
+ {
+ target.restorePosition(restorePosition_);
+ }
+ finally
+ {
+ suspendSourceNavigationAdding_ = false;
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(ServerError info)
+ {
+ suspendSourceNavigationAdding_ = false;
+ if (retryCommand_.isEnabled())
+ retryCommand_.execute();
+ }
+
+ @Override
+ public void onCancelled()
+ {
+ suspendSourceNavigationAdding_ = false;
+ }
+
+ private final SourcePosition restorePosition_;
+ private final AppCommand retryCommand_;
+ }
+
+ @Override
+ public void onSourceExtendedTypeDetected(SourceExtendedTypeDetectedEvent e)
+ {
+ // set the extended type of the specified source file
+ for (EditingTarget editor : editors_)
+ {
+ if (editor.getId().equals(e.getDocId()))
+ {
+ editor.adaptToExtendedFileType(e.getExtendedType());
+ break;
+ }
+ }
+ }
+
+ ArrayList<EditingTarget> editors_ = new ArrayList<EditingTarget>();
+ private EditingTarget activeEditor_;
+ private final Commands commands_;
+ private final Display view_;
+ private final SourceServerOperations server_;
+ private final EditingTargetSource editingTargetSource_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final GlobalDisplay globalDisplay_;
+ private final WorkbenchContext workbenchContext_;
+ private final FileDialogs fileDialogs_;
+ private final RemoteFileSystemContext fileContext_;
+ private final EventBus events_;
+ private final Session session_;
+ private final Synctex synctex_;
+ private final Provider<FileMRUList> pMruList_;
+ private final UIPrefs uiPrefs_;
+ private final RnwWeaveRegistry rnwWeaveRegistry_;
+ private HashSet<AppCommand> activeCommands_ = new HashSet<AppCommand>();
+ private final HashSet<AppCommand> dynamicCommands_;
+ private final SourceNavigationHistory sourceNavigationHistory_ =
+ new SourceNavigationHistory(30);
+
+ private boolean suspendSourceNavigationAdding_;
+
+ private static final String MODULE_SOURCE = "source-pane";
+ private static final String KEY_ACTIVETAB = "activeTab";
+ private boolean initialized_;
+ private Timer debugSelectionTimer_ = null;
+
+ // If positive, a new tab is about to be created
+ private int newTabPending_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceBuildHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceBuildHelper.java
new file mode 100644
index 0000000..a0ddd66
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceBuildHelper.java
@@ -0,0 +1,90 @@
+/*
+ * SourceBuildHelper.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog.Result;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class SourceBuildHelper
+{
+ @Inject
+ public SourceBuildHelper(UIPrefs uiPrefs,
+ SourceShim sourceShim)
+ {
+ uiPrefs_ = uiPrefs;
+ sourceShim_ = sourceShim;
+ }
+
+ public void withSaveFilesBeforeCommand(final Command command,
+ String commandSource)
+ {
+ if (uiPrefs_.saveAllBeforeBuild().getValue())
+ {
+ sourceShim_.saveAllUnsaved(command);
+ }
+ else
+ {
+ String alwaysSaveOption = !uiPrefs_.saveAllBeforeBuild().getValue() ?
+ "Always save files before build" : null;
+
+ ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
+ sourceShim_.getUnsavedChanges();
+
+ if (unsavedSourceDocs.size() > 0)
+ {
+ new UnsavedChangesDialog(
+ commandSource,
+ alwaysSaveOption,
+ unsavedSourceDocs,
+ new OperationWithInput<UnsavedChangesDialog.Result>() {
+ @Override
+ public void execute(Result result)
+ {
+ if (result.getAlwaysSave())
+ {
+ uiPrefs_.saveAllBeforeBuild().setGlobalValue(true);
+ uiPrefs_.writeUIPrefs();
+ }
+
+ sourceShim_.handleUnsavedChangesBeforeExit(
+ result.getSaveTargets(),
+ command);
+
+
+ }
+ },
+ null
+ ).showModal();
+ }
+ else
+ {
+ command.execute();
+ }
+ }
+ }
+
+ private final SourceShim sourceShim_;
+ private final UIPrefs uiPrefs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourcePane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourcePane.java
new file mode 100644
index 0000000..48bffe8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourcePane.java
@@ -0,0 +1,304 @@
+/*
+ * SourcePane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import org.rstudio.core.client.events.*;
+import org.rstudio.core.client.layout.RequiresVisibilityChanged;
+import org.rstudio.core.client.theme.DocTabLayoutPanel;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.BeforeShowCallback;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.common.AutoGlassAttacher;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
+import org.rstudio.studio.client.workbench.views.source.Source.Display;
+
+import java.util.ArrayList;
+
+public class SourcePane extends Composite implements Display,
+ HasEnsureVisibleHandlers,
+ HasEnsureHeightHandlers,
+ RequiresResize,
+ ProvidesResize,
+ BeforeShowCallback,
+ RequiresVisibilityChanged
+{
+ @Inject
+ public SourcePane()
+ {
+ final int UTILITY_AREA_SIZE = 74;
+
+ panel_ = new LayoutPanel();
+
+ new AutoGlassAttacher(panel_);
+
+ tabPanel_ = new DocTabLayoutPanel(true, 65, UTILITY_AREA_SIZE);
+ panel_.add(tabPanel_);
+ panel_.setWidgetTopBottom(tabPanel_, 0, Unit.PX, 0, Unit.PX);
+ panel_.setWidgetLeftRight(tabPanel_, 0, Unit.PX, 0, Unit.PX);
+
+ utilPanel_ = new HTML();
+ utilPanel_.setStylePrimaryName(ThemeStyles.INSTANCE.multiPodUtilityArea());
+ panel_.add(utilPanel_);
+ panel_.setWidgetRightWidth(utilPanel_,
+ 0, Unit.PX,
+ UTILITY_AREA_SIZE, Unit.PX);
+ panel_.setWidgetTopHeight(utilPanel_, 0, Unit.PX, 22, Unit.PX);
+
+ tabOverflowPopup_ = new TabOverflowPopupPanel();
+ tabOverflowPopup_.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ manageChevronVisibility();
+ }
+ });
+ chevron_ = new Image(ThemeResources.INSTANCE.chevron());
+ chevron_.getElement().getStyle().setCursor(Cursor.POINTER);
+ chevron_.addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ tabOverflowPopup_.showRelativeTo(chevron_);
+ }
+ });
+
+ panel_.add(chevron_);
+ panel_.setWidgetTopHeight(chevron_,
+ 8, Unit.PX,
+ chevron_.getHeight(), Unit.PX);
+ panel_.setWidgetRightWidth(chevron_,
+ 52, Unit.PX,
+ chevron_.getWidth(), Unit.PX);
+
+ initWidget(panel_);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ onResize();
+ }
+ });
+ }
+
+ public void addTab(Widget widget,
+ ImageResource icon,
+ String name,
+ String tooltip,
+ boolean switchToTab)
+ {
+ tabPanel_.add(widget, icon, name, tooltip);
+ if (switchToTab)
+ tabPanel_.selectTab(widget);
+ }
+
+ public void closeTab(Widget child, boolean interactive)
+ {
+ closeTab(child, interactive, null);
+ }
+
+ public void closeTab(Widget child, boolean interactive, Command onClosed)
+ {
+ closeTab(tabPanel_.getWidgetIndex(child), interactive, onClosed);
+ }
+
+ public void closeTab(int index, boolean interactive)
+ {
+ closeTab(index, interactive, null);
+ }
+
+ public void closeTab(int index, boolean interactive, Command onClosed)
+ {
+ if (interactive)
+ tabPanel_.tryCloseTab(index, onClosed);
+ else
+ tabPanel_.closeTab(index, onClosed);
+ }
+
+ public void setDirty(Widget widget, boolean dirty)
+ {
+ Widget tab = tabPanel_.getTabWidget(widget);
+ if (dirty)
+ tab.addStyleName(ThemeStyles.INSTANCE.dirtyTab());
+ else
+ tab.removeStyleName(ThemeStyles.INSTANCE.dirtyTab());
+ }
+
+ public void ensureVisible()
+ {
+ fireEvent(new EnsureVisibleEvent());
+ }
+
+ public void renameTab(Widget child,
+ ImageResource icon,
+ String value,
+ String tooltip)
+ {
+ tabPanel_.replaceDocName(tabPanel_.getWidgetIndex(child),
+ icon,
+ value,
+ tooltip);
+ }
+
+ public int getActiveTabIndex()
+ {
+ return tabPanel_.getSelectedIndex();
+ }
+
+ public void selectTab(int tabIndex)
+ {
+ tabPanel_.selectTab(tabIndex);
+ }
+
+ public void selectTab(Widget child)
+ {
+ tabPanel_.selectTab(child);
+ }
+
+ public int getTabCount()
+ {
+ return tabPanel_.getWidgetCount();
+ }
+
+ public HandlerRegistration addTabClosingHandler(TabClosingHandler handler)
+ {
+ return tabPanel_.addTabClosingHandler(handler);
+ }
+
+ public HandlerRegistration addTabCloseHandler(
+ TabCloseHandler handler)
+ {
+ return tabPanel_.addTabCloseHandler(handler);
+ }
+
+ public HandlerRegistration addTabClosedHandler(TabClosedHandler handler)
+ {
+ return tabPanel_.addTabClosedHandler(handler);
+ }
+
+ public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler)
+ {
+ return tabPanel_.addSelectionHandler(handler);
+ }
+
+ public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<Integer> handler)
+ {
+ return tabPanel_.addBeforeSelectionHandler(handler);
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addEnsureHeightHandler(
+ EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ public void onResize()
+ {
+ panel_.onResize();
+ manageChevronVisibility();
+ }
+
+ public void manageChevronVisibility()
+ {
+ int tabsWidth = tabPanel_.getTabsEffectiveWidth();
+ setOverflowVisible(tabsWidth > getOffsetWidth() - 50);
+ }
+
+ public void showOverflowPopup()
+ {
+ setOverflowVisible(true);
+ tabOverflowPopup_.showRelativeTo(chevron_);
+ }
+
+ @Override
+ public void showUnsavedChangesDialog(
+ String title,
+ ArrayList<UnsavedChangesTarget> dirtyTargets,
+ OperationWithInput<UnsavedChangesDialog.Result> saveOperation,
+ Command onCancelled)
+ {
+ new UnsavedChangesDialog(title,
+ dirtyTargets,
+ saveOperation,
+ onCancelled).showModal();
+ }
+
+ private void setOverflowVisible(boolean visible)
+ {
+ utilPanel_.setVisible(visible);
+ chevron_.setVisible(visible);
+ }
+
+ public void onBeforeShow()
+ {
+ fireEvent(new BeforeShowEvent());
+ }
+
+ public HandlerRegistration addBeforeShowHandler(BeforeShowHandler handler)
+ {
+ return addHandler(handler, BeforeShowEvent.TYPE);
+ }
+
+ public void onVisibilityChanged(boolean visible)
+ {
+ if (getActiveTabIndex() >= 0)
+ {
+ Widget w = tabPanel_.getTabWidget(getActiveTabIndex());
+ if (w instanceof RequiresVisibilityChanged)
+ ((RequiresVisibilityChanged)w).onVisibilityChanged(visible);
+ }
+ }
+
+
+ private DocTabLayoutPanel tabPanel_;
+ private HTML utilPanel_;
+ private Image chevron_;
+ private LayoutPanel panel_;
+ private PopupPanel tabOverflowPopup_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceShim.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceShim.java
new file mode 100644
index 0000000..821b94e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/SourceShim.java
@@ -0,0 +1,306 @@
+/*
+ * SourceShim.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import java.util.ArrayList;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.*;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.rstudio.core.client.AsyncShim;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.EnsureHeightEvent;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.events.HasEnsureHeightHandlers;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+import org.rstudio.core.client.layout.RequiresVisibilityChanged;
+import org.rstudio.core.client.widget.BeforeShowCallback;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.events.OpenPresentationSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenPresentationSourceFileHandler;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
+import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileHandler;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.events.*;
+
+ at Singleton
+public class SourceShim extends Composite
+ implements IsWidget, HasEnsureVisibleHandlers, HasEnsureHeightHandlers, BeforeShowCallback,
+ ProvidesResize, RequiresResize, RequiresVisibilityChanged
+{
+ public interface Binder extends CommandBinder<Commands, AsyncSource> {}
+
+ public abstract static class AsyncSource extends AsyncShim<Source>
+ implements OpenSourceFileHandler,
+ OpenPresentationSourceFileHandler,
+ EditPresentationSourceEvent.Handler,
+ InsertSourceHandler,
+ FileEditHandler
+ {
+ public abstract void onOpenSourceFile(OpenSourceFileEvent event);
+ public abstract void onOpenPresentationSourceFile(OpenPresentationSourceFileEvent event);
+ public abstract void onEditPresentationSource(EditPresentationSourceEvent event);
+
+ @Handler
+ public abstract void onNewSourceDoc();
+ @Handler
+ public abstract void onNewTextDoc();
+ @Handler
+ public abstract void onNewCppDoc();
+ @Handler
+ public abstract void onNewSweaveDoc();
+ @Handler
+ public abstract void onNewRMarkdownDoc();
+ @Handler
+ public abstract void onNewRHTMLDoc();
+ @Handler
+ public abstract void onNewRDocumentationDoc();
+ @Handler
+ public abstract void onNewRPresentationDoc();
+ @Handler
+ public abstract void onOpenSourceDoc();
+ @Handler
+ public abstract void onCloseSourceDoc();
+ @Handler
+ public abstract void onSaveAllSourceDocs();
+ @Handler
+ public abstract void onCloseAllSourceDocs();
+ @Handler
+ public abstract void onFindInFiles();
+ @Handler
+ public abstract void onActivateSource();
+ @Handler
+ public abstract void onPreviousTab();
+ @Handler
+ public abstract void onNextTab();
+ @Handler
+ public abstract void onFirstTab();
+ @Handler
+ public abstract void onLastTab();
+ @Handler
+ public abstract void onSwitchToTab();
+ @Handler
+ public abstract void onSourceNavigateBack();
+ @Handler
+ public abstract void onSourceNavigateForward();
+ @Handler
+ public abstract void onShowProfiler();
+
+ @Override
+ protected void preInstantiationHook(Command continuation)
+ {
+ AceEditor.load(continuation);
+ }
+
+ @Override
+ protected void onDelayLoadSuccess(final Source obj)
+ {
+ final Widget child = obj.asWidget();
+ if (child instanceof HasEnsureVisibleHandlers)
+ {
+ ((HasEnsureVisibleHandlers)child).addEnsureVisibleHandler(
+ new EnsureVisibleHandler()
+ {
+ public void onEnsureVisible(EnsureVisibleEvent event)
+ {
+ parent_.fireEvent(new EnsureVisibleEvent(event.getActivate()));
+ }
+ });
+ }
+ if (child instanceof HasEnsureHeightHandlers)
+ {
+ ((HasEnsureHeightHandlers)child).addEnsureHeightHandler(
+ new EnsureHeightHandler() {
+
+ @Override
+ public void onEnsureHeight(EnsureHeightEvent event)
+ {
+ parent_.fireEvent(event);
+ }
+ });
+ }
+ child.setSize("100%", "100%");
+ parent_.panel_.add(child);
+ parent_.panel_.setWidgetTopBottom(child, 0, Unit.PX, 0, Unit.PX);
+ parent_.panel_.setWidgetLeftRight(child, 0, Unit.PX, 0, Unit.PX);
+
+ parent_.setSource(obj);
+ }
+
+ public void setParent(SourceShim parent)
+ {
+ parent_ = parent;
+ }
+
+ private SourceShim parent_;
+ }
+
+ @Inject
+ public SourceShim(AsyncSource asyncSource,
+ final Commands commands,
+ EventBus events,
+ Binder binder)
+ {
+ panel_ = new LayoutPanel();
+ panel_.setSize("100%", "100%");
+ initWidget(panel_);
+
+ binder.bind(commands, asyncSource);
+ asyncSource.setParent(this);
+ events.addHandler(OpenSourceFileEvent.TYPE, asyncSource);
+ events.addHandler(OpenPresentationSourceFileEvent.TYPE, asyncSource);
+ events.addHandler(EditPresentationSourceEvent.TYPE, asyncSource);
+ events.addHandler(InsertSourceEvent.TYPE, asyncSource);
+ asyncSource_ = asyncSource;
+
+ events.fireEvent(new DocTabsChangedEvent(new String[0],
+ new ImageResource[0],
+ new String[0],
+ new String[0]));
+
+ events.addHandler(FileEditEvent.TYPE, asyncSource);
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ public void forceLoad()
+ {
+ asyncSource_.forceLoad(false);
+ AceEditor.preload();
+ }
+
+ public void onBeforeShow()
+ {
+ for (Widget w : panel_)
+ if (w instanceof BeforeShowCallback)
+ ((BeforeShowCallback)w).onBeforeShow();
+ }
+
+ public void onResize()
+ {
+ panel_.onResize();
+ }
+
+ public void onVisibilityChanged(boolean visible)
+ {
+ for (Widget w : panel_)
+ if (w instanceof RequiresVisibilityChanged)
+ ((RequiresVisibilityChanged)w).onVisibilityChanged(visible);
+ }
+
+ public void saveAllUnsaved(Command onCompleted)
+ {
+ if (source_ != null)
+ {
+ source_.saveAllUnsaved(onCompleted);
+ }
+ }
+
+ public void closeAllSourceDocs(String caption, Command onCompleted)
+ {
+ if (source_ != null)
+ {
+ source_.closeAllSourceDocs(caption, onCompleted);
+ }
+ else
+ {
+ onCompleted.execute();
+ }
+ }
+
+ public ArrayList<UnsavedChangesTarget> getUnsavedChanges()
+ {
+ if (source_ != null)
+ return source_.getUnsavedChanges();
+ else
+ return new ArrayList<UnsavedChangesTarget>();
+ }
+
+ public void saveWithPrompt(UnsavedChangesTarget target,
+ Command onCompleted,
+ Command onCancelled)
+ {
+ if (source_ != null)
+ {
+ source_.saveWithPrompt(target, onCompleted, onCancelled);
+ }
+ else
+ {
+ onCompleted.execute();
+ }
+ }
+
+ public Command revertUnsavedChangesBeforeExitCommand(
+ final Command onCompleted)
+ {
+ return new Command()
+ {
+ @Override
+ public void execute()
+ {
+ handleUnsavedChangesBeforeExit(
+ new ArrayList<UnsavedChangesTarget>(),
+ onCompleted);
+ }
+
+ };
+ }
+
+ public void handleUnsavedChangesBeforeExit(
+ ArrayList<UnsavedChangesTarget> saveTargets,
+ Command onCompleted)
+ {
+ if (source_ != null)
+ {
+ source_.handleUnsavedChangesBeforeExit(saveTargets, onCompleted);
+ }
+ else
+ {
+ onCompleted.execute();
+ }
+ }
+
+ void setSource(Source source)
+ {
+ source_ = source;
+ }
+
+ private final LayoutPanel panel_;
+ private AsyncSource asyncSource_;
+ private Source source_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/TabOverflowPopupPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/TabOverflowPopupPanel.java
new file mode 100644
index 0000000..7c1c14e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/TabOverflowPopupPanel.java
@@ -0,0 +1,162 @@
+/*
+ * TabOverflowPopupPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import org.rstudio.core.client.command.BaseMenuBar;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.SearchWidget;
+import org.rstudio.core.client.widget.ThemedPopupPanel;
+
+import java.util.ArrayList;
+
+public class TabOverflowPopupPanel extends ThemedPopupPanel
+ implements ValueChangeHandler<String>
+{
+ private class DocsOracle extends SuggestOracle
+ {
+ @Override
+ public void requestSuggestions(Request request, Callback callback)
+ {
+
+ }
+ }
+
+ private class MenuKeyHandler implements KeyDownHandler
+ {
+ public MenuKeyHandler(BaseMenuBar menu)
+ {
+ menu_ = menu;
+ }
+
+ public void onKeyDown(KeyDownEvent event)
+ {
+ switch (event.getNativeKeyCode())
+ {
+ case KeyCodes.KEY_DOWN:
+ case KeyCodes.KEY_UP:
+ event.preventDefault();
+ event.stopPropagation();
+
+ ArrayList<MenuItem> items = menu_.getVisibleItems();
+
+ if (items.size() == 0)
+ return;
+
+ boolean up = event.getNativeKeyCode() == KeyCodes.KEY_UP;
+
+ int index = up ? items.size() + 1 : -1;
+
+ MenuItem selectedItem = menu_.getSelectedItem();
+ if (selectedItem != null && items.contains(selectedItem))
+ index = items.indexOf(selectedItem);
+
+ index = (index + (up ? -1 : 1) + items.size()) % items.size();
+
+ menu_.selectItem(items.get(index));
+ break;
+ case KeyCodes.KEY_ENTER:
+ event.preventDefault();
+ event.stopPropagation();
+
+ MenuItem selected = menu_.getSelectedItem();
+ if (selected != null && selected.isVisible())
+ selected.getScheduledCommand().execute();
+ else
+ {
+ ArrayList<MenuItem> visibleItems = menu_.getVisibleItems();
+ if (visibleItems.size() == 1)
+ visibleItems.get(0).getScheduledCommand().execute();
+ }
+ break;
+ case KeyCodes.KEY_ESCAPE:
+ event.preventDefault();
+ event.stopPropagation();
+
+ hide(false);
+ }
+ }
+
+ private final BaseMenuBar menu_;
+ }
+
+ public TabOverflowPopupPanel()
+ {
+ super(true, false);
+
+ DockPanel dockPanel = new DockPanel();
+
+ search_ = new SearchWidget(new DocsOracle());
+ search_.addValueChangeHandler(this);
+
+ search_.getElement().getStyle().setMarginRight(0, Unit.PX);
+ dockPanel.add(search_, DockPanel.NORTH);
+
+ menu_ = new DocsMenu();
+ menu_.setOwnerPopupPanel(this);
+ menu_.setWidth("100%");
+ dockPanel.add(menu_, DockPanel.CENTER);
+ setWidget(dockPanel);
+
+ setStylePrimaryName(ThemeStyles.INSTANCE.tabOverflowPopup());
+
+ addDomHandler(new MenuKeyHandler(menu_), KeyDownEvent.getType());
+
+ addHandler(new CloseHandler<PopupPanel>() {
+
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ search_.setText("", true);
+ menu_.filter(null);
+ menu_.selectItem(null);
+ }
+ }, CloseEvent.getType());
+ }
+
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ String value = event.getValue();
+ menu_.filter(value);
+ }
+
+ @Override
+ public void show()
+ {
+ super.show();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ search_.focus();
+ }
+ });
+ }
+
+ private final DocsMenu menu_;
+ private final SearchWidget search_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTarget.java
new file mode 100644
index 0000000..b095c87
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTarget.java
@@ -0,0 +1,131 @@
+/*
+ * EditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors;
+
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.events.HasEnsureHeightHandlers;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.studio.client.common.ReadOnlyValue;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import java.util.HashSet;
+
+public interface EditingTarget extends IsWidget,
+ HasEnsureVisibleHandlers,
+ HasEnsureHeightHandlers,
+ HasCloseHandlers<Void>,
+ UnsavedChangesTarget
+{
+ String getId();
+
+ /**
+ * Used as the tab name
+ */
+ HasValue<String> getName();
+ String getTitle();
+ String getPath();
+ String getContext();
+ ImageResource getIcon();
+ String getTabTooltip();
+
+ void adaptToExtendedFileType(String extendedType);
+ String getExtendedFileType();
+
+ HashSet<AppCommand> getSupportedCommands();
+ boolean canCompilePdf();
+
+ void verifyCppPrerequisites();
+
+ void focus();
+ void onActivate();
+ void onDeactivate();
+
+ void onInitiallyLoaded();
+
+ void recordCurrentNavigationPosition();
+ void navigateToPosition(SourcePosition position,
+ boolean recordCurrent);
+ void navigateToPosition(SourcePosition position,
+ boolean recordCurrent,
+ boolean highlightLine);
+ void restorePosition(SourcePosition position);
+ boolean isAtSourceRow(SourcePosition position);
+
+ void forceLineHighlighting();
+
+ void setCursorPosition(Position position);
+ void ensureCursorVisible();
+
+ Position search(String regex);
+ Position search(Position startPos, String regex);
+
+ void highlightDebugLocation(
+ SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing);
+ void endDebugHighlighting();
+
+ /**
+ * @return True if dismissal is allowed, false to cancel.
+ */
+ boolean onBeforeDismiss();
+ void onDismiss();
+
+ ReadOnlyValue<Boolean> dirtyState();
+
+ boolean isSaveCommandActive();
+
+ /**
+ * Save the document, prompting only if the file is dirty and untitled
+ */
+ void save(Command onCompleted);
+
+ /**
+ * Save the document, always prompting if the file is dirty
+ */
+ void saveWithPrompt(Command onCompleted, Command onCancelled);
+
+ /**
+ * Revert any changes
+ */
+ void revertChanges(Command onCompleted);
+
+ void initialize(SourceDocument document,
+ FileSystemContext fileContext,
+ FileType type,
+ Provider<String> defaultNameProvider);
+
+ /**
+ * Any bigger than this, and the file should NOT be allowed to open
+ */
+ long getFileSizeLimit();
+
+ /**
+ * Any bigger than this, and the user should be warned before opening
+ */
+ long getLargeFileSize();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetCodeExecution.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetCodeExecution.java
new file mode 100644
index 0000000..f90d490
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetCodeExecution.java
@@ -0,0 +1,192 @@
+/*
+ * EditingTargetCodeExecution.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.editors;
+
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.events.ConsoleExecutePendingInputEvent;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay.AnchoredSelection;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+
+import com.google.inject.Inject;
+
+public class EditingTargetCodeExecution
+{
+ public interface CodeExtractor
+ {
+ String extractCode(DocDisplay docDisplay, Range range);
+ }
+
+ public EditingTargetCodeExecution(DocDisplay docDisplay)
+ {
+ this(docDisplay, new CodeExtractor() {
+ @Override
+ public String extractCode(DocDisplay docDisplay, Range range)
+ {
+ return docDisplay.getCode(range.getStart(), range.getEnd());
+ }
+ });
+ }
+
+ public EditingTargetCodeExecution(DocDisplay docDisplay,
+ CodeExtractor codeExtractor)
+ {
+ docDisplay_ = docDisplay;
+ codeExtractor_ = codeExtractor;
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ void initialize(EventBus events, UIPrefs prefs)
+ {
+ events_ = events;
+ prefs_ = prefs;
+ }
+
+ public void executeSelection(boolean consoleExecuteWhenNotFocused)
+ {
+ // allow console a chance to execute code if we aren't focused
+ if (consoleExecuteWhenNotFocused && !docDisplay_.isFocused())
+ {
+ events_.fireEvent(new ConsoleExecutePendingInputEvent());
+ return;
+ }
+
+
+ Range selectionRange = docDisplay_.getSelectionRange();
+ boolean noSelection = selectionRange.isEmpty();
+ if (noSelection)
+ {
+ int row = docDisplay_.getSelectionStart().getRow();
+ selectionRange = Range.fromPoints(
+ Position.create(row, 0),
+ Position.create(row, docDisplay_.getLength(row)));
+ }
+
+ executeRange(selectionRange);
+
+ // advance if there is no current selection
+ if (noSelection)
+ {
+ if (!docDisplay_.moveSelectionToNextLine(true))
+ docDisplay_.moveSelectionToBlankLine();
+ }
+ }
+
+ public void executeRange(Range range)
+ {
+ String code = codeExtractor_.extractCode(docDisplay_, range);
+
+ setLastExecuted(range.getStart(), range.getEnd());
+
+ // trim intelligently
+ code = code.trim();
+ if (code.length() == 0)
+ code = "\n";
+
+ // strip roxygen off the beginning of lines
+ if (isRoxygenExampleRange(range))
+ {
+ code = code.replaceFirst("^[ \\t]*#'[ \\t]?", "");
+ code = code.replaceAll("\n[ \\t]*#'[ \\t]?", "\n");
+ }
+
+ // send to console
+ events_.fireEvent(new SendToConsoleEvent(
+ code,
+ true,
+ prefs_.focusConsoleAfterExec().getValue()));
+ }
+
+ public void executeLastCode()
+ {
+ if (lastExecutedCode_ != null)
+ {
+ String code = lastExecutedCode_.getValue();
+ if (code != null && code.trim().length() > 0)
+ {
+ events_.fireEvent(new SendToConsoleEvent(code, true));
+ }
+ }
+ }
+
+ public void setLastExecuted(Position start, Position end)
+ {
+ detachLastExecuted();
+ lastExecutedCode_ = docDisplay_.createAnchoredSelection(start, end);
+ }
+
+ public void detachLastExecuted()
+ {
+ if (lastExecutedCode_ != null)
+ {
+ lastExecutedCode_.detach();
+ lastExecutedCode_ = null;
+ }
+ }
+
+ private boolean isRoxygenExampleRange(Range range)
+ {
+ // ensure all of the lines in the selection are within roxygen
+ int selStartRow = range.getStart().getRow();
+ int selEndRow = range.getEnd().getRow();
+
+ // ignore the last row if it's column 0
+ if (range.getEnd().getColumn() == 0)
+ selEndRow = Math.max(selEndRow-1, selStartRow);
+
+ for (int i=selStartRow; i<=selEndRow; i++)
+ {
+ if (!isRoxygenLine(docDisplay_.getLine(i)))
+ return false;
+ }
+
+ // scan backwards and look for @example
+ int row = selStartRow;
+ while (--row >= 0)
+ {
+ String line = docDisplay_.getLine(row);
+
+ // must still be within roxygen
+ if (!isRoxygenLine(line))
+ return false;
+
+ // if we are in an example block return true
+ if (line.matches("^\\s*#'\\s*@example.*$"))
+ return true;
+ }
+
+ // didn't find the example block
+ return false;
+ }
+
+ private boolean isRoxygenLine(String line)
+ {
+ String trimmedLine = line.trim();
+ return (trimmedLine.length() == 0) || trimmedLine.startsWith("#'");
+ }
+
+ private EventBus events_;
+ private UIPrefs prefs_;
+ private final DocDisplay docDisplay_;
+ private final CodeExtractor codeExtractor_;
+ private AnchoredSelection lastExecutedCode_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetSource.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetSource.java
new file mode 100644
index 0000000..813666b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetSource.java
@@ -0,0 +1,96 @@
+/*
+ * EditingTargetSource.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.Debug;
+import org.rstudio.studio.client.common.filetypes.*;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.views.source.editors.codebrowser.CodeBrowserEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.data.DataEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.urlcontent.UrlContentEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+
+public interface EditingTargetSource
+{
+ EditingTarget getEditingTarget(FileType fileType);
+ EditingTarget getEditingTarget(SourceDocument document,
+ RemoteFileSystemContext fileContext,
+ Provider<String> defaultNameProvider);
+
+ public static class Impl implements EditingTargetSource
+ {
+ @Inject
+ public Impl(FileTypeRegistry registry,
+ Provider<TextEditingTarget> pTextEditingTarget,
+ Provider<DataEditingTarget> pDataEditingTarget,
+ Provider<UrlContentEditingTarget> pUrlContentEditingTarget,
+ Provider<CodeBrowserEditingTarget> pCodeBrowserEditingTarget,
+ Provider<ProfilerEditingTarget> pProfilerEditingTarget)
+ {
+ registry_ = registry;
+ pTextEditingTarget_ = pTextEditingTarget;
+ pDataEditingTarget_ = pDataEditingTarget;
+ pUrlContentEditingTarget_ = pUrlContentEditingTarget;
+ pCodeBrowserEditingTarget_ = pCodeBrowserEditingTarget;
+ pProfilerEditingTarget_ = pProfilerEditingTarget;
+ }
+
+ public EditingTarget getEditingTarget(FileType type)
+ {
+ if (type instanceof TextFileType)
+ return pTextEditingTarget_.get();
+ else if (type instanceof DataFrameType)
+ return pDataEditingTarget_.get();
+ else if (type instanceof UrlContentType)
+ return pUrlContentEditingTarget_.get();
+ else if (type instanceof CodeBrowserType)
+ return pCodeBrowserEditingTarget_.get();
+ else if (type instanceof ProfilerType)
+ return pProfilerEditingTarget_.get();
+ else
+ return null;
+ }
+
+ public EditingTarget getEditingTarget(final SourceDocument document,
+ final RemoteFileSystemContext fileContext,
+ final Provider<String> defaultNameProvider)
+ {
+ FileType type = registry_.getTypeByTypeName(document.getType());
+ if (type == null)
+ {
+ Debug.log("Unknown document type: " + document.getType());
+ type = FileTypeRegistry.TEXT;
+ }
+ final FileType finalType = type;
+ EditingTarget target = getEditingTarget(type);
+ target.initialize(document,
+ fileContext,
+ finalType,
+ defaultNameProvider);
+ return target;
+ }
+
+ private final FileTypeRegistry registry_;
+ private final Provider<TextEditingTarget> pTextEditingTarget_;
+ private final Provider<DataEditingTarget> pDataEditingTarget_;
+ private final Provider<UrlContentEditingTarget> pUrlContentEditingTarget_;
+ private final Provider<CodeBrowserEditingTarget> pCodeBrowserEditingTarget_;
+ private final Provider<ProfilerEditingTarget> pProfilerEditingTarget_;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetToolbar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetToolbar.java
new file mode 100644
index 0000000..31e64f1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/EditingTargetToolbar.java
@@ -0,0 +1,34 @@
+/*
+ * EditingTargetToolbar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors;
+
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.commands.Commands;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.Widget;
+
+public class EditingTargetToolbar extends Toolbar
+{
+ public EditingTargetToolbar(Commands commands)
+ {
+ addLeftWidget(commands.sourceNavigateBack().createToolbarButton());
+ Widget forwardButton = commands.sourceNavigateForward().createToolbarButton();
+ forwardButton.getElement().getStyle().setMarginLeft(-6, Unit.PX);
+ addLeftWidget(forwardButton);
+ addLeftSeparator();
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserContextWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserContextWidget.java
new file mode 100644
index 0000000..19a87ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserContextWidget.java
@@ -0,0 +1,157 @@
+/*
+ * CodeBrowserContextWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.codebrowser;
+
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.ScrollableToolbarPopupMenu;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class CodeBrowserContextWidget extends Composite
+ implements HasSelectionHandlers<String>
+{
+ public CodeBrowserContextWidget(
+ final CodeBrowserEditingTargetWidget.Styles styles)
+ {
+ HorizontalPanel panel = new HorizontalPanel();
+
+ captionLabel_ = new Label();
+ captionLabel_.addStyleName(styles.captionLabel());
+ panel.add(captionLabel_);
+
+
+ ClickHandler clickHandler = new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ if (dropDownImage_.isVisible())
+ {
+ CodeBrowserPopupMenu menu = new CodeBrowserPopupMenu();
+
+ JsArrayString methods = functionDef_.getMethods();
+ for (int i=0; i < methods.length(); i++)
+ {
+ final String method = methods.get(i);
+ MenuItem mi = new MenuItem(method, new Command() {
+ @Override
+ public void execute()
+ {
+ SelectionEvent.fire(CodeBrowserContextWidget.this,
+ method) ;
+ }
+ });
+ mi.getElement().getStyle().setPaddingRight(20, Unit.PX);
+ menu.addItem(mi);
+ }
+
+ menu.showRelativeTo(nameLabel_);
+ menu.getElement().getStyle().setPaddingTop(3, Unit.PX);
+ }
+ }
+ };
+
+
+ nameLabel_ = new Label();
+ nameLabel_.addStyleName(styles.menuElement());
+ nameLabel_.addStyleName(styles.functionName());
+ nameLabel_.addClickHandler(clickHandler);
+ panel.add(nameLabel_);
+
+ namespaceLabel_ = new Label();
+ namespaceLabel_.addStyleName(styles.menuElement());
+ namespaceLabel_.addStyleName(styles.functionNamespace());
+ namespaceLabel_.addClickHandler(clickHandler);
+ panel.add(namespaceLabel_);
+
+ dropDownImage_ = new Image(ThemeResources.INSTANCE.mediumDropDownArrow());
+ dropDownImage_.addStyleName(styles.menuElement());
+ dropDownImage_.addStyleName(styles.dropDownImage());
+ dropDownImage_.addClickHandler(clickHandler);
+ panel.add(dropDownImage_);
+ dropDownImage_.setVisible(false);
+
+ initWidget(panel);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionHandler(
+ SelectionHandler<String> handler)
+ {
+ return handlers_.addHandler(SelectionEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event) ;
+ }
+
+ public void setCurrentFunction(SearchPathFunctionDefinition functionDef)
+ {
+ functionDef_ = functionDef;
+
+ nameLabel_.setText(functionDef.getName());
+ namespaceLabel_.setText("(" + functionDef.getNamespace() + ")");
+
+ if (functionDef.getMethods().length() > 0)
+ {
+ captionLabel_.setText("Method:");
+ dropDownImage_.setVisible(true);
+ }
+ else
+ {
+ captionLabel_.setText("Function:");
+ dropDownImage_.setVisible(false);
+ }
+
+
+ }
+
+ private class CodeBrowserPopupMenu extends ScrollableToolbarPopupMenu
+ {
+ @Override
+ protected int getMaxHeight()
+ {
+ return Window.getClientHeight() - captionLabel_.getAbsoluteTop() -
+ captionLabel_.getOffsetHeight() - 50;
+ }
+
+ }
+
+ private SearchPathFunctionDefinition functionDef_;
+ private Label captionLabel_;
+ private Label nameLabel_;
+ private Label namespaceLabel_;
+ private Image dropDownImage_;
+
+ private final HandlerManager handlers_ = new HandlerManager(null);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTarget.java
new file mode 100644
index 0000000..0a6b83b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTarget.java
@@ -0,0 +1,713 @@
+/*
+ * CodeBrowserEditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.codebrowser;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.ReadOnlyValue;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.Value;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetCodeExecution;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.WarningBarDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FindRequestedEvent;
+import org.rstudio.studio.client.workbench.views.source.model.CodeBrowserContents;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class CodeBrowserEditingTarget implements EditingTarget
+{
+ public static final String PATH = "code_browser://";
+
+ public interface Display extends TextDisplay,
+ WarningBarDisplay
+ {
+ void showFunction(SearchPathFunctionDefinition functionDef);
+ void showFind(boolean defaultForward);
+ void findNext();
+ void findPrevious();
+ void findFromSelection();
+ void scrollToLeft();
+ }
+
+ interface MyBinder extends CommandBinder<Commands, CodeBrowserEditingTarget>
+ {}
+
+ @Inject
+ public CodeBrowserEditingTarget(SourceServerOperations server,
+ Commands commands,
+ EventBus events,
+ UIPrefs prefs,
+ FontSizeManager fontSizeManager,
+ GlobalDisplay globalDisplay,
+ DocDisplay docDisplay)
+ {
+ server_ = server;
+ commands_ = commands;
+ events_ = events;
+ prefs_ = prefs;
+ fontSizeManager_ = fontSizeManager;
+ globalDisplay_ = globalDisplay;
+ docDisplay_ = docDisplay;
+ codeExecution_ = new EditingTargetCodeExecution(docDisplay);
+
+ TextEditingTarget.addRecordNavigationPositionHandler(releaseOnDismiss_,
+ docDisplay_,
+ events_,
+ this);
+
+ docDisplay_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ NativeEvent ne = event.getNativeEvent();
+ int mod = KeyboardShortcut.getModifierValue(ne);
+ if ((mod == KeyboardShortcut.META ||
+ (mod == KeyboardShortcut.CTRL && !BrowseCap.hasMetaKey()))
+ && ne.getKeyCode() == 'F')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ commands_.findReplace().execute();
+ }
+ else if (BrowseCap.hasMetaKey() &&
+ (mod == KeyboardShortcut.META) &&
+ (ne.getKeyCode() == 'E'))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ commands_.findFromSelection().execute();
+ }
+ }
+ });
+
+ docDisplay_.addFindRequestedHandler(new FindRequestedEvent.Handler() {
+
+ @Override
+ public void onFindRequested(FindRequestedEvent event)
+ {
+ view_.showFind(event.getDefaultForward());
+ }
+ });
+ }
+
+ @Override
+ public void initialize(SourceDocument document,
+ FileSystemContext fileContext,
+ FileType type,
+ Provider<String> defaultNameProvider)
+ {
+ doc_ = document;
+ view_ = new CodeBrowserEditingTargetWidget(commands_,
+ globalDisplay_,
+ events_,
+ prefs_,
+ server_,
+ docDisplay_);
+
+ TextEditingTarget.registerPrefs(releaseOnDismiss_,
+ prefs_,
+ docDisplay_,
+ document);
+
+ TextEditingTarget.syncFontSize(releaseOnDismiss_,
+ events_,
+ view_,
+ fontSizeManager_);
+
+ releaseOnDismiss_.add(prefs_.softWrapRFiles().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> evt)
+ {
+ view_.adaptToFileType(FileTypeRegistry.R);
+ }
+ }
+ ));
+
+
+ // if we have contents then set them
+ CodeBrowserContents contents = getContents();
+ if (contents.getContext().length() > 0)
+ {
+ ensureContext(contents.getContext(), new Command() {
+ @Override
+ public void execute()
+ {
+ }
+ });
+ }
+ else
+ {
+ docDisplay_.setCode("", false);
+ }
+
+ }
+
+ public void showFunction(SearchPathFunctionDefinition functionDef)
+ {
+ // set the current function
+ currentFunction_ = functionDef;
+ view_.showFunction(functionDef);
+ view_.scrollToLeft();
+
+ // we only show the warning bar (for debug line matching) once per
+ // function; don't keep showing it if the user dismisses
+ shownWarningBar_ = false;
+
+ // update document properties if necessary
+ final CodeBrowserContents contents =
+ CodeBrowserContents.create(getContext());
+ if (!contents.equalTo(getContents()))
+ {
+ HashMap<String, String> props = new HashMap<String, String>();
+ contents.fillProperties(props);
+ server_.modifyDocumentProperties(
+ doc_.getId(),
+ props,
+ new SimpleRequestCallback<Void>("Error")
+ {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ contents.fillProperties(doc_.getProperties());
+ }
+ });
+ }
+ }
+
+ @Handler
+ void onExecuteCode()
+ {
+ codeExecution_.executeSelection(true);
+ }
+
+ @Handler
+ void onExecuteCodeWithoutFocus()
+ {
+ codeExecution_.executeSelection(false);
+ }
+
+ @Handler
+ void onExecuteLastCode()
+ {
+ docDisplay_.focus();
+
+ codeExecution_.executeLastCode();
+ }
+
+
+ @Handler
+ void onPrintSourceDoc()
+ {
+ TextEditingTarget.onPrintSourceDoc(docDisplay_);
+ }
+
+ @Handler
+ void onGoToHelp()
+ {
+ docDisplay_.goToHelp();
+ }
+
+ @Handler
+ void onGoToFunctionDefinition()
+ {
+ docDisplay_.goToFunctionDefinition();
+ }
+
+ @Handler
+ void onFindReplace()
+ {
+ view_.showFind(true);
+ }
+
+ @Handler
+ void onFindNext()
+ {
+ view_.findNext();
+ }
+
+ @Handler
+ void onFindPrevious()
+ {
+ view_.findPrevious();
+ }
+
+ @Handler
+ void onFindFromSelection()
+ {
+ view_.findFromSelection();
+ }
+
+ @Override
+ public Position search(String regex)
+ {
+ return search(Position.create(0, 0), regex);
+ }
+
+ @Override
+ public Position search(Position startPos, String regex)
+ {
+ InputEditorSelection sel = docDisplay_.search(regex,
+ false,
+ false,
+ false,
+ false,
+ startPos,
+ null,
+ true);
+ if (sel != null)
+ return docDisplay_.selectionToPosition(sel.getStart());
+ else
+ return null;
+ }
+
+
+ @Override
+ public String getId()
+ {
+ return doc_.getId();
+ }
+
+ @Override
+ public void adaptToExtendedFileType(String extendedType)
+ {
+ }
+
+ @Override
+ public String getExtendedFileType()
+ {
+ return null;
+ }
+
+ @Override
+ public HasValue<String> getName()
+ {
+ return new Value<String>("Source Viewer");
+ }
+
+ @Override
+ public String getTitle()
+ {
+ return getName().getValue();
+ }
+
+ @Override
+ public String getPath()
+ {
+ return PATH;
+ }
+
+ @Override
+ public String getContext()
+ {
+ if (currentFunction_ != null)
+ {
+ return currentFunction_.getNamespace() + ":::" +
+ currentFunction_.getName();
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return FileIconResources.INSTANCE.iconSourceViewer();
+ }
+
+ @Override
+ public String getTabTooltip()
+ {
+ return "R Source Viewer";
+ }
+
+ @Override
+ public HashSet<AppCommand> getSupportedCommands()
+ {
+ HashSet<AppCommand> commands = new HashSet<AppCommand>();
+ commands.add(commands_.printSourceDoc());
+ commands.add(commands_.findReplace());
+ commands.add(commands_.findNext());
+ commands.add(commands_.findPrevious());
+ commands.add(commands_.findFromSelection());
+ commands.add(commands_.goToHelp());
+ commands.add(commands_.goToFunctionDefinition());
+ commands.add(commands_.executeCode());
+ commands.add(commands_.executeCodeWithoutFocus());
+ commands.add(commands_.executeLastCode());
+ return commands;
+ }
+
+ @Override
+ public boolean canCompilePdf()
+ {
+ return false;
+ }
+
+ @Override
+ public void verifyCppPrerequisites()
+ {
+ }
+
+ @Override
+ public void focus()
+ {
+ docDisplay_.focus();
+ }
+
+
+ @Override
+ public void onActivate()
+ {
+ // IMPORTANT NOTE: most of this logic is duplicated in
+ // TextEditingTarget (no straightforward way to create a
+ // re-usable implementation) so changes here need to be synced
+
+ if (commandReg_ != null)
+ {
+ Debug.log("Warning: onActivate called twice without intervening onDeactivate");
+ commandReg_.removeHandler();
+ commandReg_ = null;
+ }
+ commandReg_ = binder_.bind(commands_, this);
+
+ view_.onActivate();
+ }
+
+ @Override
+ public void onDeactivate()
+ {
+ // IMPORTANT NOTE: most of this logic is duplicated in
+ // TextEditingTarget (no straightforward way to create a
+ // re-usable implementation) so changes here need to be synced
+
+ commandReg_.removeHandler();
+ commandReg_ = null;
+
+ // switching tabs is a navigation action
+ try
+ {
+ docDisplay_.recordCurrentNavigationPosition();
+ }
+ catch(Exception e)
+ {
+ Debug.log("Exception recording nav position: " + e.toString());
+ }
+ }
+
+ @Override
+ public void onInitiallyLoaded()
+ {
+ }
+
+ @Override
+ public void recordCurrentNavigationPosition()
+ {
+ docDisplay_.recordCurrentNavigationPosition();
+ }
+
+ @Override
+ public void navigateToPosition(final SourcePosition position,
+ final boolean recordCurrent)
+ {
+ navigateToPosition(position, recordCurrent, false);
+ }
+
+ @Override
+ public void navigateToPosition(final SourcePosition position,
+ final boolean recordCurrent,
+ final boolean highlightLine)
+ {
+ ensureContext(position.getContext(), new Command() {
+ @Override
+ public void execute()
+ {
+ docDisplay_.navigateToPosition(position,
+ recordCurrent,
+ highlightLine);
+ view_.scrollToLeft();
+ }
+ });
+ }
+
+ @Override
+ public void restorePosition(final SourcePosition position)
+ {
+ ensureContext(position.getContext(), new Command() {
+ @Override
+ public void execute()
+ {
+ docDisplay_.restorePosition(position);
+ view_.scrollToLeft();
+ }
+ });
+ }
+
+ @Override
+ public boolean isAtSourceRow(SourcePosition position)
+ {
+ return getContext().equals(position.getContext()) &&
+ docDisplay_.isAtSourceRow(position);
+ }
+
+ @Override
+ public void setCursorPosition(Position position)
+ {
+ docDisplay_.setCursorPosition(position);
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ docDisplay_.ensureCursorVisible();
+ }
+
+ @Override
+ public void forceLineHighlighting()
+ {
+ docDisplay_.setHighlightSelectedLine(true);
+ }
+
+ @Override
+ public boolean onBeforeDismiss()
+ {
+ return true;
+ }
+
+ @Override
+ public void onDismiss()
+ {
+ while (releaseOnDismiss_.size() > 0)
+ releaseOnDismiss_.remove(0).removeHandler();
+ }
+
+ @Override
+ public ReadOnlyValue<Boolean> dirtyState()
+ {
+ return dirtyState_;
+ }
+
+ @Override
+ public boolean isSaveCommandActive()
+ {
+ return dirtyState().getValue();
+ }
+
+ @Override
+ public void save(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ @Override
+ public void saveWithPrompt(Command onCompleted, Command onCancelled)
+ {
+ onCompleted.execute();
+ }
+
+ @Override
+ public void revertChanges(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ @Override
+ public long getFileSizeLimit()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public long getLargeFileSize()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Override
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ @Override
+ public HandlerRegistration addCloseHandler(CloseHandler<java.lang.Void> handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ assert false : "Not implemented";
+ }
+
+ @Override
+ public void highlightDebugLocation(
+ SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing)
+ {
+ docDisplay_.highlightDebugLocation(startPos, endPos, executing);
+ if (!shownWarningBar_)
+ {
+ view_.showWarningBar("Debug location is approximate because the " +
+ "source is not available.");
+ shownWarningBar_ = true;
+ }
+ }
+
+ @Override
+ public void endDebugHighlighting()
+ {
+ docDisplay_.endDebugHighlighting();
+ }
+
+ // Private methods --------------------------------------------------------
+
+ private CodeBrowserContents getContents()
+ {
+ if (doc_.getProperties().keys().length() > 0)
+ return (CodeBrowserContents)doc_.getProperties().cast();
+ else
+ return CodeBrowserContents.create("");
+ }
+
+ private void ensureContext(String context, final Command onRestored)
+ {
+ if (!context.equals(getContext()))
+ {
+ // get namespace and function
+ String[] contextElements = context.split(":::");
+ if (contextElements.length != 2)
+ return;
+ String namespace = contextElements[0];
+ String name = contextElements[1];
+
+ server_.getSearchPathFunctionDefinition(
+ name,
+ namespace,
+ new SimpleRequestCallback<SearchPathFunctionDefinition>(
+ "Error Reading Function Definition") {
+ @Override
+ public void onResponseReceived(
+ SearchPathFunctionDefinition functionDef)
+ {
+ showFunction(functionDef);
+ onRestored.execute();
+ }
+ });
+ }
+ else
+ {
+ onRestored.execute();
+ }
+ }
+
+ private SourceDocument doc_;
+
+ private final Value<Boolean> dirtyState_ = new Value<Boolean>(false);
+ private ArrayList<HandlerRegistration> releaseOnDismiss_ =
+ new ArrayList<HandlerRegistration>();
+ private final SourceServerOperations server_;
+ private final Commands commands_;
+ private final EventBus events_;
+ private final GlobalDisplay globalDisplay_;
+ private final UIPrefs prefs_;
+ private final FontSizeManager fontSizeManager_;
+ private Display view_;
+ private HandlerRegistration commandReg_;
+ private boolean shownWarningBar_ = false;
+
+ private DocDisplay docDisplay_;
+ private EditingTargetCodeExecution codeExecution_;
+
+ private SearchPathFunctionDefinition currentFunction_ = null;
+
+ private static final MyBinder binder_ = GWT.create(MyBinder.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.css
new file mode 100644
index 0000000..13db5fc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.css
@@ -0,0 +1,28 @@
+
+.captionLabel {
+ font-weight: bold;
+ color: #494949;
+}
+
+.menuElement {
+ cursor: default;
+}
+
+.functionName {
+ margin-left: 7px;
+ padding-right: 7px;
+}
+
+.functionNamespace {
+ color: #888;
+ padding-right: 6px;
+}
+
+.dropDownImage {
+ margin-bottom: 2px;
+}
+
+.readOnly {
+ margin-right: 7px;
+ color: #888;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.java
new file mode 100644
index 0000000..47b7596
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/codebrowser/CodeBrowserEditingTargetWidget.java
@@ -0,0 +1,485 @@
+/*
+ * CodeBrowserEditingTargetWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.codebrowser;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ResizeComposite;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.regex.Pattern.ReplaceOperation;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.InfoBar;
+import org.rstudio.core.client.widget.SecondaryToolbar;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorLineWithCursorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorUtil;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetFindReplace;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
+import org.rstudio.studio.client.workbench.views.source.events.CodeBrowserNavigationEvent;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+public class CodeBrowserEditingTargetWidget extends ResizeComposite
+ implements CodeBrowserEditingTarget.Display
+{
+ public CodeBrowserEditingTargetWidget(Commands commands,
+ final GlobalDisplay globalDisplay,
+ final EventBus eventBus,
+ final UIPrefs uiPrefs,
+ final CodeToolsServerOperations server,
+ final DocDisplay docDisplay)
+ {
+ commands_ = commands;
+ uiPrefs_ = uiPrefs;
+ globalDisplay_ = globalDisplay;
+ eventBus_ = eventBus;
+ server_ = server;
+
+ docDisplay_ = docDisplay;
+
+ findReplace_ = new TextEditingTargetFindReplace(
+ new TextEditingTargetFindReplace.Container() {
+
+ @Override
+ public AceEditor getEditor()
+ {
+ return (AceEditor)docDisplay_;
+ }
+
+ @Override
+ public void insertFindReplace(FindReplaceBar findReplaceBar)
+ {
+ panel_.insertNorth(findReplaceBar,
+ findReplaceBar.getHeight(),
+ null);
+ }
+
+ @Override
+ public void removeFindReplace(FindReplaceBar findReplaceBar)
+ {
+ panel_.remove(findReplaceBar);
+ }
+
+ },
+ false); // don't show replace UI
+
+ panel_ = new PanelWithToolbars(createToolbar(),
+ createSecondaryToolbar(),
+ docDisplay_.asWidget(),
+ null);
+ panel_.setSize("100%", "100%");
+
+ docDisplay_.setReadOnly(true);
+
+ // setup custom completion manager for executing F1 and F2 actions
+ docDisplay_.setFileType(FileTypeRegistry.R, new CompletionManager() {
+
+ @Override
+ public boolean previewKeyDown(NativeEvent event)
+ {
+ int modifier = KeyboardShortcut.getModifierValue(event);
+ if (modifier == KeyboardShortcut.NONE)
+ {
+ if (event.getKeyCode() == 112) // F1
+ {
+ goToHelp();
+ }
+ else if (event.getKeyCode() == 113) // F2
+ {
+ goToFunctionDefinition();
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void goToHelp()
+ {
+ InputEditorLineWithCursorPosition linePos =
+ InputEditorUtil.getLineWithCursorPosition(docDisplay);
+
+ server.getHelpAtCursor(
+ linePos.getLine(), linePos.getPosition(),
+ new SimpleRequestCallback<Void>("Help"));
+ }
+
+ @Override
+ public void goToFunctionDefinition()
+ {
+ // determine current line and cursor position
+ InputEditorLineWithCursorPosition lineWithPos =
+ InputEditorUtil.getLineWithCursorPosition(docDisplay);
+
+ // navigate to the function at this position (if any)
+ navigateToFunction(lineWithPos);
+ }
+
+ @Override
+ public void codeCompletion()
+ {
+ // no-op since this is a code browser
+ }
+
+ @Override
+ public boolean previewKeyPress(char charCode)
+ {
+ return false;
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ });
+
+ initWidget(panel_);
+
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+
+ @Override
+ public void adaptToFileType(TextFileType fileType)
+ {
+ docDisplay_.setFileType(fileType, true);
+ }
+
+
+ @Override
+ public void setFontSize(double size)
+ {
+ docDisplay_.setFontSize(size);
+ }
+
+ @Override
+ public void onActivate()
+ {
+ docDisplay_.onActivate();
+ }
+
+ @Override
+ public void showFunction(SearchPathFunctionDefinition functionDef)
+ {
+ currentFunctionNamespace_ = functionDef.getNamespace();
+ docDisplay_.setCode(formatCode(functionDef), false);
+ // don't send focus to the display for debugging; we want it to stay in
+ // the console
+ if (!functionDef.isActiveDebugCode())
+ {
+ docDisplay_.focus();
+ }
+ contextWidget_.setCurrentFunction(functionDef);
+ }
+
+ @Override
+ public void showFind(boolean defaultForward)
+ {
+ findReplace_.showFindReplace(defaultForward);
+ }
+
+ @Override
+ public void findNext()
+ {
+ findReplace_.findNext();
+
+ }
+
+ @Override
+ public void findPrevious()
+ {
+ findReplace_.findPrevious();
+ }
+
+ @Override
+ public void findFromSelection()
+ {
+ findReplace_.findFromSelection();
+ }
+
+ @Override
+ public void scrollToLeft()
+ {
+ new Timer() {
+ @Override
+ public void run()
+ {
+ docDisplay_.scrollToX(0);
+ }
+ }.schedule(100);
+ }
+
+ @Override
+ public void showWarningBar(String warning)
+ {
+ if (warningBar_ == null)
+ {
+ warningBar_ = new InfoBar(InfoBar.WARNING, new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ hideWarningBar();
+ }
+
+ });
+ }
+ warningBar_.setText(warning);
+ panel_.insertNorth(warningBar_, warningBar_.getHeight(), null);
+ }
+
+ @Override
+ public void hideWarningBar()
+ {
+ if (warningBar_ != null)
+ {
+ panel_.remove(warningBar_);
+ }
+ }
+
+ private void navigateToFunction(
+ InputEditorLineWithCursorPosition lineWithPos)
+ {
+ server_.findFunctionInSearchPath(
+ lineWithPos.getLine(),
+ lineWithPos.getPosition(),
+ currentFunctionNamespace_,
+ new FunctionSearchRequestCallback(true));
+ }
+
+ private class FunctionSearchRequestCallback
+ extends ServerRequestCallback<SearchPathFunctionDefinition>
+ {
+ public FunctionSearchRequestCallback(boolean searchLocally)
+ {
+ searchLocally_ = searchLocally;
+
+ // delayed progress indicator
+ progress_ = new GlobalProgressDelayer(
+ globalDisplay_, 1000, "Searching for function definition...");
+
+ }
+
+ @Override
+ public void onResponseReceived(SearchPathFunctionDefinition def)
+ {
+ // dismiss progress
+ progress_.dismiss();
+
+ // if we got a hit
+ if (def != null && def.getName() != null)
+ {
+ // try to search for the function locally
+ SourcePosition position = searchLocally_ ?
+ docDisplay_.findFunctionPositionFromCursor(def.getName()) :
+ null;
+
+ if (position != null)
+ {
+ docDisplay_.navigateToPosition(position, true);
+ }
+ else if (def.getNamespace() != null)
+ {
+ docDisplay_.recordCurrentNavigationPosition();
+ eventBus_.fireEvent(new CodeBrowserNavigationEvent(
+ def));
+ }
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ progress_.dismiss();
+
+ globalDisplay_.showErrorMessage(
+ "Error Searching for Function",
+ error.getUserMessage());
+ }
+
+ private final boolean searchLocally_;
+ private final GlobalProgressDelayer progress_;
+ }
+
+ private Toolbar createToolbar()
+ {
+ Toolbar toolbar = new EditingTargetToolbar(commands_);
+
+ toolbar.addLeftWidget(commands_.printSourceDoc().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(findReplace_.createFindReplaceButton());
+
+ ImageResource icon = ThemeResources.INSTANCE.codeTransform();
+
+ ToolbarPopupMenu menu = new ToolbarPopupMenu();
+ menu.addItem(commands_.goToHelp().createMenuItem(false));
+ menu.addItem(commands_.goToFunctionDefinition().createMenuItem(false));
+ ToolbarButton codeTools = new ToolbarButton("", icon, menu);
+ codeTools.setTitle("Code Tools");
+ toolbar.addLeftWidget(codeTools);
+
+ toolbar.addRightWidget(commands_.executeCode().createToolbarButton());
+ toolbar.addRightSeparator();
+ toolbar.addRightWidget(commands_.executeLastCode().createToolbarButton());
+
+ return toolbar;
+ }
+
+ private Toolbar createSecondaryToolbar()
+ {
+ SecondaryToolbar toolbar = new SecondaryToolbar();
+
+ contextWidget_ = new CodeBrowserContextWidget(RES.styles());
+ contextWidget_.addSelectionHandler(new SelectionHandler<String> () {
+ @Override
+ public void onSelection(SelectionEvent<String> event)
+ {
+ server_.getMethodDefinition(
+ event.getSelectedItem(),
+ new FunctionSearchRequestCallback(false));
+ }
+
+ });
+ toolbar.addLeftWidget(contextWidget_);
+
+ Label readOnlyLabel = new Label("(Read-only)");
+ readOnlyLabel.addStyleName(RES.styles().readOnly());
+ toolbar.addRightWidget(readOnlyLabel);
+
+ return toolbar;
+ }
+
+ private String formatCode(SearchPathFunctionDefinition functionDef)
+ {
+ // deal with null
+ String code = functionDef.getCode();
+ if (code == null)
+ return "";
+
+ // if this is from a source ref then leave it alone
+ if (functionDef.isCodeFromSrcAttrib())
+ return code;
+
+ // determine the replacement text based on the user's current
+ // editing preferences
+ String replaceText = "\t";
+ if (uiPrefs_.useSpacesForTab().getValue())
+ {
+ StringBuilder replaceBuilder = new StringBuilder();
+ for (int i=0; i<uiPrefs_.numSpacesForTab().getValue(); i++)
+ replaceBuilder.append(' ');
+ replaceText = replaceBuilder.toString();
+ }
+
+ // create regex pattern used to find leading space
+ // NOTE: the 4 spaces comes from the implementation of printtab2buff
+ // in deparse.c -- it is hard-coded to use 4 spaces for the first 4
+ // levels of indentation and then 2 spaces for subsequent levels.
+ final String replaceWith = replaceText;
+ Pattern pattern = Pattern.create("^( ){1,4}");
+ code = pattern.replaceAll(code, new ReplaceOperation()
+ {
+ @Override
+ public String replace(Match m)
+ {
+ return m.getValue().replace(" ", replaceWith);
+ }
+ });
+ Pattern pattern2 = Pattern.create("^\t{4}( )+");
+ code = pattern2.replaceAll(code, new ReplaceOperation()
+ {
+ @Override
+ public String replace(Match m)
+ {
+ return m.getValue().replace(" ", replaceWith);
+ }
+ });
+
+ return code;
+ }
+
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ interface Resources extends ClientBundle
+ {
+ @Source("CodeBrowserEditingTargetWidget.css")
+ Styles styles();
+
+ }
+
+ interface Styles extends CssResource
+ {
+ String captionLabel();
+ String menuElement();
+ String functionName();
+ String functionNamespace();
+ String dropDownImage();
+ String readOnly();
+ }
+
+ static Resources RES = GWT.create(Resources.class);
+
+ private final PanelWithToolbars panel_;
+ private CodeBrowserContextWidget contextWidget_;
+ private final CodeToolsServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus eventBus_;
+ private final Commands commands_;
+ private final UIPrefs uiPrefs_;
+ private final DocDisplay docDisplay_;
+ private final TextEditingTargetFindReplace findReplace_;
+ private String currentFunctionNamespace_ = null;
+ private InfoBar warningBar_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTarget.java
new file mode 100644
index 0000000..9d5c091
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTarget.java
@@ -0,0 +1,153 @@
+/*
+ * DataEditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.data;
+
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.widget.SimplePanelWithProgress;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.editors.urlcontent.UrlContentEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.model.DataItem;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+import java.util.HashMap;
+
+public class DataEditingTarget extends UrlContentEditingTarget
+{
+ @Inject
+ public DataEditingTarget(SourceServerOperations server,
+ Commands commands,
+ GlobalDisplay globalDisplay,
+ EventBus events)
+ {
+ super(server, commands, globalDisplay, events);
+ }
+
+ @Override
+ protected Display createDisplay()
+ {
+ progressPanel_ = new SimplePanelWithProgress();
+ progressPanel_.setSize("100%", "100%");
+ reloadDisplay();
+ return new Display()
+ {
+ public void print()
+ {
+ ((Display)progressPanel_.getWidget()).print();
+ }
+
+ public Widget asWidget()
+ {
+ return progressPanel_;
+ }
+ };
+ }
+
+ private void clearDisplay()
+ {
+ progressPanel_.showProgress(1);
+ }
+
+ private void reloadDisplay()
+ {
+ DataEditingTargetWidget view = new DataEditingTargetWidget(
+ commands_,
+ getDataItem());
+ view.setSize("100%", "100%");
+ progressPanel_.setWidget(view);
+ }
+
+ @Override
+ public String getPath()
+ {
+ return getDataItem().getURI();
+ }
+
+ @Override
+ public ImageResource getIcon()
+ {
+ return FileIconResources.INSTANCE.iconCsv();
+ }
+
+ private DataItem getDataItem()
+ {
+ return doc_.getProperties().cast();
+ }
+
+ @Override
+ protected String getContentTitle()
+ {
+ return getDataItem().getCaption();
+ }
+
+ @Override
+ protected String getContentUrl()
+ {
+ return getDataItem().getContentUrl();
+ }
+
+ public void updateData(final DataItem data)
+ {
+ final Widget originalWidget = progressPanel_.getWidget();
+
+ clearDisplay();
+
+ final String oldContentUrl = getContentUrl();
+
+ HashMap<String, String> props = new HashMap<String, String>();
+ data.fillProperties(props);
+ server_.modifyDocumentProperties(
+ doc_.getId(),
+ props,
+ new SimpleRequestCallback<Void>("Error")
+ {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ server_.removeContentUrl(
+ oldContentUrl,
+ new ServerRequestCallback<Void>() {
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+
+ data.fillProperties(doc_.getProperties());
+ reloadDisplay();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ super.onError(error);
+ progressPanel_.setWidget(originalWidget);
+ }
+ });
+ }
+
+ private SimplePanelWithProgress progressPanel_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.css
new file mode 100644
index 0000000..ed0f668
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.css
@@ -0,0 +1,22 @@
+.description {
+ position: relative;
+ top: -1px;
+ margin-right: 6px;
+}
+
+.statusBar {
+ font-size: 10px;
+ background-color: #F3F3F3;
+ border-top: 1px solid #CCC;
+ padding: 3px 0 0 6px;
+}
+
+.statusBarDisplayed {
+ display: inline;
+}
+
+.statusBarOmitted {
+ display: inline;
+ color: #777;
+ margin-left: 4px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.java
new file mode 100644
index 0000000..810132d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/data/DataEditingTargetWidget.java
@@ -0,0 +1,135 @@
+/*
+ * DataEditingTargetWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.data;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.widget.RStudioFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
+import org.rstudio.studio.client.workbench.views.source.editors.urlcontent.UrlContentEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.model.DataItem;
+
+public class DataEditingTargetWidget extends Composite
+ implements UrlContentEditingTarget.Display
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("DataEditingTargetWidget.css")
+ Styles styles();
+ }
+ private static Resources resources = GWT.create(Resources.class);
+
+ public interface Styles extends CssResource
+ {
+ String description();
+
+ String statusBar();
+ String statusBarDisplayed();
+ String statusBarOmitted();
+ }
+
+ static
+ {
+ resources.styles().ensureInjected();
+ }
+
+ public DataEditingTargetWidget(Commands commands, DataItem dataItem)
+ {
+ Styles styles = resources.styles();
+
+ commands_ = commands;
+
+ frame_ = new RStudioFrame(dataItem.getContentUrl());
+ frame_.setSize("100%", "100%");
+
+ Widget mainWidget = frame_;
+
+ if (dataItem.getDisplayedObservations() != dataItem.getTotalObservations())
+ {
+ FlowPanel statusBar = new FlowPanel();
+ statusBar.setStylePrimaryName(styles.statusBar());
+ statusBar.setSize("100%", "100%");
+ Label label1 = new Label(
+ "Displayed "
+ + StringUtil.formatGeneralNumber(dataItem.getDisplayedObservations())
+ + " rows of "
+ + StringUtil.formatGeneralNumber(dataItem.getTotalObservations()));
+ int omitted = dataItem.getTotalObservations()
+ - dataItem.getDisplayedObservations();
+ Label label2 = new Label("(" +
+ StringUtil.formatGeneralNumber(omitted) +
+ " omitted)");
+
+ label1.addStyleName(styles.statusBarDisplayed());
+ label2.addStyleName(styles.statusBarOmitted());
+
+ statusBar.add(label1);
+ statusBar.add(label2);
+
+ DockLayoutPanel dockPanel = new DockLayoutPanel(Unit.PX);
+ dockPanel.addSouth(statusBar, 20);
+ dockPanel.add(frame_);
+ dockPanel.setSize("100%", "100%");
+ mainWidget = dockPanel;
+ }
+
+ PanelWithToolbars panel = new PanelWithToolbars(createToolbar(dataItem,
+ styles),
+ mainWidget);
+
+ initWidget(panel);
+
+ }
+
+ private Toolbar createToolbar(DataItem dataItem, Styles styles)
+ {
+ Label description = new Label(
+ StringUtil.formatGeneralNumber(dataItem.getTotalObservations())
+ + " observations of " +
+ StringUtil.formatGeneralNumber(dataItem.getVariables())
+ + " variables",
+ false);
+ description.addStyleName(styles.description());
+
+ Toolbar toolbar = new EditingTargetToolbar(commands_);
+ toolbar.addLeftWidget(commands_.popoutDoc().createToolbarButton());
+ toolbar.addRightWidget(description);
+
+ return toolbar;
+ }
+
+ public void print()
+ {
+ IFrameElementEx frameEl = (IFrameElementEx) frame_.getElement().cast();
+ frameEl.getContentWindow().print();
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ private final Commands commands_;
+ private RStudioFrame frame_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTarget.java
new file mode 100644
index 0000000..74864e3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTarget.java
@@ -0,0 +1,324 @@
+/*
+ * ProfilerEditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.profiler;
+
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.ReadOnlyValue;
+import org.rstudio.studio.client.common.Value;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.events.SourceNavigationEvent;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.source.model.SourceNavigation;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import java.util.HashSet;
+
+public class ProfilerEditingTarget implements EditingTarget
+{
+ public static final String PATH = "profiler://";
+
+ @Inject
+ public ProfilerEditingTarget(ProfilerPresenter presenter,
+ Commands commands,
+ EventBus events)
+ {
+ presenter_ = presenter;
+ commands_ = commands;
+ events_ = events;
+ }
+
+ public String getId()
+ {
+ return doc_.getId();
+ }
+
+ @Override
+ public void adaptToExtendedFileType(String extendedType)
+ {
+ }
+
+ @Override
+ public String getExtendedFileType()
+ {
+ return null;
+ }
+
+ public HasValue<String> getName()
+ {
+ return new Value<String>("Profiler");
+ }
+
+ public String getTitle()
+ {
+ return "Profiler";
+ }
+
+ public String getPath()
+ {
+ return PATH;
+ }
+
+ public String getContext()
+ {
+ return null;
+ }
+
+ public ImageResource getIcon()
+ {
+ return FileIconResources.INSTANCE.iconProfiler();
+ }
+
+ public String getTabTooltip()
+ {
+ return "R Profiler";
+ }
+
+ public HashSet<AppCommand> getSupportedCommands()
+ {
+ return new HashSet<AppCommand>();
+ }
+
+ @Override
+ public boolean canCompilePdf()
+ {
+ return false;
+ }
+
+
+ @Override
+ public void verifyCppPrerequisites()
+ {
+ }
+
+ @Override
+ public Position search(String regex)
+ {
+ return null;
+ }
+
+ @Override
+ public Position search(Position startPos, String regex)
+ {
+ return null;
+ }
+
+ @Override
+ public void forceLineHighlighting()
+ {
+ }
+
+
+ public void focus()
+ {
+ }
+
+ public void onActivate()
+ {
+ }
+
+ public void onDeactivate()
+ {
+ recordCurrentNavigationPosition();
+ }
+
+ @Override
+ public void onInitiallyLoaded()
+ {
+ }
+
+ @Override
+ public void recordCurrentNavigationPosition()
+ {
+ events_.fireEvent(new SourceNavigationEvent(
+ SourceNavigation.create(
+ getId(),
+ getPath(),
+ SourcePosition.create(0, 0))));
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent)
+ {
+ }
+
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent,
+ boolean highlightLine)
+ {
+ }
+
+ @Override
+ public void restorePosition(SourcePosition position)
+ {
+ }
+
+ @Override
+ public void setCursorPosition(Position position)
+ {
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ }
+
+ @Override
+ public boolean isAtSourceRow(SourcePosition position)
+ {
+ // always true because profiler docs don't have the
+ // concept of a position
+ return true;
+ }
+
+ @Override
+ public void highlightDebugLocation(SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing)
+ {
+ }
+
+ @Override
+ public void endDebugHighlighting()
+ {
+ }
+
+ public boolean onBeforeDismiss()
+ {
+ return true;
+ }
+
+ public ReadOnlyValue<Boolean> dirtyState()
+ {
+ return neverDirtyState_;
+ }
+
+ @Override
+ public boolean isSaveCommandActive()
+ {
+ return dirtyState().getValue();
+ }
+
+ public void save(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ public void saveWithPrompt(Command onCompleted, Command onCancelled)
+ {
+ onCompleted.execute();
+ }
+
+ public void revertChanges(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ public void initialize(SourceDocument document,
+ FileSystemContext fileContext,
+ FileType type,
+ Provider<String> defaultNameProvider)
+ {
+ // initialize doc, view, and presenter
+ doc_ = document;
+ view_ = new ProfilerEditingTargetWidget(commands_);
+ presenter_.attatch(doc_, view_);
+ }
+
+ public void onDismiss()
+ {
+ presenter_.detach();
+ }
+
+ public long getFileSizeLimit()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ public long getLargeFileSize()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ @Override
+ public HandlerRegistration addCloseHandler(CloseHandler<java.lang.Void> handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ assert false : "Not implemented";
+ }
+
+ private SourceDocument doc_;
+ private ProfilerEditingTargetWidget view_;
+ private final ProfilerPresenter presenter_;
+
+ private final Value<Boolean> neverDirtyState_ = new Value<Boolean>(false);
+
+ private final EventBus events_;
+ private final Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTargetWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTargetWidget.java
new file mode 100644
index 0000000..d63c106
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerEditingTargetWidget.java
@@ -0,0 +1,82 @@
+/*
+ * ProfilerEditingTargetWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.profiler;
+
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IntegerBox;
+import com.google.gwt.user.client.ui.Label;
+
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
+
+public class ProfilerEditingTargetWidget extends Composite
+ implements ProfilerPresenter.Display
+
+{
+ public ProfilerEditingTargetWidget(Commands commands)
+ {
+ VerticalPanel panel = new VerticalPanel();
+ panel.add(new Label("PropA"));
+ txtPropA_ = new IntegerBox();
+ panel.add(txtPropA_);
+ panel.add(new Label("PropB"));
+ chkPropB_ = new CheckBox();
+ panel.add(chkPropB_);
+ panel.setSize("100%", "100%");
+
+ PanelWithToolbars mainPanel = new PanelWithToolbars(
+ createToolbar(commands),
+ panel);
+
+ initWidget(mainPanel);
+
+ }
+
+ private Toolbar createToolbar(Commands commands)
+ {
+ Toolbar toolbar = new EditingTargetToolbar(commands);
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands.startProfiler().createToolbarButton());
+ toolbar.addLeftWidget(commands.stopProfiler().createToolbarButton());
+ return toolbar;
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ @Override
+ public HasValue<Integer> getPropA()
+ {
+ return txtPropA_;
+ }
+
+ @Override
+ public HasValue<Boolean> getPropB()
+ {
+ return chkPropB_;
+ }
+
+ private IntegerBox txtPropA_;
+ private CheckBox chkPropB_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerPresenter.java
new file mode 100644
index 0000000..0c53381
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/ProfilerPresenter.java
@@ -0,0 +1,184 @@
+/*
+ * ProfilerPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.profiler;
+
+import java.util.HashMap;
+
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerContents;
+import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerServerOperations;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class ProfilerPresenter
+{
+ public interface Display
+ {
+ HasValue<Integer> getPropA();
+ HasValue<Boolean> getPropB();
+ }
+
+ @Inject
+ public ProfilerPresenter(ProfilerServerOperations server,
+ Binder binder,
+ Commands commands)
+ {
+ server_ = server;
+ commands_ = commands;
+ binder.bind(commands, this);
+
+ // default profiler commands to disabled until we are attached
+ // to a document and view
+ disableAllCommands();
+ }
+
+ public void attatch(SourceDocument doc, Display view)
+ {
+ // save references to doc and view
+ doc_ = doc;
+ view_ = view;
+
+ // initialize view
+ ProfilerContents contents = getContents();
+ view_.getPropA().setValue(contents.getPropA());
+ view_.getPropB().setValue(contents.getPropB());
+
+ // subscribe to value changes on the view to save contents
+ // to the server whenenver it's modfied
+ handlerRegistrations_.add(view_.getPropA()
+ .addValueChangeHandler(new ValueChangeHandler<Integer>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Integer> event)
+ {
+ contentsUpdater_.nudge();
+ }
+
+ }));
+
+ handlerRegistrations_.add(view_.getPropB()
+ .addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ contentsUpdater_.nudge();
+ }
+ }));
+
+ // enable commands for stopped state
+ enableStoppedCommands();
+ }
+
+ public void detach()
+ {
+ // unsubscribe from view
+ handlerRegistrations_.removeHandler();
+
+ // null out references to doc and view
+ doc_ = null;
+ view_ = null;
+
+ // disable all commands
+ disableAllCommands();
+ }
+
+ @Handler
+ public void onStartProfiler()
+ {
+
+ // manage commands
+ enableStartedCommands();
+ }
+
+ @Handler
+ public void onStopProfiler()
+ {
+
+
+ // manage commands
+ enableStoppedCommands();
+ }
+
+ private void disableAllCommands()
+ {
+ commands_.startProfiler().setEnabled(false);
+ commands_.stopProfiler().setEnabled(false);
+ }
+
+
+ private void enableStartedCommands()
+ {
+ commands_.startProfiler().setEnabled(false);
+ commands_.stopProfiler().setEnabled(true);
+ }
+ private void enableStoppedCommands()
+ {
+ commands_.startProfiler().setEnabled(true);
+ commands_.stopProfiler().setEnabled(false);
+ }
+
+ // create a time buffered command for updating profiler contents (ensures
+ // that we save no more frequently than every 100ms even in the face of
+ // many changes over a short time)
+ private TimeBufferedCommand contentsUpdater_ = new TimeBufferedCommand(100) {
+
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ // tab might have been closed in the meantime, check for this first
+ if (doc_ == null || view_ == null)
+ return;
+
+ // update document properties if they've changed
+ ProfilerContents contents = ProfilerContents.create(
+ view_.getPropA().getValue(),
+ view_.getPropB().getValue());
+ if (!contents.equalTo(getContents()))
+ {
+ HashMap<String, String> props = new HashMap<String, String>();
+ contents.fillProperties(props);
+ server_.modifyDocumentProperties(doc_.getId(),
+ props,
+ new VoidServerRequestCallback());
+ }
+ }
+ };
+
+ // typed access to underlying document properties
+ private ProfilerContents getContents()
+ {
+ return (ProfilerContents)doc_.getProperties().cast();
+ }
+
+
+ private SourceDocument doc_ = null;
+ private Display view_ = null;
+ private final ProfilerServerOperations server_;
+ private final Commands commands_;
+ private final HandlerRegistrations handlerRegistrations_ =
+ new HandlerRegistrations();
+
+ public interface Binder extends CommandBinder<Commands, ProfilerPresenter> {}
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerContents.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerContents.java
new file mode 100644
index 0000000..84e3dc7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerContents.java
@@ -0,0 +1,72 @@
+/*
+ * ProfilerContents.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.profiler.model;
+
+import java.util.HashMap;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ProfilerContents extends JavaScriptObject
+{
+ protected ProfilerContents()
+ {
+ }
+
+ public static final ProfilerContents createDefault()
+ {
+ return create(10, true);
+ }
+
+ public static final native ProfilerContents create(int propA,
+ boolean propB) /*-{
+ var contents = new Object();
+ contents.prop_a = propA.toString();
+ contents.prop_b = propB.toString();
+ return contents ;
+ }-*/;
+
+
+ public final int getPropA()
+ {
+ return Integer.parseInt(getPropAString());
+ };
+
+ public final boolean getPropB()
+ {
+ return Boolean.parseBoolean(getPropBString());
+ };
+
+ public final boolean equalTo(ProfilerContents other)
+ {
+ return getPropA() == other.getPropA() &&
+ getPropB() == other.getPropB();
+ }
+
+ public final void fillProperties(HashMap<String, String> properties)
+ {
+ properties.put("prop_a", getPropAString());
+ properties.put("prop_b", getPropBString());
+ }
+
+ private native final String getPropAString() /*-{
+ return this.prop_a;
+ }-*/;
+
+ private native final String getPropBString() /*-{
+ return this.prop_b;
+ }-*/;
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerServerOperations.java
new file mode 100644
index 0000000..a350bfc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/profiler/model/ProfilerServerOperations.java
@@ -0,0 +1,27 @@
+/*
+ * ProfilerServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.profiler.model;
+
+import java.util.HashMap;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+
+public interface ProfilerServerOperations
+{
+ void modifyDocumentProperties(String id, HashMap<String, String> properties,
+ ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditor.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditor.java
new file mode 100644
index 0000000..0a115b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditor.java
@@ -0,0 +1,1902 @@
+/*
+ * AceEditor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.PreElement;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.ExternalJavaScriptLoader;
+import org.rstudio.core.client.ExternalJavaScriptLoader.Callback;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.widget.DynamicIFrame;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.ChangeTracker;
+import org.rstudio.studio.client.workbench.model.EventBasedChangeTracker;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager.InitCompletionFilter;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionPopupPanel;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.NullCompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.RCompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorUtil;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.*;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceClickEvent.Handler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Mode.InsertChunkInfo;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Renderer.ScreenCoordinates;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.WordIterable;
+import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionManager;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.*;
+import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionEvent;
+import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionHandler;
+import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+public class AceEditor implements DocDisplay,
+ InputEditorDisplay,
+ NavigableSourceEditor
+{
+ public enum NewLineMode
+ {
+ Windows("windows"),
+ Unix("unix"),
+ Auto("auto");
+
+ NewLineMode(String type)
+ {
+ this.type = type;
+ }
+
+ public String getType()
+ {
+ return type;
+ }
+
+ private String type;
+ }
+
+ private class Filter implements InitCompletionFilter
+ {
+ public boolean shouldComplete(NativeEvent event)
+ {
+ // Never complete if there's an active selection
+ Range range = getSession().getSelection().getRange();
+ if (!range.isEmpty())
+ return false;
+
+ // Don't consider Tab to be a completion if we're at the start of a
+ // line (e.g. only zero or more whitespace characters between the
+ // beginning of the line and the cursor)
+
+ if (event != null && event.getKeyCode() != KeyCodes.KEY_TAB)
+ return true;
+
+ int col = range.getStart().getColumn();
+ if (col == 0)
+ return false;
+
+ String row = getSession().getLine(range.getStart().getRow());
+ return row.substring(0, col).trim().length() != 0;
+ }
+ }
+
+ private class AnchoredSelectionImpl implements AnchoredSelection
+ {
+ private AnchoredSelectionImpl(Anchor start, Anchor end)
+ {
+ start_ = start;
+ end_ = end;
+ }
+
+ public String getValue()
+ {
+ return getSession().getTextRange(getRange());
+ }
+
+ public void apply()
+ {
+ getSession().getSelection().setSelectionRange(
+ getRange());
+ }
+
+ public Range getRange()
+ {
+ return Range.fromPoints(start_.getPosition(), end_.getPosition());
+ }
+
+ public void detach()
+ {
+ start_.detach();
+ end_.detach();
+ }
+
+ private final Anchor start_;
+ private final Anchor end_;
+ }
+
+ private class AceEditorChangeTracker extends EventBasedChangeTracker<Void>
+ {
+ private AceEditorChangeTracker()
+ {
+ super(AceEditor.this);
+ AceEditor.this.addFoldChangeHandler(new org.rstudio.studio.client.workbench.views.source.editors.text.events.FoldChangeEvent.Handler()
+ {
+ @Override
+ public void onFoldChange(FoldChangeEvent event)
+ {
+ changed_ = true;
+ }
+ });
+ }
+
+ @Override
+ public ChangeTracker fork()
+ {
+ AceEditorChangeTracker forked = new AceEditorChangeTracker();
+ forked.changed_ = changed_;
+ return forked;
+ }
+ }
+
+ public static void preload()
+ {
+ load(null);
+ }
+
+ public static void load(final Command command)
+ {
+ aceLoader_.addCallback(new Callback()
+ {
+ public void onLoaded()
+ {
+ aceSupportLoader_.addCallback(new Callback()
+ {
+ public void onLoaded()
+ {
+ if (command != null)
+ command.execute();
+ }
+ });
+ }
+ });
+ }
+
+ @Inject
+ public AceEditor()
+ {
+ widget_ = new AceEditorWidget();
+ ElementIds.assignElementId(widget_.getElement(),
+ ElementIds.SOURCE_TEXT_EDITOR);
+
+ completionManager_ = new NullCompletionManager();
+ RStudioGinjector.INSTANCE.injectMembers(this);
+
+ widget_.addValueChangeHandler(new ValueChangeHandler<Void>()
+ {
+ public void onValueChange(ValueChangeEvent<Void> evt)
+ {
+ ValueChangeEvent.fire(AceEditor.this, null);
+ }
+ });
+ widget_.addFoldChangeHandler(new FoldChangeEvent.Handler()
+ {
+ @Override
+ public void onFoldChange(FoldChangeEvent event)
+ {
+ AceEditor.this.fireEvent(new FoldChangeEvent());
+ }
+ });
+
+ addCapturingKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (useVimMode_)
+ return;
+
+ int mod = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ if (mod == KeyboardShortcut.CTRL)
+ {
+ switch (event.getNativeKeyCode())
+ {
+ case 'U':
+ event.preventDefault();
+ InputEditorUtil.yankBeforeCursor(AceEditor.this, true);
+ break;
+ case 'K':
+ event.preventDefault();
+ InputEditorUtil.yankAfterCursor(AceEditor.this, true);
+ break;
+ case 'Y':
+ event.preventDefault();
+ Position start = getSelectionStart();
+ InputEditorUtil.pasteYanked(AceEditor.this);
+ indentPastedRange(Range.fromPoints(start,
+ getSelectionEnd()));
+ break;
+ }
+ }
+
+ }
+ });
+
+ addPasteHandler(new PasteEvent.Handler()
+ {
+ @Override
+ public void onPaste(PasteEvent event)
+ {
+ final Position start = getSelectionStart();
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ Range range = Range.fromPoints(start, getSelectionEnd());
+ indentPastedRange(range);
+ }
+ });
+ }
+ });
+
+ // handle click events
+ addAceClickHandler(new AceClickEvent.Handler()
+ {
+ @Override
+ public void onClick(AceClickEvent event)
+ {
+ fixVerticalOffsetBug();
+ if (DomUtils.isCommandClick(event.getNativeEvent()))
+ {
+ // eat the event so ace doesn't do anything with it
+ event.preventDefault();
+ event.stopPropagation();
+
+ // set the cursor position
+ setCursorPosition(event.getDocumentPosition());
+
+ // go to function definition
+ fireEvent(new CommandClickEvent());
+ }
+ else
+ {
+ // if the focus in the Help pane or another iframe
+ // we need to make sure to get it back
+ WindowEx.get().focus();
+ }
+ }
+ });
+
+ addCursorChangedHandler(new CursorChangedHandler()
+ {
+ @Override
+ public void onCursorChanged(CursorChangedEvent event)
+ {
+ fixVerticalOffsetBug();
+ clearLineHighlight();
+ }
+ });
+
+ addValueChangeHandler(new ValueChangeHandler<Void>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Void> event)
+ {
+ clearDebugLineHighlight();
+ }
+ });
+ }
+
+ private void indentPastedRange(Range range)
+ {
+ if (fileType_ == null ||
+ !fileType_.canAutoIndent() ||
+ !RStudioGinjector.INSTANCE.getUIPrefs().reindentOnPaste().getValue())
+ {
+ return;
+ }
+
+ String firstLinePrefix = getSession().getTextRange(
+ Range.fromPoints(Position.create(range.getStart().getRow(), 0),
+ range.getStart()));
+
+ if (firstLinePrefix.trim().length() != 0)
+ {
+ Position newStart = Position.create(range.getStart().getRow() + 1, 0);
+ if (newStart.compareTo(range.getEnd()) >= 0)
+ return;
+
+ range = Range.fromPoints(newStart, range.getEnd());
+ }
+
+ getSession().reindent(range);
+ }
+
+ @Inject
+ void initialize(CodeToolsServerOperations server)
+ {
+ server_ = server;
+ }
+
+ public TextFileType getFileType()
+ {
+ return fileType_;
+ }
+
+ public void setFileType(TextFileType fileType)
+ {
+ setFileType(fileType, false);
+ }
+
+ public void setFileType(TextFileType fileType, boolean suppressCompletion)
+ {
+ fileType_ = fileType;
+ updateLanguage(suppressCompletion);
+ }
+
+ public void setFileType(TextFileType fileType,
+ CompletionManager completionManager)
+ {
+ fileType_ = fileType;
+ updateLanguage(completionManager);
+ }
+
+ @Override
+ public void setRnwCompletionContext(RnwCompletionContext rnwContext)
+ {
+ rnwContext_ = rnwContext;
+ }
+
+ private void updateLanguage(boolean suppressCompletion)
+ {
+ if (fileType_ == null)
+ return;
+
+ CompletionManager completionManager;
+ if (!suppressCompletion)
+ {
+ if (fileType_.getEditorLanguage().useRCompletion())
+ {
+ completionManager = new RCompletionManager(
+ this,
+ this,
+ new CompletionPopupPanel(),
+ server_,
+ new Filter(),
+ fileType_.canExecuteChunks() ? rnwContext_ : null);
+
+ // if this is cpp then we use our own completion manager
+ // that can optionally delegate to the R completion manager
+ if (fileType_.isCpp() || fileType_.isRmd())
+ {
+ completionManager = new CppCompletionManager(this,
+ this,
+ new Filter(),
+ completionManager);
+ }
+ }
+ else
+ completionManager = new NullCompletionManager();
+ }
+ else
+ completionManager = new NullCompletionManager();
+
+ updateLanguage(completionManager);
+ }
+
+ private void updateLanguage(CompletionManager completionManager)
+ {
+ if (fileType_ == null)
+ return;
+
+ completionManager_ = completionManager;
+
+ updateKeyboardHandlers();
+
+ getSession().setEditorMode(
+ fileType_.getEditorLanguage().getParserName(),
+ false);
+ getSession().setUseWrapMode(fileType_.getWordWrap());
+ syncWrapLimit();
+ }
+
+ private void syncWrapLimit()
+ {
+ // bail if there is no filetype yet
+ if (fileType_ == null)
+ return;
+
+ // We originally observed that large word-wrapped documents
+ // would cause Chrome on Liunx to freeze (bug #3207), eventually
+ // running of of memory. Running the profiler indicated that the
+ // time was being spent inside wrap width calculations in Ace.
+ // Looking at the Ace bug database there were other wrapping problems
+ // that were solvable by changing the wrap mode from "free" to a
+ // specific range. So, for Chrome on Linux we started syncing the
+ // wrap limit to the user-specified margin width.
+ //
+ // Unfortunately, this caused another problem whereby the ace
+ // horizontal scrollbar would show up over the top of the editor
+ // and the console (bug #3428). We tried reverting the fix to
+ // #3207 and sure enough this solved the horizontal scrollbar
+ // problem _and_ no longer froze Chrome (so perhaps there was a
+ // bug in Chrome).
+ //
+ // In the meantime we added user pref to soft wrap to the margin
+ // column, essentially allowing users to opt-in to the behavior
+ // we used to fix the bug. So the net is:
+ //
+ // (1) To fix the horizontal scrollbar problem we revereted
+ // the wrap mode behavior we added from Chrome (under the
+ // assumption that the issue has been fixed in Chrome)
+ //
+ // (2) We added another check for desktop mode (since we saw
+ // the problem in both Chrome and Safari) to prevent the
+ // application of the problematic wrap mode setting.
+ //
+ // Perhaps there is an ace issue here as well, so the next time
+ // we sync to Ace tip we should see if we can bring back the
+ // wrapping option for Chrome (note the repro for this
+ // is having a soft-wrapping source document in the editor that
+ // exceed the horizontal threshold)
+
+ // NOTE: we no longer do this at all since we observed the
+ // scollbar problem on desktop as well
+ }
+
+ private void updateKeyboardHandlers()
+ {
+ // create a keyboard previewer for our special hooks
+ AceKeyboardPreviewer previewer = new AceKeyboardPreviewer(
+ completionManager_);
+
+ // reset keyboard handlers
+ widget_.getEditor().setKeyboardHandler(null);
+
+ // if required add vim handlers (to main editor and our previewer)
+ if (useVimMode_)
+ {
+ widget_.getEditor().addKeyboardHandler(KeyboardHandler.vim());
+ previewer.addHandler(
+ new AceVimCommandHandler(new CommandWithArg<Boolean>() {
+ @Override
+ public void execute(Boolean arg)
+ {
+ fireEvent(new FindRequestedEvent(arg));
+ }
+ }));
+ }
+
+ // add the previewer's handler
+ widget_.getEditor().addKeyboardHandler(previewer.getKeyboardHandler());
+ }
+
+ public String getCode()
+ {
+ return getSession().getValue();
+ }
+
+ public void setCode(String code, boolean preserveCursorPosition)
+ {
+ // Calling setCode("", false) while the editor contains multiple lines of
+ // content causes bug 2928: Flickering console when typing. Empirically,
+ // first setting code to a single line of content and then clearing it,
+ // seems to correct this problem.
+ if (StringUtil.isNullOrEmpty(code))
+ doSetCode(" ", preserveCursorPosition);
+
+ doSetCode(code, preserveCursorPosition);
+ }
+
+ private void doSetCode(String code, boolean preserveCursorPosition)
+ {
+ // Filter out Escape characters that might have snuck in from an old
+ // bug in 0.95. We can choose to remove this when 0.95 ships, hopefully
+ // any documents that would be affected by this will be gone by then.
+ code = code.replaceAll("\u001B", "");
+
+ final AceEditorNative ed = widget_.getEditor();
+
+ if (preserveCursorPosition)
+ {
+ final Position cursorPos;
+ final int scrollTop, scrollLeft;
+
+ cursorPos = ed.getSession().getSelection().getCursor();
+ scrollTop = ed.getRenderer().getScrollTop();
+ scrollLeft = ed.getRenderer().getScrollLeft();
+
+ // Setting the value directly on the document prevents undo/redo
+ // stack from being blown away
+ widget_.getEditor().getSession().getDocument().setValue(code);
+
+ ed.getSession().getSelection().moveCursorTo(cursorPos.getRow(),
+ cursorPos.getColumn(),
+ false);
+ ed.getRenderer().scrollToY(scrollTop);
+ ed.getRenderer().scrollToX(scrollLeft);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ ed.getRenderer().scrollToY(scrollTop);
+ ed.getRenderer().scrollToX(scrollLeft);
+ }
+ });
+ }
+ else
+ {
+ ed.getSession().setValue(code);
+ ed.getSession().getSelection().moveCursorTo(0, 0, false);
+ }
+ }
+
+ public int getScrollLeft()
+ {
+ return widget_.getEditor().getRenderer().getScrollLeft();
+ }
+
+ public void scrollToX(int x)
+ {
+ widget_.getEditor().getRenderer().scrollToX(x);
+ }
+
+ public int getScrollTop()
+ {
+ return widget_.getEditor().getRenderer().getScrollTop();
+ }
+
+ public void scrollToY(int y)
+ {
+ widget_.getEditor().getRenderer().scrollToY(y);
+ }
+
+ public void insertCode(String code)
+ {
+ insertCode(code, false);
+ }
+
+ public void insertCode(String code, boolean blockMode)
+ {
+ // TODO: implement block mode
+ getSession().replace(
+ getSession().getSelection().getRange(), code);
+ }
+
+ public String getCode(Position start, Position end)
+ {
+ return getSession().getTextRange(Range.fromPoints(start, end));
+ }
+
+
+ @Override
+ public InputEditorSelection search(String needle,
+ boolean backwards,
+ boolean wrap,
+ boolean caseSensitive,
+ boolean wholeWord,
+ Position start,
+ Range range,
+ boolean regexpMode)
+ {
+ Search search = Search.create(needle,
+ backwards,
+ wrap,
+ caseSensitive,
+ wholeWord,
+ start,
+ range,
+ regexpMode);
+
+ Range resultRange = search.find(getSession());
+ if (resultRange != null)
+ {
+ return createSelection(resultRange.getStart(), resultRange.getEnd());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public void insertCode(InputEditorPosition position, String content)
+ {
+ getSession().insert(selectionToPosition(position), content);
+ }
+
+ @Override
+ public String getCode(InputEditorSelection selection)
+ {
+ return getCode(((AceInputEditorPosition)selection.getStart()).getValue(),
+ ((AceInputEditorPosition)selection.getEnd()).getValue());
+ }
+
+ public void focus()
+ {
+ widget_.getEditor().focus();
+ }
+
+ public boolean isFocused()
+ {
+ return widget_.getEditor().isFocused();
+ }
+
+
+ public void codeCompletion()
+ {
+ completionManager_.codeCompletion();
+ }
+
+ public void goToHelp()
+ {
+ completionManager_.goToHelp();
+ }
+
+ public void goToFunctionDefinition()
+ {
+ completionManager_.goToFunctionDefinition();
+ }
+
+ class PrintIFrame extends DynamicIFrame
+ {
+ public PrintIFrame(String code, double fontSize)
+ {
+ code_ = code;
+ fontSize_ = fontSize;
+
+ getElement().getStyle().setPosition(com.google.gwt.dom.client.Style.Position.ABSOLUTE);
+ getElement().getStyle().setLeft(-5000, Unit.PX);
+ }
+
+ @Override
+ protected void onFrameLoaded()
+ {
+ Document doc = getDocument();
+ PreElement pre = doc.createPreElement();
+ pre.setInnerText(code_);
+ pre.getStyle().setProperty("whiteSpace", "pre-wrap");
+ pre.getStyle().setFontSize(fontSize_, Unit.PT);
+ doc.getBody().appendChild(pre);
+
+ getWindow().print();
+
+ // Bug 1224: ace: print from source causes inability to reconnect
+ // This was caused by the iframe being removed from the document too
+ // quickly after the print job was sent. As a result, attempting to
+ // navigate away from the page at any point afterwards would result
+ // in the error "Document cannot change while printing or in Print
+ // Preview". The only thing you could do is close the browser tab.
+ // By inserting a 5-minute delay hopefully Firefox would be done with
+ // whatever print related operations are important.
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ PrintIFrame.this.removeFromParent();
+ return false;
+ }
+ }, 1000 * 60 * 5);
+ }
+
+ private final String code_;
+ private final double fontSize_;
+ }
+
+ public void print()
+ {
+ PrintIFrame printIFrame = new PrintIFrame(
+ getCode(),
+ RStudioGinjector.INSTANCE.getUIPrefs().fontSize().getValue());
+ RootPanel.get().add(printIFrame);
+ }
+
+ public String getText()
+ {
+ return getSession().getLine(
+ getSession().getSelection().getCursor().getRow());
+ }
+
+ public void setText(String string)
+ {
+ setCode(string, false);
+ getSession().getSelection().moveCursorFileEnd();
+ }
+
+ public boolean hasSelection()
+ {
+ return true;
+ }
+
+ public InputEditorSelection getSelection()
+ {
+ Range selection = getSession().getSelection().getRange();
+ return new InputEditorSelection(
+ new AceInputEditorPosition(getSession(), selection.getStart()),
+ new AceInputEditorPosition(getSession(), selection.getEnd()));
+
+ }
+
+ public String getSelectionValue()
+ {
+ return getSession().getTextRange(
+ getSession().getSelection().getRange());
+ }
+
+ public Position getSelectionStart()
+ {
+ return getSession().getSelection().getRange().getStart();
+ }
+
+ public Position getSelectionEnd()
+ {
+ return getSession().getSelection().getRange().getEnd();
+ }
+
+ @Override
+ public Range getSelectionRange()
+ {
+ return Range.fromPoints(getSelectionStart(), getSelectionEnd());
+ }
+
+ @Override
+ public void setSelectionRange(Range range)
+ {
+ getSession().getSelection().setSelectionRange(range);
+ }
+
+ public int getLength(int row)
+ {
+ return getSession().getDocument().getLine(row).length();
+ }
+
+ public int getRowCount()
+ {
+ return getSession().getDocument().getLength();
+ }
+
+ public String getLine(int row)
+ {
+ return getSession().getLine(row);
+ }
+
+ @Override
+ public InputEditorSelection createSelection(Position pos1, Position pos2)
+ {
+ return new InputEditorSelection(
+ new AceInputEditorPosition(getSession(), pos1),
+ new AceInputEditorPosition(getSession(), pos2));
+ }
+
+ @Override
+ public Position selectionToPosition(InputEditorPosition pos)
+ {
+ // HACK: This cast is gross, InputEditorPosition should just become
+ // AceInputEditorPosition
+ return Position.create((Integer) pos.getLine(), pos.getPosition());
+ }
+
+ @Override
+ public Iterable<Range> getWords(TokenPredicate tokenPredicate,
+ CharClassifier charClassifier,
+ Position start,
+ Position end)
+ {
+ return new WordIterable(getSession(),
+ tokenPredicate,
+ charClassifier,
+ start,
+ end);
+ }
+
+ @Override
+ public String getTextForRange(Range range)
+ {
+ return getSession().getTextRange(range);
+ }
+
+ @Override
+ public Anchor createAnchor(Position pos)
+ {
+ return Anchor.createAnchor(getSession().getDocument(),
+ pos.getRow(),
+ pos.getColumn());
+ }
+
+ private void fixVerticalOffsetBug()
+ {
+ widget_.getEditor().getRenderer().fixVerticalOffsetBug();
+ }
+
+ @Override
+ public String debug_getDocumentDump()
+ {
+ return widget_.getEditor().getSession().getDocument().getDocumentDump();
+ }
+
+ @Override
+ public void debug_setSessionValueDirectly(String s)
+ {
+ widget_.getEditor().getSession().setValue(s);
+ }
+
+ public void setSelection(InputEditorSelection selection)
+ {
+ AceInputEditorPosition start = (AceInputEditorPosition)selection.getStart();
+ AceInputEditorPosition end = (AceInputEditorPosition)selection.getEnd();
+ getSession().getSelection().setSelectionRange(Range.fromPoints(
+ start.getValue(), end.getValue()));
+ }
+
+ public Rectangle getCursorBounds()
+ {
+ Range range = getSession().getSelection().getRange();
+ Renderer renderer = widget_.getEditor().getRenderer();
+ ScreenCoordinates start = renderer.textToScreenCoordinates(
+ range.getStart().getRow(),
+ range.getStart().getColumn());
+ ScreenCoordinates end = renderer.textToScreenCoordinates(
+ range.getEnd().getRow(),
+ range.getEnd().getColumn());
+ return new Rectangle(start.getPageX(),
+ start.getPageY(),
+ end.getPageX() - start.getPageX(),
+ renderer.getLineHeight());
+ }
+
+ public Rectangle getPositionBounds(InputEditorPosition position)
+ {
+ Renderer renderer = widget_.getEditor().getRenderer();
+
+ Position pos = ((AceInputEditorPosition) position).getValue();
+
+ ScreenCoordinates start = renderer.textToScreenCoordinates(
+ pos.getRow(),
+ pos.getColumn());
+
+ return new Rectangle(start.getPageX(), start.getPageY(),
+ (int) Math.round(renderer.getCharacterWidth()),
+ (int) (renderer.getLineHeight() * 0.8));
+ }
+
+ public Rectangle getBounds()
+ {
+ return new Rectangle(
+ widget_.getAbsoluteLeft(),
+ widget_.getAbsoluteTop(),
+ widget_.getOffsetWidth(),
+ widget_.getOffsetHeight());
+ }
+
+ public void setFocus(boolean focused)
+ {
+ if (focused)
+ widget_.getEditor().focus();
+ else
+ widget_.getEditor().blur();
+ }
+
+ public String replaceSelection(String value, boolean collapseSelection)
+ {
+ Selection selection = getSession().getSelection();
+ String oldValue = getSession().getTextRange(selection.getRange());
+
+ replaceSelection(value);
+
+ if (collapseSelection)
+ {
+ collapseSelection(false);
+ }
+
+ return oldValue;
+ }
+
+ public boolean isSelectionCollapsed()
+ {
+ return getSession().getSelection().isEmpty();
+ }
+
+ public boolean isCursorAtEnd()
+ {
+ int lastRow = getRowCount() - 1;
+ Position cursorPos = getCursorPosition();
+ return cursorPos.compareTo(Position.create(lastRow,
+ getLength(lastRow))) == 0;
+ }
+
+ public void clear()
+ {
+ setCode("", false);
+ }
+
+ public void collapseSelection(boolean collapseToStart)
+ {
+ Selection selection = getSession().getSelection();
+ Range rng = selection.getRange();
+ Position pos = collapseToStart ? rng.getStart() : rng.getEnd();
+ selection.setSelectionRange(Range.fromPoints(pos, pos));
+ }
+
+ public InputEditorSelection getStart()
+ {
+ return new InputEditorSelection(
+ new AceInputEditorPosition(getSession(), Position.create(0, 0)));
+ }
+
+ public InputEditorSelection getEnd()
+ {
+ EditSession session = getSession();
+ int rows = session.getLength();
+ Position end = Position.create(rows, session.getLine(rows).length());
+ return new InputEditorSelection(new AceInputEditorPosition(session, end));
+ }
+
+ public String getCurrentLine()
+ {
+ int row = getSession().getSelection().getRange().getStart().getRow();
+ return getSession().getLine(row);
+ }
+
+ public int getCurrentLineNum()
+ {
+ Position pos = getCursorPosition();
+ return getSession().documentToScreenRow(pos);
+ }
+
+ public int getCurrentLineCount()
+ {
+ return getSession().getScreenLength();
+ }
+
+ @Override
+ public String getLanguageMode(Position position)
+ {
+ return getSession().getMode().getLanguageMode(position);
+ }
+
+ public void replaceCode(String code)
+ {
+ int endRow, endCol;
+
+ endRow = getSession().getLength() - 1;
+ if (endRow < 0)
+ {
+ endRow = 0;
+ endCol = 0;
+ }
+ else
+ {
+ endCol = getSession().getLine(endRow).length();
+ }
+
+ Range range = Range.fromPoints(Position.create(0, 0),
+ Position.create(endRow, endCol));
+ getSession().replace(range, code);
+ }
+
+ public void replaceSelection(String code)
+ {
+ Range selRange = getSession().getSelection().getRange();
+ Position position = getSession().replace(selRange, code);
+ Range range = Range.fromPoints(selRange.getStart(), position);
+ getSession().getSelection().setSelectionRange(range);
+ }
+
+ public boolean moveSelectionToNextLine(boolean skipBlankLines)
+ {
+ int curRow = getSession().getSelection().getCursor().getRow();
+ while (++curRow < getSession().getLength())
+ {
+ String line = getSession().getLine(curRow);
+ Pattern pattern = Pattern.create("[^\\s]");
+ Match match = pattern.match(line, 0);
+ if (skipBlankLines && match == null)
+ continue;
+ int col = (match != null) ? match.getIndex() : 0;
+ getSession().getSelection().moveCursorTo(curRow, col, false);
+ getSession().unfold(curRow, true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean moveSelectionToBlankLine()
+ {
+ int curRow = getSession().getSelection().getCursor().getRow();
+
+ // if the current row is the last row then insert a new row
+ if (curRow == (getSession().getLength() - 1))
+ {
+ int rowLen = getSession().getLine(curRow).length();
+ getSession().getSelection().moveCursorTo(curRow, rowLen, false);
+ insertCode("\n");
+ }
+
+ while (curRow < getSession().getLength())
+ {
+ String line = getSession().getLine(curRow);
+ if (line.length() == 0)
+ {
+ getSession().getSelection().moveCursorTo(curRow, 0, false);
+ getSession().unfold(curRow, true);
+ return true;
+ }
+
+ curRow++;
+ }
+ return false;
+ }
+
+ @Override
+ public void reindent()
+ {
+ boolean emptySelection = getSelection().isEmpty();
+ getSession().reindent(getSession().getSelection().getRange());
+ if (emptySelection)
+ moveSelectionToNextLine(false);
+ }
+
+ @Override
+ public void toggleCommentLines()
+ {
+ widget_.getEditor().toggleCommentLines();
+ }
+
+ public ChangeTracker getChangeTracker()
+ {
+ return new AceEditorChangeTracker();
+ }
+
+ public AnchoredSelection createAnchoredSelection(Position startPos,
+ Position endPos)
+ {
+ Anchor start = Anchor.createAnchor(getSession().getDocument(),
+ startPos.getRow(),
+ startPos.getColumn());
+ Anchor end = Anchor.createAnchor(getSession().getDocument(),
+ endPos.getRow(),
+ endPos.getColumn());
+ return new AnchoredSelectionImpl(start, end);
+ }
+
+ public void fitSelectionToLines(boolean expand)
+ {
+ Range range = getSession().getSelection().getRange();
+ Position start = range.getStart();
+ Position newStart = start;
+
+ if (start.getColumn() > 0)
+ {
+ if (expand)
+ {
+ newStart = Position.create(start.getRow(), 0);
+ }
+ else
+ {
+ String firstLine = getSession().getLine(start.getRow());
+ if (firstLine.substring(0, start.getColumn()).trim().length() == 0)
+ newStart = Position.create(start.getRow(), 0);
+ }
+ }
+
+ Position end = range.getEnd();
+ Position newEnd = end;
+ if (expand)
+ {
+ int endRow = end.getRow();
+ if (endRow == newStart.getRow() || end.getColumn() > 0)
+ {
+ // If selection ends at the start of a line, keep the selection
+ // there--unless that means less than one line will be selected
+ // in total.
+ newEnd = Position.create(
+ endRow, getSession().getLine(endRow).length());
+ }
+ }
+ else
+ {
+ while (newEnd.getRow() != newStart.getRow())
+ {
+ String line = getSession().getLine(newEnd.getRow());
+ if (line.substring(0, newEnd.getColumn()).trim().length() != 0)
+ break;
+
+ int prevRow = newEnd.getRow() - 1;
+ int len = getSession().getLine(prevRow).length();
+ newEnd = Position.create(prevRow, len);
+ }
+ }
+
+ getSession().getSelection().setSelectionRange(
+ Range.fromPoints(newStart, newEnd));
+ }
+
+ public int getSelectionOffset(boolean start)
+ {
+ Range range = getSession().getSelection().getRange();
+ if (start)
+ return range.getStart().getColumn();
+ else
+ return range.getEnd().getColumn();
+ }
+
+ public void onActivate()
+ {
+ Scheduler.get().scheduleFinally(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ widget_.onResize();
+ widget_.onActivate();
+
+ return false;
+ }
+ });
+ }
+
+ public void onVisibilityChanged(boolean visible)
+ {
+ if (visible)
+ widget_.getEditor().getRenderer().updateFontSize();
+ }
+
+ public void setHighlightSelectedLine(boolean on)
+ {
+ widget_.getEditor().setHighlightActiveLine(on);
+ }
+
+ public void setHighlightSelectedWord(boolean on)
+ {
+ widget_.getEditor().setHighlightSelectedWord(on);
+ }
+
+ public void setShowLineNumbers(boolean on)
+ {
+ widget_.getEditor().getRenderer().setShowGutter(on);
+ }
+
+ public void setUseSoftTabs(boolean on)
+ {
+ getSession().setUseSoftTabs(on);
+ }
+
+ /**
+ * Warning: This will be overridden whenever the file type is set
+ */
+ public void setUseWrapMode(boolean useWrapMode)
+ {
+ getSession().setUseWrapMode(useWrapMode);
+ }
+
+ public void setTabSize(int tabSize)
+ {
+ getSession().setTabSize(tabSize);
+ }
+
+ public void setShowInvisibles(boolean show)
+ {
+ widget_.getEditor().getRenderer().setShowInvisibles(show);
+ }
+
+ public void setShowIndentGuides(boolean show)
+ {
+ widget_.getEditor().getRenderer().setShowIndentGuides(show);
+ }
+
+ public void setBlinkingCursor(boolean blinking)
+ {
+ widget_.getEditor().getRenderer().setBlinkingCursor(blinking);
+ }
+
+ public void setShowPrintMargin(boolean on)
+ {
+ widget_.getEditor().getRenderer().setShowPrintMargin(on);
+ }
+
+ @Override
+ public void setUseVimMode(boolean use)
+ {
+ // no-op if the editor is read-only (since vim mode doesn't
+ // work for read-only ace instances)
+ if (widget_.getEditor().getReadOnly())
+ return;
+
+ useVimMode_ = use;
+ updateKeyboardHandlers();
+ }
+
+ public void setPadding(int padding)
+ {
+ widget_.getEditor().getRenderer().setPadding(padding);
+ }
+
+ public void setPrintMarginColumn(int column)
+ {
+ widget_.getEditor().getRenderer().setPrintMarginColumn(column);
+ syncWrapLimit();
+ }
+
+ @Override
+ public JsArray<AceFold> getFolds()
+ {
+ return getSession().getAllFolds();
+ }
+
+ @Override
+ public void addFold(Range range)
+ {
+ getSession().addFold("...", range);
+ }
+
+ @Override
+ public void addFoldFromRow(int row)
+ {
+ FoldingRules foldingRules = getSession().getMode().getFoldingRules();
+ if (foldingRules == null)
+ return;
+ Range range = foldingRules.getFoldWidgetRange(getSession(),
+ "markbegin",
+ row);
+
+ if (range != null)
+ addFold(range);
+ }
+
+ @Override
+ public void unfold(AceFold fold)
+ {
+ getSession().unfold(Range.fromPoints(fold.getStart(), fold.getEnd()),
+ false);
+ }
+
+ @Override
+ public void unfold(int row)
+ {
+ getSession().unfold(row, false);
+ }
+
+ @Override
+ public void unfold(Range range)
+ {
+ getSession().unfold(range, false);
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ widget_.getEditor().setReadOnly(readOnly);
+ }
+
+ public HandlerRegistration addCursorChangedHandler(final CursorChangedHandler handler)
+ {
+ return widget_.addCursorChangedHandler(handler);
+ }
+
+ public HandlerRegistration addEditorFocusHandler(FocusHandler handler)
+ {
+ return widget_.addFocusHandler(handler);
+ }
+
+ public Scope getCurrentScope()
+ {
+ return getSession().getMode().getCodeModel().getCurrentScope(
+ getCursorPosition());
+ }
+
+ public Scope getCurrentChunk()
+ {
+ return getCurrentChunk(getCursorPosition());
+ }
+
+ @Override
+ public Scope getCurrentChunk(Position position)
+ {
+ return getSession().getMode().getCodeModel().getCurrentChunk(position);
+ }
+
+ @Override
+ public Scope getCurrentFunction()
+ {
+ return getFunctionAtPosition(getCursorPosition());
+ }
+
+ @Override
+ public Scope getFunctionAtPosition(Position position)
+ {
+ return getSession().getMode().getCodeModel().getCurrentFunction(
+ position);
+ }
+
+ @Override
+ public Scope getCurrentSection()
+ {
+ return getSectionAtPosition(getCursorPosition());
+ }
+
+ @Override
+ public Scope getSectionAtPosition(Position position)
+ {
+ return getSession().getMode().getCodeModel().getCurrentSection(position);
+ }
+
+ public Position getCursorPosition()
+ {
+ return getSession().getSelection().getCursor();
+ }
+
+ public void setCursorPosition(Position position)
+ {
+ getSession().getSelection().setSelectionRange(
+ Range.fromPoints(position, position));
+ }
+
+ @Override
+ public void moveCursorNearTop(int rowOffset)
+ {
+ int screenRow = getSession().documentToScreenRow(getCursorPosition());
+ widget_.getEditor().scrollToRow(Math.max(0, screenRow - rowOffset));
+ }
+
+ @Override
+ public void moveCursorNearTop()
+ {
+ moveCursorNearTop(7);
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ int screenRow = getSession().documentToScreenRow(getCursorPosition());
+ if (!widget_.getEditor().isRowFullyVisible(screenRow))
+ moveCursorNearTop();
+ }
+
+ public void scrollToBottom()
+ {
+ SourcePosition pos = SourcePosition.create(getCurrentLineCount() - 1, 0);
+ navigate(pos, false);
+ }
+
+ public void revealRange(Range range, boolean animate)
+ {
+ widget_.getEditor().revealRange(range, animate);
+ }
+
+ public boolean hasScopeTree()
+ {
+ return getSession().getMode().getCodeModel().hasScopes();
+ }
+
+ public JsArray<Scope> getScopeTree()
+ {
+ return getSession().getMode().getCodeModel().getScopeTree();
+ }
+
+ @Override
+ public InsertChunkInfo getInsertChunkInfo()
+ {
+ return getSession().getMode().getInsertChunkInfo();
+ }
+
+ @Override
+ public void foldAll()
+ {
+ getSession().foldAll();
+ }
+
+ @Override
+ public void unfoldAll()
+ {
+ getSession().unfoldAll();
+ }
+
+ @Override
+ public void toggleFold()
+ {
+ getSession().toggleFold();
+ }
+
+ @Override
+ public void jumpToMatching()
+ {
+ widget_.getEditor().jumpToMatching();
+ }
+
+ @Override
+ public SourcePosition findFunctionPositionFromCursor(String functionName)
+ {
+ Scope func =
+ getSession().getMode().getCodeModel().findFunctionDefinitionFromUsage(
+ getCursorPosition(),
+ functionName);
+ if (func != null)
+ {
+ Position position = func.getPreamble();
+ return SourcePosition.create(position.getRow(), position.getColumn());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public void recordCurrentNavigationPosition()
+ {
+ fireRecordNavigationPosition(getCursorPosition());
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent)
+ {
+ navigateToPosition(position, recordCurrent, false);
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent,
+ boolean highlightLine)
+ {
+ if (recordCurrent)
+ recordCurrentNavigationPosition();
+
+ navigate(position, true, highlightLine);
+ }
+
+ @Override
+ public void restorePosition(SourcePosition position)
+ {
+ navigate(position, false);
+ }
+
+ @Override
+ public boolean isAtSourceRow(SourcePosition position)
+ {
+ Position currPos = getCursorPosition();
+ return currPos.getRow() == position.getRow();
+ }
+
+ @Override
+ public void highlightDebugLocation(SourcePosition startPosition,
+ SourcePosition endPosition,
+ boolean executing)
+ {
+ int firstRow = widget_.getEditor().getFirstVisibleRow();
+ int lastRow = widget_.getEditor().getLastVisibleRow();
+
+ // if the expression is large, let's just try to land in the middle
+ int debugRow = (int) Math.floor(startPosition.getRow() + (
+ endPosition.getRow() - startPosition.getRow())/2);
+
+ // if the row at which the debugging occurs is inside a fold, unfold it
+ getSession().unfold(debugRow, true);
+
+ // if the line to be debugged is past or near the edges of the screen,
+ // scroll it into view. allow some lines of context.
+ if (debugRow <= (firstRow + DEBUG_CONTEXT_LINES) ||
+ debugRow >= (lastRow - DEBUG_CONTEXT_LINES))
+ {
+ widget_.getEditor().scrollToLine(debugRow, true);
+ }
+
+ applyDebugLineHighlight(
+ startPosition.asPosition(),
+ endPosition.asPosition(),
+ executing);
+ }
+
+ @Override
+ public void endDebugHighlighting()
+ {
+ clearDebugLineHighlight();
+ }
+
+ @Override
+ public HandlerRegistration addBreakpointSetHandler(
+ BreakpointSetEvent.Handler handler)
+ {
+ return widget_.addBreakpointSetHandler(handler);
+ }
+
+ @Override
+ public HandlerRegistration addBreakpointMoveHandler(
+ BreakpointMoveEvent.Handler handler)
+ {
+ return widget_.addBreakpointMoveHandler(handler);
+ }
+
+ @Override
+ public void addOrUpdateBreakpoint(Breakpoint breakpoint)
+ {
+ widget_.addOrUpdateBreakpoint(breakpoint);
+ }
+
+ @Override
+ public void removeBreakpoint(Breakpoint breakpoint)
+ {
+ widget_.removeBreakpoint(breakpoint);
+ }
+
+ @Override
+ public void toggleBreakpointAtCursor()
+ {
+ widget_.toggleBreakpointAtCursor();
+ }
+
+ @Override
+ public void removeAllBreakpoints()
+ {
+ widget_.removeAllBreakpoints();
+ }
+
+ @Override
+ public boolean hasBreakpoints()
+ {
+ return widget_.hasBreakpoints();
+ }
+
+ private void navigate(SourcePosition srcPosition, boolean addToHistory)
+ {
+ navigate(srcPosition, addToHistory, false);
+ }
+
+ private void navigate(SourcePosition srcPosition,
+ boolean addToHistory,
+ boolean highlightLine)
+ {
+ // set cursor to function line
+ Position position = Position.create(srcPosition.getRow(),
+ srcPosition.getColumn());
+ setCursorPosition(position);
+
+ // skip whitespace if necessary
+ if (srcPosition.getColumn() == 0)
+ {
+ int curRow = getSession().getSelection().getCursor().getRow();
+ String line = getSession().getLine(curRow);
+ int funStart = line.indexOf(line.trim());
+ position = Position.create(curRow, funStart);
+ setCursorPosition(position);
+ }
+
+ // scroll as necessary
+ if (srcPosition.getScrollPosition() != -1)
+ scrollToY(srcPosition.getScrollPosition());
+ else
+ moveCursorNearTop();
+
+ // set focus
+ focus();
+
+ if (highlightLine)
+ applyLineHighlight(position.getRow());
+
+ // add to navigation history if requested and our current mode
+ // supports history navigation
+ if (addToHistory)
+ fireRecordNavigationPosition(position);
+ }
+
+ private void fireRecordNavigationPosition(Position pos)
+ {
+ SourcePosition srcPos = SourcePosition.create(pos.getRow(),
+ pos.getColumn());
+ fireEvent(new RecordNavigationPositionEvent(srcPos));
+ }
+
+ @Override
+ public HandlerRegistration addRecordNavigationPositionHandler(
+ RecordNavigationPositionHandler handler)
+ {
+ return handlers_.addHandler(RecordNavigationPositionEvent.TYPE, handler);
+ }
+
+ @Override
+ public HandlerRegistration addCommandClickHandler(
+ CommandClickEvent.Handler handler)
+ {
+ return handlers_.addHandler(CommandClickEvent.TYPE, handler);
+ }
+
+ @Override
+ public HandlerRegistration addFindRequestedHandler(
+ FindRequestedEvent.Handler handler)
+ {
+ return handlers_.addHandler(FindRequestedEvent.TYPE, handler);
+ }
+
+ public void setFontSize(double size)
+ {
+ // No change needed--the AceEditorWidget uses the "normalSize" style
+ // However, we do need to resize the gutter
+ widget_.getEditor().getRenderer().updateFontSize();
+ widget_.forceResize();
+ }
+
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<Void> handler)
+ {
+ return handlers_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ public HandlerRegistration addFoldChangeHandler(
+ FoldChangeEvent.Handler handler)
+ {
+ return handlers_.addHandler(FoldChangeEvent.TYPE, handler);
+ }
+
+ public HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler)
+ {
+ return widget_.addCapturingKeyDownHandler(handler);
+ }
+
+ public HandlerRegistration addCapturingKeyPressHandler(KeyPressHandler handler)
+ {
+ return widget_.addCapturingKeyPressHandler(handler);
+ }
+
+ public HandlerRegistration addCapturingKeyUpHandler(KeyUpHandler handler)
+ {
+ return widget_.addCapturingKeyUpHandler(handler);
+ }
+
+ public HandlerRegistration addUndoRedoHandler(UndoRedoHandler handler)
+ {
+ return widget_.addUndoRedoHandler(handler);
+ }
+
+ public HandlerRegistration addPasteHandler(PasteEvent.Handler handler)
+ {
+ return widget_.addPasteHandler(handler);
+ }
+
+ public HandlerRegistration addAceClickHandler(Handler handler)
+ {
+ return widget_.addAceClickHandler(handler);
+ }
+
+ public JavaScriptObject getCleanStateToken()
+ {
+ return getSession().getUndoManager().peek();
+ }
+
+ public boolean checkCleanStateToken(JavaScriptObject token)
+ {
+ JavaScriptObject other = getSession().getUndoManager().peek();
+ if (token == null ^ other == null)
+ return false;
+ return token == null || other.equals(token);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ public Widget asWidget()
+ {
+ return widget_;
+ }
+
+ public EditSession getSession()
+ {
+ return widget_.getEditor().getSession();
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return widget_.addBlurHandler(handler);
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return widget_.addClickHandler(handler);
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return widget_.addFocusHandler(handler);
+ }
+
+ public Widget getWidget()
+ {
+ return widget_;
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return widget_.addKeyDownHandler(handler);
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
+ {
+ return widget_.addKeyPressHandler(handler);
+ }
+
+ public void autoHeight()
+ {
+ widget_.autoHeight();
+ }
+
+ public void forceCursorChange()
+ {
+ widget_.forceCursorChange();
+ }
+
+ public void scrollToCursor(ScrollPanel scrollPanel,
+ int paddingVert,
+ int paddingHoriz)
+ {
+ DomUtils.ensureVisibleVert(
+ scrollPanel.getElement(),
+ widget_.getEditor().getRenderer().getCursorElement(),
+ paddingVert);
+ DomUtils.ensureVisibleHoriz(
+ scrollPanel.getElement(),
+ widget_.getEditor().getRenderer().getCursorElement(),
+ paddingHoriz, paddingHoriz,
+ false);
+ }
+
+ public void forceImmediateRender()
+ {
+ widget_.getEditor().getRenderer().forceImmediateRender();
+ }
+
+ public void setNewLineMode(NewLineMode mode)
+ {
+ getSession().setNewLineMode(mode.getType());
+ }
+
+ public boolean isPasswordMode()
+ {
+ return passwordMode_;
+ }
+
+ public void setPasswordMode(boolean passwordMode)
+ {
+ passwordMode_ = passwordMode;
+ widget_.getEditor().getRenderer().setPasswordMode(passwordMode);
+ }
+
+ public void setDisableOverwrite(boolean disableOverwrite)
+ {
+ getSession().setDisableOverwrite(disableOverwrite);
+ }
+
+ private Integer createLineHighlightMarker(int line, String style)
+ {
+ return createRangeHighlightMarker(Position.create(line, 0),
+ Position.create(line+1, 0),
+ style);
+ }
+
+ private Integer createRangeHighlightMarker(
+ Position start,
+ Position end,
+ String style)
+ {
+ Range range = Range.fromPoints(start, end);
+ return getSession().addMarker(range, style, "text", false);
+ }
+
+
+ private void applyLineHighlight(int line)
+ {
+ clearLineHighlight();
+
+ if (!widget_.getEditor().getHighlightActiveLine())
+ {
+ lineHighlightMarkerId_ = createLineHighlightMarker(line,
+ "ace_find_line");
+ }
+ }
+
+ private void clearLineHighlight()
+ {
+ if (lineHighlightMarkerId_ != null)
+ {
+ getSession().removeMarker(lineHighlightMarkerId_);
+ lineHighlightMarkerId_ = null;
+ }
+ }
+
+ private void applyDebugLineHighlight(
+ Position startPos,
+ Position endPos,
+ boolean executing)
+ {
+ clearDebugLineHighlight();
+ lineDebugMarkerId_ = createRangeHighlightMarker(
+ startPos, endPos,
+ "ace_active_debug_line");
+ if (executing)
+ {
+ executionLine_ = startPos.getRow();
+ widget_.getEditor().getRenderer().addGutterDecoration(
+ executionLine_,
+ "ace_executing-line");
+ }
+ }
+
+ private void clearDebugLineHighlight()
+ {
+ if (lineDebugMarkerId_ != null)
+ {
+ getSession().removeMarker(lineDebugMarkerId_);
+ lineDebugMarkerId_ = null;
+ }
+ if (executionLine_ != null)
+ {
+ widget_.getEditor().getRenderer().removeGutterDecoration(
+ executionLine_,
+ "ace_executing-line");
+ executionLine_ = null;
+ }
+ }
+
+ private static final int DEBUG_CONTEXT_LINES = 2;
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ private final AceEditorWidget widget_;
+ private CompletionManager completionManager_;
+ private CodeToolsServerOperations server_;
+ private TextFileType fileType_;
+ private boolean passwordMode_;
+ private boolean useVimMode_ = false;
+ private RnwCompletionContext rnwContext_;
+ private Integer lineHighlightMarkerId_ = null;
+ private Integer lineDebugMarkerId_ = null;
+ private Integer executionLine_ = null;
+ private static final ExternalJavaScriptLoader aceLoader_ =
+ new ExternalJavaScriptLoader(AceResources.INSTANCE.acejs().getSafeUri().asString());
+ private static final ExternalJavaScriptLoader aceSupportLoader_ =
+ new ExternalJavaScriptLoader(AceResources.INSTANCE.acesupportjs().getSafeUri().asString());
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditorWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditorWidget.java
new file mode 100644
index 0000000..c46a32c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceEditorWidget.java
@@ -0,0 +1,622 @@
+/*
+ * AceEditorWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RequiresResize;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceClickEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceDocumentChangeEventNative;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceMouseEventNative;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.*;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FoldChangeEvent.Handler;
+
+public class AceEditorWidget extends Composite
+ implements RequiresResize,
+ HasValueChangeHandlers<Void>,
+ HasFoldChangeHandlers,
+ HasKeyDownHandlers,
+ HasKeyPressHandlers
+{
+ public AceEditorWidget()
+ {
+ initWidget(new HTML());
+ FontSizer.applyNormalFontSize(this);
+ setSize("100%", "100%");
+
+ capturingHandlers_ = new HandlerManager(this);
+ addEventListener(getElement(), "keydown", capturingHandlers_);
+ addEventListener(getElement(), "keyup", capturingHandlers_);
+ addEventListener(getElement(), "keypress", capturingHandlers_);
+
+ addStyleName("loading");
+
+ editor_ = AceEditorNative.createEditor(getElement());
+ editor_.getRenderer().setHScrollBarAlwaysVisible(false);
+ editor_.setShowPrintMargin(false);
+ editor_.setPrintMarginColumn(0);
+ editor_.setHighlightActiveLine(false);
+ editor_.setHighlightGutterLine(false);
+ editor_.delegateEventsTo(AceEditorWidget.this);
+ editor_.onChange(new CommandWithArg<AceDocumentChangeEventNative>()
+ {
+ public void execute(AceDocumentChangeEventNative changeEvent)
+ {
+ ValueChangeEvent.fire(AceEditorWidget.this, null);
+ updateBreakpoints(changeEvent);
+ }
+
+ });
+ editor_.onChangeFold(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ fireEvent(new FoldChangeEvent());
+ }
+ });
+ editor_.onGutterMouseDown(new CommandWithArg<AceMouseEventNative>()
+ {
+ @Override
+ public void execute(AceMouseEventNative arg)
+ {
+ // make sure the click is actually intended for the gutter
+ com.google.gwt.dom.client.Element targetElement =
+ Element.as(arg.getNativeEvent().getEventTarget());
+ if (targetElement.getClassName().indexOf("ace_gutter-cell") < 0)
+ {
+ return;
+ }
+
+ NativeEvent evt = arg.getNativeEvent();
+
+ // right-clicking shouldn't set a breakpoint
+ if (evt.getButton() != NativeEvent.BUTTON_LEFT)
+ {
+ return;
+ }
+
+ // make sure that the click was in the left half of the element--
+ // clicking on the line number itself (or the gutter near the
+ // text) shouldn't set a breakpoint.
+ if (evt.getClientX() <
+ (targetElement.getAbsoluteLeft() +
+ (targetElement.getClientWidth() / 2)))
+ {
+ toggleBreakpointAtPosition(arg.getDocumentPosition());
+ }
+ }
+ });
+ editor_.getSession().getSelection().addCursorChangeHandler(new CommandWithArg<Position>()
+ {
+ public void execute(Position arg)
+ {
+ AceEditorWidget.this.fireEvent(new CursorChangedEvent(arg));
+ }
+ });
+ AceEditorNative.addEventListener(
+ editor_,
+ "undo",
+ new CommandWithArg<Void>()
+ {
+ public void execute(Void arg)
+ {
+ fireEvent(new UndoRedoEvent(false));
+ }
+ });
+ AceEditorNative.addEventListener(
+ editor_,
+ "redo",
+ new CommandWithArg<Void>()
+ {
+ public void execute(Void arg)
+ {
+ fireEvent(new UndoRedoEvent(true));
+ }
+ });
+ AceEditorNative.addEventListener(
+ editor_,
+ "paste",
+ new CommandWithArg<String>()
+ {
+ public void execute(String text)
+ {
+ fireEvent(new PasteEvent(text));
+ }
+ });
+ AceEditorNative.addEventListener(
+ editor_,
+ "mousedown",
+ new CommandWithArg<AceMouseEventNative>()
+ {
+ @Override
+ public void execute(AceMouseEventNative event)
+ {
+ fireEvent(new AceClickEvent(event));
+ }
+ });
+ }
+
+ public HandlerRegistration addCursorChangedHandler(
+ CursorChangedHandler handler)
+ {
+ return addHandler(handler, CursorChangedEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addFoldChangeHandler(Handler handler)
+ {
+ return addHandler(handler, FoldChangeEvent.TYPE);
+ }
+
+ public HandlerRegistration addBreakpointSetHandler
+ (BreakpointSetEvent.Handler handler)
+ {
+ return addHandler(handler, BreakpointSetEvent.TYPE);
+ }
+
+ public HandlerRegistration addBreakpointMoveHandler
+ (BreakpointMoveEvent.Handler handler)
+ {
+ return addHandler(handler, BreakpointMoveEvent.TYPE);
+ }
+
+ public void toggleBreakpointAtCursor()
+ {
+ Position pos = editor_.getSession().getSelection().getCursor();
+ toggleBreakpointAtPosition(Position.create(pos.getRow(), 0));
+ }
+
+ public AceEditorNative getEditor() {
+ return editor_;
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+
+ editor_.getRenderer().updateFontSize();
+ onResize();
+ fireEvent(new EditorLoadedEvent());
+
+ int delayMs = initToEmptyString_ ? 100 : 500;
+
+ // On Windows desktop sometimes we inexplicably end up at the wrong size
+ // if the editor is being resized while it's loading (such as when a new
+ // document is created while the source pane is hidden)
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ if (isAttached())
+ onResize();
+ removeStyleName("loading");
+ return false;
+ }
+ }, delayMs);
+ }
+
+ public void onResize()
+ {
+ editor_.resize();
+ }
+
+ public void onActivate()
+ {
+ if (editor_ != null)
+ {
+ if (BrowseCap.INSTANCE.aceVerticalScrollBarIssue())
+ editor_.getRenderer().forceScrollbarUpdate();
+ editor_.getRenderer().updateFontSize();
+ editor_.getRenderer().forceImmediateRender();
+ }
+ }
+
+ public void setCode(String code)
+ {
+ code = StringUtil.notNull(code);
+ initToEmptyString_ = code.length() == 0;
+ editor_.getSession().setValue(code);
+ }
+
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Void> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler)
+ {
+ return addHandler(handler, FocusEvent.getType());
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler)
+ {
+ return addHandler(handler, BlurEvent.getType());
+ }
+
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return addDomHandler(handler, ClickEvent.getType());
+ }
+
+ public HandlerRegistration addEditorLoadedHandler(EditorLoadedHandler handler)
+ {
+ return addHandler(handler, EditorLoadedEvent.TYPE);
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return addHandler(handler, KeyDownEvent.getType());
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler)
+ {
+ return addHandler(handler, KeyPressEvent.getType());
+ }
+
+ public HandlerRegistration addCapturingKeyDownHandler(KeyDownHandler handler)
+ {
+ return capturingHandlers_.addHandler(KeyDownEvent.getType(), handler);
+ }
+
+ public HandlerRegistration addCapturingKeyPressHandler(KeyPressHandler handler)
+ {
+ return capturingHandlers_.addHandler(KeyPressEvent.getType(), handler);
+ }
+
+ public HandlerRegistration addCapturingKeyUpHandler(KeyUpHandler handler)
+ {
+ return capturingHandlers_.addHandler(KeyUpEvent.getType(), handler);
+ }
+
+ private static native void addEventListener(Element element,
+ String event,
+ HasHandlers handlers) /*-{
+ var listener = $entry(function(e) {
+ @com.google.gwt.event.dom.client.DomEvent::fireNativeEvent(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/event/shared/HasHandlers;Lcom/google/gwt/dom/client/Element;)(e, handlers, element);
+ });
+ element.addEventListener(event, listener, true);
+
+ }-*/;
+
+ public HandlerRegistration addUndoRedoHandler(UndoRedoHandler handler)
+ {
+ return addHandler(handler, UndoRedoEvent.TYPE);
+ }
+
+ public HandlerRegistration addPasteHandler(PasteEvent.Handler handler)
+ {
+ return addHandler(handler, PasteEvent.TYPE);
+ }
+
+ public HandlerRegistration addAceClickHandler(AceClickEvent.Handler handler)
+ {
+ return addHandler(handler, AceClickEvent.TYPE);
+ }
+
+ public void forceResize()
+ {
+ editor_.getRenderer().onResize(true);
+ }
+
+ public void autoHeight()
+ {
+ editor_.autoHeight();
+ }
+
+ public void forceCursorChange()
+ {
+ editor_.onCursorChange();
+ }
+
+ public void addOrUpdateBreakpoint(Breakpoint breakpoint)
+ {
+ int idx = getBreakpointIdxById(breakpoint.getBreakpointId());
+ if (idx >= 0)
+ {
+ removeBreakpointMarker(breakpoint);
+ breakpoint.setEditorState(breakpoint.getState());
+ breakpoint.setEditorLineNumber(breakpoint.getLineNumber());
+ }
+ else
+ {
+ breakpoints_.add(breakpoint);
+ }
+ placeBreakpointMarker(breakpoint);
+ }
+
+ public void removeBreakpoint(Breakpoint breakpoint)
+ {
+ int idx = getBreakpointIdxById(breakpoint.getBreakpointId());
+ if (idx >= 0)
+ {
+ removeBreakpointMarker(breakpoint);
+ breakpoints_.remove(idx);
+ }
+ }
+
+ public void removeAllBreakpoints()
+ {
+ for (Breakpoint breakpoint: breakpoints_)
+ {
+ removeBreakpointMarker(breakpoint);
+ }
+ breakpoints_.clear();
+ }
+
+ public boolean hasBreakpoints()
+ {
+ return breakpoints_.size() > 0;
+ }
+
+ private void updateBreakpoints(AceDocumentChangeEventNative changeEvent)
+ {
+ // if there are no breakpoints, don't do any work to move them about
+ if (breakpoints_.size() == 0)
+ {
+ return;
+ }
+
+ // see if we need to move any breakpoints around in response to
+ // this change to the document's text
+ String action = changeEvent.getAction();
+ Range range = changeEvent.getRange();
+ Position start = range.getStart();
+ Position end = range.getEnd();
+
+ // if the edit was all on one line or the action didn't change text
+ // in a way that could change lines, we can't have moved anything
+ if (start.getRow() == end.getRow() ||
+ (!action.equals("insertText") &&
+ !action.equals("insertLines") &&
+ !action.equals("removeText") &&
+ !action.equals("removeLines")))
+ {
+ return;
+ }
+
+ int shiftedBy = 0;
+ int shiftStartRow = 0;
+
+ // compute how many rows to shift
+ if (action == "insertText" ||
+ action == "insertLines")
+ {
+ shiftedBy = end.getRow() - start.getRow();
+ }
+ else
+ {
+ shiftedBy = start.getRow() - end.getRow();
+ }
+
+ // compute where to start shifting
+ shiftStartRow = start.getRow() +
+ ((action == "insertText" && start.getColumn() > 0) ?
+ 1 : 0);
+
+ // make a pass through the breakpoints and move them as appropriate:
+ // remove all the breakpoints after the row where the change
+ // happened, and add them back at their new position if they were
+ // not part of a deleted range.
+ ArrayList<Breakpoint> movedBreakpoints = new ArrayList<Breakpoint>();
+
+ for (int idx = 0; idx < breakpoints_.size(); idx++)
+ {
+ Breakpoint breakpoint = breakpoints_.get(idx);
+ int breakpointRow = rowFromLine(breakpoint.getEditorLineNumber());
+ if (breakpointRow >= shiftStartRow)
+ {
+ // remove the breakpoint from its old position
+ movedBreakpoints.add(breakpoint);
+ removeBreakpointMarker(breakpoint);
+ }
+ }
+ for (Breakpoint breakpoint: movedBreakpoints)
+ {
+ // calculate the new position of the breakpoint
+ int oldBreakpointPosition =
+ rowFromLine(breakpoint.getEditorLineNumber());
+ int newBreakpointPosition =
+ oldBreakpointPosition + shiftedBy;
+
+ // add a breakpoint in this new position only if it wasn't
+ // in a deleted range, and if we don't already have a
+ // breakpoint there
+ if (oldBreakpointPosition >= end.getRow() &&
+ !(oldBreakpointPosition == end.getRow() && shiftedBy < 0) &&
+ getBreakpointIdxByLine(lineFromRow(newBreakpointPosition)) < 0)
+ {
+ breakpoint.moveToLineNumber(lineFromRow(newBreakpointPosition));
+ placeBreakpointMarker(breakpoint);
+ fireEvent(new BreakpointMoveEvent(breakpoint.getBreakpointId()));
+ }
+ else
+ {
+ breakpoints_.remove(breakpoint);
+ fireEvent(new BreakpointSetEvent(
+ breakpoint.getEditorLineNumber(),
+ breakpoint.getBreakpointId(),
+ false));
+ }
+ }
+ }
+
+ private void placeBreakpointMarker(Breakpoint breakpoint)
+ {
+ int line = breakpoint.getEditorLineNumber();
+ if (breakpoint.getEditorState() == Breakpoint.STATE_ACTIVE)
+ {
+ editor_.getSession().setBreakpoint(rowFromLine(line));
+ }
+ else if (breakpoint.getEditorState() == Breakpoint.STATE_PROCESSING)
+ {
+ editor_.getRenderer().addGutterDecoration(
+ rowFromLine(line),
+ "ace_pending-breakpoint");
+ }
+ else if (breakpoint.getEditorState() == Breakpoint.STATE_INACTIVE)
+ {
+ editor_.getRenderer().addGutterDecoration(
+ rowFromLine(line),
+ "ace_inactive-breakpoint");
+ }
+ }
+
+ private void removeBreakpointMarker(Breakpoint breakpoint)
+ {
+ int line = breakpoint.getEditorLineNumber();
+ if (breakpoint.getEditorState() == Breakpoint.STATE_ACTIVE)
+ {
+ editor_.getSession().clearBreakpoint(rowFromLine(line));
+ }
+ else if (breakpoint.getEditorState() == Breakpoint.STATE_PROCESSING)
+ {
+ editor_.getRenderer().removeGutterDecoration(
+ rowFromLine(line),
+ "ace_pending-breakpoint");
+ }
+ else if (breakpoint.getEditorState() == Breakpoint.STATE_INACTIVE)
+ {
+ editor_.getRenderer().removeGutterDecoration(
+ rowFromLine(line),
+ "ace_inactive-breakpoint");
+ }
+ }
+
+ private void toggleBreakpointAtPosition(Position pos)
+ {
+ // rows are 0-based, but debug line numbers are 1-based
+ int lineNumber = lineFromRow(pos.getRow());
+ int breakpointIdx = getBreakpointIdxByLine(lineNumber);
+
+ // if there's already a breakpoint on that line, remove it
+ if (breakpointIdx >= 0)
+ {
+ Breakpoint breakpoint = breakpoints_.get(breakpointIdx);
+ removeBreakpointMarker(breakpoint);
+ fireEvent(new BreakpointSetEvent(
+ lineNumber,
+ breakpoint.getBreakpointId(),
+ false));
+ breakpoints_.remove(breakpointIdx);
+ }
+
+ // if there's no breakpoint on that line yet, create a new unset
+ // breakpoint there (the breakpoint manager will pick up the new
+ // breakpoint and attempt to set it on the server)
+ else
+ {
+ try
+ {
+ // move the breakpoint down to the first line that has a
+ // non-whitespace, non-comment token
+ if (editor_.getSession().getMode().getCodeModel() != null)
+ {
+ Position tokenPos = editor_.getSession().getMode().getCodeModel()
+ .findNextSignificantToken(pos);
+ if (tokenPos != null)
+ {
+ lineNumber = lineFromRow(tokenPos.getRow());
+ if (getBreakpointIdxByLine(lineNumber) >= 0)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // if there are no tokens anywhere after the line, don't
+ // set a breakpoint
+ return;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // If we failed at any point to fast-forward to the next line with
+ // a statement, we'll try to set a breakpoint on the line the user
+ // originally clicked.
+ }
+
+ fireEvent(new BreakpointSetEvent(
+ lineNumber,
+ BreakpointSetEvent.UNSET_BREAKPOINT_ID,
+ true));
+ }
+ }
+
+ private int getBreakpointIdxById(int breakpointId)
+ {
+ for (int idx = 0; idx < breakpoints_.size(); idx++)
+ {
+ if (breakpoints_.get(idx).getBreakpointId() == breakpointId)
+ {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ private int getBreakpointIdxByLine(int lineNumber)
+ {
+ for (int idx = 0; idx < breakpoints_.size(); idx++)
+ {
+ if (breakpoints_.get(idx).getEditorLineNumber() == lineNumber)
+ {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ private int lineFromRow(int row)
+ {
+ return row + 1;
+ }
+
+ private int rowFromLine(int line)
+ {
+ return line - 1;
+ }
+
+ private final AceEditorNative editor_;
+ private final HandlerManager capturingHandlers_;
+ private boolean initToEmptyString_ = true;
+ private ArrayList<Breakpoint> breakpoints_ = new ArrayList<Breakpoint>();
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceKeyboardPreviewer.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceKeyboardPreviewer.java
new file mode 100644
index 0000000..48f3685
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceKeyboardPreviewer.java
@@ -0,0 +1,112 @@
+/*
+ * AceKeyboardPreviewer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.NativeEvent;
+
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.KeyboardHandler;
+
+public class AceKeyboardPreviewer
+{
+ public interface Handler
+ {
+ boolean previewKeyDown(JavaScriptObject data, NativeEvent event) ;
+ boolean previewKeyPress(JavaScriptObject data, char charCode) ;
+ }
+
+ public AceKeyboardPreviewer(final CompletionManager completionManager)
+ {
+ addHandler(new Handler() {
+
+ @Override
+ public boolean previewKeyDown(JavaScriptObject data, NativeEvent event)
+ {
+ return completionManager.previewKeyDown(event);
+ }
+
+ @Override
+ public boolean previewKeyPress(JavaScriptObject data, char charCode)
+ {
+ return completionManager.previewKeyPress(charCode);
+ }
+ });
+ }
+
+ public void addHandler(Handler handler)
+ {
+ handlers_.add(handler);
+ }
+
+
+ public native final KeyboardHandler getKeyboardHandler() /*-{
+ var event = $wnd.require("ace/lib/event");
+ var self = this;
+ var noop = {command: "null"};
+ return {
+ handleKeyboard: $entry(function(data, hashId, keyOrText, keyCode, e) {
+ if (hashId != -1 || keyCode) {
+ if (self. at org.rstudio.studio.client.workbench.views.source.editors.text.AceKeyboardPreviewer::onKeyDown(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/dom/client/NativeEvent;)(data, e)) {
+ event.stopEvent(e);
+ return noop; // perform a no-op
+ }
+ else
+ return false; // allow default behavior
+ }
+ else {
+ if (self. at org.rstudio.studio.client.workbench.views.source.editors.text.AceKeyboardPreviewer::onTextInput(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(data, keyOrText))
+ return noop;
+ else
+ return false;
+ }
+ })
+ };
+ }-*/;
+
+ private boolean onKeyDown(JavaScriptObject data, NativeEvent e)
+ {
+ for (Handler handler : handlers_)
+ {
+ if (handler.previewKeyDown(data, e))
+ return true;
+ }
+ return false;
+ }
+
+
+ private boolean onTextInput(JavaScriptObject data, String text)
+ {
+ if (text == null)
+ return false;
+
+ // Escape key comes in as a character on desktop builds
+ if (text.equals("\u001B"))
+ return true;
+
+ for (Handler handler : handlers_)
+ {
+ for (int i = 0; i < text.length(); i++)
+ if (handler.previewKeyPress(data, text.charAt(i)))
+ return true;
+ }
+
+ return false;
+ }
+
+ private ArrayList<Handler> handlers_ = new ArrayList<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceVimCommandHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceVimCommandHandler.java
new file mode 100644
index 0000000..6be2628
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/AceVimCommandHandler.java
@@ -0,0 +1,97 @@
+/*
+ * AceVimCommandHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.command.KeyboardShortcut;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.NativeEvent;
+
+public class AceVimCommandHandler implements AceKeyboardPreviewer.Handler
+{
+ public AceVimCommandHandler(CommandWithArg<Boolean> onFindCommand)
+ {
+ onFindCommand_ = onFindCommand;
+ }
+
+
+ @Override
+ public boolean previewKeyDown(JavaScriptObject data, NativeEvent event)
+ {
+ if (event.getKeyCode() == 191) /* '/' or '?' */
+ {
+ if (isNormalMode(data))
+ {
+ int modifier = KeyboardShortcut.getModifierValue(event);
+ if (modifier == KeyboardShortcut.NONE)
+ {
+ onFindCommand_.execute(true);
+ return true;
+ }
+ else if (modifier == KeyboardShortcut.SHIFT)
+ {
+ onFindCommand_.execute(false);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean previewKeyPress(JavaScriptObject data, char charCode)
+ {
+ if (charCode == '/' && isNormalMode(data))
+ {
+ onFindCommand_.execute(true);
+ return true;
+ }
+ else if (charCode == '?' && isNormalMode(data))
+ {
+ onFindCommand_.execute(false);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+ private boolean isNormalMode(JavaScriptObject data)
+ {
+ VimHandlerData vimData = data.cast();
+ return "start".equals(vimData.getState());
+ }
+
+
+ private static final class VimHandlerData extends JavaScriptObject
+ {
+ protected VimHandlerData()
+ {
+ }
+
+ public native final String getState() /*-{
+ return this.state;
+ }-*/;
+
+ }
+
+ private CommandWithArg<Boolean> onFindCommand_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/DocDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/DocDisplay.java
new file mode 100644
index 0000000..df48af6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/DocDisplay.java
@@ -0,0 +1,224 @@
+/*
+ * DocDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.model.ChangeTracker;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceFold;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Anchor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Mode.InsertChunkInfo;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.BreakpointMoveEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.BreakpointSetEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.CommandClickEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedHandler;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FindRequestedEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.HasFoldChangeHandlers;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.UndoRedoHandler;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.IsWidget;
+import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+public interface DocDisplay extends HasValueChangeHandlers<Void>,
+ HasFoldChangeHandlers,
+ IsWidget,
+ HasFocusHandlers,
+ HasKeyDownHandlers,
+ InputEditorDisplay,
+ NavigableSourceEditor
+{
+ public interface AnchoredSelection
+ {
+ String getValue();
+ Range getRange();
+ void apply();
+ void detach();
+ }
+ TextFileType getFileType();
+ void setFileType(TextFileType fileType);
+ void setFileType(TextFileType fileType, boolean suppressCompletion);
+ void setFileType(TextFileType fileType, CompletionManager completionManager);
+ void setRnwCompletionContext(RnwCompletionContext rnwContext);
+ String getCode();
+ void setCode(String code, boolean preserveCursorPosition);
+ void insertCode(String code, boolean blockMode);
+ void focus();
+ boolean isFocused();
+ void print();
+ void codeCompletion();
+ void goToHelp();
+ void goToFunctionDefinition();
+ String getSelectionValue();
+ String getCurrentLine();
+ // This returns null for most file types, but for Sweave it returns "R" or
+ // "TeX". Use SweaveFileType constants to test for these values.
+ String getLanguageMode(Position position);
+ void replaceSelection(String code);
+ boolean moveSelectionToNextLine(boolean skipBlankLines);
+ boolean moveSelectionToBlankLine();
+ void reindent();
+ ChangeTracker getChangeTracker();
+
+ String getCode(Position start, Position end);
+ DocDisplay.AnchoredSelection createAnchoredSelection(Position start,
+ Position end);
+ String getCode(InputEditorSelection selection);
+
+ void fitSelectionToLines(boolean expand);
+ int getSelectionOffset(boolean start);
+
+ // Fix bug 964
+ void onActivate();
+
+ void setReadOnly(boolean readOnly);
+ void setFontSize(double size);
+
+ void onVisibilityChanged(boolean visible);
+
+ void setHighlightSelectedLine(boolean on);
+ void setHighlightSelectedWord(boolean on);
+ void setShowLineNumbers(boolean on);
+ void setUseSoftTabs(boolean on);
+ void setUseWrapMode(boolean on);
+ void setTabSize(int tabSize);
+ void setShowPrintMargin(boolean on);
+ void setPrintMarginColumn(int column);
+ void setShowInvisibles(boolean show);
+ void setShowIndentGuides(boolean show);
+ void setUseVimMode(boolean use);
+ void setBlinkingCursor(boolean blinking);
+
+ JsArray<AceFold> getFolds();
+ void addFold(Range range);
+ void addFoldFromRow(int row);
+ void unfold(AceFold fold);
+ void unfold(int row);
+ void unfold(Range range);
+
+ void toggleCommentLines();
+
+ HandlerRegistration addEditorFocusHandler(FocusHandler handler);
+
+ HandlerRegistration addCommandClickHandler(CommandClickEvent.Handler handler);
+
+ HandlerRegistration addFindRequestedHandler(FindRequestedEvent.Handler handler);
+
+ HandlerRegistration addCursorChangedHandler(CursorChangedHandler handler);
+
+ Position getCursorPosition();
+ void setCursorPosition(Position position);
+ void moveCursorNearTop();
+ void moveCursorNearTop(int rowOffset);
+ void ensureCursorVisible();
+
+
+ InputEditorSelection search(String needle,
+ boolean backwards,
+ boolean wrap,
+ boolean caseSensitive,
+ boolean wholeWord,
+ Position start,
+ Range range,
+ boolean regexpModex);
+
+ void insertCode(InputEditorPosition position, String code);
+
+ int getScrollLeft();
+ void scrollToX(int x);
+
+ int getScrollTop();
+ void scrollToY(int y);
+
+ Scope getCurrentScope();
+ Scope getCurrentChunk();
+ Scope getCurrentChunk(Position position);
+ Scope getCurrentFunction();
+ Scope getCurrentSection();
+ Scope getFunctionAtPosition(Position position);
+ Scope getSectionAtPosition(Position position);
+ boolean hasScopeTree();
+ JsArray<Scope> getScopeTree();
+ InsertChunkInfo getInsertChunkInfo();
+
+ void foldAll();
+ void unfoldAll();
+ void toggleFold();
+
+ void jumpToMatching();
+
+ HandlerRegistration addUndoRedoHandler(UndoRedoHandler handler);
+ JavaScriptObject getCleanStateToken();
+ boolean checkCleanStateToken(JavaScriptObject token);
+
+ Position getSelectionStart();
+ Position getSelectionEnd();
+ Range getSelectionRange();
+ void setSelectionRange(Range range);
+ int getLength(int row);
+ int getRowCount();
+
+ String getLine(int row);
+
+ String debug_getDocumentDump();
+ void debug_setSessionValueDirectly(String s);
+
+ // HACK: This should not use Ace-specific data structures
+ InputEditorSelection createSelection(Position pos1, Position pos2);
+
+ // HACK: InputEditorPosition should just become AceInputEditorPosition
+ Position selectionToPosition(InputEditorPosition pos);
+
+ Iterable<Range> getWords(TokenPredicate tokenPredicate,
+ CharClassifier charClassifier,
+ Position start,
+ Position end);
+
+ String getTextForRange(Range range);
+
+ Anchor createAnchor(Position pos);
+
+ void highlightDebugLocation(
+ SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing);
+ void endDebugHighlighting();
+
+ HandlerRegistration addBreakpointSetHandler
+ (BreakpointSetEvent.Handler handler);
+ HandlerRegistration addBreakpointMoveHandler
+ (BreakpointMoveEvent.Handler handler);
+ void addOrUpdateBreakpoint(Breakpoint breakpoint);
+ void removeBreakpoint(Breakpoint breakpoint);
+ void removeAllBreakpoints();
+ void toggleBreakpointAtCursor();
+ boolean hasBreakpoints();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Fold.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Fold.java
new file mode 100644
index 0000000..382c014
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Fold.java
@@ -0,0 +1,208 @@
+/*
+ * Fold.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayMixed;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceFold;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+
+import java.util.ArrayList;
+
+public class Fold
+{
+ public static int getCollectiveHashCode(ArrayList<Fold> folds)
+ {
+ int hashCode = 0;
+ for (Fold fold : folds)
+ {
+ hashCode *= 31;
+ hashCode += fold.hashCode();
+ }
+ return hashCode;
+ }
+
+ public static Fold fromAceFold(AceFold fold)
+ {
+ return new Fold(fold.getStart(), fold.getEnd(), fold.getPlaceholder());
+ }
+
+ public static String encode(ArrayList<Fold> folds)
+ {
+ StringBuilder result = new StringBuilder();
+ for (Fold f : folds)
+ {
+ result.append(f.getStartRow()).append('|')
+ .append(f.getStartColumn()).append('|')
+ .append(f.getEndRow()).append('|')
+ .append(f.getEndColumn()).append('|')
+ .append('\n');
+ }
+ return result.toString();
+ }
+
+ public static ArrayList<Fold> decode(String foldData)
+ {
+ ArrayList<Fold> results = new ArrayList<Fold>();
+ String[] chunks = foldData.split("\n");
+ for (String chunk : chunks)
+ {
+ if (chunk.isEmpty())
+ continue;
+
+ String[] pieces = chunk.split("\\|");
+ results.add(new Fold(Integer.parseInt(pieces[0]),
+ Integer.parseInt(pieces[1]),
+ Integer.parseInt(pieces[2]),
+ Integer.parseInt(pieces[3]),
+ "..."));//pieces[4]));
+ }
+ return results;
+ }
+
+ public static JsArray<JsArrayMixed> toJs(ArrayList<Fold> folds)
+ {
+ JsArray<JsArrayMixed> results = JavaScriptObject.createArray().cast();
+ for (Fold f : folds)
+ {
+ JsArrayMixed foldData = JavaScriptObject.createArray().cast();
+ foldData.set(0, f.getStartRow());
+ foldData.set(1, f.getStartColumn());
+ foldData.set(2, f.getEndRow());
+ foldData.set(3, f.getEndColumn());
+ foldData.set(4, f.getPlaceholder());
+ results.push(foldData);
+ }
+ return results;
+ }
+
+ public static ArrayList<Fold> fromJs(JsArray<JsArrayMixed> folds)
+ {
+ ArrayList<Fold> results = new ArrayList<Fold>();
+ for (int i = 0; i < folds.length(); i++)
+ {
+ JsArrayMixed foldData = folds.get(i);
+ results.add(new Fold((int)foldData.getNumber(0),
+ (int)foldData.getNumber(1),
+ (int)foldData.getNumber(2),
+ (int)foldData.getNumber(3),
+ foldData.getString(4)));
+ }
+ return results;
+ }
+
+
+ /**
+ * Puts the input ace folds, and their subfolds (recursively), into a flat
+ * list of Fold objects.
+ */
+ public static ArrayList<Fold> flatten(JsArray<AceFold> folds)
+ {
+ ArrayList<Fold> results = new ArrayList<Fold>();
+ for (int i = 0; i < folds.length(); i++)
+ collect(folds.get(i), results);
+ return results;
+ }
+
+ private static void collect(AceFold fold, ArrayList<Fold> results)
+ {
+ results.add(fromAceFold(fold));
+ JsArray<AceFold> subFolds = fold.getSubFolds();
+ for (int i = 0; i < subFolds.length(); i++)
+ collect(subFolds.get(i), results);
+ }
+
+ public Fold(Position start, Position end, String placeholder)
+ {
+ this(start.getRow(),
+ start.getColumn(),
+ end.getRow(),
+ end.getColumn(),
+ placeholder);
+ }
+
+ public Fold(int startRow,
+ int startColumn,
+ int endRow,
+ int endColumn,
+ String placeholder)
+ {
+ startRow_ = startRow;
+ startColumn_ = startColumn;
+ endRow_ = endRow;
+ endColumn_ = endColumn;
+ placeholder_ = placeholder;
+ }
+
+ public int getStartRow()
+ {
+ return startRow_;
+ }
+
+ public int getStartColumn()
+ {
+ return startColumn_;
+ }
+
+ public int getEndRow()
+ {
+ return endRow_;
+ }
+
+ public int getEndColumn()
+ {
+ return endColumn_;
+ }
+
+ public String getPlaceholder()
+ {
+ return placeholder_;
+ }
+
+ public Range getRange()
+ {
+ return Range.fromPoints(
+ Position.create(getStartRow(), getStartColumn()),
+ Position.create(getEndRow(), getEndColumn()));
+ }
+
+ public AceFold toAceFold()
+ {
+ return AceFold.createFold(getRange(), placeholder_);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hashCode = startRow_;
+ hashCode *= 31;
+ hashCode += startColumn_;
+ hashCode *= 31;
+ hashCode += endRow_;
+ hashCode *= 31;
+ hashCode += endColumn_;
+ hashCode *= 31;
+ hashCode += placeholder_.hashCode();
+ return hashCode;
+ }
+
+ private final int startRow_;
+ private final int startColumn_;
+ private final int endRow_;
+ private final int endColumn_;
+ private final String placeholder_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/IconvListResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/IconvListResult.java
new file mode 100644
index 0000000..0b20a2c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/IconvListResult.java
@@ -0,0 +1,33 @@
+/*
+ * IconvListResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+public class IconvListResult extends JavaScriptObject
+{
+ protected IconvListResult()
+ {
+ }
+
+ public native final JsArrayString getCommon() /*-{
+ return this.common;
+ }-*/;
+
+ public native final JsArrayString getAll() /*-{
+ return this.all;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/NavigableSourceEditor.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/NavigableSourceEditor.java
new file mode 100644
index 0000000..458be04
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/NavigableSourceEditor.java
@@ -0,0 +1,42 @@
+/*
+ * NavigableSourceEditor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionHandler;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+
+public interface NavigableSourceEditor
+{
+ SourcePosition findFunctionPositionFromCursor(String functionName);
+
+ void recordCurrentNavigationPosition();
+
+ void navigateToPosition(SourcePosition position,
+ boolean recordCurrentPosition);
+
+ void navigateToPosition(SourcePosition position,
+ boolean recordCurrentPosition,
+ boolean highlightLine);
+
+ void restorePosition(SourcePosition position);
+
+ boolean isAtSourceRow(SourcePosition position);
+
+ HandlerRegistration addRecordNavigationPositionHandler(
+ RecordNavigationPositionHandler handler);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Scope.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Scope.java
new file mode 100644
index 0000000..f5db87a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/Scope.java
@@ -0,0 +1,96 @@
+/*
+ * Scope.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+public class Scope extends JavaScriptObject
+{
+ protected Scope()
+ {}
+
+ public native final String getLabel() /*-{
+ return this.label;
+ }-*/;
+
+ public native final boolean isTopLevel() /*-{
+ return this.isRoot();
+ }-*/;
+
+ public native final boolean isBrace() /*-{
+ return this.isBrace();
+ }-*/;
+
+ public native final boolean isChunk() /*-{
+ return this.isChunk();
+ }-*/;
+
+ public native final boolean isSection() /*-{
+ return this.isSection();
+ }-*/;
+
+ public native final boolean isFunction() /*-{
+ return this.isFunction();
+ }-*/;
+
+ public native final Scope getParentScope() /*-{
+ return this.parentScope;
+ }-*/;
+
+ /**
+ * For named functions, the preamble points to the beginning of the function
+ * declaration, including function name. For chunks, it points to the
+ * beginning of the chunk itself. For other scopes, it just points to the
+ * opening brace (same as getBodyStart).
+ */
+ public native final Position getPreamble() /*-{
+ return this.preamble;
+ }-*/;
+
+ /**
+ * Points to the start of the body of the scope. Note that for named
+ * functions, chunks, and sections, this is different than the preamble.
+ */
+ public native final Position getBodyStart() /*-{
+ return this.start;
+ }-*/;
+
+ /**
+ * Points to the part of a scope where a fold would begin.
+ */
+ public final Position getFoldStart()
+ {
+ if (isFunction())
+ return getBodyStart();
+ else
+ return getPreamble();
+
+ }
+
+ public native final Position getEnd() /*-{
+ return this.end;
+ }-*/;
+
+ public native final JsArray<Scope> getChildren() /*-{
+ return this.$children;
+ }-*/;
+
+ public native final String getChunkLabel() /*-{
+ return this.chunkLabel;
+ }-*/;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ScopeList.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ScopeList.java
new file mode 100644
index 0000000..b790f9d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ScopeList.java
@@ -0,0 +1,148 @@
+/*
+ * ScopeList.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Represents a flattened list of scopes in the given DocDisplay. It takes a
+ * snapshot at the time of construction, so don't hold onto a ScopeList whose
+ * document is changing.
+ */
+public class ScopeList implements Iterable<Scope>
+{
+ public interface ScopePredicate
+ {
+ boolean test(Scope scope);
+ }
+
+ public static class ContainsFoldPredicate implements ScopePredicate
+ {
+ public ContainsFoldPredicate(Range range)
+ {
+ range_ = range;
+ }
+
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.getFoldStart().isBeforeOrEqualTo(range_.getStart()) &&
+ scope.getEnd().isAfterOrEqualTo(range_.getStart()) &&
+ scope.getFoldStart().isBeforeOrEqualTo(range_.getEnd()) &&
+ scope.getEnd().isAfterOrEqualTo(range_.getEnd());
+ }
+
+ private final Range range_;
+ }
+
+ public static final ScopePredicate CHUNK = new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.isChunk();
+ }
+ };
+
+ public static final ScopePredicate SECT = new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.isSection();
+ }
+ };
+
+ public static final ScopePredicate FUNC = new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.isBrace() && !StringUtil.isNullOrEmpty(scope.getLabel());
+ }
+ };
+
+ public static final ScopePredicate ANON_BRACE = new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.isBrace() && StringUtil.isNullOrEmpty(scope.getLabel());
+ }
+ };
+
+ public ScopeList(DocDisplay docDisplay)
+ {
+ addScopes(docDisplay.getScopeTree());
+ }
+
+ @Override
+ public Iterator<Scope> iterator()
+ {
+ return scopes_.iterator();
+ }
+
+ public Scope[] getScopes()
+ {
+ return scopes_.toArray(new Scope[scopes_.size()]);
+ }
+
+ public void removeAll(ScopePredicate shouldRemove)
+ {
+ for (int i = 0; i < scopes_.size(); i++)
+ if (shouldRemove.test(scopes_.get(i)))
+ scopes_.remove(i--);
+ }
+
+ public void selectAll(ScopePredicate shouldRetain)
+ {
+ for (int i = 0; i < scopes_.size(); i++)
+ if (!shouldRetain.test(scopes_.get(i)))
+ scopes_.remove(i--);
+ }
+
+ public Scope findFirst(ScopePredicate predicate)
+ {
+ for (Scope scope : scopes_)
+ if (predicate.test(scope))
+ return scope;
+ return null;
+ }
+
+ public Scope findLast(ScopePredicate predicate)
+ {
+ for (int i = scopes_.size() - 1; i >= 0; i--)
+ if (predicate.test(scopes_.get(i)))
+ return scopes_.get(i);
+ return null;
+ }
+
+ private void addScopes(JsArray<Scope> scopes)
+ {
+ for (Scope scope : JsUtil.asIterable(scopes))
+ {
+ scopes_.add(scope);
+ addScopes(scope.getChildren());
+ }
+ }
+
+ private final ArrayList<Scope> scopes_ = new ArrayList<Scope>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextDisplay.java
new file mode 100644
index 0000000..8b51a8a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextDisplay.java
@@ -0,0 +1,26 @@
+/*
+ * TextDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface TextDisplay extends IsWidget
+{
+ void onActivate();
+ void adaptToFileType(TextFileType fileType);
+ void setFontSize(double size);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTarget.java
new file mode 100644
index 0000000..a120b02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTarget.java
@@ -0,0 +1,3883 @@
+/*
+ * TextEditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.*;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.*;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.events.HasEnsureHeightHandlers;
+import org.rstudio.core.client.events.HasEnsureVisibleHandlers;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.ChangeFontSizeEvent;
+import org.rstudio.studio.client.application.events.ChangeFontSizeHandler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.*;
+import org.rstudio.studio.client.common.debugging.BreakpointManager;
+import org.rstudio.studio.client.common.debugging.events.BreakpointsSavedEvent;
+import org.rstudio.studio.client.common.debugging.model.Breakpoint;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.common.filetypes.FileTypeCommands;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.SweaveFileType;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+import org.rstudio.studio.client.common.synctex.Synctex;
+import org.rstudio.studio.client.common.synctex.SynctexUtils;
+import org.rstudio.studio.client.common.synctex.model.SourceLocation;
+import org.rstudio.studio.client.htmlpreview.events.ShowHTMLPreviewEvent;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
+import org.rstudio.studio.client.notebook.CompileNotebookOptions;
+import org.rstudio.studio.client.notebook.CompileNotebookOptionsDialog;
+import org.rstudio.studio.client.notebook.CompileNotebookPrefs;
+import org.rstudio.studio.client.notebook.CompileNotebookResult;
+import org.rstudio.studio.client.pdfviewer.events.ShowPDFViewerEvent;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.shiny.events.ShinyApplicationStatusEvent;
+import org.rstudio.studio.client.shiny.events.ShinyAppsActionEvent;
+import org.rstudio.studio.client.workbench.WorkbenchContext;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefsAccessor;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
+import org.rstudio.studio.client.workbench.views.output.compilepdf.events.CompilePdfEvent;
+import org.rstudio.studio.client.workbench.views.presentation.events.SourceFileSaveCompletedEvent;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
+import org.rstudio.studio.client.workbench.views.source.SourceBuildHelper;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetCodeExecution;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ScopeList.ContainsFoldPredicate;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceFold;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Mode.InsertChunkInfo;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.*;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBar;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarPopupMenu;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarPopupRequest;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ui.ChooseEncodingDialog;
+import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionEvent;
+import org.rstudio.studio.client.workbench.views.source.events.RecordNavigationPositionHandler;
+import org.rstudio.studio.client.workbench.views.source.events.SourceFileSavedEvent;
+import org.rstudio.studio.client.workbench.views.source.events.SourceNavigationEvent;
+import org.rstudio.studio.client.workbench.views.source.model.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsDiffEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsHistoryEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRevertFileEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsViewOnGitHubEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.GitHubViewRequest;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class TextEditingTarget implements
+ EditingTarget,
+ EditingTargetCodeExecution.CodeExtractor
+{
+ interface MyCommandBinder
+ extends CommandBinder<Commands, TextEditingTarget>
+ {
+ }
+
+ private static final String NOTEBOOK_TITLE = "notebook_title";
+ private static final String NOTEBOOK_AUTHOR = "notebook_author";
+ private static final String NOTEBOOK_TYPE = "notebook_type";
+
+ private static final MyCommandBinder commandBinder =
+ GWT.create(MyCommandBinder.class);
+
+ public interface Display extends TextDisplay,
+ WarningBarDisplay,
+ HasEnsureVisibleHandlers,
+ HasEnsureHeightHandlers
+ {
+ HasValue<Boolean> getSourceOnSave();
+ void ensureVisible();
+ void showFindReplace(boolean defaultForward);
+ void findNext();
+ void findPrevious();
+ void findFromSelection();
+ void replaceAndFind();
+
+ StatusBar getStatusBar();
+
+ boolean isAttached();
+
+ void adaptToExtendedFileType(String extendedType);
+ void onShinyApplicationStateChanged(String state);
+
+ void debug_dumpContents();
+ void debug_importDump();
+ }
+
+ private class SaveProgressIndicator implements ProgressIndicator
+ {
+
+ public SaveProgressIndicator(FileSystemItem file,
+ TextFileType fileType,
+ Command executeOnSuccess)
+ {
+ file_ = file;
+ newFileType_ = fileType;
+ executeOnSuccess_ = executeOnSuccess;
+ }
+
+ public void onProgress(String message)
+ {
+ }
+
+ public void clearProgress()
+ {
+ }
+
+ public void onCompleted()
+ {
+ // don't need to check again soon because we just saved
+ // (without this and when file monitoring is active we'd
+ // end up immediately checking for external edits)
+ externalEditCheckInterval_.reset(250);
+
+ if (newFileType_ != null)
+ fileType_ = newFileType_;
+
+ if (file_ != null)
+ {
+ ignoreDeletes_ = false;
+ commands_.reopenSourceDocWithEncoding().setEnabled(true);
+ name_.setValue(file_.getName(), true);
+ // Make sure tooltip gets updated, even if name hasn't changed
+ name_.fireChangeEvent();
+
+ // If we were dirty prior to saving, clean up the debug state so
+ // we don't continue highlighting after saving. (There are cases
+ // in which we want to restore highlighting after the dirty state
+ // is marked clean--i.e. when unwinding the undo stack.)
+ if (dirtyState_.getValue())
+ endDebugHighlighting();
+
+ dirtyState_.markClean();
+ }
+
+ if (newFileType_ != null)
+ {
+ // Make sure the icon gets updated, even if name hasn't changed
+ name_.fireChangeEvent();
+ updateStatusBarLanguage();
+ view_.adaptToFileType(newFileType_);
+ events_.fireEvent(new FileTypeChangedEvent());
+ if (!fileType_.canSourceOnSave() && docUpdateSentinel_.sourceOnSave())
+ {
+ view_.getSourceOnSave().setValue(false, true);
+ }
+ }
+
+ if (executeOnSuccess_ != null)
+ executeOnSuccess_.execute();
+ }
+
+ public void onError(final String message)
+ {
+ // in case the error occured saving a document that wasn't
+ // in the foreground
+ view_.ensureVisible();
+
+ // command to show the error
+ final Command showErrorCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ globalDisplay_.showErrorMessage("Error Saving File",
+ message);
+ }
+ };
+
+ // check whether the file exists and isn't writeable
+ if (file_ != null)
+ {
+ server_.isReadOnlyFile(file_.getPath(),
+ new ServerRequestCallback<Boolean>() {
+
+ @Override
+ public void onResponseReceived(Boolean isReadOnly)
+ {
+ if (isReadOnly)
+ {
+ String message = "This source file is read-only " +
+ "so changes cannot be saved";
+ view_.showWarningBar(message);
+
+ String saveAsPath = file_.getParentPath().completePath(
+ file_.getStem() + "-copy" + file_.getExtension());
+ saveNewFile(
+ saveAsPath,
+ null,
+ CommandUtil.join(postSaveCommand(), new Command() {
+
+ @Override
+ public void execute()
+ {
+ view_.hideWarningBar();
+ }
+ }));
+
+ }
+ else
+ {
+ showErrorCommand.execute();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ showErrorCommand.execute();
+ }
+ });
+ }
+ else
+ {
+ showErrorCommand.execute();
+ }
+
+
+ }
+
+ private final FileSystemItem file_;
+
+ private final TextFileType newFileType_;
+ private final Command executeOnSuccess_;
+ }
+
+ @Inject
+ public TextEditingTarget(Commands commands,
+ SourceServerOperations server,
+ EventBus events,
+ GlobalDisplay globalDisplay,
+ FileDialogs fileDialogs,
+ FileTypeRegistry fileTypeRegistry,
+ FileTypeCommands fileTypeCommands,
+ ConsoleDispatcher consoleDispatcher,
+ WorkbenchContext workbenchContext,
+ Session session,
+ Synctex synctex,
+ FontSizeManager fontSizeManager,
+ DocDisplay docDisplay,
+ UIPrefs prefs,
+ BreakpointManager breakpointManager,
+ SourceBuildHelper sourceBuildHelper)
+ {
+ commands_ = commands;
+ server_ = server;
+ events_ = events;
+ globalDisplay_ = globalDisplay;
+ fileDialogs_ = fileDialogs;
+ fileTypeRegistry_ = fileTypeRegistry;
+ fileTypeCommands_ = fileTypeCommands;
+ consoleDispatcher_ = consoleDispatcher;
+ workbenchContext_ = workbenchContext;
+ session_ = session;
+ synctex_ = synctex;
+ fontSizeManager_ = fontSizeManager;
+ breakpointManager_ = breakpointManager;
+ sourceBuildHelper_ = sourceBuildHelper;
+
+ docDisplay_ = docDisplay;
+ dirtyState_ = new DirtyState(docDisplay_, false);
+ prefs_ = prefs;
+ codeExecution_ = new EditingTargetCodeExecution(docDisplay_, this);
+ compilePdfHelper_ = new TextEditingTargetCompilePdfHelper(docDisplay_);
+ previewHtmlHelper_ = new TextEditingTargetPreviewHtmlHelper();
+ cppHelper_ = new TextEditingTargetCppHelper(server);
+ presentationHelper_ = new TextEditingTargetPresentationHelper(
+ docDisplay_);
+ docDisplay_.setRnwCompletionContext(compilePdfHelper_);
+ scopeHelper_ = new TextEditingTargetScopeHelper(docDisplay_);
+
+ addRecordNavigationPositionHandler(releaseOnDismiss_,
+ docDisplay_,
+ events_,
+ this);
+
+ docDisplay_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ NativeEvent ne = event.getNativeEvent();
+ int mod = KeyboardShortcut.getModifierValue(ne);
+ if ((mod == KeyboardShortcut.META || (mod == KeyboardShortcut.CTRL && !BrowseCap.hasMetaKey()))
+ && ne.getKeyCode() == 'F')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ commands_.findReplace().execute();
+ }
+ else if (BrowseCap.hasMetaKey() &&
+ (mod == KeyboardShortcut.META) &&
+ (ne.getKeyCode() == 'E'))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ commands_.findFromSelection().execute();
+ }
+ else if (mod == KeyboardShortcut.ALT
+ && ne.getKeyCode() == 189) // hyphen
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ docDisplay_.insertCode(" <- ", false);
+ }
+ else if (mod == KeyboardShortcut.CTRL
+ && ne.getKeyCode() == KeyCodes.KEY_UP
+ && fileType_ == FileTypeRegistry.R)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ jumpToPreviousFunction();
+ }
+ else if (mod == KeyboardShortcut.CTRL
+ && ne.getKeyCode() == KeyCodes.KEY_DOWN
+ && fileType_ == FileTypeRegistry.R)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ jumpToNextFunction();
+ }
+ else if ((ne.getKeyCode() == KeyCodes.KEY_ESCAPE) &&
+ !prefs_.useVimMode().getValue())
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ commands_.interruptR().execute();
+ }
+ }
+ });
+
+ docDisplay_.addCommandClickHandler(new CommandClickEvent.Handler()
+ {
+ @Override
+ public void onCommandClick(CommandClickEvent event)
+ {
+ if (fileType_.canCompilePDF() &&
+ commands_.synctexSearch().isEnabled())
+ {
+ // warn firefox users that this doesn't really work in Firefox
+ if (BrowseCap.isFirefox() && !BrowseCap.isMacintosh())
+ SynctexUtils.maybeShowFirefoxWarning("PDF preview");
+
+ doSynctexSearch(true);
+ }
+ else
+ {
+ docDisplay_.goToFunctionDefinition();
+ }
+ }
+ });
+
+ docDisplay_.addFindRequestedHandler(new FindRequestedEvent.Handler() {
+ @Override
+ public void onFindRequested(FindRequestedEvent event)
+ {
+ view_.showFindReplace(event.getDefaultForward());
+ }
+ });
+
+ events_.addHandler(
+ ShinyApplicationStatusEvent.TYPE,
+ new ShinyApplicationStatusEvent.Handler()
+ {
+ @Override
+ public void onShinyApplicationStatus(
+ ShinyApplicationStatusEvent event)
+ {
+ // If the document appears to be inside the directory
+ // associated with the event, update the view to match the
+ // new state.
+ if (getPath() != null &&
+ getPath().startsWith(event.getParams().getPath()))
+ {
+ view_.onShinyApplicationStateChanged(
+ event.getParams().getState());
+ }
+ }
+ });
+
+ events_.addHandler(
+ BreakpointsSavedEvent.TYPE,
+ new BreakpointsSavedEvent.Handler()
+ {
+ @Override
+ public void onBreakpointsSaved(BreakpointsSavedEvent event)
+ {
+ // if this document isn't ready for breakpoints, stop now
+ if (docUpdateSentinel_ == null)
+ {
+ return;
+ }
+ for (Breakpoint breakpoint: event.breakpoints())
+ {
+ // discard the breakpoint if it's not related to the file this
+ // editor instance is concerned with
+ if (!breakpoint.isInFile(getPath()))
+ {
+ continue;
+ }
+
+ // if the breakpoint was saved successfully, enable it on the
+ // editor surface; otherwise, just remove it.
+ if (event.successful())
+ {
+ docDisplay_.addOrUpdateBreakpoint(breakpoint);
+ }
+ else
+ {
+ // Show a warning for breakpoints that didn't get set (unless
+ // the reason the breakpoint wasn't set was that it's being
+ // removed)
+ if (breakpoint.getState() != Breakpoint.STATE_REMOVING)
+ {
+ view_.showWarningBar("Breakpoints can only be set inside "+
+ "the body of a function. ");
+ }
+ docDisplay_.removeBreakpoint(breakpoint);
+ }
+ }
+ updateBreakpointWarningBar();
+ }
+ });
+ }
+
+ @Override
+ public void recordCurrentNavigationPosition()
+ {
+ docDisplay_.recordCurrentNavigationPosition();
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent)
+ {
+ docDisplay_.navigateToPosition(position, recordCurrent);
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent,
+ boolean highlightLine)
+ {
+ docDisplay_.navigateToPosition(position, recordCurrent, highlightLine);
+ }
+
+ @Override
+ public void restorePosition(SourcePosition position)
+ {
+ docDisplay_.restorePosition(position);
+ }
+
+ @Override
+ public boolean isAtSourceRow(SourcePosition position)
+ {
+ return docDisplay_.isAtSourceRow(position);
+ }
+
+ @Override
+ public void setCursorPosition(Position position)
+ {
+ docDisplay_.setCursorPosition(position);
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ docDisplay_.ensureCursorVisible();
+ }
+
+ @Override
+ public void forceLineHighlighting()
+ {
+ docDisplay_.setHighlightSelectedLine(true);
+ }
+
+ @Override
+ public void highlightDebugLocation(
+ SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing)
+ {
+ debugStartPos_ = startPos;
+ debugEndPos_ = endPos;
+ docDisplay_.highlightDebugLocation(startPos, endPos, executing);
+ updateDebugWarningBar();
+ }
+
+ @Override
+ public void endDebugHighlighting()
+ {
+ docDisplay_.endDebugHighlighting();
+ debugStartPos_ = null;
+ debugEndPos_ = null;
+ updateDebugWarningBar();
+ }
+
+ private void updateDebugWarningBar()
+ {
+ // show the warning bar if we're debugging and the document is dirty
+ if (debugStartPos_ != null &&
+ dirtyState().getValue() &&
+ !isDebugWarningVisible_)
+ {
+ view_.showWarningBar("Debug lines may not match because the file contains unsaved changes.");
+ isDebugWarningVisible_ = true;
+ }
+ // hide the warning bar if the dirty state or debug state change
+ else if (isDebugWarningVisible_ &&
+ (debugStartPos_ == null || dirtyState().getValue() == false))
+ {
+ view_.hideWarningBar();
+ // if we're still debugging, start highlighting the line again
+ if (debugStartPos_ != null)
+ {
+ docDisplay_.highlightDebugLocation(
+ debugStartPos_,
+ debugEndPos_, false);
+ }
+ isDebugWarningVisible_ = false;
+ }
+ }
+
+ private void jumpToPreviousFunction()
+ {
+ Scope jumpTo = scopeHelper_.getPreviousFunction(
+ docDisplay_.getCursorPosition());
+
+ if (jumpTo != null)
+ docDisplay_.navigateToPosition(toSourcePosition(jumpTo), true);
+ }
+
+ private void jumpToNextFunction()
+ {
+ Scope jumpTo = scopeHelper_.getNextFunction(
+ docDisplay_.getCursorPosition());
+
+ if (jumpTo != null)
+ docDisplay_.navigateToPosition(toSourcePosition(jumpTo), true);
+ }
+
+ public void initialize(SourceDocument document,
+ FileSystemContext fileContext,
+ FileType type,
+ Provider<String> defaultNameProvider)
+ {
+ id_ = document.getId();
+ fileContext_ = fileContext;
+ fileType_ = (TextFileType) type;
+ extendedType_ = document.getExtendedType();
+ view_ = new TextEditingTargetWidget(commands_,
+ prefs_,
+ fileTypeRegistry_,
+ docDisplay_,
+ fileType_,
+ document.getExtendedType(),
+ events_);
+ docUpdateSentinel_ = new DocUpdateSentinel(
+ server_,
+ docDisplay_,
+ document,
+ globalDisplay_.getProgressIndicator("Save File"),
+ dirtyState_,
+ events_);
+
+ // ensure that Makefile and Makebars always uses tabs
+ name_.addValueChangeHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ if ("Makefile".equals(event.getValue()) ||
+ "Makevars".equals(event.getValue()) ||
+ "Makevars.win".equals(event.getValue()))
+ {
+ docDisplay_.setUseSoftTabs(false);
+ }
+ }
+ });
+
+ name_.setValue(getNameFromDocument(document, defaultNameProvider), true);
+ docDisplay_.setCode(document.getContents(), false);
+
+ final ArrayList<Fold> folds = Fold.decode(document.getFoldSpec());
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ for (Fold fold : folds)
+ docDisplay_.addFold(fold.getRange());
+ }
+ });
+
+ registerPrefs(releaseOnDismiss_, prefs_, docDisplay_, document);
+
+ // Initialize sourceOnSave, and keep it in sync
+ view_.getSourceOnSave().setValue(document.sourceOnSave(), false);
+ view_.getSourceOnSave().addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ docUpdateSentinel_.setSourceOnSave(
+ event.getValue(),
+ globalDisplay_.getProgressIndicator("Error Saving Setting"));
+ }
+ });
+
+ if (document.isDirty())
+ dirtyState_.markDirty(false);
+ else
+ dirtyState_.markClean();
+ docDisplay_.addValueChangeHandler(new ValueChangeHandler<Void>()
+ {
+ public void onValueChange(ValueChangeEvent<Void> event)
+ {
+ dirtyState_.markDirty(true);
+ }
+ });
+
+ docDisplay_.addFocusHandler(new FocusHandler()
+ {
+ public void onFocus(FocusEvent event)
+ {
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ if (view_.isAttached())
+ checkForExternalEdit();
+ return false;
+ }
+ }, 500);
+ }
+ });
+
+ if (fileType_.isR())
+ {
+ docDisplay_.addBreakpointSetHandler(new BreakpointSetEvent.Handler()
+ {
+ @Override
+ public void onBreakpointSet(BreakpointSetEvent event)
+ {
+ if (event.isSet())
+ {
+ Breakpoint breakpoint = null;
+
+ // don't try to set breakpoints in unsaved code
+ if (isNewDoc())
+ {
+ view_.showWarningBar("Breakpoints cannot be set until " +
+ "the file is saved.");
+ return;
+ }
+
+ // don't try to set breakpoints if the R version is too old
+ if (!session_.getSessionInfo().getHaveSrcrefAttribute())
+ {
+ view_.showWarningBar("Editor breakpoints require R 2.14 " +
+ "or newer.");
+ return;
+ }
+
+ Position breakpointPosition =
+ Position.create(event.getLineNumber() - 1, 1);
+
+ // if we're not in function scope, set a top-level breakpoint
+ Scope innerFunction =
+ docDisplay_.getFunctionAtPosition(breakpointPosition);
+ if (innerFunction == null || !innerFunction.isFunction())
+ {
+ breakpoint = breakpointManager_.setTopLevelBreakpoint(
+ getPath(),
+ event.getLineNumber());
+ }
+
+ // the scope tree will find nested functions, but in R these
+ // are addressable only as substeps of the parent function.
+ // keep walking up the scope tree until we've reached the top
+ // level function.
+ else
+ {
+ while (innerFunction.getParentScope() != null &&
+ innerFunction.getParentScope().isFunction())
+ {
+ innerFunction = innerFunction.getParentScope();
+ }
+ breakpoint = breakpointManager_.setBreakpoint(
+ getPath(),
+ innerFunction.getLabel(),
+ event.getLineNumber(),
+ dirtyState().getValue() == false);
+ }
+
+ docDisplay_.addOrUpdateBreakpoint(breakpoint);
+ }
+ else
+ {
+ breakpointManager_.removeBreakpoint(event.getBreakpointId());
+ }
+ updateBreakpointWarningBar();
+ }
+ });
+
+ docDisplay_.addBreakpointMoveHandler(new BreakpointMoveEvent.Handler()
+ {
+ @Override
+ public void onBreakpointMove(BreakpointMoveEvent event)
+ {
+ breakpointManager_.moveBreakpoint(event.getBreakpointId());
+ }
+ });
+ }
+
+ // validate required components (e.g. Tex, knitr, C++ etc.)
+ checkCompilePdfDependencies();
+ previewHtmlHelper_.verifyPrerequisites(view_, fileType_);
+
+ syncFontSize(releaseOnDismiss_, events_, view_, fontSizeManager_);
+
+
+ final String rTypeId = FileTypeRegistry.R.getTypeId();
+ releaseOnDismiss_.add(prefs_.softWrapRFiles().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> evt)
+ {
+ if (fileType_.getTypeId().equals(rTypeId))
+ view_.adaptToFileType(fileType_);
+ }
+ }
+ ));
+
+ releaseOnDismiss_.add(events_.addHandler(FileChangeEvent.TYPE,
+ new FileChangeHandler() {
+ @Override
+ public void onFileChange(FileChangeEvent event)
+ {
+ // screen out adds and events that aren't for our path
+ FileChange fileChange = event.getFileChange();
+ if (fileChange.getType() == FileChange.ADD)
+ return;
+ else if (!fileChange.getFile().getPath().equals(getPath()))
+ return;
+
+ // always check for changes if this is the active editor
+ if (commandHandlerReg_ != null)
+ {
+ checkForExternalEdit();
+ }
+
+ // also check for changes on modifications if we are not dirty
+ // note that we don't check for changes on removed files because
+ // this will show a confirmation dialog
+ else if (event.getFileChange().getType() == FileChange.MODIFIED &&
+ dirtyState().getValue() == false)
+ {
+ checkForExternalEdit();
+ }
+ }
+ }));
+
+ spelling_ = new TextEditingTargetSpelling(docDisplay_,
+ docUpdateSentinel_);
+
+ // show/hide the debug toolbar when the dirty state changes. (note:
+ // this doesn't yet handle the case where the user saves the document,
+ // in which case we should still show some sort of warning.)
+ dirtyState().addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> evt)
+ {
+ updateDebugWarningBar();
+ }
+ }
+ );
+
+ // find all of the debug breakpoints set in this document and replay them
+ // onto the edit surface
+ ArrayList<Breakpoint> breakpoints =
+ breakpointManager_.getBreakpointsInFile(getPath());
+ for (Breakpoint breakpoint: breakpoints)
+ {
+ docDisplay_.addOrUpdateBreakpoint(breakpoint);
+ }
+
+ initStatusBar();
+ }
+
+ private void updateBreakpointWarningBar()
+ {
+ // check to see if there are any inactive breakpoints in this file
+ boolean hasInactiveBreakpoints = false;
+ boolean hasDebugPendingBreakpoints = false;
+ boolean hasPackagePendingBreakpoints = false;
+ String pendingPackageName = "";
+ ArrayList<Breakpoint> breakpoints =
+ breakpointManager_.getBreakpointsInFile(getPath());
+ for (Breakpoint breakpoint: breakpoints)
+ {
+ if (breakpoint.getState() == Breakpoint.STATE_INACTIVE)
+ {
+ if (breakpoint.isPendingDebugCompletion())
+ {
+ hasDebugPendingBreakpoints = true;
+ }
+ else if (breakpoint.isPackageBreakpoint())
+ {
+ hasPackagePendingBreakpoints = true;
+ pendingPackageName = breakpoint.getPackageName();
+ }
+ else
+ {
+ hasInactiveBreakpoints = true;
+ }
+ break;
+ }
+ }
+ boolean showWarning = hasDebugPendingBreakpoints ||
+ hasInactiveBreakpoints ||
+ hasPackagePendingBreakpoints;
+
+ if (showWarning && !isBreakpointWarningVisible_)
+ {
+ String message = "";
+ if (hasDebugPendingBreakpoints)
+ {
+ message = "Breakpoints will be activated when the file or " +
+ "function is finished executing.";
+ }
+ else if (isPackageFile())
+ {
+ message = "Breakpoints will be activated when the package is " +
+ "built and reloaded.";
+ }
+ else if (hasPackagePendingBreakpoints)
+ {
+ message = "Breakpoints will be activated when an updated version " +
+ "of the " + pendingPackageName + " package is loaded";
+ }
+ else
+ {
+ message = "Breakpoints will be activated when this file is " +
+ "sourced.";
+ }
+ view_.showWarningBar(message);
+ isBreakpointWarningVisible_ = true;
+ }
+ else if (!showWarning && isBreakpointWarningVisible_)
+ {
+ hideBreakpointWarningBar();
+ }
+ }
+
+ private void hideBreakpointWarningBar()
+ {
+ if (isBreakpointWarningVisible_)
+ {
+ view_.hideWarningBar();
+ isBreakpointWarningVisible_ = false;
+ }
+ }
+
+ private boolean isPackageFile()
+ {
+ // not a package file if we're not in package development mode
+ String type = session_.getSessionInfo().getBuildToolsType();
+ if (!type.equals(SessionInfo.BUILD_TOOLS_PACKAGE))
+ {
+ return false;
+ }
+
+ // get the directory associated with the project and see if the file is
+ // inside that directory
+ FileSystemItem projectDir = session_.getSessionInfo()
+ .getActiveProjectDir();
+ return getPath().startsWith(projectDir.getPath() + "/R");
+ }
+
+ private void checkCompilePdfDependencies()
+ {
+ compilePdfHelper_.checkCompilers(view_, fileType_);
+ }
+
+ private void initStatusBar()
+ {
+ statusBar_ = view_.getStatusBar();
+ docDisplay_.addCursorChangedHandler(new CursorChangedHandler()
+ {
+ public void onCursorChanged(CursorChangedEvent event)
+ {
+ updateStatusBarPosition();
+ }
+ });
+ updateStatusBarPosition();
+ updateStatusBarLanguage();
+
+
+ // build file type menu dynamically (so it can change according
+ // to whether e.g. knitr is installed)
+ statusBar_.getLanguage().addMouseDownHandler(new MouseDownHandler() {
+
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ // build menu with all file types - also track whether we need
+ // to add the current type (may be the case for types which we
+ // support but don't want to expose on the menu -- e.g. Rmd
+ // files when knitr isn't installed)
+ boolean addCurrentType = true;
+ final StatusBarPopupMenu menu = new StatusBarPopupMenu();
+ TextFileType[] fileTypes = fileTypeCommands_.statusBarFileTypes();
+ for (TextFileType type : fileTypes)
+ {
+ menu.addItem(createMenuItemForType(type));
+ if (addCurrentType && type.equals(fileType_))
+ addCurrentType = false;
+ }
+
+ // add the current type if isn't on the menu
+ if (addCurrentType)
+ menu.addItem(createMenuItemForType(fileType_));
+
+ // show the menu
+ menu.showRelativeToUpward((UIObject) statusBar_.getLanguage());
+ }
+ });
+
+ statusBar_.getScope().addMouseDownHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ // Unlike the other status bar elements, the function outliner
+ // needs its menu built on demand
+ JsArray<Scope> tree = docDisplay_.getScopeTree();
+ final StatusBarPopupMenu menu = new StatusBarPopupMenu();
+ MenuItem defaultItem = null;
+ if (fileType_.isRpres())
+ {
+ String path = docUpdateSentinel_.getPath();
+ if (path != null)
+ {
+ presentationHelper_.buildSlideMenu(
+ docUpdateSentinel_.getPath(),
+ dirtyState_.getValue(),
+ TextEditingTarget.this,
+ new CommandWithArg<StatusBarPopupRequest>() {
+
+ @Override
+ public void execute(StatusBarPopupRequest request)
+ {
+ showStatusBarPopupMenu(request);
+ }
+ });
+ }
+ }
+ else
+ {
+ defaultItem = addFunctionsToMenu(
+ menu, tree, "", docDisplay_.getCurrentScope(), true);
+
+ showStatusBarPopupMenu(new StatusBarPopupRequest(menu,
+ defaultItem));
+ }
+ }
+ });
+ }
+
+ private void showStatusBarPopupMenu(StatusBarPopupRequest popupRequest)
+ {
+ final StatusBarPopupMenu menu = popupRequest.getMenu();
+ MenuItem defaultItem = popupRequest.getDefaultMenuItem();
+ if (defaultItem != null)
+ {
+ menu.selectItem(defaultItem);
+ Scheduler.get().scheduleFinally(new RepeatingCommand()
+ {
+ public boolean execute()
+ {
+ menu.ensureSelectedIsVisible();
+ return false;
+ }
+ });
+ }
+ menu.showRelativeToUpward((UIObject) statusBar_.getScope());
+ }
+
+ private MenuItem createMenuItemForType(final TextFileType type)
+ {
+ SafeHtmlBuilder labelBuilder = new SafeHtmlBuilder();
+ labelBuilder.appendEscaped(type.getLabel());
+
+ MenuItem menuItem = new MenuItem(
+ labelBuilder.toSafeHtml(),
+ new Command()
+ {
+ public void execute()
+ {
+ docUpdateSentinel_.changeFileType(
+ type.getTypeId(),
+ new SaveProgressIndicator(null, type, null));
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute()
+ {
+ focus();
+ }
+ });
+ }
+ });
+
+ return menuItem;
+ }
+
+ private MenuItem addFunctionsToMenu(StatusBarPopupMenu menu,
+ final JsArray<Scope> funcs,
+ String indent,
+ Scope defaultFunction,
+ boolean includeNoFunctionsMessage)
+ {
+ MenuItem defaultMenuItem = null;
+
+ if (funcs.length() == 0 && includeNoFunctionsMessage)
+ {
+ String type = fileType_.canExecuteChunks() ? "chunks" : "functions";
+ MenuItem noFunctions = new MenuItem("(No " + type + " defined)",
+ false,
+ (Command) null);
+ noFunctions.setEnabled(false);
+ noFunctions.getElement().addClassName("disabled");
+ menu.addItem(noFunctions);
+ }
+
+ for (int i = 0; i < funcs.length(); i++)
+ {
+ final Scope func = funcs.get(i);
+
+ String childIndent = indent;
+ if (!StringUtil.isNullOrEmpty(func.getLabel()))
+ {
+ SafeHtmlBuilder labelBuilder = new SafeHtmlBuilder();
+ labelBuilder.appendHtmlConstant(indent);
+ labelBuilder.appendEscaped(func.getLabel());
+
+ final MenuItem menuItem = new MenuItem(
+ labelBuilder.toSafeHtml(),
+ new Command()
+ {
+ public void execute()
+ {
+ docDisplay_.navigateToPosition(toSourcePosition(func),
+ true);
+ }
+ });
+ menu.addItem(menuItem);
+
+ childIndent = indent + " ";
+
+ if (defaultFunction != null && defaultMenuItem == null &&
+ func.getLabel().equals(defaultFunction.getLabel()) &&
+ func.getPreamble().getRow() == defaultFunction.getPreamble().getRow() &&
+ func.getPreamble().getColumn() == defaultFunction.getPreamble().getColumn())
+ {
+ defaultMenuItem = menuItem;
+ }
+ }
+
+ MenuItem childDefaultMenuItem = addFunctionsToMenu(
+ menu,
+ func.getChildren(),
+ childIndent,
+ defaultMenuItem == null ? defaultFunction : null,
+ false);
+ if (childDefaultMenuItem != null)
+ defaultMenuItem = childDefaultMenuItem;
+ }
+
+ return defaultMenuItem;
+ }
+
+ private void updateStatusBarLanguage()
+ {
+ statusBar_.getLanguage().setValue(fileType_.getLabel());
+ boolean isR = fileType_.canShowScopeTree();
+ statusBar_.setScopeVisible(isR);
+ if (isR)
+ updateCurrentScope();
+ }
+
+ private void updateStatusBarPosition()
+ {
+ Position pos = docDisplay_.getCursorPosition();
+ statusBar_.getPosition().setValue((pos.getRow() + 1) + ":" +
+ (pos.getColumn() + 1));
+
+ if (fileType_.canShowScopeTree())
+ updateCurrentScope();
+ }
+
+ private void updateCurrentScope()
+ {
+ Scheduler.get().scheduleDeferred(
+ new ScheduledCommand()
+ {
+ public void execute()
+ {
+ // special handing for presentations since we extract
+ // the slide structure in a differerent manner than
+ // the editor scope trees
+ if (fileType_.isRpres())
+ {
+ statusBar_.getScope().setValue(
+ presentationHelper_.getCurrentSlide());
+ statusBar_.setScopeType(StatusBar.SCOPE_SLIDE);
+
+ }
+ else
+ {
+ Scope function = docDisplay_.getCurrentScope();
+ String label = function != null
+ ? function.getLabel()
+ : null;
+ statusBar_.getScope().setValue(label);
+
+ if (function != null)
+ {
+ boolean useChunk =
+ function.isChunk() ||
+ (fileType_.isRnw() && function.isTopLevel());
+ if (useChunk)
+ statusBar_.setScopeType(StatusBar.SCOPE_CHUNK);
+ else if (function.isSection())
+ statusBar_.setScopeType(StatusBar.SCOPE_SECTION);
+ else
+ statusBar_.setScopeType(StatusBar.SCOPE_FUNCTION);
+ }
+ }
+ }
+ });
+ }
+
+ private String getNameFromDocument(SourceDocument document,
+ Provider<String> defaultNameProvider)
+ {
+ if (document.getPath() != null)
+ return FileSystemItem.getNameFromPath(document.getPath());
+
+ String name = document.getProperties().getString("tempName");
+ if (!StringUtil.isNullOrEmpty(name))
+ return name;
+
+ String defaultName = defaultNameProvider.get();
+ docUpdateSentinel_.setProperty("tempName", defaultName, null);
+ return defaultName;
+ }
+
+ public long getFileSizeLimit()
+ {
+ return 2 * 1024 * 1024;
+ }
+
+ public long getLargeFileSize()
+ {
+ return 512 * 1024;
+ }
+
+ public void insertCode(String source, boolean blockMode)
+ {
+ docDisplay_.insertCode(source, blockMode);
+ }
+
+ public HashSet<AppCommand> getSupportedCommands()
+ {
+ return fileType_.getSupportedCommands(commands_);
+ }
+
+ @Override
+ public boolean canCompilePdf()
+ {
+ return fileType_.canCompilePDF();
+ }
+
+
+ @Override
+ public void verifyCppPrerequisites()
+ {
+ // NOTE: will be a no-op for non-c/c++ file types
+ cppHelper_.checkBuildCppDependencies(this, view_, fileType_);
+ }
+
+
+ public void focus()
+ {
+ docDisplay_.focus();
+ }
+
+ public String getSelectedText()
+ {
+ if (docDisplay_.hasSelection())
+ return docDisplay_.getSelectionValue();
+ else
+ return "";
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return view_.addEnsureVisibleHandler(handler);
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return view_.addEnsureHeightHandler(handler);
+ }
+
+ public HandlerRegistration addCloseHandler(CloseHandler<java.lang.Void> handler)
+ {
+ return handlers_.addHandler(CloseEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ public void onActivate()
+ {
+ // IMPORTANT NOTE: most of this logic is duplicated in
+ // CodeBrowserEditingTarget (no straightforward way to create a
+ // re-usable implementation) so changes here need to be synced
+
+ // If we're already hooked up for some reason, unhook.
+ // This shouldn't happen though.
+ if (commandHandlerReg_ != null)
+ {
+ Debug.log("Warning: onActivate called twice without intervening onDeactivate");
+ commandHandlerReg_.removeHandler();
+ commandHandlerReg_ = null;
+ }
+ commandHandlerReg_ = commandBinder.bind(commands_, this);
+
+ Scheduler.get().scheduleFinally(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ // This has to be executed in a scheduleFinally because
+ // Source.manageCommands gets called after this.onActivate,
+ // and if we're going from a non-editor (like data view) to
+ // an editor, setEnabled(true) will be called on the command
+ // in manageCommands.
+ commands_.reopenSourceDocWithEncoding().setEnabled(
+ docUpdateSentinel_.getPath() != null);
+ }
+ });
+
+ view_.onActivate();
+ }
+
+ public void onDeactivate()
+ {
+ // IMPORTANT NOTE: most of this logic is duplicated in
+ // CodeBrowserEditingTarget (no straightforward way to create a
+ // re-usable implementation) so changes here need to be synced
+
+ externalEditCheckInvalidation_.invalidate();
+
+ commandHandlerReg_.removeHandler();
+ commandHandlerReg_ = null;
+
+ // switching tabs is a navigation action
+ try
+ {
+ docDisplay_.recordCurrentNavigationPosition();
+ }
+ catch(Exception e)
+ {
+ Debug.log("Exception recording nav position: " + e.toString());
+ }
+ }
+
+ @Override
+ public void onInitiallyLoaded()
+ {
+ checkForExternalEdit();
+ }
+
+ public boolean onBeforeDismiss()
+ {
+ Command closeCommand = new Command() {
+ public void execute()
+ {
+ CloseEvent.fire(TextEditingTarget.this, null);
+ }
+ };
+
+ if (dirtyState_.getValue())
+ saveWithPrompt(closeCommand, null);
+ else
+ closeCommand.execute();
+
+ return false;
+ }
+
+ public void save()
+ {
+ save(new Command() {
+ @Override
+ public void execute()
+ {
+ }});
+ }
+
+ public void save(Command onCompleted)
+ {
+ saveThenExecute(null, CommandUtil.join(postSaveCommand(),
+ onCompleted));
+ }
+
+ public void saveWithPrompt(final Command command, final Command onCancelled)
+ {
+ view_.ensureVisible();
+
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_WARNING,
+ getName().getValue() + " - Unsaved Changes",
+ "The document '" + getName().getValue() +
+ "' has unsaved changes.\n\n" +
+ "Do you want to save these changes?",
+ true,
+ new Operation() {
+ public void execute() { saveThenExecute(null, command); }
+ },
+ new Operation() {
+ public void execute() { command.execute(); }
+ },
+ new Operation() {
+ public void execute() {
+ if (onCancelled != null)
+ onCancelled.execute();
+ }
+ },
+ "Save",
+ "Don't Save",
+ true);
+ }
+
+ public void revertChanges(Command onCompleted)
+ {
+ docUpdateSentinel_.revert(onCompleted);
+ }
+
+ private void saveThenExecute(String encodingOverride, final Command command)
+ {
+ checkCompilePdfDependencies();
+
+ final String path = docUpdateSentinel_.getPath();
+ if (path == null)
+ {
+ saveNewFile(null, encodingOverride, command);
+ return;
+ }
+
+ withEncodingRequiredUnlessAscii(
+ encodingOverride,
+ new CommandWithArg<String>()
+ {
+ public void execute(String encoding)
+ {
+ fixupCodeBeforeSaving();
+
+ docUpdateSentinel_.save(path,
+ null,
+ encoding,
+ new SaveProgressIndicator(
+ FileSystemItem.createFile(path),
+ null,
+ command
+ ));
+ }
+ });
+ }
+
+ private void saveNewFile(final String suggestedPath,
+ String encodingOverride,
+ final Command executeOnSuccess)
+ {
+ withEncodingRequiredUnlessAscii(
+ encodingOverride,
+ new CommandWithArg<String>()
+ {
+ public void execute(String encoding)
+ {
+ saveNewFileWithEncoding(suggestedPath,
+ encoding,
+ executeOnSuccess);
+ }
+ });
+ }
+
+ private void withEncodingRequiredUnlessAscii(
+ final String encodingOverride,
+ final CommandWithArg<String> command)
+ {
+ final String encoding = StringUtil.firstNotNullOrEmpty(new String[] {
+ encodingOverride,
+ docUpdateSentinel_.getEncoding(),
+ prefs_.defaultEncoding().getValue()
+ });
+
+ if (StringUtil.isNullOrEmpty(encoding))
+ {
+ if (docUpdateSentinel_.isAscii())
+ {
+ // Don't bother asking when it's just ASCII
+ command.execute(null);
+ }
+ else
+ {
+ withChooseEncoding(session_.getSessionInfo().getSystemEncoding(),
+ new CommandWithArg<String>()
+ {
+ public void execute(String newEncoding)
+ {
+ command.execute(newEncoding);
+ }
+ });
+ }
+ }
+ else
+ {
+ command.execute(encoding);
+ }
+ }
+
+ private void withChooseEncoding(final String defaultEncoding,
+ final CommandWithArg<String> command)
+ {
+ view_.ensureVisible();;
+
+ server_.iconvlist(new SimpleRequestCallback<IconvListResult>()
+ {
+ @Override
+ public void onResponseReceived(IconvListResult response)
+ {
+ // Stupid compiler. Use this Value shim to make the dialog available
+ // in its own handler.
+ final HasValue<ChooseEncodingDialog> d = new Value<ChooseEncodingDialog>(null);
+ d.setValue(new ChooseEncodingDialog(
+ response.getCommon(),
+ response.getAll(),
+ defaultEncoding,
+ false,
+ true,
+ new OperationWithInput<String>()
+ {
+ public void execute(String newEncoding)
+ {
+ if (newEncoding == null)
+ return;
+
+ if (d.getValue().isSaveAsDefault())
+ {
+ prefs_.defaultEncoding().setGlobalValue(newEncoding);
+ prefs_.writeUIPrefs();
+ }
+
+ command.execute(newEncoding);
+ }
+ }));
+ d.getValue().showModal();
+ }
+ });
+
+ }
+
+ private void saveNewFileWithEncoding(String suggestedPath,
+ final String encoding,
+ final Command executeOnSuccess)
+ {
+ view_.ensureVisible();
+
+ FileSystemItem fsi;
+ if (suggestedPath != null)
+ fsi = FileSystemItem.createFile(suggestedPath);
+ else
+ fsi = getSaveFileDefaultDir();
+
+ fileDialogs_.saveFile(
+ "Save File - " + getName().getValue(),
+ fileContext_,
+ fsi,
+ fileType_.getDefaultExtension(),
+ false,
+ new ProgressOperationWithInput<FileSystemItem>()
+ {
+ public void execute(final FileSystemItem saveItem,
+ ProgressIndicator indicator)
+ {
+ if (saveItem == null)
+ return;
+
+ try
+ {
+ workbenchContext_.setDefaultFileDialogDir(
+ saveItem.getParentPath());
+
+ final TextFileType fileType =
+ fileTypeRegistry_.getTextTypeForFile(saveItem);
+
+ final Command saveCommand = new Command() {
+
+ @Override
+ public void execute()
+ {
+ // breakpoints are file-specific, so when saving as
+ // a different file, clear the display of breakpoints
+ // from the old file name
+ if (!getPath().equals(saveItem.getPath()))
+ {
+ docDisplay_.removeAllBreakpoints();
+ }
+
+ fixupCodeBeforeSaving();
+
+ docUpdateSentinel_.save(
+ saveItem.getPath(),
+ fileType.getTypeId(),
+ encoding,
+ new SaveProgressIndicator(saveItem,
+ fileType,
+ executeOnSuccess));
+
+ events_.fireEvent(
+ new SourceFileSavedEvent(saveItem.getPath()));
+ }
+
+ };
+
+ // if we are switching from an R file type
+ // to a non-R file type then confirm
+ if (fileType_.isR() && !fileType.isR())
+ {
+ globalDisplay_.showYesNoMessage(
+ MessageDialog.WARNING,
+ "Confirm Change File Type",
+ "This file was created as an R script however " +
+ "the file extension you specified will change " +
+ "it into another file type that will no longer " +
+ "open as an R script.\n\n" +
+ "Are you sure you want to change the type of " +
+ "the file so that it is no longer an R script?",
+ new Operation() {
+
+ @Override
+ public void execute()
+ {
+ saveCommand.execute();
+ }
+ },
+ false);
+ }
+ else
+ {
+ saveCommand.execute();
+ }
+ }
+ catch (Exception e)
+ {
+ indicator.onError(e.toString());
+ return;
+ }
+
+ indicator.onCompleted();
+ }
+ });
+ }
+
+
+ private void fixupCodeBeforeSaving()
+ {
+ int lineCount = docDisplay_.getRowCount();
+ if (lineCount < 1)
+ return;
+
+ if (prefs_.stripTrailingWhitespace().getValue())
+ {
+ String code = docDisplay_.getCode();
+ Pattern pattern = Pattern.create("[ \t]+$");
+ String strippedCode = pattern.replaceAll(code, "");
+ if (!strippedCode.equals(code))
+ docDisplay_.setCode(strippedCode, true);
+ }
+
+ if (prefs_.autoAppendNewline().getValue())
+ {
+ String lastLine = docDisplay_.getLine(lineCount - 1);
+ if (lastLine.length() != 0)
+ docDisplay_.insertCode(docDisplay_.getEnd().getEnd(), "\n");
+ }
+ }
+
+ private FileSystemItem getSaveFileDefaultDir()
+ {
+ FileSystemItem fsi = null;
+ SessionInfo si = session_.getSessionInfo();
+
+ if (si.getBuildToolsType() == SessionInfo.BUILD_TOOLS_PACKAGE)
+ {
+ FileSystemItem pkg = FileSystemItem.createDir(si.getBuildTargetDir());
+
+ if (fileType_.isR())
+ {
+ fsi = FileSystemItem.createDir(pkg.completePath("R"));
+ }
+ else if (fileType_.isC() && si.getHasPackageSrcDir())
+ {
+ fsi = FileSystemItem.createDir(pkg.completePath("src"));
+ }
+ else if (fileType_.isRd())
+ {
+ fsi = FileSystemItem.createDir(pkg.completePath("man"));
+ }
+ else if ((fileType_.isRnw() || fileType_.isRmd()) &&
+ si.getHasPackageVignetteDir())
+ {
+ fsi = FileSystemItem.createDir(pkg.completePath("vignettes"));
+ }
+ }
+
+ if (fsi == null)
+ fsi = workbenchContext_.getDefaultFileDialogDir();
+
+ return fsi;
+ }
+
+
+
+ public void onDismiss()
+ {
+ docUpdateSentinel_.stop();
+
+ if (spelling_ != null)
+ spelling_.onDismiss();
+
+ while (releaseOnDismiss_.size() > 0)
+ releaseOnDismiss_.remove(0).removeHandler();
+
+ codeExecution_.detachLastExecuted();
+ }
+
+ public ReadOnlyValue<Boolean> dirtyState()
+ {
+ return dirtyState_;
+ }
+
+ @Override
+ public boolean isSaveCommandActive()
+ {
+ return
+ // standard check of dirty state
+ (dirtyState().getValue() == true) ||
+
+ // empty untitled document (allow for immediate save)
+ ((getPath() == null) && docDisplay_.getCode().isEmpty()) ||
+
+ // source on save is active
+ (fileType_.canSourceOnSave() && docUpdateSentinel_.sourceOnSave());
+ }
+
+ public Widget asWidget()
+ {
+ return (Widget) view_;
+ }
+
+ public String getId()
+ {
+ return id_;
+ }
+
+ @Override
+ public void adaptToExtendedFileType(String extendedType)
+ {
+ view_.adaptToExtendedFileType(extendedType);
+ extendedType_ = extendedType;
+ }
+
+ @Override
+ public String getExtendedFileType()
+ {
+ return extendedType_;
+ }
+
+ public HasValue<String> getName()
+ {
+ return name_;
+ }
+
+ public String getTitle()
+ {
+ return getName().getValue();
+ }
+
+ public String getPath()
+ {
+ if (docUpdateSentinel_ == null)
+ return null;
+ return docUpdateSentinel_.getPath();
+ }
+
+ public String getContext()
+ {
+ return null;
+ }
+
+ public ImageResource getIcon()
+ {
+ return fileType_.getDefaultIcon();
+ }
+
+ public String getTabTooltip()
+ {
+ return getPath();
+ }
+
+ @Handler
+ void onCheckSpelling()
+ {
+ spelling_.checkSpelling();
+ }
+
+ @Handler
+ void onDebugDumpContents()
+ {
+ view_.debug_dumpContents();
+ }
+
+ @Handler
+ void onDebugImportDump()
+ {
+ view_.debug_importDump();
+ }
+
+ @Handler
+ void onReopenSourceDocWithEncoding()
+ {
+ withChooseEncoding(
+ docUpdateSentinel_.getEncoding(),
+ new CommandWithArg<String>()
+ {
+ public void execute(String encoding)
+ {
+ docUpdateSentinel_.reopenWithEncoding(encoding);
+ }
+ });
+ }
+
+ @Handler
+ void onSaveSourceDoc()
+ {
+ saveThenExecute(null, postSaveCommand());
+ }
+
+ @Handler
+ void onSaveSourceDocAs()
+ {
+ saveNewFile(docUpdateSentinel_.getPath(),
+ null,
+ postSaveCommand());
+ }
+
+ @Handler
+ void onSaveSourceDocWithEncoding()
+ {
+ withChooseEncoding(
+ StringUtil.firstNotNullOrEmpty(new String[] {
+ docUpdateSentinel_.getEncoding(),
+ prefs_.defaultEncoding().getValue(),
+ session_.getSessionInfo().getSystemEncoding()
+ }),
+ new CommandWithArg<String>()
+ {
+ public void execute(String encoding)
+ {
+ saveThenExecute(encoding, postSaveCommand());
+ }
+ });
+ }
+
+ @Handler
+ void onPrintSourceDoc()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ docDisplay_.print();
+ }
+ });
+ }
+
+ @Handler
+ void onVcsFileDiff()
+ {
+ Command showDiffCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ events_.fireEvent(new ShowVcsDiffEvent(
+ FileSystemItem.createFile(docUpdateSentinel_.getPath())));
+ }
+ };
+
+ if (dirtyState_.getValue())
+ saveWithPrompt(showDiffCommand, null);
+ else
+ showDiffCommand.execute();
+ }
+
+ @Handler
+ void onVcsFileLog()
+ {
+ events_.fireEvent(new ShowVcsHistoryEvent(
+ FileSystemItem.createFile(docUpdateSentinel_.getPath())));
+ }
+
+ @Handler
+ void onVcsFileRevert()
+ {
+ events_.fireEvent(new VcsRevertFileEvent(
+ FileSystemItem.createFile(docUpdateSentinel_.getPath())));
+ }
+
+ @Handler
+ void onVcsViewOnGitHub()
+ {
+ fireVcsViewOnGithubEvent(GitHubViewRequest.ViewType.View);
+ }
+
+ @Handler
+ void onVcsBlameOnGitHub()
+ {
+ fireVcsViewOnGithubEvent(GitHubViewRequest.ViewType.Blame);
+ }
+
+ private void fireVcsViewOnGithubEvent(GitHubViewRequest.ViewType type)
+ {
+ FileSystemItem file =
+ FileSystemItem.createFile(docUpdateSentinel_.getPath());
+
+ if (docDisplay_.getSelectionValue().length() > 0)
+ {
+ int start = docDisplay_.getSelectionStart().getRow() + 1;
+ int end = docDisplay_.getSelectionEnd().getRow() + 1;
+ events_.fireEvent(new VcsViewOnGitHubEvent(
+ new GitHubViewRequest(file, type, start, end)));
+ }
+ else
+ {
+ events_.fireEvent(new VcsViewOnGitHubEvent(
+ new GitHubViewRequest(file, type)));
+ }
+ }
+
+ @Handler
+ void onExtractLocalVariable()
+ {
+ docDisplay_.focus();
+
+ String initialSelection = docDisplay_.getSelectionValue();
+ final String refactoringName = "Extract local variable";
+ final String pleaseSelectCodeMessage = "Please select the code to " +
+ "extract into a variable.";
+ if (checkSelectionAndAlert(refactoringName,
+ pleaseSelectCodeMessage,
+ initialSelection)) return;
+
+ docDisplay_.fitSelectionToLines(false);
+
+ final String code = docDisplay_.getSelectionValue();
+ if (checkSelectionAndAlert(refactoringName,
+ pleaseSelectCodeMessage,
+ code))
+ return;
+
+ // get the first line of the selection and calculate it's indentation
+ String firstLine = docDisplay_.getLine(
+ docDisplay_.getSelectionStart().getRow());
+ final String indentation = extractIndentation(firstLine);
+
+ // used to parse the code
+ server_.detectFreeVars(code,
+ new RefactorServerRequestCallback(refactoringName)
+ {
+ @Override
+ void doExtract(JsArrayString response)
+ {
+ globalDisplay_.promptForText(
+ refactoringName,
+ "Variable Name",
+ "",
+ new OperationWithInput<String>()
+ {
+ public void execute(String input)
+ {
+ final String extractedCode = indentation
+ + input.trim()
+ + " <- "
+ + code
+ + "\n";
+ InputEditorPosition insertPosition = docDisplay_
+ .getSelection()
+ .extendToLineStart()
+ .getStart();
+ docDisplay_.replaceSelection(
+ input.trim());
+ docDisplay_.insertCode(
+ insertPosition,
+ extractedCode);
+ }
+ }
+ );
+ }
+ }
+ );
+ }
+
+ @Handler
+ void onExtractFunction()
+ {
+ docDisplay_.focus();
+
+ String initialSelection = docDisplay_.getSelectionValue();
+ final String refactoringName = "Extract Function";
+ final String pleaseSelectCodeMessage = "Please select the code to " +
+ "extract into a function.";
+ if (checkSelectionAndAlert(refactoringName,
+ pleaseSelectCodeMessage,
+ initialSelection)) return;
+
+ docDisplay_.fitSelectionToLines(false);
+
+ final String code = docDisplay_.getSelectionValue();
+ if (checkSelectionAndAlert(refactoringName,
+ pleaseSelectCodeMessage,
+ code)) return;
+
+ final String indentation = extractIndentation(code);
+ server_.detectFreeVars(code,
+ new RefactorServerRequestCallback(refactoringName)
+ {
+ @Override
+ void doExtract(final JsArrayString response)
+ {
+ globalDisplay_.promptForText(
+ refactoringName,
+ "Function Name",
+ "",
+ new OperationWithInput<String>()
+ {
+ public void execute(String input)
+ {
+ String prefix;
+ if (docDisplay_.getSelectionOffset(true) == 0)
+ prefix = "";
+ else prefix = "\n";
+ String args = response != null ? response.join(", ")
+ : "";
+ docDisplay_.replaceSelection(
+ prefix
+ + indentation
+ + input.trim()
+ + " <- "
+ + "function (" + args + ") {\n"
+ + StringUtil.indent(code, " ")
+ + "\n"
+ + indentation
+ + "}");
+ }
+ }
+ );
+ }
+ }
+ );
+ }
+
+ private boolean checkSelectionAndAlert(String refactoringName,
+ String pleaseSelectCodeMessage,
+ String selection)
+ {
+ if (isSelectionValueEmpty(selection))
+ {
+ globalDisplay_.showErrorMessage(refactoringName,
+ pleaseSelectCodeMessage);
+ return true;
+ }
+ return false;
+ }
+
+ private String extractIndentation(String code)
+ {
+ Pattern leadingWhitespace = Pattern.create("^(\\s*)");
+ Match match = leadingWhitespace.match(code, 0);
+ return match == null ? "" : match.getGroup(1);
+ }
+
+ private boolean isSelectionValueEmpty(String selection)
+ {
+ return selection == null || selection.trim().length() == 0;
+ }
+
+ @Handler
+ void onJumpToMatching()
+ {
+ docDisplay_.jumpToMatching();
+ }
+
+ @Handler
+ void onCommentUncomment()
+ {
+ if (fileType_.isCpp())
+ docDisplay_.toggleCommentLines();
+ else if (isCursorInTexMode())
+ doCommentUncomment("%");
+ else if (isCursorInRMode())
+ doCommentUncomment("#");
+ }
+
+ private void doCommentUncomment(String c)
+ {
+ boolean selectionCollapsed = docDisplay_.isSelectionCollapsed();
+ docDisplay_.fitSelectionToLines(true);
+ String selection = docDisplay_.getSelectionValue();
+
+ // If any line's first non-whitespace character is not #, then the whole
+ // selection should be commented. Exception: If the whole selection is
+ // whitespace, then we comment out the whitespace.
+ Match match = Pattern.create("^\\s*[^" + c + "\\s]").match(selection, 0);
+ boolean uncomment = match == null && selection.trim().length() != 0;
+ if (uncomment)
+ selection = selection.replaceAll("((^|\\n)\\s*)" + c + " ?", "$1");
+ else
+ {
+ selection = c + " " + selection.replaceAll("\\n", "\n" + c + " ");
+
+ // If the selection ends at the very start of a line, we don't want
+ // to comment out that line. This enables Shift+DownArrow to select
+ // one line at a time.
+ if (selection.endsWith("\n" + c + " "))
+ selection = selection.substring(0, selection.length() - 2);
+ }
+
+ docDisplay_.replaceSelection(selection);
+
+ if (selectionCollapsed)
+ docDisplay_.collapseSelection(true);
+
+ docDisplay_.focus();
+ }
+
+ @Handler
+ void onReindent()
+ {
+ docDisplay_.reindent();
+ docDisplay_.focus();
+ }
+
+ @Handler
+ void onReflowComment()
+ {
+ if (fileType_.isCpp())
+ {
+ String currentLine = docDisplay_.getLine(
+ docDisplay_.getCursorPosition().getRow());
+ if (currentLine.startsWith(" *"))
+ doReflowComment("( \\*[^/])", false);
+ else
+ doReflowComment("(//)");
+ }
+
+ else
+ doReflowComment("(#)");
+ }
+
+ @Handler
+ void onDebugBreakpoint()
+ {
+ docDisplay_.toggleBreakpointAtCursor();
+ }
+
+ @Handler
+ void onShinyAppsDeploy()
+ {
+ events_.fireEvent(new ShinyAppsActionEvent(
+ ShinyAppsActionEvent.ACTION_TYPE_DEPLOY,
+ docUpdateSentinel_.getPath()));
+ }
+
+ @Handler
+ void onShinyAppsTerminate()
+ {
+ events_.fireEvent(new ShinyAppsActionEvent(
+ ShinyAppsActionEvent.ACTION_TYPE_TERMINATE,
+ docUpdateSentinel_.getPath()));
+ }
+
+ void doReflowComment(String commentPrefix)
+ {
+ doReflowComment(commentPrefix, true);
+ }
+
+ void doReflowComment(String commentPrefix, boolean multiParagraphIndent)
+ {
+ docDisplay_.focus();
+
+ InputEditorSelection originalSelection = docDisplay_.getSelection();
+ InputEditorSelection selection = originalSelection;
+
+ if (selection.isEmpty())
+ {
+ selection = selection.growToIncludeLines("^\\s*" + commentPrefix + ".*$");
+ }
+ else
+ {
+ selection = selection.shrinkToNonEmptyLines();
+ selection = selection.extendToLineStart();
+ selection = selection.extendToLineEnd();
+ }
+ if (selection.isEmpty())
+ return;
+
+ reflowComments(commentPrefix,
+ multiParagraphIndent,
+ selection,
+ originalSelection.isEmpty() ?
+ originalSelection.getStart() :
+ null);
+ }
+
+ private Position selectionToPosition(InputEditorPosition pos)
+ {
+ return docDisplay_.selectionToPosition(pos);
+ }
+
+ private void reflowComments(String commentPrefix,
+ final boolean multiParagraphIndent,
+ InputEditorSelection selection,
+ final InputEditorPosition cursorPos)
+ {
+ String code = docDisplay_.getCode(selection);
+ String[] lines = code.split("\n");
+ String prefix = StringUtil.getCommonPrefix(lines, true);
+ Pattern pattern = Pattern.create("^\\s*" + commentPrefix + "+('?)\\s*");
+ Match match = pattern.match(prefix, 0);
+ // Selection includes non-comments? Abort.
+ if (match == null)
+ return;
+ prefix = match.getValue();
+ final boolean roxygen = match.hasGroup(1);
+
+ int cursorRowIndex = 0;
+ int cursorColIndex = 0;
+ if (cursorPos != null)
+ {
+ cursorRowIndex = selectionToPosition(cursorPos).getRow() -
+ selectionToPosition(selection.getStart()).getRow();
+ cursorColIndex =
+ Math.max(0, cursorPos.getPosition() - prefix.length());
+ }
+ final WordWrapCursorTracker wwct = new WordWrapCursorTracker(
+ cursorRowIndex, cursorColIndex);
+
+ int maxLineLength =
+ prefs_.printMarginColumn().getValue() - prefix.length();
+
+ WordWrap wordWrap = new WordWrap(maxLineLength, false)
+ {
+ @Override
+ protected boolean forceWrapBefore(String line)
+ {
+ String trimmed = line.trim();
+ if (roxygen && trimmed.startsWith("@") && !trimmed.startsWith("@@"))
+ {
+ // Roxygen tags always need to be at the start of a line. If
+ // there is content immediately following the roxygen tag, then
+ // content should be wrapped until the next roxygen tag is
+ // encountered.
+
+ indent_ = "";
+ if (TAG_WITH_CONTENTS.match(line, 0) != null)
+ {
+ indentRestOfLines_ = true;
+ }
+ return true;
+ }
+ // empty line disables indentation
+ else if (!multiParagraphIndent && (line.trim().length() == 0))
+ {
+ indent_ = "";
+ indentRestOfLines_ = false;
+ }
+
+ return super.forceWrapBefore(line);
+ }
+
+ @Override
+ protected void onChunkWritten(String chunk,
+ int insertionRow,
+ int insertionCol,
+ int indexInOriginalString)
+ {
+ if (indentRestOfLines_)
+ {
+ indentRestOfLines_ = false;
+ indent_ = " "; // TODO: Use real indent from settings
+ }
+
+ wwct.onChunkWritten(chunk, insertionRow, insertionCol,
+ indexInOriginalString);
+ }
+
+ private boolean indentRestOfLines_ = false;
+ private Pattern TAG_WITH_CONTENTS = Pattern.create("@\\w+\\s+[^\\s]");
+ };
+
+ for (String line : lines)
+ {
+ String content = line.substring(Math.min(line.length(),
+ prefix.length()));
+
+ if (content.matches("^\\s*\\@examples\\b.*$"))
+ wordWrap.setWrappingEnabled(false);
+ else if (content.trim().startsWith("@"))
+ wordWrap.setWrappingEnabled(true);
+
+ wwct.onBeginInputRow();
+ wordWrap.appendLine(content);
+ }
+
+ String wrappedString = wordWrap.getOutput();
+
+ StringBuilder finalOutput = new StringBuilder();
+ for (String line : StringUtil.getLineIterator(wrappedString))
+ finalOutput.append(prefix).append(line).append("\n");
+ // Remove final \n
+ if (finalOutput.length() > 0)
+ finalOutput.deleteCharAt(finalOutput.length()-1);
+
+ String reflowed = finalOutput.toString();
+
+ docDisplay_.setSelection(selection);
+ if (!reflowed.equals(code))
+ {
+ docDisplay_.replaceSelection(reflowed);
+ }
+
+ if (cursorPos != null)
+ {
+ if (wwct.getResult() != null)
+ {
+ int row = wwct.getResult().getY();
+ int col = wwct.getResult().getX();
+ row += selectionToPosition(selection.getStart()).getRow();
+ col += prefix.length();
+ Position pos = Position.create(row, col);
+ docDisplay_.setSelection(docDisplay_.createSelection(pos, pos));
+ }
+ else
+ {
+ docDisplay_.collapseSelection(false);
+ }
+ }
+ }
+
+ @Handler
+ void onExecuteCodeWithoutFocus()
+ {
+ codeExecution_.executeSelection(false);
+ }
+
+ @Handler
+ void onExecuteCode()
+ {
+ codeExecution_.executeSelection(true);
+ }
+
+ @Override
+ public String extractCode(DocDisplay docDisplay, Range range)
+ {
+ Scope sweaveChunk = null;
+
+ if (fileType_.canExecuteChunks())
+ sweaveChunk = scopeHelper_.getCurrentSweaveChunk(range.getStart());
+
+ String code = sweaveChunk != null
+ ? scopeHelper_.getSweaveChunkText(sweaveChunk, range)
+ : docDisplay_.getCode(range.getStart(), range.getEnd());
+
+ return code;
+ }
+
+
+
+ @Handler
+ void onExecuteAllCode()
+ {
+ sourceActiveDocument(true);
+ }
+
+ @Handler
+ void onExecuteToCurrentLine()
+ {
+ docDisplay_.focus();
+
+
+ int row = docDisplay_.getSelectionEnd().getRow();
+ int col = docDisplay_.getLength(row);
+
+ codeExecution_.executeRange(Range.fromPoints(Position.create(0, 0),
+ Position.create(row, col)));
+ }
+
+ @Handler
+ void onExecuteFromCurrentLine()
+ {
+ docDisplay_.focus();
+
+ int startRow = docDisplay_.getSelectionStart().getRow();
+ int startColumn = 0;
+ Position start = Position.create(startRow, startColumn);
+
+ codeExecution_.executeRange(Range.fromPoints(start, endPosition()));
+ }
+
+ @Handler
+ void onExecuteCurrentFunction()
+ {
+ docDisplay_.focus();
+
+ // HACK: This is just to force the entire function tree to be built.
+ // It's the easiest way to make sure getCurrentScope() returns
+ // a Scope with an end.
+ docDisplay_.getScopeTree();
+ Scope currentFunction = docDisplay_.getCurrentFunction();
+
+ // Check if we're at the top level (i.e. not in a function), or in
+ // an unclosed function
+ if (currentFunction == null || currentFunction.getEnd() == null)
+ return;
+
+ Position start = currentFunction.getPreamble();
+ Position end = currentFunction.getEnd();
+
+ codeExecution_.executeRange(Range.fromPoints(start, end));
+ }
+
+ @Handler
+ void onExecuteCurrentSection()
+ {
+ docDisplay_.focus();
+
+ // Determine the current section.
+ docDisplay_.getScopeTree();
+ Scope currentSection = docDisplay_.getCurrentSection();
+ if (currentSection == null)
+ return;
+
+ // Determine the start and end of the section
+ Position start = currentSection.getBodyStart();
+ if (start == null)
+ start = Position.create(0, 0);
+ Position end = currentSection.getEnd();
+ if (end == null)
+ end = endPosition();
+
+ codeExecution_.executeRange(Range.fromPoints(start, end));
+ }
+
+ private Position endPosition()
+ {
+ int endRow = Math.max(0, docDisplay_.getRowCount() - 1);
+ int endColumn = docDisplay_.getLength(endRow);
+ return Position.create(endRow, endColumn);
+ }
+
+ @Handler
+ void onInsertChunk()
+ {
+ Position pos = moveCursorToNextInsertLocation();
+ InsertChunkInfo insertChunkInfo = docDisplay_.getInsertChunkInfo();
+ if (insertChunkInfo != null)
+ {
+ docDisplay_.insertCode(insertChunkInfo.getValue(), false);
+ Position cursorPosition = insertChunkInfo.getCursorPosition();
+ docDisplay_.setCursorPosition(Position.create(
+ pos.getRow() + cursorPosition.getRow(),
+ cursorPosition.getColumn()));
+ docDisplay_.focus();
+ }
+ else
+ {
+ assert false : "Mode did not have insertChunkInfo available";
+ }
+ }
+
+ @Handler
+ void onInsertSection()
+ {
+ globalDisplay_.promptForText(
+ "Insert Section",
+ "Section label:",
+ "",
+ new OperationWithInput<String>() {
+ @Override
+ public void execute(String label)
+ {
+ // move cursor to next insert location
+ Position pos = moveCursorToNextInsertLocation();
+
+ // truncate length to print margin - 5
+ int printMarginColumn = prefs_.printMarginColumn().getValue();
+ int length = printMarginColumn - 5;
+
+ // truncate label to maxLength - 10 (but always allow at
+ // least 20 chars for the label)
+ int maxLabelLength = length - 10;
+ maxLabelLength = Math.max(maxLabelLength, 20);
+ if (label.length() > maxLabelLength)
+ label = label.substring(0, maxLabelLength-1);
+
+ // prefix
+ String prefix = "# " + label + " ";
+
+ // fill to maxLength (bit ensure at least 4 fill characters
+ // so the section parser is sure to pick it up)
+ StringBuffer sectionLabel = new StringBuffer();
+ sectionLabel.append("\n");
+ sectionLabel.append(prefix);
+ int fillChars = length - prefix.length();
+ fillChars = Math.max(fillChars, 4);
+ for (int i=0; i<fillChars; i++)
+ sectionLabel.append("-");
+ sectionLabel.append("\n\n");
+
+ // insert code and move cursor
+ docDisplay_.insertCode(sectionLabel.toString(), false);
+ docDisplay_.setCursorPosition(Position.create(pos.getRow() + 3,
+ 0));
+ docDisplay_.focus();
+
+ }
+ });
+ }
+
+ private Position moveCursorToNextInsertLocation()
+ {
+ docDisplay_.collapseSelection(true);
+ if (!docDisplay_.moveSelectionToBlankLine())
+ {
+ int lastRow = docDisplay_.getRowCount();
+ int lastCol = docDisplay_.getLength(lastRow);
+ Position endPos = Position.create(lastRow, lastCol);
+ docDisplay_.setCursorPosition(endPos);
+ docDisplay_.insertCode("\n", false);
+ }
+ return docDisplay_.getCursorPosition();
+
+ }
+
+ @Handler
+ void onExecuteCurrentChunk()
+ {
+ // HACK: This is just to force the entire function tree to be built.
+ // It's the easiest way to make sure getCurrentScope() returns
+ // a Scope with an end.
+ docDisplay_.getScopeTree();
+
+ executeSweaveChunk(scopeHelper_.getCurrentSweaveChunk(), false);
+ }
+
+ @Handler
+ void onExecuteNextChunk()
+ {
+ // HACK: This is just to force the entire function tree to be built.
+ // It's the easiest way to make sure getCurrentScope() returns
+ // a Scope with an end.
+ docDisplay_.getScopeTree();
+
+ executeSweaveChunk(scopeHelper_.getNextSweaveChunk(), true);
+ }
+
+ private void executeSweaveChunk(Scope chunk, boolean scrollNearTop)
+ {
+ if (chunk == null)
+ return;
+
+ Range range = scopeHelper_.getSweaveChunkInnerRange(chunk);
+ if (scrollNearTop)
+ {
+ docDisplay_.navigateToPosition(
+ SourcePosition.create(range.getStart().getRow(),
+ range.getStart().getColumn()),
+ true);
+ }
+ docDisplay_.setSelection(
+ docDisplay_.createSelection(range.getStart(), range.getEnd()));
+ if (!range.isEmpty())
+ {
+ codeExecution_.setLastExecuted(range.getStart(), range.getEnd());
+ String code = scopeHelper_.getSweaveChunkText(chunk);
+ events_.fireEvent(new SendToConsoleEvent(code, true));
+
+ docDisplay_.collapseSelection(true);
+ }
+ }
+
+ @Handler
+ void onJumpTo()
+ {
+ statusBar_.getScope().click();
+ }
+
+ @Handler
+ void onGoToLine()
+ {
+ globalDisplay_.promptForInteger(
+ "Go to Line",
+ "Enter line number:",
+ null,
+ new ProgressOperationWithInput<Integer>()
+ {
+ @Override
+ public void execute(Integer line, ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+
+ line = Math.max(1, line);
+ line = Math.min(docDisplay_.getRowCount(), line);
+
+ docDisplay_.navigateToPosition(
+ SourcePosition.create(line-1, 0),
+ true);
+ }
+ },
+ null);
+ }
+
+ @Handler
+ void onCodeCompletion()
+ {
+ docDisplay_.codeCompletion();
+ }
+
+ @Handler
+ void onGoToHelp()
+ {
+ docDisplay_.goToHelp();
+ }
+
+ @Handler
+ void onGoToFunctionDefinition()
+ {
+ docDisplay_.goToFunctionDefinition();
+ }
+
+ @Handler
+ public void onSetWorkingDirToActiveDoc()
+ {
+ // get path
+ String activeDocPath = docUpdateSentinel_.getPath();
+ if (activeDocPath != null)
+ {
+ FileSystemItem wdPath =
+ FileSystemItem.createFile(activeDocPath).getParentPath();
+ consoleDispatcher_.executeSetWd(wdPath, true);
+ }
+ else
+ {
+ globalDisplay_.showMessage(
+ MessageDialog.WARNING,
+ "Source File Not Saved",
+ "The currently active source file is not saved so doesn't " +
+ "have a directory to change into.");
+ return;
+ }
+ }
+
+
+ @SuppressWarnings("unused")
+ private String stangle(String sweaveStr)
+ {
+ ScopeList chunks = new ScopeList(docDisplay_);
+ chunks.selectAll(ScopeList.CHUNK);
+
+ StringBuilder code = new StringBuilder();
+ for (Scope chunk : chunks)
+ {
+ String text = scopeHelper_.getSweaveChunkText(chunk);
+ code.append(text);
+ if (text.length() > 0 && text.charAt(text.length()-1) != '\n')
+ code.append('\n');
+ }
+ return code.toString();
+ }
+
+ @Handler
+ void onSourceActiveDocument()
+ {
+ sourceActiveDocument(false);
+ }
+
+ @Handler
+ void onSourceActiveDocumentWithEcho()
+ {
+ sourceActiveDocument(true);
+ }
+
+
+ private void sourceActiveDocument(final boolean echo)
+ {
+ docDisplay_.focus();
+
+ // If the document being sourced is a Shiny file, run the app instead.
+ if (fileType_.isR() &&
+ extendedType_.equals("shiny"))
+ {
+ runShinyApp();
+ return;
+ }
+
+ String code = docDisplay_.getCode();
+ if (code != null && code.trim().length() > 0)
+ {
+ // R 2.14 prints a warning when sourcing a file with no trailing \n
+ if (!code.endsWith("\n"))
+ code = code + "\n";
+
+ boolean sweave =
+ fileType_.canCompilePDF() ||
+ fileType_.canKnitToHTML() ||
+ fileType_.isRpres();
+
+ RnwWeave rnwWeave = compilePdfHelper_.getActiveRnwWeave();
+ final boolean forceEcho = sweave && (rnwWeave != null) ? rnwWeave.forceEchoOnExec() : false;
+
+ // NOTE: we always set echo to true for knitr because knitr doesn't
+ // require print statements so if you don't echo to the console
+ // then you don't see any of the output
+
+ boolean saveWhenSourcing = fileType_.isCpp() ||
+ docDisplay_.hasBreakpoints();
+
+ if ((dirtyState_.getValue() || sweave) && !saveWhenSourcing)
+ {
+ server_.saveActiveDocument(code,
+ sweave,
+ compilePdfHelper_.getActiveRnwWeaveName(),
+ new SimpleRequestCallback<Void>() {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ consoleDispatcher_.executeSourceCommand(
+ "~/.active-rstudio-document",
+ fileType_,
+ "UTF-8",
+ activeCodeIsAscii(),
+ forceEcho ? true : echo,
+ true,
+ docDisplay_.hasBreakpoints());
+ }
+ });
+ }
+ else
+ {
+ Command sourceCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ if (docDisplay_.hasBreakpoints())
+ {
+ hideBreakpointWarningBar();
+ }
+ consoleDispatcher_.executeSourceCommand(
+ getPath(),
+ fileType_,
+ docUpdateSentinel_.getEncoding(),
+ activeCodeIsAscii(),
+ forceEcho ? true : echo,
+ true,
+ docDisplay_.hasBreakpoints());
+ }
+ };
+
+ if (saveWhenSourcing && (dirtyState_.getValue() || (getPath() == null)))
+ saveThenExecute(null, sourceCommand);
+ else
+ sourceCommand.execute();
+ }
+ }
+
+ // update pref if necessary
+ if (prefs_.sourceWithEcho().getValue() != echo)
+ {
+ prefs_.sourceWithEcho().setGlobalValue(echo, true);
+ prefs_.writeUIPrefs();
+ }
+ }
+
+ private void runShinyApp()
+ {
+ sourceBuildHelper_.withSaveFilesBeforeCommand(new Command() {
+ @Override
+ public void execute()
+ {
+ RStudioGinjector.INSTANCE.getShinyApplication()
+ .launchShinyApplication(getPath());
+ }
+ }, "Run Shiny Application");
+ }
+
+ private boolean activeCodeIsAscii()
+ {
+ String code = docDisplay_.getCode();
+ for (int i=0; i< code.length(); i++)
+ {
+ if (code.charAt(i) > 127)
+ return false;
+ }
+
+ return true;
+ }
+
+ @Handler
+ void onExecuteLastCode()
+ {
+ docDisplay_.focus();
+
+ codeExecution_.executeLastCode();
+ }
+
+ @Handler
+ void onMarkdownHelp()
+ {
+ events_.fireEvent(new ShowHelpEvent("help/doc/markdown_help.html")) ;
+ }
+
+ @Handler
+ void onUsingRMarkdownHelp()
+ {
+ globalDisplay_.openRStudioLink("using_markdown");
+ }
+
+ @Handler
+ void onAuthoringRPresentationsHelp()
+ {
+ globalDisplay_.openRStudioLink("authoring_presentations");
+ }
+
+ @Handler
+ void onRcppHelp()
+ {
+ globalDisplay_.openRStudioLink("rcpp_help");
+ }
+
+ @Handler
+ void onDebugHelp()
+ {
+ globalDisplay_.openRStudioLink("visual_debugger");
+ }
+
+ @Handler
+ void onKnitDocument()
+ {
+ onPreviewHTML();
+ }
+
+ @Handler
+ void onPreviewHTML()
+ {
+ if (fileType_.isRd())
+ previewRd();
+ else if (fileType_.isRpres())
+ previewRpresentation();
+ else
+ previewHTML();
+ }
+
+ void previewRpresentation()
+ {
+ SessionInfo sessionInfo = session_.getSessionInfo();
+ if (!fileTypeCommands_.getHTMLCapabiliites().isRMarkdownSupported())
+ {
+ globalDisplay_.showMessage(
+ MessageDisplay.MSG_WARNING,
+ "Unable to Preview",
+ "R Presentations require the knitr package " +
+ "(version 1.2 or higher)");
+ return;
+ }
+
+ PresentationState state = sessionInfo.getPresentationState();
+
+ // if we are showing a tutorial then don't allow preview
+ if (state.isTutorial())
+ {
+ globalDisplay_.showMessage(
+ MessageDisplay.MSG_WARNING,
+ "Unable to Preview",
+ "R Presentations cannot be previewed when a Tutorial " +
+ "is active");
+ return;
+ }
+
+ // if this presentation is already showing then just activate
+ if (state.isActive() &&
+ state.getFilePath().equals(docUpdateSentinel_.getPath()))
+ {
+ commands_.activatePresentation().execute();
+ save();
+ }
+ // otherwise reload
+ else
+ {
+ saveThenExecute(null, new Command() {
+ @Override
+ public void execute()
+ {
+ server_.showPresentationPane(docUpdateSentinel_.getPath(),
+ new VoidServerRequestCallback());
+ }
+
+ });
+ }
+ }
+
+
+ void previewRd()
+ {
+ saveThenExecute(null, new Command() {
+ @Override
+ public void execute()
+ {
+ String previewURL = "help/preview?file=";
+ previewURL += URL.encodeQueryString(docUpdateSentinel_.getPath());
+ events_.fireEvent(new ShowHelpEvent(previewURL)) ;
+ }
+
+ });
+
+ }
+
+ void previewHTML()
+ {
+ // validate pre-reqs
+ if (!previewHtmlHelper_.verifyPrerequisites(view_, fileType_))
+ return;
+
+ doHtmlPreview(new Provider<HTMLPreviewParams>()
+ {
+ @Override
+ public HTMLPreviewParams get()
+ {
+ return HTMLPreviewParams.create(docUpdateSentinel_.getPath(),
+ docUpdateSentinel_.getEncoding(),
+ fileType_.isMarkdown(),
+ fileType_.requiresKnit(),
+ false);
+ }
+ });
+ }
+
+ private void doHtmlPreview(final Provider<HTMLPreviewParams> pParams)
+ {
+ // command to show the preview window
+ final Command showPreviewWindowCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ HTMLPreviewParams params = pParams.get();
+ events_.fireEvent(new ShowHTMLPreviewEvent(params));
+ }
+ };
+
+ // command to run the preview
+ final Command runPreviewCommand = new Command() {
+ @Override
+ public void execute()
+ {
+ final HTMLPreviewParams params = pParams.get();
+ server_.previewHTML(params, new SimpleRequestCallback<Boolean>());
+ }
+ };
+
+ if (pParams.get().isNotebook())
+ {
+ saveThenExecute(null, new Command()
+ {
+ @Override
+ public void execute()
+ {
+ generateNotebook(new Command()
+ {
+ @Override
+ public void execute()
+ {
+ showPreviewWindowCommand.execute();
+ runPreviewCommand.execute();
+ }
+ });
+ }
+ });
+ }
+ // if the document is new and unsaved, then resolve that and then
+ // show the preview window -- it won't activate in web mode
+ // due to popup activation rules but at least it will show up
+ else if (isNewDoc())
+ {
+ saveThenExecute(null, CommandUtil.join(showPreviewWindowCommand,
+ runPreviewCommand));
+ }
+ // otherwise if it's dirty then show the preview window first (to
+ // beat the popup blockers) then save & run
+ else if (dirtyState().getValue())
+ {
+ showPreviewWindowCommand.execute();
+ saveThenExecute(null, runPreviewCommand);
+ }
+ // otherwise show the preview window then run the preview
+ else
+ {
+ showPreviewWindowCommand.execute();
+ runPreviewCommand.execute();
+ }
+ }
+
+ private void generateNotebook(final Command executeOnSuccess)
+ {
+ // default title
+ String defaultTitle = docUpdateSentinel_.getProperty(NOTEBOOK_TITLE);
+ if (StringUtil.isNullOrEmpty(defaultTitle))
+ defaultTitle = FileSystemItem.getNameFromPath(docUpdateSentinel_.getPath());
+
+ // default author
+ String defaultAuthor = docUpdateSentinel_.getProperty(NOTEBOOK_AUTHOR);
+ if (StringUtil.isNullOrEmpty(defaultAuthor))
+ {
+ defaultAuthor = prefs_.compileNotebookOptions().getValue().getAuthor();
+ if (StringUtil.isNullOrEmpty(defaultAuthor))
+ defaultAuthor = session_.getSessionInfo().getUserIdentity();
+ }
+
+ // default type
+ String defaultType = docUpdateSentinel_.getProperty(NOTEBOOK_TYPE);
+ if (StringUtil.isNullOrEmpty(defaultType))
+ {
+ defaultType = prefs_.compileNotebookOptions().getValue().getType();
+ if (StringUtil.isNullOrEmpty(defaultType))
+ defaultType = CompileNotebookOptions.TYPE_DEFAULT;
+ }
+
+ CompileNotebookOptionsDialog dialog = new CompileNotebookOptionsDialog(
+ getId(),
+ defaultTitle,
+ defaultAuthor,
+ defaultType,
+ new OperationWithInput<CompileNotebookOptions>()
+ {
+ @Override
+ public void execute(CompileNotebookOptions input)
+ {
+ server_.createNotebook(
+ input,
+ new SimpleRequestCallback<CompileNotebookResult>()
+ {
+ @Override
+ public void onResponseReceived(CompileNotebookResult response)
+ {
+ if (response.getSucceeded())
+ {
+ executeOnSuccess.execute();
+ }
+ else
+ {
+ globalDisplay_.showErrorMessage(
+ "Unable to Compile Notebook",
+ response.getFailureMessage());
+ }
+ }
+ });
+
+ // save options for this document
+ HashMap<String, String> changedProperties = new HashMap<String, String>();
+ changedProperties.put(NOTEBOOK_TITLE, input.getNotebookTitle());
+ changedProperties.put(NOTEBOOK_AUTHOR, input.getNotebookAuthor());
+ changedProperties.put(NOTEBOOK_TYPE, input.getNotebookType());
+ docUpdateSentinel_.modifyProperties(changedProperties, null);
+
+ // save global prefs
+ CompileNotebookPrefs prefs = CompileNotebookPrefs.create(
+ input.getNotebookAuthor(),
+ input.getNotebookType());
+ if (!CompileNotebookPrefs.areEqual(
+ prefs,
+ prefs_.compileNotebookOptions().getValue()))
+ {
+ prefs_.compileNotebookOptions().setGlobalValue(prefs);
+ prefs_.writeUIPrefs();
+ }
+ }
+ }
+ );
+ dialog.showModal();
+ }
+
+ @Handler
+ void onCompileNotebook()
+ {
+ if (!previewHtmlHelper_.verifyPrerequisites("Compile Notebook",
+ view_,
+ FileTypeRegistry.RMARKDOWN))
+ {
+ return;
+ }
+
+ doHtmlPreview(new Provider<HTMLPreviewParams>()
+ {
+ @Override
+ public HTMLPreviewParams get()
+ {
+ return HTMLPreviewParams.create(docUpdateSentinel_.getPath(),
+ docUpdateSentinel_.getEncoding(),
+ true,
+ true,
+ true);
+ }
+ });
+ }
+
+ @Handler
+ void onCompilePDF()
+ {
+ String pdfPreview = prefs_.getPdfPreviewValue();
+ boolean showPdf = !pdfPreview.equals(UIPrefsAccessor.PDF_PREVIEW_NONE);
+ boolean useInternalPreview =
+ pdfPreview.equals(UIPrefsAccessor.PDF_PREVIEW_RSTUDIO) &&
+ session_.getSessionInfo().isInternalPdfPreviewEnabled();
+ boolean useDesktopSynctexPreview =
+ pdfPreview.equals(UIPrefsAccessor.PDF_PREVIEW_DESKTOP_SYNCTEX) &&
+ Desktop.isDesktop();
+
+ Command onBeforeCompile = null;
+ if (useInternalPreview)
+ {
+ onBeforeCompile = new Command() {
+ @Override
+ public void execute()
+ {
+ events_.fireEvent(new ShowPDFViewerEvent());
+ }
+ };
+ }
+
+ String action = new String();
+ if (showPdf && !useInternalPreview && !useDesktopSynctexPreview)
+ action = "view_external";
+
+ handlePdfCommand(action, useInternalPreview, onBeforeCompile);
+ }
+
+ @Handler
+ void onSynctexSearch()
+ {
+ doSynctexSearch(true);
+ }
+
+ private void doSynctexSearch(boolean fromClick)
+ {
+ SourceLocation sourceLocation = getSelectionAsSourceLocation(fromClick);
+ if (sourceLocation == null)
+ return;
+
+ // compute the target pdf
+ FileSystemItem editorFile = FileSystemItem.createFile(
+ docUpdateSentinel_.getPath());
+ FileSystemItem targetFile = compilePdfHelper_.getTargetFile(editorFile);
+ String pdfFile =
+ targetFile.getParentPath().completePath(targetFile.getStem() + ".pdf");
+
+ synctex_.forwardSearch(pdfFile, sourceLocation);
+ }
+
+
+ private SourceLocation getSelectionAsSourceLocation(boolean fromClick)
+ {
+ // get doc path (bail if the document is unsaved)
+ String file = docUpdateSentinel_.getPath();
+ if (file == null)
+ return null;
+
+ Position selPos = docDisplay_.getSelectionStart();
+ int line = selPos.getRow() + 1;
+ int column = selPos.getColumn() + 1;
+ return SourceLocation.create(file, line, column, fromClick);
+ }
+
+ @Handler
+ void onFindReplace()
+ {
+ view_.showFindReplace(true);
+ }
+
+ @Handler
+ void onFindNext()
+ {
+ view_.findNext();
+ }
+
+ @Handler
+ void onFindPrevious()
+ {
+ view_.findPrevious();
+ }
+
+ @Handler
+ void onFindFromSelection()
+ {
+ view_.findFromSelection();
+ }
+
+ @Handler
+ void onReplaceAndFind()
+ {
+ view_.replaceAndFind();
+ }
+
+ @Override
+ public Position search(String regex)
+ {
+ return search(Position.create(0, 0), regex);
+ }
+
+ @Override
+ public Position search(Position startPos, String regex)
+ {
+ InputEditorSelection sel = docDisplay_.search(regex,
+ false,
+ false,
+ false,
+ false,
+ startPos,
+ null,
+ true);
+ if (sel != null)
+ return docDisplay_.selectionToPosition(sel.getStart());
+ else
+ return null;
+ }
+
+
+ @Handler
+ void onFold()
+ {
+ if (useScopeTreeFolding())
+ {
+ Range range = Range.fromPoints(docDisplay_.getSelectionStart(),
+ docDisplay_.getSelectionEnd());
+ if (range.isEmpty())
+ {
+ // If no selection, fold the innermost non-anonymous scope
+
+ ScopeList scopeList = new ScopeList(docDisplay_);
+ scopeList.removeAll(ScopeList.ANON_BRACE);
+ Scope scope = scopeList.findLast(new ContainsFoldPredicate(
+ Range.fromPoints(docDisplay_.getSelectionStart(),
+ docDisplay_.getSelectionEnd())));
+
+ if (scope == null)
+ return;
+
+ docDisplay_.addFoldFromRow(scope.getFoldStart().getRow());
+ }
+ else
+ {
+ // If selection, fold the selection
+
+ docDisplay_.addFold(range);
+ }
+ }
+ else
+ {
+ int row = docDisplay_.getSelectionStart().getRow();
+ docDisplay_.addFoldFromRow(row);
+ }
+ }
+
+ @Handler
+ void onUnfold()
+ {
+ if (useScopeTreeFolding())
+ {
+ Range range = Range.fromPoints(docDisplay_.getSelectionStart(),
+ docDisplay_.getSelectionEnd());
+ if (range.isEmpty())
+ {
+ // If no selection, unfold the closest fold on the current row
+
+ Position pos = range.getStart();
+
+ AceFold startCandidate = null;
+ AceFold endCandidate = null;
+
+ for (AceFold f : JsUtil.asIterable(docDisplay_.getFolds()))
+ {
+ if (startCandidate == null
+ && f.getStart().getRow() == pos.getRow()
+ && f.getStart().getColumn() >= pos.getColumn())
+ {
+ startCandidate = f;
+ }
+
+ if (f.getEnd().getRow() == pos.getRow()
+ && f.getEnd().getColumn() <= pos.getColumn())
+ {
+ endCandidate = f;
+ }
+ }
+
+ if (startCandidate == null ^ endCandidate == null)
+ {
+ docDisplay_.unfold(startCandidate != null ? startCandidate
+ : endCandidate);
+ }
+ else if (startCandidate != null)
+ {
+ // Both are candidates; see which is closer
+ int startDelta = startCandidate.getStart().getColumn() - pos.getColumn();
+ int endDelta = pos.getColumn() - endCandidate.getEnd().getColumn();
+ docDisplay_.unfold(startDelta <= endDelta? startCandidate
+ : endCandidate);
+ }
+ }
+ else
+ {
+ // If selection, unfold the selection
+
+ docDisplay_.unfold(range);
+ }
+ }
+ else
+ {
+ int row = docDisplay_.getSelectionStart().getRow();
+ docDisplay_.unfold(row);
+ }
+ }
+
+ @Handler
+ void onFoldAll()
+ {
+ if (useScopeTreeFolding())
+ {
+ // Fold all except anonymous braces
+ HashSet<Integer> rowsFolded = new HashSet<Integer>();
+ for (AceFold f : JsUtil.asIterable(docDisplay_.getFolds()))
+ rowsFolded.add(f.getStart().getRow());
+
+ ScopeList scopeList = new ScopeList(docDisplay_);
+ scopeList.removeAll(ScopeList.ANON_BRACE);
+ for (Scope scope : scopeList)
+ {
+ int row = scope.getFoldStart().getRow();
+ if (!rowsFolded.contains(row))
+ docDisplay_.addFoldFromRow(row);
+ }
+ }
+ else
+ {
+ docDisplay_.foldAll();
+ }
+ }
+
+ @Handler
+ void onUnfoldAll()
+ {
+ if (useScopeTreeFolding())
+ {
+ for (AceFold f : JsUtil.asIterable(docDisplay_.getFolds()))
+ docDisplay_.unfold(f);
+ }
+ else
+ {
+ docDisplay_.unfoldAll();
+ }
+ }
+
+ boolean useScopeTreeFolding()
+ {
+ return docDisplay_.hasScopeTree() && !fileType_.isRmd();
+ }
+
+ void handlePdfCommand(final String completedAction,
+ final boolean useInternalPreview,
+ final Command onBeforeCompile)
+ {
+ if (fileType_.isRnw() && prefs_.alwaysEnableRnwConcordance().getValue())
+ compilePdfHelper_.ensureRnwConcordance();
+
+ // if the document has been previously saved then we should execute
+ // the onBeforeCompile command immediately
+ final boolean isNewDoc = isNewDoc();
+ if (!isNewDoc && (onBeforeCompile != null))
+ onBeforeCompile.execute();
+
+ saveThenExecute(null, new Command()
+ {
+ public void execute()
+ {
+ // if this was a new doc then we still need to execute the
+ // onBeforeCompile command
+ if (isNewDoc && (onBeforeCompile != null))
+ onBeforeCompile.execute();
+
+ String path = docUpdateSentinel_.getPath();
+ if (path != null)
+ {
+ String encoding = StringUtil.notNull(
+ docUpdateSentinel_.getEncoding());
+ fireCompilePdfEvent(path,
+ encoding,
+ completedAction,
+ useInternalPreview);
+ }
+ }
+ });
+ }
+
+ private void fireCompilePdfEvent(String path,
+ String encoding,
+ String completedAction,
+ boolean useInternalPreview)
+ {
+ // first validate the path to make sure it doesn't contain spaces
+ FileSystemItem file = FileSystemItem.createFile(path);
+ if (file.getName().indexOf(' ') != -1)
+ {
+ globalDisplay_.showErrorMessage(
+ "Invalid Filename",
+ "The file '" + file.getName() + "' cannot be compiled to " +
+ "a PDF because TeX does not understand paths with spaces. " +
+ "If you rename the file to remove spaces then " +
+ "PDF compilation will work correctly.");
+
+ return;
+ }
+
+ CompilePdfEvent event = new CompilePdfEvent(
+ compilePdfHelper_.getTargetFile(file),
+ encoding,
+ getSelectionAsSourceLocation(false),
+ completedAction,
+ useInternalPreview);
+ events_.fireEvent(event);
+ }
+
+ private Command postSaveCommand()
+ {
+ return new Command()
+ {
+ public void execute()
+ {
+ // fire source document saved event
+ FileSystemItem file = FileSystemItem.createFile(
+ docUpdateSentinel_.getPath());
+ events_.fireEvent(new SourceFileSaveCompletedEvent(
+ file,
+ docUpdateSentinel_.getContents(),
+ docDisplay_.getCursorPosition()));
+
+ // check for source on save
+ if (fileType_.canSourceOnSave() && docUpdateSentinel_.sourceOnSave())
+ {
+ if (fileType_.isRd())
+ {
+ previewRd();
+ }
+ else
+ {
+ if (docDisplay_.hasBreakpoints())
+ {
+ hideBreakpointWarningBar();
+ }
+ consoleDispatcher_.executeSourceCommand(
+ docUpdateSentinel_.getPath(),
+ fileType_,
+ docUpdateSentinel_.getEncoding(),
+ activeCodeIsAscii(),
+ false,
+ false,
+ docDisplay_.hasBreakpoints());
+ }
+ }
+ }
+ };
+ }
+
+ public void checkForExternalEdit()
+ {
+ if (!externalEditCheckInterval_.hasElapsed())
+ return;
+ externalEditCheckInterval_.reset();
+
+ externalEditCheckInvalidation_.invalidate();
+
+ // If the doc has never been saved, don't even bother checking
+ if (getPath() == null)
+ return;
+
+ final Token token = externalEditCheckInvalidation_.getInvalidationToken();
+
+ server_.checkForExternalEdit(
+ id_,
+ new ServerRequestCallback<CheckForExternalEditResult>()
+ {
+ @Override
+ public void onResponseReceived(CheckForExternalEditResult response)
+ {
+ if (token.isInvalid())
+ return;
+
+ if (response.isDeleted())
+ {
+ if (ignoreDeletes_)
+ return;
+
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "File Deleted",
+ "The file " +
+ StringUtil.notNull(docUpdateSentinel_.getPath()) +
+ " has been deleted or moved. " +
+ "Do you want to close this file now?",
+ false,
+ new Operation()
+ {
+ public void execute()
+ {
+ CloseEvent.fire(TextEditingTarget.this, null);
+ }
+ },
+ new Operation()
+ {
+ public void execute()
+ {
+ externalEditCheckInterval_.reset();
+ ignoreDeletes_ = true;
+ // Make sure it stays dirty
+ dirtyState_.markDirty(false);
+ }
+ },
+ true
+ );
+ }
+ else if (response.isModified())
+ {
+ ignoreDeletes_ = false; // Now we know it exists
+
+ // Use StringUtil.formatDate(response.getLastModified())?
+
+ if (!dirtyState_.getValue())
+ {
+ docUpdateSentinel_.revert();
+ }
+ else
+ {
+ externalEditCheckInterval_.reset();
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "File Changed",
+ "The file " + name_.getValue() + " has changed " +
+ "on disk. Do you want to reload the file from " +
+ "disk and discard your unsaved changes?",
+ false,
+ new Operation()
+ {
+ public void execute()
+ {
+ docUpdateSentinel_.revert();
+ }
+ },
+ new Operation()
+ {
+ public void execute()
+ {
+ externalEditCheckInterval_.reset();
+ docUpdateSentinel_.ignoreExternalEdit();
+ // Make sure it stays dirty
+ dirtyState_.markDirty(false);
+ }
+ },
+ true
+ );
+ }
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+
+ private SourcePosition toSourcePosition(Scope func)
+ {
+ Position pos = func.getPreamble();
+ return SourcePosition.create(pos.getRow(), pos.getColumn());
+ }
+
+ private boolean isCursorInTexMode()
+ {
+ if (fileType_.canCompilePDF())
+ {
+ if (fileType_.isRnw())
+ {
+ return SweaveFileType.TEX_LANG_MODE.equals(
+ docDisplay_.getLanguageMode(docDisplay_.getCursorPosition()));
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private boolean isCursorInRMode()
+ {
+ String mode = docDisplay_.getLanguageMode(docDisplay_.getCursorPosition());
+ if (mode == null)
+ return true;
+ if (mode.equals(TextFileType.R_LANG_MODE))
+ return true;
+ return false;
+ }
+
+ private boolean isNewDoc()
+ {
+ return docUpdateSentinel_.getPath() == null;
+ }
+
+ // these methods are public static so that other editing targets which
+ // display source code (but don't inherit from TextEditingTarget) can share
+ // their implementation
+
+ public static interface PrefsContext
+ {
+ FileSystemItem getActiveFile();
+ }
+
+ public static void registerPrefs(
+ ArrayList<HandlerRegistration> releaseOnDismiss,
+ UIPrefs prefs,
+ DocDisplay docDisplay,
+ final SourceDocument sourceDoc)
+ {
+ registerPrefs(releaseOnDismiss,
+ prefs,
+ docDisplay,
+ new PrefsContext() {
+ @Override
+ public FileSystemItem getActiveFile()
+ {
+ String path = sourceDoc.getPath();
+ if (path != null)
+ return FileSystemItem.createFile(path);
+ else
+ return null;
+ }
+ });
+ }
+
+ public static void registerPrefs(
+ ArrayList<HandlerRegistration> releaseOnDismiss,
+ UIPrefs prefs,
+ final DocDisplay docDisplay,
+ final PrefsContext context)
+ {
+ releaseOnDismiss.add(prefs.highlightSelectedLine().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setHighlightSelectedLine(arg);
+ }}));
+ releaseOnDismiss.add(prefs.highlightSelectedWord().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setHighlightSelectedWord(arg);
+ }}));
+ releaseOnDismiss.add(prefs.showLineNumbers().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setShowLineNumbers(arg);
+ }}));
+ releaseOnDismiss.add(prefs.useSpacesForTab().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ // Makefile always uses tabs
+ FileSystemItem file = context.getActiveFile();
+ if ((file != null) &&
+ ("Makefile".equals(file.getName()) ||
+ "Makevars".equals(file.getName()) ||
+ "Makevars.win".equals(file.getName())))
+ {
+ docDisplay.setUseSoftTabs(false);
+ }
+ else
+ {
+ docDisplay.setUseSoftTabs(arg);
+ }
+ }}));
+ releaseOnDismiss.add(prefs.numSpacesForTab().bind(
+ new CommandWithArg<Integer>() {
+ public void execute(Integer arg) {
+ docDisplay.setTabSize(arg);
+ }}));
+ releaseOnDismiss.add(prefs.showMargin().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setShowPrintMargin(arg);
+ }}));
+ releaseOnDismiss.add(prefs.blinkingCursor().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setBlinkingCursor(arg);
+ }}));
+ releaseOnDismiss.add(prefs.printMarginColumn().bind(
+ new CommandWithArg<Integer>() {
+ public void execute(Integer arg) {
+ docDisplay.setPrintMarginColumn(arg);
+ }}));
+ releaseOnDismiss.add(prefs.showInvisibles().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setShowInvisibles(arg);
+ }}));
+ releaseOnDismiss.add(prefs.showIndentGuides().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setShowIndentGuides(arg);
+ }}));
+ releaseOnDismiss.add(prefs.useVimMode().bind(
+ new CommandWithArg<Boolean>() {
+ public void execute(Boolean arg) {
+ docDisplay.setUseVimMode(arg);
+ }}));
+ }
+
+ public static void syncFontSize(
+ ArrayList<HandlerRegistration> releaseOnDismiss,
+ EventBus events,
+ final TextDisplay view,
+ FontSizeManager fontSizeManager)
+ {
+ releaseOnDismiss.add(events.addHandler(
+ ChangeFontSizeEvent.TYPE,
+ new ChangeFontSizeHandler()
+ {
+ public void onChangeFontSize(ChangeFontSizeEvent event)
+ {
+ view.setFontSize(event.getFontSize());
+ }
+ }));
+ view.setFontSize(fontSizeManager.getSize());
+
+ }
+
+ public static void onPrintSourceDoc(final DocDisplay docDisplay)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ public void execute()
+ {
+ docDisplay.print();
+ }
+ });
+ }
+
+ public static void addRecordNavigationPositionHandler(
+ ArrayList<HandlerRegistration> releaseOnDismiss,
+ final DocDisplay docDisplay,
+ final EventBus events,
+ final EditingTarget target)
+ {
+ releaseOnDismiss.add(docDisplay.addRecordNavigationPositionHandler(
+ new RecordNavigationPositionHandler() {
+ @Override
+ public void onRecordNavigationPosition(
+ RecordNavigationPositionEvent event)
+ {
+ SourcePosition pos = SourcePosition.create(
+ target.getContext(),
+ event.getPosition().getRow(),
+ event.getPosition().getColumn(),
+ docDisplay.getScrollTop());
+ events.fireEvent(new SourceNavigationEvent(
+ SourceNavigation.create(
+ target.getId(),
+ target.getPath(),
+ pos)));
+ }
+ }));
+ }
+
+ private StatusBar statusBar_;
+ private final DocDisplay docDisplay_;
+ private final UIPrefs prefs_;
+ private Display view_;
+ private final Commands commands_;
+ private SourceServerOperations server_;
+ private EventBus events_;
+ private final GlobalDisplay globalDisplay_;
+ private final FileDialogs fileDialogs_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final FileTypeCommands fileTypeCommands_;
+ private final ConsoleDispatcher consoleDispatcher_;
+ private final WorkbenchContext workbenchContext_;
+ private final Session session_;
+ private final Synctex synctex_;
+ private final FontSizeManager fontSizeManager_;
+ private final SourceBuildHelper sourceBuildHelper_;
+ private DocUpdateSentinel docUpdateSentinel_;
+ private Value<String> name_ = new Value<String>(null);
+ private TextFileType fileType_;
+ private String id_;
+ private HandlerRegistration commandHandlerReg_;
+ private ArrayList<HandlerRegistration> releaseOnDismiss_ =
+ new ArrayList<HandlerRegistration>();
+ private final DirtyState dirtyState_;
+ private HandlerManager handlers_ = new HandlerManager(this);
+ private FileSystemContext fileContext_;
+ private final TextEditingTargetCompilePdfHelper compilePdfHelper_;
+ private final TextEditingTargetPreviewHtmlHelper previewHtmlHelper_;
+ private final TextEditingTargetCppHelper cppHelper_;
+ private final TextEditingTargetPresentationHelper presentationHelper_;
+ private boolean ignoreDeletes_;
+ private final TextEditingTargetScopeHelper scopeHelper_;
+ private TextEditingTargetSpelling spelling_;
+ private BreakpointManager breakpointManager_;
+
+ // Allows external edit checks to supercede one another
+ private final Invalidation externalEditCheckInvalidation_ =
+ new Invalidation();
+ // Prevents external edit checks from happening too soon after each other
+ private final IntervalTracker externalEditCheckInterval_ =
+ new IntervalTracker(1000, true);
+ private final EditingTargetCodeExecution codeExecution_;
+
+ private SourcePosition debugStartPos_ = null;
+ private SourcePosition debugEndPos_ = null;
+ private boolean isDebugWarningVisible_ = false;
+ private boolean isBreakpointWarningVisible_ = false;
+ private String extendedType_;
+
+ private abstract class RefactorServerRequestCallback
+ extends ServerRequestCallback<JsArrayString>
+ {
+ private final String refactoringName_;
+
+ public RefactorServerRequestCallback(String refactoringName)
+ {
+ refactoringName_ = refactoringName;
+ }
+
+ @Override
+ public void onResponseReceived(final JsArrayString response)
+ {
+ doExtract(response);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ refactoringName_,
+ "The selected code could not be " +
+ "parsed.\n\n" +
+ "Are you sure you want to continue?",
+ new Operation()
+ {
+ public void execute()
+ {
+ doExtract(null);
+ }
+ },
+ false);
+ }
+
+ abstract void doExtract(final JsArrayString response);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCompilePdfHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCompilePdfHelper.java
new file mode 100644
index 0000000..60e6677
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCompilePdfHelper.java
@@ -0,0 +1,428 @@
+/*
+ * TextEditingTargetCompilePdfHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.tex.TexMagicComment;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.latex.LatexProgramRegistry;
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+import org.rstudio.studio.client.common.rnw.RnwWeaveDirective;
+import org.rstudio.studio.client.common.rnw.RnwWeaveRegistry;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.SessionInfo;
+import org.rstudio.studio.client.workbench.model.TexCapabilities;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.source.model.RnwChunkOptions;
+import org.rstudio.studio.client.workbench.views.source.model.RnwCompletionContext;
+import org.rstudio.studio.client.workbench.views.source.model.TexServerOperations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class TextEditingTargetCompilePdfHelper
+ implements RnwCompletionContext
+{
+ public TextEditingTargetCompilePdfHelper(DocDisplay docDisplay)
+ {
+ docDisplay_ = docDisplay;
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(UIPrefs prefs,
+ Session session,
+ TexServerOperations server,
+ RnwWeaveRegistry rnwWeaveRegistry,
+ LatexProgramRegistry latexProgramRegistry)
+ {
+ prefs_ = prefs;
+ session_ = session;
+ server_ = server;
+ rnwWeaveRegistry_ = rnwWeaveRegistry;
+ latexProgramRegistry_ = latexProgramRegistry;
+ }
+
+ // get the chunk options which apply to the current document. when
+ // the chunk options are ready the callback command is execute (note
+ // that if there are no chunk options available then execute will
+ // never be called). this method caches the results from the server
+ // so that chunk options are only retreived once per session -- we
+ // do this not only to save the round-trip but also because knitr takes
+ // over 500ms to load and it may need to be loaded to serve the
+ // request for chunk options
+ public void getChunkOptions(
+ final ServerRequestCallback<RnwChunkOptions> requestCallback)
+ {
+ // determine the current rnw weave type
+ final RnwWeave rnwWeave = getActiveRnwWeave();
+ if (rnwWeave == null)
+ return;
+
+ // look it up in the cache
+ if (chunkOptionsCache_.containsKey(rnwWeave.getName()))
+ {
+ requestCallback.onResponseReceived(
+ chunkOptionsCache_.get(rnwWeave.getName()));
+ }
+ else
+ {
+ server_.getChunkOptions(
+ rnwWeave.getName(),
+ new ServerRequestCallback<RnwChunkOptions>() {
+ @Override
+ public void onResponseReceived(RnwChunkOptions options)
+ {
+ chunkOptionsCache_.put(rnwWeave.getName(), options);
+ requestCallback.onResponseReceived(options);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ requestCallback.onError(error);
+ }
+ });
+ }
+ }
+
+ public void ensureRnwConcordance()
+ {
+ RnwWeave rnwWeave = getActiveRnwWeave();
+ if ( (rnwWeave != null) && rnwWeave.getInjectConcordance())
+ {
+ if (!hasConcordanceDirective(docDisplay_.getCode()))
+ {
+ InputEditorSelection doc = docDisplay_.search(
+ "\\\\begin{document}",
+ false, // backwards
+ true, // wrap
+ false, // case sensitive
+ false, // whole word
+ null, // from selection
+ null, // range (search all)
+ true); // regexp mode
+ if (doc != null)
+ {
+ InputEditorPosition pos = doc.getEnd().moveToNextLine();
+ docDisplay_.insertCode(pos, "\\SweaveOpts{concordance=TRUE}\n");
+ }
+ }
+ }
+ }
+
+
+ public void checkCompilers(final WarningBarDisplay display,
+ TextFileType fileType)
+ {
+ // for all tex files we need to parse magic comments and validate
+ // any explict latex proram directive
+ ArrayList<TexMagicComment> magicComments = null;
+ if (fileType.canCompilePDF())
+ {
+ magicComments = TexMagicComment.parseComments(docDisplay_.getCode());
+ String latexProgramDirective =
+ detectLatexProgramDirective(magicComments);
+
+ if (latexProgramDirective != null)
+ {
+ if (latexProgramRegistry_.findTypeIgnoreCase(latexProgramDirective)
+ == null)
+ {
+ // show warning and bail
+ display.showWarningBar(
+ "Unknown LaTeX program type '" + latexProgramDirective +
+ "' specified (valid types are " +
+ latexProgramRegistry_.getPrintableTypeNames() + ")");
+
+ return;
+ }
+ }
+ }
+
+ // for Rnw we first determine the RnwWeave type
+ RnwWeave rnwWeave = null;
+ RnwWeaveDirective rnwWeaveDirective = null;
+ if (fileType.isRnw())
+ {
+ rnwWeaveDirective = detectRnwWeaveDirective(magicComments);
+ if (rnwWeaveDirective != null)
+ {
+ rnwWeave = rnwWeaveDirective.getRnwWeave();
+ if (rnwWeave == null)
+ {
+ // show warning and bail
+ display.showWarningBar(
+ "Unknown Rnw weave method '" + rnwWeaveDirective.getName() +
+ "' specified (valid types are " +
+ rnwWeaveRegistry_.getPrintableTypeNames() + ")");
+
+ return;
+ }
+ }
+ else
+ {
+ rnwWeave = rnwWeaveRegistry_.findTypeIgnoreCase(
+ prefs_.defaultSweaveEngine().getValue());
+ }
+ }
+
+
+ final SessionInfo sessionInfo = session_.getSessionInfo();
+ TexCapabilities texCap = sessionInfo.getTexCapabilities();
+
+ final boolean checkForTeX = fileType.canCompilePDF() &&
+ !texCap.isTexInstalled();
+
+ final boolean checkForRnwWeave = (rnwWeave != null) &&
+ !texCap.isRnwWeaveAvailable(rnwWeave);
+
+ if (checkForTeX || checkForRnwWeave)
+ {
+ // alias variables to finals
+ final boolean hasRnwWeaveDirective = rnwWeaveDirective != null;
+ final RnwWeave fRnwWeave = rnwWeave;
+
+ server_.getTexCapabilities(new ServerRequestCallback<TexCapabilities>()
+ {
+ @Override
+ public void onResponseReceived(TexCapabilities response)
+ {
+ if (checkForTeX && !response.isTexInstalled())
+ {
+ String warning;
+ if (Desktop.isDesktop())
+ warning = "No TeX installation detected. Please install " +
+ "TeX before compiling.";
+ else
+ warning = "This server does not have TeX installed. You " +
+ "may not be able to compile.";
+ display.showWarningBar(warning);
+ }
+ else if (checkForRnwWeave &&
+ !response.isRnwWeaveAvailable(fRnwWeave))
+ {
+ String forContext = "";
+ if (hasRnwWeaveDirective)
+ forContext = "this file";
+ else if (sessionInfo.getActiveProjectFile() != null)
+ forContext = "Rnw files for this project";
+ else
+ forContext = "Rnw files";
+
+ display.showWarningBar(
+ fRnwWeave.getName() + " is configured to weave " +
+ forContext + " " + "however the " +
+ fRnwWeave.getPackageName() + " package is not installed.");
+ }
+ else
+ {
+ display.hideWarningBar();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+ else
+ {
+ display.hideWarningBar();
+ }
+ }
+
+ public FileSystemItem getTargetFile(FileSystemItem editorFile)
+ {
+ ArrayList<TexMagicComment> magicComments =
+ TexMagicComment.parseComments(docDisplay_.getCode());
+ String root = StringUtil.notNull(detectRootDirective(magicComments));
+ if (root.length() > 0)
+ {
+ return FileSystemItem.createFile(
+ editorFile.getParentPath().completePath(root));
+ }
+ else
+ {
+ String rootPref = prefs_.rootDocument().getValue();
+ if (rootPref.length() > 0)
+ {
+ FileSystemItem projDir =
+ session_.getSessionInfo().getActiveProjectDir();
+ if (projDir != null)
+ return FileSystemItem.createFile(projDir.completePath(rootPref));
+ else
+ return editorFile;
+ }
+ else
+ {
+ return editorFile;
+ }
+ }
+ }
+
+
+ // get the currently active rnw weave method -- note this can return
+ // null in the case that there is an embedded directive which is invalid
+ public RnwWeave getActiveRnwWeave()
+ {
+ if (docDisplay_.getFileType().canKnitToHTML())
+ return rnwWeaveRegistry_.findTypeIgnoreCase("knitr");
+
+ RnwWeaveDirective rnwWeaveDirective = detectRnwWeaveDirective(
+ TexMagicComment.parseComments(docDisplay_.getCode()));
+ if (rnwWeaveDirective != null)
+ return rnwWeaveDirective.getRnwWeave();
+ else
+ return rnwWeaveRegistry_.findTypeIgnoreCase(
+ prefs_.defaultSweaveEngine().getValue());
+ }
+
+ @Override
+ public int getRnwOptionsStart(String line, int cursorPos)
+ {
+ Pattern pattern = docDisplay_.getFileType().getRnwStartPatternBegin();
+ if (pattern == null)
+ return -1;
+
+ String linePart = line.substring(0, cursorPos);
+ Match match = pattern.match(linePart, 0);
+ if (match == null)
+ return -1;
+
+ // See if the cursor is already past the end of the chunk header,
+ // for example <<foo>>=[CURSOR].
+ Pattern patternEnd = docDisplay_.getFileType().getRnwStartPatternEnd();
+ if (patternEnd != null && patternEnd.match(linePart, 0) != null)
+ return -1;
+
+ return match.getValue().length();
+ }
+
+ // get the currently active rnw weave name -- arranges to always return
+ // a valid string by returing the pref if the directive is invalid
+ public String getActiveRnwWeaveName()
+ {
+ if (docDisplay_.getFileType().canKnitToHTML() ||
+ docDisplay_.getFileType().isRpres())
+ return "knitr";
+
+ RnwWeaveDirective rnwWeaveDirective = detectRnwWeaveDirective(
+ TexMagicComment.parseComments(docDisplay_.getCode()));
+ if (rnwWeaveDirective != null)
+ {
+ RnwWeave rnwWeave = rnwWeaveDirective.getRnwWeave();
+ if (rnwWeave != null)
+ return rnwWeave.getName();
+ }
+
+ return rnwWeaveRegistry_.findTypeIgnoreCase(
+ prefs_.defaultSweaveEngine().getValue()).getName();
+ }
+
+ private boolean hasConcordanceDirective(String code)
+ {
+ Iterable<String> lines = StringUtil.getLineIterator(code);
+
+ for (String line : lines)
+ {
+ line = line.trim();
+ if (line.length() == 0)
+ {
+ continue;
+ }
+ else if (line.startsWith("\\SweaveOpts"))
+ {
+ Match match = concordancePattern_.match(line, 0);
+ if (match != null)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private RnwWeaveDirective detectRnwWeaveDirective(
+ ArrayList<TexMagicComment> magicComments)
+ {
+ for (TexMagicComment comment : magicComments)
+ {
+ RnwWeaveDirective rnwWeaveDirective =
+ RnwWeaveDirective.fromTexMagicComment(comment);
+ if (rnwWeaveDirective != null)
+ return rnwWeaveDirective;
+ }
+
+ return null;
+ }
+
+ private String detectLatexProgramDirective(
+ ArrayList<TexMagicComment> magicComments)
+ {
+ for (TexMagicComment comment : magicComments)
+ {
+ if (comment.getScope().equalsIgnoreCase("tex") &&
+ (comment.getVariable().equalsIgnoreCase("program") ||
+ comment.getVariable().equalsIgnoreCase("ts-program")))
+ {
+ return comment.getValue();
+ }
+ }
+
+ return null;
+ }
+
+ private String detectRootDirective(ArrayList<TexMagicComment> magicComments)
+ {
+ for (TexMagicComment comment : magicComments)
+ {
+ String scope = comment.getScope().toLowerCase();
+ if ((scope.equals("rnw") || scope.equals("tex")) &&
+ comment.getVariable().equalsIgnoreCase("root"))
+ {
+ return comment.getValue();
+ }
+ }
+
+ return null;
+ }
+
+ private final DocDisplay docDisplay_;
+
+ private UIPrefs prefs_;
+ private Session session_;
+ private TexServerOperations server_;
+ private RnwWeaveRegistry rnwWeaveRegistry_;
+ private LatexProgramRegistry latexProgramRegistry_;
+
+ private static final Pattern concordancePattern_ = Pattern.create(
+ "\\\\[\\s]*SweaveOpts[\\s]*{.*concordance[\\s]*=.*}");
+
+ private static HashMap<String, RnwChunkOptions> chunkOptionsCache_ =
+ new HashMap<String, RnwChunkOptions>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCppHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCppHelper.java
new file mode 100644
index 0000000..5170f0c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetCppHelper.java
@@ -0,0 +1,86 @@
+/*
+ * TextEditingTargetCppHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildServerOperations;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.model.CppCapabilities;
+
+public class TextEditingTargetCppHelper
+{
+ public TextEditingTargetCppHelper(BuildServerOperations server)
+ {
+ server_ = server;
+ }
+
+ public void checkBuildCppDependencies(
+ final EditingTarget editingTarget,
+ final WarningBarDisplay warningBar,
+ TextFileType fileType)
+ {
+ // bail if this isn't a C file or we've already verified we can build
+ if (!fileType.isC() || capabilities_.hasAllCapabiliites())
+ return;
+
+ server_.getCppCapabilities(
+ new ServerRequestCallback<CppCapabilities>() {
+
+ @Override
+ public void onResponseReceived(CppCapabilities capabilities)
+ {
+ if (capabilities_.hasAllCapabiliites())
+ {
+ capabilities_ = capabilities;
+ }
+ else
+ {
+ if (!capabilities.getCanBuild())
+ {
+ warningBar.showWarningBar(
+ "The tools required to build C/C++ code for R " +
+ "are not currently installed");
+ }
+ else if (!capabilities.getCanSourceCpp())
+ {
+ if (editingTarget.search("Rcpp\\:\\:export") != null)
+ {
+ warningBar.showWarningBar(
+ "The Rcpp package (version 0.10.1 or higher) is not " +
+ "currently installed");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ // ignore error since this is merely advisory
+ }
+ });
+ }
+
+
+ private BuildServerOperations server_;
+
+
+ // cache the value statically -- once we get an affirmative response
+ // we never check again
+ private static CppCapabilities capabilities_
+ = CppCapabilities.createDefault();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetFindReplace.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetFindReplace.java
new file mode 100644
index 0000000..ae0b800
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetFindReplace.java
@@ -0,0 +1,169 @@
+/*
+ * TextEditingTargetFindReplace.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplace;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.user.client.ui.Widget;
+
+public class TextEditingTargetFindReplace
+{
+ public interface Container
+ {
+ AceEditor getEditor();
+ void insertFindReplace(FindReplaceBar findReplaceBar);
+ void removeFindReplace(FindReplaceBar findReplaceBar);
+ }
+
+ public TextEditingTargetFindReplace(Container container)
+ {
+ this(container, true);
+ }
+
+ public TextEditingTargetFindReplace(Container container, boolean showReplace)
+ {
+ container_ = container;
+ showReplace_ = showReplace;
+
+ container_.getEditor().addEditorFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event)
+ {
+ if (findReplace_ != null)
+ findReplace_.notifyEditorFocused();
+ }
+ });
+ }
+
+ public Widget createFindReplaceButton()
+ {
+ if (findReplaceBar_ == null)
+ {
+ findReplaceButton_ = new ToolbarButton(
+ FindReplaceBar.getFindIcon(),
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ if (findReplaceBar_ == null)
+ showFindReplace(true);
+ else
+ hideFindReplace();
+ }
+ });
+ String title = showReplace_ ? "Find/Replace" : "Find";
+ findReplaceButton_.setTitle(title);
+ }
+ return findReplaceButton_;
+ }
+
+ public void showFindReplace(boolean defaultForward)
+ {
+ ensureFindReplaceBar(defaultForward);
+
+ String selection = container_.getEditor().getSelectionValue();
+ boolean multiLineSelection = selection.indexOf('\n') != -1;
+
+ String searchText = null;
+ if ((selection.length() != 0) && !multiLineSelection)
+ searchText = selection;
+
+ findReplace_.activate(searchText, defaultForward, multiLineSelection);
+ }
+
+ private void ensureFindReplaceBar(boolean defaultForward)
+ {
+ if (findReplaceBar_ == null)
+ {
+ findReplaceBar_ = new FindReplaceBar(showReplace_, defaultForward);
+ findReplace_ = new FindReplace(
+ container_.getEditor(),
+ findReplaceBar_,
+ RStudioGinjector.INSTANCE.getGlobalDisplay(),
+ showReplace_);
+ container_.insertFindReplace(findReplaceBar_);
+ findReplaceBar_.getCloseButton().addClickHandler(new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ hideFindReplace();
+ }
+ });
+
+ findReplaceButton_.setLeftImage(FindReplaceBar.getFindLatchedIcon());
+ }
+ }
+
+ public boolean isShowing()
+ {
+ return findReplaceBar_ != null;
+ }
+
+ public void hideFindReplace()
+ {
+ if (findReplaceBar_ != null)
+ {
+ container_.removeFindReplace(findReplaceBar_);
+ findReplace_.notifyClosing();
+ findReplace_ = null;
+ findReplaceBar_ = null;
+ findReplaceButton_.setLeftImage(FindReplaceBar.getFindIcon());
+ }
+ container_.getEditor().focus();
+ }
+
+ public void findNext()
+ {
+ if (findReplace_ != null)
+ findReplace_.findNext();
+ }
+
+ public void findPrevious()
+ {
+ if (findReplace_ != null)
+ findReplace_.findPrevious();
+ }
+
+ public void findFromSelection()
+ {
+ String selection = container_.getEditor().getSelectionValue();
+ boolean multiLineSelection = selection.indexOf('\n') != -1;
+ if ((selection.length()) > 0 && !multiLineSelection)
+ {
+ ensureFindReplaceBar(true);
+ findReplace_.activate(selection, true, false);
+ findReplace_.findNext();
+ }
+ }
+
+ public void replaceAndFind()
+ {
+ if (findReplace_ != null)
+ findReplace_.replaceAndFind();
+ }
+
+
+ private final Container container_;
+ private final boolean showReplace_;
+ private FindReplace findReplace_;
+ private FindReplaceBar findReplaceBar_;
+ private ToolbarButton findReplaceButton_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetLatexFormatMenu.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetLatexFormatMenu.java
new file mode 100644
index 0000000..784779d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetLatexFormatMenu.java
@@ -0,0 +1,163 @@
+/*
+ * TextEditingTargetLatexFormatMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class TextEditingTargetLatexFormatMenu extends ToolbarPopupMenu
+{
+ public TextEditingTargetLatexFormatMenu(DocDisplay editor, UIPrefs prefs)
+ {
+ editor_ = editor;
+ prefs_ = prefs;
+
+ addItem(createLatexMenu("Section", "section*", true));
+ addItem(createLatexMenu("Subsection", "subsection*", true));
+ addItem(createLatexMenu("Sub-Subsection", "subsubsection*", true));
+ addSeparator();
+ addItem(createLatexMenu("Bold", "textbf"));
+ addItem(createLatexMenu("Italic", "emph"));
+ addItem(createLatexMenu("Typewriter", "texttt"));
+ addItem(createLatexMenu("Quote", "``", "''"));
+ addSeparator();
+ addItem(createLatexListMenu("Bullet List", "itemize", false));
+ addItem(createLatexListMenu("Numbered List","enumerate", false));
+ addItem(createLatexListMenu("Description List", "description", true));
+ addSeparator();
+ addItem(createLatexMenu("Verbatim",
+ "\\begin{verbatim}\n",
+ "\n\\end{verbatim}"));
+ addItem(createLatexMenu("Block Quote",
+ "\\begin{quote}\n",
+ "\n\\end{quote}"));
+ }
+
+ private MenuItem createLatexMenu(String text, String macro)
+ {
+ return createLatexMenu(text, macro, false);
+ }
+
+ private MenuItem createLatexMenu(String text,
+ String macro,
+ boolean isSectionMenu)
+ {
+ return createLatexMenu(text, "\\" + macro + "{", "}", isSectionMenu);
+ }
+
+ private MenuItem createLatexMenu(String text, String prefix, String suffix)
+ {
+ return createLatexMenu(text, prefix, suffix, false);
+ }
+
+ private MenuItem createLatexMenu(String text,
+ String prefix,
+ String suffix,
+ boolean isSectionMenu)
+ {
+ return new MenuItem(text, false, createLatexCommand(prefix,
+ suffix,
+ isSectionMenu));
+ }
+
+
+
+ private Command createLatexCommand(final String prefix,
+ final String suffix,
+ final boolean isSectionCommand)
+ {
+ return new Command() {
+
+ @Override
+ public void execute()
+ {
+ String selection = editor_.getSelectionValue();
+
+ // modify prefix based on prefs
+ String insertPrefix = prefix;
+ if (isSectionCommand &&
+ prefs_.insertNumberedLatexSections().getValue())
+ {
+ insertPrefix = insertPrefix.replace("*", "");
+ }
+
+ editor_.insertCode(insertPrefix + selection + suffix, false);
+ editor_.focus();
+
+ // if there was no previous selection then put the cursor
+ // inside the braces
+ if (selection.length() == 0)
+ {
+ Position pos = editor_.getCursorPosition();
+ int row = pos.getRow();
+ if (suffix.startsWith("\n"))
+ row = Math.max(0, row - 1);
+ int col = Math.max(0, pos.getColumn() - suffix.length());
+
+ editor_.setCursorPosition(Position.create(row, col));
+ }
+ }};
+ }
+
+
+ private MenuItem createLatexListMenu(final String text,
+ final String type,
+ final boolean isDescription)
+ {
+ return new MenuItem(text, false, new Command(){
+ @Override
+ public void execute()
+ {
+ editor_.collapseSelection(true);
+ Position pos = editor_.getCursorPosition();
+
+ StringBuilder indent = new StringBuilder();
+ if (prefs_.useSpacesForTab().getValue())
+ {
+ int spaces = prefs_.numSpacesForTab().getValue();
+ for (int i=0; i<spaces; i++)
+ indent.append(' ');
+ }
+ else
+ {
+ indent.append('\t');
+ }
+
+ String item = indent.toString() + "\\item";
+ String itemElement = item + (isDescription ? "[]" : " ");
+
+ String code = "\\begin{" + type + "}\n";
+ code += itemElement;
+ code += "\n\\end{" + type + "}\n";
+
+
+ editor_.insertCode(code, false);
+ editor_.focus();
+
+ editor_.setCursorPosition(Position.create(pos.getRow() + 1,
+ item.length() + 1));
+ }
+
+ });
+ }
+
+
+ private final DocDisplay editor_;
+ private final UIPrefs prefs_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPresentationHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPresentationHelper.java
new file mode 100644
index 0000000..60b77f5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPresentationHelper.java
@@ -0,0 +1,212 @@
+/*
+ * TextEditingTargetPresentationHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorSelection;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
+import org.rstudio.studio.client.workbench.views.presentation.model.SlideNavigation;
+import org.rstudio.studio.client.workbench.views.presentation.model.SlideNavigationItem;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarPopupMenu;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarPopupRequest;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.inject.Inject;
+
+public class TextEditingTargetPresentationHelper
+{
+ public static interface SlideNavigator
+ {
+ void navigateToSlide(int index);
+ }
+
+ public TextEditingTargetPresentationHelper(DocDisplay docDisplay)
+ {
+ docDisplay_ = docDisplay;
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ void initialize(PresentationServerOperations server)
+ {
+ server_ = server;
+ }
+
+ public String getCurrentSlide()
+ {
+ // search starting two lines ahead
+ Position cursorPos = docDisplay_.getCursorPosition();
+ Position searchPos = Position.create(cursorPos.getRow()+2, 0);
+ InputEditorSelection sel = docDisplay_.search(SLIDE_REGEX,
+ true,
+ false,
+ false,
+ false,
+ searchPos,
+ null,
+ true);
+
+
+ if (sel != null)
+ {
+ InputEditorPosition titlePos = sel.getStart().moveToPreviousLine();
+ String title = docDisplay_.getLine(
+ docDisplay_.selectionToPosition(titlePos).getRow());
+ title = title.trim();
+ if (title.length() > 0 && SLIDE_PATTERN.match(title, 0) == null)
+ return title;
+ else
+ return "(Untitled Slide)";
+ }
+ else
+ return "(No Slides)";
+ }
+
+ public void buildSlideMenu(
+ final String path,
+ boolean isDirty,
+ final EditingTarget editor,
+ final CommandWithArg<StatusBarPopupRequest> onCompleted)
+ {
+ // rpc response handler
+ SimpleRequestCallback<SlideNavigation> requestCallback =
+ new SimpleRequestCallback<SlideNavigation>() {
+
+ @Override
+ public void onResponseReceived(SlideNavigation slideNavigation)
+ {
+ // create the menu and make sure we have some slides to return
+ StatusBarPopupMenu menu = new StatusBarPopupMenu();
+ if (slideNavigation.getTotalSlides() == 0)
+ {
+ onCompleted.execute(new StatusBarPopupRequest(menu, null));
+ return;
+ }
+
+ MenuItem defaultMenuItem = null;
+ int length = slideNavigation.getItems().length();
+ for (int i=0; i<length; i++)
+ {
+ SlideNavigationItem item = slideNavigation.getItems().get(i);
+ String title = item.getTitle();
+ if (StringUtil.isNullOrEmpty(title))
+ title = "(Untitled Slide)";
+
+ StringBuilder indentBuilder = new StringBuilder();
+ for (int level=0; level<item.getIndent(); level++)
+ indentBuilder.append(" ");
+
+ SafeHtmlBuilder labelBuilder = new SafeHtmlBuilder();
+ labelBuilder.appendHtmlConstant(indentBuilder.toString());
+ labelBuilder.appendEscaped(title);
+
+ final int targetSlide = i;
+ final MenuItem menuItem = new MenuItem(
+ labelBuilder.toSafeHtml(),
+ new Command()
+ {
+ public void execute()
+ {
+ navigateToSlide(editor, targetSlide);
+ }
+ });
+ menu.addItem(menuItem);
+
+ // see if this is the default menu item
+ if (defaultMenuItem == null &&
+ item.getLine() >= (docDisplay_.getSelectionStart().getRow()))
+ {
+ defaultMenuItem = menuItem;
+ }
+ }
+
+ StatusBarPopupRequest request = new StatusBarPopupRequest(
+ menu,
+ defaultMenuItem);
+ onCompleted.execute(request);
+ }
+ };
+
+ // send code over the wire if we are dirty
+ if (isDirty)
+ {
+ server_.getSlideNavigationForCode(
+ docDisplay_.getCode(),
+ FileSystemItem.createFile(path).getParentPathString(),
+ requestCallback);
+ }
+ else
+ {
+ server_.getSlideNavigationForFile(path, requestCallback);
+ }
+ }
+
+ public static void navigateToSlide(final EditingTarget editor,
+ int slideIndex)
+ {
+ // scan for the specified slide
+ int currentSlide = 0;
+ Position navPos = null;
+ Position pos = Position.create(0, 0);
+ while ((pos = editor.search(pos, "^\\={3,}\\s*$")) != null)
+ {
+ if (currentSlide++ == slideIndex)
+ {
+ navPos = Position.create(pos.getRow() - 1, 0);
+ break;
+ }
+
+ pos = Position.create(pos.getRow() + 1, 0);
+ }
+
+ // navigate to the slide
+ if (navPos != null)
+ {
+ final Position navPosAlias = navPos;
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute()
+ {
+ editor.navigateToPosition(
+ SourcePosition.create(navPosAlias.getRow(), 0),
+ false);
+
+ }
+ });
+ }
+ }
+
+
+ private final DocDisplay docDisplay_;
+ private PresentationServerOperations server_;
+
+ private static final String SLIDE_REGEX = "^\\={3,}\\s*$";
+ private static final Pattern SLIDE_PATTERN = Pattern.create(SLIDE_REGEX);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPreviewHtmlHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPreviewHtmlHelper.java
new file mode 100644
index 0000000..68fc28a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetPreviewHtmlHelper.java
@@ -0,0 +1,83 @@
+/*
+ * TextEditingTargetPreviewHtmlHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.inject.Inject;
+
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.filetypes.FileTypeCommands;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+
+public class TextEditingTargetPreviewHtmlHelper
+{
+ public TextEditingTargetPreviewHtmlHelper()
+ {
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ public void initialize(FileTypeCommands fileTypeCommands)
+ {
+ fileTypeCommands_ = fileTypeCommands;
+ }
+
+ public boolean verifyPrerequisites(WarningBarDisplay display,
+ TextFileType fileType)
+ {
+ return verifyPrerequisites(null, display, fileType);
+ }
+
+ public boolean verifyPrerequisites(String feature,
+ WarningBarDisplay display,
+ TextFileType fileType)
+ {
+ if (feature == null)
+ feature = fileType.getLabel();
+
+ // if this file requires knitr then validate pre-reqs
+ boolean haveRMarkdown =
+ fileTypeCommands_.getHTMLCapabiliites().isRMarkdownSupported();
+ if (!haveRMarkdown)
+ {
+ if (fileType.isRpres())
+ {
+ showKnitrPreviewWarning(display, "R Presentations", "1.2");
+ return false;
+ }
+ else if (fileType.requiresKnit())
+ {
+
+ showKnitrPreviewWarning(display, feature, "1.2");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void showKnitrPreviewWarning(WarningBarDisplay display,
+ String feature,
+ String requiredVersion)
+ {
+ display.showWarningBar(feature + " requires the " +
+ "knitr package (version " + requiredVersion +
+ " or higher)");
+ }
+
+ private FileTypeCommands fileTypeCommands_;
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetScopeHelper.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetScopeHelper.java
new file mode 100644
index 0000000..a4d3296
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetScopeHelper.java
@@ -0,0 +1,163 @@
+/*
+ * TextEditingTargetScopeHelper.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.regex.Pattern.ReplaceOperation;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ScopeList.ScopePredicate;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+
+public class TextEditingTargetScopeHelper
+{
+ public TextEditingTargetScopeHelper(DocDisplay docDisplay)
+ {
+ docDisplay_ = docDisplay;
+ }
+
+ public Scope getCurrentSweaveChunk()
+ {
+ return docDisplay_.getCurrentChunk();
+ }
+
+ public Scope getCurrentSweaveChunk(Position position)
+ {
+ return docDisplay_.getCurrentChunk(position);
+ }
+
+ private class SweaveIncludeContext
+ {
+ private SweaveIncludeContext()
+ {
+ scopeList_ = new ScopeList(docDisplay_);
+ scopeList_.selectAll(ScopeList.CHUNK);
+ }
+
+ public String getSweaveChunkText(final Scope chunk, Range range)
+ {
+ String text = docDisplay_.getCode(range.getStart(), range.getEnd());
+ return Pattern.create("^<<(.*?)>>.*").replaceAll(text, new ReplaceOperation()
+ {
+ @Override
+ public String replace(Match m)
+ {
+ String label = m.getGroup(1).trim();
+ Scope included = getScopeByChunkLabel(label,
+ chunk.getPreamble());
+ if (included == null)
+ return m.getValue();
+ else
+ return getSweaveChunkText(included,
+ getSweaveChunkInnerRange(included));
+ }
+ });
+ }
+
+ private Scope getScopeByChunkLabel(String label, Position beforeHere)
+ {
+ for (Scope s : scopeList_)
+ {
+ if (beforeHere != null
+ && s.getPreamble().isAfterOrEqualTo(beforeHere))
+ return null;
+ if (StringUtil.notNull(s.getChunkLabel()).equals(label))
+ return s;
+ }
+ return null;
+ }
+
+ private final ScopeList scopeList_;
+ }
+
+ public String getSweaveChunkText(Scope chunk)
+ {
+ return getSweaveChunkText(chunk, null);
+ }
+
+ public String getSweaveChunkText(Scope chunk, Range range)
+ {
+ if (range == null)
+ range = getSweaveChunkInnerRange(chunk);
+
+ assert chunk.getPreamble().isBeforeOrEqualTo(range.getStart())
+ && chunk.getEnd().isAfterOrEqualTo(range.getEnd());
+
+ return new SweaveIncludeContext().getSweaveChunkText(chunk, range);
+ }
+
+ public Range getSweaveChunkInnerRange(Scope chunk)
+ {
+ if (chunk == null)
+ return null;
+
+ assert chunk.isChunk();
+
+ Position start = Position.create(chunk.getPreamble().getRow() + 1, 0);
+ Position end = Position.create(chunk.getEnd().getRow(), 0);
+ if (start.getRow() != end.getRow())
+ {
+ end = Position.create(end.getRow()-1,
+ docDisplay_.getLine(end.getRow()-1).length());
+ }
+ return Range.fromPoints(start, end);
+ }
+
+ public Scope getNextSweaveChunk()
+ {
+ ScopeList scopeList = new ScopeList(docDisplay_);
+ scopeList.selectAll(ScopeList.CHUNK);
+ final Position selectionEnd = docDisplay_.getSelectionEnd();
+ return scopeList.findFirst(new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.getPreamble().compareTo(selectionEnd) > 0;
+ }
+ });
+ }
+
+ public Scope getNextFunction(final Position position)
+ {
+ ScopeList scopeList = new ScopeList(docDisplay_);
+ scopeList.selectAll(ScopeList.FUNC);
+ return scopeList.findFirst(new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.getPreamble().compareTo(position) > 0;
+ }
+ });
+ }
+
+ public Scope getPreviousFunction(final Position position)
+ {
+ ScopeList scopeList = new ScopeList(docDisplay_);
+ scopeList.selectAll(ScopeList.FUNC);
+ return scopeList.findLast(new ScopePredicate()
+ {
+ @Override
+ public boolean test(Scope scope)
+ {
+ return scope.getPreamble().compareTo(position) < 0;
+ }
+ });
+ }
+
+ private DocDisplay docDisplay_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetSpelling.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetSpelling.java
new file mode 100644
index 0000000..2a0da5e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetSpelling.java
@@ -0,0 +1,140 @@
+/*
+ * TextEditingTargetSpelling.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.rstudio.core.client.CsvReader;
+import org.rstudio.core.client.CsvWriter;
+import org.rstudio.core.client.ResultCallback;
+import org.rstudio.core.client.widget.NullProgressIndicator;
+import org.rstudio.studio.client.common.spelling.SpellChecker;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.source.editors.text.spelling.CheckSpelling;
+import org.rstudio.studio.client.workbench.views.source.editors.text.spelling.InitialProgressDialog;
+import org.rstudio.studio.client.workbench.views.source.editors.text.spelling.SpellingDialog;
+import org.rstudio.studio.client.workbench.views.source.model.DocUpdateSentinel;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+
+public class TextEditingTargetSpelling implements SpellChecker.Context
+{
+ public TextEditingTargetSpelling(DocDisplay docDisplay,
+ DocUpdateSentinel docUpdateSentinel)
+ {
+ docDisplay_ = docDisplay;
+ docUpdateSentinel_ = docUpdateSentinel;
+ spellChecker_ = new SpellChecker(this);
+
+ }
+
+ public void checkSpelling()
+ {
+ if (isSpellChecking_)
+ return;
+ isSpellChecking_ = true;
+ new CheckSpelling(spellChecker_, docDisplay_,
+ new SpellingDialog(),
+ new InitialProgressDialog(1000),
+ new ResultCallback<Void, Exception>()
+ {
+ @Override
+ public void onSuccess(Void result)
+ {
+ isSpellChecking_ = false;
+ }
+
+ @Override
+ public void onFailure(Exception e)
+ {
+ isSpellChecking_ = false;
+ }
+
+ @Override
+ public void onCancelled()
+ {
+ isSpellChecking_ = false;
+ }
+ });
+ }
+
+ @Override
+ public void invalidateAllWords()
+ {
+
+ }
+
+ @Override
+ public void invalidateMisspelledWords()
+ {
+
+ }
+
+ @Override
+ public ArrayList<String> readDictionary()
+ {
+ ArrayList<String> ignoredWords = new ArrayList<String>();
+ String ignored = docUpdateSentinel_.getProperty(IGNORED_WORDS);
+ if (ignored != null)
+ {
+ Iterator<String[]> iterator = new CsvReader(ignored).iterator();
+ if (iterator.hasNext())
+ {
+ String[] words = iterator.next();
+ for (String word : words)
+ ignoredWords.add(word);
+ }
+ }
+ return ignoredWords;
+ }
+
+ @Override
+ public void writeDictionary(ArrayList<String> ignoredWords)
+ {
+ CsvWriter csvWriter = new CsvWriter();
+ for (String ignored : ignoredWords)
+ csvWriter.writeValue(ignored);
+ csvWriter.endLine();
+ docUpdateSentinel_.setProperty(IGNORED_WORDS,
+ csvWriter.getValue(),
+ new NullProgressIndicator());
+ }
+
+ void onDismiss()
+ {
+ while (releaseOnDismiss_.size() > 0)
+ releaseOnDismiss_.remove(0).removeHandler();
+ }
+
+
+ @Override
+ public void releaseOnDismiss(HandlerRegistration handler)
+ {
+ releaseOnDismiss_.add(handler);
+ }
+
+ private boolean isSpellChecking_;
+
+ private final static String IGNORED_WORDS = "ignored_words";
+
+ private final DocDisplay docDisplay_;
+ private final DocUpdateSentinel docUpdateSentinel_;
+ private final SpellChecker spellChecker_;
+
+ private ArrayList<HandlerRegistration> releaseOnDismiss_ =
+ new ArrayList<HandlerRegistration>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetWidget.java
new file mode 100644
index 0000000..545445f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/TextEditingTargetWidget.java
@@ -0,0 +1,568 @@
+/*
+ * TextEditingTargetWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.events.EnsureHeightEvent;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleEvent;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.layout.RequiresVisibilityChanged;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.shiny.ui.ShinyViewerTypePopupMenu;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.views.edit.ui.EditDialog;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget.Display;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBar;
+import org.rstudio.studio.client.workbench.views.source.editors.text.status.StatusBarWidget;
+
+public class TextEditingTargetWidget
+ extends ResizeComposite
+ implements Display, RequiresVisibilityChanged
+{
+ public TextEditingTargetWidget(Commands commands,
+ UIPrefs uiPrefs,
+ FileTypeRegistry fileTypeRegistry,
+ DocDisplay editor,
+ TextFileType fileType,
+ String extendedType,
+ EventBus events)
+ {
+ commands_ = commands;
+ uiPrefs_ = uiPrefs;
+ fileTypeRegistry_ = fileTypeRegistry;
+ editor_ = editor;
+ extendedType_ = extendedType;
+ sourceOnSave_ = new CheckBox();
+ srcOnSaveLabel_ =
+ new CheckboxLabel(sourceOnSave_, "Source on Save").getLabel();
+ statusBar_ = new StatusBarWidget();
+ shinyViewerMenu_ = RStudioGinjector.INSTANCE.getShinyViewerTypePopupMenu();
+
+ findReplace_ = new TextEditingTargetFindReplace(
+ new TextEditingTargetFindReplace.Container()
+ {
+ @Override
+ public AceEditor getEditor()
+ {
+ return (AceEditor)editor_;
+ }
+
+ @Override
+ public void insertFindReplace(FindReplaceBar findReplaceBar)
+ {
+ Widget beforeWidget = null;
+ if (warningBar_ != null && warningBar_.isAttached())
+ beforeWidget = warningBar_;
+ panel_.insertNorth(findReplaceBar,
+ findReplaceBar.getHeight(),
+ beforeWidget);
+
+ }
+
+ @Override
+ public void removeFindReplace(FindReplaceBar findReplaceBar)
+ {
+ panel_.remove(findReplaceBar);
+ }
+ });
+
+ panel_ = new PanelWithToolbars(toolbar_ = createToolbar(fileType),
+ editor.asWidget(),
+ statusBar_);
+ adaptToFileType(fileType);
+
+ initWidget(panel_);
+ }
+
+ private StatusBarWidget statusBar_;
+
+ private Toolbar createToolbar(TextFileType fileType)
+ {
+ Toolbar toolbar = new EditingTargetToolbar(commands_);
+
+ toolbar.addLeftWidget(commands_.saveSourceDoc().createToolbarButton());
+ sourceOnSave_.getElement().getStyle().setMarginRight(0, Unit.PX);
+ toolbar.addLeftWidget(sourceOnSave_);
+ srcOnSaveLabel_.getElement().getStyle().setMarginRight(9, Unit.PX);
+ toolbar.addLeftWidget(srcOnSaveLabel_);
+
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.checkSpelling().createToolbarButton());
+
+ toolbar.addLeftWidget(findReplace_.createFindReplaceButton());
+ toolbar.addLeftWidget(createCodeTransformMenuButton());
+
+ texSeparatorWidget_ = toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(texToolbarButton_ = createLatexFormatButton());
+
+ ToolbarPopupMenu helpMenu = new ToolbarPopupMenu();
+ helpMenu.addItem(commands_.usingRMarkdownHelp().createMenuItem(false));
+ helpMenu.addItem(commands_.authoringRPresentationsHelp().createMenuItem(false));
+ helpMenu.addSeparator();
+ helpMenu.addItem(commands_.markdownHelp().createMenuItem(false));
+ helpMenuButton_ = new ToolbarButton(null,
+ StandardIcons.INSTANCE.help(),
+ helpMenu);
+ toolbar.addLeftWidget(helpMenuButton_);
+ toolbar.addLeftWidget(rcppHelpButton_ = commands_.rcppHelp().createToolbarButton());
+
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(previewHTMLButton_ = commands_.previewHTML().createToolbarButton());
+ toolbar.addLeftWidget(knitDocumentButton_ = commands_.knitDocument().createToolbarButton());
+ toolbar.addLeftWidget(compilePdfButton_ = commands_.compilePDF().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.synctexSearch().createToolbarButton());
+
+ toolbar.addRightWidget(runButton_ = commands_.executeCode().createToolbarButton(false));
+ toolbar.addRightSeparator();
+ toolbar.addRightWidget(runLastButton_ = commands_.executeLastCode().createToolbarButton(false));
+ toolbar.addRightSeparator();
+ final String SOURCE_BUTTON_TITLE = "Source the active document";
+
+ sourceButton_ = new ToolbarButton(
+ "Source",
+ commands_.sourceActiveDocument().getImageResource(),
+ new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (uiPrefs_.sourceWithEcho().getValue())
+ commands_.sourceActiveDocumentWithEcho().execute();
+ else
+ commands_.sourceActiveDocument().execute();
+ }
+ });
+
+ sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
+ toolbar.addRightWidget(sourceButton_);
+
+ uiPrefs_.sourceWithEcho().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ if (event.getValue())
+ sourceButton_.setTitle(SOURCE_BUTTON_TITLE + " (with echo)");
+ else
+ sourceButton_.setTitle(SOURCE_BUTTON_TITLE);
+ }
+ });
+
+ ToolbarPopupMenu sourceMenu = new ToolbarPopupMenu();
+ sourceMenu.addItem(commands_.sourceActiveDocument().createMenuItem(false));
+ sourceMenu.addItem(commands_.sourceActiveDocumentWithEcho().createMenuItem(false));
+ sourceMenu.addSeparator();
+ sourceMenu.addItem(commands_.compileNotebook().createMenuItem(false));
+
+ sourceMenuButton_ = new ToolbarButton(sourceMenu, true);
+ toolbar.addRightWidget(sourceMenuButton_);
+
+ toolbar.addRightSeparator();
+
+ ToolbarPopupMenu chunksMenu = new ToolbarPopupMenu();
+ chunksMenu.addItem(commands_.insertChunk().createMenuItem(false));
+ chunksMenu.addSeparator();
+ chunksMenu.addItem(commands_.jumpTo().createMenuItem(false));
+ chunksMenu.addSeparator();
+ chunksMenu.addItem(commands_.executeCurrentChunk().createMenuItem(false));
+ chunksMenu.addItem(commands_.executeNextChunk().createMenuItem(false));
+ chunksMenu.addSeparator();
+ chunksMenu.addItem(commands_.executeAllCode().createMenuItem(false));
+ chunksButton_ = new ToolbarButton(
+ "Chunks",
+ StandardIcons.INSTANCE.chunk_menu(),
+ chunksMenu,
+ true);
+ toolbar.addRightWidget(chunksButton_);
+
+ ToolbarPopupMenu shinyLaunchMenu = shinyViewerMenu_;
+ shinyLaunchButton_ = new ToolbarButton(
+ "",
+ StandardIcons.INSTANCE.viewer_window(),
+ shinyLaunchMenu,
+ true);
+ shinyLaunchButton_.setVisible(false);
+ toolbar.addRightWidget(shinyLaunchButton_);
+ return toolbar;
+ }
+
+ private ToolbarButton createLatexFormatButton()
+ {
+ ToolbarPopupMenu texMenu = new TextEditingTargetLatexFormatMenu(editor_,
+ uiPrefs_);
+
+ ToolbarButton texButton = new ToolbarButton(
+ "Format",
+ fileTypeRegistry_.getIconForFilename("foo.tex"),
+ texMenu,
+ false);
+ return texButton;
+ }
+
+ private Widget createCodeTransformMenuButton()
+ {
+ if (codeTransform_ == null)
+ {
+ ImageResource icon = ThemeResources.INSTANCE.codeTransform();
+
+ ToolbarPopupMenu menu = new ToolbarPopupMenu();
+ menu.addItem(commands_.codeCompletion().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.goToHelp().createMenuItem(false));
+ menu.addItem(commands_.goToFunctionDefinition().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.extractFunction().createMenuItem(false));
+ menu.addItem(commands_.extractLocalVariable().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.reindent().createMenuItem(false));
+ menu.addItem(commands_.reflowComment().createMenuItem(false));
+ menu.addItem(commands_.commentUncomment().createMenuItem(false));
+ codeTransform_ = new ToolbarButton("", icon, menu);
+ codeTransform_.setTitle("Code Tools");
+ }
+ return codeTransform_;
+ }
+
+ public void adaptToExtendedFileType(String extendedType)
+ {
+ extendedType_ = extendedType;
+ adaptToFileType(editor_.getFileType());
+ }
+
+ public void adaptToFileType(TextFileType fileType)
+ {
+ editor_.setFileType(fileType);
+ boolean canCompilePdf = fileType.canCompilePDF();
+ boolean canSource = fileType.canSource();
+ boolean canSourceWithEcho = fileType.canSourceWithEcho();
+ boolean canSourceOnSave = fileType.canSourceOnSave();
+ boolean canExecuteCode = fileType.canExecuteCode();
+ boolean canExecuteChunks = fileType.canExecuteChunks();
+ boolean isMarkdown = fileType.isMarkdown();
+ boolean isPlainMarkdown = fileType.isPlainMarkdown();
+ boolean isRPresentation = fileType.isRpres();
+ boolean isCpp = fileType.isCpp();
+
+ // don't show the run buttons for cpp files, or R files in Shiny
+ runButton_.setVisible(canExecuteCode && !isCpp && !isShinyFile());
+ runLastButton_.setVisible(runButton_.isVisible());
+
+ sourceOnSave_.setVisible(canSourceOnSave);
+ srcOnSaveLabel_.setVisible(canSourceOnSave);
+ if (fileType.isRd())
+ srcOnSaveLabel_.setText("Preview on Save");
+ else
+ srcOnSaveLabel_.setText("Source on Save");
+ codeTransform_.setVisible(
+ (canExecuteCode && !fileType.canAuthorContent()) ||
+ fileType.isCpp());
+
+ sourceButton_.setVisible(canSource && !isPlainMarkdown);
+ sourceMenuButton_.setVisible(canSourceWithEcho && !isPlainMarkdown);
+
+ texSeparatorWidget_.setVisible(canCompilePdf);
+ texToolbarButton_.setVisible(canCompilePdf);
+ compilePdfButton_.setVisible(canCompilePdf);
+ chunksButton_.setVisible(canExecuteChunks);
+
+ helpMenuButton_.setVisible(isMarkdown || isRPresentation);
+ rcppHelpButton_.setVisible(isCpp);
+
+ if (isShinyFile())
+ {
+ sourceOnSave_.setVisible(false);
+ srcOnSaveLabel_.setVisible(false);
+ runButton_.setVisible(false);
+ sourceMenuButton_.setVisible(false);
+ chunksButton_.setVisible(false);
+ shinyLaunchButton_.setVisible(true);
+ setSourceButtonFromShinyState();
+ }
+
+ toolbar_.invalidateSeparators();
+ }
+
+ private boolean isShinyFile()
+ {
+ return extendedType_.equals("shiny");
+ }
+
+ public HasValue<Boolean> getSourceOnSave()
+ {
+ return sourceOnSave_;
+ }
+
+ public void ensureVisible()
+ {
+ fireEvent(new EnsureVisibleEvent());
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+
+ manageToolbarSizes();
+
+ }
+
+ private void manageToolbarSizes()
+ {
+ // sometimes width is passed in as 0 (not sure why)
+ int width = getOffsetWidth();
+ if (width == 0)
+ return;
+
+ texToolbarButton_.setText(width < 520 ? "" : "Format");
+ runButton_.setText(((width < 480) || isShinyFile()) ? "" : "Run");
+ compilePdfButton_.setText(width < 450 ? "" : "Compile PDF");
+ previewHTMLButton_.setText(width < 450 ? "" : "Preview");
+ knitDocumentButton_.setText(width < 450 ? "" : "Knit");
+
+ if (editor_.getFileType().isRd())
+ srcOnSaveLabel_.setText(width < 450 ? "Preview" : "Preview on Save");
+ else
+ srcOnSaveLabel_.setText(width < 450 ? "Source" : "Source on Save");
+ sourceButton_.setText(width < 400 ? "" : sourceCommandText_);
+ chunksButton_.setText(width < 400 ? "" : "Chunks");
+ }
+
+
+ public void showWarningBar(String warning)
+ {
+ if (warningBar_ == null)
+ {
+ warningBar_ = new InfoBar(InfoBar.WARNING, new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ hideWarningBar();
+ }
+
+ });
+ }
+ warningBar_.setText(warning);
+ panel_.insertNorth(warningBar_, warningBar_.getHeight(), null);
+ }
+
+ public void hideWarningBar()
+ {
+ if (warningBar_ != null)
+ {
+ panel_.remove(warningBar_);
+ }
+ }
+
+ public void showFindReplace(boolean defaultForward)
+ {
+ findReplace_.showFindReplace(defaultForward);
+ }
+
+ @Override
+ public void findNext()
+ {
+ findReplace_.findNext();
+ }
+
+ @Override
+ public void findPrevious()
+ {
+ findReplace_.findPrevious();
+ }
+
+ @Override
+ public void findFromSelection()
+ {
+ findReplace_.findFromSelection();
+ }
+
+ @Override
+ public void replaceAndFind()
+ {
+ findReplace_.replaceAndFind();
+ }
+
+ public void onActivate()
+ {
+ editor_.onActivate();
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute()
+ {
+ manageToolbarSizes();
+ }
+ });
+ }
+
+ public void setFontSize(double size)
+ {
+ editor_.setFontSize(size);
+ }
+
+ public StatusBar getStatusBar()
+ {
+ return statusBar_;
+ }
+
+ @Override
+ public void debug_dumpContents()
+ {
+ String dump = editor_.debug_getDocumentDump();
+ new EditDialog(dump, false, false, new ProgressOperationWithInput<String>()
+ {
+ @Override
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ }
+ }).showModal();
+ }
+
+ @Override
+ public void debug_importDump()
+ {
+ new EditDialog("", false, false, new ProgressOperationWithInput<String>()
+ {
+ @Override
+ public void execute(String input, ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+ if (input == null)
+ return;
+
+ input = input.replaceAll("[ \\r\\n]+", " ");
+ String[] chars = input.split(" ");
+
+ StringBuilder sb = new StringBuilder();
+ for (String s : chars)
+ {
+ if (s.equals("."))
+ sb.append('\n');
+ else
+ sb.append((char)Integer.parseInt(s));
+ }
+
+ editor_.debug_setSessionValueDirectly(sb.toString());
+ }
+ }).showModal();
+ }
+
+ // Called by the owning TextEditingTarget to notify the widget that the
+ // Shiny application associated with this widget has changed state.
+ @Override
+ public void onShinyApplicationStateChanged(String state)
+ {
+ shinyAppState_ = state;
+ setSourceButtonFromShinyState();
+ }
+
+ public void setSourceButtonFromShinyState()
+ {
+ sourceCommandText_ = commands_.sourceActiveDocument().getButtonLabel();
+ String sourceCommandDesc = commands_.sourceActiveDocument().getDesc();
+ if (isShinyFile())
+ {
+ if (shinyAppState_.equals(ShinyApplicationParams.STATE_STARTED))
+ {
+ sourceCommandText_ = "Reload App";
+ sourceCommandDesc = "Save changes and reload the Shiny application";
+ sourceButton_.setLeftImage(
+ commands_.reloadShinyApp().getImageResource());
+ }
+ else if (shinyAppState_.equals(ShinyApplicationParams.STATE_STOPPED))
+ {
+ sourceCommandText_ = "Run App";
+ sourceCommandDesc = "Run the Shiny application";
+ sourceButton_.setLeftImage(
+ commands_.debugContinue().getImageResource());
+ }
+ }
+ sourceButton_.setTitle(sourceCommandDesc);
+ sourceButton_.setText(sourceCommandText_);
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return addHandler(handler, EnsureVisibleEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addEnsureHeightHandler(
+ EnsureHeightHandler handler)
+ {
+ return addHandler(handler, EnsureHeightEvent.TYPE);
+ }
+
+ public void onVisibilityChanged(boolean visible)
+ {
+ editor_.onVisibilityChanged(visible);
+ }
+
+ private final Commands commands_;
+ private final UIPrefs uiPrefs_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final DocDisplay editor_;
+ private final ShinyViewerTypePopupMenu shinyViewerMenu_;
+ private String extendedType_;
+ private CheckBox sourceOnSave_;
+ private PanelWithToolbars panel_;
+ private Toolbar toolbar_;
+ private InfoBar warningBar_;
+ private final TextEditingTargetFindReplace findReplace_;
+ private ToolbarButton codeTransform_;
+ private ToolbarButton compilePdfButton_;
+ private ToolbarButton previewHTMLButton_;
+ private ToolbarButton knitDocumentButton_;
+ private ToolbarButton runButton_;
+ private ToolbarButton runLastButton_;
+ private ToolbarButton sourceButton_;
+ private ToolbarButton sourceMenuButton_;
+ private ToolbarButton chunksButton_;
+ private ToolbarButton helpMenuButton_;
+ private ToolbarButton rcppHelpButton_;
+ private ToolbarButton shinyLaunchButton_;
+
+ private Widget texSeparatorWidget_;
+ private ToolbarButton texToolbarButton_;
+ private Label srcOnSaveLabel_;
+
+ private String shinyAppState_ = ShinyApplicationParams.STATE_STOPPED;
+ private String sourceCommandText_ = "Source";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WarningBarDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WarningBarDisplay.java
new file mode 100644
index 0000000..b580b17
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WarningBarDisplay.java
@@ -0,0 +1,23 @@
+/*
+ * WarningBarDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import com.google.gwt.user.client.ui.IsWidget;
+
+public interface WarningBarDisplay extends IsWidget
+{
+ void showWarningBar(String message);
+ void hideWarningBar();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WordWrapCursorTracker.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WordWrapCursorTracker.java
new file mode 100644
index 0000000..039e1ae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/WordWrapCursorTracker.java
@@ -0,0 +1,80 @@
+/*
+ * WordWrapCursorTracker.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text;
+
+import org.rstudio.core.client.Point;
+
+public class WordWrapCursorTracker
+{
+ public WordWrapCursorTracker(int row, int column)
+ {
+ row_ = row;
+ column_ = column;
+ }
+
+ public Point getResult()
+ {
+ return output_;
+ }
+
+ public void onBeginInputRow()
+ {
+ currentInputRow_++;
+ }
+
+ public void onChunkWritten(String chunk,
+ int outputRow,
+ int outputColumn,
+ int inputColumn)
+ {
+ if (output_ != null)
+ return; // we already have an answer
+
+// Debug.devlogf("Row: {1}, Chunk: {0}", chunk, row_);
+
+ // Compare the current insertion point to the desired cursor position.
+ int compare = currentInputRow_ - row_;
+ if (compare == 0)
+ compare = (inputColumn + chunk.length()) - column_;
+
+ if (compare < 0)
+ {
+// Debug.devlog("skip");
+ // We haven't gotten there yet--do nothing
+ }
+ else if (currentInputRow_ == row_ && inputColumn <= column_)
+ {
+ // Cursor position is inside the current chunk--nice!
+ output_ = new Point(outputColumn + (column_ - inputColumn),
+ outputRow);
+// Debug.devlogf("exact: {0}, {1}", output_.getY(), output_.getX());
+ }
+ else
+ {
+// Debug.devlog("slop");
+ // We've gone past the cursor position; use the current insertion
+ // point before we get any further away
+ output_ = new Point(outputColumn, outputRow);
+ }
+ }
+
+
+ private final int row_;
+ private final int column_;
+
+ private Point output_;
+
+ private int currentInputRow_ = -1;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceClickEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceClickEvent.java
new file mode 100644
index 0000000..7436a64
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceClickEvent.java
@@ -0,0 +1,68 @@
+/*
+ * AceClickEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class AceClickEvent extends GwtEvent<AceClickEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onClick(AceClickEvent event);
+ }
+
+ public AceClickEvent(AceMouseEventNative event)
+ {
+ event_ = event;
+ }
+
+ public void stopPropagation()
+ {
+ event_.stopPropagation();
+ }
+
+ public void preventDefault()
+ {
+ event_.preventDefault();
+ }
+
+ public Position getDocumentPosition()
+ {
+ return event_.getDocumentPosition();
+ }
+
+ public NativeEvent getNativeEvent()
+ {
+ return event_.getNativeEvent();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onClick(this);
+ }
+
+ private final AceMouseEventNative event_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceDocumentChangeEventNative.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceDocumentChangeEventNative.java
new file mode 100644
index 0000000..a9413a4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceDocumentChangeEventNative.java
@@ -0,0 +1,30 @@
+/*
+ * AceDocumentChangeEventNative.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class AceDocumentChangeEventNative extends JavaScriptObject
+{
+ protected AceDocumentChangeEventNative() {};
+
+ public final native String getAction() /*-{
+ return this.data.action;
+ }-*/;
+
+ public final native Range getRange() /*-{
+ return this.data.range;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceEditorNative.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceEditorNative.java
new file mode 100644
index 0000000..5cef335
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceEditorNative.java
@@ -0,0 +1,257 @@
+/*
+ * AceEditorNative.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.CommandWithArg;
+
+import java.util.LinkedList;
+
+public class AceEditorNative extends JavaScriptObject {
+
+ protected AceEditorNative() {}
+
+ public native final EditSession getSession() /*-{
+ return this.getSession();
+ }-*/;
+
+ public native final Renderer getRenderer() /*-{
+ return this.renderer;
+ }-*/;
+
+ public native final void resize() /*-{
+ this.resize();
+ }-*/;
+
+ public native final void setShowPrintMargin(boolean show) /*-{
+ this.setShowPrintMargin(show);
+ }-*/;
+
+ public native final void setPrintMarginColumn(int column) /*-{
+ this.setPrintMarginColumn(column);
+ }-*/;
+
+ public native final boolean getHighlightActiveLine() /*-{
+ return this.getHighlightActiveLine();
+ }-*/;
+
+ public native final void setHighlightActiveLine(boolean highlight) /*-{
+ this.setHighlightActiveLine(highlight);
+ }-*/;
+
+ public native final void setHighlightGutterLine(boolean highlight) /*-{
+ this.setHighlightGutterLine(highlight);
+ }-*/;
+
+ public native final void setHighlightSelectedWord(boolean highlight) /*-{
+ this.setHighlightSelectedWord(highlight);
+ }-*/;
+
+ public native final boolean getReadOnly() /*-{
+ return this.getReadOnly();
+ }-*/;
+
+ public native final void setReadOnly(boolean readOnly) /*-{
+ this.setReadOnly(readOnly);
+ }-*/;
+
+ public native final void toggleCommentLines() /*-{
+ this.toggleCommentLines();
+ }-*/;
+
+ public native final void focus() /*-{
+ this.focus();
+ }-*/;
+
+ public native final boolean isFocused() /*-{
+ return this.isFocused();
+ }-*/;
+
+ public native final boolean isRowFullyVisible(int row) /*-{
+ return this.isRowFullyVisible(row);
+ }-*/;
+
+ public native final void blur() /*-{
+ this.blur();
+ }-*/;
+
+ public native final void setKeyboardHandler(KeyboardHandler keyboardHandler) /*-{
+ this.setKeyboardHandler(keyboardHandler);
+ }-*/;
+
+ public native final void addKeyboardHandler(KeyboardHandler keyboardHandler) /*-{
+ this.keyBinding.addKeyboardHandler(keyboardHandler);
+ }-*/;
+
+
+ public native final void onChange(CommandWithArg<AceDocumentChangeEventNative> command) /*-{
+ this.getSession().on("change",
+ $entry(function (arg) {
+ command. at org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
+ }));
+ }-*/;
+
+ public native final void onChangeFold(Command command) /*-{
+ this.getSession().on("changeFold",
+ $entry(function () {
+ command. at com.google.gwt.user.client.Command::execute()();
+ }));
+ }-*/;
+
+ public native final <T> void onGutterMouseDown(CommandWithArg<T> command) /*-{
+ this.on("guttermousedown",
+ $entry(function (arg) {
+ command. at org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
+ }));
+ }-*/;
+
+ public final HandlerRegistration delegateEventsTo(HasHandlers handlers)
+ {
+ final LinkedList<JavaScriptObject> handles = new LinkedList<JavaScriptObject>();
+ handles.add(addDomListener(getTextInputElement(), "keydown", handlers));
+ handles.add(addDomListener(getTextInputElement(), "keypress", handlers));
+ handles.add(addDomListener(this.<Element>cast(), "focus", handlers));
+ handles.add(addDomListener(this.<Element>cast(), "blur", handlers));
+
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ while (!handles.isEmpty())
+ removeDomListener(handles.remove());
+ }
+ };
+ }
+
+ private native Element getTextInputElement() /*-{
+ return this.textInput.getElement();
+ }-*/;
+
+ private native static JavaScriptObject addDomListener(
+ Element element,
+ String eventName,
+ HasHandlers hasHandlers) /*-{
+ var event = $wnd.require("ace/lib/event");
+ var listener = $entry(function(e) {
+ @com.google.gwt.event.dom.client.DomEvent::fireNativeEvent(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/event/shared/HasHandlers;Lcom/google/gwt/dom/client/Element;)(e, hasHandlers, element);
+ });
+ event.addListener(element, eventName, listener);
+ return $entry(function() {
+ event.removeListener(element, eventName, listener);
+ });
+ }-*/;
+
+ private native static void removeDomListener(JavaScriptObject handle) /*-{
+ handle();
+ }-*/;
+
+ public static native AceEditorNative createEditor(Element container) /*-{
+ var require = $wnd.require;
+ var loader = require("rstudio/loader");
+ return loader.loadEditor(container);
+ }-*/;
+
+ public static <T> HandlerRegistration addEventListener(
+ JavaScriptObject target,
+ String event,
+ CommandWithArg<T> command)
+ {
+ final JavaScriptObject functor = addEventListenerInternal(target,
+ event,
+ command);
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ invokeFunctor(functor);
+ }
+ };
+ }
+
+ private static native <T> JavaScriptObject addEventListenerInternal(
+ JavaScriptObject target,
+ String eventName,
+ CommandWithArg<T> command) /*-{
+ var callback = $entry(function(arg) {
+ command. at org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
+ });
+
+ target.addEventListener(eventName, callback);
+ return function() {
+ target.removeEventListener(eventName, callback);
+ };
+ }-*/;
+
+ private static native void invokeFunctor(JavaScriptObject functor) /*-{
+ functor();
+ }-*/;
+
+ public final native void scrollToRow(int row) /*-{
+ this.scrollToRow(row);
+ }-*/;
+
+ public final native void scrollToLine(int line, boolean center) /*-{
+ this.scrollToLine(line, center);
+ }-*/;
+
+ public final native void jumpToMatching() /*-{
+ this.jumpToMatching();
+ }-*/;
+
+ public native final void revealRange(Range range, boolean animate) /*-{
+ this.revealRange(range, animate);
+ }-*/;
+
+ public final native void autoHeight() /*-{
+ var editor = this;
+ function updateEditorHeight() {
+ editor.container.style.height = (Math.max(1, editor.getSession().getScreenLength()) * editor.renderer.lineHeight) + 'px';
+ editor.resize();
+ editor.renderer.scrollToY(0);
+ editor.renderer.scrollToX(0);
+ }
+ if (!editor.autoHeightAttached) {
+ editor.autoHeightAttached = true;
+ editor.getSession().getDocument().on("change", updateEditorHeight);
+ editor.renderer.$textLayer.on("changeCharacterSize", updateEditorHeight);
+ }
+ updateEditorHeight();
+ }-*/;
+
+ public final native void onCursorChange() /*-{
+ this.onCursorChange();
+ }-*/;
+
+ public static native void setInsertMatching(boolean insertMatching) /*-{
+ $wnd.require("mode/auto_brace_insert").setInsertMatching(insertMatching);
+ }-*/;
+
+ public static native void setVerticallyAlignFunctionArgs(
+ boolean verticallyAlign) /*-{
+ $wnd.require("mode/r_code_model").setVerticallyAlignFunctionArgs(verticallyAlign);
+ }-*/;
+
+ public final native int getFirstVisibleRow() /*-{
+ return this.getFirstVisibleRow();
+ }-*/;
+
+ public final native int getLastVisibleRow() /*-{
+ return this.getLastVisibleRow();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceFold.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceFold.java
new file mode 100644
index 0000000..e9bf78b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceFold.java
@@ -0,0 +1,36 @@
+/*
+ * AceFold.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class AceFold extends JavaScriptObject
+{
+ public static native AceFold createFold(Range range, String placeholder) /*-{
+ var Fold = $wnd.require('ace/edit_session/fold').Fold;
+ return new Fold(range, placeholder);
+ }-*/;
+
+ protected AceFold() {}
+
+ public native final Position getStart() /*-{ return this.start; }-*/;
+ public native final Position getEnd() /*-{ return this.end; }-*/;
+ public native final String getPlaceholder() /*-{ return this.placeholder; }-*/;
+
+ public native final JsArray<AceFold> getSubFolds() /*-{
+ return this.subFolds;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceInputEditorPosition.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceInputEditorPosition.java
new file mode 100644
index 0000000..45cbb0b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceInputEditorPosition.java
@@ -0,0 +1,163 @@
+/*
+ * AceInputEditorPosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorPosition;
+
+public class AceInputEditorPosition extends InputEditorPosition
+{
+ public AceInputEditorPosition(EditSession session, Position position)
+ {
+ super(position.getRow(), position.getColumn());
+ session_ = session;
+ }
+
+ @Override
+ protected int compareLineTo(Object other)
+ {
+ return getRow() - (Integer)other;
+ }
+
+ @Override
+ public InputEditorPosition movePosition(int position, boolean relative)
+ {
+ return new AceInputEditorPosition(
+ session_,
+ Position.create(getRow(),
+ relative ? getPosition() + position : position));
+ }
+
+ @Override
+ public InputEditorPosition moveToNextLine()
+ {
+ return new AceInputEditorPosition(session_,
+ Position.create(getRow() + 1, 0));
+ }
+
+ @Override
+ public InputEditorPosition moveToPreviousLine()
+ {
+ int newRow = Math.max(getRow() - 1, 0);
+ return new AceInputEditorPosition(session_, Position.create(newRow, 0));
+ }
+
+ private int getRow()
+ {
+ return (Integer)getLine();
+ }
+
+ @Override
+ public int getLineLength()
+ {
+ return session_.getLine(getRow()).length();
+ }
+
+ /**
+ *
+ * @param upwards True if the position should be moved upwards. If true, the
+ * resulting position (if non-null) will be at the end of a non-empty
+ * line. If false, the resulting position (if non-null) will be at the
+ * beginning of a non-empty line.
+ * @param boundary If non-null, provides a boundary point beyond which the
+ * skipping may not pass.
+ * @return A position that's on a non-empty line, or else, null if such a
+ * position couldn't be found before hitting the beginning/end of the
+ * document or a boundary position.
+ */
+ @Override
+ public InputEditorPosition skipEmptyLines(boolean upwards,
+ InputEditorPosition boundary)
+ {
+ Position position = Position.create(getRow(), getPosition());
+ while (isLineEmpty(position, upwards))
+ {
+ if (upwards)
+ {
+ if (position.getRow() <= 0)
+ return null;
+
+ position = Position.create(
+ position.getRow() - 1,
+ session_.getLine(position.getRow() - 1).length());
+ }
+ else
+ {
+ if (position.getRow() >= session_.getLength()-1)
+ return null;
+
+ position = Position.create(position.getRow() + 1, 0);
+ }
+ }
+
+ InputEditorPosition pos = new AceInputEditorPosition(session_, position);
+ return boundary == null ? pos :
+ (upwards && pos.compareTo(boundary) >= 0) ? pos :
+ (!upwards && pos.compareTo(boundary) <= 0) ? pos :
+ null;
+ }
+
+ @Override
+ public InputEditorPosition growToIncludeLines(String pattern, boolean upwards)
+ {
+ int rowNum = getRow();
+ String line = session_.getLine(rowNum);
+ if (!line.matches(pattern))
+ return this;
+
+ while (true)
+ {
+ if (upwards)
+ {
+ if (rowNum == 0)
+ break;
+ if (!session_.getLine(rowNum-1).matches(pattern))
+ break;
+ rowNum--;
+ }
+ else
+ {
+ if (rowNum == session_.getLength()-1)
+ break;
+ if (!session_.getLine(rowNum+1).matches(pattern))
+ break;
+ rowNum++;
+ }
+ }
+
+ int col = upwards ? 0 : session_.getLine(rowNum).length();
+
+ return new AceInputEditorPosition(session_, Position.create(rowNum, col));
+ }
+
+ private boolean isLineEmpty(Position position, boolean leftwards)
+ {
+ String line = session_.getLine(position.getRow());
+ int column = position.getColumn();
+ if (leftwards)
+ line = line.substring(0, Math.min(line.length(), column));
+ else
+ line = line.substring(Math.min(line.length(), column));
+
+ return StringUtil.notNull(line).trim().length() == 0;
+ }
+
+ public Position getValue()
+ {
+ return Position.create(getRow(), getPosition());
+ }
+
+ private final EditSession session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceMouseEventNative.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceMouseEventNative.java
new file mode 100644
index 0000000..f441929
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceMouseEventNative.java
@@ -0,0 +1,47 @@
+/*
+ * AceMouseEventNative.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.NativeEvent;
+
+public class AceMouseEventNative extends JavaScriptObject
+{
+ public interface MouseHandlers
+ {
+ void onMouseDown(AceMouseEventNative event);
+ void onMouseMove(AceMouseEventNative event);
+ }
+
+ protected AceMouseEventNative()
+ {
+ }
+
+ public native final void stopPropagation() /*-{
+ this.stopPropagation();
+ }-*/;
+
+ public native final void preventDefault() /*-{
+ this.preventDefault();
+ }-*/;
+
+ public native final Position getDocumentPosition() /*-{
+ return this.getDocumentPosition();
+ }-*/;
+
+ public native final NativeEvent getNativeEvent() /*-{
+ return this.domEvent;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceResources.java
new file mode 100644
index 0000000..c113e1c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/AceResources.java
@@ -0,0 +1,30 @@
+/*
+ * AceResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import org.rstudio.core.client.resources.StaticDataResource;
+
+public interface AceResources extends ClientBundle
+{
+ public static final AceResources INSTANCE = GWT.create(AceResources.class);
+
+ @Source("ace.js")
+ StaticDataResource acejs();
+
+ @Source("acesupport.js")
+ StaticDataResource acesupportjs();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Anchor.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Anchor.java
new file mode 100644
index 0000000..bc29446
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Anchor.java
@@ -0,0 +1,36 @@
+/*
+ * Anchor.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Anchor extends JavaScriptObject
+{
+ protected Anchor()
+ {}
+
+ public native final Position getPosition() /*-{
+ return this.getPosition();
+ }-*/;
+
+ public native final void detach() /*-{
+ this.detach();
+ }-*/;
+
+ public native static Anchor createAnchor(Document document, int row, int column) /*-{
+ var Anchor = $wnd.require('ace/anchor').Anchor;
+ return new Anchor(document, row, column);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/CodeModel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/CodeModel.java
new file mode 100644
index 0000000..3bd2ab0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/CodeModel.java
@@ -0,0 +1,83 @@
+/*
+ * CodeModel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import org.rstudio.studio.client.workbench.views.source.editors.text.Scope;
+
+public class CodeModel extends JavaScriptObject
+{
+ protected CodeModel() {}
+
+ public native final boolean hasScopes() /*-{
+ if (this.getCurrentScope)
+ return true;
+ else
+ return false;
+ }-*/;
+
+ public native final Scope getCurrentScope(Position position) /*-{
+ if (!this.getCurrentScope)
+ return null;
+ return this.getCurrentScope(position);
+ }-*/;
+
+ public native final Scope getCurrentChunk(Position position) /*-{
+ if (!this.getCurrentScope)
+ return null;
+ return this.getCurrentScope(position, function(scope) {
+ return scope.isChunk();
+ });
+ }-*/;
+
+ public native final Scope getCurrentFunction(Position position) /*-{
+ if (!this.getCurrentScope)
+ return null;
+ return this.getCurrentScope(position, function(scope) {
+ return scope.isBrace() && scope.label;
+ });
+ }-*/;
+
+ public native final Scope getCurrentSection(Position position) /*-{
+ if (!this.getCurrentScope)
+ return null;
+ return this.getCurrentScope(position, function(scope) {
+ return scope.isSection();
+ });
+ }-*/;
+
+ public native final JsArray<Scope> getScopeTree() /*-{
+ return this.getScopeTree ? this.getScopeTree() : [];
+ }-*/;
+
+ public native final Scope findFunctionDefinitionFromUsage(
+ Position usagePos, String functionName) /*-{
+ if (this.findFunctionDefinitionFromUsage != null)
+ return this.findFunctionDefinitionFromUsage(usagePos, functionName);
+ else
+ return null;
+ }-*/;
+
+ public native final Position findNextSignificantToken(Position pos) /*-{
+ // Used to seek past whitespace and comments to find an expression for
+ // breakpoint setting. Use the code model's findNextSignificantToken
+ // method if available; if not, this is a no-op.
+ if (this.findNextSignificantToken)
+ return this.findNextSignificantToken(pos);
+ else
+ return pos;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Document.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Document.java
new file mode 100644
index 0000000..476cdb8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Document.java
@@ -0,0 +1,53 @@
+/*
+ * Document.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Document extends JavaScriptObject
+{
+ protected Document()
+ {}
+
+ public native final void setValue(String value) /*-{
+ this.setValue(value);
+ }-*/;
+
+ public native final String getLine(int row) /*-{
+ return this.getLine(row);
+ }-*/;
+
+ public native final int getLength() /*-{
+ return this.getLength();
+ }-*/;
+
+ public final String getDocumentDump()
+ {
+ StringBuilder output = new StringBuilder();
+ for (int i = 0; i < getLength(); i++)
+ {
+ String line = getLine(i);
+ for (int j = 0; j < line.length(); j++)
+ {
+ char c = line.charAt(j);
+ output.append((int)c);
+ output.append(' ');
+ }
+
+ output.append(". \n");
+ }
+ return output.toString();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/EditSession.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/EditSession.java
new file mode 100644
index 0000000..1994e6c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/EditSession.java
@@ -0,0 +1,171 @@
+/*
+ * EditSession.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+public class EditSession extends JavaScriptObject
+{
+ protected EditSession() {}
+
+ public native final String getValue() /*-{
+ return this.toString();
+ }-*/;
+
+ public native final void setValue(String code) /*-{
+ this.setValue(code);
+ }-*/;
+
+ public native final void insert(Position position, String text) /*-{
+ this.insert(position, text);
+ }-*/;
+
+ public native final Selection getSelection() /*-{
+ return this.getSelection();
+ }-*/;
+
+ public native final Position replace(Range range, String text) /*-{
+ return this.replace(range, text);
+ }-*/;
+
+ public native final String getTextRange(Range range) /*-{
+ return this.getTextRange(range);
+ }-*/;
+
+ public native final String getLine(int row) /*-{
+ return this.getLine(row);
+ }-*/;
+
+ public native final void setUseWrapMode(boolean useWrapMode) /*-{
+ return this.setUseWrapMode(useWrapMode);
+ }-*/;
+
+ public native final void setWrapLimitRange(int min, int max) /*-{
+ this.setWrapLimitRange(min, max);
+ }-*/;
+
+ public native final void setUseSoftTabs(boolean on) /*-{
+ this.setUseSoftTabs(on);
+ }-*/;
+
+ public native final void setTabSize(int tabSize) /*-{
+ this.setTabSize(tabSize);
+ }-*/;
+
+ /**
+ * Number of rows
+ */
+ public native final int getLength() /*-{
+ return this.getLength();
+ }-*/;
+
+ public native final void setEditorMode(String parserName,
+ boolean suppressHighlighting) /*-{
+ var Mode = $wnd.require(parserName).Mode;
+ this.setMode(new Mode(suppressHighlighting, this.getDocument(), this));
+ }-*/;
+
+ public native final Mode getMode() /*-{
+ return this.getMode();
+ }-*/;
+
+ public native final void setDisableOverwrite(boolean disableOverwrite) /*-{
+ this.setDisableOverwrite(disableOverwrite);
+ }-*/;
+
+ public native final int documentToScreenRow(Position position) /*-{
+ return this.documentToScreenRow(position.row, position.column);
+ }-*/;
+
+ public native final int getScreenLength() /*-{
+ return this.getScreenLength();
+ }-*/;
+
+ public native final UndoManager getUndoManager() /*-{
+ return this.getUndoManager();
+ }-*/;
+
+ public native final Document getDocument() /*-{
+ return this.getDocument();
+ }-*/;
+
+ public native final void setNewLineMode(String type) /*-{
+ this.setNewLineMode(type);
+ }-*/;
+
+ public native final void reindent(Range range) /*-{
+ this.reindent(range);
+ }-*/;
+
+ public native final void foldAll() /*-{
+ this.foldAll();
+ }-*/;
+
+ public native final void unfoldAll() /*-{
+ this.unfold();
+ }-*/;
+
+ public native final void toggleFold() /*-{
+ this.toggleFold(false);
+ }-*/;
+
+ public native final JsArray<AceFold> getAllFolds() /*-{
+ return this.getAllFolds();
+ }-*/;
+
+ public native final void addFold(String placeholder, Range range) /*-{
+ this.addFold(placeholder, range);
+ }-*/;
+
+ public native final void unfold(Range range, boolean expandInner) /*-{
+ this.unfold(range, expandInner);
+ }-*/;
+
+ public native final void unfold(Position pos, boolean expandInner) /*-{
+ this.unfold(pos, expandInner);
+ }-*/;
+
+ public native final void unfold(int row, boolean expandInner) /*-{
+ this.unfold(row, expandInner);
+ }-*/;
+
+ public native final int addMarker(Range range,
+ String clazz,
+ String type,
+ boolean inFront) /*-{
+ return this.addMarker(range, clazz, type, inFront);
+ }-*/;
+
+ public native final void removeMarker(int markerId) /*-{
+ this.removeMarker(markerId);
+ }-*/;
+
+ public native final void setBreakpoint(int line) /*-{
+ this.setBreakpoint(line);
+ }-*/;
+
+ public native final void clearBreakpoint(int line) /*-{
+ this.clearBreakpoint(line);
+ }-*/;
+
+ public native final void setBreakpoints(int[] lines) /*-{
+ this.setBreakpoints(lines);
+ }-*/;
+
+ public native final void clearBreakpoints(int[] lines) /*-{
+ this.clearBreakpoints(lines);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/FoldingRules.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/FoldingRules.java
new file mode 100644
index 0000000..f004d9a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/FoldingRules.java
@@ -0,0 +1,29 @@
+/*
+ * FoldingRules.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class FoldingRules extends JavaScriptObject
+{
+ protected FoldingRules()
+ {}
+
+ public native final Range getFoldWidgetRange(EditSession session,
+ String foldStyle,
+ int row) /*-{
+ return this.getFoldWidgetRange(session, foldStyle, row) || null;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/KeyboardHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/KeyboardHandler.java
new file mode 100644
index 0000000..e92ca05
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/KeyboardHandler.java
@@ -0,0 +1,27 @@
+/*
+ * KeyboardHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class KeyboardHandler extends JavaScriptObject
+{
+ protected KeyboardHandler() {}
+
+ public static native KeyboardHandler vim() /*-{
+ var vim = $wnd.require('ace/keyboard/vim').handler;
+ return vim;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Mode.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Mode.java
new file mode 100644
index 0000000..20ac4c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Mode.java
@@ -0,0 +1,59 @@
+/*
+ * Mode.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Mode extends JavaScriptObject
+{
+ public static class InsertChunkInfo extends JavaScriptObject
+ {
+ protected InsertChunkInfo() {}
+
+ public native final String getValue() /*-{
+ return this.value;
+ }-*/;
+
+ /**
+ * @return Position cursor should be navigated to, relative to the
+ * beginning of the value.
+ */
+ public native final Position getCursorPosition() /*-{
+ return this.position || {row: 0, column: 0};
+ }-*/;
+ }
+
+ protected Mode()
+ {
+ }
+
+ public native final CodeModel getCodeModel() /*-{
+ return this.codeModel || {};
+ }-*/;
+
+ public native final String getLanguageMode(Position position) /*-{
+ if (!this.getLanguageMode)
+ return null;
+ return this.getLanguageMode(position);
+ }-*/;
+
+ public native final FoldingRules getFoldingRules() /*-{
+ return this.foldingRules;
+ }-*/;
+
+ public native final InsertChunkInfo getInsertChunkInfo() /*-{
+ return this.insertChunkInfo || null;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Position.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Position.java
new file mode 100644
index 0000000..689cfe2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Position.java
@@ -0,0 +1,74 @@
+/*
+ * Position.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Position extends JavaScriptObject
+{
+ protected Position() {}
+
+ public static native Position create(int row, int column) /*-{
+ return {row: row, column: column};
+ }-*/;
+
+ public native final int getRow() /*-{
+ return this.row;
+ }-*/;
+
+ public native final int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ public final int compareTo(Position other)
+ {
+ if (other == null)
+ return 1;
+
+ int result = getRow() - other.getRow();
+ if (result != 0)
+ return result;
+
+ return getColumn() - other.getColumn();
+ }
+
+ public final boolean isBefore(Position other)
+ {
+ return compareTo(other) < 0;
+ }
+
+ public final boolean isBeforeOrEqualTo(Position other)
+ {
+ return compareTo(other) <= 0;
+ }
+
+ public final boolean isAfter(Position other)
+ {
+ return compareTo(other) > 0;
+ }
+
+ public final boolean isAfterOrEqualTo(Position other)
+ {
+ return compareTo(other) >= 0;
+ }
+
+ public native final void setRow(int row) /*-{
+ this.row = row;
+ }-*/;
+
+ public native final void setColumn(int column) /*-{
+ this.column = column;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Range.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Range.java
new file mode 100644
index 0000000..97b8f59
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Range.java
@@ -0,0 +1,43 @@
+/*
+ * Range.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Range extends JavaScriptObject
+{
+ protected Range() {}
+
+ public static native Range fromPoints(Position start, Position end) /*-{
+ var Range = $wnd.require('ace/range').Range;
+ return Range.fromPoints(start, end);
+ }-*/;
+
+ public final native Position getStart() /*-{
+ return this.start;
+ }-*/;
+
+ public final native Position getEnd() /*-{
+ return this.end;
+ }-*/;
+
+ public final native boolean isEmpty() /*-{
+ return this.isEmpty();
+ }-*/;
+
+ public final native Range extend(int row, int column) /*-{
+ return this.extend(row, column);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Renderer.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Renderer.java
new file mode 100644
index 0000000..3f499fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Renderer.java
@@ -0,0 +1,149 @@
+/*
+ * Renderer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.Element;
+
+public class Renderer extends JavaScriptObject
+{
+ public static class ScreenCoordinates extends JavaScriptObject
+ {
+ protected ScreenCoordinates() {}
+
+ public native final int getPageX() /*-{
+ return Math.round(this.pageX);
+ }-*/;
+
+ public native final int getPageY() /*-{
+ return Math.round(this.pageY);
+ }-*/;
+ }
+
+ protected Renderer() {}
+
+ public native final ScreenCoordinates textToScreenCoordinates(int row,
+ int col) /*-{
+ return this.textToScreenCoordinates(row, col);
+ }-*/;
+
+ public native final void forceScrollbarUpdate() /*-{
+ // WebKit-based browsers have problems repainting the scrollbar after
+ // the editor is hidden and then made visible again. Poking the style
+ // a little bit seems to force a redraw.
+ var style = this.scrollBar.element.style;
+ style.marginBottom = (style.marginBottom == "-1px") ? "0" : "-1px";
+ }-*/;
+
+ public native final void updateFontSize() /*-{
+ this.updateFontSize();
+ }-*/;
+
+ public native final void onResize(boolean force) /*-{
+ this.onResize(force);
+ }-*/;
+
+ public native final void setHScrollBarAlwaysVisible(boolean on) /*-{
+ this.setHScrollBarAlwaysVisible(on);
+ }-*/;
+
+ public native final void setShowGutter(boolean on) /*-{
+ this.setShowGutter(on);
+ }-*/;
+
+ public native final void setShowPrintMargin(boolean on) /*-{
+ this.setShowPrintMargin(on);
+ }-*/;
+
+ public native final void setPrintMarginColumn(int column) /*-{
+ this.setPrintMarginColumn(column);
+ }-*/;
+
+ public native final void setShowInvisibles(boolean show) /*-{
+ this.setShowInvisibles(show);
+ }-*/;
+
+ public native final void setShowIndentGuides(boolean show) /*-{
+ this.setDisplayIndentGuides(show);
+ }-*/;
+
+ public native final void setBlinkingCursor(boolean blinking) /*-{
+ this.$cursorLayer.setBlinking(blinking);
+ }-*/;
+
+ public native final void setPadding(int padding) /*-{
+ this.setPadding(padding);
+ }-*/;
+
+ public native final int getLineHeight() /*-{
+ return this.lineHeight;
+ }-*/;
+
+ public native final double getCharacterWidth() /*-{
+ return this.characterWidth;
+ }-*/;
+
+ public native final Element getCursorElement() /*-{
+ return this.$cursorLayer.cursor;
+ }-*/;
+
+ public native final int getScrollTop() /*-{
+ return this.getScrollTop() || 0;
+ }-*/;
+
+ public native final int getScrollLeft() /*-{
+ return this.getScrollLeft() || 0;
+ }-*/;
+
+ public native final void scrollToY(int scrollTop) /*-{
+ this.scrollToY(scrollTop);
+ }-*/;
+
+ public native final void scrollToX(int scrollLeft) /*-{
+ this.scrollToX(scrollLeft);
+ }-*/;
+
+ public native final void forceImmediateRender() /*-{
+ this.$renderChanges(this.CHANGE_FULL);
+ }-*/;
+
+ public native final void fixVerticalOffsetBug() /*-{
+ this.scroller.scrollTop = 0;
+ }-*/;
+
+ public native final void setPasswordMode(boolean passwordMode) /*-{
+
+ if (passwordMode)
+ {
+ this.characterWidth = 0;
+ this.$textLayer.element.style.visibility = 'hidden';
+ this.$renderChanges(this.CHANGE_FULL);
+ }
+ else
+ {
+ this.characterWidth = this.$textLayer.getCharacterWidth();
+ this.$textLayer.element.style.visibility = 'visible';
+ this.$renderChanges(this.CHANGE_FULL);
+ }
+ }-*/;
+
+ public native final void addGutterDecoration(int line, String clazz) /*-{
+ this.addGutterDecoration(line, clazz);
+ }-*/;
+
+ public native final void removeGutterDecoration(int line, String clazz) /*-{
+ this.removeGutterDecoration(line, clazz);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Search.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Search.java
new file mode 100644
index 0000000..500cdf2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Search.java
@@ -0,0 +1,47 @@
+/*
+ * Search.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Search extends JavaScriptObject
+{
+ public static native Search create(String needle,
+ boolean backwards,
+ boolean wrap,
+ boolean caseSensitive,
+ boolean wholeWord,
+ Position start,
+ Range range,
+ boolean regexpMode) /*-{
+ var Search = $wnd.require('ace/search').Search;
+ return new Search().set({
+ needle: needle,
+ backwards: backwards,
+ wrap: wrap,
+ caseSensitive: caseSensitive,
+ wholeWord: wholeWord,
+ start: start,
+ range: range,
+ regExp: regexpMode
+ })
+ }-*/;
+
+ protected Search() {}
+
+ public final native Range find(EditSession session) /*-{
+ return this.find(session);
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Selection.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Selection.java
new file mode 100644
index 0000000..eabb1c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Selection.java
@@ -0,0 +1,77 @@
+/*
+ * Selection.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.Command;
+import org.rstudio.core.client.CommandWithArg;
+
+public class Selection extends JavaScriptObject
+{
+ protected Selection() {}
+
+ public native final Range getRange() /*-{
+ return this.getRange();
+ }-*/;
+
+ public native final void setSelectionRange(Range range) /*-{
+ this.session.unfold(range, true);
+ this.setSelectionRange(range);
+ }-*/;
+
+ public native final Position getCursor() /*-{
+ return this.getCursor();
+ }-*/;
+
+ public native final void moveCursorTo(int row,
+ int column,
+ boolean preventUpdateDesiredColumn) /*-{
+ this.session.unfold({row: row, column: column}, true);
+ this.moveCursorTo(row, column, preventUpdateDesiredColumn);
+ this.setSelectionAnchor(row, column);
+ }-*/;
+
+ public native final boolean isEmpty() /*-{
+ return this.isEmpty();
+ }-*/;
+
+ public native final void selectAll() /*-{
+ this.selectAll();
+ }-*/;
+
+ public final void addCursorChangeHandler(final CommandWithArg<Position> handler)
+ {
+ onCursorChange(new Command()
+ {
+ public void execute()
+ {
+ handler.execute(getCursor());
+ }
+ });
+ }
+
+ public native final void moveCursorFileEnd() /*-{
+ this.moveCursorFileEnd();
+ var cursor = this.getCursor();
+ this.setSelectionAnchor(cursor.row, cursor.column);
+ }-*/;
+
+ private native void onCursorChange(Command command) /*-{
+ this.on("changeCursor",
+ $entry(function () {
+ command. at com.google.gwt.user.client.Command::execute()();
+ }));
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Token.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Token.java
new file mode 100644
index 0000000..d36b4c3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/Token.java
@@ -0,0 +1,30 @@
+/*
+ * Token.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class Token extends JavaScriptObject
+{
+ protected Token() {}
+
+ public native final String getValue() /*-{
+ return this.value;
+ }-*/;
+
+ public native final String getType() /*-{
+ return this.type;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/TokenIterator.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/TokenIterator.java
new file mode 100644
index 0000000..7cbd1ee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/TokenIterator.java
@@ -0,0 +1,49 @@
+/*
+ * TokenIterator.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class TokenIterator extends JavaScriptObject
+{
+ public static native TokenIterator create(EditSession session,
+ int initialRow,
+ int initialColumn) /*-{
+ var TokenIterator = $wnd.require('ace/token_iterator').TokenIterator;
+ return new TokenIterator(session, initialRow, initialColumn);
+ }-*/;
+
+ protected TokenIterator() {}
+
+ public native final Token stepForward() /*-{
+ return this.stepForward();
+ }-*/;
+
+ public native final Token stepBackward() /*-{
+ return this.stepBackward();
+ }-*/;
+
+ public native final Token getCurrentToken() /*-{
+ return this.getCurrentToken();
+ }-*/;
+
+ public native final int getCurrentTokenRow() /*-{
+ return this.getCurrentTokenRow();
+ }-*/;
+
+ public native final int getCurrentTokenColumn() /*-{
+ return this.getCurrentTokenColumn();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/UndoManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/UndoManager.java
new file mode 100644
index 0000000..113678c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/UndoManager.java
@@ -0,0 +1,27 @@
+/*
+ * UndoManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class UndoManager extends JavaScriptObject
+{
+ protected UndoManager()
+ {}
+
+ public native final JavaScriptObject peek() /*-{
+ return this.peek();
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace-uncompressed.js b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace-uncompressed.js
new file mode 100644
index 0000000..1571e5a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace-uncompressed.js
@@ -0,0 +1,19980 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs <fabian AT ajax DOT org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Define a module along with a payload
+ * @param module a name for the payload
+ * @param payload a function to call with (require, exports, module) params
+ */
+
+(function() {
+
+var ACE_NAMESPACE = "";
+
+var global = (function() {
+ return this;
+})();
+
+var _define = function(module, deps, payload) {
+ if (typeof module !== 'string') {
+ if (_define.original)
+ _define.original.apply(window, arguments);
+ else {
+ console.error('dropping module because define wasn\'t a string.');
+ console.trace();
+ }
+ return;
+ }
+
+ if (arguments.length == 2)
+ payload = deps;
+
+ if (!_define.modules)
+ _define.modules = {};
+
+ _define.modules[module] = payload;
+};
+var _require = function(parentId, module, callback) {
+ if (Object.prototype.toString.call(module) === "[object Array]") {
+ var params = [];
+ for (var i = 0, l = module.length; i < l; ++i) {
+ var dep = lookup(parentId, module[i]);
+ if (!dep && _require.original)
+ return _require.original.apply(window, arguments);
+ params.push(dep);
+ }
+ if (callback) {
+ callback.apply(null, params);
+ }
+ }
+ else if (typeof module === 'string') {
+ var payload = lookup(parentId, module);
+ if (!payload && _require.original)
+ return _require.original.apply(window, arguments);
+
+ if (callback) {
+ callback();
+ }
+
+ return payload;
+ }
+ else {
+ if (_require.original)
+ return _require.original.apply(window, arguments);
+ }
+};
+
+var normalizeModule = function(parentId, moduleName) {
+ // normalize plugin requires
+ if (moduleName.indexOf("!") !== -1) {
+ var chunks = moduleName.split("!");
+ return normalizeModule(parentId, chunks[0]) + "!" + normalizeModule(parentId, chunks[1]);
+ }
+ // normalize relative requires
+ if (moduleName.charAt(0) == ".") {
+ var base = parentId.split("/").slice(0, -1).join("/");
+ moduleName = base + "/" + moduleName;
+
+ while(moduleName.indexOf(".") !== -1 && previous != moduleName) {
+ var previous = moduleName;
+ moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "");
+ }
+ }
+
+ return moduleName;
+};
+var lookup = function(parentId, moduleName) {
+
+ moduleName = normalizeModule(parentId, moduleName);
+
+ var module = _define.modules[moduleName];
+ if (!module) {
+ return null;
+ }
+
+ if (typeof module === 'function') {
+ var exports = {};
+ var mod = {
+ id: moduleName,
+ uri: '',
+ exports: exports,
+ packaged: true
+ };
+
+ var req = function(module, callback) {
+ return _require(moduleName, module, callback);
+ };
+
+ var returnValue = module(req, exports, mod);
+ exports = returnValue || mod.exports;
+
+ // cache the resulting module object for next time
+ _define.modules[moduleName] = exports;
+ return exports;
+ }
+
+ return module;
+};
+
+function exportAce(ns) {
+
+ if (typeof requirejs !== "undefined") {
+
+ var define = global.define;
+ global.define = function(id, deps, callback) {
+ if (typeof callback !== "function")
+ return define.apply(this, arguments);
+
+ return define(id, deps, function(require, exports, module) {
+ if (deps[2] == "module")
+ module.packaged = true;
+ return callback.apply(this, arguments);
+ });
+ };
+ global.define.packaged = true;
+
+ return;
+ }
+
+ var require = function(module, callback) {
+ return _require("", module, callback);
+ };
+ require.packaged = true;
+
+ var root = global;
+ if (ns) {
+ if (!global[ns])
+ global[ns] = {};
+ root = global[ns];
+ }
+
+ if (root.define)
+ _define.original = root.define;
+
+ root.define = _define;
+
+ if (root.require)
+ _require.original = root.require;
+
+ root.require = require;
+}
+
+exportAce(ACE_NAMESPACE);
+
+})();
+
+/**
+ * class Ace
+ *
+ * The main class required to set up an Ace instance in the browser.
+ *
+ *
+ **/
+
+define('ace/ace', ['require', 'exports', 'module' , 'ace/lib/fixoldbrowsers', 'ace/lib/dom', 'ace/lib/event', 'ace/editor', 'ace/edit_session', 'ace/undomanager', 'ace/virtual_renderer', 'ace/multi_select', 'ace/worker/worker_client', 'ace/keyboard/hash_handler', 'ace/keyboard/state_handler', 'ace/placeholder', 'ace/config', 'ace/theme/textmate'], function(require, exports, module) {
+
+
+require("./lib/fixoldbrowsers");
+
+var Dom = require("./lib/dom");
+var Event = require("./lib/event");
+
+var Editor = require("./editor").Editor;
+var EditSession = require("./edit_session").EditSession;
+var UndoManager = require("./undomanager").UndoManager;
+var Renderer = require("./virtual_renderer").VirtualRenderer;
+var MultiSelect = require("./multi_select").MultiSelect;
+
+// The following require()s are for inclusion in the built ace file
+require("./worker/worker_client");
+require("./keyboard/hash_handler");
+require("./keyboard/state_handler");
+require("./placeholder");
+exports.config = require("./config");
+exports.edit = function(el) {
+ if (typeof(el) == "string") {
+ el = document.getElementById(el);
+ }
+
+ if (el.env && el.env.editor instanceof Editor)
+ return el.env.editor;
+
+ var doc = new EditSession(Dom.getInnerText(el));
+ doc.setUndoManager(new UndoManager());
+ el.innerHTML = '';
+
+ var editor = new Editor(new Renderer(el, require("./theme/textmate")));
+ new MultiSelect(editor);
+ editor.setSession(doc);
+
+ var env = {};
+ env.document = doc;
+ env.editor = editor;
+ editor.resize();
+ Event.addListener(window, "resize", function() {
+ editor.resize();
+ });
+ el.env = env;
+ // Store env on editor such that it can be accessed later on from
+ // the returned object.
+ editor.env = env;
+ return editor;
+};
+
+});
+// vim:set ts=4 sts=4 sw=4 st:
+// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
+// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project)
+// -- dantman Daniel Friesen Copyright(C) 2010 XXX No License Specified
+// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License
+// -- Irakli Gozalishvili Copyright (C) 2010 MIT License
+
+/*!
+ Copyright (c) 2009, 280 North Inc. http://280north.com/
+ MIT License. http://github.com/280north/narwhal/blob/master/README.md
+*/
+
+define('ace/lib/fixoldbrowsers', ['require', 'exports', 'module' , 'ace/lib/regexp', 'ace/lib/es5-shim'], function(require, exports, module) {
+
+
+require("./regexp");
+require("./es5-shim");
+
+});
+
+define('ace/lib/regexp', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+ //---------------------------------
+ // Private variables
+ //---------------------------------
+
+ var real = {
+ exec: RegExp.prototype.exec,
+ test: RegExp.prototype.test,
+ match: String.prototype.match,
+ replace: String.prototype.replace,
+ split: String.prototype.split
+ },
+ compliantExecNpcg = real.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups
+ compliantLastIndexIncrement = function () {
+ var x = /^/g;
+ real.test.call(x, "");
+ return !x.lastIndex;
+ }();
+
+ if (compliantLastIndexIncrement && compliantExecNpcg)
+ return;
+
+ //---------------------------------
+ // Overriden native methods
+ //---------------------------------
+
+ // Adds named capture support (with backreferences returned as `result.name`), and fixes two
+ // cross-browser issues per ES3:
+ // - Captured values for nonparticipating capturing groups should be returned as `undefined`,
+ // rather than the empty string.
+ // - `lastIndex` should not be incremented after zero-length matches.
+ RegExp.prototype.exec = function (str) {
+ var match = real.exec.apply(this, arguments),
+ name, r2;
+ if ( typeof(str) == 'string' && match) {
+ // Fix browsers whose `exec` methods don't consistently return `undefined` for
+ // nonparticipating capturing groups
+ if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) {
+ r2 = RegExp(this.source, real.replace.call(getNativeFlags(this), "g", ""));
+ // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
+ // matching due to characters outside the match
+ real.replace.call(str.slice(match.index), r2, function () {
+ for (var i = 1; i < arguments.length - 2; i++) {
+ if (arguments[i] === undefined)
+ match[i] = undefined;
+ }
+ });
+ }
+ // Attach named capture properties
+ if (this._xregexp && this._xregexp.captureNames) {
+ for (var i = 1; i < match.length; i++) {
+ name = this._xregexp.captureNames[i - 1];
+ if (name)
+ match[name] = match[i];
+ }
+ }
+ // Fix browsers that increment `lastIndex` after zero-length matches
+ if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
+ this.lastIndex--;
+ }
+ return match;
+ };
+
+ // Don't override `test` if it won't change anything
+ if (!compliantLastIndexIncrement) {
+ // Fix browser bug in native method
+ RegExp.prototype.test = function (str) {
+ // Use the native `exec` to skip some processing overhead, even though the overriden
+ // `exec` would take care of the `lastIndex` fix
+ var match = real.exec.call(this, str);
+ // Fix browsers that increment `lastIndex` after zero-length matches
+ if (match && this.global && !match[0].length && (this.lastIndex > match.index))
+ this.lastIndex--;
+ return !!match;
+ };
+ }
+
+ //---------------------------------
+ // Private helper functions
+ //---------------------------------
+
+ function getNativeFlags (regex) {
+ return (regex.global ? "g" : "") +
+ (regex.ignoreCase ? "i" : "") +
+ (regex.multiline ? "m" : "") +
+ (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3
+ (regex.sticky ? "y" : "");
+ }
+
+ function indexOf (array, item, from) {
+ if (Array.prototype.indexOf) // Use the native array method if available
+ return array.indexOf(item, from);
+ for (var i = from || 0; i < array.length; i++) {
+ if (array[i] === item)
+ return i;
+ }
+ return -1;
+ }
+
+});
+// vim: ts=4 sts=4 sw=4 expandtab
+// -- kriskowal Kris Kowal Copyright (C) 2009-2011 MIT License
+// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project)
+// -- dantman Daniel Friesen Copyright (C) 2010 XXX TODO License or CLA
+// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License
+// -- Gozala Irakli Gozalishvili Copyright (C) 2010 MIT License
+// -- kitcambridge Kit Cambridge Copyright (C) 2011 MIT License
+// -- kossnocorp Sasha Koss XXX TODO License or CLA
+// -- bryanforbes Bryan Forbes XXX TODO License or CLA
+// -- killdream Quildreen Motta Copyright (C) 2011 MIT Licence
+// -- michaelficarra Michael Ficarra Copyright (C) 2011 3-clause BSD License
+// -- sharkbrainguy Gerard Paapu Copyright (C) 2011 MIT License
+// -- bbqsrc Brendan Molloy (C) 2011 Creative Commons Zero (public domain)
+// -- iwyg XXX TODO License or CLA
+// -- DomenicDenicola Domenic Denicola Copyright (C) 2011 MIT License
+// -- xavierm02 Montillet Xavier XXX TODO License or CLA
+// -- Raynos Raynos XXX TODO License or CLA
+// -- samsonjs Sami Samhuri Copyright (C) 2010 MIT License
+// -- rwldrn Rick Waldron Copyright (C) 2011 MIT License
+// -- lexer Alexey Zakharov XXX TODO License or CLA
+
+/*!
+ Copyright (c) 2009, 280 North Inc. http://280north.com/
+ MIT License. http://github.com/280north/narwhal/blob/master/README.md
+*/
+
+define('ace/lib/es5-shim', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+/*
+ * Brings an environment as close to ECMAScript 5 compliance
+ * as is possible with the facilities of erstwhile engines.
+ *
+ * Annotated ES5: http://es5.github.com/ (specific links below)
+ * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
+ *
+ * @module
+ */
+
+/*whatsupdoc*/
+
+//
+// Function
+// ========
+//
+
+// ES-5 15.3.4.5
+// http://es5.github.com/#x15.3.4.5
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) { // .length is 1
+ // 1. Let Target be the this value.
+ var target = this;
+ // 2. If IsCallable(Target) is false, throw a TypeError exception.
+ if (typeof target != "function")
+ throw new TypeError(); // TODO message
+ // 3. Let A be a new (possibly empty) internal list of all of the
+ // argument values provided after thisArg (arg1, arg2 etc), in order.
+ // XXX slicedArgs will stand in for "A" if used
+ var args = slice.call(arguments, 1); // for normal call
+ // 4. Let F be a new native ECMAScript object.
+ // 11. Set the [[Prototype]] internal property of F to the standard
+ // built-in Function prototype object as specified in 15.3.3.1.
+ // 12. Set the [[Call]] internal property of F as described in
+ // 15.3.4.5.1.
+ // 13. Set the [[Construct]] internal property of F as described in
+ // 15.3.4.5.2.
+ // 14. Set the [[HasInstance]] internal property of F as described in
+ // 15.3.4.5.3.
+ var bound = function () {
+
+ if (this instanceof bound) {
+ // 15.3.4.5.2 [[Construct]]
+ // When the [[Construct]] internal method of a function object,
+ // F that was created using the bind function is called with a
+ // list of arguments ExtraArgs, the following steps are taken:
+ // 1. Let target be the value of F's [[TargetFunction]]
+ // internal property.
+ // 2. If target has no [[Construct]] internal method, a
+ // TypeError exception is thrown.
+ // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Construct]] internal
+ // method of target providing args as the arguments.
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F;
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (result !== null && Object(result) === result)
+ return result;
+ return self;
+
+ } else {
+ // 15.3.4.5.1 [[Call]]
+ // When the [[Call]] internal method of a function object, F,
+ // which was created using the bind function is called with a
+ // this value and a list of arguments ExtraArgs, the following
+ // steps are taken:
+ // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 2. Let boundThis be the value of F's [[BoundThis]] internal
+ // property.
+ // 3. Let target be the value of F's [[TargetFunction]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Call]] internal method
+ // of target providing boundThis as the this value and
+ // providing args as the arguments.
+
+ // equiv: target.call(this, ...boundArgs, ...args)
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+ // XXX bound.length is never writable, so don't even try
+ //
+ // 15. If the [[Class]] internal property of Target is "Function", then
+ // a. Let L be the length property of Target minus the length of A.
+ // b. Set the length own property of F to either 0 or L, whichever is
+ // larger.
+ // 16. Else set the length own property of F to 0.
+ // 17. Set the attributes of the length own property of F to the values
+ // specified in 15.3.5.1.
+
+ // TODO
+ // 18. Set the [[Extensible]] internal property of F to true.
+
+ // TODO
+ // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
+ // 20. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
+ // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
+ // false.
+ // 21. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
+ // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
+ // and false.
+
+ // TODO
+ // NOTE Function objects created using Function.prototype.bind do not
+ // have a prototype property or the [[Code]], [[FormalParameters]], and
+ // [[Scope]] internal properties.
+ // XXX can't delete prototype in pure-js.
+
+ // 22. Return F.
+ return bound;
+ };
+}
+
+// Shortcut to an often accessed properties, in order to avoid multiple
+// dereference that costs universally.
+// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
+// us it in defining shortcuts.
+var call = Function.prototype.call;
+var prototypeOfArray = Array.prototype;
+var prototypeOfObject = Object.prototype;
+var slice = prototypeOfArray.slice;
+var toString = call.bind(prototypeOfObject.toString);
+var owns = call.bind(prototypeOfObject.hasOwnProperty);
+
+// If JS engine supports accessors creating shortcuts.
+var defineGetter;
+var defineSetter;
+var lookupGetter;
+var lookupSetter;
+var supportsAccessors;
+if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
+ defineGetter = call.bind(prototypeOfObject.__defineGetter__);
+ defineSetter = call.bind(prototypeOfObject.__defineSetter__);
+ lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
+ lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
+}
+
+//
+// Array
+// =====
+//
+
+// ES5 15.4.3.2
+// http://es5.github.com/#x15.4.3.2
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
+if (!Array.isArray) {
+ Array.isArray = function isArray(obj) {
+ return toString(obj) == "[object Array]";
+ };
+}
+
+// The IsCallable() check in the Array functions
+// has been replaced with a strict check on the
+// internal class of the object to trap cases where
+// the provided function was actually a regular
+// expression literal, which in V8 and
+// JavaScriptCore is a typeof "function". Only in
+// V8 are regular expression literals permitted as
+// reduce parameters, so it is desirable in the
+// general case for the shim to match the more
+// strict and common behavior of rejecting regular
+// expressions.
+
+// ES5 15.4.4.18
+// http://es5.github.com/#x15.4.4.18
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function forEach(fun /*, thisp*/) {
+ var self = toObject(this),
+ thisp = arguments[1],
+ i = 0,
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ while (i < length) {
+ if (i in self) {
+ // Invoke the callback function with call, passing arguments:
+ // context, property value, property key, thisArg object context
+ fun.call(thisp, self[i], i, self);
+ }
+ i++;
+ }
+ };
+}
+
+// ES5 15.4.4.19
+// http://es5.github.com/#x15.4.4.19
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
+if (!Array.prototype.map) {
+ Array.prototype.map = function map(fun /*, thisp*/) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ result = Array(length),
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self)
+ result[i] = fun.call(thisp, self[i], i, self);
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.20
+// http://es5.github.com/#x15.4.4.20
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
+if (!Array.prototype.filter) {
+ Array.prototype.filter = function filter(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ result = [],
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && fun.call(thisp, self[i], i, self))
+ result.push(self[i]);
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.16
+// http://es5.github.com/#x15.4.4.16
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
+if (!Array.prototype.every) {
+ Array.prototype.every = function every(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && !fun.call(thisp, self[i], i, self))
+ return false;
+ }
+ return true;
+ };
+}
+
+// ES5 15.4.4.17
+// http://es5.github.com/#x15.4.4.17
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
+if (!Array.prototype.some) {
+ Array.prototype.some = function some(fun /*, thisp */) {
+ var self = toObject(this),
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && fun.call(thisp, self[i], i, self))
+ return true;
+ }
+ return false;
+ };
+}
+
+// ES5 15.4.4.21
+// http://es5.github.com/#x15.4.4.21
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
+if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function reduce(fun /*, initial*/) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ // no value to return if no initial value and an empty array
+ if (!length && arguments.length == 1)
+ throw new TypeError(); // TODO message
+
+ var i = 0;
+ var result;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i++];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (++i >= length)
+ throw new TypeError(); // TODO message
+ } while (true);
+ }
+
+ for (; i < length; i++) {
+ if (i in self)
+ result = fun.call(void 0, result, self[i], i, self);
+ }
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.22
+// http://es5.github.com/#x15.4.4.22
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
+if (!Array.prototype.reduceRight) {
+ Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ // no value to return if no initial value, empty array
+ if (!length && arguments.length == 1)
+ throw new TypeError(); // TODO message
+
+ var result, i = length - 1;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i--];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (--i < 0)
+ throw new TypeError(); // TODO message
+ } while (true);
+ }
+
+ do {
+ if (i in this)
+ result = fun.call(void 0, result, self[i], i, self);
+ } while (i--);
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.14
+// http://es5.github.com/#x15.4.4.14
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ if (!length)
+ return -1;
+
+ var i = 0;
+ if (arguments.length > 1)
+ i = toInteger(arguments[1]);
+
+ // handle negative indices
+ i = i >= 0 ? i : Math.max(0, length + i);
+ for (; i < length; i++) {
+ if (i in self && self[i] === sought) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+// ES5 15.4.4.15
+// http://es5.github.com/#x15.4.4.15
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
+if (!Array.prototype.lastIndexOf) {
+ Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
+ var self = toObject(this),
+ length = self.length >>> 0;
+
+ if (!length)
+ return -1;
+ var i = length - 1;
+ if (arguments.length > 1)
+ i = Math.min(i, toInteger(arguments[1]));
+ // handle negative indices
+ i = i >= 0 ? i : length - Math.abs(i);
+ for (; i >= 0; i--) {
+ if (i in self && sought === self[i])
+ return i;
+ }
+ return -1;
+ };
+}
+
+//
+// Object
+// ======
+//
+
+// ES5 15.2.3.2
+// http://es5.github.com/#x15.2.3.2
+if (!Object.getPrototypeOf) {
+ // https://github.com/kriskowal/es5-shim/issues#issue/2
+ // http://ejohn.org/blog/objectgetprototypeof/
+ // recommended by fschaefer on github
+ Object.getPrototypeOf = function getPrototypeOf(object) {
+ return object.__proto__ || (
+ object.constructor ?
+ object.constructor.prototype :
+ prototypeOfObject
+ );
+ };
+}
+
+// ES5 15.2.3.3
+// http://es5.github.com/#x15.2.3.3
+if (!Object.getOwnPropertyDescriptor) {
+ var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a " +
+ "non-object: ";
+ Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) {
+ if ((typeof object != "object" && typeof object != "function") || object === null)
+ throw new TypeError(ERR_NON_OBJECT + object);
+ // If object does not owns property return undefined immediately.
+ if (!owns(object, property))
+ return;
+
+ var descriptor, getter, setter;
+
+ // If object has a property then it's for sure both `enumerable` and
+ // `configurable`.
+ descriptor = { enumerable: true, configurable: true };
+
+ // If JS engine supports accessor properties then property may be a
+ // getter or setter.
+ if (supportsAccessors) {
+ // Unfortunately `__lookupGetter__` will return a getter even
+ // if object has own non getter property along with a same named
+ // inherited getter. To avoid misbehavior we temporary remove
+ // `__proto__` so that `__lookupGetter__` will return getter only
+ // if it's owned by an object.
+ var prototype = object.__proto__;
+ object.__proto__ = prototypeOfObject;
+
+ var getter = lookupGetter(object, property);
+ var setter = lookupSetter(object, property);
+
+ // Once we have getter and setter we can put values back.
+ object.__proto__ = prototype;
+
+ if (getter || setter) {
+ if (getter) descriptor.get = getter;
+ if (setter) descriptor.set = setter;
+
+ // If it was accessor property we're done and return here
+ // in order to avoid adding `value` to the descriptor.
+ return descriptor;
+ }
+ }
+
+ // If we got this far we know that object has an own property that is
+ // not an accessor so we set it as a value and return descriptor.
+ descriptor.value = object[property];
+ return descriptor;
+ };
+}
+
+// ES5 15.2.3.4
+// http://es5.github.com/#x15.2.3.4
+if (!Object.getOwnPropertyNames) {
+ Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
+ return Object.keys(object);
+ };
+}
+
+// ES5 15.2.3.5
+// http://es5.github.com/#x15.2.3.5
+if (!Object.create) {
+ Object.create = function create(prototype, properties) {
+ var object;
+ if (prototype === null) {
+ object = { "__proto__": null };
+ } else {
+ if (typeof prototype != "object")
+ throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'");
+ var Type = function () {};
+ Type.prototype = prototype;
+ object = new Type();
+ // IE has no built-in implementation of `Object.getPrototypeOf`
+ // neither `__proto__`, but this manually setting `__proto__` will
+ // guarantee that `Object.getPrototypeOf` will work as expected with
+ // objects created using `Object.create`
+ object.__proto__ = prototype;
+ }
+ if (properties !== void 0)
+ Object.defineProperties(object, properties);
+ return object;
+ };
+}
+
+// ES5 15.2.3.6
+// http://es5.github.com/#x15.2.3.6
+
+// Patch for WebKit and IE8 standard mode
+// Designed by hax <hax.github.com>
+// related issue: https://github.com/kriskowal/es5-shim/issues#issue/5
+// IE8 Reference:
+// http://msdn.microsoft.com/en-us/library/dd282900.aspx
+// http://msdn.microsoft.com/en-us/library/dd229916.aspx
+// WebKit Bugs:
+// https://bugs.webkit.org/show_bug.cgi?id=36423
+
+function doesDefinePropertyWork(object) {
+ try {
+ Object.defineProperty(object, "sentinel", {});
+ return "sentinel" in object;
+ } catch (exception) {
+ // returns falsy
+ }
+}
+
+// check whether defineProperty works if it's given. Otherwise,
+// shim partially.
+if (Object.defineProperty) {
+ var definePropertyWorksOnObject = doesDefinePropertyWork({});
+ var definePropertyWorksOnDom = typeof document == "undefined" ||
+ doesDefinePropertyWork(document.createElement("div"));
+ if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
+ var definePropertyFallback = Object.defineProperty;
+ }
+}
+
+if (!Object.defineProperty || definePropertyFallback) {
+ var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: ";
+ var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: "
+ var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined " +
+ "on this javascript engine";
+
+ Object.defineProperty = function defineProperty(object, property, descriptor) {
+ if ((typeof object != "object" && typeof object != "function") || object === null)
+ throw new TypeError(ERR_NON_OBJECT_TARGET + object);
+ if ((typeof descriptor != "object" && typeof descriptor != "function") || descriptor === null)
+ throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
+
+ // make a valiant attempt to use the real defineProperty
+ // for I8's DOM elements.
+ if (definePropertyFallback) {
+ try {
+ return definePropertyFallback.call(Object, object, property, descriptor);
+ } catch (exception) {
+ // try the shim if the real one doesn't work
+ }
+ }
+
+ // If it's a data property.
+ if (owns(descriptor, "value")) {
+ // fail silently if "writable", "enumerable", or "configurable"
+ // are requested but not supported
+ /*
+ // alternate approach:
+ if ( // can't implement these features; allow false but not true
+ !(owns(descriptor, "writable") ? descriptor.writable : true) ||
+ !(owns(descriptor, "enumerable") ? descriptor.enumerable : true) ||
+ !(owns(descriptor, "configurable") ? descriptor.configurable : true)
+ )
+ throw new RangeError(
+ "This implementation of Object.defineProperty does not " +
+ "support configurable, enumerable, or writable."
+ );
+ */
+
+ if (supportsAccessors && (lookupGetter(object, property) ||
+ lookupSetter(object, property)))
+ {
+ // As accessors are supported only on engines implementing
+ // `__proto__` we can safely override `__proto__` while defining
+ // a property to make sure that we don't hit an inherited
+ // accessor.
+ var prototype = object.__proto__;
+ object.__proto__ = prototypeOfObject;
+ // Deleting a property anyway since getter / setter may be
+ // defined on object itself.
+ delete object[property];
+ object[property] = descriptor.value;
+ // Setting original `__proto__` back now.
+ object.__proto__ = prototype;
+ } else {
+ object[property] = descriptor.value;
+ }
+ } else {
+ if (!supportsAccessors)
+ throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
+ // If we got that far then getters and setters can be defined !!
+ if (owns(descriptor, "get"))
+ defineGetter(object, property, descriptor.get);
+ if (owns(descriptor, "set"))
+ defineSetter(object, property, descriptor.set);
+ }
+
+ return object;
+ };
+}
+
+// ES5 15.2.3.7
+// http://es5.github.com/#x15.2.3.7
+if (!Object.defineProperties) {
+ Object.defineProperties = function defineProperties(object, properties) {
+ for (var property in properties) {
+ if (owns(properties, property))
+ Object.defineProperty(object, property, properties[property]);
+ }
+ return object;
+ };
+}
+
+// ES5 15.2.3.8
+// http://es5.github.com/#x15.2.3.8
+if (!Object.seal) {
+ Object.seal = function seal(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// ES5 15.2.3.9
+// http://es5.github.com/#x15.2.3.9
+if (!Object.freeze) {
+ Object.freeze = function freeze(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// detect a Rhino bug and patch it
+try {
+ Object.freeze(function () {});
+} catch (exception) {
+ Object.freeze = (function freeze(freezeObject) {
+ return function freeze(object) {
+ if (typeof object == "function") {
+ return object;
+ } else {
+ return freezeObject(object);
+ }
+ };
+ })(Object.freeze);
+}
+
+// ES5 15.2.3.10
+// http://es5.github.com/#x15.2.3.10
+if (!Object.preventExtensions) {
+ Object.preventExtensions = function preventExtensions(object) {
+ // this is misleading and breaks feature-detection, but
+ // allows "securable" code to "gracefully" degrade to working
+ // but insecure code.
+ return object;
+ };
+}
+
+// ES5 15.2.3.11
+// http://es5.github.com/#x15.2.3.11
+if (!Object.isSealed) {
+ Object.isSealed = function isSealed(object) {
+ return false;
+ };
+}
+
+// ES5 15.2.3.12
+// http://es5.github.com/#x15.2.3.12
+if (!Object.isFrozen) {
+ Object.isFrozen = function isFrozen(object) {
+ return false;
+ };
+}
+
+// ES5 15.2.3.13
+// http://es5.github.com/#x15.2.3.13
+if (!Object.isExtensible) {
+ Object.isExtensible = function isExtensible(object) {
+ // 1. If Type(O) is not Object throw a TypeError exception.
+ if (Object(object) === object) {
+ throw new TypeError(); // TODO message
+ }
+ // 2. Return the Boolean value of the [[Extensible]] internal property of O.
+ var name = '';
+ while (owns(object, name)) {
+ name += '?';
+ }
+ object[name] = true;
+ var returnValue = owns(object, name);
+ delete object[name];
+ return returnValue;
+ };
+}
+
+// ES5 15.2.3.14
+// http://es5.github.com/#x15.2.3.14
+if (!Object.keys) {
+ // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
+ var hasDontEnumBug = true,
+ dontEnums = [
+ "toString",
+ "toLocaleString",
+ "valueOf",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "constructor"
+ ],
+ dontEnumsLength = dontEnums.length;
+
+ for (var key in {"toString": null})
+ hasDontEnumBug = false;
+
+ Object.keys = function keys(object) {
+
+ if ((typeof object != "object" && typeof object != "function") || object === null)
+ throw new TypeError("Object.keys called on a non-object");
+
+ var keys = [];
+ for (var name in object) {
+ if (owns(object, name)) {
+ keys.push(name);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
+ var dontEnum = dontEnums[i];
+ if (owns(object, dontEnum)) {
+ keys.push(dontEnum);
+ }
+ }
+ }
+
+ return keys;
+ };
+
+}
+
+//
+// Date
+// ====
+//
+
+// ES5 15.9.5.43
+// http://es5.github.com/#x15.9.5.43
+// This function returns a String value represent the instance in time
+// represented by this Date object. The format of the String is the Date Time
+// string format defined in 15.9.1.15. All fields are present in the String.
+// The time zone is always UTC, denoted by the suffix Z. If the time value of
+// this object is not a finite Number a RangeError exception is thrown.
+if (!Date.prototype.toISOString || (new Date(-62198755200000).toISOString().indexOf('-000001') === -1)) {
+ Date.prototype.toISOString = function toISOString() {
+ var result, length, value, year;
+ if (!isFinite(this))
+ throw new RangeError;
+
+ // the date time string format is specified in 15.9.1.15.
+ result = [this.getUTCMonth() + 1, this.getUTCDate(),
+ this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
+ year = this.getUTCFullYear();
+ year = (year < 0 ? '-' : (year > 9999 ? '+' : '')) + ('00000' + Math.abs(year)).slice(0 <= year && year <= 9999 ? -4 : -6);
+
+ length = result.length;
+ while (length--) {
+ value = result[length];
+ // pad months, days, hours, minutes, and seconds to have two digits.
+ if (value < 10)
+ result[length] = "0" + value;
+ }
+ // pad milliseconds to have three digits.
+ return year + "-" + result.slice(0, 2).join("-") + "T" + result.slice(2).join(":") + "." +
+ ("000" + this.getUTCMilliseconds()).slice(-3) + "Z";
+ }
+}
+
+// ES5 15.9.4.4
+// http://es5.github.com/#x15.9.4.4
+if (!Date.now) {
+ Date.now = function now() {
+ return new Date().getTime();
+ };
+}
+
+// ES5 15.9.5.44
+// http://es5.github.com/#x15.9.5.44
+// This function provides a String representation of a Date object for use by
+// JSON.stringify (15.12.3).
+if (!Date.prototype.toJSON) {
+ Date.prototype.toJSON = function toJSON(key) {
+ // When the toJSON method is called with argument key, the following
+ // steps are taken:
+
+ // 1. Let O be the result of calling ToObject, giving it the this
+ // value as its argument.
+ // 2. Let tv be ToPrimitive(O, hint Number).
+ // 3. If tv is a Number and is not finite, return null.
+ // XXX
+ // 4. Let toISO be the result of calling the [[Get]] internal method of
+ // O with argument "toISOString".
+ // 5. If IsCallable(toISO) is false, throw a TypeError exception.
+ if (typeof this.toISOString != "function")
+ throw new TypeError(); // TODO message
+ // 6. Return the result of calling the [[Call]] internal method of
+ // toISO with O as the this value and an empty argument list.
+ return this.toISOString();
+
+ // NOTE 1 The argument is ignored.
+
+ // NOTE 2 The toJSON function is intentionally generic; it does not
+ // require that its this value be a Date object. Therefore, it can be
+ // transferred to other kinds of objects for use as a method. However,
+ // it does require that any such object have a toISOString method. An
+ // object is free to use the argument key to filter its
+ // stringification.
+ };
+}
+
+// ES5 15.9.4.2
+// http://es5.github.com/#x15.9.4.2
+// based on work shared by Daniel Friesen (dantman)
+// http://gist.github.com/303249
+if (Date.parse("+275760-09-13T00:00:00.000Z") !== 8.64e15) {
+ // XXX global assignment won't work in embeddings that use
+ // an alternate object for the context.
+ Date = (function(NativeDate) {
+
+ // Date.length === 7
+ var Date = function Date(Y, M, D, h, m, s, ms) {
+ var length = arguments.length;
+ if (this instanceof NativeDate) {
+ var date = length == 1 && String(Y) === Y ? // isString(Y)
+ // We explicitly pass it through parse:
+ new NativeDate(Date.parse(Y)) :
+ // We have to manually make calls depending on argument
+ // length here
+ length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) :
+ length >= 6 ? new NativeDate(Y, M, D, h, m, s) :
+ length >= 5 ? new NativeDate(Y, M, D, h, m) :
+ length >= 4 ? new NativeDate(Y, M, D, h) :
+ length >= 3 ? new NativeDate(Y, M, D) :
+ length >= 2 ? new NativeDate(Y, M) :
+ length >= 1 ? new NativeDate(Y) :
+ new NativeDate();
+ // Prevent mixups with unfixed Date object
+ date.constructor = Date;
+ return date;
+ }
+ return NativeDate.apply(this, arguments);
+ };
+
+ // 15.9.1.15 Date Time String Format.
+ var isoDateExpression = new RegExp("^" +
+ "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign + 6-digit extended year
+ "(?:-(\\d{2})" + // optional month capture
+ "(?:-(\\d{2})" + // optional day capture
+ "(?:" + // capture hours:minutes:seconds.milliseconds
+ "T(\\d{2})" + // hours capture
+ ":(\\d{2})" + // minutes capture
+ "(?:" + // optional :seconds.milliseconds
+ ":(\\d{2})" + // seconds capture
+ "(?:\\.(\\d{3}))?" + // milliseconds capture
+ ")?" +
+ "(?:" + // capture UTC offset component
+ "Z|" + // UTC capture
+ "(?:" + // offset specifier +/-hours:minutes
+ "([-+])" + // sign capture
+ "(\\d{2})" + // hours offset capture
+ ":(\\d{2})" + // minutes offset capture
+ ")" +
+ ")?)?)?)?" +
+ "$");
+
+ // Copy any custom methods a 3rd party library may have added
+ for (var key in NativeDate)
+ Date[key] = NativeDate[key];
+
+ // Copy "native" methods explicitly; they may be non-enumerable
+ Date.now = NativeDate.now;
+ Date.UTC = NativeDate.UTC;
+ Date.prototype = NativeDate.prototype;
+ Date.prototype.constructor = Date;
+
+ // Upgrade Date.parse to handle simplified ISO 8601 strings
+ Date.parse = function parse(string) {
+ var match = isoDateExpression.exec(string);
+ if (match) {
+ match.shift(); // kill match[0], the full match
+ // parse months, days, hours, minutes, seconds, and milliseconds
+ for (var i = 1; i < 7; i++) {
+ // provide default values if necessary
+ match[i] = +(match[i] || (i < 3 ? 1 : 0));
+ // match[1] is the month. Months are 0-11 in JavaScript
+ // `Date` objects, but 1-12 in ISO notation, so we
+ // decrement.
+ if (i == 1)
+ match[i]--;
+ }
+
+ // parse the UTC offset component
+ var minuteOffset = +match.pop(), hourOffset = +match.pop(), sign = match.pop();
+
+ // compute the explicit time zone offset if specified
+ var offset = 0;
+ if (sign) {
+ // detect invalid offsets and return early
+ if (hourOffset > 23 || minuteOffset > 59)
+ return NaN;
+
+ // express the provided time zone offset in minutes. The offset is
+ // negative for time zones west of UTC; positive otherwise.
+ offset = (hourOffset * 60 + minuteOffset) * 6e4 * (sign == "+" ? -1 : 1);
+ }
+
+ // Date.UTC for years between 0 and 99 converts year to 1900 + year
+ // The Gregorian calendar has a 400-year cycle, so
+ // to Date.UTC(year + 400, .... ) - 12622780800000 == Date.UTC(year, ...),
+ // where 12622780800000 - number of milliseconds in Gregorian calendar 400 years
+ var year = +match[0];
+ if (0 <= year && year <= 99) {
+ match[0] = year + 400;
+ return NativeDate.UTC.apply(this, match) + offset - 12622780800000;
+ }
+
+ // compute a new UTC date value, accounting for the optional offset
+ return NativeDate.UTC.apply(this, match) + offset;
+ }
+ return NativeDate.parse.apply(this, arguments);
+ };
+
+ return Date;
+ })(Date);
+}
+
+//
+// String
+// ======
+//
+
+// ES5 15.5.4.20
+// http://es5.github.com/#x15.5.4.20
+var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
+ "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
+ "\u2029\uFEFF";
+if (!String.prototype.trim || ws.trim()) {
+ // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+ // http://perfectionkills.com/whitespace-deviations/
+ ws = "[" + ws + "]";
+ var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
+ trimEndRegexp = new RegExp(ws + ws + "*$");
+ String.prototype.trim = function trim() {
+ return String(this).replace(trimBeginRegexp, "").replace(trimEndRegexp, "");
+ };
+}
+
+//
+// Util
+// ======
+//
+
+// ES5 9.4
+// http://es5.github.com/#x9.4
+// http://jsperf.com/to-integer
+var toInteger = function (n) {
+ n = +n;
+ if (n !== n) // isNaN
+ n = 0;
+ else if (n !== 0 && n !== (1/0) && n !== -(1/0))
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ return n;
+};
+
+var prepareString = "a"[0] != "a",
+ // ES5 9.9
+ // http://es5.github.com/#x9.9
+ toObject = function (o) {
+ if (o == null) { // this matches both null and undefined
+ throw new TypeError(); // TODO message
+ }
+ // If the implementation doesn't support by-index access of
+ // string characters (ex. IE < 7), split the string
+ if (prepareString && typeof o == "string" && o) {
+ return o.split("");
+ }
+ return Object(o);
+ };
+});
+
+define('ace/lib/dom', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+var XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+exports.createElement = function(tag, ns) {
+ return document.createElementNS ?
+ document.createElementNS(ns || XHTML_NS, tag) :
+ document.createElement(tag);
+};
+
+exports.setText = function(elem, text) {
+ if (elem.innerText !== undefined) {
+ elem.innerText = text;
+ }
+ if (elem.textContent !== undefined) {
+ elem.textContent = text;
+ }
+};
+
+exports.hasCssClass = function(el, name) {
+ var classes = el.className.split(/\s+/g);
+ return classes.indexOf(name) !== -1;
+};
+exports.addCssClass = function(el, name) {
+ if (!exports.hasCssClass(el, name)) {
+ el.className += " " + name;
+ }
+};
+exports.removeCssClass = function(el, name) {
+ var classes = el.className.split(/\s+/g);
+ while (true) {
+ var index = classes.indexOf(name);
+ if (index == -1) {
+ break;
+ }
+ classes.splice(index, 1);
+ }
+ el.className = classes.join(" ");
+};
+
+exports.toggleCssClass = function(el, name) {
+ var classes = el.className.split(/\s+/g), add = true;
+ while (true) {
+ var index = classes.indexOf(name);
+ if (index == -1) {
+ break;
+ }
+ add = false;
+ classes.splice(index, 1);
+ }
+ if(add)
+ classes.push(name);
+
+ el.className = classes.join(" ");
+ return add;
+};
+exports.setCssClass = function(node, className, include) {
+ if (include) {
+ exports.addCssClass(node, className);
+ } else {
+ exports.removeCssClass(node, className);
+ }
+};
+
+exports.hasCssString = function(id, doc) {
+ var index = 0, sheets;
+ doc = doc || document;
+
+ if (doc.createStyleSheet && (sheets = doc.styleSheets)) {
+ while (index < sheets.length)
+ if (sheets[index++].owningElement.id === id) return true;
+ } else if ((sheets = doc.getElementsByTagName("style"))) {
+ while (index < sheets.length)
+ if (sheets[index++].id === id) return true;
+ }
+
+ return false;
+};
+
+exports.importCssString = function importCssString(cssText, id, doc) {
+ doc = doc || document;
+ // If style is already imported return immediately.
+ if (id && exports.hasCssString(id, doc))
+ return null;
+
+ var style;
+
+ if (doc.createStyleSheet) {
+ style = doc.createStyleSheet();
+ style.cssText = cssText;
+ if (id)
+ style.owningElement.id = id;
+ } else {
+ style = doc.createElementNS
+ ? doc.createElementNS(XHTML_NS, "style")
+ : doc.createElement("style");
+
+ style.appendChild(doc.createTextNode(cssText));
+ if (id)
+ style.id = id;
+
+ var head = doc.getElementsByTagName("head")[0] || doc.documentElement;
+ head.appendChild(style);
+ }
+};
+
+exports.importCssStylsheet = function(uri, doc) {
+ if (doc.createStyleSheet) {
+ doc.createStyleSheet(uri);
+ } else {
+ var link = exports.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = uri;
+
+ var head = doc.getElementsByTagName("head")[0] || doc.documentElement;
+ head.appendChild(link);
+ }
+};
+
+exports.getInnerWidth = function(element) {
+ return (
+ parseInt(exports.computedStyle(element, "paddingLeft"), 10) +
+ parseInt(exports.computedStyle(element, "paddingRight"), 10) +
+ element.clientWidth
+ );
+};
+
+exports.getInnerHeight = function(element) {
+ return (
+ parseInt(exports.computedStyle(element, "paddingTop"), 10) +
+ parseInt(exports.computedStyle(element, "paddingBottom"), 10) +
+ element.clientHeight
+ );
+};
+
+if (window.pageYOffset !== undefined) {
+ exports.getPageScrollTop = function() {
+ return window.pageYOffset;
+ };
+
+ exports.getPageScrollLeft = function() {
+ return window.pageXOffset;
+ };
+}
+else {
+ exports.getPageScrollTop = function() {
+ return document.body.scrollTop;
+ };
+
+ exports.getPageScrollLeft = function() {
+ return document.body.scrollLeft;
+ };
+}
+
+if (window.getComputedStyle)
+ exports.computedStyle = function(element, style) {
+ if (style)
+ return (window.getComputedStyle(element, "") || {})[style] || "";
+ return window.getComputedStyle(element, "") || {};
+ };
+else
+ exports.computedStyle = function(element, style) {
+ if (style)
+ return element.currentStyle[style];
+ return element.currentStyle;
+ };
+
+exports.scrollbarWidth = function(document) {
+
+ var inner = exports.createElement("p");
+ inner.style.width = "100%";
+ inner.style.minWidth = "0px";
+ inner.style.height = "200px";
+
+ var outer = exports.createElement("div");
+ var style = outer.style;
+
+ style.position = "absolute";
+ style.left = "-10000px";
+ style.overflow = "hidden";
+ style.width = "200px";
+ style.minWidth = "0px";
+ style.height = "150px";
+
+ outer.appendChild(inner);
+
+ var body = document.body || document.documentElement;
+ body.appendChild(outer);
+
+ var noScrollbar = inner.offsetWidth;
+
+ style.overflow = "scroll";
+ var withScrollbar = inner.offsetWidth;
+
+ if (noScrollbar == withScrollbar) {
+ withScrollbar = outer.clientWidth;
+ }
+
+ body.removeChild(outer);
+
+ return noScrollbar-withScrollbar;
+};
+exports.setInnerHtml = function(el, innerHtml) {
+ var element = el.cloneNode(false);//document.createElement("div");
+ element.innerHTML = innerHtml;
+ el.parentNode.replaceChild(element, el);
+ return element;
+};
+
+exports.setInnerText = function(el, innerText) {
+ var document = el.ownerDocument;
+ if (document.body && "textContent" in document.body)
+ el.textContent = innerText;
+ else
+ el.innerText = innerText;
+
+};
+
+exports.getInnerText = function(el) {
+ var document = el.ownerDocument;
+ if (document.body && "textContent" in document.body)
+ return el.textContent;
+ else
+ return el.innerText || el.textContent || "";
+};
+
+exports.getParentWindow = function(document) {
+ return document.defaultView || document.parentWindow;
+};
+
+});
+
+define('ace/lib/event', ['require', 'exports', 'module' , 'ace/lib/keys', 'ace/lib/useragent', 'ace/lib/dom'], function(require, exports, module) {
+
+
+var keys = require("./keys");
+var useragent = require("./useragent");
+var dom = require("./dom");
+
+exports.addListener = function(elem, type, callback) {
+ if (elem.addEventListener) {
+ return elem.addEventListener(type, callback, false);
+ }
+ if (elem.attachEvent) {
+ var wrapper = function() {
+ callback(window.event);
+ };
+ callback._wrapper = wrapper;
+ elem.attachEvent("on" + type, wrapper);
+ }
+};
+
+exports.removeListener = function(elem, type, callback) {
+ if (elem.removeEventListener) {
+ return elem.removeEventListener(type, callback, false);
+ }
+ if (elem.detachEvent) {
+ elem.detachEvent("on" + type, callback._wrapper || callback);
+ }
+};
+exports.stopEvent = function(e) {
+ exports.stopPropagation(e);
+ exports.preventDefault(e);
+ return false;
+};
+
+exports.stopPropagation = function(e) {
+ if (e.stopPropagation)
+ e.stopPropagation();
+ else
+ e.cancelBubble = true;
+};
+
+exports.preventDefault = function(e) {
+ if (e.preventDefault)
+ e.preventDefault();
+ else
+ e.returnValue = false;
+};
+exports.getButton = function(e) {
+ if (e.type == "dblclick")
+ return 0;
+ if (e.type == "contextmenu" || (e.ctrlKey && useragent.isMac))
+ return 2;
+
+ // DOM Event
+ if (e.preventDefault) {
+ return e.button;
+ }
+ // old IE
+ else {
+ return {1:0, 2:2, 4:1}[e.button];
+ }
+};
+
+if (document.documentElement.setCapture) {
+ exports.capture = function(el, eventHandler, releaseCaptureHandler) {
+ var called = false;
+ function onReleaseCapture(e) {
+ eventHandler(e);
+
+ if (!called) {
+ called = true;
+ releaseCaptureHandler(e);
+ }
+
+ exports.removeListener(el, "mousemove", eventHandler);
+ exports.removeListener(el, "mouseup", onReleaseCapture);
+ exports.removeListener(el, "losecapture", onReleaseCapture);
+
+ el.releaseCapture();
+ }
+
+ exports.addListener(el, "mousemove", eventHandler);
+ exports.addListener(el, "mouseup", onReleaseCapture);
+ exports.addListener(el, "losecapture", onReleaseCapture);
+ el.setCapture();
+ };
+}
+else {
+ exports.capture = function(el, eventHandler, releaseCaptureHandler) {
+ function onMouseUp(e) {
+ eventHandler && eventHandler(e);
+ releaseCaptureHandler && releaseCaptureHandler(e);
+
+ document.removeEventListener("mousemove", eventHandler, true);
+ document.removeEventListener("mouseup", onMouseUp, true);
+
+ e.stopPropagation();
+ }
+
+ document.addEventListener("mousemove", eventHandler, true);
+ document.addEventListener("mouseup", onMouseUp, true);
+ };
+}
+
+var useMacCompensation = window.desktop && window.navigator.platform == 'MacIntel';
+var useWinCompensation = window.desktop && window.navigator.platform == 'Win32';
+
+if (window.desktop && window.desktop.getScrollingCompensationType) {
+ var compType = window.desktop.getScrollingCompensationType();
+ useMacCompensation = compType == 'Mac';
+ useWinCompensation = compType == 'Win';
+}
+
+exports.addMouseWheelListener = function(el, callback) {
+ var listener = function(e) {
+ var factor = (useMacCompensation && Math.abs(e.wheelDeltaY) > 120) || (useWinCompensation && Math.abs(e.wheelDeltaY) > 3000) ? 960 : 8;
+ if (e.wheelDelta !== undefined) {
+ if (e.wheelDeltaX !== undefined) {
+ e.wheelX = -e.wheelDeltaX / factor;
+ e.wheelY = -e.wheelDeltaY / factor;
+ } else {
+ e.wheelX = 0;
+ e.wheelY = -e.wheelDelta / factor;
+ }
+ }
+ else {
+ if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
+ e.wheelX = (e.detail || 0) * 5;
+ e.wheelY = 0;
+ } else {
+ e.wheelX = 0;
+ e.wheelY = (e.detail || 0) * 5;
+ }
+ }
+ callback(e);
+ };
+ exports.addListener(el, "DOMMouseScroll", listener);
+ exports.addListener(el, "mousewheel", listener);
+};
+
+exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbackName) {
+ var clicks = 0;
+ var startX, startY, timer;
+ var eventNames = {
+ 2: "dblclick",
+ 3: "tripleclick",
+ 4: "quadclick"
+ };
+
+ exports.addListener(el, "mousedown", function(e) {
+ if (exports.getButton(e) != 0) {
+ clicks = 0;
+ } else {
+ var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
+
+ if (!timer || isNewClick)
+ clicks = 0;
+
+ clicks += 1;
+
+ if (timer)
+ clearTimeout(timer)
+ timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
+ }
+ if (clicks == 1) {
+ startX = e.clientX;
+ startY = e.clientY;
+ }
+
+ eventHandler[callbackName]("mousedown", e);
+
+ if (clicks > 4)
+ clicks = 0;
+ else if (clicks > 1)
+ return eventHandler[callbackName](eventNames[clicks], e);
+ });
+
+ if (useragent.isOldIE) {
+ exports.addListener(el, "dblclick", function(e) {
+ clicks = 2;
+ if (timer)
+ clearTimeout(timer);
+ timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
+ eventHandler[callbackName]("mousedown", e);
+ eventHandler[callbackName](eventNames[clicks], e);
+ });
+ }
+};
+
+function normalizeCommandKeys(callback, e, keyCode) {
+ var hashId = 0;
+ if ((useragent.isOpera && !("KeyboardEvent" in window)) && useragent.isMac) {
+ hashId = 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0)
+ | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
+ } else {
+ hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0)
+ | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
+ }
+
+ if (keyCode in keys.MODIFIER_KEYS) {
+ switch (keys.MODIFIER_KEYS[keyCode]) {
+ case "Alt":
+ hashId = 2;
+ break;
+ case "Shift":
+ hashId = 4;
+ break;
+ case "Ctrl":
+ hashId = 1;
+ break;
+ default:
+ hashId = 8;
+ break;
+ }
+ keyCode = 0;
+ }
+
+ if (hashId & 8 && (keyCode == 91 || keyCode == 93)) {
+ keyCode = 0;
+ }
+
+ // If there is no hashID and the keyCode is not a function key, then
+ // we don't call the callback as we don't handle a command key here
+ // (it's a normal key/character input).
+ if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
+ return false;
+ }
+ return callback(e, hashId, keyCode);
+}
+
+exports.addCommandKeyListener = function(el, callback) {
+ var addListener = exports.addListener;
+ if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
+ // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
+ // event if the user pressed the key for a longer time. Instead, the
+ // keydown event was fired once and later on only the keypress event.
+ // To emulate the 'right' keydown behavior, the keyCode of the initial
+ // keyDown event is stored and in the following keypress events the
+ // stores keyCode is used to emulate a keyDown event.
+ var lastKeyDownKeyCode = null;
+ addListener(el, "keydown", function(e) {
+ lastKeyDownKeyCode = e.keyCode;
+ });
+ addListener(el, "keypress", function(e) {
+ return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
+ });
+ } else {
+ var lastDown = null;
+
+ addListener(el, "keydown", function(e) {
+ lastDown = e.keyIdentifier || e.keyCode;
+ return normalizeCommandKeys(callback, e, e.keyCode);
+ });
+ }
+};
+
+if (window.postMessage && !useragent.isOldIE) {
+ var postMessageId = 1;
+ exports.nextTick = function(callback, win) {
+ win = win || window;
+ var messageName = "zero-timeout-message-" + postMessageId;
+ exports.addListener(win, "message", function listener(e) {
+ if (e.data == messageName) {
+ exports.stopPropagation(e);
+ exports.removeListener(win, "message", listener);
+ callback();
+ }
+ });
+ win.postMessage(messageName, "*");
+ };
+}
+else {
+ exports.nextTick = function(callback, win) {
+ win = win || window;
+ window.setTimeout(callback, 0);
+ };
+}
+
+});
+
+// Most of the following code is taken from SproutCore with a few changes.
+
+define('ace/lib/keys', ['require', 'exports', 'module' , 'ace/lib/oop'], function(require, exports, module) {
+
+
+var oop = require("./oop");
+var Keys = (function() {
+ var ret = {
+ MODIFIER_KEYS: {
+ 16: 'Shift', 17: 'Ctrl', 18: 'Alt', 224: 'Meta'
+ },
+
+ KEY_MODS: {
+ "ctrl": 1, "alt": 2, "option" : 2,
+ "shift": 4, "meta": 8, "command": 8
+ },
+
+ FUNCTION_KEYS : {
+ 8 : "Backspace",
+ 9 : "Tab",
+ 13 : "Return",
+ 19 : "Pause",
+ 27 : "Esc",
+ 32 : "Space",
+ 33 : "PageUp",
+ 34 : "PageDown",
+ 35 : "End",
+ 36 : "Home",
+ 37 : "Left",
+ 38 : "Up",
+ 39 : "Right",
+ 40 : "Down",
+ 44 : "Print",
+ 45 : "Insert",
+ 46 : "Delete",
+ 96 : "Numpad0",
+ 97 : "Numpad1",
+ 98 : "Numpad2",
+ 99 : "Numpad3",
+ 100: "Numpad4",
+ 101: "Numpad5",
+ 102: "Numpad6",
+ 103: "Numpad7",
+ 104: "Numpad8",
+ 105: "Numpad9",
+ 112: "F1",
+ 113: "F2",
+ 114: "F3",
+ 115: "F4",
+ 116: "F5",
+ 117: "F6",
+ 118: "F7",
+ 119: "F8",
+ 120: "F9",
+ 121: "F10",
+ 122: "F11",
+ 123: "F12",
+ 144: "Numlock",
+ 145: "Scrolllock"
+ },
+
+ PRINTABLE_KEYS: {
+ 32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5',
+ 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 61: '=', 65: 'a',
+ 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h',
+ 73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o',
+ 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v',
+ 87: 'w', 88: 'x', 89: 'y', 90: 'z', 107: '+', 109: '-', 110: '.',
+ 188: ',', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\',
+ 221: ']', 222: '\''
+ }
+ };
+
+ // A reverse map of FUNCTION_KEYS
+ for (var i in ret.FUNCTION_KEYS) {
+ var name = ret.FUNCTION_KEYS[i].toUpperCase();
+ ret[name] = parseInt(i, 10);
+ }
+
+ // Add the MODIFIER_KEYS, FUNCTION_KEYS and PRINTABLE_KEYS to the KEY
+ // variables as well.
+ oop.mixin(ret, ret.MODIFIER_KEYS);
+ oop.mixin(ret, ret.PRINTABLE_KEYS);
+ oop.mixin(ret, ret.FUNCTION_KEYS);
+
+ return ret;
+})();
+oop.mixin(exports, Keys);
+
+exports.keyCodeToString = function(keyCode) {
+ return (Keys[keyCode] || String.fromCharCode(keyCode)).toLowerCase();
+}
+
+});
+
+define('ace/lib/oop', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+exports.inherits = (function() {
+ var tempCtor = function() {};
+ return function(ctor, superCtor) {
+ tempCtor.prototype = superCtor.prototype;
+ ctor.super_ = superCtor.prototype;
+ ctor.prototype = new tempCtor();
+ ctor.prototype.constructor = ctor;
+ };
+}());
+
+exports.mixin = function(obj, mixin) {
+ for (var key in mixin) {
+ obj[key] = mixin[key];
+ }
+};
+
+exports.implement = function(proto, mixin) {
+ exports.mixin(proto, mixin);
+};
+
+});
+
+define('ace/lib/useragent', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+var os = (navigator.platform.match(/mac|win|linux/i) || ["other"])[0].toLowerCase();
+var ua = navigator.userAgent;
+
+// Is the user using a browser that identifies itself as Windows
+exports.isWin = (os == "win");
+
+// Is the user using a browser that identifies itself as Mac OS
+exports.isMac = (os == "mac");
+
+// Is the user using a browser that identifies itself as Linux
+exports.isLinux = (os == "linux");
+
+exports.isIE =
+ navigator.appName == "Microsoft Internet Explorer"
+ && parseFloat(navigator.userAgent.match(/MSIE ([0-9]+[\.0-9]+)/)[1]);
+
+exports.isOldIE = exports.isIE && exports.isIE < 9;
+
+// Is this Firefox or related?
+exports.isGecko = exports.isMozilla = window.controllers && window.navigator.product === "Gecko";
+
+// oldGecko == rev < 2.0
+exports.isOldGecko = exports.isGecko && parseInt((navigator.userAgent.match(/rv\:(\d+)/)||[])[1], 10) < 4;
+
+// Is this Opera
+exports.isOpera = window.opera && Object.prototype.toString.call(window.opera) == "[object Opera]";
+
+// Is the user using a browser that identifies itself as WebKit
+exports.isWebKit = parseFloat(ua.split("WebKit/")[1]) || undefined;
+
+exports.isChrome = parseFloat(ua.split(" Chrome/")[1]) || undefined;
+
+exports.isAIR = ua.indexOf("AdobeAIR") >= 0;
+
+exports.isIPad = ua.indexOf("iPad") >= 0;
+
+exports.isTouchPad = ua.indexOf("TouchPad") >= 0;
+exports.OS = {
+ LINUX: "LINUX",
+ MAC: "MAC",
+ WINDOWS: "WINDOWS"
+};
+exports.getOS = function() {
+ if (exports.isMac) {
+ return exports.OS.MAC;
+ } else if (exports.isLinux) {
+ return exports.OS.LINUX;
+ } else {
+ return exports.OS.WINDOWS;
+ }
+};
+
+});
+
+define('ace/editor', ['require', 'exports', 'module' , 'ace/lib/fixoldbrowsers', 'ace/mode/markdown', 'ace/mode/html', 'ace/mode/javascript', 'ace/mode/css', 'ace/mode/svg', 'ace/mode/xml', 'ace/mode/c_cpp', 'ace/lib/oop', 'ace/lib/lang', 'ace/lib/useragent', 'ace/keyboard/textinput', 'ace/mouse/mouse_handler', 'ace/mouse/fold_handler', 'ace/keyboard/keybinding', 'ace/keyboard/emacs', 'ace/keyboard/vim', 'ace/keyboard/hash_handler', 'ace/edit_session', 'ace/search', 'ace/range', 'ace/lib [...]
+
+
+require("./lib/fixoldbrowsers");
+require("./mode/markdown");
+require("./mode/html");
+require("./mode/javascript");
+require("./mode/css");
+require("./mode/svg");
+require("./mode/xml");
+require("./mode/c_cpp");
+
+var oop = require("./lib/oop");
+var lang = require("./lib/lang");
+var useragent = require("./lib/useragent");
+var TextInput = require("./keyboard/textinput").TextInput;
+var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
+var FoldHandler = require("./mouse/fold_handler").FoldHandler;
+//var TouchHandler = require("./touch_handler").TouchHandler;
+var KeyBinding = require("./keyboard/keybinding").KeyBinding;
+var EmacsKeyHandler = require("./keyboard/emacs").handler;
+var VimKeyHandler = require("./keyboard/vim").handler;
+var HashHandler = require("./keyboard/hash_handler").HashHandler;
+var EditSession = require("./edit_session").EditSession;
+var Search = require("./search").Search;
+var Range = require("./range").Range;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var CommandManager = require("./commands/command_manager").CommandManager;
+var defaultCommands = require("./commands/default_commands").commands;
+
+// Required by RStudio
+require("./mode/matching_brace_outdent");
+
+/**
+ * new Editor(renderer, session)
+ * - renderer (VirtualRenderer): Associated `VirtualRenderer` that draws everything
+ * - session (EditSession): The `EditSession` to refer to
+ *
+ * Creates a new `Editor` object.
+ *
+ **/
+var Editor = function(renderer, session) {
+ var container = renderer.getContainerElement();
+ this.container = container;
+ this.renderer = renderer;
+
+ this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
+ this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
+ this.renderer.textarea = this.textInput.getElement();
+ this.keyBinding = new KeyBinding(this);
+
+ // TODO detect touch event support
+ if (useragent.isIPad) {
+ //this.$mouseHandler = new TouchHandler(this);
+ } else {
+ this.$mouseHandler = new MouseHandler(this);
+ new FoldHandler(this);
+ }
+
+ this.$blockScrolling = 0;
+ this.$search = new Search().set({
+ wrap: true
+ });
+
+ this.setSession(session || new EditSession(""));
+};
+
+(function(){
+
+ oop.implement(this, EventEmitter);
+ this.setKeyboardHandler = function(keyboardHandler) {
+ this.keyBinding.setKeyboardHandler(keyboardHandler);
+ };
+ this.getKeyboardHandler = function() {
+ return this.keyBinding.getKeyboardHandler();
+ };
+ this.setSession = function(session) {
+ if (this.session == session)
+ return;
+
+ if (this.session) {
+ var oldSession = this.session;
+ this.session.removeEventListener("change", this.$onDocumentChange);
+ this.session.removeEventListener("changeMode", this.$onChangeMode);
+ this.session.removeEventListener("tokenizerUpdate", this.$onTokenizerUpdate);
+ this.session.removeEventListener("changeTabSize", this.$onChangeTabSize);
+ this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit);
+ this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode);
+ this.session.removeEventListener("onChangeFold", this.$onChangeFold);
+ this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker);
+ this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker);
+ this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint);
+ this.session.removeEventListener("changeAnnotation", this.$onChangeAnnotation);
+ this.session.removeEventListener("changeOverwrite", this.$onCursorChange);
+ this.session.removeEventListener("changeScrollTop", this.$onScrollTopChange);
+ this.session.removeEventListener("changeLeftTop", this.$onScrollLeftChange);
+
+ var selection = this.session.getSelection();
+ selection.removeEventListener("changeCursor", this.$onCursorChange);
+ selection.removeEventListener("changeSelection", this.$onSelectionChange);
+ }
+
+ this.session = session;
+
+ this.$onDocumentChange = this.onDocumentChange.bind(this);
+ session.addEventListener("change", this.$onDocumentChange);
+ this.renderer.setSession(session);
+
+ this.$onChangeMode = this.onChangeMode.bind(this);
+ session.addEventListener("changeMode", this.$onChangeMode);
+
+ this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
+ session.addEventListener("tokenizerUpdate", this.$onTokenizerUpdate);
+
+ this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
+ session.addEventListener("changeTabSize", this.$onChangeTabSize);
+
+ this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
+ session.addEventListener("changeWrapLimit", this.$onChangeWrapLimit);
+
+ this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
+ session.addEventListener("changeWrapMode", this.$onChangeWrapMode);
+
+ this.$onChangeFold = this.onChangeFold.bind(this);
+ session.addEventListener("changeFold", this.$onChangeFold);
+
+ this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
+ this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker);
+
+ this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
+ this.session.addEventListener("changeBackMarker", this.$onChangeBackMarker);
+
+ this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
+ this.session.addEventListener("changeBreakpoint", this.$onChangeBreakpoint);
+
+ this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
+ this.session.addEventListener("changeAnnotation", this.$onChangeAnnotation);
+
+ this.$onCursorChange = this.onCursorChange.bind(this);
+ this.session.addEventListener("changeOverwrite", this.$onCursorChange);
+
+ this.$onScrollTopChange = this.onScrollTopChange.bind(this);
+ this.session.addEventListener("changeScrollTop", this.$onScrollTopChange);
+
+ this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
+ this.session.addEventListener("changeScrollLeft", this.$onScrollLeftChange);
+
+ this.selection = session.getSelection();
+ this.selection.addEventListener("changeCursor", this.$onCursorChange);
+
+ this.$onSelectionChange = this.onSelectionChange.bind(this);
+ this.selection.addEventListener("changeSelection", this.$onSelectionChange);
+
+ this.onChangeMode();
+
+ this.$blockScrolling += 1;
+ this.onCursorChange();
+ this.$blockScrolling -= 1;
+
+ this.onScrollTopChange();
+ this.onScrollLeftChange();
+ this.onSelectionChange();
+ this.onChangeFrontMarker();
+ this.onChangeBackMarker();
+ this.onChangeBreakpoint();
+ this.onChangeAnnotation();
+ this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
+ this.renderer.updateFull();
+
+ this._emit("changeSession", {
+ session: session,
+ oldSession: oldSession
+ });
+ };
+ this.getSession = function() {
+ return this.session;
+ };
+ this.setValue = function(val, cursorPos) {
+ this.session.doc.setValue(val);
+
+ if (!cursorPos)
+ this.selectAll();
+ else if (cursorPos == 1)
+ this.navigateFileEnd();
+ else if (cursorPos == -1)
+ this.navigateFileStart();
+
+ return val;
+ };
+ this.getValue = function() {
+ return this.session.getValue();
+ };
+ this.getSelection = function() {
+ return this.selection;
+ };
+ this.resize = function(force) {
+ this.renderer.onResize(force);
+ };
+ this.setTheme = function(theme) {
+ this.renderer.setTheme(theme);
+ };
+ this.getTheme = function() {
+ return this.renderer.getTheme();
+ };
+ this.setStyle = function(style) {
+ this.renderer.setStyle(style);
+ };
+ this.unsetStyle = function(style) {
+ this.renderer.unsetStyle(style);
+ };
+ this.setFontSize = function(size) {
+ this.container.style.fontSize = size;
+ this.renderer.updateFontSize();
+ };
+ this.$highlightBrackets = function() {
+ if (this.session.$bracketHighlight) {
+ this.session.removeMarker(this.session.$bracketHighlight);
+ this.session.$bracketHighlight = null;
+ }
+
+ if (this.$highlightPending) {
+ return;
+ }
+
+ // perform highlight async to not block the browser during navigation
+ var self = this;
+ this.$highlightPending = true;
+ setTimeout(function() {
+ self.$highlightPending = false;
+
+ var pos = self.session.findMatchingBracket(self.getCursorPosition());
+ if (pos) {
+ var range = new Range(pos.row, pos.column, pos.row, pos.column+1);
+ self.session.$bracketHighlight = self.session.addMarker(range, "ace_bracket", "text");
+ }
+ }, 10);
+ };
+ this.focus = function() {
+ // Safari needs the timeout
+ // iOS and Firefox need it called immediately
+ // to be on the save side we do both
+ var _self = this;
+ setTimeout(function() {
+ _self.textInput.focus();
+ });
+ this.textInput.focus();
+ };
+ this.isFocused = function() {
+ return this.textInput.isFocused();
+ };
+ this.blur = function() {
+ this.textInput.blur();
+ };
+ this.onFocus = function() {
+ if (this.$isFocused)
+ return;
+ this.$isFocused = true;
+ this.renderer.showCursor();
+ this.renderer.visualizeFocus();
+ this._emit("focus");
+ };
+ this.onBlur = function() {
+ if (!this.$isFocused)
+ return;
+ this.$isFocused = false;
+ this.renderer.hideCursor();
+ this.renderer.visualizeBlur();
+ this._emit("blur");
+ };
+
+ this.$cursorChange = function() {
+ this.renderer.updateCursor();
+ };
+ this.onDocumentChange = function(e) {
+ var delta = e.data;
+ var range = delta.range;
+ var lastRow;
+
+ if (range.start.row == range.end.row && delta.action != "insertLines" && delta.action != "removeLines")
+ lastRow = range.end.row;
+ else
+ lastRow = Infinity;
+ this.renderer.updateLines(range.start.row, lastRow);
+
+ this._emit("change", e);
+
+ // update cursor because tab characters can influence the cursor position
+ this.$cursorChange();
+ };
+ this.onTokenizerUpdate = function(e) {
+ var rows = e.data;
+ this.renderer.updateLines(rows.first, rows.last);
+ };
+ this.onScrollTopChange = function() {
+ this.renderer.scrollToY(this.session.getScrollTop());
+ };
+ this.onScrollLeftChange = function() {
+ this.renderer.scrollToX(this.session.getScrollLeft());
+ };
+ this.onCursorChange = function() {
+ this.$cursorChange();
+
+ if (!this.$blockScrolling) {
+ this.renderer.scrollCursorIntoView();
+ }
+
+ this.$highlightBrackets();
+ this.$updateHighlightActiveLine();
+ };
+ this.$updateHighlightActiveLine = function() {
+ var session = this.getSession();
+
+ if (session.$highlightLineMarker)
+ session.removeMarker(session.$highlightLineMarker);
+
+ session.$highlightLineMarker = null;
+
+ if (this.$highlightActiveLine) {
+ var cursor = this.getCursorPosition();
+ var foldLine = this.session.getFoldLine(cursor.row);
+
+ if ((this.getSelectionStyle() != "line" || !this.selection.isMultiLine())) {
+ var range;
+ if (foldLine) {
+ range = new Range(foldLine.start.row, 0, foldLine.end.row + 1, 0);
+ } else {
+ range = new Range(cursor.row, 0, cursor.row+1, 0);
+ }
+ session.$highlightLineMarker = session.addMarker(range, "ace_active_line", "background");
+ }
+ }
+ };
+ this.onSelectionChange = function(e) {
+ var session = this.session;
+
+ if (session.$selectionMarker) {
+ session.removeMarker(session.$selectionMarker);
+ }
+ session.$selectionMarker = null;
+
+ if (!this.selection.isEmpty()) {
+ var range = this.selection.getRange();
+ var style = this.getSelectionStyle();
+ session.$selectionMarker = session.addMarker(range, "ace_selection", style);
+ } else {
+ this.$updateHighlightActiveLine();
+ }
+
+ var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp()
+ this.session.highlight(re);
+ };
+
+ this.$getSelectionHighLightRegexp = function() {
+ var session = this.session;
+
+ var selection = this.getSelectionRange();
+ if (selection.isEmpty() || selection.isMultiLine())
+ return;
+
+ var startOuter = selection.start.column - 1;
+ var endOuter = selection.end.column + 1;
+ var line = session.getLine(selection.start.row);
+ var lineCols = line.length;
+ var needle = line.substring(Math.max(startOuter, 0),
+ Math.min(endOuter, lineCols));
+
+ // Make sure the outer characters are not part of the word.
+ if ((startOuter >= 0 && /^[\w\d]/.test(needle)) ||
+ (endOuter <= lineCols && /[\w\d]$/.test(needle)))
+ return;
+
+ needle = line.substring(selection.start.column, selection.end.column);
+ if (!/^[\w\d]+$/.test(needle))
+ return;
+
+ var re = this.$search.$assembleRegExp({
+ wholeWord: true,
+ caseSensitive: true,
+ needle: needle
+ });
+
+ return re;
+ };
+ this.onChangeFrontMarker = function() {
+ this.renderer.updateFrontMarkers();
+ };
+ this.onChangeBackMarker = function() {
+ this.renderer.updateBackMarkers();
+ };
+ this.onChangeBreakpoint = function() {
+ this.renderer.updateBreakpoints();
+ };
+ this.onChangeAnnotation = function() {
+ this.renderer.setAnnotations(this.session.getAnnotations());
+ };
+ this.onChangeMode = function() {
+ this.renderer.updateText();
+ };
+ this.onChangeWrapLimit = function() {
+ this.renderer.updateFull();
+ };
+ this.onChangeWrapMode = function() {
+ this.renderer.onResize(true);
+ };
+ this.onChangeFold = function() {
+ // Update the active line marker as due to folding changes the current
+ // line range on the screen might have changed.
+ this.$updateHighlightActiveLine();
+ // TODO: This might be too much updating. Okay for now.
+ this.renderer.updateFull();
+ };
+ this.getCopyText = function() {
+ var text = "";
+ if (!this.selection.isEmpty())
+ text = this.session.getTextRange(this.getSelectionRange());
+
+ this._emit("copy", text);
+ return text;
+ };
+ this.onCopy = function() {
+ this.commands.exec("copy", this);
+ };
+ this.onCut = function() {
+ this.commands.exec("cut", this);
+ };
+ this.onPaste = function(text) {
+ // todo this should change when paste becomes a command
+ if (this.$readOnly)
+ return;
+ this._emit("paste", text);
+ this.insert(text);
+ };
+ this.insert = function(text) {
+ var session = this.session;
+ var mode = session.getMode();
+
+ var cursor = this.getCursorPosition();
+
+ if (this.getBehavioursEnabled()) {
+ // Get a transform if the current mode wants one.
+ var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
+ if (transform)
+ text = transform.text;
+ }
+
+ text = text.replace("\t", this.session.getTabString());
+
+ // remove selected text
+ if (!this.selection.isEmpty()) {
+ cursor = this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+ else if (this.session.getOverwrite()) {
+ var range = new Range.fromPoints(cursor, cursor);
+ range.end.column += text.length;
+ this.session.remove(range);
+ }
+
+ this.clearSelection();
+
+ var start = cursor.column;
+ var lineState = session.getState(cursor.row);
+ var shouldOutdent = mode.checkOutdent(lineState, session.getLine(cursor.row), text);
+ var line = session.getLine(cursor.row);
+ var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString(), this.session.getTabSize(), cursor.row);
+ var end = session.insert(cursor, text);
+
+ if (transform && transform.selection) {
+ if (transform.selection.length == 2) { // Transform relative to the current column
+ this.selection.setSelectionRange(
+ new Range(cursor.row, start + transform.selection[0],
+ cursor.row, start + transform.selection[1]));
+ } else { // Transform relative to the current row.
+ this.selection.setSelectionRange(
+ new Range(cursor.row + transform.selection[0],
+ transform.selection[1],
+ cursor.row + transform.selection[2],
+ transform.selection[3]));
+ }
+ }
+
+ var lineState = session.getState(cursor.row);
+
+ // TODO disabled multiline auto indent
+ // possibly doing the indent before inserting the text
+ // if (cursor.row !== end.row) {
+ if (session.getDocument().isNewLine(text)) {
+ this.moveCursorTo(cursor.row+1, 0);
+ cursor = this.getCursorPosition();
+
+ var size = session.getTabSize();
+ var minIndent = Number.MAX_VALUE;
+
+ for (var row = cursor.row; row <= end.row; ++row) {
+ var indent = 0;
+
+ line = session.getLine(row);
+ for (var i = 0; i < line.length; ++i)
+ if (line.charAt(i) == '\t')
+ indent += size;
+ else if (line.charAt(i) == ' ')
+ indent += 1;
+ else
+ break;
+ if (/[^\s]/.test(line))
+ minIndent = Math.min(indent, minIndent);
+ }
+
+ for (var row = cursor.row; row <= end.row; ++row) {
+ var outdent = minIndent;
+
+ line = session.getLine(row);
+ for (var i = 0; i < line.length && outdent > 0; ++i)
+ if (line.charAt(i) == '\t')
+ outdent -= size;
+ else if (line.charAt(i) == ' ')
+ outdent -= 1;
+ session.remove(new Range(row, 0, row, i));
+ }
+ session.indentRows(cursor.row, end.row, lineIndent);
+ }
+ if (shouldOutdent)
+ mode.autoOutdent(lineState, session, cursor.row);
+ };
+ this.onTextInput = function(text) {
+ this.keyBinding.onTextInput(text);
+ };
+ this.onCommandKey = function(e, hashId, keyCode) {
+ this.keyBinding.onCommandKey(e, hashId, keyCode);
+ };
+ this.setOverwrite = function(overwrite) {
+ this.session.setOverwrite(overwrite);
+ };
+ this.getOverwrite = function() {
+ return this.session.getOverwrite();
+ };
+ this.toggleOverwrite = function() {
+ this.session.toggleOverwrite();
+ };
+ this.setScrollSpeed = function(speed) {
+ this.$mouseHandler.setScrollSpeed(speed);
+ };
+ this.getScrollSpeed = function() {
+ return this.$mouseHandler.getScrollSpeed();
+ };
+ this.setDragDelay = function(dragDelay) {
+ this.$mouseHandler.setDragDelay(dragDelay);
+ };
+ this.getDragDelay = function() {
+ return this.$mouseHandler.getDragDelay();
+ };
+
+ this.$selectionStyle = "line";
+ this.setSelectionStyle = function(style) {
+ if (this.$selectionStyle == style) return;
+
+ this.$selectionStyle = style;
+ this.onSelectionChange();
+ this._emit("changeSelectionStyle", {data: style});
+ };
+ this.getSelectionStyle = function() {
+ return this.$selectionStyle;
+ };
+
+ this.$highlightActiveLine = true;
+ this.setHighlightActiveLine = function(shouldHighlight) {
+ if (this.$highlightActiveLine == shouldHighlight)
+ return;
+
+ this.$highlightActiveLine = shouldHighlight;
+ this.$updateHighlightActiveLine();
+ };
+ this.getHighlightActiveLine = function() {
+ return this.$highlightActiveLine;
+ };
+
+ this.$highlightGutterLine = true;
+ this.setHighlightGutterLine = function(shouldHighlight) {
+ if (this.$highlightGutterLine == shouldHighlight)
+ return;
+
+ this.renderer.setHighlightGutterLine(shouldHighlight);
+ this.$highlightGutterLine = shouldHighlight;
+ };
+
+ this.getHighlightGutterLine = function() {
+ return this.$highlightGutterLine;
+ };
+
+ this.$highlightSelectedWord = true;
+ this.setHighlightSelectedWord = function(shouldHighlight) {
+ if (this.$highlightSelectedWord == shouldHighlight)
+ return;
+
+ this.$highlightSelectedWord = shouldHighlight;
+ this.$onSelectionChange();
+ };
+ this.getHighlightSelectedWord = function() {
+ return this.$highlightSelectedWord;
+ };
+
+ this.setAnimatedScroll = function(shouldAnimate){
+ this.renderer.setAnimatedScroll(shouldAnimate);
+ };
+
+ this.getAnimatedScroll = function(){
+ return this.renderer.getAnimatedScroll();
+ };
+ this.setShowInvisibles = function(showInvisibles) {
+ this.renderer.setShowInvisibles(showInvisibles);
+ };
+ this.getShowInvisibles = function() {
+ return this.renderer.getShowInvisibles();
+ };
+
+ this.setDisplayIndentGuides = function(display) {
+ this.renderer.setDisplayIndentGuides(display);
+ };
+
+ this.getDisplayIndentGuides = function() {
+ return this.renderer.getDisplayIndentGuides();
+ };
+ this.setShowPrintMargin = function(showPrintMargin) {
+ this.renderer.setShowPrintMargin(showPrintMargin);
+ };
+ this.getShowPrintMargin = function() {
+ return this.renderer.getShowPrintMargin();
+ };
+ this.setPrintMarginColumn = function(showPrintMargin) {
+ this.renderer.setPrintMarginColumn(showPrintMargin);
+ };
+ this.getPrintMarginColumn = function() {
+ return this.renderer.getPrintMarginColumn();
+ };
+
+ this.$readOnly = false;
+ this.setReadOnly = function(readOnly) {
+ this.$readOnly = readOnly;
+ };
+ this.getReadOnly = function() {
+ return this.$readOnly;
+ };
+
+ this.$modeBehaviours = true;
+ this.setBehavioursEnabled = function (enabled) {
+ this.$modeBehaviours = enabled;
+ };
+ this.getBehavioursEnabled = function () {
+ return this.$modeBehaviours;
+ };
+ this.setShowFoldWidgets = function(show) {
+ var gutter = this.renderer.$gutterLayer;
+ if (gutter.getShowFoldWidgets() == show)
+ return;
+
+ this.renderer.$gutterLayer.setShowFoldWidgets(show);
+ this.$showFoldWidgets = show;
+ this.renderer.updateFull();
+ };
+ this.getShowFoldWidgets = function() {
+ return this.renderer.$gutterLayer.getShowFoldWidgets();
+ };
+
+ this.setFadeFoldWidgets = function(show) {
+ this.renderer.setFadeFoldWidgets(show);
+ };
+
+ this.getFadeFoldWidgets = function() {
+ return this.renderer.getFadeFoldWidgets();
+ };
+ this.remove = function(dir) {
+ if (this.selection.isEmpty()){
+ if (dir == "left")
+ this.selection.selectLeft();
+ else
+ this.selection.selectRight();
+ }
+
+ var range = this.getSelectionRange();
+ if (this.getBehavioursEnabled()) {
+ var session = this.session;
+ var state = session.getState(range.start.row);
+ var new_range = session.getMode().transformAction(state, 'deletion', this, session, range);
+ if (new_range)
+ range = new_range;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ };
+ this.removeWordRight = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordRight();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+ this.removeWordLeft = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectWordLeft();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+ this.removeToLineStart = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineStart();
+
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ };
+ this.removeToLineEnd = function() {
+ if (this.selection.isEmpty())
+ this.selection.selectLineEnd();
+
+ var range = this.getSelectionRange();
+ if (range.start.column == range.end.column && range.start.row == range.end.row) {
+ range.end.column = 0;
+ range.end.row++;
+ }
+
+ this.session.remove(range);
+ this.clearSelection();
+ };
+ this.splitLine = function() {
+ if (!this.selection.isEmpty()) {
+ this.session.remove(this.getSelectionRange());
+ this.clearSelection();
+ }
+
+ var cursor = this.getCursorPosition();
+ this.insert("\n");
+ this.moveCursorToPosition(cursor);
+ };
+ this.transposeLetters = function() {
+ if (!this.selection.isEmpty()) {
+ return;
+ }
+
+ var cursor = this.getCursorPosition();
+ var column = cursor.column;
+ if (column === 0)
+ return;
+
+ var line = this.session.getLine(cursor.row);
+ var swap, range;
+ if (column < line.length) {
+ swap = line.charAt(column) + line.charAt(column-1);
+ range = new Range(cursor.row, column-1, cursor.row, column+1);
+ }
+ else {
+ swap = line.charAt(column-1) + line.charAt(column-2);
+ range = new Range(cursor.row, column-2, cursor.row, column);
+ }
+ this.session.replace(range, swap);
+ };
+ this.toLowerCase = function() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toLowerCase());
+ this.selection.setSelectionRange(originalRange);
+ };
+ this.toUpperCase = function() {
+ var originalRange = this.getSelectionRange();
+ if (this.selection.isEmpty()) {
+ this.selection.selectWord();
+ }
+
+ var range = this.getSelectionRange();
+ var text = this.session.getTextRange(range);
+ this.session.replace(range, text.toUpperCase());
+ this.selection.setSelectionRange(originalRange);
+ };
+ this.indent = function() {
+ var session = this.session;
+ var range = this.getSelectionRange();
+
+ if (range.start.row < range.end.row || range.start.column < range.end.column) {
+ var rows = this.$getSelectedRows();
+ session.indentRows(rows.first, rows.last, "\t");
+ } else {
+ var indentString;
+
+ if (this.session.getUseSoftTabs()) {
+ var size = session.getTabSize(),
+ position = this.getCursorPosition(),
+ column = session.documentToScreenColumn(position.row, position.column),
+ count = (size - column % size);
+
+ indentString = lang.stringRepeat(" ", count);
+ } else
+ indentString = "\t";
+ return this.insert(indentString);
+ }
+ };
+ this.blockOutdent = function() {
+ var selection = this.session.getSelection();
+ this.session.outdentRows(selection.getRange());
+ };
+ this.toggleCommentLines = function() {
+ var state = this.session.getState(this.getCursorPosition().row);
+ var rows = this.$getSelectedRows();
+ this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
+ };
+ this.removeLines = function() {
+ var rows = this.$getSelectedRows();
+ var range;
+ if (rows.first === 0 || rows.last+1 < this.session.getLength())
+ range = new Range(rows.first, 0, rows.last+1, 0);
+ else
+ range = new Range(
+ rows.first-1, this.session.getLine(rows.first-1).length,
+ rows.last, this.session.getLine(rows.last).length
+ );
+ this.session.remove(range);
+ this.clearSelection();
+ };
+
+ this.duplicateSelection = function() {
+ var sel = this.selection;
+ var doc = this.session;
+ var range = sel.getRange();
+ if (range.isEmpty()) {
+ var row = range.start.row;
+ doc.duplicateLines(row, row);
+ } else {
+ var reverse = sel.isBackwards()
+ var point = sel.isBackwards() ? range.start : range.end;
+ var endPoint = doc.insert(point, doc.getTextRange(range), false);
+ range.start = point;
+ range.end = endPoint;
+
+ sel.setSelectionRange(range, reverse)
+ }
+ };
+ this.moveLinesDown = function() {
+ this.$moveLines(function(firstRow, lastRow) {
+ return this.session.moveLinesDown(firstRow, lastRow);
+ });
+ };
+ this.moveLinesUp = function() {
+ this.$moveLines(function(firstRow, lastRow) {
+ return this.session.moveLinesUp(firstRow, lastRow);
+ });
+ };
+ this.moveText = function(range, toPosition) {
+ if (this.$readOnly)
+ return null;
+
+ return this.session.moveText(range, toPosition);
+ };
+ this.copyLinesUp = function() {
+ this.$moveLines(function(firstRow, lastRow) {
+ this.session.duplicateLines(firstRow, lastRow);
+ return 0;
+ });
+ };
+ this.copyLinesDown = function() {
+ this.$moveLines(function(firstRow, lastRow) {
+ return this.session.duplicateLines(firstRow, lastRow);
+ });
+ };
+ this.$moveLines = function(mover) {
+ var rows = this.$getSelectedRows();
+ var selection = this.selection;
+ if (!selection.isMultiLine()) {
+ var range = selection.getRange();
+ var reverse = selection.isBackwards();
+ }
+
+ var linesMoved = mover.call(this, rows.first, rows.last);
+
+ if (range) {
+ range.start.row += linesMoved;
+ range.end.row += linesMoved;
+ selection.setSelectionRange(range, reverse);
+ }
+ else {
+ selection.setSelectionAnchor(rows.last+linesMoved+1, 0);
+ selection.$moveSelection(function() {
+ selection.moveCursorTo(rows.first+linesMoved, 0);
+ });
+ }
+ };
+ this.$getSelectedRows = function() {
+ var range = this.getSelectionRange().collapseRows();
+
+ return {
+ first: range.start.row,
+ last: range.end.row
+ };
+ };
+ this.onCompositionStart = function(text) {
+ this.renderer.showComposition(this.getCursorPosition());
+ };
+ this.onCompositionUpdate = function(text) {
+ this.renderer.setCompositionText(text);
+ };
+ this.onCompositionEnd = function() {
+ this.renderer.hideComposition();
+ };
+ this.getFirstVisibleRow = function() {
+ return this.renderer.getFirstVisibleRow();
+ };
+ this.getLastVisibleRow = function() {
+ return this.renderer.getLastVisibleRow();
+ };
+ this.isRowVisible = function(row) {
+ return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
+ };
+ this.isRowFullyVisible = function(row) {
+ return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
+ };
+ this.$getVisibleRowCount = function() {
+ return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
+ };
+
+ this.$moveByPage = function(dir, select) {
+ var renderer = this.renderer;
+ var config = this.renderer.layerConfig;
+ var rows = dir * Math.floor(config.height / config.lineHeight);
+
+ this.$blockScrolling++;
+ if (select == true) {
+ this.selection.$moveSelection(function(){
+ this.moveCursorBy(rows, 0);
+ });
+ } else if (select == false) {
+ this.selection.moveCursorBy(rows, 0);
+ this.selection.clearSelection();
+ }
+ this.$blockScrolling--;
+
+ var scrollTop = renderer.scrollTop;
+
+ renderer.scrollBy(0, rows * config.lineHeight);
+ if (select != null)
+ renderer.scrollCursorIntoView(null, 0.5);
+
+ renderer.animateScrolling(scrollTop);
+ };
+ this.selectPageDown = function() {
+ this.$moveByPage(1, true);
+ };
+ this.selectPageUp = function() {
+ this.$moveByPage(-1, true);
+ };
+ this.gotoPageDown = function() {
+ this.$moveByPage(1, false);
+ };
+ this.gotoPageUp = function() {
+ this.$moveByPage(-1, false);
+ };
+ this.scrollPageDown = function() {
+ this.$moveByPage(1);
+ };
+ this.scrollPageUp = function() {
+ this.$moveByPage(-1);
+ };
+ this.scrollToRow = function(row) {
+ this.renderer.scrollToRow(row);
+ };
+ this.scrollToLine = function(line, center, animate, callback) {
+ this.renderer.scrollToLine(line, center, animate, callback);
+ };
+ this.centerSelection = function() {
+ var range = this.getSelectionRange();
+ var pos = {
+ row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
+ column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
+ }
+ this.renderer.alignCursor(pos, 0.5);
+ };
+ this.getCursorPosition = function() {
+ return this.selection.getCursor();
+ };
+ this.getCursorPositionScreen = function() {
+ return this.session.documentToScreenPosition(this.getCursorPosition());
+ };
+ this.getSelectionRange = function() {
+ return this.selection.getRange();
+ };
+ this.selectAll = function() {
+ this.$blockScrolling += 1;
+ this.selection.selectAll();
+ this.$blockScrolling -= 1;
+ };
+ this.clearSelection = function() {
+ this.selection.clearSelection();
+ };
+ this.moveCursorTo = function(row, column) {
+ this.selection.moveCursorTo(row, column);
+ };
+ this.moveCursorToPosition = function(pos) {
+ this.selection.moveCursorToPosition(pos);
+ };
+ this.jumpToMatching = function(select) {
+ var cursor = this.getCursorPosition();
+
+ var range = this.session.getBracketRange(cursor);
+ if (!range) {
+ range = this.find({
+ needle: /[{}()\[\]]/g,
+ preventScroll:true,
+ start: {row: cursor.row, column: cursor.column - 1}
+ });
+ if (!range)
+ return;
+ var pos = range.start;
+ if (pos.row == cursor.row && Math.abs(pos.column - cursor.column) < 2)
+ range = this.session.getBracketRange(pos);
+ }
+
+ pos = range && range.cursor || pos;
+ if (pos) {
+ if (select) {
+ if (range && range.isEqual(this.getSelectionRange()))
+ this.clearSelection();
+ else
+ this.selection.selectTo(pos.row, pos.column);
+ } else {
+ this.clearSelection();
+ this.moveCursorTo(pos.row, pos.column);
+ }
+ }
+ };
+ this.gotoLine = function(lineNumber, column, animate) {
+ this.selection.clearSelection();
+ this.session.unfold({row: lineNumber - 1, column: column || 0});
+
+ this.$blockScrolling += 1;
+ this.moveCursorTo(lineNumber - 1, column || 0);
+ this.$blockScrolling -= 1;
+
+ if (!this.isRowFullyVisible(lineNumber - 1))
+ this.scrollToLine(lineNumber - 1, true, animate);
+ };
+ this.navigateTo = function(row, column) {
+ this.clearSelection();
+ this.moveCursorTo(row, column);
+ };
+ this.navigateUp = function(times) {
+ this.selection.clearSelection();
+ times = times || 1;
+ this.selection.moveCursorBy(-times, 0);
+ };
+ this.navigateDown = function(times) {
+ this.selection.clearSelection();
+ times = times || 1;
+ this.selection.moveCursorBy(times, 0);
+ };
+ this.navigateLeft = function(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionStart = this.getSelectionRange().start;
+ this.moveCursorToPosition(selectionStart);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorLeft();
+ }
+ }
+ this.clearSelection();
+ };
+ this.navigateRight = function(times) {
+ if (!this.selection.isEmpty()) {
+ var selectionEnd = this.getSelectionRange().end;
+ this.moveCursorToPosition(selectionEnd);
+ }
+ else {
+ times = times || 1;
+ while (times--) {
+ this.selection.moveCursorRight();
+ }
+ }
+ this.clearSelection();
+ };
+ this.navigateLineStart = function() {
+ this.selection.moveCursorLineStart();
+ this.clearSelection();
+ };
+ this.navigateLineEnd = function() {
+ this.selection.moveCursorLineEnd();
+ this.clearSelection();
+ };
+ this.navigateFileEnd = function() {
+ var scrollTop = this.renderer.scrollTop;
+ this.selection.moveCursorFileEnd();
+ this.clearSelection();
+ this.renderer.animateScrolling(scrollTop);
+ };
+ this.navigateFileStart = function() {
+ var scrollTop = this.renderer.scrollTop;
+ this.selection.moveCursorFileStart();
+ this.clearSelection();
+ this.renderer.animateScrolling(scrollTop);
+ };
+ this.navigateWordRight = function() {
+ this.selection.moveCursorWordRight();
+ this.clearSelection();
+ };
+ this.navigateWordLeft = function() {
+ this.selection.moveCursorWordLeft();
+ this.clearSelection();
+ };
+ this.replace = function(replacement, options) {
+ if (options)
+ this.$search.set(options);
+
+ var range = this.$search.find(this.session);
+ var replaced = 0;
+ if (!range)
+ return replaced;
+
+ if (this.$tryReplace(range, replacement)) {
+ replaced = 1;
+ }
+ if (range !== null) {
+ this.selection.setSelectionRange(range);
+ this.renderer.scrollSelectionIntoView(range.start, range.end);
+ }
+
+ return replaced;
+ };
+ this.replaceAll = function(replacement, options) {
+ if (options) {
+ this.$search.set(options);
+ }
+
+ var ranges = this.$search.findAll(this.session);
+ var replaced = 0;
+ if (!ranges.length)
+ return replaced;
+
+ this.$blockScrolling += 1;
+
+ var selection = this.getSelectionRange();
+ this.clearSelection();
+ this.selection.moveCursorTo(0, 0);
+
+ for (var i = ranges.length - 1; i >= 0; --i) {
+ if(this.$tryReplace(ranges[i], replacement)) {
+ replaced++;
+ }
+ }
+
+ this.selection.setSelectionRange(selection);
+ this.$blockScrolling -= 1;
+
+ return replaced;
+ };
+
+ this.$tryReplace = function(range, replacement) {
+ var input = this.session.getTextRange(range);
+ replacement = this.$search.replace(input, replacement);
+ if (replacement !== null) {
+ range.end = this.session.replace(range, replacement);
+ return range;
+ } else {
+ return null;
+ }
+ };
+ this.getLastSearchOptions = function() {
+ return this.$search.getOptions();
+ };
+ this.find = function(needle, options, animate) {
+ if (!options)
+ options = {};
+
+ if (typeof needle == "string" || needle instanceof RegExp)
+ options.needle = needle;
+ else if (typeof needle == "object")
+ oop.mixin(options, needle);
+
+ var range = this.selection.getRange();
+ if (options.needle == null) {
+ needle = this.session.getTextRange(range)
+ || this.$search.$options.needle;
+ if (!needle) {
+ range = this.session.getWordRange(range.start.row, range.start.column);
+ needle = this.session.getTextRange(range);
+ }
+ this.$search.set({needle: needle});
+ }
+
+ this.$search.set(options);
+ if (!options.start)
+ this.$search.set({start: range});
+
+ var newRange = this.$search.find(this.session);
+ if (options.preventScroll)
+ return newRange;
+ if (newRange) {
+ this.revealRange(newRange, animate);
+ return newRange;
+ }
+ // clear selection if nothing is found
+ if (options.backwards)
+ range.start = range.end;
+ else
+ range.end = range.start;
+ this.selection.setRange(range);
+ };
+ this.findNext = function(options, animate) {
+ this.find({skipCurrent: true, backwards: false}, options, animate);
+ };
+ this.findPrevious = function(options, animate) {
+ this.find(options, {skipCurrent: true, backwards: true}, animate);
+ };
+
+ this.revealRange = function(range, animate) {
+ this.$blockScrolling += 1;
+ this.session.unfold(range);
+ this.selection.setSelectionRange(range);
+ this.$blockScrolling -= 1;
+
+ var scrollTop = this.renderer.scrollTop;
+ this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
+ if (animate != false)
+ this.renderer.animateScrolling(scrollTop);
+ };
+ this.undo = function() {
+ this.$blockScrolling++;
+ this.session.getUndoManager().undo();
+ this.$blockScrolling--;
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ };
+ this.redo = function() {
+ this.$blockScrolling++;
+ this.session.getUndoManager().redo();
+ this.$blockScrolling--;
+ this.renderer.scrollCursorIntoView(null, 0.5);
+ };
+ this.destroy = function() {
+ this.renderer.destroy();
+ };
+
+}).call(Editor.prototype);
+
+
+exports.Editor = Editor;
+});
+
+define('ace/mode/markdown', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/mode/javascript', 'ace/mode/xml', 'ace/mode/html', 'ace/tokenizer', 'ace/mode/markdown_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var JavaScriptMode = require("./javascript").Mode;
+var XmlMode = require("./xml").Mode;
+var HtmlMode = require("./html").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var MarkdownHighlightRules = require("./markdown_highlight_rules").MarkdownHighlightRules;
+
+var Mode = function() {
+ var highlighter = new MarkdownHighlightRules();
+
+ this.$tokenizer = new Tokenizer(highlighter.getRules());
+ this.$embeds = highlighter.getEmbeds();
+ this.createModeDelegates({
+ "js-": JavaScriptMode,
+ "xml-": XmlMode,
+ "html-": HtmlMode
+ });
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+ this.getNextLineIndent = function(state, line, tab) {
+ if (state == "listblock") {
+ var match = /^((?:.+)?)([-+*][ ]+)/.exec(line);
+ if (match) {
+ return new Array(match[1].length + 1).join(" ") + match[2];
+ } else {
+ return "";
+ }
+ } else {
+ return this.$getIndent(line);
+ }
+ };
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/text', ['require', 'exports', 'module' , 'ace/tokenizer', 'ace/mode/text_highlight_rules', 'ace/mode/behaviour', 'ace/unicode'], function(require, exports, module) {
+
+
+var Tokenizer = require("../tokenizer").Tokenizer;
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+var Behaviour = require("./behaviour").Behaviour;
+var unicode = require("../unicode");
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new TextHighlightRules().getRules());
+ this.$behaviour = new Behaviour();
+};
+
+(function() {
+
+ this.tokenRe = new RegExp("^["
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "\\$_]+", "g"
+ );
+
+ this.nonTokenRe = new RegExp("^(?:[^"
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "\\$_]|\s])+", "g"
+ );
+
+ this.getTokenizer = function() {
+ return this.$tokenizer;
+ };
+
+ this.toggleCommentLines = function(state, doc, startRow, endRow) {
+ };
+
+ this.getNextLineIndent = function(state, line, tab) {
+ return "";
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return false;
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ };
+
+ this.$getIndent = function(line) {
+ var match = line.match(/^(\s+)/);
+ if (match) {
+ return match[1];
+ }
+
+ return "";
+ };
+
+ this.createWorker = function(session) {
+ return null;
+ };
+
+ this.createModeDelegates = function (mapping) {
+ if (!this.$embeds) {
+ return;
+ }
+ this.$modes = {};
+ for (var i = 0; i < this.$embeds.length; i++) {
+ if (mapping[this.$embeds[i]]) {
+ this.$modes[this.$embeds[i]] = new mapping[this.$embeds[i]]();
+ }
+ }
+
+ var delegations = ['toggleCommentLines', 'getNextLineIndent', 'checkOutdent', 'autoOutdent', 'transformAction'];
+
+ for (var i = 0; i < delegations.length; i++) {
+ (function(scope) {
+ var functionName = delegations[i];
+ var defaultHandler = scope[functionName];
+ scope[delegations[i]] = function() {
+ return this.$delegator(functionName, arguments, defaultHandler);
+ }
+ } (this));
+ }
+ }
+
+ this.$delegator = function(method, args, defaultHandler) {
+ var state = args[0];
+
+ for (var i = 0; i < this.$embeds.length; i++) {
+ if (!this.$modes[this.$embeds[i]]) continue;
+
+ var split = state.split(this.$embeds[i]);
+ if (!split[0] && split[1]) {
+ args[0] = split[1];
+ var mode = this.$modes[this.$embeds[i]];
+ return mode[method].apply(mode, args);
+ }
+ }
+ var ret = defaultHandler.apply(this, args);
+ return defaultHandler ? ret : undefined;
+ };
+
+ this.transformAction = function(state, action, editor, session, param) {
+ if (this.$behaviour) {
+ var behaviours = this.$behaviour.getBehaviours();
+ for (var key in behaviours) {
+ if (behaviours[key][action]) {
+ var ret = behaviours[key][action].apply(this, arguments);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ }
+ }
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/tokenizer', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * class Tokenizer
+ *
+ * This class takes a set of highlighting rules, and creates a tokenizer out of them. For more information, see [the wiki on extending highlighters](https://github.com/ajaxorg/ace/wiki/Creating-or-Extending-an-Edit-Mode#wiki-extendingTheHighlighter).
+ *
+ **/
+
+/**
+ * new Tokenizer(rules, flag)
+ * - rules (Object): The highlighting rules
+ * - flag (String): Any additional regular expression flags to pass (like "i" for case insensitive)
+ *
+ * Constructs a new tokenizer based on the given rules and flags.
+ *
+ **/
+var Tokenizer = function(rules, flag) {
+ flag = flag ? "g" + flag : "g";
+ this.rules = rules;
+
+ this.regExps = {};
+ this.matchMappings = {};
+ for ( var key in this.rules) {
+ var rule = this.rules[key];
+ var state = rule;
+ var ruleRegExps = [];
+ var matchTotal = 0;
+ var mapping = this.matchMappings[key] = {};
+
+ for ( var i = 0; i < state.length; i++) {
+
+ if (state[i].regex instanceof RegExp)
+ state[i].regex = state[i].regex.toString().slice(1, -1);
+
+ // Count number of matching groups. 2 extra groups from the full match
+ // And the catch-all on the end (used to force a match);
+ var matchcount = new RegExp("(?:(" + state[i].regex + ")|(.))").exec("a").length - 2;
+
+ // Replace any backreferences and offset appropriately.
+ var adjustedregex = state[i].regex.replace(/\\([0-9]+)/g, function (match, digit) {
+ return "\\" + (parseInt(digit, 10) + matchTotal + 1);
+ });
+
+ if (matchcount > 1 && state[i].token.length !== matchcount-1)
+ throw new Error("Matching groups and length of the token array don't match in rule #" + i + " of state " + key);
+
+ mapping[matchTotal] = {
+ rule: i,
+ len: matchcount
+ };
+ matchTotal += matchcount;
+
+ ruleRegExps.push(adjustedregex);
+ }
+
+ this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", flag);
+ }
+};
+
+(function() {
+
+ /**
+ * Tokenizer.getLineTokens() -> Object
+ *
+ * Returns an object containing two properties: `tokens`, which contains all the tokens; and `state`, the current state.
+ **/
+ this.getLineTokens = function(line, startState) {
+ var currentState = startState || "start";
+ var state = this.rules[currentState];
+ var mapping = this.matchMappings[currentState];
+ var re = this.regExps[currentState];
+ re.lastIndex = 0;
+
+ var match, tokens = [];
+
+ var lastIndex = 0;
+
+ var token = {
+ type: null,
+ value: ""
+ };
+
+ while (match = re.exec(line)) {
+ var type = "text";
+ var rule = null;
+ var value = [match[0]];
+
+ for (var i = 0; i < match.length-2; i++) {
+ if (match[i + 1] === undefined)
+ continue;
+
+ rule = state[mapping[i].rule];
+
+ if (mapping[i].len > 1)
+ value = match.slice(i+2, i+1+mapping[i].len);
+
+ // compute token type
+ if (typeof rule.token == "function")
+ type = rule.token.apply(this, value);
+ else
+ type = rule.token;
+
+ if (rule.next) {
+ currentState = rule.next;
+ state = this.rules[currentState];
+ mapping = this.matchMappings[currentState];
+ lastIndex = re.lastIndex;
+
+ re = this.regExps[currentState];
+ re.lastIndex = lastIndex;
+ }
+ break;
+ }
+
+ if (value[0]) {
+ if (typeof type == "string") {
+ value = [value.join("")];
+ type = [type];
+ }
+ for (var i = 0; i < value.length; i++) {
+ if (!value[i])
+ continue;
+
+ if ((!rule || rule.merge || type[i] === "text") && token.type === type[i]) {
+ token.value += value[i];
+ } else {
+ if (token.type)
+ tokens.push(token);
+
+ token = {
+ type: type[i],
+ value: value[i],
+ column: match.index
+ };
+ }
+ }
+ }
+
+ if (lastIndex == line.length)
+ break;
+
+ lastIndex = re.lastIndex;
+ }
+
+ if (token.type)
+ tokens.push(token);
+
+ return {
+ tokens : tokens,
+ state : currentState
+ };
+ };
+
+}).call(Tokenizer.prototype);
+
+exports.Tokenizer = Tokenizer;
+});
+
+define('ace/mode/text_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/lang'], function(require, exports, module) {
+
+
+var lang = require("../lib/lang");
+
+var TextHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [{
+ token : "empty_line",
+ regex : '^$'
+ }, {
+ token : "text",
+ regex : ".+"
+ }]
+ };
+};
+
+(function() {
+
+ this.addRules = function(rules, prefix) {
+ for (var key in rules) {
+ var state = rules[key];
+ for (var i=0; i<state.length; i++) {
+ var rule = state[i];
+ if (rule.next) {
+ rule.next = prefix + rule.next;
+ }
+ }
+ this.$rules[prefix + key] = state;
+ }
+ };
+
+ this.getRules = function() {
+ return this.$rules;
+ };
+
+ this.embedRules = function (HighlightRules, prefix, escapeRules, states) {
+ var embedRules = new HighlightRules().getRules();
+ if (states) {
+ for (var i = 0; i < states.length; i++) {
+ states[i] = prefix + states[i];
+ }
+ } else {
+ states = [];
+ for (var key in embedRules) {
+ states.push(prefix + key);
+ }
+ }
+ this.addRules(embedRules, prefix);
+
+ for (var i = 0; i < states.length; i++) {
+ Array.prototype.unshift.apply(this.$rules[states[i]], lang.deepCopy(escapeRules));
+ }
+
+ if (!this.$embeds) {
+ this.$embeds = [];
+ }
+ this.$embeds.push(prefix);
+ }
+
+ this.getEmbeds = function() {
+ return this.$embeds;
+ }
+
+}).call(TextHighlightRules.prototype);
+
+exports.TextHighlightRules = TextHighlightRules;
+});
+
+define('ace/lib/lang', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+exports.stringReverse = function(string) {
+ return string.split("").reverse().join("");
+};
+
+exports.stringRepeat = function (string, count) {
+ return new Array(count + 1).join(string);
+};
+
+var trimBeginRegexp = /^\s\s*/;
+var trimEndRegexp = /\s\s*$/;
+
+exports.stringTrimLeft = function (string) {
+ return string.replace(trimBeginRegexp, '');
+};
+
+exports.stringTrimRight = function (string) {
+ return string.replace(trimEndRegexp, '');
+};
+
+exports.copyObject = function(obj) {
+ var copy = {};
+ for (var key in obj) {
+ copy[key] = obj[key];
+ }
+ return copy;
+};
+
+exports.copyArray = function(array){
+ var copy = [];
+ for (var i=0, l=array.length; i<l; i++) {
+ if (array[i] && typeof array[i] == "object")
+ copy[i] = this.copyObject( array[i] );
+ else
+ copy[i] = array[i];
+ }
+ return copy;
+};
+
+exports.deepCopy = function (obj) {
+ if (typeof obj != "object") {
+ return obj;
+ }
+
+ var copy = obj.constructor();
+ for (var key in obj) {
+ if (typeof obj[key] == "object") {
+ copy[key] = this.deepCopy(obj[key]);
+ } else {
+ copy[key] = obj[key];
+ }
+ }
+ return copy;
+};
+
+exports.arrayToMap = function(arr) {
+ var map = {};
+ for (var i=0; i<arr.length; i++) {
+ map[arr[i]] = 1;
+ }
+ return map;
+
+};
+exports.arrayRemove = function(array, value) {
+ for (var i = 0; i <= array.length; i++) {
+ if (value === array[i]) {
+ array.splice(i, 1);
+ }
+ }
+};
+
+exports.escapeRegExp = function(str) {
+ return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
+};
+
+exports.getMatchOffsets = function(string, regExp) {
+ var matches = [];
+
+ string.replace(regExp, function(str) {
+ matches.push({
+ offset: arguments[arguments.length-2],
+ length: str.length
+ });
+ });
+
+ return matches;
+};
+
+
+exports.deferredCall = function(fcn) {
+
+ var timer = null;
+ var callback = function() {
+ timer = null;
+ fcn();
+ };
+
+ var deferred = function(timeout) {
+ deferred.cancel();
+ timer = setTimeout(callback, timeout || 0);
+ return deferred;
+ };
+
+ deferred.schedule = deferred;
+
+ deferred.call = function() {
+ this.cancel();
+ fcn();
+ return deferred;
+ };
+
+ deferred.cancel = function() {
+ clearTimeout(timer);
+ timer = null;
+ return deferred;
+ };
+
+ return deferred;
+};
+
+});
+
+define('ace/mode/behaviour', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+var Behaviour = function() {
+ this.$behaviours = {};
+};
+
+(function () {
+
+ this.add = function (name, action, callback) {
+ switch (undefined) {
+ case this.$behaviours:
+ this.$behaviours = {};
+ case this.$behaviours[name]:
+ this.$behaviours[name] = {};
+ }
+ this.$behaviours[name][action] = callback;
+ }
+
+ this.addBehaviours = function (behaviours) {
+ for (var key in behaviours) {
+ for (var action in behaviours[key]) {
+ this.add(key, action, behaviours[key][action]);
+ }
+ }
+ }
+
+ this.remove = function (name) {
+ if (this.$behaviours && this.$behaviours[name]) {
+ delete this.$behaviours[name];
+ }
+ }
+
+ this.inherit = function (mode, filter) {
+ if (typeof mode === "function") {
+ var behaviours = new mode().getBehaviours(filter);
+ } else {
+ var behaviours = mode.getBehaviours(filter);
+ }
+ this.addBehaviours(behaviours);
+ }
+
+ this.getBehaviours = function (filter) {
+ if (!filter) {
+ return this.$behaviours;
+ } else {
+ var ret = {}
+ for (var i = 0; i < filter.length; i++) {
+ if (this.$behaviours[filter[i]]) {
+ ret[filter[i]] = this.$behaviours[filter[i]];
+ }
+ }
+ return ret;
+ }
+ }
+
+}).call(Behaviour.prototype);
+
+exports.Behaviour = Behaviour;
+});
+define('ace/unicode', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/*
+XRegExp Unicode plugin pack: Categories 1.0
+(c) 2010 Steven Levithan
+MIT License
+<http://xregexp.com>
+Uses the Unicode 5.2 character database
+
+This package for the XRegExp Unicode plugin enables the following Unicode categories (aka properties):
+
+L - Letter (the top-level Letter category is included in the Unicode plugin base script)
+ Ll - Lowercase letter
+ Lu - Uppercase letter
+ Lt - Titlecase letter
+ Lm - Modifier letter
+ Lo - Letter without case
+M - Mark
+ Mn - Non-spacing mark
+ Mc - Spacing combining mark
+ Me - Enclosing mark
+N - Number
+ Nd - Decimal digit
+ Nl - Letter number
+ No - Other number
+P - Punctuation
+ Pd - Dash punctuation
+ Ps - Open punctuation
+ Pe - Close punctuation
+ Pi - Initial punctuation
+ Pf - Final punctuation
+ Pc - Connector punctuation
+ Po - Other punctuation
+S - Symbol
+ Sm - Math symbol
+ Sc - Currency symbol
+ Sk - Modifier symbol
+ So - Other symbol
+Z - Separator
+ Zs - Space separator
+ Zl - Line separator
+ Zp - Paragraph separator
+C - Other
+ Cc - Control
+ Cf - Format
+ Co - Private use
+ Cs - Surrogate
+ Cn - Unassigned
+
+Example usage:
+
+ \p{N}
+ \p{Cn}
+*/
+
+
+// will be populated by addUnicodePackage
+exports.packages = {};
+
+addUnicodePackage({
+ L: "0041-005A0061-007A00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05250531-055605590561-058705D0-05EA05F0-05F20621-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280904-0939093D09500958-0961097109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A [...]
+ Ll: "0061-007A00AA00B500BA00DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF0 [...]
+ Lu: "0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC [...]
+ Lt: "01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC",
+ Lm: "02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D611D781D9B-1DBF2071207F2090-20942C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A9CFAA70AADDFF70FF9EFF9F",
+ Lo: "01BB01C0-01C3029405D0-05EA05F0-05F20621-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150904-0939093D09500958-096109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610 [...]
+ M: "0300-036F0483-04890591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DE-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0903093C093E-094E0951-0955096209630981-098309BC09BE-09C409C709C809CB-09CD09D709E209E30A01-0A030A3C0A3E-0A420A470A480A4B-0A4D0A510A700A710A750A81-0A830ABC0ABE-0AC50AC7-0AC90ACB-0ACD0AE20AE30B01-0B030B3C0B3E-0B440B470B480B4B-0B4D0B560B570B620B630B820BBE-0BC20BC6-0BC80BCA-0BCD0BD70C01-0C030C3E-0C440C46-0C [...]
+ Mn: "0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0902093C0941-0948094D0951-095509620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630 [...]
+ Mc: "0903093E-09400949-094C094E0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A [...]
+ Me: "0488048906DE20DD-20E020E2-20E4A670-A672",
+ N: "0030-003900B200B300B900BC-00BE0660-066906F0-06F907C0-07C90966-096F09E6-09EF09F4-09F90A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BF20C66-0C6F0C78-0C7E0CE6-0CEF0D66-0D750E50-0E590ED0-0ED90F20-0F331040-10491090-10991369-137C16EE-16F017E0-17E917F0-17F91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C5920702074-20792080-20892150-21822185-21892460-249B24EA-24FF2776-27932CFD30073021-30293038-303A3192-31953220-32293251-325F3280-328932B1-32BFA620-A629A6E6-A6EFA830-A835 [...]
+ Nd: "0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",
+ Nl: "16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF",
+ No: "00B200B300B900BC-00BE09F4-09F90BF0-0BF20C78-0C7E0D70-0D750F2A-0F331369-137C17F0-17F920702074-20792080-20892150-215F21892460-249B24EA-24FF2776-27932CFD3192-31953220-32293251-325F3280-328932B1-32BFA830-A835",
+ P: "0021-00230025-002A002C-002F003A003B003F0040005B-005D005F007B007D00A100AB00B700BB00BF037E0387055A-055F0589058A05BE05C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F3A-0F3D0F850FD0-0FD4104A-104F10FB1361-13681400166D166E169B169C16EB-16ED1735173617D4-17D617D8-17DA1800-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD32010-20272030-20432045-20512053-205E207D207E208D208E2329232A2768- [...]
+ Pd: "002D058A05BE140018062010-20152E172E1A301C303030A0FE31FE32FE58FE63FF0D",
+ Ps: "0028005B007B0F3A0F3C169B201A201E2045207D208D23292768276A276C276E27702772277427C527E627E827EA27EC27EE2983298529872989298B298D298F299129932995299729D829DA29FC2E222E242E262E283008300A300C300E3010301430163018301A301DFD3EFE17FE35FE37FE39FE3BFE3DFE3FFE41FE43FE47FE59FE5BFE5DFF08FF3BFF5BFF5FFF62",
+ Pe: "0029005D007D0F3B0F3D169C2046207E208E232A2769276B276D276F27712773277527C627E727E927EB27ED27EF298429862988298A298C298E2990299229942996299829D929DB29FD2E232E252E272E293009300B300D300F3011301530173019301B301E301FFD3FFE18FE36FE38FE3AFE3CFE3EFE40FE42FE44FE48FE5AFE5CFE5EFF09FF3DFF5DFF60FF63",
+ Pi: "00AB2018201B201C201F20392E022E042E092E0C2E1C2E20",
+ Pf: "00BB2019201D203A2E032E052E0A2E0D2E1D2E21",
+ Pc: "005F203F20402054FE33FE34FE4D-FE4FFF3F",
+ Po: "0021-00230025-0027002A002C002E002F003A003B003F0040005C00A100B700BF037E0387055A-055F058905C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F850FD0-0FD4104A-104F10FB1361-1368166D166E16EB-16ED1735173617D4-17D617D8-17DA1800-18051807-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD3201620172020-20272030-2038203B-203E2041-20432047-205120532055-205E2CF9-2CFC2CFE2CFF2E002E012E06-2E082E0 [...]
+ S: "0024002B003C-003E005E0060007C007E00A2-00A900AC00AE-00B100B400B600B800D700F702C2-02C502D2-02DF02E5-02EB02ED02EF-02FF03750384038503F604820606-0608060B060E060F06E906FD06FE07F609F209F309FA09FB0AF10B700BF3-0BFA0C7F0CF10CF20D790E3F0F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-139917DB194019E0-19FF1B61-1B6A1B74-1B7C1FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE20442052207A-207C208A-208C20A0-20B8210021012103-21062108210921142116-2118211E-21 [...]
+ Sm: "002B003C-003E007C007E00AC00B100D700F703F60606-060820442052207A-207C208A-208C2140-2144214B2190-2194219A219B21A021A321A621AE21CE21CF21D221D421F4-22FF2308-230B23202321237C239B-23B323DC-23E125B725C125F8-25FF266F27C0-27C427C7-27CA27CC27D0-27E527F0-27FF2900-29822999-29D729DC-29FB29FE-2AFF2B30-2B442B47-2B4CFB29FE62FE64-FE66FF0BFF1C-FF1EFF5CFF5EFFE2FFE9-FFEC",
+ Sc: "002400A2-00A5060B09F209F309FB0AF10BF90E3F17DB20A0-20B8A838FDFCFE69FF04FFE0FFE1FFE5FFE6",
+ Sk: "005E006000A800AF00B400B802C2-02C502D2-02DF02E5-02EB02ED02EF-02FF0375038403851FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE309B309CA700-A716A720A721A789A78AFF3EFF40FFE3",
+ So: "00A600A700A900AE00B000B60482060E060F06E906FD06FE07F609FA0B700BF3-0BF80BFA0C7F0CF10CF20D790F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-1399194019E0-19FF1B61-1B6A1B74-1B7C210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B214A214C214D214F2195-2199219C-219F21A121A221A421A521A7-21AD21AF-21CD21D021D121D321D5-21F32300-2307230C-231F2322-2328232B-237B237D-239A23B4-23DB23E2-23E82400-24262440-244A249C-24E92500-25B625B8-25C0 [...]
+ Z: "002000A01680180E2000-200A20282029202F205F3000",
+ Zs: "002000A01680180E2000-200A202F205F3000",
+ Zl: "2028",
+ Zp: "2029",
+ C: "0000-001F007F-009F00AD03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-0605061C061D0620065F06DD070E070F074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE4 [...]
+ Cc: "0000-001F007F-009F",
+ Cf: "00AD0600-060306DD070F17B417B5200B-200F202A-202E2060-2064206A-206FFEFFFFF9-FFFB",
+ Co: "E000-F8FF",
+ Cs: "D800-DFFF",
+ Cn: "03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-05FF06040605061C061D0620065F070E074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF00AF2-0B000B040 [...]
+});
+
+function addUnicodePackage (pack) {
+ var codePoint = /\w{4}/g;
+ for (var name in pack)
+ exports.packages[name] = pack[name].replace(codePoint, "\\u$&");
+};
+
+});
+
+define('ace/mode/javascript', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/javascript_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/worker/worker_client', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
+var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent;
+var Range = require("../range").Range;
+var WorkerClient = require("../worker/worker_client").WorkerClient;
+var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour;
+var CStyleFoldMode = require("./folding/cstyle").FoldMode;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new JavaScriptHighlightRules().getRules());
+ this.$outdent = new MatchingBraceOutdent();
+ this.$behaviour = new CstyleBehaviour();
+ this.foldingRules = new CStyleFoldMode();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+
+ this.toggleCommentLines = function(state, doc, startRow, endRow) {
+ var outdent = true;
+ var re = /^(\s*)\/\//;
+
+ for (var i=startRow; i<= endRow; i++) {
+ if (!re.test(doc.getLine(i))) {
+ outdent = false;
+ break;
+ }
+ }
+
+ if (outdent) {
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i=startRow; i<= endRow; i++)
+ {
+ var line = doc.getLine(i);
+ var m = line.match(re);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = m[0].length;
+ doc.replace(deleteRange, m[1]);
+ }
+ }
+ else {
+ doc.indentRows(startRow, endRow, "//");
+ }
+ };
+
+ this.getNextLineIndent = function(state, line, tab) {
+ var indent = this.$getIndent(line);
+
+ var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
+ var tokens = tokenizedLine.tokens;
+ var endState = tokenizedLine.state;
+
+ if (tokens.length && tokens[tokens.length-1].type == "comment") {
+ return indent;
+ }
+
+ if (state == "start" || state == "regex_allowed") {
+ var match = line.match(/^.*(?:\bcase\b.*\:|[\{\(\[])\s*$/);
+ if (match) {
+ indent += tab;
+ }
+ } else if (state == "doc-start") {
+ if (endState == "start" || state == "regex_allowed") {
+ return "";
+ }
+ var match = line.match(/^\s*(\/?)\*/);
+ if (match) {
+ if (match[1]) {
+ indent += " ";
+ }
+ indent += "* ";
+ }
+ }
+
+ return indent;
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ this.$outdent.autoOutdent(doc, row);
+ };
+
+ this.createWorker = function(session) {
+ var worker = new WorkerClient(["ace"], "ace/mode/javascript_worker", "JavaScriptWorker");
+ worker.attachToDocument(session.getDocument());
+
+ worker.on("jslint", function(results) {
+ var errors = [];
+ for (var i=0; i<results.data.length; i++) {
+ var error = results.data[i];
+ if (error)
+ errors.push({
+ row: error.line-1,
+ column: error.character-1,
+ text: error.reason,
+ type: "warning",
+ lint: error
+ });
+ }
+ session.setAnnotations(errors);
+ });
+
+ worker.on("narcissus", function(e) {
+ session.setAnnotations([e.data]);
+ });
+
+ worker.on("terminate", function() {
+ session.clearAnnotations();
+ });
+
+ return worker;
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/javascript_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/unicode', 'ace/mode/doc_comment_highlight_rules', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var lang = require("../lib/lang");
+var unicode = require("../unicode");
+var DocCommentHighlightRules = require("./doc_comment_highlight_rules").DocCommentHighlightRules;
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var JavaScriptHighlightRules = function() {
+
+ // see: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects
+ var globals = lang.arrayToMap(
+ // Constructors
+ ("Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|" +
+ // E4X
+ "Namespace|QName|XML|XMLList|" +
+ "ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|" +
+ "Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|" +
+ // Errors
+ "Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|" +
+ "SyntaxError|TypeError|URIError|" +
+ // Non-constructor functions
+ "decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|" +
+ "isNaN|parseFloat|parseInt|" +
+ // Other
+ "JSON|Math|" +
+ // Pseudo
+ "this|arguments|prototype|window|document"
+ ).split("|")
+ );
+
+ var keywords = lang.arrayToMap(
+ ("break|case|catch|continue|default|delete|do|else|finally|for|function|" +
+ "if|in|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|" +
+ "const|yield|import|get|set").split("|")
+ );
+
+ // keywords which can be followed by regular expressions
+ var kwBeforeRe = "case|do|else|finally|in|instanceof|return|throw|try|typeof|yield";
+
+ var deprecated = lang.arrayToMap(
+ ("__parent__|__count__|escape|unescape|with|__proto__").split("|")
+ );
+
+ var definitions = lang.arrayToMap(("const|let|var|function").split("|"));
+
+ var buildinConstants = lang.arrayToMap(
+ ("null|Infinity|NaN|undefined").split("|")
+ );
+
+ var futureReserved = lang.arrayToMap(
+ ("class|enum|extends|super|export|implements|private|" +
+ "public|interface|package|protected|static").split("|")
+ );
+
+ // TODO: Unicode escape sequences
+ var identifierRe = "[" + unicode.packages.L + "\\$_]["
+ + unicode.packages.L
+ + unicode.packages.Mn + unicode.packages.Mc
+ + unicode.packages.Nd
+ + unicode.packages.Pc + "\\$_]*\\b";
+
+ var escapedRe = "\\\\(?:x[0-9a-fA-F]{2}|" + // hex
+ "u[0-9a-fA-F]{4}|" + // unicode
+ "[0-2][0-7]{0,2}|" + // oct
+ "3[0-6][0-7]?|" + // oct
+ "37[0-7]?|" + // oct
+ "[4-7][0-7]?|" + //oct
+ ".)";
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ token : "comment",
+ regex : /\/\/.*$/
+ },
+ DocCommentHighlightRules.getStartRule("doc-start"),
+ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : /\/\*/,
+ next : "comment"
+ }, {
+ token : "string",
+ regex : "'(?=.)",
+ next : "qstring"
+ }, {
+ token : "string",
+ regex : '"(?=.)',
+ next : "qqstring"
+ }, {
+ token : "constant.numeric", // hex
+ regex : /0[xX][0-9a-fA-F]+\b/
+ }, {
+ token : "constant.numeric", // float
+ regex : /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
+ }, { // match stuff like: Sound.prototype.play = function() { }
+ token : [
+ "storage.type",
+ "punctuation.operator",
+ "support.function",
+ "punctuation.operator",
+ "entity.name.function",
+ "text",
+ "keyword.operator",
+ "text",
+ "storage.type",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // match stuff like: Sound.prototype.play = myfunc
+ token : [
+ "storage.type",
+ "punctuation.operator",
+ "support.function",
+ "punctuation.operator",
+ "entity.name.function",
+ "text",
+ "keyword.operator",
+ "text"
+ ],
+ regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)",
+ next: "function_arguments"
+ }, { // match stuff like: Sound.play = function() { }
+ token : [
+ "storage.type",
+ "punctuation.operator",
+ "entity.name.function",
+ "text",
+ "keyword.operator",
+ "text",
+ "storage.type",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // match stuff like: play = function() { }
+ token : [
+ "entity.name.function",
+ "text",
+ "keyword.operator",
+ "text",
+ "storage.type",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // match stuff like: Sound.play = function play() { }
+ token : [
+ "storage.type",
+ "punctuation.operator",
+ "entity.name.function",
+ "text",
+ "keyword.operator",
+ "text",
+ "storage.type",
+ "text",
+ "entity.name.function",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // match regular function like: function myFunc(arg) { }
+ token : [
+ "storage.type",
+ "text",
+ "entity.name.function",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(function)(\\s+)(" + identifierRe + ")(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // match stuff like: foobar: function() { }
+ token : [
+ "entity.name.function",
+ "text",
+ "punctuation.operator",
+ "text",
+ "storage.type",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(" + identifierRe + ")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",
+ next: "function_arguments"
+ }, { // Attempt to match : function() { } (this is for issues with 'foo': function() { })
+ token : [
+ "text",
+ "text",
+ "storage.type",
+ "text",
+ "paren.lparen"
+ ],
+ regex : "(:)(\\s*)(function)(\\s*)(\\()",
+ next: "function_arguments"
+ }, {
+ token : "constant.language.boolean",
+ regex : /(?:true|false)\b/
+ }, {
+ token : "keyword",
+ regex : "(?:" + kwBeforeRe + ")\\b",
+ next : "regex_allowed"
+ }, {
+ token : ["punctuation.operator", "support.function"],
+ regex : /(\.)(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:opzzzz|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|nav [...]
+ }, {
+ token : ["punctuation.operator", "support.function.dom"],
+ regex : /(\.)(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)| [...]
+ }, {
+ token : ["punctuation.operator", "support.constant"],
+ regex : /(\.)(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypt [...]
+ }, {
+ token : ["storage.type", "punctuation.operator", "support.function.firebug"],
+ regex : /(console)(\.)(warn|info|log|error|time|timeEnd|assert)\b/
+ }, {
+ token : function(value) {
+ if (globals.hasOwnProperty(value))
+ return "variable.language";
+ else if (deprecated.hasOwnProperty(value))
+ return "invalid.deprecated";
+ else if (definitions.hasOwnProperty(value))
+ return "storage.type";
+ else if (keywords.hasOwnProperty(value))
+ return "keyword";
+ else if (buildinConstants.hasOwnProperty(value))
+ return "constant.language";
+ else if (futureReserved.hasOwnProperty(value))
+ return "invalid.illegal";
+ else if (value == "debugger")
+ return "invalid.deprecated";
+ else
+ return "identifier";
+ },
+ regex : identifierRe
+ }, {
+ token : "keyword.operator",
+ regex : /!|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|\*=|%=|\+=|\-=|&=|\^=|\b(?:in|instanceof|new|delete|typeof|void)/,
+ next : "regex_allowed"
+ }, {
+ token : "punctuation.operator",
+ regex : /\?|\:|\,|\;|\./,
+ next : "regex_allowed"
+ }, {
+ token : "paren.lparen",
+ regex : /[\[({]/,
+ next : "regex_allowed"
+ }, {
+ token : "paren.rparen",
+ regex : /[\])}]/
+ }, {
+ token : "keyword.operator",
+ regex : /\/=?/,
+ next : "regex_allowed"
+ }, {
+ token: "comment",
+ regex: /^#!.*$/
+ }, {
+ token : "text",
+ regex : /\s+/
+ }
+ ],
+ // regular expressions are only allowed after certain tokens. This
+ // makes sure we don't mix up regexps with the divison operator
+ "regex_allowed": [
+ DocCommentHighlightRules.getStartRule("doc-start"),
+ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "comment_regex_allowed"
+ }, {
+ token : "comment",
+ regex : "\\/\\/.*$"
+ }, {
+ token: "string.regexp",
+ regex: "\\/",
+ next: "regex",
+ merge: true
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }, {
+ // immediately return to the start mode without matching
+ // anything
+ token: "empty",
+ regex: "",
+ next: "start"
+ }
+ ],
+ "regex": [
+ {
+ token: "regexp.keyword.operator",
+ regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
+ }, {
+ // flag
+ token: "string.regexp",
+ regex: "/\\w*",
+ next: "start",
+ merge: true
+ }, {
+ token: "string.regexp",
+ regex: "[^\\\\/\\[]+",
+ merge: true
+ }, {
+ token: "string.regexp.charachterclass",
+ regex: "\\[",
+ next: "regex_character_class",
+ merge: true
+ }, {
+ token: "empty",
+ regex: "",
+ next: "start"
+ }
+ ],
+ "regex_character_class": [
+ {
+ token: "regexp.keyword.operator",
+ regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
+ }, {
+ token: "string.regexp.charachterclass",
+ regex: "]",
+ next: "regex",
+ merge: true
+ }, {
+ token: "string.regexp.charachterclass",
+ regex: "[^\\\\\\]]+",
+ merge: true
+ }, {
+ token: "empty",
+ regex: "",
+ next: "start"
+ }
+ ],
+ "function_arguments": [
+ {
+ token: "variable.parameter",
+ regex: identifierRe
+ }, {
+ token: "punctuation.operator",
+ regex: "[, ]+",
+ merge: true
+ }, {
+ token: "punctuation.operator",
+ regex: "$",
+ merge: true
+ }, {
+ token: "empty",
+ regex: "",
+ next: "start"
+ }
+ ],
+ "comment_regex_allowed" : [
+ {
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ merge : true,
+ next : "regex_allowed"
+ }, {
+ token : "comment", // comment spanning whole line
+ merge : true,
+ regex : ".+"
+ }
+ ],
+ "comment" : [
+ {
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ merge : true,
+ next : "start"
+ }, {
+ token : "comment", // comment spanning whole line
+ merge : true,
+ regex : ".+"
+ }
+ ],
+ "qqstring" : [
+ {
+ token : "constant.language.escape",
+ regex : escapedRe
+ }, {
+ token : "string",
+ regex : '[^"\\\\]+',
+ merge : true
+ }, {
+ token : "string",
+ regex : "\\\\$",
+ next : "qqstring",
+ merge : true
+ }, {
+ token : "string",
+ regex : '"|$',
+ next : "start",
+ merge : true
+ }
+ ],
+ "qstring" : [
+ {
+ token : "constant.language.escape",
+ regex : escapedRe
+ }, {
+ token : "string",
+ regex : "[^'\\\\]+",
+ merge : true
+ }, {
+ token : "string",
+ regex : "\\\\$",
+ next : "qstring",
+ merge : true
+ }, {
+ token : "string",
+ regex : "'|$",
+ next : "start",
+ merge : true
+ }
+ ]
+ };
+
+ this.embedRules(DocCommentHighlightRules, "doc-",
+ [ DocCommentHighlightRules.getEndRule("start") ]);
+};
+
+oop.inherits(JavaScriptHighlightRules, TextHighlightRules);
+
+exports.JavaScriptHighlightRules = JavaScriptHighlightRules;
+});
+
+define('ace/mode/doc_comment_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var DocCommentHighlightRules = function() {
+
+ this.$rules = {
+ "start" : [ {
+ token : "comment.doc.tag",
+ regex : "@[\\w\\d_]+" // TODO: fix email addresses
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "\\s+"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "TODO"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "[^@\\*]+"
+ }, {
+ token : "comment.doc",
+ merge : true,
+ regex : "."
+ }]
+ };
+};
+
+oop.inherits(DocCommentHighlightRules, TextHighlightRules);
+
+DocCommentHighlightRules.getStartRule = function(start) {
+ return {
+ token : "comment.doc", // doc comment
+ merge : true,
+ regex : "\\/\\*(?=\\*)",
+ next : start
+ };
+};
+
+DocCommentHighlightRules.getEndRule = function (start) {
+ return {
+ token : "comment.doc", // closing comment
+ merge : true,
+ regex : "\\*\\/",
+ next : start
+ };
+};
+
+
+exports.DocCommentHighlightRules = DocCommentHighlightRules;
+
+});
+
+define('ace/mode/matching_brace_outdent', ['require', 'exports', 'module' , 'ace/range'], function(require, exports, module) {
+
+
+var Range = require("../range").Range;
+
+var MatchingBraceOutdent = function() {};
+
+(function() {
+
+ this.checkOutdent = function(line, input) {
+ if (! /^\s+$/.test(line))
+ return false;
+
+ return /^\s*\}/.test(input);
+ };
+
+ this.autoOutdent = function(doc, row) {
+ var line = doc.getLine(row);
+ var match = line.match(/^(\s*\})/);
+
+ if (!match) return 0;
+
+ var column = match[1].length;
+ var openBracePos = doc.findMatchingBracket({row: row, column: column});
+
+ if (!openBracePos || openBracePos.row == row) return 0;
+
+ var indent = this.$getIndent(doc.getLine(openBracePos.row));
+ doc.replace(new Range(row, 0, row, column-1), indent);
+ };
+
+ this.$getIndent = function(line) {
+ var match = line.match(/^(\s+)/);
+ if (match) {
+ return match[1];
+ }
+
+ return "";
+ };
+
+}).call(MatchingBraceOutdent.prototype);
+
+exports.MatchingBraceOutdent = MatchingBraceOutdent;
+});
+
+define('ace/range', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * class Range
+ *
+ * This object is used in various places to indicate a region within the editor. To better visualize how this works, imagine a rectangle. Each quadrant of the rectangle is analogus to a range, as ranges contain a starting row and starting column, and an ending row, and ending column.
+ *
+ **/
+
+/**
+ * new Range(startRow, startColumn, endRow, endColumn)
+ * - startRow (Number): The starting row
+ * - startColumn (Number): The starting column
+ * - endRow (Number): The ending row
+ * - endColumn (Number): The ending column
+ *
+ * Creates a new `Range` object with the given starting and ending row and column points.
+ *
+ **/
+var Range = function(startRow, startColumn, endRow, endColumn) {
+ this.start = {
+ row: startRow,
+ column: startColumn
+ };
+
+ this.end = {
+ row: endRow,
+ column: endColumn
+ };
+};
+
+(function() {
+ /**
+ * Range.isEqual(range) -> Boolean
+ * - range (Range): A range to check against
+ *
+ * Returns `true` if and only if the starting row and column, and ending tow and column, are equivalent to those given by `range`.
+ *
+ **/
+ this.isEqual = function(range) {
+ return this.start.row == range.start.row &&
+ this.end.row == range.end.row &&
+ this.start.column == range.start.column &&
+ this.end.column == range.end.column
+ };
+ this.toString = function() {
+ return ("Range: [" + this.start.row + "/" + this.start.column +
+ "] -> [" + this.end.row + "/" + this.end.column + "]");
+ };
+
+ this.contains = function(row, column) {
+ return this.compare(row, column) == 0;
+ };
+ this.compareRange = function(range) {
+ var cmp,
+ end = range.end,
+ start = range.start;
+
+ cmp = this.compare(end.row, end.column);
+ if (cmp == 1) {
+ cmp = this.compare(start.row, start.column);
+ if (cmp == 1) {
+ return 2;
+ } else if (cmp == 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (cmp == -1) {
+ return -2;
+ } else {
+ cmp = this.compare(start.row, start.column);
+ if (cmp == -1) {
+ return -1;
+ } else if (cmp == 1) {
+ return 42;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /** related to: Range.compare
+ * Range.comparePoint(p) -> Number
+ * - p (Range): A point to compare with
+ * + (Number): This method returns one of the following numbers:<br/>
+ * * `0` if the two points are exactly equal<br/>
+ * * `-1` if `p.row` is less then the calling range<br/>
+ * * `1` if `p.row` is greater than the calling range<br/>
+ * <br/>
+ * If the starting row of the calling range is equal to `p.row`, and:<br/>
+ * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
+ * * Otherwise, it returns -1<br/>
+ *<br/>
+ * If the ending row of the calling range is equal to `p.row`, and:<br/>
+ * * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/>
+ * * Otherwise, it returns 1<br/>
+ *
+ * Checks the row and column points of `p` with the row and column points of the calling range.
+ *
+ *
+ *
+ **/
+ this.comparePoint = function(p) {
+ return this.compare(p.row, p.column);
+ }
+
+ /** related to: Range.comparePoint
+ * Range.containsRange(range) -> Boolean
+ * - range (Range): A range to compare with
+ *
+ * Checks the start and end points of `range` and compares them to the calling range. Returns `true` if the `range` is contained within the caller's range.
+ *
+ **/
+ this.containsRange = function(range) {
+ return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0;
+ }
+
+ /**
+ * Range.intersects(range) -> Boolean
+ * - range (Range): A range to compare with
+ *
+ * Returns `true` if passed in `range` intersects with the one calling this method.
+ *
+ **/
+ this.intersects = function(range) {
+ var cmp = this.compareRange(range);
+ return (cmp == -1 || cmp == 0 || cmp == 1);
+ }
+
+ /**
+ * Range.isEnd(row, column) -> Boolean
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ *
+ * Returns `true` if the caller's ending row point is the same as `row`, and if the caller's ending column is the same as `column`.
+ *
+ **/
+ this.isEnd = function(row, column) {
+ return this.end.row == row && this.end.column == column;
+ }
+
+ /**
+ * Range.isStart(row, column) -> Boolean
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ *
+ * Returns `true` if the caller's starting row point is the same as `row`, and if the caller's starting column is the same as `column`.
+ *
+ **/
+ this.isStart = function(row, column) {
+ return this.start.row == row && this.start.column == column;
+ }
+
+ /**
+ * Range.setStart(row, column)
+ * - row (Number): A row point to set
+ * - column (Number): A column point to set
+ *
+ * Sets the starting row and column for the range.
+ *
+ **/
+ this.setStart = function(row, column) {
+ if (typeof row == "object") {
+ this.start.column = row.column;
+ this.start.row = row.row;
+ } else {
+ this.start.row = row;
+ this.start.column = column;
+ }
+ }
+
+ /**
+ * Range.setEnd(row, column)
+ * - row (Number): A row point to set
+ * - column (Number): A column point to set
+ *
+ * Sets the starting row and column for the range.
+ *
+ **/
+ this.setEnd = function(row, column) {
+ if (typeof row == "object") {
+ this.end.column = row.column;
+ this.end.row = row.row;
+ } else {
+ this.end.row = row;
+ this.end.column = column;
+ }
+ }
+
+ /** related to: Range.compare
+ * Range.inside(row, column) -> Boolean
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ *
+ * Returns `true` if the `row` and `column` are within the given range.
+ *
+ **/
+ this.inside = function(row, column) {
+ if (this.compare(row, column) == 0) {
+ if (this.isEnd(row, column) || this.isStart(row, column)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** related to: Range.compare
+ * Range.insideStart(row, column) -> Boolean
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ *
+ * Returns `true` if the `row` and `column` are within the given range's starting points.
+ *
+ **/
+ this.insideStart = function(row, column) {
+ if (this.compare(row, column) == 0) {
+ if (this.isEnd(row, column)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** related to: Range.compare
+ * Range.insideEnd(row, column) -> Boolean
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ *
+ * Returns `true` if the `row` and `column` are within the given range's ending points.
+ *
+ **/
+ this.insideEnd = function(row, column) {
+ if (this.compare(row, column) == 0) {
+ if (this.isStart(row, column)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Range.compare(row, column) -> Number
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ * + (Number): This method returns one of the following numbers:<br/>
+ * * `0` if the two points are exactly equal <br/>
+ * * `-1` if `p.row` is less then the calling range <br/>
+ * * `1` if `p.row` is greater than the calling range <br/>
+ * <br/>
+ * If the starting row of the calling range is equal to `p.row`, and: <br/>
+ * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
+ * * Otherwise, it returns -1<br/>
+ * <br/>
+ * If the ending row of the calling range is equal to `p.row`, and: <br/>
+ * * `p.column` is less than or equal to the calling range's ending column, this returns `0` <br/>
+ * * Otherwise, it returns 1
+ *
+ * Checks the row and column points with the row and column points of the calling range.
+ *
+ *
+ **/
+ this.compare = function(row, column) {
+ if (!this.isMultiLine()) {
+ if (row === this.start.row) {
+ return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
+ };
+ }
+
+ if (row < this.start.row)
+ return -1;
+
+ if (row > this.end.row)
+ return 1;
+
+ if (this.start.row === row)
+ return column >= this.start.column ? 0 : -1;
+
+ if (this.end.row === row)
+ return column <= this.end.column ? 0 : 1;
+
+ return 0;
+ };
+ this.compareStart = function(row, column) {
+ if (this.start.row == row && this.start.column == column) {
+ return -1;
+ } else {
+ return this.compare(row, column);
+ }
+ }
+
+ /**
+ * Range.compareEnd(row, column) -> Number
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ * + (Number): This method returns one of the following numbers:<br/>
+ * * `0` if the two points are exactly equal<br/>
+ * * `-1` if `p.row` is less then the calling range<br/>
+ * * `1` if `p.row` is greater than the calling range, or if `isEnd` is `true.<br/>
+ * <br/>
+ * If the starting row of the calling range is equal to `p.row`, and:<br/>
+ * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
+ * * Otherwise, it returns -1<br/>
+ *<br/>
+ * If the ending row of the calling range is equal to `p.row`, and:<br/>
+ * * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/>
+ * * Otherwise, it returns 1
+ *
+ * Checks the row and column points with the row and column points of the calling range.
+ *
+ *
+ **/
+ this.compareEnd = function(row, column) {
+ if (this.end.row == row && this.end.column == column) {
+ return 1;
+ } else {
+ return this.compare(row, column);
+ }
+ }
+
+ /**
+ * Range.compareInside(row, column) -> Number
+ * - row (Number): A row point to compare with
+ * - column (Number): A column point to compare with
+ * + (Number): This method returns one of the following numbers:<br/>
+ * * `1` if the ending row of the calling range is equal to `row`, and the ending column of the calling range is equal to `column`<br/>
+ * * `-1` if the starting row of the calling range is equal to `row`, and the starting column of the calling range is equal to `column`<br/>
+ * <br/>
+ * Otherwise, it returns the value after calling [[Range.compare `compare()`]].
+ *
+ * Checks the row and column points with the row and column points of the calling range.
+ *
+ *
+ *
+ **/
+ this.compareInside = function(row, column) {
+ if (this.end.row == row && this.end.column == column) {
+ return 1;
+ } else if (this.start.row == row && this.start.column == column) {
+ return -1;
+ } else {
+ return this.compare(row, column);
+ }
+ }
+
+ /**
+ * Range.clipRows(firstRow, lastRow) -> Range
+ * - firstRow (Number): The starting row
+ * - lastRow (Number): The ending row
+ *
+ * Returns the part of the current `Range` that occurs within the boundaries of `firstRow` and `lastRow` as a new `Range` object.
+ *
+ **/
+ this.clipRows = function(firstRow, lastRow) {
+ if (this.end.row > lastRow) {
+ var end = {
+ row: lastRow+1,
+ column: 0
+ };
+ }
+
+ if (this.start.row > lastRow) {
+ var start = {
+ row: lastRow+1,
+ column: 0
+ };
+ }
+
+ if (this.start.row < firstRow) {
+ var start = {
+ row: firstRow,
+ column: 0
+ };
+ }
+
+ if (this.end.row < firstRow) {
+ var end = {
+ row: firstRow,
+ column: 0
+ };
+ }
+ return Range.fromPoints(start || this.start, end || this.end);
+ };
+ this.extend = function(row, column) {
+ var cmp = this.compare(row, column);
+
+ if (cmp == 0)
+ return this;
+ else if (cmp == -1)
+ var start = {row: row, column: column};
+ else
+ var end = {row: row, column: column};
+
+ return Range.fromPoints(start || this.start, end || this.end);
+ };
+
+ this.isEmpty = function() {
+ return (this.start.row == this.end.row && this.start.column == this.end.column);
+ };
+ this.isMultiLine = function() {
+ return (this.start.row !== this.end.row);
+ };
+ this.clone = function() {
+ return Range.fromPoints(this.start, this.end);
+ };
+ this.collapseRows = function() {
+ if (this.end.column == 0)
+ return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0)
+ else
+ return new Range(this.start.row, 0, this.end.row, 0)
+ };
+ this.toScreenRange = function(session) {
+ var screenPosStart =
+ session.documentToScreenPosition(this.start);
+ var screenPosEnd =
+ session.documentToScreenPosition(this.end);
+
+ return new Range(
+ screenPosStart.row, screenPosStart.column,
+ screenPosEnd.row, screenPosEnd.column
+ );
+ };
+
+}).call(Range.prototype);
+Range.fromPoints = function(start, end) {
+ return new Range(start.row, start.column, end.row, end.column);
+};
+
+exports.Range = Range;
+});
+
+define('ace/worker/worker_client', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/config'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var EventEmitter = require("../lib/event_emitter").EventEmitter;
+var config = require("../config");
+
+var WorkerClient = function(topLevelNamespaces, mod, classname) {
+
+ this.changeListener = this.changeListener.bind(this);
+
+ if (config.get("packaged")) {
+ this.$worker = new Worker(config.moduleUrl(mod, "worker"));
+ }
+ else {
+ var workerUrl;
+ if (typeof require.supports !== "undefined" && require.supports.indexOf("ucjs2-pinf-0") >= 0) {
+ // We are running in the sourcemint loader.
+ workerUrl = require.nameToUrl("ace/worker/worker_sourcemint");
+ } else {
+ // We are running in RequireJS.
+ // nameToUrl is renamed to toUrl in requirejs 2
+ if (require.nameToUrl && !require.toUrl)
+ require.toUrl = require.nameToUrl;
+ workerUrl = this.$normalizePath(require.toUrl("ace/worker/worker", null, "_"));
+ }
+ this.$worker = new Worker(workerUrl);
+
+ var tlns = {};
+ for (var i=0; i<topLevelNamespaces.length; i++) {
+ var ns = topLevelNamespaces[i];
+ var path = this.$normalizePath(require.toUrl(ns, null, "_").replace(/.js(\?.*)?$/, ""));
+
+ tlns[ns] = path;
+ }
+ }
+
+ this.$worker.postMessage({
+ init : true,
+ tlns: tlns,
+ module: mod,
+ classname: classname
+ });
+
+ this.callbackId = 1;
+ this.callbacks = {};
+
+ var _self = this;
+ this.$worker.onerror = function(e) {
+ window.console && console.log && console.log(e);
+ throw e;
+ };
+ this.$worker.onmessage = function(e) {
+ var msg = e.data;
+ switch(msg.type) {
+ case "log":
+ window.console && console.log && console.log(msg.data);
+ break;
+
+ case "event":
+ _self._emit(msg.name, {data: msg.data});
+ break;
+
+ case "call":
+ var callback = _self.callbacks[msg.id];
+ if (callback) {
+ callback(msg.data);
+ delete _self.callbacks[msg.id];
+ }
+ break;
+ }
+ };
+};
+
+(function(){
+
+ oop.implement(this, EventEmitter);
+
+ this.$normalizePath = function(path) {
+ if (!location.host) // needed for file:// protocol
+ return path;
+ path = path.replace(/^[a-z]+:\/\/[^\/]+/, ""); // Remove domain name and rebuild it
+ path = location.protocol + "//" + location.host
+ // paths starting with a slash are relative to the root (host)
+ + (path.charAt(0) == "/" ? "" : location.pathname.replace(/\/[^\/]*$/, ""))
+ + "/" + path.replace(/^[\/]+/, "");
+ return path;
+ };
+
+ this.terminate = function() {
+ this._emit("terminate", {});
+ this.$worker.terminate();
+ this.$worker = null;
+ this.$doc.removeEventListener("change", this.changeListener);
+ this.$doc = null;
+ };
+
+ this.send = function(cmd, args) {
+ this.$worker.postMessage({command: cmd, args: args});
+ };
+
+ this.call = function(cmd, args, callback) {
+ if (callback) {
+ var id = this.callbackId++;
+ this.callbacks[id] = callback;
+ args.push(id);
+ }
+ this.send(cmd, args);
+ };
+
+ this.emit = function(event, data) {
+ try {
+ // firefox refuses to clone objects which have function properties
+ // TODO: cleanup event
+ this.$worker.postMessage({event: event, data: {data: data.data}});
+ }
+ catch(ex) {}
+ };
+
+ this.attachToDocument = function(doc) {
+ if(this.$doc)
+ this.terminate();
+
+ this.$doc = doc;
+ this.call("setValue", [doc.getValue()]);
+ doc.on("change", this.changeListener);
+ };
+
+ this.changeListener = function(e) {
+ e.range = {
+ start: e.data.range.start,
+ end: e.data.range.end
+ };
+ this.emit("change", e);
+ };
+
+}).call(WorkerClient.prototype);
+
+exports.WorkerClient = WorkerClient;
+
+});
+
+define('ace/lib/event_emitter', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+var EventEmitter = {};
+
+EventEmitter._emit =
+EventEmitter._dispatchEvent = function(eventName, e) {
+ this._eventRegistry = this._eventRegistry || {};
+ this._defaultHandlers = this._defaultHandlers || {};
+
+ var listeners = this._eventRegistry[eventName] || [];
+ var defaultHandler = this._defaultHandlers[eventName];
+ if (!listeners.length && !defaultHandler)
+ return;
+
+ e = e || {};
+ if (!e.type)
+ e.type = eventName;
+
+ if (!e.stopPropagation) {
+ e.stopPropagation = function() {
+ this.propagationStopped = true;
+ };
+ }
+
+ if (!e.preventDefault) {
+ e.preventDefault = function() {
+ this.defaultPrevented = true;
+ };
+ }
+
+ for (var i=0; i<listeners.length; i++) {
+ listeners[i](e);
+ if (e.propagationStopped)
+ break;
+ }
+
+ if (defaultHandler && !e.defaultPrevented)
+ return defaultHandler(e);
+};
+
+EventEmitter.setDefaultHandler = function(eventName, callback) {
+ this._defaultHandlers = this._defaultHandlers || {};
+
+ if (this._defaultHandlers[eventName])
+ throw new Error("The default handler for '" + eventName + "' is already set");
+
+ this._defaultHandlers[eventName] = callback;
+};
+
+EventEmitter.on =
+EventEmitter.addEventListener = function(eventName, callback) {
+ this._eventRegistry = this._eventRegistry || {};
+
+ var listeners = this._eventRegistry[eventName];
+ if (!listeners)
+ listeners = this._eventRegistry[eventName] = [];
+
+ if (listeners.indexOf(callback) == -1)
+ listeners.push(callback);
+};
+
+EventEmitter.removeListener =
+EventEmitter.removeEventListener = function(eventName, callback) {
+ this._eventRegistry = this._eventRegistry || {};
+
+ var listeners = this._eventRegistry[eventName];
+ if (!listeners)
+ return;
+
+ var index = listeners.indexOf(callback);
+ if (index !== -1)
+ listeners.splice(index, 1);
+};
+
+EventEmitter.removeAllListeners = function(eventName) {
+ if (this._eventRegistry) this._eventRegistry[eventName] = [];
+};
+
+exports.EventEmitter = EventEmitter;
+
+});
+
+define('ace/config', ['require', 'exports', 'module' , 'ace/lib/lang'], function(require, exports, module) {
+"no use strict";
+
+var lang = require("./lib/lang");
+
+var global = (function() {
+ return this;
+})();
+
+var options = {
+ packaged: false,
+ workerPath: "",
+ modePath: "",
+ themePath: "",
+ suffix: ".js",
+ $moduleUrls: {}
+};
+
+exports.get = function(key) {
+ if (!options.hasOwnProperty(key))
+ throw new Error("Unknown config key: " + key);
+
+ return options[key];
+};
+
+exports.set = function(key, value) {
+ if (!options.hasOwnProperty(key))
+ throw new Error("Unknown config key: " + key);
+
+ options[key] = value;
+};
+
+exports.all = function() {
+ return lang.copyObject(options);
+};
+
+exports.moduleUrl = function(name, component) {
+ if (options.$moduleUrls[name])
+ return options.$moduleUrls[name];
+
+ var parts = name.split("/");
+ component = component || parts[parts.length - 2] || "";
+ var base = parts[parts.length - 1].replace(component, "").replace(/(^[\-_])|([\-_]$)/, "");
+
+ if (!base && parts.length > 1)
+ base = parts[parts.length - 2];
+ return this.get(component + "Path") + "/" + component + "-" + base + this.get("suffix");
+};
+
+exports.setModuleUrl = function(name, subst) {
+ return options.$moduleUrls[name] = subst;
+};
+
+exports.init = function() {
+ options.packaged = require.packaged || module.packaged || (global.define && define.packaged);
+
+ if (!global.document)
+ return "";
+
+ var scriptOptions = {};
+ var scriptUrl = "";
+
+ var scripts = document.getElementsByTagName("script");
+ for (var i=0; i<scripts.length; i++) {
+ var script = scripts[i];
+
+ var src = script.src || script.getAttribute("src");
+ if (!src) {
+ continue;
+ }
+
+ var attributes = script.attributes;
+ for (var j=0, l=attributes.length; j < l; j++) {
+ var attr = attributes[j];
+ if (attr.name.indexOf("data-ace-") === 0) {
+ scriptOptions[deHyphenate(attr.name.replace(/^data-ace-/, ""))] = attr.value;
+ }
+ }
+
+ var m = src.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/);
+ if (m)
+ scriptUrl = m[1];
+ }
+
+ if (scriptUrl) {
+ scriptOptions.base = scriptOptions.base || scriptUrl;
+ scriptOptions.packaged = true;
+ }
+
+ scriptOptions.workerPath = scriptOptions.workerPath || scriptOptions.base;
+ scriptOptions.modePath = scriptOptions.modePath || scriptOptions.base;
+ scriptOptions.themePath = scriptOptions.themePath || scriptOptions.base;
+ delete scriptOptions.base;
+
+ for (var key in scriptOptions)
+ if (typeof scriptOptions[key] !== "undefined")
+ exports.set(key, scriptOptions[key]);
+};
+
+function deHyphenate(str) {
+ return str.replace(/-(.)/g, function(m, m1) { return m1.toUpperCase(); });
+}
+
+});
+
+define('ace/mode/behaviour/cstyle', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/behaviour'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var Behaviour = require("../behaviour").Behaviour;
+
+var CstyleBehaviour = function () {
+
+ this.add("braces", "insertion", function (state, action, editor, session, text) {
+ if (text == '{') {
+ var selection = editor.getSelectionRange();
+ var selected = session.doc.getTextRange(selection);
+ if (selected !== "") {
+ return {
+ text: '{' + selected + '}',
+ selection: false
+ };
+ } else {
+ return {
+ text: '{}',
+ selection: [1, 1]
+ };
+ }
+ } else if (text == '}') {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var rightChar = line.substring(cursor.column, cursor.column + 1);
+ if (rightChar == '}') {
+ var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
+ if (matching !== null) {
+ return {
+ text: '',
+ selection: [1, 1]
+ };
+ }
+ }
+ } else if (text == "\n") {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var rightChar = line.substring(cursor.column, cursor.column + 1);
+ if (rightChar == '}') {
+ var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column + 1});
+ if (!openBracePos)
+ return null;
+
+ var indent = this.getNextLineIndent(state, line.substring(0, line.length - 1), session.getTabString());
+ var next_indent = this.$getIndent(session.doc.getLine(openBracePos.row));
+
+ return {
+ text: '\n' + indent + '\n' + next_indent,
+ selection: [1, indent.length, 1, indent.length]
+ };
+ }
+ }
+ });
+
+ this.add("braces", "deletion", function (state, action, editor, session, range) {
+ var selected = session.doc.getTextRange(range);
+ if (!range.isMultiLine() && selected == '{') {
+ var line = session.doc.getLine(range.start.row);
+ var rightChar = line.substring(range.end.column, range.end.column + 1);
+ if (rightChar == '}') {
+ range.end.column++;
+ return range;
+ }
+ }
+ });
+
+ this.add("parens", "insertion", function (state, action, editor, session, text) {
+ if (text == '(') {
+ var selection = editor.getSelectionRange();
+ var selected = session.doc.getTextRange(selection);
+ if (selected !== "") {
+ return {
+ text: '(' + selected + ')',
+ selection: false
+ };
+ } else {
+ return {
+ text: '()',
+ selection: [1, 1]
+ };
+ }
+ } else if (text == ')') {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var rightChar = line.substring(cursor.column, cursor.column + 1);
+ if (rightChar == ')') {
+ var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row});
+ if (matching !== null) {
+ return {
+ text: '',
+ selection: [1, 1]
+ };
+ }
+ }
+ }
+ });
+
+ this.add("parens", "deletion", function (state, action, editor, session, range) {
+ var selected = session.doc.getTextRange(range);
+ if (!range.isMultiLine() && selected == '(') {
+ var line = session.doc.getLine(range.start.row);
+ var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
+ if (rightChar == ')') {
+ range.end.column++;
+ return range;
+ }
+ }
+ });
+
+ this.add("brackets", "insertion", function (state, action, editor, session, text) {
+ if (text == '[') {
+ var selection = editor.getSelectionRange();
+ var selected = session.doc.getTextRange(selection);
+ if (selected !== "") {
+ return {
+ text: '[' + selected + ']',
+ selection: false
+ };
+ } else {
+ return {
+ text: '[]',
+ selection: [1, 1]
+ };
+ }
+ } else if (text == ']') {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var rightChar = line.substring(cursor.column, cursor.column + 1);
+ if (rightChar == ']') {
+ var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
+ if (matching !== null) {
+ return {
+ text: '',
+ selection: [1, 1]
+ };
+ }
+ }
+ }
+ });
+
+ this.add("brackets", "deletion", function (state, action, editor, session, range) {
+ var selected = session.doc.getTextRange(range);
+ if (!range.isMultiLine() && selected == '[') {
+ var line = session.doc.getLine(range.start.row);
+ var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
+ if (rightChar == ']') {
+ range.end.column++;
+ return range;
+ }
+ }
+ });
+
+ this.add("string_dquotes", "insertion", function (state, action, editor, session, text) {
+ if (text == '"' || text == "'") {
+ var quote = text;
+ var selection = editor.getSelectionRange();
+ var selected = session.doc.getTextRange(selection);
+ if (selected !== "") {
+ return {
+ text: quote + selected + quote,
+ selection: false
+ };
+ } else {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var leftChar = line.substring(cursor.column-1, cursor.column);
+
+ // We're escaped.
+ if (leftChar == '\\') {
+ return null;
+ }
+
+ // Find what token we're inside.
+ var tokens = session.getTokens(selection.start.row);
+ var col = 0, token;
+ var quotepos = -1; // Track whether we're inside an open quote.
+
+ for (var x = 0; x < tokens.length; x++) {
+ token = tokens[x];
+ if (token.type == "string") {
+ quotepos = -1;
+ } else if (quotepos < 0) {
+ quotepos = token.value.indexOf(quote);
+ }
+ if ((token.value.length + col) > selection.start.column) {
+ break;
+ }
+ col += tokens[x].value.length;
+ }
+
+ // Try and be smart about when we auto insert.
+ if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) {
+ return {
+ text: quote + quote,
+ selection: [1,1]
+ };
+ } else if (token && token.type === "string") {
+ // Ignore input and move right one if we're typing over the closing quote.
+ var rightChar = line.substring(cursor.column, cursor.column + 1);
+ if (rightChar == quote) {
+ return {
+ text: '',
+ selection: [1, 1]
+ };
+ }
+ }
+ }
+ }
+ });
+
+ this.add("string_dquotes", "deletion", function (state, action, editor, session, range) {
+ var selected = session.doc.getTextRange(range);
+ if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
+ var line = session.doc.getLine(range.start.row);
+ var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
+ if (rightChar == '"') {
+ range.end.column++;
+ return range;
+ }
+ }
+ });
+
+};
+
+oop.inherits(CstyleBehaviour, Behaviour);
+
+exports.CstyleBehaviour = CstyleBehaviour;
+});
+
+define('ace/mode/folding/cstyle', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/range', 'ace/mode/folding/fold_mode'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var Range = require("../../range").Range;
+var BaseFoldMode = require("./fold_mode").FoldMode;
+
+var FoldMode = exports.FoldMode = function() {};
+oop.inherits(FoldMode, BaseFoldMode);
+
+(function() {
+
+ this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
+ this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ var match = line.match(this.foldingStartMarker);
+ if (match) {
+ var i = match.index;
+
+ if (match[1])
+ return this.openingBracketBlock(session, match[1], row, i);
+
+ var range = session.getCommentFoldRange(row, i + match[0].length);
+ range.end.column -= 2;
+ return range;
+ }
+
+ if (foldStyle !== "markbeginend")
+ return;
+
+ var match = line.match(this.foldingStopMarker);
+ if (match) {
+ var i = match.index + match[0].length;
+
+ if (match[2]) {
+ var range = session.getCommentFoldRange(row, i);
+ range.end.column -= 2;
+ return range;
+ }
+
+ var end = {row: row, column: i};
+ var start = session.$findOpeningBracket(match[1], end);
+
+ if (!start)
+ return;
+
+ start.column++;
+ end.column--;
+
+ return Range.fromPoints(start, end);
+ }
+ };
+
+}).call(FoldMode.prototype);
+
+});
+
+define('ace/mode/folding/fold_mode', ['require', 'exports', 'module' , 'ace/range'], function(require, exports, module) {
+
+
+var Range = require("../../range").Range;
+
+var FoldMode = exports.FoldMode = function() {};
+
+(function() {
+
+ this.foldingStartMarker = null;
+ this.foldingStopMarker = null;
+
+ // must return "" if there's no fold, to enable caching
+ this.getFoldWidget = function(session, foldStyle, row) {
+ var line = session.getLine(row);
+ if (this.foldingStartMarker.test(line))
+ return "start";
+ if (foldStyle == "markbeginend"
+ && this.foldingStopMarker
+ && this.foldingStopMarker.test(line))
+ return "end";
+ return "";
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ return null;
+ };
+
+ this.indentationBlock = function(session, row, column) {
+ var re = /\S/;
+ var line = session.getLine(row);
+ var startLevel = line.search(re);
+ if (startLevel == -1)
+ return;
+
+ var startColumn = column || line.length;
+ var maxRow = session.getLength();
+ var startRow = row;
+ var endRow = row;
+
+ while (++row < maxRow) {
+ var level = session.getLine(row).search(re);
+
+ if (level == -1)
+ continue;
+
+ if (level <= startLevel)
+ break;
+
+ endRow = row;
+ }
+
+ if (endRow > startRow) {
+ var endColumn = session.getLine(endRow).length;
+ return new Range(startRow, startColumn, endRow, endColumn);
+ }
+ };
+
+ this.openingBracketBlock = function(session, bracket, row, column, typeRe) {
+ var start = {row: row, column: column + 1};
+ var end = session.$findClosingBracket(bracket, start, typeRe);
+ if (!end)
+ return;
+
+ var fw = session.foldWidgets[end.row];
+ if (fw == null)
+ fw = this.getFoldWidget(session, end.row);
+
+ if (fw == "start" && end.row > start.row) {
+ end.row --;
+ end.column = session.getLine(end.row).length;
+ }
+ return Range.fromPoints(start, end);
+ };
+
+}).call(FoldMode.prototype);
+
+});
+
+define('ace/mode/xml', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/xml_highlight_rules', 'ace/mode/behaviour/xml', 'ace/mode/folding/xml'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var XmlHighlightRules = require("./xml_highlight_rules").XmlHighlightRules;
+var XmlBehaviour = require("./behaviour/xml").XmlBehaviour;
+var XmlFoldMode = require("./folding/xml").FoldMode;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new XmlHighlightRules().getRules());
+ this.$behaviour = new XmlBehaviour();
+ this.foldingRules = new XmlFoldMode();
+};
+
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/xml_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/xml_util', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var xmlUtil = require("./xml_util");
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var XmlHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+ this.$rules = {
+ start : [{
+ token : "text",
+ regex : "<\\!\\[CDATA\\[",
+ next : "cdata"
+ }, {
+ token : "xml_pe",
+ regex : "<\\?.*?\\?>"
+ }, {
+ token : "comment",
+ merge : true,
+ regex : "<\\!--",
+ next : "comment"
+ }, {
+ token : "xml_pe",
+ regex : "<\\!.*?>"
+ }, {
+ token : "meta.tag", // opening tag
+ regex : "<\\/?",
+ next : "tag"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }, {
+ token : "constant.character.entity",
+ regex : "(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"
+ }, {
+ token : "text",
+ regex : "[^<]+"
+ }],
+
+ cdata : [{
+ token : "text",
+ regex : "\\]\\]>",
+ next : "start"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }, {
+ token : "text",
+ regex : "(?:[^\\]]|\\](?!\\]>))+"
+ }],
+
+ comment : [{
+ token : "comment",
+ regex : ".*?-->",
+ next : "start"
+ }, {
+ token : "comment",
+ merge : true,
+ regex : ".+"
+ }]
+ };
+
+ xmlUtil.tag(this.$rules, "tag", "start");
+};
+
+oop.inherits(XmlHighlightRules, TextHighlightRules);
+
+exports.XmlHighlightRules = XmlHighlightRules;
+});
+
+define('ace/mode/xml_util', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+function string(state) {
+ return [{
+ token : "string",
+ regex : '".*?"'
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : '["].*',
+ next : state + "_qqstring"
+ }, {
+ token : "string",
+ regex : "'.*?'"
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : "['].*",
+ next : state + "_qstring"
+ }];
+}
+
+function multiLineString(quote, state) {
+ return [{
+ token : "string",
+ merge : true,
+ regex : ".*?" + quote,
+ next : state
+ }, {
+ token : "string",
+ merge : true,
+ regex : '.+'
+ }];
+}
+
+exports.tag = function(states, name, nextState, tagMap) {
+ states[name] = [{
+ token : "text",
+ regex : "\\s+"
+ }, {
+ //token : "meta.tag",
+
+ token : function(value) {
+ if (tagMap && tagMap[value]) {
+ return "meta.tag.tag-name" + '.' + tagMap[value];
+ } else {
+ return "meta.tag.tag-name";
+ }
+ },
+ merge : true,
+ regex : "[-_a-zA-Z0-9:]+",
+ next : name + "_embed_attribute_list"
+ }, {
+ token: "empty",
+ regex: "",
+ next : name + "_embed_attribute_list"
+ }];
+
+ states[name + "_qstring"] = multiLineString("'", name + "_embed_attribute_list");
+ states[name + "_qqstring"] = multiLineString("\"", name + "_embed_attribute_list");
+
+ states[name + "_embed_attribute_list"] = [{
+ token : "meta.tag",
+ merge : true,
+ regex : "\/?>",
+ next : nextState
+ }, {
+ token : "keyword.operator",
+ regex : "="
+ }, {
+ token : "entity.other.attribute-name",
+ regex : "[-_a-zA-Z0-9:]+"
+ }, {
+ token : "constant.numeric", // float
+ regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }].concat(string(name));
+};
+
+});
+
+define('ace/mode/behaviour/xml', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/behaviour', 'ace/mode/behaviour/cstyle', 'ace/token_iterator'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var Behaviour = require("../behaviour").Behaviour;
+var CstyleBehaviour = require("./cstyle").CstyleBehaviour;
+var TokenIterator = require("../../token_iterator").TokenIterator;
+
+function hasType(token, type) {
+ var hasType = true;
+ var typeList = token.type.split('.');
+ var needleList = type.split('.');
+ needleList.forEach(function(needle){
+ if (typeList.indexOf(needle) == -1) {
+ hasType = false;
+ return false;
+ }
+ });
+ return hasType;
+}
+
+var XmlBehaviour = function () {
+
+ this.inherit(CstyleBehaviour, ["string_dquotes"]); // Get string behaviour
+
+ this.add("autoclosing", "insertion", function (state, action, editor, session, text) {
+ if (text == '>') {
+ var position = editor.getCursorPosition();
+ var iterator = new TokenIterator(session, position.row, position.column);
+ var token = iterator.getCurrentToken();
+ var atCursor = false;
+ if (!token || !hasType(token, 'meta.tag') && !(hasType(token, 'text') && token.value.match('/'))){
+ do {
+ token = iterator.stepBackward();
+ } while (token && (hasType(token, 'string') || hasType(token, 'keyword.operator') || hasType(token, 'entity.attribute-name') || hasType(token, 'text')));
+ } else {
+ atCursor = true;
+ }
+ if (!token || !hasType(token, 'meta.tag-name') || iterator.stepBackward().value.match('/')) {
+ return
+ }
+ var tag = token.value;
+ if (atCursor){
+ var tag = tag.substring(0, position.column - token.start);
+ }
+
+ return {
+ text: '>' + '</' + tag + '>',
+ selection: [1, 1]
+ }
+ }
+ });
+
+ this.add('autoindent', 'insertion', function (state, action, editor, session, text) {
+ if (text == "\n") {
+ var cursor = editor.getCursorPosition();
+ var line = session.doc.getLine(cursor.row);
+ var rightChars = line.substring(cursor.column, cursor.column + 2);
+ if (rightChars == '</') {
+ var indent = this.$getIndent(session.doc.getLine(cursor.row)) + session.getTabString();
+ var next_indent = this.$getIndent(session.doc.getLine(cursor.row));
+
+ return {
+ text: '\n' + indent + '\n' + next_indent,
+ selection: [1, indent.length, 1, indent.length]
+ }
+ }
+ }
+ });
+
+}
+oop.inherits(XmlBehaviour, Behaviour);
+
+exports.XmlBehaviour = XmlBehaviour;
+});
+
+define('ace/token_iterator', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * class TokenIterator
+ *
+ * This class provides an essay way to treat the document as a stream of tokens, and provides methods to iterate over these tokens.
+ *
+ **/
+
+/**
+ * new TokenIterator(session, initialRow, initialColumn)
+ * - session (EditSession): The session to associate with
+ * - initialRow (Number): The row to start the tokenizing at
+ * - initialColumn (Number): The column to start the tokenizing at
+ *
+ * Creates a new token iterator object. The inital token index is set to the provided row and column coordinates.
+ *
+ **/
+var TokenIterator = function(session, initialRow, initialColumn) {
+ this.$session = session;
+ this.$row = initialRow;
+ this.$rowTokens = session.getTokens(initialRow);
+
+ var token = session.getTokenAt(initialRow, initialColumn);
+ this.$tokenIndex = token ? token.index : -1;
+};
+
+(function() {
+
+ /**
+ * TokenIterator.stepBackward() -> [String]
+ * + (String): If the current point is not at the top of the file, this function returns `null`. Otherwise, it returns an array of the tokenized strings.
+ *
+ * Tokenizes all the items from the current point to the row prior in the document.
+ **/
+ this.stepBackward = function() {
+ this.$tokenIndex -= 1;
+
+ while (this.$tokenIndex < 0) {
+ this.$row -= 1;
+ if (this.$row < 0) {
+ this.$row = 0;
+ return null;
+ }
+
+ this.$rowTokens = this.$session.getTokens(this.$row);
+ this.$tokenIndex = this.$rowTokens.length - 1;
+ }
+
+ return this.$rowTokens[this.$tokenIndex];
+ };
+ this.stepForward = function() {
+ var rowCount = this.$session.getLength();
+ this.$tokenIndex += 1;
+
+ while (this.$tokenIndex >= this.$rowTokens.length) {
+ this.$row += 1;
+ if (this.$row >= rowCount) {
+ this.$row = rowCount - 1;
+ return null;
+ }
+
+ this.$rowTokens = this.$session.getTokens(this.$row);
+ this.$tokenIndex = 0;
+ }
+
+ return this.$rowTokens[this.$tokenIndex];
+ };
+ this.getCurrentToken = function () {
+ return this.$rowTokens[this.$tokenIndex];
+ };
+ this.getCurrentTokenRow = function () {
+ return this.$row;
+ };
+ this.getCurrentTokenColumn = function() {
+ var rowTokens = this.$rowTokens;
+ var tokenIndex = this.$tokenIndex;
+
+ // If a column was cached by EditSession.getTokenAt, then use it
+ var column = rowTokens[tokenIndex].start;
+ if (column !== undefined)
+ return column;
+
+ column = 0;
+ while (tokenIndex > 0) {
+ tokenIndex -= 1;
+ column += rowTokens[tokenIndex].value.length;
+ }
+
+ return column;
+ };
+
+}).call(TokenIterator.prototype);
+
+exports.TokenIterator = TokenIterator;
+});
+
+define('ace/mode/folding/xml', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/range', 'ace/mode/folding/fold_mode', 'ace/token_iterator'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var lang = require("../../lib/lang");
+var Range = require("../../range").Range;
+var BaseFoldMode = require("./fold_mode").FoldMode;
+var TokenIterator = require("../../token_iterator").TokenIterator;
+
+var FoldMode = exports.FoldMode = function(voidElements) {
+ BaseFoldMode.call(this);
+ this.voidElements = voidElements || {};
+};
+oop.inherits(FoldMode, BaseFoldMode);
+
+(function() {
+
+ this.getFoldWidget = function(session, foldStyle, row) {
+ var tag = this._getFirstTagInLine(session, row);
+
+ if (tag.closing)
+ return foldStyle == "markbeginend" ? "end" : "";
+
+ if (!tag.tagName || this.voidElements[tag.tagName.toLowerCase()])
+ return "";
+
+ if (tag.selfClosing)
+ return "";
+
+ if (tag.value.indexOf("/" + tag.tagName) !== -1)
+ return "";
+
+ return "start";
+ };
+
+ this._getFirstTagInLine = function(session, row) {
+ var tokens = session.getTokens(row);
+ var value = "";
+ for (var i = 0; i < tokens.length; i++) {
+ var token = tokens[i];
+ if (token.type.indexOf("meta.tag") === 0)
+ value += token.value;
+ else
+ value += lang.stringRepeat(" ", token.value.length);
+ }
+
+ return this._parseTag(value);
+ };
+
+ this.tagRe = /^(\s*)(<?(\/?)([-_a-zA-Z0-9:!]*)\s*(\/?)>?)/;
+ this._parseTag = function(tag) {
+
+ var match = this.tagRe.exec(tag);
+ var column = this.tagRe.lastIndex || 0;
+ this.tagRe.lastIndex = 0;
+
+ return {
+ value: tag,
+ match: match ? match[2] : "",
+ closing: match ? !!match[3] : false,
+ selfClosing: match ? !!match[5] || match[2] == "/>" : false,
+ tagName: match ? match[4] : "",
+ column: match[1] ? column + match[1].length : column
+ };
+ };
+ this._readTagForward = function(iterator) {
+ var token = iterator.getCurrentToken();
+ if (!token)
+ return null;
+
+ var value = "";
+ var start;
+
+ do {
+ if (token.type.indexOf("meta.tag") === 0) {
+ if (!start) {
+ var start = {
+ row: iterator.getCurrentTokenRow(),
+ column: iterator.getCurrentTokenColumn()
+ };
+ }
+ value += token.value;
+ if (value.indexOf(">") !== -1) {
+ var tag = this._parseTag(value);
+ tag.start = start;
+ tag.end = {
+ row: iterator.getCurrentTokenRow(),
+ column: iterator.getCurrentTokenColumn() + token.value.length
+ };
+ iterator.stepForward();
+ return tag;
+ }
+ }
+ } while(token = iterator.stepForward());
+
+ return null;
+ };
+
+ this._readTagBackward = function(iterator) {
+ var token = iterator.getCurrentToken();
+ if (!token)
+ return null;
+
+ var value = "";
+ var end;
+
+ do {
+ if (token.type.indexOf("meta.tag") === 0) {
+ if (!end) {
+ end = {
+ row: iterator.getCurrentTokenRow(),
+ column: iterator.getCurrentTokenColumn() + token.value.length
+ };
+ }
+ value = token.value + value;
+ if (value.indexOf("<") !== -1) {
+ var tag = this._parseTag(value);
+ tag.end = end;
+ tag.start = {
+ row: iterator.getCurrentTokenRow(),
+ column: iterator.getCurrentTokenColumn()
+ };
+ iterator.stepBackward();
+ return tag;
+ }
+ }
+ } while(token = iterator.stepBackward());
+
+ return null;
+ };
+
+ this._pop = function(stack, tag) {
+ while (stack.length) {
+
+ var top = stack[stack.length-1];
+ if (!tag || top.tagName == tag.tagName) {
+ return stack.pop();
+ }
+ else if (this.voidElements[tag.tagName]) {
+ return;
+ }
+ else if (this.voidElements[top.tagName]) {
+ stack.pop();
+ continue;
+ } else {
+ return null;
+ }
+ }
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var firstTag = this._getFirstTagInLine(session, row);
+
+ if (!firstTag.match)
+ return null;
+
+ var isBackward = firstTag.closing || firstTag.selfClosing;
+ var stack = [];
+ var tag;
+
+ if (!isBackward) {
+ var iterator = new TokenIterator(session, row, firstTag.column);
+ var start = {
+ row: row,
+ column: firstTag.column + firstTag.tagName.length + 2
+ };
+ while (tag = this._readTagForward(iterator)) {
+ if (tag.selfClosing) {
+ if (!stack.length) {
+ tag.start.column += tag.tagName.length + 2;
+ tag.end.column -= 2;
+ return Range.fromPoints(tag.start, tag.end);
+ } else
+ continue;
+ }
+
+ if (tag.closing) {
+ this._pop(stack, tag);
+ if (stack.length == 0)
+ return Range.fromPoints(start, tag.start);
+ }
+ else {
+ stack.push(tag)
+ }
+ }
+ }
+ else {
+ var iterator = new TokenIterator(session, row, firstTag.column + firstTag.match.length);
+ var end = {
+ row: row,
+ column: firstTag.column
+ };
+
+ while (tag = this._readTagBackward(iterator)) {
+ if (tag.selfClosing) {
+ if (!stack.length) {
+ tag.start.column += tag.tagName.length + 2;
+ tag.end.column -= 2;
+ return Range.fromPoints(tag.start, tag.end);
+ } else
+ continue;
+ }
+
+ if (!tag.closing) {
+ this._pop(stack, tag);
+ if (stack.length == 0) {
+ tag.start.column += tag.tagName.length + 2;
+ return Range.fromPoints(tag.start, end);
+ }
+ }
+ else {
+ stack.push(tag)
+ }
+ }
+ }
+
+ };
+
+}).call(FoldMode.prototype);
+
+});
+
+define('ace/mode/html', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/mode/javascript', 'ace/mode/css', 'ace/tokenizer', 'ace/mode/html_highlight_rules', 'ace/mode/behaviour/html', 'ace/mode/folding/html'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var JavaScriptMode = require("./javascript").Mode;
+var CssMode = require("./css").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules;
+var HtmlBehaviour = require("./behaviour/html").HtmlBehaviour;
+var HtmlFoldMode = require("./folding/html").FoldMode;
+
+var Mode = function() {
+ var highlighter = new HtmlHighlightRules();
+ this.$tokenizer = new Tokenizer(highlighter.getRules());
+ this.$behaviour = new HtmlBehaviour();
+
+ this.$embeds = highlighter.getEmbeds();
+ this.createModeDelegates({
+ "js-": JavaScriptMode,
+ "css-": CssMode
+ });
+
+ this.foldingRules = new HtmlFoldMode();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+
+ this.toggleCommentLines = function(state, doc, startRow, endRow) {
+ return 0;
+ };
+
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return false;
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/css', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/css_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/worker/worker_client', 'ace/mode/folding/cstyle'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules;
+var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent;
+var WorkerClient = require("../worker/worker_client").WorkerClient;
+var CStyleFoldMode = require("./folding/cstyle").FoldMode;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new CssHighlightRules().getRules(), "i");
+ this.$outdent = new MatchingBraceOutdent();
+ this.foldingRules = new CStyleFoldMode();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+ this.foldingRules = "cStyle";
+
+ this.getNextLineIndent = function(state, line, tab) {
+ var indent = this.$getIndent(line);
+
+ // ignore braces in comments
+ var tokens = this.$tokenizer.getLineTokens(line, state).tokens;
+ if (tokens.length && tokens[tokens.length-1].type == "comment") {
+ return indent;
+ }
+
+ var match = line.match(/^.*\{\s*$/);
+ if (match) {
+ indent += tab;
+ }
+
+ return indent;
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ this.$outdent.autoOutdent(doc, row);
+ };
+
+ this.createWorker = function(session) {
+ var worker = new WorkerClient(["ace"], "ace/mode/css_worker", "Worker");
+ worker.attachToDocument(session.getDocument());
+
+ worker.on("csslint", function(e) {
+ var errors = [];
+ e.data.forEach(function(message) {
+ errors.push({
+ row: message.line - 1,
+ column: message.col - 1,
+ text: message.message,
+ type: message.type,
+ lint: message
+ });
+ });
+
+ session.setAnnotations(errors);
+ });
+ return worker;
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+
+});
+
+define('ace/mode/css_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var lang = require("../lib/lang");
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var CssHighlightRules = function() {
+
+ var properties = lang.arrayToMap(
+ ("animation-fill-mode|alignment-adjust|alignment-baseline|animation-delay|animation-direction|animation-duration|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|animation|appearance|azimuth|backface-visibility|background-attachment|background-break|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|background|baseline-shift|binding|bleed|bookmark-label|bookmark-level|boo [...]
+ );
+
+ var functions = lang.arrayToMap(
+ ("rgb|rgba|url|attr|counter|counters").split("|")
+ );
+
+ var constants = lang.arrayToMap(
+ ("absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default [...]
+ );
+
+ var colors = lang.arrayToMap(
+ ("aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|" +
+ "purple|red|silver|teal|white|yellow").split("|")
+ );
+
+ var fonts = lang.arrayToMap(
+ ("arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|" +
+ "symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|" +
+ "serif|monospace").split("|")
+ );
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ var numRe = "\\-?(?:(?:[0-9]+)|(?:[0-9]*\\.[0-9]+))";
+ var pseudoElements = "(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b";
+ var pseudoClasses = "(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b";
+
+ var base_ruleset = [
+ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "ruleset_comment"
+ }, {
+ token : "string", // single line
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
+ }, {
+ token : "string", // single line
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
+ }, {
+ token : ["constant.numeric", "keyword"],
+ regex : "(" + numRe + ")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vm|vw|%)"
+ }, {
+ token : ["constant.numeric"],
+ regex : "([0-9]+)"
+ }, {
+ token : "constant.numeric", // hex6 color
+ regex : "#[a-f0-9]{6}"
+ }, {
+ token : "constant.numeric", // hex3 color
+ regex : "#[a-f0-9]{3}"
+ }, {
+ token : ["punctuation", "entity.other.attribute-name.pseudo-element.css"],
+ regex : pseudoElements
+ }, {
+ token : ["punctuation", "entity.other.attribute-name.pseudo-class.css"],
+ regex : pseudoClasses
+ }, {
+ token : function(value) {
+ if (properties.hasOwnProperty(value.toLowerCase())) {
+ return "support.type";
+ }
+ else if (functions.hasOwnProperty(value.toLowerCase())) {
+ return "support.function";
+ }
+ else if (constants.hasOwnProperty(value.toLowerCase())) {
+ return "support.constant";
+ }
+ else if (colors.hasOwnProperty(value.toLowerCase())) {
+ return "support.constant.color";
+ }
+ else if (fonts.hasOwnProperty(value.toLowerCase())) {
+ return "support.constant.fonts";
+ }
+ else {
+ return "text";
+ }
+ },
+ regex : "\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"
+ }
+ ];
+
+ var ruleset = lang.copyArray(base_ruleset);
+ ruleset.unshift({
+ token : "paren.rparen",
+ regex : "\\}",
+ next: "start"
+ });
+
+ var media_ruleset = lang.copyArray( base_ruleset );
+ media_ruleset.unshift({
+ token : "paren.rparen",
+ regex : "\\}",
+ next: "media"
+ });
+
+ var base_comment = [{
+ token : "comment", // comment spanning whole line
+ merge : true,
+ regex : ".+"
+ }];
+
+ var comment = lang.copyArray(base_comment);
+ comment.unshift({
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ next : "start"
+ });
+
+ var media_comment = lang.copyArray(base_comment);
+ media_comment.unshift({
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ next : "media"
+ });
+
+ var ruleset_comment = lang.copyArray(base_comment);
+ ruleset_comment.unshift({
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ next : "ruleset"
+ });
+
+ this.$rules = {
+ "start" : [{
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "comment"
+ }, {
+ token: "paren.lparen",
+ regex: "\\{",
+ next: "ruleset"
+ }, {
+ token: "string",
+ regex: "@.*?{",
+ next: "media"
+ },{
+ token: "keyword",
+ regex: "#[a-z0-9-_]+"
+ },{
+ token: "variable",
+ regex: "\\.[a-z0-9-_]+"
+ },{
+ token: "string",
+ regex: ":[a-z0-9-_]+"
+ },{
+ token: "constant",
+ regex: "[a-z0-9-_]+"
+ }],
+
+ "media" : [ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "media_comment"
+ }, {
+ token: "paren.lparen",
+ regex: "\\{",
+ next: "media_ruleset"
+ },{
+ token: "string",
+ regex: "\\}",
+ next: "start"
+ },{
+ token: "keyword",
+ regex: "#[a-z0-9-_]+"
+ },{
+ token: "variable",
+ regex: "\\.[a-z0-9-_]+"
+ },{
+ token: "string",
+ regex: ":[a-z0-9-_]+"
+ },{
+ token: "constant",
+ regex: "[a-z0-9-_]+"
+ }],
+
+ "comment" : comment,
+
+ "ruleset" : ruleset,
+ "ruleset_comment" : ruleset_comment,
+
+ "media_ruleset" : media_ruleset,
+ "media_comment" : media_comment
+ };
+};
+
+oop.inherits(CssHighlightRules, TextHighlightRules);
+
+exports.CssHighlightRules = CssHighlightRules;
+
+});
+
+define('ace/mode/html_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/css_highlight_rules', 'ace/mode/javascript_highlight_rules', 'ace/mode/xml_util', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules;
+var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
+var xmlUtil = require("./xml_util");
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var tagMap = {
+ a : 'anchor',
+ button : 'form',
+ form : 'form',
+ img : 'image',
+ input : 'form',
+ label : 'form',
+ script : 'script',
+ select : 'form',
+ textarea : 'form',
+ style : 'style',
+ table : 'table',
+ tbody : 'table',
+ td : 'table',
+ tfoot : 'table',
+ th : 'table',
+ tr : 'table'
+};
+
+var HtmlHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+ this.$rules = {
+ start : [{
+ token : "text",
+ merge : true,
+ regex : "<\\!\\[CDATA\\[",
+ next : "cdata"
+ }, {
+ token : "xml_pe",
+ regex : "<\\?.*?\\?>"
+ }, {
+ token : "comment",
+ merge : true,
+ regex : "<\\!--",
+ next : "comment"
+ }, {
+ token : "xml_pe",
+ regex : "<\\!.*?>"
+ }, {
+ token : "meta.tag",
+ regex : "<(?=\s*script\\b)",
+ next : "script"
+ }, {
+ token : "meta.tag",
+ regex : "<(?=\s*style\\b)",
+ next : "style"
+ }, {
+ token : "meta.tag", // opening tag
+ regex : "<\\/?",
+ next : "tag"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }, {
+ token : "constant.character.entity",
+ regex : "(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"
+ }, {
+ token : "text",
+ regex : "[^<]+"
+ } ],
+
+ cdata : [ {
+ token : "text",
+ regex : "\\]\\]>",
+ next : "start"
+ }, {
+ token : "text",
+ merge : true,
+ regex : "\\s+"
+ }, {
+ token : "text",
+ merge : true,
+ regex : ".+"
+ } ],
+
+ comment : [ {
+ token : "comment",
+ regex : ".*?-->",
+ next : "start"
+ }, {
+ token : "comment",
+ merge : true,
+ regex : ".+"
+ } ]
+ };
+
+ xmlUtil.tag(this.$rules, "tag", "start", tagMap);
+ xmlUtil.tag(this.$rules, "style", "css-start", tagMap);
+ xmlUtil.tag(this.$rules, "script", "js-start", tagMap);
+
+ this.embedRules(JavaScriptHighlightRules, "js-", [{
+ token: "comment",
+ regex: "\\/\\/.*(?=<\\/script>)",
+ next: "tag"
+ }, {
+ token: "meta.tag",
+ regex: "<\\/(?=script)",
+ next: "tag"
+ }]);
+
+ this.embedRules(CssHighlightRules, "css-", [{
+ token: "meta.tag",
+ regex: "<\\/(?=style)",
+ next: "tag"
+ }]);
+};
+
+oop.inherits(HtmlHighlightRules, TextHighlightRules);
+
+exports.HtmlHighlightRules = HtmlHighlightRules;
+});
+
+define('ace/mode/behaviour/html', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/behaviour/xml', 'ace/mode/behaviour/cstyle', 'ace/token_iterator'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var XmlBehaviour = require("../behaviour/xml").XmlBehaviour;
+var CstyleBehaviour = require("./cstyle").CstyleBehaviour;
+var TokenIterator = require("../../token_iterator").TokenIterator;
+var voidElements = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
+
+function hasType(token, type) {
+ var hasType = true;
+ var typeList = token.type.split('.');
+ var needleList = type.split('.');
+ needleList.forEach(function(needle){
+ if (typeList.indexOf(needle) == -1) {
+ hasType = false;
+ return false;
+ }
+ });
+ return hasType;
+}
+
+var HtmlBehaviour = function () {
+
+ this.inherit(XmlBehaviour); // Get xml behaviour
+
+ this.add("autoclosing", "insertion", function (state, action, editor, session, text) {
+ if (text == '>') {
+ var position = editor.getCursorPosition();
+ var iterator = new TokenIterator(session, position.row, position.column);
+ var token = iterator.getCurrentToken();
+ var atCursor = false;
+ if (!token || !hasType(token, 'meta.tag') && !(hasType(token, 'text') && token.value.match('/'))){
+ do {
+ token = iterator.stepBackward();
+ } while (token && (hasType(token, 'string') || hasType(token, 'keyword.operator') || hasType(token, 'entity.attribute-name') || hasType(token, 'text')));
+ } else {
+ atCursor = true;
+ }
+ if (!token || !hasType(token, 'meta.tag-name') || iterator.stepBackward().value.match('/')) {
+ return
+ }
+ var element = token.value;
+ if (atCursor){
+ var element = element.substring(0, position.column - token.start);
+ }
+ if (voidElements.indexOf(element) !== -1){
+ return;
+ }
+ return {
+ text: '>' + '</' + element + '>',
+ selection: [1, 1]
+ }
+ }
+ });
+}
+oop.inherits(HtmlBehaviour, XmlBehaviour);
+
+exports.HtmlBehaviour = HtmlBehaviour;
+});
+
+define('ace/mode/folding/html', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/folding/mixed', 'ace/mode/folding/xml', 'ace/mode/folding/cstyle'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var MixedFoldMode = require("./mixed").FoldMode;
+var XmlFoldMode = require("./xml").FoldMode;
+var CStyleFoldMode = require("./cstyle").FoldMode;
+
+var FoldMode = exports.FoldMode = function() {
+ MixedFoldMode.call(this, new XmlFoldMode({
+ // void elements
+ "area": 1,
+ "base": 1,
+ "br": 1,
+ "col": 1,
+ "command": 1,
+ "embed": 1,
+ "hr": 1,
+ "img": 1,
+ "input": 1,
+ "keygen": 1,
+ "link": 1,
+ "meta": 1,
+ "param": 1,
+ "source": 1,
+ "track": 1,
+ "wbr": 1,
+
+ // optional tags
+ "li": 1,
+ "dt": 1,
+ "dd": 1,
+ "p": 1,
+ "rt": 1,
+ "rp": 1,
+ "optgroup": 1,
+ "option": 1,
+ "colgroup": 1,
+ "td": 1,
+ "th": 1
+ }), {
+ "js-": new CStyleFoldMode(),
+ "css-": new CStyleFoldMode()
+ });
+};
+
+oop.inherits(FoldMode, MixedFoldMode);
+
+});
+
+define('ace/mode/folding/mixed', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/folding/fold_mode'], function(require, exports, module) {
+
+
+var oop = require("../../lib/oop");
+var BaseFoldMode = require("./fold_mode").FoldMode;
+
+var FoldMode = exports.FoldMode = function(defaultMode, subModes) {
+ this.defaultMode = defaultMode;
+ this.subModes = subModes;
+};
+oop.inherits(FoldMode, BaseFoldMode);
+
+(function() {
+
+
+ this.$getMode = function(state) {
+ for (var key in this.subModes) {
+ if (state.indexOf(key) === 0)
+ return this.subModes[key];
+ }
+ return null;
+ };
+
+ this.$tryMode = function(state, session, foldStyle, row) {
+ var mode = this.$getMode(state);
+ return (mode ? mode.getFoldWidget(session, foldStyle, row) : "");
+ };
+
+ this.getFoldWidget = function(session, foldStyle, row) {
+ return (
+ this.$tryMode(session.getState(row-1), session, foldStyle, row) ||
+ this.$tryMode(session.getState(row), session, foldStyle, row) ||
+ this.defaultMode.getFoldWidget(session, foldStyle, row)
+ );
+ };
+
+ this.getFoldWidgetRange = function(session, foldStyle, row) {
+ var mode = this.$getMode(session.getState(row-1));
+
+ if (!mode || !mode.getFoldWidget(session, foldStyle, row))
+ mode = this.$getMode(session.getState(row));
+
+ if (!mode || !mode.getFoldWidget(session, foldStyle, row))
+ mode = this.defaultMode;
+
+ return mode.getFoldWidgetRange(session, foldStyle, row);
+ };
+
+}).call(FoldMode.prototype);
+
+});
+
+define('ace/mode/markdown_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text_highlight_rules', 'ace/mode/javascript_highlight_rules', 'ace/mode/xml_highlight_rules', 'ace/mode/html_highlight_rules', 'ace/mode/css_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
+var XmlHighlightRules = require("./xml_highlight_rules").XmlHighlightRules;
+var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules;
+var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules;
+
+function github_embed(tag, prefix) {
+ return { // Github style block
+ token : "support.function",
+ regex : "^```" + tag + "\\s*$",
+ next : prefix + "start"
+ };
+}
+
+var MarkdownHighlightRules = function() {
+
+ // regexp must not have capturing parentheses
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [ {
+ token : "empty_line",
+ regex : '^$'
+ }, { // code span `
+ token : ["support.function", "support.function", "support.function"],
+ regex : "(`+)([^\\r]*?[^`])(\\1)"
+ }, { // code block
+ token : "support.function",
+ regex : "^[ ]{4}.+"
+ }, { // h1
+ token: "markup.heading.1",
+ regex: "^=+(?=\\s*$)"
+ }, { // h2
+ token: "markup.heading.1",
+ regex: "^\\-+(?=\\s*$)"
+ }, { // header
+ token : function(value) {
+ return "markup.heading." + value.length;
+ },
+ regex : "^#{1,6}"
+ }, github_embed("(?:javascript|js)", "js-"),
+ github_embed("xml", "xml-"),
+ github_embed("html", "html-"),
+ github_embed("css", "css-"),
+ { // Github style block
+ token : "support.function",
+ regex : "^```[a-zA-Z]+\\s*$",
+ next : "githubblock"
+ }, { // block quote
+ token : "string",
+ regex : "^>[ ].+$",
+ next : "blockquote"
+ }, { // reference
+ token : ["text", "constant", "text", "url", "string", "text"],
+ regex : "^([ ]{0,3}\\[)([^\\]]+)(\\]:\\s*)([^ ]+)(\\s*(?:[\"][^\"]+[\"])?(\\s*))$"
+ }, { // link by reference
+ token : ["text", "string", "text", "constant", "text"],
+ regex : "(\\[)((?:[[^\\]]*\\]|[^\\[\\]])*)(\\][ ]?(?:\\n[ ]*)?\\[)(.*?)(\\])"
+ }, { // link by url
+ token : ["text", "string", "text", "markup.underline", "string", "text"],
+ regex : "(\\[)"+
+ "(\\[[^\\]]*\\]|[^\\[\\]]*)"+
+ "(\\]\\([ \\t]*)"+
+ "(<?(?:(?:[^\\(]*?\\([^\\)]*?\\)\\S*?)|(?:.*?))>?)"+
+ "((?:[ \t]*\"(?:.*?)\"[ \\t]*)?)"+
+ "(\\))"
+ }, { // HR *
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\*[ ]?){3,}\\s*$"
+ }, { // HR -
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\-[ ]?){3,}\\s*$"
+ }, { // HR _
+ token : "constant",
+ regex : "^[ ]{0,2}(?:[ ]?\\_[ ]?){3,}\\s*$"
+ }, { // list
+ token : "markup.list",
+ regex : "^\\s{0,3}(?:[*+-]|\\d+\\.)\\s+",
+ next : "listblock"
+ }, { // strong ** __
+ token : ["string", "string", "string"],
+ regex : "([*]{2}|[_]{2}(?=\\S))([^\\r]*?\\S[*_]*)(\\1)"
+ }, { // emphasis * _
+ token : ["string", "string", "string"],
+ regex : "([*]|[_](?=\\S))([^\\r]*?\\S[*_]*)(\\1)"
+ }, { //
+ token : ["text", "url", "text"],
+ regex : "(<)("+
+ "(?:https?|ftp|dict):[^'\">\\s]+"+
+ "|"+
+ "(?:mailto:)?[-.\\w]+\\@[-a-z0-9]+(?:\\.[-a-z0-9]+)*\\.[a-z]+"+
+ ")(>)"
+ }, {
+ token : "text",
+ regex : "[^\\*_%$`\\[#<>]+"
+ } ],
+
+ "listblock" : [ { // Lists only escape on completely blank lines.
+ token : "empty_line",
+ regex : "^$",
+ next : "start"
+ }, {
+ token : "markup.list",
+ regex : ".+"
+ } ],
+
+ "blockquote" : [ { // BLockquotes only escape on blank lines.
+ token : "empty_line",
+ regex : "^\\s*$",
+ next : "start"
+ }, {
+ token : "string",
+ regex : ".+"
+ } ],
+
+ "githubblock" : [ {
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }, {
+ token : "support.function",
+ regex : ".+"
+ } ]
+ };
+
+ this.embedRules(JavaScriptHighlightRules, "js-", [{
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }]);
+
+ this.embedRules(HtmlHighlightRules, "html-", [{
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }]);
+
+ this.embedRules(CssHighlightRules, "css-", [{
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }]);
+
+ this.embedRules(XmlHighlightRules, "xml-", [{
+ token : "support.function",
+ regex : "^```",
+ next : "start"
+ }]);
+};
+oop.inherits(MarkdownHighlightRules, TextHighlightRules);
+
+exports.MarkdownHighlightRules = MarkdownHighlightRules;
+});
+
+define('ace/mode/svg', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/xml', 'ace/mode/javascript', 'ace/tokenizer', 'ace/mode/svg_highlight_rules', 'ace/mode/folding/mixed', 'ace/mode/folding/xml', 'ace/mode/folding/cstyle'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var XmlMode = require("./xml").Mode;
+var JavaScriptMode = require("./javascript").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var SvgHighlightRules = require("./svg_highlight_rules").SvgHighlightRules;
+var MixedFoldMode = require("./folding/mixed").FoldMode;
+var XmlFoldMode = require("./folding/xml").FoldMode;
+var CStyleFoldMode = require("./folding/cstyle").FoldMode;
+
+var Mode = function() {
+ XmlMode.call(this);
+
+ this.highlighter = new SvgHighlightRules();
+ this.$tokenizer = new Tokenizer(this.highlighter.getRules());
+
+ this.$embeds = this.highlighter.getEmbeds();
+ this.createModeDelegates({
+ "js-": JavaScriptMode
+ });
+
+ this.foldingRules = new MixedFoldMode(new XmlFoldMode({}), {
+ "js-": new CStyleFoldMode()
+ });
+};
+
+oop.inherits(Mode, XmlMode);
+
+(function() {
+
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$getIndent(line);
+ };
+
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/svg_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/javascript_highlight_rules', 'ace/mode/xml_highlight_rules', 'ace/mode/xml_util'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
+var XmlHighlightRules = require("./xml_highlight_rules").XmlHighlightRules;
+var xmlUtil = require("./xml_util");
+
+var SvgHighlightRules = function() {
+ XmlHighlightRules.call(this);
+
+ this.$rules.start.splice(3, 0, {
+ token : "meta.tag",
+ regex : "<(?=\s*script)",
+ next : "script"
+ });
+
+ xmlUtil.tag(this.$rules, "script", "js-start");
+
+ this.embedRules(JavaScriptHighlightRules, "js-", [{
+ token: "comment",
+ regex: "\\/\\/.*(?=<\\/script>)",
+ next: "tag"
+ }, {
+ token: "meta.tag",
+ regex: "<\\/(?=script)",
+ next: "tag"
+ }]);
+};
+
+oop.inherits(SvgHighlightRules, XmlHighlightRules);
+
+exports.SvgHighlightRules = SvgHighlightRules;
+});
+
+define('ace/mode/c_cpp', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/c_cpp_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var TextMode = require("./text").Mode;
+var Tokenizer = require("../tokenizer").Tokenizer;
+var c_cppHighlightRules = require("./c_cpp_highlight_rules").c_cppHighlightRules;
+var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent;
+var Range = require("../range").Range;
+var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour;
+var CStyleFoldMode = require("./folding/cstyle").FoldMode;
+
+var Mode = function() {
+ this.$tokenizer = new Tokenizer(new c_cppHighlightRules().getRules());
+ this.$outdent = new MatchingBraceOutdent();
+ this.$behaviour = new CstyleBehaviour();
+ this.foldingRules = new CStyleFoldMode();
+};
+oop.inherits(Mode, TextMode);
+
+(function() {
+
+ this.toggleCommentLines = function(state, doc, startRow, endRow) {
+ var outdent = true;
+ var re = /^(\s*)\/\//;
+
+ for (var i=startRow; i<= endRow; i++) {
+ if (!re.test(doc.getLine(i))) {
+ outdent = false;
+ break;
+ }
+ }
+
+ if (outdent) {
+ var deleteRange = new Range(0, 0, 0, 0);
+ for (var i=startRow; i<= endRow; i++)
+ {
+ var line = doc.getLine(i);
+ var m = line.match(re);
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ deleteRange.end.column = m[0].length;
+ doc.replace(deleteRange, m[1]);
+ }
+ }
+ else {
+ doc.indentRows(startRow, endRow, "//");
+ }
+ };
+
+ this.getNextLineIndent = function(state, line, tab) {
+ var indent = this.$getIndent(line);
+
+ var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
+ var tokens = tokenizedLine.tokens;
+ var endState = tokenizedLine.state;
+
+ if (tokens.length && tokens[tokens.length-1].type == "comment") {
+ return indent;
+ }
+
+ if (state == "start") {
+ var match = line.match(/^.*[\{\(\[]\s*$/);
+ if (match) {
+ indent += tab;
+ }
+ } else if (state == "doc-start") {
+ if (endState == "start") {
+ return "";
+ }
+ var match = line.match(/^\s*(\/?)\*/);
+ if (match) {
+ if (match[1]) {
+ indent += " ";
+ }
+ indent += "* ";
+ }
+ }
+
+ return indent;
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ this.$outdent.autoOutdent(doc, row);
+ };
+
+}).call(Mode.prototype);
+
+exports.Mode = Mode;
+});
+
+define('ace/mode/c_cpp_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/mode/doc_comment_highlight_rules', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var lang = require("../lib/lang");
+var DocCommentHighlightRules = require("./doc_comment_highlight_rules").DocCommentHighlightRules;
+var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
+
+var c_cppHighlightRules = function() {
+
+ var keywords = lang.arrayToMap(
+ ("and|double|not_eq|throw|and_eq|dynamic_cast|operator|true|" +
+ "asm|else|or|try|auto|enum|or_eq|typedef|bitand|explicit|private|" +
+ "typeid|bitor|extern|protected|typename|bool|false|public|union|" +
+ "break|float|register|unsigned|case|fro|reinterpret-cast|using|catch|" +
+ "friend|return|virtual|char|goto|short|void|class|if|signed|volatile|" +
+ "compl|inline|sizeof|wchar_t|const|int|static|while|const-cast|long|" +
+ "static_cast|xor|continue|mutable|struct|xor_eq|default|namespace|" +
+ "switch|delete|new|template|do|not|this|for").split("|")
+ );
+
+ var buildinConstants = lang.arrayToMap(
+ ("NULL").split("|")
+ );
+
+ // regexp must not have capturing parentheses. Use (?:) instead.
+ // regexps are ordered -> the first match is used
+
+ this.$rules = {
+ "start" : [
+ {
+ token : "comment",
+ regex : "\\/\\/.*$"
+ },
+ DocCommentHighlightRules.getStartRule("doc-start"),
+ {
+ token : "comment", // multi line comment
+ merge : true,
+ regex : "\\/\\*",
+ next : "comment"
+ }, {
+ token : "string", // single line
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : '["].*\\\\$',
+ next : "qqstring"
+ }, {
+ token : "string", // single line
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
+ }, {
+ token : "string", // multi line string start
+ merge : true,
+ regex : "['].*\\\\$",
+ next : "qstring"
+ }, {
+ token : "constant.numeric", // hex
+ regex : "0[xX][0-9a-fA-F]+\\b"
+ }, {
+ token : "constant.numeric", // float
+ regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
+ }, {
+ token : "constant", // <CONSTANT>
+ regex : "<[a-zA-Z0-9.]+>"
+ }, {
+ token : "keyword", // pre-compiler directivs
+ regex : "(?:#include|#pragma|#line|#define|#undef|#ifdef|#else|#elif|#endif|#ifndef)"
+ }, {
+ token : function(value) {
+ if (value == "this")
+ return "variable.language";
+ else if (keywords.hasOwnProperty(value))
+ return "keyword";
+ else if (buildinConstants.hasOwnProperty(value))
+ return "constant.language";
+ else
+ return "identifier";
+ },
+ regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
+ }, {
+ token : "keyword.operator",
+ regex : "!|\\$|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|==|=|!=|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|new|delete|typeof|void)"
+ }, {
+ token : "punctuation.operator",
+ regex : "\\?|\\:|\\,|\\;|\\."
+ }, {
+ token : "paren.lparen",
+ regex : "[[({]"
+ }, {
+ token : "paren.rparen",
+ regex : "[\\])}]"
+ }, {
+ token : "text",
+ regex : "\\s+"
+ }
+ ],
+ "comment" : [
+ {
+ token : "comment", // closing comment
+ regex : ".*?\\*\\/",
+ next : "start"
+ }, {
+ token : "comment", // comment spanning whole line
+ merge : true,
+ regex : ".+"
+ }
+ ],
+ "qqstring" : [
+ {
+ token : "string",
+ regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
+ next : "start"
+ }, {
+ token : "string",
+ merge : true,
+ regex : '.+'
+ }
+ ],
+ "qstring" : [
+ {
+ token : "string",
+ regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
+ next : "start"
+ }, {
+ token : "string",
+ merge : true,
+ regex : '.+'
+ }
+ ]
+ };
+
+ this.embedRules(DocCommentHighlightRules, "doc-",
+ [ DocCommentHighlightRules.getEndRule("start") ]);
+};
+
+oop.inherits(c_cppHighlightRules, TextHighlightRules);
+
+exports.c_cppHighlightRules = c_cppHighlightRules;
+});
+
+define('ace/keyboard/textinput', ['require', 'exports', 'module' , 'ace/lib/event', 'ace/lib/useragent', 'ace/lib/dom'], function(require, exports, module) {
+
+
+var event = require("../lib/event");
+var useragent = require("../lib/useragent");
+var dom = require("../lib/dom");
+
+var TextInput = function(parentNode, host) {
+ var text = dom.createElement("textarea");
+ if (useragent.isTouchPad)
+ text.setAttribute("x-palm-disable-auto-cap", true);
+
+ text.setAttribute("wrap", "off");
+
+ text.style.top = "-2em";
+ parentNode.insertBefore(text, parentNode.firstChild);
+
+ var PLACEHOLDER = useragent.isIE ? "\x01" : "\x01";
+ sendText();
+
+ var inCompostion = false;
+ var copied = false;
+ var pasted = false;
+ var tempStyle = '';
+
+ function reset(full) {
+ try {
+ if (full) {
+ text.value = PLACEHOLDER;
+ text.selectionStart = 0;
+ text.selectionEnd = 1;
+ } else
+ text.select();
+ } catch (e) {}
+ }
+
+ function sendText(valueToSend) {
+ if (!copied) {
+ var value = valueToSend || text.value;
+ if (value) {
+ if (value.length > 1) {
+ if (value.charAt(0) == PLACEHOLDER)
+ value = value.substr(1);
+ else if (value.charAt(value.length - 1) == PLACEHOLDER)
+ value = value.slice(0, -1);
+ }
+
+ if (value && value != PLACEHOLDER) {
+ if (pasted)
+ host.onPaste(value);
+ else
+ host.onTextInput(value);
+ }
+ }
+ }
+
+ copied = false;
+ pasted = false;
+
+ // Safari doesn't fire copy events if no text is selected
+ reset(true);
+ }
+
+ var onTextInput = function(e) {
+ if (!inCompostion)
+ sendText(e.data);
+ setTimeout(function () {
+ if (!inCompostion)
+ reset(true);
+ }, 0);
+ };
+
+ var onPropertyChange = function(e) {
+ setTimeout(function() {
+ if (!inCompostion)
+ if(text.value != "") {
+ sendText();
+ }
+ }, 0);
+ };
+
+ var onCompositionStart = function(e) {
+ inCompostion = true;
+ host.onCompositionStart();
+ setTimeout(onCompositionUpdate, 0);
+ };
+
+ var onCompositionUpdate = function() {
+ if (!inCompostion) return;
+ host.onCompositionUpdate(text.value);
+ };
+
+ var onCompositionEnd = function(e) {
+ inCompostion = false;
+ host.onCompositionEnd();
+ };
+
+ var onCopy = function(e) {
+ copied = true;
+ var copyText = host.getCopyText();
+ if(copyText)
+ text.value = copyText;
+ else
+ e.preventDefault();
+ reset();
+ setTimeout(function () {
+ sendText();
+ }, 0);
+ };
+
+ var onCut = function(e) {
+ copied = true;
+ var copyText = host.getCopyText();
+ if(copyText) {
+ text.value = copyText;
+ host.onCut();
+ } else
+ e.preventDefault();
+ reset();
+ setTimeout(function () {
+ sendText();
+ }, 0);
+ };
+
+ event.addCommandKeyListener(text, host.onCommandKey.bind(host));
+ event.addListener(text, "input", onTextInput);
+
+ if (useragent.isOldIE) {
+ var keytable = { 13:1, 27:1 };
+ event.addListener(text, "keyup", function (e) {
+ if (inCompostion && (!text.value || keytable[e.keyCode]))
+ setTimeout(onCompositionEnd, 0);
+ if ((text.value.charCodeAt(0)|0) < 129) {
+ return;
+ }
+ inCompostion ? onCompositionUpdate() : onCompositionStart();
+ });
+
+ event.addListener(text, "propertychange", function() {
+ if (text.value != PLACEHOLDER)
+ setTimeout(sendText, 0);
+ });
+ }
+
+ event.addListener(text, "paste", function(e) {
+ // Mark that the next input text comes from past.
+ pasted = true;
+ // Some browsers support the event.clipboardData API. Use this to get
+ // the pasted content which increases speed if pasting a lot of lines.
+ if (window.navigator.userAgent.indexOf("Qt/") < 0 && e.clipboardData && e.clipboardData.getData) {
+ sendText(e.clipboardData.getData("text/plain"));
+ e.preventDefault();
+ }
+ else {
+ // If a browser doesn't support any of the things above, use the regular
+ // method to detect the pasted input.
+ onPropertyChange();
+ }
+ });
+
+ if ("onbeforecopy" in text && typeof clipboardData !== "undefined") {
+ event.addListener(text, "beforecopy", function(e) {
+ if (tempStyle)
+ return; // without this text is copied when contextmenu is shown
+ var copyText = host.getCopyText();
+ if (copyText)
+ clipboardData.setData("Text", copyText);
+ else
+ e.preventDefault();
+ });
+ event.addListener(parentNode, "keydown", function(e) {
+ if (e.ctrlKey && e.keyCode == 88) {
+ var copyText = host.getCopyText();
+ if (copyText) {
+ clipboardData.setData("Text", copyText);
+ host.onCut();
+ }
+ event.preventDefault(e);
+ }
+ });
+ event.addListener(text, "cut", onCut); // for ie9 context menu
+ }
+ else if (useragent.isOpera && !("KeyboardEvent" in window)) {
+ event.addListener(parentNode, "keydown", function(e) {
+ if ((useragent.isMac && !e.metaKey) || !e.ctrlKey)
+ return;
+
+ if ((e.keyCode == 88 || e.keyCode == 67)) {
+ var copyText = host.getCopyText();
+ if (copyText) {
+ text.value = copyText;
+ text.select();
+ if (e.keyCode == 88)
+ host.onCut();
+ }
+ }
+ });
+ }
+ else {
+ event.addListener(text, "copy", onCopy);
+ event.addListener(text, "cut", onCut);
+ }
+
+ event.addListener(text, "compositionstart", onCompositionStart);
+ if (useragent.isGecko) {
+ event.addListener(text, "text", onCompositionUpdate);
+ }
+ if (useragent.isWebKit) {
+ event.addListener(text, "keyup", onCompositionUpdate);
+ }
+ event.addListener(text, "compositionend", onCompositionEnd);
+
+ event.addListener(text, "blur", function() {
+ host.onBlur();
+ });
+
+ event.addListener(text, "focus", function() {
+ host.onFocus();
+ reset();
+ });
+
+ this.focus = function() {
+ reset();
+ text.focus();
+ };
+
+ this.blur = function() {
+ text.blur();
+ };
+
+ function isFocused() {
+ return document.activeElement === text;
+ }
+ this.isFocused = isFocused;
+
+ this.getElement = function() {
+ return text;
+ };
+
+ this.onContextMenu = function(e) {
+ if (!tempStyle)
+ tempStyle = text.style.cssText;
+
+ text.style.cssText =
+ "position:fixed; z-index:100000;" +
+ (useragent.isIE ? "background:rgba(0, 0, 0, 0.03); opacity:0.1;" : "") + //"background:rgba(250, 0, 0, 0.3); opacity:1;" +
+ "left:" + (e.clientX - 2) + "px; top:" + (e.clientY - 2) + "px;";
+
+ if (host.selection.isEmpty())
+ text.value = "";
+ else
+ reset(true);
+
+ if (e.type != "mousedown")
+ return;
+
+ if (host.renderer.$keepTextAreaAtCursor)
+ host.renderer.$keepTextAreaAtCursor = null;
+
+ // on windows context menu is opened after mouseup
+ if (useragent.isWin && (useragent.isGecko || useragent.isIE))
+ event.capture(host.container, function(e) {
+ text.style.left = e.clientX - 2 + "px";
+ text.style.top = e.clientY - 2 + "px";
+ }, onContextMenuClose);
+ };
+
+ function onContextMenuClose() {
+ setTimeout(function () {
+ if (tempStyle) {
+ text.style.cssText = tempStyle;
+ tempStyle = '';
+ }
+ sendText();
+ if (host.renderer.$keepTextAreaAtCursor == null) {
+ host.renderer.$keepTextAreaAtCursor = true;
+ host.renderer.$moveTextAreaToCursor();
+ }
+ }, 0);
+ };
+ this.onContextMenuClose = onContextMenuClose;
+
+ // firefox fires contextmenu event after opening it
+ if (!useragent.isGecko)
+ event.addListener(text, "contextmenu", function(e) {
+ host.textInput.onContextMenu(e);
+ onContextMenuClose()
+ });
+};
+
+exports.TextInput = TextInput;
+});
+
+define('ace/mouse/mouse_handler', ['require', 'exports', 'module' , 'ace/lib/event', 'ace/lib/useragent', 'ace/mouse/default_handlers', 'ace/mouse/default_gutter_handler', 'ace/mouse/mouse_event', 'ace/mouse/dragdrop'], function(require, exports, module) {
+
+
+var event = require("../lib/event");
+var useragent = require("../lib/useragent");
+var DefaultHandlers = require("./default_handlers").DefaultHandlers;
+var DefaultGutterHandler = require("./default_gutter_handler").GutterHandler;
+var MouseEvent = require("./mouse_event").MouseEvent;
+var DragdropHandler = require("./dragdrop").DragdropHandler;
+
+var MouseHandler = function(editor) {
+ this.editor = editor;
+
+ new DefaultHandlers(this);
+ new DefaultGutterHandler(this);
+ new DragdropHandler(this);
+
+ event.addListener(editor.container, "mousedown", function(e) {
+ editor.focus();
+ return event.preventDefault(e);
+ });
+
+ var mouseTarget = editor.renderer.getMouseEventTarget();
+ event.addListener(mouseTarget, "click", this.onMouseEvent.bind(this, "click"));
+ event.addListener(mouseTarget, "mousemove", this.onMouseMove.bind(this, "mousemove"));
+ event.addMultiMouseDownListener(mouseTarget, [300, 300, 250], this, "onMouseEvent");
+ event.addMouseWheelListener(editor.container, this.onMouseWheel.bind(this, "mousewheel"));
+
+ var gutterEl = editor.renderer.$gutter;
+ event.addListener(gutterEl, "mousedown", this.onMouseEvent.bind(this, "guttermousedown"));
+ event.addListener(gutterEl, "click", this.onMouseEvent.bind(this, "gutterclick"));
+ event.addListener(gutterEl, "dblclick", this.onMouseEvent.bind(this, "gutterdblclick"));
+ event.addListener(gutterEl, "mousemove", this.onMouseMove.bind(this, "gutter"));
+};
+
+(function() {
+
+ this.$scrollSpeed = 1;
+ this.setScrollSpeed = function(speed) {
+ this.$scrollSpeed = speed;
+ };
+
+ this.getScrollSpeed = function() {
+ return this.$scrollSpeed;
+ };
+
+ this.onMouseEvent = function(name, e) {
+ this.editor._emit(name, new MouseEvent(e, this.editor));
+ };
+
+ this.$dragDelay = 250;
+ this.setDragDelay = function(dragDelay) {
+ this.$dragDelay = dragDelay;
+ };
+
+ this.getDragDelay = function() {
+ return this.$dragDelay;
+ };
+
+ this.onMouseMove = function(name, e) {
+ // optimization, because mousemove doesn't have a default handler.
+ var listeners = this.editor._eventRegistry && this.editor._eventRegistry.mousemove;
+ if (!listeners || !listeners.length)
+ return;
+
+ this.editor._emit(name, new MouseEvent(e, this.editor));
+ };
+
+ this.onMouseWheel = function(name, e) {
+ var mouseEvent = new MouseEvent(e, this.editor);
+ mouseEvent.speed = this.$scrollSpeed * 2;
+ mouseEvent.wheelX = e.wheelX;
+ mouseEvent.wheelY = e.wheelY;
+
+ this.editor._emit(name, mouseEvent);
+ };
+
+ this.setState = function(state) {
+ this.state = state;
+ };
+
+ this.captureMouse = function(ev, state) {
+ if (state)
+ this.setState(state);
+
+ this.x = ev.x;
+ this.y = ev.y;
+
+ // do not move textarea during selection
+ var renderer = this.editor.renderer;
+ if (renderer.$keepTextAreaAtCursor)
+ renderer.$keepTextAreaAtCursor = null;
+
+ var self = this;
+ var onMouseMove = function(e) {
+ self.x = e.clientX;
+ self.y = e.clientY;
+ };
+
+ var onCaptureEnd = function(e) {
+ clearInterval(timerId);
+ self[self.state + "End"] && self[self.state + "End"](e);
+ self.$clickSelection = null;
+ if (renderer.$keepTextAreaAtCursor == null) {
+ renderer.$keepTextAreaAtCursor = true;
+ renderer.$moveTextAreaToCursor();
+ }
+ };
+
+ var onCaptureInterval = function() {
+ self[self.state] && self[self.state]();
+ }
+
+ if (useragent.isOldIE && ev.domEvent.type == "dblclick") {
+ setTimeout(function() {
+ onCaptureInterval();
+ onCaptureEnd(ev.domEvent);
+ });
+ return;
+ }
+
+ event.capture(this.editor.container, onMouseMove, onCaptureEnd);
+ var timerId = setInterval(onCaptureInterval, 20);
+ };
+}).call(MouseHandler.prototype);
+
+exports.MouseHandler = MouseHandler;
+});
+
+define('ace/mouse/default_handlers', ['require', 'exports', 'module' , 'ace/lib/dom', 'ace/lib/useragent'], function(require, exports, module) {
+
+
+var dom = require("../lib/dom");
+var useragent = require("../lib/useragent");
+
+var DRAG_OFFSET = 5; // pixels
+
+function DefaultHandlers(mouseHandler) {
+ mouseHandler.$clickSelection = null;
+
+ var editor = mouseHandler.editor;
+ editor.setDefaultHandler("mousedown", this.onMouseDown.bind(mouseHandler));
+ editor.setDefaultHandler("dblclick", this.onDoubleClick.bind(mouseHandler));
+ editor.setDefaultHandler("tripleclick", this.onTripleClick.bind(mouseHandler));
+ editor.setDefaultHandler("quadclick", this.onQuadClick.bind(mouseHandler));
+ editor.setDefaultHandler("mousewheel", this.onScroll.bind(mouseHandler));
+
+ var exports = ["select", "startSelect", "drag", "dragEnd", "dragWait",
+ "dragWaitEnd", "startDrag", "focusWait"];
+
+ exports.forEach(function(x) {
+ mouseHandler[x] = this[x];
+ }, this);
+
+ mouseHandler.selectByLines = this.extendSelectionBy.bind(mouseHandler, "getLineRange");
+ mouseHandler.selectByWords = this.extendSelectionBy.bind(mouseHandler, "getWordRange");
+
+ mouseHandler.$focusWaitTimout = 250;
+}
+
+(function() {
+
+ this.onMouseDown = function(ev) {
+ var inSelection = ev.inSelection();
+ var pos = ev.getDocumentPosition();
+ this.mousedownEvent = ev;
+ var editor = this.editor;
+
+ var button = ev.getButton();
+ if (button !== 0) {
+ var selectionRange = editor.getSelectionRange();
+ var selectionEmpty = selectionRange.isEmpty();
+
+ if (selectionEmpty) {
+ editor.moveCursorToPosition(pos);
+ editor.selection.clearSelection();
+ }
+
+ // 2: contextmenu, 1: linux paste
+ editor.textInput.onContextMenu(ev.domEvent);
+ return; // stopping event here breaks contextmenu on ff mac
+ }
+
+ // if this click caused the editor to be focused should not clear the
+ // selection
+ if (inSelection && !editor.isFocused()) {
+ editor.focus();
+ if (this.$focusWaitTimout && !this.$clickSelection) {
+ this.setState("focusWait");
+ this.captureMouse(ev);
+ return ev.preventDefault();
+ }
+ }
+
+ if (!inSelection || this.$clickSelection || ev.getShiftKey()) {
+ // Directly pick STATE_SELECT, since the user is not clicking inside
+ // a selection.
+ this.startSelect(pos);
+ } else if (inSelection) {
+ this.mousedownEvent.time = (new Date()).getTime();
+ this.setState("dragWait");
+ }
+
+ this.captureMouse(ev);
+ return ev.preventDefault();
+ };
+
+ this.startSelect = function(pos) {
+ pos = pos || this.editor.renderer.screenToTextCoordinates(this.x, this.y);
+ if (this.mousedownEvent.getShiftKey()) {
+ this.editor.selection.selectToPosition(pos);
+ }
+ else if (!this.$clickSelection) {
+ this.editor.moveCursorToPosition(pos);
+ this.editor.selection.clearSelection();
+ }
+ this.setState("select");
+ };
+
+ this.select = function() {
+ var anchor, editor = this.editor;
+ var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
+
+ if (this.$clickSelection) {
+ var cmp = this.$clickSelection.comparePoint(cursor);
+
+ if (cmp == -1) {
+ anchor = this.$clickSelection.end;
+ } else if (cmp == 1) {
+ anchor = this.$clickSelection.start;
+ } else {
+ var orientedRange = calcRangeOrientation(this.$clickSelection, cursor);
+ cursor = orientedRange.cursor;
+ anchor = orientedRange.anchor;
+ }
+ editor.selection.setSelectionAnchor(anchor.row, anchor.column);
+ }
+ editor.selection.selectToPosition(cursor);
+
+ editor.renderer.scrollCursorIntoView();
+ };
+
+ this.extendSelectionBy = function(unitName) {
+ var anchor, editor = this.editor;
+ var cursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
+ var range = editor.selection[unitName](cursor.row, cursor.column);
+
+ if (this.$clickSelection) {
+ var cmpStart = this.$clickSelection.comparePoint(range.start);
+ var cmpEnd = this.$clickSelection.comparePoint(range.end);
+
+ if (cmpStart == -1 && cmpEnd <= 0) {
+ anchor = this.$clickSelection.end;
+ if (range.end.row != cursor.row || range.end.column != cursor.column)
+ cursor = range.start;
+ } else if (cmpEnd == 1 && cmpStart >= 0) {
+ anchor = this.$clickSelection.start;
+ if (range.start.row != cursor.row || range.start.column != cursor.column)
+ cursor = range.end;
+ } else if (cmpStart == -1 && cmpEnd == 1) {
+ cursor = range.end;
+ anchor = range.start;
+ } else {
+ var orientedRange = calcRangeOrientation(this.$clickSelection, cursor);
+ cursor = orientedRange.cursor;
+ anchor = orientedRange.anchor;
+ }
+ editor.selection.setSelectionAnchor(anchor.row, anchor.column);
+ }
+ editor.selection.selectToPosition(cursor);
+
+ editor.renderer.scrollCursorIntoView();
+ };
+
+ this.startDrag = function() {
+ var editor = this.editor;
+ this.setState("drag");
+ this.dragRange = editor.getSelectionRange();
+ var style = editor.getSelectionStyle();
+ this.dragSelectionMarker = editor.session.addMarker(this.dragRange, "ace_selection", style);
+ editor.clearSelection();
+ dom.addCssClass(editor.container, "ace_dragging");
+ if (!this.$dragKeybinding) {
+ this.$dragKeybinding = {
+ handleKeyboard: function(data, hashId, keyString, keyCode) {
+ if (keyString == "esc")
+ return {command: this.command};
+ },
+ command: {
+ exec: function(editor) {
+ var self = editor.$mouseHandler;
+ self.dragCursor = null;
+ self.dragEnd();
+ self.startSelect();
+ }
+ }
+ }
+ }
+
+ editor.keyBinding.addKeyboardHandler(this.$dragKeybinding);
+ };
+
+ this.focusWait = function() {
+ var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y);
+ var time = (new Date()).getTime();
+
+ if (distance > DRAG_OFFSET ||time - this.mousedownEvent.time > this.$focusWaitTimout)
+ this.startSelect();
+ };
+
+ this.dragWait = function(e) {
+ var distance = calcDistance(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y);
+ var time = (new Date()).getTime();
+ var editor = this.editor;
+
+ if (distance > DRAG_OFFSET) {
+ this.startSelect(this.mousedownEvent.getDocumentPosition());
+ } else if (time - this.mousedownEvent.time > editor.getDragDelay()) {
+ this.startDrag();
+ }
+ };
+
+ this.dragWaitEnd = function(e) {
+ this.mousedownEvent.domEvent = e;
+ this.startSelect();
+ };
+
+ this.drag = function() {
+ var editor = this.editor;
+ this.dragCursor = editor.renderer.screenToTextCoordinates(this.x, this.y);
+ editor.moveCursorToPosition(this.dragCursor);
+ editor.renderer.scrollCursorIntoView();
+ };
+
+ this.dragEnd = function(e) {
+ var editor = this.editor;
+ var dragCursor = this.dragCursor;
+ var dragRange = this.dragRange;
+ dom.removeCssClass(editor.container, "ace_dragging");
+ editor.session.removeMarker(this.dragSelectionMarker);
+ editor.keyBinding.removeKeyboardHandler(this.$dragKeybinding);
+
+ if (!dragCursor)
+ return;
+
+ editor.clearSelection();
+ if (e && (e.ctrlKey || e.altKey)) {
+ var session = editor.session;
+ var newRange = dragRange;
+ newRange.end = session.insert(dragCursor, session.getTextRange(dragRange));
+ newRange.start = dragCursor;
+ } else if (dragRange.contains(dragCursor.row, dragCursor.column)) {
+ return;
+ } else {
+ var newRange = editor.moveText(dragRange, dragCursor);
+ }
+
+ if (!newRange)
+ return;
+
+ editor.selection.setSelectionRange(newRange);
+ };
+
+ this.onDoubleClick = function(ev) {
+ var pos = ev.getDocumentPosition();
+ var editor = this.editor;
+ var session = editor.session;
+
+ var range = session.getBracketRange(pos);
+ if (range) {
+ if (range.isEmpty()) {
+ range.start.column--;
+ range.end.column++;
+ }
+ this.$clickSelection = range;
+ this.setState("select");
+ return;
+ }
+
+ this.$clickSelection = editor.selection.getWordRange(pos.row, pos.column);
+ this.setState("selectByWords");
+ };
+
+ this.onTripleClick = function(ev) {
+ var pos = ev.getDocumentPosition();
+ var editor = this.editor;
+
+ this.setState("selectByLines");
+ this.$clickSelection = editor.selection.getLineRange(pos.row);
+ };
+
+ this.onQuadClick = function(ev) {
+ var editor = this.editor;
+
+ editor.selectAll();
+ this.$clickSelection = editor.getSelectionRange();
+ this.setState("null");
+ };
+
+ this.onScroll = function(ev) {
+ var editor = this.editor;
+ var isScrolable = editor.renderer.isScrollableBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
+ if (isScrolable) {
+ this.$passScrollEvent = false;
+ } else {
+ if (this.$passScrollEvent)
+ return;
+
+ if (!this.$scrollStopTimeout) {
+ var self = this;
+ this.$scrollStopTimeout = setTimeout(function() {
+ self.$passScrollEvent = true;
+ self.$scrollStopTimeout = null;
+ }, 200);
+ }
+ }
+
+ editor.renderer.scrollBy(ev.wheelX * ev.speed, ev.wheelY * ev.speed);
+ return ev.preventDefault();
+ };
+
+}).call(DefaultHandlers.prototype);
+
+exports.DefaultHandlers = DefaultHandlers;
+
+function calcDistance(ax, ay, bx, by) {
+ return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2));
+}
+
+function calcRangeOrientation(range, cursor) {
+ if (range.start.row == range.end.row)
+ var cmp = 2 * cursor.column - range.start.column - range.end.column;
+ else
+ var cmp = 2 * cursor.row - range.start.row - range.end.row;
+
+ if (cmp < 0)
+ return {cursor: range.start, anchor: range.end};
+ else
+ return {cursor: range.end, anchor: range.start};
+}
+
+});
+
+define('ace/mouse/default_gutter_handler', ['require', 'exports', 'module' , 'ace/lib/dom'], function(require, exports, module) {
+
+var dom = require("../lib/dom");
+
+function GutterHandler(mouseHandler) {
+ var editor = mouseHandler.editor;
+
+ mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) {
+ if (!editor.isFocused())
+ return;
+ var gutterRegion = editor.renderer.$gutterLayer.getRegion(e);
+
+ if (gutterRegion)
+ return;
+
+ var row = e.getDocumentPosition().row;
+ var selection = editor.session.selection;
+
+ if (e.getShiftKey())
+ selection.selectTo(row, 0);
+ else
+ mouseHandler.$clickSelection = editor.selection.getLineRange(row);
+
+ mouseHandler.captureMouse(e, "selectByLines");
+ return e.preventDefault();
+ });
+}
+
+exports.GutterHandler = GutterHandler;
+
+});
+
+define('ace/mouse/mouse_event', ['require', 'exports', 'module' , 'ace/lib/event', 'ace/lib/useragent'], function(require, exports, module) {
+
+
+var event = require("../lib/event");
+var useragent = require("../lib/useragent");
+var MouseEvent = exports.MouseEvent = function(domEvent, editor) {
+ this.domEvent = domEvent;
+ this.editor = editor;
+
+ this.x = this.clientX = domEvent.clientX;
+ this.y = this.clientY = domEvent.clientY;
+
+ this.$pos = null;
+ this.$inSelection = null;
+
+ this.propagationStopped = false;
+ this.defaultPrevented = false;
+};
+
+(function() {
+
+ this.stopPropagation = function() {
+ event.stopPropagation(this.domEvent);
+ this.propagationStopped = true;
+ };
+
+ this.preventDefault = function() {
+ event.preventDefault(this.domEvent);
+ this.defaultPrevented = true;
+ };
+
+ this.stop = function() {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+ this.getDocumentPosition = function() {
+ if (this.$pos)
+ return this.$pos;
+
+ this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY);
+ return this.$pos;
+ };
+ this.inSelection = function() {
+ if (this.$inSelection !== null)
+ return this.$inSelection;
+
+ var editor = this.editor;
+
+ if (editor.getReadOnly()) {
+ this.$inSelection = false;
+ }
+ else {
+ var selectionRange = editor.getSelectionRange();
+ if (selectionRange.isEmpty())
+ this.$inSelection = false;
+ else {
+ var pos = this.getDocumentPosition();
+ this.$inSelection = selectionRange.contains(pos.row, pos.column);
+ }
+ }
+ return this.$inSelection;
+ };
+ this.getButton = function() {
+ return event.getButton(this.domEvent);
+ };
+ this.getShiftKey = function() {
+ return this.domEvent.shiftKey;
+ };
+
+ this.getAccelKey = useragent.isMac
+ ? function() { return this.domEvent.metaKey; }
+ : function() { return this.domEvent.ctrlKey; };
+
+}).call(MouseEvent.prototype);
+
+});
+
+define('ace/mouse/dragdrop', ['require', 'exports', 'module' , 'ace/lib/event'], function(require, exports, module) {
+
+
+var event = require("../lib/event");
+
+var DragdropHandler = function(mouseHandler) {
+ var editor = mouseHandler.editor;
+ var dragSelectionMarker, x, y;
+ var timerId, range, isBackwards;
+ var dragCursor, counter = 0;
+
+ var mouseTarget = editor.container;
+ event.addListener(mouseTarget, "dragenter", function(e) {
+ counter++;
+ if (!dragSelectionMarker) {
+ range = editor.getSelectionRange();
+ isBackwards = editor.selection.isBackwards();
+ var style = editor.getSelectionStyle();
+ dragSelectionMarker = editor.session.addMarker(range, "ace_selection", style);
+ editor.clearSelection();
+ clearInterval(timerId);
+ timerId = setInterval(onDragInterval, 20);
+ }
+ return event.preventDefault(e);
+ });
+
+ event.addListener(mouseTarget, "dragover", function(e) {
+ x = e.clientX;
+ y = e.clientY;
+ return event.preventDefault(e);
+ });
+
+ var onDragInterval = function() {
+ dragCursor = editor.renderer.screenToTextCoordinates(x, y);
+ editor.moveCursorToPosition(dragCursor);
+ editor.renderer.scrollCursorIntoView();
+ };
+
+ event.addListener(mouseTarget, "dragleave", function(e) {
+ counter--;
+ if (counter > 0)
+ return;
+ console.log(e.type, counter,e.target);
+ clearInterval(timerId);
+ editor.session.removeMarker(dragSelectionMarker);
+ dragSelectionMarker = null;
+ editor.selection.setSelectionRange(range, isBackwards);
+ return event.preventDefault(e);
+ });
+
+ event.addListener(mouseTarget, "drop", function(e) {
+ console.log(e.type, counter,e.target);
+ counter = 0;
+ clearInterval(timerId);
+ editor.session.removeMarker(dragSelectionMarker);
+ dragSelectionMarker = null;
+
+ range.end = editor.session.insert(dragCursor, e.dataTransfer.getData('Text'));
+ range.start = dragCursor;
+ editor.focus();
+ editor.selection.setSelectionRange(range);
+ return event.preventDefault(e);
+ });
+
+};
+
+exports.DragdropHandler = DragdropHandler;
+});
+
+define('ace/mouse/fold_handler', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+function FoldHandler(editor) {
+
+ editor.on("click", function(e) {
+ var position = e.getDocumentPosition();
+ var session = editor.session;
+
+ // If the user clicked on a fold, then expand it.
+ var fold = session.getFoldAt(position.row, position.column, 1);
+ if (fold) {
+ if (e.getAccelKey())
+ session.removeFold(fold);
+ else
+ session.expandFold(fold);
+
+ e.stop();
+ }
+ });
+
+ editor.on("gutterclick", function(e) {
+ var gutterRegion = editor.renderer.$gutterLayer.getRegion(e);
+
+ if (gutterRegion == "foldWidgets") {
+ var row = e.getDocumentPosition().row;
+ var session = editor.session;
+ if (session.foldWidgets && session.foldWidgets[row])
+ editor.session.onFoldWidgetClick(row, e);
+ e.stop();
+ }
+ });
+}
+
+exports.FoldHandler = FoldHandler;
+
+});
+
+define('ace/keyboard/keybinding', ['require', 'exports', 'module' , 'ace/lib/keys', 'ace/lib/event'], function(require, exports, module) {
+
+
+var keyUtil = require("../lib/keys");
+var event = require("../lib/event");
+
+var KeyBinding = function(editor) {
+ this.$editor = editor;
+ this.$data = { };
+ this.$handlers = [];
+ this.setDefaultHandler(editor.commands);
+};
+
+(function() {
+ this.setDefaultHandler = function(kb) {
+ this.removeKeyboardHandler(this.$defaultHandler);
+ this.$defaultHandler = kb;
+ this.addKeyboardHandler(kb, 0);
+ this.$data = {editor: this.$editor};
+ };
+
+ this.setKeyboardHandler = function(kb) {
+ if (this.$handlers[this.$handlers.length - 1] == kb)
+ return;
+
+ while (this.$handlers[1])
+ this.removeKeyboardHandler(this.$handlers[1]);
+
+ this.addKeyboardHandler(kb, 1);
+ };
+
+ this.addKeyboardHandler = function(kb, pos) {
+ if (!kb)
+ return;
+ var i = this.$handlers.indexOf(kb);
+ if (i != -1)
+ this.$handlers.splice(i, 1);
+
+ if (pos == undefined)
+ this.$handlers.push(kb);
+ else
+ this.$handlers.splice(pos, 0, kb);
+
+ if (i == -1 && kb.attach)
+ kb.attach(this.$editor);
+ };
+
+ this.removeKeyboardHandler = function(kb) {
+ var i = this.$handlers.indexOf(kb);
+ if (i == -1)
+ return false;
+ this.$handlers.splice(i, 1);
+ kb.detach && kb.detach(this.$editor);
+ return true;
+ };
+
+ this.getKeyboardHandler = function() {
+ return this.$handlers[this.$handlers.length - 1];
+ };
+
+ this.$callKeyboardHandlers = function (hashId, keyString, keyCode, e) {
+ var toExecute;
+ for (var i = this.$handlers.length; i--;) {
+ toExecute = this.$handlers[i].handleKeyboard(
+ this.$data, hashId, keyString, keyCode, e
+ );
+ if (toExecute && toExecute.command)
+ break;
+ }
+
+ if (!toExecute || !toExecute.command)
+ return false;
+
+ var success = false;
+ var commands = this.$editor.commands;
+
+ // allow keyboardHandler to consume keys
+ if (toExecute.command != "null")
+ success = commands.exec(toExecute.command, this.$editor, toExecute.args, e);
+ else
+ success = toExecute.passEvent != true;
+
+ // do not stop input events to not break repeating
+ if (success && e && hashId != -1)
+ event.stopEvent(e);
+
+ return success;
+ };
+
+ this.onCommandKey = function(e, hashId, keyCode) {
+ var keyString = keyUtil.keyCodeToString(keyCode);
+ this.$callKeyboardHandlers(hashId, keyString, keyCode, e);
+ };
+
+ this.onTextInput = function(text) {
+ if (window.desktop)
+ text = window.desktop.filterText(text);
+ var success = this.$callKeyboardHandlers(-1, text);
+ if (!success)
+ this.$editor.commands.exec("insertstring", this.$editor, text);
+ };
+
+}).call(KeyBinding.prototype);
+
+exports.KeyBinding = KeyBinding;
+});
+
+define('ace/keyboard/emacs', ['require', 'exports', 'module' , 'ace/lib/dom', 'ace/keyboard/hash_handler', 'ace/lib/keys'], function(require, exports, module) {
+
+
+var dom = require("../lib/dom");
+
+var screenToTextBlockCoordinates = function(pageX, pageY) {
+ var canvasPos = this.scroller.getBoundingClientRect();
+
+ var col = Math.floor(
+ (pageX + this.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft()) / this.characterWidth
+ );
+ var row = Math.floor(
+ (pageY + this.scrollTop - canvasPos.top - dom.getPageScrollTop()) / this.lineHeight
+ );
+
+ return this.session.screenToDocumentPosition(row, col);
+};
+
+var HashHandler = require("./hash_handler").HashHandler;
+exports.handler = new HashHandler();
+
+var initialized = false;
+exports.handler.attach = function(editor) {
+ if (!initialized) {
+ initialized = true;
+ dom.importCssString('\
+ .emacs-mode .ace_cursor{\
+ border: 2px rgba(50,250,50,0.8) solid!important;\
+ -moz-box-sizing: border-box!important;\
+ box-sizing: border-box!important;\
+ background-color: rgba(0,250,0,0.9);\
+ opacity: 0.5;\
+ }\
+ .emacs-mode .ace_cursor.ace_hidden{\
+ opacity: 1;\
+ background-color: transparent;\
+ }\
+ .emacs-mode .ace_cursor.ace_overwrite {\
+ opacity: 1;\
+ background-color: transparent;\
+ border-width: 0 0 2px 2px !important;\
+ }\
+ .emacs-mode .ace_text-layer {\
+ z-index: 4\
+ }\
+ .emacs-mode .ace_cursor-layer {\
+ z-index: 2\
+ }', 'emacsMode'
+ );
+ }
+
+ editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
+ editor.setStyle("emacs-mode");
+};
+
+exports.handler.detach = function(editor) {
+ delete editor.renderer.screenToTextCoordinates;
+ editor.unsetStyle("emacs-mode");
+};
+
+
+var keys = require("../lib/keys").KEY_MODS;
+var eMods = {
+ C: "ctrl", S: "shift", M: "alt"
+};
+["S-C-M", "S-C", "S-M", "C-M", "S", "C", "M"].forEach(function(c) {
+ var hashId = 0;
+ c.split("-").forEach(function(c){
+ hashId = hashId | keys[eMods[c]];
+ });
+ eMods[hashId] = c.toLowerCase() + "-";
+});
+
+exports.handler.bindKey = function(key, command) {
+ if (!key)
+ return;
+
+ var ckb = this.commmandKeyBinding;
+ key.split("|").forEach(function(keyPart) {
+ keyPart = keyPart.toLowerCase();
+ ckb[keyPart] = command;
+ keyPart = keyPart.split(" ")[0];
+ if (!ckb[keyPart])
+ ckb[keyPart] = "null";
+ }, this);
+};
+
+
+exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
+ if (hashId == -1) {
+ if (data.count) {
+ var str = Array(data.count + 1).join(key);
+ data.count = null;
+ return {command: "insertstring", args: str};
+ }
+ }
+
+ if (key == "\x00")
+ return;
+
+ var modifier = eMods[hashId];
+ if (modifier == "c-" || data.universalArgument) {
+ var count = parseInt(key[key.length - 1]);
+ if (count) {
+ data.count = count;
+ return {command: "null"};
+ }
+ }
+ data.universalArgument = false;
+
+ if (modifier)
+ key = modifier + key;
+
+ if (data.keyChain)
+ key = data.keyChain += " " + key;
+
+ var command = this.commmandKeyBinding[key];
+ data.keyChain = command == "null" ? key : "";
+
+ if (!command)
+ return;
+
+ if (command == "null")
+ return {command: "null"};
+
+ if (command == "universalArgument") {
+ data.universalArgument = true;
+ return {command: "null"};
+ }
+
+ if (typeof command != "string") {
+ var args = command.args;
+ command = command.command;
+ }
+
+ if (typeof command == "string") {
+ command = this.commands[command] || data.editor.commands.commands[command];
+ }
+
+ if (!command.readonly && !command.isYank)
+ data.lastCommand = null;
+
+ if (data.count) {
+ var count = data.count;
+ data.count = 0;
+ return {
+ args: args,
+ command: {
+ exec: function(editor, args) {
+ for (var i = 0; i < count; i++)
+ command.exec(editor, args);
+ }
+ }
+ };
+ }
+
+ return {command: command, args: args};
+};
+
+exports.emacsKeys = {
+ // movement
+ "Up|C-p" : "golineup",
+ "Down|C-n" : "golinedown",
+ "Left|C-b" : "gotoleft",
+ "Right|C-f" : "gotoright",
+ "C-Left|M-b" : "gotowordleft",
+ "C-Right|M-f" : "gotowordright",
+ "Home|C-a" : "gotolinestart",
+ "End|C-e" : "gotolineend",
+ "C-Home|S-M-,": "gotostart",
+ "C-End|S-M-." : "gotoend",
+
+ // selection
+ "S-Up|S-C-p" : "selectup",
+ "S-Down|S-C-n" : "selectdown",
+ "S-Left|S-C-b" : "selectleft",
+ "S-Right|S-C-f" : "selectright",
+ "S-C-Left|S-M-b" : "selectwordleft",
+ "S-C-Right|S-M-f" : "selectwordright",
+ "S-Home|S-C-a" : "selecttolinestart",
+ "S-End|S-C-e" : "selecttolineend",
+ "S-C-Home" : "selecttostart",
+ "S-C-End" : "selecttoend",
+
+ "C-l|M-s" : "centerselection",
+ "M-g": "gotoline",
+ "C-x C-p": "selectall",
+
+ // todo fix these
+ "C-Down": "gotopagedown",
+ "C-Up": "gotopageup",
+ "PageDown|C-v": "gotopagedown",
+ "PageUp|M-v": "gotopageup",
+ "S-C-Down": "selectpagedown",
+ "S-C-Up": "selectpageup",
+ "C-s": "findnext",
+ "C-r": "findprevious",
+ "M-C-s": "findnext",
+ "M-C-r": "findprevious",
+ "S-M-5": "replace",
+
+ // basic editing
+ "Backspace": "backspace",
+ "Delete|C-d": "del",
+ "Return|C-m": {command: "insertstring", args: "\n"}, // "newline"
+ "C-o": "splitline",
+
+ "M-d|C-Delete": {command: "killWord", args: "right"},
+ "C-Backspace|M-Backspace|M-Delete": {command: "killWord", args: "left"},
+ "C-k": "killLine",
+
+ "C-y|S-Delete": "yank",
+ "M-y": "yankRotate",
+ "C-g": "keyboardQuit",
+
+ "C-w": "killRegion",
+ "M-w": "killRingSave",
+
+ "C-Space": "setMark",
+ "C-x C-x": "exchangePointAndMark",
+
+ "C-t": "transposeletters",
+
+ "M-u": "touppercase",
+ "M-l": "tolowercase",
+ "M-/": "autocomplete",
+ "C-u": "universalArgument",
+ "M-;": "togglecomment",
+
+ "C-/|C-x u|S-C--|C-z": "undo",
+ "S-C-/|S-C-x u|C--|S-C-z": "redo", //infinite undo?
+ // vertical editing
+ "C-x r": "selectRectangularRegion"
+
+ // todo
+ // "M-x" "C-x C-t" "M-t" "M-c" "F11" "C-M- "M-q"
+};
+
+
+exports.handler.bindKeys(exports.emacsKeys);
+
+exports.handler.addCommands({
+ selectRectangularRegion: function(editor) {
+ editor.multiSelect.toggleBlockSelection();
+ },
+ setMark: function() {
+ },
+ exchangePointAndMark: {
+ exec: function(editor) {
+ var range = editor.selection.getRange();
+ editor.selection.setSelectionRange(range, !editor.selection.isBackwards());
+ },
+ readonly: true,
+ multiselectAction: "forEach"
+ },
+ killWord: {
+ exec: function(editor, dir) {
+ editor.clearSelection();
+ if (dir == "left")
+ editor.selection.selectWordLeft();
+ else
+ editor.selection.selectWordRight();
+
+ var range = editor.getSelectionRange();
+ var text = editor.session.getTextRange(range);
+ exports.killRing.add(text);
+
+ editor.session.remove(range);
+ editor.clearSelection();
+ },
+ multiselectAction: "forEach"
+ },
+ killLine: function(editor) {
+ editor.selection.selectLine();
+ var range = editor.getSelectionRange();
+ var text = editor.session.getTextRange(range);
+ exports.killRing.add(text);
+
+ editor.session.remove(range);
+ editor.clearSelection();
+ },
+ yank: function(editor) {
+ editor.onPaste(exports.killRing.get());
+ editor.keyBinding.$data.lastCommand = "yank";
+ },
+ yankRotate: function(editor) {
+ if (editor.keyBinding.$data.lastCommand != "yank")
+ return;
+
+ editor.undo();
+ editor.onPaste(exports.killRing.rotate());
+ editor.keyBinding.$data.lastCommand = "yank";
+ },
+ killRegion: function(editor) {
+ exports.killRing.add(editor.getCopyText());
+ editor.cut();
+ },
+ killRingSave: function(editor) {
+ exports.killRing.add(editor.getCopyText());
+ }
+});
+
+var commands = exports.handler.commands;
+commands.yank.isYank = true;
+commands.yankRotate.isYank = true;
+
+exports.killRing = {
+ $data: [],
+ add: function(str) {
+ str && this.$data.push(str);
+ if (this.$data.length > 30)
+ this.$data.shift();
+ },
+ get: function() {
+ return this.$data[this.$data.length - 1] || "";
+ },
+ pop: function() {
+ if (this.$data.length > 1)
+ this.$data.pop();
+ return this.get();
+ },
+ rotate: function() {
+ this.$data.unshift(this.$data.pop());
+ return this.get();
+ }
+};
+
+
+});
+
+define('ace/keyboard/hash_handler', ['require', 'exports', 'module' , 'ace/lib/keys'], function(require, exports, module) {
+
+
+var keyUtil = require("../lib/keys");
+
+function HashHandler(config, platform) {
+ this.platform = platform;
+ this.commands = {};
+ this.commmandKeyBinding = {};
+
+ this.addCommands(config);
+};
+
+(function() {
+
+ this.addCommand = function(command) {
+ if (this.commands[command.name])
+ this.removeCommand(command);
+
+ this.commands[command.name] = command;
+
+ if (command.bindKey)
+ this._buildKeyHash(command);
+ };
+
+ this.removeCommand = function(command) {
+ var name = (typeof command === 'string' ? command : command.name);
+ command = this.commands[name];
+ delete this.commands[name];
+
+ // exhaustive search is brute force but since removeCommand is
+ // not a performance critical operation this should be OK
+ var ckb = this.commmandKeyBinding;
+ for (var hashId in ckb) {
+ for (var key in ckb[hashId]) {
+ if (ckb[hashId][key] == command)
+ delete ckb[hashId][key];
+ }
+ }
+ };
+
+ this.bindKey = function(key, command) {
+ if(!key)
+ return;
+ if (typeof command == "function") {
+ this.addCommand({exec: command, bindKey: key, name: key});
+ return;
+ }
+
+ var ckb = this.commmandKeyBinding;
+ key.split("|").forEach(function(keyPart) {
+ var binding = this.parseKeys(keyPart, command);
+ var hashId = binding.hashId;
+ (ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command;
+ }, this);
+ };
+
+ this.addCommands = function(commands) {
+ commands && Object.keys(commands).forEach(function(name) {
+ var command = commands[name];
+ if (typeof command === "string")
+ return this.bindKey(command, name);
+
+ if (typeof command === "function")
+ command = { exec: command };
+
+ if (!command.name)
+ command.name = name;
+
+ this.addCommand(command);
+ }, this);
+ };
+
+ this.removeCommands = function(commands) {
+ Object.keys(commands).forEach(function(name) {
+ this.removeCommand(commands[name]);
+ }, this);
+ };
+
+ this.bindKeys = function(keyList) {
+ Object.keys(keyList).forEach(function(key) {
+ this.bindKey(key, keyList[key]);
+ }, this);
+ };
+
+ this._buildKeyHash = function(command) {
+ var binding = command.bindKey;
+ if (!binding)
+ return;
+
+ var key = typeof binding == "string" ? binding: binding[this.platform];
+ this.bindKey(key, command);
+ };
+
+ this.parseKeys = function(keys) {
+ var key;
+ var hashId = 0;
+ var parts = keys.toLowerCase().trim().split(/\s*\-\s*/);
+
+ for (var i = 0, l = parts.length; i < l; i++) {
+ if (keyUtil.KEY_MODS[parts[i]])
+ hashId = hashId | keyUtil.KEY_MODS[parts[i]];
+ else
+ key = parts[i] || "-"; //when empty, the splitSafe removed a '-'
+ }
+
+ if (parts[0] == "text" && parts.length == 2) {
+ hashId = -1;
+ key = parts[1];
+ }
+
+ return {
+ key: key,
+ hashId: hashId
+ };
+ };
+
+ this.findKeyCommand = function findKeyCommand(hashId, keyString) {
+ var ckbr = this.commmandKeyBinding;
+ return ckbr[hashId] && ckbr[hashId][keyString.toLowerCase()];
+ };
+
+ this.handleKeyboard = function(data, hashId, keyString, keyCode) {
+ return {
+ command: this.findKeyCommand(hashId, keyString)
+ };
+ };
+
+}).call(HashHandler.prototype)
+
+exports.HashHandler = HashHandler;
+});
+
+define('ace/keyboard/vim', ['require', 'exports', 'module' , 'ace/lib/keys', 'ace/keyboard/vim/commands', 'ace/keyboard/vim/maps/util'], function(require, exports, module) {
+
+
+var keyUtil = require("../lib/keys");
+var cmds = require("./vim/commands");
+var coreCommands = cmds.coreCommands;
+var util = require("./vim/maps/util");
+
+var startCommands = {
+ "i": {
+ command: coreCommands.start
+ },
+ "I": {
+ command: coreCommands.startBeginning
+ },
+ "a": {
+ command: coreCommands.append
+ },
+ "A": {
+ command: coreCommands.appendEnd
+ },
+ "ctrl-f": {
+ command: "gotopagedown"
+ },
+ "ctrl-b": {
+ command: "gotopageup"
+ }
+};
+
+exports.handler = {
+ handleKeyboard: function(data, hashId, key, keyCode, e) {
+ // ignore command keys (shift, ctrl etc.)
+ if (hashId != 0 && (key == "" || key == "\x00"))
+ return null;
+
+ if (hashId == 1)
+ key = "ctrl-" + key;
+
+ if ((key == "esc" && hashId == 0) || key == "ctrl-[") {
+ return {command: coreCommands.stop};
+ } else if (data.state == "start") {
+ if (hashId == -1 || hashId == 1) {
+ if (cmds.inputBuffer.idle && startCommands[key])
+ return startCommands[key];
+
+ return { command: {
+ exec: function(editor) {cmds.inputBuffer.push(editor, key);}
+ } };
+ } // wait for input
+ else if (key.length == 1 && (hashId == 0 || hashId == 4)) { //no modifier || shift
+ return {command: "null", passEvent: true};
+ } else if (key == "esc") {
+ return {command: coreCommands.stop};
+ }
+ } else {
+ if (key == "ctrl-w") {
+ return {command: "removewordleft"};
+ }
+ }
+ },
+
+ attach: function(editor) {
+ editor.on("click", exports.onCursorMove);
+ if (util.currentMode !== "insert")
+ cmds.coreCommands.stop.exec(editor);
+ },
+
+ detach: function(editor) {
+ editor.removeListener("click", exports.onCursorMove);
+ util.noMode(editor);
+ util.currentMode = "normal";
+ },
+
+ actions: cmds.actions
+};
+
+
+exports.onCursorMove = function(e) {
+ cmds.onCursorMove(e.editor, e);
+ exports.onCursorMove.scheduled = false;
+};
+
+});
+
+define('ace/keyboard/vim/commands', ['require', 'exports', 'module' , 'ace/keyboard/vim/maps/util', 'ace/keyboard/vim/maps/motions', 'ace/keyboard/vim/maps/operators', 'ace/keyboard/vim/maps/aliases', 'ace/keyboard/vim/registers'], function(require, exports, module) {
+
+"never use strict";
+
+var util = require("./maps/util");
+var motions = require("./maps/motions");
+var operators = require("./maps/operators");
+var alias = require("./maps/aliases");
+var registers = require("./registers");
+
+var NUMBER = 1;
+var OPERATOR = 2;
+var MOTION = 3;
+var ACTION = 4;
+var HMARGIN = 8; // Minimum amount of line separation between margins;
+
+var repeat = function repeat(fn, count, args) {
+ while (0 < count--)
+ fn.apply(this, args);
+};
+
+var ensureScrollMargin = function(editor) {
+ var renderer = editor.renderer;
+ var pos = renderer.$cursorLayer.getPixelPosition();
+
+ var top = pos.top;
+
+ var margin = HMARGIN * renderer.layerConfig.lineHeight;
+ if (2 * margin > renderer.$size.scrollerHeight)
+ margin = renderer.$size.scrollerHeight / 2;
+
+ if (renderer.scrollTop > top - margin) {
+ renderer.session.setScrollTop(top - margin);
+ }
+
+ if (renderer.scrollTop + renderer.$size.scrollerHeight < top + margin + renderer.lineHeight) {
+ renderer.session.setScrollTop(top + margin + renderer.lineHeight - renderer.$size.scrollerHeight);
+ }
+};
+
+var actions = exports.actions = {
+ "z": {
+ param: true,
+ fn: function(editor, range, count, param) {
+ switch (param) {
+ case "z":
+ editor.alignCursor(null, 0.5);
+ break;
+ case "t":
+ editor.alignCursor(null, 0);
+ break;
+ case "b":
+ editor.alignCursor(null, 1);
+ break;
+ }
+ }
+ },
+ "r": {
+ param: true,
+ fn: function(editor, range, count, param) {
+ if (param && param.length) {
+ repeat(function() { editor.insert(param); }, count || 1);
+ editor.navigateLeft();
+ }
+ }
+ },
+ "R": {
+ fn: function(editor, range, count, param) {
+ util.insertMode(editor);
+ editor.setOverwrite(true);
+ }
+ },
+ "~": {
+ fn: function(editor, range, count) {
+ repeat(function() {
+ var range = editor.selection.getRange();
+ if (range.isEmpty())
+ range.end.column++;
+ var text = editor.session.getTextRange(range);
+ var toggled = text.toUpperCase();
+ if (toggled == text)
+ editor.navigateRight();
+ else
+ editor.session.replace(range, toggled);
+ }, count || 1);
+ }
+ },
+ "*": {
+ fn: function(editor, range, count, param) {
+ editor.selection.selectWord();
+ editor.findNext();
+ ensureScrollMargin(editor);
+ var r = editor.selection.getRange();
+ editor.selection.setSelectionRange(r, true);
+ }
+ },
+ "#": {
+ fn: function(editor, range, count, param) {
+ editor.selection.selectWord();
+ editor.findPrevious();
+ ensureScrollMargin(editor);
+ var r = editor.selection.getRange();
+ editor.selection.setSelectionRange(r, true);
+ }
+ },
+ "n": {
+ fn: function(editor, range, count, param) {
+ var options = editor.getLastSearchOptions();
+ options.backwards = false;
+
+ editor.selection.moveCursorRight();
+ editor.selection.clearSelection();
+ editor.findNext(options);
+
+ ensureScrollMargin(editor);
+ var r = editor.selection.getRange();
+ r.end.row = r.start.row;
+ r.end.column = r.start.column;
+ editor.selection.setSelectionRange(r, true);
+ }
+ },
+ "N": {
+ fn: function(editor, range, count, param) {
+ var options = editor.getLastSearchOptions();
+ options.backwards = true;
+
+ editor.findPrevious(options);
+ ensureScrollMargin(editor);
+ var r = editor.selection.getRange();
+ r.end.row = r.start.row;
+ r.end.column = r.start.column;
+ editor.selection.setSelectionRange(r, true);
+ }
+ },
+ "v": {
+ fn: function(editor, range, count, param) {
+ editor.selection.selectRight();
+ util.visualMode(editor, false);
+ },
+ acceptsMotion: true
+ },
+ "V": {
+ fn: function(editor, range, count, param) {
+ //editor.selection.selectLine();
+ //editor.selection.selectLeft();
+ var row = editor.getCursorPosition().row;
+ editor.selection.clearSelection();
+ editor.selection.moveCursorTo(row, 0);
+ editor.selection.selectLineEnd();
+ editor.selection.visualLineStart = row;
+
+ util.visualMode(editor, true);
+ },
+ acceptsMotion: true
+ },
+ "Y": {
+ fn: function(editor, range, count, param) {
+ util.copyLine(editor);
+ }
+ },
+ "p": {
+ fn: function(editor, range, count, param) {
+ var defaultReg = registers._default;
+
+ editor.setOverwrite(false);
+ if (defaultReg.isLine) {
+ var pos = editor.getCursorPosition();
+ var lines = defaultReg.text.split("\n");
+ editor.session.getDocument().insertLines(pos.row + 1, lines);
+ editor.moveCursorTo(pos.row + 1, 0);
+ }
+ else {
+ editor.navigateRight();
+ editor.insert(defaultReg.text);
+ editor.navigateLeft();
+ }
+ editor.setOverwrite(true);
+ editor.selection.clearSelection();
+ }
+ },
+ "P": {
+ fn: function(editor, range, count, param) {
+ var defaultReg = registers._default;
+ editor.setOverwrite(false);
+
+ if (defaultReg.isLine) {
+ var pos = editor.getCursorPosition();
+ var lines = defaultReg.text.split("\n");
+ editor.session.getDocument().insertLines(pos.row, lines);
+ editor.moveCursorTo(pos.row, 0);
+ }
+ else {
+ editor.insert(defaultReg.text);
+ }
+ editor.setOverwrite(true);
+ editor.selection.clearSelection();
+ }
+ },
+ "J": {
+ fn: function(editor, range, count, param) {
+ var session = editor.session;
+ range = editor.getSelectionRange();
+ var pos = {row: range.start.row, column: range.start.column};
+ count = count || range.end.row - range.start.row;
+ var maxRow = Math.min(pos.row + (count || 1), session.getLength() - 1);
+
+ range.start.column = session.getLine(pos.row).length;
+ range.end.column = session.getLine(maxRow).length;
+ range.end.row = maxRow;
+
+ var text = "";
+ for (var i = pos.row; i < maxRow; i++) {
+ var nextLine = session.getLine(i + 1);
+ text += " " + /^\s*(.*)$/.exec(nextLine)[1] || "";
+ }
+
+ session.replace(range, text);
+ editor.moveCursorTo(pos.row, pos.column);
+ }
+ },
+ "u": {
+ fn: function(editor, range, count, param) {
+ count = parseInt(count || 1, 10);
+ for (var i = 0; i < count; i++) {
+ editor.undo();
+ }
+ editor.selection.clearSelection();
+ }
+ },
+ "ctrl-r": {
+ fn: function(editor, range, count, param) {
+ count = parseInt(count || 1, 10);
+ for (var i = 0; i < count; i++) {
+ editor.redo();
+ }
+ editor.selection.clearSelection();
+ }
+ },
+ ":": {
+ fn: function(editor, range, count, param) {
+ // not implemented
+ }
+ },
+ "/": {
+ fn: function(editor, range, count, param) {
+ // not implemented
+ }
+ },
+ "?": {
+ fn: function(editor, range, count, param) {
+ // not implemented
+ }
+ },
+ ".": {
+ fn: function(editor, range, count, param) {
+ util.onInsertReplaySequence = inputBuffer.lastInsertCommands;
+ var previous = inputBuffer.previous;
+ if (previous) // If there is a previous action
+ inputBuffer.exec(editor, previous.action, previous.param);
+ }
+ }
+};
+
+var inputBuffer = exports.inputBuffer = {
+ accepting: [NUMBER, OPERATOR, MOTION, ACTION],
+ currentCmd: null,
+ //currentMode: 0,
+ currentCount: "",
+
+ // Types
+ operator: null,
+ motion: null,
+
+ lastInsertCommands: [],
+
+ push: function(editor, char, keyId) {
+ this.idle = false;
+ var wObj = this.waitingForParam;
+ if (wObj) {
+ this.exec(editor, wObj, char);
+ }
+ // If input is a number (that doesn't start with 0)
+ else if (!(char === "0" && !this.currentCount.length) &&
+ (char.match(/^\d+$/) && this.isAccepting(NUMBER))) {
+ // Assuming that char is always of type String, and not Number
+ this.currentCount += char;
+ this.currentCmd = NUMBER;
+ this.accepting = [NUMBER, OPERATOR, MOTION, ACTION];
+ }
+ else if (!this.operator && this.isAccepting(OPERATOR) && operators[char]) {
+ this.operator = {
+ char: char,
+ count: this.getCount()
+ };
+ this.currentCmd = OPERATOR;
+ this.accepting = [NUMBER, MOTION, ACTION];
+ this.exec(editor, { operator: this.operator });
+ }
+ else if (motions[char] && this.isAccepting(MOTION)) {
+ this.currentCmd = MOTION;
+
+ var ctx = {
+ operator: this.operator,
+ motion: {
+ char: char,
+ count: this.getCount()
+ }
+ };
+
+ if (motions[char].param)
+ this.waitForParam(ctx);
+ else
+ this.exec(editor, ctx);
+ }
+ else if (alias[char] && this.isAccepting(MOTION)) {
+ alias[char].operator.count = this.getCount();
+ this.exec(editor, alias[char]);
+ }
+ else if (actions[char] && this.isAccepting(ACTION)) {
+ var actionObj = {
+ action: {
+ fn: actions[char].fn,
+ count: this.getCount()
+ }
+ };
+
+ if (actions[char].param) {
+ this.waitForParam(actionObj);
+ }
+ else {
+ this.exec(editor, actionObj);
+ }
+
+ if (actions[char].acceptsMotion)
+ this.idle = false;
+ }
+ else if (this.operator) {
+ this.exec(editor, { operator: this.operator }, char);
+ }
+ else {
+ this.reset();
+ }
+ },
+
+ waitForParam: function(cmd) {
+ this.waitingForParam = cmd;
+ },
+
+ getCount: function() {
+ var count = this.currentCount;
+ this.currentCount = "";
+ return count && parseInt(count, 10);
+ },
+
+ exec: function(editor, action, param) {
+ var m = action.motion;
+ var o = action.operator;
+ var a = action.action;
+
+ if (!param)
+ param = action.param;
+
+ if (o) {
+ this.previous = {
+ action: action,
+ param: param
+ };
+ }
+
+ if (o && !editor.selection.isEmpty()) {
+ if (operators[o.char].selFn) {
+ operators[o.char].selFn(editor, editor.getSelectionRange(), o.count, param);
+ this.reset();
+ }
+ return;
+ }
+
+ // There is an operator, but no motion or action. We try to pass the
+ // current char to the operator to see if it responds to it (an example
+ // of this is the 'dd' operator).
+ else if (!m && !a && o && param) {
+ operators[o.char].fn(editor, null, o.count, param);
+ this.reset();
+ }
+ else if (m) {
+ var run = function(fn) {
+ if (fn && typeof fn === "function") { // There should always be a motion
+ if (m.count && !motionObj.handlesCount)
+ repeat(fn, m.count, [editor, null, m.count, param]);
+ else
+ fn(editor, null, m.count, param);
+ }
+ };
+
+ var motionObj = motions[m.char];
+ var selectable = motionObj.sel;
+
+ if (!o) {
+ if ((util.onVisualMode || util.onVisualLineMode) && selectable)
+ run(motionObj.sel);
+ else
+ run(motionObj.nav);
+ }
+ else if (selectable) {
+ repeat(function() {
+ run(motionObj.sel);
+ operators[o.char].fn(editor, editor.getSelectionRange(), o.count, param);
+ }, o.count || 1);
+ }
+ this.reset();
+ }
+ else if (a) {
+ a.fn(editor, editor.getSelectionRange(), a.count, param);
+ this.reset();
+ }
+ handleCursorMove(editor);
+ },
+
+ isAccepting: function(type) {
+ return this.accepting.indexOf(type) !== -1;
+ },
+
+ reset: function() {
+ this.operator = null;
+ this.motion = null;
+ this.currentCount = "";
+ this.accepting = [NUMBER, OPERATOR, MOTION, ACTION];
+ this.idle = true;
+ this.waitingForParam = null;
+ }
+};
+
+function setPreviousCommand(fn) {
+ inputBuffer.previous = { action: { action: { fn: fn } } };
+}
+
+exports.coreCommands = {
+ start: {
+ exec: function start(editor) {
+ util.insertMode(editor);
+ setPreviousCommand(start);
+ }
+ },
+ startBeginning: {
+ exec: function startBeginning(editor) {
+ editor.navigateLineStart();
+ util.insertMode(editor);
+ setPreviousCommand(startBeginning);
+ }
+ },
+ // Stop Insert mode as soon as possible. Works like typing <Esc> in
+ // insert mode.
+ stop: {
+ exec: function stop(editor) {
+ inputBuffer.reset();
+ util.onVisualMode = false;
+ util.onVisualLineMode = false;
+ inputBuffer.lastInsertCommands = util.normalMode(editor);
+ }
+ },
+ append: {
+ exec: function append(editor) {
+ var pos = editor.getCursorPosition();
+ var lineLen = editor.session.getLine(pos.row).length;
+ if (lineLen)
+ editor.navigateRight();
+ util.insertMode(editor);
+ setPreviousCommand(append);
+ }
+ },
+ appendEnd: {
+ exec: function appendEnd(editor) {
+ editor.navigateLineEnd();
+ util.insertMode(editor);
+ setPreviousCommand(appendEnd);
+ }
+ }
+};
+
+var handleCursorMove = exports.onCursorMove = function(editor, e) {
+ if (util.currentMode === 'insert' || handleCursorMove.running)
+ return;
+ else if(!editor.selection.isEmpty()) {
+ handleCursorMove.running = true;
+ if (util.onVisualLineMode) {
+ var originRow = editor.selection.visualLineStart;
+ var cursorRow = editor.getCursorPosition().row;
+ if(originRow <= cursorRow) {
+ var endLine = editor.session.getLine(cursorRow);
+ editor.selection.clearSelection();
+ editor.selection.moveCursorTo(originRow, 0);
+ editor.selection.selectTo(cursorRow, endLine.length);
+ } else {
+ var endLine = editor.session.getLine(originRow);
+ editor.selection.clearSelection();
+ editor.selection.moveCursorTo(originRow, endLine.length);
+ editor.selection.selectTo(cursorRow, 0);
+ }
+ }
+ handleCursorMove.running = false;
+ return;
+ }
+ else {
+ if (e && (util.onVisualLineMode || util.onVisualMode)) {
+ editor.selection.clearSelection();
+ util.normalMode(editor);
+ }
+
+ handleCursorMove.running = true;
+ var pos = editor.getCursorPosition();
+ var lineLen = editor.session.getLine(pos.row).length;
+
+ if (lineLen && pos.column === lineLen)
+ editor.navigateLeft();
+ handleCursorMove.running = false;
+ }
+};
+});
+define('ace/keyboard/vim/maps/util', ['require', 'exports', 'module' , 'ace/keyboard/vim/registers', 'ace/lib/dom'], function(require, exports, module) {
+var registers = require("../registers");
+
+var dom = require("../../../lib/dom");
+dom.importCssString('.insert-mode. ace_cursor{\
+ border-left: 2px solid #333333;\
+}\
+.ace_dark.insert-mode .ace_cursor{\
+ border-left: 2px solid #eeeeee;\
+}\
+.normal-mode .ace_cursor{\
+ border: 0!important;\
+ background-color: red;\
+ opacity: 0.5;\
+}', 'vimMode');
+
+module.exports = {
+ onVisualMode: false,
+ onVisualLineMode: false,
+ currentMode: 'normal',
+ noMode: function(editor) {
+ editor.unsetStyle('insert-mode');
+ editor.unsetStyle('normal-mode');
+ if (editor.commands.recording)
+ editor.commands.toggleRecording();
+ editor.setOverwrite(false);
+ },
+ insertMode: function(editor) {
+ this.currentMode = 'insert';
+ // Switch editor to insert mode
+ editor.setStyle('insert-mode');
+ editor.unsetStyle('normal-mode');
+
+ editor.setOverwrite(false);
+ editor.keyBinding.$data.buffer = "";
+ editor.keyBinding.$data.state = "insertMode";
+ this.onVisualMode = false;
+ this.onVisualLineMode = false;
+ if(this.onInsertReplaySequence) {
+ // Ok, we're apparently replaying ("."), so let's do it
+ editor.commands.macro = this.onInsertReplaySequence;
+ editor.commands.replay(editor);
+ this.onInsertReplaySequence = null;
+ this.normalMode(editor);
+ } else {
+ editor._emit("vimMode", "insert");
+ // Record any movements, insertions in insert mode
+ if(!editor.commands.recording)
+ editor.commands.toggleRecording();
+ }
+ },
+ normalMode: function(editor) {
+ // Switch editor to normal mode
+ this.currentMode = 'normal';
+
+ editor.unsetStyle('insert-mode');
+ editor.setStyle('normal-mode');
+ editor.clearSelection();
+
+ var pos;
+ if (!editor.getOverwrite()) {
+ pos = editor.getCursorPosition();
+ if (pos.column > 0)
+ editor.navigateLeft();
+ }
+
+ editor.setOverwrite(true);
+ editor.keyBinding.$data.buffer = "";
+ editor.keyBinding.$data.state = "start";
+ this.onVisualMode = false;
+ this.onVisualLineMode = false;
+ editor._emit("changeVimMode", "normal");
+ // Save recorded keystrokes
+ if (editor.commands.recording) {
+ editor.commands.toggleRecording();
+ return editor.commands.macro;
+ }
+ else {
+ return [];
+ }
+ },
+ visualMode: function(editor, lineMode) {
+ if (
+ (this.onVisualLineMode && lineMode)
+ || (this.onVisualMode && !lineMode)
+ ) {
+ this.normalMode(editor);
+ return;
+ }
+
+ editor.setStyle('insert-mode');
+ editor.unsetStyle('normal-mode');
+
+ editor._emit("changeVimMode", "visual");
+ if (lineMode) {
+ this.onVisualLineMode = true;
+ } else {
+ this.onVisualMode = true;
+ this.onVisualLineMode = false;
+ }
+ },
+ getRightNthChar: function(editor, cursor, char, n) {
+ var line = editor.getSession().getLine(cursor.row);
+ var matches = line.substr(cursor.column + 1).split(char);
+
+ return n < matches.length ? matches.slice(0, n).join(char).length : null;
+ },
+ getLeftNthChar: function(editor, cursor, char, n) {
+ var line = editor.getSession().getLine(cursor.row);
+ var matches = line.substr(0, cursor.column).split(char);
+
+ return n < matches.length ? matches.slice(-1 * n).join(char).length : null;
+ },
+ toRealChar: function(char) {
+ if (char.length === 1)
+ return char;
+
+ if (/^shift-./.test(char))
+ return char[char.length - 1].toUpperCase();
+ else
+ return "";
+ },
+ copyLine: function(editor) {
+ var pos = editor.getCursorPosition();
+ editor.selection.clearSelection();
+ editor.moveCursorTo(pos.row, pos.column);
+ editor.selection.selectLine();
+ registers._default.isLine = true;
+ registers._default.text = editor.getCopyText().replace(/\n$/, "");
+ editor.selection.clearSelection();
+ editor.moveCursorTo(pos.row, pos.column);
+ }
+};
+});
+
+define('ace/keyboard/vim/registers', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+"never use strict";
+
+module.exports = {
+ _default: {
+ text: "",
+ isLine: false
+ }
+};
+
+});
+
+"use strict"
+
+define('ace/keyboard/vim/maps/motions', ['require', 'exports', 'module' , 'ace/keyboard/vim/maps/util', 'ace/search', 'ace/range'], function(require, exports, module) {
+
+var util = require("./util");
+
+var keepScrollPosition = function(editor, fn) {
+ var scrollTopRow = editor.renderer.getScrollTopRow();
+ var initialRow = editor.getCursorPosition().row;
+ var diff = initialRow - scrollTopRow;
+ fn && fn.call(editor);
+ editor.renderer.scrollToRow(editor.getCursorPosition().row - diff);
+};
+
+function Motion(getRange, type){
+ if (type == 'extend')
+ var extend = true;
+ else
+ var reverse = type;
+
+ this.nav = function(editor) {
+ var r = getRange(editor);
+ if (!r)
+ return;
+ if (!r.end)
+ var a = r;
+ else if (reverse)
+ var a = r.start;
+ else
+ var a = r.end;
+
+ editor.clearSelection();
+ editor.moveCursorTo(a.row, a.column);
+ }
+ this.sel = function(editor){
+ var r = getRange(editor);
+ if (!r)
+ return;
+ if (extend)
+ return editor.selection.setSelectionRange(r);
+
+ if (!r.end)
+ var a = r;
+ else if (reverse)
+ var a = r.start;
+ else
+ var a = r.end;
+
+ editor.selection.selectTo(a.row, a.column);
+ }
+}
+
+var nonWordRe = /[\s.\/\\()\"'-:,.;<>~!@#$%^&*|+=\[\]{}`~?]/;
+var wordSeparatorRe = /[.\/\\()\"'-:,.;<>~!@#$%^&*|+=\[\]{}`~?]/;
+var whiteRe = /\s/;
+var StringStream = function(editor, cursor) {
+ var sel = editor.selection;
+ this.range = sel.getRange();
+ cursor = cursor || sel.selectionLead;
+ this.row = cursor.row;
+ this.col = cursor.column;
+ var line = editor.session.getLine(this.row);
+ var maxRow = editor.session.getLength()
+ this.ch = line[this.col] || '\n'
+ this.skippedLines = 0;
+
+ this.next = function() {
+ this.ch = line[++this.col] || this.handleNewLine(1);
+ //this.debug()
+ return this.ch;
+ }
+ this.prev = function() {
+ this.ch = line[--this.col] || this.handleNewLine(-1);
+ //this.debug()
+ return this.ch;
+ }
+ this.peek = function(dir) {
+ var ch = line[this.col + dir];
+ if (ch)
+ return ch;
+ if (dir == -1)
+ return '\n';
+ if (this.col == line.length - 1)
+ return '\n';
+ return editor.session.getLine(this.row + 1)[0] || '\n';
+ }
+
+ this.handleNewLine = function(dir) {
+ if (dir == 1){
+ if (this.col == line.length)
+ return '\n';
+ if (this.row == maxRow - 1)
+ return '';
+ this.col = 0;
+ this.row ++;
+ line = editor.session.getLine(this.row);
+ this.skippedLines++;
+ return line[0] || '\n';
+ }
+ if (dir == -1) {
+ if (this.row == 0)
+ return '';
+ this.row --;
+ line = editor.session.getLine(this.row);
+ this.col = line.length;
+ this.skippedLines--;
+ return '\n';
+ }
+ }
+ this.debug = function() {
+ console.log(line.substring(0, this.col)+'|'+this.ch+'\''+this.col+'\''+line.substr(this.col+1));
+ }
+}
+
+var Search = require("ace/search").Search;
+var search = new Search();
+
+function find(editor, needle, dir) {
+ search.$options.needle = needle;
+ search.$options.backwards = dir == -1;
+ return search.find(editor.session);
+}
+
+var Range = require("ace/range").Range;
+
+module.exports = {
+ "w": new Motion(function(editor) {
+ var str = new StringStream(editor);
+
+ if (str.ch && wordSeparatorRe.test(str.ch)) {
+ while (str.ch && wordSeparatorRe.test(str.ch))
+ str.next();
+ } else {
+ while (str.ch && !nonWordRe.test(str.ch))
+ str.next();
+ }
+ while (str.ch && whiteRe.test(str.ch) && str.skippedLines < 2)
+ str.next();
+
+ str.skippedLines == 2 && str.prev();
+ return {column: str.col, row: str.row};
+ }),
+ "W": new Motion(function(editor) {
+ var str = new StringStream(editor);
+ while(str.ch && !(whiteRe.test(str.ch) && !whiteRe.test(str.peek(1))) && str.skippedLines < 2)
+ str.next();
+ if (str.skippedLines == 2)
+ str.prev();
+ else
+ str.next();
+
+ return {column: str.col, row: str.row}
+ }),
+ "b": new Motion(function(editor) {
+ var str = new StringStream(editor);
+
+ str.prev();
+ while (str.ch && whiteRe.test(str.ch) && str.skippedLines > -2)
+ str.prev();
+
+ if (str.ch && wordSeparatorRe.test(str.ch)) {
+ while (str.ch && wordSeparatorRe.test(str.ch))
+ str.prev();
+ } else {
+ while (str.ch && !nonWordRe.test(str.ch))
+ str.prev();
+ }
+ str.ch && str.next();
+ return {column: str.col, row: str.row};
+ }),
+ "B": new Motion(function(editor) {
+ var str = new StringStream(editor)
+ str.prev();
+ while(str.ch && !(!whiteRe.test(str.ch) && whiteRe.test(str.peek(-1))) && str.skippedLines > -2)
+ str.prev();
+
+ if (str.skippedLines == -2)
+ str.next();
+
+ return {column: str.col, row: str.row};
+ }, true),
+ "e": new Motion(function(editor) {
+ var str = new StringStream(editor);
+
+ str.next();
+ while (str.ch && whiteRe.test(str.ch))
+ str.next();
+
+ if (str.ch && wordSeparatorRe.test(str.ch)) {
+ while (str.ch && wordSeparatorRe.test(str.ch))
+ str.next();
+ } else {
+ while (str.ch && !nonWordRe.test(str.ch))
+ str.next();
+ }
+ str.ch && str.prev();
+ return {column: str.col, row: str.row};
+ }),
+ "E": new Motion(function(editor) {
+ var str = new StringStream(editor);
+ str.next();
+ while(str.ch && !(!whiteRe.test(str.ch) && whiteRe.test(str.peek(1))))
+ str.next();
+
+ return {column: str.col, row: str.row};
+ }),
+
+ "l": {
+ nav: function(editor) {
+ editor.navigateRight();
+ },
+ sel: function(editor) {
+ var pos = editor.getCursorPosition();
+ var col = pos.column;
+ var lineLen = editor.session.getLine(pos.row).length;
+
+ // Solving the behavior at the end of the line due to the
+ // different 0 index-based colum positions in ACE.
+ if (lineLen && col !== lineLen) //In selection mode you can select the newline
+ editor.selection.selectRight();
+ }
+ },
+ "h": {
+ nav: function(editor) {
+ var pos = editor.getCursorPosition();
+ if (pos.column > 0)
+ editor.navigateLeft();
+ },
+ sel: function(editor) {
+ var pos = editor.getCursorPosition();
+ if (pos.column > 0)
+ editor.selection.selectLeft();
+ }
+ },
+ "k": {
+ nav: function(editor) {
+ editor.navigateUp();
+ },
+ sel: function(editor) {
+ editor.selection.selectUp();
+ }
+ },
+ "j": {
+ nav: function(editor) {
+ editor.navigateDown();
+ },
+ sel: function(editor) {
+ editor.selection.selectDown();
+ }
+ },
+
+ "i": {
+ param: true,
+ sel: function(editor, range, count, param) {
+ switch (param) {
+ case "w":
+ editor.selection.selectWord();
+ break;
+ case "W":
+ editor.selection.selectAWord();
+ break;
+ case "(":
+ case "{":
+ case "[":
+ var cursor = editor.getCursorPosition();
+ var end = editor.session.$findClosingBracket(param, cursor, /paren/);
+ if (!end)
+ return;
+ var start = editor.session.$findOpeningBracket(editor.session.$brackets[param], cursor, /paren/);
+ if (!start)
+ return;
+ start.column ++;
+ editor.selection.setSelectionRange(Range.fromPoints(start, end));
+ break;
+ case "'":
+ case '"':
+ case "/":
+ var end = find(editor, param, 1);
+ if (!end)
+ return;
+ var start = find(editor, param, -1);
+ if (!start)
+ return;
+ editor.selection.setSelectionRange(Range.fromPoints(start.end, end.start));
+ break;
+ }
+ }
+ },
+ "a": {
+ param: true,
+ sel: function(editor, range, count, param) {
+ switch (param) {
+ case "w":
+ editor.selection.selectAWord();
+ break;
+ case "W":
+ editor.selection.selectAWord();
+ break;
+ case "(":
+ case "{":
+ case "[":
+ var cursor = editor.getCursorPosition();
+ var end = editor.session.$findClosingBracket(param, cursor, /paren/);
+ if (!end)
+ return;
+ var start = editor.session.$findOpeningBracket(editor.session.$brackets[param], cursor, /paren/);
+ if (!start)
+ return;
+ end.column ++;
+ editor.selection.setSelectionRange(Range.fromPoints(start, end));
+ break;
+ case "'":
+ case "\"":
+ case "/":
+ var end = find(editor, param, 1);
+ if (!end)
+ return;
+ var start = find(editor, param, -1);
+ if (!start)
+ return;
+ end.column ++;
+ editor.selection.setSelectionRange(Range.fromPoints(start.start, end.end));
+ break;
+ }
+ }
+ },
+
+ "f": {
+ param: true,
+ handlesCount: true,
+ nav: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getRightNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.selection.clearSelection(); // Why does it select in the first place?
+ ed.moveCursorTo(cursor.row, column + cursor.column + 1);
+ }
+ },
+ sel: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getRightNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.moveCursorTo(cursor.row, column + cursor.column + 1);
+ }
+ }
+ },
+ "F": {
+ param: true,
+ handlesCount: true,
+ nav: function(editor, range, count, param) {
+ count = parseInt(count, 10) || 1;
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getLeftNthChar(editor, cursor, param, count);
+
+ if (typeof column === "number") {
+ ed.selection.clearSelection(); // Why does it select in the first place?
+ ed.moveCursorTo(cursor.row, cursor.column - column - 1);
+ }
+ },
+ sel: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getLeftNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.moveCursorTo(cursor.row, cursor.column - column - 1);
+ }
+ }
+ },
+ "t": {
+ param: true,
+ handlesCount: true,
+ nav: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getRightNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.selection.clearSelection(); // Why does it select in the first place?
+ ed.moveCursorTo(cursor.row, column + cursor.column);
+ }
+ },
+ sel: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getRightNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.moveCursorTo(cursor.row, column + cursor.column);
+ }
+ }
+ },
+ "T": {
+ param: true,
+ handlesCount: true,
+ nav: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getLeftNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.selection.clearSelection(); // Why does it select in the first place?
+ ed.moveCursorTo(cursor.row, -column + cursor.column);
+ }
+ },
+ sel: function(editor, range, count, param) {
+ var ed = editor;
+ var cursor = ed.getCursorPosition();
+ var column = util.getLeftNthChar(editor, cursor, param, count || 1);
+
+ if (typeof column === "number") {
+ ed.moveCursorTo(cursor.row, -column + cursor.column);
+ }
+ }
+ },
+
+ "^": {
+ nav: function(editor) {
+ editor.navigateLineStart();
+ },
+ sel: function(editor) {
+ editor.selection.selectLineStart();
+ }
+ },
+ "$": {
+ nav: function(editor) {
+ editor.navigateLineEnd();
+ },
+ sel: function(editor) {
+ editor.selection.selectLineEnd();
+ }
+ },
+ "0": {
+ nav: function(editor) {
+ var ed = editor;
+ ed.navigateTo(ed.selection.selectionLead.row, 0);
+ },
+ sel: function(editor) {
+ var ed = editor;
+ ed.selectTo(ed.selection.selectionLead.row, 0);
+ }
+ },
+ "G": {
+ nav: function(editor, range, count, param) {
+ if (!count && count !== 0) { // Stupid JS
+ count = editor.session.getLength();
+ }
+ editor.gotoLine(count);
+ },
+ sel: function(editor, range, count, param) {
+ if (!count && count !== 0) { // Stupid JS
+ count = editor.session.getLength();
+ }
+ editor.selection.selectTo(count, 0);
+ }
+ },
+ "g": {
+ param: true,
+ nav: function(editor, range, count, param) {
+ switch(param) {
+ case "m":
+ console.log("Middle line");
+ break;
+ case "e":
+ console.log("End of prev word");
+ break;
+ case "g":
+ editor.gotoLine(count || 0);
+ case "u":
+ editor.gotoLine(count || 0);
+ case "U":
+ editor.gotoLine(count || 0);
+ }
+ },
+ sel: function(editor, range, count, param) {
+ switch(param) {
+ case "m":
+ console.log("Middle line");
+ break;
+ case "e":
+ console.log("End of prev word");
+ break;
+ case "g":
+ editor.selection.selectTo(count || 0, 0);
+ }
+ }
+ },
+ "o": {
+ nav: function(editor, range, count, param) {
+ count = count || 1;
+ var content = "";
+ while (0 < count--)
+ content += "\n";
+
+ if (content.length) {
+ editor.navigateLineEnd()
+ editor.insert(content);
+ util.insertMode(editor);
+ }
+ }
+ },
+ "O": {
+ nav: function(editor, range, count, param) {
+ var row = editor.getCursorPosition().row;
+ count = count || 1;
+ var content = "";
+ while (0 < count--)
+ content += "\n";
+
+ if (content.length) {
+ if(row > 0) {
+ editor.navigateUp();
+ editor.navigateLineEnd()
+ editor.insert(content);
+ } else {
+ editor.session.insert({row: 0, column: 0}, content);
+ editor.navigateUp();
+ }
+ util.insertMode(editor);
+ }
+ }
+ },
+ "%": new Motion(function(editor){
+ var brRe = /[\[\]{}()]/g;
+ var cursor = editor.getCursorPosition();
+ var ch = editor.session.getLine(cursor.row)[cursor.column];
+ if (!brRe.test(ch)) {
+ var range = find(editor, brRe);
+ if (!range)
+ return;
+ cursor = range.start;
+ }
+ var match = editor.session.findMatchingBracket({
+ row: cursor.row,
+ column: cursor.column + 1
+ });
+
+ return match;
+ }),
+ "ctrl-d": {
+ nav: function(editor, range, count, param) {
+ editor.selection.clearSelection();
+ keepScrollPosition(editor, editor.gotoPageDown);
+ },
+ sel: function(editor, range, count, param) {
+ keepScrollPosition(editor, editor.selectPageDown);
+ }
+ },
+ "ctrl-u": {
+ nav: function(editor, range, count, param) {
+ editor.selection.clearSelection();
+ keepScrollPosition(editor, editor.gotoPageUp);
+
+ },
+ sel: function(editor, range, count, param) {
+ keepScrollPosition(editor, editor.selectPageUp);
+ }
+ },
+};
+
+module.exports.backspace = module.exports.left = module.exports.h;
+module.exports.right = module.exports.l;
+module.exports.up = module.exports.k;
+module.exports.down = module.exports.j;
+module.exports.pagedown = module.exports["ctrl-d"];
+module.exports.pageup = module.exports["ctrl-u"];
+
+});
+
+define('ace/search', ['require', 'exports', 'module' , 'ace/lib/lang', 'ace/lib/oop', 'ace/range'], function(require, exports, module) {
+
+
+var lang = require("./lib/lang");
+var oop = require("./lib/oop");
+var Range = require("./range").Range;
+
+/**
+ * new Search()
+ *
+ * Creates a new `Search` object. The following search options are avaliable:
+ *
+ * * needle: string or regular expression
+ * * backwards: false
+ * * wrap: false
+ * * caseSensitive: false
+ * * wholeWord: false
+ * * range: Range or null for whole document
+ * * regExp: false
+ * * start: Range or position
+ * * skipCurrent: false
+ *
+**/
+
+var Search = function() {
+ this.$options = {};
+};
+
+(function() {
+ /**
+ * Search.set(options) -> Search
+ * - options (Object): An object containing all the new search properties
+ *
+ * Sets the search options via the `options` parameter.
+ *
+ **/
+ this.set = function(options) {
+ oop.mixin(this.$options, options);
+ return this;
+ };
+ this.getOptions = function() {
+ return lang.copyObject(this.$options);
+ };
+
+ this.setOptions = function(options) {
+ this.$options = options;
+ };
+ this.find = function(session) {
+ var iterator = this.$matchIterator(session, this.$options);
+
+ if (!iterator)
+ return false;
+
+ var firstRange = null;
+ iterator.forEach(function(range, row, offset) {
+ if (!range.start) {
+ var column = range.offset + (offset || 0);
+ firstRange = new Range(row, column, row, column+range.length);
+ } else
+ firstRange = range;
+ return true;
+ });
+
+ return firstRange;
+ };
+ this.findAll = function(session) {
+ var options = this.$options;
+ if (!options.needle)
+ return [];
+ this.$assembleRegExp(options);
+
+ var range = options.range;
+ var lines = range
+ ? session.getLines(range.start.row, range.end.row)
+ : session.doc.getAllLines();
+
+ var ranges = [];
+ var re = options.re;
+ if (options.$isMultiLine) {
+ var len = re.length;
+ var maxRow = lines.length - len;
+ for (var row = re.offset || 0; row <= maxRow; row++) {
+ for (var j = 0; j < len; j++)
+ if (lines[row + j].search(re[j]) == -1)
+ break;
+
+ var startLine = lines[row];
+ var line = lines[row + len - 1];
+ var startIndex = startLine.match(re[0])[0].length;
+ var endIndex = line.match(re[len - 1])[0].length;
+
+ ranges.push(new Range(
+ row, startLine.length - startIndex,
+ row + len - 1, endIndex
+ ));
+ }
+ } else {
+ for (var i = 0; i < lines.length; i++) {
+ var matches = lang.getMatchOffsets(lines[i], re);
+ for (var j = 0; j < matches.length; j++) {
+ var match = matches[j];
+ ranges.push(new Range(i, match.offset, i, match.offset + match.length));
+ }
+ }
+ }
+
+ if (range) {
+ var startColumn = range.start.column;
+ var endColumn = range.start.column;
+ var i = 0, j = ranges.length - 1;
+ while (i < j && ranges[i].start.column < startColumn && ranges[i].start.row == range.start.row)
+ i++;
+
+ while (i < j && ranges[j].end.column > endColumn && ranges[j].end.row == range.end.row)
+ j--;
+ return ranges.slice(i, j + 1);
+ }
+
+ return ranges;
+ };
+ this.replace = function(input, replacement) {
+ var options = this.$options;
+
+ var re = this.$assembleRegExp(options);
+ if (options.$isMultiLine)
+ return replacement;
+
+ if (!re)
+ return;
+
+ var match = re.exec(input);
+ if (!match || match[0].length != input.length)
+ return null;
+
+ replacement = input.replace(re, replacement);
+ if (options.preserveCase) {
+ replacement = replacement.split("");
+ for (var i = Math.min(input.length, input.length); i--; ) {
+ var ch = input[i];
+ if (ch && ch.toLowerCase() != ch)
+ replacement[i] = replacement[i].toUpperCase();
+ else
+ replacement[i] = replacement[i].toLowerCase();
+ }
+ replacement = replacement.join("");
+ }
+
+ return replacement;
+ };
+ this.$matchIterator = function(session, options) {
+ var re = this.$assembleRegExp(options);
+ if (!re)
+ return false;
+
+ var self = this, callback, backwards = options.backwards;
+
+ if (options.$isMultiLine) {
+ var len = re.length;
+ var matchIterator = function(line, row, offset) {
+ var startIndex = line.search(re[0]);
+ if (startIndex == -1)
+ return;
+ for (var i = 1; i < len; i++) {
+ line = session.getLine(row + i);
+ if (line.search(re[i]) == -1)
+ return;
+ }
+
+ var endIndex = line.match(re[len - 1])[0].length;
+
+ var range = new Range(row, startIndex, row + len - 1, endIndex);
+ if (re.offset == 1) {
+ range.start.row--;
+ range.start.column = Number.MAX_VALUE;
+ } else if (offset)
+ range.start.column += offset;
+
+ if (callback(range))
+ return true;
+ };
+ } else if (backwards) {
+ var matchIterator = function(line, row, startIndex) {
+ var matches = lang.getMatchOffsets(line, re);
+ for (var i = matches.length-1; i >= 0; i--)
+ if (callback(matches[i], row, startIndex))
+ return true;
+ };
+ } else {
+ var matchIterator = function(line, row, startIndex) {
+ var matches = lang.getMatchOffsets(line, re);
+ for (var i = 0; i < matches.length; i++)
+ if (callback(matches[i], row, startIndex))
+ return true;
+ };
+ }
+
+ return {
+ forEach: function(_callback) {
+ callback = _callback;
+ self.$lineIterator(session, options).forEach(matchIterator);
+ }
+ };
+ };
+
+ this.$assembleRegExp = function(options) {
+ if (options.needle instanceof RegExp)
+ return options.re = options.needle;
+
+ var needle = options.needle;
+
+ if (!options.needle)
+ return options.re = false;
+
+ if (!options.regExp)
+ needle = lang.escapeRegExp(needle);
+
+ if (options.wholeWord)
+ needle = "\\b" + needle + "\\b";
+
+ var modifier = options.caseSensitive ? "g" : "gi";
+
+ options.$isMultiLine = /[\n\r]/.test(needle);
+ if (options.$isMultiLine)
+ return options.re = this.$assembleMultilineRegExp(needle, modifier);
+
+ try {
+ var re = new RegExp(needle, modifier);
+ } catch(e) {
+ re = false;
+ }
+ return options.re = re;
+ };
+
+ this.$assembleMultilineRegExp = function(needle, modifier) {
+ var parts = needle.replace(/\r\n|\r|\n/g, "$\n^").split("\n");
+ var re = [];
+ for (var i = 0; i < parts.length; i++) try {
+ re.push(new RegExp(parts[i], modifier));
+ } catch(e) {
+ return false;
+ }
+ if (parts[0] == "") {
+ re.shift();
+ re.offset = 1;
+ } else {
+ re.offset = 0;
+ }
+ return re;
+ };
+
+ this.$lineIterator = function(session, options) {
+ var backwards = options.backwards == true;
+ var skipCurrent = options.skipCurrent != false;
+
+ var range = options.range;
+ var start = options.start;
+ if (!start)
+ start = range ? range[backwards ? "end" : "start"] : session.selection.getRange();
+
+ if (start.start)
+ start = start[skipCurrent != backwards ? "end" : "start"];
+
+ var firstRow = range ? range.start.row : 0;
+ var lastRow = range ? range.end.row : session.getLength() - 1;
+
+ var forEach = backwards ? function(callback) {
+ var row = start.row;
+
+ var line = session.getLine(row).substring(0, start.column);
+ if (callback(line, row))
+ return;
+
+ for (row--; row >= firstRow; row--)
+ if (callback(session.getLine(row), row))
+ return;
+
+ if (options.wrap == false)
+ return;
+
+ for (row = lastRow, firstRow = start.row; row >= firstRow; row--)
+ if (callback(session.getLine(row), row))
+ return;
+ } : function(callback) {
+ var row = start.row;
+
+ var line = session.getLine(row).substr(start.column);
+ if (callback(line, row, start.column))
+ return;
+
+ for (row = row+1; row <= lastRow; row++)
+ if (callback(session.getLine(row), row))
+ return;
+
+ if (options.wrap == false)
+ return;
+
+ for (row = firstRow, lastRow = start.row; row <= lastRow; row++)
+ if (callback(session.getLine(row), row))
+ return;
+ };
+
+ return {forEach: forEach};
+ };
+
+}).call(Search.prototype);
+
+exports.Search = Search;
+});
+
+define('ace/keyboard/vim/maps/operators', ['require', 'exports', 'module' , 'ace/keyboard/vim/maps/util', 'ace/keyboard/vim/registers'], function(require, exports, module) {
+
+"never use strict";
+
+var util = require("./util");
+var registers = require("../registers");
+
+module.exports = {
+ "d": {
+ selFn: function(editor, range, count, param) {
+ registers._default.text = editor.getCopyText();
+ registers._default.isLine = util.onVisualLineMode;
+ if(util.onVisualLineMode)
+ editor.removeLines();
+ else
+ editor.session.remove(range);
+ util.normalMode(editor);
+ },
+ fn: function(editor, range, count, param) {
+ count = count || 1;
+ switch (param) {
+ case "d":
+ registers._default.text = "";
+ registers._default.isLine = true;
+ for (var i = 0; i < count; i++) {
+ editor.selection.selectLine();
+ registers._default.text += editor.getCopyText();
+ var selRange = editor.getSelectionRange();
+ // check if end of the document was reached
+ if (!selRange.isMultiLine()) {
+ lastLineReached = true
+ var row = selRange.start.row - 1;
+ var col = editor.session.getLine(row).length
+ selRange.setStart(row, col);
+ editor.session.remove(selRange);
+ editor.selection.clearSelection();
+ break;
+ }
+ editor.session.remove(selRange);
+ editor.selection.clearSelection();
+ }
+ registers._default.text = registers._default.text.replace(/\n$/, "");
+ break;
+ default:
+ if (range) {
+ editor.selection.setSelectionRange(range);
+ registers._default.text = editor.getCopyText();
+ registers._default.isLine = false;
+ editor.session.remove(range);
+ editor.selection.clearSelection();
+ }
+ }
+ }
+ },
+ "c": {
+ selFn: function(editor, range, count, param) {
+ editor.session.remove(range);
+ util.insertMode(editor);
+ },
+ fn: function(editor, range, count, param) {
+ count = count || 1;
+ switch (param) {
+ case "c":
+ for (var i = 0; i < count; i++) {
+ editor.removeLines();
+ util.insertMode(editor);
+ }
+
+ break;
+ default:
+ if (range) {
+
+ // range.end.column ++;
+ editor.session.remove(range);
+ util.insertMode(editor);
+ }
+ }
+ }
+ },
+ "y": {
+ selFn: function(editor, range, count, param) {
+ registers._default.text = editor.getCopyText();
+ registers._default.isLine = util.onVisualLineMode;
+ editor.selection.clearSelection();
+ util.normalMode(editor);
+ },
+ fn: function(editor, range, count, param) {
+ count = count || 1;
+ switch (param) {
+ case "y":
+ var pos = editor.getCursorPosition();
+ editor.selection.selectLine();
+ for (var i = 0; i < count - 1; i++) {
+ editor.selection.moveCursorDown();
+ }
+ registers._default.text = editor.getCopyText().replace(/\n$/, "");
+ editor.selection.clearSelection();
+ registers._default.isLine = true;
+ editor.moveCursorToPosition(pos);
+ break;
+ default:
+ if (range) {
+ var pos = editor.getCursorPosition();
+ editor.selection.setSelectionRange(range);
+ registers._default.text = editor.getCopyText();
+ registers._default.isLine = false;
+ editor.selection.clearSelection();
+ editor.moveCursorTo(pos.row, pos.column);
+ }
+ }
+ }
+ },
+ ">": {
+ selFn: function(editor, range, count, param) {
+ count = count || 1;
+ for (var i = 0; i < count; i++) {
+ editor.indent();
+ }
+ util.normalMode(editor);
+ },
+ fn: function(editor, range, count, param) {
+ count = parseInt(count || 1, 10);
+ switch (param) {
+ case ">":
+ var pos = editor.getCursorPosition();
+ editor.selection.selectLine();
+ for (var i = 0; i < count - 1; i++) {
+ editor.selection.moveCursorDown();
+ }
+ editor.indent();
+ editor.selection.clearSelection();
+ editor.moveCursorToPosition(pos);
+ editor.navigateLineEnd();
+ editor.navigateLineStart();
+ break;
+ }
+ }
+ },
+ "<": {
+ selFn: function(editor, range, count, param) {
+ count = count || 1;
+ for (var i = 0; i < count; i++) {
+ editor.blockOutdent();
+ }
+ util.normalMode(editor);
+ },
+ fn: function(editor, range, count, param) {
+ count = count || 1;
+ switch (param) {
+ case "<":
+ var pos = editor.getCursorPosition();
+ editor.selection.selectLine();
+ for (var i = 0; i < count - 1; i++) {
+ editor.selection.moveCursorDown();
+ }
+ editor.blockOutdent();
+ editor.selection.clearSelection();
+ editor.moveCursorToPosition(pos);
+ editor.navigateLineEnd();
+ editor.navigateLineStart();
+ break;
+ }
+ }
+ }
+};
+});
+
+"use strict"
+
+define('ace/keyboard/vim/maps/aliases', ['require', 'exports', 'module' ], function(require, exports, module) {
+module.exports = {
+ "x": {
+ operator: {
+ char: "d",
+ count: 1
+ },
+ motion: {
+ char: "l",
+ count: 1
+ }
+ },
+ "X": {
+ operator: {
+ char: "d",
+ count: 1
+ },
+ motion: {
+ char: "h",
+ count: 1
+ }
+ },
+ "D": {
+ operator: {
+ char: "d",
+ count: 1
+ },
+ motion: {
+ char: "$",
+ count: 1
+ }
+ },
+ "C": {
+ operator: {
+ char: "c",
+ count: 1
+ },
+ motion: {
+ char: "$",
+ count: 1
+ }
+ },
+ "s": {
+ operator: {
+ char: "c",
+ count: 1
+ },
+ motion: {
+ char: "l",
+ count: 1
+ }
+ },
+ "S": {
+ operator: {
+ char: "c",
+ count: 1
+ },
+ param: "c"
+ }
+};
+});
+
+define('ace/edit_session', ['require', 'exports', 'module' , 'ace/config', 'ace/lib/oop', 'ace/lib/lang', 'ace/lib/net', 'ace/lib/event_emitter', 'ace/selection', 'ace/mode/text', 'ace/range', 'ace/document', 'ace/background_tokenizer', 'ace/search_highlight', 'ace/edit_session/folding', 'ace/edit_session/bracket_match'], function(require, exports, module) {
+
+
+var config = require("./config");
+var oop = require("./lib/oop");
+var lang = require("./lib/lang");
+var net = require("./lib/net");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var Selection = require("./selection").Selection;
+var TextMode = require("./mode/text").Mode;
+var Range = require("./range").Range;
+var Document = require("./document").Document;
+var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer;
+var SearchHighlight = require("./search_highlight").SearchHighlight;
+
+/**
+ * new EditSession(text, mode)
+ * - text (Document | String): If `text` is a `Document`, it associates the `EditSession` with it. Otherwise, a new `Document` is created, with the initial text
+ * - mode (TextMode): The inital language mode to use for the document
+ *
+ * Sets up a new `EditSession` and associates it with the given `Document` and `TextMode`.
+ *
+ **/
+
+var EditSession = function(text, mode) {
+ this.$modified = true;
+ this.$breakpoints = [];
+ this.$frontMarkers = {};
+ this.$backMarkers = {};
+ this.$markerId = 1;
+ this.$resetRowCache(0);
+ this.$wrapData = [];
+ this.$foldData = [];
+ this.$rowLengthCache = [];
+ this.$undoSelect = true;
+ this.$foldData.toString = function() {
+ var str = "";
+ this.forEach(function(foldLine) {
+ str += "\n" + foldLine.toString();
+ });
+ return str;
+ }
+
+ if (typeof text == "object" && text.getLine) {
+ this.setDocument(text);
+ } else {
+ this.setDocument(new Document(text));
+ }
+
+ this.selection = new Selection(this);
+ this.setMode(mode);
+};
+
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+ this.setDocument = function(doc) {
+ if (this.doc)
+ throw new Error("Document is already set");
+
+ this.doc = doc;
+ doc.on("change", this.onChange.bind(this));
+ this.on("changeFold", this.onChangeFold.bind(this));
+
+ if (this.bgTokenizer) {
+ this.bgTokenizer.setDocument(this.getDocument());
+ this.bgTokenizer.start(0);
+ }
+ };
+ this.getDocument = function() {
+ return this.doc;
+ };
+ this.$resetRowCache = function(docRrow) {
+ if (!docRrow) {
+ this.$docRowCache = [];
+ this.$screenRowCache = [];
+ return;
+ }
+
+ var i = this.$getRowCacheIndex(this.$docRowCache, docRrow) + 1;
+ var l = this.$docRowCache.length;
+ this.$docRowCache.splice(i, l);
+ this.$screenRowCache.splice(i, l);
+
+ };
+
+ this.$getRowCacheIndex = function(cacheArray, val) {
+ var low = 0;
+ var hi = cacheArray.length - 1;
+
+ while (low <= hi) {
+ var mid = (low + hi) >> 1;
+ var c = cacheArray[mid];
+
+ if (val > c)
+ low = mid + 1;
+ else if (val < c)
+ hi = mid - 1;
+ else
+ return mid;
+ }
+
+ return low && low -1;
+ };
+ this.onChangeFold = function(e) {
+ var fold = e.data;
+ this.$resetRowCache(fold.start.row);
+ };
+ this.onChange = function(e) {
+ var delta = e.data;
+ this.$modified = true;
+
+ this.$resetRowCache(delta.range.start.row);
+
+ var removedFolds = this.$updateInternalDataOnChange(e);
+ if (!this.$fromUndo && this.$undoManager && !delta.ignore) {
+ this.$deltasDoc.push(delta);
+ if (removedFolds && removedFolds.length != 0) {
+ this.$deltasFold.push({
+ action: "removeFolds",
+ folds: removedFolds
+ });
+ }
+
+ this.$informUndoManager.schedule();
+ }
+
+ this.bgTokenizer.$updateOnChange(delta);
+ this._emit("change", e);
+ };
+ this.setValue = function(text) {
+ this.doc.setValue(text);
+ this.selection.moveCursorTo(0, 0);
+ this.selection.clearSelection();
+
+ this.$resetRowCache(0);
+ this.$deltas = [];
+ this.$deltasDoc = [];
+ this.$deltasFold = [];
+ this.getUndoManager().reset();
+ };
+ /** alias of: EditSession.getValue
+ * EditSession.toString() -> String
+ *
+ * Returns the current [[Document `Document`]] as a string.
+ *
+ **/
+ this.getValue =
+ this.toString = function() {
+ return this.doc.getValue();
+ };
+ this.getSelection = function() {
+ return this.selection;
+ };
+ this.getState = function(row) {
+ return this.bgTokenizer.getState(row);
+ };
+ this.getTokens = function(row) {
+ return this.bgTokenizer.getTokens(row);
+ };
+ this.getTokenAt = function(row, column) {
+ var tokens = this.bgTokenizer.getTokens(row);
+ var token, c = 0;
+ if (column == null) {
+ i = tokens.length - 1;
+ c = this.getLine(row).length;
+ } else {
+ for (var i = 0; i < tokens.length; i++) {
+ c += tokens[i].value.length;
+ if (c >= column)
+ break;
+ }
+ }
+ token = tokens[i];
+ if (!token)
+ return null;
+ token.index = i;
+ token.start = c - token.value.length;
+ return token;
+ };
+
+ this.highlight = function(re) {
+ if (!this.$searchHighlight) {
+ var highlight = new SearchHighlight(null, "ace_selected_word", "text");
+ this.$searchHighlight = this.addDynamicMarker(highlight);
+ }
+ this.$searchHighlight.setRegexp(re);
+ }
+ /**
+ * EditSession.setUndoManager(undoManager)
+ * - undoManager (UndoManager): The new undo manager
+ *
+ * Sets the undo manager.
+ **/
+ this.setUndoManager = function(undoManager) {
+ this.$undoManager = undoManager;
+ this.$deltas = [];
+ this.$deltasDoc = [];
+ this.$deltasFold = [];
+
+ if (this.$informUndoManager)
+ this.$informUndoManager.cancel();
+
+ if (undoManager) {
+ var self = this;
+ this.$syncInformUndoManager = function() {
+ self.$informUndoManager.cancel();
+
+ if (self.$deltasFold.length) {
+ self.$deltas.push({
+ group: "fold",
+ deltas: self.$deltasFold
+ });
+ self.$deltasFold = [];
+ }
+
+ if (self.$deltasDoc.length) {
+ self.$deltas.push({
+ group: "doc",
+ deltas: self.$deltasDoc
+ });
+ self.$deltasDoc = [];
+ }
+
+ if (self.$deltas.length > 0) {
+ undoManager.execute({
+ action: "aceupdate",
+ args: [self.$deltas, self]
+ });
+ }
+
+ self.$deltas = [];
+ }
+ this.$informUndoManager =
+ lang.deferredCall(this.$syncInformUndoManager);
+ }
+ };
+
+ this.$defaultUndoManager = {
+ undo: function() {},
+ redo: function() {},
+ reset: function() {}
+ };
+ this.getUndoManager = function() {
+ return this.$undoManager || this.$defaultUndoManager;
+ },
+
+ /**
+ * EditSession.getTabString() -> String
+ *
+ * Returns the current value for tabs. If the user is using soft tabs, this will be a series of spaces (defined by [[EditSession.getTabSize `getTabSize()`]]); otherwise it's simply `'\t'`.
+ **/
+ this.getTabString = function() {
+ if (this.getUseSoftTabs()) {
+ return lang.stringRepeat(" ", this.getTabSize());
+ } else {
+ return "\t";
+ }
+ };
+
+ this.$useSoftTabs = true;
+ this.setUseSoftTabs = function(useSoftTabs) {
+ if (this.$useSoftTabs === useSoftTabs) return;
+
+ this.$useSoftTabs = useSoftTabs;
+ };
+ this.getUseSoftTabs = function() {
+ return this.$useSoftTabs;
+ };
+
+ this.$tabSize = 4;
+ this.setTabSize = function(tabSize) {
+ if (isNaN(tabSize) || this.$tabSize === tabSize) return;
+
+ this.$modified = true;
+ this.$rowLengthCache = [];
+ this.$tabSize = tabSize;
+ this._emit("changeTabSize");
+ };
+ this.getTabSize = function() {
+ return this.$tabSize;
+ };
+ this.isTabStop = function(position) {
+ return this.$useSoftTabs && (position.column % this.$tabSize == 0);
+ };
+
+ this.$overwrite = false;
+ this.setOverwrite = function(overwrite) {
+ if (this.$overwrite == overwrite) return;
+
+ this.$overwrite = overwrite;
+ this._emit("changeOverwrite");
+ };
+ this.getOverwrite = function() {
+ return this.$overwrite;
+ };
+ this.toggleOverwrite = function() {
+ this.setOverwrite(!this.$overwrite);
+ };
+ this.getBreakpoints = function() {
+ return this.$breakpoints;
+ };
+ this.setBreakpoints = function(rows) {
+ this.$breakpoints = [];
+ for (var i=0; i<rows.length; i++) {
+ this.$breakpoints[rows[i]] = true;
+ }
+ this._emit("changeBreakpoint", {});
+ };
+ this.clearBreakpoints = function() {
+ this.$breakpoints = [];
+ this._emit("changeBreakpoint", {});
+ };
+ this.setBreakpoint = function(row) {
+ this.$breakpoints[row] = true;
+ this._emit("changeBreakpoint", {});
+ };
+ this.clearBreakpoint = function(row) {
+ delete this.$breakpoints[row];
+ this._emit("changeBreakpoint", {});
+ };
+ this.addMarker = function(range, clazz, type, inFront) {
+ var id = this.$markerId++;
+
+ var marker = {
+ range : range,
+ type : type || "line",
+ renderer: typeof type == "function" ? type : null,
+ clazz : clazz,
+ inFront: !!inFront,
+ id: id
+ }
+
+ if (inFront) {
+ this.$frontMarkers[id] = marker;
+ this._emit("changeFrontMarker")
+ } else {
+ this.$backMarkers[id] = marker;
+ this._emit("changeBackMarker")
+ }
+
+ return id;
+ };
+ this.addDynamicMarker = function(marker, inFront) {
+ if (!marker.update)
+ return;
+ var id = this.$markerId++;
+ marker.id = id;
+ marker.inFront = !!inFront;
+
+ if (inFront) {
+ this.$frontMarkers[id] = marker;
+ this._emit("changeFrontMarker")
+ } else {
+ this.$backMarkers[id] = marker;
+ this._emit("changeBackMarker")
+ }
+
+ return marker;
+ };
+ this.removeMarker = function(markerId) {
+ var marker = this.$frontMarkers[markerId] || this.$backMarkers[markerId];
+ if (!marker)
+ return;
+
+ var markers = marker.inFront ? this.$frontMarkers : this.$backMarkers;
+ if (marker) {
+ delete (markers[markerId]);
+ this._emit(marker.inFront ? "changeFrontMarker" : "changeBackMarker");
+ }
+ };
+ this.getMarkers = function(inFront) {
+ return inFront ? this.$frontMarkers : this.$backMarkers;
+ };
+ /**
+ * EditSession.setAnnotations(annotations)
+ * - annotations (Array): A list of annotations
+ *
+ * Sets annotations for the `EditSession`. This functions emits the `'changeAnnotation'` event.
+ **/
+ this.setAnnotations = function(annotations) {
+ this.$annotations = {};
+ for (var i=0; i<annotations.length; i++) {
+ var annotation = annotations[i];
+ var row = annotation.row;
+ if (this.$annotations[row])
+ this.$annotations[row].push(annotation);
+ else
+ this.$annotations[row] = [annotation];
+ }
+ this._emit("changeAnnotation", {});
+ };
+ this.getAnnotations = function() {
+ return this.$annotations || {};
+ };
+ this.clearAnnotations = function() {
+ this.$annotations = {};
+ this._emit("changeAnnotation", {});
+ };
+ this.$detectNewLine = function(text) {
+ var match = text.match(/^.*?(\r?\n)/m);
+ if (match) {
+ this.$autoNewLine = match[1];
+ } else {
+ this.$autoNewLine = "\n";
+ }
+ };
+ this.getWordRange = function(row, column) {
+ var line = this.getLine(row);
+
+ var inToken = false;
+ if (column > 0)
+ inToken = !!line.charAt(column - 1).match(this.tokenRe);
+
+ if (!inToken)
+ inToken = !!line.charAt(column).match(this.tokenRe);
+
+ if (inToken)
+ var re = this.tokenRe;
+ else if (/^\s+$/.test(line.slice(column-1, column+1)))
+ var re = /\s/;
+ else
+ var re = this.nonTokenRe;
+
+ var start = column;
+ if (start > 0) {
+ do {
+ start--;
+ }
+ while (start >= 0 && line.charAt(start).match(re));
+ start++;
+ }
+
+ var end = column;
+ while (end < line.length && line.charAt(end).match(re)) {
+ end++;
+ }
+
+ return new Range(row, start, row, end);
+ };
+ this.getAWordRange = function(row, column) {
+ var wordRange = this.getWordRange(row, column);
+ var line = this.getLine(wordRange.end.row);
+
+ while (line.charAt(wordRange.end.column).match(/[ \t]/)) {
+ wordRange.end.column += 1;
+ }
+ return wordRange;
+ };
+ this.setNewLineMode = function(newLineMode) {
+ this.doc.setNewLineMode(newLineMode);
+ };
+ this.getNewLineMode = function() {
+ return this.doc.getNewLineMode();
+ };
+
+ this.$useWorker = true;
+ this.setUseWorker = function(useWorker) {
+ if (this.$useWorker == useWorker)
+ return;
+
+ this.$useWorker = useWorker;
+
+ this.$stopWorker();
+ if (useWorker)
+ this.$startWorker();
+ };
+ this.getUseWorker = function() {
+ return this.$useWorker;
+ };
+ this.onReloadTokenizer = function(e) {
+ var rows = e.data;
+ this.bgTokenizer.start(rows.first);
+ this._emit("tokenizerUpdate", e);
+ };
+
+ this.$modes = {};
+ this._loadMode = function(mode, callback) {
+ if (!this.$modes["null"])
+ this.$modes["null"] = this.$modes["ace/mode/text"] = new TextMode();
+
+ if (this.$modes[mode])
+ return callback(this.$modes[mode]);
+
+ var _self = this;
+ var module;
+ try {
+ module = require(mode);
+ } catch (e) {};
+ // sometimes require returns empty object (this bug is present in requirejs 2 as well)
+ if (module && module.Mode)
+ return done(module);
+
+ // set mode to text until loading is finished
+ if (!this.$mode)
+ this.$setModePlaceholder();
+
+ fetch(mode, function() {
+ require([mode], done);
+ });
+
+ function done(module) {
+ if (_self.$modes[mode])
+ return callback(_self.$modes[mode]);
+
+ _self.$modes[mode] = new module.Mode();
+ _self.$modes[mode].$id = mode;
+ _self._emit("loadmode", {
+ name: mode,
+ mode: _self.$modes[mode]
+ });
+ callback(_self.$modes[mode]);
+ }
+
+ function fetch(name, callback) {
+ if (!config.get("packaged"))
+ return callback();
+
+ net.loadScript(config.moduleUrl(name, "mode"), callback);
+ }
+ };
+
+ this.$setModePlaceholder = function() {
+ this.$mode = this.$modes["null"];
+ var tokenizer = this.$mode.getTokenizer();
+
+ if (!this.bgTokenizer) {
+ this.bgTokenizer = new BackgroundTokenizer(tokenizer);
+ var _self = this;
+ this.bgTokenizer.addEventListener("update", function(e) {
+ _self._emit("tokenizerUpdate", e);
+ });
+ } else {
+ this.bgTokenizer.setTokenizer(tokenizer);
+ }
+ this.bgTokenizer.setDocument(this.getDocument());
+
+ this.tokenRe = this.$mode.tokenRe;
+ this.nonTokenRe = this.$mode.nonTokenRe;
+ };
+ this.$mode = null;
+ this.$modeId = null;
+ this.setMode = function(mode) {
+ mode = mode || "null";
+ // load on demand
+ if (typeof mode === "string") {
+ if (this.$modeId == mode)
+ return;
+
+ this.$modeId = mode;
+ var _self = this;
+ this._loadMode(mode, function(module) {
+ if (_self.$modeId !== mode)
+ return;
+
+ _self.setMode(module);
+ });
+ return;
+ }
+
+ if (this.$mode === mode) return;
+ this.$mode = mode;
+ this.$modeId = mode.$id;
+
+ this.$stopWorker();
+
+ if (this.$useWorker)
+ this.$startWorker();
+
+ var tokenizer = mode.getTokenizer();
+
+ if(tokenizer.addEventListener !== undefined) {
+ var onReloadTokenizer = this.onReloadTokenizer.bind(this);
+ tokenizer.addEventListener("update", onReloadTokenizer);
+ }
+
+ if (!this.bgTokenizer) {
+ this.bgTokenizer = new BackgroundTokenizer(tokenizer);
+ var _self = this;
+ this.bgTokenizer.addEventListener("update", function(e) {
+ _self._emit("tokenizerUpdate", e);
+ });
+ } else {
+ this.bgTokenizer.setTokenizer(tokenizer);
+ }
+
+ this.bgTokenizer.setDocument(this.getDocument());
+ this.bgTokenizer.start(0);
+
+ this.tokenRe = mode.tokenRe;
+ this.nonTokenRe = mode.nonTokenRe;
+
+ this.$setFolding(mode.foldingRules);
+
+ this._emit("changeMode");
+ };
+ this.$stopWorker = function() {
+ if (this.$worker)
+ this.$worker.terminate();
+
+ this.$worker = null;
+ };
+ this.$startWorker = function() {
+ if (typeof Worker !== "undefined" && !require.noWorker) {
+ try {
+ this.$worker = this.$mode.createWorker(this);
+ } catch (e) {
+ console.log("Could not load worker");
+ console.log(e);
+ this.$worker = null;
+ }
+ }
+ else
+ this.$worker = null;
+ };
+ this.getMode = function() {
+ return this.$mode;
+ };
+
+ this.$scrollTop = 0;
+ this.setScrollTop = function(scrollTop) {
+ scrollTop = Math.round(Math.max(0, scrollTop));
+ if (this.$scrollTop === scrollTop)
+ return;
+
+ this.$scrollTop = scrollTop;
+ this._emit("changeScrollTop", scrollTop);
+ };
+ this.getScrollTop = function() {
+ return this.$scrollTop;
+ };
+
+ this.$scrollLeft = 0;
+ this.setScrollLeft = function(scrollLeft) {
+ scrollLeft = Math.round(Math.max(0, scrollLeft));
+ if (this.$scrollLeft === scrollLeft)
+ return;
+
+ this.$scrollLeft = scrollLeft;
+ this._emit("changeScrollLeft", scrollLeft);
+ };
+ this.getScrollLeft = function() {
+ return this.$scrollLeft;
+ };
+ this.getScreenWidth = function() {
+ this.$computeWidth();
+ return this.screenWidth;
+ };
+
+ this.$computeWidth = function(force) {
+ if (this.$modified || force) {
+ this.$modified = false;
+
+ if (this.$useWrapMode)
+ return this.screenWidth = this.$wrapLimit;
+
+ var lines = this.doc.getAllLines();
+ var cache = this.$rowLengthCache;
+ var longestScreenLine = 0;
+ var foldIndex = 0;
+ var foldLine = this.$foldData[foldIndex];
+ var foldStart = foldLine ? foldLine.start.row : Infinity;
+ var len = lines.length;
+
+ for (var i = 0; i < len; i++) {
+ if (i > foldStart) {
+ i = foldLine.end.row + 1;
+ if (i >= len)
+ break;
+ foldLine = this.$foldData[foldIndex++];
+ foldStart = foldLine ? foldLine.start.row : Infinity;
+ }
+
+ if (cache[i] == null)
+ cache[i] = this.$getStringScreenWidth(lines[i])[0];
+
+ if (cache[i] > longestScreenLine)
+ longestScreenLine = cache[i];
+ }
+ this.screenWidth = longestScreenLine;
+ }
+ };
+ this.getLine = function(row) {
+ return this.doc.getLine(row);
+ };
+ this.getLines = function(firstRow, lastRow) {
+ return this.doc.getLines(firstRow, lastRow);
+ };
+ this.getLength = function() {
+ return this.doc.getLength();
+ };
+ this.getTextRange = function(range) {
+ return this.doc.getTextRange(range || this.selection.getRange());
+ };
+ this.insert = function(position, text) {
+ return this.doc.insert(position, text);
+ };
+ this.remove = function(range) {
+ return this.doc.remove(range);
+ };
+ this.undoChanges = function(deltas, dontSelect) {
+ if (!deltas.length)
+ return;
+
+ this.$fromUndo = true;
+ var lastUndoRange = null;
+ for (var i = deltas.length - 1; i != -1; i--) {
+ var delta = deltas[i];
+ if (delta.group == "doc") {
+ this.doc.revertDeltas(delta.deltas);
+ lastUndoRange =
+ this.$getUndoSelection(delta.deltas, true, lastUndoRange);
+ } else {
+ delta.deltas.forEach(function(foldDelta) {
+ this.addFolds(foldDelta.folds);
+ }, this);
+ }
+ }
+ this.$fromUndo = false;
+ lastUndoRange &&
+ this.$undoSelect &&
+ !dontSelect &&
+ this.selection.setSelectionRange(lastUndoRange);
+ return lastUndoRange;
+ };
+ this.redoChanges = function(deltas, dontSelect) {
+ if (!deltas.length)
+ return;
+
+ this.$fromUndo = true;
+ var lastUndoRange = null;
+ for (var i = 0; i < deltas.length; i++) {
+ var delta = deltas[i];
+ if (delta.group == "doc") {
+ this.doc.applyDeltas(delta.deltas);
+ lastUndoRange =
+ this.$getUndoSelection(delta.deltas, false, lastUndoRange);
+ }
+ }
+ this.$fromUndo = false;
+ lastUndoRange &&
+ this.$undoSelect &&
+ !dontSelect &&
+ this.selection.setSelectionRange(lastUndoRange);
+ return lastUndoRange;
+ };
+ this.setUndoSelect = function(enable) {
+ this.$undoSelect = enable;
+ };
+ this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) {
+ function isInsert(delta) {
+ var insert =
+ delta.action == "insertText" || delta.action == "insertLines";
+ return isUndo ? !insert : insert;
+ }
+
+ var delta = deltas[0];
+ var range, point;
+ var lastDeltaIsInsert = false;
+ if (isInsert(delta)) {
+ range = delta.range.clone();
+ lastDeltaIsInsert = true;
+ } else {
+ range = Range.fromPoints(delta.range.start, delta.range.start);
+ lastDeltaIsInsert = false;
+ }
+
+ for (var i = 1; i < deltas.length; i++) {
+ delta = deltas[i];
+ if (isInsert(delta)) {
+ point = delta.range.start;
+ if (range.compare(point.row, point.column) == -1) {
+ range.setStart(delta.range.start);
+ }
+ point = delta.range.end;
+ if (range.compare(point.row, point.column) == 1) {
+ range.setEnd(delta.range.end);
+ }
+ lastDeltaIsInsert = true;
+ } else {
+ point = delta.range.start;
+ if (range.compare(point.row, point.column) == -1) {
+ range =
+ Range.fromPoints(delta.range.start, delta.range.start);
+ }
+ lastDeltaIsInsert = false;
+ }
+ }
+
+ // Check if this range and the last undo range has something in common.
+ // If true, merge the ranges.
+ if (lastUndoRange != null) {
+ var cmp = lastUndoRange.compareRange(range);
+ if (cmp == 1) {
+ range.setStart(lastUndoRange.start);
+ } else if (cmp == -1) {
+ range.setEnd(lastUndoRange.end);
+ }
+ }
+
+ return range;
+ },
+
+ /** related to: Document.replace
+ * EditSession.replace(range, text) -> Object
+ * - range (Range): A specified Range to replace
+ * - text (String): The new text to use as a replacement
+ * + (Object): Returns an object containing the final row and column, like this:<br/>
+ * ```{row: endRow, column: 0}```<br/>
+ * If the text and range are empty, this function returns an object containing the current `range.start` value.<br/>
+ * If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value.
+ *
+ * Replaces a range in the document with the new `text`.
+ *
+ *
+ *
+ **/
+ this.replace = function(range, text) {
+ return this.doc.replace(range, text);
+ };
+ this.moveText = function(fromRange, toPosition) {
+ var text = this.getTextRange(fromRange);
+ this.remove(fromRange);
+
+ var toRow = toPosition.row;
+ var toColumn = toPosition.column;
+
+ // Make sure to update the insert location, when text is removed in
+ // front of the chosen point of insertion.
+ if (!fromRange.isMultiLine() && fromRange.start.row == toRow &&
+ fromRange.end.column < toColumn)
+ toColumn -= text.length;
+
+ if (fromRange.isMultiLine() && fromRange.end.row < toRow) {
+ var lines = this.doc.$split(text);
+ toRow -= lines.length - 1;
+ }
+
+ var endRow = toRow + fromRange.end.row - fromRange.start.row;
+ var endColumn = fromRange.isMultiLine() ?
+ fromRange.end.column :
+ toColumn + fromRange.end.column - fromRange.start.column;
+
+ var toRange = new Range(toRow, toColumn, endRow, endColumn);
+
+ this.insert(toRange.start, text);
+
+ return toRange;
+ };
+ this.indentRows = function(startRow, endRow, indentString) {
+ indentString = indentString.replace(/\t/g, this.getTabString());
+ for (var row=startRow; row<=endRow; row++)
+ this.insert({row: row, column:0}, indentString);
+ };
+ this.outdentRows = function (range) {
+ var rowRange = range.collapseRows();
+ var deleteRange = new Range(0, 0, 0, 0);
+ var size = this.getTabSize();
+
+ for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) {
+ var line = this.getLine(i);
+
+ deleteRange.start.row = i;
+ deleteRange.end.row = i;
+ for (var j = 0; j < size; ++j)
+ if (line.charAt(j) != ' ')
+ break;
+ if (j < size && line.charAt(j) == '\t') {
+ deleteRange.start.column = j;
+ deleteRange.end.column = j + 1;
+ } else {
+ deleteRange.start.column = 0;
+ deleteRange.end.column = j;
+ }
+ this.remove(deleteRange);
+ }
+ };
+ this.moveLinesUp = function(firstRow, lastRow) {
+ if (firstRow <= 0) return 0;
+
+ var removed = this.doc.removeLines(firstRow, lastRow);
+ this.doc.insertLines(firstRow - 1, removed);
+ return -1;
+ };
+ this.moveLinesDown = function(firstRow, lastRow) {
+ if (lastRow >= this.doc.getLength()-1) return 0;
+
+ var removed = this.doc.removeLines(firstRow, lastRow);
+ this.doc.insertLines(firstRow+1, removed);
+ return 1;
+ };
+ this.duplicateLines = function(firstRow, lastRow) {
+ var firstRow = this.$clipRowToDocument(firstRow);
+ var lastRow = this.$clipRowToDocument(lastRow);
+
+ var lines = this.getLines(firstRow, lastRow);
+ this.doc.insertLines(firstRow, lines);
+
+ var addedRows = lastRow - firstRow + 1;
+ return addedRows;
+ };
+
+
+ this.$clipRowToDocument = function(row) {
+ return Math.max(0, Math.min(row, this.doc.getLength()-1));
+ };
+
+ this.$clipColumnToRow = function(row, column) {
+ if (column < 0)
+ return 0;
+ return Math.min(this.doc.getLine(row).length, column);
+ };
+
+
+ this.$clipPositionToDocument = function(row, column) {
+ column = Math.max(0, column);
+
+ if (row < 0) {
+ row = 0;
+ column = 0;
+ } else {
+ var len = this.doc.getLength();
+ if (row >= len) {
+ row = len - 1;
+ column = this.doc.getLine(len-1).length;
+ } else {
+ column = Math.min(this.doc.getLine(row).length, column);
+ }
+ }
+
+ return {
+ row: row,
+ column: column
+ };
+ };
+
+ this.$clipRangeToDocument = function(range) {
+ if (range.start.row < 0) {
+ range.start.row = 0;
+ range.start.column = 0;
+ } else {
+ range.start.column = this.$clipColumnToRow(
+ range.start.row,
+ range.start.column
+ );
+ }
+
+ var len = this.doc.getLength() - 1;
+ if (range.end.row > len) {
+ range.end.row = len;
+ range.end.column = this.doc.getLine(len).length;
+ } else {
+ range.end.column = this.$clipColumnToRow(
+ range.end.row,
+ range.end.column
+ );
+ }
+ return range;
+ };
+
+ // WRAPMODE
+ this.$wrapLimit = 80;
+ this.$useWrapMode = false;
+ this.$wrapLimitRange = {
+ min : null,
+ max : null
+ };
+ this.setUseWrapMode = function(useWrapMode) {
+ if (useWrapMode != this.$useWrapMode) {
+ this.$useWrapMode = useWrapMode;
+ this.$modified = true;
+ this.$resetRowCache(0);
+
+ // If wrapMode is activaed, the wrapData array has to be initialized.
+ if (useWrapMode) {
+ var len = this.getLength();
+ this.$wrapData = [];
+ for (var i = 0; i < len; i++) {
+ this.$wrapData.push([]);
+ }
+ this.$updateWrapData(0, len - 1);
+ }
+
+ this._emit("changeWrapMode");
+ }
+ };
+ this.getUseWrapMode = function() {
+ return this.$useWrapMode;
+ };
+
+ // Allow the wrap limit to move freely between min and max. Either
+ // parameter can be null to allow the wrap limit to be unconstrained
+ // in that direction. Or set both parameters to the same number to pin
+ // the limit to that value.
+ /**
+ * EditSession.setWrapLimitRange(min, max)
+ * - min (Number): The minimum wrap value (the left side wrap)
+ * - max (Number): The maximum wrap value (the right side wrap)
+ *
+ * Sets the boundaries of wrap. Either value can be `null` to have an unconstrained wrap, or, they can be the same number to pin the limit. If the wrap limits for `min` or `max` are different, this method also emits the `'changeWrapMode'` event.
+ **/
+ this.setWrapLimitRange = function(min, max) {
+ if (this.$wrapLimitRange.min !== min || this.$wrapLimitRange.max !== max) {
+ this.$wrapLimitRange.min = min;
+ this.$wrapLimitRange.max = max;
+ this.$modified = true;
+ // This will force a recalculation of the wrap limit
+ this._emit("changeWrapMode");
+ }
+ };
+ this.adjustWrapLimit = function(desiredLimit) {
+ var wrapLimit = this.$constrainWrapLimit(desiredLimit);
+ if (wrapLimit != this.$wrapLimit && wrapLimit > 0) {
+ this.$wrapLimit = wrapLimit;
+ this.$modified = true;
+ if (this.$useWrapMode) {
+ this.$updateWrapData(0, this.getLength() - 1);
+ this.$resetRowCache(0);
+ this._emit("changeWrapLimit");
+ }
+ return true;
+ }
+ return false;
+ };
+ this.$constrainWrapLimit = function(wrapLimit) {
+ var min = this.$wrapLimitRange.min;
+ if (min)
+ wrapLimit = Math.max(min, wrapLimit);
+
+ var max = this.$wrapLimitRange.max;
+ if (max)
+ wrapLimit = Math.min(max, wrapLimit);
+
+ // What would a limit of 0 even mean?
+ return Math.max(1, wrapLimit);
+ };
+ this.getWrapLimit = function() {
+ return this.$wrapLimit;
+ };
+ this.getWrapLimitRange = function() {
+ // Avoid unexpected mutation by returning a copy
+ return {
+ min : this.$wrapLimitRange.min,
+ max : this.$wrapLimitRange.max
+ };
+ };
+ this.$updateInternalDataOnChange = function(e) {
+ var useWrapMode = this.$useWrapMode;
+ var len;
+ var action = e.data.action;
+ var firstRow = e.data.range.start.row;
+ var lastRow = e.data.range.end.row;
+ var start = e.data.range.start;
+ var end = e.data.range.end;
+ var removedFolds = null;
+
+ if (action.indexOf("Lines") != -1) {
+ if (action == "insertLines") {
+ lastRow = firstRow + (e.data.lines.length);
+ } else {
+ lastRow = firstRow;
+ }
+ len = e.data.lines ? e.data.lines.length : lastRow - firstRow;
+ } else {
+ len = lastRow - firstRow;
+ }
+
+ if (len != 0) {
+ if (action.indexOf("remove") != -1) {
+ this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
+
+ var foldLines = this.$foldData;
+ removedFolds = this.getFoldsInRange(e.data.range);
+ this.removeFolds(removedFolds);
+
+ var foldLine = this.getFoldLine(end.row);
+ var idx = 0;
+ if (foldLine) {
+ foldLine.addRemoveChars(end.row, end.column, start.column - end.column);
+ foldLine.shiftRow(-len);
+
+ var foldLineBefore = this.getFoldLine(firstRow);
+ if (foldLineBefore && foldLineBefore !== foldLine) {
+ foldLineBefore.merge(foldLine);
+ foldLine = foldLineBefore;
+ }
+ idx = foldLines.indexOf(foldLine) + 1;
+ }
+
+ for (idx; idx < foldLines.length; idx++) {
+ var foldLine = foldLines[idx];
+ if (foldLine.start.row >= end.row) {
+ foldLine.shiftRow(-len);
+ }
+ }
+
+ lastRow = firstRow;
+ } else {
+ var args;
+ if (useWrapMode) {
+ args = [firstRow, 0];
+ for (var i = 0; i < len; i++) args.push([]);
+ this.$wrapData.splice.apply(this.$wrapData, args);
+ } else {
+ args = Array(len);
+ args.unshift(firstRow, 0);
+ this.$rowLengthCache.splice.apply(this.$rowLengthCache, args);
+ }
+
+ // If some new line is added inside of a foldLine, then split
+ // the fold line up.
+ var foldLines = this.$foldData;
+ var foldLine = this.getFoldLine(firstRow);
+ var idx = 0;
+ if (foldLine) {
+ var cmp = foldLine.range.compareInside(start.row, start.column)
+ // Inside of the foldLine range. Need to split stuff up.
+ if (cmp == 0) {
+ foldLine = foldLine.split(start.row, start.column);
+ foldLine.shiftRow(len);
+ foldLine.addRemoveChars(
+ lastRow, 0, end.column - start.column);
+ } else
+ // Infront of the foldLine but same row. Need to shift column.
+ if (cmp == -1) {
+ foldLine.addRemoveChars(firstRow, 0, end.column - start.column);
+ foldLine.shiftRow(len);
+ }
+ // Nothing to do if the insert is after the foldLine.
+ idx = foldLines.indexOf(foldLine) + 1;
+ }
+
+ for (idx; idx < foldLines.length; idx++) {
+ var foldLine = foldLines[idx];
+ if (foldLine.start.row >= firstRow) {
+ foldLine.shiftRow(len);
+ }
+ }
+ }
+ } else {
+ // Realign folds. E.g. if you add some new chars before a fold, the
+ // fold should "move" to the right.
+ len = Math.abs(e.data.range.start.column - e.data.range.end.column);
+ if (action.indexOf("remove") != -1) {
+ // Get all the folds in the change range and remove them.
+ removedFolds = this.getFoldsInRange(e.data.range);
+ this.removeFolds(removedFolds);
+
+ len = -len;
+ }
+ var foldLine = this.getFoldLine(firstRow);
+ if (foldLine) {
+ foldLine.addRemoveChars(firstRow, start.column, len);
+ }
+ }
+
+ if (useWrapMode && this.$wrapData.length != this.doc.getLength()) {
+ console.error("doc.getLength() and $wrapData.length have to be the same!");
+ }
+
+ if (useWrapMode)
+ this.$updateWrapData(firstRow, lastRow);
+ else
+ this.$updateRowLengthCache(firstRow, lastRow);
+
+ return removedFolds;
+ };
+
+ this.$updateRowLengthCache = function(firstRow, lastRow, b) {
+ //console.log(firstRow, lastRow, b)
+ this.$rowLengthCache[firstRow] = null;
+ this.$rowLengthCache[lastRow] = null;
+ //console.log(this.$rowLengthCache)
+ };
+ this.$updateWrapData = function(firstRow, lastRow) {
+ var lines = this.doc.getAllLines();
+ var tabSize = this.getTabSize();
+ var wrapData = this.$wrapData;
+ var wrapLimit = this.$wrapLimit;
+ var tokens;
+ var foldLine;
+
+ var row = firstRow;
+ lastRow = Math.min(lastRow, lines.length - 1);
+ while (row <= lastRow) {
+ foldLine = this.getFoldLine(row, foldLine);
+ if (!foldLine) {
+ tokens = this.$getDisplayTokens(lang.stringTrimRight(lines[row]));
+ wrapData[row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize);
+ row ++;
+ } else {
+ tokens = [];
+ foldLine.walk(
+ function(placeholder, row, column, lastColumn) {
+ var walkTokens;
+ if (placeholder) {
+ walkTokens = this.$getDisplayTokens(
+ placeholder, tokens.length);
+ walkTokens[0] = PLACEHOLDER_START;
+ for (var i = 1; i < walkTokens.length; i++) {
+ walkTokens[i] = PLACEHOLDER_BODY;
+ }
+ } else {
+ walkTokens = this.$getDisplayTokens(
+ lines[row].substring(lastColumn, column),
+ tokens.length);
+ }
+ tokens = tokens.concat(walkTokens);
+ }.bind(this),
+ foldLine.end.row,
+ lines[foldLine.end.row].length + 1
+ );
+ // Remove spaces/tabs from the back of the token array.
+ while (tokens.length != 0 && tokens[tokens.length - 1] >= SPACE)
+ tokens.pop();
+
+ wrapData[foldLine.start.row]
+ = this.$computeWrapSplits(tokens, wrapLimit, tabSize);
+ row = foldLine.end.row + 1;
+ }
+ }
+ };
+
+ // "Tokens"
+ var CHAR = 1,
+ CHAR_EXT = 2,
+ PLACEHOLDER_START = 3,
+ PLACEHOLDER_BODY = 4,
+ PUNCTUATION = 9,
+ SPACE = 10,
+ TAB = 11,
+ TAB_SPACE = 12;
+ this.$computeWrapSplits = function(tokens, wrapLimit) {
+ if (tokens.length == 0) {
+ return [];
+ }
+
+ var splits = [];
+ var displayLength = tokens.length;
+ var lastSplit = 0, lastDocSplit = 0;
+
+ function addSplit(screenPos) {
+ var displayed = tokens.slice(lastSplit, screenPos);
+
+ // The document size is the current size - the extra width for tabs
+ // and multipleWidth characters.
+ var len = displayed.length;
+ displayed.join("").
+ // Get all the TAB_SPACEs.
+ replace(/12/g, function() {
+ len -= 1;
+ }).
+ // Get all the CHAR_EXT/multipleWidth characters.
+ replace(/2/g, function() {
+ len -= 1;
+ });
+
+ lastDocSplit += len;
+ splits.push(lastDocSplit);
+ lastSplit = screenPos;
+ }
+
+ while (displayLength - lastSplit > wrapLimit) {
+ // This is, where the split should be.
+ var split = lastSplit + wrapLimit;
+
+ // If there is a space or tab at this split position, then making
+ // a split is simple.
+ if (tokens[split] >= SPACE) {
+ // Include all following spaces + tabs in this split as well.
+ while (tokens[split] >= SPACE) {
+ split ++;
+ }
+ addSplit(split);
+ continue;
+ }
+
+ // === ELSE ===
+ // Check if split is inside of a placeholder. Placeholder are
+ // not splitable. Therefore, seek the beginning of the placeholder
+ // and try to place the split beofre the placeholder's start.
+ if (tokens[split] == PLACEHOLDER_START
+ || tokens[split] == PLACEHOLDER_BODY)
+ {
+ // Seek the start of the placeholder and do the split
+ // before the placeholder. By definition there always
+ // a PLACEHOLDER_START between split and lastSplit.
+ for (split; split != lastSplit - 1; split--) {
+ if (tokens[split] == PLACEHOLDER_START) {
+ // split++; << No incremental here as we want to
+ // have the position before the Placeholder.
+ break;
+ }
+ }
+
+ // If the PLACEHOLDER_START is not the index of the
+ // last split, then we can do the split
+ if (split > lastSplit) {
+ addSplit(split);
+ continue;
+ }
+
+ // If the PLACEHOLDER_START IS the index of the last
+ // split, then we have to place the split after the
+ // placeholder. So, let's seek for the end of the placeholder.
+ split = lastSplit + wrapLimit;
+ for (split; split < tokens.length; split++) {
+ if (tokens[split] != PLACEHOLDER_BODY)
+ {
+ break;
+ }
+ }
+
+ // If spilt == tokens.length, then the placeholder is the last
+ // thing in the line and adding a new split doesn't make sense.
+ if (split == tokens.length) {
+ break; // Breaks the while-loop.
+ }
+
+ // Finally, add the split...
+ addSplit(split);
+ continue;
+ }
+
+ // === ELSE ===
+ // Search for the first non space/tab/placeholder/punctuation token backwards.
+ var minSplit = Math.max(split - 10, lastSplit - 1);
+ while (split > minSplit && tokens[split] < PLACEHOLDER_START) {
+ split --;
+ }
+ while (split > minSplit && tokens[split] == PUNCTUATION) {
+ split --;
+ }
+ // If we found one, then add the split.
+ if (split > minSplit) {
+ addSplit(++split);
+ continue;
+ }
+
+ // === ELSE ===
+ split = lastSplit + wrapLimit;
+ // The split is inside of a CHAR or CHAR_EXT token and no space
+ // around -> force a split.
+ addSplit(split);
+ }
+ return splits;
+ };
+ this.$getDisplayTokens = function(str, offset) {
+ var arr = [];
+ var tabSize;
+ offset = offset || 0;
+
+ for (var i = 0; i < str.length; i++) {
+ var c = str.charCodeAt(i);
+ // Tab
+ if (c == 9) {
+ tabSize = this.getScreenTabSize(arr.length + offset);
+ arr.push(TAB);
+ for (var n = 1; n < tabSize; n++) {
+ arr.push(TAB_SPACE);
+ }
+ }
+ // Space
+ else if (c == 32) {
+ arr.push(SPACE);
+ } else if((c > 39 && c < 48) || (c > 57 && c < 64)) {
+ arr.push(PUNCTUATION);
+ }
+ // full width characters
+ else if (c >= 0x1100 && isFullWidth(c)) {
+ arr.push(CHAR, CHAR_EXT);
+ } else {
+ arr.push(CHAR);
+ }
+ }
+ return arr;
+ };
+ this.$getStringScreenWidth = function(str, maxScreenColumn, screenColumn) {
+ if (maxScreenColumn == 0)
+ return [0, 0];
+ if (maxScreenColumn == null)
+ maxScreenColumn = Infinity;
+ screenColumn = screenColumn || 0;
+
+ var c, column;
+ for (column = 0; column < str.length; column++) {
+ c = str.charCodeAt(column);
+ // tab
+ if (c == 9) {
+ screenColumn += this.getScreenTabSize(screenColumn);
+ }
+ // full width characters
+ else if (c >= 0x1100 && isFullWidth(c)) {
+ screenColumn += 2;
+ } else {
+ screenColumn += 1;
+ }
+ if (screenColumn > maxScreenColumn) {
+ break;
+ }
+ }
+
+ return [screenColumn, column];
+ };
+ this.getRowLength = function(row) {
+ if (!this.$useWrapMode || !this.$wrapData[row]) {
+ return 1;
+ } else {
+ return this.$wrapData[row].length + 1;
+ }
+ };
+ this.getScreenLastRowColumn = function(screenRow) {
+ var pos = this.screenToDocumentPosition(screenRow, Number.MAX_VALUE);
+ return this.documentToScreenColumn(pos.row, pos.column);
+ };
+ this.getDocumentLastRowColumn = function(docRow, docColumn) {
+ var screenRow = this.documentToScreenRow(docRow, docColumn);
+ return this.getScreenLastRowColumn(screenRow);
+ };
+ this.getDocumentLastRowColumnPosition = function(docRow, docColumn) {
+ var screenRow = this.documentToScreenRow(docRow, docColumn);
+ return this.screenToDocumentPosition(screenRow, Number.MAX_VALUE / 10);
+ };
+ this.getRowSplitData = function(row) {
+ if (!this.$useWrapMode) {
+ return undefined;
+ } else {
+ return this.$wrapData[row];
+ }
+ };
+ this.getScreenTabSize = function(screenColumn) {
+ return this.$tabSize - screenColumn % this.$tabSize;
+ };
+ this.screenToDocumentRow = function(screenRow, screenColumn) {
+ return this.screenToDocumentPosition(screenRow, screenColumn).row;
+ };
+ this.screenToDocumentColumn = function(screenRow, screenColumn) {
+ return this.screenToDocumentPosition(screenRow, screenColumn).column;
+ };
+ this.screenToDocumentPosition = function(screenRow, screenColumn) {
+ if (screenRow < 0)
+ return {row: 0, column: 0};
+
+ var line;
+ var docRow = 0;
+ var docColumn = 0;
+ var column;
+ var row = 0;
+ var rowLength = 0;
+
+ var rowCache = this.$screenRowCache;
+ var i = this.$getRowCacheIndex(rowCache, screenRow);
+ if (0 < i && i < rowCache.length) {
+ var row = rowCache[i];
+ var docRow = this.$docRowCache[i];
+ var doCache = screenRow > row || (screenRow == row && i == rowCache.length - 1);
+ } else {
+ var doCache = true;
+ }
+
+ var maxRow = this.getLength() - 1;
+ var foldLine = this.getNextFoldLine(docRow);
+ var foldStart = foldLine ? foldLine.start.row : Infinity;
+
+ while (row <= screenRow) {
+ rowLength = this.getRowLength(docRow);
+ if (row + rowLength - 1 >= screenRow || docRow >= maxRow) {
+ break;
+ } else {
+ row += rowLength;
+ docRow++;
+ if (docRow > foldStart) {
+ docRow = foldLine.end.row+1;
+ foldLine = this.getNextFoldLine(docRow, foldLine);
+ foldStart = foldLine ? foldLine.start.row : Infinity;
+ }
+ }
+ if (doCache) {
+ this.$docRowCache.push(docRow);
+ this.$screenRowCache.push(row);
+ }
+ }
+
+ if (foldLine && foldLine.start.row <= docRow) {
+ line = this.getFoldDisplayLine(foldLine);
+ docRow = foldLine.start.row;
+ } else if (row + rowLength <= screenRow || docRow > maxRow) {
+ // clip at the end of the document
+ return {
+ row: maxRow,
+ column: this.getLine(maxRow).length
+ }
+ } else {
+ line = this.getLine(docRow);
+ foldLine = null;
+ }
+
+ if (this.$useWrapMode) {
+ var splits = this.$wrapData[docRow];
+ if (splits) {
+ column = splits[screenRow - row];
+ if(screenRow > row && splits.length) {
+ docColumn = splits[screenRow - row - 1] || splits[splits.length - 1];
+ line = line.substring(docColumn);
+ }
+ }
+ }
+
+ docColumn += this.$getStringScreenWidth(line, screenColumn)[1];
+
+ // We remove one character at the end so that the docColumn
+ // position returned is not associated to the next row on the screen.
+ if (this.$useWrapMode && docColumn >= column)
+ docColumn = column - 1;
+
+ if (foldLine)
+ return foldLine.idxToPosition(docColumn);
+
+ return {row: docRow, column: docColumn};
+ };
+ this.documentToScreenPosition = function(docRow, docColumn) {
+ // Normalize the passed in arguments.
+ if (typeof docColumn === "undefined")
+ var pos = this.$clipPositionToDocument(docRow.row, docRow.column);
+ else
+ pos = this.$clipPositionToDocument(docRow, docColumn);
+
+ docRow = pos.row;
+ docColumn = pos.column;
+
+ var screenRow = 0;
+ var foldStartRow = null;
+ var fold = null;
+
+ // Clamp the docRow position in case it's inside of a folded block.
+ fold = this.getFoldAt(docRow, docColumn, 1);
+ if (fold) {
+ docRow = fold.start.row;
+ docColumn = fold.start.column;
+ }
+
+ var rowEnd, row = 0;
+
+
+ var rowCache = this.$docRowCache;
+ var i = this.$getRowCacheIndex(rowCache, docRow);
+ if (0 < i && i < rowCache.length) {
+ var row = rowCache[i];
+ var screenRow = this.$screenRowCache[i];
+ var doCache = docRow > row || (docRow == row && i == rowCache.length - 1);
+ } else {
+ var doCache = true;
+ }
+
+ var foldLine = this.getNextFoldLine(row);
+ var foldStart = foldLine ?foldLine.start.row :Infinity;
+
+ while (row < docRow) {
+ if (row >= foldStart) {
+ rowEnd = foldLine.end.row + 1;
+ if (rowEnd > docRow)
+ break;
+ foldLine = this.getNextFoldLine(rowEnd, foldLine);
+ foldStart = foldLine ?foldLine.start.row :Infinity;
+ }
+ else {
+ rowEnd = row + 1;
+ }
+
+ screenRow += this.getRowLength(row);
+ row = rowEnd;
+
+ if (doCache) {
+ this.$docRowCache.push(row);
+ this.$screenRowCache.push(screenRow);
+ }
+ }
+
+ // Calculate the text line that is displayed in docRow on the screen.
+ var textLine = "";
+ // Check if the final row we want to reach is inside of a fold.
+ if (foldLine && row >= foldStart) {
+ textLine = this.getFoldDisplayLine(foldLine, docRow, docColumn);
+ foldStartRow = foldLine.start.row;
+ } else {
+ textLine = this.getLine(docRow).substring(0, docColumn);
+ foldStartRow = docRow;
+ }
+ // Clamp textLine if in wrapMode.
+ if (this.$useWrapMode) {
+ var wrapRow = this.$wrapData[foldStartRow];
+ var screenRowOffset = 0;
+ while (textLine.length >= wrapRow[screenRowOffset]) {
+ screenRow ++;
+ screenRowOffset++;
+ }
+ textLine = textLine.substring(
+ wrapRow[screenRowOffset - 1] || 0, textLine.length
+ );
+ }
+
+ return {
+ row: screenRow,
+ column: this.$getStringScreenWidth(textLine)[0]
+ };
+ };
+ this.documentToScreenColumn = function(row, docColumn) {
+ return this.documentToScreenPosition(row, docColumn).column;
+ };
+ this.documentToScreenRow = function(docRow, docColumn) {
+ return this.documentToScreenPosition(docRow, docColumn).row;
+ };
+ this.getScreenLength = function() {
+ var screenRows = 0;
+ var fold = null;
+ if (!this.$useWrapMode) {
+ screenRows = this.getLength();
+
+ // Remove the folded lines again.
+ var foldData = this.$foldData;
+ for (var i = 0; i < foldData.length; i++) {
+ fold = foldData[i];
+ screenRows -= fold.end.row - fold.start.row;
+ }
+ } else {
+ var lastRow = this.$wrapData.length;
+ var row = 0, i = 0;
+ var fold = this.$foldData[i++];
+ var foldStart = fold ? fold.start.row :Infinity;
+
+ while (row < lastRow) {
+ screenRows += this.$wrapData[row].length + 1;
+ row ++;
+ if (row > foldStart) {
+ row = fold.end.row+1;
+ fold = this.$foldData[i++];
+ foldStart = fold ?fold.start.row :Infinity;
+ }
+ }
+ }
+
+ return screenRows;
+ }
+
+ // For every keystroke this gets called once per char in the whole doc!!
+ // Wouldn't hurt to make it a bit faster for c >= 0x1100
+ function isFullWidth(c) {
+ if (c < 0x1100)
+ return false;
+ return c >= 0x1100 && c <= 0x115F ||
+ c >= 0x11A3 && c <= 0x11A7 ||
+ c >= 0x11FA && c <= 0x11FF ||
+ c >= 0x2329 && c <= 0x232A ||
+ c >= 0x2E80 && c <= 0x2E99 ||
+ c >= 0x2E9B && c <= 0x2EF3 ||
+ c >= 0x2F00 && c <= 0x2FD5 ||
+ c >= 0x2FF0 && c <= 0x2FFB ||
+ c >= 0x3000 && c <= 0x303E ||
+ c >= 0x3041 && c <= 0x3096 ||
+ c >= 0x3099 && c <= 0x30FF ||
+ c >= 0x3105 && c <= 0x312D ||
+ c >= 0x3131 && c <= 0x318E ||
+ c >= 0x3190 && c <= 0x31BA ||
+ c >= 0x31C0 && c <= 0x31E3 ||
+ c >= 0x31F0 && c <= 0x321E ||
+ c >= 0x3220 && c <= 0x3247 ||
+ c >= 0x3250 && c <= 0x32FE ||
+ c >= 0x3300 && c <= 0x4DBF ||
+ c >= 0x4E00 && c <= 0xA48C ||
+ c >= 0xA490 && c <= 0xA4C6 ||
+ c >= 0xA960 && c <= 0xA97C ||
+ c >= 0xAC00 && c <= 0xD7A3 ||
+ c >= 0xD7B0 && c <= 0xD7C6 ||
+ c >= 0xD7CB && c <= 0xD7FB ||
+ c >= 0xF900 && c <= 0xFAFF ||
+ c >= 0xFE10 && c <= 0xFE19 ||
+ c >= 0xFE30 && c <= 0xFE52 ||
+ c >= 0xFE54 && c <= 0xFE66 ||
+ c >= 0xFE68 && c <= 0xFE6B ||
+ c >= 0xFF01 && c <= 0xFF60 ||
+ c >= 0xFFE0 && c <= 0xFFE6;
+ };
+
+}).call(EditSession.prototype);
+
+require("./edit_session/folding").Folding.call(EditSession.prototype);
+require("./edit_session/bracket_match").BracketMatch.call(EditSession.prototype);
+
+exports.EditSession = EditSession;
+});
+define('ace/lib/net', ['require', 'exports', 'module' , 'ace/lib/useragent'], function(require, exports, module) {
+
+
+var useragent = require("./useragent");
+
+exports.get = function (url, callback) {
+ var xhr = exports.createXhr();
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = function (evt) {
+ //Do not explicitly handle errors, those should be
+ //visible via console output in the browser.
+ if (xhr.readyState === 4) {
+ callback(xhr.responseText);
+ }
+ };
+ xhr.send(null);
+};
+
+var progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
+
+exports.createXhr = function() {
+ //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
+ var xhr, i, progId;
+ if (typeof XMLHttpRequest !== "undefined") {
+ return new XMLHttpRequest();
+ } else {
+ for (i = 0; i < 3; i++) {
+ progId = progIds[i];
+ try {
+ xhr = new ActiveXObject(progId);
+ } catch (e) {}
+
+ if (xhr) {
+ progIds = [progId]; // so faster next time
+ break;
+ }
+ }
+ }
+
+ if (!xhr) {
+ throw new Error("createXhr(): XMLHttpRequest not available");
+ }
+
+ return xhr;
+};
+
+exports.loadScript = function(path, callback) {
+ var head = document.getElementsByTagName('head')[0];
+ var s = document.createElement('script');
+
+ s.src = path;
+ head.appendChild(s);
+
+ if (useragent.isOldIE)
+ s.onreadystatechange = function () {
+ this.readyState == 'loaded' && callback();
+ };
+ else
+ s.onload = callback;
+};
+
+});
+
+define('ace/selection', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/lang', 'ace/lib/event_emitter', 'ace/range'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var lang = require("./lib/lang");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var Range = require("./range").Range;
+
+/**
+ * new Selection(session)
+ * - session (EditSession): The session to use
+ *
+ * Creates a new `Selection` object.
+ *
+**/
+var Selection = function(session) {
+ this.session = session;
+ this.doc = session.getDocument();
+
+ this.clearSelection();
+ this.lead = this.selectionLead = this.doc.createAnchor(0, 0);
+ this.anchor = this.selectionAnchor = this.doc.createAnchor(0, 0);
+
+ var self = this;
+ this.lead.on("change", function(e) {
+ self._emit("changeCursor");
+ if (!self.$isEmpty)
+ self._emit("changeSelection");
+ if (!self.$keepDesiredColumnOnChange && e.old.column != e.value.column)
+ self.$desiredColumn = null;
+ });
+
+ this.selectionAnchor.on("change", function() {
+ if (!self.$isEmpty)
+ self._emit("changeSelection");
+ });
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+ this.isEmpty = function() {
+ return (this.$isEmpty || (
+ this.anchor.row == this.lead.row &&
+ this.anchor.column == this.lead.column
+ ));
+ };
+ this.isMultiLine = function() {
+ if (this.isEmpty()) {
+ return false;
+ }
+
+ return this.getRange().isMultiLine();
+ };
+ this.getCursor = function() {
+ return this.lead.getPosition();
+ };
+ this.setSelectionAnchor = function(row, column) {
+ this.anchor.setPosition(row, column);
+
+ if (this.$isEmpty) {
+ this.$isEmpty = false;
+ this._emit("changeSelection");
+ }
+ };
+ this.getSelectionAnchor = function() {
+ if (this.$isEmpty)
+ return this.getSelectionLead()
+ else
+ return this.anchor.getPosition();
+ };
+ this.getSelectionLead = function() {
+ return this.lead.getPosition();
+ };
+ this.shiftSelection = function(columns) {
+ if (this.$isEmpty) {
+ this.moveCursorTo(this.lead.row, this.lead.column + columns);
+ return;
+ };
+
+ var anchor = this.getSelectionAnchor();
+ var lead = this.getSelectionLead();
+
+ var isBackwards = this.isBackwards();
+
+ if (!isBackwards || anchor.column !== 0)
+ this.setSelectionAnchor(anchor.row, anchor.column + columns);
+
+ if (isBackwards || lead.column !== 0) {
+ this.$moveSelection(function() {
+ this.moveCursorTo(lead.row, lead.column + columns);
+ });
+ }
+ };
+ this.isBackwards = function() {
+ var anchor = this.anchor;
+ var lead = this.lead;
+ return (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column));
+ };
+ this.getRange = function() {
+ var anchor = this.anchor;
+ var lead = this.lead;
+
+ if (this.isEmpty())
+ return Range.fromPoints(lead, lead);
+
+ if (this.isBackwards()) {
+ return Range.fromPoints(lead, anchor);
+ }
+ else {
+ return Range.fromPoints(anchor, lead);
+ }
+ };
+ this.clearSelection = function() {
+ if (!this.$isEmpty) {
+ this.$isEmpty = true;
+ this._emit("changeSelection");
+ }
+ };
+ this.selectAll = function() {
+ var lastRow = this.doc.getLength() - 1;
+ this.setSelectionAnchor(0, 0);
+ this.moveCursorTo(lastRow, this.doc.getLine(lastRow).length);
+ };
+ this.setRange =
+ this.setSelectionRange = function(range, reverse) {
+ if (reverse) {
+ this.setSelectionAnchor(range.end.row, range.end.column);
+ this.selectTo(range.start.row, range.start.column);
+ } else {
+ this.setSelectionAnchor(range.start.row, range.start.column);
+ this.selectTo(range.end.row, range.end.column);
+ }
+ this.$desiredColumn = null;
+ };
+
+ this.$moveSelection = function(mover) {
+ var lead = this.lead;
+ if (this.$isEmpty)
+ this.setSelectionAnchor(lead.row, lead.column);
+
+ mover.call(this);
+ };
+ this.selectTo = function(row, column) {
+ this.$moveSelection(function() {
+ this.moveCursorTo(row, column);
+ });
+ };
+ this.selectToPosition = function(pos) {
+ this.$moveSelection(function() {
+ this.moveCursorToPosition(pos);
+ });
+ };
+ this.selectUp = function() {
+ this.$moveSelection(this.moveCursorUp);
+ };
+ this.selectDown = function() {
+ this.$moveSelection(this.moveCursorDown);
+ };
+ this.selectRight = function() {
+ this.$moveSelection(this.moveCursorRight);
+ };
+ this.selectLeft = function() {
+ this.$moveSelection(this.moveCursorLeft);
+ };
+ this.selectLineStart = function() {
+ this.$moveSelection(this.moveCursorLineStart);
+ };
+ this.selectLineEnd = function() {
+ this.$moveSelection(this.moveCursorLineEnd);
+ };
+ this.selectFileEnd = function() {
+ this.$moveSelection(this.moveCursorFileEnd);
+ };
+ this.selectFileStart = function() {
+ this.$moveSelection(this.moveCursorFileStart);
+ };
+ this.selectWordRight = function() {
+ this.$moveSelection(this.moveCursorWordRight);
+ };
+ this.selectWordLeft = function() {
+ this.$moveSelection(this.moveCursorWordLeft);
+ };
+ this.getWordRange = function(row, column) {
+ if (typeof column == "undefined") {
+ var cursor = row || this.lead;
+ row = cursor.row;
+ column = cursor.column;
+ }
+ return this.session.getWordRange(row, column);
+ };
+
+ this.selectWord = function() {
+ this.setSelectionRange(this.getWordRange());
+ };
+ this.selectAWord = function() {
+ var cursor = this.getCursor();
+ var range = this.session.getAWordRange(cursor.row, cursor.column);
+ this.setSelectionRange(range);
+ };
+
+ this.getLineRange = function(row, excludeLastChar) {
+ var rowStart = typeof row == "number" ? row : this.lead.row;
+ var rowEnd;
+
+ var foldLine = this.session.getFoldLine(rowStart);
+ if (foldLine) {
+ rowStart = foldLine.start.row;
+ rowEnd = foldLine.end.row;
+ } else {
+ rowEnd = rowStart;
+ }
+ if (excludeLastChar)
+ return new Range(rowStart, 0, rowEnd, this.session.getLine(rowEnd).length);
+ else
+ return new Range(rowStart, 0, rowEnd + 1, 0);
+ };
+ this.selectLine = function() {
+ this.setSelectionRange(this.getLineRange());
+ };
+ this.moveCursorUp = function() {
+ this.moveCursorBy(-1, 0);
+ };
+ this.moveCursorDown = function() {
+ this.moveCursorBy(1, 0);
+ };
+ this.moveCursorLeft = function() {
+ var cursor = this.lead.getPosition(),
+ fold;
+
+ if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) {
+ this.moveCursorTo(fold.start.row, fold.start.column);
+ } else if (cursor.column == 0) {
+ // cursor is a line (start
+ if (cursor.row > 0) {
+ this.moveCursorTo(cursor.row - 1, this.doc.getLine(cursor.row - 1).length);
+ }
+ }
+ else {
+ var tabSize = this.session.getTabSize();
+ if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column-tabSize, cursor.column).split(" ").length-1 == tabSize)
+ this.moveCursorBy(0, -tabSize);
+ else
+ this.moveCursorBy(0, -1);
+ }
+ };
+ this.moveCursorRight = function() {
+ var cursor = this.lead.getPosition(),
+ fold;
+ if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) {
+ this.moveCursorTo(fold.end.row, fold.end.column);
+ }
+ else if (this.lead.column == this.doc.getLine(this.lead.row).length) {
+ if (this.lead.row < this.doc.getLength() - 1) {
+ this.moveCursorTo(this.lead.row + 1, 0);
+ }
+ }
+ else {
+ var tabSize = this.session.getTabSize();
+ var cursor = this.lead;
+ if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column, cursor.column+tabSize).split(" ").length-1 == tabSize)
+ this.moveCursorBy(0, tabSize);
+ else
+ this.moveCursorBy(0, 1);
+ }
+ };
+ this.moveCursorLineStart = function() {
+ var row = this.lead.row;
+ var column = this.lead.column;
+ var screenRow = this.session.documentToScreenRow(row, column);
+
+ // Determ the doc-position of the first character at the screen line.
+ var firstColumnPosition = this.session.screenToDocumentPosition(screenRow, 0);
+
+ // Determ the line
+ var beforeCursor = this.session.getDisplayLine(
+ row, null,
+ firstColumnPosition.row, firstColumnPosition.column
+ );
+
+ var leadingSpace = beforeCursor.match(/^\s*/);
+ if (leadingSpace[0].length == column) {
+ this.moveCursorTo(
+ firstColumnPosition.row, firstColumnPosition.column
+ );
+ }
+ else {
+ this.moveCursorTo(
+ firstColumnPosition.row,
+ firstColumnPosition.column + leadingSpace[0].length
+ );
+ }
+ };
+ this.moveCursorLineEnd = function() {
+ var lead = this.lead;
+ var lastRowColumnPosition =
+ this.session.getDocumentLastRowColumnPosition(lead.row, lead.column);
+ this.moveCursorTo(
+ lastRowColumnPosition.row,
+ lastRowColumnPosition.column
+ );
+ };
+ this.moveCursorFileEnd = function() {
+ var row = this.doc.getLength() - 1;
+ var column = this.doc.getLine(row).length;
+ this.moveCursorTo(row, column);
+ };
+ this.moveCursorFileStart = function() {
+ this.moveCursorTo(0, 0);
+ };
+ this.moveCursorLongWordRight = function() {
+ var row = this.lead.row;
+ var column = this.lead.column;
+ var line = this.doc.getLine(row);
+ var rightOfCursor = line.substring(column);
+
+ var match;
+ this.session.nonTokenRe.lastIndex = 0;
+ this.session.tokenRe.lastIndex = 0;
+
+ // skip folds
+ var fold = this.session.getFoldAt(row, column, 1);
+ if (fold) {
+ this.moveCursorTo(fold.end.row, fold.end.column);
+ return;
+ }
+
+ // first skip space
+ if (match = this.session.nonTokenRe.exec(rightOfCursor)) {
+ column += this.session.nonTokenRe.lastIndex;
+ this.session.nonTokenRe.lastIndex = 0;
+ rightOfCursor = line.substring(column);
+ }
+
+ // if at line end proceed with next line
+ if (column >= line.length) {
+ this.moveCursorTo(row, line.length);
+ this.moveCursorRight();
+ if (row < this.doc.getLength() - 1)
+ this.moveCursorWordRight();
+ return;
+ }
+
+ // advance to the end of the next token
+ if (match = this.session.tokenRe.exec(rightOfCursor)) {
+ column += this.session.tokenRe.lastIndex;
+ this.session.tokenRe.lastIndex = 0;
+ }
+
+ this.moveCursorTo(row, column);
+ };
+ this.moveCursorLongWordLeft = function() {
+ var row = this.lead.row;
+ var column = this.lead.column;
+
+ // skip folds
+ var fold;
+ if (fold = this.session.getFoldAt(row, column, -1)) {
+ this.moveCursorTo(fold.start.row, fold.start.column);
+ return;
+ }
+
+ var str = this.session.getFoldStringAt(row, column, -1);
+ if (str == null) {
+ str = this.doc.getLine(row).substring(0, column)
+ }
+
+ var leftOfCursor = lang.stringReverse(str);
+ var match;
+ this.session.nonTokenRe.lastIndex = 0;
+ this.session.tokenRe.lastIndex = 0;
+
+ // skip whitespace
+ if (match = this.session.nonTokenRe.exec(leftOfCursor)) {
+ column -= this.session.nonTokenRe.lastIndex;
+ leftOfCursor = leftOfCursor.slice(this.session.nonTokenRe.lastIndex);
+ this.session.nonTokenRe.lastIndex = 0;
+ }
+
+ // if at begin of the line proceed in line above
+ if (column <= 0) {
+ this.moveCursorTo(row, 0);
+ this.moveCursorLeft();
+ if (row > 0)
+ this.moveCursorWordLeft();
+ return;
+ }
+
+ // move to the begin of the word
+ if (match = this.session.tokenRe.exec(leftOfCursor)) {
+ column -= this.session.tokenRe.lastIndex;
+ this.session.tokenRe.lastIndex = 0;
+ }
+
+ this.moveCursorTo(row, column);
+ };
+
+ this.$shortWordEndIndex = function(rightOfCursor) {
+ var match, index = 0, ch;
+ var whitespaceRe = /\s/;
+ var tokenRe = this.session.tokenRe;
+
+ tokenRe.lastIndex = 0;
+ if (match = this.session.tokenRe.exec(rightOfCursor)) {
+ index = this.session.tokenRe.lastIndex;
+ } else {
+ while ((ch = rightOfCursor[index]) && whitespaceRe.test(ch))
+ index ++;
+
+ if (index <= 1) {
+ tokenRe.lastIndex = 0;
+ while ((ch = rightOfCursor[index]) && !tokenRe.test(ch)) {
+ tokenRe.lastIndex = 0;
+ index ++;
+ if (whitespaceRe.test(ch)) {
+ if (index > 2) {
+ index--
+ break;
+ } else {
+ while ((ch = rightOfCursor[index]) && whitespaceRe.test(ch))
+ index ++;
+ if (index > 2)
+ break
+ }
+ }
+ }
+ }
+ }
+ tokenRe.lastIndex = 0;
+
+ return index;
+ };
+
+ this.moveCursorShortWordRight = function() {
+ var row = this.lead.row;
+ var column = this.lead.column;
+ var line = this.doc.getLine(row);
+ var rightOfCursor = line.substring(column);
+
+ var fold = this.session.getFoldAt(row, column, 1);
+ if (fold)
+ return this.moveCursorTo(fold.end.row, fold.end.column);
+
+ if (column == line.length) {
+ var l = this.doc.getLength();
+ do {
+ row++;
+ rightOfCursor = this.doc.getLine(row)
+ } while (row < l && /^\s*$/.test(rightOfCursor))
+
+ if (!/^\s+/.test(rightOfCursor))
+ rightOfCursor = ""
+ column = 0;
+ }
+
+ var index = this.$shortWordEndIndex(rightOfCursor);
+
+ this.moveCursorTo(row, column + index);
+ };
+
+ this.moveCursorShortWordLeft = function() {
+ var row = this.lead.row;
+ var column = this.lead.column;
+
+ var fold;
+ if (fold = this.session.getFoldAt(row, column, -1))
+ return this.moveCursorTo(fold.start.row, fold.start.column);
+
+ var line = this.session.getLine(row).substring(0, column);
+ if (column == 0) {
+ do {
+ row--;
+ line = this.doc.getLine(row);
+ } while (row > 0 && /^\s*$/.test(line))
+
+ column = line.length;
+ if (!/\s+$/.test(line))
+ line = ""
+ }
+
+ var leftOfCursor = lang.stringReverse(line);
+ var index = this.$shortWordEndIndex(leftOfCursor);
+
+ return this.moveCursorTo(row, column - index);
+ };
+
+ this.moveCursorWordRight = function() {
+ if (this.session.$selectLongWords)
+ this.moveCursorLongWordRight();
+ else
+ this.moveCursorShortWordRight();
+ };
+
+ this.moveCursorWordLeft = function() {
+ if (this.session.$selectLongWords)
+ this.moveCursorLongWordLeft();
+ else
+ this.moveCursorShortWordLeft();
+ };
+ this.moveCursorBy = function(rows, chars) {
+ var screenPos = this.session.documentToScreenPosition(
+ this.lead.row,
+ this.lead.column
+ );
+
+ if (chars === 0) {
+ if (this.$desiredColumn)
+ screenPos.column = this.$desiredColumn;
+ else
+ this.$desiredColumn = screenPos.column;
+ }
+
+ var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenPos.column);
+
+ // move the cursor and update the desired column
+ this.moveCursorTo(docPos.row, docPos.column + chars, chars === 0);
+ };
+ this.moveCursorToPosition = function(position) {
+ this.moveCursorTo(position.row, position.column);
+ };
+ this.moveCursorTo = function(row, column, keepDesiredColumn) {
+ // Ensure the row/column is not inside of a fold.
+ var fold = this.session.getFoldAt(row, column, 1);
+ if (fold) {
+ row = fold.start.row;
+ column = fold.start.column;
+ }
+
+ this.$keepDesiredColumnOnChange = true;
+ this.lead.setPosition(row, column);
+ this.$keepDesiredColumnOnChange = false;
+
+ if (!keepDesiredColumn)
+ this.$desiredColumn = null;
+ };
+ this.moveCursorToScreen = function(row, column, keepDesiredColumn) {
+ var pos = this.session.screenToDocumentPosition(row, column);
+ this.moveCursorTo(pos.row, pos.column, keepDesiredColumn);
+ };
+
+ // remove listeners from document
+ this.detach = function() {
+ this.lead.detach();
+ this.anchor.detach();
+ this.session = this.doc = null;
+ }
+
+ this.fromOrientedRange = function(range) {
+ this.setSelectionRange(range, range.cursor == range.start);
+ this.$desiredColumn = range.desiredColumn || this.$desiredColumn;
+ }
+
+ this.toOrientedRange = function(range) {
+ var r = this.getRange();
+ if (range) {
+ range.start.column = r.start.column;
+ range.start.row = r.start.row;
+ range.end.column = r.end.column;
+ range.end.row = r.end.row;
+ } else {
+ range = r;
+ }
+
+ range.cursor = this.isBackwards() ? range.start : range.end;
+ range.desiredColumn = this.$desiredColumn;
+ return range;
+ }
+
+}).call(Selection.prototype);
+
+exports.Selection = Selection;
+});
+
+define('ace/document', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter', 'ace/range', 'ace/anchor'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var Range = require("./range").Range;
+var Anchor = require("./anchor").Anchor;
+
+ /**
+ * new Document([text])
+ * - text (String | Array): The starting text
+ *
+ * Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty.
+ *
+ **/
+var Document = function(text) {
+ this.$lines = [];
+
+ // There has to be one line at least in the document. If you pass an empty
+ // string to the insert function, nothing will happen. Workaround.
+ if (text.length == 0) {
+ this.$lines = [""];
+ } else if (Array.isArray(text)) {
+ this.insertLines(0, text);
+ } else {
+ this.insert({row: 0, column:0}, text);
+ }
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+ this.setValue = function(text) {
+ var len = this.getLength();
+ this.remove(new Range(0, 0, len, this.getLine(len-1).length));
+ this.insert({row: 0, column:0}, text);
+ };
+ this.getValue = function() {
+ return this.getAllLines().join(this.getNewLineCharacter());
+ };
+ this.createAnchor = function(row, column) {
+ return new Anchor(this, row, column);
+ };
+
+ // check for IE split bug
+ if ("aaa".split(/a/).length == 0)
+ this.$split = function(text) {
+ return text.replace(/\r\n|\r/g, "\n").split("\n");
+ }
+ else
+ this.$split = function(text) {
+ return text.split(/\r\n|\r|\n/);
+ };
+ this.$detectNewLine = function(text) {
+ var match = text.match(/^.*?(\r\n|\r|\n)/m);
+ if (match) {
+ this.$autoNewLine = match[1];
+ } else {
+ this.$autoNewLine = "\n";
+ }
+ };
+ this.getNewLineCharacter = function() {
+ switch (this.$newLineMode) {
+ case "windows":
+ return "\r\n";
+
+ case "unix":
+ return "\n";
+
+ case "auto":
+ return this.$autoNewLine;
+ }
+ };
+
+ this.$autoNewLine = "\n";
+ this.$newLineMode = "auto";
+ this.setNewLineMode = function(newLineMode) {
+ if (this.$newLineMode === newLineMode)
+ return;
+
+ this.$newLineMode = newLineMode;
+ };
+ this.getNewLineMode = function() {
+ return this.$newLineMode;
+ };
+ this.isNewLine = function(text) {
+ return (text == "\r\n" || text == "\r" || text == "\n");
+ };
+ this.getLine = function(row) {
+ return this.$lines[row] || "";
+ };
+ this.getLines = function(firstRow, lastRow) {
+ return this.$lines.slice(firstRow, lastRow + 1);
+ };
+ this.getAllLines = function() {
+ return this.getLines(0, this.getLength());
+ };
+ this.getLength = function() {
+ return this.$lines.length;
+ };
+ this.getTextRange = function(range) {
+ if (range.start.row == range.end.row) {
+ return this.$lines[range.start.row].substring(range.start.column,
+ range.end.column);
+ }
+ else {
+ var lines = this.getLines(range.start.row+1, range.end.row-1);
+ lines.unshift((this.$lines[range.start.row] || "").substring(range.start.column));
+ lines.push((this.$lines[range.end.row] || "").substring(0, range.end.column));
+ return lines.join(this.getNewLineCharacter());
+ }
+ };
+ this.$clipPosition = function(position) {
+ var length = this.getLength();
+ if (position.row >= length) {
+ position.row = Math.max(0, length - 1);
+ position.column = this.getLine(length-1).length;
+ }
+ return position;
+ };
+ this.insert = function(position, text) {
+ if (!text || text.length === 0)
+ return position;
+
+ position = this.$clipPosition(position);
+
+ // only detect new lines if the document has no line break yet
+ if (this.getLength() <= 1)
+ this.$detectNewLine(text);
+
+ var lines = this.$split(text);
+ var firstLine = lines.splice(0, 1)[0];
+ var lastLine = lines.length == 0 ? null : lines.splice(lines.length - 1, 1)[0];
+
+ position = this.insertInLine(position, firstLine);
+ if (lastLine !== null) {
+ position = this.insertNewLine(position); // terminate first line
+ position = this.insertLines(position.row, lines);
+ position = this.insertInLine(position, lastLine || "");
+ }
+ return position;
+ };
+ this.insertLines = function(row, lines) {
+ if (lines.length == 0)
+ return {row: row, column: 0};
+
+ // apply doesn't work for big arrays (smallest threshold is on safari 0xFFFF)
+ // to circumvent that we have to break huge inserts into smaller chunks here
+ if (lines.length > 0xFFFF) {
+ var end = this.insertLines(row, lines.slice(0xFFFF));
+ lines = lines.slice(0, 0xFFFF);
+ }
+
+ var args = [row, 0];
+ args.push.apply(args, lines);
+ this.$lines.splice.apply(this.$lines, args);
+
+ var range = new Range(row, 0, row + lines.length, 0);
+ var delta = {
+ action: "insertLines",
+ range: range,
+ lines: lines
+ };
+ this._emit("change", { data: delta });
+ return end || range.end;
+ };
+ this.insertNewLine = function(position) {
+ position = this.$clipPosition(position);
+ var line = this.$lines[position.row] || "";
+
+ this.$lines[position.row] = line.substring(0, position.column);
+ this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length));
+
+ var end = {
+ row : position.row + 1,
+ column : 0
+ };
+
+ var delta = {
+ action: "insertText",
+ range: Range.fromPoints(position, end),
+ text: this.getNewLineCharacter()
+ };
+ this._emit("change", { data: delta });
+
+ return end;
+ };
+ this.insertInLine = function(position, text) {
+ if (text.length == 0)
+ return position;
+
+ var line = this.$lines[position.row] || "";
+
+ this.$lines[position.row] = line.substring(0, position.column) + text
+ + line.substring(position.column);
+
+ var end = {
+ row : position.row,
+ column : position.column + text.length
+ };
+
+ var delta = {
+ action: "insertText",
+ range: Range.fromPoints(position, end),
+ text: text
+ };
+ this._emit("change", { data: delta });
+
+ return end;
+ };
+ this.remove = function(range) {
+ // clip to document
+ range.start = this.$clipPosition(range.start);
+ range.end = this.$clipPosition(range.end);
+
+ if (range.isEmpty())
+ return range.start;
+
+ var firstRow = range.start.row;
+ var lastRow = range.end.row;
+
+ if (range.isMultiLine()) {
+ var firstFullRow = range.start.column == 0 ? firstRow : firstRow + 1;
+ var lastFullRow = lastRow - 1;
+
+ if (range.end.column > 0)
+ this.removeInLine(lastRow, 0, range.end.column);
+
+ if (lastFullRow >= firstFullRow)
+ this.removeLines(firstFullRow, lastFullRow);
+
+ if (firstFullRow != firstRow) {
+ this.removeInLine(firstRow, range.start.column, this.getLine(firstRow).length);
+ this.removeNewLine(range.start.row);
+ }
+ }
+ else {
+ this.removeInLine(firstRow, range.start.column, range.end.column);
+ }
+ return range.start;
+ };
+ this.removeInLine = function(row, startColumn, endColumn) {
+ if (startColumn == endColumn)
+ return;
+
+ var range = new Range(row, startColumn, row, endColumn);
+ var line = this.getLine(row);
+ var removed = line.substring(startColumn, endColumn);
+ var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length);
+ this.$lines.splice(row, 1, newLine);
+
+ var delta = {
+ action: "removeText",
+ range: range,
+ text: removed
+ };
+ this._emit("change", { data: delta });
+ return range.start;
+ };
+ this.removeLines = function(firstRow, lastRow) {
+ var range = new Range(firstRow, 0, lastRow + 1, 0);
+ var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1);
+
+ var delta = {
+ action: "removeLines",
+ range: range,
+ nl: this.getNewLineCharacter(),
+ lines: removed
+ };
+ this._emit("change", { data: delta });
+ return removed;
+ };
+ this.removeNewLine = function(row) {
+ var firstLine = this.getLine(row);
+ var secondLine = this.getLine(row+1);
+
+ var range = new Range(row, firstLine.length, row+1, 0);
+ var line = firstLine + secondLine;
+
+ this.$lines.splice(row, 2, line);
+
+ var delta = {
+ action: "removeText",
+ range: range,
+ text: this.getNewLineCharacter()
+ };
+ this._emit("change", { data: delta });
+ };
+ this.replace = function(range, text) {
+ if (text.length == 0 && range.isEmpty())
+ return range.start;
+
+ // Shortcut: If the text we want to insert is the same as it is already
+ // in the document, we don't have to replace anything.
+ if (text == this.getTextRange(range))
+ return range.end;
+
+ this.remove(range);
+ if (text) {
+ var end = this.insert(range.start, text);
+ }
+ else {
+ end = range.start;
+ }
+
+ return end;
+ };
+ this.applyDeltas = function(deltas) {
+ for (var i=0; i<deltas.length; i++) {
+ var delta = deltas[i];
+ var range = Range.fromPoints(delta.range.start, delta.range.end);
+
+ if (delta.action == "insertLines")
+ this.insertLines(range.start.row, delta.lines);
+ else if (delta.action == "insertText")
+ this.insert(range.start, delta.text);
+ else if (delta.action == "removeLines")
+ this.removeLines(range.start.row, range.end.row - 1);
+ else if (delta.action == "removeText")
+ this.remove(range);
+ }
+ };
+ this.revertDeltas = function(deltas) {
+ for (var i=deltas.length-1; i>=0; i--) {
+ var delta = deltas[i];
+
+ var range = Range.fromPoints(delta.range.start, delta.range.end);
+
+ if (delta.action == "insertLines")
+ this.removeLines(range.start.row, range.end.row - 1);
+ else if (delta.action == "insertText")
+ this.remove(range);
+ else if (delta.action == "removeLines")
+ this.insertLines(range.start.row, delta.lines);
+ else if (delta.action == "removeText")
+ this.insert(range.start, delta.text);
+ }
+ };
+
+}).call(Document.prototype);
+
+exports.Document = Document;
+});
+
+define('ace/anchor', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+
+/**
+ * new Anchor(doc, row, column)
+ * - doc (Document): The document to associate with the anchor
+ * - row (Number): The starting row position
+ * - column (Number): The starting column position
+ *
+ * Creates a new `Anchor` and associates it with a document.
+ *
+ **/
+
+var Anchor = exports.Anchor = function(doc, row, column) {
+ this.document = doc;
+
+ if (typeof column == "undefined")
+ this.setPosition(row.row, row.column);
+ else
+ this.setPosition(row, column);
+
+ this.$onChange = this.onChange.bind(this);
+ doc.on("change", this.$onChange);
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+
+ this.getPosition = function() {
+ return this.$clipPositionToDocument(this.row, this.column);
+ };
+
+ this.getDocument = function() {
+ return this.document;
+ };
+
+ this.onChange = function(e) {
+ var delta = e.data;
+ var range = delta.range;
+
+ if (range.start.row == range.end.row && range.start.row != this.row)
+ return;
+
+ if (range.start.row > this.row)
+ return;
+
+ if (range.start.row == this.row && range.start.column > this.column)
+ return;
+
+ var row = this.row;
+ var column = this.column;
+
+ if (delta.action === "insertText") {
+ if (range.start.row === row && range.start.column <= column) {
+ if (range.start.row === range.end.row) {
+ column += range.end.column - range.start.column;
+ }
+ else {
+ column -= range.start.column;
+ row += range.end.row - range.start.row;
+ }
+ }
+ else if (range.start.row !== range.end.row && range.start.row < row) {
+ row += range.end.row - range.start.row;
+ }
+ } else if (delta.action === "insertLines") {
+ if (range.start.row <= row) {
+ row += range.end.row - range.start.row;
+ }
+ }
+ else if (delta.action == "removeText") {
+ if (range.start.row == row && range.start.column < column) {
+ if (range.end.column >= column)
+ column = range.start.column;
+ else
+ column = Math.max(0, column - (range.end.column - range.start.column));
+
+ } else if (range.start.row !== range.end.row && range.start.row < row) {
+ if (range.end.row == row) {
+ column = Math.max(0, column - range.end.column) + range.start.column;
+ }
+ row -= (range.end.row - range.start.row);
+ }
+ else if (range.end.row == row) {
+ row -= range.end.row - range.start.row;
+ column = Math.max(0, column - range.end.column) + range.start.column;
+ }
+ } else if (delta.action == "removeLines") {
+ if (range.start.row <= row) {
+ if (range.end.row <= row)
+ row -= range.end.row - range.start.row;
+ else {
+ row = range.start.row;
+ column = 0;
+ }
+ }
+ }
+
+ this.setPosition(row, column, true);
+ };
+
+ this.setPosition = function(row, column, noClip) {
+ var pos;
+ if (noClip) {
+ pos = {
+ row: row,
+ column: column
+ };
+ }
+ else {
+ pos = this.$clipPositionToDocument(row, column);
+ }
+
+ if (this.row == pos.row && this.column == pos.column)
+ return;
+
+ var old = {
+ row: this.row,
+ column: this.column
+ };
+
+ this.row = pos.row;
+ this.column = pos.column;
+ this._emit("change", {
+ old: old,
+ value: pos
+ });
+ };
+
+ this.detach = function() {
+ this.document.removeEventListener("change", this.$onChange);
+ };
+
+ this.$clipPositionToDocument = function(row, column) {
+ var pos = {};
+
+ if (row >= this.document.getLength()) {
+ pos.row = Math.max(0, this.document.getLength() - 1);
+ pos.column = this.document.getLine(pos.row).length;
+ }
+ else if (row < 0) {
+ pos.row = 0;
+ pos.column = 0;
+ }
+ else {
+ pos.row = row;
+ pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
+ }
+
+ if (column < 0)
+ pos.column = 0;
+
+ return pos;
+ };
+
+}).call(Anchor.prototype);
+
+});
+
+define('ace/background_tokenizer', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+
+// tokenizing lines longer than this makes editor very slow
+var MAX_LINE_LENGTH = 5000;
+
+/**
+ * new BackgroundTokenizer(tokenizer, editor)
+ * - tokenizer (Tokenizer): The tokenizer to use
+ * - editor (Editor): The editor to associate with
+ *
+ * Creates a new `BackgroundTokenizer` object.
+ *
+ *
+ **/
+
+var BackgroundTokenizer = function(tokenizer, editor) {
+ this.running = false;
+ this.lines = [];
+ this.states = [];
+ this.currentLine = 0;
+ this.tokenizer = tokenizer;
+
+ var self = this;
+
+ this.$worker = function() {
+ if (!self.running) { return; }
+
+ var workerStart = new Date();
+ var startLine = self.currentLine;
+ var doc = self.doc;
+
+ var processedLines = 0;
+
+ var len = doc.getLength();
+ while (self.currentLine < len) {
+ self.$tokenizeRow(self.currentLine);
+ while (self.lines[self.currentLine])
+ self.currentLine++;
+
+ // only check every 5 lines
+ processedLines ++;
+ if ((processedLines % 5 == 0) && (new Date() - workerStart) > 20) {
+ self.fireUpdateEvent(startLine, self.currentLine-1);
+ self.running = setTimeout(self.$worker, 20);
+ return;
+ }
+ }
+
+ self.running = false;
+
+ self.fireUpdateEvent(startLine, len - 1);
+ };
+};
+
+(function(){
+
+ oop.implement(this, EventEmitter);
+ this.setTokenizer = function(tokenizer) {
+ this.tokenizer = tokenizer;
+ this.lines = [];
+ this.states = [];
+
+ this.start(0);
+ };
+ this.setDocument = function(doc) {
+ this.doc = doc;
+ this.lines = [];
+ this.states = [];
+
+ this.stop();
+ };
+ this.fireUpdateEvent = function(firstRow, lastRow) {
+ var data = {
+ first: firstRow,
+ last: lastRow
+ };
+ this._emit("update", {data: data});
+ };
+ this.start = function(startRow) {
+ this.currentLine = Math.min(startRow || 0, this.currentLine, this.doc.getLength());
+
+ // remove all cached items below this line
+ this.lines.splice(this.currentLine, this.lines.length);
+ this.states.splice(this.currentLine, this.states.length);
+
+ this.stop();
+ // pretty long delay to prevent the tokenizer from interfering with the user
+ this.running = setTimeout(this.$worker, 700);
+ };
+
+ this.$updateOnChange = function(delta) {
+ var range = delta.range;
+ var startRow = range.start.row;
+ var len = range.end.row - startRow;
+
+ if (len === 0) {
+ this.lines[startRow] = null;
+ } else if (delta.action == "removeText" || delta.action == "removeLines") {
+ this.lines.splice(startRow, len + 1, null);
+ this.states.splice(startRow, len + 1, null);
+ } else {
+ var args = Array(len + 1);
+ args.unshift(startRow, 1);
+ this.lines.splice.apply(this.lines, args);
+ this.states.splice.apply(this.states, args);
+ }
+
+ this.currentLine = Math.min(startRow, this.currentLine, this.doc.getLength());
+
+ this.stop();
+ // pretty long delay to prevent the tokenizer from interfering with the user
+ this.running = setTimeout(this.$worker, 700);
+ };
+ this.stop = function() {
+ if (this.running)
+ clearTimeout(this.running);
+ this.running = false;
+ };
+ this.getTokens = function(row) {
+ return this.lines[row] || this.$tokenizeRow(row);
+ };
+ this.getState = function(row) {
+ if (this.currentLine == row)
+ this.$tokenizeRow(row);
+ return this.states[row] || "start";
+ };
+
+ this.$tokenizeRow = function(row) {
+ var line = this.doc.getLine(row);
+ var state = this.states[row - 1];
+
+ if (line.length > MAX_LINE_LENGTH) {
+ var overflow = {value: line.substr(MAX_LINE_LENGTH), type: "text"};
+ line = line.slice(0, MAX_LINE_LENGTH);
+ }
+ var data = this.tokenizer.getLineTokens(line, state);
+ if (overflow) {
+ data.tokens.push(overflow);
+ data.state = "start";
+ }
+
+ if (this.states[row] !== data.state) {
+ this.states[row] = data.state;
+ this.lines[row + 1] = null;
+ if (this.currentLine > row + 1)
+ this.currentLine = row + 1;
+ } else if (this.currentLine == row) {
+ this.currentLine = row + 1;
+ }
+
+ return this.lines[row] = data.tokens;
+ };
+
+}).call(BackgroundTokenizer.prototype);
+
+exports.BackgroundTokenizer = BackgroundTokenizer;
+});
+
+define('ace/search_highlight', ['require', 'exports', 'module' , 'ace/lib/lang', 'ace/lib/oop', 'ace/range'], function(require, exports, module) {
+
+
+var lang = require("./lib/lang");
+var oop = require("./lib/oop");
+var Range = require("./range").Range;
+
+var SearchHighlight = function(regExp, clazz, type) {
+ this.setRegexp(regExp);
+ this.clazz = clazz;
+ this.type = type || "text";
+};
+
+(function() {
+ this.setRegexp = function(regExp) {
+ if (this.regExp+"" == regExp+"")
+ return;
+ this.regExp = regExp;
+ this.cache = [];
+ };
+
+ this.update = function(html, markerLayer, session, config) {
+ if (!this.regExp)
+ return;
+ var start = config.firstRow, end = config.lastRow;
+
+ for (var i = start; i <= end; i++) {
+ var ranges = this.cache[i];
+ if (ranges == null) {
+ ranges = lang.getMatchOffsets(session.getLine(i), this.regExp);
+ ranges = ranges.map(function(match) {
+ return new Range(i, match.offset, i, match.offset + match.length);
+ });
+ this.cache[i] = ranges.length ? ranges : "";
+ }
+
+ for (var j = ranges.length; j --; ) {
+ markerLayer.drawSingleLineMarker(
+ html, ranges[j].toScreenRange(session), this.clazz, config,
+ null, this.type
+ );
+ }
+ }
+ };
+
+}).call(SearchHighlight.prototype);
+
+exports.SearchHighlight = SearchHighlight;
+});
+
+define('ace/edit_session/folding', ['require', 'exports', 'module' , 'ace/range', 'ace/edit_session/fold_line', 'ace/edit_session/fold', 'ace/token_iterator'], function(require, exports, module) {
+
+
+var Range = require("../range").Range;
+var FoldLine = require("./fold_line").FoldLine;
+var Fold = require("./fold").Fold;
+var TokenIterator = require("../token_iterator").TokenIterator;
+
+function Folding() {
+ /*
+ * Looks up a fold at a given row/column. Possible values for side:
+ * -1: ignore a fold if fold.start = row/column
+ * +1: ignore a fold if fold.end = row/column
+ */
+ this.getFoldAt = function(row, column, side) {
+ var foldLine = this.getFoldLine(row);
+ if (!foldLine)
+ return null;
+
+ var folds = foldLine.folds;
+ for (var i = 0; i < folds.length; i++) {
+ var fold = folds[i];
+ if (fold.range.contains(row, column)) {
+ if (side == 1 && fold.range.isEnd(row, column)) {
+ continue;
+ } else if (side == -1 && fold.range.isStart(row, column)) {
+ continue;
+ }
+ return fold;
+ }
+ }
+ };
+ this.getFoldsInRange = function(range) {
+ range = range.clone();
+ var start = range.start;
+ var end = range.end;
+ var foldLines = this.$foldData;
+ var foundFolds = [];
+
+ start.column += 1;
+ end.column -= 1;
+
+ for (var i = 0; i < foldLines.length; i++) {
+ var cmp = foldLines[i].range.compareRange(range);
+ if (cmp == 2) {
+ // Range is before foldLine. No intersection. This means,
+ // there might be other foldLines that intersect.
+ continue;
+ }
+ else if (cmp == -2) {
+ // Range is after foldLine. There can't be any other foldLines then,
+ // so let's give up.
+ break;
+ }
+
+ var folds = foldLines[i].folds;
+ for (var j = 0; j < folds.length; j++) {
+ var fold = folds[j];
+ cmp = fold.range.compareRange(range);
+ if (cmp == -2) {
+ break;
+ } else if (cmp == 2) {
+ continue;
+ } else
+ // WTF-state: Can happen due to -1/+1 to start/end column.
+ if (cmp == 42) {
+ break;
+ }
+ foundFolds.push(fold);
+ }
+ }
+ return foundFolds;
+ };
+ this.getAllFolds = function() {
+ var folds = [];
+ var foldLines = this.$foldData;
+
+ function addFold(fold) {
+ folds.push(fold);
+ if (!fold.subFolds)
+ return;
+
+ for (var i = 0; i < fold.subFolds.length; i++)
+ addFold(fold.subFolds[i]);
+ }
+
+ for (var i = 0; i < foldLines.length; i++)
+ for (var j = 0; j < foldLines[i].folds.length; j++)
+ addFold(foldLines[i].folds[j]);
+
+ return folds;
+ };
+ this.getFoldStringAt = function(row, column, trim, foldLine) {
+ foldLine = foldLine || this.getFoldLine(row);
+ if (!foldLine)
+ return null;
+
+ var lastFold = {
+ end: { column: 0 }
+ };
+ // TODO: Refactor to use getNextFoldTo function.
+ var str, fold;
+ for (var i = 0; i < foldLine.folds.length; i++) {
+ fold = foldLine.folds[i];
+ var cmp = fold.range.compareEnd(row, column);
+ if (cmp == -1) {
+ str = this
+ .getLine(fold.start.row)
+ .substring(lastFold.end.column, fold.start.column);
+ break;
+ }
+ else if (cmp === 0) {
+ return null;
+ }
+ lastFold = fold;
+ }
+ if (!str)
+ str = this.getLine(fold.start.row).substring(lastFold.end.column);
+
+ if (trim == -1)
+ return str.substring(0, column - lastFold.end.column);
+ else if (trim == 1)
+ return str.substring(column - lastFold.end.column);
+ else
+ return str;
+ };
+
+ this.getFoldLine = function(docRow, startFoldLine) {
+ var foldData = this.$foldData;
+ var i = 0;
+ if (startFoldLine)
+ i = foldData.indexOf(startFoldLine);
+ if (i == -1)
+ i = 0;
+ for (i; i < foldData.length; i++) {
+ var foldLine = foldData[i];
+ if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) {
+ return foldLine;
+ } else if (foldLine.end.row > docRow) {
+ return null;
+ }
+ }
+ return null;
+ };
+
+ // returns the fold which starts after or contains docRow
+ this.getNextFoldLine = function(docRow, startFoldLine) {
+ var foldData = this.$foldData;
+ var i = 0;
+ if (startFoldLine)
+ i = foldData.indexOf(startFoldLine);
+ if (i == -1)
+ i = 0;
+ for (i; i < foldData.length; i++) {
+ var foldLine = foldData[i];
+ if (foldLine.end.row >= docRow) {
+ return foldLine;
+ }
+ }
+ return null;
+ };
+
+ this.getFoldedRowCount = function(first, last) {
+ var foldData = this.$foldData, rowCount = last-first+1;
+ for (var i = 0; i < foldData.length; i++) {
+ var foldLine = foldData[i],
+ end = foldLine.end.row,
+ start = foldLine.start.row;
+ if (end >= last) {
+ if(start < last) {
+ if(start >= first)
+ rowCount -= last-start;
+ else
+ rowCount = 0;//in one fold
+ }
+ break;
+ } else if(end >= first){
+ if (start >= first) //fold inside range
+ rowCount -= end-start;
+ else
+ rowCount -= end-first+1;
+ }
+ }
+ return rowCount;
+ };
+
+ this.$addFoldLine = function(foldLine) {
+ this.$foldData.push(foldLine);
+ this.$foldData.sort(function(a, b) {
+ return a.start.row - b.start.row;
+ });
+ return foldLine;
+ };
+ this.addFold = function(placeholder, range) {
+ var foldData = this.$foldData;
+ var added = false;
+ var fold;
+
+ if (placeholder instanceof Fold)
+ fold = placeholder;
+ else
+ fold = new Fold(range, placeholder);
+
+ this.$clipRangeToDocument(fold.range);
+
+ var startRow = fold.start.row;
+ var startColumn = fold.start.column;
+ var endRow = fold.end.row;
+ var endColumn = fold.end.column;
+
+ // --- Some checking ---
+ if (fold.placeholder.length < 2)
+ throw "Placeholder has to be at least 2 characters";
+
+ if (startRow == endRow && endColumn - startColumn < 2)
+ throw "The range has to be at least 2 characters width";
+
+ var startFold = this.getFoldAt(startRow, startColumn, 1);
+ var endFold = this.getFoldAt(endRow, endColumn, -1);
+ if (startFold && endFold == startFold)
+ return startFold.addSubFold(fold);
+
+ if (
+ (startFold && !startFold.range.isStart(startRow, startColumn))
+ || (endFold && !endFold.range.isEnd(endRow, endColumn))
+ ) {
+ throw "A fold can't intersect already existing fold" + fold.range + startFold.range;
+ }
+
+ // Check if there are folds in the range we create the new fold for.
+ var folds = this.getFoldsInRange(fold.range);
+ if (folds.length > 0) {
+ // Remove the folds from fold data.
+ this.removeFolds(folds);
+ // Add the removed folds as subfolds on the new fold.
+ fold.subFolds = folds;
+ }
+
+ for (var i = 0; i < foldData.length; i++) {
+ var foldLine = foldData[i];
+ if (endRow == foldLine.start.row) {
+ foldLine.addFold(fold);
+ added = true;
+ break;
+ }
+ else if (startRow == foldLine.end.row) {
+ foldLine.addFold(fold);
+ added = true;
+ if (!fold.sameRow) {
+ // Check if we might have to merge two FoldLines.
+ var foldLineNext = foldData[i + 1];
+ if (foldLineNext && foldLineNext.start.row == endRow) {
+ // We need to merge!
+ foldLine.merge(foldLineNext);
+ break;
+ }
+ }
+ break;
+ }
+ else if (endRow <= foldLine.start.row) {
+ break;
+ }
+ }
+
+ if (!added)
+ foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold));
+
+ if (this.$useWrapMode)
+ this.$updateWrapData(foldLine.start.row, foldLine.start.row);
+ else
+ this.$updateRowLengthCache(foldLine.start.row, foldLine.start.row);
+
+ // Notify that fold data has changed.
+ this.$modified = true;
+ this._emit("changeFold", { data: fold });
+
+ return fold;
+ };
+
+ this.addFolds = function(folds) {
+ folds.forEach(function(fold) {
+ this.addFold(fold);
+ }, this);
+ };
+
+ this.removeFold = function(fold) {
+ var foldLine = fold.foldLine;
+ var startRow = foldLine.start.row;
+ var endRow = foldLine.end.row;
+
+ var foldLines = this.$foldData;
+ var folds = foldLine.folds;
+ // Simple case where there is only one fold in the FoldLine such that
+ // the entire fold line can get removed directly.
+ if (folds.length == 1) {
+ foldLines.splice(foldLines.indexOf(foldLine), 1);
+ } else
+ // If the fold is the last fold of the foldLine, just remove it.
+ if (foldLine.range.isEnd(fold.end.row, fold.end.column)) {
+ folds.pop();
+ foldLine.end.row = folds[folds.length - 1].end.row;
+ foldLine.end.column = folds[folds.length - 1].end.column;
+ } else
+ // If the fold is the first fold of the foldLine, just remove it.
+ if (foldLine.range.isStart(fold.start.row, fold.start.column)) {
+ folds.shift();
+ foldLine.start.row = folds[0].start.row;
+ foldLine.start.column = folds[0].start.column;
+ } else
+ // We know there are more then 2 folds and the fold is not at the edge.
+ // This means, the fold is somewhere in between.
+ //
+ // If the fold is in one row, we just can remove it.
+ if (fold.sameRow) {
+ folds.splice(folds.indexOf(fold), 1);
+ } else
+ // The fold goes over more then one row. This means remvoing this fold
+ // will cause the fold line to get splitted up. newFoldLine is the second part
+ {
+ var newFoldLine = foldLine.split(fold.start.row, fold.start.column);
+ folds = newFoldLine.folds;
+ folds.shift();
+ newFoldLine.start.row = folds[0].start.row;
+ newFoldLine.start.column = folds[0].start.column;
+ }
+
+ if (this.$useWrapMode)
+ this.$updateWrapData(startRow, endRow);
+ else
+ this.$updateRowLengthCache(startRow, endRow);
+
+ // Notify that fold data has changed.
+ this.$modified = true;
+ this._emit("changeFold", { data: fold });
+ };
+
+ this.removeFolds = function(folds) {
+ // We need to clone the folds array passed in as it might be the folds
+ // array of a fold line and as we call this.removeFold(fold), folds
+ // are removed from folds and changes the current index.
+ var cloneFolds = [];
+ for (var i = 0; i < folds.length; i++) {
+ cloneFolds.push(folds[i]);
+ }
+
+ cloneFolds.forEach(function(fold) {
+ this.removeFold(fold);
+ }, this);
+ this.$modified = true;
+ };
+
+ this.expandFold = function(fold) {
+ this.removeFold(fold);
+ fold.subFolds.forEach(function(fold) {
+ this.addFold(fold);
+ }, this);
+ fold.subFolds = [];
+ };
+
+ this.expandFolds = function(folds) {
+ folds.forEach(function(fold) {
+ this.expandFold(fold);
+ }, this);
+ };
+
+ this.unfold = function(location, expandInner) {
+ var range, folds;
+ if (location == null)
+ range = new Range(0, 0, this.getLength(), 0);
+ else if (typeof location == "number")
+ range = new Range(location, 0, location, this.getLine(location).length);
+ else if ("row" in location)
+ range = Range.fromPoints(location, location);
+ else
+ range = location;
+
+ folds = this.getFoldsInRange(range);
+ if (expandInner) {
+ this.removeFolds(folds);
+ } else {
+ // TODO: might need to remove and add folds in one go instead of using
+ // expandFolds several times.
+ while (folds.length) {
+ this.expandFolds(folds);
+ folds = this.getFoldsInRange(range);
+ }
+ }
+ };
+ this.isRowFolded = function(docRow, startFoldRow) {
+ return !!this.getFoldLine(docRow, startFoldRow);
+ };
+
+ this.getRowFoldEnd = function(docRow, startFoldRow) {
+ var foldLine = this.getFoldLine(docRow, startFoldRow);
+ return foldLine ? foldLine.end.row : docRow;
+ };
+
+ this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) {
+ if (startRow == null) {
+ startRow = foldLine.start.row;
+ startColumn = 0;
+ }
+
+ if (endRow == null) {
+ endRow = foldLine.end.row;
+ endColumn = this.getLine(endRow).length;
+ }
+
+ // Build the textline using the FoldLine walker.
+ var doc = this.doc;
+ var textLine = "";
+
+ foldLine.walk(function(placeholder, row, column, lastColumn) {
+ if (row < startRow) {
+ return;
+ } else if (row == startRow) {
+ if (column < startColumn) {
+ return;
+ }
+ lastColumn = Math.max(startColumn, lastColumn);
+ }
+ if (placeholder) {
+ textLine += placeholder;
+ } else {
+ textLine += doc.getLine(row).substring(lastColumn, column);
+ }
+ }.bind(this), endRow, endColumn);
+ return textLine;
+ };
+
+ this.getDisplayLine = function(row, endColumn, startRow, startColumn) {
+ var foldLine = this.getFoldLine(row);
+
+ if (!foldLine) {
+ var line;
+ line = this.doc.getLine(row);
+ return line.substring(startColumn || 0, endColumn || line.length);
+ } else {
+ return this.getFoldDisplayLine(
+ foldLine, row, endColumn, startRow, startColumn);
+ }
+ };
+
+ this.$cloneFoldData = function() {
+ var fd = [];
+ fd = this.$foldData.map(function(foldLine) {
+ var folds = foldLine.folds.map(function(fold) {
+ return fold.clone();
+ });
+ return new FoldLine(fd, folds);
+ });
+
+ return fd;
+ };
+
+ this.toggleFold = function(tryToUnfold) {
+ var selection = this.selection;
+ var range = selection.getRange();
+ var fold;
+ var bracketPos;
+
+ if (range.isEmpty()) {
+ var cursor = range.start;
+ fold = this.getFoldAt(cursor.row, cursor.column);
+
+ if (fold) {
+ this.expandFold(fold);
+ return;
+ }
+ else if (bracketPos = this.findMatchingBracket(cursor)) {
+ if (range.comparePoint(bracketPos) == 1) {
+ range.end = bracketPos;
+ }
+ else {
+ range.start = bracketPos;
+ range.start.column++;
+ range.end.column--;
+ }
+ }
+ else if (bracketPos = this.findMatchingBracket({row: cursor.row, column: cursor.column + 1})) {
+ if (range.comparePoint(bracketPos) == 1)
+ range.end = bracketPos;
+ else
+ range.start = bracketPos;
+
+ range.start.column++;
+ }
+ else {
+ range = this.getCommentFoldRange(cursor.row, cursor.column) || range;
+ }
+ } else {
+ var folds = this.getFoldsInRange(range);
+ if (tryToUnfold && folds.length) {
+ this.expandFolds(folds);
+ return;
+ }
+ else if (folds.length == 1 ) {
+ fold = folds[0];
+ }
+ }
+
+ if (!fold)
+ fold = this.getFoldAt(range.start.row, range.start.column);
+
+ if (fold && fold.range.toString() == range.toString()) {
+ this.expandFold(fold);
+ return;
+ }
+
+ var placeholder = "...";
+ if (!range.isMultiLine()) {
+ placeholder = this.getTextRange(range);
+ if(placeholder.length < 4)
+ return;
+ placeholder = placeholder.trim().substring(0, 2) + "..";
+ }
+
+ this.addFold(placeholder, range);
+ };
+
+ this.getCommentFoldRange = function(row, column) {
+ var iterator = new TokenIterator(this, row, column);
+ var token = iterator.getCurrentToken();
+ if (token && /^comment|string/.test(token.type)) {
+ var range = new Range();
+ var re = new RegExp(token.type.replace(/\..*/, "\\."));
+ do {
+ token = iterator.stepBackward();
+ } while(token && re.test(token.type));
+
+ iterator.stepForward();
+ range.start.row = iterator.getCurrentTokenRow();
+ range.start.column = iterator.getCurrentTokenColumn() + 2;
+
+ iterator = new TokenIterator(this, row, column);
+
+ do {
+ token = iterator.stepForward();
+ } while(token && re.test(token.type));
+
+ token = iterator.stepBackward();
+
+ range.end.row = iterator.getCurrentTokenRow();
+ range.end.column = iterator.getCurrentTokenColumn() + token.value.length;
+ return range;
+ }
+ };
+
+ this.foldAll = function(startRow, endRow) {
+ var foldWidgets = this.foldWidgets;
+ endRow = endRow || this.getLength();
+ for (var row = startRow || 0; row < endRow; row++) {
+ if (foldWidgets[row] == null)
+ foldWidgets[row] = this.getFoldWidget(row);
+ if (foldWidgets[row] != "start")
+ continue;
+
+ var range = this.getFoldWidgetRange(row);
+ // sometimes range can be incompatible with existing fold
+ // wouldn't it be better for addFold to return null istead of throwing?
+ if (range && range.end.row < endRow) try {
+ this.addFold("...", range);
+ } catch(e) {}
+ }
+ };
+
+ this.$foldStyles = {
+ "manual": 1,
+ "markbegin": 1,
+ "markbeginend": 1
+ };
+ this.$foldStyle = "markbegin";
+ this.setFoldStyle = function(style) {
+ if (!this.$foldStyles[style])
+ throw new Error("invalid fold style: " + style + "[" + Object.keys(this.$foldStyles).join(", ") + "]");
+
+ if (this.$foldStyle == style)
+ return;
+
+ this.$foldStyle = style;
+
+ if (style == "manual")
+ this.unfold();
+
+ // reset folding
+ var mode = this.$foldMode;
+ this.$setFolding(null);
+ this.$setFolding(mode);
+ };
+
+ // structured folding
+ this.$setFolding = function(foldMode) {
+ if (this.$foldMode == foldMode)
+ return;
+
+ this.$foldMode = foldMode;
+
+ this.removeListener('change', this.$updateFoldWidgets);
+ this._emit("changeAnnotation");
+
+ if (!foldMode || this.$foldStyle == "manual") {
+ this.foldWidgets = null;
+ return;
+ }
+
+ this.foldWidgets = [];
+ this.getFoldWidget = foldMode.getFoldWidget.bind(foldMode, this, this.$foldStyle);
+ this.getFoldWidgetRange = foldMode.getFoldWidgetRange.bind(foldMode, this, this.$foldStyle);
+
+ this.$updateFoldWidgets = this.updateFoldWidgets.bind(this);
+ this.on('change', this.$updateFoldWidgets);
+
+ };
+
+ this.onFoldWidgetClick = function(row, e) {
+ var type = this.getFoldWidget(row);
+ var line = this.getLine(row);
+ var onlySubfolds = e.shiftKey;
+ var addSubfolds = onlySubfolds || e.ctrlKey || e.altKey || e.metaKey;
+ var fold;
+
+ if (type == "end")
+ fold = this.getFoldAt(row, 0, -1);
+ else
+ fold = this.getFoldAt(row, line.length, 1);
+
+ if (fold) {
+ if (addSubfolds)
+ this.removeFold(fold);
+ else
+ this.expandFold(fold);
+ return;
+ }
+
+ var range = this.getFoldWidgetRange(row);
+ if (range) {
+ // sometimes singleline folds can be missed by the code above
+ if (!range.isMultiLine()) {
+ fold = this.getFoldAt(range.start.row, range.start.column, 1);
+ if (fold && range.isEqual(fold.range)) {
+ this.removeFold(fold);
+ return;
+ }
+ }
+
+ if (!onlySubfolds)
+ this.addFold("...", range);
+
+ if (addSubfolds)
+ this.foldAll(range.start.row + 1, range.end.row);
+ } else {
+ if (addSubfolds)
+ this.foldAll(row + 1, this.getLength());
+ (e.target || e.srcElement).className += " invalid"
+ }
+ };
+
+ this.updateFoldWidgets = function(e) {
+ var delta = e.data;
+ var range = delta.range;
+ var firstRow = range.start.row;
+ var len = range.end.row - firstRow;
+
+ if (len === 0) {
+ this.foldWidgets[firstRow] = null;
+ } else if (delta.action == "removeText" || delta.action == "removeLines") {
+ this.foldWidgets.splice(firstRow, len + 1, null);
+ } else {
+ var args = Array(len + 1);
+ args.unshift(firstRow, 1);
+ this.foldWidgets.splice.apply(this.foldWidgets, args);
+ }
+ };
+
+}
+
+exports.Folding = Folding;
+
+});
+
+define('ace/edit_session/fold_line', ['require', 'exports', 'module' , 'ace/range'], function(require, exports, module) {
+
+
+var Range = require("../range").Range;
+function FoldLine(foldData, folds) {
+ this.foldData = foldData;
+ if (Array.isArray(folds)) {
+ this.folds = folds;
+ } else {
+ folds = this.folds = [ folds ];
+ }
+
+ var last = folds[folds.length - 1]
+ this.range = new Range(folds[0].start.row, folds[0].start.column,
+ last.end.row, last.end.column);
+ this.start = this.range.start;
+ this.end = this.range.end;
+
+ this.folds.forEach(function(fold) {
+ fold.setFoldLine(this);
+ }, this);
+}
+
+(function() {
+ /*
+ * Note: This doesn't update wrapData!
+ */
+ this.shiftRow = function(shift) {
+ this.start.row += shift;
+ this.end.row += shift;
+ this.folds.forEach(function(fold) {
+ fold.start.row += shift;
+ fold.end.row += shift;
+ });
+ }
+
+ this.addFold = function(fold) {
+ if (fold.sameRow) {
+ if (fold.start.row < this.startRow || fold.endRow > this.endRow) {
+ throw "Can't add a fold to this FoldLine as it has no connection";
+ }
+ this.folds.push(fold);
+ this.folds.sort(function(a, b) {
+ return -a.range.compareEnd(b.start.row, b.start.column);
+ });
+ if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) {
+ this.end.row = fold.end.row;
+ this.end.column = fold.end.column;
+ } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) {
+ this.start.row = fold.start.row;
+ this.start.column = fold.start.column;
+ }
+ } else if (fold.start.row == this.end.row) {
+ this.folds.push(fold);
+ this.end.row = fold.end.row;
+ this.end.column = fold.end.column;
+ } else if (fold.end.row == this.start.row) {
+ this.folds.unshift(fold);
+ this.start.row = fold.start.row;
+ this.start.column = fold.start.column;
+ } else {
+ throw "Trying to add fold to FoldRow that doesn't have a matching row";
+ }
+ fold.foldLine = this;
+ }
+
+ this.containsRow = function(row) {
+ return row >= this.start.row && row <= this.end.row;
+ }
+
+ this.walk = function(callback, endRow, endColumn) {
+ var lastEnd = 0,
+ folds = this.folds,
+ fold,
+ comp, stop, isNewRow = true;
+
+ if (endRow == null) {
+ endRow = this.end.row;
+ endColumn = this.end.column;
+ }
+
+ for (var i = 0; i < folds.length; i++) {
+ fold = folds[i];
+
+ comp = fold.range.compareStart(endRow, endColumn);
+ // This fold is after the endRow/Column.
+ if (comp == -1) {
+ callback(null, endRow, endColumn, lastEnd, isNewRow);
+ return;
+ }
+
+ stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow);
+ stop = !stop && callback(fold.placeholder, fold.start.row, fold.start.column, lastEnd);
+
+ // If the user requested to stop the walk or endRow/endColumn is
+ // inside of this fold (comp == 0), then end here.
+ if (stop || comp == 0) {
+ return;
+ }
+
+ // Note the new lastEnd might not be on the same line. However,
+ // it's the callback's job to recognize this.
+ isNewRow = !fold.sameRow;
+ lastEnd = fold.end.column;
+ }
+ callback(null, endRow, endColumn, lastEnd, isNewRow);
+ }
+
+ this.getNextFoldTo = function(row, column) {
+ var fold, cmp;
+ for (var i = 0; i < this.folds.length; i++) {
+ fold = this.folds[i];
+ cmp = fold.range.compareEnd(row, column);
+ if (cmp == -1) {
+ return {
+ fold: fold,
+ kind: "after"
+ };
+ } else if (cmp == 0) {
+ return {
+ fold: fold,
+ kind: "inside"
+ }
+ }
+ }
+ return null;
+ }
+
+ this.addRemoveChars = function(row, column, len) {
+ var ret = this.getNextFoldTo(row, column),
+ fold, folds;
+ if (ret) {
+ fold = ret.fold;
+ if (ret.kind == "inside"
+ && fold.start.column != column
+ && fold.start.row != row)
+ {
+ //throwing here breaks whole editor
+ //@todo properly handle this
+ window.console && window.console.log(row, column, fold);
+ } else if (fold.start.row == row) {
+ folds = this.folds;
+ var i = folds.indexOf(fold);
+ if (i == 0) {
+ this.start.column += len;
+ }
+ for (i; i < folds.length; i++) {
+ fold = folds[i];
+ fold.start.column += len;
+ if (!fold.sameRow) {
+ return;
+ }
+ fold.end.column += len;
+ }
+ this.end.column += len;
+ }
+ }
+ }
+
+ this.split = function(row, column) {
+ var fold = this.getNextFoldTo(row, column).fold,
+ folds = this.folds;
+ var foldData = this.foldData;
+
+ if (!fold) {
+ return null;
+ }
+ var i = folds.indexOf(fold);
+ var foldBefore = folds[i - 1];
+ this.end.row = foldBefore.end.row;
+ this.end.column = foldBefore.end.column;
+
+ // Remove the folds after row/column and create a new FoldLine
+ // containing these removed folds.
+ folds = folds.splice(i, folds.length - i);
+
+ var newFoldLine = new FoldLine(foldData, folds);
+ foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine);
+ return newFoldLine;
+ }
+
+ this.merge = function(foldLineNext) {
+ var folds = foldLineNext.folds;
+ for (var i = 0; i < folds.length; i++) {
+ this.addFold(folds[i]);
+ }
+ // Remove the foldLineNext - no longer needed, as
+ // it's merged now with foldLineNext.
+ var foldData = this.foldData;
+ foldData.splice(foldData.indexOf(foldLineNext), 1);
+ }
+
+ this.toString = function() {
+ var ret = [this.range.toString() + ": [" ];
+
+ this.folds.forEach(function(fold) {
+ ret.push(" " + fold.toString());
+ });
+ ret.push("]")
+ return ret.join("\n");
+ }
+
+ this.idxToPosition = function(idx) {
+ var lastFoldEndColumn = 0;
+ var fold;
+
+ for (var i = 0; i < this.folds.length; i++) {
+ var fold = this.folds[i];
+
+ idx -= fold.start.column - lastFoldEndColumn;
+ if (idx < 0) {
+ return {
+ row: fold.start.row,
+ column: fold.start.column + idx
+ };
+ }
+
+ idx -= fold.placeholder.length;
+ if (idx < 0) {
+ return fold.start;
+ }
+
+ lastFoldEndColumn = fold.end.column;
+ }
+
+ return {
+ row: this.end.row,
+ column: this.end.column + idx
+ };
+ }
+}).call(FoldLine.prototype);
+
+exports.FoldLine = FoldLine;
+});
+
+define('ace/edit_session/fold', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/*
+ * Simple fold-data struct.
+ **/
+var Fold = exports.Fold = function(range, placeholder) {
+ this.foldLine = null;
+ this.placeholder = placeholder;
+ this.range = range;
+ this.start = range.start;
+ this.end = range.end;
+
+ this.sameRow = range.start.row == range.end.row;
+ this.subFolds = [];
+};
+
+(function() {
+
+ this.toString = function() {
+ return '"' + this.placeholder + '" ' + this.range.toString();
+ };
+
+ this.setFoldLine = function(foldLine) {
+ this.foldLine = foldLine;
+ this.subFolds.forEach(function(fold) {
+ fold.setFoldLine(foldLine);
+ });
+ };
+
+ this.clone = function() {
+ var range = this.range.clone();
+ var fold = new Fold(range, this.placeholder);
+ this.subFolds.forEach(function(subFold) {
+ fold.subFolds.push(subFold.clone());
+ });
+ return fold;
+ };
+
+ this.addSubFold = function(fold) {
+ if (this.range.isEqual(fold))
+ return this;
+
+ if (!this.range.containsRange(fold))
+ throw "A fold can't intersect already existing fold" + fold.range + this.range;
+
+ var row = fold.range.start.row, column = fold.range.start.column;
+ for (var i = 0, cmp = -1; i < this.subFolds.length; i++) {
+ cmp = this.subFolds[i].range.compare(row, column);
+ if (cmp != 1)
+ break;
+ }
+ var afterStart = this.subFolds[i];
+
+ if (cmp == 0)
+ return afterStart.addSubFold(fold);
+
+ // cmp == -1
+ var row = fold.range.end.row, column = fold.range.end.column;
+ for (var j = i, cmp = -1; j < this.subFolds.length; j++) {
+ cmp = this.subFolds[j].range.compare(row, column);
+ if (cmp != 1)
+ break;
+ }
+ var afterEnd = this.subFolds[j];
+
+ if (cmp == 0)
+ throw "A fold can't intersect already existing fold" + fold.range + this.range;
+
+ var consumedFolds = this.subFolds.splice(i, j - i, fold);
+ fold.setFoldLine(this.foldLine);
+
+ return fold;
+ };
+
+}).call(Fold.prototype);
+
+});
+
+define('ace/edit_session/bracket_match', ['require', 'exports', 'module' , 'ace/token_iterator', 'ace/range'], function(require, exports, module) {
+
+
+var TokenIterator = require("../token_iterator").TokenIterator;
+var Range = require("../range").Range;
+
+/**
+ * new BracketMatch(position)
+ * - platform (String): Identifier for the platform; must be either `'mac'` or `'win'`
+ * - commands (Array): A list of commands
+ *
+ * TODO
+ *
+ *
+ **/
+function BracketMatch() {
+
+ /**
+ * new findMatchingBracket(position)
+ * - position (Number): Identifier for the platform; must be either `'mac'` or `'win'`
+ * - commands (Array): A list of commands
+ *
+ * TODO
+ *
+ *
+ **/
+ this.findMatchingBracket = function(position) {
+ if (position.column == 0) return null;
+
+ var charBeforeCursor = this.getLine(position.row).charAt(position.column-1);
+ if (charBeforeCursor == "") return null;
+
+ var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/);
+ if (!match)
+ return null;
+
+ if (match[1])
+ return this.$findClosingBracket(match[1], position);
+ else
+ return this.$findOpeningBracket(match[2], position);
+ };
+
+ this.getBracketRange = function(pos) {
+ var line = this.getLine(pos.row);
+ var before = true, range;
+
+ var chr = line.charAt(pos.column-1);
+ var match = chr && chr.match(/([\(\[\{])|([\)\]\}])/);
+ if (!match) {
+ chr = line.charAt(pos.column);
+ pos = {row: pos.row, column: pos.column + 1};
+ match = chr && chr.match(/([\(\[\{])|([\)\]\}])/);
+ before = false;
+ }
+ if (!match)
+ return null;
+
+ if (match[1]) {
+ var bracketPos = this.$findClosingBracket(match[1], pos);
+ if (!bracketPos)
+ return null;
+ range = Range.fromPoints(pos, bracketPos);
+ if (!before) {
+ range.end.column++;
+ range.start.column--;
+ }
+ range.cursor = range.end;
+ } else {
+ var bracketPos = this.$findOpeningBracket(match[2], pos);
+ if (!bracketPos)
+ return null;
+ range = Range.fromPoints(bracketPos, pos);
+ if (!before) {
+ range.start.column++;
+ range.end.column--;
+ }
+ range.cursor = range.start;
+ }
+
+ return range;
+ };
+
+ this.$brackets = {
+ ")": "(",
+ "(": ")",
+ "]": "[",
+ "[": "]",
+ "{": "}",
+ "}": "{"
+ };
+
+ this.$findOpeningBracket = function(bracket, position, typeRe) {
+ var openBracket = this.$brackets[bracket];
+ var depth = 1;
+
+ var iterator = new TokenIterator(this, position.row, position.column);
+ var token = iterator.getCurrentToken();
+ if (!token)
+ token = iterator.stepForward();
+ if (!token)
+ return;
+
+ if (!typeRe){
+ typeRe = new RegExp(
+ "(\\.?" +
+ token.type.replace(".", "\\.").replace("rparen", ".paren")
+ + ")+"
+ );
+ }
+
+ // Start searching in token, just before the character at position.column
+ var valueIndex = position.column - iterator.getCurrentTokenColumn() - 2;
+ var value = token.value;
+
+ while (true) {
+
+ while (valueIndex >= 0) {
+ var chr = value.charAt(valueIndex);
+ if (chr == openBracket) {
+ depth -= 1;
+ if (depth == 0) {
+ return {row: iterator.getCurrentTokenRow(),
+ column: valueIndex + iterator.getCurrentTokenColumn()};
+ }
+ }
+ else if (chr == bracket) {
+ depth += 1;
+ }
+ valueIndex -= 1;
+ }
+
+ // Scan backward through the document, looking for the next token
+ // whose type matches typeRe
+ do {
+ token = iterator.stepBackward();
+ } while (token && !typeRe.test(token.type));
+
+ if (token == null)
+ break;
+
+ value = token.value;
+ valueIndex = value.length - 1;
+ }
+
+ return null;
+ };
+
+ this.$findClosingBracket = function(bracket, position, typeRe) {
+ var closingBracket = this.$brackets[bracket];
+ var depth = 1;
+
+ var iterator = new TokenIterator(this, position.row, position.column);
+ var token = iterator.getCurrentToken();
+ if (!token)
+ token = iterator.stepForward();
+ if (!token)
+ return;
+
+ if (!typeRe){
+ typeRe = new RegExp(
+ "(\\.?" +
+ token.type.replace(".", "\\.").replace("lparen", ".paren")
+ + ")+"
+ );
+ }
+
+ // Start searching in token, after the character at position.column
+ var valueIndex = position.column - iterator.getCurrentTokenColumn();
+
+ while (true) {
+
+ var value = token.value;
+ var valueLength = value.length;
+ while (valueIndex < valueLength) {
+ var chr = value.charAt(valueIndex);
+ if (chr == closingBracket) {
+ depth -= 1;
+ if (depth == 0) {
+ return {row: iterator.getCurrentTokenRow(),
+ column: valueIndex + iterator.getCurrentTokenColumn()};
+ }
+ }
+ else if (chr == bracket) {
+ depth += 1;
+ }
+ valueIndex += 1;
+ }
+
+ // Scan forward through the document, looking for the next token
+ // whose type matches typeRe
+ do {
+ token = iterator.stepForward();
+ } while (token && !typeRe.test(token.type));
+
+ if (token == null)
+ break;
+
+ valueIndex = 0;
+ }
+
+ return null;
+ };
+}
+exports.BracketMatch = BracketMatch;
+
+});
+define('ace/commands/command_manager', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/keyboard/hash_handler', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var HashHandler = require("../keyboard/hash_handler").HashHandler;
+var EventEmitter = require("../lib/event_emitter").EventEmitter;
+
+/**
+ * new CommandManager(platform, commands)
+ * - platform (String): Identifier for the platform; must be either `'mac'` or `'win'`
+ * - commands (Array): A list of commands
+ *
+ * TODO
+ *
+ *
+ **/
+
+var CommandManager = function(platform, commands) {
+ this.platform = platform;
+ this.commands = {};
+ this.commmandKeyBinding = {};
+
+ this.addCommands(commands);
+
+ this.setDefaultHandler("exec", function(e) {
+ return e.command.exec(e.editor, e.args || {});
+ });
+};
+
+oop.inherits(CommandManager, HashHandler);
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+
+ this.exec = function(command, editor, args) {
+ if (typeof command === 'string')
+ command = this.commands[command];
+
+ if (!command)
+ return false;
+
+ if (editor && editor.$readOnly && !command.readOnly)
+ return false;
+
+ var retvalue = this._emit("exec", {
+ editor: editor,
+ command: command,
+ args: args
+ });
+
+ return retvalue === false ? false : true;
+ };
+
+ this.toggleRecording = function() {
+ if (this.$inReplay)
+ return;
+ if (this.recording) {
+ this.macro.pop();
+ this.removeEventListener("exec", this.$addCommandToMacro);
+
+ if (!this.macro.length)
+ this.macro = this.oldMacro;
+
+ return this.recording = false;
+ }
+ if (!this.$addCommandToMacro) {
+ this.$addCommandToMacro = function(e) {
+ this.macro.push([e.command, e.args]);
+ }.bind(this);
+ }
+
+ this.oldMacro = this.macro;
+ this.macro = [];
+ this.on("exec", this.$addCommandToMacro);
+ return this.recording = true;
+ };
+
+ this.replay = function(editor) {
+ if (this.$inReplay || !this.macro)
+ return;
+
+ if (this.recording)
+ return this.toggleRecording();
+
+ try {
+ this.$inReplay = true;
+ this.macro.forEach(function(x) {
+ if (typeof x == "string")
+ this.exec(x, editor);
+ else
+ this.exec(x[0], editor, x[1]);
+ }, this);
+ } finally {
+ this.$inReplay = false;
+ }
+ };
+
+ this.trimMacro = function(m) {
+ return m.map(function(x){
+ if (typeof x[0] != "string")
+ x[0] = x[0].name;
+ if (!x[1])
+ x = x[0];
+ return x;
+ });
+ };
+
+}).call(CommandManager.prototype);
+
+exports.CommandManager = CommandManager;
+
+});
+
+define('ace/commands/default_commands', ['require', 'exports', 'module' , 'ace/lib/lang'], function(require, exports, module) {
+
+
+var lang = require("../lib/lang");
+
+function bindKey(win, mac) {
+ return {
+ win: win,
+ mac: mac
+ };
+}
+
+exports.commands = [{
+ name: "selectall",
+ bindKey: bindKey("Ctrl-A", "Command-A"),
+ exec: function(editor) { editor.selectAll(); },
+ readOnly: true
+}, {
+ name: "centerselection",
+ bindKey: bindKey(null, "Ctrl-L"),
+ exec: function(editor) { editor.centerSelection(); },
+ readOnly: true
+}, {
+ name: "gotoline",
+ bindKey: bindKey("Ctrl-L", "Command-L"),
+ exec: function(editor) {
+ var line = parseInt(prompt("Enter line number:"), 10);
+ if (!isNaN(line)) {
+ editor.gotoLine(line);
+ }
+ },
+ readOnly: true
+}, {
+ name: "fold",
+ bindKey: bindKey("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"),
+ exec: function(editor) { editor.session.toggleFold(false); },
+ readOnly: true
+}, {
+ name: "unfold",
+ bindKey: bindKey("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"),
+ exec: function(editor) { editor.session.toggleFold(true); },
+ readOnly: true
+}, {
+ name: "foldall",
+ bindKey: bindKey("Alt-0", "Command-Option-0"),
+ exec: function(editor) { editor.session.foldAll(); },
+ readOnly: true
+}, {
+ name: "unfoldall",
+ bindKey: bindKey("Alt-Shift-0", "Command-Option-Shift-0"),
+ exec: function(editor) { editor.session.unfold(); },
+ readOnly: true
+}, {
+ name: "findnext",
+ bindKey: bindKey("Ctrl-K", "Command-G"),
+ exec: function(editor) { editor.findNext(); },
+ readOnly: true
+}, {
+ name: "findprevious",
+ bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"),
+ exec: function(editor) { editor.findPrevious(); },
+ readOnly: true
+}, {
+ name: "find",
+ bindKey: bindKey("Ctrl-F", "Command-F"),
+ exec: function(editor) {
+ var needle = prompt("Find:", editor.getCopyText());
+ editor.find(needle);
+ },
+ readOnly: true
+}, {
+ name: "overwrite",
+ bindKey: "Insert",
+ exec: function(editor) { editor.toggleOverwrite(); },
+ readOnly: true
+}, {
+ name: "selecttostart",
+ bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"),
+ exec: function(editor) { editor.getSelection().selectFileStart(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotostart",
+ bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"),
+ exec: function(editor) { editor.navigateFileStart(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectup",
+ bindKey: bindKey("Shift-Up", "Shift-Up"),
+ exec: function(editor) { editor.getSelection().selectUp(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "golineup",
+ bindKey: bindKey("Up", "Up|Ctrl-P"),
+ exec: function(editor, args) { editor.navigateUp(args.times); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selecttoend",
+ bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"),
+ exec: function(editor) { editor.getSelection().selectFileEnd(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotoend",
+ bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"),
+ exec: function(editor) { editor.navigateFileEnd(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectdown",
+ bindKey: bindKey("Shift-Down", "Shift-Down"),
+ exec: function(editor) { editor.getSelection().selectDown(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "golinedown",
+ bindKey: bindKey("Down", "Down|Ctrl-N"),
+ exec: function(editor, args) { editor.navigateDown(args.times); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectwordleft",
+ bindKey: bindKey("Ctrl-Shift-Left", "Option-Shift-Left"),
+ exec: function(editor) { editor.getSelection().selectWordLeft(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotowordleft",
+ bindKey: bindKey("Ctrl-Left", "Option-Left"),
+ exec: function(editor) { editor.navigateWordLeft(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selecttolinestart",
+ bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left"),
+ exec: function(editor) { editor.getSelection().selectLineStart(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotolinestart",
+ bindKey: bindKey("Alt-Left|Home", "Command-Left|Home|Ctrl-A"),
+ exec: function(editor) { editor.navigateLineStart(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectleft",
+ bindKey: bindKey("Shift-Left", "Shift-Left"),
+ exec: function(editor) { editor.getSelection().selectLeft(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotoleft",
+ bindKey: bindKey("Left", "Left|Ctrl-B"),
+ exec: function(editor, args) { editor.navigateLeft(args.times); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectwordright",
+ bindKey: bindKey("Ctrl-Shift-Right", "Option-Shift-Right"),
+ exec: function(editor) { editor.getSelection().selectWordRight(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotowordright",
+ bindKey: bindKey("Ctrl-Right", "Option-Right"),
+ exec: function(editor) { editor.navigateWordRight(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selecttolineend",
+ bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right"),
+ exec: function(editor) { editor.getSelection().selectLineEnd(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotolineend",
+ bindKey: bindKey("Alt-Right|End", "Command-Right|End|Ctrl-E"),
+ exec: function(editor) { editor.navigateLineEnd(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectright",
+ bindKey: bindKey("Shift-Right", "Shift-Right"),
+ exec: function(editor) { editor.getSelection().selectRight(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "gotoright",
+ bindKey: bindKey("Right", "Right|Ctrl-F"),
+ exec: function(editor, args) { editor.navigateRight(args.times); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectpagedown",
+ bindKey: "Shift-PageDown",
+ exec: function(editor) { editor.selectPageDown(); },
+ readOnly: true
+}, {
+ name: "pagedown",
+ bindKey: bindKey(null, "Option-PageDown"),
+ exec: function(editor) { editor.scrollPageDown(); },
+ readOnly: true
+}, {
+ name: "gotopagedown",
+ bindKey: bindKey("PageDown", "PageDown|Ctrl-V"),
+ exec: function(editor) { editor.gotoPageDown(); },
+ readOnly: true
+}, {
+ name: "selectpageup",
+ bindKey: "Shift-PageUp",
+ exec: function(editor) { editor.selectPageUp(); },
+ readOnly: true
+}, {
+ name: "pageup",
+ bindKey: bindKey(null, "Option-PageUp"),
+ exec: function(editor) { editor.scrollPageUp(); },
+ readOnly: true
+}, {
+ name: "gotopageup",
+ bindKey: "PageUp",
+ exec: function(editor) { editor.gotoPageUp(); },
+ readOnly: true
+}, {
+ name: "scrollup",
+ bindKey: bindKey("Ctrl-Up", null),
+ exec: function(e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight); },
+ readOnly: true
+}, {
+ name: "scrolldown",
+ bindKey: bindKey("Ctrl-Down", null),
+ exec: function(e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight); },
+ readOnly: true
+}, {
+ name: "selectlinestart",
+ bindKey: "Shift-Home",
+ exec: function(editor) { editor.getSelection().selectLineStart(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selectlineend",
+ bindKey: "Shift-End",
+ exec: function(editor) { editor.getSelection().selectLineEnd(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "togglerecording",
+ bindKey: bindKey("Ctrl-Alt-E", "Command-Option-E"),
+ exec: function(editor) { editor.commands.toggleRecording(); },
+ readOnly: true
+}, {
+ name: "replaymacro",
+ bindKey: bindKey("Ctrl-Shift-E", "Command-Shift-E"),
+ exec: function(editor) { editor.commands.replay(editor); },
+ readOnly: true
+}, {
+ name: "jumptomatching",
+ bindKey: bindKey("Ctrl-P", "Ctrl-Shift-P"),
+ exec: function(editor) { editor.jumpToMatching(); },
+ multiSelectAction: "forEach",
+ readOnly: true
+}, {
+ name: "selecttomatching",
+ bindKey: bindKey("Ctrl-Shift-P", null),
+ exec: function(editor) { editor.jumpToMatching(true); },
+ readOnly: true
+},
+
+// commands disabled in readOnly mode
+{
+ name: "cut",
+ exec: function(editor) {
+ var range = editor.getSelectionRange();
+ editor._emit("cut", range);
+
+ if (!editor.selection.isEmpty()) {
+ editor.session.remove(range);
+ editor.clearSelection();
+ }
+ },
+ multiSelectAction: "forEach"
+}, {
+ name: "removeline",
+ bindKey: bindKey("Ctrl-D", "Command-D"),
+ exec: function(editor) { editor.removeLines(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "duplicateSelection",
+ bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"),
+ exec: function(editor) { editor.duplicateSelection(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "togglecomment",
+ bindKey: bindKey("Ctrl-/", "Command-/"),
+ exec: function(editor) { editor.toggleCommentLines(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "replace",
+ bindKey: bindKey("Ctrl-R", "Command-Option-F"),
+ exec: function(editor) {
+ var needle = prompt("Find:", editor.getCopyText());
+ if (!needle)
+ return;
+ var replacement = prompt("Replacement:");
+ if (!replacement)
+ return;
+ editor.replace(replacement, {needle: needle});
+ }
+}, {
+ name: "replaceall",
+ bindKey: bindKey("Ctrl-Shift-R", "Command-Shift-Option-F"),
+ exec: function(editor) {
+ var needle = prompt("Find:");
+ if (!needle)
+ return;
+ var replacement = prompt("Replacement:");
+ if (!replacement)
+ return;
+ editor.replaceAll(replacement, {needle: needle});
+ }
+}, {
+ name: "undo",
+ bindKey: bindKey("Ctrl-Z", "Command-Z"),
+ exec: function(editor) { editor.undo(); }
+}, {
+ name: "redo",
+ bindKey: bindKey("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"),
+ exec: function(editor) { editor.redo(); }
+}, {
+ name: "copylinesup",
+ bindKey: bindKey("Alt-Shift-Up", "Command-Option-Up"),
+ exec: function(editor) { editor.copyLinesUp(); }
+}, {
+ name: "movelinesup",
+ bindKey: bindKey("Alt-Up", "Option-Up"),
+ exec: function(editor) { editor.moveLinesUp(); }
+}, {
+ name: "copylinesdown",
+ bindKey: bindKey("Alt-Shift-Down", "Command-Option-Down"),
+ exec: function(editor) { editor.copyLinesDown(); }
+}, {
+ name: "movelinesdown",
+ bindKey: bindKey("Alt-Down", "Option-Down"),
+ exec: function(editor) { editor.moveLinesDown(); }
+}, {
+ name: "del",
+ bindKey: bindKey("Delete", "Delete|Ctrl-D"),
+ exec: function(editor) { editor.remove("right"); },
+ multiSelectAction: "forEach"
+}, {
+ name: "backspace",
+ bindKey: bindKey(
+ "Command-Backspace|Option-Backspace|Shift-Backspace|Backspace",
+ "Ctrl-Backspace|Command-Backspace|Shift-Backspace|Backspace|Ctrl-H"
+ ),
+ exec: function(editor) { editor.remove("left"); },
+ multiSelectAction: "forEach"
+}, {
+ name: "removetolinestart",
+ bindKey: bindKey("Alt-Backspace", "Command-Backspace"),
+ exec: function(editor) { editor.removeToLineStart(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "removetolineend",
+ bindKey: bindKey("Alt-Delete", "Ctrl-K"),
+ exec: function(editor) { editor.removeToLineEnd(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "removewordleft",
+ bindKey: bindKey("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"),
+ exec: function(editor) { editor.removeWordLeft(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "removewordright",
+ bindKey: bindKey("Ctrl-Delete", "Alt-Delete"),
+ exec: function(editor) { editor.removeWordRight(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "outdent",
+ bindKey: bindKey("Shift-Tab", "Shift-Tab"),
+ exec: function(editor) { editor.blockOutdent(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "indent",
+ bindKey: bindKey("Tab", "Tab"),
+ exec: function(editor) { editor.indent(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "insertstring",
+ exec: function(editor, str) { editor.insert(str); },
+ multiSelectAction: "forEach"
+}, {
+ name: "inserttext",
+ exec: function(editor, args) {
+ editor.insert(lang.stringRepeat(args.text || "", args.times || 1));
+ },
+ multiSelectAction: "forEach"
+}, {
+ name: "splitline",
+ bindKey: bindKey(null, "Ctrl-O"),
+ exec: function(editor) { editor.splitLine(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "transposeletters",
+ bindKey: bindKey("Ctrl-T", "Ctrl-T"),
+ exec: function(editor) { editor.transposeLetters(); },
+ multiSelectAction: function(editor) {editor.transposeSelections(1); }
+}, {
+ name: "touppercase",
+ bindKey: bindKey("Ctrl-U", "Ctrl-U"),
+ exec: function(editor) { editor.toUpperCase(); },
+ multiSelectAction: "forEach"
+}, {
+ name: "tolowercase",
+ bindKey: bindKey("Ctrl-Shift-U", "Ctrl-Shift-U"),
+ exec: function(editor) { editor.toLowerCase(); },
+ multiSelectAction: "forEach"
+}];
+
+});
+
+define('ace/undomanager', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * class UndoManager
+ *
+ * This object maintains the undo stack for an [[EditSession `EditSession`]].
+ *
+ **/
+
+/**
+ * new UndoManager()
+ *
+ * Resets the current undo state and creates a new `UndoManager`.
+ **/
+var UndoManager = function() {
+ this.reset();
+};
+
+(function() {
+
+ /**
+ * UndoManager.execute(options) -> Void
+ * - options (Object): Contains additional properties
+ *
+ * Provides a means for implementing your own undo manager. `options` has one property, `args`, an [[Array `Array`]], with two elements:
+ * * `args[0]` is an array of deltas
+ * * `args[1]` is the document to associate with
+ *
+ **/
+ this.execute = function(options) {
+ var deltas = options.args[0];
+ this.$doc = options.args[1];
+ this.$undoStack.push(deltas);
+ this.$redoStack = [];
+ };
+ this.undo = function(dontSelect) {
+ var deltas = this.$undoStack.pop();
+ var undoSelectionRange = null;
+ if (deltas) {
+ undoSelectionRange =
+ this.$doc.undoChanges(deltas, dontSelect);
+ this.$redoStack.push(deltas);
+ }
+ return undoSelectionRange;
+ };
+ this.redo = function(dontSelect) {
+ var deltas = this.$redoStack.pop();
+ var redoSelectionRange = null;
+ if (deltas) {
+ redoSelectionRange =
+ this.$doc.redoChanges(deltas, dontSelect);
+ this.$undoStack.push(deltas);
+ }
+ return redoSelectionRange;
+ };
+ this.reset = function() {
+ this.$undoStack = [];
+ this.$redoStack = [];
+ };
+ this.hasUndo = function() {
+ return this.$undoStack.length > 0;
+ };
+ this.hasRedo = function() {
+ return this.$redoStack.length > 0;
+ };
+
+}).call(UndoManager.prototype);
+
+exports.UndoManager = UndoManager;
+});
+
+define('ace/virtual_renderer', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/dom', 'ace/lib/event', 'ace/lib/useragent', 'ace/config', 'ace/lib/net', 'ace/layer/gutter', 'ace/layer/marker', 'ace/layer/text', 'ace/layer/cursor', 'ace/scrollbar', 'ace/renderloop', 'ace/lib/event_emitter', 'text!ace/css/editor.css'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var dom = require("./lib/dom");
+var event = require("./lib/event");
+var useragent = require("./lib/useragent");
+var config = require("./config");
+var net = require("./lib/net");
+var GutterLayer = require("./layer/gutter").Gutter;
+var MarkerLayer = require("./layer/marker").Marker;
+var TextLayer = require("./layer/text").Text;
+var CursorLayer = require("./layer/cursor").Cursor;
+var ScrollBar = require("./scrollbar").ScrollBar;
+var RenderLoop = require("./renderloop").RenderLoop;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var editorCss = require("text!./css/editor.css");
+
+dom.importCssString(editorCss, "ace_editor");
+
+/**
+ * new VirtualRenderer(container, theme)
+ * - container (DOMElement): The root element of the editor
+ * - theme (String): The starting theme
+ *
+ * Constructs a new `VirtualRenderer` within the `container` specified, applying the given `theme`.
+ *
+ **/
+
+var VirtualRenderer = function(container, theme) {
+ var _self = this;
+
+ this.container = container;
+
+ // TODO: this breaks rendering in Cloud9 with multiple ace instances
+// // Imports CSS once per DOM document ('ace_editor' serves as an identifier).
+// dom.importCssString(editorCss, "ace_editor", container.ownerDocument);
+
+ // in IE <= 9 the native cursor always shines through
+ this.$keepTextAreaAtCursor = !useragent.isIE;
+
+ dom.addCssClass(container, "ace_editor");
+
+ this.setTheme(theme);
+
+ this.$gutter = dom.createElement("div");
+ this.$gutter.className = "ace_gutter";
+ this.container.appendChild(this.$gutter);
+
+ this.scroller = dom.createElement("div");
+ this.scroller.className = "ace_scroller";
+ this.container.appendChild(this.scroller);
+
+ this.content = dom.createElement("div");
+ this.content.className = "ace_content";
+ this.scroller.appendChild(this.content);
+
+ this.setHighlightGutterLine(true);
+ this.$gutterLayer = new GutterLayer(this.$gutter);
+ this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true));
+
+ this.$markerBack = new MarkerLayer(this.content);
+
+ var textLayer = this.$textLayer = new TextLayer(this.content);
+ this.canvas = textLayer.element;
+
+ this.$markerFront = new MarkerLayer(this.content);
+
+ this.characterWidth = textLayer.getCharacterWidth();
+ this.lineHeight = textLayer.getLineHeight();
+
+ this.$cursorLayer = new CursorLayer(this.content);
+ this.$cursorPadding = 8;
+
+ // Indicates whether the horizontal scrollbar is visible
+ this.$horizScroll = false;
+ this.$horizScrollAlwaysVisible = false;
+
+ this.$animatedScroll = false;
+
+ this.scrollBar = new ScrollBar(container);
+ this.scrollBar.addEventListener("scroll", function(e) {
+ if (!_self.$inScrollAnimation)
+ _self.session.setScrollTop(e.data);
+ });
+
+ this.scrollTop = 0;
+ this.scrollLeft = 0;
+
+ event.addListener(this.scroller, "scroll", function() {
+ var scrollLeft = _self.scroller.scrollLeft;
+ _self.scrollLeft = scrollLeft;
+ _self.session.setScrollLeft(scrollLeft);
+ });
+
+ this.cursorPos = {
+ row : 0,
+ column : 0
+ };
+
+ this.$textLayer.addEventListener("changeCharacterSize", function() {
+ _self.characterWidth = textLayer.getCharacterWidth();
+ _self.lineHeight = textLayer.getLineHeight();
+ _self.$updatePrintMargin();
+ _self.onResize(true);
+
+ _self.$loop.schedule(_self.CHANGE_FULL);
+ });
+
+ this.$size = {
+ width: 0,
+ height: 0,
+ scrollerHeight: 0,
+ scrollerWidth: 0
+ };
+
+ this.layerConfig = {
+ width : 1,
+ padding : 0,
+ firstRow : 0,
+ firstRowScreen: 0,
+ lastRow : 0,
+ lineHeight : 1,
+ characterWidth : 1,
+ minHeight : 1,
+ maxHeight : 1,
+ offset : 0,
+ height : 1
+ };
+
+ this.$loop = new RenderLoop(
+ this.$renderChanges.bind(this),
+ this.container.ownerDocument.defaultView
+ );
+ this.$loop.schedule(this.CHANGE_FULL);
+
+ this.setPadding(4);
+ this.$updatePrintMargin();
+};
+
+(function() {
+ this.showGutter = true;
+
+ this.CHANGE_CURSOR = 1;
+ this.CHANGE_MARKER = 2;
+ this.CHANGE_GUTTER = 4;
+ this.CHANGE_SCROLL = 8;
+ this.CHANGE_LINES = 16;
+ this.CHANGE_TEXT = 32;
+ this.CHANGE_SIZE = 64;
+ this.CHANGE_MARKER_BACK = 128;
+ this.CHANGE_MARKER_FRONT = 256;
+ this.CHANGE_FULL = 512;
+ this.CHANGE_H_SCROLL = 1024;
+
+ oop.implement(this, EventEmitter);
+ this.setSession = function(session) {
+ this.session = session;
+
+ this.scroller.className = "ace_scroller";
+
+ this.$cursorLayer.setSession(session);
+ this.$markerBack.setSession(session);
+ this.$markerFront.setSession(session);
+ this.$gutterLayer.setSession(session);
+ this.$textLayer.setSession(session);
+ this.$loop.schedule(this.CHANGE_FULL);
+
+ };
+ this.updateLines = function(firstRow, lastRow) {
+ if (lastRow === undefined)
+ lastRow = Infinity;
+
+ if (!this.$changedLines) {
+ this.$changedLines = {
+ firstRow: firstRow,
+ lastRow: lastRow
+ };
+ }
+ else {
+ if (this.$changedLines.firstRow > firstRow)
+ this.$changedLines.firstRow = firstRow;
+
+ if (this.$changedLines.lastRow < lastRow)
+ this.$changedLines.lastRow = lastRow;
+ }
+
+ this.$loop.schedule(this.CHANGE_LINES);
+ };
+
+ this.onChangeTabSize = function() {
+ this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER);
+ this.$textLayer.onChangeTabSize();
+ };
+ this.updateText = function() {
+ this.$loop.schedule(this.CHANGE_TEXT);
+ };
+ this.updateFull = function(force) {
+ if (force){
+ this.$renderChanges(this.CHANGE_FULL, true);
+ }
+ else {
+ this.$loop.schedule(this.CHANGE_FULL);
+ }
+ };
+ this.updateFontSize = function() {
+ this.$textLayer.checkForSizeChanges();
+ };
+ this.onResize = function(force, gutterWidth, width, height) {
+ var changes = this.CHANGE_SIZE;
+ var size = this.$size;
+
+ if (this.resizing > 2)
+ return;
+ else if (this.resizing > 1)
+ this.resizing++;
+ else
+ this.resizing = force ? 1 : 0;
+
+ if (!height)
+ height = dom.getInnerHeight(this.container);
+ if (force || size.height != height) {
+ size.height = height;
+
+ this.scroller.style.height = height + "px";
+ size.scrollerHeight = this.scroller.clientHeight;
+ this.scrollBar.setHeight(size.scrollerHeight);
+
+ if (this.session) {
+ this.session.setScrollTop(this.getScrollTop());
+ changes = changes | this.CHANGE_FULL;
+ }
+ }
+
+ if (!width)
+ width = dom.getInnerWidth(this.container);
+ if (force || this.resizing > 1 || size.width != width) {
+ size.width = width;
+
+ var gutterWidth = this.showGutter ? this.$gutter.offsetWidth : 0;
+ this.scroller.style.left = gutterWidth + "px";
+ size.scrollerWidth = Math.max(0, width - gutterWidth - this.scrollBar.getWidth());
+ this.scroller.style.right = this.scrollBar.getWidth() + "px";
+
+ if (this.session.getUseWrapMode() && this.adjustWrapLimit() || force)
+ changes = changes | this.CHANGE_FULL;
+ }
+
+ if (force)
+ this.$renderChanges(changes, true);
+ else
+ this.$loop.schedule(changes);
+
+ if (force)
+ delete this.resizing;
+ };
+ this.adjustWrapLimit = function() {
+ var availableWidth = this.$size.scrollerWidth - this.$padding * 2;
+ var limit = Math.floor(availableWidth / this.characterWidth);
+ return this.session.adjustWrapLimit(limit);
+ };
+ this.setAnimatedScroll = function(shouldAnimate){
+ this.$animatedScroll = shouldAnimate;
+ };
+ this.getAnimatedScroll = function() {
+ return this.$animatedScroll;
+ };
+ this.setShowInvisibles = function(showInvisibles) {
+ if (this.$textLayer.setShowInvisibles(showInvisibles))
+ this.$loop.schedule(this.CHANGE_TEXT);
+ };
+ this.getShowInvisibles = function() {
+ return this.$textLayer.showInvisibles;
+ };
+
+ this.getDisplayIndentGuides = function() {
+ return this.$textLayer.displayIndentGuides;
+ };
+
+ this.setDisplayIndentGuides = function(display) {
+ if (this.$textLayer.setDisplayIndentGuides(display))
+ this.$loop.schedule(this.CHANGE_TEXT);
+ };
+
+ this.$showPrintMargin = true;
+ this.setShowPrintMargin = function(showPrintMargin) {
+ this.$showPrintMargin = showPrintMargin;
+ this.$updatePrintMargin();
+ };
+ this.getShowPrintMargin = function() {
+ return this.$showPrintMargin;
+ };
+
+ this.$printMarginColumn = 80;
+ this.setPrintMarginColumn = function(showPrintMargin) {
+ this.$printMarginColumn = showPrintMargin;
+ this.$updatePrintMargin();
+ };
+ this.getPrintMarginColumn = function() {
+ return this.$printMarginColumn;
+ };
+ this.getShowGutter = function(){
+ return this.showGutter;
+ };
+ this.setShowGutter = function(show){
+ if(this.showGutter === show)
+ return;
+ this.$gutter.style.display = show ? "block" : "none";
+ this.showGutter = show;
+ this.onResize(true);
+ };
+
+ this.getFadeFoldWidgets = function(){
+ return dom.hasCssClass(this.$gutter, "ace_fade-fold-widgets");
+ };
+
+ this.setFadeFoldWidgets = function(show) {
+ if (show)
+ dom.addCssClass(this.$gutter, "ace_fade-fold-widgets");
+ else
+ dom.removeCssClass(this.$gutter, "ace_fade-fold-widgets");
+ };
+
+ this.$highlightGutterLine = false;
+ this.setHighlightGutterLine = function(shouldHighlight) {
+ if (this.$highlightGutterLine == shouldHighlight)
+ return;
+ this.$highlightGutterLine = shouldHighlight;
+
+ if (!this.$gutterLineHighlight) {
+ this.$gutterLineHighlight = dom.createElement("div");
+ this.$gutterLineHighlight.className = "ace_gutter_active_line";
+ this.$gutter.appendChild(this.$gutterLineHighlight);
+ return;
+ }
+
+ this.$gutterLineHighlight.style.display = shouldHighlight ? "" : "none";
+ // if cursorlayer have never been updated there's nothing on screen to update
+ if (this.$cursorLayer.$pixelPos)
+ this.$updateGutterLineHighlight();
+ };
+
+ this.getHighlightGutterLine = function() {
+ return this.$highlightGutterLine;
+ };
+
+ this.$updateGutterLineHighlight = function() {
+ this.$gutterLineHighlight.style.top = this.$cursorLayer.$pixelPos.top - this.layerConfig.offset + "px";
+ this.$gutterLineHighlight.style.height = this.layerConfig.lineHeight + "px";
+ };
+
+ this.$updatePrintMargin = function() {
+ var containerEl;
+
+ if (!this.$showPrintMargin && !this.$printMarginEl)
+ return;
+
+ if (!this.$printMarginEl) {
+ containerEl = dom.createElement("div");
+ containerEl.className = "ace_print_margin_layer";
+ this.$printMarginEl = dom.createElement("div");
+ this.$printMarginEl.className = "ace_print_margin";
+ containerEl.appendChild(this.$printMarginEl);
+ this.content.insertBefore(containerEl, this.$textLayer.element);
+ }
+
+ var style = this.$printMarginEl.style;
+ style.left = ((this.characterWidth * this.$printMarginColumn) + this.$padding) + "px";
+ style.visibility = this.$showPrintMargin ? "visible" : "hidden";
+ };
+ this.getContainerElement = function() {
+ return this.container;
+ };
+ this.getMouseEventTarget = function() {
+ return this.content;
+ };
+ this.getTextAreaContainer = function() {
+ return this.container;
+ };
+
+ // move text input over the cursor
+ // this is required for iOS and IME
+ this.$moveTextAreaToCursor = function() {
+ if (!this.$keepTextAreaAtCursor)
+ return;
+
+ var posTop = this.$cursorLayer.$pixelPos.top;
+ var posLeft = this.$cursorLayer.$pixelPos.left;
+ posTop -= this.layerConfig.offset;
+
+ if (posTop < 0 || posTop > this.layerConfig.height - this.lineHeight)
+ return;
+
+ var w = this.characterWidth;
+ if (this.$composition)
+ w += this.textarea.scrollWidth;
+ posLeft -= this.scrollLeft;
+ if (posLeft > this.$size.scrollerWidth - w)
+ posLeft = this.$size.scrollerWidth - w;
+
+ if (this.showGutter)
+ posLeft += this.$gutterLayer.gutterWidth;
+
+ var bounds = this.container.getBoundingClientRect();
+
+ this.textarea.style.height = this.lineHeight + "px";
+ this.textarea.style.width = w + "px";
+ this.textarea.style.left = posLeft + bounds.left + "px";
+ this.textarea.style.top = posTop + bounds.top - 1 + "px";
+ };
+ this.getFirstVisibleRow = function() {
+ return this.layerConfig.firstRow;
+ };
+ this.getFirstFullyVisibleRow = function() {
+ return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1);
+ };
+ this.getLastFullyVisibleRow = function() {
+ var flint = Math.floor((this.layerConfig.height + this.layerConfig.offset) / this.layerConfig.lineHeight);
+ return this.layerConfig.firstRow - 1 + flint;
+ };
+ this.getLastVisibleRow = function() {
+ return this.layerConfig.lastRow;
+ };
+
+ this.$padding = null;
+ this.setPadding = function(padding) {
+ this.$padding = padding;
+ this.$textLayer.setPadding(padding);
+ this.$cursorLayer.setPadding(padding);
+ this.$markerFront.setPadding(padding);
+ this.$markerBack.setPadding(padding);
+ this.$loop.schedule(this.CHANGE_FULL);
+ this.$updatePrintMargin();
+ };
+ this.getHScrollBarAlwaysVisible = function() {
+ return this.$horizScrollAlwaysVisible;
+ };
+ this.setHScrollBarAlwaysVisible = function(alwaysVisible) {
+ if (this.$horizScrollAlwaysVisible != alwaysVisible) {
+ this.$horizScrollAlwaysVisible = alwaysVisible;
+ if (!this.$horizScrollAlwaysVisible || !this.$horizScroll)
+ this.$loop.schedule(this.CHANGE_SCROLL);
+ }
+ };
+
+ this.$updateScrollBar = function() {
+ this.scrollBar.setInnerHeight(this.layerConfig.maxHeight);
+ this.scrollBar.setScrollTop(this.scrollTop);
+ };
+
+ this.$renderChanges = function(changes, force) {
+ if (!force && (!changes || !this.session || !this.container.offsetWidth))
+ return;
+
+ // text, scrolling and resize changes can cause the view port size to change
+ if (changes & this.CHANGE_FULL ||
+ changes & this.CHANGE_SIZE ||
+ changes & this.CHANGE_TEXT ||
+ changes & this.CHANGE_LINES ||
+ changes & this.CHANGE_SCROLL
+ )
+ this.$computeLayerConfig();
+
+ // horizontal scrolling
+ if (changes & this.CHANGE_H_SCROLL) {
+ this.scroller.scrollLeft = this.scrollLeft;
+
+ // read the value after writing it since the value might get clipped
+ var scrollLeft = this.scroller.scrollLeft;
+ this.scrollLeft = scrollLeft;
+ this.session.setScrollLeft(scrollLeft);
+
+ this.scroller.className = this.scrollLeft == 0 ? "ace_scroller" : "ace_scroller horscroll";
+ }
+
+ // full
+ if (changes & this.CHANGE_FULL) {
+ this.$textLayer.checkForSizeChanges();
+ // update scrollbar first to not lose scroll position when gutter calls resize
+ this.$updateScrollBar();
+ this.$textLayer.update(this.layerConfig);
+ if (this.showGutter)
+ this.$gutterLayer.update(this.layerConfig);
+ this.$markerBack.update(this.layerConfig);
+ this.$markerFront.update(this.layerConfig);
+ this.$cursorLayer.update(this.layerConfig);
+ this.$moveTextAreaToCursor();
+ this.$highlightGutterLine && this.$updateGutterLineHighlight();
+ return;
+ }
+
+ // scrolling
+ if (changes & this.CHANGE_SCROLL) {
+ this.$updateScrollBar();
+ if (changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES)
+ this.$textLayer.update(this.layerConfig);
+ else
+ this.$textLayer.scrollLines(this.layerConfig);
+
+ if (this.showGutter)
+ this.$gutterLayer.update(this.layerConfig);
+ this.$markerBack.update(this.layerConfig);
+ this.$markerFront.update(this.layerConfig);
+ this.$cursorLayer.update(this.layerConfig);
+ this.$moveTextAreaToCursor();
+ this.$highlightGutterLine && this.$updateGutterLineHighlight();
+ return;
+ }
+
+ if (changes & this.CHANGE_TEXT) {
+ this.$textLayer.update(this.layerConfig);
+ if (this.showGutter)
+ this.$gutterLayer.update(this.layerConfig);
+ }
+ else if (changes & this.CHANGE_LINES) {
+ if (this.$updateLines()) {
+ this.$updateScrollBar();
+ if (this.showGutter)
+ this.$gutterLayer.update(this.layerConfig);
+ }
+ } else if (changes & this.CHANGE_GUTTER) {
+ if (this.showGutter)
+ this.$gutterLayer.update(this.layerConfig);
+ }
+
+ if (changes & this.CHANGE_CURSOR) {
+ this.$cursorLayer.update(this.layerConfig);
+ this.$moveTextAreaToCursor();
+ this.$highlightGutterLine && this.$updateGutterLineHighlight();
+ }
+
+ if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) {
+ this.$markerFront.update(this.layerConfig);
+ }
+
+ if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK)) {
+ this.$markerBack.update(this.layerConfig);
+ }
+
+ if (changes & this.CHANGE_SIZE)
+ this.$updateScrollBar();
+ };
+
+ this.$computeLayerConfig = function() {
+ var session = this.session;
+
+ var offset = this.scrollTop % this.lineHeight;
+ var minHeight = this.$size.scrollerHeight + this.lineHeight;
+
+ var longestLine = this.$getLongestLine();
+
+ var horizScroll = this.$horizScrollAlwaysVisible || this.$size.scrollerWidth - longestLine < 0;
+ var horizScrollChanged = this.$horizScroll !== horizScroll;
+ this.$horizScroll = horizScroll;
+ if (horizScrollChanged) {
+ this.scroller.style.overflowX = horizScroll ? "scroll" : "hidden";
+ // when we hide scrollbar scroll event isn't emited
+ // leaving session with wrong scrollLeft value
+ if (!horizScroll)
+ this.session.setScrollLeft(0);
+ }
+ var maxHeight = this.session.getScreenLength() * this.lineHeight;
+ this.session.setScrollTop(Math.max(0, Math.min(this.scrollTop, maxHeight - this.$size.scrollerHeight)));
+
+ var lineCount = Math.ceil(minHeight / this.lineHeight) - 1;
+ var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight));
+ var lastRow = firstRow + lineCount;
+
+ // Map lines on the screen to lines in the document.
+ var firstRowScreen, firstRowHeight;
+ var lineHeight = this.lineHeight;
+ firstRow = session.screenToDocumentRow(firstRow, 0);
+
+ // Check if firstRow is inside of a foldLine. If true, then use the first
+ // row of the foldLine.
+ var foldLine = session.getFoldLine(firstRow);
+ if (foldLine) {
+ firstRow = foldLine.start.row;
+ }
+
+ firstRowScreen = session.documentToScreenRow(firstRow, 0);
+ firstRowHeight = session.getRowLength(firstRow) * lineHeight;
+
+ lastRow = Math.min(session.screenToDocumentRow(lastRow, 0), session.getLength() - 1);
+ minHeight = this.$size.scrollerHeight + session.getRowLength(lastRow) * lineHeight +
+ firstRowHeight;
+
+ offset = this.scrollTop - firstRowScreen * lineHeight;
+
+ this.layerConfig = {
+ width : longestLine,
+ padding : this.$padding,
+ firstRow : firstRow,
+ firstRowScreen: firstRowScreen,
+ lastRow : lastRow,
+ lineHeight : lineHeight,
+ characterWidth : this.characterWidth,
+ minHeight : minHeight,
+ maxHeight : maxHeight,
+ offset : offset,
+ height : this.$size.scrollerHeight
+ };
+
+ // For debugging.
+ // console.log(JSON.stringify(this.layerConfig));
+
+ this.$gutterLayer.element.style.marginTop = (-offset) + "px";
+ this.content.style.marginTop = (-offset) + "px";
+ this.content.style.width = longestLine + 2 * this.$padding + "px";
+ this.content.style.height = minHeight + "px";
+
+ // Horizontal scrollbar visibility may have changed, which changes
+ // the client height of the scroller
+ if (horizScrollChanged)
+ this.onResize(true);
+ };
+
+ this.$updateLines = function() {
+ var firstRow = this.$changedLines.firstRow;
+ var lastRow = this.$changedLines.lastRow;
+ this.$changedLines = null;
+
+ var layerConfig = this.layerConfig;
+
+ if (firstRow > layerConfig.lastRow + 1) { return; }
+ if (lastRow < layerConfig.firstRow) { return; }
+
+ // if the last row is unknown -> redraw everything
+ if (lastRow === Infinity) {
+ if (this.showGutter)
+ this.$gutterLayer.update(layerConfig);
+ this.$textLayer.update(layerConfig);
+ return;
+ }
+
+ // else update only the changed rows
+ this.$textLayer.updateLines(layerConfig, firstRow, lastRow);
+ return true;
+ };
+
+ this.$getLongestLine = function() {
+ var charCount = this.session.getScreenWidth();
+ if (this.$textLayer.showInvisibles)
+ charCount += 1;
+
+ return Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(charCount * this.characterWidth));
+ };
+ this.updateFrontMarkers = function() {
+ this.$markerFront.setMarkers(this.session.getMarkers(true));
+ this.$loop.schedule(this.CHANGE_MARKER_FRONT);
+ };
+ this.updateBackMarkers = function() {
+ this.$markerBack.setMarkers(this.session.getMarkers());
+ this.$loop.schedule(this.CHANGE_MARKER_BACK);
+ };
+ this.addGutterDecoration = function(row, className){
+ this.$gutterLayer.addGutterDecoration(row, className);
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ };
+ this.removeGutterDecoration = function(row, className){
+ this.$gutterLayer.removeGutterDecoration(row, className);
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ };
+ this.updateBreakpoints = function(rows) {
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ };
+ this.setAnnotations = function(annotations) {
+ this.$gutterLayer.setAnnotations(annotations);
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ };
+ this.updateCursor = function() {
+ this.$loop.schedule(this.CHANGE_CURSOR);
+ };
+ this.hideCursor = function() {
+ this.$cursorLayer.hideCursor();
+ };
+ this.showCursor = function() {
+ this.$cursorLayer.showCursor();
+ };
+
+ this.scrollSelectionIntoView = function(anchor, lead, offset) {
+ // first scroll anchor into view then scroll lead into view
+ this.scrollCursorIntoView(anchor, offset);
+ this.scrollCursorIntoView(lead, offset);
+ };
+ this.scrollCursorIntoView = function(cursor, offset) {
+ // the editor is not visible
+ if (this.$size.scrollerHeight === 0)
+ return;
+
+ var pos = this.$cursorLayer.getPixelPosition(cursor);
+
+ var left = pos.left;
+ var top = pos.top;
+
+ if (this.scrollTop > top) {
+ if (offset)
+ top -= offset * this.$size.scrollerHeight;
+ this.session.setScrollTop(top);
+ } else if (this.scrollTop + this.$size.scrollerHeight < top + this.lineHeight) {
+ if (offset)
+ top += offset * this.$size.scrollerHeight;
+ this.session.setScrollTop(top + this.lineHeight - this.$size.scrollerHeight);
+ }
+
+ var scrollLeft = this.scrollLeft;
+
+ if (scrollLeft > left) {
+ if (left < this.$padding + 2 * this.layerConfig.characterWidth)
+ left = 0;
+ this.session.setScrollLeft(left);
+ } else if (scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) {
+ this.session.setScrollLeft(Math.round(left + this.characterWidth - this.$size.scrollerWidth));
+ }
+ };
+ this.getScrollTop = function() {
+ return this.session.getScrollTop();
+ };
+ this.getScrollLeft = function() {
+ return this.session.getScrollLeft();
+ };
+ this.getScrollTopRow = function() {
+ return this.scrollTop / this.lineHeight;
+ };
+ this.getScrollBottomRow = function() {
+ return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1);
+ };
+ this.scrollToRow = function(row) {
+ this.session.setScrollTop(row * this.lineHeight);
+ };
+
+ this.alignCursor = function(cursor, alignment) {
+ if (typeof cursor == "number")
+ cursor = {row: cursor, column: 0};
+
+ var pos = this.$cursorLayer.getPixelPosition(cursor);
+ var offset = pos.top - this.$size.scrollerHeight * (alignment || 0);
+
+ this.session.setScrollTop(offset);
+ };
+
+ this.STEPS = 8;
+ this.$calcSteps = function(fromValue, toValue){
+ var i = 0;
+ var l = this.STEPS;
+ var steps = [];
+
+ var func = function(t, x_min, dx) {
+ return dx * (Math.pow(t - 1, 3) + 1) + x_min;
+ };
+
+ for (i = 0; i < l; ++i)
+ steps.push(func(i / this.STEPS, fromValue, toValue - fromValue));
+
+ return steps;
+ };
+ this.scrollToLine = function(line, center, animate, callback) {
+ var pos = this.$cursorLayer.getPixelPosition({row: line, column: 0});
+ var offset = pos.top;
+ if (center)
+ offset -= this.$size.scrollerHeight / 2;
+
+ var initialScroll = this.scrollTop;
+ this.session.setScrollTop(offset);
+ if (animate !== false)
+ this.animateScrolling(initialScroll, callback);
+ };
+
+ this.animateScrolling = function(fromValue, callback) {
+ var toValue = this.scrollTop;
+ if (this.$animatedScroll && Math.abs(fromValue - toValue) < 100000) {
+ var _self = this;
+ var steps = _self.$calcSteps(fromValue, toValue);
+ this.$inScrollAnimation = true;
+
+ clearInterval(this.$timer);
+
+ _self.session.setScrollTop(steps.shift());
+ this.$timer = setInterval(function() {
+ if (steps.length) {
+ _self.session.setScrollTop(steps.shift());
+ // trick session to think it's already scrolled to not loose toValue
+ _self.session.$scrollTop = toValue;
+ } else if (toValue != null) {
+ _self.session.$scrollTop = -1;
+ _self.session.setScrollTop(toValue);
+ toValue = null;
+ } else {
+ // do this on separate step to not get spurious scroll event from scrollbar
+ _self.$timer = clearInterval(_self.$timer);
+ _self.$inScrollAnimation = false;
+ callback && callback();
+ }
+ }, 10);
+ }
+ };
+ this.scrollToY = function(scrollTop) {
+ // after calling scrollBar.setScrollTop
+ // scrollbar sends us event with same scrollTop. ignore it
+ if (this.scrollTop !== scrollTop) {
+ this.$loop.schedule(this.CHANGE_SCROLL);
+ this.scrollTop = scrollTop;
+ }
+ };
+ this.scrollToX = function(scrollLeft) {
+ if (scrollLeft < 0)
+ scrollLeft = 0;
+
+ if (this.scrollLeft !== scrollLeft)
+ this.scrollLeft = scrollLeft;
+ this.$loop.schedule(this.CHANGE_H_SCROLL);
+ };
+ this.scrollBy = function(deltaX, deltaY) {
+ deltaY && this.session.setScrollTop(this.session.getScrollTop() + deltaY);
+ deltaX && this.session.setScrollLeft(this.session.getScrollLeft() + deltaX);
+ };
+ this.isScrollableBy = function(deltaX, deltaY) {
+ if (deltaY < 0 && this.session.getScrollTop() > 0)
+ return true;
+ if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight < this.layerConfig.maxHeight)
+ return true;
+ // todo: handle horizontal scrolling
+ };
+
+ this.pixelToScreenCoordinates = function(x, y) {
+ var canvasPos = this.scroller.getBoundingClientRect();
+
+ var offset = (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth;
+ var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight);
+ var col = Math.round(offset);
+
+ return {row: row, column: col, side: offset - col > 0 ? 1 : -1};
+ };
+
+ this.screenToTextCoordinates = function(x, y) {
+ var canvasPos = this.scroller.getBoundingClientRect();
+
+ var col = Math.round(
+ (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth
+ );
+ var row = Math.floor(
+ (y + this.scrollTop - canvasPos.top) / this.lineHeight
+ );
+
+ return this.session.screenToDocumentPosition(row, Math.max(col, 0));
+ };
+ this.textToScreenCoordinates = function(row, column) {
+ var canvasPos = this.scroller.getBoundingClientRect();
+ var pos = this.session.documentToScreenPosition(row, column);
+
+ var x = this.$padding + Math.round(pos.column * this.characterWidth);
+ var y = pos.row * this.lineHeight;
+
+ return {
+ pageX: canvasPos.left + x - this.scrollLeft,
+ pageY: canvasPos.top + y - this.scrollTop
+ };
+ };
+ this.visualizeFocus = function() {
+ dom.addCssClass(this.container, "ace_focus");
+ };
+ this.visualizeBlur = function() {
+ dom.removeCssClass(this.container, "ace_focus");
+ };
+ this.showComposition = function(position) {
+ if (!this.$composition)
+ this.$composition = {
+ keepTextAreaAtCursor: this.$keepTextAreaAtCursor,
+ cssText: this.textarea.style.cssText
+ };
+
+ this.$keepTextAreaAtCursor = true;
+ dom.addCssClass(this.textarea, "ace_composition");
+ this.textarea.style.cssText = "";
+ this.$moveTextAreaToCursor();
+ };
+ this.setCompositionText = function(text) {
+ this.$moveTextAreaToCursor();
+ };
+ this.hideComposition = function() {
+ if (!this.$composition)
+ return;
+
+ dom.removeCssClass(this.textarea, "ace_composition");
+ this.$keepTextAreaAtCursor = this.$composition.keepTextAreaAtCursor;
+ this.textarea.style.cssText = this.$composition.cssText;
+ this.$composition = null;
+ };
+
+ this._loadTheme = function(name, callback) {
+ if (!config.get("packaged"))
+ return callback();
+
+ net.loadScript(config.moduleUrl(name, "theme"), callback);
+ };
+ this.setTheme = function(theme) {
+ var _self = this;
+
+ this.$themeValue = theme;
+ if (!theme || typeof theme == "string") {
+ var moduleName = theme || "ace/theme/textmate";
+
+ var module;
+ try {
+ module = require(moduleName);
+ } catch (e) {};
+ if (module)
+ return afterLoad(module);
+
+ _self._loadTheme(moduleName, function() {
+ require([moduleName], function(module) {
+ if (_self.$themeValue !== theme)
+ return;
+
+ afterLoad(module);
+ });
+ });
+ } else {
+ afterLoad(theme);
+ }
+
+ function afterLoad(theme) {
+ dom.importCssString(
+ theme.cssText,
+ theme.cssClass,
+ _self.container.ownerDocument
+ );
+
+ if (_self.$theme)
+ dom.removeCssClass(_self.container, _self.$theme);
+
+ _self.$theme = theme ? theme.cssClass : null;
+
+ if (_self.$theme)
+ dom.addCssClass(_self.container, _self.$theme);
+
+ if (theme && theme.isDark)
+ dom.addCssClass(_self.container, "ace_dark");
+ else
+ dom.removeCssClass(_self.container, "ace_dark");
+
+ // force re-measure of the gutter width
+ if (_self.$size) {
+ _self.$size.width = 0;
+ _self.onResize();
+ }
+ }
+ };
+ this.getTheme = function() {
+ return this.$themeValue;
+ };
+
+ // Methods allows to add / remove CSS classnames to the editor element.
+ // This feature can be used by plug-ins to provide a visual indication of
+ // a certain mode that editor is in.
+
+ /**
+ * VirtualRenderer.setStyle(style) -> Void
+ * - style (String): A class name
+ *
+ * [Adds a new class, `style`, to the editor.]{: #VirtualRenderer.setStyle}
+ **/
+ this.setStyle = function setStyle(style) {
+ dom.addCssClass(this.container, style);
+ };
+ this.unsetStyle = function unsetStyle(style) {
+ dom.removeCssClass(this.container, style);
+ };
+ this.destroy = function() {
+ this.$textLayer.destroy();
+ this.$cursorLayer.destroy();
+ };
+
+}).call(VirtualRenderer.prototype);
+
+exports.VirtualRenderer = VirtualRenderer;
+});
+
+define('ace/layer/gutter', ['require', 'exports', 'module' , 'ace/lib/dom', 'ace/lib/oop', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var dom = require("../lib/dom");
+var oop = require("../lib/oop");
+var EventEmitter = require("../lib/event_emitter").EventEmitter;
+
+var Gutter = function(parentEl) {
+ this.element = dom.createElement("div");
+ this.element.className = "ace_layer ace_gutter-layer";
+ parentEl.appendChild(this.element);
+ this.setShowFoldWidgets(this.$showFoldWidgets);
+
+ this.gutterWidth = 0;
+
+ this.$breakpoints = [];
+ this.$annotations = [];
+ this.$decorations = [];
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+
+ this.setSession = function(session) {
+ this.session = session;
+ };
+
+ this.addGutterDecoration = function(row, className){
+ if (!this.$decorations[row])
+ this.$decorations[row] = "";
+ this.$decorations[row] += " " + className;
+ };
+
+ this.removeGutterDecoration = function(row, className){
+ this.$decorations[row] = (this.$decorations[row] || "").replace(" " + className, "");
+ };
+
+ this.setAnnotations = function(annotations) {
+ // iterate over sparse array
+ this.$annotations = [];
+ for (var row in annotations) if (annotations.hasOwnProperty(row)) {
+ var rowAnnotations = annotations[row];
+ if (!rowAnnotations)
+ continue;
+
+ var rowInfo = this.$annotations[row] = {
+ text: []
+ };
+ for (var i=0; i<rowAnnotations.length; i++) {
+ var annotation = rowAnnotations[i];
+ var annoText = annotation.text.replace(/"/g, """).replace(/'/g, "’").replace(/</, "<");
+ if (rowInfo.text.indexOf(annoText) === -1)
+ rowInfo.text.push(annoText);
+ var type = annotation.type;
+ if (type == "error")
+ rowInfo.className = "ace_error";
+ else if (type == "warning" && rowInfo.className != "ace_error")
+ rowInfo.className = "ace_warning";
+ else if (type == "info" && (!rowInfo.className))
+ rowInfo.className = "ace_info";
+ }
+ }
+ };
+
+ // Pad this number to the width of the last line number in the session
+ this.pad = function(n) {
+ function countDigits(num) {
+ return (num + "").length;
+ }
+ var delta = countDigits(this.session.getLength()) - countDigits(n);
+ return Array(delta+1).join("\u00A0") + n;
+ };
+
+ this.update = function(config) {
+ this.$config = config;
+
+ var emptyAnno = {className: "", text: []};
+ var html = [];
+ var i = config.firstRow;
+ var lastRow = config.lastRow;
+ var fold = this.session.getNextFoldLine(i);
+ var foldStart = fold ? fold.start.row : Infinity;
+ var foldWidgets = this.$showFoldWidgets && this.session.foldWidgets;
+ var breakpoints = this.session.$breakpoints;
+
+ while (true) {
+ if(i > foldStart) {
+ i = fold.end.row + 1;
+ fold = this.session.getNextFoldLine(i, fold);
+ foldStart = fold ?fold.start.row :Infinity;
+ }
+ if(i > lastRow)
+ break;
+
+ var annotation = this.$annotations[i] || emptyAnno;
+ html.push("<div class='ace_gutter-cell",
+ this.$decorations[i] || "",
+ breakpoints[i] ? " ace_breakpoint " : " ",
+ annotation.className,
+ "' title='", annotation.text.join("\n"),
+ "' style='height:", this.session.getRowLength(i) * config.lineHeight, "px;'>", this.pad(i+1));
+
+ if (foldWidgets) {
+ var c = foldWidgets[i];
+ // check if cached value is invalidated and we need to recompute
+ if (c == null)
+ c = foldWidgets[i] = this.session.getFoldWidget(i);
+ if (c)
+ html.push(
+ "<span class='ace_fold-widget ", c,
+ c == "start" && i == foldStart && i < fold.end.row ? " closed" : " open",
+ "'></span>"
+ );
+ }
+
+ html.push("</div>");
+
+ i++;
+ }
+
+ if (this.session.$useWrapMode)
+ html.push(
+ "<div class='ace_gutter-cell' style='pointer-events:none;opacity:0'>",
+ this.session.getLength() - 1,
+ "</div>"
+ );
+
+ this.element = dom.setInnerHtml(this.element, html.join(""));
+ this.element.style.height = config.minHeight + "px";
+
+ var gutterWidth = this.element.offsetWidth;
+ if (gutterWidth !== this.gutterWidth) {
+ this.gutterWidth = gutterWidth;
+ this._emit("changeGutterWidth", gutterWidth);
+ }
+ };
+
+ this.$showFoldWidgets = true;
+ this.setShowFoldWidgets = function(show) {
+ if (show)
+ dom.addCssClass(this.element, "ace_folding-enabled");
+ else
+ dom.removeCssClass(this.element, "ace_folding-enabled");
+
+ this.$showFoldWidgets = show;
+ this.$padding = null;
+ };
+
+ this.getShowFoldWidgets = function() {
+ return this.$showFoldWidgets;
+ };
+
+ this.$computePadding = function() {
+ if (!this.element.firstChild)
+ return {left: 0, right: 0};
+ var style = dom.computedStyle(this.element.firstChild);
+ this.$padding = {}
+ this.$padding.left = parseInt(style.paddingLeft) + 1;
+ this.$padding.right = parseInt(style.paddingRight);
+ return this.$padding;
+ };
+
+ this.getRegion = function(point) {
+ var padding = this.$padding || this.$computePadding();
+ var rect = this.element.getBoundingClientRect();
+ if (point.x < padding.left + rect.left)
+ return "markers";
+ if (this.$showFoldWidgets && point.x > rect.right - padding.right)
+ return "foldWidgets";
+ };
+
+}).call(Gutter.prototype);
+
+exports.Gutter = Gutter;
+
+});
+
+define('ace/layer/marker', ['require', 'exports', 'module' , 'ace/range', 'ace/lib/dom'], function(require, exports, module) {
+
+
+var Range = require("../range").Range;
+var dom = require("../lib/dom");
+
+var Marker = function(parentEl) {
+ this.element = dom.createElement("div");
+ this.element.className = "ace_layer ace_marker-layer";
+ parentEl.appendChild(this.element);
+};
+
+(function() {
+
+ this.$padding = 0;
+
+ this.setPadding = function(padding) {
+ this.$padding = padding;
+ };
+ this.setSession = function(session) {
+ this.session = session;
+ };
+
+ this.setMarkers = function(markers) {
+ this.markers = markers;
+ };
+
+ this.update = function(config) {
+ var config = config || this.config;
+ if (!config)
+ return;
+
+ this.config = config;
+
+
+ var html = [];
+ for (var key in this.markers) {
+ var marker = this.markers[key];
+
+ if (!marker.range) {
+ marker.update(html, this, this.session, config);
+ continue;
+ }
+
+ var range = marker.range.clipRows(config.firstRow, config.lastRow);
+ if (range.isEmpty()) continue;
+
+ range = range.toScreenRange(this.session);
+ if (marker.renderer) {
+ var top = this.$getTop(range.start.row, config);
+ var left = Math.round(
+ this.$padding + range.start.column * config.characterWidth
+ );
+ marker.renderer(html, range, left, top, config);
+ }
+ else if (range.isMultiLine()) {
+ if (marker.type == "text") {
+ this.drawTextMarker(html, range, marker.clazz, config);
+ } else {
+ this.drawMultiLineMarker(
+ html, range, marker.clazz, config,
+ marker.type
+ );
+ }
+ }
+ else {
+ this.drawSingleLineMarker(
+ html, range, marker.clazz + " start", config,
+ null, marker.type
+ );
+ }
+ }
+ this.element = dom.setInnerHtml(this.element, html.join(""));
+ };
+
+ this.$getTop = function(row, layerConfig) {
+ return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight;
+ };
+
+ // Draws a marker, which spans a range of text on multiple lines
+ this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) {
+ // selection start
+ var row = range.start.row;
+
+ var lineRange = new Range(
+ row, range.start.column,
+ row, this.session.getScreenLastRowColumn(row)
+ );
+ this.drawSingleLineMarker(stringBuilder, lineRange, clazz + " start", layerConfig, 1, "text");
+
+ // selection end
+ row = range.end.row;
+ lineRange = new Range(row, 0, row, range.end.column);
+ this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 0, "text");
+
+ for (row = range.start.row + 1; row < range.end.row; row++) {
+ lineRange.start.row = row;
+ lineRange.end.row = row;
+ lineRange.end.column = this.session.getScreenLastRowColumn(row);
+ this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1, "text");
+ }
+ };
+
+ // Draws a multi line marker, where lines span the full width
+ this.drawMultiLineMarker = function(stringBuilder, range, clazz, config, type) {
+ var padding = type === "background" ? 0 : this.$padding;
+ // from selection start to the end of the line
+ var height = config.lineHeight;
+ var top = this.$getTop(range.start.row, config);
+ var left = Math.round(padding + range.start.column * config.characterWidth);
+
+ stringBuilder.push(
+ "<div class='", clazz, " start' style='",
+ "height:", height, "px;",
+ "right:0;",
+ "top:", top, "px;",
+ "left:", left, "px;'></div>"
+ );
+
+ // from start of the last line to the selection end
+ top = this.$getTop(range.end.row, config);
+ var width = Math.round(range.end.column * config.characterWidth);
+
+ stringBuilder.push(
+ "<div class='", clazz, "' style='",
+ "height:", height, "px;",
+ "width:", width, "px;",
+ "top:", top, "px;",
+ "left:", padding, "px;'></div>"
+ );
+
+ // all the complete lines
+ height = (range.end.row - range.start.row - 1) * config.lineHeight;
+ if (height < 0)
+ return;
+ top = this.$getTop(range.start.row + 1, config);
+
+ stringBuilder.push(
+ "<div class='", clazz, "' style='",
+ "height:", height, "px;",
+ "right:0;",
+ "top:", top, "px;",
+ "left:", padding, "px;'></div>"
+ );
+ };
+
+ // Draws a marker which covers part or whole width of a single screen line
+ this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig, extraLength, type) {
+ var padding = type === "background" ? 0 : this.$padding;
+ var height = layerConfig.lineHeight;
+
+ if (type === "background")
+ var width = layerConfig.width;
+ else
+ width = Math.round((range.end.column + (extraLength || 0) - range.start.column) * layerConfig.characterWidth);
+
+ var top = this.$getTop(range.start.row, layerConfig);
+ var left = Math.round(
+ padding + range.start.column * layerConfig.characterWidth
+ );
+
+ stringBuilder.push(
+ "<div class='", clazz, "' style='",
+ "height:", height, "px;",
+ "width:", width, "px;",
+ "top:", top, "px;",
+ "left:", left,"px;'></div>"
+ );
+ };
+
+}).call(Marker.prototype);
+
+exports.Marker = Marker;
+
+});
+
+define('ace/layer/text', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/dom', 'ace/lib/lang', 'ace/lib/useragent', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var oop = require("../lib/oop");
+var dom = require("../lib/dom");
+var lang = require("../lib/lang");
+var useragent = require("../lib/useragent");
+var EventEmitter = require("../lib/event_emitter").EventEmitter;
+
+var Text = function(parentEl) {
+ this.element = dom.createElement("div");
+ this.element.className = "ace_layer ace_text-layer";
+ parentEl.appendChild(this.element);
+
+ this.$characterSize = this.$measureSizes() || {width: 0, height: 0};
+ this.$pollSizeChanges();
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+
+ this.EOF_CHAR = "\xB6"; //"¶";
+ this.EOL_CHAR = "\xAC"; //"¬";
+ this.TAB_CHAR = "\u2192"; //"→" "\u21E5";
+ this.SPACE_CHAR = "\xB7"; //"·";
+ this.$padding = 0;
+
+ this.setPadding = function(padding) {
+ this.$padding = padding;
+ this.element.style.padding = "0 " + padding + "px";
+ };
+
+ this.getLineHeight = function() {
+ return this.$characterSize.height || 1;
+ };
+
+ this.getCharacterWidth = function() {
+ return this.$characterSize.width || 1;
+ };
+
+ this.checkForSizeChanges = function() {
+ var size = this.$measureSizes();
+ if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) {
+ this.$characterSize = size;
+ this._emit("changeCharacterSize", {data: size});
+ }
+ };
+
+ this.$pollSizeChanges = function() {
+ var self = this;
+ this.$pollSizeChangesTimer = setInterval(function() {
+ self.checkForSizeChanges();
+ }, 500);
+ };
+
+ this.$fontStyles = {
+ fontFamily : 1,
+ fontSize : 1,
+ fontWeight : 1,
+ fontStyle : 1,
+ lineHeight : 1
+ };
+
+ this.$measureSizes = useragent.isIE || useragent.isOldGecko ? function() {
+ var n = 1000;
+ if (!this.$measureNode) {
+ var measureNode = this.$measureNode = dom.createElement("div");
+ var style = measureNode.style;
+
+ style.width = style.height = "auto";
+ style.left = style.top = (-n * 40) + "px";
+
+ style.visibility = "hidden";
+ style.position = "fixed";
+ style.overflow = "visible";
+ style.whiteSpace = "nowrap";
+
+ // in FF 3.6 monospace fonts can have a fixed sub pixel width.
+ // that's why we have to measure many characters
+ // Note: characterWidth can be a float!
+ measureNode.innerHTML = lang.stringRepeat("Xy", n);
+
+ if (this.element.ownerDocument.body) {
+ this.element.ownerDocument.body.appendChild(measureNode);
+ } else {
+ var container = this.element.parentNode;
+ while (!dom.hasCssClass(container, "ace_editor"))
+ container = container.parentNode;
+ container.appendChild(measureNode);
+ }
+ }
+
+ // Size and width can be null if the editor is not visible or
+ // detached from the document
+ if (!this.element.offsetWidth)
+ return null;
+
+ var style = this.$measureNode.style;
+ var computedStyle = dom.computedStyle(this.element);
+ for (var prop in this.$fontStyles)
+ style[prop] = computedStyle[prop];
+
+ var size = {
+ height: this.$measureNode.offsetHeight,
+ width: this.$measureNode.offsetWidth / (n * 2)
+ };
+
+ // Size and width can be null if the editor is not visible or
+ // detached from the document
+ if (size.width == 0 || size.height == 0)
+ return null;
+
+ return size;
+ }
+ : function() {
+ if (!this.$measureNode) {
+ var measureNode = this.$measureNode = dom.createElement("div");
+ var style = measureNode.style;
+
+ style.width = style.height = "auto";
+ style.left = style.top = -100 + "px";
+
+ style.visibility = "hidden";
+ style.position = "fixed";
+ style.overflow = "visible";
+ style.whiteSpace = "nowrap";
+
+ measureNode.innerHTML = lang.stringRepeat("X", 1024);
+
+ var container = this.element.parentNode;
+ while (container && !dom.hasCssClass(container, "ace_editor"))
+ container = container.parentNode;
+
+ if (!container)
+ return this.$measureNode = null;
+
+ container.appendChild(measureNode);
+ }
+
+ var rect = this.$measureNode.getBoundingClientRect();
+
+ var size = {
+ height: rect.height,
+ width: rect.width / 1024
+ };
+
+ // Size and width can be null if the editor is not visible or
+ // detached from the document
+ if (size.width == 0 || size.height == 0)
+ return null;
+
+ return size;
+ };
+
+ this.setSession = function(session) {
+ this.session = session;
+ this.$computeTabString();
+ };
+
+ this.showInvisibles = false;
+ this.setShowInvisibles = function(showInvisibles) {
+ if (this.showInvisibles == showInvisibles)
+ return false;
+
+ this.showInvisibles = showInvisibles;
+ this.$computeTabString();
+ return true;
+ };
+
+ this.displayIndentGuides = true;
+ this.setDisplayIndentGuides = function(display) {
+ if (this.displayIndentGuides == display)
+ return false;
+
+ this.displayIndentGuides = display;
+ this.$computeTabString();
+ return true;
+ };
+
+ this.$tabStrings = [];
+ this.onChangeTabSize =
+ this.$computeTabString = function() {
+ var tabSize = this.session.getTabSize();
+ this.tabSize = tabSize;
+ var tabStr = this.$tabStrings = [0];
+ for (var i = 1; i < tabSize + 1; i++) {
+ if (this.showInvisibles) {
+ tabStr.push("<span class='ace_invisible'>"
+ + this.TAB_CHAR
+ + Array(i).join(" ")
+ + "</span>");
+ } else {
+ tabStr.push(new Array(i+1).join(" "));
+ }
+ }
+ if (this.displayIndentGuides) {
+ this.$indentGuideRe = /\s\S| \t|\t |\s$/;
+ var className = "ace_indent-guide";
+ var content = Array(this.tabSize + 1).join(" ");
+ var tabContent = content;
+ if (this.showInvisibles) {
+ className += " ace_invisible";
+ tabContent = this.TAB_CHAR + content.substr(6);
+ }
+
+ this.$tabStrings[" "] = "<span class='" + className + "'>" + content + "</span>";
+ this.$tabStrings["\t"] = "<span class='" + className + "'>" + tabContent + "</span>";
+ }
+ };
+
+ this.updateLines = function(config, firstRow, lastRow) {
+ // Due to wrap line changes there can be new lines if e.g.
+ // the line to updated wrapped in the meantime.
+ if (this.config.lastRow != config.lastRow ||
+ this.config.firstRow != config.firstRow) {
+ this.scrollLines(config);
+ }
+ this.config = config;
+
+ var first = Math.max(firstRow, config.firstRow);
+ var last = Math.min(lastRow, config.lastRow);
+
+ var lineElements = this.element.childNodes;
+ var lineElementsIdx = 0;
+
+ for (var row = config.firstRow; row < first; row++) {
+ var foldLine = this.session.getFoldLine(row);
+ if (foldLine) {
+ if (foldLine.containsRow(first)) {
+ first = foldLine.start.row;
+ break;
+ } else {
+ row = foldLine.end.row;
+ }
+ }
+ lineElementsIdx ++;
+ }
+
+ var row = first;
+ var foldLine = this.session.getNextFoldLine(row);
+ var foldStart = foldLine ? foldLine.start.row : Infinity;
+
+ while (true) {
+ if (row > foldStart) {
+ row = foldLine.end.row+1;
+ foldLine = this.session.getNextFoldLine(row, foldLine);
+ foldStart = foldLine ? foldLine.start.row :Infinity;
+ }
+ if (row > last)
+ break;
+
+ var lineElement = lineElements[lineElementsIdx++];
+ if (lineElement) {
+ var html = [];
+ this.$renderLine(
+ html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false
+ );
+ dom.setInnerHtml(lineElement, html.join(""));
+ }
+ row++;
+ }
+ };
+
+ this.scrollLines = function(config) {
+ var oldConfig = this.config;
+ this.config = config;
+
+ if (!oldConfig || oldConfig.lastRow < config.firstRow)
+ return this.update(config);
+
+ if (config.lastRow < oldConfig.firstRow)
+ return this.update(config);
+
+ var el = this.element;
+ if (oldConfig.firstRow < config.firstRow)
+ for (var row=this.session.getFoldedRowCount(oldConfig.firstRow, config.firstRow - 1); row>0; row--)
+ el.removeChild(el.firstChild);
+
+ if (oldConfig.lastRow > config.lastRow)
+ for (var row=this.session.getFoldedRowCount(config.lastRow + 1, oldConfig.lastRow); row>0; row--)
+ el.removeChild(el.lastChild);
+
+ if (config.firstRow < oldConfig.firstRow) {
+ var fragment = this.$renderLinesFragment(config, config.firstRow, oldConfig.firstRow - 1);
+ if (el.firstChild)
+ el.insertBefore(fragment, el.firstChild);
+ else
+ el.appendChild(fragment);
+ }
+
+ if (config.lastRow > oldConfig.lastRow) {
+ var fragment = this.$renderLinesFragment(config, oldConfig.lastRow + 1, config.lastRow);
+ el.appendChild(fragment);
+ }
+ };
+
+ this.$renderLinesFragment = function(config, firstRow, lastRow) {
+ var fragment = this.element.ownerDocument.createDocumentFragment();
+ var row = firstRow;
+ var foldLine = this.session.getNextFoldLine(row);
+ var foldStart = foldLine ? foldLine.start.row : Infinity;
+
+ while (true) {
+ if (row > foldStart) {
+ row = foldLine.end.row+1;
+ foldLine = this.session.getNextFoldLine(row, foldLine);
+ foldStart = foldLine ? foldLine.start.row : Infinity;
+ }
+ if (row > lastRow)
+ break;
+
+ var container = dom.createElement("div");
+
+ var html = [];
+ // Get the tokens per line as there might be some lines in between
+ // beeing folded.
+ this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
+
+ // don't use setInnerHtml since we are working with an empty DIV
+ container.innerHTML = html.join("");
+ if (this.$useLineGroups()) {
+ container.className = 'ace_line_group';
+ fragment.appendChild(container);
+ } else {
+ var lines = container.childNodes
+ while(lines.length)
+ fragment.appendChild(lines[0]);
+ }
+
+ row++;
+ }
+ return fragment;
+ };
+
+ this.update = function(config) {
+ this.config = config;
+
+ var html = [];
+ var firstRow = config.firstRow, lastRow = config.lastRow;
+
+ var row = firstRow;
+ var foldLine = this.session.getNextFoldLine(row);
+ var foldStart = foldLine ? foldLine.start.row : Infinity;
+
+ while (true) {
+ if (row > foldStart) {
+ row = foldLine.end.row+1;
+ foldLine = this.session.getNextFoldLine(row, foldLine);
+ foldStart = foldLine ? foldLine.start.row :Infinity;
+ }
+ if (row > lastRow)
+ break;
+
+ if (this.$useLineGroups())
+ html.push("<div class='ace_line_group'>")
+
+ this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
+
+ if (this.$useLineGroups())
+ html.push("</div>"); // end the line group
+
+ row++;
+ }
+ this.element = dom.setInnerHtml(this.element, html.join(""));
+ };
+
+ this.$textToken = {
+ "text": true,
+ "rparen": true,
+ "lparen": true
+ };
+
+ this.$renderToken = function(stringBuilder, screenColumn, token, value) {
+ var self = this;
+ var replaceReg = /\t|&|<|( +)|([\u0000-\u0019\u00a0\u1680\u180E\u2000-\u200b\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE [...]
+ var replaceFunc = function(c, a, b, tabIdx, idx4) {
+ if (a) {
+ return new Array(c.length+1).join(" ");
+ } else if (c == "&") {
+ return "&";
+ } else if (c == "<") {
+ return "<";
+ } else if (c == "\t") {
+ var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
+ screenColumn += tabSize - 1;
+ return self.$tabStrings[tabSize];
+ } else if (c == "\u3000") {
+ // U+3000 is both invisible AND full-width, so must be handled uniquely
+ var classToUse = self.showInvisibles ? "ace_cjk ace_invisible" : "ace_cjk";
+ var space = self.showInvisibles ? self.SPACE_CHAR : "";
+ screenColumn += 1;
+ return "<span class='" + classToUse + "' style='width:" +
+ (self.config.characterWidth * 2) +
+ "px'>" + space + "</span>";
+ } else if (b) {
+ return "<span class='ace_invisible ace_invalid'>" + self.SPACE_CHAR + "</span>";
+ } else {
+ screenColumn += 1;
+ return "<span class='ace_cjk' style='width:" +
+ (self.config.characterWidth * 2) +
+ "px'>" + c + "</span>";
+ }
+ };
+
+ var output = value.replace(replaceReg, replaceFunc);
+
+ if (!this.$textToken[token.type]) {
+ var classes = "ace_" + token.type.replace(/\./g, " ace_");
+ var style = "";
+ if (token.type == "fold")
+ style = " style='width:" + (token.value.length * this.config.characterWidth) + "px;' ";
+ stringBuilder.push("<span class='", classes, "'", style, ">", output, "</span>");
+ }
+ else {
+ stringBuilder.push(output);
+ }
+ return screenColumn + value.length;
+ };
+
+ this.renderIndentGuide = function(stringBuilder, value) {
+ var cols = value.search(this.$indentGuideRe);
+ if (cols <= 0)
+ return value;
+ if (value[0] == " ") {
+ cols -= cols % this.tabSize;
+ stringBuilder.push(Array(cols/this.tabSize + 1).join(this.$tabStrings[" "]));
+ return value.substr(cols);
+ } else if (value[0] == "\t") {
+ stringBuilder.push(Array(cols + 1).join(this.$tabStrings["\t"]));
+ return value.substr(cols);
+ }
+ return value;
+ };
+
+ this.$renderWrappedLine = function(stringBuilder, tokens, splits, onlyContents) {
+ var chars = 0;
+ var split = 0;
+ var splitChars = splits[0];
+ var screenColumn = 0;
+
+ for (var i = 0; i < tokens.length; i++) {
+ var token = tokens[i];
+ var value = token.value;
+ if (i == 0 && this.displayIndentGuides) {
+ chars = value.length;
+ value = this.renderIndentGuide(stringBuilder, value);
+ if (!value)
+ continue;
+ chars -= value.length;
+ }
+
+ if (chars + value.length < splitChars) {
+ screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
+ chars += value.length;
+ } else {
+ while (chars + value.length >= splitChars) {
+ screenColumn = this.$renderToken(
+ stringBuilder, screenColumn,
+ token, value.substring(0, splitChars - chars)
+ );
+ value = value.substring(splitChars - chars);
+ chars = splitChars;
+
+ if (!onlyContents) {
+ stringBuilder.push("</div>",
+ "<div class='ace_line' style='height:",
+ this.config.lineHeight, "px'>"
+ );
+ }
+
+ split ++;
+ screenColumn = 0;
+ splitChars = splits[split] || Number.MAX_VALUE;
+ }
+ if (value.length != 0) {
+ chars += value.length;
+ screenColumn = this.$renderToken(
+ stringBuilder, screenColumn, token, value
+ );
+ }
+ }
+ }
+ };
+
+ this.$renderSimpleLine = function(stringBuilder, tokens) {
+ var screenColumn = 0;
+ var token = tokens[0];
+ var value = token.value;
+ if (this.displayIndentGuides)
+ value = this.renderIndentGuide(stringBuilder, value);
+ if (value)
+ screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
+ for (var i = 1; i < tokens.length; i++) {
+ token = tokens[i];
+ value = token.value;
+ screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
+ }
+ };
+
+ // row is either first row of foldline or not in fold
+ this.$renderLine = function(stringBuilder, row, onlyContents, foldLine) {
+ if (!foldLine && foldLine != false)
+ foldLine = this.session.getFoldLine(row);
+
+ if (foldLine)
+ var tokens = this.$getFoldLineTokens(row, foldLine);
+ else
+ var tokens = this.session.getTokens(row);
+
+
+ if (!onlyContents) {
+ stringBuilder.push(
+ "<div class='ace_line' style='height:", this.config.lineHeight, "px'>"
+ );
+ }
+
+ if (tokens.length) {
+ var splits = this.session.getRowSplitData(row);
+ if (splits && splits.length)
+ this.$renderWrappedLine(stringBuilder, tokens, splits, onlyContents);
+ else
+ this.$renderSimpleLine(stringBuilder, tokens);
+ }
+
+ if (this.showInvisibles) {
+ if (foldLine)
+ row = foldLine.end.row
+
+ stringBuilder.push(
+ "<span class='ace_invisible'>",
+ row == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR,
+ "</span>"
+ );
+ }
+ if (!onlyContents)
+ stringBuilder.push("</div>");
+ };
+
+ this.$getFoldLineTokens = function(row, foldLine) {
+ var session = this.session;
+ var renderTokens = [];
+
+ function addTokens(tokens, from, to) {
+ var idx = 0, col = 0;
+ while ((col + tokens[idx].value.length) < from) {
+ col += tokens[idx].value.length;
+ idx++;
+
+ if (idx == tokens.length)
+ return;
+ }
+ if (col != from) {
+ var value = tokens[idx].value.substring(from - col);
+ // Check if the token value is longer then the from...to spacing.
+ if (value.length > (to - from))
+ value = value.substring(0, to - from);
+
+ renderTokens.push({
+ type: tokens[idx].type,
+ value: value
+ });
+
+ col = from + value.length;
+ idx += 1;
+ }
+
+ while (col < to && idx < tokens.length) {
+ var value = tokens[idx].value;
+ if (value.length + col > to) {
+ renderTokens.push({
+ type: tokens[idx].type,
+ value: value.substring(0, to - col)
+ });
+ } else
+ renderTokens.push(tokens[idx]);
+ col += value.length;
+ idx += 1;
+ }
+ }
+
+ var tokens = session.getTokens(row);
+ foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
+ if (placeholder) {
+ renderTokens.push({
+ type: "fold",
+ value: placeholder
+ });
+ } else {
+ if (isNewRow)
+ tokens = session.getTokens(row);
+
+ if (tokens.length)
+ addTokens(tokens, lastColumn, column);
+ }
+ }, foldLine.end.row, this.session.getLine(foldLine.end.row).length);
+
+ return renderTokens;
+ };
+
+ this.$useLineGroups = function() {
+ // For the updateLines function to work correctly, it's important that the
+ // child nodes of this.element correspond on a 1-to-1 basis to rows in the
+ // document (as distinct from lines on the screen). For sessions that are
+ // wrapped, this means we need to add a layer to the node hierarchy (tagged
+ // with the class name ace_line_group).
+ return this.session.getUseWrapMode();
+ };
+
+ this.destroy = function() {
+ clearInterval(this.$pollSizeChangesTimer);
+ if (this.$measureNode)
+ this.$measureNode.parentNode.removeChild(this.$measureNode);
+ delete this.$measureNode;
+ };
+
+}).call(Text.prototype);
+
+exports.Text = Text;
+
+});
+
+define('ace/layer/cursor', ['require', 'exports', 'module' , 'ace/lib/dom'], function(require, exports, module) {
+
+
+var dom = require("../lib/dom");
+
+var Cursor = function(parentEl) {
+ this.element = dom.createElement("div");
+ this.element.className = "ace_layer ace_cursor-layer";
+ parentEl.appendChild(this.element);
+
+ this.isVisible = false;
+ this.isBlinking = true;
+
+ this.cursors = [];
+ this.cursor = this.addCursor();
+};
+
+(function() {
+
+ this.$padding = 0;
+ this.setPadding = function(padding) {
+ this.$padding = padding;
+ };
+
+ this.setSession = function(session) {
+ this.session = session;
+ };
+
+ this.setBlinking = function(blinking) {
+ this.isBlinking = blinking;
+ if (blinking)
+ this.restartTimer();
+ };
+
+ this.addCursor = function() {
+ var el = dom.createElement("div");
+ var className = "ace_cursor";
+ if (!this.isVisible)
+ className += " ace_hidden";
+ if (this.overwrite)
+ className += " ace_overwrite";
+
+ el.className = className;
+ this.element.appendChild(el);
+ this.cursors.push(el);
+ return el;
+ };
+
+ this.removeCursor = function() {
+ if (this.cursors.length > 1) {
+ var el = this.cursors.pop();
+ el.parentNode.removeChild(el);
+ return el;
+ }
+ };
+
+ this.hideCursor = function() {
+ this.isVisible = false;
+ for (var i = this.cursors.length; i--; )
+ dom.addCssClass(this.cursors[i], "ace_hidden");
+ clearInterval(this.blinkId);
+ };
+
+ this.showCursor = function() {
+ this.isVisible = true;
+ for (var i = this.cursors.length; i--; )
+ dom.removeCssClass(this.cursors[i], "ace_hidden");
+
+ this.element.style.visibility = "";
+ this.restartTimer();
+ };
+
+ this.restartTimer = function() {
+ clearInterval(this.blinkId);
+ if (!this.isBlinking)
+ return;
+ if (!this.isVisible)
+ return;
+
+ var element = this.cursors.length == 1 ? this.cursor : this.element;
+ this.blinkId = setInterval(function() {
+ element.style.visibility = "hidden";
+ setTimeout(function() {
+ element.style.visibility = "";
+ }, 400);
+ }, 1000);
+ };
+
+ this.getPixelPosition = function(position, onScreen) {
+ if (!this.config || !this.session) {
+ return {
+ left : 0,
+ top : 0
+ };
+ }
+
+ if (!position)
+ position = this.session.selection.getCursor();
+ var pos = this.session.documentToScreenPosition(position);
+ var cursorLeft = Math.round(this.$padding +
+ pos.column * this.config.characterWidth);
+ var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) *
+ this.config.lineHeight;
+
+ return {
+ left : cursorLeft,
+ top : cursorTop
+ };
+ };
+
+ this.update = function(config) {
+ this.config = config;
+
+ if (this.session.selectionMarkerCount > 0) {
+ var selections = this.session.$selectionMarkers;
+ var i = 0, sel, cursorIndex = 0;
+
+ for (var i = selections.length; i--; ) {
+ sel = selections[i];
+ var pixelPos = this.getPixelPosition(sel.cursor, true);
+
+ var style = (this.cursors[cursorIndex++] || this.addCursor()).style;
+
+ style.left = pixelPos.left + "px";
+ style.top = pixelPos.top + "px";
+ style.width = config.characterWidth + "px";
+ style.height = config.lineHeight + "px";
+ }
+ if (cursorIndex > 1)
+ while (this.cursors.length > cursorIndex)
+ this.removeCursor();
+ } else {
+ var pixelPos = this.getPixelPosition(null, true);
+ var style = this.cursor.style;
+ style.left = pixelPos.left + "px";
+ style.top = pixelPos.top + "px";
+ style.width = config.characterWidth + "px";
+ style.height = config.lineHeight + "px";
+
+ while (this.cursors.length > 1)
+ this.removeCursor();
+ }
+
+ var overwrite = this.session.getOverwrite();
+ if (overwrite != this.overwrite)
+ this.$setOverite(overwrite);
+
+ // cache for textarea and gutter highlight
+ this.$pixelPos = pixelPos;
+
+ this.restartTimer();
+ };
+
+ this.$setOverite = function(overwrite) {
+ this.overwrite = overwrite;
+ for (var i = this.cursors.length; i--; ) {
+ if (overwrite)
+ dom.addCssClass(this.cursors[i], "ace_overwrite");
+ else
+ dom.removeCssClass(this.cursors[i], "ace_overwrite");
+ }
+ };
+
+ this.destroy = function() {
+ clearInterval(this.blinkId);
+ }
+
+}).call(Cursor.prototype);
+
+exports.Cursor = Cursor;
+
+});
+
+define('ace/scrollbar', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/lib/dom', 'ace/lib/event', 'ace/lib/event_emitter'], function(require, exports, module) {
+
+
+var oop = require("./lib/oop");
+var dom = require("./lib/dom");
+var event = require("./lib/event");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+
+/**
+ * new ScrollBar(parent)
+ * - parent (DOMElement): A DOM element
+ *
+ * Creates a new `ScrollBar`. `parent` is the owner of the scroll bar.
+ *
+ **/
+var ScrollBar = function(parent) {
+ this.element = dom.createElement("div");
+ this.element.className = "ace_sb";
+
+ this.inner = dom.createElement("div");
+ this.element.appendChild(this.inner);
+
+ parent.appendChild(this.element);
+
+ // in OSX lion the scrollbars appear to have no width. In this case resize
+ // the to show the scrollbar but still pretend that the scrollbar has a width
+ // of 0px
+ // in Firefox 6+ scrollbar is hidden if element has the same width as scrollbar
+ // make element a little bit wider to retain scrollbar when page is zoomed
+ this.width = dom.scrollbarWidth(parent.ownerDocument);
+ this.element.style.width = (this.width || 15) + 5 + "px";
+
+ event.addListener(this.element, "scroll", this.onScroll.bind(this));
+};
+
+(function() {
+ oop.implement(this, EventEmitter);
+ this.onScroll = function() {
+ this._emit("scroll", {data: this.element.scrollTop});
+ };
+ this.getWidth = function() {
+ return this.width;
+ };
+ this.setHeight = function(height) {
+ this.element.style.height = height + "px";
+ };
+ this.setInnerHeight = function(height) {
+ this.inner.style.height = height + "px";
+ };
+ // TODO: on chrome 17+ after for small zoom levels after this function
+ // this.element.scrollTop != scrollTop which makes page to scroll up.
+ this.setScrollTop = function(scrollTop) {
+ this.element.scrollTop = scrollTop;
+ };
+
+}).call(ScrollBar.prototype);
+
+exports.ScrollBar = ScrollBar;
+});
+
+define('ace/renderloop', ['require', 'exports', 'module' , 'ace/lib/event'], function(require, exports, module) {
+
+
+var event = require("./lib/event");
+
+/** internal, hide
+ * new RenderLoop(onRender, win)
+ *
+ *
+ *
+**/
+var RenderLoop = function(onRender, win) {
+ this.onRender = onRender;
+ this.pending = false;
+ this.changes = 0;
+ this.window = win || window;
+};
+
+(function() {
+
+ /** internal, hide
+ * RenderLoop.schedule(change)
+ * - change (Array):
+ *
+ *
+ **/
+ this.schedule = function(change) {
+ //this.onRender(change);
+ //return;
+ this.changes = this.changes | change;
+ if (!this.pending) {
+ this.pending = true;
+ var _self = this;
+ event.nextTick(function() {
+ _self.pending = false;
+ var changes;
+ while (changes = _self.changes) {
+ _self.changes = 0;
+ _self.onRender(changes);
+ }
+ }, this.window);
+ }
+ };
+
+}).call(RenderLoop.prototype);
+
+exports.RenderLoop = RenderLoop;
+});
+define("text!ace/css/editor.css", [], ".ace_editor {\n" +
+ " position: absolute;\n" +
+ " overflow: hidden;\n" +
+ " font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Droid Sans Mono', 'Consolas', monospace;\n" +
+ " font-size: 12px;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_scroller {\n" +
+ " position: absolute;\n" +
+ " overflow: hidden;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_content {\n" +
+ " position: absolute;\n" +
+ " box-sizing: border-box;\n" +
+ " -moz-box-sizing: border-box;\n" +
+ " -webkit-box-sizing: border-box;\n" +
+ " cursor: text;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter {\n" +
+ " position: absolute;\n" +
+ " overflow : hidden;\n" +
+ " height: 100%;\n" +
+ " width: auto;\n" +
+ " cursor: default;\n" +
+ " z-index: 4;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter_active_line {\n" +
+ " position: absolute;\n" +
+ " left: 0;\n" +
+ " right: 0;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_scroller.horscroll {\n" +
+ " box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter-cell {\n" +
+ " padding-left: 19px;\n" +
+ " padding-right: 6px;\n" +
+ " background-repeat: no-repeat;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter-cell.ace_error {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4 [...]
+ " background-repeat: no-repeat;\n" +
+ " background-position: 2px center;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter-cell.ace_warning {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4 [...]
+ " background-position: 2px center;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter-cell.ace_info {\n" +
+ " background-image: url(\"data:image/gif;base64,R0lGODlhEAAQAMQAAAAAAEFBQVJSUl5eXmRkZGtra39/f4WFhYmJiZGRkaampry8vMPDw8zMzNXV1dzc3OTk5Orq6vDw8P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABQALAAAAAAQABAAAAUuICWOZGmeaBml5XGwFCQSBGyXRSAwtqQIiRuiwIM5BoYVbEFIyGCQoeJGrVptIQA7\");\n" +
+ " background-position: 2px center;\n" +
+ "}\n" +
+ ".ace_dark .ace_gutter-cell.ace_info {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4 [...]
+ "}\n" +
+ "\n" +
+ ".ace_editor .ace_sb {\n" +
+ " position: absolute;\n" +
+ " overflow-x: hidden;\n" +
+ " overflow-y: scroll;\n" +
+ " right: 0;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor .ace_sb div {\n" +
+ " position: absolute;\n" +
+ " width: 1px;\n" +
+ " left: 0;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor .ace_print_margin_layer {\n" +
+ " z-index: 1;\n" +
+ " position: absolute;\n" +
+ " overflow: hidden;\n" +
+ " margin: 0;\n" +
+ " left: 0;\n" +
+ " height: 100%;\n" +
+ " width: 100%;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor .ace_print_margin {\n" +
+ " position: absolute;\n" +
+ " height: 100%;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor > textarea {\n" +
+ " position: fixed;\n" +
+ " z-index: 0;\n" +
+ " width: 0.5em;\n" +
+ " height: 1em;\n" +
+ " opacity: 0;\n" +
+ " background: transparent;\n" +
+ " appearance: none;\n" +
+ " -moz-appearance: none;\n" +
+ " border: none;\n" +
+ " resize: none;\n" +
+ " outline: none;\n" +
+ " overflow: hidden;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor > textarea.ace_composition {\n" +
+ " background: #fff;\n" +
+ " color: #000;\n" +
+ " z-index: 1000;\n" +
+ " opacity: 1;\n" +
+ " border: solid lightgray 1px;\n" +
+ " margin: -1px\n" +
+ "}\n" +
+ "\n" +
+ ".ace_layer {\n" +
+ " z-index: 1;\n" +
+ " position: absolute;\n" +
+ " overflow: hidden;\n" +
+ " white-space: nowrap;\n" +
+ " height: 100%;\n" +
+ " width: 100%;\n" +
+ " box-sizing: border-box;\n" +
+ " -moz-box-sizing: border-box;\n" +
+ " -webkit-box-sizing: border-box;\n" +
+ " /* setting pointer-events: auto; on node under the mouse, which changes\n" +
+ " during scroll, will break mouse wheel scrolling in Safari */\n" +
+ " pointer-events: none;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_gutter .ace_layer {\n" +
+ " position: relative;\n" +
+ " min-width: 40px;\n" +
+ " width: auto;\n" +
+ " text-align: right;\n" +
+ " pointer-events: auto;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_text-layer {\n" +
+ " color: black;\n" +
+ " font: inherit !important;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_cjk {\n" +
+ " display: inline-block;\n" +
+ " text-align: center;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_cursor-layer {\n" +
+ " z-index: 4;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_cursor {\n" +
+ " z-index: 4;\n" +
+ " position: absolute;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_cursor.ace_hidden {\n" +
+ " opacity: 0.2;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_editor.multiselect .ace_cursor {\n" +
+ " border-left-width: 1px;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_line {\n" +
+ " white-space: nowrap;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_marker-layer .ace_step {\n" +
+ " position: absolute;\n" +
+ " z-index: 3;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_marker-layer .ace_selection {\n" +
+ " position: absolute;\n" +
+ " z-index: 5;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_marker-layer .ace_bracket {\n" +
+ " position: absolute;\n" +
+ " z-index: 6;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_marker-layer .ace_active_line {\n" +
+ " position: absolute;\n" +
+ " z-index: 0;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_marker-layer .ace_selected_word {\n" +
+ " position: absolute;\n" +
+ " z-index: 4;\n" +
+ " box-sizing: border-box;\n" +
+ " -moz-box-sizing: border-box;\n" +
+ " -webkit-box-sizing: border-box;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_line .ace_fold {\n" +
+ " box-sizing: border-box;\n" +
+ " -moz-box-sizing: border-box;\n" +
+ " -webkit-box-sizing: border-box;\n" +
+ "\n" +
+ " display: inline-block;\n" +
+ " height: 11px;\n" +
+ " margin-top: -2px;\n" +
+ " vertical-align: middle;\n" +
+ "\n" +
+ " background-image:\n" +
+ " url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%11%00%00%00%09%08%06%00%00%00%D4%E8%C7%0C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E% [...]
+ " url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%007%08%06%00%00%00%C4%DD%80C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C [...]
+ " background-repeat: no-repeat, repeat-x;\n" +
+ " background-position: center center, top left;\n" +
+ " color: transparent;\n" +
+ "\n" +
+ " border: 1px solid black;\n" +
+ " -moz-border-radius: 2px;\n" +
+ " -webkit-border-radius: 2px;\n" +
+ " border-radius: 2px;\n" +
+ "\n" +
+ " cursor: pointer;\n" +
+ " pointer-events: auto;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_dark .ace_fold {\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold:hover{\n" +
+ " background-image:\n" +
+ " url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%11%00%00%00%09%08%06%00%00%00%D4%E8%C7%0C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E% [...]
+ " url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%007%08%06%00%00%00%C4%DD%80C%00%00%03%1EiCCPICC%20Profile%00%00x%01%85T%DFk%D3P%14%FE%DAe%9D%B0%E1%8B%3Ag%11%09%3Eh%91ndStC%9C%B6kW%BA%CDZ%EA6%B7!H%9B%A6m%5C%9A%C6%24%ED~%B0%07%D9%8Bo%3A%C5w%F1%07%3E%F9%07%0C%D9%83o%7B%92%0D%C6%14a%F8%AC%88%22L%F6%22%B3%9E%9B4M'S%03%B9%F7%BB%DF%F9%EE9'%E7%E4%5E%A0%F9qZ%D3%14%2F%0F%14USO%C5%C2%FC%C4%E4%14%DF%F2%01%5E%1CC%2B%FChM%8B%86%16J%26G%40%0F%D3%B2y%EF%B3%F3%0E%1E%C [...]
+ " background-repeat: no-repeat, repeat-x;\n" +
+ " background-position: center center, top left;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_dragging .ace_content {\n" +
+ " cursor: move;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_folding-enabled > .ace_gutter-cell {\n" +
+ " padding-right: 13px;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold-widget {\n" +
+ " box-sizing: border-box;\n" +
+ " -moz-box-sizing: border-box;\n" +
+ " -webkit-box-sizing: border-box;\n" +
+ "\n" +
+ " margin: 0 -12px 1px 1px;\n" +
+ " display: inline-block;\n" +
+ " height: 14px;\n" +
+ " width: 11px;\n" +
+ " vertical-align: text-bottom;\n" +
+ "\n" +
+ " background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%00%05%08%06%00%00%00%8Do%26%E5%00%00%004IDATx%DAe%8A%B1%0D%000%0C%C2%F2%2CK%96%BC%D0%8F9%81%88H%E9%D0%0E%96%C0%10%92%3E%02%80%5E%82%E4%A9*-%EEsw%C8%CC%11%EE%96w%D8%DC%E9*Eh%0C%151(%00%00%00%00IEND%AEB%60%82\");\n" +
+ " background-repeat: no-repeat;\n" +
+ " background-position: center 4px;\n" +
+ "\n" +
+ " border-radius: 3px;\n" +
+ " \n" +
+ " border: 1px solid transparent;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold-widget.end {\n" +
+ " background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%05%00%00%00%05%08%06%00%00%00%8Do%26%E5%00%00%004IDATx%DAm%C7%C1%09%000%08C%D1%8C%ECE%C8E(%8E%EC%02)%1EZJ%F1%C1'%04%07I%E1%E5%EE%CAL%F5%A2%99%99%22%E2%D6%1FU%B5%FE0%D9x%A7%26Wz5%0E%D5%00%00%00%00IEND%AEB%60%82\");\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold-widget.closed {\n" +
+ " background-image: url(\"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%03%00%00%00%06%08%06%00%00%00%06%E5%24%0C%00%00%009IDATx%DA5%CA%C1%09%000%08%03%C0%AC*(%3E%04%C1%0D%BA%B1%23%A4Uh%E0%20%81%C0%CC%F8%82%81%AA%A2%AArGfr%88%08%11%11%1C%DD%7D%E0%EE%5B%F6%F6%CB%B8%05Q%2F%E9tai%D9%00%00%00%00IEND%AEB%60%82\");\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold-widget:hover {\n" +
+ " border: 1px solid rgba(0, 0, 0, 0.3);\n" +
+ " background-color: rgba(255, 255, 255, 0.2);\n" +
+ " -moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);\n" +
+ " -webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);\n" +
+ " box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);\n" +
+ " background-position: center 4px;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fold-widget:active {\n" +
+ " border: 1px solid rgba(0, 0, 0, 0.4);\n" +
+ " background-color: rgba(0, 0, 0, 0.05);\n" +
+ " -moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);\n" +
+ " -webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);\n" +
+ " box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);\n" +
+ "}\n" +
+ "/**\n" +
+ " * Dark version for fold widgets\n" +
+ " */\n" +
+ ".ace_dark .ace_fold-widget {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC\");\n" +
+ "}\n" +
+ ".ace_dark .ace_fold-widget.end {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==\");\n" +
+ "}\n" +
+ ".ace_dark .ace_fold-widget.closed {\n" +
+ " background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==\");\n" +
+ "}\n" +
+ ".ace_dark .ace_fold-widget:hover {\n" +
+ " box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n" +
+ " background-color: rgba(255, 255, 255, 0.1);\n" +
+ "}\n" +
+ ".ace_dark .ace_fold-widget:active {\n" +
+ " -moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n" +
+ " -webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n" +
+ " box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);\n" +
+ "}\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ ".ace_fold-widget.invalid {\n" +
+ " background-color: #FFB4B4;\n" +
+ " border-color: #DE5555;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fade-fold-widgets .ace_fold-widget {\n" +
+ " -moz-transition: opacity 0.4s ease 0.05s;\n" +
+ " -webkit-transition: opacity 0.4s ease 0.05s;\n" +
+ " -o-transition: opacity 0.4s ease 0.05s;\n" +
+ " -ms-transition: opacity 0.4s ease 0.05s;\n" +
+ " transition: opacity 0.4s ease 0.05s;\n" +
+ " opacity: 0;\n" +
+ "}\n" +
+ "\n" +
+ ".ace_fade-fold-widgets:hover .ace_fold-widget {\n" +
+ " -moz-transition: opacity 0.05s ease 0.05s;\n" +
+ " -webkit-transition: opacity 0.05s ease 0.05s;\n" +
+ " -o-transition: opacity 0.05s ease 0.05s;\n" +
+ " -ms-transition: opacity 0.05s ease 0.05s;\n" +
+ " transition: opacity 0.05s ease 0.05s;\n" +
+ " opacity:1;\n" +
+ "}\n" +
+ "");
+
+define('ace/multi_select', ['require', 'exports', 'module' , 'ace/range_list', 'ace/range', 'ace/selection', 'ace/mouse/multi_select_handler', 'ace/lib/event', 'ace/commands/multi_select_commands', 'ace/search', 'ace/edit_session', 'ace/editor'], function(require, exports, module) {
+
+var RangeList = require("./range_list").RangeList;
+var Range = require("./range").Range;
+var Selection = require("./selection").Selection;
+var onMouseDown = require("./mouse/multi_select_handler").onMouseDown;
+var event = require("./lib/event");
+var commands = require("./commands/multi_select_commands");
+exports.commands = commands.defaultCommands.concat(commands.multiSelectCommands);
+
+// Todo: session.find or editor.findVolatile that returns range
+var Search = require("./search").Search;
+var search = new Search();
+
+function find(session, needle, dir) {
+ search.$options.wrap = true;
+ search.$options.needle = needle;
+ search.$options.backwards = dir == -1;
+ return search.find(session);
+}
+
+// extend EditSession
+var EditSession = require("./edit_session").EditSession;
+(function() {
+ this.getSelectionMarkers = function() {
+ return this.$selectionMarkers;
+ };
+}).call(EditSession.prototype);
+
+// extend Selection
+(function() {
+ // list of ranges in reverse addition order
+ this.ranges = null;
+
+ // automatically sorted list of ranges
+ this.rangeList = null;
+ this.addRange = function(range, $blockChangeEvents) {
+ if (!range)
+ return;
+
+ if (!this.inMultiSelectMode && this.rangeCount == 0) {
+ var oldRange = this.toOrientedRange();
+ if (range.intersects(oldRange))
+ return $blockChangeEvents || this.fromOrientedRange(range);
+
+ this.rangeList.add(oldRange);
+ this.$onAddRange(oldRange);
+ }
+
+ if (!range.cursor)
+ range.cursor = range.end;
+
+ var removed = this.rangeList.add(range);
+
+ this.$onAddRange(range);
+
+ if (removed.length)
+ this.$onRemoveRange(removed);
+
+ if (this.rangeCount > 1 && !this.inMultiSelectMode) {
+ this._emit("multiSelect");
+ this.inMultiSelectMode = true;
+ this.session.$undoSelect = false;
+ this.rangeList.attach(this.session);
+ }
+
+ return $blockChangeEvents || this.fromOrientedRange(range);
+ };
+
+ this.toSingleRange = function(range) {
+ range = range || this.ranges[0];
+ var removed = this.rangeList.removeAll();
+ if (removed.length)
+ this.$onRemoveRange(removed);
+
+ range && this.fromOrientedRange(range);
+ };
+ this.substractPoint = function(pos) {
+ var removed = this.rangeList.substractPoint(pos);
+ if (removed) {
+ this.$onRemoveRange(removed);
+ return removed[0];
+ }
+ };
+ this.mergeOverlappingRanges = function() {
+ var removed = this.rangeList.merge();
+ if (removed.length)
+ this.$onRemoveRange(removed);
+ else if(this.ranges[0])
+ this.fromOrientedRange(this.ranges[0]);
+ };
+
+ this.$onAddRange = function(range) {
+ this.rangeCount = this.rangeList.ranges.length;
+ this.ranges.unshift(range);
+ this._emit("addRange", {range: range});
+ };
+
+ this.$onRemoveRange = function(removed) {
+ this.rangeCount = this.rangeList.ranges.length;
+ if (this.rangeCount == 1 && this.inMultiSelectMode) {
+ var lastRange = this.rangeList.ranges.pop();
+ removed.push(lastRange);
+ this.rangeCount = 0;
+ }
+
+ for (var i = removed.length; i--; ) {
+ var index = this.ranges.indexOf(removed[i]);
+ this.ranges.splice(index, 1);
+ }
+
+ this._emit("removeRange", {ranges: removed});
+
+ if (this.rangeCount == 0 && this.inMultiSelectMode) {
+ this.inMultiSelectMode = false;
+ this._emit("singleSelect");
+ this.session.$undoSelect = true;
+ this.rangeList.detach(this.session);
+ }
+
+ lastRange = lastRange || this.ranges[0];
+ if (lastRange && !lastRange.isEqual(this.getRange()))
+ this.fromOrientedRange(lastRange);
+ };
+
+ // adds multicursor support to selection
+ this.$initRangeList = function() {
+ if (this.rangeList)
+ return;
+
+ this.rangeList = new RangeList();
+ this.ranges = [];
+ this.rangeCount = 0;
+ };
+
+ this.getAllRanges = function() {
+ return this.rangeList.ranges.concat();
+ };
+
+ this.splitIntoLines = function () {
+ if (this.rangeCount > 1) {
+ var ranges = this.rangeList.ranges;
+ var lastRange = ranges[ranges.length - 1];
+ var range = Range.fromPoints(ranges[0].start, lastRange.end);
+
+ this.toSingleRange();
+ this.setSelectionRange(range, lastRange.cursor == lastRange.start);
+ } else {
+ var range = this.getRange();
+ var startRow = range.start.row;
+ var endRow = range.end.row;
+ if (startRow == endRow)
+ return;
+
+ var rectSel = [];
+ var r = this.getLineRange(startRow, true);
+ r.start.column = range.start.column;
+ rectSel.push(r);
+
+ for (var i = startRow + 1; i < endRow; i++)
+ rectSel.push(this.getLineRange(i, true));
+
+ r = this.getLineRange(endRow, true);
+ r.end.column = range.end.column;
+ rectSel.push(r);
+
+ rectSel.forEach(this.addRange, this);
+ }
+ };
+
+ this.toggleBlockSelection = function () {
+ if (this.rangeCount > 1) {
+ var ranges = this.rangeList.ranges;
+ var lastRange = ranges[ranges.length - 1];
+ var range = Range.fromPoints(ranges[0].start, lastRange.end);
+
+ this.toSingleRange();
+ this.setSelectionRange(range, lastRange.cursor == lastRange.start);
+ } else {
+ var cursor = this.session.documentToScreenPosition(this.selectionLead);
+ var anchor = this.session.documentToScreenPosition(this.selectionAnchor);
+
+ var rectSel = this.rectangularRangeBlock(cursor, anchor);
+ rectSel.forEach(this.addRange, this);
+ }
+ };
+ this.rectangularRangeBlock = function(screenCursor, screenAnchor, includeEmptyLines) {
+ var rectSel = [];
+
+ var xBackwards = screenCursor.column < screenAnchor.column;
+ if (xBackwards) {
+ var startColumn = screenCursor.column;
+ var endColumn = screenAnchor.column;
+ } else {
+ var startColumn = screenAnchor.column;
+ var endColumn = screenCursor.column;
+ }
+
+ var yBackwards = screenCursor.row < screenAnchor.row;
+ if (yBackwards) {
+ var startRow = screenCursor.row;
+ var endRow = screenAnchor.row;
+ } else {
+ var startRow = screenAnchor.row;
+ var endRow = screenCursor.row;
+ }
+
+ if (startColumn < 0)
+ startColumn = 0;
+ if (startRow < 0)
+ startRow = 0;
+
+ if (startRow == endRow)
+ includeEmptyLines = true;
+
+ for (var row = startRow; row <= endRow; row++) {
+ var range = Range.fromPoints(
+ this.session.screenToDocumentPosition(row, startColumn),
+ this.session.screenToDocumentPosition(row, endColumn)
+ );
+ if (range.isEmpty()) {
+ if (docEnd && isSamePoint(range.end, docEnd))
+ break;
+ var docEnd = range.end;
+ }
+ range.cursor = xBackwards ? range.start : range.end;
+ rectSel.push(range);
+ }
+
+ if (yBackwards)
+ rectSel.reverse();
+
+ if (!includeEmptyLines) {
+ var end = rectSel.length - 1;
+ while (rectSel[end].isEmpty() && end > 0)
+ end--;
+ if (end > 0) {
+ var start = 0;
+ while (rectSel[start].isEmpty())
+ start++;
+ }
+ for (var i = end; i >= start; i--) {
+ if (rectSel[i].isEmpty())
+ rectSel.splice(i, 1);
+ }
+ }
+
+ return rectSel;
+ };
+}).call(Selection.prototype);
+
+// extend Editor
+var Editor = require("./editor").Editor;
+(function() {
+
+ /** extension
+ * Editor.updateSelectionMarkers()
+ *
+ * Updates the cursor and marker layers.
+ **/
+ this.updateSelectionMarkers = function() {
+ this.renderer.updateCursor();
+ this.renderer.updateBackMarkers();
+ };
+ this.addSelectionMarker = function(orientedRange) {
+ if (!orientedRange.cursor)
+ orientedRange.cursor = orientedRange.end;
+
+ var style = this.getSelectionStyle();
+ orientedRange.marker = this.session.addMarker(orientedRange, "ace_selection", style);
+
+ this.session.$selectionMarkers.push(orientedRange);
+ this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
+ return orientedRange;
+ };
+ this.removeSelectionMarker = function(range) {
+ if (!range.marker)
+ return;
+ this.session.removeMarker(range.marker);
+ var index = this.session.$selectionMarkers.indexOf(range);
+ if (index != -1)
+ this.session.$selectionMarkers.splice(index, 1);
+ this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
+ };
+
+ this.removeSelectionMarkers = function(ranges) {
+ var markerList = this.session.$selectionMarkers;
+ for (var i = ranges.length; i--; ) {
+ var range = ranges[i];
+ if (!range.marker)
+ continue;
+ this.session.removeMarker(range.marker);
+ var index = markerList.indexOf(range);
+ if (index != -1)
+ markerList.splice(index, 1);
+ }
+ this.session.selectionMarkerCount = markerList.length;
+ };
+
+ this.$onAddRange = function(e) {
+ this.addSelectionMarker(e.range);
+ this.renderer.updateCursor();
+ this.renderer.updateBackMarkers();
+ };
+
+ this.$onRemoveRange = function(e) {
+ this.removeSelectionMarkers(e.ranges);
+ this.renderer.updateCursor();
+ this.renderer.updateBackMarkers();
+ };
+
+ this.$onMultiSelect = function(e) {
+ if (this.inMultiSelectMode)
+ return;
+ this.inMultiSelectMode = true;
+
+ this.setStyle("multiselect");
+ this.keyBinding.addKeyboardHandler(commands.keyboardHandler);
+ this.commands.on("exec", this.$onMultiSelectExec);
+
+ this.renderer.updateCursor();
+ this.renderer.updateBackMarkers();
+ };
+
+ this.$onSingleSelect = function(e) {
+ if (this.session.multiSelect.inVirtualMode)
+ return;
+ this.inMultiSelectMode = false;
+
+ this.unsetStyle("multiselect");
+ this.keyBinding.removeKeyboardHandler(commands.keyboardHandler);
+
+ this.commands.removeEventListener("exec", this.$onMultiSelectExec);
+ this.renderer.updateCursor();
+ this.renderer.updateBackMarkers();
+ };
+
+ this.$onMultiSelectExec = function(e) {
+ var command = e.command;
+ var editor = e.editor;
+ if (!editor.multiSelect)
+ return;
+ if (!command.multiSelectAction) {
+ command.exec(editor, e.args || {});
+ editor.multiSelect.addRange(editor.multiSelect.toOrientedRange());
+ editor.multiSelect.mergeOverlappingRanges();
+ } else if (command.multiSelectAction == "forEach") {
+ editor.forEachSelection(command, e.args);
+ } else if (command.multiSelectAction == "single") {
+ editor.exitMultiSelectMode();
+ command.exec(editor, e.args || {});
+ } else {
+ command.multiSelectAction(editor, e.args || {});
+ }
+ e.preventDefault();
+ };
+ this.forEachSelection = function(cmd, args) {
+ if (this.inVirtualSelectionMode)
+ return;
+
+ var session = this.session;
+ var selection = this.selection;
+ var rangeList = selection.rangeList;
+
+ var reg = selection._eventRegistry;
+ selection._eventRegistry = {};
+
+ var tmpSel = new Selection(session);
+ this.inVirtualSelectionMode = true;
+ for (var i = rangeList.ranges.length; i--;) {
+ tmpSel.fromOrientedRange(rangeList.ranges[i]);
+ this.selection = session.selection = tmpSel;
+ cmd.exec(this, args || {});
+ tmpSel.toOrientedRange(rangeList.ranges[i]);
+ }
+ tmpSel.detach();
+
+ this.selection = session.selection = selection;
+ this.inVirtualSelectionMode = false;
+ selection._eventRegistry = reg;
+ selection.mergeOverlappingRanges();
+
+ this.onCursorChange();
+ this.onSelectionChange();
+ };
+ this.exitMultiSelectMode = function() {
+ if (this.inVirtualSelectionMode)
+ return;
+ this.multiSelect.toSingleRange();
+ };
+
+ this.getCopyText = function() {
+ var text = "";
+ if (this.inMultiSelectMode) {
+ var ranges = this.multiSelect.rangeList.ranges;
+ text = [];
+ for (var i = 0; i < ranges.length; i++) {
+ text.push(this.session.getTextRange(ranges[i]));
+ }
+ text = text.join(this.session.getDocument().getNewLineCharacter());
+ } else if (!this.selection.isEmpty()) {
+ text = this.session.getTextRange(this.getSelectionRange());
+ }
+
+ return text;
+ };
+
+ // todo this should change when paste becomes a command
+ this.onPaste = function(text) {
+ if (this.$readOnly)
+ return;
+
+ this._emit("paste", text);
+ if (!this.inMultiSelectMode)
+ return this.insert(text);
+
+ var lines = text.split(/\r\n|\r|\n/);
+ var ranges = this.selection.rangeList.ranges;
+
+ if (lines.length > ranges.length || (lines.length <= 2 || !lines[1]))
+ return this.commands.exec("insertstring", this, text);
+
+ for (var i = ranges.length; i--; ) {
+ var range = ranges[i];
+ if (!range.isEmpty())
+ this.session.remove(range);
+
+ this.session.insert(range.start, lines[i]);
+ }
+ };
+ this.findAll = function(needle, options, additive) {
+ options = options || {};
+ options.needle = needle || options.needle;
+ this.$search.set(options);
+
+ var ranges = this.$search.findAll(this.session);
+ if (!ranges.length)
+ return 0;
+
+ this.$blockScrolling += 1;
+ var selection = this.multiSelect;
+
+ if (!additive)
+ selection.toSingleRange(ranges[0]);
+
+ for (var i = ranges.length; i--; )
+ selection.addRange(ranges[i], true);
+
+ this.$blockScrolling -= 1;
+
+ return ranges.length;
+ };
+
+ // commands
+ /** extension
+ * Editor.selectMoreLines(dir, skip)
+ * - dir (Number): The direction of lines to select: -1 for up, 1 for down
+ * - skip (Boolean): If `true`, removes the active selection range
+ *
+ * Adds a cursor above or below the active cursor.
+ **/
+ this.selectMoreLines = function(dir, skip) {
+ var range = this.selection.toOrientedRange();
+ var isBackwards = range.cursor == range.end;
+
+ var screenLead = this.session.documentToScreenPosition(range.cursor);
+ if (this.selection.$desiredColumn)
+ screenLead.column = this.selection.$desiredColumn;
+
+ var lead = this.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column);
+
+ if (!range.isEmpty()) {
+ var screenAnchor = this.session.documentToScreenPosition(isBackwards ? range.end : range.start);
+ var anchor = this.session.screenToDocumentPosition(screenAnchor.row + dir, screenAnchor.column);
+ } else {
+ var anchor = lead;
+ }
+
+ if (isBackwards) {
+ var newRange = Range.fromPoints(lead, anchor);
+ newRange.cursor = newRange.start;
+ } else {
+ var newRange = Range.fromPoints(anchor, lead);
+ newRange.cursor = newRange.end;
+ }
+
+ newRange.desiredColumn = screenLead.column;
+ if (!this.selection.inMultiSelectMode) {
+ this.selection.addRange(range);
+ } else {
+ if (skip)
+ var toRemove = range.cursor;
+ }
+
+ this.selection.addRange(newRange);
+ if (toRemove)
+ this.selection.substractPoint(toRemove);
+ };
+ this.transposeSelections = function(dir) {
+ var session = this.session;
+ var sel = session.multiSelect;
+ var all = sel.ranges;
+
+ for (var i = all.length; i--; ) {
+ var range = all[i];
+ if (range.isEmpty()) {
+ var tmp = session.getWordRange(range.start.row, range.start.column);
+ range.start.row = tmp.start.row;
+ range.start.column = tmp.start.column;
+ range.end.row = tmp.end.row;
+ range.end.column = tmp.end.column;
+ }
+ }
+ sel.mergeOverlappingRanges();
+
+ var words = [];
+ for (var i = all.length; i--; ) {
+ var range = all[i];
+ words.unshift(session.getTextRange(range));
+ }
+
+ if (dir < 0)
+ words.unshift(words.pop());
+ else
+ words.push(words.shift());
+
+ for (var i = all.length; i--; ) {
+ var range = all[i];
+ var tmp = range.clone();
+ session.replace(range, words[i]);
+ range.start.row = tmp.start.row;
+ range.start.column = tmp.start.column;
+ }
+ }
+
+ /** extension
+ * Editor.selectMore(dir, skip)
+ * - dir (Number): The direction of lines to select: -1 for up, 1 for down
+ * - skip (Boolean): If `true`, removes the active selection range
+ *
+ * Finds the next occurence of text in an active selection and adds it to the selections.
+ **/
+ this.selectMore = function (dir, skip) {
+ var session = this.session;
+ var sel = session.multiSelect;
+
+ var range = sel.toOrientedRange();
+ if (range.isEmpty()) {
+ var range = session.getWordRange(range.start.row, range.start.column);
+ range.cursor = range.end;
+ this.multiSelect.addRange(range);
+ }
+ var needle = session.getTextRange(range);
+
+ var newRange = find(session, needle, dir);
+ if (newRange) {
+ newRange.cursor = dir == -1 ? newRange.start : newRange.end;
+ this.multiSelect.addRange(newRange);
+ }
+ if (skip)
+ this.multiSelect.substractPoint(range.cursor);
+ };
+}).call(Editor.prototype);
+
+
+function isSamePoint(p1, p2) {
+ return p1.row == p2.row && p1.column == p2.column;
+}
+
+// patch
+// adds multicursor support to a session
+exports.onSessionChange = function(e) {
+ var session = e.session;
+ if (!session.multiSelect) {
+ session.$selectionMarkers = [];
+ session.selection.$initRangeList();
+ session.multiSelect = session.selection;
+ }
+ this.multiSelect = session.multiSelect;
+
+ var oldSession = e.oldSession;
+ if (oldSession) {
+ // todo use events
+ if (oldSession.multiSelect && oldSession.multiSelect.editor == this)
+ oldSession.multiSelect.editor = null;
+
+ session.multiSelect.removeEventListener("addRange", this.$onAddRange);
+ session.multiSelect.removeEventListener("removeRange", this.$onRemoveRange);
+ session.multiSelect.removeEventListener("multiSelect", this.$onMultiSelect);
+ session.multiSelect.removeEventListener("singleSelect", this.$onSingleSelect);
+ }
+
+ session.multiSelect.on("addRange", this.$onAddRange);
+ session.multiSelect.on("removeRange", this.$onRemoveRange);
+ session.multiSelect.on("multiSelect", this.$onMultiSelect);
+ session.multiSelect.on("singleSelect", this.$onSingleSelect);
+
+ // this.$onSelectionChange = this.onSelectionChange.bind(this);
+
+ if (this.inMultiSelectMode != session.selection.inMultiSelectMode) {
+ if (session.selection.inMultiSelectMode)
+ this.$onMultiSelect();
+ else
+ this.$onSingleSelect();
+ }
+};
+
+// MultiSelect(editor)
+// adds multiple selection support to the editor
+// (note: should be called only once for each editor instance)
+function MultiSelect(editor) {
+ editor.$onAddRange = editor.$onAddRange.bind(editor);
+ editor.$onRemoveRange = editor.$onRemoveRange.bind(editor);
+ editor.$onMultiSelect = editor.$onMultiSelect.bind(editor);
+ editor.$onSingleSelect = editor.$onSingleSelect.bind(editor);
+
+ exports.onSessionChange.call(editor, editor);
+ editor.on("changeSession", exports.onSessionChange.bind(editor));
+
+ editor.on("mousedown", onMouseDown);
+ editor.commands.addCommands(commands.defaultCommands);
+
+ addAltCursorListeners(editor);
+}
+
+function addAltCursorListeners(editor){
+ var el = editor.textInput.getElement();
+ var altCursor = false;
+ var contentEl = editor.renderer.content;
+ event.addListener(el, "keydown", function(e) {
+ if (e.keyCode == 18 && !(e.ctrlKey || e.shiftKey || e.metaKey)) {
+ if (!altCursor) {
+ contentEl.style.cursor = "crosshair";
+ altCursor = true;
+ }
+ } else if (altCursor) {
+ contentEl.style.cursor = "";
+ }
+ });
+
+ event.addListener(el, "keyup", reset);
+ event.addListener(el, "blur", reset);
+ function reset() {
+ if (altCursor) {
+ contentEl.style.cursor = "";
+ altCursor = false;
+ }
+ }
+}
+
+exports.MultiSelect = MultiSelect;
+
+});
+
+define('ace/range_list', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+
+var RangeList = function() {
+ this.ranges = [];
+};
+
+(function() {
+ this.comparePoints = function(p1, p2) {
+ return p1.row - p2.row || p1.column - p2.column;
+ };
+
+ this.pointIndex = function(pos, startIndex) {
+ var list = this.ranges;
+
+ for (var i = startIndex || 0; i < list.length; i++) {
+ var range = list[i];
+ var cmp = this.comparePoints(pos, range.end);
+
+ if (cmp > 0)
+ continue;
+ if (cmp == 0)
+ return i;
+ cmp = this.comparePoints(pos, range.start);
+ if (cmp >= 0)
+ return i;
+
+ return -i-1;
+ }
+ return -i - 1;
+ };
+
+ this.add = function(range) {
+ var startIndex = this.pointIndex(range.start);
+ if (startIndex < 0)
+ startIndex = -startIndex - 1;
+
+ var endIndex = this.pointIndex(range.end, startIndex);
+
+ if (endIndex < 0)
+ endIndex = -endIndex - 1;
+ else
+ endIndex++;
+
+ return this.ranges.splice(startIndex, endIndex - startIndex, range);
+ };
+
+ this.addList = function(list) {
+ var removed = [];
+ for (var i = list.length; i--; ) {
+ removed.push.call(removed, this.add(list[i]));
+ }
+ return removed;
+ };
+
+ this.substractPoint = function(pos) {
+ var i = this.pointIndex(pos);
+
+ if (i >= 0)
+ return this.ranges.splice(i, 1);
+ };
+
+ // merge overlapping ranges
+ this.merge = function() {
+ var removed = [];
+ var list = this.ranges;
+ var next = list[0], range;
+ for (var i = 1; i < list.length; i++) {
+ range = next;
+ next = list[i];
+ var cmp = this.comparePoints(range.end, next.start);
+ if (cmp < 0)
+ continue;
+
+ if (cmp == 0 && !(range.isEmpty() || next.isEmpty()))
+ continue;
+
+ if (this.comparePoints(range.end, next.end) < 0) {
+ range.end.row = next.end.row;
+ range.end.column = next.end.column;
+ }
+
+ list.splice(i, 1);
+ removed.push(next);
+ next = range;
+ i--;
+ }
+
+ return removed;
+ };
+
+ this.contains = function(row, column) {
+ return this.pointIndex({row: row, column: column}) >= 0;
+ };
+
+ this.containsPoint = function(pos) {
+ return this.pointIndex(pos) >= 0;
+ };
+
+ this.rangeAtPoint = function(pos) {
+ var i = this.pointIndex(pos);
+ if (i >= 0)
+ return this.ranges[i];
+ };
+
+
+ this.clipRows = function(startRow, endRow) {
+ var list = this.ranges;
+ if (list[0].start.row > endRow || list[list.length - 1].start.row < startRow)
+ return [];
+
+ var startIndex = this.pointIndex({row: startRow, column: 0});
+ if (startIndex < 0)
+ startIndex = -startIndex - 1;
+ var endIndex = this.pointIndex({row: endRow, column: 0}, startIndex);
+ if (endIndex < 0)
+ endIndex = -endIndex - 1;
+
+ var clipped = [];
+ for (var i = startIndex; i < endIndex; i++) {
+ clipped.push(list[i]);
+ }
+ return clipped;
+ };
+
+ this.removeAll = function() {
+ return this.ranges.splice(0, this.ranges.length);
+ };
+
+ this.attach = function(session) {
+ if (this.session)
+ this.detach();
+
+ this.session = session;
+ this.onChange = this.$onChange.bind(this);
+
+ this.session.on('change', this.onChange);
+ };
+
+ this.detach = function() {
+ if (!this.session)
+ return;
+ this.session.removeListener('change', this.onChange);
+ this.session = null;
+ };
+
+ this.$onChange = function(e) {
+ var changeRange = e.data.range;
+ if (e.data.action[0] == "i"){
+ var start = changeRange.start;
+ var end = changeRange.end;
+ } else {
+ var end = changeRange.start;
+ var start = changeRange.end;
+ }
+ var startRow = start.row;
+ var endRow = end.row;
+ var lineDif = endRow - startRow;
+
+ var colDiff = -start.column + end.column;
+ var ranges = this.ranges;
+
+ for (var i = 0, n = ranges.length; i < n; i++) {
+ var r = ranges[i];
+ if (r.end.row < startRow)
+ continue;
+ if (r.start.row > startRow)
+ break;
+
+ if (r.start.row == startRow && r.start.column >= start.column ) {
+ r.start.column += colDiff;
+ r.start.row += lineDif;
+ }
+ if (r.end.row == startRow && r.end.column >= start.column) {
+ r.end.column += colDiff;
+ r.end.row += lineDif;
+ }
+ }
+
+ if (lineDif != 0 && i < n) {
+ for (; i < n; i++) {
+ var r = ranges[i];
+ r.start.row += lineDif;
+ r.end.row += lineDif;
+ }
+ }
+ };
+
+}).call(RangeList.prototype);
+
+exports.RangeList = RangeList;
+});
+
+define('ace/mouse/multi_select_handler', ['require', 'exports', 'module' , 'ace/lib/event'], function(require, exports, module) {
+
+var event = require("../lib/event");
+
+
+// mouse
+function isSamePoint(p1, p2) {
+ return p1.row == p2.row && p1.column == p2.column;
+}
+
+function onMouseDown(e) {
+ var ev = e.domEvent;
+ var alt = ev.altKey;
+ var shift = ev.shiftKey;
+ var ctrl = e.getAccelKey();
+ var button = e.getButton();
+
+ if (e.editor.inMultiSelectMode && button == 2) {
+ e.editor.textInput.onContextMenu(e.domEvent);
+ return;
+ }
+
+ if (!ctrl && !alt) {
+ if (button == 0 && e.editor.inMultiSelectMode)
+ e.editor.exitMultiSelectMode();
+ return;
+ }
+
+ var editor = e.editor;
+ var selection = editor.selection;
+ var isMultiSelect = editor.inMultiSelectMode;
+ var pos = e.getDocumentPosition();
+ var cursor = selection.getCursor();
+ var inSelection = e.inSelection() || (selection.isEmpty() && isSamePoint(pos, cursor));
+
+
+ var mouseX = e.x, mouseY = e.y;
+ var onMouseSelection = function(e) {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ };
+
+ var blockSelect = function() {
+ var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY);
+ var cursor = session.screenToDocumentPosition(newCursor.row, newCursor.column);
+
+ if (isSamePoint(screenCursor, newCursor)
+ && isSamePoint(cursor, selection.selectionLead))
+ return;
+ screenCursor = newCursor;
+
+ editor.selection.moveCursorToPosition(cursor);
+ editor.selection.clearSelection();
+ editor.renderer.scrollCursorIntoView();
+
+ editor.removeSelectionMarkers(rectSel);
+ rectSel = selection.rectangularRangeBlock(screenCursor, screenAnchor);
+ rectSel.forEach(editor.addSelectionMarker, editor);
+ editor.updateSelectionMarkers();
+ };
+
+ var session = editor.session;
+ var screenAnchor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY);
+ var screenCursor = screenAnchor;
+
+
+
+ if (ctrl && !shift && !alt && button == 0) {
+ if (!isMultiSelect && inSelection)
+ return; // dragging
+
+ if (!isMultiSelect) {
+ var range = selection.toOrientedRange();
+ editor.addSelectionMarker(range);
+ }
+
+ var oldRange = selection.rangeList.rangeAtPoint(pos);
+
+ event.capture(editor.container, function(){}, function() {
+ var tmpSel = selection.toOrientedRange();
+
+ if (oldRange && tmpSel.isEmpty() && isSamePoint(oldRange.cursor, tmpSel.cursor))
+ selection.substractPoint(tmpSel.cursor);
+ else {
+ if (range) {
+ editor.removeSelectionMarker(range);
+ selection.addRange(range);
+ }
+ selection.addRange(tmpSel);
+ }
+ });
+
+ } else if (!shift && alt && button == 0) {
+ e.stop();
+
+ if (isMultiSelect && !ctrl)
+ selection.toSingleRange();
+ else if (!isMultiSelect && ctrl)
+ selection.addRange();
+
+ selection.moveCursorToPosition(pos);
+ selection.clearSelection();
+
+ var rectSel = [];
+
+ var onMouseSelectionEnd = function(e) {
+ clearInterval(timerId);
+ editor.removeSelectionMarkers(rectSel);
+ for (var i = 0; i < rectSel.length; i++)
+ selection.addRange(rectSel[i]);
+ };
+
+ var onSelectionInterval = blockSelect;
+
+ event.capture(editor.container, onMouseSelection, onMouseSelectionEnd);
+ var timerId = setInterval(function() {onSelectionInterval();}, 20);
+
+ return e.preventDefault();
+ }
+}
+
+
+exports.onMouseDown = onMouseDown;
+
+});
+
+define('ace/commands/multi_select_commands', ['require', 'exports', 'module' , 'ace/keyboard/hash_handler'], function(require, exports, module) {
+
+// commands to enter multiselect mode
+exports.defaultCommands = [{
+ name: "addCursorAbove",
+ exec: function(editor) { editor.selectMoreLines(-1); },
+ bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"},
+ readonly: true
+}, {
+ name: "addCursorBelow",
+ exec: function(editor) { editor.selectMoreLines(1); },
+ bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"},
+ readonly: true
+}, {
+ name: "addCursorAboveSkipCurrent",
+ exec: function(editor) { editor.selectMoreLines(-1, true); },
+ bindKey: {win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up"},
+ readonly: true
+}, {
+ name: "addCursorBelowSkipCurrent",
+ exec: function(editor) { editor.selectMoreLines(1, true); },
+ bindKey: {win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down"},
+ readonly: true
+}, {
+ name: "selectMoreBefore",
+ exec: function(editor) { editor.selectMore(-1); },
+ bindKey: {win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left"},
+ readonly: true
+}, {
+ name: "selectMoreAfter",
+ exec: function(editor) { editor.selectMore(1); },
+ bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"},
+ readonly: true
+}, {
+ name: "selectNextBefore",
+ exec: function(editor) { editor.selectMore(-1, true); },
+ bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"},
+ readonly: true
+}, {
+ name: "selectNextAfter",
+ exec: function(editor) { editor.selectMore(1, true); },
+ bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"},
+ readonly: true
+}, {
+ name: "splitIntoLines",
+ exec: function(editor) { editor.multiSelect.splitIntoLines(); },
+ bindKey: {win: "Ctrl-Shift-L", mac: "Ctrl-Shift-L"},
+ readonly: true
+}];
+
+// commands active in multiselect mode
+exports.multiSelectCommands = [{
+ name: "singleSelection",
+ bindKey: "esc",
+ exec: function(editor) { editor.exitMultiSelectMode(); },
+ readonly: true,
+ isAvailable: function(editor) {return editor && editor.inMultiSelectMode}
+}];
+
+var HashHandler = require("../keyboard/hash_handler").HashHandler;
+exports.keyboardHandler = new HashHandler(exports.multiSelectCommands);
+
+});
+
+define('ace/keyboard/state_handler', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+// If you're developing a new keymapping and want to get an idea what's going
+// on, then enable debugging.
+var DEBUG = false;
+
+function StateHandler(keymapping) {
+ this.keymapping = this.$buildKeymappingRegex(keymapping);
+}
+
+StateHandler.prototype = {
+ /*
+ * Build the RegExp from the keymapping as RegExp can't stored directly
+ * in the metadata JSON and as the RegExp used to match the keys/buffer
+ * need to be adapted.
+ */
+ $buildKeymappingRegex: function(keymapping) {
+ for (var state in keymapping) {
+ this.$buildBindingsRegex(keymapping[state]);
+ }
+ return keymapping;
+ },
+
+ $buildBindingsRegex: function(bindings) {
+ // Escape a given Regex string.
+ bindings.forEach(function(binding) {
+ if (binding.key) {
+ binding.key = new RegExp('^' + binding.key + '$');
+ } else if (Array.isArray(binding.regex)) {
+ if (!('key' in binding))
+ binding.key = new RegExp('^' + binding.regex[1] + '$');
+ binding.regex = new RegExp(binding.regex.join('') + '$');
+ } else if (binding.regex) {
+ binding.regex = new RegExp(binding.regex + '$');
+ }
+ });
+ },
+
+ $composeBuffer: function(data, hashId, key, e) {
+ // Initialize the data object.
+ if (data.state == null || data.buffer == null) {
+ data.state = "start";
+ data.buffer = "";
+ }
+
+ var keyArray = [];
+ if (hashId & 1) keyArray.push("ctrl");
+ if (hashId & 8) keyArray.push("command");
+ if (hashId & 2) keyArray.push("option");
+ if (hashId & 4) keyArray.push("shift");
+ if (key) keyArray.push(key);
+
+ var symbolicName = keyArray.join("-");
+ var bufferToUse = data.buffer + symbolicName;
+
+ // Don't add the symbolic name to the key buffer if the alt_ key is
+ // part of the symbolic name. If it starts with alt_, this means
+ // that the user hit an alt keycombo and there will be a single,
+ // new character detected after this event, which then will be
+ // added to the buffer (e.g. alt_j will result in ∆).
+ //
+ // We test for 2 and not for & 2 as we only want to exclude the case where
+ // the option key is pressed alone.
+ if (hashId != 2) {
+ data.buffer = bufferToUse;
+ }
+
+ var bufferObj = {
+ bufferToUse: bufferToUse,
+ symbolicName: symbolicName
+ };
+
+ if (e) {
+ bufferObj.keyIdentifier = e.keyIdentifier;
+ }
+
+ return bufferObj;
+ },
+
+ $find: function(data, buffer, symbolicName, hashId, key, keyIdentifier) {
+ // Holds the command to execute and the args if a command matched.
+ var result = {};
+
+ // Loop over all the bindings of the keymap until a match is found.
+ this.keymapping[data.state].some(function(binding) {
+ var match;
+
+ // Check if the key matches.
+ if (binding.key && !binding.key.test(symbolicName)) {
+ return false;
+ }
+
+ // Check if the regex matches.
+ if (binding.regex && !(match = binding.regex.exec(buffer))) {
+ return false;
+ }
+
+ // Check if the match function matches.
+ if (binding.match && !binding.match(buffer, hashId, key, symbolicName, keyIdentifier)) {
+ return false;
+ }
+
+ // Check for disallowed matches.
+ if (binding.disallowMatches) {
+ for (var i = 0; i < binding.disallowMatches.length; i++) {
+ if (!!match[binding.disallowMatches[i]]) {
+ return false;
+ }
+ }
+ }
+
+ // If there is a command to execute, then figure out the
+ // command and the arguments.
+ if (binding.exec) {
+ result.command = binding.exec;
+
+ // Build the arguments.
+ if (binding.params) {
+ var value;
+ result.args = {};
+ binding.params.forEach(function(param) {
+ if (param.match != null && match != null) {
+ value = match[param.match] || param.defaultValue;
+ } else {
+ value = param.defaultValue;
+ }
+
+ if (param.type === 'number') {
+ value = parseInt(value);
+ }
+
+ result.args[param.name] = value;
+ });
+ }
+ data.buffer = "";
+ }
+
+ // Handle the 'then' property.
+ if (binding.then) {
+ data.state = binding.then;
+ data.buffer = "";
+ }
+
+ // If no command is set, then execute the "null" fake command.
+ if (result.command == null) {
+ result.command = "null";
+ }
+
+ if (DEBUG) {
+ console.log("KeyboardStateMapper#find", binding);
+ }
+ return true;
+ });
+
+ if (result.command) {
+ return result;
+ } else {
+ data.buffer = "";
+ return false;
+ }
+ },
+
+ /*
+ * This function is called by keyBinding.
+ */
+ handleKeyboard: function(data, hashId, key, keyCode, e) {
+ if (hashId == -1)
+ hashId = 0
+ // If we pressed any command key but no other key, then ignore the input.
+ // Otherwise "shift-" is added to the buffer, and later on "shift-g"
+ // which results in "shift-shift-g" which doesn't make sense.
+ if (hashId != 0 && (key == "" || key == String.fromCharCode(0))) {
+ return null;
+ }
+
+ // Compute the current value of the keyboard input buffer.
+ var r = this.$composeBuffer(data, hashId, key, e);
+ var buffer = r.bufferToUse;
+ var symbolicName = r.symbolicName;
+ var keyId = r.keyIdentifier;
+
+ r = this.$find(data, buffer, symbolicName, hashId, key, keyId);
+ if (DEBUG) {
+ console.log("KeyboardStateMapper#match", buffer, symbolicName, r);
+ }
+
+ return r;
+ }
+}
+
+/*
+ * This is a useful matching function and therefore is defined here so that
+ * users of KeyboardStateMapper can use it.
+ *
+ * @return boolean
+ * If no command key (Command|Option|Shift|Ctrl) is pressed, it
+ * returns true. If the only the Shift key is pressed + a character
+ * true is returned as well. Otherwise, false is returned.
+ * Summing up, the function returns true whenever the user typed
+ * a normal character on the keyboard and no shortcut.
+ */
+exports.matchCharacterOnly = function(buffer, hashId, key, symbolicName) {
+ // If no command keys are pressed, then catch the input.
+ if (hashId == 0) {
+ return true;
+ }
+ // If only the shift key is pressed and a character key, then
+ // catch that input as well.
+ else if ((hashId == 4) && key.length == 1) {
+ return true;
+ }
+ // Otherwise, we let the input got through.
+ else {
+ return false;
+ }
+};
+
+exports.StateHandler = StateHandler;
+});
+define('ace/placeholder', ['require', 'exports', 'module' , 'ace/range', 'ace/lib/event_emitter', 'ace/lib/oop'], function(require, exports, module) {
+
+
+var Range = require('./range').Range;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var oop = require("./lib/oop");
+
+/**
+ * new PlaceHolder(session, length, pos, others, mainClass, othersClass)
+ * - session (Document): The document to associate with the anchor
+ * - length (Number): The starting row position
+ * - pos (Number): The starting column position
+ * - others (String):
+ * - mainClass (String):
+ * - othersClass (String):
+ *
+ * TODO
+ *
+ **/
+
+var PlaceHolder = function(session, length, pos, others, mainClass, othersClass) {
+ var _self = this;
+ this.length = length;
+ this.session = session;
+ this.doc = session.getDocument();
+ this.mainClass = mainClass;
+ this.othersClass = othersClass;
+ this.$onUpdate = this.onUpdate.bind(this);
+ this.doc.on("change", this.$onUpdate);
+ this.$others = others;
+
+ this.$onCursorChange = function() {
+ setTimeout(function() {
+ _self.onCursorChange();
+ });
+ };
+
+ this.$pos = pos;
+ // Used for reset
+ var undoStack = session.getUndoManager().$undoStack || session.getUndoManager().$undostack || {length: -1};
+ this.$undoStackDepth = undoStack.length;
+ this.setup();
+
+ session.selection.on("changeCursor", this.$onCursorChange);
+};
+
+(function() {
+
+ oop.implement(this, EventEmitter);
+ this.setup = function() {
+ var _self = this;
+ var doc = this.doc;
+ var session = this.session;
+ var pos = this.$pos;
+
+ this.pos = doc.createAnchor(pos.row, pos.column);
+ this.markerId = session.addMarker(new Range(pos.row, pos.column, pos.row, pos.column + this.length), this.mainClass, null, false);
+ this.pos.on("change", function(event) {
+ session.removeMarker(_self.markerId);
+ _self.markerId = session.addMarker(new Range(event.value.row, event.value.column, event.value.row, event.value.column+_self.length), _self.mainClass, null, false);
+ });
+ this.others = [];
+ this.$others.forEach(function(other) {
+ var anchor = doc.createAnchor(other.row, other.column);
+ _self.others.push(anchor);
+ });
+ session.setUndoSelect(false);
+ };
+ this.showOtherMarkers = function() {
+ if(this.othersActive) return;
+ var session = this.session;
+ var _self = this;
+ this.othersActive = true;
+ this.others.forEach(function(anchor) {
+ anchor.markerId = session.addMarker(new Range(anchor.row, anchor.column, anchor.row, anchor.column+_self.length), _self.othersClass, null, false);
+ anchor.on("change", function(event) {
+ session.removeMarker(anchor.markerId);
+ anchor.markerId = session.addMarker(new Range(event.value.row, event.value.column, event.value.row, event.value.column+_self.length), _self.othersClass, null, false);
+ });
+ });
+ };
+ this.hideOtherMarkers = function() {
+ if(!this.othersActive) return;
+ this.othersActive = false;
+ for (var i = 0; i < this.others.length; i++) {
+ this.session.removeMarker(this.others[i].markerId);
+ }
+ };
+ this.onUpdate = function(event) {
+ var delta = event.data;
+ var range = delta.range;
+ if(range.start.row !== range.end.row) return;
+ if(range.start.row !== this.pos.row) return;
+ if (this.$updating) return;
+ this.$updating = true;
+ var lengthDiff = delta.action === "insertText" ? range.end.column - range.start.column : range.start.column - range.end.column;
+
+ if(range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1) {
+ var distanceFromStart = range.start.column - this.pos.column;
+ this.length += lengthDiff;
+ if(!this.session.$fromUndo) {
+ if(delta.action === "insertText") {
+ for (var i = this.others.length - 1; i >= 0; i--) {
+ var otherPos = this.others[i];
+ var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
+ if(otherPos.row === range.start.row && range.start.column < otherPos.column)
+ newPos.column += lengthDiff;
+ this.doc.insert(newPos, delta.text);
+ }
+ } else if(delta.action === "removeText") {
+ for (var i = this.others.length - 1; i >= 0; i--) {
+ var otherPos = this.others[i];
+ var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
+ if(otherPos.row === range.start.row && range.start.column < otherPos.column)
+ newPos.column += lengthDiff;
+ this.doc.remove(new Range(newPos.row, newPos.column, newPos.row, newPos.column - lengthDiff));
+ }
+ }
+ // Special case: insert in beginning
+ if(range.start.column === this.pos.column && delta.action === "insertText") {
+ setTimeout(function() {
+ this.pos.setPosition(this.pos.row, this.pos.column - lengthDiff);
+ for (var i = 0; i < this.others.length; i++) {
+ var other = this.others[i];
+ var newPos = {row: other.row, column: other.column - lengthDiff};
+ if(other.row === range.start.row && range.start.column < other.column)
+ newPos.column += lengthDiff;
+ other.setPosition(newPos.row, newPos.column);
+ }
+ }.bind(this), 0);
+ }
+ else if(range.start.column === this.pos.column && delta.action === "removeText") {
+ setTimeout(function() {
+ for (var i = 0; i < this.others.length; i++) {
+ var other = this.others[i];
+ if(other.row === range.start.row && range.start.column < other.column) {
+ other.setPosition(other.row, other.column - lengthDiff);
+ }
+ }
+ }.bind(this), 0);
+ }
+ }
+ this.pos._emit("change", {value: this.pos});
+ for (var i = 0; i < this.others.length; i++) {
+ this.others[i]._emit("change", {value: this.others[i]});
+ }
+ }
+ this.$updating = false;
+ };
+
+ this.onCursorChange = function(event) {
+ if (this.$updating) return;
+ var pos = this.session.selection.getCursor();
+ if(pos.row === this.pos.row && pos.column >= this.pos.column && pos.column <= this.pos.column + this.length) {
+ this.showOtherMarkers();
+ this._emit("cursorEnter", event);
+ } else {
+ this.hideOtherMarkers();
+ this._emit("cursorLeave", event);
+ }
+ };
+ this.detach = function() {
+ this.session.removeMarker(this.markerId);
+ this.hideOtherMarkers();
+ this.doc.removeEventListener("change", this.$onUpdate);
+ this.session.selection.removeEventListener("changeCursor", this.$onCursorChange);
+ this.pos.detach();
+ for (var i = 0; i < this.others.length; i++) {
+ this.others[i].detach();
+ }
+ this.session.setUndoSelect(true);
+ };
+ this.cancel = function() {
+ if(this.$undoStackDepth === -1)
+ throw Error("Canceling placeholders only supported with undo manager attached to session.");
+ var undoManager = this.session.getUndoManager();
+ var undosRequired = (undoManager.$undoStack || undoManager.$undostack).length - this.$undoStackDepth;
+ for (var i = 0; i < undosRequired; i++) {
+ undoManager.undo(true);
+ }
+ };
+}).call(PlaceHolder.prototype);
+
+
+exports.PlaceHolder = PlaceHolder;
+});
+
+define('ace/theme/textmate', ['require', 'exports', 'module' , 'text!ace/theme/textmate.css', 'ace/lib/dom'], function(require, exports, module) {
+
+
+exports.isDark = false;
+exports.cssClass = "ace-tm";
+exports.cssText = require('text!./textmate.css');
+
+var dom = require("../lib/dom");
+dom.importCssString(exports.cssText, exports.cssClass);
+});
+define("text!ace/theme/textmate.css", [], ".ace-tm .ace_editor {\n" +
+ " border: 2px solid rgb(159, 159, 159);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_editor.ace_focus {\n" +
+ " border: 2px solid #327fbd;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_gutter {\n" +
+ " background: #f0f0f0;\n" +
+ " color: #333;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_print_margin {\n" +
+ " width: 1px;\n" +
+ " background: #e8e8e8;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_fold {\n" +
+ " background-color: #6B72E6;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_text-layer {\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_cursor {\n" +
+ " border-left: 2px solid black;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_cursor.ace_overwrite {\n" +
+ " border-left: 0px;\n" +
+ " border-bottom: 1px solid black;\n" +
+ "}\n" +
+ " \n" +
+ ".ace-tm .ace_line .ace_invisible {\n" +
+ " color: rgb(191, 191, 191);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_storage,\n" +
+ ".ace-tm .ace_line .ace_keyword {\n" +
+ " color: blue;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_constant {\n" +
+ " color: rgb(197, 6, 11);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_constant.ace_buildin {\n" +
+ " color: rgb(88, 72, 246);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_constant.ace_language {\n" +
+ " color: rgb(88, 92, 246);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_constant.ace_library {\n" +
+ " color: rgb(6, 150, 14);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_invalid {\n" +
+ " background-color: rgba(255, 0, 0, 0.1);\n" +
+ " color: red;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_support.ace_function {\n" +
+ " color: rgb(60, 76, 114);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_support.ace_constant {\n" +
+ " color: rgb(6, 150, 14);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_support.ace_type,\n" +
+ ".ace-tm .ace_line .ace_support.ace_class {\n" +
+ " color: rgb(109, 121, 222);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_keyword.ace_operator {\n" +
+ " color: rgb(104, 118, 135);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_string {\n" +
+ " color: rgb(3, 106, 7);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_comment {\n" +
+ " color: rgb(76, 136, 107);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_comment.ace_doc {\n" +
+ " color: rgb(0, 102, 255);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_comment.ace_doc.ace_tag {\n" +
+ " color: rgb(128, 159, 191);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_constant.ace_numeric {\n" +
+ " color: rgb(0, 0, 205);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_variable {\n" +
+ " color: rgb(49, 132, 149);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_line .ace_xml_pe {\n" +
+ " color: rgb(104, 104, 91);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_entity.ace_name.ace_function {\n" +
+ " color: #0000A2;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_markup.ace_markupine {\n" +
+ " text-decoration:underline;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_markup.ace_heading {\n" +
+ " color: rgb(12, 7, 255);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_markup.ace_list {\n" +
+ " color:rgb(185, 6, 144);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_marker-layer .ace_selection {\n" +
+ " background: rgb(181, 213, 255);\n" +
+ "}\n" +
+ ".ace-tm.multiselect .ace_selection.start {\n" +
+ " box-shadow: 0 0 3px 0px white;\n" +
+ " border-radius: 2px;\n" +
+ "}\n" +
+ ".ace-tm .ace_marker-layer .ace_step {\n" +
+ " background: rgb(252, 255, 0);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_marker-layer .ace_stack {\n" +
+ " background: rgb(164, 229, 101);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_marker-layer .ace_bracket {\n" +
+ " margin: -1px 0 0 -1px;\n" +
+ " border: 1px solid rgb(192, 192, 192);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_marker-layer .ace_active_line {\n" +
+ " background: rgba(0, 0, 0, 0.07);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_gutter_active_line {\n" +
+ " background-color : #dcdcdc;\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_marker-layer .ace_selected_word {\n" +
+ " background: rgb(250, 250, 255);\n" +
+ " border: 1px solid rgb(200, 200, 250);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_meta.ace_tag {\n" +
+ " color:rgb(0, 22, 142);\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_string.ace_regex {\n" +
+ " color: rgb(255, 0, 0)\n" +
+ "}\n" +
+ "\n" +
+ ".ace-tm .ace_indent-guide {\n" +
+ " background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y;\n" +
+ "}\n" +
+ "");
+
+;
+ (function() {
+ window.require(["ace/ace"], function(a) {
+ a && a.config.init();
+ if (!window.ace)
+ window.ace = {};
+ for (var key in a) if (a.hasOwnProperty(key))
+ ace[key] = a[key];
+ });
+ })();
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace.js b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace.js
new file mode 100644
index 0000000..4665877
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace.js
@@ -0,0 +1,11 @@
+(function(){function o(e){if(typeof requirejs!="undefined"){var i=t.define;t.define=function(e,t,n){return typeof n!="function"?i.apply(this,arguments):i(e,t,function(e,r,i){return t[2]=="module"&&(i.packaged=!0),n.apply(this,arguments)})},t.define.packaged=!0;return}var s=function(e,t){return r("",e,t)};s.packaged=!0;var o=t;e&&(t[e]||(t[e]={}),o=t[e]),o.define&&(n.original=o.define),o.define=n,o.require&&(r.original=o.require),o.require=s}var e="",t=function(){return this}(),n=function [...]
+ (function() {
+ window.require(["ace/ace"], function(a) {
+ a && a.config.init();
+ if (!window.ace)
+ window.ace = {};
+ for (var key in a) if (a.hasOwnProperty(key))
+ ace[key] = a[key];
+ });
+ })();
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/CharClassifier.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/CharClassifier.java
new file mode 100644
index 0000000..b05324f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/CharClassifier.java
@@ -0,0 +1,27 @@
+/*
+ * CharClassifier.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling;
+
+public interface CharClassifier
+{
+ public enum CharClass
+ {
+ Word,
+ Boundary, // Can be part of word, but can't end a word (e.g. apostrophe)
+ NonWord
+ }
+
+ CharClass classify(char c);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/TokenPredicate.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/TokenPredicate.java
new file mode 100644
index 0000000..dd06fa5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/TokenPredicate.java
@@ -0,0 +1,22 @@
+/*
+ * TokenPredicate.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling;
+
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Token;
+
+public interface TokenPredicate
+{
+ boolean test(Token token, int row, int column);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/WordIterable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/WordIterable.java
new file mode 100644
index 0000000..c4da814
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/spelling/WordIterable.java
@@ -0,0 +1,231 @@
+/*
+ * WordIterable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling;
+
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.*;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier.CharClass;
+
+import java.util.Iterator;
+
+public class WordIterable implements Iterable<Range>
+{
+ public WordIterable(EditSession session,
+ TokenPredicate checkableToken,
+ CharClassifier wordChar,
+ Position start)
+ {
+ this(session, checkableToken, wordChar, start, null);
+ }
+
+
+ public WordIterable(EditSession session,
+ TokenPredicate checkableToken,
+ CharClassifier wordChar,
+ Position start,
+ Position end)
+ {
+ if (end == null)
+ {
+ int lastRow = session.getLength() - 1;
+ if (lastRow < 0)
+ end = Position.create(0, 0);
+ else
+ end = Position.create(lastRow, session.getLine(lastRow).length());
+ }
+
+ session_ = session;
+ isCheckableToken_ = checkableToken;
+ charClassifier_ = wordChar;
+ start_ = start;
+ end_ = end;
+ }
+
+ @Override
+ public Iterator<Range> iterator()
+ {
+ return new RangeIterator(session_,
+ isCheckableToken_,
+ charClassifier_,
+ start_,
+ end_);
+ }
+
+ private final EditSession session_;
+ private final TokenPredicate isCheckableToken_;
+ private final CharClassifier charClassifier_;
+ private final Position start_;
+ private final Position end_;
+}
+
+class RangeIterator implements Iterator<Range>
+{
+ public RangeIterator(EditSession session,
+ TokenPredicate isCheckableToken,
+ CharClassifier charClassifier,
+ Position start,
+ Position end)
+ {
+ start_ = start;
+ end_ = end;
+ isCheckableToken_ = isCheckableToken;
+ charClassifier_ = charClassifier;
+
+ tokenIterator_ = TokenIterator.create(session, start.getRow(),
+ start.getColumn());
+ currentValue_ = "";
+ initialize();
+ }
+
+ private void initialize()
+ {
+ Token token = tokenIterator_.getCurrentToken();
+ if (token != null && isCheckableToken_.test(token,
+ tokenIterator_.getCurrentTokenRow(),
+ tokenIterator_.getCurrentTokenColumn()))
+ {
+ currentValue_ = token.getValue();
+ tokenPos_ = 0;
+
+ if (tokenIterator_.getCurrentTokenRow() == start_.getRow()
+ && tokenIterator_.getCurrentTokenColumn() < start_.getColumn())
+ {
+ // If start_ is inside the current token, skip over any words that
+ // end before start_
+
+ for (Range range; null != (range = nextWord()); )
+ {
+ if (range.getEnd().isAfter(start_))
+ {
+ tokenPos_ = range.getStart().getColumn() -
+ tokenIterator_.getCurrentTokenColumn();
+ break;
+ }
+ }
+ }
+ }
+ advance();
+ }
+
+ private boolean nextToken()
+ {
+ if (ended_)
+ return false;
+
+ while (true)
+ {
+ Token token = tokenIterator_.stepForward();
+ if (token == null)
+ {
+ ended_ = true;
+ return false;
+ }
+
+ if (isCheckableToken_.test(token,
+ tokenIterator_.getCurrentTokenRow(),
+ tokenIterator_.getCurrentTokenColumn()))
+ {
+ currentValue_ = token.getValue();
+ tokenPos_ = 0;
+ return true;
+ }
+ }
+ }
+
+ private Range nextWord()
+ {
+ if (ended_)
+ return null;
+
+ while (tokenPos_ < currentValue_.length() &&
+ charClassifier_.classify(currentValue_.charAt(tokenPos_)) != CharClass.Word)
+ {
+ tokenPos_++;
+ }
+
+ if (tokenPos_ == currentValue_.length())
+ return null;
+
+ int wordStart = tokenPos_++;
+
+ while (tokenPos_ < currentValue_.length() &&
+ charClassifier_.classify(currentValue_.charAt(tokenPos_)) != CharClass.NonWord)
+ {
+ tokenPos_++;
+ }
+
+ while (charClassifier_.classify(currentValue_.charAt(tokenPos_-1)) == CharClass.Boundary)
+ {
+ tokenPos_--;
+ }
+
+ int row = tokenIterator_.getCurrentTokenRow();
+ Position startPos = Position.create(
+ row, tokenIterator_.getCurrentTokenColumn() + wordStart);
+ Position endPos = Position.create(
+ row, tokenIterator_.getCurrentTokenColumn() + tokenPos_);
+
+ if (startPos.isAfterOrEqualTo(end_))
+ {
+ ended_ = true;
+ return null;
+ }
+
+ return Range.fromPoints(startPos, endPos);
+ }
+
+ private void advance()
+ {
+ do
+ {
+ nextValue_ = nextWord();
+ if (nextValue_ != null)
+ {
+ return;
+ }
+ } while (nextToken());
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return nextValue_ != null;
+ }
+
+ @Override
+ public Range next()
+ {
+ Range result = nextValue_;
+ advance();
+ return result;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ private final Position start_;
+ private final Position end_;
+ private final TokenPredicate isCheckableToken_;
+ private final CharClassifier charClassifier_;
+ private final TokenIterator tokenIterator_;
+
+ private boolean ended_;
+ private String currentValue_;
+ private int tokenPos_;
+
+ private Range nextValue_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/cpp/CppCompletionManager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/cpp/CppCompletionManager.java
new file mode 100644
index 0000000..ae55f61
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/cpp/CppCompletionManager.java
@@ -0,0 +1,373 @@
+/*
+ * CppCompletionManager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.editors.text.cpp;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.events.SelectionCommitEvent;
+import org.rstudio.core.client.events.SelectionCommitHandler;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionListPopupPanel;
+import org.rstudio.studio.client.workbench.views.console.shell.assist.CompletionManager;
+import org.rstudio.studio.client.workbench.views.console.shell.editor.InputEditorDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.NavigableSourceEditor;
+
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+
+public class CppCompletionManager implements CompletionManager
+{
+ public CppCompletionManager(InputEditorDisplay input,
+ NavigableSourceEditor navigableSourceEditor,
+ InitCompletionFilter initFilter,
+ CompletionManager rCompletionManager)
+ {
+ input_ = input;
+ navigableSourceEditor_ = navigableSourceEditor;
+ initFilter_ = initFilter;
+ rCompletionManager_ = rCompletionManager;
+ }
+
+ // return false to indicate key not handled
+ @Override
+ public boolean previewKeyDown(NativeEvent event)
+ {
+ if (isCursorInRMode())
+ return rCompletionManager_.previewKeyDown(event);
+
+ /*
+ if (popup_ == null)
+ {
+ if (false) // check for user completion key combo
+ // (we don't have any right now)
+ {
+ if (initFilter_ == null || initFilter_.shouldComplete(event))
+ {
+ beginSuggest();
+ return true;
+ }
+ }
+ }
+ else
+ {
+ switch (event.getKeyCode())
+ {
+ case KeyCodes.KEY_SHIFT:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_ALT:
+ return false ; // bare modifiers should do nothing
+ }
+
+ if (event.getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ close();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ input_.setText(popup_.getSelectedValue());
+ close();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_UP)
+ {
+ popup_.selectPrev();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_DOWN)
+ {
+ popup_.selectNext();
+ return true;
+ }
+ else if (event.getKeyCode() == KeyCodes.KEY_UP)
+ return popup_.selectPrev() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_DOWN)
+ return popup_.selectNext() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEUP)
+ return popup_.selectPrevPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_PAGEDOWN)
+ return popup_.selectNextPage() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_HOME)
+ return popup_.selectFirst() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_END)
+ return popup_.selectLast() ;
+ else if (event.getKeyCode() == KeyCodes.KEY_LEFT)
+ {
+ close();
+ return true ;
+ }
+
+ close();
+ return false;
+ }
+ */
+
+ return false;
+ }
+
+ // return false to indicate key not handled
+ @Override
+ public boolean previewKeyPress(char c)
+ {
+ if (isCursorInRMode())
+ return rCompletionManager_.previewKeyPress(c);
+
+ /*
+ if (popup_ != null)
+ {
+ // right now additional suggestions will be for attributes names
+ // and parameters (identifiers) so we use these characters to
+ // indicate to do another completion query (note that _ and : are
+ // valid R identifier chars but not package identifier chars)
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '.' || c == '_' || c == ':')
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ beginSuggest() ;
+ }
+ });
+ }
+ }
+ else
+ {
+ if (isAttributeCompletionValidHere(c))
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ beginSuggest() ;
+ }
+ });
+ }
+ else if (!input_.isSelectionCollapsed())
+ {
+ switch(c)
+ {
+ case '"':
+ case '\'':
+ encloseSelection(c, c);
+ return true;
+ case '(':
+ encloseSelection('(', ')');
+ return true;
+ case '{':
+ encloseSelection('{', '}');
+ return true;
+ case '[':
+ encloseSelection('[', ']');
+ return true;
+ }
+ }
+ }
+ */
+
+ return false ;
+ }
+
+
+ @SuppressWarnings("unused")
+ private void encloseSelection(char beginChar, char endChar)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(beginChar);
+ builder.append(input_.getSelectionValue());
+ builder.append(endChar);
+ input_.replaceSelection(builder.toString(), true);
+ }
+
+ @SuppressWarnings("unused")
+ private boolean isAttributeCompletionValidHere(char c)
+ {
+ // TODO: we can't just append the character since it could
+ // be anywhere withinh the line -- need to insert it into
+ // the right spot
+ String line = input_.getText() + c;
+ if (line.matches("\\s*//\\s+\\[\\[.*"))
+ {
+ // get text up to selection
+ String linePart = input_.getText().substring(
+ 0, input_.getSelection().getStart().getPosition());
+ return true;
+ }
+ return false;
+ }
+
+
+ // go to help at the current cursor location
+ @Override
+ public void goToHelp()
+ {
+ if (isCursorInRMode())
+ rCompletionManager_.goToHelp();
+ }
+
+ // find the definition of the function at the current cursor location
+ @Override
+ public void goToFunctionDefinition()
+ {
+ if (isCursorInRMode())
+ rCompletionManager_.goToFunctionDefinition();
+ }
+
+ // perform completion at the current cursor location
+ @Override
+ public void codeCompletion()
+ {
+ if (isCursorInRMode())
+ {
+ rCompletionManager_.codeCompletion();
+ }
+ else
+ {
+ if (initFilter_ == null || initFilter_.shouldComplete(null))
+ {
+
+ }
+ }
+ }
+
+ // close the completion popup (if any)
+ @Override
+ public void close()
+ {
+ if (isCursorInRMode())
+ {
+ rCompletionManager_.close();
+ }
+ else
+ {
+ if (popup_ != null)
+ {
+ popup_.hide();
+ popup_ = null;
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void beginSuggest()
+ {
+ completionRequestInvalidation_.invalidate();
+ final Token token = completionRequestInvalidation_.getInvalidationToken();
+
+ String value = input_.getText();
+ Debug.logToConsole(value);
+
+ getCompletions(value,
+ new SimpleRequestCallback<JsArrayString>()
+ {
+ @Override
+ public void onResponseReceived(JsArrayString resp)
+ {
+ if (token.isInvalid())
+ return;
+
+ if (resp.length() == 0)
+ {
+ popup_ = new CompletionListPopupPanel(new String[0]);
+ popup_.setText("(No matching commands)");
+ }
+ else
+ {
+ String[] entries = JsUtil.toStringArray(resp);
+ popup_ = new CompletionListPopupPanel(entries);
+ }
+
+ popup_.setMaxWidth(input_.getBounds().getWidth());
+ popup_.setPopupPositionAndShow(new PositionCallback()
+ {
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ Rectangle bounds = input_.getBounds();
+
+ int top = bounds.getTop() - offsetHeight;
+ if (top < 20)
+ top = bounds.getBottom();
+
+ popup_.selectLast();
+ popup_.setPopupPosition(bounds.getLeft() - 6, top);
+ }
+ });
+
+ popup_.addSelectionCommitHandler(new SelectionCommitHandler<String>()
+ {
+ public void onSelectionCommit(SelectionCommitEvent<String> e)
+ {
+ input_.setText(e.getSelectedItem());
+ close();
+ }
+ });
+
+ popup_.addCloseHandler(new CloseHandler<PopupPanel>() {
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ popup_ = null;
+ }
+
+ });
+ }
+ });
+ }
+
+
+ private void getCompletions(
+ String line,
+ ServerRequestCallback<JsArrayString> requestCallback)
+ {
+ }
+
+ private boolean isCursorInRMode()
+ {
+ String mode = input_.getLanguageMode(input_.getCursorPosition());
+ if (mode == null)
+ return false;
+ if (mode.equals(TextFileType.R_LANG_MODE))
+ return true;
+ return false;
+ }
+
+
+ private final InputEditorDisplay input_ ;
+ @SuppressWarnings("unused")
+ private final NavigableSourceEditor navigableSourceEditor_;
+ private CompletionListPopupPanel popup_;
+ private final InitCompletionFilter initFilter_ ;
+ private final CompletionManager rCompletionManager_;
+ private final Invalidation completionRequestInvalidation_ = new Invalidation();
+
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointMoveEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointMoveEvent.java
new file mode 100644
index 0000000..1de3685
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointMoveEvent.java
@@ -0,0 +1,53 @@
+/*
+ * BreakpointSetEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BreakpointMoveEvent extends GwtEvent<BreakpointMoveEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onBreakpointMove(BreakpointMoveEvent event);
+ }
+
+ public BreakpointMoveEvent(int breakpointId)
+ {
+ breakpointId_ = breakpointId;
+ }
+
+ public int getBreakpointId()
+ {
+ return breakpointId_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBreakpointMove(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private int breakpointId_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointSetEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointSetEvent.java
new file mode 100644
index 0000000..9021f0f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/BreakpointSetEvent.java
@@ -0,0 +1,67 @@
+/*
+ * BreakpointSetEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class BreakpointSetEvent extends GwtEvent<BreakpointSetEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onBreakpointSet(BreakpointSetEvent event);
+ }
+
+ public BreakpointSetEvent(int lineNumber, int breakpointId, boolean set)
+ {
+ lineNumber_ = lineNumber;
+ breakpointId_ = breakpointId;
+ set_ = set;
+ }
+
+ public int getLineNumber()
+ {
+ return lineNumber_;
+ }
+
+ public int getBreakpointId()
+ {
+ return breakpointId_;
+ }
+
+ public boolean isSet()
+ {
+ return set_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onBreakpointSet(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+ public static final int UNSET_BREAKPOINT_ID = -1;
+
+ private int lineNumber_;
+ private int breakpointId_;
+ private boolean set_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CommandClickEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CommandClickEvent.java
new file mode 100644
index 0000000..6718e1c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CommandClickEvent.java
@@ -0,0 +1,41 @@
+/*
+ * CommandClickEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CommandClickEvent extends GwtEvent<CommandClickEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCommandClick(CommandClickEvent event);
+ }
+
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onCommandClick(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedEvent.java
new file mode 100644
index 0000000..cdd482b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedEvent.java
@@ -0,0 +1,47 @@
+/*
+ * CursorChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+public class CursorChangedEvent extends GwtEvent<CursorChangedHandler>
+{
+ public static final Type<CursorChangedHandler> TYPE = new Type<CursorChangedHandler>();
+
+ public CursorChangedEvent(Position position)
+ {
+ position_ = position;
+ }
+
+ public Position getPosition()
+ {
+ return position_;
+ }
+
+ private final Position position_;
+
+ @Override
+ public Type<CursorChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(CursorChangedHandler handler)
+ {
+ handler.onCursorChanged(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedHandler.java
new file mode 100644
index 0000000..e9346ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/CursorChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * CursorChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface CursorChangedHandler extends EventHandler
+{
+ void onCursorChanged(CursorChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedEvent.java
new file mode 100644
index 0000000..c24f508
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedEvent.java
@@ -0,0 +1,34 @@
+/*
+ * EditorLoadedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EditorLoadedEvent extends GwtEvent<EditorLoadedHandler>
+{
+ public static final Type<EditorLoadedHandler> TYPE = new Type<EditorLoadedHandler>();
+
+ @Override
+ public Type<EditorLoadedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(EditorLoadedHandler handler)
+ {
+ handler.onEditorLoaded(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedHandler.java
new file mode 100644
index 0000000..af607ca
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/EditorLoadedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * EditorLoadedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface EditorLoadedHandler extends EventHandler
+{
+ void onEditorLoaded(EditorLoadedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedEvent.java
new file mode 100644
index 0000000..d25b91b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedEvent.java
@@ -0,0 +1,39 @@
+/*
+ * FileTypeChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FileTypeChangedEvent extends GwtEvent<FileTypeChangedHandler>
+{
+ public static final Type<FileTypeChangedHandler> TYPE =
+ new Type<FileTypeChangedHandler>();
+
+ public FileTypeChangedEvent()
+ {
+ }
+
+ @Override
+ public Type<FileTypeChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(FileTypeChangedHandler handler)
+ {
+ handler.onFileTypeChanged(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedHandler.java
new file mode 100644
index 0000000..0ec3b8e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FileTypeChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * FileTypeChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface FileTypeChangedHandler extends EventHandler
+{
+ void onFileTypeChanged(FileTypeChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FindRequestedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FindRequestedEvent.java
new file mode 100644
index 0000000..cd130ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FindRequestedEvent.java
@@ -0,0 +1,52 @@
+/*
+ * FindRequestedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FindRequestedEvent extends GwtEvent<FindRequestedEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onFindRequested(FindRequestedEvent event);
+ }
+
+ public FindRequestedEvent(boolean defaultForward)
+ {
+ defaultForward_ = defaultForward;
+ }
+
+ public boolean getDefaultForward()
+ {
+ return defaultForward_;
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onFindRequested(this);
+ }
+
+ private final boolean defaultForward_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FoldChangeEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FoldChangeEvent.java
new file mode 100644
index 0000000..e6f9dde
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/FoldChangeEvent.java
@@ -0,0 +1,44 @@
+/*
+ * FoldChangeEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class FoldChangeEvent extends GwtEvent<FoldChangeEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onFoldChange(FoldChangeEvent event);
+ }
+
+ public FoldChangeEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onFoldChange(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/HasFoldChangeHandlers.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/HasFoldChangeHandlers.java
new file mode 100644
index 0000000..392933e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/HasFoldChangeHandlers.java
@@ -0,0 +1,23 @@
+/*
+ * HasFoldChangeHandlers.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+
+public interface HasFoldChangeHandlers extends HasHandlers
+{
+ HandlerRegistration addFoldChangeHandler(FoldChangeEvent.Handler handler);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/PasteEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/PasteEvent.java
new file mode 100644
index 0000000..d683542
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/PasteEvent.java
@@ -0,0 +1,53 @@
+/*
+ * PasteEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.PasteEvent.Handler;
+
+public class PasteEvent extends GwtEvent<Handler>
+{
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ public interface Handler extends EventHandler
+ {
+ void onPaste(PasteEvent event);
+ }
+
+ public PasteEvent(String pastedText)
+ {
+ this.pastedText = pastedText;
+ }
+
+ public String getPastedText()
+ {
+ return pastedText;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onPaste(this);
+ }
+
+ private final String pastedText;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedEvent.java
new file mode 100644
index 0000000..70a18d6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedEvent.java
@@ -0,0 +1,34 @@
+/*
+ * SourceOnSaveChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceOnSaveChangedEvent extends GwtEvent<SourceOnSaveChangedHandler>
+{
+ public static final Type<SourceOnSaveChangedHandler> TYPE = new Type<SourceOnSaveChangedHandler>();
+
+ @Override
+ public Type<SourceOnSaveChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SourceOnSaveChangedHandler handler)
+ {
+ handler.onSourceOnSaveChanged(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedHandler.java
new file mode 100644
index 0000000..b64733a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/SourceOnSaveChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SourceOnSaveChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SourceOnSaveChangedHandler extends EventHandler
+{
+ void onSourceOnSaveChanged(SourceOnSaveChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoEvent.java
new file mode 100644
index 0000000..998cdbf
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoEvent.java
@@ -0,0 +1,45 @@
+/*
+ * UndoRedoEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class UndoRedoEvent extends GwtEvent<UndoRedoHandler>
+{
+ public UndoRedoEvent(boolean redo)
+ {
+ redo_ = redo;
+ }
+
+ public boolean isRedo()
+ {
+ return redo_;
+ }
+
+ @Override
+ public Type<UndoRedoHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(UndoRedoHandler handler)
+ {
+ handler.onUndoRedo(this);
+ }
+
+ public static Type<UndoRedoHandler> TYPE = new Type<UndoRedoHandler>();
+ private final boolean redo_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoHandler.java
new file mode 100644
index 0000000..0400ce8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/events/UndoRedoHandler.java
@@ -0,0 +1,22 @@
+/*
+ * UndoRedoHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface UndoRedoHandler extends EventHandler
+{
+ void onUndoRedo(UndoRedoEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplace.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplace.java
new file mode 100644
index 0000000..6050298
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplace.java
@@ -0,0 +1,555 @@
+/*
+ * FindReplace.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.findreplace;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.HasValue;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.core.client.regex.Pattern.ReplaceOperation;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay.AnchoredSelection;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Search;
+
+// TODO: For regex mode, stop using Ace's search code and do our own, in order
+// to avoid bugs with context directives (lookahead/lookbehind, ^, $)
+
+public class FindReplace
+{
+ public interface Display
+ {
+ HasValue<String> getFindValue();
+ void addFindKeyUpHandler(KeyUpHandler keyUpHandler);
+ HasValue<String> getReplaceValue();
+ HasValue<Boolean> getInSelection();
+ HasValue<Boolean> getCaseSensitive();
+ HasValue<Boolean> getWrapSearch();
+ HasValue<Boolean> getWholeWord();
+ HasValue<Boolean> getRegex();
+ HasClickHandlers getReplaceAll();
+
+ void activate(String searchText,
+ boolean defaultForward,
+ boolean inSelection);
+
+ void focusFindField(boolean selectAll);
+ }
+
+ public FindReplace(AceEditor editor,
+ Display display,
+ GlobalDisplay globalDisplay,
+ boolean showingReplace)
+ {
+ editor_ = editor;
+ display_ = display;
+ globalDisplay_ = globalDisplay;
+ errorCaption_ = showingReplace ? "Find/Replace" : "Find";
+
+ HasValue<Boolean> caseSensitive = display_.getCaseSensitive();
+ caseSensitive.setValue(defaultCaseSensitive_);
+ caseSensitive.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ defaultCaseSensitive_ = event.getValue();
+ }
+ });
+
+ HasValue<Boolean> wholeWord = display_.getWholeWord();
+ wholeWord.setValue(defaultWholeWord_);
+ wholeWord.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ defaultWholeWord_ = event.getValue();
+ }
+ });
+
+ HasValue<Boolean> regex = display_.getRegex();
+ regex.setValue(defaultRegex_);
+ regex.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ defaultRegex_ = event.getValue();
+ }
+ });
+
+ HasValue<Boolean> wrapSearch = display_.getWrapSearch();
+ wrapSearch.setValue(defaultWrapSearch_);
+ wrapSearch.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ defaultWrapSearch_ = event.getValue();
+ }
+ });
+
+ HasValue<Boolean> inSelection = display_.getInSelection();
+ inSelection.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ if (event.getValue())
+ {
+ resetTargetSelection();
+ display_.focusFindField(true);
+ }
+ else
+ clearTargetSelection();
+ }
+ });
+
+
+ addClickHandler(display.getReplaceAll(), new ClickHandler()
+ {
+ public void onClick(ClickEvent event)
+ {
+ replaceAll();
+ }
+ });
+
+ display_.addFindKeyUpHandler(new KeyUpHandler() {
+
+ @Override
+ public void onKeyUp(KeyUpEvent event)
+ {
+ // bail on navigational keys
+ if (event.getNativeKeyCode() == KeyCodes.KEY_TAB ||
+ event.getNativeKeyCode() == KeyCodes.KEY_ENTER ||
+ event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE ||
+ event.getNativeKeyCode() == KeyCodes.KEY_HOME ||
+ event.getNativeKeyCode() == KeyCodes.KEY_END ||
+ event.getNativeKeyCode() == KeyCodes.KEY_RIGHT ||
+ event.getNativeKeyCode() == KeyCodes.KEY_LEFT)
+ {
+ return ;
+ }
+
+ // perform incremental search
+ find(defaultForward_ ? FindType.Forward : FindType.Reverse, true);
+ }
+
+ });
+ }
+
+ public void activate(String searchText,
+ boolean defaultForward,
+ boolean inSelection)
+ {
+ defaultForward_ = defaultForward;
+ display_.activate(searchText, defaultForward, inSelection);
+ }
+
+ public void findNext()
+ {
+ find(FindType.Forward);
+ }
+
+ public void findPrevious()
+ {
+ find(FindType.Reverse);
+ }
+
+ public void replaceAndFind()
+ {
+ replace();
+ }
+
+ public void notifyEditorFocused()
+ {
+ display_.getInSelection().setValue(false, true);
+ }
+
+ public void notifyClosing()
+ {
+ clearTargetSelection();
+ }
+
+ private void addClickHandler(HasClickHandlers hasClickHandlers,
+ ClickHandler clickHandler)
+ {
+ if (hasClickHandlers != null)
+ hasClickHandlers.addClickHandler(clickHandler);
+ }
+
+ private enum FindType { Forward, Reverse }
+
+ private boolean find(FindType findType)
+ {
+ return find(findType, false);
+ }
+
+ private boolean find(FindType findType, boolean incremental)
+ {
+ String searchString = display_.getFindValue().getValue();
+ if (searchString.length() == 0)
+ {
+ // if this was an incremental search and the user has cleared
+ // out the searching string then return to the original position
+ if (incremental && (incrementalSearchPosition_ != null))
+ editor_.setSelectionRange(Range.fromPoints(
+ incrementalSearchPosition_, incrementalSearchPosition_));
+
+ return false;
+ }
+
+ boolean ignoreCase = !display_.getCaseSensitive().getValue();
+ boolean regex = display_.getRegex().getValue();
+ boolean wholeWord = display_.getWholeWord().getValue();
+ boolean wrap = display_.getWrapSearch().getValue();
+
+ // if we are searching in a selection then create a custom position
+ // (based on the current selection) and range (based on the originally
+ // saved selection range)
+ Position position = null;
+ Range range = null;
+ if (display_.getInSelection().getValue() && (targetSelection_ != null))
+ {
+ range = targetSelection_.getRange();
+
+ if (findType == FindType.Forward)
+ {
+ Position selectionEnd = editor_.getSelectionEnd();
+ if (selectionEnd.isBefore(range.getEnd()))
+ position = selectionEnd;
+ }
+ else
+ {
+ Position selectionStart = editor_.getSelectionStart();
+ if (selectionStart.isAfter(range.getStart()))
+ position = selectionStart;
+ }
+ }
+
+ // if this is an incremental search and we don't have a previous
+ // incremental start position then set it, otherwise clear it
+ if (incremental)
+ {
+ if (incrementalSearchPosition_ == null)
+ {
+ if (position != null)
+ incrementalSearchPosition_ = position;
+ else
+ incrementalSearchPosition_ = defaultForward_ ?
+ editor_.getSelectionStart() :
+ editor_.getSelectionEnd();
+ }
+
+ // incremental searches always continue searching from the
+ // original search position
+ position = incrementalSearchPosition_;
+ }
+ else
+ {
+ incrementalSearchPosition_ = null;
+ }
+
+ // do the search
+ Search search = Search.create(searchString,
+ findType != FindType.Forward,
+ wrap,
+ !ignoreCase,
+ wholeWord,
+ position,
+ range,
+ regex);
+
+ try
+ {
+ Range resultRange = search.find(editor_.getSession());
+ if (resultRange == null)
+ {
+ if (!incremental)
+ {
+ globalDisplay_.showMessage(GlobalDisplay.MSG_INFO,
+ errorCaption_,
+ "No more occurrences.");
+ }
+ else
+ {
+ editor_.collapseSelection(true);
+ }
+
+ return false;
+ }
+ else
+ {
+ editor_.revealRange(resultRange, false);
+ return true;
+ }
+ }
+ catch(Throwable e)
+ {
+ globalDisplay_.showMessage(GlobalDisplay.MSG_ERROR,
+ errorCaption_,
+ "Invalid search term.");
+
+ return false;
+ }
+ }
+
+ private void replace()
+ {
+ String searchString = display_.getFindValue().getValue();
+ if (searchString.length() == 0)
+ return;
+
+ Pattern pattern = createPattern();
+ String line = editor_.getCurrentLine();
+ Match m = pattern.match(line,
+ editor_.getSelectionStart().getColumn());
+ if (m != null
+ && m.getIndex() == editor_.getSelectionStart().getColumn()
+ && m.getValue().length() == editor_.getSelectionValue().length())
+ {
+ String replacement = display_.getReplaceValue().getValue();
+ editor_.replaceSelection(display_.getRegex().getValue()
+ ? substitute(m, replacement, line)
+ : replacement);
+
+ if (targetSelection_ != null)
+ targetSelection_.syncMarker();
+ }
+
+ find(defaultForward_ ? FindType.Forward : FindType.Reverse);
+ }
+
+ private Pattern createPattern()
+ {
+ boolean caseSensitive = display_.getCaseSensitive().getValue();
+ boolean regex = display_.getRegex().getValue();
+ String find = display_.getFindValue().getValue();
+
+ String flags = caseSensitive ? "gm" : "igm";
+ String query = regex ? find : Pattern.escape(find);
+ return Pattern.create(query, flags);
+ }
+
+ private void replaceAll()
+ {
+ String code = null;
+ if (targetSelection_ != null)
+ {
+ Range range = targetSelection_.getRange();
+ code = editor_.getCode(range.getStart(), range.getEnd());
+ }
+ else
+ {
+ code = editor_.getCode();
+ }
+
+ boolean regex = display_.getRegex().getValue();
+ String find = display_.getFindValue().getValue();
+ String repl = display_.getReplaceValue().getValue();
+
+ int occurrences = 0;
+ if (find.length() > 0)
+ {
+ Pattern pattern = createPattern();
+ StringBuilder result = new StringBuilder();
+
+ int pos = 0; // pointer into original string
+ for (Match m = pattern.match(code, 0);
+ m != null;
+ m = m.nextMatch())
+ {
+ occurrences++;
+
+ // Add everything between the end of the last match, and this one
+ int index = m.getIndex();
+ result.append(code, pos, index);
+
+ // Add the replacement value
+ if (regex)
+ result.append(substitute(m, repl, code));
+ else
+ result.append(repl);
+
+ // Point to the end of this match
+ pos = index + m.getValue().length();
+ }
+ result.append(code, pos, code.length());
+
+ String newCode = result.toString();
+
+ // either replace all or replace just the target range
+ if (targetSelection_ != null)
+ {
+ // restore and then replace the selection
+ editor_.setSelectionRange(targetSelection_.getRange());
+ editor_.replaceSelection(newCode, false);
+
+ // reset the target selection
+ resetTargetSelection();
+ }
+ else
+ {
+ editor_.replaceCode(newCode);
+ }
+ }
+ globalDisplay_.showMessage(GlobalDisplay.MSG_INFO,
+ errorCaption_,
+ occurrences + " occurrences replaced.");
+ }
+
+ private String substitute(final Match match,
+ String replacement,
+ final String data)
+ {
+ Pattern pattern = Pattern.create("[$\\\\]([1-9][0-9]?|.)");
+ return pattern.replaceAll(replacement, new ReplaceOperation()
+ {
+ public String replace(Match m)
+ {
+ char p = m.getValue().charAt(0);
+ char c = m.getValue().charAt(1);
+ switch (p)
+ {
+ case '\\':
+ switch (c)
+ {
+ case '\\':
+ return "\\";
+ case 'n':
+ return "\n";
+ case 'r':
+ return "\r";
+ case 't':
+ return "\t";
+ }
+ break;
+ case '$':
+ switch (c)
+ {
+ case '$':
+ return "$";
+ case '&':
+ return match.getValue();
+ case '`':
+ String prefix = data.substring(0, match.getIndex());
+ int lastLF = prefix.lastIndexOf("\n");
+ if (lastLF > 0)
+ prefix = prefix.substring(lastLF + 1);
+ return prefix;
+ case '\'':
+ String suffix = data.substring(match.getIndex() + match.getValue().length());
+ int firstBreak = suffix.indexOf("\r");
+ if (firstBreak < 0)
+ firstBreak = suffix.indexOf("\n");
+ if (firstBreak >= 0)
+ suffix = suffix.substring(0, firstBreak);
+ return suffix;
+ }
+ break;
+ }
+
+ switch (c)
+ {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ int index = Integer.parseInt(m.getGroup(1));
+ return StringUtil.notNull(match.getGroup(index));
+ }
+ return m.getValue();
+ }
+ });
+ }
+
+ private final AceEditor editor_;
+ private final Display display_;
+ private final GlobalDisplay globalDisplay_;
+ private final String errorCaption_;
+ private boolean defaultForward_ = true;
+ private Position incrementalSearchPosition_ = null;
+
+ private class TargetSelectionTracker
+ {
+ public TargetSelectionTracker()
+ {
+ // expand the selection to include lines (ace will effectively do
+ // this for a range based search)
+ editor_.fitSelectionToLines(true);
+ Position start = editor_.getSelectionStart();
+ Position end = editor_.getSelectionEnd();
+ anchoredSelection_ = editor_.createAnchoredSelection(start,end);
+
+ // collapse the cursor to the beginning or end
+ editor_.collapseSelection(defaultForward_);
+
+ // sync marker
+ syncMarker();
+ }
+
+ public Range getRange()
+ {
+ return anchoredSelection_.getRange();
+ }
+
+ public void syncMarker()
+ {
+ clear();
+
+ markerId_ = editor_.getSession().addMarker(getRange(),
+ "ace_find_line",
+ "background",
+ false);
+ }
+
+ public void clear()
+ {
+ if (markerId_ != null)
+ editor_.getSession().removeMarker(markerId_);
+ }
+
+ private Integer markerId_ = null;
+ private AnchoredSelection anchoredSelection_ = null;
+
+ }
+ private TargetSelectionTracker targetSelection_ = null;
+
+ private void clearTargetSelection()
+ {
+ if (targetSelection_ != null)
+ targetSelection_.clear();
+ targetSelection_ = null;
+ }
+
+ private void resetTargetSelection()
+ {
+ clearTargetSelection();
+ targetSelection_ = new TargetSelectionTracker();
+ }
+
+
+ private static boolean defaultCaseSensitive_ = false;
+ private static boolean defaultWrapSearch_ = true;
+ private static boolean defaultRegex_ = false;
+ private static boolean defaultWholeWord_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.css
new file mode 100644
index 0000000..1638508
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.css
@@ -0,0 +1,68 @@
+ at external gwt-CheckBox;
+ at external macintosh, windows, ubuntu_mono;
+
+.findReplaceBar div, .findReplaceBar td {
+ white-space: nowrap;
+}
+
+.findReplaceBar button {
+ position: relative;
+ top: 1px;
+ margin-right: 3px;
+}
+
+.findReplaceBar .gwt-CheckBox {
+ margin-top: 1px;
+ font-size: 9px;
+ white-space: nowrap;
+}
+
+.replaceTextBox {
+ margin-left: 10px;
+}
+
+.findPanel {
+ margin-top: 4px;
+}
+
+.optionsPanel {
+ margin-top: 9px;
+}
+
+.checkboxLabel {
+ margin-top: 2px;
+ margin-right: 8px;
+ font-size: 9px;
+}
+
+.ubuntu_mono .checkboxLabel {
+ font-size: 10px;
+}
+
+.windows .checkboxLabel {
+ font-size: 10px;
+ margin-left: 2px;
+ margin-top: 3px;
+}
+
+ at if rstudio.desktop true {
+.macintosh .checkboxLabel {
+ margin-top: 5px;
+}
+.ubuntu_mono .checkboxLabel {
+ margin-top: 5px;
+}
+.macintosh .optionsPanel {
+ margin-top: 7px;
+}
+}
+
+.closeButton {
+ position: relative;
+ top: 1px;
+ background-color: transparent;
+ border: 0;
+ padding: 0 !important;
+ margin: 0 !important;
+ outline: 0;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.java
new file mode 100644
index 0000000..efc7502
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/FindReplaceBar.java
@@ -0,0 +1,338 @@
+/*
+ * FindReplaceBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.findreplace;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.ElementIds;
+import org.rstudio.core.client.dom.WindowEx;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.CheckboxLabel;
+import org.rstudio.core.client.widget.FindTextBox;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.history.view.Shelf;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplace.Display;
+
+public class FindReplaceBar extends Composite implements Display, RequiresResize
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("FindReplaceBar.css")
+ Styles styles();
+
+ ImageResource findReplace();
+ ImageResource findReplaceLatched();
+ }
+
+ interface Styles extends CssResource
+ {
+ String findReplaceBar();
+ String replaceTextBox();
+ String findPanel();
+ String optionsPanel();
+ String checkboxLabel();
+ String closeButton();
+ }
+
+ public FindReplaceBar(boolean showReplace, final boolean defaultForward)
+ {
+ defaultForward_ = defaultForward;
+
+ Shelf shelf = new Shelf(true);
+ shelf.setWidth("100%");
+
+ VerticalPanel panel = new VerticalPanel();
+ ElementIds.assignElementId(panel.getElement(),
+ ElementIds.FIND_REPLACE_BAR);
+
+ HorizontalPanel findReplacePanel = new HorizontalPanel();
+ findReplacePanel.addStyleName(RES.styles().findPanel());
+ findReplacePanel.add(txtFind_ = new FindTextBox("Find"));
+ txtFind_.setIconVisible(true);
+
+ Commands cmds = RStudioGinjector.INSTANCE.getCommands();
+ findReplacePanel.add(btnFindNext_ = new SmallButton(cmds.findNext()));
+ findReplacePanel.add(btnFindPrev_ = new SmallButton(cmds.findPrevious()));
+
+ findReplacePanel.add(txtReplace_ = new FindTextBox("Replace"));
+ txtReplace_.addStyleName(RES.styles().replaceTextBox());
+ findReplacePanel.add(btnReplace_ = new SmallButton(cmds.replaceAndFind()));
+ findReplacePanel.add(btnReplaceAll_ = new SmallButton("All"));
+
+ panel.add(findReplacePanel);
+
+ HorizontalPanel optionsPanel = new HorizontalPanel();
+ optionsPanel.addStyleName(RES.styles().optionsPanel());
+
+ optionsPanel.add(chkInSelection_ = new CheckBox());
+ Label inSelectionLabel = new CheckboxLabel(chkInSelection_,
+ "In selection").getLabel();
+ inSelectionLabel.addStyleName(RES.styles().checkboxLabel());
+ optionsPanel.add(inSelectionLabel);
+
+ optionsPanel.add(chkCaseSensitive_ = new CheckBox());
+ Label matchCaseLabel =
+ new CheckboxLabel(chkCaseSensitive_, "Match case").getLabel();
+ matchCaseLabel.addStyleName(RES.styles().checkboxLabel());
+ optionsPanel.add(matchCaseLabel);
+
+ optionsPanel.add(chkWholeWord_ = new CheckBox());
+ Label wholeWordLabel =
+ new CheckboxLabel(chkWholeWord_, "Whole word").getLabel();
+ wholeWordLabel.addStyleName(RES.styles().checkboxLabel());
+ optionsPanel.add(wholeWordLabel);
+
+ optionsPanel.add(chkRegEx_ = new CheckBox());
+ Label regexLabel = new CheckboxLabel(chkRegEx_, "Regex").getLabel();
+ regexLabel.addStyleName(RES.styles().checkboxLabel());
+
+ optionsPanel.add(regexLabel);
+
+ optionsPanel.add(chkWrapSearch_ = new CheckBox());
+ Label wrapSearchLabel = new CheckboxLabel(chkWrapSearch_,
+ "Wrap").getLabel();
+ wrapSearchLabel.addStyleName(RES.styles().checkboxLabel());
+ optionsPanel.add(wrapSearchLabel);
+
+
+ panel.add(optionsPanel);
+
+ shelf.addLeftWidget(panel);
+
+ // fixup tab indexes of controls
+ txtFind_.setTabIndex(100);
+ txtReplace_.setTabIndex(101);
+ chkInSelection_.setTabIndex(102);
+ chkCaseSensitive_.setTabIndex(103);
+ chkWholeWord_.setTabIndex(104);
+ chkRegEx_.setTabIndex(105);
+ chkWrapSearch_.setTabIndex(106);
+
+ // remove SmallButton instances from tab order since (a) they aren't
+ // capable of showing a focused state; and (b) enter is already a
+ // keyboard shortcut for both find and replace
+ btnFindNext_.setTabIndex(-1);
+ btnFindPrev_.setTabIndex(-1);
+ btnReplace_.setTabIndex(-1);
+ btnReplaceAll_.setTabIndex(-1);
+
+ shelf.setRightVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
+ shelf.addRightWidget(btnClose_ = new Button());
+ btnClose_.setStyleName(RES.styles().closeButton());
+ btnClose_.addStyleName(ThemeStyles.INSTANCE.closeTabButton());
+ btnClose_.getElement().appendChild(
+ new Image(ThemeResources.INSTANCE.closeTab()).getElement());
+
+ txtFind_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ if (defaultForward_)
+ btnFindNext_.click();
+ else
+ btnFindPrev_.click();
+ focusFindField(false);
+ }
+ }
+ });
+
+ txtReplace_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ btnReplace_.click();
+ WindowEx.get().focus();
+ txtReplace_.focus();
+ }
+ }
+ });
+
+ shelf.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE
+ && !event.isAnyModifierKeyDown())
+ {
+ event.stopPropagation();
+ event.preventDefault();
+
+ btnClose_.click();
+ }
+ }
+ });
+
+ if (!showReplace)
+ {
+ txtReplace_.setVisible(false);
+ btnReplace_.setVisible(false);
+ btnReplaceAll_.setVisible(false);
+ }
+
+ initWidget(shelf);
+
+ addStyleName(RES.styles().findReplaceBar());
+ }
+
+ public HasValue<String> getFindValue()
+ {
+ return txtFind_;
+ }
+
+ public void addFindKeyUpHandler(KeyUpHandler keyUpHandler)
+ {
+ txtFind_.addKeyUpHandler(keyUpHandler);
+ }
+
+ public HasValue<String> getReplaceValue()
+ {
+ return txtReplace_;
+ }
+
+ public HasValue<Boolean> getInSelection()
+ {
+ return chkInSelection_;
+ }
+
+ public HasValue<Boolean> getCaseSensitive()
+ {
+ return chkCaseSensitive_;
+ }
+
+ public HasValue<Boolean> getWholeWord()
+ {
+ return chkWholeWord_;
+ }
+
+ public HasValue<Boolean> getRegex()
+ {
+ return chkRegEx_;
+ }
+
+ public HasValue<Boolean> getWrapSearch()
+ {
+ return chkWrapSearch_;
+ }
+
+ public HasClickHandlers getReplaceAll()
+ {
+ return btnReplaceAll_;
+ }
+
+
+ public HasClickHandlers getCloseButton()
+ {
+ return btnClose_;
+ }
+
+ public double getHeight()
+ {
+ return 56;
+ }
+
+ public void activate(String searchText,
+ boolean defaultForward,
+ boolean inSelection)
+ {
+ defaultForward_ = defaultForward;
+
+ if (searchText != null)
+ {
+ focusFindField(false);
+ txtFind_.setValue(searchText);
+ }
+ else
+ {
+ focusFindField(true);
+ }
+
+ chkInSelection_.setValue(inSelection, true);
+ }
+
+ public void focusFindField(boolean selectAll)
+ {
+ WindowEx.get().focus();
+ txtFind_.focus();
+ if (selectAll)
+ txtFind_.selectAll();
+ }
+
+
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ public void onResize()
+ {
+ int width = getOffsetWidth();
+ setNarrowMode(width > 0 && width < 520);
+ }
+
+ private void setNarrowMode(boolean narrow)
+ {
+ if (narrow)
+ {
+ txtFind_.setOverrideWidth(100);
+ txtReplace_.setOverrideWidth(100);
+ }
+ else
+ {
+ txtFind_.setOverrideWidth(160);
+ txtReplace_.setOverrideWidth(160);
+ }
+ }
+
+ public static ImageResource getFindIcon()
+ {
+ return RES.findReplace();
+ }
+
+ public static ImageResource getFindLatchedIcon()
+ {
+ return RES.findReplaceLatched();
+ }
+
+ private FindTextBox txtFind_;
+ private FindTextBox txtReplace_;
+ private SmallButton btnFindNext_;
+ private SmallButton btnFindPrev_;
+ private SmallButton btnReplace_;
+ private SmallButton btnReplaceAll_;
+ private CheckBox chkWholeWord_;
+ private CheckBox chkCaseSensitive_;
+ private CheckBox chkRegEx_;
+ private CheckBox chkWrapSearch_;
+ private CheckBox chkInSelection_;
+ private Button btnClose_;
+ private static Resources RES = GWT.create(Resources.class);
+ private boolean defaultForward_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplace.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplace.png
new file mode 100644
index 0000000..6adab66
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplace.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplaceLatched.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplaceLatched.png
new file mode 100644
index 0000000..9000955
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/findreplace/findReplaceLatched.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/CheckSpelling.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/CheckSpelling.java
new file mode 100644
index 0000000..bc10c44
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/CheckSpelling.java
@@ -0,0 +1,413 @@
+/*
+ * CheckSpelling.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.spelling;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.PopupPanel;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.ResultCallback;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.spelling.SpellChecker;
+import org.rstudio.studio.client.common.spelling.model.SpellCheckerResult;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Anchor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class CheckSpelling
+{
+ public interface Display extends HasCloseHandlers<PopupPanel>
+ {
+ HasClickHandlers getAddButton();
+ HasClickHandlers getIgnoreAllButton();
+ HasClickHandlers getSkipButton();
+ HasClickHandlers getChangeButton();
+ HasClickHandlers getChangeAllButton();
+
+ HasText getMisspelledWord();
+ HasText getReplacement();
+ void setSuggestions(String[] values);
+ void clearSuggestions();
+ HasChangeHandlers getSuggestionList();
+ String getSelectedSuggestion();
+
+ void focusReplacement();
+
+ void showModal();
+ boolean isShowing();
+ void closeDialog();
+
+ void showProgress();
+ void hideProgress();
+
+ void setEditorSelectionBounds(Rectangle bounds);
+ }
+
+ public interface ProgressDisplay
+ {
+ void show();
+ void hide();
+ boolean isShowing();
+ HasClickHandlers getCancelButton();
+ }
+
+ public CheckSpelling(SpellChecker spellChecker,
+ DocDisplay docDisplay,
+ Display view,
+ ProgressDisplay progressDisplay,
+ ResultCallback<Void, Exception> callback)
+ {
+ spellChecker_ = spellChecker;
+ docDisplay_ = docDisplay;
+ view_ = view;
+ progressDisplay_ = progressDisplay;
+ callback_ = callback;
+
+ currentPos_ = docDisplay_.getSelectionStart();
+ initialCursorPos_ = docDisplay_.createAnchor(currentPos_);
+ wrapped_ = false;
+
+ view_.getChangeButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ doReplacement(view_.getReplacement().getText());
+ findNextMisspelling();
+ }
+ });
+
+ view_.getChangeAllButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (!view_.getMisspelledWord().getText().equals(view_.getReplacement().getText()))
+ {
+ changeAll_.put(view_.getMisspelledWord().getText(),
+ view_.getReplacement().getText());
+ }
+ doReplacement(view_.getReplacement().getText());
+ findNextMisspelling();
+ }
+ });
+
+ view_.getSkipButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ currentPos_ = docDisplay_.getCursorPosition();
+ findNextMisspelling();
+ }
+ });
+
+ view_.getIgnoreAllButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ spellChecker_.addIgnoredWord(view_.getMisspelledWord().getText());
+ currentPos_ = docDisplay_.getCursorPosition();
+ findNextMisspelling();
+ }
+ });
+
+ view_.getAddButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ spellChecker_.addToUserDictionary(
+ view_.getMisspelledWord().getText());
+ currentPos_ = docDisplay_.getCursorPosition();
+ findNextMisspelling();
+ }
+ });
+
+ view_.getSuggestionList().addChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ String replacement = view_.getSelectedSuggestion();
+ if (replacement != null)
+ view_.getReplacement().setText(replacement);
+ }
+ });
+
+ view_.addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ cancel();
+ }
+ });
+
+ progressDisplay_.getCancelButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ cancel();
+ progressDisplay_.hide();
+ }
+ });
+
+ progressDisplay_.show();
+
+ findNextMisspelling();
+ }
+
+ private void cancel()
+ {
+ canceled_ = true;
+ callback_.onCancelled();
+ }
+
+ private void doReplacement(String replacement)
+ {
+ docDisplay_.replaceSelection(replacement);
+ // Spell check what we just replaced
+ currentPos_ = docDisplay_.getSelectionStart();
+ }
+
+ private void findNextMisspelling()
+ {
+ try
+ {
+ if (checkForCancel())
+ return;
+
+ showProgress();
+
+ Iterable<Range> wordSource = docDisplay_.getWords(
+ docDisplay_.getFileType().getTokenPredicate(),
+ docDisplay_.getFileType().getCharPredicate(),
+ currentPos_,
+ wrapped_ ? initialCursorPos_.getPosition() : null);
+
+ final ArrayList<String> words = new ArrayList<String>();
+ final ArrayList<Range> wordRanges = new ArrayList<Range>();
+
+ for (Range r : wordSource)
+ {
+ // Don't worry about pathologically long words
+ if (r.getEnd().getColumn() - r.getStart().getColumn() > 250)
+ continue;
+
+ wordRanges.add(r);
+ words.add(docDisplay_.getTextForRange(r));
+
+ // Check a maximum of N words at a time
+ if (wordRanges.size() == 100)
+ break;
+ }
+
+ if (wordRanges.size() > 0)
+ {
+ spellChecker_.checkSpelling(words, new SimpleRequestCallback<SpellCheckerResult>()
+ {
+ @Override
+ public void onResponseReceived(SpellCheckerResult response)
+ {
+ if (checkForCancel())
+ return;
+
+ for (int i = 0; i < words.size(); i++)
+ {
+ if (response.getIncorrect().contains(words.get(i)))
+ {
+ handleMisspelledWord(wordRanges.get(i));
+ return;
+ }
+ }
+
+ currentPos_ = wordRanges.get(wordRanges.size()-1).getEnd();
+ // Everything spelled correctly, continue
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ findNextMisspelling();
+ }
+ });
+ }
+ });
+ }
+ else
+ {
+ // No misspellings
+ if (wrapped_)
+ {
+ close();
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showMessage(
+ GlobalDisplay.MSG_INFO,
+ "Check Spelling",
+ "Spell check is complete.");
+ callback_.onSuccess(Void.create());
+ }
+ else
+ {
+ wrapped_ = true;
+ currentPos_ = Position.create(0, 0);
+ findNextMisspelling();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.log(e.toString());
+ close();
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Check Spelling",
+ "An error has occurred:\n\n" + e.getMessage());
+ callback_.onFailure(e);
+ }
+ }
+
+ private void close()
+ {
+ progressDisplay_.hide();
+ view_.closeDialog();
+ }
+
+ private boolean checkForCancel()
+ {
+ return canceled_;
+ }
+
+ private void showProgress()
+ {
+ if (view_.isShowing())
+ view_.showProgress();
+ }
+
+ private void showDialog(Rectangle selectedWordBounds)
+ {
+ if (progressDisplay_.isShowing())
+ progressDisplay_.hide();
+
+ view_.setEditorSelectionBounds(selectedWordBounds);
+ if (!view_.isShowing())
+ view_.showModal();
+ view_.hideProgress();
+ }
+
+ private void handleMisspelledWord(Range range)
+ {
+ try
+ {
+ docDisplay_.setSelectionRange(range);
+ docDisplay_.moveCursorNearTop();
+ view_.clearSuggestions();
+ view_.getReplacement().setText("");
+
+ final String word = docDisplay_.getTextForRange(range);
+
+ if (changeAll_.containsKey(word))
+ {
+ doReplacement(changeAll_.get(word));
+ findNextMisspelling();
+ return;
+ }
+
+ view_.getMisspelledWord().setText(word);
+
+ // This fixed delay is regrettable but necessary as it can take some
+ // time for Ace's scrolling logic to actually execute (i.e. the next
+ // time the renderloop runs). If we don't wait, then misspelled words
+ // at the end of the document will result in misreported cursor bounds,
+ // meaning we'll be avoiding a completely incorrect region.
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ @Override
+ public boolean execute()
+ {
+ showDialog(docDisplay_.getCursorBounds());
+
+ view_.focusReplacement();
+
+ spellChecker_.suggestionList(word, new ServerRequestCallback<JsArrayString>()
+ {
+ @Override
+ public void onResponseReceived(
+ JsArrayString response)
+ {
+ String[] suggestions = JsUtil.toStringArray(response);
+ view_.setSuggestions(suggestions);
+ if (suggestions.length > 0)
+ {
+ view_.getReplacement().setText(suggestions[0]);
+ view_.focusReplacement();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+
+ return false;
+ }
+ }, 100);
+ }
+ catch (Exception e)
+ {
+ Debug.log(e.toString());
+ close();
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Check Spelling",
+ "An error has occurred:\n\n" + e.getMessage());
+ callback_.onFailure(e);
+}
+ }
+
+ private final SpellChecker spellChecker_;
+ private final DocDisplay docDisplay_;
+ private final Display view_;
+ private final ProgressDisplay progressDisplay_;
+ private final ResultCallback<org.rstudio.studio.client.server.Void, Exception> callback_;
+ private final Anchor initialCursorPos_;
+
+ private final HashMap<String, String> changeAll_ = new HashMap<String, String>();
+
+ private Position currentPos_;
+
+ private boolean wrapped_;
+ private boolean canceled_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/InitialProgressDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/InitialProgressDialog.java
new file mode 100644
index 0000000..6bc7d0a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/InitialProgressDialog.java
@@ -0,0 +1,82 @@
+/*
+ * InitialProgressDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.spelling;
+
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.user.client.Timer;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.workbench.views.source.editors.text.spelling.CheckSpelling.ProgressDisplay;
+
+public class InitialProgressDialog implements ProgressDisplay
+{
+ public InitialProgressDialog(int delayShowMs)
+ {
+ delayShowMs_ = delayShowMs;
+ dialog_ = new MessageDialog(MessageDialog.INFO,
+ "Check Spelling",
+ "Spell check in progress...");
+ cancel_ = dialog_.addButton("Cancel", (Operation) null, true, true);
+
+ delayShowTimer_ = new Timer()
+ {
+ @Override
+ public void run()
+ {
+ dialog_.showModal();
+ }
+ };
+ }
+
+ @Override
+ public void show()
+ {
+ if (running_)
+ return;
+
+ running_ = true;
+ delayShowTimer_.schedule(delayShowMs_);
+ }
+
+ @Override
+ public void hide()
+ {
+ if (running_)
+ {
+ delayShowTimer_.cancel();
+ if (dialog_.isShowing())
+ dialog_.closeDialog();
+ }
+ }
+
+ @Override
+ public boolean isShowing()
+ {
+ return running_;
+ }
+
+ @Override
+ public HasClickHandlers getCancelButton()
+ {
+ return cancel_;
+ }
+
+ private final MessageDialog dialog_;
+ private ThemedButton cancel_;
+ private final Timer delayShowTimer_;
+ private final int delayShowMs_;
+ private boolean running_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.java
new file mode 100644
index 0000000..befe3fc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.java
@@ -0,0 +1,254 @@
+/*
+ * SpellingDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.spelling;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.HasChangeHandlers;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.Rectangle;
+import org.rstudio.core.client.Rectangle.FailureMode;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.ThemedButton;
+
+public class SpellingDialog extends ModalDialogBase implements CheckSpelling.Display
+{
+ interface Binder extends UiBinder<Widget, SpellingDialog>
+ {}
+
+ public SpellingDialog()
+ {
+ setText("Check Spelling");
+
+ btnAdd_ = new ThemedButton("Add");
+ btnAdd_.setTitle("Add word to user dictionary");
+ btnSkip_ = new ThemedButton("Ignore");
+ btnIgnoreAll_ = new ThemedButton("Ignore All");
+ btnChange_ = new ThemedButton("Change");
+ btnChangeAll_ = new ThemedButton("Change All");
+ prepareButtons(btnAdd_, btnSkip_, btnIgnoreAll_, btnChange_, btnChangeAll_);
+
+ mainWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ buttons_ = new ThemedButton[] {
+ btnAdd_, btnIgnoreAll_, btnSkip_, btnChange_, btnChangeAll_
+ };
+
+ addCancelButton();
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return mainWidget_;
+ }
+
+ private void prepareButtons(ThemedButton... buttons)
+ {
+ for (ThemedButton button : buttons)
+ {
+ button.setTight(true);
+ }
+ }
+
+ @Override
+ public HasClickHandlers getAddButton()
+ {
+ return btnAdd_;
+ }
+
+ @Override
+ public HasClickHandlers getIgnoreAllButton()
+ {
+ return btnIgnoreAll_;
+ }
+
+ @Override
+ public HasClickHandlers getSkipButton()
+ {
+ return btnSkip_;
+ }
+
+ @Override
+ public HasClickHandlers getChangeButton()
+ {
+ return btnChange_;
+ }
+
+ @Override
+ public HasClickHandlers getChangeAllButton()
+ {
+ return btnChangeAll_;
+ }
+
+ @Override
+ public HasText getMisspelledWord()
+ {
+ return txtDisplay_;
+ }
+
+ @Override
+ public HasText getReplacement()
+ {
+ return txtReplacement_;
+ }
+
+ @Override
+ public void setSuggestions(String[] values)
+ {
+ lstSuggestions_.clear();
+ for (String value : values)
+ lstSuggestions_.addItem(value);
+
+ if (values.length > 0)
+ lstSuggestions_.setSelectedIndex(0);
+ }
+
+ @Override
+ public void clearSuggestions()
+ {
+ lstSuggestions_.clear();
+ }
+
+ @Override
+ public HasChangeHandlers getSuggestionList()
+ {
+ return lstSuggestions_;
+ }
+
+ @Override
+ public String getSelectedSuggestion()
+ {
+ int index = lstSuggestions_.getSelectedIndex();
+ if (index < 0)
+ return null;
+ return lstSuggestions_.getItemText(index);
+ }
+
+ @Override
+ public void focusReplacement()
+ {
+ txtReplacement_.setFocus(true);
+ txtReplacement_.selectAll();
+ }
+
+ @Override
+ public void closeDialog()
+ {
+ super.closeDialog();
+ }
+
+ @Override
+ public void showProgress()
+ {
+ txtDisplay_.setText("Checking...");
+ txtReplacement_.setText("");
+
+ txtReplacement_.setEnabled(false);
+ lstSuggestions_.setEnabled(false);
+ clearSuggestions();
+ setButtonsEnabled(false);
+ }
+
+ @Override
+ public void hideProgress()
+ {
+ txtReplacement_.setEnabled(true);
+ lstSuggestions_.setEnabled(true);
+ setButtonsEnabled(true);
+ }
+
+ @Override
+ public void setEditorSelectionBounds(Rectangle selectionBounds)
+ {
+ // Inflate the bounds by 10 pixels to add a little air
+ boundsToAvoid_ = selectionBounds.inflate(10);
+ if (isShowing())
+ {
+ Rectangle screen = new Rectangle(0, 0,
+ Window.getClientWidth(),
+ Window.getClientHeight());
+
+ Rectangle bounds = new Rectangle(getPopupLeft(),
+ getPopupTop(),
+ getOffsetWidth(),
+ getOffsetHeight());
+
+ // In case user moved the dialog off the screen
+ bounds = bounds.attemptToMoveInto(screen, FailureMode.NO_CHANGE);
+
+ // Now avoid the selected word
+ move(bounds.avoidBounds(boundsToAvoid_, screen), true);
+ }
+ }
+
+ @Override
+ protected void positionAndShowDialog(final Command onCompleted)
+ {
+ setPopupPositionAndShow(new PositionCallback()
+ {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ Rectangle screen = new Rectangle(0, 0,
+ Window.getClientWidth(),
+ Window.getClientHeight());
+
+ Rectangle bounds = screen.createCenteredRect(offsetWidth,
+ offsetHeight);
+
+ move(bounds.avoidBounds(boundsToAvoid_, screen), false);
+
+ onCompleted.execute();
+ }
+ });
+ }
+
+ private void setButtonsEnabled(boolean enabled)
+ {
+ for (ThemedButton button : buttons_)
+ button.setEnabled(enabled);
+ }
+
+ private ThemedButton[] buttons_;
+
+ @UiField(provided = true)
+ ThemedButton btnAdd_;
+ @UiField(provided = true)
+ ThemedButton btnIgnoreAll_;
+ @UiField(provided = true)
+ ThemedButton btnSkip_;
+ @UiField(provided = true)
+ ThemedButton btnChange_;
+ @UiField(provided = true)
+ ThemedButton btnChangeAll_;
+ @UiField
+ TextBox txtReplacement_;
+ @UiField
+ ListBox lstSuggestions_;
+ @UiField
+ TextBox txtDisplay_;
+ private final Widget mainWidget_;
+
+ private Rectangle boundsToAvoid_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.ui.xml
new file mode 100644
index 0000000..b28d9ba
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/spelling/SpellingDialog.ui.xml
@@ -0,0 +1,79 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .outer input {
+ height: 20px !important;
+ }
+
+ .control {
+ width: 275px;
+ border: 1px solid #888 !important;
+ background-color: white;
+ margin: 0 0 3px 0;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ font-size: 12px;
+ }
+
+ .label {
+ display: inline-block;
+ width: 110px;
+ }
+
+ .suggestions {
+ width: 192px;
+ margin-bottom: 24px;
+ }
+
+ .start2 {
+ margin-left: 110px;
+ }
+
+ .col2_3 {
+ width: 275px;
+ }
+
+ .smallTopMargin {
+ margin-top: 4px;
+ }
+
+ .bigTopMargin {
+ margin-top: 18px;
+ }
+
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.outer}">
+
+ <div>
+ <div class="{style.label}">Not in dictionary:
+ </div><g:TextBox ui:field="txtDisplay_"
+ styleName="{style.control}"
+ readOnly="true"/>
+ </div>
+ <div class="{style.smallTopMargin} {style.start2} {style.col2_3}">
+ <widget:ThemedButton ui:field="btnSkip_"/><div style="display:inline-block;width:8px"/><widget:ThemedButton ui:field="btnIgnoreAll_"/>
+ <div style="float: right">
+ <widget:ThemedButton ui:field="btnAdd_"/>
+ </div>
+ </div>
+ <div>
+ <div class="{style.bigTopMargin} {style.label}">Change to:
+ </div><g:TextBox ui:field="txtReplacement_" styleName="{style.control}" />
+ </div>
+ <div class="{style.smallTopMargin} {style.start2} {style.col2_3}">
+ <g:ListBox
+ ui:field="lstSuggestions_"
+ styleName="{style.control} {style.suggestions}"
+ visibleItemCount="5"/>
+ <div style="float: right">
+ <div><widget:ThemedButton ui:field="btnChange_"/></div>
+ <div style="margin-top: 2px"><widget:ThemedButton ui:field="btnChangeAll_"/></div>
+ </div>
+ </div>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBar.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBar.java
new file mode 100644
index 0000000..672f3e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBar.java
@@ -0,0 +1,29 @@
+/*
+ * StatusBar.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+public interface StatusBar
+{
+ public static final int SCOPE_SLIDE = 4;
+ public static final int SCOPE_SECTION = 3;
+ public static final int SCOPE_CHUNK = 2;
+ public static final int SCOPE_FUNCTION = 1;
+
+ StatusBarElement getPosition();
+ StatusBarElement getScope();
+ StatusBarElement getLanguage();
+ void setScopeVisible(boolean visible);
+ void setScopeType(int type);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElement.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElement.java
new file mode 100644
index 0000000..da6aa63
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElement.java
@@ -0,0 +1,32 @@
+/*
+ * StatusBarElement.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+import com.google.gwt.event.dom.client.HasMouseDownHandlers;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+
+public interface StatusBarElement extends HasSelectionHandlers<String>,
+ HasMouseDownHandlers
+{
+ public void setValue(String value);
+ public String getValue();
+
+ void addOptionValue(String label);
+ void clearOptions();
+
+ void setVisible(boolean visible);
+
+ void click();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElementWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElementWidget.java
new file mode 100644
index 0000000..d104a27
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarElementWidget.java
@@ -0,0 +1,163 @@
+/*
+ * StatusBarElementWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasSelectionHandlers;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuItem;
+
+import java.util.ArrayList;
+
+public class StatusBarElementWidget extends FlowPanel
+ implements StatusBarElement, HasSelectionHandlers<String>
+{
+ interface Resources extends ClientBundle
+ {
+ ImageResource upDownArrow();
+ }
+
+ public StatusBarElementWidget()
+ {
+ options_ = new ArrayList<String>();
+ label_ = new Label();
+ add(label_);
+
+ addDomHandler(new MouseDownHandler()
+ {
+ public void onMouseDown(MouseDownEvent event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (options_.size() == 0)
+ return;
+
+ StatusBarPopupMenu menu = new StatusBarPopupMenu();
+ for (final String option : options_)
+ menu.addItem(new MenuItem(option, new Command()
+ {
+ public void execute()
+ {
+ SelectionEvent.fire(StatusBarElementWidget.this, option);
+ }
+ }));
+ menu.showRelativeToUpward(label_);
+ }
+ }, MouseDownEvent.getType());
+ }
+
+ public void setValue(String value)
+ {
+ label_.setText(value);
+ }
+
+ public String getValue()
+ {
+ return label_.getText();
+ }
+
+ public void addOptionValue(String option)
+ {
+ options_.add(option);
+ }
+
+ public void clearOptions()
+ {
+ options_.clear();
+ }
+
+ public void click()
+ {
+ NativeEvent evt = Document.get().createMouseDownEvent(0, 0, 0, 0, 0,
+ false, false,
+ false, false, 0);
+ ClickEvent.fireNativeEvent(evt, this);
+ }
+
+ public void setShowArrows(boolean showArrows)
+ {
+ if (showArrows ^ arrows_ != null)
+ {
+ if (showArrows)
+ {
+ Resources res = GWT.create(Resources.class);
+ arrows_ = new Image(res.upDownArrow());
+ add(arrows_);
+ }
+ else
+ {
+ arrows_.removeFromParent();
+ arrows_ = null;
+ }
+ }
+ }
+
+ public String getText()
+ {
+ return label_.getText();
+ }
+
+ public void setText(String s)
+ {
+ label_.setText(s);
+ }
+
+ public HandlerRegistration addSelectionHandler(SelectionHandler<String> handler)
+ {
+ return addHandler(handler, SelectionEvent.getType());
+ }
+
+ public HandlerRegistration addMouseDownHandler(final MouseDownHandler handler)
+ {
+ return addDomHandler(new MouseDownHandler()
+ {
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ if (clicksEnabled_)
+ handler.onMouseDown(event);
+ }
+ }, MouseDownEvent.getType());
+ }
+
+ public void setContentsVisible(boolean visible)
+ {
+ label_.setVisible(visible);
+ if (arrows_ != null)
+ arrows_.setVisible(visible);
+ }
+
+ public void setClicksEnabled(boolean enabled)
+ {
+ clicksEnabled_ = enabled;
+ }
+
+ private final ArrayList<String> options_;
+ private final Label label_;
+ private Image arrows_;
+ private boolean clicksEnabled_ = true;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupMenu.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupMenu.java
new file mode 100644
index 0000000..3f61e61
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupMenu.java
@@ -0,0 +1,46 @@
+/*
+ * StatusBarPopupMenu.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.ScrollableToolbarPopupMenu;
+
+public class StatusBarPopupMenu extends ScrollableToolbarPopupMenu
+{
+ public StatusBarPopupMenu()
+ {
+ super();
+ addStyleName(ThemeStyles.INSTANCE.statusBarMenu());
+ }
+
+ public void showRelativeToUpward(final UIObject target)
+ {
+ setPopupPositionAndShow(new PositionCallback()
+ {
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ setPopupPosition(target.getAbsoluteLeft(),
+ target.getAbsoluteTop() - offsetHeight);
+ }
+ });
+ }
+
+ @Override
+ protected int getMaxHeight()
+ {
+ return 350;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupRequest.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupRequest.java
new file mode 100644
index 0000000..ab4211a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarPopupRequest.java
@@ -0,0 +1,40 @@
+/*
+ * StatusBarPopupRequest.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+import com.google.gwt.user.client.ui.MenuItem;
+
+public class StatusBarPopupRequest
+{
+ public StatusBarPopupRequest(StatusBarPopupMenu menu,
+ MenuItem defaultMenuItem)
+ {
+ menu_ = menu;
+ defaultMenuItem_ = defaultMenuItem;
+ }
+
+ public StatusBarPopupMenu getMenu()
+ {
+ return menu_;
+ }
+
+ public MenuItem getDefaultMenuItem()
+ {
+ return defaultMenuItem_;
+ }
+
+ private final StatusBarPopupMenu menu_;
+ private final MenuItem defaultMenuItem_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.java
new file mode 100644
index 0000000..8aa1a7c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.java
@@ -0,0 +1,109 @@
+/*
+ * StatusBarWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.status;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.widget.IsWidgetWithHeight;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+
+public class StatusBarWidget extends Composite
+ implements StatusBar, IsWidgetWithHeight
+{
+ private int height_;
+
+ interface Binder extends UiBinder<HorizontalPanel, StatusBarWidget>
+ {
+ }
+
+ public StatusBarWidget()
+ {
+ Binder binder = GWT.create(Binder.class);
+ HorizontalPanel hpanel = binder.createAndBindUi(this);
+ hpanel.setVerticalAlignment(HorizontalPanel.ALIGN_TOP);
+
+ hpanel.setCellWidth(hpanel.getWidget(2), "100%");
+
+ initWidget(hpanel);
+
+ height_ = 16;
+ }
+
+ public int getHeight()
+ {
+ return height_;
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ public StatusBarElement getPosition()
+ {
+ return position_;
+ }
+
+ public StatusBarElement getScope()
+ {
+ return scope_;
+ }
+
+ public StatusBarElement getLanguage()
+ {
+ return language_;
+ }
+
+ public void setScopeVisible(boolean visible)
+ {
+ scope_.setClicksEnabled(visible);
+ scope_.setContentsVisible(visible);
+ scopeIcon_.setVisible(visible);
+ }
+
+ public void setScopeType(int type)
+ {
+ if (type == StatusBar.SCOPE_FUNCTION)
+ scopeIcon_.setResource(StandardIcons.INSTANCE.function());
+ else if (type == StatusBar.SCOPE_CHUNK)
+ scopeIcon_.setResource(RES.chunk());
+ else if (type == StatusBar.SCOPE_SECTION)
+ scopeIcon_.setResource(RES.section());
+ else if (type == StatusBar.SCOPE_SLIDE)
+ scopeIcon_.setResource(RES.slide());
+ }
+
+ @UiField
+ StatusBarElementWidget position_;
+ @UiField
+ StatusBarElementWidget scope_;
+ @UiField
+ StatusBarElementWidget language_;
+ @UiField
+ Image scopeIcon_;
+
+ interface Resources extends ClientBundle
+ {
+ ImageResource chunk();
+ ImageResource section();
+ ImageResource slide();
+ }
+ private static Resources RES = GWT.create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.ui.xml
new file mode 100644
index 0000000..cade1c9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/StatusBarWidget.ui.xml
@@ -0,0 +1,67 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:sb='urn:import:org.rstudio.studio.client.workbench.views.source.editors.text.status'>
+
+ <ui:image field='statusBarTile' src='statusBarTile.png' repeatStyle='Horizontal'/>
+ <ui:data field='statusBarSeparator' src='statusBarSeparator.png'/>
+
+ <ui:style>
+ @url SEPARATOR statusBarSeparator;
+ @external gwt-Label;
+
+ @sprite .statusBar {
+ gwt-image: 'statusBarTile';
+ width: 100%;
+ font-size: 11px;
+ color: #3c474d;
+ }
+ .statusBar div {
+ display: inline;
+ height: 16px;
+ white-space: nowrap;
+ }
+ .statusBar .gwt-Label {
+ position: relative;
+ top: 1px;
+ }
+ .statusBar img {
+ position: relative;
+ top: 2px;
+ margin-left: 6px;
+ }
+ .statusBar img.scopeIcon {
+ top: 2px;
+ margin-left: 4px;
+ }
+ .statusBar .element {
+ display: block;
+ cursor: default;
+ padding-left: 5px;
+ padding-right: 6px;
+ background-image: SEPARATOR;
+ background-position: right 0;
+ background-repeat: no-repeat;
+ }
+ .statusBar .element.pos {
+ min-width: 45px;
+ text-align: center;
+ }
+ .statusBar .element.last {
+ background-image: none;
+ }
+ </ui:style>
+
+ <g:HorizontalPanel styleName="{style.statusBar}">
+ <sb:StatusBarElementWidget ui:field="position_"
+ styleName="{style.element} {style.pos}"
+ showArrows="false"/>
+ <g:Image ui:field="scopeIcon_"
+ styleName="{style.scopeIcon}"/>
+ <sb:StatusBarElementWidget ui:field="scope_"
+ styleName="{style.element}"
+ showArrows="true"/>
+ <sb:StatusBarElementWidget ui:field="language_"
+ styleName="{style.element} {style.last}"
+ showArrows="true"/>
+ </g:HorizontalPanel>
+</ui:UiBinder>
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/chunk.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/chunk.png
new file mode 100644
index 0000000..b4a9edf
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/chunk.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/section.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/section.png
new file mode 100644
index 0000000..8ec0616
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/section.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/slide.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/slide.png
new file mode 100644
index 0000000..ff50621
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/slide.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarSeparator.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarSeparator.png
new file mode 100644
index 0000000..d21396e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarSeparator.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarTile.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarTile.png
new file mode 100644
index 0000000..3b5380b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/statusBarTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/upDownArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/upDownArrow.png
new file mode 100644
index 0000000..c2ba15c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/status/upDownArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemeResources.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemeResources.java
new file mode 100644
index 0000000..e7a6251
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemeResources.java
@@ -0,0 +1,57 @@
+/*
+ * AceThemeResources.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.themes;
+
+import com.google.gwt.resources.client.ClientBundle;
+import org.rstudio.core.client.resources.StaticDataResource;
+
+public interface AceThemeResources extends ClientBundle
+{
+ @Source("textmate.css")
+ StaticDataResource textmate();
+
+ @Source("eclipse.css")
+ StaticDataResource eclipse();
+
+ @Source("idle_fingers.css")
+ StaticDataResource idle_fingers();
+
+ @Source("twilight.css")
+ StaticDataResource twilight();
+
+ @Source("cobalt.css")
+ StaticDataResource cobalt();
+
+ @Source("solarized_light.css")
+ StaticDataResource solarized();
+
+ @Source("solarized_dark.css")
+ StaticDataResource solarizedDark();
+
+ @Source("tomorrow.css")
+ StaticDataResource tomorrow();
+
+ @Source("tomorrow_night.css")
+ StaticDataResource tomorrow_night();
+
+ @Source("tomorrow_night_blue.css")
+ StaticDataResource tomorrow_night_blue();
+
+ @Source("tomorrow_night_bright.css")
+ StaticDataResource tomorrow_night_bright();
+
+ @Source("tomorrow_night_eighties.css")
+ StaticDataResource tomorrow_night_eighties();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemes.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemes.java
new file mode 100644
index 0000000..d87cd78
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/AceThemes.java
@@ -0,0 +1,119 @@
+/*
+ * AceThemes.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.themes;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.LinkElement;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.resources.StaticDataResource;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+ at Singleton
+public class AceThemes
+{
+ public static final String TEXTMATE = "TextMate";
+ public static final String ECLIPSE = "Eclipse";
+ public static final String TOMORROW = "Tomorrow";
+ public static final String COBALT = "Cobalt";
+ public static final String IDLE_FINGERS = "Idle Fingers";
+ public static final String TWILIGHT = "Twilight";
+ public static final String TOMORROW_NIGHT = "Tomorrow Night";
+ public static final String TOMORROW_NIGHT_BLUE = "Tomorrow Night Blue";
+ public static final String TOMORROW_NIGHT_BRIGHT = "Tomorrow Night Bright";
+ public static final String TOMORROW_NIGHT_80S = "Tomorrow Night 80's";
+ public static final String SOLARIZED = "Solarized";
+ public static final String SOLARIZED_DARK = "Solarized Dark";
+
+ @Inject
+ public AceThemes(AceThemeResources res,
+ final Provider<UIPrefs> prefs,
+ EventBus eventBus)
+ {
+ themes_ = new ArrayList<String>();
+ themesByName_ = new HashMap<String, String>();
+
+ addTheme(TEXTMATE, res.textmate());
+ addTheme(ECLIPSE, res.eclipse());
+ addTheme(TOMORROW, res.tomorrow());
+ addTheme(COBALT, res.cobalt());
+ addTheme(IDLE_FINGERS, res.idle_fingers());
+ addTheme(TWILIGHT, res.twilight());
+ addTheme(TOMORROW_NIGHT, res.tomorrow_night());
+ addTheme(TOMORROW_NIGHT_BLUE, res.tomorrow_night_blue());
+ addTheme(TOMORROW_NIGHT_BRIGHT, res.tomorrow_night_bright());
+ addTheme(TOMORROW_NIGHT_80S, res.tomorrow_night_eighties());
+ addTheme(SOLARIZED, res.solarized());
+ addTheme(SOLARIZED_DARK, res.solarizedDark());
+
+
+ prefs.get().theme().bind(new CommandWithArg<String>()
+ {
+ public void execute(String theme)
+ {
+ applyTheme(theme);
+ }
+ });
+ }
+
+ public String[] getThemeNames()
+ {
+ return themes_.toArray(new String[themes_.size()]);
+ }
+
+ public String getThemeUrl(String name)
+ {
+ String url = themesByName_.get(name);
+ return url != null ? url : themesByName_.get(defaultThemeName_);
+ }
+
+ private void addTheme(String name, StaticDataResource resource)
+ {
+ themes_.add(name);
+ themesByName_.put(name, resource.getSafeUri().asString());
+ }
+
+ private void applyTheme(String themeName)
+ {
+ if (currentStyleEl_ != null)
+ currentStyleEl_.removeFromParent();
+
+ currentStyleEl_ = Document.get().createLinkElement();
+ currentStyleEl_.setType("text/css");
+ currentStyleEl_.setRel("stylesheet");
+ currentStyleEl_.setHref(getThemeUrl(themeName));
+ Document.get().getBody().appendChild(
+ currentStyleEl_);
+ }
+
+ public String getEffectiveThemeName(String themeName)
+ {
+ return themesByName_.containsKey(themeName)
+ ? themeName
+ : defaultThemeName_;
+ }
+
+ private final ArrayList<String> themes_;
+ private final HashMap<String, String> themesByName_;
+ private final String defaultThemeName_ = "TextMate";
+
+ private LinkElement currentStyleEl_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/chrome.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/chrome.css
new file mode 100644
index 0000000..74c12fc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/chrome.css
@@ -0,0 +1,170 @@
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #ebebeb;
+ color: #333;
+ overflow : hidden;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_text-layer {
+}
+
+.ace_cursor {
+ border-left: 2px solid black;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid black;
+}
+
+.ace_line .ace_invisible {
+ color: rgb(191, 191, 191);
+}
+
+.ace_line .ace_constant.ace_buildin {
+ color: rgb(88, 72, 246);
+}
+
+.ace_line .ace_constant.ace_language {
+ color: rgb(88, 92, 246);
+}
+
+.ace_line .ace_constant.ace_library {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_invalid {
+ background-color: rgb(153, 0, 0);
+ color: white;
+}
+
+.ace_line .ace_fold {
+}
+
+.ace_line .ace_support.ace_function {
+ color: rgb(60, 76, 114);
+}
+
+.ace_line .ace_support.ace_constant {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_support.ace_type,
+.ace_line .ace_support.ace_class {
+ color: rgb(109, 121, 222);
+}
+
+.ace_variable.ace_parameter {
+ font-style:italic;
+color:#FD971F;
+}
+.ace_line .ace_keyword.ace_operator {
+ color: rgb(104, 118, 135);
+}
+
+.ace_line .ace_comment {
+ color: #236e24;
+}
+
+.ace_line .ace_comment.ace_doc {
+ color: #236e24;
+}
+
+.ace_line .ace_comment.ace_doc.ace_tag {
+ color: #236e24;
+}
+
+.ace_line .ace_constant.ace_numeric {
+ color: rgb(0, 0, 205);
+}
+
+.ace_line .ace_variable {
+ color: rgb(49, 132, 149);
+}
+
+.ace_line .ace_xml_pe {
+ color: rgb(104, 104, 91);
+}
+
+.ace_entity.ace_name.ace_function {
+ color: #0000A2;
+}
+
+.ace_markup.ace_markupine {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color: rgb(12, 7, 255);
+}
+
+.ace_markup.ace_list {
+ color:rgb(185, 6, 144);
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgb(181, 213, 255);
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(252, 255, 0);
+}
+
+.ace_marker-layer .ace_stack {
+ background: rgb(164, 229, 101);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgb(192, 192, 192);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(0, 0, 0, 0.07);
+}
+
+.ace_marker-layer .ace_selected_word {
+ background: rgb(250, 250, 255);
+ border: 1px solid rgb(200, 200, 250);
+}
+
+.ace_storage,
+.ace_line .ace_keyword,
+.ace_meta.ace_tag {
+ color: rgb(147, 15, 128);
+}
+
+.ace_string.ace_regex {
+ color: rgb(255, 0, 0)
+}
+
+.ace_line .ace_string{
+ color: #1A1AA6;
+}
+
+.ace_entity.ace_other.ace_attribute-name{
+ color: #994409;
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+
+.nocolor.ace_editor .ace_line span {color: rgb(104, 118, 135) !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgb(192, 192, 192);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(242, 242, 242);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(223, 223, 223);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(242, 242, 242); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds.css
new file mode 100644
index 0000000..942b926
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds.css
@@ -0,0 +1,128 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #ebebeb;
+ color: #333;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #FFFFFF;
+}
+
+.ace_text-layer {
+ color: #000000;
+}
+
+.ace_cursor {
+ border-left: 2px solid #000000;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #000000;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #BDD5FC;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #FFFFFF;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(255, 255, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #BFBFBF;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #FFFBD1;
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #BDD5FC;
+}
+
+.ace_invisible {
+ color: #BFBFBF;
+}
+
+.ace_keyword, .ace_meta {
+ color:#AF956F;
+}
+
+.ace_keyword.ace_operator {
+ color:#484848;
+}
+
+.ace_constant.ace_language {
+ color:#39946A;
+}
+
+.ace_constant.ace_numeric {
+ color:#46A609;
+}
+
+.ace_invalid {
+ background-color:#FF002A;
+}
+
+.ace_fold {
+ background-color: #AF956F;
+ border-color: #000000;
+}
+
+.ace_support.ace_function {
+ color:#C52727;
+}
+
+.ace_storage {
+ color:#C52727;
+}
+
+.ace_string {
+ color:#5D90CD;
+}
+
+.ace_comment {
+ color:#BCC8BA;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#606060;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#AF956F !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #BFBFBF;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(204, 204, 204);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(127, 127, 127);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(204, 204, 204); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds_midnight.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds_midnight.css
new file mode 100644
index 0000000..522dff0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/clouds_midnight.css
@@ -0,0 +1,129 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #232323;
+ color: #929292;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #232323;
+}
+
+.ace_scroller {
+ background-color: #191919;
+}
+
+.ace_text-layer {
+ color: #929292;
+}
+
+.ace_cursor {
+ border-left: 2px solid #7DA5DC;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #7DA5DC;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #000000;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #191919;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #BFBFBF;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(215, 215, 215, 0.031);
+}
+
+.ace_gutter_active_line {
+ background-color: rgba(215, 215, 215, 0.031);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #000000;
+}
+
+.ace_invisible {
+ color: #BFBFBF;
+}
+
+.ace_keyword, .ace_meta {
+ color:#927C5D;
+}
+
+.ace_keyword.ace_operator {
+ color:#4B4B4B;
+}
+
+.ace_constant.ace_language {
+ color:#39946A;
+}
+
+.ace_constant.ace_numeric {
+ color:#46A609;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#E92E2E;
+}
+
+.ace_fold {
+ background-color: #927C5D;
+ border-color: #929292;
+}
+
+.ace_support.ace_function {
+ color:#E92E2E;
+}
+
+.ace_storage {
+ color:#E92E2E;
+}
+
+.ace_string {
+ color:#5D90CD;
+}
+
+.ace_comment {
+ color:#3C403B;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#606060;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWOQlJT8z1BeXv4fAA2KA6+h9Z+2AAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#927C5D !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #BFBFBF;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(49, 49, 49);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(85, 85, 85);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(140, 123, 40);}
+.ace_console_error { background-color: rgb(49, 49, 49); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/cobalt.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/cobalt.css
new file mode 100644
index 0000000..028d478
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/cobalt.css
@@ -0,0 +1,155 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #011e3a;
+ color: #fff;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #011e3a;
+}
+
+.ace_scroller {
+ background-color: #002240;
+}
+
+.ace_text-layer {
+ color: #FFFFFF;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(179, 101, 57, 0.75);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #002240;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(127, 111, 19);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(0, 0, 0, 0.35);
+}
+
+.ace_gutter_active_line {
+ background-color : rgba(0, 0, 0, 0.35);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(179, 101, 57, 0.75);
+}
+
+.ace_invisible {
+ color: rgba(255, 255, 255, 0.15);
+}
+
+.ace_keyword, .ace_meta {
+ color:#FF9D00;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#FF628C;
+}
+
+.ace_constant.ace_character, {
+ color:#FF628C;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#FF628C;
+}
+
+.ace_invalid {
+ color:#F8F8F8;
+background-color:#800F00;
+}
+
+.ace_support {
+ color:#80FFBB;
+}
+
+.ace_support.ace_constant {
+ color:#EB939A;
+}
+
+.ace_fold {
+ background-color: #FF9D00;
+ border-color: #FFFFFF;
+}
+
+.ace_support.ace_function {
+ color:#FFB054;
+}
+
+.ace_storage {
+ color:#FFEE80;
+}
+
+.ace_string.ace_regexp {
+ color:#80FFC2;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#0088FF;
+}
+
+.ace_variable {
+ color:#CCCCCC;
+}
+
+.ace_variable.ace_language {
+ color:#FF80E1;
+}
+
+.ace_meta.ace_tag {
+ color:#9EFFFF;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#C8E4FD;
+background-color:#001221;
+}
+
+.ace_markup.ace_list {
+ background-color:#130D26;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgUHL4zzBz5sz/AA80BCzv+WXhAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#FF9D00 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(255, 255, 255, 0.15);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(50, 78, 102);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(127, 144, 159);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(127, 128, 60);}
+.ace_console_error { background-color: rgb(50, 78, 102); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/crimson_editor.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/crimson_editor.css
new file mode 100644
index 0000000..13ecf4c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/crimson_editor.css
@@ -0,0 +1,161 @@
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #ebebeb;
+ color: #333;
+ overflow : hidden;
+}
+
+.ace_gutter-layer {
+ width: 100%;
+ text-align: right;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_text-layer {
+ color: rgb(64, 64, 64);
+}
+
+.ace_cursor {
+ border-left: 2px solid black;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid black;
+}
+
+.ace_line .ace_invisible {
+ color: rgb(191, 191, 191);
+}
+
+.ace_line .ace_identifier {
+ color: black;
+}
+
+.ace_line .ace_keyword {
+ color: blue;
+}
+
+.ace_line .ace_constant.ace_buildin {
+ color: rgb(88, 72, 246);
+}
+
+.ace_line .ace_constant.ace_language {
+ color: rgb(255, 156, 0);
+}
+
+.ace_line .ace_constant.ace_library {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_invalid {
+ text-decoration: line-through;
+ color: rgb(224, 0, 0);
+}
+
+.ace_line .ace_fold {
+}
+
+.ace_line .ace_support.ace_function {
+ color: rgb(192, 0, 0);
+}
+
+.ace_line .ace_support.ace_constant {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_support.ace_type,
+.ace_line .ace_support.ace_class {
+ color: rgb(109, 121, 222);
+}
+
+.ace_line .ace_keyword.ace_operator {
+ color: rgb(49, 132, 149);
+}
+
+.ace_line .ace_string {
+ color: rgb(128, 0, 128);
+}
+
+.ace_line .ace_comment {
+ color: rgb(76, 136, 107);
+}
+
+.ace_line .ace_comment.ace_doc {
+ color: rgb(0, 102, 255);
+}
+
+.ace_line .ace_comment.ace_doc.ace_tag {
+ color: rgb(128, 159, 191);
+}
+
+.ace_line .ace_constant.ace_numeric {
+ color: rgb(0, 0, 64);
+}
+
+.ace_line .ace_variable {
+ color: rgb(0, 64, 128);
+}
+
+.ace_line .ace_xml_pe {
+ color: rgb(104, 104, 91);
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgb(181, 213, 255);
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(252, 255, 0);
+}
+
+.ace_marker-layer .ace_stack {
+ background: rgb(164, 229, 101);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgb(192, 192, 192);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgb(232, 242, 254);
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_meta.ace_tag {
+ color:rgb(28, 2, 255);
+}
+
+.ace_marker-layer .ace_selected_word {
+ background: rgb(250, 250, 255);
+ border: 1px solid rgb(200, 200, 250);
+}
+
+.ace_string.ace_regex {
+ color: rgb(192, 0, 192);
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color: blue !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgb(192, 192, 192);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(216, 216, 216);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(159, 159, 159);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(216, 216, 216); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dawn.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dawn.css
new file mode 100644
index 0000000..31b332b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dawn.css
@@ -0,0 +1,159 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #ebebeb;
+ color: #333;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #F9F9F9;
+}
+
+.ace_text-layer {
+ color: #080808;
+}
+
+.ace_cursor {
+ border-left: 2px solid #000000;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #000000;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(39, 95, 255, 0.30);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #F9F9F9;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(255, 255, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(75, 75, 126, 0.50);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(36, 99, 180, 0.12);
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(39, 95, 255, 0.30);
+}
+
+.ace_invisible {
+ color: rgba(75, 75, 126, 0.50);
+}
+
+.ace_keyword, .ace_meta {
+ color:#794938;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#811F24;
+}
+
+.ace_constant.ace_character, {
+ color:#811F24;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#811F24;
+}
+
+.ace_invalid.ace_illegal {
+ text-decoration:underline;
+font-style:italic;
+color:#F8F8F8;
+background-color:#B52A1D;
+}
+
+.ace_invalid.ace_deprecated {
+ text-decoration:underline;
+font-style:italic;
+color:#B52A1D;
+}
+
+.ace_support {
+ color:#691C97;
+}
+
+.ace_support.ace_constant {
+ color:#B4371F;
+}
+
+.ace_fold {
+ background-color: #794938;
+ border-color: #080808;
+}
+
+.ace_support.ace_function {
+ color:#693A17;
+}
+
+.ace_storage {
+ font-style:italic;
+color:#A71D5D;
+}
+
+.ace_string {
+ color:#0B6125;
+}
+
+.ace_string.ace_regexp {
+ color:#CF5628;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#5A525F;
+}
+
+.ace_variable {
+ color:#234A97;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#19356D;
+}
+
+.ace_markup.ace_list {
+ color:#693A17;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4+fPnf4ZVq1b9BwAkVQboFQv98gAAAABJRU5ErkJggg==) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#794938 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(75, 75, 126, 0.50);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(200, 200, 200);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(128, 128, 128);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(252, 235, 152);}
+.ace_console_error { background-color: rgb(200, 200, 200); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/demo.html b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/demo.html
new file mode 100644
index 0000000..812f46f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/demo.html
@@ -0,0 +1,2 @@
+<link rel="stylesheet" type="text/css" href="pastel_on_dark.css"/>
+<div class="gwt-HTML G1dpdwhmPF ace_editor" style="width: 100%; height: 100%; position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; "><div class="ace_gutter"><div class="ace_layer ace_gutter-layer" style="margin-top: 0px; height: 347px; "><div class="ace_gutter-cell " title="" style="height:16px;">25</div><div class="ace_gutter-cell " title="" style="height:16px;">26</div><div class="ace_gutter-cell " title="" style="height:16px;">27</div><div class="ace_gutter-cell " title=" [...]
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dreamweaver.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dreamweaver.css
new file mode 100644
index 0000000..dbb1e23
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/dreamweaver.css
@@ -0,0 +1,192 @@
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #e8e8e8;
+ color: #333;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_fold {
+ background-color: #00F;
+}
+
+.ace_text-layer {
+}
+
+.ace_cursor {
+ border-left: 2px solid black;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid black;
+}
+
+.ace_line .ace_invisible {
+ color: rgb(191, 191, 191);
+}
+
+.ace_line .ace_storage,
+.ace_line .ace_keyword {
+ color: blue;
+}
+
+.ace_line .ace_constant.ace_buildin {
+ color: rgb(88, 72, 246);
+}
+
+.ace_line .ace_constant.ace_language {
+ color: rgb(88, 92, 246);
+}
+
+.ace_line .ace_constant.ace_library {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_invalid {
+ background-color: rgb(153, 0, 0);
+ color: white;
+}
+
+.ace_line .ace_support.ace_function {
+ color: rgb(60, 76, 114);
+}
+
+.ace_line .ace_support.ace_constant {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_support.ace_type,
+.ace_line .ace_support.ace_class {
+ color: #009;
+}
+
+.ace_line .ace_support.ace_php_tag {
+ color: #f00;
+}
+
+.ace_line .ace_keyword.ace_operator {
+ color: rgb(104, 118, 135);
+}
+
+.ace_line .ace_string {
+ color: #00F;
+}
+
+.ace_line .ace_comment {
+ color: rgb(76, 136, 107);
+}
+
+.ace_line .ace_comment.ace_doc {
+ color: rgb(0, 102, 255);
+}
+
+.ace_line .ace_comment.ace_doc.ace_tag {
+ color: rgb(128, 159, 191);
+}
+
+.ace_line .ace_constant.ace_numeric {
+ color: rgb(0, 0, 205);
+}
+
+.ace_line .ace_variable {
+ color: #06F
+}
+
+.ace_line .ace_xml_pe {
+ color: rgb(104, 104, 91);
+}
+
+.ace_entity.ace_name.ace_function {
+ color: #00F;
+}
+
+.ace_markup.ace_markupine {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color: rgb(12, 7, 255);
+}
+
+.ace_markup.ace_list {
+ color:rgb(185, 6, 144);
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgb(181, 213, 255);
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(252, 255, 0);
+}
+
+.ace_marker-layer .ace_stack {
+ background: rgb(164, 229, 101);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgb(192, 192, 192);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(0, 0, 0, 0.07);
+}
+
+.ace_marker-layer .ace_selected_word {
+ background: rgb(250, 250, 255);
+ border: 1px solid rgb(200, 200, 250);
+}
+
+.ace_meta.ace_tag {
+ color:#009;
+}
+
+.ace_meta.ace_tag.ace_anchor {
+ color:#060;
+}
+
+.ace_meta.ace_tag.ace_form {
+ color:#F90;
+}
+
+.ace_meta.ace_tag.ace_image {
+ color:#909;
+}
+
+.ace_meta.ace_tag.ace_script {
+ color:#900;
+}
+
+.ace_meta.ace_tag.ace_style {
+ color:#909;
+}
+
+.ace_meta.ace_tag.ace_table {
+ color:#099;
+}
+
+.ace_string.ace_regex {
+ color: rgb(255, 0, 0)
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color: blue !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgb(192, 192, 192);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(242, 242, 242);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(223, 223, 223);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(242, 242, 242); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/eclipse.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/eclipse.css
new file mode 100644
index 0000000..60703e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/eclipse.css
@@ -0,0 +1,116 @@
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #ebebeb;
+ border-right: 1px solid rgb(159, 159, 159);
+ color: rgb(136, 136, 136);
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #ebebeb;
+}
+
+.ace_fold {
+ background-color: rgb(60, 76, 114);
+}
+
+.ace_text-layer {
+}
+
+.ace_cursor {
+ border-left: 2px solid black;
+}
+
+.ace_line .ace_storage,
+.ace_line .ace_keyword,
+.ace_line .ace_variable {
+ color: rgb(127, 0, 85);
+}
+
+.ace_line .ace_constant.ace_buildin {
+ color: rgb(88, 72, 246);
+}
+
+.ace_line .ace_constant.ace_library {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_function {
+ color: rgb(60, 76, 114);
+}
+
+.ace_line .ace_string {
+ color: rgb(42, 0, 255);
+}
+
+.ace_line .ace_comment {
+ color: rgb(63, 127, 95);
+}
+
+.ace_line .ace_comment.ace_doc {
+ color: rgb(63, 95, 191);
+}
+
+.ace_line .ace_comment.ace_doc.ace_tag {
+ color: rgb(127, 159, 191);
+}
+
+.ace_line .ace_constant.ace_numeric {
+}
+
+.ace_line .ace_tag {
+ color: rgb(63, 127, 127);
+}
+
+.ace_line .ace_type {
+ color: rgb(127, 0, 127);
+}
+
+.ace_line .ace_xml_pe {
+ color: rgb(104, 104, 91);
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgb(181, 213, 255);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgb(192, 192, 192);
+}
+
+.ace_line .ace_meta.ace_tag {
+ color:rgb(63, 127, 127);
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:rgb(127, 0, 127);
+}
+.ace_marker-layer .ace_step {
+ background: rgb(255, 255, 0);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgb(232, 242, 254);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgb(181, 213, 255);
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color: rgb(127, 0, 85) !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgb(192, 192, 192);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(229, 204, 221);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(191, 127, 170);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(229, 204, 221); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/idle_fingers.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/idle_fingers.css
new file mode 100644
index 0000000..7156097
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/idle_fingers.css
@@ -0,0 +1,147 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #3b3b3b;
+ color: #fff;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #3b3b3b;
+}
+
+.ace_scroller {
+ background-color: #323232;
+}
+
+.ace_text-layer {
+ color: #FFFFFF;
+}
+
+.ace_cursor {
+ border-left: 2px solid #91FF00;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #91FF00;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(90, 100, 126, 0.88);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #323232;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #404040;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #353637;
+}
+
+.ace_gutter_active_line {
+ background-color: #353637;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(90, 100, 126, 0.88);
+}
+
+.ace_invisible {
+ color: #404040;
+}
+
+.ace_keyword, .ace_meta {
+ color:#CC7833;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#6C99BB;
+}
+
+.ace_constant.ace_character, {
+ color:#6C99BB;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#6C99BB;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#FF0000;
+}
+
+.ace_support.ace_constant {
+ color:#6C99BB;
+}
+
+.ace_fold {
+ background-color: #CC7833;
+ border-color: #FFFFFF;
+}
+
+.ace_support.ace_function {
+ color:#B83426;
+}
+
+.ace_variable.ace_parameter {
+ font-style:italic;
+}
+
+.ace_string {
+ color:#A5C261;
+}
+
+.ace_string.ace_regexp {
+ color:#CCCC33;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#BC9458;
+}
+
+.ace_meta.ace_tag {
+ color:#FFE5BB;
+}
+
+.ace_entity.ace_name {
+ color:#FFC66D;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_collab.ace_user1 {
+ color:#323232;
+ background-color:#FFF980;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWMwMjL6zzBz5sz/ABEUBGCqhK6UAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#CC7833 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #404040;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(90, 90, 90);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(152, 152, 152);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(152, 136, 53);}
+.ace_console_error { background-color: rgb(90, 90, 90); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/kr_theme.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/kr_theme.css
new file mode 100644
index 0000000..c89cb19
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/kr_theme.css
@@ -0,0 +1,150 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #1c1917;
+ color: #FCFFE0;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #1c1917;
+}
+
+.ace_scroller {
+ background-color: #0B0A09;
+}
+
+.ace_text-layer {
+ color: #FCFFE0;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FF9900;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FF9900;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(170, 0, 255, 0.45);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #0B0A09;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(255, 177, 111, 0.32);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #38403D;
+}
+
+.ace_gutter_active_line {
+ background-color : #38403D;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(170, 0, 255, 0.45);
+}
+
+.ace_invisible {
+ color: rgba(255, 177, 111, 0.32);
+}
+
+.ace_keyword, .ace_meta {
+ color:#949C8B;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:rgba(210, 117, 24, 0.76);
+}
+
+.ace_constant.ace_character, {
+ color:rgba(210, 117, 24, 0.76);
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:rgba(210, 117, 24, 0.76);
+}
+
+.ace_invalid {
+ color:#F8F8F8;
+background-color:#A41300;
+}
+
+.ace_support {
+ color:#9FC28A;
+}
+
+.ace_support.ace_constant {
+ color:#C27E66;
+}
+
+.ace_fold {
+ background-color: #949C8B;
+ border-color: #FCFFE0;
+}
+
+.ace_support.ace_function {
+ color:#85873A;
+}
+
+.ace_storage {
+ color:#FFEE80;
+}
+
+.ace_string.ace_regexp {
+ color:rgba(125, 255, 192, 0.65);
+}
+
+.ace_comment {
+ font-style:italic;
+color:#706D5B;
+}
+
+.ace_variable {
+ color:#D1A796;
+}
+
+.ace_variable.ace_language {
+ color:#FF80E1;
+}
+
+.ace_meta.ace_tag {
+ color:#BABD9C;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_list {
+ background-color:#0F0040;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPg5uL8zzBz5sz/AA1WA+hUYIqjAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#949C8B !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(255, 177, 111, 0.32);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(59, 58, 51);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(131, 132, 116);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(133, 116, 32);}
+.ace_console_error { background-color: rgb(59, 58, 51); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore.css
new file mode 100644
index 0000000..98733e7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore.css
@@ -0,0 +1,150 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #202020;
+ color: #E6E1DC;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #161616;
+}
+
+.ace_text-layer {
+ color: #E6E1DC;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #454545;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #161616;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #404040;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #333435;
+}
+
+.ace_gutter_active_line {
+ background-color : #333435;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #454545;
+}
+
+.ace_invisible {
+ color: #404040;
+}
+
+.ace_keyword, .ace_meta {
+ color:#FC6F09;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#1EDAFB;
+}
+
+.ace_constant.ace_character, {
+ color:#1EDAFB;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#1EDAFB;
+}
+
+.ace_constant.ace_language {
+ color:#FDC251;
+}
+
+.ace_constant.ace_library {
+ color:#8DFF0A;
+}
+
+.ace_constant.ace_numeric {
+ color:#58C554;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#990000;
+}
+
+.ace_support.ace_constant {
+ color:#8DFF0A;
+}
+
+.ace_fold {
+ background-color: #FC6F09;
+ border-color: #E6E1DC;
+}
+
+.ace_support.ace_function {
+ color:#FC6F09;
+}
+
+.ace_storage {
+ color:#FC6F09;
+}
+
+.ace_string {
+ color:#8DFF0A;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#AD2EA4;
+}
+
+.ace_meta.ace_tag {
+ color:#FC6F09;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#FFFF89;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWMQExP7zzBz5sz/AA50BAyDznYhAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#FC6F09 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #404040;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(63, 62, 61);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(126, 123, 121);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(138, 122, 39);}
+.ace_console_error { background-color: rgb(63, 62, 61); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore_soft.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore_soft.css
new file mode 100644
index 0000000..b5a3e30
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/merbivore_soft.css
@@ -0,0 +1,156 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #262424;
+ color: #E6E1DC;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #262424;
+}
+
+.ace_scroller {
+ background-color: #1C1C1C;
+}
+
+.ace_text-layer {
+ color: #E6E1DC;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #494949;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #1C1C1C;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #404040;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #333435;
+}
+
+.ace_gutter_active_line {
+ background-color: #333435;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #494949;
+}
+
+.ace_invisible {
+ color: #404040;
+}
+
+.ace_keyword, .ace_meta {
+ color:#FC803A;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#68C1D8;
+}
+
+.ace_constant.ace_character, {
+ color:#68C1D8;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#68C1D8;
+}
+
+.ace_constant.ace_language {
+ color:#E1C582;
+}
+
+.ace_constant.ace_library {
+ color:#8EC65F;
+}
+
+.ace_constant.ace_numeric {
+ color:#7FC578;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#FE3838;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#FFFFFF;
+background-color:#FE3838;
+}
+
+.ace_support.ace_constant {
+ color:#8EC65F;
+}
+
+.ace_fold {
+ background-color: #FC803A;
+ border-color: #E6E1DC;
+}
+
+.ace_storage {
+ color:#FC803A;
+}
+
+.ace_string {
+ color:#8EC65F;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#AC4BB8;
+}
+
+.ace_meta {
+ font-style:italic;
+color:#AC4BB8;
+}
+
+.ace_meta.ace_tag {
+ color:#FC803A;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#EAF1A3;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWOQkZH5zzBz5sz/AA8EBB6crd1rAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#FC803A !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #404040;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(68, 67, 66);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(129, 126, 124);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(141, 125, 42);}
+.ace_console_error { background-color: rgb(68, 67, 66); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/mono_industrial.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/mono_industrial.css
new file mode 100644
index 0000000..962b68f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/mono_industrial.css
@@ -0,0 +1,158 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #1d2521;
+ color: #fff;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #222C28;
+}
+
+.ace_text-layer {
+ color: #FFFFFF;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(145, 153, 148, 0.40);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #222C28;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(102, 108, 104, 0.50);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(12, 13, 12, 0.25);
+}
+
+.ace_gutter_active_line {
+ background-color: rgba(12, 13, 12, 0.25);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(145, 153, 148, 0.40);
+}
+
+.ace_invisible {
+ color: rgba(102, 108, 104, 0.50);
+}
+
+.ace_keyword, .ace_meta {
+ color:#A39E64;
+}
+
+.ace_keyword.ace_operator {
+ color:#A8B3AB;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#E98800;
+}
+
+.ace_constant.ace_character, {
+ color:#E98800;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#E98800;
+}
+
+.ace_constant.ace_numeric {
+ color:#E98800;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:rgba(153, 0, 0, 0.68);
+}
+
+.ace_support.ace_constant {
+ color:#C87500;
+}
+
+.ace_fold {
+ background-color: #A8B3AB;
+ border-color: #FFFFFF;
+}
+
+.ace_support.ace_function {
+ color:#588E60;
+}
+
+.ace_storage {
+ color:#C23B00;
+}
+
+.ace_variable {
+ color:#A8B3AB;
+}
+
+.ace_variable.ace_parameter {
+ color:#648BD2;
+}
+
+.ace_comment {
+ color:#666C68;
+background-color:#151C19;
+}
+
+.ace_variable.ace_language {
+ color:#648BD2;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#909993;
+}
+
+.ace_entity.ace_name {
+ color:#5778B6;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#A8B3AB;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQ0tH4zzBz5sz/ABAOBECKH+evAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#A39E64 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(102, 108, 104, 0.50);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(78, 86, 82);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(144, 149, 147);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(144, 133, 48);}
+.ace_console_error { background-color: rgb(78, 86, 82); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/monokai.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/monokai.css
new file mode 100644
index 0000000..5e20933
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/monokai.css
@@ -0,0 +1,155 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #2f3129;
+ color: #f1f1f1;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #272822;
+}
+
+.ace_text-layer {
+ color: #F8F8F2;
+}
+
+.ace_cursor {
+ border-left: 2px solid #F8F8F0;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #F8F8F0;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #49483E;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #272822;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #49483E;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #49483E;
+}
+.ace_gutter_active_line {
+ background-color: #191916;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #49483E;
+}
+
+.ace_invisible {
+ color: #49483E;
+}
+
+.ace_keyword, .ace_meta {
+ color:#F92672;
+}
+
+.ace_constant.ace_language {
+ color:#AE81FF;
+}
+
+.ace_constant.ace_numeric {
+ color:#AE81FF;
+}
+
+.ace_constant.ace_other {
+ color:#AE81FF;
+}
+
+.ace_invalid {
+ color:#F8F8F0;
+background-color:#F92672;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#F8F8F0;
+background-color:#AE81FF;
+}
+
+.ace_support.ace_constant {
+ color:#66D9EF;
+}
+
+.ace_fold {
+ background-color: #A6E22E;
+ border-color: #F8F8F2;
+}
+
+.ace_support.ace_function {
+ color:#66D9EF;
+}
+
+.ace_storage {
+ color:#F92672;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ font-style:italic;
+color:#66D9EF;
+}
+
+.ace_variable {
+ color:#A6E22E;
+}
+
+.ace_variable.ace_parameter {
+ font-style:italic;
+color:#FD971F;
+}
+
+.ace_string {
+ color:#E6DB74;
+}
+
+.ace_comment {
+ color:#75715E;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#A6E22E;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#A6E22E;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQ11D6z7Bq1ar/ABCKBG6g04U2AAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#F92672 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #49483E;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(80, 81, 75);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(143, 144, 138);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(147, 131, 45);}
+.ace_console_error { background-color: rgb(80, 81, 75); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/pastel_on_dark.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/pastel_on_dark.css
new file mode 100644
index 0000000..ed8e3eb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/pastel_on_dark.css
@@ -0,0 +1,160 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #353030;
+ color: #8F938F;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #353030;
+}
+
+.ace_scroller {
+ background-color: #2C2828;
+}
+
+.ace_text-layer {
+ color: #8F938F;
+}
+
+.ace_cursor {
+ border-left: 2px solid #A7A7A7;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #A7A7A7;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(221, 240, 255, 0.20);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #2C2828;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(255, 255, 255, 0.25);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(255, 255, 255, 0.031);
+}
+
+.ace_gutter_active_line {
+ background-color: rgba(255, 255, 255, 0.031);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(221, 240, 255, 0.20);
+}
+
+.ace_invisible {
+ color: rgba(255, 255, 255, 0.25);
+}
+
+.ace_keyword, .ace_meta {
+ color:#757aD8;
+}
+
+.ace_keyword.ace_operator {
+ color:#797878;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#4FB7C5;
+}
+
+.ace_constant.ace_character, {
+ color:#4FB7C5;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#4FB7C5;
+}
+
+.ace_constant.ace_language {
+ color:#DE8E30;
+}
+
+.ace_constant.ace_numeric {
+ color:#CCCCCC;
+}
+
+.ace_invalid {
+ color:#F8F8F8;
+background-color:rgba(86, 45, 86, 0.75);
+}
+
+.ace_invalid.ace_illegal {
+ color:#F8F8F8;
+background-color:rgba(86, 45, 86, 0.75);
+}
+
+.ace_invalid.ace_deprecated {
+ text-decoration:underline;
+font-style:italic;
+color:#D2A8A1;
+}
+
+.ace_fold {
+ background-color: #757aD8;
+ border-color: #8F938F;
+}
+
+.ace_support.ace_function {
+ color:#AEB2F8;
+}
+
+.ace_string {
+ color:#66A968;
+}
+
+.ace_string.ace_regexp {
+ color:#E9C062;
+}
+
+.ace_comment {
+ color:#A6C6FF;
+}
+
+.ace_variable {
+ color:#BEBF55;
+}
+
+.ace_variable.ace_language {
+ color:#C1C144;
+}
+
+.ace_xml_pe {
+ color:#494949;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0dD4z9DR0fEfAA+vBBPqhbn1AAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#757aD8 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(255, 255, 255, 0.25);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(63, 61, 60);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(93, 93, 91);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(149, 131, 48);}
+.ace_console_error { background-color: rgb(63, 61, 60); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_dark.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_dark.css
new file mode 100644
index 0000000..9217c58
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_dark.css
@@ -0,0 +1,141 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #01313f;
+ color: #d0edf7;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #002B36;
+}
+
+.ace_text-layer {
+ color: #93A1A1;
+}
+
+.ace_cursor {
+ border-left: 2px solid #D30102;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #D30102;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #073642;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #002B36;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(147, 161, 161, 0.50);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #073642;
+}
+
+.ace_gutter_active_line {
+ background-color: #0d3440;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #073642;
+}
+
+.ace_invisible {
+ color: rgba(147, 161, 161, 0.50);
+}
+
+.ace_keyword, .ace_meta {
+ color:#859900;
+}
+
+.ace_constant.ace_language {
+ color:#B58900;
+}
+
+.ace_constant.ace_numeric {
+ color:#D33682;
+}
+
+.ace_constant.ace_other {
+ color:#CB4B16;
+}
+
+.ace_fold {
+ background-color: #268BD2;
+ border-color: #93A1A1;
+}
+
+.ace_support.ace_function {
+ color:#268BD2;
+}
+
+.ace_storage {
+ color:#93A1A1;
+}
+
+.ace_variable {
+ color:#268BD2;
+}
+
+.ace_string {
+ color:#2AA198;
+}
+
+.ace_string.ace_regexp {
+ color:#D30102;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#657B83;
+}
+
+.ace_variable.ace_language {
+ color:#268BD2;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#93A1A1;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#268BD2;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNg0Db7zzBz5sz/AA82BCv7wOIDAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#859900 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(147, 161, 161, 0.50);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(29, 66, 75);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(73, 102, 107);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(127, 132, 55);}
+.ace_console_error { background-color: rgb(29, 66, 75); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_light.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_light.css
new file mode 100644
index 0000000..debc833
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/solarized_light.css
@@ -0,0 +1,140 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #fbf1d3;
+ color: #333;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_scroller {
+ background-color: #FDF6E3;
+}
+
+.ace_text-layer {
+ color: #586E75;
+}
+
+.ace_cursor {
+ border-left: 2px solid #000000;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #000000;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #073642;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #FDF6E3;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(255, 255, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(147, 161, 161, 0.50);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #EEE8D5;
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #073642;
+}
+
+.ace_invisible {
+ color: rgba(147, 161, 161, 0.50);
+}
+
+.ace_keyword, .ace_meta {
+ color:#859900;
+}
+
+.ace_constant.ace_language {
+ color:#B58900;
+}
+
+.ace_constant.ace_numeric {
+ color:#D33682;
+}
+
+.ace_constant.ace_other {
+ color:#CB4B16;
+}
+
+.ace_fold {
+ background-color: #268BD2;
+ border-color: #586E75;
+}
+
+.ace_support.ace_function {
+ color:#268BD2;
+}
+
+.ace_storage {
+ color:#073642;
+}
+
+.ace_variable {
+ color:#268BD2;
+}
+
+.ace_string {
+ color:#2AA198;
+}
+
+.ace_string.ace_regexp {
+ color:#D30102;
+}
+
+.ace_comment {
+ color:#93A1A1;
+}
+
+.ace_variable.ace_language {
+ color:#268BD2;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#93A1A1;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#268BD2;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4++3xf4ZVq1b9BwAjxwbT1g3hiwAAAABJRU5ErkJggg==) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#859900 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(147, 161, 161, 0.50);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(220, 218, 205);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(170, 178, 172);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(254, 234, 141);}
+.ace_console_error { background-color: rgb(220, 218, 205); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/textmate.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/textmate.css
new file mode 100644
index 0000000..735c599
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/textmate.css
@@ -0,0 +1,176 @@
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #f0f0f0;
+ color: #333;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #e8e8e8;
+}
+
+.ace_fold {
+ background-color: #6B72E6;
+}
+
+.ace_text-layer {
+}
+
+.ace_cursor {
+ border-left: 2px solid black;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid black;
+}
+
+.ace_line .ace_invisible {
+ color: rgb(191, 191, 191);
+}
+
+.ace_line .ace_storage,
+.ace_line .ace_keyword {
+ color: blue;
+}
+
+.ace_line .ace_constant {
+ color: rgb(197, 6, 11);
+}
+
+.ace_line .ace_constant.ace_buildin {
+ color: rgb(88, 72, 246);
+}
+
+.ace_line .ace_constant.ace_language {
+ color: rgb(88, 92, 246);
+}
+
+.ace_line .ace_constant.ace_library {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_invalid {
+ background-color: rgba(255, 0, 0, 0.1);
+ color: red;
+}
+
+.ace_line .ace_support.ace_function {
+ color: rgb(60, 76, 114);
+}
+
+.ace_line .ace_support.ace_constant {
+ color: rgb(6, 150, 14);
+}
+
+.ace_line .ace_support.ace_type,
+.ace_line .ace_support.ace_class {
+ color: rgb(109, 121, 222);
+}
+
+.ace_line .ace_keyword.ace_operator {
+ color: rgb(104, 118, 135);
+}
+
+.ace_line .ace_string {
+ color: rgb(3, 106, 7);
+}
+
+.ace_line .ace_comment {
+ color: rgb(76, 136, 107);
+}
+
+.ace_line .ace_comment.ace_doc {
+ color: rgb(0, 102, 255);
+}
+
+.ace_line .ace_comment.ace_doc.ace_tag {
+ color: rgb(128, 159, 191);
+}
+
+.ace_line .ace_constant.ace_numeric {
+ color: rgb(0, 0, 205);
+}
+
+.ace_line .ace_variable {
+ color: rgb(49, 132, 149);
+}
+
+.ace_line .ace_xml_pe {
+ color: rgb(104, 104, 91);
+}
+
+.ace_entity.ace_name.ace_function {
+ color: #0000A2;
+}
+
+.ace_markup.ace_markupine {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color: rgb(12, 7, 255);
+}
+
+.ace_markup.ace_list {
+ color:rgb(185, 6, 144);
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgb(181, 213, 255);
+}
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px white;
+ border-radius: 2px;
+}
+.ace_marker-layer .ace_step {
+ background: rgb(252, 255, 0);
+}
+
+.ace_marker-layer .ace_stack {
+ background: rgb(164, 229, 101);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgb(192, 192, 192);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(0, 0, 0, 0.07);
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_marker-layer .ace_selected_word {
+ background: rgb(250, 250, 255);
+ border: 1px solid rgb(200, 200, 250);
+}
+
+.ace_meta.ace_tag {
+ color:rgb(0, 22, 142);
+}
+
+.ace_string.ace_regex {
+ color: rgb(255, 0, 0)
+}
+
+.ace_indent-guide {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;
+}
+
+.nocolor.ace_editor .ace_line span {color: blue !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgb(192, 192, 192);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(242, 242, 242);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(223, 223, 223);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(242, 242, 242); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow.css
new file mode 100644
index 0000000..732cb51
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow.css
@@ -0,0 +1,174 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #f6f6f6;
+ color: #4D4D4C;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #f6f6f6;
+}
+
+.ace_scroller {
+ background-color: #FFFFFF;
+}
+
+.ace_text-layer {
+ color: #4D4D4C;
+}
+
+.ace_cursor {
+ border-left: 2px solid #AEAFAD;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #AEAFAD;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #D6D6D6;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #FFFFFF;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(255, 255, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #D1D1D1;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #EFEFEF;
+}
+
+.ace_gutter_active_line {
+ background-color : #dcdcdc;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #D6D6D6;
+}
+
+.ace_invisible {
+ color: #D1D1D1;
+}
+
+.ace_keyword, .ace_meta {
+ color:#8959A8;
+}
+
+.ace_keyword.ace_operator {
+ color:#3E999F;
+}
+
+.ace_constant.ace_language {
+ color:#F5871F;
+}
+
+.ace_constant.ace_numeric {
+ color:#F5871F;
+}
+
+.ace_constant.ace_other {
+ color:#666969;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#C82829;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#FFFFFF;
+background-color:#8959A8;
+}
+
+.ace_support.ace_constant {
+ color:#F5871F;
+}
+
+.ace_fold {
+ background-color: #4271AE;
+ border-color: #4D4D4C;
+}
+
+.ace_support.ace_function {
+ color:#4271AE;
+}
+
+.ace_storage {
+ color:#8959A8;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ color:#8959A8;
+}
+
+.ace_variable {
+ color:#4271AE;
+}
+
+.ace_variable.ace_parameter {
+ color:#F5871F;
+}
+
+.ace_string {
+ color:#718C00;
+}
+
+.ace_string.ace_regexp {
+ color:#C82829;
+}
+
+.ace_comment {
+ color:#8E908C;
+}
+
+.ace_variable {
+ color:#C82829;
+}
+
+.ace_meta.ace_tag {
+ color:#C82829;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#C82829;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#4271AE;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#718C00;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#8959A8 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #D1D1D1;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(219, 219, 219);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(166, 166, 165);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(255, 238, 155);}
+.ace_console_error { background-color: rgb(219, 219, 219); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night.css
new file mode 100644
index 0000000..a01d016
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night.css
@@ -0,0 +1,174 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #25282c;
+ color: #C5C8C6;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #25282c;
+}
+
+.ace_scroller {
+ background-color: #1D1F21;
+}
+
+.ace_text-layer {
+ color: #C5C8C6;
+}
+
+.ace_cursor {
+ border-left: 2px solid #AEAFAD;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #AEAFAD;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #373B41;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #1D1F21;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #4B4E55;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #282A2E;
+}
+
+.ace_gutter_active_line {
+ background-color: #282A2E;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #373B41;
+}
+
+.ace_invisible {
+ color: #4B4E55;
+}
+
+.ace_keyword, .ace_meta {
+ color:#B294BB;
+}
+
+.ace_keyword.ace_operator {
+ color:#8ABEB7;
+}
+
+.ace_constant.ace_language {
+ color:#DE935F;
+}
+
+.ace_constant.ace_numeric {
+ color:#DE935F;
+}
+
+.ace_constant.ace_other {
+ color:#CED1CF;
+}
+
+.ace_invalid {
+ color:#CED2CF;
+background-color:#DF5F5F;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#CED2CF;
+background-color:#B798BF;
+}
+
+.ace_support.ace_constant {
+ color:#DE935F;
+}
+
+.ace_fold {
+ background-color: #81A2BE;
+ border-color: #C5C8C6;
+}
+
+.ace_support.ace_function {
+ color:#81A2BE;
+}
+
+.ace_storage {
+ color:#B294BB;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ color:#B294BB;
+}
+
+.ace_variable {
+ color:#81A2BE;
+}
+
+.ace_variable.ace_parameter {
+ color:#DE935F;
+}
+
+.ace_string {
+ color:#B5BD68;
+}
+
+.ace_string.ace_regexp {
+ color:#CC6666;
+}
+
+.ace_comment {
+ color:#969896;
+}
+
+.ace_variable {
+ color:#CC6666;
+}
+
+.ace_meta.ace_tag {
+ color:#CC6666;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#CC6666;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#81A2BE;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#B5BD68;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWOQlVf8z7Bq1ar/AA/hBFp7egmpAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#B294BB !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #4B4E55;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(62, 64, 66);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(113, 115, 115);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(142, 126, 44);}
+.ace_console_error { background-color: rgb(62, 64, 66); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_blue.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_blue.css
new file mode 100644
index 0000000..f4ed1b2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_blue.css
@@ -0,0 +1,174 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #00204b;
+ color: #7388b5;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #00204b;
+}
+
+.ace_scroller {
+ background-color: #002451;
+}
+
+.ace_text-layer {
+ color: #FFFFFF;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #003F8E;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #002451;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(127, 111, 19);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #404F7D;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #00346E;
+}
+
+.ace_gutter_active_line {
+ background-color: #022040;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #003F8E;
+}
+
+.ace_invisible {
+ color: #404F7D;
+}
+
+.ace_keyword, .ace_meta {
+ color:#EBBBFF;
+}
+
+.ace_keyword.ace_operator {
+ color:#99FFFF;
+}
+
+.ace_constant.ace_language {
+ color:#FFC58F;
+}
+
+.ace_constant.ace_numeric {
+ color:#FFC58F;
+}
+
+.ace_constant.ace_other {
+ color:#FFFFFF;
+}
+
+.ace_invalid {
+ color:#FFFFFF;
+background-color:#F99DA5;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#FFFFFF;
+background-color:#EBBBFF;
+}
+
+.ace_support.ace_constant {
+ color:#FFC58F;
+}
+
+.ace_fold {
+ background-color: #BBDAFF;
+ border-color: #FFFFFF;
+}
+
+.ace_support.ace_function {
+ color:#BBDAFF;
+}
+
+.ace_storage {
+ color:#EBBBFF;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ color:#EBBBFF;
+}
+
+.ace_variable {
+ color:#BBDAFF;
+}
+
+.ace_variable.ace_parameter {
+ color:#FFC58F;
+}
+
+.ace_string {
+ color:#D1F1A9;
+}
+
+.ace_string.ace_regexp {
+ color:#FF9DA4;
+}
+
+.ace_comment {
+ color:#7285B7;
+}
+
+.ace_variable {
+ color:#FF9DA4;
+}
+
+.ace_meta.ace_tag {
+ color:#FF9DA4;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#FF9DA4;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#BBDAFF;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#D1F1A9;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgUAn8z7Bq1ar/ABBUBHJ4/r3JAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#EBBBFF !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #404F7D;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(50, 79, 115);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(127, 145, 168);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(127, 129, 68);}
+.ace_console_error { background-color: rgb(50, 79, 115); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_bright.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_bright.css
new file mode 100644
index 0000000..cd97c3b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_bright.css
@@ -0,0 +1,174 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #1a1a1a;
+ color: #DEDEDE;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #1a1a1a;
+}
+
+.ace_scroller {
+ background-color: #000000;
+}
+
+.ace_text-layer {
+ color: #DEDEDE;
+}
+
+.ace_cursor {
+ border-left: 2px solid #9F9F9F;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #9F9F9F;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #424242;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #000000;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #343434;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #2A2A2A;
+}
+
+.ace_gutter_active_line {
+ background-color: #2A2A2A;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #424242;
+}
+
+.ace_invisible {
+ color: #343434;
+}
+
+.ace_keyword, .ace_meta {
+ color:#C397D8;
+}
+
+.ace_keyword.ace_operator {
+ color:#70C0B1;
+}
+
+.ace_constant.ace_language {
+ color:#E78C45;
+}
+
+.ace_constant.ace_numeric {
+ color:#E78C45;
+}
+
+.ace_constant.ace_other {
+ color:#EEEEEE;
+}
+
+.ace_invalid {
+ color:#CED2CF;
+background-color:#DF5F5F;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#CED2CF;
+background-color:#B798BF;
+}
+
+.ace_support.ace_constant {
+ color:#E78C45;
+}
+
+.ace_fold {
+ background-color: #7AA6DA;
+ border-color: #DEDEDE;
+}
+
+.ace_support.ace_function {
+ color:#7AA6DA;
+}
+
+.ace_storage {
+ color:#C397D8;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ color:#C397D8;
+}
+
+.ace_variable {
+ color:#7AA6DA;
+}
+
+.ace_variable.ace_parameter {
+ color:#E78C45;
+}
+
+.ace_string {
+ color:#B9CA4A;
+}
+
+.ace_string.ace_regexp {
+ color:#D54E53;
+}
+
+.ace_comment {
+ color:#969896;
+}
+
+.ace_variable {
+ color:#D54E53;
+}
+
+.ace_meta.ace_tag {
+ color:#D54E53;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#D54E53;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#7AA6DA;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#B9CA4A;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGD4z7Bq1ar/AAz9A/2naJQKAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#C397D8 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #343434;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(44, 44, 44);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(111, 111, 111);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(127, 111, 28);}
+.ace_console_error { background-color: rgb(44, 44, 44); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_eighties.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_eighties.css
new file mode 100644
index 0000000..f757809
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/tomorrow_night_eighties.css
@@ -0,0 +1,170 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #272727;
+ color: #CCC;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #272727;
+}
+
+.ace_scroller {
+ background-color: #2D2D2D;
+}
+
+.ace_text-layer {
+ color: #CCCCCC;
+}
+
+.ace_cursor {
+ border-left: 2px solid #CCCCCC;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #CCCCCC;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #515151;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #2D2D2D;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #6A6A6A;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #393939;
+}
+
+.ace_gutter_active_line {
+ background-color: #393939;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #515151;
+}
+
+.ace_invisible {
+ color: #6A6A6A;
+}
+
+.ace_keyword, .ace_meta {
+ color:#CC99CC;
+}
+
+.ace_keyword.ace_operator {
+ color:#66CCCC;
+}
+
+.ace_constant.ace_language {
+ color:#F99157;
+}
+
+.ace_constant.ace_numeric {
+ color:#F99157;
+}
+
+.ace_constant.ace_other {
+ color:#CCCCCC;
+}
+
+.ace_invalid {
+ color:#CDCDCD;
+background-color:#F2777A;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#CDCDCD;
+background-color:#CC99CC;
+}
+
+.ace_support.ace_constant {
+ color:#F99157;
+}
+
+.ace_fold {
+ background-color: #6699CC;
+ border-color: #CCCCCC;
+}
+
+.ace_support.ace_function {
+ color:#6699CC;
+}
+
+.ace_storage {
+ color:#CC99CC;
+}
+
+.ace_storage.ace_type, .ace_support.ace_type{
+ color:#CC99CC;
+}
+
+.ace_variable {
+ color:#6699CC;
+}
+
+.ace_variable.ace_parameter {
+ color:#F99157;
+}
+
+.ace_string {
+ color:#99CC99;
+}
+
+.ace_comment {
+ color:#999999;
+}
+
+.ace_variable {
+ color:#F2777A;
+}
+
+.ace_meta.ace_tag {
+ color:#F2777A;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ color:#F2777A;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#6699CC;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#99CC99;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ1dX9z7Bq1ar/ABE1BITwhhuFAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#CC99CC !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #6A6A6A;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(76, 76, 76);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(124, 124, 124);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(150, 133, 50);}
+.ace_console_error { background-color: rgb(76, 76, 76); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/twilight.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/twilight.css
new file mode 100644
index 0000000..384ebce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/twilight.css
@@ -0,0 +1,172 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #232323;
+ color: #F8F8F8;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #232323;
+}
+
+.ace_scroller {
+ background-color: #141414;
+}
+
+.ace_text-layer {
+ color: #F8F8F8;
+}
+
+.ace_cursor {
+ border-left: 2px solid #A7A7A7;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #A7A7A7;
+}
+
+.ace_marker-layer .ace_selection {
+ background: rgba(221, 240, 255, 0.20);
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #141414;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid rgba(255, 255, 255, 0.25);
+}
+
+.ace_marker-layer .ace_active_line {
+ background: rgba(255, 255, 255, 0.031);
+}
+
+.ace_gutter_active_line {
+ background-color: rgba(255, 255, 255, 0.031);
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid rgba(221, 240, 255, 0.20);
+}
+
+.ace_invisible {
+ color: rgba(255, 255, 255, 0.25);
+}
+
+.ace_keyword, .ace_meta {
+ color:#CDA869;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#CF6A4C;
+}
+
+.ace_constant.ace_character, {
+ color:#CF6A4C;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#CF6A4C;
+}
+
+.ace_invalid.ace_illegal {
+ color:#F8F8F8;
+background-color:rgba(86, 45, 86, 0.75);
+}
+
+.ace_invalid.ace_deprecated {
+ text-decoration:underline;
+font-style:italic;
+color:#D2A8A1;
+}
+
+.ace_support {
+ color:#9B859D;
+}
+
+.ace_support.ace_constant {
+ color:#CF6A4C;
+}
+
+.ace_fold {
+ background-color: #AC885B;
+ border-color: #F8F8F8;
+}
+
+.ace_support.ace_function {
+ color:#DAD085;
+}
+
+.ace_storage {
+ color:#F9EE98;
+}
+
+.ace_variable {
+ color:#AC885B;
+}
+
+.ace_string {
+ color:#8F9D6A;
+}
+
+.ace_string.ace_regexp {
+ color:#E9C062;
+}
+
+.ace_comment {
+ font-style:italic;
+color:#5F5A60;
+}
+
+.ace_variable {
+ color:#7587A6;
+}
+
+.ace_xml_pe {
+ color:#494949;
+}
+
+.ace_meta.ace_tag {
+ color:#AC885B;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#AC885B;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_markup.ace_heading {
+ color:#CF6A4C;
+}
+
+.ace_markup.ace_list {
+ color:#F9EE98;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWMQERH5zzBz5sz/AA5EBAYqeZXWAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#CDA869 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: rgba(255, 255, 255, 0.25);}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(65, 65, 65);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(134, 134, 134);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(137, 121, 38);}
+.ace_console_error { background-color: rgb(65, 65, 65); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/vibrant_ink.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/vibrant_ink.css
new file mode 100644
index 0000000..406b342
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/themes/vibrant_ink.css
@@ -0,0 +1,151 @@
+
+.ace_editor {
+ border: 2px solid rgb(159, 159, 159);
+}
+
+.ace_editor.ace_focus {
+ border: 2px solid #327fbd;
+}
+
+.ace_gutter {
+ background: #1a1a1a;
+ color: white;
+}
+
+.ace_print_margin {
+ width: 1px;
+ background: #1a1a1a;
+}
+
+.ace_scroller {
+ background-color: #0F0F0F;
+}
+
+.ace_text-layer {
+ color: #FFFFFF;
+}
+
+.ace_cursor {
+ border-left: 2px solid #FFFFFF;
+}
+
+.ace_cursor.ace_overwrite {
+ border-left: 0px;
+ border-bottom: 1px solid #FFFFFF;
+}
+
+.ace_marker-layer .ace_selection {
+ background: #6699CC;
+}
+
+.multiselect .ace_selection.start {
+ box-shadow: 0 0 3px 0px #0F0F0F;
+ border-radius: 2px;
+}
+
+.ace_marker-layer .ace_step {
+ background: rgb(102, 82, 0);
+}
+
+.ace_marker-layer .ace_bracket {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #404040;
+}
+
+.ace_marker-layer .ace_active_line {
+ background: #333333;
+}
+
+.ace_gutter_active_line {
+ background-color: #333333;
+}
+
+.ace_marker-layer .ace_selected_word {
+ border: 1px solid #6699CC;
+}
+
+.ace_invisible {
+ color: #404040;
+}
+
+.ace_keyword, .ace_meta {
+ color:#FF6600;
+}
+
+.ace_constant, .ace_constant.ace_other {
+ color:#339999;
+}
+
+.ace_constant.ace_character, {
+ color:#339999;
+}
+
+.ace_constant.ace_character.ace_escape, {
+ color:#339999;
+}
+
+.ace_constant.ace_numeric {
+ color:#99CC99;
+}
+
+.ace_invalid {
+ color:#CCFF33;
+background-color:#000000;
+}
+
+.ace_invalid.ace_deprecated {
+ color:#CCFF33;
+background-color:#000000;
+}
+
+.ace_fold {
+ background-color: #FFCC00;
+ border-color: #FFFFFF;
+}
+
+.ace_support.ace_function {
+ color:#FFCC00;
+}
+
+.ace_variable {
+ color:#FFCC00;
+}
+
+.ace_variable.ace_parameter {
+ font-style:italic;
+}
+
+.ace_string {
+ color:#66FF00;
+}
+
+.ace_string.ace_regexp {
+ color:#44B4CC;
+}
+
+.ace_comment {
+ color:#9933CC;
+}
+
+.ace_entity.ace_other.ace_attribute-name {
+ font-style:italic;
+color:#99CC99;
+}
+
+.ace_entity.ace_name.ace_function {
+ color:#FFCC00;
+}
+
+.ace_markup.ace_underline {
+ text-decoration:underline;
+}
+
+.ace_indent-guide {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPg5+f/z7Bq1ar/AA5lBCqoLxsgAAAAAElFTkSuQmCC) right repeat-y;
+}
+.nocolor.ace_editor .ace_line span {color:#FF6600 !important;}
+.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #404040;}
+.ace_marker-layer .ace_foreign_line {position: absolute; z-index: -1; background-color: rgb(62, 62, 62);}
+.ace_marker-layer .ace_find_line {position: absolute; z-index: -1; background-color: rgb(135, 135, 135);}
+.ace_marker-layer .ace_active_debug_line {position: absolute; z-index: -1; background-color: rgb(135, 118, 35);}
+.ace_console_error { background-color: rgb(62, 62, 62); }
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/ChooseEncodingDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/ChooseEncodingDialog.java
new file mode 100644
index 0000000..67dc77d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/ChooseEncodingDialog.java
@@ -0,0 +1,203 @@
+/*
+ * ChooseEncodingDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.text.ui;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.workbench.model.Session;
+
+public class ChooseEncodingDialog extends ModalDialog<String>
+{
+ public ChooseEncodingDialog(JsArrayString commonEncodings,
+ JsArrayString allEncodings,
+ String currentEncoding,
+ boolean includePromptForEncoding,
+ boolean includeSaveAsDefault,
+ OperationWithInput<String> operation)
+ {
+ super("Choose Encoding", operation);
+ commonEncodings_ = commonEncodings;
+ allEncodings_ = allEncodings;
+ currentEncoding_ = currentEncoding;
+ includePromptForEncoding_ = includePromptForEncoding;
+ includeSaveAsDefault_ = includeSaveAsDefault;
+
+ Session session = RStudioGinjector.INSTANCE.getSession();
+ systemEncoding_ = session.getSessionInfo().getSystemEncoding();
+ systemEncodingNormalized_ = normalizeEncoding(systemEncoding_);
+ }
+
+ private void setCurrentValue(String currentEncoding)
+ {
+ currentEncoding = StringUtil.notNull(currentEncoding);
+ if (!includePromptForEncoding_ && "".equals(currentEncoding))
+ return;
+
+ // Select current value if it exists--if not, add it
+ for (int i = 0; i < listBox_.getItemCount(); i++)
+ if (listBox_.getValue(i).equalsIgnoreCase(currentEncoding))
+ {
+ listBox_.setSelectedIndex(i);
+ return;
+ }
+
+ listBox_.insertItem(currentEncoding, 0);
+ listBox_.setSelectedIndex(0);
+ }
+
+ @Override
+ protected String collectInput()
+ {
+ if (listBox_.getSelectedIndex() >= 0)
+ return listBox_.getValue(listBox_.getSelectedIndex());
+ else
+ return null;
+ }
+
+ @Override
+ protected boolean validate(String input)
+ {
+ return input != null;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ listBox_ = new ListBox(true);
+ listBox_.setVisibleItemCount(15);
+ listBox_.setWidth("350px");
+
+ setEncodings(commonEncodings_, currentEncoding_);
+
+ CheckBox showAll = new CheckBox("Show all encodings");
+ showAll.addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ public void onValueChange(ValueChangeEvent<Boolean> e)
+ {
+ if (e.getValue())
+ setEncodings(allEncodings_, currentEncoding_);
+ else
+ setEncodings(commonEncodings_, currentEncoding_);
+ }
+ });
+ setCheckBoxMargins(showAll, 8, 12);
+
+ VerticalPanel panel = new VerticalPanel();
+ panel.add(listBox_);
+ panel.add(showAll);
+
+ if (includeSaveAsDefault_)
+ {
+ saveAsDefault_ = new CheckBox("Set as default encoding for " +
+ "source files");
+ setCheckBoxMargins(showAll, 8, 0);
+ setCheckBoxMargins(saveAsDefault_, 3, 12);
+ panel.add(saveAsDefault_);
+ }
+
+ return panel;
+ }
+
+ private void setCheckBoxMargins(CheckBox checkBox,
+ int topMargin,
+ int bottomMargin)
+ {
+ checkBox.getElement().getStyle().setDisplay(Display.BLOCK);
+ checkBox.getElement().getStyle().setMarginTop(topMargin, Unit.PX);
+ checkBox.getElement().getStyle().setMarginBottom(bottomMargin, Unit.PX);
+ }
+
+ public boolean isSaveAsDefault()
+ {
+ return saveAsDefault_ != null && saveAsDefault_.getValue();
+ }
+
+ private void setEncodings(JsArrayString encodings, String encoding)
+ {
+ listBox_.clear();
+
+ for (int i = 0; i < encodings.length(); i++)
+ {
+ listBox_.addItem(encodings.get(i));
+ }
+
+ int sysIndex = findSystemEncodingIndex();
+ if (!StringUtil.isNullOrEmpty(systemEncoding_))
+ {
+ // Remove the system encoding (if it is present) so we can move it
+ // to the top of the list. If it's already present, use the same
+ // label (un-normalized encoding name) so it's consistent with
+ // related encodings that are also present in the list.
+ String sysEncName = sysIndex < 0 ? systemEncoding_
+ : listBox_.getValue(sysIndex);
+ if (sysIndex >= 0)
+ listBox_.removeItem(sysIndex);
+ listBox_.insertItem(sysEncName + " (System default)",
+ systemEncoding_,
+ 0);
+ }
+
+ if (includePromptForEncoding_)
+ {
+ listBox_.insertItem(ASK_LABEL, "", 0);
+ }
+
+ if (isSystemEncoding(encoding))
+ setCurrentValue(listBox_.getValue(includePromptForEncoding_ ? 1 : 0));
+ else
+ setCurrentValue(encoding);
+ }
+
+ private int findSystemEncodingIndex()
+ {
+ for (int i = 0; i < listBox_.getItemCount(); i++)
+ if (isSystemEncoding(listBox_.getValue(i)))
+ return i;
+ return -1;
+ }
+
+ private boolean isSystemEncoding(String encoding)
+ {
+ return !StringUtil.isNullOrEmpty(encoding)
+ && normalizeEncoding(encoding).equals(systemEncodingNormalized_);
+ }
+
+ public static String normalizeEncoding(String encoding)
+ {
+ return StringUtil.notNull(encoding).replaceAll("[- ]", "").toLowerCase();
+ }
+
+ private ListBox listBox_;
+ private final JsArrayString commonEncodings_;
+ private final JsArrayString allEncodings_;
+ private final String currentEncoding_;
+ private final boolean includePromptForEncoding_;
+ private final boolean includeSaveAsDefault_;
+ private CheckBox saveAsDefault_;
+ private final String systemEncoding_;
+ private final String systemEncodingNormalized_;
+ public static final String ASK_LABEL = "[Ask]";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.java
new file mode 100644
index 0000000..1052e65
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.java
@@ -0,0 +1,103 @@
+/*
+ * NewRdDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+package org.rstudio.studio.client.workbench.views.source.editors.text.ui;
+
+
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+
+public class NewRdDialog extends ModalDialog<NewRdDialog.Result>
+{
+ public static class Result
+ {
+ public static final String TYPE_NONE = "none";
+
+ public Result(String name, String type)
+ {
+ this.name = name;
+ this.type = type;
+ }
+ public final String name;
+ public final String type;
+ }
+
+ public interface Binder extends UiBinder<Widget, NewRdDialog>
+ {
+ }
+
+ public NewRdDialog(OperationWithInput<Result> operation)
+ {
+ super("New R Documentation File", operation);
+ mainWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return mainWidget_;
+ }
+
+ @Override
+ protected void onDialogShown()
+ {
+ txtName_.setFocus(true);
+ }
+
+
+ @Override
+ protected Result collectInput()
+ {
+ return new Result(txtName_.getText().trim(),
+ listDocType_.getValue(listDocType_.getSelectedIndex()));
+ }
+
+
+ @Override
+ protected boolean validate(Result input)
+ {
+ if (input.name.length() == 0)
+ {
+ RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
+ "Name Not Specified",
+ "You must specify a topic name for the new Rd file.",
+ txtName_);
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+
+ @UiField
+ TextBox txtName_;
+ @UiField
+ ListBox listDocType_;
+
+ private Widget mainWidget_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.ui.xml
new file mode 100644
index 0000000..462dd6f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/text/ui/NewRdDialog.ui.xml
@@ -0,0 +1,32 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .panel {
+ width: 300px;
+ }
+
+ br {
+ margin-bottom: 10px;
+ }
+
+ .fillWidth {
+ width: 100%;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.panel}">
+
+ <g:Label text="Topic name:"/>
+ <g:TextBox ui:field="txtName_" styleName="{style.fillWidth}"/> <br/>
+
+ <g:Label text="Rd template:"/>
+ <g:ListBox ui:field="listDocType_" styleName="{style.fillWidth}">
+ <g:item value="function">Function</g:item>
+ <g:item value="data">Dataset</g:item>
+ <g:item value="none">(Empty Topic)</g:item>
+ </g:ListBox> <br/>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTarget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTarget.java
new file mode 100644
index 0000000..0915837
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTarget.java
@@ -0,0 +1,393 @@
+/*
+ * UrlContentEditingTarget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.urlcontent;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.events.EnsureHeightHandler;
+import org.rstudio.core.client.events.EnsureVisibleHandler;
+import org.rstudio.core.client.files.FileSystemContext;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.ReadOnlyValue;
+import org.rstudio.studio.client.common.Value;
+import org.rstudio.studio.client.common.filetypes.FileIconResources;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+import org.rstudio.studio.client.workbench.views.source.events.SourceNavigationEvent;
+import org.rstudio.studio.client.workbench.views.source.model.ContentItem;
+import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
+import org.rstudio.studio.client.workbench.views.source.model.SourceNavigation;
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
+
+import java.util.HashSet;
+
+public class UrlContentEditingTarget implements EditingTarget
+{
+ public interface Display extends IsWidget
+ {
+ void print();
+ }
+
+ interface MyBinder extends CommandBinder<Commands, UrlContentEditingTarget>
+ {}
+
+ @Inject
+ public UrlContentEditingTarget(SourceServerOperations server,
+ Commands commands,
+ GlobalDisplay globalDisplay,
+ EventBus events)
+ {
+ server_ = server;
+ commands_ = commands;
+ globalDisplay_ = globalDisplay;
+ events_ = events;
+ }
+
+ public String getId()
+ {
+ return doc_.getId();
+ }
+
+ @Override
+ public void adaptToExtendedFileType(String extendedType)
+ {
+ }
+
+ @Override
+ public String getExtendedFileType()
+ {
+ return null;
+ }
+
+ public HasValue<String> getName()
+ {
+ String title = getContentTitle();
+ return new Value<String>(title);
+ }
+
+ public String getTitle()
+ {
+ return getContentTitle();
+ }
+
+ public String getPath()
+ {
+ return null;
+ }
+
+ public String getContext()
+ {
+ return null;
+ }
+
+ public ImageResource getIcon()
+ {
+ return FileIconResources.INSTANCE.iconText();
+ }
+
+ public String getTabTooltip()
+ {
+ return null;
+ }
+
+ public HashSet<AppCommand> getSupportedCommands()
+ {
+ HashSet<AppCommand> commands = new HashSet<AppCommand>();
+ commands.add(commands_.printSourceDoc());
+ commands.add(commands_.popoutDoc());
+ return commands;
+ }
+
+ @Override
+ public boolean canCompilePdf()
+ {
+ return false;
+ }
+
+
+ @Override
+ public void verifyCppPrerequisites()
+ {
+ }
+
+ @Override
+ public Position search(String regex)
+ {
+ return null;
+ }
+
+ @Override
+ public Position search(Position startPos, String regex)
+ {
+ return null;
+ }
+
+ @Override
+ public void forceLineHighlighting()
+ {
+ }
+
+ @Handler
+ void onPrintSourceDoc()
+ {
+ view_.print();
+ }
+
+ @Handler
+ void onPopoutDoc()
+ {
+ globalDisplay_.openWindow(getContentUrl());
+ }
+
+ public void focus()
+ {
+ }
+
+ public void onActivate()
+ {
+ if (commandReg_ != null)
+ {
+ Debug.log("Warning: onActivate called twice without intervening onDeactivate");
+ commandReg_.removeHandler();
+ commandReg_ = null;
+ }
+ commandReg_ = binder_.bind(commands_, this);
+ }
+
+ public void onDeactivate()
+ {
+ commandReg_.removeHandler();
+ commandReg_ = null;
+
+ recordCurrentNavigationPosition();
+
+ }
+
+ @Override
+ public void onInitiallyLoaded()
+ {
+ }
+
+ @Override
+ public void recordCurrentNavigationPosition()
+ {
+ events_.fireEvent(new SourceNavigationEvent(
+ SourceNavigation.create(
+ getId(),
+ getPath(),
+ SourcePosition.create(0, 0))));
+ }
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent)
+ {
+ }
+
+
+ @Override
+ public void navigateToPosition(SourcePosition position,
+ boolean recordCurrent,
+ boolean highlightLine)
+ {
+ }
+
+ @Override
+ public void restorePosition(SourcePosition position)
+ {
+ }
+
+ @Override
+ public void setCursorPosition(Position position)
+ {
+ }
+
+ @Override
+ public void ensureCursorVisible()
+ {
+ }
+
+ @Override
+ public boolean isAtSourceRow(SourcePosition position)
+ {
+ // always true because url content editing targets don't have the
+ // concept of a position
+ return true;
+ }
+
+ @Override
+ public void highlightDebugLocation(
+ SourcePosition startPos,
+ SourcePosition endPos,
+ boolean executing)
+ {
+ }
+
+ @Override
+ public void endDebugHighlighting()
+ {
+ }
+
+ public boolean onBeforeDismiss()
+ {
+ return true;
+ }
+
+ public void onDismiss()
+ {
+ server_.removeContentUrl(getContentUrl(),
+ new ServerRequestCallback<org.rstudio.studio.client.server.Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+
+ protected String getContentTitle()
+ {
+ return getContentItem().getTitle();
+ }
+
+ protected String getContentUrl()
+ {
+ return getContentItem().getContentUrl();
+ }
+
+ public ReadOnlyValue<Boolean> dirtyState()
+ {
+ return dirtyState_;
+ }
+
+ @Override
+ public boolean isSaveCommandActive()
+ {
+ return dirtyState().getValue();
+ }
+
+ public void save(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ public void saveWithPrompt(Command onCompleted, Command onCancelled)
+ {
+ onCompleted.execute();
+ }
+
+ public void revertChanges(Command onCompleted)
+ {
+ onCompleted.execute();
+ }
+
+ public void initialize(SourceDocument document,
+ FileSystemContext fileContext,
+ FileType type,
+ Provider<String> defaultNameProvider)
+ {
+ doc_ = document;
+ view_ = createDisplay();
+ }
+
+ protected Display createDisplay()
+ {
+ return new UrlContentEditingTargetWidget(commands_,
+ getContentUrl());
+ }
+
+ public long getFileSizeLimit()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ public long getLargeFileSize()
+ {
+ return Long.MAX_VALUE;
+ }
+
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ public HandlerRegistration addEnsureVisibleHandler(EnsureVisibleHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ public HandlerRegistration addEnsureHeightHandler(EnsureHeightHandler handler)
+ {
+ return new HandlerRegistration()
+ {
+ public void removeHandler()
+ {
+ }
+ };
+ }
+
+ public HandlerRegistration addCloseHandler(
+ CloseHandler<Void> voidCloseHandler)
+ {
+ return addEnsureVisibleHandler(null);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ assert false : "Not implemented";
+ }
+
+ private ContentItem getContentItem()
+ {
+ return (ContentItem)doc_.getProperties().cast();
+ }
+
+ protected SourceDocument doc_;
+ private Value<Boolean> dirtyState_ = new Value<Boolean>(false);
+
+ protected final SourceServerOperations server_;
+ protected final Commands commands_;
+ private final GlobalDisplay globalDisplay_;
+ private final EventBus events_;
+ private Display view_;
+ private HandlerRegistration commandReg_;
+
+ private static final MyBinder binder_ = GWT.create(MyBinder.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTargetWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTargetWidget.java
new file mode 100644
index 0000000..6296fc5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/editors/urlcontent/UrlContentEditingTargetWidget.java
@@ -0,0 +1,63 @@
+/*
+ * UrlContentEditingTargetWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.editors.urlcontent;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.dom.IFrameElementEx;
+import org.rstudio.core.client.widget.RStudioFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.EditingTargetToolbar;
+
+public class UrlContentEditingTargetWidget extends Composite
+ implements UrlContentEditingTarget.Display
+{
+ public UrlContentEditingTargetWidget(Commands commands, String url)
+ {
+ commands_ = commands;
+
+ frame_ = new RStudioFrame(url);
+ frame_.setSize("100%", "100%");
+
+ PanelWithToolbars panel = new PanelWithToolbars(createToolbar(),
+ frame_);
+
+ initWidget(panel);
+
+ }
+
+ private Toolbar createToolbar()
+ {
+ Toolbar toolbar = new EditingTargetToolbar(commands_);
+ toolbar.addLeftWidget(commands_.popoutDoc().createToolbarButton());
+ return toolbar;
+ }
+
+ public void print()
+ {
+ IFrameElementEx frameEl = (IFrameElementEx) frame_.getElement().cast();
+ frameEl.getContentWindow().print();
+ }
+
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ private final Commands commands_;
+ private RStudioFrame frame_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedEvent.java
new file mode 100644
index 0000000..1709e05
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedEvent.java
@@ -0,0 +1,41 @@
+/*
+ * CodeBrowserFinishedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.source.events;
+
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CodeBrowserFinishedEvent extends GwtEvent<CodeBrowserFinishedHandler>
+{
+ public static final GwtEvent.Type<CodeBrowserFinishedHandler> TYPE =
+ new GwtEvent.Type<CodeBrowserFinishedHandler>();
+
+ public CodeBrowserFinishedEvent()
+ {
+ }
+
+ @Override
+ protected void dispatch(CodeBrowserFinishedHandler handler)
+ {
+ handler.onCodeBrowserFinished(this);
+ }
+
+ @Override
+ public GwtEvent.Type<CodeBrowserFinishedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedHandler.java
new file mode 100644
index 0000000..d513451
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserFinishedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * CodeBrowserFinishedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface CodeBrowserFinishedHandler extends EventHandler
+{
+ void onCodeBrowserFinished(CodeBrowserFinishedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserHighlightEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserHighlightEvent.java
new file mode 100644
index 0000000..03a63a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserHighlightEvent.java
@@ -0,0 +1,57 @@
+/*
+ * CodeBrowserHighlightEvent.java
+ *
+ * Copyright (C) 2009-14 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import org.rstudio.core.client.DebugFilePosition;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CodeBrowserHighlightEvent
+ extends GwtEvent<CodeBrowserHighlightEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onCodeBrowserHighlight(CodeBrowserHighlightEvent event);
+ }
+
+ public static final GwtEvent.Type<CodeBrowserHighlightEvent.Handler> TYPE =
+ new GwtEvent.Type<CodeBrowserHighlightEvent.Handler>();
+
+ public CodeBrowserHighlightEvent(DebugFilePosition debugPosition)
+ {
+ debugPosition_ = debugPosition;
+ }
+
+ public DebugFilePosition getDebugPosition()
+ {
+ return debugPosition_;
+ }
+
+
+ @Override
+ protected void dispatch(CodeBrowserHighlightEvent.Handler handler)
+ {
+ handler.onCodeBrowserHighlight(this);
+ }
+
+ @Override
+ public GwtEvent.Type<CodeBrowserHighlightEvent.Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ final DebugFilePosition debugPosition_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationEvent.java
new file mode 100644
index 0000000..d4d853e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationEvent.java
@@ -0,0 +1,72 @@
+/*
+ * CodeBrowserNavigationEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import org.rstudio.core.client.DebugFilePosition;
+import org.rstudio.studio.client.workbench.codesearch.model.SearchPathFunctionDefinition;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class CodeBrowserNavigationEvent extends GwtEvent<CodeBrowserNavigationHandler>
+{
+ public static final GwtEvent.Type<CodeBrowserNavigationHandler> TYPE =
+ new GwtEvent.Type<CodeBrowserNavigationHandler>();
+
+ public CodeBrowserNavigationEvent(SearchPathFunctionDefinition function)
+ {
+ this(function, null, false);
+ }
+
+ public CodeBrowserNavigationEvent(SearchPathFunctionDefinition function,
+ DebugFilePosition debugPosition,
+ boolean executing)
+ {
+ function_ = function;
+ debugPosition_ = debugPosition;
+ executing_ = executing;
+ }
+
+ public SearchPathFunctionDefinition getFunction()
+ {
+ return function_;
+ }
+
+ public DebugFilePosition getDebugPosition()
+ {
+ return debugPosition_;
+ }
+
+ public boolean getExecuting()
+ {
+ return executing_;
+ }
+
+ @Override
+ protected void dispatch(CodeBrowserNavigationHandler handler)
+ {
+ handler.onCodeBrowserNavigation(this);
+ }
+
+ @Override
+ public GwtEvent.Type<CodeBrowserNavigationHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ final DebugFilePosition debugPosition_;
+ final SearchPathFunctionDefinition function_;
+ final boolean executing_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationHandler.java
new file mode 100644
index 0000000..f601b65
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/CodeBrowserNavigationHandler.java
@@ -0,0 +1,22 @@
+/*
+ * CodeBrowserNavigationHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface CodeBrowserNavigationHandler extends EventHandler
+{
+ void onCodeBrowserNavigation(CodeBrowserNavigationEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedEvent.java
new file mode 100644
index 0000000..8aeca9b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedEvent.java
@@ -0,0 +1,71 @@
+/*
+ * DocTabsChangedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.resources.client.ImageResource;
+
+public class DocTabsChangedEvent extends GwtEvent<DocTabsChangedHandler>
+{
+ public static final Type<DocTabsChangedHandler> TYPE = new Type<DocTabsChangedHandler>();
+
+ public DocTabsChangedEvent(String[] ids,
+ ImageResource[] icons,
+ String[] names,
+ String[] paths)
+ {
+ ids_ = ids;
+ this.icons = icons;
+ this.names = names;
+ this.paths = paths;
+ }
+
+ public String[] getIds()
+ {
+ return ids_;
+ }
+
+ public ImageResource[] getIcons()
+ {
+ return icons;
+ }
+
+ public String[] getNames()
+ {
+ return names;
+ }
+
+ public String[] getPaths()
+ {
+ return paths;
+ }
+
+ @Override
+ public Type<DocTabsChangedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(DocTabsChangedHandler handler)
+ {
+ handler.onDocTabsChanged(this);
+ }
+
+ private final String[] ids_;
+ private final ImageResource[] icons;
+ private final String[] names;
+ private final String[] paths;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedHandler.java
new file mode 100644
index 0000000..c822d89
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/DocTabsChangedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * DocTabsChangedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface DocTabsChangedHandler extends EventHandler
+{
+ void onDocTabsChanged(DocTabsChangedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/EditPresentationSourceEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/EditPresentationSourceEvent.java
new file mode 100644
index 0000000..375e66f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/EditPresentationSourceEvent.java
@@ -0,0 +1,62 @@
+/*
+ * EditPresentationSourceEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class EditPresentationSourceEvent extends GwtEvent<EditPresentationSourceEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onEditPresentationSource(EditPresentationSourceEvent e);
+ }
+
+ public EditPresentationSourceEvent(FileSystemItem sourceFile,
+ int slideIndex)
+ {
+ sourceFile_ = sourceFile;
+ slideIndex_ = slideIndex;
+ }
+
+ public FileSystemItem getSourceFile()
+ {
+ return sourceFile_;
+ }
+
+ public int getSlideIndex()
+ {
+ return slideIndex_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onEditPresentationSource(this);
+ }
+
+ private final FileSystemItem sourceFile_;
+ private final int slideIndex_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditEvent.java
new file mode 100644
index 0000000..c72c23f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditEvent.java
@@ -0,0 +1,49 @@
+/*
+ * FileEditEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class FileEditEvent extends GwtEvent<FileEditHandler>
+{
+ public static final GwtEvent.Type<FileEditHandler> TYPE =
+ new GwtEvent.Type<FileEditHandler>();
+
+ public FileEditEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ protected void dispatch(FileEditHandler handler)
+ {
+ handler.onFileEdit(this);
+ }
+
+ @Override
+ public GwtEvent.Type<FileEditHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private FileSystemItem file_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditHandler.java
new file mode 100644
index 0000000..60f4c9c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/FileEditHandler.java
@@ -0,0 +1,22 @@
+/*
+ * FileEditHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface FileEditHandler extends EventHandler
+{
+ void onFileEdit(FileEditEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceEvent.java
new file mode 100644
index 0000000..371daa4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceEvent.java
@@ -0,0 +1,55 @@
+/*
+ * InsertSourceEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class InsertSourceEvent extends GwtEvent<InsertSourceHandler>
+{
+ public static final Type<InsertSourceHandler> TYPE =
+ new Type<InsertSourceHandler>();
+
+ public InsertSourceEvent(String source, boolean block)
+ {
+
+ source_ = source;
+ block_ = block;
+ }
+
+ public String getCode()
+ {
+ return source_;
+ }
+
+ public boolean isBlock()
+ {
+ return block_;
+ }
+
+ @Override
+ public Type<InsertSourceHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(InsertSourceHandler handler)
+ {
+ handler.onInsertSource(this);
+ }
+
+ private final String source_;
+ private final boolean block_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceHandler.java
new file mode 100644
index 0000000..8af6c91
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/InsertSourceHandler.java
@@ -0,0 +1,22 @@
+/*
+ * InsertSourceHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface InsertSourceHandler extends EventHandler
+{
+ void onInsertSource(InsertSourceEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedEvent.java
new file mode 100644
index 0000000..5143dfd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * LastSourceDocClosedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class LastSourceDocClosedEvent extends GwtEvent<LastSourceDocClosedHandler>
+{
+ public static final Type<LastSourceDocClosedHandler> TYPE =
+ new Type<LastSourceDocClosedHandler>();
+
+ @Override
+ protected void dispatch(LastSourceDocClosedHandler handler)
+ {
+ handler.onLastSourceDocClosed(this);
+ }
+
+ @Override
+ public Type<LastSourceDocClosedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedHandler.java
new file mode 100644
index 0000000..e78d5df
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/LastSourceDocClosedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * LastSourceDocClosedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface LastSourceDocClosedHandler extends EventHandler
+{
+ void onLastSourceDocClosed(LastSourceDocClosedEvent event);
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionEvent.java
new file mode 100644
index 0000000..13c1b73
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionEvent.java
@@ -0,0 +1,48 @@
+/*
+ * RecordNavigationPositionEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import org.rstudio.studio.client.workbench.views.source.model.SourcePosition;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class RecordNavigationPositionEvent extends GwtEvent<RecordNavigationPositionHandler>
+{
+ public static final Type<RecordNavigationPositionHandler> TYPE = new Type<RecordNavigationPositionHandler>();
+
+ public RecordNavigationPositionEvent(SourcePosition position)
+ {
+ position_ = position;
+ }
+
+ public SourcePosition getPosition()
+ {
+ return position_;
+ }
+
+ @Override
+ public Type<RecordNavigationPositionHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(RecordNavigationPositionHandler handler)
+ {
+ handler.onRecordNavigationPosition(this);
+ }
+
+ private final SourcePosition position_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionHandler.java
new file mode 100644
index 0000000..edddc3a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/RecordNavigationPositionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * RecordNavigationPositionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface RecordNavigationPositionHandler extends EventHandler
+{
+ public void onRecordNavigationPosition(RecordNavigationPositionEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileEvent.java
new file mode 100644
index 0000000..c19df59
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileEvent.java
@@ -0,0 +1,36 @@
+/*
+ * SaveFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SaveFileEvent extends GwtEvent<SaveFileHandler>
+{
+ public static final GwtEvent.Type<SaveFileHandler> TYPE =
+ new GwtEvent.Type<SaveFileHandler>();
+
+ @Override
+ protected void dispatch(SaveFileHandler handler)
+ {
+ handler.onSaveFile(this);
+ }
+
+ @Override
+ public GwtEvent.Type<SaveFileHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileHandler.java
new file mode 100644
index 0000000..abd5242
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SaveFileHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SaveFileHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SaveFileHandler extends EventHandler
+{
+ void onSaveFile(SaveFileEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentEvent.java
new file mode 100644
index 0000000..439e7b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ShowContentEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.source.model.ContentItem;
+
+public class ShowContentEvent extends GwtEvent<ShowContentHandler>
+{
+ public static final GwtEvent.Type<ShowContentHandler> TYPE =
+ new GwtEvent.Type<ShowContentHandler>();
+
+ public ShowContentEvent(ContentItem content)
+ {
+ content_ = content;
+ }
+
+ public ContentItem getContent()
+ {
+ return content_;
+ }
+
+ @Override
+ protected void dispatch(ShowContentHandler handler)
+ {
+ handler.onShowContent(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowContentHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private ContentItem content_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentHandler.java
new file mode 100644
index 0000000..a32f24f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowContentHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowContentHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowContentHandler extends EventHandler
+{
+ void onShowContent(ShowContentEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataEvent.java
new file mode 100644
index 0000000..168c89f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataEvent.java
@@ -0,0 +1,49 @@
+/*
+ * ShowDataEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.source.model.DataItem;
+
+public class ShowDataEvent extends GwtEvent<ShowDataHandler>
+{
+ public static final GwtEvent.Type<ShowDataHandler> TYPE =
+ new GwtEvent.Type<ShowDataHandler>();
+
+ public ShowDataEvent(DataItem data)
+ {
+ data_ = data;
+ }
+
+ public DataItem getData()
+ {
+ return data_;
+ }
+
+ @Override
+ protected void dispatch(ShowDataHandler handler)
+ {
+ handler.onShowData(this);
+ }
+
+ @Override
+ public GwtEvent.Type<ShowDataHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ private DataItem data_;
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataHandler.java
new file mode 100644
index 0000000..0034b2c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/ShowDataHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ShowDataHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ShowDataHandler extends EventHandler
+{
+ void onShowData(ShowDataEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceExtendedTypeDetectedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceExtendedTypeDetectedEvent.java
new file mode 100644
index 0000000..d9b0de4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceExtendedTypeDetectedEvent.java
@@ -0,0 +1,73 @@
+/*
+ * SourceExtendedTypeDetectedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceExtendedTypeDetectedEvent extends GwtEvent<SourceExtendedTypeDetectedEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public native final String getDocId() /*-{
+ return this.doc_id;
+ }-*/;
+
+ public native final String getExtendedType() /*-{
+ return this.extended_type;
+ }-*/;
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onSourceExtendedTypeDetected(SourceExtendedTypeDetectedEvent e);
+ }
+
+ public SourceExtendedTypeDetectedEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getDocId()
+ {
+ return data_.getDocId();
+ }
+
+ public String getExtendedType()
+ {
+ return data_.getExtendedType();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSourceExtendedTypeDetected(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedEvent.java
new file mode 100644
index 0000000..4b4c280
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedEvent.java
@@ -0,0 +1,50 @@
+/*
+ * SourceFileSavedEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Fired when an explicit "Save As", or Save of a previously unsaved file,
+ * occurs. Does NOT fire when a Save occurs on a previously saved file.
+ */
+public class SourceFileSavedEvent extends GwtEvent<SourceFileSavedHandler>
+{
+ public static final Type<SourceFileSavedHandler> TYPE = new Type<SourceFileSavedHandler>();
+
+ public SourceFileSavedEvent(String path)
+ {
+ path_ = path;
+ }
+
+ public String getPath()
+ {
+ return path_;
+ }
+
+ @Override
+ public Type<SourceFileSavedHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SourceFileSavedHandler handler)
+ {
+ handler.onSourceFileSaved(this);
+ }
+
+ private final String path_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedHandler.java
new file mode 100644
index 0000000..41e46aa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceFileSavedHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SourceFileSavedHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SourceFileSavedHandler extends EventHandler
+{
+ public void onSourceFileSaved(SourceFileSavedEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationEvent.java
new file mode 100644
index 0000000..bd10364
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationEvent.java
@@ -0,0 +1,48 @@
+/*
+ * SourceNavigationEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import org.rstudio.studio.client.workbench.views.source.model.SourceNavigation;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SourceNavigationEvent extends GwtEvent<SourceNavigationHandler>
+{
+ public static final Type<SourceNavigationHandler> TYPE = new Type<SourceNavigationHandler>();
+
+ public SourceNavigationEvent(SourceNavigation navigation)
+ {
+ navigation_ = navigation;
+ }
+
+ public SourceNavigation getNavigation()
+ {
+ return navigation_;
+ }
+
+ @Override
+ public Type<SourceNavigationHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SourceNavigationHandler handler)
+ {
+ handler.onSourceNavigation(this);
+ }
+
+ private final SourceNavigation navigation_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationHandler.java
new file mode 100644
index 0000000..6fe00a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SourceNavigationHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SourceNavigationHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SourceNavigationHandler extends EventHandler
+{
+ public void onSourceNavigation(SourceNavigationEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocEvent.java
new file mode 100644
index 0000000..8703cb7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocEvent.java
@@ -0,0 +1,46 @@
+/*
+ * SwitchToDocEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SwitchToDocEvent extends GwtEvent<SwitchToDocHandler>
+{
+ public static final Type<SwitchToDocHandler> TYPE = new Type<SwitchToDocHandler>();
+
+ public SwitchToDocEvent(int selectedIndex)
+ {
+ selectedIndex_ = selectedIndex;
+ }
+
+ public int getSelectedIndex()
+ {
+ return selectedIndex_;
+ }
+
+ @Override
+ public Type<SwitchToDocHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SwitchToDocHandler handler)
+ {
+ handler.onSwitchToDoc(this);
+ }
+
+ private final int selectedIndex_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocHandler.java
new file mode 100644
index 0000000..c8b12ef
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/events/SwitchToDocHandler.java
@@ -0,0 +1,22 @@
+/*
+ * SwitchToDocHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface SwitchToDocHandler extends EventHandler
+{
+ void onSwitchToDoc(SwitchToDocEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CheckForExternalEditResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CheckForExternalEditResult.java
new file mode 100644
index 0000000..88726b0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CheckForExternalEditResult.java
@@ -0,0 +1,40 @@
+/*
+ * CheckForExternalEditResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class CheckForExternalEditResult extends JavaScriptObject
+{
+ protected CheckForExternalEditResult()
+ {
+ }
+
+ public native final boolean isModified() /*-{
+ return this.modified;
+ }-*/;
+
+ public native final boolean isDeleted() /*-{
+ return this.deleted;
+ }-*/;
+
+ /**
+ * Only non-null if file was modified and wasn't deleted
+ */
+ public native final FileSystemItem getItem() /*-{
+ return this.item;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CodeBrowserContents.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CodeBrowserContents.java
new file mode 100644
index 0000000..c0ce612
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CodeBrowserContents.java
@@ -0,0 +1,55 @@
+/*
+ * CodeBrowserContents.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import java.util.HashMap;
+
+import org.rstudio.core.client.js.JsObject;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CodeBrowserContents extends JavaScriptObject
+{
+ protected CodeBrowserContents()
+ {
+ }
+
+ public static final native CodeBrowserContents create(String context) /*-{
+ var contents = new Object();
+ contents.context = context;
+ return contents ;
+ }-*/;
+
+
+ public native final String getContext() /*-{
+ return this.context;
+ }-*/;
+
+ public final boolean equalTo(CodeBrowserContents other)
+ {
+ return getContext().equals(other.getContext());
+ }
+
+
+ public final void fillProperties(HashMap<String, String> properties)
+ {
+ properties.put("context", getContext());
+ }
+
+ public final void fillProperties(JsObject properties)
+ {
+ properties.setString("context", getContext());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CompletionOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CompletionOptions.java
new file mode 100644
index 0000000..3d2ad78
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CompletionOptions.java
@@ -0,0 +1,44 @@
+/*
+ * CompletionOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import java.util.ArrayList;
+
+public class CompletionOptions
+{
+ public void addOption(String optionValue,
+ int minPrefixChars)
+ {
+ options_.add(optionValue);
+ filterPrefixes_.add(optionValue.substring(0, minPrefixChars));
+ }
+
+ public ArrayList<String> getCompletions(String prefix)
+ {
+ ArrayList<String> results = new ArrayList<String>();
+ for (int i = 0; i < options_.size(); i++)
+ {
+ if (options_.get(i).startsWith(prefix) &&
+ prefix.startsWith(filterPrefixes_.get(i)))
+ {
+ results.add(options_.get(i));
+ }
+ }
+ return results;
+ }
+
+ private ArrayList<String> options_ = new ArrayList<String>();
+ private ArrayList<String> filterPrefixes_ = new ArrayList<String>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/ContentItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/ContentItem.java
new file mode 100644
index 0000000..b8f06e5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/ContentItem.java
@@ -0,0 +1,41 @@
+/*
+ * ContentItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ContentItem extends JavaScriptObject
+{
+ protected ContentItem()
+ {
+ }
+
+ public static final native ContentItem create(String title,
+ String contentUrl) /*-{
+ var contentItem = new Object();
+ contentItem.title = title ;
+ contentItem.contentUrl = contentUrl ;
+ return contentItem ;
+ }-*/;
+
+
+ public native final String getTitle() /*-{
+ return this.title;
+ }-*/;
+
+ public native final String getContentUrl() /*-{
+ return this.contentUrl;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CppCapabilities.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CppCapabilities.java
new file mode 100644
index 0000000..b5a673b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/CppCapabilities.java
@@ -0,0 +1,44 @@
+/*
+ * CppCapabilities.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CppCapabilities extends JavaScriptObject
+{
+ protected CppCapabilities()
+ {
+ }
+
+ public static final native CppCapabilities createDefault() /*-{
+ var caps = new Object();
+ caps.can_build = false ;
+ caps.can_source_cpp = false ;
+ return caps;
+ }-*/;
+
+ public native final boolean getCanBuild() /*-{
+ return this.can_build;
+ }-*/;
+
+ public native final boolean getCanSourceCpp() /*-{
+ return this.can_source_cpp;
+ }-*/;
+
+ public final boolean hasAllCapabiliites()
+ {
+ return getCanBuild() && getCanSourceCpp();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DataItem.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DataItem.java
new file mode 100644
index 0000000..3309dd6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DataItem.java
@@ -0,0 +1,93 @@
+/*
+ * DataItem.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.js.JsObject;
+
+import java.util.HashMap;
+
+public class DataItem extends JavaScriptObject
+{
+ public static String URI_PREFIX = "data://";
+
+ protected DataItem()
+ {
+ }
+
+ public static final native DataItem create(String caption,
+ int totalObservations,
+ int displayedObservations,
+ int variables,
+ String contentUrl) /*-{
+ var dataItem = new Object();
+ dataItem.caption = caption ;
+ dataItem.totalObservations = totalObservations ;
+ dataItem.displayedObservations = displayedObservations;
+ dataItem.variables = variables;
+ dataItem.contentUrl = contentUrl;
+ return dataItem ;
+ }-*/;
+
+ public final String getURI()
+ {
+ return URI_PREFIX + getCaption();
+ }
+
+ public native final String getCaption() /*-{
+ return this.caption;
+ }-*/;
+
+ public native final int getTotalObservations() /*-{
+ // This will sometimes be a number, sometimes a string. Ugh
+ return this.totalObservations - 0;
+ }-*/;
+
+ public native final int getDisplayedObservations() /*-{
+ // This will sometimes be a number, sometimes a string. Ugh
+ return this.displayedObservations - 0;
+ }-*/;
+
+ public native final int getVariables() /*-{
+ // This will sometimes be a number, sometimes a string. Ugh
+ return this.variables - 0;
+ }-*/;
+
+ public native final String getContentUrl() /*-{
+ return this.contentUrl;
+ }-*/;
+
+ public final void fillProperties(HashMap<String, String> properties)
+ {
+ // This has the unfortunate side-effect of converting the numeric values
+ // to strings. Can't be helped without refactoring
+ // SourceServerOperations#modifyDocumentProperties to take a less typesafe
+ // container type.
+ properties.put("caption", getCaption());
+ properties.put("totalObservations", getTotalObservations() + "");
+ properties.put("displayedObservations", getDisplayedObservations() + "");
+ properties.put("variables", getVariables() + "");
+ properties.put("contentUrl", getContentUrl());
+ }
+
+ public final void fillProperties(JsObject properties)
+ {
+ properties.setString("caption", getCaption());
+ properties.setInteger("totalObservations", getTotalObservations());
+ properties.setInteger("displayedObservations", getDisplayedObservations());
+ properties.setInteger("variables", getVariables());
+ properties.setString("contentUrl", getContentUrl());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DirtyState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DirtyState.java
new file mode 100644
index 0000000..8a83320
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DirtyState.java
@@ -0,0 +1,111 @@
+/*
+ * DirtyState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import org.rstudio.studio.client.common.ReadOnlyValue;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.UndoRedoEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.UndoRedoHandler;
+
+public class DirtyState implements ReadOnlyValue<Boolean>
+{
+ public DirtyState(DocDisplay editor, boolean initialState)
+ {
+ editor_ = editor;
+
+ if (initialState)
+ markDirty(false);
+ else
+ markClean();
+
+ editor_.addUndoRedoHandler(new UndoRedoHandler()
+ {
+ public void onUndoRedo(UndoRedoEvent event)
+ {
+ if (editor_.checkCleanStateToken(cleanUndoStateToken_))
+ markClean();
+ else
+ markDirty(true);
+ }
+ });
+ }
+
+ public void markDirty(boolean allowUndoBackToClean)
+ {
+ if (!allowUndoBackToClean)
+ cleanUndoStateToken_ = JavaScriptObject.createObject();
+
+ if (!value_)
+ {
+ value_ = true;
+ fire(value_);
+ }
+ }
+
+ public void markClean()
+ {
+ cleanUndoStateToken_ = editor_.getCleanStateToken();
+ if (value_)
+ {
+ value_ = false;
+ fire(value_);
+ }
+ }
+
+ public Boolean getValue()
+ {
+ return value_;
+ }
+
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler)
+ {
+ return handlers_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ private void fire(boolean value)
+ {
+ ValueChangeEvent.fire(
+ new HasValueChangeHandlers<Boolean>()
+ {
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<Boolean> handler)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public void fireEvent(GwtEvent<?> event)
+ {
+ DirtyState.this.fireEvent(event);
+ }
+ }, value);
+ }
+
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ private JavaScriptObject cleanUndoStateToken_;
+ private boolean value_;
+ private final DocDisplay editor_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DocUpdateSentinel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DocUpdateSentinel.java
new file mode 100644
index 0000000..d1b796c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/DocUpdateSentinel.java
@@ -0,0 +1,556 @@
+/*
+ * DocUpdateSentinel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import org.rstudio.core.client.Barrier.Token;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.core.client.patch.SubstringDiff;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
+import org.rstudio.studio.client.workbench.events.LastChanceSaveHandler;
+import org.rstudio.studio.client.workbench.model.ChangeTracker;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.Fold;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.FoldChangeEvent;
+import org.rstudio.studio.client.workbench.views.source.editors.text.events.SourceOnSaveChangedEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DocUpdateSentinel
+ implements ValueChangeHandler<Void>,
+ FoldChangeEvent.Handler
+{
+ private class ReopenFileCallback extends ServerRequestCallback<SourceDocument>
+ {
+ public ReopenFileCallback()
+ {
+ }
+
+ public ReopenFileCallback(Command onCompleted)
+ {
+ onCompleted_ = onCompleted;
+ }
+
+ @Override
+ public void onResponseReceived(
+ SourceDocument response)
+ {
+ sourceDoc_ = response;
+ docDisplay_.setCode(sourceDoc_.getContents(), true);
+ dirtyState_.markClean();
+
+ if (progress_ != null)
+ progress_.onCompleted();
+
+ if (onCompleted_ != null)
+ onCompleted_.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (progress_ != null)
+ {
+ progress_.onError(error.getUserMessage());
+ }
+
+ if (onCompleted_ != null)
+ onCompleted_.execute();
+ }
+
+ private Command onCompleted_ = null;
+ }
+
+ public DocUpdateSentinel(SourceServerOperations server,
+ DocDisplay docDisplay,
+ SourceDocument sourceDoc,
+ ProgressIndicator progress,
+ DirtyState dirtyState,
+ EventBus events)
+ {
+ server_ = server;
+ docDisplay_ = docDisplay;
+ sourceDoc_ = sourceDoc;
+ progress_ = progress;
+ dirtyState_ = dirtyState;
+ eventBus_ = events;
+ changeTracker_ = docDisplay.getChangeTracker();
+
+ bufferedCommand_ = new TimeBufferedCommand(2000)
+ {
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ assert !shouldSchedulePassive;
+ maybeAutoSave();
+ }
+ };
+
+ docDisplay_.addValueChangeHandler(this);
+ docDisplay_.addFoldChangeHandler(this);
+
+ // Web only
+ closeHandlerReg_ = Window.addWindowClosingHandler(new ClosingHandler()
+ {
+ public void onWindowClosing(ClosingEvent event)
+ {
+ if (changesPending_)
+ event.setMessage("Some of your source edits are still being " +
+ "backed up. If you continue, your latest " +
+ "changes may be lost. Do you want to continue?");
+ }
+ });
+
+ // Desktop only
+ lastChanceSaveHandlerReg_ = events.addHandler(
+ LastChanceSaveEvent.TYPE,
+ new LastChanceSaveHandler() {
+ public void onLastChanceSave(LastChanceSaveEvent event)
+ {
+ // We're quitting. Save one last time.
+
+ final Token token = event.acquire();
+ boolean saving = doSave(null, null, null,
+ new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ }
+
+ public void clearProgress()
+ {
+ // alternate way to signal completion. safe to quit
+ token.release();
+ }
+
+ public void onCompleted()
+ {
+ // We saved successfully. We're safe to quit now.
+ token.release();
+ }
+
+ public void onError(String message)
+ {
+ // The save didn't succeed. Oh well. Nothing we can
+ // do but quit.
+ token.release();
+ }
+ });
+
+ if (!saving)
+ {
+ // No save was performed (not needed). We're safe to quit
+ // now, no need to wait for server requests to complete.
+ token.release();
+ }
+ }
+ });
+ }
+
+ private boolean maybeAutoSave()
+ {
+ if (changeTracker_.hasChanged())
+ {
+ return doSave(null, null, null, progress_);
+ }
+ else
+ {
+ changesPending_ = false;
+ return false;
+ }
+ }
+
+ public void changeFileType(String fileType, final ProgressIndicator progress)
+ {
+ saveWithSuspendedAutoSave(null, fileType, null, progress);
+ }
+
+ public void save(String path,
+ // fileType==null means don't change value
+ String fileType,
+ // encoding==null means don't change value
+ String encoding,
+ final ProgressIndicator progress)
+ {
+ assert path != null;
+ if (path == null)
+ throw new IllegalArgumentException("Path cannot be null");
+ saveWithSuspendedAutoSave(path, fileType, encoding, progress);
+ }
+
+ private void saveWithSuspendedAutoSave(String path,
+ String fileType,
+ String encoding,
+ final ProgressIndicator progress)
+ {
+ bufferedCommand_.suspend();
+ doSave(path, fileType, encoding, new ProgressIndicator()
+ {
+ public void onProgress(String message)
+ {
+ if (progress != null)
+ progress.onProgress(message);
+ }
+
+ public void clearProgress()
+ {
+ bufferedCommand_.resume();
+ if (progress != null)
+ progress.clearProgress();
+ }
+
+ public void onCompleted()
+ {
+ bufferedCommand_.resume();
+ if (progress != null)
+ progress.onCompleted();
+ }
+
+ public void onError(String message)
+ {
+ bufferedCommand_.resume();
+ if (progress != null)
+ progress.onError(message);
+ }
+ });
+ }
+
+ private boolean doSave(final String path,
+ final String fileType,
+ final String encoding,
+ final ProgressIndicator progress)
+ {
+ /* We need to fork the change tracker so that we can "mark" the moment
+ in history when we took the contents from the source doc, so that
+ if the document is edited while the save is in progress we don't
+ reset the true change tracker's state to a version we haven't
+ actually sent to the server. */
+ final ChangeTracker thisChangeTracker = changeTracker_.fork();
+
+ final String newContents = docDisplay_.getCode();
+ String oldContents = sourceDoc_.getContents();
+ final String hash = sourceDoc_.getHash();
+
+ final String foldSpec = Fold.encode(Fold.flatten(docDisplay_.getFolds()));
+ String oldFoldSpec = sourceDoc_.getFoldSpec();
+
+ //String patch = DiffMatchPatch.diff(oldContents, newContents);
+ SubstringDiff diff = new SubstringDiff(oldContents, newContents);
+
+ // Don't auto-save when there are no changes. In addition to being
+ // wasteful, it causes the server to think the document is dirty.
+ if (path == null && fileType == null && diff.isEmpty()
+ && foldSpec.equals(oldFoldSpec))
+ {
+ changesPending_ = false;
+ return false;
+ }
+
+ if (path == null && fileType == null
+ && oldContents.length() == 0
+ && newContents.equals("\n"))
+ {
+ // This is necessary due to us adding an extra \n to empty
+ // documents, which we have to do or else CodeMirror starts
+ // acting funny. If we add the extra \n but don't do this
+ // check, then reloading the browser causes empty documents
+ // to appear dirty.
+ changesPending_ = false;
+ return false;
+ }
+
+ server_.saveDocumentDiff(
+ sourceDoc_.getId(),
+ path,
+ fileType,
+ encoding,
+ foldSpec,
+ diff.getReplacement(),
+ diff.getOffset(),
+ diff.getLength(),
+ hash,
+ new ServerRequestCallback<String>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ if (progress != null)
+ progress.onError(error.getUserMessage());
+ changesPending_ = false;
+ }
+
+ @Override
+ public void onResponseReceived(String newHash)
+ {
+ if (newHash != null)
+ {
+ // If the document hasn't changed further since the version
+ // we saved, then we know we're all synced up.
+ if (!thisChangeTracker.hasChanged())
+ changeTracker_.reset();
+
+ onSuccessfulUpdate(newContents,
+ newHash,
+ path,
+ fileType,
+ encoding);
+ if (progress != null)
+ progress.onCompleted();
+ }
+ else if (!hash.equals(sourceDoc_.getHash()))
+ {
+ // We just hit a race condition where two updates
+ // happened at once. Try again
+ doSave(path, fileType, encoding, progress);
+ }
+ else
+ {
+ /*Debug.log("Diff-based save failed--falling back to " +
+ "snapshot save");*/
+ server_.saveDocument(
+ sourceDoc_.getId(),
+ path,
+ fileType,
+ encoding,
+ foldSpec,
+ newContents,
+ this);
+ }
+ }
+ });
+
+ return true;
+ }
+
+ private void onSuccessfulUpdate(String contents,
+ String hash,
+ String path,
+ String fileType,
+ String encoding)
+ {
+ changesPending_ = false;
+ sourceDoc_.setContents(contents);
+ sourceDoc_.setHash(hash);
+ if (path != null)
+ {
+ sourceDoc_.setDirty(false);
+ sourceDoc_.setPath(path);
+ }
+ if (fileType != null)
+ {
+ sourceDoc_.setType(fileType);
+ }
+ if (encoding != null)
+ sourceDoc_.setEncoding(encoding);
+ }
+
+ public boolean sourceOnSave()
+ {
+ return sourceDoc_.sourceOnSave();
+ }
+
+ public void setSourceOnSave(final boolean shouldSourceOnSave,
+ final ProgressIndicator progress)
+ {
+ if (sourceDoc_.sourceOnSave() == shouldSourceOnSave)
+ return;
+
+ server_.setSourceDocumentOnSave(
+ sourceDoc_.getId(),
+ shouldSourceOnSave,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ if (progress != null)
+ progress.onError(error.getUserMessage());
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ sourceDoc_.setSourceOnSave(shouldSourceOnSave);
+
+ eventBus_.fireEvent(new SourceOnSaveChangedEvent());
+
+ if (progress != null)
+ progress.onCompleted();
+ }
+ });
+ }
+
+ public String getProperty(String propertyName)
+ {
+ JsObject properties = sourceDoc_.getProperties();
+ return properties.getString(propertyName);
+ }
+
+ public void setProperty(String name,
+ String value,
+ ProgressIndicator progress)
+ {
+ HashMap<String, String> props = new HashMap<String, String>();
+ props.put(name, value);
+ modifyProperties(props, progress);
+ }
+
+ /**
+ * Applies the values in the given HashMap to the document's property bag.
+ * This does NOT replace all of the doc's properties on the server; any
+ * properties that already exist but are not present in the HashMap, are
+ * left unchanged. If a HashMap entry has a null value, that property
+ * should be removed.
+ */
+ public void modifyProperties(final HashMap<String, String> properties,
+ final ProgressIndicator progress)
+ {
+ server_.modifyDocumentProperties(
+ sourceDoc_.getId(),
+ properties,
+ new ServerRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ if (progress != null)
+ progress.onError(error.getUserMessage());
+ }
+
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ applyProperties(sourceDoc_.getProperties(), properties);
+ if (progress != null)
+ progress.onCompleted();
+ }
+ });
+ }
+
+ private void applyProperties(JsObject properties,
+ HashMap<String, String> newProperties)
+ {
+ for (Map.Entry<String, String> entry : newProperties.entrySet())
+ {
+ if (entry.getValue() == null)
+ properties.unset(entry.getKey());
+ else
+ properties.setString(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public void onValueChange(ValueChangeEvent<Void> voidValueChangeEvent)
+ {
+ changesPending_ = true;
+ bufferedCommand_.nudge();
+ }
+
+ @Override
+ public void onFoldChange(FoldChangeEvent event)
+ {
+ changesPending_ = true;
+ bufferedCommand_.nudge();
+ }
+
+ public String getPath()
+ {
+ return sourceDoc_.getPath();
+ }
+
+ public String getContents()
+ {
+ return sourceDoc_.getContents();
+ }
+
+ public void stop()
+ {
+ bufferedCommand_.suspend();
+ closeHandlerReg_.removeHandler();
+ lastChanceSaveHandlerReg_.removeHandler();
+ }
+
+ public void revert()
+ {
+ revert(null);
+ }
+
+ public void revert(Command onCompleted)
+ {
+ server_.revertDocument(
+ sourceDoc_.getId(),
+ sourceDoc_.getType(),
+ new ReopenFileCallback(onCompleted));
+ }
+
+ public void reopenWithEncoding(String encoding)
+ {
+ server_.reopenWithEncoding(
+ sourceDoc_.getId(),
+ encoding,
+ new ReopenFileCallback());
+ }
+
+ public void ignoreExternalEdit()
+ {
+ // Warning: This leaves the sourceDoc_ with a stale LastModifiedDate
+ // but we don't use it.
+ server_.ignoreExternalEdit(sourceDoc_.getId(),
+ new SimpleRequestCallback<Void>());
+ }
+
+ public String getEncoding()
+ {
+ return sourceDoc_.getEncoding();
+ }
+
+ public boolean isAscii()
+ {
+ String code = docDisplay_.getCode();
+ for (int i = 0; i < code.length(); i++)
+ if (code.charAt(i) >= 128)
+ return false;
+ return true;
+ }
+
+ private boolean changesPending_ = false;
+ private final ChangeTracker changeTracker_;
+ private final SourceServerOperations server_;
+ private final DocDisplay docDisplay_;
+ private SourceDocument sourceDoc_;
+ private final ProgressIndicator progress_;
+ private final DirtyState dirtyState_;
+ private final EventBus eventBus_;
+ private final TimeBufferedCommand bufferedCommand_;
+ private final HandlerRegistration closeHandlerReg_;
+ private HandlerRegistration lastChanceSaveHandlerReg_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RdShellResult.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RdShellResult.java
new file mode 100644
index 0000000..f558010
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RdShellResult.java
@@ -0,0 +1,35 @@
+/*
+ * RdShellResult.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/*
+ * An RdShellResult will either have a path or have contents (but never both)
+ */
+public class RdShellResult extends JavaScriptObject
+{
+ protected RdShellResult()
+ {
+ }
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final String getContents() /*-{
+ return this.contents;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwChunkOptions.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwChunkOptions.java
new file mode 100644
index 0000000..376ab75
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwChunkOptions.java
@@ -0,0 +1,257 @@
+/*
+ * RnwChunkOptions.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONObject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.common.r.RToken;
+import org.rstudio.studio.client.common.r.RTokenizer;
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Stack;
+
+public class RnwChunkOptions extends JavaScriptObject
+{
+ protected RnwChunkOptions()
+ {
+ }
+
+ public final ArrayList<String> getOptions()
+ {
+ ArrayList<String> options = new ArrayList<String>(
+ new JSONObject(this).keySet());
+ Collections.sort(options);
+ return options;
+ }
+
+ // types: "numeric", "character", "logical", "list", or null for unknown
+ public final String getOptionType(String name)
+ {
+ if (!hasOption(name))
+ return null;
+
+ JSONArray arr = new JSONArray(getOptionTypeNative(name));;
+ if (arr.size() == 1)
+ return arr.get(0).isString().stringValue();
+ else
+ return "list";
+ }
+
+ public final ArrayList<String> getOptionValues(String name)
+ {
+ JSONArray arr = new JSONArray(getOptionTypeNative(name));
+ ArrayList<String> values = new ArrayList<String>();
+ for (int i=0; i<arr.size(); i++)
+ values.add(arr.get(i).isArray().get(0).isString().stringValue());
+ return values;
+ }
+
+ private native final JavaScriptObject getOptionTypeNative(String name) /*-{
+ return this[name];
+ }-*/;
+
+ private native final boolean hasOption(String name) /*-{
+ return typeof(this[name]) != 'undefined';
+ }-*/;
+
+ public static class RnwOptionCompletionResult
+ {
+ public String token;
+ public JsArrayString completions;
+ }
+
+ public final RnwOptionCompletionResult getCompletions(String line,
+ int optionsStartOffset,
+ int cursorPos,
+ RnwWeave rnwWeave)
+ {
+ assert cursorPos >= optionsStartOffset :
+ "cursorPos was less than optionsStartOffset";
+
+ String linePart = line.substring(optionsStartOffset, cursorPos);
+
+ // This can be pretty simple because Noweb doesn't allow = or , to appear
+ // in names or values (i.e. no quotes or escaping to make parsing more
+ // complicated).
+
+ String token = null;
+ JsArrayString completions = JsArrayString.createArray().cast();
+ ArrayList<String> names = new ArrayList<String>();
+ ArrayList<String> values = new ArrayList<String>();
+ parseRnwChunkHeader(linePart, names, values);
+
+ assert names.size() == values.size();
+
+ String name = names.size() == 0
+ ? null : names.get(names.size()-1);
+ String value = values.size() == 0
+ ? null : values.get(values.size()-1);
+
+ if (value != null)
+ {
+ token = value;
+ // If value is not null, we follow an equal sign; try to complete
+ // based on value.
+ completeValue(rnwWeave, name, value, completions);
+ }
+ else if (name != null)
+ {
+ token = name;
+ for (String optionName : this.getOptions())
+ if (optionName.startsWith(name))
+ completions.push(optionName + "=");
+ }
+
+ RnwOptionCompletionResult result = new RnwOptionCompletionResult();
+ result.token = token;
+ result.completions = completions;
+ return result;
+ }
+
+ private void completeValue(RnwWeave rnwWeave,
+ String name,
+ String value,
+ JsArrayString completions)
+ {
+ String optionType = StringUtil.notNull(this.getOptionType(name));
+ if (optionType.equals("logical"))
+ {
+ CompletionOptions options = new CompletionOptions();
+ options.addOption("TRUE", 0);
+ options.addOption("FALSE", 0);
+ if (!rnwWeave.usesCodeForOptions())
+ {
+ // Legacy Sweave is case insensitive
+ options.addOption("true", 1);
+ options.addOption("false", 1);
+ options.addOption("True", 2);
+ options.addOption("False", 2);
+ }
+ for (String logical : options.getCompletions(value))
+ completions.push(logical);
+ }
+ else if (optionType.equals("list"))
+ {
+ CompletionOptions options = new CompletionOptions();
+ ArrayList<String> optionValues = this.getOptionValues(name);
+ if (!rnwWeave.usesCodeForOptions())
+ {
+ // Legacy Sweave
+ for (String optionVal : optionValues)
+ options.addOption(optionVal, 0);
+ }
+ else
+ {
+ for (String optionVal : optionValues)
+ options.addOption("'" + optionVal + "'", 0);
+ for (String optionVal : optionValues)
+ options.addOption('"' + optionVal + '"', 1);
+ }
+
+ for (String option : options.getCompletions(value))
+ completions.push(option);
+ }
+ }
+
+ private static void parseRnwChunkHeader(String line,
+ ArrayList<String> names,
+ ArrayList<String> values)
+ {
+ String currentName = null;
+ String currentValue = null;
+
+ int currentPartBegin = 0;
+ Stack<Integer> braceStack = new Stack<Integer>();
+
+ RTokenizer tokenizer = new RTokenizer(line);
+ for (RToken token; null != (token = tokenizer.nextToken()); )
+ {
+ switch (token.getTokenType())
+ {
+ case RToken.OPER:
+ if (token.getContent().equals("=") &&
+ currentName == null &&
+ braceStack.empty())
+ {
+ String part = line.substring(currentPartBegin,
+ token.getOffset());
+ currentName = part;
+ currentPartBegin = token.getOffset() + token.getLength();
+ }
+ break;
+ case RToken.COMMA:
+ if (braceStack.empty())
+ {
+ String part = line.substring(currentPartBegin,
+ token.getOffset());
+ if (currentName == null)
+ currentName = part;
+ else
+ currentValue = part;
+
+ names.add(currentName.trim());
+ values.add(currentValue != null ?
+ StringUtil.trimLeft(currentValue) :
+ null);
+ currentName = null;
+ currentValue = null;
+
+ currentPartBegin = token.getOffset() + token.getLength();
+ }
+ break;
+
+ case RToken.LBRACE: braceStack.push(RToken.RBRACE); break;
+ case RToken.LBRACKET: braceStack.push(RToken.RBRACKET); break;
+ case RToken.LDBRACKET: braceStack.push(RToken.RDBRACKET); break;
+ case RToken.LPAREN: braceStack.push(RToken.RPAREN); break;
+
+ case RToken.RBRACE:
+ case RToken.RBRACKET:
+ case RToken.RDBRACKET:
+ case RToken.RPAREN:
+ int distance = braceStack.search(token.getTokenType());
+ if (distance > 0)
+ {
+ for (int i = 0; i < distance; i++)
+ braceStack.pop();
+ }
+ break;
+ }
+ }
+
+ String part = line.substring(currentPartBegin,
+ line.length());
+ if (currentName == null)
+ currentName = part;
+ else
+ currentValue = part;
+
+ if (currentValue == null)
+ {
+ names.add(StringUtil.trimLeft(currentName));
+ values.add(null);
+ }
+ else
+ {
+ names.add(currentName.trim());
+ values.add(StringUtil.trimLeft(currentValue));
+ }
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwCompletionContext.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwCompletionContext.java
new file mode 100644
index 0000000..54cb28b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/RnwCompletionContext.java
@@ -0,0 +1,34 @@
+/*
+ * RnwCompletionContext.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import org.rstudio.studio.client.common.rnw.RnwWeave;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public interface RnwCompletionContext
+{
+ void getChunkOptions(
+ ServerRequestCallback<RnwChunkOptions> requestCallback);
+
+ RnwWeave getActiveRnwWeave();
+
+ /**
+ * Determines whether the given line and cursor position are a valid place
+ * to attempt an Rnw options completion. If not, -1 is returned. If so,
+ * the number returned is the index into <code>line</code> where the options
+ * start.
+ */
+ int getRnwOptionsStart(String line, int cursorPos);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceDocument.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceDocument.java
new file mode 100644
index 0000000..4e8b474
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceDocument.java
@@ -0,0 +1,125 @@
+/*
+ * SourceDocument.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import org.rstudio.core.client.js.JsObject;
+
+public class SourceDocument extends JavaScriptObject
+{
+ protected SourceDocument()
+ {
+ }
+
+ /**
+ * A unique ID that identifies this document. This can't simply be the
+ * path, because multiple documents that have never been saved can be
+ * opened at the same time. (And in theory you could also have the same
+ * file opened as multiple documents with independent state.)
+ */
+ public native final String getId() /*-{
+ return this.id;
+ }-*/;
+
+ /**
+ * Gets the path where this file was last saved (or opened from). This may
+ * change over the lifetime of this document if Save As is used. It may
+ * also be null if the document has never been saved.
+ */
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final void setPath(String path) /*-{
+ this.path = path;
+ }-*/;
+
+ public native final String getType() /*-{
+ return this.type;
+ }-*/;
+
+ public native final void setType(String type) /*-{
+ this.type = type;
+ }-*/;
+
+ public native final String getExtendedType() /*-{
+ return this.extended_type;
+ }-*/;
+
+ public native final void setExtendedType(String extendedType) /*-{
+ this.extended_type = extendedType;
+ }-*/;
+
+ /**
+ * Gets the contents of the file.
+ */
+ public native final String getContents() /*-{
+ return this.contents;
+ }-*/;
+
+ public native final void setContents(String contents) /*-{
+ this.contents = contents;
+ }-*/;
+
+ /**
+ * True if changes have been saved to the ID that have not been persisted
+ * to the file.
+ */
+ public native final boolean isDirty() /*-{
+ return this.dirty;
+ }-*/;
+
+ public native final void setDirty(boolean dirty) /*-{
+ this.dirty = dirty;
+ }-*/;
+
+ public native final String getHash() /*-{
+ return this.hash;
+ }-*/;
+
+ public native final void setHash(String hash) /*-{
+ this.hash = hash;
+ }-*/;
+
+ public native final boolean sourceOnSave() /*-{
+ return this.source_on_save;
+ }-*/;
+
+ public native final void setSourceOnSave(boolean sourceOnSave) /*-{
+ this.source_on_save = sourceOnSave;
+ }-*/;
+
+ public native final String getEncoding() /*-{
+ return this.encoding || "";
+ }-*/;
+
+ public native final void setEncoding(String encoding) /*-{
+ this.encoding = encoding;
+ }-*/;
+
+ public native final JsObject getProperties() /*-{
+ if (!this.properties)
+ this.properties = {};
+ return this.properties;
+ }-*/;
+
+ public native final String getFoldSpec() /*-{
+ return this.folds || "";
+ }-*/;
+
+ public native final void setFoldSpec(String foldSpec) /*-{
+ this.folds = foldSpec;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigation.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigation.java
new file mode 100644
index 0000000..b08a0a7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigation.java
@@ -0,0 +1,74 @@
+/*
+ * SourceNavigation.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SourceNavigation extends JavaScriptObject
+{
+ protected SourceNavigation()
+ {
+ }
+
+ public static final native SourceNavigation create(
+ String document_id,
+ String path,
+ SourcePosition position) /*-{
+ var sourceNavPosition = new Object();
+ sourceNavPosition.document_id = document_id;
+ sourceNavPosition.path = path;
+ sourceNavPosition.position = position;
+ return sourceNavPosition ;
+ }-*/;
+
+ public native final String getDocumentId() /*-{
+ return this.document_id;
+ }-*/;
+
+ public native final String getPath() /*-{
+ return this.path;
+ }-*/;
+
+ public native final SourcePosition getPosition() /*-{
+ return this.position;
+ }-*/;
+
+ public final boolean isEqualTo(SourceNavigation other)
+ {
+ return isAtSameRowAs(other) &&
+ (getPosition().getColumn() == other.getPosition().getColumn());
+ }
+
+ public final boolean isAtSameRowAs(SourceNavigation other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+ else
+ {
+ return getDocumentId().equals(other.getDocumentId()) &&
+ getPosition().isSameRowAs(other.getPosition());
+ }
+ }
+
+ public final String toDebugString()
+ {
+ return (getPath() != null ? getPath() : getDocumentId()) + " (" +
+ (getPosition().getContext() != null ? getPosition().getContext() + " = " : "") +
+ getPosition().getRow() + ", " + getPosition().getColumn() + ")";
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigationHistory.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigationHistory.java
new file mode 100644
index 0000000..e864133
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceNavigationHistory.java
@@ -0,0 +1,154 @@
+/*
+ * SourceNavigationHistory.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import java.util.LinkedList;
+
+import org.rstudio.core.client.Debug;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+
+
+public class SourceNavigationHistory
+{
+ public interface Filter
+ {
+ boolean includeEntry(SourceNavigation navigation);
+ }
+
+ public SourceNavigationHistory(int maxItems)
+ {
+ maxItems_ = maxItems;
+ history_ = new LinkedList<SourceNavigation>();
+ currentLocation_ = -1;
+ }
+
+ public void add(SourceNavigation navigation)
+ {
+ // rewind the history to the current location
+ while ( (history_.size() - 1) > currentLocation_)
+ history_.removeLast();
+
+ // screen out duplicates
+ if ( (history_.size() == 0) || !history_.getLast().isAtSameRowAs(navigation))
+ {
+ // implement capacity restriction
+ if (history_.size() == maxItems_)
+ history_.removeFirst();
+
+ // add the item and set the current location
+ history_.add(navigation);
+ currentLocation_ = history_.size() - 1;
+ }
+
+ fireChangeEvent();
+ }
+
+
+ public void clear()
+ {
+ history_.clear();
+ currentLocation_ = -1;
+ fireChangeEvent();
+ }
+
+ public boolean isBackEnabled()
+ {
+ return currentLocation_ >= 0;
+ }
+
+ public boolean isForwardEnabled()
+ {
+ return currentLocation_ < (history_.size() - 1);
+ }
+
+ public SourceNavigation scanBack(Filter filter)
+ {
+ if (!isBackEnabled())
+ return null;
+
+ for (int i=currentLocation_; i >= 0; i--)
+ {
+ if (filter.includeEntry(history_.get(i)))
+ return history_.get(i);
+ }
+
+ return null;
+ }
+
+ public SourceNavigation goBack()
+ {
+ if (!isBackEnabled())
+ return null;
+
+ SourceNavigation navigation = history_.get(currentLocation_--);
+
+ // if we have only one more item in the stack and it matches
+ // this one then clear the history
+ if (isBackEnabled() &&
+ (history_.size() == 1) &&
+ navigation.isAtSameRowAs(history_.get(currentLocation_)))
+ {
+ clear();
+ }
+
+ fireChangeEvent();
+
+ return navigation;
+
+ }
+
+ public SourceNavigation goForward()
+ {
+ if (!isForwardEnabled())
+ return null;
+
+ SourceNavigation navigation = history_.get(++currentLocation_);
+ fireChangeEvent();
+ return navigation;
+ }
+
+ public HandlerRegistration addChangeHandler(ChangeHandler handler)
+ {
+ return handlerManager_.addHandler(ChangeEvent.getType(), handler);
+ }
+
+ private void fireChangeEvent()
+ {
+ DomEvent.fireNativeEvent(Document.get().createChangeEvent(),
+ handlerManager_);
+
+ }
+
+ @SuppressWarnings("unused")
+ private void debugPrintCurrentHistory()
+ {
+ Debug.log("HISTORY (location=" + currentLocation_ + ")");
+ for (int i=0; i<history_.size(); i++)
+ Debug.log(history_.get(i).toDebugString());
+ Debug.log("");
+ }
+
+ private final int maxItems_;
+ private LinkedList<SourceNavigation> history_;
+ private int currentLocation_;
+ private HandlerManager handlerManager_ = new HandlerManager(this);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourcePosition.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourcePosition.java
new file mode 100644
index 0000000..ed156fe
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourcePosition.java
@@ -0,0 +1,79 @@
+/*
+ * SourcePosition.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class SourcePosition extends JavaScriptObject
+{
+ protected SourcePosition() {}
+
+ public static native SourcePosition create(int row,
+ int column) /*-{
+ return {context: null, row: row, column: column, scroll_position: -1};
+ }-*/;
+
+ public static native SourcePosition create(String context,
+ int row,
+ int column,
+ int scrollPosition) /*-{
+ return {context: context, row: row, column: column, scroll_position: scrollPosition};
+ }-*/;
+
+ /*
+ * NOTE: optional context for editors that have multiple internal
+ * contexts with independent rows & columns (e.g. code browser)
+ * this will be null for some implementations including TextEditingTarget
+ */
+ public native final String getContext() /*-{
+ return this.context;
+ }-*/;
+
+ public native final int getRow() /*-{
+ return this.row;
+ }-*/;
+
+ public native final int getColumn() /*-{
+ return this.column;
+ }-*/;
+
+ /*
+ * NOTE: optional scroll position -- can be -1 to indicate no
+ * scroll position recorded
+ */
+ public native final int getScrollPosition() /*-{
+ return this.scroll_position;
+ }-*/;
+
+ public final boolean isSameRowAs(SourcePosition other)
+ {
+ if (getContext() == null && other.getContext() == null)
+ return other.getRow() == getRow();
+ else if (getContext() == null && other.getContext() != null)
+ return false;
+ else if (other.getContext() == null && getContext() != null)
+ return false;
+ else
+ return other.getContext().equals(getContext()) &&
+ (other.getRow() == getRow());
+ }
+
+ public final Position asPosition()
+ {
+ return Position.create(getRow(), getColumn());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceServerOperations.java
new file mode 100644
index 0000000..9af17ea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/SourceServerOperations.java
@@ -0,0 +1,185 @@
+/*
+ * SourceServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import com.google.gwt.core.client.JsArrayString;
+import org.rstudio.core.client.js.JsObject;
+import org.rstudio.studio.client.common.codetools.CodeToolsServerOperations;
+import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewServerOperations;
+import org.rstudio.studio.client.notebook.CompileNotebookOptions;
+import org.rstudio.studio.client.notebook.CompileNotebookResult;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.codesearch.model.CodeSearchServerOperations;
+import org.rstudio.studio.client.workbench.views.buildtools.model.BuildServerOperations;
+import org.rstudio.studio.client.workbench.views.files.model.FilesServerOperations;
+import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
+import org.rstudio.studio.client.workbench.views.source.editors.text.IconvListResult;
+
+import java.util.HashMap;
+
+/**
+ * The server manages a "working list" of documents that are being edited by
+ * the user. The working list must contain the current "live" contents of
+ * the tab (within a tolerance of a few seconds of latency) regardless of
+ * whether the user has actually hit save.
+ */
+public interface SourceServerOperations extends FilesServerOperations,
+ CodeToolsServerOperations,
+ CodeSearchServerOperations,
+ TexServerOperations,
+ HTMLPreviewServerOperations,
+ BuildServerOperations,
+ PresentationServerOperations
+
+{
+ /**
+ * Create a new, empty document, without a path but with a unique ID, and
+ * appends it to the current working list.
+ *
+ * The unique ID is necessary to distinguish between multiple docs that
+ * have never been saved.
+ */
+ void newDocument(String fileType,
+ String contents,
+ JsObject properties,
+ ServerRequestCallback<SourceDocument> requestCallback);
+
+ /**
+ * Opens a document from disk, assigns it a unique ID, and appends it to
+ * the current working list.
+ */
+ void openDocument(String path,
+ String fileType,
+ String encoding,
+ ServerRequestCallback<SourceDocument> requestCallback);
+
+ /**
+ * Saves the given contents for the given ID, and optionally saves it to
+ * a path on disk.
+ *
+ * If path is null, then this is an autosave operation--nothing is written
+ * to the persistent area of disk, whether this ID has a path associated
+ * with it or not.
+ *
+ * If path is non-null, then it's a Save or Save As operation. Data will
+ * be written to the actual file path, and the ID will now be associated
+ * with that path.
+ */
+ void saveDocument(String id,
+ String path,
+ String fileType,
+ String encoding,
+ String foldSpec,
+ String contents,
+ ServerRequestCallback<String> requestCallback);
+
+ /**
+ * Same as saveDocument, but instead of sending the full contents, just
+ * a diff is sent, along with a hash of the contents it expects the server
+ * to currently have (before the diff is applied).
+ *
+ * Note in particular that the semantics for the path parameter is the
+ * same as saveDocument.
+ *
+ * If the return value is null, the save failed for some reason and
+ * saveDocument() should be used as a fallback. If the return value is
+ * non-null, it is the hash value of the new contents.
+ */
+ void saveDocumentDiff(String id,
+ String path,
+ String fileType,
+ String encoding,
+ String foldSpec,
+ String replacement,
+ int offset,
+ int length,
+ String hash,
+ ServerRequestCallback<String> requestCallback);
+
+ void checkForExternalEdit(
+ String id,
+ ServerRequestCallback<CheckForExternalEditResult> requestCallback);
+
+ void ignoreExternalEdit(String id,
+ ServerRequestCallback<Void> requestCallback);
+
+ /**
+ * Removes an item from the working list.
+ */
+ void closeDocument(String id, ServerRequestCallback<Void> requestCallback);
+
+ /**
+ * Clears the working list.
+ */
+ void closeAllDocuments(ServerRequestCallback<Void> requestCallback);
+
+ void setSourceDocumentOnSave(String id, boolean shouldSourceOnSave,
+ ServerRequestCallback<Void> requestCallback);
+
+ void saveActiveDocument(String contents,
+ boolean sweave,
+ String rnwWeave,
+ ServerRequestCallback<Void> requestCallback);
+
+ /**
+ * Applies the values in the given HashMap to the document's property bag.
+ * This does NOT replace all of the doc's properties on the server; any
+ * properties that already exist but are not present in the HashMap, are
+ * left unchanged. If a HashMap entry has a null value, that property
+ * should be removed.
+ * These properties are durable (they exist even after the document closed).
+ * This makes them suitable for long-term document meta-data such as
+ * publishing history. However, note that they are actually associated with
+ * the path rather than the document and are not currently cleaned up if
+ * files are deleted. Therefore they can be "resurrected" to be associated
+ * with a different file if another file with the same path is created.
+ */
+ void modifyDocumentProperties(String id, HashMap<String, String> properties,
+ ServerRequestCallback<Void> requestCallback);
+
+ void revertDocument(String id,
+ String fileType,
+ ServerRequestCallback<SourceDocument> requestCallback);
+
+ void reopenWithEncoding(
+ String id,
+ String encoding,
+ ServerRequestCallback<SourceDocument> requestCallback);
+
+ void removeContentUrl(String contentUrl,
+ ServerRequestCallback<Void> requestCallback);
+
+ void detectFreeVars(String code,
+ ServerRequestCallback<JsArrayString> requestCallback);
+
+ void iconvlist(ServerRequestCallback<IconvListResult> requestCallback);
+
+ void getSourceTemplate(String name,
+ String template,
+ ServerRequestCallback<String> requestCallback);
+
+ void createRdShell(String name,
+ String type,
+ ServerRequestCallback<RdShellResult> requestCallback);
+
+
+ void createNotebook(
+ CompileNotebookOptions options,
+ ServerRequestCallback<CompileNotebookResult> requestCallback);
+
+ void isReadOnlyFile(String path,
+ ServerRequestCallback<Boolean> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/TexServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/TexServerOperations.java
new file mode 100644
index 0000000..84b38d0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/source/model/TexServerOperations.java
@@ -0,0 +1,27 @@
+/*
+ * TexServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.source.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.TexCapabilities;
+
+public interface TexServerOperations
+{
+ void getTexCapabilities(
+ ServerRequestCallback<TexCapabilities> requestCallback);
+
+ void getChunkOptions(String weaveType,
+ ServerRequestCallback<RnwChunkOptions> requestCallback);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BaseVcsPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BaseVcsPresenter.java
new file mode 100644
index 0000000..8586f92
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BaseVcsPresenter.java
@@ -0,0 +1,49 @@
+/*
+ * BaseVcsPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.GitHubViewRequest;
+
+public abstract class BaseVcsPresenter extends BasePresenter
+{
+ protected BaseVcsPresenter(WorkbenchView view)
+ {
+ super(view);
+ }
+
+ public abstract void onVcsCommit();
+
+ public abstract void onVcsShowHistory();
+
+ public abstract void onVcsPull();
+
+ public abstract void onVcsPush();
+
+ public abstract void onVcsCleanup();
+
+ public abstract void onVcsIgnore();
+
+ public abstract void showHistory(FileSystemItem fileFilter);
+
+ public abstract void showDiff(FileSystemItem file);
+
+ public abstract void revertFile(FileSystemItem file);
+
+ public abstract void viewOnGitHub(GitHubViewRequest viewRequest);
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BranchToolbarButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BranchToolbarButton.java
new file mode 100644
index 0000000..de1dc49
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/BranchToolbarButton.java
@@ -0,0 +1,121 @@
+/*
+ * BranchToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.widget.ScrollableToolbarPopupMenu;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+public class BranchToolbarButton extends ToolbarButton
+ implements HasValueChangeHandlers<String>,
+ VcsRefreshHandler
+{
+ protected class SwitchBranchCommand implements Command
+ {
+ public SwitchBranchCommand(String branchLabel, String branchValue)
+ {
+ branchLabel_ = branchLabel;
+ branchValue_ = branchValue;
+ }
+
+ @Override
+ public void execute()
+ {
+ setBranchCaption(branchLabel_);
+ ValueChangeEvent.fire(BranchToolbarButton.this, branchValue_);
+ }
+
+ private final String branchLabel_;
+ private final String branchValue_;
+ }
+
+ @Inject
+ public BranchToolbarButton(final Provider<GitState> pVcsState)
+ {
+ super("",
+ StandardIcons.INSTANCE.empty_command(),
+ new ScrollableToolbarPopupMenu());
+ pVcsState_ = pVcsState;
+
+ setTitle("Switch branch");
+
+ new WidgetHandlerRegistration(this)
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return pVcsState.get().addVcsRefreshHandler(
+ BranchToolbarButton.this, true);
+ }
+ };
+ }
+
+ public void setBranchCaption(String caption)
+ {
+ if (StringUtil.isNullOrEmpty(caption))
+ caption = NO_BRANCH;
+
+ setText(caption);
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ ToolbarPopupMenu rootMenu = getMenu();
+ rootMenu.clearItems();
+ JsArrayString branches = pVcsState_.get().getBranchInfo()
+ .getBranches();
+
+ onBeforePopulateMenu(rootMenu);
+ for (int i = 0; i < branches.length(); i++)
+ {
+ String branch = branches.get(i);
+ final String branchLabel = branch.replaceFirst("^remotes/", "");
+ final String branchValue = branch.replaceFirst(" ->.*", "");
+ rootMenu.addItem(new MenuItem(branchLabel,
+ new SwitchBranchCommand(branchLabel,
+ branchValue)));
+ }
+ }
+
+ protected void onBeforePopulateMenu(ToolbarPopupMenu rootMenu)
+ {
+ }
+
+ protected final Provider<GitState> pVcsState_;
+
+ private static final String NO_BRANCH = "(No branch)";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/CheckoutBranchToolbarButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/CheckoutBranchToolbarButton.java
new file mode 100644
index 0000000..339cbaa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/CheckoutBranchToolbarButton.java
@@ -0,0 +1,68 @@
+/*
+ * CheckoutBranchToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import com.google.inject.Provider;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.inject.Inject;
+
+public class CheckoutBranchToolbarButton extends BranchToolbarButton
+{
+ @Inject
+ public CheckoutBranchToolbarButton(final Provider<GitState> pVcsState,
+ final GitServerOperations server)
+ {
+ super(pVcsState);
+
+ addValueChangeHandler(new ValueChangeHandler<String>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ final String branch = event.getValue();
+ server.gitCheckout(
+ branch,
+ new SimpleRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ new ConsoleProgressDialog(proc,
+ server).showModal();
+ }
+ });
+
+ }
+
+ });
+
+ }
+
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ super.onVcsRefresh(event);
+
+ setBranchCaption(pVcsState_.get().getBranchInfo().getActiveBranch());
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/HistoryBranchToolbarButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/HistoryBranchToolbarButton.java
new file mode 100644
index 0000000..96de926
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/HistoryBranchToolbarButton.java
@@ -0,0 +1,56 @@
+/*
+ * HistoryBranchToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+public class HistoryBranchToolbarButton extends BranchToolbarButton
+{
+ @Inject
+ public HistoryBranchToolbarButton(final Provider<GitState> vcsState)
+ {
+ super(vcsState);
+ }
+
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ super.onVcsRefresh(event);
+
+ // one time initialization of our caption (need to do it here
+ // because vcsState.getBranchInfo is null when we are created)
+ if (!initialized_)
+ {
+ initialized_ = true;
+ setBranchCaption(pVcsState_.get().getBranchInfo().getActiveBranch());
+ }
+ }
+
+ @Override
+ protected void onBeforePopulateMenu(ToolbarPopupMenu rootMenu)
+ {
+ String label = "(all branches)";
+ rootMenu.addItem(
+ new MenuItem(label, new SwitchBranchCommand(label, "--all")));
+ }
+
+ private boolean initialized_ = false;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSPresenter.java
new file mode 100644
index 0000000..ee77743
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSPresenter.java
@@ -0,0 +1,142 @@
+/*
+ * VCSPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsHistoryEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsDiffEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRevertFileEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsViewOnGitHubEvent;
+
+public class VCSPresenter implements IsWidget,
+ ShowVcsHistoryEvent.Handler,
+ ShowVcsDiffEvent.Handler,
+ VcsRevertFileEvent.Handler,
+ VcsViewOnGitHubEvent.Handler
+{
+ @Inject
+ public VCSPresenter(Session session,
+ Provider<GitPresenter> pGitPresenter,
+ Provider<SVNPresenter> pSVNPresenter)
+ {
+ session_ = session;
+ pGitPresenter_ = pGitPresenter;
+ pSVNPresenter_ = pSVNPresenter;
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ ensurePresenterCreated();
+
+ return presenter_.asWidget();
+ }
+
+ private void ensurePresenterCreated()
+ {
+ if (presenter_ == null)
+ {
+ String vcsName = session_.getSessionInfo().getVcsName();
+ if (VCSConstants.GIT_ID.equalsIgnoreCase(vcsName))
+ presenter_ = pGitPresenter_.get();
+ else if (VCSConstants.SVN_ID.equalsIgnoreCase(vcsName))
+ presenter_ = pSVNPresenter_.get();
+ }
+ }
+
+ public void onBeforeUnselected()
+ {
+ presenter_.onBeforeUnselected();
+ }
+
+ public void onBeforeSelected()
+ {
+ presenter_.onBeforeSelected();
+ }
+
+ public void onSelected()
+ {
+ presenter_.onSelected();
+ }
+
+
+ void onVcsCommit()
+ {
+ presenter_.onVcsCommit();
+ }
+
+ void onVcsShowHistory()
+ {
+ presenter_.onVcsShowHistory();
+ }
+
+ void onVcsPull()
+ {
+ presenter_.onVcsPull();
+ }
+
+ void onVcsPush()
+ {
+ presenter_.onVcsPush();
+ }
+
+ void onVcsCleanup()
+ {
+ presenter_.onVcsCleanup();
+ }
+
+ void onVcsIgnore()
+ {
+ presenter_.onVcsIgnore();
+ }
+
+ @Override
+ public void onShowVcsHistory(ShowVcsHistoryEvent event)
+ {
+ presenter_.showHistory(event.getFileFilter());
+ }
+
+ @Override
+ public void onShowVcsDiff(ShowVcsDiffEvent event)
+ {
+ presenter_.showDiff(event.getFile());
+ }
+
+ @Override
+ public void onVcsRevertFile(VcsRevertFileEvent event)
+ {
+ presenter_.revertFile(event.getFile());
+ }
+
+ @Override
+ public void onVcsViewOnGitHub(VcsViewOnGitHubEvent event)
+ {
+ presenter_.viewOnGitHub(event.getViewRequest());
+ }
+
+ private final Session session_;
+ private final Provider<GitPresenter> pGitPresenter_;
+ private final Provider<SVNPresenter> pSVNPresenter_;
+ private BaseVcsPresenter presenter_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSTab.java
new file mode 100644
index 0000000..eae9a78
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/VCSTab.java
@@ -0,0 +1,79 @@
+/*
+ * VCSTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs;
+
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsDiffEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsHistoryEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRevertFileEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsViewOnGitHubEvent;
+
+public class VCSTab extends DelayLoadWorkbenchTab<VCSPresenter>
+
+{
+ public interface Binder extends CommandBinder<Commands, VCSTab.VCSShim> {}
+
+ public abstract static class VCSShim extends DelayLoadTabShim<VCSPresenter, VCSTab>
+ implements ShowVcsHistoryEvent.Handler,
+ ShowVcsDiffEvent.Handler,
+ VcsRevertFileEvent.Handler,
+ VcsViewOnGitHubEvent.Handler
+ {
+ @Handler
+ public abstract void onVcsCommit();
+ @Handler
+ public abstract void onVcsShowHistory();
+ @Handler
+ public abstract void onVcsPull();
+ @Handler
+ public abstract void onVcsPush();
+ @Handler
+ public abstract void onVcsCleanup();
+ @Handler
+ public abstract void onVcsIgnore();
+ }
+
+ @Inject
+ protected VCSTab(VCSShim shim,
+ Commands commands,
+ Binder binder,
+ EventBus eventBus,
+ Session session)
+ {
+ super(session.getSessionInfo().getVcsName(), shim);
+ binder.bind(commands, shim);
+ session_ = session;
+ eventBus.addHandler(ShowVcsHistoryEvent.TYPE, shim);
+ eventBus.addHandler(ShowVcsDiffEvent.TYPE, shim);
+ eventBus.addHandler(VcsRevertFileEvent.TYPE, shim);
+ eventBus.addHandler(VcsViewOnGitHubEvent.TYPE, shim);
+ }
+
+ @Override
+ public boolean isSuppressed()
+ {
+ return !session_.getSessionInfo().isVcsEnabled();
+ }
+
+ private final Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.css
new file mode 100644
index 0000000..6cacffa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.css
@@ -0,0 +1,4 @@
+
+.infoBar {
+ border-bottom: 1px solid #bcc1c5;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.java
new file mode 100644
index 0000000..4d915ee
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTable.java
@@ -0,0 +1,437 @@
+/*
+ * ChangelistTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.ColumnSortEvent;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.view.client.*;
+import org.rstudio.core.client.theme.RStudioCellTableStyle;
+import org.rstudio.core.client.widget.InfoBar;
+import org.rstudio.core.client.widget.MultiSelectCellTable;
+import org.rstudio.core.client.widget.ProgressPanel;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class ChangelistTable extends Composite
+ implements HasKeyDownHandlers, HasClickHandlers, HasMouseDownHandlers,
+ HasContextMenuHandlers
+{
+ public interface ChangelistTableCellTableResources extends CellTable.Resources
+ {
+ @Override
+ @Source("ascendingArrow.png")
+ ImageResource cellTableSortAscending();
+
+ @Override
+ @Source("descendingArrow.png")
+ ImageResource cellTableSortDescending();
+
+ @Override
+ @Source({RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS,
+ "ChangelistTableCellTableStyle.css"})
+ ChangelistTableCellTableStyle cellTableStyle();
+
+ @Source("ChangelistTable.css")
+ Styles styles();
+ }
+
+ public interface ChangelistTableCellTableStyle extends CellTable.Style
+ {
+ String status();
+ }
+
+
+ interface Styles extends CssResource
+ {
+ String infoBar();
+ }
+
+ public static void ensureStylesInjected()
+ {
+ resources_.styles().ensureInjected();
+ }
+
+
+ /**
+ * The whole point of this subclass is to force CellTable to update its
+ * cellIsEditing private member more often. This is to work around a bug
+ * where the ChangelistTable can get into a state where mouse clicks don't
+ * change the selection, because TriStateCheckboxCell gets a mouseover event
+ * but not a matching mouseout.
+ */
+ protected static class NotEditingTextCell extends TextCell
+ {
+ public NotEditingTextCell()
+ {
+ super();
+ init();
+ }
+
+ public NotEditingTextCell(SafeHtmlRenderer<String> renderer)
+ {
+ super(renderer);
+ init();
+ }
+
+ private void init()
+ {
+ Set<String> inheritedEvents = getConsumedEvents();
+ if (inheritedEvents != null)
+ consumedEvents_.addAll(inheritedEvents);
+ consumedEvents_.add("mouseover");
+ }
+
+ @Override
+ public Set<String> getConsumedEvents()
+ {
+ return consumedEvents_;
+ }
+
+ @Override
+ public boolean isEditing(Context context, Element parent, String value)
+ {
+ return false;
+ }
+
+ private Set<String> consumedEvents_ = new HashSet<String>();
+ }
+
+ public ChangelistTable()
+ {
+ table_ = new MultiSelectCellTable<StatusAndPath>(100, resources_);
+
+ dataProvider_ = new ListDataProvider<StatusAndPath>();
+ sortHandler_ = new ColumnSortEvent.ListHandler<StatusAndPath>(
+ dataProvider_.getList());
+ table_.addColumnSortHandler(sortHandler_);
+
+ selectionModel_ = createSelectionModel();
+ table_.setSelectionModel(selectionModel_);
+ dataProvider_.addDataDisplay(table_);
+
+ configureTable();
+
+ table_.setSize("100%", "auto");
+
+ layout_ = new LayoutPanel();
+ scrollPanel_ = new ScrollPanel(table_);
+ layout_.add(scrollPanel_);
+ layout_.setWidgetTopBottom(scrollPanel_, 0, Unit.PX, 0, Unit.PX);
+ layout_.setWidgetLeftRight(scrollPanel_, 0, Unit.PX, 0, Unit.PX);
+ progressPanel_ = new ProgressPanel();
+ progressPanel_.getElement().getStyle().setBackgroundColor("white");
+ layout_.add(progressPanel_);
+ layout_.setWidgetTopBottom(progressPanel_, 0, Unit.PX, 0, Unit.PX);
+ layout_.setWidgetLeftRight(progressPanel_, 0, Unit.PX, 0, Unit.PX);
+
+ setProgress(true);
+
+ initWidget(layout_);
+ }
+
+ protected MultiSelectionModel<StatusAndPath> createSelectionModel()
+ {
+ return new MultiSelectionModel<StatusAndPath>(
+ new ProvidesKey<StatusAndPath>()
+ {
+ @Override
+ public Object getKey(StatusAndPath item)
+ {
+ return item.getPath();
+ }
+ });
+ }
+
+ protected abstract SafeHtmlRenderer<String> getStatusRenderer();
+
+ public void setSelectFirstItemByDefault(boolean selectFirstItemByDefault)
+ {
+ selectFirstItemByDefault_ = selectFirstItemByDefault;
+ }
+
+ public void moveSelectionDown()
+ {
+ if (getSelectedItems().size() == 1)
+ {
+ table_.moveSelection(false, false);
+ }
+ }
+
+ public void showProgress()
+ {
+ setProgress(true);
+ }
+
+ protected void setProgress(boolean showProgress)
+ {
+ if (showProgress)
+ {
+ layout_.setWidgetVisible(progressPanel_, true);
+ progressPanel_.beginProgressOperation(300);
+ }
+ else
+ {
+ layout_.setWidgetVisible(progressPanel_, false);
+ progressPanel_.endProgressOperation();
+ }
+ }
+
+
+ public void showInfoBar(String message)
+ {
+ if (infoBar_ == null)
+ {
+ infoBar_ = new ChangelistInfoBar();
+ layout_.add(infoBar_);
+ layout_.setWidgetLeftRight(infoBar_, 0, Unit.PX, 0, Unit.PX);
+ layout_.setWidgetTopHeight(infoBar_,
+ 0, Unit.PX,
+ infoBar_.getHeight(), Unit.PX);
+ layout_.setWidgetTopBottom(scrollPanel_,
+ infoBar_.getHeight(), Unit.PX,
+ 0, Unit.PX);
+ infoBar_.setText(message);
+ layout_.animate(250);
+ }
+ else
+ {
+ infoBar_.setText(message);
+ }
+ }
+
+ public void hideInfoBar()
+ {
+ if (infoBar_ != null)
+ {
+ layout_.remove(infoBar_);
+ layout_.setWidgetTopBottom(scrollPanel_, 0, Unit.PX, 0, Unit.PX);
+ layout_.animate(250);
+ infoBar_ = null;
+ }
+ }
+
+ protected void configureTable()
+ {
+ Column<StatusAndPath, String> statusColumn = new Column<StatusAndPath, String>(
+ new NotEditingTextCell(getStatusRenderer()))
+ {
+ @Override
+ public String getValue(StatusAndPath object)
+ {
+ return object.getStatus();
+ }
+ };
+ statusColumn.setSortable(true);
+ statusColumn.setHorizontalAlignment(Column.ALIGN_CENTER);
+ table_.addColumn(statusColumn, "Status");
+ table_.setColumnWidth(statusColumn, "56px");
+ sortHandler_.setComparator(statusColumn, new Comparator<StatusAndPath>()
+ {
+ @Override
+ public int compare(StatusAndPath a, StatusAndPath b)
+ {
+ return a.getStatus().compareTo(b.getStatus());
+ }
+ });
+
+ Column<StatusAndPath, String> pathColumn = new Column<StatusAndPath, String>(
+ new NotEditingTextCell())
+ {
+ @Override
+ public String getValue(StatusAndPath object)
+ {
+ String path = object.getPath();
+ if (object.isDirectory() && !path.endsWith("/"))
+ path = path + "/";
+ return path;
+ }
+ };
+ pathColumn.setSortable(true);
+ sortHandler_.setComparator(pathColumn, new StatusAndPath.PathComparator());
+ table_.addColumn(pathColumn, "Path");
+
+ table_.getColumnSortList().push(pathColumn);
+ }
+
+ public HandlerRegistration addSelectionChangeHandler(
+ SelectionChangeEvent.Handler handler)
+ {
+ return selectionModel_.addSelectionChangeHandler(handler);
+ }
+
+ public void setItems(ArrayList<StatusAndPath> items)
+ {
+ setProgress(false);
+ table_.setPageSize(items.size());
+ dataProvider_.getList().clear();
+ dataProvider_.getList().addAll(items);
+ ColumnSortEvent.fire(table_,
+ table_.getColumnSortList());
+
+ if (selectFirstItemByDefault_)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ if (table_.getVisibleItemCount() > 0
+ && selectionModel_.getSelectedSet().isEmpty())
+ {
+ selectionModel_.setSelected(table_.getVisibleItem(0), true);
+ }
+ }
+ });
+ }
+ }
+
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ SelectionModel<? super StatusAndPath> selectionModel = table_.getSelectionModel();
+
+ ArrayList<StatusAndPath> results = new ArrayList<StatusAndPath>();
+ for (StatusAndPath item : dataProvider_.getList())
+ {
+ if (selectionModel.isSelected(item))
+ results.add(item);
+ }
+ return results;
+ }
+
+ public ArrayList<String> getSelectedPaths()
+ {
+ SelectionModel<? super StatusAndPath> selectionModel = table_.getSelectionModel();
+
+ ArrayList<String> results = new ArrayList<String>();
+ for (StatusAndPath item : dataProvider_.getList())
+ {
+ if (selectionModel.isSelected(item))
+ results.add(item.getPath());
+ }
+ return results;
+ }
+
+ public void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ selectionModel_.clear();
+ for (StatusAndPath path : selectedPaths)
+ selectionModel_.setSelected(path, true);
+ }
+
+ public ArrayList<String> getSelectedDiscardablePaths()
+ {
+ SelectionModel<? super StatusAndPath> selectionModel = table_.getSelectionModel();
+
+ ArrayList<String> results = new ArrayList<String>();
+ for (StatusAndPath item : dataProvider_.getList())
+ {
+ if (selectionModel.isSelected(item) && item.isDiscardable())
+ results.add(item.getPath());
+ }
+ return results;
+ }
+
+ public void selectNextUnselectedItem()
+ {
+ boolean selectNext = false;
+ for (StatusAndPath path : table_.getVisibleItems())
+ {
+ if (selectionModel_.isSelected(path))
+ selectNext = true;
+ else if (selectNext)
+ {
+ ArrayList<StatusAndPath> selection = new ArrayList<StatusAndPath>();
+ selection.add(path);
+ setSelectedStatusAndPaths(selection);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler)
+ {
+ return table_.addKeyDownHandler(handler);
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return table_.addClickHandler(handler);
+ }
+
+ @Override
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler)
+ {
+ return table_.addMouseDownHandler(handler);
+ }
+
+ @Override
+ public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler)
+ {
+ return table_.addContextMenuHandler(handler);
+ }
+
+ public HandlerRegistration addRowCountChangeHandler(RowCountChangeEvent.Handler handler)
+ {
+ return table_.addRowCountChangeHandler(handler);
+ }
+
+ public void focus()
+ {
+ table_.setFocus(true);
+ }
+
+ private class ChangelistInfoBar extends InfoBar
+ {
+ public ChangelistInfoBar()
+ {
+ super(InfoBar.INFO);
+ addStyleName(resources_.styles().infoBar());
+ container_.getElement().getStyle().setBackgroundColor("#EEEFF1");
+
+ }
+ }
+
+ protected final MultiSelectCellTable<StatusAndPath> table_;
+ protected final MultiSelectionModel<StatusAndPath> selectionModel_;
+ protected final ColumnSortEvent.ListHandler<StatusAndPath> sortHandler_;
+ protected final ListDataProvider<StatusAndPath> dataProvider_;
+ private final ProgressPanel progressPanel_;
+ private LayoutPanel layout_;
+ private ScrollPanel scrollPanel_;
+ private ChangelistInfoBar infoBar_;
+ private boolean selectFirstItemByDefault_;
+ private static final ChangelistTableCellTableResources resources_ = GWT.<ChangelistTableCellTableResources>create(ChangelistTableCellTableResources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTableCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTableCellTableStyle.css
new file mode 100644
index 0000000..67d6fbd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ChangelistTableCellTableStyle.css
@@ -0,0 +1,20 @@
+span.status {
+ white-space: nowrap;
+}
+span.status img:first-child {
+ margin-right: 6px;
+}
+
+.cellTableCell:first-child img {
+ position: relative;
+ top: 3px;
+}
+.cellTableCell:first-child+.cellTableCell img {
+ position: relative;
+ top: 1px;
+}
+
+.cellTableCell input[type=checkbox] {
+ margin-top: 0;
+ margin-bottom: 0;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressDialog.java
new file mode 100644
index 0000000..9626483
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressDialog.java
@@ -0,0 +1,320 @@
+/*
+ * ConsoleProgressDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.CommandWithArg;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleOutputEvent;
+import org.rstudio.studio.client.common.console.ConsolePromptEvent;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.console.ConsoleProcessInfo;
+import org.rstudio.studio.client.common.console.ProcessExitEvent;
+import org.rstudio.studio.client.common.crypto.CryptoServerOperations;
+import org.rstudio.studio.client.common.shell.ShellInput;
+import org.rstudio.studio.client.common.shell.ShellInteractionManager;
+import org.rstudio.studio.client.common.shell.ShellOutputWriter;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+
+public class ConsoleProgressDialog extends ProgressDialog
+ implements ConsoleOutputEvent.Handler,
+ ConsolePromptEvent.Handler,
+ ProcessExitEvent.Handler,
+ ClickHandler
+{
+ public ConsoleProgressDialog(ConsoleProcess consoleProcess,
+ CryptoServerOperations server)
+ {
+ this(consoleProcess.getProcessInfo().getCaption(),
+ consoleProcess,
+ "",
+ null,
+ server);
+ }
+
+ public ConsoleProgressDialog(String title,
+ String output,
+ int exitCode)
+ {
+ this(title, null, output, exitCode, null);
+ }
+
+ public ConsoleProgressDialog(String title,
+ ConsoleProcess consoleProcess,
+ String initialOutput,
+ Integer exitCode,
+ CryptoServerOperations server)
+ {
+ super(title);
+
+ if (consoleProcess == null && exitCode == null)
+ {
+ throw new IllegalArgumentException(
+ "Invalid combination of arguments to ConsoleProgressDialog");
+ }
+
+ consoleProcess_ = consoleProcess;
+
+ if (getInteractionMode() != ConsoleProcessInfo.INTERACTION_NEVER)
+ {
+ ShellInteractionManager shellInteractionManager =
+ new ShellInteractionManager(display_, server, inputHandler_);
+
+ if (getInteractionMode() != ConsoleProcessInfo.INTERACTION_ALWAYS)
+ shellInteractionManager.setHistoryEnabled(false);
+
+ outputWriter_ = shellInteractionManager;
+ }
+ else
+ {
+ display_.setReadOnly(true);
+ outputWriter_ = display_;
+ }
+
+ stopButton().addClickHandler(this);
+
+ if (!StringUtil.isNullOrEmpty(initialOutput))
+ {
+ outputWriter_.consoleWriteOutput(initialOutput);
+ }
+
+ if (consoleProcess != null)
+ {
+ addHandlerRegistration(consoleProcess.addConsolePromptHandler(this));
+ addHandlerRegistration(consoleProcess.addConsoleOutputHandler(this));
+ addHandlerRegistration(consoleProcess.addProcessExitHandler(this));
+
+ consoleProcess.start(new SimpleRequestCallback<Void>()
+ {
+ @Override
+ public void onError(ServerError error)
+ {
+ // Show error and stop
+ super.onError(error);
+
+ // if this is showOnOutput_ then we will never get
+ // a ProcessExitEvent or an onUnload so we should unsubscribe
+ // from events here
+ unregisterHandlers();
+
+ closeDialog();
+ }
+ });
+ }
+
+ addCloseHandler(new CloseHandler<PopupPanel>()
+ {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
+ {
+ if (consoleProcess_ != null)
+ consoleProcess_.reap(new VoidServerRequestCallback());
+ }
+ });
+
+ if (exitCode != null)
+ setExitCode(exitCode);
+
+ // interaction-always mode is a shell -- customize ui accordingly
+ if (getInteractionMode() == ConsoleProcessInfo.INTERACTION_ALWAYS)
+ {
+ stopButton().setText("Close");
+
+ hideProgress();
+
+ int height = Window.getClientHeight() - 150;
+ display_.getElement().getStyle().setHeight(height, Unit.PX);
+ }
+
+ }
+
+ @Override
+ protected Widget createDisplayWidget(Object param)
+ {
+ display_ = new ConsoleProgressWidget();
+ display_.setMaxOutputLines(getMaxOutputLines());
+ display_.setSuppressPendingInput(true);
+ return display_;
+ }
+
+ public void showOnOutput()
+ {
+ showOnOutput_ = true;
+ }
+
+ @Override
+ protected boolean handleEnterKey()
+ {
+ if (!running_)
+ {
+ stopButton().click();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public void onConsoleOutput(ConsoleOutputEvent event)
+ {
+ maybeShowOnOutput(event.getOutput());
+ outputWriter_.consoleWriteOutput(event.getOutput());
+ }
+
+ @Override
+ public void onConsolePrompt(ConsolePromptEvent event)
+ {
+ maybeShowOnOutput(event.getPrompt());
+ outputWriter_.consoleWritePrompt(event.getPrompt());
+ }
+
+ @Override
+ public void onProcessExit(ProcessExitEvent event)
+ {
+ setExitCode(event.getExitCode());
+
+ if (isShowing())
+ {
+ display_.setReadOnly(true);
+ stopButton().setFocus(true);
+
+ // when a shell exits we close the dialog
+ if (getInteractionMode() == ConsoleProcessInfo.INTERACTION_ALWAYS)
+ stopButton().click();
+
+ // when we were showOnOutput and the process succeeded then
+ // we also auto-close
+ else if (showOnOutput_ && (event.getExitCode() == 0))
+ stopButton().click();
+ }
+
+ // the dialog was showOnOutput_ but was never shown so just tear
+ // down registrations and reap the process
+ else if (showOnOutput_)
+ {
+ unregisterHandlers();
+
+ if (consoleProcess_ != null)
+ consoleProcess_.reap(new VoidServerRequestCallback());
+ }
+
+ }
+
+ private void setExitCode(int exitCode)
+ {
+ running_ = false;
+ stopButton().setText("Close");
+ stopButton().setDefault(true);
+ hideProgress();
+ }
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ if (running_)
+ {
+ consoleProcess_.interrupt(new SimpleRequestCallback<Void>() {
+ @Override
+ public void onResponseReceived(Void response)
+ {
+ closeDialog();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ stopButton().setEnabled(true);
+ super.onError(error);
+ }
+ });
+ stopButton().setEnabled(false);
+ }
+ else
+ {
+ closeDialog();
+ }
+
+ // Whether success or failure, we don't want to interrupt again
+ running_ = false;
+ }
+
+ private CommandWithArg<ShellInput> inputHandler_ =
+ new CommandWithArg<ShellInput>()
+ {
+ @Override
+ public void execute(ShellInput input)
+ {
+ consoleProcess_.writeStandardInput(
+ input,
+ new VoidServerRequestCallback() {
+ @Override
+ public void onError(ServerError error)
+ {
+ outputWriter_.consoleWriteError(error.getUserMessage());
+ }
+ });
+ }
+
+ };
+
+
+ private int getInteractionMode()
+ {
+ if (consoleProcess_ != null)
+ return consoleProcess_.getProcessInfo().getInteractionMode();
+ else
+ return ConsoleProcessInfo.INTERACTION_NEVER;
+ }
+
+ private int getMaxOutputLines()
+ {
+ if (consoleProcess_ != null)
+ return consoleProcess_.getProcessInfo().getMaxOutputLines();
+ else
+ return 1000;
+ }
+
+ private void maybeShowOnOutput(String output)
+ {
+ // NOTE: we have to trim the output because when the password
+ // manager provides a password non-interactively the back-end
+ // process sometimes echos a newline back to us
+ if (!isShowing() && showOnOutput_ && (output.trim().length() > 0))
+ showModal();
+ }
+
+ private boolean running_ = true;
+
+ private boolean showOnOutput_ = false;
+
+ private final ConsoleProcess consoleProcess_;
+
+ private final ShellOutputWriter outputWriter_;
+
+ private ConsoleProgressWidget display_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressWidget.java
new file mode 100644
index 0000000..13d5c29
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ConsoleProgressWidget.java
@@ -0,0 +1,29 @@
+/*
+ * ConsoleProgressWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import org.rstudio.studio.client.common.shell.ShellDisplay;
+import org.rstudio.studio.client.common.shell.ShellWidget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+
+public class ConsoleProgressWidget extends ShellWidget implements ShellDisplay
+{
+ public ConsoleProgressWidget()
+ {
+ super(new AceEditor(), null);
+ }
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/Pager.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/Pager.java
new file mode 100644
index 0000000..158ecb7
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/Pager.java
@@ -0,0 +1,118 @@
+/*
+ * Pager.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.cellview.client.SimplePager;
+import com.google.gwt.view.client.HasRows;
+import com.google.gwt.view.client.Range;
+
+public class Pager extends SimplePager
+{
+ interface SimplePagerResources extends SimplePager.Resources
+ {
+ @Override
+ @Source("images/PageForwardButton.png")
+ ImageResource simplePagerFastForward();
+
+ @Override
+ @Source("images/PageForwardButtonDisabled.png")
+ ImageResource simplePagerFastForwardDisabled();
+
+ @Override
+ @Source("images/PageFirstButton.png")
+ ImageResource simplePagerFirstPage();
+
+ @Override
+ @Source("images/PageFirstButtonDisabled.png")
+ ImageResource simplePagerFirstPageDisabled();
+
+ @Override
+ @Source("images/PageLastButton.png")
+ ImageResource simplePagerLastPage();
+
+ @Override
+ @Source("images/PageLastButtonDisabled.png")
+ ImageResource simplePagerLastPageDisabled();
+
+ @Override
+ @Source("images/PageNextButton.png")
+ ImageResource simplePagerNextPage();
+
+ @Override
+ @Source("images/PageNextButtonDisabled.png")
+ ImageResource simplePagerNextPageDisabled();
+
+ @Override
+ @Source("images/PagePreviousButton.png")
+ ImageResource simplePagerPreviousPage();
+
+ @Override
+ @Source("images/PagePreviousButtonDisabled.png")
+ ImageResource simplePagerPreviousPageDisabled();
+
+ @Override
+ @Source({"com/google/gwt/user/cellview/client/SimplePager.css",
+ "SimplePagerStyle.css"})
+ SimplePagerStyle simplePagerStyle();
+ }
+
+ interface SimplePagerStyle extends SimplePager.Style
+ {
+ }
+
+ public Pager(int pageSize,
+ int fastForwardRows)
+ {
+ super(TextLocation.CENTER,
+ GWT.<SimplePagerResources>create(SimplePagerResources.class),
+ fastForwardRows > 0, fastForwardRows, fastForwardRows > 0);
+ getElement().setAttribute("align", "center");
+ setPageSize(pageSize);
+ }
+
+ @Override
+ protected String createText()
+ {
+ final HasRows display = getDisplay();
+ if (display.getVisibleRange().getStart() == display.getRowCount())
+ return "";
+
+ String text = super.createText();
+
+ if (display.isRowCountExact())
+ return "Commits " + text;
+ else
+ {
+ int pos = text.indexOf(" of ");
+ return "Commits " + (pos >= 0 ? text.substring(0, pos) : text);
+ }
+ }
+
+ @Override
+ public void setPageStart(int index) {
+ HasRows display = getDisplay();
+ if (display != null) {
+ Range range = display.getVisibleRange();
+ int pageSize = range.getLength();
+ index = Math.max(0, index);
+ if (index != range.getStart()) {
+ display.setVisibleRange(index, pageSize);
+ }
+ }
+ }
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ProcessCallback.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ProcessCallback.java
new file mode 100644
index 0000000..94a68fd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ProcessCallback.java
@@ -0,0 +1,113 @@
+/*
+ * ProcessCallback.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.vcs.ProcessResult;
+import org.rstudio.studio.client.server.ServerError;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+
+public class ProcessCallback extends SimpleRequestCallback<ProcessResult>
+{
+ public ProcessCallback(String title)
+ {
+ this(title, null);
+ }
+
+ public ProcessCallback(String title, String progressMessage)
+ {
+ this(title, progressMessage, 0);
+ }
+
+ public ProcessCallback(String title,
+ String progressMessage,
+ int progressPaddingMs)
+ {
+ super(title);
+ title_ = title;
+ progressPaddingMs_ = progressPaddingMs;
+
+ if (progressMessage != null)
+ {
+ GlobalDisplay globalDisplay =
+ RStudioGinjector.INSTANCE.getGlobalDisplay();
+ dismissProgress_ = globalDisplay.showProgress(progressMessage);
+ }
+ }
+
+ @Override
+ public void onResponseReceived(ProcessResult response)
+ {
+ if (!StringUtil.isNullOrEmpty(response.getOutput()))
+ {
+ dismissProgress();
+
+ new ConsoleProgressDialog(title_,
+ response.getOutput(),
+ response.getExitCode()).showModal();
+ }
+ else
+ {
+ delayedDismissProgress();
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ dismissProgress();
+
+ super.onError(error);
+ }
+
+ private void delayedDismissProgress()
+ {
+ if (dismissProgress_ != null)
+ {
+ if (progressPaddingMs_ > 0)
+ {
+ new Timer() {
+ @Override
+ public void run()
+ {
+ dismissProgress();
+ }
+ }.schedule(progressPaddingMs_);
+ }
+ else
+ {
+ dismissProgress();
+ }
+ }
+ }
+
+ private void dismissProgress()
+ {
+ if (dismissProgress_ != null)
+ {
+ dismissProgress_.execute();
+ dismissProgress_ = null;
+ }
+ }
+
+ private final String title_;
+ private final int progressPaddingMs_;
+ private Command dismissProgress_ = null;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/SimplePagerStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/SimplePagerStyle.css
new file mode 100644
index 0000000..2a30d02
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/SimplePagerStyle.css
@@ -0,0 +1,8 @@
+.pageDetails {
+ min-width: 120px;
+}
+
+.button img {
+ display: block;
+ position: relative;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/VCSFileOpener.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/VCSFileOpener.java
new file mode 100644
index 0000000..45b6b35
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/VCSFileOpener.java
@@ -0,0 +1,117 @@
+/*
+ * VCSFileOpener.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateEvent;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+ at Singleton
+public class VCSFileOpener
+{
+ @Inject
+ public VCSFileOpener(Commands commands,
+ EventBus eventBus,
+ FileTypeRegistry fileTypeRegistry,
+ Satellite satellite)
+ {
+ commands_ = commands;
+ eventBus_ = eventBus;
+ fileTypeRegistry_ = fileTypeRegistry;
+ satellite_ = satellite;
+
+ if (!satellite.isCurrentWindowSatellite())
+ exportOpenFilesCallback();
+ }
+
+ public void openFiles(final ArrayList<StatusAndPath> items)
+ {
+ if (satellite_.isCurrentWindowSatellite())
+ {
+ satellite_.focusMainWindow();
+ callSatelliteOpenFiles(toJsArray(items));
+ }
+ else
+ {
+ doOpenFiles(toJsArray(items));
+ }
+ }
+
+ private void satelliteOpenFiles(JavaScriptObject items)
+ {
+ JsArray<FileSystemItem> itemsArray = items.cast();
+ doOpenFiles(itemsArray);
+ }
+
+ private void doOpenFiles(JsArray<FileSystemItem> items)
+ {
+ for (int i=0; i<items.length(); i++)
+ {
+ FileSystemItem item = items.get(i);
+ if (!item.isDirectory())
+ {
+ fileTypeRegistry_.openFile(item);
+ }
+ else
+ {
+ commands_.activateFiles().execute();
+ eventBus_.fireEvent(new DirectoryNavigateEvent(item));
+ }
+ }
+ }
+
+ private JsArray<FileSystemItem> toJsArray(ArrayList<StatusAndPath> items)
+ {
+ JsArray<FileSystemItem> jsItems = JavaScriptObject.createArray().cast();
+ for (StatusAndPath item : items)
+ {
+ if (item.isDirectory())
+ jsItems.push(FileSystemItem.createDir(item.getRawPath()));
+ else
+ jsItems.push(FileSystemItem.createFile(item.getRawPath()));
+ }
+ return jsItems;
+ }
+
+ private final native void exportOpenFilesCallback()/*-{
+ var vcsUtils = this;
+ $wnd.vcsOpenFilesFromRStudioSatellite = $entry(
+ function(items) {
+ vcsUtils. at org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener::satelliteOpenFiles(Lcom/google/gwt/core/client/JavaScriptObject;)(items);
+ }
+ );
+ }-*/;
+
+ private final native void callSatelliteOpenFiles(JavaScriptObject items)/*-{
+ $wnd.opener.vcsOpenFilesFromRStudioSatellite(items);
+ }-*/;
+
+
+ private final Commands commands_;
+ private final EventBus eventBus_;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final Satellite satellite_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ascendingArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ascendingArrow.png
new file mode 100755
index 0000000..b88c48b
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/ascendingArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/descendingArrow.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/descendingArrow.png
new file mode 100755
index 0000000..37a7582
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/descendingArrow.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkHeaderParser.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkHeaderParser.java
new file mode 100644
index 0000000..194f224
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkHeaderParser.java
@@ -0,0 +1,143 @@
+/*
+ * ChunkHeaderParser.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import java.util.ArrayList;
+
+class ChunkHeaderParser
+{
+ public ChunkHeaderParser(String s)
+ {
+ s_ = s;
+ p_ = 0;
+ }
+
+ public ChunkHeaderInfo parse()
+ {
+ // Example: @@ -6,4 +10,5 @@
+ // Example: @@ -6 +10,1 @@
+ // Example: @@@ -6,4 -6,3 +10,5 @@@
+ int atCount = 0;
+ while (matchChar('@'))
+ atCount++;
+
+ if (atCount < 2)
+ return null;
+
+ // match atCount many ranges
+ ArrayList<Range> ranges = new ArrayList<Range>(atCount);
+ for (int i = 0; i < atCount; i++)
+ {
+ matchWhitespace();
+
+ char prefix = (i < atCount-1) ? '-' : '+';
+ Integer start, count;
+
+ if (!matchChar(prefix)
+ || null == (start = matchNumber()))
+ {
+ return null;
+ }
+
+
+ if (matchChar(','))
+ {
+ if (null == (count = matchNumber()))
+ return null;
+ }
+ else
+ {
+ count = 1;
+ }
+
+ ranges.add(new Range(start, count));
+ }
+
+ matchWhitespace();
+
+ for (int i = 0; i < atCount; i++)
+ {
+ if (!matchChar('@'))
+ return null;
+ }
+
+ String extraInfo = s_.substring(p_);
+
+ return new ChunkHeaderInfo(ranges.toArray(new Range[ranges.size()]),
+ extraInfo);
+ }
+
+ Integer matchNumber()
+ {
+ StringBuilder num = new StringBuilder();
+ while (true)
+ {
+ int i = peek();
+ if (i >= '0' && i <= '9')
+ {
+ num.append((char)i);
+ p_++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (num.length() == 0)
+ return null;
+
+ return Integer.parseInt(num.toString());
+ }
+
+ boolean matchChar(char c)
+ {
+ if (peek() == c)
+ {
+ p_++;
+ return true;
+ }
+
+ return false;
+ }
+
+ boolean matchWhitespace()
+ {
+ boolean sawWhitespace = false;
+ while (true)
+ {
+ switch (peek())
+ {
+ case ' ':
+ case '\t':
+ p_++;
+ sawWhitespace = true;
+ break;
+ default:
+ return sawWhitespace;
+ }
+ }
+ }
+
+ int peek()
+ {
+ if (p_ >= s_.length())
+ return -1;
+ return s_.charAt(p_);
+ }
+
+ final String s_;
+ int p_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkOrLine.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkOrLine.java
new file mode 100644
index 0000000..d5e870c
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/ChunkOrLine.java
@@ -0,0 +1,55 @@
+/*
+ * ChunkOrLine.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import java.util.ArrayList;
+
+public class ChunkOrLine
+{
+ public static ArrayList<ChunkOrLine> fromChunk(DiffChunk chunk)
+ {
+ ArrayList<ChunkOrLine> list = new ArrayList<ChunkOrLine>();
+ if (!chunk.shouldIgnore())
+ list.add(new ChunkOrLine(chunk));
+ for (Line line : chunk.getLines())
+ list.add(new ChunkOrLine(line));
+ return list;
+ }
+
+ public ChunkOrLine(DiffChunk chunk)
+ {
+ chunk_ = chunk;
+ line_ = null;
+ }
+
+ public ChunkOrLine(Line line)
+ {
+ line_ = line;
+ chunk_ = null;
+ }
+
+ public DiffChunk getChunk()
+ {
+ return chunk_;
+ }
+
+ public Line getLine()
+ {
+ return line_;
+ }
+
+ private final DiffChunk chunk_;
+ private final Line line_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffChunk.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffChunk.java
new file mode 100644
index 0000000..2dacae3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffChunk.java
@@ -0,0 +1,81 @@
+/*
+ * DiffChunk.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import java.util.ArrayList;
+
+public class DiffChunk
+{
+ public DiffChunk(Range[] ranges,
+ String lineText,
+ ArrayList<Line> diffLines,
+ int diffIndex)
+ {
+ this.ranges_ = ranges;
+ this.lineText_ = lineText;
+ this.diffLines_ = diffLines;
+ diffIndex_ = diffIndex;
+ }
+
+ public DiffChunk reverse()
+ {
+ if (ranges_.length != 2)
+ throw new UnsupportedOperationException(
+ "Can't reverse a combined diff");
+
+ Range[] newRanges = new Range[2];
+ newRanges[0] = ranges_[1];
+ newRanges[1] = ranges_[0];
+
+ return new DiffChunk(newRanges,
+ lineText_,
+ Line.reverseLines(diffLines_), diffIndex_);
+ }
+
+ public ArrayList<Line> getLines()
+ {
+ return diffLines_;
+ }
+
+ public Range[] getRanges()
+ {
+ return ranges_;
+ }
+
+ public String getLineText()
+ {
+ return lineText_;
+ }
+
+ public int getDiffIndex()
+ {
+ return diffIndex_;
+ }
+
+ /**
+ * Returns true if this is a chunk that can't be rendered. Needed for SVN
+ * property chunks (these don't have a header but the DiffParser interface
+ * requires lines to be returned in chunks).
+ */
+ public boolean shouldIgnore()
+ {
+ return ranges_ == null;
+ }
+
+ private final String lineText_;
+ private final ArrayList<Line> diffLines_;
+ private final int diffIndex_;
+ private final Range[] ranges_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFileHeader.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFileHeader.java
new file mode 100644
index 0000000..22c12e4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFileHeader.java
@@ -0,0 +1,46 @@
+/*
+ * DiffFileHeader.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import java.util.ArrayList;
+
+public class DiffFileHeader
+{
+ public DiffFileHeader(ArrayList<String> headerLines,
+ String oldFile,
+ String newFile)
+ {
+ headerLines_ = headerLines;
+ oldFile_ = oldFile.replaceFirst("^a/", "").replaceFirst("\\s\\(revision [0-9]+\\)", "");
+ newFile_ = newFile.replaceFirst("^b/", "").replaceFirst("\\s\\(revision [0-9]+\\)", "");
+ }
+
+ public String getDescription()
+ {
+ if (oldFile_.equals(newFile_))
+ return oldFile_;
+
+ if (oldFile_.equals("/dev/null"))
+ return newFile_;
+ if (newFile_.equals("/dev/null"))
+ return oldFile_;
+ return oldFile_ + " => " + newFile_;
+ }
+
+ @SuppressWarnings("unused")
+ private final ArrayList<String> headerLines_;
+ private final String oldFile_;
+ private final String newFile_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFormatException.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFormatException.java
new file mode 100644
index 0000000..5fe0ca3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffFormatException.java
@@ -0,0 +1,25 @@
+/*
+ * DiffFormatException.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+public class DiffFormatException extends IllegalArgumentException
+{
+ private static final long serialVersionUID = 7949356716139877435L;
+
+ public DiffFormatException(String tag)
+ {
+ super("Invalid diff format [" + tag + "]");
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffParser.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffParser.java
new file mode 100644
index 0000000..e76b1ce
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/DiffParser.java
@@ -0,0 +1,22 @@
+/*
+ * DiffParser.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+public interface DiffParser
+{
+ DiffFileHeader nextFilePair();
+
+ DiffChunk nextChunk();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Line.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Line.java
new file mode 100644
index 0000000..3f71c66
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Line.java
@@ -0,0 +1,167 @@
+/*
+ * Line.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import java.util.ArrayList;
+
+public class Line implements Comparable<Line>
+{
+ public enum Type
+ {
+ Same(' '),
+ Insertion('+'),
+ Deletion('-'),
+ Comment('\\'),
+ Info('_');
+
+ Type(char value)
+ {
+ value_ = value;
+ }
+
+ public char getValue()
+ {
+ return value_;
+ }
+
+ public Type getInverse()
+ {
+ switch (this)
+ {
+ case Same:
+ return Same;
+ case Insertion:
+ return Deletion;
+ case Deletion:
+ return Insertion;
+ case Comment:
+ return Comment;
+ case Info:
+ return Info;
+ default:
+ assert false : "Couldn't getInverse on Type value";
+ throw new IllegalStateException("Couldn't getInverse on Type value");
+ }
+ }
+
+ private final char value_;
+ }
+
+ public Line(Type type, int oldLine, int newLine, String text, int diffIndex)
+ {
+ type_ = type;
+ lines_ = new int[] {oldLine, newLine};
+ appliesTo_ = new boolean[]{ true };
+ text_ = text;
+ diffIndex_ = diffIndex;
+ }
+
+ public Line(Type type,
+ boolean[] appliesTo,
+ int[] lines,
+ String text,
+ int diffIndex)
+ {
+ if (lines.length < 2)
+ throw new IllegalArgumentException("Too few lines");
+ if (appliesTo.length != lines.length)
+ throw new IllegalArgumentException("appliesTo had unexpected length");
+
+ type_ = type;
+ appliesTo_ = appliesTo;
+ lines_ = lines;
+ text_ = text;
+ diffIndex_ = diffIndex;
+ }
+
+ public Type getType()
+ {
+ return type_;
+ }
+
+ public int getOldLine()
+ {
+ return lines_[0];
+ }
+
+ public int getNewLine()
+ {
+ return lines_[1];
+ }
+
+ public String getText()
+ {
+ return text_;
+ }
+
+ public int getDiffIndex()
+ {
+ return diffIndex_;
+ }
+
+ public Line reverse()
+ {
+ if (appliesTo_.length > 2)
+ throw new UnsupportedOperationException("Can't reverse combined diff");
+
+ return new Line(type_.getInverse(),
+ lines_[1],
+ lines_[0],
+ text_,
+ diffIndex_);
+ }
+
+ @Override
+ public int compareTo(Line line)
+ {
+ return diffIndex_ - line.diffIndex_;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return diffIndex_;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return o instanceof Line && compareTo((Line) o) == 0;
+ }
+
+ public static ArrayList<Line> reverseLines(ArrayList<Line> lines)
+ {
+ ArrayList<Line> rlines = new ArrayList<Line>(lines.size());
+ for (Line line : lines)
+ rlines.add(line.reverse());
+ return rlines;
+ }
+
+ public int[] getLines()
+ {
+ return lines_;
+ }
+
+ public boolean[] getAppliesTo()
+ {
+ return appliesTo_;
+ }
+
+ private final Type type_;
+ private final int[] lines_;
+ private final boolean[] appliesTo_;
+ private final String text_;
+ private final int diffIndex_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButton.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButton.css
new file mode 100644
index 0000000..857e274
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButton.css
@@ -0,0 +1,27 @@
+.button {
+ cursor: pointer;
+ display: inline-table;
+ border: none;
+ outline: none;
+ padding: 0;
+ margin: 0 0 0 4px;
+ background-color: transparent;
+ height: value('buttonLeft.getHeight', 'px');
+}
+.button div {
+ display: table-cell;
+ vertical-align: middle;
+ font-size: 9px !important;
+}
+
+ at sprite .left {
+ gwt-image: 'buttonLeft';
+}
+
+ at sprite .center {
+ gwt-image: 'buttonTile';
+}
+
+ at sprite .right {
+ gwt-image: 'buttonRight';
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButtonRenderer.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButtonRenderer.java
new file mode 100644
index 0000000..93d43e6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineActionButtonRenderer.java
@@ -0,0 +1,139 @@
+/*
+ * LineActionButtonRenderer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import org.rstudio.core.client.SafeHtmlUtil;
+
+public class LineActionButtonRenderer
+{
+ interface Resources
+ {
+ ImageResource buttonLeft();
+
+ @ImageOptions(repeatStyle = ImageResource.RepeatStyle.Horizontal)
+ ImageResource buttonTile();
+
+ ImageResource buttonRight();
+
+ Styles styles();
+ }
+
+ interface Styles extends CssResource
+ {
+ String button();
+ String left();
+ String center();
+ String right();
+ }
+
+ interface BlueResources extends Resources, ClientBundle
+ {
+ @Override
+ @Source("images/SmallBlueButtonLeft.png")
+ ImageResource buttonLeft();
+
+ @Override
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ @Source("images/SmallBlueButtonTile.png")
+ ImageResource buttonTile();
+
+ @Override
+ @Source("images/SmallBlueButtonRight.png")
+ ImageResource buttonRight();
+
+ @Source("LineActionButton.css")
+ BlueStyles styles();
+ }
+
+ interface BlueStyles extends Styles
+ {}
+
+ interface GrayResources extends Resources, ClientBundle
+ {
+ @Override
+ @Source("images/SmallGrayButtonLeft.png")
+ ImageResource buttonLeft();
+
+ @Override
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ @Source("images/SmallGrayButtonTile.png")
+ ImageResource buttonTile();
+
+ @Override
+ @Source("images/SmallGrayButtonRight.png")
+ ImageResource buttonRight();
+
+ @Source("LineActionButton.css")
+ GrayStyles styles();
+ }
+
+ interface GrayStyles extends Styles
+ {}
+
+ public static LineActionButtonRenderer createBlue()
+ {
+ return new LineActionButtonRenderer(GWT.<Resources>create(BlueResources.class));
+ }
+
+ public static LineActionButtonRenderer createGray()
+ {
+ return new LineActionButtonRenderer(GWT.<Resources>create(GrayResources.class));
+ }
+
+ protected LineActionButtonRenderer(Resources resources)
+ {
+ resources_ = resources;
+ resources_.styles().ensureInjected();
+ }
+
+ public void render(SafeHtmlBuilder builder, String text, String action)
+ {
+ {
+ builder.append(SafeHtmlUtil.createOpenTag(
+ "div",
+ "class", resources_.styles().button(),
+ "data-action", action));
+ {
+ builder.append(SafeHtmlUtil.createOpenTag(
+ "div",
+ "class", resources_.styles().left()));
+ builder.appendHtmlConstant("<br/></div>");
+
+ builder.append(SafeHtmlUtil.createOpenTag(
+ "div",
+ "class", resources_.styles().center()));
+ {
+ builder.appendEscaped(text);
+ }
+ builder.appendHtmlConstant("</div>");
+
+ builder.append(SafeHtmlUtil.createOpenTag(
+ "div",
+ "class", resources_.styles().right()));
+ builder.appendHtmlConstant("<br/></div>");
+ }
+ builder.appendHtmlConstant("</div>");
+ }
+ }
+
+ private final Resources resources_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTablePresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTablePresenter.java
new file mode 100644
index 0000000..ae95eea
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTablePresenter.java
@@ -0,0 +1,41 @@
+/*
+ * LineTablePresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffLinesActionHandler;
+
+import java.util.ArrayList;
+
+public class LineTablePresenter
+{
+ public interface Display
+ {
+ void setData(ArrayList<ChunkOrLine> diffData, PatchMode patchMode);
+ void clear();
+ ArrayList<Line> getSelectedLines();
+ ArrayList<Line> getAllLines();
+
+ void setShowActions(boolean showActions);
+
+ HandlerRegistration addDiffChunkActionHandler(DiffChunkActionHandler handler);
+ HandlerRegistration addDiffLineActionHandler(DiffLinesActionHandler handler);
+
+ HandlerRegistration addSelectionChangeHandler(SelectionChangeEvent.Handler handler);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableView.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableView.java
new file mode 100644
index 0000000..1cf4bb0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableView.java
@@ -0,0 +1,558 @@
+/*
+ * LineTableView.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.RowStyles;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.gwt.view.client.SelectionChangeEvent.Handler;
+import com.google.inject.Inject;
+import org.rstudio.core.client.SafeHtmlUtil;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.dom.DomUtils.NodePredicate;
+import org.rstudio.core.client.theme.RStudioCellTableStyle;
+import org.rstudio.core.client.widget.FontSizer;
+import org.rstudio.core.client.widget.MultiSelectCellTable;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.Line.Type;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTablePresenter.Display;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionEvent.Action;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffLinesActionEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffLinesActionHandler;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class LineTableView extends MultiSelectCellTable<ChunkOrLine> implements Display
+{
+ public interface LineTableViewCellTableResources extends CellTable.Resources
+ {
+ @Source({RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS,
+ "LineTableViewCellTableStyle.css"})
+ LineTableViewCellTableStyle cellTableStyle();
+ }
+
+ public interface LineTableViewCellTableStyle extends CellTable.Style
+ {
+ String header();
+ String same();
+ String insertion();
+ String deletion();
+ String comment();
+ String info();
+
+ String lineNumber();
+ String lastLineNumber();
+
+ String actions();
+ String lineActions();
+ String chunkActions();
+
+ String start();
+ String end();
+
+ String stageMode();
+ String workingMode();
+ String noStageMode();
+ }
+
+ public class LineContentCell extends AbstractCell<ChunkOrLine>
+ {
+ public LineContentCell()
+ {
+ super("mousedown");
+ }
+
+ @Override
+ public void onBrowserEvent(Context context,
+ Element parent,
+ ChunkOrLine value,
+ NativeEvent event,
+ ValueUpdater<ChunkOrLine> chunkOrLineValueUpdater)
+ {
+ if ("mousedown".equals(event.getType())
+ && event.getButton() == NativeEvent.BUTTON_LEFT
+ && parent.isOrHasChild(event.getEventTarget().<Node>cast()))
+ {
+ Element el = (Element) DomUtils.findNodeUpwards(
+ event.getEventTarget().<Node>cast(),
+ parent,
+ new NodePredicate()
+ {
+ @Override
+ public boolean test(Node n)
+ {
+ return n.getNodeType() == Node.ELEMENT_NODE &&
+ ((Element) n).hasAttribute("data-action");
+ }
+ });
+
+ if (el != null)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ Action action = Action.valueOf(el.getAttribute("data-action"));
+
+ if (value.getChunk() != null)
+ fireEvent(new DiffChunkActionEvent(action, value.getChunk()));
+ else
+ fireEvent(new DiffLinesActionEvent(action));
+ }
+ }
+
+ super.onBrowserEvent(context,
+ parent,
+ value,
+ event,
+ chunkOrLineValueUpdater);
+ }
+
+ @Override
+ public void render(Context context, ChunkOrLine value, SafeHtmlBuilder sb)
+ {
+ if (value.getLine() != null)
+ {
+ sb.appendEscaped(value.getLine().getText());
+ if (showActions_
+ && value.getLine().getType() != Line.Type.Same
+ && value.getLine().getType() != Line.Type.Info
+ && value == firstSelectedLine_)
+ {
+ renderActionButtons(
+ sb,
+ RES.cellTableStyle().lineActions(),
+ selectionModel_.getSelectedSet().size() > 1
+ ? " selection"
+ : " line");
+ }
+ }
+ else
+ {
+ sb.appendEscaped(UnifiedEmitter.createChunkString(value.getChunk()));
+ if (showActions_)
+ {
+ renderActionButtons(sb,
+ RES.cellTableStyle().chunkActions(),
+ " chunk");
+ }
+ }
+ }
+
+ private void renderActionButtons(SafeHtmlBuilder sb,
+ String className,
+ String labelSuffix)
+ {
+ sb.append(SafeHtmlUtil.createOpenTag(
+ "div",
+ "class", RES.cellTableStyle().actions() + " " + className));
+ renderActionButton(sb, Action.Unstage, labelSuffix);
+ renderActionButton(sb, Action.Stage, labelSuffix);
+ renderActionButton(sb, Action.Discard, labelSuffix);
+ sb.appendHtmlConstant("</div>");
+ }
+
+ private void renderActionButton(SafeHtmlBuilder sb,
+ Action action,
+ String labelSuffix)
+ {
+ if (action == Action.Stage)
+ {
+ blueButtonRenderer_.render(
+ sb, action.name() + labelSuffix, action.name());
+ }
+ else
+ {
+ grayButtonRenderer_.render(
+ sb, action.name() + labelSuffix, action.name());
+ }
+ }
+ }
+
+ private class SwitchableSelectionModel<T> extends MultiSelectionModel<T>
+ {
+ private SwitchableSelectionModel()
+ {
+ }
+
+ private SwitchableSelectionModel(ProvidesKey<T> keyProvider)
+ {
+ super(keyProvider);
+ }
+
+ @Override
+ public void setSelected(T object, boolean selected)
+ {
+ if (!enabled_)
+ return;
+
+ super.setSelected(object, selected);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isEnabled()
+ {
+ return enabled_;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled_ = enabled;
+ }
+
+ private boolean enabled_ = true;
+ }
+
+ public LineTableView(int filesCompared)
+ {
+ this(filesCompared,
+ GWT.<LineTableViewCellTableResources>create(LineTableViewCellTableResources.class));
+ }
+
+ @Inject
+ public LineTableView(final LineTableViewCellTableResources res)
+ {
+ this(2, res);
+ }
+
+ public LineTableView(int filesCompared,
+ final LineTableViewCellTableResources res)
+ {
+ super(1, res);
+
+ FontSizer.applyNormalFontSize(this);
+
+ for (int i = 0; i < filesCompared; i++)
+ {
+ final int index = i;
+
+ TextColumn<ChunkOrLine> col = new TextColumn<ChunkOrLine>()
+ {
+ @Override
+ public String getValue(ChunkOrLine object)
+ {
+ Line line = object.getLine();
+ if (line == null)
+ return "\u00A0";
+
+ if (!line.getAppliesTo()[index])
+ return "\u00A0";
+
+ return intToString(line.getLines()[index]);
+ }
+ };
+ col.setHorizontalAlignment(TextColumn.ALIGN_RIGHT);
+ addColumn(col);
+ setColumnWidth(col, 100, Unit.PX);
+ addColumnStyleName(i, res.cellTableStyle().lineNumber());
+ if (i == filesCompared - 1)
+ addColumnStyleName(i, res.cellTableStyle().lastLineNumber());
+ }
+
+ Column<ChunkOrLine, ChunkOrLine> textCol =
+ new Column<ChunkOrLine, ChunkOrLine>(new LineContentCell())
+ {
+ @Override
+ public ChunkOrLine getValue(ChunkOrLine object)
+ {
+ return object;
+ }
+ };
+ addColumn(textCol);
+
+ setColumnWidth(textCol, 100, Unit.PCT);
+
+ setRowStyles(new RowStyles<ChunkOrLine>()
+ {
+ @Override
+ public String getStyleNames(ChunkOrLine chunkOrLine, int rowIndex)
+ {
+ Line line = chunkOrLine.getLine();
+
+ if (line == null)
+ {
+ return res.cellTableStyle().header();
+ }
+ else
+ {
+ String prefix = "";
+ if (startRows_.contains(rowIndex))
+ prefix += res.cellTableStyle().start() + " ";
+ if (endRows_.contains(rowIndex))
+ prefix += res.cellTableStyle().end() + " ";
+
+ switch (line.getType())
+ {
+ case Same:
+ return prefix + res.cellTableStyle().same();
+ case Insertion:
+ return prefix + res.cellTableStyle().insertion();
+ case Deletion:
+ return prefix + res.cellTableStyle().deletion();
+ case Comment:
+ return prefix + res.cellTableStyle().comment();
+ case Info:
+ return prefix + res.cellTableStyle().info();
+ default:
+ return "";
+ }
+ }
+
+ }
+ });
+
+ selectionModel_ = new SwitchableSelectionModel<ChunkOrLine>(new ProvidesKey<ChunkOrLine>()
+ {
+ @Override
+ public Object getKey(ChunkOrLine item)
+ {
+ if (item.getChunk() != null)
+ return item.getChunk().getDiffIndex();
+ else
+ return item.getLine().getDiffIndex();
+ }
+ }) {
+ @Override
+ public void setSelected(ChunkOrLine object, boolean selected)
+ {
+ if (object.getLine() != null &&
+ object.getLine().getType() != Line.Type.Same &&
+ object.getLine().getType() != Line.Type.Info)
+ {
+ super.setSelected(object, selected);
+ }
+ }
+ };
+ selectionModel_.addSelectionChangeHandler(new Handler()
+ {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent event)
+ {
+ ChunkOrLine newFirstSelectedLine = null;
+ for (ChunkOrLine value : selectionModel_.getSelectedSet())
+ {
+ if (value.getLine() != null &&
+ (newFirstSelectedLine == null || newFirstSelectedLine.getLine().compareTo(value.getLine()) > 0))
+ {
+ newFirstSelectedLine = value;
+ }
+ }
+
+ if (newFirstSelectedLine != null)
+ refreshValue(newFirstSelectedLine);
+ if (firstSelectedLine_ != newFirstSelectedLine)
+ {
+ if (firstSelectedLine_ != null)
+ refreshValue(firstSelectedLine_);
+ }
+
+ firstSelectedLine_ = newFirstSelectedLine;
+ }
+ });
+ setSelectionModel(selectionModel_);
+
+ setData(new ArrayList<ChunkOrLine>(), PatchMode.Working);
+ }
+
+ private void refreshValue(ChunkOrLine value)
+ {
+ int index = lines_.indexOf(value);
+ if (index >= 0)
+ {
+ ArrayList<ChunkOrLine> list = new ArrayList<ChunkOrLine>();
+ list.add(value);
+ setRowData(index, list);
+ }
+
+ }
+
+ private String intToString(Integer value)
+ {
+ if (value == null)
+ return "";
+ return value.toString();
+ }
+
+ public boolean isShowActions()
+ {
+ return showActions_;
+ }
+
+ public void setShowActions(boolean showActions)
+ {
+ showActions_ = showActions;
+ selectionModel_.setEnabled(showActions);
+ }
+
+ public void hideStageCommands()
+ {
+ addStyleName(RES.cellTableStyle().noStageMode());
+ }
+
+ public void setUseStartBorder(boolean useStartBorder)
+ {
+ useStartBorder_ = useStartBorder;
+ }
+
+ public void setUseEndBorder(boolean useEndBorder)
+ {
+ useEndBorder_ = useEndBorder;
+ }
+
+ @Override
+ public void setData(ArrayList<ChunkOrLine> diffData, PatchMode patchMode)
+ {
+ removeStyleName(RES.cellTableStyle().stageMode());
+ removeStyleName(RES.cellTableStyle().workingMode());
+ switch (patchMode)
+ {
+ case Stage:
+ addStyleName(RES.cellTableStyle().stageMode());
+ break;
+ case Working:
+ addStyleName(RES.cellTableStyle().workingMode());
+ break;
+ }
+
+ lines_ = diffData;
+ setPageSize(diffData.size());
+ selectionModel_.clear();
+ firstSelectedLine_ = null;
+ setRowData(diffData);
+
+ startRows_.clear();
+ endRows_.clear();
+
+ Line.Type state = Line.Type.Same;
+ boolean suppressNextStart = true; // Suppress at start to avoid 2px border
+ for (int i = 0; i < lines_.size(); i++)
+ {
+ ChunkOrLine chunkOrLine = lines_.get(i);
+ Line line = chunkOrLine.getLine();
+ boolean isChunk = line == null;
+ Line.Type newState = isChunk ? Line.Type.Same : line.getType();
+
+ if (useStartBorder_ && i == 0)
+ startRows_.add(i);
+
+ // Edge case: last line is a diff line
+ if (useEndBorder_ && i == lines_.size() - 1)
+ endRows_.add(i);
+
+ if (newState != state)
+ {
+ // Note: endRows_ doesn't include the borders between insertions and
+ // deletions, or vice versa. This is to avoid 2px borders between
+ // these regions when just about everything else is 1px.
+ if (state != Line.Type.Same && newState == Line.Type.Same && !isChunk)
+ endRows_.add(i-1);
+ if (!suppressNextStart && newState != Line.Type.Same)
+ startRows_.add(i);
+
+ state = newState;
+ }
+
+ suppressNextStart = isChunk;
+ }
+ }
+
+ @Override
+ protected boolean canSelectVisibleRow(int visibleRow)
+ {
+ if (visibleRow < 0 || visibleRow >= lines_.size())
+ return false;
+
+ Line line = lines_.get(visibleRow).getLine();
+ return line != null && (line.getType() == Type.Insertion
+ || line.getType() == Type.Deletion);
+ }
+
+ @Override
+ public void clear()
+ {
+ setData(new ArrayList<ChunkOrLine>(), PatchMode.Working);
+ }
+
+ @Override
+ public ArrayList<Line> getSelectedLines()
+ {
+ ArrayList<Line> selected = new ArrayList<Line>();
+ for (ChunkOrLine line : lines_)
+ if (line.getLine() != null && selectionModel_.isSelected(line))
+ selected.add(line.getLine());
+ return selected;
+ }
+
+ @Override
+ public ArrayList<Line> getAllLines()
+ {
+ ArrayList<Line> selected = new ArrayList<Line>();
+ for (ChunkOrLine line : lines_)
+ if (line.getLine() != null)
+ selected.add(line.getLine());
+ return selected;
+ }
+
+ @Override
+ public HandlerRegistration addDiffChunkActionHandler(DiffChunkActionHandler handler)
+ {
+ return addHandler(handler, DiffChunkActionEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addDiffLineActionHandler(DiffLinesActionHandler handler)
+ {
+ return addHandler(handler, DiffLinesActionEvent.TYPE);
+ }
+
+ @Override
+ public HandlerRegistration addSelectionChangeHandler(SelectionChangeEvent.Handler handler)
+ {
+ return selectionModel_.addSelectionChangeHandler(handler);
+ }
+
+ public static void ensureStylesInjected()
+ {
+ RES.cellTableStyle().ensureInjected();
+ }
+
+ private boolean showActions_ = true;
+ private ArrayList<ChunkOrLine> lines_;
+ private SwitchableSelectionModel<ChunkOrLine> selectionModel_;
+ private HashSet<Integer> startRows_ = new HashSet<Integer>();
+ private HashSet<Integer> endRows_ = new HashSet<Integer>();
+ private boolean useStartBorder_ = false;
+ private boolean useEndBorder_ = true;
+ // Keep explicit track of the first selected line so we can render it differently
+ private ChunkOrLine firstSelectedLine_;
+ private static final LineTableViewCellTableResources RES = GWT.create(LineTableViewCellTableResources.class);
+ private static final LineActionButtonRenderer blueButtonRenderer_ = LineActionButtonRenderer.createBlue();
+ private static final LineActionButtonRenderer grayButtonRenderer_ = LineActionButtonRenderer.createGray();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableViewCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableViewCellTableStyle.css
new file mode 100644
index 0000000..242b5ff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/LineTableViewCellTableStyle.css
@@ -0,0 +1,138 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+
+ at def selectionBorderWidth 1px;
+.cellTableWidget {
+ font-family: fixedWidthFont;
+ white-space: pre-wrap;
+ color: #888;
+ -webkit-user-select: auto;
+}
+.cellTableWidget > tbody > tr > td {
+ vertical-align: top;
+}
+
+.header {
+ background-color: #DDD;
+ color: #444;
+}
+.header > td {
+ border-top: selectionBorderWidth #BBB solid !important;
+ border-bottom: selectionBorderWidth #BBB solid !important;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.same {
+
+}
+
+.insertion {
+ background-color: #DDFEDA;
+ color: #444;
+}
+.insertion.start>td {
+ border-top: #90D989 solid selectionBorderWidth !important;
+}
+.insertion.end>td {
+ border-bottom: #90D989 solid selectionBorderWidth !important;
+}
+
+.deletion {
+ background-color: #FFDEDC;
+ color: #444;
+}
+.deletion.start>td {
+ border-top: #E1938F solid selectionBorderWidth !important;
+}
+.deletion.end>td {
+ border-bottom: #E1938F solid selectionBorderWidth !important;
+}
+
+.comment {
+ background-color: #F0F0F0;
+ color: #777;
+}
+
+.info {
+ background-color: #FFFFDC;
+ color: #444;
+}
+.info.start>td {
+ border-top: #A0A060 solid selectionBorderWidth !important;
+}
+.info.end>td {
+ border-bottom: #A0A060 solid selectionBorderWidth !important;
+}
+
+.cellTableWidget > .lineNumber {
+ background-color: #F0F0F0;
+ border-right-color: #D9D9D9 !important;
+}
+.cellTableWidget > tbody > tr .lineNumber.lastLineNumber {
+ border-right-color: #BBB !important;
+}
+.cellTableWidget > tbody > tr.same .lineNumber {
+ background-color: #F0F0F0;
+}
+.cellTableWidget > tbody > tr.header .lineNumber {
+ border-right-color: transparent;
+}
+
+.cellTableWidget > tbody > tr > td:first-child {
+ text-align: right;
+}
+
+.cellTableCell {
+ padding: 0 8px;
+}
+
+.cellTableWidget > tbody > tr > td {
+ border: selectionBorderWidth solid transparent;
+ border-right-color: #D9D9D9 !important;
+}
+
+.cellTableSelectedRow {
+ background: #CCC !important;
+}
+
+.cellTableSelectedRowCell {
+ background: #CCC !important;
+}
+
+.cellTableWidget:focus .cellTableSelectedRow {
+ background: rgb(146, 193, 240) !important;
+}
+.cellTableWidget:focus .cellTableSelectedRowCell {
+ background: rgb(146, 193, 240) !important;
+}
+
+.stageMode .actions div[data-action='Stage'],
+.stageMode .actions div[data-action='Discard'] {
+ display: none;
+}
+
+.workingMode .actions div[data-action='Unstage'] {
+ display: none;
+}
+
+.noStageMode .actions div[data-action='Stage'],
+.noStageMode .actions div[data-action='Unstage'] {
+ display: none;
+}
+
+.chunkActions {
+}
+
+.lineActions {
+}
+
+.actions {
+ font-family: proportionalFont;
+ float: right;
+ height: 16px;
+ padding: 1px 0 0 0;
+ margin-bottom: -5px; /* Prevent selected line from changing height on Ubuntu */
+ overflow: visible;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Range.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Range.java
new file mode 100644
index 0000000..9fd4325
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/Range.java
@@ -0,0 +1,27 @@
+/*
+ * Range.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+public class Range
+{
+ Range(int startRow, int rowCount)
+ {
+ this.startRow = startRow;
+ this.rowCount = rowCount;
+ }
+
+ public int startRow;
+ public int rowCount;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedEmitter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedEmitter.java
new file mode 100644
index 0000000..93d95e9
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedEmitter.java
@@ -0,0 +1,390 @@
+/*
+ * UnifiedEmitter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import org.rstudio.core.client.DuplicateHelper;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.Line.Type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class is used to subset an existing patch (the existing patch is
+ * modeled in DiffChunk and the subset of changes we want to keep is zero
+ * or more ArrayList<Line>).
+ *
+ * It works by using the existing patch to recreate the original data, then
+ * merging with the specific changes we want to keep.
+ *
+ * The output is in "Unified diff" format.
+ *
+ * You can also use this class to generate reverse selective patches (basically
+ * the same as above, but with the effect of undoing the patch on the changed
+ * file, rather than applying the patch to the original file) by simply
+ * reversing the DiffChunk (DiffChunk.reverse()) and lines (Line.reverseLines())
+ * before calling addDiffs().
+ */
+public class UnifiedEmitter
+{
+ public UnifiedEmitter(String relPath)
+ {
+ this("a/" + relPath , "b/" + relPath);
+ }
+
+ public UnifiedEmitter(String fileA, String fileB)
+ {
+ fileA_ = fileA;
+ fileB_ = fileB;
+ }
+
+ public void addContext(DiffChunk chunk)
+ {
+ contextLines_.addAll(chunk.getLines());
+ }
+
+ public void addDiffs(ArrayList<Line> lines)
+ {
+ diffLines_.addAll(lines);
+ }
+
+ public String createPatch(boolean includeFileHeader)
+ {
+ prepareList(contextLines_, Type.Insertion);
+ prepareList(diffLines_, Type.Same);
+ final ArrayList<DiffChunk> chunks = toDiffChunks(
+ new OutputLinesGenerator(contextLines_, diffLines_).getOutput());
+
+ if (chunks.size() == 0)
+ return "";
+
+ StringBuilder p = new StringBuilder();
+
+ // Write file header
+ if (includeFileHeader)
+ {
+ p.append("--- ").append(fileA_).append(EOL);
+ p.append("+++ ").append(fileB_).append(EOL);
+ }
+
+ for (DiffChunk chunk : chunks)
+ {
+ p.append(createChunkString(chunk));
+
+ p.append(EOL);
+
+ for (Line line : chunk.getLines())
+ {
+ switch (line.getType())
+ {
+ case Same: p.append(' '); break;
+ case Insertion: p.append('+'); break;
+ case Deletion: p.append('-'); break;
+ case Comment: p.append('\\'); break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ p.append(line.getText()).append(EOL);
+ }
+ }
+
+ return p.toString();
+ }
+
+ public static String createChunkString(DiffChunk chunk)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ // Write chunk header: @@ -A,B +C,D @@
+
+ Range[] ranges = chunk.getRanges();
+ for (int i = 0; i < ranges.length; i++)
+ sb.append('@');
+
+ for (int i = 0; i < ranges.length - 1; i++)
+ {
+ sb.append(" -").append(ranges[i].startRow);
+ sb.append(',').append(ranges[i].rowCount);
+ }
+ sb.append(" +").append(ranges[ranges.length-1].startRow);
+ sb.append(",").append(ranges[ranges.length-1].rowCount);
+
+ sb.append(' ');
+ for (int i = 0; i < ranges.length; i++)
+ sb.append('@');
+
+ sb.append(chunk.getLineText());
+
+ return sb.toString();
+ }
+
+ /**
+ * Divide a list of sorted lines into DiffChunks.
+ *
+ * NOTE: If we cared about compact diffs we could detect long runs of
+ * unchanged lines and elide them, like diff tools usually do. (Currently
+ * we keep all the lines we're given, and only use discontinuities to
+ * break up into chunks.)
+ */
+ private ArrayList<DiffChunk> toDiffChunks(ArrayList<Line> lines)
+ {
+ ArrayList<DiffChunk> chunks = new ArrayList<DiffChunk>();
+
+ if (lines.size() == 0)
+ return chunks;
+
+ int line = lines.get(0).getOldLine();
+
+ // The index of the earliest line that hasn't been put into a chunk yet
+ int head = 0;
+
+ for (int i = 1; i < lines.size(); i++)
+ {
+ if ((lines.get(i).getOldLine() - line) > 1)
+ {
+ // There's a gap between this line and the previous line. Turn
+ // the previous contiguous run into a DiffChunk.
+
+ List<Line> sublist = lines.subList(head, i);
+ chunks.add(contiguousLinesToChunk(sublist));
+
+ // This line is now the start of a new contiguous run.
+ head = i;
+ }
+ line = lines.get(i).getOldLine();
+ }
+
+ // Add final contiguous run
+ List<Line> sublist = lines.subList(head, lines.size());
+ chunks.add(contiguousLinesToChunk(sublist));
+
+ return chunks;
+ }
+
+ private DiffChunk contiguousLinesToChunk(List<Line> sublist)
+ {
+ Line first = sublist.get(0);
+ Line last = sublist.get(sublist.size() - 1);
+
+ int[] firstLines = first.getLines();
+ int[] lastLines = last.getLines();
+
+ // for purposes of chunk generation we need to not have any rows indicated
+ // as zero
+ for (int i = 0; i < firstLines.length; i++)
+ firstLines[i] = Math.max(1, firstLines[i]);
+ for (int i = 0; i < lastLines.length; i++)
+ lastLines[i] = Math.max(1, lastLines[i]);
+
+ Range[] ranges = new Range[firstLines.length];
+ for (int i = 0; i < firstLines.length; i++)
+ ranges[i] = new Range(firstLines[i], 1 + lastLines[i] - firstLines[i]);
+
+ return new DiffChunk(ranges,
+ "",
+ new ArrayList<Line>(sublist),
+ -1);
+ }
+
+ private static void prepareList(ArrayList<Line> lines, Type typeToRemove)
+ {
+ // Remove any entries that match the given type
+ for (int i = 0; i < lines.size(); i++)
+ if (lines.get(i).getType() == typeToRemove)
+ lines.remove(i--);
+
+ // Sort and deduplicate
+ Collections.sort(lines);
+ DuplicateHelper.dedupeSortedList(lines);
+ }
+
+ /**
+ * Here is where the heavy lifting of merging is done. The only reason this
+ * is factored into a class is to make up for the lack of real closures in
+ * Java.
+ */
+ private static class OutputLinesGenerator
+ {
+ private final ArrayList<Line> output = new ArrayList<Line>();
+
+ private final Iterator<Line> ctxit; // Iterator for all context lines
+ private final Iterator<Line> dffit; // Iterator for all diff lines
+ private Line ctx; // Points to the current context line
+ private Line dff; // Points to the current diff line
+
+ // Tracks the amount that the "new" line numbers are offset from the "old"
+ // line numbers. new = old + skew
+ private int skew = 0;
+
+ private OutputLinesGenerator(ArrayList<Line> contextLines,
+ ArrayList<Line> diffLines)
+ {
+ ctxit = contextLines.iterator();
+ dffit = diffLines.iterator();
+
+ // Set ctx and dff to first lines (or null if empty)
+ ctxPop(false);
+ dffPop(false);
+
+ /**
+ * Now we have two ordered iterators, one for the context (original
+ * document) and one for the diffs we want to apply to it. We want to
+ * merge them together into the output ArrayList in the proper order,
+ * being careful to throw out any context lines that are made obsolete
+ * by the diff lines.
+ */
+
+ // Do this while loop while both iterators still have elements
+ while (ctx != null && dff != null)
+ {
+ // Now we have a context line (ctx) and a diff line (dff) in hand.
+
+/*
+ Debug.devlogf("DiffIndex: {0}/{1}, {2}-{3}, {4}-{5}",
+ ctx.getDiffIndex(),
+ dff.getDiffIndex(),
+ ctx.getOldLine(),
+ ctx.getNewLine(),
+ dff.getOldLine(),
+ dff.getNewLine());
+*/
+ int cmp = ctx.getDiffIndex() - dff.getDiffIndex();
+ if (cmp < 0)
+ ctxPop(true);
+ else if (cmp > 0)
+ dffPop(true);
+ else
+ {
+ /**
+ * ctx and dff are identical. And since we dropped all Insertions
+ * from contextLines_ and all Sames from diffLines_, we know they
+ * must be either Deletions or Comments.
+ */
+ if (ctx.getType() == Type.Deletion)
+ {
+ dffPop(true);
+ ctxPop(false);
+ }
+ else if (ctx.getType() == Type.Comment)
+ {
+ dffPop(false);
+ ctxPop(true);
+ }
+ else
+ {
+ throw new IllegalStateException(
+ "Unexpected line type: " + ctx.getType().name());
+ }
+ }
+ }
+
+ // Finish off the context iterator if necessary
+ while (ctx != null)
+ ctxPop(true);
+
+ // Finish off the diff iterator if necessary
+ while (dff != null)
+ dffPop(true);
+ }
+
+ /**
+ * (Optionally) adds the value of ctx to the output, and then (always)
+ * sets ctx to the next context line
+ */
+ private void ctxPop(boolean addToOutput)
+ {
+ if (addToOutput)
+ writeContextLine(output, ctx, skew);
+ ctx = ctxit.hasNext() ? ctxit.next() : null;
+ }
+
+ /**
+ * (Optionally) adds the value of dff to the output, and then (always)
+ * sets dff to the next diff line
+ */
+ private void dffPop(boolean addToOutput)
+ {
+ if (addToOutput)
+ skew = writeDiffLine(output, dff, skew);
+ dff = dffit.hasNext() ? dffit.next() : null;
+ }
+
+ private void writeContextLine(ArrayList<Line> output, Line ctx, int skew)
+ {
+ switch (ctx.getType())
+ {
+ case Same:
+ case Comment:
+ output.add(new Line(ctx.getType(), ctx.getOldLine(),
+ ctx.getOldLine() + skew,
+ ctx.getText(),
+ ctx.getDiffIndex()));
+ break;
+ case Deletion:
+ // This is a line that, in the source diff, was deleted from orig.
+ // But since we're processing it as context, we ignore the delete,
+ // so we turn it back into "Same".
+ output.add(new Line(Type.Same, ctx.getOldLine(),
+ ctx.getOldLine() + skew,
+ ctx.getText(),
+ ctx.getDiffIndex()));
+ break;
+ default:
+ assert false : "Unexpected context line type";
+ throw new IllegalStateException();
+ }
+ }
+
+ private int writeDiffLine(ArrayList<Line> output, Line dff, int skew)
+ {
+ switch (dff.getType())
+ {
+ case Deletion:
+ output.add(new Line(Type.Deletion, dff.getOldLine(),
+ dff.getOldLine() + skew,
+ dff.getText(),
+ dff.getDiffIndex()));
+ skew--;
+ break;
+ case Insertion:
+ output.add(new Line(Type.Insertion, dff.getOldLine(),
+ dff.getOldLine() + skew,
+ dff.getText(),
+ dff.getDiffIndex()));
+ skew++;
+ break;
+ default:
+ assert false : "Unexpected diff line type";
+ throw new IllegalStateException();
+ }
+ return skew;
+ }
+
+ /**
+ * Get the result
+ */
+ public ArrayList<Line> getOutput()
+ {
+ return output;
+ }
+ }
+
+ private final ArrayList<Line> contextLines_ = new ArrayList<Line>();
+ private final ArrayList<Line> diffLines_ = new ArrayList<Line>();
+ private final String fileA_;
+ private final String fileB_;
+ private static final String EOL = "\n";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParser.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParser.java
new file mode 100644
index 0000000..3d20af3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParser.java
@@ -0,0 +1,321 @@
+/*
+ * UnifiedParser.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.Line.Type;
+
+import java.util.ArrayList;
+
+public class UnifiedParser implements DiffParser
+{
+ public UnifiedParser(String data)
+ {
+ this(data, 0);
+ }
+
+ public UnifiedParser(String data, int startDiffIndex)
+ {
+ data_ = data;
+ diffIndex_ = startDiffIndex;
+ }
+
+ public int getDiffIndex()
+ {
+ return diffIndex_;
+ }
+
+ @Override
+ public DiffFileHeader nextFilePair()
+ {
+ ArrayList<String> headerLines = new ArrayList<String>();
+
+ boolean inDiff = false;
+
+ String line;
+ while (null != (line = nextLine()) && !line.startsWith("--- "))
+ {
+ if (isNewFileLine(line))
+ inDiff = true;
+
+ if (inDiff)
+ headerLines.add(line);
+ }
+
+ if (line == null)
+ return null;
+
+ String fileA = line.substring(4);
+ line = nextLine();
+ if (line == null || !line.startsWith("+++ "))
+ throw new DiffFormatException("Incomplete file header");
+ String fileB = line.substring(4);
+ return new DiffFileHeader(headerLines, fileA, fileB);
+ }
+
+ @Override
+ public DiffChunk nextChunk()
+ {
+ String nextLine = peekLine();
+ if (nextLine != null && isNewFileLine(nextLine))
+ return null;
+
+ String line;
+ while (null != (line = nextLine()) && !(line.startsWith("@@") || line.startsWith("--- ")))
+ {
+ }
+
+ if (line == null)
+ return null;
+
+ if (line.startsWith("--- "))
+ return null;
+
+
+ ChunkHeaderInfo chunkHeaderInfo = new ChunkHeaderParser(line).parse();
+ if (chunkHeaderInfo == null)
+ throw new DiffFormatException("Malformed chunk header");
+
+ int chunkDiffIndex = diffIndex_++;
+
+ Range[] ranges = chunkHeaderInfo.ranges;
+ int[] counts = new int[ranges.length];
+ int[] positions = new int[ranges.length];
+ boolean[] MASK_NONE = new boolean[ranges.length];
+ boolean[] MASK_ALL = new boolean[ranges.length];
+ for (int i = 0; i < ranges.length; i++)
+ {
+ counts[i] = ranges[i].rowCount;
+ positions[i] = ranges[i].startRow-1;
+ MASK_ALL[i] = true;
+ }
+ int columns = ranges.length - 1;
+
+ boolean[] mask = new boolean[ranges.length];
+
+ ArrayList<Line> lines = new ArrayList<Line>();
+ for (;
+ !isEmpty(counts) || nextLineIsComment();
+ diffIndex_++)
+ {
+ String diffLine = nextLine();
+ if (diffLine == null)
+ throw new DiffFormatException("Diff ended prematurely");
+ if (diffLine.length() < columns)
+ throw new DiffFormatException("Unexpected line format");
+
+ int directive = ' ';
+ for (int i = 0; i < columns; i++)
+ {
+ mask[i] = diffLine.charAt(i) != ' ';
+ if (mask[i])
+ {
+ if (directive == ' ')
+ directive = diffLine.charAt(i);
+ else if (directive != diffLine.charAt(i))
+ throw new DiffFormatException("Conflicting directives");
+ }
+ }
+
+ switch (directive)
+ {
+ case ' ':
+ // All positions increase by one (including new)
+
+ addToSelected(positions, MASK_ALL, +1);
+ addToSelected(counts, MASK_ALL, -1);
+ lines.add(new Line(Type.Same,
+ MASK_ALL,
+ clone(positions),
+ diffLine.substring(columns),
+ diffIndex_));
+ break;
+ case '-':
+ // Masked positions increase by one
+
+ addToSelected(positions, mask, +1);
+ addToSelected(counts, mask, -1);
+ lines.add(new Line(Type.Deletion,
+ clone(mask),
+ clone(positions),
+ diffLine.substring(columns),
+ diffIndex_));
+ break;
+ case '+':
+ // Unmasked positions increase by one (including new)
+
+ addToUnselected(positions, mask, +1);
+ addToUnselected(counts, mask, -1);
+ lines.add(new Line(Type.Insertion,
+ complement(mask),
+ clone(positions),
+ diffLine.substring(columns),
+ diffIndex_));
+ break;
+ case '\\':
+ // No positions move??
+
+ // e.g. "\\ No newline at end of file"
+ lines.add(new Line(Type.Comment,
+ MASK_NONE,
+ clone(positions),
+ diffLine.substring(columns),
+ diffIndex_));
+ break;
+ default:
+ throw new DiffFormatException("Unexpected leading character");
+ }
+ }
+
+ if (!isZero(counts))
+ throw new DiffFormatException("Diff didn't match header ranges");
+
+ return new DiffChunk(ranges, chunkHeaderInfo.extraInfo, lines, chunkDiffIndex);
+ }
+
+ private boolean isNewFileLine(String nextLine)
+ {
+ return nextLine.startsWith("diff ") || nextLine.startsWith("Index: ");
+ }
+
+ private boolean[] complement(boolean[] array)
+ {
+ boolean[] newArray = new boolean[array.length];
+ for (int i = 0; i < array.length; i++)
+ newArray[i] = !array[i];
+ return newArray;
+ }
+
+ private int[] clone(int[] array)
+ {
+ int[] newArray = new int[array.length];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ return newArray;
+ }
+
+ private boolean[] clone(boolean[] array)
+ {
+ boolean[] newArray = new boolean[array.length];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ return newArray;
+ }
+
+ private void addToSelected(int[] array, boolean[] mask, int value)
+ {
+ for (int i = 0; i < mask.length; i++)
+ {
+ if (mask[i])
+ array[i] += value;
+ }
+ }
+
+ private void addToUnselected(int[] array, boolean[] mask, int value)
+ {
+ for (int i = 0; i < mask.length; i++)
+ {
+ if (!mask[i])
+ array[i] += value;
+ }
+ }
+
+ private boolean isEmpty(int[] array)
+ {
+ for (int i : array)
+ {
+ if (i > 0)
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isZero(int[] array)
+ {
+ for (int i : array)
+ {
+ if (i != 0)
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isEOD()
+ {
+ return pos_ >= data_.length();
+ }
+
+ private boolean nextLineIsComment()
+ {
+ return !isEOD() && data_.charAt(pos_) == '\\';
+ }
+
+ private String peekLine()
+ {
+ return nextLine(true);
+ }
+
+ private String nextLine()
+ {
+ return nextLine(false);
+ }
+
+ private String nextLine(boolean peek)
+ {
+ if (isEOD())
+ return null;
+
+ int head = pos_;
+ // i will point to the tail (exclusive) of the string to be returned
+ int i = data_.indexOf('\n', head);
+ // length will indicate how far past i we should set pos_ to (unless peek)
+ int length;
+
+ if (i == -1)
+ {
+ i = data_.length();
+ length = 0;
+ }
+ else if (i > 0 && data_.charAt(i-1) == '\r')
+ {
+ i--;
+ length = 2;
+ }
+ else
+ {
+ length = 1;
+ }
+
+ if (!peek)
+ pos_ = i + length;
+
+ return data_.substring(head, i);
+ }
+
+ private final String data_;
+ private int pos_;
+ private int diffIndex_;
+}
+
+
+class ChunkHeaderInfo
+{
+ ChunkHeaderInfo(Range[] ranges,
+ String extraInfo)
+ {
+ this.ranges = ranges;
+ this.extraInfo = extraInfo;
+ }
+
+ public Range[] ranges;
+ public String extraInfo;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonLeft.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonLeft.png
new file mode 100644
index 0000000..3105816
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonRight.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonRight.png
new file mode 100644
index 0000000..910f812
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonTile.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonTile.png
new file mode 100644
index 0000000..d12172d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallBlueButtonTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonLeft.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonLeft.png
new file mode 100644
index 0000000..febdb76
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonLeft.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonRight.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonRight.png
new file mode 100644
index 0000000..d596c16
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonRight.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonTile.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonTile.png
new file mode 100644
index 0000000..bd22590
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/diff/images/SmallGrayButtonTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/AskPassEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/AskPassEvent.java
new file mode 100644
index 0000000..f49784a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/AskPassEvent.java
@@ -0,0 +1,84 @@
+/*
+ * AskPassEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class AskPassEvent extends GwtEvent<AskPassEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onAskPass(AskPassEvent e);
+ }
+
+ public static class Data extends JavaScriptObject
+ {
+ protected Data() {}
+
+ public native final String getPrompt() /*-{
+ return this.prompt;
+ }-*/;
+
+ public native final String getRememberPrompt() /*-{
+ return this.remember_prompt;
+ }-*/;
+
+ public native final String getWindow() /*-{
+ return this.window;
+ }-*/;
+ }
+
+ public AskPassEvent(AskPassEvent.Data data)
+ {
+ prompt_ = data.getPrompt();
+ rememberPrompt_ = data.getRememberPrompt();
+ window_ = data.getWindow();
+ }
+
+ public String getPrompt()
+ {
+ return prompt_;
+ }
+
+ public String getRememberPasswordPrompt()
+ {
+ return rememberPrompt_;
+ }
+
+ public String getWindow()
+ {
+ return window_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onAskPass(this);
+ }
+
+ private final String prompt_;
+ private final String rememberPrompt_;
+ private final String window_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionEvent.java
new file mode 100644
index 0000000..bc959f0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionEvent.java
@@ -0,0 +1,61 @@
+/*
+ * DiffChunkActionEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.DiffChunk;
+
+public class DiffChunkActionEvent extends GwtEvent<DiffChunkActionHandler>
+{
+ public enum Action
+ {
+ Stage,
+ Unstage,
+ Discard
+ }
+
+ public DiffChunkActionEvent(Action action, DiffChunk diffChunk)
+ {
+ action_ = action;
+ diffChunk_ = diffChunk;
+ }
+
+ public Action getAction()
+ {
+ return action_;
+ }
+
+ public DiffChunk getDiffChunk()
+ {
+ return diffChunk_;
+ }
+
+ @Override
+ public Type<DiffChunkActionHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(DiffChunkActionHandler handler)
+ {
+ handler.onDiffChunkAction(this);
+ }
+
+ private Action action_;
+ private DiffChunk diffChunk_;
+
+ public static final Type<DiffChunkActionHandler> TYPE = new Type<DiffChunkActionHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionHandler.java
new file mode 100644
index 0000000..a89f56f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffChunkActionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * DiffChunkActionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface DiffChunkActionHandler extends EventHandler
+{
+ void onDiffChunkAction(DiffChunkActionEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionEvent.java
new file mode 100644
index 0000000..004f598
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionEvent.java
@@ -0,0 +1,47 @@
+/*
+ * DiffLinesActionEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionEvent.Action;
+
+public class DiffLinesActionEvent extends GwtEvent<DiffLinesActionHandler>
+{
+ public DiffLinesActionEvent(Action action)
+ {
+ action_ = action;
+ }
+
+ public Action getAction()
+ {
+ return action_;
+ }
+
+ @Override
+ public Type<DiffLinesActionHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(DiffLinesActionHandler handler)
+ {
+ handler.onDiffLinesAction(this);
+ }
+
+ private final Action action_;
+
+ public static final Type<DiffLinesActionHandler> TYPE = new Type<DiffLinesActionHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionHandler.java
new file mode 100644
index 0000000..ac22f78
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/DiffLinesActionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * DiffLinesActionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface DiffLinesActionHandler extends EventHandler
+{
+ void onDiffLinesAction(DiffLinesActionEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsDiffEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsDiffEvent.java
new file mode 100644
index 0000000..e05d6f1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsDiffEvent.java
@@ -0,0 +1,54 @@
+/*
+ * ShowVcsDiffEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowVcsDiffEvent extends GwtEvent<ShowVcsDiffEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShowVcsDiff(ShowVcsDiffEvent event);
+ }
+
+ public ShowVcsDiffEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onShowVcsDiff(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private final FileSystemItem file_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsHistoryEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsHistoryEvent.java
new file mode 100644
index 0000000..ea3ff3e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ShowVcsHistoryEvent.java
@@ -0,0 +1,54 @@
+/*
+ * ShowVcsHistoryEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ShowVcsHistoryEvent extends GwtEvent<ShowVcsHistoryEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onShowVcsHistory(ShowVcsHistoryEvent event);
+ }
+
+ public ShowVcsHistoryEvent(FileSystemItem fileFilter)
+ {
+ fileFilter_ = fileFilter;
+ }
+
+ public FileSystemItem getFileFilter()
+ {
+ return fileFilter_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onShowVcsHistory(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private final FileSystemItem fileFilter_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageEvent.java
new file mode 100644
index 0000000..5489095
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageEvent.java
@@ -0,0 +1,56 @@
+/*
+ * StageUnstageEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+
+import java.util.ArrayList;
+
+public class StageUnstageEvent extends GwtEvent<StageUnstageHandler>
+{
+ public StageUnstageEvent(boolean unstage, ArrayList<StatusAndPath> paths)
+ {
+ unstage_ = unstage;
+ this.paths = paths;
+ }
+
+ public boolean isUnstage()
+ {
+ return unstage_;
+ }
+
+ public ArrayList<StatusAndPath> getPaths()
+ {
+ return paths;
+ }
+
+ @Override
+ public Type<StageUnstageHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(StageUnstageHandler handler)
+ {
+ handler.onStageUnstage(this);
+ }
+
+ private final boolean unstage_;
+ private final ArrayList<StatusAndPath> paths;
+
+ public static final Type<StageUnstageHandler> TYPE = new Type<StageUnstageHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageHandler.java
new file mode 100644
index 0000000..f4a8f8a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/StageUnstageHandler.java
@@ -0,0 +1,22 @@
+/*
+ * StageUnstageHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface StageUnstageHandler extends EventHandler
+{
+ void onStageUnstage(StageUnstageEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/SwitchViewEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/SwitchViewEvent.java
new file mode 100644
index 0000000..afa318a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/SwitchViewEvent.java
@@ -0,0 +1,44 @@
+/*
+ * SwitchViewEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class SwitchViewEvent extends GwtEvent<SwitchViewEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onSwitchView(SwitchViewEvent event);
+ }
+
+ public SwitchViewEvent()
+ {
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onSwitchView(this);
+ }
+
+ private static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshEvent.java
new file mode 100644
index 0000000..68ed790
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshEvent.java
@@ -0,0 +1,60 @@
+/*
+ * VcsRefreshEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class VcsRefreshEvent extends GwtEvent<VcsRefreshHandler>
+{
+ public enum Reason { NA, FileChange, VcsOperation }
+
+ private final Reason reason_;
+ private final int delayMs_;
+
+ public static final Type<VcsRefreshHandler> TYPE = new Type<VcsRefreshHandler>();
+
+ public VcsRefreshEvent(Reason reason)
+ {
+ this(reason, 0);
+ }
+
+ public VcsRefreshEvent(Reason reason, int delayMs)
+ {
+ reason_ = reason;
+ delayMs_ = delayMs;
+ }
+
+ public Reason getReason()
+ {
+ return reason_;
+ }
+
+ public int getDelayMs()
+ {
+ return delayMs_;
+ }
+
+ @Override
+ public Type<VcsRefreshHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(VcsRefreshHandler handler)
+ {
+ handler.onVcsRefresh(this);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshHandler.java
new file mode 100644
index 0000000..af4def0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRefreshHandler.java
@@ -0,0 +1,22 @@
+/*
+ * VcsRefreshHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface VcsRefreshHandler extends EventHandler
+{
+ void onVcsRefresh(VcsRefreshEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRevertFileEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRevertFileEvent.java
new file mode 100644
index 0000000..fb1cc4f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsRevertFileEvent.java
@@ -0,0 +1,54 @@
+/*
+ * VcsRevertFileEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class VcsRevertFileEvent extends GwtEvent<VcsRevertFileEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onVcsRevertFile(VcsRevertFileEvent event);
+ }
+
+ public VcsRevertFileEvent(FileSystemItem file)
+ {
+ file_ = file;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onVcsRevertFile(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private final FileSystemItem file_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsViewOnGitHubEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsViewOnGitHubEvent.java
new file mode 100644
index 0000000..9736fb6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/VcsViewOnGitHubEvent.java
@@ -0,0 +1,54 @@
+/*
+ * VcsViewOnGitHubEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import org.rstudio.studio.client.workbench.views.vcs.common.model.GitHubViewRequest;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class VcsViewOnGitHubEvent extends GwtEvent<VcsViewOnGitHubEvent.Handler>
+{
+ public interface Handler extends EventHandler
+ {
+ void onVcsViewOnGitHub(VcsViewOnGitHubEvent event);
+ }
+
+ public VcsViewOnGitHubEvent(GitHubViewRequest viewRequest)
+ {
+ viewRequest_ = viewRequest;
+ }
+
+ public GitHubViewRequest getViewRequest()
+ {
+ return viewRequest_;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onVcsViewOnGitHub(this);
+ }
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+
+ private final GitHubViewRequest viewRequest_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionEvent.java
new file mode 100644
index 0000000..a89e34d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionEvent.java
@@ -0,0 +1,53 @@
+/*
+ * ViewFileRevisionEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ViewFileRevisionEvent extends GwtEvent<ViewFileRevisionHandler>
+{
+ public ViewFileRevisionEvent(String revision, String filename)
+ {
+ revision_ = revision;
+ filename_ = filename;
+ }
+
+ public String getRevision()
+ {
+ return revision_;
+ }
+
+ public String getFilename()
+ {
+ return filename_;
+ }
+
+ @Override
+ public Type<ViewFileRevisionHandler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ViewFileRevisionHandler handler)
+ {
+ handler.onViewFileRevision(this);
+ }
+
+ private final String revision_;
+ private final String filename_;
+
+ public static final Type<ViewFileRevisionHandler> TYPE = new Type<ViewFileRevisionHandler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionHandler.java
new file mode 100644
index 0000000..ee1d1c5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/events/ViewFileRevisionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * ViewFileRevisionHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface ViewFileRevisionHandler extends EventHandler
+{
+ void onViewFileRevision(ViewFileRevisionEvent event);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButton.png
new file mode 100755
index 0000000..341541f
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButtonDisabled.png
new file mode 100755
index 0000000..0f8afe1
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageBackwardButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButton.png
new file mode 100755
index 0000000..53de9aa
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButtonDisabled.png
new file mode 100755
index 0000000..de3cb12
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageFirstButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButton.png
new file mode 100755
index 0000000..e8b5034
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButtonDisabled.png
new file mode 100755
index 0000000..e0414d6
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageForwardButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButton.png
new file mode 100755
index 0000000..8acd352
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButtonDisabled.png
new file mode 100755
index 0000000..8525585
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageLastButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButton.png
new file mode 100755
index 0000000..4559e2c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButtonDisabled.png
new file mode 100755
index 0000000..7bdcd99
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PageNextButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButton.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButton.png
new file mode 100755
index 0000000..dd8f1df
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButton.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButtonDisabled.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButtonDisabled.png
new file mode 100755
index 0000000..0b1ab5c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/images/PagePreviousButtonDisabled.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/GitHubViewRequest.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/GitHubViewRequest.java
new file mode 100644
index 0000000..b53cf89
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/GitHubViewRequest.java
@@ -0,0 +1,68 @@
+/*
+ * GitHubViewRequest.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.client.workbench.views.vcs.common.model;
+
+import org.rstudio.core.client.files.FileSystemItem;
+
+public class GitHubViewRequest
+{
+ public enum ViewType
+ {
+ View,
+ Blame
+ }
+
+ public GitHubViewRequest(FileSystemItem file, ViewType type)
+ {
+ this(file, type, -1, -1);
+ }
+
+ public GitHubViewRequest(FileSystemItem file,
+ ViewType type,
+ int startLine,
+ int endLine)
+ {
+ file_ = file;
+ type_ = type;
+ startLine_ = startLine;
+ endLine_ = endLine;
+ }
+
+ public FileSystemItem getFile()
+ {
+ return file_;
+ }
+
+ public ViewType getViewType()
+ {
+ return type_;
+ }
+
+ public int getStartLine()
+ {
+ return startLine_;
+ }
+
+ public int getEndLine()
+ {
+ return endLine_;
+ }
+
+ private final FileSystemItem file_;
+ private final ViewType type_;
+ private final int startLine_;
+ private final int endLine_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/VcsState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/VcsState.java
new file mode 100644
index 0000000..c26e056
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/common/model/VcsState.java
@@ -0,0 +1,194 @@
+/*
+ * VcsState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.model;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.StatusAndPathInfo;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler;
+import org.rstudio.studio.client.workbench.views.files.model.FileChange;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+
+import java.util.ArrayList;
+
+public abstract class VcsState
+{
+ public VcsState(EventBus eventBus,
+ GlobalDisplay globalDisplay,
+ final Session session)
+ {
+ eventBus_ = eventBus;
+ globalDisplay_ = globalDisplay;
+ session_ = session;
+ final HandlerRegistrations registrations = new HandlerRegistrations();
+ registrations.add(eventBus_.addHandler(VcsRefreshEvent.TYPE, new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ if (!session.getSessionInfo().isVcsEnabled())
+ registrations.removeHandler();
+
+ if (event.getDelayMs() > 0)
+ {
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand()
+ {
+ @Override
+ public boolean execute()
+ {
+ refresh(false);
+
+ return false;
+ }
+ }, event.getDelayMs());
+ }
+ else
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ refresh(false);
+ }
+ });
+ }
+ }
+ }));
+ registrations.add(eventBus_.addHandler(FileChangeEvent.TYPE, new FileChangeHandler()
+ {
+ @Override
+ public void onFileChange(FileChangeEvent event)
+ {
+ if (!session.getSessionInfo().isVcsEnabled())
+ registrations.removeHandler();
+
+ FileChange fileChange = event.getFileChange();
+ FileSystemItem file = fileChange.getFile();
+
+ StatusAndPath status = StatusAndPath.fromInfo(
+ getStatusFromFile(file));
+
+ if (needsFullRefresh(file))
+ {
+ refresh(false);
+ return;
+ }
+
+ if (status_ != null && status != null)
+ {
+ for (int i = 0; i < status_.size(); i++)
+ {
+ if (status.getRawPath().equals(status_.get(i).getRawPath()))
+ {
+ if (StringUtil.notNull(status.getStatus()).trim().length() == 0)
+ status_.remove(i);
+ else
+ status_.set(i, status);
+ handlers_.fireEvent(new VcsRefreshEvent(Reason.FileChange));
+ return;
+ }
+ }
+
+ if (status.getStatus().trim().length() != 0)
+ {
+ status_.add(status);
+ handlers_.fireEvent(new VcsRefreshEvent(Reason.FileChange));
+ return;
+ }
+ }
+ }
+ }));
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ refresh(false);
+ }
+ });
+ }
+
+ public void bindRefreshHandler(Widget owner,
+ final VcsRefreshHandler handler)
+ {
+ new WidgetHandlerRegistration(owner)
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return addVcsRefreshHandler(handler);
+ }
+ };
+ }
+
+ public HandlerRegistration addVcsRefreshHandler(VcsRefreshHandler handler)
+ {
+ return addVcsRefreshHandler(handler, true);
+ }
+
+ public HandlerRegistration addVcsRefreshHandler(VcsRefreshHandler handler,
+ boolean fireOnAdd)
+ {
+ HandlerRegistration hreg = handlers_.addHandler(
+ VcsRefreshEvent.TYPE, handler);
+
+ if (fireOnAdd && isInitialized())
+ handler.onVcsRefresh(new VcsRefreshEvent(Reason.VcsOperation));
+
+ return hreg;
+ }
+
+ public ArrayList<StatusAndPath> getStatus()
+ {
+ return status_;
+ }
+
+ public void refresh()
+ {
+ if (session_.getSessionInfo().isVcsEnabled())
+ refresh(true);
+ }
+
+ protected abstract StatusAndPathInfo getStatusFromFile(FileSystemItem file);
+
+ protected abstract boolean needsFullRefresh(FileSystemItem file);
+
+ public abstract void refresh(final boolean showError);
+
+ protected abstract boolean isInitialized();
+
+ protected final HandlerManager handlers_ = new HandlerManager(this);
+ protected ArrayList<StatusAndPath> status_;
+ protected final EventBus eventBus_;
+ protected final GlobalDisplay globalDisplay_;
+ protected final Session session_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitCount.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitCount.java
new file mode 100644
index 0000000..ae63b04
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitCount.java
@@ -0,0 +1,28 @@
+/*
+ * CommitCount.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CommitCount extends JavaScriptObject
+{
+ protected CommitCount()
+ {
+ }
+
+ public native final int getCount() /*-{
+ return this.count;
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.java
new file mode 100644
index 0000000..8270ac4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.java
@@ -0,0 +1,275 @@
+/*
+ * CommitDetail.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.Point;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.dom.DomUtils;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.ProgressPanel;
+import org.rstudio.core.client.widget.images.ProgressImages;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ViewFileRevisionEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ViewFileRevisionHandler;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.CommitDetailDisplay;
+
+import java.util.ArrayList;
+
+public class CommitDetail extends Composite implements CommitDetailDisplay
+{
+ interface Binder extends UiBinder<Widget, CommitDetail>
+ {}
+
+ public CommitDetail()
+ {
+ sizeWarning_ = new SizeWarningWidget("commit");
+ sizeWarning_.setVisible(false);
+ progressPanel_ = new ProgressPanel(ProgressImages.createLargeGray());
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ ThemeStyles styles = ThemeStyles.INSTANCE;
+ labelId_.addStyleName(styles.selectableText());
+ labelParent_.addStyleName(styles.selectableText());
+ }
+
+ public void setIdDesc(String idDesc)
+ {
+ labelIdDesc_.setText(idDesc);
+ }
+
+ public void setScrollPanel(ScrollPanel container)
+ {
+ container_ = container;
+ }
+
+ @Override
+ public void setSelectedCommit(CommitInfo commit)
+ {
+ commit_ = commit;
+ if (commit_ != null)
+ updateInfo();
+
+ commitViewPanel_.setVisible(commit_ != null);
+ emptySelectionLabel_.setVisible(!(commit_ != null));
+ }
+
+ @Override
+ public void clearDetails()
+ {
+ invalidation_.invalidate();
+ tocPanel_.clear();
+ detailPanel_.clear();
+
+ setProgressVisible(false);
+ }
+
+ @Override
+ public void showDetailProgress()
+ {
+ clearDetails();
+
+ setProgressVisible(true);
+ }
+
+ @Override
+ public void setDetails(final DiffParser unifiedParser,
+ final boolean suppressViewLink)
+ {
+ setProgressVisible(false);
+
+ invalidation_.invalidate();
+ final Token token = invalidation_.getInvalidationToken();
+
+ Scheduler.get().scheduleIncremental(new RepeatingCommand() {
+ @Override
+ public boolean execute()
+ {
+ if (token.isInvalid())
+ return false;
+
+ final DiffFileHeader fileHeader = unifiedParser.nextFilePair();
+ if (fileHeader == null)
+ return false;
+
+ int filesCompared = 2;
+ ArrayList<ChunkOrLine> lines = new ArrayList<ChunkOrLine>();
+ DiffChunk chunk;
+ while (null != (chunk = unifiedParser.nextChunk()))
+ {
+ if (!chunk.shouldIgnore())
+ filesCompared = chunk.getRanges().length;
+ lines.addAll(ChunkOrLine.fromChunk(chunk));
+ }
+
+ LineTableView view = new LineTableView(filesCompared);
+ view.setUseStartBorder(true);
+ view.setUseEndBorder(false);
+ view.setShowActions(false);
+ view.setData(lines, PatchMode.Stage);
+ view.setWidth("100%");
+
+ final DiffFrame diffFrame = new DiffFrame(
+ null,
+ fileHeader.getDescription(),
+ null,
+ commit_.getId(),
+ view,
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fireEvent(new ViewFileRevisionEvent(
+ commit_.getId(),
+ fileHeader.getDescription().trim()));
+
+ }
+ },
+ suppressViewLink);
+ diffFrame.setWidth("100%");
+ detailPanel_.add(diffFrame);
+
+ CommitTocRow tocAnchor = new CommitTocRow(fileHeader.getDescription());
+ tocAnchor.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ Point relativePosition = DomUtils.getRelativePosition(
+ container_.getElement(),
+ diffFrame.getElement());
+ container_.setVerticalScrollPosition(relativePosition.getY());
+ }
+ });
+ tocPanel_.add(tocAnchor);
+
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void setCommitListIsLoading(boolean isLoading)
+ {
+ emptySelectionLabel_.setText(isLoading ? "" : "(No commit selected)");
+ }
+
+ @Override
+ public HandlerRegistration addViewFileRevisionHandler(
+ ViewFileRevisionHandler handler)
+ {
+ return addHandler(handler, ViewFileRevisionEvent.TYPE);
+ }
+
+ private void updateInfo()
+ {
+ labelId_.setText(commit_.getId());
+ labelAuthor_.setText(commit_.getAuthor());
+ labelDate_.setText(
+ DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT).format(commit_.getDate()) +
+ " " +
+ DateTimeFormat.getFormat(PredefinedFormat.TIME_SHORT).format(commit_.getDate())
+ );
+ labelSubject_.setText(commit_.getSubject());
+
+ parentTableRow_.getStyle().setProperty(
+ "display",
+ StringUtil.isNullOrEmpty(commit_.getParent()) ? "none"
+ : "table-row");
+ labelParent_.setText(commit_.getParent());
+ }
+
+ public void showSizeWarning(long sizeInBytes)
+ {
+ tocPanel_.setVisible(false);
+ detailPanel_.setVisible(false);
+ setProgressVisible(false);
+ sizeWarning_.setSize(sizeInBytes);
+ sizeWarning_.setVisible(true);
+ }
+
+ private void setProgressVisible(boolean visible)
+ {
+ if (visible)
+ {
+ progressPanel_.setVisible(true);
+ progressPanel_.beginProgressOperation(100);
+ }
+ else
+ {
+ progressPanel_.setVisible(false);
+ progressPanel_.endProgressOperation();
+ }
+ }
+
+ public void hideSizeWarning()
+ {
+ tocPanel_.setVisible(true);
+ detailPanel_.setVisible(true);
+ sizeWarning_.setVisible(false);
+ }
+
+ public HasClickHandlers getOverrideSizeWarningButton()
+ {
+ return sizeWarning_;
+ }
+
+ private final Invalidation invalidation_ = new Invalidation();
+ private CommitInfo commit_;
+ @UiField
+ Label labelIdDesc_;
+ @UiField
+ Label labelId_;
+ @UiField
+ Label labelAuthor_;
+ @UiField
+ Label labelDate_;
+ @UiField
+ Label labelSubject_;
+ @UiField
+ Label labelParent_;
+ @UiField
+ VerticalPanel tocPanel_;
+ @UiField(provided = true)
+ ProgressPanel progressPanel_;
+ @UiField
+ VerticalPanel detailPanel_;
+ @UiField(provided = true)
+ SizeWarningWidget sizeWarning_;
+ @UiField
+ TableRowElement parentTableRow_;
+ @UiField
+ Label emptySelectionLabel_;
+ @UiField
+ HTMLPanel commitViewPanel_;
+
+ private ScrollPanel container_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.ui.xml
new file mode 100644
index 0000000..95c6bc5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitDetail.ui.xml
@@ -0,0 +1,84 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:hist='urn:import:org.rstudio.studio.client.workbench.views.vcs.dialog'
+ xmlns:widget="urn:import:org.rstudio.core.client.widget">
+
+ <ui:style>
+ .summary td:first-child {
+ font-weight: bold;
+ padding-right: 16px;
+ }
+
+ .subject {
+ font-weight: bold;
+ }
+
+ .summary td div {
+ white-space: pre-wrap;
+ }
+
+ .summary td {
+ padding-bottom: 6px;
+ }
+
+ .toc {
+
+ }
+ .detailPanel {
+ width: 100%;
+ }
+ .emptySelectionLabel {
+ width: 100%;
+ height: 48px;
+ margin-top: 80px;
+ font-size: 130%;
+ font-weight: bold;
+ color: #AAA;
+ text-align: center;
+ }
+ .progressPanel {
+ width: 100%;
+ height: 48px;
+ margin-top: 30px;
+ }
+ </ui:style>
+
+ <g:FlowPanel>
+ <!-- Text will be dynamically set to "(No commit selected)" -->
+ <g:Label ui:field="emptySelectionLabel_"
+ styleName="{style.emptySelectionLabel}"/>
+
+ <g:HTMLPanel ui:field="commitViewPanel_" visible="false">
+ <table class="{style.summary}" cellspacing="0" cellpadding="0">
+ <tr>
+ <td><g:Label ui:field="labelIdDesc_" /></td>
+ <td><g:Label ui:field="labelId_" /></td>
+ </tr>
+ <tr>
+ <td>Author</td>
+ <td><g:Label ui:field="labelAuthor_" /></td>
+ </tr>
+ <tr>
+ <td>Date</td>
+ <td><g:Label ui:field="labelDate_" /></td>
+ </tr>
+ <tr>
+ <td>Subject</td>
+ <td class="{style.subject}"><g:Label ui:field="labelSubject_" /></td>
+ </tr>
+ <tr ui:field="parentTableRow_">
+ <td>Parent</td>
+ <td><g:Label ui:field="labelParent_" /></td>
+ </tr>
+ </table>
+ <hist:SizeWarningWidget ui:field="sizeWarning_"/>
+ <g:VerticalPanel ui:field="tocPanel_" styleName="{style.toc}" />
+ <g:VerticalPanel ui:field="detailPanel_" styleName="{style.detailPanel}" />
+
+ <widget:ProgressPanel ui:field="progressPanel_" visible="false"
+ styleName="{style.progressPanel}"/>
+
+ </g:HTMLPanel>
+ </g:FlowPanel>
+
+</ui:UiBinder>
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitFilterToolbarButton.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitFilterToolbarButton.java
new file mode 100644
index 0000000..9886854
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitFilterToolbarButton.java
@@ -0,0 +1,171 @@
+/*
+ * CommitFilterToolbarButton.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.inject.Inject;
+
+public class CommitFilterToolbarButton extends ToolbarButton
+ implements HasValue<FileSystemItem>
+{
+ @Inject
+ public CommitFilterToolbarButton(FileTypeRegistry fileTypeRegistry,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileContext,
+ Session session)
+ {
+ super(ALL_COMMITS,
+ StandardIcons.INSTANCE.empty_command(),
+ new ToolbarPopupMenu());
+
+ fileTypeRegistry_ = fileTypeRegistry;
+ fileDialogs_ = fileDialogs;
+ fileContext_ = fileContext;
+ session_ = session;
+
+ setTitle(ALL_COMMITS_TITLE);
+
+ ToolbarPopupMenu menu = getMenu();
+
+ menu.addItem(new MenuItem("(all commits)", new Command() {
+ @Override
+ public void execute()
+ {
+ setValue(null);
+ }
+ }));
+
+ menu.addItem(new MenuItem("Filter by File...", new Command() {
+ @Override
+ public void execute()
+ {
+ fileDialogs_.openFile("Choose File",
+ fileContext_,
+ getInitialChooserPath(),
+ chooserOperation());
+ }
+ }));
+
+ menu.addItem(new MenuItem("Filter by Directory...", new Command() {
+ @Override
+ public void execute()
+ {
+ fileDialogs_.chooseFolder("Choose Folder",
+ fileContext_,
+ getInitialChooserPath(),
+ chooserOperation());
+
+ }
+ }));
+
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<FileSystemItem> handler)
+ {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ @Override
+ public FileSystemItem getValue()
+ {
+ return value_;
+ }
+
+ @Override
+ public void setValue(FileSystemItem value)
+ {
+ setValue(value, true);
+
+ }
+
+ @Override
+ public void setValue(FileSystemItem value, boolean fireEvents)
+ {
+ if (!FileSystemItem.areEqual(value, value_))
+ {
+ value_ = value;
+
+ if (value_ == null)
+ {
+ setLeftImage(StandardIcons.INSTANCE.empty_command());
+ setText(ALL_COMMITS);
+ setTitle(ALL_COMMITS_TITLE);
+ }
+ else
+ {
+ if (value_.isDirectory())
+ setLeftImage(value_.getIcon());
+ else
+ setLeftImage(fileTypeRegistry_.getIconForFile(value_));
+ setText(value_.getName());
+ setTitle("Filter: " + value_.getPath());
+ }
+
+ if (fireEvents)
+ ValueChangeEvent.fire(CommitFilterToolbarButton.this, value_);
+ }
+
+ }
+
+ private FileSystemItem getInitialChooserPath()
+ {
+ return value_ != null ? value_.getParentPath() :
+ session_.getSessionInfo().getActiveProjectDir();
+ }
+
+ private ProgressOperationWithInput<FileSystemItem> chooserOperation()
+ {
+ return new ProgressOperationWithInput<FileSystemItem>() {
+
+ @Override
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ indicator.onCompleted();
+
+ if (input != null)
+ setValue(input);
+ }
+ };
+ }
+
+
+ private FileSystemItem value_ = null;
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final RemoteFileSystemContext fileContext_;
+ private final FileDialogs fileDialogs_;
+ private final Session session_;
+
+ private static final String ALL_COMMITS = "(all commits)";
+ private static final String ALL_COMMITS_TITLE = "Filter: (None)";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitInfo.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitInfo.java
new file mode 100644
index 0000000..87c1399
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitInfo.java
@@ -0,0 +1,66 @@
+/*
+ * CommitInfo.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+import java.util.Date;
+
+public class CommitInfo extends JavaScriptObject
+{
+ protected CommitInfo() {}
+
+ public native final String getId() /*-{
+ return this.id;
+ }-*/;
+
+ public native final String getAuthor() /*-{
+ return this.author;
+ }-*/;
+
+ public native final String getParent() /*-{
+ return this.parent || "";
+ }-*/;
+
+ public native final String getSubject() /*-{
+ return this.subject;
+ }-*/;
+
+ public native final String getDescription() /*-{
+ return this.description || "";
+ }-*/;
+
+ public native final String getGraph() /*-{
+ return this.graph || "";
+ }-*/;
+
+ public final Date getDate()
+ {
+ return new Date((long) getDateRaw() * 1000);
+ }
+
+ public native final double getDateRaw() /*-{
+ return this.date;
+ }-*/;
+
+ public native final JsArrayString getRefs() /*-{
+ return this.refs || [];
+ }-*/;
+
+ public native final JsArrayString getTags() /*-{
+ return this.tags || [];
+ }-*/;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTable.java
new file mode 100644
index 0000000..c6395fc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTable.java
@@ -0,0 +1,327 @@
+/*
+ * CommitListTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.cell.client.AbstractSafeHtmlCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.CellTable;
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.TextColumn;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.gwt.view.client.SingleSelectionModel;
+import org.rstudio.core.client.SafeHtmlUtil;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.js.JsUtil;
+import org.rstudio.core.client.theme.RStudioCellTableStyle;
+import org.rstudio.core.client.widget.MultiSelectCellTable;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPanel.Styles;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.CommitListDisplay;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.graph.GraphLine;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.graph.GraphTheme;
+
+import java.util.List;
+
+public class CommitListTable extends MultiSelectCellTable<CommitInfo>
+ implements CommitListDisplay
+{
+ interface Resources extends CellTable.Resources
+ {
+ @Override
+ @Source({RStudioCellTableStyle.RSTUDIO_DEFAULT_CSS, "CommitListTableCellTableStyle.css"})
+ Style cellTableStyle();
+ }
+
+ interface CommitListTableCellTableStyle extends CellTable.Style
+ {
+ }
+
+ private static class CommitColumn extends Column<CommitInfo, CommitInfo>
+ {
+ private static class RenderCell extends AbstractSafeHtmlCell<CommitInfo>
+ {
+ private RenderCell(SafeHtmlRenderer<CommitInfo> commitInfoSafeHtmlRenderer,
+ String... consumedEvents)
+ {
+ super(commitInfoSafeHtmlRenderer, consumedEvents);
+ }
+
+ @Override
+ protected void render(Context context, SafeHtml data, SafeHtmlBuilder sb)
+ {
+ if (data != null)
+ sb.append(data);
+ }
+ }
+
+ private CommitColumn(SafeHtmlRenderer<CommitInfo> renderer)
+ {
+ super(new RenderCell(renderer));
+ }
+
+ @Override
+ public CommitInfo getValue(CommitInfo object)
+ {
+ return object;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private class GraphAndSubjectRenderer implements SafeHtmlRenderer<CommitInfo>
+ {
+ private GraphAndSubjectRenderer(GraphTheme theme)
+ {
+ graphRenderer_ = new GraphRenderer(theme);
+ subjectRenderer_ = new SubjectRenderer();
+ }
+
+ @Override
+ public SafeHtml render(CommitInfo object)
+ {
+ return SafeHtmlUtil.concat(graphRenderer_.render(object),
+ subjectRenderer_.render(object));
+ }
+
+ @Override
+ public void render(CommitInfo object, SafeHtmlBuilder builder)
+ {
+ builder.append(render(object));
+ }
+
+ private final GraphRenderer graphRenderer_;
+ private final SubjectRenderer subjectRenderer_;
+ }
+
+ private class GraphRenderer implements SafeHtmlRenderer<CommitInfo>
+ {
+ public GraphRenderer(GraphTheme theme)
+ {
+ theme_ = theme;
+ }
+
+ @Override
+ public SafeHtml render(CommitInfo object)
+ {
+ if (lastGraphImg_ != null && object.getGraph().equals(lastGraph_))
+ return lastGraphImg_;
+
+ lastGraph_ = object.getGraph();
+ if (object.getGraph().length() == 0)
+ return lastGraphImg_ = SafeHtmlUtil.createEmpty();
+ return lastGraphImg_ = new GraphLine(object.getGraph()).render(theme_);
+ }
+
+ @Override
+ public void render(CommitInfo object, SafeHtmlBuilder builder)
+ {
+ builder.append(render(object));
+ }
+
+ private final GraphTheme theme_;
+ private String lastGraph_ = "";
+ private SafeHtml lastGraphImg_;
+ }
+
+ private class SubjectRenderer implements SafeHtmlRenderer<CommitInfo>
+ {
+ @Override
+ public SafeHtml render(CommitInfo commit)
+ {
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+
+ for (String ref : JsUtil.asIterable(commit.getRefs()))
+ {
+ String style = styles_.ref();
+ if (ref.startsWith("refs/heads/"))
+ {
+ ref = ref.substring("refs/heads/".length());
+ style += " " + styles_.branch();
+ }
+ else if (ref.startsWith("refs/remotes/"))
+ {
+ ref = ref.substring("refs/remotes/".length());
+ style += " " + styles_.remote();
+ }
+ else if (ref.equals("HEAD"))
+ {
+ style += " " + styles_.head();
+ }
+
+ SafeHtmlUtil.appendSpan(builder, style, ref);
+ }
+ for (String tag : JsUtil.asIterable(commit.getTags()))
+ {
+ if (tag.startsWith("refs/tags/"))
+ tag = tag.substring("refs/tags/".length());
+ SafeHtmlUtil.appendSpan(builder, styles_.tag(), tag);
+ }
+
+ builder.appendEscaped(commit.getSubject());
+
+ return builder.toSafeHtml();
+ }
+
+ @Override
+ public void render(CommitInfo object, SafeHtmlBuilder builder)
+ {
+ builder.append(render(object));
+ }
+ }
+
+ public CommitListTable(Styles styles, String idColName)
+ {
+ super(100,
+ GWT.<Resources>create(Resources.class));
+ styles_ = styles;
+
+ graphTheme_ = new GraphTheme(styles.graphLineImg());
+ graphCol_ = new CommitColumn(new GraphRenderer(graphTheme_));
+ addColumn(graphCol_);
+
+
+ CommitColumn subjectCol = new CommitColumn(new SubjectRenderer());
+ addColumn(subjectCol, "Subject");
+
+ TextColumn<CommitInfo> authorCol = new TextColumn<CommitInfo>()
+ {
+ @Override
+ public String getValue(CommitInfo object)
+ {
+ return object.getAuthor();
+ }
+ };
+ addColumn(authorCol, "Author");
+
+ TextColumn<CommitInfo> dateCol = new TextColumn<CommitInfo>()
+ {
+ @Override
+ public String getValue(CommitInfo object)
+ {
+ return DateTimeFormat.getFormat(
+ PredefinedFormat.DATE_SHORT).format(object.getDate());
+ }
+ };
+ addColumn(dateCol, "Date");
+
+ TextColumn<CommitInfo> idCol = new TextColumn<CommitInfo>()
+ {
+ @Override
+ public String getValue(CommitInfo object)
+ {
+ return object.getId();
+ }
+ };
+ addColumn(idCol, idColName);
+ setColumnWidth(graphCol_, "0");
+ setColumnWidth(idCol, "100px");
+ setColumnWidth(subjectCol, "67%");
+ setColumnWidth(authorCol, "33%");
+ setColumnWidth(dateCol, "100px");
+
+ selectionModel_ = new SingleSelectionModel<CommitInfo>();
+ setSelectionModel(selectionModel_);
+
+ }
+
+ private void updateGraphColumnWidth()
+ {
+ int width = 0;
+ for (CommitInfo commit : getVisibleItems())
+ {
+ if (!StringUtil.isNullOrEmpty(commit.getGraph()))
+ {
+ width = Math.max(
+ width,
+ new GraphLine(commit.getGraph()).getTotalWidth(graphTheme_));
+ }
+ }
+
+ if (width > 0)
+ setColumnWidth(graphCol_, (width + 12) + "px");
+ else
+ setColumnWidth(graphCol_, "0");
+ }
+
+ @Override
+ public void setRowData(int start, List<? extends CommitInfo> values)
+ {
+ if (selectionModel_.getSelectedObject() != null)
+ {
+ selectionModel_.setSelected(selectionModel_.getSelectedObject(),
+ false);
+ }
+ super.setRowData(start, values);
+ updateGraphColumnWidth();
+ maybePreselectFirstRow();
+ }
+
+ private void maybePreselectFirstRow()
+ {
+ if (!autoSelectFirstRow_)
+ return;
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ if (getVisibleItemCount() > 0
+ && (selectionModel_.getSelectedObject() == null ||
+ selectionModel_.getSelectedObject().getId().equals(getVisibleItem(0).getId())))
+ {
+ selectionModel_.setSelected(getVisibleItem(0), true);
+ }
+ }
+ });
+ }
+
+ public HandlerRegistration addSelectionChangeHandler(SelectionChangeEvent.Handler handler)
+ {
+ return selectionModel_.addSelectionChangeHandler(handler);
+ }
+
+ @Override
+ public CommitInfo getSelectedCommit()
+ {
+ return selectionModel_.getSelectedObject();
+ }
+
+ @Override
+ public void clearSelection()
+ {
+ if (selectionModel_.getSelectedObject() != null)
+ selectionModel_.setSelected(selectionModel_.getSelectedObject(),
+ false);
+ }
+
+ @Override
+ public void setAutoSelectFirstRow(boolean autoSelect)
+ {
+ autoSelectFirstRow_ = autoSelect;
+ }
+
+ private final SingleSelectionModel<CommitInfo> selectionModel_;
+ private final Styles styles_;
+ private CommitColumn graphCol_;
+ private GraphTheme graphTheme_;
+ private boolean autoSelectFirstRow_ = true;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTableCellTableStyle.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTableCellTableStyle.css
new file mode 100644
index 0000000..b960ce6
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitListTableCellTableStyle.css
@@ -0,0 +1,15 @@
+.cellTableWidget {
+ table-layout: fixed;
+ width: 100%;
+}
+.cellTableWidget td {
+ white-space: pre;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cellTableCell {
+ padding-top: 0;
+ padding-bottom: 0;
+ height: 24px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.java
new file mode 100644
index 0000000..1b037c0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.java
@@ -0,0 +1,60 @@
+/*
+ * CommitTocRow.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import org.rstudio.core.client.HandlerRegistrations;
+import org.rstudio.studio.client.RStudioGinjector;
+
+public class CommitTocRow extends Composite implements HasClickHandlers
+{
+ public interface MyBinder extends UiBinder<FlowPanel, CommitTocRow>
+ {}
+
+ public CommitTocRow(String filename)
+ {
+ icon_ = new Image(
+ RStudioGinjector.INSTANCE.getFileTypeRegistry().getIconForFilename(
+ filename));
+ anchor_ = new Anchor(filename);
+
+ initWidget(binder_.createAndBindUi(this));
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return new HandlerRegistrations(
+ icon_.addClickHandler(handler),
+ anchor_.addClickHandler(handler));
+ }
+
+
+ @UiField(provided = true)
+ Image icon_;
+ @UiField(provided = true)
+ Anchor anchor_;
+
+ private static final MyBinder binder_ = GWT.<MyBinder>create(MyBinder.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.ui.xml
new file mode 100644
index 0000000..ffdb7bc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/CommitTocRow.ui.xml
@@ -0,0 +1,26 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:style>
+ .outer {}
+ .outer img {
+ position: relative;
+ top: 4px;
+ margin-right: 4px;
+ cursor: pointer;
+ }
+ .outer a {
+ text-decoration: none;
+ color: #0000AA;
+ }
+ .outer a:hover {
+ text-decoration: underline;
+ }
+ </ui:style>
+
+ <g:FlowPanel styleName="{style.outer}">
+ <g:Image ui:field="icon_"/>
+ <g:Anchor ui:field="anchor_"/>
+ </g:FlowPanel>
+
+</ui:UiBinder>
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.css
new file mode 100644
index 0000000..76b85b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.css
@@ -0,0 +1,35 @@
+ at sprite .header {
+ gwt-image: 'diffHeaderTile';
+ position: relative;
+}
+
+.header img, .fileIcon {
+ display: block;
+ position: absolute;
+ top: 5px;
+ left: 6px;
+}
+
+.headerLabel {
+ display: inline-block;
+ font-weight: bold;
+ font-size: 12px;
+ position: absolute;
+ top: 6px;
+ left: 28px;
+}
+
+.viewFilePanel {
+ position: absolute;
+ right: 6px;
+}
+
+.viewFileSeparator {
+ margin-top: 3px;
+ margin-right: 5px;
+}
+
+.viewFileHyperlink {
+ font-size: 11px;
+ margin-top: 6px;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.java
new file mode 100644
index 0000000..b9d2e9b
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.java
@@ -0,0 +1,116 @@
+/*
+ * DiffFrame.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.*;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.widget.HyperlinkLabel;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.filetypes.FileType;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView;
+
+public class DiffFrame extends Composite
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("DiffFrame.css")
+ Styles styles();
+
+ @Source("images/diffHeaderTile.png")
+ @ImageOptions(repeatStyle = ImageResource.RepeatStyle.Horizontal)
+ ImageResource diffHeaderTile();
+ }
+
+ interface Styles extends CssResource
+ {
+ String header();
+ String fileIcon();
+ String headerLabel();
+ String viewFilePanel();
+ String viewFileSeparator();
+ String viewFileHyperlink();
+ }
+
+ interface Binder extends UiBinder<Widget, DiffFrame>
+ {}
+
+ public DiffFrame(ImageResource icon,
+ String filename1,
+ String filename2,
+ String commitId,
+ LineTableView diff,
+ ClickHandler viewFileClickHandler,
+ boolean suppressViewLink)
+ {
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ FileTypeRegistry fileTypeRegistry =
+ RStudioGinjector.INSTANCE.getFileTypeRegistry();
+
+ FileSystemItem fsItem = FileSystemItem.createFile(filename2 == null ?
+ filename1 : filename2);
+
+ fileIcon_.setResource(fileTypeRegistry.getIconForFile(fsItem));
+
+ headerLabel_.setText(filename1);
+
+ // if the file is text file then show a view link for it
+ FileType fileType = fileTypeRegistry.getTypeForFile(fsItem);
+ boolean showLink = !suppressViewLink
+ && fileType != null
+ && fileType instanceof TextFileType;
+ if (showLink)
+ {
+ separatorImage_.setResource(ThemeResources.INSTANCE.toolbarSeparator());
+ separatorImage_.addStyleName(RES.styles().viewFileSeparator());
+
+ viewFileHyperlink_.setClickHandler(viewFileClickHandler);
+ viewFileHyperlink_.setAlwaysUnderline(false);
+ viewFileHyperlink_.setText("View file @ " + commitId);
+ viewFileHyperlink_.addStyleName(RES.styles().viewFileHyperlink());
+ }
+
+ container_.add(diff);
+ }
+
+ public static void ensureStylesInjected()
+ {
+ RES.styles().ensureInjected();
+ }
+
+ @UiField
+ FlowPanel container_;
+ @UiField
+ Label headerLabel_;
+ @UiField
+ Image fileIcon_;
+ @UiField
+ Image separatorImage_;
+ @UiField
+ HyperlinkLabel viewFileHyperlink_;
+
+ private static final Resources RES = GWT.<Resources>create(Resources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.ui.xml
new file mode 100644
index 0000000..30fb80a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/DiffFrame.ui.xml
@@ -0,0 +1,34 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:rsw='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field="res"
+ type="org.rstudio.studio.client.workbench.views.vcs.dialog.DiffFrame.Resources"/>
+
+ <ui:style>
+ .container {
+ margin-top: 8px;
+ border: 1px solid #c1c3c5;
+ }
+ </ui:style>
+
+ <g:FlowPanel ui:field="container_" styleName="{style.container}">
+ <g:LayoutPanel width='100%' height='27px'>
+ <g:layer left='0px' right='175px'>
+ <g:FlowPanel styleName="{res.styles.header}">
+ <g:Image styleName="{res.styles.fileIcon}" ui:field="fileIcon_"/>
+ <g:Label styleName="{res.styles.headerLabel}" ui:field="headerLabel_"/>
+ </g:FlowPanel>
+ </g:layer>
+ <g:layer right='0px' width='175px'>
+ <g:SimplePanel>
+ <g:HorizontalPanel styleName="{res.styles.viewFilePanel}">
+ <g:Image ui:field="separatorImage_"/>
+ <rsw:HyperlinkLabel ui:field="viewFileHyperlink_"/>
+ </g:HorizontalPanel>
+ </g:SimplePanel>
+ </g:layer>
+ </g:LayoutPanel>
+ </g:FlowPanel>
+
+</ui:UiBinder>
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryAsyncDataProvider.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryAsyncDataProvider.java
new file mode 100644
index 0000000..ed394b1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryAsyncDataProvider.java
@@ -0,0 +1,148 @@
+/*
+ * HistoryAsyncDataProvider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.user.cellview.client.AbstractHasData;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.view.client.AsyncDataProvider;
+import com.google.gwt.view.client.HasData;
+import com.google.gwt.view.client.Range;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.Value;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+
+public abstract class HistoryAsyncDataProvider extends AsyncDataProvider<CommitInfo>
+{
+ @Inject
+ public HistoryAsyncDataProvider()
+ {
+ rev_ = "";
+ searchText_ = new Value<String>("");
+ fileFilter_ = new Value<FileSystemItem>(null);
+ }
+
+ public void setHistoryStrategy(HistoryStrategy strategy)
+ {
+ strategy_ = strategy;
+ }
+
+ @Override
+ public void addDataDisplay(HasData<CommitInfo> display)
+ {
+ super.addDataDisplay(display);
+ }
+
+ public void setSearchText(HasValue<String> searchText)
+ {
+ searchText_ = searchText;
+ }
+
+ public void setFileFilter(HasValue<FileSystemItem> fileFilter)
+ {
+ fileFilter_ = fileFilter;
+ }
+
+ public void setRev(String rev)
+ {
+ rev_ = rev;
+ }
+
+
+
+ public void refreshCount()
+ {
+ getHistoryCount(
+ rev_,
+ fileFilter_.getValue(),
+ searchText_.getValue(),
+ new ServerRequestCallback<CommitCount>()
+ {
+ @Override
+ public void onResponseReceived(CommitCount response)
+ {
+ updateRowCount(response.getCount(), true);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+
+ @Override
+ public void onRangeChanged(final HasData<CommitInfo> display)
+ {
+ final Range rng = display.getVisibleRange();
+ final int start = rng.getStart();
+ final int length = rng.getLength();
+
+ if (length == 0)
+ return;
+
+ getHistory(
+ rev_, fileFilter_.getValue(),
+ start, length, searchText_.getValue(),
+ new SimpleRequestCallback<RpcObjectList<CommitInfo>>("Error Fetching History")
+ {
+ @Override
+ public void onResponseReceived(RpcObjectList<CommitInfo> response)
+ {
+ super.onResponseReceived(response);
+ if (response.length() < length)
+ updateRowCount(start + response.length(), true);
+ updateRowData(start, response.toArrayList());
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (display instanceof AbstractHasData)
+ {
+ display.setVisibleRangeAndClearData(new Range(start, 0), true);
+ }
+ if (strategy_.getShowHistoryErrors())
+ super.onError(error);
+ else
+ Debug.logError(error);
+ }
+ });
+ }
+
+ protected abstract void getHistoryCount(
+ String revision,
+ FileSystemItem fileFilter,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback);
+
+ protected abstract void getHistory(
+ String revision,
+ FileSystemItem fileFilter,
+ int skip,
+ int maxEntries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback);
+
+ private String rev_;
+ private HasValue<String> searchText_;
+ private HasValue<FileSystemItem> fileFilter_;
+ private HistoryStrategy strategy_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.css
new file mode 100644
index 0000000..1f14adb
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.css
@@ -0,0 +1,40 @@
+.splitPanel {}
+.toolbarWrapper {}
+.whitebg {}
+.toolbar {}
+
+.commitDetail {
+ border: 12px solid transparent;
+}
+
+.commitTableScrollPanel {
+ background-color: white;
+}
+
+.ref, .tag {
+ font-size: 80%;
+ border-radius: 8px;
+ background-color: green;
+ padding: 0 6px;
+ margin-right: 4px;
+}
+
+.head {
+ background-color: rgb(182, 231, 231);
+}
+
+.branch {
+ background-color: rgb(206, 182, 251);
+}
+
+.remote {
+ background-color: rgb(213, 218, 255);
+}
+
+.tag {
+ background-color: rgb(255, 229, 170);
+}
+
+.graphLineImg {
+ display: block;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.java
new file mode 100644
index 0000000..a88131f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.java
@@ -0,0 +1,261 @@
+/*
+ * HistoryPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.cellview.client.AbstractPager;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.view.client.HasData;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.HistoryBranchToolbarButton;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.CommitDetailDisplay;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.CommitListDisplay;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.Display;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter.DisplayBuilder;
+
+public class HistoryPanel extends Composite implements Display
+{
+ public interface Resources extends ClientBundle
+ {
+ @Source("HistoryPanel.css")
+ Styles styles();
+ }
+
+ public interface Styles extends SharedStyles
+ {
+ String commitDetail();
+ String commitTableScrollPanel();
+
+ String ref();
+ String head();
+ String branch();
+ String remote();
+ String tag();
+
+ String graphLineImg();
+ }
+
+ interface Binder extends UiBinder<Widget, HistoryPanel>
+ {}
+
+ public static class Builder implements DisplayBuilder
+ {
+ @Inject
+ public Builder(HistoryBranchToolbarButton branchToolbarButton,
+ CommitFilterToolbarButton commitFilterToolbarButton,
+ Commands commands)
+ {
+ branchToolbarButton_ = branchToolbarButton;
+ commitFilterToolbarButton_ = commitFilterToolbarButton;
+ commands_ = commands;
+ }
+
+ public HistoryPanel build(HistoryStrategy strategy)
+ {
+ return new HistoryPanel(branchToolbarButton_,
+ commitFilterToolbarButton_,
+ commands_,
+ strategy);
+ }
+
+ private final HistoryBranchToolbarButton branchToolbarButton_;
+ private final CommitFilterToolbarButton commitFilterToolbarButton_;
+ private final Commands commands_;
+ }
+
+ protected HistoryPanel(HistoryBranchToolbarButton branchToolbarButton,
+ CommitFilterToolbarButton commitFilterToolbarButton,
+ Commands commands,
+ HistoryStrategy strategy)
+ {
+ Styles styles = GWT.<Resources>create(Resources.class).styles();
+ commitTable_ = new CommitListTable(styles, strategy.idColumnName());
+ splitPanel_ = new SplitLayoutPanel(4);
+ pager_ = strategy.getPager();
+ branchToolbarButton_ = branchToolbarButton;
+ commitFilterToolbarButton_ = commitFilterToolbarButton;
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ commitDetail_.setIdDesc(strategy.idColumnName());
+ commitDetail_.setScrollPanel(detailScrollPanel_);
+
+ topToolbar_.addStyleName(styles.toolbar());
+
+ switchViewButton_ = new LeftRightToggleButton("Changes", "History", false);
+ topToolbar_.addLeftWidget(switchViewButton_);
+ topToolbar_.addLeftWidget(branchToolbarButton_);
+
+ topToolbar_.addLeftWidget(commitFilterToolbarButton_);
+
+ searchText_ = new SearchWidget(new MultiWordSuggestOracle(),
+ new TextBoxWithCue("Search"),
+ null);
+ topToolbar_.addRightWidget(searchText_);
+ topToolbar_.addRightSeparator();
+
+ refreshButton_ = new ToolbarButton(
+ "Refresh",
+ commands.vcsRefresh().getImageResource(),
+ (ClickHandler) null);
+ topToolbar_.addRightWidget(refreshButton_);
+
+ topToolbar_.addRightSeparator();
+
+ topToolbar_.addRightWidget(commands.vcsPull().createToolbarButton());
+
+ pager_.setDisplay(commitTable_);
+ }
+
+ @Override
+ public HasClickHandlers getSwitchViewButton()
+ {
+ return switchViewButton_;
+ }
+
+ @Override
+ public CommitListDisplay getCommitList()
+ {
+ return commitTable_;
+ }
+
+ @Override
+ public CommitDetailDisplay getCommitDetail()
+ {
+ return commitDetail_;
+ }
+
+ @Override
+ public HasClickHandlers getOverrideSizeWarningButton()
+ {
+ return commitDetail_.getOverrideSizeWarningButton();
+ }
+
+ @Override
+ public HasClickHandlers getRefreshButton()
+ {
+ return refreshButton_;
+ }
+
+ @Override
+ public HasData<CommitInfo> getDataDisplay()
+ {
+ return commitTable_;
+ }
+
+ @Override
+ public HasValue<FileSystemItem> getFileFilter()
+ {
+ return commitFilterToolbarButton_;
+ }
+
+ @Override
+ public void removeBranchToolbarButton()
+ {
+ topToolbar_.removeLeftWidget(branchToolbarButton_);
+ commitFilterToolbarButton_.getElement().getStyle().setMarginLeft(10,
+ Unit.PX);
+ }
+
+ @Override
+ public void removeSearchTextBox()
+ {
+ searchText_.removeFromParent();
+ }
+
+ @Override
+ public HasValue<String> getSearchTextBox()
+ {
+ return searchText_;
+ }
+
+ @Override
+ public void setPageStart(int pageStart)
+ {
+ commitTable_.setPageStart(pageStart);
+ }
+
+ @Override
+ public HandlerRegistration addBranchChangedHandler(
+ ValueChangeHandler<String> handler)
+ {
+ return branchToolbarButton_.addValueChangeHandler(handler);
+ }
+
+ @Override
+ public void showSizeWarning(long sizeInBytes)
+ {
+ commitDetail_.showSizeWarning(sizeInBytes);
+ }
+
+ @Override
+ public void hideSizeWarning()
+ {
+ commitDetail_.hideSizeWarning();
+ }
+
+ @Override
+ public void onShow()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ commitTable_.setFocus(true);
+ }
+ });
+ }
+
+ @UiField(provided = true)
+ SplitLayoutPanel splitPanel_;
+ @UiField
+ Toolbar topToolbar_;
+ @UiField(provided = true)
+ CommitListTable commitTable_;
+ @UiField
+ CommitDetail commitDetail_;
+ @UiField
+ ScrollPanel detailScrollPanel_;
+ @UiField(provided = true)
+ AbstractPager pager_;
+
+ SearchWidget searchText_;
+
+ private LeftRightToggleButton switchViewButton_;
+
+ static
+ {
+ GWT.<Resources>create(Resources.class).styles().ensureInjected();
+ }
+
+ private ToolbarButton refreshButton_;
+ private final HistoryBranchToolbarButton branchToolbarButton_;
+ private final CommitFilterToolbarButton commitFilterToolbarButton_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.ui.xml
new file mode 100644
index 0000000..e142eaa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPanel.ui.xml
@@ -0,0 +1,37 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:v='urn:import:com.google.gwt.user.cellview.client'
+ xmlns:vcs_dialog='urn:import:org.rstudio.studio.client.workbench.views.vcs.dialog'
+ xmlns:rs_widget='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPanel.Resources"/>
+
+ <g:SplitLayoutPanel ui:field="splitPanel_" styleName="{res.styles.splitPanel}">
+ <g:north size="230">
+ <g:DockLayoutPanel>
+ <g:north size="28">
+ <g:SimplePanel styleName="{res.styles.toolbarWrapper}">
+ <rs_widget:Toolbar ui:field="topToolbar_"/>
+ </g:SimplePanel>
+ </g:north>
+ <g:center>
+ <g:ScrollPanel styleName="{res.styles.commitTableScrollPanel}">
+ <vcs_dialog:CommitListTable ui:field="commitTable_" width="100%" />
+ </g:ScrollPanel>
+ </g:center>
+ <g:south size="28">
+ <g:FlowPanel>
+ <v:AbstractPager ui:field="pager_"/>
+ </g:FlowPanel>
+ </g:south>
+ </g:DockLayoutPanel>
+ </g:north>
+ <g:center>
+ <g:ScrollPanel ui:field="detailScrollPanel_">
+ <vcs_dialog:CommitDetail styleName="{res.styles.commitDetail}"
+ ui:field="commitDetail_" />
+ </g:ScrollPanel>
+ </g:center>
+ </g:SplitLayoutPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPresenter.java
new file mode 100644
index 0000000..dc59354
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryPresenter.java
@@ -0,0 +1,412 @@
+/*
+ * HistoryPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.user.cellview.client.LoadingStateChangeEvent;
+import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.HasData;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.RangeChangeEvent;
+import com.google.gwt.view.client.RangeChangeEvent.Handler;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.TimeBufferedCommand;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.GlobalProgressDelayer;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.DiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsHistoryEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.SwitchViewEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ViewFileRevisionEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ViewFileRevisionHandler;
+import org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitHistoryStrategy;
+import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNHistoryStrategy;
+
+public class HistoryPresenter
+{
+ public interface DisplayBuilder
+ {
+ Display build(HistoryStrategy strategy);
+ }
+
+ public interface Display extends IsWidget
+ {
+ HasClickHandlers getSwitchViewButton();
+ CommitListDisplay getCommitList();
+ CommitDetailDisplay getCommitDetail();
+
+ HasClickHandlers getOverrideSizeWarningButton();
+
+ HasClickHandlers getRefreshButton();
+
+ HasData<CommitInfo> getDataDisplay();
+
+ HasValue<String> getSearchTextBox();
+
+ void setPageStart(int pageStart);
+
+ HandlerRegistration addBranchChangedHandler(
+ ValueChangeHandler<String> handler);
+
+ HasValue<FileSystemItem> getFileFilter();
+
+ void removeBranchToolbarButton();
+ void removeSearchTextBox();
+
+ void showSizeWarning(long sizeInBytes);
+ void hideSizeWarning();
+
+ void onShow();
+ }
+
+ public interface CommitListDisplay
+ {
+ HandlerRegistration addSelectionChangeHandler(
+ SelectionChangeEvent.Handler handler);
+
+ HandlerRegistration addRangeChangeHandler(
+ RangeChangeEvent.Handler handler);
+
+ HandlerRegistration addLoadingStateChangeHandler(
+ LoadingStateChangeEvent.Handler handler);
+
+ CommitInfo getSelectedCommit();
+
+ void clearSelection();
+
+ void setAutoSelectFirstRow(boolean autoSelect);
+ }
+
+ public interface CommitDetailDisplay extends HasHandlers
+ {
+ void setSelectedCommit(CommitInfo commit);
+ void clearDetails();
+ void showDetailProgress();
+ void setDetails(DiffParser unifiedParser, boolean suppressViewLink);
+ void setCommitListIsLoading(boolean isLoading);
+
+ HandlerRegistration addViewFileRevisionHandler(
+ ViewFileRevisionHandler handler);
+ }
+
+ @Inject
+ public HistoryPresenter(final GlobalDisplay globalDisplay,
+ final Provider<ViewFilePanel> pViewFilePanel,
+ final DisplayBuilder viewBuilder,
+ final Session session,
+ final Provider<GitHistoryStrategy> pGitStrategy,
+ final Provider<SVNHistoryStrategy> pSvnStrategy)
+ {
+ String vcsName = session.getSessionInfo().getVcsName();
+ if (vcsName.equalsIgnoreCase("git"))
+ strategy_ = pGitStrategy.get();
+ else if (vcsName.equalsIgnoreCase("svn"))
+ strategy_ = pSvnStrategy.get();
+ else
+ throw new IllegalStateException("Unknown vcs name: " + vcsName);
+
+ view_ = viewBuilder.build(strategy_);
+
+ view_.getCommitList().setAutoSelectFirstRow(
+ strategy_.getAutoSelectFirstRow());
+
+ if (strategy_.isBranchingSupported())
+ {
+ view_.addBranchChangedHandler(new ValueChangeHandler<String>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event)
+ {
+ strategy_.setRev(event.getValue());
+ refreshHistory();
+ view_.setPageStart(0);
+ }
+ });
+ }
+ else
+ {
+ view_.removeBranchToolbarButton();
+ }
+
+ view_.getCommitList().addSelectionChangeHandler(new SelectionChangeEvent.Handler()
+ {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent event)
+ {
+ showCommitDetail(false);
+ }
+ });
+ view_.getCommitList().addRangeChangeHandler(new Handler()
+ {
+ @Override
+ public void onRangeChange(RangeChangeEvent event)
+ {
+ view_.getCommitList().clearSelection();
+ }
+ });
+ view_.getCommitList().addLoadingStateChangeHandler(new LoadingStateChangeEvent.Handler()
+ {
+ @Override
+ public void onLoadingStateChanged(LoadingStateChangeEvent event)
+ {
+ view_.getCommitDetail().setCommitListIsLoading(
+ event.getLoadingState() == LoadingState.LOADING);
+ }
+ });
+
+ view_.getRefreshButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ refreshHistory();
+ }
+ });
+
+ view_.getOverrideSizeWarningButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ showCommitDetail(true);
+ }
+ });
+
+ if (strategy_.isSearchSupported())
+ {
+ strategy_.setSearchText(view_.getSearchTextBox());
+ view_.getSearchTextBox().addValueChangeHandler(new ValueChangeHandler<String>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<String> stringValueChangeEvent)
+ {
+ refreshHistoryCommand_.nudge();
+ }
+ });
+ }
+ else
+ {
+ view_.removeSearchTextBox();
+ }
+
+ strategy_.setFileFilter(view_.getFileFilter());
+ view_.getFileFilter().addValueChangeHandler(new ValueChangeHandler<FileSystemItem>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<FileSystemItem> event)
+ {
+ view_.getCommitDetail().clearDetails();
+ view_.getCommitDetail().setSelectedCommit(null);
+ refreshHistory();
+ view_.setPageStart(0);
+ }
+ });
+
+ view_.getCommitDetail().addViewFileRevisionHandler(
+ new ViewFileRevisionHandler() {
+ @Override
+ public void onViewFileRevision(final ViewFileRevisionEvent event)
+ {
+ final ProgressIndicator indicator =
+ new GlobalProgressDelayer(globalDisplay,
+ 500,
+ "Reading file...").getIndicator();
+
+ strategy_.showFile(
+ event.getRevision(),
+ event.getFilename(),
+ new ServerRequestCallback<String>()
+ {
+
+ @Override
+ public void onResponseReceived(String contents)
+ {
+ indicator.onCompleted();
+
+ ViewFilePanel viewFilePanel = pViewFilePanel.get();
+ viewFilePanel.addShowVcsHistoryHandler(
+ new ShowVcsHistoryEvent.Handler()
+ {
+ @Override
+ public void onShowVcsHistory(
+ ShowVcsHistoryEvent event)
+ {
+ view_.getFileFilter().setValue(
+ event.getFileFilter());
+
+ }
+
+ });
+
+ viewFilePanel.showFile(
+ FileSystemItem.createFile(event.getFilename()),
+ event.getRevision(),
+ contents);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (strategy_.getShowHistoryErrors())
+ {
+ indicator.onError(error.getUserMessage());
+ }
+ else
+ {
+ indicator.onCompleted();
+ Debug.logError(error);
+ }
+ }
+
+ });
+ }
+
+ });
+ }
+
+ private void showCommitDetail(boolean noSizeWarning)
+ {
+ final CommitInfo commitInfo = view_.getCommitList().getSelectedCommit();
+
+ if (!noSizeWarning
+ && commitInfo != null
+ && commitInfo.getId().equals(commitShowing_))
+ {
+ return;
+ }
+
+ commitShowing_ = null;
+
+ view_.hideSizeWarning();
+
+ view_.getCommitDetail().setSelectedCommit(commitInfo);
+ view_.getCommitDetail().showDetailProgress();
+ invalidation_.invalidate();
+
+ if (commitInfo == null)
+ return;
+
+ final Token token = invalidation_.getInvalidationToken();
+
+ strategy_.showCommit(
+ commitInfo.getId(),
+ noSizeWarning,
+ new SimpleRequestCallback<String>()
+ {
+ @Override
+ public void onResponseReceived(String response)
+ {
+ super.onResponseReceived(response);
+ if (token.isInvalid())
+ return;
+
+ DiffParser parser = strategy_.createParserForCommit(response);
+ view_.getCommitDetail().setDetails(
+ parser, !strategy_.isShowFileSupported());
+ commitShowing_ = commitInfo.getId();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ commitShowing_ = null;
+
+ JSONNumber size = error.getClientInfo().isNumber();
+ if (size != null)
+ view_.showSizeWarning((long) size.doubleValue());
+ else if (strategy_.getShowHistoryErrors())
+ super.onError(error);
+ else
+ Debug.logError(error);
+ }
+ });
+ }
+
+ private void refreshHistory()
+ {
+ strategy_.refreshCount();
+ view_.getDataDisplay().setVisibleRangeAndClearData(new Range(0, 100), true);
+ }
+
+ public HandlerRegistration addSwitchViewHandler(
+ final SwitchViewEvent.Handler h)
+ {
+ return view_.getSwitchViewButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ h.onSwitchView(new SwitchViewEvent());
+ }
+ });
+ }
+
+
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ public void setFileFilter(FileSystemItem fileFilter)
+ {
+ if (fileFilter != null)
+ view_.getFileFilter().setValue(fileFilter);
+ }
+
+ public void onShow()
+ {
+ if (!initialized_)
+ {
+ initialized_ = true;
+ strategy_.initializeHistory(view_.getDataDisplay());
+ }
+ view_.onShow();
+ }
+
+ private final TimeBufferedCommand refreshHistoryCommand_ = new TimeBufferedCommand(1000)
+ {
+ @Override
+ protected void performAction(boolean shouldSchedulePassive)
+ {
+ refreshHistory();
+ }
+ };
+
+ private final Display view_;
+ private final HistoryStrategy strategy_;
+ private final Invalidation invalidation_ = new Invalidation();
+ private boolean initialized_;
+ private String commitShowing_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryStrategy.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryStrategy.java
new file mode 100644
index 0000000..1ccbc83
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/HistoryStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * HistoryStrategy.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.cellview.client.AbstractPager;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.view.client.HasData;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.DiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+
+public interface HistoryStrategy
+{
+ void setRev(String rev);
+
+ String idColumnName();
+ boolean isBranchingSupported();
+ boolean isShowFileSupported();
+ boolean isSearchSupported();
+
+ void setSearchText(HasValue<String> searchText);
+
+ void setFileFilter(HasValue<FileSystemItem> fileFilter);
+
+ void showFile(String revision,
+ String filename,
+ ServerRequestCallback<String> requestCallback);
+
+ HandlerRegistration addVcsRefreshHandler(VcsRefreshHandler refreshHandler);
+
+ void showCommit(String commitId,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback);
+
+ void addDataDisplay(HasData<CommitInfo> display);
+ void onRangeChanged(HasData<CommitInfo> display);
+
+ void refreshCount();
+
+ void initializeHistory(HasData<CommitInfo> dataDisplay);
+
+ AbstractPager getPager();
+
+ boolean getAutoSelectFirstRow();
+
+ DiffParser createParserForCommit(String commitDiff);
+
+ boolean getShowHistoryErrors();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenter.java
new file mode 100644
index 0000000..b52a50e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenter.java
@@ -0,0 +1,31 @@
+/*
+ * ReviewPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.IsWidget;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.SwitchViewEvent.Handler;
+
+import java.util.ArrayList;
+
+public interface ReviewPresenter extends IsWidget
+{
+ void setSelectedPaths(ArrayList<StatusAndPath> selectedPaths);
+
+ void onShow();
+
+ HandlerRegistration addSwitchViewHandler(Handler handler);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenterImpl.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenterImpl.java
new file mode 100644
index 0000000..e8feb26
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ReviewPresenterImpl.java
@@ -0,0 +1,72 @@
+/*
+ * ReviewPresenterImpl.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.VCSConstants;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.SwitchViewEvent.Handler;
+import org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPresenter;
+
+import java.util.ArrayList;
+
+public class ReviewPresenterImpl implements ReviewPresenter
+{
+ @Inject
+ public ReviewPresenterImpl(Provider<GitReviewPresenter> pGitReviewPresenter,
+ Provider<SVNReviewPresenter> pSvnReviewPresenter,
+ Session session)
+ {
+ String vcsName = session.getSessionInfo().getVcsName();
+
+ if (vcsName.equalsIgnoreCase(VCSConstants.GIT_ID))
+ pres_ = pGitReviewPresenter.get();
+ else if (vcsName.equalsIgnoreCase(VCSConstants.SVN_ID))
+ pres_ = pSvnReviewPresenter.get();
+ else
+ throw new IllegalStateException("Unknown vcs name: " + vcsName);
+ }
+
+ @Override
+ public void setSelectedPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ pres_.setSelectedPaths(selectedPaths);
+ }
+
+ @Override
+ public void onShow()
+ {
+ pres_.onShow();
+ }
+
+ @Override
+ public HandlerRegistration addSwitchViewHandler(Handler handler)
+ {
+ return pres_.addSwitchViewHandler(handler);
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return pres_.asWidget();
+ }
+
+ private ReviewPresenter pres_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SharedStyles.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SharedStyles.java
new file mode 100644
index 0000000..aeb1dae
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SharedStyles.java
@@ -0,0 +1,28 @@
+/*
+ * SharedStyles.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Shared;
+
+ at Shared
+public interface SharedStyles extends CssResource
+{
+ String splitPanel();
+ String whitebg();
+
+ String toolbar();
+ String toolbarWrapper();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.java
new file mode 100644
index 0000000..5e229d3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.java
@@ -0,0 +1,60 @@
+/*
+ * SizeWarningWidget.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.ThemedButton;
+
+public class SizeWarningWidget extends Composite implements HasClickHandlers
+{
+ interface Binder extends UiBinder<Widget, SizeWarningWidget>
+ {}
+
+ public SizeWarningWidget(String subject)
+ {
+ showDiffButton_ = new ThemedButton("Show Diff");
+
+ initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this));
+
+ subject_.setInnerText(subject);
+ }
+
+ public void setSize(long size)
+ {
+ size_.setInnerText(StringUtil.formatFileSize(size));
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return showDiffButton_.addClickHandler(handler);
+ }
+
+ @UiField(provided = true)
+ ThemedButton showDiffButton_;
+ @UiField
+ SpanElement subject_;
+ @UiField
+ SpanElement size_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.ui.xml
new file mode 100644
index 0000000..c91b7c1
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/SizeWarningWidget.ui.xml
@@ -0,0 +1,44 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:w='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:style>
+ .warning {
+ margin-top: 50px;
+ }
+ .label {
+ margin: 0 auto 12px auto;
+ }
+ .buttonCell {
+ padding-top: 12px;
+ }
+ .labelCell {
+ padding-top: 4px;
+ }
+ </ui:style>
+ <ui:with field="messageDialogImages"
+ type="org.rstudio.core.client.widget.images.MessageDialogImages"/>
+
+ <g:HTMLPanel styleName="{style.warning}">
+ <table class="{style.label}" cellspacing="8" cellpadding="0" align="center">
+ <tr>
+ <td valign="top">
+ <g:Image resource='{messageDialogImages.dialog_warning}'/>
+ </td>
+ <td valign="top" class="{style.labelCell}">
+ <strong>This <span ui:field="subject_"/> is extremely large
+ (<span ui:field="size_"/>) and may<br/>
+ cause RStudio to slow down or even hang.</strong>
+ <br/><br/>
+ Are you sure you want to continue?
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" align="center" class="{style.buttonCell}">
+ <w:ThemedButton ui:field="showDiffButton_"/>
+ </td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ViewFilePanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ViewFilePanel.java
new file mode 100644
index 0000000..3907aec
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/ViewFilePanel.java
@@ -0,0 +1,359 @@
+/*
+ * ViewFilePanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog;
+
+import java.util.ArrayList;
+
+import org.rstudio.core.client.BrowseCap;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.theme.res.ThemeResources;
+import org.rstudio.core.client.theme.res.ThemeStyles;
+import org.rstudio.core.client.widget.FullscreenPopupPanel;
+import org.rstudio.core.client.widget.ProgressIndicator;
+import org.rstudio.core.client.widget.ProgressOperationWithInput;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.FileDialogs;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.filetypes.TextFileType;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
+import org.rstudio.studio.client.workbench.ui.FontSizeManager;
+import org.rstudio.studio.client.workbench.views.source.PanelWithToolbars;
+import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
+import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextDisplay;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget;
+import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTargetFindReplace;
+import org.rstudio.studio.client.workbench.views.source.editors.text.findreplace.FindReplaceBar;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.ShowVcsHistoryEvent;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+public class ViewFilePanel extends Composite implements TextDisplay
+{
+ @Inject
+ public ViewFilePanel(DocDisplay docDisplay,
+ FileTypeRegistry fileTypeRegistry,
+ UIPrefs uiPrefs,
+ EventBus events,
+ Commands commands,
+ FontSizeManager fontSizeManager,
+ FileDialogs fileDialogs,
+ RemoteFileSystemContext fileContext,
+ Session session,
+ GitServerOperations server)
+ {
+ fileTypeRegistry_ = fileTypeRegistry;
+ commands_ = commands;
+ fileDialogs_ = fileDialogs;
+ fileContext_ = fileContext;
+ session_ = session;
+ server_ = server;
+ docDisplay_ = docDisplay;
+ docDisplay_.setReadOnly(true);
+
+ TextEditingTarget.registerPrefs(releaseOnDismiss_,
+ uiPrefs,
+ docDisplay_,
+ new TextEditingTarget.PrefsContext()
+ {
+ @Override
+ public FileSystemItem getActiveFile()
+ {
+ return targetFile_;
+ }
+ });
+
+ TextEditingTarget.syncFontSize(releaseOnDismiss_,
+ events,
+ this,
+ fontSizeManager);
+
+ findReplace_ = new TextEditingTargetFindReplace(
+ new TextEditingTargetFindReplace.Container() {
+
+ @Override
+ public AceEditor getEditor()
+ {
+ return (AceEditor)docDisplay_;
+ }
+
+ @Override
+ public void insertFindReplace(FindReplaceBar findReplaceBar)
+ {
+ panel_.insertNorth(findReplaceBar,
+ findReplaceBar.getHeight(),
+ null);
+ }
+
+ @Override
+ public void removeFindReplace(FindReplaceBar findReplaceBar)
+ {
+ panel_.remove(findReplaceBar);
+ }
+
+ },
+ false); // don't show replace UI
+
+ panel_ = new PanelWithToolbars(createToolbar(),
+ null,
+ docDisplay_.asWidget(),
+ null);
+ panel_.setSize("100%", "100%");
+
+ releaseOnDismiss_.add(docDisplay_.addKeyDownHandler(new KeyDownHandler()
+ {
+ public void onKeyDown(KeyDownEvent event)
+ {
+ NativeEvent ne = event.getNativeEvent();
+ int mod = KeyboardShortcut.getModifierValue(ne);
+ if ((mod == KeyboardShortcut.META ||
+ (mod == KeyboardShortcut.CTRL && !BrowseCap.hasMetaKey())))
+ {
+ if (ne.getKeyCode() == 'F')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ findReplace_.showFindReplace(true);
+ }
+ else if (ne.getKeyCode() == 'S')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ saveFileAs();
+ }
+ }
+ else if (mod == KeyboardShortcut.NONE &&
+ ne.getKeyCode() == KeyCodes.KEY_ESCAPE)
+ {
+ if (findReplace_.isShowing())
+ findReplace_.hideFindReplace();
+ else
+ popupPanel_.close();
+ }
+
+ }
+ }));
+
+
+ initWidget(panel_);
+ }
+
+ HandlerRegistration addShowVcsHistoryHandler(
+ ShowVcsHistoryEvent.Handler handler)
+ {
+ return addHandler(handler, ShowVcsHistoryEvent.TYPE);
+ }
+
+ public void showFile(FileSystemItem file, String commitId, String contents)
+ {
+ commitId_ = commitId;
+ targetFile_ = file;
+
+ docDisplay_.setCode(contents, false);
+
+ adaptToFileType(fileTypeRegistry_.getTextTypeForFile(file));
+
+ ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
+
+ // header widget has icon + label
+ HorizontalPanel panel = new HorizontalPanel();
+
+ Image imgFile = new Image(fileTypeRegistry_.getIconForFile(file));
+ imgFile.addStyleName(styles.fullscreenCaptionIcon());
+ panel.add(imgFile);
+
+ Label lblCaption = new Label(file.getPath() + " @ " + commitId);
+ lblCaption.addStyleName(styles.fullscreenCaptionLabel());
+ panel.add(lblCaption);
+
+ popupPanel_ = new FullscreenPopupPanel(panel,asWidget(), false);
+ popupPanel_.center();
+
+ // set focus to the doc display after 100ms
+ Timer timer = new Timer() {
+ public void run() {
+ docDisplay_.focus();
+ }
+ };
+ timer.schedule(100);
+ }
+
+ private Toolbar createToolbar()
+ {
+ Toolbar toolbar = new ViewFileToolbar();
+
+ toolbar.addLeftWidget(new ToolbarButton(
+ "Save As",
+ commands_.saveSourceDoc().getImageResource(),
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ saveFileAs();
+ }
+
+ }));
+ toolbar.addLeftSeparator();
+
+ toolbar.addLeftWidget(new ToolbarButton(
+ null,
+ commands_.printSourceDoc().getImageResource(),
+ new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ docDisplay_.print();
+ }
+
+ }));
+ toolbar.addLeftSeparator();
+
+ toolbar.addLeftWidget(findReplace_.createFindReplaceButton());
+
+
+ toolbar.addRightWidget(new ToolbarButton(
+ "Show History",
+ commands_.goToWorkingDir().getImageResource(),
+ new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ fireEvent(new ShowVcsHistoryEvent(targetFile_));
+ popupPanel_.close();
+ }
+
+ }));
+
+ return toolbar;
+ }
+
+ @Override
+ public void onActivate()
+ {
+ docDisplay_.onActivate();
+ }
+
+ @Override
+ public void adaptToFileType(TextFileType fileType)
+ {
+ docDisplay_.setFileType(fileType, true);
+ }
+
+ @Override
+ public void setFontSize(double size)
+ {
+ docDisplay_.setFontSize(size);
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return this;
+ }
+
+ @Override
+ public void onUnload()
+ {
+ super.onUnload();
+
+ while (releaseOnDismiss_.size() > 0)
+ releaseOnDismiss_.remove(0).removeHandler();
+ }
+
+ private void saveFileAs()
+ {
+ fileDialogs_.saveFile(
+ "Save File - " + targetFile_.getName(),
+ fileContext_,
+ FileSystemItem.createFile(
+ session_.getSessionInfo().getActiveProjectDir()
+ .completePath(targetFile_.getName())),
+ targetFile_.getExtension(),
+ false,
+ new ProgressOperationWithInput<FileSystemItem> () {
+
+ @Override
+ public void execute(FileSystemItem input,
+ ProgressIndicator indicator)
+ {
+ if (input == null)
+ {
+ indicator.onCompleted();
+ return;
+ }
+
+ indicator.onProgress("Saving file...");
+
+ server_.gitExportFile(
+ commitId_,
+ targetFile_.getPath(),
+ input.getPath(),
+ new VoidServerRequestCallback(indicator));
+
+ }
+
+ });
+ }
+
+ private class ViewFileToolbar extends Toolbar
+ {
+ @Override
+ public int getHeight()
+ {
+ return 23;
+ }
+ }
+
+ private final FileTypeRegistry fileTypeRegistry_;
+ private final RemoteFileSystemContext fileContext_;
+ private final FileDialogs fileDialogs_;
+ private final Commands commands_;
+ private final Session session_;
+ private final GitServerOperations server_;
+ private final DocDisplay docDisplay_;
+
+ private final PanelWithToolbars panel_;
+ private FullscreenPopupPanel popupPanel_;
+ private final TextEditingTargetFindReplace findReplace_;
+
+ private String commitId_ = null;
+ private FileSystemItem targetFile_ = null;
+
+ private final ArrayList<HandlerRegistration> releaseOnDismiss_ =
+ new ArrayList<HandlerRegistration>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphColumn.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphColumn.java
new file mode 100644
index 0000000..8e8bbfa
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphColumn.java
@@ -0,0 +1,47 @@
+/*
+ * GraphColumn.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog.graph;
+
+public class GraphColumn
+{
+ GraphColumn(String line)
+ {
+ start = line.indexOf('+') >= 0;
+ end = line.indexOf('-') >= 0;
+ nexus = line.indexOf('*') >= 0;
+ id = Integer.parseInt(
+ line.replace('*', ' ').replace('+', ' ').replace('-', ' ').trim());
+ }
+
+ /**
+ * The id of this column--will be used for coloring
+ */
+ public int id;
+
+ /**
+ * If true, this column starts on this row
+ */
+ public boolean start;
+
+ /**
+ * If true, this column ends on this row
+ */
+ public boolean end;
+
+ /**
+ * If true, this column is the nexus column
+ */
+ public boolean nexus;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphLine.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphLine.java
new file mode 100644
index 0000000..96fb53f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphLine.java
@@ -0,0 +1,158 @@
+/*
+ * GraphLine.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog.graph;
+
+import com.google.gwt.canvas.client.Canvas;
+import com.google.gwt.canvas.dom.client.Context2d;
+import com.google.gwt.canvas.dom.client.Context2d.LineJoin;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import org.rstudio.core.client.SafeHtmlUtil;
+
+public class GraphLine
+{
+ public GraphLine(String value)
+ {
+ String[] vals = value.length() == 0 ? new String[] {} : value.split(" ");
+ columns_ = new GraphColumn[vals.length];
+ for (int i = 0; i < columns_.length; i++)
+ columns_[i] = new GraphColumn(vals[i]);
+ }
+
+ public GraphColumn[] getColumns()
+ {
+ return columns_;
+ }
+
+ public int getTotalWidth(GraphTheme theme)
+ {
+ int startColumns = 0;
+ int endColumns = 0;
+ for (GraphColumn c : columns_)
+ {
+ if (!c.start)
+ startColumns++;
+ if (!c.end)
+ endColumns++;
+ }
+ return Math.max(startColumns, endColumns) * theme.getColumnWidth();
+ }
+
+ public SafeHtml render(GraphTheme theme)
+ {
+ draw(s_canvas, theme);
+ return SafeHtmlUtil.createOpenTag("img",
+ "class", theme.getImgClassName(),
+ "src", s_canvas.toDataUrl());
+ }
+
+ private void draw(Canvas canvas, GraphTheme theme)
+ {
+ int height = theme.getRowHeight();
+ int colWidth = theme.getColumnWidth();
+ double pad = theme.getVerticalLinePadding();
+
+ canvas.setCoordinateSpaceHeight(height);
+ canvas.setCoordinateSpaceWidth(colWidth * getTotalWidth(theme));
+ Context2d ctx = canvas.getContext2d();
+
+ //ctx.clearRect(0, 0, colWidth * columns_.length, height);
+
+ ctx.translate(colWidth / 2.0, 0);
+
+ int startPos = -1;
+ int endPos = -1;
+ int nexusColumn = -1;
+ for (int i = 0; i < columns_.length; i++)
+ {
+ GraphColumn c = columns_[i];
+
+ if (!c.start)
+ startPos++;
+ if (!c.end)
+ endPos++;
+
+ ctx.setStrokeStyle(theme.getColorForId(c.id));
+ ctx.setLineWidth(theme.getStrokeWidth());
+ ctx.setLineJoin(LineJoin.ROUND);
+
+ if (!c.nexus && !c.start && !c.end)
+ {
+ // Just draw a line from start to end position
+
+ ctx.beginPath();
+ ctx.moveTo(startPos * colWidth, 0);
+ ctx.lineTo(startPos * colWidth, pad);
+ // This next lineTo helps ensure that the shape of the line looks
+ // congruous to any specials on the same line
+ ctx.lineTo(Math.min(startPos, endPos) * colWidth, height / 2.0);
+ ctx.lineTo(endPos * colWidth, height - pad);
+ ctx.lineTo(endPos * colWidth, height);
+ ctx.stroke();
+ }
+ else
+ {
+ // something special
+
+ if (c.nexus)
+ {
+ nexusColumn = i;
+ ctx.setFillStyle(theme.getColorForId(c.id));
+ }
+
+ if (!c.start)
+ {
+ // draw from i to nexusColumn;
+ ctx.beginPath();
+ ctx.moveTo(startPos * colWidth, 0);
+ ctx.lineTo(startPos * colWidth, pad);
+ ctx.lineTo(nexusColumn * colWidth, height / 2.0);
+ ctx.stroke();
+ }
+
+ if (!c.end)
+ {
+ // draw from nexusColumn to endPosition
+ ctx.beginPath();
+ ctx.moveTo(nexusColumn * colWidth, height / 2.0);
+ ctx.lineTo(endPos * colWidth, height - pad);
+ ctx.lineTo(endPos * colWidth, height);
+ ctx.stroke();
+ }
+
+ }
+ }
+
+ // draw a circle on the nexus
+ ctx.beginPath();
+ ctx.arc(nexusColumn * colWidth, height / 2.0,
+ theme.getCircleRadius() + theme.getStrokeWidth(), 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(nexusColumn * colWidth, height / 2.0,
+ theme.getCircleRadius(), 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.setFillStyle("white");
+ ctx.fill();
+
+ }
+
+ private GraphColumn[] columns_;
+
+ // Use a static canvas to avoid the overhead of continually recreating them
+ private static final Canvas s_canvas = Canvas.createIfSupported();
+}
+
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphTheme.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphTheme.java
new file mode 100644
index 0000000..2475437
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/graph/GraphTheme.java
@@ -0,0 +1,79 @@
+/*
+ * GraphTheme.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.dialog.graph;
+
+import com.google.gwt.canvas.dom.client.CssColor;
+
+import java.util.HashMap;
+
+public class GraphTheme
+{
+ public GraphTheme(String className)
+ {
+ className_ = className;
+ }
+
+ public int getColumnWidth()
+ {
+ return 10;
+ }
+
+ public int getRowHeight()
+ {
+ return 24;
+ }
+
+ public double getStrokeWidth()
+ {
+ return 2.0;
+ }
+
+ public double getCircleRadius()
+ {
+ return 3.0;
+ }
+
+ public CssColor getColorForId(int id)
+ {
+ if (!colors_.containsKey(id))
+ {
+ colors_.put(id, CssColor.make(
+ randomColorValue(),
+ randomColorValue(),
+ randomColorValue()
+ ).value());
+ }
+ return CssColor.make(colors_.get(id));
+ }
+
+ private int randomColorValue()
+ {
+ return 60 + (int)(Math.random() * 140);
+ }
+
+ public double getVerticalLinePadding()
+ {
+ return 2.0;
+ }
+
+ public String getImgClassName()
+ {
+ return className_;
+ }
+
+ private final String className_;
+
+ private static HashMap<Integer, String> colors_ = new HashMap<Integer, String>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/blankFileIcon.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/blankFileIcon.png
new file mode 100644
index 0000000..53a1912
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/blankFileIcon.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/diffHeaderTile.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/diffHeaderTile.png
new file mode 100644
index 0000000..4350142
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/diffHeaderTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/discard.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/discard.png
new file mode 100644
index 0000000..7163e2a
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/discard.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/ignore.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/ignore.png
new file mode 100644
index 0000000..c39060d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/ignore.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileH.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileH.png
new file mode 100644
index 0000000..d92c5a2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileH.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileV.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileV.png
new file mode 100644
index 0000000..485bf45
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/splitterTileV.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stage.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stage.png
new file mode 100644
index 0000000..e1d8f44
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stage.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stageAllFiles.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stageAllFiles.png
new file mode 100644
index 0000000..38d848e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/stageAllFiles.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/toolbarTile.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/toolbarTile.png
new file mode 100644
index 0000000..5753471
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/dialog/images/toolbarTile.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/frame/VCSPopup.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/frame/VCSPopup.java
new file mode 100644
index 0000000..cfb0964
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/frame/VCSPopup.java
@@ -0,0 +1,112 @@
+/*
+ * VCSPopup.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.frame;
+
+import java.util.ArrayList;
+
+import com.google.gwt.dom.client.Style.Unit;
+
+import com.google.gwt.user.client.ui.*;
+
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.SwitchViewEvent;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenter;
+
+public class VCSPopup
+{
+ public interface Controller
+ {
+ void switchToHistory(FileSystemItem fileFilter);
+ void switchToReview(ArrayList<StatusAndPath> selected);
+ }
+
+ public static Controller show(final LayoutPanel swapContainer,
+ final ReviewPresenter rpres,
+ final HistoryPresenter hpres,
+ boolean showHistory)
+ {
+ final Widget review = rpres.asWidget();
+ review.setSize("100%", "100%");
+
+ final Widget history = hpres.asWidget();
+ history.setSize("100%", "100%");
+
+ swapContainer.setSize("100%", "100%");
+ swapContainer.add(review);
+ swapContainer.setWidgetLeftRight(review, 0, Unit.PX, 0, Unit.PX);
+ swapContainer.setWidgetTopBottom(review, 0, Unit.PX, 0, Unit.PX);
+ swapContainer.add(history);
+ swapContainer.setWidgetLeftRight(history, 0, Unit.PX, 0, Unit.PX);
+ swapContainer.setWidgetTopBottom(history, 0, Unit.PX, 0, Unit.PX);
+
+ if (showHistory)
+ {
+ swapContainer.setWidgetVisible(review, false);
+ hpres.onShow();
+ }
+ else
+ {
+ swapContainer.setWidgetVisible(history, false);
+ rpres.onShow();
+ }
+
+ // create a controller used to implement switch view and to return
+ final Controller controller = new Controller() {
+ @Override
+ public void switchToHistory(FileSystemItem fileFilter)
+ {
+ if (fileFilter != null)
+ hpres.setFileFilter(fileFilter);
+
+ hpres.onShow();
+ swapContainer.setWidgetVisible(history, true);
+ swapContainer.setWidgetVisible(review, false);
+ }
+
+ @Override
+ public void switchToReview(ArrayList<StatusAndPath> selected)
+ {
+ if (selected != null)
+ rpres.setSelectedPaths(selected);
+
+ rpres.onShow();
+ swapContainer.setWidgetVisible(review, true);
+ swapContainer.setWidgetVisible(history, false);
+ }
+
+ };
+
+ rpres.addSwitchViewHandler(new SwitchViewEvent.Handler() {
+ @Override
+ public void onSwitchView(SwitchViewEvent event)
+ {
+ controller.switchToHistory(null);
+ }
+ });
+ hpres.addSwitchViewHandler(new SwitchViewEvent.Handler() {
+ @Override
+ public void onSwitchView(SwitchViewEvent event)
+ {
+ controller.switchToReview(null);
+ }
+ });
+
+ return controller;
+ }
+
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTable.java
new file mode 100644
index 0000000..a259481
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTable.java
@@ -0,0 +1,103 @@
+/*
+ * GitChangelistTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.Column;
+import org.rstudio.core.client.cellview.TriStateCheckboxCell;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.StageUnstageEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.StageUnstageHandler;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class GitChangelistTable extends ChangelistTable
+{
+ public void toggleStaged(boolean moveSelection)
+ {
+ ArrayList<StatusAndPath> items = getSelectedItems();
+ if (items.size() > 0)
+ {
+ boolean unstage = items.get(0).getStatus().charAt(1) == ' ';
+ fireEvent(new StageUnstageEvent(unstage, items));
+
+ if (moveSelection)
+ {
+ moveSelectionDown();
+ }
+ }
+ }
+
+ @Override
+ protected SafeHtmlRenderer<String> getStatusRenderer()
+ {
+ return new GitStatusRenderer();
+ }
+
+ @Override
+ protected void configureTable()
+ {
+ final Column<StatusAndPath, Boolean> stagedColumn = new Column<StatusAndPath, Boolean>(
+ new TriStateCheckboxCell<StatusAndPath>(selectionModel_))
+ {
+ @Override
+ public Boolean getValue(StatusAndPath object)
+ {
+ return "??".equals(object.getStatus()) ? Boolean.FALSE :
+ object.getStatus().charAt(1) == ' ' ? Boolean.TRUE :
+ object.getStatus().charAt(0) == ' ' ? Boolean.FALSE :
+ null;
+ }
+ };
+
+ stagedColumn.setHorizontalAlignment(Column.ALIGN_CENTER);
+ stagedColumn.setFieldUpdater(new FieldUpdater<StatusAndPath, Boolean>()
+ {
+ @Override
+ public void update(final int index,
+ final StatusAndPath object,
+ Boolean value)
+ {
+ fireEvent(new StageUnstageEvent(!value, getSelectedItems()));
+ }
+ });
+ stagedColumn.setSortable(true);
+ sortHandler_.setComparator(stagedColumn, new Comparator<StatusAndPath>()
+ {
+ @Override
+ public int compare(StatusAndPath a, StatusAndPath b)
+ {
+ Boolean a1 = stagedColumn.getValue(a);
+ Boolean b1 = stagedColumn.getValue(b);
+ int a2 = a1 == null ? 0 : a1 ? -1 : 1;
+ int b2 = b1 == null ? 0 : b1 ? -1 : 1;
+ return a2 - b2;
+ }
+ });
+ table_.addColumn(stagedColumn, "Staged");
+ table_.setColumnWidth(stagedColumn, "46px");
+
+ super.configureTable();
+ }
+
+ public HandlerRegistration addStageUnstageHandler(StageUnstageHandler handler)
+ {
+ return addHandler(handler, StageUnstageEvent.TYPE);
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTablePresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTablePresenter.java
new file mode 100644
index 0000000..73d9ecd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitChangelistTablePresenter.java
@@ -0,0 +1,102 @@
+/*
+ * GitChangelistTablePresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+import com.google.inject.Inject;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.RemoteBranchInfo;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.StageUnstageEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.StageUnstageHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+import java.util.ArrayList;
+
+public class GitChangelistTablePresenter
+{
+ @Inject
+ public GitChangelistTablePresenter(GitServerOperations server,
+ GitChangelistTable view,
+ GitState gitState)
+ {
+ server_ = server;
+ view_ = view;
+ gitState_ = gitState;
+
+ view_.addStageUnstageHandler(new StageUnstageHandler()
+ {
+ @Override
+ public void onStageUnstage(StageUnstageEvent event)
+ {
+ ArrayList<String> paths = new ArrayList<String>();
+ for (StatusAndPath path : event.getPaths())
+ paths.add(path.getPath());
+
+ if (event.isUnstage())
+ {
+ server_.gitUnstage(paths,
+ new SimpleRequestCallback<Void>());
+ }
+ else
+ {
+ server_.gitStage(paths,
+ new SimpleRequestCallback<Void>());
+ }
+ }
+ });
+
+ gitState_.bindRefreshHandler(view_, new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ view_.setItems(gitState_.getStatus());
+
+ RemoteBranchInfo remote = gitState_.getRemoteBranchInfo();
+ if (remote != null && remote.getCommitsBehind() > 0)
+ {
+ String message =
+ "Your branch is ahead of '" + remote.getName() + "' by " +
+ remote.getCommitsBehind() + " commit" +
+ (remote.getCommitsBehind() > 1 ? "s" : "") + ".";
+
+ view_.showInfoBar(message);
+ }
+ else
+ {
+ view_.hideInfoBar();
+ }
+ }
+ });
+ }
+
+ public void setSelectFirstItemByDefault(boolean selectFirstItemByDefault)
+ {
+ view_.setSelectFirstItemByDefault(selectFirstItemByDefault);
+ }
+
+ public GitChangelistTable getView()
+ {
+ return view_;
+ }
+
+ private final GitServerOperations server_;
+ private final GitChangelistTable view_;
+ private final GitState gitState_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPane.java
new file mode 100644
index 0000000..42850da
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPane.java
@@ -0,0 +1,208 @@
+/*
+ * GitPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.view.client.SelectionChangeEvent.Handler;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.vcs.CheckoutBranchToolbarButton;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitPresenter.Display;
+
+import java.util.ArrayList;
+
+public class GitPane extends WorkbenchPane implements Display
+{
+ @Inject
+ public GitPane(GitChangelistTablePresenter changelistTablePresenter,
+ Session session,
+ Commands commands,
+ CheckoutBranchToolbarButton branchToolbarButton)
+ {
+ super(session.getSessionInfo().getVcsName());
+ commands_ = commands;
+ branchToolbarButton_ = branchToolbarButton;
+
+ table_ = changelistTablePresenter.getView();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ ToolbarPopupMenu moreMenu = new ToolbarPopupMenu();
+ moreMenu.addItem(commands_.vcsRevert().createMenuItem(false));
+ moreMenu.addItem(commands_.vcsIgnore().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.showShellDialog().createMenuItem(false));
+
+ Toolbar toolbar = new Toolbar();
+ toolbar.addLeftWidget(commands_.vcsDiff().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.vcsCommit().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(pullButton_ = commands_.vcsPull().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(pushButton_ = commands_.vcsPush().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(historyButton_ = commands_.vcsShowHistory().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(moreButton_ = new ToolbarButton(
+ "More",
+ StandardIcons.INSTANCE.more_actions(),
+ moreMenu));
+
+ toolbar.addRightWidget(branchToolbarButton_);
+
+ toolbar.addRightWidget(new ToolbarButton(
+ commands_.vcsRefresh().getImageResource(),
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ table_.showProgress();
+ commands_.vcsRefresh().execute();
+ }
+ }));
+
+ return toolbar;
+ }
+
+ @Override
+ public void onSelected()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute()
+ {
+ manageToolbarSizes();
+ }
+ });
+ }
+
+ @Override
+ public void onResize()
+ {
+ super.onResize();
+
+ manageToolbarSizes();
+
+ }
+
+ private void manageToolbarSizes()
+ {
+ // sometimes width is passed in as 0 (not sure why)
+ int width = getOffsetWidth();
+ if (width == 0)
+ return;
+
+ pullButton_.setText(width > 500 ? "Pull" : "");
+ pushButton_.setText(width > 500 ? "Push" : "");
+ historyButton_.setText(width > 580 ? "History" : "");
+ moreButton_.setText(width > 580 ? "More" : "");
+
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return table_;
+ }
+
+ @Override
+ public void setItems(ArrayList<StatusAndPath> items)
+ {
+ table_.setItems(items);
+ }
+
+ @Override
+ public ArrayList<String> getSelectedPaths()
+ {
+ return table_.getSelectedPaths();
+ }
+
+ @Override
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ return table_.getSelectedItems();
+ }
+
+ @Override
+ public int getSelectedItemCount()
+ {
+ return table_.getSelectedItems().size();
+ }
+
+ @Override
+ public void onRefreshBegin()
+ {
+ table_.showProgress();
+ }
+
+ @Override
+ public HandlerRegistration addSelectionChangeHandler(Handler handler)
+ {
+ return table_.addSelectionChangeHandler(handler);
+ }
+
+ @Override
+ public GitChangelistTable getChangelistTable()
+ {
+ return table_;
+ }
+
+ @Override
+ public void showContextMenu(final int clientX, final int clientY)
+ {
+ final ToolbarPopupMenu menu = new ToolbarPopupMenu();
+
+ menu.addItem(commands_.vcsDiff().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsRevert().createMenuItem(false));
+ menu.addItem(commands_.vcsIgnore().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsOpen().createMenuItem(false));
+
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ menu.setPopupPosition(clientX, clientY);
+ }
+ });
+ }
+
+ private ToolbarButton historyButton_;
+ private ToolbarButton moreButton_;
+ private ToolbarButton pullButton_;
+ private ToolbarButton pushButton_;
+
+ private final Commands commands_;
+ private final CheckoutBranchToolbarButton branchToolbarButton_;
+ private GitChangelistTable table_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenter.java
new file mode 100644
index 0000000..13ad329
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenter.java
@@ -0,0 +1,407 @@
+/*
+ * GitPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.DoubleClickState;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.vcs.VCSApplicationParams;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.BaseVcsPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.GitHubViewRequest;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+import java.util.ArrayList;
+
+public class GitPresenter extends BaseVcsPresenter implements IsWidget
+{
+ public interface Binder extends CommandBinder<Commands, GitPresenter> {}
+
+ public interface Display extends WorkbenchView, IsWidget
+ {
+ void setItems(ArrayList<StatusAndPath> items);
+ ArrayList<String> getSelectedPaths();
+ ArrayList<StatusAndPath> getSelectedItems();
+ int getSelectedItemCount();
+
+ void onRefreshBegin();
+
+ HandlerRegistration addSelectionChangeHandler(
+ SelectionChangeEvent.Handler handler);
+
+ GitChangelistTable getChangelistTable();
+
+ void showContextMenu(int clientX, int clientY);
+ }
+
+ @Inject
+ public GitPresenter(GitPresenterCore gitCore,
+ VCSFileOpener vcsFileOpener,
+ Display view,
+ GitServerOperations server,
+ final Commands commands,
+ Binder commandBinder,
+ GitState gitState,
+ final GlobalDisplay globalDisplay,
+ SatelliteManager satelliteManager)
+ {
+ super(view);
+ gitPresenterCore_ = gitCore;
+ vcsFileOpener_ = vcsFileOpener;
+ view_ = view;
+ server_ = server;
+ commands_ = commands;
+ gitState_ = gitState;
+ globalDisplay_ = globalDisplay;
+ satelliteManager_ = satelliteManager;
+
+ commandBinder.bind(commands, this);
+
+ gitState_.addVcsRefreshHandler(new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ view_.setItems(gitState_.getStatus());
+ }
+ });
+
+ view_.getChangelistTable().addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ int mod = KeyboardShortcut.getModifierValue(event.getNativeEvent());
+ if (mod != KeyboardShortcut.NONE)
+ return;
+
+ if (event.getNativeKeyCode() == ' ')
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ view_.getChangelistTable().toggleStaged(false);
+ }
+ else if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ view_.getChangelistTable().toggleStaged(true);
+ }
+ }
+ });
+ view_.getChangelistTable().addMouseDownHandler(new MouseDownHandler()
+ {
+ private DoubleClickState dblClick = new DoubleClickState();
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ if (dblClick.checkForDoubleClick(event.getNativeEvent()))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ view_.getChangelistTable().toggleStaged(false);
+ }
+ }
+ });
+
+ view_.getChangelistTable().addContextMenuHandler(new ContextMenuHandler(){
+ @Override
+ public void onContextMenu(ContextMenuEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ view_.showContextMenu(nativeEvent.getClientX(),
+ nativeEvent.getClientY());
+ }
+ });
+
+ view_.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent event)
+ {
+ manageCommands();
+ }
+ });
+ manageCommands();
+ }
+
+ private void openSelectedFiles()
+ {
+ vcsFileOpener_.openFiles(view_.getSelectedItems());
+ }
+
+ private void manageCommands()
+ {
+ boolean anySelected = view_.getSelectedItemCount() > 0;
+ commands_.vcsRevert().setEnabled(anySelected);
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Handler
+ void onVcsDiff()
+ {
+ showChanges(view_.getSelectedItems());
+ }
+
+ private void showChanges(ArrayList<StatusAndPath> items)
+ {
+ showReviewPane(false, null, items);
+ }
+
+ private void showReviewPane(boolean showHistory,
+ FileSystemItem historyFileFilter,
+ ArrayList<StatusAndPath> items)
+ {
+ // setup params
+ VCSApplicationParams params = VCSApplicationParams.create(
+ showHistory,
+ historyFileFilter,
+ items);
+
+ // open the window
+ satelliteManager_.openSatellite("review_changes",
+ params,
+ new Size(1000,1200));
+ }
+
+ @Handler
+ void onVcsRevert()
+ {
+ final ArrayList<String> paths = view_.getSelectedPaths();
+ if (paths.size() == 0)
+ return;
+
+ doRevert(paths, new Command() {
+ @Override
+ public void execute()
+ {
+ view_.getChangelistTable().selectNextUnselectedItem();
+ view_.getChangelistTable().focus();
+ }
+
+ });
+ }
+
+ @Handler
+ void onVcsOpen()
+ {
+ openSelectedFiles();
+ }
+
+
+ @Override
+ public void onVcsCommit()
+ {
+ showChanges(view_.getSelectedItems());
+ }
+
+ @Override
+ public void onVcsShowHistory()
+ {
+ showHistory(null);
+ }
+
+ @Override
+ public void onVcsPull()
+ {
+ gitPresenterCore_.onVcsPull();
+ }
+
+ @Override
+ public void onVcsPush()
+ {
+ gitPresenterCore_.onVcsPush();
+ }
+
+ @Override
+ public void onVcsIgnore()
+ {
+ gitPresenterCore_.onVcsIgnore(view_.getSelectedItems());
+ }
+
+ @Override
+ public void showHistory(FileSystemItem fileFilter)
+ {
+ showReviewPane(true, fileFilter, new ArrayList<StatusAndPath>());
+ }
+
+ @Override
+ public void showDiff(FileSystemItem file)
+ {
+ // build an ArrayList<StatusAndPath> so we can call the core helper
+ ArrayList<StatusAndPath> diffList = new ArrayList<StatusAndPath>();
+ for (StatusAndPath item : gitState_.getStatus())
+ {
+ if (item.getRawPath().equals(file.getPath()))
+ {
+ diffList.add(item);
+ break;
+ }
+ }
+
+ if (diffList.size() > 0)
+ {
+ showChanges(diffList);
+ }
+ else
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "No Changes to File",
+ "There are no changes to the file \"" +
+ file.getName() + "\" to diff.");
+ }
+
+ }
+
+ @Override
+ public void revertFile(FileSystemItem file)
+ {
+ // build an ArrayList<String> so we can call the core helper
+ ArrayList<String> revertList = new ArrayList<String>();
+ for (StatusAndPath item : gitState_.getStatus())
+ {
+ if (item.getRawPath().equals(file.getPath()))
+ {
+ revertList.add(item.getPath());
+ break;
+ }
+ }
+
+ if (revertList.size() > 0)
+ {
+ doRevert(revertList, null);
+ }
+ else
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "No Changes to Revert",
+ "There are no changes to the file \"" +
+ file.getName() + "\" to revert.");
+ }
+
+
+ }
+
+
+ private void doRevert(final ArrayList<String> revertList,
+ final Command onRevertConfirmed)
+ {
+ String noun = revertList.size() == 1 ? "file" : "files";
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Revert Changes",
+ "Changes to the selected " + noun + " will be lost, including " +
+ "staged changes.\n\nAre you sure you want to continue?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ if (onRevertConfirmed != null)
+ onRevertConfirmed.execute();
+
+ server_.gitRevert(
+ revertList,
+ new SimpleRequestCallback<Void>("Revert Changes"));
+
+ }
+ },
+ false);
+ }
+
+ @Override
+ public void viewOnGitHub(final GitHubViewRequest viewRequest)
+ {
+ String view = null;
+ if (viewRequest.getViewType() == GitHubViewRequest.ViewType.View)
+ view = "blob";
+ else if (viewRequest.getViewType() == GitHubViewRequest.ViewType.Blame)
+ view = "blame";
+
+ final String path = viewRequest.getFile().getPath();
+ server_.gitGithubRemoteUrl(view,
+ path,
+ new SimpleRequestCallback<String>() {
+
+ @Override
+ public void onResponseReceived(String url)
+ {
+ if (url.length() == 0)
+ {
+ globalDisplay_.showErrorMessage(
+ "Error",
+ "Unable to view " + path + " on GitHub.\n\n" +
+ "Are you sure that this file is on GithHub and is " +
+ "contained in the currently active project?");
+ }
+ else
+ {
+ if (viewRequest.getStartLine() != -1)
+ url += "#L" + viewRequest.getStartLine();
+ if (viewRequest.getEndLine() != viewRequest.getStartLine())
+ url += "-L" + viewRequest.getEndLine();
+
+ globalDisplay_.openWindow(url);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onVcsCleanup()
+ {
+ // svn specific, not supported by git
+
+ }
+
+ private final Display view_;
+ private final GitPresenterCore gitPresenterCore_;
+ private final GitServerOperations server_;
+ private final Commands commands_;
+ private final GitState gitState_;
+ private final GlobalDisplay globalDisplay_;
+ private final SatelliteManager satelliteManager_;
+ private final VCSFileOpener vcsFileOpener_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenterCore.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenterCore.java
new file mode 100644
index 0000000..3c5aa81
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitPresenterCore.java
@@ -0,0 +1,178 @@
+/*
+ * GitPresenterCore.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+
+import java.util.ArrayList;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.satellite.Satellite;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.ProcessResult;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.ignore.Ignore;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+ at Singleton
+public class GitPresenterCore
+{
+ public interface Binder extends CommandBinder<Commands, GitPresenterCore> {}
+
+ @Inject
+ public GitPresenterCore(GitServerOperations server,
+ GitState gitState,
+ Provider<Ignore> pIgnore,
+ final Commands commands,
+ Binder commandBinder,
+ EventBus eventBus,
+ final GlobalDisplay globalDisplay,
+ final Satellite satellite,
+ final SatelliteManager satelliteManager)
+ {
+ server_ = server;
+ gitState_ = gitState;
+ pIgnore_ = pIgnore;
+
+ commandBinder.bind(commands, this);
+
+ gitState_.addVcsRefreshHandler(new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ boolean hasRemote = gitState_.hasRemote();
+ commands.vcsPull().setEnabled(hasRemote);
+ commands.vcsPush().setEnabled(hasRemote);
+ }
+ });
+ }
+
+ @Handler
+ void onVcsRefresh()
+ {
+ gitState_.refresh();
+ }
+
+ @Handler
+ void onVcsRefreshNoError()
+ {
+ gitState_.refresh(false);
+ }
+
+
+ public void onVcsPull()
+ {
+ server_.gitPull(new SimpleRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ new ConsoleProgressDialog(proc, server_).showModal();
+ }
+ });
+ }
+
+ public void onVcsPush()
+ {
+ server_.gitPush(new SimpleRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ new ConsoleProgressDialog(proc, server_).showModal();
+ }
+ });
+ }
+
+ public void onVcsIgnore(ArrayList<StatusAndPath> items)
+ {
+ ArrayList<String> paths = getPathArray(items);
+
+ pIgnore_.get().showDialog(paths, new Ignore.Strategy() {
+
+ @Override
+ public String getDialogCaption()
+ {
+ return "Git Ignore";
+ }
+
+ @Override
+ public String getIgnoresCaption()
+ {
+ return ".gitignore";
+ }
+
+ @Override
+ public String getHelpLinkName()
+ {
+ return "git_ignore_help";
+ }
+
+ @Override
+ public Ignore.Strategy.Filter getFilter()
+ {
+ return new Ignore.Strategy.Filter() {
+ @Override
+ public boolean includeFile(FileSystemItem file)
+ {
+ return !file.getName().equals(".gitignore");
+ }
+ };
+ }
+
+ @Override
+ public void getIgnores(String path,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ server_.gitGetIgnores(path, requestCallback);
+ }
+
+ @Override
+ public void setIgnores(String path, String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ server_.gitSetIgnores(path, ignores, requestCallback);
+ }
+ });
+
+ }
+
+ private ArrayList<String> getPathArray(ArrayList<StatusAndPath> items)
+ {
+ ArrayList<String> paths = new ArrayList<String>();
+ for (StatusAndPath item : items)
+ paths.add(item.getPath());
+ return paths;
+ }
+
+ private final GitServerOperations server_;
+ private final GitState gitState_;
+ private final Provider<Ignore> pIgnore_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitStatusRenderer.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitStatusRenderer.java
new file mode 100644
index 0000000..56ec6b8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/GitStatusRenderer.java
@@ -0,0 +1,154 @@
+/*
+ * GitStatusRenderer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable.ChangelistTableCellTableResources;
+
+public class GitStatusRenderer implements SafeHtmlRenderer<String>
+{
+ interface StatusResources extends ClientBundle
+ {
+ @Source("images/statusAdded.png")
+ ImageResource statusAdded();
+ @Source("images/statusDeleted.png")
+ ImageResource statusDeleted();
+ @Source("images/statusModified.png")
+ ImageResource statusModified();
+ @Source("images/statusNone.png")
+ ImageResource statusNone();
+ @Source("images/statusCopied.png")
+ ImageResource statusCopied();
+ @Source("images/statusUntracked.png")
+ ImageResource statusUntracked();
+ @Source("images/statusUnmerged.png")
+ ImageResource statusUnmerged();
+ @Source("images/statusRenamed.png")
+ ImageResource statusRenamed();
+ }
+
+ public GitStatusRenderer()
+ {
+ }
+
+ @Override
+ public SafeHtml render(String str)
+ {
+ if (str.length() != 2)
+ return null;
+
+ ImageResource indexImg = imgForStatus(str.charAt(0));
+ ImageResource treeImg = imgForStatus(str.charAt(1));
+
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ builder.append(SafeHtmlUtils.fromTrustedString(
+ "<span " +
+ "class=\"" + ctRes_.cellTableStyle().status() + "\" " +
+ "title=\"" +
+ SafeHtmlUtils.htmlEscape(descForStatus(str)) +
+ "\">"));
+
+ builder.append(SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(
+ indexImg).getHTML()));
+ builder.append(SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(treeImg).getHTML()));
+
+ builder.appendHtmlConstant("</span>");
+
+ return builder.toSafeHtml();
+ }
+
+ private String descForStatus(String str)
+ {
+ String indexDesc = descForStatus(str.charAt(0));
+ String treeDesc = descForStatus(str.charAt(1));
+
+ if (indexDesc.length() > 0 && treeDesc.length() > 0)
+ return indexDesc + "/" + treeDesc;
+ else if (indexDesc.length() > 0)
+ return indexDesc;
+ else if (treeDesc.length() > 0)
+ return treeDesc;
+ else
+ return "";
+ }
+
+ private String descForStatus(char c)
+ {
+ switch (c)
+ {
+ case 'A':
+ return "Added";
+ case 'M':
+ return "Modified";
+ case 'D':
+ return "Deleted";
+ case 'R':
+ return "Renamed";
+ case 'C':
+ return "Copied";
+ case '?':
+ return "Untracked";
+ case 'U':
+ return "Unmerged";
+ case ' ':
+ return "";
+ default:
+ return "";
+ }
+ }
+
+ private ImageResource imgForStatus(char c)
+ {
+ switch (c)
+ {
+ case 'A':
+ return resources_.statusAdded();
+ case 'M':
+ return resources_.statusModified();
+ case 'D':
+ return resources_.statusDeleted();
+ case 'R':
+ return resources_.statusRenamed();
+ case 'C':
+ return resources_.statusCopied();
+ case '?':
+ return resources_.statusUntracked();
+ case 'U':
+ return resources_.statusUnmerged();
+ case ' ':
+ return resources_.statusNone();
+ default:
+ return resources_.statusNone();
+ }
+ }
+
+ @Override
+ public void render(String str, SafeHtmlBuilder builder)
+ {
+ SafeHtml safeHtml = render(str);
+ if (safeHtml != null)
+ builder.append(safeHtml);
+ }
+
+ private static final StatusResources resources_ = GWT.create(StatusResources.class);
+ private static final ChangelistTableCellTableResources ctRes_ = GWT.create(ChangelistTableCellTableResources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryAsyncDataProvider.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryAsyncDataProvider.java
new file mode 100644
index 0000000..d205597
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryAsyncDataProvider.java
@@ -0,0 +1,63 @@
+/*
+ * GitHistoryAsyncDataProvider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git.dialog;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitCount;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryAsyncDataProvider;
+
+public class GitHistoryAsyncDataProvider extends HistoryAsyncDataProvider
+{
+ @Inject
+ public GitHistoryAsyncDataProvider(GitServerOperations server)
+ {
+ server_ = server;
+ }
+
+ @Override
+ protected void getHistoryCount(String revision,
+ FileSystemItem fileFilter,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback)
+ {
+ server_.gitHistoryCount(revision,
+ fileFilter,
+ searchText,
+ requestCallback);
+ }
+
+ @Override
+ protected void getHistory(String revision,
+ FileSystemItem fileFilter,
+ int skip,
+ int maxEntries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback)
+ {
+ server_.gitHistory(revision,
+ fileFilter,
+ skip,
+ maxEntries,
+ searchText,
+ requestCallback);
+ }
+
+ private final GitServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryStrategy.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryStrategy.java
new file mode 100644
index 0000000..e00d9e2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitHistoryStrategy.java
@@ -0,0 +1,163 @@
+/*
+ * GitHistoryStrategy.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git.dialog;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.cellview.client.AbstractPager;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.view.client.HasData;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.Pager;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.DiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.UnifiedParser;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryStrategy;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+public class GitHistoryStrategy implements HistoryStrategy
+{
+ @Inject
+ public GitHistoryStrategy(GitServerOperations server,
+ GitHistoryAsyncDataProvider dataProvider,
+ Provider<GitState> pVcsState)
+ {
+ server_ = server;
+ dataProvider_ = dataProvider;
+ dataProvider_.setHistoryStrategy(this);
+ pVcsState_ = pVcsState;
+ }
+
+ @Override
+ public void setRev(String rev)
+ {
+ dataProvider_.setRev(rev);
+ }
+
+ @Override
+ public String idColumnName()
+ {
+ return "SHA";
+ }
+
+ @Override
+ public boolean isBranchingSupported()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isShowFileSupported()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isSearchSupported()
+ {
+ return true;
+ }
+
+ @Override
+ public void setSearchText(HasValue<String> searchText)
+ {
+ dataProvider_.setSearchText(searchText);
+ }
+
+ @Override
+ public void setFileFilter(HasValue<FileSystemItem> fileFilter)
+ {
+ dataProvider_.setFileFilter(fileFilter);
+ }
+
+ @Override
+ public void showFile(String revision,
+ String filename,
+ ServerRequestCallback<String> requestCallback)
+ {
+ server_.gitShowFile(revision, filename, requestCallback);
+ }
+
+ @Override
+ public HandlerRegistration addVcsRefreshHandler(VcsRefreshHandler handler)
+ {
+ return pVcsState_.get().addVcsRefreshHandler(handler, false);
+ }
+
+ @Override
+ public void showCommit(String commitId,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback)
+ {
+ server_.gitShow(commitId, noSizeWarning, requestCallback);
+ }
+
+ @Override
+ public void addDataDisplay(HasData<CommitInfo> display)
+ {
+ dataProvider_.addDataDisplay(display);
+ }
+
+ @Override
+ public void onRangeChanged(HasData<CommitInfo> display)
+ {
+ dataProvider_.onRangeChanged(display);
+ }
+
+ @Override
+ public void refreshCount()
+ {
+ dataProvider_.refreshCount();
+ }
+
+ @Override
+ public void initializeHistory(HasData<CommitInfo> dataDisplay)
+ {
+ addDataDisplay(dataDisplay);
+ refreshCount();
+ }
+
+ @Override
+ public AbstractPager getPager()
+ {
+ return new Pager(100, 500);
+ }
+
+ @Override
+ public boolean getAutoSelectFirstRow()
+ {
+ return true;
+ }
+
+ @Override
+ public DiffParser createParserForCommit(String commitDiff)
+ {
+ return new UnifiedParser(commitDiff);
+ }
+
+ @Override
+ public boolean getShowHistoryErrors()
+ {
+ return true;
+ }
+
+ private final GitServerOperations server_;
+ private final GitHistoryAsyncDataProvider dataProvider_;
+ private final Provider<GitState> pVcsState_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.css
new file mode 100644
index 0000000..60a8fab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.css
@@ -0,0 +1,84 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external gwt-SplitLayoutPanel-VDragger, gwt-SplitLayoutPanel-HDragger;
+
+/** BEGIN SHARED BETWEEN HISTORY AND REVIEW PANELS **/
+
+.splitPanel {
+ background-color: #f3f4f4;
+}
+ at sprite .splitPanel .gwt-SplitLayoutPanel-VDragger {
+ gwt-image: 'splitterTileV';
+}
+ at sprite .splitPanelCommit .gwt-SplitLayoutPanel-HDragger {
+ gwt-image: 'splitterTileH';
+}
+
+.whitebg {
+ background-color: white;
+}
+
+.toolbar {
+ background-image: none !important;
+}
+
+ at sprite .toolbarWrapper {
+ position: relative;
+ gwt-image: 'toolbarTile';
+ padding-top: 2px;
+ font-size: 11px;
+}
+.toolbarWrapper select {
+ font-size: 11px;
+}
+
+/** END SHARED BETWEEN HISTORY AND REVIEW PANELS **/
+
+
+.splitPanelCommit {
+ background-color: #f3f4f4;
+ font-size: 11px;
+}
+
+.stagedLabel {
+ display: inline;
+ margin-right: 8px;
+}
+
+.staged {
+ margin-right: 8px;
+}
+
+.unstaged {
+ margin-right: 22px;
+}
+
+.diffToolbar {
+ width: auto;
+}
+
+.diffViewOptions {
+ white-space: nowrap;
+ margin-left: 6px;
+ margin-right: 16px;
+}
+
+.commitMessage {
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ resize: none;
+ border: 1px solid #c3c5c7;
+ border-radius: 4px;
+ padding: 3px;
+ margin: 0;
+ font-family: fixedWidthFont;
+}
+
+.commitButton {
+ float: right;
+ margin-right: -1px;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.java
new file mode 100644
index 0000000..c0263f3
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.java
@@ -0,0 +1,669 @@
+/*
+ * GitReviewPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.widget.*;
+import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.CheckoutBranchToolbarButton;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.ChunkOrLine;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTablePresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.SharedStyles;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.SizeWarningWidget;
+import org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitReviewPresenter.Display;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitChangelistTablePresenter;
+
+import java.util.ArrayList;
+
+public class GitReviewPanel extends ResizeComposite implements Display
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("GitReviewPanel.css")
+ Styles styles();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ @Source("../../dialog/images/toolbarTile.png")
+ ImageResource toolbarTile();
+
+ @Source("../../dialog/images/stageAllFiles.png")
+ ImageResource stageAllFiles();
+
+ @Source("../../dialog/images/discard.png")
+ ImageResource discard();
+
+ @Source("../../dialog/images/ignore.png")
+ ImageResource ignore();
+
+ @Source("../../dialog/images/stage.png")
+ ImageResource stage();
+
+ @Source("../../dialog/images/splitterTileV.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource splitterTileV();
+
+ @Source("../../dialog/images/splitterTileH.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource splitterTileH();
+
+ @Source("../../dialog/images/blankFileIcon.png")
+ ImageResource blankFileIcon();
+ }
+
+ interface Styles extends SharedStyles
+ {
+ String diffToolbar();
+
+ String stagedLabel();
+ String staged();
+
+ String unstaged();
+
+ String diffViewOptions();
+
+ String commitMessage();
+ String commitButton();
+
+ String splitPanelCommit();
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClickCommand implements HasClickHandlers, Command
+ {
+ @Override
+ public void execute()
+ {
+ ClickEvent.fireNativeEvent(
+ Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false),
+ this);
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return handlerManager_.addHandler(ClickEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlerManager_.fireEvent(event);
+ }
+
+ private final HandlerManager handlerManager_ = new HandlerManager(this);
+ }
+
+ private static class ListBoxAdapter implements HasValue<Integer>
+ {
+ private ListBoxAdapter(ListBox listBox)
+ {
+ listBox_ = listBox;
+ listBox_.addChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ ValueChangeEvent.fire(ListBoxAdapter.this, getValue());
+ }
+ });
+ }
+
+ @Override
+ public Integer getValue()
+ {
+ return Integer.parseInt(
+ listBox_.getValue(listBox_.getSelectedIndex()));
+ }
+
+ @Override
+ public void setValue(Integer value)
+ {
+ setValue(value, true);
+ }
+
+ @Override
+ public void setValue(Integer value, boolean fireEvents)
+ {
+ String valueStr = value.toString();
+ for (int i = 0; i < listBox_.getItemCount(); i++)
+ {
+ if (listBox_.getValue(i).equals(valueStr))
+ {
+ listBox_.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler)
+ {
+ return handlers_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ private final ListBox listBox_;
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ }
+
+
+ interface Binder extends UiBinder<Widget, GitReviewPanel>
+ {
+ }
+
+ @Inject
+ public GitReviewPanel(GitChangelistTablePresenter changelist,
+ LineTableView diffPane,
+ final Commands commands,
+ FileTypeRegistry fileTypeRegistry,
+ CheckoutBranchToolbarButton branchToolbarButton)
+ {
+ fileTypeRegistry_ = fileTypeRegistry;
+ splitPanel_ = new SplitLayoutPanel(4);
+ splitPanelCommit_ = new SplitLayoutPanel(4);
+
+ commitButton_ = new ThemedButton("Commit");
+ commitButton_.addStyleName(RES.styles().commitButton());
+
+ changelist_ = changelist.getView();
+ lines_ = diffPane;
+ lines_.getElement().setTabIndex(-1);
+
+ overrideSizeWarning_ = new SizeWarningWidget("diff");
+
+ changelist.setSelectFirstItemByDefault(true);
+
+ Widget widget = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+ initWidget(widget);
+
+ topToolbar_.addStyleName(RES.styles().toolbar());
+
+ switchViewButton_ = new LeftRightToggleButton("Changes", "History", true);
+ topToolbar_.addLeftWidget(switchViewButton_);
+
+ topToolbar_.addLeftWidget(branchToolbarButton);
+
+ stageFilesButton_ = topToolbar_.addLeftWidget(new ToolbarButton(
+ "Stage",
+ RES.stage(),
+ (ClickHandler) null));
+
+ topToolbar_.addLeftSeparator();
+
+ revertFilesButton_ = topToolbar_.addLeftWidget(new ToolbarButton(
+ "Revert",
+ commands.vcsRevert().getImageResource(),
+ (ClickHandler) null));
+
+ ignoreButton_ = topToolbar_.addLeftWidget(new ToolbarButton(
+ "Ignore", RES.ignore(), (ClickHandler) null));
+
+ topToolbar_.addRightWidget(new ToolbarButton(
+ "Refresh", commands.vcsRefresh().getImageResource(),
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ changelist_.showProgress();
+ commands.vcsRefresh().execute();
+ }
+ }));
+
+ topToolbar_.addRightSeparator();
+
+
+ topToolbar_.addRightWidget(commands.vcsPull().createToolbarButton());
+
+ topToolbar_.addRightSeparator();
+
+ topToolbar_.addRightWidget(commands.vcsPush().createToolbarButton());
+
+ diffToolbar_.addStyleName(RES.styles().toolbar());
+ diffToolbar_.addStyleName(RES.styles().diffToolbar());
+
+ toolbarWrapper_.setCellWidth(diffToolbar_, "100%");
+
+ stageAllButton_ = diffToolbar_.addLeftWidget(new ToolbarButton(
+ "Stage All", RES.stage(), (ClickHandler) null));
+ diffToolbar_.addLeftSeparator();
+ discardAllButton_ = diffToolbar_.addLeftWidget(new ToolbarButton(
+ "Discard All", RES.discard(), (ClickHandler) null));
+
+ unstageAllButton_ = diffToolbar_.addLeftWidget(new ToolbarButton(
+ "Unstage All", RES.discard(), (ClickHandler) null));
+ unstageAllButton_.setVisible(false);
+
+ unstagedCheckBox_.addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> e)
+ {
+ ValueChangeEvent.fire(stagedCheckBox_, stagedCheckBox_.getValue());
+ }
+ });
+
+ stagedCheckBox_.addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> e)
+ {
+ stageAllButton_.setVisible(!e.getValue());
+ discardAllButton_.setVisible(!e.getValue());
+ unstageAllButton_.setVisible(e.getValue());
+ diffToolbar_.invalidateSeparators();
+ }
+ });
+
+ commitMessage_.getElement().setAttribute("spellcheck", "false");
+
+ listBoxAdapter_ = new ListBoxAdapter(contextLines_);
+
+ FontSizer.applyNormalFontSize(commitMessage_);
+
+ new WidgetHandlerRegistration(this)
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return Event.addNativePreviewHandler(new NativePreviewHandler()
+ {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && KeyboardShortcut.getModifierValue(nativeEvent) == KeyboardShortcut.CTRL)
+ {
+ switch (nativeEvent.getKeyCode())
+ {
+ case KeyCodes.KEY_DOWN:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, getLineScroll(diffScroll_), 0);
+ break;
+ case KeyCodes.KEY_UP:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, -getLineScroll(diffScroll_), 0);
+ break;
+ case KeyCodes.KEY_PAGEDOWN:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, getPageScroll(diffScroll_), 0);
+ break;
+ case KeyCodes.KEY_PAGEUP:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, -getPageScroll(diffScroll_), 0);
+ break;
+ }
+ }
+ }
+ });
+ }
+ };
+ }
+
+ private void scrollBy(ScrollPanel scrollPanel, int vscroll, int hscroll)
+ {
+ if (vscroll != 0)
+ {
+ scrollPanel.setVerticalScrollPosition(
+ Math.max(0, scrollPanel.getVerticalScrollPosition() + vscroll));
+ }
+
+ if (hscroll != 0)
+ {
+ scrollPanel.setHorizontalScrollPosition(
+ Math.max(0, scrollPanel.getHorizontalScrollPosition() + hscroll));
+ }
+ }
+
+ private int getLineScroll(ScrollPanel panel)
+ {
+ return 30;
+ }
+
+ private int getPageScroll(ScrollPanel panel)
+ {
+ // Return slightly less than the client height (so there's overlap between
+ // one screen and the next) but never less than the line scoll height.
+ return Math.max(
+ getLineScroll(panel),
+ panel.getElement().getClientHeight() - getLineScroll(panel));
+ }
+
+ @Override
+ public HasClickHandlers getSwitchViewButton()
+ {
+ return switchViewButton_;
+ }
+
+ @Override
+ public HasClickHandlers getStageFilesButton()
+ {
+ return stageFilesButton_;
+ }
+
+ @Override
+ public HasClickHandlers getRevertFilesButton()
+ {
+ return revertFilesButton_;
+ }
+
+ @Override
+ public void setFilesCommandsEnabled(boolean enabled)
+ {
+ stageFilesButton_.setEnabled(enabled);
+ revertFilesButton_.setEnabled(enabled);
+ ignoreButton_.setEnabled(enabled);
+
+ }
+
+ @Override
+ public HasClickHandlers getIgnoreButton()
+ {
+ return ignoreButton_;
+ }
+
+ @Override
+ public HasClickHandlers getStageAllButton()
+ {
+ return stageAllButton_;
+ }
+
+ @Override
+ public HasClickHandlers getDiscardAllButton()
+ {
+ return discardAllButton_;
+ }
+
+ @Override
+ public HasClickHandlers getUnstageAllButton()
+ {
+ return unstageAllButton_;
+ }
+
+ @Override
+ public void setShowActions(boolean showActions)
+ {
+ diffToolbar_.setVisible(showActions);
+ lines_.setShowActions(showActions);
+ }
+
+ @Override
+ public void setData(ArrayList<ChunkOrLine> lines, PatchMode patchMode)
+ {
+ int vscroll = diffScroll_.getVerticalScrollPosition();
+ int hscroll = diffScroll_.getHorizontalScrollPosition();
+
+ getLineTableDisplay().setData(lines, patchMode);
+
+ diffScroll_.setVerticalScrollPosition(vscroll);
+ diffScroll_.setHorizontalScrollPosition(hscroll);
+ }
+
+ @Override
+ public HasText getCommitMessage()
+ {
+ return commitMessage_;
+ }
+
+ @Override
+ public HasClickHandlers getCommitButton()
+ {
+ return commitButton_;
+ }
+
+ @Override
+ public HasValue<Boolean> getCommitIsAmend()
+ {
+ return commitIsAmend_;
+ }
+
+ @Override
+ public ArrayList<String> getSelectedPaths()
+ {
+ return changelist_.getSelectedPaths();
+ }
+
+ @Override
+ public void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ changelist_.setSelectedStatusAndPaths(selectedPaths);
+ }
+
+ @Override
+ public ArrayList<String> getSelectedDiscardablePaths()
+ {
+ return changelist_.getSelectedDiscardablePaths();
+ }
+
+ @Override
+ public HasValue<Boolean> getStagedCheckBox()
+ {
+ return stagedCheckBox_;
+ }
+
+ @Override
+ public HasValue<Boolean> getUnstagedCheckBox()
+ {
+ return unstagedCheckBox_;
+ }
+
+ @Override
+ public LineTablePresenter.Display getLineTableDisplay()
+ {
+ return lines_;
+ }
+
+ @Override
+ public ChangelistTable getChangelistTable()
+ {
+ return changelist_;
+ }
+
+ @Override
+ public HasValue<Integer> getContextLines()
+ {
+ return listBoxAdapter_;
+ }
+
+ @Override
+ public HasClickHandlers getOverrideSizeWarningButton()
+ {
+ return overrideSizeWarning_;
+ }
+
+ @Override
+ public void showSizeWarning(long sizeInBytes)
+ {
+ overrideSizeWarning_.setSize(sizeInBytes);
+ diffScroll_.setWidget(overrideSizeWarning_);
+ }
+
+ @Override
+ public void hideSizeWarning()
+ {
+ diffScroll_.setWidget(lines_);
+ }
+
+ @Override
+ public void showContextMenu(final int clientX,
+ final int clientY,
+ Command openSelectedCommand)
+ {
+ final ToolbarPopupMenu menu = new ToolbarPopupMenu();
+
+ MenuItem stageMenu = new MenuItem(
+ AppCommand.formatMenuLabel(RES.stage(), "Stage", ""),
+ true,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ stageFilesButton_.click();
+ }
+
+ });
+ if (stageFilesButton_.isEnabled())
+ {
+ menu.addItem(stageMenu);
+ menu.addSeparator();
+ }
+
+
+
+ MenuItem revertMenu = new MenuItem(
+ AppCommand.formatMenuLabel(RES.discard(), "Revert...", ""),
+ true,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ revertFilesButton_.click();
+ }
+
+ });
+ if (revertFilesButton_.isEnabled())
+ menu.addItem(revertMenu);
+
+ MenuItem ignoreMenu = new MenuItem(
+ AppCommand.formatMenuLabel(RES.ignore(), "Ignore...", ""),
+ true,
+ new Command() {
+ @Override
+ public void execute()
+ {
+ ignoreButton_.click();
+ }
+
+ });
+ if (ignoreButton_.isEnabled())
+ menu.addItem(ignoreMenu);
+
+ menu.addSeparator();
+ MenuItem openMenu = new MenuItem(
+ AppCommand.formatMenuLabel(null, "Open File", ""),
+ true,
+ openSelectedCommand);
+ menu.addItem(openMenu);
+
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ menu.setPopupPosition(clientX, clientY);
+ }
+ });
+
+ }
+
+ @Override
+ public void onShow()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ changelist_.focus();
+ }
+ });
+ }
+
+ @UiField(provided = true)
+ SplitLayoutPanel splitPanel_;
+ @UiField(provided = true)
+ SplitLayoutPanel splitPanelCommit_;
+ @UiField(provided = true)
+ ChangelistTable changelist_;
+ @UiField(provided = true)
+ ThemedButton commitButton_;
+ @UiField
+ RadioButton stagedCheckBox_;
+ @UiField
+ RadioButton unstagedCheckBox_;
+ @UiField(provided = true)
+ LineTableView lines_;
+ @UiField
+ ListBox contextLines_;
+ @UiField
+ Toolbar topToolbar_;
+ @UiField
+ Toolbar diffToolbar_;
+ @UiField
+ TextArea commitMessage_;
+ @UiField
+ CheckBox commitIsAmend_;
+ @UiField
+ ScrollPanel diffScroll_;
+ @UiField
+ FlowPanel diffViewOptions_;
+ @UiField
+ HorizontalPanel toolbarWrapper_;
+
+ private ListBoxAdapter listBoxAdapter_;
+
+ private ToolbarButton stageFilesButton_;
+ private ToolbarButton revertFilesButton_;
+ private ToolbarButton ignoreButton_;
+ private ToolbarButton stageAllButton_;
+ private ToolbarButton discardAllButton_;
+ private ToolbarButton unstageAllButton_;
+ @SuppressWarnings("unused")
+ private final FileTypeRegistry fileTypeRegistry_;
+ private LeftRightToggleButton switchViewButton_;
+
+ private SizeWarningWidget overrideSizeWarning_;
+
+ private static final Resources RES = GWT.create(Resources.class);
+ static {
+ RES.styles().ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.ui.xml
new file mode 100644
index 0000000..587388d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPanel.ui.xml
@@ -0,0 +1,78 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:vcs='urn:import:org.rstudio.studio.client.workbench.views.vcs.common'
+ xmlns:vcs_diff='urn:import:org.rstudio.studio.client.workbench.views.vcs.common.diff'
+ xmlns:rs_widget='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.workbench.views.vcs.git.dialog.GitReviewPanel.Resources"/>
+
+ <g:SplitLayoutPanel ui:field="splitPanel_" styleName="{res.styles.splitPanel}">
+ <g:north size="230">
+ <g:DockLayoutPanel>
+ <g:north size="28">
+ <g:SimplePanel styleName="{res.styles.toolbarWrapper}">
+ <rs_widget:Toolbar ui:field="topToolbar_"/>
+ </g:SimplePanel>
+ </g:north>
+ <g:center>
+ <g:SplitLayoutPanel ui:field="splitPanelCommit_" styleName="{res.styles.splitPanelCommit}">
+ <g:east size="400">
+ <g:LayoutPanel>
+ <g:layer left="6px" right="6px" top="4px" height="20px">
+ <g:Label text="Commit message"/>
+ </g:layer>
+ <g:layer left="6px" right="6px" top="20px" bottom="34px">
+ <g:TextArea ui:field="commitMessage_" styleName="{res.styles.commitMessage}"/>
+ </g:layer>
+ <g:layer left="6px" right="6px" bottom="0" height="30px">
+ <g:FlowPanel>
+ <g:CheckBox ui:field="commitIsAmend_" text="Amend previous commit"/>
+ <rs_widget:ThemedButton ui:field="commitButton_"/>
+ </g:FlowPanel>
+ </g:layer>
+ </g:LayoutPanel>
+ </g:east>
+ <g:center>
+ <vcs:ChangelistTable styleName="{res.styles.whitebg}" ui:field="changelist_" width="100%" height="100%"/>
+ </g:center>
+ </g:SplitLayoutPanel>
+ </g:center>
+ </g:DockLayoutPanel>
+ </g:north>
+ <g:center>
+ <g:DockLayoutPanel>
+ <g:north size="28">
+ <g:HorizontalPanel ui:field="toolbarWrapper_" styleName="{res.styles.toolbarWrapper}">
+ <g:FlowPanel ui:field="diffViewOptions_" styleName="{res.styles.diffViewOptions}">
+ <g:Label text="Show" styleName="{res.styles.stagedLabel}"/>
+ <g:RadioButton ui:field="stagedCheckBox_"
+ name="ReviewPanelShow"
+ text="Staged"
+ styleName="{res.styles.staged}"/>
+ <g:RadioButton ui:field="unstagedCheckBox_"
+ name="ReviewPanelShow"
+ text="Unstaged"
+ checked="true"
+ styleName="{res.styles.unstaged}"/>
+ <g:Label text="Context" styleName="{res.styles.stagedLabel}"/>
+ <g:ListBox ui:field="contextLines_" visibleItemCount="1" selectedIndex="0">
+ <g:item value="5">5 line</g:item>
+ <g:item value="10">10 line</g:item>
+ <g:item value="25">25 line</g:item>
+ <g:item value="50">50 line</g:item>
+ <g:item value="-1">All lines</g:item>
+ </g:ListBox>
+ </g:FlowPanel>
+ <rs_widget:Toolbar ui:field="diffToolbar_"/>
+ </g:HorizontalPanel>
+ </g:north>
+ <g:center>
+ <g:ScrollPanel ui:field="diffScroll_" styleName="{res.styles.whitebg}" width="100%" height="100%">
+ <vcs_diff:LineTableView ui:field="lines_" width="100%"/>
+ </g:ScrollPanel>
+ </g:center>
+ </g:DockLayoutPanel>
+ </g:center>
+ </g:SplitLayoutPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPresenter.java
new file mode 100644
index 0000000..6440fad
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/dialog/GitReviewPresenter.java
@@ -0,0 +1,814 @@
+/*
+ * GitReviewPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git.dialog;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasAttachHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.RowCountChangeEvent;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.core.client.widget.DoubleClickState;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.SuperDevMode;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.console.ProcessExitEvent;
+import org.rstudio.studio.client.common.vcs.DiffResult;
+import org.rstudio.studio.client.common.vcs.GitServerOperations;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.IntStateValue;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionEvent.Action;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.git.GitPresenterCore;
+import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
+
+import java.util.ArrayList;
+
+public class GitReviewPresenter implements ReviewPresenter
+{
+ public interface Binder extends CommandBinder<Commands, GitReviewPresenter> {}
+
+ public interface Display extends IsWidget, HasAttachHandlers
+ {
+ ArrayList<String> getSelectedPaths();
+ ArrayList<String> getSelectedDiscardablePaths();
+ void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths);
+
+ HasValue<Boolean> getStagedCheckBox();
+ HasValue<Boolean> getUnstagedCheckBox();
+ LineTablePresenter.Display getLineTableDisplay();
+ ChangelistTable getChangelistTable();
+ HasValue<Integer> getContextLines();
+
+ HasClickHandlers getSwitchViewButton();
+ HasClickHandlers getStageFilesButton();
+ HasClickHandlers getRevertFilesButton();
+ void setFilesCommandsEnabled(boolean enabled);
+ HasClickHandlers getIgnoreButton();
+ HasClickHandlers getStageAllButton();
+ HasClickHandlers getDiscardAllButton();
+ HasClickHandlers getUnstageAllButton();
+
+ HasText getCommitMessage();
+ HasClickHandlers getCommitButton();
+
+ HasValue<Boolean> getCommitIsAmend();
+
+ void setData(ArrayList<ChunkOrLine> lines, PatchMode patchMode);
+
+ HasClickHandlers getOverrideSizeWarningButton();
+ void showSizeWarning(long sizeInBytes);
+ void hideSizeWarning();
+
+ void showContextMenu(int clientX,
+ int clientY,
+ Command openSelectedCommand);
+
+ void onShow();
+
+ void setShowActions(boolean showActions);
+ }
+
+ private class ApplyPatchClickHandler implements ClickHandler, Command
+ {
+ public ApplyPatchClickHandler(PatchMode patchMode,
+ boolean reverse)
+ {
+ patchMode_ = patchMode;
+ reverse_ = reverse;
+ }
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ execute();
+ }
+
+ @Override
+ public void execute()
+ {
+ ArrayList<String> paths = view_.getSelectedPaths();
+
+ if (patchMode_ == PatchMode.Stage && !reverse_)
+ server_.gitStage(paths, new SimpleRequestCallback<Void>("Stage"));
+ else if (patchMode_ == PatchMode.Stage && reverse_)
+ server_.gitUnstage(paths,
+ new SimpleRequestCallback<Void>("Unstage"));
+ else if (patchMode_ == PatchMode.Working && reverse_)
+ server_.gitDiscard(paths,
+ new SimpleRequestCallback<Void>("Discard"));
+ else
+ throw new RuntimeException("Unknown patchMode and reverse combo");
+
+ view_.getChangelistTable().moveSelectionDown();
+ }
+
+ private final PatchMode patchMode_;
+ private final boolean reverse_;
+ }
+
+ private class ApplyPatchHandler implements DiffChunkActionHandler,
+ DiffLinesActionHandler
+ {
+ @Override
+ public void onDiffChunkAction(DiffChunkActionEvent event)
+ {
+ ArrayList<DiffChunk> chunks = new ArrayList<DiffChunk>();
+ chunks.add(event.getDiffChunk());
+ doPatch(event.getAction(), event.getDiffChunk().getLines(), chunks);
+ }
+
+ @Override
+ public void onDiffLinesAction(DiffLinesActionEvent event)
+ {
+ ArrayList<Line> lines = view_.getLineTableDisplay().getSelectedLines();
+ doPatch(event.getAction(), lines, activeChunks_);
+ }
+
+ private void doPatch(Action action,
+ ArrayList<Line> lines,
+ ArrayList<DiffChunk> chunks)
+ {
+ boolean reverse;
+ PatchMode patchMode;
+ switch (action)
+ {
+ case Stage:
+ reverse = false;
+ patchMode = GitServerOperations.PatchMode.Stage;
+ break;
+ case Unstage:
+ reverse = true;
+ patchMode = GitServerOperations.PatchMode.Stage;
+ break;
+ case Discard:
+ reverse = true;
+ patchMode = GitServerOperations.PatchMode.Working;
+ break;
+ default:
+ throw new IllegalArgumentException("Unhandled diff chunk action");
+ }
+
+ applyPatch(chunks, lines, reverse, patchMode);
+ }
+
+ }
+
+ @Inject
+ public GitReviewPresenter(GitPresenterCore gitPresenterCore,
+ GitServerOperations server,
+ Display view,
+ Binder binder,
+ Commands commands,
+ final EventBus events,
+ final GitState gitState,
+ final Session session,
+ final GlobalDisplay globalDisplay,
+ VCSFileOpener vcsFileOpener)
+ {
+ gitPresenterCore_ = gitPresenterCore;
+ server_ = server;
+ view_ = view;
+ globalDisplay_ = globalDisplay;
+ gitState_ = gitState;
+ vcsFileOpener_ = vcsFileOpener;
+
+ binder.bind(commands, this);
+
+ new WidgetHandlerRegistration(view.asWidget())
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return gitState_.addVcsRefreshHandler(new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ if (event.getReason() == Reason.VcsOperation)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ updateDiff(true);
+
+ initialized_ = true;
+ }
+ });
+ }
+ }
+ }, false);
+ }
+ };
+
+ new WidgetHandlerRegistration(view.asWidget())
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return events.addHandler(FileChangeEvent.TYPE, new FileChangeHandler()
+ {
+ @Override
+ public void onFileChange(FileChangeEvent event)
+ {
+ ArrayList<StatusAndPath> paths = view_.getChangelistTable()
+ .getSelectedItems();
+ if (paths.size() != 1)
+ {
+ clearDiff();
+ return;
+ }
+
+ StatusAndPath vcsStatus = StatusAndPath.fromInfo(
+ event.getFileChange().getFile().getGitStatus());
+ if (paths.get(0).getRawPath().equals(vcsStatus.getRawPath()))
+ {
+ gitState.refresh(false);
+ }
+ }
+ });
+ }
+ };
+
+ view_.getChangelistTable().addSelectionChangeHandler(new SelectionChangeEvent.Handler()
+ {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent event)
+ {
+ overrideSizeWarning_ = false;
+ view_.setFilesCommandsEnabled(view_.getSelectedPaths().size() > 0);
+ if (initialized_)
+ updateDiff(true);
+ }
+ });
+ view_.getChangelistTable().addRowCountChangeHandler(new RowCountChangeEvent.Handler()
+ {
+ @Override
+ public void onRowCountChange(RowCountChangeEvent event)
+ {
+ // This is necessary because during initial load, the selection
+ // model has its selection set before any items are loaded into
+ // the table (so therefore view_.getSelectedPaths().size() is always
+ // 0, and the files commands are not enabled until selection changes
+ // again). By updating the files commands' enabled state on row
+ // count change as well, we can make sure they get enabled.
+ view_.setFilesCommandsEnabled(view_.getSelectedPaths().size() > 0);
+ }
+ });
+ view_.getChangelistTable().addKeyDownHandler(new KeyDownHandler()
+ {
+ @Override
+ public void onKeyDown(KeyDownEvent event)
+ {
+ // Space toggles the staged/unstaged state of the current selection.
+ // Enter does the same plus moves the selection down.
+
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER
+ || event.getNativeKeyCode() == ' ')
+ {
+ getTable().toggleStaged(
+ event.getNativeKeyCode() == KeyCodes.KEY_ENTER);
+ event.preventDefault();
+ }
+ }
+ });
+ view_.getChangelistTable().addMouseDownHandler(new MouseDownHandler()
+ {
+ private DoubleClickState dblClick = new DoubleClickState();
+ @Override
+ public void onMouseDown(MouseDownEvent event)
+ {
+ if (dblClick.checkForDoubleClick(event.getNativeEvent()))
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ getTable().toggleStaged(false);
+ }
+ }
+ });
+
+ view_.getChangelistTable().addContextMenuHandler(new ContextMenuHandler()
+ {
+ @Override
+ public void onContextMenu(ContextMenuEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ view_.showContextMenu(nativeEvent.getClientX(),
+ nativeEvent.getClientY(),
+ new Command() {
+ @Override
+ public void execute()
+ {
+ openSelectedFiles();
+ }
+ });
+
+ }
+ });
+
+ view_.getStageFilesButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ ArrayList<String> paths = view_.getSelectedPaths();
+ if (paths.size() == 0)
+ return;
+ server_.gitStage(paths, new SimpleRequestCallback<Void>());
+
+ view_.getChangelistTable().focus();
+ }
+ });
+
+ view_.getRevertFilesButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ final ArrayList<String> paths = view_.getSelectedPaths();
+ if (paths.size() == 0)
+ return;
+ String noun = paths.size() == 1 ? "file" : "files";
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Revert Changes",
+ "Changes to the selected " + noun + " will be lost, including " +
+ "staged changes.\n\nAre you sure you want to continue?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ view_.getChangelistTable().selectNextUnselectedItem();
+
+ server_.gitRevert(
+ paths,
+ new SimpleRequestCallback<Void>("Revert Changes"));
+
+ view_.getChangelistTable().focus();
+ }
+ },
+ false);
+ }
+ });
+
+ view_.getIgnoreButton().addClickHandler(new ClickHandler() {
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ gitPresenterCore_.onVcsIgnore(
+ view_.getChangelistTable().getSelectedItems());
+ }
+ });
+
+ view_.getCommitIsAmend().addValueChangeHandler(new ValueChangeHandler<Boolean>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> booleanValueChangeEvent)
+ {
+ server_.gitHistory("", null, 0, 1, null, new ServerRequestCallback<RpcObjectList<CommitInfo>>() {
+ @Override
+ public void onResponseReceived(RpcObjectList<CommitInfo> response)
+ {
+ if (response.length() == 1)
+ {
+ String description = response.get(0).getDescription();
+
+ if (view_.getCommitIsAmend().getValue())
+ {
+ if (view_.getCommitMessage().getText().length() == 0)
+ view_.getCommitMessage().setText(description);
+ }
+ else
+ {
+ if (view_.getCommitMessage().getText().equals(description))
+ view_.getCommitMessage().setText("");
+ }
+ }
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ }
+ });
+ }
+ });
+
+ view_.getStageAllButton().addClickHandler(
+ new ApplyPatchClickHandler(PatchMode.Stage, false));
+ view_.getDiscardAllButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ String which = view_.getLineTableDisplay().getSelectedLines().size() == 0
+ ? "All unstaged"
+ : "The selected";
+ globalDisplay.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Discard All",
+ which + " changes in this file will be " +
+ "lost.\n\nAre you sure you want to continue?",
+ new Operation() {
+ @Override
+ public void execute() {
+ new ApplyPatchClickHandler(PatchMode.Working, true).execute();
+ }
+ },
+ false);
+ }
+ });
+ view_.getUnstageAllButton().addClickHandler(
+ new ApplyPatchClickHandler(PatchMode.Stage, true));
+ view_.getStagedCheckBox().addValueChangeHandler(
+ new ValueChangeHandler<Boolean>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event)
+ {
+ if (initialized_)
+ updateDiff(false);
+ }
+ });
+ view_.getLineTableDisplay().addDiffChunkActionHandler(new ApplyPatchHandler());
+ view_.getLineTableDisplay().addDiffLineActionHandler(new ApplyPatchHandler());
+
+ new IntStateValue(MODULE_GIT, KEY_CONTEXT_LINES, ClientState.PERSISTENT,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(Integer value)
+ {
+ if (value != null)
+ view_.getContextLines().setValue(value);
+ }
+
+ @Override
+ protected Integer getValue()
+ {
+ return view_.getContextLines().getValue();
+ }
+ };
+
+ view_.getContextLines().addValueChangeHandler(new ValueChangeHandler<Integer>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Integer> event)
+ {
+ updateDiff(false);
+ }
+ });
+
+ view_.getCommitButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ server_.gitCommit(
+ view_.getCommitMessage().getText(),
+ view_.getCommitIsAmend().getValue(),
+ false,
+ new SimpleRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(ConsoleProcess proc)
+ {
+ proc.addProcessExitHandler(new ProcessExitEvent.Handler()
+ {
+ @Override
+ public void onProcessExit(ProcessExitEvent event)
+ {
+ if (event.getExitCode() == 0)
+ {
+ view_.getCommitMessage().setText("");
+ if (view_.getCommitIsAmend().getValue())
+ view_.getCommitIsAmend().setValue(false);
+ }
+ }
+ });
+ new ConsoleProgressDialog(proc, server_).showModal();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ if (error.getClientInfo() != null
+ && error.getClientInfo().isString() != null)
+ {
+ globalDisplay_.showErrorMessage(
+ "Commit",
+ error.getClientInfo().isString().stringValue());
+ }
+ else
+ {
+ super.onError(error);
+ }
+ }
+ });
+ }
+ });
+
+ view_.getOverrideSizeWarningButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ overrideSizeWarning_ = true;
+ updateDiff(false);
+ }
+ });
+ }
+
+ private GitChangelistTable getTable()
+ {
+ return (GitChangelistTable) view_.getChangelistTable();
+ }
+
+ private void applyPatch(ArrayList<DiffChunk> chunks,
+ ArrayList<Line> lines,
+ boolean reverse,
+ PatchMode patchMode)
+ {
+ chunks = new ArrayList<DiffChunk>(chunks);
+
+ if (reverse)
+ {
+ for (int i = 0; i < chunks.size(); i++)
+ chunks.set(i, chunks.get(i).reverse());
+ lines = Line.reverseLines(lines);
+ }
+
+ String path = view_.getChangelistTable().getSelectedPaths().get(0);
+ if (path.indexOf(" -> ") >= 0)
+ path = path.substring(path.indexOf(" -> ") + " -> ".length());
+ UnifiedEmitter emitter = new UnifiedEmitter(path);
+ for (DiffChunk chunk : chunks)
+ emitter.addContext(chunk);
+ emitter.addDiffs(lines);
+ String patch = emitter.createPatch(true);
+
+ softModeSwitch_ = true;
+ server_.gitApplyPatch(patch, patchMode,
+ StringUtil.notNull(currentSourceEncoding_),
+ new SimpleRequestCallback<Void>());
+ }
+
+ private void updateDiff(boolean allowModeSwitch)
+ {
+ view_.hideSizeWarning();
+
+ final ArrayList<StatusAndPath> paths = view_.getChangelistTable().getSelectedItems();
+ if (paths.size() != 1)
+ {
+ clearDiff();
+ return;
+ }
+
+ final StatusAndPath item = paths.get(0);
+
+ if (allowModeSwitch)
+ {
+ if (!softModeSwitch_)
+ {
+ boolean staged = item.getStatus().charAt(0) != ' ' &&
+ item.getStatus().charAt(1) == ' ';
+ HasValue<Boolean> checkbox = staged ?
+ view_.getStagedCheckBox() :
+ view_.getUnstagedCheckBox();
+ if (!checkbox.getValue())
+ {
+ clearDiff();
+ checkbox.setValue(true, true);
+ }
+ }
+ else
+ {
+ if (view_.getStagedCheckBox().getValue()
+ && (item.getStatus().charAt(0) == ' ' || item.getStatus().charAt(0) == '?'))
+ {
+ clearDiff();
+ view_.getUnstagedCheckBox().setValue(true, true);
+ }
+ else if (view_.getUnstagedCheckBox().getValue()
+ && item.getStatus().charAt(1) == ' ')
+ {
+ clearDiff();
+ view_.getStagedCheckBox().setValue(true, true);
+ }
+ }
+ }
+ softModeSwitch_ = false;
+
+ if (!item.getPath().equals(currentFilename_))
+ {
+ clearDiff();
+ currentFilename_ = item.getPath();
+ }
+
+ diffInvalidation_.invalidate();
+ final Token token = diffInvalidation_.getInvalidationToken();
+
+ final PatchMode patchMode = view_.getStagedCheckBox().getValue()
+ ? PatchMode.Stage
+ : PatchMode.Working;
+ server_.gitDiffFile(
+ item.getPath(),
+ patchMode,
+ view_.getContextLines().getValue(),
+ overrideSizeWarning_,
+ new SimpleRequestCallback<DiffResult>("Diff Error")
+ {
+ @Override
+ public void onResponseReceived(DiffResult diffResult)
+ {
+ if (token.isInvalid())
+ return;
+
+ // Use lastResponse_ to prevent unnecessary flicker
+ String response = diffResult.getDecodedValue();
+ if (response.equals(currentResponse_))
+ return;
+ currentResponse_ = response;
+ currentSourceEncoding_ = diffResult.getSourceEncoding();
+
+ UnifiedParser parser = new UnifiedParser(response);
+ parser.nextFilePair();
+
+ ArrayList<ChunkOrLine> allLines = new ArrayList<ChunkOrLine>();
+
+ activeChunks_.clear();
+ for (DiffChunk chunk;
+ null != (chunk = parser.nextChunk());)
+ {
+ activeChunks_.add(chunk);
+ allLines.add(new ChunkOrLine(chunk));
+ for (Line line : chunk.getLines())
+ allLines.add(new ChunkOrLine(line));
+ }
+
+ view_.setShowActions(
+ !"??".equals(item.getStatus()) &&
+ !"UU".equals(item.getStatus()));
+ view_.setData(allLines, patchMode);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ JSONNumber size = error.getClientInfo().isNumber();
+ if (size != null)
+ view_.showSizeWarning((long) size.doubleValue());
+ else
+ super.onError(error);
+ }
+ });
+ }
+
+ private void clearDiff()
+ {
+ softModeSwitch_ = false;
+ currentResponse_ = null;
+ currentFilename_ = null;
+ view_.getLineTableDisplay().clear();
+ }
+
+ private void openSelectedFiles()
+ {
+ vcsFileOpener_.openFiles(view_.getChangelistTable().getSelectedItems());
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Override
+ public HandlerRegistration addSwitchViewHandler(
+ final SwitchViewEvent.Handler h)
+ {
+ return view_.getSwitchViewButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ h.onSwitchView(new SwitchViewEvent());
+ }
+ });
+ }
+
+ @Override
+ public void setSelectedPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ view_.setSelectedStatusAndPaths(selectedPaths);
+ }
+
+ public void onShow()
+ {
+ // Ensure that we're fresh
+ gitState_.refresh();
+
+ view_.onShow();
+ }
+
+ @Handler
+ public void onVcsPull()
+ {
+ gitPresenterCore_.onVcsPull();
+ }
+
+ @Handler
+ public void onVcsPush()
+ {
+ gitPresenterCore_.onVcsPush();
+ }
+
+ @Handler
+ public void onVcsIgnore()
+ {
+ gitPresenterCore_.onVcsIgnore(
+ view_.getChangelistTable().getSelectedItems());
+ }
+
+ @Handler
+ public void onRefreshSuperDevMode()
+ {
+ SuperDevMode.reload();
+ }
+
+ private final Invalidation diffInvalidation_ = new Invalidation();
+ private final GitServerOperations server_;
+ private final GitPresenterCore gitPresenterCore_;
+ private final Display view_;
+ private final GlobalDisplay globalDisplay_;
+ private ArrayList<DiffChunk> activeChunks_ = new ArrayList<DiffChunk>();
+ private String currentResponse_;
+ private String currentSourceEncoding_;
+ private String currentFilename_;
+ // Hack to prevent us flipping to unstaged view when a line is unstaged
+ // from staged view
+ private boolean softModeSwitch_;
+ private GitState gitState_;
+ private final VCSFileOpener vcsFileOpener_;
+ private boolean initialized_;
+ private static final String MODULE_GIT = "vcs_git";
+ private static final String KEY_CONTEXT_LINES = "context_lines";
+
+ private boolean overrideSizeWarning_ = false;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusAdded.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusAdded.png
new file mode 100755
index 0000000..b5f87dd
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusAdded.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusCopied.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusCopied.png
new file mode 100644
index 0000000..0aff892
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusCopied.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusDeleted.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusDeleted.png
new file mode 100755
index 0000000..443a8e2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusDeleted.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusModified.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusModified.png
new file mode 100755
index 0000000..4ae65f7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusModified.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusNone.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusNone.png
new file mode 100644
index 0000000..f72bf9d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusNone.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusRenamed.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusRenamed.png
new file mode 100644
index 0000000..f4f3ad2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusRenamed.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUnmerged.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUnmerged.png
new file mode 100644
index 0000000..eed0448
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUnmerged.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUntracked.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUntracked.png
new file mode 100644
index 0000000..f6df2f8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/images/statusUntracked.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/model/GitState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/model/GitState.java
new file mode 100644
index 0000000..d3e6ef2
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/git/model/GitState.java
@@ -0,0 +1,113 @@
+/*
+ * GitState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.git.model;
+
+import com.google.gwt.user.client.Command;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.vcs.*;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.VcsState;
+
+ at Singleton
+public class GitState extends VcsState
+{
+ @Inject
+ public GitState(GitServerOperations server,
+ EventBus eventBus,
+ GlobalDisplay globalDisplay,
+ Session session)
+ {
+ super(eventBus, globalDisplay, session);
+ server_ = server;
+ }
+
+ public BranchesInfo getBranchInfo()
+ {
+ return branches_;
+ }
+
+ public boolean hasRemote()
+ {
+ return getRemoteBranchInfo() != null;
+ }
+
+ public RemoteBranchInfo getRemoteBranchInfo()
+ {
+ return remoteBranchInfo_;
+ }
+
+ @Override
+ protected boolean isInitialized()
+ {
+ return branches_ != null;
+ }
+
+ @Override
+ protected StatusAndPathInfo getStatusFromFile(FileSystemItem file)
+ {
+ return file.getGitStatus();
+ }
+
+ @Override
+ protected boolean needsFullRefresh(FileSystemItem file)
+ {
+ return file.getName().equalsIgnoreCase(".gitignore");
+ }
+
+ public void refresh(boolean showError)
+ {
+ refresh(showError, null);
+ }
+
+ public void refresh(final boolean showError, final Command onCompleted)
+ {
+ server_.gitAllStatus(new ServerRequestCallback<AllStatus>()
+ {
+ @Override
+ public void onResponseReceived(AllStatus response)
+ {
+ status_ = StatusAndPath.fromInfos(response.getStatus());
+ branches_ = response.getBranches();
+ remoteBranchInfo_ = response.getRemoteBranchInfo();
+ handlers_.fireEvent(new VcsRefreshEvent(Reason.VcsOperation));
+ if (onCompleted != null)
+ onCompleted.execute();
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ if (showError)
+ globalDisplay_.showErrorMessage("Error",
+ error.getUserMessage());
+ }
+ });
+ }
+
+ private BranchesInfo branches_;
+ private RemoteBranchInfo remoteBranchInfo_;
+ private final GitServerOperations server_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTable.java
new file mode 100644
index 0000000..a956e65
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTable.java
@@ -0,0 +1,75 @@
+/*
+ * SVNChangelistTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.cellview.client.Column;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+
+import java.util.Comparator;
+
+public class SVNChangelistTable extends ChangelistTable
+{
+ public SVNChangelistTable()
+ {
+ }
+
+ public void setChangelistColumnVisible(boolean visible)
+ {
+ if ((changelistColumn_ != null) != visible)
+ {
+ if (!visible)
+ {
+ table_.removeColumn(changelistColumn_);
+ changelistColumn_ = null;
+ }
+ else
+ {
+ changelistColumn_ = new Column<StatusAndPath, String>(
+ new NotEditingTextCell())
+ {
+ @Override
+ public String getValue(StatusAndPath object)
+ {
+ return object.getChangelist();
+ }
+ };
+ changelistColumn_.setSortable(true);
+ sortHandler_.setComparator(changelistColumn_, new Comparator<StatusAndPath>()
+ {
+ @Override
+ public int compare(StatusAndPath a,
+ StatusAndPath b)
+ {
+ return StringUtil.notNull(a.getChangelist())
+ .compareToIgnoreCase(
+ b.getChangelist());
+ }
+ });
+ table_.addColumn(changelistColumn_, "Changelist");
+ }
+ }
+ }
+
+ @Override
+ protected SafeHtmlRenderer<String> getStatusRenderer()
+ {
+ return new SVNStatusRenderer();
+ }
+
+ private Column<StatusAndPath, String> changelistColumn_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTablePresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTablePresenter.java
new file mode 100644
index 0000000..af49b99
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNChangelistTablePresenter.java
@@ -0,0 +1,81 @@
+/*
+ * SVNChangelistTablePresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+import java.util.ArrayList;
+
+public class SVNChangelistTablePresenter
+{
+ @Inject
+ public SVNChangelistTablePresenter(final SVNChangelistTable view,
+ final SVNState svnState)
+ {
+ view_ = view;
+
+ svnState.bindRefreshHandler(view, new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ ArrayList<StatusAndPath> items =
+ new ArrayList<StatusAndPath>(svnState.getStatus());
+
+ boolean usesChangelists = false;
+ for (int i = items.size()-1; i >= 0; i--)
+ {
+ StatusAndPath item = items.get(i);
+
+ if (!StringUtil.isNullOrEmpty(item.getChangelist()))
+ usesChangelists = true;
+
+ if (rejectItem(item))
+ items.remove(i);
+ }
+ view.setItems(items);
+ view.setChangelistColumnVisible(usesChangelists);
+ }
+ });
+
+ }
+
+ protected boolean rejectItem(StatusAndPath item)
+ {
+ return false;
+ }
+
+ public void setSelectFirstItemByDefault(boolean selectFirstItemByDefault)
+ {
+ view_.setSelectFirstItemByDefault(selectFirstItemByDefault);
+ }
+
+ public SVNChangelistTable getView()
+ {
+ return view_;
+ }
+
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ return view_.getSelectedItems();
+ }
+
+ private final SVNChangelistTable view_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNCommandHandler.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNCommandHandler.java
new file mode 100644
index 0000000..28d2269
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNCommandHandler.java
@@ -0,0 +1,363 @@
+/*
+ * SVNCommandHandler.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import java.util.ArrayList;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.core.client.widget.OperationWithInput;
+import org.rstudio.studio.client.RStudioGinjector;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.vcs.ProcessResult;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.ignore.Ignore;
+import org.rstudio.studio.client.common.vcs.ignore.IgnoreList;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.common.ProcessCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
+import org.rstudio.studio.client.workbench.views.vcs.svn.commit.SVNCommitDialog;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.Command;
+
+public class SVNCommandHandler
+{
+ public interface Binder extends CommandBinder<Commands, SVNCommandHandler>
+ {
+ }
+
+ public SVNCommandHandler(SVNPresenterDisplay display,
+ GlobalDisplay globalDisplay,
+ Commands commands,
+ SVNServerOperations server,
+ SVNState svnState,
+ VCSFileOpener vcsFileOpener)
+ {
+ display_ = display;
+ globalDisplay_ = globalDisplay;
+ commands_ = commands;
+ server_ = server;
+ svnState_ = svnState;
+ vcsFileOpener_ = vcsFileOpener;
+ GWT.<Binder>create(Binder.class).bind(commands, this);
+
+ RStudioGinjector.INSTANCE.injectMembers(this);
+ }
+
+ @Inject
+ void initialize(Provider<SVNCommitDialog> pCommitDialog,
+ Provider<Ignore> pIgnore)
+ {
+ pCommitDialog_ = pCommitDialog;
+ pIgnore_ = pIgnore;
+ }
+
+ public void setFilesCommandsEnabled(boolean enabled)
+ {
+ commands_.vcsAddFiles().setEnabled(enabled);
+ commands_.vcsRemoveFiles().setEnabled(enabled);
+ commands_.vcsRevert().setEnabled(enabled);
+ commands_.vcsIgnore().setEnabled(enabled);
+ commands_.vcsResolve().setEnabled(enabled);
+ }
+
+ // onVcsPull and onVcsCommit and not direct command handlers because they
+ // are handled globally in the main frame (by BaseVcsPresenter or the
+ // satellite frame)
+
+ public void onVcsPull()
+ {
+ server_.svnUpdate(new SimpleRequestCallback<ConsoleProcess>()
+ {
+ @Override
+ public void onResponseReceived(ConsoleProcess response)
+ {
+ new ConsoleProgressDialog(response, server_).showModal();
+ }
+ });
+ }
+
+ public void onVcsCommit()
+ {
+ pCommitDialog_.get().showModal();
+ }
+
+ public void onVcsIgnore()
+ {
+ // special case for a single directory with property changes
+ ArrayList<StatusAndPath> items = display_.getSelectedItems();
+ if (items.size() == 1)
+ {
+ StatusAndPath item = items.get(0);
+ if (item.isDirectory() && item.getStatus().equals("M"))
+ {
+ String path = item.getPath();
+ if (path.equals("."))
+ path = "";
+ IgnoreList ignoreList = new IgnoreList(path,
+ new ArrayList<String>());
+ pIgnore_.get().showDialog(ignoreList, ignoreStrategy_);
+ return;
+ }
+ }
+
+ // standard case
+ ArrayList<String> paths = getPathArray();
+ pIgnore_.get().showDialog(paths, ignoreStrategy_);
+
+ }
+
+ private final Ignore.Strategy ignoreStrategy_ = new Ignore.Strategy() {
+
+ @Override
+ public String getDialogCaption()
+ {
+ return "SVN Ignore";
+ }
+
+ @Override
+ public String getIgnoresCaption()
+ {
+ return "svn:ignore";
+ }
+
+ @Override
+ public String getHelpLinkName()
+ {
+ return "svn_ignore_help";
+ }
+
+ @Override
+ public Filter getFilter()
+ {
+ return null;
+ }
+
+
+ @Override
+ public void getIgnores(String path,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ server_.svnGetIgnores(path, requestCallback);
+ }
+
+ @Override
+ public void setIgnores(String path, String ignores,
+ ServerRequestCallback<ProcessResult> requestCallback)
+ {
+ server_.svnSetIgnores(path, ignores, requestCallback);
+ }
+ };
+
+ @Handler
+ void onVcsOpen()
+ {
+ vcsFileOpener_.openFiles(display_.getSelectedItems());
+ }
+
+ @Handler
+ void onVcsRefresh()
+ {
+ display_.getChangelistTable().showProgress();
+ svnState_.refresh(true);
+ }
+
+ @Handler
+ void onVcsRefreshNoError()
+ {
+ display_.getChangelistTable().showProgress();
+ svnState_.refresh(false);
+ }
+
+ @Handler
+ void onVcsAddFiles()
+ {
+ ArrayList<String> paths = getPathArray();
+
+ if (paths.size() > 0)
+ server_.svnAdd(paths, new ProcessCallback("SVN Add"));
+ }
+
+ @Handler
+ void onVcsRemoveFiles()
+ {
+ ArrayList<String> paths = getPathArray();
+
+ if (paths.size() > 0)
+ server_.svnDelete(paths, new ProcessCallback("SVN Delete"));
+ }
+
+ @Handler
+ void onVcsRevert()
+ {
+ final ArrayList<String> paths = getPathArray();
+ if (paths.size() == 0)
+ return;
+
+ doRevert(paths, new Command() {
+ @Override
+ public void execute()
+ {
+ display_.getChangelistTable().selectNextUnselectedItem();
+ display_.getChangelistTable().focus();
+ }
+
+ });
+ }
+
+ @Handler
+ void onVcsResolve()
+ {
+ ArrayList<StatusAndPath> items = display_.getSelectedItems();
+
+ if (items.size() == 0)
+ return;
+
+ final ArrayList<String> paths = new ArrayList<String>();
+
+ boolean conflict = false;
+ for (StatusAndPath item : items)
+ {
+ paths.add(item.getPath());
+ if ("C".equals(item.getStatus()))
+ conflict = true;
+ else if (item.isDirectory())
+ conflict = true;
+ }
+
+ Operation resolveOperation = new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ new SVNResolveDialog(
+ paths.size(),
+ "Resolve",
+ new OperationWithInput<String>()
+ {
+ @Override
+ public void execute(String input)
+ {
+ server_.svnResolve(
+ input, paths,
+ new ProcessCallback("SVN Resolve"));
+ }
+ }).showModal();
+ }
+ };
+
+ if (conflict)
+ {
+ resolveOperation.execute();
+ }
+ else
+ {
+ String message =
+ (paths.size() > 1 ?
+ "None of the selected paths appear to have conflicts." :
+ "The selected path does not appear to have conflicts.") +
+ "\n\nDo you want to resolve anyway?";
+
+ globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_WARNING,
+ "No Conflicts Detected",
+ message,
+ resolveOperation,
+ true);
+ }
+ }
+
+ public void revertFile(FileSystemItem file)
+ {
+ // build an ArrayList<String> so we can call the core helper
+ ArrayList<String> revertList = new ArrayList<String>();
+ for (StatusAndPath item : svnState_.getStatus())
+ {
+ if (item.getRawPath().equals(file.getPath()))
+ {
+ revertList.add(item.getPath());
+ break;
+ }
+ }
+
+ if (revertList.size() > 0)
+ {
+ doRevert(revertList, null);
+ }
+ else
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "No Changes to Revert",
+ "There are no changes to the file \"" +
+ file.getName() + "\" to revert.");
+ }
+
+ }
+
+ private void doRevert(final ArrayList<String> revertList,
+ final Command onRevertConfirmed)
+ {
+ String noun = revertList.size() == 1 ? "file" : "files";
+ globalDisplay_.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Revert Changes",
+ "Changes to the selected " + noun + " will be reverted.\n\n" +
+ "Are you sure you want to continue?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ if (onRevertConfirmed != null)
+ onRevertConfirmed.execute();
+
+ server_.svnRevert(revertList,
+ new ProcessCallback("SVN Revert"));
+
+ }
+ },
+ false);
+ }
+
+
+ private ArrayList<String> getPathArray()
+ {
+ ArrayList<StatusAndPath> items = display_.getSelectedItems();
+ ArrayList<String> paths = new ArrayList<String>();
+ for (StatusAndPath item : items)
+ paths.add(item.getPath());
+ return paths;
+ }
+
+ private final SVNPresenterDisplay display_;
+ private final GlobalDisplay globalDisplay_;
+ private final Commands commands_;
+ private final SVNServerOperations server_;
+ private final SVNState svnState_;
+ private final VCSFileOpener vcsFileOpener_;
+ private Provider<SVNCommitDialog> pCommitDialog_;
+ private Provider<Ignore> pIgnore_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNDiffParser.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNDiffParser.java
new file mode 100644
index 0000000..475b5a4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNDiffParser.java
@@ -0,0 +1,209 @@
+/*
+ * SVNDiffParser.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.regex.Match;
+import org.rstudio.core.client.regex.Pattern;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.Line.Type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Provides parsing of SVN diffs, which may contain property changes. Property
+ * changes are treated as Info lines in an ignorable chunk, whereas item diffs
+ * are treated as usual using UnifiedParser.
+ */
+public class SVNDiffParser implements DiffParser
+{
+ private static class Section
+ {
+ private Section(boolean property, String filename, String data)
+ {
+ isProperty = property;
+ this.filename = filename;
+ this.data = data;
+ }
+
+ public boolean isProperty;
+ public String filename;
+ public String data;
+ }
+
+ public SVNDiffParser(String data)
+ {
+ Pattern sectionHeaderPattern = Pattern.create(
+ "^((Index: ([^\\r\\n]+)\\r?\\n=+)|(\\r?\\nProperty changes on: ([^\\r\\n]+)\\r?\\n_+))$");
+
+ ArrayList<String> sectionData = new ArrayList<String>();
+ ArrayList<Match> sectionMatches = new ArrayList<Match>();
+
+ int pos = 0;
+ int lastHeaderStart = -1;
+ Match m;
+ while (null != (m = sectionHeaderPattern.match(data, pos)))
+ {
+ sectionMatches.add(m);
+
+ if (lastHeaderStart >= 0)
+ sectionData.add(data.substring(lastHeaderStart, m.getIndex()));
+
+ lastHeaderStart = m.getIndex();
+ pos = m.getIndex() + m.getValue().length();
+ }
+ if (lastHeaderStart >= 0)
+ sectionData.add(data.substring(lastHeaderStart));
+
+ sections_ = new ArrayList<Section>();
+ for (int i = 0; i < sectionData.size(); i++)
+ {
+ sections_.add(parseSection(sectionMatches.get(i), sectionData.get(i)));
+ }
+ }
+
+ private Section parseSection(Match m, String sectionData)
+ {
+ String filename = m.getGroup(3) != null ? m.getGroup(3)
+ : m.getGroup(5);
+ if (filename == null)
+ {
+ throw new RuntimeException(
+ "Programmer error: Filename not found in diff section header");
+ }
+
+ boolean isProperty = m.getGroup(4) != null;
+
+ return new Section(isProperty, filename, sectionData);
+ }
+
+ @Override
+ public DiffFileHeader nextFilePair()
+ {
+ pendingDiffChunks_.clear();
+
+ if (sections_.size() == 0)
+ return null;
+
+ ArrayList<Section> sectionsToUse = new ArrayList<Section>();
+
+ sectionsToUse.add(sections_.remove(0));
+ String filename = sectionsToUse.get(0).filename;
+ while (sections_.size() > 0 && sections_.get(0).filename.equals(filename))
+ {
+ sectionsToUse.add(sections_.remove(0));
+ }
+
+ // Put the properties above the item diffs.
+ Collections.sort(sectionsToUse, new Comparator<Section>()
+ {
+ @Override
+ public int compare(Section a, Section b)
+ {
+ return a.isProperty == b.isProperty ? 0 :
+ a.isProperty ? -1 :
+ 1;
+ }
+ });
+
+ Section lastSection = null;
+ for (Section section : sectionsToUse)
+ {
+ if (section.isProperty)
+ {
+ String trimmed = StringUtil.trimBlankLines(section.data);
+ pendingDiffChunks_.add(
+ createInfoChunk(StringUtil.getLineIterator(trimmed)));
+ }
+ else
+ {
+ UnifiedParser parser = new UnifiedParser(section.data, diffIndex_);
+ DiffFileHeader filePair = parser.nextFilePair();
+
+ if (filePair == null)
+ {
+ // Although "Index: <filename>" appeared, no diff was actually
+ // generated (e.g. binary file)
+ Pattern hrule = Pattern.create("^=+$");
+ Match m = hrule.match(section.data, 0);
+ int startAt = (m != null) ? m.getIndex() + m.getValue().length()
+ : 0;
+ Iterable<String> lines = StringUtil.getLineIterator(
+ StringUtil.trimBlankLines(section.data.substring(startAt)));
+ DiffChunk chunk = createInfoChunk(lines);
+ if (lastSection != null && lastSection.isProperty)
+ {
+ // This is to work around special case where a binary file is
+ // initially checked in. If we do nothing, in the history it
+ // will appear as the property changes, then without a break,
+ // the message that this file can't be displayed. That's the
+ // correct content, but we want it in the order of the message
+ // that the file can't be displayed, then a blank line, then
+ // the property changes.
+ pendingDiffChunks_.add(0, chunk);
+ pendingDiffChunks_.add(
+ 1, createInfoChunk(StringUtil.getLineIterator("\n")));
+ }
+ else
+ {
+ pendingDiffChunks_.add(chunk);
+ }
+ }
+
+ DiffChunk chunk;
+ while (null != (chunk = parser.nextChunk()))
+ {
+ pendingDiffChunks_.add(chunk);
+ }
+
+ diffIndex_ = parser.getDiffIndex();
+ }
+
+ lastSection = section;
+ }
+
+ return new DiffFileHeader(new ArrayList<String>(), filename, filename);
+ }
+
+ private DiffChunk createInfoChunk(Iterable<String> lines)
+ {
+ ArrayList<Line> outLines = new ArrayList<Line>();
+ int chunkDiffIndex = diffIndex_++;
+ for (String line : lines)
+ {
+ outLines.add(new Line(Type.Info,
+ new boolean[] {false, false},
+ new int[] {-1, -1},
+ StringUtil.isNullOrEmpty(line) ? "\n": line,
+ diffIndex_++));
+ }
+ return new DiffChunk(null, null, outLines, chunkDiffIndex);
+ }
+
+ @Override
+ public DiffChunk nextChunk()
+ {
+ if (pendingDiffChunks_.size() == 0)
+ return null;
+
+ return pendingDiffChunks_.remove(0);
+ }
+
+ private final ArrayList<DiffChunk> pendingDiffChunks_ = new ArrayList<DiffChunk>();
+ private final ArrayList<Section> sections_;
+ private int diffIndex_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPane.java
new file mode 100644
index 0000000..b7b4559
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPane.java
@@ -0,0 +1,158 @@
+/*
+ * SVNPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.icons.StandardIcons;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNPresenter.Display;
+
+import java.util.ArrayList;
+
+public class SVNPane extends WorkbenchPane implements Display
+{
+ @Inject
+ public SVNPane(SVNChangelistTablePresenter changelistTablePresenter,
+ Session session,
+ Commands commands)
+ {
+ super(session.getSessionInfo().getVcsName());
+
+ changelistTablePresenter_ = changelistTablePresenter;
+ commands_ = commands;
+ }
+
+ @Override
+ public void bringToFront()
+ {
+ }
+
+ @Override
+ public void setProgress(boolean showProgress)
+ {
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+
+ toolbar.addLeftWidget(commands_.vcsDiff().createToolbarButton());
+ toolbar.addLeftSeparator();
+ toolbar.addLeftWidget(commands_.vcsAddFiles().createToolbarButton());
+ toolbar.addLeftWidget(commands_.vcsRemoveFiles().createToolbarButton());
+ toolbar.addLeftSeparator();
+
+ toolbar.addLeftWidget(commands_.vcsCommit().createToolbarButton());
+
+ toolbar.addLeftSeparator();
+
+ ToolbarPopupMenu moreMenu = new ToolbarPopupMenu();
+
+ moreMenu.addItem(commands_.vcsRevert().createMenuItem(false));
+ moreMenu.addItem(commands_.vcsIgnore().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.vcsResolve().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.vcsPull().createMenuItem(false));
+ moreMenu.addItem(commands_.vcsCleanup().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.vcsShowHistory().createMenuItem(false));
+ moreMenu.addSeparator();
+ moreMenu.addItem(commands_.showShellDialog().createMenuItem(false));
+
+ toolbar.addLeftWidget(new ToolbarButton(
+ "More",
+ StandardIcons.INSTANCE.more_actions(),
+ moreMenu));
+
+ toolbar.addLeftSeparator();
+ toolbar.addRightWidget(commands_.vcsRefresh().createToolbarButton());
+
+ return toolbar;
+ }
+
+ @Override
+ public void setItems(ArrayList<StatusAndPath> items)
+ {
+ getChangelistTable().setItems(items);
+ }
+
+ @Override
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ return changelistTablePresenter_.getSelectedItems();
+ }
+
+ @Override
+ public ChangelistTable getChangelistTable()
+ {
+ return changelistTablePresenter_.getView();
+ }
+
+ @Override
+ public void showContextMenu(final int clientX, final int clientY)
+ {
+ final ToolbarPopupMenu menu = new ToolbarPopupMenu();
+
+ menu.addItem(commands_.vcsDiff().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsAddFiles().createMenuItem(false));
+ menu.addItem(commands_.vcsRemoveFiles().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsRevert().createMenuItem(false));
+ menu.addItem(commands_.vcsIgnore().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsResolve().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsOpen().createMenuItem(false));
+
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ menu.setPopupPosition(clientX, clientY);
+ }
+ });
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return changelistTablePresenter_.getView();
+ }
+
+ @Override
+ public void onBeforeSelected()
+ {
+ }
+
+ @Override
+ public void onSelected()
+ {
+ }
+
+ private final SVNChangelistTablePresenter changelistTablePresenter_;
+ private final Commands commands_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenter.java
new file mode 100644
index 0000000..54e5f82
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenter.java
@@ -0,0 +1,235 @@
+/*
+ * SVNPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Size;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.widget.MessageDialog;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.satellite.SatelliteManager;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.vcs.VCSApplicationParams;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.BaseVcsPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.ProcessCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.GitHubViewRequest;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+import java.util.ArrayList;
+
+public class SVNPresenter extends BaseVcsPresenter
+{
+ public interface Binder extends CommandBinder<Commands, SVNPresenter>
+ {
+ }
+
+ public interface Display extends WorkbenchView, IsWidget, SVNPresenterDisplay
+ {
+ void setItems(ArrayList<StatusAndPath> items);
+
+ void showContextMenu(int clientX, int clientY);
+ }
+
+ @Inject
+ public SVNPresenter(Display view,
+ GlobalDisplay globalDisplay,
+ Binder binder,
+ Commands commands,
+ SVNServerOperations server,
+ SVNState svnState,
+ SatelliteManager satelliteManager,
+ VCSFileOpener vcsFileOpener)
+ {
+ super(view);
+ view_ = view;
+ globalDisplay_ = globalDisplay;
+ server_ = server;
+ svnState_ = svnState;
+ satelliteManager_ = satelliteManager;
+
+ commandHandler_ = new SVNCommandHandler(view,
+ globalDisplay,
+ commands,
+ server,
+ svnState,
+ vcsFileOpener);
+
+ binder.bind(commands, this);
+
+ svnState_.addVcsRefreshHandler(new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ view_.setItems(svnState_.getStatus());
+ }
+ });
+
+ view_.getChangelistTable().addContextMenuHandler(new ContextMenuHandler(){
+ @Override
+ public void onContextMenu(ContextMenuEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ view_.showContextMenu(nativeEvent.getClientX(),
+ nativeEvent.getClientY());
+ }
+ });
+ }
+
+ private void showChanges(ArrayList<StatusAndPath> items)
+ {
+ showReviewPane(false, null, items);
+ }
+
+ private void showReviewPane(boolean showHistory,
+ FileSystemItem historyFileFilter,
+ ArrayList<StatusAndPath> items)
+ {
+ // setup params
+ VCSApplicationParams params = VCSApplicationParams.create(
+ showHistory,
+ historyFileFilter,
+ items);
+
+ // open the window
+ satelliteManager_.openSatellite("review_changes",
+ params,
+ new Size(1000,1200));
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Handler
+ void onVcsDiff()
+ {
+ showChanges(view_.getSelectedItems());
+ }
+
+ // the following commands are BaseVcsPresenter overrides rather than
+ // direct handlers (they are handled within the base class and then
+ // dispatched to via these overrides)
+
+ @Override
+ public void onVcsCommit()
+ {
+ commandHandler_.onVcsCommit();
+ }
+
+ @Override
+ public void onVcsShowHistory()
+ {
+ showHistory(null);
+ }
+
+ @Override
+ public void onVcsPull()
+ {
+ commandHandler_.onVcsPull();
+ }
+
+ @Override
+ public void onVcsCleanup()
+ {
+ server_.svnCleanup(new ProcessCallback(
+ "SVN Cleanup",
+ "Cleaning up working directory...",
+ 750)); // pad progress for feedback
+ }
+
+ @Override
+ public void onVcsIgnore()
+ {
+ commandHandler_.onVcsIgnore();
+ }
+
+ @Override
+ public void showHistory(FileSystemItem fileFilter)
+ {
+ showReviewPane(true, fileFilter, new ArrayList<StatusAndPath>());
+ }
+
+ @Override
+ public void showDiff(FileSystemItem file)
+ {
+ // build an ArrayList<StatusAndPath> so we can call the core helper
+ ArrayList<StatusAndPath> diffList = new ArrayList<StatusAndPath>();
+ for (StatusAndPath item : svnState_.getStatus())
+ {
+ if (item.getRawPath().equals(file.getPath()))
+ {
+ diffList.add(item);
+ break;
+ }
+ }
+
+ if (diffList.size() > 0)
+ {
+ showChanges(diffList);
+ }
+ else
+ {
+ globalDisplay_.showMessage(MessageDialog.INFO,
+ "No Changes to File",
+ "There are no changes to the file \"" +
+ file.getName() + "\" to diff.");
+ }
+
+ }
+
+ @Override
+ public void revertFile(FileSystemItem file)
+ {
+ commandHandler_.revertFile(file);
+ }
+
+
+ @Override
+ public void onVcsPush()
+ {
+ // git specific, not supported by svn
+ }
+
+ @Override
+ public void viewOnGitHub(GitHubViewRequest viewRequest)
+ {
+ // git specific, not supported by svn
+ }
+
+
+ private final Display view_;
+ private final GlobalDisplay globalDisplay_;
+ private final SVNServerOperations server_;
+ private final SVNCommandHandler commandHandler_;
+ private final SVNState svnState_;
+ private final SatelliteManager satelliteManager_;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenterDisplay.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenterDisplay.java
new file mode 100644
index 0000000..cbd7b4d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNPresenterDisplay.java
@@ -0,0 +1,26 @@
+/*
+ * SVNPresenterDisplay.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import java.util.ArrayList;
+
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+
+public interface SVNPresenterDisplay
+{
+ ArrayList<StatusAndPath> getSelectedItems();
+ ChangelistTable getChangelistTable();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.java
new file mode 100644
index 0000000..6c865d5
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.java
@@ -0,0 +1,107 @@
+/*
+ * SVNResolveDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.InputElement;
+import com.google.gwt.dom.client.LabelElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Widget;
+import org.rstudio.core.client.widget.ModalDialog;
+import org.rstudio.core.client.widget.OperationWithInput;
+
+public class SVNResolveDialog extends ModalDialog<String>
+{
+ interface Binder extends UiBinder<HTMLPanel, SVNResolveDialog>
+ {}
+
+ public SVNResolveDialog(int fileCount,
+ String caption,
+ OperationWithInput<String> operation)
+ {
+ super(caption, operation);
+ fileCount_ = fileCount;
+ }
+
+ @Override
+ protected String collectInput()
+ {
+ for (InputElement el : inputElements_)
+ {
+ if (el.isChecked())
+ return el.getValue();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected boolean validate(String input)
+ {
+ return input != null;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ HTMLPanel widget = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+
+ inputElements_ = new InputElement[] {
+ radioWorking_,
+ radioMineConflict_,
+ radioTheirsConflict_,
+ radioMineAll_,
+ radioTheirsAll_,
+ };
+
+ spanTargetNoun_.setInnerText(fileCount_ == 1 ? "path" : "paths");
+
+ labelWorking_.setPropertyString("for", radioWorking_.getId());
+ labelMineConflict_.setPropertyString("for", radioMineConflict_.getId());
+ labelTheirsConflict_.setPropertyString("for", radioTheirsConflict_.getId());
+ labelMineAll_.setPropertyString("for", radioMineAll_.getId());
+ labelTheirsAll_.setPropertyString("for", radioTheirsAll_.getId());
+
+ return widget;
+ }
+
+ @UiField
+ InputElement radioWorking_;
+ @UiField
+ InputElement radioMineConflict_;
+ @UiField
+ InputElement radioTheirsConflict_;
+ @UiField
+ InputElement radioMineAll_;
+ @UiField
+ InputElement radioTheirsAll_;
+ @UiField
+ SpanElement spanTargetNoun_;
+ @UiField
+ LabelElement labelWorking_;
+ @UiField
+ LabelElement labelMineConflict_;
+ @UiField
+ LabelElement labelTheirsConflict_;
+ @UiField
+ LabelElement labelMineAll_;
+ @UiField
+ LabelElement labelTheirsAll_;
+ private final int fileCount_;
+ private InputElement[] inputElements_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.ui.xml
new file mode 100644
index 0000000..ec7d2f0
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNResolveDialog.ui.xml
@@ -0,0 +1,112 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+
+ <ui:style>
+ .resolvePanel {
+ padding: 12px;
+ }
+ .resolvePanel label {
+ font-weight: bold;
+ }
+ .resolvePanel td {
+ padding: 0;
+ }
+ .description {
+ padding-bottom: 12px !important;
+ width: 240px;
+ color: #888;
+ }
+ .prompt {
+ display: block;
+ font-weight: bold;
+ margin-bottom: 16px;
+ }
+ </ui:style>
+
+ <g:HTMLPanel styleName="{style.resolvePanel}">
+ <div class="{style.prompt}">
+ Resolve the selected <span ui:field="spanTargetNoun_"></span> using:
+ </div>
+ <table>
+ <tr>
+ <td><input type="radio"
+ name="svnResolve"
+ ui:field="radioWorking_"
+ value="working"/>
+ </td>
+ <td><label ui:field="labelWorking_">Working</label></td>
+ </tr>
+ <tr>
+ <td />
+ <td class="{style.description}">
+ Mark the working copy as resolved
+ </td>
+ </tr>
+ <tr>
+ <td><input type="radio"
+ name="svnResolve"
+ ui:field="radioMineConflict_"
+ value="mine-conflict"/>
+ </td>
+ <td>
+ <label ui:field="labelMineConflict_">Mine-Conflict</label>
+ </td>
+ </tr>
+ <tr>
+ <td />
+ <td class="{style.description}">
+ Accept my version for all conflicts
+ </td>
+ </tr>
+ <tr>
+ <td><input type="radio"
+ name="svnResolve"
+ ui:field="radioTheirsConflict_"
+ value="theirs-conflict"/>
+ </td>
+ <td>
+ <label ui:field="labelTheirsConflict_">Theirs-Conflict</label>
+ </td>
+ </tr>
+ <tr>
+ <td />
+ <td class="{style.description}">
+ Accept their version for all conflicts
+ </td>
+ </tr>
+ <tr>
+ <td><input type="radio"
+ name="svnResolve"
+ ui:field="radioMineAll_"
+ value="mine-all"/>
+ </td>
+ <td>
+ <label ui:field="labelMineAll_">Mine-All</label>
+ </td>
+ </tr>
+ <tr>
+ <td />
+ <td class="{style.description}">
+ Accept my version of the entire file, even non-conflicts
+ </td>
+ </tr>
+ <tr>
+ <td><input type="radio"
+ name="svnResolve"
+ ui:field="radioTheirsAll_"
+ value="theirs-all"/>
+ </td>
+ <td>
+ <label ui:field="labelTheirsAll_">Theirs-All</label>
+ </td>
+ </tr>
+ <tr>
+ <td />
+ <td class="{style.description}">
+ Accept their version of the entire file, even non-conflicts
+ </td>
+ </tr>
+ </table>
+ </g:HTMLPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTable.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTable.java
new file mode 100644
index 0000000..21f1852
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTable.java
@@ -0,0 +1,148 @@
+/*
+ * SVNSelectChangelistTable.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.view.client.MultiSelectionModel;
+import org.rstudio.core.client.cellview.TriStateCheckboxCell;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+
+import java.util.*;
+
+public class SVNSelectChangelistTable extends SVNChangelistTable
+{
+ public SVNSelectChangelistTable()
+ {
+ uncommitableStatuses.add("X");
+ uncommitableStatuses.add("?");
+ uncommitableStatuses.add("!");
+ uncommitableStatuses.add("C");
+ }
+
+ @Override
+ protected MultiSelectionModel<StatusAndPath> createSelectionModel()
+ {
+ // Squelch normal table selection, we're using checkboxes instead
+
+ return new MultiSelectionModel<StatusAndPath>()
+ {
+ @Override
+ public void setSelected(StatusAndPath object, boolean selected)
+ {
+ // do nothing
+ }
+ };
+ }
+
+ @Override
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ throw new UnsupportedOperationException(
+ "SVNSelectChangelistTable.getSelectedItems is not supported");
+ }
+
+ @Override
+ public ArrayList<String> getSelectedPaths()
+ {
+ ArrayList<String> selectedPaths = new ArrayList<String>();
+ for (Map.Entry<String, Boolean> entry : selected_.entrySet())
+ if (entry.getValue() != null && entry.getValue())
+ selectedPaths.add(entry.getKey());
+ return selectedPaths;
+ }
+
+ @Override
+ public ArrayList<String> getSelectedDiscardablePaths()
+ {
+ throw new UnsupportedOperationException(
+ "SVNSelectChangelistTable.getSelectedDiscardablePaths is not " +
+ "supported");
+ }
+
+ @Override
+ public void setItems(ArrayList<StatusAndPath> items)
+ {
+ for (StatusAndPath item: items)
+ {
+ if (selected_.containsKey(item.getPath()))
+ continue;
+
+ selected_.put(item.getPath(),
+ !uncommitableStatuses.contains(item.getStatus()));
+ }
+ super.setItems(items);
+ }
+
+ public Column<StatusAndPath, Boolean> getCommitColumn()
+ {
+ return commitColumn_;
+ }
+
+ public void setSelected(StatusAndPath obj, Boolean selected)
+ {
+ selected_.put(obj.getPath(), selected);
+ List<StatusAndPath> list = dataProvider_.getList();
+ int index = list.indexOf(obj);
+ if (index >= 0)
+ list.set(index, list.get(index));
+ }
+
+ @Override
+ protected void configureTable()
+ {
+ commitColumn_ = new Column<StatusAndPath, Boolean>(
+ new TriStateCheckboxCell<StatusAndPath>(selectionModel_))
+ {
+ @Override
+ public Boolean getValue(StatusAndPath object)
+ {
+ return selected_.containsKey(object.getPath()) &&
+ selected_.get(object.getPath());
+ }
+ };
+
+ commitColumn_.setHorizontalAlignment(Column.ALIGN_CENTER);
+ commitColumn_.setSortable(true);
+ sortHandler_.setComparator(commitColumn_, new Comparator<StatusAndPath>()
+ {
+ @Override
+ public int compare(StatusAndPath a, StatusAndPath b)
+ {
+ Boolean a1 = commitColumn_.getValue(a);
+ Boolean b1 = commitColumn_.getValue(b);
+ int a2 = a1 == null ? 0 : a1 ? -1 : 1;
+ int b2 = b1 == null ? 0 : b1 ? -1 : 1;
+ return a2 - b2;
+ }
+ });
+ table_.addColumn(commitColumn_, "Commit");
+ table_.setColumnWidth(commitColumn_, "46px");
+
+ super.configureTable();
+ }
+
+ public void clearSelection()
+ {
+ selected_.clear();
+ List<StatusAndPath> list = dataProvider_.getList();
+ for (int i = 0; i < list.size(); i++)
+ list.set(i, list.get(i));
+ }
+
+ private final HashMap<String, Boolean> selected_ =
+ new HashMap<String, Boolean>();
+ private final HashSet<String> uncommitableStatuses = new HashSet<String>();
+ private Column<StatusAndPath, Boolean> commitColumn_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTablePresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTablePresenter.java
new file mode 100644
index 0000000..1b5b1ab
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNSelectChangelistTablePresenter.java
@@ -0,0 +1,134 @@
+/*
+ * SVNSelectChangelistTablePresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.inject.Inject;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.vcs.ProcessResult;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+import java.util.ArrayList;
+
+public class SVNSelectChangelistTablePresenter extends SVNChangelistTablePresenter
+{
+ @Inject
+ public SVNSelectChangelistTablePresenter(final SVNSelectChangelistTable view,
+ SVNState svnState,
+ final SVNServerOperations server,
+ final GlobalDisplay globalDisplay)
+ {
+ super(view, svnState);
+ view_ = view;
+
+ view.getCommitColumn().setFieldUpdater(new FieldUpdater<StatusAndPath, Boolean>()
+ {
+ @Override
+ public void update(final int index,
+ final StatusAndPath object,
+ Boolean value)
+ {
+ if (value)
+ {
+ if (object.getStatus().equals("?"))
+ {
+ server.svnAdd(toArray(object.getPath()), new SimpleRequestCallback<ProcessResult>()
+ {
+ @Override
+ public void onResponseReceived(ProcessResult response)
+ {
+ if (response.getExitCode() == 0)
+ view.setSelected(object, true);
+ }
+ });
+ return;
+ }
+ if (object.getStatus().equals("!"))
+ {
+ server.svnDelete(toArray(object.getPath()),
+ new SimpleRequestCallback<ProcessResult>()
+ {
+ @Override
+ public void onResponseReceived(
+ ProcessResult response)
+ {
+ if (response.getExitCode() == 0)
+ view.setSelected(object, true);
+ }
+ });
+ return;
+ }
+ if (object.getStatus().equals("C"))
+ {
+ globalDisplay.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "File Conflict",
+ "This file has a conflict. Would you like to mark it " +
+ "as resolved now?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ server.svnResolve(
+ "working",
+ toArray(object.getPath()),
+ new SimpleRequestCallback<ProcessResult>()
+ {
+ @Override
+ public void onResponseReceived(
+ ProcessResult response)
+ {
+ if (response.getExitCode() == 0)
+ view.setSelected(object, true);
+ }
+ });
+ }
+ },
+ false
+ );
+ return;
+ }
+ }
+
+ view.setSelected(object, value);
+ }
+
+ private ArrayList<String> toArray(String path)
+ {
+ ArrayList<String> result = new ArrayList<String>();
+ result.add(path);
+ return result;
+ }
+ });
+ }
+
+ @Override
+ protected boolean rejectItem(StatusAndPath item)
+ {
+ return super.rejectItem(item) || "X".equals(item.getStatus());
+ }
+
+ public void clearSelection()
+ {
+ view_.clearSelection();
+ }
+
+ private final SVNSelectChangelistTable view_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNStatusRenderer.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNStatusRenderer.java
new file mode 100644
index 0000000..4c30d00
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/SVNStatusRenderer.java
@@ -0,0 +1,163 @@
+/*
+ * SVNStatusRenderer.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable.ChangelistTableCellTableResources;
+
+public class SVNStatusRenderer implements SafeHtmlRenderer<String>
+{
+ interface StatusResources extends ClientBundle
+ {
+ @Source("images/statusAdded.png")
+ ImageResource statusAdded();
+ @Source("images/statusConflicted.png")
+ ImageResource statusConflicted();
+ @Source("images/statusDeleted.png")
+ ImageResource statusDeleted();
+ @Source("images/statusExternal.png")
+ ImageResource statusExternal();
+ @Source("images/statusIgnored.png")
+ ImageResource statusIgnored();
+ @Source("images/statusMissing.png")
+ ImageResource statusMissing();
+// @Source("images/statusMerged")
+// ImageResource statusMerged();
+ @Source("images/statusModified.png")
+ ImageResource statusModified();
+ @Source("images/statusNone.png")
+ ImageResource statusNone();
+ @Source("images/statusObstructed.png")
+ ImageResource statusObstructed();
+ @Source("images/statusUnversioned.png")
+ ImageResource statusUnversioned();
+ }
+
+ public SVNStatusRenderer()
+ {
+ }
+
+ @Override
+ public SafeHtml render(String str)
+ {
+ if (str.length() != 1)
+ return SafeHtmlUtils.fromString(str);
+
+ ImageResource img = imgForStatus(str.charAt(0));
+
+ if (img == null)
+ return SafeHtmlUtils.fromString(str);
+
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
+ builder.append(SafeHtmlUtils.fromTrustedString(
+ "<span " +
+ "class=\"" + ctRes_.cellTableStyle().status() + "\" " +
+ "title=\"" +
+ SafeHtmlUtils.htmlEscape(descForStatus(str)) +
+ "\">"));
+
+ builder.append(SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(
+ img).getHTML()));
+
+ builder.appendHtmlConstant("</span>");
+
+ return builder.toSafeHtml();
+ }
+
+ private String descForStatus(String str)
+ {
+ char c = str.charAt(0);
+
+ switch (c)
+ {
+ case 'A':
+ return "Added";
+ case 'C':
+ return "Conflicted";
+ case 'D':
+ return "Deleted";
+ case 'X':
+ return "External";
+ case 'I':
+ return "Ignored";
+ case '!':
+ return "Missing";
+// case 'G':
+// return resources_.statusMerged();
+ case 'M':
+ return "Modified";
+ case ' ':
+ return "";
+ case '~':
+ return "Obstructed";
+ case '?':
+ return "Unversioned";
+ default:
+ return "";
+ }
+
+ }
+
+
+
+ private ImageResource imgForStatus(char c)
+ {
+ switch (c)
+ {
+ case 'A':
+ return resources_.statusAdded();
+ case 'C':
+ return resources_.statusConflicted();
+ case 'D':
+ return resources_.statusDeleted();
+ case 'X':
+ return resources_.statusExternal();
+ case 'I':
+ return resources_.statusIgnored();
+ case '!':
+ return resources_.statusMissing();
+// case 'G':
+// return resources_.statusMerged();
+ case 'M':
+ return resources_.statusModified();
+ case ' ':
+ return resources_.statusNone();
+ case '~':
+ return resources_.statusObstructed();
+ case '?':
+ return resources_.statusUnversioned();
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void render(String str, SafeHtmlBuilder builder)
+ {
+ SafeHtml safeHtml = render(str);
+ if (safeHtml != null)
+ builder.append(safeHtml);
+ }
+
+ private static final StatusResources resources_ = GWT.create(StatusResources.class);
+ private static final ChangelistTableCellTableResources ctRes_ = GWT.create(ChangelistTableCellTableResources.class);
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.java
new file mode 100644
index 0000000..97fae8d
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.java
@@ -0,0 +1,266 @@
+/*
+ * SVNCommitDialog.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.commit;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.widget.ModalDialogBase;
+import org.rstudio.core.client.widget.SmallButton;
+import org.rstudio.core.client.widget.ThemedButton;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.console.ConsoleProcess;
+import org.rstudio.studio.client.common.console.ProcessExitEvent;
+import org.rstudio.studio.client.common.console.ProcessExitEvent.Handler;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.StringStateValue;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNSelectChangelistTablePresenter;
+
+public class SVNCommitDialog extends ModalDialogBase
+{
+ interface Binder extends UiBinder<Widget, SVNCommitDialog>
+ {
+ }
+
+ @Inject
+ public SVNCommitDialog(SVNServerOperations server,
+ final SVNSelectChangelistTablePresenter changelistPresenter,
+ GlobalDisplay globalDisplay,
+ Session session)
+ {
+ server_ = server;
+ globalDisplay_ = globalDisplay;
+ session_ = session;
+
+ setText("Commit");
+
+ if (commitDraftStateValue_ == null)
+ {
+ commitDraftStateValue_ = new StringStateValue(
+ MODULE_SVN,
+ KEY_COMMIT_MESSAGE,
+ ClientState.PROJECT_PERSISTENT,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(String value)
+ {
+ commitDraft_ = value;
+ }
+
+ @Override
+ protected String getValue()
+ {
+ return commitDraft_;
+ }
+ };
+ }
+
+ ThemedButton commitButton = new ThemedButton("Commit",
+ new ClickHandler() {
+ public void onClick(ClickEvent event)
+ {
+ attemptCommit();
+ }
+ });
+ addButton(commitButton);
+ addCancelButton();
+
+
+ changelist_ = changelistPresenter.getView();
+ widget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+ widget_.setSize("500px", "400px");
+
+ topHPanel_.setCellWidth(selectLabel_, "99%");
+ topHPanel_.setCellVerticalAlignment(selectLabel_,
+ HorizontalPanel.ALIGN_BOTTOM);
+
+ topHPanel_.setCellWidth(btnClearSelection_, "1%");
+ topHPanel_.setCellVerticalAlignment(btnClearSelection_,
+ HorizontalPanel.ALIGN_TOP);
+ topHPanel_.setCellHorizontalAlignment(btnClearSelection_,
+ HorizontalPanel.ALIGN_RIGHT);
+
+ btnClearSelection_.addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ changelistPresenter.clearSelection();
+ }
+ });
+
+ message_.getElement().setAttribute("spellcheck", "false");
+
+ if (!StringUtil.isNullOrEmpty(commitDraft_))
+ message_.setText(commitDraft_);
+ }
+
+ @Override
+ protected void onLoad()
+ {
+ super.onLoad();
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ message_.setFocus(true);
+ }
+ });
+ }
+
+ @Override
+ protected void onUnload()
+ {
+ super.onUnload();
+ commitDraft_ = message_.getText();
+ session_.persistClientState();
+ }
+
+
+
+ private void attemptCommit()
+ {
+ if (validateInput())
+ {
+ server_.svnCommit(
+ changelist_.getSelectedPaths(),
+ message_.getText(),
+ new SimpleRequestCallback<ConsoleProcess>("SVN Commit")
+ {
+ private int exitCode_ = 0;
+
+ @Override
+ public void onResponseReceived(ConsoleProcess cp)
+ {
+ // hide the dialog -- we'll re-show it if the commit fails
+ SVNCommitDialog.this.setVisible(false);
+
+ // subscribe to process exit so we can record the
+ // exit code and manage the commit draft persistence
+ cp.addProcessExitHandler(new Handler()
+ {
+ @Override
+ public void onProcessExit(ProcessExitEvent event)
+ {
+ // save the exit code so we can use it to decide
+ // whether to become visible or close the dialog
+ // once the console process dialog exits
+ exitCode_ = event.getExitCode();
+
+ // We'll set the commitDraft_ on unload, so clear
+ // out the text box now
+ if (exitCode_ == 0)
+ message_.setText("");
+ }
+ });
+
+ // create the console progress dialog and then subscribe
+ // to its onClose event to figure out whether we need
+ // to re-appear or fully close
+ ConsoleProgressDialog dialog = new ConsoleProgressDialog(
+ cp,
+ server_);
+ dialog.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event)
+ {
+ if (exitCode_== 0)
+ closeDialog();
+ else
+ SVNCommitDialog.this.setVisible(true);
+ }
+ });
+
+ // show the dialog
+ dialog.showModal();
+ }
+ });
+ }
+ }
+
+ private boolean validateInput()
+ {
+ if (changelist_.getSelectedPaths().size() == 0)
+ {
+ globalDisplay_.showMessage(GlobalDisplay.MSG_WARNING,
+ "No Items Selected",
+ "Please select one or more items to " +
+ "commit.",
+ message_);
+ return false;
+ }
+
+ // actually validate
+ if (message_.getText().trim().length() == 0)
+ {
+ globalDisplay_.showMessage(GlobalDisplay.MSG_WARNING,
+ "Message Required",
+ "Please provide a commit message.",
+ message_);
+ return false;
+ }
+
+
+ return true;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ return widget_;
+ }
+
+ @UiField(provided = true)
+ ChangelistTable changelist_;
+ @UiField
+ TextArea message_;
+ @UiField
+ HorizontalPanel topHPanel_;
+ @UiField
+ SmallButton btnClearSelection_;
+ @UiField
+ Label selectLabel_;
+
+ private Widget widget_;
+ private final SVNServerOperations server_;
+ private final GlobalDisplay globalDisplay_;
+ private final Session session_;
+
+ private static String commitDraft_;
+ private static StringStateValue commitDraftStateValue_;
+
+ private static final String MODULE_SVN = "svn";
+ private static final String KEY_COMMIT_MESSAGE = "commitMessage";
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.ui.xml
new file mode 100644
index 0000000..d0d7912
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/commit/SVNCommitDialog.ui.xml
@@ -0,0 +1,52 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:vcs='urn:import:org.rstudio.studio.client.workbench.views.vcs.common'
+ xmlns:rs='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:style>
+ .changelistTableBackground {
+ position: relative;
+ background-color: white;
+ width: 100%;
+ height: 100%;
+ border: 1px solid #888;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ }
+ .commitMessageLabel {
+ margin-top: 12px;
+ margin-bottom: 3px;
+ }
+ .commitMessage {
+ width: 100%;
+ height: 80px;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ resize: none;
+ }
+ </ui:style>
+
+ <g:DockLayoutPanel>
+ <g:north size="22"><!-- 19 is the height of SmallButton -->
+ <g:HorizontalPanel ui:field="topHPanel_" width="100%">
+ <g:Label ui:field="selectLabel_" text="Select items to commit:"/>
+ <rs:SmallButton ui:field="btnClearSelection_"
+ text="Clear Selection"/>
+ </g:HorizontalPanel>
+ </g:north>
+ <g:center>
+ <vcs:ChangelistTable ui:field="changelist_" styleName="{style.changelistTableBackground}" />
+ </g:center>
+ <g:south size="125">
+ <g:FlowPanel>
+ <g:Label text="Commit message:" styleName="{style.commitMessageLabel}"/>
+ <g:TextArea ui:field="message_" styleName="{style.commitMessage}"/>
+ </g:FlowPanel>
+ </g:south>
+ </g:DockLayoutPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryAsyncDataProvider.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryAsyncDataProvider.java
new file mode 100644
index 0000000..1b405dc
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryAsyncDataProvider.java
@@ -0,0 +1,64 @@
+/*
+ * SVNHistoryAsyncDataProvider.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.dialog;
+
+import com.google.inject.Inject;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.core.client.jsonrpc.RpcObjectList;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitCount;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryAsyncDataProvider;
+
+public class SVNHistoryAsyncDataProvider extends HistoryAsyncDataProvider
+{
+ @Inject
+ public SVNHistoryAsyncDataProvider(SVNServerOperations server)
+ {
+ server_ = server;
+ }
+
+ @Override
+ protected void getHistoryCount(String revision,
+ FileSystemItem fileFilter,
+ String searchText,
+ ServerRequestCallback<CommitCount> requestCallback)
+ {
+ server_.svnHistoryCount(StringUtil.parseInt(revision, -1),
+ fileFilter,
+ searchText,
+ requestCallback);
+ }
+
+ @Override
+ protected void getHistory(String revision,
+ FileSystemItem fileFilter,
+ int skip,
+ int maxEntries,
+ String searchText,
+ ServerRequestCallback<RpcObjectList<CommitInfo>> requestCallback)
+ {
+ server_.svnHistory(StringUtil.parseInt(revision, -1),
+ fileFilter,
+ skip,
+ maxEntries,
+ searchText,
+ requestCallback);
+ }
+
+ private final SVNServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryStrategy.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryStrategy.java
new file mode 100644
index 0000000..56e263f
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNHistoryStrategy.java
@@ -0,0 +1,181 @@
+/*
+ * SVNHistoryStrategy.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.dialog;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.cellview.client.AbstractPager;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.view.client.HasData;
+import com.google.inject.Inject;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.Pager;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.DiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshHandler;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.CommitInfo;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.HistoryStrategy;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNDiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+public class SVNHistoryStrategy implements HistoryStrategy
+{
+ @Inject
+ public SVNHistoryStrategy(SVNServerOperations server,
+ SVNHistoryAsyncDataProvider dataProvider,
+ SVNState vcsState)
+ {
+ server_ = server;
+ dataProvider_ = dataProvider;
+ dataProvider_.setHistoryStrategy(this);
+ vcsState_ = vcsState;
+ }
+
+ @Override
+ public void setRev(String rev)
+ {
+ dataProvider_.setRev(rev);
+ }
+
+ @Override
+ public String idColumnName()
+ {
+ return "Revision";
+ }
+
+ @Override
+ public boolean isBranchingSupported()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isShowFileSupported()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isSearchSupported()
+ {
+ return false;
+ }
+
+ @Override
+ public void setSearchText(HasValue<String> searchText)
+ {
+ dataProvider_.setSearchText(searchText);
+ }
+
+ @Override
+ public void setFileFilter(HasValue<FileSystemItem> fileFilter)
+ {
+ dataProvider_.setFileFilter(fileFilter);
+ }
+
+ @Override
+ public void showFile(String revision,
+ String filename,
+ ServerRequestCallback<String> requestCallback)
+ {
+ int rev = parseRevision(revision);
+ server_.svnShowFile(rev, filename, requestCallback);
+ }
+
+ @Override
+ public HandlerRegistration addVcsRefreshHandler(VcsRefreshHandler handler)
+ {
+ return vcsState_.addVcsRefreshHandler(handler, false);
+ }
+
+ @Override
+ public void showCommit(String commitId,
+ boolean noSizeWarning,
+ ServerRequestCallback<String> requestCallback)
+ {
+ int rev = parseRevision(commitId);
+ server_.svnShow(rev, noSizeWarning, requestCallback);
+ }
+
+ @Override
+ public void addDataDisplay(HasData<CommitInfo> display)
+ {
+ dataProvider_.addDataDisplay(display);
+ }
+
+ @Override
+ public void onRangeChanged(HasData<CommitInfo> display)
+ {
+ dataProvider_.onRangeChanged(display);
+ }
+
+ @Override
+ public void refreshCount()
+ {
+ }
+
+ @Override
+ public void initializeHistory(final HasData<CommitInfo> dataDisplay)
+ {
+ // Run a very short svnHistory call before allowing initialization to
+ // proceed. We do this to force authentication to happen in a predictable
+ // way, whereas without this mechanism, three different auth prompts
+ // happen at the same time.
+
+ addDataDisplay(dataDisplay);
+ }
+
+ @Override
+ public AbstractPager getPager()
+ {
+ return new Pager(100, -1);
+ }
+
+ @Override
+ public boolean getAutoSelectFirstRow()
+ {
+ return false;
+ }
+
+ @Override
+ public DiffParser createParserForCommit(String commitDiff)
+ {
+ return new SVNDiffParser(commitDiff);
+ }
+
+ @Override
+ public boolean getShowHistoryErrors()
+ {
+ return false;
+ }
+
+ private int parseRevision(String revision)
+ {
+ int rev;
+ try
+ {
+ rev = Integer.parseInt(revision);
+ }
+ catch (NumberFormatException nfe)
+ {
+ rev = -1;
+ }
+ return rev;
+ }
+
+ private final SVNServerOperations server_;
+ private final SVNHistoryAsyncDataProvider dataProvider_;
+ private final SVNState vcsState_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.css b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.css
new file mode 100644
index 0000000..8eba7dd
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.css
@@ -0,0 +1,52 @@
+ at eval proportionalFont org.rstudio.core.client.theme.ThemeFonts.getProportionalFont();
+ at eval fixedWidthFont org.rstudio.core.client.theme.ThemeFonts.getFixedWidthFont();
+
+ at external gwt-SplitLayoutPanel-VDragger, gwt-SplitLayoutPanel-HDragger;
+
+/** BEGIN SHARED BETWEEN HISTORY AND REVIEW PANELS **/
+
+.splitPanel {
+ background-color: #f3f4f4;
+}
+ at sprite .splitPanel .gwt-SplitLayoutPanel-VDragger {
+ gwt-image: 'splitterTileV';
+}
+
+.whitebg {
+ background-color: white;
+}
+
+.toolbar {
+ background-image: none !important;
+}
+
+ at sprite .toolbarWrapper {
+ position: relative;
+ gwt-image: 'toolbarTile';
+ padding-top: 2px;
+ font-size: 11px;
+}
+.toolbarWrapper select {
+ font-size: 11px;
+}
+
+/** END SHARED BETWEEN HISTORY AND REVIEW PANELS **/
+
+
+.contextLabel {
+ display: inline;
+ margin-right: 8px;
+}
+
+.diffToolbar {
+ float: right;
+ width: auto;
+}
+
+.diffViewOptions {
+ position: absolute;
+ left: 6px;
+ top: 2px;
+ bottom: 2px;
+ width: auto;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.java
new file mode 100644
index 0000000..c539044
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.java
@@ -0,0 +1,480 @@
+/*
+ * SVNReviewPanel.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.dialog;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.inject.Inject;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.command.KeyboardShortcut;
+import org.rstudio.core.client.widget.LeftRightToggleButton;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.core.client.widget.ToolbarButton;
+import org.rstudio.core.client.widget.ToolbarPopupMenu;
+import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.ChunkOrLine;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTablePresenter;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.SharedStyles;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.SizeWarningWidget;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNChangelistTablePresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPresenter.Display;
+
+import java.util.ArrayList;
+
+public class SVNReviewPanel extends ResizeComposite implements Display
+{
+ interface Resources extends ClientBundle
+ {
+ @Source("SVNReviewPanel.css")
+ Styles styles();
+
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ @Source("../../dialog/images/toolbarTile.png")
+ ImageResource toolbarTile();
+
+ @Source("../../dialog/images/stageAllFiles.png")
+ ImageResource stageAllFiles();
+
+ @Source("../../dialog/images/discard.png")
+ ImageResource discard();
+
+ @Source("../../dialog/images/stage.png")
+ ImageResource stage();
+
+ @Source("../../dialog/images/splitterTileV.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+ ImageResource splitterTileV();
+
+ @Source("../../dialog/images/splitterTileH.png")
+ @ImageOptions(repeatStyle = RepeatStyle.Vertical)
+ ImageResource splitterTileH();
+
+ @Source("../../dialog/images/blankFileIcon.png")
+ ImageResource blankFileIcon();
+ }
+
+ interface Styles extends SharedStyles
+ {
+ String contextLabel();
+ String diffToolbar();
+ String diffViewOptions();
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClickCommand implements HasClickHandlers, Command
+ {
+ @Override
+ public void execute()
+ {
+ ClickEvent.fireNativeEvent(
+ Document.get()
+ .createClickEvent(0,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false),
+ this);
+ }
+
+ @Override
+ public HandlerRegistration addClickHandler(ClickHandler handler)
+ {
+ return handlerManager_.addHandler(ClickEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlerManager_.fireEvent(event);
+ }
+
+ private final HandlerManager handlerManager_ = new HandlerManager(this);
+ }
+
+ private static class ListBoxAdapter implements HasValue<Integer>
+ {
+ private ListBoxAdapter(ListBox listBox)
+ {
+ listBox_ = listBox;
+ listBox_.addChangeHandler(new ChangeHandler()
+ {
+ @Override
+ public void onChange(ChangeEvent event)
+ {
+ ValueChangeEvent.fire(ListBoxAdapter.this, getValue());
+ }
+ });
+ }
+
+ @Override
+ public Integer getValue()
+ {
+ return Integer.parseInt(
+ listBox_.getValue(listBox_.getSelectedIndex()));
+ }
+
+ @Override
+ public void setValue(Integer value)
+ {
+ setValue(value, true);
+ }
+
+ @Override
+ public void setValue(Integer value, boolean fireEvents)
+ {
+ String valueStr = value.toString();
+ for (int i = 0; i < listBox_.getItemCount(); i++)
+ {
+ if (listBox_.getValue(i).equals(valueStr))
+ {
+ listBox_.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler)
+ {
+ return handlers_.addHandler(ValueChangeEvent.getType(), handler);
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event)
+ {
+ handlers_.fireEvent(event);
+ }
+
+ private final ListBox listBox_;
+ private final HandlerManager handlers_ = new HandlerManager(this);
+ }
+
+
+ interface Binder extends UiBinder<Widget, SVNReviewPanel>
+ {
+ }
+
+ @Inject
+ public SVNReviewPanel(SVNChangelistTablePresenter changelist,
+ LineTableView diffPane,
+ Commands commands)
+ {
+ commands_ = commands;
+ splitPanel_ = new SplitLayoutPanel(4);
+
+ changelist_ = changelist.getView();
+ lines_ = diffPane;
+ lines_.getElement().setTabIndex(-1);
+ lines_.hideStageCommands();
+
+ overrideSizeWarning_ = new SizeWarningWidget("diff");
+
+ changelist.setSelectFirstItemByDefault(true);
+
+ Widget widget = GWT.<Binder>create(Binder.class).createAndBindUi(this);
+ initWidget(widget);
+
+ topToolbar_.addStyleName(RES.styles().toolbar());
+
+ switchViewButton_ = new LeftRightToggleButton("Changes", "History", true);
+ switchViewButton_.getElement().getStyle().setMarginRight(8, Unit.PX);
+ topToolbar_.addLeftWidget(switchViewButton_);
+
+ topToolbar_.addLeftWidget(commands.vcsAddFiles().createToolbarButton());
+ topToolbar_.addLeftWidget(commands.vcsRemoveFiles().createToolbarButton());
+ topToolbar_.addLeftSeparator();
+ topToolbar_.addLeftWidget(commands.vcsRevert().createToolbarButton());
+ topToolbar_.addLeftWidget(commands.vcsIgnore().createToolbarButton());
+ topToolbar_.addLeftSeparator();
+ topToolbar_.addLeftWidget(commands.vcsResolve().createToolbarButton());
+ topToolbar_.addLeftSeparator();
+ topToolbar_.addLeftWidget(commands.vcsCommit().createToolbarButton());
+
+
+ topToolbar_.addRightWidget(new ToolbarButton(
+ "Refresh", commands.vcsRefresh().getImageResource(),
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ changelist_.showProgress();
+ commands_.vcsRefresh().execute();
+ }
+ }));
+
+ commands.vcsPull().setButtonLabel("Update");
+ commands.vcsPull().setMenuLabel("Update");
+ topToolbar_.addRightWidget(commands.vcsPull().createToolbarButton());
+
+ diffToolbar_.addStyleName(RES.styles().toolbar());
+ diffToolbar_.addStyleName(RES.styles().diffToolbar());
+
+ diffToolbar_.addLeftSeparator();
+ discardAllButton_ = diffToolbar_.addLeftWidget(new ToolbarButton(
+ "Discard All", RES.discard(), (ClickHandler) null));
+
+ listBoxAdapter_ = new ListBoxAdapter(contextLines_);
+
+ new WidgetHandlerRegistration(this)
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return Event.addNativePreviewHandler(new NativePreviewHandler()
+ {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && KeyboardShortcut.getModifierValue(nativeEvent) == KeyboardShortcut.CTRL)
+ {
+ switch (nativeEvent.getKeyCode())
+ {
+ case KeyCodes.KEY_DOWN:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, getLineScroll(diffScroll_), 0);
+ break;
+ case KeyCodes.KEY_UP:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_,
+ -getLineScroll(diffScroll_),
+ 0);
+ break;
+ case KeyCodes.KEY_PAGEDOWN:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_, getPageScroll(diffScroll_), 0);
+ break;
+ case KeyCodes.KEY_PAGEUP:
+ nativeEvent.preventDefault();
+ scrollBy(diffScroll_,
+ -getPageScroll(diffScroll_),
+ 0);
+ break;
+ }
+ }
+ }
+ });
+ }
+ };
+ }
+
+ private void scrollBy(ScrollPanel scrollPanel, int vscroll, int hscroll)
+ {
+ if (vscroll != 0)
+ {
+ scrollPanel.setVerticalScrollPosition(
+ Math.max(0, scrollPanel.getVerticalScrollPosition() + vscroll));
+ }
+
+ if (hscroll != 0)
+ {
+ scrollPanel.setHorizontalScrollPosition(
+ Math.max(0, scrollPanel.getHorizontalScrollPosition() + hscroll));
+ }
+ }
+
+ private int getLineScroll(ScrollPanel panel)
+ {
+ return 30;
+ }
+
+ private int getPageScroll(ScrollPanel panel)
+ {
+ // Return slightly less than the client height (so there's overlap between
+ // one screen and the next) but never less than the line scoll height.
+ return Math.max(
+ getLineScroll(panel),
+ panel.getElement().getClientHeight() - getLineScroll(panel));
+ }
+
+ @Override
+ public HasClickHandlers getSwitchViewButton()
+ {
+ return switchViewButton_;
+ }
+
+ @Override
+ public HasClickHandlers getDiscardAllButton()
+ {
+ return discardAllButton_;
+ }
+
+
+ @Override
+ public void setData(ArrayList<ChunkOrLine> lines)
+ {
+ int vscroll = diffScroll_.getVerticalScrollPosition();
+ int hscroll = diffScroll_.getHorizontalScrollPosition();
+
+ getLineTableDisplay().setData(lines, PatchMode.Working);
+
+ diffScroll_.setVerticalScrollPosition(vscroll);
+ diffScroll_.setHorizontalScrollPosition(hscroll);
+ }
+
+ @Override
+ public ArrayList<String> getSelectedPaths()
+ {
+ return changelist_.getSelectedPaths();
+ }
+
+ @Override
+ public ArrayList<StatusAndPath> getSelectedItems()
+ {
+ return changelist_.getSelectedItems();
+ }
+
+ @Override
+ public void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ changelist_.setSelectedStatusAndPaths(selectedPaths);
+ }
+
+ @Override
+ public LineTablePresenter.Display getLineTableDisplay()
+ {
+ return lines_;
+ }
+
+ @Override
+ public ChangelistTable getChangelistTable()
+ {
+ return changelist_;
+ }
+
+ @Override
+ public HasValue<Integer> getContextLines()
+ {
+ return listBoxAdapter_;
+ }
+
+ @Override
+ public HasClickHandlers getOverrideSizeWarningButton()
+ {
+ return overrideSizeWarning_;
+ }
+
+ @Override
+ public void showSizeWarning(long sizeInBytes)
+ {
+ overrideSizeWarning_.setSize(sizeInBytes);
+ diffScroll_.setWidget(overrideSizeWarning_);
+ }
+
+ @Override
+ public void hideSizeWarning()
+ {
+ diffScroll_.setWidget(lines_);
+ }
+
+ @Override
+ public void showContextMenu(final int clientX,
+ final int clientY)
+ {
+ final ToolbarPopupMenu menu = new ToolbarPopupMenu();
+
+ menu.addItem(commands_.vcsAddFiles().createMenuItem(false));
+ menu.addItem(commands_.vcsRemoveFiles().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsRevert().createMenuItem(false));
+ menu.addItem(commands_.vcsIgnore().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsResolve().createMenuItem(false));
+ menu.addSeparator();
+ menu.addItem(commands_.vcsOpen().createMenuItem(false));
+
+ menu.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight)
+ {
+ menu.setPopupPosition(clientX, clientY);
+ }
+ });
+ }
+
+ @Override
+ public void onShow()
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ changelist_.focus();
+ }
+ });
+ }
+
+ @UiField(provided = true)
+ SplitLayoutPanel splitPanel_;
+ @UiField(provided = true)
+ ChangelistTable changelist_;
+ @UiField(provided = true)
+ LineTableView lines_;
+ @UiField
+ ListBox contextLines_;
+ @UiField
+ Toolbar topToolbar_;
+ @UiField
+ Toolbar diffToolbar_;
+ @UiField
+ ScrollPanel diffScroll_;
+
+ private final Commands commands_;
+
+ private ListBoxAdapter listBoxAdapter_;
+
+ private ToolbarButton discardAllButton_;
+
+ private LeftRightToggleButton switchViewButton_;
+
+ private SizeWarningWidget overrideSizeWarning_;
+
+ private static final Resources RES = GWT.create(Resources.class);
+ static {
+ RES.styles().ensureInjected();
+ }
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.ui.xml b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.ui.xml
new file mode 100644
index 0000000..2e3fb62
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPanel.ui.xml
@@ -0,0 +1,48 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:vcs='urn:import:org.rstudio.studio.client.workbench.views.vcs.common'
+ xmlns:vcs_diff='urn:import:org.rstudio.studio.client.workbench.views.vcs.common.diff'
+ xmlns:rs_widget='urn:import:org.rstudio.core.client.widget'>
+
+ <ui:with field="res" type="org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPanel.Resources"/>
+
+ <g:SplitLayoutPanel ui:field="splitPanel_" styleName="{res.styles.splitPanel}">
+ <g:north size="230">
+ <g:DockLayoutPanel>
+ <g:north size="28">
+ <g:SimplePanel styleName="{res.styles.toolbarWrapper}">
+ <rs_widget:Toolbar ui:field="topToolbar_"/>
+ </g:SimplePanel>
+ </g:north>
+ <g:center>
+ <vcs:ChangelistTable styleName="{res.styles.whitebg}" ui:field="changelist_" width="100%" height="100%"/>
+ </g:center>
+ </g:DockLayoutPanel>
+ </g:north>
+ <g:center>
+ <g:DockLayoutPanel>
+ <g:north size="28">
+ <g:FlowPanel styleName="{res.styles.toolbarWrapper}">
+ <g:FlowPanel styleName="{res.styles.diffViewOptions}">
+ <g:Label text="Context" styleName="{res.styles.contextLabel}"/>
+ <g:ListBox ui:field="contextLines_" visibleItemCount="1" selectedIndex="0">
+ <g:item value="5">5 line</g:item>
+ <g:item value="10">10 line</g:item>
+ <g:item value="25">25 line</g:item>
+ <g:item value="50">50 line</g:item>
+ <g:item value="-1">All lines</g:item>
+ </g:ListBox>
+ <rs_widget:Toolbar ui:field="diffToolbar_"/>
+ </g:FlowPanel>
+ </g:FlowPanel>
+ </g:north>
+ <g:center>
+ <g:ScrollPanel ui:field="diffScroll_" styleName="{res.styles.whitebg}" width="100%" height="100%">
+ <vcs_diff:LineTableView ui:field="lines_" width="100%"/>
+ </g:ScrollPanel>
+ </g:center>
+ </g:DockLayoutPanel>
+ </g:center>
+ </g:SplitLayoutPanel>
+
+</ui:UiBinder>
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPresenter.java
new file mode 100644
index 0000000..f9bdb2a
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/dialog/SVNReviewPresenter.java
@@ -0,0 +1,525 @@
+/*
+ * SVNReviewPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.dialog;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.HasAttachHandlers;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.RowCountChangeEvent;
+import com.google.gwt.view.client.SelectionChangeEvent;
+import com.google.inject.Inject;
+import org.rstudio.core.client.Invalidation;
+import org.rstudio.core.client.Invalidation.Token;
+import org.rstudio.core.client.StringUtil;
+import org.rstudio.core.client.WidgetHandlerRegistration;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.core.client.widget.Operation;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.SimpleRequestCallback;
+import org.rstudio.studio.client.common.vcs.DiffResult;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.Void;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.model.ClientState;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.model.helper.IntStateValue;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent;
+import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler;
+import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable;
+import org.rstudio.studio.client.workbench.views.vcs.common.ProcessCallback;
+import org.rstudio.studio.client.workbench.views.vcs.common.VCSFileOpener;
+import org.rstudio.studio.client.workbench.views.vcs.common.diff.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.*;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.DiffChunkActionEvent.Action;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.vcs.dialog.ReviewPresenter;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNCommandHandler;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNDiffParser;
+import org.rstudio.studio.client.workbench.views.vcs.svn.SVNPresenterDisplay;
+import org.rstudio.studio.client.workbench.views.vcs.svn.model.SVNState;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class SVNReviewPresenter implements ReviewPresenter
+{
+ public interface Binder extends CommandBinder<Commands, SVNReviewPresenter> {}
+
+ public interface Display extends IsWidget, HasAttachHandlers, SVNPresenterDisplay
+ {
+ ArrayList<String> getSelectedPaths();
+ void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths);
+
+ LineTablePresenter.Display getLineTableDisplay();
+ ChangelistTable getChangelistTable();
+ HasValue<Integer> getContextLines();
+
+ HasClickHandlers getSwitchViewButton();
+
+ HasClickHandlers getDiscardAllButton();
+
+ void setData(ArrayList<ChunkOrLine> lines);
+
+ HasClickHandlers getOverrideSizeWarningButton();
+ void showSizeWarning(long sizeInBytes);
+ void hideSizeWarning();
+
+ void showContextMenu(int clientX, int clientY);
+
+ void onShow();
+ }
+
+ private class DiscardClickHandler implements ClickHandler, Command
+ {
+ public DiscardClickHandler()
+ {
+ }
+
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ execute();
+ }
+
+ @Override
+ public void execute()
+ {
+ ArrayList<String> paths = view_.getSelectedPaths();
+
+ server_.svnRevert(paths,
+ new ProcessCallback("Revert"));
+
+ view_.getChangelistTable().moveSelectionDown();
+ }
+ }
+
+ private class ApplyPatchHandler implements DiffChunkActionHandler,
+ DiffLinesActionHandler
+ {
+ @Override
+ public void onDiffChunkAction(DiffChunkActionEvent event)
+ {
+ ArrayList<DiffChunk> chunks = new ArrayList<DiffChunk>();
+ chunks.add(event.getDiffChunk());
+ doPatch(event.getAction(), event.getDiffChunk().getLines(), chunks);
+ }
+
+ @Override
+ public void onDiffLinesAction(DiffLinesActionEvent event)
+ {
+ ArrayList<Line> lines = view_.getLineTableDisplay().getSelectedLines();
+ doPatch(event.getAction(), lines, activeChunks_);
+ }
+
+ private void doPatch(Action action,
+ ArrayList<Line> lines,
+ ArrayList<DiffChunk> chunks)
+ {
+ if (action != Action.Discard)
+ throw new IllegalArgumentException("Unhandled diff chunk action");
+
+ applyPatch(chunks, lines, true);
+ }
+
+ }
+
+ @Inject
+ public SVNReviewPresenter(SVNServerOperations server,
+ Display view,
+ Binder binder,
+ Commands commands,
+ final EventBus events,
+ final SVNState svnState,
+ final Session session,
+ final GlobalDisplay globalDisplay,
+ VCSFileOpener vcsFileOpener)
+ {
+ server_ = server;
+ view_ = view;
+ svnState_ = svnState;
+
+ binder.bind(commands, this);
+
+ undiffableStatuses_.add("?");
+ undiffableStatuses_.add("!");
+ undiffableStatuses_.add("X");
+
+ commandHandler_ = new SVNCommandHandler(view,
+ globalDisplay,
+ commands,
+ server,
+ svnState,
+ vcsFileOpener);
+
+ new WidgetHandlerRegistration(view.asWidget())
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return svnState_.addVcsRefreshHandler(new VcsRefreshHandler()
+ {
+ @Override
+ public void onVcsRefresh(VcsRefreshEvent event)
+ {
+ if (event.getReason() == Reason.VcsOperation)
+ {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand()
+ {
+ @Override
+ public void execute()
+ {
+ updateDiff();
+
+ initialized_ = true;
+ }
+ });
+ }
+ }
+ }, false);
+ }
+ };
+
+ new WidgetHandlerRegistration(view.asWidget())
+ {
+ @Override
+ protected HandlerRegistration doRegister()
+ {
+ return events.addHandler(FileChangeEvent.TYPE, new FileChangeHandler()
+ {
+ @Override
+ public void onFileChange(FileChangeEvent event)
+ {
+ ArrayList<StatusAndPath> paths = view_.getChangelistTable()
+ .getSelectedItems();
+ if (paths.size() != 1)
+ {
+ clearDiff();
+ return;
+ }
+
+ StatusAndPath vcsStatus = StatusAndPath.fromInfo(
+ event.getFileChange().getFile().getSVNStatus());
+ if (paths.get(0).getRawPath().equals(vcsStatus.getRawPath()))
+ {
+ svnState.refresh(false);
+ }
+ }
+ });
+ }
+ };
+
+ view_.getChangelistTable().addSelectionChangeHandler(new com.google.gwt.view.client.SelectionChangeEvent.Handler()
+ {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent event)
+ {
+ overrideSizeWarning_ = false;
+ commandHandler_.setFilesCommandsEnabled(view_.getSelectedPaths().size() > 0);
+ if (initialized_)
+ updateDiff();
+ }
+ });
+ view_.getChangelistTable().addRowCountChangeHandler(new RowCountChangeEvent.Handler()
+ {
+ @Override
+ public void onRowCountChange(RowCountChangeEvent event)
+ {
+ // This is necessary because during initial load, the selection
+ // model has its selection set before any items are loaded into
+ // the table (so therefore view_.getSelectedPaths().size() is always
+ // 0, and the files commands are not enabled until selection changes
+ // again). By updating the files commands' enabled state on row
+ // count change as well, we can make sure they get enabled.
+ commandHandler_.setFilesCommandsEnabled(view_.getSelectedPaths().size() > 0);
+ }
+ });
+
+ view_.getChangelistTable().addContextMenuHandler(new ContextMenuHandler()
+ {
+ @Override
+ public void onContextMenu(ContextMenuEvent event)
+ {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ view_.showContextMenu(nativeEvent.getClientX(),
+ nativeEvent.getClientY());
+ }
+ });
+
+ view_.getDiscardAllButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ String which = view_.getLineTableDisplay()
+ .getSelectedLines()
+ .size() == 0
+ ? "All "
+ : "The selected";
+ globalDisplay.showYesNoMessage(
+ GlobalDisplay.MSG_WARNING,
+ "Discard All",
+ which + " changes in this file will be " +
+ "lost.\n\nAre you sure you want to continue?",
+ new Operation()
+ {
+ @Override
+ public void execute()
+ {
+ new DiscardClickHandler().execute();
+ }
+ },
+ false);
+ }
+ });
+
+ view_.getLineTableDisplay().addDiffChunkActionHandler(new ApplyPatchHandler());
+ view_.getLineTableDisplay().addDiffLineActionHandler(new ApplyPatchHandler());
+
+ new IntStateValue(MODULE_SVN, KEY_CONTEXT_LINES, ClientState.PERSISTENT,
+ session.getSessionInfo().getClientState())
+ {
+ @Override
+ protected void onInit(Integer value)
+ {
+ if (value != null)
+ view_.getContextLines().setValue(value);
+ }
+
+ @Override
+ protected Integer getValue()
+ {
+ return view_.getContextLines().getValue();
+ }
+ };
+
+ view_.getContextLines().addValueChangeHandler(new ValueChangeHandler<Integer>()
+ {
+ @Override
+ public void onValueChange(ValueChangeEvent<Integer> event)
+ {
+ updateDiff();
+ }
+ });
+
+ view_.getOverrideSizeWarningButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ overrideSizeWarning_ = true;
+ updateDiff();
+ }
+ });
+ }
+
+ private void applyPatch(ArrayList<DiffChunk> chunks,
+ ArrayList<Line> lines,
+ boolean reverse)
+ {
+ chunks = new ArrayList<DiffChunk>(chunks);
+
+ if (reverse)
+ {
+ for (int i = 0; i < chunks.size(); i++)
+ chunks.set(i, chunks.get(i).reverse());
+ lines = Line.reverseLines(lines);
+ }
+
+ String path = view_.getChangelistTable().getSelectedPaths().get(0);
+
+ UnifiedEmitter emitter = new UnifiedEmitter(path);
+ for (DiffChunk chunk : chunks)
+ emitter.addContext(chunk);
+ emitter.addDiffs(lines);
+ String patch = emitter.createPatch(false);
+
+ server_.svnApplyPatch(path, patch,
+ StringUtil.notNull(currentEncoding_),
+ new SimpleRequestCallback<Void>());
+ }
+
+ private void updateDiff()
+ {
+ view_.hideSizeWarning();
+
+ final ArrayList<StatusAndPath> paths = view_.getChangelistTable().getSelectedItems();
+ if (paths.size() != 1)
+ {
+ clearDiff();
+ return;
+ }
+
+ final StatusAndPath item = paths.get(0);
+
+ if (!item.getPath().equals(currentFilename_))
+ {
+ clearDiff();
+ currentFilename_ = item.getPath();
+ }
+
+ // bail if this is an undiffable status
+ if (undiffableStatuses_.contains(item.getStatus()))
+ return;
+
+ diffInvalidation_.invalidate();
+ final Token token = diffInvalidation_.getInvalidationToken();
+
+ server_.svnDiffFile(
+ item.getPath(),
+ view_.getContextLines().getValue(),
+ overrideSizeWarning_,
+ new SimpleRequestCallback<DiffResult>("Diff Error")
+ {
+ @Override
+ public void onResponseReceived(DiffResult diffResult)
+ {
+ if (token.isInvalid())
+ return;
+
+ String response = diffResult.getDecodedValue();
+
+ // Use lastResponse_ to prevent unnecessary flicker
+ if (response.equals(currentResponse_))
+ return;
+ currentResponse_ = response;
+ currentEncoding_ = diffResult.getSourceEncoding();
+
+ SVNDiffParser parser = new SVNDiffParser(response);
+ parser.nextFilePair();
+
+ ArrayList<ChunkOrLine> allLines = new ArrayList<ChunkOrLine>();
+
+ activeChunks_.clear();
+ for (DiffChunk chunk;
+ null != (chunk = parser.nextChunk());)
+ {
+ if (!chunk.shouldIgnore())
+ {
+ activeChunks_.add(chunk);
+ allLines.add(new ChunkOrLine(chunk));
+ }
+
+ for (Line line : chunk.getLines())
+ allLines.add(new ChunkOrLine(line));
+ }
+
+ view_.getLineTableDisplay().setShowActions(
+ !"?".equals(item.getStatus()));
+ view_.setData(allLines);
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ JSONNumber size = error.getClientInfo().isNumber();
+ if (size != null)
+ view_.showSizeWarning((long) size.doubleValue());
+ else
+ super.onError(error);
+ }
+ });
+ }
+
+ private void clearDiff()
+ {
+ currentResponse_ = null;
+ currentFilename_ = null;
+ view_.getLineTableDisplay().clear();
+ }
+
+ @Override
+ public Widget asWidget()
+ {
+ return view_.asWidget();
+ }
+
+ @Override
+ public HandlerRegistration addSwitchViewHandler(
+ final SwitchViewEvent.Handler h)
+ {
+ return view_.getSwitchViewButton().addClickHandler(new ClickHandler()
+ {
+ @Override
+ public void onClick(ClickEvent event)
+ {
+ h.onSwitchView(new SwitchViewEvent());
+ }
+ });
+ }
+
+ @Override
+ public void setSelectedPaths(ArrayList<StatusAndPath> selectedPaths)
+ {
+ view_.setSelectedStatusAndPaths(selectedPaths);
+ }
+
+ public void onShow()
+ {
+ // Ensure that we're fresh
+ svnState_.refresh();
+
+ view_.onShow();
+ }
+
+ @Handler
+ public void onVcsCommit()
+ {
+ commandHandler_.onVcsCommit();
+ }
+
+ @Handler
+ public void onVcsPull()
+ {
+ commandHandler_.onVcsPull();
+ }
+
+ @Handler
+ public void onVcsIgnore()
+ {
+ commandHandler_.onVcsIgnore();
+ }
+
+ private final Invalidation diffInvalidation_ = new Invalidation();
+ private final SVNServerOperations server_;
+ private final SVNCommandHandler commandHandler_;
+ private final Display view_;
+ private ArrayList<DiffChunk> activeChunks_ = new ArrayList<DiffChunk>();
+ private String currentResponse_;
+ private String currentEncoding_;
+ private String currentFilename_;
+ private SVNState svnState_;
+ private boolean initialized_;
+ private static final String MODULE_SVN = "vcs_svn";
+ private static final String KEY_CONTEXT_LINES = "context_lines";
+
+ private final HashSet<String> undiffableStatuses_ = new HashSet<String>();
+
+ private boolean overrideSizeWarning_ = false;
+
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusAdded.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusAdded.png
new file mode 100755
index 0000000..b5f87dd
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusAdded.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusConflicted.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusConflicted.png
new file mode 100755
index 0000000..c96c8ce
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusConflicted.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusDeleted.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusDeleted.png
new file mode 100755
index 0000000..443a8e2
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusDeleted.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusExternal.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusExternal.png
new file mode 100755
index 0000000..410edaf
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusExternal.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusIgnored.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusIgnored.png
new file mode 100755
index 0000000..0b799bc
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusIgnored.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLocked.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLocked.png
new file mode 100755
index 0000000..0108f4c
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLocked.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLockedInRepository.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLockedInRepository.png
new file mode 100755
index 0000000..e8d0f94
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusLockedInRepository.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusMissing.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusMissing.png
new file mode 100755
index 0000000..7f2a77e
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusMissing.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusModified.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusModified.png
new file mode 100755
index 0000000..4ae65f7
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusModified.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusNone.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusNone.png
new file mode 100644
index 0000000..f72bf9d
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusNone.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusObstructed.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusObstructed.png
new file mode 100755
index 0000000..c87a685
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusObstructed.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusUnversioned.png b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusUnversioned.png
new file mode 100644
index 0000000..f6df2f8
Binary files /dev/null and b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/images/statusUnversioned.png differ
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/model/SVNState.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/model/SVNState.java
new file mode 100644
index 0000000..7580434
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/vcs/svn/model/SVNState.java
@@ -0,0 +1,89 @@
+/*
+ * SVNState.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.svn.model;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.rstudio.core.client.Debug;
+import org.rstudio.core.client.files.FileSystemItem;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.common.vcs.SVNServerOperations;
+import org.rstudio.studio.client.common.vcs.StatusAndPath;
+import org.rstudio.studio.client.common.vcs.StatusAndPathInfo;
+import org.rstudio.studio.client.server.ServerError;
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent;
+import org.rstudio.studio.client.workbench.views.vcs.common.events.VcsRefreshEvent.Reason;
+import org.rstudio.studio.client.workbench.views.vcs.common.model.VcsState;
+
+ at Singleton
+public class SVNState extends VcsState
+{
+ @Inject
+ public SVNState(SVNServerOperations server,
+ EventBus eventBus,
+ GlobalDisplay globalDisplay,
+ Session session)
+ {
+ super(eventBus, globalDisplay, session);
+ server_ = server;
+ }
+
+ @Override
+ protected StatusAndPathInfo getStatusFromFile(FileSystemItem file)
+ {
+ return file.getSVNStatus();
+ }
+
+ @Override
+ protected boolean needsFullRefresh(FileSystemItem file)
+ {
+ return false;
+ }
+
+ @Override
+ public void refresh(final boolean showError)
+ {
+ server_.svnStatus(new ServerRequestCallback<JsArray<StatusAndPathInfo>>()
+ {
+ @Override
+ public void onResponseReceived(JsArray<StatusAndPathInfo> response)
+ {
+ status_ = StatusAndPath.fromInfos(response);
+ handlers_.fireEvent(new VcsRefreshEvent(Reason.VcsOperation));
+ }
+
+ @Override
+ public void onError(ServerError error)
+ {
+ Debug.logError(error);
+ if (showError)
+ globalDisplay_.showErrorMessage("Error",
+ error.getUserMessage());
+ }
+ });
+ }
+
+ @Override
+ protected boolean isInitialized()
+ {
+ return status_ != null;
+ }
+
+ private final SVNServerOperations server_;
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPane.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPane.java
new file mode 100644
index 0000000..a8be24e
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPane.java
@@ -0,0 +1,123 @@
+/*
+ * ViewerPane.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.viewer;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.widget.RStudioFrame;
+import org.rstudio.core.client.widget.Toolbar;
+import org.rstudio.studio.client.common.AutoGlassPanel;
+import org.rstudio.studio.client.common.GlobalDisplay;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
+
+public class ViewerPane extends WorkbenchPane implements ViewerPresenter.Display
+{
+ @Inject
+ public ViewerPane(Commands commands, GlobalDisplay globalDisplay)
+ {
+ super("Viewer");
+ commands_ = commands;
+ globalDisplay_ = globalDisplay;
+ ensureWidget();
+ }
+
+ @Override
+ protected Toolbar createMainToolbar()
+ {
+ Toolbar toolbar = new Toolbar();
+ toolbar.addLeftWidget(commands_.viewerPopout().createToolbarButton());
+
+ toolbar.addRightWidget(commands_.viewerClear().createToolbarButton());
+ toolbar.addRightWidget(commands_.viewerStop().createToolbarButton());
+ toolbar.addRightSeparator();
+ toolbar.addRightWidget(commands_.viewerRefresh().createToolbarButton());
+ return toolbar;
+ }
+
+ @Override
+ protected Widget createMainWidget()
+ {
+ frame_ = new RStudioFrame() ;
+ frame_.setSize("100%", "100%");
+ navigate(ABOUT_BLANK);
+ return new AutoGlassPanel(frame_);
+ }
+
+ @Override
+ public void navigate(String url)
+ {
+ // save the unmodified URL for pop-out
+ unmodifiedUrl_ = url;
+
+ // append the viewer_pane query parameter
+ if ((unmodifiedUrl_ != null) && !unmodifiedUrl_.equals(ABOUT_BLANK))
+ {
+ // first split into base and anchor
+ String base = new String(unmodifiedUrl_);
+ String anchor = new String();
+ int anchorPos = base.indexOf('#');
+ if (anchorPos != -1)
+ {
+ anchor = base.substring(anchorPos);
+ base = base.substring(0, anchorPos);
+ }
+
+ // add the query param
+ if (!base.contains("?"))
+ base = base + "?";
+ else
+ base = base + "&";
+ base = base + "viewer_pane=1";
+
+ // add the anchor back on
+ String viewerUrl = base + anchor;
+
+ // set the url
+ frame_.setUrl(viewerUrl);
+ }
+ else
+ {
+ frame_.setUrl(unmodifiedUrl_);
+ }
+ }
+
+
+ @Override
+ public String getUrl()
+ {
+ return frame_.getUrl();
+ }
+
+ @Override
+ public void popout()
+ {
+ if (unmodifiedUrl_ != null)
+ globalDisplay_.openWindow(unmodifiedUrl_);
+ }
+
+ @Override
+ public void refresh()
+ {
+ String url = frame_.getUrl();
+ if (url != null)
+ frame_.setUrl(url);
+ }
+
+ private RStudioFrame frame_;
+ private String unmodifiedUrl_;
+ private final Commands commands_;
+ private final GlobalDisplay globalDisplay_;
+ private static final String ABOUT_BLANK = "about:blank";
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPresenter.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPresenter.java
new file mode 100644
index 0000000..c716800
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerPresenter.java
@@ -0,0 +1,201 @@
+/*
+ * ViewerPresenter.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.viewer;
+
+import com.google.inject.Inject;
+
+import org.rstudio.core.client.command.AppCommand;
+import org.rstudio.core.client.command.CommandBinder;
+import org.rstudio.core.client.command.EnabledChangedHandler;
+import org.rstudio.core.client.command.Handler;
+import org.rstudio.studio.client.application.Desktop;
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.server.VoidServerRequestCallback;
+import org.rstudio.studio.client.shiny.events.ShinyApplicationStatusEvent;
+import org.rstudio.studio.client.shiny.model.ShinyApplicationParams;
+import org.rstudio.studio.client.shiny.model.ShinyViewerType;
+import org.rstudio.studio.client.workbench.WorkbenchView;
+import org.rstudio.studio.client.workbench.commands.Commands;
+import org.rstudio.studio.client.workbench.views.BasePresenter;
+import org.rstudio.studio.client.workbench.views.viewer.events.ViewerNavigateEvent;
+import org.rstudio.studio.client.workbench.views.viewer.model.ViewerServerOperations;
+
+public class ViewerPresenter extends BasePresenter
+ implements ShinyApplicationStatusEvent.Handler
+{
+ public interface Binder extends CommandBinder<Commands, ViewerPresenter> {}
+
+ public interface Display extends WorkbenchView
+ {
+ void navigate(String url);
+ String getUrl();
+ void popout();
+ void refresh();
+ }
+
+ @Inject
+ public ViewerPresenter(Display display,
+ EventBus eventBus,
+ Commands commands,
+ Binder binder,
+ ViewerServerOperations server)
+ {
+ super(display);
+ display_ = display;
+ commands_ = commands;
+ server_ = server;
+ events_ = eventBus;
+
+ binder.bind(commands, this);
+
+ enableCommands(false);
+
+ // show a stop button when the console is busy (the stop and
+ // clear commands are mutually exclusive)
+ commands_.viewerStop().setVisible(commands_.interruptR().isEnabled());
+ commands_.viewerClear().setVisible(!commands_.viewerStop().isVisible());
+ commands_.interruptR().addEnabledChangedHandler(
+ new EnabledChangedHandler() {
+ @Override
+ public void onEnabledChanged(AppCommand command)
+ {
+ commands_.viewerStop().setVisible(command.isEnabled());
+ commands_.viewerClear().setVisible(!command.isEnabled());
+ }
+ });
+
+ eventBus.addHandler(ShinyApplicationStatusEvent.TYPE, this);
+ initializeEvents();
+ }
+
+ public void onViewerNavigate(ViewerNavigateEvent event)
+ {
+ enableCommands(true);
+
+ if (event.getURL().length() > 0)
+ {
+ display_.bringToFront();
+
+ int ensureHeight = event.getHeight();
+ if (ensureHeight > 0)
+ display_.ensureHeight(ensureHeight);
+
+ navigate(event.getURL());
+ }
+ else
+ {
+ navigate("about:blank");
+ }
+ }
+
+ @Override
+ public void onShinyApplicationStatus(ShinyApplicationStatusEvent event)
+ {
+ if (event.getParams().getViewerType() ==
+ ShinyViewerType.SHINY_VIEWER_PANE &&
+ event.getParams().getState() ==
+ ShinyApplicationParams.STATE_STARTED)
+ {
+ enableCommands(true);
+ display_.bringToFront();
+ navigate(event.getParams().getUrl());
+ runningShinyAppParams_ = event.getParams();
+ }
+ }
+
+ @Handler
+ public void onViewerPopout() { display_.popout(); }
+ @Handler
+ public void onViewerRefresh() { display_.refresh(); }
+
+
+ @Handler
+ public void onViewerClear()
+ {
+ stop(false);
+ }
+
+ @Handler
+ public void onViewerStop()
+ {
+ stop(true);
+ }
+
+ private void navigate(String url)
+ {
+ if (Desktop.isDesktop())
+ Desktop.getFrame().setViewerUrl(url);
+ display_.navigate(url);
+ }
+
+ private void stop(boolean interruptR)
+ {
+ enableCommands(false);
+ navigate("about:blank");
+ if (interruptR)
+ commands_.interruptR().execute();
+ server_.viewerStopped(new VoidServerRequestCallback());
+
+ // If we were viewing a Shiny application, let the rest of the app know
+ // that the application has been stopped
+ if (runningShinyAppParams_ != null)
+ {
+ runningShinyAppParams_.setState(ShinyApplicationParams.STATE_STOPPED);
+ events_.fireEvent(new ShinyApplicationStatusEvent(
+ runningShinyAppParams_));
+ }
+ runningShinyAppParams_ = null;
+ }
+
+ private void enableCommands(boolean enable)
+ {
+ commands_.viewerPopout().setEnabled(enable);
+ commands_.viewerRefresh().setEnabled(enable);
+ commands_.viewerClear().setEnabled(enable);
+ }
+
+ private native void initializeEvents() /*-{
+ var thiz = this;
+ $wnd.addEventListener(
+ "message",
+ $entry(function(e) {
+ thiz. at org.rstudio.studio.client.workbench.views.viewer.ViewerPresenter::onMessage(Ljava/lang/String;Ljava/lang/String;)(e.data, e.origin);
+ }),
+ true);
+ }-*/;
+
+ private void onMessage(String data, String origin)
+ {
+ if ("disconnected".equals(data))
+ {
+ // ensure the frame url starts with the specified origin
+ if (display_.getUrl().startsWith(origin))
+ onViewerClear();
+ }
+ }
+
+ private String normalizeUrl(String url)
+ {
+ if (url.endsWith("/"))
+ return url.substring(0, url.length()-1);
+ else
+ return url;
+ }
+
+ private final Display display_ ;
+ private final Commands commands_;
+ private final ViewerServerOperations server_;
+ private final EventBus events_;
+
+ private ShinyApplicationParams runningShinyAppParams_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerTab.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerTab.java
new file mode 100644
index 0000000..227fdff
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/ViewerTab.java
@@ -0,0 +1,40 @@
+/*
+ * ViewerTab.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.viewer;
+
+import com.google.inject.Inject;
+
+import org.rstudio.studio.client.application.events.EventBus;
+import org.rstudio.studio.client.workbench.model.Session;
+import org.rstudio.studio.client.workbench.ui.DelayLoadTabShim;
+import org.rstudio.studio.client.workbench.ui.DelayLoadWorkbenchTab;
+import org.rstudio.studio.client.workbench.views.viewer.events.ViewerNavigateEvent;
+
+public class ViewerTab extends DelayLoadWorkbenchTab<ViewerPresenter>
+{
+ public abstract static class Shim
+ extends DelayLoadTabShim<ViewerPresenter, ViewerTab>
+ implements ViewerNavigateEvent.Handler {}
+
+ @Inject
+ public ViewerTab(Shim shim, Session session, EventBus eventBus)
+ {
+ super("Viewer", shim);
+ session_ = session;
+
+ eventBus.addHandler(ViewerNavigateEvent.TYPE, shim);
+ }
+
+ @SuppressWarnings("unused")
+ private Session session_;
+}
\ No newline at end of file
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/events/ViewerNavigateEvent.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/events/ViewerNavigateEvent.java
new file mode 100644
index 0000000..56af4b4
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/events/ViewerNavigateEvent.java
@@ -0,0 +1,75 @@
+/*
+ * ViewerNavigateEvent.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.viewer.events;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+public class ViewerNavigateEvent extends GwtEvent<ViewerNavigateEvent.Handler>
+{
+ public static class Data extends JavaScriptObject
+ {
+ protected Data()
+ {
+ }
+
+ public native final String getURL() /*-{
+ return this.url;
+ }-*/;
+
+ public native final int getHeight() /*-{
+ return this.height;
+ }-*/;
+
+
+ }
+
+ public interface Handler extends EventHandler
+ {
+ void onViewerNavigate(ViewerNavigateEvent event);
+ }
+
+ public ViewerNavigateEvent(Data data)
+ {
+ data_ = data;
+ }
+
+ public String getURL()
+ {
+ return data_.getURL();
+ }
+
+ public int getHeight()
+ {
+ return data_.getHeight();
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(Handler handler)
+ {
+ handler.onViewerNavigate(this);
+ }
+
+ private final Data data_;
+
+ public static final Type<Handler> TYPE = new Type<Handler>();
+}
diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/model/ViewerServerOperations.java b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/model/ViewerServerOperations.java
new file mode 100644
index 0000000..cbd55d8
--- /dev/null
+++ b/src/gwt/src/org/rstudio/studio/client/workbench/views/viewer/model/ViewerServerOperations.java
@@ -0,0 +1,23 @@
+/*
+ * ViewerServerOperations.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.viewer.model;
+
+import org.rstudio.studio.client.server.ServerRequestCallback;
+import org.rstudio.studio.client.server.Void;
+
+public interface ViewerServerOperations
+{
+ void viewerStopped(ServerRequestCallback<Void> requestCallback);
+}
diff --git a/src/gwt/test/autoindent_test.html b/src/gwt/test/autoindent_test.html
new file mode 100644
index 0000000..cb8ec72
--- /dev/null
+++ b/src/gwt/test/autoindent_test.html
@@ -0,0 +1,325 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <!script type="text/javascript" src="../tools/ace/build_support/mini_require.js"></script>
+ <script type="text/javascript" src="../tools/ace/build/src/ace.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/auto_brace_insert.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/tex_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_matching_brace_outdent.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_code_model.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_scope_tree.js"></script>
+ <style type="text/css">
+ pre {
+ margin-bottom: 30px;
+ padding: 3px;
+ border: 1px solid #999;
+ }
+ </style>
+</head>
+<body>
+
+<h2>Interactive Tester</h2>
+<div id="editor" style="width: 600px; height: 200px; border: 1px solid #999"></div>
+
+<h2 style="margin-top: 250px">Automated Tests</h2>
+<p>Passed: <span id="passed"></span>, Failed: <span id="failed"></span></p>
+<ol id="testcontainer" style="display: none;">
+<li><pre data-expected="2">
+doPlot <- function()
+</pre></li>
+<li><pre data-expected="2">
+doPlot <- function() {
+</pre></li>
+<li><pre data-expected="4">
+ doPlot <- function() {
+</pre></li>
+<li><pre data-expected="2">
+doPlot <- function()
+{
+</pre></li>
+<li><pre data-expected="2">
+plot(
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="5">
+plot(x,
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="5">
+plot(x,
+ y,
+</pre></li>
+<li><pre data-expected="7">
+plot(x,
+ c(foo,
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="5">
+plot(x,
+ c(foo,
+ bar),
+</pre></li>
+<li><pre data-expected="2">
+plot(
+ x,
+ c(foo,
+ bar),
+</pre></li>
+<li><pre data-expected="0">
+plot(x, c(foo, bar))
+</pre></li>
+<li><pre data-expected="0">
+plot(
+ x, c(foo, bar))
+</pre></li>
+<li><pre data-expected="0">
+plot(x,
+ c(foo, bar))
+</pre></li>
+<li><pre data-expected="2">
+ plot(x,
+ c(foo, bar))
+</pre></li>
+<li><pre data-expected="4">
+ if (foo())
+</pre></li>
+<li><pre data-expected="4">
+ if (foo())
+ bar
+ else
+</pre></li>
+<li><pre data-expected="4">
+ if (foo())
+ bar
+ else
+# annoyingly placed comment
+</pre></li>
+<li><pre data-expected="0">
+if (foo())
+ bar
+else NULL
+</pre></li>
+<li><pre data-expected="4">
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1)
+ # annoyingly placed comment
+</pre></li>
+<li><pre data-expected="4">
+function() {
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1)
+</pre></li>
+<li><pre data-expected="2">
+function() {
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1) NULL
+</pre></li>
+<li><pre data-expected="2">
+function() {
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1) ()
+</pre></li>
+<li><pre data-expected="4">
+function() {
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1)
+ {
+</pre></li>
+<li><pre data-expected="4">
+function() {
+ for (i = 0;
+ i < (20 * intervals);
+ i += 1) {
+</pre></li>
+<li><pre data-expected="4">
+ if (x) {
+</pre></li>
+<li><pre data-expected="4">
+ if (foo(
+</pre></li>
+<li><pre data-expected="6">
+ if (foo(
+ bar),
+</pre></li>
+<li><pre data-expected="2">
+ while (foo())
+ NULL
+</pre></li>
+<li><pre data-expected="4">
+ while (foo())
+ {
+</pre></li>
+<li><pre data-expected="2">
+ # Invalid parse tree
+ while (foo())
+ {
+ foo (()
+ }
+</pre></li>
+<li><pre data-expected="2">
+ repeat
+ foo()
+</pre></li>
+<li><pre data-expected="4">
+function() {
+ repeat
+</pre></li>
+<li><pre data-expected="4">
+function() {
+ repeat {
+</pre></li>
+<li><pre data-expected="2">
+5 *
+</pre></li>
+<li><pre data-expected="2">
+# One expression continued over more than two lines
+5 ~
+ 5 +
+</pre></li>
+<li><pre data-expected="4">
+else
+ 5 +
+</pre></li>
+<li><pre data-expected="4">
+if (foo())
+ 5 +
+</pre></li>
+<li><pre data-expected="2">
+xyplot(ysim ~ xsim) +
+ layer(panel.ablineq(lm(y ~ x), r.sq = TRUE, rot = TRUE,
+ at = 0.8, pos = 3), style = 1) +
+</pre></li>
+<li><pre data-expected="2">
+verylongfunction(a=1,
+ b=2,
+</pre></li>
+<li><pre data-expected="2">
+verylongfunction(a=1,
+ b=2 + 3 +
+ 4,
+</pre></li>
+<li><pre data-expected="2">
+verylongfunction(a=1,
+ b="Multi line
+strings are evil"
+</pre></li>
+<li><pre data-expected="2">
+verylongfunction(a=1,
+ "Single line strings are OK"
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="17">
+verylongfunction(a=1,
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="17">
+verylongfunction("Multi
+line strings are evil"
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="17">
+verylongfunction(x=1
+
+
+</pre></li>
+<li><pre data-expected="2" data-expected-vertical-args="3">
+verylongfunction(x=1
+ # I'm about to document an arg
+</pre></li>
+</ol>
+
+<script type="text/javascript">
+var RCodeModel = require('mode/r_code_model').RCodeModel;
+var Document = require('ace/document').Document;
+var RMode = require('mode/r').Mode;
+require('mode/auto_brace_insert').setInsertMatching(true);
+
+function doIndentTest(el, state) {
+ var doc = new Document('');
+ var mode = new RMode(false, doc);
+ var rCodeModel = new RCodeModel(doc, mode.$tokenizer);
+
+ doc.insert({row:0, column:0}, (el.innerText || el.textContent).trimRight());
+
+ var indent = rCodeModel.getNextLineIndent(doc.getLength() - 1,
+ doc.getLine(doc.getLength() - 1),
+ "start",
+ " ",
+ 2);
+ el.appendChild(document.createTextNode(indent + "|"));
+ var attrName = "data-expected";
+ if (state.length > 0 &&
+ el.getAttribute(attrName + "-" + state))
+ {
+ attrName += ("-" + state);
+ }
+ var expected = el.getAttribute(attrName);
+ if (expected == indent.length + "")
+ {
+ el.style.backgroundColor = '#BFB';
+ return true;
+ }
+ else
+ {
+ el.style.backgroundColor = 'pink';
+ return false;
+ }
+}
+
+var test = document.getElementById("runme");
+var container = document.getElementById("testcontainer");
+var passed = 0, failed = 0;
+var states = [ "", "vertical-args" ];
+for (var i = 0; i < states.length; i++)
+{
+ // Set up the state to be tested
+ require('mode/r_code_model').setVerticallyAlignFunctionArgs(
+ states[i] === "vertical-args");
+ // Clone the tests for the new state
+ var testnode = container.cloneNode(true);
+ var header = document.createElement("h2");
+ header.innerText = "Test " + i + (
+ states[i].length > 0 ?
+ ": " + states[i] :
+ "");
+ container.parentNode.appendChild(header);
+ container.parentNode.appendChild(testnode);
+ testnode.style.display = "block";
+ pres = testnode.getElementsByTagName("pre");
+ for (var j = 0; j < pres.length; j++)
+ {
+ if (!test)
+ {
+ if (doIndentTest(pres[j], states[i]))
+ passed++;
+ else
+ failed++;
+ }
+ else
+ {
+ if (pres[j] != test)
+ pres[j].style.display = 'none';
+ }
+ }
+}
+if (test)
+{
+ if (doIndentTest(test))
+ passed++;
+ else
+ failed++;
+}
+
+document.getElementById('passed').innerText = passed;
+document.getElementById('failed').innerText = failed;
+
+var editor = ace.edit('editor');
+editor.renderer.setHScrollBarAlwaysVisible(false);
+editor.setHighlightActiveLine(false);
+var RMode = require('mode/r').Mode;
+editor.getSession().setMode(new RMode(false, editor.getSession().getDocument()));
+//editor.getSession().setUseSoftTabs(false);
+</script>
+</body>
+</html>
diff --git a/src/gwt/test/org/rstudio/studio/client/common/r/RTokenizerTests.java b/src/gwt/test/org/rstudio/studio/client/common/r/RTokenizerTests.java
new file mode 100644
index 0000000..2dd2ba8
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/common/r/RTokenizerTests.java
@@ -0,0 +1,182 @@
+/*
+ * RTokenizerTests.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.common.r;
+
+import junit.framework.Assert ;
+
+import com.google.gwt.junit.client.GWTTestCase ;
+import org.rstudio.studio.client.common.r.RToken;
+import org.rstudio.studio.client.common.r.RTokenizer;
+
+public class RTokenizerTests extends GWTTestCase
+{
+ @Override
+ public String getModuleName()
+ {
+ return "org.rstudio.studio.RStudio" ;
+ }
+
+ public void testVoid()
+ {
+ RTokenizer rt = new RTokenizer("") ;
+ Assert.assertNull(rt.nextToken()) ;
+ }
+
+ public void testSimple()
+ {
+ Verifier v = new Verifier(RToken.ERROR, " ", " ") ;
+ v.verify(RToken.LPAREN, "(") ;
+ v.verify(RToken.RPAREN, ")") ;
+ v.verify(RToken.LBRACKET, "[") ;
+ v.verify(RToken.RBRACKET, "]") ;
+ v.verify(RToken.LBRACE, "{") ;
+ v.verify(RToken.RBRACE, "}") ;
+ v.verify(RToken.COMMA, ",") ;
+ v.verify(RToken.SEMI, ";") ;
+ }
+
+ public void testError()
+ {
+ }
+
+ public void testComment()
+ {
+ Verifier v = new Verifier(RToken.COMMENT, " ", "\n") ;
+ v.verify("#");
+ v.verify("# foo #");
+
+ Verifier v2 = new Verifier(RToken.COMMENT, " ", "\r\n") ;
+ v2.verify("#");
+ v2.verify("# foo #");
+ }
+
+ public void testNumbers()
+ {
+ Verifier v = new Verifier(RToken.NUMBER, " ", " ") ;
+ v.verify(new String[] {
+ "1", "10", "0.1", ".2", "1e-7", "1.2e+7", "2e", "3e+",
+ "0x", "0x0", "0xDEADBEEF", "0xcafebad", "1L", "0x10L",
+ "1000000L", "1e6L", "1.1L", "1e-3L", "2i", "4.1i",
+ "1e-2i"
+ }) ;
+ }
+
+ public void testOperators()
+ {
+ Verifier v = new Verifier(RToken.OPER, " ", " ") ;
+ v.verify(
+ "+ - * / ^ > >= < <= == != ! & | ~ -> <- $ : =".split(" ")) ;
+ }
+
+ public void testUOperators()
+ {
+ Verifier v = new Verifier(RToken.UOPER, " ", " ") ;
+ v.verify(new String[] {
+ "%%", "%test test%"
+ }) ;
+ }
+
+ public void testStrings()
+ {
+ Verifier v = new Verifier(RToken.STRING, " ", " ") ;
+ v.verify("\"test\"") ;
+ v.verify("\" '$\t\r\n\\\"\"") ;
+ v.verify("\"\"") ;
+ v.verify("''") ;
+ v.verify("'\"'") ;
+ v.verify("'\\\"'") ;
+ v.verify("'\n'") ;
+ v.verify("'foo bar \\U654'") ;
+ }
+
+ public void testIdentifiers()
+ {
+ Verifier v = new Verifier(RToken.ID, " ", " ") ;
+ v.verify(new String[] {
+ ".", "...", "..1", "..2", "foo", "FOO", "f1",
+ "a_b", "ab_", "\u00C1qc1", "`foo`", "`$@!$@#$`", "`a\n\"'b`"
+ }) ;
+ }
+
+ public void testWhitespace()
+ {
+ Verifier v = new Verifier(RToken.WHITESPACE, "a", "z") ;
+ v.verify("\u00A0") ;
+ v.verify(new String[] {
+ " ", " ", "\u00A0", "\t\n"
+ }) ;
+ }
+
+ protected void verify(String data,
+ int tokenType,
+ String content)
+ {
+ RTokenizer rt = new RTokenizer(data) ;
+ RToken token = rt.nextToken() ;
+ Assert.assertNotNull(token) ;
+ Assert.assertEquals(tokenType, token.getTokenType()) ;
+ Assert.assertEquals(0, token.getOffset()) ;
+ Assert.assertEquals(content.length(), token.getLength()) ;
+ Assert.assertEquals(content, token.getContent()) ;
+ }
+
+ class Verifier
+ {
+ private final int defaultTokenType ;
+ private final String prefix ;
+ private final String suffix ;
+
+ public Verifier(int defaultTokenType, String prefix, String suffix)
+ {
+ super() ;
+ this.defaultTokenType = defaultTokenType ;
+ this.prefix = prefix ;
+ this.suffix = suffix ;
+ }
+
+ public void verify(String value)
+ {
+ verify(defaultTokenType, value) ;
+ }
+
+ public void verify(int tokenType, String value)
+ {
+ RTokenizer rt = new RTokenizer(prefix + value + suffix) ;
+ RToken t ;
+ while (null != (t = rt.nextToken()))
+ {
+ if (t.getOffset() == prefix.length())
+ {
+ Assert.assertEquals(tokenType, t.getTokenType()) ;
+ Assert.assertEquals(value.length(), t.getLength()) ;
+ Assert.assertEquals(value, t.getContent()) ;
+ return ;
+ }
+ }
+ Assert.fail("Bad prefix?") ;
+ }
+
+ public void verify(String[] values)
+ {
+ verify(defaultTokenType, values) ;
+ }
+
+ public void verify(int tokenType, String[] values)
+ {
+ for (String value : values)
+ verify(tokenType, value) ;
+ }
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParserTest.java b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParserTest.java
new file mode 100644
index 0000000..3cd946a
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/UnifiedParserTest.java
@@ -0,0 +1,102 @@
+/*
+ * UnifiedParserTest.java
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.client.workbench.views.vcs.common.diff;
+
+import junit.framework.TestCase;
+
+import java.io.*;
+import java.net.URL;
+
+public class UnifiedParserTest extends TestCase
+{
+ public void setUp() throws Exception
+ {
+
+ }
+
+ public void tearDown() throws Exception
+ {
+
+ }
+
+ private String readFileResource(String name) throws Exception
+ {
+ FileInputStream fileInputStream = null;
+ try
+ {
+ URL url = getClass().getResource(name);
+ fileInputStream = new FileInputStream(url.getFile());
+ StringWriter sw = new StringWriter();
+ for (int c; -1 != (c = fileInputStream.read()); )
+ {
+ sw.append((char) c);
+ }
+ return sw.toString();
+ }
+ finally
+ {
+ if (fileInputStream != null)
+ fileInputStream.close();
+ }
+ }
+
+ public void testNextChunk() throws Exception
+ {
+ testFile("diff1");
+ }
+
+ public void testNextChunk2() throws Exception
+ {
+ testFile("diff2");
+ }
+
+ private void testFile(String testName) throws Exception
+ {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter output = new PrintWriter(stringWriter);
+ UnifiedParser parser = new UnifiedParser(readFileResource(testName + ".txt"));
+ DiffChunk chunk;
+ while (null != (chunk = parser.nextChunk()))
+ {
+ output.println(UnifiedEmitter.createChunkString(chunk));
+ for (Line line : chunk.getLines())
+ {
+ char c;
+ switch (line.getType())
+ {
+ case Insertion:
+ c = '+';
+ break;
+ case Same:
+ c = ' ';
+ break;
+ case Deletion:
+ c = '-';
+ break;
+ case Comment:
+ c = '/';
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ boolean[] appliesTo = line.getAppliesTo();
+ for (int i = 0; i < appliesTo.length-1; i++)
+ output.print(appliesTo[i] ? c : ' ');
+ output.println(line.getText());
+ }
+ }
+ assertEquals(readFileResource(testName + ".out.txt"), stringWriter.toString());
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.out.txt b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.out.txt
new file mode 100644
index 0000000..81bf892
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.out.txt
@@ -0,0 +1,23 @@
+@@@ -98,20 -98,12 +98,20 @@@
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+ }
+ z
+- static void describe(char *arg)
+ -static void describe(struct commit *cmit, int last_one)
+++static void describe(char *arg, int last_one)
+ {
+ + unsigned char sha1[20];
+ + struct commit *cmit;
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+ z
+ + if (get_sha1(arg, sha1) < 0)
+ + usage(describe_usage);
+ + cmit = lookup_commit_reference(sha1);
+ + if (!cmit)
+ + usage(describe_usage);
+ +
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
diff --git a/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.txt b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.txt
new file mode 100644
index 0000000..972bea6
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff1.txt
@@ -0,0 +1,27 @@
+diff --combined describe.c
+index fabadb8,cc95eb0..4866510
+--- a/describe.c
++++ b/describe.c
+@@@ -98,20 -98,12 +98,20 @@@
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+ }
+ z
+- static void describe(char *arg)
+ -static void describe(struct commit *cmit, int last_one)
+++static void describe(char *arg, int last_one)
+ {
+ + unsigned char sha1[20];
+ + struct commit *cmit;
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+ z
+ + if (get_sha1(arg, sha1) < 0)
+ + usage(describe_usage);
+ + cmit = lookup_commit_reference(sha1);
+ + if (!cmit)
+ + usage(describe_usage);
+ +
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
\ No newline at end of file
diff --git a/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.out.txt b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.out.txt
new file mode 100644
index 0000000..7c54a93
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.out.txt
@@ -0,0 +1,12 @@
+@@ -0,0 +1,11 @@
++package org.rstudio.studio.client.workbench.views.vcs.common.diff;
++public class Range
++{
++ Range(int startRow, int rowCount)
++ {
++ this.startRow = startRow;
++ this.rowCount = rowCount;
++ }
++ public int startRow;
++ public int rowCount;
++}
diff --git a/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.txt b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.txt
new file mode 100644
index 0000000..7c54a93
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/client/workbench/views/vcs/common/diff/diff2.txt
@@ -0,0 +1,12 @@
+@@ -0,0 +1,11 @@
++package org.rstudio.studio.client.workbench.views.vcs.common.diff;
++public class Range
++{
++ Range(int startRow, int rowCount)
++ {
++ this.startRow = startRow;
++ this.rowCount = rowCount;
++ }
++ public int startRow;
++ public int rowCount;
++}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/BootRStudio.java b/src/gwt/test/org/rstudio/studio/selenium/BootRStudio.java
new file mode 100644
index 0000000..bb8cb55
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/BootRStudio.java
@@ -0,0 +1,36 @@
+/*
+ * BootRStudio.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+package org.rstudio.studio.selenium;
+
+import org.openqa.selenium.WebDriver;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class BootRStudio {
+
+ @Test
+ public void testRStudioBoot() throws Exception {
+ WebDriver driver = RStudioWebAppDriver.start();
+
+ // Check the title of the page
+ assertEquals(driver.getTitle(), "RStudio");
+
+ // Close the browser
+ RStudioWebAppDriver.stop();
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/ConsoleTestUtils.java b/src/gwt/test/org/rstudio/studio/selenium/ConsoleTestUtils.java
new file mode 100644
index 0000000..b41563e
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/ConsoleTestUtils.java
@@ -0,0 +1,61 @@
+/*
+ * ConsoleTestUtils.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.rstudio.core.client.ElementIds;
+
+public class ConsoleTestUtils
+{
+ public static void beginConsoleInteraction(final WebDriver driver) {
+ // Wait for the console panel to load
+ (new WebDriverWait(driver, 15)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver.findElements(By.id(
+ ElementIds.getElementId(ElementIds.CONSOLE_INPUT)));
+ return elements.size() > 0;
+ }
+ });
+
+ resumeConsoleInteraction(driver);
+ }
+
+ public static void resumeConsoleInteraction(WebDriver driver) {
+ // Click on the console
+ WebElement console = driver.findElement(By.id(
+ ElementIds.getElementId(ElementIds.SHELL_WIDGET)));
+
+ console.click();
+ }
+
+ public static void waitForConsoleContainsText(WebDriver driver,
+ final String text) {
+ final WebElement output = driver.findElement(By.id(
+ ElementIds.getElementId(ElementIds.CONSOLE_OUTPUT)));
+
+ (new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ String outputText = output.getText();
+ return outputText.contains(text);
+ }
+ });
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/DataImportTests.java b/src/gwt/test/org/rstudio/studio/selenium/DataImportTests.java
new file mode 100644
index 0000000..dcbc7f3
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/DataImportTests.java
@@ -0,0 +1,94 @@
+/*
+ * DataImportTests.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.io.File;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class DataImportTests
+{
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ driver_ = RStudioWebAppDriver.start();
+
+ (new WebDriverWait(driver_, 10))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.className("gwt-MenuBar")));
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ RStudioWebAppDriver.stop();
+ }
+
+ @Test
+ public void testImportCSVFile() throws Exception {
+ WebElement menuEntry = MenuNavigator.getMenuItem(driver_,
+ "Tools", "Import Dataset", "From Text File...");
+
+ menuEntry.click();
+ final WebElement importFileDialog =
+ DialogTestUtils.waitForModalToAppear(driver_);
+
+ DialogTestUtils.waitForFocusedInput(driver_, importFileDialog);
+
+ Actions typeName = new Actions(driver_);
+ File csvFile = new File("test/org/rstudio/studio/selenium/resources/banklist.csv");
+ typeName.sendKeys(csvFile.getAbsolutePath());
+ typeName.perform();
+ DialogTestUtils.respondToModalDialog(driver_, "Open");
+
+ // After a moment the modal prompting for the path will disappear, and
+ // the modal prompting for input will appear.
+ (new WebDriverWait(driver_, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver_.findElements(By.className(
+ "gwt-DialogBox-ModalDialog"));
+ if (elements.size() > 0) {
+ if (elements.get(0).getText().contains("Import Dataset")) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+
+ DialogTestUtils.respondToModalDialog(driver_, "Import");
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "read.csv");
+
+ // Once the CSV has been read, make sure all the rows made it to the
+ // generated object
+ ConsoleTestUtils.beginConsoleInteraction(driver_);
+ (new Actions(driver_))
+ .sendKeys(Keys.ESCAPE + "nrow(banklist)" + Keys.ENTER)
+ .perform();
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "515");
+ }
+
+ private static WebDriver driver_;
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/DialogTestUtils.java b/src/gwt/test/org/rstudio/studio/selenium/DialogTestUtils.java
new file mode 100644
index 0000000..1b12897
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/DialogTestUtils.java
@@ -0,0 +1,79 @@
+/*
+ * DialogTestUtils.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class DialogTestUtils
+{
+ public static WebElement waitForModalToAppear(WebDriver driver) {
+ return (new WebDriverWait(driver, 2))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.className("gwt-DialogBox-ModalDialog")));
+ }
+
+ public static void waitForModalToDisappear(final WebDriver driver) {
+ (new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver.findElements(By.className(
+ "gwt-DialogBox-ModalDialog"));
+ return elements.size() == 0;
+ }
+ });
+ }
+
+ public static void respondToModalDialog(WebDriver driver,
+ String response) {
+ WebElement dialog = waitForModalToAppear(driver);
+
+ // Find the button requested and invoke it
+ List<WebElement> buttons =
+ dialog.findElements(By.className("gwt-Button-DialogAction"));
+ for (WebElement button: buttons) {
+ if (button.getText().equals(response)) {
+ button.click();
+ break;
+ }
+ }
+
+ (new WebDriverWait(driver, 5)).until(
+ ExpectedConditions.stalenessOf(dialog));
+ }
+
+ // Unfortunately even after the dialog is loaded, the textbox into which
+ // we need to type may not yet be ready for input. Wait for it to be
+ // present and focused before continuing.
+ public static void waitForFocusedInput(final WebDriver driver,
+ final WebElement dialog) {
+ (new WebDriverWait(driver, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = dialog.findElements(By.tagName(
+ "input"));
+ if (elements.size() > 0 &&
+ driver.switchTo().activeElement().equals(elements.get(0))) {
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/MenuNavigator.java b/src/gwt/test/org/rstudio/studio/selenium/MenuNavigator.java
new file mode 100644
index 0000000..bb9e16d
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/MenuNavigator.java
@@ -0,0 +1,83 @@
+/*
+ * MenuNavigator.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import static org.junit.Assert.*;
+
+public class MenuNavigator
+{
+ public static WebElement findMenuItemByName(WebElement menuElement,
+ String itemName) {
+ List<WebElement> menuItems = menuElement.findElements(
+ By.className("gwt-MenuItem"));
+ WebElement foundMenu = null;
+ for (WebElement menuItem: menuItems) {
+ if (menuItem.getText().startsWith(itemName)) {
+ foundMenu = menuItem;
+ break;
+ }
+ }
+ assertNotNull(foundMenu);
+ return foundMenu;
+ }
+
+
+ public static WebElement getMenuItem(WebDriver driver, String level1,
+ String level2) {
+ WebElement menuBar = driver.findElement(By.className("gwt-MenuBar"));
+ WebElement menu1 = findMenuItemByName(menuBar, level1);
+ menu1.click();
+
+ WebElement menu1Popup = (new WebDriverWait(driver, 1))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.className("gwt-MenuBarPopup")));
+
+ return findMenuItemByName(menu1Popup, level2);
+ }
+
+ public static WebElement getMenuItem(final WebDriver driver, String level1,
+ String level2, String level3)
+ {
+ WebElement popupItem = getMenuItem(driver, level1, level2);
+ Actions action = new Actions(driver);
+ action.moveToElement(popupItem).build().perform();
+
+ // Wait for there to be two popups open (the level1 and level2 menus)
+ (new WebDriverWait(driver, 1)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver.findElements(
+ By.className("gwt-MenuBarPopup"));
+ return elements.size() > 1;
+ }
+ });
+
+ // Get the second popup menu
+ List<WebElement>elements = driver.findElements(
+ By.className("gwt-MenuBarPopup"));
+ WebElement menu2popup = elements.get(1);
+
+ return findMenuItemByName(menu2popup, level3);
+ }
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/RConsoleInteraction.java b/src/gwt/test/org/rstudio/studio/selenium/RConsoleInteraction.java
new file mode 100644
index 0000000..d82988d
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/RConsoleInteraction.java
@@ -0,0 +1,145 @@
+/*
+ * RConsoleInteraction.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.openqa.selenium.Keys;
+import org.rstudio.core.client.ElementIds;
+
+import static org.junit.Assert.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+import junit.framework.Assert;
+
+public class RConsoleInteraction {
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ driver_ = RStudioWebAppDriver.start();
+
+ ConsoleTestUtils.beginConsoleInteraction(driver_);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ RStudioWebAppDriver.stop();
+ }
+
+ @Test
+ public void testBasicRInteraction() {
+ Actions do42 = new Actions(driver_);
+ do42.sendKeys(Keys.chord(Keys.CONTROL, "l"));
+ do42.sendKeys(Keys.ESCAPE);
+ do42.sendKeys("41 + 1");
+ do42.sendKeys(Keys.ENTER);
+ do42.perform();
+
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "42");
+ }
+
+ @Test
+ public void testPopupCompletion() {
+ // Test invoking autocomplete
+ List<WebElement>elements = driver_.findElements(By.id(
+ ElementIds.getElementId(ElementIds.POPUP_COMPLETIONS)));
+ assertEquals(elements.size(), 0);
+
+ Actions popup = new Actions(driver_);
+ popup.sendKeys(Keys.ESCAPE);
+ popup.sendKeys("print");
+ popup.sendKeys(Keys.TAB);
+ popup.perform();
+
+ (new WebDriverWait(driver_, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver_.findElements(By.id(
+ ElementIds.getElementId(ElementIds.POPUP_COMPLETIONS)));
+ return elements.size() > 0;
+ }
+ });
+
+ // Test cancelling autocomplete once invoked
+ Actions close = new Actions(driver_);
+ close.sendKeys(Keys.ESCAPE).perform();
+
+ (new WebDriverWait(driver_, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver_.findElements(By.id(
+ ElementIds.getElementId(ElementIds.POPUP_COMPLETIONS)));
+ return elements.size() == 0;
+ }
+ });
+ }
+
+ @Test
+ public void testPlotGeneration() {
+ ConsoleTestUtils.resumeConsoleInteraction(driver_);
+
+ Actions plotCars = new Actions(driver_);
+ plotCars.sendKeys(Keys.ESCAPE + "plot(cars)" + Keys.ENTER);
+ plotCars.perform();
+
+ // Wait for the Plot window to activate
+ final WebElement plotWindow = (new WebDriverWait(driver_, 5))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.id(ElementIds.getElementId(ElementIds.PLOT_IMAGE_FRAME))));
+
+ // Wait for a plot to appear in the window
+ Assert.assertEquals(plotWindow.getTagName(), "iframe");
+ driver_.switchTo().frame(plotWindow);
+
+ (new WebDriverWait(driver_, 5))
+ .until(ExpectedConditions.presenceOfElementLocated(By.tagName("img")));
+
+ // Switch back to document context
+ driver_.switchTo().defaultContent();
+ }
+
+ @Test
+ public void testInvokeHelp() {
+ ConsoleTestUtils.resumeConsoleInteraction(driver_);
+ Actions help = new Actions(driver_);
+ help.sendKeys(Keys.ESCAPE + "?lapply" + Keys.ENTER);
+ help.perform();
+
+ // Wait for the Help window to activate
+ final WebElement helpWindow = (new WebDriverWait(driver_, 5))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.id(ElementIds.getElementId(ElementIds.HELP_FRAME))));
+
+ // Wait for help to appear in the window
+ Assert.assertEquals(helpWindow.getTagName(), "iframe");
+ driver_.switchTo().frame(helpWindow);
+
+ (new WebDriverWait(driver_, 5))
+ .until(ExpectedConditions.textToBePresentInElement(
+ By.tagName("body"), "lapply"));
+
+ // Switch back to document context
+ driver_.switchTo().defaultContent();
+ }
+
+ private static WebDriver driver_;
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/RStudioTestSuite.java b/src/gwt/test/org/rstudio/studio/selenium/RStudioTestSuite.java
new file mode 100644
index 0000000..ca92518
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/RStudioTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * RStudioTestSuite.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+ at RunWith(Suite.class)
+ at SuiteClasses({ BootRStudio.class,
+ RConsoleInteraction.class,
+ SourceInteraction.class,
+ DataImportTests.class,
+ WorkbenchTests.class
+ })
+public class RStudioTestSuite
+{
+
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/RStudioWebAppDriver.java b/src/gwt/test/org/rstudio/studio/selenium/RStudioWebAppDriver.java
new file mode 100644
index 0000000..9ac5fb2
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/RStudioWebAppDriver.java
@@ -0,0 +1,38 @@
+/*
+ * RStudioWebAppDriver.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.net.URL;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+public class RStudioWebAppDriver
+{
+ public static WebDriver start() throws Exception {
+ driver_ = new RemoteWebDriver(
+ new URL("http://localhost:9515/"), DesiredCapabilities.chrome());
+
+ driver_.get("http://localhost:4011/");
+ return driver_;
+ }
+
+ public static void stop() {
+ driver_.quit();
+ }
+
+ private static WebDriver driver_;
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/SourceInteraction.java b/src/gwt/test/org/rstudio/studio/selenium/SourceInteraction.java
new file mode 100644
index 0000000..4054247
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/SourceInteraction.java
@@ -0,0 +1,149 @@
+/*
+ * SourceInteraction.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.rstudio.core.client.ElementIds;
+
+public class SourceInteraction
+{
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ driver_ = RStudioWebAppDriver.start();
+
+ (new WebDriverWait(driver_, 10))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.className("gwt-MenuBar")));
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ RStudioWebAppDriver.stop();
+ }
+
+ @Test
+ public void createAndSourceRFile() {
+ createRFile();
+
+ // Type some code into the file. Note that the matching brace is auto
+ // completed.
+ Actions a = new Actions(driver_);
+ a.sendKeys("f <- function() {" + Keys.ENTER);
+ a.sendKeys(Keys.TAB + "42");
+ a.perform();
+
+ // Source the entire file
+ WebElement sourceMenuEntry = MenuNavigator.getMenuItem(driver_,
+ "Code", "Source");
+ sourceMenuEntry.click();
+
+ // Wait for the console to contain the string "source"
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "source(");
+
+ closeUnsavedRFile();
+ }
+
+ @Test
+ public void findAndReplace() {
+ createRFile();
+
+ // Type some code into the file
+ String preReplaceCode = "foo <- 'bar'";
+ Actions a = new Actions(driver_);
+ a.sendKeys(preReplaceCode + Keys.ENTER);
+ a.perform();
+
+ // Find the ACE editor instance that the code appears in. (CONSIDER:
+ // This is not the best way to find the code editor instance.)
+ WebElement editor = null;
+ List<WebElement> editors = driver_.findElements(
+ By.className("ace_content"));
+ for (WebElement e: editors) {
+ if (e.getText().contains(preReplaceCode)) {
+ editor = e;
+ break;
+ }
+ }
+ Assert.assertNotNull(editor);
+
+ // Invoke find and replace
+ WebElement findMenuEntry = MenuNavigator.getMenuItem(driver_,
+ "Edit", "Find...");
+ findMenuEntry.click();
+
+ // Wait for the find and replace panel to come up
+ (new WebDriverWait(driver_, 2))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.id(ElementIds.getElementId(ElementIds.FIND_REPLACE_BAR))));
+
+ // Type the text and the text to be replaced (replace 'bar' with 'foo')
+ Actions rep = new Actions(driver_);
+ rep.sendKeys("bar" + Keys.TAB + "foo" + Keys.ENTER);
+ rep.perform();
+
+ DialogTestUtils.respondToModalDialog(driver_, "OK");
+
+ Actions dismiss = new Actions(driver_);
+ dismiss.sendKeys(Keys.ESCAPE);
+ dismiss.perform();
+
+ // Ensure that the source has been updated
+ Assert.assertTrue(editor.getText().contains("foo <- 'foo'"));
+
+ closeUnsavedRFile();
+ }
+
+ private void createRFile() {
+ WebElement newRScriptMenuEntry = MenuNavigator.getMenuItem(driver_,
+ "File", "New File", "R Script");
+ newRScriptMenuEntry.click();
+
+ // Wait for the "Untitled" buffer to appear
+ (new WebDriverWait(driver_, 5)).until(new ExpectedCondition<Boolean>() {
+ public Boolean apply(WebDriver d) {
+ List<WebElement>elements = driver_.findElements(By.className(
+ "gwt-TabLayoutPanelTab-selected"));
+ for (WebElement e: elements) {
+ if (e.getText().startsWith("Untitled")) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ }
+
+ private void closeUnsavedRFile() {
+ WebElement closeEntry = MenuNavigator.getMenuItem(driver_,
+ "File", "Close");
+ closeEntry.click();
+ DialogTestUtils.respondToModalDialog(driver_, "Don't Save");
+ }
+
+ private static WebDriver driver_;
+}
diff --git a/src/gwt/test/org/rstudio/studio/selenium/WorkbenchTests.java b/src/gwt/test/org/rstudio/studio/selenium/WorkbenchTests.java
new file mode 100644
index 0000000..fccb45c
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/WorkbenchTests.java
@@ -0,0 +1,115 @@
+/*
+ * WorkbenchTests.java
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+package org.rstudio.studio.selenium;
+
+import java.io.File;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class WorkbenchTests
+{
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ driver_ = RStudioWebAppDriver.start();
+
+ (new WebDriverWait(driver_, 10))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.className("gwt-MenuBar")));
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ RStudioWebAppDriver.stop();
+ }
+
+ @Test
+ public void testWorkbenchPersistance() throws Exception {
+ clearWorkspace();
+
+ // Add a variable to the workspace
+ (new Actions(driver_))
+ .sendKeys(Keys.ESCAPE + "selenium <- function() { 42 }" + Keys.ENTER)
+ .perform();
+
+ // Save the workspace
+ WebElement saveItem = MenuNavigator.getMenuItem(driver_,
+ "Session", "Save Workspace As...");
+ saveItem.click();
+
+ WebElement saveDialog = DialogTestUtils.waitForModalToAppear(driver_);
+ DialogTestUtils.waitForFocusedInput(driver_, saveDialog);
+
+ File tempFile = File.createTempFile("rstudio-selenium-workspace-", ".RData");
+ String workspaceFilePath = tempFile.getAbsolutePath();
+ tempFile.delete();
+
+ (new Actions(driver_))
+ .sendKeys(workspaceFilePath + Keys.ENTER)
+ .perform();
+
+ DialogTestUtils.waitForModalToDisappear(driver_);
+
+ // Clear the workspace and load it again
+ clearWorkspace();
+
+ WebElement loadItem = MenuNavigator.getMenuItem(driver_,
+ "Session", "Load Workspace...");
+ loadItem.click();
+
+ WebElement loadDialog = DialogTestUtils.waitForModalToAppear(driver_);
+ DialogTestUtils.waitForFocusedInput(driver_, loadDialog);
+
+ (new Actions(driver_))
+ .sendKeys(workspaceFilePath + Keys.ENTER)
+ .perform();
+
+ DialogTestUtils.waitForModalToDisappear(driver_);
+
+ // List the workspace and see if the variable is present
+ ConsoleTestUtils.beginConsoleInteraction(driver_);
+ (new Actions(driver_))
+ .sendKeys(Keys.chord(Keys.CONTROL + "l"))
+ .sendKeys(Keys.ESCAPE + "ls()" + Keys.ENTER)
+ .perform();
+
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "selenium");
+ }
+
+ private void clearWorkspace() {
+ // Clear out the workspace and make sure it's clear
+ WebElement menuItem = MenuNavigator.getMenuItem(driver_,
+ "Session", "Clear Workspace...");
+ menuItem.click();
+ DialogTestUtils.respondToModalDialog(driver_, "Yes");
+
+ ConsoleTestUtils.beginConsoleInteraction(driver_);
+ (new Actions(driver_))
+ .sendKeys(Keys.ESCAPE + "ls()" + Keys.ENTER)
+ .perform();
+
+ ConsoleTestUtils.waitForConsoleContainsText(driver_, "character(0)");
+ }
+
+ private static WebDriver driver_;
+}
\ No newline at end of file
diff --git a/src/gwt/test/org/rstudio/studio/selenium/resources/banklist.csv b/src/gwt/test/org/rstudio/studio/selenium/resources/banklist.csv
new file mode 100644
index 0000000..e5882ed
--- /dev/null
+++ b/src/gwt/test/org/rstudio/studio/selenium/resources/banklist.csv
@@ -0,0 +1,516 @@
+Bank Name,City,ST,CERT,Acquiring Institution,Closing Date,Updated Date
+Bank of Jackson County,Graceville,FL,14794,First Federal Bank of Florida,30-Oct-13,4-Nov-13
+First National Bank also operating as The National Bank of El Paso,Edinburg,TX,14318,PlainsCapital Bank,13-Sep-13,1-Nov-13
+The Community's Bank,Bridgeport,CT,57041,No Acquirer,13-Sep-13,24-Oct-13
+Sunrise Bank of Arizona,Phoenix,AZ,34707,"First Fidelity Bank, National Association",23-Aug-13,1-Nov-13
+Community South Bank,Parsons,TN,19849,"CB&S Bank, Inc.",23-Aug-13,1-Nov-13
+Bank of Wausau,Wausau,WI,35016,Nicolet National Bank,9-Aug-13,24-Oct-13
+First Community Bank of Southwest Florida (also operating as Community Bank of Cape Coral),Fort Myers,FL,34943,C1 Bank,2-Aug-13,24-Oct-13
+Mountain National Bank,Sevierville,TN,34789,"First Tennessee Bank, National Association",7-Jun-13,12-Jul-13
+1st Commerce Bank,North Las Vegas,NV,58358,Plaza Bank,6-Jun-13,12-Jul-13
+Banks of Wisconsin d/b/a Bank of Kenosha,Kenosha,WI,35386,"North Shore Bank, FSB",31-May-13,29-Oct-13
+Central Arizona Bank,Scottsdale,AZ,34527,Western State Bank,14-May-13,12-Jul-13
+Sunrise Bank,Valdosta,GA,58185,Synovus Bank,10-May-13,12-Jul-13
+Pisgah Community Bank,Asheville,NC,58701,"Capital Bank, N.A.",10-May-13,5-Aug-13
+Douglas County Bank,Douglasville,GA,21649,Hamilton State Bank,26-Apr-13,12-Jul-13
+Parkway Bank,Lenoir,NC,57158,"CertusBank, National Association",26-Apr-13,12-Jul-13
+Chipola Community Bank,Marianna,FL,58034,First Federal Bank of Florida,19-Apr-13,12-Jul-13
+Heritage Bank of North Florida,Orange Park,FL,26680,FirstAtlantic Bank,19-Apr-13,12-Jul-13
+First Federal Bank,Lexington,KY,29594,Your Community Bank,19-Apr-13,12-Jul-13
+Gold Canyon Bank,Gold Canyon,AZ,58066,"First Scottsdale Bank, National Association",5-Apr-13,12-Jul-13
+Frontier Bank,LaGrange,GA,16431,HeritageBank of the South,8-Mar-13,26-Mar-13
+Covenant Bank,Chicago,IL,22476,Liberty Bank and Trust Company,15-Feb-13,4-Mar-13
+1st Regents Bank,Andover,MN,57157,First Minnesota Bank,18-Jan-13,28-Feb-13
+Westside Community Bank,University Place,WA,33997,Sunwest Bank,11-Jan-13,24-Jan-13
+Community Bank of the Ozarks,Sunrise Beach,MO,27331,Bank of Sullivan,14-Dec-12,24-Jan-13
+Hometown Community Bank,Braselton,GA,57928,"CertusBank, National Association",16-Nov-12,25-Nov-13
+Citizens First National Bank,Princeton,IL,3731,Heartland Bank and Trust Company,2-Nov-12,22-Nov-13
+Heritage Bank of Florida,Lutz,FL,35009,Centennial Bank,2-Nov-12,21-Nov-13
+NOVA Bank,Berwyn,PA,27148,No Acquirer,26-Oct-12,24-Jan-13
+Excel Bank,Sedalia,MO,19189,Simmons First National Bank,19-Oct-12,22-Oct-13
+First East Side Savings Bank,Tamarac,FL,28144,Stearns Bank N.A.,19-Oct-12,22-Oct-13
+GulfSouth Private Bank,Destin,FL,58073,SmartBank,19-Oct-12,22-Oct-13
+First United Bank,Crete,IL,20685,"Old Plank Trail Community Bank, National Association",28-Sep-12,1-Nov-13
+Truman Bank,St. Louis,MO,27316,Simmons First National Bank,14-Sep-12,17-Dec-12
+First Commercial Bank,Bloomington,MN,35246,Republic Bank & Trust Company,7-Sep-12,17-Dec-12
+Waukegan Savings Bank,Waukegan,IL,28243,First Midwest Bank,3-Aug-12,30-Jul-13
+Jasper Banking Company,Jasper,GA,16240,Stearns Bank N.A.,27-Jul-12,30-Jul-13
+Second Federal Savings and Loan Association of Chicago,Chicago,IL,27986,Hinsdale Bank & Trust Company,20-Jul-12,14-Jan-13
+Heartland Bank,Leawood,KS,1361,Metcalf Bank,20-Jul-12,30-Jul-13
+First Cherokee State Bank,Woodstock,GA,32711,Community & Southern Bank,20-Jul-12,30-Jul-13
+Georgia Trust Bank,Buford,GA,57847,Community & Southern Bank,20-Jul-12,29-Jul-13
+The Royal Palm Bank of Florida,Naples,FL,57096,First National Bank of the Gulf Coast,20-Jul-12,29-Jul-13
+Glasgow Savings Bank,Glasgow,MO,1056,Regional Missouri Bank,13-Jul-12,29-Jul-13
+Montgomery Bank & Trust,Ailey,GA,19498,Ameris Bank,6-Jul-12,31-Oct-12
+The Farmers Bank of Lynchburg,Lynchburg,TN,1690,Clayton Bank and Trust,15-Jun-12,27-Jun-13
+Security Exchange Bank,Marietta,GA,35299,Fidelity Bank,15-Jun-12,10-Oct-12
+Putnam State Bank,Palatka,FL,27405,Harbor Community Bank,15-Jun-12,10-Oct-12
+Waccamaw Bank,Whiteville,NC,34515,First Community Bank,8-Jun-12,14-Jun-13
+Farmers' and Traders' State Bank,Shabbona,IL,9257,First State Bank,8-Jun-12,10-Oct-12
+Carolina Federal Savings Bank,Charleston,SC,35372,Bank of North Carolina,8-Jun-12,31-Oct-12
+First Capital Bank,Kingfisher,OK,416,F & M Bank,8-Jun-12,10-Jun-13
+"Alabama Trust Bank, National Association",Sylacauga,AL,35224,Southern States Bank,18-May-12,20-May-13
+"Security Bank, National Association",North Lauderdale,FL,23156,Banesco USA,4-May-12,31-Oct-12
+Palm Desert National Bank,Palm Desert,CA,23632,Pacific Premier Bank,27-Apr-12,17-May-13
+Plantation Federal Bank,Pawleys Island,SC,32503,First Federal Bank,27-Apr-12,17-May-13
+"Inter Savings Bank, fsb D/B/A InterBank, fsb",Maple Grove,MN,31495,Great Southern Bank,27-Apr-12,17-May-13
+HarVest Bank of Maryland,Gaithersburg,MD,57766,Sonabank,27-Apr-12,17-May-13
+Bank of the Eastern Shore,Cambridge,MD,26759,No Acquirer,27-Apr-12,17-Oct-12
+"Fort Lee Federal Savings Bank, FSB",Fort Lee,NJ,35527,Alma Bank,20-Apr-12,17-May-13
+Fidelity Bank,Dearborn,MI,33883,The Huntington National Bank,30-Mar-12,16-May-13
+Premier Bank,Wilmette,IL,35419,International Bank of Chicago,23-Mar-12,17-Oct-12
+Covenant Bank & Trust,Rock Spring,GA,58068,"Stearns Bank, N.A.",23-Mar-12,31-Oct-12
+New City Bank,Chicago,IL,57597,No Acquirer,9-Mar-12,29-Oct-12
+Global Commerce Bank,Doraville,GA,34046,Metro City Bank,2-Mar-12,31-Oct-12
+Home Savings of America,Little Falls,MN,29178,No Acquirer,24-Feb-12,17-Dec-12
+Central Bank of Georgia,Ellaville,GA,5687,Ameris Bank,24-Feb-12,9-Aug-12
+SCB Bank,Shelbyville,IN,29761,"First Merchants Bank, National Association",10-Feb-12,25-Mar-13
+Charter National Bank and Trust,Hoffman Estates,IL,23187,"Barrington Bank & Trust Company, National Association",10-Feb-12,25-Mar-13
+BankEast,Knoxville,TN,19869,"U.S. Bank, N.A.",27-Jan-12,8-Mar-13
+Patriot Bank Minnesota,Forest Lake,MN,34823,First Resource Bank,27-Jan-12,12-Sep-12
+Tennessee Commerce Bank,Franklin,TN,35296,Republic Bank & Trust Company,27-Jan-12,20-Nov-12
+First Guaranty Bank and Trust Company of Jacksonville,Jacksonville,FL,16579,"CenterState Bank of Florida, N.A.",27-Jan-12,12-Sep-12
+American Eagle Savings Bank,Boothwyn,PA,31581,"Capital Bank, N.A.",20-Jan-12,25-Jan-13
+The First State Bank,Stockbridge,GA,19252,Hamilton State Bank,20-Jan-12,25-Jan-13
+Central Florida State Bank,Belleview,FL,57186,"CenterState Bank of Florida, N.A.",20-Jan-12,25-Jan-13
+Western National Bank,Phoenix,AZ,57917,Washington Federal,16-Dec-11,12-Nov-13
+Premier Community Bank of the Emerald Coast,Crestview,FL,58343,Summit Bank,16-Dec-11,12-Sep-12
+Central Progressive Bank,Lacombe,LA,19657,First NBC Bank,18-Nov-11,13-Aug-12
+Polk County Bank,Johnston,IA,14194,Grinnell State Bank,18-Nov-11,15-Aug-12
+Community Bank of Rockmart,Rockmart,GA,57860,Century Bank of Georgia,10-Nov-11,13-Aug-12
+SunFirst Bank,Saint George,UT,57087,Cache Valley Bank,4-Nov-11,16-Nov-12
+"Mid City Bank, Inc.",Omaha,NE,19397,Premier Bank,4-Nov-11,15-Aug-12
+All American Bank,Des Plaines,IL,57759,International Bank of Chicago,28-Oct-11,15-Aug-12
+Community Banks of Colorado,Greenwood Village,CO,21132,"Bank Midwest, N.A.",21-Oct-11,2-Jan-13
+Community Capital Bank,Jonesboro,GA,57036,State Bank and Trust Company,21-Oct-11,8-Nov-12
+Decatur First Bank,Decatur,GA,34392,Fidelity Bank,21-Oct-11,8-Nov-12
+Old Harbor Bank,Clearwater,FL,57537,1st United Bank,21-Oct-11,8-Nov-12
+Country Bank,Aledo,IL,35395,Blackhawk Bank & Trust,14-Oct-11,15-Aug-12
+First State Bank,Cranford,NJ,58046,Northfield Bank,14-Oct-11,8-Nov-12
+"Blue Ridge Savings Bank, Inc.",Asheville,NC,32347,Bank of North Carolina,14-Oct-11,8-Nov-12
+Piedmont Community Bank,Gray,GA,57256,State Bank and Trust Company,14-Oct-11,22-Jan-13
+Sun Security Bank,Ellington,MO,20115,Great Southern Bank,7-Oct-11,7-Nov-12
+The RiverBank,Wyoming,MN,10216,Central Bank,7-Oct-11,7-Nov-12
+First International Bank,Plano,TX,33513,American First National Bank,30-Sep-11,9-Oct-12
+Citizens Bank of Northern California,Nevada City,CA,33983,Tri Counties Bank,23-Sep-11,9-Oct-12
+Bank of the Commonwealth,Norfolk,VA,20408,Southern Bank and Trust Company,23-Sep-11,9-Oct-12
+The First National Bank of Florida,Milton,FL,25155,CharterBank,9-Sep-11,6-Sep-12
+CreekSide Bank,Woodstock,GA,58226,Georgia Commerce Bank,2-Sep-11,6-Sep-12
+Patriot Bank of Georgia,Cumming,GA,58273,Georgia Commerce Bank,2-Sep-11,2-Nov-12
+First Choice Bank,Geneva,IL,57212,Inland Bank & Trust,19-Aug-11,15-Aug-12
+First Southern National Bank,Statesboro,GA,57239,Heritage Bank of the South,19-Aug-11,2-Nov-12
+Lydian Private Bank,Palm Beach,FL,35356,"Sabadell United Bank, N.A.",19-Aug-11,2-Nov-12
+Public Savings Bank,Huntingdon Valley,PA,34130,"Capital Bank, N.A.",18-Aug-11,15-Aug-12
+The First National Bank of Olathe,Olathe,KS,4744,Enterprise Bank & Trust,12-Aug-11,23-Aug-12
+Bank of Whitman,Colfax,WA,22528,Columbia State Bank,5-Aug-11,16-Aug-12
+Bank of Shorewood,Shorewood,IL,22637,Heartland Bank and Trust Company,5-Aug-11,16-Aug-12
+Integra Bank National Association,Evansville,IN,4392,Old National Bank,29-Jul-11,16-Aug-12
+"BankMeridian, N.A.",Columbia,SC,58222,SCBT National Association,29-Jul-11,2-Nov-12
+Virginia Business Bank,Richmond,VA,58283,Xenith Bank,29-Jul-11,9-Oct-12
+Bank of Choice,Greeley,CO,2994,"Bank Midwest, N.A.",22-Jul-11,12-Sep-12
+LandMark Bank of Florida,Sarasota,FL,35244,American Momentum Bank,22-Jul-11,2-Nov-12
+Southshore Community Bank,Apollo Beach,FL,58056,American Momentum Bank,22-Jul-11,2-Nov-12
+Summit Bank,Prescott,AZ,57442,The Foothills Bank,15-Jul-11,16-Aug-12
+First Peoples Bank,Port St. Lucie,FL,34870,"Premier American Bank, N.A.",15-Jul-11,2-Nov-12
+High Trust Bank,Stockbridge,GA,19554,Ameris Bank,15-Jul-11,2-Nov-12
+One Georgia Bank,Atlanta,GA,58238,Ameris Bank,15-Jul-11,2-Nov-12
+Signature Bank,Windsor,CO,57835,Points West Community Bank,8-Jul-11,26-Oct-12
+Colorado Capital Bank,Castle Rock,CO,34522,First-Citizens Bank & Trust Company,8-Jul-11,15-Jan-13
+First Chicago Bank & Trust,Chicago,IL,27935,Northbrook Bank & Trust Company,8-Jul-11,9-Sep-12
+Mountain Heritage Bank,Clayton,GA,57593,First American Bank and Trust Company,24-Jun-11,2-Nov-12
+First Commercial Bank of Tampa Bay,Tampa,FL,27583,Stonegate Bank,17-Jun-11,2-Nov-12
+McIntosh State Bank,Jackson,GA,19237,Hamilton State Bank,17-Jun-11,2-Nov-12
+Atlantic Bank and Trust,Charleston,SC,58420,"First Citizens Bank and Trust Company, Inc.",3-Jun-11,31-Jul-13
+First Heritage Bank,Snohomish,WA,23626,Columbia State Bank,27-May-11,28-Jan-13
+Summit Bank,Burlington,WA,513,Columbia State Bank,20-May-11,22-Jan-13
+First Georgia Banking Company,Franklin,GA,57647,"CertusBank, National Association",20-May-11,13-Nov-12
+Atlantic Southern Bank,Macon,GA,57213,"CertusBank, National Association",20-May-11,31-Oct-12
+Coastal Bank,Cocoa Beach,FL,34898,"Florida Community Bank, a division of Premier American Bank, N.A.",6-May-11,30-Nov-12
+Community Central Bank,Mount Clemens,MI,34234,Talmer Bank & Trust,29-Apr-11,16-Aug-12
+The Park Avenue Bank,Valdosta,GA,19797,Bank of the Ozarks,29-Apr-11,30-Nov-12
+First Choice Community Bank,Dallas,GA,58539,Bank of the Ozarks,29-Apr-11,22-Jan-13
+Cortez Community Bank,Brooksville,FL,57625,"Florida Community Bank, a division of Premier American Bank, N.A.",29-Apr-11,30-Nov-12
+First National Bank of Central Florida,Winter Park,FL,26297,"Florida Community Bank, a division of Premier American Bank, N.A.",29-Apr-11,30-Nov-12
+Heritage Banking Group,Carthage,MS,14273,Trustmark National Bank,15-Apr-11,30-Nov-12
+Rosemount National Bank,Rosemount,MN,24099,Central Bank,15-Apr-11,16-Aug-12
+Superior Bank,Birmingham,AL,17750,"Superior Bank, National Association",15-Apr-11,30-Nov-12
+Nexity Bank,Birmingham,AL,19794,AloStar Bank of Commerce,15-Apr-11,4-Sep-12
+New Horizons Bank,East Ellijay,GA,57705,Citizens South Bank,15-Apr-11,16-Aug-12
+Bartow County Bank,Cartersville,GA,21495,Hamilton State Bank,15-Apr-11,22-Jan-13
+Nevada Commerce Bank,Las Vegas,NV,35418,City National Bank,8-Apr-11,9-Sep-12
+Western Springs National Bank and Trust,Western Springs,IL,10086,Heartland Bank and Trust Company,8-Apr-11,22-Jan-13
+The Bank of Commerce,Wood Dale,IL,34292,Advantage National Bank Group,25-Mar-11,22-Jan-13
+Legacy Bank,Milwaukee,WI,34818,Seaway Bank and Trust Company,11-Mar-11,12-Sep-12
+First National Bank of Davis,Davis,OK,4077,The Pauls Valley National Bank,11-Mar-11,20-Aug-12
+Valley Community Bank,St. Charles,IL,34187,First State Bank,25-Feb-11,12-Sep-12
+"San Luis Trust Bank, FSB",San Luis Obispo,CA,34783,First California Bank,18-Feb-11,20-Aug-12
+Charter Oak Bank,Napa,CA,57855,Bank of Marin,18-Feb-11,12-Sep-12
+Citizens Bank of Effingham,Springfield,GA,34601,Heritage Bank of the South,18-Feb-11,2-Nov-12
+Habersham Bank,Clarkesville,GA,151,SCBT National Association,18-Feb-11,2-Nov-12
+Canyon National Bank,Palm Springs,CA,34692,Pacific Premier Bank,11-Feb-11,12-Sep-12
+Badger State Bank,Cassville,WI,13272,Royal Bank,11-Feb-11,12-Sep-12
+Peoples State Bank,Hamtramck,MI,14939,First Michigan Bank,11-Feb-11,22-Jan-13
+Sunshine State Community Bank,Port Orange,FL,35478,"Premier American Bank, N.A.",11-Feb-11,2-Nov-12
+Community First Bank Chicago,Chicago,IL,57948,Northbrook Bank & Trust Company,4-Feb-11,20-Aug-12
+North Georgia Bank,Watkinsville,GA,35242,BankSouth,4-Feb-11,2-Nov-12
+American Trust Bank,Roswell,GA,57432,Renasant Bank,4-Feb-11,31-Oct-12
+First Community Bank,Taos,NM,12261,"U.S. Bank, N.A.",28-Jan-11,12-Sep-12
+FirsTier Bank,Louisville,CO,57646,No Acquirer,28-Jan-11,12-Sep-12
+Evergreen State Bank,Stoughton,WI,5328,McFarland State Bank,28-Jan-11,12-Sep-12
+The First State Bank,Camargo,OK,2303,Bank 7,28-Jan-11,12-Sep-12
+United Western Bank,Denver,CO,31293,First-Citizens Bank & Trust Company,21-Jan-11,12-Sep-12
+The Bank of Asheville,Asheville,NC,34516,First Bank,21-Jan-11,2-Nov-12
+CommunitySouth Bank & Trust,Easley,SC,57868,"CertusBank, National Association",21-Jan-11,2-Nov-12
+Enterprise Banking Company,McDonough,GA,19758,No Acquirer,21-Jan-11,2-Nov-12
+Oglethorpe Bank,Brunswick,GA,57440,Bank of the Ozarks,14-Jan-11,2-Nov-12
+Legacy Bank,Scottsdale,AZ,57820,Enterprise Bank & Trust,7-Jan-11,12-Sep-12
+First Commercial Bank of Florida,Orlando,FL,34965,First Southern Bank,7-Jan-11,2-Nov-12
+Community National Bank,Lino Lakes,MN,23306,Farmers & Merchants Savings Bank,17-Dec-10,20-Aug-12
+First Southern Bank,Batesville,AR,58052,Southern Bank,17-Dec-10,20-Aug-12
+"United Americas Bank, N.A.",Atlanta,GA,35065,State Bank and Trust Company,17-Dec-10,2-Nov-12
+"Appalachian Community Bank, FSB",McCaysville,GA,58495,Peoples Bank of East Tennessee,17-Dec-10,31-Oct-12
+Chestatee State Bank,Dawsonville,GA,34578,Bank of the Ozarks,17-Dec-10,2-Nov-12
+"The Bank of Miami,N.A.",Coral Gables,FL,19040,1st United Bank,17-Dec-10,2-Nov-12
+Earthstar Bank,Southampton,PA,35561,Polonia Bank,10-Dec-10,20-Aug-12
+Paramount Bank,Farmington Hills,MI,34673,Level One Bank,10-Dec-10,20-Aug-12
+First Banking Center,Burlington,WI,5287,First Michigan Bank,19-Nov-10,20-Aug-12
+Allegiance Bank of North America,Bala Cynwyd,PA,35078,VIST Bank,19-Nov-10,20-Aug-12
+Gulf State Community Bank,Carrabelle,FL,20340,Centennial Bank,19-Nov-10,2-Nov-12
+Copper Star Bank,Scottsdale,AZ,35463,"Stearns Bank, N.A.",12-Nov-10,20-Aug-12
+Darby Bank & Trust Co.,Vidalia,GA,14580,Ameris Bank,12-Nov-10,15-Jan-13
+Tifton Banking Company,Tifton,GA,57831,Ameris Bank,12-Nov-10,2-Nov-12
+First Vietnamese American Bank,Westminster,CA,57885,Grandpoint Bank,5-Nov-10,12-Sep-12
+Pierce Commercial Bank,Tacoma,WA,34411,Heritage Bank,5-Nov-10,20-Aug-12
+Western Commercial Bank,Woodland Hills,CA,58087,First California Bank,5-Nov-10,12-Sep-12
+K Bank,Randallstown,MD,31263,Manufacturers and Traders Trust Company (M&T Bank),5-Nov-10,20-Aug-12
+"First Arizona Savings, A FSB",Scottsdale,AZ,32582,No Acquirer,22-Oct-10,20-Aug-12
+Hillcrest Bank,Overland Park,KS,22173,"Hillcrest Bank, N.A.",22-Oct-10,20-Aug-12
+First Suburban National Bank,Maywood,IL,16089,Seaway Bank and Trust Company,22-Oct-10,20-Aug-12
+The First National Bank of Barnesville,Barnesville,GA,2119,United Bank,22-Oct-10,2-Nov-12
+The Gordon Bank,Gordon,GA,33904,Morris Bank,22-Oct-10,2-Nov-12
+Progress Bank of Florida,Tampa,FL,32251,Bay Cities Bank,22-Oct-10,2-Nov-12
+First Bank of Jacksonville,Jacksonville,FL,27573,Ameris Bank,22-Oct-10,2-Nov-12
+Premier Bank,Jefferson City,MO,34016,Providence Bank,15-Oct-10,20-Aug-12
+WestBridge Bank and Trust Company,Chesterfield,MO,58205,Midland States Bank,15-Oct-10,20-Aug-12
+"Security Savings Bank, F.S.B.",Olathe,KS,30898,Simmons First National Bank,15-Oct-10,20-Aug-12
+Shoreline Bank,Shoreline,WA,35250,GBC International Bank,1-Oct-10,20-Aug-12
+Wakulla Bank,Crawfordville,FL,21777,Centennial Bank,1-Oct-10,2-Nov-12
+North County Bank,Arlington,WA,35053,Whidbey Island Bank,24-Sep-10,20-Aug-12
+Haven Trust Bank Florida,Ponte Vedra Beach,FL,58308,First Southern Bank,24-Sep-10,5-Nov-12
+Maritime Savings Bank,West Allis,WI,28612,"North Shore Bank, FSB",17-Sep-10,20-Aug-12
+Bramble Savings Bank,Milford,OH,27808,Foundation Bank,17-Sep-10,20-Aug-12
+The Peoples Bank,Winder,GA,182,Community & Southern Bank,17-Sep-10,5-Nov-12
+First Commerce Community Bank,Douglasville,GA,57448,Community & Southern Bank,17-Sep-10,15-Jan-13
+Bank of Ellijay,Ellijay,GA,58197,Community & Southern Bank,17-Sep-10,15-Jan-13
+ISN Bank,Cherry Hill,NJ,57107,Customers Bank,17-Sep-10,22-Aug-12
+Horizon Bank,Bradenton,FL,35061,Bank of the Ozarks,10-Sep-10,5-Nov-12
+Sonoma Valley Bank,Sonoma,CA,27259,Westamerica Bank,20-Aug-10,12-Sep-12
+Los Padres Bank,Solvang,CA,32165,Pacific Western Bank,20-Aug-10,12-Sep-12
+Butte Community Bank,Chico,CA,33219,"Rabobank, N.A.",20-Aug-10,12-Sep-12
+Pacific State Bank,Stockton,CA,27090,"Rabobank, N.A.",20-Aug-10,12-Sep-12
+ShoreBank,Chicago,IL,15640,Urban Partnership Bank,20-Aug-10,16-May-13
+Imperial Savings and Loan Association,Martinsville,VA,31623,"River Community Bank, N.A.",20-Aug-10,24-Aug-12
+Independent National Bank,Ocala,FL,27344,"CenterState Bank of Florida, N.A.",20-Aug-10,5-Nov-12
+Community National Bank at Bartow,Bartow,FL,25266,"CenterState Bank of Florida, N.A.",20-Aug-10,5-Nov-12
+Palos Bank and Trust Company,Palos Heights,IL,17599,First Midwest Bank,13-Aug-10,22-Aug-12
+Ravenswood Bank,Chicago,IL,34231,Northbrook Bank & Trust Company,6-Aug-10,22-Aug-12
+LibertyBank,Eugene,OR,31964,Home Federal Bank,30-Jul-10,22-Aug-12
+The Cowlitz Bank,Longview,WA,22643,Heritage Bank,30-Jul-10,22-Aug-12
+Coastal Community Bank,Panama City Beach,FL,9619,Centennial Bank,30-Jul-10,5-Nov-12
+Bayside Savings Bank,Port Saint Joe,FL,57669,Centennial Bank,30-Jul-10,5-Nov-12
+Northwest Bank & Trust,Acworth,GA,57658,State Bank and Trust Company,30-Jul-10,5-Nov-12
+Home Valley Bank,Cave Junction,OR,23181,South Valley Bank & Trust,23-Jul-10,12-Sep-12
+SouthwestUSA Bank,Las Vegas,NV,35434,Plaza Bank,23-Jul-10,22-Aug-12
+Community Security Bank,New Prague,MN,34486,Roundbank,23-Jul-10,12-Sep-12
+Thunder Bank,Sylvan Grove,KS,10506,The Bennington State Bank,23-Jul-10,13-Sep-12
+Williamsburg First National Bank,Kingstree,SC,17837,"First Citizens Bank and Trust Company, Inc.",23-Jul-10,5-Nov-12
+Crescent Bank and Trust Company,Jasper,GA,27559,Renasant Bank,23-Jul-10,5-Nov-12
+Sterling Bank,Lantana,FL,32536,IBERIABANK,23-Jul-10,5-Nov-12
+"Mainstreet Savings Bank, FSB",Hastings,MI,28136,Commercial Bank,16-Jul-10,13-Sep-12
+Olde Cypress Community Bank,Clewiston,FL,28864,"CenterState Bank of Florida, N.A.",16-Jul-10,5-Nov-12
+Turnberry Bank,Aventura,FL,32280,NAFH National Bank,16-Jul-10,5-Nov-12
+Metro Bank of Dade County,Miami,FL,25172,NAFH National Bank,16-Jul-10,5-Nov-12
+First National Bank of the South,Spartanburg,SC,35383,NAFH National Bank,16-Jul-10,5-Nov-12
+Woodlands Bank,Bluffton,SC,32571,Bank of the Ozarks,16-Jul-10,5-Nov-12
+Home National Bank,Blackwell,OK,11636,RCB Bank,9-Jul-10,10-Dec-12
+USA Bank,Port Chester,NY,58072,New Century Bank,9-Jul-10,14-Sep-12
+Ideal Federal Savings Bank,Baltimore,MD,32456,No Acquirer,9-Jul-10,14-Sep-12
+Bay National Bank,Baltimore,MD,35462,"Bay Bank, FSB",9-Jul-10,15-Jan-13
+High Desert State Bank,Albuquerque,NM,35279,First American Bank,25-Jun-10,14-Sep-12
+First National Bank,Savannah,GA,34152,"The Savannah Bank, N.A.",25-Jun-10,5-Nov-12
+Peninsula Bank,Englewood,FL,26563,"Premier American Bank, N.A.",25-Jun-10,5-Nov-12
+Nevada Security Bank,Reno,NV,57110,Umpqua Bank,18-Jun-10,23-Aug-12
+Washington First International Bank,Seattle,WA,32955,East West Bank,11-Jun-10,14-Sep-12
+TierOne Bank,Lincoln,NE,29341,Great Western Bank,4-Jun-10,14-Sep-12
+Arcola Homestead Savings Bank,Arcola,IL,31813,No Acquirer,4-Jun-10,14-Sep-12
+First National Bank,Rosedale,MS,15814,The Jefferson Bank,4-Jun-10,5-Nov-12
+Sun West Bank,Las Vegas,NV,34785,City National Bank,28-May-10,14-Sep-12
+"Granite Community Bank, NA",Granite Bay,CA,57315,Tri Counties Bank,28-May-10,14-Sep-12
+Bank of Florida - Tampa,Tampa,FL,57814,EverBank,28-May-10,5-Nov-12
+Bank of Florida - Southwest,Naples,FL,35106,EverBank,28-May-10,5-Nov-12
+Bank of Florida - Southeast,Fort Lauderdale,FL,57360,EverBank,28-May-10,5-Nov-12
+Pinehurst Bank,Saint Paul,MN,57735,Coulee Bank,21-May-10,26-Oct-12
+Midwest Bank and Trust Company,Elmwood Park,IL,18117,"FirstMerit Bank, N.A.",14-May-10,23-Aug-12
+Southwest Community Bank,Springfield,MO,34255,Simmons First National Bank,14-May-10,23-Aug-12
+New Liberty Bank,Plymouth,MI,35586,Bank of Ann Arbor,14-May-10,23-Aug-12
+Satilla Community Bank,Saint Marys,GA,35114,Ameris Bank,14-May-10,5-Nov-12
+1st Pacific Bank of California,San Diego,CA,35517,City National Bank,7-May-10,13-Dec-12
+Towne Bank of Arizona,Mesa,AZ,57697,Commerce Bank of Arizona,7-May-10,23-Aug-12
+Access Bank,Champlin,MN,16476,PrinsBank,7-May-10,23-Aug-12
+The Bank of Bonifay,Bonifay,FL,14246,First Federal Bank of Florida,7-May-10,5-Nov-12
+Frontier Bank,Everett,WA,22710,"Union Bank, N.A.",30-Apr-10,15-Jan-13
+BC National Banks,Butler,MO,17792,Community First Bank,30-Apr-10,23-Aug-12
+Champion Bank,Creve Coeur,MO,58362,BankLiberty,30-Apr-10,23-Aug-12
+CF Bancorp,Port Huron,MI,30005,First Michigan Bank,30-Apr-10,15-Jan-13
+Westernbank Puerto Rico,Mayaguez,PR,31027,Banco Popular de Puerto Rico,30-Apr-10,25-Oct-13
+R-G Premier Bank of Puerto Rico,Hato Rey,PR,32185,Scotiabank de Puerto Rico,30-Apr-10,25-Oct-13
+Eurobank,San Juan,PR,27150,Oriental Bank and Trust,30-Apr-10,25-Oct-13
+Wheatland Bank,Naperville,IL,58429,Wheaton Bank & Trust,23-Apr-10,23-Aug-12
+Peotone Bank and Trust Company,Peotone,IL,10888,First Midwest Bank,23-Apr-10,23-Aug-12
+Lincoln Park Savings Bank,Chicago,IL,30600,Northbrook Bank & Trust Company,23-Apr-10,23-Aug-12
+New Century Bank,Chicago,IL,34821,"MB Financial Bank, N.A.",23-Apr-10,23-Aug-12
+Citizens Bank and Trust Company of Chicago,Chicago,IL,34658,Republic Bank of Chicago,23-Apr-10,23-Aug-12
+Broadway Bank,Chicago,IL,22853,"MB Financial Bank, N.A.",23-Apr-10,23-Aug-12
+"Amcore Bank, National Association",Rockford,IL,3735,Harris N.A.,23-Apr-10,23-Aug-12
+City Bank,Lynnwood,WA,21521,Whidbey Island Bank,16-Apr-10,14-Sep-12
+Tamalpais Bank,San Rafael,CA,33493,"Union Bank, N.A.",16-Apr-10,23-Aug-12
+Innovative Bank,Oakland,CA,23876,Center Bank,16-Apr-10,23-Aug-12
+Butler Bank,Lowell,MA,26619,People's United Bank,16-Apr-10,23-Aug-12
+Riverside National Bank of Florida,Fort Pierce,FL,24067,"TD Bank, N.A.",16-Apr-10,5-Nov-12
+AmericanFirst Bank,Clermont,FL,57724,"TD Bank, N.A.",16-Apr-10,31-Oct-12
+First Federal Bank of North Florida,Palatka,FL,28886,"TD Bank, N.A.",16-Apr-10,15-Jan-13
+Lakeside Community Bank,Sterling Heights,MI,34878,No Acquirer,16-Apr-10,23-Aug-12
+Beach First National Bank,Myrtle Beach,SC,34242,Bank of North Carolina,9-Apr-10,5-Nov-12
+Desert Hills Bank,Phoenix,AZ,57060,New York Community Bank,26-Mar-10,23-Aug-12
+Unity National Bank,Cartersville,GA,34678,Bank of the Ozarks,26-Mar-10,14-Sep-12
+Key West Bank,Key West,FL,34684,Centennial Bank,26-Mar-10,23-Aug-12
+McIntosh Commercial Bank,Carrollton,GA,57399,CharterBank,26-Mar-10,23-Aug-12
+State Bank of Aurora,Aurora,MN,8221,Northern State Bank,19-Mar-10,23-Aug-12
+First Lowndes Bank,Fort Deposit,AL,24957,First Citizens Bank,19-Mar-10,23-Aug-12
+Bank of Hiawassee,Hiawassee,GA,10054,Citizens South Bank,19-Mar-10,23-Aug-12
+Appalachian Community Bank,Ellijay,GA,33989,Community & Southern Bank,19-Mar-10,31-Oct-12
+Advanta Bank Corp.,Draper,UT,33535,No Acquirer,19-Mar-10,14-Sep-12
+Century Security Bank,Duluth,GA,58104,Bank of Upson,19-Mar-10,23-Aug-12
+American National Bank,Parma,OH,18806,The National Bank and Trust Company,19-Mar-10,23-Aug-12
+Statewide Bank,Covington,LA,29561,Home Bank,12-Mar-10,23-Aug-12
+Old Southern Bank,Orlando,FL,58182,Centennial Bank,12-Mar-10,23-Aug-12
+The Park Avenue Bank,New York,NY,27096,Valley National Bank,12-Mar-10,23-Aug-12
+LibertyPointe Bank,New York,NY,58071,Valley National Bank,11-Mar-10,23-Aug-12
+Centennial Bank,Ogden,UT,34430,No Acquirer,5-Mar-10,14-Sep-12
+Waterfield Bank,Germantown,MD,34976,No Acquirer,5-Mar-10,23-Aug-12
+Bank of Illinois,Normal,IL,9268,Heartland Bank and Trust Company,5-Mar-10,23-Aug-12
+Sun American Bank,Boca Raton,FL,27126,First-Citizens Bank & Trust Company,5-Mar-10,23-Aug-12
+Rainier Pacific Bank,Tacoma,WA,38129,Umpqua Bank,26-Feb-10,23-Aug-12
+Carson River Community Bank,Carson City,NV,58352,Heritage Bank of Nevada,26-Feb-10,15-Jan-13
+"La Jolla Bank, FSB",La Jolla,CA,32423,"OneWest Bank, FSB",19-Feb-10,24-Aug-12
+George Washington Savings Bank,Orland Park,IL,29952,"FirstMerit Bank, N.A.",19-Feb-10,24-Aug-12
+The La Coste National Bank,La Coste,TX,3287,Community National Bank,19-Feb-10,14-Sep-12
+Marco Community Bank,Marco Island,FL,57586,Mutual of Omaha Bank,19-Feb-10,24-Aug-12
+1st American State Bank of Minnesota,Hancock,MN,15448,"Community Development Bank, FSB",5-Feb-10,1-Nov-13
+American Marine Bank,Bainbridge Island,WA,16730,Columbia State Bank,29-Jan-10,24-Aug-12
+First Regional Bank,Los Angeles,CA,23011,First-Citizens Bank & Trust Company,29-Jan-10,24-Aug-12
+Community Bank and Trust,Cornelia,GA,5702,SCBT National Association,29-Jan-10,15-Jan-13
+"Marshall Bank, N.A.",Hallock,MN,16133,United Valley Bank,29-Jan-10,23-Aug-12
+Florida Community Bank,Immokalee,FL,5672,"Premier American Bank, N.A.",29-Jan-10,15-Jan-13
+First National Bank of Georgia,Carrollton,GA,16480,Community & Southern Bank,29-Jan-10,13-Dec-12
+Columbia River Bank,The Dalles,OR,22469,Columbia State Bank,22-Jan-10,14-Sep-12
+Evergreen Bank,Seattle,WA,20501,Umpqua Bank,22-Jan-10,15-Jan-13
+Charter Bank,Santa Fe,NM,32498,Charter Bank,22-Jan-10,23-Aug-12
+Bank of Leeton,Leeton,MO,8265,"Sunflower Bank, N.A.",22-Jan-10,15-Jan-13
+Premier American Bank,Miami,FL,57147,"Premier American Bank, N.A.",22-Jan-10,13-Dec-12
+Barnes Banking Company,Kaysville,UT,1252,No Acquirer,15-Jan-10,23-Aug-12
+St. Stephen State Bank,St. Stephen,MN,17522,First State Bank of St. Joseph,15-Jan-10,23-Aug-12
+Town Community Bank & Trust,Antioch,IL,34705,First American Bank,15-Jan-10,23-Aug-12
+Horizon Bank,Bellingham,WA,22977,Washington Federal Savings and Loan Association,8-Jan-10,23-Aug-12
+"First Federal Bank of California, F.S.B.",Santa Monica,CA,28536,"OneWest Bank, FSB",18-Dec-09,23-Aug-12
+Imperial Capital Bank,La Jolla,CA,26348,City National Bank,18-Dec-09,5-Sep-12
+Independent Bankers' Bank,Springfield,IL,26820,The Independent BankersBank (TIB),18-Dec-09,23-Aug-12
+New South Federal Savings Bank,Irondale,AL,32276,Beal Bank,18-Dec-09,23-Aug-12
+Citizens State Bank,New Baltimore,MI,1006,No Acquirer,18-Dec-09,5-Nov-12
+Peoples First Community Bank,Panama City,FL,32167,Hancock Bank,18-Dec-09,5-Nov-12
+RockBridge Commercial Bank,Atlanta,GA,58315,No Acquirer,18-Dec-09,5-Nov-12
+SolutionsBank,Overland Park,KS,4731,Arvest Bank,11-Dec-09,23-Aug-12
+"Valley Capital Bank, N.A.",Mesa,AZ,58399,Enterprise Bank & Trust,11-Dec-09,23-Aug-12
+"Republic Federal Bank, N.A.",Miami,FL,22846,1st United Bank,11-Dec-09,5-Nov-12
+Greater Atlantic Bank,Reston,VA,32583,Sonabank,4-Dec-09,5-Nov-12
+Benchmark Bank,Aurora,IL,10440,"MB Financial Bank, N.A.",4-Dec-09,23-Aug-12
+AmTrust Bank,Cleveland,OH,29776,New York Community Bank,4-Dec-09,5-Nov-12
+The Tattnall Bank,Reidsville,GA,12080,Heritage Bank of the South,4-Dec-09,5-Nov-12
+First Security National Bank,Norcross,GA,26290,State Bank and Trust Company,4-Dec-09,5-Nov-12
+The Buckhead Community Bank,Atlanta,GA,34663,State Bank and Trust Company,4-Dec-09,5-Nov-12
+Commerce Bank of Southwest Florida,Fort Myers,FL,58016,Central Bank,20-Nov-09,5-Nov-12
+Pacific Coast National Bank,San Clemente,CA,57914,Sunwest Bank,13-Nov-09,22-Aug-12
+Orion Bank,Naples,FL,22427,IBERIABANK,13-Nov-09,5-Nov-12
+"Century Bank, F.S.B.",Sarasota,FL,32267,IBERIABANK,13-Nov-09,22-Aug-12
+United Commercial Bank,San Francisco,CA,32469,East West Bank,6-Nov-09,5-Nov-12
+Gateway Bank of St. Louis,St. Louis,MO,19450,Central Bank of Kansas City,6-Nov-09,22-Aug-12
+Prosperan Bank,Oakdale,MN,35074,"Alerus Financial, N.A.",6-Nov-09,22-Aug-12
+Home Federal Savings Bank,Detroit,MI,30329,Liberty Bank and Trust Company,6-Nov-09,22-Aug-12
+United Security Bank,Sparta,GA,22286,Ameris Bank,6-Nov-09,15-Jan-13
+North Houston Bank,Houston,TX,18776,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+Madisonville State Bank,Madisonville,TX,33782,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+Citizens National Bank,Teague,TX,25222,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+Park National Bank,Chicago,IL,11677,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+Pacific National Bank,San Francisco,CA,30006,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+California National Bank,Los Angeles,CA,34659,U.S. Bank N.A.,30-Oct-09,5-Sep-12
+San Diego National Bank,San Diego,CA,23594,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+Community Bank of Lemont,Lemont,IL,35291,U.S. Bank N.A.,30-Oct-09,15-Jan-13
+"Bank USA, N.A.",Phoenix,AZ,32218,U.S. Bank N.A.,30-Oct-09,22-Aug-12
+First DuPage Bank,Westmont,IL,35038,First Midwest Bank,23-Oct-09,22-Aug-12
+Riverview Community Bank,Otsego,MN,57525,Central Bank,23-Oct-09,22-Aug-12
+Bank of Elmwood,Racine,WI,18321,Tri City National Bank,23-Oct-09,22-Aug-12
+Flagship National Bank,Bradenton,FL,35044,First Federal Bank of Florida,23-Oct-09,22-Aug-12
+Hillcrest Bank Florida,Naples,FL,58336,Stonegate Bank,23-Oct-09,22-Aug-12
+American United Bank,Lawrenceville,GA,57794,Ameris Bank,23-Oct-09,5-Sep-12
+Partners Bank,Naples,FL,57959,Stonegate Bank,23-Oct-09,15-Jan-13
+San Joaquin Bank,Bakersfield,CA,23266,Citizens Business Bank,16-Oct-09,22-Aug-12
+Southern Colorado National Bank,Pueblo,CO,57263,Legacy Bank,2-Oct-09,5-Sep-12
+Jennings State Bank,Spring Grove,MN,11416,Central Bank,2-Oct-09,21-Aug-12
+Warren Bank,Warren,MI,34824,The Huntington National Bank,2-Oct-09,21-Aug-12
+Georgian Bank,Atlanta,GA,57151,"First Citizens Bank and Trust Company, Inc.",25-Sep-09,21-Aug-12
+"Irwin Union Bank, F.S.B.",Louisville,KY,57068,"First Financial Bank, N.A.",18-Sep-09,5-Sep-12
+Irwin Union Bank and Trust Company,Columbus,IN,10100,"First Financial Bank, N.A.",18-Sep-09,21-Aug-12
+Venture Bank,Lacey,WA,22868,First-Citizens Bank & Trust Company,11-Sep-09,21-Aug-12
+Brickwell Community Bank,Woodbury,MN,57736,CorTrust Bank N.A.,11-Sep-09,15-Jan-13
+"Corus Bank, N.A.",Chicago,IL,13693,"MB Financial Bank, N.A.",11-Sep-09,21-Aug-12
+First State Bank,Flagstaff,AZ,34875,Sunwest Bank,4-Sep-09,15-Jan-13
+Platinum Community Bank,Rolling Meadows,IL,35030,No Acquirer,4-Sep-09,21-Aug-12
+Vantus Bank,Sioux City,IA,27732,Great Southern Bank,4-Sep-09,21-Aug-12
+InBank,Oak Forest,IL,20203,"MB Financial Bank, N.A.",4-Sep-09,21-Aug-12
+First Bank of Kansas City,Kansas City,MO,25231,Great American Bank,4-Sep-09,21-Aug-12
+Affinity Bank,Ventura,CA,27197,Pacific Western Bank,28-Aug-09,21-Aug-12
+Mainstreet Bank,Forest Lake,MN,1909,Central Bank,28-Aug-09,21-Aug-12
+Bradford Bank,Baltimore,MD,28312,Manufacturers and Traders Trust Company (M&T Bank),28-Aug-09,15-Jan-13
+Guaranty Bank,Austin,TX,32618,BBVA Compass,21-Aug-09,21-Aug-12
+CapitalSouth Bank,Birmingham,AL,22130,IBERIABANK,21-Aug-09,15-Jan-13
+First Coweta Bank,Newnan,GA,57702,United Bank,21-Aug-09,15-Jan-13
+ebank,Atlanta,GA,34682,"Stearns Bank, N.A.",21-Aug-09,21-Aug-12
+Community Bank of Nevada,Las Vegas,NV,34043,No Acquirer,14-Aug-09,21-Aug-12
+Community Bank of Arizona,Phoenix,AZ,57645,MidFirst Bank,14-Aug-09,21-Aug-12
+"Union Bank, National Association",Gilbert,AZ,34485,MidFirst Bank,14-Aug-09,21-Aug-12
+Colonial Bank,Montgomery,AL,9609,"Branch Banking & Trust Company, (BB&T)",14-Aug-09,5-Sep-12
+Dwelling House Savings and Loan Association,Pittsburgh,PA,31559,"PNC Bank, N.A.",14-Aug-09,15-Jan-13
+Community First Bank,Prineville,OR,23268,Home Federal Bank,7-Aug-09,15-Jan-13
+Community National Bank of Sarasota County,Venice,FL,27183,"Stearns Bank, N.A.",7-Aug-09,20-Aug-12
+First State Bank,Sarasota,FL,27364,"Stearns Bank, N.A.",7-Aug-09,20-Aug-12
+Mutual Bank,Harvey,IL,18659,United Central Bank,31-Jul-09,20-Aug-12
+First BankAmericano,Elizabeth,NJ,34270,Crown Bank,31-Jul-09,20-Aug-12
+Peoples Community Bank,West Chester,OH,32288,"First Financial Bank, N.A.",31-Jul-09,20-Aug-12
+Integrity Bank,Jupiter,FL,57604,Stonegate Bank,31-Jul-09,20-Aug-12
+First State Bank of Altus,Altus,OK,9873,Herring Bank,31-Jul-09,20-Aug-12
+Security Bank of Jones County,Gray,GA,8486,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Security Bank of Houston County,Perry,GA,27048,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Security Bank of Bibb County,Macon,GA,27367,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Security Bank of North Metro,Woodstock,GA,57105,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Security Bank of North Fulton,Alpharetta,GA,57430,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Security Bank of Gwinnett County,Suwanee,GA,57346,State Bank and Trust Company,24-Jul-09,20-Aug-12
+Waterford Village Bank,Williamsville,NY,58065,"Evans Bank, N.A.",24-Jul-09,1-Nov-13
+Temecula Valley Bank,Temecula,CA,34341,First-Citizens Bank & Trust Company,17-Jul-09,20-Aug-12
+Vineyard Bank,Rancho Cucamonga,CA,23556,California Bank & Trust,17-Jul-09,20-Aug-12
+BankFirst,Sioux Falls,SD,34103,"Alerus Financial, N.A.",17-Jul-09,20-Aug-12
+First Piedmont Bank,Winder,GA,34594,First American Bank and Trust Company,17-Jul-09,15-Jan-13
+Bank of Wyoming,Thermopolis,WY,22754,Central Bank & Trust,10-Jul-09,20-Aug-12
+Founders Bank,Worth,IL,18390,The PrivateBank and Trust Company,2-Jul-09,20-Aug-12
+Millennium State Bank of Texas,Dallas,TX,57667,State Bank of Texas,2-Jul-09,26-Oct-12
+First National Bank of Danville,Danville,IL,3644,"First Financial Bank, N.A.",2-Jul-09,20-Aug-12
+Elizabeth State Bank,Elizabeth,IL,9262,Galena State Bank and Trust Company,2-Jul-09,20-Aug-12
+Rock River Bank,Oregon,IL,15302,The Harvard State Bank,2-Jul-09,20-Aug-12
+First State Bank of Winchester,Winchester,IL,11710,The First National Bank of Beardstown,2-Jul-09,20-Aug-12
+John Warner Bank,Clinton,IL,12093,State Bank of Lincoln,2-Jul-09,20-Aug-12
+Mirae Bank,Los Angeles,CA,57332,Wilshire State Bank,26-Jun-09,20-Aug-12
+MetroPacific Bank,Irvine,CA,57893,Sunwest Bank,26-Jun-09,20-Aug-12
+Horizon Bank,Pine City,MN,9744,"Stearns Bank, N.A.",26-Jun-09,20-Aug-12
+Neighborhood Community Bank,Newnan,GA,35285,CharterBank,26-Jun-09,20-Aug-12
+Community Bank of West Georgia,Villa Rica,GA,57436,No Acquirer,26-Jun-09,17-Aug-12
+First National Bank of Anthony,Anthony,KS,4614,Bank of Kansas,19-Jun-09,17-Aug-12
+Cooperative Bank,Wilmington,NC,27837,First Bank,19-Jun-09,17-Aug-12
+Southern Community Bank,Fayetteville,GA,35251,United Community Bank,19-Jun-09,17-Aug-12
+Bank of Lincolnwood,Lincolnwood,IL,17309,Republic Bank of Chicago,5-Jun-09,17-Aug-12
+Citizens National Bank,Macomb,IL,5757,Morton Community Bank,22-May-09,4-Sep-12
+Strategic Capital Bank,Champaign,IL,35175,Midland States Bank,22-May-09,4-Sep-12
+"BankUnited, FSB",Coral Gables,FL,32247,BankUnited,21-May-09,17-Aug-12
+Westsound Bank,Bremerton,WA,34843,Kitsap Bank,8-May-09,4-Sep-12
+America West Bank,Layton,UT,35461,Cache Valley Bank,1-May-09,17-Aug-12
+Citizens Community Bank,Ridgewood,NJ,57563,North Jersey Community Bank,1-May-09,4-Sep-12
+"Silverton Bank, NA",Atlanta,GA,26535,No Acquirer,1-May-09,17-Aug-12
+First Bank of Idaho,Ketchum,ID,34396,"U.S. Bank, N.A.",24-Apr-09,17-Aug-12
+First Bank of Beverly Hills,Calabasas,CA,32069,No Acquirer,24-Apr-09,4-Sep-12
+Michigan Heritage Bank,Farmington Hills,MI,34369,Level One Bank,24-Apr-09,17-Aug-12
+American Southern Bank,Kennesaw,GA,57943,Bank of North Georgia,24-Apr-09,17-Aug-12
+Great Basin Bank of Nevada,Elko,NV,33824,Nevada State Bank,17-Apr-09,4-Sep-12
+American Sterling Bank,Sugar Creek,MO,8266,Metcalf Bank,17-Apr-09,31-Aug-12
+New Frontier Bank,Greeley,CO,34881,No Acquirer,10-Apr-09,4-Sep-12
+Cape Fear Bank,Wilmington,NC,34639,First Federal Savings and Loan Association,10-Apr-09,17-Aug-12
+Omni National Bank,Atlanta,GA,22238,No Acquirer,27-Mar-09,17-Aug-12
+"TeamBank, NA",Paola,KS,4754,Great Southern Bank,20-Mar-09,17-Aug-12
+Colorado National Bank,Colorado Springs,CO,18896,Herring Bank,20-Mar-09,17-Aug-12
+FirstCity Bank,Stockbridge,GA,18243,No Acquirer,20-Mar-09,17-Aug-12
+Freedom Bank of Georgia,Commerce,GA,57558,Northeast Georgia Bank,6-Mar-09,17-Aug-12
+Security Savings Bank,Henderson,NV,34820,Bank of Nevada,27-Feb-09,7-Sep-12
+Heritage Community Bank,Glenwood,IL,20078,"MB Financial Bank, N.A.",27-Feb-09,17-Aug-12
+Silver Falls Bank,Silverton,OR,35399,Citizens Bank,20-Feb-09,17-Aug-12
+Pinnacle Bank of Oregon,Beaverton,OR,57342,Washington Trust Bank of Spokane,13-Feb-09,17-Aug-12
+Corn Belt Bank & Trust Co.,Pittsfield,IL,16500,The Carlinville National Bank,13-Feb-09,17-Aug-12
+Riverside Bank of the Gulf Coast,Cape Coral,FL,34563,TIB Bank,13-Feb-09,17-Aug-12
+Sherman County Bank,Loup City,NE,5431,Heritage Bank,13-Feb-09,17-Aug-12
+County Bank,Merced,CA,22574,Westamerica Bank,6-Feb-09,4-Sep-12
+Alliance Bank,Culver City,CA,23124,California Bank & Trust,6-Feb-09,16-Aug-12
+FirstBank Financial Services,McDonough,GA,57017,Regions Bank,6-Feb-09,16-Aug-12
+Ocala National Bank,Ocala,FL,26538,"CenterState Bank of Florida, N.A.",30-Jan-09,4-Sep-12
+Suburban FSB,Crofton,MD,30763,Bank of Essex,30-Jan-09,16-Aug-12
+MagnetBank,Salt Lake City,UT,58001,No Acquirer,30-Jan-09,16-Aug-12
+1st Centennial Bank,Redlands,CA,33025,First California Bank,23-Jan-09,16-Aug-12
+Bank of Clark County,Vancouver,WA,34959,Umpqua Bank,16-Jan-09,16-Aug-12
+National Bank of Commerce,Berkeley,IL,19733,Republic Bank of Chicago,16-Jan-09,16-Aug-12
+Sanderson State Bank,Sanderson,TX,11568,The Pecos County State Bank,12-Dec-08,25-Oct-13
+Haven Trust Bank,Duluth,GA,35379,"Branch Banking & Trust Company, (BB&T)",12-Dec-08,16-Aug-12
+First Georgia Community Bank,Jackson,GA,34301,United Bank,5-Dec-08,16-Aug-12
+PFF Bank & Trust,Pomona,CA,28344,"U.S. Bank, N.A.",21-Nov-08,4-Jan-13
+Downey Savings & Loan,Newport Beach,CA,30968,"U.S. Bank, N.A.",21-Nov-08,4-Jan-13
+Community Bank,Loganville,GA,16490,Bank of Essex,21-Nov-08,4-Sep-12
+Security Pacific Bank,Los Angeles,CA,23595,Pacific Western Bank,7-Nov-08,28-Aug-12
+"Franklin Bank, SSB",Houston,TX,26870,Prosperity Bank,7-Nov-08,16-Aug-12
+Freedom Bank,Bradenton,FL,57930,Fifth Third Bank,31-Oct-08,16-Aug-12
+Alpha Bank & Trust,Alpharetta,GA,58241,"Stearns Bank, N.A.",24-Oct-08,16-Aug-12
+Meridian Bank,Eldred,IL,13789,National Bank,10-Oct-08,31-May-12
+Main Street Bank,Northville,MI,57654,Monroe Bank & Trust,10-Oct-08,1-Aug-13
+Washington Mutual Bank (Including its subsidiary Washington Mutual Bank FSB),Henderson,NV,32633,JP Morgan Chase Bank,25-Sep-08,16-Aug-12
+Ameribank,Northfork,WV,6782,"The Citizens Savings Bank, Pioneer Community Bank, Inc.",19-Sep-08,16-Aug-12
+Silver State Bank,Henderson,NV,34194,Nevada State Bank,5-Sep-08,25-Oct-13
+Integrity Bank,Alpharetta,GA,35469,Regions Bank,29-Aug-08,16-Aug-12
+Columbian Bank & Trust,Topeka,KS,22728,Citizens Bank & Trust,22-Aug-08,16-Aug-12
+First Priority Bank,Bradenton,FL,57523,SunTrust Bank,1-Aug-08,16-Aug-12
+"First Heritage Bank, NA",Newport Beach,CA,57961,Mutual of Omaha Bank,25-Jul-08,28-Aug-12
+First National Bank of Nevada,Reno,NV,27011,Mutual of Omaha Bank,25-Jul-08,28-Aug-12
+IndyMac Bank,Pasadena,CA,29730,"OneWest Bank, FSB",11-Jul-08,28-Aug-12
+"First Integrity Bank, NA",Staples,MN,12736,First International Bank and Trust,30-May-08,28-Aug-12
+"ANB Financial, NA",Bentonville,AR,33901,Pulaski Bank and Trust Company,9-May-08,28-Aug-12
+Hume Bank,Hume,MO,1971,Security Bank,7-Mar-08,28-Aug-12
+Douglass National Bank,Kansas City,MO,24660,Liberty Bank and Trust Company,25-Jan-08,26-Oct-12
+Miami Valley Bank,Lakeview,OH,16848,The Citizens Banking Company,4-Oct-07,28-Aug-12
+NetBank,Alpharetta,GA,32575,ING DIRECT,28-Sep-07,28-Aug-12
+Metropolitan Savings Bank,Pittsburgh,PA,35353,Allegheny Valley Bank of Pittsburgh,2-Feb-07,27-Oct-10
+Bank of Ephraim,Ephraim,UT,1249,Far West Bank,25-Jun-04,9-Apr-08
+Reliance Bank,White Plains,NY,26778,Union State Bank,19-Mar-04,9-Apr-08
+Guaranty National Bank of Tallahassee,Tallahassee,FL,26838,Hancock Bank of Florida,12-Mar-04,5-Jun-12
+Dollar Savings Bank,Newark,NJ,31330,No Acquirer,14-Feb-04,9-Apr-08
+Pulaski Savings Bank,Philadelphia,PA,27203,Earthstar Bank,14-Nov-03,22-Jul-05
+First National Bank of Blanchardville,Blanchardville,WI,11639,The Park Bank,9-May-03,5-Jun-12
+Southern Pacific Bank,Torrance,CA,27094,Beal Bank,7-Feb-03,20-Oct-08
+Farmers Bank of Cheneyville,Cheneyville,LA,16445,Sabine State Bank & Trust,17-Dec-02,20-Oct-04
+Bank of Alamo,Alamo,TN,9961,No Acquirer,8-Nov-02,18-Mar-05
+AmTrade International Bank,Atlanta,GA,33784,No Acquirer,30-Sep-02,11-Sep-06
+Universal Federal Savings Bank,Chicago,IL,29355,Chicago Community Bank,27-Jun-02,9-Apr-08
+Connecticut Bank of Commerce,Stamford,CT,19183,Hudson United Bank,26-Jun-02,14-Feb-12
+New Century Bank,Shelby Township,MI,34979,No Acquirer,28-Mar-02,18-Mar-05
+Net 1st National Bank,Boca Raton,FL,26652,Bank Leumi USA,1-Mar-02,9-Apr-08
+"NextBank, NA",Phoenix,AZ,22314,No Acquirer,7-Feb-02,27-Aug-10
+Oakwood Deposit Bank Co.,Oakwood,OH,8966,The State Bank & Trust Company,1-Feb-02,25-Oct-12
+Bank of Sierra Blanca,Sierra Blanca,TX,22002,The Security State Bank of Pecos,18-Jan-02,6-Nov-03
+"Hamilton Bank, NA",Miami,FL,24382,Israel Discount Bank of New York,11-Jan-02,5-Jun-12
+Sinclair National Bank,Gravette,AR,34248,Delta Trust & Bank,7-Sep-01,10-Feb-04
+"Superior Bank, FSB",Hinsdale,IL,32646,"Superior Federal, FSB",27-Jul-01,5-Jun-12
+Malta National Bank,Malta,OH,6629,North Valley Bank,3-May-01,18-Nov-02
+First Alliance Bank & Trust Co.,Manchester,NH,34264,Southern New Hampshire Bank & Trust,2-Feb-01,18-Feb-03
+National State Bank of Metropolis,Metropolis,IL,3815,Banterra Bank of Marion,14-Dec-00,17-Mar-05
+Bank of Honolulu,Honolulu,HI,21029,Bank of the Orient,13-Oct-00,17-Mar-05
diff --git a/src/gwt/test/outline_harness.html b/src/gwt/test/outline_harness.html
new file mode 100644
index 0000000..1040c04
--- /dev/null
+++ b/src/gwt/test/outline_harness.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript" src="../tools/ace/build/src/ace-uncompressed.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/auto_brace_insert.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/tex_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_scope_tree.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_code_model.js"></script>
+ <style type="text/css">
+ pre {
+ margin-bottom: 30px;
+ padding: 3px;
+ border: 1px solid #999;
+ }
+ </style>
+</head>
+<body>
+
+<h2>Interactive Tester</h2>
+<input id="funcName" type="text" readonly="true" style="width: 598px; border: 1px solid #999; margin-bottom: 12px"/><button onclick="updateLabel();">Update</button>
+<div id="editor" style="width: 600px; height: 300px; border: 1px solid #999">foo {
+a <- function(b=function() { }) {
+
+}
+}</div>
+
+
+<script type="text/javascript">
+var RCodeModel = require('mode/r_code_model').RCodeModel;
+var Document = require('ace/document').Document;
+require('mode/auto_brace_insert').setInsertMatching(true);
+
+var editor = ace.edit('editor');
+editor.renderer.setHScrollBarAlwaysVisible(false);
+editor.setHighlightActiveLine(false);
+var RMode = require('mode/r').Mode;
+var mode = new RMode(false, editor.getSession().getDocument());
+editor.getSession().setMode(mode);
+function updateLabel() {
+ var mgr = mode.codeModel;
+ var fname = mgr.getCurrentScope(editor.getSession().getSelection().getCursor()).label;
+ document.getElementById('funcName').value = fname;
+}
+
+editor.getSession().getSelection().on("changeCursor", function() {
+ setTimeout(updateLabel, 0);
+});
+updateLabel();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/gwt/test/rmode_harness.html b/src/gwt/test/rmode_harness.html
new file mode 100644
index 0000000..26661d1
--- /dev/null
+++ b/src/gwt/test/rmode_harness.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript" src="../tools/ace/build/src/ace-uncompressed.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/auto_brace_insert.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/tex_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_code_model.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_scope_tree.js"></script>
+</head>
+<body style="margin: 0">
+<div id="editor" style="width: 100%; height: 100%"></div>
+<script type="text/javascript">
+var editor = ace.edit('editor');
+require('mode/auto_brace_insert').setInsertMatching(true);
+var RMode = require('mode/r').Mode;
+editor.getSession().setMode(new RMode(false,
+ editor.getSession().getDocument(),
+ editor.getSession()));
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/gwt/test/sizetofit_harness.html b/src/gwt/test/sizetofit_harness.html
new file mode 100644
index 0000000..263e60a
--- /dev/null
+++ b/src/gwt/test/sizetofit_harness.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="text/javascript" src="../tools/ace/build/src/ace-uncompressed.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/auto_brace_insert.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/tex_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_highlight_rules.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_code_model.js"></script>
+ <script type="text/javascript" src="../acesupport/acemode/r_scope_tree.js"></script>
+ <style type="text/css">
+ .ace_sb {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+
+<h2>Size-to-fit Editor</h2>
+<div id="editor" style="width: 50%; height: 20px; border-top: 1px dotted #ccc; border-bottom: 1px dotted #ccc"></div>
+
+
+<script type="text/javascript">
+var RCodeModel = require('mode/r_code_model').RCodeModel;
+var Document = require('ace/document').Document;
+require('mode/auto_brace_insert').setInsertMatching(true);
+
+var editor = ace.edit('editor');
+editor.renderer.setHScrollBarAlwaysVisible(false);
+editor.setHighlightActiveLine(false);
+editor.getSession().setUseWrapMode(true);
+editor.renderer.setShowGutter(false);
+editor.renderer.setShowPrintMargin(false);
+var RMode = require('mode/r').Mode;
+var mode = new RMode(false, editor.getSession().getDocument());
+editor.getSession().setMode(mode);
+
+function updateEditorHeight() {
+ editor.container.style.height = (Math.max(1, editor.getSession().getScreenLength()) * editor.renderer.lineHeight) + 'px';
+ editor.resize();
+ editor.renderer.scrollToY(0);
+ editor.renderer.scrollToX(0);
+}
+editor.getSession().getDocument().on("change", updateEditorHeight);
+updateEditorHeight();
+editor.focus();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/gwt/tools/compile-themes b/src/gwt/tools/compile-themes
new file mode 100755
index 0000000..5d31f08
--- /dev/null
+++ b/src/gwt/tools/compile-themes
@@ -0,0 +1,92 @@
+#!/usr/bin/ruby
+
+require 'find'
+
+def parse_css_color(value)
+ value.strip!
+ if /#([0-9A-F]{6})/i =~ value
+ red = $1.slice(0, 2).to_i(16)
+ green = $1.slice(2, 2).to_i(16)
+ blue = $1.slice(4, 2).to_i(16)
+ elsif /rgb\((\d+), (\d+), (\d+)\)/ =~ value
+ red = $1.to_i
+ green = $2.to_i
+ blue = $3.to_i
+ else
+ raise "Unexpected color format: #{value}"
+ end
+
+ [red, green, blue]
+end
+
+def format_css_color(color)
+ "rgb(#{color[0]}, #{color[1]}, #{color[2]})"
+end
+
+def mix_values(x, y, proportion)
+ (x.to_f * proportion + y.to_f * (1 - proportion)).to_i
+end
+
+def mix_colors(a, b, proportion)
+ red = mix_values(a[0], b[0], proportion)
+ green = mix_values(a[1], b[1], proportion)
+ blue = mix_values(a[2], b[2], proportion)
+ return [red, green, blue]
+end
+
+def create_line_marker_rule(markerName, markerColor)
+ return "\n.ace_marker-layer #{markerName} {position: absolute; z-index: -1; background-color: #{markerColor};}"
+end
+
+
+OutDir = '../src/org/rstudio/studio/client/workbench/views/source/editors/text/themes'
+
+Find.find('ace/lib/ace/theme/') do |file|
+ next if FileTest.directory?(file)
+ next unless file =~ /([^\\\/]+)\.css$/
+
+ style_name = "ace-#{$1.gsub(/_/, '-')}"
+
+ next if style_name == 'ace-github'
+ style_name = 'ace-tm' if style_name == 'ace-textmate'
+
+ puts style_name
+
+ rules = File.read(file)
+
+ raise "File #{file} needs explicit style name" unless rules =~ /\.#{style_name}\b/
+
+ rules.gsub!(/\.#{style_name} ?/, '')
+
+ raise "No keyword color found for #{style_name}" unless /\.ace_keyword\b.*?\s+(color:[^;\n]+)/m =~ rules
+ rules += "\n.nocolor.ace_editor .ace_line span {#{$1} !important;}"
+
+ raise "No bracket color found for #{style_name}" unless /\.ace_bracket\b.*?\s+border: 1px solid ([^;\n]+)/m =~ rules
+ rules += "\n.ace_bracket {margin: 0 !important; border: 0 !important; background-color: #{$1};}"
+
+ if /\.ace_scroller\b.*?\s+background-color: ([^;\n]+)/m =~ rules
+ background = $1
+ else
+ background = '#FFFFFF'
+ end
+ raise "No text color found for #{style_name}" unless /\.ace_text-layer\b.*?\s+color: ([^;\n]+)/m =~ rules
+ foreground = $1
+ background = parse_css_color(background)
+ foreground = parse_css_color(foreground)
+ foreign_bg = format_css_color(mix_colors(background, foreground, 0.8))
+ rules += create_line_marker_rule(".ace_foreign_line", foreign_bg)
+
+ find_bg = format_css_color(mix_colors(background, foreground, 0.5))
+ rules += create_line_marker_rule(".ace_find_line", find_bg)
+
+ # compute the background color to be used for the line currently active in the debugger
+ debug_primary = parse_css_color("#FFDE38")
+ debug_bg = format_css_color(mix_colors(background, debug_primary, 0.5))
+ rules += create_line_marker_rule(".ace_active_debug_line", debug_bg)
+
+ error_bg = format_css_color(mix_colors(background, foreground, 0.8))
+ rules += "\n.ace_console_error { background-color: #{error_bg}; }"
+
+ newfile = "#{OutDir}/#{File.basename(file).gsub(/\.js$/, '.css')}"
+ File.open(newfile, 'w') {|f| f.write(rules)}
+end
diff --git a/src/gwt/tools/compiler/COPYING b/src/gwt/tools/compiler/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/src/gwt/tools/compiler/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/gwt/tools/compiler/README b/src/gwt/tools/compiler/README
new file mode 100644
index 0000000..e6d12c4
--- /dev/null
+++ b/src/gwt/tools/compiler/README
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2009 The Closure Compiler Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Contents
+//
+
+The Closure Compiler performs checking, instrumentation, and
+optimizations on JavaScript code. The purpose of this README is to
+explain how to build and run the Closure Compiler.
+
+The Closure Compiler requires Java 6 or higher.
+http://www.java.com/
+
+
+//
+// Building The Closure Compiler
+//
+
+There are three ways to get a Closure Compiler executable.
+
+1) Use one we built for you.
+
+Pre-built Closure binaries can be found at
+http://code.google.com/p/closure-compiler/downloads/list
+
+
+2) Check out the source and build it with Apache Ant.
+
+First, check out the full source tree of the Closure Compiler. There
+are instructions on how to do this at the project site.
+http://code.google.com/p/closure-compiler/source/checkout
+
+Apache Ant is a cross-platform build tool.
+http://ant.apache.org/
+
+At the root of the source tree, there is an Ant file named
+build.xml. To use it, navigate to the same directory and type the
+command
+
+ant jar
+
+This will produce a jar file called "build/compiler.jar".
+
+
+3) Check out the source and build it with Eclipse.
+
+Eclipse is a cross-platform IDE.
+http://www.eclipse.org/
+
+Under Eclipse's File menu, click "New > Project ..." and create a
+"Java Project." You will see an options screen. Give the project a
+name, select "Create project from existing source," and choose the
+root of the checked-out source tree as the existing directory. Verify
+that you are using JRE version 6 or higher.
+
+Eclipse can use the build.xml file to discover rules. When you
+navigate to the build.xml file, you will see all the build rules in
+the "Outline" pane. Run the "jar" rule to build the compiler in
+build/compiler.jar.
+
+
+//
+// Running The Closure Compiler
+//
+
+Once you have the jar binary, running the Closure Compiler is straightforward.
+
+On the command line, type
+
+java -jar compiler.jar
+
+This starts the compiler in interactive mode. Type
+
+var x = 17 + 25;
+
+then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux)
+and "Enter" again. The Compiler will respond:
+
+var x=42;
+
+The Closure Compiler has many options for reading input from a file,
+writing output to a file, checking your code, and running
+optimizations. To learn more, type
+
+java -jar compiler.jar --help
+
+You can read more detailed documentation about the many flags at
+http://code.google.com/closure/compiler/docs/gettingstarted_app.html
+
+
+//
+// Compiling Multiple Scripts
+//
+
+If you have multiple scripts, you should compile them all together with
+one compile command.
+
+java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js
+
+The Closure Compiler will concatenate the files in the order they're
+passed at the command line.
+
+If you need to compile many, many scripts together, you may start to
+run into problems with managing dependencies between scripts. You
+should check out the Closure Library. It contains functions for
+enforcing dependencies between scripts, and a tool called calcdeps.py
+that knows how to give scripts to the Closure Compiler in the right
+order.
+
+http://code.google.com/p/closure-library/
+
+//
+// Licensing
+//
+
+Unless otherwise stated, all source files are licensed under
+the Apache License, Version 2.0.
+
+
+-----
+Code under:
+src/com/google/javascript/rhino
+test/com/google/javascript/rhino
+
+URL: http://www.mozilla.org/rhino
+Version: 1.5R3, with heavy modifications
+License: Netscape Public License and MPL / GPL dual license
+
+Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an
+implementation of JavaScript for the JVM. The JavaScript parser and
+the parse tree data structures were extracted and modified
+significantly for use by Google's JavaScript compiler.
+
+Local Modifications: The packages have been renamespaced. All code not
+relavant to parsing has been removed. A JSDoc parser and static typing
+system have been added.
+
+
+-----
+Code in:
+lib/rhino
+
+Rhino
+URL: http://www.mozilla.org/rhino
+Version: Trunk
+License: Netscape Public License and MPL / GPL dual license
+
+Description: Mozilla Rhino is an implementation of JavaScript for the JVM.
+
+Local Modifications: Minor changes to parsing JSDoc that usually get pushed
+up-stream to Rhino trunk.
+
+
+-----
+Code in:
+lib/args4j.jar
+
+Args4j
+URL: https://args4j.dev.java.net/
+Version: 2.0.12
+License: MIT
+
+Description:
+args4j is a small Java class library that makes it easy to parse command line
+options/arguments in your CUI application.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/guava.jar
+
+Guava Libraries
+URL: http://code.google.com/p/guava-libraries/
+Version: r08
+License: Apache License 2.0
+
+Description: Google's core Java libraries.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/jsr305.jar
+
+Annotations for software defect detection
+URL: http://code.google.com/p/jsr-305/
+Version: svn revision 47
+License: BSD License
+
+Description: Annotations for software defect detection.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/jarjar.jar
+
+Jar Jar Links
+URL: http://jarjar.googlecode.com/
+Version: 1.1
+License: Apache License 2.0
+
+Description:
+A utility for repackaging Java libraries.
+
+Local Modifications: None.
+
+
+----
+Code in:
+lib/junit.jar
+
+JUnit
+URL: http://sourceforge.net/projects/junit/
+Version: 4.8.2
+License: Common Public License 1.0
+
+Description: A framework for writing and running automated tests in Java.
+
+Local Modifications: None.
+
+
+---
+Code in:
+lib/protobuf-java.jar
+
+Protocol Buffers
+URL: http://code.google.com/p/protobuf/
+Version: 2.3.0
+License: New BSD License
+
+Description: Supporting libraries for protocol buffers,
+an encoding of structured data.
+
+Local Modifications: None
+
+
+---
+Code in:
+lib/ant.jar
+lib/ant-launcher.jar
+
+URL: http://ant.apache.org/bindownload.cgi
+Version: 1.8.1
+License: Apache License 2.0
+Description:
+ Ant is a Java based build tool. In theory it is kind of like "make"
+ without make's wrinkles and with the full portability of pure java code.
+
+Local Modifications: None
+
+
+---
+Code in:
+lib/json.jar
+URL: http://json.org/java/index.html
+Version: JSON version 20090211
+License: MIT license
+Description:
+JSON is a set of java files for use in transmitting data in JSON format.
+
+Local Modifications: None
+
+---
+Code in:
+tools/maven-ant-tasks-2.1.1.jar
+URL: http://maven.apache.org
+Version 2.1.1
+License: Apache License 2.0
+Description:
+ Maven Ant tasks are used to manage dependencies and to install/deploy to
+ maven repositories.
+
+Local Modifications: None
diff --git a/src/gwt/tools/compiler/compiler.jar b/src/gwt/tools/compiler/compiler.jar
new file mode 100644
index 0000000..87a299d
Binary files /dev/null and b/src/gwt/tools/compiler/compiler.jar differ
diff --git a/src/gwt/tools/compiler/installed_version b/src/gwt/tools/compiler/installed_version
new file mode 100644
index 0000000..402d42b
--- /dev/null
+++ b/src/gwt/tools/compiler/installed_version
@@ -0,0 +1 @@
+compiler-latest.zip as of February 21, 2012
diff --git a/src/gwt/tools/encrypt-bootstrap.js b/src/gwt/tools/encrypt-bootstrap.js
new file mode 100644
index 0000000..305cf7d
--- /dev/null
+++ b/src/gwt/tools/encrypt-bootstrap.js
@@ -0,0 +1,20 @@
+//
+// encrypt-boostrap.js
+//
+// Copyright (C) 2009-12 by RStudio, Inc.
+//
+// This program is licensed to you under the terms of version 3 of the
+// GNU Affero General Public License. This program is distributed WITHOUT
+// ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+// AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+//
+
+// Provides an entry point to the RSA encryption code that
+// Closure Compiler won't rename
+
+window['encrypt'] = function (value, exponent, modulo) {
+ var rsa = new RSAKey();
+ rsa.setPublic(modulo, exponent);
+ return hex2b64(rsa.encrypt(value));
+}
diff --git a/src/gwt/tools/rsa.js b/src/gwt/tools/rsa.js
new file mode 100644
index 0000000..2389ea4
--- /dev/null
+++ b/src/gwt/tools/rsa.js
@@ -0,0 +1,861 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/ at Tue Nov 30 00:42:57 PST 2010
+// ==== File: jsbn.js
+// Copyright (c) 2005 Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+ if(a != null)
+ if("number" == typeof a) this.fromNumber(a,b,c);
+ else if(b == null && "string" != typeof a) this.fromString(a,256);
+ else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+ while(--n >= 0) {
+ var v = x*this[i++]+w[j]+c;
+ c = Math.floor(v/0x4000000);
+ w[j++] = v&0x3ffffff;
+ }
+ return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+ var xl = x&0x7fff, xh = x>>15;
+ while(--n >= 0) {
+ var l = this[i]&0x7fff;
+ var h = this[i++]>>15;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);
+ c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+ w[j++] = l&0x3fffffff;
+ }
+ return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+ var xl = x&0x3fff, xh = x>>14;
+ while(--n >= 0) {
+ var l = this[i]&0x3fff;
+ var h = this[i++]>>14;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x3fff)<<14)+w[j]+c;
+ c = (l>>28)+(m>>14)+xh*h;
+ w[j++] = l&0xfffffff;
+ }
+ return c;
+}
+if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+ BigInteger.prototype.am = am2;
+ dbits = 30;
+}
+else if(j_lm && (navigator.appName != "Netscape")) {
+ BigInteger.prototype.am = am1;
+ dbits = 26;
+}
+else { // Mozilla/Netscape seems to prefer am3
+ BigInteger.prototype.am = am3;
+ dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+ var c = BI_RC[s.charCodeAt(i)];
+ return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+ for(var i = this.t-1; i >= 0; --i) r[i] = this[i];
+ r.t = this.t;
+ r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+ this.t = 1;
+ this.s = (x<0)?-1:0;
+ if(x > 0) this[0] = x;
+ else if(x < -1) this[0] = x+DV;
+ else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 256) k = 8; // byte array
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else { this.fromRadix(s,b); return; }
+ this.t = 0;
+ this.s = 0;
+ var i = s.length, mi = false, sh = 0;
+ while(--i >= 0) {
+ var x = (k==8)?s[i]&0xff:intAt(s,i);
+ if(x < 0) {
+ if(s.charAt(i) == "-") mi = true;
+ continue;
+ }
+ mi = false;
+ if(sh == 0)
+ this[this.t++] = x;
+ else if(sh+k > this.DB) {
+ this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+ this[this.t++] = (x>>(this.DB-sh));
+ }
+ else
+ this[this.t-1] |= x<<sh;
+ sh += k;
+ if(sh >= this.DB) sh -= this.DB;
+ }
+ if(k == 8 && (s[0]&0x80) != 0) {
+ this.s = -1;
+ if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+ }
+ this.clamp();
+ if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+ var c = this.s&this.DM;
+ while(this.t > 0 && this[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+ if(this.s < 0) return "-"+this.negate().toString(b);
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else return this.toRadix(b);
+ var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+ var p = this.DB-(i*this.DB)%k;
+ if(i-- > 0) {
+ if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+ while(i >= 0) {
+ if(p < k) {
+ d = (this[i]&((1<<p)-1))<<(k-p);
+ d |= this[--i]>>(p+=this.DB-k);
+ }
+ else {
+ d = (this[i]>>(p-=k))&km;
+ if(p <= 0) { p += this.DB; --i; }
+ }
+ if(d > 0) m = true;
+ if(m) r += int2char(d);
+ }
+ }
+ return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+ var r = this.s-a.s;
+ if(r != 0) return r;
+ var i = this.t;
+ r = i-a.t;
+ if(r != 0) return r;
+ while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+ return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+ var r = 1, t;
+ if((t=x>>>16) != 0) { x = t; r += 16; }
+ if((t=x>>8) != 0) { x = t; r += 8; }
+ if((t=x>>4) != 0) { x = t; r += 4; }
+ if((t=x>>2) != 0) { x = t; r += 2; }
+ if((t=x>>1) != 0) { x = t; r += 1; }
+ return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+ if(this.t <= 0) return 0;
+ return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+ var i;
+ for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+ for(i = n-1; i >= 0; --i) r[i] = 0;
+ r.t = this.t+n;
+ r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+ for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+ r.t = Math.max(this.t-n,0);
+ r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<cbs)-1;
+ var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+ for(i = this.t-1; i >= 0; --i) {
+ r[i+ds+1] = (this[i]>>cbs)|c;
+ c = (this[i]&bm)<<bs;
+ }
+ for(i = ds-1; i >= 0; --i) r[i] = 0;
+ r[ds] = c;
+ r.t = this.t+ds+1;
+ r.s = this.s;
+ r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+ r.s = this.s;
+ var ds = Math.floor(n/this.DB);
+ if(ds >= this.t) { r.t = 0; return; }
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<bs)-1;
+ r[0] = this[ds]>>bs;
+ for(var i = ds+1; i < this.t; ++i) {
+ r[i-ds-1] |= (this[i]&bm)<<cbs;
+ r[i-ds] = this[i]>>bs;
+ }
+ if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs;
+ r.t = this.t-ds;
+ r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+ var i = 0, c = 0, m = Math.min(a.t,this.t);
+ while(i < m) {
+ c += this[i]-a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ if(a.t < this.t) {
+ c -= a.s;
+ while(i < this.t) {
+ c += this[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c += this.s;
+ }
+ else {
+ c += this.s;
+ while(i < a.t) {
+ c -= a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c -= a.s;
+ }
+ r.s = (c<0)?-1:0;
+ if(c < -1) r[i++] = this.DV+c;
+ else if(c > 0) r[i++] = c;
+ r.t = i;
+ r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+ var x = this.abs(), y = a.abs();
+ var i = x.t;
+ r.t = i+y.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+ r.s = 0;
+ r.clamp();
+ if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+ var x = this.abs();
+ var i = r.t = 2*x.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < x.t-1; ++i) {
+ var c = x.am(i,x[i],r,2*i,0,1);
+ if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+ r[i+x.t] -= x.DV;
+ r[i+x.t+1] = 1;
+ }
+ }
+ if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+ r.s = 0;
+ r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m. q or r may be null.
+function bnpDivRemTo(m,q,r) {
+ var pm = m.abs();
+ if(pm.t <= 0) return;
+ var pt = this.abs();
+ if(pt.t < pm.t) {
+ if(q != null) q.fromInt(0);
+ if(r != null) this.copyTo(r);
+ return;
+ }
+ if(r == null) r = nbi();
+ var y = nbi(), ts = this.s, ms = m.s;
+ var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
+ if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+ else { pm.copyTo(y); pt.copyTo(r); }
+ var ys = y.t;
+ var y0 = y[ys-1];
+ if(y0 == 0) return;
+ var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0);
+ var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+ var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+ y.dlShiftTo(j,t);
+ if(r.compareTo(t) >= 0) {
+ r[r.t++] = 1;
+ r.subTo(t,r);
+ }
+ BigInteger.ONE.dlShiftTo(ys,t);
+ t.subTo(y,y); // "negative" y so we can replace sub with am later
+ while(y.t < ys) y[y.t++] = 0;
+ while(--j >= 0) {
+ // Estimate quotient digit
+ var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+ if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
+ y.dlShiftTo(j,t);
+ r.subTo(t,r);
+ while(r[i] < --qd) r.subTo(t,r);
+ }
+ }
+ if(q != null) {
+ r.drShiftTo(ys,q);
+ if(ts != ms) BigInteger.ZERO.subTo(q,q);
+ }
+ r.t = ys;
+ r.clamp();
+ if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
+ if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+ var r = nbi();
+ this.abs().divRemTo(a,null,r);
+ if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+ return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+ if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+ else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+// xy == 1 (mod m)
+// xy = 1+km
+// xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+ if(this.t < 1) return 0;
+ var x = this[0];
+ if((x&1) == 0) return 0;
+ var y = x&3; // y == 1/x mod 2^2
+ y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
+ y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
+ y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
+ // last step - calculate inverse mod DV directly;
+ // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+ y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
+ // we really want the negative inverse, and -DV < y < DV
+ return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+ this.m = m;
+ this.mp = m.invDigit();
+ this.mpl = this.mp&0x7fff;
+ this.mph = this.mp>>15;
+ this.um = (1<<(m.DB-15))-1;
+ this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+ var r = nbi();
+ x.abs().dlShiftTo(this.m.t,r);
+ r.divRemTo(this.m,null,r);
+ if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+ return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+ var r = nbi();
+ x.copyTo(r);
+ this.reduce(r);
+ return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+ while(x.t <= this.mt2) // pad x so am has enough room later
+ x[x.t++] = 0;
+ for(var i = 0; i < this.m.t; ++i) {
+ // faster way of calculating u0 = x[i]*mp mod DV
+ var j = x[i]&0x7fff;
+ var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+ // use am to combine the multiply-shift-add into one call
+ j = i+this.m.t;
+ x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+ // propagate carry
+ while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+ }
+ x.clamp();
+ x.drShiftTo(this.m.t,x);
+ if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+ if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+ var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+ g.copyTo(r);
+ while(--i >= 0) {
+ z.sqrTo(r,r2);
+ if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+ else { var t = r; r = r2; r2 = t; }
+ }
+ return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+ var z;
+ if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+ return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
+// ==== File: prng4.js
+// prng4.js - uses Arcfour as a PRNG
+
+function Arcfour() {
+ this.i = 0;
+ this.j = 0;
+ this.S = new Array();
+}
+
+// Initialize arcfour context from key, an array of ints, each from [0..255]
+function ARC4init(key) {
+ var i, j, t;
+ for(i = 0; i < 256; ++i)
+ this.S[i] = i;
+ j = 0;
+ for(i = 0; i < 256; ++i) {
+ j = (j + this.S[i] + key[i % key.length]) & 255;
+ t = this.S[i];
+ this.S[i] = this.S[j];
+ this.S[j] = t;
+ }
+ this.i = 0;
+ this.j = 0;
+}
+
+function ARC4next() {
+ var t;
+ this.i = (this.i + 1) & 255;
+ this.j = (this.j + this.S[this.i]) & 255;
+ t = this.S[this.i];
+ this.S[this.i] = this.S[this.j];
+ this.S[this.j] = t;
+ return this.S[(t + this.S[this.i]) & 255];
+}
+
+Arcfour.prototype.init = ARC4init;
+Arcfour.prototype.next = ARC4next;
+
+// Plug in your RNG constructor here
+function prng_newstate() {
+ return new Arcfour();
+}
+
+// Pool size must be a multiple of 4 and greater than 32.
+// An array of bytes the size of the pool will be passed to init()
+var rng_psize = 256;
+// ==== File: rng.js
+// Random number generator - requires a PRNG backend, e.g. prng4.js
+
+// For best results, put code like
+// <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'>
+// in your main HTML document.
+
+var rng_state;
+var rng_pool;
+var rng_pptr;
+
+// Mix in a 32-bit integer into the pool
+function rng_seed_int(x) {
+ rng_pool[rng_pptr++] ^= x & 255;
+ rng_pool[rng_pptr++] ^= (x >> 8) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 16) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 24) & 255;
+ if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
+}
+
+// Mix in the current time (w/milliseconds) into the pool
+function rng_seed_time() {
+ rng_seed_int(new Date().getTime());
+}
+
+// Initialize the pool with junk if needed.
+if(rng_pool == null) {
+ rng_pool = new Array();
+ rng_pptr = 0;
+ var t;
+ if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
+ // Extract entropy (256 bits) from NS4 RNG if available
+ var z = window.crypto.random(32);
+ for(t = 0; t < z.length; ++t)
+ rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
+ }
+ while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
+ t = Math.floor(65536 * Math.random());
+ rng_pool[rng_pptr++] = t >>> 8;
+ rng_pool[rng_pptr++] = t & 255;
+ }
+ rng_pptr = 0;
+ rng_seed_time();
+ //rng_seed_int(window.screenX);
+ //rng_seed_int(window.screenY);
+}
+
+function rng_get_byte() {
+ if(rng_state == null) {
+ rng_seed_time();
+ rng_state = prng_newstate();
+ rng_state.init(rng_pool);
+ for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
+ rng_pool[rng_pptr] = 0;
+ rng_pptr = 0;
+ //rng_pool = null;
+ }
+ // TODO: allow reseeding after first request
+ return rng_state.next();
+}
+
+function rng_get_bytes(ba) {
+ var i;
+ for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
+}
+
+function SecureRandom() {}
+
+SecureRandom.prototype.nextBytes = rng_get_bytes;
+// ==== File: rsa.js
+// Depends on jsbn.js and rng.js
+
+// Version 1.1: support utf-8 encoding in pkcs1pad2
+
+// convert a (hex) string to a bignum object
+function parseBigInt(str,r) {
+ return new BigInteger(str,r);
+}
+
+function linebrk(s,n) {
+ var ret = "";
+ var i = 0;
+ while(i + n < s.length) {
+ ret += s.substring(i,i+n) + "\n";
+ i += n;
+ }
+ return ret + s.substring(i,s.length);
+}
+
+function byte2Hex(b) {
+ if(b < 0x10)
+ return "0" + b.toString(16);
+ else
+ return b.toString(16);
+}
+
+// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
+function pkcs1pad2(s,n) {
+ if(n < s.length + 11) { // TODO: fix for utf-8
+ alert("Message too long for RSA");
+ return null;
+ }
+ var ba = new Array();
+ var i = s.length - 1;
+ while(i >= 0 && n > 0) {
+ var c = s.charCodeAt(i--);
+ if(c < 128) { // encode using utf-8
+ ba[--n] = c;
+ }
+ else if((c > 127) && (c < 2048)) {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = (c >> 6) | 192;
+ }
+ else {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = ((c >> 6) & 63) | 128;
+ ba[--n] = (c >> 12) | 224;
+ }
+ }
+ ba[--n] = 0;
+ var rng = new SecureRandom();
+ var x = new Array();
+ while(n > 2) { // random non-zero pad
+ x[0] = 0;
+ while(x[0] == 0) rng.nextBytes(x);
+ ba[--n] = x[0];
+ }
+ ba[--n] = 2;
+ ba[--n] = 0;
+ return new BigInteger(ba);
+}
+
+// "empty" RSA key constructor
+function RSAKey() {
+ this.n = null;
+ this.e = 0;
+ this.d = null;
+ this.p = null;
+ this.q = null;
+ this.dmp1 = null;
+ this.dmq1 = null;
+ this.coeff = null;
+}
+
+// Set the public key fields N and e from hex strings
+function RSASetPublic(N,E) {
+ if(N != null && E != null && N.length > 0 && E.length > 0) {
+ this.n = parseBigInt(N,16);
+ this.e = parseInt(E,16);
+ }
+ else
+ alert("Invalid RSA public key");
+}
+
+// Perform raw public operation on "x": return x^e (mod n)
+function RSADoPublic(x) {
+ return x.modPowInt(this.e, this.n);
+}
+
+// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
+function RSAEncrypt(text) {
+ var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
+ if(m == null) return null;
+ var c = this.doPublic(m);
+ if(c == null) return null;
+ var h = c.toString(16);
+ if((h.length & 1) == 0) return h; else return "0" + h;
+}
+
+// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
+//function RSAEncryptB64(text) {
+// var h = this.encrypt(text);
+// if(h) return hex2b64(h); else return null;
+//}
+
+// protected
+RSAKey.prototype.doPublic = RSADoPublic;
+
+// public
+RSAKey.prototype.setPublic = RSASetPublic;
+RSAKey.prototype.encrypt = RSAEncrypt;
+//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
+// ==== File: base64.js
+var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+var b64pad="=";
+
+function hex2b64(h) {
+ var i;
+ var c;
+ var ret = "";
+ for(i = 0; i+3 <= h.length; i+=3) {
+ c = parseInt(h.substring(i,i+3),16);
+ ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
+ }
+ if(i+1 == h.length) {
+ c = parseInt(h.substring(i,i+1),16);
+ ret += b64map.charAt(c << 2);
+ }
+ else if(i+2 == h.length) {
+ c = parseInt(h.substring(i,i+2),16);
+ ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
+ }
+ while((ret.length & 3) > 0) ret += b64pad;
+ return ret;
+}
+
+// convert a base64 string to hex
+function b64tohex(s) {
+ var ret = ""
+ var i;
+ var k = 0; // b64 state, 0-3
+ var slop;
+ for(i = 0; i < s.length; ++i) {
+ if(s.charAt(i) == b64pad) break;
+ v = b64map.indexOf(s.charAt(i));
+ if(v < 0) continue;
+ if(k == 0) {
+ ret += int2char(v >> 2);
+ slop = v & 3;
+ k = 1;
+ }
+ else if(k == 1) {
+ ret += int2char((slop << 2) | (v >> 4));
+ slop = v & 0xf;
+ k = 2;
+ }
+ else if(k == 2) {
+ ret += int2char(slop);
+ ret += int2char(v >> 2);
+ slop = v & 3;
+ k = 3;
+ }
+ else {
+ ret += int2char((slop << 2) | (v >> 4));
+ ret += int2char(v & 0xf);
+ k = 0;
+ }
+ }
+ if(k == 1)
+ ret += int2char(slop << 2);
+ return ret;
+}
+
+// convert a base64 string to a byte/number array
+function b64toBA(s) {
+ //piggyback on b64tohex for now, optimize later
+ var h = b64tohex(s);
+ var i;
+ var a = new Array();
+ for(i = 0; 2*i < h.length; ++i) {
+ a[i] = parseInt(h.substring(2*i,2*i+2),16);
+ }
+ return a;
+}
diff --git a/src/gwt/tools/sync-ace-commits b/src/gwt/tools/sync-ace-commits
new file mode 100755
index 0000000..8fa9692
--- /dev/null
+++ b/src/gwt/tools/sync-ace-commits
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+# This script is used to automate combining an upstream version of Ace
+# with the patches that we maintain.
+#
+# If you need to run this script, some prerequisites are required.
+# - Install Node.js and npm
+# - Run "sudo npm install --global dryice"
+#
+# To sync to a more recent upstream commit of Ace, update the "git checkout"
+# line below with the SHA-1 of the upstream commit. (Do NOT use a branch, for
+# example "upstream/master"; use the SHA-1 as it's the only thing guaranteed
+# never to change.) Then run this script.
+#
+# As upstream Ace evolves, inevitably they introduce changes that conflict
+# with the patches we maintain. To resolve these conflicts, go to the ./ace
+# subdirectory that this script creates, and:
+# - `git remote set-url origin git at github.com:rstudio/ace.git`
+# # Only need to do set-url once
+# - `git checkout <SHA-1>` # Go to the upstream commit
+# - `git merge <bugfix-branch>` # Try merging
+# - If previous step SUCCEEDS, start at the top but try the next branch.
+# - If the merge FAILS:
+# - `git merge --abort`
+# - `git checkout -b <new-bugfix-branch>` # I use the old name plus a number
+# - `git merge <bugfix-branch>`
+# - Resolve the merge conflict as you usually would, and commit
+# - `git push -u origin <new-bugfix-branch>`
+# - Update this sync-ace-commits script to refer to the new bugfix branch
+# - Repeat all of these steps if you still get merge conflicts
+
+set -e
+
+if [ ! -d "./ace" ]; then
+ git clone git://github.com/rstudio/ace.git
+ cd ace
+ git remote add upstream git://github.com/ajaxorg/ace.git
+ cd ..
+fi
+
+if [ ! -d "./ace/node_modules" ]; then
+ cd ace
+ npm install uglify-js
+ npm install dryice
+ cd ..
+fi
+
+cd ace
+cd build
+git checkout -- .
+cd ..
+
+git fetch origin
+git fetch upstream
+
+# Point this to the upstream commit that we've tested
+# NOTE: the following commits have been merged by upstream
+# so should be removed the next time we sync:
+#
+# origin/patch/eclipse-selectedword
+# origin/patch/blinking-cursor
+# origin/patch/vim-ctrl-behavior
+#
+git checkout fc01cc624296c1b1be59b55ce03051fa29b5bd96
+
+git submodule update --init --recursive
+
+# Merge all the bugfix branches
+git merge \
+ origin/bugfix-webkit-paste2 \
+ origin/bugfix-updatefontsize2 \
+ origin/bugfix-active-line-back \
+ origin/patch-advanced-indent4 \
+ origin/patch/normalizeinput4 \
+ origin/bugfix/no-paste-event-on-indent \
+ origin/patch/build-matchingbraceoutdent3
+
+git merge \
+ origin/patch/mousewheel3 \
+ origin/patch/consistent-gutter-width3 \
+ origin/patch/suppress-native-scroll \
+ origin/patch/build \
+ origin/patch/remove-webfont \
+ origin/patch/eclipse-selectedword \
+ origin/patch/emacs-vim-keybindings
+
+git merge \
+ origin/patch/blinking-cursor \
+ origin/patch/vim-ctrl-behavior \
+ origin/patch/laggy-typing \
+ origin/bugfix/outdent-newline \
+ origin/bugfix/safari-fractional-cursor \
+ origin/feature/scrolling-compensation
+
+
+node ./Makefile.dryice.js normal
+node ./Makefile.dryice.js -m normal
+cp build/src/ace.js ../../src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace-uncompressed.js
+cp build/src-min/ace.js ../../src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace.js
+
+stripUseStrict() {
+ sed 's/"use strict";//g' "$1" > "$1.stripped"
+ mv "$1.stripped" "$1"
+}
+
+stripUseStrict ../../src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace.js
+stripUseStrict ../../src/org/rstudio/studio/client/workbench/views/source/editors/text/ace/ace-uncompressed.js
diff --git a/src/gwt/tools/sync-pdfjs b/src/gwt/tools/sync-pdfjs
new file mode 100755
index 0000000..3460d45
--- /dev/null
+++ b/src/gwt/tools/sync-pdfjs
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "./pdfjs" ]; then
+ git clone git://github.com/rstudio/pdf.js.git pdfjs
+ cd pdfjs
+ git remote add upstream git://github.com/mozilla/pdf.js.git
+ cd ..
+fi
+
+cd pdfjs
+git clean -dfx
+
+# Use this commit
+git checkout c684dfc
+
+make
+cd ..
+
+minify () {
+ echo "Minifying $1.js => $1.min.js"
+ CC_OPTS="--compilation_level SIMPLE_OPTIMIZATIONS --language_in ECMASCRIPT5"
+ java -jar "compiler/compiler.jar" $CC_OPTS --js ../src/org/rstudio/studio/client/pdfviewer/pdfjs/$1.js --js_output_file ../src/org/rstudio/studio/client/pdfviewer/pdfjs/$1.min.js
+}
+
+cp pdfjs/build/pdf.js ../src/org/rstudio/studio/client/pdfviewer/pdfjs/
+cp pdfjs/web/compatibility.js ../src/org/rstudio/studio/client/pdfviewer/pdfjs/
+cp pdfjs/web/debugger.js ../src/org/rstudio/studio/client/pdfviewer/pdfjs/
+cp pdfjs/web/viewer.js ../src/org/rstudio/studio/client/pdfviewer/pdfjs/viewer.js
+cp pdfjs/web/viewer.css ../src/org/rstudio/studio/client/pdfviewer/pdfjs/
+
+minify pdf
+minify compatibility
+minify debugger
+minify viewer
diff --git a/src/gwt/tools/unicode-chars-util.rb b/src/gwt/tools/unicode-chars-util.rb
new file mode 100755
index 0000000..ad3fab5
--- /dev/null
+++ b/src/gwt/tools/unicode-chars-util.rb
@@ -0,0 +1,49 @@
+#!/usr/bin/env ruby
+
+#
+# unicode-chars-util.rb
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+
+# Takes utf_info.cxx from Hunspell, which is a list of the Unicode code points
+# that the spelling tokenizer should treat as letters, and turns them into a
+# compact string representation to be used in UnicodeLetters.java.
+
+if ARGV.length != 1
+ $stderr.puts "Usage: #{$0} <path/to/utf_info.cxx>"
+ $stderr.puts "utf_info.cxx is available from the Hunspell source distribution"
+ exit 1
+end
+
+def emit(from, to)
+ printf "\\u%04X\\u%04X", from, to
+end
+
+open = nil
+prev = -1
+
+print '"'
+File.open(ARGV[0], "r") do |file|
+ file.each do |line|
+ next unless line =~ /\{ 0x([0-9A-F]{4}), 0x([0-9A-F]{4}), 0x([0-9A-F]{4}) }/
+ value = $1.to_i(16)
+
+ raise "Lines are not sorted, script needs to be modified to sort lines!" unless value > prev
+
+ if value != prev + 1
+ emit(open, prev) if open
+ open = value
+ end
+ prev = value
+ end
+end
+
+emit(open, prev) if open
+puts '"'
diff --git a/src/gwt/tools/update-rsa-js b/src/gwt/tools/update-rsa-js
new file mode 100755
index 0000000..22a85e2
--- /dev/null
+++ b/src/gwt/tools/update-rsa-js
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+#
+# update-rsa-js
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+#
+
+# install dir
+INSTALL_DIR=`pwd`
+
+echo "// Downloaded from http://www-cs-students.stanford.edu/~tjw/ at `date`" > rsa.js
+echo "// ==== File: jsbn.js" >> rsa.js
+wget --quiet -O - http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js >> rsa.js
+echo "// ==== File: prng4.js" >> rsa.js
+wget --quiet -O - http://www-cs-students.stanford.edu/~tjw/jsbn/prng4.js >> rsa.js
+echo "// ==== File: rng.js" >> rsa.js
+wget --quiet -O - http://www-cs-students.stanford.edu/~tjw/jsbn/rng.js >> rsa.js
+echo "// ==== File: rsa.js" >> rsa.js
+wget --quiet -O - http://www-cs-students.stanford.edu/~tjw/jsbn/rsa.js >> rsa.js
+echo "// ==== File: base64.js" >> rsa.js
+wget --quiet -O - http://www-cs-students.stanford.edu/~tjw/jsbn/base64.js >> rsa.js
+
+CC_OPTS="--compilation_level ADVANCED_OPTIMIZATIONS"
+java -jar "compiler/compiler.jar" $CC_OPTS --js rsa.js --js encrypt-bootstrap.js --js_output_file "$INSTALL_DIR/../www/js/encrypt.min.js"
+
diff --git a/src/gwt/www/.gitignore b/src/gwt/www/.gitignore
new file mode 100644
index 0000000..b260e64
--- /dev/null
+++ b/src/gwt/www/.gitignore
@@ -0,0 +1,3 @@
+rstudio
+templates/addins
+
diff --git a/src/gwt/www/css/data.css b/src/gwt/www/css/data.css
new file mode 100644
index 0000000..c8e3f43
--- /dev/null
+++ b/src/gwt/www/css/data.css
@@ -0,0 +1,45 @@
+/*
+ * data.css
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+body {
+ margin: 0;
+ padding: 0;
+}
+table {
+ border-collapse: collapse;
+ margin: 0;
+ border: none;
+}
+th, td {
+ font-size: 11px;
+ border: 1px solid #DDD;
+ padding: 3px 12px 3px 6px;
+ white-space: pre;
+}
+th {
+ text-align: left;
+}
+td {
+ font-family: Consolas, Lucida Console, Monaco, monospace;
+}
+thead th, .rn, #origin {
+ font-family: Segoe UI, Lucida Grande, Verdana, Helvetica;
+ background-color: #F0F0F0;
+ font-weight: bold;
+ color: #555;
+}
+.rn, #origin {
+ text-align: right;
+ border-left: none;
+}
diff --git a/src/gwt/www/docs/keyboard.htm b/src/gwt/www/docs/keyboard.htm
new file mode 100644
index 0000000..9f5ccbe
--- /dev/null
+++ b/src/gwt/www/docs/keyboard.htm
@@ -0,0 +1,687 @@
+<html>
+
+<head>
+
+<title>RStudio: Keyboard Shortcuts</title>
+
+<link rel="stylesheet" href="../rstudio.css" type="text/css"/>
+
+<style type="text/css">
+#banner {
+ margin-bottom: 0px;
+}
+.shortcuts th {
+ text-align: left;
+ padding-right: 20px;
+}
+.shortcuts, .shortcuts td {
+ font-size: 10pt;
+ padding-right: 20px;
+}
+</style>
+
+</head>
+
+<body>
+
+<h3 id="banner"><img src="../images/rstudio.png" width="73" height="17" title="RStudio"/></h3>
+
+<div style="margin-left: 20px; margin-right: 20px">
+
+<h2>Keyboard Shortcuts</h2>
+
+<style type='text/css'>
+ .shortcuts td, .shortcuts th {
+ padding-left: 1.6em;
+ text-indent: -1.6em;
+ padding-bottom: 0.5em; }
+</style>
+<table class='shortcuts'>
+ <tr><td colspan="3"><h3>Console</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Move cursor to Console</td>
+ <td>Ctrl+2</td>
+ <td>Ctrl+2</td>
+ </tr>
+ <tr>
+ <td>Clear console</td>
+ <td>Ctrl+L</td>
+ <td>Command+L</td>
+ </tr>
+ <tr>
+ <td>Move cursor to beginning of line</td>
+ <td>Home</td>
+ <td>Command+Left</td>
+ </tr>
+ <tr>
+ <td>Move cursor to end of line</td>
+ <td>End</td>
+ <td>Command+Right</td>
+ </tr>
+ <tr>
+ <td>Navigate command history</td>
+ <td>Up/Down</td>
+ <td>Up/Down</td>
+ </tr>
+ <tr>
+ <td>Popup command history</td>
+ <td>Ctrl+Up</td>
+ <td>Command+Up</td>
+ </tr>
+ <tr>
+ <td>Interrupt currently executing command</td>
+ <td>Esc</td>
+ <td>Esc</td>
+ </tr>
+ <tr>
+ <td>Change working directory</td>
+ <td>Ctrl+Shift+H</td>
+ <td>Ctrl+Shift+H</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Source</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Goto File/Function</td>
+ <td>Ctrl+.</td>
+ <td>Ctrl+.</td>
+ </tr>
+ <tr>
+ <td>Move cursor to Source Editor</td>
+ <td>Ctrl+1</td>
+ <td>Ctrl+1</td>
+ </tr>
+ <tr>
+ <td>New document (except on Chrome/Windows)</td>
+ <td>Ctrl+Shift+N</td>
+ <td>Command+Shift+N</td>
+ </tr>
+ <tr>
+ <td>Open document</td>
+ <td>Ctrl+O</td>
+ <td>Command+O</td>
+ </tr>
+ <tr>
+ <td>Save active document</td>
+ <td>Ctrl+S</td>
+ <td>Command+S</td>
+ </tr>
+ <tr>
+ <td>Close active document (except on Chrome)</td>
+ <td>Ctrl+W</td>
+ <td>Command+W</td>
+ </tr>
+ <tr>
+ <td>Close active document (Chrome only)</td>
+ <td>Ctrl+Alt+W</td>
+ <td>Command+Option+W</td>
+ </tr>
+ <tr>
+ <td>Close all open documents</td>
+ <td>Ctrl+Shift+W</td>
+ <td>Command+Shift+W</td>
+ </tr>
+ <tr>
+ <td>Preview HTML (Markdown and HTML)</td>
+ <td>Ctrl+Shift+Y</td>
+ <td>Command+Shift+Y</td>
+ </tr>
+ <tr>
+ <td>Knit Document (knitr)</td>
+ <td>Ctrl+Shift+K</td>
+ <td>Command+Shift+K</td>
+ </tr>
+ <tr>
+ <td>Compile PDF (TeX and Sweave)</td>
+ <td>Ctrl+Shift+I</td>
+ <td>Command+Shift+I</td>
+ </tr>
+ <tr>
+ <td>Insert chunk (Sweave and Knitr)</td>
+ <td>Ctrl+Alt+I</td>
+ <td>Command+Option+I</td>
+ </tr>
+ <tr>
+ <td>Insert code section</td>
+ <td>Ctrl+Shift+R</td>
+ <td>Command+Shift+R</td>
+ </tr>
+ <tr>
+ <td>Run current line/selection</td>
+ <td>Ctrl+Enter</td>
+ <td>Command+Enter</td>
+ </tr>
+ <tr>
+ <td>Re-run previous region</td>
+ <td>Ctrl+Shift+P</td>
+ <td>Command+Shift+P</td>
+ </tr>
+ <tr>
+ <td>Run current document</td>
+ <td>Ctrl+Alt+R</td>
+ <td>Command+Option+R</td>
+ </tr>
+ <tr>
+ <td>Run from document beginning to current line</td>
+ <td>Ctrl+Alt+B</td>
+ <td>Command+Option+B</td>
+ </tr>
+ <tr>
+ <td>Run from current line to document end</td>
+ <td>Ctrl+Alt+E</td>
+ <td>Command+Option+E</td>
+ </tr>
+ <tr>
+ <td>Run the current function definition</td>
+ <td>Ctrl+Alt+F</td>
+ <td>Command+Option+F</td>
+ </tr>
+ <tr>
+ <td>Run the current code section</td>
+ <td>Ctrl+Alt+S</td>
+ <td>Command+Option+S</td>
+ </tr>
+ <tr>
+ <td>Run the current Sweave chunk</td>
+ <td>Ctrl+Alt+C</td>
+ <td>Command+Option+C</td>
+ </tr>
+ <tr>
+ <td>Run the next Sweave chunk</td>
+ <td>Ctrl+Alt+N</td>
+ <td>Command+Option+N</td>
+ </tr>
+ <tr>
+ <td>Source a file</td>
+ <td>Ctrl+Shift+O</td>
+ <td>Command+Shift+O</td>
+ </tr>
+ <tr>
+ <td>Source the current document</td>
+ <td>Ctrl+Shift+S</td>
+ <td>Command+Shift+S</td>
+ </tr>
+ <tr>
+ <td>Source the current document (with echo)</td>
+ <td>Ctrl+Shift+Enter</td>
+ <td>Command+Shift+Enter</td>
+ </tr>
+ <tr>
+ <td>Fold Selected</td>
+ <td>Alt+L</td>
+ <td>Cmd+Option+L</td>
+ </tr>
+ <tr>
+ <td>Unfold Selected</td>
+ <td>Shift+Alt+L</td>
+ <td>Cmd+Shift+Option+L</td>
+ </tr>
+ <tr>
+ <td>Fold All</td>
+ <td>Alt+O</td>
+ <td>Cmd+Option+O</td>
+ </tr>
+ <tr>
+ <td>Unfold All</td>
+ <td>Shift+Alt+O</td>
+ <td>Cmd+Shift+Option+O</td>
+ </tr>
+ <tr>
+ <td>Go to line</td>
+ <td>Shift+Alt+G</td>
+ <td>Cmd+Shift+Option+G</td>
+ </tr>
+ <tr>
+ <td>Jump to</td>
+ <td>Shift+Alt+J</td>
+ <td>Cmd+Shift+Option+J</td>
+ </tr>
+ <tr>
+ <td>Switch to tab</td>
+ <td>Ctrl+Alt+Down</td>
+ <td>Ctrl+Option+Down</td>
+ </tr>
+ <tr>
+ <td>Previous tab</td>
+ <td>Win: Ctrl+Alt+Left, Linux: Ctrl+PageUp</td>
+ <td>Ctrl+Option+Left</td>
+ </tr>
+ <tr>
+ <td>Next tab</td>
+ <td>Win: Ctrl+Alt+Right, Linux: Ctrl+PageDown</td>
+ <td>Ctrl+Option+Right</td>
+ </tr>
+ <tr>
+ <td>First tab</td>
+ <td>Ctrl+Shift+Alt+Left</td>
+ <td>Ctrl+Shift+Option+Left</td>
+ </tr>
+ <tr>
+ <td>Last tab</td>
+ <td>Ctrl+Shift+Alt+Right</td>
+ <td>Ctrl+Shift+Option+Right</td>
+ </tr>
+ <tr>
+ <td>Navigate back</td>
+ <td>Ctrl+F9</td>
+ <td>Cmd+F9</td>
+ </tr>
+ <tr>
+ <td>Navigate forward</td>
+ <td>Ctrl+F10</td>
+ <td>Cmd+F10</td>
+ </tr>
+ <tr>
+ <td>Extract function from selection</td>
+ <td>Ctrl+Alt+T</td>
+ <td>Command+Option+T</td>
+ </tr>
+ <tr>
+ <td>Extract variable from selection</td>
+ <td>Ctrl+Alt+V</td>
+ <td>Command+Option+V</td>
+ </tr>
+ <tr>
+ <td>Reindent lines</td>
+ <td>Ctrl+I</td>
+ <td>Command+I</td>
+ </tr>
+ <tr>
+ <td>Comment/uncomment current line/selection</td>
+ <td>Ctrl+Shift+C</td>
+ <td>Command+Shift+C</td>
+ </tr>
+ <tr>
+ <td>Reflow Comment</td>
+ <td>Ctrl+Shift+/</td>
+ <td>Command+Shift+/</td>
+ </tr>
+ <tr>
+ <td>Transpose Letters</td>
+ <td></td>
+ <td>Ctrl+T</td>
+ </tr>
+ <tr>
+ <td>Move Lines Up/Down</td>
+ <td>Alt+Up/Down</td>
+ <td>Option+Up/Down</td>
+ </tr>
+ <tr>
+ <td>Copy Lines Up/Down</td>
+ <td>Shift+Alt+Up/Down</td>
+ <td>Command+Option+Up/Down</td>
+ </tr>
+ <tr>
+ <td>Jump to Matching Brace/Paren</td>
+ <td>Ctrl+P</td>
+ <td>Ctrl+P</td>
+ </tr>
+ <tr>
+ <td>Find and Replace</td>
+ <td>Ctrl+F</td>
+ <td>Command+F</td>
+ </tr>
+ <tr>
+ <td>Find Next</td>
+ <td>Win: F3, Linux: Ctrl+G</td>
+ <td>Command+G</td>
+ </tr>
+ <tr>
+ <td>Find Previous</td>
+ <td>Win: Shift+F3, Linux: Ctrl+Shift+G</td>
+ <td>Command+Shift+G</td>
+ </tr>
+ <tr>
+ <td>Use Selection for Find</td>
+ <td>Ctrl+F3</td>
+ <td>Command+E</td>
+ </tr>
+ <tr>
+ <td>Replace and Find</td>
+ <td>Ctrl+Shift+J</td>
+ <td>Command+Shift+J</td>
+ </tr>
+ <tr>
+ <td>Find in Files</td>
+ <td>Ctrl+Shift+F</td>
+ <td>Command+Shift+F</td>
+ </tr>
+ <tr>
+ <td>Check Spelling</td>
+ <td>F7</td>
+ <td>F7</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Editing (Console and Source)</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Undo</td>
+ <td>Ctrl+Z</td>
+ <td>Command+Z</td>
+ </tr>
+ <tr>
+ <td>Redo</td>
+ <td>Ctrl+Shift+Z</td>
+ <td>Command+Shift+Z</td>
+ </tr>
+ <tr>
+ <td>Cut</td>
+ <td>Ctrl+X</td>
+ <td>Command+X</td>
+ </tr>
+ <tr>
+ <td>Copy</td>
+ <td>Ctrl+C</td>
+ <td>Command+C</td>
+ </tr>
+ <tr>
+ <td>Paste</td>
+ <td>Ctrl+V</td>
+ <td>Command+V</td>
+ </tr>
+ <tr>
+ <td>Select All</td>
+ <td>Ctrl+A</td>
+ <td>Command+A</td>
+ </tr>
+ <tr>
+ <td>Jump to Word</td>
+ <td>Ctrl+Left/Right</td>
+ <td>Option+Left/Right</td>
+ </tr>
+ <tr>
+ <td>Jump to Start/End</td>
+ <td>Ctrl+Home/End or Ctrl+Up/Down</td>
+ <td>Command+Home/End or Command+Up/Down</td>
+ </tr>
+ <tr>
+ <td>Delete Line</td>
+ <td>Ctrl+D</td>
+ <td>Command+D</td>
+ </tr>
+ <tr>
+ <td>Select</td>
+ <td>Shift+[Arrow]</td>
+ <td>Shift+[Arrow]</td>
+ </tr>
+ <tr>
+ <td>Select Word</td>
+ <td>Ctrl+Shift+Left/Right</td>
+ <td>Option+Shift+Left/Right</td>
+ </tr>
+ <tr>
+ <td>Select to Line Start</td>
+ <td>Alt+Shift+Left</td>
+ <td>Command+Shift+Left</td>
+ </tr>
+ <tr>
+ <td>Select to Line End</td>
+ <td>Alt+Shift+Right</td>
+ <td>Command+Shift+Right</td>
+ </tr>
+ <tr>
+ <td>Select Page Up/Down</td>
+ <td>Shift+PageUp/PageDown</td>
+ <td>Shift+PageUp/Down</td>
+ </tr>
+ <tr>
+ <td>Select to Start/End</td>
+ <td>Ctrl+Shift+Home/End or Shift+Alt+Up/Down</td>
+ <td>Command+Shift+Up/Down</td>
+ </tr>
+ <tr>
+ <td>Delete Word Left</td>
+ <td>Ctrl+Backspace</td>
+ <td>Option+Backspace or Ctrl+Option+Backspace</td>
+ </tr>
+ <tr>
+ <td>Delete Word Right</td>
+ <td></td>
+ <td>Option+Delete</td>
+ </tr>
+ <tr>
+ <td>Delete to Line End</td>
+ <td></td>
+ <td>Ctrl+K</td>
+ </tr>
+ <tr>
+ <td>Delete to Line Start</td>
+ <td></td>
+ <td>Option+Backspace</td>
+ </tr>
+ <tr>
+ <td>Indent</td>
+ <td>Tab (at beginning of line)</td>
+ <td>Tab (at beginning of line)</td>
+ </tr>
+ <tr>
+ <td>Outdent</td>
+ <td>Shift+Tab</td>
+ <td>Shift+Tab</td>
+ </tr>
+ <tr>
+ <td>Yank line up to cursor</td>
+ <td>Ctrl+U</td>
+ <td>Ctrl+U</td>
+ </tr>
+ <tr>
+ <td>Yank line after cursor</td>
+ <td>Ctrl+K</td>
+ <td>Ctrl+K</td>
+ </tr>
+ <tr>
+ <td>Insert currently yanked text</td>
+ <td>Ctrl+Y</td>
+ <td>Ctrl+Y</td>
+ </tr>
+ <tr>
+ <td>Insert assignment operator</td>
+ <td>Alt+-</td>
+ <td>Option+-</td>
+ </tr>
+ <tr>
+ <td>Show help for function at cursor</td>
+ <td>F1</td>
+ <td>F1</td>
+ </tr>
+ <tr>
+ <td>Show source code for function at cursor</td>
+ <td>F2</td>
+ <td>F2</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Completions (Console and Source)</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Attempt completion</td>
+ <td>Tab or Ctrl+Space</td>
+ <td>Tab or Command+Space</td>
+ </tr>
+ <tr>
+ <td>Navigate candidates</td>
+ <td>Up/Down</td>
+ <td>Up/Down</td>
+ </tr>
+ <tr>
+ <td>Accept selected candidate</td>
+ <td>Enter, Tab, or Right</td>
+ <td>Enter, Tab, or Right</td>
+ </tr>
+ <tr>
+ <td>Dismiss completion popup</td>
+ <td>Esc</td>
+ <td>Esc</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Views</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Move focus to Source Editor</td>
+ <td>Ctrl+1</td>
+ <td>Ctrl+1</td>
+ </tr>
+ <tr>
+ <td>Move focus to Console</td>
+ <td>Ctrl+2</td>
+ <td>Ctrl+2</td>
+ </tr>
+ <tr>
+ <td>Move focus to Help</td>
+ <td>Ctrl+3</td>
+ <td>Ctrl+3</td>
+ </tr>
+ <tr>
+ <td>Show History</td>
+ <td>Ctrl+4</td>
+ <td>Ctrl+4</td>
+ </tr>
+ <tr>
+ <td>Show Files</td>
+ <td>Ctrl+5</td>
+ <td>Ctrl+5</td>
+ </tr>
+ <tr>
+ <td>Show Plots</td>
+ <td>Ctrl+6</td>
+ <td>Ctrl+6</td>
+ </tr>
+ <tr>
+ <td>Show Packages</td>
+ <td>Ctrl+7</td>
+ <td>Ctrl+7</td>
+ </tr>
+ <tr>
+ <td>Show Environment</td>
+ <td>Ctrl+8</td>
+ <td>Ctrl+8</td>
+ </tr>
+ <tr>
+ <td>Show Git/SVN</td>
+ <td>Ctrl+9</td>
+ <td>Ctrl+9</td>
+ </tr>
+ <tr>
+ <td>Show Build</td>
+ <td>Ctrl+0</td>
+ <td>Ctrl+0</td>
+ </tr>
+ <tr>
+ <td>Sync Editor & PDF Preview</td>
+ <td>Ctrl+F8</td>
+ <td>Cmd+F8</td>
+ </tr>
+ <tr>
+ <td>Show Keyboard Shortcut Reference</td>
+ <td>Alt+Shift+K</td>
+ <td>Option+Shift+K</td>
+ </tr>
+ <tr><td><br/></td></tr>
+
+ <tr><td colspan="3"><h3>Build</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Build and Reload</td>
+ <td>Ctrl+Shift+B</td>
+ <td>Cmd+Shift+B</td>
+ </tr>
+ <tr>
+ <td>Load All (devtools)</td>
+ <td>Ctrl+Shift+L</td>
+ <td>Cmd+Shift+L</td>
+ </tr>
+ <tr>
+ <td>Test Package (Desktop)</td>
+ <td>Ctrl+Shift+T</td>
+ <td>Cmd+Shift+T</td>
+ </tr>
+ <tr>
+ <td>Test Package (Web)</td>
+ <td>Ctrl+Alt+F7</td>
+ <td>Cmd+Alt+F7</td>
+ </tr>
+ <tr>
+ <td>Check Package</td>
+ <td>Ctrl+Shift+E</td>
+ <td>Cmd+Shift+E</td>
+ </tr>
+ <tr>
+ <td>Document Package</td>
+ <td>Ctrl+Shift+D</td>
+ <td>Cmd+Shift+D</td>
+ </tr>
+ <tr><td><br/></td></tr>
+
+ <tr><td colspan="3"><h3>Debug</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Toggle Breakpoint</td>
+ <td>Shift+F9</td>
+ <td>Shift+F9</td>
+ </tr>
+ <tr>
+ <td>Execute Next Line</td>
+ <td>F10</td>
+ <td>F10</td>
+ </tr>
+ <tr>
+ <td>Continue</td>
+ <td>Shift+F5</td>
+ <td>Shift+F5</td>
+ </tr>
+ <tr>
+ <td>Stop Debugging</td>
+ <td>Shift+F8</td>
+ <td>Shift+F8</td>
+ </tr>
+ <tr><td><br/></td></tr>
+
+ <tr><td colspan="3"><h3>Plots</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Previous plot</td>
+ <td>Ctrl+Shift+PageUp</td>
+ <td>Command+Shift+PageUp</td>
+ </tr>
+ <tr>
+ <td>Next plot</td>
+ <td>Ctrl+Shift+PageDown</td>
+ <td>Command+Shift+PageDown</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Git/SVN</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Diff active source document</td>
+ <td>Ctrl+Alt+D</td>
+ <td>Ctrl+Option+D</td>
+ </tr>
+ <tr>
+ <td>Commit changes</td>
+ <td>Ctrl+Alt+M</td>
+ <td>Ctrl+Option+M</td>
+ </tr>
+ <tr>
+ <td>Scroll diff view</td>
+ <td>Ctrl+Up/Down</td>
+ <td>Ctrl+Up/Down</td>
+ </tr>
+ <tr>
+ <td>Stage/Unstage (Git)</td>
+ <td>Spacebar</td>
+ <td>Spacebar</td>
+ </tr>
+ <tr>
+ <td>Stage/Unstage and move to next (Git)</td>
+ <td>Enter</td>
+ <td>Enter</td>
+ </tr>
+ <tr><td><br/></td></tr>
+ <tr><td colspan="3"><h3>Session</h3></td></tr><tr><th>Description</th><th>Windows & Linux</th><th>Mac</th></tr>
+ <tr>
+ <td>Quit Session (desktop only)</td>
+ <td>Ctrl+Q</td>
+ <td>Command+Q</td>
+ </tr>
+ <tr>
+ <td>Restart R Session</td>
+ <td>Ctrl+Shift+F10</td>
+ <td>Command+Shift+F10</td>
+ </tr>
+ <tr><td><br/></td></tr>
+</table>
+</div>
+
+</body>
+</html>
diff --git a/src/gwt/www/expired.htm b/src/gwt/www/expired.htm
new file mode 100644
index 0000000..49d1027
--- /dev/null
+++ b/src/gwt/www/expired.htm
@@ -0,0 +1,88 @@
+<!--
+#
+# expired.htm
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+<html>
+
+<head>
+
+<title>RStudio Server Expired</title>
+<link rel="shortcut icon" href="images/favicon.ico" />
+
+<style type="text/css"/>
+
+body {
+ background-color: #fff;
+}
+body, td {
+ font-family: Lucida Grande, Lucida Sans Unicode, Helvetica, sans-serif;
+ font-size: 11pt;
+}
+
+
+
+#banner {
+ background-color: #e0e2e5;
+ margin: 30px 20px 70px 20px;
+ padding: 8px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+#border {
+ width: 300px;
+ border: 4px solid #e0e2e5;
+ padding: 8px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+}
+#image {
+ margin-right: 30px;
+}
+
+#caption {
+ text-align: left;
+ border-bottom: 4px solid #e0e2e5;
+ padding-bottom: 8px;
+ margin-bottom: 8px;
+ margin-right: 10px;
+ font-size: 16px;
+ font-weight: normal;
+ width: 300px;
+}
+
+#detail {
+ color: #aaa;
+ margin-top: 8px;
+ margin-right: 10px;
+}
+
+</style>
+
+</head>
+
+<h3 id="banner"><img src="images/rstudio.png" width="73" height="17" title="RStudio"/></h3>
+
+<table id="border" align="center">
+ <tr>
+ <td><img id="image" src="images/expired.png"/></td>
+ <td>
+ <h2 id="caption">RStudio Server License Expired</h2>
+ <div id="detail">This version of RStudio Server has expired. Please <a href="http://www.rstudio.com">contact us</a> to obtain an up to date license.</div>
+ </td>
+ </tr>
+</table>
+
+
+</body>
+
+</html>
diff --git a/src/gwt/www/favicon.ico b/src/gwt/www/favicon.ico
new file mode 100644
index 0000000..8644e72
Binary files /dev/null and b/src/gwt/www/favicon.ico differ
diff --git a/src/gwt/www/images/buttonLeft.png b/src/gwt/www/images/buttonLeft.png
new file mode 100644
index 0000000..a1d9b0d
Binary files /dev/null and b/src/gwt/www/images/buttonLeft.png differ
diff --git a/src/gwt/www/images/buttonRight.png b/src/gwt/www/images/buttonRight.png
new file mode 100644
index 0000000..7d9e454
Binary files /dev/null and b/src/gwt/www/images/buttonRight.png differ
diff --git a/src/gwt/www/images/buttonTile.png b/src/gwt/www/images/buttonTile.png
new file mode 100644
index 0000000..0cd910e
Binary files /dev/null and b/src/gwt/www/images/buttonTile.png differ
diff --git a/src/gwt/www/images/expired.png b/src/gwt/www/images/expired.png
new file mode 100644
index 0000000..c56c490
Binary files /dev/null and b/src/gwt/www/images/expired.png differ
diff --git a/src/gwt/www/images/favicon.ico b/src/gwt/www/images/favicon.ico
new file mode 100644
index 0000000..1238135
Binary files /dev/null and b/src/gwt/www/images/favicon.ico differ
diff --git a/src/gwt/www/images/offline.png b/src/gwt/www/images/offline.png
new file mode 100644
index 0000000..58144a9
Binary files /dev/null and b/src/gwt/www/images/offline.png differ
diff --git a/src/gwt/www/images/progress_large.gif b/src/gwt/www/images/progress_large.gif
new file mode 100644
index 0000000..f7b442f
Binary files /dev/null and b/src/gwt/www/images/progress_large.gif differ
diff --git a/src/gwt/www/images/rstudio.png b/src/gwt/www/images/rstudio.png
new file mode 100644
index 0000000..0b423c3
Binary files /dev/null and b/src/gwt/www/images/rstudio.png differ
diff --git a/src/gwt/www/images/warning.png b/src/gwt/www/images/warning.png
new file mode 100644
index 0000000..211c762
Binary files /dev/null and b/src/gwt/www/images/warning.png differ
diff --git a/src/gwt/www/index.htm b/src/gwt/www/index.htm
new file mode 100644
index 0000000..71f5e14
--- /dev/null
+++ b/src/gwt/www/index.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+
+<!--
+#
+# index.htm
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+<!-- standards mode -->
+<html>
+ <head>
+
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
+ <meta name="gwt:property" content="compiler.stackMode=#compiler_stack_mode#"/>
+ <link rel="shortcut icon" href="images/favicon.ico" />
+ <title>RStudio</title>
+ #!head_tags#
+ <script type="text/javascript" language="javascript" src="rstudio/rstudio.nocache.js"></script>
+
+ </head>
+
+ <body>
+ </body>
+
+</html>
diff --git a/src/gwt/www/js/diff.js b/src/gwt/www/js/diff.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/gwt/www/js/diff.js
@@ -0,0 +1 @@
+
diff --git a/src/gwt/www/js/encrypt.min.js b/src/gwt/www/js/encrypt.min.js
new file mode 100644
index 0000000..5ba4265
--- /dev/null
+++ b/src/gwt/www/js/encrypt.min.js
@@ -0,0 +1,23 @@
+var g,j,k=(244837814094590&16777215)==15715070;function l(b,a,c){if(b!=null)if("number"==typeof b)this.ca(b,a,c);else a==null&&"string"!=typeof b?this.z(b,256):this.z(b,a)}function m(){return new l(null)}function o(b,a,c,d,e,f){for(;--f>=0;){var h=a*this[b++]+c[d]+e;e=Math.floor(h/67108864);c[d++]=h&67108863}return e}
+function p(b,a,c,d,e,f){var h=a&32767;for(a=a>>15;--f>=0;){var i=this[b]&32767,n=this[b++]>>15,r=a*i+n*h;i=h*i+((r&32767)<<15)+c[d]+(e&1073741823);e=(i>>>30)+(r>>>15)+a*n+(e>>>30);c[d++]=i&1073741823}return e}function s(b,a,c,d,e,f){var h=a&16383;for(a=a>>14;--f>=0;){var i=this[b]&16383,n=this[b++]>>14,r=a*i+n*h;i=h*i+((r&16383)<<14)+c[d]+e;e=(i>>28)+(r>>14)+a*n;c[d++]=i&268435455}return e}
+if(k&&navigator.appName=="Microsoft Internet Explorer"){l.prototype.i=p;j=30}else if(k&&navigator.appName!="Netscape"){l.prototype.i=o;j=26}else{l.prototype.i=s;j=28}g=l.prototype;g.c=j;g.g=(1<<j)-1;g.h=1<<j;g.K=Math.pow(2,52);g.r=52-j;g.s=2*j-52;var t="0123456789abcdefghijklmnopqrstuvwxyz",u=[],x,z;x="0".charCodeAt(0);for(z=0;z<=9;++z)u[x++]=z;x="a".charCodeAt(0);for(z=10;z<36;++z)u[x++]=z;x="A".charCodeAt(0);for(z=10;z<36;++z)u[x++]=z;function A(b,a){b=u[b.charCodeAt(a)];return b==null?-1:b}
+function B(b){for(var a=this.a-1;a>=0;--a)b[a]=this[a];b.a=this.a;b.b=this.b}function D(b){this.a=1;this.b=b<0?-1:0;if(b>0)this[0]=b;else if(b<-1)this[0]=b+DV;else this.a=0}function E(b){var a=m();a.w(b);return a}
+function F(b,a){if(a==16)a=4;else if(a==8)a=3;else if(a==256)a=8;else if(a==2)a=1;else if(a==32)a=5;else if(a==4)a=2;else{this.da(b,a);return}this.b=this.a=0;for(var c=b.length,d=false,e=0;--c>=0;){var f=a==8?b[c]&255:A(b,c);if(f<0){if(b.charAt(c)=="-")d=true}else{d=false;if(e==0)this[this.a++]=f;else if(e+a>this.c){this[this.a-1]|=(f&(1<<this.c-e)-1)<<e;this[this.a++]=f>>this.c-e}else this[this.a-1]|=f<<e;e+=a;if(e>=this.c)e-=this.c}}if(a==8&&(b[0]&128)!=0){this.b=-1;if(e>0)this[this.a- [...]
+e)-1<<e}this.j();d&&G.f(this,this)}function H(){for(var b=this.b&this.g;this.a>0&&this[this.a-1]==b;)--this.a}
+function I(b){if(this.b<0)return"-"+this.G().toString(b);if(b==16)b=4;else if(b==8)b=3;else if(b==2)b=1;else if(b==32)b=5;else if(b==4)b=2;else return this.ga(b);var a=(1<<b)-1,c,d=false,e="",f=this.a,h=this.c-f*this.c%b;if(f-- >0){if(h<this.c&&(c=this[f]>>h)>0){d=true;e=t.charAt(c)}for(;f>=0;){if(h<b){c=(this[f]&(1<<h)-1)<<b-h;c|=this[--f]>>(h+=this.c-b)}else{c=this[f]>>(h-=b)&a;if(h<=0){h+=this.c;--f}}if(c>0)d=true;if(d)e+=t.charAt(c)}}return d?e:"0"}
+function J(){var b=m();G.f(this,b);return b}function K(){return this.b<0?this.G():this}function L(b){var a=this.b-b.b;if(a!=0)return a;var c=this.a;a=c-b.a;if(a!=0)return a;for(;--c>=0;)if((a=this[c]-b[c])!=0)return a;return 0}function M(b){var a=1,c;if((c=b>>>16)!=0){b=c;a+=16}if((c=b>>8)!=0){b=c;a+=8}if((c=b>>4)!=0){b=c;a+=4}if((c=b>>2)!=0){b=c;a+=2}if(b>>1!=0)a+=1;return a}function N(){if(this.a<=0)return 0;return this.c*(this.a-1)+M(this[this.a-1]^this.b&this.g)}
+function aa(b,a){var c;for(c=this.a-1;c>=0;--c)a[c+b]=this[c];for(c=b-1;c>=0;--c)a[c]=0;a.a=this.a+b;a.b=this.b}function ba(b,a){for(var c=b;c<this.a;++c)a[c-b]=this[c];a.a=Math.max(this.a-b,0);a.b=this.b}function ca(b,a){var c=b%this.c,d=this.c-c,e=(1<<d)-1;b=Math.floor(b/this.c);var f=this.b<<c&this.g,h;for(h=this.a-1;h>=0;--h){a[h+b+1]=this[h]>>d|f;f=(this[h]&e)<<c}for(h=b-1;h>=0;--h)a[h]=0;a[b]=f;a.a=this.a+b+1;a.b=this.b;a.j()}
+function da(b,a){a.b=this.b;var c=Math.floor(b/this.c);if(c>=this.a)a.a=0;else{b=b%this.c;var d=this.c-b,e=(1<<b)-1;a[0]=this[c]>>b;for(var f=c+1;f<this.a;++f){a[f-c-1]|=(this[f]&e)<<d;a[f-c]=this[f]>>b}if(b>0)a[this.a-c-1]|=(this.b&e)<<d;a.a=this.a-c;a.j()}}
+function ea(b,a){for(var c=0,d=0,e=Math.min(b.a,this.a);c<e;){d+=this[c]-b[c];a[c++]=d&this.g;d>>=this.c}if(b.a<this.a){for(d-=b.b;c<this.a;){d+=this[c];a[c++]=d&this.g;d>>=this.c}d+=this.b}else{for(d+=this.b;c<b.a;){d-=b[c];a[c++]=d&this.g;d>>=this.c}d-=b.b}a.b=d<0?-1:0;if(d<-1)a[c++]=this.h+d;else if(d>0)a[c++]=d;a.a=c;a.j()}function fa(b,a){var c=this.abs(),d=b.abs(),e=c.a;for(a.a=e+d.a;--e>=0;)a[e]=0;for(e=0;e<d.a;++e)a[e+c.a]=c.i(0,d[e],a,e,0,c.a);a.b=0;a.j();this.b!=b.b&&G.f(a,a)}
+function ga(b){for(var a=this.abs(),c=b.a=2*a.a;--c>=0;)b[c]=0;for(c=0;c<a.a-1;++c){var d=a.i(c,a[c],b,2*c,0,1);if((b[c+a.a]+=a.i(c+1,2*a[c],b,2*c+1,d,a.a-c-1))>=a.h){b[c+a.a]-=a.h;b[c+a.a+1]=1}}if(b.a>0)b[b.a-1]+=a.i(c,a[c],b,2*c,0,1);b.b=0;b.j()}
+function ha(b,a,c){var d=b.abs();if(!(d.a<=0)){var e=this.abs();if(e.a<d.a){a!=null&&a.w(0);c!=null&&this.m(c)}else{if(c==null)c=m();var f=m(),h=this.b;b=b.b;var i=this.c-M(d[d.a-1]);if(i>0){d.A(i,f);e.A(i,c)}else{d.m(f);e.m(c)}d=f.a;e=f[d-1];if(e!=0){var n=e*(1<<this.r)+(d>1?f[d-2]>>this.s:0),r=this.K/n;n=(1<<this.r)/n;var ia=1<<this.s,w=c.a,y=w-d,q=a==null?m():a;f.o(y,q);if(c.l(q)>=0){c[c.a++]=1;c.f(q,c)}O.o(d,q);for(q.f(f,f);f.a<d;)f[f.a++]=0;for(;--y>=0;){var C=c[--w]==e?this.g:Math. [...]
+r+(c[w-1]+ia)*n);if((c[w]+=f.i(0,C,c,y,0,d))<C){f.o(y,q);for(c.f(q,c);c[w]<--C;)c.f(q,c)}}if(a!=null){c.u(d,a);h!=b&&G.f(a,a)}c.a=d;c.j();i>0&&c.W(i,c);h<0&&G.f(c,c)}}}}function ja(b){var a=m();this.abs().p(b,null,a);this.b<0&&a.l(G)>0&&b.f(a,a);return a}function P(b){this.d=b}function ka(b){return b.b<0||b.l(this.d)>=0?b.R(this.d):b}function la(b){return b}function ma(b){b.p(this.d,null,b)}function na(b,a,c){b.F(a,c);this.reduce(c)}function oa(b,a){b.J(a);this.reduce(a)}g=P.prototype;g.t=ka;
+g.H=la;g.reduce=ma;g.D=na;g.I=oa;function pa(){if(this.a<1)return 0;var b=this[0];if((b&1)==0)return 0;var a=b&3;a=a*(2-(b&15)*a)&15;a=a*(2-(b&255)*a)&255;a=a*(2-((b&65535)*a&65535))&65535;a=a*(2-b*a%this.h)%this.h;return a>0?this.h-a:-a}function Q(b){this.d=b;this.B=b.P();this.C=this.B&32767;this.T=this.B>>15;this.Y=(1<<b.c-15)-1;this.U=2*b.a}function qa(b){var a=m();b.abs().o(this.d.a,a);a.p(this.d,null,a);b.b<0&&a.l(G)>0&&this.d.f(a,a);return a}
+function ra(b){var a=m();b.m(a);this.reduce(a);return a}function sa(b){for(;b.a<=this.U;)b[b.a++]=0;for(var a=0;a<this.d.a;++a){var c=b[a]&32767,d=c*this.C+((c*this.T+(b[a]>>15)*this.C&this.Y)<<15)&b.g;c=a+this.d.a;for(b[c]+=this.d.i(0,d,b,a,0,this.d.a);b[c]>=b.h;){b[c]-=b.h;b[++c]++}}b.j();b.u(this.d.a,b);b.l(this.d)>=0&&b.f(this.d,b)}function ta(b,a){b.J(a);this.reduce(a)}function ua(b,a,c){b.F(a,c);this.reduce(c)}g=Q.prototype;g.t=qa;g.H=ra;g.reduce=sa;g.D=ua;g.I=ta;
+function va(){return(this.a>0?this[0]&1:this.b)==0}function wa(b,a){if(b>4294967295||b<1)return O;var c=m(),d=m(),e=a.t(this),f=M(b)-1;for(e.m(c);--f>=0;){a.I(c,d);if((b&1<<f)>0)a.D(d,e,c);else{var h=c;c=d;d=h}}return a.H(c)}function xa(b,a){a=b<256||a.Q()?new P(a):new Q(a);return this.exp(b,a)}g=l.prototype;g.m=B;g.w=D;g.z=F;g.j=H;g.o=aa;g.u=ba;g.A=ca;g.W=da;g.f=ea;g.F=fa;g.J=ga;g.p=ha;g.P=pa;g.Q=va;g.exp=wa;g.toString=I;g.G=J;g.abs=K;g.l=L;g.L=N;g.R=ja;g.S=xa;var G=E(0),O=E(1);
+function R(){this.n=this.k=0;this.e=[]}function ya(b){var a,c,d;for(a=0;a<256;++a)this.e[a]=a;for(a=c=0;a<256;++a){c=c+this.e[a]+b[a%b.length]&255;d=this.e[a];this.e[a]=this.e[c];this.e[c]=d}this.n=this.k=0}function za(){var b;this.k=this.k+1&255;this.n=this.n+this.e[this.k]&255;b=this.e[this.k];this.e[this.k]=this.e[this.n];this.e[this.n]=b;return this.e[b+this.e[this.k]&255]}R.prototype.O=ya;R.prototype.next=za;var S=256,T,U,V;
+function W(b){U[V++]^=b&255;U[V++]^=b>>8&255;U[V++]^=b>>16&255;U[V++]^=b>>24&255;if(V>=S)V-=S}if(U==null){U=[];V=0;var X;if(navigator.appName=="Netscape"&&navigator.appVersion<"5"&&window.crypto){var Y=window.crypto.random(32);for(X=0;X<Y.length;++X)U[V++]=Y.charCodeAt(X)&255}for(;V<S;){X=Math.floor(65536*Math.random());U[V++]=X>>>8;U[V++]=X&255}V=0;W((new Date).getTime())}function Aa(){if(T==null){W((new Date).getTime());T=new R;T.O(U);for(V=0;V<U.length;++V)U[V]=0;V=0}return T.next()}
+function Ba(b){var a;for(a=0;a<b.length;++a)b[a]=Aa()}function Z(){}Z.prototype.V=Ba;function $(){this.q=null;this.v=0;this.Z=this.ba=this.aa=this.fa=this.ea=this.$=null}function Ca(b,a){if(b!=null&&a!=null&&b.length>0&&a.length>0){this.q=new l(b,16);this.v=parseInt(a,16)}else alert("Invalid RSA public key")}function Da(b){return b.S(this.v,this.q)}
+function Ea(b){var a;a=this.q.L()+7>>3;if(a<b.length+11){alert("Message too long for RSA");a=null}else{for(var c=[],d=b.length-1;d>=0&&a>0;){var e=b.charCodeAt(d--);if(e<128)c[--a]=e;else if(e>127&&e<2048){c[--a]=e&63|128;c[--a]=e>>6|192}else{c[--a]=e&63|128;c[--a]=e>>6&63|128;c[--a]=e>>12|224}}c[--a]=0;b=new Z;for(d=[];a>2;){for(d[0]=0;d[0]==0;)b.V(d);c[--a]=d[0]}c[--a]=2;c[--a]=0;a=new l(c)}if(a==null)return null;a=this.M(a);if(a==null)return null;a=a.toString(16);return(a.length&1)==0 [...]
+$.prototype.M=Da;$.prototype.X=Ca;$.prototype.N=Ea;window.encrypt=function(b,a,c){var d=new $;d.X(c,a);b=d.N(b);d="";for(a=0;a+3<=b.length;a+=3){c=parseInt(b.substring(a,a+3),16);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c>>6)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c&63)}if(a+1==b.length){c=parseInt(b.substring(a,a+1),16);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c<<2)}else if(a+2==b. [...]
+2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt((c&3)<<4)}for(;(d.length&3)>0;)d+="=";return d};
diff --git a/src/gwt/www/offline.htm b/src/gwt/www/offline.htm
new file mode 100644
index 0000000..cb18d80
--- /dev/null
+++ b/src/gwt/www/offline.htm
@@ -0,0 +1,88 @@
+<!--
+#
+# offline.htm
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+<html>
+
+<head>
+
+<title>RStudio Offline</title>
+<link rel="shortcut icon" href="images/favicon.ico" />
+
+<style type="text/css"/>
+
+body {
+ background-color: #fff;
+}
+body, td {
+ font-family: Lucida Grande, Lucida Sans Unicode, Helvetica, sans-serif;
+ font-size: 11pt;
+}
+
+
+
+#banner {
+ background-color: #e0e2e5;
+ margin: 30px 20px 70px 20px;
+ padding: 8px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+#border {
+ width: 300px;
+ border: 4px solid #e0e2e5;
+ padding: 8px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+}
+#image {
+ margin-right: -18px;
+}
+
+#caption {
+ text-align: left;
+ border-bottom: 4px solid #e0e2e5;
+ padding-bottom: 8px;
+ margin-bottom: 8px;
+ margin-right: 10px;
+ font-size: 16px;
+ font-weight: normal;
+ width: 250px;
+}
+
+#detail {
+ color: #aaa;
+ margin-top: 8px;
+ margin-right: 10px;
+}
+
+</style>
+
+</head>
+
+<h3 id="banner"><img src="images/rstudio.png" width="73" height="17" title="RStudio"/></h3>
+
+<table id="border" align="center">
+ <tr>
+ <td><img id="image" src="images/offline.png"/></td>
+ <td>
+ <h2 id="caption">RStudio Temporarily Offline</h2>
+ <div id="detail">RStudio is temporarily offline due to system maintenance. We apologize for the inconvenience, please try again in a few minutes.</div>
+ </td>
+ </tr>
+</table>
+
+
+</body>
+
+</html>
diff --git a/src/gwt/www/progress.htm b/src/gwt/www/progress.htm
new file mode 100644
index 0000000..43ee563
--- /dev/null
+++ b/src/gwt/www/progress.htm
@@ -0,0 +1,33 @@
+<!--
+#
+# progress.htm
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+
+<html>
+
+<head>
+<title>RStudio</title>
+
+<link rel="stylesheet" href="rstudio.css" type="text/css"/>
+<link rel="shortcut icon" href="images/favicon.ico" />
+</head>
+
+<body>
+
+<div style="text-align: center; margin-top: 30px;">
+<img src="images/progress_large.gif"/> <span style="position: relative; top: -10px; margin-left: 5px;"> #message# </span>
+</div>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/gwt/www/rstudio.css b/src/gwt/www/rstudio.css
new file mode 100644
index 0000000..a1b3571
--- /dev/null
+++ b/src/gwt/www/rstudio.css
@@ -0,0 +1,69 @@
+/*
+ * rstudio.css
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+body {
+ background-color: #fff;
+}
+body, td {
+ font-family: Lucida Grande, Lucida Sans Unicode, Helvetica, sans-serif;
+ font-size: 11pt;
+}
+
+
+
+#banner {
+ background-color: #e0e2e5;
+ margin: 30px 20px 70px 20px;
+ padding: 8px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+#border {
+ width: 300px;
+ border: 4px solid #e0e2e5;
+ padding: 8px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+}
+#image {
+ margin-right: -18px;
+}
+
+#iewarn {
+ color: #888;
+ width: 480px;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+}
+
+#caption {
+ text-align: left;
+ border-bottom: 4px solid #e0e2e5;
+ padding-bottom: 8px;
+ margin-bottom: 8px;
+ margin-right: 10px;
+ font-size: 16px;
+ font-weight: normal;
+ width: 250px;
+}
+
+#detail {
+ color: #aaa;
+ margin-top: 8px;
+ margin-right: 10px;
+}
+
+a img {
+ border: none;
+}
\ No newline at end of file
diff --git a/src/gwt/www/templates/encrypted-sign-in.htm b/src/gwt/www/templates/encrypted-sign-in.htm
new file mode 100644
index 0000000..b2688d1
--- /dev/null
+++ b/src/gwt/www/templates/encrypted-sign-in.htm
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+
+<!--
+#
+# encrypted-sign-in.htm
+#
+# Copyright (C) 2009-12 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+-->
+
+<html>
+
+<head>
+
+<title>RStudio Sign In</title>
+<link rel="shortcut icon" href="images/favicon.ico" />
+
+<script language='javascript'>
+function verifyMe()
+{
+ if(document.getElementById('username').value=='')
+ {
+ alert('You must enter a username');
+ document.getElementById('username').focus();
+ return false;
+ }
+ if(document.getElementById('password').value=='')
+ {
+ alert('You must enter a password');
+ document.getElementById('password').focus();
+ return false;
+ }
+ return true;
+}
+</script>
+
+<link rel="stylesheet" href="rstudio.css" type="text/css"/>
+
+<style type="text/css">
+
+body, td {
+ font-size: 12px;
+}
+
+#caption {
+ text-align: center;
+ font-size: 14px;
+ margin-right: 0;
+ width: 100%;
+}
+
+input[type=text], input[type=password] {
+ width: 262px;
+ border: 1px solid #aaa;
+ font-size: 14px;
+ padding: 3px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ outline: none;
+}
+
+#buttonpanel {
+ text-align: center;
+ margin-top: 12px;
+}
+#errorpanel {
+ text-align: center;
+ padding: 0 25% 0 25%;
+ color: red;
+ display: #errorDisplay#;
+ font-weight: bold;
+}
+button.fancy {
+ padding: 0;
+ border: 0 none;
+ margin: 0;
+ outline: none;
+ cursor: pointer;
+ background-color: white;
+}
+button.fancy .left {
+ width: 11px;
+ height: 35px;
+ background: url(images/buttonLeft.png) center right no-repeat;
+}
+button.fancy .inner {
+ color: white;
+ font-weight: bold;
+ font-size: 13px;
+ background: url(images/buttonTile.png) center repeat-x;
+ height: 35px;
+ padding: 5px;
+ padding-top: 1px;
+}
+button.fancy .right {
+ width: 11px;
+ height: 35px;
+ background: url(images/buttonRight.png) center left no-repeat;
+}
+</style>
+
+<script type="text/javascript" src="js/encrypt.min.js"></script>
+<script type="text/javascript">
+function prepare() {
+ if (!verifyMe())
+ return false;
+ try {
+ var payload = document.getElementById('username').value + "\n" +
+ document.getElementById('password').value;
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "#'publicKeyUrl#", true);
+ xhr.onreadystatechange = function() {
+ try {
+ if (xhr.readyState == 4) {
+ if (xhr.status != 200) {
+ var errorMessage;
+ if (xhr.status == 0)
+ errorMessage = "Error: Could not reach server--check your internet connection";
+ else
+ errorMessage = "Error: " + xhr.statusText;
+
+ var errorDiv = document.getElementById('errorpanel');
+ errorDiv.innerHTML = '';
+ var errorp = document.createElement('p');
+ errorDiv.appendChild(errorp);
+ if (typeof(errorp.innerText) == 'undefined')
+ errorp.textContent = errorMessage;
+ else
+ errorp.innerText = errorMessage;
+ errorDiv.style.display = 'block';
+ }
+ else {
+ var response = xhr.responseText;
+ var chunks = response.split(':', 2);
+ var exp = chunks[0];
+ var mod = chunks[1];
+ var encrypted = encrypt(payload, exp, mod);
+ document.getElementById('persist').value = document.getElementById('staySignedIn').checked ? "1" : "0";
+ document.getElementById('package').value = encrypted;
+ document.getElementById('clientPath').value = window.location.pathname;
+ document.realform.submit();
+ }
+ }
+ } catch (exception) {
+ alert("Error: " + exception);
+ }
+ };
+ xhr.send(null);
+ } catch (exception) {
+ alert("Error: " + exception);
+ }
+}
+function submitRealForm() {
+ if (prepare())
+ document.realform.submit();
+}
+</script>
+
+</head>
+
+<h3 id="banner"><img src="images/rstudio.png" width="73" height="17" title="RStudio"/></h3>
+
+<div id="errorpanel">
+<p>Error: #errorMessage#</p>
+</div>
+
+<form action="javascript:void" method="POST" onsubmit="submitRealForm();return false">
+<table id="border" align="center">
+ <tr>
+ <td>
+ <h2 id="caption">Sign in to RStudio</h2>
+ <p>
+ <label for="username">Username:</label><br />
+ <input type='text'
+ name='username'
+ value=''
+ id='username'
+ size='45'/><br />
+ </p>
+ <p>
+ <label for="password">Password:</label><br />
+ <input type='password'
+ name='password'
+ value=''
+ id='password'
+ size='45'/><br />
+ </p>
+ <p>
+ <input type="checkbox" name="staySignedIn" id="staySignedIn"/>
+ <label for="staySignedIn">Stay signed in</label>
+ </p>
+ <div id="buttonpanel"><button class="fancy" type="submit"><table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td class="left"></td>
+ <td class="inner" valign="middle">Sign In</td>
+ <td class="right"></td>
+ </tr>
+ </table></button></div>
+ </td>
+ </tr>
+</table>
+</form>
+
+<form action="#action#" name="realform" method="POST">
+ <input type="hidden" name="persist" id="persist" value=""/>
+ <input type="hidden" name="appUri" value="#appUri#"/>
+ <input type="hidden" name="clientPath" id="clientPath" value=""/>
+ <input id="package" type="hidden" name="v" value=""/>
+</form>
+
+<script type="text/javascript">
+document.getElementById('username').focus();
+</script>
+</body>
+</html>
diff --git a/src/gwt/www/unsupported_browser.htm b/src/gwt/www/unsupported_browser.htm
new file mode 100644
index 0000000..438e46e
--- /dev/null
+++ b/src/gwt/www/unsupported_browser.htm
@@ -0,0 +1,42 @@
+<html>
+
+<head>
+<title>RStudio: Browser Not Supported</title>
+
+<link rel="stylesheet" href="rstudio.css" type="text/css"/>
+<link rel="shortcut icon" href="images/favicon.ico" />
+
+<style type="text/css">
+
+#banner {
+ margin-bottom: 0px;
+}
+
+p {
+ margin: 20px 20px 10px 20px;
+}
+</style>
+
+</head>
+
+<body>
+
+<h3 id="banner"><img src="images/rstudio.png" width="73" height="17" title="RStudio"/></h3>
+
+<p>Your web browser is not supported by RStudio.</p>
+
+<p>RStudio requires one of the following browser versions (or higher):</p>
+
+<ul>
+
+<li><a href="http://www.getfirefox.com/">Firefox 3.5</a></li>
+<li><a href="http://www.apple.com/safari/">Safari 4.0</a></li>
+<li><a href="http://www.google.com/chrome">Google Chrome 5.0</a></li>
+
+</ul>
+
+<p>In addition, RStudio can be used with Internet Explorer if you install the <a href="http://www.google.com/chromeframe">Google Chrome Frame</a> plugin.</p>
+
+</body>
+
+</html>
diff --git a/src/gwt/www/webkit.nocache.html b/src/gwt/www/webkit.nocache.html
new file mode 100644
index 0000000..8424067
--- /dev/null
+++ b/src/gwt/www/webkit.nocache.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+
+<!-- standards mode -->
+<html>
+ <head>
+
+ </head>
+
+ <body>
+ <script>
+
+ var desktop = window.Desktop;
+
+ document.write(desktop.proportionalFont() + ' ');
+ document.write(desktop.fixedWidthFont() + '\n');
+ desktop.collectPendingQuitRequest();
+ desktop.workbenchInitialized();
+ desktop.browseUrl("http://www.foo.bar");
+ document.write(desktop.getOpenFileName('caption', 'dir', 'filter'));
+ document.write(desktop.getSaveFileName('caption', 'dir', 'cpp', true));
+ desktop.undo();
+ desktop.redo();
+ desktop.clipboardCut();
+ desktop.clipboardCopy();
+ desktop.clipboardPaste();
+ document.write(desktop.getUriForPath('/var/foo'));
+
+ document.write(desktop.getRVersion());
+ document.write(desktop.chooseRVersion());
+ document.write('can choose: ' + desktop.canChooseRVersion());
+ document.write('is retina: ' + desktop.isRetina());
+
+ desktop.openMinimalWindow('foo', 'url', 100, 100);
+ desktop.activateSatelliteWindow('foo');
+ desktop.prepareForSatelliteWindow('foo', 100, 100);
+ desktop.copyImageToClipboard(0,0,100,100);
+
+ document.write('metafile: ' + desktop.supportsClipboardMetafile());
+ document.write('showMessageBox: ' + desktop.showMessageBox(1, 'foo', 'bar', 'buttons', 1, 1));
+
+ result = desktop.promptForText("title",
+ "caption",
+ "defaultValue",
+ true,
+ "extraOptionPrompt",
+ true,
+ false,
+ 0,
+ 1);
+ document.write('\n' + result);
+
+ desktop.checkForUpdates();
+ desktop.showAboutDialog();
+ desktop.bringMainFrameToFront();
+
+ document.write('filterText: ' + desktop.filterText('foo') + '\n');
+ desktop.cleanClipboard();
+ desktop.setPendingQuit();
+ desktop.openProjectInNewWindow('foo');
+ desktop.openTerminal('foo', 'workdir', 'extra');
+
+ document.write(desktop.getFontList() + '\n');
+ document.write(desktop.getFixedWidthFont() + '\n');
+ desktop.setFixedWidthFont('foo');
+ document.write(desktop.getZoomLevels() + '\n');
+ document.write(desktop.getZoomLevel() + '\n');
+ desktop.setZoomLevel(2);
+ document.write(desktop.forceFastScrollFactor() + '\n');
+ document.write(desktop.getDesktopSynctexViewer() + '\n');
+
+ desktop.externalSynctexPreview('foo', 3);
+ desktop.externalSynctexView('file', 'src', 1, 1);
+ document.write(desktop.supportsFullscreenMode() + '\n');
+ desktop.toggleFullscreenMode();
+ desktop.showKeyboardShortcutHelp();
+ desktop.launchSession(true);
+ desktop.reloadZoomWindow();
+ desktop.setViewerUrl('foo');
+
+ </script>
+ </body>
+
+ <a href="progress.htm" target="_blank">progress</a>
+
+
+</html>
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/rstudio.git
More information about the debian-science-commits
mailing list